From 66877212bfdf169b2b9b04f9628c84fa45c16ab8 Mon Sep 17 00:00:00 2001 From: gnosygnu Date: Mon, 23 Oct 2017 20:50:50 -0400 Subject: [PATCH] Embeddable: Create core dbs in proper subdirectory --- 100_core/src/gplx/Array_.java | 120 + 100_core/src/gplx/Array__tst.java | 39 + 100_core/src/gplx/Bool_.java | 65 + 100_core/src/gplx/Bool__tst.java | 29 + 100_core/src/gplx/Bry_.java | 1181 +++ 100_core/src/gplx/Bry__tst.java | 307 + 100_core/src/gplx/Bry_bfr.java | 717 ++ 100_core/src/gplx/Bry_bfr_.java | 43 + 100_core/src/gplx/Bry_bfr_tst.java | 247 + 100_core/src/gplx/Bry_find_.java | 402 + 100_core/src/gplx/Bry_find__tst.java | 90 + 100_core/src/gplx/Bry_fmt.java | 87 + 100_core/src/gplx/Bry_split_.java | 155 + 100_core/src/gplx/Bry_split__tst.java | 80 + 100_core/src/gplx/Byte_.java | 68 + 100_core/src/gplx/Byte__tst.java | 33 + 100_core/src/gplx/Byte_ascii.java | 129 + 100_core/src/gplx/Cancelable.java | 20 + 100_core/src/gplx/Cancelable_.java | 29 + 100_core/src/gplx/Char_.java | 72 + 100_core/src/gplx/CompareAble.java | 18 + 100_core/src/gplx/CompareAble_.java | 43 + 100_core/src/gplx/DateAdp.java | 151 + 100_core/src/gplx/DateAdp_.java | 124 + 100_core/src/gplx/DateAdp__tst.java | 86 + 100_core/src/gplx/Datetime_now.java | 67 + 100_core/src/gplx/Decimal_adp.java | 90 + 100_core/src/gplx/Decimal_adp_.java | 69 + 100_core/src/gplx/Decimal_adp__tst.java | 86 + 100_core/src/gplx/Double_.java | 52 + 100_core/src/gplx/Double__tst.java | 27 + 100_core/src/gplx/Enm_.java | 20 + 100_core/src/gplx/Err.java | 89 + 100_core/src/gplx/Err_.java | 77 + 100_core/src/gplx/Err_tst.java | 45 + 100_core/src/gplx/Float_.java | 39 + 100_core/src/gplx/GfoMsg.java | 70 + 100_core/src/gplx/GfoMsgUtl.java | 23 + 100_core/src/gplx/GfoMsg_.java | 268 + 100_core/src/gplx/GfoMsg_tst.java | 49 + 100_core/src/gplx/GfoTemplate.java | 19 + 100_core/src/gplx/GfoTemplateFactory.java | 30 + 100_core/src/gplx/Gfo_evt_itm.java | 17 + 100_core/src/gplx/Gfo_evt_mgr.java | 124 + 100_core/src/gplx/Gfo_evt_mgr_.java | 43 + 100_core/src/gplx/Gfo_evt_mgr_owner.java | 19 + 100_core/src/gplx/Gfo_evt_mgr_tst.java | 67 + 100_core/src/gplx/Gfo_invk.java | 26 + 100_core/src/gplx/Gfo_invk_.java | 41 + 100_core/src/gplx/Gfo_invk_cmd.java | 30 + 100_core/src/gplx/Gfo_invk_cmd_mgr.java | 66 + 100_core/src/gplx/Gfo_invk_cmd_mgr_owner.java | 19 + 100_core/src/gplx/Gfo_invk_root_wkr.java | 19 + 100_core/src/gplx/Gfo_invk_to_str.java | 42 + 100_core/src/gplx/Gfo_log.java | 25 + 100_core/src/gplx/Gfo_log_.java | 34 + 100_core/src/gplx/Gfo_log_bfr.java | 27 + 100_core/src/gplx/Gfo_usr_dlg.java | 33 + 100_core/src/gplx/Gfo_usr_dlg_.java | 72 + 100_core/src/gplx/Gfo_usr_dlg__gui.java | 25 + 100_core/src/gplx/Gfo_usr_dlg__gui_.java | 50 + 100_core/src/gplx/Gfo_usr_dlg__gui_test.java | 27 + 100_core/src/gplx/Gfo_usr_dlg__log.java | 29 + 100_core/src/gplx/Gfo_usr_dlg__log_.java | 33 + 100_core/src/gplx/Gfo_usr_dlg__log_base.java | 119 + 100_core/src/gplx/Gfo_usr_dlg_base.java | 64 + 100_core/src/gplx/GfsCtx.java | 63 + 100_core/src/gplx/Guid_adp.java | 20 + 100_core/src/gplx/Guid_adp_.java | 23 + 100_core/src/gplx/Guid_adp__tst.java | 26 + 100_core/src/gplx/Hash_adp.java | 29 + 100_core/src/gplx/Hash_adp_.java | 36 + 100_core/src/gplx/Hash_adp_bry.java | 205 + 100_core/src/gplx/Hash_adp_bry_tst.java | 66 + 100_core/src/gplx/Int_.java | 220 + 100_core/src/gplx/Int__tst.java | 97 + 100_core/src/gplx/Int_ary_.java | 163 + 100_core/src/gplx/Int_ary__tst.java | 58 + 100_core/src/gplx/Internal.java | 17 + 100_core/src/gplx/Io_mgr.java | 176 + 100_core/src/gplx/Io_mgr__tst.java | 98 + 100_core/src/gplx/Io_url.java | 89 + 100_core/src/gplx/Io_url_.java | 111 + 100_core/src/gplx/Io_url__tst.java | 30 + 100_core/src/gplx/Keyval.java | 30 + 100_core/src/gplx/Keyval_.java | 105 + 100_core/src/gplx/Keyval_hash.java | 40 + 100_core/src/gplx/Keyval_list.java | 34 + 100_core/src/gplx/List_adp.java | 75 + 100_core/src/gplx/List_adp_.java | 53 + 100_core/src/gplx/List_adp_base.java | 173 + 100_core/src/gplx/List_adp_tst.java | 220 + 100_core/src/gplx/Long_.java | 110 + 100_core/src/gplx/Long__tst.java | 47 + 100_core/src/gplx/Math_.java | 72 + 100_core/src/gplx/Math__tst.java | 59 + 100_core/src/gplx/New.java | 17 + 100_core/src/gplx/ObjAry.java | 34 + 100_core/src/gplx/Object_.java | 59 + 100_core/src/gplx/Object__tst.java | 34 + 100_core/src/gplx/Ordered_hash.java | 29 + 100_core/src/gplx/Ordered_hash_.java | 28 + 100_core/src/gplx/Ordered_hash_base.java | 101 + 100_core/src/gplx/Ordered_hash_tst.java | 37 + 100_core/src/gplx/RandomAdp.java | 22 + 100_core/src/gplx/RandomAdp_.java | 28 + 100_core/src/gplx/Rls_able.java | 19 + 100_core/src/gplx/Rls_able_.java | 24 + 100_core/src/gplx/Short_.java | 23 + 100_core/src/gplx/String_.java | 550 ++ 100_core/src/gplx/String__tst.java | 182 + 100_core/src/gplx/Tfds.java | 234 + 100_core/src/gplx/TfdsTstr_fxt.java | 97 + 100_core/src/gplx/Time_span.java | 81 + 100_core/src/gplx/Time_span_.java | 161 + 100_core/src/gplx/To_str_able.java | 19 + 100_core/src/gplx/To_str_able_.java | 19 + 100_core/src/gplx/Type_.java | 56 + 100_core/src/gplx/Type_ids_.java | 56 + 100_core/src/gplx/UsrDlg.java | 49 + 100_core/src/gplx/UsrDlg_.java | 19 + 100_core/src/gplx/UsrMsg.java | 66 + 100_core/src/gplx/UsrMsgWkr.java | 48 + 100_core/src/gplx/UsrMsgWkr_.java | 25 + 100_core/src/gplx/UsrMsgWkr_console.java | 33 + 100_core/src/gplx/UsrMsgWkr_test.java | 36 + 100_core/src/gplx/Virtual.java | 17 + 100_core/src/gplx/Yn.java | 77 + 100_core/src/gplx/core/bits/Bitmask_.java | 23 + 100_core/src/gplx/core/brys/Bfr_arg.java | 4 + 100_core/src/gplx/core/brys/Bfr_arg_.java | 18 + .../src/gplx/core/brys/Bfr_arg_clearable.java | 5 + 100_core/src/gplx/core/brys/Bry_bfr_able.java | 4 + .../src/gplx/core/brys/Bry_bfr_able_.java | 20 + 100_core/src/gplx/core/brys/Bry_bfr_mkr.java | 25 + .../src/gplx/core/brys/Bry_bfr_mkr_mgr.java | 83 + .../src/gplx/core/brys/Bry_bfr_mkr_tst.java | 40 + 100_core/src/gplx/core/brys/Bry_err_wkr.java | 36 + 100_core/src/gplx/core/brys/Bry_rdr.java | 214 + 100_core/src/gplx/core/brys/Bry_rdr_old.java | 152 + 100_core/src/gplx/core/brys/Bry_rdr_tst.java | 39 + .../src/gplx/core/brys/Bry_split_wkr.java | 4 + .../src/gplx/core/brys/args/Bfr_arg__bry.java | 26 + .../gplx/core/brys/args/Bfr_arg__bry_ary.java | 9 + .../gplx/core/brys/args/Bfr_arg__bry_fmt.java | 21 + .../core/brys/args/Bfr_arg__bry_fmtr.java | 11 + .../gplx/core/brys/args/Bfr_arg__byte.java | 6 + .../core/brys/args/Bfr_arg__decimal_int.java | 8 + .../src/gplx/core/brys/args/Bfr_arg__int.java | 11 + .../gplx/core/brys/args/Bfr_arg__time.java | 38 + .../core/brys/args/Bfr_arg__time_tst.java | 25 + .../src/gplx/core/brys/fmtrs/Bry_fmtr.java | 249 + .../core/brys/fmtrs/Bry_fmtr_eval_mgr.java | 5 + .../core/brys/fmtrs/Bry_fmtr_eval_mgr_.java | 10 + .../brys/fmtrs/Bry_fmtr_eval_mgr_gfs.java | 9 + .../gplx/core/brys/fmtrs/Bry_fmtr_itm.java | 16 + .../gplx/core/brys/fmtrs/Bry_fmtr_tst.java | 57 + .../gplx/core/brys/fmtrs/Bry_fmtr_vals.java | 15 + .../src/gplx/core/brys/fmts/Bfr_fmt_arg.java | 7 + .../src/gplx/core/brys/fmts/Bry_fmt_itm.java | 15 + .../gplx/core/brys/fmts/Bry_fmt_parser_.java | 76 + .../src/gplx/core/brys/fmts/Bry_fmt_tst.java | 35 + .../core/brys/fmts/Bry_keys_parser_tst.java | 15 + .../src/gplx/core/btries/Btrie_bwd_mgr.java | 103 + .../gplx/core/btries/Btrie_bwd_mgr_tst.java | 70 + .../src/gplx/core/btries/Btrie_fast_mgr.java | 177 + .../gplx/core/btries/Btrie_fast_mgr_tst.java | 68 + .../src/gplx/core/btries/Btrie_itm_stub.java | 6 + 100_core/src/gplx/core/btries/Btrie_mgr.java | 8 + 100_core/src/gplx/core/btries/Btrie_rv.java | 12 + .../src/gplx/core/btries/Btrie_slim_itm.java | 115 + .../gplx/core/btries/Btrie_slim_itm_tst.java | 32 + .../src/gplx/core/btries/Btrie_slim_mgr.java | 205 + .../gplx/core/btries/Btrie_slim_mgr_tst.java | 75 + .../src/gplx/core/btries/Btrie_u8_itm.java | 51 + .../src/gplx/core/btries/Btrie_u8_mgr.java | 82 + .../src/gplx/core/consoles/Console_adp.java | 11 + .../src/gplx/core/consoles/Console_adp_.java | 15 + .../gplx/core/consoles/Console_adp__mem.java | 33 + .../gplx/core/consoles/Console_adp__sys.java | 50 + .../src/gplx/core/criterias/Criteria.java | 7 + .../src/gplx/core/criterias/Criteria_.java | 36 + .../gplx/core/criterias/Criteria_between.java | 23 + .../core/criterias/Criteria_bool_base.java | 31 + .../gplx/core/criterias/Criteria_comp.java | 18 + .../src/gplx/core/criterias/Criteria_eq.java | 17 + .../src/gplx/core/criterias/Criteria_fld.java | 54 + .../src/gplx/core/criterias/Criteria_in.java | 31 + .../core/criterias/Criteria_ioItm_tst.java | 33 + .../gplx/core/criterias/Criteria_ioMatch.java | 20 + .../gplx/core/criterias/Criteria_like.java | 17 + .../src/gplx/core/criterias/Criteria_not.java | 10 + .../src/gplx/core/criterias/Criteria_tst.java | 75 + 100_core/src/gplx/core/encoders/B85_fp_.java | 39 + .../src/gplx/core/encoders/B85_fp__tst.java | 14 + 100_core/src/gplx/core/encoders/Base85_.java | 49 + .../src/gplx/core/encoders/Base85__tst.java | 44 + .../src/gplx/core/encoders/Gfo_hzip_int_.java | 46 + 100_core/src/gplx/core/encoders/Hex_utl_.java | 162 + .../src/gplx/core/encoders/Hex_utl__tst.java | 74 + 100_core/src/gplx/core/envs/Env_.java | 31 + 100_core/src/gplx/core/envs/Jar_adp_.java | 16 + 100_core/src/gplx/core/envs/Op_sys.java | 79 + 100_core/src/gplx/core/envs/Op_sys_.java | 17 + 100_core/src/gplx/core/envs/Process_adp.java | 380 + .../src/gplx/core/envs/Process_adp_tst.java | 15 + 100_core/src/gplx/core/envs/Runtime_.java | 17 + 100_core/src/gplx/core/envs/System_.java | 46 + 100_core/src/gplx/core/errs/Err_msg.java | 32 + 100_core/src/gplx/core/gfo_ndes/GfoFld.java | 12 + .../src/gplx/core/gfo_ndes/GfoFldList.java | 11 + .../src/gplx/core/gfo_ndes/GfoFldList_.java | 46 + 100_core/src/gplx/core/gfo_ndes/GfoNde.java | 55 + .../src/gplx/core/gfo_ndes/GfoNdeFxt.java | 19 + .../src/gplx/core/gfo_ndes/GfoNdeList.java | 10 + .../src/gplx/core/gfo_ndes/GfoNdeList_.java | 23 + 100_core/src/gplx/core/gfo_ndes/GfoNde_.java | 30 + .../src/gplx/core/gfo_regys/GfoMsgParser.java | 4 + 100_core/src/gplx/core/gfo_regys/GfoRegy.java | 78 + .../src/gplx/core/gfo_regys/GfoRegyItm.java | 14 + .../core/gfo_regys/GfoRegy_RegDir_tst.java | 44 + .../core/gfo_regys/GfoRegy_basic_tst.java | 14 + .../src/gplx/core/interfaces/InjectAble.java | 4 + .../src/gplx/core/interfaces/ParseAble.java | 4 + .../src/gplx/core/interfaces/SrlAble.java | 4 + .../src/gplx/core/interfaces/SrlAble_.java | 48 + .../gplx/core/interfaces/SrlAble__tst.java | 49 + .../src/gplx/core/intls/Gfo_case_itm.java | 7 + .../src/gplx/core/intls/Gfo_case_mgr.java | 5 + .../src/gplx/core/intls/Gfo_case_mgr_.java | 4 + 100_core/src/gplx/core/intls/Utf16_.java | 120 + 100_core/src/gplx/core/intls/Utf16__tst.java | 43 + 100_core/src/gplx/core/intls/Utf8_.java | 128 + 100_core/src/gplx/core/intls/Utf8__tst.java | 52 + 100_core/src/gplx/core/ios/IoEngine.java | 139 + 100_core/src/gplx/core/ios/IoEngineFxt.java | 37 + 100_core/src/gplx/core/ios/IoEnginePool.java | 17 + 100_core/src/gplx/core/ios/IoEngine_.java | 13 + 100_core/src/gplx/core/ios/IoEngine_base.java | 42 + .../src/gplx/core/ios/IoEngine_memory.java | 186 + .../src/gplx/core/ios/IoEngine_system.java | 723 ++ .../gplx/core/ios/IoEngine_xrg_deleteDir.java | 17 + .../gplx/core/ios/IoEngine_xrg_deleteFil.java | 13 + .../core/ios/IoEngine_xrg_downloadFil.java | 54 + .../ios/IoEngine_xrg_fil_affects1_base.java | 8 + .../core/ios/IoEngine_xrg_loadFilStr.java | 27 + .../gplx/core/ios/IoEngine_xrg_openRead.java | 19 + .../gplx/core/ios/IoEngine_xrg_openWrite.java | 15 + .../gplx/core/ios/IoEngine_xrg_queryDir.java | 38 + .../core/ios/IoEngine_xrg_recycleFil.java | 42 + .../core/ios/IoEngine_xrg_saveFilStr.java | 16 + .../gplx/core/ios/IoEngine_xrg_xferDir.java | 20 + .../gplx/core/ios/IoEngine_xrg_xferFil.java | 17 + 100_core/src/gplx/core/ios/IoErr.java | 13 + 100_core/src/gplx/core/ios/IoItmAttrib.java | 8 + 100_core/src/gplx/core/ios/IoItmClassXtn.java | 16 + 100_core/src/gplx/core/ios/IoItmDir.java | 54 + 100_core/src/gplx/core/ios/IoItmDir_.java | 38 + 100_core/src/gplx/core/ios/IoItmFil.java | 26 + 100_core/src/gplx/core/ios/IoItmFil_.java | 14 + 100_core/src/gplx/core/ios/IoItmFil_mem.java | 22 + 100_core/src/gplx/core/ios/IoItmHash.java | 26 + 100_core/src/gplx/core/ios/IoItmList.java | 59 + 100_core/src/gplx/core/ios/IoItm_base.java | 37 + 100_core/src/gplx/core/ios/IoItm_base_.java | 9 + 100_core/src/gplx/core/ios/IoItm_fxt.java | 17 + 100_core/src/gplx/core/ios/IoRecycleBin.java | 43 + 100_core/src/gplx/core/ios/IoUrlInfo.java | 228 + 100_core/src/gplx/core/ios/IoUrlInfoRegy.java | 37 + 100_core/src/gplx/core/ios/IoUrlInfo_.java | 17 + 100_core/src/gplx/core/ios/IoUrlTypeRegy.java | 59 + 100_core/src/gplx/core/ios/IoZipWkr.java | 24 + 100_core/src/gplx/core/ios/IoZipWkr_tst.java | 11 + .../src/gplx/core/ios/Io_download_fmt.java | 77 + .../gplx/core/ios/Io_download_fmt_tst.java | 56 + 100_core/src/gplx/core/ios/Io_fil.java | 21 + 100_core/src/gplx/core/ios/Io_fil_mkr.java | 7 + 100_core/src/gplx/core/ios/Io_size_.java | 155 + 100_core/src/gplx/core/ios/Io_size__tst.java | 68 + .../src/gplx/core/ios/Io_url_obj_ref.java | 13 + .../src/gplx/core/ios/drives/Io_drive.java | 5 + .../src/gplx/core/ios/drives/Io_drive_.java | 4 + .../gplx/core/ios/drives/Io_drive__jre.java | 5 + .../src/gplx/core/ios/loaders/Io_loader.java | 4 + .../src/gplx/core/ios/streams/IoStream.java | 16 + .../src/gplx/core/ios/streams/IoStream_.java | 47 + .../gplx/core/ios/streams/IoStream_base.java | 121 + .../gplx/core/ios/streams/IoStream_mem.java | 53 + .../core/ios/streams/IoStream_mem_tst.java | 13 + .../gplx/core/ios/streams/IoStream_mock.java | 28 + .../core/ios/streams/IoStream_mock_tst.java | 31 + .../core/ios/streams/IoStream_stream_rdr.java | 22 + .../gplx/core/ios/streams/Io_stream_rdr.java | 13 + .../gplx/core/ios/streams/Io_stream_rdr_.java | 85 + .../core/ios/streams/Io_stream_rdr__tst.java | 39 + .../gplx/core/ios/streams/Io_stream_tid_.java | 41 + .../gplx/core/ios/streams/Io_stream_wtr.java | 11 + .../gplx/core/ios/streams/Io_stream_wtr_.java | 51 + .../ios/streams/rdrs/Io_stream_rdr__adp.java | 24 + .../ios/streams/rdrs/Io_stream_rdr__base.java | 31 + .../streams/rdrs/Io_stream_rdr__bzip2.java | 12 + .../ios/streams/rdrs/Io_stream_rdr__gzip.java | 27 + .../ios/streams/rdrs/Io_stream_rdr__noop.java | 13 + .../ios/streams/rdrs/Io_stream_rdr__raw.java | 20 + .../ios/streams/rdrs/Io_stream_rdr__xz.java | 8 + .../ios/streams/rdrs/Io_stream_rdr__zip.java | 37 + .../ios/streams/wtrs/Io_stream_wtr__base.java | 43 + .../streams/wtrs/Io_stream_wtr__bzip2.java | 8 + .../ios/streams/wtrs/Io_stream_wtr__gzip.java | 8 + .../ios/streams/wtrs/Io_stream_wtr__raw.java | 38 + .../ios/streams/wtrs/Io_stream_wtr__xz.java | 8 + .../ios/streams/wtrs/Io_stream_wtr__zip.java | 51 + .../ios/zips/Io_zip_compress_cmd__jre.java | 57 + .../core/ios/zips/Io_zip_decompress_cmd.java | 9 + .../core/ios/zips/Io_zip_decompress_cmd_.java | 4 + .../ios/zips/Io_zip_decompress_cmd__base.java | 57 + .../ios/zips/Io_zip_decompress_cmd__jre.java | 89 + .../src/gplx/core/ios/zips/Io_zip_mgr.java | 8 + .../gplx/core/ios/zips/Io_zip_mgr_base.java | 103 + .../gplx/core/ios/zips/Io_zip_mgr_mok.java | 19 + .../gplx/core/ios/zips/Io_zip_mgr_tst.java | 14 + 100_core/src/gplx/core/js/Js_wtr.java | 90 + 100_core/src/gplx/core/js/Js_wtr_tst.java | 24 + .../src/gplx/core/lists/ComparerAble.java | 3 + .../src/gplx/core/lists/ComparerAble_.java | 4 + 100_core/src/gplx/core/lists/EnumerAble.java | 2 + .../src/gplx/core/lists/Hash_adp_base.java | 35 + .../src/gplx/core/lists/Hash_adp_list.java | 23 + .../src/gplx/core/lists/Iterator_null.java | 7 + .../src/gplx/core/lists/Iterator_objAry.java | 8 + .../gplx/core/lists/List_adp__getable.java | 5 + .../src/gplx/core/lists/List_adp_sorter.java | 48 + .../gplx/core/lists/List_adp_sorter_tst.java | 20 + 100_core/src/gplx/core/lists/StackAdp.java | 9 + 100_core/src/gplx/core/lists/StackAdp_.java | 24 + .../src/gplx/core/lists/StackAdp_tst.java | 16 + .../src/gplx/core/lists/rings/Ring__long.java | 37 + .../core/lists/rings/Ring__long__tst.java | 24 + .../gplx/core/lists/rings/Ring__string.java | 43 + .../core/lists/rings/Ring__string__tst.java | 29 + .../src/gplx/core/log_msgs/Gfo_msg_data.java | 17 + .../src/gplx/core/log_msgs/Gfo_msg_grp.java | 29 + .../src/gplx/core/log_msgs/Gfo_msg_grp_.java | 13 + .../src/gplx/core/log_msgs/Gfo_msg_itm.java | 41 + .../src/gplx/core/log_msgs/Gfo_msg_itm_.java | 10 + .../src/gplx/core/log_msgs/Gfo_msg_log.java | 43 + .../src/gplx/core/log_msgs/Gfo_msg_obj.java | 5 + .../src/gplx/core/log_msgs/Gfo_msg_root.java | 63 + .../gplx/core/log_msgs/Gfo_msg_root_tst.java | 45 + .../src/gplx/core/logs/Gfo_log__base.java | 27 + .../src/gplx/core/logs/Gfo_log__file.java | 46 + 100_core/src/gplx/core/logs/Gfo_log__mem.java | 9 + 100_core/src/gplx/core/logs/Gfo_log_itm.java | 17 + .../src/gplx/core/logs/Gfo_log_itm_wtr.java | 4 + .../gplx/core/logs/Gfo_log_itm_wtr__csv.java | 53 + .../src/gplx/core/memorys/Gfo_memory_itm.java | 4 + .../src/gplx/core/memorys/Gfo_memory_mgr.java | 16 + .../gplx/core/primitives/Bool_obj_ref.java | 19 + .../gplx/core/primitives/Bool_obj_val.java | 17 + .../src/gplx/core/primitives/Bry_obj_ref.java | 28 + .../gplx/core/primitives/Byte_obj_ref.java | 14 + .../gplx/core/primitives/Byte_obj_val.java | 12 + .../gplx/core/primitives/Double_obj_val.java | 15 + 100_core/src/gplx/core/primitives/EnmMgr.java | 55 + .../gplx/core/primitives/EnmParser_tst.java | 43 + .../src/gplx/core/primitives/Int_list.java | 38 + .../src/gplx/core/primitives/Int_obj_ref.java | 20 + .../src/gplx/core/primitives/Int_obj_val.java | 9 + .../gplx/core/primitives/String_obj_ref.java | 13 + .../gplx/core/primitives/String_obj_val.java | 10 + 100_core/src/gplx/core/progs/Gfo_prog_ui.java | 9 + .../src/gplx/core/progs/Gfo_prog_ui_.java | 32 + .../src/gplx/core/progs/Gfo_resume_wkr.java | 36 + .../src/gplx/core/security/Hash_algo.java | 8 + .../src/gplx/core/security/Hash_algo_.java | 130 + .../core/security/Hash_algo__md5__tst.java | 24 + .../core/security/Hash_algo__sha1__tst.java | 16 + .../security/Hash_algo__sha2_256__tst.java | 16 + .../core/security/Hash_algo__tth_192.java | 990 ++ .../security/Hash_algo__tth_192__tst.java | 19 + .../security/Hash_algo__tth_192_tree_tst.java | 48 + .../core/security/Hash_console_wtr_tst.java | 24 + 100_core/src/gplx/core/stores/DataRdr.java | 34 + 100_core/src/gplx/core/stores/DataRdr_.java | 57 + .../src/gplx/core/stores/DataRdr_base.java | 198 + .../src/gplx/core/stores/DataRdr_mem.java | 62 + 100_core/src/gplx/core/stores/DataWtr.java | 16 + 100_core/src/gplx/core/stores/DataWtr_.java | 31 + .../src/gplx/core/stores/DataWtr_base.java | 35 + 100_core/src/gplx/core/stores/GfoNdeRdr.java | 5 + 100_core/src/gplx/core/stores/GfoNdeRdr_.java | 32 + 100_core/src/gplx/core/stores/SrlMgr.java | 18 + 100_core/src/gplx/core/stores/SrlObj.java | 5 + .../src/gplx/core/stores/xmls/XmlDataRdr.java | 60 + .../gplx/core/stores/xmls/XmlDataRdr_.java | 8 + .../gplx/core/stores/xmls/XmlDataWtr_.java | 96 + .../src/gplx/core/strings/String_bldr.java | 99 + .../src/gplx/core/strings/String_bldr_.java | 5 + 100_core/src/gplx/core/tests/Gftest.java | 199 + .../src/gplx/core/tests/PerfLogMgr_fxt.java | 49 + .../src/gplx/core/tests/TfdsEqListItmStr.java | 4 + .../src/gplx/core/texts/Base32Converter.java | 82 + .../src/gplx/core/texts/Base64Converter.java | 62 + .../gplx/core/texts/BaseXXConverter_tst.java | 41 + 100_core/src/gplx/core/texts/CharStream.java | 50 + .../src/gplx/core/texts/CharStream_tst.java | 44 + .../gplx/core/texts/RegxPatn_cls_ioMatch.java | 18 + .../core/texts/RegxPatn_cls_ioMatch_.java | 36 + .../core/texts/RegxPatn_cls_ioMatch_tst.java | 41 + .../gplx/core/texts/RegxPatn_cls_like.java | 15 + .../gplx/core/texts/RegxPatn_cls_like_.java | 46 + .../core/texts/RegxPatn_cls_like_tst.java | 69 + .../src/gplx/core/texts/StringTableBldr.java | 40 + .../gplx/core/texts/StringTableBldr_tst.java | 42 + .../src/gplx/core/texts/StringTableCol.java | 22 + .../gplx/core/texts/StringTableColAlign.java | 12 + 100_core/src/gplx/core/threads/Gfo_lock.java | 11 + .../src/gplx/core/threads/Thread_adp.java | 29 + .../src/gplx/core/threads/Thread_adp_.java | 17 + .../src/gplx/core/threads/Thread_adp_mgr.java | 44 + .../gplx/core/threads/Thread_halt_cbk.java | 4 + .../gplx/core/threads/Thread_halt_cbk_.java | 7 + .../gplx/core/threads/Thread_halt_wkr.java | 46 + .../threads/poolables/Gfo_poolable_itm.java | 5 + .../threads/poolables/Gfo_poolable_mgr.java | 81 + .../threads/poolables/Gfo_poolable_mgr_.java | 5 + .../poolables/Gfo_poolable_mgr_tst.java | 66 + .../threads/utils/Gfo_blocking_queue.java | 18 + .../threads/utils/Gfo_countdown_latch.java | 15 + .../src/gplx/core/times/DateAdp_parser.java | 103 + .../gplx/core/times/DateAdp_parser_tst.java | 17 + .../gplx/core/times/Time_span__basic_tst.java | 70 + .../gplx/core/times/Time_span__parse_tst.java | 37 + .../core/times/Time_span__to_str_tst.java | 42 + .../src/gplx/core/type_xtns/BoolClassXtn.java | 24 + .../src/gplx/core/type_xtns/ByteClassXtn.java | 11 + .../src/gplx/core/type_xtns/ClassXtn.java | 12 + .../src/gplx/core/type_xtns/ClassXtnPool.java | 22 + .../gplx/core/type_xtns/ClassXtn_base.java | 11 + .../gplx/core/type_xtns/DateAdpClassXtn.java | 11 + .../core/type_xtns/DateAdpClassXtn_tst.java | 11 + .../core/type_xtns/DecimalAdpClassXtn.java | 10 + .../gplx/core/type_xtns/DoubleClassXtn.java | 9 + .../gplx/core/type_xtns/FloatClassXtn.java | 9 + .../src/gplx/core/type_xtns/IntClassXtn.java | 11 + .../gplx/core/type_xtns/IoUrlClassXtn.java | 12 + .../src/gplx/core/type_xtns/LongClassXtn.java | 10 + .../gplx/core/type_xtns/ObjectClassXtn.java | 10 + .../gplx/core/type_xtns/StringClassXtn.java | 11 + .../core/type_xtns/TimeSpanAdpClassXtn.java | 11 + .../src/gplx/langs/dsvs/DsvDataRdrOpts.java | 9 + 100_core/src/gplx/langs/dsvs/DsvDataRdr_.java | 231 + .../langs/dsvs/DsvDataRdr_csv_dat_tst.java | 199 + .../langs/dsvs/DsvDataRdr_dsv_dat_tst.java | 53 + .../langs/dsvs/DsvDataRdr_dsv_hdr_tst.java | 65 + .../langs/dsvs/DsvDataRdr_dsv_misc_tst.java | 59 + .../langs/dsvs/DsvDataRdr_layout_tst.java | 114 + 100_core/src/gplx/langs/dsvs/DsvDataWtr.java | 98 + 100_core/src/gplx/langs/dsvs/DsvDataWtr_.java | 14 + .../gplx/langs/dsvs/DsvDataWtr_csv_tst.java | 83 + .../gplx/langs/dsvs/DsvDataWtr_tbls_tst.java | 56 + .../src/gplx/langs/dsvs/DsvHeaderList.java | 27 + .../src/gplx/langs/dsvs/DsvStoreLayout.java | 34 + 100_core/src/gplx/langs/dsvs/DsvSymbols.java | 35 + 100_core/src/gplx/langs/gfs/GfsCore.java | 73 + 100_core/src/gplx/langs/gfs/GfsCoreHelp.java | 56 + 100_core/src/gplx/langs/gfs/GfsCore_.java | 74 + 100_core/src/gplx/langs/gfs/GfsLibIni.java | 4 + .../src/gplx/langs/gfs/GfsLibIni_core.java | 17 + 100_core/src/gplx/langs/gfs/GfsRegy.java | 37 + 100_core/src/gplx/langs/gfs/GfsTypeNames.java | 11 + 100_core/src/gplx/langs/gfs/Gfs_Date_tst.java | 25 + .../langs/htmls/Url_encoder_interface.java | 5 + .../htmls/Url_encoder_interface_same.java | 6 + .../gplx/langs/htmls/entitys/Gfh_entity_.java | 18 + .../langs/htmls/entitys/Gfh_entity_itm.java | 40 + .../langs/htmls/entitys/Gfh_entity_trie.java | 300 + 100_core/src/gplx/langs/regxs/Regx_adp.java | 51 + 100_core/src/gplx/langs/regxs/Regx_adp_.java | 26 + .../src/gplx/langs/regxs/Regx_adp__tst.java | 76 + 100_core/src/gplx/langs/regxs/Regx_bldr.java | 45 + 100_core/src/gplx/langs/regxs/Regx_group.java | 20 + 100_core/src/gplx/langs/regxs/Regx_match.java | 11 + 100_core/src/gplx/langs/regxs/Regx_rslt.java | 29 + 100_core/src/gplx/langs/xmls/HierStrBldr.java | 39 + .../src/gplx/langs/xmls/HierStrBldr_tst.java | 30 + 100_core/src/gplx/langs/xmls/XmlAtr.java | 7 + 100_core/src/gplx/langs/xmls/XmlAtrList.java | 20 + 100_core/src/gplx/langs/xmls/XmlDoc.java | 7 + 100_core/src/gplx/langs/xmls/XmlDoc_.java | 36 + 100_core/src/gplx/langs/xmls/XmlDoc_tst.java | 54 + .../src/gplx/langs/xmls/XmlFileSplitter.java | 124 + .../gplx/langs/xmls/XmlFileSplitterOpts.java | 10 + .../gplx/langs/xmls/XmlFileSplitter_tst.java | 71 + 100_core/src/gplx/langs/xmls/XmlNde.java | 34 + 100_core/src/gplx/langs/xmls/XmlNdeList.java | 18 + 100_core/src/gplx/langs/xmls/XmlSplitRdr.java | 34 + 100_core/src/gplx/langs/xmls/XmlSplitWtr.java | 23 + 100_core/src/gplx/langs/xmls/Xpath_.java | 89 + 100_core/src/gplx/langs/xmls/Xpath__tst.java | 27 + 100_core/tst/gplx/GfoMsg_rdr_tst.java | 55 + 100_core/tst/gplx/GfoTreeBldr_fxt.java | 30 + .../core/ios/IoEngine_dir_basic_base.java | 62 + .../ios/IoEngine_dir_basic_memory_tst.java | 7 + .../ios/IoEngine_dir_basic_system_tst.java | 11 + .../gplx/core/ios/IoEngine_dir_deep_base.java | 109 + .../ios/IoEngine_dir_deep_memory_tst.java | 19 + .../ios/IoEngine_dir_deep_system_tst.java | 8 + .../core/ios/IoEngine_fil_basic_base.java | 159 + .../ios/IoEngine_fil_basic_memory_tst.java | 40 + .../ios/IoEngine_fil_basic_system_tst.java | 41 + .../gplx/core/ios/IoEngine_fil_xfer_base.java | 89 + .../ios/IoEngine_fil_xfer_memory_tst.java | 11 + .../ios/IoEngine_fil_xfer_system_tst.java | 11 + .../core/ios/IoEngine_stream_xfer_tst.java | 32 + .../core/ios/IoEngine_xrg_queryDir_tst.java | 48 + .../core/ios/IoEngine_xrg_recycleFil_tst.java | 15 + .../ios/IoItmDir_FetchDeepOrNull_tst.java | 23 + .../gplx/core/ios/IoUrlInfo_alias_tst.java | 41 + 100_core/tst/gplx/core/ios/IoUrl_lnx_tst.java | 38 + 100_core/tst/gplx/core/ios/IoUrl_map_tst.java | 14 + 100_core/tst/gplx/core/ios/IoUrl_wnt_tst.java | 81 + .../gplx/core/stores/GfoNdeRdr_read_tst.java | 31 + .../tst/gplx/core/stores/GfoNdeRdr_tst.java | 172 + .../gplx/core/stores/xmls/XmlDataRdr_tst.java | 85 + .../gplx/core/stores/xmls/XmlDataWtr_tst.java | 78 + 110_gfml/src_100_tkn/gplx/gfml/GfmlLxr.java | 32 + 110_gfml/src_100_tkn/gplx/gfml/GfmlLxr_.java | 215 + 110_gfml/src_100_tkn/gplx/gfml/GfmlObj.java | 27 + .../src_100_tkn/gplx/gfml/GfmlObjList.java | 23 + 110_gfml/src_100_tkn/gplx/gfml/GfmlTkn.java | 44 + 110_gfml/src_100_tkn/gplx/gfml/GfmlTkn_.java | 63 + 110_gfml/src_100_tkn/gplx/gfml/GfmlTrie.java | 112 + .../src_100_tkn/gplx/gfml/IntObjHash.java | 75 + 110_gfml/src_200_type/gplx/gfml/GfmlFld.java | 38 + .../src_200_type/gplx/gfml/GfmlFldList.java | 32 + 110_gfml/src_200_type/gplx/gfml/GfmlType.java | 51 + .../gplx/gfml/GfmlTypeCompiler.java | 103 + .../src_200_type/gplx/gfml/GfmlTypeHash.java | 47 + .../src_200_type/gplx/gfml/GfmlTypeMakr.java | 65 + .../src_200_type/gplx/gfml/GfmlTypeMgr.java | 138 + 110_gfml/src_300_gdoc/gplx/gfml/GfmlAtr.java | 74 + 110_gfml/src_300_gdoc/gplx/gfml/GfmlDoc.java | 43 + .../src_300_gdoc/gplx/gfml/GfmlDocLxrs.java | 128 + .../src_300_gdoc/gplx/gfml/GfmlDocPos.java | 69 + .../src_300_gdoc/gplx/gfml/GfmlDocWtr_.java | 38 + 110_gfml/src_300_gdoc/gplx/gfml/GfmlDoc_.java | 44 + 110_gfml/src_300_gdoc/gplx/gfml/GfmlItm.java | 27 + .../src_300_gdoc/gplx/gfml/GfmlItmHnds.java | 22 + .../src_300_gdoc/gplx/gfml/GfmlItmKeys.java | 59 + 110_gfml/src_300_gdoc/gplx/gfml/GfmlNde.java | 111 + .../src_300_gdoc/gplx/gfml/GfmlScopeItm.java | 71 + .../src_400_pragma/gplx/gfml/GfmlPragma.java | 48 + .../gplx/gfml/GfmlPragmaDefault.java | 131 + .../gplx/gfml/GfmlPragmaLxrFrm.java | 62 + .../gplx/gfml/GfmlPragmaLxrSym.java | 54 + .../gplx/gfml/GfmlPragmaType.java | 77 + .../gplx/gfml/GfmlPragmaVar.java | 76 + .../src_400_pragma/gplx/gfml/GfmlVarCtx.java | 60 + .../src_400_pragma/gplx/gfml/GfmlVarItm.java | 30 + .../src_400_pragma/gplx/gfml/GfmlVarTkn.java | 37 + .../src_500_build/gplx/gfml/GfmlBldr.java | 72 + .../src_500_build/gplx/gfml/GfmlBldrCmd.java | 38 + .../src_500_build/gplx/gfml/GfmlBldrCmds.java | 126 + .../src_500_build/gplx/gfml/GfmlBldr_.java | 42 + .../src_500_build/gplx/gfml/GfmlFrame.java | 33 + .../src_500_build/gplx/gfml/GfmlFrame_.java | 85 + .../gplx/gfml/GfmlFrame_nde.java | 248 + .../gplx/gfml/GfmlFrame_ndeTknMgr.java | 88 + .../gplx/gfml/GfmlStringHighlighter.java | 101 + .../src_600_rdrWtr/gplx/gfml/GfmlDataNde.java | 100 + .../src_600_rdrWtr/gplx/gfml/GfmlDataRdr.java | 46 + .../gplx/gfml/GfmlDataRdr_base.java | 58 + .../src_600_rdrWtr/gplx/gfml/GfmlDataWtr.java | 97 + .../gplx/gfml/GfmlDataWtrOpts.java | 25 + .../gplx/gfml/GfoMsgParser_gfml.java | 21 + .../src_600_rdrWtr/gplx/gfml/SqlConsts.java | 40 + 110_gfml/src_600_rdrWtr/gplx/gfml/SqlDoc.java | 142 + 110_gfml/tst/gplx/gfml/GfmlDataRdr_tst.java | 118 + .../tst/gplx/gfml/yfxts_GfmlParse_fxt.java | 77 + .../gplx/gfml/yfxts_GfmlTypeCompiler_fxt.java | 100 + .../gplx/gfml/ymoks_GfmlAtr_GfmlNde_mok.java | 180 + 110_gfml/tst/gplx/gfml/ymoks_GfmlTkn_mok.java | 75 + .../gplx/gfml/ymoks_GfmlTyp_GfmlFld_mok.java | 81 + 110_gfml/tst/gplx/gfml/ymoks_UsrMsg_mok.java | 34 + .../tst/gplx/gfml/z011_IntObjHash_tst.java | 61 + 110_gfml/tst/gplx/gfml/z012_GfmlTrie_tst.java | 71 + .../tst/gplx/gfml/z015_GfmlDocPos_tst.java | 50 + .../tst/gplx/gfml/z016_GfmlScopeList_tst.java | 53 + .../gfml/z017_GfmlStringHighlighter_tst.java | 51 + .../gplx/gfml/z051_GfmlFldPool_keyed_tst.java | 75 + .../tst/gplx/gfml/z081_GfmlDataWtr_tst.java | 69 + .../gplx/gfml/z082_GfmlDataWtrOpts_tst.java | 52 + .../tst/gplx/gfml/z091_GfmlLxr_basic_tst.java | 66 + .../gplx/gfml/z101_core_ndeInline_tst.java | 48 + .../gplx/gfml/z102_core_whitespace_tst.java | 77 + .../tst/gplx/gfml/z103_core_elmKey_tst.java | 50 + .../tst/gplx/gfml/z111_core_comment0_tst.java | 49 + .../tst/gplx/gfml/z112_core_comment1_tst.java | 67 + .../tst/gplx/gfml/z120_quotes_eval0_tst.java | 39 + .../gplx/gfml/z121_quotes_quotes0_tst.java | 50 + .../gfml/z122_quotes_quote0_eval0_tst.java | 62 + .../gplx/gfml/z123_quotes_quoteBlock_tst.java | 38 + .../gplx/gfml/z124_quotes_quoteFold_tst.java | 59 + .../tst/gplx/gfml/z151_ndeSubs_basic_tst.java | 63 + .../tst/gplx/gfml/z152_ndeSubs_data_tst.java | 66 + .../gplx/gfml/z161_ndeHdrs_inline_tst.java | 44 + .../tst/gplx/gfml/z162_ndeHdrs_err_tst.java | 31 + .../tst/gplx/gfml/z163_ndeHdrs_body_tst.java | 50 + .../tst/gplx/gfml/z164_hdeHdrs_data_tst.java | 51 + .../tst/gplx/gfml/z181_ndeDots_basic_tst.java | 63 + .../tst/gplx/gfml/z182_ndeDots_subs_tst.java | 71 + .../gplx/gfml/z183_ndeDots_parens_tst.java | 138 + .../gplx/gfml/z184_ndeDots_atrSpr_tst.java | 45 + .../gplx/gfml/z191_ndeProps_basic_tst.java | 118 + .../tst/gplx/gfml/z192_ndeProps_dots_tst.java | 37 + .../tst/gplx/gfml/z400_GfmlTypeMakr_tst.java | 64 + .../gfml/z401_types_compile_basic_tst.java | 63 + .../gfml/z402_types_compile_implicit_tst.java | 61 + .../gfml/z403_types_compile_default_tst.java | 62 + .../gfml/z411_types_apply_atrs_basic_tst.java | 64 + .../gfml/z421_types_apply_ndes_basic_tst.java | 50 + .../gfml/z422_types_apply_ndes_multi_tst.java | 72 + .../gfml/z423_types_apply_ndes_misc_tst.java | 47 + .../gfml/z424_types_apply_ndes_nest_tst.java | 65 + .../gplx/gfml/z441_types_parse_basic_tst.java | 51 + .../gfml/z442_types_parse_default_tst.java | 99 + .../gplx/gfml/z443_types_parse_keyd_tst.java | 48 + .../gfml/z450_fx_GfmlDefaultItem_fxt.java | 39 + .../tst/gplx/gfml/z451_dflts_compile_tst.java | 44 + .../tst/gplx/gfml/z452_dflts_exec_tst.java | 94 + .../tst/gplx/gfml/z455_dflts_scope_tst.java | 47 + .../tst/gplx/gfml/z456_dflts_parse_tst.java | 33 + .../tst/gplx/gfml/z481_vars_compile_tst.java | 33 + .../tst/gplx/gfml/z482_vars_parse_tst.java | 83 + .../tst/gplx/gfml/z501_lxr_parse_tst.java | 86 + 110_gfml/tst/gplx/gfml/z601_edit_atr_tst.java | 128 + 110_gfml/tst/gplx/gfml/z602_edit_nde_tst.java | 22 + .../gplx/gfml/z801_useCase_DataRdr_tst.java | 79 + .../gfml/z803_useCase_KbdKeyboard_tst.java | 85 + .../gplx/gfml/z811_useCase_GfmlIoSql_tst.java | 48 + 110_gfml/tst/gplx/gfml/z901_perf_tst.java | 115 + .../src/gplx/core/srls/Dbmeta_dat_itm.java | 7 + .../src/gplx/core/srls/Dbmeta_dat_mgr.java | 13 + 140_dbs/src/gplx/core/srls/Gfo_srl_ctx.java | 6 + 140_dbs/src/gplx/core/srls/Gfo_srl_itm.java | 6 + .../src/gplx/core/srls/Gfo_srl_mgr_rdr.java | 9 + .../src/gplx/core/srls/Gfo_srl_mgr_wtr.java | 10 + 140_dbs/src/gplx/core/stores/DbMaprArg.java | 10 + 140_dbs/src/gplx/core/stores/DbMaprItm.java | 35 + 140_dbs/src/gplx/core/stores/DbMaprMgr.java | 31 + .../src/gplx/core/stores/DbMaprMgr_tst.java | 123 + 140_dbs/src/gplx/core/stores/DbMaprRdr.java | 105 + 140_dbs/src/gplx/core/stores/DbMaprWtr.java | 98 + 140_dbs/src/gplx/core/stores/Db_data_rdr.java | 64 + .../src/gplx/core/stores/Db_data_rdr_.java | 5 + 140_dbs/src/gplx/core/stores/MockDiscObj.java | 113 + 140_dbs/src/gplx/dbs/Db_attach_itm.java | 26 + 140_dbs/src/gplx/dbs/Db_attach_mgr.java | 120 + 140_dbs/src/gplx/dbs/Db_attach_mgr__tst.java | 57 + 140_dbs/src/gplx/dbs/Db_cmd_mode.java | 40 + 140_dbs/src/gplx/dbs/Db_conn.java | 157 + 140_dbs/src/gplx/dbs/Db_conn_.java | 19 + 140_dbs/src/gplx/dbs/Db_conn_bldr.java | 52 + 140_dbs/src/gplx/dbs/Db_conn_bldr_data.java | 22 + 140_dbs/src/gplx/dbs/Db_conn_bldr_wkr.java | 63 + 140_dbs/src/gplx/dbs/Db_conn_info.java | 23 + 140_dbs/src/gplx/dbs/Db_conn_info_.java | 86 + 140_dbs/src/gplx/dbs/Db_conn_info__base.java | 48 + 140_dbs/src/gplx/dbs/Db_conn_info_tst.java | 46 + 140_dbs/src/gplx/dbs/Db_conn_pool.java | 64 + 140_dbs/src/gplx/dbs/Db_conn_utl.java | 75 + 140_dbs/src/gplx/dbs/Db_crt_.java | 63 + 140_dbs/src/gplx/dbs/Db_crt_tst.java | 50 + 140_dbs/src/gplx/dbs/Db_idx_itm.java | 32 + 140_dbs/src/gplx/dbs/Db_mock_cell.java | 22 + 140_dbs/src/gplx/dbs/Db_mock_row.java | 29 + 140_dbs/src/gplx/dbs/Db_null.java | 22 + 140_dbs/src/gplx/dbs/Db_qry.java | 28 + 140_dbs/src/gplx/dbs/Db_qry_.java | 61 + 140_dbs/src/gplx/dbs/Db_qry_fxt.java | 65 + 140_dbs/src/gplx/dbs/Db_rdr.java | 34 + 140_dbs/src/gplx/dbs/Db_rdr_.java | 37 + 140_dbs/src/gplx/dbs/Db_rdr__basic.java | 49 + 140_dbs/src/gplx/dbs/Db_sql_.java | 60 + 140_dbs/src/gplx/dbs/Db_sql_select.java | 76 + 140_dbs/src/gplx/dbs/Db_stmt.java | 65 + 140_dbs/src/gplx/dbs/Db_stmt_.java | 74 + 140_dbs/src/gplx/dbs/Db_stmt_bldr.java | 46 + 140_dbs/src/gplx/dbs/Db_tbl.java | 20 + 140_dbs/src/gplx/dbs/Db_tbl_.java | 28 + 140_dbs/src/gplx/dbs/Db_tbl_owner.java | 19 + 140_dbs/src/gplx/dbs/Dbmeta_fld_itm.java | 79 + 140_dbs/src/gplx/dbs/Dbmeta_fld_list.java | 106 + 140_dbs/src/gplx/dbs/Dbmeta_fld_tid.java | 71 + 140_dbs/src/gplx/dbs/Dbmeta_idx_itm.java | 52 + 140_dbs/src/gplx/dbs/Dbmeta_tbl_itm.java | 41 + .../dbs/conn_props/Db_conn_props_mgr.java | 11 + 140_dbs/src/gplx/dbs/diffs/Gdif_core.java | 29 + 140_dbs/src/gplx/dbs/diffs/Gdif_db.java | 14 + 140_dbs/src/gplx/dbs/diffs/Gdif_db_.java | 16 + 140_dbs/src/gplx/dbs/diffs/Gfdb_diff_cmd.java | 41 + 140_dbs/src/gplx/dbs/diffs/Gfdb_diff_tbl.java | 69 + .../src/gplx/dbs/diffs/Gfdb_diff_tbl_mgr.java | 18 + 140_dbs/src/gplx/dbs/diffs/Gfdb_rdr_utl_.java | 61 + .../gplx/dbs/diffs/builds/Gdif_bldr_ctx.java | 21 + .../gplx/dbs/diffs/builds/Gfdb_diff_bldr.java | 23 + .../dbs/diffs/builds/Gfdb_diff_bldr_tst.java | 90 + .../diffs/builds/Gfdb_diff_rdr_comparer.java | 49 + .../gplx/dbs/diffs/builds/Gfdb_diff_wkr.java | 8 + .../dbs/diffs/builds/Gfdb_diff_wkr__db.java | 72 + .../gplx/dbs/diffs/cmds/Gfdb_diff_cmd.java | 288 + .../cmds/Gfdb_diff_cmd__idx__create.java | 100 + .../cmds/Gfdb_diff_cmd_sql_bldr_tst.java | 54 + .../src/gplx/dbs/diffs/itms/Gdif_cmd_itm.java | 12 + .../src/gplx/dbs/diffs/itms/Gdif_cmd_tbl.java | 26 + .../src/gplx/dbs/diffs/itms/Gdif_job_itm.java | 11 + .../src/gplx/dbs/diffs/itms/Gdif_job_tbl.java | 29 + .../src/gplx/dbs/diffs/itms/Gdif_txn_itm.java | 12 + .../src/gplx/dbs/diffs/itms/Gdif_txn_tbl.java | 26 + 140_dbs/src/gplx/dbs/engines/Db_engine.java | 34 + .../gplx/dbs/engines/Db_engine_sql_base.java | 124 + .../gplx/dbs/engines/mems/Mem_conn_info.java | 15 + .../src/gplx/dbs/engines/mems/Mem_db_fxt.java | 57 + .../src/gplx/dbs/engines/mems/Mem_engine.java | 55 + .../dbs/engines/mems/Mem_exec_select.java | 194 + .../dbs/engines/mems/Mem_exec_select_tst.java | 113 + .../gplx/dbs/engines/mems/Mem_qry_set.java | 7 + .../src/gplx/dbs/engines/mems/Mem_rdr.java | 29 + .../src/gplx/dbs/engines/mems/Mem_row.java | 22 + .../src/gplx/dbs/engines/mems/Mem_stmt.java | 131 + .../gplx/dbs/engines/mems/Mem_stmt_args.java | 60 + .../src/gplx/dbs/engines/mems/Mem_tbl.java | 89 + .../dbs/engines/mysql/Mysql_conn_info.java | 28 + .../gplx/dbs/engines/mysql/Mysql_engine.java | 33 + .../dbs/engines/noops/Noop_conn_info.java | 7 + .../gplx/dbs/engines/noops/Noop_engine.java | 35 + .../engines/postgres/Postgres_conn_info.java | 29 + .../dbs/engines/postgres/Postgres_engine.java | 19 + .../dbs/engines/sqlite/Sqlite_conn_info.java | 32 + .../dbs/engines/sqlite/Sqlite_engine.java | 148 + .../dbs/engines/sqlite/Sqlite_engine_.java | 62 + .../dbs/engines/sqlite/Sqlite_pragma.java | 33 + .../dbs/engines/sqlite/Sqlite_schema_mgr.java | 65 + .../gplx/dbs/engines/sqlite/Sqlite_tid.java | 4 + .../dbs/engines/sqlite/Sqlite_txn_mgr.java | 54 + .../dbs/engines/tdbs/TdbConnectInfo_tst.java | 15 + .../gplx/dbs/engines/tdbs/TdbDatabase.java | 30 + .../gplx/dbs/engines/tdbs/TdbDbLoadMgr.java | 33 + .../dbs/engines/tdbs/TdbDbLoadMgr_tst.java | 84 + .../gplx/dbs/engines/tdbs/TdbDbSaveMgr.java | 47 + .../dbs/engines/tdbs/TdbDbSaveMgr_tst.java | 60 + .../src/gplx/dbs/engines/tdbs/TdbDelete.java | 30 + .../src/gplx/dbs/engines/tdbs/TdbEngine.java | 80 + .../src/gplx/dbs/engines/tdbs/TdbFile.java | 14 + .../gplx/dbs/engines/tdbs/TdbFileList.java | 47 + .../src/gplx/dbs/engines/tdbs/TdbFlush.java | 17 + .../gplx/dbs/engines/tdbs/TdbFlush_tst.java | 102 + .../src/gplx/dbs/engines/tdbs/TdbInsert.java | 51 + .../src/gplx/dbs/engines/tdbs/TdbSelect.java | 96 + .../src/gplx/dbs/engines/tdbs/TdbStores.java | 15 + .../src/gplx/dbs/engines/tdbs/TdbTable.java | 48 + .../gplx/dbs/engines/tdbs/TdbTableList.java | 46 + .../src/gplx/dbs/engines/tdbs/TdbUpdate.java | 29 + .../gplx/dbs/engines/tdbs/Tdb_conn_info.java | 20 + .../src/gplx/dbs/metas/Dbmeta_fld_mgr.java | 18 + .../src/gplx/dbs/metas/Dbmeta_idx_fld.java | 22 + .../src/gplx/dbs/metas/Dbmeta_idx_mgr.java | 11 + .../src/gplx/dbs/metas/Dbmeta_itm_tid.java | 11 + .../src/gplx/dbs/metas/Dbmeta_reload_cmd.java | 4 + .../gplx/dbs/metas/Dbmeta_reload_cmd_.java | 7 + .../src/gplx/dbs/metas/Dbmeta_tbl_mgr.java | 13 + .../metas/parsers/Dbmeta_fld_wkr__base.java | 131 + .../dbs/metas/parsers/Dbmeta_parser__fld.java | 106 + .../metas/parsers/Dbmeta_parser__fld_tst.java | 57 + .../dbs/metas/parsers/Dbmeta_parser__idx.java | 41 + .../metas/parsers/Dbmeta_parser__idx_tst.java | 20 + .../dbs/metas/parsers/Dbmeta_parser__tbl.java | 34 + .../metas/parsers/Dbmeta_parser__tbl_tst.java | 48 + .../gplx/dbs/metas/parsers/Sql_bry_rdr.java | 39 + .../dbs/metas/parsers/Sql_bry_rdr_tst.java | 35 + 140_dbs/src/gplx/dbs/qrys/Db_arg.java | 7 + 140_dbs/src/gplx/dbs/qrys/Db_arg_owner.java | 16 + .../src/gplx/dbs/qrys/Db_qry__select_cmd.java | 96 + .../gplx/dbs/qrys/Db_qry__select_in_tbl.java | 70 + 140_dbs/src/gplx/dbs/qrys/Db_qry_delete.java | 15 + 140_dbs/src/gplx/dbs/qrys/Db_qry_dml_tst.java | 27 + 140_dbs/src/gplx/dbs/qrys/Db_qry_flush.java | 20 + 140_dbs/src/gplx/dbs/qrys/Db_qry_insert.java | 49 + .../src/gplx/dbs/qrys/Db_qry_select_tst.java | 72 + 140_dbs/src/gplx/dbs/qrys/Db_qry_sql.java | 65 + 140_dbs/src/gplx/dbs/qrys/Db_qry_sql_tst.java | 31 + 140_dbs/src/gplx/dbs/qrys/Db_qry_update.java | 42 + 140_dbs/src/gplx/dbs/qrys/Db_stmt_cmd.java | 161 + 140_dbs/src/gplx/dbs/qrys/Db_stmt_sql.java | 174 + .../src/gplx/dbs/qrys/Db_stmt_sql_tst.java | 12 + 140_dbs/src/gplx/dbs/qrys/Db_val_type.java | 19 + .../dbs/qrys/bats/Db_batch__journal_off.java | 31 + .../dbs/qrys/bats/Db_batch__journal_wal.java | 45 + .../src/gplx/dbs/qrys/bats/Db_batch_grp.java | 26 + .../src/gplx/dbs/qrys/bats/Db_batch_itm.java | 6 + .../src/gplx/dbs/qrys/bats/Db_batch_mgr.java | 11 + 140_dbs/src/gplx/dbs/sqls/Sql_qry_wtr.java | 6 + 140_dbs/src/gplx/dbs/sqls/Sql_qry_wtr_.java | 12 + .../gplx/dbs/sqls/itms/Db_obj_ary_crt.java | 19 + .../gplx/dbs/sqls/itms/Db_obj_ary_fld.java | 6 + .../gplx/dbs/sqls/itms/Db_obj_ary_tst.java | 27 + .../gplx/dbs/sqls/itms/Sql_from_clause.java | 9 + .../gplx/dbs/sqls/itms/Sql_group_clause.java | 11 + .../src/gplx/dbs/sqls/itms/Sql_join_fld.java | 17 + .../gplx/dbs/sqls/itms/Sql_order_clause.java | 10 + .../src/gplx/dbs/sqls/itms/Sql_order_fld.java | 20 + .../dbs/sqls/itms/Sql_order_fld_sorter.java | 24 + .../gplx/dbs/sqls/itms/Sql_select_clause.java | 5 + .../gplx/dbs/sqls/itms/Sql_select_fld.java | 49 + .../dbs/sqls/itms/Sql_select_fld_func.java | 35 + .../dbs/sqls/itms/Sql_select_fld_list.java | 8 + .../src/gplx/dbs/sqls/itms/Sql_tbl_itm.java | 27 + .../gplx/dbs/sqls/itms/Sql_where_clause.java | 8 + .../src/gplx/dbs/sqls/wtrs/Sql_core_wtr.java | 88 + .../dbs/sqls/wtrs/Sql_core_wtr__mysql.java | 3 + .../dbs/sqls/wtrs/Sql_core_wtr__sqlite.java | 4 + .../gplx/dbs/sqls/wtrs/Sql_core_wtr_fxt.java | 19 + .../src/gplx/dbs/sqls/wtrs/Sql_from_wtr.java | 37 + .../gplx/dbs/sqls/wtrs/Sql_from_wtr_tst.java | 16 + .../dbs/sqls/wtrs/Sql_qry_wtr__ansi__tst.java | 82 + .../sqls/wtrs/Sql_qry_wtr__iosql__tst.java | 44 + .../gplx/dbs/sqls/wtrs/Sql_schema_wtr.java | 88 + .../dbs/sqls/wtrs/Sql_schema_wtr_tst.java | 51 + .../gplx/dbs/sqls/wtrs/Sql_select_wtr.java | 63 + .../dbs/sqls/wtrs/Sql_select_wtr_tst.java | 11 + .../src/gplx/dbs/sqls/wtrs/Sql_val_wtr.java | 74 + .../gplx/dbs/sqls/wtrs/Sql_val_wtr_tst.java | 22 + .../src/gplx/dbs/sqls/wtrs/Sql_where_wtr.java | 128 + .../gplx/dbs/sqls/wtrs/Sql_where_wtr_tst.java | 38 + .../src/gplx/dbs/sqls/wtrs/Sql_wtr_ctx.java | 5 + 140_dbs/src/gplx/dbs/stmts/Db_stmt_arg.java | 8 + .../src/gplx/dbs/stmts/Db_stmt_arg_list.java | 50 + 140_dbs/src/gplx/dbs/stmts/Db_stmt_mgr.java | 36 + 140_dbs/src/gplx/dbs/sys/Db_sys_mgr.java | 21 + 140_dbs/src/gplx/dbs/sys/Db_sys_mgr_tst.java | 17 + 140_dbs/src/gplx/dbs/sys/Db_sys_tbl.java | 51 + 140_dbs/src/gplx/dbs/utls/Db_cmd_backup.java | 50 + .../src/gplx/dbs/utls/Db_cmd_backup_tst.java | 14 + .../src/gplx/dbs/utls/Db_in_wkr__base.java | 32 + 140_dbs/src/gplx/dbs/utls/Db_tbl__crud_.java | 65 + 140_dbs/src/gplx/dbs/utls/PoolIds.java | 35 + 140_dbs/src/gplx/dbs/utls/PoolIds_tst.java | 39 + 140_dbs/tst/gplx/dbs/Db_conn_fxt.java | 53 + 140_dbs/tst/gplx/dbs/GfoNdeTstr.java | 31 + .../tst/gplx/dbs/engines/db_CrudOps_tst.java | 112 + .../gplx/dbs/engines/db_DataTypes_tst.java | 62 + .../gplx/dbs/groupBys/GroupBys_base_tst.java | 78 + .../gplx/dbs/groupBys/GroupBys_mysql_tst.java | 11 + .../gplx/dbs/groupBys/GroupBys_tdb_tst.java | 12 + .../dbs/insertIntos/InsertIntos_base_tst.java | 43 + .../insertIntos/InsertIntos_mysql_tst.java | 7 + .../dbs/insertIntos/InsertIntos_tdb_tst.java | 7 + .../tst/gplx/dbs/joins/Joins_base_tst.java | 27 + 140_dbs/tst/gplx/dbs/joins/Joins_tdb_tst.java | 14 + .../gplx/dbs/orderBys/OrderBys_base_tst.java | 35 + .../gplx/dbs/orderBys/OrderBys_tdb_tst.java | 21 + 140_dbs/xtn/gplx/dbs/Bug_Utf8.java | 174 + 140_dbs/xtn/gplx/dbs/SqliteDbMain.java | 245 + 150_gfui/lib/swt.jar | Bin 0 -> 2048267 bytes 150_gfui/src/gplx/gfui/DirInt.java | 33 + 150_gfui/src/gplx/gfui/GfuiAlign.java | 21 + 150_gfui/src/gplx/gfui/GfuiAlign_.java | 61 + 150_gfui/src/gplx/gfui/GfuiAxisType.java | 23 + 150_gfui/src/gplx/gfui/GfuiBorderEdge.java | 56 + 150_gfui/src/gplx/gfui/PointAdp.java | 33 + 150_gfui/src/gplx/gfui/PointAdp_.java | 30 + 150_gfui/src/gplx/gfui/RectAdp.java | 42 + 150_gfui/src/gplx/gfui/RectAdpF.java | 31 + 150_gfui/src/gplx/gfui/RectAdp_.java | 32 + 150_gfui/src/gplx/gfui/SizeAdp.java | 34 + 150_gfui/src/gplx/gfui/SizeAdpF.java | 24 + 150_gfui/src/gplx/gfui/SizeAdpF_.java | 32 + 150_gfui/src/gplx/gfui/SizeAdp_.java | 40 + .../src/gplx/gfui/controls/GfuiBorderMgr.java | 53 + .../gplx/gfui/controls/GfuiBorderMgr_tst.java | 36 + .../customs/DataBndr_whenEvt_execCmd.java | 35 + .../controls/customs/GfuiBnd_box_status.java | 39 + .../controls/customs/GfuiCheckListBox.java | 25 + .../controls/customs/GfuiCheckListPanel.java | 44 + .../gfui/controls/customs/GfuiFormPanel.java | 22 + .../controls/customs/GfuiIoDialogUtl.java | 36 + .../controls/customs/GfuiIoUrlSelectBox.java | 54 + .../controls/customs/GfuiIoUrlSelectBox_.java | 5 + .../controls/customs/GfuiMoveElemBnd.java | 65 + .../controls/customs/GfuiMoveElemBtn.java | 71 + .../controls/customs/GfuiMoveElemBtn_tst.java | 18 + .../gfui/controls/customs/GfuiStatusBar.java | 32 + .../controls/customs/GfuiStatusBarBnd.java | 13 + .../gfui/controls/customs/GfuiStatusBox.java | 77 + .../controls/customs/GfuiStatusBoxBnd.java | 23 + .../gfui/controls/customs/GfuiStatusBox_.java | 15 + .../gplx/gfui/controls/elems/GfuiElem.java | 63 + .../gfui/controls/elems/GfuiElemBase.java | 283 + .../gfui/controls/elems/GfuiElemBase_.java | 5 + .../gfui/controls/elems/GfuiElemKeys.java | 29 + .../gfui/controls/elems/GfuiElemList.java | 36 + .../gplx/gfui/controls/elems/GfuiElem_.java | 27 + .../gfui/controls/gxws/GxwBoxListener.java | 14 + .../gplx/gfui/controls/gxws/GxwCbkHost.java | 19 + .../gplx/gfui/controls/gxws/GxwCbkHost_.java | 70 + .../gfui/controls/gxws/GxwCheckListBox.java | 14 + .../controls/gxws/GxwCheckListBox_lang.java | 184 + .../gplx/gfui/controls/gxws/GxwComboBox.java | 22 + .../gfui/controls/gxws/GxwComboBox_lang.java | 108 + .../gplx/gfui/controls/gxws/GxwCore_base.java | 32 + .../gplx/gfui/controls/gxws/GxwCore_lang.java | 149 + .../gplx/gfui/controls/gxws/GxwCore_mock.java | 34 + .../src/gplx/gfui/controls/gxws/GxwElem.java | 9 + .../gfui/controls/gxws/GxwElemFactory_.java | 6 + .../controls/gxws/GxwElemFactory_base.java | 15 + .../gxws/GxwElemFactory_cls_lang.java | 26 + .../gxws/GxwElemFactory_cls_mock.java | 14 + .../gplx/gfui/controls/gxws/GxwElem_lang.java | 51 + .../gfui/controls/gxws/GxwElem_mock_base.java | 75 + .../gplx/gfui/controls/gxws/GxwListBox.java | 8 + .../gfui/controls/gxws/GxwListBox_lang.java | 66 + .../gfui/controls/gxws/GxwTextBox_lang.java | 203 + .../gplx/gfui/controls/gxws/GxwTextFld.java | 11 + .../gplx/gfui/controls/gxws/GxwTextHtml.java | 7 + .../gfui/controls/gxws/GxwTextHtml_lang.java | 240 + .../gplx/gfui/controls/gxws/GxwTextMemo.java | 18 + .../gfui/controls/gxws/GxwTextMemo_lang.java | 335 + .../src/gplx/gfui/controls/gxws/GxwWin.java | 16 + .../gplx/gfui/controls/gxws/GxwWin_lang.java | 253 + .../src/gplx/gfui/controls/gxws/Gxw_grp.java | 4 + .../src/gplx/gfui/controls/gxws/Gxw_html.java | 15 + .../controls/gxws/Gxw_html_load_tid_.java | 9 + .../gplx/gfui/controls/gxws/Gxw_tab_itm.java | 8 + .../gplx/gfui/controls/gxws/Gxw_tab_mgr.java | 18 + .../src/gplx/gfui/controls/gxws/MockForm.java | 15 + .../gplx/gfui/controls/standards/GfuiBtn.java | 57 + .../controls/standards/GfuiBtnClickBnd.java | 25 + .../gfui/controls/standards/GfuiBtn_.java | 31 + .../gfui/controls/standards/GfuiChkBox.java | 45 + .../gfui/controls/standards/GfuiChkBox_.java | 57 + .../gfui/controls/standards/GfuiComboBox.java | 56 + .../gplx/gfui/controls/standards/GfuiLbl.java | 23 + .../gfui/controls/standards/GfuiLbl_.java | 35 + .../gfui/controls/standards/GfuiListBox.java | 27 + .../gfui/controls/standards/GfuiTextBox.java | 53 + .../gfui/controls/standards/GfuiTextBox_.java | 30 + .../gfui/controls/standards/GfuiTextMemo.java | 25 + .../gfui/controls/standards/Gfui_grp.java | 9 + .../gfui/controls/standards/Gfui_html.java | 55 + .../gfui/controls/standards/Gfui_tab_itm.java | 14 + .../controls/standards/Gfui_tab_itm_data.java | 21 + .../standards/Gfui_tab_itm_data_tst.java | 15 + .../gfui/controls/standards/Gfui_tab_mgr.java | 32 + .../src/gplx/gfui/controls/tabs/TabBnd.java | 74 + .../src/gplx/gfui/controls/tabs/TabBox.java | 138 + .../controls/tabs/TabBoxEvt_orderChanged.java | 16 + .../controls/tabs/TabBoxEvt_tabSelect.java | 20 + .../gplx/gfui/controls/tabs/TabBoxMgr.java | 40 + .../src/gplx/gfui/controls/tabs/TabBox_.java | 17 + .../gplx/gfui/controls/tabs/TabBox_tst.java | 114 + .../gplx/gfui/controls/tabs/TabPnlItm.java | 21 + .../gfui/controls/windows/GfoConsoleWin.java | 238 + .../controls/windows/GfuiBnd_win_host.java | 23 + .../gfui/controls/windows/GfuiCmdForm.java | 37 + .../gfui/controls/windows/GfuiFocusMgr.java | 14 + .../controls/windows/GfuiFocusOrderer.java | 42 + .../windows/GfuiFocusOrderer_tst.java | 70 + .../controls/windows/GfuiFocusXferBnd.java | 37 + .../gfui/controls/windows/GfuiForm_menu.java | 59 + .../gfui/controls/windows/GfuiMenuBar.java | 239 + .../controls/windows/GfuiMenuFormUtl.java | 23 + .../gfui/controls/windows/GfuiQuitMode.java | 20 + .../gfui/controls/windows/GfuiTipTextMgr.java | 17 + .../gplx/gfui/controls/windows/GfuiWin.java | 108 + .../controls/windows/GfuiWinFocusMgr.java | 120 + .../controls/windows/GfuiWinKeyCmdMgr.java | 45 + .../gplx/gfui/controls/windows/GfuiWin_.java | 56 + .../controls/windows/GfuiWin_toaster.java | 165 + .../controls/windows/GxwBorderFactory.java | 25 + .../gplx/gfui/controls/windows/GxwWinNpi.java | 3 + 150_gfui/src/gplx/gfui/draws/ColorAdp.java | 29 + .../src/gplx/gfui/draws/ColorAdpCache.java | 11 + 150_gfui/src/gplx/gfui/draws/ColorAdp_.java | 101 + .../src/gplx/gfui/draws/ColorAdp__tst.java | 41 + 150_gfui/src/gplx/gfui/draws/FontAdp.java | 48 + .../src/gplx/gfui/draws/FontAdpCache.java | 19 + .../src/gplx/gfui/draws/FontStyleAdp.java | 6 + .../src/gplx/gfui/draws/FontStyleAdp_.java | 51 + 150_gfui/src/gplx/gfui/draws/PenAdp.java | 36 + 150_gfui/src/gplx/gfui/draws/PenAdp_.java | 8 + .../src/gplx/gfui/draws/SolidBrushAdp.java | 14 + .../src/gplx/gfui/draws/SolidBrushAdp_.java | 21 + .../src/gplx/gfui/envs/ClipboardAdp_.java | 42 + .../src/gplx/gfui/envs/ClipboardAdp__tst.java | 9 + 150_gfui/src/gplx/gfui/envs/CursorAdp.java | 22 + 150_gfui/src/gplx/gfui/envs/ScreenAdp.java | 15 + 150_gfui/src/gplx/gfui/envs/ScreenAdp_.java | 42 + .../src/gplx/gfui/envs/ScreenAdp_tst.java | 12 + 150_gfui/src/gplx/gfui/envs/TimerAdp.java | 37 + 150_gfui/src/gplx/gfui/gfxs/GfxAdp.java | 13 + 150_gfui/src/gplx/gfui/gfxs/GfxAdpBase.java | 91 + 150_gfui/src/gplx/gfui/gfxs/GfxAdpMok.java | 33 + 150_gfui/src/gplx/gfui/gfxs/GfxAdp_.java | 10 + 150_gfui/src/gplx/gfui/gfxs/GfxItm.java | 2 + 150_gfui/src/gplx/gfui/gfxs/GfxItmList.java | 13 + 150_gfui/src/gplx/gfui/gfxs/GfxItm_base.java | 17 + 150_gfui/src/gplx/gfui/gfxs/GfxLineItm.java | 24 + 150_gfui/src/gplx/gfui/gfxs/GfxRectItm.java | 21 + .../src/gplx/gfui/gfxs/GfxStringData.java | 100 + 150_gfui/src/gplx/gfui/gfxs/GfxStringItm.java | 22 + 150_gfui/src/gplx/gfui/gfxs/PaintArgs.java | 12 + 150_gfui/src/gplx/gfui/imgs/IconAdp.java | 30 + 150_gfui/src/gplx/gfui/imgs/ImageAdp.java | 34 + 150_gfui/src/gplx/gfui/imgs/ImageAdp_.java | 109 + .../src/gplx/gfui/imgs/ImageAdp_base.java | 53 + .../src/gplx/gfui/imgs/ImageAdp_null.java | 18 + 150_gfui/src/gplx/gfui/imgs/ImageAdp_tst.java | 31 + .../gplx/gfui/ipts/GfuiClickKeyMgr_tst.java | 14 + 150_gfui/src/gplx/gfui/ipts/IptArg.java | 27 + .../gplx/gfui/ipts/IptArgChainMgr_tst.java | 45 + 150_gfui/src/gplx/gfui/ipts/IptArg_.java | 101 + .../src/gplx/gfui/ipts/IptArg_parser_tst.java | 46 + 150_gfui/src/gplx/gfui/ipts/IptBnd.java | 8 + 150_gfui/src/gplx/gfui/ipts/IptBndMgr.java | 277 + .../src/gplx/gfui/ipts/IptBndMgr_tst.java | 55 + 150_gfui/src/gplx/gfui/ipts/IptBnd_.java | 45 + .../src/gplx/gfui/ipts/IptBnd_chkBox.java | 26 + .../src/gplx/gfui/ipts/IptBnd_txt_cmd.java | 28 + .../src/gplx/gfui/ipts/IptBnd_txt_range.java | 80 + .../gplx/gfui/ipts/IptBnd_upDownRange.java | 43 + 150_gfui/src/gplx/gfui/ipts/IptBndsOwner.java | 4 + 150_gfui/src/gplx/gfui/ipts/IptCfg.java | 76 + 150_gfui/src/gplx/gfui/ipts/IptCfgItm.java | 7 + 150_gfui/src/gplx/gfui/ipts/IptCfgRegy.java | 23 + 150_gfui/src/gplx/gfui/ipts/IptCfg_.java | 14 + 150_gfui/src/gplx/gfui/ipts/IptCfg_tst.java | 67 + 150_gfui/src/gplx/gfui/ipts/IptEventData.java | 33 + 150_gfui/src/gplx/gfui/ipts/IptEventMgr.java | 82 + 150_gfui/src/gplx/gfui/ipts/IptEventType.java | 19 + .../src/gplx/gfui/ipts/IptEventType_.java | 41 + .../src/gplx/gfui/ipts/IptEventType_tst.java | 17 + .../src/gplx/gfui/ipts/IptEvtDataKey.java | 19 + .../src/gplx/gfui/ipts/IptEvtDataKeyHeld.java | 14 + .../src/gplx/gfui/ipts/IptEvtDataMouse.java | 17 + 150_gfui/src/gplx/gfui/ipts/IptKey.java | 13 + 150_gfui/src/gplx/gfui/ipts/IptKeyStrMgr.java | 58 + .../src/gplx/gfui/ipts/IptKeyStrMgr_tst.java | 42 + 150_gfui/src/gplx/gfui/ipts/IptKey_.java | 224 + 150_gfui/src/gplx/gfui/ipts/IptKey__tst.java | 22 + 150_gfui/src/gplx/gfui/ipts/IptMouseBtn.java | 7 + 150_gfui/src/gplx/gfui/ipts/IptMouseBtn_.java | 37 + 150_gfui/src/gplx/gfui/ipts/IptMouseMove.java | 6 + .../src/gplx/gfui/ipts/IptMouseWheel.java | 6 + .../src/gplx/gfui/ipts/IptMouseWheel_.java | 19 + .../gplx/gfui/kits/core/GfoFactory_gfui.java | 23 + .../gplx/gfui/kits/core/GfsLibIni_gfui.java | 9 + .../src/gplx/gfui/kits/core/GfuiEnv_.java | 96 + .../src/gplx/gfui/kits/core/GfuiInvkCmd.java | 3 + .../src/gplx/gfui/kits/core/GfuiInvkCmd_.java | 4 + .../gplx/gfui/kits/core/Gfui_clipboard.java | 9 + .../gplx/gfui/kits/core/Gfui_clipboard_.java | 5 + .../src/gplx/gfui/kits/core/Gfui_dlg_dir.java | 7 + .../gplx/gfui/kits/core/Gfui_dlg_dir_.java | 10 + .../gplx/gfui/kits/core/Gfui_dlg_file.java | 8 + .../gplx/gfui/kits/core/Gfui_dlg_file_.java | 11 + .../src/gplx/gfui/kits/core/Gfui_dlg_msg.java | 8 + .../gplx/gfui/kits/core/Gfui_dlg_msg_.java | 13 + .../src/gplx/gfui/kits/core/Gfui_kit.java | 36 + .../src/gplx/gfui/kits/core/Gfui_kit_.java | 15 + .../gplx/gfui/kits/core/Gfui_kit_base.java | 105 + .../src/gplx/gfui/kits/core/Gfui_mnu_grp.java | 13 + .../gplx/gfui/kits/core/Gfui_mnu_grp_.java | 23 + .../src/gplx/gfui/kits/core/Gfui_mnu_itm.java | 21 + .../gplx/gfui/kits/core/Gfui_mnu_itm_.java | 5 + .../src/gplx/gfui/kits/core/Mem_html.java | 54 + 150_gfui/src/gplx/gfui/kits/core/Mem_kit.java | 24 + .../src/gplx/gfui/kits/core/Swing_kit.java | 20 + 150_gfui/src/gplx/gfui/kits/core/Swt_kit.java | 369 + .../src/gplx/gfui/kits/core/TxtFindMgr.java | 31 + .../gplx/gfui/kits/swts/Swt_app_browser.java | 82 + .../src/gplx/gfui/kits/swts/Swt_app_main.java | 412 + 150_gfui/src/gplx/gfui/kits/swts/Swt_btn.java | 52 + .../gfui/kits/swts/Swt_btn_no_border.java | 79 + .../gplx/gfui/kits/swts/Swt_clipboard.java | 45 + .../src/gplx/gfui/kits/swts/Swt_combo.java | 91 + .../gplx/gfui/kits/swts/Swt_combo_ctrl.java | 386 + .../src/gplx/gfui/kits/swts/Swt_control.java | 12 + .../src/gplx/gfui/kits/swts/Swt_control_.java | 26 + .../gplx/gfui/kits/swts/Swt_core__base.java | 132 + .../gplx/gfui/kits/swts/Swt_core__basic.java | 13 + .../gplx/gfui/kits/swts/Swt_core__dual.java | 34 + .../gplx/gfui/kits/swts/Swt_core__frames.java | 100 + .../gplx/gfui/kits/swts/Swt_core_lnrs.java | 248 + .../src/gplx/gfui/kits/swts/Swt_dlg_dir.java | 14 + .../src/gplx/gfui/kits/swts/Swt_dlg_file.java | 23 + .../src/gplx/gfui/kits/swts/Swt_dlg_msg.java | 69 + 150_gfui/src/gplx/gfui/kits/swts/Swt_grp.java | 45 + .../src/gplx/gfui/kits/swts/Swt_html.java | 298 + .../gfui/kits/swts/Swt_html_eval_rslt.java | 7 + 150_gfui/src/gplx/gfui/kits/swts/Swt_img.java | 36 + 150_gfui/src/gplx/gfui/kits/swts/Swt_lbl.java | 30 + .../gfui/kits/swts/Swt_lnr__menu_detect.java | 20 + .../gplx/gfui/kits/swts/Swt_popup_grp.java | 204 + .../src/gplx/gfui/kits/swts/Swt_tab_itm.java | 45 + .../src/gplx/gfui/kits/swts/Swt_tab_mgr.java | 268 + .../src/gplx/gfui/kits/swts/Swt_text.java | 50 + .../gfui/kits/swts/Swt_text_w_border.java | 82 + 150_gfui/src/gplx/gfui/kits/swts/Swt_win.java | 102 + 150_gfui/src/gplx/gfui/layouts/GftBand.java | 74 + .../src/gplx/gfui/layouts/GftBand_tst.java | 95 + 150_gfui/src/gplx/gfui/layouts/GftCell.java | 9 + 150_gfui/src/gplx/gfui/layouts/GftGrid.java | 122 + .../src/gplx/gfui/layouts/GftGrid_fx.java | 76 + 150_gfui/src/gplx/gfui/layouts/GftItem.java | 17 + .../src/gplx/gfui/layouts/GftSizeCalc.java | 44 + .../gfui/layouts/swts/Swt_layout_data.java | 3 + .../layouts/swts/Swt_layout_data__grid.java | 17 + .../gfui/layouts/swts/Swt_layout_mgr.java | 3 + .../layouts/swts/Swt_layout_mgr__grid.java | 9 + 400_xowa/src/gplx/core/brys/Bit_.java | 38 + 400_xowa/src/gplx/core/brys/Bit__tst.java | 51 + 400_xowa/src/gplx/core/brys/Bit_heap_rdr.java | 75 + .../src/gplx/core/brys/Bit_heap_rdr_tst.java | 63 + 400_xowa/src/gplx/core/brys/Bit_heap_wtr.java | 54 + .../src/gplx/core/brys/Bit_heap_wtr_tst.java | 69 + 400_xowa/src/gplx/core/brys/Bry_bldr.java | 23 + 400_xowa/src/gplx/core/brys/Bry_comparer.java | 9 + 400_xowa/src/gplx/core/brys/Bry_diff_.java | 35 + 400_xowa/src/gplx/core/brys/Bry_diff_tst.java | 27 + 400_xowa/src/gplx/core/brys/Bry_tmp.java | 23 + .../src/gplx/core/brys/Int_flag_bldr.java | 21 + .../src/gplx/core/brys/Int_flag_bldr_.java | 72 + .../gplx/core/brys/Int_flag_bldr__tst.java | 57 + 400_xowa/src/gplx/core/brys/Mid_able.java | 6 + .../gplx/core/brys/evals/Bry_eval_mgr.java | 91 + .../core/brys/evals/Bry_eval_mgr__tst.java | 35 + .../gplx/core/brys/evals/Bry_eval_wkr.java | 5 + .../gplx/core/btries/Btrie_u8_mgr_tst.java | 79 + .../src/gplx/core/caches/Gfo_cache_data.java | 29 + .../src/gplx/core/caches/Gfo_cache_mgr.java | 75 + .../gplx/core/caches/Gfo_cache_mgr_base.java | 31 + .../gplx/core/caches/Gfo_cache_mgr_bry.java | 36 + .../gplx/core/caches/Gfo_cache_mgr_tst.java | 56 + .../gplx/core/consoles/Gfo_cmd_arg_itm.java | 39 + .../gplx/core/consoles/Gfo_cmd_arg_itm_.java | 31 + .../gplx/core/consoles/Gfo_cmd_arg_mgr.java | 74 + .../gplx/core/consoles/Gfo_cmd_arg_mgr_.java | 25 + .../consoles/Gfo_cmd_arg_mgr_printer.java | 78 + .../core/consoles/Gfo_cmd_arg_mgr_tst.java | 106 + .../src/gplx/core/enums/Gfo_enum_grp.java | 24 + 400_xowa/src/gplx/core/flds/Gfo_fld_base.java | 31 + 400_xowa/src/gplx/core/flds/Gfo_fld_rdr.java | 109 + .../src/gplx/core/flds/Gfo_fld_rdr_tst.java | 39 + 400_xowa/src/gplx/core/flds/Gfo_fld_wtr.java | 42 + 400_xowa/src/gplx/core/gfobjs/Gfobj_ary.java | 14 + 400_xowa/src/gplx/core/gfobjs/Gfobj_fld.java | 74 + 400_xowa/src/gplx/core/gfobjs/Gfobj_grp.java | 4 + 400_xowa/src/gplx/core/gfobjs/Gfobj_grp_.java | 7 + 400_xowa/src/gplx/core/gfobjs/Gfobj_nde.java | 44 + .../src/gplx/core/gfobjs/Gfobj_rdr__json.java | 75 + .../gplx/core/gfobjs/Gfobj_rdr__json_tst.java | 71 + .../src/gplx/core/gfobjs/Gfobj_wtr__json.java | 69 + .../gplx/core/gfobjs/Gfobj_wtr__json_fxt.java | 37 + .../gplx/core/gfobjs/Gfobj_wtr__json_tst.java | 138 + .../gplx/core/intls/String_surrogate_utl.java | 19 + .../core/intls/String_surrogate_utl_tst.java | 40 + .../gplx/core/intls/ucas/Uca_collator.java | 5 + .../gplx/core/intls/ucas/Uca_collator_.java | 8 + .../intls/ucas/Uca_collator__icu__4_8.java | 32 + .../core/intls/ucas/Uca_ltr_extractor.java | 34 + .../gplx/core/ios/BinaryHeap_Io_line_rdr.java | 67 + .../core/ios/BinaryHeap_Io_line_rdr_tst.java | 33 + 400_xowa/src/gplx/core/ios/Io_buffer_rdr.java | 56 + .../src/gplx/core/ios/Io_buffer_rdr_tst.java | 47 + 400_xowa/src/gplx/core/ios/Io_fil_chkr.java | 15 + 400_xowa/src/gplx/core/ios/Io_line_rdr.java | 146 + .../gplx/core/ios/Io_line_rdr_key_gen.java | 4 + .../gplx/core/ios/Io_line_rdr_key_gen_.java | 39 + .../src/gplx/core/ios/Io_line_rdr_tst.java | 79 + 400_xowa/src/gplx/core/ios/Io_make_cmd.java | 4 + 400_xowa/src/gplx/core/ios/Io_sort.java | 82 + 400_xowa/src/gplx/core/ios/Io_sort_cmd.java | 6 + .../src/gplx/core/ios/Io_sort_filCmd.java | 12 + .../src/gplx/core/ios/Io_sort_fil_basic.java | 21 + .../src/gplx/core/ios/Io_sort_misc_tst.java | 41 + .../src/gplx/core/ios/Io_sort_split_itm.java | 18 + .../core/ios/Io_sort_split_itm_sorter.java | 8 + 400_xowa/src/gplx/core/ios/Io_sort_tst.java | 49 + .../gplx/core/ios/Io_stream_rdr_process.java | 51 + .../src/gplx/core/ios/Io_stream_zip_mgr.java | 39 + 400_xowa/src/gplx/core/ios/Io_url_gen.java | 7 + 400_xowa/src/gplx/core/ios/Io_url_gen_.java | 28 + 400_xowa/src/gplx/core/lists/Queue_adp.java | 38 + .../src/gplx/core/lists/Queue_adp_tst.java | 44 + 400_xowa/src/gplx/core/lists/StatRng.java | 125 + 400_xowa/src/gplx/core/lists/StatRng_tst.java | 22 + .../binary_searches/Binary_comparer.java | 4 + .../lists/binary_searches/Binary_search_.java | 46 + .../binary_searches/Binary_search__tst.java | 38 + .../binary_searches/Binary_search_cmp.java | 19 + .../binary_searches/Binary_search_grp.java | 17 + .../gplx/core/lists/hashs/Hash_adp__int.java | 15 + 400_xowa/src/gplx/core/net/Gfo_inet_conn.java | 7 + .../src/gplx/core/net/Gfo_inet_conn_.java | 30 + .../gplx/core/net/Gfo_inet_conn__http.java | 12 + .../src/gplx/core/net/Gfo_protocol_itm.java | 122 + 400_xowa/src/gplx/core/net/Gfo_url.java | 23 + .../src/gplx/core/net/Gfo_url_parser.java | 169 + .../src/gplx/core/net/Gfo_url_parser_fxt.java | 39 + .../src/gplx/core/net/Gfo_url_parser_tst.java | 111 + .../src/gplx/core/net/Gfo_url_site_data.java | 7 + .../src/gplx/core/net/Http_client_rdr.java | 8 + .../src/gplx/core/net/Http_client_rdr_.java | 19 + .../core/net/Http_client_rdr__stream.java | 21 + .../src/gplx/core/net/Http_client_wtr.java | 10 + .../src/gplx/core/net/Http_client_wtr_.java | 4 + .../core/net/Http_client_wtr__stream.java | 36 + .../gplx/core/net/Http_post_data_hash.java | 10 + .../src/gplx/core/net/Http_post_data_itm.java | 6 + .../src/gplx/core/net/Http_request_itm.java | 65 + .../gplx/core/net/Http_request_parser.java | 159 + .../core/net/Http_request_parser_tst.java | 78 + .../src/gplx/core/net/Http_server_wtr.java | 7 + .../src/gplx/core/net/Http_server_wtr_.java | 5 + .../core/net/Http_server_wtr__console.java | 10 + 400_xowa/src/gplx/core/net/Local_host_.java | 7 + .../src/gplx/core/net/Server_socket_adp.java | 6 + .../core/net/Server_socket_adp__base.java | 27 + 400_xowa/src/gplx/core/net/Socket_adp.java | 7 + .../src/gplx/core/net/Socket_adp__base.java | 22 + .../core/net/downloads/Http_download_wkr.java | 9 + .../net/downloads/Http_download_wkr_.java | 4 + .../downloads/Http_download_wkr__base.java | 66 + .../net/downloads/Http_download_wkr__jre.java | 94 + .../gplx/core/net/emails/Gfo_email_mgr.java | 7 + .../gplx/core/net/emails/Gfo_email_mgr_.java | 5 + .../net/emails/Gfo_email_mgr__apache.java | 5 + .../core/net/emails/Gfo_email_mgr__jre.java | 21 + .../core/net/qargs/Gfo_qarg_enum_itm.java | 12 + .../core/net/qargs/Gfo_qarg_enum_mgr.java | 12 + .../src/gplx/core/net/qargs/Gfo_qarg_itm.java | 12 + .../src/gplx/core/net/qargs/Gfo_qarg_mgr.java | 48 + .../gplx/core/net/qargs/Gfo_qarg_mgr_old.java | 90 + .../gplx/core/primitives/Bool_ary_bldr.java | 22 + .../src/gplx/core/primitives/Bry_ary.java | 40 + .../src/gplx/core/primitives/Bry_cache.java | 18 + .../core/primitives/Gfo_number_parser.java | 170 + .../primitives/Gfo_number_parser_tst.java | 92 + .../core/primitives/Hash_adp__primitive.java | 12 + .../src/gplx/core/primitives/Int_2_ref.java | 41 + .../src/gplx/core/primitives/Int_2_val.java | 16 + .../src/gplx/core/primitives/Int_ary.java | 58 + .../gplx/core/primitives/Int_ary_bldr.java | 6 + .../gplx/core/primitives/Int_ary_parser.java | 22 + .../core/primitives/Int_ary_parser_tst.java | 13 + .../src/gplx/core/primitives/Int_pool.java | 53 + .../gplx/core/primitives/Int_pool_tst.java | 58 + .../core/primitives/Obj_ary_parser_base.java | 41 + .../gplx/core/progs/rates/Gfo_rate_list.java | 29 + .../core/progs/rates/Gfo_rate_list_tst.java | 19 + .../gplx/core/progs/rates/Gfo_rate_mgr.java | 22 + .../gplx/core/scripts/Gfo_script_engine.java | 9 + .../gplx/core/scripts/Gfo_script_engine_.java | 14 + .../Gfo_script_engine__javascript.java | 55 + .../core/scripts/Gfo_script_engine__luaj.java | 82 + .../core/scripts/Gfo_script_engine__noop.java | 9 + .../gplx/core/security/files/Cksum_itm.java | 14 + .../gplx/core/security/files/Cksum_list.java | 67 + .../core/security/files/Cksum_list_tst.java | 33 + 400_xowa/src/gplx/core/tests/Tst_chkr.java | 13 + 400_xowa/src/gplx/core/tests/Tst_mgr.java | 117 + .../gplx/core/threads/Gfo_async_cmd_itm.java | 25 + .../gplx/core/threads/Gfo_async_cmd_mkr.java | 17 + .../src/gplx/core/threads/Gfo_async_mgr.java | 41 + .../src/gplx/core/threads/Gfo_thread_cmd.java | 13 + .../gplx/core/threads/Gfo_thread_cmd_.java | 5 + .../core/threads/Gfo_thread_cmd_base.java | 25 + .../core/threads/Gfo_thread_cmd_download.java | 57 + .../core/threads/Gfo_thread_cmd_replace.java | 45 + .../core/threads/Gfo_thread_cmd_unzip.java | 99 + .../src/gplx/core/threads/Gfo_thread_grp.java | 73 + .../src/gplx/core/threads/Gfo_thread_itm.java | 7 + .../src/gplx/core/threads/Gfo_thread_mgr.java | 20 + .../gplx/core/threads/Gfo_thread_pool.java | 45 + .../src/gplx/core/threads/Gfo_thread_wkr.java | 6 + 400_xowa/src/gplx/dbs/Db_diff_bldr.java | 52 + .../src/gplx/dbs/bulks/Db_bulk_exec_.java | 71 + 400_xowa/src/gplx/dbs/bulks/Db_bulk_prog.java | 4 + 400_xowa/src/gplx/dbs/bulks/Db_tbl_copy.java | 39 + .../src/gplx/dbs/bulks/Db_tbl_copy_tst.java | 22 + 400_xowa/src/gplx/dbs/cfgs/Db_cfg_hash.java | 18 + 400_xowa/src/gplx/dbs/cfgs/Db_cfg_itm.java | 40 + 400_xowa/src/gplx/dbs/cfgs/Db_cfg_tbl.java | 146 + .../src/gplx/dbs/metas/Schema_db_mgr.java | 11 + .../src/gplx/dbs/metas/Schema_loader_mgr.java | 4 + .../gplx/dbs/metas/Schema_loader_mgr_.java | 33 + .../dbs/metas/updates/Schema_update_cmd.java | 6 + .../dbs/metas/updates/Schema_update_cmd_.java | 20 + .../dbs/metas/updates/Schema_update_mgr.java | 15 + .../metas/updates/Schema_update_mgr_tst.java | 38 + .../gplx/dbs/percentiles/Log_tbl_fmtr.java | 57 + .../gplx/dbs/percentiles/Percentile_rng.java | 77 + .../dbs/percentiles/Percentile_rng_log.java | 26 + .../dbs/percentiles/Percentile_rng_tst.java | 46 + .../percentiles/Percentile_select_base.java | 50 + 400_xowa/src/gplx/dbs/updates/Sql_runner.java | 38 + 400_xowa/src/gplx/fsdb/Fsdb_db_file.java | 26 + 400_xowa/src/gplx/fsdb/Fsdb_db_mgr.java | 28 + 400_xowa/src/gplx/fsdb/Fsdb_db_mgr_.java | 78 + 400_xowa/src/gplx/fsdb/Fsdb_db_mgr__v1.java | 111 + 400_xowa/src/gplx/fsdb/Fsdb_db_mgr__v2.java | 64 + .../src/gplx/fsdb/Fsdb_db_mgr__v2_bldr.java | 110 + 400_xowa/src/gplx/fsdb/data/Fsd_bin_itm.java | 17 + 400_xowa/src/gplx/fsdb/data/Fsd_bin_tbl.java | 105 + 400_xowa/src/gplx/fsdb/data/Fsd_dir_itm.java | 14 + 400_xowa/src/gplx/fsdb/data/Fsd_dir_tbl.java | 52 + 400_xowa/src/gplx/fsdb/data/Fsd_fil_itm.java | 30 + 400_xowa/src/gplx/fsdb/data/Fsd_fil_tbl.java | 103 + 400_xowa/src/gplx/fsdb/data/Fsd_img_itm.java | 10 + 400_xowa/src/gplx/fsdb/data/Fsd_thm_itm.java | 51 + 400_xowa/src/gplx/fsdb/data/Fsd_thm_tbl.java | 142 + .../src/gplx/fsdb/data/Fsd_thm_tbl_tst.java | 41 + 400_xowa/src/gplx/fsdb/meta/Fsm_atr_fil.java | 92 + 400_xowa/src/gplx/fsdb/meta/Fsm_atr_mgr.java | 17 + 400_xowa/src/gplx/fsdb/meta/Fsm_atr_tbl.java | 43 + 400_xowa/src/gplx/fsdb/meta/Fsm_bin_fil.java | 24 + 400_xowa/src/gplx/fsdb/meta/Fsm_bin_mgr.java | 53 + 400_xowa/src/gplx/fsdb/meta/Fsm_bin_tbl.java | 43 + 400_xowa/src/gplx/fsdb/meta/Fsm_cfg_mgr.java | 37 + 400_xowa/src/gplx/fsdb/meta/Fsm_id_itm.java | 7 + 400_xowa/src/gplx/fsdb/meta/Fsm_mnt_itm.java | 62 + 400_xowa/src/gplx/fsdb/meta/Fsm_mnt_mgr.java | 48 + 400_xowa/src/gplx/fsdb/meta/Fsm_mnt_tbl.java | 43 + 400_xowa/src/gplx/gfui/Gfui_bnd_parser.java | 330 + .../src/gplx/gfui/Gfui_bnd_parser_tst.java | 62 + .../src/gplx/langs/dsvs/Dsv_fld_parser.java | 5 + .../src/gplx/langs/dsvs/Dsv_fld_parser_.java | 97 + .../src/gplx/langs/dsvs/Dsv_tbl_parser.java | 66 + .../langs/dsvs/Dsv_tbl_parser_int_tst.java | 47 + .../langs/dsvs/Dsv_tbl_parser_str_tst.java | 92 + .../src/gplx/langs/dsvs/Dsv_wkr_base.java | 25 + 400_xowa/src/gplx/langs/gfs/Gfs_lxr.java | 197 + 400_xowa/src/gplx/langs/gfs/Gfs_lxr_.java | 22 + 400_xowa/src/gplx/langs/gfs/Gfs_msg_bldr.java | 36 + .../src/gplx/langs/gfs/Gfs_msg_bldr_tst.java | 59 + 400_xowa/src/gplx/langs/gfs/Gfs_nde.java | 68 + 400_xowa/src/gplx/langs/gfs/Gfs_parser.java | 88 + .../src/gplx/langs/gfs/Gfs_parser_ctx.java | 110 + .../src/gplx/langs/gfs/Gfs_parser_tst.java | 179 + 400_xowa/src/gplx/langs/gfs/Gfs_wtr.java | 29 + 400_xowa/src/gplx/langs/htmls/Gfh_atr_.java | 59 + 400_xowa/src/gplx/langs/htmls/Gfh_bldr_.java | 25 + 400_xowa/src/gplx/langs/htmls/Gfh_nde.java | 77 + 400_xowa/src/gplx/langs/htmls/Gfh_parser.java | 148 + .../src/gplx/langs/htmls/Gfh_parser_tst.java | 36 + .../src/gplx/langs/htmls/Gfh_selecter.java | 23 + 400_xowa/src/gplx/langs/htmls/Gfh_tag_.java | 270 + .../src/gplx/langs/htmls/Gfh_tag_meta.java | 11 + 400_xowa/src/gplx/langs/htmls/Gfh_utl.java | 193 + .../gplx/langs/htmls/Gfh_utl__basic__tst.java | 41 + .../langs/htmls/Gfh_utl__comments__tst.java | 9 + 400_xowa/src/gplx/langs/htmls/Gfh_wtr.java | 90 + .../gplx/langs/htmls/clses/Gfh_class_.java | 44 + .../langs/htmls/clses/Gfh_class__tst.java | 41 + .../langs/htmls/clses/Gfh_class_parser_.java | 32 + .../htmls/clses/Gfh_class_parser__tst.java | 27 + .../htmls/clses/Gfh_class_parser_wkr.java | 4 + .../src/gplx/langs/htmls/docs/Gfh_atr.java | 30 + .../gplx/langs/htmls/docs/Gfh_doc_parser.java | 37 + .../gplx/langs/htmls/docs/Gfh_doc_wkr.java | 5 + .../src/gplx/langs/htmls/docs/Gfh_tag.java | 147 + .../gplx/langs/htmls/docs/Gfh_tag_rdr.java | 343 + .../langs/htmls/docs/Gfh_tag_rdr_tst.java | 63 + .../gplx/langs/htmls/docs/Gfh_txt_wkr.java | 4 + .../langs/htmls/encoders/Gfo_url_encoder.java | 48 + .../htmls/encoders/Gfo_url_encoder_.java | 118 + .../htmls/encoders/Gfo_url_encoder_itm.java | 88 + .../htmls/encoders/Gfo_url_encoder_mkr.java | 66 + .../htmls/encoders/Gfo_url_encoder_tst.java | 54 + .../langs/htmls/styles/Gfh_style_itm.java | 8 + .../langs/htmls/styles/Gfh_style_key_.java | 7 + .../langs/htmls/styles/Gfh_style_parser_.java | 52 + .../htmls/styles/Gfh_style_parser__tst.java | 34 + .../langs/htmls/styles/Gfh_style_wkr.java | 4 + .../htmls/styles/Gfh_style_wkr__ary.java | 14 + .../styles/Gfh_style_wkr__val_as_int.java | 18 + 400_xowa/src/gplx/langs/jsons/Json_ary.java | 63 + 400_xowa/src/gplx/langs/jsons/Json_doc.java | 67 + .../src/gplx/langs/jsons/Json_doc_bldr.java | 25 + .../src/gplx/langs/jsons/Json_doc_srl.java | 72 + .../src/gplx/langs/jsons/Json_doc_tst.java | 29 + .../src/gplx/langs/jsons/Json_doc_wtr.java | 81 + .../src/gplx/langs/jsons/Json_factory.java | 13 + 400_xowa/src/gplx/langs/jsons/Json_grp.java | 18 + 400_xowa/src/gplx/langs/jsons/Json_itm.java | 18 + 400_xowa/src/gplx/langs/jsons/Json_itm_.java | 11 + .../src/gplx/langs/jsons/Json_itm_base.java | 12 + .../src/gplx/langs/jsons/Json_itm_bool.java | 11 + .../gplx/langs/jsons/Json_itm_decimal.java | 17 + .../src/gplx/langs/jsons/Json_itm_int.java | 18 + .../src/gplx/langs/jsons/Json_itm_long.java | 18 + .../src/gplx/langs/jsons/Json_itm_str.java | 62 + .../src/gplx/langs/jsons/Json_itm_tmp.java | 14 + 400_xowa/src/gplx/langs/jsons/Json_kv.java | 22 + .../src/gplx/langs/jsons/Json_kv_ary_srl.java | 44 + .../gplx/langs/jsons/Json_kv_ary_srl_tst.java | 33 + 400_xowa/src/gplx/langs/jsons/Json_nde.java | 154 + .../src/gplx/langs/jsons/Json_parser.java | 169 + .../langs/jsons/Json_parser__itm__base.java | 57 + .../jsons/Json_parser__list_nde__base.java | 53 + .../src/gplx/langs/jsons/Json_parser_tst.java | 83 + .../src/gplx/langs/jsons/Json_printer.java | 37 + .../gplx/langs/jsons/Json_printer_tst.java | 71 + 400_xowa/src/gplx/langs/jsons/Json_wtr.java | 297 + .../src/gplx/langs/jsons/Json_wtr_tst.java | 117 + .../gplx/langs/mustaches/Mustache_bfr.java | 24 + .../langs/mustaches/Mustache_doc_itm.java | 6 + .../langs/mustaches/Mustache_doc_itm_.java | 14 + .../mustaches/Mustache_itm_render_tst.java | 147 + .../langs/mustaches/Mustache_render_ctx.java | 83 + .../langs/mustaches/Mustache_tkn_def.java | 27 + .../langs/mustaches/Mustache_tkn_itm.java | 95 + .../langs/mustaches/Mustache_tkn_parser.java | 137 + .../mustaches/Mustache_tkn_parser_tst.java | 22 + .../gplx/langs/mustaches/Mustache_wtr_.java | 12 + 400_xowa/src/gplx/langs/phps/Php_ctx.java | 4 + .../src/gplx/langs/phps/Php_evaluator.java | 258 + 400_xowa/src/gplx/langs/phps/Php_itm.java | 27 + 400_xowa/src/gplx/langs/phps/Php_itm_.java | 27 + 400_xowa/src/gplx/langs/phps/Php_itm_ary.java | 20 + 400_xowa/src/gplx/langs/phps/Php_itm_int.java | 7 + 400_xowa/src/gplx/langs/phps/Php_itm_kv.java | 7 + .../src/gplx/langs/phps/Php_itm_quote.java | 6 + 400_xowa/src/gplx/langs/phps/Php_itm_sub.java | 6 + 400_xowa/src/gplx/langs/phps/Php_key.java | 6 + 400_xowa/src/gplx/langs/phps/Php_line.java | 2 + .../src/gplx/langs/phps/Php_line_assign.java | 6 + 400_xowa/src/gplx/langs/phps/Php_lxr.java | 266 + 400_xowa/src/gplx/langs/phps/Php_parser.java | 105 + .../src/gplx/langs/phps/Php_parser_fxt.java | 275 + .../src/gplx/langs/phps/Php_parser_tst.java | 67 + 400_xowa/src/gplx/langs/phps/Php_srl_itm.java | 122 + .../src/gplx/langs/phps/Php_srl_parser.java | 191 + .../gplx/langs/phps/Php_srl_parser_tst.java | 95 + .../src/gplx/langs/phps/Php_text_itm.java | 45 + .../gplx/langs/phps/Php_text_itm_parser.java | 128 + .../src/gplx/langs/phps/Php_text_itm_tst.java | 36 + 400_xowa/src/gplx/langs/phps/Php_tkn.java | 57 + .../src/gplx/langs/phps/Php_tkn_factory.java | 11 + 400_xowa/src/gplx/langs/phps/Php_tkn_wkr.java | 19 + .../src/gplx/langs/regxs/Gfo_pattern.java | 33 + .../src/gplx/langs/regxs/Gfo_pattern_ctx.java | 14 + .../src/gplx/langs/regxs/Gfo_pattern_itm.java | 47 + .../gplx/langs/regxs/Gfo_pattern_itm_.java | 34 + .../src/gplx/langs/regxs/Gfo_pattern_tst.java | 76 + 400_xowa/src/gplx/langs/xmls/Gfo_xml_wtr.java | 134 + .../src/gplx/langs/xmls/Gfo_xml_wtr_tst.java | 64 + 400_xowa/src/gplx/xowa/Xoa_app.java | 67 + 400_xowa/src/gplx/xowa/Xoa_app_.java | 48 + 400_xowa/src/gplx/xowa/Xoa_app_fxt.java | 105 + 400_xowa/src/gplx/xowa/Xoa_page.java | 34 + 400_xowa/src/gplx/xowa/Xoa_page_.java | 29 + 400_xowa/src/gplx/xowa/Xoa_test_.java | 52 + 400_xowa/src/gplx/xowa/Xoa_ttl.java | 495 + 400_xowa/src/gplx/xowa/Xoa_url.java | 115 + 400_xowa/src/gplx/xowa/Xoa_url_.java | 34 + 400_xowa/src/gplx/xowa/Xoae_app.java | 255 + 400_xowa/src/gplx/xowa/Xoae_page.java | 104 + 400_xowa/src/gplx/xowa/Xoae_page__tst.java | 42 + 400_xowa/src/gplx/xowa/Xop_fxt.java | 485 + 400_xowa/src/gplx/xowa/Xow_wiki.java | 65 + 400_xowa/src/gplx/xowa/Xow_wiki_.java | 34 + 400_xowa/src/gplx/xowa/Xowa_main.java | 21 + 400_xowa/src/gplx/xowa/Xowe_wiki.java | 324 + 400_xowa/src/gplx/xowa/Xowe_wiki_.java | 38 + .../src/gplx/xowa/addons/Xoax_addon_itm.java | 4 + .../xowa/addons/Xoax_addon_itm__bldr.java | 5 + .../xowa/addons/Xoax_addon_itm__init.java | 6 + .../xowa/addons/Xoax_addon_itm__json.java | 4 + .../xowa/addons/Xoax_addon_itm__special.java | 4 + .../src/gplx/xowa/addons/Xoax_addon_mgr.java | 118 + .../xowa/addons/apps/cfgs/Xoa_cfg_addon.java | 20 + .../gplx/xowa/addons/apps/cfgs/Xocfg_mgr.java | 134 + .../addons/apps/cfgs/dbs/Xocfg_db_app.java | 25 + .../addons/apps/cfgs/dbs/Xocfg_db_usr.java | 26 + .../apps/cfgs/dbs/tbls/Xocfg_grp_row.java | 11 + .../apps/cfgs/dbs/tbls/Xocfg_grp_tbl.java | 40 + .../apps/cfgs/dbs/tbls/Xocfg_itm_row.java | 19 + .../apps/cfgs/dbs/tbls/Xocfg_itm_tbl.java | 49 + .../apps/cfgs/dbs/tbls/Xocfg_map_tbl.java | 47 + .../apps/cfgs/dbs/tbls/Xocfg_txt_itm.java | 15 + .../apps/cfgs/dbs/tbls/Xocfg_txt_tbl.java | 35 + .../apps/cfgs/dbs/tbls/Xocfg_val_row.java | 13 + .../apps/cfgs/dbs/tbls/Xocfg_val_tbl.java | 53 + .../apps/cfgs/enums/Xoitm_gui_binding.java | 29 + .../apps/cfgs/enums/Xoitm_lang_tid.java | 4 + .../apps/cfgs/enums/Xoitm_scope_enum.java | 9 + .../apps/cfgs/enums/Xoitm_type_enum.java | 25 + .../cfgs/mgrs/caches/Xocfg_cache_grp.java | 70 + .../cfgs/mgrs/caches/Xocfg_cache_itm.java | 12 + .../cfgs/mgrs/caches/Xocfg_cache_mgr.java | 88 + .../mgrs/caches/Xocfg_cache_mgr__tst.java | 63 + .../cfgs/mgrs/caches/Xocfg_cache_sub.java | 13 + .../apps/cfgs/mgrs/dflts/Xocfg_dflt_mgr.java | 40 + .../apps/cfgs/mgrs/execs/Xocfg_exec_mgr.java | 22 + .../apps/cfgs/mgrs/types/Xocfg_type_mgr.java | 31 + .../cfgs/specials/edits/objs/Xoedit_grp.java | 52 + .../cfgs/specials/edits/objs/Xoedit_itm.java | 86 + .../specials/edits/objs/Xoedit_itm_html.java | 104 + .../specials/edits/objs/Xoedit_nav_itm.java | 20 + .../specials/edits/objs/Xoedit_nav_mgr.java | 15 + .../cfgs/specials/edits/objs/Xoedit_nde.java | 7 + .../specials/edits/objs/Xoedit_nde_hash.java | 61 + .../cfgs/specials/edits/objs/Xoedit_root.java | 38 + .../specials/edits/objs/Xogui_nde_iter.java | 41 + .../specials/edits/pages/Xocfg_edit_html.java | 32 + .../edits/pages/Xocfg_edit_special.java | 28 + .../edits/services/Xocfg_edit_bridge.java | 29 + .../edits/services/Xocfg_edit_loader.java | 241 + .../edits/services/Xocfg_edit_svc.java | 58 + .../maints/pages/Xocfg_maint_html.java | 21 + .../maints/pages/Xocfg_maint_special.java | 12 + .../maints/services/Xocfg_maint_bridge.java | 26 + .../maints/services/Xocfg_maint_nde.java | 43 + .../maints/services/Xocfg_maint_parser.java | 57 + .../services/Xocfg_maint_parser__tst.java | 28 + .../maints/services/Xocfg_maint_svc.java | 64 + .../apps/cfgs/upgrades/Xocfg_upgrade_mgr.java | 112 + .../cfgs/upgrades/Xocfg_upgrade_mgr__tst.java | 41 + .../addons/apps/helps/logs/Xolog_addon.java | 11 + .../addons/apps/helps/logs/Xolog_doc.java | 70 + .../addons/apps/helps/logs/Xolog_html.java | 21 + .../addons/apps/helps/logs/Xolog_special.java | 70 + .../maints/sql_execs/Xosql_exec_addon.java | 16 + .../sql_execs/cbks/Xosql_exec_bridge.java | 26 + .../maints/sql_execs/cbks/Xosql_exec_svc.java | 75 + .../sql_execs/specials/Xosql_exec_doc.java | 22 + .../sql_execs/specials/Xosql_exec_html.java | 34 + .../specials/Xosql_exec_special.java | 17 + .../addons/apps/scripts/Xoscript_env.java | 33 + .../addons/apps/scripts/Xoscript_mgr.java | 23 + .../apps/scripts/apis/Xoscript_doc.java | 44 + .../apps/scripts/apis/Xoscript_doc_head.java | 4 + .../scripts/apis/Xoscript_doc_head__tst.java | 57 + .../scripts/apis/Xoscript_doc_sect_base.java | 65 + .../apps/scripts/apis/Xoscript_doc_tail.java | 4 + .../apps/scripts/apis/Xoscript_log.java | 6 + .../apps/scripts/apis/Xoscript_page.java | 11 + .../apps/scripts/apis/Xoscript_url.java | 6 + .../apps/scripts/xtns/Xoscript_xtn_itm.java | 12 + .../apps/scripts/xtns/Xoscript_xtn_mgr.java | 52 + .../addons/apps/updates/Xoa_update_addon.java | 16 + .../apps/updates/Xoa_update_startup.java | 32 + .../apps/updates/apps/Xoa_manifest_item.java | 9 + .../apps/updates/apps/Xoa_manifest_list.java | 44 + .../apps/updates/apps/Xoa_manifest_view.java | 97 + .../apps/updates/apps/Xoa_manifest_wkr.java | 69 + .../apps/updates/dbs/Xoa_app_version_itm.java | 35 + .../apps/updates/dbs/Xoa_app_version_tbl.java | 58 + .../apps/updates/dbs/Xoa_update_db_mgr.java | 13 + .../apps/updates/dbs/Xoa_update_db_mgr_.java | 44 + .../apps/updates/js/Xojs_wkr__base.java | 83 + .../apps/updates/js/Xojs_wkr__download.java | 19 + .../apps/updates/js/Xojs_wkr__replace.java | 36 + .../apps/updates/js/Xojs_wkr__unzip.java | 16 + .../updates/specials/Xoa_update_html.java | 59 + .../specials/Xoa_update_itm__leaf.java | 30 + .../specials/Xoa_update_itm__root.java | 30 + .../updates/specials/Xoa_update_special.java | 11 + .../specials/svcs/Xoa_update_bridge.java | 29 + .../updates/specials/svcs/Xoa_update_svc.java | 131 + .../specials/svcs/Xoa_update_svc__tst.java | 20 + .../app_cfgs/Xoac_wiki_cfg_bldr_cmd.java | 13 + .../app_cfgs/Xoac_wiki_cfg_bldr_fil.java | 18 + .../bldrs/app_cfgs/Xob_wiki_cfg_bldr.java | 40 + .../bldrs/app_cfgs/Xob_wiki_cfg_bldr_tst.java | 162 + .../wm_server_cfgs/Xowm_server_cfg_cmd.java | 13 + .../wm_server_cfgs/Xowm_server_cfg_mgr.java | 66 + .../bldrs/centrals/Xobc_task_addon.java | 24 + .../bldrs/centrals/Xobc_task_bridge.java | 47 + .../addons/bldrs/centrals/Xobc_task_doc.java | 17 + .../addons/bldrs/centrals/Xobc_task_html.java | 27 + .../addons/bldrs/centrals/Xobc_task_mgr.java | 66 + .../bldrs/centrals/Xobc_task_special.java | 29 + .../bldrs/centrals/cmds/Xobc_cmd__base.java | 141 + .../centrals/cmds/Xobc_cmd__download.java | 67 + .../centrals/cmds/Xobc_cmd__fsdb_delete.java | 29 + .../centrals/cmds/Xobc_cmd__move_fils.java | 24 + .../bldrs/centrals/cmds/Xobc_cmd__unzip.java | 29 + .../centrals/cmds/Xobc_cmd__verify_dir.java | 43 + .../centrals/cmds/Xobc_cmd__verify_fil.java | 25 + .../centrals/cmds/Xobc_cmd__wiki_merge.java | 40 + .../centrals/cmds/Xobc_cmd__wiki_reg.java | 36 + .../bldrs/centrals/cmds/Xobc_cmd_ctx.java | 5 + .../bldrs/centrals/cmds/Xobc_cmd_itm.java | 17 + .../bldrs/centrals/dbs/Xobc_data_db.java | 46 + .../centrals/dbs/Xobc_data_db_upgrader.java | 72 + .../centrals/dbs/Xobc_task_step_hash.java | 14 + .../bldrs/centrals/dbs/Xobc_user_db.java | 14 + .../dbs/datas/Xobc_host_regy_itm.java | 13 + .../dbs/datas/Xobc_host_regy_tbl.java | 34 + .../dbs/datas/Xobc_lang_regy_itm.java | 11 + .../dbs/datas/Xobc_lang_regy_tbl.java | 28 + .../centrals/dbs/datas/Xobc_step_map_itm.java | 13 + .../centrals/dbs/datas/Xobc_step_map_tbl.java | 75 + .../dbs/datas/Xobc_step_regy_tbl.java | 38 + .../dbs/datas/Xobc_task_regy_itm.java | 18 + .../dbs/datas/Xobc_task_regy_tbl.java | 83 + .../dbs/datas/Xobc_version_regy_itm.java | 11 + .../dbs/datas/Xobc_version_regy_tbl.java | 33 + .../dbs/datas/imports/Xobc_import_date.java | 6 + .../datas/imports/Xobc_import_step_itm.java | 35 + .../datas/imports/Xobc_import_step_tbl.java | 103 + .../dbs/datas/imports/Xobc_import_type.java | 18 + .../dbs/datas/imports/Xobc_zip_type.java | 10 + .../dbs/users/Xobc_done_step_tbl.java | 38 + .../dbs/users/Xobc_done_task_tbl.java | 47 + .../dbs/users/Xobc_work_task_tbl.java | 56 + .../bldrs/centrals/hosts/Host_eval_itm.java | 29 + .../bldrs/centrals/hosts/Host_eval_wkr.java | 34 + .../centrals/hosts/Host_eval_wkr__tst.java | 15 + .../bldrs/centrals/mgrs/Xobc_filter_mgr.java | 35 + .../bldrs/centrals/mgrs/Xobc_lang_mgr.java | 17 + .../bldrs/centrals/mgrs/Xobc_skip_mgr.java | 22 + .../centrals/steps/Xobc_step_factory.java | 74 + .../bldrs/centrals/steps/Xobc_step_itm.java | 46 + .../bldrs/centrals/tasks/Xobc_task_itm.java | 36 + .../bldrs/centrals/tasks/Xobc_task_key.java | 29 + .../centrals/tasks/Xobc_task_regy__base.java | 26 + .../centrals/tasks/Xobc_task_regy__done.java | 8 + .../centrals/tasks/Xobc_task_regy__todo.java | 31 + .../centrals/tasks/Xobc_task_regy__work.java | 146 + .../utils/Bry_eval_wkr__builder_central.java | 50 + .../bldrs/centrals/utils/Time_dhms_.java | 37 + .../bldrs/centrals/utils/Time_dhms__tst.java | 20 + .../addons/bldrs/exports/Export_addon.java | 14 + .../addons/bldrs/exports/Xow_db_file_mgr.java | 16 + .../bldrs/exports/merges/Merge2_copy_utl.java | 95 + .../exports/merges/Merge2_copy_wkr__lot.java | 128 + .../bldrs/exports/merges/Merge2_heap_db.java | 13 + .../bldrs/exports/merges/Merge2_heap_mgr.java | 96 + .../bldrs/exports/merges/Merge2_mgr.java | 54 + .../bldrs/exports/merges/Merge2_trg_itm.java | 11 + .../bldrs/exports/merges/Merge2_trg_mgr.java | 30 + .../bldrs/exports/merges/Merge2_wkr.java | 6 + .../exports/merges/Merge2_wkr__heap_base.java | 17 + .../exports/merges/Merge2_wkr__heap_lot.java | 22 + .../exports/merges/Merge2_wkr__heap_one.java | 22 + .../bldrs/exports/merges/Merge_bldr_cmd.java | 35 + .../bldrs/exports/merges/Merge_ctx.java | 17 + .../exports/merges/Merge_prog_checkpoint.java | 44 + .../bldrs/exports/merges/Merge_prog_wkr.java | 47 + .../bldrs/exports/merges/Merge_wkr__core.java | 79 + .../bldrs/exports/merges/Merge_wkr_utl.java | 52 + .../packs/files/Pack_file_bldr_cmd.java | 20 + .../exports/packs/files/Pack_file_cfg.java | 34 + .../exports/packs/files/Pack_file_mgr.java | 158 + .../bldrs/exports/packs/files/Pack_hash.java | 38 + .../exports/packs/files/Pack_hash_bldr.java | 146 + .../bldrs/exports/packs/files/Pack_itm.java | 13 + .../bldrs/exports/packs/files/Pack_list.java | 13 + .../packs/files/Pack_zip_name_bldr.java | 51 + .../packs/files/Pack_zip_name_bldr__tst.java | 24 + .../bldrs/exports/packs/splits/Pack_itm.java | 13 + .../bldrs/exports/packs/splits/Pack_list.java | 10 + .../bldrs/exports/packs/splits/Pack_mgr.java | 129 + .../packs/splits/Pack_split_bldr_cmd.java | 20 + .../bldrs/exports/splits/Split_bldr_cmd.java | 21 + .../bldrs/exports/splits/Split_ctx.java | 61 + .../bldrs/exports/splits/Split_mgr.java | 65 + .../bldrs/exports/splits/Split_wkr.java | 11 + .../splits/archives/Reindex_html_dbs_cmd.java | 161 + .../exports/splits/files/Bin_meta_itm.java | 10 + .../splits/files/Split_init__file.java | 111 + .../splits/files/Split_meta_wkr__bin.java | 91 + .../splits/files/Split_meta_wkr__fil.java | 41 + .../splits/files/Split_meta_wkr__org.java | 39 + .../splits/files/Split_meta_wkr__thm.java | 44 + .../splits/files/Split_rslt_wkr__bin.java | 7 + .../splits/files/Split_rslt_wkr__fil.java | 7 + .../splits/files/Split_rslt_wkr__org.java | 10 + .../splits/files/Split_rslt_wkr__thm.java | 7 + .../exports/splits/files/Split_wkr__file.java | 40 + .../splits/htmls/Split_rslt_wkr__html.java | 7 + .../exports/splits/htmls/Split_wkr__html.java | 65 + .../splits/htmls/Xoh_page_tbl_itm.java | 16 + .../exports/splits/htmls/Xoh_src_tbl_mgr.java | 27 + .../exports/splits/htmls/Xoh_trg_tbl_mgr.java | 34 + .../splits/metas/Split_meta_wkr_base.java | 40 + .../exports/splits/metas/Split_page_itm.java | 18 + .../exports/splits/metas/Split_page_list.java | 9 + .../splits/metas/Split_page_list_type_.java | 7 + .../exports/splits/metas/Split_page_mgr.java | 10 + .../bldrs/exports/splits/mgrs/Split_cfg.java | 33 + .../splits/mgrs/Split_db_size_calc.java | 20 + .../exports/splits/mgrs/Split_file_tid_.java | 23 + .../exports/splits/mgrs/Split_mgr_init.java | 56 + .../exports/splits/mgrs/Split_ns_itm.java | 7 + .../splits/mgrs/Split_page_loader.java | 55 + .../exports/splits/mgrs/Split_type_cfg.java | 16 + .../splits/pages/Split_rslt_wkr__page.java | 7 + .../exports/splits/pages/Split_wkr__page.java | 40 + .../splits/rndms/Split_rslt_wkr__rndm.java | 9 + .../exports/splits/rndms/Split_wkr__rndm.java | 55 + .../exports/splits/rslts/Split_rslt_mgr.java | 85 + .../exports/splits/rslts/Split_rslt_tid_.java | 11 + .../exports/splits/rslts/Split_rslt_wkr.java | 11 + .../exports/splits/rslts/Split_rslt_wkr_.java | 14 + .../rslts/Split_rslt_wkr__int__base.java | 48 + .../rslts/Split_rslt_wkr__objs__base.java | 58 + .../exports/splits/rslts/Wkr_stats_itm.java | 7 + .../exports/splits/rslts/Wkr_stats_tbl.java | 46 + .../splits/srchs/Split_meta_wkr__link.java | 52 + .../splits/srchs/Split_meta_wkr__word.java | 35 + .../splits/srchs/Split_rslt_wkr__link.java | 10 + .../splits/srchs/Split_rslt_wkr__word.java | 7 + .../exports/splits/srchs/Split_srch_init.java | 44 + .../exports/splits/srchs/Split_wkr__srch.java | 42 + .../addons/bldrs/exports/utls/Split_tbl.java | 105 + .../addons/bldrs/exports/utls/Split_tbl_.java | 30 + .../bldrs/files/Xoax_builds_files_addon.java | 42 + .../bldrs/files/checks/Xocheck_cmd.java | 13 + .../bldrs/files/checks/Xocheck_mgr.java | 72 + .../bldrs/files/cksums/Xocksum_calc_cmd.java | 13 + .../bldrs/files/cksums/Xocksum_calc_mgr.java | 62 + .../files/cksums/dbs/Xocksum_cksum_db.java | 14 + .../files/cksums/dbs/Xocksum_cksum_row.java | 24 + .../files/cksums/dbs/Xocksum_cksum_tbl.java | 90 + .../Xob_hdump_tbl_retriever__ns_to_db.java | 17 + .../cmds/Xobldr__fsdb_db__create_data.java | 370 + .../cmds/Xobldr__fsdb_db__create_orig.java | 105 + .../files/cmds/Xobldr__image__create.java | 112 + .../files/cmds/Xobldr__image__create_tst.java | 34 + .../files/cmds/Xobldr__lnki_regy__create.java | 16 + .../files/cmds/Xobldr__lnki_temp__create.java | 174 + .../cmds/Xobldr__lnki_temp__create_.java | 36 + .../cmds/Xobldr__lnki_temp__create__tst.java | 26 + .../files/cmds/Xobldr__orig_regy__create.java | 31 + .../cmds/Xobldr__page_file_map__create.java | 145 + .../files/cmds/Xobldr__page_regy__create.java | 35 + .../files/cmds/Xobldr__redirect__create.java | 48 + .../cmds/Xobldr__text_db__drop_page.java | 27 + .../cmds/Xobldr__text_db__make_page.java | 29 + .../files/cmds/Xobldr__xfer_regy__create.java | 20 + .../Xobldr__xfer_regy__update_downloaded.java | 53 + .../cmds/Xobldr__xfer_temp__insert_orig.java | 91 + .../cmds/Xobldr__xfer_temp__insert_thm.java | 64 + .../bldrs/files/dbs/Page_file_map_tbl.java | 21 + .../bldrs/files/dbs/Xob_fsdb_regy_tbl_.java | 115 + .../addons/bldrs/files/dbs/Xob_image_tbl.java | 41 + .../bldrs/files/dbs/Xob_lnki_regy_tbl.java | 98 + .../bldrs/files/dbs/Xob_lnki_temp_tbl.java | 48 + .../bldrs/files/dbs/Xob_orig_regy_tbl.java | 205 + .../bldrs/files/dbs/Xob_page_dump_tbl.java | 30 + .../bldrs/files/dbs/Xob_page_regy_tbl.java | 70 + .../bldrs/files/dbs/Xob_redirect_tbl.java | 150 + .../files/dbs/Xob_xfer_regy_log_tbl.java | 25 + .../bldrs/files/dbs/Xob_xfer_regy_tbl.java | 119 + .../bldrs/files/dbs/Xob_xfer_temp_tbl.java | 71 + .../Xobldr_missing_origs_cmd.java | 138 + .../apis/Xowmf_imageinfo_api.java | 117 + .../apis/Xowmf_imageinfo_item.java | 101 + .../apis/Xowmf_imageinfo_item__tst.java | 26 + .../apis/Xowmf_recentchanges_api.java | 106 + .../apis/Xowmf_recentchanges_item.java | 47 + .../bldrs/files/shrinks/Xoshrink_cmd.java | 13 + .../bldrs/files/shrinks/Xoshrink_mgr.java | 56 + .../bldrs/files/utls/Xob_bin_db_itm.java | 40 + .../bldrs/files/utls/Xob_bin_db_mgr.java | 55 + .../bldrs/files/utls/Xob_xfer_temp_itm.java | 124 + .../files/utls/Xob_xfer_temp_itm_tst.java | 175 + .../bldrs/files/utls/Xobu_poll_mgr.java | 21 + .../bldrs/hdumps/diffs/Dumpdiff_addon.java | 11 + .../bldrs/hdumps/diffs/Dumpdiff_cfg.java | 12 + .../bldrs/hdumps/diffs/Dumpdiff_cmd.java | 21 + .../bldrs/hdumps/diffs/Dumpdiff_log_tbl.java | 41 + .../bldrs/hdumps/diffs/Dumpdiff_mgr.java | 61 + .../bldrs/hdumps/diffs/Dumpdiff_page_itm.java | 15 + .../hdumps/diffs/Dumpdiff_page_loader.java | 37 + .../bldrs/hdumps/diffs/Hdump_html_loader.java | 28 + .../htmls/Html__dump_to_fsys__addon.java | 11 + .../bldrs/htmls/Html__dump_to_fsys__cmd.java | 71 + .../addons/bldrs/htmls/Html_page_itm.java | 32 + .../addons/bldrs/infos/Xobc_info_doc.java | 35 + .../addons/bldrs/infos/Xobc_info_html.java | 73 + .../bldrs/infos/Xobc_info_html__tst.java | 12 + .../addons/bldrs/infos/Xobc_info_special.java | 18 + .../mass_parses/dbs/Xomp_lock_req_tbl.java | 35 + .../bldrs/mass_parses/dbs/Xomp_lock_tbl.java | 29 + .../bldrs/mass_parses/dbs/Xomp_mgr_db.java | 37 + .../bldrs/mass_parses/dbs/Xomp_page_tbl.java | 40 + .../bldrs/mass_parses/dbs/Xomp_wkr_db.java | 21 + .../bldrs/mass_parses/dbs/Xomp_wkr_tbl.java | 82 + .../mass_parses/inits/Xomp_init_cmd.java | 18 + .../mass_parses/inits/Xomp_init_mgr.java | 36 + .../mass_parses/inits/Xomp_init_mgr_cfg.java | 23 + .../mass_parses/makes/Xob_lnki_temp_row.java | 36 + .../mass_parses/makes/Xomp_html_db_rdr.java | 26 + .../mass_parses/makes/Xomp_html_db_wtr.java | 86 + .../mass_parses/makes/Xomp_make_cmd.java | 19 + .../mass_parses/makes/Xomp_make_cmd_cfg.java | 16 + .../mass_parses/makes/Xomp_make_html.java | 54 + .../mass_parses/makes/Xomp_make_lnki.java | 54 + .../mass_parses/makes/Xomp_make_merger.java | 8 + .../makes/Xomp_make_merger__base.java | 83 + .../makes/Xomp_make_merger__lnki_temp.java | 30 + .../makes/Xomp_make_merger__xnde.java | 50 + .../mass_parses/parses/Xomp_parse_cmd.java | 19 + .../mass_parses/parses/Xow_wiki_utl_.java | 27 + .../parses/locks/Xomp_lock_mgr.java | 6 + .../parses/locks/Xomp_lock_mgr__db.java | 40 + .../parses/locks/Xomp_lock_mgr__fsys.java | 74 + .../parses/mgrs/Xomp_parse_mgr.java | 122 + .../parses/mgrs/Xomp_parse_mgr_cfg.java | 69 + .../parses/mgrs/Xomp_prog_mgr.java | 46 + .../parses/pools/Xomp_load_wkr.java | 111 + .../parses/pools/Xomp_page_itm.java | 23 + .../parses/pools/Xomp_page_pool.java | 45 + .../parses/pools/Xomp_page_pool_loader.java | 85 + .../parses/utls/Xomp_lnki_temp_wkr.java | 37 + .../parses/utls/Xomp_ns_ord_mgr.java | 12 + .../parses/utls/Xomp_text_db_loader.java | 79 + .../parses/utls/Xomp_tmpl_cache_bldr.java | 87 + .../wkrs/Xob_hdump_tbl_retriever__xomp.java | 16 + .../parses/wkrs/Xomp_parse_wkr.java | 180 + .../mass_parses/resumes/Xomp_resume_cmd.java | 12 + .../mass_parses/resumes/Xomp_resume_mgr.java | 31 + .../updates/files/Xob_delete_regy_tbl.java | 16 + .../bldrs/updates/files/Xodel_addon.java | 16 + .../bldrs/updates/files/Xodel_exec_cmd.java | 19 + .../bldrs/updates/files/Xodel_exec_mgr.java | 117 + .../bldrs/updates/files/Xodel_make_cmd.java | 14 + .../bldrs/updates/files/Xodel_make_mgr.java | 46 + .../bldrs/updates/files/Xodel_prune_itm.java | 9 + .../bldrs/updates/files/Xodel_small_cmd.java | 34 + .../bldrs/updates/files/Xodel_small_mgr.java | 39 + .../find_missings/Xodel_find_missing_cmd.java | 14 + .../find_missings/Xodel_find_missing_mgr.java | 79 + .../Xoax_builds_utils_rankings_addon.java | 12 + .../bldrs/Sqlite_percentile_cmd.java | 73 + .../bldrs/Statistic_calculator.java | 24 + .../bldrs/utils_rankings/bldrs/Str_ary_.java | 18 + .../addons/bldrs/volumes/Volume_make_itm.java | 19 + .../volumes/Volume_page_loader__wiki.java | 46 + .../addons/bldrs/volumes/Volume_prep_cmd.java | 31 + .../addons/bldrs/volumes/Volume_prep_itm.java | 19 + .../addons/bldrs/volumes/Volume_prep_mgr.java | 59 + .../addons/bldrs/volumes/Volume_prep_rdr.java | 28 + .../bldrs/volumes/Volume_prep_rdr_tst.java | 23 + .../wmdumps/imglinks/Db_bulk_cmd_base.java | 60 + .../bldrs/wmdumps/imglinks/Imglnk_addon.java | 12 + .../wmdumps/imglinks/Imglnk_bldr_cmd.java | 35 + .../wmdumps/imglinks/Imglnk_bldr_mgr.java | 34 + .../wmdumps/imglinks/Imglnk_reg_tbl.java | 61 + .../wmdumps/imglinks/Imglnk_tmp_tbl.java | 37 + .../imglinks/Xof_orig_wkr__img_links.java | 59 + .../imglinks/Xof_orig_wkr__img_links_.java | 76 + .../Xoax_builds_pagelinks_addon.java | 12 + .../pagelinks/bldrs/Pglnk_bldr_cmd.java | 61 + .../pagelinks/dbs/Pglnk_page_link_tbl.java | 20 + .../dbs/Pglnk_page_link_temp_tbl.java | 28 + .../addons/bldrs/xodirs/Xobc_xodir_addon.java | 29 + .../addons/bldrs/xodirs/Xobc_xodir_cfg.java | 13 + .../addons/bldrs/xodirs/Xobc_xodir_dir.java | 34 + .../addons/bldrs/xodirs/Xobc_xodir_html.java | 23 + .../addons/bldrs/xodirs/Xobc_xodir_mgr.java | 4 + .../bldrs/xodirs/Xobc_xodir_mgr__pc.java | 15 + .../bldrs/xodirs/Xobc_xodir_special.java | 28 + .../xodirs/Xow_import_dir_cbk__xodir.java | 2 + .../htmls/sidebars/Xoh_sidebar_htmlr.java | 41 + .../htmls/sidebars/Xoh_sidebar_itm.java | 32 + .../htmls/sidebars/Xoh_sidebar_mgr.java | 37 + .../htmls/sidebars/Xoh_sidebar_mgr_tst.java | 220 + .../htmls/sidebars/Xoh_sidebar_parser.java | 96 + .../xowa/addons/htmls/tocs/Xoh_toc_htmlr.java | 75 + .../htmls/tocs/Xoh_toc_htmlr__basic__tst.java | 160 + .../xowa/addons/htmls/tocs/Xoh_toc_itm.java | 18 + .../xowa/addons/htmls/tocs/Xoh_toc_mgr.java | 39 + .../addons/htmls/tocs/Xoh_toc_wkr__lvl.java | 58 + .../tocs/Xoh_toc_wkr__lvl__basic__tst.java | 49 + .../addons/htmls/tocs/Xoh_toc_wkr__txt.java | 159 + .../tocs/Xoh_toc_wkr__txt__basic__tst.java | 69 + .../tocs/Xoh_toc_wkr__txt__dupe__tst.java | 25 + .../tocs/Xoh_toc_wkr__txt__xnde__tst.java | 31 + .../addons/htmls/tocs/Xowe_hdr_bldr__tst.java | 532 + .../addons/htmls/tocs/Xowe_hdr_bldr_fxt.java | 54 + .../mediawikis/Xop_mediawiki_loader.java | 4 + .../parsers/mediawikis/Xop_mediawiki_mgr.java | 51 + .../parsers/mediawikis/Xop_mediawiki_wkr.java | 66 + .../mediawikis/Xop_mediawiki_wkr__tst.java | 39 + .../Xow_page_cache_wkr__embeddable.java | 30 + .../servers/https/Http_long_poll_cmd.java | 68 + .../servers/https/Http_send_msg_cmd.java | 12 + .../servers/https/Xoax_long_poll_addon.java | 13 + .../servers/https/Xog_cbk_wkr__http.java | 14 + .../xowa/addons/wikis/ctgs/Xoa_ctg_mgr.java | 17 + .../addons/wikis/ctgs/Xoax_ctg_addon.java | 40 + .../xowa/addons/wikis/ctgs/Xoctg_ctg_itm.java | 15 + .../addons/wikis/ctgs/Xoctg_page_xtn.java | 6 + .../wikis/ctgs/bldrs/Xoax_ctg_bldr_addon.java | 12 + .../wikis/ctgs/bldrs/Xob_catlink_cmd.java | 41 + .../wikis/ctgs/bldrs/Xob_catlink_mgr.java | 80 + .../wikis/ctgs/bldrs/Xob_catlink_wkr.java | 128 + .../wikis/ctgs/bldrs/Xob_pageprop_cmd.java | 44 + .../addons/wikis/ctgs/dbs/Xodb_cat_db_.java | 9 + .../wikis/ctgs/dbs/Xodb_cat_link_row.java | 17 + .../wikis/ctgs/dbs/Xodb_cat_link_tbl.java | 108 + .../wikis/ctgs/dbs/Xodb_cat_sort_tbl.java | 27 + .../wikis/ctgs/dbs/Xodb_tmp_cat_db.java | 14 + .../ctgs/dbs/Xodb_tmp_cat_hidden_tbl.java | 27 + .../wikis/ctgs/dbs/Xodb_tmp_cat_link_tbl.java | 44 + .../wikis/ctgs/edits/Xoctg_edit_mgr.java | 138 + .../ctgs/enums/Xoctg_collation_enum.java | 14 + .../wikis/ctgs/enums/Xoctg_type_enum.java | 15 + .../htmls/catpages/Xoctg_catpage_mgr.java | 88 + .../Xoctg_catpage_mgr__basic__tst.java | 296 + .../Xoctg_catpage_mgr__navlink__tst.java | 42 + .../catpages/dbs/Xoctg_catlink_loader.java | 176 + .../dbs/Xoctg_catlink_loader__tst.java | 22 + .../catpages/dbs/Xoctg_catpage_loader.java | 54 + .../htmls/catpages/dbs/Xoctg_page_loader.java | 25 + .../catpages/doms/Xoctg_catpage_ctg.java | 20 + .../catpages/doms/Xoctg_catpage_grp.java | 37 + .../catpages/doms/Xoctg_catpage_itm.java | 84 + .../catpages/doms/Xoctg_catpage_tmp.java | 26 + .../htmls/catpages/fmts/Xoctg_fmt_grp.java | 92 + .../catpages/fmts/Xoctg_fmt_itm_base.java | 84 + .../catpages/fmts/Xoctg_fmt_itm_page.java | 25 + .../catpages/fmts/Xoctg_fmt_itm_subc.java | 52 + .../htmls/catpages/fmts/Xoctg_fmt_ltr.java | 69 + .../catpages/langs/Xoctg_collation_mgr.java | 16 + .../catpages/langs/Xoctg_collation_wkr.java | 50 + .../catpages/langs/Xoctg_collation_wkr_.java | 19 + .../langs/Xoctg_collation_wkr___tst.java | 24 + .../catpages/urls/Xoctg_catpage_url.java | 18 + .../catpages/urls/Xoctg_catpage_url__tst.java | 30 + .../urls/Xoctg_catpage_url_parser.java | 78 + .../htmls/pageboxs/Xoctg_pagebox_hash.java | 39 + .../htmls/pageboxs/Xoctg_pagebox_itm.java | 33 + .../htmls/pageboxs/Xoctg_pagebox_loader.java | 60 + .../htmls/pageboxs/Xoctg_pagebox_wtr.java | 57 + .../pageboxs/doubles/Xoctg_double_box.java | 35 + .../doubles/Xoctg_double_box__tst.java | 68 + .../pageboxs/doubles/Xoctg_double_grp.java | 41 + .../pageboxs/doubles/Xoctg_double_itm.java | 43 + .../pageboxs/singles/Xoctg_single_box.java | 25 + .../singles/Xoctg_single_box__tst.java | 45 + .../pageboxs/singles/Xoctg_single_itm.java | 24 + .../addons/wikis/directorys/Xowdir_addon.java | 36 + .../wikis/directorys/dbs/Xowdir_db_mgr.java | 9 + .../wikis/directorys/dbs/Xowdir_db_utl.java | 8 + .../wikis/directorys/dbs/Xowdir_wiki_itm.java | 13 + .../directorys/dbs/Xowdir_wiki_json.java | 31 + .../directorys/dbs/Xowdir_wiki_props.java | 27 + .../directorys/dbs/Xowdir_wiki_props_mgr.java | 75 + .../dbs/Xowdir_wiki_props_mgr_.java | 4 + .../dbs/Xowdir_wiki_props_mgr__tst.java | 55 + .../dbs/Xowdir_wiki_props_mgr__xowa.java | 42 + .../wikis/directorys/dbs/Xowdir_wiki_tbl.java | 57 + .../specials/items/Xowdir_item_bridge.java | 31 + .../specials/items/Xowdir_item_doc.java | 41 + .../specials/items/Xowdir_item_html.java | 35 + .../specials/items/Xowdir_item_mgr.java | 176 + .../specials/items/Xowdir_item_special.java | 15 + .../specials/items/bldrs/Xodb_wiki_data.java | 11 + .../specials/items/bldrs/Xodb_wiki_db.java | 28 + .../items/bldrs/Xodb_wiki_db_tid.java | 4 + .../specials/items/bldrs/Xodb_wiki_mgr.java | 13 + .../specials/items/bldrs/Xopg_db_mgr.java | 122 + .../specials/items/bldrs/Xow_db_mkr.java | 119 + .../items/bldrs/Xow_wiki_factory.java | 35 + .../items/bldrs/Xow_wiki_upgrade_.java | 97 + .../specials/lists/Xowdir_list_bridge.java | 27 + .../specials/lists/Xowdir_list_doc.java | 16 + .../specials/lists/Xowdir_list_html.java | 25 + .../specials/lists/Xowdir_list_special.java | 11 + .../specials/lists/Xowdir_list_svc.java | 49 + .../fulltexts/Xosearch_fulltext_addon.java | 33 + .../fulltexts/core/Xofulltext_extractor.java | 58 + .../core/Xofulltext_extractor__tst.java | 30 + .../fulltexts/core/Xofulltext_punct_.java | 5 + .../bldrs/Xofulltext_indexer_args.java | 58 + .../bldrs/Xofulltext_indexer_cmd.java | 18 + .../bldrs/Xofulltext_indexer_mgr.java | 80 + .../bldrs/Xofulltext_indexer_wkr.java | 33 + .../specials/Xofulltext_indexer_html.java | 47 + .../specials/Xofulltext_indexer_special.java | 21 + .../svcs/Xofulltext_indexer_bridge.java | 26 + .../indexers/svcs/Xofulltext_indexer_svc.java | 62 + .../indexers/svcs/Xofulltext_indexer_ui.java | 15 + .../caches/Xofulltext_cache_line.java | 9 + .../caches/Xofulltext_cache_mgr.java | 73 + .../caches/Xofulltext_cache_page.java | 12 + .../caches/Xofulltext_cache_qry.java | 11 + .../searchers/mgrs/Xofulltext_args_qry.java | 64 + .../searchers/mgrs/Xofulltext_args_wiki.java | 31 + .../searchers/mgrs/Xofulltext_searcher.java | 7 + .../brutes/Xofulltext_searcher__brute.java | 79 + .../brutes/finders/Xofulltext_finder_cbk.java | 7 + .../finders/Xofulltext_finder_cbk__eval.java | 15 + .../Xofulltext_finder_cbk__eval__tst.java | 107 + .../Xofulltext_finder_cbk__highlight.java | 71 + .../brutes/finders/Xofulltext_finder_mgr.java | 66 + .../finders/Xofulltext_word_bounds.java | 9 + .../brutes/finders/Xofulltext_word_lang.java | 102 + .../brutes/finders/Xofulltext_word_node.java | 54 + .../brutes/finders/Xofulltext_word_node_.java | 62 + .../gflucenes/Xofulltext_highlighter_mgr.java | 72 + .../Xofulltext_searcher__lucene.java | 83 + .../mgrs/uis/Xofulltext_searcher_line.java | 13 + .../mgrs/uis/Xofulltext_searcher_page.java | 15 + .../mgrs/uis/Xofulltext_searcher_ui.java | 62 + .../specials/Xofulltext_searcher_html.java | 102 + .../specials/Xofulltext_searcher_special.java | 33 + .../svcs/Xofulltext_searcher_bridge.java | 32 + .../svcs/Xofulltext_searcher_svc.java | 172 + .../wikis/htmls/css/bldrs/Xob_css_cmd.java | 37 + .../wikis/htmls/css/dbs/Css_db_mgr.java | 33 + .../htmls/css/dbs/Xowd_css_core_itm.java | 9 + .../htmls/css/dbs/Xowd_css_core_tbl.java | 56 + .../htmls/css/dbs/Xowd_css_file_itm.java | 7 + .../htmls/css/dbs/Xowd_css_file_tbl.java | 46 + .../wikis/htmls/css/mgrs/Xob_css_status.java | 62 + .../wikis/htmls/css/mgrs/Xow_css_mgr.java | 37 + .../htmls/css/mgrs/Xowd_css_core_mgr.java | 53 + .../htmls/css/mgrs/Xowd_css_core_mgr_tst.java | 103 + .../wikis/imports/Xow_import_addon.java | 25 + .../wikis/imports/Xow_import_dir_cbk.java | 5 + .../addons/wikis/imports/Xow_import_doc.java | 66 + .../addons/wikis/imports/Xow_import_html.java | 22 + .../wikis/imports/Xow_import_special.java | 44 + .../addons/wikis/pagebaks/Pagebaks_addon.java | 40 + .../wikis/pages/randoms/Rndm_addon.java | 33 + .../pages/randoms/bldrs/Rndm_bldr_cmd.java | 20 + .../pages/randoms/bldrs/Rndm_bldr_wkr.java | 55 + .../randoms/bldrs/Rndm_ns_rebuilder.java | 53 + .../wikis/pages/randoms/dbs/Rndm_db_mgr.java | 37 + .../wikis/pages/randoms/dbs/Rndm_qry_itm.java | 24 + .../wikis/pages/randoms/dbs/Rndm_qry_tbl.java | 39 + .../wikis/pages/randoms/dbs/Rndm_rng_itm.java | 13 + .../wikis/pages/randoms/dbs/Rndm_rng_tbl.java | 38 + .../wikis/pages/randoms/dbs/Rndm_seq_tbl.java | 35 + .../wikis/pages/randoms/mgrs/Rndm_mgr.java | 24 + .../wikis/pages/randoms/mgrs/Rndm_ns_mgr.java | 12 + .../randoms/specials/Rndm_page_special.java | 18 + .../randoms/specials/Rndm_root_special.java | 18 + .../specials/Rndm_root_special_tst.java | 39 + .../wikis/pages/syncs/Xosync_addon.java | 22 + .../pages/syncs/core/Xosync_read_mgr.java | 125 + .../pages/syncs/core/Xosync_update_mgr.java | 92 + .../core/loaders/Xosync_page_loader.java | 94 + .../core/loaders/Xosync_page_loader__fxt.java | 35 + .../core/loaders/Xosync_page_loader__tst.java | 24 + .../core/parsers/Xosync_hdoc_parser.java | 89 + .../parsers/Xosync_hdoc_parser__err__tst.java | 30 + .../Xosync_hdoc_parser__file__tst.java | 41 + .../core/parsers/Xosync_hdoc_parser__fxt.java | 54 + .../Xosync_hdoc_parser__misc__tst.java | 11 + .../core/parsers/Xosync_hdoc_parser__tst.java | 24 + .../syncs/core/parsers/Xosync_hdoc_wtr.java | 26 + .../core/parsers/Xosync_img_src_parser.java | 217 + .../syncs/core/parsers/Xowd_html_tbl_mgr.java | 21 + .../pages/syncs/dbs/Xosync_sync_tbl.java | 24 + .../syncs/specials/Sync_html_special.java | 30 + .../pages/syncs/wmapis/Xowm_parse_data.java | 21 + .../pages/syncs/wmapis/Xowm_parse_wmf.java | 68 + .../wikis/registrys/Wiki_registry_addon.java | 12 + .../wikis/registrys/infos/Xow_info_doc.java | 27 + .../wikis/registrys/infos/Xow_info_html.java | 47 + .../registrys/infos/Xow_info_special.java | 44 + .../wikis/registrys/lists/Xow_list_doc.java | 29 + .../wikis/registrys/lists/Xow_list_html.java | 27 + .../registrys/lists/Xow_list_special.java | 27 + .../wikis/searchs/Srch_search_addon.java | 39 + .../wikis/searchs/Srch_search_addon_api.java | 6 + .../searchs/Xoax_builds_search_addon.java | 13 + .../wikis/searchs/bldrs/Srch_bldr_cmd.java | 17 + .../wikis/searchs/bldrs/Srch_bldr_mgr_.java | 21 + .../wikis/searchs/bldrs/Srch_bldr_wkr.java | 20 + .../searchs/bldrs/Srch_temp_tbl_wkr.java | 202 + .../bldrs/cmds/Xobldr__link__link_score.java | 177 + .../bldrs/cmds/Xobldr__page__page_score.java | 104 + .../bldrs/cmds/Xobldr__word__link_count.java | 39 + .../cmds/adjustments/Adjustment_cmd.java | 148 + .../cmds/adjustments/Page_matcher_itm.java | 41 + .../cmds/adjustments/Page_matcher_mgr.java | 20 + .../cmds/adjustments/Page_matcher_wkr.java | 56 + .../bldrs/cmds/adjustments/Page_stub.java | 23 + .../addons/wikis/searchs/dbs/Srch_db_cfg.java | 25 + .../wikis/searchs/dbs/Srch_db_cfg_.java | 44 + .../addons/wikis/searchs/dbs/Srch_db_mgr.java | 117 + .../wikis/searchs/dbs/Srch_db_mgr_.java | 5 + .../wikis/searchs/dbs/Srch_db_upgrade.java | 35 + .../wikis/searchs/dbs/Srch_link_reg_tbl.java | 27 + .../wikis/searchs/dbs/Srch_link_row.java | 15 + .../wikis/searchs/dbs/Srch_link_tbl.java | 39 + .../wikis/searchs/dbs/Srch_temp_tbl.java | 31 + .../wikis/searchs/dbs/Srch_word_row.java | 19 + .../wikis/searchs/dbs/Srch_word_tbl.java | 64 + .../gui/htmlbars/Srch_htmlbar_mgr.java | 37 + .../searchs/gui/urlbars/Srch_urlbar_mgr.java | 85 + .../searchs/parsers/Srch_highlight_mgr.java | 100 + .../parsers/Srch_highlight_mgr_tst.java | 30 + .../searchs/parsers/Srch_sym_parser.java | 245 + .../searchs/parsers/Srch_text_parser.java | 157 + .../searchs/parsers/Srch_text_parser_tst.java | 130 + .../searchs/parsers/Srch_text_parser_wkr.java | 8 + .../wikis/searchs/parsers/Srch_word_hash.java | 16 + .../wikis/searchs/parsers/Srch_word_itm.java | 11 + .../wikis/searchs/searchers/Srch_ns_mgr.java | 82 + .../searchs/searchers/Srch_search_cmd.java | 38 + .../searchs/searchers/Srch_search_ctx.java | 52 + .../searchs/searchers/Srch_search_mgr.java | 85 + .../searchs/searchers/Srch_search_phrase.java | 119 + .../searchers/Srch_search_phrase_tst.java | 31 + .../searchs/searchers/Srch_search_qry.java | 34 + .../cbks/Srch_rslt_cbk__suggest_box.java | 40 + .../cbks/Srch_rslt_cbk__url_bar.java | 68 + .../searchs/searchers/crts/Srch_crt_itm.java | 31 + .../searchs/searchers/crts/Srch_crt_mgr.java | 27 + .../searchers/crts/Srch_crt_parser.java | 129 + .../searchers/crts/Srch_crt_parser_tst.java | 51 + .../searchers/crts/Srch_crt_scanner.java | 119 + .../searchers/crts/Srch_crt_scanner_syms.java | 97 + .../searchers/crts/Srch_crt_scanner_tst.java | 68 + .../searchs/searchers/crts/Srch_crt_sql.java | 59 + .../searchers/crts/Srch_crt_sql_tst.java | 23 + .../searchs/searchers/crts/Srch_crt_tkn.java | 21 + .../searchers/crts/Srch_crt_visitor.java | 4 + .../visitors/Srch_crt_visitor__print.java | 32 + .../visitors/Srch_crt_visitor__words.java | 45 + .../searchers/rslts/Srch_rslt_cache.java | 15 + .../searchers/rslts/Srch_rslt_cbk.java | 5 + .../searchers/rslts/Srch_rslt_cbk_.java | 8 + .../searchers/rslts/Srch_rslt_list.java | 46 + .../searchers/rslts/Srch_rslt_list_.java | 36 + .../searchers/rslts/Srch_rslt_row.java | 44 + .../searchers/slabs/Srch_slab_itm.java | 7 + .../searchers/slabs/Srch_slab_itm_parser.java | 17 + .../searchs/searchers/wkrs/Srch_link_wkr.java | 147 + .../searchers/wkrs/Srch_link_wkr_.java | 124 + .../searchers/wkrs/Srch_link_wkr_sql.java | 139 + .../searchers/wkrs/Srch_link_wkr_sql_tst.java | 22 + .../searchers/wkrs/Srch_page_tbl_wkr.java | 56 + .../searchers/wkrs/Srch_page_tbl_wkr_tst.java | 30 + .../searchers/wkrs/Srch_word_count_wkr.java | 61 + .../wikis/searchs/specials/Srch_qarg_mgr.java | 47 + .../searchs/specials/Srch_special_cfg.java | 52 + .../searchs/specials/Srch_special_cmd.java | 88 + .../searchs/specials/Srch_special_page.java | 96 + .../specials/Srch_special_searcher.java | 53 + .../specials/Srch_special_searcher_tst.java | 206 + .../specials/Xow_domain_sorter__manual.java | 104 + .../specials/htmls/Srch_html_page_bldr.java | 82 + .../htmls/Srch_html_page_bldr_tst.java | 68 + .../specials/htmls/Srch_html_row_bldr.java | 35 + .../specials/htmls/Srch_html_row_wkr.java | 51 + .../specials/htmls/Srch_rslt_cbk_tst.java | 49 + 400_xowa/src/gplx/xowa/apps/Xoa_app_eval.java | 10 + .../src/gplx/xowa/apps/Xoa_app_eval_tst.java | 25 + 400_xowa/src/gplx/xowa/apps/Xoa_app_mode.java | 49 + 400_xowa/src/gplx/xowa/apps/Xoa_cur.java | 14 + 400_xowa/src/gplx/xowa/apps/Xoa_shell.java | 25 + .../src/gplx/xowa/apps/Xoa_shell_tst.java | 13 + 400_xowa/src/gplx/xowa/apps/Xoa_stage_.java | 4 + 400_xowa/src/gplx/xowa/apps/Xoa_sys_cfg.java | 74 + 400_xowa/src/gplx/xowa/apps/Xoa_thread_.java | 13 + .../src/gplx/xowa/apps/Xoa_thread_mgr.java | 10 + 400_xowa/src/gplx/xowa/apps/Xoav_app.java | 100 + .../src/gplx/xowa/apps/Xoav_wiki_mgr.java | 33 + .../src/gplx/xowa/apps/apis/Xoapi_root.java | 55 + .../gplx/xowa/apps/apis/xowa/Xoapi_addon.java | 13 + .../gplx/xowa/apps/apis/xowa/Xoapi_app.java | 21 + .../gplx/xowa/apps/apis/xowa/Xoapi_bldr.java | 11 + .../gplx/xowa/apps/apis/xowa/Xoapi_gui.java | 19 + .../gplx/xowa/apps/apis/xowa/Xoapi_html.java | 17 + .../gplx/xowa/apps/apis/xowa/Xoapi_nav.java | 23 + .../gplx/xowa/apps/apis/xowa/Xoapi_usr.java | 22 + .../gplx/xowa/apps/apis/xowa/Xoapi_xtns.java | 13 + .../apis/xowa/addons/Xoapi_addon_bldr.java | 10 + .../apis/xowa/addons/Xoapi_addon_search.java | 16 + .../xowa/addons/bldrs/Xoapi_central_api.java | 12 + .../xowa/addons/bldrs/Xopg_match_mgr.java | 78 + .../addons/bldrs/Xopg_match_mgr__tst.java | 53 + .../addons/searchs/Xoapi_search_mode_.java | 17 + .../xowa/apps/apis/xowa/apps/Xoapi_fsys.java | 16 + .../apps/apis/xowa/bldrs/Xoapi_bldr_wiki.java | 17 + .../bldrs/filters/titles/Xoapi_title.java | 32 + .../apis/xowa/bldrs/imports/Xoapi_import.java | 19 + .../apis/xowa/bldrs/runners/Xoapi_runner.java | 30 + .../apps/apis/xowa/gui/Xoapi_browser.java | 60 + .../xowa/apps/apis/xowa/gui/Xoapi_font.java | 34 + .../xowa/apps/apis/xowa/gui/Xoapi_page.java | 19 + .../xowa/gui/browsers/Xoapi_allpages.java | 17 + .../apis/xowa/gui/browsers/Xoapi_find.java | 75 + .../xowa/gui/browsers/Xoapi_html_box.java | 26 + .../apis/xowa/gui/browsers/Xoapi_info.java | 20 + .../apis/xowa/gui/browsers/Xoapi_prog.java | 14 + .../xowa/gui/browsers/Xoapi_prog_log.java | 12 + .../apis/xowa/gui/browsers/Xoapi_search.java | 17 + .../apis/xowa/gui/browsers/Xoapi_tabs.java | 73 + .../apis/xowa/gui/browsers/Xoapi_url.java | 68 + .../apps/apis/xowa/gui/pages/Xoapi_edit.java | 41 + .../apis/xowa/gui/pages/Xoapi_selection.java | 35 + .../apps/apis/xowa/gui/pages/Xoapi_view.java | 46 + .../apps/apis/xowa/html/Xoapi_modules.java | 13 + .../xowa/apps/apis/xowa/html/Xoapi_page.java | 13 + .../apps/apis/xowa/html/Xoapi_toggle_itm.java | 74 + .../apps/apis/xowa/html/Xoapi_toggle_mgr.java | 84 + .../apis/xowa/html/modules/Xoapi_popups.java | 27 + .../apps/apis/xowa/navs/Xoapi_nav_wiki.java | 24 + .../apps/apis/xowa/usrs/Xoapi_bookmarks.java | 44 + .../apps/apis/xowa/usrs/Xoapi_history.java | 16 + .../apps/apis/xowa/xtns/Xoapi_wikibase.java | 31 + .../gplx/xowa/apps/boots/Xoa_boot_mgr.java | 140 + .../gplx/xowa/apps/boots/Xoa_cmd_arg_mgr.java | 89 + .../xowa/apps/boots/Xoa_cmd_arg_mgr_.java | 31 + .../xowa/apps/caches/Wdata_doc_cache.java | 9 + .../gplx/xowa/apps/caches/Xoa_cache_mgr.java | 7 + .../gplx/xowa/apps/cfgs/Xocfg_gui_mgr.java | 12 + .../src/gplx/xowa/apps/cfgs/Xocfg_win.java | 36 + .../src/gplx/xowa/apps/cfgs/Xowc_parser.java | 21 + .../gplx/xowa/apps/cfgs/Xowc_xtn_pages.java | 67 + .../xowa/apps/cfgs/Xowc_xtn_pages_tst.java | 55 + .../src/gplx/xowa/apps/cfgs/Xowc_xtns.java | 11 + .../gplx/xowa/apps/fmtrs/Gfo_sort_able.java | 4 + .../gplx/xowa/apps/fmtrs/Xoa_fmtr_itm.java | 49 + .../xowa/apps/fmtrs/Xoa_fmtr_itm_tst.java | 25 + .../gplx/xowa/apps/fmtrs/Xoa_fmtr_mgr.java | 9 + .../xowa/apps/fmtrs/Xoa_fmtr_sort_mgr.java | 49 + .../gplx/xowa/apps/fsys/Xoa_fsys_eval.java | 26 + .../src/gplx/xowa/apps/fsys/Xoa_fsys_mgr.java | 49 + .../gplx/xowa/apps/fsys/Xoa_url_finder.java | 32 + .../xowa/apps/fsys/Xoa_url_finder_tst.java | 49 + .../gplx/xowa/apps/gfs/Gfs_php_converter.java | 129 + .../gfs/Gfs_php_converter__to_gfs__tst.java | 38 + .../gfs/Gfs_php_converter__to_php__tst.java | 13 + .../src/gplx/xowa/apps/gfs/Xoa_gfs_bldr.java | 72 + .../src/gplx/xowa/apps/gfs/Xoa_gfs_mgr.java | 53 + .../src/gplx/xowa/apps/gfs/Xoa_gfs_mgr_.java | 13 + .../src/gplx/xowa/apps/gfs/Xoa_gfs_wtr_.java | 21 + .../gplx/xowa/apps/metas/Xoa_meta_mgr.java | 25 + .../gplx/xowa/apps/miscs/Xoa_misc_mgr.java | 30 + .../gplx/xowa/apps/progs/Xoa_prog_mgr.java | 129 + .../xowa/apps/servers/Gxw_html_server.java | 69 + .../apps/servers/http/File_retrieve_mode.java | 19 + .../apps/servers/http/Http_data__client.java | 10 + .../xowa/apps/servers/http/Http_file_utl.java | 33 + .../apps/servers/http/Http_server_mgr.java | 141 + .../apps/servers/http/Http_server_socket.java | 47 + .../apps/servers/http/Http_server_wkr.java | 168 + .../apps/servers/http/Http_server_wkr_.java | 22 + .../servers/http/Http_server_wkr__tst.java | 25 + .../servers/http/Http_server_wkr_pool.java | 26 + .../xowa/apps/servers/tcp/Socket_rdr.java | 38 + .../xowa/apps/servers/tcp/Socket_wtr.java | 34 + .../apps/servers/tcp/Xosrv_cmd_types.java | 8 + .../gplx/xowa/apps/servers/tcp/Xosrv_msg.java | 104 + .../xowa/apps/servers/tcp/Xosrv_msg_rdr.java | 47 + .../apps/servers/tcp/Xosrv_msg_rdr_tst.java | 48 + .../xowa/apps/servers/tcp/Xosrv_server.java | 113 + .../apps/servers/tcp/Xosrv_server_tst.java | 23 + .../apps/servers/tcp/Xosrv_socket_rdr.java | 31 + .../apps/servers/tcp/Xosrv_socket_wtr.java | 17 + .../apps/servers/tcp/Xowa_tcp_console.java | 213 + .../gplx/xowa/apps/setups/Xoa_setup_mgr.java | 59 + .../xowa/apps/setups/Xoa_setup_mgr_tst.java | 19 + .../xowa/apps/shells/Gfo_log__console.java | 15 + .../site_cfgs/Xoa_site_cfg_itm__base.java | 20 + .../Xoa_site_cfg_itm__extensiontags.java | 31 + .../Xoa_site_cfg_itm__interwikimap.java | 108 + .../apps/site_cfgs/Xoa_site_cfg_loader.java | 19 + .../site_cfgs/Xoa_site_cfg_loader__cfg.java | 27 + .../Xoa_site_cfg_loader__fallback.java | 6 + .../site_cfgs/Xoa_site_cfg_loader__fsys.java | 13 + .../site_cfgs/Xoa_site_cfg_loader__inet.java | 49 + .../xowa/apps/site_cfgs/Xoa_site_cfg_mgr.java | 52 + .../apps/site_cfgs/Xoa_site_cfg_mgr_tst.java | 163 + .../xowa/apps/urls/Xoa_url__basic__tst.java | 15 + .../xowa/apps/urls/Xoa_url__to_str__tst.java | 39 + .../src/gplx/xowa/apps/urls/Xoav_url.java | 8 + .../gplx/xowa/apps/urls/Xoav_url_parser.java | 26 + .../xowa/apps/urls/Xoav_url_parser_tst.java | 26 + .../gplx/xowa/apps/urls/Xow_url_parser.java | 305 + .../apps/urls/Xow_url_parser__proto_tst.java | 25 + .../apps/urls/Xow_url_parser__qarg__tst.java | 47 + .../apps/urls/Xow_url_parser__ttl_tst.java | 39 + .../urls/Xow_url_parser__url_bar_tst.java | 51 + .../apps/urls/Xow_url_parser__wiki_tst.java | 24 + .../apps/urls/Xow_url_parser__xcmd_tst.java | 14 + .../apps/urls/Xow_url_parser__xwiki_tst.java | 63 + .../xowa/apps/urls/Xow_url_parser_fxt.java | 66 + .../gplx/xowa/apps/utls/Xoa_url_encoder.java | 39 + .../xowa/apps/utls/Xoa_url_encoder__tst.java | 14 + .../gplx/xowa/apps/versions/Xoa_version_.java | 21 + .../xowa/apps/versions/Xoa_version_tst.java | 20 + .../xowa/apps/wms/apis/Xowm_api_bldr.java | 8 + .../xowa/apps/wms/apis/Xowmf_api_mgr.java | 5 + .../apps/wms/apis/origs/Xoapi_orig_base.java | 17 + .../wms/apis/origs/Xoapi_orig_base_tst.java | 45 + .../apps/wms/apis/origs/Xoapi_orig_mok.java | 19 + .../apps/wms/apis/origs/Xoapi_orig_rslts.java | 14 + .../apps/wms/apis/origs/Xoapi_orig_wmf.java | 80 + .../apis/revisions/Xowm_revision_base.java | 74 + .../wms/apis/revisions/Xowm_revn_data.java | 23 + 400_xowa/src/gplx/xowa/bldrs/Db_idx_mode.java | 21 + 400_xowa/src/gplx/xowa/bldrs/Db_mgr_fxt.java | 77 + .../src/gplx/xowa/bldrs/Xob_base_fxt.java | 62 + 400_xowa/src/gplx/xowa/bldrs/Xob_bldr.java | 152 + .../src/gplx/xowa/bldrs/Xob_cmd_keys.java | 34 + 400_xowa/src/gplx/xowa/bldrs/Xob_cmd_mgr.java | 115 + .../src/gplx/xowa/bldrs/Xob_cmd_regy.java | 13 + 400_xowa/src/gplx/xowa/bldrs/Xob_db_file.java | 39 + 400_xowa/src/gplx/xowa/bldrs/Xob_fxt.java | 137 + .../src/gplx/xowa/bldrs/Xob_ns_to_db_mgr.java | 72 + .../src/gplx/xowa/bldrs/Xob_ns_to_db_wkr.java | 7 + .../src/gplx/xowa/bldrs/Xob_page_wkr_cmd.java | 81 + 400_xowa/src/gplx/xowa/bldrs/Xobd_parser.java | 49 + .../src/gplx/xowa/bldrs/Xobd_parser_wkr.java | 8 + .../src/gplx/xowa/bldrs/Xobdc_merger.java | 26 + 400_xowa/src/gplx/xowa/bldrs/Xobdc_utl.java | 36 + 400_xowa/src/gplx/xowa/bldrs/Xobldr_cfg.java | 46 + .../gplx/xowa/bldrs/aria2/Aria2_lib_mgr.java | 34 + .../xowa/bldrs/aria2/Gfui_process_win.java | 23 + .../xowa/bldrs/cmds/Xob_dump_mgr_base.java | 310 + .../gplx/xowa/bldrs/cmds/Xob_ns_file_itm.java | 47 + .../bldrs/cmds/Xob_ns_file_itm_parser.java | 76 + .../bldrs/cmds/Xob_parse_all_src_sql.java | 76 + .../bldrs/cmds/diffs/Bfr_arg__dump_dir.java | 28 + .../bldrs/cmds/diffs/Xob_diff_build_cmd.java | 29 + .../bldrs/cmds/diffs/Xob_diff_build_wkr.java | 78 + .../bldrs/cmds/diffs/Xob_diff_manifest.java | 32 + .../xowa/bldrs/cmds/diffs/Xowd_tbl_mapr.java | 17 + .../xowa/bldrs/cmds/texts/Xob_init_base.java | 50 + .../xowa/bldrs/cmds/texts/Xob_term_base.java | 40 + .../bldrs/cmds/texts/sqls/Xob_init_cmd.java | 26 + .../texts/sqls/Xob_ns_to_db_wkr__text.java | 13 + .../bldrs/cmds/texts/sqls/Xob_page_cmd.java | 94 + .../cmds/texts/sqls/Xob_page_delete_cmd.java | 74 + .../bldrs/cmds/texts/sqls/Xob_term_cmd.java | 20 + .../bldrs/cmds/texts/tdbs/Io_sort_cmd_ns.java | 44 + .../cmds/texts/tdbs/Srch_bldr_wkr_base.java | 90 + .../cmds/texts/tdbs/Xob_calc_stats_cmd.java | 98 + .../cmds/texts/tdbs/Xob_init_base_tst.java | 26 + .../bldrs/cmds/texts/tdbs/Xob_init_tdb.java | 11 + .../cmds/texts/tdbs/Xob_make_cmd_site.java | 121 + .../cmds/texts/tdbs/Xob_make_id_wkr.java | 21 + .../tdbs/Xob_parse_dump_templates_cmd.java | 19 + .../cmds/texts/xmls/Xob_siteinfo_nde.java | 32 + .../cmds/texts/xmls/Xob_siteinfo_parser_.java | 64 + .../texts/xmls/Xob_siteinfo_parser__tst.java | 93 + .../xowa/bldrs/cmds/utils/Xob_alert_cmd.java | 23 + .../bldrs/cmds/utils/Xob_cleanup_cmd.java | 116 + .../bldrs/cmds/utils/Xob_core_batch_utl.java | 28 + .../cmds/utils/Xob_decompress_bz2_cmd.java | 38 + .../xowa/bldrs/cmds/utils/Xob_delete_cmd.java | 32 + .../bldrs/cmds/utils/Xob_download_cmd.java | 70 + .../bldrs/cmds/utils/Xob_exec_sql_cmd.java | 28 + .../bldrs/cmds/utils/Xob_site_meta_cmd.java | 76 + .../xowa/bldrs/cmds/utils/Xob_unzip_wkr.java | 25 + .../bldrs/cmds/utils/Xob_xml_dumper_cmd.java | 49 + .../xowa/bldrs/css/Xoa_css_extractor.java | 280 + .../css/Xoa_css_extractor_basic_tst.java | 62 + .../xowa/bldrs/css/Xoa_css_extractor_fxt.java | 56 + .../bldrs/css/Xoa_css_extractor_wiki_tst.java | 29 + .../bldrs/css/Xoa_css_img_downloader.java | 174 + .../bldrs/css/Xoa_css_img_downloader_tst.java | 166 + .../gplx/xowa/bldrs/css/Xob_css_parser.java | 40 + .../bldrs/css/Xob_css_parser__import.java | 26 + .../bldrs/css/Xob_css_parser__import_tst.java | 21 + .../xowa/bldrs/css/Xob_css_parser__url.java | 41 + .../bldrs/css/Xob_css_parser__url_tst.java | 43 + .../xowa/bldrs/css/Xob_css_tkn__base.java | 101 + .../gplx/xowa/bldrs/css/Xob_mirror_mgr.java | 42 + .../xowa/bldrs/css/Xob_mirror_mgr_tst.java | 46 + .../gplx/xowa/bldrs/css/Xob_url_fixer.java | 82 + .../xowa/bldrs/css/Xob_url_fixer_tst.java | 25 + .../xowa/bldrs/css/Xobc_download_itm.java | 8 + .../filters/core/Xob_ttl_filter_mgr.java | 26 + .../filters/core/Xob_ttl_filter_mgr_srl.java | 22 + .../core/Xob_ttl_filter_mgr_srl_tst.java | 37 + .../filters/core/Xob_ttl_filter_mgr_tst.java | 34 + .../dansguardians/Crt__match_base.java | 30 + .../bldrs/filters/dansguardians/Dg_file.java | 68 + .../filters/dansguardians/Dg_log_mgr.java | 158 + .../filters/dansguardians/Dg_match_mgr.java | 173 + .../dansguardians/Dg_match_mgr_tst.java | 41 + .../filters/dansguardians/Dg_ns_skip_mgr.java | 25 + .../filters/dansguardians/Dg_parser.java | 78 + .../filters/dansguardians/Dg_parser_tst.java | 40 + .../gplx/xowa/bldrs/infos/Xob_info_file.java | 53 + .../xowa/bldrs/infos/Xob_info_session.java | 44 + .../xowa/bldrs/installs/Xoi_cmd_base.java | 86 + .../xowa/bldrs/installs/Xoi_cmd_dumpfile.java | 43 + .../bldrs/installs/Xoi_cmd_dumpfile_tst.java | 59 + .../Xoi_cmd_imageMagick_download.java | 32 + .../gplx/xowa/bldrs/installs/Xoi_cmd_mgr.java | 130 + .../bldrs/installs/Xoi_cmd_wiki_download.java | 38 + .../installs/Xoi_cmd_wiki_goto_page.java | 13 + .../bldrs/installs/Xoi_cmd_wiki_import.java | 96 + .../xowa/bldrs/installs/Xoi_cmd_wiki_tst.java | 113 + .../bldrs/installs/Xoi_cmd_wiki_unzip.java | 33 + .../xowa/bldrs/installs/Xoi_dump_mgr.java | 9 + .../bldrs/installs/Xoi_mirror_parser.java | 30 + .../bldrs/installs/Xoi_mirror_parser_tst.java | 43 + .../xowa/bldrs/installs/Xoi_setup_mgr.java | 25 + .../bldrs/setups/addons/Xoi_addon_mgr.java | 11 + .../setups/addons/Xoi_firefox_installer.java | 47 + .../addons/Xoi_firefox_installer_tst.java | 26 + .../bldrs/setups/maints/Wmf_dump_itm.java | 20 + .../setups/maints/Wmf_dump_list_parser.java | 57 + .../maints/Wmf_dump_list_parser_tst.java | 120 + .../bldrs/setups/maints/Wmf_latest_itm.java | 9 + .../setups/maints/Wmf_latest_parser.java | 51 + .../setups/maints/Wmf_latest_parser_tst.java | 36 + .../bldrs/setups/maints/Xoa_maint_mgr.java | 66 + .../setups/maints/Xoa_maint_wikis_mgr.java | 30 + .../bldrs/setups/maints/Xow_maint_mgr.java | 41 + .../setups/upgrades/Xoa_upgrade_mgr.java | 20 + .../setups/upgrades/Xoa_upgrade_mgr_tst.java | 16 + .../xowa/bldrs/sql_dumps/Xosql_dump_cbk.java | 6 + .../bldrs/sql_dumps/Xosql_dump_parser.java | 144 + .../sql_dumps/Xosql_dump_parser__tst.java | 73 + .../xowa/bldrs/sql_dumps/Xosql_fld_itm.java | 38 + .../bldrs/sql_dumps/Xosql_tbl_parser.java | 52 + .../sql_dumps/Xosql_tbl_parser__tst.java | 69 + .../gplx/xowa/bldrs/syncs/Xob_sync_itm.java | 37 + .../bldrs/wiki_cfgs/Xob_subpage_parser.java | 72 + .../bldrs/wiki_cfgs/Xoi_wiki_props_alias.java | 10 + .../bldrs/wiki_cfgs/Xoi_wiki_props_api.java | 69 + .../wiki_cfgs/Xoi_wiki_props_api_tst.java | 125 + .../bldrs/wiki_cfgs/Xoi_wiki_props_ns.java | 10 + .../bldrs/wiki_cfgs/Xoi_wiki_props_wiki.java | 6 + .../src/gplx/xowa/bldrs/wkrs/Xob_cmd.java | 10 + .../gplx/xowa/bldrs/wkrs/Xob_cmd__base.java | 18 + .../gplx/xowa/bldrs/wkrs/Xob_cmd_base.java | 11 + .../gplx/xowa/bldrs/wkrs/Xob_idx_base.java | 22 + .../src/gplx/xowa/bldrs/wkrs/Xob_io_utl_.java | 43 + .../gplx/xowa/bldrs/wkrs/Xob_io_utl__tst.java | 26 + .../xowa/bldrs/wkrs/Xob_itm_basic_base.java | 18 + .../xowa/bldrs/wkrs/Xob_itm_dump_base.java | 38 + .../gplx/xowa/bldrs/wkrs/Xob_page_wkr.java | 8 + .../xowa/bldrs/wkrs/Xob_sql_dump_base.java | 55 + .../src/gplx/xowa/bldrs/wms/Xowm_api_mgr.java | 12 + .../src/gplx/xowa/bldrs/wms/Xowmf_mgr.java | 13 + .../Xowmf_wiki_dump_dirs_parser.java | 18 + .../Xowmf_wiki_dump_dirs_parser_tst.java | 51 + .../xowa/bldrs/wms/dumps/Xowm_dump_file.java | 83 + .../xowa/bldrs/wms/dumps/Xowm_dump_file_.java | 64 + .../bldrs/wms/dumps/Xowm_dump_file_tst.java | 28 + .../xowa/bldrs/wms/dumps/Xowm_dump_type_.java | 32 + .../bldrs/wms/dumps/Xowm_dump_type__tst.java | 12 + .../xowa/bldrs/wms/revs/Wmapi_itm__page.java | 61 + .../bldrs/wms/revs/Wmapi_itm_json_wtr.java | 75 + .../wms/revs/Xowm_json_parser__page.java | 36 + .../xowa/bldrs/wms/revs/Xowm_rev_sync.java | 70 + .../wms/revs/Xowm_rev_wkr__meta__wm.java | 38 + .../wms/revs/Xowm_rev_wkr__meta__wm_tst.java | 66 + .../bldrs/wms/revs/Xwom_rev_wkr__meta.java | 11 + .../xowa/bldrs/wms/sites/Site_core_db.java | 119 + .../xowa/bldrs/wms/sites/Site_core_itm.java | 16 + .../xowa/bldrs/wms/sites/Site_core_tbl.java | 78 + .../bldrs/wms/sites/Site_extension_itm.java | 26 + .../bldrs/wms/sites/Site_extension_tbl.java | 97 + .../bldrs/wms/sites/Site_general_itm.java | 95 + .../wms/sites/Site_interwikimap_itm.java | 26 + .../wms/sites/Site_interwikimap_tbl.java | 76 + .../bldrs/wms/sites/Site_json_fetcher.java | 49 + .../bldrs/wms/sites/Site_json_parser.java | 87 + .../bldrs/wms/sites/Site_json_parser_tst.java | 414 + .../xowa/bldrs/wms/sites/Site_kv_itm.java | 6 + .../xowa/bldrs/wms/sites/Site_kv_tbl.java | 53 + .../bldrs/wms/sites/Site_language_itm.java | 7 + .../bldrs/wms/sites/Site_language_tbl.java | 55 + .../bldrs/wms/sites/Site_library_itm.java | 7 + .../bldrs/wms/sites/Site_library_tbl.java | 55 + .../bldrs/wms/sites/Site_magicword_itm.java | 10 + .../bldrs/wms/sites/Site_magicword_tbl.java | 58 + .../xowa/bldrs/wms/sites/Site_meta_itm.java | 38 + .../wms/sites/Site_meta_parser__general.java | 250 + .../bldrs/wms/sites/Site_namespace_itm.java | 17 + .../bldrs/wms/sites/Site_namespace_tbl.java | 70 + .../wms/sites/Site_namespacealias_itm.java | 11 + .../wms/sites/Site_namespacealias_tbl.java | 55 + .../bldrs/wms/sites/Site_showhook_itm.java | 10 + .../bldrs/wms/sites/Site_showhook_tbl.java | 58 + .../xowa/bldrs/wms/sites/Site_skin_itm.java | 11 + .../xowa/bldrs/wms/sites/Site_skin_tbl.java | 61 + .../wms/sites/Site_specialpagealias_itm.java | 7 + .../wms/sites/Site_specialpagealias_tbl.java | 55 + .../bldrs/wms/sites/Site_statistic_itm.java | 25 + .../bldrs/wms/sites/Site_statistic_tbl.java | 71 + .../xowa/bldrs/wms/sites/Site_val_tbl.java | 51 + .../src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr.java | 24 + .../gplx/xowa/bldrs/wtrs/Xob_tmp_wtr_mgr.java | 28 + .../gplx/xowa/bldrs/wtrs/Xob_tmp_wtr_wkr.java | 6 + .../xowa/bldrs/wtrs/Xob_tmp_wtr_wkr__ttl.java | 9 + .../gplx/xowa/bldrs/xmls/Xob_import_cfg.java | 47 + .../xowa/bldrs/xmls/Xob_import_marker.java | 32 + .../gplx/xowa/bldrs/xmls/Xob_xml_dumper.java | 81 + .../xowa/bldrs/xmls/Xob_xml_dumper_tst.java | 85 + .../xowa/bldrs/xmls/Xob_xml_page_bldr.java | 63 + .../gplx/xowa/bldrs/xmls/Xob_xml_parser.java | 112 + .../gplx/xowa/bldrs/xmls/Xob_xml_parser_.java | 56 + .../xowa/bldrs/xmls/Xob_xml_parser_tst.java | 131 + 400_xowa/src/gplx/xowa/drds/Xod_app.java | 50 + 400_xowa/src/gplx/xowa/drds/Xod_app_tst.java | 56 + .../src/gplx/xowa/drds/Xowd_data_tstr.java | 35 + .../xowa/drds/files/Xod_activity_adp.java | 6 + .../gplx/xowa/drds/files/Xod_file_mgr.java | 22 + .../gplx/xowa/drds/files/Xod_fsys_mgr.java | 18 + .../xowa/drds/ios/assets/Xod_asset_mgr.java | 4 + .../ios/media_scanners/Xod_media_scanner.java | 5 + .../Xod_media_scanner__base.java | 26 + .../gplx/xowa/drds/pages/Xod_page_itm.java | 79 + .../gplx/xowa/drds/pages/Xod_page_mgr.java | 62 + .../gplx/xowa/drds/powers/Xod_power_mgr.java | 5 + .../gplx/xowa/drds/powers/Xod_power_mgr_.java | 15 + .../src/gplx/xowa/files/Xoa_file_mgr.java | 21 + .../src/gplx/xowa/files/Xoa_repo_mgr.java | 50 + .../src/gplx/xowa/files/Xof_bin_updater.java | 26 + .../src/gplx/xowa/files/Xof_cfg_download.java | 38 + .../src/gplx/xowa/files/Xof_exec_tid.java | 8 + 400_xowa/src/gplx/xowa/files/Xof_ext.java | 30 + 400_xowa/src/gplx/xowa/files/Xof_ext_.java | 175 + .../src/gplx/xowa/files/Xof_file_fxt.java | 33 + .../src/gplx/xowa/files/Xof_file_itm.java | 51 + .../src/gplx/xowa/files/Xof_file_mgr.java | 24 + .../src/gplx/xowa/files/Xof_file_wkr.java | 120 + .../src/gplx/xowa/files/Xof_file_wkr_.java | 43 + .../gplx/xowa/files/Xof_file_wkr__tst.java | 16 + .../src/gplx/xowa/files/Xof_fsdb_itm.java | 178 + .../src/gplx/xowa/files/Xof_fsdb_itm_fxt.java | 64 + .../src/gplx/xowa/files/Xof_fsdb_mode.java | 17 + .../src/gplx/xowa/files/Xof_html_elem.java | 10 + .../src/gplx/xowa/files/Xof_img_size.java | 147 + .../src/gplx/xowa/files/Xof_img_size_tst.java | 90 + .../src/gplx/xowa/files/Xof_lnki_page.java | 11 + .../src/gplx/xowa/files/Xof_lnki_time.java | 27 + .../src/gplx/xowa/files/Xof_media_type.java | 13 + .../src/gplx/xowa/files/Xof_mime_minor_.java | 38 + .../xowa/files/Xof_patch_upright_tid_.java | 14 + .../src/gplx/xowa/files/Xof_url_bldr.java | 214 + .../gplx/xowa/files/Xof_url_bldr__tst.java | 50 + 400_xowa/src/gplx/xowa/files/Xof_wkr_mgr.java | 19 + .../src/gplx/xowa/files/Xof_xfer_itm.java | 296 + .../src/gplx/xowa/files/Xof_xfer_itm_.java | 78 + .../src/gplx/xowa/files/Xof_xfer_itm_tst.java | 48 + .../gplx/xowa/files/Xofv_file_mgr_tst.java | 169 + .../src/gplx/xowa/files/Xofv_repo_itm.java | 18 + .../src/gplx/xowa/files/Xofv_repo_mgr.java | 17 + .../gplx/xowa/files/Xog_redlink_thread.java | 14 + .../src/gplx/xowa/files/Xow_file_mgr.java | 161 + .../src/gplx/xowa/files/bins/Bin_fetcher.java | 48 + .../gplx/xowa/files/bins/Io_download_mgr.java | 5 + .../xowa/files/bins/Io_download_mgr_.java | 14 + .../files/bins/Io_download_mgr__memory.java | 11 + .../src/gplx/xowa/files/bins/Xof_bin_mgr.java | 178 + .../xowa/files/bins/Xof_bin_skip_mgr.java | 72 + .../src/gplx/xowa/files/bins/Xof_bin_wkr.java | 10 + .../gplx/xowa/files/bins/Xof_bin_wkr_.java | 19 + .../files/bins/Xof_bin_wkr__fsdb_sql.java | 113 + .../files/bins/Xof_bin_wkr__fsys_base.java | 53 + .../files/bins/Xof_bin_wkr__http_wmf.java | 69 + .../bins/Xof_bin_wkr__http_wmf__tst.java | 50 + .../gplx/xowa/files/caches/Xof_cache_mgr.java | 64 + .../xowa/files/caches/Xof_cache_mgr_tst.java | 55 + .../gplx/xowa/files/caches/Xofc_cfg_mgr.java | 33 + .../gplx/xowa/files/caches/Xofc_dir_itm.java | 13 + .../gplx/xowa/files/caches/Xofc_dir_mgr.java | 75 + .../gplx/xowa/files/caches/Xofc_dir_tbl.java | 70 + .../gplx/xowa/files/caches/Xofc_fil_itm.java | 56 + .../gplx/xowa/files/caches/Xofc_fil_mgr.java | 133 + .../gplx/xowa/files/caches/Xofc_fil_tbl.java | 138 + .../xowa/files/caches/Xou_cache_finder.java | 6 + .../xowa/files/caches/Xou_cache_finder_.java | 39 + .../files/caches/Xou_cache_finder_mem.java | 26 + .../gplx/xowa/files/caches/Xou_cache_itm.java | 72 + .../gplx/xowa/files/caches/Xou_cache_mgr.java | 235 + .../xowa/files/caches/Xou_cache_mgr_tst.java | 97 + .../gplx/xowa/files/caches/Xou_cache_tbl.java | 180 + .../xowa/files/caches/Xou_cache_tbl_tst.java | 44 + .../files/caches/Xou_file_itm_finder.java | 46 + .../gplx/xowa/files/cnvs/Xof_cnv_wkr_.java | 4 + .../files/cnvs/Xof_img_wkr_resize_img.java | 5 + .../Xof_img_wkr_resize_img_imageMagick.java | 27 + .../cnvs/Xof_img_wkr_resize_img_mok.java | 15 + .../files/commons/Xof_commons_image_itm.java | 15 + .../files/commons/Xof_commons_image_tbl.java | 55 + .../files/downloads/Xof_download_wkr.java | 6 + .../files/downloads/Xof_download_wkr_io.java | 23 + .../downloads/Xof_download_wkr_test.java | 13 + .../gplx/xowa/files/exts/Xof_rule_grp.java | 25 + .../gplx/xowa/files/exts/Xof_rule_itm.java | 16 + .../gplx/xowa/files/exts/Xof_rule_mgr.java | 28 + .../xowa/files/fsdb/Fsdb_regy_fil_tbl.java | 61 + .../gplx/xowa/files/fsdb/Xof_fsdb_mgr.java | 9 + .../xowa/files/fsdb/Xof_fsdb_mgr__sql.java | 59 + .../xowa/files/fsdb/Xof_fsdb_mgr_cfg.java | 10 + .../files/fsdb/fs_roots/Fs_root_core.java | 59 + .../xowa/files/fsdb/fs_roots/Fs_root_mgr.java | 66 + .../xowa/files/fsdb/fs_roots/Fs_root_wkr.java | 133 + .../files/fsdb/fs_roots/Fs_root_wkr_tst.java | 67 + .../files/fsdb/fs_roots/Orig_fil_mgr.java | 7 + .../files/fsdb/fs_roots/Orig_fil_row.java | 30 + .../files/fsdb/fs_roots/Orig_fil_tbl.java | 62 + .../fsdb/fs_roots/Xof_orig_wkr__fs_root.java | 26 + .../fsdb/tsts/Xof_file_ext__bmp_tst.java | 16 + .../fsdb/tsts/Xof_file_ext__flac_tst.java | 18 + .../fsdb/tsts/Xof_file_ext__oga_tst.java | 18 + .../fsdb/tsts/Xof_file_ext__ogg_tst.java | 20 + .../fsdb/tsts/Xof_file_ext__ogv_tst.java | 19 + .../fsdb/tsts/Xof_file_ext__pdf_tst.java | 12 + .../fsdb/tsts/Xof_file_ext__png_tst.java | 46 + .../fsdb/tsts/Xof_file_ext__svg_tst.java | 23 + .../fsdb/tsts/Xof_file_ext__unknown_tst.java | 18 + .../fsdb/tsts/Xof_file_ext__wav_tst.java | 19 + .../fsdb/tsts/Xof_file_ext__xcf_tst.java | 18 + .../xowa/files/fsdb/tsts/Xof_file_fxt.java | 175 + .../fsdb/tsts/Xof_file_redirect_tst.java | 49 + .../src/gplx/xowa/files/imgs/Xof_img_mgr.java | 12 + .../gplx/xowa/files/imgs/Xof_img_mode_.java | 9 + .../Xof_img_wkr_convert_djvu_to_tiff.java | 4 + .../Xof_img_wkr_convert_djvu_to_tiff_.java | 20 + .../imgs/Xof_img_wkr_query_img_size.java | 21 + .../imgs/Xof_img_wkr_query_img_size_test.java | 8 + .../gplx/xowa/files/origs/Xof_orig_itm.java | 34 + .../gplx/xowa/files/origs/Xof_orig_mgr.java | 87 + .../gplx/xowa/files/origs/Xof_orig_tbl.java | 97 + .../xowa/files/origs/Xof_orig_tbl_tst.java | 59 + .../gplx/xowa/files/origs/Xof_orig_wkr.java | 10 + .../gplx/xowa/files/origs/Xof_orig_wkr_.java | 24 + .../xowa/files/origs/Xof_orig_wkr__mock.java | 19 + .../files/origs/Xof_orig_wkr__orig_db.java | 20 + .../files/origs/Xof_orig_wkr__wmf_api.java | 42 + .../files/origs/Xof_orig_wkr__xo_meta.java | 33 + .../xowa/files/origs/Xof_wiki_finder.java | 54 + .../xowa/files/origs/Xof_wiki_finder_itm.java | 11 + .../gplx/xowa/files/repos/Xof_itm_ttl_.java | 27 + .../gplx/xowa/files/repos/Xof_repo_itm.java | 104 + .../xowa/files/repos/Xof_repo_itm__tst.java | 41 + .../gplx/xowa/files/repos/Xof_repo_pair.java | 17 + .../gplx/xowa/files/repos/Xof_repo_tid_.java | 19 + .../files/repos/Xofw_file_finder_rslt.java | 14 + .../xowa/files/repos/Xofw_wiki_finder.java | 6 + .../xowa/files/repos/Xofw_wiki_wkr_base.java | 55 + .../xowa/files/repos/Xofw_wiki_wkr_mock.java | 26 + .../gplx/xowa/files/repos/Xow_repo_mgr.java | 8 + .../gplx/xowa/files/repos/Xow_repo_mgr_.java | 38 + .../gplx/xowa/files/repos/Xowe_repo_mgr.java | 259 + .../gplx/xowa/files/xfers/Xof_xfer_mgr.java | 360 + .../gplx/xowa/files/xfers/Xof_xfer_queue.java | 77 + .../files/xfers/Xof_xfer_queue_base_fxt.java | 98 + .../xfers/Xof_xfer_queue_html_basic_tst.java | 117 + .../xfers/Xof_xfer_queue_html_cases_tst.java | 273 + .../files/xfers/Xof_xfer_queue_html_fxt.java | 45 + .../Xof_xfer_queue_html_offline_tst.java | 13 + .../Xof_xfer_queue_html_wmf_api_tst.java | 156 + .../gplx/xowa/files/xfers/Xof_xfer_rslt.java | 14 + 400_xowa/src/gplx/xowa/guis/Xoa_gui_mgr.java | 147 + 400_xowa/src/gplx/xowa/guis/Xog_html_mgr.java | 13 + .../gplx/xowa/guis/Xogv_page_load_wkr.java | 32 + .../src/gplx/xowa/guis/Xogv_tab_base.java | 45 + .../src/gplx/xowa/guis/bnds/Xog_bnd_box.java | 34 + .../src/gplx/xowa/guis/bnds/Xog_bnd_box_.java | 124 + .../src/gplx/xowa/guis/bnds/Xog_bnd_itm.java | 16 + .../src/gplx/xowa/guis/bnds/Xog_bnd_mgr.java | 390 + .../src/gplx/xowa/guis/bnds/Xog_bnd_temp.java | 50 + .../src/gplx/xowa/guis/bnds/Xog_bnd_win.java | 92 + .../src/gplx/xowa/guis/cbks/Xog_cbk_mgr.java | 21 + .../src/gplx/xowa/guis/cbks/Xog_cbk_trg.java | 18 + .../src/gplx/xowa/guis/cbks/Xog_cbk_wkr.java | 8 + .../src/gplx/xowa/guis/cbks/Xog_json_wkr.java | 5 + .../gplx/xowa/guis/cbks/js/Js_img_mgr.java | 42 + .../gplx/xowa/guis/cbks/js/Js_img_wkr.java | 6 + .../gplx/xowa/guis/cbks/js/Xog_js_wkr.java | 12 + .../gplx/xowa/guis/cbks/js/Xog_js_wkr_.java | 14 + .../xowa/guis/cbks/js/Xog_js_wkr__log.java | 21 + .../xowa/guis/cbks/swts/Gfo_log__swt.java | 17 + .../cbks/swts/Gfobj_wtr__json__browser.java | 23 + .../swts/Gfobj_wtr__json__browser__tst.java | 24 + .../xowa/guis/cbks/swts/Xog_cbk_wkr__swt.java | 54 + .../src/gplx/xowa/guis/cmds/Xog_cmd_ctg.java | 54 + .../src/gplx/xowa/guis/cmds/Xog_cmd_itm.java | 18 + .../src/gplx/xowa/guis/cmds/Xog_cmd_itm_.java | 167 + .../src/gplx/xowa/guis/cmds/Xog_cmd_mgr.java | 41 + .../gplx/xowa/guis/cmds/Xog_cmd_mgr_invk.java | 10 + .../xowa/guis/history/Xog_history_itm.java | 35 + .../xowa/guis/history/Xog_history_mgr.java | 60 + .../xowa/guis/history/Xog_history_stack.java | 53 + .../guis/history/Xog_history_stack_tst.java | 68 + .../gplx/xowa/guis/langs/Xol_font_info.java | 28 + .../gplx/xowa/guis/menus/Xog_menu_bldr.java | 48 + .../gplx/xowa/guis/menus/Xog_menu_mgr.java | 40 + .../xowa/guis/menus/Xog_popup_mnu_mgr.java | 56 + .../xowa/guis/menus/Xog_window_mnu_mgr.java | 38 + .../xowa/guis/menus/dom/Xog_mnu_base.java | 101 + .../xowa/guis/menus/dom/Xog_mnu_bldr.java | 68 + .../xowa/guis/menus/dom/Xog_mnu_evt_mgr.java | 21 + .../gplx/xowa/guis/menus/dom/Xog_mnu_grp.java | 83 + .../gplx/xowa/guis/menus/dom/Xog_mnu_itm.java | 71 + .../xowa/guis/menus/dom/Xog_mnu_regy.java | 84 + .../src/gplx/xowa/guis/tabs/Xog_tab_mgr.java | 4 + .../src/gplx/xowa/guis/tabs/Xog_tab_mgr_.java | 9 + .../gplx/xowa/guis/tabs/Xog_tab_mgr__swt.java | 15 + .../src/gplx/xowa/guis/urls/Xog_url_wkr.java | 64 + .../xowa/guis/urls/Xog_url_wkr__file.java | 67 + .../guis/urls/Xog_url_wkr__file__tst.java | 51 + .../gplx/xowa/guis/urls/Xog_url_wkr__tst.java | 31 + .../urls/url_macros/Xog_url_macro_grp.java | 27 + .../urls/url_macros/Xog_url_macro_mgr.java | 75 + .../url_macros/Xog_url_macro_mgr_tst.java | 39 + .../guis/views/Gfo_usr_dlg__gui__swt.java | 39 + .../gplx/xowa/guis/views/Gfo_usr_dlg_fmt.java | 17 + .../gplx/xowa/guis/views/Load_page_wkr.java | 44 + .../src/gplx/xowa/guis/views/Rect_ref.java | 21 + .../gplx/xowa/guis/views/Xog_async_wkr.java | 63 + .../gplx/xowa/guis/views/Xog_html_itm.java | 218 + .../gplx/xowa/guis/views/Xog_js_procs.java | 29 + .../xowa/guis/views/Xog_launcher_tabs.java | 86 + .../src/gplx/xowa/guis/views/Xog_layout.java | 101 + .../gplx/xowa/guis/views/Xog_layout_box.java | 66 + .../xowa/guis/views/Xog_startup_tabs.java | 111 + .../xowa/guis/views/Xog_startup_win_.java | 92 + .../xowa/guis/views/Xog_tab_close_lnr.java | 4 + .../xowa/guis/views/Xog_tab_close_mgr.java | 16 + .../src/gplx/xowa/guis/views/Xog_tab_itm.java | 239 + .../gplx/xowa/guis/views/Xog_tab_itm_.java | 13 + .../xowa/guis/views/Xog_tab_itm_edit_mgr.java | 143 + .../xowa/guis/views/Xog_tab_itm_read_mgr.java | 77 + .../src/gplx/xowa/guis/views/Xog_tab_mgr.java | 261 + .../src/gplx/xowa/guis/views/Xog_win_itm.java | 406 + .../gplx/xowa/guis/views/Xog_win_itm_.java | 64 + .../views/Xog_win_itm__prog_href_mgr.java | 21 + .../gplx/xowa/guis/views/Xog_win_itm_cfg.java | 21 + .../xowa/guis/views/boots/Xog_error_data.java | 51 + .../xowa/guis/views/boots/Xog_error_win.java | 99 + .../xowa/guis/views/boots/Xog_splash_win.java | 36 + .../views/nightmodes/Xog_nightmode_mgr.java | 136 + .../views/url_box_fmts/Xog_urlfmtr_mgr.java | 59 + .../url_box_fmts/Xog_urlfmtr_mgr_tst.java | 47 + .../src/gplx/xowa/htmls/Xoh_cfg_file.java | 13 + 400_xowa/src/gplx/xowa/htmls/Xoh_cmd_itm.java | 6 + 400_xowa/src/gplx/xowa/htmls/Xoh_cmd_mgr.java | 23 + 400_xowa/src/gplx/xowa/htmls/Xoh_consts.java | 36 + .../src/gplx/xowa/htmls/Xoh_html_mgr.java | 16 + 400_xowa/src/gplx/xowa/htmls/Xoh_img_mgr.java | 35 + 400_xowa/src/gplx/xowa/htmls/Xoh_page.java | 89 + 400_xowa/src/gplx/xowa/htmls/Xoh_page_.java | 17 + .../src/gplx/xowa/htmls/Xoh_page_bfr.java | 33 + .../gplx/xowa/htmls/Xoh_page_html_source.java | 4 + .../xowa/htmls/Xoh_page_html_source_.java | 13 + .../src/gplx/xowa/htmls/Xoh_page_mgr.java | 46 + .../src/gplx/xowa/htmls/Xoh_page_wtr_mgr.java | 96 + .../gplx/xowa/htmls/Xoh_page_wtr_mgr_tst.java | 30 + .../src/gplx/xowa/htmls/Xoh_page_wtr_wkr.java | 239 + .../gplx/xowa/htmls/Xoh_page_wtr_wkr_.java | 41 + .../gplx/xowa/htmls/Xoh_page_wtr_wkr_tst.java | 59 + .../src/gplx/xowa/htmls/Xow_html_mgr.java | 50 + .../xowa/htmls/bridges/Bridge_cmd_itm.java | 7 + .../xowa/htmls/bridges/Bridge_cmd_mgr.java | 28 + .../xowa/htmls/bridges/Bridge_msg_bldr.java | 86 + .../htmls/bridges/Bridge_msg_bldr_tst.java | 16 + .../gplx/xowa/htmls/bridges/Gfo_tree_itm.java | 28 + .../xowa/htmls/bridges/Xoh_bridge_mgr.java | 8 + .../htmls/bridges/dbuis/Dbui_cmd_mgr.java | 74 + .../bridges/dbuis/fmtrs/Dbui_cells_fmtr.java | 61 + .../bridges/dbuis/fmtrs/Dbui_tbl_fmtr.java | 57 + .../dbuis/fmtrs/Dbui_tbl_fmtr_tst.java | 24 + .../bridges/dbuis/fmtrs/Dbui_val_fmtr.java | 32 + .../bridges/dbuis/fmtrs/Dbui_val_fmtr_.java | 5 + .../bridges/dbuis/tbls/Dbui_btn_itm.java | 9 + .../bridges/dbuis/tbls/Dbui_col_itm.java | 9 + .../bridges/dbuis/tbls/Dbui_row_itm.java | 9 + .../bridges/dbuis/tbls/Dbui_tbl_itm.java | 11 + .../bridges/dbuis/tbls/Dbui_tbl_itm_.java | 12 + .../bridges/dbuis/tbls/Dbui_val_hash.java | 10 + .../bridges/dbuis/tbls/Dbui_val_itm.java | 7 + .../gplx/xowa/htmls/core/Xow_hdump_mgr.java | 32 + .../xowa/htmls/core/Xow_hdump_mgr__load.java | 171 + .../xowa/htmls/core/Xow_hdump_mgr__save.java | 59 + .../gplx/xowa/htmls/core/Xow_hdump_mode.java | 31 + .../xowa/htmls/core/bldrs/Xob_hdump_bldr.java | 78 + .../core/bldrs/Xob_hdump_tbl_retriever.java | 7 + .../htmls/core/bldrs/Xob_link_dump_cmd.java | 38 + .../htmls/core/bldrs/Xob_link_dump_tbl.java | 52 + .../core/bldrs/Xob_ns_to_db_wkr__html.java | 36 + .../htmls/core/bldrs/Xob_redlink_mkr_cmd.java | 75 + .../xowa/htmls/core/dbs/Xoh_redlink_tbl.java | 43 + .../xowa/htmls/core/dbs/Xowd_html_row.java | 21 + .../xowa/htmls/core/dbs/Xowd_html_tbl.java | 107 + .../htmls/core/htmls/Xoh_display_ttl_wtr.java | 30 + .../xowa/htmls/core/htmls/Xoh_html_wtr.java | 538 ++ .../xowa/htmls/core/htmls/Xoh_html_wtr_.java | 7 + .../htmls/core/htmls/Xoh_html_wtr_cfg.java | 7 + .../core/htmls/Xoh_html_wtr_escaper.java | 113 + .../htmls/core/htmls/Xoh_html_wtr_tst.java | 333 + .../xowa/htmls/core/htmls/Xoh_wtr_ctx.java | 26 + .../core/htmls/tidy/Xoh_tidy_mgr_tst.java | 75 + .../htmls/core/htmls/tidy/Xoh_tidy_wkr.java | 7 + .../htmls/core/htmls/tidy/Xoh_tidy_wkr_.java | 11 + .../core/htmls/tidy/Xoh_tidy_wkr_jtidy.java | 67 + .../htmls/tidy/Xoh_tidy_wkr_jtidy_tst.java | 48 + .../core/htmls/tidy/Xoh_tidy_wkr_tidy.java | 44 + .../htmls/core/htmls/tidy/Xow_tidy_mgr.java | 66 + .../htmls/tidy/Xow_tidy_mgr_interface.java | 4 + .../htmls/tidy/Xow_tidy_mgr_interface_.java | 7 + .../core/htmls/utls/Xoh_anchor_kv_bldr.java | 32 + .../htmls/core/htmls/utls/Xoh_img_path.java | 10 + .../htmls/core/htmls/utls/Xoh_lnki_bldr.java | 70 + .../htmls/core/hzips/Gfo_decimal_parser.java | 64 + .../core/hzips/Gfo_decimal_parser_tst.java | 29 + .../xowa/htmls/core/hzips/Xoh_data_itm.java | 10 + .../htmls/core/hzips/Xoh_hzip_decimal.java | 29 + .../xowa/htmls/core/hzips/Xoh_hzip_dict.java | 3 + .../xowa/htmls/core/hzips/Xoh_hzip_dict_.java | 40 + .../xowa/htmls/core/hzips/Xoh_hzip_int.java | 131 + .../htmls/core/hzips/Xoh_hzip_int__tst.java | 31 + .../htmls/core/hzips/Xoh_hzip_int_tst.java | 69 + .../xowa/htmls/core/hzips/Xoh_hzip_mgr.java | 74 + .../xowa/htmls/core/hzips/Xoh_hzip_wkr.java | 10 + .../htmls/core/hzips/Xoh_pool_mgr__data.java | 37 + .../htmls/core/hzips/Xoh_pool_mgr__hzip.java | 59 + .../htmls/core/hzips/Xoh_pool_mgr__wtr.java | 35 + .../xowa/htmls/core/hzips/Xoh_stat_itm.java | 48 + .../xowa/htmls/core/hzips/Xoh_stat_tbl.java | 58 + .../xowa/htmls/core/hzips/Xoh_wtr_itm.java | 7 + .../xowa/htmls/core/makes/Xoh_make_mgr.java | 14 + .../htmls/core/makes/tests/Xoh_make_fxt.java | 38 + .../htmls/core/makes/tests/Xoh_page_chkr.java | 36 + .../xowa/htmls/core/wkrs/Xoh_hdoc_ctx.java | 82 + .../xowa/htmls/core/wkrs/Xoh_hdoc_parser.java | 25 + .../xowa/htmls/core/wkrs/Xoh_hdoc_wkr.java | 14 + .../htmls/core/wkrs/Xoh_hdoc_wkr__hzip.java | 32 + .../htmls/core/wkrs/Xoh_hdoc_wkr__make.java | 29 + .../xowa/htmls/core/wkrs/Xoh_hzip_bfr.java | 22 + .../xowa/htmls/core/wkrs/Xoh_hzip_fxt.java | 100 + .../xowa/htmls/core/wkrs/Xoh_itm_parser.java | 5 + .../htmls/core/wkrs/Xoh_itm_parser_fxt.java | 32 + .../addons/gallerys/Xoh_gallery_data.java | 26 + .../addons/gallerys/Xoh_gallery_hzip.java | 33 + .../addons/gallerys/Xoh_gallery_hzip_tst.java | 12 + .../wkrs/addons/gallerys/Xoh_gallery_wtr.java | 25 + .../wkrs/addons/medias/Xoh_media_data.java | 109 + .../wkrs/addons/medias/Xoh_media_hzip.java | 67 + .../addons/medias/Xoh_media_hzip_tst.java | 78 + .../wkrs/addons/medias/Xoh_media_wtr.java | 49 + .../wkrs/addons/pgbnrs/Xoh_pgbnr_data.java | 30 + .../wkrs/addons/pgbnrs/Xoh_pgbnr_hzip.java | 33 + .../addons/timelines/Xoh_timeline_data.java | 26 + .../addons/timelines/Xoh_timeline_hzip.java | 33 + .../timelines/Xoh_timeline_hzip_tst.java | 9 + .../addons/timelines/Xoh_timeline_wtr.java | 26 + .../core/wkrs/bfr_args/Bfr_arg__hatr_.java | 4 + .../core/wkrs/bfr_args/Bfr_arg__hatr_arg.java | 23 + .../core/wkrs/bfr_args/Bfr_arg__hatr_bry.java | 32 + .../core/wkrs/bfr_args/Bfr_arg__hatr_cls.java | 20 + .../wkrs/bfr_args/Bfr_arg__hatr_fmtr.java | 22 + .../core/wkrs/bfr_args/Bfr_arg__hatr_id.java | 21 + .../core/wkrs/bfr_args/Bfr_arg__hatr_int.java | 16 + .../core/wkrs/bfr_args/Bfr_arg__href.java | 35 + .../core/wkrs/bfr_args/Bfr_arg__wrapper.java | 15 + .../core/wkrs/escapes/Xoh_escape_data.java | 13 + .../core/wkrs/escapes/Xoh_escape_hzip.java | 27 + .../wkrs/escapes/Xoh_escape_hzip_tst.java | 14 + .../core/wkrs/glys/Bfr_arg__elem__capt.java | 21 + .../core/wkrs/glys/Bfr_arg__hatr__style.java | 39 + .../core/wkrs/glys/Bfr_arg__hatr__xogly.java | 24 + .../core/wkrs/glys/Xoh_gly_grp_data.java | 163 + .../htmls/core/wkrs/glys/Xoh_gly_grp_wtr.java | 62 + .../wkrs/glys/Xoh_gly_html__dump__tst.java | 89 + .../htmls/core/wkrs/glys/Xoh_gly_hzip.java | 119 + .../wkrs/glys/Xoh_gly_hzip__basic__tst.java | 164 + .../wkrs/glys/Xoh_gly_hzip__caption__tst.java | 107 + .../wkrs/glys/Xoh_gly_hzip__style__tst.java | 127 + .../core/wkrs/glys/Xoh_gly_itm_data.java | 93 + .../htmls/core/wkrs/glys/Xoh_gly_itm_wtr.java | 123 + .../core/wkrs/glys/Xoh_gly_itm_wtr_tst.java | 37 + .../htmls/core/wkrs/hdrs/Xoh_hdr_data.java | 50 + .../htmls/core/wkrs/hdrs/Xoh_hdr_html.java | 73 + .../core/wkrs/hdrs/Xoh_hdr_html_tst.java | 32 + .../htmls/core/wkrs/hdrs/Xoh_hdr_hzip.java | 46 + .../core/wkrs/hdrs/Xoh_hdr_hzip_tst.java | 91 + .../core/wkrs/hdrs/Xoh_hdr_make_tst.java | 32 + .../htmls/core/wkrs/hdrs/Xoh_hdr_wtr.java | 38 + .../htmls/core/wkrs/imgs/Bfr_arg__pgbnr.java | 20 + .../core/wkrs/imgs/Xoh_img_bare_data.java | 50 + .../core/wkrs/imgs/Xoh_img_bare_hzip.java | 31 + .../wkrs/imgs/Xoh_img_bare_hzip__tst.java | 32 + .../core/wkrs/imgs/Xoh_img_bare_wtr.java | 31 + .../htmls/core/wkrs/imgs/Xoh_img_data.java | 121 + .../wkrs/imgs/Xoh_img_html__dump__tst.java | 36 + .../htmls/core/wkrs/imgs/Xoh_img_hzip.java | 307 + .../imgs/Xoh_img_hzip__dump__basic__tst.java | 87 + .../imgs/Xoh_img_hzip__dump__link__tst.java | 102 + .../imgs/Xoh_img_hzip__dump__orig__tst.java | 56 + .../imgs/Xoh_img_hzip__dump__pgbnr__tst.java | 18 + .../wkrs/imgs/Xoh_img_hzip__view__tst.java | 19 + .../wkrs/imgs/Xoh_img_make__dump__tst.java | 45 + .../htmls/core/wkrs/imgs/Xoh_img_wtr.java | 155 + .../core/wkrs/imgs/atrs/Xoh_anch_cls_.java | 42 + .../wkrs/imgs/atrs/Xoh_anch_cls_data.java | 25 + .../core/wkrs/imgs/atrs/Xoh_img_cls_.java | 56 + .../core/wkrs/imgs/atrs/Xoh_img_cls__tst.java | 17 + .../core/wkrs/imgs/atrs/Xoh_img_cls_data.java | 53 + .../core/wkrs/imgs/atrs/Xoh_img_src_data.java | 125 + .../wkrs/imgs/atrs/Xoh_img_src_data_tst.java | 51 + .../wkrs/imgs/atrs/Xoh_img_xoimg_data.java | 85 + .../wkrs/imgs/atrs/Xoh_img_xoimg_hzip.java | 50 + .../htmls/core/wkrs/lnkes/Xoh_lnke_data.java | 66 + .../htmls/core/wkrs/lnkes/Xoh_lnke_dict_.java | 32 + .../htmls/core/wkrs/lnkes/Xoh_lnke_html.java | 83 + .../wkrs/lnkes/Xoh_lnke_html__basic__tst.java | 29 + .../wkrs/lnkes/Xoh_lnke_html__hdump__tst.java | 13 + .../htmls/core/wkrs/lnkes/Xoh_lnke_hzip.java | 46 + .../core/wkrs/lnkes/Xoh_lnke_hzip_tst.java | 48 + .../htmls/core/wkrs/lnkes/Xoh_lnke_wtr.java | 35 + .../core/wkrs/lnkes/atrs/Xoh_href_hzip.java | 83 + .../htmls/core/wkrs/lnkis/Xoh_lnki_data.java | 140 + .../htmls/core/wkrs/lnkis/Xoh_lnki_dict_.java | 12 + .../wkrs/lnkis/Xoh_lnki_html__basic__tst.java | 10 + .../wkrs/lnkis/Xoh_lnki_html__hdump__tst.java | 17 + .../htmls/core/wkrs/lnkis/Xoh_lnki_hzip.java | 227 + .../wkrs/lnkis/Xoh_lnki_hzip__anch__tst.java | 23 + .../wkrs/lnkis/Xoh_lnki_hzip__diff__tst.java | 20 + .../wkrs/lnkis/Xoh_lnki_hzip__ns__tst.java | 69 + .../wkrs/lnkis/Xoh_lnki_hzip__same__tst.java | 37 + .../wkrs/lnkis/Xoh_lnki_hzip__site__tst.java | 67 + .../core/wkrs/lnkis/Xopg_lnki_itm__hdump.java | 7 + .../wkrs/lnkis/anchs/Xoh_anch_capt_itm.java | 65 + .../lnkis/anchs/Xoh_anch_capt_itm_tst.java | 35 + .../core/wkrs/lnkis/anchs/Xoh_anch_cls_.java | 30 + .../wkrs/lnkis/anchs/Xoh_anch_href_data.java | 133 + .../lnkis/anchs/Xoh_anch_href_data_tst.java | 45 + .../wkrs/lnkis/htmls/Xoh_arg_img_core.java | 4 + .../lnkis/htmls/Xoh_arg_img_core__basic.java | 10 + .../core/wkrs/lnkis/htmls/Xoh_file_fmtr.java | 8 + .../lnkis/htmls/Xoh_file_fmtr__basic.java | 127 + .../lnkis/htmls/Xoh_file_fmtr__hdump.java | 58 + .../core/wkrs/lnkis/htmls/Xoh_file_mgr.java | 79 + .../lnkis/htmls/Xoh_file_wtr__audio__tst.java | 87 + .../wkrs/lnkis/htmls/Xoh_file_wtr__basic.java | 277 + .../Xoh_file_wtr__image__basic__tst.java | 363 + .../htmls/Xoh_file_wtr__image__link__tst.java | 29 + .../lnkis/htmls/Xoh_file_wtr__media__tst.java | 46 + .../lnkis/htmls/Xoh_file_wtr__video__tst.java | 115 + .../wkrs/lnkis/htmls/Xoh_lnki_consts.java | 13 + .../wkrs/lnkis/htmls/Xoh_lnki_text_fmtr.java | 28 + .../wkrs/lnkis/htmls/Xoh_lnki_title_bldr.java | 56 + .../lnkis/htmls/Xoh_lnki_title_bldr_tst.java | 26 + .../core/wkrs/lnkis/htmls/Xoh_lnki_wtr.java | 192 + .../htmls/core/wkrs/tags/Xoh_tag_parser.java | 94 + .../htmls/core/wkrs/thms/Xoh_thm_data.java | 61 + .../core/wkrs/thms/Xoh_thm_html_tst.java | 49 + .../htmls/core/wkrs/thms/Xoh_thm_hzip.java | 69 + .../wkrs/thms/Xoh_thm_hzip__avo__tst.java | 49 + .../wkrs/thms/Xoh_thm_hzip__basic__tst.java | 107 + .../wkrs/thms/Xoh_thm_hzip__pseudo__tst.java | 126 + .../wkrs/thms/Xoh_thm_hzip__tidy__tst.java | 76 + .../htmls/core/wkrs/thms/Xoh_thm_wtr.java | 77 + .../wkrs/thms/divs/Xoh_thm_caption_data.java | 66 + .../wkrs/thms/divs/Xoh_thm_magnify_data.java | 29 + .../htmls/core/wkrs/tocs/Xoh_toc_data.java | 29 + .../htmls/core/wkrs/tocs/Xoh_toc_hzip.java | 29 + .../core/wkrs/tocs/Xoh_toc_hzip_tst.java | 8 + .../htmls/core/wkrs/tocs/Xoh_toc_wtr.java | 49 + .../htmls/core/wkrs/txts/Xoh_txt_parser.java | 7 + .../htmls/core/wkrs/xndes/Xoh_xnde_hzip.java | 23 + .../wkrs/xndes/Xoh_xnde_hzip_nde__tst.java | 12 + .../core/wkrs/xndes/Xoh_xnde_parser.java | 22 + .../core/wkrs/xndes/atrs/Xohz_atr_itm.java | 11 + .../core/wkrs/xndes/atrs/Xohz_atr_itm_.java | 119 + .../wkrs/xndes/atrs/Xohz_atr_itm__style.java | 52 + .../core/wkrs/xndes/atrs/Xohz_atr_regy.java | 46 + .../wkrs/xndes/dicts/Xoh_xnde_dict__tst.java | 61 + .../wkrs/xndes/dicts/Xoh_xnde_dict_grp.java | 55 + .../wkrs/xndes/dicts/Xoh_xnde_dict_itm.java | 12 + .../wkrs/xndes/dicts/Xoh_xnde_dict_reg.java | 6 + .../core/wkrs/xndes/styles/Hz_atr_itm.java | 171 + .../wkrs/xndes/styles/Xoh_style_regy.java | 36 + .../htmls/core/wkrs/xndes/tags/Xohz_tag.java | 67 + .../core/wkrs/xndes/tags/Xohz_tag_regy.java | 16 + .../core/wkrs/xndes/tags/Xohz_tag_regy_.java | 37 + .../src/gplx/xowa/htmls/doms/Xoh_dom_.java | 54 + .../src/gplx/xowa/htmls/doms/Xoh_dom_tst.java | 28 + .../src/gplx/xowa/htmls/doms/Xoh_find.java | 12 + .../gplx/xowa/htmls/heads/Xoh_head_itm_.java | 27 + .../xowa/htmls/heads/Xoh_head_itm__base.java | 31 + .../heads/Xoh_head_itm__collapsible.java | 17 + .../xowa/htmls/heads/Xoh_head_itm__css.java | 10 + .../xowa/htmls/heads/Xoh_head_itm__dbui.java | 106 + .../htmls/heads/Xoh_head_itm__gallery.java | 12 + .../heads/Xoh_head_itm__gallery_styles.java | 11 + .../htmls/heads/Xoh_head_itm__globals.java | 89 + .../xowa/htmls/heads/Xoh_head_itm__graph.java | 27 + .../xowa/htmls/heads/Xoh_head_itm__hiero.java | 11 + .../htmls/heads/Xoh_head_itm__mathjax.java | 11 + .../htmls/heads/Xoh_head_itm__navframe.java | 17 + .../htmls/heads/Xoh_head_itm__page_cfg.java | 14 + .../xowa/htmls/heads/Xoh_head_itm__pgbnr.java | 20 + .../htmls/heads/Xoh_head_itm__popups.java | 41 + .../heads/Xoh_head_itm__search_suggest.java | 18 + .../htmls/heads/Xoh_head_itm__server.java | 17 + .../htmls/heads/Xoh_head_itm__tabber.java | 18 + .../htmls/heads/Xoh_head_itm__timeline.java | 10 + .../heads/Xoh_head_itm__title_rewrite.java | 10 + .../xowa/htmls/heads/Xoh_head_itm__toc.java | 27 + .../htmls/heads/Xoh_head_itm__top_icon.java | 15 + .../htmls/heads/Xoh_head_itm__xo_elem.java | 14 + .../gplx/xowa/htmls/heads/Xoh_head_mgr.java | 170 + .../xowa/htmls/heads/Xoh_head_mgr_tst.java | 128 + .../gplx/xowa/htmls/heads/Xoh_head_wtr.java | 217 + .../xowa/htmls/heads/Xoh_head_wtr_tst.java | 46 + .../xowa/htmls/heads/Xow_fragment_mgr.java | 49 + .../htmls/heads/Xow_fragment_mgr_tst.java | 40 + .../src/gplx/xowa/htmls/hrefs/Xoh_href_.java | 23 + .../xowa/htmls/hrefs/Xoh_href_gui_utl.java | 77 + .../htmls/hrefs/Xoh_href_gui_utl_tst.java | 51 + .../xowa/htmls/hrefs/Xoh_href_parser.java | 67 + .../hrefs/Xoh_href_parser__basic__tst.java | 79 + .../hrefs/Xoh_href_parser__qargs__tst.java | 21 + .../hrefs/Xoh_href_parser__wiki__tst.java | 33 + .../gplx/xowa/htmls/hrefs/Xoh_href_wtr.java | 90 + .../xowa/htmls/hrefs/Xoh_href_wtr_tst.java | 42 + .../src/gplx/xowa/htmls/js/Xoh_js_cbk.java | 183 + .../gplx/xowa/htmls/js/Xoh_js_cbk_tst.java | 29 + .../htmls/js/Xoh_js_cbk_wdata_labels_tst.java | 46 + .../gplx/xowa/htmls/js/Xoh_js_cleaner.java | 187 + .../xowa/htmls/js/Xoh_js_cleaner_tst.java | 25 + .../htmls/modules/popups/Xopg_popup_mgr.java | 7 + .../popups/Xow_popup_anchor_finder.java | 53 + .../Xow_popup_anchor_finder__hdr_tst.java | 68 + .../Xow_popup_anchor_finder__id_tst.java | 33 + .../htmls/modules/popups/Xow_popup_cfg.java | 17 + .../modules/popups/Xow_popup_html_mkr.java | 88 + .../htmls/modules/popups/Xow_popup_itm.java | 33 + .../htmls/modules/popups/Xow_popup_mgr.java | 234 + .../htmls/modules/popups/Xow_popup_mgr_.java | 60 + .../modules/popups/Xow_popup_parser.java | 247 + .../modules/popups/Xow_popup_parser_data.java | 48 + .../modules/popups/Xow_popup_parser_tst.java | 505 + .../htmls/modules/popups/Xow_popup_word.java | 13 + .../modules/popups/Xow_popup_wrdx_mkr.java | 187 + .../popups/keeplists/Xop_keeplist_rule.java | 30 + .../popups/keeplists/Xop_keeplist_wiki.java | 32 + .../keeplists/Xop_keeplist_wiki_srl.java | 49 + .../keeplists/Xop_keeplist_wiki_tst.java | 44 + .../Xoh_file_page__other_resolutions.java | 23 + .../htmls/ns_files/Xoh_file_page_wtr.java | 88 + .../htmls/ns_files/Xoh_ns_file_page_mgr.java | 92 + .../ns_files/Xoh_ns_file_page_mgr_tst.java | 114 + .../htmls/portal/Xoa_available_wikis_mgr.java | 37 + .../xowa/htmls/portal/Xoa_portal_mgr.java | 9 + .../xowa/htmls/portal/Xoh_page_body_cls.java | 115 + .../htmls/portal/Xoh_page_body_cls_tst.java | 37 + .../gplx/xowa/htmls/portal/Xoh_rtl_utl.java | 56 + .../xowa/htmls/portal/Xoh_rtl_utl_tst.java | 47 + .../xowa/htmls/portal/Xoh_subpages_bldr.java | 49 + .../htmls/portal/Xoh_subpages_bldr_tst.java | 32 + .../xowa/htmls/portal/Xow_portal_mgr.java | 188 + .../xowa/htmls/portal/Xow_portal_mgr_tst.java | 57 + .../htmls/portal/vnts/Vnt_mnu_grp_fmtr.java | 45 + .../portal/vnts/Vnt_mnu_grp_fmtr_tst.java | 50 + .../xowa/htmls/sections/Xoh_section_itm.java | 20 + .../xowa/htmls/sections/Xoh_section_mgr.java | 26 + .../gplx/xowa/htmls/skins/Xoh_skin_itm.java | 17 + .../gplx/xowa/htmls/skins/Xoh_skin_mgr.java | 34 + .../gplx/xowa/htmls/skins/Xoh_skin_regy.java | 17 + .../src/gplx/xowa/langs/Xoa_lang_mgr.java | 38 + .../src/gplx/xowa/langs/Xol_lang_itm.java | 156 + .../src/gplx/xowa/langs/Xol_lang_itm_.java | 263 + .../src/gplx/xowa/langs/Xol_lang_stub.java | 7 + .../src/gplx/xowa/langs/Xol_lang_stub_.java | 947 ++ .../xowa/langs/bldrs/Json_itm_wkr__base.java | 66 + .../xowa/langs/bldrs/Xob_i18n_parser.java | 15 + .../xowa/langs/bldrs/Xob_i18n_parser_tst.java | 47 + .../xowa/langs/bldrs/Xobc_utl_make_lang.java | 33 + .../langs/bldrs/Xobc_utl_make_lang_kwds.java | 132 + .../langs/bldrs/Xobc_utl_make_lang_tst.java | 163 + .../gplx/xowa/langs/bldrs/Xobcl_kwd_row.java | 11 + .../xowa/langs/bldrs/Xol_lang_transform.java | 8 + .../xowa/langs/bldrs/Xol_mw_lang_parser.java | 353 + .../langs/bldrs/Xol_mw_lang_parser_tst.java | 300 + .../src/gplx/xowa/langs/bldrs/Xol_ns_grp.java | 29 + .../gplx/xowa/langs/cases/Xol_case_itm.java | 70 + .../gplx/xowa/langs/cases/Xol_case_itm_.java | 132 + .../gplx/xowa/langs/cases/Xol_case_mgr.java | 124 + .../gplx/xowa/langs/cases/Xol_case_mgr_.java | 1106 +++ .../xowa/langs/cases/Xol_case_mgr_tst.java | 141 + .../gplx/xowa/langs/commas/Xol_comma_wkr.java | 6 + .../xowa/langs/commas/Xol_comma_wkr__add.java | 13 + .../langs/durations/Xol_duration_itm.java | 11 + .../langs/durations/Xol_duration_itm_.java | 63 + .../langs/durations/Xol_duration_mgr.java | 77 + .../langs/durations/Xol_interval_itm.java | 15 + .../gplx/xowa/langs/funcs/Xol_func_itm.java | 20 + .../gplx/xowa/langs/funcs/Xol_func_regy.java | 94 + .../gplx/xowa/langs/genders/Xol_gender.java | 4 + .../gplx/xowa/langs/genders/Xol_gender_.java | 17 + .../gplx/xowa/langs/grammars/Xol_grammar.java | 4 + .../xowa/langs/grammars/Xol_grammar_.java | 41 + .../xowa/langs/grammars/Xol_grammar_fi.java | 65 + .../xowa/langs/grammars/Xol_grammar_he.java | 34 + .../grammars/Xol_grammar_manual_regy.java | 17 + .../xowa/langs/grammars/Xol_grammar_ru.java | 57 + .../src/gplx/xowa/langs/kwds/Xol_kwd_grp.java | 17 + .../gplx/xowa/langs/kwds/Xol_kwd_grp_.java | 474 + .../src/gplx/xowa/langs/kwds/Xol_kwd_itm.java | 7 + .../src/gplx/xowa/langs/kwds/Xol_kwd_mgr.java | 80 + .../xowa/langs/kwds/Xol_kwd_parse_data.java | 63 + .../langs/kwds/Xol_kwd_parse_data_tst.java | 21 + .../langs/lnki_trails/Xol_lnki_trail_mgr.java | 56 + .../lnki_trails/Xol_lnki_trail_mgr_tst.java | 26 + .../src/gplx/xowa/langs/msgs/Xol_msg_itm.java | 31 + .../gplx/xowa/langs/msgs/Xol_msg_itm_.java | 509 + .../gplx/xowa/langs/msgs/Xol_msg_itm_tst.java | 22 + .../src/gplx/xowa/langs/msgs/Xol_msg_mgr.java | 90 + .../gplx/xowa/langs/msgs/Xol_msg_mgr_.java | 144 + .../gplx/xowa/langs/msgs/Xol_msg_mgr_tst.java | 53 + .../xowa/langs/msgs/Xow_mainpage_finder.java | 14 + .../langs/msgs/Xow_mainpage_finder_tst.java | 56 + .../src/gplx/xowa/langs/msgs/Xow_msg_mgr.java | 98 + .../xowa/langs/numbers/Xol_num_fmtr_base.java | 187 + .../langs/numbers/Xol_num_fmtr_base_tst.java | 101 + .../gplx/xowa/langs/numbers/Xol_num_grp.java | 9 + .../xowa/langs/numbers/Xol_num_grp_fmtr.java | 64 + .../langs/numbers/Xol_num_grp_fmtr_tst.java | 37 + .../gplx/xowa/langs/numbers/Xol_num_mgr.java | 58 + .../gplx/xowa/langs/numbers/Xol_num_mgr_.java | 23 + .../langs/numbers/Xol_num_mgr__commafy_5.java | 27 + .../xowa/langs/numbers/Xol_transform_mgr.java | 37 + .../xowa/langs/parsers/Xol_csv_parser.java | 65 + .../langs/parsers/Xol_csv_parser_tst.java | 24 + .../gplx/xowa/langs/parsers/Xol_lang_srl.java | 258 + .../xowa/langs/parsers/Xol_lang_srl_tst.java | 331 + .../gplx/xowa/langs/plurals/Xol_plural.java | 4 + .../gplx/xowa/langs/plurals/Xol_plural_.java | 27 + .../xowa/langs/plurals/Xol_plural_ru.java | 23 + .../xowa/langs/plurals/Xol_plural_ru_tst.java | 18 + .../xowa/langs/specials/Xol_specials_itm.java | 6 + .../xowa/langs/specials/Xol_specials_mgr.java | 33 + .../gplx/xowa/langs/vnts/Xol_vnt_dir_.java | 10 + .../src/gplx/xowa/langs/vnts/Xol_vnt_itm.java | 33 + .../src/gplx/xowa/langs/vnts/Xol_vnt_mgr.java | 56 + .../gplx/xowa/langs/vnts/Xol_vnt_regy.java | 44 + .../xowa/langs/vnts/Xol_vnt_regy_fxt.java | 45 + .../xowa/langs/vnts/Xol_vnt_regy_tst.java | 32 + .../langs/vnts/converts/Xol_convert_grp.java | 45 + .../langs/vnts/converts/Xol_convert_itm.java | 6 + .../langs/vnts/converts/Xol_convert_mgr.java | 54 + .../langs/vnts/converts/Xol_convert_regy.java | 20 + .../vnts/converts/Xol_convert_regy_tst.java | 76 + .../langs/vnts/converts/Xol_convert_wkr.java | 54 + .../langs/vnts/converts/Xol_mw_parse_tst.java | 126 + .../parsers/headingsOld/Xomw_heading_cbk.java | 5 + .../parsers/headingsOld/Xomw_heading_wkr.java | 81 + .../src/gplx/xowa/parsers/Xoa_parser_mgr.java | 33 + 400_xowa/src/gplx/xowa/parsers/Xop_ctx.java | 327 + 400_xowa/src/gplx/xowa/parsers/Xop_ctx_.java | 12 + .../src/gplx/xowa/parsers/Xop_ctx__tst.java | 16 + .../gplx/xowa/parsers/Xop_ctx_page_data.java | 20 + .../src/gplx/xowa/parsers/Xop_ctx_wkr.java | 6 + 400_xowa/src/gplx/xowa/parsers/Xop_lxr.java | 9 + 400_xowa/src/gplx/xowa/parsers/Xop_lxr_.java | 10 + .../src/gplx/xowa/parsers/Xop_lxr_mgr.java | 90 + .../src/gplx/xowa/parsers/Xop_parser.java | 208 + .../src/gplx/xowa/parsers/Xop_parser_.java | 26 + .../gplx/xowa/parsers/Xop_parser__tst.java | 41 + .../gplx/xowa/parsers/Xop_parser_tid_.java | 4 + .../src/gplx/xowa/parsers/Xop_root_tkn.java | 11 + .../gplx/xowa/parsers/Xop_tkn_chkr_base.java | 48 + .../src/gplx/xowa/parsers/Xop_tkn_grp.java | 17 + .../src/gplx/xowa/parsers/Xop_tkn_itm.java | 34 + .../src/gplx/xowa/parsers/Xop_tkn_itm_.java | 113 + .../gplx/xowa/parsers/Xop_tkn_itm_base.java | 152 + .../src/gplx/xowa/parsers/Xop_tkn_mkr.java | 158 + .../src/gplx/xowa/parsers/Xop_tkn_null.java | 38 + .../src/gplx/xowa/parsers/Xop_tmp_mgr.java | 13 + .../src/gplx/xowa/parsers/Xop_txt_tkn.java | 17 + .../gplx/xowa/parsers/Xow_mw_parser_mgr.java | 5 + .../src/gplx/xowa/parsers/Xow_parser_mgr.java | 90 + .../gplx/xowa/parsers/amps/Xop_amp_lxr.java | 12 + .../gplx/xowa/parsers/amps/Xop_amp_mgr.java | 140 + .../amps/Xop_amp_mgr__decode__tst.java | 47 + .../amps/Xop_amp_mgr__parse_tkn__tst.java | 10 + .../xowa/parsers/amps/Xop_amp_mgr_rslt.java | 25 + .../xowa/parsers/amps/Xop_amp_tkn_ent.java | 15 + .../xowa/parsers/amps/Xop_amp_tkn_num.java | 10 + .../gplx/xowa/parsers/amps/Xop_amp_wkr.java | 17 + .../xowa/parsers/amps/Xop_amp_wkr_tst.java | 20 + .../gplx/xowa/parsers/apos/Xop_apos_dat.java | 64 + .../gplx/xowa/parsers/apos/Xop_apos_itm.java | 67 + .../gplx/xowa/parsers/apos/Xop_apos_lxr.java | 10 + .../gplx/xowa/parsers/apos/Xop_apos_tkn.java | 12 + .../gplx/xowa/parsers/apos/Xop_apos_tkn_.java | 19 + .../xowa/parsers/apos/Xop_apos_tkn_chkr.java | 14 + .../gplx/xowa/parsers/apos/Xop_apos_wkr.java | 97 + .../xowa/parsers/apos/Xop_apos_wkr_tst.java | 136 + .../gplx/xowa/parsers/hdrs/Xop_hdr_log.java | 11 + .../gplx/xowa/parsers/hdrs/Xop_hdr_lxr.java | 11 + .../gplx/xowa/parsers/hdrs/Xop_hdr_tkn.java | 24 + .../gplx/xowa/parsers/hdrs/Xop_hdr_wkr.java | 116 + .../parsers/hdrs/Xop_hdr_wkr__basic_tst.java | 110 + .../parsers/hdrs/Xop_hdr_wkr__para_tst.java | 9 + .../hdrs/sections/Xop_section_itm.java | 15 + .../hdrs/sections/Xop_section_list.java | 103 + .../Xop_section_list__merge__tst.java | 162 + .../Xop_section_list__slice__tst.java | 145 + .../hdrs/sections/Xop_section_mgr.java | 73 + .../gplx/xowa/parsers/htmls/Mwh_atr_itm.java | 49 + .../gplx/xowa/parsers/htmls/Mwh_atr_itm_.java | 34 + .../parsers/htmls/Mwh_atr_itm_owner1.java | 4 + .../parsers/htmls/Mwh_atr_itm_owner2.java | 4 + .../gplx/xowa/parsers/htmls/Mwh_atr_mgr.java | 81 + .../xowa/parsers/htmls/Mwh_atr_mgr_tst.java | 22 + .../xowa/parsers/htmls/Mwh_atr_parser.java | 464 + .../parsers/htmls/Mwh_atr_parser_fxt.java | 59 + .../parsers/htmls/Mwh_atr_parser_tst.java | 61 + .../gplx/xowa/parsers/htmls/Mwh_atr_wkr.java | 4 + .../gplx/xowa/parsers/htmls/Mwh_doc_itm.java | 8 + .../gplx/xowa/parsers/htmls/Mwh_doc_mgr.java | 45 + .../xowa/parsers/htmls/Mwh_doc_parser.java | 232 + .../parsers/htmls/Mwh_doc_parser_fxt.java | 58 + .../parsers/htmls/Mwh_doc_parser_tst.java | 44 + .../gplx/xowa/parsers/htmls/Mwh_doc_wkr.java | 10 + .../gplx/xowa/parsers/htmls/Mwh_doc_wkr_.java | 14 + .../parsers/htmls/Mwh_doc_wkr__atr_bldr.java | 30 + .../xowa/parsers/lists/HierPosAryBldr.java | 44 + .../parsers/lists/HierPosAryBldr_tst.java | 48 + .../xowa/parsers/lists/Xop_colon_lxr.java | 24 + .../gplx/xowa/parsers/lists/Xop_list_lxr.java | 11 + .../gplx/xowa/parsers/lists/Xop_list_tkn.java | 15 + .../xowa/parsers/lists/Xop_list_tkn_.java | 37 + .../xowa/parsers/lists/Xop_list_tkn_chkr.java | 20 + .../gplx/xowa/parsers/lists/Xop_list_wkr.java | 169 + .../xowa/parsers/lists/Xop_list_wkr_.java | 37 + .../parsers/lists/Xop_list_wkr_basic_tst.java | 336 + .../parsers/lists/Xop_list_wkr_para_tst.java | 71 + .../lists/Xop_list_wkr_uncommon_tst.java | 392 + .../xowa/parsers/lnkes/Xop_lnke_end_lxr.java | 10 + .../gplx/xowa/parsers/lnkes/Xop_lnke_log.java | 6 + .../gplx/xowa/parsers/lnkes/Xop_lnke_lxr.java | 29 + .../gplx/xowa/parsers/lnkes/Xop_lnke_tkn.java | 22 + .../gplx/xowa/parsers/lnkes/Xop_lnke_wkr.java | 302 + .../parsers/lnkes/Xop_lnke_wkr_brack_tst.java | 77 + .../lnkes/Xop_lnke_wkr_dangling_tst.java | 22 + .../lnkes/Xop_lnke_wkr_relative_tst.java | 25 + .../parsers/lnkes/Xop_lnke_wkr_text_tst.java | 82 + .../lnkes/Xop_lnke_wkr_uncommon_tst.java | 32 + .../parsers/lnkes/Xop_lnke_wkr_xwiki_tst.java | 46 + .../xowa/parsers/lnkes/Xop_tkn_chkr_lnke.java | 16 + .../xowa/parsers/lnkis/Xop_link_parser.java | 97 + .../xowa/parsers/lnkis/Xop_lnki_align_h_.java | 21 + .../parsers/lnkis/Xop_lnki_arg_parser.java | 175 + .../gplx/xowa/parsers/lnkis/Xop_lnki_log.java | 12 + .../xowa/parsers/lnkis/Xop_lnki_lxr_bgn.java | 41 + .../xowa/parsers/lnkis/Xop_lnki_lxr_end.java | 11 + .../gplx/xowa/parsers/lnkis/Xop_lnki_tkn.java | 55 + .../xowa/parsers/lnkis/Xop_lnki_tkn_chkr.java | 42 + .../xowa/parsers/lnkis/Xop_lnki_type.java | 71 + .../gplx/xowa/parsers/lnkis/Xop_lnki_wkr.java | 169 + .../xowa/parsers/lnkis/Xop_lnki_wkr_.java | 130 + .../lnkis/Xop_lnki_wkr__basic_tst.java | 300 + .../parsers/lnkis/Xop_lnki_wkr__ctg_tst.java | 115 + .../lnkis/Xop_lnki_wkr__frame_tst.java | 8 + .../lnkis/Xop_lnki_wkr__invalid_tst.java | 80 + .../lnkis/Xop_lnki_wkr__link__basic__tst.java | 66 + .../lnkis/Xop_lnki_wkr__link__xwiki__tst.java | 87 + .../parsers/lnkis/Xop_lnki_wkr__pre_tst.java | 87 + .../parsers/lnkis/Xop_lnki_wkr__size_tst.java | 46 + .../lnkis/Xop_lnki_wkr__subpage_tst.java | 51 + .../lnkis/Xop_lnki_wkr__uncommon_tst.java | 43 + .../lnkis/Xop_lnki_wkr__video_tst.java | 14 + .../lnkis/Xop_lnki_wkr__xwiki_tst.java | 98 + .../xowa/parsers/lnkis/cfgs/Xoc_lnki_cfg.java | 10 + .../lnkis/cfgs/Xoc_xwiki_repo_mgr.java | 28 + .../parsers/lnkis/files/Xop_file_logger.java | 7 + .../parsers/lnkis/files/Xop_file_logger_.java | 5 + .../xowa/parsers/logs/Xop_log_basic_tbl.java | 44 + .../xowa/parsers/logs/Xop_log_basic_wkr.java | 60 + .../xowa/parsers/logs/Xop_log_invoke_wkr.java | 63 + .../gplx/xowa/parsers/logs/Xop_log_mgr.java | 53 + .../parsers/logs/Xop_log_property_wkr.java | 59 + .../parsers/logs/Xop_log_wkr_factory.java | 15 + .../gplx/xowa/parsers/miscs/Xop_bry_tkn.java | 11 + .../gplx/xowa/parsers/miscs/Xop_comm_log.java | 8 + .../gplx/xowa/parsers/miscs/Xop_comm_lxr.java | 81 + .../xowa/parsers/miscs/Xop_comm_lxr_tst.java | 85 + .../gplx/xowa/parsers/miscs/Xop_cr_lxr.java | 12 + .../gplx/xowa/parsers/miscs/Xop_cr_tkn.java | 18 + .../gplx/xowa/parsers/miscs/Xop_eq_lxr.java | 66 + .../gplx/xowa/parsers/miscs/Xop_eq_tkn.java | 7 + .../gplx/xowa/parsers/miscs/Xop_hr_lxr.java | 29 + .../parsers/miscs/Xop_hr_lxr_basic_tst.java | 14 + .../parsers/miscs/Xop_hr_lxr_para_tst.java | 37 + .../gplx/xowa/parsers/miscs/Xop_hr_tkn.java | 6 + .../xowa/parsers/miscs/Xop_ignore_tkn.java | 12 + .../parsers/miscs/Xop_ignore_tkn_chkr.java | 12 + .../gplx/xowa/parsers/miscs/Xop_misc_log.java | 8 + .../gplx/xowa/parsers/miscs/Xop_pipe_lxr.java | 75 + .../gplx/xowa/parsers/miscs/Xop_pipe_tkn.java | 5 + .../xowa/parsers/miscs/Xop_space_lxr.java | 14 + .../xowa/parsers/miscs/Xop_space_lxr_tst.java | 15 + .../xowa/parsers/miscs/Xop_space_tkn.java | 19 + .../gplx/xowa/parsers/miscs/Xop_tab_lxr.java | 17 + .../gplx/xowa/parsers/miscs/Xop_tab_tkn.java | 20 + .../xowa/parsers/miscs/Xop_tkn_chkr_hr.java | 13 + .../xowa/parsers/miscs/Xop_under_lxr.java | 134 + .../xowa/parsers/miscs/Xop_under_lxr_tst.java | 211 + .../xowa/parsers/miscs/Xop_under_tkn.java | 6 + .../gplx/xowa/parsers/paras/Xop_nl_lxr.java | 101 + .../xowa/parsers/paras/Xop_nl_tab_lxr.java | 36 + .../parsers/paras/Xop_nl_tab_lxr_tst.java | 48 + .../gplx/xowa/parsers/paras/Xop_nl_tkn.java | 10 + .../xowa/parsers/paras/Xop_nl_tkn_chkr.java | 12 + .../gplx/xowa/parsers/paras/Xop_para_tkn.java | 37 + .../xowa/parsers/paras/Xop_para_tkn_chkr.java | 14 + .../gplx/xowa/parsers/paras/Xop_para_wkr.java | 329 + .../parsers/paras/Xop_para_wkr_basic_tst.java | 1011 ++ .../parsers/paras/Xop_para_wkr_para_tst.java | 92 + .../parsers/paras/Xop_para_wkr_pre_tst.java | 243 + .../gplx/xowa/parsers/paras/Xop_pre_lxr.java | 77 + .../gplx/xowa/parsers/paras/Xop_pre_tkn.java | 18 + .../gplx/xowa/parsers/tblws/Xop_tblw_log.java | 15 + .../gplx/xowa/parsers/tblws/Xop_tblw_lxr.java | 120 + .../xowa/parsers/tblws/Xop_tblw_lxr_ws.java | 49 + .../xowa/parsers/tblws/Xop_tblw_tb_tkn.java | 21 + .../parsers/tblws/Xop_tblw_tb_tkn_chkr.java | 15 + .../xowa/parsers/tblws/Xop_tblw_tc_tkn.java | 14 + .../parsers/tblws/Xop_tblw_tc_tkn_chkr.java | 10 + .../xowa/parsers/tblws/Xop_tblw_td_tkn.java | 14 + .../parsers/tblws/Xop_tblw_td_tkn_chkr.java | 13 + .../xowa/parsers/tblws/Xop_tblw_th_tkn.java | 14 + .../parsers/tblws/Xop_tblw_th_tkn_chkr.java | 13 + .../gplx/xowa/parsers/tblws/Xop_tblw_tkn.java | 11 + .../xowa/parsers/tblws/Xop_tblw_tr_tkn.java | 18 + .../parsers/tblws/Xop_tblw_tr_tkn_chkr.java | 13 + .../gplx/xowa/parsers/tblws/Xop_tblw_wkr.java | 559 ++ .../parsers/tblws/Xop_tblw_wkr__atrs_tst.java | 195 + .../tblws/Xop_tblw_wkr__basic_tst.java | 799 ++ .../tblws/Xop_tblw_wkr__dangling_tst.java | 40 + .../tblws/Xop_tblw_wkr__double_pipe_tst.java | 91 + .../parsers/tblws/Xop_tblw_wkr__errs_tst.java | 77 + .../tblws/Xop_tblw_wkr__nested_tst.java | 148 + .../parsers/tblws/Xop_tblw_wkr__para_tst.java | 160 + .../parsers/tblws/Xop_tblw_wkr__tblx_tst.java | 137 + .../tblws/Xop_tblw_wkr__uncommon_tst.java | 112 + .../xowa/parsers/tblws/Xop_tblw_ws_itm.java | 47 + .../src/gplx/xowa/parsers/tmpls/Arg_bldr.java | 189 + .../gplx/xowa/parsers/tmpls/Arg_itm_tkn.java | 14 + .../xowa/parsers/tmpls/Arg_itm_tkn_base.java | 36 + .../xowa/parsers/tmpls/Arg_itm_tkn_null.java | 13 + .../gplx/xowa/parsers/tmpls/Arg_nde_tkn.java | 26 + .../xowa/parsers/tmpls/Arg_nde_tkn_mock.java | 25 + .../xowa/parsers/tmpls/Nowiki_escape_itm.java | 53 + .../parsers/tmpls/Xop_arg_itm_tkn_chkr.java | 10 + .../parsers/tmpls/Xop_arg_nde_tkn_chkr.java | 14 + .../gplx/xowa/parsers/tmpls/Xop_arg_wkr.java | 4 + .../gplx/xowa/parsers/tmpls/Xop_arg_wkr_.java | 4 + .../xowa/parsers/tmpls/Xop_brack_bgn_lxr.java | 14 + .../xowa/parsers/tmpls/Xop_brack_end_lxr.java | 17 + .../xowa/parsers/tmpls/Xop_curly_bgn_lxr.java | 21 + .../xowa/parsers/tmpls/Xop_curly_bgn_tkn.java | 6 + .../xowa/parsers/tmpls/Xop_curly_end_lxr.java | 10 + .../xowa/parsers/tmpls/Xop_curly_log.java | 12 + .../xowa/parsers/tmpls/Xop_curly_wkr.java | 144 + .../xowa/parsers/tmpls/Xop_func_arg_itm.java | 33 + .../xowa/parsers/tmpls/Xop_subst_tst.java | 59 + .../src/gplx/xowa/parsers/tmpls/Xop_tkn_.java | 10 + .../xowa/parsers/tmpls/Xop_tkn_print_tst.java | 23 + .../gplx/xowa/parsers/tmpls/Xop_tmpl_log.java | 13 + .../xowa/parsers/tmpls/Xot_compile_data.java | 8 + .../src/gplx/xowa/parsers/tmpls/Xot_defn.java | 17 + .../gplx/xowa/parsers/tmpls/Xot_defn_.java | 21 + .../xowa/parsers/tmpls/Xot_defn_subst.java | 10 + .../xowa/parsers/tmpls/Xot_defn_tmpl.java | 79 + .../xowa/parsers/tmpls/Xot_defn_tmpl_.java | 53 + .../xowa/parsers/tmpls/Xot_defn_trace.java | 39 + .../tmpls/Xot_defn_trace_brief_tst.java | 41 + .../parsers/tmpls/Xot_defn_trace_dbg.java | 124 + .../parsers/tmpls/Xot_defn_trace_dbg_tst.java | 32 + .../parsers/tmpls/Xot_defn_trace_null.java | 8 + .../xowa/parsers/tmpls/Xot_examples_tst.java | 114 + .../src/gplx/xowa/parsers/tmpls/Xot_fmtr.java | 58 + .../src/gplx/xowa/parsers/tmpls/Xot_invk.java | 16 + .../xowa/parsers/tmpls/Xot_invk_mock.java | 81 + .../parsers/tmpls/Xot_invk_sandbox_tst.java | 31 + .../xowa/parsers/tmpls/Xot_invk_temp.java | 73 + .../gplx/xowa/parsers/tmpls/Xot_invk_tkn.java | 434 + .../xowa/parsers/tmpls/Xot_invk_tkn_.java | 64 + .../xowa/parsers/tmpls/Xot_invk_tkn_chkr.java | 21 + .../gplx/xowa/parsers/tmpls/Xot_invk_wkr.java | 110 + .../tmpls/Xot_invk_wkr__basic__tst.java | 423 + .../tmpls/Xot_invk_wkr__missing__tst.java | 30 + .../tmpls/Xot_invk_wkr__prepend_nl__tst.java | 104 + .../tmpls/Xot_invk_wkr__raw_msg__tst.java | 32 + .../tmpls/Xot_invk_wkr__transclude__tst.java | 39 + .../gplx/xowa/parsers/tmpls/Xot_prm_chkr.java | 13 + .../gplx/xowa/parsers/tmpls/Xot_prm_log.java | 12 + .../gplx/xowa/parsers/tmpls/Xot_prm_tkn.java | 72 + .../xowa/parsers/tmpls/Xot_prm_tkn_tst.java | 40 + .../gplx/xowa/parsers/tmpls/Xot_prm_wkr.java | 29 + .../gplx/xowa/parsers/tmpls/Xot_tmpl_wtr.java | 94 + .../gplx/xowa/parsers/uniqs/Xop_uniq_mgr.java | 116 + .../uniqs/Xop_uniq_mgr__parse__tst.java | 11 + .../xowa/parsers/uniqs/Xop_uniq_mgr__tst.java | 64 + .../gplx/xowa/parsers/utils/TstObj_tst.java | 216 + .../xowa/parsers/utils/Xop_redirect_mgr.java | 120 + .../parsers/utils/Xop_redirect_mgr_tst.java | 69 + .../xowa/parsers/utils/Xop_sanitizer.java | 95 + .../xowa/parsers/utils/Xop_sanitizer_tst.java | 26 + .../xowa/parsers/vnts/Vnt_convert_lang.java | 171 + .../vnts/Vnt_convert_lang__html__tst.java | 85 + .../vnts/Vnt_convert_lang__syntax__tst.java | 66 + .../parsers/vnts/Vnt_convert_lang_fxt.java | 39 + .../xowa/parsers/vnts/Vnt_convert_rule.java | 177 + .../xowa/parsers/vnts/Vnt_flag_code_.java | 33 + .../xowa/parsers/vnts/Vnt_flag_code_mgr.java | 39 + .../xowa/parsers/vnts/Vnt_flag_lang_mgr.java | 18 + .../xowa/parsers/vnts/Vnt_flag_parser.java | 51 + .../parsers/vnts/Vnt_flag_parser_tst.java | 39 + .../xowa/parsers/vnts/Vnt_html_doc_wkr.java | 65 + .../gplx/xowa/parsers/vnts/Vnt_log_mgr.java | 41 + .../gplx/xowa/parsers/vnts/Vnt_log_tbl.java | 89 + .../xowa/parsers/vnts/Vnt_rule_bidi_mgr.java | 51 + .../xowa/parsers/vnts/Vnt_rule_parser.java | 81 + .../vnts/Vnt_rule_parser__bidi_tst.java | 10 + .../vnts/Vnt_rule_parser__undi_tst.java | 7 + .../parsers/vnts/Vnt_rule_parser_fxt.java | 20 + .../xowa/parsers/vnts/Vnt_rule_undi_mgr.java | 63 + .../parsers/xndes/Xop_xatr_whitelist_mgr.java | 248 + .../xndes/Xop_xatr_whitelist_mgr_tst.java | 58 + .../gplx/xowa/parsers/xndes/Xop_xnde_log.java | 20 + .../gplx/xowa/parsers/xndes/Xop_xnde_lxr.java | 10 + .../gplx/xowa/parsers/xndes/Xop_xnde_tag.java | 68 + .../xowa/parsers/xndes/Xop_xnde_tag_.java | 266 + .../xowa/parsers/xndes/Xop_xnde_tag_lang.java | 15 + .../xowa/parsers/xndes/Xop_xnde_tag_regy.java | 59 + .../parsers/xndes/Xop_xnde_tag_stack.java | 18 + .../gplx/xowa/parsers/xndes/Xop_xnde_tkn.java | 130 + .../xowa/parsers/xndes/Xop_xnde_tkn_chkr.java | 24 + .../gplx/xowa/parsers/xndes/Xop_xnde_wkr.java | 716 ++ .../xowa/parsers/xndes/Xop_xnde_wkr_.java | 57 + .../xndes/Xop_xnde_wkr__basic_tst.java | 136 + .../xndes/Xop_xnde_wkr__blockquote_tst.java | 43 + .../xndes/Xop_xnde_wkr__err_dangling_tst.java | 189 + .../Xop_xnde_wkr__err_malformed_tst.java | 57 + .../xndes/Xop_xnde_wkr__err_misc_tst.java | 179 + .../Xop_xnde_wkr__include_basic_tst.java | 66 + .../Xop_xnde_wkr__include_uncommon_tst.java | 177 + .../parsers/xndes/Xop_xnde_wkr__li_tst.java | 87 + .../xndes/Xop_xnde_wkr__nowiki_tst.java | 127 + .../parsers/xndes/Xop_xnde_wkr__tblx_tst.java | 63 + .../xndes/Xop_xnde_wkr__text_block_tst.java | 62 + .../parsers/xndes/Xop_xnde_wkr__tidy_tst.java | 29 + .../xndes/Xop_xnde_wkr__xatrs_tst.java | 43 + .../gplx/xowa/specials/Xoa_special_mgr.java | 18 + .../gplx/xowa/specials/Xow_special_meta.java | 28 + .../gplx/xowa/specials/Xow_special_meta_.java | 38 + .../gplx/xowa/specials/Xow_special_mgr.java | 105 + .../gplx/xowa/specials/Xow_special_page.java | 6 + .../xowa/specials/Xow_special_wtr__base.java | 28 + .../specials/allPages/Xows_page_allpages.java | 188 + .../allPages/Xows_page_allpages_tst.java | 189 + .../specials/deletes/Xodel_page_special.java | 22 + .../xowa/specials/mgrs/Xoa_special_regy.java | 16 + .../xowa/specials/mgrs/Xosp_special_mgr.java | 37 + .../xowa/specials/movePage/Move_page.java | 169 + .../gplx/xowa/specials/nearby/Nearby_mgr.java | 184 + .../xowa/specials/nearby/Nearby_mgr_tst.java | 60 + .../statistics/Xop_statistics_page.java | 120 + .../statistics/Xop_statistics_page_tst.java | 38 + .../xowa/default_tab/Default_tab_page.java | 20 + .../xowa/specials/xowa/diags/Db_rdr_utl.java | 57 + .../xowa/diags/Xows_cmd__file_check.java | 122 + .../xowa/diags/Xows_cmd__fs_check.java | 43 + .../xowa/diags/Xows_cmd__sql_dump.java | 35 + .../specials/xowa/diags/Xows_diag_page.java | 31 + .../popup_history/Popup_history_page.java | 27 + .../xowa/system_data/System_data_page.java | 55 + .../src/gplx/xowa/users/Xou_fsys_mgr.java | 27 + 400_xowa/src/gplx/xowa/users/Xou_user.java | 12 + 400_xowa/src/gplx/xowa/users/Xou_user_.java | 50 + .../src/gplx/xowa/users/Xou_user_mgr.java | 21 + .../src/gplx/xowa/users/Xou_user_tst.java | 27 + 400_xowa/src/gplx/xowa/users/Xoue_user.java | 104 + 400_xowa/src/gplx/xowa/users/Xouv_user.java | 25 + .../xowa/users/bmks/Dbui_tbl_itm__bmk.java | 110 + .../xowa/users/bmks/Xoud_bmk_dir_row.java | 10 + .../xowa/users/bmks/Xoud_bmk_dir_tbl.java | 43 + .../xowa/users/bmks/Xoud_bmk_itm_row.java | 13 + .../xowa/users/bmks/Xoud_bmk_itm_tbl.java | 89 + .../gplx/xowa/users/bmks/Xoud_bmk_mgr.java | 16 + .../gplx/xowa/users/bmks/Xows_bmk_page.java | 20 + .../gplx/xowa/users/cfgs/Xocfg_meta_itm.java | 10 + .../gplx/xowa/users/cfgs/Xocfg_meta_tbl.java | 23 + .../src/gplx/xowa/users/cfgs/Xou_cfg_itm.java | 13 + .../src/gplx/xowa/users/cfgs/Xou_cfg_mgr.java | 56 + .../src/gplx/xowa/users/cfgs/Xou_cfg_tbl.java | 53 + .../src/gplx/xowa/users/data/Xou_db_file.java | 24 + .../src/gplx/xowa/users/data/Xou_db_mgr.java | 42 + .../gplx/xowa/users/data/Xoud_cfg_mgr.java | 65 + .../src/gplx/xowa/users/data/Xoud_id_mgr.java | 13 + .../gplx/xowa/users/data/Xoud_opt_scope.java | 68 + .../xowa/users/data/Xoud_opt_scope_tst.java | 35 + .../gplx/xowa/users/data/Xoud_regy_row.java | 7 + .../gplx/xowa/users/data/Xoud_regy_tbl.java | 72 + .../gplx/xowa/users/data/Xoud_site_mgr.java | 24 + .../gplx/xowa/users/data/Xoud_site_row.java | 13 + .../gplx/xowa/users/data/Xoud_site_tbl.java | 74 + .../gplx/xowa/users/data/Xoud_user_tbl.java | 54 + .../users/history/Dbui_tbl_itm__history.java | 80 + .../xowa/users/history/Xou_history_cfg.java | 12 + .../xowa/users/history/Xou_history_html.java | 42 + .../xowa/users/history/Xou_history_itm.java | 62 + .../xowa/users/history/Xou_history_mgr.java | 150 + .../users/history/Xou_history_mgr_tst.java | 83 + .../users/history/Xou_history_sorter.java | 11 + .../xowa/users/history/Xoud_history_mgr.java | 40 + .../xowa/users/history/Xoud_history_row.java | 13 + .../users/history/Xoud_history_special.java | 16 + .../xowa/users/history/Xoud_history_tbl.java | 79 + .../xowa/users/wikis/Xofs_url_itm_parser.java | 63 + .../users/wikis/Xofs_url_itm_parser_tst.java | 36 + .../gplx/xowa/users/wikis/Xou_wiki_mgr.java | 34 + .../src/gplx/xowa/wikis/Xoa_wiki_mgr.java | 12 + .../src/gplx/xowa/wikis/Xoa_wiki_mgr_.java | 28 + .../src/gplx/xowa/wikis/Xoa_wiki_regy.java | 34 + .../src/gplx/xowa/wikis/Xoae_wiki_mgr.java | 115 + .../src/gplx/xowa/wikis/Xow_page_tid.java | 27 + .../src/gplx/xowa/wikis/Xowv_repo_mgr.java | 55 + 400_xowa/src/gplx/xowa/wikis/Xowv_wiki.java | 128 + .../gplx/xowa/wikis/caches/Xow_cache_mgr.java | 69 + .../xowa/wikis/caches/Xow_defn_cache.java | 21 + .../xowa/wikis/caches/Xow_ifexist_cache.java | 76 + .../xowa/wikis/caches/Xow_page_cache.java | 109 + .../xowa/wikis/caches/Xow_page_cache_itm.java | 29 + .../xowa/wikis/caches/Xow_page_cache_wkr.java | 4 + .../xowa/wikis/data/Xow_data_mgr_tst.java | 147 + .../src/gplx/xowa/wikis/data/Xow_db_file.java | 98 + .../gplx/xowa/wikis/data/Xow_db_file_.java | 46 + .../xowa/wikis/data/Xow_db_file__core_.java | 116 + .../xowa/wikis/data/Xow_db_file_hash.java | 28 + .../wikis/data/Xow_db_file_schema_props.java | 21 + .../gplx/xowa/wikis/data/Xow_db_layout.java | 30 + .../src/gplx/xowa/wikis/data/Xow_db_mgr.java | 215 + .../gplx/xowa/wikis/data/Xowd_cfg_key_.java | 26 + .../gplx/xowa/wikis/data/Xowd_cfg_tbl_.java | 30 + .../xowa/wikis/data/Xowd_core_db_props.java | 65 + .../wikis/data/fetchers/Xow_page_fetcher.java | 7 + .../data/fetchers/Xow_page_fetcher_test.java | 15 + .../data/fetchers/Xow_page_fetcher_wiki.java | 10 + .../data/site_stats/Xowd_site_stats_mgr.java | 39 + .../data/site_stats/Xowd_site_stats_tbl.java | 43 + .../xowa/wikis/data/tbls/Select_in_cbk.java | 39 + .../wikis/data/tbls/Xowd_cat_core_tbl.java | 97 + .../data/tbls/Xowd_cat_core_tbl__in_wkr.java | 28 + .../wikis/data/tbls/Xowd_cat_link_tbl.java | 73 + .../wikis/data/tbls/Xowd_category_itm.java | 36 + .../xowa/wikis/data/tbls/Xowd_page_itm.java | 143 + .../wikis/data/tbls/Xowd_page_itm_sorter.java | 36 + .../wikis/data/tbls/Xowd_page_itm_tst.java | 25 + .../xowa/wikis/data/tbls/Xowd_page_tbl.java | 397 + .../wikis/data/tbls/Xowd_page_tbl__id.java | 23 + .../tbls/Xowd_page_tbl__in_wkr__base.java | 35 + .../wikis/data/tbls/Xowd_page_tbl__ttl.java | 24 + .../data/tbls/Xowd_page_tbl__ttl_ns.java | 35 + .../wikis/data/tbls/Xowd_page_tbl_tst.java | 18 + .../wikis/data/tbls/Xowd_site_ns_tbl.java | 74 + .../wikis/data/tbls/Xowd_text_bry_owner.java | 5 + .../xowa/wikis/data/tbls/Xowd_text_row.java | 9 + .../xowa/wikis/data/tbls/Xowd_text_tbl.java | 79 + .../wikis/data/tbls/Xowd_wbase_pid_tbl.java | 49 + .../wikis/data/tbls/Xowd_wbase_qid_tbl.java | 47 + .../data/tbls/Xowd_wbase_qid_tbl_tst.java | 31 + .../wikis/data/tbls/Xowd_xowa_db_tbl.java | 82 + .../gplx/xowa/wikis/dbs/Xodb_load_mgr.java | 20 + .../xowa/wikis/dbs/Xodb_load_mgr_sql.java | 65 + .../xowa/wikis/dbs/Xodb_load_mgr_txt.java | 394 + .../src/gplx/xowa/wikis/dbs/Xodb_mgr.java | 10 + .../src/gplx/xowa/wikis/dbs/Xodb_mgr_sql.java | 45 + .../src/gplx/xowa/wikis/dbs/Xodb_mgr_txt.java | 46 + .../gplx/xowa/wikis/dbs/Xodb_page_rdr.java | 7 + .../xowa/wikis/dbs/Xodb_page_rdr__sql.java | 23 + .../xowa/wikis/dbs/Xodb_page_rdr__tdb.java | 71 + .../gplx/xowa/wikis/dbs/Xodb_save_mgr.java | 10 + .../xowa/wikis/dbs/Xodb_save_mgr_sql.java | 63 + .../xowa/wikis/dbs/Xodb_save_mgr_txt.java | 106 + .../gplx/xowa/wikis/domains/Xow_abrv_wm.java | 14 + .../gplx/xowa/wikis/domains/Xow_abrv_wm_.java | 163 + .../xowa/wikis/domains/Xow_abrv_wm_tst.java | 79 + .../gplx/xowa/wikis/domains/Xow_abrv_xo_.java | 38 + .../xowa/wikis/domains/Xow_abrv_xo__tst.java | 26 + .../xowa/wikis/domains/Xow_domain_itm.java | 33 + .../xowa/wikis/domains/Xow_domain_itm_.java | 135 + .../wikis/domains/Xow_domain_itm_tst.java | 38 + .../xowa/wikis/domains/Xow_domain_regy.java | 847 ++ .../xowa/wikis/domains/Xow_domain_tid.java | 22 + .../xowa/wikis/domains/Xow_domain_tid_.java | 104 + .../xowa/wikis/domains/Xow_domain_uid_.java | 85 + .../wikis/domains/Xow_domain_uid__tst.java | 20 + .../domains/crts/Xow_domain_crt_itm.java | 5 + .../domains/crts/Xow_domain_crt_itm_.java | 47 + .../crts/Xow_domain_crt_itm__any_wiki.java | 5 + .../crts/Xow_domain_crt_itm__none.java | 5 + .../crts/Xow_domain_crt_itm__same_lang.java | 5 + .../crts/Xow_domain_crt_itm__same_type.java | 5 + .../crts/Xow_domain_crt_itm__self.java | 5 + .../crts/Xow_domain_crt_itm_parser.java | 74 + .../crts/Xow_domain_crt_kv_itm_mgr.java | 50 + .../gplx/xowa/wikis/fsys/Xow_fsys_mgr.java | 11 + .../xowa/wikis/metas/Bfmtr_eval_wiki.java | 16 + .../gplx/xowa/wikis/metas/Xow_html_util.java | 19 + .../gplx/xowa/wikis/metas/Xow_script_mgr.java | 42 + .../gplx/xowa/wikis/metas/Xow_sys_cfg.java | 16 + .../src/gplx/xowa/wikis/metas/Xow_user.java | 9 + .../gplx/xowa/wikis/metas/Xow_wiki_props.java | 80 + .../xowa/wikis/modules/Xow_module_base.java | 13 + .../xowa/wikis/modules/Xow_module_mgr.java | 37 + 400_xowa/src/gplx/xowa/wikis/nss/Xow_ns.java | 92 + 400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_.java | 58 + .../xowa/wikis/nss/Xow_ns_canonical_.java | 113 + .../src/gplx/xowa/wikis/nss/Xow_ns_case_.java | 18 + .../src/gplx/xowa/wikis/nss/Xow_ns_mgr.java | 250 + .../src/gplx/xowa/wikis/nss/Xow_ns_mgr_.java | 35 + .../gplx/xowa/wikis/nss/Xow_ns_mgr_tst.java | 62 + .../src/gplx/xowa/wikis/nss/Xow_ns_tst.java | 46 + .../wikis/pages/Xoa_page__commons_mgr.java | 10 + .../xowa/wikis/pages/Xopage_html_data.java | 30 + .../xowa/wikis/pages/Xopg_module_mgr.java | 39 + .../src/gplx/xowa/wikis/pages/Xopg_page_.java | 4 + .../xowa/wikis/pages/Xopg_page_heading.java | 32 + .../gplx/xowa/wikis/pages/Xopg_tab_data.java | 12 + .../wikis/pages/Xopg_tmpl_prepend_mgr.java | 45 + .../gplx/xowa/wikis/pages/Xow_page_mgr.java | 183 + .../gplx/xowa/wikis/pages/Xowe_page_mgr.java | 106 + .../xowa/wikis/pages/dbs/Xopg_db_data.java | 13 + .../xowa/wikis/pages/dbs/Xopg_db_html.java | 14 + .../xowa/wikis/pages/dbs/Xopg_db_page.java | 51 + .../wikis/pages/dbs/Xopg_db_protection.java | 15 + .../xowa/wikis/pages/dbs/Xopg_db_text.java | 8 + .../wikis/pages/hdumps/Xopg_hdump_data.java | 8 + .../wikis/pages/htmls/Xopg_html_data.java | 80 + .../wikis/pages/lnkis/Xoh_redlink_utl.java | 5 + .../xowa/wikis/pages/lnkis/Xopg_lnki_itm.java | 5 + .../wikis/pages/lnkis/Xopg_lnki_list.java | 34 + .../wikis/pages/lnkis/Xopg_redlink_mgr.java | 78 + .../pages/redirects/Xopg_redirect_itm.java | 12 + .../pages/redirects/Xopg_redirect_mgr.java | 40 + .../redirects/Xopg_redirect_mgr__tst.java | 36 + .../pages/skins/Xopg_xtn_skin_fmtr_arg.java | 16 + .../wikis/pages/skins/Xopg_xtn_skin_itm.java | 6 + .../pages/skins/Xopg_xtn_skin_itm_stub.java | 8 + .../pages/skins/Xopg_xtn_skin_itm_tid.java | 4 + .../wikis/pages/skins/Xopg_xtn_skin_mgr.java | 9 + .../xowa/wikis/pages/tags/Xopg_alertify_.java | 17 + .../xowa/wikis/pages/tags/Xopg_tag_itm.java | 71 + .../xowa/wikis/pages/tags/Xopg_tag_mgr.java | 34 + .../xowa/wikis/pages/tags/Xopg_tag_wtr.java | 95 + .../xowa/wikis/pages/tags/Xopg_tag_wtr_.java | 65 + .../wikis/pages/tags/Xopg_tag_wtr_cbk.java | 7 + .../wikis/pages/tags/Xopg_tag_wtr_cbk_.java | 4 + .../xowa/wikis/pages/wtxts/Xopg_toc_mgr.java | 40 + .../wikis/pages/wtxts/Xopg_wtxt_data.java | 15 + .../src/gplx/xowa/wikis/tdbs/ByteAry_fil.java | 22 + .../gplx/xowa/wikis/tdbs/Xotdb_dir_info.java | 37 + .../gplx/xowa/wikis/tdbs/Xotdb_dir_info_.java | 45 + .../gplx/xowa/wikis/tdbs/Xotdb_fsys_mgr.java | 66 + .../xowa/wikis/tdbs/Xotdb_fsys_mgr_tst.java | 21 + .../gplx/xowa/wikis/tdbs/Xotdb_page_itm_.java | 71 + .../wikis/tdbs/Xotdb_page_raw_parser.java | 43 + .../tdbs/bldrs/Io_line_rdr_key_gen_all.java | 8 + .../tdbs/hives/Bry_comparer_bgn_eos.java | 8 + .../xowa/wikis/tdbs/hives/Xoa_hive_mgr.java | 23 + .../xowa/wikis/tdbs/hives/Xob_hive_mgr.java | 181 + .../wikis/tdbs/hives/Xob_hive_mgr_tst.java | 12 + .../wikis/tdbs/hives/Xow_hive_mgr_fxt.java | 96 + .../xowa/wikis/tdbs/hives/Xowd_hive_mgr.java | 79 + .../wikis/tdbs/hives/Xowd_hive_mgr_tst.java | 82 + .../wikis/tdbs/hives/Xowd_hive_regy_itm.java | 36 + .../xowa/wikis/tdbs/hives/Xowd_regy_mgr.java | 59 + .../xowa/wikis/tdbs/hives/Xowd_regy_mgr_.java | 27 + .../wikis/tdbs/hives/Xowd_regy_mgr__tst.java | 20 + .../wikis/tdbs/hives/Xowd_regy_mgr_tst.java | 71 + .../hives/Xowd_ttl_file_comparer_end.java | 10 + .../xowa/wikis/tdbs/metas/Xof_meta_fil.java | 44 + .../wikis/tdbs/metas/Xof_meta_fil_tst.java | 12 + .../xowa/wikis/tdbs/metas/Xof_meta_itm.java | 167 + .../xowa/wikis/tdbs/metas/Xof_meta_mgr.java | 101 + .../wikis/tdbs/metas/Xof_meta_mgr_tst.java | 79 + .../xowa/wikis/tdbs/metas/Xof_meta_thumb.java | 34 + .../tdbs/metas/Xof_meta_thumb_parser.java | 56 + .../tdbs/metas/Xof_meta_thumb_parser_tst.java | 33 + .../xowa/wikis/tdbs/stats/Xob_stat_itm.java | 26 + .../xowa/wikis/tdbs/stats/Xob_stat_mgr.java | 52 + .../xowa/wikis/tdbs/stats/Xob_stat_type.java | 26 + .../xowa/wikis/tdbs/utils/Xos_url_gen.java | 31 + .../wikis/tdbs/utils/Xos_url_gen_tst.java | 15 + .../xowa/wikis/tdbs/xdats/Xob_xdat_file.java | 256 + .../wikis/tdbs/xdats/Xob_xdat_file_tst.java | 101 + .../wikis/tdbs/xdats/Xob_xdat_file_wtr.java | 131 + .../tdbs/xdats/Xob_xdat_file_wtr_tst.java | 37 + .../xowa/wikis/tdbs/xdats/Xob_xdat_itm.java | 12 + .../xowa/wikis/ttls/Xoa_ttl__err_tst.java | 16 + .../gplx/xowa/wikis/ttls/Xoa_ttl_chkr.java | 13 + .../src/gplx/xowa/wikis/ttls/Xoa_ttl_fxt.java | 18 + .../xowa/wikis/ttls/Xow_ttl__anchor_tst.java | 26 + .../xowa/wikis/ttls/Xow_ttl__basic_tst.java | 44 + .../wikis/ttls/Xow_ttl__html_entity_tst.java | 12 + .../xowa/wikis/ttls/Xow_ttl__i18n_tst.java | 37 + .../xowa/wikis/ttls/Xow_ttl__qarg_tst.java | 14 + .../gplx/xowa/wikis/ttls/Xow_ttl__ws_tst.java | 12 + .../xowa/wikis/ttls/Xow_ttl__xwik_tst.java | 25 + .../src/gplx/xowa/wikis/ttls/Xow_ttl_fxt.java | 72 + .../gplx/xowa/wikis/ttls/Xow_ttl_parser.java | 8 + .../gplx/xowa/wikis/xwikis/Xow_xwiki_itm.java | 43 + .../xowa/wikis/xwikis/Xow_xwiki_itm_tst.java | 32 + .../gplx/xowa/wikis/xwikis/Xow_xwiki_mgr.java | 87 + .../xwikis/bldrs/Xow_xwiki_itm_bldr.java | 36 + .../xwikis/bldrs/Xow_xwiki_itm_bldr_tst.java | 21 + .../xwikis/interwikis/Xow_interwiki_itm.java | 11 + .../xwikis/interwikis/Xow_interwiki_map.java | 10 + .../xwikis/parsers/Xow_xwiki_itm_parser.java | 75 + .../parsers/Xow_xwiki_itm_parser_tst.java | 80 + .../xwikis/parsers/Xow_xwiki_mgr_tst.java | 130 + .../xwikis/sitelinks/Xoa_sitelink_grp.java | 34 + .../sitelinks/Xoa_sitelink_grp_mgr.java | 28 + .../xwikis/sitelinks/Xoa_sitelink_itm.java | 34 + .../sitelinks/Xoa_sitelink_itm_mgr.java | 20 + .../xwikis/sitelinks/Xoa_sitelink_mgr.java | 31 + .../sitelinks/Xoa_sitelink_mgr_parser.java | 35 + .../Xoa_sitelink_mgr_parser_tst.java | 52 + .../sitelinks/htmls/Xoa_sitelink_div_wtr.java | 52 + .../htmls/Xoa_sitelink_div_wtr_fxt.java | 45 + .../htmls/Xoa_sitelink_div_wtr_tst.java | 83 + .../sitelinks/htmls/Xoa_sitelink_grp_wtr.java | 22 + .../sitelinks/htmls/Xoa_sitelink_itm_wtr.java | 48 + .../htmls/Xoa_sitelink_itm_wtr__badge.java | 34 + 400_xowa/src/gplx/xowa/xtns/Xow_xtn_mgr.java | 101 + 400_xowa/src/gplx/xowa/xtns/Xox_mgr.java | 9 + 400_xowa/src/gplx/xowa/xtns/Xox_mgr_base.java | 55 + 400_xowa/src/gplx/xowa/xtns/Xox_xnde.java | 7 + 400_xowa/src/gplx/xowa/xtns/Xox_xnde_.java | 34 + .../xtns/assessments/Assessment_func.java | 12 + .../assessments/Assessment_func__tst.java | 8 + .../Xtn_categoryList_nde_tst.java | 7 + .../categoryList/Xtn_categorylist_nde.java | 7 + .../xtns/categorytrees/Categorytree_func.java | 16 + .../categorytrees/Categorytree_func_tst.java | 8 + .../gplx/xowa/xtns/cites/Cite_xtn_mgr.java | 14 + .../gplx/xowa/xtns/cites/Ref_html_wtr.java | 96 + .../xowa/xtns/cites/Ref_html_wtr_cfg.java | 106 + .../src/gplx/xowa/xtns/cites/Ref_itm_grp.java | 34 + .../src/gplx/xowa/xtns/cites/Ref_itm_lst.java | 48 + .../src/gplx/xowa/xtns/cites/Ref_itm_mgr.java | 36 + .../xowa/xtns/cites/Ref_itm_mgr_cfg_tst.java | 18 + .../gplx/xowa/xtns/cites/Ref_itm_mgr_tst.java | 66 + .../src/gplx/xowa/xtns/cites/Ref_nde.java | 64 + .../gplx/xowa/xtns/cites/References_nde.java | 46 + .../xtns/cites/References_nde_basic_tst.java | 185 + .../xtns/cites/References_nde_group_tst.java | 105 + .../xtns/cites/References_nde_pre_tst.java | 50 + .../xtns/cites/References_nde_rare_tst.java | 91 + .../xowa/xtns/cites/Xoh_ref_list_fmtr.java | 42 + .../gplx/xowa/xtns/cldrs/Cldr_lang_tbl.java | 30 + .../xtns/dynamicPageList/Dpl_html_data.java | 38 + .../xowa/xtns/dynamicPageList/Dpl_itm.java | 175 + .../xtns/dynamicPageList/Dpl_itm_keys.java | 108 + .../xowa/xtns/dynamicPageList/Dpl_page.java | 18 + .../xtns/dynamicPageList/Dpl_redirect.java | 13 + .../xowa/xtns/dynamicPageList/Dpl_sort.java | 26 + .../xowa/xtns/dynamicPageList/Dpl_xnde.java | 164 + .../xtns/dynamicPageList/Dpl_xnde_tst.java | 346 + .../flaggedRevs/Flagged_revs_xtn_mgr.java | 10 + .../Pages_using_pending_changes_func.java | 12 + .../Pages_using_pending_changes_func_tst.java | 8 + .../Pending_change_level_func.java | 10 + .../Pending_change_level_func_tst.java | 8 + .../scribunto/Flagged_revs_lib.java | 46 + .../scribunto/Flagged_revs_lib_tst.java | 20 + .../xtns/gallery/Gallery_box_w_fmtr_arg.java | 11 + .../gallery/Gallery_img_pad_fmtr_arg.java | 11 + .../gplx/xowa/xtns/gallery/Gallery_itm.java | 47 + .../xowa/xtns/gallery/Gallery_mgr_base.java | 50 + .../xowa/xtns/gallery/Gallery_mgr_base_.java | 58 + .../gallery/Gallery_mgr_base__basic__tst.java | 170 + .../gallery/Gallery_mgr_base__xatrs__tst.java | 48 + .../xtns/gallery/Gallery_mgr_packed_base.java | 65 + .../xowa/xtns/gallery/Gallery_mgr_wtr.java | 176 + .../xowa/xtns/gallery/Gallery_mgr_wtr_.java | 38 + .../xowa/xtns/gallery/Gallery_parser.java | 256 + .../xowa/xtns/gallery/Gallery_parser_tst.java | 97 + .../gplx/xowa/xtns/gallery/Gallery_xnde.java | 85 + .../xowa/xtns/gallery/Gallery_xnde_atrs.java | 23 + .../xowa/xtns/gallery/Gallery_xnde_tst.java | 165 + .../xowa/xtns/gallery/Gallery_xtn_mgr.java | 9 + .../xowa/xtns/geoCrumbs/Geoc_isin_func.java | 18 + .../xtns/geoCrumbs/Geoc_isin_func_tst.java | 18 + .../xowa/xtns/geoCrumbs/Geoc_isin_mgr.java | 16 + .../xtns/geodata/Geo_coordinates_func.java | 10 + .../geodata/Geo_coordinates_func_tst.java | 7 + .../src/gplx/xowa/xtns/graphs/Graph_xnde.java | 28 + .../src/gplx/xowa/xtns/graphs/Json_fmtr.java | 83 + .../gplx/xowa/xtns/graphs/Json_fmtr_tst.java | 55 + .../gplx/xowa/xtns/hieros/Hiero_file_mgr.java | 49 + .../gplx/xowa/xtns/hieros/Hiero_html_mgr.java | 220 + .../xowa/xtns/hieros/Hiero_html_mgr_fxt.java | 39 + .../xowa/xtns/hieros/Hiero_html_mgr_tst.java | 372 + .../gplx/xowa/xtns/hieros/Hiero_html_wtr.java | 154 + .../xtns/hieros/Hiero_mw_tables_parser.java | 102 + .../hieros/Hiero_mw_tables_parser_tst.java | 58 + .../gplx/xowa/xtns/hieros/Hiero_parser.java | 118 + .../xowa/xtns/hieros/Hiero_parser_tst.java | 42 + .../xowa/xtns/hieros/Hiero_phoneme_mgr.java | 42 + .../xowa/xtns/hieros/Hiero_prefab_mgr.java | 36 + .../src/gplx/xowa/xtns/hieros/Hiero_xnde.java | 28 + .../gplx/xowa/xtns/hieros/Hiero_xtn_mgr.java | 40 + .../gplx/xowa/xtns/imaps/Imap_base_fxt.java | 30 + .../src/gplx/xowa/xtns/imaps/Imap_map.java | 66 + .../src/gplx/xowa/xtns/imaps/Imap_parser.java | 221 + .../gplx/xowa/xtns/imaps/Imap_parser_tst.java | 60 + .../src/gplx/xowa/xtns/imaps/Imap_xnde.java | 25 + .../gplx/xowa/xtns/imaps/Imap_xtn_mgr.java | 34 + .../imaps/htmls/Imap_html__hdump__tst.java | 31 + .../imaps/htmls/Imap_html__hview__tst.java | 191 + .../xtns/imaps/htmls/Imap_html_fmtrs.java | 48 + .../xowa/xtns/imaps/htmls/Imap_img_arg.java | 61 + .../xowa/xtns/imaps/htmls/Imap_map_arg.java | 14 + .../xtns/imaps/htmls/Imap_shape_pts_arg.java | 19 + .../xtns/imaps/htmls/Imap_shapes_arg.java | 25 + .../xtns/imaps/htmls/Imap_shapes_arg_tst.java | 16 + .../xowa/xtns/imaps/itms/Imap_desc_tid.java | 53 + .../gplx/xowa/xtns/imaps/itms/Imap_err.java | 6 + .../xowa/xtns/imaps/itms/Imap_link_owner.java | 8 + .../xtns/imaps/itms/Imap_link_owner_.java | 60 + .../gplx/xowa/xtns/imaps/itms/Imap_part.java | 4 + .../gplx/xowa/xtns/imaps/itms/Imap_part_.java | 28 + .../xowa/xtns/imaps/itms/Imap_part_desc.java | 6 + .../xowa/xtns/imaps/itms/Imap_part_dflt.java | 9 + .../xowa/xtns/imaps/itms/Imap_part_img.java | 7 + .../xowa/xtns/imaps/itms/Imap_part_shape.java | 17 + .../xtns/indicators/Indicator_html_bldr.java | 46 + .../indicators/Indicator_html_bldr_tst.java | 49 + .../xowa/xtns/indicators/Indicator_xnde.java | 27 + .../xtns/indicators/Indicator_xnde_tst.java | 8 + .../xtns/indicators/Indicator_xtn_mgr.java | 9 + .../xowa/xtns/inputBox/Xtn_inputbox_nde.java | 7 + .../xtns/inputBox/Xtn_inputbox_nde_tst.java | 8 + .../gplx/xowa/xtns/insiders/Insider_func.java | 19 + .../xowa/xtns/insiders/Insider_func_tst.java | 21 + .../xowa/xtns/insiders/Insider_html_bldr.java | 60 + .../xtns/insiders/Insider_html_bldr_tst.java | 45 + .../xowa/xtns/insiders/Insider_xtn_mgr.java | 23 + .../xtns/kartographers/Mapframe_xnde.java | 14 + .../kartographers/Mapframe_xnde__tst.java | 14 + .../xowa/xtns/kartographers/Maplink_xnde.java | 14 + .../xtns/kartographers/Maplink_xnde__tst.java | 14 + .../gplx/xowa/xtns/listings/Listing_xnde.java | 253 + .../xtns/listings/Listing_xnde_basic_tst.java | 91 + .../listings/Listing_xnde_template_tst.java | 126 + .../xowa/xtns/listings/Listing_xtn_mgr.java | 65 + .../src/gplx/xowa/xtns/lst/Lst_pfunc_itm.java | 98 + .../src/gplx/xowa/xtns/lst/Lst_pfunc_lst.java | 21 + .../gplx/xowa/xtns/lst/Lst_pfunc_lst_.java | 55 + .../gplx/xowa/xtns/lst/Lst_pfunc_lst_tst.java | 121 + .../gplx/xowa/xtns/lst/Lst_pfunc_lsth.java | 20 + .../gplx/xowa/xtns/lst/Lst_pfunc_lsth_.java | 56 + .../xowa/xtns/lst/Lst_pfunc_lsth_tst.java | 115 + .../gplx/xowa/xtns/lst/Lst_pfunc_lstx.java | 19 + .../gplx/xowa/xtns/lst/Lst_pfunc_lstx_.java | 27 + .../xowa/xtns/lst/Lst_pfunc_lstx_tst.java | 18 + .../gplx/xowa/xtns/lst/Lst_section_nde.java | 40 + .../xowa/xtns/lst/Lst_section_nde_mgr.java | 8 + .../xowa/xtns/lst/Lst_section_nde_tst.java | 14 + .../xowa/xtns/mapSources/Map_dd2dms_func.java | 54 + .../xtns/mapSources/Map_dd2dms_func_tst.java | 9 + .../xowa/xtns/mapSources/Map_deg2dd_func.java | 19 + .../xtns/mapSources/Map_deg2dd_func_tst.java | 10 + .../xtns/mapSources/Map_geolink_func.java | 89 + .../xtns/mapSources/Map_geolink_func_tst.java | 18 + .../gplx/xowa/xtns/mapSources/Map_math.java | 290 + .../xtns/massMessage/Message_target_func.java | 13 + .../massMessage/Message_target_func_tst.java | 8 + .../src/gplx/xowa/xtns/math/Xomath_addon.java | 12 + .../src/gplx/xowa/xtns/math/Xomath_core.java | 28 + .../gplx/xowa/xtns/math/Xomath_core__tst.java | 36 + .../gplx/xowa/xtns/math/Xomath_html_wtr.java | 89 + .../xowa/xtns/math/Xomath_latex_bldr.java | 79 + .../gplx/xowa/xtns/math/Xomath_latex_itm.java | 13 + .../gplx/xowa/xtns/math/Xomath_subst_mgr.java | 171 + .../xowa/xtns/math/Xomath_subst_mgr__tst.java | 10 + .../src/gplx/xowa/xtns/math/Xomath_xnde.java | 17 + .../xtns/math/bldrs/Xomath_check_cmd.java | 16 + .../xtns/math/bldrs/Xomath_check_mgr.java | 81 + .../xowa/xtns/math/texvcs/Texvc_checker.java | 48 + .../xtns/math/texvcs/Texvc_checker_tst.java | 39 + .../gplx/xowa/xtns/math/texvcs/Texvc_ctx.java | 18 + .../xowa/xtns/math/texvcs/Texvc_parser.java | 39 + .../xtns/math/texvcs/Texvc_parser_tst.java | 124 + .../math/texvcs/funcs/Texvc_func_itm.java | 35 + .../math/texvcs/funcs/Texvc_func_itm_.java | 692 ++ .../math/texvcs/funcs/Texvc_func_regy.java | 704 ++ .../math/texvcs/funcs/Texvc_scope_itm.java | 6 + .../math/texvcs/funcs/Texvc_scope_itm_.java | 17 + .../math/texvcs/funcs/Texvc_scope_regy.java | 28 + .../xowa/xtns/math/texvcs/lxrs/Texvc_lxr.java | 6 + .../xtns/math/texvcs/lxrs/Texvc_lxr_.java | 10 + .../texvcs/lxrs/Texvc_lxr__backslash.java | 36 + .../texvcs/lxrs/Texvc_lxr__curly_bgn.java | 10 + .../texvcs/lxrs/Texvc_lxr__curly_end.java | 11 + .../math/texvcs/lxrs/Texvc_lxr__leaf.java | 10 + .../xtns/math/texvcs/lxrs/Texvc_lxr__ws.java | 15 + .../math/texvcs/lxrs/Texvc_lxr_trie_bldr.java | 47 + .../xtns/math/texvcs/tkns/Texvc_regy_nde.java | 32 + .../xtns/math/texvcs/tkns/Texvc_regy_sub.java | 50 + .../xtns/math/texvcs/tkns/Texvc_regy_tkn.java | 70 + .../xtns/math/texvcs/tkns/Texvc_root.java | 80 + .../xowa/xtns/math/texvcs/tkns/Texvc_tkn.java | 14 + .../xtns/math/texvcs/tkns/Texvc_tkn_.java | 48 + .../math/texvcs/tkns/Texvc_tkn__func.java | 53 + .../math/texvcs/tkns/Texvc_tkn__leaf_raw.java | 24 + .../texvcs/tkns/Texvc_tkn__leaf_repl.java | 28 + .../xtns/math/texvcs/tkns/Texvc_tkn_mkr.java | 13 + .../math/texvcs/tkns/Texvc_tkn_stack.java | 30 + .../New_window_link_func.java | 36 + .../New_window_link_func_tst.java | 9 + .../gplx/xowa/xtns/pagebanners/Pgbnr_cfg.java | 23 + .../xowa/xtns/pagebanners/Pgbnr_func.java | 192 + .../xowa/xtns/pagebanners/Pgbnr_func_tst.java | 69 + .../xowa/xtns/pagebanners/Pgbnr_icon.java | 25 + .../gplx/xowa/xtns/pagebanners/Pgbnr_itm.java | 106 + .../xowa/xtns/pagebanners/Pgbnr_xtn_mgr.java | 86 + .../src/gplx/xowa/xtns/pfuncs/Pf_func.java | 8 + .../src/gplx/xowa/xtns/pfuncs/Pf_func_.java | 432 + .../gplx/xowa/xtns/pfuncs/Pf_func_base.java | 55 + .../gplx/xowa/xtns/pfuncs/exprs/Dot_tkn.java | 8 + .../gplx/xowa/xtns/pfuncs/exprs/Expr_tkn.java | 6 + .../xowa/xtns/pfuncs/exprs/Expr_tkn_.java | 5 + .../gplx/xowa/xtns/pfuncs/exprs/Func_tkn.java | 9 + .../xowa/xtns/pfuncs/exprs/Func_tkn_abs.java | 12 + .../xowa/xtns/pfuncs/exprs/Func_tkn_acos.java | 14 + .../xowa/xtns/pfuncs/exprs/Func_tkn_and.java | 13 + .../xowa/xtns/pfuncs/exprs/Func_tkn_asin.java | 14 + .../xowa/xtns/pfuncs/exprs/Func_tkn_atan.java | 12 + .../xowa/xtns/pfuncs/exprs/Func_tkn_base.java | 18 + .../xowa/xtns/pfuncs/exprs/Func_tkn_ceil.java | 12 + .../xowa/xtns/pfuncs/exprs/Func_tkn_cos.java | 12 + .../xtns/pfuncs/exprs/Func_tkn_divide.java | 18 + .../xtns/pfuncs/exprs/Func_tkn_e_const.java | 13 + .../xowa/xtns/pfuncs/exprs/Func_tkn_e_op.java | 24 + .../xowa/xtns/pfuncs/exprs/Func_tkn_eq.java | 13 + .../xowa/xtns/pfuncs/exprs/Func_tkn_exp.java | 12 + .../xtns/pfuncs/exprs/Func_tkn_floor.java | 12 + .../xowa/xtns/pfuncs/exprs/Func_tkn_fmod.java | 17 + .../xowa/xtns/pfuncs/exprs/Func_tkn_gt.java | 13 + .../xowa/xtns/pfuncs/exprs/Func_tkn_gte.java | 13 + .../xowa/xtns/pfuncs/exprs/Func_tkn_ln.java | 14 + .../xowa/xtns/pfuncs/exprs/Func_tkn_lt.java | 13 + .../xowa/xtns/pfuncs/exprs/Func_tkn_lte.java | 13 + .../xtns/pfuncs/exprs/Func_tkn_minus.java | 14 + .../pfuncs/exprs/Func_tkn_minus_negative.java | 13 + .../xowa/xtns/pfuncs/exprs/Func_tkn_mod.java | 19 + .../xowa/xtns/pfuncs/exprs/Func_tkn_neq.java | 13 + .../xowa/xtns/pfuncs/exprs/Func_tkn_not.java | 12 + .../xowa/xtns/pfuncs/exprs/Func_tkn_or.java | 13 + .../xowa/xtns/pfuncs/exprs/Func_tkn_pi.java | 12 + .../xowa/xtns/pfuncs/exprs/Func_tkn_plus.java | 15 + .../pfuncs/exprs/Func_tkn_plus_positive.java | 9 + .../xowa/xtns/pfuncs/exprs/Func_tkn_pow.java | 24 + .../xtns/pfuncs/exprs/Func_tkn_round.java | 23 + .../xowa/xtns/pfuncs/exprs/Func_tkn_sin.java | 12 + .../xowa/xtns/pfuncs/exprs/Func_tkn_sqrt.java | 12 + .../xtns/pfuncs/exprs/Func_tkn_stack.java | 22 + .../xowa/xtns/pfuncs/exprs/Func_tkn_tan.java | 12 + .../xtns/pfuncs/exprs/Func_tkn_times.java | 13 + .../xtns/pfuncs/exprs/Func_tkn_trunc.java | 12 + .../gplx/xowa/xtns/pfuncs/exprs/Num_tkn.java | 11 + .../xowa/xtns/pfuncs/exprs/Paren_bgn_tkn.java | 13 + .../xowa/xtns/pfuncs/exprs/Paren_end_tkn.java | 8 + .../xowa/xtns/pfuncs/exprs/Pfunc_expr.java | 29 + .../xtns/pfuncs/exprs/Pfunc_expr_shunter.java | 208 + .../xtns/pfuncs/exprs/Pfunc_expr_tst.java | 99 + .../xowa/xtns/pfuncs/exprs/Val_stack.java | 22 + .../gplx/xowa/xtns/pfuncs/exprs/Ws_tkn.java | 8 + .../gplx/xowa/xtns/pfuncs/ifs/Pfunc_if.java | 22 + .../xowa/xtns/pfuncs/ifs/Pfunc_if_tst.java | 28 + .../gplx/xowa/xtns/pfuncs/ifs/Pfunc_ifeq.java | 17 + .../xowa/xtns/pfuncs/ifs/Pfunc_ifeq_tst.java | 31 + .../xowa/xtns/pfuncs/ifs/Pfunc_iferror.java | 109 + .../xtns/pfuncs/ifs/Pfunc_iferror_tst.java | 13 + .../xowa/xtns/pfuncs/ifs/Pfunc_ifexist.java | 19 + .../xtns/pfuncs/ifs/Pfunc_ifexist_mgr.java | 66 + .../xtns/pfuncs/ifs/Pfunc_ifexist_tst.java | 32 + .../xowa/xtns/pfuncs/ifs/Pfunc_ifexpr.java | 26 + .../xtns/pfuncs/ifs/Pfunc_ifexpr_tst.java | 14 + .../xowa/xtns/pfuncs/ifs/Pfunc_switch.java | 76 + .../xtns/pfuncs/ifs/Pfunc_switch_tst.java | 74 + .../xowa/xtns/pfuncs/ifs/Xop_xowa_dbg.java | 13 + .../xowa/xtns/pfuncs/langs/Pfunc_gender.java | 43 + .../xtns/pfuncs/langs/Pfunc_gender_tst.java | 14 + .../xowa/xtns/pfuncs/langs/Pfunc_grammar.java | 17 + .../xtns/pfuncs/langs/Pfunc_grammar_tst.java | 48 + .../xtns/pfuncs/langs/Pfunc_i18n_tst.java | 39 + .../xowa/xtns/pfuncs/langs/Pfunc_int.java | 23 + .../xowa/xtns/pfuncs/langs/Pfunc_int_tst.java | 99 + .../xtns/pfuncs/langs/Pfunc_language.java | 19 + .../xtns/pfuncs/langs/Pfunc_language_tst.java | 13 + .../xowa/xtns/pfuncs/langs/Pfunc_plural.java | 16 + .../xtns/pfuncs/langs/Pfunc_plural_tst.java | 10 + .../xtns/pfuncs/numbers/Pf_formatnum.java | 28 + .../pfuncs/numbers/Pf_formatnum_de_tst.java | 21 + .../pfuncs/numbers/Pf_formatnum_en_tst.java | 30 + .../pfuncs/numbers/Pf_formatnum_es_tst.java | 21 + .../pfuncs/numbers/Pf_formatnum_fa_tst.java | 39 + .../xtns/pfuncs/pages/Pfunc_defaultsort.java | 9 + .../xtns/pfuncs/pages/Pfunc_displaytitle.java | 36 + .../pfuncs/pages/Pfunc_displaytitle_tst.java | 32 + .../xtns/pfuncs/pages/Pfunc_misc_tst.java | 7 + .../pfuncs/pages/Pfunc_noeditsection.java | 9 + .../xtns/pfuncs/pages/Pfunc_rev_props.java | 39 + .../pfuncs/pages/Pfunc_rev_props_tst.java | 14 + .../pfuncs/scribunto/Pfunc_scrib_lib.java | 34 + .../pfuncs/scribunto/Pfunc_scrib_lib_tst.java | 20 + .../xtns/pfuncs/scribunto/Pfunc_xtn_mgr.java | 10 + .../xowa/xtns/pfuncs/strings/Pfunc_case.java | 24 + .../xtns/pfuncs/strings/Pfunc_case_tst.java | 23 + .../xowa/xtns/pfuncs/strings/Pfunc_pad.java | 45 + .../xtns/pfuncs/strings/Pfunc_pad_tst.java | 17 + .../xowa/xtns/pfuncs/strings/Pfunc_tag.java | 45 + .../pfuncs/strings/Pfunc_tag_kvp_wtr.java | 60 + .../xtns/pfuncs/strings/Pfunc_tag_tst.java | 41 + .../xtns/pfuncs/stringutils/Pfunc_count.java | 26 + .../pfuncs/stringutils/Pfunc_count_tst.java | 8 + .../pfuncs/stringutils/Pfunc_explode.java | 44 + .../pfuncs/stringutils/Pfunc_explode_tst.java | 11 + .../xtns/pfuncs/stringutils/Pfunc_len.java | 13 + .../pfuncs/stringutils/Pfunc_len_tst.java | 9 + .../xtns/pfuncs/stringutils/Pfunc_pos.java | 18 + .../pfuncs/stringutils/Pfunc_pos_tst.java | 9 + .../pfuncs/stringutils/Pfunc_replace.java | 23 + .../pfuncs/stringutils/Pfunc_replace_tst.java | 10 + .../xtns/pfuncs/stringutils/Pfunc_rpos.java | 17 + .../pfuncs/stringutils/Pfunc_rpos_tst.java | 9 + .../xtns/pfuncs/stringutils/Pfunc_sub.java | 28 + .../pfuncs/stringutils/Pfunc_sub_tst.java | 12 + .../pfuncs/stringutils/Pfunc_urldecode.java | 13 + .../stringutils/Pfunc_urldecode_tst.java | 6 + .../xowa/xtns/pfuncs/times/Pft_fmt_itm.java | 6 + .../xowa/xtns/pfuncs/times/Pft_fmt_itm_.java | 201 + .../pfuncs/times/Pft_fmt_itm_foreign.java | 134 + .../pfuncs/times/Pft_fmt_itm_hebrew_.java | 229 + .../xtns/pfuncs/times/Pft_fmt_itm_hijiri.java | 56 + .../pfuncs/times/Pft_fmt_itm_iranian.java | 76 + .../pfuncs/times/Pft_fmt_itm_seg_int.java | 114 + .../xtns/pfuncs/times/Pft_func_date_int.java | 78 + .../pfuncs/times/Pft_func_date_lcl_tst.java | 20 + .../xtns/pfuncs/times/Pft_func_date_name.java | 19 + .../pfuncs/times/Pft_func_date_rev_tst.java | 13 + .../pfuncs/times/Pft_func_date_utc_tst.java | 21 + .../pfuncs/times/Pft_func_formatdate.java | 36 + .../times/Pft_func_formatdate_bldr.java | 50 + .../pfuncs/times/Pft_func_formatdate_tst.java | 21 + .../xowa/xtns/pfuncs/times/Pft_func_time.java | 80 + .../times/Pft_func_time__basic__tst.java | 85 + .../times/Pft_func_time__hebrew__tst.java | 18 + .../pfuncs/times/Pft_func_time__int__tst.java | 7 + .../times/Pft_func_time__other__tst.java | 36 + .../times/Pft_func_time__uncommon__tst.java | 10 + .../times/Pft_func_time_foreign_fxt.java | 26 + .../xowa/xtns/pfuncs/times/Pxd_eval_seg.java | 186 + .../gplx/xowa/xtns/pfuncs/times/Pxd_itm_.java | 106 + .../xowa/xtns/pfuncs/times/Pxd_itm_int.java | 315 + .../xowa/xtns/pfuncs/times/Pxd_itm_misc.java | 121 + .../xtns/pfuncs/times/Pxd_itm_month_name.java | 362 + .../xowa/xtns/pfuncs/times/Pxd_parser.java | 255 + .../xtns/pfuncs/times/Pxd_parser_tst.java | 79 + .../xtns/pfuncs/ttls/Pfunc_anchorencode.java | 17 + .../pfuncs/ttls/Pfunc_anchorencode_mgr.java | 83 + .../pfuncs/ttls/Pfunc_anchorencode_tst.java | 18 + .../xowa/xtns/pfuncs/ttls/Pfunc_filepath.java | 54 + .../xtns/pfuncs/ttls/Pfunc_filepath_itm.java | 7 + .../xtns/pfuncs/ttls/Pfunc_filepath_tst.java | 49 + .../gplx/xowa/xtns/pfuncs/ttls/Pfunc_ns.java | 43 + .../xowa/xtns/pfuncs/ttls/Pfunc_ns_tst.java | 20 + .../xowa/xtns/pfuncs/ttls/Pfunc_rel2abs.java | 171 + .../xtns/pfuncs/ttls/Pfunc_rel2abs_tst.java | 51 + .../xtns/pfuncs/ttls/Pfunc_titleparts.java | 62 + .../pfuncs/ttls/Pfunc_titleparts_log.java | 9 + .../pfuncs/ttls/Pfunc_titleparts_tst.java | 64 + .../gplx/xowa/xtns/pfuncs/ttls/Pfunc_ttl.java | 38 + .../xowa/xtns/pfuncs/ttls/Pfunc_ttl_tst.java | 34 + .../xtns/pfuncs/ttls/Pfunc_urlencode.java | 14 + .../xtns/pfuncs/ttls/Pfunc_urlencode_tst.java | 13 + .../xowa/xtns/pfuncs/ttls/Pfunc_urlfunc.java | 51 + .../xtns/pfuncs/ttls/Pfunc_urlfunc_tst.java | 25 + .../pfuncs/wikis/Pfunc_pagesincategory.java | 46 + .../wikis/Pfunc_pagesincategory_tst.java | 49 + .../xtns/pfuncs/wikis/Pfunc_wiki_props.java | 25 + .../pfuncs/wikis/Pfunc_wiki_props_tst.java | 22 + .../xtns/pfuncs/wikis/Pfunc_wiki_stats.java | 35 + .../pfuncs/wikis/Pfunc_wiki_stats_tst.java | 15 + .../src/gplx/xowa/xtns/poems/Poem_nde.java | 73 + .../gplx/xowa/xtns/poems/Poem_nde_tst.java | 263 + .../gplx/xowa/xtns/poems/Poem_xtn_mgr.java | 12 + .../xtns/proofreadPage/Pp_index_parser.java | 105 + .../xtns/proofreadPage/Pp_pagelist_nde.java | 10 + .../proofreadPage/Pp_pagelist_nde_tst.java | 8 + .../proofreadPage/Pp_pagequality_nde.java | 7 + .../proofreadPage/Pp_pagequality_nde_tst.java | 8 + .../xowa/xtns/proofreadPage/Pp_pages_nde.java | 432 + .../proofreadPage/Pp_pages_nde_basic_tst.java | 111 + .../proofreadPage/Pp_pages_nde_hdr_tst.java | 140 + .../proofreadPage/Pp_pages_nde_index_tst.java | 219 + .../Pp_pages_nde_recursion_tst.java | 25 + .../xowa/xtns/proofreadPage/Pp_xtn_mgr.java | 12 + .../src/gplx/xowa/xtns/quiz/Quiz_xnde.java | 9 + .../gplx/xowa/xtns/quiz/Quiz_xnde_tst.java | 8 + .../xtns/relatedArticles/Articles_func.java | 73 + .../relatedArticles/Articles_func_tst.java | 33 + .../xtns/relatedSites/Sites_html_bldr.java | 61 + .../relatedSites/Sites_html_bldr_tst.java | 52 + .../xtns/relatedSites/Sites_regy_itm.java | 11 + .../xtns/relatedSites/Sites_regy_mgr.java | 35 + .../xowa/xtns/relatedSites/Sites_xtn_mgr.java | 27 + 400_xowa/src/gplx/xowa/xtns/rss/Rss_xnde.java | 9 + .../src/gplx/xowa/xtns/rss/Rss_xnde_tst.java | 10 + .../src/gplx/xowa/xtns/scores/Score_xnde.java | 188 + .../gplx/xowa/xtns/scores/Score_xnde_tst.java | 8 + .../gplx/xowa/xtns/scores/Score_xtn_mgr.java | 58 + .../gplx/xowa/xtns/scribunto/Scrib_core.java | 206 + .../xowa/xtns/scribunto/Scrib_core_fxt.java | 132 + .../xowa/xtns/scribunto/Scrib_core_mgr.java | 38 + .../xowa/xtns/scribunto/Scrib_core_tst.java | 100 + .../xtns/scribunto/Scrib_err_filter_mgr.java | 77 + .../scribunto/Scrib_err_filter_mgr_tst.java | 33 + .../xowa/xtns/scribunto/Scrib_frame_.java | 27 + .../xowa/xtns/scribunto/Scrib_fsys_mgr.java | 45 + .../xtns/scribunto/Scrib_fsys_mgr_tst.java | 45 + .../xtns/scribunto/Scrib_invoke_func.java | 73 + .../xtns/scribunto/Scrib_invoke_func_fxt.java | 221 + .../xowa/xtns/scribunto/Scrib_kv_utl_.java | 55 + .../gplx/xowa/xtns/scribunto/Scrib_lib.java | 9 + .../xowa/xtns/scribunto/Scrib_lib_mgr.java | 14 + .../xowa/xtns/scribunto/Scrib_lua_mod.java | 45 + .../xowa/xtns/scribunto/Scrib_lua_proc.java | 10 + .../xowa/xtns/scribunto/Scrib_xtn_mgr.java | 51 + .../xtns/scribunto/engines/Scrib_engine.java | 10 + .../scribunto/engines/Scrib_engine_type.java | 17 + .../xtns/scribunto/engines/Scrib_server.java | 11 + .../scribunto/engines/luaj/Luaj_engine.java | 108 + .../scribunto/engines/luaj/Luaj_server.java | 79 + .../engines/luaj/Luaj_server_func_dbg.java | 22 + .../engines/luaj/Luaj_server_func_recv.java | 14 + .../scribunto/engines/luaj/Luaj_value_.java | 138 + .../engines/mocks/Mock_proc_fxt.java | 27 + .../engines/mocks/Mock_scrib_fxt.java | 85 + .../engines/process/Process_engine.java | 91 + .../engines/process/Process_recv_msg.java | 42 + .../engines/process/Process_send_wtr.java | 82 + .../engines/process/Process_send_wtr_tst.java | 25 + .../engines/process/Process_server.java | 109 + .../engines/process/Process_server_mock.java | 77 + .../engines/process/Process_stream_rdr.java | 71 + .../process/Process_stream_rdr_tst.java | 62 + .../xtns/scribunto/errs/Gfo_comp_op_1.java | 113 + .../xtns/scribunto/errs/Gfo_fld_owner.java | 51 + .../xowa/xtns/scribunto/errs/Gfo_val.java | 14 + .../xtns/scribunto/errs/Scrib_err_mgr.java | 72 + .../xtns/scribunto/libs/Keyval_find_.java | 32 + .../xowa/xtns/scribunto/libs/Kv_ary_utl.java | 66 + .../xowa/xtns/scribunto/libs/Scrib_err.java | 7 + .../xtns/scribunto/libs/Scrib_lib_html.java | 17 + .../scribunto/libs/Scrib_lib_language.java | 255 + .../libs/Scrib_lib_language_tst.java | 146 + .../scribunto/libs/Scrib_lib_message.java | 167 + .../scribunto/libs/Scrib_lib_message_tst.java | 59 + .../xtns/scribunto/libs/Scrib_lib_mw.java | 388 + .../libs/Scrib_lib_mw__invoke_tst.java | 116 + .../scribunto/libs/Scrib_lib_mw__lib_tst.java | 98 + .../xtns/scribunto/libs/Scrib_lib_site.java | 199 + .../scribunto/libs/Scrib_lib_site_tst.java | 175 + .../xtns/scribunto/libs/Scrib_lib_text.java | 125 + .../xtns/scribunto/libs/Scrib_lib_text_.java | 50 + .../libs/Scrib_lib_text__json_util.java | 206 + .../libs/Scrib_lib_text_html_entities.java | 1522 +++ .../libs/Scrib_lib_text_json_tst.java | 243 + .../scribunto/libs/Scrib_lib_text_tst.java | 17 + .../xtns/scribunto/libs/Scrib_lib_title.java | 227 + .../scribunto/libs/Scrib_lib_title_tst.java | 151 + .../xtns/scribunto/libs/Scrib_lib_uri.java | 63 + .../scribunto/libs/Scrib_lib_uri_tst.java | 26 + .../scribunto/libs/Scrib_lib_ustring.java | 356 + .../libs/Scrib_lib_ustring__find__tst.java | 37 + .../libs/Scrib_lib_ustring__gmatch__tst.java | 36 + .../libs/Scrib_lib_ustring__gsub__tst.java | 119 + .../libs/Scrib_lib_ustring__match__tst.java | 34 + .../Scrib_lib_ustring__shell_cmd__tst.java | 54 + .../scribunto/libs/Scrib_lib_wikibase.java | 108 + .../libs/Scrib_lib_wikibase_entity.java | 70 + .../libs/Scrib_lib_wikibase_entity_tst.java | 29 + .../libs/Scrib_lib_wikibase_srl.java | 161 + .../libs/Scrib_lib_wikibase_srl_tst.java | 471 + .../libs/Scrib_lib_wikibase_srl_visitor.java | 79 + .../libs/Scrib_lib_wikibase_tst.java | 138 + .../scribunto/libs/Scrib_regx_converter.java | 284 + .../libs/Scrib_regx_converter_tst.java | 52 + .../xtns/scribunto/libs/Xow_wiki_tst.java | 24 + .../xowa/xtns/scribunto/procs/Scrib_proc.java | 15 + .../xtns/scribunto/procs/Scrib_proc_args.java | 173 + .../scribunto/procs/Scrib_proc_args__tst.java | 32 + .../xtns/scribunto/procs/Scrib_proc_mgr.java | 27 + .../xtns/scribunto/procs/Scrib_proc_rslt.java | 45 + .../xtns/syntax_highlights/Int_rng_mgr.java | 70 + .../syntax_highlights/Int_rng_mgr_tst.java | 29 + .../xtns/syntax_highlights/Synh_xtn_nde.java | 40 + .../xtns/syntax_highlights/Synh_xtn_nde_.java | 61 + .../syntax_highlights/Synh_xtn_nde_tst.java | 157 + .../templateData/Xtn_templateData_nde.java | 15 + .../Xtn_templateData_nde_tst.java | 8 + .../titleBlacklists/Blacklist_scrib_lib.java | 25 + .../Blacklist_scrib_lib_tst.java | 12 + .../titleBlacklists/Blacklist_xtn_mgr.java | 10 + .../xtns/translates/Xop_languages_xnde.java | 114 + .../translates/Xop_languages_xnde_tst.java | 57 + .../xtns/translates/Xop_mylanguage_page.java | 21 + .../translates/Xop_mylanguage_page_tst.java | 41 + .../xtns/translates/Xop_translate_xnde.java | 16 + .../translates/Xop_translate_xnde_tst.java | 11 + .../xowa/xtns/translates/Xop_tvar_lxr.java | 22 + .../xtns/translates/Xop_tvar_lxr_tst.java | 15 + .../xowa/xtns/translates/Xop_tvar_tkn.java | 16 + .../gplx/xowa/xtns/wbases/Wbase_doc_mgr.java | 91 + .../xowa/xtns/wbases/Wbase_doc_mgr__tst.java | 16 + .../gplx/xowa/xtns/wbases/Wbase_pid_mgr.java | 40 + .../gplx/xowa/xtns/wbases/Wbase_prop_mgr.java | 29 + .../xtns/wbases/Wbase_prop_mgr_loader.java | 5 + .../xtns/wbases/Wbase_prop_mgr_loader_.java | 35 + .../gplx/xowa/xtns/wbases/Wbase_qid_mgr.java | 40 + .../src/gplx/xowa/xtns/wbases/Wdata_doc.java | 52 + .../gplx/xowa/xtns/wbases/Wdata_doc_bldr.java | 32 + .../gplx/xowa/xtns/wbases/Wdata_doc_wtr.java | 155 + .../xtns/wbases/Wdata_prop_val_visitor.java | 163 + .../xtns/wbases/Wdata_prop_val_visitor_.java | 166 + .../gplx/xowa/xtns/wbases/Wdata_wiki_mgr.java | 184 + .../xowa/xtns/wbases/Wdata_wiki_mgr_fxt.java | 163 + .../xowa/xtns/wbases/Wdata_wiki_mgr_tst.java | 59 + .../gplx/xowa/xtns/wbases/Wdata_xtn_mgr.java | 23 + .../xtns/wbases/Wdata_xwiki_link_wtr.java | 64 + .../xtns/wbases/Wdata_xwiki_link_wtr_tst.java | 123 + .../xtns/wbases/claims/Wbase_claim_grp.java | 22 + .../wbases/claims/Wbase_claim_grp_list.java | 7 + .../wbases/claims/Wbase_claim_visitor.java | 11 + .../wbases/claims/Wbase_references_grp.java | 6 + .../enums/Wbase_claim_entity_type_.java | 12 + .../claims/enums/Wbase_claim_rank_.java | 16 + .../wbases/claims/enums/Wbase_claim_type.java | 6 + .../claims/enums/Wbase_claim_type_.java | 43 + .../claims/enums/Wbase_claim_value_type_.java | 14 + .../wbases/claims/enums/Wbase_enum_hash.java | 41 + .../wbases/claims/enums/Wbase_enum_itm.java | 11 + .../wbases/claims/itms/Wbase_claim_base.java | 29 + .../claims/itms/Wbase_claim_entity.java | 35 + .../claims/itms/Wbase_claim_entity_.java | 15 + .../itms/Wbase_claim_globecoordinate.java | 26 + .../itms/Wbase_claim_globecoordinate_.java | 26 + .../itms/Wbase_claim_monolingualtext.java | 15 + .../itms/Wbase_claim_monolingualtext_.java | 13 + .../claims/itms/Wbase_claim_quantity.java | 55 + .../claims/itms/Wbase_claim_quantity_.java | 17 + .../claims/itms/Wbase_claim_string.java | 14 + .../wbases/claims/itms/Wbase_claim_time.java | 75 + .../wbases/claims/itms/Wbase_claim_time_.java | 49 + .../wbases/claims/itms/Wbase_claim_value.java | 16 + .../claims/itms/times/Wbase_data_itm.java | 14 + .../wbases/claims/itms/times/Wbase_date.java | 25 + .../wbases/claims/itms/times/Wbase_date_.java | 151 + .../claims/itms/times/Wbase_date_tst.java | 75 + .../xtns/wbases/core/Wdata_alias_itm.java | 12 + .../xtns/wbases/core/Wdata_dict_claim.java | 23 + .../xtns/wbases/core/Wdata_dict_claim_v1.java | 17 + .../wbases/core/Wdata_dict_datavalue.java | 15 + .../xtns/wbases/core/Wdata_dict_langtext.java | 13 + .../xtns/wbases/core/Wdata_dict_mainsnak.java | 21 + .../wbases/core/Wdata_dict_reference.java | 15 + .../xtns/wbases/core/Wdata_dict_sitelink.java | 15 + .../xowa/xtns/wbases/core/Wdata_dict_utl.java | 13 + .../xtns/wbases/core/Wdata_lang_sortable.java | 5 + .../xtns/wbases/core/Wdata_lang_sorter.java | 66 + .../xtns/wbases/core/Wdata_langtext_itm.java | 25 + .../xtns/wbases/core/Wdata_sitelink_itm.java | 17 + .../xowa/xtns/wbases/dbs/Xowb_prop_tbl.java | 48 + .../wbases/hwtrs/Wdata_fmtr__claim_grp.java | 279 + .../xtns/wbases/hwtrs/Wdata_fmtr__json.java | 32 + .../hwtrs/Wdata_fmtr__langtext_tbl.java | 112 + .../wbases/hwtrs/Wdata_fmtr__oview_tbl.java | 77 + .../xtns/wbases/hwtrs/Wdata_fmtr__slink.java | 149 + .../wbases/hwtrs/Wdata_fmtr__toc_div.java | 46 + .../xtns/wbases/hwtrs/Wdata_hwtr_mgr.java | 79 + .../xtns/wbases/hwtrs/Wdata_hwtr_mgr_tst.java | 311 + .../xtns/wbases/hwtrs/Wdata_hwtr_msgs.java | 212 + .../xowa/xtns/wbases/hwtrs/Wdata_lbl_itm.java | 30 + .../xowa/xtns/wbases/hwtrs/Wdata_lbl_mgr.java | 122 + .../xowa/xtns/wbases/hwtrs/Wdata_lbl_wkr.java | 5 + .../xtns/wbases/hwtrs/Wdata_lbl_wkr_wiki.java | 33 + .../xtns/wbases/hwtrs/Wdata_slink_grp.java | 66 + .../xtns/wbases/hwtrs/Wdata_toc_data.java | 20 + .../wbases/hwtrs/Wdata_visitor__html_wtr.java | 46 + .../hwtrs/Wdata_visitor__html_wtr_tst.java | 86 + .../hwtrs/Wdata_visitor__lbl_gatherer.java | 26 + .../wbases/imports/Wdata_idx_mgr_base.java | 25 + .../xtns/wbases/imports/Wdata_idx_wtr.java | 37 + .../wbases/imports/Xob_wbase_ns_parser.java | 34 + .../xtns/wbases/imports/Xob_wdata_db_cmd.java | 407 + .../wbases/imports/Xob_wdata_pid_base.java | 45 + .../imports/Xob_wdata_pid_base_tst.java | 58 + .../wbases/imports/Xob_wdata_pid_sql.java | 42 + .../wbases/imports/Xob_wdata_pid_txt.java | 33 + .../wbases/imports/Xob_wdata_qid_base.java | 50 + .../imports/Xob_wdata_qid_base_tst.java | 158 + .../wbases/imports/Xob_wdata_qid_sql.java | 25 + .../wbases/imports/Xob_wdata_qid_txt.java | 31 + .../xtns/wbases/imports/Xowb_bldr_addon.java | 12 + .../imports/json/Io_stream_rdr_mgr.java | 56 + .../imports/json/Xowb_json_dump_cmd.java | 23 + .../imports/json/Xowb_json_dump_db.java | 85 + .../imports/json/Xowb_json_dump_parser.java | 70 + .../wbases/parsers/Wbase_claim_factory.java | 101 + .../parsers/Wdata_claims_parser_v2.java | 130 + .../xtns/wbases/parsers/Wdata_doc_parser.java | 13 + .../parsers/Wdata_doc_parser_fxt_base.java | 85 + .../wbases/parsers/Wdata_doc_parser_v1.java | 248 + .../parsers/Wdata_doc_parser_v1_tst.java | 170 + .../wbases/parsers/Wdata_doc_parser_v2.java | 130 + .../Wdata_doc_parser_v2__basic__tst.java | 243 + .../Wdata_doc_parser_v2__claims__tst.java | 176 + .../wbases/pfuncs/Wbase_statement_mgr_.java | 65 + .../Wdata_external_lang_links_data.java | 37 + .../pfuncs/Wdata_pf_noExternalLangLinks.java | 17 + .../Wdata_pf_noExternalLangLinks_tst.java | 47 + .../xtns/wbases/pfuncs/Wdata_pf_property.java | 12 + .../pfuncs/Wdata_pf_property__basic__tst.java | 126 + .../pfuncs/Wdata_pf_property__parse__tst.java | 37 + .../wbases/pfuncs/Wdata_pf_property_data.java | 52 + .../wbases/pfuncs/Wdata_pf_property_fmt.java | 34 + .../wbases/pfuncs/Wdata_pf_statements.java | 12 + .../Wdata_pf_statements__basic__tst.java | 16 + .../wbases/pfuncs/Wdata_pf_wbreponame.java | 12 + .../pfuncs/Wdata_pf_wbreponame_tst.java | 19 + .../specials/Wdata_itemByTitle_cfg.java | 14 + .../specials/Wdata_itemByTitle_page.java | 72 + .../specials/Wdata_itemByTitle_page_tst.java | 59 + .../xtns/wikias/Random_selection_xnde.java | 94 + .../wikias/Random_selection_xnde_tst.java | 30 + .../gplx/xowa/xtns/wikias/Tabber_tab_itm.java | 34 + .../gplx/xowa/xtns/wikias/Tabber_xnde.java | 47 + .../xowa/xtns/wikias/Tabber_xnde_tst.java | 53 + .../gplx/xowa/xtns/wikias/Tabview_xnde.java | 136 + .../xowa/xtns/wikias/Tabview_xnde_tst.java | 25 + .../xtns/xowa_cmds/Xo_custom_html_pos_.java | 32 + .../xowa/xtns/xowa_cmds/Xop_xowa_cmd.java | 27 + .../xowa/xtns/xowa_cmds/Xop_xowa_cmd_tst.java | 78 + .../xowa/xtns/xowa_cmds/Xop_xowa_func.java | 19 + .../xtns/xowa_cmds/Xop_xowa_func_tst.java | 14 + .../xtns/xowa_cmds/Xox_xowa_html_cmd.java | 43 + .../xtns/xowa_cmds/Xox_xowa_html_cmd_tst.java | 40 + .../wiki_setups/Xop_wiki_setup_mgr.java | 60 + .../wiki_setups/Xop_wiki_setup_xnde.java | 34 + .../xowa_cmds/wiki_setups/Xows_root_itm.java | 91 + .../src/gplx/xowa/Xoa_manifest_item.java | 24 + .../src/gplx/xowa/Xoa_manifest_list.java | 59 + .../src/gplx/xowa/Xoa_manifest_view.java | 112 + .../src/gplx/xowa/Xoa_manifest_wkr.java | 90 + .../src/gplx/xowa/Xowa_app_updater.java | 28 + .../analyzers/Gflucene_analyzer_mgr_.java | 42 + .../gflucene/core/Gflucene_analyzer_data.java | 20 + .../gplx/gflucene/core/Gflucene_doc_data.java | 16 + .../gflucene/core/Gflucene_index_data.java | 12 + .../Gflucene_highlighter_item.java | 9 + .../Gflucene_highlighter_mgr.java | 88 + .../gflucene/indexers/Gflucene_idx_opt.java | 34 + .../indexers/Gflucene_indexer_mgr.java | 109 + .../searchers/Gflucene_searcher_mgr.java | 83 + .../searchers/Gflucene_searcher_qry.java | 9 + .../src/gplx/core/lists/HashByInt.java | 52 + .../src/gplx/xowa/mediawiki/XomwEnv.java | 37 + .../src/gplx/xowa/mediawiki/XophpArray.java | 30 + .../src/gplx/xowa/mediawiki/XophpEncode.java | 6 + .../XophpInvalidArgumentException.java | 3 + .../src/gplx/xowa/mediawiki/XophpMath.java | 12 + .../src/gplx/xowa/mediawiki/XophpPreg.java | 97 + .../gplx/xowa/mediawiki/XophpPregTest.java | 16 + .../src/gplx/xowa/mediawiki/XophpString.java | 146 + .../gplx/xowa/mediawiki/XophpStringTest.java | 70 + .../src/gplx/xowa/mediawiki/XophpUrl.java | 26 + .../src/gplx/xowa/mediawiki/XophpUtility.java | 30 + .../includes/XomwDefaultSettings.java | 8544 +++++++++++++++++ .../xowa/mediawiki/includes/XomwDefines.java | 294 + .../includes/XomwGlobalFunctions.java | 3687 +++++++ .../xowa/mediawiki/includes/XomwHtml.java | 1141 +++ .../xowa/mediawiki/includes/XomwHtmlTemp.java | 6 + .../XomwHtml_expandAttributesTest.java | 28 + .../xowa/mediawiki/includes/XomwLinker.java | 2209 +++++ .../XomwLinker_NormalizeSubpageLink.java | 10 + .../XomwLinker_NormalizeSubpageLinkTest.java | 25 + .../includes/XomwLinker_SplitTrailTest.java | 22 + .../mediawiki/includes/XomwMagicWord.java | 16 + .../includes/XomwMagicWordArray.java | 359 + .../includes/XomwMagicWordArrayTest.java | 47 + .../mediawiki/includes/XomwMagicWordMgr.java | 11 + .../includes/XomwMagicWordSynonym.java | 74 + .../includes/XomwMediaWikiServices.java | 586 ++ .../xowa/mediawiki/includes/XomwMessage.java | 1401 +++ .../mediawiki/includes/XomwMessageMgr.java | 9 + .../mediawiki/includes/XomwNamespace.java | 489 + .../mediawiki/includes/XomwNamespaceItem.java | 9 + .../includes/XomwNamespacesById.java | 22 + .../includes/XomwNamespacesByName.java | 20 + .../mediawiki/includes/XomwRawMessage.java | 52 + .../mediawiki/includes/XomwSanitizer.java | 2499 +++++ .../mediawiki/includes/XomwSanitizerTest.java | 161 + .../xowa/mediawiki/includes/XomwSetup.java | 884 ++ .../xowa/mediawiki/includes/XomwTitle.java | 4926 ++++++++++ .../mediawiki/includes/XomwTitleTest.java | 13 + .../gplx/xowa/mediawiki/includes/XomwXml.java | 68 + .../includes/cache/XomwMessageCache.java | 1267 +++ .../includes/content/XomwAbstractContent.java | 560 ++ .../includes/content/XomwContent.java | 508 + .../includes/content/XomwContentHandler.java | 1162 +++ .../includes/content/XomwMessageContent.java | 153 + .../content/XomwTextContentHandler.java | 135 + .../content/XomwWikitextContentHandler.java | 133 + .../includes/exception/XomwMWException.java | 5 + .../includes/filerepo/XomwFileRepo.java | 1907 ++++ .../includes/filerepo/file/XomwFile.java | 2271 +++++ .../filerepo/file/XomwFileFinder.java | 4 + .../filerepo/file/XomwFileFinderMock.java | 16 + .../filerepo/file/XomwFileFinderNoop.java | 4 + .../includes/filerepo/file/XomwLocalFile.java | 3110 ++++++ .../includes/interwiki/XomwInterwiki.java | 167 + .../interwiki/XomwInterwikiLookup.java | 37 + .../interwiki/XomwInterwikiLookupAdapter.java | 154 + .../includes/libs/XomwArrayObject.java | 24 + .../includes/libs/XomwGenericArrayObject.java | 216 + .../includes/libs/XomwStringUtils.java | 353 + .../includes/libs/XomwStringUtilsTest.java | 59 + .../libs/replacers/XomwRegexlikeReplacer.java | 10 + .../includes/libs/replacers/XomwReplacer.java | 8 + .../includes/linkers/XomwLinkRenderer.java | 462 + .../linkers/XomwLinkRendererTest.java | 18 + .../includes/media/XomwImageHandler.java | 286 + .../includes/media/XomwImageHandlerTest.java | 46 + .../includes/media/XomwMediaHandler.java | 851 ++ .../media/XomwMediaHandlerFactory.java | 46 + .../media/XomwMediaTransformOutput.java | 263 + .../includes/media/XomwThumbnailImage.java | 196 + .../XomwTransformationalImageHandler.java | 594 ++ .../includes/parsers/XomwBlockLevelPass.java | 649 ++ .../parsers/XomwBlockLevelPassTest.java | 25 + .../includes/parsers/XomwLinkHolderArray.java | 764 ++ .../parsers/XomwLinkHolderArrayTest.java | 29 + .../includes/parsers/XomwParser.java | 5341 +++++++++++ .../includes/parsers/XomwParserBfr.java | 31 + .../includes/parsers/XomwParserBfr_.java | 52 + .../includes/parsers/XomwParserCtx.java | 15 + .../includes/parsers/XomwParserIface.java | 12 + .../includes/parsers/XomwParserOptions.java | 916 ++ .../includes/parsers/XomwParserOutput.java | 1097 +++ .../includes/parsers/XomwParserTest.java | 63 + .../includes/parsers/XomwStripState.java | 331 + .../includes/parsers/XomwStripStateTest.java | 27 + .../includes/parsers/Xomw_output_type.java | 10 + .../includes/parsers/Xomw_regex_.java | 28 + .../includes/parsers/Xomw_regex_boundary.java | 22 + .../includes/parsers/Xomw_regex_parser.java | 84 + .../parsers/Xomw_regex_parser__tst.java | 25 + .../includes/parsers/Xomw_regex_space.java | 47 + .../includes/parsers/Xomw_regex_url.java | 23 + .../doubleunders/Xomw_doubleunder_data.java | 39 + .../doubleunders/Xomw_doubleunder_wkr.java | 187 + .../Xomw_doubleunder_wkr__tst.java | 35 + .../parsers/headings/Xomw_heading_cbk.java | 5 + .../headings/Xomw_heading_cbk__html.java | 35 + .../parsers/headings/Xomw_heading_wkr.java | 107 + .../headings/Xomw_heading_wkr__tst.java | 24 + .../includes/parsers/hrs/Xomw_hr_wkr.java | 63 + .../parsers/hrs/Xomw_hr_wkr__tst.java | 19 + .../includes/parsers/lnkes/Xomw_lnke_wkr.java | 215 + .../parsers/lnkes/Xomw_lnke_wkr__tst.java | 56 + .../parsers/lnkis/Xomw_image_params.java | 5 + .../includes/parsers/lnkis/Xomw_lnki_wkr.java | 1147 +++ .../lnkis/Xomw_lnki_wkr__file__tst.java | 117 + .../lnkis/Xomw_lnki_wkr__text__tst.java | 12 + .../parsers/lnkis/Xomw_param_itm.java | 40 + .../parsers/lnkis/Xomw_param_map.java | 60 + .../parsers/lnkis/Xomw_params_frame.java | 67 + .../parsers/lnkis/Xomw_params_handler.java | 27 + .../parsers/lnkis/Xomw_params_horizAlign.java | 6 + .../parsers/lnkis/Xomw_params_mto.java | 27 + .../parsers/lnkis/Xomw_params_scalar.java | 18 + .../parsers/lnkis/Xomw_params_vertAlign.java | 6 + .../magiclinks/Xomw_magiclinks_wkr.java | 481 + .../magiclinks/Xomw_magiclinks_wkr__tst.java | 71 + .../includes/parsers/nbsps/Xomw_nbsp_wkr.java | 117 + .../parsers/nbsps/Xomw_nbsp_wkr__tst.java | 23 + .../parsers/prepros/Xomw_frame_itm.java | 6 + .../parsers/prepros/Xomw_frame_wkr.java | 547 ++ .../parsers/prepros/Xomw_prepro_node.java | 81 + .../prepros/Xomw_prepro_node__base.java | 11 + .../prepros/Xomw_prepro_node__part.java | 28 + .../prepros/Xomw_prepro_node__template.java | 19 + .../parsers/prepros/Xomw_prepro_rule.java | 49 + .../parsers/prepros/Xomw_prepro_stack.java | 153 + .../parsers/prepros/Xomw_prepro_wkr.java | 771 ++ .../parsers/prepros/Xomw_prepro_wkr__tst.java | 218 + .../parsers/quotes/Xomw_quote_wkr.java | 448 + .../parsers/quotes/Xomw_quote_wkr__tst.java | 28 + .../parsers/tables/Xomw_table_wkr.java | 465 + .../parsers/tables/Xomw_table_wkr__tst.java | 117 + .../includes/site/XomwMediaWikiSite.java | 185 + .../mediawiki/includes/site/XomwSite.java | 680 ++ .../mediawiki/includes/site/XomwSiteList.java | 338 + .../includes/site/XomwSiteLookup.java | 27 + .../includes/site/XomwXowaSiteLookup.java | 32 + .../title/XomwMalformedTitleException.java | 3 + .../title/XomwMediaWikiTitleCodec.java | 479 + .../title/XomwMediaWikiTitleCodecParts.java | 23 + .../title/XomwMediaWikiTitleCodecTest.java | 45 + .../includes/title/XomwTitleFormatter.java | 81 + .../includes/title/XomwTitleParser.java | 25 + .../includes/xohtml/Xomw_atr_itm.java | 12 + .../includes/xohtml/Xomw_atr_mgr.java | 55 + .../includes/xohtml/Xomw_html_elem.java | 9 + .../includes/xohtml/Xomw_opt_mgr.java | 7 + .../includes/xohtml/Xomw_qry_mgr.java | 10 + .../mediawiki/languages/XomwLanguage.java | 5027 ++++++++++ .../mediawiki/languages/XomwLanguageTest.java | 114 + 4537 files changed, 311750 insertions(+) create mode 100644 100_core/src/gplx/Array_.java create mode 100644 100_core/src/gplx/Array__tst.java create mode 100644 100_core/src/gplx/Bool_.java create mode 100644 100_core/src/gplx/Bool__tst.java create mode 100644 100_core/src/gplx/Bry_.java create mode 100644 100_core/src/gplx/Bry__tst.java create mode 100644 100_core/src/gplx/Bry_bfr.java create mode 100644 100_core/src/gplx/Bry_bfr_.java create mode 100644 100_core/src/gplx/Bry_bfr_tst.java create mode 100644 100_core/src/gplx/Bry_find_.java create mode 100644 100_core/src/gplx/Bry_find__tst.java create mode 100644 100_core/src/gplx/Bry_fmt.java create mode 100644 100_core/src/gplx/Bry_split_.java create mode 100644 100_core/src/gplx/Bry_split__tst.java create mode 100644 100_core/src/gplx/Byte_.java create mode 100644 100_core/src/gplx/Byte__tst.java create mode 100644 100_core/src/gplx/Byte_ascii.java create mode 100644 100_core/src/gplx/Cancelable.java create mode 100644 100_core/src/gplx/Cancelable_.java create mode 100644 100_core/src/gplx/Char_.java create mode 100644 100_core/src/gplx/CompareAble.java create mode 100644 100_core/src/gplx/CompareAble_.java create mode 100644 100_core/src/gplx/DateAdp.java create mode 100644 100_core/src/gplx/DateAdp_.java create mode 100644 100_core/src/gplx/DateAdp__tst.java create mode 100644 100_core/src/gplx/Datetime_now.java create mode 100644 100_core/src/gplx/Decimal_adp.java create mode 100644 100_core/src/gplx/Decimal_adp_.java create mode 100644 100_core/src/gplx/Decimal_adp__tst.java create mode 100644 100_core/src/gplx/Double_.java create mode 100644 100_core/src/gplx/Double__tst.java create mode 100644 100_core/src/gplx/Enm_.java create mode 100644 100_core/src/gplx/Err.java create mode 100644 100_core/src/gplx/Err_.java create mode 100644 100_core/src/gplx/Err_tst.java create mode 100644 100_core/src/gplx/Float_.java create mode 100644 100_core/src/gplx/GfoMsg.java create mode 100644 100_core/src/gplx/GfoMsgUtl.java create mode 100644 100_core/src/gplx/GfoMsg_.java create mode 100644 100_core/src/gplx/GfoMsg_tst.java create mode 100644 100_core/src/gplx/GfoTemplate.java create mode 100644 100_core/src/gplx/GfoTemplateFactory.java create mode 100644 100_core/src/gplx/Gfo_evt_itm.java create mode 100644 100_core/src/gplx/Gfo_evt_mgr.java create mode 100644 100_core/src/gplx/Gfo_evt_mgr_.java create mode 100644 100_core/src/gplx/Gfo_evt_mgr_owner.java create mode 100644 100_core/src/gplx/Gfo_evt_mgr_tst.java create mode 100644 100_core/src/gplx/Gfo_invk.java create mode 100644 100_core/src/gplx/Gfo_invk_.java create mode 100644 100_core/src/gplx/Gfo_invk_cmd.java create mode 100644 100_core/src/gplx/Gfo_invk_cmd_mgr.java create mode 100644 100_core/src/gplx/Gfo_invk_cmd_mgr_owner.java create mode 100644 100_core/src/gplx/Gfo_invk_root_wkr.java create mode 100644 100_core/src/gplx/Gfo_invk_to_str.java create mode 100644 100_core/src/gplx/Gfo_log.java create mode 100644 100_core/src/gplx/Gfo_log_.java create mode 100644 100_core/src/gplx/Gfo_log_bfr.java create mode 100644 100_core/src/gplx/Gfo_usr_dlg.java create mode 100644 100_core/src/gplx/Gfo_usr_dlg_.java create mode 100644 100_core/src/gplx/Gfo_usr_dlg__gui.java create mode 100644 100_core/src/gplx/Gfo_usr_dlg__gui_.java create mode 100644 100_core/src/gplx/Gfo_usr_dlg__gui_test.java create mode 100644 100_core/src/gplx/Gfo_usr_dlg__log.java create mode 100644 100_core/src/gplx/Gfo_usr_dlg__log_.java create mode 100644 100_core/src/gplx/Gfo_usr_dlg__log_base.java create mode 100644 100_core/src/gplx/Gfo_usr_dlg_base.java create mode 100644 100_core/src/gplx/GfsCtx.java create mode 100644 100_core/src/gplx/Guid_adp.java create mode 100644 100_core/src/gplx/Guid_adp_.java create mode 100644 100_core/src/gplx/Guid_adp__tst.java create mode 100644 100_core/src/gplx/Hash_adp.java create mode 100644 100_core/src/gplx/Hash_adp_.java create mode 100644 100_core/src/gplx/Hash_adp_bry.java create mode 100644 100_core/src/gplx/Hash_adp_bry_tst.java create mode 100644 100_core/src/gplx/Int_.java create mode 100644 100_core/src/gplx/Int__tst.java create mode 100644 100_core/src/gplx/Int_ary_.java create mode 100644 100_core/src/gplx/Int_ary__tst.java create mode 100644 100_core/src/gplx/Internal.java create mode 100644 100_core/src/gplx/Io_mgr.java create mode 100644 100_core/src/gplx/Io_mgr__tst.java create mode 100644 100_core/src/gplx/Io_url.java create mode 100644 100_core/src/gplx/Io_url_.java create mode 100644 100_core/src/gplx/Io_url__tst.java create mode 100644 100_core/src/gplx/Keyval.java create mode 100644 100_core/src/gplx/Keyval_.java create mode 100644 100_core/src/gplx/Keyval_hash.java create mode 100644 100_core/src/gplx/Keyval_list.java create mode 100644 100_core/src/gplx/List_adp.java create mode 100644 100_core/src/gplx/List_adp_.java create mode 100644 100_core/src/gplx/List_adp_base.java create mode 100644 100_core/src/gplx/List_adp_tst.java create mode 100644 100_core/src/gplx/Long_.java create mode 100644 100_core/src/gplx/Long__tst.java create mode 100644 100_core/src/gplx/Math_.java create mode 100644 100_core/src/gplx/Math__tst.java create mode 100644 100_core/src/gplx/New.java create mode 100644 100_core/src/gplx/ObjAry.java create mode 100644 100_core/src/gplx/Object_.java create mode 100644 100_core/src/gplx/Object__tst.java create mode 100644 100_core/src/gplx/Ordered_hash.java create mode 100644 100_core/src/gplx/Ordered_hash_.java create mode 100644 100_core/src/gplx/Ordered_hash_base.java create mode 100644 100_core/src/gplx/Ordered_hash_tst.java create mode 100644 100_core/src/gplx/RandomAdp.java create mode 100644 100_core/src/gplx/RandomAdp_.java create mode 100644 100_core/src/gplx/Rls_able.java create mode 100644 100_core/src/gplx/Rls_able_.java create mode 100644 100_core/src/gplx/Short_.java create mode 100644 100_core/src/gplx/String_.java create mode 100644 100_core/src/gplx/String__tst.java create mode 100644 100_core/src/gplx/Tfds.java create mode 100644 100_core/src/gplx/TfdsTstr_fxt.java create mode 100644 100_core/src/gplx/Time_span.java create mode 100644 100_core/src/gplx/Time_span_.java create mode 100644 100_core/src/gplx/To_str_able.java create mode 100644 100_core/src/gplx/To_str_able_.java create mode 100644 100_core/src/gplx/Type_.java create mode 100644 100_core/src/gplx/Type_ids_.java create mode 100644 100_core/src/gplx/UsrDlg.java create mode 100644 100_core/src/gplx/UsrDlg_.java create mode 100644 100_core/src/gplx/UsrMsg.java create mode 100644 100_core/src/gplx/UsrMsgWkr.java create mode 100644 100_core/src/gplx/UsrMsgWkr_.java create mode 100644 100_core/src/gplx/UsrMsgWkr_console.java create mode 100644 100_core/src/gplx/UsrMsgWkr_test.java create mode 100644 100_core/src/gplx/Virtual.java create mode 100644 100_core/src/gplx/Yn.java create mode 100644 100_core/tst/gplx/GfoMsg_rdr_tst.java create mode 100644 100_core/tst/gplx/GfoTreeBldr_fxt.java create mode 100644 110_gfml/src_100_tkn/gplx/gfml/GfmlLxr.java create mode 100644 110_gfml/src_100_tkn/gplx/gfml/GfmlLxr_.java create mode 100644 110_gfml/src_100_tkn/gplx/gfml/GfmlObj.java create mode 100644 110_gfml/src_100_tkn/gplx/gfml/GfmlObjList.java create mode 100644 110_gfml/src_100_tkn/gplx/gfml/GfmlTkn.java create mode 100644 110_gfml/src_100_tkn/gplx/gfml/GfmlTkn_.java create mode 100644 110_gfml/src_100_tkn/gplx/gfml/GfmlTrie.java create mode 100644 110_gfml/src_100_tkn/gplx/gfml/IntObjHash.java create mode 100644 110_gfml/src_200_type/gplx/gfml/GfmlFld.java create mode 100644 110_gfml/src_200_type/gplx/gfml/GfmlFldList.java create mode 100644 110_gfml/src_200_type/gplx/gfml/GfmlType.java create mode 100644 110_gfml/src_200_type/gplx/gfml/GfmlTypeCompiler.java create mode 100644 110_gfml/src_200_type/gplx/gfml/GfmlTypeHash.java create mode 100644 110_gfml/src_200_type/gplx/gfml/GfmlTypeMakr.java create mode 100644 110_gfml/src_200_type/gplx/gfml/GfmlTypeMgr.java create mode 100644 110_gfml/src_300_gdoc/gplx/gfml/GfmlAtr.java create mode 100644 110_gfml/src_300_gdoc/gplx/gfml/GfmlDoc.java create mode 100644 110_gfml/src_300_gdoc/gplx/gfml/GfmlDocLxrs.java create mode 100644 110_gfml/src_300_gdoc/gplx/gfml/GfmlDocPos.java create mode 100644 110_gfml/src_300_gdoc/gplx/gfml/GfmlDocWtr_.java create mode 100644 110_gfml/src_300_gdoc/gplx/gfml/GfmlDoc_.java create mode 100644 110_gfml/src_300_gdoc/gplx/gfml/GfmlItm.java create mode 100644 110_gfml/src_300_gdoc/gplx/gfml/GfmlItmHnds.java create mode 100644 110_gfml/src_300_gdoc/gplx/gfml/GfmlItmKeys.java create mode 100644 110_gfml/src_300_gdoc/gplx/gfml/GfmlNde.java create mode 100644 110_gfml/src_300_gdoc/gplx/gfml/GfmlScopeItm.java create mode 100644 110_gfml/src_400_pragma/gplx/gfml/GfmlPragma.java create mode 100644 110_gfml/src_400_pragma/gplx/gfml/GfmlPragmaDefault.java create mode 100644 110_gfml/src_400_pragma/gplx/gfml/GfmlPragmaLxrFrm.java create mode 100644 110_gfml/src_400_pragma/gplx/gfml/GfmlPragmaLxrSym.java create mode 100644 110_gfml/src_400_pragma/gplx/gfml/GfmlPragmaType.java create mode 100644 110_gfml/src_400_pragma/gplx/gfml/GfmlPragmaVar.java create mode 100644 110_gfml/src_400_pragma/gplx/gfml/GfmlVarCtx.java create mode 100644 110_gfml/src_400_pragma/gplx/gfml/GfmlVarItm.java create mode 100644 110_gfml/src_400_pragma/gplx/gfml/GfmlVarTkn.java create mode 100644 110_gfml/src_500_build/gplx/gfml/GfmlBldr.java create mode 100644 110_gfml/src_500_build/gplx/gfml/GfmlBldrCmd.java create mode 100644 110_gfml/src_500_build/gplx/gfml/GfmlBldrCmds.java create mode 100644 110_gfml/src_500_build/gplx/gfml/GfmlBldr_.java create mode 100644 110_gfml/src_500_build/gplx/gfml/GfmlFrame.java create mode 100644 110_gfml/src_500_build/gplx/gfml/GfmlFrame_.java create mode 100644 110_gfml/src_500_build/gplx/gfml/GfmlFrame_nde.java create mode 100644 110_gfml/src_500_build/gplx/gfml/GfmlFrame_ndeTknMgr.java create mode 100644 110_gfml/src_500_build/gplx/gfml/GfmlStringHighlighter.java create mode 100644 110_gfml/src_600_rdrWtr/gplx/gfml/GfmlDataNde.java create mode 100644 110_gfml/src_600_rdrWtr/gplx/gfml/GfmlDataRdr.java create mode 100644 110_gfml/src_600_rdrWtr/gplx/gfml/GfmlDataRdr_base.java create mode 100644 110_gfml/src_600_rdrWtr/gplx/gfml/GfmlDataWtr.java create mode 100644 110_gfml/src_600_rdrWtr/gplx/gfml/GfmlDataWtrOpts.java create mode 100644 110_gfml/src_600_rdrWtr/gplx/gfml/GfoMsgParser_gfml.java create mode 100644 110_gfml/src_600_rdrWtr/gplx/gfml/SqlConsts.java create mode 100644 110_gfml/src_600_rdrWtr/gplx/gfml/SqlDoc.java create mode 100644 110_gfml/tst/gplx/gfml/GfmlDataRdr_tst.java create mode 100644 110_gfml/tst/gplx/gfml/yfxts_GfmlParse_fxt.java create mode 100644 110_gfml/tst/gplx/gfml/yfxts_GfmlTypeCompiler_fxt.java create mode 100644 110_gfml/tst/gplx/gfml/ymoks_GfmlAtr_GfmlNde_mok.java create mode 100644 110_gfml/tst/gplx/gfml/ymoks_GfmlTkn_mok.java create mode 100644 110_gfml/tst/gplx/gfml/ymoks_GfmlTyp_GfmlFld_mok.java create mode 100644 110_gfml/tst/gplx/gfml/ymoks_UsrMsg_mok.java create mode 100644 110_gfml/tst/gplx/gfml/z011_IntObjHash_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z012_GfmlTrie_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z015_GfmlDocPos_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z016_GfmlScopeList_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z017_GfmlStringHighlighter_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z051_GfmlFldPool_keyed_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z081_GfmlDataWtr_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z082_GfmlDataWtrOpts_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z091_GfmlLxr_basic_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z101_core_ndeInline_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z102_core_whitespace_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z103_core_elmKey_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z111_core_comment0_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z112_core_comment1_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z120_quotes_eval0_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z121_quotes_quotes0_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z122_quotes_quote0_eval0_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z123_quotes_quoteBlock_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z124_quotes_quoteFold_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z151_ndeSubs_basic_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z152_ndeSubs_data_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z161_ndeHdrs_inline_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z162_ndeHdrs_err_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z163_ndeHdrs_body_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z164_hdeHdrs_data_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z181_ndeDots_basic_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z182_ndeDots_subs_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z183_ndeDots_parens_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z184_ndeDots_atrSpr_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z191_ndeProps_basic_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z192_ndeProps_dots_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z400_GfmlTypeMakr_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z401_types_compile_basic_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z402_types_compile_implicit_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z403_types_compile_default_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z411_types_apply_atrs_basic_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z421_types_apply_ndes_basic_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z422_types_apply_ndes_multi_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z423_types_apply_ndes_misc_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z424_types_apply_ndes_nest_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z441_types_parse_basic_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z442_types_parse_default_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z443_types_parse_keyd_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z450_fx_GfmlDefaultItem_fxt.java create mode 100644 110_gfml/tst/gplx/gfml/z451_dflts_compile_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z452_dflts_exec_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z455_dflts_scope_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z456_dflts_parse_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z481_vars_compile_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z482_vars_parse_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z501_lxr_parse_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z601_edit_atr_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z602_edit_nde_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z801_useCase_DataRdr_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z803_useCase_KbdKeyboard_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z811_useCase_GfmlIoSql_tst.java create mode 100644 110_gfml/tst/gplx/gfml/z901_perf_tst.java create mode 100644 140_dbs/src/gplx/dbs/Db_attach_itm.java create mode 100644 140_dbs/src/gplx/dbs/Db_attach_mgr.java create mode 100644 140_dbs/src/gplx/dbs/Db_attach_mgr__tst.java create mode 100644 140_dbs/src/gplx/dbs/Db_cmd_mode.java create mode 100644 140_dbs/src/gplx/dbs/Db_conn.java create mode 100644 140_dbs/src/gplx/dbs/Db_conn_.java create mode 100644 140_dbs/src/gplx/dbs/Db_conn_bldr.java create mode 100644 140_dbs/src/gplx/dbs/Db_conn_bldr_data.java create mode 100644 140_dbs/src/gplx/dbs/Db_conn_bldr_wkr.java create mode 100644 140_dbs/src/gplx/dbs/Db_conn_info.java create mode 100644 140_dbs/src/gplx/dbs/Db_conn_info_.java create mode 100644 140_dbs/src/gplx/dbs/Db_conn_info__base.java create mode 100644 140_dbs/src/gplx/dbs/Db_conn_info_tst.java create mode 100644 140_dbs/src/gplx/dbs/Db_conn_pool.java create mode 100644 140_dbs/src/gplx/dbs/Db_conn_utl.java create mode 100644 140_dbs/src/gplx/dbs/Db_crt_.java create mode 100644 140_dbs/src/gplx/dbs/Db_crt_tst.java create mode 100644 140_dbs/src/gplx/dbs/Db_idx_itm.java create mode 100644 140_dbs/src/gplx/dbs/Db_mock_cell.java create mode 100644 140_dbs/src/gplx/dbs/Db_mock_row.java create mode 100644 140_dbs/src/gplx/dbs/Db_null.java create mode 100644 140_dbs/src/gplx/dbs/Db_qry.java create mode 100644 140_dbs/src/gplx/dbs/Db_qry_.java create mode 100644 140_dbs/src/gplx/dbs/Db_qry_fxt.java create mode 100644 140_dbs/src/gplx/dbs/Db_rdr.java create mode 100644 140_dbs/src/gplx/dbs/Db_rdr_.java create mode 100644 140_dbs/src/gplx/dbs/Db_rdr__basic.java create mode 100644 140_dbs/src/gplx/dbs/Db_sql_.java create mode 100644 140_dbs/src/gplx/dbs/Db_sql_select.java create mode 100644 140_dbs/src/gplx/dbs/Db_stmt.java create mode 100644 140_dbs/src/gplx/dbs/Db_stmt_.java create mode 100644 140_dbs/src/gplx/dbs/Db_stmt_bldr.java create mode 100644 140_dbs/src/gplx/dbs/Db_tbl.java create mode 100644 140_dbs/src/gplx/dbs/Db_tbl_.java create mode 100644 140_dbs/src/gplx/dbs/Db_tbl_owner.java create mode 100644 140_dbs/src/gplx/dbs/Dbmeta_fld_itm.java create mode 100644 140_dbs/src/gplx/dbs/Dbmeta_fld_list.java create mode 100644 140_dbs/src/gplx/dbs/Dbmeta_fld_tid.java create mode 100644 140_dbs/src/gplx/dbs/Dbmeta_idx_itm.java create mode 100644 140_dbs/src/gplx/dbs/Dbmeta_tbl_itm.java create mode 100644 140_dbs/tst/gplx/dbs/Db_conn_fxt.java create mode 100644 140_dbs/tst/gplx/dbs/GfoNdeTstr.java create mode 100644 140_dbs/xtn/gplx/dbs/Bug_Utf8.java create mode 100644 140_dbs/xtn/gplx/dbs/SqliteDbMain.java create mode 100644 150_gfui/lib/swt.jar create mode 100644 150_gfui/src/gplx/gfui/DirInt.java create mode 100644 150_gfui/src/gplx/gfui/GfuiAlign.java create mode 100644 150_gfui/src/gplx/gfui/GfuiAlign_.java create mode 100644 150_gfui/src/gplx/gfui/GfuiAxisType.java create mode 100644 150_gfui/src/gplx/gfui/GfuiBorderEdge.java create mode 100644 150_gfui/src/gplx/gfui/PointAdp.java create mode 100644 150_gfui/src/gplx/gfui/PointAdp_.java create mode 100644 150_gfui/src/gplx/gfui/RectAdp.java create mode 100644 150_gfui/src/gplx/gfui/RectAdpF.java create mode 100644 150_gfui/src/gplx/gfui/RectAdp_.java create mode 100644 150_gfui/src/gplx/gfui/SizeAdp.java create mode 100644 150_gfui/src/gplx/gfui/SizeAdpF.java create mode 100644 150_gfui/src/gplx/gfui/SizeAdpF_.java create mode 100644 150_gfui/src/gplx/gfui/SizeAdp_.java create mode 100644 400_xowa/src/gplx/dbs/Db_diff_bldr.java create mode 100644 400_xowa/src/gplx/fsdb/Fsdb_db_file.java create mode 100644 400_xowa/src/gplx/fsdb/Fsdb_db_mgr.java create mode 100644 400_xowa/src/gplx/fsdb/Fsdb_db_mgr_.java create mode 100644 400_xowa/src/gplx/fsdb/Fsdb_db_mgr__v1.java create mode 100644 400_xowa/src/gplx/fsdb/Fsdb_db_mgr__v2.java create mode 100644 400_xowa/src/gplx/fsdb/Fsdb_db_mgr__v2_bldr.java create mode 100644 400_xowa/src/gplx/gfui/Gfui_bnd_parser.java create mode 100644 400_xowa/src/gplx/gfui/Gfui_bnd_parser_tst.java create mode 100644 400_xowa/src/gplx/xowa/Xoa_app.java create mode 100644 400_xowa/src/gplx/xowa/Xoa_app_.java create mode 100644 400_xowa/src/gplx/xowa/Xoa_app_fxt.java create mode 100644 400_xowa/src/gplx/xowa/Xoa_page.java create mode 100644 400_xowa/src/gplx/xowa/Xoa_page_.java create mode 100644 400_xowa/src/gplx/xowa/Xoa_test_.java create mode 100644 400_xowa/src/gplx/xowa/Xoa_ttl.java create mode 100644 400_xowa/src/gplx/xowa/Xoa_url.java create mode 100644 400_xowa/src/gplx/xowa/Xoa_url_.java create mode 100644 400_xowa/src/gplx/xowa/Xoae_app.java create mode 100644 400_xowa/src/gplx/xowa/Xoae_page.java create mode 100644 400_xowa/src/gplx/xowa/Xoae_page__tst.java create mode 100644 400_xowa/src/gplx/xowa/Xop_fxt.java create mode 100644 400_xowa/src/gplx/xowa/Xow_wiki.java create mode 100644 400_xowa/src/gplx/xowa/Xow_wiki_.java create mode 100644 400_xowa/src/gplx/xowa/Xowa_main.java create mode 100644 400_xowa/src/gplx/xowa/Xowe_wiki.java create mode 100644 400_xowa/src/gplx/xowa/Xowe_wiki_.java create mode 100644 401_xowa_updater/src/gplx/xowa/Xoa_manifest_item.java create mode 100644 401_xowa_updater/src/gplx/xowa/Xoa_manifest_list.java create mode 100644 401_xowa_updater/src/gplx/xowa/Xoa_manifest_view.java create mode 100644 401_xowa_updater/src/gplx/xowa/Xoa_manifest_wkr.java create mode 100644 401_xowa_updater/src/gplx/xowa/Xowa_app_updater.java diff --git a/100_core/src/gplx/Array_.java b/100_core/src/gplx/Array_.java new file mode 100644 index 000000000..3efe7d815 --- /dev/null +++ b/100_core/src/gplx/Array_.java @@ -0,0 +1,120 @@ +/* +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; +import java.lang.reflect.Array; +import gplx.core.strings.*; import gplx.core.lists.*; +public class Array_ { + public static Object cast(Object o) {return (Object)o;} + public static void Sort(Object[] obj) {List_adp_sorter.new_().Sort(obj, obj.length);} + public static void Sort(Object[] obj, gplx.core.lists.ComparerAble comparer) {List_adp_sorter.new_().Sort(obj, obj.length, true, comparer);} + public static Object[] Insert(Object[] cur, Object[] add, int add_pos) { + int cur_len = cur.length, add_len = add.length; + Object[] rv = (Object[])Array_.Create(Array_.Component_type(cur), cur_len + add_len); + for (int i = 0; i < add_pos; i++) // copy old up to add_pos + rv[i] = cur[i]; + for (int i = 0; i < add_len; i++) // insert add + rv[i + add_pos] = add[i]; + for (int i = add_pos; i < cur_len; i++) // copy old after add_pos + rv[i + add_len] = cur[i]; + return rv; + } + public static Object[] Replace_insert(Object[] cur, Object[] add, int curReplacePos, int addInsertPos) { + int curLen = cur.length, addLen = add.length; int newLen = addLen - addInsertPos; + Object[] rv = (Object[])Array_.Create(Array_.Component_type(cur), curLen + newLen); + for (int i = 0; i < curReplacePos; i++) // copy old up to curInsertPos; EX: curReplacePos=5, addInsertPos=2; copy up to element 3; 4, 5 are dropped + rv[i] = cur[i]; + for (int i = 0; i < addLen; i++) // insert add + rv[i + curReplacePos] = add[i]; + for (int i = curReplacePos + addInsertPos; i < curLen; i++) // copy old after curReplacePos + rv[i + newLen] = cur[i]; + return rv; + } + public static Object Resize_add_one(Object src, int src_len, Object new_obj) { + Object rv = Resize(src, src_len + 1); + Set_at(rv, src_len, new_obj); + return rv; + } + public static Object Resize(Object src, int trg_len) { + Object trg = Create(Component_type(src), trg_len); + int src_len = Array.getLength(src); + int copy_len = src_len > trg_len ? trg_len : src_len; // trg_len can either expand or shrink + Copy_to(src, 0, trg, 0, copy_len); + return trg; + } + public static List_adp To_list(Object ary) { + int aryLen = Array_.Len(ary); + List_adp rv = List_adp_.New(); + for (int i = 0; i < aryLen; i++) + rv.Add(Array_.Get_at(ary, i)); + return rv; + } + public static String To_str_nested_obj(Object o) { + Bry_bfr bfr = Bry_bfr_.New(); + To_str_nested_ary(bfr, (Object)o, 0); + return bfr.To_str_and_clear(); + } + private static void To_str_nested_ary(Bry_bfr bfr, Object ary, int indent) { + int len = Len(ary); + for (int i = 0; i < len; i++) { + Object itm = Get_at(ary, i); + if (itm != null && Type_.Is_array(itm.getClass())) + To_str_nested_ary(bfr, (Object)itm, indent + 1); + else { + if (indent > 0) bfr.Add_byte_repeat(Byte_ascii.Space, indent * 2); + bfr.Add_str_u8(Object_.Xto_str_strict_or_null_mark(itm)).Add_byte_nl(); + } + } + } + public static String To_str_obj(Object o) {return To_str((Object)o);} + public static String To_str(Object ary) { + String_bldr sb = String_bldr_.new_(); + int ary_len = Len(ary); + for (int i = 0; i < ary_len; i++) + sb.Add_obj(Get_at(ary, i)).Add_char_nl(); + return sb.To_str(); + } + public static int Len(Object ary) {return Array.getLength(ary);} + public static final int Len_obj(Object[] ary) {return ary == null ? 0 : ary.length;} + public static Object Get_at(Object ary, int i) {return Array.get(ary, i);} + public static void Set_at(Object ary, int i, Object o) {Array.set(ary, i, o);} + public static Object Create(Class t, int count) {return Array.newInstance(t, count);} + public static Object Expand(Object src, Object trg, int src_len) { + try {System.arraycopy(src, 0, trg, 0, src_len);} + catch (Exception e) {throw Err_.new_exc(e, "core", "Array_.Expand failed", "src_len", src_len);} + return trg; + } + public static void Copy(Object src, Object trg) {System.arraycopy(src, 0, trg, 0, Len(src));} + public static void Copy_to(Object src, Object trg, int trgPos) {System.arraycopy(src, 0, trg, trgPos, Len(src));} + public static void Copy_to(Object src, int srcBgn, Object trg, int trgBgn, int srcLen) {System.arraycopy(src, srcBgn, trg, trgBgn, srcLen);} + private static Class Component_type(Object ary) { + if (ary == null) throw Err_.new_null(); + return ary.getClass().getComponentType(); + } + public static Object Resize_add(Object src, Object add) { + int srcLen = Len(src); + int trgLen = srcLen + Len(add); + Object trg = Create(Component_type(src), trgLen); + Copy(src, trg); + for (int i = srcLen; i < trgLen; i++) + Set_at(trg, i, Get_at(add, i - srcLen)); + return trg; + } + public static Object Clone(Object src) { + Object trg = Create(Component_type(src), Len(src)); + Copy(src, trg); + return trg; + } + } diff --git a/100_core/src/gplx/Array__tst.java b/100_core/src/gplx/Array__tst.java new file mode 100644 index 000000000..f00e89500 --- /dev/null +++ b/100_core/src/gplx/Array__tst.java @@ -0,0 +1,39 @@ +/* +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; +import org.junit.*; +public class Array__tst { + @Test public void Resize_add() { + tst_Resize_add(ary_(), ary_(1), ary_(1)); // 0 + 1 = 1 + tst_Resize_add(ary_(0), ary_(), ary_(0)); // 1 + 0 = 1 + tst_Resize_add(ary_(0), ary_(1), ary_(0, 1)); // 1 + 1 = 2 + } void tst_Resize_add(int[] source, int[] added, int[] expd) {Tfds.Eq_ary(expd, (int[])Array_.Resize_add(source, added));} + @Test public void Resize() { + tst_Resize(ary_(0), 0, ary_()); // 1 -> 0 + tst_Resize(ary_(0, 1), 1, ary_(0)); // 2 -> 1 + } void tst_Resize(int[] source, int length, int[] expd) {Tfds.Eq_ary(expd, (int[])Array_.Resize(source, length));} + @Test public void Insert() { + tst_Insert(ary_obj(0, 1, 4, 5), ary_obj(2, 3), 2, ary_obj(0, 1, 2, 3, 4, 5)); + } void tst_Insert(Object[] cur, Object[] add, int addPos, Object[] expd) {Tfds.Eq_ary(expd, Array_.Insert(cur, add, addPos));} + @Test public void ReplaceInsert() { + tst_ReplaceInsert(ary_obj(0, 1, 4, 5) , ary_obj(1, 2, 3), 1, 1, ary_obj(0, 1, 2, 3, 4, 5)); + tst_ReplaceInsert(ary_obj(0, 1, 2, 4, 5) , ary_obj(1, 2, 3), 1, 2, ary_obj(0, 1, 2, 3, 4, 5)); + tst_ReplaceInsert(ary_obj(0, 1, 2, 3, 4, 5) , ary_obj(1, 2, 3), 1, 3, ary_obj(0, 1, 2, 3, 4, 5)); + tst_ReplaceInsert(ary_obj(0, 1, 9, 4, 5) , ary_obj(2, 3) , 2, 1, ary_obj(0, 1, 2, 3, 4, 5)); + } void tst_ReplaceInsert(Object[] cur, Object[] add, int curReplacePos, int addInsertPos, Object[] expd) {Tfds.Eq_ary(expd, Array_.Replace_insert(cur, add, curReplacePos, addInsertPos));} + Object[] ary_obj(Object... ary) {return ary;} + int[] ary_(int... ary) {return ary;} +} diff --git a/100_core/src/gplx/Bool_.java b/100_core/src/gplx/Bool_.java new file mode 100644 index 000000000..b1cdde9da --- /dev/null +++ b/100_core/src/gplx/Bool_.java @@ -0,0 +1,65 @@ +/* +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; +public class Bool_ {//RF:2017-10-08 + public static final String Cls_val_name = "bool"; + public static final Class Cls_ref_type = Boolean.class; + + public static final boolean N = false , Y = true; + public static final byte N_byte = 0 , Y_byte = 1 , __byte = 127; + public static final int N_int = 0 , Y_int = 1 , __int = -1; + public static final byte[] N_bry = new byte[] {Byte_ascii.Ltr_n}, Y_bry = new byte[] {Byte_ascii.Ltr_y}; + + public static final String True_str = "true", False_str = "false"; + public static final byte[] True_bry = Bry_.new_a7(True_str), False_bry = Bry_.new_a7(False_str); + + public static boolean By_int(int v) {return v == Y_int;} + public static int To_int(boolean v) {return v ? Y_int : N_int;} + public static byte To_byte(boolean v) {return v ? Y_byte : N_byte;} + public static String To_str_lower(boolean v) {return v ? True_str : False_str;} + + public static boolean Cast(Object obj) {try {return (Boolean)obj;} catch (Exception e) {throw Err_.new_type_mismatch_w_exc(e, boolean.class, obj);}} + public static boolean Cast_or(Object obj, boolean or) {try {return (Boolean)obj;} catch (Exception e) {Err_.Noop(e); return or;}} + + public static boolean Parse(String raw) { + if ( String_.Eq(raw, True_str) + || String_.Eq(raw, "True") // needed for Store_Wtr(){boolVal.toString();} + ) + return true; + else if ( String_.Eq(raw, False_str) + || String_.Eq(raw, "False") + ) + return false; + throw Err_.new_parse_type(boolean.class, raw); + } + public static boolean Parse_or(String raw, boolean or) { + if ( String_.Eq(raw, True_str) + || String_.Eq(raw, "True") // needed for Store_Wtr(){boolVal.toString();} + ) + return true; + else if ( String_.Eq(raw, False_str) + || String_.Eq(raw, "False") + ) + return false; + return or; + } + + public static int Compare(boolean lhs, boolean rhs) { + if ( lhs == rhs) return CompareAble_.Same; + else if (!lhs && rhs) return CompareAble_.Less; + else /*lhs && !rhs*/ return CompareAble_.More; + } +} diff --git a/100_core/src/gplx/Bool__tst.java b/100_core/src/gplx/Bool__tst.java new file mode 100644 index 000000000..f95218094 --- /dev/null +++ b/100_core/src/gplx/Bool__tst.java @@ -0,0 +1,29 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +import org.junit.*; +public class Bool__tst { + private final Bool__fxt fxt = new Bool__fxt(); + @Test public void Compare() { + fxt.Test__compare(Bool_.Y, Bool_.Y, CompareAble_.Same); + fxt.Test__compare(Bool_.N, Bool_.N, CompareAble_.Same); + fxt.Test__compare(Bool_.N, Bool_.Y, CompareAble_.Less); + fxt.Test__compare(Bool_.Y, Bool_.N, CompareAble_.More); + } +} +class Bool__fxt { + public void Test__compare(boolean lhs, boolean rhs, int expd) {Tfds.Eq(expd, Bool_.Compare(lhs, rhs));} +} diff --git a/100_core/src/gplx/Bry_.java b/100_core/src/gplx/Bry_.java new file mode 100644 index 000000000..4c95a28ab --- /dev/null +++ b/100_core/src/gplx/Bry_.java @@ -0,0 +1,1181 @@ +/* +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; +import java.lang.*; +import gplx.core.brys.*; import gplx.core.primitives.*; import gplx.core.ios.*; +import gplx.langs.htmls.entitys.*; +public class Bry_ { + public static final String Cls_val_name = "byte[]"; + public static final byte[] Empty = new byte[0]; + public static final byte[][] Ary_empty = new byte[0][]; + public static final Class Cls_ref_type = byte[].class; + public static byte[] cast(Object val) {return (byte[])val;} + public static byte[] New_by_byte(byte b) {return new byte[] {b};} + public static byte[] New_by_ints(int... ary) { + int len = ary.length; + byte[] rv = new byte[len]; + for (int i = 0; i < len; i++) + rv[i] = (byte)ary[i]; + return rv; + } + public static byte[] New_by_objs(Bry_bfr bfr, Object... ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) { + Object itm = ary[i]; + Class type = Type_.Type_by_obj(itm); + if (Type_.Eq(type, int.class)) bfr.Add_byte((byte)Int_.Cast(itm)); + else if (Type_.Eq(type, String.class)) bfr.Add_str_u8((String)itm); + else if (Type_.Eq(type, byte[].class)) bfr.Add((byte[])itm); + else throw Err_.new_unhandled(Type_.Canonical_name(type)); + } + return bfr.To_bry_and_clear(); + } + public static byte[] Coalesce_to_empty(byte[] v) {return v == null ? Bry_.Empty : v;} + public static byte[] Coalesce(byte[] v, byte[] or) {return v == null ? or : v;} + public static byte[] new_a7(String str) { + if (str == null) return null; + int str_len = str.length(); + if (str_len == 0) return Bry_.Empty; + byte[] rv = new byte[str_len]; + for (int i = 0; i < str_len; ++i) { + char c = str.charAt(i); + if (c > 128) c = '?'; + rv[i] = (byte)c; + } + return rv; + } + public static byte[] new_u8_safe(String str) {return str == null ? null : new_u8(str);} + public static byte[] new_u8(String str) { + try { + int str_len = str.length(); + if (str_len == 0) return Bry_.Empty; + int bry_len = new_u8__by_len(str, str_len); + byte[] rv = new byte[bry_len]; + new_u8__write(str, str_len, rv, 0); + return rv; + } + catch (Exception e) {throw Err_.new_exc(e, "core", "invalid UTF-8 sequence", "s", str);} + } + public static int new_u8__by_len(String s, int s_len) { + int rv = 0; + for (int i = 0; i < s_len; ++i) { + char c = s.charAt(i); + int c_len = 0; + if ( c < 128) c_len = 1; // 1 << 7 + else if ( c < 2048) c_len = 2; // 1 << 11 + else if ( (c > 55295) // 0xD800 + && (c < 56320)) c_len = 4; // 0xDFFF + else c_len = 3; // 1 << 16 + if (c_len == 4) ++i; // surrogate is 2 wide, not 1 + rv += c_len; + } + return rv; + } + public static byte[] New_u8_nl_apos(String... lines) { + Bry_bfr bfr = Bry_bfr_.Get(); + try { + New_u8_nl_apos(bfr, lines); + return bfr.To_bry_and_clear(); + } + finally {bfr.Mkr_rls();} + } + public static void New_u8_nl_apos(Bry_bfr bfr, String... lines) { + int lines_len = lines.length; + for (int i = 0; i < lines_len; ++i) { + if (i != 0) bfr.Add_byte_nl(); + byte[] line = Bry_.new_u8(lines[i]); + boolean dirty = false; + int prv = 0; + int line_len = line.length; + for (int j = 0; j < line_len; ++j) { + byte b = line[j]; + if (b == Byte_ascii.Apos) { + bfr.Add_mid(line, prv, j); + bfr.Add_byte(Byte_ascii.Quote); + dirty = true; + prv = j + 1; + } + } + if (dirty) + bfr.Add_mid(line, prv, line_len); + else + bfr.Add(line); + } + } + public static void new_u8__write(String str, int str_len, byte[] bry, int bry_pos) { + for (int i = 0; i < str_len; ++i) { + char c = str.charAt(i); + if ( c < 128) { + bry[bry_pos++] = (byte)c; + } + else if ( c < 2048) { + bry[bry_pos++] = (byte)(0xC0 | (c >> 6)); + bry[bry_pos++] = (byte)(0x80 | (c & 0x3F)); + } + else if ( (c > 55295) // 0xD800 + && (c < 56320)) { // 0xDFFF + if (i >= str_len) throw Err_.new_wo_type("incomplete surrogate pair at end of String", "char", c); + char nxt_char = str.charAt(i + 1); + int v = 0x10000 + (c - 0xD800) * 0x400 + (nxt_char - 0xDC00); + bry[bry_pos++] = (byte)(0xF0 | (v >> 18)); + bry[bry_pos++] = (byte)(0x80 | (v >> 12) & 0x3F); + bry[bry_pos++] = (byte)(0x80 | (v >> 6) & 0x3F); + bry[bry_pos++] = (byte)(0x80 | (v & 0x3F)); + ++i; + } + else { + bry[bry_pos++] = (byte)(0xE0 | (c >> 12)); + bry[bry_pos++] = (byte)(0x80 | (c >> 6) & 0x3F); + bry[bry_pos++] = (byte)(0x80 | (c & 0x3F)); + } + } + } + public static byte[] Copy(byte[] src) { + int src_len = src.length; + byte[] trg = new byte[src_len]; + for (int i = 0; i < src_len; ++i) + trg[i] = src[i]; + return trg; + } + public static byte[] Resize(byte[] src, int trg_len) {return Resize(src, 0, trg_len);} + public static byte[] Resize(byte[] src, int src_bgn, int trg_len) { + byte[] trg = new byte[trg_len]; + int src_len = src.length; if (src_len > trg_len) src_len = trg_len; // trg_len can be less than src_len + Copy_by_len(src, src_bgn, src_len, trg, 0); + return trg; + } + public static byte[] Repeat_space(int len) {return Repeat(Byte_ascii.Space, len);} + public static byte[] Repeat(byte b, int len) { + byte[] rv = new byte[len]; + for (int i = 0; i < len; i++) + rv[i] = b; + return rv; + } + public static byte[] Repeat_bry(byte[] bry, int len) { + int bry_len = bry.length; + int rv_len = len * bry_len; + byte[] rv = new byte[rv_len]; + for (int i = 0; i < len; i++) { + for (int j = 0; j < bry_len; j++) { + rv[(i * bry_len) + j] = bry[j]; + } + } + return rv; + } + public static byte[] Add(byte[] src, byte b) { + int src_len = src.length; + byte[] rv = new byte[src_len + 1]; + Copy_by_pos(src, 0, src_len, rv, 0); + rv[src_len] = b; + return rv; + } + public static byte[] Add(byte b, byte[] src) { + int src_len = src.length; + byte[] rv = new byte[src_len + 1]; + Copy_by_pos(src, 0, src_len, rv, 1); + rv[0] = b; + return rv; + } + public static byte[] Add(byte[]... all) { + int all_len = all.length, rv_len = 0; + for (int i = 0; i < all_len; ++i) { + byte[] cur = all[i]; if (cur == null) continue; + rv_len += cur.length; + } + byte[] rv = new byte[rv_len]; + int rv_idx = 0; + for (int i = 0; i < all_len; ++i) { + byte[] cur = all[i]; if (cur == null) continue; + int cur_len = cur.length; + for (int j = 0; j < cur_len; ++j) + rv[rv_idx++] = cur[j]; + } + return rv; + } + public static byte[] Add_w_dlm(byte[] dlm, byte[]... ary) { + int ary_len = ary.length; + if (ary_len == 0) return Bry_.Empty; + int dlm_len = dlm.length; + int rv_len = dlm_len * (ary_len - 1); // rv will have at least as many dlms as itms - 1 + for (int i = 0; i < ary_len; i++) { + byte[] itm = ary[i]; + if (itm != null) rv_len += itm.length; + } + int rv_pos = 0; + byte[] rv = new byte[rv_len]; + for (int i = 0; i < ary_len; i++) { + byte[] itm = ary[i]; + if (i != 0) { + for (int j = 0; j < dlm_len; j++) { + rv[rv_pos++] = dlm[j]; + } + } + if (itm == null) continue; + int itm_len = itm.length; + for (int j = 0; j < itm_len; j++) { + rv[rv_pos++] = itm[j]; + } + } + return rv; + } + public static byte[] Add_w_dlm(byte dlm, byte[]... ary) { + int ary_len = ary.length; + if (ary_len == 0) return Bry_.Empty; + int rv_len = ary_len - 1; // rv will have at least as many dlms as itms - 1 + for (int i = 0; i < ary_len; i++) { + byte[] itm = ary[i]; + if (itm != null) rv_len += itm.length; + } + int rv_pos = 0; + byte[] rv = new byte[rv_len]; + for (int i = 0; i < ary_len; i++) { + byte[] itm = ary[i]; + if (i != 0) rv[rv_pos++] = dlm; + if (itm == null) continue; + int itm_len = itm.length; + for (int j = 0; j < itm_len; j++) { + rv[rv_pos++] = itm[j]; + } + } + return rv; + } + public static int Len(byte[] v) {return v == null ? 0 : v.length;} + public static boolean Len_gt_0(byte[] v) {return v != null && v.length > 0;} + public static boolean Len_eq_0(byte[] v) {return v == null || v.length == 0;} + public static byte Get_at_end(byte[] bry) {return bry[bry.length - 1];} // don't bother checking for errors; depend on error trace + public static boolean Has_at(byte[] src, int src_len, int pos, byte b) {return (pos < src_len) && (src[pos] == b);} + public static boolean Has(byte[] src, byte[] lkp) {return Bry_find_.Find_fwd(src, lkp) != Bry_find_.Not_found;} + public static boolean Has(byte[] src, byte lkp) { + if (src == null) return false; + int len = src.length; + for (int i = 0; i < len; i++) + if (src[i] == lkp) return true; + return false; + } + public static boolean Has_at_bgn(byte[] src, byte lkp) {return Has_at_bgn(src, lkp, 0);} + public static boolean Has_at_bgn(byte[] src, byte lkp, int src_bgn) {return src_bgn < src.length ? src[src_bgn] == lkp : false;} + public static boolean Has_at_bgn(byte[] src, byte[] lkp) {return Has_at_bgn(src, lkp, 0, src.length);} + public static boolean Has_at_bgn(byte[] src, byte[] lkp, int src_bgn, int src_end) { + int lkp_len = lkp.length; + if (lkp_len + src_bgn > src_end) return false; // lkp is longer than src + for (int i = 0; i < lkp_len; i++) { + if (lkp[i] != src[i + src_bgn]) return false; + } + return true; + } + public static boolean Has_at_end(byte[] src, byte lkp) { + if (src == null) return false; + int src_len = src.length; + if (src_len == 0) return false; + return src[src_len - 1] == lkp; + } + public static boolean Has_at_end(byte[] src, byte[] lkp) {int src_len = src.length; return Has_at_end(src, lkp, src_len - lkp.length, src_len);} + public static boolean Has_at_end(byte[] src, byte[] lkp, int src_bgn, int src_end) { + int lkp_len = lkp.length; + if (src_bgn < 0) return false; + int pos = src_end - lkp_len; if (pos < src_bgn) return false; // lkp is longer than src + for (int i = 0; i < lkp_len; i++) { + if (lkp[i] != src[i + pos]) return false; + } + return true; + } + public static void Set(byte[] src, int bgn, int end, byte[] repl) { + int repl_len = repl.length; + for (int i = 0; i < repl_len; i++) + src[i + bgn] = repl[i]; + } + public static void Copy_by_pos(byte[] src, int src_bgn, int src_end, byte[] trg, int trg_bgn) { + int trg_adj = trg_bgn - src_bgn; + for (int i = src_bgn; i < src_end; i++) + trg[i + trg_adj] = src[i]; + } + public static void Copy_by_pos_reversed(byte[] src, int src_bgn, int src_end, byte[] trg, int trg_bgn) { + int len = src_end - src_bgn; + for (int i = 0; i < len; i++) + trg[trg_bgn + i] = src[src_end - i - 1]; + } + private static void Copy_by_len(byte[] src, int src_bgn, int src_len, byte[] trg, int trg_bgn) { + for (int i = 0; i < src_len; i++) + trg[i + trg_bgn] = src[i + src_bgn]; + } + public static byte[] Replace_one(byte[] src, byte[] find, byte[] repl) { + int src_len = src.length; + int findPos = Bry_find_.Find(src, find, 0, src_len, true); if (findPos == Bry_find_.Not_found) return src; + int findLen = find.length, replLen = repl.length; + int rvLen = src_len + replLen - findLen; + byte[] rv = new byte[rvLen]; + Copy_by_len(src , 0 , findPos , rv, 0 ); + Copy_by_len(repl, 0 , replLen , rv, findPos ); + Copy_by_len(src , findPos + findLen , src_len - findPos - findLen , rv, findPos + replLen); + return rv; + } + public static void Replace_all_direct(byte[] src, byte find, byte repl) {Replace_all_direct(src, find, repl, 0, src.length);} + public static void Replace_all_direct(byte[] src, byte find, byte repl, int bgn, int end) { + for (int i = bgn; i < end; i++) { + byte b = src[i]; + if (b == find) src[i] = repl; + } + } + public static byte[] Limit(byte[] src, int len) { + if (src == null) return null; + int src_len = src.length; + return len < src_len ? Bry_.Mid(src, 0, len) : src; + } + public static byte[] Mid_by_nearby(byte[] src, int pos, int around) { + int bgn = pos - around; if (bgn < 0) bgn = 0; + int src_len = src.length; + int end = pos + around; if (end > src_len) end = src_len; + return Mid(src, bgn, end); + } + public static byte[] Mid_by_len(byte[] src, int bgn, int len) {return Mid(src, bgn, bgn + len);} + public static byte[] Mid_by_len_safe(byte[] src, int bgn, int len) { + int src_len = src.length; + if (bgn < 0) bgn = 0; + if (len + bgn > src_len) len = (src_len - bgn); + return Mid(src, bgn, bgn + len); + } + public static String MidByLenToStr(byte[] src, int bgn, int len) { + int end = bgn + len; end = Int_.BoundEnd(end, src.length); + byte[] ary = Bry_.Mid(src, bgn, end); + return String_.new_u8(ary); + } + public static byte[] Mid_safe(byte[] src, int bgn, int end) { + if (src == null) return null; + int src_len = src.length; + if (bgn < 0) bgn = 0; + if (end >= src_len) end = src_len; + if (bgn > end) bgn = end; + else if (end < bgn) end = bgn; + return Mid(src, bgn, end); + } + public static byte[] Mid(byte[] src, int bgn) {return Mid(src, bgn, src.length);} + public static byte[] Mid_or(byte[] src, int bgn, int end, byte[] or) { + int src_len = src.length; + if ( src == null + || (bgn < 0 || bgn > src_len) + || (end < 0 || end > src_len) + || (end < bgn) + ) + return or; + return bgn == src_len ? Bry_.Empty : Mid(src, bgn, src_len); + } + public static byte[] Mid(byte[] src, int bgn, int end) { + try { + int len = end - bgn; if (len == 0) return Bry_.Empty; + byte[] rv = new byte[len]; + for (int i = bgn; i < end; i++) + rv[i - bgn] = src[i]; + return rv; + } catch (Exception e) {Err_.Noop(e); throw Err_.new_("Bry_", "mid failed", "bgn", bgn, "end", end);} + } + public static byte[] Mid_w_trim(byte[] src, int bgn, int end) { + int len = end - bgn; if (len == 0) return Bry_.Empty; + int actl_bgn = bgn, actl_end = end; + // trim at bgn + boolean chars_seen = false; + for (int i = bgn; i < end; ++i) { + switch (src[i]) { + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: + break; + default: + chars_seen = true; + actl_bgn = i; + i = end; + break; + } + } + if (!chars_seen) return Bry_.Empty; // all ws + // trim at end + for (int i = end - 1; i >= actl_bgn; --i) { + switch (src[i]) { + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: + break; + default: + actl_end = i + 1; + i = -1; + break; + } + } + // extract mid + len = actl_end - actl_bgn; if (len == 0) return Bry_.Empty; + byte[] rv = new byte[len]; + for (int i = actl_bgn; i < actl_end; ++i) + rv[i - actl_bgn] = src[i]; + return rv; + } + public static byte[] mask_(int len, byte... itms) { + byte[] rv = new byte[len]; + int itms_len = itms.length; + for (int i = 0; i < itms_len; i++) { + byte itm = itms[i]; + rv[itm & 0xFF] = itm; // PATCH.JAVA:need to convert to unsigned byte + } + return rv; + } + public static final byte[] Trim_ary_ws = mask_(256, Byte_ascii.Tab, Byte_ascii.Nl, Byte_ascii.Cr, Byte_ascii.Space); + public static byte[] Trim(byte[] src) {return Trim(src, 0, src.length, true, true, Trim_ary_ws);} + public static byte[] Trim(byte[] src, int bgn, int end) {return Trim(src, bgn, end, true, true, Trim_ary_ws);} + public static byte[] Trim(byte[] src, int bgn, int end, boolean trim_bgn, boolean trim_end, byte[] trim_ary) { + int txt_bgn = bgn, txt_end = end; + boolean all_ws = true; + if (trim_bgn) { + for (int i = bgn; i < end; i++) { + byte b = src[i]; + if (trim_ary[b & 0xFF] == Byte_ascii.Null) { + txt_bgn = i; + i = end; + all_ws = false; + } + } + if (all_ws) return Bry_.Empty; + } + if (trim_end) { + for (int i = end - 1; i > -1; i--) { + byte b = src[i]; + if (trim_ary[b & 0xFF] == Byte_ascii.Null) { + txt_end = i + 1; + i = -1; + all_ws = false; + } + } + if (all_ws) return Bry_.Empty; + } + + if ( bgn == 0 && end == src.length // Trim is called on entire bry, not subset + && bgn == txt_bgn && end == txt_end // Trim hasn't trimmed anything + ) { + return src; + } + else + return Bry_.Mid(src, txt_bgn, txt_end); + } + public static byte[] Trim_end(byte[] v, byte trim, int end) { + boolean trimmed = false; + int pos = end - 1; // NOTE: -1 b/c callers will always be passing pos + 1; EX: src, src_len + for (; pos > -1; pos--) { + if (v[pos] == trim) { + trimmed = true; + } + else + break; + } + return trimmed ? Bry_.Mid(v, 0, pos + 1) : v; + } + public static int Compare(byte[] lhs, byte[] rhs) { + if (lhs == null) return CompareAble_.More; + else if (rhs == null) return CompareAble_.Less; + else return Compare(lhs, 0, lhs.length, rhs, 0, rhs.length); + } + public static int Compare(byte[] lhs, int lhs_bgn, int lhs_end, byte[] rhs, int rhs_bgn, int rhs_end) { + int lhs_len = lhs_end - lhs_bgn, rhs_len = rhs_end - rhs_bgn; + int min = lhs_len < rhs_len ? lhs_len : rhs_len; + int rv = CompareAble_.Same; + for (int i = 0; i < min; i++) { + rv = (lhs[i + lhs_bgn] & 0xff) - (rhs[i + rhs_bgn] & 0xff); // PATCH.JAVA:need to convert to unsigned byte + if (rv != CompareAble_.Same) return rv > CompareAble_.Same ? CompareAble_.More : CompareAble_.Less; // NOTE: changed from if (rv != CompareAble_.Same) return rv; DATE:2013-04-25 + } + return Int_.Compare(lhs_len, rhs_len); // lhs and rhs share same beginning bytes; return len comparisons + } + public static boolean Eq(byte[] src, byte[] val) {return Eq(src, 0, src == null ? 0 : src.length, val);} + public static boolean Eq(byte[] src, int src_bgn, int src_end, byte[] val) { + if (src == null && val == null) return true; + else if (src == null || val == null) return false; + if (src_bgn < 0) return false; + int val_len = val.length; + if (val_len != src_end - src_bgn) return false; + int src_len = src.length; + for (int i = 0; i < val_len; i++) { + int src_pos = i + src_bgn; + if (src_pos == src_len) return false; + if (val[i] != src[src_pos]) return false; + } + return true; + } + public static boolean Eq_ci_a7(byte[] lhs, byte[] rhs, int rhs_bgn, int rhs_end) { + if (lhs == null && rhs == null) return true; + else if (lhs == null || rhs == null) return false; + int lhs_len = lhs.length; + int rhs_len = rhs_end - rhs_bgn; + if (lhs_len != rhs_len) return false; + for (int i = 0; i < lhs_len; i++) { + byte lhs_b = lhs[i]; if (lhs_b > 64 && lhs_b < 91) lhs_b += 32; // lowercase + byte rhs_b = rhs[i + rhs_bgn]; if (rhs_b > 64 && rhs_b < 91) rhs_b += 32; // lowercase + if (lhs_b != rhs_b) return false; + } + return true; + } + public static boolean Match(byte[] src, byte[] find) {return Match(src, 0, src.length, find, 0, find.length);} + public static boolean Match(byte[] src, int src_bgn, byte[] find) {return Match(src, src_bgn, src.length, find, 0, find.length);} + public static boolean Match(byte[] src, int src_bgn, int src_end, byte[] find) {return Match(src, src_bgn, src_end, find, 0, find.length);} + public static boolean Match(byte[] src, int src_bgn, int src_end, byte[] find, int find_bgn, int find_end) { + if (src_bgn == -1) return false; + int src_len = src.length; + if (src_end > src_len) src_end = src_len; // must limit src_end to src_len, else ArrayIndexOutOfBounds below; DATE:2015-01-31 + int find_len = find_end - find_bgn; + if (find_len != src_end - src_bgn) return false; + if (find_len == 0) return src_end - src_bgn == 0; // "" only matches "" + for (int i = 0; i < find_len; i++) { + int pos = src_bgn + i; + if (pos >= src_end) return false; // ran out of src; exit; EX: src=ab; find=abc + if (src[pos] != find[i + find_bgn]) return false; + } + return true; + } + public static boolean Match_w_swap(byte[] src, int src_bgn, int src_end, byte[] find, int find_bgn, int find_end, byte swap_src, byte swap_trg) {// same as above, but used by XOWA for ttl matches; + int src_len = src.length; + if (src_end > src_len) src_end = src_len; // must limit src_end to src_len, else ArrayIndexOutOfBounds below; DATE:2015-01-31 + int find_len = find_end - find_bgn; + if (find_len != src_end - src_bgn) return false; + if (find_len == 0) return src_end - src_bgn == 0; // "" only matches "" + for (int i = 0; i < find_len; i++) { + int pos = src_bgn + i; + if (pos >= src_end) return false; // ran out of src; exit; EX: src=ab; find=abc + byte src_byte = src[pos]; if (src_byte == swap_src) src_byte = swap_trg; + byte trg_byte = find[i + find_bgn]; if (trg_byte == swap_src) trg_byte = swap_trg; + if (src_byte != trg_byte) return false; + } + return true; + } + public static boolean Match_bwd_any(byte[] src, int src_end, int src_bgn, byte[] find) { // NOTE: utf8 doesn't matter (matching byte for byte) + int find_len = find.length; + for (int i = 0; i < find_len; i++) { + int src_pos = src_end - i; + int find_pos = find_len - i - 1; + if (src_pos < src_bgn) return false; // ran out of src; exit; EX: src=ab; find=abc + if (src[src_pos] != find[find_pos]) return false; + } + return true; + } + public static int To_int_by_a7(byte[] v) { + int v_len = v.length; + int mod = 8 * (v_len - 1); + int rv = 0; + for (int i = 0; i < v_len; i++) { + rv |= v[i] << mod; + mod -= 8; + } + return rv; +// return ((0xFF & v[0]) << 24) +// | ((0xFF & v[1]) << 16) +// | ((0xFF & v[2]) << 8) +// | (0xFF & v[3]); + } + public static byte[] To_a7_bry(int val, int pad_len) {return To_a7_bry(val, null, 0, pad_len);} + public static byte[] To_a7_bry(int val, byte[] ary, int aryPos, int pad_len) { + int neg = 0; + if (val < 0) { + val *= -1; + neg = 1; + } + int digits = val == 0 ? 0 : Math_.Log10(val); + digits += 1; // digits = log + 1; EX: Log(1-9) = 0, Log(10-99) = 1 + int ary_len = digits + neg, aryBgn = aryPos, pad = 0; + if (ary_len < pad_len) { // padding specified + pad = pad_len - ary_len; + ary_len = pad_len; + } + if (ary == null) ary = new byte[ary_len]; + long factor = 1; // factor needs to be long to handle 1 billion (for which factor would be 10 billion) + for (int i = 0; i < digits; i++) // calc maxFactor + factor *= 10; + if (neg == 1) ary[0] = Byte_NegSign; + + for (int i = 0; i < pad; i++) // fill ary with pad + ary[i + aryBgn] = Byte_ascii.To_a7_str(0); + aryBgn += pad; // advance aryBgn by pad + for (int i = neg; i < ary_len - pad; i++) { + int denominator = (int)(factor / 10); // cache denominator to check for divide by 0 + int digit = denominator == 0 ? 0 : (int)((val % factor) / denominator); + ary[aryBgn + i] = Byte_ascii.To_a7_str(digit); + factor /= 10; + } + return ary; + } + public static byte[] new_by_int(int v) { + byte b0 = (byte)(v >> 24); + byte b1 = (byte)(v >> 16); + byte b2 = (byte)(v >> 8); + byte b3 = (byte)(v); + if (b0 != 0) return new byte[] {b0, b1, b2, b3}; + else if (b1 != 0) return new byte[] {b1, b2, b3}; + else if (b2 != 0) return new byte[] {b2, b3}; + else return new byte[] {b3}; + } + public static boolean To_bool_or(byte[] raw, boolean or) { + return Bry_.Eq(raw, Bool_.True_bry) ? true : or; + } + public static boolean To_bool_by_int(byte[] ary) { + int rv = To_int_or(ary, 0, ary.length, Int_.Min_value, Bool_.Y, null); + switch (rv) { + case 0: return false; + case 1: return true; + default: throw Err_.new_wo_type("could not parse to boolean int", "val", String_.new_u8(ary)); + } + } + public static byte To_int_as_byte(byte[] ary, int bgn, int end, byte or) {return (byte)To_int_or(ary, bgn, end, or);} + public static int To_int(byte[] ary) {return To_int_or_fail(ary, 0, ary.length);} + public static int To_int_or_fail(byte[] ary, int bgn, int end) { + int rv = To_int_or(ary, bgn, end, Int_.Min_value, Bool_.Y, null); + if (rv == Int_.Min_value) throw Err_.new_wo_type("could not parse to int", "val", String_.new_u8(ary, bgn, end)); + return rv; + } + public static int To_int_or_neg1(byte[] ary) {return To_int_or(ary, 0 , ary.length, -1, Bool_.Y, null);} + public static int To_int_or(byte[] ary, int or) {return To_int_or(ary, 0 , ary.length, or, Bool_.Y, null);} + public static int To_int_or(byte[] ary, int bgn, int end, int or) {return To_int_or(ary, bgn , end , or, Bool_.Y, null);} + public static int To_int_or__strict(byte[] ary, int or) {return To_int_or(ary, 0 , ary.length, or, Bool_.N, null);} + private static int To_int_or(byte[] ary, int bgn, int end, int or, boolean sign_is_valid, byte[] ignore_ary) { + if ( ary == null + || end == bgn // null-len + ) return or; + int rv = 0, multiple = 1; + for (int i = end - 1; i >= bgn; i--) { // -1 b/c end will always be next char; EX: {{{1}}}; bgn = 3, end = 4 + byte b = ary[i]; + switch (b) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + rv += multiple * (b - Byte_ascii.Num_0); + multiple *= 10; + break; + case Byte_ascii.Dash: + return i == bgn && sign_is_valid ? rv * -1 : or; + case Byte_ascii.Plus: + return i == bgn && sign_is_valid ? rv : or; + default: + boolean invalid = true; + if (ignore_ary != null) { + int ignore_ary_len = ignore_ary.length; + for (int j = 0; j < ignore_ary_len; j++) { + if (b == ignore_ary[j]) { + invalid = false; + break; + } + } + } + if (invalid) return or; + break; + } + } + return rv; + } + public static int To_int_or__trim_ws(byte[] ary, int bgn, int end, int or) { // NOTE: same as To_int_or, except trims ws at bgn / end; DATE:2014-02-09 + if (end == bgn) return or; // null len + int rv = 0, multiple = 1; + boolean numbers_seen = false, ws_seen = false; + for (int i = end - 1; i >= bgn; i--) { // -1 b/c end will always be next char; EX: {{{1}}}; bgn = 3, end = 4 + byte b = ary[i]; + switch (b) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + rv += multiple * (b - Byte_ascii.Num_0); + multiple *= 10; + if (ws_seen) // "number ws number" pattern; invalid ws in middle; see tests + return or; + numbers_seen = true; + break; + case Byte_ascii.Dash: + return i == bgn ? rv * -1 : or; + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: + if (numbers_seen) + ws_seen = true; + break; + default: return or; + } + } + return rv; + } + public static int To_int_or__lax(byte[] ary, int bgn, int end, int or) { + if (end == bgn) return or; // null-len + int end_num = end; + for (int i = bgn; i < end; i++) { + byte b = ary[i]; + switch (b) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + break; + case Byte_ascii.Dash: + if (i != bgn) { + end_num = i; + i = end; + } + break; + default: + end_num = i; + i = end; + break; + } + } + return To_int_or(ary, bgn, end_num, or); + } + public static long To_long_or(byte[] ary, long or) {return To_long_or(ary, null, 0, ary.length, or);} + public static long To_long_or(byte[] ary, byte[] ignore_ary, int bgn, int end, long or) { + if ( ary == null + || end == bgn // null-len + ) return or; + long rv = 0, multiple = 1; + for (int i = end - 1; i >= bgn; i--) { // -1 b/c end will always be next char; EX: {{{1}}}; bgn = 3, end = 4 + byte b = ary[i]; + switch (b) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + rv += multiple * (b - Byte_ascii.Num_0); + multiple *= 10; + break; + case Byte_ascii.Dash: + return i == bgn ? rv * -1 : or; + case Byte_ascii.Plus: + return i == bgn ? rv : or; + default: + boolean invalid = true; + if (ignore_ary != null) { + int ignore_ary_len = ignore_ary.length; + for (int j = 0; j < ignore_ary_len; j++) { + if (b == ignore_ary[j]) { + invalid = false; + break; + } + } + } + if (invalid) return or; + break; + } + } + return rv; + } + public static double To_double(byte[] ary, int bgn, int end) {return Double_.parse(String_.new_u8(ary, bgn, end));} + public static double To_double_or(byte[] bry, double or) {return Double_.parse_or(String_.new_u8(bry, 0, bry.length), or);} + public static double To_double_or(byte[] ary, int bgn, int end, double or) {return Double_.parse_or(String_.new_u8(ary, bgn, end), or);} + public static Decimal_adp To_decimal(byte[] ary, int bgn, int end) {return Decimal_adp_.parse(String_.new_u8(ary, bgn, end));} + public static byte[][] Ary_add(byte[][] lhs, byte[][] rhs) { + int lhs_len = lhs.length, rhs_len = rhs.length; + if (lhs_len == 0) return rhs; + else if (rhs_len == 0) return lhs; + else { + byte[][] rv = new byte[lhs_len + rhs_len][]; + for (int i = 0; i < lhs_len; i++) + rv[i] = lhs[i]; + for (int i = 0; i < rhs_len; i++) + rv[i + lhs_len] = rhs[i]; + return rv; + } + } + public static byte[][] Ary(byte[]... ary) {return ary;} + public static byte[][] Ary(String... ary) { + int ary_len = ary.length; + byte[][] rv = new byte[ary_len][]; + for (int i = 0; i < ary_len; i++) { + String itm = ary[i]; + rv[i] = itm == null ? null : Bry_.new_u8(itm); + } + return rv; + } + public static byte[][] Ary_obj(Object... ary) { + if (ary == null) return Bry_.Ary_empty; + int ary_len = ary.length; + byte[][] rv = new byte[ary_len][]; + for (int i = 0; i < ary_len; i++) { + Object itm = ary[i]; + rv[i] = itm == null ? null : Bry_.new_u8(Object_.Xto_str_strict_or_empty(itm)); + } + return rv; + } + public static boolean Ary_eq(byte[][] lhs, byte[][] rhs) { + int lhs_len = lhs.length; + int rhs_len = rhs.length; + if (lhs_len != rhs_len) return false; + for (int i = 0; i < lhs_len; ++i) + if (!Bry_.Eq(lhs[i], rhs[i])) return false; + return true; + } + public static final byte Dlm_fld = (byte)'|', Dlm_row = (byte)'\n', Dlm_quote = (byte)'"', Dlm_null = 0, Ascii_zero = 48; + public static final String Fmt_csvDte = "yyyyMMdd HHmmss.fff"; + public static DateAdp ReadCsvDte(byte[] ary, Int_obj_ref posRef, byte lkp) {// ASSUME: fmt = yyyyMMdd HHmmss.fff + int y = 0, M = 0, d = 0, H = 0, m = 0, s = 0, f = 0; + int bgn = posRef.Val(); + y += (ary[bgn + 0] - Ascii_zero) * 1000; + y += (ary[bgn + 1] - Ascii_zero) * 100; + y += (ary[bgn + 2] - Ascii_zero) * 10; + y += (ary[bgn + 3] - Ascii_zero); + M += (ary[bgn + 4] - Ascii_zero) * 10; + M += (ary[bgn + 5] - Ascii_zero); + d += (ary[bgn + 6] - Ascii_zero) * 10; + d += (ary[bgn + 7] - Ascii_zero); + H += (ary[bgn + 9] - Ascii_zero) * 10; + H += (ary[bgn + 10] - Ascii_zero); + m += (ary[bgn + 11] - Ascii_zero) * 10; + m += (ary[bgn + 12] - Ascii_zero); + s += (ary[bgn + 13] - Ascii_zero) * 10; + s += (ary[bgn + 14] - Ascii_zero); + f += (ary[bgn + 16] - Ascii_zero) * 100; + f += (ary[bgn + 17] - Ascii_zero) * 10; + f += (ary[bgn + 18] - Ascii_zero); + if (ary[bgn + 19] != lkp) throw Err_.new_wo_type("csv date is invalid", "txt", String_.new_u8__by_len(ary, bgn, 20)); + posRef.Val_add(19 + 1); // +1=lkp.len + return DateAdp_.new_(y, M, d, H, m, s, f); + } + public static String ReadCsvStr(byte[] ary, Int_obj_ref posRef, byte lkp) {return String_.new_u8(ReadCsvBry(ary, posRef, lkp, true));} + public static byte[] ReadCsvBry(byte[] ary, Int_obj_ref posRef, byte lkp) {return ReadCsvBry(ary, posRef, lkp, true);} + public static byte[] ReadCsvBry(byte[] ary, Int_obj_ref posRef, byte lkp, boolean make) { + int bgn = posRef.Val(), aryLen = ary.length; + Bry_bfr bb = null; + if (aryLen > 0 && ary[0] == Dlm_quote) { + int pos = bgn + 1; // +1 to skip quote + if (make) bb = Bry_bfr_.New(); + while (true) { + if (pos == aryLen) throw Err_.new_wo_type("endOfAry reached, but no quote found", "txt", String_.new_u8__by_len(ary, bgn, pos)); + byte b = ary[pos]; + if (b == Dlm_quote) { + if (pos == aryLen - 1) throw Err_.new_wo_type("endOfAry reached, quote found but lkp not", "txt", String_.new_u8__by_len(ary, bgn, pos)); + byte next = ary[pos + 1]; + if (next == Dlm_quote) { // byte followed by quote + if (make) bb.Add_byte(b); + pos += 2; + } + else if (next == lkp) { + posRef.Val_(pos + 2); // 1=endQuote;1=lkp; + return make ? bb.To_bry() : Bry_.Empty; + } + else throw Err_.new_wo_type("quote found, but not doubled", "txt", String_.new_u8__by_len(ary, bgn, pos + 1)); + } + else { + if (make) bb.Add_byte(b); + pos++; + } + } + } + else { + for (int i = bgn; i < aryLen; i++) { + if (ary[i] == lkp) { + posRef.Val_(i + 1); // +1 = lkp.Len + return make ? Bry_.Mid(ary, bgn, i) : Bry_.Empty; + } + } + throw Err_.new_wo_type("lkp failed", "lkp", (char)lkp, "txt", String_.new_u8__by_len(ary, bgn, aryLen)); + } + } + public static int ReadCsvInt(byte[] ary, Int_obj_ref posRef, byte lkp) { + int bgn = posRef.Val(); + int pos = Bry_find_.Find_fwd(ary, lkp, bgn, ary.length); + if (pos == Bry_find_.Not_found) throw Err_.new_wo_type("lkp failed", "lkp", (char)lkp, "bgn", bgn); + int rv = Bry_.To_int_or(ary, posRef.Val(), pos, -1); + posRef.Val_(pos + 1); // +1 = lkp.Len + return rv; + } + public static double ReadCsvDouble(byte[] ary, Int_obj_ref posRef, byte lkp) { + int bgn = posRef.Val(); + int pos = Bry_find_.Find_fwd(ary, lkp, bgn, ary.length); + if (pos == Bry_find_.Not_found) throw Err_.new_wo_type("lkp failed", "lkp", (char)lkp, "bgn", bgn); + double rv = Bry_.To_double(ary, posRef.Val(), pos); + posRef.Val_(pos + 1); // +1 = lkp.Len + return rv; + } + public static void ReadCsvNext(byte[] ary, Int_obj_ref posRef, byte lkp) { + int bgn = posRef.Val(); + int pos = Bry_find_.Find_fwd(ary, lkp, bgn, ary.length); + posRef.Val_(pos + 1); // +1 = lkp.Len + } + public static byte Byte_NegSign = (byte)'-'; + public static byte[] Replace_create(byte[] src, byte find, byte replace) { + byte[] rv = Bry_.Copy(src); + Replace_reuse(rv, find, replace); + return rv; + } + public static void Replace_reuse(byte[] src, byte find, byte replace) { + int src_len = src.length; + for (int i = 0; i < src_len; i++) { + if (src[i] == find) src[i] = replace; + } + } + public static byte[] Replace(byte[] src, byte find, byte replace) {return Replace(src, 0, src.length, find, replace);} + public static byte[] Replace(byte[] src, int bgn, int end, byte find, byte replace) { + int src_len = src.length; + byte[] rv = new byte[src_len]; + for (int i = bgn; i < end; ++i) { + byte b = src[i]; + rv[i] = b == find ? replace : b; + } + for (int i = end; i < src_len; ++i) + rv[i] = src[i]; + return rv; + } + public static byte[] Replace_safe(Bry_bfr bfr, byte[] src, byte[] find, byte[] repl) { + if (src == null || find == null || repl == null) return null; + return Replace(bfr, src, find, repl, 0, src.length); + } + public static byte[] Replace(Bry_bfr bfr, byte[] src, byte[] find, byte[] repl) {return Replace(bfr, src, find, repl, 0, src.length);} + public static byte[] Replace(Bry_bfr bfr, byte[] src, byte[] find, byte[] repl, int src_bgn, int src_end) {return Replace(bfr, src, find, repl, src_bgn, src_end, Int_.Max_value);} + public static byte[] Replace(Bry_bfr bfr, byte[] src, byte[] find, byte[] repl, int src_bgn, int src_end, int limit) { + int pos = src_bgn; + boolean dirty = false; + int find_len = find.length; + int bfr_bgn = pos; + int replace_count = 0; + while (pos < src_end) { + int find_pos = Bry_find_.Find_fwd(src, find, pos); + if (find_pos == Bry_find_.Not_found) break; + dirty = true; + bfr.Add_mid(src, bfr_bgn, find_pos); + bfr.Add(repl); + pos = find_pos + find_len; + bfr_bgn = pos; + ++replace_count; + if (replace_count == limit) break; + } + if (dirty) + bfr.Add_mid(src, bfr_bgn, src_end); + return dirty ? bfr.To_bry_and_clear() : src; + } + public static byte[] Replace(byte[] src, byte[] find, byte[] replace) {return Replace_between(src, find, null, replace);} + public static byte[] Replace_between(byte[] src, byte[] bgn, byte[] end, byte[] replace) { + Bry_bfr bfr = Bry_bfr_.New(); + boolean replace_all = end == null; + int src_len = src.length, bgn_len = bgn.length, end_len = replace_all ? 0 : end.length; + int pos = 0; + while (true) { + if (pos >= src_len) break; + int bgn_pos = Bry_find_.Find_fwd(src, bgn, pos); + if (bgn_pos == Bry_find_.Not_found) { + bfr.Add_mid(src, pos, src_len); + break; + } + else { + int bgn_rhs = bgn_pos + bgn_len; + int end_pos = replace_all ? bgn_rhs : Bry_find_.Find_fwd(src, end, bgn_rhs); + if (end_pos == Bry_find_.Not_found) { + bfr.Add_mid(src, pos, src_len); + break; + } + else { + bfr.Add_mid(src, pos, bgn_pos); + bfr.Add(replace); + pos = end_pos + end_len; + } + } + } + return bfr.To_bry_and_clear(); + } + public static int Trim_end_pos(byte[] src, int end) { + for (int i = end - 1; i > -1; i--) { + switch (src[i]) { + case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: case Byte_ascii.Space: + break; + default: + return i + 1; + } + } + return 0; + } + public static byte[] Increment_last(byte[] ary) {return Increment_last(ary, ary.length - 1);} + public static byte[] Increment_last(byte[] ary, int end_idx) { + for (int i = end_idx; i > -1; i--) { + byte end_val_old = ary[i]; + byte end_val_new = (byte)(end_val_old + 1); + ary[i] = end_val_new; + if (end_val_new > (end_val_old & 0xff)) break; // PATCH.JAVA:need to convert to unsigned byte + } + return ary; + } + public static byte[] Ucase__all(byte[] src) {return Xcase__all(Bool_.Y, src, 0, -1);} + public static byte[] Lcase__all(byte[] src) {return Xcase__all(Bool_.N, src, 0, -1);} + public static byte[] Lcase__all(byte[] src, int bgn, int end) {return Xcase__all(Bool_.N, src, bgn, end);} + private static byte[] Xcase__all(boolean upper, byte[] src, int bgn, int end) { + if (src == null) return null; + int len = end == -1 ? src.length : end - bgn; if (len == 0) return src; + byte[] rv = new byte[len]; + for (int i = 0; i < len; ++i) { + byte b = src[i + bgn]; + if (upper) { + if (b > 96 && b < 123) b -= 32; + } + else { + if (b > 64 && b < 91) b += 32; + } + rv[i] = b; + } + return rv; + } + public static byte[] Xcase__build__all(Bry_bfr tmp, boolean upper, byte[] src) { + if (src == null) return null; + int src_bgn = 0; + int src_end = src.length; + int lbound = 96, ubound = 123; + if (!upper) { + lbound = 64; ubound = 91; + } + + boolean dirty = false; + for (int i = src_bgn; i < src_end; i++) { + byte b = src[i]; + if (b > lbound && b < ubound) { + if (!dirty) { + dirty = true; + tmp.Add_mid(src, src_bgn, i); + } + if (upper) + b -= 32; + else + b += 32; + } + if (dirty) + tmp.Add_byte(b); + } + return dirty ? tmp.To_bry_and_clear() : src; + } + public static byte[] Ucase__1st(byte[] src) {return Xcase__1st(Bool_.Y, src);} + public static byte[] Lcase__1st(byte[] src) {return Xcase__1st(Bool_.N, src);} + private static byte[] Xcase__1st(boolean upper, byte[] src) { + if (src == null) return null; + int len = src.length; if (len == 0) return src; + byte[] rv = new byte[len]; + byte b = src[0]; + if (upper) { + if (b > 96 && b < 123) b -= 32; + } + else { + if (b > 64 && b < 91) b += 32; + } + rv[0] = b; + for (int i = 1; i < len; ++i) { + rv[i] = src[i]; + } + return rv; + } + public static byte[] Null_if_empty(byte[] v) {return Len_eq_0(v) ? null : v;} + + public static byte[] Escape_ws(byte[] bry) {Bry_bfr bfr = Bry_bfr_.Get(); byte[] rv = Escape_ws(bfr, bry); bfr.Mkr_rls(); return rv;} + public static byte[] Escape_ws(Bry_bfr bfr, byte[] src) { + boolean dirty = false; + int len = src.length; + for (int i = 0; i < len; ++i) { + byte b = src[i]; + byte escape = Byte_.Zero; + switch (b) { + case Byte_ascii.Tab: escape = Byte_ascii.Ltr_t; break; + case Byte_ascii.Nl: escape = Byte_ascii.Ltr_n; break; + case Byte_ascii.Cr: escape = Byte_ascii.Ltr_r; break; + default: if (dirty) bfr.Add_byte(b); break; + } + if (escape != Byte_.Zero) { + if (!dirty) { + dirty = true; + bfr.Add_mid(src, 0, i); + } + bfr.Add_byte_backslash().Add_byte(escape); + } + } + return dirty ? bfr.To_bry_and_clear() : src; + } + public static byte[] Resolve_escape(Bry_bfr bfr, byte escape, byte[] raw, int bgn, int end) { + int pos = bgn; + boolean dirty = false; + while (pos < end) { + byte b = raw[pos]; + if (b == escape) { + if (!dirty) { + dirty = true; + bfr.Add_mid(raw, bgn, pos); + } + ++pos; + if (pos < end) { // check for eos; note that this ignores trailing "\"; EX: "a\" -> "a" + bfr.Add_byte(raw[pos]); + ++pos; + } + } + else { + if (dirty) bfr.Add_byte(b); + ++pos; + } + } + return dirty ? bfr.To_bry_and_clear() : raw; + } + public static void Clear(byte[] bry) { + int len = bry.length; + for (int i = 0; i < len; ++i) + bry[i] = Byte_.Zero; + } + public static byte[] Replace_nl_w_tab(byte[] src, int bgn, int end) { + return Bry_.Replace(Bry_.Mid(src, bgn, end), Byte_ascii.Nl, Byte_ascii.Tab); + } + public static byte[] Escape_html(byte[] src) { + return Escape_html(null, Bool_.N, src, 0, src.length); + } + public static byte[] Escape_html(Bry_bfr bfr, boolean ws, byte[] src, int src_bgn, int src_end) { // uses PHP rules for htmlspecialchars; REF.PHP:http://php.net/manual/en/function.htmlspecialchars.php + boolean dirty = false; + int cur = src_bgn; + int prv = cur; + boolean called_by_bry = bfr == null; + + // loop over chars + while (true) { + // if EOS, exit + if (cur == src_end) { + if (dirty) { + bfr.Add_mid(src, prv, src_end); + } + break; + } + + // check current byte if escaped + byte b = src[cur]; + byte[] escaped = null; + switch (b) { + case Byte_ascii.Amp: escaped = Gfh_entity_.Amp_bry; break; + case Byte_ascii.Quote: escaped = Gfh_entity_.Quote_bry; break; + case Byte_ascii.Apos: escaped = Gfh_entity_.Apos_num_bry; break; + case Byte_ascii.Lt: escaped = Gfh_entity_.Lt_bry; break; + case Byte_ascii.Gt: escaped = Gfh_entity_.Gt_bry; break; + case Byte_ascii.Nl: if (ws) escaped = Gfh_entity_.Nl_bry; break; + case Byte_ascii.Cr: if (ws) escaped = Gfh_entity_.Cr_bry; break; + case Byte_ascii.Tab: if (ws) escaped = Gfh_entity_.Tab_bry; break; + } + + // not escaped; increment and continue + if (escaped == null) { + cur++; + continue; + } + // escaped + else { + dirty = true; + if (bfr == null) bfr = Bry_bfr_.New(); + + if (prv < cur) + bfr.Add_mid(src, prv, cur); + bfr.Add(escaped); + cur++; + prv = cur; + } + } + + if (dirty) { + if (called_by_bry) + return bfr.To_bry_and_clear(); + else + return null; + } + else { + if (called_by_bry) { + if (src_bgn == 0 && src_end == src.length) + return src; + else + return Bry_.Mid(src, src_bgn, src_end); + } + else { + bfr.Add_mid(src, src_bgn, src_end); + return null; + } + } + } +} diff --git a/100_core/src/gplx/Bry__tst.java b/100_core/src/gplx/Bry__tst.java new file mode 100644 index 000000000..6e531636d --- /dev/null +++ b/100_core/src/gplx/Bry__tst.java @@ -0,0 +1,307 @@ +/* +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; +import org.junit.*; import gplx.core.primitives.*; import gplx.core.brys.*; import gplx.core.tests.*; +public class Bry__tst { + private final Bry__fxt fxt = new Bry__fxt(); + @Test public void new_ascii_() { + fxt.Test_new_a7("a" , Bry_.New_by_ints(97)); // one + fxt.Test_new_a7("abc" , Bry_.New_by_ints(97, 98, 99)); // many + fxt.Test_new_a7("" , Bry_.Empty); // none + fxt.Test_new_a7("¢€𤭢" , Bry_.New_by_ints(63, 63, 63, 63)); // non-ascii -> ? + } + @Test public void new_u8() { + fxt.Test_new_u8("a" , Bry_.New_by_ints(97)); // one + fxt.Test_new_u8("abc" , Bry_.New_by_ints(97, 98, 99)); // many + fxt.Test_new_u8("¢" , Bry_.New_by_ints(194, 162)); // bry_len=2; cent + fxt.Test_new_u8("€" , Bry_.New_by_ints(226, 130, 172)); // bry_len=3; euro + fxt.Test_new_u8("𤭢" , Bry_.New_by_ints(240, 164, 173, 162)); // bry_len=4; example from en.w:UTF-8 + } + @Test public void Add__bry_plus_byte() { + fxt.Test_add("a" , Byte_ascii.Pipe , "a|"); // basic + fxt.Test_add("" , Byte_ascii.Pipe , "|"); // empty String + } + @Test public void Add__byte_plus_bry() { + fxt.Test_add(Byte_ascii.Pipe , "a" , "|a"); // basic + fxt.Test_add(Byte_ascii.Pipe , "" , "|"); // empty String + } + @Test public void Add_w_dlm() { + fxt.Test_add_w_dlm(Byte_ascii.Pipe, String_.Ary("a", "b", "c") , "a|b|c"); // basic + fxt.Test_add_w_dlm(Byte_ascii.Pipe, String_.Ary("a") , "a"); // one item + fxt.Test_add_w_dlm(Byte_ascii.Pipe, String_.Ary("a", null, "c") , "a||c"); // null + } + @Test public void Add_w_dlm_bry() { + fxt.Test_add_w_dlm("<>", String_.Ary("a","b","c"), "a<>b<>c"); + } + @Test public void MidByPos() { + tst_MidByPos("abcba", 0, 1, "a"); + tst_MidByPos("abcba", 0, 2, "ab"); + tst_MidByPos("abcba", 1, 4, "bcb"); + } void tst_MidByPos(String src, int bgn, int end, String expd) {Tfds.Eq(expd, String_.new_u8(Bry_.Mid(Bry_.new_u8(src), bgn, end)));} + @Test public void Replace_one() { + tst_ReplaceOne("a" , "b" , "c" , "a"); + tst_ReplaceOne("b" , "b" , "c" , "c"); + tst_ReplaceOne("bb" , "b" , "c" , "cb"); + tst_ReplaceOne("abcd" , "bc" , "" , "ad"); + tst_ReplaceOne("abcd" , "b" , "ee" , "aeecd"); + } void tst_ReplaceOne(String src, String find, String repl, String expd) {Tfds.Eq(expd, String_.new_u8(Bry_.Replace_one(Bry_.new_u8(src), Bry_.new_u8(find), Bry_.new_u8(repl))));} + @Test public void XtoStrBytesByInt() { + tst_XtoStrBytesByInt(0, 0); + tst_XtoStrBytesByInt(9, 9); + tst_XtoStrBytesByInt(10, 1, 0); + tst_XtoStrBytesByInt(321, 3, 2, 1); + tst_XtoStrBytesByInt(-321, Bry_.Byte_NegSign, 3, 2, 1); + tst_XtoStrBytesByInt(Int_.Max_value, 2,1,4,7,4,8,3,6,4,7); + } + void tst_XtoStrBytesByInt(int val, int... expdAryAsInt) { + byte[] expd = new byte[expdAryAsInt.length]; + for (int i = 0; i < expd.length; i++) { + int expdInt = expdAryAsInt[i]; + expd[i] = expdInt == Bry_.Byte_NegSign ? Bry_.Byte_NegSign : Byte_ascii.To_a7_str(expdAryAsInt[i]); + } + Tfds.Eq_ary(expd, Bry_.To_a7_bry(val, Int_.DigitCount(val))); + } + @Test public void Has_at_end() { + tst_HasAtEnd("a|bcd|e", "d" , 2, 5, true); // y_basic + tst_HasAtEnd("a|bcd|e", "bcd" , 2, 5, true); // y_many + tst_HasAtEnd("a|bcd|e", "|bcd" , 2, 5, false); // n_long + tst_HasAtEnd("a|bcd|e", "|bc" , 2, 5, false); // n_pos + tst_HasAtEnd("abc", "bc", true); // y + tst_HasAtEnd("abc", "bd", false); // n + tst_HasAtEnd("a", "ab", false); // exceeds_len + } + void tst_HasAtEnd(String src, String find, int bgn, int end, boolean expd) {Tfds.Eq(expd, Bry_.Has_at_end(Bry_.new_u8(src), Bry_.new_u8(find), bgn, end));} + void tst_HasAtEnd(String src, String find, boolean expd) {Tfds.Eq(expd, Bry_.Has_at_end(Bry_.new_u8(src), Bry_.new_u8(find)));} + @Test public void Has_at_bgn() { + tst_HasAtBgn("y_basic" , "a|bcd|e", "b" , 2, 5, true); + tst_HasAtBgn("y_many" , "a|bcd|e", "bcd" , 2, 5, true); + tst_HasAtBgn("n_long" , "a|bcd|e", "bcde" , 2, 5, false); + tst_HasAtBgn("n_pos" , "a|bcd|e", "|bc" , 2, 5, false); + } void tst_HasAtBgn(String tst, String src, String find, int bgn, int end, boolean expd) {Tfds.Eq(expd, Bry_.Has_at_bgn(Bry_.new_u8(src), Bry_.new_u8(find), bgn, end), tst);} + @Test public void Match() { + tst_Match("abc", 0, "abc", true); + tst_Match("abc", 2, "c", true); + tst_Match("abc", 0, "cde", false); + tst_Match("abc", 2, "abc", false); // bounds check + tst_Match("abc", 0, "abcd", false); + tst_Match("a" , 0, "", false); + tst_Match("" , 0, "a", false); + tst_Match("" , 0, "", true); + tst_Match("ab", 0, "a", false); // FIX: "ab" should not match "a" b/c .length is different + } void tst_Match(String src, int srcPos, String find, boolean expd) {Tfds.Eq(expd, Bry_.Match(Bry_.new_u8(src), srcPos, Bry_.new_u8(find)));} + @Test public void ReadCsvStr() { + tst_ReadCsvStr("a|" , "a"); + tst_ReadCsvStr("|a|", 1 , "a"); + Int_obj_ref bgn = Int_obj_ref.New_zero(); tst_ReadCsvStr("a|b|c|", bgn, "a"); tst_ReadCsvStr("a|b|c|", bgn, "b"); tst_ReadCsvStr("a|b|c|", bgn, "c"); + tst_ReadCsvStr("|", ""); + tst_ReadCsvStr_err("a"); + + tst_ReadCsvStr("'a'|" , "a"); + tst_ReadCsvStr("'a''b'|" , "a'b"); + tst_ReadCsvStr("'a|b'|" , "a|b"); + tst_ReadCsvStr("''|", ""); + tst_ReadCsvStr_err("''"); + tst_ReadCsvStr_err("'a'b'"); + tst_ReadCsvStr_err("'a"); + tst_ReadCsvStr_err("'a|"); + tst_ReadCsvStr_err("'a'"); + } + @Test public void XtoIntBy4Bytes() { // test len=1, 2, 3, 4 + tst_XtoIntBy4Bytes(32, (byte)32); // space + tst_XtoIntBy4Bytes(8707, (byte)34, (byte)3); // ∃ + tst_XtoIntBy4Bytes(6382179, Byte_ascii.Ltr_a, Byte_ascii.Ltr_b, Byte_ascii.Ltr_c); + tst_XtoIntBy4Bytes(1633837924, Byte_ascii.Ltr_a, Byte_ascii.Ltr_b, Byte_ascii.Ltr_c, Byte_ascii.Ltr_d); + } + @Test public void XtoInt() { + tst_XtoInt("1", 1); + tst_XtoInt("123", 123); + tst_XtoInt("a", Int_.Min_value, Int_.Min_value); + tst_XtoInt("-1", Int_.Min_value, -1); + tst_XtoInt("-123", Int_.Min_value, -123); + tst_XtoInt("123-1", Int_.Min_value, Int_.Min_value); + tst_XtoInt("+123", Int_.Min_value, 123); + tst_XtoInt("", -1); + } + void tst_XtoInt(String val, int expd) {tst_XtoInt(val, -1, expd);} + void tst_XtoInt(String val, int or, int expd) {Tfds.Eq(expd, Bry_.To_int_or(Bry_.new_u8(val), or));} + void tst_XtoIntBy4Bytes(int expd, byte... ary) {Tfds.Eq(expd, Bry_.To_int_by_a7(ary), "XtoInt"); Tfds.Eq_ary(ary, Bry_.new_by_int(expd), "XbyInt");} + void tst_ReadCsvStr(String raw, String expd) {tst_ReadCsvStr(raw, Int_obj_ref.New_zero() , expd);} + void tst_ReadCsvStr(String raw, int bgn, String expd) {tst_ReadCsvStr(raw, Int_obj_ref.New(bgn), expd);} + void tst_ReadCsvStr(String raw, Int_obj_ref bgnRef, String expd) { + int bgn = bgnRef.Val(); + boolean rawHasQuotes = String_.CharAt(raw, bgn) == '\''; + String actl = String_.Replace(Bry_.ReadCsvStr(Bry_.new_u8(String_.Replace(raw, "'", "\"")), bgnRef, (byte)'|'), "\"", "'"); + Tfds.Eq(expd, actl, "rv"); + if (rawHasQuotes) { + int quoteAdj = String_.Count(actl, "'"); + Tfds.Eq(bgn + 1 + String_.Len(actl) + 2 + quoteAdj, bgnRef.Val(), "pos_quote"); // +1=lkp.Len; +2=bgn/end quotes + } + else + Tfds.Eq(bgn + 1 + String_.Len(actl), bgnRef.Val(), "pos"); // +1=lkp.Len + } + void tst_ReadCsvStr_err(String raw) { + try {Bry_.ReadCsvStr(Bry_.new_u8(String_.Replace(raw, "'", "\"")), Int_obj_ref.New_zero(), (byte)'|');} + catch (Exception e) {Err_.Noop(e); return;} + Tfds.Fail_expdError(); + } + @Test public void ReadCsvDte() { + tst_ReadCsvDte("20110801 221435.987"); + } void tst_ReadCsvDte(String raw) {Tfds.Eq_date(DateAdp_.parse_fmt(raw, Bry_.Fmt_csvDte), Bry_.ReadCsvDte(Bry_.new_u8(raw + "|"), Int_obj_ref.New_zero(), (byte)'|'));} + @Test public void ReadCsvInt() { + tst_ReadCsvInt("1234567890"); + } void tst_ReadCsvInt(String raw) {Tfds.Eq(Int_.Parse(raw), Bry_.ReadCsvInt(Bry_.new_u8(raw + "|"), Int_obj_ref.New_zero(), (byte)'|'));} + @Test public void Trim() { + Trim_tst("a b c", 1, 4, "b"); + Trim_tst("a c", 1, 3, ""); + Trim_tst(" ", 0, 2, ""); + } void Trim_tst(String raw, int bgn, int end, String expd) {Tfds.Eq(expd, String_.new_u8(Bry_.Trim(Bry_.new_u8(raw), bgn, end)));} + @Test public void Xto_int_lax() { + tst_Xto_int_lax("12a", 12); + tst_Xto_int_lax("1", 1); + tst_Xto_int_lax("123", 123); + tst_Xto_int_lax("a", 0); + tst_Xto_int_lax("-1", -1); + } + private void tst_Xto_int_lax(String val, int expd) {Tfds.Eq(expd, Bry_.To_int_or__lax(Bry_.new_u8(val), 0, String_.Len(val), 0));} + @Test public void To_int_or__trim_ws() { + tst_Xto_int_trim("123 " , 123); + tst_Xto_int_trim(" 123" , 123); + tst_Xto_int_trim(" 123 " , 123); + tst_Xto_int_trim(" 1 3 " , -1); + } + private void tst_Xto_int_trim(String val, int expd) {Tfds.Eq(expd, Bry_.To_int_or__trim_ws(Bry_.new_u8(val), 0, String_.Len(val), -1));} + @Test public void Compare() { + tst_Compare("abcde", 0, 1, "abcde", 0, 1, CompareAble_.Same); + tst_Compare("abcde", 0, 1, "abcde", 1, 2, CompareAble_.Less); + tst_Compare("abcde", 1, 2, "abcde", 0, 1, CompareAble_.More); + tst_Compare("abcde", 0, 1, "abcde", 0, 2, CompareAble_.Less); + tst_Compare("abcde", 0, 2, "abcde", 0, 1, CompareAble_.More); + tst_Compare("abcde", 2, 3, "abçde", 2, 3, CompareAble_.Less); + } void tst_Compare(String lhs, int lhs_bgn, int lhs_end, String rhs, int rhs_bgn, int rhs_end, int expd) {Tfds.Eq(expd, Bry_.Compare(Bry_.new_u8(lhs), lhs_bgn, lhs_end, Bry_.new_u8(rhs), rhs_bgn, rhs_end));} + @Test public void Increment_last() { + tst_IncrementLast(ary_(0), ary_(1)); + tst_IncrementLast(ary_(0, 255), ary_(1, 0)); + tst_IncrementLast(ary_(104, 111, 112, 101), ary_(104, 111, 112, 102)); + } + byte[] ary_(int... ary) { + byte[] rv = new byte[ary.length]; + for (int i = 0; i < ary.length; i++) + rv[i] = Byte_.By_int(ary[i]); + return rv; + } + void tst_IncrementLast(byte[] ary, byte[] expd) {Tfds.Eq_ary(expd, Bry_.Increment_last(Bry_.Copy(ary)));} + @Test public void Replace_between() { + tst_Replace_between("a[0]b" , "[", "]", "0", "a0b"); + tst_Replace_between("a[0]b[1]c" , "[", "]", "0", "a0b0c"); + tst_Replace_between("a[0b" , "[", "]", "0", "a[0b"); + } public void tst_Replace_between(String src, String bgn, String end, String repl, String expd) {Tfds.Eq(expd, String_.new_a7(Bry_.Replace_between(Bry_.new_a7(src), Bry_.new_a7(bgn), Bry_.new_a7(end), Bry_.new_a7(repl))));} + @Test public void Replace() { + Bry_bfr tmp_bfr = Bry_bfr_.New(); + tst_Replace(tmp_bfr, "a0b" , "0", "00", "a00b"); // 1 -> 1 + tst_Replace(tmp_bfr, "a0b0c" , "0", "00", "a00b00c"); // 1 -> 2 + tst_Replace(tmp_bfr, "a00b00c" , "00", "0", "a0b0c"); // 2 -> 1 + tst_Replace(tmp_bfr, "a0b0" , "0", "00", "a00b00"); // 1 -> 2; EOS + tst_Replace(tmp_bfr, "a00b00" , "00", "0", "a0b0"); // 2 -> 1; EOS + tst_Replace(tmp_bfr, "a0b0" , "1", "2", "a0b0"); // no match + tst_Replace(tmp_bfr, "a0b0" , "b1", "b2", "a0b0"); // false match; EOS + } + public void tst_Replace(Bry_bfr tmp_bfr, String src, String bgn, String repl, String expd) { + Tfds.Eq(expd, String_.new_a7(Bry_.Replace(tmp_bfr, Bry_.new_a7(src), Bry_.new_a7(bgn), Bry_.new_a7(repl)))); + } + @Test public void Split_bry() { + Split_bry_tst("a|b|c|" , "|" , String_.Ary("a", "b", "c")); + Split_bry_tst("a|" , "|" , String_.Ary("a")); + } + void Split_bry_tst(String src, String dlm, String[] expd) { + String[] actl = String_.Ary(Bry_split_.Split(Bry_.new_a7(src), Bry_.new_a7(dlm))); + Tfds.Eq_ary_str(expd, actl); + } + @Test public void Split_lines() { + Tst_split_lines("a\nb" , "a", "b"); // basic + Tst_split_lines("a\nb\n" , "a", "b"); // do not create empty trailing lines + Tst_split_lines("a\r\nb" , "a", "b"); // crlf + Tst_split_lines("a\rb" , "a", "b"); // cr only + } + void Tst_split_lines(String src, String... expd) { + Tfds.Eq_ary(expd, New_ary(Bry_split_.Split_lines(Bry_.new_a7(src)))); + } + String[] New_ary(byte[][] lines) { + int len = lines.length; + String[] rv = new String[len]; + for (int i = 0; i < len; i++) + rv[i] = String_.new_u8(lines[i]); + return rv; + } + @Test public void Match_bwd_any() { + Tst_match_bwd_any("abc", 2, 0, "c", true); + Tst_match_bwd_any("abc", 2, 0, "b", false); + Tst_match_bwd_any("abc", 2, 0, "bc", true); + Tst_match_bwd_any("abc", 2, 0, "abc", true); + Tst_match_bwd_any("abc", 2, 0, "zabc", false); + Tst_match_bwd_any("abc", 1, 0, "ab", true); + } + void Tst_match_bwd_any(String src, int src_end, int src_bgn, String find, boolean expd) { + Tfds.Eq(expd, Bry_.Match_bwd_any(Bry_.new_a7(src), src_end, src_bgn, Bry_.new_a7(find))); + } + @Test public void Trim_end() { + fxt.Test_trim_end("a " , Byte_ascii.Space, "a"); // trim.one + fxt.Test_trim_end("a " , Byte_ascii.Space, "a"); // trim.many + fxt.Test_trim_end("a" , Byte_ascii.Space, "a"); // trim.none + fxt.Test_trim_end("" , Byte_ascii.Space, ""); // empty + } + @Test public void Mid_w_trim() { + fxt.Test_Mid_w_trim("abc", "abc"); // no ws + fxt.Test_Mid_w_trim(" a b c ", "a b c"); // ws at bgn and end + fxt.Test_Mid_w_trim("\r\n\t a\r\n\t b \r\n\t ", "a\r\n\t b"); // space at bgn and end + fxt.Test_Mid_w_trim("", ""); // handle 0 bytes + fxt.Test_Mid_w_trim(" ", ""); // handle all ws + } + @Test public void New_u8_nl_apos() { + fxt.Test__new_u8_nl_apos(String_.Ary("a"), "a"); + fxt.Test__new_u8_nl_apos(String_.Ary("a", "b"), "a\nb"); + fxt.Test__new_u8_nl_apos(String_.Ary("a", "b'c", "d"), "a\nb\"c\nd"); + } + @Test public void Repeat_bry() { + fxt.Test__repeat_bry("abc" , 3, "abcabcabc"); + } + @Test public void Xcase__build__all() { + fxt.Test__xcase__build__all(Bool_.N, "abc", "abc"); + fxt.Test__xcase__build__all(Bool_.N, "aBc", "abc"); + } +} +class Bry__fxt { + private final Bry_bfr tmp = Bry_bfr_.New(); + public void Test_trim_end(String raw, byte trim, String expd) { + byte[] raw_bry = Bry_.new_a7(raw); + Tfds.Eq(expd, String_.new_u8(Bry_.Trim_end(raw_bry, trim, raw_bry.length))); + } + public void Test_new_u8(String raw, byte[] expd) {Tfds.Eq_ary(expd, Bry_.new_u8(raw));} + public void Test_new_a7(String raw, byte[] expd) {Tfds.Eq_ary(expd, Bry_.new_a7(raw));} + public void Test_add(String s, byte b, String expd) {Tfds.Eq_str(expd, String_.new_u8(Bry_.Add(Bry_.new_u8(s), b)));} + public void Test_add(byte b, String s, String expd) {Tfds.Eq_str(expd, String_.new_u8(Bry_.Add(b, Bry_.new_u8(s))));} + public void Test_add_w_dlm(String dlm, String[] itms, String expd) {Tfds.Eq(expd, String_.new_u8(Bry_.Add_w_dlm(Bry_.new_u8(dlm), Bry_.Ary(itms))));} + public void Test_add_w_dlm(byte dlm, String[] itms, String expd) {Tfds.Eq(expd, String_.new_u8(Bry_.Add_w_dlm(dlm, Bry_.Ary(itms))));} + public void Test_Mid_w_trim(String src, String expd) {byte[] bry = Bry_.new_u8(src); Tfds.Eq(expd, String_.new_u8(Bry_.Mid_w_trim(bry, 0, bry.length)));} + public void Test__new_u8_nl_apos(String[] ary, String expd) { + Tfds.Eq_str_lines(expd, String_.new_u8(Bry_.New_u8_nl_apos(ary))); + } + public void Test__repeat_bry(String s, int count, String expd) { + Gftest.Eq__str(expd, Bry_.Repeat_bry(Bry_.new_u8(s), count)); + } + public void Test__xcase__build__all(boolean upper, String src, String expd) { + Gftest.Eq__str(expd, Bry_.Xcase__build__all(tmp, upper, Bry_.new_u8(src))); + } +} diff --git a/100_core/src/gplx/Bry_bfr.java b/100_core/src/gplx/Bry_bfr.java new file mode 100644 index 000000000..465183f1c --- /dev/null +++ b/100_core/src/gplx/Bry_bfr.java @@ -0,0 +1,717 @@ +/* +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; +import gplx.core.primitives.*; import gplx.core.brys.*; import gplx.core.encoders.*; +import gplx.langs.htmls.entitys.*; +public class Bry_bfr { + private Bry_bfr_mkr_mgr mkr_mgr; private int reset; + public byte[] Bfr() {return bfr;} private byte[] bfr; + public int Len() {return bfr_len;} private int bfr_len; + public boolean Len_eq_0() {return bfr_len == 0;} + public boolean Len_gt_0() {return bfr_len > 0;} + public void Bfr_init(byte[] bfr, int bfr_len) { + synchronized (this) { + this.bfr = bfr; + this.bfr_len = bfr_len; + this.bfr_max = bfr.length; // NOTE: must sync bfr_max, else will fail later during add; bfr will think bfr has .length of bfr_max, when it actually has .length of bfr_len; DATE:2014-03-09 + } + } + public Bry_bfr Mkr_rls() { + if (mkr_mgr != null) { + synchronized (this) { + mkr_mgr.Rls(mkr_idx); + this.mkr_mgr = null; + this.mkr_idx = -1; + } + } + return this; + } + public void Clear_and_rls() { + this.Clear(); + this.Mkr_rls(); + } + public String To_str_and_rls() {return String_.new_u8(To_bry_and_rls());} + public byte[] To_bry_and_rls() { + byte[] rv = null; + synchronized (bfr) { + rv = To_bry(); + this.Clear(); + if (reset > 0) Reset_if_gt(reset); + synchronized (this) { // SAME: Mkr_rls() + mkr_mgr.Rls(mkr_idx); + mkr_idx = -1; // TS: DATE:2016-07-06 + mkr_mgr = null; + } + } + return rv; + } + public Bry_bfr Reset_(int v) {reset = v; return this;} + public Bry_bfr Reset_if_gt(int limit) { + if (bfr_max > limit) { + this.bfr_max = limit; + this.bfr = new byte[limit]; + } + bfr_len = 0; + return this; + } + public Bry_bfr Clear() { + synchronized (this) { + this.bfr_len = 0; + } + return this; + } + public Bry_bfr ClearAndReset() {bfr_len = 0; if (reset > 0) Reset_if_gt(reset); return this;} + public byte Get_at_last_or_nil_if_empty() {return bfr_len == 0 ? Byte_ascii.Null : bfr[bfr_len - 1];} + public Bry_bfr Add_safe(byte[] val) {return val == null ? this : Add(val);} + public Bry_bfr Add(byte[] val) { + int val_len = val.length; + if (bfr_len + val_len > bfr_max) Resize((bfr_max + val_len) * 2); + Bry_.Copy_by_pos(val, 0, val_len, bfr, bfr_len); + // Array_.Copy_to(val, 0, bfr, bfr_len, val_len); + bfr_len += val_len; + return this; + } + public Bry_bfr Add_mid(byte[] val, int bgn, int end) { + int len = end - bgn; + if (len < 0) throw Err_.new_wo_type("negative len", "bgn", bgn, "end", end, "excerpt", String_.new_u8__by_len(val, bgn, bgn + 16)); // NOTE: check for invalid end < bgn, else difficult to debug errors later; DATE:2014-05-11 + if (bfr_len + len > bfr_max) Resize((bfr_max + len) * 2); + Bry_.Copy_by_pos(val, bgn, end, bfr, bfr_len); + // Array_.Copy_to(val, bgn, bfr, bfr_len, len); + bfr_len += len; + return this; + } + public Bry_bfr Add_reverse_mid(byte[] val, int bgn, int end) { + int len = end - bgn; + if (len < 0) throw Err_.new_wo_type("negative len", "bgn", bgn, "end", end, "excerpt", String_.new_u8__by_len(val, bgn, bgn + 16)); // NOTE: check for invalid end < bgn, else difficult to debug errors later; DATE:2014-05-11 + if (bfr_len + len > bfr_max) Resize((bfr_max + len) * 2); + Bry_.Copy_by_pos_reversed(val, bgn, end, bfr, bfr_len); + // Array_.Copy_to(val, bgn, bfr, bfr_len, len); + bfr_len += len; + return this; + } + public Bry_bfr Add_mid_w_swap(byte[] val, int bgn, int end, byte swap_src, byte swap_trg) { + int len = end - bgn; + if (len < 0) throw Err_.new_wo_type("negative len", "bgn", bgn, "end", end, "excerpt", String_.new_u8__by_len(val, bgn, bgn + 16)); // NOTE: check for invalid end < bgn, else difficult to debug errors later; DATE:2014-05-11 + if (bfr_len + len > bfr_max) Resize((bfr_max + len) * 2); + int val_len = end - bgn; + for (int i = 0; i < val_len; ++i) { + byte b = val[i + bgn]; if (b == swap_src) b = swap_trg; + bfr[i + bfr_len] = b; + } + bfr_len += len; + return this; + } + public Bry_bfr Add_bry_ref_obj(Bry_obj_ref v) {v.Bfr_arg__add(this); return this;} + public Bry_bfr Add_bfr_and_preserve(Bry_bfr src) { + int len = src.bfr_len; + if (bfr_len + len > bfr_max) Resize((bfr_max + len) * 2); + Bry_.Copy_by_pos(src.bfr, 0, len, bfr, bfr_len); + // Array_.Copy_to(src.bfr, 0, bfr, bfr_len, len); + bfr_len += len; + return this; + } + public Bry_bfr Add_bfr_and_clear(Bry_bfr src) { + Add_bfr_and_preserve(src); + src.ClearAndReset(); + return this; + } + public Bry_bfr Add_bfr_or_mid(boolean escaped, Bry_bfr tmp_bfr, byte[] src, int src_bgn, int src_end) { + return escaped + ? this.Add_bfr_and_clear(tmp_bfr) + : this.Add_mid(src, src_bgn, src_end); + } + public Bry_bfr Add_bfr_trim_and_clear(Bry_bfr src, boolean trim_bgn, boolean trim_end) {return Add_bfr_trim_and_clear(src, trim_bgn, trim_end, Bry_.Trim_ary_ws);} + public Bry_bfr Add_bfr_trim_and_clear(Bry_bfr src, boolean trim_bgn, boolean trim_end, byte[] trim_ary) { + int src_len = src.bfr_len; + if (bfr_len + src_len > bfr_max) Resize((bfr_max + src_len) * 2); + byte[] src_bry = src.Bfr(); + int src_bgn = 0, src_end = src_len; + boolean all_ws = true; + if (trim_bgn) { + for (int i = 0; i < src_len; i++) { + byte b = src_bry[i]; + if (trim_ary[b & 0xFF] == Byte_ascii.Null) { + src_bgn = i; + i = src_len; + all_ws = false; + } + } + if (all_ws) return this; + } + if (trim_end) { + for (int i = src_len - 1; i > -1; i--) { + byte b = src_bry[i]; + if (trim_ary[b & 0xFF] == Byte_ascii.Null) { + src_end = i + 1; + i = -1; + all_ws = false; + } + } + if (all_ws) return this; + } + src_len = src_end - src_bgn; + Bry_.Copy_by_pos(src.bfr, src_bgn, src_end, bfr, bfr_len); + // Array_.Copy_to(src.bfr, src_bgn, bfr, bfr_len, src_len); + bfr_len += src_len; + src.Clear(); + return this; + } + public Bry_bfr Add_byte_as_a7(byte v) {return Add_byte((byte)(v + Byte_ascii.Num_0));} + public Bry_bfr Add_byte_eq() {return Add_byte(Byte_ascii.Eq);} + public Bry_bfr Add_byte_pipe() {return Add_byte(Byte_ascii.Pipe);} + public Bry_bfr Add_byte_comma() {return Add_byte(Byte_ascii.Comma);} + public Bry_bfr Add_byte_semic() {return Add_byte(Byte_ascii.Semic);} + public Bry_bfr Add_byte_apos() {return Add_byte(Byte_ascii.Apos);} + public Bry_bfr Add_byte_slash() {return Add_byte(Byte_ascii.Slash);} + public Bry_bfr Add_byte_backslash() {return Add_byte(Byte_ascii.Backslash);} + public Bry_bfr Add_byte_quote() {return Add_byte(Byte_ascii.Quote);} + public Bry_bfr Add_byte_space() {return Add_byte(Byte_ascii.Space);} + public Bry_bfr Add_byte_nl() {return Add_byte(Byte_ascii.Nl);} + public Bry_bfr Add_byte_dot() {return Add_byte(Byte_ascii.Dot);} + public Bry_bfr Add_byte_colon() {return Add_byte(Byte_ascii.Colon);} + public Bry_bfr Add_byte(byte val) { + int new_pos = bfr_len + 1; + if (new_pos > bfr_max) Resize(bfr_len * 2); + bfr[bfr_len] = val; + bfr_len = new_pos; + return this; + } + public Bry_bfr Add_byte_repeat(byte b, int len) { + if (bfr_len + len > bfr_max) Resize((bfr_max + len) * 2); + for (int i = 0; i < len; i++) + bfr[i + bfr_len] = b; + bfr_len += len; + return this; + } + public Bry_bfr Add_byte_if_not_last(byte b) { + if (bfr_len == 0 || (bfr_len > 0 && bfr[bfr_len - 1] == b)) return this; + this.Add_byte(b); + return this; + } + public Bry_bfr Add_byte_variable(byte v) {return Add_int_variable(v);} + public Bry_bfr Add_short_variable(short v) {return Add_int_variable(v);} + public Bry_bfr Add_u8_int(int val) { + if (bfr_len + 4 > bfr_max) Resize((bfr_max + 4) * 2); + int utf8_len = gplx.core.intls.Utf16_.Encode_int(val, bfr, bfr_len); + bfr_len += utf8_len; + return this; + } + public Bry_bfr Add_bool(boolean v) {return Add(v ? Bool_.True_bry : Bool_.False_bry);} + public Bry_bfr Add_int_bool(boolean v) {return Add_int_fixed(v ? 1 : 0, 1);} + public Bry_bfr Add_int_variable(int val) { + if (val < 0) { + this.Add(Int_.To_bry(val)); + return this; + } + int log10 = Int_.Log10(val); + int slots = val > -1 ? log10 + 1 : log10 * -1 + 2; + return Add_int(val, log10, slots); + } + public Bry_bfr Add_int_pad_bgn(byte pad_byte, int str_len, int val) { + int digit_len = Int_.DigitCount(val); + int pad_len = str_len - digit_len; + if (pad_len > 0) // note that this skips pad_len == 0, as well as guarding against negative pad_len; EX: pad(" ", 3, 1234) -> "1234" + Add_byte_repeat(pad_byte, pad_len); + Add_int_fixed(val, digit_len); + return this; + } + public Bry_bfr Add_int_digits(int digits, int val) {return Add_int(val, Int_.Log10(val), digits);} + public Bry_bfr Add_int_fixed(int val, int digits) {return Add_int(val, Int_.Log10(val), digits);} + public Bry_bfr Add_int(int val, int valLog, int arySlots) { + int aryBgn = bfr_len, aryEnd = bfr_len + arySlots; + if (aryEnd > bfr_max) Resize((aryEnd) * 2); + if (val < 0) { + bfr[aryBgn++] = Byte_ascii.Dash; + val *= -1; // make positive + valLog *= -1; // valLog will be negative; make positive + arySlots -= 1; // reduce slot by 1 + } + if (valLog >= arySlots) { + val %= Int_.Log10Ary[arySlots]; + } + for (int i = 0; i < arySlots; i++) { + int logIdx = arySlots - i - 1; + int div = logIdx < Int_.Log10AryLen ? Int_.Log10Ary[logIdx] : Int_.Max_value; + bfr[aryBgn + i] = (byte)((val / div) + 48); + val %= div; + } + bfr_len = aryEnd; + return this; + } + public Bry_bfr Add_long_variable(long v) {int digitCount = Long_.DigitCount(v); return Add_long(v, digitCount, digitCount);} + public Bry_bfr Add_long_fixed(long val, int digits) {return Add_long(val, Long_.DigitCount(val), digits);} + protected Bry_bfr Add_long(long val, int digitCount, int arySlots) { + int aryBgn = bfr_len, aryEnd = bfr_len + arySlots; + if (aryEnd > bfr_max) Resize((aryEnd) * 2); + if (val < 0) { + bfr[aryBgn++] = Byte_ascii.Dash; + val *= -1; // make positive + arySlots -= 1; // reduce slot by 1 + } + if (digitCount >= arySlots) { + val %= Long_.Log10Ary[arySlots]; + } + for (int i = 0; i < arySlots; i++) { + int logIdx = arySlots - i - 1; + long div = logIdx < Long_.Log10Ary_len ? Long_.Log10Ary[logIdx] : Long_.Max_value; + bfr[aryBgn + i] = (byte)((val / div) + 48); + val %= div; + } + bfr_len = aryEnd; + return this; + } + public Bry_bfr Add_bry_comma(byte[] v) {return Add_bry(Byte_ascii.Comma, v);} + public Bry_bfr Add_bry(byte dlm, byte[] v) { + if (v == null) return this; + int v_len = v.length; + for (int i = 0; i < v_len; i++) { + if (i != 0) this.Add_byte(dlm); + this.Add_int_variable(v[i]); + } + return this; + } + public Bry_bfr Add_bry_escape(byte quote_byte, byte[] escape, byte[] val, int bgn, int end) { // used for xml_wtr; DATE:2015-04-09 + boolean clean = true; // add with chunks of bytes instead of one-by-one + for (int i = bgn; i < end; ++i) { + byte b = val[i]; + if (clean) { + if (b == quote_byte) { + clean = false; + this.Add_mid(val, bgn, i); + this.Add(escape); + } + else {} + } + else { + if (b == quote_byte) this.Add(escape); + else this.Add_byte(b); + } + } + if (clean) + Add_mid(val, bgn, end); + return this; + } + public Bry_bfr Add_bry_many(byte[]... ary) { + int len = ary.length; + for (int i = 0; i < len; i++) { + byte[] bry = ary[i]; + if (bry != null && bry.length > 0) + this.Add(bry); + } + return this; + } + public Bry_bfr Add_bry_escape_html(byte[] val) { + if (val == null) return this; + return Add_bry_escape_html(val, 0, val.length); + } + public Bry_bfr Add_bry_escape_html(byte[] val, int bgn, int end) { + Bry_.Escape_html(this, Bool_.N, val, bgn, end); + return this; + } + public Bry_bfr Add_bry_escape_xml(byte[] val, int bgn, int end) { + Bry_.Escape_html(this, Bool_.Y, val, bgn, end); + return this; + } + public Bry_bfr Add_str_u8_w_nl(String s) {Add_str_u8(s); return Add_byte_nl();} + public Bry_bfr Add_str_u8_null(String s) {return Add_str_u8(s == null ? String_.Null_mark : s);} + public Bry_bfr Add_str_u8(String str) { + try { + int str_len = str.length(); + int bry_len = Bry_.new_u8__by_len(str, str_len); + if (bfr_len + bry_len > bfr_max) Resize((bfr_max + bry_len) * 2); + Bry_.new_u8__write(str, str_len, bfr, bfr_len); + bfr_len += bry_len; + return this; + } + catch (Exception e) {throw Err_.new_exc(e, "core", "invalid UTF-8 sequence", "s", str);} + } + public Bry_bfr Add_str_u8_many(String... ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) + Add_str_u8(ary[i]); + return this; + } + public Bry_bfr Add_str_u8_fmt(String fmt, Object... args) { + Add_str_u8(String_.Format(fmt, args)); + return this; + } + public Bry_bfr Add_str_a7_null(String s) {return Add_str_a7(s == null ? String_.Null_mark : s);} + public Bry_bfr Add_str_a7_w_nl(String s) {Add_str_a7(s); return Add_byte_nl();} + public Bry_bfr Add_str_a7(String str) { + try { + int bry_len = str.length(); + if (bfr_len + bry_len > bfr_max) Resize((bfr_max + bry_len) * 2); + for (int i = 0; i < bry_len; ++i) { + char c = str.charAt(i); + if (c > 128) c = '?'; + bfr[i + bfr_len] = (byte)c; + } + bfr_len += bry_len; + return this; + } + catch (Exception e) {throw Err_.new_exc(e, "core", "invalid UTF-8 sequence", "s", str);} + } + public Bry_bfr Add_kv_dlm(boolean line, String key, Object val) { + this.Add_str_a7(key).Add_byte_colon().Add_byte_space(); + this.Add(Bry_.new_u8(Object_.Xto_str_strict_or_null_mark(val))); + this.Add_byte(line ? Byte_ascii.Nl : Byte_ascii.Tab); + return this; + } + public Bry_bfr Add_float(float f) {Add_str_a7(Float_.To_str(f)); return this;} + public Bry_bfr Add_double(double v) {Add_str_a7(Double_.To_str(v)); return this;} + public Bry_bfr Add_dte(DateAdp val) {return Add_dte_segs(val.Year(), val.Month(),val.Day(), val.Hour(), val.Minute(), val.Second(), val.Frac());} + public Bry_bfr Add_dte_segs(int y, int M, int d, int H, int m, int s, int f) { // yyyyMMdd HHmmss.fff + if (bfr_len + 19 > bfr_max) Resize((bfr_len + 19) * 2); + bfr[bfr_len + 0] = (byte)((y / 1000) + Bry_.Ascii_zero); y %= 1000; + bfr[bfr_len + 1] = (byte)((y / 100) + Bry_.Ascii_zero); y %= 100; + bfr[bfr_len + 2] = (byte)((y / 10) + Bry_.Ascii_zero); y %= 10; + bfr[bfr_len + 3] = (byte)( y + Bry_.Ascii_zero); + bfr[bfr_len + 4] = (byte)((M / 10) + Bry_.Ascii_zero); M %= 10; + bfr[bfr_len + 5] = (byte)( M + Bry_.Ascii_zero); + bfr[bfr_len + 6] = (byte)((d / 10) + Bry_.Ascii_zero); d %= 10; + bfr[bfr_len + 7] = (byte)( d + Bry_.Ascii_zero); + bfr[bfr_len + 8] = Byte_ascii.Space; + bfr[bfr_len + 9] = (byte)((H / 10) + Bry_.Ascii_zero); H %= 10; + bfr[bfr_len + 10] = (byte)( H + Bry_.Ascii_zero); + bfr[bfr_len + 11] = (byte)((m / 10) + Bry_.Ascii_zero); m %= 10; + bfr[bfr_len + 12] = (byte)( m + Bry_.Ascii_zero); + bfr[bfr_len + 13] = (byte)((s / 10) + Bry_.Ascii_zero); s %= 10; + bfr[bfr_len + 14] = (byte)( s + Bry_.Ascii_zero); + bfr[bfr_len + 15] = Byte_ascii.Dot; + bfr[bfr_len + 16] = (byte)((f / 100) + Bry_.Ascii_zero); f %= 100; + bfr[bfr_len + 17] = (byte)((f / 10) + Bry_.Ascii_zero); f %= 10; + bfr[bfr_len + 18] = (byte)( f + Bry_.Ascii_zero); + bfr_len += 19; + return this; + } + public Bry_bfr Add_dte_utc(int y, int M, int d, int H, int m, int s, int f) { // yyyy-MM-ddTHH:mm:ssZ + if (bfr_len + 20 > bfr_max) Resize((bfr_len + 20) * 2); + bfr[bfr_len + 0] = (byte)((y / 1000) + Bry_.Ascii_zero); y %= 1000; + bfr[bfr_len + 1] = (byte)((y / 100) + Bry_.Ascii_zero); y %= 100; + bfr[bfr_len + 2] = (byte)((y / 10) + Bry_.Ascii_zero); y %= 10; + bfr[bfr_len + 3] = (byte)( y + Bry_.Ascii_zero); + bfr[bfr_len + 4] = Byte_ascii.Dash; + bfr[bfr_len + 5] = (byte)((M / 10) + Bry_.Ascii_zero); M %= 10; + bfr[bfr_len + 6] = (byte)( M + Bry_.Ascii_zero); + bfr[bfr_len + 7] = Byte_ascii.Dash; + bfr[bfr_len + 8] = (byte)((d / 10) + Bry_.Ascii_zero); d %= 10; + bfr[bfr_len + 9] = (byte)( d + Bry_.Ascii_zero); + bfr[bfr_len + 10] = Byte_ascii.Ltr_T; + bfr[bfr_len + 11] = (byte)((H / 10) + Bry_.Ascii_zero); H %= 10; + bfr[bfr_len + 12] = (byte)( H + Bry_.Ascii_zero); + bfr[bfr_len + 13] = Byte_ascii.Colon; + bfr[bfr_len + 14] = (byte)((m / 10) + Bry_.Ascii_zero); m %= 10; + bfr[bfr_len + 15] = (byte)( m + Bry_.Ascii_zero); + bfr[bfr_len + 16] = Byte_ascii.Colon; + bfr[bfr_len + 17] = (byte)((s / 10) + Bry_.Ascii_zero); s %= 10; + bfr[bfr_len + 18] = (byte)( s + Bry_.Ascii_zero); + bfr[bfr_len + 19] = Byte_ascii.Ltr_Z; + bfr_len += 20; + return this; + } + public Bry_bfr Add_swap_ws(byte[] src) {return Add_swap_ws(src, 0, src.length);} + public Bry_bfr Add_swap_ws(byte[] src, int bgn, int end) { + int len = end - bgn; + if (bfr_len + (len * 2) > bfr_max) Resize((bfr_max + (len * 2)) * 2); + for (int i = bgn; i < end; i++) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Nl: bfr[bfr_len] = Byte_ascii.Backslash; bfr[bfr_len + 1] = Byte_ascii.Ltr_n; bfr_len += 2; break; + case Byte_ascii.Tab: bfr[bfr_len] = Byte_ascii.Backslash; bfr[bfr_len + 1] = Byte_ascii.Ltr_t; bfr_len += 2; break; + case Byte_ascii.Backslash: bfr[bfr_len] = Byte_ascii.Backslash; bfr[bfr_len + 1] = Byte_ascii.Backslash; bfr_len += 2; break; + default: bfr[bfr_len] = b; ++bfr_len; break; + } + } + return this; + } + public Bry_bfr Add_str_pad_space_bgn(String v, int pad_max) {return Add_str_pad_space(v, pad_max, Bool_.N);} + public Bry_bfr Add_str_pad_space_end(String v, int pad_max) {return Add_str_pad_space(v, pad_max, Bool_.Y);} + Bry_bfr Add_str_pad_space(String v, int pad_max, boolean pad_end) { + byte[] v_bry = Bry_.new_u8(v); + if (pad_end) Add(v_bry); + int pad_len = pad_max - v_bry.length; + if (pad_len > 0) + Add_byte_repeat(Byte_ascii.Space, pad_len); + if (!pad_end) Add(v_bry); + return this; + } + + public Bry_bfr Add_obj(Object o) { + if (o == null) return this; // treat null as empty String; + Class o_type = o.getClass(); + if (o_type == byte[].class) Add((byte[])o); + else if (o_type == Integer.class) Add_int_variable(Int_.Cast(o)); + else if (o_type == Byte.class) Add_byte(Byte_.Cast(o)); + else if (o_type == Long.class) Add_long_variable(Long_.cast(o)); + else if (o_type == String.class) Add_str_u8((String)o); + else if (o_type == Bry_bfr.class) Add_bfr_and_preserve((Bry_bfr)o); + else if (o_type == DateAdp.class) Add_dte((DateAdp)o); + else if (o_type == Io_url.class) Add(((Io_url)o).RawBry()); + else if (o_type == Boolean.class) Add_yn(Bool_.Cast(o)); + else if (o_type == Double.class) Add_double(Double_.cast(o)); + else if (o_type == Float.class) Add_float(Float_.cast(o)); + else ((Bfr_arg)o).Bfr_arg__add(this); + return this; + } + public Bry_bfr Add_obj_strict(Object o) { + if (o == null) return this; // treat null as empty String; + Class o_type = o.getClass(); + if (o_type == byte[].class) Add((byte[])o); + else if (o_type == Integer.class) Add_int_variable(Int_.Cast(o)); + else if (o_type == Byte.class) Add_byte(Byte_.Cast(o)); + else if (o_type == Long.class) Add_long_variable(Long_.cast(o)); + else if (o_type == String.class) Add_str_u8((String)o); + else if (o_type == Bry_bfr.class) Add_bfr_and_preserve((Bry_bfr)o); + else if (o_type == DateAdp.class) Add_dte((DateAdp)o); + else if (o_type == Io_url.class) Add(((Io_url)o).RawBry()); + else if (o_type == Boolean.class) Add_bool(Bool_.Cast(o)); + else if (o_type == Double.class) Add_double(Double_.cast(o)); + else if (o_type == Float.class) Add_float(Float_.cast(o)); + else ((Bfr_arg)o).Bfr_arg__add(this); + return this; + } + public Bry_bfr Add_yn(boolean v) {Add_byte(v ? Byte_ascii.Ltr_y : Byte_ascii.Ltr_n); return this;} + public Bry_bfr Add_base85_len_5(int v) {return Add_base85(v, 5);} + public Bry_bfr Add_base85(int v, int pad) { + int new_len = bfr_len + pad; + if (new_len > bfr_max) Resize((new_len) * 2); + Base85_.Set_bry(v, bfr, bfr_len, pad); + bfr_len = new_len; + return this; + } + public boolean Match_end_byt(byte b) {return bfr_len == 0 ? false : bfr[bfr_len - 1] == b;} + public boolean Match_end_byt_nl_or_bos() {return bfr_len == 0 ? true : bfr[bfr_len - 1] == Byte_ascii.Nl;} + public boolean Match_end_ary(byte[] ary) {return Bry_.Match(bfr, bfr_len - ary.length, bfr_len, ary);} + public Bry_bfr Insert_at(int add_pos, byte[] add_bry) {return Insert_at(add_pos, add_bry, 0, add_bry.length);} + public Bry_bfr Insert_at(int add_pos, byte[] add_bry, int add_bgn, int add_end) { + int add_len = add_end - add_bgn; + int new_max = bfr_max + add_len; + byte[] new_bfr = new byte[new_max]; + if (add_pos > 0) + Bry_.Copy_by_pos (bfr , 0, add_pos, new_bfr, 0); + Bry_.Copy_by_pos (add_bry, add_bgn, add_end, new_bfr, add_pos); + Bry_.Copy_by_pos (bfr , add_pos, bfr_len, new_bfr, add_pos + add_len); + bfr = new_bfr; + bfr_len += add_len; + bfr_max = new_max; + return this; + } + public Bry_bfr Delete_rng_to_bgn(int pos) {return Delete_rng(0, pos);} + public Bry_bfr Delete_rng_to_end(int pos) {return Delete_rng(pos, bfr_len);} + public Bry_bfr Delete_rng(int rng_bgn, int rng_end) { + int rng_len = rng_end - rng_bgn; + Bry_.Copy_by_pos(bfr, rng_end, bfr_len, bfr, rng_bgn); + bfr_len -= rng_len; + return this; + } + public Bry_bfr Del_by_1() { + bfr_len -= 1; bfr[bfr_len] = 0; return this; + } + public Bry_bfr Del_by(int count) { + int new_len = bfr_len - count; + if (new_len > -1) bfr_len = new_len; + return this; + } + public Bry_bfr Trim_end(byte trim_byte) { + if (bfr_len == 0) return this; + int count = 0; + for (int i = bfr_len - 1; i > -1; --i) { + byte b = bfr[i]; + if (b == trim_byte) + ++count; + else + break; + } + if (count > 0) + this.Del_by(count); + return this; + } + public Bry_bfr Trim_end_ws() { + if (bfr_len == 0) return this; + int count = 0; + for (int i = bfr_len - 1; i > -1; --i) { + byte b = bfr[i]; + if (Trim_end_ws_ary[b]) + ++count; + else + break; + } + if (count > 0) + this.Del_by(count); + return this; + } + private static final boolean[] Trim_end_ws_ary = Trim_end_ws_new(); + private static boolean[] Trim_end_ws_new() { + boolean[] rv = new boolean[256]; + rv[32] = true; + rv[ 9] = true; + rv[10] = true; + rv[13] = true; + rv[11] = true; + return rv; + } + public Bry_bfr Concat_skip_empty(byte[] dlm, byte[]... ary) { + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) { + byte[] itm = ary[i]; + boolean itm_has_bytes = Bry_.Len_gt_0(itm); + if ( i != 0 + && itm_has_bytes + && bfr_len > 0 + ) + this.Add(dlm); + if (itm_has_bytes) + this.Add(itm); + } + return this; + } + public boolean Eq(byte b) {return bfr_len == 1 && bfr[0] == b;} + public byte[] To_bry(int bgn, int end) {return bfr_len == 0 ? Bry_.Empty : Bry_.Mid(bfr, bgn, end);} + public byte[] To_bry() {return bfr_len == 0 ? Bry_.Empty : Bry_.Mid(bfr, 0, bfr_len);} + public byte[] To_bry_and_clear_and_trim() {return To_bry_and_clear_and_trim(true, true, Bry_.Trim_ary_ws);} + public byte[] To_bry_and_clear_and_trim(boolean trim_bgn, boolean trim_end, byte[] trim_bry) { + byte[] rv = Bry_.Trim(bfr, 0, bfr_len, trim_bgn, trim_end, trim_bry); + this.Clear(); + return rv; + } + public byte[] To_bry_and_clear() { + byte[] rv = To_bry(); + this.Clear(); + if (reset > 0) Reset_if_gt(reset); + return rv; + } + public byte[] To_bry_and_clear_and_rls() { + byte[] rv = To_bry_and_clear(); + this.Mkr_rls(); + return rv; + } + public byte[] To_reversed_bry_and_clear() { + int len = this.Len(); + byte[] rv = new byte[len]; + for (int i = 0; i < len; i++) { + rv[len - i - 1] = bfr[i]; + } + this.Clear(); + if (reset > 0) Reset_if_gt(reset); + return rv; + } + public String To_str() {return String_.new_u8(To_bry());} + public String To_str_by_pos(int bgn, int end) {return String_.new_u8(To_bry(), bgn, end);} + public String To_str_and_clear() {return String_.new_u8(To_bry_and_clear());} + public String To_str_and_clear_and_trim() {return String_.new_u8(To_bry_and_clear_and_trim());} + public int To_int_and_clear(int or) {int rv = To_int(or); this.Clear(); return rv;} + public int To_int(int or) { + switch (bfr_len) { + case 0: return or; + case 1: { + byte b = bfr[0]; + return Byte_ascii.Is_num(b) ? b - Byte_ascii.Num_0 : or; + } + default: + long rv = 0, mult = 1; + for (int i = bfr_len - 1; i > -1; i--) { + byte b = bfr[i]; + if (!Byte_ascii.Is_num(b)) return or; + long dif = (b - Byte_ascii.Num_0 ) * mult; + long new_val = rv + dif; + if (new_val > Int_.Max_value) return or; // if number is > 2^32 consider error (int overflow); return or; DATE:2014-06-10 + rv = new_val; + mult *= 10; + } + return (int)rv; + } + } + public void Rls() { + bfr = null; + this.Mkr_rls(); + } + public byte[][] To_bry_ary_and_clear() { + if (bfr_len == 0) return Bry_.Ary_empty; + Int_list line_ends = Find_all(Byte_ascii.Nl); + + // create lines + int lines_len = line_ends.Len(); + byte[][] rv = new byte[lines_len][]; + int line_bgn = 0; + for (int i = 0; i < lines_len; ++i) { + int line_end = line_ends.Get_at(i); + rv[i] = Bry_.Mid(bfr, line_bgn, line_end); + line_bgn = line_end + 1; + } + this.ClearAndReset(); + return rv; + } + public String[] To_str_ary_and_clear() { + if (bfr_len == 0) return String_.Ary_empty; + Int_list line_ends = Find_all(Byte_ascii.Nl); + + // create lines + int lines_len = line_ends.Len(); + String[] rv = new String[lines_len]; + int line_bgn = 0; + for (int i = 0; i < lines_len; ++i) { + int line_end = line_ends.Get_at(i); + rv[i] = String_.new_u8(bfr, line_bgn, line_end); + line_bgn = line_end + 1; + } + this.ClearAndReset(); + return rv; + } + private Int_list Find_all(byte find) { + Int_list rv = new Int_list(); + // scan bfr for nl + int line_bgn = 0, line_end = 0; + while (line_bgn < bfr_len) { + line_end = Bry_find_.Find_fwd(bfr, find, line_bgn, bfr_len); + if (line_end == Bry_find_.Not_found) { // no more \n; add bfr_end + rv.Add(bfr_len); + break; + } + else { // \n found; add it + rv.Add(line_end); + line_bgn = line_end + 1; + } + } + return rv; + } + @Override public int hashCode() {return Bry_obj_ref.CalcHashCode(bfr, 0, bfr_len);} + @Override public boolean equals(Object obj) { + if (obj == null) return false; // NOTE: strange, but null check needed; throws null error; EX.WP: File:Eug�ne Delacroix - La libert� guidant le peuple.jpg + Bry_obj_ref comp = (Bry_obj_ref)obj; + return Bry_.Match(bfr, 0, bfr_len, comp.Val(), comp.Val_bgn(), comp.Val_end()); + } + public void Resize(int v) { + bfr_max = v; + bfr = Bry_.Resize(bfr, 0, v); + } + public int Mkr_idx() {return mkr_idx;} private int mkr_idx = -1; + public boolean Mkr_idx_is_null() {return mkr_idx == -1;} + public int Bfr_max() {return bfr_max;} private int bfr_max; + public Bry_bfr Mkr_init(Bry_bfr_mkr_mgr mkr_mgr, int itm) { + synchronized (this) { + this.mkr_mgr = mkr_mgr; this.mkr_idx = itm; + } + return this; + } + protected Bry_bfr() {} + public Bry_bfr(int bfr_max) { + Init(bfr_max); + } + protected void Init(int bfr_max) { + this.bfr_max = bfr_max; + this.bfr = new byte[bfr_max]; + } +} diff --git a/100_core/src/gplx/Bry_bfr_.java b/100_core/src/gplx/Bry_bfr_.java new file mode 100644 index 000000000..8f0c43648 --- /dev/null +++ b/100_core/src/gplx/Bry_bfr_.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; +import gplx.core.brys.*; +public class Bry_bfr_ { + public static Bry_bfr New() {return new Bry_bfr(16);} + public static Bry_bfr New_w_size(int v) {return new Bry_bfr(v);} + public static Bry_bfr Reset(int v) {return new Bry_bfr(16).Reset_(v);} // PERF: set initial size to 16, not reset val; allows for faster "startup"; DATE:2014-06-14 + + public static final Bry_bfr[] Ary_empty = new Bry_bfr[0]; + private static Bry_bfr_mkr_mgr dflt; + public static Bry_bfr Get() {if (dflt == null) dflt = new Bry_bfr_mkr_mgr(Bry_bfr_mkr.Tid_b128, 128); return dflt.Get();} // NOTE: lazy else "Object synchronization" error; DATE:2015-11-18 + public static void Assert_at_end(Bry_bfr bfr, byte assert_byte) { + int len = bfr.Len(); if (len == 0) return; + int assert_count = 0; + byte[] bfr_bry = bfr.Bfr(); + for (int i = len - 1; i > -1; --i) { + byte b = bfr_bry[i]; + if (b == assert_byte) + ++assert_count; + else + break; + } + switch (assert_count) { + case 0: bfr.Add_byte(assert_byte); break; + case 1: break; + default: bfr.Del_by(assert_count - 1); break; + } + } +} diff --git a/100_core/src/gplx/Bry_bfr_tst.java b/100_core/src/gplx/Bry_bfr_tst.java new file mode 100644 index 000000000..dcaee5c64 --- /dev/null +++ b/100_core/src/gplx/Bry_bfr_tst.java @@ -0,0 +1,247 @@ +/* +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; +import org.junit.*; import gplx.core.tests.*; +public class Bry_bfr_tst { + private Bry_bfr bb = Bry_bfr_.New(); + @Before public void setup() {bb.Clear();} private ByteAryBfr_fxt fxt = new ByteAryBfr_fxt(); + @Test public void AddByte() { + bb = Bry_bfr_.New_w_size(2); // NOTE: make sure auto-expands + tst_AddByte("a", "a", 2); + tst_AddByte("b", "ab", 2); + tst_AddByte("c", "abc", 4); + } + @Test public void AddAry() { // NOTE: make sure auto-expands + bb = Bry_bfr_.New_w_size(2); + tst_AddByte("abcd", "abcd", 12); + } + @Test public void Add_byte_repeat() { // NOTE: make sure auto-expands + bb = Bry_bfr_.New_w_size(2); + tst_Add_byte_repeat(Byte_ascii.Space, 12, String_.Repeat(" ", 12)); + } void tst_Add_byte_repeat(byte b, int len, String expd) {Tfds.Eq(expd, bb.Add_byte_repeat(b, len).To_str_and_clear());} + void tst_AddByte(String s, String expdStr, int expdLen) { + if (String_.Len(s) == 1) + bb.Add_byte((byte)String_.CharAt(s, 0)); + else + bb.Add(Bry_.new_u8(s)); + Tfds.Eq(expdStr, String_.new_u8(bb.To_bry())); + Tfds.Eq(expdLen, bb.Bfr_max()); + } + @Test public void Add_dte() { + tst_AddDte("20110801 221435.987"); + } + void tst_AddDte(String raw) { + bb.Add_dte(DateAdp_.parse_fmt(raw, Bry_.Fmt_csvDte)); + Tfds.Eq(raw, String_.new_u8(bb.To_bry())); + } + @Test public void Add_int_variable() { + Add_int_variable(-1); + Add_int_variable(-12); + Add_int_variable(-1234); + Add_int_variable(2); + Add_int_variable(12); + Add_int_variable(1234); + Add_int_variable(123456789); + } + @Test public void Add_float() { + tst_Add_float(1 / 3); + tst_Add_float(-1 / 3); + } + void tst_Add_float(float v) { + bb.Add_float(v); + Tfds.Eq(v, Float_.parse(String_.new_u8(bb.To_bry()))); + } + void Add_int_variable(int val) { + bb.Clear(); + bb.Add_int_variable(val); + Tfds.Eq(val, Int_.Parse(String_.new_u8(bb.To_bry()))); + } + @Test public void Add_int_fixed_len3() {tst_Add_int_fixed(123, 3, "123");} + @Test public void Add_int_fixed_pad_1() {tst_Add_int_fixed(2, 1, "2");} + @Test public void Add_int_fixed_pad_2() {tst_Add_int_fixed(2, 2, "02");} + @Test public void Add_int_fixed_pad_16() {tst_Add_int_fixed(2, 16, "0000000000000002");} // test overflows int + @Test public void Add_int_fixed_neg() {tst_Add_int_fixed(-2, 2, "-2");} + @Test public void Add_int_fixed_neg_pad1() {tst_Add_int_fixed(-2, 1, "-");} + @Test public void Add_int_fixed_chop_1() {tst_Add_int_fixed(123, 1, "3");} + @Test public void Add_int_fixed_chop_neg() {tst_Add_int_fixed(-21, 2, "-1");} + void tst_Add_int_fixed(int val, int digits, String expd) {Tfds.Eq(expd, String_.new_u8(bb.Add_int_fixed(val, digits).To_bry()));} + @Test public void Add_long_fixed_len3() {tst_Add_long_fixed(123, 3, "123");} + @Test public void Add_long_fixed_pad_1() {tst_Add_long_fixed(2, 1, "2");} + @Test public void Add_long_fixed_pad_2() {tst_Add_long_fixed(2, 2, "02");} + @Test public void Add_long_fixed_pad_16() {tst_Add_long_fixed(2, 16, "0000000000000002");} // test overflows long + @Test public void Add_long_fixed_neg() {tst_Add_long_fixed(-2, 2, "-2");} + @Test public void Add_long_fixed_neg_pad1() {tst_Add_long_fixed(-2, 1, "-");} + @Test public void Add_long_fixed_chop_1() {tst_Add_long_fixed(123, 1, "3");} + @Test public void Add_long_fixed_chop_neg() {tst_Add_long_fixed(-21, 2, "-1");} + @Test public void Add_long_fixed_large() {tst_Add_long_fixed(123456789012345L, 15, "123456789012345");} + void tst_Add_long_fixed(long val, int digits, String expd) {Tfds.Eq(expd, String_.new_u8(bb.Add_long_fixed(val, digits).To_bry()));} + @Test public void AddDte_short() { + tst_AddDte_short("2010-08-26T22:38:36Z"); + } + void tst_AddDte_short(String raw) { +// byte[] ary = String_.XtoByteAryAscii(raw); +// Bry_fmtr_IntBldr ib = new Bry_fmtr_IntBldr(); +// int y = 0, m = 0, d = 0, h = 0, n = 0, s = 0, aryLen = ary.length; +// for (int i = 0; i < aryLen; i++) { +// byte b = ary[i]; +// switch (i) { +// case 4: y = ib.To_int_and_clear(); break; +// case 7: m = ib.To_int_and_clear(); break; +// case 10: d = ib.To_int_and_clear(); break; +// case 13: h = ib.To_int_and_clear(); break; +// case 16: n = ib.To_int_and_clear(); break; +// case 19: s = ib.To_int_and_clear(); break; +// default: ib.Add(b); break; +// } +// } +// long l = Pow38_to(y, m, d, h, n, s); +//// Base85_.Set_bry(l, bb. +// bb.Add_int(l); + } +// @Test public void InsertAt_str() { +// tst_InsertAt_str("", 0, "c", "c"); +// tst_InsertAt_str("ab", 0, "c", "cab"); +// tst_InsertAt_str("ab", 0, "cdefghij", "cdefghijab"); +// } +// void tst_InsertAt_str(String orig, int insertAt, String insertStr, String expd) { +// bb = Bry_bfr_.New(16); +// bb.Add_str(orig); +// bb.InsertAt_str(insertAt, insertStr); +// String actl = bb.To_str_and_clear(); +// Tfds.Eq(expd, actl); +// } + @Test public void To_bry_and_clear_and_trim() { + tst_XtoAryAndClearAndTrim("a" , "a"); + tst_XtoAryAndClearAndTrim(" a " , "a"); + tst_XtoAryAndClearAndTrim(" a b " , "a b"); + tst_XtoAryAndClearAndTrim(" " , ""); + } + void tst_XtoAryAndClearAndTrim(String raw, String expd) { + bb.Add_str_u8(raw); + Tfds.Eq(expd, String_.new_u8(bb.To_bry_and_clear_and_trim())); + } + @Test public void XtoInt() { + tst_XtoInt("123", 123); + tst_XtoInt("a", Int_.Min_value); + tst_XtoInt("9999999999", Int_.Min_value); + } + void tst_XtoInt(String raw, int expd) { + bb.Add_str_u8(raw); + Tfds.Eq(expd, bb.To_int_and_clear(Int_.Min_value)); + } + static long Pow38_to(int year, int month, int day, int hour, int minute, int second, int frac) { + return ((long)year) << 26 + | ((long)month & 0x0f) << 22 // 16 + | ((long)day & 0x1f) << 17 // 32 + | ((long)hour & 0x1f) << 12 // 32 + | ((long)minute & 0x3f) << 6 // 64 + | ((long)second & 0x3f) // 64 + ; + } + static DateAdp Pow38_by(long v) { + int year = (int) (v >> 26); + int month = (int)((v >> 22) & 0x0f); + int day = (int)((v >> 17) & 0x1f); + int hour = (int)((v >> 12) & 0x1f); + int minute = (int)((v >> 6) & 0x3f); + int second = (int)((v ) & 0x3f); + return DateAdp_.new_(year, month, day, hour, minute, second, 0); + } + @Test public void Add_bfr_trimEnd_and_clear() { + tst_Add_bfr_trimEnd_and_clear("a ", "a"); + } + void tst_Add_bfr_trimEnd_and_clear(String raw, String expd) { + Bry_bfr tmp = Bry_bfr_.New().Add_str_u8(raw); + Tfds.Eq(expd, bb.Add_bfr_trim_and_clear(tmp, false, true).To_str_and_clear()); + } + @Test public void Add_bfr_trimAll_and_clear() { + tst_Add_bfr_trimAll_and_clear(" a ", "a"); + tst_Add_bfr_trimAll_and_clear(" a b ", "a b"); + tst_Add_bfr_trimAll_and_clear("a", "a"); + tst_Add_bfr_trimAll_and_clear("", ""); + } + void tst_Add_bfr_trimAll_and_clear(String raw, String expd) { + Bry_bfr tmp = Bry_bfr_.New().Add_str_u8(raw); + Tfds.Eq(expd, bb.Add_bfr_trim_and_clear(tmp, true, true).To_str_and_clear()); + } + @Test public void Add_int_pad_bgn() { + fxt.Test_Add_int_pad_bgn(Byte_ascii.Num_0, 3, 0, "000"); + fxt.Test_Add_int_pad_bgn(Byte_ascii.Num_0, 3, 1, "001"); + fxt.Test_Add_int_pad_bgn(Byte_ascii.Num_0, 3, 10, "010"); + fxt.Test_Add_int_pad_bgn(Byte_ascii.Num_0, 3, 100, "100"); + fxt.Test_Add_int_pad_bgn(Byte_ascii.Num_0, 3, 1000, "1000"); + } + @Test public void Add_bry_escape() { + fxt.Test__add_bry_escape("abc" , "abc"); // nothing to escape + fxt.Test__add_bry_escape("a'bc" , "a''bc"); // single escape (code handles first quote differently) + fxt.Test__add_bry_escape("a'b'c" , "a''b''c"); // double escape (code handles subsequent quotes different than first) + fxt.Test__add_bry_escape("abc", 1, 2 , "b"); // nothing to escape + } + @Test public void Add_bry_escape_html() { + fxt.Test__add_bry_escape_html("abc" , "abc"); // escape=none + fxt.Test__add_bry_escape_html("a&\"'<>b" , "a&"'<>b"); // escape=all; code handles first escape differently + fxt.Test__add_bry_escape_html("a&b&c" , "a&b&c"); // staggered; code handles subsequent escapes differently + fxt.Test__add_bry_escape_html("abc", 1, 2 , "b"); // by index; fixes bug in initial implementation + } + @Test public void Insert_at() { + fxt.Test_Insert_at("abcd", 0, "xyz" , "xyzabcd"); // bgn + fxt.Test_Insert_at("abcd", 4, "xyz" , "abcdxyz"); // end + fxt.Test_Insert_at("abcd", 2, "xyz" , "abxyzcd"); // mid + fxt.Test_Insert_at("abcd", 2, "xyz", 1, 2 , "abycd"); // mid + } + @Test public void Delete_rng() { + fxt.Test_Delete_rng("abcd", 0, 2 , "cd"); // bgn + fxt.Test_Delete_rng("abcd", 2, 4 , "ab"); // end + fxt.Test_Delete_rng("abcd", 1, 3 , "ad"); // mid + } + @Test public void Delete_rng_to_bgn() { + fxt.Test_Delete_rng_to_bgn("abcd", 2 , "cd"); + } + @Test public void Delete_rng_to_end() { + fxt.Test_Delete_rng_to_end("abcd", 2 , "ab"); + } + @Test public void To_bry_ary_and_clear() { + fxt.Test__to_bry_ary_and_clear("" ); // empty + fxt.Test__to_bry_ary_and_clear("a" , "a"); // lines=1 + fxt.Test__to_bry_ary_and_clear("a\nb\nc" , "a", "b", "c"); // lines=n + fxt.Test__to_bry_ary_and_clear("a\n" , "a"); // nl at end + } +} +class ByteAryBfr_fxt { + private final Bry_bfr bfr = Bry_bfr_.Reset(16); + public Bry_bfr Bfr() {return bfr;} + public void Clear() { + bfr.ClearAndReset(); + } + public void Test_Add_int_pad_bgn(byte pad_byte, int str_len, int val, String expd) {Tfds.Eq(expd, bfr.Add_int_pad_bgn(pad_byte, str_len, val).To_str_and_clear());} + public void Test__add_bry_escape(String src, String expd) {Test__add_bry_escape(src, 0, String_.Len(src), expd);} + public void Test__add_bry_escape(String src, int src_bgn, int src_end, String expd) { + byte[] val_bry = Bry_.new_u8(src); + Tfds.Eq(expd, bfr.Add_bry_escape(Byte_ascii.Apos, Byte_.Ary(Byte_ascii.Apos, Byte_ascii.Apos), val_bry, src_bgn, src_end).To_str_and_clear()); + } + public void Test_Insert_at(String init, int pos, String val, String expd) {Tfds.Eq(expd, bfr.Add_str_u8(init).Insert_at(pos, Bry_.new_u8(val)).To_str_and_clear());} + public void Test_Insert_at(String init, int pos, String val, int val_bgn, int val_end, String expd) {Tfds.Eq(expd, bfr.Add_str_u8(init).Insert_at(pos, Bry_.new_u8(val), val_bgn, val_end).To_str_and_clear());} + public void Test_Delete_rng(String init, int bgn, int end, String expd) {Tfds.Eq(expd, bfr.Add_str_u8(init).Delete_rng(bgn, end).To_str_and_clear());} + public void Test_Delete_rng_to_bgn(String init, int pos, String expd) {Tfds.Eq(expd, bfr.Add_str_u8(init).Delete_rng_to_bgn(pos).To_str_and_clear());} + public void Test_Delete_rng_to_end(String init, int pos, String expd) {Tfds.Eq(expd, bfr.Add_str_u8(init).Delete_rng_to_end(pos).To_str_and_clear());} + public void Test__to_bry_ary_and_clear(String bfr_str, String... expd) { + Tfds.Eq_ary(expd, String_.Ary(bfr.Add_str_u8(bfr_str).To_bry_ary_and_clear())); + } + public void Test__add_bry_escape_html(String src, String expd) {Test__add_bry_escape_html(src, 0, String_.Len(src), expd);} + public void Test__add_bry_escape_html(String src, int src_bgn, int src_end, String expd) { + Gftest.Eq__bry(Bry_.new_u8(expd), bfr.Add_bry_escape_html(Bry_.new_u8(src), src_bgn, src_end).To_bry_and_clear()); + } +} diff --git a/100_core/src/gplx/Bry_find_.java b/100_core/src/gplx/Bry_find_.java new file mode 100644 index 000000000..5695ea643 --- /dev/null +++ b/100_core/src/gplx/Bry_find_.java @@ -0,0 +1,402 @@ +/* +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; +public class Bry_find_ { + public static final int Not_found = -1; + public static int Find_fwd(byte[] src, byte lkp) {return Find_fwd(src, lkp, 0, src.length);} + public static int Find_fwd(byte[] src, byte lkp, int cur) {return Find_fwd(src, lkp, cur, src.length);} + public static int Find_fwd(byte[] src, byte lkp, int cur, int end) { + for (int i = cur; i < end; i++) + if (src[i] == lkp) return i; + return Bry_find_.Not_found; + } + public static int Find_fwd_or(byte[] src, byte lkp, int cur, int end, int or) { + int rv = Find_fwd(src, lkp, cur, end); + return rv == Bry_find_.Not_found ? or : rv; + } + public static int Find_fwd_or(byte[] src, byte[] lkp, int cur, int end, int or) { + int rv = Find_fwd(src, lkp, cur, end); + return rv == Bry_find_.Not_found ? or : rv; + } + public static int Find_bwd(byte[] src, byte lkp) {return Find_bwd(src, lkp, src.length, 0);} + public static int Find_bwd(byte[] src, byte lkp, int cur) {return Find_bwd(src, lkp, cur , 0);} + public static int Find_bwd(byte[] src, byte lkp, int cur, int end) { + --cur; // always subtract 1 from cur; allows passing in src_len or cur_pos without forcing caller to subtract - 1; DATE:2014-02-11 + --end; + for (int i = cur; i > end; i--) + if (src[i] == lkp) return i; + return Bry_find_.Not_found; + } + 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; + } + public static int Move_fwd(byte[] src, byte[] lkp, int cur) {return Move_fwd(src, lkp, cur, src.length);} + 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 + lkp.length; + } + public static int Find_fwd(byte[] src, byte[] lkp) {return Find(src, lkp, 0 , src.length, true);} + public static int Find_fwd(byte[] src, byte[] lkp, int cur) {return Find(src, lkp, cur , src.length, true);} + public static int Find_fwd(byte[] src, byte[] lkp, int cur, int end) {return Find(src, lkp, cur , end, true);} + private static final int OffsetCompare = 1;// handle srcPos >= 1 -> srcPosChk > 0 + public static int Find(byte[] src, byte[] lkp, int src_bgn, int src_end, boolean fwd) { + if (src_bgn < 0 || src.length == 0) return Bry_find_.Not_found; + int dif, lkp_len = lkp.length, lkp_bgn, lkp_end, src_end_chk; + if (fwd) { + if (src_bgn > src_end) return Bry_find_.Not_found; + dif = 1; lkp_bgn = 0; lkp_end = lkp_len; src_end_chk = src_end - OffsetCompare; + } + else { + if (src_bgn < src_end) return Bry_find_.Not_found; + dif = -1; lkp_bgn = lkp_len - 1; lkp_end = -1; src_end_chk = src.length - OffsetCompare; // src_end_chk needed when going bwd, b/c lkp_len may be > 1 + } + while (src_bgn != src_end) { // while src is not done; + int lkp_cur = lkp_bgn; + while (lkp_cur != lkp_end) { // while lkp is not done + int pos = src_bgn + lkp_cur; + if ( pos > src_end_chk // outside bounds; occurs when lkp_len > 1 + || src[pos] != lkp[lkp_cur]) // srcByte doesn't match lkpByte + break; + else + lkp_cur += dif; + } + if (lkp_cur == lkp_end) return src_bgn; // lkp matches src; exit + src_bgn += dif; + } + return Bry_find_.Not_found; + } + public static int Find_bwd(byte[] src, byte[] lkp, int cur) {return Find_bwd(src, lkp, cur , 0);} + public static int Find_bwd(byte[] src, byte[] lkp, int cur, int end) { + if (cur < 1) return Bry_find_.Not_found; + --cur; // always subtract 1 from cur; allows passing in src_len or cur_pos without forcing caller to subtract - 1; DATE:2014-02-11 + --end; + int src_len = src.length; + int lkp_len = lkp.length; + for (int i = cur; i > end; i--) { + if (i + lkp_len > src_len) continue; // lkp too small for pos; EX: src=abcde; lkp=bcd; pos=4 + boolean match = true; + for (int j = 0; j < lkp_len; j++) { + if (lkp[j] != src[i + j]) { + match = false; + break; + } + } + if (match) return i; + } + return Bry_find_.Not_found; + } + public static int Find_bwd_last_ws(byte[] src, int cur) { + if (cur < 1) return Bry_find_.Not_found; + --cur; + int rv = Bry_find_.Not_found; + for (int i = cur; i > -1; i--) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: + rv = i; + break; + default: + i = -1; + break; + } + } + return rv; + } + public static int Find_bwd_ws(byte[] src, int cur, int end) { + for (int i = cur; i > -1; --i) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: + return i; + } + } + return Bry_find_.Not_found; + } + public static int Find_fwd_last_ws(byte[] src, int cur) { + int end = src.length; + if (cur >= end) return Bry_find_.Not_found; + int rv = Bry_find_.Not_found; + for (int i = cur; i < end; i++) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: + rv = i; + break; + default: + i = -1; + break; + } + } + return rv; + } + public static int Find_bwd_non_ws_or_not_found(byte[] src, int cur, int end) { // get pos of 1st char that is not ws; + if (cur >= src.length) return Bry_find_.Not_found; + for (int i = cur; i >= end; i--) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: + break; + default: + return i; + } + } + return Bry_find_.Not_found; + } + public static int Find_bwd__while_space_or_tab(byte[] src, int cur, int end) { // get pos of 1st char that is not \t or \s + if (cur < 0 || cur > src.length) return Bry_find_.Not_found; + for (int i = cur - 1; i >= end; i--) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Space: case Byte_ascii.Tab: + break; + default: + return i + 1; + } + } + return Bry_find_.Not_found; + } + public static int Find_bwd_non_ws_or_end(byte[] src, int cur, int end) { + if (cur >= src.length) return Bry_find_.Not_found; + for (int i = cur; i >= end; i--) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: + break; + default: + return i; + } + } + return end; + } + public static int Find_bwd__skip_ws(byte[] src, int end, int bgn) { + int src_len = src.length; + if (end == src_len) return end; + if (end > src_len || end < 0) return Bry_find_.Not_found; + int pos = end - 1; // start from end - 1; handles situations where len is passed in + for (int i = pos; i >= bgn; --i) { + switch (src[i]) { + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: + break; + default: + return i + 1; + } + } + return bgn; + } + public static int Find_bwd__skip(byte[] src, int end, int bgn, byte skip) { + int src_len = src.length; // if (end == src_len) return end; + if (end > src_len || end < 0) return Bry_find_.Not_found; + int pos = end - 1; // start from end - 1; handles situations where len is passed in + for (int i = pos; i >= bgn; --i) { + if (src[i] != skip) + return i + 1; + } + return bgn; + } + public static int Find_bwd__skip(byte[] src, int end, int bgn, byte[] skip) { + int src_len = src.length; + if (end > src_len || end < 0) return Bry_find_.Not_found; + int skip_len = skip.length; + int pos = end - skip_len; // start from end - 1; handles situations where len is passed in + for (int i = pos; i >= bgn; --i) { + if (!Bry_.Eq(src, i, i + skip_len, skip)) + return i + skip_len; + } + return bgn; + } + public static int Find_bwd_while(byte[] src, int cur, int end, byte while_byte) { + --cur; + while (true) { + if ( cur < end + || src[cur] != while_byte) return cur; + --cur; + } + } + public static int Find_bwd_while_v2(byte[] src, int cur, int end, byte while_byte) { + --cur; + while (true) { + if ( cur < end + || src[cur] != while_byte) return cur + 1; + --cur; + } + } + public static int Find_fwd_while(byte[] src, int cur, int end, byte while_byte) { + while (true) { + if ( cur == end + || src[cur] != while_byte) return cur; + cur++; + } + } + public static int Find_fwd_while(byte[] src, int cur, int end, byte[] while_bry) { + int while_len = while_bry.length; + while (true) { + if (cur == end) return cur; + for (int i = 0; i < while_len; i++) { + if (while_bry[i] != src[i + cur]) return cur; + } + cur += while_len; + } + } + public static int Find_fwd_while_in(byte[] src, int cur, int end, boolean[] while_ary) { + while (cur < end) { + if (cur == end || !while_ary[src[cur]]) return cur; + cur++; + } + return end; + } + public static int Find_fwd_until(byte[] src, int cur, int end, byte until_byte) { + while (true) { + if ( cur == end + || src[cur] == until_byte) return cur; + cur++; + } + } + public static int Find_fwd_until_space_or_tab(byte[] src, int cur, int end) { + while (true) { + if (cur == end) return Bry_find_.Not_found; + switch (src[cur]) { + case Byte_ascii.Space: case Byte_ascii.Tab: + return cur; + default: + ++cur; + break; + } + } + } + public static int Find_fwd_until_ws(byte[] src, int cur, int end) { + while (true) { + if (cur == end) return Bry_find_.Not_found; + switch (src[cur]) { + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: + return cur; + default: + ++cur; + break; + } + } + } + public static int Find_fwd_while_space_or_tab(byte[] src, int cur, int end) { + while (true) { + if (cur == end) return cur; + switch (src[cur]) { + case Byte_ascii.Space: case Byte_ascii.Tab: ++cur; break; + default: return cur; + } + } + } + public static int Trim_fwd_space_tab(byte[] src, int cur, int end) { + while (true) { + if (cur == end) return cur; + switch (src[cur]) { + case Byte_ascii.Space: case Byte_ascii.Tab: ++cur; break; + default: return cur; + } + } + } + public static int Trim_bwd_space_tab(byte[] src, int cur, int bgn) { + while (true) { + int prv_cur = cur - 1; // check byte before cur; EX: "a b " will have len of 4, and pass cur=4; + if (prv_cur < bgn) return cur; // checking byte before prv; exit; + switch (src[prv_cur]) { + case Byte_ascii.Space: case Byte_ascii.Tab: --cur; break; + default: return cur; + } + } + } + public static int Find_fwd_while_ws(byte[] src, int cur, int end) { + while (true) { + if (cur == end) return cur; + try { + switch (src[cur]) { + case Byte_ascii.Nl: case Byte_ascii.Cr: + case Byte_ascii.Space: case Byte_ascii.Tab: ++cur; break; + default: return cur; + } + } catch (Exception e) {throw Err_.new_exc(e, "core", "idx is invalid", "cur", cur, "src", src);} + } + } + public static int Find_fwd_while_letter(byte[] src, int cur, int end) { + while (cur < end) { + switch (src[cur]) { + case Byte_ascii.Ltr_A: case Byte_ascii.Ltr_B: case Byte_ascii.Ltr_C: case Byte_ascii.Ltr_D: case Byte_ascii.Ltr_E: + case Byte_ascii.Ltr_F: case Byte_ascii.Ltr_G: case Byte_ascii.Ltr_H: case Byte_ascii.Ltr_I: case Byte_ascii.Ltr_J: + case Byte_ascii.Ltr_K: case Byte_ascii.Ltr_L: case Byte_ascii.Ltr_M: case Byte_ascii.Ltr_N: case Byte_ascii.Ltr_O: + case Byte_ascii.Ltr_P: case Byte_ascii.Ltr_Q: case Byte_ascii.Ltr_R: case Byte_ascii.Ltr_S: case Byte_ascii.Ltr_T: + case Byte_ascii.Ltr_U: case Byte_ascii.Ltr_V: case Byte_ascii.Ltr_W: case Byte_ascii.Ltr_X: case Byte_ascii.Ltr_Y: case Byte_ascii.Ltr_Z: + case Byte_ascii.Ltr_a: case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_e: + case Byte_ascii.Ltr_f: case Byte_ascii.Ltr_g: case Byte_ascii.Ltr_h: case Byte_ascii.Ltr_i: case Byte_ascii.Ltr_j: + case Byte_ascii.Ltr_k: case Byte_ascii.Ltr_l: case Byte_ascii.Ltr_m: case Byte_ascii.Ltr_n: case Byte_ascii.Ltr_o: + case Byte_ascii.Ltr_p: case Byte_ascii.Ltr_q: case Byte_ascii.Ltr_r: case Byte_ascii.Ltr_s: case Byte_ascii.Ltr_t: + case Byte_ascii.Ltr_u: case Byte_ascii.Ltr_v: case Byte_ascii.Ltr_w: case Byte_ascii.Ltr_x: case Byte_ascii.Ltr_y: case Byte_ascii.Ltr_z: + break; + default: + return cur; + } + ++cur; + } + return cur; + } + public static int Find_fwd_while_num(byte[] src) {return Find_fwd_while_num(src, 0, src.length);} + public static int Find_fwd_while_num(byte[] src, int cur, int end) { + while (cur < end) { + if (!Byte_ascii.Is_num(src[cur])) + return cur; + ++cur; + } + return cur; + } + public static int Find_fwd_while_not_ws(byte[] src, int cur, int end) { + while (true) { + if (cur == end) return cur; + switch (src[cur]) { + case Byte_ascii.Space: + case Byte_ascii.Nl: + case Byte_ascii.Tab: + case Byte_ascii.Cr: + ++cur; + break; + default: + return cur; + } + } + } + public static int Find_bwd_while_alphanum(byte[] src, int cur) {return Find_bwd_while_alphanum(src, cur, -1);} + public static int Find_bwd_while_alphanum(byte[] src, int cur, int end) { + --cur; + while (cur > end) { + switch (src[cur]) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + case Byte_ascii.Ltr_A: case Byte_ascii.Ltr_B: case Byte_ascii.Ltr_C: case Byte_ascii.Ltr_D: case Byte_ascii.Ltr_E: + case Byte_ascii.Ltr_F: case Byte_ascii.Ltr_G: case Byte_ascii.Ltr_H: case Byte_ascii.Ltr_I: case Byte_ascii.Ltr_J: + case Byte_ascii.Ltr_K: case Byte_ascii.Ltr_L: case Byte_ascii.Ltr_M: case Byte_ascii.Ltr_N: case Byte_ascii.Ltr_O: + case Byte_ascii.Ltr_P: case Byte_ascii.Ltr_Q: case Byte_ascii.Ltr_R: case Byte_ascii.Ltr_S: case Byte_ascii.Ltr_T: + case Byte_ascii.Ltr_U: case Byte_ascii.Ltr_V: case Byte_ascii.Ltr_W: case Byte_ascii.Ltr_X: case Byte_ascii.Ltr_Y: case Byte_ascii.Ltr_Z: + case Byte_ascii.Ltr_a: case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_e: + case Byte_ascii.Ltr_f: case Byte_ascii.Ltr_g: case Byte_ascii.Ltr_h: case Byte_ascii.Ltr_i: case Byte_ascii.Ltr_j: + case Byte_ascii.Ltr_k: case Byte_ascii.Ltr_l: case Byte_ascii.Ltr_m: case Byte_ascii.Ltr_n: case Byte_ascii.Ltr_o: + case Byte_ascii.Ltr_p: case Byte_ascii.Ltr_q: case Byte_ascii.Ltr_r: case Byte_ascii.Ltr_s: case Byte_ascii.Ltr_t: + case Byte_ascii.Ltr_u: case Byte_ascii.Ltr_v: case Byte_ascii.Ltr_w: case Byte_ascii.Ltr_x: case Byte_ascii.Ltr_y: case Byte_ascii.Ltr_z: + --cur; + break; + default: + return cur; + } + } + return 0; // always return a valid index + } +} diff --git a/100_core/src/gplx/Bry_find__tst.java b/100_core/src/gplx/Bry_find__tst.java new file mode 100644 index 000000000..38fa5893b --- /dev/null +++ b/100_core/src/gplx/Bry_find__tst.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; +import org.junit.*; import gplx.core.tests.*; +public class Bry_find__tst { + private Bry_find__fxt fxt = new Bry_find__fxt(); + @Test public void Find_fwd() { + fxt.Test_Find_fwd("abcba", "b", 0, 1); + fxt.Test_Find_fwd("abcba", "z", 0, -1); + fxt.Test_Find_fwd("abcba", "b", 1, 1); + fxt.Test_Find_fwd("abcba", "b", 2, 3); + fxt.Test_Find_fwd("abcba", "b", 4, -1); + fxt.Test_Find_fwd("abcba", "zb", 4, -1); + fxt.Test_Find_fwd("abcba", "a", 6, -1); + } + @Test public void Find_bwd() { + fxt.Test_Find_bwd("abcba", "b", 4, 3); + fxt.Test_Find_bwd("abcba", "z", 4, -1); + fxt.Test_Find_bwd("abcba", "b", 3, 1); + fxt.Test_Find_bwd("abcba", "b", 2, 1); + fxt.Test_Find_bwd("abcba", "b", 0, -1); + fxt.Test_Find_bwd("abcba", "zb", 4, -1); + fxt.Test_Find_fwd("abcba", "a", -1, -1); + fxt.Test_Find_bwd("abcba", "ab", 4, 0); + } + @Test public void Find_bwd_last_ws() { + fxt.Test_Find_bwd_1st_ws_tst("a b" , 2, 1); // basic + fxt.Test_Find_bwd_1st_ws_tst("a b" , 3, 1); // multiple + fxt.Test_Find_bwd_1st_ws_tst("ab" , 1, Bry_find_.Not_found); // none + } + @Test public void Trim_fwd_space_tab() { + fxt.Test_Trim_fwd_space_tab(" a b" , 1); + fxt.Test_Trim_fwd_space_tab("\ta b" , 1); + fxt.Test_Trim_fwd_space_tab(" \ta b" , 2); + fxt.Test_Trim_fwd_space_tab("a bc" , 0); + fxt.Test_Trim_fwd_space_tab("" , 0); + fxt.Test_Trim_fwd_space_tab(" \t" , 2); + } + @Test public void Trim_bwd_space_tab() { + fxt.Test_Trim_bwd_space_tab("a b " , 3); + fxt.Test_Trim_bwd_space_tab("a b\t" , 3); + fxt.Test_Trim_bwd_space_tab("a b\t " , 3); + fxt.Test_Trim_bwd_space_tab("a bc" , 4); + fxt.Test_Trim_bwd_space_tab("" , 0); + fxt.Test_Trim_bwd_space_tab(" \t" , 0); + } + @Test public void Find_fwd_while_in() { + boolean[] while_ary = fxt.Init__find_fwd_while_in(Byte_ascii.Space, Byte_ascii.Tab, Byte_ascii.Nl); + fxt.Test__find_fwd_while_in(" \t\na", while_ary, 3); + } +} +class Bry_find__fxt { + public void Test_Find_fwd(String src, String lkp, int bgn, int expd) {Tfds.Eq(expd, Bry_find_.Find_fwd(Bry_.new_u8(src), Bry_.new_u8(lkp), bgn));} + public void Test_Find_bwd(String src, String lkp, int bgn, int expd) {Tfds.Eq(expd, Bry_find_.Find_bwd(Bry_.new_u8(src), Bry_.new_u8(lkp), bgn));} + public void Test_Find_bwd_1st_ws_tst(String src, int pos, int expd) {Tfds.Eq(expd, Bry_find_.Find_bwd_last_ws(Bry_.new_a7(src), pos));} + public void Test_Trim_bwd_space_tab(String raw_str, int expd) { + byte[] raw_bry = Bry_.new_u8(raw_str); + int actl = Bry_find_.Trim_bwd_space_tab(raw_bry, raw_bry.length, 0); + Tfds.Eq(expd, actl, raw_str); + } + public void Test_Trim_fwd_space_tab(String raw_str, int expd) { + byte[] raw_bry = Bry_.new_u8(raw_str); + int actl = Bry_find_.Trim_fwd_space_tab(raw_bry, 0, raw_bry.length); + Tfds.Eq(expd, actl, raw_str); + } + public boolean[] Init__find_fwd_while_in(byte... ary) { + boolean[] rv = new boolean[256]; + int len = ary.length; + for (int i = 0; i < len; i++) + rv[ary[i]] = true; + return rv; + } + public void Test__find_fwd_while_in(String src, boolean[] ary, int expd) { + byte[] src_bry = Bry_.new_u8(src); + Gftest.Eq__int(expd, Bry_find_.Find_fwd_while_in(src_bry, 0, src_bry.length, ary)); + } +} diff --git a/100_core/src/gplx/Bry_fmt.java b/100_core/src/gplx/Bry_fmt.java new file mode 100644 index 000000000..ea3b72e0e --- /dev/null +++ b/100_core/src/gplx/Bry_fmt.java @@ -0,0 +1,87 @@ +/* +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; +import gplx.core.brys.*; import gplx.core.brys.fmts.*; +public class Bry_fmt { + private final Object thread_lock = new Object(); + private byte[] src; + private Bry_fmt_itm[] itms; private int itms_len; + private Bfr_fmt_arg[] args = Bfr_fmt_arg.Ary_empty; + private byte[][] keys = Bry_.Ary_empty; + private Object[] vals = null; + private boolean dirty; + public Bry_fmt(byte[] src, byte[][] keys, Bfr_fmt_arg[] args) { + dirty = true; + this.src = src; this.keys = keys; this.args = args; + } + public byte[] Fmt() {return src;} + public Bry_fmt Fmt_(String v) {return Fmt_(Bry_.new_u8(v));} + public Bry_fmt Fmt_(byte[] v) {dirty = true; src = v; return this;} + public Bry_fmt Args_(Bfr_fmt_arg... v) {dirty = true; args = v; return this;} + public Bry_fmt Keys_(String... v) {return Keys_(Bry_.Ary(v));} + public Bry_fmt Keys_(byte[]... v) {dirty = true; keys = v; return this;} + public Bry_fmt Vals_(Object... v) {vals = v; return this;} + public String Bld_many_to_str_auto_bfr(Object... vals_ary) { + Bry_bfr bfr = Bry_bfr_.Get(); + try {return Bld_many_to_str(bfr, vals_ary);} + finally {bfr.Mkr_rls();} + } + public String Bld_many_to_str(Bry_bfr bfr, Object... vals_ary) { + Bld_many(bfr, vals_ary); + return bfr.To_str_and_clear(); + } + public byte[] Bld_many_to_bry(Bry_bfr bfr, Object... vals_ary) { + Bld_many(bfr, vals_ary); + return bfr.To_bry_and_clear(); + } + public void Bld_many(Bry_bfr bfr, Object... vals_ary) { + if (dirty) Compile(); + int vals_len = vals_ary.length; + for (int i = 0; i < itms_len; ++i) { + Bry_fmt_itm itm = itms[i]; + switch (itm.Tid) { + case Bry_fmt_itm.Tid__txt: bfr.Add_mid(src, itm.Src_bgn, itm.Src_end); break; + case Bry_fmt_itm.Tid__arg: itm.Arg.Bfr_arg__add(bfr);break; + case Bry_fmt_itm.Tid__key: + int idx = itm.Key_idx; + if (idx > -1 && idx < vals_len) + bfr.Add_obj(vals_ary[idx]); + else + bfr.Add_mid(src, itm.Src_bgn, itm.Src_end); + break; + default: throw Err_.new_unhandled(itm.Tid); + } + } + } + public String To_str() {return Bld_many_to_str_auto_bfr(vals);} + private void Compile() { + synchronized (thread_lock) { + dirty = false; + this.itms = Bry_fmt_parser_.Parse(Byte_ascii.Tilde, Byte_ascii.Curly_bgn, Byte_ascii.Curly_end, args, keys, src); + this.itms_len = itms.length; + } + } + public static Bry_fmt New(String fmt, String... keys) {return new Bry_fmt(Bry_.new_u8(fmt), Bry_.Ary(keys), Bfr_fmt_arg.Ary_empty);} + public static Bry_fmt New(byte[] fmt, String... keys) {return new Bry_fmt(fmt , Bry_.Ary(keys), Bfr_fmt_arg.Ary_empty);} + public static String Make_str(String fmt_str, Object... vals) {return Auto(fmt_str).Vals_(vals).To_str();} + public static Bry_fmt Auto_nl_apos(String... lines) {return Auto(Bry_.New_u8_nl_apos(lines));} + public static Bry_fmt Auto_nl_skip_last(String... lines) {return Auto(Bry_.new_u8(String_.Concat_lines_nl_skip_last(lines)));} + public static Bry_fmt Auto(String fmt_str) {return Auto(Bry_.new_u8(fmt_str));} + public static Bry_fmt Auto(byte[] fmt_bry) { + byte[][] keys_bry = Bry_fmt_parser_.Parse_keys(fmt_bry); + return new Bry_fmt(fmt_bry, keys_bry, Bfr_fmt_arg.Ary_empty); + } +} diff --git a/100_core/src/gplx/Bry_split_.java b/100_core/src/gplx/Bry_split_.java new file mode 100644 index 000000000..b0a0b433f --- /dev/null +++ b/100_core/src/gplx/Bry_split_.java @@ -0,0 +1,155 @@ +/* +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; +import gplx.core.brys.*; +public class Bry_split_ { + private static final Object thread_lock = new Object(); + public static byte[][] Split(byte[] src, byte dlm) {return Split(src, dlm, false);} + public static byte[][] Split(byte[] src, byte dlm, boolean trim) {return src == null ? Bry_.Ary_empty : Split(src, 0, src.length, dlm, trim);} + public static byte[][] Split(byte[] src, int bgn, int end, byte dlm, boolean trim) { + synchronized (thread_lock) { + Bry_split_wkr__to_ary wkr = Bry_split_wkr__to_ary.Instance; + Split(src, bgn, end, dlm, trim, wkr); + return wkr.To_ary(); + } + } + public static int Split(byte[] src, int src_bgn, int src_end, byte dlm, boolean trim, Bry_split_wkr wkr) { + if (src == null || src_end - src_bgn < 1) return 0; + int pos = src_bgn; + int itm_bgn = -1, itm_end = -1; + int count = 0; + while (true) { + boolean pos_is_last = pos == src_end; + byte b = pos_is_last ? dlm : src[pos]; + int nxt_pos = pos + 1; + boolean process = true; + switch (b) { + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: // ignore ws; assumes that flags have no ws (they are single char) and vnts have no ws (EX: zh-hans) + if (trim && b != dlm) process = false; // b != dlm handles cases where ws is dlm, but trim is enabled; EX: " a \n b" -> "a", "b" + break; + } + if (process) { + if (b == dlm) { + boolean reset = true; + if (itm_bgn == -1) { + if (pos_is_last) {} // skip dlm at bgn / end; EX: "a," + else {wkr.Split(src, pos, pos );} // else, process "empty" dlm; EX: ",a" + } + else { + int rv = wkr.Split(src, itm_bgn, itm_end); + switch (rv) { + case Rv__ok: ++count; break; + case Rv__extend: reset = false; break; + case Rv__cancel: return count; + default: throw Err_.new_unhandled(rv); + } + } + if (reset) itm_bgn = itm_end = -1; + } + else { + if (itm_bgn == -1) itm_bgn = pos; + itm_end = nxt_pos; + } + } + if (pos_is_last) break; + pos = nxt_pos; + } + return count; + } + public static byte[][] Split(byte[] src, byte[] dlm) {return Split(src, 0, src.length, dlm);} + public static byte[][] Split(byte[] src, int src_bgn, int src_end, byte[] dlm) { + if (src == null) return Bry_.Ary_empty; + int src_len = src.length; + if (src_len == 0) return Bry_.Ary_empty; + int cur_pos = src_bgn, dlm_len = dlm.length; + List_adp rv = List_adp_.New(); + while (true) { + int find_pos = Bry_find_.Find_fwd(src, dlm, cur_pos); + if (find_pos == Bry_find_.Not_found) { + if (cur_pos >= src_end) break; // dlm is last sequence in src; do not create empty itm + find_pos = src_end; + } + rv.Add(Bry_.Mid(src, cur_pos, find_pos)); + cur_pos = find_pos + dlm_len; + if (cur_pos >= src_end) break; + } + return (byte[][])rv.To_ary(byte[].class); + } + public static byte[][] Split_lines(byte[] src) { + if (Bry_.Len_eq_0(src)) return Bry_.Ary_empty; + int src_len = src.length, src_pos = 0, fld_bgn = 0; + List_adp rv = List_adp_.New(); + while (true) { + boolean last = src_pos == src_len; + byte b = last ? Byte_ascii.Nl : src[src_pos]; + int nxt_bgn = src_pos + 1; + switch (b) { + case Byte_ascii.Cr: + case Byte_ascii.Nl: + if ( b == Byte_ascii.Cr // check for crlf + && nxt_bgn < src_len && src[nxt_bgn] == Byte_ascii.Nl) { + ++nxt_bgn; + } + if (last && (src_pos - fld_bgn == 0)) {} // ignore trailing itms + else + rv.Add(Bry_.Mid(src, fld_bgn, src_pos)); + fld_bgn = nxt_bgn; + break; + } + if (last) break; + src_pos = nxt_bgn; + } + return (byte[][])rv.To_ary(byte[].class); + } + public static byte[][] Split_w_max(byte[] src, byte dlm, int max) { + byte[][] rv = new byte[max][]; + int src_len = src.length; + int rv_idx = 0; + int itm_bgn = 0; + int src_pos = 0; + while (true) { + boolean is_last = src_pos == src_len; + byte b = is_last ? dlm : src[src_pos]; + if (b == dlm) { + rv[rv_idx++] = Bry_.Mid(src, itm_bgn, src_pos); + itm_bgn = src_pos + 1; + } + if (is_last || rv_idx == max) + break; + else + src_pos++; + } + return rv; + } + + public static final int Rv__ok = 0, Rv__extend = 1, Rv__cancel = 2; +} +class Bry_split_wkr__to_ary implements gplx.core.brys.Bry_split_wkr { + private final List_adp list = List_adp_.New(); + public int Split(byte[] src, int itm_bgn, int itm_end) { + synchronized (list) { + byte[] bry = itm_end == itm_bgn ? Bry_.Empty : Bry_.Mid(src, itm_bgn, itm_end); + list.Add(bry); + return Bry_split_.Rv__ok; + } + } + public byte[][] To_ary() { + synchronized (list) { + return (byte[][])list.To_ary_and_clear(byte[].class); + } + } + public static final Bry_split_wkr__to_ary Instance = new Bry_split_wkr__to_ary(); Bry_split_wkr__to_ary() {} +} diff --git a/100_core/src/gplx/Bry_split__tst.java b/100_core/src/gplx/Bry_split__tst.java new file mode 100644 index 000000000..c6fb53018 --- /dev/null +++ b/100_core/src/gplx/Bry_split__tst.java @@ -0,0 +1,80 @@ +/* +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; +import org.junit.*; import gplx.core.tests.*; +public class Bry_split__tst { + private final Bry_split__fxt fxt = new Bry_split__fxt(); + @Test public void Split() { + fxt.Test_split("a" , Byte_ascii.Pipe, Bool_.N, "a"); // no trim + fxt.Test_split("a|" , Byte_ascii.Pipe, Bool_.N, "a"); + fxt.Test_split("|a" , Byte_ascii.Pipe, Bool_.N, "", "a"); + fxt.Test_split("|" , Byte_ascii.Pipe, Bool_.N, ""); + fxt.Test_split("" , Byte_ascii.Pipe, Bool_.N); + fxt.Test_split("a|b|c" , Byte_ascii.Pipe, Bool_.N, "a", "b", "c"); + fxt.Test_split(" a " , Byte_ascii.Pipe, Bool_.Y, "a"); // trim + fxt.Test_split(" a |" , Byte_ascii.Pipe, Bool_.Y, "a"); + fxt.Test_split("| a " , Byte_ascii.Pipe, Bool_.Y, "", "a"); + fxt.Test_split(" | " , Byte_ascii.Pipe, Bool_.Y, ""); + fxt.Test_split(" " , Byte_ascii.Pipe, Bool_.Y); + fxt.Test_split(" a | b | c " , Byte_ascii.Pipe, Bool_.Y, "a", "b", "c"); + fxt.Test_split(" a b | c d " , Byte_ascii.Pipe, Bool_.Y, "a b", "c d"); + fxt.Test_split(" a \n b " , Byte_ascii.Nl , Bool_.N, " a ", " b "); // ws as dlm + fxt.Test_split(" a \n b " , Byte_ascii.Nl , Bool_.Y, "a", "b"); // ws as dlm; trim + fxt.Test_split("a|extend|b" , Byte_ascii.Pipe, Bool_.Y, "a", "extend|b"); // extend + fxt.Test_split("extend|a" , Byte_ascii.Pipe, Bool_.Y, "extend|a"); // extend + fxt.Test_split("a|cancel|b" , Byte_ascii.Pipe, Bool_.Y, "a"); // cancel + } + @Test public void Split__bry() { + fxt.Test_split("a|b|c|d" , 2, 6, "|", "b", "c"); + fxt.Test_split("a|b|c|d" , 2, 4, "|", "b"); + } + @Test public void Empty() { + fxt.Test_split("a\n\nb" , Byte_ascii.Nl, Bool_.N, "a", "", "b"); + } + @Test public void Split_w_max() { + fxt.Test__split_w_max("a|b|c|d" , Byte_ascii.Pipe, 2, "a", "b"); // max is less + fxt.Test__split_w_max("a" , Byte_ascii.Pipe, 2, "a", null); // max is more + fxt.Test__split_w_max("|" , Byte_ascii.Pipe, 2, "", ""); // empty itms + } +} +class Bry_split__fxt { + private final Bry_split_wkr__example wkr = new Bry_split_wkr__example(); + public void Test_split(String raw_str, byte dlm, boolean trim, String... expd) { + byte[] src = Bry_.new_a7(raw_str); + Bry_split_.Split(src, 0, src.length, dlm, trim, wkr); + byte[][] actl_ary = wkr.To_ary(); + Tfds.Eq_ary_str(expd, String_.Ary(actl_ary)); + } + public void Test_split(String src, int src_bgn, int src_end, String dlm, String... expd) { + Tfds.Eq_ary_str(Bry_.Ary(expd), Bry_split_.Split(Bry_.new_u8(src), src_bgn, src_end, Bry_.new_u8(dlm))); + } + public void Test__split_w_max(String src, byte dlm, int max, String... expd) { + Gftest.Eq__ary(expd, String_.Ary(Bry_split_.Split_w_max(Bry_.new_u8(src), dlm, max))); + } +} +class Bry_split_wkr__example implements gplx.core.brys.Bry_split_wkr { + private final List_adp list = List_adp_.New(); + public int Split(byte[] src, int itm_bgn, int itm_end) { + byte[] bry = itm_end == itm_bgn ? Bry_.Empty : Bry_.Mid(src, itm_bgn, itm_end); + if (Bry_.Eq(bry, Bry_.new_a7("extend"))) return Bry_split_.Rv__extend; + else if (Bry_.Eq(bry, Bry_.new_a7("cancel"))) return Bry_split_.Rv__cancel; + list.Add(bry); + return Bry_split_.Rv__ok; + } + public byte[][] To_ary() { + return (byte[][])list.To_ary_and_clear(byte[].class); + } +} diff --git a/100_core/src/gplx/Byte_.java b/100_core/src/gplx/Byte_.java new file mode 100644 index 000000000..042b073d4 --- /dev/null +++ b/100_core/src/gplx/Byte_.java @@ -0,0 +1,68 @@ +/* +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; +public class Byte_ {//RF:2017-10-08 + public static final String Cls_val_name = "byte"; + public static final Class Cls_ref_type = Byte.class; + + public static final byte + Zero = 0 + , Min_value = Byte.MIN_VALUE + , Max_value_127 = 127 + , Val_128 = -128 + , Val_255 = -1 + ; + + public static byte By_int(int v) {return v > 127 ? (byte)(v - 256) : (byte)v;} // PERF?: (byte)(v & 0xff) + public static int To_int(byte v) {return v < 0 ? (int)v + 256 : v;} + public static String To_str(byte v) {return new Byte(v).toString();} + public static byte[] To_bry(byte v) {return new byte[] {v};} + + public static byte Cast(Object o) {try {return (Byte)o;} catch (Exception e) {throw Err_.new_type_mismatch_w_exc(e, byte.class, o);}} + public static byte Parse(String raw) {return Byte.parseByte(raw);} + public static byte Parse_or(String raw, byte or) { + if (raw == null) return or; + try {return Parse(raw);} + catch (Exception e) {Err_.Noop(e); return or;} + } + + public static boolean Match_any(byte v, byte... ary) { + for (byte itm : ary) + if (v == itm) return true; + return false; + } + public static boolean Match_all(byte v, byte... ary) { + for (byte itm : ary) + if (v != itm) return false; + return true; + } + + public static int Compare(byte lhs, byte rhs) { + if (lhs == rhs) return CompareAble_.Same; + else if (lhs < rhs) return CompareAble_.Less; + else return CompareAble_.More; + } + + public static byte[] Ary(byte... ary) {return ary;} + public static byte[] Ary_by_ints(int... ary) { + int ary_len = ary.length; + byte[] rv = new byte[ary_len]; + for (int i = 0; i < ary_len; i++) { + rv[i] = By_int(ary[i]); + } + return rv; + } +} diff --git a/100_core/src/gplx/Byte__tst.java b/100_core/src/gplx/Byte__tst.java new file mode 100644 index 000000000..e6d1194ae --- /dev/null +++ b/100_core/src/gplx/Byte__tst.java @@ -0,0 +1,33 @@ +/* +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; +import org.junit.*; +public class Byte__tst { + @Test public void int_() { + tst_int_( 0, 0); + tst_int_( 127, 127); + tst_int_( 128, 128); // NOTE: JAVA defines byte as -128 -> 127 + tst_int_( 255, 255); + tst_int_( 256, 0); // NOTE: 256 will cast to 1; (byte)256 works same in both JAVA/.NET + } void tst_int_(int v, int expd) {Tfds.Eq((byte)expd, Byte_.By_int(v));} // WORKAROUND/JAVA: expd is of type int b/c java promotes numbers to ints + @Test public void To_int() { + tst_XtoInt( 0, 0); + tst_XtoInt( 127, 127); + tst_XtoInt( 128, 128); + tst_XtoInt( 255, 255); + tst_XtoInt( 256, 0); + } void tst_XtoInt(int v, int expd) {Tfds.Eq(expd, Byte_.To_int((byte)v));} // WORKAROUND/JAVA: v is of type int b/c java promotes numbers to ints +} diff --git a/100_core/src/gplx/Byte_ascii.java b/100_core/src/gplx/Byte_ascii.java new file mode 100644 index 000000000..29d1275eb --- /dev/null +++ b/100_core/src/gplx/Byte_ascii.java @@ -0,0 +1,129 @@ +/* +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; +public class Byte_ascii { + public static final byte + Null = 0 , Backfeed = 8, Tab = 9 + , Nl = 10, Formfeed = 12, Cr = 13 + , Escape = 27 + , Space = 32, Bang = 33, Quote = 34 + , Hash = 35, Dollar = 36, Percent = 37, Amp = 38, Apos = 39 + , Paren_bgn = 40, Paren_end = 41, Star = 42, Plus = 43, Comma = 44 + , Dash = 45, Dot = 46, Slash = 47, Num_0 = 48, Num_1 = 49 + , Num_2 = 50, Num_3 = 51, Num_4 = 52, Num_5 = 53, Num_6 = 54 + , Num_7 = 55, Num_8 = 56, Num_9 = 57, Colon = 58, Semic = 59 + , Lt = 60, Eq = 61, Gt = 62, Question = 63, At = 64 + , Ltr_A = 65, Ltr_B = 66, Ltr_C = 67, Ltr_D = 68, Ltr_E = 69 + , Ltr_F = 70, Ltr_G = 71, Ltr_H = 72, Ltr_I = 73, Ltr_J = 74 + , Ltr_K = 75, Ltr_L = 76, Ltr_M = 77, Ltr_N = 78, Ltr_O = 79 + , Ltr_P = 80, Ltr_Q = 81, Ltr_R = 82, Ltr_S = 83, Ltr_T = 84 + , Ltr_U = 85, Ltr_V = 86, Ltr_W = 87, Ltr_X = 88, Ltr_Y = 89 + , Ltr_Z = 90, Brack_bgn = 91, Backslash = 92, Brack_end = 93, Pow = 94 // Circumflex + , Underline = 95, Tick = 96, Ltr_a = 97, Ltr_b = 98, Ltr_c = 99 + , Ltr_d = 100, Ltr_e = 101, Ltr_f = 102, Ltr_g = 103, Ltr_h = 104 + , Ltr_i = 105, Ltr_j = 106, Ltr_k = 107, Ltr_l = 108, Ltr_m = 109 + , Ltr_n = 110, Ltr_o = 111, Ltr_p = 112, Ltr_q = 113, Ltr_r = 114 + , Ltr_s = 115, Ltr_t = 116, Ltr_u = 117, Ltr_v = 118, Ltr_w = 119 + , Ltr_x = 120, Ltr_y = 121, Ltr_z = 122, Curly_bgn = 123, Pipe = 124 + , Curly_end = 125, Tilde = 126, Delete = 127 + ; + public static final byte + Angle_bgn = Lt, Angle_end = Gt + ; + + public static final int Len_1 = 1; + public static final byte Max_7_bit = (byte)127, Ascii_min = 0, Ascii_max = 127; + public static boolean Is_sym(byte b) { + switch (b) { + case Byte_ascii.Bang: case Byte_ascii.Quote: + case Byte_ascii.Hash: case Byte_ascii.Dollar: case Byte_ascii.Percent: case Byte_ascii.Amp: case Byte_ascii.Apos: + case Byte_ascii.Paren_bgn: case Byte_ascii.Paren_end: case Byte_ascii.Star: case Byte_ascii.Plus: case Byte_ascii.Comma: + case Byte_ascii.Dash: case Byte_ascii.Dot: case Byte_ascii.Slash: + case Byte_ascii.Colon: case Byte_ascii.Semic: + case Byte_ascii.Lt: case Byte_ascii.Eq: case Byte_ascii.Gt: case Byte_ascii.Question: case Byte_ascii.At: + case Byte_ascii.Brack_bgn: case Byte_ascii.Backslash: case Byte_ascii.Brack_end: case Byte_ascii.Pow: + case Byte_ascii.Underline: case Byte_ascii.Tick: + case Byte_ascii.Curly_bgn: case Byte_ascii.Pipe: + case Byte_ascii.Curly_end: case Byte_ascii.Tilde: + return true; + default: + return false; + } + } + public static boolean Is_ltr(byte b) { + return ( b >= Byte_ascii.Ltr_a && b <= Byte_ascii.Ltr_z + || b >= Byte_ascii.Ltr_A && b <= Byte_ascii.Ltr_Z); + } + public static boolean Is_ws(byte b) { + switch (b) { + case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: case Byte_ascii.Space: return true; + default: return false; + } + } + public static boolean Is_num(byte b) { + return b > Byte_ascii.Slash && b < Byte_ascii.Colon; + } + public static byte To_a7_int(byte b) {return (byte)(b - Byte_ascii.Num_0);} + public static byte To_a7_str(int digit) { + switch (digit) { + case 0: return Byte_ascii.Num_0; case 1: return Byte_ascii.Num_1; case 2: return Byte_ascii.Num_2; case 3: return Byte_ascii.Num_3; case 4: return Byte_ascii.Num_4; + case 5: return Byte_ascii.Num_5; case 6: return Byte_ascii.Num_6; case 7: return Byte_ascii.Num_7; case 8: return Byte_ascii.Num_8; case 9: return Byte_ascii.Num_9; + default: throw Err_.new_("Byte_ascii", "unknown digit", "digit", digit); + } + } + public static String To_str(byte b) {return Char_.To_str((char)b);} + public static byte Case_upper(byte b) { + return b > 96 && b < 123 + ? (byte)(b - 32) + : b; + } + public static byte Case_lower(byte b) { + return b > 64 && b < 91 + ? (byte)(b + 32) + : b; + } + public static final byte[] Space_len2 = new byte[] {Space, Space}, Space_len4 = new byte[] {Space, Space, Space, Space}; + public static final byte[] + Tab_bry = new byte[] {Byte_ascii.Tab} + , Nl_bry = new byte[] {Byte_ascii.Nl} + , Space_bry = new byte[] {Byte_ascii.Space} + , Bang_bry = new byte[] {Byte_ascii.Bang} + , Quote_bry = new byte[] {Byte_ascii.Quote} + , Hash_bry = new byte[] {Byte_ascii.Hash} + , Dot_bry = new byte[] {Byte_ascii.Dot} + , Angle_bgn_bry = new byte[] {Byte_ascii.Angle_bgn} + , Angle_end_bry = new byte[] {Byte_ascii.Angle_end} + , Comma_bry = new byte[] {Byte_ascii.Comma} + , Colon_bry = new byte[] {Byte_ascii.Colon} + , Semic_bry = new byte[] {Byte_ascii.Semic} + , Eq_bry = new byte[] {Byte_ascii.Eq} + , Amp_bry = new byte[] {Byte_ascii.Amp} + , Lt_bry = new byte[] {Byte_ascii.Lt} + , Gt_bry = new byte[] {Byte_ascii.Gt} + , Question_bry = new byte[] {Byte_ascii.Question} + , Brack_bgn_bry = new byte[] {Byte_ascii.Brack_bgn} + , Brack_end_bry = new byte[] {Byte_ascii.Brack_end} + , Apos_bry = new byte[] {Byte_ascii.Apos} + , Pipe_bry = new byte[] {Byte_ascii.Pipe} + , Underline_bry = new byte[] {Byte_ascii.Underline} + , Slash_bry = new byte[] {Byte_ascii.Slash} + , Star_bry = new byte[] {Byte_ascii.Star} + , Dash_bry = new byte[] {Byte_ascii.Dash} + , Cr_lf_bry = new byte[] {Byte_ascii.Cr, Byte_ascii.Nl} + , Num_0_bry = new byte[] {Byte_ascii.Num_0} + , Num_1_bry = new byte[] {Byte_ascii.Num_1} + ; +} diff --git a/100_core/src/gplx/Cancelable.java b/100_core/src/gplx/Cancelable.java new file mode 100644 index 000000000..c7ff3842c --- /dev/null +++ b/100_core/src/gplx/Cancelable.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; +public interface Cancelable { + boolean Canceled(); + void Cancel(); +} diff --git a/100_core/src/gplx/Cancelable_.java b/100_core/src/gplx/Cancelable_.java new file mode 100644 index 000000000..f49e14a0c --- /dev/null +++ b/100_core/src/gplx/Cancelable_.java @@ -0,0 +1,29 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +public class Cancelable_ { + public static final Cancelable Never = new Cancelable_never(); + public static Cancelable New_proxy() {return new Cancelable_proxy();} +} +class Cancelable_never implements Cancelable { + public boolean Canceled() {return false;} + public void Cancel() {} +} +class Cancelable_proxy implements Cancelable { + private boolean canceled = false; + public boolean Canceled() {return canceled;} + public void Cancel() {canceled = true;} +} diff --git a/100_core/src/gplx/Char_.java b/100_core/src/gplx/Char_.java new file mode 100644 index 000000000..54bf0b9ac --- /dev/null +++ b/100_core/src/gplx/Char_.java @@ -0,0 +1,72 @@ +/* +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; +public class Char_ { + public static final String Cls_val_name = "char"; + public static final Class Cls_ref_type = Character.class; + public static final char Null = '\0', NewLine = '\n'; + public static boolean IsCaseLower(char c) {return Character.isLowerCase(c);} + public static boolean IsLetterOrDigit(char c) {return Character.isLetterOrDigit(c);} + public static boolean IsLetterEnglish(char c) { + switch (c) { + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': return true; + default: return false; + } + } + public static boolean IsLetterLowerEnglish(char c) { + switch (c) { + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': return true; + default: return false; + } + } + public static boolean IsNumber(char c) { + switch (c) { + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return true; + default: return false; + } + } + public static boolean IsWhitespace(char c) { + switch (c) { + case ' ': case '\t': case '\n': case '\r': return true; + default: return false; + } + } + public static boolean In(char match, char... ary) { + for (char itm : ary) + if (itm == match) return true; + return false; + } + public static int To_int_or(char c, int or) { + switch (c) { + case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; + case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; + default: return or; + } + } + public static String To_str(char[] ary, int pos, int length) {return new String(ary, pos, length);} + public static String To_str(int b) {return To_str((char)b);} + public static String To_str(char c) {return String.valueOf(c);} + public static char By_int(int i) {return (char)i;} + public static char cast(Object o) {try {return (Character)o;} catch(Exception e) {throw Err_.new_type_mismatch_w_exc(e, char.class, o);}} + public static char parse(String raw) {try {return raw.charAt(0);} catch(Exception exc) {throw Err_.new_parse_exc(exc, char.class, raw);}} +} diff --git a/100_core/src/gplx/CompareAble.java b/100_core/src/gplx/CompareAble.java new file mode 100644 index 000000000..0817a260b --- /dev/null +++ b/100_core/src/gplx/CompareAble.java @@ -0,0 +1,18 @@ +/* +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; +public interface CompareAble extends Comparable {} // URL:/doc/gplx/CompareAble_.txt +// public int compareTo(Object obj) {Type comp = (Type)obj; return prop.compareTo(comp.prop);} diff --git a/100_core/src/gplx/CompareAble_.java b/100_core/src/gplx/CompareAble_.java new file mode 100644 index 000000000..5c6725a5b --- /dev/null +++ b/100_core/src/gplx/CompareAble_.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; +public class CompareAble_ { + public static Comparable as_(Object obj) {return obj instanceof Comparable ? (Comparable)obj : null;} + public static int Compare_obj(Object lhs, Object rhs) {return Compare_comp(as_(lhs), as_(rhs));} + public static int Compare_comp(Comparable lhs, Comparable rhs) { + if (lhs == null && rhs == null) return CompareAble_.Same; + else if (lhs == null) return CompareAble_.More; + else if (rhs == null) return CompareAble_.Less; + else return Compare(lhs, rhs); + } + public static int Compare(Comparable lhs, Comparable rhs) {return lhs.compareTo(rhs);} + + public static boolean Is(int expd, Comparable lhs, Comparable rhs) { + int actl = Compare_comp(lhs, rhs); + if (actl == Same && expd % 2 == Same) // actl=Same and expd=(Same||MoreOrSame||LessOrSame) + return true; + else + return (actl * expd) > 0; // actl=More||Less; expd will match if on same side of 0 (ex: expd=Less; actl=Less; -1 * -1 = 1) + } + + public static final int + More = 1 + , Less = -1 + , Same = 0 + , More_or_same = 2 + , Less_or_same = -2 + ; +} diff --git a/100_core/src/gplx/DateAdp.java b/100_core/src/gplx/DateAdp.java new file mode 100644 index 000000000..fa12a01d3 --- /dev/null +++ b/100_core/src/gplx/DateAdp.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; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; +import java.text.SimpleDateFormat; +public class DateAdp implements CompareAble, Gfo_invk { + public int compareTo(Object obj) {DateAdp comp = (DateAdp)obj; return under.compareTo(comp.under);} + @Override public String toString() {return XtoStr_gplx_long();} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_XtoStr_fmt)) return XtoStr_fmt("yyyy-MM-dd HH:mm:ss"); + else if (ctx.Match(k, Invk_AddDays)) { + int days = m.ReadInt("days"); + if (ctx.Deny()) return this; + return this.Add_day(days); + } + else return Gfo_invk_.Rv_unhandled; + } public static final String Invk_XtoStr_fmt = "XtoStr_fmt", Invk_AddDays = "Add_day"; + public int Segment(int segmentIdx) { + switch (segmentIdx) { + case DateAdp_.SegIdx_year: return this.Year(); + case DateAdp_.SegIdx_month: return this.Month(); + case DateAdp_.SegIdx_day: return this.Day(); + case DateAdp_.SegIdx_hour: return this.Hour(); + case DateAdp_.SegIdx_minute: return this.Minute(); + case DateAdp_.SegIdx_second: return this.Second(); + case DateAdp_.SegIdx_frac: return this.Frac(); + case DateAdp_.SegIdx_dayOfWeek: return this.DayOfWeek(); + case DateAdp_.SegIdx_weekOfYear: return this.WeekOfYear(); + case DateAdp_.SegIdx_dayOfYear: return this.DayOfYear(); + default: throw Err_.new_unhandled(segmentIdx); + } + } + public int[] XtoSegAry() { + int[] rv = new int[7]; + rv[DateAdp_.SegIdx_year] = this.Year(); + rv[DateAdp_.SegIdx_month] = this.Month(); + rv[DateAdp_.SegIdx_day] = this.Day(); + rv[DateAdp_.SegIdx_hour] = this.Hour(); + rv[DateAdp_.SegIdx_minute] = this.Minute(); + rv[DateAdp_.SegIdx_second] = this.Second(); + rv[DateAdp_.SegIdx_frac] = this.Frac(); + return rv; + } + public String XtoStr_gplx() {return XtoStr_fmt("yyyyMMdd_HHmmss.fff");} + public String XtoStr_gplx_long() {return XtoStr_fmt("yyyy-MM-dd HH:mm:ss.fff");} + public String XtoStr_fmt_HHmmss() {return XtoStr_fmt("HH:mm:ss");} + public String XtoStr_fmt_HHmm() {return XtoStr_fmt("HH:mm");} + public String XtoStr_fmt_yyyy_MM_dd() {return XtoStr_fmt("yyyy-MM-dd");} + public String XtoStr_fmt_yyyyMMdd_HHmmss() {return XtoStr_fmt("yyyyMMdd_HHmmss");} + public String XtoStr_fmt_yyyyMMdd_HHmmss_fff() {return XtoStr_fmt("yyyyMMdd_HHmmss.fff");} + public String XtoStr_fmt_yyyyMMdd_HHmm() {return XtoStr_fmt("yyyyMMdd_HHmm");} + public String XtoStr_fmt_yyyy_MM_dd_HH_mm() {return XtoStr_fmt("yyyy-MM-dd HH:mm");} + public String XtoStr_fmt_yyyy_MM_dd_HH_mm_ss() {return XtoStr_fmt("yyyy-MM-dd HH:mm:ss");} + public String XtoStr_fmt_iso_8561() {return XtoStr_fmt("yyyy-MM-dd HH:mm:ss");} + public String XtoStr_fmt_iso_8561_w_tz() {return XtoStr_fmt("yyyy-MM-dd'T'HH:mm:ss'Z'");} + public static int Timezone_offset_test = Int_.Min_value; + public Calendar UnderDateTime() {return under;} Calendar under; + public int Year() {return under.get(Calendar.YEAR);} + public int Month() {return under.get(Calendar.MONTH) + Month_base0adj;} + public int Day() {return under.get(Calendar.DAY_OF_MONTH);} + public int Hour() {return under.get(Calendar.HOUR_OF_DAY);} + public int Minute() {return under.get(Calendar.MINUTE);} + public int Second() {return under.get(Calendar.SECOND);} + public int DayOfWeek() {return under.get(Calendar.DAY_OF_WEEK) - 1;} // -1 : Base0; NOTE: dotnet/php is also Sunday=0 + public int DayOfYear() {return under.get(Calendar.DAY_OF_YEAR);} + public int Timezone_offset() { + return Timezone_offset_test == Int_.Min_value // Timezone_offset_test not over-ridden + ? 0 + // ? under.getTimeZone().getOffset(this.Timestamp_unix()) / 1000 // divide by 1000 to convert from ms to seconds + : Timezone_offset_test + ; + } + public DateAdp XtoUtc() { + java.util.Date date = under.getTime(); + java.util.TimeZone tz = under.getTimeZone(); + long msFromEpochGmt = date.getTime(); + int offsetFromUTC = tz.getOffset(msFromEpochGmt); + Calendar gmtCal = Calendar.getInstance(); + gmtCal.setTimeInMillis(msFromEpochGmt + -offsetFromUTC); + return new DateAdp(gmtCal); + } + public DateAdp XtoLocal() { + java.util.Date date = under.getTime(); + java.util.TimeZone tz = under.getTimeZone(); + long msFromEpochGmt = date.getTime(); + int offsetFromUTC = tz.getOffset(msFromEpochGmt); + Calendar gmtCal = Calendar.getInstance(); + gmtCal.setTimeInMillis(msFromEpochGmt + offsetFromUTC); + return new DateAdp(gmtCal); + } + public long Timestamp_unix() { + long offsetFromUTC = (under.getTimeZone().getOffset(0)); + boolean dst = TimeZone.getDefault().inDaylightTime(under.getTime()); + long dst_adj = dst ? 3600000 : 0; + return (under.getTimeInMillis() + offsetFromUTC + dst_adj) / 1000; + } + public int WeekOfYear() {return under.get(Calendar.WEEK_OF_YEAR);} + public int Frac() {return under.get(Calendar.MILLISECOND);} + public DateAdp Add_frac(int val) {return CloneAndAdd(Calendar.MILLISECOND, val);} + public DateAdp Add_second(int val) {return CloneAndAdd(Calendar.SECOND, val);} + public DateAdp Add_minute(int val) {return CloneAndAdd(Calendar.MINUTE, val);} + public DateAdp Add_hour(int val) {return CloneAndAdd(Calendar.HOUR, val);} + public DateAdp Add_day(int val) {return CloneAndAdd(Calendar.DAY_OF_MONTH, val);} + public DateAdp Add_month(int val) {return CloneAndAdd(Calendar.MONTH, val);} + public DateAdp Add_year(int val) {return CloneAndAdd(Calendar.YEAR, val);} + DateAdp CloneAndAdd(int field, int val) { + Calendar clone = (Calendar)under.clone(); + clone.add(field, val); + return new DateAdp(clone); + } + public String XtoStr_fmt(String fmt) { + fmt = fmt.replace("f", "S"); + SimpleDateFormat sdf = new SimpleDateFormat(fmt); + return sdf.format(under.getTime()); + } + public String XtoStr_tz() { + SimpleDateFormat sdf = new SimpleDateFormat("Z"); + String time_zone = sdf.format(under.getTime()); + return String_.Mid(time_zone, 0, 3) + ":" + String_.Mid(time_zone, 3, String_.Len(time_zone)); + } + public boolean Eq(DateAdp v) {DateAdp comp = v; return Object_.Eq(under.getTimeInMillis(), comp.under.getTimeInMillis());} + public int Diff_days(DateAdp prev) { + long diff = this.under.getTimeInMillis() - prev.under.getTimeInMillis(); + return (int)(diff / (1000 * 60 * 60 * 24)); + } + public Time_span Diff(DateAdp earlier) { + long diff = this.under.getTimeInMillis() - earlier.under.getTimeInMillis(); + return Time_span_.fracs_(diff); + } + protected DateAdp(Calendar under) {this.under = under;} + protected DateAdp(int year, int month, int day, int hour, int minute, int second, int frac) { + this.under = new GregorianCalendar(year, month - Month_base0adj, day, hour, minute, second); + under.set(Calendar.MILLISECOND, frac); + } + public static final int Month_base0adj = 1; + } diff --git a/100_core/src/gplx/DateAdp_.java b/100_core/src/gplx/DateAdp_.java new file mode 100644 index 000000000..e87aca9b0 --- /dev/null +++ b/100_core/src/gplx/DateAdp_.java @@ -0,0 +1,124 @@ +/* +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; +import java.sql.Timestamp; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.TimeZone; +import gplx.core.times.*; +public class DateAdp_ implements Gfo_invk { + public static final String Cls_ref_name = "Date"; + public static final Class Cls_ref_type = DateAdp.class; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_Now)) return Datetime_now.Get(); + else return Gfo_invk_.Rv_unhandled; + } public static final String Invk_Now = "Now"; + public static final DateAdp MinValue = new DateAdp( 1, 1, 1, 0, 0, 0, 0); + public static final DateAdp MaxValue = new DateAdp(9999, 12, 31, 23, 59, 59, 999); +// public static DateAdp Now() {return Tfds.Now_enabled() ? Tfds.Now() : new DateAdp(new GregorianCalendar());} + public static DateAdp new_(int year, int month, int day, int hour, int minute, int second, int frac) {return new DateAdp(year, month, day, hour, minute, second, frac);} + public static DateAdp seg_(int[] ary) { + int ary_len = ary.length; + int y = ary_len > 0 ? ary[0] : 1; + int M = ary_len > 1 ? ary[1] : 1; + int d = ary_len > 2 ? ary[2] : 1; + int h = ary_len > 3 ? ary[3] : 0; + int m = ary_len > 4 ? ary[4] : 0; + int s = ary_len > 5 ? ary[5] : 0; + int f = ary_len > 6 ? ary[6] : 0; + return new DateAdp(y, M, d, h, m, s, f); + } + public static DateAdp cast(Object arg) {try {return (DateAdp)arg;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, DateAdp.class, arg);}} + public static DateAdp parse_iso8561_or(String raw, DateAdp or) { + try {return parse_iso8561(raw);} + catch (Exception e) {Err_.Noop(e); return or;} + } + public static DateAdp parse_iso8561(String raw) { // NOTE: for now, same as parse_gplx + int[] ary = date_parser.Parse_iso8651_like(raw); + if (ary[1] < 1 || ary[1] > 12) return DateAdp_.MinValue; // guard against invalid month + if (ary[2] < 1 || ary[2] > 31) return DateAdp_.MinValue; + return new DateAdp(ary[0], ary[1], ary[2], ary[3], ary[4], ary[5], ary[6]); + } + public static DateAdp parse_gplx(String raw) { + int[] ary = date_parser.Parse_iso8651_like(raw); + if (ary[1] < 1 || ary[1] > 12) return DateAdp_.MinValue; // guard against invalid month + if (ary[2] < 1 || ary[2] > 31) return DateAdp_.MinValue; + return new DateAdp(ary[0], ary[1], ary[2], ary[3], ary[4], ary[5], ary[6]); + } static DateAdp_parser date_parser = DateAdp_parser.new_(); + public static DateAdp dateTime_(GregorianCalendar v) {return new DateAdp(v);} + public static DateAdp dateTime_obj_(Object v) {return new DateAdp((GregorianCalendar)v);} + public static final DateAdp_ Gfs = new DateAdp_(); + + public static int DaysInMonth(DateAdp date) { + int rv = DaysInMonth_ary[date.Month() - Int_.Base1]; + if (rv == 28 && IsLeapYear(date.Year())) rv = 29; + return rv; + } static int [] DaysInMonth_ary = {31,28,31,30,31,30,31,31,30,31,30,31}; + public static boolean IsLeapYear(int year) { + if (year % 4 != 0) return false; + else if (year % 400 == 0) return true; + else if (year % 100 == 0) return false; + else return true; + } + public static DateAdp unixtime_utc_seconds_(long v) {return unixtime_utc_ms_(v * 1000);} + public static DateAdp parse_fmt_or(String raw, String fmt, DateAdp or) { + try {return parse_fmt(raw, fmt);} + catch (Exception e) {Err_.Noop(e); return or;} + } + public static DateAdp db_(Object v) { + Timestamp ts = (Timestamp)v; + Calendar gc = Calendar.getInstance(); + gc.setTimeInMillis(ts.getTime()); + return new DateAdp(gc); + } + public static DateAdp parse_(String raw) { + SimpleDateFormat sdf = new SimpleDateFormat(); + Date d = null; + try {d = sdf.parse(raw);} + catch (ParseException e) {throw Err_.new_("parse", "failed to parse to DateAdp", "raw", raw);} + GregorianCalendar cal = (GregorianCalendar)Calendar.getInstance(); + cal.setTime(d); + return dateTime_(cal); + } + public static DateAdp parse_fmt(String raw, String fmt) { + fmt = fmt.replace('t', 'a'); // AM/PM + fmt = fmt.replace('f', 'S'); // milliseconds + SimpleDateFormat sdf = new SimpleDateFormat(fmt, Locale.US); + Date d = null; + try {d = sdf.parse(raw);} + catch (ParseException e) {throw Err_.new_("parse", "failed to parse to DateAdp", "raw", raw, "fmt", fmt);} + GregorianCalendar cal = (GregorianCalendar)Calendar.getInstance(); + cal.setTime(d); + return dateTime_(cal); + } + public static DateAdp unixtime_utc_ms_(long v) {return unixtime_lcl_ms_(v).XtoUtc();} + public static DateAdp unixtime_lcl_ms_(long v) { + GregorianCalendar c = new GregorianCalendar(); + c.setTimeInMillis(v); + return new DateAdp(c); + } + public static final int SegIdx_year = 0, SegIdx_month = 1, SegIdx_day = 2, SegIdx_hour = 3, SegIdx_minute = 4, SegIdx_second = 5, SegIdx_frac = 6, SegIdx_dayOfWeek = 7, SegIdx_weekOfYear = 8, SegIdx_dayOfYear = 9, SegIdx__max = 10; + public static String Xto_str_fmt_or(DateAdp v, String fmt, String or) { + return v == null ? or : v.XtoStr_fmt(fmt); + } + public static final String + Fmt_iso8561_date_time = "yyyy-MM-dd HH:mm:ss" + , Fmt__yyyyMMdd = "yyyyMMdd"; +} diff --git a/100_core/src/gplx/DateAdp__tst.java b/100_core/src/gplx/DateAdp__tst.java new file mode 100644 index 000000000..02d99a22f --- /dev/null +++ b/100_core/src/gplx/DateAdp__tst.java @@ -0,0 +1,86 @@ +/* +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; +import org.junit.*; import gplx.core.tests.*; +public class DateAdp__tst { + private final DateAdp__fxt fxt = new DateAdp__fxt(); + @Test public void Parse_gplx() { + fxt.Test__parse_gplx("99991231_235959.999" , "99991231_235959.999"); + fxt.Test__parse_gplx("20090430_213200.123" , "20090430_213200.123"); + fxt.Test__parse_gplx("20090430_213200" , "20090430_213200.000"); + fxt.Test__parse_gplx("20090430" , "20090430_000000.000"); + } + @Test public void Parse_separators() { + fxt.Test__parse_gplx("2009-04-30 21:32:00.123" , "20090430_213200.123"); + fxt.Test__parse_gplx("2009-04-30 21:32:00" , "20090430_213200.000"); + fxt.Test__parse_gplx("2009-04-30" , "20090430_000000.000"); + } + @Test public void Parse_utc() { + fxt.Test__parse_gplx("2015-12-26T10:03:53Z" , "20151226_100353.000"); + } + @Test public void DayOfWeek() { + fxt.Test__day_of_week("2012-01-18", 3); //3=Wed + } + @Test public void WeekOfYear() { + fxt.Test__week_of_year("2006-02-01", 5); // 1-1:Sun;2-1:Wed + fxt.Test__week_of_year("2007-02-01", 5); // 1-1:Mon;2-1:Thu + fxt.Test__week_of_year("2008-02-01", 5); // 1-1:Tue;2-1:Fri + fxt.Test__week_of_year("2009-02-01", 6); // 1-1:Thu;2-1:Sun + fxt.Test__week_of_year("2010-02-01", 6); // 1-1:Fri;2-1:Mon + fxt.Test__week_of_year("2011-02-01", 6); // 1-1:Sat;2-1:Tue + } + @Test public void DayOfYear() { + fxt.Test__day_of_year("2012-01-01", 1); + fxt.Test__day_of_year("2012-02-29", 60); + fxt.Test__day_of_year("2012-12-31", 366); + } + @Test public void Timestamp_unix() { + fxt.Test__timestamp_unix("1970-01-01 00:00:00", 0); + fxt.Test__timestamp_unix("2012-01-01 00:00:00", 1325376000); + } + @Test public void DaysInMonth() { + fxt.Test__days_in_month("2012-01-01", 31); + fxt.Test__days_in_month("2012-02-01", 29); + fxt.Test__days_in_month("2012-04-01", 30); + fxt.Test__days_in_month("2011-02-01", 28); + } + @Test public void XtoUtc() { + fxt.Test__to_utc("2012-01-01 00:00", "2012-01-01 05:00"); //4=Wed + } +} +class DateAdp__fxt { + public void Test__parse_gplx(String raw, String expd) { + Gftest.Eq__str(expd, DateAdp_.parse_gplx(raw).XtoStr_gplx()); + } + public void Test__day_of_week(String raw, int expd) { + Gftest.Eq__int(expd, DateAdp_.parse_gplx(raw).DayOfWeek()); + } + public void Test__week_of_year(String raw, int expd) { + Gftest.Eq__int(expd, DateAdp_.parse_gplx(raw).WeekOfYear()); + } + public void Test__day_of_year(String raw, int expd) { + Gftest.Eq__int(expd, DateAdp_.parse_gplx(raw).DayOfYear()); + } + public void Test__days_in_month(String raw, int expd) { + Gftest.Eq__int(expd, DateAdp_.DaysInMonth(DateAdp_.parse_gplx(raw))); + } + public void Test__timestamp_unix(String raw, long expd) { + Gftest.Eq__long(expd, DateAdp_.parse_gplx(raw).Timestamp_unix()); + } + public void Test__to_utc(String raw, String expd) { + Tfds.Eq(expd, DateAdp_.parse_gplx(raw).XtoUtc().XtoStr_fmt_yyyy_MM_dd_HH_mm()); + } +} diff --git a/100_core/src/gplx/Datetime_now.java b/100_core/src/gplx/Datetime_now.java new file mode 100644 index 000000000..e6573e01c --- /dev/null +++ b/100_core/src/gplx/Datetime_now.java @@ -0,0 +1,67 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +import java.util.GregorianCalendar; +public class Datetime_now { + private static final DateAdp dflt = DateAdp_.parse_gplx("2001-01-01 00:00:00.000"); + private static DateAdp manual; + private static boolean autoincrement = true; + public static void Manual_y_() { + manual = dflt; + } + public static void Manual_n_() { + manual = null; + autoincrement = true; + } + public static void Manual_and_freeze_(DateAdp v) { + manual = v; + autoincrement = false; + } + public static void Manual_(DateAdp v) { + manual = v; + } + public static void Autoincrement_n_() { + autoincrement = false; + } + public static DateAdp Dflt_add_min_(int v) { + return dflt.Add_minute(v); + } + + public static DateAdp Get() { + if (manual == null) return new DateAdp(new GregorianCalendar()); + DateAdp rv = manual; + if (autoincrement) manual = rv.Add_minute(1); // simulate passage of manual by increasing manual by 1 minute with each call + return rv; + } + public static DateAdp Get_force() { // ignore manual and force get of real time + return new DateAdp(new GregorianCalendar()); + } +// private static final DateAdp manual_time_dflt = DateAdp_.parse_gplx("2001-01-01 00:00:00.000"); +// private static DateAdp manual_time; +// static boolean Now_enabled() {return now_enabled;} private static boolean now_enabled; +// static void Now_enabled_y_() {now_enabled = Bool_.Y; manual_time = manual_time_dflt;} +// static void Now_enabled_n_() {now_enabled = Bool_.N; now_freeze = false;} + // public static void Now_set(DateAdp date) {now_enabled = true; manual_time = date;} + // public static void Now_freeze_y_() {now_freeze = true;} +// private static boolean now_freeze; + // public static DateAdp Now_time0_add_min(int minutes) {return manual_time_dflt.Add_minute(minutes);} +// @gplx.Internal protected static DateAdp Now() { +// DateAdp rv = manual_time; +// if (!now_freeze) manual_time = rv.Add_minute(1); +// return rv; +// } + +} \ No newline at end of file diff --git a/100_core/src/gplx/Decimal_adp.java b/100_core/src/gplx/Decimal_adp.java new file mode 100644 index 000000000..752b8ce38 --- /dev/null +++ b/100_core/src/gplx/Decimal_adp.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; +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.text.DecimalFormat; +public class Decimal_adp implements CompareAble { + public int compareTo(Object obj) {Decimal_adp comp = (Decimal_adp)obj; return under.compareTo(comp.under);} + protected Decimal_adp(BigDecimal v) {this.under = v;} private final BigDecimal under; + protected Decimal_adp(int v) {this.under = new BigDecimal(v);} + public Object Under() {return under;} + public BigDecimal Under_as_native() {return under;} + public int Precision() {return under.precision();} + public int Frac_1000() {return (int)(under.movePointRight(3).floatValue() % 1000);} + public boolean Eq(Decimal_adp v) {return v.under.doubleValue() == under.doubleValue();} + public boolean Eq(int v) {return under.doubleValue() == v;} + public String To_str() { + BigDecimal tmp = under; + int tmp_scale = tmp.scale(); + if (tmp_scale <= -14) return tmp.toString(); // NOTE: if large number, call .toString which will return exponential notaion (1E##) instead of literal (1000....); 14 matches MW code; DATE:2015-04-10 + if (tmp_scale > 14) + tmp = tmp.setScale(14, RoundingMode.DOWN); // NOTE: if small number, round down to remove excessive zeroes; 14 matches PHP/C# values more closely; RoundingMode.Down for same reason; see E, Pi tests + return tmp .stripTrailingZeros() // NOTE: stripTrailingZeros for exp tests; EX: 120.0 -> 120; 0.01200000000000 -> .012 + .toPlainString(); // NOTE: toPlainString b/c stripTrailingZeros now converts 120 to 1.2E+2 (and any other value that is a multiple of 10) + } + public String To_str(String fmt) { + return new DecimalFormat(fmt).format(under); + } + @Override public String toString() {return under.toString();} + public int To_int() {return (int)under.doubleValue();} + public long To_long() {return (long)under.doubleValue();} + public long To_long_mult_1000() {return under.movePointRight(3).longValue();} + public double To_double() {return under.doubleValue();} + public Decimal_adp Add(Decimal_adp v) {return new Decimal_adp(under.add(v.under, Decimal_adp_.Gplx_rounding_context));} + public Decimal_adp Subtract(Decimal_adp v) {return new Decimal_adp(under.subtract(v.under, Decimal_adp_.Gplx_rounding_context));} + public Decimal_adp Multiply(Decimal_adp v) {return new Decimal_adp(under.multiply(v.under));} + public Decimal_adp Multiply(double v) {return new Decimal_adp(under.multiply(new BigDecimal(v, Decimal_adp_.Gplx_rounding_context)));} + public Decimal_adp Multiply(long v) {return new Decimal_adp(under.multiply(new BigDecimal(v)));} + public Decimal_adp Divide(Decimal_adp v) {return new Decimal_adp(under.divide(v.under, Decimal_adp_.Gplx_rounding_context));} + public Decimal_adp Mod(Decimal_adp v) {return new Decimal_adp(under.remainder(v.under, Decimal_adp_.Gplx_rounding_context));} + public Decimal_adp Abs() {return new Decimal_adp(under.abs(Decimal_adp_.Gplx_rounding_context));} + public Decimal_adp Pow(int v) {return new Decimal_adp(under.pow(v, Decimal_adp_.Gplx_rounding_context));} + public Decimal_adp Sqrt() {return new Decimal_adp(new BigDecimal(Math_.Sqrt(under.doubleValue())));} + public Decimal_adp Truncate() {return new Decimal_adp(under.intValue());} + public Decimal_adp Round_old(int v) {return new Decimal_adp(under.setScale(v, RoundingMode.HALF_UP));} + public Decimal_adp Round(int v) { + BigDecimal new_val = null; + if (v > 0) { + new_val = under.setScale(v, RoundingMode.HALF_UP); + } + else { + int actl_places = under.precision() - under.scale(); + int reqd_places = -v; + if (reqd_places < actl_places) + new_val = under.round(new java.math.MathContext(actl_places - reqd_places, RoundingMode.HALF_UP)); + else if (reqd_places == actl_places) { + int base_10 = (int)Math_.Pow(10, reqd_places - 1); + if (under.intValue() / base_10 < 5) + new_val = BigDecimal.ZERO; + else + new_val = new BigDecimal(Math_.Pow(10, reqd_places)); + } + else + new_val = BigDecimal.ZERO; + } + return new Decimal_adp(new_val); + } + public boolean Comp_gte(Decimal_adp v) {return under.doubleValue() >= v.under.doubleValue();} + public boolean Comp_gte(int v) {return under.doubleValue() >= v;} + public boolean Comp_lte(Decimal_adp v) {return under.doubleValue() <= v.under.doubleValue();} + public boolean Comp_lte(int v) {return under.doubleValue() <= v;} + public boolean Comp_gt(Decimal_adp v) {return under.doubleValue() > v.under.doubleValue();} + public boolean Comp_gt(int v) {return under.doubleValue() > v;} + public boolean Comp_lt(Decimal_adp v) {return under.doubleValue() < v.under.doubleValue();} + public boolean Comp_lt(int v) {return under.doubleValue() < v;} + } diff --git a/100_core/src/gplx/Decimal_adp_.java b/100_core/src/gplx/Decimal_adp_.java new file mode 100644 index 000000000..e9dd872a5 --- /dev/null +++ b/100_core/src/gplx/Decimal_adp_.java @@ -0,0 +1,69 @@ +/* +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; +import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.Locale; +public class Decimal_adp_ { + public static final String Cls_val_name = "decimal"; + public static final Class Cls_ref_type = Decimal_adp.class; + public static Decimal_adp as_(Object obj) {return obj instanceof Decimal_adp ? (Decimal_adp)obj : null;} + public static final Decimal_adp Zero = new Decimal_adp(0); + public static final Decimal_adp One = new Decimal_adp(1); + public static final Decimal_adp Neg1 = new Decimal_adp(-1); + public static final Decimal_adp Const_e = Decimal_adp_.double_(Math_.E); + public static final Decimal_adp Const_pi = Decimal_adp_.double_(Math_.Pi); + public static Decimal_adp base1000_(long v) {return divide_(v, 1000);} + public static Decimal_adp parts_1000_(long num, int frc) {return divide_((num * (1000)) + frc, 1000);} + public static Decimal_adp parts_(long num, int frc) { + // int log10 = frc == 0 ? 0 : (Math_.Log10(frc) + 1); + // int pow10 = (int)Math_.Pow(10, log10); + int pow10 = XtoPow10(frc); + return divide_((num * (pow10)) + frc, pow10); + } + public static Decimal_adp cast(Object obj) {return (Decimal_adp)obj;} + static int XtoPow10(int v) { + if (v > -1 && v < 10) return 10; + else if (v > 9 && v < 100) return 100; + else if (v > 99 && v < 1000) return 1000; + else if (v > 999 && v < 10000) return 10000; + else if (v > 9999 && v < 100000) return 100000; + else if (v > 99999 && v < 1000000) return 1000000; + else if (v > 999999 && v < 10000000) return 10000000; + else if (v > 9999999 && v < 100000000) return 100000000; + else if (v > 99999999 && v < 1000000000) return 1000000000; + else throw Err_.new_wo_type("value must be between 0 and 1 billion", "v", v); + } + public static String CalcPctStr(long dividend, long divisor, String fmt) { + if (divisor == 0) return "%ERR"; + return Decimal_adp_.float_(Float_.Div(dividend, divisor) * 100).To_str(fmt) + "%"; + } + public static Decimal_adp divide_safe_(long lhs, long rhs) {return rhs == 0 ? Zero : divide_(lhs, rhs);} + public static Decimal_adp divide_(long lhs, long rhs) { return new Decimal_adp(new BigDecimal(lhs).divide(new BigDecimal(rhs), Gplx_rounding_context)); } public static Decimal_adp int_(int v) {return new Decimal_adp(new BigDecimal(v));} public static Decimal_adp long_(long v) {return new Decimal_adp(new BigDecimal(v));} + public static Decimal_adp float_(float v) {return new Decimal_adp(new BigDecimal(v));} public static Decimal_adp double_(double v) {return new Decimal_adp(new BigDecimal(v));} + public static Decimal_adp double_thru_str_(double v) {return new Decimal_adp(BigDecimal.valueOf(v));} + public static Decimal_adp db_(Object v) {return new Decimal_adp((BigDecimal)v);} public static Decimal_adp parse(String raw) { + try { + DecimalFormat nf = (DecimalFormat)NumberFormat.getInstance(Locale.US); // always parse as US format; EX:".9" should not be ",9" in german; DATE:2016-01-31 + nf.setParseBigDecimal(true); + BigDecimal bd = (BigDecimal)nf.parse(raw); + return new Decimal_adp(bd); + } catch (ParseException e) { + throw Err_.new_("Decimal_adp_", "parse to decimal failed", "raw", raw); + } + } public static Decimal_adp pow_10_(int v) {return new Decimal_adp(new BigDecimal(1).scaleByPowerOfTen(v));} + public static final MathContext RoundDownContext = new MathContext(0, RoundingMode.DOWN); public static final MathContext Gplx_rounding_context = new MathContext(14, RoundingMode.HALF_UP); // changed from 28 to 14; DATE:2015-07-31 } diff --git a/100_core/src/gplx/Decimal_adp__tst.java b/100_core/src/gplx/Decimal_adp__tst.java new file mode 100644 index 000000000..5ee584dc8 --- /dev/null +++ b/100_core/src/gplx/Decimal_adp__tst.java @@ -0,0 +1,86 @@ +/* +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; +import org.junit.*; +public class Decimal_adp__tst { + private final Decimal_adp__fxt fxt = new Decimal_adp__fxt(); + @Test public void divide_() { + fxt.Test_divide(1, 1000, "0.001"); + fxt.Test_divide(1, 3, "0.33333333333333"); + fxt.Test_divide(1, 7, "0.14285714285714"); + } + @Test public void base1000_() { + fxt.Test_base_1000(1000, "1"); + fxt.Test_base_1000(1234, "1.234"); + fxt.Test_base_1000(123, "0.123"); + } + @Test public void parts_() { + fxt.Test_parts(1, 0, "1"); + fxt.Test_parts(1, 2, "1.2"); + fxt.Test_parts(1, 23, "1.23"); + fxt.Test_parts(123, 4567, "123.4567"); + } + @Test public void parse() { + fxt.Test_parse("1", "1"); + fxt.Test_parse("1.2", "1.2"); + fxt.Test_parse("0.1", "0.1"); + } + @Test public void Truncate_decimal() { + fxt.Test_truncate_decimal("1", "1"); + fxt.Test_truncate_decimal("1.1", "1"); + fxt.Test_truncate_decimal("1.9", "1"); + } + @Test public void Fraction1000() { + fxt.Test_frac_1000(1, 1000, 1); // 0.001 + fxt.Test_frac_1000(1, 3, 333); // 0.33333333 + fxt.Test_frac_1000(1234, 1000, 234); // 1.234 + fxt.Test_frac_1000(12345, 10000, 234); // 1.2345 + } + @Test public void Lt() { + fxt.Test_comp_lt(1,123, 2, true); + fxt.Test_comp_lt(1,99999999, 2, true); + } + @Test public void To_str_fmt() { + fxt.Test_to_str_fmt(1, 2, "0.0", "0.5"); + fxt.Test_to_str_fmt(1, 3, "0.0", "0.3"); + fxt.Test_to_str_fmt(10000, 7, "0,000.000", "1,428.571"); + fxt.Test_to_str_fmt(1, 2, "00.00", "00.50"); + } + @Test public void Round() { + fxt.Test_round("123.456", 3, "123.456"); + fxt.Test_round("123.456", 2, "123.46"); + fxt.Test_round("123.456", 1, "123.5"); + fxt.Test_round("123.456", 0, "123"); + fxt.Test_round("123.456", -1, "120"); + fxt.Test_round("123.456", -2, "100"); + fxt.Test_round("123.456", -3, "0"); + + fxt.Test_round("6", -1, "10"); + fxt.Test_round("5", -1, "10"); + fxt.Test_round("6", -2, "0"); + } +} +class Decimal_adp__fxt { + public void Test_divide(int lhs, int rhs, String expd) {Tfds.Eq(expd, Decimal_adp_.divide_(lhs, rhs).To_str());} + public void Test_base_1000(int val, String expd) {Tfds.Eq(expd, Decimal_adp_.base1000_(val).To_str());} + public void Test_parts(int num, int fracs, String expd) {Tfds.Eq(expd, Decimal_adp_.parts_(num, fracs).To_str());} + public void Test_parse(String raw, String expd) {Tfds.Eq(expd, Decimal_adp_.parse(raw).To_str());} + public void Test_truncate_decimal(String raw, String expd) {Tfds.Eq(Decimal_adp_.parse(expd).To_str(), Decimal_adp_.parse(raw).Truncate().To_str());} + public void Test_frac_1000(int lhs, int rhs, int expd) {Tfds.Eq(expd, Decimal_adp_.divide_(lhs, rhs).Frac_1000());} + public void Test_comp_lt(int lhsNum, int lhsFrc, int rhs, boolean expd) {Tfds.Eq(expd, Decimal_adp_.parts_(lhsNum, lhsFrc).Comp_lt(rhs));} + public void Test_to_str_fmt(int l, int r, String fmt, String expd) {Tfds.Eq(expd, Decimal_adp_.divide_(l, r).To_str(fmt));} + public void Test_round(String raw, int places, String expd) {Tfds.Eq_str(expd, Decimal_adp_.parse(raw).Round(places).To_str(), "round");} +} diff --git a/100_core/src/gplx/Double_.java b/100_core/src/gplx/Double_.java new file mode 100644 index 000000000..18f12e217 --- /dev/null +++ b/100_core/src/gplx/Double_.java @@ -0,0 +1,52 @@ +/* +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; +public class Double_ { + public static final String Cls_val_name = "double"; + public static final Class Cls_ref_type = Double.class; + public static final double + MinValue = Double.MIN_VALUE + , NaN = Double.NaN + , Inf_pos = Double.POSITIVE_INFINITY + ; + public static final byte[] + NaN_bry = Bry_.new_a7("NaN") + , Inf_pos_bry = Bry_.new_a7("INF") + ; + public static boolean IsNaN(double v) {return Double.isNaN(v);} + public static double cast(Object o) {try {return (Double)o;} catch(Exception e) {throw Err_.new_type_mismatch_w_exc(e, double.class, o);}} + public static double parse(String raw) {try {return Double.parseDouble(raw);} catch(Exception e) {throw Err_.new_parse_exc(e, double.class, raw);}} + public static double parse_or(String raw, double v) {try {return Double.parseDouble(raw);} catch(Exception e) {Err_.Noop(e); return v;}} + public static double coerce_(Object v) { + try {String s = String_.as_(v); return s == null ? Double_.cast(v) : Double_.parse(s);} + catch (Exception e) {throw Err_.new_cast(e, double.class, v);} + } + public static String To_str(double v) { + int v_int = (int)v; + return v - v_int == 0 ? Int_.To_str(v_int) : Double.toString(v); + } + public static String To_str_loose(double v) { + int v_as_int = (int)v; + return v == v_as_int + ? Int_.To_str(v_as_int) // convert to int, and call print String to eliminate any trailing decimal places + : Float_.To_str((float)v); // calling ((float)v).toString is better at removing trailing 0s than String.format("%g", v). note that .net .toString() handles it better; EX:2449.600000000000d; DATE:2014-07-29 + } + public static int Compare(double lhs, double rhs) { + if (lhs == rhs) return CompareAble_.Same; + else if (lhs < rhs) return CompareAble_.Less; + else return CompareAble_.More; + } +} diff --git a/100_core/src/gplx/Double__tst.java b/100_core/src/gplx/Double__tst.java new file mode 100644 index 000000000..72c7ece9e --- /dev/null +++ b/100_core/src/gplx/Double__tst.java @@ -0,0 +1,27 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +import org.junit.*; +public class Double__tst { + private Double__fxt fxt = new Double__fxt(); + @Test public void Xto_str_loose() { + fxt.Test_Xto_str_loose(2449.6000000d , "2449.6"); + fxt.Test_Xto_str_loose(623.700d , "623.7"); + } +} +class Double__fxt { + public void Test_Xto_str_loose(double v, String expd) {Tfds.Eq(expd, Double_.To_str_loose(v));} +} diff --git a/100_core/src/gplx/Enm_.java b/100_core/src/gplx/Enm_.java new file mode 100644 index 000000000..31c6c4908 --- /dev/null +++ b/100_core/src/gplx/Enm_.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; +public class Enm_ { + public static int To_int(Object enm) {return Ordinal_lang(enm);} + private static int Ordinal_lang(Object v) {return ((Enum)v).ordinal();} +} diff --git a/100_core/src/gplx/Err.java b/100_core/src/gplx/Err.java new file mode 100644 index 000000000..d8c13bae5 --- /dev/null +++ b/100_core/src/gplx/Err.java @@ -0,0 +1,89 @@ +/* +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; +import gplx.core.errs.*; +public class Err extends RuntimeException { + private final boolean is_gplx; + private final String trace; + private Err_msg[] msgs_ary = new Err_msg[8]; private int msgs_len = 8, msgs_idx = 0; + + public Err(boolean is_gplx, String trace, String type, String msg, Object... args) { + this.is_gplx = is_gplx; + + // NOTE: Err_ factory methods pass in null stack trace for gplx excs; call Stack_trace here, note that trace will not show constructor + this.trace = is_gplx ? Err_.Trace_lang(this) : trace; + Msgs_add(type, msg, args); + } + + // marks messages logged so they can be ignored; used by Gfh_utl + public boolean Logged() {return logged;} public Err Logged_y_() {logged = true; return this;} private boolean logged; + + // ignores current frame for reporting messages + public int Trace_ignore() {return trace_ignore;} public Err Trace_ignore_add_1_() {++trace_ignore; return this;} private int trace_ignore = 0; + + public Err Args_add(Object... args) {msgs_ary[msgs_idx - 1].Args_add(args); return this;} // i - 1 to get current + + public String To_str__full() {return To_str(Bool_.N, Bool_.Y);} + public String To_str__log() {return To_str(Bool_.Y, Bool_.Y);} + public String To_str__msg_only(){ + return msgs_idx == 0 ? "<>" : msgs_ary[0].To_str_wo_type(); // take 1st message only + } + public String To_str__top_wo_args() { + return msgs_idx == 0 ? "<>" : msgs_ary[0].To_str_wo_args(); + } + private String To_str(boolean called_by_log, boolean include_trace) { + String nl_str = called_by_log ? "\t" : "\n"; + String rv = ""; + for (int i = 0; i < msgs_idx; ++i) { + rv += "[err " + Int_.To_str(i) + "] " + String_.Replace(msgs_ary[i].To_str(), "\n", nl_str) + nl_str; + } + if (include_trace) + rv += "[trace]:" + Trace_to_str(is_gplx, called_by_log, trace_ignore, trace == null ? Err_.Trace_lang(this) : trace); + return rv; + } + @Override public String getMessage() {return To_str__msg_only();} + public static String Trace_to_str(boolean is_gplx, boolean called_by_log, int ignore_lines, String trace) { + if (trace == null) return ""; // WORKAROUND:.NET: StackTrace is only available when error is thrown; can't do "Console.Write(new Exception().StackTrace); + String[] lines = String_.Split_lang(trace, '\n'); int lines_len = lines.length; + int line_bgn = 0; + if (is_gplx) { // remove Err_.new_wo_type lines from trace for gplx exceptions + for (int i = 0; i < lines_len; ++i) { + String line = lines[i]; + if (String_.Has_at_bgn(line, "gplx.Err_.new")) continue; // ignore trace frames with "gplx.Err_.new"; EX: throw Err_.new_unimplemented + line_bgn = i + ignore_lines; + break; + } + } + // concat lines + String rv = ""; + String line_bgn_dlm = called_by_log ? "\t " : "\n "; // "\n " indents + for (int i = line_bgn; i < lines_len; ++i) + rv += line_bgn_dlm + lines[i]; + return rv; + } + + @gplx.Internal protected void Msgs_add(String type, String msg, Object[] args) { + if (msgs_idx == msgs_len) { + int new_len = msgs_len * 2; + Err_msg[] new_ary = new Err_msg[new_len]; + Array_.Copy_to(msgs_ary, new_ary, 0); + this.msgs_ary = new_ary; + this.msgs_len = new_len; + } + msgs_ary[msgs_idx] = new Err_msg(type, msg, args); + ++msgs_idx; + } +} diff --git a/100_core/src/gplx/Err_.java b/100_core/src/gplx/Err_.java new file mode 100644 index 000000000..dd2c184ac --- /dev/null +++ b/100_core/src/gplx/Err_.java @@ -0,0 +1,77 @@ +/* +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; +public class Err_ { + private static String Type__gplx = "gplx", Trace_null = null; + public static void Noop(Exception e) {} + public static Err New(String msg, Object... args) {return new Err(Bool_.Y, Trace_null, "", String_.Format(msg, args));} + + public static Err new_(String type, String msg, Object... args) {return new Err(Bool_.Y, Trace_null, type, msg, args);} + public static Err new_wo_type(String msg, Object... args) {return new Err(Bool_.Y, Trace_null, Type__gplx, msg, args);} + public static Err new_exc(Exception e, String type, String msg, Object... args) { + Err rv = Cast_or_make(e); + rv.Msgs_add(type, msg, args); + return rv; + } + public static Err new_unhandled(Object val) {return new Err(Bool_.Y, Trace_null, Type__gplx, "val is not in switch/if", "val", val);} + public static Err new_unhandled_default(Object val) {return new Err(Bool_.Y, Trace_null, Type__gplx, "val is not in switch", "val", val);} + public static Err new_unsupported() {return new Err(Bool_.Y, Trace_null, Type__gplx, "method not supported");} + public static Err new_unimplemented() {return new Err(Bool_.Y, Trace_null, Type__gplx, "method not implemented");} + public static Err new_unimplemented_w_msg(String msg, Object... args) {return new Err(Bool_.Y, Trace_null, Type__gplx, msg, args);} + + public static Err new_deprecated(String s) {return new Err(Bool_.Y, Trace_null, Type__gplx, "deprecated", "method", s);} + public static Err new_parse_type(Class c, String raw) {return new_parse(Type_.Canonical_name(c), raw);} + public static Err new_parse_exc(Exception e, Class c, String raw) {return new_parse(Type_.Canonical_name(c), raw).Args_add("e", Err_.Message_lang(e));} + public static Err new_parse(String type, String raw) {return new Err(Bool_.Y, Trace_null, Type__gplx, "parse failed", "type", type, "raw", raw);} + public static Err new_null() {return new Err(Bool_.Y, Trace_null, Type__gplx, "null obj");} + public static Err new_null(String arg) {return new Err(Bool_.Y, Trace_null, Type__gplx, "null obj", "arg", arg);} + public static Err new_missing_idx(int idx, int len) {return new Err(Bool_.Y, Trace_null, Type__gplx, "index is out of bounds", "idx", idx, "len", len);} + public static Err new_missing_key(String key) {return new Err(Bool_.Y, Trace_null, Type__gplx, "key not found", "key", key);} + public static Err new_invalid_op(String msg) {return new Err(Bool_.Y, Trace_null, Type__gplx, msg);} + public static Err new_invalid_arg(String msg, Object... args) {return new Err(Bool_.Y, Trace_null, Type__gplx, msg, args);} + public static Err new_op_canceled() {return new Err(Bool_.Y, Trace_null, Type__op_canceled, "canceled by usr");} + public static Err new_type_mismatch_w_exc(Exception ignore, Class t, Object o) {return new_type_mismatch(t, o);} + public static Err new_type_mismatch(Class t, Object o) {return new Err(Bool_.Y, Trace_null, Type__gplx, "type mismatch", "expdType", Type_.Canonical_name(t), "actlType", Type_.Name_by_obj(o), "actlObj", Object_.Xto_str_strict_or_null_mark(o));} + public static Err new_cast(Exception ignore, Class t, Object o) { + String o_str = ""; + try {o_str = Object_.Xto_str_strict_or_null_mark(o);} + catch (Exception e) {o_str = ""; Err_.Noop(e);} + return new Err(Bool_.Y, Trace_null, Type__gplx, "cast failed", "type", Type_.Name(t), "obj", o_str); + } + + public static String Message_gplx_full(Exception e) {return Cast_or_make(e).To_str__full();} + public static String Message_gplx_log(Exception e) {return Cast_or_make(e).To_str__log();} + + public static String Message_lang(Throwable e) { + return Error.class.isAssignableFrom(e.getClass()) + ? e.toString() // error has null for "getMessage()" return "toString()" instead + : e.getMessage(); + } + public static String Trace_lang(Throwable e) {return Trace_lang_exec(e.getStackTrace());} + private static String Trace_lang_exec(StackTraceElement[] ary) { + String rv = ""; + int len = ary.length; + for (int i = 0; i < len; i++) { + if (i != 0) rv += "\n"; + rv += ary[i].toString(); + } + return rv; + } + + public static Err Cast_or_null(Exception e) {return Type_.Eq_by_obj(e, Err.class) ? (Err)e : null;} + public static Err Cast_or_make(Throwable e) {return Type_.Eq_by_obj(e, Err.class) ? (Err)e : new Err(Bool_.N, Err_.Trace_lang(e), Type_.Name_by_obj(e), Err_.Message_lang(e));} + public static final String Type__op_canceled = "gplx.op_canceled"; +} diff --git a/100_core/src/gplx/Err_tst.java b/100_core/src/gplx/Err_tst.java new file mode 100644 index 000000000..fcc162028 --- /dev/null +++ b/100_core/src/gplx/Err_tst.java @@ -0,0 +1,45 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +import org.junit.*; +public class Err_tst { + private final Err_fxt fxt = new Err_fxt(); + @Test public void Trace_to_str__gplx() { + fxt.Test_Trace_to_str(Bool_.Y, Bool_.N, 0, String_.Concat_lines_nl_skip_last + ( "gplx.Err_.new_wo_type(Err_.java:1)" // ignore this line + , "gplx.String_.Len(String_.java:2)" + ), String_.Concat_lines_nl_skip_last + ( "" + , " gplx.String_.Len(String_.java:2)" + )); + } + @Test public void Trace_to_str__gplx_ignore() { + fxt.Test_Trace_to_str(Bool_.Y, Bool_.N, 1, String_.Concat_lines_nl_skip_last + ( "gplx.Err_.new_wo_type(Err_.java:1)" // ignore this line + , "gplx.String_.Fail(String_.java:2)" // ignore this line also + , "gplx.String_.Len(String_.java:3)" + ), String_.Concat_lines_nl_skip_last + ( "" + , " gplx.String_.Len(String_.java:3)" + )); + } +} +class Err_fxt { + public void Test_Trace_to_str(boolean is_gplx, boolean called_by_log, int ignore_lines, String trace, String expd) { + String actl = Err.Trace_to_str(is_gplx, called_by_log, ignore_lines, trace); + Tfds.Eq_str_lines(expd, actl); + } +} diff --git a/100_core/src/gplx/Float_.java b/100_core/src/gplx/Float_.java new file mode 100644 index 000000000..fb9a2e94c --- /dev/null +++ b/100_core/src/gplx/Float_.java @@ -0,0 +1,39 @@ +/* +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; +public class Float_ { + public static final String Cls_val_name = "float"; + public static final Class Cls_ref_type = Float.class; + public static final float NaN = Float.NaN;; + public static boolean IsNaN(float v) {return Float.isNaN(v);} + public static float cast(Object obj) {try {return (Float)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, float.class, obj);}} + public static float parse(String raw) {try {return Float.parseFloat(raw);} catch(Exception exc) {throw Err_.new_parse_exc(exc, float.class, raw);}} + public static int Compare(float lhs, float rhs) { + if ( lhs == rhs) return CompareAble_.Same; + else if ( lhs < rhs) return CompareAble_.Less; + else /*lhs > rhs*/ return CompareAble_.More; + } + public static String To_str(float v) { + int v_int = (int)v; + return v - v_int == 0 ? Int_.To_str(v_int) : Float.toString(v); + } + public static float Div(int val, int divisor) {return (float)val / (float)divisor;} + public static float Div(long val, long divisor) {return (float)val / (float)divisor;} + public static int RoundUp(float val) { + int rv = (int)val; + return (rv == val) ? rv : rv + 1; + } +} diff --git a/100_core/src/gplx/GfoMsg.java b/100_core/src/gplx/GfoMsg.java new file mode 100644 index 000000000..774ea2285 --- /dev/null +++ b/100_core/src/gplx/GfoMsg.java @@ -0,0 +1,70 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +import gplx.core.interfaces.*; +public interface GfoMsg { + String Key(); + GfoMsg CloneNew(); + String To_str(); + GfoMsg Clear(); + GfoMsg Parse_(boolean v); + + int Args_count(); + Keyval Args_getAt(int i); + GfoMsg Args_ovr(String k, Object v); + void Args_reset(); + GfoMsg Add(String k, Object v); + int Subs_count(); + GfoMsg Subs_getAt(int i); + GfoMsg Subs_add(GfoMsg m); + GfoMsg Subs_(GfoMsg... ary); + + boolean ReadBool(String k); + boolean ReadBoolOr(String k, boolean or); + boolean ReadBoolOrFalse(String k); + boolean ReadBoolOrTrue(String k); + int ReadInt(String k); + int ReadIntOr(String k, int or); + long ReadLong(String k); + long ReadLongOr(String k, long or); + float ReadFloat(String k); + float ReadFloatOr(String k, float or); + double ReadDouble(String k); + double ReadDoubleOr(String k, double or); + DateAdp ReadDate(String k); + DateAdp ReadDateOr(String k, DateAdp or); + Decimal_adp ReadDecimal(String k); + Decimal_adp ReadDecimalOr(String k, Decimal_adp or); + String ReadStr(String k); + String ReadStrOr(String k, String or); + Io_url ReadIoUrl(String k); + Io_url ReadIoUrlOr(String k, Io_url url); + boolean ReadYn(String k); + boolean ReadYn_toggle(String k, boolean cur); + boolean ReadYnOrY(String k); + byte ReadByte(String k); + byte[] ReadBry(String k); + byte[] ReadBryOr(String k, byte[] or); + Object ReadObj(String k); + Object ReadObj(String k, ParseAble parseAble); + Object ReadObjOr(String k, ParseAble parseAble, Object or); + String[]ReadStrAry(String k, String spr); + String[]ReadStrAryIgnore(String k, String spr, String ignore); + byte[][]ReadBryAry(String k, byte spr); + Object ReadValAt(int i); + Object CastObj(String k); + Object CastObjOr(String k, Object or); +} diff --git a/100_core/src/gplx/GfoMsgUtl.java b/100_core/src/gplx/GfoMsgUtl.java new file mode 100644 index 000000000..10c8ea4f9 --- /dev/null +++ b/100_core/src/gplx/GfoMsgUtl.java @@ -0,0 +1,23 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +public class GfoMsgUtl { + public static int SetInt(GfsCtx ctx, GfoMsg m, int cur) {return ctx.Deny() ? cur : m.ReadIntOr("v", cur);} + public static boolean SetBool(GfsCtx ctx, GfoMsg m, boolean cur) {return ctx.Deny() ? cur : m.ReadBoolOr("v", cur);} + public static String SetStr(GfsCtx ctx, GfoMsg m, String cur) {return ctx.Deny() ? cur : m.ReadStrOr("v", cur);} + public static Io_url SetIoUrl(GfsCtx ctx, GfoMsg m, Io_url cur) {return ctx.Deny() ? cur : m.ReadIoUrlOr("v", cur);} + public static Decimal_adp SetDecimal(GfsCtx ctx, GfoMsg m, Decimal_adp cur) {return ctx.Deny() ? cur : m.ReadDecimalOr("v", cur);} +} diff --git a/100_core/src/gplx/GfoMsg_.java b/100_core/src/gplx/GfoMsg_.java new file mode 100644 index 000000000..07bbd193d --- /dev/null +++ b/100_core/src/gplx/GfoMsg_.java @@ -0,0 +1,268 @@ +/* +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; +import gplx.core.primitives.*; import gplx.core.strings.*; import gplx.core.brys.*; import gplx.core.interfaces.*; +public class GfoMsg_ { + public static GfoMsg as_(Object obj) {return obj instanceof GfoMsg ? (GfoMsg)obj : null;} + public static final GfoMsg Null = new GfoMsg_base().ctor_("<>", false); + public static GfoMsg new_parse_(String key) {return new GfoMsg_base().ctor_(key, true);} + public static GfoMsg new_cast_(String key) {return new GfoMsg_base().ctor_(key, false);} + public static GfoMsg srl_(GfoMsg owner, String key) { + GfoMsg rv = new_parse_(key); + owner.Subs_add(rv); + return rv; + } + public static GfoMsg root_(String... ary) {return root_leafArgs_(ary);} + public static GfoMsg root_leafArgs_(String[] ary, Keyval... kvAry) { + int len = Array_.Len(ary); if (len == 0) throw Err_.new_invalid_arg("== 0", "@len", len); + GfoMsg root = new GfoMsg_base().ctor_(ary[0], false); + GfoMsg owner = root; + for (int i = 1; i < len; i++) { + String key = ary[i]; + GfoMsg cur = new GfoMsg_base().ctor_(key, false); + owner.Subs_add(cur); + owner = cur; + } + for (int i = 0; i < kvAry.length; i++) { + Keyval kv = kvAry[i]; + owner.Add(kv.Key(), kv.Val()); + } + return root; + } + public static GfoMsg chain_(GfoMsg owner, String key) { + GfoMsg sub = owner; + List_adp list = List_adp_.New(); + list.Add(sub.Key()); + while (sub != null) { + if (sub.Subs_count() == 0) break; + sub = (GfoMsg)sub.Subs_getAt(0); + list.Add(sub.Key()); + } + list.Add(key); + + GfoMsg root = GfoMsg_.new_parse_((String)list.Get_at(0)); + GfoMsg cur = root; + for (int i = 1; i < list.Count(); i++) { + String k = (String)list.Get_at(i); + GfoMsg mm = GfoMsg_.new_parse_(k); + cur.Subs_add(mm); + cur = mm; + } + return root; + } + public static GfoMsg wtr_() {return new GfoMsg_wtr().ctor_("", false);} + public static GfoMsg rdr_(String cmd) {return new GfoMsg_rdr().ctor_(cmd, false);} + public static GfoMsg basic_(String cmd, Object... vals) { + GfoMsg rv = new_cast_(cmd); + int len = vals.length; + for (int i = 0; i < len; i++) + rv.Add("", vals[i]); + return rv; + } +} +class GfoMsg_wtr extends GfoMsg_base { + @Override protected Object ReadOr(String k, Object defaultOr) { + if (args == null) args = List_adp_.New(); + args.Add(Keyval_.new_(k, null)); + return defaultOr; + } +} +class GfoMsg_rdr extends GfoMsg_base { + @Override protected Object ReadOr(String k, Object defaultOr) { + if (args == null) args = List_adp_.New(); + args.Add(Keyval_.new_(k, defaultOr)); + return defaultOr; + } +} +class GfoMsg_base implements GfoMsg { + public String Key() {return key;} private String key; + public int Subs_count() {return subs == null ? 0 : subs.Count();} + public GfoMsg Subs_getAt(int i) {return subs == null ? null : (GfoMsg)subs.Get_at(i);} + public GfoMsg Subs_add(GfoMsg m) {if (subs == null) subs = List_adp_.New(); subs.Add(m); return this;} + public GfoMsg Subs_(GfoMsg... ary) {for (GfoMsg m : ary) Subs_add(m); return this;} + public int Args_count() {return args == null ? 0 : args.Count();} + public void Args_reset() { + counter = 0; + Args_reset(this); + } + public GfoMsg Clear() { + this.Args_reset(); + if (subs != null) subs.Clear(); + if (args != null) args.Clear(); + return this; + } + static void Args_reset(GfoMsg owner) { + int len = owner.Subs_count(); + for (int i = 0; i < len; i++) { + GfoMsg sub = owner.Subs_getAt(i); + sub.Args_reset(); + } + } + public Keyval Args_getAt(int i) {return args == null ? null : (Keyval)args.Get_at(i);} + public GfoMsg Args_ovr(String k, Object v) { + if (args == null) args = List_adp_.New(); + for (int i = 0; i < args.Count(); i++) { + Keyval kv = (Keyval)args.Get_at(i); + if (String_.Eq(k, kv.Key())) { + kv.Val_(v); + return this; + } + } + args.Add(Keyval_.new_(k, v)); + return this; + } + public GfoMsg Parse_(boolean v) {parse = v; return this;} + public GfoMsg Add(String k, Object v) { + if (args == null) args = List_adp_.New(); + args.Add(Keyval_.new_(k, v)); + return this; + } + public boolean ReadBool(String k) {Object rv = ReadOr(k,false); if (rv == Nil) ThrowNotFound(k); return parse ? Yn.parse_or((String)rv, false) : Bool_.Cast(rv);} + public int ReadInt(String k) {Object rv = ReadOr(k, 0) ; if (rv == Nil) ThrowNotFound(k); return parse ? Int_.Parse((String)rv) : Int_.Cast(rv);} + public byte ReadByte(String k) {Object rv = ReadOr(k, 0) ; if (rv == Nil) ThrowNotFound(k); return parse ? Byte_.Parse((String)rv) : Byte_.Cast(rv);} + public long ReadLong(String k) {Object rv = ReadOr(k, 0) ; if (rv == Nil) ThrowNotFound(k); return parse ? Long_.parse((String)rv) : Long_.cast(rv);} + public float ReadFloat(String k) {Object rv = ReadOr(k, 0) ; if (rv == Nil) ThrowNotFound(k); return parse ? Float_.parse((String)rv) : Float_.cast(rv);} + public double ReadDouble(String k) {Object rv = ReadOr(k, 0) ; if (rv == Nil) ThrowNotFound(k); return parse ? Double_.parse((String)rv) : Double_.cast(rv);} + public Decimal_adp ReadDecimal(String k) {Object rv = ReadOr(k, 0) ; if (rv == Nil) ThrowNotFound(k); return parse ? Decimal_adp_.parse((String)rv) : Decimal_adp_.cast(rv);} + public String ReadStr(String k) {Object rv = ReadOr(k, null); if (rv == Nil) ThrowNotFound(k); return (String)rv;} + public DateAdp ReadDate(String k) {Object rv = ReadOr(k, null); if (rv == Nil) ThrowNotFound(k); return parse ? DateAdp_.parse_gplx((String)rv) : DateAdp_.cast(rv);} + public Io_url ReadIoUrl(String k) {Object rv = ReadOr(k, null); if (rv == Nil) ThrowNotFound(k); return parse ? Io_url_.new_any_((String)rv) : Io_url_.cast(rv);} + public Object CastObj(String k) {Object rv = ReadOr(k, null); if (rv == Nil) ThrowNotFound(k); return rv;} + public boolean ReadBoolOr(String k, boolean or) {Object rv = ReadOr(k, or) ; if (rv == Nil) return or ; return parse ? Yn.parse_or((String)rv, or) : Bool_.Cast(rv);} + public int ReadIntOr(String k, int or) {Object rv = ReadOr(k, or) ; if (rv == Nil) return or ; return parse ? Int_.Parse((String)rv) : Int_.Cast(rv);} + public long ReadLongOr(String k, long or) {Object rv = ReadOr(k, or) ; if (rv == Nil) return or ; return parse ? Long_.parse((String)rv) : Long_.cast(rv);} + public float ReadFloatOr(String k, float or) {Object rv = ReadOr(k, or) ; if (rv == Nil) return or ; return parse ? Float_.parse((String)rv) : Float_.cast(rv);} + public double ReadDoubleOr(String k,double or) {Object rv = ReadOr(k, or) ; if (rv == Nil) return or ; return parse ? Double_.parse((String)rv) : Double_.cast(rv);} + public Decimal_adp ReadDecimalOr(String k,Decimal_adp or) {Object rv = ReadOr(k, or); if (rv == Nil) return or ; return parse ? Decimal_adp_.parse((String)rv) : Decimal_adp_.cast(rv);} + public String ReadStrOr(String k, String or) {Object rv = ReadOr(k, or) ; if (rv == Nil) return or ; return (String)rv;} + public DateAdp ReadDateOr(String k, DateAdp or) {Object rv = ReadOr(k, or) ; if (rv == Nil) return or ; return parse ? DateAdp_.parse_gplx((String)rv) : DateAdp_.cast(rv);} + public Io_url ReadIoUrlOr(String k, Io_url or) {Object rv = ReadOr(k, or) ; if (rv == Nil) return or ; return parse ? Io_url_.new_any_((String)rv) : Io_url_.cast(rv);} + public boolean ReadBoolOrFalse(String k) {Object rv = ReadOr(k,false); if (rv == Nil) return false ; return parse ? Yn.parse_or((String)rv, false) : Bool_.Cast(rv);} + public boolean ReadBoolOrTrue(String k) {Object rv = ReadOr(k, true); if (rv == Nil) return true ; return parse ? Yn.parse_or((String)rv, true) : Bool_.Cast(rv);} + public boolean ReadYnOrY(String k) {Object rv = ReadOr(k, true); if (rv == Nil) return true ; return parse ? Yn.parse_or((String)rv, true) : Bool_.Cast(rv);} + public boolean ReadYn(String k) {Object rv = ReadOr(k,false); if (rv == Nil) ThrowNotFound(k); return parse ? Yn.parse_or((String)rv, false) : Yn.coerce_(rv);} + public boolean ReadYn_toggle(String k, boolean cur) { + Object rv = ReadOr(k, "!"); + if (rv == Nil) ThrowNotFound(k); + if (!parse) throw Err_.new_wo_type("only parse supported"); + String rv_str = (String)rv; + return (String_.Eq(rv_str, "!")) ? !cur : Yn.parse(rv_str); + } + public byte[] ReadBry(String k) {Object rv = ReadOr(k,false); if (rv == Nil) ThrowNotFound(k); return parse ? Bry_.new_u8((String)rv) : (byte[])rv;} + public byte[] ReadBryOr(String k, byte[] or) {Object rv = ReadOr(k, or); if (rv == Nil) return or; return parse ? Bry_.new_u8((String)rv) : (byte[])rv;} + public Object CastObjOr(String k, Object or) {Object rv = ReadOr(k, or) ; if (rv == Nil) return or ; return rv;} + public Object ReadObj(String k) {Object rv = ReadOr(k, null); if (rv == Nil) ThrowNotFound(k); return rv;} + public Object ReadObj(String k, ParseAble parseAble) {Object rv = ReadOr(k, null); if (rv == Nil) ThrowNotFound(k); return parse ? parseAble.ParseAsObj((String)rv) : rv;} + public Object ReadObjOr(String k, ParseAble parseAble, Object or) {Object rv = ReadOr(k, or) ; if (rv == Nil) return or ; return parse ? parseAble.ParseAsObj((String)rv) : rv;} + public String[] ReadStrAry(String k, String spr) {return String_.Split(ReadStr(k), spr);} + public byte[][] ReadBryAry(String k, byte spr) {return Bry_split_.Split(ReadBry(k), spr);} + public String[] ReadStrAryIgnore(String k, String spr, String ignore) {return String_.Split(String_.Replace(ReadStr(k), ignore, ""), spr);} + public Object ReadValAt(int i) {return Args_getAt(i).Val();} + @gplx.Virtual protected Object ReadOr(String k, Object defaultOr) { + if (args == null) return Nil; // WORKAROUND.gfui: args null for DataBndr_whenEvt_execCmd + if (!String_.Eq(k, "")) { + for (int i = 0; i < args.Count(); i++) { + Keyval kv = (Keyval)args.Get_at(i); + if (String_.Eq(k, kv.Key())) return kv.Val(); + } + } + if (counter >= args.Count()) return Nil; + for (int i = 0; i < args.Count(); i++) { + Keyval kv = (Keyval)args.Get_at(i); + if (String_.Eq(kv.Key(), "") && i >= counter) { + counter++; + return kv.Val(); + } + } + return Nil; + } int counter = 0; + void ThrowNotFound(String k) {throw Err_.new_wo_type("arg not found in msg", "k", k, "counter", counter, "args", args);} + String ArgsXtoStr() { + if (this.Args_count() == 0) return "<>"; + String_bldr sb = String_bldr_.new_(); + for (int i = 0; i < this.Args_count(); i++) { + Keyval rv = (Keyval)this.Args_getAt(i); + sb.Add_fmt("{0};", rv.Key()); + } + return sb.To_str(); + } + public GfoMsg CloneNew() { + GfoMsg_base rv = new GfoMsg_base().ctor_(key, parse); + if (args != null) { + rv.args = List_adp_.New(); + for (int i = 0; i < args.Count(); i++) + rv.args.Add(args.Get_at(i)); + } + if (subs != null) { + rv.subs = List_adp_.New(); + for (int i = 0; i < args.Count(); i++) { + GfoMsg sub = (GfoMsg)args.Get_at(i); + rv.subs.Add(sub.CloneNew()); // NOTE: recursion + } + } + return rv; + } + + protected List_adp args; + List_adp subs; + public String To_str() { + String_bldr sb = String_bldr_.new_(); + To_str(sb, new XtoStrWkr_gplx(), this); + return sb.To_str_and_clear(); + } + void To_str(String_bldr sb, XtoStrWkr wkr, GfoMsg m) { + sb.Add(m.Key()); + if (m.Subs_count() == 0) { + sb.Add(":"); + boolean first = true; + for (int i = 0; i < m.Args_count(); i++) { + Keyval kv = m.Args_getAt(i); + if (kv.Val() == null) continue; + if (!first) sb.Add(" "); + sb.Add(kv.Key()); + sb.Add("='"); + sb.Add(wkr.To_str(kv.Val())); + sb.Add("'"); + first = false; + } + sb.Add(";"); + } + else { + sb.Add("."); + To_str(sb, wkr, m.Subs_getAt(0)); + } + } + + public GfoMsg_base ctor_(String key, boolean parse) {this.key = key; this.parse = parse; return this;} private boolean parse; + @gplx.Internal protected GfoMsg_base(){} + static final String_obj_val Nil = String_obj_val.new_("<>"); +} +interface XtoStrWkr { + String To_str(Object o); +} +class XtoStrWkr_gplx implements XtoStrWkr { + public String To_str(Object o) { + if (o == null) return "<>"; + Class type = Type_.Type_by_obj(o); + String rv = null; + if (Type_.Eq(type, String_.Cls_ref_type)) rv = String_.cast(o); + else if (Type_.Eq(type, Int_.Cls_ref_type)) return Int_.To_str(Int_.Cast(o)); + else if (Type_.Eq(type, Bool_.Cls_ref_type)) return Yn.To_str(Bool_.Cast(o)); + else if (Type_.Eq(type, DateAdp_.Cls_ref_type)) return DateAdp_.cast(o).XtoStr_gplx(); + else rv = Object_.Xto_str_strict_or_empty(o); + return String_.Replace(rv, "'", "''"); + } +} diff --git a/100_core/src/gplx/GfoMsg_tst.java b/100_core/src/gplx/GfoMsg_tst.java new file mode 100644 index 000000000..0610da5ff --- /dev/null +++ b/100_core/src/gplx/GfoMsg_tst.java @@ -0,0 +1,49 @@ +/* +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; +import org.junit.*; import gplx.langs.gfs.*; +public class GfoMsg_tst { + @Before public void setup() { + GfsCore.Instance.AddObj(new Mok(), "Mok"); + } + @Test public void Write1() { + GfoMsg m = GfoMsg_.root_leafArgs_(String_.Ary("a", "b"), Keyval_.new_("int0", 1)); + tst_Msg(m, "a.b:int0='1';"); + } + @Test public void Write() { + Mok mok = new Mok(); + tst_Msg(Gfo_invk_to_str.WriteMsg(mok, Mok.Invk_Cmd0, true, 1, "a"), "Mok.Cmd0:bool0='y' int0='1' str0='a';"); + mok.Int0 = 2; + mok.Bool0 = true; + mok.Str0 = "b"; + tst_Msg(Gfo_invk_to_str.ReadMsg(mok, Mok.Invk_Cmd0), "Mok.Cmd0:bool0='y' int0='2' str0='b';"); + } + void tst_Msg(GfoMsg m, String expd) {Tfds.Eq(expd, m.To_str());} + class Mok implements Gfo_invk { + public boolean Bool0; + public int Int0; + public String Str0; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_Cmd0)) { + Bool0 = m.ReadBoolOr("bool0", Bool0); + Int0 = m.ReadIntOr("int0", Int0); + Str0 = m.ReadStrOr("str0", Str0); + if (ctx.Deny()) return this; + } + return this; + } public static final String Invk_Cmd0 = "Cmd0"; + } +} diff --git a/100_core/src/gplx/GfoTemplate.java b/100_core/src/gplx/GfoTemplate.java new file mode 100644 index 000000000..9de7b0cfe --- /dev/null +++ b/100_core/src/gplx/GfoTemplate.java @@ -0,0 +1,19 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +public interface GfoTemplate { + Object NewCopy(GfoTemplate template); +} diff --git a/100_core/src/gplx/GfoTemplateFactory.java b/100_core/src/gplx/GfoTemplateFactory.java new file mode 100644 index 000000000..918a974e3 --- /dev/null +++ b/100_core/src/gplx/GfoTemplateFactory.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; +public class GfoTemplateFactory implements Gfo_invk { + public void Reg(String key, GfoTemplate template) {hash.Add(key, template);} + public Object Make(String key) { + GfoTemplate template = (GfoTemplate)hash.Get_by(key); + return template.NewCopy(template); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + ctx.Match(k, k); + Object o = hash.Get_by(k); + return o == null ? Gfo_invk_.Rv_unhandled : o; + } + public static final GfoTemplateFactory Instance = new GfoTemplateFactory(); GfoTemplateFactory() {} + Hash_adp hash = Hash_adp_.New(); +} diff --git a/100_core/src/gplx/Gfo_evt_itm.java b/100_core/src/gplx/Gfo_evt_itm.java new file mode 100644 index 000000000..989a6ce36 --- /dev/null +++ b/100_core/src/gplx/Gfo_evt_itm.java @@ -0,0 +1,17 @@ +/* +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; +public interface Gfo_evt_itm extends Gfo_invk, Gfo_evt_mgr_owner {} diff --git a/100_core/src/gplx/Gfo_evt_mgr.java b/100_core/src/gplx/Gfo_evt_mgr.java new file mode 100644 index 000000000..4118d9177 --- /dev/null +++ b/100_core/src/gplx/Gfo_evt_mgr.java @@ -0,0 +1,124 @@ +/* +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; +import gplx.core.lists.*; +public class Gfo_evt_mgr { + private final Gfo_evt_mgr_owner sender; private Ordered_hash subsRegy, pubsRegy; + public Gfo_evt_mgr(Gfo_evt_mgr_owner sender) {this.sender = sender;} + @gplx.Internal protected void AddSub(Gfo_evt_mgr_owner pub, String pubEvt, Gfo_evt_itm sub, String subPrc) { + GfoEvLnk lnk = new GfoEvLnk(pub, pubEvt, sub, subPrc); + if (subsRegy == null) subsRegy = Ordered_hash_.New(); + AddInList(subsRegy, pubEvt, lnk); + sub.Evt_mgr().AddPub(pubEvt, lnk); + } + @gplx.Internal protected void Lnk(Gfo_evt_mgr_owner pub) { + if (pub.Evt_mgr().lnks == null) pub.Evt_mgr().lnks = List_adp_.New(); + pub.Evt_mgr().lnks.Add(this); + } List_adp lnks; + void AddInList(Ordered_hash regy, String key, GfoEvLnk lnk) { + GfoEvLnkList list = (GfoEvLnkList)regy.Get_by(key); + if (list == null) { + list = new GfoEvLnkList(key); + regy.Add(key, list); + } + list.Add(lnk); + } + @gplx.Internal protected void AddPub(String pubEvt, GfoEvLnk lnk) { + if (pubsRegy == null) pubsRegy = Ordered_hash_.New(); + AddInList(pubsRegy, pubEvt, lnk); + } + @gplx.Internal protected void Pub(GfsCtx ctx, String evt, GfoMsg m) { + ctx.MsgSrc_(sender); + GfoEvLnkList subs = subsRegy == null ? null : (GfoEvLnkList)subsRegy.Get_by(evt); + if (subs != null) { + for (int i = 0; i < subs.Count(); i++) { + GfoEvLnk lnk = (GfoEvLnk)subs.Get_at(i); + lnk.Sub().Invk(ctx, 0, lnk.SubPrc(), m); // NOTE: itm.Key() needed for Subscribe_diff() + } + } + if (lnks != null) { + for (int i = 0; i < lnks.Count(); i++) { + Gfo_evt_mgr lnk = (Gfo_evt_mgr)lnks.Get_at(i); + lnk.Pub(ctx, evt, m); + } + } + } + @gplx.Internal protected void RlsSub(Gfo_evt_mgr_owner eobj) { + RlsRegyObj(pubsRegy, eobj, true); + RlsRegyObj(subsRegy, eobj, false); + } + @gplx.Internal protected void RlsPub(Gfo_evt_mgr_owner eobj) { + RlsRegyObj(pubsRegy, eobj, true); + RlsRegyObj(subsRegy, eobj, false); + } + @gplx.Internal protected void RlsRegyObj(Ordered_hash regy, Gfo_evt_mgr_owner eobj, boolean pub) { + if (regy == null) return; + List_adp delList = List_adp_.New(); + for (int i = 0; i < regy.Count(); i++) { + GfoEvLnkList pubsList = (GfoEvLnkList)regy.Get_at(i); + delList.Clear(); + for (int j = 0; j < pubsList.Count(); j++) { + GfoEvLnk lnk = (GfoEvLnk)pubsList.Get_at(j); + if (lnk.End(!pub) == eobj) delList.Add(lnk); + } + for (int j = 0; j < delList.Count(); j++) { + GfoEvLnk del = (GfoEvLnk)delList.Get_at(j); + del.End(pub).Evt_mgr().RlsLnk(!pub, pubsList.Key(), del.End(!pub)); + pubsList.Del(del); + } + } + } + @gplx.Internal protected void RlsLnk(boolean pubEnd, String key, Gfo_evt_mgr_owner endObj) { + Ordered_hash regy = pubEnd ? pubsRegy : subsRegy; + GfoEvLnkList list = (GfoEvLnkList)regy.Get_by(key); + List_adp delList = List_adp_.New(); + for (int i = 0; i < list.Count(); i++) { + GfoEvLnk lnk = (GfoEvLnk)list.Get_at(i); + if (lnk.End(pubEnd) == endObj) delList.Add(lnk); + } + for (int i = 0; i < delList.Count(); i++) { + GfoEvLnk lnk = (GfoEvLnk)delList.Get_at(i); + list.Del(lnk); + } + delList.Clear(); + } +} +class GfoEvLnkList { + public String Key() {return key;} private String key; + public int Count() {return list.Count();} + public void Add(GfoEvLnk lnk) {list.Add(lnk);} + public void Del(GfoEvLnk lnk) {list.Del(lnk);} + public GfoEvLnk Get_at(int i) {return (GfoEvLnk)list.Get_at(i);} + public GfoEvLnkList(String key) {this.key = key;} + List_adp list = List_adp_.New(); +} +class GfoEvLnk { + public Gfo_evt_mgr_owner Pub() {return pub;} Gfo_evt_mgr_owner pub; + public String PubEvt() {return pubEvt;} private String pubEvt; + public Gfo_evt_itm Sub() {return sub;} Gfo_evt_itm sub; + public String SubPrc() {return subPrc;} private String subPrc; + public Gfo_evt_mgr_owner End(boolean pubEnd) {return pubEnd ? pub : sub;} + public GfoEvLnk(Gfo_evt_mgr_owner pub, String pubEvt, Gfo_evt_itm sub, String subPrc) {this.pub = pub; this.pubEvt = pubEvt; this.sub = sub; this.subPrc = subPrc;} +} +class GfoEvItm { + public String Key() {return key;} private String key; + public Gfo_invk InvkAble() {return invkAble;} Gfo_invk invkAble; + public static GfoEvItm new_(Gfo_invk invkAble, String key) { + GfoEvItm rv = new GfoEvItm(); + rv.invkAble = invkAble; rv.key = key; + return rv; + } +} diff --git a/100_core/src/gplx/Gfo_evt_mgr_.java b/100_core/src/gplx/Gfo_evt_mgr_.java new file mode 100644 index 000000000..dc842da09 --- /dev/null +++ b/100_core/src/gplx/Gfo_evt_mgr_.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; +public class Gfo_evt_mgr_ { + public static void Sub(Gfo_evt_mgr_owner pub, String pubEvt, Gfo_evt_itm sub, String subEvt) {pub.Evt_mgr().AddSub(pub, pubEvt, sub, subEvt);} + public static void Sub_same(Gfo_evt_mgr_owner pub, String evt, Gfo_evt_itm sub) {pub.Evt_mgr().AddSub(pub, evt, sub, evt);} + public static void Sub_same_many(Gfo_evt_mgr_owner pub, Gfo_evt_itm sub, String... evts) { + int len = evts.length; + for (int i = 0; i < len; i++) { + String evt = evts[i]; + pub.Evt_mgr().AddSub(pub, evt, sub, evt); + } + } + public static void Pub(Gfo_evt_mgr_owner pub, String pubEvt) {pub.Evt_mgr().Pub(GfsCtx.new_(), pubEvt, GfoMsg_.new_cast_(pubEvt));} + public static void Pub_obj(Gfo_evt_mgr_owner pub, String pubEvt, String key, Object v) {pub.Evt_mgr().Pub(GfsCtx.new_(), pubEvt, msg_(pubEvt, Keyval_.new_(key, v)));} + public static void Pub_val(Gfo_evt_mgr_owner pub, String pubEvt, Object v) {pub.Evt_mgr().Pub(GfsCtx.new_(), pubEvt, msg_(pubEvt, Keyval_.new_("v", v)));} + public static void Pub_vals(Gfo_evt_mgr_owner pub, String pubEvt, Keyval... ary) {pub.Evt_mgr().Pub(GfsCtx.new_(), pubEvt, msg_(pubEvt, ary));} + public static void Pub_msg(Gfo_evt_mgr_owner pub, GfsCtx ctx, String pubEvt, GfoMsg m) {pub.Evt_mgr().Pub(ctx, pubEvt, m);} + public static void Lnk(Gfo_evt_mgr_owner pub, Gfo_evt_mgr_owner sub) {sub.Evt_mgr().Lnk(pub);} + public static void Rls_pub(Gfo_evt_mgr_owner pub) {pub.Evt_mgr().RlsPub(pub);} + public static void Rls_sub(Gfo_evt_mgr_owner sub) {sub.Evt_mgr().RlsSub(sub);} + static GfoMsg msg_(String evt, Keyval... kvAry) { + GfoMsg m = GfoMsg_.new_cast_(evt); + for (int i = 0; i < kvAry.length; i++) { + Keyval kv = kvAry[i]; + m.Add(kv.Key(), kv.Val()); + } + return m; + } +} diff --git a/100_core/src/gplx/Gfo_evt_mgr_owner.java b/100_core/src/gplx/Gfo_evt_mgr_owner.java new file mode 100644 index 000000000..28e7e966d --- /dev/null +++ b/100_core/src/gplx/Gfo_evt_mgr_owner.java @@ -0,0 +1,19 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +public interface Gfo_evt_mgr_owner { + Gfo_evt_mgr Evt_mgr(); +} diff --git a/100_core/src/gplx/Gfo_evt_mgr_tst.java b/100_core/src/gplx/Gfo_evt_mgr_tst.java new file mode 100644 index 000000000..28c91d2cc --- /dev/null +++ b/100_core/src/gplx/Gfo_evt_mgr_tst.java @@ -0,0 +1,67 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +import org.junit.*; +public class Gfo_evt_mgr_tst { + @Before public void setup() { + pub = make_(); sub = make_(); + } MockEvObj pub, sub; + @Test public void Basic() { + Gfo_evt_mgr_.Sub_same(pub, "ev1", sub); + Gfo_evt_mgr_.Pub_val(pub, "ev1", "val1"); + sub.tst_Handled("val1"); + } + @Test public void None() {// make sure no subscribers does not cause exception + Gfo_evt_mgr_.Sub_same(pub, "ev1", sub); + Gfo_evt_mgr_.Pub_val(pub, "ev2", "val1"); //ev2 does not exist + sub.tst_Handled(); + } + @Test public void Lnk() { + MockEvObj mid = make_(); + mid.Evt_mgr().Lnk(pub); + Gfo_evt_mgr_.Sub_same(mid, "ev1", sub); + Gfo_evt_mgr_.Pub_val(pub, "ev1", "val1"); + sub.tst_Handled("val1"); + } + @Test public void RlsSub() { + this.Basic(); + + Gfo_evt_mgr_.Rls_sub(sub); + Gfo_evt_mgr_.Pub_val(pub, "ev1", "val1"); + sub.tst_Handled(); + } + @Test public void RlsPub() { + this.Basic(); + + Gfo_evt_mgr_.Rls_sub(pub); + Gfo_evt_mgr_.Pub_val(pub, "ev1", "val1"); + sub.tst_Handled(); + } + MockEvObj make_() {return new MockEvObj();} +} +class MockEvObj implements Gfo_evt_itm { + public Gfo_evt_mgr Evt_mgr() {return eventMgr;} Gfo_evt_mgr eventMgr; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + handled.Add(m.ReadStr("v")); + return this; + } + List_adp handled = List_adp_.New(); + public void tst_Handled(String... expd) { + Tfds.Eq_ary_str(expd, handled.To_str_ary()); + handled.Clear(); + } + public MockEvObj(){eventMgr = new Gfo_evt_mgr(this);} +} diff --git a/100_core/src/gplx/Gfo_invk.java b/100_core/src/gplx/Gfo_invk.java new file mode 100644 index 000000000..3c509eb03 --- /dev/null +++ b/100_core/src/gplx/Gfo_invk.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; +public interface Gfo_invk { + Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m); +} +/* + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__set)) {} + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk__set = "set"; +*/ diff --git a/100_core/src/gplx/Gfo_invk_.java b/100_core/src/gplx/Gfo_invk_.java new file mode 100644 index 000000000..b6bc37202 --- /dev/null +++ b/100_core/src/gplx/Gfo_invk_.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; +import gplx.core.primitives.*; +public class Gfo_invk_ { + public static final String Mutator_suffix = "_"; + public static final Gfo_invk Noop = new Gfo_invk__noop(); + public static final String_obj_val + Rv_unhandled = String_obj_val.new_("Unhandled") + , Rv_handled = String_obj_val.new_("Handled") + , Rv_host = String_obj_val.new_("Host") + , Rv_cancel = String_obj_val.new_("Cancel") + , Rv_error = String_obj_val.new_("Error"); + + public static Gfo_invk as_(Object obj) {return obj instanceof Gfo_invk ? (Gfo_invk)obj : null;} + + public static Object Invk_no_key(Gfo_invk invk) {return Invk_by_msg(invk, "", GfoMsg_.Null);} + public static Object Invk_by_key(Gfo_invk invk, String k) {return Invk_by_msg(invk, k , GfoMsg_.Null);} + public static Object Invk_by_val(Gfo_invk invk, String k, Object v) {return Invk_by_msg(invk, k , GfoMsg_.new_cast_(k).Add("v", v));} + public static Object Invk_by_msg(Gfo_invk invk, String k, GfoMsg m) { + Object rv = invk.Invk(GfsCtx.Instance, 0, k, m); + if (rv == Gfo_invk_.Rv_unhandled) throw Err_.new_wo_type("invkable did not handle message", "key", k); + return rv; + } +} +class Gfo_invk__noop implements Gfo_invk { + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return this;} +} diff --git a/100_core/src/gplx/Gfo_invk_cmd.java b/100_core/src/gplx/Gfo_invk_cmd.java new file mode 100644 index 000000000..cd8f59254 --- /dev/null +++ b/100_core/src/gplx/Gfo_invk_cmd.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; +public class Gfo_invk_cmd { + private final Gfo_invk itm; private final String cmd; private final GfoMsg msg; + public Gfo_invk_cmd(Gfo_invk itm, String cmd, GfoMsg msg) { + this.itm = itm; this.cmd = cmd; this.msg = msg; + } + public Object Exec() {return itm.Invk(GfsCtx.Instance, 0, cmd, msg);} + public Object Exec_by_ctx(GfsCtx ctx, GfoMsg msg) {return itm.Invk(ctx, 0, cmd, msg);} + + public static final Gfo_invk_cmd Noop = new Gfo_invk_cmd(Gfo_invk_.Noop, "", GfoMsg_.Null); + public static Gfo_invk_cmd New_by_key(Gfo_invk itm, String cmd) {return New_by_val(itm, cmd, null);} + public static Gfo_invk_cmd New_by_val(Gfo_invk itm, String cmd, Object val) { + return new Gfo_invk_cmd(itm, cmd, (val == null) ? GfoMsg_.Null : GfoMsg_.new_parse_(cmd).Add("v", val)); + } +} diff --git a/100_core/src/gplx/Gfo_invk_cmd_mgr.java b/100_core/src/gplx/Gfo_invk_cmd_mgr.java new file mode 100644 index 000000000..973bfc1f8 --- /dev/null +++ b/100_core/src/gplx/Gfo_invk_cmd_mgr.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 +*/ +package gplx; +import gplx.core.primitives.*; +public class Gfo_invk_cmd_mgr { + public Gfo_invk_cmd_mgr Add_cmd_many(Gfo_invk invk, String... keys) { + for (String key : keys) + list.Add(GfoInvkCmdItm.new_(key, invk)); + return this; + } + public Gfo_invk_cmd_mgr Add_cmd(String key, Gfo_invk invk) { + list.Add(GfoInvkCmdItm.new_(key, invk)); + return this; + } + public Gfo_invk_cmd_mgr Add_mgr(String key, Gfo_invk invk) { + list.Add(GfoInvkCmdItm.new_(key, invk).Type_isMgr_(true)); + return this; + } + public Gfo_invk_cmd_mgr Add_xtn(Gfo_invk xtn) { + list.Add(GfoInvkCmdItm.new_("xtn", xtn).Type_isXtn_(true)); + return this; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m, Object host) { + for (int i = 0; i < list.Count(); i++) { + GfoInvkCmdItm itm = (GfoInvkCmdItm)list.Get_at(i); + if (itm.Type_isXtn()) { + Object invkVal = itm.Invk().Invk(ctx, ikey, k, m); + if (invkVal != Gfo_invk_.Rv_unhandled) return invkVal; + } + if (!ctx.Match(k, itm.Key())) continue; + if (itm.Type_isMgr()) return itm.Invk(); + Object rv = null; + m.Add("host", host); + rv = itm.Invk().Invk(ctx, ikey, k, m); + return rv == Gfo_invk_.Rv_host ? host : rv; // if returning "this" return host + } + return Unhandled; + } + public static final String_obj_val Unhandled = String_obj_val.new_("Gfo_invk_cmd_mgr Unhandled"); + List_adp list = List_adp_.New(); + public static Gfo_invk_cmd_mgr new_() {return new Gfo_invk_cmd_mgr();} Gfo_invk_cmd_mgr() {} +} +class GfoInvkCmdItm { + public String Key() {return key;} private String key; + public Gfo_invk Invk() {return invk;} Gfo_invk invk; + public boolean Type_isMgr() {return type_isMgr;} public GfoInvkCmdItm Type_isMgr_(boolean v) {type_isMgr = v; return this;} private boolean type_isMgr; + public boolean Type_isXtn() {return type_isXtn;} public GfoInvkCmdItm Type_isXtn_(boolean v) {type_isXtn = v; return this;} private boolean type_isXtn; + public static GfoInvkCmdItm new_(String key, Gfo_invk invk) { + GfoInvkCmdItm rv = new GfoInvkCmdItm(); + rv.key = key; rv.invk = invk; + return rv; + } GfoInvkCmdItm() {} +} diff --git a/100_core/src/gplx/Gfo_invk_cmd_mgr_owner.java b/100_core/src/gplx/Gfo_invk_cmd_mgr_owner.java new file mode 100644 index 000000000..9de4a11e6 --- /dev/null +++ b/100_core/src/gplx/Gfo_invk_cmd_mgr_owner.java @@ -0,0 +1,19 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +public interface Gfo_invk_cmd_mgr_owner { + Gfo_invk_cmd_mgr InvkMgr(); +} diff --git a/100_core/src/gplx/Gfo_invk_root_wkr.java b/100_core/src/gplx/Gfo_invk_root_wkr.java new file mode 100644 index 000000000..9ae5e3f11 --- /dev/null +++ b/100_core/src/gplx/Gfo_invk_root_wkr.java @@ -0,0 +1,19 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +public interface Gfo_invk_root_wkr { + Object Run_str_for(Gfo_invk invk, GfoMsg msg); +} diff --git a/100_core/src/gplx/Gfo_invk_to_str.java b/100_core/src/gplx/Gfo_invk_to_str.java new file mode 100644 index 000000000..72c340b77 --- /dev/null +++ b/100_core/src/gplx/Gfo_invk_to_str.java @@ -0,0 +1,42 @@ +/* +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; +import gplx.langs.gfs.*; +public class Gfo_invk_to_str { + public static GfoMsg ReadMsg(Gfo_invk invk, String k) { + GfsCtx ctx = GfsCtx.wtr_(); + GfoMsg m = GfoMsg_.rdr_(k); + invk.Invk(ctx, 0, k, m); + String invkKey = GfsCore.Instance.FetchKey(invk); + GfoMsg root = GfoMsg_.new_cast_(invkKey); + root.Subs_add(m); + return root; + } + public static GfoMsg WriteMsg(Gfo_invk invk, String k, Object... ary) {return WriteMsg(GfsCore.Instance.FetchKey(invk), invk, k, ary);} + public static GfoMsg WriteMsg(String invkKey, Gfo_invk invk, String k, Object... ary) { + GfsCtx ctx = GfsCtx.wtr_(); + GfoMsg m = GfoMsg_.wtr_(); + invk.Invk(ctx, 0, k, m); + GfoMsg rv = GfoMsg_.new_cast_(k); + for (int i = 0; i < m.Args_count(); i++) { + Keyval kv = m.Args_getAt(i); + rv.Add(kv.Key(), ary[i]); + } + GfoMsg root = GfoMsg_.new_cast_(invkKey); + root.Subs_add(rv); + return root; + } +} diff --git a/100_core/src/gplx/Gfo_log.java b/100_core/src/gplx/Gfo_log.java new file mode 100644 index 000000000..4278ee181 --- /dev/null +++ b/100_core/src/gplx/Gfo_log.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; +public interface Gfo_log { + List_adp Itms(); + Gfo_log Itms_(List_adp v); + void Warn(String msg, Object... args); + void Note(String msg, Object... args); + void Info(String msg, Object... args); + void Prog(String msg, Object... args); + void Flush(); +} diff --git a/100_core/src/gplx/Gfo_log_.java b/100_core/src/gplx/Gfo_log_.java new file mode 100644 index 000000000..aeff5ae49 --- /dev/null +++ b/100_core/src/gplx/Gfo_log_.java @@ -0,0 +1,34 @@ +/* +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; +import gplx.core.logs.*; +public class Gfo_log_ { + public static Gfo_log Instance = new Gfo_log__mem(); + public static Gfo_log Instance__set(Gfo_log v) { + v.Itms_(Instance.Itms()); + Instance = v; + return v; + } + public static final String File__fmt = "yyyyMMdd_HHmmss", File__ext = ".log"; + public static Io_url New_url(Io_url dir) { + return dir.GenSubFil(Datetime_now.Get().XtoUtc().XtoStr_fmt(Gfo_log_.File__fmt) + Gfo_log_.File__ext); + } + public static Gfo_log New_file(Io_url dir) { + Io_url url = dir.GenSubFil(Datetime_now.Get().XtoStr_fmt(File__fmt) + File__ext); + Gfo_log__file.Delete_old_files(dir, Gfo_log_.Instance); + return new Gfo_log__file(url, new Gfo_log_itm_wtr__csv()); + } +} diff --git a/100_core/src/gplx/Gfo_log_bfr.java b/100_core/src/gplx/Gfo_log_bfr.java new file mode 100644 index 000000000..2f45eabbe --- /dev/null +++ b/100_core/src/gplx/Gfo_log_bfr.java @@ -0,0 +1,27 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +public class Gfo_log_bfr { + private Bry_bfr bfr = Bry_bfr_.Reset(255); + public Gfo_log_bfr Add(String s) { + bfr.Add_str_a7(Datetime_now.Get().XtoUtc().XtoStr_fmt_yyyyMMdd_HHmmss_fff()); + bfr.Add_byte_space(); + bfr.Add_str_u8(s); + bfr.Add_byte_nl(); + return this; + } + public String Xto_str() {return bfr.To_str_and_clear();} +} diff --git a/100_core/src/gplx/Gfo_usr_dlg.java b/100_core/src/gplx/Gfo_usr_dlg.java new file mode 100644 index 000000000..d53eac78d --- /dev/null +++ b/100_core/src/gplx/Gfo_usr_dlg.java @@ -0,0 +1,33 @@ +/* +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; +public interface Gfo_usr_dlg extends Cancelable { + void Canceled_y_(); void Canceled_n_(); + Gfo_usr_dlg__log Log_wkr(); void Log_wkr_(Gfo_usr_dlg__log v); + Gfo_usr_dlg__gui Gui_wkr(); void Gui_wkr_(Gfo_usr_dlg__gui v); + String Log_many(String grp_key, String msg_key, String fmt, Object... args); + String Warn_many(String grp_key, String msg_key, String fmt, Object... args); + Err Fail_many(String grp_key, String msg_key, String fmt, Object... args); + String Prog_many(String grp_key, String msg_key, String fmt, Object... args); + String Prog_none(String grp_key, String msg_key, String fmt); + String Note_many(String grp_key, String msg_key, String fmt, Object... args); + String Note_none(String grp_key, String msg_key, String fmt); + String Note_gui_none(String grp_key, String msg_key, String fmt); + String Prog_one(String grp_key, String msg_key, String fmt, Object arg); + String Prog_direct(String msg); + String Log_direct(String msg); + String Plog_many(String grp_key, String msg_key, String fmt, Object... args); +} diff --git a/100_core/src/gplx/Gfo_usr_dlg_.java b/100_core/src/gplx/Gfo_usr_dlg_.java new file mode 100644 index 000000000..4934fd343 --- /dev/null +++ b/100_core/src/gplx/Gfo_usr_dlg_.java @@ -0,0 +1,72 @@ +/* +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; +public class Gfo_usr_dlg_ { + private static Gfo_usr_dlg_base test__list, test__show; + public static Gfo_usr_dlg Instance = Gfo_usr_dlg_noop.Instance; // NOTE: global instance which can be reassigned + public static final Gfo_usr_dlg Noop = Gfo_usr_dlg_noop.Instance; + public static Gfo_usr_dlg__gui Test__list__init() { + if (test__list == null) + test__list = new Gfo_usr_dlg_base(Gfo_usr_dlg__log_.Noop, Gfo_usr_dlg__gui_.Test); + Gfo_usr_dlg__gui_.Test.Clear(); + Instance = test__list; + return Gfo_usr_dlg__gui_.Test; + } + public static String Test__list__term__get_1st() { + Instance = Noop; + String[] rv = ((Gfo_usr_dlg__gui_test)test__list.Gui_wkr()).Warns().To_str_ary_and_clear(); + return rv.length == 0 ? "" : rv[0]; + } + public static void Test__show__init() { + if (test__show == null) + test__show = new Gfo_usr_dlg_base(Gfo_usr_dlg__log_.Noop, Gfo_usr_dlg__gui_.Console); + Instance = test__show; + } + public static void Test__show__term() { + Instance = Noop; + } + public static Gfo_usr_dlg Test() { + if (test__list == null) + test__list = new Gfo_usr_dlg_base(Gfo_usr_dlg__log_.Noop, Gfo_usr_dlg__gui_.Test); + return test__list; + } + public static Gfo_usr_dlg Test_console() { + if (test_console == null) + test_console = new Gfo_usr_dlg_base(Gfo_usr_dlg__log_.Noop, Gfo_usr_dlg__gui_.Console); + return test_console; + } private static Gfo_usr_dlg_base test_console; +} +class Gfo_usr_dlg_noop implements Gfo_usr_dlg { + public boolean Canceled() {return false;} public void Canceled_y_() {} public void Canceled_n_() {} + public void Cancel() {} + public void Clear() {} + public Gfo_usr_dlg__log Log_wkr() {return Gfo_usr_dlg__log_.Noop;} public void Log_wkr_(Gfo_usr_dlg__log v) {} + public Gfo_usr_dlg__gui Gui_wkr() {return Gfo_usr_dlg__gui_.Noop;} public void Gui_wkr_(Gfo_usr_dlg__gui v) {} + public String Log_many(String grp_key, String msg_key, String fmt, Object... args) {return "";} + public String Warn_many(String grp_key, String msg_key, String fmt, Object... args) {return "";} + public Err Fail_many(String grp_key, String msg_key, String fmt, Object... args) {return Err_.new_wo_type(fmt);} + public String Prog_many(String grp_key, String msg_key, String fmt, Object... args) {return "";} + public String Prog_none(String grp_key, String msg_key, String fmt) {return "";} + public String Note_many(String grp_key, String msg_key, String fmt, Object... args) {return "";} + public String Note_none(String grp_key, String msg_key, String fmt) {return "";} + public String Note_gui_none(String grp_key, String msg_key, String fmt) {return "";} + public String Prog_one(String grp_key, String msg_key, String fmt, Object arg) {return "";} + public String Prog_direct(String msg) {return "";} + public String Log_direct(String msg) {return "";} + public String Plog_many(String grp_key, String msg_key, String fmt, Object... args) {return "";} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return this;} + public static final Gfo_usr_dlg_noop Instance = new Gfo_usr_dlg_noop(); Gfo_usr_dlg_noop() {} +} diff --git a/100_core/src/gplx/Gfo_usr_dlg__gui.java b/100_core/src/gplx/Gfo_usr_dlg__gui.java new file mode 100644 index 000000000..36555707d --- /dev/null +++ b/100_core/src/gplx/Gfo_usr_dlg__gui.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; +import gplx.core.lists.rings.*; +public interface Gfo_usr_dlg__gui { + void Clear(); + Ring__string Prog_msgs(); + void Write_prog(String text); + void Write_note(String text); + void Write_warn(String text); + void Write_stop(String text); +} diff --git a/100_core/src/gplx/Gfo_usr_dlg__gui_.java b/100_core/src/gplx/Gfo_usr_dlg__gui_.java new file mode 100644 index 000000000..5b32428fa --- /dev/null +++ b/100_core/src/gplx/Gfo_usr_dlg__gui_.java @@ -0,0 +1,50 @@ +/* +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; +import gplx.core.consoles.*; import gplx.core.lists.rings.*; +public class Gfo_usr_dlg__gui_ { + public static final Gfo_usr_dlg__gui Noop = new Gfo_usr_dlg__gui_noop(); + public static final Gfo_usr_dlg__gui Console = new Gfo_usr_dlg__gui_console(); + public static final Gfo_usr_dlg__gui Test = new Gfo_usr_dlg__gui_test(); + public static final Gfo_usr_dlg__gui Mem = new Gfo_usr_dlg__gui_mem_string(); + public static String Mem_file() {return ((Gfo_usr_dlg__gui_mem_string)Mem).file;} +} +class Gfo_usr_dlg__gui_noop implements Gfo_usr_dlg__gui { + public void Clear() {} + public Ring__string Prog_msgs() {return ring;} Ring__string ring = new Ring__string().Max_(0); + public void Write_prog(String text) {} + public void Write_note(String text) {} + public void Write_warn(String text) {} + public void Write_stop(String text) {} +} +class Gfo_usr_dlg__gui_console implements Gfo_usr_dlg__gui { + private final Console_adp__sys console = Console_adp__sys.Instance; + public void Clear() {} + public Ring__string Prog_msgs() {return ring;} private final Ring__string ring = new Ring__string().Max_(0); + public void Write_prog(String text) {console.Write_tmp(text);} + public void Write_note(String text) {console.Write_str_w_nl(text);} + public void Write_warn(String text) {console.Write_str_w_nl(text);} + public void Write_stop(String text) {console.Write_str_w_nl(text);} +} +class Gfo_usr_dlg__gui_mem_string implements Gfo_usr_dlg__gui { + public String file = ""; + public void Clear() {file = "";} + public Ring__string Prog_msgs() {return ring;} private final Ring__string ring = new Ring__string().Max_(0); + public void Write_prog(String text) {file += text + "\n";} + public void Write_note(String text) {file += text + "\n";} + public void Write_warn(String text) {file += text + "\n";} + public void Write_stop(String text) {file += text + "\n";} +} diff --git a/100_core/src/gplx/Gfo_usr_dlg__gui_test.java b/100_core/src/gplx/Gfo_usr_dlg__gui_test.java new file mode 100644 index 000000000..e5c84763a --- /dev/null +++ b/100_core/src/gplx/Gfo_usr_dlg__gui_test.java @@ -0,0 +1,27 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +import gplx.core.lists.rings.*; +public class Gfo_usr_dlg__gui_test implements Gfo_usr_dlg__gui { + public List_adp Warns() {return warns;} private final List_adp warns = List_adp_.New(); + public List_adp Msgs() {return msgs;} private final List_adp msgs = List_adp_.New(); + public Ring__string Prog_msgs() {return ring;} private final Ring__string ring = new Ring__string().Max_(0); + public void Clear() {msgs.Clear(); warns.Clear();} + public void Write_prog(String text) {msgs.Add(text);} + public void Write_note(String text) {msgs.Add(text);} + public void Write_warn(String text) {warns.Add(text);} + public void Write_stop(String text) {msgs.Add(text);} +} diff --git a/100_core/src/gplx/Gfo_usr_dlg__log.java b/100_core/src/gplx/Gfo_usr_dlg__log.java new file mode 100644 index 000000000..ca62c6ecb --- /dev/null +++ b/100_core/src/gplx/Gfo_usr_dlg__log.java @@ -0,0 +1,29 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +public interface Gfo_usr_dlg__log extends Gfo_invk { + boolean Enabled(); void Enabled_(boolean v); + boolean Queue_enabled(); void Queue_enabled_(boolean v); + Io_url Log_dir(); void Log_dir_(Io_url v); + Io_url Session_dir(); + Io_url Session_fil(); + void Log_msg_to_url_fmt(Io_url url, String fmt, Object... args); + void Log_to_session(String txt); + void Log_to_session_fmt(String fmt, Object... args); + void Log_to_session_direct(String txt); + void Log_to_err(String txt); + void Log_term(); +} diff --git a/100_core/src/gplx/Gfo_usr_dlg__log_.java b/100_core/src/gplx/Gfo_usr_dlg__log_.java new file mode 100644 index 000000000..b9dce63f6 --- /dev/null +++ b/100_core/src/gplx/Gfo_usr_dlg__log_.java @@ -0,0 +1,33 @@ +/* +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; +public class Gfo_usr_dlg__log_ { + public static final Gfo_usr_dlg__log Noop = new Gfo_usr_dlg__log_noop(); +} +class Gfo_usr_dlg__log_noop implements Gfo_usr_dlg__log { + public Io_url Session_fil() {return Io_url_.Empty;} + public Io_url Session_dir() {return Io_url_.Empty;} + public Io_url Log_dir() {return Io_url_.Empty;} public void Log_dir_(Io_url v) {} + public boolean Enabled() {return enabled;} public void Enabled_(boolean v) {enabled = v;} private boolean enabled; + public boolean Queue_enabled() {return queue_enabled;} public void Queue_enabled_(boolean v) {queue_enabled = v;} private boolean queue_enabled; + public void Log_msg_to_url_fmt(Io_url url, String fmt, Object... args) {} + public void Log_to_session_fmt(String fmt, Object... args) {} + public void Log_to_session(String txt) {} + public void Log_to_session_direct(String txt) {} + public void Log_to_err(String txt) {} + public void Log_term() {} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return this;} +} diff --git a/100_core/src/gplx/Gfo_usr_dlg__log_base.java b/100_core/src/gplx/Gfo_usr_dlg__log_base.java new file mode 100644 index 000000000..39a85a638 --- /dev/null +++ b/100_core/src/gplx/Gfo_usr_dlg__log_base.java @@ -0,0 +1,119 @@ +/* +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; +import gplx.core.strings.*; import gplx.core.consoles.*; import gplx.core.brys.fmtrs.*; +public class Gfo_usr_dlg__log_base implements Gfo_usr_dlg__log { + private int archive_dirs_max = 8; + private Io_url log_dir, err_fil; + private Ordered_hash queued_list = Ordered_hash_.New(); + private Bry_fmtr fmtr = Bry_fmtr.New__tmp(); private Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); + public boolean Queue_enabled() {return queue_enabled;} public void Queue_enabled_(boolean v) {queue_enabled = v; if (!v) this.Flush();} private boolean queue_enabled; + public boolean Enabled() {return enabled;} public void Enabled_(boolean v) {enabled = v;} private boolean enabled = true; + public Io_url Session_dir() {return session_dir;} private Io_url session_dir; + public Io_url Session_fil() {return session_fil;} private Io_url session_fil; + private void Flush() { + int queued_len = queued_list.Count(); + for (int i = 0; i < queued_len; i++) { + Usr_log_fil fil = (Usr_log_fil)queued_list.Get_at(i); + if (fil.Url() == null) { + fil.Url_(session_dir.GenSubFil("session.txt")); + } + fil.Flush(); + } + } + public Io_url Log_dir() {return log_dir;} + public void Log_dir_(Io_url log_dir) { + this.log_dir = log_dir; + session_dir = log_dir.GenSubDir(Datetime_now.Get().XtoStr_fmt_yyyyMMdd_HHmmss_fff()); + session_fil = session_dir.GenSubFil("session.txt"); + err_fil = session_dir.GenSubFil("err.txt"); + } + public void Log_term() { + if (!enabled) return; + Io_url[] archive_dirs = Io_mgr.Instance.QueryDir_args(log_dir).DirInclude_().DirOnly_().ExecAsUrlAry(); + int archive_dirs_len = archive_dirs.length; + int session_cutoff = archive_dirs_len - archive_dirs_max; + for (int i = 0; i < session_cutoff; i++) { + Io_url archive_dir = archive_dirs[i]; + Io_mgr.Instance.DeleteDirDeep(archive_dir); + this.Log_to_session("archive dir del: " + session_dir.Raw()); + } + this.Log_to_session("app term"); + } + public void Log_info(boolean warn, String s) {if (warn) Log_to_err(s); else Log_to_session(s);} + public void Log_msg_to_url_fmt(Io_url url, String fmt, Object... args) { + if (!enabled) return; + String msg = Bld_msg(String_.new_u8(fmtr.Fmt_(fmt).Bld_bry_many(tmp_bfr, args))); + Log_msg(url, msg); + Log_msg(session_fil, msg); + } + public void Log_to_session_fmt(String fmt, Object... args) {Log_to_session(String_.new_u8(fmtr.Fmt_(fmt).Bld_bry_many(tmp_bfr, args)));} + public void Log_to_session(String s) { + if (!enabled) return; + String line = Bld_msg(s); + Log_msg(session_fil, line); + } + public void Log_to_session_direct(String s) { + if (!enabled) return; + Log_msg(session_fil, s); + } + public void Log_to_err(String s) { + if (!enabled) return; + try { + String line = Bld_msg(s); + Log_msg(session_fil, line); + Log_msg(err_fil, line); + } + catch (Exception e) {Err_.Noop(e);} // java.lang.StringBuilder can throw exceptions in some situations when called on a different thread; ignore errors + } private String_bldr sb = String_bldr_.new_thread(); // NOTE: use java.lang.StringBuffer to try to avoid random exceptions when called on a different thread + private String Bld_msg(String s) {return sb.Add(Datetime_now.Get_force().XtoUtc().XtoStr_fmt_yyyyMMdd_HHmmss_fff()).Add(" ").Add(s).Add_char_nl().To_str_and_clear();} + private void Log_msg(Io_url url, String txt) { + if (queue_enabled) { + String url_raw = url == null ? "mem" : url.Raw(); + Usr_log_fil fil = (Usr_log_fil)queued_list.Get_by(url_raw); + if (fil == null) { + fil = new Usr_log_fil(url); + queued_list.Add(url_raw, fil); + } + fil.Add(txt); + } + else + Io_mgr.Instance.AppendFilStr(url, txt); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_enabled_)) enabled = m.ReadYn("v"); + else if (ctx.Match(k, Invk_archive_dirs_max_)) archive_dirs_max = m.ReadInt("v"); + else if (ctx.Match(k, Invk_log_dir_)) log_dir = m.ReadIoUrl("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String Invk_enabled_ = "enabled_", Invk_archive_dirs_max_ = "archive_dirs_max_", Invk_log_dir_ = "log_dir_"; + static final String Dir_name_log = "log"; + public static final Gfo_usr_dlg__log_base Instance = new Gfo_usr_dlg__log_base(); +} +class Usr_log_fil { + public Usr_log_fil(Io_url url) {this.url = url;} + public Io_url Url() {return url;} public Usr_log_fil Url_(Io_url v) {url = v; return this;} Io_url url; + public void Add(String text) {sb.Add(text);} String_bldr sb = String_bldr_.new_(); + public void Flush() { + if (sb.Count() == 0) return; + try { + Io_mgr.Instance.AppendFilStr(url, sb.To_str_and_clear()); + } + catch (Exception e) { + Console_adp__sys.Instance.Write_str_w_nl(Err_.Message_gplx_full(e)); + } + } +} diff --git a/100_core/src/gplx/Gfo_usr_dlg_base.java b/100_core/src/gplx/Gfo_usr_dlg_base.java new file mode 100644 index 000000000..be6b213b0 --- /dev/null +++ b/100_core/src/gplx/Gfo_usr_dlg_base.java @@ -0,0 +1,64 @@ +/* +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; +import gplx.core.brys.fmtrs.*; +public class Gfo_usr_dlg_base implements Gfo_usr_dlg { + private final Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); + private final Bry_fmtr tmp_fmtr = Bry_fmtr.New__tmp().Fail_when_invalid_escapes_(false); // do not fail b/c msgs may contain excerpt of random text; EX:[[User:A|~A~]] DATE:2014-11-28 + public Gfo_usr_dlg_base(Gfo_usr_dlg__log log_wkr, Gfo_usr_dlg__gui gui_wkr) {this.log_wkr = log_wkr; this.gui_wkr = gui_wkr;} + public Gfo_usr_dlg__log Log_wkr() {return log_wkr;} public void Log_wkr_(Gfo_usr_dlg__log v) {log_wkr = v;} private Gfo_usr_dlg__log log_wkr; + public Gfo_usr_dlg__gui Gui_wkr() {return gui_wkr;} public void Gui_wkr_(Gfo_usr_dlg__gui v) {gui_wkr = v;} private Gfo_usr_dlg__gui gui_wkr; + public boolean Canceled() {return canceled;} public void Canceled_y_() {canceled = true;} public void Canceled_n_() {canceled = false;} private boolean canceled; + public void Cancel() {canceled = true;} + public String Log_many(String grp_key, String msg_key, String fmt, Object... args) {String rv = Bld_msg_many(grp_key, msg_key, fmt, args ); log_wkr.Log_to_session(rv); return rv;} + public String Warn_many(String grp_key, String msg_key, String fmt, Object... args) {String rv = Bld_msg_many(grp_key, msg_key, fmt, args ); log_wkr.Log_to_err(rv); gui_wkr.Write_warn(rv); return rv;} + public String Prog_many(String grp_key, String msg_key, String fmt, Object... args) {String rv = Bld_msg_many(grp_key, msg_key, fmt, args ); gui_wkr.Write_prog(rv); return rv;} + public String Prog_one(String grp_key, String msg_key, String fmt, Object arg) {String rv = Bld_msg_one (grp_key, msg_key, fmt, arg ); gui_wkr.Write_prog(rv); return rv;} + public String Prog_none(String grp_key, String msg_key, String fmt) {String rv = Bld_msg_none(grp_key, msg_key, fmt ); gui_wkr.Write_prog(rv); return rv;} + public String Prog_direct(String msg) { gui_wkr.Write_prog(msg); return msg;} + public String Log_direct(String msg) { log_wkr.Log_to_session(msg); return msg;} + public String Note_many(String grp_key, String msg_key, String fmt, Object... args) {String rv = Bld_msg_many(grp_key, msg_key, fmt, args ); log_wkr.Log_to_session(rv); gui_wkr.Write_note(rv); return rv;} + public String Note_none(String grp_key, String msg_key, String fmt) {String rv = Bld_msg_none(grp_key, msg_key, fmt ); log_wkr.Log_to_session(rv); gui_wkr.Write_note(rv); return rv;} + public String Note_gui_none(String grp_key, String msg_key, String fmt) {String rv = Bld_msg_none(grp_key, msg_key, fmt ); gui_wkr.Write_note(rv); return rv;} + public String Plog_many(String grp_key, String msg_key, String fmt, Object... args) { + String rv = Log_many(grp_key, msg_key, fmt, args); + return Prog_direct(rv); + } + public Err Fail_many(String grp_key, String msg_key, String fmt, Object... args) { + Err rv = Err_.new_wo_type(Bld_msg_many(grp_key, msg_key, fmt, args)); + log_wkr.Log_to_err(rv.To_str__full()); + return rv; + } + private String Bld_msg_many(String grp_key, String msg_key, String fmt, Object[] args) { + synchronized (tmp_fmtr) { + try { + tmp_fmtr.Fmt_(fmt).Bld_bfr_many(tmp_bfr, args); + return tmp_bfr.To_str_and_clear(); + } + catch (Exception e) { // NOTE: can fail if fmt has ~{}; callers should proactively remove, but for now, just return fmt if fails; EX:Page_sync and en.w:Web_crawler; DATE:2016-11-17 + Err_.Noop(e); + return fmt; + } + } + } + private String Bld_msg_one(String grp_key, String msg_key, String fmt, Object val) { + synchronized (tmp_fmtr) { + tmp_fmtr.Fmt_(fmt).Bld_bfr_one(tmp_bfr, val); + return tmp_bfr.To_str_and_clear(); + } + } + private String Bld_msg_none(String grp_key, String msg_key, String fmt) {return fmt;} +} diff --git a/100_core/src/gplx/GfsCtx.java b/100_core/src/gplx/GfsCtx.java new file mode 100644 index 000000000..ea98afa8b --- /dev/null +++ b/100_core/src/gplx/GfsCtx.java @@ -0,0 +1,63 @@ +/* +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; +public class GfsCtx { + public Ordered_hash Vars() {return vars;} Ordered_hash vars = Ordered_hash_.New(); + public boolean Fail_if_unhandled() {return fail_if_unhandled;} public GfsCtx Fail_if_unhandled_(boolean v) {fail_if_unhandled = v; return this;} private boolean fail_if_unhandled; + public Gfo_usr_dlg Usr_dlg() {return usr_dlg;} public GfsCtx Usr_dlg_(Gfo_usr_dlg v) {usr_dlg = v; return this;} Gfo_usr_dlg usr_dlg; + public boolean Help_browseMode() {return help_browseMode;} public GfsCtx Help_browseMode_(boolean v) {help_browseMode = v; return this;} private boolean help_browseMode; + public List_adp Help_browseList() {return help_browseList;} List_adp help_browseList = List_adp_.New(); + public Object MsgSrc() {return msgSrc;} public GfsCtx MsgSrc_(Object v) {msgSrc = v; return this;} Object msgSrc; + public boolean Match(String k, String match) { + if (help_browseMode) { + help_browseList.Add(match); + return false; + } + else + return String_.Eq(k, match); + } + public boolean MatchPriv(String k, String match) {return help_browseMode ? false : String_.Eq(k, match);} + public boolean MatchIn(String k, String... match) { + if (help_browseMode) { + for (String i : match) + help_browseList.Add(i); + return false; + } + return String_.In(k, match); + } + public boolean Write_note(String fmt, Object... ary) {UsrDlg_.Instance.Note(fmt, ary); return false;} + public boolean Write_warn(String fmt, Object... ary) {UsrDlg_.Instance.Note("! " + fmt, ary); return false;} + public boolean Write_stop(UsrMsg umsg) {UsrDlg_.Instance.Note("* " + umsg.To_str()); return false;} + public boolean Write_stop(String fmt, Object... ary) {UsrDlg_.Instance.Note("* " + fmt, ary); return false;} + public boolean Deny() {return deny;} private boolean deny; + public static final GfsCtx Instance = new GfsCtx(); + public static GfsCtx new_() {return new GfsCtx();} GfsCtx() {} + public static GfsCtx rdr_() { + GfsCtx rv = new GfsCtx(); + rv.deny = true; + rv.mode = "read"; + return rv; + } + public static GfsCtx wtr_() { + GfsCtx rv = new GfsCtx(); + rv.deny = true; + rv.mode = Mode_write; + return rv; + } + public String Mode() {return mode;} public GfsCtx Mode_(String v) {mode = v; return this;} private String mode = "regular"; + public static final String Mode_write = "write"; + public static final int Ikey_null = -1; +} diff --git a/100_core/src/gplx/Guid_adp.java b/100_core/src/gplx/Guid_adp.java new file mode 100644 index 000000000..93a31c2e3 --- /dev/null +++ b/100_core/src/gplx/Guid_adp.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; +public class Guid_adp { + public Guid_adp(java.util.UUID guid) {this.guid = guid;} java.util.UUID guid; + public String To_str() {return guid.toString();} +} \ No newline at end of file diff --git a/100_core/src/gplx/Guid_adp_.java b/100_core/src/gplx/Guid_adp_.java new file mode 100644 index 000000000..4560a276a --- /dev/null +++ b/100_core/src/gplx/Guid_adp_.java @@ -0,0 +1,23 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +public class Guid_adp_ { + public static final String Cls_ref_name = "Guid"; + public static final Guid_adp Empty = Parse("00000000-0000-0000-0000-000000000000"); + public static String New_str() {return New().To_str();} + public static Guid_adp New() {return new Guid_adp(java.util.UUID.randomUUID());} + public static Guid_adp Parse(String s) {return new Guid_adp(java.util.UUID.fromString(s));} +} \ No newline at end of file diff --git a/100_core/src/gplx/Guid_adp__tst.java b/100_core/src/gplx/Guid_adp__tst.java new file mode 100644 index 000000000..4eac0e7dd --- /dev/null +++ b/100_core/src/gplx/Guid_adp__tst.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; +import org.junit.*; +public class Guid_adp__tst { + @Test public void parse() { + tst_parse_("467ffb41-cdfe-402f-b22b-be855425784b"); + } + void tst_parse_(String s) { + Guid_adp uuid = Guid_adp_.Parse(s); + Tfds.Eq(uuid.To_str(), s); + } +} diff --git a/100_core/src/gplx/Hash_adp.java b/100_core/src/gplx/Hash_adp.java new file mode 100644 index 000000000..e1ca3d844 --- /dev/null +++ b/100_core/src/gplx/Hash_adp.java @@ -0,0 +1,29 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +public interface Hash_adp extends gplx.core.lists.EnumerAble { + int Count(); + boolean Has(Object key); + Object Get_by(Object key); + Object Get_by_or_fail(Object key); + void Add(Object key, Object val); + Hash_adp Add_and_more(Object key, Object val); + void Add_as_key_and_val(Object val); + boolean Add_if_dupe_use_1st(Object key, Object val); + void Add_if_dupe_use_nth(Object key, Object val); + void Del(Object key); + void Clear(); +} diff --git a/100_core/src/gplx/Hash_adp_.java b/100_core/src/gplx/Hash_adp_.java new file mode 100644 index 000000000..8f6476b9b --- /dev/null +++ b/100_core/src/gplx/Hash_adp_.java @@ -0,0 +1,36 @@ +/* +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; +import gplx.core.primitives.*; +public class Hash_adp_ { + public static Hash_adp New() {return new Hash_adp_obj();} + public static final Hash_adp Noop = new Hash_adp_noop(); +} +class Hash_adp_obj extends gplx.core.lists.Hash_adp_base implements Hash_adp {}//_20110428 +class Hash_adp_noop implements Hash_adp { + public int Count() {return 0;} + public boolean Has(Object key) {return false;} + public Object Get_by(Object key) {return null;} + public Object Get_by_or_fail(Object key) {throw Err_.new_missing_key(Object_.Xto_str_strict_or_null_mark(key));} + public void Add(Object key, Object val) {} + public Hash_adp Add_and_more(Object key, Object val) {return this;} + public void Add_as_key_and_val(Object val) {} + public void Add_if_dupe_use_nth(Object key, Object val) {} + public boolean Add_if_dupe_use_1st(Object key, Object val) {return false;} + public void Del(Object key) {} + public void Clear() {} + public java.util.Iterator iterator() {return gplx.core.lists.Iterator_null.Instance;} +} diff --git a/100_core/src/gplx/Hash_adp_bry.java b/100_core/src/gplx/Hash_adp_bry.java new file mode 100644 index 000000000..8cb6afa8e --- /dev/null +++ b/100_core/src/gplx/Hash_adp_bry.java @@ -0,0 +1,205 @@ +/* +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; +import gplx.core.primitives.*; +import gplx.core.intls.*; +public class Hash_adp_bry extends gplx.core.lists.Hash_adp_base implements Hash_adp { + private final Hash_adp_bry_itm_base proto, key_ref; + Hash_adp_bry(Hash_adp_bry_itm_base proto) { + this.proto = proto; + this.key_ref = proto.New(); + } + @Override protected Object Fetch_base(Object key) {synchronized (key_ref) {return super.Fetch_base(key_ref.Init((byte[])key));}} // TS: DATE:2016-07-06 + @Override protected void Del_base(Object key) {synchronized (key_ref) {super.Del_base(key_ref.Init((byte[])key));}}// TS: DATE:2016-07-06 + @Override protected boolean Has_base(Object key) {synchronized (key_ref) {return super.Has_base(key_ref.Init((byte[])key));}}// TS: DATE:2016-07-06 + public int Get_as_int(byte[] key) {return Get_as_int(key, 0, key.length);} + public int Get_as_int(byte[] key, int bgn, int end) { + int rv = Get_as_int_or(key, bgn, end, Int_.Min_value); if (rv == Int_.Min_value) throw Err_.new_("core", "unknown key", "key", key); + return rv; + } + public int Get_as_int_or(byte[] key, int or) {return Get_as_int_or(key, 0, key.length, or);} + public int Get_as_int_or(byte[] key, int bgn, int end, int or) { + Object o = Get_by_mid(key, bgn, end); + return (o == null) ? or : ((Int_obj_val)o).Val(); + } + public byte Get_as_byte_or(byte[] key, byte or) {return Get_as_byte_or(key, 0, key.length, or);} + public byte Get_as_byte_or(byte[] key, int bgn, int end, byte or) { + Object o = Get_by_mid(key, bgn, end); + return o == null ? or : ((Byte_obj_val)o).Val(); + } + public Object Get_by_bry(byte[] src) {synchronized (key_ref) {return super.Fetch_base(key_ref.Init(src));}} // TS: DATE:2016-07-06 + public Object Get_by_mid(byte[] src, int bgn, int end) {synchronized (key_ref) {return super.Fetch_base(key_ref.Init(src, bgn, end));}}// TS: DATE:2016-07-06 + public Hash_adp_bry Add_byte_int(byte key, int val) {this.Add_base(new byte[]{key}, new Int_obj_val(val)); return this;} + public Hash_adp_bry Add_bry_byte(byte[] key, byte val) {this.Add_base(key, Byte_obj_val.new_(val)); return this;} + public Hash_adp_bry Add_bry_int(byte[] key, int val) {this.Add_base(key, new Int_obj_val(val)); return this;} + public Hash_adp_bry Add_bry_bry(byte[] key) {this.Add_base(key, key); return this;} + public Hash_adp_bry Add_str_byte(String key, byte val) {this.Add_base(Bry_.new_u8(key), Byte_obj_val.new_(val)); return this;} + public Hash_adp_bry Add_str_int(String key, int val) {this.Add_base(Bry_.new_u8(key), new Int_obj_val(val)); return this;} + public Hash_adp_bry Add_str_obj(String key, Object val) {this.Add_base(Bry_.new_u8(key), val); return this;} + public Hash_adp_bry Add_bry_obj(byte[] key, Object val) {this.Add_base(key, val); return this;} + public Hash_adp_bry Add_many_str(String... ary) { + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) { + String itm = ary[i]; + byte[] bry = Bry_.new_u8(itm); + Add_bry_bry(bry); + } + return this; + } + public Hash_adp_bry Add_many_bry(byte[]... ary) { + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) + Add_bry_bry(ary[i]); + return this; + } + @Override protected void Add_base(Object key, Object val) { + byte[] key_bry = (byte[])key; + Hash_adp_bry_itm_base key_itm = proto.New(); + key_itm.Init(key_bry, 0, key_bry.length); + super.Add_base(key_itm, val); + } + public static Hash_adp_bry cs() {return new Hash_adp_bry(Hash_adp_bry_itm_cs.Instance);} + public static Hash_adp_bry ci_a7() {return new Hash_adp_bry(Hash_adp_bry_itm_ci_a7.Instance);} + public static Hash_adp_bry ci_u8(Gfo_case_mgr case_mgr) {return new Hash_adp_bry(Hash_adp_bry_itm_ci_u8.get_or_new(case_mgr));} + public static Hash_adp_bry c__u8(boolean case_match, Gfo_case_mgr case_mgr) {return case_match ? cs() : ci_u8(case_mgr);} +} +abstract class Hash_adp_bry_itm_base { + public abstract Hash_adp_bry_itm_base New(); + public Hash_adp_bry_itm_base Init(byte[] src) {return this.Init(src, 0, src.length);} + public abstract Hash_adp_bry_itm_base Init(byte[] src, int src_bgn, int src_end); +} +class Hash_adp_bry_itm_cs extends Hash_adp_bry_itm_base { + private byte[] src; int src_bgn, src_end; + @Override public Hash_adp_bry_itm_base New() {return new Hash_adp_bry_itm_cs();} + @Override public Hash_adp_bry_itm_base Init(byte[] src, int src_bgn, int src_end) {this.src = src; this.src_bgn = src_bgn; this.src_end = src_end; return this;} + @Override public int hashCode() { + int rv = 0; + for (int i = src_bgn; i < src_end; i++) { + int b_int = src[i] & 0xFF; // JAVA: patch + rv = (31 * rv) + b_int; + } + return rv; + } + @Override public boolean equals(Object obj) { + if (obj == null) return false; + Hash_adp_bry_itm_cs comp = (Hash_adp_bry_itm_cs)obj; + byte[] comp_src = comp.src; int comp_bgn = comp.src_bgn, comp_end = comp.src_end; + int comp_len = comp_end - comp_bgn, src_len = src_end - src_bgn; + if (comp_len != src_len) return false; + for (int i = 0; i < comp_len; i++) { + int src_pos = src_bgn + i; + if (src_pos >= src_end) return false; // ran out of src; exit; EX: src=ab; find=abc + if (src[src_pos] != comp_src[i + comp_bgn]) return false; + } + return true; + } + public static final Hash_adp_bry_itm_cs Instance = new Hash_adp_bry_itm_cs(); Hash_adp_bry_itm_cs() {} +} +class Hash_adp_bry_itm_ci_a7 extends Hash_adp_bry_itm_base { + private byte[] src; int src_bgn, src_end; + @Override public Hash_adp_bry_itm_base New() {return new Hash_adp_bry_itm_ci_a7();} + @Override public Hash_adp_bry_itm_base Init(byte[] src, int src_bgn, int src_end) {this.src = src; this.src_bgn = src_bgn; this.src_end = src_end; return this;} + @Override public int hashCode() { + int rv = 0; + for (int i = src_bgn; i < src_end; i++) { + int b_int = src[i] & 0xFF; // JAVA: patch + if (b_int > 64 && b_int < 91) // 64=before A; 91=after Z; NOTE: lowering upper-case on PERF assumption that there will be more lower-case letters than upper-case + b_int += 32; + rv = (31 * rv) + b_int; + } + return rv; + } + @Override public boolean equals(Object obj) { + if (obj == null) return false; + Hash_adp_bry_itm_ci_a7 comp = (Hash_adp_bry_itm_ci_a7)obj; + byte[] comp_src = comp.src; int comp_bgn = comp.src_bgn, comp_end = comp.src_end; + int comp_len = comp_end - comp_bgn, src_len = src_end - src_bgn; + if (comp_len != src_len) return false; + for (int i = 0; i < comp_len; i++) { + int src_pos = src_bgn + i; + if (src_pos >= src_end) return false; // ran out of src; exit; EX: src=ab; find=abc + byte src_byte = src[src_pos]; + if (src_byte > 64 && src_byte < 91) src_byte += 32; + byte comp_byte = comp_src[i + comp_bgn]; + if (comp_byte > 64 && comp_byte < 91) comp_byte += 32; + if (src_byte != comp_byte) return false; + } + return true; + } + public static final Hash_adp_bry_itm_ci_a7 Instance = new Hash_adp_bry_itm_ci_a7(); Hash_adp_bry_itm_ci_a7() {} +} +class Hash_adp_bry_itm_ci_u8 extends Hash_adp_bry_itm_base { + private final Gfo_case_mgr case_mgr; + Hash_adp_bry_itm_ci_u8(Gfo_case_mgr case_mgr) {this.case_mgr = case_mgr;} + private byte[] src; int src_bgn, src_end; + @Override public Hash_adp_bry_itm_base New() {return new Hash_adp_bry_itm_ci_u8(case_mgr);} + @Override public Hash_adp_bry_itm_base Init(byte[] src, int src_bgn, int src_end) {this.src = src; this.src_bgn = src_bgn; this.src_end = src_end; return this;} + @Override public int hashCode() { + int rv = 0; + for (int i = src_bgn; i < src_end; i++) { + byte b = src[i]; + int b_int = b & 0xFF; // JAVA: patch + Gfo_case_itm itm = case_mgr.Get_or_null(b, src, i, src_end); + if (itm == null) { // unknown itm; byte is a number, symbol, or unknown; just use the existing byte + } + else { // known itm; use its hash_code + b_int = itm.Hashcode_lo(); + int b_len = Utf8_.Len_of_char_by_1st_byte(b); // NOTE: must calc b_len for langs with asymmetric upper / lower; PAGE:tr.w:Zvishavane DATE:2015-09-07 + i += b_len - 1; + } + rv = (31 * rv) + b_int; + } + return rv; + } + @Override public boolean equals(Object obj) { + if (obj == null) return false; + Hash_adp_bry_itm_ci_u8 trg_itm = (Hash_adp_bry_itm_ci_u8)obj; + byte[] trg = trg_itm.src; int trg_bgn = trg_itm.src_bgn, trg_end = trg_itm.src_end; + int src_c_bgn = src_bgn; + int trg_c_bgn = trg_bgn; + while ( src_c_bgn < src_end + && trg_c_bgn < trg_end) { // exit once one goes out of bounds + byte src_c = src[src_c_bgn]; + byte trg_c = trg[trg_c_bgn]; + int src_c_len = Utf8_.Len_of_char_by_1st_byte(src_c); + int trg_c_len = Utf8_.Len_of_char_by_1st_byte(trg_c); + int src_c_end = src_c_bgn + src_c_len; + int trg_c_end = trg_c_bgn + trg_c_len; + Gfo_case_itm src_c_itm = case_mgr.Get_or_null(src_c, src, src_c_bgn, src_c_end); + Gfo_case_itm trg_c_itm = case_mgr.Get_or_null(trg_c, trg, trg_c_bgn, trg_c_end); + if (src_c_itm != null && trg_c_itm == null) return false; // src == ltr; trg != ltr; EX: a, 1 + else if (src_c_itm == null && trg_c_itm != null) return false; // src != ltr; trg == ltr; EX: 1, a + else if (src_c_itm == null && trg_c_itm == null) { // src != ltr; trg != ltr; EX: 1, 2; _, Ⓐ + if (!Bry_.Match(src, src_c_bgn, src_c_end, trg, trg_c_bgn, trg_c_end)) return false;// syms do not match; return false; + } + else { + if (src_c_itm.Utf8_id_lo() != trg_c_itm.Utf8_id_lo()) return false; // lower-case utf8-ids don't match; return false; NOTE: using utf8-ids instead of hash-code to handle asymmetric brys; DATE:2015-09-07 + } + src_c_bgn = src_c_end; + trg_c_bgn = trg_c_end; + } + return src_c_bgn == src_end && trg_c_bgn == trg_end; // only return true if both src and trg read to end of their brys, otherwise "a","ab" will match + } + public static Hash_adp_bry_itm_ci_u8 get_or_new(Gfo_case_mgr case_mgr) { + switch (case_mgr.Tid()) { + case Gfo_case_mgr_.Tid_a7: if (Itm_a7 == null) Itm_a7 = new Hash_adp_bry_itm_ci_u8(case_mgr); return Itm_a7; + case Gfo_case_mgr_.Tid_u8: if (Itm_u8 == null) Itm_u8 = new Hash_adp_bry_itm_ci_u8(case_mgr); return Itm_u8; + case Gfo_case_mgr_.Tid_custom: return new Hash_adp_bry_itm_ci_u8(case_mgr); + default: throw Err_.new_unhandled(case_mgr.Tid()); + } + } + private static Hash_adp_bry_itm_ci_u8 Itm_a7, Itm_u8; +} diff --git a/100_core/src/gplx/Hash_adp_bry_tst.java b/100_core/src/gplx/Hash_adp_bry_tst.java new file mode 100644 index 000000000..e33f2be9d --- /dev/null +++ b/100_core/src/gplx/Hash_adp_bry_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 +*/ +package gplx; +import org.junit.*; +public class Hash_adp_bry_tst { + @Before public void setup() {fxt.Clear();} private Hash_adp_bry_fxt fxt = new Hash_adp_bry_fxt(); + @Test public void Add_bry() { + fxt .New_cs() + .Add("a0").Add("b0").Add("c0") + .Get_bry_tst("a0").Get_bry_tst("b0").Get_bry_tst("c0").Get_bry_tst("A0", null) + ; + } + @Test public void Get_mid() { + fxt .New_cs() + .Add("a0").Add("b0").Add("c0") + .Get_mid_tst("xyza0xyz", 3, 5, "a0") + .Get_mid_tst("xyza0xyz", 3, 4, null) + ; + } + @Test public void Case_insensitive() { + fxt .New_ci() + .Add("a0").Add("B0").Add("c0") + .Get_bry_tst("a0", "a0") + .Get_bry_tst("A0", "a0") + .Get_bry_tst("b0", "B0") + .Get_bry_tst("B0", "B0") + .Get_mid_tst("xyza0xyz", 3, 5, "a0") + .Get_mid_tst("xyzA0xyz", 3, 5, "a0") + .Count_tst(3) + ; + } +} +class Hash_adp_bry_fxt { + Hash_adp_bry hash; + public void Clear() {} + public Hash_adp_bry_fxt New_cs() {hash = Hash_adp_bry.cs(); return this;} + public Hash_adp_bry_fxt New_ci() {hash = Hash_adp_bry.ci_a7(); return this;} + public Hash_adp_bry_fxt Add(String key) {byte[] key_bry = Bry_.new_u8(key); hash.Add(key_bry, key_bry); return this;} + public Hash_adp_bry_fxt Count_tst(int expd) {Tfds.Eq(expd, hash.Count()); return this;} + public Hash_adp_bry_fxt Get_bry_tst(String key) {return Get_bry_tst(key, key);} + public Hash_adp_bry_fxt Get_bry_tst(String key, String expd) { + byte[] key_bry = Bry_.new_u8(key); + byte[] actl_bry = (byte[])hash.Get_by_bry(key_bry); + Tfds.Eq(expd, String_.new_u8(actl_bry)); + return this; + } + public Hash_adp_bry_fxt Get_mid_tst(String key, int bgn, int end, String expd) { + byte[] key_bry = Bry_.new_u8(key); + byte[] actl_bry = (byte[])hash.Get_by_mid(key_bry, bgn, end); + Tfds.Eq(expd, String_.new_u8(actl_bry)); + return this; + } +} diff --git a/100_core/src/gplx/Int_.java b/100_core/src/gplx/Int_.java new file mode 100644 index 000000000..6163fddd2 --- /dev/null +++ b/100_core/src/gplx/Int_.java @@ -0,0 +1,220 @@ +/* +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; +import gplx.core.strings.*; import gplx.langs.gfs.*; +public class Int_ { + public static final String Cls_val_name = "int"; + public static final Class Cls_ref_type = Integer.class; + + public static final int + Min_value = Integer.MIN_VALUE + , Max_value = Integer.MAX_VALUE + , Max_value__31 = 2147483647 + , Neg1 = -1 + , Null = Int_.Min_value + , Base1 = 1 // for super 1 lists / arrays; EX: PHP; [a, b, c]; [1] => a + , Offset_1 = 1 // common symbol for + 1 after current pos; EX: String_.Mid(lhs + Offset_1, rhs) + ; + + public static int Cast(Object obj) { + try { + return (Integer)obj; + } + catch(Exception exc) { + throw Err_.new_type_mismatch_w_exc(exc, int.class, obj); + } + } + public static int Cast_or(Object obj, int or) { + try { + return (Integer)obj; + } + catch(Exception e) { + Err_.Noop(e); + return or; + } + } + public static int Coerce(Object v) { + try { + String s = String_.as_(v); + return s == null ? Int_.Cast(v) : Int_.Parse(s); + } + catch (Exception e) {throw Err_.new_cast(e, int.class, v);} + } + + public static int Parse(String raw) {try {return Integer.parseInt(raw);} catch(Exception e) {throw Err_.new_parse_exc(e, int.class, raw);}} + public static int Parse_or(String raw, int or) { + if (raw == null) return or; + int rawLen = String_.Len(raw); if (rawLen == 0) return or; + int rv = 0, tmp = 0, factor = 1; + for (int i = rawLen; i > 0; i--) { + char c = String_.CharAt(raw, i - 1); + switch (c) { + case '0': tmp = 0; break; case '1': tmp = 1; break; case '2': tmp = 2; break; case '3': tmp = 3; break; case '4': tmp = 4; break; + case '5': tmp = 5; break; case '6': tmp = 6; break; case '7': tmp = 7; break; case '8': tmp = 8; break; case '9': tmp = 9; break; + case '-': rv *= -1; continue; // NOTE: note continue + default: return or; + } + rv += (tmp * factor); + factor *= 10; + } + return rv; + } + + public static int By_double(double v) {return (int)v;} + public static int By_hex_bry(byte[] src) {return By_hex_bry(src, 0, src.length);} + public static int By_hex_bry(byte[] src, int bgn, int end) { + int rv = 0; int factor = 1; + for (int i = end - 1; i >= bgn; i--) { + int val = By_hex_byte(src[i]); + rv += (val * factor); + factor *= 16; + } + return rv; + } + public static int By_hex_byte(byte b) { + switch (b) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + return b - Byte_ascii.Num_0; + case Byte_ascii.Ltr_A: case Byte_ascii.Ltr_B: case Byte_ascii.Ltr_C: case Byte_ascii.Ltr_D: case Byte_ascii.Ltr_E: case Byte_ascii.Ltr_F: + return b - Byte_ascii.Ltr_A + 10; + case Byte_ascii.Ltr_a: case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_e: case Byte_ascii.Ltr_f: + return b - Byte_ascii.Ltr_a + 10; + default: + return -1; + } + } + + public static byte[] To_bry(int v) {return Bry_.new_a7(To_str(v));} + public static String To_str(int v) {return new Integer(v).toString();} + public static String To_str_fmt(int v, String fmt) {return new java.text.DecimalFormat(fmt).format(v);} + public static String To_str_pad_bgn_space(int val, int reqd_len) {return To_str_pad(val, reqd_len, Bool_.Y, Byte_ascii.Space);} // EX: 1, 3 returns " 1" + public static String To_str_pad_bgn_zero (int val, int reqd_len) {return To_str_pad(val, reqd_len, Bool_.Y, Byte_ascii.Num_0);} // EX: 1, 3 returns "001" + private static String To_str_pad(int val, int reqd_len, boolean bgn, byte pad_chr) { + // get val_len and pad_len; exit early, if no padding needed + int val_len = DigitCount(val); + int pad_len = reqd_len - val_len; + if (pad_len < 0) + return Int_.To_str(val); + + // padding needed + Bry_bfr bfr = Bry_bfr_.New(); + + // handle negative numbers; EX: -1 -> "-001", not "00-1" + if (val < 0) { + bfr.Add_byte(Byte_ascii.Dash); + val *= -1; + --val_len; + } + + // build outpt + if (!bgn) + bfr.Add_int_fixed(val, val_len); + bfr.Add_byte_repeat(pad_chr, pad_len); + if (bgn) + bfr.Add_int_fixed(val, val_len); + + return bfr.To_str(); + } + public static String To_str_hex(int v) {return To_str_hex(Bool_.Y, Bool_.Y, v);} + public static String To_str_hex(boolean zero_pad, boolean upper, int v) { + String rv = Integer.toHexString(v); + int rv_len = String_.Len(rv); + if (zero_pad && rv_len < 8) rv = String_.Repeat("0", 8 - rv_len) + rv; + return upper ? String_.Upper(rv) : rv; + } + + public static int Compare(int lhs, int rhs) { + if (lhs == rhs) return CompareAble_.Same; + else if (lhs < rhs) return CompareAble_.Less; + else return CompareAble_.More; + } + public static boolean In(int v, int comp0, int comp1) {return v == comp0 || v == comp1;} + public static boolean In(int v, int... ary) { + for (int itm : ary) + if (v == itm) return true; + return false; + } + public static boolean Between(int v, int lhs, int rhs) { + int lhsCompare = v == lhs ? 0 : (v < lhs ? -1 : 1); + int rhsCompare = v == rhs ? 0 : (v < rhs ? -1 : 1); + return (lhsCompare * rhsCompare) != 1; // 1 when v is (a) greater than both or (b) less than both + } + public static boolean RangeCheck(int v, int max) {return v >= 0 && v < max;} + public static void RangeCheckOrFail_list(int v, int max, String s) {if (v < 0 || v >= max) throw Err_.new_wo_type("bounds check failed", "msg", s, "v", v, "min", 0, "max", max - 1);} + public static boolean Bounds_chk(int bgn, int end, int len) {return bgn > -1 && end < len;} + public static int BoundEnd(int v, int end) {return v >= end ? end - 1 : v;} + public static int EnsureLessThan(int v, int max) {return v >= max ? max : v;} + + public static int Min(int lhs, int rhs) {return lhs < rhs ? lhs : rhs;} + public static int Max(int lhs, int rhs) {return lhs > rhs ? lhs : rhs;} + public static int Min_many(int... ary) { + int len = ary.length; if (len == 0) throw Err_.new_wo_type("Min_many requires at least 1 value"); + boolean init = true; + int min = Int_.Min_value; + for (int i = 0; i < len; ++i) { + int val = ary[i]; + if (init) { + min = val; + init = false; + } + else { + if (val < min) + min = val; + } + } + return min; + } + + public static int Subtract_long(long lhs, long rhs) {return (int)(lhs - rhs);} + public static int Div(int v, float divisor) {return (int)((float)v / divisor);} + public static int DivAndRoundUp(int v, int divisor) { + int whole = v / divisor; + int partial = v % divisor == 0 ? 0 : 1; + return whole + partial; + } + public static int Mult(int v, float multiplier) { + float product = ((float)v * multiplier); // WORKAROUND (DotNet): (int)((float)v * multiplier) returns 0 for 100 and .01f + return (int)product; + } + + public static int[] Log10Ary = new int[] {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, Int_.Max_value}; + public static int Log10AryLen = 11; + public static int Log10(int v) { + if (v == 0) return 0; + int sign = 1; + if (v < 0) { + if (v == Int_.Min_value) return -9; // NOTE: Int_.Min_value * -1 = Int_.Min_value + v *= -1; + sign = -1; + } + int rv = Log10AryLen - 2; // rv will only happen when v == Int_.Max_value + int bgn = 0; + if (v > 1000) { // optimization to reduce number of ops to < 5 + bgn = 3; + if (v > 1000000) bgn = 6; + } + for (int i = bgn; i < Log10AryLen; i++) { + if (v < Log10Ary[i]) {rv = i - 1; break;} + } + return rv * sign; + } + + public static int DigitCount(int v) { + int log10 = Log10(v); + return v > -1 ? log10 + 1 : log10 * -1 + 2; + } +} diff --git a/100_core/src/gplx/Int__tst.java b/100_core/src/gplx/Int__tst.java new file mode 100644 index 000000000..e4efed599 --- /dev/null +++ b/100_core/src/gplx/Int__tst.java @@ -0,0 +1,97 @@ +/* +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; +import org.junit.*; +public class Int__tst { + @Test public void XtoStr_PadBgn() { + tst_XtoStr_PadLeft_Zeroes(1 , 3, "001"); // pad + tst_XtoStr_PadLeft_Zeroes(123 , 3, "123"); // no pad + tst_XtoStr_PadLeft_Zeroes(1234 , 3, "1234"); // val exceeds pad; confirm noop + tst_XtoStr_PadLeft_Zeroes(-1 , 3, "-01"); // negative + tst_XtoStr_PadLeft_Zeroes(-12 , 3, "-12"); // negative + tst_XtoStr_PadLeft_Zeroes(-123 , 3, "-123"); // negative + tst_XtoStr_PadLeft_Zeroes(-1234 , 3, "-1234"); // negative + } void tst_XtoStr_PadLeft_Zeroes(int val, int zeros, String expd) {Tfds.Eq(expd, Int_.To_str_pad_bgn_zero(val, zeros));} + @Test public void parseOr_() { + tst_ParseOr("", -1); // empty + tst_ParseOr("123", 123); // single + tst_ParseOr("1a", -1); // fail + } void tst_ParseOr(String raw, int expd) {Tfds.Eq(expd, Int_.Parse_or(raw, -1));} + @Test public void Between() { + tst_Between(1, 0, 2, true); // simple true + tst_Between(3, 0, 2, false); // simple false + tst_Between(0, 0, 2, true); // bgn true + tst_Between(2, 0, 2, true); // end true + } void tst_Between(int val, int lhs, int rhs, boolean expd) {Tfds.Eq(expd, Int_.Between(val, lhs, rhs));} + @Test public void Xto_fmt() { + tst_XtoStr_fmt(1, "1"); + tst_XtoStr_fmt(1000, "1,000"); + } void tst_XtoStr_fmt(int v, String expd) {Tfds.Eq(expd, Int_.To_str_fmt(v, "#,###"));} + @Test public void Log10_pos() { + tst_Log10(0, 0); + tst_Log10(1, 0); + tst_Log10(9, 0); + tst_Log10(10, 1); + tst_Log10(100, 2); + tst_Log10(1000000, 6); + tst_Log10(1000000000, 9); + tst_Log10(Int_.Max_value, 9); + } + @Test public void Log10_neg() { + tst_Log10(-1, 0); + tst_Log10(-10, -1); + tst_Log10(-100, -2); + tst_Log10(-1000000, -6); + tst_Log10(-1000000000, -9); + tst_Log10(Int_.Min_value, -9); + tst_Log10(Int_.Min_value + 1, -9); + } + void tst_Log10(int val, int expd) {Tfds.Eq(expd, Int_.Log10(val));} + @Test public void DigitCount() { + tst_DigitCount(0, 1); + tst_DigitCount(9, 1); + tst_DigitCount(100, 3); + tst_DigitCount(-1, 2); + tst_DigitCount(-100, 4); + } void tst_DigitCount(int val, int expd) {Tfds.Eq(expd, Int_.DigitCount(val), Int_.To_str(val));} + @Test public void Log10() { + tst_Log10( 0, 0); + tst_Log10( 1, 0); + tst_Log10( 2, 0); + tst_Log10( 10, 1); + tst_Log10( 12, 1); + tst_Log10( 100, 2); + tst_Log10( 123, 2); + tst_Log10( 1000, 3); + tst_Log10( 1234, 3); + tst_Log10( 10000, 4); + tst_Log10( 12345, 4); + tst_Log10( 100000, 5); + tst_Log10( 123456, 5); + tst_Log10( 1000000, 6); + tst_Log10( 1234567, 6); + tst_Log10( 10000000, 7); + tst_Log10( 12345678, 7); + tst_Log10( 100000000, 8); + tst_Log10( 123456789, 8); + tst_Log10( 1000000000, 9); + tst_Log10( 1234567890, 9); + tst_Log10(Int_.Max_value, 9); + } + @Test public void Xto_int_hex_tst() { + Xto_int_hex("007C", 124); + } void Xto_int_hex(String raw, int expd) {Tfds.Eq(expd, Int_.By_hex_bry(Bry_.new_a7(raw)));} +} diff --git a/100_core/src/gplx/Int_ary_.java b/100_core/src/gplx/Int_ary_.java new file mode 100644 index 000000000..6070f9d6b --- /dev/null +++ b/100_core/src/gplx/Int_ary_.java @@ -0,0 +1,163 @@ +/* +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; +import gplx.core.strings.*; +public class Int_ary_ {//RF:DATE:2017-10-09 + public static int[] Empty = new int[0]; + + public static int[] New(int... v) {return v;} + + public static void Copy_to(int[] src, int src_len, int[] trg) { + for (int i = 0; i < src_len; ++i) + trg[i] = src[i]; + } + + public static String To_str(String spr, int... ary) { + Bry_bfr bfr = Bry_bfr_.New(); + int len = ary.length; + for (int i = 0; i < len; ++i) { + if (i != 0) bfr.Add_str_u8(spr); + int itm = ary[i]; + bfr.Add_int_variable(itm); + } + return bfr.To_str_and_clear(); + } + + public static int[] Parse(String raw, String spr) { + String[] ary = String_.Split(raw, spr); + int len = ary.length; + int[] rv = new int[len]; + for (int i = 0; i < len; i++) + rv[i] = Int_.Parse(ary[i]); + return rv; + } + + // parses to a reqd len; EX: "1" -> "[1, 0]" + public static int[] Parse(String raw_str, int reqd_len, int[] or) { + byte[] raw_bry = Bry_.new_a7(raw_str); + int raw_bry_len = raw_bry.length; + int[] rv = new int[reqd_len]; + int cur_val = 0, cur_mult = 1, cur_idx = reqd_len - 1; boolean signed = false; + for (int i = raw_bry_len - 1; i > -2; i--) { + byte b = i == -1 ? Byte_ascii.Comma : raw_bry[i]; + switch (b) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + if (signed) return or; + cur_val += (b - Byte_ascii.Num_0) * cur_mult; + cur_mult *= 10; + break; + case Byte_ascii.Space: case Byte_ascii.Nl: case Byte_ascii.Cr: case Byte_ascii.Tab: + break; + case Byte_ascii.Comma: + if (cur_idx < 0) return or; + rv[cur_idx--] = cur_val; + cur_val = 0; cur_mult = 1; + signed = false; + break; + case Byte_ascii.Dash: + if (signed) return or; + cur_val *= -1; + signed = true; + break; + case Byte_ascii.Plus: // noop; all values positive by default + if (signed) return or; + signed = true; + break; + default: + return or; + } + } + return cur_idx == -1 ? rv : or; // cur_idx == -1 checks for unfilled; EX: Ary_parse("1,2", 3, null) is unfilled + } + + // optimizes parse + public static int[] Parse_or(byte[] src, int[] or) { + try { + if (Bry_.Len_eq_0(src)) return or; // null, "" should return [0] + int raw_len = src.length; + int[] rv = null; int rv_idx = 0, rv_len = 0; + int pos = 0; + int num_bgn = -1, num_end = -1; + boolean itm_done = false, itm_is_rng = false; + int rng_bgn = Int_.Min_value; + while (true) { + boolean pos_is_last = pos == raw_len; + if ( itm_done + || pos_is_last + ) { + if (num_bgn == -1) return or; // empty itm; EX: "1,"; "1,,2" + int num = Bry_.To_int_or(src, num_bgn, num_end, Int_.Min_value); + if (num == Int_.Min_value) return or; // not a number; parse failed + if (rv_len == 0) { // rv not init'd + rv_len = (raw_len / 2) + 1; // default rv_len to len of String / 2; + 1 to avoid fraction rounding down + rv = new int[rv_len]; + } + int add_len = 1; + if (itm_is_rng) { + add_len = num - rng_bgn + List_adp_.Base1; + if (add_len == 0) return or; // bgn >= end; + } + if (add_len + rv_idx > rv_len) { // ary out of space; resize + rv_len = (add_len + rv_idx) * 2; + rv = (int[])Array_.Resize(rv, rv_len); + } + if (itm_is_rng) { + for (int i = rng_bgn; i <= num; i++) + rv[rv_idx++] = i; + } + else { + rv[rv_idx++] = num; + } + num_bgn = num_end = -1; + itm_done = itm_is_rng = false; + rng_bgn = Int_.Min_value; + if (pos_is_last) break; + } + byte b = src[pos]; + switch (b) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + if (num_bgn == -1) // num_bgn not set + num_bgn = pos; + num_end = pos + 1; // num_end is always after pos; EX: "9": num_end = 1; "98,7": num_end=2 + break; + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: // NOTE: parseNumList replaces ws with '', so "1 1" will become "11" + break; + case Byte_ascii.Comma: + if (pos == raw_len -1) return or; // eos; EX: "1," + if (num_bgn == -1) return or; // empty itm; EX: ","; "1,,2" + itm_done = true; + break; + case Byte_ascii.Dash: + if (pos == raw_len -1) return or; // eos; EX: "1-" + if (num_bgn == -1) return or; // no rng_bgn; EX: "-2" + rng_bgn = Bry_.To_int_or(src, num_bgn, pos, Int_.Min_value); + if (rng_bgn == Int_.Min_value) return or; + num_bgn = -1; + itm_is_rng = true; + break; + default: + return or; + } + ++pos; + } + return (rv_idx == rv_len) // on the off-chance that rv_len == rv_idx; EX: "1" + ? rv + : (int[])Array_.Resize(rv, rv_idx); + } catch (Exception e) {Err_.Noop(e); return or;} + } +} diff --git a/100_core/src/gplx/Int_ary__tst.java b/100_core/src/gplx/Int_ary__tst.java new file mode 100644 index 000000000..e2486b2f1 --- /dev/null +++ b/100_core/src/gplx/Int_ary__tst.java @@ -0,0 +1,58 @@ +/* +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; +import org.junit.*; import gplx.core.tests.*; +public class Int_ary__tst { + private Int_ary__fxt fxt = new Int_ary__fxt(); + + @Test public void Parse() { + fxt.Test__Parse("1,2,3" , 3, Int_ary_.Empty, 1, 2, 3); + fxt.Test__Parse("123,321,213" , 3, Int_ary_.Empty, 123, 321, 213); + fxt.Test__Parse(" 1, 2,3" , 3, Int_ary_.Empty, 1, 2, 3); + fxt.Test__Parse("-1,+2,-3" , 3, Int_ary_.Empty, -1, 2, -3); + fxt.Test__Parse(Int_.To_str(Int_.Min_value) , 1, Int_ary_.Empty, Int_.Min_value); + fxt.Test__Parse(Int_.To_str(Int_.Max_value) , 1, Int_ary_.Empty, Int_.Max_value); + fxt.Test__Parse("1,2" , 1, Int_ary_.Empty); + fxt.Test__Parse("1" , 2, Int_ary_.Empty); + fxt.Test__Parse("a" , 1, Int_ary_.Empty); + fxt.Test__Parse("1-2," , 1, Int_ary_.Empty); + } + + @Test public void Parse_list_or_() { + fxt.Test__Parse_or("1", 1); + fxt.Test__Parse_or("123", 123); + fxt.Test__Parse_or("1,2,123", 1, 2, 123); + fxt.Test__Parse_or("1,2,12,123", 1, 2, 12, 123); + fxt.Test__Parse_or("1-5", 1, 2, 3, 4, 5); + fxt.Test__Parse_or("1-1", 1); + fxt.Test__Parse_or("1-3,7,11-13,21", 1, 2, 3, 7, 11, 12, 13, 21); + + fxt.Test__Parse_or__empty("1 2"); // NOTE: MW would gen 12; treat as invalid + fxt.Test__Parse_or__empty("1,"); // eos + fxt.Test__Parse_or__empty("1,,2"); // empty comma + fxt.Test__Parse_or__empty("1-"); // eos + fxt.Test__Parse_or__empty("3-1"); // bgn > end + fxt.Test__Parse_or__empty("1,a,2"); + fxt.Test__Parse_or__empty("a-1,2"); + fxt.Test__Parse_or__empty("-1"); // no rng bgn + } +} +class Int_ary__fxt { + public void Test__Parse_or__empty(String raw) {Tfds.Eq_ary(Int_ary_.Empty, Int_ary_.Parse_or(Bry_.new_a7(raw), Int_ary_.Empty));} + public void Test__Parse_or(String raw, int... expd) {Tfds.Eq_ary(expd, Int_ary_.Parse_or(Bry_.new_a7(raw), Int_ary_.Empty));} + + public void Test__Parse(String raw, int reqd_len, int[] or, int... expd) {Gftest.Eq__ary(expd, Int_ary_.Parse(raw, reqd_len, or), "failed to parse: {0}", raw);} +} diff --git a/100_core/src/gplx/Internal.java b/100_core/src/gplx/Internal.java new file mode 100644 index 000000000..b8adf679c --- /dev/null +++ b/100_core/src/gplx/Internal.java @@ -0,0 +1,17 @@ +/* +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; +public @interface Internal {} diff --git a/100_core/src/gplx/Io_mgr.java b/100_core/src/gplx/Io_mgr.java new file mode 100644 index 000000000..ceea1c679 --- /dev/null +++ b/100_core/src/gplx/Io_mgr.java @@ -0,0 +1,176 @@ +/* +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; +import gplx.core.primitives.*; import gplx.core.ios.*; /*IoItmFil, IoItmDir..*/ import gplx.core.ios.streams.*; import gplx.core.ios.loaders.*; +public class Io_mgr implements Gfo_evt_mgr_owner { // exists primarily to gather all cmds under gplx namespace; otherwise need to use gplx.core.ios whenever copying/deleting file + public Io_mgr() {evt_mgr = new Gfo_evt_mgr(this);} + public Gfo_evt_mgr Evt_mgr() {return evt_mgr;} private final Gfo_evt_mgr evt_mgr; + public boolean Exists(Io_url url) {return url.Type_dir() ? ExistsDir(url) : ExistsFil(url);} + public boolean ExistsFil(Io_url url) {return IoEnginePool.Instance.Get_by(url.Info().EngineKey()).ExistsFil_api(url);} + public void ExistsFilOrFail(Io_url url) {if (!ExistsFil(url)) throw Err_.new_wo_type("could not find file", "url", url);} + public void SaveFilStr(String url, String text) {SaveFilStr_args(Io_url_.new_fil_(url), text).Exec();} + public void SaveFilStr(Io_url url, String text) {SaveFilStr_args(url, text).Exec();} + public IoEngine_xrg_saveFilStr SaveFilStr_args(Io_url url, String text) {return IoEngine_xrg_saveFilStr.new_(url, text);} + public void AppendFilStr(String url, String text) {AppendFilStr(Io_url_.new_fil_(url), text);} + public void AppendFilStr(Io_url url, String text) {SaveFilStr_args(url, text).Append_(true).Exec();} + public void DeleteFil(Io_url url) {DeleteFil_args(url).Exec();} + public IoEngine_xrg_deleteFil DeleteFil_args(Io_url url) {return IoEngine_xrg_deleteFil.new_(url);} + public void MoveFil(Io_url src, Io_url trg) {IoEngine_xrg_xferFil.move_(src, trg).Exec();} + public IoEngine_xrg_xferFil MoveFil_args(Io_url src, Io_url trg, boolean overwrite) {return IoEngine_xrg_xferFil.move_(src, trg).Overwrite_(overwrite);} + public void CopyFil(Io_url src, Io_url trg, boolean overwrite) {IoEngine_xrg_xferFil.copy_(src, trg).Overwrite_(overwrite).Exec();} + public IoEngine_xrg_xferFil CopyFil_args(Io_url src, Io_url trg, boolean overwrite) {return IoEngine_xrg_xferFil.copy_(src, trg).Overwrite_(overwrite);} + public IoRecycleBin RecycleBin() {return recycleBin;} private IoRecycleBin recycleBin = IoRecycleBin.Instance; + public Io_loader Loader() {return loader;} public void Loader_(Io_loader v) {this.loader = v;} private Io_loader loader; + + public IoStream OpenStreamWrite(Io_url url) {return OpenStreamWrite_args(url).Exec();} + public IoEngine_xrg_openWrite OpenStreamWrite_args(Io_url url) {return IoEngine_xrg_openWrite.new_(url);} + public IoItmFil QueryFil(Io_url url) {return IoEnginePool.Instance.Get_by(url.Info().EngineKey()).QueryFil(url);} + public void UpdateFilAttrib(Io_url url, IoItmAttrib attrib) {IoEnginePool.Instance.Get_by(url.Info().EngineKey()).UpdateFilAttrib(url, attrib);} + public void UpdateFilModifiedTime(Io_url url, DateAdp modified) {IoEnginePool.Instance.Get_by(url.Info().EngineKey()).UpdateFilModifiedTime(url, modified);} + + public boolean ExistsDir(Io_url url) {return IoEnginePool.Instance.Get_by(url.Info().EngineKey()).ExistsDir(url);} + public void CreateDir(Io_url url) {IoEnginePool.Instance.Get_by(url.Info().EngineKey()).CreateDir(url);} + public boolean CreateDirIfAbsent(Io_url url) { + boolean exists = ExistsDir(url); + if (!exists) { + CreateDir(url); + return true; + } + return false; + } + public void Create_fil_ary(Io_fil[] fil_ary) { + for (Io_fil fil : fil_ary) + SaveFilStr(fil.Url(), fil.Data()); + } + public Io_url[] QueryDir_fils(Io_url dir) {return QueryDir_args(dir).ExecAsUrlAry();} + public IoEngine_xrg_queryDir QueryDir_args(Io_url dir) {return IoEngine_xrg_queryDir.new_(dir);} + public void DeleteDirSubs(Io_url url) {IoEngine_xrg_deleteDir.new_(url).Exec();} + public IoEngine_xrg_deleteDir DeleteDir_cmd(Io_url url) {return IoEngine_xrg_deleteDir.new_(url);} + public void DeleteDirDeep(Io_url url) {IoEngine_xrg_deleteDir.new_(url).Recur_().Exec();} + public void DeleteDirDeep_ary(Io_url... urls) {for (Io_url url : urls) IoEngine_xrg_deleteDir.new_(url).Recur_().Exec();} + public int Delete_dir_empty(Io_url url) {return Io_mgr_.Delete_dir_empty(url);} + public void Delete_sub_by_wildcard() { + } + public boolean Truncate_fil(Io_url url, long size) {return IoEnginePool.Instance.Get_by(url.Info().EngineKey()).Truncate_fil(url, size);} + public void MoveDirDeep(Io_url src, Io_url trg) {IoEngine_xrg_xferDir.move_(src, trg).Recur_().Exec();} + public IoEngine_xrg_xferDir CopyDir_cmd(Io_url src, Io_url trg) {return IoEngine_xrg_xferDir.copy_(src, trg);} + public void CopyDirSubs(Io_url src, Io_url trg) {IoEngine_xrg_xferDir.copy_(src, trg).Exec();} + public void CopyDirDeep(Io_url src, Io_url trg) {IoEngine_xrg_xferDir.copy_(src, trg).Recur_().Exec();} + public void DeleteDirIfEmpty(Io_url url) { + if (Array_.Len(QueryDir_fils(url)) == 0) + this.DeleteDirDeep(url); + } + public void AliasDir_sysEngine(String srcRoot, String trgRoot) {AliasDir(srcRoot, trgRoot, IoEngine_.SysKey);} + public void AliasDir(String srcRoot, String trgRoot, String engineKey) {IoUrlInfoRegy.Instance.Reg(IoUrlInfo_.alias_(srcRoot, trgRoot, engineKey));} + public IoStream OpenStreamRead(Io_url url) {return OpenStreamRead_args(url).ExecAsIoStreamOrFail();} + public IoEngine_xrg_openRead OpenStreamRead_args(Io_url url) {return IoEngine_xrg_openRead.new_(url);} + public String LoadFilStr(String url) {return LoadFilStr_args(Io_url_.new_fil_(url)).Exec();} + public String LoadFilStr(Io_url url) {return LoadFilStr_args(url).Exec();} + public IoEngine_xrg_loadFilStr LoadFilStr_args(Io_url url) {return IoEngine_xrg_loadFilStr.new_(url);} + public byte[] LoadFilBryOrNull(Io_url url) {return LoadFilBryOr(url, null);} + public byte[] LoadFilBryOr(Io_url url, byte[] or) {return ExistsFil(url) ? LoadFilBry(url) : or;} + public byte[] LoadFilBry(String url) {return LoadFilBry_reuse(Io_url_.new_fil_(url), Bry_.Empty, Int_obj_ref.New_zero());} + public byte[] LoadFilBry(Io_url url) {return LoadFilBry_reuse(url, Bry_.Empty, Int_obj_ref.New_zero());} + public void LoadFilBryByBfr(Io_url url, Bry_bfr bfr) { + Int_obj_ref len = Int_obj_ref.New_zero(); + byte[] bry = LoadFilBry_reuse(url, Bry_.Empty, len); + bfr.Bfr_init(bry, len.Val()); + } + public static final byte[] LoadFilBry_fail = Bry_.Empty; + public byte[] LoadFilBry_reuse(Io_url url, byte[] ary, Int_obj_ref ary_len) { + if (loader != null) { + byte[] rv = loader.Load_fil_as_bry(url); + if (rv != null) return rv; + } + if (!ExistsFil(url)) { + ary_len.Val_(0); + return LoadFilBry_fail; + } + IoStream stream = IoStream_.Null; + try { + stream = OpenStreamRead(url); + int stream_len = (int)stream.Len(); + ary_len.Val_(stream_len); + if (stream_len > ary.length) + ary = new byte[stream_len]; + stream.ReadAry(ary); + return ary; + } + catch (Exception e) {throw Err_.new_wo_type("failed to load file", "url", url.Xto_api(), "e", Err_.Message_lang(e));} + finally {stream.Rls();} + } + public byte[] LoadFilBry_loose(Io_url url) {return Bry_.new_u8(LoadFilStr_loose(url));} + public String LoadFilStr_loose(Io_url url) { + String rv = LoadFilStr_args(url).BomUtf8Convert_(Bool_.Y).MissingIgnored_(Bool_.Y).Exec(); + if (String_.Has(rv, "\r\n")) + rv = String_.Replace(rv, "\r\n", "\n"); + return rv; + } + public void AppendFilBfr(Io_url url, Bry_bfr bfr) {AppendFilByt(url, bfr.Bfr(), 0, bfr.Len()); bfr.ClearAndReset();} + public void AppendFilByt(Io_url url, byte[] val) {AppendFilByt(url, val, 0, val.length);} + public void AppendFilByt(Io_url url, byte[] val, int len) {AppendFilByt(url, val, 0, len);} + public void AppendFilByt(Io_url url, byte[] val, int bgn, int len) { + IoStream stream = IoStream_.Null; + try { + stream = OpenStreamWrite_args(url).Mode_(IoStream_.Mode_wtr_append).Exec(); + stream.Write(val, bgn, len); + } finally {stream.Rls();} + } + public void SaveFilBfr(Io_url url, Bry_bfr bfr) {SaveFilBry(url, bfr.Bfr(), bfr.Len()); bfr.Clear();} + public void SaveFilBry(String urlStr, byte[] val) {SaveFilBry(Io_url_.new_fil_(urlStr), val);} + public void SaveFilBry(Io_url url, byte[] val) {SaveFilBry(url, val, val.length);} + public void SaveFilBry(Io_url url, byte[] val, int len) {SaveFilBry(url, val, 0, len);} + public void SaveFilBry(Io_url url, byte[] val, int bgn, int len) { + IoStream stream = IoStream_.Null; + try { + stream = OpenStreamWrite(url); + stream.Write(val, bgn, len); + } finally {stream.Rls();} + } + public IoEngine InitEngine_mem() {return IoEngine_.Mem_init_();} + public IoEngine InitEngine_mem_(String key) { + IoEngine engine = IoEngine_.mem_new_(key); + IoEnginePool.Instance.Add_if_dupe_use_nth(engine); + IoUrlInfoRegy.Instance.Reg(IoUrlInfo_.mem_(key, key)); + return engine; + } + public boolean DownloadFil(String src, Io_url trg) {return IoEngine_xrg_downloadFil.new_(src, trg).Exec();} + public IoEngine_xrg_downloadFil DownloadFil_args(String src, Io_url trg) {return IoEngine_xrg_downloadFil.new_(src, trg);} + public static final Io_mgr Instance = new Io_mgr(); + public static final int Len_kb = 1024, Len_mb = 1048576, Len_gb = 1073741824, Len_gb_2 = 2147483647; + public static final long Len_mb_long = Len_mb; + public static final long Len_null = -1; + public static final String Evt__fil_created = "fil_created"; +} +class Io_mgr_ { + public static int Delete_dir_empty(Io_url url) { + IoItmDir dir = Io_mgr.Instance.QueryDir_args(url).ExecAsDir(); + int sub_dirs_len = dir.SubDirs().Count(); + int deleted_dirs = 0; + for (int i = 0; i < sub_dirs_len; ++i) { + IoItmDir sub_dir = (IoItmDir)dir.SubDirs().Get_at(i); + deleted_dirs += Io_mgr.Instance.Delete_dir_empty(sub_dir.Url()); + } + if ( dir.SubFils().Count() == 0 + && deleted_dirs == sub_dirs_len + ) { + Io_mgr.Instance.DeleteDirIfEmpty(url); + return 1; + } + else + return 0; + } +} diff --git a/100_core/src/gplx/Io_mgr__tst.java b/100_core/src/gplx/Io_mgr__tst.java new file mode 100644 index 000000000..5f5a6b912 --- /dev/null +++ b/100_core/src/gplx/Io_mgr__tst.java @@ -0,0 +1,98 @@ +/* +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; +import org.junit.*; +public class Io_mgr__tst { + @Before public void init() {fxt.Clear();} private final Io_mgr__fxt fxt = new Io_mgr__fxt(); + @Test public void Dir_delete_empty__basic() { + fxt.Exec_itm_create("mem/dir/"); + fxt.Exec_dir_delete_empty("mem/dir/"); + fxt.Test_itm_exists_n("mem/dir/"); + } + @Test public void Dir_delete_empty__no_delete() { + fxt.Exec_itm_create + ( "mem/dir/" + , "mem/dir/fil.txt" + ); + fxt.Exec_dir_delete_empty("mem/dir/"); + fxt.Test_itm_exists_y("mem/dir/"); + } + @Test public void Dir_delete_empty__nested_simple() { + fxt.Exec_itm_create + ( "mem/dir/" + , "mem/dir/1/" + , "mem/dir/1/11/" + ); + fxt.Exec_dir_delete_empty("mem/dir/"); + fxt.Test_itm_exists_n("mem/dir/"); + } + @Test public void Dir_delete_empty__nested_many() { + fxt.Exec_itm_create + ( "mem/dir/" + , "mem/dir/1/" + , "mem/dir/1/11/" + , "mem/dir/2/22/" + , "mem/dir/2/22/222a/" + , "mem/dir/2/22/222b/" + ); + fxt.Exec_dir_delete_empty("mem/dir/"); + fxt.Test_itm_exists_n("mem/dir/"); + } + @Test public void Dir_delete_empty__nested_some() { + fxt.Exec_itm_create + ( "mem/dir/" + , "mem/dir/1/" + , "mem/dir/1/11/" + , "mem/dir/2/22/" + , "mem/dir/2/22/a.txt" + , "mem/dir/2/22/222a/" + , "mem/dir/2/22/222b/" + ); + fxt.Exec_dir_delete_empty("mem/dir/"); + fxt.Test_itm_exists_n + ( "mem/dir/1/" + , "mem/dir/1/11/" + , "mem/dir/2/22/222a/" + , "mem/dir/2/22/222b/" + ); + fxt.Test_itm_exists_y + ( "mem/dir/" + , "mem/dir/2/22/" + ); + } +} +class Io_mgr__fxt { + public void Clear() {Io_mgr.Instance.InitEngine_mem();} + public void Exec_itm_create(String... ary) { + for (String itm : ary) { + Io_url url = Io_url_.new_any_(itm); + if (url.Type_dir()) + Io_mgr.Instance.CreateDir(url); + else + Io_mgr.Instance.SaveFilStr(url, url.NameAndExt()); + } + } + public void Exec_dir_delete_empty(String url) {Io_mgr.Instance.Delete_dir_empty(Io_url_.mem_dir_(url));} + public void Test_itm_exists_n(String... ary) {Test_itm_exists(Bool_.N, ary);} + public void Test_itm_exists_y(String... ary) {Test_itm_exists(Bool_.Y, ary);} + public void Test_itm_exists(boolean expd, String... ary) { + for (String itm : ary) { + Io_url url = Io_url_.new_any_(itm); + boolean actl = url.Type_dir() ? Io_mgr.Instance.ExistsDir(url) : Io_mgr.Instance.ExistsFil(url); + Tfds.Eq(expd, actl, itm); + } + } +} diff --git a/100_core/src/gplx/Io_url.java b/100_core/src/gplx/Io_url.java new file mode 100644 index 000000000..358163cd6 --- /dev/null +++ b/100_core/src/gplx/Io_url.java @@ -0,0 +1,89 @@ +/* +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; +import gplx.core.strings.*; import gplx.core.ios.*; /*IoUrlInfo*/ import gplx.core.envs.*; import gplx.langs.htmls.*; import gplx.core.interfaces.*; +public class Io_url implements CompareAble, ParseAble, Gfo_invk { //_20101005 URL:doc/Io_url.txt + public IoUrlInfo Info() {return info;} IoUrlInfo info; + public String Raw() {return raw;} final String raw; + public byte[] RawBry() {return Bry_.new_u8(raw);} + public String To_http_file_str() {return String_.Len_eq_0(raw) ? String_.Empty : String_.Concat (Http_file_str, Http_file_str_encoder.Encode_str(raw));} + public byte[] To_http_file_bry() {return String_.Len_eq_0(raw) ? Bry_.Empty : Bry_.Add (Http_file_bry, Http_file_str_encoder.Encode_bry(raw));} + public static Url_encoder_interface Http_file_str_encoder = Url_encoder_interface_same.Instance; + public static final String Http_file_str = "file:///"; + public static final int Http_file_len = String_.Len(Http_file_str); + public static final byte[] Http_file_bry = Bry_.new_a7(Http_file_str); + public boolean Type_dir() {return info.IsDir(raw);} public boolean Type_fil() {return !info.IsDir(raw);} + public Io_url OwnerDir() {return Io_url_.new_inf_(info.OwnerDir(raw), info);} + public Io_url OwnerRoot() {return Io_url_.new_inf_(info.OwnerRoot(raw), info);} + public String NameAndExt() {return info.NameAndExt(raw);} + public String NameAndExt_noDirSpr() {return this.Type_dir() ? this.NameOnly() : this.NameAndExt();} + public String NameOnly() {return info.NameOnly(raw);} + public String Ext() {return info.Ext(raw);} + public String Xto_api() {return info.Xto_api(raw);} + public String XtoCaseNormalized() {return String_.CaseNormalize(info.CaseSensitive(), raw);} + public Io_url GenSubDir(String subDirName) {return Io_url_.new_inf_(String_.Concat(raw, subDirName, info.DirSpr()), info);} + public Io_url GenSubDir_nest(String... ary) {return GenSub(false, ary);} + public Io_url GenSubFil(String val) {return Io_url_.new_inf_(raw + val, info);} + public Io_url GenSubFil_ary(String... ary) {return Io_url_.new_inf_(raw + String_.Concat(ary), info);} + public Io_url GenSubFil_nest(String... ary) {return GenSub(true, ary);} + public Io_url GenNewNameAndExt(String val) {return this.OwnerDir().GenSubFil(val);} + public Io_url GenNewNameOnly(String val) {return this.OwnerDir().GenSubFil(val + this.Ext());} + public Io_url GenNewExt(String val) {return this.OwnerDir().GenSubFil(this.NameOnly() + val);} + public String Gen_sub_path_for_os(String val) { + if (Op_sys.Cur().Tid_is_wnt()) val = String_.Replace(val, Op_sys.Lnx.Fsys_dir_spr_str(), Op_sys.Wnt.Fsys_dir_spr_str()); + return raw + val; + } + public String GenRelUrl_orEmpty(Io_url dir) { + String dirRaw = dir.Raw(); + return String_.Has_at_bgn(raw, dirRaw) + ? String_.DelBgn(raw, String_.Len(dirRaw)) + : String_.Empty; + } + public List_adp XtoNames() { + List_adp list = List_adp_.New(); + Io_url cur = this; + while (!cur.EqNull()) { + list.Add(cur.NameAndExt_noDirSpr()); + cur = cur.OwnerDir(); + } + list.Reverse(); + return list; + } + public Io_url GenParallel(Io_url oldRoot, Io_url newRoot) {return newRoot.GenSubFil_ary(GenRelUrl_orEmpty(oldRoot));} + public boolean Eq(Object obj) {if (obj == null) return false; return String_.Eq(raw, ((Io_url)obj).raw);} + public boolean EqNull() {return this.Eq(Io_url_.Empty);} + Io_url GenSub(boolean isFil, String[] ary) { + String_bldr sb = String_bldr_.new_().Add(raw); + int len = Array_.Len(ary); + for (int i = 0; i < len; i++) { + sb.Add(ary[i]); + if (isFil && i == len - 1) break; // do not add closing backslash if last term + sb.Add(info.DirSpr()); + } + return Io_url_.new_inf_(sb.To_str(), info); + } + public Object ParseAsObj(String raw) {return Io_url_.new_any_(raw);} + @Override public String toString() {return raw;} + public int compareTo(Object obj) {return CompareAble_.Compare_obj(raw, ((Io_url)obj).raw);} + @Override public boolean equals(Object obj) {return String_.Eq(raw, Io_url_.as_(obj).raw);} + @Override public int hashCode() {return raw.hashCode();} + @gplx.Internal protected Io_url(String raw, IoUrlInfo info) {this.raw = raw; this.info = info;} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_to_http_file)) return To_http_file_str(); + else if (ctx.Match(k, Invk_gen_sub_path_for_os)) return Gen_sub_path_for_os(m.ReadStr("v")); + else return Gfo_invk_.Rv_unhandled; + } static final String Invk_to_http_file = "to_http_file", Invk_gen_sub_path_for_os = "gen_sub_path_for_os"; +} diff --git a/100_core/src/gplx/Io_url_.java b/100_core/src/gplx/Io_url_.java new file mode 100644 index 000000000..f750fcafd --- /dev/null +++ b/100_core/src/gplx/Io_url_.java @@ -0,0 +1,111 @@ +/* +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; +import gplx.core.ios.*; /*IoUrlInfo_*/ import gplx.core.stores.*; import gplx.core.envs.*; +public class Io_url_ { + public static final Io_url Empty = new Io_url("", IoUrlInfo_.Nil); + public static final Io_url NullPtr = null; + public static final Io_url Parser = new Io_url("", IoUrlInfo_.Nil); + public static Io_url as_(Object obj) {return obj instanceof Io_url ? (Io_url)obj : null;} + public static Io_url cast(Object obj) {try {return (Io_url)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, Io_url.class, obj);}} + public static Io_url Usr() { + if (usr_dir == null) { + switch (Op_sys.Cur().Tid()) { + case Op_sys.Tid_wnt: usr_dir = Io_url_.new_inf_("C:\\", IoUrlInfo_.Wnt); break; + case Op_sys.Tid_lnx: usr_dir = Io_url_.new_inf_(String_.Format("/home/{0}/", System_.Prop__user_name()), IoUrlInfo_.Lnx); break; + case Op_sys.Tid_osx: usr_dir = Io_url_.new_inf_(String_.Format("/Users/{0}/", System_.Prop__user_name()), IoUrlInfo_.Lnx); break; + case Op_sys.Tid_drd: usr_dir = Io_url_.new_inf_(String_.Format("/mnt/{0}/", System_.Prop__user_name()), IoUrlInfo_.Lnx); break; + default: throw Err_.new_unhandled(Op_sys.Cur().Tid()); + } + } + return usr_dir; + } static Io_url usr_dir; + public static Io_url Usr_Gplx() {return Usr().GenSubDir("gplx");} + public static Io_url mem_dir_(String raw) { + raw = EndsWith_or_add(raw, Op_sys.Lnx.Fsys_dir_spr_str()); + return new Io_url(raw, IoUrlInfoRegy.Instance.Match(raw)); + } + public static Io_url mem_fil_(String raw) {return new_inf_(raw, IoUrlInfoRegy.Instance.Match(raw));} + public static Io_url wnt_fil_(String raw) {return new_inf_(raw, IoUrlInfo_.Wnt);} + public static Io_url wnt_dir_(String raw) {return new_inf_(EndsWith_or_add(raw, Op_sys.Wnt.Fsys_dir_spr_str()), IoUrlInfo_.Wnt);} + public static Io_url lnx_fil_(String raw) {return new_inf_(raw, IoUrlInfo_.Lnx);} + public static Io_url lnx_dir_(String raw) {return new_inf_(EndsWith_or_add(raw, Op_sys.Lnx.Fsys_dir_spr_str()), IoUrlInfo_.Lnx);} + public static Io_url new_fil_(String raw) {return new_any_(raw);} + public static Io_url new_dir_(String raw) {return new_any_(raw);} // NOTE: for now, same as new_fil; stack overflow when doing new_dir + public static Io_url new_dir_infer(String raw) {return Op_sys.Cur().Tid_is_wnt() ? wnt_dir_(raw) : lnx_dir_(raw);} + public static Io_url new_any_(String raw) {return new_inf_(raw, IoUrlInfoRegy.Instance.Match(raw));} + public static Io_url new_inf_(String raw, IoUrlInfo info) {return String_.Eq(raw, "") ? Io_url_.Empty : new Io_url(raw, info);} + public static Io_url New__http_or_fail(String raw) {return New__http_or_fail(Bry_.new_u8(raw));} + public static Io_url New__http_or_fail(byte[] raw) { + Io_url rv = New__http_or_null(raw); + if (rv == null) throw Err_.new_wo_type("url:invalid http_file raw", "raw", raw); + return rv; + } + public static Io_url New__http_or_null(String raw) {return New__http_or_null(Bry_.new_u8(raw));} + public static Io_url New__http_or_null(byte[] raw) { + int len = raw.length; + if (!Bry_.Has_at_bgn(raw, Io_url.Http_file_bry, 0, len)) return null; // doesn't start with "file:///"; return null; + + // bld rv; note that wnt has to convert / to \ + byte[] rv = null; + if (Op_sys.Cur().Tid_is_wnt()) { + int rv_len = len - Io_url.Http_file_len; + rv = new byte[rv_len]; + for (int i = 0; i < rv_len; ++i) { + byte b = raw[i + Io_url.Http_file_len]; + if (b == Op_sys.Dir_spr__lnx) b = Op_sys.Dir_spr__wnt; + rv[i] = b; + } + } + else + rv = Bry_.Mid(raw, Io_url.Http_file_len); + return rv == null ? null : new_any_(String_.new_u8(rv)); + } + + public static Io_url store_orFail_(SrlMgr mgr, String key, Io_url v) { + String s = mgr.SrlStrOr(key, v.Raw()); + return (mgr.Type_rdr()) ? Io_url_.new_any_(s) : v; + } + public static Io_url store_orSelf_(SrlMgr mgr, String key, Io_url v) { + String s = mgr.SrlStrOr(key, v.Raw()); + return (mgr.Type_rdr()) ? Io_url_.new_any_(s) : v; + } + public static Io_url rdrOr_(DataRdr rdr, String key, Io_url or) { + String val = rdr.ReadStrOr(key, null); if (val == null) return or; // NOTE: val == null also checks for rdr == DataRdr_.Null + return Io_url_.new_any_(val); + } + static String EndsWith_or_add(String raw, String endsWith) { + if (String_.Has_at_end(raw, endsWith)) return raw; + return raw += endsWith; + } + public static Io_url Rel_dir(String s) {return IsAbs(s) ? Io_url_.new_dir_(s) : Env_.AppUrl().OwnerDir().GenSubDir(s);} + public static Io_url Rel_fil(String s) {return IsAbs(s) ? Io_url_.new_fil_(s) : Env_.AppUrl().OwnerDir().GenSubFil(s);} + static boolean IsAbs(String s) { + return String_.Has_at_bgn(s, Op_sys.Lnx.Fsys_dir_spr_str()) + || (String_.Len(s) > 2 + && ( (String_.CharAt(s, 1) == ':' && String_.CharAt(s, 2) == '\\') + || (String_.CharAt(s, 1) == '\\' && String_.CharAt(s, 2) == '\\') + ) + ); + } + public static Io_url[] Ary(String... ary) { + int len = ary.length; + Io_url[] rv = new Io_url[len]; + for (int i = 0; i < len; ++i) + rv[i] = Io_url_.new_any_(ary[i]); + return rv; + } +} diff --git a/100_core/src/gplx/Io_url__tst.java b/100_core/src/gplx/Io_url__tst.java new file mode 100644 index 000000000..5d2198292 --- /dev/null +++ b/100_core/src/gplx/Io_url__tst.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; +import org.junit.*; import gplx.core.tests.*; import gplx.core.envs.*; +public class Io_url__tst { + @Before public void init() {fxt.Clear();} private final Io_url__fxt fxt = new Io_url__fxt(); + @Test public void Basic__lnx() {fxt.Test__New__http_or_null(Bool_.N, "file:///C:/a.txt", "C:/a.txt");} + @Test public void Basic__wnt() {fxt.Test__New__http_or_null(Bool_.Y, "file:///C:/a.txt", "C:\\a.txt");} + @Test public void Null() {fxt.Test__New__http_or_null(Bool_.N, "C:/a.txt", null);} +} +class Io_url__fxt { + public void Clear() {Io_mgr.Instance.InitEngine_mem();} + public void Test__New__http_or_null(boolean os_is_wnt, String raw, String expd) { + Op_sys.Cur_(os_is_wnt ? Op_sys.Tid_wnt : Op_sys.Tid_lnx); + Gftest.Eq__obj_or_null(expd, Io_url_.New__http_or_null(raw)); + } +} diff --git a/100_core/src/gplx/Keyval.java b/100_core/src/gplx/Keyval.java new file mode 100644 index 000000000..47b64e3bc --- /dev/null +++ b/100_core/src/gplx/Keyval.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; +public class Keyval implements To_str_able { + @gplx.Internal protected Keyval(int key_tid, Object key, Object val) {this.key_tid = key_tid; this.key = key; this.val = val;} + public String Key() {return Object_.Xto_str_strict_or_null(key);} + public Object Key_as_obj() {return key;} private Object key; + public int Key_tid() {return key_tid;} private int key_tid; + public Object Val() {return val;} private Object val; + public String Val_to_str_or_empty() {return Object_.Xto_str_strict_or_empty(val);} + public String Val_to_str_or_null() {return Object_.Xto_str_strict_or_null(val);} + public byte[] Val_to_bry() {return Bry_.new_u8(Object_.Xto_str_strict_or_null(val));} + public Keyval Key_(Object v) {this.key = v; return this;} + public Keyval Val_(Object v) {this.val = v; return this;} + public String To_str() {return Key() + "=" + Object_.Xto_str_strict_or_null_mark(val);} + @Override public String toString() {return To_str();} +} diff --git a/100_core/src/gplx/Keyval_.java b/100_core/src/gplx/Keyval_.java new file mode 100644 index 000000000..9eecc4124 --- /dev/null +++ b/100_core/src/gplx/Keyval_.java @@ -0,0 +1,105 @@ +/* +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; +import gplx.core.strings.*; +public class Keyval_ { + public static final Keyval[] Ary_empty = new Keyval[0]; + public static Keyval[] Ary(Keyval... ary) {return ary;} + public static Keyval[] Ary_cast_(Object o) { + try {return (Keyval[])o;} + catch (Exception e) {throw Err_.new_cast(e, Keyval.class, o);} + } + public static Keyval[] Ary_insert(Keyval[] orig, boolean insert_at_end, Keyval... vals) { + int orig_len = orig.length, vals_len = vals.length; + int rv_len = orig_len + vals_len; + Keyval[] rv = new Keyval[rv_len]; + int vals_bgn = 0 , vals_end = vals_len; + int orig_bgn = vals_len , orig_end = rv_len; + if (insert_at_end) { + orig_bgn = 0 ; orig_end = orig_len; + vals_bgn = orig_len ; vals_end = rv_len; + } + for (int i = orig_bgn; i < orig_end; i++) + rv[i] = orig[i - orig_bgn]; + for (int i = vals_bgn; i < vals_end; i++) + rv[i] = vals[i - vals_bgn]; + return rv; + } + public static String Ary_to_str(Keyval... ary) { + String_bldr sb = String_bldr_.new_(); + int len = ary.length; + for (int i = 0; i < len; i++) { + Keyval itm = ary[i]; + if (itm == null) { + sb.Add("<>"); + continue; + } + sb.Add(itm.Key()).Add("="); + Object itm_val = itm.Val(); + if (Type_.Eq_by_obj(itm_val, Keyval[].class)) + sb.Add(Ary_to_str((Keyval[])itm_val)); + else + sb.Add(Object_.Xto_str_strict_or_null_mark(itm_val)); + sb.Add_char_nl(); + } + return sb.To_str(); + } + public static Object Ary_get_by_key_or_null(Keyval[] ary, String key) { + int len = ary.length; + for (int i = 0; i < len; i++) { + Keyval kv = ary[i]; + if (String_.Eq(kv.Key(), key)) return kv.Val(); + } + return null; + } + public static String Ary__to_str__nest(Keyval... ary) { + Bry_bfr bfr = Bry_bfr_.New(); + Ary__to_str__nest(bfr, 0, ary); + return bfr.To_str_and_clear(); + } + private static void Ary__to_str__nest(Bry_bfr bfr, int indent, Keyval[] ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) { + Keyval itm = ary[i]; + if (indent > 0) + bfr.Add_byte_repeat(Byte_ascii.Space, indent * 2); // add indent : " " + bfr.Add_str_u8(Object_.Xto_str_strict_or_empty(itm.Key())).Add_byte_eq();// add key + eq : "key=" + Object val = itm.Val(); + if (val == null) + bfr.Add_str_a7(String_.Null_mark); + else { + Class val_type = Type_.Type_by_obj(val); + if (Type_.Eq(val_type, Keyval[].class)) { // val is Keyval[]; recurse + bfr.Add_byte_nl(); // add nl : "\n" + Ary__to_str__nest(bfr, indent + 1, (Keyval[])val); + continue; // don't add \n below + } + else if (Type_.Eq(val_type, Bool_.Cls_ref_type)) { // val is boolean + boolean val_as_bool = Bool_.Cast(val); + bfr.Add(val_as_bool ? Bool_.True_bry : Bool_.False_bry); // add "true" or "false"; don't call toString + } + else + bfr.Add_str_u8(Object_.Xto_str_strict_or_null_mark(val)); // call toString() + } + bfr.Add_byte_nl(); + } + } + public static Keyval as_(Object obj) {return obj instanceof Keyval ? (Keyval)obj : null;} + public static Keyval new_(String key) {return new Keyval(Type_ids_.Id__str, key, key);} + public static Keyval new_(String key, Object val) {return new Keyval(Type_ids_.Id__str, key, val);} + public static Keyval int_(int key, Object val) {return new Keyval(Type_ids_.Id__int, key, val);} + public static Keyval obj_(Object key, Object val) {return new Keyval(Type_ids_.Id__obj, key, val);} +} diff --git a/100_core/src/gplx/Keyval_hash.java b/100_core/src/gplx/Keyval_hash.java new file mode 100644 index 000000000..0cc108c84 --- /dev/null +++ b/100_core/src/gplx/Keyval_hash.java @@ -0,0 +1,40 @@ +/* +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; +public class Keyval_hash { + private final Ordered_hash hash = Ordered_hash_.New(); + public int Count() {return hash.Count();} + public int Len() {return hash.Count();} + public Keyval_hash Clear() {hash.Clear(); return this;} + public boolean Has(String key) {return hash.Has(key);} + public Keyval Get_at(int i) {return (Keyval)hash.Get_at(i);} + public Object Get_val_or(String key, Object or) {Keyval rv = Get_kvp_or_null(key); return rv == null ? or : rv.Val();} + public Object Get_val_or_null(String key) {return Get_val_or(key, null);} + public Object Get_val_or_fail(String key) {return Keyval_.as_(hash.Get_by_or_fail(key)).Val();} + public String Get_val_as_str_or_fail(String key) {return (String)Get_val_or_fail(key);} + public Keyval Get_kvp_or_null(String key) {return Keyval_.as_(hash.Get_by(key));} + public Keyval_hash Add(Keyval kv) {hash.Add(kv.Key(), kv); return this;} + public Keyval_hash Add(String key, Object val) {hash.Add(key, Keyval_.new_(key, val)); return this;} + public Keyval_hash Add_if_dupe_use_nth(String key, Object val) {hash.Add_if_dupe_use_nth(key, Keyval_.new_(key, val)); return this;} + public void Del(String key) {hash.Del(key);} + public Keyval[] To_ary() { + int len = this.Count(); + Keyval[] rv = new Keyval[len]; + for (int i = 0; i < len; ++i) + rv[i] = this.Get_at(i); + return rv; + } +} \ No newline at end of file diff --git a/100_core/src/gplx/Keyval_list.java b/100_core/src/gplx/Keyval_list.java new file mode 100644 index 000000000..e0f11f34b --- /dev/null +++ b/100_core/src/gplx/Keyval_list.java @@ -0,0 +1,34 @@ +/* +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; +public class Keyval_list { + public int Count() {return list.Count();} private final List_adp list = List_adp_.New(); + public void Clear() {list.Clear();} + public Keyval Get_at(int i) {return (Keyval)list.Get_at(i);} + public Keyval_list Add(String key, Object val) {list.Add(Keyval_.new_(key, val)); return this;} + public Keyval[] To_ary() {return (Keyval[])list.To_ary(Keyval.class);} + public String To_str() { + Bry_bfr bfr = Bry_bfr_.New(); + int len = list.Count(); + for (int i = 0; i < len; ++i) { + Keyval kv = (Keyval)list.Get_at(i); + if (i == 0) bfr.Add_byte_space(); + bfr.Add_str_u8(kv.Key()).Add_byte_eq().Add_str_u8(kv.Val_to_str_or_empty()); + } + return bfr.To_str_and_clear(); + } + public static Keyval_list New_with_one(String key, Object val) {return new Keyval_list().Add(key, val);} +} diff --git a/100_core/src/gplx/List_adp.java b/100_core/src/gplx/List_adp.java new file mode 100644 index 000000000..0bebb6494 --- /dev/null +++ b/100_core/src/gplx/List_adp.java @@ -0,0 +1,75 @@ +/* +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; +import gplx.core.lists.*; /*EnumerAble,ComparerAble*/ +public interface List_adp extends EnumerAble, List_adp__getable { + int Count(); + Object Get_at_last(); + void Add(Object o); + void Add_at(int i, Object o); + void Add_many(Object... ary); + void Del(Object o); + void Del_at(int i); + void Del_range(int bgn, int end); + void Clear(); + int Idx_of(Object o); + int Idx_last(); + Object To_ary(Class memberType); + Object To_ary_and_clear(Class memberType); + String[] To_str_ary(); + String[] To_str_ary_and_clear(); + String To_str(); + Object[] To_obj_ary(); + void Resize_bounds(int i); + void Move_to(int src, int trg); + void Reverse(); + void Sort(); + void Sort_by(ComparerAble comparer); + void Shuffle(); +} +class List_adp_obj extends List_adp_base implements List_adp { + public List_adp_obj() {super();} + public List_adp_obj(int v) {super(v);} +} +class List_adp_noop implements List_adp { + public int Count() {return 0;} + public int Len() {return 0;} + public Object Get_at(int i) {return null;} + public Object Get_at_last() {return null;} + public Object PopLast() {return null;} + public void Add(Object o) {} + public void Add_at(int i, Object o) {} + public void Add_many(Object... ary) {} + public void Del(Object o) {} + public void Del_at(int i) {} + public void Del_range(int bgn, int end) {} + public void Clear() {} + public int Idx_last() {return -1;} + public int Idx_of(Object o) {return List_adp_.Not_found;} + public void Move_to(int elemPos, int newPos) {} + public void Resize_bounds(int i) {} + public Object To_ary(Class memberType) {return Object_.Ary_empty;} + public Object To_ary_and_clear(Class memberType) {return Object_.Ary_empty;} + public String[] To_str_ary() {return String_.Ary_empty;} + public String[] To_str_ary_and_clear() {return To_str_ary();} + public String To_str() {return "";} + public Object[] To_obj_ary() {return Object_.Ary_empty;} + public java.util.Iterator iterator() {return Iterator_null.Instance;} + public void Reverse() {} + public void Sort() {} + public void Sort_by(ComparerAble comparer) {} + public void Shuffle() {} +} diff --git a/100_core/src/gplx/List_adp_.java b/100_core/src/gplx/List_adp_.java new file mode 100644 index 000000000..8e5795d0c --- /dev/null +++ b/100_core/src/gplx/List_adp_.java @@ -0,0 +1,53 @@ +/* +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; +import gplx.core.lists.*; +public class List_adp_ { + public static final List_adp Noop = new List_adp_noop(); + public static List_adp New() {return new List_adp_obj();} + public static List_adp New_w_size(int v) {return new List_adp_obj(v);} + public static List_adp New_by_many(Object... ary) { + List_adp rv = new List_adp_obj(); + rv.Add_many(ary); + return rv; + } + public static void Del_at_last(List_adp list) {list.Del_at(list.Count() - 1);} + public static Object Pop(List_adp list) { + int lastIdx = list.Count() - 1; + Object rv = list.Get_at(lastIdx); + list.Del_at(lastIdx); + return rv; + } + public static Object Pop_first(List_adp list) { // NOTE: dirty way of implementing FIFO queue; should not be used with lists with many members + Object rv = list.Get_at(0); + list.Del_at(0); + return rv; + } + public static Object Pop_last(List_adp list) { + int last_idx = list.Count() - 1; + Object rv = list.Get_at(last_idx); + list.Del_at(last_idx); + return rv; + } + public static Object Pop_or(List_adp list, Object or) { + int list_len = list.Count(); if (list_len == 0) return or; + int last_idx = list_len - 1; + Object rv = list.Get_at(last_idx); + list.Del_at(last_idx); + return rv; + } + public static final int Not_found = -1, Base1 = 1; +} diff --git a/100_core/src/gplx/List_adp_base.java b/100_core/src/gplx/List_adp_base.java new file mode 100644 index 000000000..99b36cec8 --- /dev/null +++ b/100_core/src/gplx/List_adp_base.java @@ -0,0 +1,173 @@ +/* +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; +import gplx.core.strings.*; import gplx.core.lists.*; +public abstract class List_adp_base implements List_adp, Gfo_invk { + private Object[] list; private int count; + public List_adp_base(int capacity) { + this.list = new Object[capacity]; + } + public java.util.Iterator iterator() { + if (count == 0) + return Iterator_null.Instance; + else + return new Iterator_objAry(list, count); + } + public void Add_many(Object... ary) {for (Object o : ary) Add_base(o);} + public int Len() {return count;} + public int Count() {return count;} + public int Idx_last() {return count - 1;} + protected Object Get_at_base(int index) {if (index >= count || index < 0) throw Err_.new_missing_idx(index, count); + return list[index]; + } + protected void Add_base(Object o) { + if (count == Array_.Len_obj(list)) Resize_expand(); + list[count] = o; + count++; + } + protected int Del_base(Object o) { + int index = IndexOf_base(o); if (index == List_adp_.Not_found) return List_adp_.Not_found; + this.Del_at(index); + return index; + } + public void Del_range(int delBgn, int delEnd) { + BoundsChk(delBgn, delEnd, count); + if (delBgn == 0 && delEnd == count - 1) { // entire list deleted; call .Clear, else will have 0 elem array + this.Clear(); + return; + } + int delLen = (delEnd - delBgn) + 1; // EX: 0,2 creates 3 len ary + int newLen = count - delLen; + Object[] newList = new Object[newLen]; + if (delBgn != 0) // copy elements < delBgn; skip if delBgn == 0 + Array_.Copy_to(list, 0, newList, 0, delBgn); + if (delEnd != count -1 ) // copy elements > delEnd; skip if delEnd == lastIdx + Array_.Copy_to(list, delEnd + 1, newList, delBgn, newLen - delBgn); + list = newList; + count = list.length; + } + protected int IndexOf_base(Object o) { + for (int i = 0; i < count; i++) + if (Object_.Eq(list[i], o)) return i; + return List_adp_.Not_found; + } + @gplx.Virtual public void Clear() { + for (int i = 0; i < count; i++) + list[i] = null; + count = 0; + } + @gplx.Virtual public void Del_at(int index) {if (index >= count || index < 0) throw Err_.new_missing_idx(index, count); + Collapse(index); + count--; + } + public void Move_to(int src, int trg) {if (src >= count || src < 0) throw Err_.new_missing_idx(src, count); if (trg >= count || trg < 0) throw Err_.new_missing_idx(trg, count); + if (src == trg) return; // position not changed + Object o = list[src]; + int dif = trg > src ? 1 : -1; + for (int i = src; i != trg; i += dif) + list[i] = list[i + dif]; + list[trg] = o; + } + protected void AddAt_base(int pos, Object o) { + if (count + 1 >= Array_.Len_obj(list)) Resize_expand(); + for (int i = count; i > pos; i--) + list[i] = list[i - 1]; + list[pos] = o; + count = count + 1; + } + public void Resize_bounds(int i) { + Resize_expand(i); + } + public void Sort() {Sort_by(null);} + public void Sort_by(ComparerAble comparer) {List_adp_sorter.new_().Sort(list, count, true, comparer);} + public void Reverse() { + int mid = count / 2; // no need to reverse pivot; ex: for 3 elements, only 1 and 3 need to be exchanged; 2 stays inplace + for (int lhs = 0; lhs < mid; lhs++) { + int rhs = count - lhs - 1; // -1 b/c list[count] is not real element + Object temp = list[lhs]; + list[lhs] = list[rhs]; + list[rhs] = temp; + } + } + @gplx.Virtual public void Shuffle() {// REF: Fisher-Yates shuffle + RandomAdp random = RandomAdp_.new_(); + for (int i = count; i > 1; i--) { + int rndIdx = random.Next(i); + Object tmp = list[rndIdx]; + list[rndIdx] = list[i-1]; + list[i-1] = tmp; + } + } + public Object Get_at(int i) {return Get_at_base(i);} + public Object Get_at_last() {if (count == 0) throw Err_.new_invalid_op("cannot call Get_at_last on empty list"); return Get_at_base(count - 1);} + public void Add(Object item) {Add_base(item);} + public void Add_at(int i, Object o) {AddAt_base(i, o);} + public void Del(Object item) {Del_base(item);} + public int Idx_of(Object o) {return IndexOf_base(o);} + public List_adp_base() { + list = new Object[Len_initial]; + } + private static final int Len_initial = 8; + public Object To_ary_and_clear(Class memberType) {Object rv = To_ary(memberType); this.Clear(); return rv;} + public Object To_ary(Class memberType) { + Object rv = Array_.Create(memberType, count); + for (int i = 0; i < count; i++) + Array_.Set_at(rv, i, list[i]); + return rv; + } + public String[] To_str_ary_and_clear() {String[] rv = To_str_ary(); this.Clear(); return rv;} + public String[] To_str_ary() {return (String[])To_ary(String.class);} + public Object[] To_obj_ary() { + Object[] rv = new Object[count]; + for (int i = 0; i < count; ++i) + rv[i] = list[i]; + return rv; + } + public String To_str() { + Bry_bfr bfr = Bry_bfr_.New(); + for (int i = 0; i < count; ++i) + bfr.Add_str_u8(Object_.Xto_str_strict_or_null_mark(list[i])).Add_byte_nl(); + return bfr.To_str_and_clear(); + } + private void BoundsChk(int bgn, int end, int len) { + if ( bgn >= 0 && bgn < len + && end >= 0 && end < len + && bgn <= end + ) return; + throw Err_.new_wo_type("bounds check failed", "bgn", bgn, "end", end, "len", len); + } + void Resize_expand() {Resize_expand(count * 2);} + void Resize_expand(int newCount) { + Object[] trg = new Object[newCount]; + for (int i = 0; i < count; i++) { + trg[i] = list[i]; + list[i] = null; + } + list = trg; + } + void Collapse(int index) { + for (int i = index; i < count; i++) { + list[i] = (i == count - 1) ? null : list[i + 1]; + } + } + @gplx.Internal protected int Capacity() {return Array_.Len_obj(list);} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_len)) return count; + else if (ctx.Match(k, Invk_get_at)) return Get_at(m.ReadInt("v")); + else return Gfo_invk_.Rv_unhandled; +// return this; + } private static final String Invk_len = "len", Invk_get_at = "get_at"; +} diff --git a/100_core/src/gplx/List_adp_tst.java b/100_core/src/gplx/List_adp_tst.java new file mode 100644 index 000000000..48cb8f3be --- /dev/null +++ b/100_core/src/gplx/List_adp_tst.java @@ -0,0 +1,220 @@ +/* +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; +import org.junit.*; +public class List_adp_tst { + @Before public void setup() { + list = List_adp_.New(); + listBase = (List_adp_base)list; + } List_adp list; List_adp_base listBase; + @Test public void Add() { + Tfds.Eq(0, list.Count()); + + list.Add("0"); + Tfds.Eq(1, list.Count()); + } + @Test public void Add_changeCapacity() { + int capacity = 8; + for (int i = 0; i < capacity; i++) + list.Add("0"); + Tfds.Eq(capacity, list.Count()); + Tfds.Eq(capacity, listBase.Capacity()); + + list.Add(capacity); // forces resize + Tfds.Eq(capacity + 1, list.Count()); + Tfds.Eq(capacity * 2, listBase.Capacity()); + } + @Test public void Get_at() { + list.Add("0"); + + Tfds.Eq("0", list.Get_at(0)); + } + @Test public void Fetch_many() { + list_AddMany("0", "1"); + + Tfds.Eq("0", list.Get_at(0)); + Tfds.Eq("1", list.Get_at(1)); + } + @Test public void FetchAt_fail() { + try {list.Get_at(0);} + catch (Exception exc) {Err_.Noop(exc); return;} + Tfds.Fail("Get_at should fail for out of bound index"); + } + @Test public void Del_at() { + list.Add("0"); + Tfds.Eq(1, list.Count()); + + list.Del_at(0); + Tfds.Eq(0, list.Count()); + } + @Test public void DelAt_shiftDown() { + list_AddMany("0", "1"); + Tfds.Eq(list.Count(), 2); + + list.Del_at(0); + Tfds.Eq(1, list.Count()); + Tfds.Eq("1", list.Get_at(0)); + } + @Test public void DelAt_fail() { + try {list.Del_at(0);} + catch (Exception exc) {Err_.Noop(exc); return;} + Tfds.Fail("Del_at should fail for out of bound index"); + } + @Test public void Del() { + list.Add("0"); + Tfds.Eq(1, list.Count()); + + list.Del("0"); + Tfds.Eq(0, list.Count()); + } + @Test public void Del_matchMember() { + list_AddMany("0", "1"); + Tfds.Eq(2, list.Count()); + + list.Del("1"); + Tfds.Eq(1, list.Count()); + Tfds.Eq("0", list.Get_at(0)); + } + @Test public void Del_matchFirst() { + list_AddMany("0", "1", "0"); + Tfds.Eq(3, list.Count()); + + list.Del("0"); + tst_Enumerator("1", "0"); + } + @Test public void Enumerator() { + list_AddMany("0", "1", "2"); + tst_Enumerator("0", "1", "2"); + } + @Test public void Enumerator_stateLess() { // run 2x, to confirm no state is being cached + list_AddMany("0", "1", "2"); + tst_Enumerator("0", "1", "2"); + tst_Enumerator("0", "1", "2"); + } + @Test public void Enumerator_recursive() { // confirm separate enumerator objects are used + int pos = 0; + list_AddMany("0", "1", "2"); + for (Object valObj : list) { + String val = (String)valObj; + Tfds.Eq(Int_.To_str(pos++), val); + tst_Enumerator("0", "1", "2"); + } + } + @Test public void Clear() { + int capacity = 8; + for (int i = 0; i < capacity + 1; i++) + list.Add("0"); + Tfds.Eq(capacity * 2, listBase.Capacity()); + + list.Clear(); + Tfds.Eq(0, list.Count()); + Tfds.Eq(16, listBase.Capacity()); // check that capacity has increased + } + @Test public void Clear_empty() { // confirm no failure + list.Clear(); + Tfds.Eq(0, list.Count()); + } + @Test public void Reverse() { + list_AddMany("0", "1", "2"); + + list.Reverse(); + tst_Enumerator("2", "1", "0"); + } + @Test public void Reverse_empty() {list.Reverse();} + @Test public void Sort() { + list_AddMany("2", "0", "1"); + + list.Sort(); + tst_Enumerator("0", "1", "2"); + } + @Test public void Sort_empty() {list.Sort();} + @Test public void Xto_bry() { + list_AddMany("0", "1"); + String[] ary = (String[])list.To_ary(String.class); + Tfds.Eq_nullNot(ary); + Tfds.Eq(2, Array_.Len(ary)); + } + @Test public void XtoAry_empty() { + String[] ary = (String[])list.To_ary(String.class); + Tfds.Eq_nullNot(ary); + Tfds.Eq(0, Array_.Len(ary)); + } + @Test public void Shuffle() { + for (int i = 0; i < 25; i++) + list.Add(i); + + list.Shuffle(); + int hasMovedCount = 0; + for (int i = 0; i < list.Count(); i++) { + int val = Int_.Cast(list.Get_at(i)); + if (val != i) hasMovedCount++; + } + Tfds.Eq_true(hasMovedCount > 0, "all documents have the same index"); // NOTE: may still fail occasionally (1%) + + int count = list.Count(); + for (int i = 0; i < count; i++) + list.Del(i); + Tfds.Eq(0, list.Count(), "shuffled list does not have the same contents as original list"); + } + @Test public void Shuffle_empty() {list.Shuffle();} + @Test public void Move_to() { + run_ClearAndAdd("0", "1", "2").run_MoveTo(0, 1).tst_Order("1", "0", "2"); + run_ClearAndAdd("0", "1", "2").run_MoveTo(0, 2).tst_Order("1", "2", "0"); + run_ClearAndAdd("0", "1", "2").run_MoveTo(2, 1).tst_Order("0", "2", "1"); + run_ClearAndAdd("0", "1", "2").run_MoveTo(2, 0).tst_Order("2", "0", "1"); + } + @Test public void Del_range() { + run_ClearAndAdd("0", "1", "2", "3").tst_DelRange(0, 2, "3"); + run_ClearAndAdd("0", "1", "2", "3").tst_DelRange(0, 3); + run_ClearAndAdd("0", "1", "2", "3").tst_DelRange(1, 2, "0", "3"); + run_ClearAndAdd("0", "1", "2", "3").tst_DelRange(1, 3, "0"); + run_ClearAndAdd("0", "1", "2", "3").tst_DelRange(0, 3); + run_ClearAndAdd("0", "1", "2", "3").tst_DelRange(0, 0, "1", "2", "3"); + } + void tst_DelRange(int bgn, int end, String... expd) { + list.Del_range(bgn, end); + Tfds.Eq_ary_str(expd, list.To_str_ary()); + } + List_adp_tst run_ClearAndAdd(String... ary) { + list.Clear(); + for (int i = 0; i < Array_.Len(ary); i++) { + String val = ary[i]; + list.Add(val); + } + return this; + } + List_adp_tst run_MoveTo(int elemPos, int newPos) {list.Move_to(elemPos, newPos); return this;} + List_adp_tst tst_Order(String... expd) { + String[] actl = (String[])list.To_ary(String.class); + Tfds.Eq_ary(expd, actl); + return this; + } + void list_AddMany(String... ary) { + for (int i = 0; i < Array_.Len(ary); i++) { + String val = ary[i]; + list.Add(val); + } + } + void tst_Enumerator(String... expd) { + int pos = 0; + int expdLength = Array_.Len(expd); + for (int i = 0; i < expdLength; i++) { + String val = expd[i]; + Tfds.Eq(expd[pos++], val); + } + Tfds.Eq(pos, expdLength); + } +} diff --git a/100_core/src/gplx/Long_.java b/100_core/src/gplx/Long_.java new file mode 100644 index 000000000..f6fd0d132 --- /dev/null +++ b/100_core/src/gplx/Long_.java @@ -0,0 +1,110 @@ +/* +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; +public class Long_ { + public static final String Cls_val_name = "long"; + public static final Class Cls_ref_type = Long.class; + public static final int Log10Ary_len = 21; + public static long[] Log10Ary = new long[] + { 1, 10, 100, 1000, 10000 + , 100000, 1000000, 10000000, 100000000, 1000000000 + , Long_.Pow(10, 10), Long_.Pow(10, 11), Long_.Pow(10, 12), Long_.Pow(10, 13), Long_.Pow(10, 14) + , Long_.Pow(10, 15), Long_.Pow(10, 16), Long_.Pow(10, 17), Long_.Pow(10, 18), Long_.Pow(10, 19) + , Long_.Max_value + }; + public static long parse(String raw) {try {return Long.parseLong(raw);} catch(Exception e) {throw Err_.new_parse_exc(e, long.class, raw);}} + public static long cast(Object obj) {try {return (Long)obj;} catch(Exception e) {throw Err_.new_type_mismatch_w_exc(e, long.class, obj);}} + public static long coerce_(Object v) { + try {String s = String_.as_(v); return s == null ? Long_.cast(v) : Long_.parse(s);} + catch (Exception e) {throw Err_.new_cast(e, long.class, v);} + } + public static String To_str(long v) {return Long.toString(v);} + public static String To_str_PadBgn(long v, int reqdPlaces) {return String_.Pad(To_str(v), reqdPlaces, "0", true);} // ex: 1, 3 returns 001 + public static long parse_or(String raw, long or) { + if (raw == null) return or; + try { + int rawLen = String_.Len(raw); + if (raw == null || rawLen == 0) return or; + long rv = 0, factor = 1; int tmp = 0; + for (int i = rawLen; i > 0; i--) { + tmp = Char_.To_int_or(String_.CharAt(raw, i - 1), Int_.Min_value); + if (tmp == Int_.Min_value) return or; + rv += (tmp * factor); + factor *= 10; + } + return rv; + } catch (Exception e) {Err_.Noop(e); return or;} + } + public static int Compare(long lhs, long rhs) { + if (lhs == rhs) return CompareAble_.Same; + else if (lhs < rhs) return CompareAble_.Less; + else return CompareAble_.More; + } + private static int FindIdx(long[] ary, long find_val) { + int ary_len = ary.length; + int adj = 1; + int prv_pos = 0; + int prv_len = ary_len; + int cur_len = 0; + int cur_idx = 0; + long cur_val = 0; + while (true) { + cur_len = prv_len / 2; + if (prv_len % 2 == 1) ++cur_len; + cur_idx = prv_pos + (cur_len * adj); + if (cur_idx < 0) cur_idx = 0; + else if (cur_idx >= ary_len) cur_idx = ary_len - 1; + cur_val = ary[cur_idx]; + if (find_val < cur_val) adj = -1; + else if (find_val > cur_val) adj = 1; + else if (find_val == cur_val) return cur_idx; + if (cur_len == 1) { + if (adj == -1 && cur_idx > 0) + return --cur_idx; + return cur_idx; + } + prv_len = cur_len; + prv_pos = cur_idx; + } + } + public static int DigitCount(long v) { + int adj = Int_.Base1; + if (v < 0) { + if (v == Long_.Min_value) return 19; // NOTE: Long_.Min_value * -1 = Long_.Min_value + v *= -1; + ++adj; + } + return FindIdx(Log10Ary, v) + adj; + } + public static long Pow(int val, int exp) { + long rv = val; + for (int i = 1; i < exp; i++) + rv *= val; + return rv; + } + public static long Int_merge(int hi, int lo) {return (long)hi << 32 | (lo & 0xFFFFFFFFL);} + public static int Int_split_lo(long v) {return (int)(v);} + public static int Int_split_hi(long v) {return (int)(v >> 32);} + public static final long + Min_value = Long.MIN_VALUE + , Max_value = Long.MAX_VALUE + ; +} +/* alternate for Int_merge; does not work in java + public static long MergeInts(int lo, int hi) {return (uint)(hi << 32) | (lo & 0xffffffff);} + public static int SplitLo(long v) {return (int)(((ulong)v & 0x00000000ffffffff));} + public static int SplitHi(long v) {return (int)(((ulong)v & 0xffffffff00000000)) >> 32;} +*/ diff --git a/100_core/src/gplx/Long__tst.java b/100_core/src/gplx/Long__tst.java new file mode 100644 index 000000000..1fa27a77e --- /dev/null +++ b/100_core/src/gplx/Long__tst.java @@ -0,0 +1,47 @@ +/* +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; +import org.junit.*; +public class Long__tst { + @Test public void DigitCount() { + tst_DigitCount(0, 1); + tst_DigitCount(1, 1); + tst_DigitCount(9, 1); + tst_DigitCount(10, 2); + tst_DigitCount(100, 3); + tst_DigitCount(10000, 5); + tst_DigitCount(100000, 6); + tst_DigitCount(1000000, 7); + tst_DigitCount(1000000000, 10); + tst_DigitCount(10000000000L, 11); + tst_DigitCount(100000000000L, 12); + tst_DigitCount(10000000000000000L, 17); + tst_DigitCount(-1, 2); + } void tst_DigitCount(long val, int expd) {Tfds.Eq(expd, Long_.DigitCount(val));} + @Test public void Int_merge() { + tst_Int_merge(123, 456, 528280977864L); + tst_Int_merge(123, 457, 528280977865L); + } + void tst_Int_merge(int hi, int lo, long expd) { + Tfds.Eq(expd, Long_.Int_merge(hi, lo)); + Tfds.Eq(hi, Long_.Int_split_hi(expd)); + Tfds.Eq(lo, Long_.Int_split_lo(expd)); + } + @Test public void parse_or() { + parse_or_tst("10000000000", 10000000000L); + } + void parse_or_tst(String raw, long expd) {Tfds.Eq(expd, Long_.parse_or(raw, -1));} +} diff --git a/100_core/src/gplx/Math_.java b/100_core/src/gplx/Math_.java new file mode 100644 index 000000000..6fb8f5f8e --- /dev/null +++ b/100_core/src/gplx/Math_.java @@ -0,0 +1,72 @@ +/* +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; +public class Math_ { + public static double Pow(double val, double exponent) {return java.lang.Math.pow(val, exponent);} + public static double Pi = java.lang.Math.PI; + public static double E = java.lang.Math.E; + public static int Ceil_as_int(double v) {return (int)Ceil(v);} + public static double Ceil(double v) {return java.lang.Math.ceil(v);} + public static int Floor_as_int(double v) {return (int)Floor(v);} + public static double Floor(double v) {return java.lang.Math.floor(v);} + public static double Round(double v, int places) { + return java.math.BigDecimal.valueOf(v).setScale(places, java.math.BigDecimal.ROUND_HALF_UP).doubleValue(); + } + public static int Trunc(double v) {return (int)v;} + public static double Exp(double v) {return java.lang.Math.exp(v);} + public static double Log(double v) {return java.lang.Math.log(v);} + public static double Sin(double v) {return java.lang.Math.sin(v);} + public static double Cos(double v) {return java.lang.Math.cos(v);} + public static double Tan(double v) {return java.lang.Math.tan(v);} + public static double Asin(double v) {return java.lang.Math.asin(v);} + public static double Acos(double v) {return java.lang.Math.acos(v);} + public static double Atan(double v) {return java.lang.Math.atan(v);} + public static double Sqrt(double v) {return java.lang.Math.sqrt(v);} + public static int Abs(int val) {return val > 0 ? val : val * -1;} + public static long Abs(long val) {return val > 0 ? val : val * -1;} + public static float Abs(float val) {return val > 0 ? val : val * -1;} + public static double Abs_double(double val) {return val > 0 ? val : val * -1;} + public static int Log10(int val) { + if (val <= 0) return Int_.Min_value; + int rv = -1, baseVal = 10; + while (val != 0) { + val = (val / baseVal); + rv++; + } + return rv; + } + public static int Div_safe_as_int(int val, int divisor) {return divisor == 0 ? 0 : val / divisor;} + public static long Div_safe_as_long(long val, long divisor) {return divisor == 0 ? 0 : val / divisor;} + public static double Div_safe_as_double(double val, double divisor) {return divisor == 0 ? 0 : val / divisor;} + public static int Min(int val0, int val1) {return val0 < val1 ? val0 : val1;} + public static int Max(int val0, int val1) {return val0 > val1 ? val0 : val1;} + public static int[] Base2Ary(int v, int max) { + int[] idxs = new int[32]; + int cur = v, mult = max, idx = 0; + while (mult > 0) { + int tmp = cur / mult; + if (tmp >= 1) { + idxs[idx++] = mult; + cur -= mult; + } + mult /= 2; + } + int[] rv = new int[idx]; + for (int i = 0; i < idx; i++) + rv[i] = idxs[idx - i - 1]; + return rv; + } +} \ No newline at end of file diff --git a/100_core/src/gplx/Math__tst.java b/100_core/src/gplx/Math__tst.java new file mode 100644 index 000000000..75ba24d8d --- /dev/null +++ b/100_core/src/gplx/Math__tst.java @@ -0,0 +1,59 @@ +/* +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; +import org.junit.*; +public class Math__tst { + @Test public void Abs() { + tst_Abs(1, 1); + tst_Abs(-1, 1); + tst_Abs(0, 0); + } void tst_Abs(int val, int expd) {Tfds.Eq(expd, Math_.Abs(val));} + @Test public void Log10() { + tst_Log10(0, Int_.Min_value); + tst_Log10(9, 0); + tst_Log10(10, 1); + tst_Log10(99, 1); + tst_Log10(100, 2); + } void tst_Log10(int val, int expd) {Tfds.Eq(expd, Math_.Log10(val));} + @Test public void Min() { + tst_Min(0, 1, 0); + tst_Min(1, 0, 0); + tst_Min(0, 0, 0); + } void tst_Min(int val0, int val1, int expd) {Tfds.Eq(expd, Math_.Min(val0, val1));} + @Test public void Pow() { + tst_Pow(2, 0, 1); + tst_Pow(2, 1, 2); + tst_Pow(2, 2, 4); + } void tst_Pow(int val, int exponent, double expd) {Tfds.Eq(expd, Math_.Pow(val, exponent));} + @Test public void Mult() { + tst_Mult(100, .01f, 1); + } void tst_Mult(int val, float multiplier, int expd) {Tfds.Eq(expd, Int_.Mult(val, multiplier));} + @Test public void Base2Ary() { + tst_Base2Ary( 1, 256, 1); + tst_Base2Ary( 2, 256, 2); + tst_Base2Ary( 3, 256, 1, 2); + tst_Base2Ary( 4, 256, 4); + tst_Base2Ary( 5, 256, 1, 4); + tst_Base2Ary( 6, 256, 2, 4); + tst_Base2Ary(511, 256, 1, 2, 4, 8, 16, 32, 64, 128, 256); + } void tst_Base2Ary(int v, int max, int... expd) {Tfds.Eq_ary(expd, Math_.Base2Ary(v, max));} + @Test public void Round() { + tst_Round(1.5 , 0, 2); + tst_Round(2.5 , 0, 3); + tst_Round(2.123 , 2, 2.12); + tst_Round(21.1 , -1, 20); + } void tst_Round(double v, int places, double expd) {Tfds.Eq(expd, Math_.Round(v, places));} +} diff --git a/100_core/src/gplx/New.java b/100_core/src/gplx/New.java new file mode 100644 index 000000000..ea7b2b121 --- /dev/null +++ b/100_core/src/gplx/New.java @@ -0,0 +1,17 @@ +/* +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; +public @interface New {} diff --git a/100_core/src/gplx/ObjAry.java b/100_core/src/gplx/ObjAry.java new file mode 100644 index 000000000..32fa41c15 --- /dev/null +++ b/100_core/src/gplx/ObjAry.java @@ -0,0 +1,34 @@ +/* +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; +public class ObjAry { + public Object[] Ary() {return ary;} Object[] ary; + public Object Get(int i) {return ary[i];} + public Object Get0() {return ary[0];} + public Object Get1() {return ary[1];} + public static ObjAry pair_(Object val0, Object val1) { + ObjAry rv = new ObjAry(); + rv.ary = new Object[2]; + rv.ary[0] = val0; + rv.ary[1] = val1; + return rv; + } ObjAry() {} + public static ObjAry many_(Object... ary) { + ObjAry rv = new ObjAry(); + rv.ary = ary; + return rv; + } +} diff --git a/100_core/src/gplx/Object_.java b/100_core/src/gplx/Object_.java new file mode 100644 index 000000000..00e8017bf --- /dev/null +++ b/100_core/src/gplx/Object_.java @@ -0,0 +1,59 @@ +/* +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; +public class Object_ { + public static final String Cls_val_name = "Object"; + public static final Object[] Ary_empty = new Object[0]; + public static Object[] Ary(Object... ary) {return ary;} + public static Object[] Ary_add(Object[] lhs, Object[] rhs) { + int lhs_len = lhs.length, rhs_len = rhs.length; + if (lhs_len == 0) return rhs; + else if (rhs_len == 0) return lhs; + 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]; + for (int i = lhs_len; i < rv_len; ++i) + rv[i] = rhs[i - lhs_len]; + 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; + else return lhs.equals(rhs); + } + public static String Xto_str_or(Object v, String or) {return v == null ? or : ToString_lang(v);} + public static String Xto_str_strict_or_null(Object v) {return v == null ? null : ToString_lang(v);} + public static String Xto_str_strict_or_null_mark(Object v) {return v == null ? String_.Null_mark : ToString_lang(v);} + public static String Xto_str_strict_or_empty(Object v) {return v == null ? String_.Empty : ToString_lang(v);} + private static String ToString_lang(Object v) { + Class c = v.getClass(); + if (Type_.Eq(c, String_.Cls_ref_type)) return (String)v; + else if (Type_.Eq(c, Bry_.Cls_ref_type)) return String_.new_u8((byte[])v); + else return v.toString(); + } + public static String Xto_str_loose_or(Object v, String or) { // tries to pretty-print doubles; also standardizes true/false; DATE:2014-07-14 + if (v == null) return null; + Class c = Type_.Type_by_obj(v); + if (Type_.Eq(c, String_.Cls_ref_type)) return (String)v; + else if (Type_.Eq(c, Bry_.Cls_ref_type)) return String_.new_u8((byte[])v); + else if (Type_.Eq(c, Bool_.Cls_ref_type)) return Bool_.Cast(v) ? Bool_.True_str : Bool_.False_str; // always return "true" / "false" + else if (Type_.Eq(c, Double_.Cls_ref_type)) return Double_.To_str_loose(Double_.cast(v)); + else return v.toString(); + } + public static final Object Null = null; + public static final byte[] Bry__null = Bry_.new_a7("null"); +} \ No newline at end of file diff --git a/100_core/src/gplx/Object__tst.java b/100_core/src/gplx/Object__tst.java new file mode 100644 index 000000000..22b51f1c7 --- /dev/null +++ b/100_core/src/gplx/Object__tst.java @@ -0,0 +1,34 @@ +/* +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; +import org.junit.*; +public class Object__tst { + @Before public void init() {} private Object__fxt fxt = new Object__fxt(); + @Test public void Eq() { + fxt.Test_eq(null, null, true); // both null + fxt.Test_eq(5, 5, true); // both non-null + fxt.Test_eq(5, null, false); // rhs non-null + fxt.Test_eq(null, 5, false); // lhs non-null + } + @Test public void Xto_str_loose_or_null() { + fxt.Test_xto_str_loose_or_null(null, null); + fxt.Test_xto_str_loose_or_null(2449.6000000000004d, "2449.6"); + } +} +class Object__fxt { + public void Test_eq(Object lhs, Object rhs, boolean expd) {Tfds.Eq(expd, Object_.Eq(lhs, rhs));} + public void Test_xto_str_loose_or_null(Object v, String expd) {Tfds.Eq(expd, Object_.Xto_str_loose_or(v, null));} +} diff --git a/100_core/src/gplx/Ordered_hash.java b/100_core/src/gplx/Ordered_hash.java new file mode 100644 index 000000000..b9007d4b4 --- /dev/null +++ b/100_core/src/gplx/Ordered_hash.java @@ -0,0 +1,29 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +import gplx.core.lists.*; /*EnumerAble,ComparerAble*/ +public interface Ordered_hash extends Hash_adp, List_adp__getable { + void Add_at(int i, Object o); + Ordered_hash Add_many_str(String... ary); + int Idx_of(Object item); + void Sort(); + void Sort_by(ComparerAble comparer); + void Resize_bounds(int i); + Object To_ary(Class t); + Object To_ary_and_clear(Class t); + void Move_to(int src, int trg); + void Lock(); +} diff --git a/100_core/src/gplx/Ordered_hash_.java b/100_core/src/gplx/Ordered_hash_.java new file mode 100644 index 000000000..6db3c5e33 --- /dev/null +++ b/100_core/src/gplx/Ordered_hash_.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; +import gplx.core.primitives.*; +public class Ordered_hash_ { + public static Ordered_hash New() {return new Ordered_hash_base();} + public static Ordered_hash New_bry() {return new Ordered_hash_bry();} +} +class Ordered_hash_bry extends Ordered_hash_base { + private final Bry_obj_ref tmp_ref = Bry_obj_ref.New_empty(); + @Override protected void Add_base(Object key, Object val) {super.Add_base(Bry_obj_ref.New((byte[])key), val);} + @Override protected void Del_base(Object key) {synchronized (tmp_ref) {super.Del_base(tmp_ref.Val_((byte[])key));}} + @Override protected boolean Has_base(Object key) {synchronized (tmp_ref) {return super.Has_base(tmp_ref.Val_((byte[])key));}} + @Override protected Object Fetch_base(Object key) {synchronized (tmp_ref) {return super.Fetch_base(tmp_ref.Val_((byte[])key));}} +} diff --git a/100_core/src/gplx/Ordered_hash_base.java b/100_core/src/gplx/Ordered_hash_base.java new file mode 100644 index 000000000..e22b0e82c --- /dev/null +++ b/100_core/src/gplx/Ordered_hash_base.java @@ -0,0 +1,101 @@ +/* +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; +import gplx.core.strings.*; import gplx.core.envs.*; +import gplx.core.lists.*; /*EnumerAble,ComparerAble*/ +public class Ordered_hash_base extends Hash_adp_base implements Ordered_hash, Gfo_invk { + private final List_adp ordered = List_adp_.New(); + @Override protected void Add_base(Object key, Object val) { + super.Add_base(key, val); + ordered.Add(val); + AssertCounts(); + } + @Override public void Del(Object key) { + if (!this.Has_base(key)) return; + Object val = this.Fetch_base(key); + this.Del_base(key); + ordered.Del(val); + AssertCounts(); + } + protected Object Get_at_base(int index) {return ordered.Get_at(index);} + protected int IndexOf_base(Object obj) {return ordered.Idx_of(obj);} + @Override public void Clear() { + if (locked) Lock_fail(); + super.Clear(); + ordered.Clear(); + } + public Object To_ary(Class type) {return ordered.To_ary(type);} + public Object To_ary_and_clear(Class t) { + Object rv = To_ary(t); + this.Clear(); + return rv; + } + @gplx.Virtual public void Sort() {if (locked) Lock_fail(); ordered.Sort();} // NOTE: uses item's .compareTo + public void Sort_by(ComparerAble comparer) {if (locked) Lock_fail(); ordered.Sort_by(comparer);} + @Override public java.util.Iterator iterator() {return ordered.iterator();} + public void Add_at(int i, Object key, Object val) { + if (locked) Lock_fail(); + super.Add_base(key, val); + ordered.Add_at(i, val); + AssertCounts(); + } + public Ordered_hash Add_many_str(String... ary) { + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) { + String itm = ary[i]; + byte[] bry = Bry_.new_u8(itm); + this.Add(bry, bry); + } + return this; + } + void AssertCounts() { + if (super.Count() != ordered.Count()) throw Err_.new_wo_type("counts do not match", "hash", super.Count(), "list", ordered.Count()); + } + public void Resize_bounds(int i) {if (locked) Lock_fail(); ordered.Resize_bounds(i);} + public void Lock() {locked = true;} private boolean locked = false; + void Lock_fail() {throw Err_.new_wo_type("collection is locked");} + static final String GRP_KEY = "gplx.core.lists.ordered_hash"; + public void Add_at(int i, Object o) {if (locked) Lock_fail(); ordered.Add_at(i, o);} + public Object Get_at(int i) {return Get_at_base(i);} + public int Idx_of(Object obj) {return this.IndexOf_base(obj);} + public void Move_to(int src, int trg) {if (locked) Lock_fail(); ordered.Move_to(src, trg);} + private String To_str_ui() { + String_bldr sb = String_bldr_.new_(); + int count = ordered.Count(); + int pad = String_.Len(Int_.To_str(count)); + for (int i = 0; i < count; i++) { + sb .Add(Int_.To_str_pad_bgn_zero(i, pad)) + .Add(":").Add(ordered.Get_at(i).toString()) + .Add(Op_sys.Cur().Nl_str()); + } + return sb.To_str(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_SetKeyOnly)) { + String s = m.ReadStr("v"); + if (ctx.Deny()) return this; + this.Add(s, s); + } + else if (ctx.Match(k, Invk_Print)) { + if (ctx.Deny()) return this; + return To_str_ui(); + } + else return Gfo_invk_.Rv_unhandled; + return this; + } static final String Invk_SetKeyOnly = "SetKeyOnly", Invk_Print = "Print"; + @Override public int Count() {return ordered.Count();} + public Ordered_hash_base() {} +} diff --git a/100_core/src/gplx/Ordered_hash_tst.java b/100_core/src/gplx/Ordered_hash_tst.java new file mode 100644 index 000000000..9865b8b9c --- /dev/null +++ b/100_core/src/gplx/Ordered_hash_tst.java @@ -0,0 +1,37 @@ +/* +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; +import org.junit.*; +public class Ordered_hash_tst { + @Before public void setup() { + hash = Ordered_hash_.New(); + } + @Test public void Get_at() { + hash.Add("key1", "val1"); + Tfds.Eq("val1", hash.Get_at(0)); + } + @Test public void iterator() { + hash.Add("key2", "val2"); + hash.Add("key1", "val1"); + + List_adp list = List_adp_.New(); + for (Object val : hash) + list.Add(val); + Tfds.Eq("val2", list.Get_at(0)); + Tfds.Eq("val1", list.Get_at(1)); + } + Ordered_hash hash; +} diff --git a/100_core/src/gplx/RandomAdp.java b/100_core/src/gplx/RandomAdp.java new file mode 100644 index 000000000..54ff1315f --- /dev/null +++ b/100_core/src/gplx/RandomAdp.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; +import java.util.*; +public class RandomAdp { + private final Random under; + public RandomAdp(Random v) {this.under = v;} + public int Next(int max) {return under.nextInt(max);} +} diff --git a/100_core/src/gplx/RandomAdp_.java b/100_core/src/gplx/RandomAdp_.java new file mode 100644 index 000000000..0da7e3772 --- /dev/null +++ b/100_core/src/gplx/RandomAdp_.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; +import java.util.*; +public class RandomAdp_ implements Gfo_invk { + public static RandomAdp new_() { + Random random = new Random(System.currentTimeMillis()); + return new RandomAdp(random); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_Next)) return RandomAdp_.new_().Next(m.ReadInt("max")); + else return Gfo_invk_.Rv_unhandled; + } static final String Invk_Next = "Next"; + public static final RandomAdp_ Gfs = new RandomAdp_(); +} diff --git a/100_core/src/gplx/Rls_able.java b/100_core/src/gplx/Rls_able.java new file mode 100644 index 000000000..3d68d915d --- /dev/null +++ b/100_core/src/gplx/Rls_able.java @@ -0,0 +1,19 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +public interface Rls_able { + void Rls(); +} diff --git a/100_core/src/gplx/Rls_able_.java b/100_core/src/gplx/Rls_able_.java new file mode 100644 index 000000000..d4ec74d98 --- /dev/null +++ b/100_core/src/gplx/Rls_able_.java @@ -0,0 +1,24 @@ +/* +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; +public class Rls_able_ { + public static Rls_able as_(Object obj) {return obj instanceof Rls_able ? (Rls_able)obj : null;} + public static Rls_able cast(Object obj) {try {return (Rls_able)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, Rls_able.class, obj);}} + public static final Rls_able Null = new Rls_able__noop(); +} +class Rls_able__noop implements Rls_able { + public void Rls() {} +} diff --git a/100_core/src/gplx/Short_.java b/100_core/src/gplx/Short_.java new file mode 100644 index 000000000..696f35525 --- /dev/null +++ b/100_core/src/gplx/Short_.java @@ -0,0 +1,23 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +public class Short_ { + public static final String Cls_val_name = "short"; + public static final Class Cls_ref_type = Short.class; + + public static short cast(Object obj) {try {return (Short)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, short.class, obj);}} + public static short By_int(int v) {return (short)v;} +} diff --git a/100_core/src/gplx/String_.java b/100_core/src/gplx/String_.java new file mode 100644 index 000000000..4983e7af1 --- /dev/null +++ b/100_core/src/gplx/String_.java @@ -0,0 +1,550 @@ +/* +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; +import java.lang.*; +import gplx.core.strings.*; import gplx.langs.gfs.*; import gplx.core.envs.*; +public class String_ { + public static int Len(String s) {return s.length();} + public static char CharAt(String s, int i) {return s.charAt(i);} + public static String new_u8(byte[] v, int bgn, int end) { + try { + return v == null + ? null + : new String(v, bgn, end - bgn, "UTF-8"); + } + catch (Exception e) {Err_.Noop(e); throw Err_.new_("core", "unsupported encoding", "bgn", bgn, "end", end);} + } + + public static final Class Cls_ref_type = String.class; + public static final String Cls_val_name = "str" + "ing"; + public static final int Find_none = -1, Pos_neg1 = -1; + public static final String Null = null, Empty = "", Null_mark = "<>", Tab = "\t", Lf = "\n", CrLf = "\r\n"; + public static String cast(Object v) {return (String)v;} + public static String as_(Object obj) {return obj instanceof String ? (String)obj : null;} + public static String new_a7(byte[] v) {return v == null ? null : new_a7(v, 0, v.length);} + public static String new_a7(byte[] v, int bgn, int end) { + try { + return v == null + ? null + : new String(v, bgn, end - bgn, "ASCII"); + } + catch (Exception e) {throw Err_.new_exc(e, "core", "unsupported encoding");} + } + public static String new_u8(byte[] v) {return v == null ? null : new_u8(v, 0, v.length);} + public static String new_u8__by_len(byte[] v, int bgn, int len) { + int v_len = v.length; + if (bgn + len > v_len) len = v_len - bgn; + return new_u8(v, bgn, bgn + len); + } + public static String[] Ary_add(String[]... arys) { + if (arys == null) return String_.Ary_empty; + int arys_len = arys.length; + int rv_len = 0; + for (int i = 0; i < arys_len; i++) { + String[] ary = arys[i]; + rv_len += ary.length; + } + int rv_idx = 0; + String[] rv = new String[rv_len]; + for (int i = 0; i < arys_len; i++) { + String[] ary = arys[i]; + int ary_len = ary.length; + for (int j = 0; j < ary_len; j++) + rv[rv_idx++] = ary[j]; + } + return rv; + } + public static boolean Len_gt_0(String s) {return s != null && s.length() > 0;} + public static boolean Len_eq_0(String s) {return s == null || s.length() == 0;} + public static String Lower(String s) {return s.toLowerCase();} + public static String Upper(String s) {return s.toUpperCase();} + public static String CaseNormalize(boolean caseSensitive, String s) {return caseSensitive ? s : String_.Lower(s);} + public static String Trim(String s) {return s.trim();} + public static String Mid(String s, int bgn) {return s.substring(bgn);} + public static String Replace(String s, String find, String replace) {return s.replace(find, replace);} + public static char[] XtoCharAry(String s) {return s.toCharArray();} + public static int CodePointAt(String s, int i) {return s.codePointAt(i);} + public static boolean Has(String s, String find) {return s.indexOf(find) != String_.Find_none;} + public static boolean Has_at_bgn(String s, String v) {return s.startsWith(v);} + public static boolean Has_at_end(String s, String v) {return s.endsWith(v);} + public static int FindFwd(String s, String find) {return s.indexOf(find);} + public static int FindFwd(String s, String find, int pos) {return s.indexOf(find, pos);} + public static int FindFwd(String s, String find, int bgn, int end) { + int rv = FindFwd(s, find, bgn); + return rv > end ? String_.Find_none : rv; + } + public static int FindBwd(String s, String find) {return s.lastIndexOf(find);} + public static int FindBwd(String s, String find, int pos) { + return s.lastIndexOf(find, pos); + } + public static int FindBetween(String s, String find, int bgn, int end) { + int rv = FindFwd(s, find, bgn); + return (rv > end) ? String_.Find_none : rv; + } + public static int FindAfter(String s, String find, int bgn) { + int rv = FindFwd(s, find, bgn); + return rv == String_.Find_none ? String_.Find_none : rv + Len(find); + } + public static int FindAfterRev(String s, String find, int pos) { + int rv = FindBwd(s, find, pos); + return rv == String_.Find_none ? String_.Find_none : rv + Len(find); + } + public static int Count(String s, String part) { + int count = 0, pos = -1; // -1 b/c first pass must be 0 (see pos + 1 below) + do { + pos = FindFwd(s, part, pos + 1); + if (pos == String_.Find_none) break; + count++; + } while (true); + return count; + } + public static boolean Eq(String lhs, String rhs) {return lhs == null ? rhs == null : lhs.equals(rhs);} + public static boolean EqAny(String lhs, String... rhsAry) { + for (int i = 0; i < rhsAry.length; i++) + if (Eq(lhs, rhsAry[i])) return true; + return false; + } + public static boolean EqNot(String lhs, String rhs) {return !Object_.Eq(lhs, rhs);} + public static boolean EqEmpty(String lhs) {return lhs.equals("");} + public static String IfNullOrEmpty(String s, String or) {return s == null || s.length() == 0 ? or : s;} + public static int Compare_as_ordinals(String lhs, String rhs) {return lhs.compareTo(rhs);} + public static int Compare_ignoreCase(String lhs, String rhs) { + if (lhs == null && rhs != null) return CompareAble_.Less; + else if (lhs != null && rhs == null) return CompareAble_.More; + else if (lhs == null && rhs == null) return CompareAble_.Same; + else return lhs.compareToIgnoreCase(rhs); + //#- + /* + if (lhs == null && rhs != null) return CompareAble_.Less; + else if (lhs != null && rhs == null) return CompareAble_.More; + else if (lhs == null && rhs == null) return CompareAble_.Same; + else return lhs.compareToIgnoreCase(rhs); + */ + } + public static int Compare(String lhs, String rhs) { + int compare = lhs.compareTo(rhs); + if (compare == CompareAble_.Same) return CompareAble_.Same; + else if (compare < CompareAble_.Same) return CompareAble_.Less; + else /* (compare > CompareAble_.Same) */ return CompareAble_.More; + } + public static int Compare_byteAry(String lhs, String rhs) { + int lhsLen = lhs.length(), rhsLen = rhs.length(); + int aryLen = lhsLen < rhsLen ? lhsLen : rhsLen; + int[] lhsAry = XtoIntAry(lhs, aryLen), rhsAry = XtoIntAry(rhs, aryLen); + for (int i = 0; i < aryLen; i++) { + int comp = Int_.Compare(lhsAry[i], rhsAry[i]); + if (comp != CompareAble_.Same) return comp; + } + return Int_.Compare(lhsLen, rhsLen); + } + public static int[] XtoIntAry(String s, int len) { + int[] rv = new int[len]; + for (int i = 0; i < len; i++) + rv[i] = (int)s.charAt(i); + return rv; + } + public static String Coalesce(String s, String alt) {return Len_eq_0(s) ? alt : s;} + public static boolean In(String s, String... ary) { + for (String itm : ary) + if (String_.Eq(s, itm)) return true; + return false; + } + + public static String new_charAry_(char[] ary, int bgn, int len) {return new String(ary, bgn, len);} + public static String Mid(String s, int bgn, int end) { + try {return Mid_lang(s, bgn, end - bgn);} + catch (Exception e) { + int len = s == null ? 0 : Len(s); + String msg = ""; + if (s == null) msg = "s is null"; + else if (bgn > end) msg = "@bgn > @end"; + else if (bgn < 0 || bgn >= len) msg = "@bgn is invalid"; + else if (end < 0 || end > len) msg = "@end is invalid"; + throw Err_.new_exc(e, "core", msg, "s", s, "bgn", bgn, "end", end, "len", len); + } + } + public static String MidByLenSafe(String s, int bgn, int len) { + if (bgn + len >= Len(s)) len = Len(s) - bgn; + return Mid_lang(s, bgn, len); + } + public static String MidByLen(String s, int bgn, int len) {return Mid_lang(s, bgn, len);} + public static String GetStrBefore(String s, String spr) { + int sprPos = String_.FindFwd(s, spr); if (sprPos == String_.Find_none) throw Err_.new_wo_type("could not find spr", "s", s, "spr", spr); + return Mid(s, 0, sprPos); + } + public static String GetStrAfter(String s, String spr) { + int sprPos = String_.FindFwd(s, spr); if (sprPos == String_.Find_none) throw Err_.new_wo_type("could not find spr", "s", s, "spr", spr); + return Mid(s, sprPos + 1); + } + public static String LimitToFirst(String s, int len) { + if (len < 0) throw Err_.new_invalid_arg("< 0", "len", len); + int sLen = Len(s); if (len > sLen) return s; + return Mid_lang(s, 0, len); + } + public static String LimitToLast(String s, int len) { + if (len < 0) throw Err_.new_invalid_arg("< 0", "len", len); + int sLen = Len(s); if (len > sLen) return s; + return Mid_lang(s, sLen - len, len); + } + public static String DelBgn(String s, int count) { + if (count < 0) throw Err_.new_invalid_arg("< 0", "count", count); + if (s == null) throw Err_.new_null(); + int len = Len(s); if (count > len) throw Err_.new_invalid_arg("> @len", "count", count, "len", len); + return String_.Mid(s, count); + } + public static String DelBgnIf(String s, String find) { + if (s == null) throw Err_.new_null(); + if (find == null) throw Err_.new_null(); + return Has_at_bgn(s, find) ? String_.Mid(s, Len(find)) : s; + } + public static String DelEnd(String s, int count) { + if (count < 0) throw Err_.new_invalid_arg("< 0", "count", count); + if (s == null) throw Err_.new_null(); + int len = Len(s); if (count > len) throw Err_.new_invalid_arg("> len", "count", count, "len", len); + return Mid_lang(s, 0, len + -count); + } + public static String DelEndIf(String s, String find) { + if (s == null) throw Err_.new_null(); + if (find == null) throw Err_.new_null(); + return Has_at_end(s, find) ? Mid_lang(s, 0, Len(s) - Len(find)) : s; + } + public static String LowerFirst(String s) { + int len = Len(s); if (len == 0) return String_.Empty; + String char0 = Lower(Mid_lang(s, 0, 1)); + return len == 1 ? char0 : char0 + Mid(s, 1); + } + public static String UpperFirst(String s) { + int len = Len(s); if (len == 0) return String_.Empty; + String char0 = Upper(Mid_lang(s, 0, 1)); + return len == 1 ? char0 : char0 + Mid(s, 1); + } + public static String PadBgn(String s, int totalLen, String pad) {return Pad(s, totalLen, pad, true);} + public static String PadEnd(String s, int totalLen, String pad) {return Pad(s, totalLen, pad, false);} + @gplx.Internal protected static String Pad(String s, int totalLen, String pad, boolean bgn) { + int sLen = Len(s); + int padLen = totalLen - sLen; if (padLen < 0) return s; + String_bldr sb = String_bldr_.new_(); + if (!bgn) sb.Add(s); + for (int i = 0; i < padLen; i++) + sb.Add(pad); + if (bgn) sb.Add(s); + return sb.To_str(); + } + public static String TrimEnd(String s) {if (s == null) return null; + int len = String_.Len(s); + if (len == 0) return s; + int last = len; + for (int i = len; i > 0; i--) { + char c = s.charAt(i - 1); + last = i; + if (c != ' ' && c != '\t' && c != '\r' && c != '\n') { + break; + } + } + return (last == len) ? s : Mid_lang(s, 0, last); + } + public static String Repeat(String s, int count) { + if (count < 0) throw Err_.new_wo_type("count cannot be negative", "count", count, "s", s); + String_bldr sb = String_bldr_.new_(); + for (int i = 0; i < count; i++) + sb.Add(s); + return sb.To_str(); + } + public static String Insert(String s, int pos, String toInsert) { + if (pos < 0 || pos >= String_.Len(s)) throw Err_.new_wo_type("String_.Insert failed; pos invalid", "pos", pos, "s", s, "toInsert", toInsert); + return s.substring(0, pos) + toInsert + s.substring(pos); + } + public static String Format(String fmt, Object... args) {return Format_do(fmt, args);} + public static String FormatOrEmptyStrIfNull(String fmt, Object arg) {return arg == null ? "" : Format(fmt, arg);} + public static String Concat(char... ary) {return new String(ary);} + public static String Concat(String s1, String s2, String s3) {return s1 + s2 + s3;} + public static String Concat(String... ary) { + String_bldr sb = String_bldr_.new_(); + for (String val : ary) + sb.Add(val); + return sb.To_str(); + } + public static String Concat_any(Object... ary) { + String_bldr sb = String_bldr_.new_(); + for (Object val : ary) + sb.Add_obj(val); + return sb.To_str(); + } + public static String Concat_with_obj(String separator, Object... ary) { + String_bldr sb = String_bldr_.new_(); + int aryLen = Array_.Len(ary); + for (int i = 0; i < aryLen; i++) { + if (i != 0) sb.Add(separator); + Object val = ary[i]; + sb.Add_obj(Object_.Xto_str_strict_or_empty(val)); + } + return sb.To_str(); + } + public static String Concat_with_str(String spr, String... ary) { + String_bldr sb = String_bldr_.new_(); + int len = ary.length; + for (int i = 0; i < len; i++) { + if (i != 0) sb.Add(spr); + sb.Add_obj(ary[i]); + } + return sb.To_str(); + } + public static String Concat_lines_crlf(String... values) { + String_bldr sb = String_bldr_.new_(); + for (String val : values) + sb.Add(val).Add(String_.CrLf); + return sb.To_str(); + } + public static String Concat_lines_crlf_skipLast(String... values) { + String_bldr sb = String_bldr_.new_(); + for (String val : values) { + if (sb.Count() != 0) sb.Add(String_.CrLf); + sb.Add(val); + } + return sb.To_str(); + } + public static String Concat_lines_nl(String... values) { + String_bldr sb = String_bldr_.new_(); + for (String val : values) + sb.Add(val).Add("\n"); + return sb.To_str(); + } + public static String Concat_lines_nl_apos_skip_last(String... lines) { + Bry_bfr bfr = Bry_bfr_.Get(); + try { + Bry_.New_u8_nl_apos(bfr, lines); + return bfr.To_str_and_clear(); + } + finally {bfr.Mkr_rls();} + } + public static String Concat_lines_nl_skip_last(String... ary) { + String_bldr sb = String_bldr_.new_(); + int ary_len = ary.length; int ary_end = ary_len - 1; + for (int i = 0; i < ary_len; i++) { + sb.Add(ary[i]); + if (i != ary_end) sb.Add("\n"); + } + return sb.To_str(); + } + + public static String[] Ary(String... ary) {return ary;} + public static String[] Ary_wo_null(String... ary) { + List_adp list = List_adp_.New(); + int len = ary.length; + for (int i = 0; i < len; ++i) { + String itm = ary[i]; + if (itm == null) continue; + list.Add(itm); + } + return list.To_str_ary(); + } + public static String AryXtoStr(String... ary) { + String_bldr sb = String_bldr_.new_(); + for (String s : ary) + sb.Add(s).Add(";"); + return sb.To_str(); + } + public static final String[] Ary_empty = new String[0]; + public static String[] Split(String raw, char dlm) {return Split(raw, dlm, false);} + public static String[] Split(String raw, char dlm, boolean addEmptyIfDlmIsLast) { + List_adp list = List_adp_.New(); String_bldr sb = String_bldr_.new_(); + int rawLen = String_.Len(raw); char c = '\0'; + for (int i = 0; i < rawLen; i++) { + c = String_.CharAt(raw, i); + if (c == dlm) { + if (!addEmptyIfDlmIsLast && sb.Count() == 0 && i == rawLen - 1) {} + else list.Add(sb.To_str_and_clear()); + } + else + sb.Add(c); + } + if (sb.Count() > 0) + list.Add(sb.To_str_and_clear()); + return list.To_str_ary(); + } + public static String[] Split(String s, String separator) {return Split_do(s, separator, false);} + public static String[] SplitLines_crlf(String s) {return Split(s, Op_sys.Wnt.Nl_str());} + public static String[] SplitLines_nl(String s) {return Split(s, Op_sys.Lnx.Nl_str());} + public static String[] SplitLines_any(String s) {return Split_do(s, Op_sys.Lnx.Nl_str(), true);} + public static String[] Split_lang(String s, char c) {return s.split(Character.toString(c));} + + static String Format_do(String s, Object[] ary) { + int aryLength = Array_.Len_obj(ary); if (aryLength == 0) return s; // nothing to format + String_bldr sb = String_bldr_.new_(); + char bracketBgn = '{', bracketEnd = '}'; + String aryVal = null; char c, next; + int pos = 0; int textLength = Len(s); String numberStr = ""; boolean bracketsOn = false; + while (true) { + if (pos == textLength) break; + c = CharAt(s, pos); + if (bracketsOn) { // mode=bracketsOn + if (c == bracketBgn) { // first bracketBgn is fake; add bracketBgn and whatever is in numberStr + sb.Add(bracketBgn).Add(numberStr); + numberStr = ""; + } + else if (c == bracketEnd) { + int aryIdx = Int_.Parse_or(numberStr, Int_.Min_value); + if (aryIdx != Int_.Min_value && Int_.Between(aryIdx, 0, aryLength - 1)) // check (a) aryIdx is num; (b) aryIdx is in bounds + aryVal = Object_.Xto_str_strict_or_empty(ary[aryIdx]); + else + aryVal = String_.Concat_any(bracketBgn, numberStr, bracketEnd); // not valid, just add String + sb.Add(aryVal); + bracketsOn = false; + numberStr = ""; + } + else // char=anythingElse + numberStr += c; + } + else { // mode=bracketsOff + if (c == bracketBgn || c == bracketEnd) { + boolean isEnd = pos == textLength - 1; + if (isEnd) + sb.Add(c); + else { + next = CharAt(s, pos + 1); + if (next == c) { // "{{" or "}}": escape by doubling + sb.Add(c); + pos++; + } + else + bracketsOn = true; + } + } + else // char=anythingElse + sb.Add(c); + } + pos++; + } + if (Len(numberStr) > 0) // unclosed bracket; add bracketBgn and whatever is in numberStr; ex: "{0" + sb.Add(bracketBgn).Add(numberStr); + return sb.To_str(); + } + static String[] Split_do(String s, String spr, boolean skipChar13) { + if (String_.Eq(s, "") // "".Split('a') return array with one member: "" + || String_.Eq(spr, "")) // "a".Split('\0') returns array with one member: "a" + return new String[] {s}; + List_adp list = List_adp_.New(); String_bldr sb = String_bldr_.new_(); + int i = 0, sprPos = 0; boolean sprMatched = false; char spr0 = CharAt(spr, 0); + int textLength = Len(s); int sprLength = Len(spr); + while (true) { + if (sprMatched + || i == textLength) { // last pass; add whatever's in sb to list + list.Add(sb.To_str_and_clear()); + if (sprMatched && i == textLength) list.Add(""); // if s ends with spr and last pass, add emptyString as last + sprMatched = false; + } + if (i == textLength) break; + char c = CharAt(s, i); + if (skipChar13 && c == (char)13) {i++; continue;} + if (c == spr0) { // matches first char of spr + sprPos = 1; + while (true) { + if (sprPos == sprLength) { // made it to end, must be match + sprMatched = true; + break; + } + if (i + sprPos == textLength) break; // ran out of s; handles partial match at end of String; ab+, +- + if (CharAt(s, i + sprPos) != CharAt(spr, sprPos)) break; // no match + sprPos++; + } + if (!sprMatched) // add partial match to sb + sb.Add(Mid_lang(s, i, sprPos)); + i += sprPos; + } + else { // no spr match; just add char, increment pos + sb.Add(c); + i++; + } + } + return (String[])list.To_ary(String.class); + } + static String Mid_lang(String s, int bgn, int len) {return s.substring(bgn, bgn + len);} + public static String Extract_after_bwd(String src, String dlm) { + int dlm_pos = String_.FindBwd(src, dlm); if (dlm_pos == String_.Find_none) return String_.Empty; + int src_len = String_.Len(src); if (dlm_pos == src_len - 1) return String_.Empty; + return String_.Mid(src, dlm_pos + 1, src_len); + } + public static String Replace_by_pos(String v, int del_bgn, int del_end, String repl) { + return String_.Mid(v, 0, del_bgn) + repl + String_.Mid(v, del_end, String_.Len(v)); + } + public static String read_(Object obj) {// NOTE: same as cast; for consistent readability only + String rv = as_(obj); + if (rv == null && obj != null) throw Err_.new_type_mismatch(String.class, obj); // NOTE: obj != null needed; EX: cast(null) --> null + return rv; + } + public static String[] Ary_parse(String raw, String dlm) {return String_.Split(raw, dlm);} + public static String[] Ary(byte[]... ary) { + if (ary == null) return String_.Ary_empty; + int ary_len = ary.length; + String[] rv = new String[ary_len]; + for (int i = 0; i < ary_len; i++) { + byte[] itm = ary[i]; + rv[i] = itm == null ? null : String_.new_u8(itm); + } + return rv; + } + public static String [] Ary_filter(String[] src, String[] filter) { + Hash_adp hash = Hash_adp_.New(); + int len = filter.length; + for (int i = 0; i < len; i++) { + String itm = filter[i]; + hash.Add_if_dupe_use_nth(itm, itm); + } + List_adp rv = List_adp_.New(); + len = src.length; + for (int i = 0; i < len; i++) { + String itm = src[i]; + if (hash.Has(itm)) rv.Add(itm); + } + return rv.To_str_ary(); + } + public static String[] Ary_flatten(String[][] src_ary) { + int trg_len = 0; + int src_len = Array_.Len(src_ary); + for (int i = 0; i < src_len; i++) { + String[] itm = src_ary[i]; + if (itm != null) trg_len += Array_.Len(itm); + } + String[] trg_ary = new String[trg_len]; + trg_len = 0; + for (int i = 0; i < src_len; i++) { + String[] itm = src_ary[i]; + if (itm == null) continue; + int itm_len = Array_.Len(itm); + for (int j = 0; j < itm_len; j++) + trg_ary[trg_len++] = itm[j]; + } + return trg_ary; + } + public static boolean Ary_eq(String[] lhs, String[] rhs) { + int lhs_len = lhs.length, rhs_len = rhs.length; + if (lhs_len != rhs_len) return false; + for (int i = 0; i < lhs_len; ++i) + if (!String_.Eq(lhs[i], rhs[i])) return false; + return true; + } + public static String To_str__as_kv_ary(String... ary) { + int len = ary.length; + Bry_bfr bfr = Bry_bfr_.New(); + for (int i = 0; i < len; i+=2) { + bfr.Add_str_u8(ary[i]).Add_byte_eq(); + String val = i + 1 < len ? ary[i + 1] : null; + if (val != null) bfr.Add_str_u8(val); + bfr.Add_byte_nl(); + } + return bfr.To_str_and_clear(); + } +} diff --git a/100_core/src/gplx/String__tst.java b/100_core/src/gplx/String__tst.java new file mode 100644 index 000000000..5fc7fa9e6 --- /dev/null +++ b/100_core/src/gplx/String__tst.java @@ -0,0 +1,182 @@ +/* +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; +import org.junit.*; +public class String__tst { + @Test public void Len() { + tst_Len("", 0); + tst_Len("abc", 3); + } void tst_Len(String v, int expd) {Tfds.Eq(expd, String_.Len(v), "Len");} + + @Test public void LimitToFirst() { + tst_LimitToFirst("abc", 0, ""); + tst_LimitToFirst("abc", 1, "a"); + tst_LimitToFirst("abc", 2, "ab"); + tst_LimitToFirst("abc", 3, "abc"); + tst_LimitToFirst("abc", 4, "abc"); + err_LimitToFirst("abc", -1); + } + void tst_LimitToFirst(String s, int v, String expd) {Tfds.Eq(expd, String_.LimitToFirst(s, v));} + void err_LimitToFirst(String s, int v) {try {String_.LimitToFirst(s, v);} catch (Exception exc) {Tfds.Err_classMatch(exc, Err.class); return;} Tfds.Fail_expdError();} + @Test public void LimitToLast() { + tst_LimitToLast("abc", 0, ""); + tst_LimitToLast("abc", 1, "c"); + tst_LimitToLast("abc", 2, "bc"); + tst_LimitToLast("abc", 3, "abc"); + tst_LimitToLast("abc", 4, "abc"); + err_LimitToLast("abc", -1); + } + void tst_LimitToLast(String s, int v, String expd) {Tfds.Eq(expd, String_.LimitToLast(s, v));} + void err_LimitToLast(String s, int v) {try {String_.LimitToLast(s, v);} catch (Exception exc) {Tfds.Err_classMatch(exc, Err.class); return;} Tfds.Fail_expdError();} + @Test public void DelBgn() { + tst_DelBgn("abc", 0, "abc"); + tst_DelBgn("abc", 1, "bc"); + tst_DelBgn("abc", 2, "c"); + tst_DelBgn("abc", 3, ""); + err_DelBgn(null, 0); + err_DelBgn("abc", 4); + } + void tst_DelBgn(String s, int v, String expd) {Tfds.Eq(expd, String_.DelBgn(s, v));} + void err_DelBgn(String s, int v) {try {String_.DelBgn(s, v);} catch (Exception exc) {Tfds.Err_classMatch(exc, Err.class); return;} Tfds.Fail_expdError();} + @Test public void DelBgnIf() { + tst_DelBgnIf("abc", "", "abc"); + tst_DelBgnIf("abc", "a", "bc"); + tst_DelBgnIf("abc", "ab", "c"); + tst_DelBgnIf("abc", "abc", ""); + tst_DelBgnIf("abc", "abcd", "abc"); + tst_DelBgnIf("abc", "bcd", "abc"); + err_DelBgnIf(null, "abc"); + err_DelBgnIf("abc", null); + } + void tst_DelBgnIf(String s, String v, String expd) {Tfds.Eq(expd, String_.DelBgnIf(s, v));} + void err_DelBgnIf(String s, String v) {try {String_.DelBgnIf(s, v);} catch (Exception exc) {Tfds.Err_classMatch(exc, Err.class); return;} Tfds.Fail_expdError();} + @Test public void DelEnd() { + tst_DelEnd("abc", 0, "abc"); + tst_DelEnd("abc", 1, "ab"); + tst_DelEnd("abc", 2, "a"); + tst_DelEnd("abc", 3, ""); + err_DelEnd(null, 0); + err_DelEnd("abc", 4); + } + void tst_DelEnd(String s, int v, String expd) {Tfds.Eq(expd, String_.DelEnd(s, v));} + void err_DelEnd(String s, int v) {try {String_.DelEnd(s, v);} catch (Exception exc) {Tfds.Err_classMatch(exc, Err.class); return;} Tfds.Fail_expdError();} + @Test public void DelEndIf() { + tst_DelEndIf("abc", "", "abc"); + tst_DelEndIf("abc", "c", "ab"); + tst_DelEndIf("abc", "bc", "a"); + tst_DelEndIf("abc", "abc", ""); + tst_DelEndIf("abc", "abcd", "abc"); + tst_DelEndIf("abc", "ab", "abc"); + err_DelEndIf(null, ""); + err_DelEndIf("", null); + } + void tst_DelEndIf(String s, String v, String expd) {Tfds.Eq(expd, String_.DelEndIf(s, v));} + void err_DelEndIf(String s, String v) {try {String_.DelEndIf(s, v);} catch (Exception exc) {Tfds.Err_classMatch(exc, Err.class); return;} Tfds.Fail_expdError();} + @Test public void MidByPos() { + tst_MidByPos("abc", 0, 0, ""); + tst_MidByPos("abc", 0, 1, "a"); + tst_MidByPos("abc", 0, 2, "ab"); + tst_MidByPos("abc", 0, 3, "abc"); + tst_MidByPos("abc", 2, 3, "c"); + err_MidByPos("abc", 1, 5); +// err_MidByPos("abc", 0, 4); + } + void tst_MidByPos(String s, int bgn, int end, String expd) {Tfds.Eq(expd, String_.Mid(s, bgn, end));} + void err_MidByPos(String s, int bgn, int end) {try {String_.Mid(s, bgn, end);} catch (Exception e) {Tfds.Err_classMatch(e, Err.class); return;} Tfds.Fail_expdError();} + @Test public void TrimEnd() { + tst_TrimEnd("a", "a"); + tst_TrimEnd("a ", "a"); + tst_TrimEnd("a\t", "a"); + tst_TrimEnd("a\n", "a"); + tst_TrimEnd("a\r", "a"); + tst_TrimEnd("a\r\n \t", "a"); + tst_TrimEnd(" a", " a"); + tst_TrimEnd(null, null); + } + void tst_TrimEnd(String s, String expd) {Tfds.Eq(expd, String_.TrimEnd(s));} + + @Test public void Count() { + String text = "0 0 0"; + Tfds.Eq(3, String_.Count(text, "0")); + } + @Test public void Has() { + String text = "find word"; + Tfds.Eq_true(String_.Has(text, "word")); + Tfds.Eq_false(String_.Has(text, "nothing")); + } + @Test public void Repeat() { + Tfds.Eq("333", String_.Repeat("3", 3)); + } + @Test public void Format() { + tst_Format("", ""); // empty + tst_Format("no args", "no args"); // no args + tst_Format("0", "{0}", 0); // one + tst_Format("0 and 1", "{0} and {1}", 0, 1); // many + tst_Format("{", "{{", 0); // escape bracketBgn + tst_Format("}", "}}", 0); // escape bracketEnd + tst_Format("{a0c}", "{a{0}c}", 0); // nested; + tst_Format("{a{b}c}", "{a{b}c}", 0); // invalid invalid + tst_Format("{1}", "{1}", 1); // invalid array index + tst_Format("{a} {b}", "{a} {b}", 0); // invalid many + tst_Format("{a}0{b}1", "{a}{0}{b}{1}", 0, 1); // invalid and valid + tst_Format("{0", "{0", 0); // invalid dangling + } void tst_Format(String expd, String fmt, Object... ary) {Tfds.Eq(expd, String_.Format(fmt, ary));} + @Test public void Split() { + tst_Split("ab", " ", "ab"); // no match -> return array with original input + tst_Split("ab cd", " ", "ab", "cd"); // separator.length = 1 + tst_Split("ab+!cd", "+!", "ab", "cd"); // separator.length = 2 + tst_Split("ab+!cd+!ef", "+!", "ab", "cd", "ef"); // terms = 3 + tst_Split("ab+!cd+!", "+!", "ab", "cd", ""); // closing separator + tst_Split("+!ab", "+!", "", "ab"); // opening separator + tst_Split("ab+cd+!ef", "+!", "ab+cd", "ef"); // ignore partial matches + tst_Split("ab+!cd+", "+!", "ab", "cd+"); // ignore partial matches; end of String + + // boundary + tst_Split("ab", "", "ab"); // separator.length = 0 -> return array with input as only member + tst_Split("", " ", ""); // empty input -> return array with empty input + + // acceptance + tst_Split("this\r\nis\na\rtest\r\n.", "\r\n", "this", "is\na\rtest", "."); + } void tst_Split(String text, String separator, String... expd) {Tfds.Eq_ary(expd, String_.Split(text, separator));} + @Test public void Concat_with_obj() { + tst_ConcatWith_any("a|b", "|", "a", "b"); // do not append final delimiter + tst_ConcatWith_any("a||c", "|", "a", null, "c"); // null + tst_ConcatWith_any("a|b", "|", Object_.Ary("a", "b")); // pass array as arg + } void tst_ConcatWith_any(String expd, String delimiter, Object... array) {Tfds.Eq(expd, String_.Concat_with_obj(delimiter, array));} + @Test public void Compare_byteAry() { + tst_Compare_byteAry("a", "a", CompareAble_.Same); + tst_Compare_byteAry("a", "b", CompareAble_.Less); + tst_Compare_byteAry("b", "a", CompareAble_.More); + tst_Compare_byteAry("ab", "ac", CompareAble_.Less); + tst_Compare_byteAry("ac", "ab", CompareAble_.More); + tst_Compare_byteAry("a", "ab", CompareAble_.Less); + tst_Compare_byteAry("ab", "a", CompareAble_.More); + tst_Compare_byteAry("101", "1-0-1", CompareAble_.More); // NOTE: regular String_.Compare_as_ordinals returns Less in .NET, More in Java + tst_Compare_byteAry("1-0-1", "101 (album)", CompareAble_.Less); + } void tst_Compare_byteAry(String lhs, String rhs, int expd) {Tfds.Eq(expd, String_.Compare_byteAry(lhs, rhs));} + @Test public void FindBwd() { // WORKAROUND.CS:String.LastIndexOf returns -1 for multi-chars; + tst_FindRev("abc", "a", 0, 0); + tst_FindRev("abc", "ab", 0, 0); // 2 chars + tst_FindRev("abc", "abc", 0, 0); // 3 chars + tst_FindRev("ab", "abc", 0, -1); // out of index error + tst_FindRev("ababab", "ab", 2, 2); // make sure cs implementation doesn't pick up next + } void tst_FindRev(String s, String find, int pos, int expd) {Tfds.Eq(expd, String_.FindBwd(s, find, pos));} + @Test public void Extract_after_bwd() { + Extract_after_bwd_tst("a/b", "/", "b"); + Extract_after_bwd_tst("a/", "/", ""); + Extract_after_bwd_tst("a", "/", ""); + } void Extract_after_bwd_tst(String src, String dlm, String expd) {Tfds.Eq(expd, String_.Extract_after_bwd(src, dlm));} +} diff --git a/100_core/src/gplx/Tfds.java b/100_core/src/gplx/Tfds.java new file mode 100644 index 000000000..668802cae --- /dev/null +++ b/100_core/src/gplx/Tfds.java @@ -0,0 +1,234 @@ +/* +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; +import gplx.core.strings.*; import gplx.core.consoles.*; import gplx.core.tests.*; +public class Tfds { // URL:doc/gplx.tfds/Tfds.txt + public static boolean SkipDb = false; + public static void Eq_bool (boolean expd , boolean actl) {Eq_exec_y(expd, actl, "", Object_.Ary_empty);} + public static void Eq_bool (boolean expd , boolean actl, String fmt, Object... args) {Eq_exec_y(expd, actl, fmt, args);} + public static void Eq_byte (byte expd , byte actl, String fmt, Object... args) {Eq_exec_y(expd, actl, fmt, args);} + public static void Eq_int (int expd , int actl) {Eq_exec_y(expd, actl, "", Object_.Ary_empty);} + public static void Eq_int (int expd , int actl, String fmt, Object... args) {Eq_exec_y(expd, actl, fmt, args);} + public static void Eq_double(double expd, double actl) {Eq_exec_y(expd, actl, "", Object_.Ary_empty);} + public static void Eq_str (byte[] expd, byte[] actl, String fmt, Object... args) {Eq_exec_y(String_.new_u8(expd), String_.new_u8(actl), fmt, args);} + public static void Eq_str (byte[] expd, String actl, String fmt, Object... args) {Eq_exec_y(String_.new_u8(expd), actl, fmt, args);} + public static void Eq_str (String expd, byte[] actl, String fmt, Object... args) {Eq_exec_y(expd, String_.new_u8(actl), fmt, args);} + public static void Eq_str (String expd, String actl) {Eq_exec_y(expd, actl, "", Object_.Ary_empty);} + public static void Eq_str (String expd, String actl, String fmt, Object... args) {Eq_exec_y(expd, actl, fmt, args);} + + public static void Eq(Object expd, Object actl) {Eq_wkr(expd, actl, true, EmptyStr);} + public static void Eq_byte(byte expd, byte actl) {Eq_wkr(expd, actl, true, EmptyStr);} + public static void Eq_long(long expd, long actl) {Eq_wkr(expd, actl, true, EmptyStr);} + public static void Eq_float(float expd, float actl) {Eq_wkr(expd, actl, true, EmptyStr);} + public static void Eq_decimal(Decimal_adp expd, Decimal_adp actl) {Eq_wkr(expd.To_double(), actl.To_double(), true, EmptyStr);} + public static void Eq_date(DateAdp expd, DateAdp actl) {Eq_wkr(expd.XtoStr_gplx(), actl.XtoStr_gplx(), true, EmptyStr);} + public static void Eq_date(DateAdp expd, DateAdp actl, String fmt, Object... args){Eq_wkr(expd.XtoStr_gplx(), actl.XtoStr_gplx(), true, String_.Format(fmt, args));} + public static void Eq_url(Io_url expd, Io_url actl) {Eq_wkr(expd.Raw(), actl.Raw(), true, EmptyStr);} + public static void Eq_str(String expd, byte[] actl) {Eq_wkr(expd, String_.new_u8(actl), true, EmptyStr);} + public static void Eq_bry(String expd, byte[] actl) {Eq_wkr(expd, String_.new_u8(actl), true, EmptyStr);} + public static void Eq_bry(byte[] expd, byte[] actl) {Eq_wkr(String_.new_u8(expd), String_.new_u8(actl), true, EmptyStr);} + public static void Eq_str_intf(To_str_able expd, To_str_able actl, String msg) {Eq_wkr(expd.To_str(), actl.To_str(), true, msg);} + public static void Eq_str_intf(To_str_able expd, To_str_able actl) {Eq_wkr(expd.To_str(), actl.To_str(), true, String_.Empty);} + public static void Eq_str_lines(String lhs, String rhs) {Eq_str_lines(lhs, rhs, EmptyStr);} + public static void Eq_str_lines(String lhs, String rhs, String note) { + if (lhs == null) lhs = ""; + if (rhs == null) rhs = ""; + Eq_ary_wkr(String_.Split(lhs, Char_.NewLine), String_.Split(rhs, Char_.NewLine), false, note); + } + public static void Eq(Object expd, Object actl, String fmt, Object... args) {Eq_wkr(expd, actl, true, String_.Format(fmt, args));} + public static void Eq_rev(Object actl, Object expd) {Eq_wkr(expd, actl, true, EmptyStr);} + public static void Eq_rev(Object actl, Object expd, String fmt, Object... args) {Eq_wkr(expd, actl, true, String_.Format(fmt, args));} + public static void Eq_true(Object actl) {Eq_wkr(true, actl, true, EmptyStr);} + public static void Eq_true(Object actl, String fmt, Object... args) {Eq_wkr(true, actl, true, String_.Format(fmt, args));} + public static void Eq_false(Object actl) {Eq_wkr(false, actl, true, EmptyStr);} + public static void Eq_false(Object actl, String fmt, Object... args) {Eq_wkr(false, actl, true, String_.Format(fmt, args));} + public static void Eq_null(Object actl) {Eq_wkr(null, actl, true, EmptyStr);} + public static void Eq_null(Object actl, String fmt, Object... args) {Eq_wkr(null, actl, true, String_.Format(fmt, args));} + public static void Eq_nullNot(Object actl) {Eq_wkr(null, actl, false, EmptyStr);} + public static void Eq_nullNot(Object actl, String fmt, Object... args) {Eq_wkr(null, actl, false, String_.Format(fmt, args));} + public static void Fail_expdError() {Eq_wkr(true, false, true, "fail expd error");} + public static void Fail(String fmt, Object... args) {Eq_wkr(true, false, true, String_.Format(fmt, args));} + public static void Eq_ary(Object lhs, Object rhs) {Eq_ary_wkr(lhs, rhs, true, EmptyStr);} + public static void Eq_ary(Object lhs, Object rhs, String fmt, Object... args){Eq_ary_wkr(lhs, rhs, true, String_.Format(fmt, args));} + public static void Eq_ary_str(Object lhs, Object rhs, String note) {Eq_ary_wkr(lhs, rhs, false, note);} + public static void Eq_ary_str(Object lhs, Object rhs) {Eq_ary_wkr(lhs, rhs, false, EmptyStr);} + public static void Eq_list(List_adp lhs, List_adp rhs) {Eq_list_wkr(lhs, rhs, TfdsEqListItmStr_cls_default.Instance, EmptyStr);} + public static void Eq_list(List_adp lhs, List_adp rhs, TfdsEqListItmStr xtoStr) {Eq_list_wkr(lhs, rhs, xtoStr, EmptyStr);} + private static void Eq_exec_y(Object lhs, Object rhs, String fmt, Object[] args) { + if (Object_.Eq(lhs, rhs)) return; + String msg = msgBldr.Eq_xtoStr(lhs, rhs, String_.Format(fmt, args)); + throw Err_.new_wo_type(msg); + } + static void Eq_wkr(Object lhs, Object rhs, boolean expd, String customMsg) { + boolean actl = Object_.Eq(lhs, rhs); + if (expd == actl) return; + String msg = msgBldr.Eq_xtoStr(lhs, rhs, customMsg); + throw Err_.new_wo_type(msg); + } + static void Eq_ary_wkr(Object lhsAry, Object rhsAry, boolean compareUsingEquals, String customMsg) { + List_adp list = List_adp_.New(); boolean pass = true; + int lhsLen = Array_.Len(lhsAry), rhsLen = Array_.Len(rhsAry); + for (int i = 0; i < lhsLen; i++) { + Object lhs = Array_.Get_at(lhsAry, i); + Object rhs = i >= rhsLen ? "<>" : Array_.Get_at(rhsAry, i); + String lhsString = msgBldr.Obj_xtoStr(lhs); String rhsString = msgBldr.Obj_xtoStr(rhs); // even if compareUsingEquals, method does ToStr on each itm for failMsg + boolean isEq = compareUsingEquals + ? Object_.Eq(lhs, rhs) + : Object_.Eq(lhsString, rhsString); + Eq_ary_wkr_addItm(list, i, isEq, lhsString, rhsString); + if (!isEq) pass = false; + } + for (int i = lhsLen; i < rhsLen; i++) { + String lhsString = "<>"; + String rhsString = msgBldr.Obj_xtoStr(Array_.Get_at(rhsAry, i)); + Eq_ary_wkr_addItm(list, i, false, lhsString, rhsString); + pass = false; + } + if (pass) return; + String msg = msgBldr.Eq_ary_xtoStr(list, lhsLen, rhsLen, customMsg); + throw Err_.new_wo_type(msg); + } + static void Eq_list_wkr(List_adp lhsList, List_adp rhsList, TfdsEqListItmStr xtoStr, String customMsg) { + List_adp list = List_adp_.New(); boolean pass = true; + int lhsLen = lhsList.Count(), rhsLen = rhsList.Count(); + for (int i = 0; i < lhsLen; i++) { + Object lhs = lhsList.Get_at(i); + Object rhs = i >= rhsLen ? null : rhsList.Get_at(i); + String lhsStr = xtoStr.To_str(lhs, lhs); + String rhsStr = rhs == null ? "<>" : xtoStr.To_str(rhs, lhs); + boolean isEq = Object_.Eq(lhsStr, rhsStr); if (!isEq) pass = false; + Eq_ary_wkr_addItm(list, i, isEq, lhsStr, rhsStr); + } + for (int i = lhsLen; i < rhsLen; i++) { + String lhsStr = "<>"; + Object rhs = rhsList.Get_at(i); + String rhsStr = xtoStr.To_str(rhs, null); + Eq_ary_wkr_addItm(list, i, false, lhsStr, rhsStr); + pass = false; + } + if (pass) return; + String msg = msgBldr.Eq_ary_xtoStr(list, lhsLen, rhsLen, customMsg); + throw Err_.new_wo_type(msg); + } + static void Eq_ary_wkr_addItm(List_adp list, int i, boolean isEq, String lhsString, String rhsString) { + TfdsEqAryItm itm = new TfdsEqAryItm().Idx_(i).Eq_(isEq).Lhs_(lhsString).Rhs_(rhsString); + list.Add(itm); + } + public static void Err_classMatch(Exception exc, Class type) { + boolean match = Type_.Eq_by_obj(exc, type); + if (!match) throw Err_.new_("Tfds", "error types do not match", "expdType", Type_.Canonical_name(type), "actlType", Type_.Name_by_obj(exc), "actlMsg", Err_.Message_lang(exc)); + } + public static void Eq_err(Err expd, Exception actlExc) { + Tfds.Eq(Err_.Message_gplx_full(expd), Err_.Message_gplx_full(actlExc)); + } + public static void Err_has(Exception e, String hdr) { + Tfds.Eq_true(String_.Has(Err_.Message_gplx_full(e), hdr), "could not find '{0}' in '{1}'", hdr, Err_.Message_gplx_full(e)); + } + static final String EmptyStr = TfdsMsgBldr.EmptyStr; + static TfdsMsgBldr msgBldr = TfdsMsgBldr.new_(); + public static final Io_url RscDir = Io_url_.Usr().GenSubDir_nest("000", "200_dev", "190_tst"); + public static void WriteText(String text) {Console_adp__sys.Instance.Write_str(text);} + public static void Write(byte[] s, int b, int e) {Write(Bry_.Mid(s, b, e));} + public static void Write() {Write("tmp");} + public static void Dbg(Object... ary) {Write(ary);} + public static void Write(Object... ary) { + String_bldr sb = String_bldr_.new_(); + int aryLen = Array_.Len(ary); + for (int i = 0; i < aryLen; i++) + sb.Add_many("'", Object_.Xto_str_strict_or_null_mark(ary[i]), "'", " "); + WriteText(sb.To_str() + String_.Lf); + } +} +class TfdsEqListItmStr_cls_default implements TfdsEqListItmStr { + public String To_str(Object cur, Object actl) { + return Object_.Xto_str_strict_or_null_mark(cur); + } + public static final TfdsEqListItmStr_cls_default Instance = new TfdsEqListItmStr_cls_default(); TfdsEqListItmStr_cls_default() {} +} +class TfdsEqAryItm { + public int Idx() {return idx;} public TfdsEqAryItm Idx_(int v) {idx = v; return this;} int idx; + public String Lhs() {return lhs;} public TfdsEqAryItm Lhs_(String v) {lhs = v; return this;} private String lhs; + public String Rhs() {return rhs;} public TfdsEqAryItm Rhs_(String v) {rhs = v; return this;} private String rhs; + public boolean Eq() {return eq;} public TfdsEqAryItm Eq_(boolean v) {eq = v; return this;} private boolean eq; +} +class TfdsMsgBldr { + public String Eq_xtoStr(Object expd, Object actl, String customMsg) { + String expdString = Obj_xtoStr(expd); String actlString = Obj_xtoStr(actl); + String detail = String_.Concat + ( CustomMsg_xtoStr(customMsg) + , "\t\t", "expd: ", expdString, String_.CrLf + , "\t\t", "actl: ", actlString, String_.CrLf + ); + return WrapMsg(detail); + } + public String Eq_ary_xtoStr(List_adp list, int lhsAryLen, int rhsAryLen, String customMsg) { + String_bldr sb = String_bldr_.new_(); + sb.Add(CustomMsg_xtoStr(customMsg)); + if (lhsAryLen != rhsAryLen) + sb.Add_fmt_line("{0}element counts differ: {1} {2}", "\t\t", lhsAryLen, rhsAryLen); + int lhsLenMax = 0, rhsLenMax = 0; + for (int i = 0; i < list.Count(); i++) { + TfdsEqAryItm itm = (TfdsEqAryItm)list.Get_at(i); + int lhsLen = String_.Len(itm.Lhs()), rhsLen = String_.Len(itm.Rhs()); + if (lhsLen > lhsLenMax) lhsLenMax = lhsLen; + if (rhsLen > rhsLenMax) rhsLenMax = rhsLen; + } + for (int i = 0; i < list.Count(); i++) { + TfdsEqAryItm itm = (TfdsEqAryItm)list.Get_at(i); + String eq_str = itm.Eq() ? "==" : ""; + if (!itm.Eq()) { +// if (lhsLenMax < 8 ) +// eq_str = "!="; +// else + eq_str = "\n!= "; + } + sb.Add_fmt_line("{0}: {1} {2} {3}" + , Int_.To_str_pad_bgn_zero(itm.Idx(), 4) + , itm.Lhs() // String_.PadBgn(itm.Lhs(), lhsLenMax, " ") + , eq_str + , itm.Rhs() // String_.PadBgn(itm.Rhs(), rhsLenMax, " ") + ); + } +// String compSym = isEq ? " " : "!="; +// String result = String_.Format("{0}: {1}{2} {3} {4}", Int_.To_str_pad_bgn_zero(i, 4), lhsString, String_.CrLf + "\t\t", compSym, rhsString); +// foreach (Object obj in list) { +// String itmComparison = (String)obj; +// sb.Add_fmt_line("{0}{1}", "\t\t", itmComparison); +// } + return WrapMsg(sb.To_str()); + } + String CustomMsg_xtoStr(String customMsg) { + return (customMsg == EmptyStr) + ? "" + : String_.Concat(customMsg, String_.CrLf); + } + public String Obj_xtoStr(Object obj) { + String s = String_.as_(obj); + if (s != null) return String_.Concat("'", s, "'"); // if Object is String, put quotes around it for legibility + To_str_able xtoStrAble = To_str_able_.as_(obj); + if (xtoStrAble != null) return xtoStrAble.To_str(); + return Object_.Xto_str_strict_or_null_mark(obj); + } + String WrapMsg(String text) { + return String_.Concat(String_.CrLf + , "************************************************************************************************", String_.CrLf + , text + , "________________________________________________________________________________________________" + ); + } + public static TfdsMsgBldr new_() {return new TfdsMsgBldr();} TfdsMsgBldr() {} + public static final String EmptyStr = ""; +} diff --git a/100_core/src/gplx/TfdsTstr_fxt.java b/100_core/src/gplx/TfdsTstr_fxt.java new file mode 100644 index 000000000..de991df69 --- /dev/null +++ b/100_core/src/gplx/TfdsTstr_fxt.java @@ -0,0 +1,97 @@ +/* +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; +import gplx.core.strings.*; +import gplx.core.lists.*; +public class TfdsTstr_fxt { + public TfdsTstr_fxt Eq_str(Object expd, Object actl, String name) { + int nameLen = String_.Len(name); if (nameLen > nameLenMax) nameLenMax = nameLen; + TfdsTstrItm itm = TfdsTstrItm.new_().Expd_(expd).Actl_(actl).Name_(name); + list.Add(itm); + return this; + } + public void SubName_push(String s) { + stack.Push(s); + TfdsTstrItm itm = TfdsTstrItm.new_(); + itm.SubName_make(stack); + itm.TypeOf = 1; + list.Add(itm); + } StackAdp stack = StackAdp_.new_(); + public void Fail() { + manualFail = true; + }boolean manualFail = false; + public int List_Max(List_adp expd, List_adp actl) {return Math_.Max(expd.Count(), actl.Count());} + public int List_Max(String[] expd, String[] actl) {return Math_.Max(expd.length, actl.length);} + public Object List_FetchAtOrNull(List_adp l, int i) {return (i >= l.Count()) ? null : l.Get_at(i);} + + public void SubName_pop() {stack.Pop();} + int nameLenMax = 0; + public void tst_Equal(String hdr) { + boolean pass = true; + for (int i = 0; i < list.Count(); i++) { + TfdsTstrItm itm = (TfdsTstrItm)list.Get_at(i); + if (!itm.Compare()) pass = false; // don't break early; Compare all vals + } + if (pass && !manualFail) return; + String_bldr sb = String_bldr_.new_(); + sb.Add_char_crlf(); + sb.Add_str_w_crlf(hdr); + for (int i = 0; i < list.Count(); i++) { + TfdsTstrItm itm = (TfdsTstrItm)list.Get_at(i); + if (itm.TypeOf == 1) { + sb.Add_fmt_line(" /{0}", itm.SubName()); + continue; + } + boolean hasError = itm.CompareResult() != TfdsTstrItm.CompareResult_eq; + String errorKey = hasError ? "*" : " "; + sb.Add_fmt_line("{0}{1} {2}", errorKey, String_.PadEnd(itm.Name(), nameLenMax, " "), itm.Expd()); + if (hasError) + sb.Add_fmt_line("{0}{1} {2}", errorKey, String_.PadEnd("", nameLenMax, " "), itm.Actl()); + } + sb.Add(String_.Repeat("_", 80)); + throw Err_.new_wo_type(sb.To_str()); + } + List_adp list = List_adp_.New(); + public static TfdsTstr_fxt new_() {return new TfdsTstr_fxt();} TfdsTstr_fxt() {} +} +class TfdsTstrItm { + public String Name() {return name;} public TfdsTstrItm Name_(String val) {name = val; return this;} private String name; + public Object Expd() {return expd;} public TfdsTstrItm Expd_(Object val) {expd = val; return this;} Object expd; + public Object Actl() {return actl;} public TfdsTstrItm Actl_(Object val) {actl = val; return this;} Object actl; + public String SubName() {return subName;} private String subName = ""; + public int TypeOf; + public void SubName_make(StackAdp stack) { + if (stack.Count() == 0) return; + List_adp list = stack.XtoList(); + String_bldr sb = String_bldr_.new_(); + for (int i = 0; i < list.Count(); i++) { + if (i != 0) sb.Add("."); + sb.Add((String)list.Get_at(i)); + } + subName = sb.To_str(); + } + public int CompareResult() {return compareResult;} public TfdsTstrItm CompareResult_(int val) {compareResult = val; return this;} int compareResult; + public boolean Compare() { + boolean eq = Object_.Eq(expd, actl); + compareResult = eq ? 1 : 0; + return eq; + } + public String CompareSym() { + return compareResult == 1 ? "==" : "!="; + } + public static TfdsTstrItm new_() {return new TfdsTstrItm();} TfdsTstrItm() {} + public static final int CompareResult_none = 0, CompareResult_eq = 1, CompareResult_eqn = 2; +} diff --git a/100_core/src/gplx/Time_span.java b/100_core/src/gplx/Time_span.java new file mode 100644 index 000000000..a9c037c0f --- /dev/null +++ b/100_core/src/gplx/Time_span.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; +import gplx.core.strings.*; +public class Time_span implements CompareAble { // NOTE: gplx.Time_span b/c System.TimeSpan + public long Fracs() {return fracs;} long fracs; public int FracsAsInt() {return (int)fracs;} + public Decimal_adp Total_days() {return Decimal_adp_.divide_(fracs, Time_span_.Divisors[Time_span_.Idx_Hour] * 24);} + public Decimal_adp Total_hours() {return Decimal_adp_.divide_(fracs, Time_span_.Divisors[Time_span_.Idx_Hour]);} + public Decimal_adp Total_mins() {return Decimal_adp_.divide_(fracs, Time_span_.Divisors[Time_span_.Idx_Min]);} + public Decimal_adp Total_secs() {return Decimal_adp_.divide_(fracs, Time_span_.Divisors[Time_span_.Idx_Sec]);} + public int[] Units() {return Time_span_.Split_long(fracs, Time_span_.Divisors);} + public int Units_fracs() { + int[] ary = Time_span_.Split_long(fracs, Time_span_.Divisors); + return ary[Time_span_.Idx_Frac]; + } + public Time_span Add(Time_span val) {return new Time_span(fracs + val.fracs);} + public Time_span Add_fracs(long val) {return new Time_span(fracs + val);} + public Time_span Add_unit(int idx, int val) { + int[] units = Time_span_.Split_long(fracs, Time_span_.Divisors); + units[idx] += val; + int sign = fracs >= 0 ? 1 : -1; + long rv = sign * Time_span_.Merge_long(units, Time_span_.Divisors); + return Time_span_.fracs_(rv); + } + public Time_span Subtract(Time_span val) {return new Time_span(fracs - val.fracs);} + + public int compareTo(Object obj) {Time_span comp = Time_span_.cast(obj); return CompareAble_.Compare_obj(fracs, comp.fracs);} + public boolean Eq(Object o) { + Time_span comp = Time_span_.cast(o); if (comp == null) return false; + return fracs == comp.fracs; + } + @Override public String toString() {return To_str(Time_span_.Fmt_Default);} + @Override public boolean equals(Object obj) {Time_span comp = Time_span_.cast(obj); return Object_.Eq(fracs, comp.fracs);} + @Override public int hashCode() {return super.hashCode();} + + public String To_str() {return Time_span_.To_str(fracs, Time_span_.Fmt_Default);} + public String To_str(String format) { + return Time_span_.To_str(fracs, format); + } + public String XtoStrUiAbbrv() { + if (fracs == 0) return "0" + UnitAbbrv(0); + int[] units = Units(); + boolean started = false; + String_bldr sb = String_bldr_.new_(); + for (int i = units.length - 1; i > -1; i--) { + int unit = units[i]; + if (!started) { + if (unit == 0) + continue; + else + started = true; + } + if (sb.Count() != 0) sb.Add(" "); + sb.Add_obj(unit).Add(UnitAbbrv(i)); + } + return sb.To_str(); + } + String UnitAbbrv(int i) { + switch (i) { + case 0: return "f"; + case 1: return "s"; + case 2: return "m"; + case 3: return "h"; + default: return "unknown:<" + Int_.To_str(i) + ">"; + } + } + @gplx.Internal protected Time_span(long fracs) {this.fracs = fracs;} +} diff --git a/100_core/src/gplx/Time_span_.java b/100_core/src/gplx/Time_span_.java new file mode 100644 index 000000000..5119854d6 --- /dev/null +++ b/100_core/src/gplx/Time_span_.java @@ -0,0 +1,161 @@ +/* +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; +import gplx.core.strings.*; import gplx.core.envs.*; +public class Time_span_ { + public static final Time_span Zero = new Time_span(0); + public static final Time_span Null = new Time_span(-1); + public static Time_span fracs_(long val) {return new Time_span(val);} + public static Time_span seconds_(double seconds) { + long fracs = (long)(seconds * Divisors[Idx_Sec]); + return new Time_span(fracs); + } + public static Time_span decimal_(Decimal_adp seconds) { + return new Time_span(seconds.To_long_mult_1000()); + } + public static Time_span units_(int frc, int sec, int min, int hour) { + int[] units = new int[] {frc, sec, min, hour}; + long fracs = Merge_long(units, Time_span_.Divisors); + return Time_span_.fracs_(fracs); + } + public static Time_span from_(long bgn) {return Time_span_.fracs_(System_.Ticks() - bgn);} + public static final long parse_null = Long_.Min_value; + public static Time_span parse(String raw) { + byte[] bry = Bry_.new_u8(raw); + long fracs = parse_to_fracs(bry, 0, bry.length, false); + return fracs == parse_null ? null : Time_span_.fracs_(fracs); + } + public static long parse_to_fracs(byte[] raw, int bgn, int end, boolean fail_if_ws) { + int sign = 1, val_f = 0, val_s = 0, val_m = 0, val_h = 0, colon_pos = 0, unit_val = 0, unit_multiple = 1; + for (int i = end - 1; i >= bgn; i--) { // start from end; fracs should be lowest unit + byte b = raw[i]; + switch (b) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + int unit_digit = Byte_ascii.To_a7_int(b); + unit_val = (unit_multiple == 1) ? unit_digit : unit_val + (unit_digit * unit_multiple); + switch (colon_pos) { + case 0: val_s = unit_val; break; + case 1: val_m = unit_val; break; + case 2: val_h = unit_val; break; + default: return parse_null; // only hour:minute:second supported for ':' separator; ':' count should be <= 2 + } + unit_multiple *= 10; + break; + case Byte_ascii.Dot: + double factor = (double)1000 / (double)unit_multiple; // factor is necessary to handle non-standard decimals; ex: .1 -> 100; .00199 -> .001 + val_f = (int)((double)val_s * factor); // move val_s unit_val to val_f; logic is indirect, b/c of differing inputs: "123" means 123 seconds; ".123" means 123 fractionals + val_s = 0; + unit_multiple = 1; + break; + case Byte_ascii.Colon: + colon_pos++; + unit_multiple = 1; + break; + case Byte_ascii.Dash: + if (i == 0 && unit_val > 0) // only if first char && unit_val > 0 + sign = -1; + break; + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: + if (fail_if_ws) return parse_null; + break; + default: + return parse_null; // invalid char; return null; + } + } + return sign * (val_f + (val_s * Divisors[1]) + (val_m * Divisors[2]) + (val_h * Divisors[3])); + } + public static String To_str(long frc, String fmt) { + String_bldr sb = String_bldr_.new_(); + int[] units = Split_long(frc, Divisors); + + if (String_.Eq(fmt, Time_span_.Fmt_Short)) { + for (int i = Idx_Hour; i > -1; i--) { + int val = units[i]; + if (val == 0 && i == Idx_Hour) continue; // skip hour if 0; ex: 01:02, instead of 00:01:02 + if (i == Idx_Frac) continue; // skip frac b/c fmt is short + if (sb.Count() > 0) // sb already has unit; add delimiter + sb.Add(Sprs[i]); + if (val < 10) // zeroPad + sb.Add("0"); + sb.Add(Int_.To_str(val)); + } + return sb.To_str_and_clear(); + } + boolean fmt_fracs = !String_.Eq(fmt, Time_span_.Fmt_NoFractionals); + boolean fmt_padZeros = String_.Eq(fmt, Time_span_.Fmt_PadZeros); + if (frc == 0) return fmt_padZeros ? "00:00:00.000" : "0"; + + int[] padZerosAry = ZeroPadding; + boolean first = true; + String dlm = ""; + int zeros = 0; + if (frc < 0) sb.Add("-"); // negative sign + for (int i = Idx_Hour; i > -1; i--) { // NOTE: "> Idx_Frac" b/c frc will be handled below + int val = units[i]; + if (i == Idx_Frac // only write fracs... + && !(val == 0 && fmt_padZeros) // ... if val == 0 && fmt is PadZeros + && !(val != 0 && fmt_fracs) // ... or val != 0 && fmt is PadZeros or Default + ) continue; + if (first && val == 0 && !fmt_padZeros) continue; // if first and val == 0, don't full pad (avoid "00:") + zeros = first && !fmt_padZeros ? 1 : padZerosAry[i]; // if first, don't zero pad (avoid "01") + dlm = first ? "" : Sprs[i]; // if first, don't use dlm (avoid ":01") + sb.Add(dlm); + sb.Add(Int_.To_str_pad_bgn_zero(val, zeros)); + first = false; + } + return sb.To_str(); + } + @gplx.Internal protected static int[] Split_long(long fracs, int[] divisors) { + int divLength = Array_.Len(divisors); + int[] rv = new int[divLength]; + long cur = Math_.Abs(fracs); + for (int i = divLength - 1; i > -1; i--) { + int divisor = divisors[i]; + long factor = cur / divisor; + rv[i] = (int)factor; + cur -= (factor * divisor); + } + return rv; + } + @gplx.Internal protected static long Merge_long(int[] vals, int[] divisors) { + long rv = 0; int valLength = Array_.Len(vals); + for (int i = 0; i < valLength; i++) { + rv += vals[i] * divisors[i]; + } + return rv; + } + public static final String Fmt_PadZeros = "00:00:00.000"; // u,h00:m00:s00.f000 + public static final String Fmt_Short = "short"; // u,h##:m#0:s00; + public static final String Fmt_Default = "0.000"; // v,#.000 + public static final String Fmt_NoFractionals = "0"; // v,# + @gplx.Internal protected static final int[] Divisors = { + 1, //1 fracs + 1000, //1,000 fracs in a second + 60000, //60,000 fracs in a minute (60 seconds * 1,000) + 3600000, //3,600,000 fracs in an hour (60 minutes * 60,000) + }; + public static final String MajorDelimiter = ":"; + public static final int + Idx_Frac = 0 + , Idx_Sec = 1 + , Idx_Min = 2 + , Idx_Hour = 3; + static int[] ZeroPadding = {3, 2, 2, 2,}; + static String[] Sprs = {".", MajorDelimiter, MajorDelimiter, "",}; + public static Time_span cast(Object arg) {try {return (Time_span)arg;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, Time_span.class, arg);}} + public static final double Ratio_f_to_s = 1000; +} diff --git a/100_core/src/gplx/To_str_able.java b/100_core/src/gplx/To_str_able.java new file mode 100644 index 000000000..0ea50ef6d --- /dev/null +++ b/100_core/src/gplx/To_str_able.java @@ -0,0 +1,19 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +public interface To_str_able {//RF:2017-10-08 + String To_str(); +} diff --git a/100_core/src/gplx/To_str_able_.java b/100_core/src/gplx/To_str_able_.java new file mode 100644 index 000000000..b6fe84673 --- /dev/null +++ b/100_core/src/gplx/To_str_able_.java @@ -0,0 +1,19 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +class To_str_able_ {//RF:2017-10-08 + public static To_str_able as_(Object obj) {return obj instanceof To_str_able ? (To_str_able)obj : null;} +} diff --git a/100_core/src/gplx/Type_.java b/100_core/src/gplx/Type_.java new file mode 100644 index 000000000..47ebce803 --- /dev/null +++ b/100_core/src/gplx/Type_.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; +public class Type_ {//RF:2017-10-08 + public static Class Type_by_obj(Object o) {return o.getClass();} + public static Class Type_by_primitive(Object o) { + Class rv = o.getClass(); + if (rv == Integer.class) rv = int.class; + else if (rv == Long.class) rv = long.class; + else if (rv == Byte.class) rv = byte.class; + else if (rv == Short.class) rv = short.class; + return rv; + } + + public static boolean Eq_by_obj(Object lhs_obj, Class rhs_type) { + Class lhs_type = lhs_obj == null ? null : lhs_obj.getClass(); + return Type_.Eq(lhs_type, rhs_type); + } + public static boolean Eq(Class lhs, Class rhs) {// DUPE_FOR_TRACKING: same as Object_.Eq + if (lhs == null && rhs == null) return true; + else if (lhs == null || rhs == null) return false; + else return lhs.equals(rhs); + } + + public static String Canonical_name_by_obj(Object o) {return Canonical_name(o.getClass());} + public static String Canonical_name(Class type) { + return type.getCanonicalName(); + } + + public static String Name_by_obj(Object obj) {return obj == null ? String_.Null_mark : Name(Type_by_obj(obj));} + public static String Name(Class type) { + return type.getName(); + } + + public static boolean Is_array(Class t) { + return t.isArray(); + } + + public static boolean Is_assignable_from_by_obj(Object o, Class generic) {return o == null ? false : Is_assignable_from(generic, o.getClass());} + public static boolean Is_assignable_from(Class generic, Class specific) { + return generic.isAssignableFrom(specific); + } +} diff --git a/100_core/src/gplx/Type_ids_.java b/100_core/src/gplx/Type_ids_.java new file mode 100644 index 000000000..183c0e677 --- /dev/null +++ b/100_core/src/gplx/Type_ids_.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; +public class Type_ids_ {//RF:2017-10-08 + public static final int // SERIALIZABLE.N + Id__obj = 0 + , Id__null = 1 + , Id__bool = 2 + , Id__byte = 3 + , Id__short = 4 + , Id__int = 5 + , Id__long = 6 + , Id__float = 7 + , Id__double = 8 + , Id__char = 9 + , Id__str = 10 + , Id__bry = 11 + , Id__date = 12 + , Id__decimal = 13 + ; + + public static int To_id_by_obj(Object o) { + if (o == null) return Type_ids_.Id__null; + Class type = o.getClass(); + return Type_ids_.To_id_by_type(type); + } + + public static int To_id_by_type(Class type) { + if (Type_.Eq(type, Int_.Cls_ref_type)) return Id__int; + else if (Type_.Eq(type, String_.Cls_ref_type)) return Id__str; + else if (Type_.Eq(type, byte[].class)) return Id__bry; + else if (Type_.Eq(type, Bool_.Cls_ref_type)) return Id__bool; + else if (Type_.Eq(type, Byte_.Cls_ref_type)) return Id__byte; + else if (Type_.Eq(type, Long_.Cls_ref_type)) return Id__long; + else if (Type_.Eq(type, Double_.Cls_ref_type)) return Id__double; + else if (Type_.Eq(type, Decimal_adp_.Cls_ref_type)) return Id__decimal; + else if (Type_.Eq(type, DateAdp_.Cls_ref_type)) return Id__date; + else if (Type_.Eq(type, Float_.Cls_ref_type)) return Id__float; + else if (Type_.Eq(type, Short_.Cls_ref_type)) return Id__short; + else if (Type_.Eq(type, Char_.Cls_ref_type)) return Id__char; + else return Id__obj; + } +} diff --git a/100_core/src/gplx/UsrDlg.java b/100_core/src/gplx/UsrDlg.java new file mode 100644 index 000000000..d20545d61 --- /dev/null +++ b/100_core/src/gplx/UsrDlg.java @@ -0,0 +1,49 @@ +/* +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; +public class UsrDlg { + public int Verbosity() {return verbosity;} public UsrDlg Verbosity_(int v) {verbosity = v; return this;} int verbosity = UsrMsgWkr_.Type_Note; + public void Note(String text, Object... ary) {Exec(text, ary, noteWkrs);} + public void Warn(String text, Object... ary) {Exec(text, ary, warnWkrs);} + public void Stop(String text, Object... ary) {Exec(text, ary, stopWkrs);} + public void Note(UsrMsg msg) {Exec(UsrMsgWkr_.Type_Note, msg);} + public void Warn(UsrMsg msg) {Exec(UsrMsgWkr_.Type_Warn, msg);} + public void Stop(UsrMsg msg) {Exec(UsrMsgWkr_.Type_Stop, msg);} + public void Exec(int type, UsrMsg umsg) { + UsrMsgWkrList list = GetList(type); + list.Exec(umsg); + } + void Exec(String text, Object[] ary, UsrMsgWkrList list) { + String msg = String_.Format(text, ary); + list.Exec(UsrMsg.new_(msg)); + } + public void Reg(int type, UsrMsgWkr wkr) { + UsrMsgWkrList list = GetList(type); + list.Add(wkr); + } + public void RegOff(int type, UsrMsgWkr wkr) { + UsrMsgWkrList list = GetList(type); + list.Del(wkr); + } + UsrMsgWkrList GetList(int type) { + if (type == UsrMsgWkr_.Type_Note) return noteWkrs; + else if (type == UsrMsgWkr_.Type_Warn) return warnWkrs; + else if (type == UsrMsgWkr_.Type_Stop) return stopWkrs; + else throw Err_.new_unhandled(type); + } + UsrMsgWkrList noteWkrs = new UsrMsgWkrList(UsrMsgWkr_.Type_Note), warnWkrs = new UsrMsgWkrList(UsrMsgWkr_.Type_Warn), stopWkrs = new UsrMsgWkrList(UsrMsgWkr_.Type_Stop); + public static UsrDlg new_() {return new UsrDlg();} +} diff --git a/100_core/src/gplx/UsrDlg_.java b/100_core/src/gplx/UsrDlg_.java new file mode 100644 index 000000000..5e1f28411 --- /dev/null +++ b/100_core/src/gplx/UsrDlg_.java @@ -0,0 +1,19 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx; +public class UsrDlg_ { + public static final UsrDlg Instance = UsrDlg.new_(); +} diff --git a/100_core/src/gplx/UsrMsg.java b/100_core/src/gplx/UsrMsg.java new file mode 100644 index 000000000..08fa021e9 --- /dev/null +++ b/100_core/src/gplx/UsrMsg.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 +*/ +package gplx; +import gplx.core.strings.*; import gplx.core.envs.*; +public class UsrMsg { + public int VisibilityDuration() {return visibilityDuration;} public UsrMsg VisibilityDuration_(int v) {visibilityDuration = v; return this;} int visibilityDuration = 3000; + public String Hdr() {return hdr;} public UsrMsg Hdr_(String val) {hdr = val; return this;} private String hdr; + public Ordered_hash Args() {return args;} Ordered_hash args = Ordered_hash_.New(); + public UsrMsg Add(String k, Object v) { + args.Add(k, Keyval_.new_(k, v)); + return this; + } + public UsrMsg Add_if_dupe_use_nth(String k, Object v) { + args.Add_if_dupe_use_nth(k, Keyval_.new_(k, v)); + return this; + } + public String XtoStrSingleLine() {return To_str(" ");} + public String To_str() {return To_str(Op_sys.Cur().Nl_str());} + String To_str(String spr) { + if (hdr == null) { + GfoMsg m = GfoMsg_.new_cast_(cmd); + for (int i = 0; i < args.Count(); i++) { + Keyval kv = (Keyval)args.Get_at(i); + m.Add(kv.Key(), kv.Val()); + } + return Object_.Xto_str_strict_or_null_mark(invk.Invk(GfsCtx.Instance, 0, cmd, m)); + } + String_bldr sb = String_bldr_.new_(); + sb.Add(hdr).Add(spr); + for (int i = 0; i < args.Count(); i++) { + Keyval kv = (Keyval)args.Get_at(i); + sb.Add_spr_unless_first("", " ", i); + sb.Add_fmt("{0}={1}", kv.Key(), kv.Val(), spr); + } + return sb.To_str(); + } + public static UsrMsg fmt_(String hdr, Object... ary) { + UsrMsg rv = new UsrMsg(); + rv.hdr = String_.Format(hdr, ary); + return rv; + } UsrMsg() {} + public static UsrMsg new_(String hdr) { + UsrMsg rv = new UsrMsg(); + rv.hdr = hdr; + return rv; + } + public static UsrMsg invk_(Gfo_invk invk, String cmd) { + UsrMsg rv = new UsrMsg(); + rv.invk = invk; + rv.cmd = cmd; + return rv; + } Gfo_invk invk; String cmd; +} diff --git a/100_core/src/gplx/UsrMsgWkr.java b/100_core/src/gplx/UsrMsgWkr.java new file mode 100644 index 000000000..cdc4e0ab8 --- /dev/null +++ b/100_core/src/gplx/UsrMsgWkr.java @@ -0,0 +1,48 @@ +/* +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; +public interface UsrMsgWkr { + void ExecUsrMsg(int type, UsrMsg umsg); +} +class UsrMsgWkrList { + public void Add(UsrMsgWkr v) { + if (wkr == null && list == null) + wkr = v; + else { + if (list == null) { + list = List_adp_.New(); + list.Add(wkr); + wkr = null; + } + list.Add(v); + } + } + public void Del(UsrMsgWkr v) { +// list.Del(v); + } + public void Exec(UsrMsg umsg) { + if (wkr != null) + wkr.ExecUsrMsg(type, umsg); + else if (list != null) { + for (Object lObj : list) { + UsrMsgWkr l = (UsrMsgWkr)lObj; + l.ExecUsrMsg(type, umsg); + } + } + } + List_adp list; UsrMsgWkr wkr; int type; + public UsrMsgWkrList(int type) {this.type = type;} +} diff --git a/100_core/src/gplx/UsrMsgWkr_.java b/100_core/src/gplx/UsrMsgWkr_.java new file mode 100644 index 000000000..2aadf3cbb --- /dev/null +++ b/100_core/src/gplx/UsrMsgWkr_.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; +public class UsrMsgWkr_ { + public static final int + Type_None = 0 + , Type_Stop = 1 + , Type_Warn = 2 + , Type_Note = 4 + , Type_Log = 8 + ; +} diff --git a/100_core/src/gplx/UsrMsgWkr_console.java b/100_core/src/gplx/UsrMsgWkr_console.java new file mode 100644 index 000000000..8ebe1790a --- /dev/null +++ b/100_core/src/gplx/UsrMsgWkr_console.java @@ -0,0 +1,33 @@ +/* +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; +import gplx.core.consoles.*; +public class UsrMsgWkr_console implements UsrMsgWkr { + public void ExecUsrMsg(int type, UsrMsg umsg) { + String text = umsg.To_str(); + if (type == UsrMsgWkr_.Type_Warn) + text = "!!!!" + text; + else if (type == UsrMsgWkr_.Type_Stop) + text = "****" + text; + Console_adp__sys.Instance.Write_str(text); + } + public static void RegAll(UsrDlg dlg) { + UsrMsgWkr wkr = new UsrMsgWkr_console(); + dlg.Reg(UsrMsgWkr_.Type_Note, wkr); + dlg.Reg(UsrMsgWkr_.Type_Stop, wkr); + dlg.Reg(UsrMsgWkr_.Type_Warn, wkr); + } +} diff --git a/100_core/src/gplx/UsrMsgWkr_test.java b/100_core/src/gplx/UsrMsgWkr_test.java new file mode 100644 index 000000000..a39c74701 --- /dev/null +++ b/100_core/src/gplx/UsrMsgWkr_test.java @@ -0,0 +1,36 @@ +/* +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; +public class UsrMsgWkr_test implements UsrMsgWkr { + public void ExecUsrMsg(int type, UsrMsg m) { + msgs.Add(m); + } + public boolean HasWarn(UsrMsg um) { + for (int i = 0; i < msgs.Count(); i++) { + UsrMsg found = (UsrMsg)msgs.Get_at(i); + if (String_.Eq(um.To_str(), found.To_str())) return true; + } + return false; + } + public static UsrMsgWkr_test RegAll(UsrDlg dlg) { + UsrMsgWkr_test wkr = new UsrMsgWkr_test(); + dlg.Reg(UsrMsgWkr_.Type_Note, wkr); + dlg.Reg(UsrMsgWkr_.Type_Stop, wkr); + dlg.Reg(UsrMsgWkr_.Type_Warn, wkr); + return wkr; + } + List_adp msgs = List_adp_.New(); +} diff --git a/100_core/src/gplx/Virtual.java b/100_core/src/gplx/Virtual.java new file mode 100644 index 000000000..154b3c2d5 --- /dev/null +++ b/100_core/src/gplx/Virtual.java @@ -0,0 +1,17 @@ +/* +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; +public @interface Virtual {} diff --git a/100_core/src/gplx/Yn.java b/100_core/src/gplx/Yn.java new file mode 100644 index 000000000..e5baab41c --- /dev/null +++ b/100_core/src/gplx/Yn.java @@ -0,0 +1,77 @@ +/* +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; +import gplx.core.stores.*; +public class Yn { + public static final String Y = "y", N = "n"; + public static boolean parse_by_char_or(String v, boolean or) { + if (String_.Eq(v, Y)) return true; + else if (String_.Eq(v, N)) return false; + else return or; + } + public static boolean parse_or_n_(String v) {return parse_or(v, false);} + public static int parse_as_int(String v) { + if (v == null) return Bool_.__int; + else if (String_.Eq(v, "y")) return Bool_.Y_int; + else if (String_.Eq(v, "n")) return Bool_.N_int; + else return Bool_.__int; + } + public static boolean parse_or(String v, boolean or) { + int v_int = parse_as_int(v); + switch (v_int) { + case Bool_.N_int: return false; + case Bool_.Y_int: return true; + case Bool_.__int: return or; + default: throw Err_.new_unhandled(v_int); + } + } + public static boolean parse(String v) { + int v_int = parse_as_int(v); + if (v_int == Bool_.__int) Err_.new_unhandled(v); + return v_int == Bool_.Y_int; + } + public static String To_str(boolean v) {return v ? "y" : "n";} + public static String To_nullable_str(byte v) { + switch (v) { + case Bool_.Y_byte: return "y"; + case Bool_.N_byte: return "n"; + case Bool_.__byte: return "?"; + default: throw Err_.new_unhandled(v); + } + } + public static byte To_nullable_byte(String v) { + if (v != null && String_.Len(v) == 1) { + char c = String_.CharAt(v, 0); + switch (c) { + case 'y': return Bool_.Y_byte; + case 'n': return Bool_.N_byte; + case '?': return Bool_.__byte; + } + } + throw Err_.new_unhandled(v); + } + public static boolean store_bool_or(SrlMgr mgr, String key, boolean or) { + String v = mgr.SrlStrOr(key, ""); + return mgr.Type_rdr() ? parse_or(v, or) : or; + } + public static boolean coerce_(Object o) {String s = String_.as_(o); return s != null ? parse_or(s, false) : Bool_.Cast(o);} + public static boolean readOrFalse_(DataRdr rdr, String key) {return read_(rdr, key, false);} + public static boolean readOrTrue_(DataRdr rdr, String key) {return read_(rdr, key, true);} + static boolean read_(DataRdr rdr, String key, boolean or) { + String v = rdr.ReadStrOr(key, null); + return parse_or(v, or); + } +} \ No newline at end of file diff --git a/100_core/src/gplx/core/bits/Bitmask_.java b/100_core/src/gplx/core/bits/Bitmask_.java index a27517de8..5b085d9dd 100644 --- a/100_core/src/gplx/core/bits/Bitmask_.java +++ b/100_core/src/gplx/core/bits/Bitmask_.java @@ -13,3 +13,26 @@ 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.core.bits; import gplx.*; import gplx.core.*; +public class Bitmask_ { + public static boolean Has_int(int val, int find) {return find == (val & find);} + public static int Flip_int(boolean enable, int val, int find) { + boolean has = find == (val & find); + return (has ^ enable) ? val ^ find : val; + } + public static int Add_int(int lhs, int rhs) {return lhs | rhs;} + public static int Add_int_ary(int... ary) { + int rv = 0; + int len = ary.length; + for (int i = 0; i < len; ++i) { + int itm = ary[i]; + if (rv == 0) + rv = itm; + else + rv = Flip_int(true, rv, itm); + } + return rv; + } + public static boolean Has_byte(byte val, byte find) {return find == (val & find);} + public static byte Add_byte(byte flag, byte itm) {return (byte)(flag | itm);} +} diff --git a/100_core/src/gplx/core/brys/Bfr_arg.java b/100_core/src/gplx/core/brys/Bfr_arg.java index a27517de8..29cd02a40 100644 --- a/100_core/src/gplx/core/brys/Bfr_arg.java +++ b/100_core/src/gplx/core/brys/Bfr_arg.java @@ -13,3 +13,7 @@ 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.core.brys; import gplx.*; import gplx.core.*; +public interface Bfr_arg { + void Bfr_arg__add(Bry_bfr bfr); +} diff --git a/100_core/src/gplx/core/brys/Bfr_arg_.java b/100_core/src/gplx/core/brys/Bfr_arg_.java index a27517de8..bad7b021e 100644 --- a/100_core/src/gplx/core/brys/Bfr_arg_.java +++ b/100_core/src/gplx/core/brys/Bfr_arg_.java @@ -13,3 +13,21 @@ 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.core.brys; import gplx.*; import gplx.core.*; +import gplx.core.brys.args.*; import gplx.core.brys.fmtrs.*; +public class Bfr_arg_ { + public static Bfr_arg__int New_int(int v) {return new Bfr_arg__int(v);} + public static Bfr_arg__byte New_byte(byte v) {return new Bfr_arg__byte(v);} + public static Bfr_arg__bry New_bry(String v) {return Bfr_arg__bry.New(Bry_.new_u8(v));} + public static Bfr_arg__bry New_bry(byte[] v) {return Bfr_arg__bry.New(v);} + public static Bfr_arg__bry_fmtr New_bry_fmtr__null() {return new Bfr_arg__bry_fmtr(null, null);} + public static Bfr_arg__bry_fmtr New_bry_fmtr(Bry_fmtr v, Bfr_arg... arg_ary) {return new Bfr_arg__bry_fmtr(v, arg_ary);} + public static final Bfr_arg Noop = new Bfr_arg___noop(); + public static void Clear(Bfr_arg_clearable... ary) { + for (Bfr_arg_clearable arg : ary) + arg.Bfr_arg__clear(); + } +} +class Bfr_arg___noop implements gplx.core.brys.Bfr_arg { + public void Bfr_arg__add(Bry_bfr bfr) {} +} diff --git a/100_core/src/gplx/core/brys/Bfr_arg_clearable.java b/100_core/src/gplx/core/brys/Bfr_arg_clearable.java index a27517de8..e753b00db 100644 --- a/100_core/src/gplx/core/brys/Bfr_arg_clearable.java +++ b/100_core/src/gplx/core/brys/Bfr_arg_clearable.java @@ -13,3 +13,8 @@ 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.core.brys; import gplx.*; import gplx.core.*; +public interface Bfr_arg_clearable extends Bfr_arg { + void Bfr_arg__clear(); + boolean Bfr_arg__missing(); +} diff --git a/100_core/src/gplx/core/brys/Bry_bfr_able.java b/100_core/src/gplx/core/brys/Bry_bfr_able.java index a27517de8..98210155c 100644 --- a/100_core/src/gplx/core/brys/Bry_bfr_able.java +++ b/100_core/src/gplx/core/brys/Bry_bfr_able.java @@ -13,3 +13,7 @@ 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.core.brys; import gplx.*; import gplx.core.*; +public interface Bry_bfr_able { + void To_bfr(Bry_bfr bfr); +} diff --git a/100_core/src/gplx/core/brys/Bry_bfr_able_.java b/100_core/src/gplx/core/brys/Bry_bfr_able_.java index a27517de8..87b3824ef 100644 --- a/100_core/src/gplx/core/brys/Bry_bfr_able_.java +++ b/100_core/src/gplx/core/brys/Bry_bfr_able_.java @@ -13,3 +13,23 @@ 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.core.brys; import gplx.*; import gplx.core.*; +public class Bry_bfr_able_ { + public static byte[][] To_bry_ary(Bry_bfr tmp_bfr, Bry_bfr_able[] ary) { + int len = ary.length; + byte[][] rv = new byte[len][]; + for (int i = 0; i < len; ++i) { + Bry_bfr_able itm = ary[i]; + if (itm != null) { + itm.To_bfr(tmp_bfr); + rv[i] = tmp_bfr.To_bry_and_clear(); + } + } + return rv; + } + public static byte[] To_bry_or_null(Bry_bfr tmp_bfr, Bry_bfr_able itm) { + if (itm == null) return null; + itm.To_bfr(tmp_bfr); + return tmp_bfr.To_bry_and_clear(); + } +} diff --git a/100_core/src/gplx/core/brys/Bry_bfr_mkr.java b/100_core/src/gplx/core/brys/Bry_bfr_mkr.java index a27517de8..ba4ff8e3a 100644 --- a/100_core/src/gplx/core/brys/Bry_bfr_mkr.java +++ b/100_core/src/gplx/core/brys/Bry_bfr_mkr.java @@ -13,3 +13,28 @@ 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.core.brys; import gplx.*; import gplx.core.*; +public class Bry_bfr_mkr { + public Bry_bfr Get_b128() {return mkr_b128.Get();} private final Bry_bfr_mkr_mgr mkr_b128 = new Bry_bfr_mkr_mgr(Tid_b128, 128); + public Bry_bfr Get_b512() {return mkr_b512.Get();} private final Bry_bfr_mkr_mgr mkr_b512 = new Bry_bfr_mkr_mgr(Tid_b512, 512); + public Bry_bfr Get_k004() {return mkr_k004.Get();} private final Bry_bfr_mkr_mgr mkr_k004 = new Bry_bfr_mkr_mgr(Tid_k004, 4 * Io_mgr.Len_kb); + public Bry_bfr Get_m001() {return mkr_m001.Get();} private final Bry_bfr_mkr_mgr mkr_m001 = new Bry_bfr_mkr_mgr(Tid_m001, 1 * Io_mgr.Len_mb); + public void Clear() { + for (byte i = Tid_b128; i <= Tid_m001; i++) + mkr(i).Clear(); + } + public void Clear_fail_check() { + for (byte i = Tid_b128; i <= Tid_m001; i++) + mkr(i).Clear_fail_check(); + } + private Bry_bfr_mkr_mgr mkr(byte tid) { + switch (tid) { + case Tid_b128: return mkr_b128; + case Tid_b512: return mkr_b512; + case Tid_k004: return mkr_k004; + case Tid_m001: return mkr_m001; + default: throw Err_.new_unhandled(tid); + } + } + public static final byte Tid_b128 = 0, Tid_b512 = 1, Tid_k004 = 2, Tid_m001 = 3; +} diff --git a/100_core/src/gplx/core/brys/Bry_bfr_mkr_mgr.java b/100_core/src/gplx/core/brys/Bry_bfr_mkr_mgr.java index a27517de8..16f71e18a 100644 --- a/100_core/src/gplx/core/brys/Bry_bfr_mkr_mgr.java +++ b/100_core/src/gplx/core/brys/Bry_bfr_mkr_mgr.java @@ -13,3 +13,86 @@ 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.core.brys; import gplx.*; import gplx.core.*; +public class Bry_bfr_mkr_mgr { + private final Object thread_lock = new Object(); + private final byte mgr_id; private final int reset; + private Bry_bfr[] used = Bry_bfr_.Ary_empty; private int used_len = 0, used_max = 0; + private int[] free; private int free_len; + public Bry_bfr_mkr_mgr(byte mgr_id, int reset) {// NOTE: random IndexOutOfBounds errors in Get around free[--free_len] with free_len being -1; put member variable initialization within thread_lock to try to avoid; DATE:2014-09-21 + this.mgr_id = mgr_id; + this.reset = reset; + this.free = Int_ary_.Empty; + this.free_len = 0; + } + public Bry_bfr Get() { + synchronized (thread_lock) { + Bry_bfr rv = null; int rv_idx = -1; + if (free_len > 0) { + try {rv_idx = free[--free_len];} catch (Exception e) {throw Err_.new_exc(e, "core", "failed to get free", "idx", free_len, "free_len", free.length);} + try {rv = used[rv_idx];} catch (Exception e) {throw Err_.new_exc(e, "core", "failed to get used", "idx", rv_idx, "used_len", used.length);} + } + else { + if (used_len == used_max) Expand(); + rv_idx = used_len++; + rv = used[rv_idx]; + if (rv == null) { + rv = Bry_bfr_.Reset(reset); + used[rv_idx] = rv; + } + } + rv.Mkr_init(this, rv_idx); + return rv.Clear(); // NOTE: ALWAYS call Clear when doing Get. caller may forget to call Clear, and reused bfr may have leftover bytes. unit tests will not catch, and difficult to spot in app + } + } + public void Rls(int idx) { + synchronized (thread_lock) { + if (idx == -1) throw Err_.new_wo_type("rls called on bfr that was not created by factory"); + int new_used_len = used_len - 1; + if (idx == new_used_len) + used_len = new_used_len; + else + free[free_len++] = idx; + } + } + public void Clear_fail_check() { + synchronized (thread_lock) { + for (int i = 0; i < used_max; i++) { + Bry_bfr itm = used[i]; + if (itm != null) { + if (!itm.Mkr_idx_is_null()) throw Err_.new_wo_type("failed to clear bfr", "mgr_id", mgr_id, "idx", Int_.To_str(i)); + itm.Clear(); + } + used[i] = null; + } + used = Bry_bfr_.Ary_empty; + free = Int_ary_.Empty; + free_len = used_len = used_max = 0; + } + } + public void Clear() { + synchronized (thread_lock) { + for (int i = 0; i < used_max; i++) { + Bry_bfr itm = used[i]; + if (itm != null) itm.Clear(); + used[i] = null; + } + used = Bry_bfr_.Ary_empty; + free = Int_ary_.Empty; + free_len = 0; + used_len = used_max = 0; + } + } + @gplx.Internal protected Bry_bfr[] Used() {return used;} + @gplx.Internal protected int Used_len() {return used_len;} + private void Expand() { + int new_max = used_max == 0 ? 2 : used_max * 2; + Bry_bfr[] new_ary = new Bry_bfr[new_max]; + Array_.Copy_to(used, 0, new_ary, 0, used_max); + used = new_ary; + used_max = new_max; + int[] new_free = new int[used_max]; + Array_.Copy_to(free, 0, new_free, 0, free_len); + free = new_free; + } +} diff --git a/100_core/src/gplx/core/brys/Bry_bfr_mkr_tst.java b/100_core/src/gplx/core/brys/Bry_bfr_mkr_tst.java index a27517de8..fd801cba5 100644 --- a/100_core/src/gplx/core/brys/Bry_bfr_mkr_tst.java +++ b/100_core/src/gplx/core/brys/Bry_bfr_mkr_tst.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.core.brys; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Bry_bfr_mkr_tst { + private final Bry_bfr_mkr_fxt fxt = new Bry_bfr_mkr_fxt(); + @Before public void setup() {fxt.Clear();} + @Test public void Get_1() {fxt.Clear().Get().Test__used(0);} + @Test public void Get_2() {fxt.Clear().Get().Get().Test__used(0, 1);} + @Test public void Get_3() {fxt.Clear().Get().Get().Get().Test__used(0, 1, 2);} + @Test public void Rls() {fxt.Clear().Get().Rls(0).Test__used();} + @Test public void Rls_skip_1() { + fxt.Clear().Get().Get().Rls(0).Test__used(-1, 1); + fxt.Get().Test__used(0, 1); + } + @Test public void Rls_skip_2_1() { + fxt.Clear().Get().Get().Get().Rls(1).Rls(0).Test__used(-1, -1, 2); + fxt.Get().Test__used(0, -1, 2); + fxt.Get().Test__used(0, 1, 2); + fxt.Get().Test__used(0, 1, 2, 3); + } + @Test public void Get_rls_get() { // PURPOSE: defect in which last rls failed b/c was not doing ++ if rv existed + fxt.Clear().Get().Rls(0).Get().Get().Rls(1).Rls(0).Test__used(); + } +} +class Bry_bfr_mkr_fxt { + private final Bry_bfr_mkr_mgr mkr = new Bry_bfr_mkr_mgr(Byte_.Zero, 32); + public Bry_bfr_mkr_fxt Clear() {mkr.Clear(); return this;} + public Bry_bfr_mkr_fxt Get() {mkr.Get(); return this;} + public Bry_bfr_mkr_fxt Rls(int i) {mkr.Used()[i].Mkr_rls(); return this;} + public Bry_bfr_mkr_fxt Test__used(int... expd) { + int actl_len = mkr.Used_len(); + int[] actl = new int[actl_len]; + for (int i = 0; i < actl_len; i++) { + Bry_bfr bfr = mkr.Used()[i]; + int actl_val = bfr == null ? -2 : bfr.Mkr_idx(); + actl[i] = actl_val; + } + Tfds.Eq_ary(expd, actl); + return this; + } +} diff --git a/100_core/src/gplx/core/brys/Bry_err_wkr.java b/100_core/src/gplx/core/brys/Bry_err_wkr.java index a27517de8..069584110 100644 --- a/100_core/src/gplx/core/brys/Bry_err_wkr.java +++ b/100_core/src/gplx/core/brys/Bry_err_wkr.java @@ -13,3 +13,39 @@ 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.core.brys; import gplx.*; import gplx.core.*; +import gplx.core.errs.*; +public class Bry_err_wkr { + private String sect; private int sect_bgn; + public byte[] Src() {return src;} private byte[] src; + public String Page() {return page;} private String page; + public void Fail_throws_err_(boolean v) {this.fail_throws_err = v;} private boolean fail_throws_err = true; + public void Init_by_page(String page, byte[] src) {this.page = page; this.src = src;} + public void Init_by_sect(String sect, int sect_bgn) {this.sect = sect; this.sect_bgn = sect_bgn;} + public void Warn(String msg, Object... args) { + boolean old = fail_throws_err; + fail_throws_err = false; + this.Fail(msg, args); + fail_throws_err = old; + } + public int Fail(String msg, Object... args) {return Fail(msg, sect_bgn, sect_bgn + 255, args);} + private int Fail(String msg, int excerpt_bgn, int excerpt_end, Object[] args) { + String err_msg = Make_msg(msg, excerpt_bgn, excerpt_end, args); + Gfo_usr_dlg_.Instance.Warn_many("", "", err_msg); + if (fail_throws_err) throw Err_.new_("Bry_err_wkr", err_msg).Logged_y_(); + return Bry_find_.Not_found; + } + private String Make_msg(String msg, int excerpt_bgn, int excerpt_end, Object[] args) { + int args_len = args.length; + args_len += 6; + args = (Object[])Array_.Resize(args, args_len); + args[args_len - 6] = "page"; args[args_len - 5] = Quote(page); + args[args_len - 4] = "sect"; args[args_len - 3] = Quote(sect); + args[args_len - 2] = "text"; args[args_len - 1] = Bry_.Escape_ws(Bry_.Mid_safe(src, excerpt_bgn, excerpt_end)); + for (int i = 0; i < args_len - 6; i += 2) { + args[i + 1] = Quote(Object_.Xto_str_strict_or_null_mark(args[i + 1])); + } + return Err_msg.To_str(msg, args); + } + private static String Quote(String v) {return "'" + v + "'";} +} diff --git a/100_core/src/gplx/core/brys/Bry_rdr.java b/100_core/src/gplx/core/brys/Bry_rdr.java index a27517de8..e45741aa1 100644 --- a/100_core/src/gplx/core/brys/Bry_rdr.java +++ b/100_core/src/gplx/core/brys/Bry_rdr.java @@ -13,3 +13,217 @@ 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.core.brys; import gplx.*; import gplx.core.*; +import gplx.core.errs.*; import gplx.core.btries.*; +public class Bry_rdr { + private final gplx.core.primitives.Int_obj_ref pos_ref = gplx.core.primitives.Int_obj_ref.New_neg1(); + private final Btrie_rv trv = new Btrie_rv(); + public byte[] Src() {return src;} protected byte[] src; + public int Src_end() {return src_end;} protected int src_end; + public int Pos() {return pos;} protected int pos; + public boolean Pos_is_eos() {return pos == src_end;} + public Bry_rdr Dflt_dlm_(byte b) {this.dflt_dlm = b; return this;} private byte dflt_dlm; + public Bry_rdr Fail_throws_err_(boolean v) {err_wkr.Fail_throws_err_(v); return this;} + public Bry_rdr Init_by_src(byte[] src) {err_wkr.Init_by_page("", src); this.pos = 0; this.src = src; this.src_end = src.length; return this;} + public Bry_rdr Init_by_page(byte[] page, byte[] src, int src_len) {err_wkr.Init_by_page(String_.new_u8(page), src); this.pos = 0; this.src = src; this.src_end = src_len; return this;} + public Bry_rdr Init_by_sect(String sect, int sect_bgn, int pos) {err_wkr.Init_by_sect(sect, sect_bgn); this.pos = pos; return this;} + public Bry_rdr Init_by_wkr (Bry_err_wkr wkr, String sect, int pos, int src_end) { + this.pos = pos; this.src = wkr.Src(); this.src_end = src_end; + err_wkr.Init_by_page(wkr.Page(), src); + err_wkr.Init_by_sect(sect, pos); + return this; + } + public Bry_err_wkr Err_wkr() {return err_wkr;} private Bry_err_wkr err_wkr = new Bry_err_wkr(); + public int Move_to(int v) {this.pos = v; return pos;} + public int Move_by_one() {return Move_by(1);} + public int Move_by(int v) {this.pos += v; return pos;} + public int Find_fwd_lr() {return Find_fwd(dflt_dlm , Bool_.Y, Bool_.N, Fail_if_missing);} + public int Find_fwd_lr(byte find) {return Find_fwd(find , Bool_.Y, Bool_.N, Fail_if_missing);} + public int Find_fwd_lr_or(byte find, int or) + {return Find_fwd(find , Bool_.Y, Bool_.N, or);} + public int Find_fwd_lr(byte[] find) {return Find_fwd(find , Bool_.Y, Bool_.N, Fail_if_missing);} + public int Find_fwd_rr() {return Find_fwd(dflt_dlm , Bool_.N, Bool_.N, Fail_if_missing);} + public int Find_fwd_rr(byte find) {return Find_fwd(find , Bool_.N, Bool_.N, Fail_if_missing);} + public int Find_fwd_rr(byte[] find) {return Find_fwd(find , Bool_.N, Bool_.N, Fail_if_missing);} + public int Find_fwd_rr_or(byte[] find, int or) + {return Find_fwd(find , Bool_.N, Bool_.N, or);} + private int Find_fwd(byte find, boolean ret_lhs, boolean pos_lhs, int or) { + int find_pos = Bry_find_.Find_fwd(src, find, pos, src_end); + if (find_pos == Bry_find_.Not_found) { + if (or == Fail_if_missing) { + err_wkr.Fail("find failed", "find", Byte_ascii.To_str(find)); + return Bry_find_.Not_found; + } + else + return or; + } + pos = find_pos + (pos_lhs ? 0 : 1); + return ret_lhs ? find_pos : pos; + } + private int Find_fwd(byte[] find, boolean ret_lhs, boolean pos_lhs, int or) { + int find_pos = Bry_find_.Find_fwd(src, find, pos, src_end); + if (find_pos == Bry_find_.Not_found) { + if (or == Fail_if_missing) { + err_wkr.Fail("find failed", "find", String_.new_u8(find)); + return Bry_find_.Not_found; + } + else + return or; + } + pos = find_pos + (pos_lhs ? 0 : find.length); + return ret_lhs ? find_pos : pos; + } + public byte Read_byte() { + byte rv = src[pos]; + ++pos; + return rv; + } + public byte Read_byte_to() {return Read_byte_to(dflt_dlm);} + public byte Read_byte_to(byte to_char) { + byte rv = src[pos]; + ++pos; + if (pos < src_end) { + if (src[pos] != to_char) {err_wkr.Fail("read byte to failed", "to", Byte_ascii.To_str(to_char)); return Byte_.Max_value_127;} + ++pos; + } + return rv; + } + public double Read_double_to() {return Read_double_to(dflt_dlm);} + public double Read_double_to(byte to_char) { + byte[] bry = Read_bry_to(to_char); + return Double_.parse(String_.new_a7(bry)); + } + public int Read_int_to() {return Read_int_to(dflt_dlm);} + public int Read_int_to_non_num() {return Read_int_to(Byte_ascii.Null);} + public int Read_int_to(byte to_char) { + int bgn = pos; + int rv = 0; + int negative = 1; + while (pos < src_end) { + byte b = src[pos++]; + switch (b) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + rv = (rv * 10) + (b - Byte_ascii.Num_0); + break; + case Byte_ascii.Dash: + if (negative == -1) { // 2nd negative + err_wkr.Fail("invalid int", "mid", String_.new_u8(src, bgn, pos)); + return Int_.Min_value; + } + else // 1st negative + negative = -1; // flag negative + break; + default: { + boolean match = b == to_char; + if (to_char == Byte_ascii.Null) {// hack for Read_int_to_non_num + --pos; + match = true; + } + if (!match) { + err_wkr.Fail("invalid int", "mid", String_.new_u8(src, bgn, pos)); + return Int_.Min_value; + } + return rv * negative; + } + } + } + if (bgn == pos) {err_wkr.Fail("int is empty"); return Int_.Min_value;} + return rv * negative; + } + public int Read_hzip_int(int reqd) { + int rv = gplx.core.encoders.Gfo_hzip_int_.Decode(reqd, src, src_end, pos, pos_ref); + pos = pos_ref.Val(); + return rv; + } + public byte[] Read_bry_to() {return Read_bry_to(dflt_dlm);} + public byte[] Read_bry_to(byte b) { + int bgn = pos; + return Bry_.Mid(src, bgn, Find_fwd_lr(b)); + } + public boolean Is(byte find) { + boolean rv = src[pos] == find; + if (rv) ++pos; // only advance if match; + return rv; + } + public boolean Is(byte[] find) { + int find_len = find.length; + int find_end = pos + find_len; + boolean rv = Bry_.Match(src, pos, find_end, find, 0, find_len); + if (rv) pos = find_end; // only advance if match; + return rv; + } + public int Chk(byte find) { + if (src[pos] != find) {err_wkr.Fail("failed check", "chk", Byte_.To_str(find)); return Bry_find_.Not_found;} + ++pos; + return pos; + } + public int Chk(byte[] find) { + int find_end = pos + find.length; + if (!Bry_.Match(src, pos, find_end, find)) {err_wkr.Fail("failed check", "chk", String_.new_u8(find)); return Bry_find_.Not_found;} + pos = find_end; + return pos; + } + public byte Chk(Btrie_slim_mgr trie) {return Chk(trie, pos, src_end);} + public void Chk_trie_val(Btrie_slim_mgr trie, byte val) { + byte rv = Chk_or(trie, Byte_.Max_value_127); + if (rv == Byte_.Max_value_127) err_wkr.Fail("failed trie check", "mid", String_.new_u8(Bry_.Mid_by_len_safe(src, pos, 16))); + } + public Object Chk_trie_as_obj(Btrie_rv trv, Btrie_slim_mgr trie) { + Object rv = trie.Match_at(trv, src, pos, src_end); if (rv == null) err_wkr.Fail("failed trie check", "mid", String_.new_u8(Bry_.Mid_by_len_safe(src, pos, 16))); + return rv; + } + public byte Chk_or(Btrie_rv trv, Btrie_slim_mgr trie, byte or) { + Object rv_obj = trie.Match_at(trv, src, pos, src_end); + return rv_obj == null ? or : ((gplx.core.primitives.Byte_obj_val)rv_obj).Val(); + } + public byte Chk_or(Btrie_slim_mgr trie, byte or) {return Chk_or(trie, pos, src_end, or);} + public byte Chk(Btrie_slim_mgr trie, int itm_bgn, int itm_end) { + byte rv = Chk_or(trie, itm_bgn, itm_end, Byte_.Max_value_127); + if (rv == Byte_.Max_value_127) {err_wkr.Fail("failed trie check", "mid", String_.new_u8(Bry_.Mid_by_len_safe(src, pos, 16))); return Byte_.Max_value_127;} + return rv; + } + public byte Chk_or(Btrie_slim_mgr trie, int itm_bgn, int itm_end, byte or) { + Object rv_obj = trie.Match_at(trv, src, itm_bgn, itm_end); + if (rv_obj == null) return or; + pos = trv.Pos(); + return ((gplx.core.primitives.Byte_obj_val)rv_obj).Val(); + } + @gplx.Virtual public Bry_rdr Skip_ws() { + while (pos < src_end) { + switch (src[pos]) { + case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: case Byte_ascii.Space: + ++pos; + break; + default: + return this; + } + } + return this; + } + public Bry_rdr Skip_alpha_num_under() { + while (pos < src_end) { + switch (src[pos]) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + case Byte_ascii.Ltr_A: case Byte_ascii.Ltr_B: case Byte_ascii.Ltr_C: case Byte_ascii.Ltr_D: case Byte_ascii.Ltr_E: + case Byte_ascii.Ltr_F: case Byte_ascii.Ltr_G: case Byte_ascii.Ltr_H: case Byte_ascii.Ltr_I: case Byte_ascii.Ltr_J: + case Byte_ascii.Ltr_K: case Byte_ascii.Ltr_L: case Byte_ascii.Ltr_M: case Byte_ascii.Ltr_N: case Byte_ascii.Ltr_O: + case Byte_ascii.Ltr_P: case Byte_ascii.Ltr_Q: case Byte_ascii.Ltr_R: case Byte_ascii.Ltr_S: case Byte_ascii.Ltr_T: + case Byte_ascii.Ltr_U: case Byte_ascii.Ltr_V: case Byte_ascii.Ltr_W: case Byte_ascii.Ltr_X: case Byte_ascii.Ltr_Y: case Byte_ascii.Ltr_Z: + case Byte_ascii.Ltr_a: case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_e: + case Byte_ascii.Ltr_f: case Byte_ascii.Ltr_g: case Byte_ascii.Ltr_h: case Byte_ascii.Ltr_i: case Byte_ascii.Ltr_j: + case Byte_ascii.Ltr_k: case Byte_ascii.Ltr_l: case Byte_ascii.Ltr_m: case Byte_ascii.Ltr_n: case Byte_ascii.Ltr_o: + case Byte_ascii.Ltr_p: case Byte_ascii.Ltr_q: case Byte_ascii.Ltr_r: case Byte_ascii.Ltr_s: case Byte_ascii.Ltr_t: + case Byte_ascii.Ltr_u: case Byte_ascii.Ltr_v: case Byte_ascii.Ltr_w: case Byte_ascii.Ltr_x: case Byte_ascii.Ltr_y: case Byte_ascii.Ltr_z: + case Byte_ascii.Underline: + ++pos; + break; + default: + return this; + } + } + return this; + } + private static final int Fail_if_missing = Int_.Min_value; +} diff --git a/100_core/src/gplx/core/brys/Bry_rdr_old.java b/100_core/src/gplx/core/brys/Bry_rdr_old.java index a27517de8..20f885459 100644 --- a/100_core/src/gplx/core/brys/Bry_rdr_old.java +++ b/100_core/src/gplx/core/brys/Bry_rdr_old.java @@ -13,3 +13,155 @@ 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.core.brys; import gplx.*; import gplx.core.*; +public class Bry_rdr_old { + private byte[] scope = Bry_.Empty; + public byte[] Src() {return src;} protected byte[] src; + public int Src_len() {return src_len;} protected int src_len; + public void Init(byte[] src) {this.Init(Bry_.Empty, src, 0);} + public void Init(byte[] src, int pos) {this.Init(Bry_.Empty, src, pos);} + public void Init(byte[] scope, byte[] src, int pos) { + this.src = src; this.src_len = src.length; this.pos = pos; + this.scope = scope; + } + public int Pos() {return pos;} public Bry_rdr_old Pos_(int v) {this.pos = v; return this;} protected int pos; + public void Pos_add(int v) {pos += v;} + public boolean Pos_is_eos() {return pos == src_len;} + public boolean Pos_is_reading() {return pos < src_len;} + public void Pos_add_one() {++pos;} + public int Or_int() {return or_int;} public void Or_int_(int v) {or_int = v;} private int or_int = Int_.Min_value; + public byte[] Or_bry() {return or_bry;} public void Or_bry_(byte[] v) {or_bry = v;} private byte[] or_bry; + public int Find_fwd(byte find) {return Bry_find_.Find_fwd(src, find, pos);} + public int Find_fwd_ws() {return Bry_find_.Find_fwd_until_ws(src, pos, src_len);} + public int Find_fwd__pos_at_lhs(byte[] find_bry) {return Find_fwd__pos_at(find_bry, Bool_.N);} + public int Find_fwd__pos_at_rhs(byte[] find_bry) {return Find_fwd__pos_at(find_bry, Bool_.Y);} + public int Find_fwd__pos_at(byte[] find_bry, boolean pos_at_rhs) { + int find_pos = Bry_find_.Find_fwd(src, find_bry, pos, src_len); + if (pos_at_rhs) find_pos += find_bry.length; + if (find_pos != Bry_find_.Not_found) pos = find_pos; + return find_pos; + } + public byte Read_byte() {return src[pos];} + public int Read_int_to_semic() {return Read_int_to(Byte_ascii.Semic);} + public int Read_int_to_comma() {return Read_int_to(Byte_ascii.Comma);} + public int Read_int_to_pipe() {return Read_int_to(Byte_ascii.Pipe);} + public int Read_int_to_nl() {return Read_int_to(Byte_ascii.Nl);} + public int Read_int_to_quote() {return Read_int_to(Byte_ascii.Quote);} + public int Read_int_to_non_num(){return Read_int_to(Byte_ascii.Null);} + public int Read_int_to(byte to_char) { + int bgn = pos; + int rv = 0; + int negative = 1; + while (pos < src_len) { + byte b = src[pos++]; + switch (b) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + rv = (rv * 10) + (b - Byte_ascii.Num_0); + break; + case Byte_ascii.Dash: + if (negative == -1) // 2nd negative + return or_int; // return or_int + else // 1st negative + negative = -1; // flag negative + break; + default: { + boolean match = b == to_char; + if (to_char == Byte_ascii.Null) {// hack for Read_int_to_non_num + --pos; + match = true; + } + return match ? rv * negative : or_int; + } + } + } + return bgn == pos ? or_int : rv * negative; + } + public byte[] Read_bry_to_nl() {return Read_bry_to(Byte_ascii.Nl);} + public byte[] Read_bry_to_semic() {return Read_bry_to(Byte_ascii.Semic);} + public byte[] Read_bry_to_pipe() {return Read_bry_to(Byte_ascii.Pipe);} + public byte[] Read_bry_to_quote() {return Read_bry_to(Byte_ascii.Quote);} + public byte[] Read_bry_to_apos() {return Read_bry_to(Byte_ascii.Apos);} + public byte[] Read_bry_to(byte to_char) { + int bgn = pos; + while (pos < src_len) { + byte b = src[pos]; + if (b == to_char) + return Bry_.Mid(src, bgn, pos++); + else + ++pos; + } + return bgn == pos ? or_bry : Bry_.Mid(src, bgn, src_len); + } + public boolean Read_yn_to_pipe() {return Read_byte_to_pipe() == Byte_ascii.Ltr_y;} + public byte Read_byte_to_pipe() { + byte rv = src[pos]; + pos += 2; // 1 for byte; 1 for pipe; + return rv; + } + public double Read_double_to_pipe() {return Read_double_to(Byte_ascii.Pipe);} + public double Read_double_to(byte to_char) { + byte[] double_bry = Read_bry_to(to_char); + return Double_.parse(String_.new_a7(double_bry)); // double will never have utf8 + } + @gplx.Virtual public Bry_rdr_old Skip_ws() { + while (pos < src_len) { + switch (src[pos]) { + case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: case Byte_ascii.Space: + ++pos; + break; + default: + return this; + } + } + return this; + } + public Bry_rdr_old Skip_alpha_num_under() { + while (pos < src_len) { + switch (src[pos]) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + case Byte_ascii.Ltr_A: case Byte_ascii.Ltr_B: case Byte_ascii.Ltr_C: case Byte_ascii.Ltr_D: case Byte_ascii.Ltr_E: + case Byte_ascii.Ltr_F: case Byte_ascii.Ltr_G: case Byte_ascii.Ltr_H: case Byte_ascii.Ltr_I: case Byte_ascii.Ltr_J: + case Byte_ascii.Ltr_K: case Byte_ascii.Ltr_L: case Byte_ascii.Ltr_M: case Byte_ascii.Ltr_N: case Byte_ascii.Ltr_O: + case Byte_ascii.Ltr_P: case Byte_ascii.Ltr_Q: case Byte_ascii.Ltr_R: case Byte_ascii.Ltr_S: case Byte_ascii.Ltr_T: + case Byte_ascii.Ltr_U: case Byte_ascii.Ltr_V: case Byte_ascii.Ltr_W: case Byte_ascii.Ltr_X: case Byte_ascii.Ltr_Y: case Byte_ascii.Ltr_Z: + case Byte_ascii.Ltr_a: case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_e: + case Byte_ascii.Ltr_f: case Byte_ascii.Ltr_g: case Byte_ascii.Ltr_h: case Byte_ascii.Ltr_i: case Byte_ascii.Ltr_j: + case Byte_ascii.Ltr_k: case Byte_ascii.Ltr_l: case Byte_ascii.Ltr_m: case Byte_ascii.Ltr_n: case Byte_ascii.Ltr_o: + case Byte_ascii.Ltr_p: case Byte_ascii.Ltr_q: case Byte_ascii.Ltr_r: case Byte_ascii.Ltr_s: case Byte_ascii.Ltr_t: + case Byte_ascii.Ltr_u: case Byte_ascii.Ltr_v: case Byte_ascii.Ltr_w: case Byte_ascii.Ltr_x: case Byte_ascii.Ltr_y: case Byte_ascii.Ltr_z: + case Byte_ascii.Underline: + ++pos; + break; + default: + return this; + } + } + return this; + } + public void Chk_bry_or_fail(byte[] bry) { + int bry_len = bry.length; + boolean match = Bry_.Match(src, pos, pos + bry_len, bry); + if (match) pos += bry_len; + else throw Err_.new_wo_type("bry.rdr:chk failed", "bry", bry, "pos", pos); + } + public void Chk_byte_or_fail(byte b) { + boolean match = pos < src_len ? src[pos] == b : false; + if (match) ++pos; + else throw Err_.new_wo_type("bry.rdr:chk failed", "byte", b, "pos", pos); + } + public byte[] Mid_by_len_safe(int len) { + int end = pos + len; if (end > src_len) end = src_len; + return Bry_.Mid(src, pos, end); + } + public boolean Chk_bry_wo_move(byte[] bry, int pos) { + int bry_len = bry.length; + return Bry_.Match(src, pos, pos + bry_len, bry); + } + public int Warn(String err, int bgn, int rv) { + int end = rv + 256; if (end > src_len) end = src_len; + Gfo_usr_dlg_.Instance.Warn_many("", "", "read failed: scope=~{0} err=~{1} mid=~{2}", scope, err, String_.new_u8(src, bgn, end)); + return rv + 1; // rv is always hook_bgn; add +1 to set at next character, else infinite loop + } +} \ No newline at end of file diff --git a/100_core/src/gplx/core/brys/Bry_rdr_tst.java b/100_core/src/gplx/core/brys/Bry_rdr_tst.java index a27517de8..fc38b774e 100644 --- a/100_core/src/gplx/core/brys/Bry_rdr_tst.java +++ b/100_core/src/gplx/core/brys/Bry_rdr_tst.java @@ -13,3 +13,42 @@ 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.core.brys; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Bry_rdr_tst { + @Before public void init() {fxt.Clear();} private Bry_rdr_fxt fxt = new Bry_rdr_fxt(); + @Test public void Int() { + fxt.Init_src("12|3456|789"); + fxt.Test_read_int(12); + fxt.Test_read_int(3456); + fxt.Test_read_int(789); + fxt.Test_read_int(Int_.Min_value); + } + @Test public void Int_negative() { + fxt.Init_src("-1|-2"); + fxt.Test_read_int(-1); + fxt.Test_read_int(-2); + } + @Test public void Bry() { + fxt.Init_src("abc|d||ef"); + fxt.Test_read_bry("abc"); + fxt.Test_read_bry("d"); + fxt.Test_read_bry(""); + fxt.Test_read_bry("ef"); + fxt.Test_read_bry(null); + } +} +class Bry_rdr_fxt { + private Bry_rdr_old rdr; + public void Clear() {rdr = new Bry_rdr_old();} + public Bry_rdr_fxt Init_src(String v) {rdr.Init(Bry_.new_u8(v)); return this;} + public Bry_rdr_fxt Init_pos(int v) {rdr.Pos_(v); return this;} + public void Test_read_int(int expd_val) { + Tfds.Eq(expd_val, rdr.Read_int_to_pipe()); + } + public void Test_read_bry(String expd_str) { + byte[] actl_bry = rdr.Read_bry_to_pipe(); + String actl_str = actl_bry == null ? null : String_.new_u8(actl_bry); + Tfds.Eq(expd_str, actl_str); + } +} diff --git a/100_core/src/gplx/core/brys/Bry_split_wkr.java b/100_core/src/gplx/core/brys/Bry_split_wkr.java index a27517de8..7420b709f 100644 --- a/100_core/src/gplx/core/brys/Bry_split_wkr.java +++ b/100_core/src/gplx/core/brys/Bry_split_wkr.java @@ -13,3 +13,7 @@ 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.core.brys; import gplx.*; import gplx.core.*; +public interface Bry_split_wkr { + int Split(byte[] src, int itm_bgn, int itm_end); +} diff --git a/100_core/src/gplx/core/brys/args/Bfr_arg__bry.java b/100_core/src/gplx/core/brys/args/Bfr_arg__bry.java index a27517de8..fee7449c6 100644 --- a/100_core/src/gplx/core/brys/args/Bfr_arg__bry.java +++ b/100_core/src/gplx/core/brys/args/Bfr_arg__bry.java @@ -13,3 +13,29 @@ 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.core.brys.args; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +public class Bfr_arg__bry implements Bfr_arg_clearable { + private int tid; + private byte[] src; private int src_bgn, src_end; + private Bfr_arg arg; + public void Set_by_mid(byte[] src, int bgn, int end) {this.tid = Tid_mid; this.src = src; this.src_bgn = bgn; this.src_end = end;} + public void Set_by_val(byte[] src) {this.tid = Tid_val; this.src = src;} + public void Set_by_arg(Bfr_arg arg) {this.tid = Tid_arg; this.arg = arg;} + public void Bfr_arg__clear() { + tid = Tid_nil; + src = null; src_bgn = src_end = -1; + arg = null; + } + public boolean Bfr_arg__missing() {return tid == Tid_nil;} + public void Bfr_arg__add(Bry_bfr bfr) { + switch (tid) { + case Tid_val: bfr.Add(src); break; + case Tid_mid: bfr.Add_mid(src, src_bgn, src_end); break; + case Tid_arg: arg.Bfr_arg__add(bfr); break; + case Tid_nil: break; + } + } + public static Bfr_arg__bry New_empty() {return new Bfr_arg__bry();} + public static Bfr_arg__bry New(byte[] v) {Bfr_arg__bry rv = new Bfr_arg__bry(); rv.Set_by_val(v); return rv;} + private static final int Tid_nil = 0, Tid_val = 1, Tid_mid = 2, Tid_arg = 3; +} diff --git a/100_core/src/gplx/core/brys/args/Bfr_arg__bry_ary.java b/100_core/src/gplx/core/brys/args/Bfr_arg__bry_ary.java index a27517de8..93da8fed2 100644 --- a/100_core/src/gplx/core/brys/args/Bfr_arg__bry_ary.java +++ b/100_core/src/gplx/core/brys/args/Bfr_arg__bry_ary.java @@ -13,3 +13,12 @@ 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.core.brys.args; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +public class Bfr_arg__bry_ary implements Bfr_arg { + private byte[][] bry_ary; + public Bfr_arg__bry_ary Set(byte[]... v) {this.bry_ary = v; return this;} + public void Bfr_arg__add(Bry_bfr bfr) { + for (byte[] bry : bry_ary) + bfr.Add(bry); + } +} diff --git a/100_core/src/gplx/core/brys/args/Bfr_arg__bry_fmt.java b/100_core/src/gplx/core/brys/args/Bfr_arg__bry_fmt.java index a27517de8..95960fad2 100644 --- a/100_core/src/gplx/core/brys/args/Bfr_arg__bry_fmt.java +++ b/100_core/src/gplx/core/brys/args/Bfr_arg__bry_fmt.java @@ -13,3 +13,24 @@ 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.core.brys.args; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +import gplx.core.brys.*; +public class Bfr_arg__bry_fmt implements Bfr_arg { + private final Bry_fmt fmt; + private Object[] arg_ary; + public Bfr_arg__bry_fmt(Bry_fmt fmt, Object... arg_ary) { + this.fmt = fmt; + Args_(arg_ary); + } + public void Bfr_arg__clear() {arg_ary = null;} + public boolean Bfr_arg__missing() {return arg_ary == null;} + + public Bfr_arg__bry_fmt Args_(Object... arg_ary) { + this.arg_ary = arg_ary; + return this; + } + public void Bfr_arg__add(Bry_bfr bfr) { + if (Bfr_arg__missing()) return; + fmt.Bld_many(bfr, arg_ary); + } +} diff --git a/100_core/src/gplx/core/brys/args/Bfr_arg__bry_fmtr.java b/100_core/src/gplx/core/brys/args/Bfr_arg__bry_fmtr.java index a27517de8..2161fe9d1 100644 --- a/100_core/src/gplx/core/brys/args/Bfr_arg__bry_fmtr.java +++ b/100_core/src/gplx/core/brys/args/Bfr_arg__bry_fmtr.java @@ -13,3 +13,14 @@ 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.core.brys.args; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +import gplx.core.brys.fmtrs.*; +public class Bfr_arg__bry_fmtr implements Bfr_arg { + private Bry_fmtr fmtr; private Object[] arg_ary; + public Bfr_arg__bry_fmtr(Bry_fmtr fmtr, Object[] arg_ary) {Set(fmtr, arg_ary);} + public Bfr_arg__bry_fmtr Set(Bry_fmtr fmtr, Object... arg_ary) { + this.fmtr = fmtr; this.arg_ary = arg_ary; + return this; + } + public void Bfr_arg__add(Bry_bfr bfr) {fmtr.Bld_bfr_many(bfr, arg_ary);} +} diff --git a/100_core/src/gplx/core/brys/args/Bfr_arg__byte.java b/100_core/src/gplx/core/brys/args/Bfr_arg__byte.java index a27517de8..cd7170bcc 100644 --- a/100_core/src/gplx/core/brys/args/Bfr_arg__byte.java +++ b/100_core/src/gplx/core/brys/args/Bfr_arg__byte.java @@ -13,3 +13,9 @@ 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.core.brys.args; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +public class Bfr_arg__byte implements Bfr_arg { + private final byte byt; + public Bfr_arg__byte(byte byt) {this.byt = byt;} + public void Bfr_arg__add(Bry_bfr bfr) {bfr.Add_byte(byt);} +} diff --git a/100_core/src/gplx/core/brys/args/Bfr_arg__decimal_int.java b/100_core/src/gplx/core/brys/args/Bfr_arg__decimal_int.java index a27517de8..502755516 100644 --- a/100_core/src/gplx/core/brys/args/Bfr_arg__decimal_int.java +++ b/100_core/src/gplx/core/brys/args/Bfr_arg__decimal_int.java @@ -13,3 +13,11 @@ 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.core.brys.args; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +public class Bfr_arg__decimal_int implements Bfr_arg { + public int Val() {return val;} public Bfr_arg__decimal_int Val_(int v) {val = v; return this;} int val; + public Bfr_arg__decimal_int Places_(int v) {places = v; multiple = (int)Math_.Pow(10, v); return this;} int multiple = 1000, places = 3; + public void Bfr_arg__add(Bry_bfr bfr) { + bfr.Add_int_variable(val / multiple).Add_byte(Byte_ascii.Dot).Add_int_fixed(val % multiple, places); + } +} diff --git a/100_core/src/gplx/core/brys/args/Bfr_arg__int.java b/100_core/src/gplx/core/brys/args/Bfr_arg__int.java index a27517de8..d782c6892 100644 --- a/100_core/src/gplx/core/brys/args/Bfr_arg__int.java +++ b/100_core/src/gplx/core/brys/args/Bfr_arg__int.java @@ -13,3 +13,14 @@ 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.core.brys.args; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +public class Bfr_arg__int implements Bfr_arg { + private int val, val_digits; + public Bfr_arg__int(int v) {Set(v);} + public Bfr_arg__int Set(int v) { + this.val = Int_.Cast(v); + this.val_digits = Int_.DigitCount(val); + return this; + } + public void Bfr_arg__add(Bry_bfr bfr) {bfr.Add_int_digits(val_digits, val);} +} diff --git a/100_core/src/gplx/core/brys/args/Bfr_arg__time.java b/100_core/src/gplx/core/brys/args/Bfr_arg__time.java index a27517de8..628f1d155 100644 --- a/100_core/src/gplx/core/brys/args/Bfr_arg__time.java +++ b/100_core/src/gplx/core/brys/args/Bfr_arg__time.java @@ -13,3 +13,41 @@ 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.core.brys.args; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +public class Bfr_arg__time implements Bfr_arg { + public Bfr_arg__time() { + units_len = units.length; + } + public long Seconds() {return seconds;} public Bfr_arg__time Seconds_(long v) {seconds = v; return this;} long seconds; + byte[][] segs = new byte[][] + { Bry_.new_a7("d") + , Bry_.new_a7("h") + , Bry_.new_a7("m") + , Bry_.new_a7("s") + }; + int[] units = new int[] {86400, 3600, 60, 1}; + int units_len; + byte[] spr = new byte[] {Byte_ascii.Space}; + public void Bfr_arg__add(Bry_bfr bfr) { + if (seconds == 0) { // handle 0 separately (since it will always be < than units[*] + bfr.Add_int_fixed(0, 2).Add(segs[units_len - 1]); + return; + } + long val = seconds; + boolean dirty = false; + for (int i = 0; i < units_len; i++) { + long unit = units[i]; + long seg = 0; + if (val >= unit) { // unit has value; EX: 87000 > 86400, so unit is 1 day + seg = val / unit; + val = val - (seg * unit); + } + if (seg > 0 || dirty) { // dirty check allows for 0 in middle units (EX: 1h 0m 1s) + if (dirty) bfr.Add(spr); + if (seg < 10) bfr.Add_byte(Byte_ascii.Num_0); // 0 pad + bfr.Add_long_variable(seg).Add(segs[i]); + dirty = true; + } + } + } +} diff --git a/100_core/src/gplx/core/brys/args/Bfr_arg__time_tst.java b/100_core/src/gplx/core/brys/args/Bfr_arg__time_tst.java index a27517de8..8c1faccb9 100644 --- a/100_core/src/gplx/core/brys/args/Bfr_arg__time_tst.java +++ b/100_core/src/gplx/core/brys/args/Bfr_arg__time_tst.java @@ -13,3 +13,28 @@ 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.core.brys.args; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +import org.junit.*; +public class Bfr_arg__time_tst { + @Test public void Basic() { + Time_fmtr_arg_fxt fxt = new Time_fmtr_arg_fxt().Clear(); + fxt.XferAry( 1, "01s"); // seconds + fxt.XferAry( 62, "01m 02s"); // minutes + fxt.XferAry( 3723, "01h 02m 03s"); // hours + fxt.XferAry( 93784, "01d 02h 03m 04s"); // days + fxt.XferAry( 0, "00s"); // handle 0 seconds + fxt.XferAry( 3601, "01h 00m 01s"); // handle 0 in middle unit + } +} +class Time_fmtr_arg_fxt { + public Time_fmtr_arg_fxt Clear() { + if (arg == null) arg = new Bfr_arg__time(); + return this; + } Bfr_arg__time arg; + public void XferAry(int seconds, String expd) { + Bry_bfr bfr = Bry_bfr_.Reset(255); + arg.Seconds_(seconds); + arg.Bfr_arg__add(bfr); + Tfds.Eq(expd, bfr.To_str()); + } +} diff --git a/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr.java b/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr.java index a27517de8..47b6a4557 100644 --- a/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr.java +++ b/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr.java @@ -13,3 +13,252 @@ 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.core.brys.fmtrs; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +import gplx.core.brys.*; import gplx.core.primitives.*; import gplx.core.strings.*; +public class Bry_fmtr { + public byte[] Fmt() {return fmt;} private byte[] fmt = Bry_.Empty; + public boolean Fmt_null() {return fmt.length == 0;} + public Bry_fmtr_eval_mgr Eval_mgr() {return eval_mgr;} public Bry_fmtr Eval_mgr_(Bry_fmtr_eval_mgr v) {eval_mgr = v; return this;} Bry_fmtr_eval_mgr eval_mgr = Bry_fmtr_eval_mgr_gfs.Instance; + public Bry_fmtr Fmt_(byte[] v) {fmt = v; dirty = true; return this;} public Bry_fmtr Fmt_(String v) {return Fmt_(Bry_.new_u8(v));} + public Bry_fmtr Keys_(String... ary) { + if (keys == null) keys = Hash_adp_.New(); + else keys.Clear(); + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) + keys.Add(Bry_obj_ref.New(Bry_.new_u8(ary[i])), new Int_obj_val(i)); + dirty = true; + return this; + } Hash_adp keys = null; + public void Bld_bfr(Bry_bfr bfr, byte[]... args) { + if (dirty) Compile(); + int args_len = args.length; + for (int i = 0; i < itms_len; i++) { + Bry_fmtr_itm itm = itms[i]; + if (itm.Arg) { + int arg_idx = itm.ArgIdx; + if (arg_idx < args_len) + bfr.Add(args[arg_idx]); + else + bfr.Add(missing_bgn).Add_int_variable(arg_idx + missing_adj).Add(missing_end); + } + else + bfr.Add(itm.Dat); + } + } + public void Bld_bfr_none(Bry_bfr bfr) { + if (dirty) Compile(); + for (int i = 0; i < itms_len; i++) { + Bry_fmtr_itm itm = itms[i]; + if (itm.Arg) + bfr.Add_byte(char_escape).Add_byte(char_arg_bgn).Add_int_variable(itm.ArgIdx).Add_byte(char_arg_end); + else + bfr.Add(itm.Dat); + } + } + public void Bld_bfr(Bry_bfr bfr, Bfr_arg... args) { + if (dirty) Compile(); + for (int i = 0; i < itms_len; i++) { + Bry_fmtr_itm itm = itms[i]; + if (itm.Arg) + args[itm.ArgIdx].Bfr_arg__add(bfr); + else + bfr.Add(itm.Dat); + } + } + public void Bld_bfr_one(Bry_bfr bfr, Object val) { + Bld_bfr_one_ary[0] = val; + Bld_bfr_ary(bfr, Bld_bfr_one_ary); + } Object[] Bld_bfr_one_ary = new Object[1]; + public void Bld_bfr_many(Bry_bfr bfr, Object... args) {Bld_bfr_ary(bfr, args);} + public void Bld_bfr_ary(Bry_bfr bfr, Object[] args) { + if (dirty) Compile(); + int args_len = args.length; + for (int i = 0; i < itms_len; i++) { + Bry_fmtr_itm itm = itms[i]; + if (itm.Arg) { + int arg_idx = itm.ArgIdx; + if (arg_idx > -1 && arg_idx < args_len) + bfr.Add_obj(args[itm.ArgIdx]); + else + bfr.Add_byte(char_escape).Add_byte(char_arg_bgn).Add_int_variable(arg_idx).Add_byte(char_arg_end); + } + else + bfr.Add(itm.Dat); + } + } + public byte[] Bld_bry_none(Bry_bfr bfr) {Bld_bfr_ary(bfr, Object_.Ary_empty); return bfr.To_bry_and_clear();} + public byte[] Bld_bry_many(Bry_bfr bfr, Object... args) { + Bld_bfr_ary(bfr, args); + return bfr.To_bry_and_clear(); + } + public String Bld_str_many(Bry_bfr bfr, String fmt, Object... args) { + this.Fmt_(fmt).Bld_bfr_many(bfr, args); + return bfr.To_str_and_clear(); + } + public String Bld_str_many(String... args) { + if (dirty) Compile(); + String_bldr rv = String_bldr_.new_(); + int args_len = args.length; + for (int i = 0; i < itms_len; i++) { + Bry_fmtr_itm itm = itms[i]; + if (itm.Arg) { + int arg_idx = itm.ArgIdx; + if (arg_idx < args_len) + rv.Add(args[arg_idx]); + else + rv.Add(missing_bgn).Add(arg_idx + missing_adj).Add(missing_end); + } + else + rv.Add(itm.DatStr()); + } + return rv.To_str(); + } private Bry_fmtr_itm[] itms; int itms_len; + public byte[] Missing_bgn() {return missing_bgn;} public Bry_fmtr Missing_bgn_(byte[] v) {missing_bgn = v; return this;} private byte[] missing_bgn = missing_bgn_static; static byte[] missing_bgn_static = Bry_.new_u8("~{"), missing_end_static = Bry_.new_u8("}"); + public byte[] Missing_end() {return missing_end;} public Bry_fmtr Missing_end_(byte[] v) {missing_end = v; return this;} private byte[] missing_end = missing_end_static; + public int Missing_adj() {return missing_adj;} public Bry_fmtr Missing_adj_(int v) {missing_adj = v; return this;} int missing_adj; + public boolean Fail_when_invalid_escapes() {return fail_when_invalid_escapes;} public Bry_fmtr Fail_when_invalid_escapes_(boolean v) {fail_when_invalid_escapes = v; return this;} private boolean fail_when_invalid_escapes = true; + public Bry_fmtr Compile() { + synchronized (this) { // THREAD: DATE:2015-04-29 + Bry_bfr lkp_bfr = Bry_bfr_.New_w_size(16); + int fmt_len = fmt.length; int fmt_end = fmt_len - 1; int fmt_pos = 0; + byte[] trg_bry = new byte[fmt_len]; int trg_pos = 0; + boolean lkp_is_active = false, lkp_is_numeric = true; + byte nxt_byte, tmp_byte; + List_adp list = List_adp_.New(); + fmt_args_exist = false; + while (true) { + if (fmt_pos > fmt_end) break; + byte cur_byte = fmt[fmt_pos]; + if (lkp_is_active) { + if (cur_byte == char_arg_end) { + if (lkp_is_numeric) + list.Add(Bry_fmtr_itm.arg_(lkp_bfr.To_int(0) - baseInt)); + else { + byte[] key_fmt = lkp_bfr.To_bry(); + Object idx_ref = keys.Get_by(Bry_obj_ref.New(key_fmt)); + if (idx_ref == null) { + int lkp_bfr_len = lkp_bfr.Len(); + byte[] lkp_bry = lkp_bfr.Bfr(); + trg_bry[trg_pos++] = char_escape; + trg_bry[trg_pos++] = char_arg_bgn; + for (int i = 0; i < lkp_bfr_len; i++) + trg_bry[trg_pos++] = lkp_bry[i]; + trg_bry[trg_pos++] = char_arg_end; + } + else { + list.Add(Bry_fmtr_itm.arg_(((Int_obj_val)idx_ref).Val() - baseInt)); + } + } + lkp_is_active = false; + lkp_bfr.Clear(); + fmt_args_exist = true; + } + else { + lkp_bfr.Add_byte(cur_byte); + switch (cur_byte) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + break; + default: + lkp_is_numeric = false; + break; + } + } + fmt_pos += 1; + } + else if (cur_byte == char_escape) { + if (fmt_pos == fmt_end) { + if (fail_when_invalid_escapes) + throw Err_.new_wo_type("escape char encountered but no more chars left"); + else { + trg_bry[trg_pos] = cur_byte; + break; + } + } + nxt_byte = fmt[fmt_pos + 1]; + if (nxt_byte == char_arg_bgn) { + if (trg_pos > 0) {list.Add(Bry_fmtr_itm.dat_(trg_bry, trg_pos)); trg_pos = 0;} // something pending; add it to list + int eval_lhs_bgn = fmt_pos + 2; + if (eval_lhs_bgn < fmt_len && fmt[eval_lhs_bgn] == char_eval_bgn) { // eval found + fmt_pos = Compile_eval_cmd(fmt, fmt_len, eval_lhs_bgn, list); + continue; + } + else { + lkp_is_active = true; + lkp_is_numeric = true; + } + } + else { // ~{0}; ~~ -> ~; ~n -> newLine; ~t -> tab + if (nxt_byte == char_escape) tmp_byte = char_escape; + else if (nxt_byte == char_escape_nl) tmp_byte = Byte_ascii.Nl; + else if (nxt_byte == char_escape_tab) tmp_byte = Byte_ascii.Tab; + else { + if (fail_when_invalid_escapes) throw Err_.new_wo_type("unknown escape code", "code", Char_.By_int(nxt_byte), "fmt_pos", fmt_pos + 1); + else + tmp_byte = cur_byte; + } + trg_bry[trg_pos++] = tmp_byte; + } + fmt_pos += 2; + } + else { + trg_bry[trg_pos++] = cur_byte; + fmt_pos += 1; + } + } + if (lkp_is_active) throw Err_.new_wo_type("idx mode not closed"); + if (trg_pos > 0) {list.Add(Bry_fmtr_itm.dat_(trg_bry, trg_pos)); trg_pos = 0;} + itms = (Bry_fmtr_itm[])list.To_ary(Bry_fmtr_itm.class); + itms_len = itms.length; + return this; + } + } + int Compile_eval_cmd(byte[] fmt, int fmt_len, int eval_lhs_bgn, List_adp list) { + int eval_lhs_end = Bry_find_.Find_fwd(fmt, char_eval_end, eval_lhs_bgn + Byte_ascii.Len_1, fmt_len); if (eval_lhs_end == Bry_find_.Not_found) throw Err_.new_wo_type("eval_lhs_end_invalid: could not find eval_lhs_end", "snip", String_.new_u8(fmt, eval_lhs_bgn, fmt_len)); + byte[] eval_dlm = Bry_.Mid(fmt, eval_lhs_bgn , eval_lhs_end + Byte_ascii.Len_1); + int eval_rhs_bgn = Bry_find_.Find_fwd(fmt, eval_dlm , eval_lhs_end + Byte_ascii.Len_1, fmt_len); if (eval_rhs_bgn == Bry_find_.Not_found) throw Err_.new_wo_type("eval_rhs_bgn_invalid: could not find eval_rhs_bgn", "snip", String_.new_u8(fmt, eval_lhs_end, fmt_len)); + byte[] eval_cmd = Bry_.Mid(fmt, eval_lhs_end + Byte_ascii.Len_1, eval_rhs_bgn); + byte[] eval_rslt = eval_mgr.Eval(eval_cmd); + int eval_rhs_end = eval_rhs_bgn + Byte_ascii.Len_1 + eval_dlm.length; + if (eval_rslt == null) eval_rslt = Bry_.Mid(fmt, eval_lhs_bgn - 2, eval_rhs_end); // not found; return original argument + list.Add(Bry_fmtr_itm.dat_bry_(eval_rslt)); + return eval_rhs_end; + } + static final String GRP_KEY = "gplx.Bry_fmtr"; + public boolean Fmt_args_exist() {return fmt_args_exist;} private boolean fmt_args_exist; + boolean dirty = true; + int baseInt = 0; + public static final byte char_escape = Byte_ascii.Tilde, char_arg_bgn = Byte_ascii.Curly_bgn, char_arg_end = Byte_ascii.Curly_end, char_escape_nl = Byte_ascii.Ltr_n, char_escape_tab = Byte_ascii.Ltr_t, char_eval_bgn = Byte_ascii.Lt, char_eval_end = Byte_ascii.Gt; + public static final Bry_fmtr Null = new Bry_fmtr().Fmt_(""); + public static Bry_fmtr New__tmp() {return new Bry_fmtr().Fmt_("").Keys_();} + public static Bry_fmtr new_(String fmt, String... keys) {return new Bry_fmtr().Fmt_(fmt).Keys_(keys);} // NOTE: keys may seem redundant, but are needed to align ordinals with proc; EX: fmt may be "~{A} ~{B}" or "~{B} ~{A}"; call will always be Bld(a, b); passing in "A", "B" guarantees A is 0 and B is 1; + public static Bry_fmtr new_(byte[] fmt, String... keys) {return new Bry_fmtr().Fmt_(fmt).Keys_(keys);} // NOTE: keys may seem redundant, but are needed to align ordinals with proc; EX: fmt may be "~{A} ~{B}" or "~{B} ~{A}"; call will always be Bld(a, b); passing in "A", "B" guarantees A is 0 and B is 1; + public static Bry_fmtr new_() {return new Bry_fmtr();} + public static Bry_fmtr keys_(String... keys) {return new Bry_fmtr().Keys_(keys);} + public static Bry_fmtr new_bry_(byte[] fmt, String... keys) {return new Bry_fmtr().Fmt_(fmt).Keys_(keys);} + public static String New_fmt_str(String key, Object[] args) { + tmp_bfr.Clear(); + tmp_bfr.Add_str_u8(key); + tmp_bfr.Add_byte(Byte_ascii.Colon); + int args_len = args.length; + for (int i = 0; i < args_len; i++) { // add " 0='~{0}'" + tmp_bfr.Add_byte(Byte_ascii.Space); + tmp_bfr.Add_int_variable(i); + tmp_bfr.Add_byte(Byte_ascii.Eq); + tmp_bfr.Add_byte(Byte_ascii.Apos); + tmp_bfr.Add_byte(Byte_ascii.Tilde); + tmp_bfr.Add_byte(Byte_ascii.Curly_bgn); + tmp_bfr.Add_int_variable(i); + tmp_bfr.Add_byte(Byte_ascii.Curly_end); + tmp_bfr.Add_byte(Byte_ascii.Apos); + } + return tmp_bfr.To_str_and_clear(); + } static Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); + public void Bld_bfr_many_and_set_fmt(Object... args) { + Bry_bfr bfr = Bry_bfr_.New(); + this.Bld_bfr_many(bfr, args); + byte[] bry = bfr.To_bry_and_clear(); + this.Fmt_(bry).Compile(); + } + public static String Escape_tilde(String v) {return String_.Replace(v, "~", "~~");} +} diff --git a/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_eval_mgr.java b/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_eval_mgr.java index a27517de8..47f6f87b4 100644 --- a/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_eval_mgr.java +++ b/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_eval_mgr.java @@ -13,3 +13,8 @@ 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.core.brys.fmtrs; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +public interface Bry_fmtr_eval_mgr { + boolean Enabled(); void Enabled_(boolean v); + byte[] Eval(byte[] cmd); +} diff --git a/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_eval_mgr_.java b/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_eval_mgr_.java index a27517de8..3cd3fcc3c 100644 --- a/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_eval_mgr_.java +++ b/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_eval_mgr_.java @@ -13,3 +13,13 @@ 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.core.brys.fmtrs; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +public class Bry_fmtr_eval_mgr_ { + public static Io_url Eval_url(Bry_fmtr_eval_mgr eval_mgr, byte[] fmt) { + if (eval_mgr == null) return Io_url_.new_any_(String_.new_u8(fmt)); + Bry_bfr bfr = Bry_bfr_.Reset(255); + Bry_fmtr fmtr = Bry_fmtr.New__tmp(); + fmtr.Eval_mgr_(eval_mgr).Fmt_(fmt).Bld_bfr_none(bfr); + return Io_url_.new_any_(bfr.To_str_and_clear()); + } +} diff --git a/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_eval_mgr_gfs.java b/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_eval_mgr_gfs.java index a27517de8..d678315ab 100644 --- a/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_eval_mgr_gfs.java +++ b/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_eval_mgr_gfs.java @@ -13,3 +13,12 @@ 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.core.brys.fmtrs; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +import gplx.langs.gfs.*; +public class Bry_fmtr_eval_mgr_gfs implements Bry_fmtr_eval_mgr { + public boolean Enabled() {return enabled;} public void Enabled_(boolean v) {enabled = v;} private boolean enabled; + public byte[] Eval(byte[] cmd) { + return enabled ? Bry_.new_u8(Object_.Xto_str_strict_or_null_mark(GfsCore.Instance.ExecText(String_.new_u8(cmd)))) : null; + } + public static final Bry_fmtr_eval_mgr_gfs Instance = new Bry_fmtr_eval_mgr_gfs(); Bry_fmtr_eval_mgr_gfs() {} +} diff --git a/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_itm.java b/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_itm.java index a27517de8..178688123 100644 --- a/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_itm.java +++ b/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_itm.java @@ -13,3 +13,19 @@ 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.core.brys.fmtrs; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +public class Bry_fmtr_itm { + Bry_fmtr_itm(boolean arg, int argIdx, byte[] dat) { + this.Arg = arg; this.ArgIdx = argIdx; this.Dat = dat; + } + public boolean Arg; + public int ArgIdx; + public byte[] Dat; + public String DatStr() { + if (datStr == null) datStr = String_.new_u8(Dat); + return datStr; + } String datStr; + public static Bry_fmtr_itm arg_(int idx) {return new Bry_fmtr_itm(true, idx, Bry_.Empty);} + public static Bry_fmtr_itm dat_(byte[] dat, int len) {return new Bry_fmtr_itm(false, -1, Bry_.Mid(dat, 0, len));} + public static Bry_fmtr_itm dat_bry_(byte[] bry) {return new Bry_fmtr_itm(false, -1, bry);} +} diff --git a/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_tst.java b/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_tst.java index a27517de8..c03461a6c 100644 --- a/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_tst.java +++ b/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_tst.java @@ -13,3 +13,60 @@ 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.core.brys.fmtrs; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +import org.junit.*; +public class Bry_fmtr_tst { + private final Bry_fmtr_fxt fxt = new Bry_fmtr_fxt(); + @Test public void Text() {fxt.Clear().Fmt("a").Test("a");} + @Test public void Idx__1() {fxt.Clear().Fmt("~{0}").Args("a").Test("a");} + @Test public void Idx__3() {fxt.Clear().Fmt("~{0}~{1}~{2}").Args("a", "b", "c").Test("abc");} + @Test public void Idx__mix() {fxt.Clear().Fmt("a~{0}c~{1}e").Args("b", "d").Test("abcde");} + @Test public void Idx__missing() {fxt.Clear().Fmt("~{0}").Test("~{0}");} + + @Test public void Key__basic() {fxt.Clear().Fmt("~{key}").Keys("key").Args("a").Test("a");} + @Test public void Key__mult() {fxt.Clear().Fmt("~{key1}~{key2}").Keys("key1", "key2").Args("a", "b").Test("ab");} + @Test public void Key__repeat() {fxt.Clear().Fmt("~{key1}~{key1}").Keys("key1").Args("a").Test("aa");} + + @Test public void Mix() {fxt.Clear().Fmt("~{key1}~{1}").Keys("key1", "key2").Args("a", "b").Test("ab");} + + @Test public void Simple() { + fxt.Clear().Fmt("0~{key1}1~{key2}2").Keys("key1", "key2").Args(".", ",").Test("0.1,2"); + } + @Test public void Cmd() { + Bry_fmtr_tst_mok mok = new Bry_fmtr_tst_mok(); + Bry_fmtr fmtr = Bry_fmtr.new_("0~{key1}2~{<>3<>}4", "key1").Eval_mgr_(mok); + Tfds.Eq("012~{<>3<>}4", fmtr.Bld_str_many("1")); + mok.Enabled_(true); + Tfds.Eq("01234", fmtr.Bld_str_many("1")); + } + @Test public void Bld_bfr_many_and_set_fmt() { + fxt.Bld_bfr_many_and_set_fmt("a~{0}c", Object_.Ary("b"), "abc"); + } + @Test public void Escape_tilde() { + Tfds.Eq("~~~~~~", Bry_fmtr.Escape_tilde("~~~")); + } +} +class Bry_fmtr_tst_mok implements Bry_fmtr_eval_mgr { + public boolean Enabled() {return enabled;} public void Enabled_(boolean v) {enabled = v;} private boolean enabled; + public byte[] Eval(byte[] cmd) { + return enabled ? cmd : null; + } +} +class Bry_fmtr_fxt { + private final Bry_fmtr fmtr = Bry_fmtr.new_(); + private final Bry_bfr bfr = Bry_bfr_.New(); + private Object[] args; + public Bry_fmtr_fxt Clear() {fmtr.Fmt_(String_.Empty).Keys_(String_.Empty); args = Object_.Ary_empty; return this;} + public Bry_fmtr_fxt Fmt (String fmt) {fmtr.Fmt_(fmt); return this;} + public Bry_fmtr_fxt Keys(String... args) {fmtr.Keys_(args); return this;} + public Bry_fmtr_fxt Args(Object... args) {this.args = args; return this;} + public void Test(String expd) { + fmtr.Bld_bfr_many(bfr, args); + Tfds.Eq(expd, bfr.To_str_and_clear()); + } + public void Bld_bfr_many_and_set_fmt(String fmt, Object[] args, String expd) { + fmtr.Fmt_(fmt); + fmtr.Bld_bfr_many_and_set_fmt(args); + Tfds.Eq(expd, String_.new_a7(fmtr.Fmt())); + } +} diff --git a/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_vals.java b/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_vals.java index a27517de8..4c7235e8b 100644 --- a/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_vals.java +++ b/100_core/src/gplx/core/brys/fmtrs/Bry_fmtr_vals.java @@ -13,3 +13,18 @@ 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.core.brys.fmtrs; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +import gplx.core.brys.*; +public class Bry_fmtr_vals implements Bfr_arg { + private final Bry_fmtr fmtr; private Object[] vals; + Bry_fmtr_vals(Bry_fmtr fmtr) {this.fmtr = fmtr;} + public Bry_fmtr_vals Vals_(Object... v) {this.vals = v; return this;} + public void Bfr_arg__add(Bry_bfr bfr) { + fmtr.Bld_bfr_ary(bfr, vals); + } + public static Bry_fmtr_vals new_fmt(String fmt, String... keys) { + Bry_fmtr fmtr = Bry_fmtr.new_(fmt, keys); + return new Bry_fmtr_vals(fmtr); + } + public static Bry_fmtr_vals new_(Bry_fmtr fmtr) {return new Bry_fmtr_vals(fmtr);} +} diff --git a/100_core/src/gplx/core/brys/fmts/Bfr_fmt_arg.java b/100_core/src/gplx/core/brys/fmts/Bfr_fmt_arg.java index a27517de8..c0e503fb5 100644 --- a/100_core/src/gplx/core/brys/fmts/Bfr_fmt_arg.java +++ b/100_core/src/gplx/core/brys/fmts/Bfr_fmt_arg.java @@ -13,3 +13,10 @@ 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.core.brys.fmts; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +public class Bfr_fmt_arg { + public Bfr_fmt_arg(byte[] key, Bfr_arg arg) {this.Key = key; this.Arg = arg;} + public byte[] Key; + public Bfr_arg Arg; + public static final Bfr_fmt_arg[] Ary_empty = new Bfr_fmt_arg[0]; +} diff --git a/100_core/src/gplx/core/brys/fmts/Bry_fmt_itm.java b/100_core/src/gplx/core/brys/fmts/Bry_fmt_itm.java index a27517de8..97aaf003f 100644 --- a/100_core/src/gplx/core/brys/fmts/Bry_fmt_itm.java +++ b/100_core/src/gplx/core/brys/fmts/Bry_fmt_itm.java @@ -13,3 +13,18 @@ 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.core.brys.fmts; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +public class Bry_fmt_itm { + public Bry_fmt_itm(int tid, int src_bgn, int src_end) { + this.Tid = tid; + this.Src_bgn = src_bgn; + this.Src_end = src_end; + } + public int Tid; + public int Src_bgn; + public int Src_end; + public int Key_idx; + public Bfr_arg Arg; + + public static final int Tid__txt = 0, Tid__key = 1, Tid__arg = 2; +} diff --git a/100_core/src/gplx/core/brys/fmts/Bry_fmt_parser_.java b/100_core/src/gplx/core/brys/fmts/Bry_fmt_parser_.java index a27517de8..b95dba82a 100644 --- a/100_core/src/gplx/core/brys/fmts/Bry_fmt_parser_.java +++ b/100_core/src/gplx/core/brys/fmts/Bry_fmt_parser_.java @@ -13,3 +13,79 @@ 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.core.brys.fmts; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +public class Bry_fmt_parser_ { + public static Bry_fmt_itm[] Parse(byte escape, byte grp_bgn, byte grp_end, Bfr_fmt_arg[] args, byte[][] keys, byte[] src) { + int src_len = src.length; + int pos = 0; + int txt_bgn = -1; + int key_idx = -1; + Hash_adp_bry keys_hash = Hash_adp_bry.cs(); + List_adp list = List_adp_.New(); + while (true) { + boolean is_last = pos == src_len; + byte b = is_last ? escape : src[pos]; + if (b == escape) { + if (txt_bgn != -1) list.Add(new Bry_fmt_itm(Bry_fmt_itm.Tid__txt, txt_bgn, pos)); + if (is_last) break; + ++pos; + if (pos == src_len) throw Err_.new_("bry_fmtr", "fmt cannot end with escape", "escape", Byte_ascii.To_str(escape), "raw", src); + b = src[pos]; + if (b == escape) { + list.Add(new Bry_fmt_itm(Bry_fmt_itm.Tid__txt, pos, pos + 1)); + ++pos; + } + else if (b == grp_bgn) { + ++pos; + int grp_end_pos = Bry_find_.Find_fwd(src, grp_end, pos); if (grp_end_pos == Bry_find_.Not_found) throw Err_.new_("bry_fmtr", "grp_end missing", "grp_bgn", Byte_ascii.To_str(grp_bgn), "grp_end", Byte_ascii.To_str(grp_end), "raw", src); + byte[] key_bry = Bry_.Mid(src, pos, grp_end_pos); + Bry_fmt_itm key_itm = (Bry_fmt_itm)keys_hash.Get_by_bry(key_bry); + if (key_itm == null) { + key_itm = new Bry_fmt_itm(Bry_fmt_itm.Tid__key, pos - 2, grp_end_pos + 1); // -2 to get "~{"; +1 to get "}" + key_itm.Key_idx = ++key_idx; + keys_hash.Add(key_bry, key_itm); + } + list.Add(key_itm); + pos = grp_end_pos + 1; + } + else throw Err_.new_("bry_fmtr", "escape must be followed by escape or group_bgn", "escape", Byte_ascii.To_str(escape), "group_bgn", Byte_ascii.To_str(escape), "raw", src); + txt_bgn = -1; + } + else { + if (txt_bgn == -1) txt_bgn = pos; + ++pos; + } + } + Bry_fmt_itm[] rv = (Bry_fmt_itm[])list.To_ary_and_clear(Bry_fmt_itm.class); + int len = args.length; + for (int i = 0; i < len; ++i) { + Bfr_fmt_arg arg = args[i]; + Bry_fmt_itm key_itm = (Bry_fmt_itm)keys_hash.Get_by(arg.Key); if (key_itm == null) continue; + key_itm.Tid = Bry_fmt_itm.Tid__arg; + key_itm.Arg = arg.Arg; + } + len = keys.length; + for (int i = 0; i < len; ++i) { + byte[] key = keys[i]; + Bry_fmt_itm key_itm = (Bry_fmt_itm)keys_hash.Get_by(key); if (key_itm == null) continue; // NOTE: ignore missing keys; EX: fmt=a~{b}c keys=b,d; do not fail b/c ~{d} is not in fmt; allows redefining from tests + key_itm.Key_idx = i; + } + return rv; + } + public static byte[][] Parse_keys(byte[] src) { + Ordered_hash list = Ordered_hash_.New_bry(); + int src_len = src.length; + int pos = -1; + while (pos < src_len) { + int lhs_pos = Bry_find_.Move_fwd(src, Bry_arg_lhs, pos + 1, src_len); + if (lhs_pos == Bry_find_.Not_found) break; // no more "~{" + int rhs_pos = Bry_find_.Find_fwd(src, Byte_ascii.Curly_end, lhs_pos, src_len); + if (rhs_pos == Bry_find_.Not_found) throw Err_.new_("bry_fmt", "unable to find closing }", "src", src); + if (rhs_pos - lhs_pos == 0) throw Err_.new_("bry_fmt", "{} will result in empty key", "src", src); + byte[] key = Bry_.Mid(src, lhs_pos, rhs_pos); + if (!list.Has(key)) list.Add(key, key); + pos = rhs_pos; // NOTE: auto-increment done at top of loop + } + return (byte[][])list.To_ary(byte[].class); + } private static final byte[] Bry_arg_lhs = Bry_.new_a7("~{"); +} diff --git a/100_core/src/gplx/core/brys/fmts/Bry_fmt_tst.java b/100_core/src/gplx/core/brys/fmts/Bry_fmt_tst.java index a27517de8..119b8b5f6 100644 --- a/100_core/src/gplx/core/brys/fmts/Bry_fmt_tst.java +++ b/100_core/src/gplx/core/brys/fmts/Bry_fmt_tst.java @@ -13,3 +13,38 @@ 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.core.brys.fmts; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +import org.junit.*; +public class Bry_fmt_tst { + private final Bry_fmt_fxt fxt = new Bry_fmt_fxt(); + @Test public void Text() {fxt.Clear().Fmt("a").Test("a");} + @Test public void Key__basic() {fxt.Clear().Fmt("~{key}").Vals("a").Test("a");} + @Test public void Key__mult() {fxt.Clear().Fmt("~{key1}~{key2}").Vals("a", "b").Test("ab");} + @Test public void Key__repeat() {fxt.Clear().Fmt("~{key1}~{key1}").Vals("a").Test("aa");} + @Test public void Key__missing() {fxt.Clear().Fmt("~{key}").Test("~{key}");} + @Test public void Tilde() {fxt.Clear().Fmt("~~~~").Test("~~");} + @Test public void Simple() {fxt.Clear().Fmt("0~{key1}1~{key2}2").Vals(".", ",").Test("0.1,2");} + @Test public void Arg() {fxt.Clear().Fmt("~{custom}").Args("custom", new Bfr_fmt_arg_mok(123)).Test("123");} + @Test public void Keys() {fxt.Clear().Fmt("~{b}~{c}~{a}").Keys("a", "b", "c").Vals("a", "b", "c").Test("bca");} +} +class Bfr_fmt_arg_mok implements Bfr_arg { + private int num; + public Bfr_fmt_arg_mok(int num) {this.num = num;} + public void Bfr_arg__add(Bry_bfr bfr) { + bfr.Add_int_variable(num); + } +} +class Bry_fmt_fxt { + private final Bry_fmt fmt = new Bry_fmt(Bry_.Empty, Bry_.Ary_empty, Bfr_fmt_arg.Ary_empty); + private final Bry_bfr bfr = Bry_bfr_.New(); + private Object[] vals; + public Bry_fmt_fxt Clear() {vals = Object_.Ary_empty; return this;} + public Bry_fmt_fxt Fmt(String s) {fmt.Fmt_(s); return this;} + public Bry_fmt_fxt Vals(Object... vals) {this.vals = vals; return this;} + public Bry_fmt_fxt Args(String key, Bfr_arg arg) {fmt.Args_(new Bfr_fmt_arg(Bry_.new_u8(key), arg)); return this;} + public Bry_fmt_fxt Keys(String... keys) {fmt.Keys_(Bry_.Ary(keys)); return this;} + public void Test(String expd) { + fmt.Bld_many(bfr, vals); + Tfds.Eq(expd, bfr.To_str_and_clear()); + } +} diff --git a/100_core/src/gplx/core/brys/fmts/Bry_keys_parser_tst.java b/100_core/src/gplx/core/brys/fmts/Bry_keys_parser_tst.java index a27517de8..862f31f8f 100644 --- a/100_core/src/gplx/core/brys/fmts/Bry_keys_parser_tst.java +++ b/100_core/src/gplx/core/brys/fmts/Bry_keys_parser_tst.java @@ -13,3 +13,18 @@ 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.core.brys.fmts; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +import org.junit.*; +public class Bry_keys_parser_tst { + private final Bry_keys_parser_fxt fxt = new Bry_keys_parser_fxt(); + @Test public void None() {fxt.Test("a");} + @Test public void One() {fxt.Test("~{a}" , "a");} + @Test public void Many() {fxt.Test("~{a}b~{c}d~{e}" , "a", "c", "e");} + @Test public void Dupe() {fxt.Test("~{a}b~{a}" , "a");} + @Test public void Bug__space() {fxt.Test("~{a}~{b} ~{c}" , "a", "b", "c");} // DATE:2016-08-02 +} +class Bry_keys_parser_fxt { + public void Test(String fmt, String... expd) { + Tfds.Eq_ary(expd, String_.Ary(Bry_fmt_parser_.Parse_keys(Bry_.new_u8(fmt)))); + } +} diff --git a/100_core/src/gplx/core/btries/Btrie_bwd_mgr.java b/100_core/src/gplx/core/btries/Btrie_bwd_mgr.java index a27517de8..b805124bd 100644 --- a/100_core/src/gplx/core/btries/Btrie_bwd_mgr.java +++ b/100_core/src/gplx/core/btries/Btrie_bwd_mgr.java @@ -13,3 +13,106 @@ 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.core.btries; import gplx.*; import gplx.core.*; +import gplx.core.primitives.*; +public class Btrie_bwd_mgr { + public int Match_pos() {return match_pos;} private int match_pos; + public Object Match_exact(byte[] src, int bgn_pos, int end_pos) { + Object rv = Match(src[bgn_pos], src, bgn_pos, end_pos); + return rv == null ? null : match_pos - bgn_pos == end_pos - bgn_pos ? rv : null; + } + + public Object Match_at(Btrie_rv rv, byte[] src, int bgn_pos, int end_pos) {return Match_at_w_b0(rv, src[bgn_pos], src, bgn_pos, end_pos);} + public Object Match_at_w_b0(Btrie_rv rv, byte b, byte[] src, int bgn_pos, int end_pos) { + // NOTE: bgn, end follows same semantics as fwd where bgn >= & end < except reversed: bgn <= & end >; EX: "abcde" should pass 5, -1 + Object rv_obj = null; + int rv_pos = bgn_pos; + int cur_pos = bgn_pos; + Btrie_slim_itm cur = root; + while (true) { + Btrie_slim_itm nxt = cur.Ary_find(b); + if (nxt == null) { // nxt does not have b; return rv_obj; + rv.Init(rv_pos, rv_obj); + return rv_obj; + } + --cur_pos; + if (nxt.Ary_is_empty()) { // nxt is leaf; return nxt.Val() (which should be non-null) + rv_obj = nxt.Val(); + rv.Init(cur_pos, rv_obj); + return rv_obj; + } + Object nxt_val = nxt.Val(); + if (nxt_val != null) {rv_pos = cur_pos; rv_obj = nxt_val;} // nxt is node; cache rv_obj (in case of false match) + if (cur_pos == end_pos) { // increment cur_pos and exit if end_pos + rv.Init(rv_pos, rv_obj); + return rv_obj; + } + b = src[cur_pos]; + cur = nxt; + } + } + + public Object Match_bgn(byte[] src, int bgn_pos, int end_pos) {return Match(src[bgn_pos], src, bgn_pos, end_pos);} + public Object Match(byte b, byte[] src, int bgn_pos, int end_pos) { + // NOTE: bgn, end follows same semantics as fwd where bgn >= & end < except reversed: bgn <= & end >; EX: "abcde" should pass 5, -1 + Object rv = null; int cur_pos = match_pos = bgn_pos; + Btrie_slim_itm cur = root; + while (true) { + Btrie_slim_itm nxt = cur.Ary_find(b); if (nxt == null) return rv; // nxt does not hav b; return rv; + --cur_pos; + if (nxt.Ary_is_empty()) {match_pos = cur_pos; return nxt.Val();} // nxt is leaf; return nxt.Val() (which should be non-null) + Object nxt_val = nxt.Val(); + if (nxt_val != null) {match_pos = cur_pos; rv = nxt_val;} // nxt is node; cache rv (in case of false match) + if (cur_pos == end_pos) return rv; // increment cur_pos and exit if src_len + b = src[cur_pos]; + cur = nxt; + } + } + public Btrie_bwd_mgr Add_str_byte(String key, byte val) {return Add(Bry_.new_u8(key), Byte_obj_val.new_(val));} + public Btrie_bwd_mgr Add_byteVal_strAry(byte val, String... ary) { + int ary_len = ary.length; + Byte_obj_val byteVal = Byte_obj_val.new_(val); + for (int i = 0; i < ary_len; i++) { + String itm = ary[i]; + Add(Bry_.new_u8(itm), byteVal); + } + return this; + } + public Btrie_bwd_mgr Add(String key, Object val) {return Add(Bry_.new_u8(key), val);} + public Btrie_bwd_mgr Add(byte[] key, Object val) { + if (val == null) throw Err_.new_wo_type("null objects cannot be registered", "key", String_.new_u8(key)); + int key_len = key.length; + Btrie_slim_itm cur = root; + for (int i = key_len - 1; i > -1; i--) { + byte b = key[i]; + if (root.Case_any() && (b > 64 && b < 91)) b += 32; + Btrie_slim_itm nxt = cur.Ary_find(b); + if (nxt == null) + nxt = cur.Ary_add(b, null); + if (i == 0) + nxt.Val_set(val); + cur = nxt; + } + count++; // FUTURE: do not increment if replacing value + return this; + } + public int Count() {return count;} private int count; + public void Del(byte[] key) { + int key_len = key.length; + Btrie_slim_itm cur = root; + for (int i = 0; i < key_len; i++) { + byte b = key[i]; + cur = cur.Ary_find(b); + if (cur == null) break; + cur.Ary_del(b); + } + count--; // FUTURE: do not decrement if not found + } + public void Clear() {root.Clear(); count = 0;} + public static Btrie_bwd_mgr cs_() {return new Btrie_bwd_mgr(false);} + public static Btrie_bwd_mgr ci_() {return new Btrie_bwd_mgr(true);} + public static Btrie_bwd_mgr c__(boolean cs) {return new Btrie_bwd_mgr(!cs);} + public Btrie_bwd_mgr(boolean caseAny) { + root = new Btrie_slim_itm(Byte_.Zero, null, caseAny); + } private Btrie_slim_itm root; +} diff --git a/100_core/src/gplx/core/btries/Btrie_bwd_mgr_tst.java b/100_core/src/gplx/core/btries/Btrie_bwd_mgr_tst.java index a27517de8..11d4e4e03 100644 --- a/100_core/src/gplx/core/btries/Btrie_bwd_mgr_tst.java +++ b/100_core/src/gplx/core/btries/Btrie_bwd_mgr_tst.java @@ -13,3 +13,73 @@ 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.core.btries; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Btrie_bwd_mgr_tst { + @Before public void init() {} private Btrie_bwd_mgr trie; + private void ini_setup1() { + trie = new Btrie_bwd_mgr(false); + run_Add("c" , 1); + run_Add("abc" , 123); + } + @Test public void Get_by() { + ini_setup1(); + tst_MatchAtCur("c" , 1); + tst_MatchAtCur("abc" , 123); + tst_MatchAtCur("bc" , 1); + tst_MatchAtCur("yzabc" , 123); + tst_MatchAtCur("ab" , null); + } + @Test public void Fetch_intl() { + trie = new Btrie_bwd_mgr(false); + run_Add("a�", 1); + tst_MatchAtCur("a�" , 1); + tst_MatchAtCur("�" , null); + } + @Test public void Eos() { + ini_setup1(); + tst_Match("ab", Byte_ascii.Ltr_c, 2, 123); + } + @Test public void Match_exact() { + ini_setup1(); + tst_MatchAtCurExact("c", 1); + tst_MatchAtCurExact("bc", null); + tst_MatchAtCurExact("abc", 123); + } + private void ini_setup2() { + trie = new Btrie_bwd_mgr(false); + run_Add("a" , 1); + run_Add("b" , 2); + } + @Test public void Match_2() { + ini_setup2(); + tst_MatchAtCur("a", 1); + tst_MatchAtCur("b", 2); + } + private void ini_setup_caseAny() { + trie = Btrie_bwd_mgr.ci_(); + run_Add("a" , 1); + run_Add("b" , 2); + } + @Test public void CaseAny() { + ini_setup_caseAny(); + tst_MatchAtCur("a", 1); + tst_MatchAtCur("A", 1); + } + private void run_Add(String k, int val) {trie.Add(Bry_.new_u8(k), val);} + private void tst_Match(String srcStr, byte b, int bgn_pos, int expd) { + byte[] src = Bry_.new_u8(srcStr); + Object actl = trie.Match(b, src, bgn_pos, -1); + Tfds.Eq(expd, actl); + } + private void tst_MatchAtCur(String srcStr, Object expd) { + byte[] src = Bry_.new_u8(srcStr); + Object actl = trie.Match(src[src.length - 1], src, src.length - 1, -1); + Tfds.Eq(expd, actl); + } + private void tst_MatchAtCurExact(String srcStr, Object expd) { + byte[] src = Bry_.new_u8(srcStr); + Object actl = trie.Match_exact(src, src.length - 1, -1); + Tfds.Eq(expd, actl); + } +} diff --git a/100_core/src/gplx/core/btries/Btrie_fast_mgr.java b/100_core/src/gplx/core/btries/Btrie_fast_mgr.java index a27517de8..d3ea45773 100644 --- a/100_core/src/gplx/core/btries/Btrie_fast_mgr.java +++ b/100_core/src/gplx/core/btries/Btrie_fast_mgr.java @@ -13,3 +13,180 @@ 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.core.btries; import gplx.*; import gplx.core.*; +import gplx.core.primitives.*; +public class Btrie_fast_mgr { + private ByteTrieItm_fast root; + public boolean CaseAny() {return root.CaseAny();} public Btrie_fast_mgr CaseAny_(boolean v) {root.CaseAny_(v); return this;} + public int Match_pos() {return match_pos;} private int match_pos; + + public Object Match_at(Btrie_rv rv, byte[] src, int bgn_pos, int end_pos) {return Match_at_w_b0(rv, src[bgn_pos], src, bgn_pos, end_pos);} + public Object Match_at_w_b0(Btrie_rv rv, byte b, byte[] src, int bgn_pos, int src_end) { + Object rv_obj = null; + int rv_pos = bgn_pos; + ByteTrieItm_fast nxt = root.Ary_find(b); + if (nxt == null) { // nxt does not have b; return rv; + rv.Init(rv_pos, rv_obj); + return rv_obj; + } + int cur_pos = bgn_pos + 1; + ByteTrieItm_fast cur = root; + while (true) { + if (nxt.Ary_is_empty()) { // nxt is leaf; return nxt.Val() (which should be non-null) + rv_obj = nxt.Val(); + rv.Init(cur_pos, rv_obj); + return rv_obj; + } + Object nxt_val = nxt.Val(); + if (nxt_val != null) { // nxt is node; cache rv (in case of false match) + rv_pos = cur_pos; + rv_obj = nxt_val; + } + if (cur_pos == src_end) { // eos; exit + rv.Init(rv_pos, rv_obj); + return rv_obj; + } + b = src[cur_pos]; + cur = nxt; + nxt = cur.Ary_find(b); + if (nxt == null) { + rv.Init(rv_pos, rv_obj); + return rv_obj; + } + ++cur_pos; + } + } + + public Object Match_exact(byte[] src, int bgn_pos, int end_pos) { + Object rv = Match_bgn_w_byte(src[bgn_pos], src, bgn_pos, end_pos); + return rv == null ? null : match_pos - bgn_pos == end_pos - bgn_pos ? rv : null; + } + public Object Match_bgn(byte[] src, int bgn_pos, int end_pos) {return Match_bgn_w_byte(src[bgn_pos], src, bgn_pos, end_pos);} + public Object Match_bgn_w_byte(byte b, byte[] src, int bgn_pos, int src_len) { + match_pos = bgn_pos; + ByteTrieItm_fast nxt = root.Ary_find(b); if (nxt == null) return null; // nxt does not have b; return rv; + Object rv = null; int cur_pos = bgn_pos + 1; + ByteTrieItm_fast cur = root; + while (true) { + if (nxt.Ary_is_empty()) {match_pos = cur_pos; return nxt.Val();} // nxt is leaf; return nxt.Val() (which should be non-null) + Object nxt_val = nxt.Val(); + if (nxt_val != null) {match_pos = cur_pos; rv = nxt_val;} // nxt is node; cache rv (in case of false match) + if (cur_pos == src_len) return rv; // eos; exit + b = src[cur_pos]; + cur = nxt; + nxt = cur.Ary_find(b); if (nxt == null) return rv; + ++cur_pos; + } + } + public Btrie_fast_mgr Add_bry_byte(byte key, byte val) {return Add(new byte[] {key}, Byte_obj_val.new_(val));} + public Btrie_fast_mgr Add_bry_byte(byte[] key, byte val) {return Add(key, Byte_obj_val.new_(val));} + public Btrie_fast_mgr Add_str_byte(String key, byte val) {return Add(Bry_.new_u8(key), Byte_obj_val.new_(val));} + public Btrie_fast_mgr Add(byte key, Object val) {return Add(new byte[] {key}, val);} + public Btrie_fast_mgr Add(String key, Object val) {return Add(Bry_.new_u8(key), val);} + public Btrie_fast_mgr Add(byte[] key, Object val) { + if (val == null) throw Err_.new_wo_type("null objects cannot be registered", "key", String_.new_u8(key)); + int key_len = key.length; int key_end = key_len - 1; + ByteTrieItm_fast cur = root; + for (int i = 0; i < key_len; i++) { + byte b = key[i]; + ByteTrieItm_fast nxt = cur.Ary_find(b); + if (nxt == null) + nxt = cur.Ary_add(b, null); + if (i == key_end) + nxt.Val_set(val); + cur = nxt; + } + return this; + } + public Btrie_fast_mgr Add_stub(byte tid, String s) { + byte[] bry = Bry_.new_u8(s); + Btrie_itm_stub stub = new Btrie_itm_stub(tid, bry); + return Add(bry, stub); + } + public void Del(byte[] key) { + int key_len = key.length; + ByteTrieItm_fast cur = root; + for (int i = 0; i < key_len; i++) { + byte b = key[i]; + Object itm_obj = cur.Ary_find(b); + if (itm_obj == null) break; // b not found; no match; exit; + ByteTrieItm_fast itm = (ByteTrieItm_fast)itm_obj; + if (i == key_len - 1) { // last char + if (itm.Val() == null) break; // itm does not have val; EX: trie with "abc", and "ab" deleted + if (itm.Ary_is_empty()) + cur.Ary_del(b); + else + itm.Val_set(null); + } + else { // mid char; set itm as cur and continue + cur = itm; + } + } + } + public void Clear() {root.Clear();} + public byte[] Replace(Bry_bfr tmp_bfr, byte[] src, int bgn, int end) { + int pos = bgn; + boolean dirty = false; + while (pos < end) { + byte b = src[pos]; + Object o = this.Match_bgn_w_byte(b, src, pos, end); + if (o == null) { + if (dirty) + tmp_bfr.Add_byte(b); + pos++; + } + else { + if (!dirty) { + tmp_bfr.Add_mid(src, bgn, pos); + dirty = true; + } + tmp_bfr.Add((byte[])o); + pos = match_pos; + } + } + return dirty ? tmp_bfr.To_bry_and_clear() : src; + } + public static Btrie_fast_mgr cs() {return new Btrie_fast_mgr(Bool_.N);} + public static Btrie_fast_mgr ci_a7() {return new Btrie_fast_mgr(Bool_.Y);} + public static Btrie_fast_mgr new_(boolean case_any) {return new Btrie_fast_mgr(case_any);} + Btrie_fast_mgr(boolean case_any) { + root = new ByteTrieItm_fast(Byte_.Zero, null, case_any); + } +} +class ByteTrieItm_fast { + private ByteTrieItm_fast[] ary = new ByteTrieItm_fast[256]; + public byte Key_byte() {return key_byte;} private byte key_byte; + public Object Val() {return val;} public void Val_set(Object val) {this.val = val;} Object val; + public boolean Ary_is_empty() {return ary_is_empty;} private boolean ary_is_empty; + public boolean CaseAny() {return case_any;} public ByteTrieItm_fast CaseAny_(boolean v) {case_any = v; return this;} private boolean case_any; + public void Clear() { + val = null; + for (int i = 0; i < 256; i++) { + if (ary[i] != null) { + ary[i].Clear(); + ary[i] = null; + } + } + ary_len = 0; + ary_is_empty = true; + } + public ByteTrieItm_fast Ary_find(byte b) { + int key_byte = (case_any && (b > 64 && b < 91) ? b + 32 : b) & 0xff;// PATCH.JAVA:need to convert to unsigned byte + return ary[key_byte]; + } + public ByteTrieItm_fast Ary_add(byte b, Object val) { + int key_byte = (case_any && (b > 64 && b < 91) ? b + 32 : b) & 0xff;// PATCH.JAVA:need to convert to unsigned byte + ByteTrieItm_fast rv = new ByteTrieItm_fast(b, val, case_any); + ary[key_byte] = rv; + ++ary_len; + ary_is_empty = false; + return rv; + } + public void Ary_del(byte b) { + int key_byte = (case_any && (b > 64 && b < 91) ? b + 32 : b) & 0xff;// PATCH.JAVA:need to convert to unsigned byte + ary[key_byte] = null; + --ary_len; + ary_is_empty = ary_len == 0; + } int ary_len = 0; + public ByteTrieItm_fast(byte key_byte, Object val, boolean case_any) {this.key_byte = key_byte; this.val = val; this.case_any = case_any;} +} diff --git a/100_core/src/gplx/core/btries/Btrie_fast_mgr_tst.java b/100_core/src/gplx/core/btries/Btrie_fast_mgr_tst.java index a27517de8..eeaebc16a 100644 --- a/100_core/src/gplx/core/btries/Btrie_fast_mgr_tst.java +++ b/100_core/src/gplx/core/btries/Btrie_fast_mgr_tst.java @@ -13,3 +13,71 @@ 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.core.btries; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Btrie_fast_mgr_tst { + private Btrie_fast_mgr_fxt fxt = new Btrie_fast_mgr_fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Get_by() { + fxt.Test_matchAtCur("a" , 1); + fxt.Test_matchAtCur("abc" , 123); + fxt.Test_matchAtCur("ab" , 1); + fxt.Test_matchAtCur("abcde" , 123); + fxt.Test_matchAtCur(" a" , null); + } + @Test public void Bos() { + fxt.Test_match("bc", Byte_ascii.Ltr_a, -1, 123); + } + @Test public void Match_exact() { + fxt.Test_matchAtCurExact("a", 1); + fxt.Test_matchAtCurExact("ab", null); + fxt.Test_matchAtCurExact("abc", 123); + } + @Test public void Del_noop__no_match() { + fxt.Exec_del("d"); + fxt.Test_matchAtCurExact("a" , 1); + fxt.Test_matchAtCurExact("abc" , 123); + } + @Test public void Del_noop__partial_match() { + fxt.Exec_del("ab"); + fxt.Test_matchAtCurExact("a" , 1); + fxt.Test_matchAtCurExact("abc" , 123); + } + @Test public void Del_match__long() { + fxt.Exec_del("abc"); + fxt.Test_matchAtCurExact("a" , 1); + fxt.Test_matchAtCurExact("abc" , null); + } + @Test public void Del_match__short() { + fxt.Exec_del("a"); + fxt.Test_matchAtCurExact("a" , null); + fxt.Test_matchAtCurExact("abc" , 123); + } +} +class Btrie_fast_mgr_fxt { + private Btrie_fast_mgr trie; + public void Clear() { + trie = Btrie_fast_mgr.cs(); + Init_add( 1 , Byte_ascii.Ltr_a); + Init_add(123 , Byte_ascii.Ltr_a, Byte_ascii.Ltr_b, Byte_ascii.Ltr_c); + } + public void Init_add(int val, byte... ary) {trie.Add(ary, val);} + public void Test_match(String src_str, byte b, int bgn_pos, int expd) { + byte[] src = Bry_.new_a7(src_str); + Object actl = trie.Match_bgn_w_byte(b, src, bgn_pos, src.length); + Tfds.Eq(expd, actl); + } + public void Test_matchAtCur(String src_str, Object expd) { + byte[] src = Bry_.new_a7(src_str); + Object actl = trie.Match_bgn(src, 0, src.length); + Tfds.Eq(expd, actl); + } + public void Test_matchAtCurExact(String src_str, Object expd) { + byte[] src = Bry_.new_a7(src_str); + Object actl = trie.Match_exact(src, 0, src.length); + Tfds.Eq(expd, actl); + } + public void Exec_del(String src_str) { + trie.Del(Bry_.new_u8(src_str)); + } +} diff --git a/100_core/src/gplx/core/btries/Btrie_itm_stub.java b/100_core/src/gplx/core/btries/Btrie_itm_stub.java index a27517de8..2c71a466f 100644 --- a/100_core/src/gplx/core/btries/Btrie_itm_stub.java +++ b/100_core/src/gplx/core/btries/Btrie_itm_stub.java @@ -13,3 +13,9 @@ 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.core.btries; import gplx.*; import gplx.core.*; +public class Btrie_itm_stub { + public Btrie_itm_stub(byte tid, byte[] val) {this.tid = tid; this.val = val;} + public byte Tid() {return tid;} private byte tid; + public byte[] Val() {return val;} private byte[] val; +} diff --git a/100_core/src/gplx/core/btries/Btrie_mgr.java b/100_core/src/gplx/core/btries/Btrie_mgr.java index a27517de8..73934fe39 100644 --- a/100_core/src/gplx/core/btries/Btrie_mgr.java +++ b/100_core/src/gplx/core/btries/Btrie_mgr.java @@ -13,3 +13,11 @@ 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.core.btries; import gplx.*; import gplx.core.*; +public interface Btrie_mgr { + int Match_pos(); + Object Match_at(Btrie_rv rv, byte[] src, int bgn_pos, int end_pos); + Object Match_bgn(byte[] src, int bgn_pos, int end_pos); + Btrie_mgr Add_obj(String key, Object val); + Btrie_mgr Add_obj(byte[] key, Object val); +} diff --git a/100_core/src/gplx/core/btries/Btrie_rv.java b/100_core/src/gplx/core/btries/Btrie_rv.java index a27517de8..2b816d3cd 100644 --- a/100_core/src/gplx/core/btries/Btrie_rv.java +++ b/100_core/src/gplx/core/btries/Btrie_rv.java @@ -13,3 +13,15 @@ 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.core.btries; import gplx.*; import gplx.core.*; +import gplx.core.threads.poolables.*; +public class Btrie_rv { + public int Match_bgn = -1; + public Object Obj() {return obj;} private Object obj; + public int Pos() {return pos;} private int pos; + public Btrie_rv Init(int pos, Object obj) { + this.obj = obj; + this.pos = pos; + return this; + } +} diff --git a/100_core/src/gplx/core/btries/Btrie_slim_itm.java b/100_core/src/gplx/core/btries/Btrie_slim_itm.java index a27517de8..fd883f432 100644 --- a/100_core/src/gplx/core/btries/Btrie_slim_itm.java +++ b/100_core/src/gplx/core/btries/Btrie_slim_itm.java @@ -13,3 +13,118 @@ 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.core.btries; import gplx.*; import gplx.core.*; +public class Btrie_slim_itm { + private Btrie_slim_itm[] ary = Btrie_slim_itm.Ary_empty; + public Btrie_slim_itm(byte key_byte, Object val, boolean case_any) {this.key_byte = key_byte; this.val = val; this.case_any = case_any;} + public byte Key_byte() {return key_byte;} private byte key_byte; + public Object Val() {return val;} public void Val_set(Object val) {this.val = val;} private Object val; + public boolean Case_any() {return case_any;} private boolean case_any; + public boolean Ary_is_empty() {return ary == Btrie_slim_itm.Ary_empty;} + public void Clear() { + val = null; + for (int i = 0; i < ary_len; i++) + ary[i].Clear(); + ary = Btrie_slim_itm.Ary_empty; + ary_len = ary_max = 0; + } + public Btrie_slim_itm Ary_find(byte b) { + int find_val = (case_any && (b > 64 && b < 91) ? b + 32 : b) & 0xff;// PATCH.JAVA:need to convert to unsigned byte + int key_val = 0; + switch (ary_len) { + case 0: return null; + case 1: + Btrie_slim_itm rv = ary[0]; + key_val = rv.Key_byte() & 0xff;// PATCH.JAVA:need to convert to unsigned byte; + key_val = (case_any && (key_val > 64 && key_val < 91) ? key_val + 32 : key_val); + return key_val == find_val ? rv : null; + default: + int adj = 1; + int prv_pos = 0; + int prv_len = ary_len; + int cur_len = 0; + int cur_idx = 0; + Btrie_slim_itm itm = null; + while (true) { + cur_len = prv_len / 2; + if (prv_len % 2 == 1) ++cur_len; + cur_idx = prv_pos + (cur_len * adj); + if (cur_idx < 0) cur_idx = 0; + else if (cur_idx >= ary_len) cur_idx = ary_len - 1; + itm = ary[cur_idx]; + key_val = itm.Key_byte() & 0xff; // PATCH.JAVA:need to convert to unsigned byte; + key_val = (case_any && (key_val > 64 && key_val < 91) ? key_val + 32 : key_val); + if (find_val < key_val) adj = -1; + else if (find_val > key_val) adj = 1; + else /*(find_val == cur_val)*/ return itm; + if (cur_len == 1) { + cur_idx += adj; + if (cur_idx < 0 || cur_idx >= ary_len) return null; + itm = ary[cur_idx]; + return (itm.Key_byte() & 0xff) == find_val ? itm : null; // PATCH.JAVA:need to convert to unsigned byte; + } + prv_len = cur_len; + prv_pos = cur_idx; + } + } + } + public Btrie_slim_itm Ary_add(byte b, Object val) { + int new_len = ary_len + 1; + if (new_len > ary_max) { + ary_max += 4; + ary = (Btrie_slim_itm[])Array_.Resize(ary, ary_max); + } + Btrie_slim_itm rv = new Btrie_slim_itm(b, val, case_any); + ary[ary_len] = rv; + ary_len = new_len; + synchronized (ByteHashItm_sorter.Instance) {// TS: DATE:2016-07-06 + ByteHashItm_sorter.Instance.Sort(ary, ary_len); + } + return rv; + } + public void Ary_del(byte b) { + boolean found = false; + for (int i = 0; i < ary_len; i++) { + if (found) { + if (i < ary_len - 1) + ary[i] = ary[i + 1]; + } + else { + if (b == ary[i].Key_byte()) found = true; + } + } + if (found) --ary_len; + } + public static final Btrie_slim_itm[] Ary_empty = new Btrie_slim_itm[0]; int ary_len = 0, ary_max = 0; +} +class ByteHashItm_sorter {// quicksort + Btrie_slim_itm[] ary; int ary_len; + public void Sort(Btrie_slim_itm[] ary, int ary_len) { + if (ary == null || ary_len < 2) return; + this.ary = ary; + this.ary_len = ary_len; + Sort_recurse(0, ary_len - 1); + } + private void Sort_recurse(int lo, int hi) { + int i = lo, j = hi; + int mid = ary[lo + (hi-lo)/2].Key_byte()& 0xFF; // get mid itm + while (i <= j) { // divide into two lists + while ((ary[i].Key_byte() & 0xFF) < mid) // if lhs.cur < mid, then get next from lhs + i++; + while ((ary[j].Key_byte() & 0xFF) > mid) // if rhs.cur > mid, then get next from rhs + j--; + + // lhs.cur > mid && rhs.cur < mid; switch lhs.cur and rhs.cur; increase i and j + if (i <= j) { + Btrie_slim_itm tmp = ary[i]; + ary[i] = ary[j]; + ary[j] = tmp; + i++; + j--; + } + } + if (lo < j) Sort_recurse(lo, j); + if (i < hi) Sort_recurse(i, hi); + } + public static final ByteHashItm_sorter Instance = new ByteHashItm_sorter(); ByteHashItm_sorter() {} +} diff --git a/100_core/src/gplx/core/btries/Btrie_slim_itm_tst.java b/100_core/src/gplx/core/btries/Btrie_slim_itm_tst.java index a27517de8..3c0b99d33 100644 --- a/100_core/src/gplx/core/btries/Btrie_slim_itm_tst.java +++ b/100_core/src/gplx/core/btries/Btrie_slim_itm_tst.java @@ -13,3 +13,35 @@ 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.core.btries; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Btrie_slim_itm_tst { + private Btrie_slim_itm itm = new Btrie_slim_itm(Byte_.Zero, null, false); + @Before public void init() {itm.Clear();} + @Test public void Find_nil() { + tst_Find(Byte_ascii.Ltr_a, null); + } + @Test public void Add_one() { + run_Add(Byte_ascii.Ltr_a); + tst_Find(Byte_ascii.Ltr_a, "a"); + } + @Test public void Add_many() { + run_Add(Byte_ascii.Bang, Byte_ascii.Num_0, Byte_ascii.Ltr_a, Byte_ascii.Ltr_B); + tst_Find(Byte_ascii.Ltr_a, "a"); + } + @Test public void Del() { + run_Add(Byte_ascii.Bang, Byte_ascii.Num_0, Byte_ascii.Ltr_a, Byte_ascii.Ltr_B); + tst_Find(Byte_ascii.Ltr_a, "a"); + run_Del(Byte_ascii.Ltr_a); + tst_Find(Byte_ascii.Ltr_a, null); + tst_Find(Byte_ascii.Num_0, "0"); + tst_Find(Byte_ascii.Ltr_B, "B"); + } + private void tst_Find(byte b, String expd) { + Btrie_slim_itm actl_itm = itm.Ary_find(b); + Object actl = actl_itm == null ? null : actl_itm.Val(); + Tfds.Eq(expd, actl); + } + private void run_Add(byte... ary) {for (byte b : ary) itm.Ary_add(b, Char_.To_str((char)b));} + private void run_Del(byte... ary) {for (byte b : ary) itm.Ary_del(b);} +} diff --git a/100_core/src/gplx/core/btries/Btrie_slim_mgr.java b/100_core/src/gplx/core/btries/Btrie_slim_mgr.java index a27517de8..5cdfa6a09 100644 --- a/100_core/src/gplx/core/btries/Btrie_slim_mgr.java +++ b/100_core/src/gplx/core/btries/Btrie_slim_mgr.java @@ -13,3 +13,208 @@ 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.core.btries; import gplx.*; import gplx.core.*; +import gplx.core.primitives.*; import gplx.core.threads.poolables.*; +public class Btrie_slim_mgr implements Btrie_mgr { + Btrie_slim_mgr(boolean case_match) {root = new Btrie_slim_itm(Byte_.Zero, null, !case_match);} private Btrie_slim_itm root; + public int Count() {return count;} private int count; + public int Match_pos() {return match_pos;} private int match_pos; + + public Object Match_at(Btrie_rv rv, byte[] src, int bgn_pos, int end_pos) {return bgn_pos < end_pos ? Match_at_w_b0(rv, src[bgn_pos], src, bgn_pos, end_pos) : null;} // handle out of bounds gracefully; EX: Match_bgn("abc", 3, 3) should return null not fail + public Object Match_at_w_b0(Btrie_rv rv, byte b, byte[] src, int bgn_pos, int src_end) { + Object rv_obj = null; + int rv_pos = bgn_pos; + int cur_pos = bgn_pos; + Btrie_slim_itm cur = root; + while (true) { + Btrie_slim_itm nxt = cur.Ary_find(b); + if (nxt == null) { + rv.Init(rv_pos, rv_obj); // nxt does not have b; return rv_obj; + return rv_obj; + } + ++cur_pos; + if (nxt.Ary_is_empty()) { + rv_obj = nxt.Val(); + rv.Init(cur_pos, rv_obj); // nxt is leaf; return nxt.Val() (which should be non-null) + return rv_obj; + } + Object nxt_val = nxt.Val(); + if (nxt_val != null) {rv_pos = cur_pos; rv_obj = nxt_val;} // nxt is node; cache rv_obj (in case of false match) + if (cur_pos == src_end) { + rv.Init(rv_pos, rv_obj); // increment cur_pos and exit if src_end + return rv_obj; + } + b = src[cur_pos]; + cur = nxt; + } + } + public Object Match_exact(byte[] src) {return src == null ? null : Match_exact(src, 0, src.length);} + public Object Match_exact(byte[] src, int bgn_pos, int end_pos) { + if (bgn_pos == end_pos) return null; // NOTE:handle empty String; DATE:2016-04-21 + Object rv = Match_bgn_w_byte(src[bgn_pos], src, bgn_pos, end_pos); + return rv == null ? null : match_pos - bgn_pos == end_pos - bgn_pos ? rv : null; + } + public Object Match_bgn(byte[] src, int bgn_pos, int end_pos) {return bgn_pos < end_pos ? Match_bgn_w_byte(src[bgn_pos], src, bgn_pos, end_pos) : null;} // handle out of bounds gracefully; EX: Match_bgn("abc", 3, 3) should return null not fail + public Object Match_bgn_w_byte(byte b, byte[] src, int bgn_pos, int src_end) { + Object rv = null; int cur_pos = match_pos = bgn_pos; + Btrie_slim_itm cur = root; + while (true) { + Btrie_slim_itm nxt = cur.Ary_find(b); if (nxt == null) return rv; // nxt does not hav b; return rv; + ++cur_pos; + if (nxt.Ary_is_empty()) {match_pos = cur_pos; return nxt.Val();} // nxt is leaf; return nxt.Val() (which should be non-null) + Object nxt_val = nxt.Val(); + if (nxt_val != null) {match_pos = cur_pos; rv = nxt_val;} // nxt is node; cache rv (in case of false match) + if (cur_pos == src_end) return rv; // increment cur_pos and exit if src_end + b = src[cur_pos]; + cur = nxt; + } + } + public byte Match_byte_or(byte b, byte[] src, int bgn, int end, byte or) { + Object rv_obj = Match_bgn_w_byte(b, src, bgn, end); + return rv_obj == null ? or : ((Byte_obj_val)rv_obj).Val(); + } + public byte Match_byte_or(byte[] src, int bgn, int end, byte or) { + Object rv_obj = Match_bgn(src, bgn, end); + return rv_obj == null ? or : ((Byte_obj_val)rv_obj).Val(); + } + public byte Match_byte_or(Btrie_rv trv, byte b, byte[] src, int bgn, int end, byte or) { + Object rv_obj = Match_at_w_b0(trv, b, src, bgn, end); + return rv_obj == null ? or : ((Byte_obj_val)rv_obj).Val(); + } + public byte Match_byte_or(Btrie_rv trv, byte[] src, int bgn, int end, byte or) { + Object rv_obj = Match_at(trv, src, bgn, end); + return rv_obj == null ? or : ((Byte_obj_val)rv_obj).Val(); + } + public byte Match_byte_or(Btrie_rv trv, byte[] src, byte or) { + Object rv_obj = Match_at(trv, src, 0, src.length); + return rv_obj == null ? or : ((Byte_obj_val)rv_obj).Val(); + } + public Btrie_slim_mgr Add_bry_tid(byte[] bry, byte tid) {return (Btrie_slim_mgr)Add_obj(bry, Byte_obj_val.new_(tid));} + public Btrie_slim_mgr Add_bry_int(byte[] key, int val) {return (Btrie_slim_mgr)Add_obj(key, new Int_obj_val(val));} + public Btrie_slim_mgr Add_str_byte(String key, byte val) {return (Btrie_slim_mgr)Add_obj(Bry_.new_u8(key), Byte_obj_val.new_(val));} + public Btrie_slim_mgr Add_str_int(String key, int val) {return (Btrie_slim_mgr)Add_obj(Bry_.new_u8(key), new Int_obj_val(val));} + public Btrie_slim_mgr Add_bry(String key, String val) {return (Btrie_slim_mgr)Add_obj(Bry_.new_u8(key), Bry_.new_u8(val));} + public Btrie_slim_mgr Add_bry(String key, byte[] val) {return (Btrie_slim_mgr)Add_obj(Bry_.new_u8(key), val);} + public Btrie_slim_mgr Add_bry(byte[] v) {return (Btrie_slim_mgr)Add_obj(v, v);} + public Btrie_slim_mgr Add_str_str(String key, String val) {return (Btrie_slim_mgr)Add_obj(Bry_.new_u8(key), Bry_.new_u8(val));} + public Btrie_slim_mgr Add_bry_bry(byte[] key, byte[] val) {return (Btrie_slim_mgr)Add_obj(key, val);} + public Btrie_slim_mgr Add_bry_byte(byte b, byte val) {return (Btrie_slim_mgr)Add_obj(new byte[] {b}, Byte_obj_val.new_(val));} + public Btrie_slim_mgr Add_bry_byte(byte[] bry, byte val) {return (Btrie_slim_mgr)Add_obj(bry, Byte_obj_val.new_(val));} + public Btrie_slim_mgr Add_str_byte__many(byte val, String... ary) { + int ary_len = ary.length; + Byte_obj_val bval = Byte_obj_val.new_(val); + for (int i = 0; i < ary_len; i++) + Add_obj(Bry_.new_u8(ary[i]), bval); + return this; + } + public Btrie_slim_mgr Add_many_str(String... ary) { + int len = ary.length; + for (int i = 0; i < len; i++) { + byte[] itm = Bry_.new_u8(ary[i]); + Add_obj(itm, itm); + } + return this; + } + public Btrie_slim_mgr Add_many_bry(byte[]... ary) { + int len = ary.length; + for (int i = 0; i < len; i++) { + byte[] itm = ary[i]; + Add_obj(itm, itm); + } + return this; + } + public Btrie_slim_mgr Add_many_int(int val, String... ary) {return Add_many_int(val, Bry_.Ary(ary));} + public Btrie_slim_mgr Add_many_int(int val, byte[]... ary) { + int len = ary.length; + Int_obj_val obj = new Int_obj_val(val); + for (int i = 0; i < len; i++) + Add_obj(ary[i], obj); + return this; + } + public Btrie_slim_mgr Add_ary_byte(byte... ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) { + byte b = ary[i]; + Byte_obj_val bval = Byte_obj_val.new_(b); + Add_obj(Bry_.New_by_byte(b), bval); + } + return this; + } + public Btrie_slim_mgr Add_replace_many(String trg_str, String... src_ary) {return Add_replace_many(Bry_.new_u8(trg_str), src_ary);} + public Btrie_slim_mgr Add_replace_many(byte[] trg_bry, String... src_ary) { + int len = src_ary.length; + for (int i = 0; i < len; i++) + Add_obj(Bry_.new_u8(src_ary[i]), trg_bry); + return this; + } + public Btrie_slim_mgr Add_stub(String key, byte val) {byte[] bry = Bry_.new_u8(key); return (Btrie_slim_mgr)Add_obj(bry, new Btrie_itm_stub(val, bry));} + public Btrie_slim_mgr Add_stubs(byte[][] ary) {return Add_stubs(ary, ary.length);} + public Btrie_slim_mgr Add_stubs(byte[][] ary, int ary_len) { + for (byte i = 0; i < ary_len; i++) { + byte[] bry = ary[i]; + Add_obj(bry, new Btrie_itm_stub(i, bry)); + } + return this; + } + public Btrie_mgr Add_obj(String key, Object val) {return Add_obj(Bry_.new_u8(key), val);} + public Btrie_mgr Add_obj(byte[] key, Object val) { + if (val == null) throw Err_.new_wo_type("null objects cannot be registered", "key", String_.new_u8(key)); + int key_len = key.length; int key_end = key_len - 1; + Btrie_slim_itm cur = root; + for (int i = 0; i < key_len; i++) { + byte b = key[i]; + if (root.Case_any() && (b > 64 && b < 91)) b += 32; + Btrie_slim_itm nxt = cur.Ary_find(b); + if (nxt == null) + nxt = cur.Ary_add(b, null); + if (i == key_end) + nxt.Val_set(val); + cur = nxt; + } + count++; // FUTURE: do not increment if replacing value + return this; + } + public void Del(byte[] key) { + int key_len = key.length; + Btrie_slim_itm cur = root; + for (int i = 0; i < key_len; i++) { + byte b = key[i]; + Btrie_slim_itm nxt = cur.Ary_find(b); + if (nxt == null) break; + Object nxt_val = nxt.Val(); + if (nxt_val == null) // cur is end of chain; remove entry; EX: Abc and at c + cur.Ary_del(b); + else // cur is mid of chain; null out entry + nxt.Val_set(null); + cur = nxt; + } + count--; // FUTURE: do not decrement if not found + } + public byte[] Replace(Bry_bfr tmp_bfr, byte[] src, int bgn, int end) { + int pos = bgn; + boolean dirty = false; + while (pos < end) { + byte b = src[pos]; + Object o = this.Match_bgn_w_byte(b, src, pos, end); + if (o == null) { + if (dirty) + tmp_bfr.Add_byte(b); + pos++; + } + else { + if (!dirty) { + tmp_bfr.Add_mid(src, bgn, pos); + dirty = true; + } + tmp_bfr.Add((byte[])o); + pos = match_pos; + } + } + return dirty ? tmp_bfr.To_bry_and_clear() : src; + } + public void Clear() {root.Clear(); count = 0;} + public static Btrie_slim_mgr cs() {return new Btrie_slim_mgr(Bool_.Y);} + public static Btrie_slim_mgr ci_a7() {return new Btrie_slim_mgr(Bool_.N);} + public static Btrie_slim_mgr ci_u8() {return new Btrie_slim_mgr(Bool_.N);} + public static Btrie_slim_mgr new_(boolean cs) {return new Btrie_slim_mgr(cs);} +} diff --git a/100_core/src/gplx/core/btries/Btrie_slim_mgr_tst.java b/100_core/src/gplx/core/btries/Btrie_slim_mgr_tst.java index a27517de8..9affb847d 100644 --- a/100_core/src/gplx/core/btries/Btrie_slim_mgr_tst.java +++ b/100_core/src/gplx/core/btries/Btrie_slim_mgr_tst.java @@ -13,3 +13,78 @@ 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.core.btries; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Btrie_slim_mgr_tst { + @Before public void init() { + } private Btrie_slim_mgr trie; + private void ini_setup1() { + trie = Btrie_slim_mgr.cs(); + run_Add("a" , 1); + run_Add("abc" , 123); + } + @Test public void Get_by() { + ini_setup1(); + tst_MatchAtCur("a" , 1); + tst_MatchAtCur("abc" , 123); + tst_MatchAtCur("ab" , 1); + tst_MatchAtCur("abcde" , 123); + tst_MatchAtCur(" a" , null); + } + @Test public void Bos() { + ini_setup1(); + tst_Match("bc", Byte_ascii.Ltr_a, -1, 123); + } + @Test public void Match_exact() { + ini_setup1(); + tst_MatchAtCurExact("a", 1); + tst_MatchAtCurExact("ab", null); + tst_MatchAtCurExact("abc", 123); + } + private void ini_setup2() { + trie = Btrie_slim_mgr.cs(); + run_Add("a" , 1); + run_Add("b" , 2); + } + @Test public void Match_2() { + ini_setup2(); + tst_MatchAtCur("a", 1); + tst_MatchAtCur("b", 2); + } + private void ini_setup_caseAny() { + trie = Btrie_slim_mgr.ci_a7(); // NOTE:ci.ascii:test + run_Add("a" , 1); + run_Add("b" , 2); + } + @Test public void CaseAny() { + ini_setup_caseAny(); + tst_MatchAtCur("a", 1); + tst_MatchAtCur("A", 1); + } + @Test public void Del() { + ini_setup1(); + trie.Del(Bry_.new_a7("a")); // delete "a"; "abc" still remains; + tst_MatchAtCur("a" , null); + tst_MatchAtCur("abc" , 123); + + trie.Del(Bry_.new_a7("abc")); + tst_MatchAtCur("abc" , null); + } + + private void run_Add(String k, int val) {trie.Add_obj(Bry_.new_a7(k), val);} + private void tst_Match(String srcStr, byte b, int bgn_pos, int expd) { + byte[] src = Bry_.new_a7(srcStr); + Object actl = trie.Match_bgn_w_byte(b, src, bgn_pos, src.length); + Tfds.Eq(expd, actl); + } + private void tst_MatchAtCur(String srcStr, Object expd) { + byte[] src = Bry_.new_a7(srcStr); + Object actl = trie.Match_bgn_w_byte(src[0], src, 0, src.length); + Tfds.Eq(expd, actl); + } + private void tst_MatchAtCurExact(String srcStr, Object expd) { + byte[] src = Bry_.new_a7(srcStr); + Object actl = trie.Match_exact(src, 0, src.length); + Tfds.Eq(expd, actl); + } +} diff --git a/100_core/src/gplx/core/btries/Btrie_u8_itm.java b/100_core/src/gplx/core/btries/Btrie_u8_itm.java index a27517de8..1d4f2d1c6 100644 --- a/100_core/src/gplx/core/btries/Btrie_u8_itm.java +++ b/100_core/src/gplx/core/btries/Btrie_u8_itm.java @@ -13,3 +13,54 @@ 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.core.btries; import gplx.*; import gplx.core.*; +import gplx.core.intls.*; +class Btrie_u8_itm { + private Hash_adp_bry nxts; + private byte[] asymmetric_bry; + public Btrie_u8_itm(byte[] key, Object val) {this.key = key; this.val = val;} + public byte[] Key() {return key;} private byte[] key; + public Object Val() {return val;} public void Val_set(Object val) {this.val = val;} private Object val; + public boolean Nxts_is_empty() {return nxts == null;} + public void Clear() { + val = null; + nxts.Clear(); + nxts = null; + } + public Btrie_u8_itm Nxts_find(byte[] src, int c_bgn, int c_end, boolean called_by_match) { + if (nxts == null) return null; + Object rv_obj = nxts.Get_by_mid(src, c_bgn, c_end); + if (rv_obj == null) return null; + Btrie_u8_itm rv = (Btrie_u8_itm)rv_obj; + byte[] asymmetric_bry = rv.asymmetric_bry; + if (asymmetric_bry == null) // itm doesn't have asymmetric_bry; note that this is the case for most items + return rv; + else { // itm has asymmetric_bry; EX: "İ" was added to trie, must match "İ" and "i"; + if (called_by_match) { // called by mgr.Match + return + ( Bry_.Eq(src, c_bgn, c_end, rv.key) // key matches src; EX: "aİ" + || Bry_.Eq(src, c_bgn, c_end, rv.asymmetric_bry) // asymmetric_bry matches src; EX: "ai"; note that "aI" won't match + ) + ? rv : null; + } + else { // called by mgr.Add; this means that an asymmetric_itm was already added; happens when "İ" added first and then "I" added next + rv.asymmetric_bry = null; // always null out asymmetric_bry; note that this noops non-asymmetric itms, while making an asymmetric_itm case-insenstivie (matches İ,i,I); see tests + return rv; + } + } + } + public Btrie_u8_itm Nxts_add(Gfo_case_mgr case_mgr, byte[] key, Object val) { + Btrie_u8_itm rv = new Btrie_u8_itm(key, val); + if (nxts == null) nxts = Hash_adp_bry.ci_u8(case_mgr); + nxts.Add_bry_obj(key, rv); + Gfo_case_itm case_itm = case_mgr.Get_or_null(key[0], key, 0, key.length); // get case_item + if (case_itm != null) { // note that case_itm may be null; EX: "__TOC__" and "_" + byte[] asymmetric_bry = case_itm.Asymmetric_bry(); + if (asymmetric_bry != null) { // case_itm has asymmetry_bry; only itms in Xol_case_itm_ that are created with Tid_upper and Tid_lower will be non-null + rv.asymmetric_bry = asymmetric_bry; // set itm to asymmetric_bry; EX: for İ, asymmetric_bry = i +// nxts.Add_bry_obj(asymmetric_bry, rv); // add the asymmetric_bry to the hash; in above example, this allows "i" to match "İ" + } + } + return rv; + } +} diff --git a/100_core/src/gplx/core/btries/Btrie_u8_mgr.java b/100_core/src/gplx/core/btries/Btrie_u8_mgr.java index a27517de8..e9f7d503e 100644 --- a/100_core/src/gplx/core/btries/Btrie_u8_mgr.java +++ b/100_core/src/gplx/core/btries/Btrie_u8_mgr.java @@ -13,3 +13,85 @@ 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.core.btries; import gplx.*; import gplx.core.*; +import gplx.core.intls.*; +public class Btrie_u8_mgr implements Btrie_mgr { + private Btrie_u8_itm root; private Gfo_case_mgr case_mgr; + Btrie_u8_mgr(Gfo_case_mgr case_mgr) { + this.case_mgr = case_mgr; + this.root = new Btrie_u8_itm(Bry_.Empty, null); + } + public int Count() {return count;} private int count; + public Object Match_at(Btrie_rv rv, byte[] src, int bgn_pos, int end_pos) {return Match_at_w_b0(rv, src[bgn_pos], src, bgn_pos, end_pos);} + public Object Match_at_w_b0(Btrie_rv rv, byte b, byte[] src, int bgn_pos, int end_pos) { + Object rv_obj = null; + int rv_pos = bgn_pos; + int cur_pos = bgn_pos; + Btrie_u8_itm cur = root; + while (true) { + int c_len = Utf8_.Len_of_char_by_1st_byte(b); + int c_end = cur_pos + c_len; + Btrie_u8_itm nxt = cur.Nxts_find(src, cur_pos, c_end, true); + if (nxt == null) { + rv.Init(rv_pos, rv_obj); // nxts does not have key; return rv_obj; + return rv_obj; + } + cur_pos = c_end; + if (nxt.Nxts_is_empty()) { // nxt is leaf; return nxt.Val() (which should be non-null) + rv_obj = nxt.Val(); + rv.Init(cur_pos, rv_obj); + return rv_obj; + } + Object nxt_val = nxt.Val(); + if (nxt_val != null) {rv_pos = cur_pos; rv_obj = nxt_val;} // nxt is node; cache rv_obj (in case of false match) + if (cur_pos == end_pos) { // increment cur_pos and exit if end + rv.Init(rv_pos, rv_obj); + return rv_obj; + } + b = src[cur_pos]; + cur = nxt; + } + } + + public int Match_pos() {return match_pos;} private int match_pos; + public Object Match_bgn(byte[] src, int bgn_pos, int end_pos) {return Match_bgn_w_byte(src[bgn_pos], src, bgn_pos, end_pos);} + public Object Match_bgn_w_byte(byte b, byte[] src, int bgn_pos, int end_pos) { + Object rv = null; int cur_pos = match_pos = bgn_pos; + Btrie_u8_itm cur = root; + while (true) { + int c_len = Utf8_.Len_of_char_by_1st_byte(b); + int c_end = cur_pos + c_len; + Btrie_u8_itm nxt = cur.Nxts_find(src, cur_pos, c_end, true); if (nxt == null) return rv; // nxts does not have key; return rv; + cur_pos = c_end; + if (nxt.Nxts_is_empty()) {match_pos = cur_pos; return nxt.Val();} // nxt is leaf; return nxt.Val() (which should be non-null) + Object nxt_val = nxt.Val(); + if (nxt_val != null) {match_pos = cur_pos; rv = nxt_val;} // nxt is node; cache rv (in case of false match) + if (cur_pos == end_pos) return rv; // increment cur_pos and exit if end + b = src[cur_pos]; + cur = nxt; + } + } + public void Clear() {root.Clear(); count = 0;} + public Btrie_mgr Add_obj(String key, Object val) {return Add_obj(Bry_.new_u8(key), val);} + public Btrie_mgr Add_obj(byte[] key, Object val) { + if (val == null) throw Err_.new_wo_type("null objects cannot be registered", "key", String_.new_u8(key)); + int key_len = key.length; + Btrie_u8_itm cur = root; + int c_bgn = 0; + while (c_bgn < key_len) { + byte c = key[c_bgn]; + int c_len = Utf8_.Len_of_char_by_1st_byte(c); + int c_end = c_bgn + c_len; + Btrie_u8_itm nxt = cur.Nxts_find(key, c_bgn, c_end, false); + if (nxt == null) + nxt = cur.Nxts_add(case_mgr, Bry_.Mid(key, c_bgn, c_end), null); + c_bgn = c_end; + if (c_bgn == key_len) + nxt.Val_set(val); + cur = nxt; + } + ++count; + return this; + } + public static Btrie_u8_mgr new_(Gfo_case_mgr case_mgr) {return new Btrie_u8_mgr(case_mgr);} +} diff --git a/100_core/src/gplx/core/consoles/Console_adp.java b/100_core/src/gplx/core/consoles/Console_adp.java index a27517de8..adb5d4c8d 100644 --- a/100_core/src/gplx/core/consoles/Console_adp.java +++ b/100_core/src/gplx/core/consoles/Console_adp.java @@ -13,3 +13,14 @@ 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.core.consoles; import gplx.*; import gplx.core.*; +public interface Console_adp { + boolean Enabled(); // optimization; allows Write to be skipped (since Write may Concat strings or generate arrays) + boolean Canceled_chk(); + int Chars_per_line_max(); void Chars_per_line_max_(int v); + void Write_str(String s); + void Write_fmt_w_nl(String fmt, Object... args); + void Write_tmp(String s); + char Read_key(String msg); + String Read_line(String msg); +} diff --git a/100_core/src/gplx/core/consoles/Console_adp_.java b/100_core/src/gplx/core/consoles/Console_adp_.java index a27517de8..b85c1e69a 100644 --- a/100_core/src/gplx/core/consoles/Console_adp_.java +++ b/100_core/src/gplx/core/consoles/Console_adp_.java @@ -13,3 +13,18 @@ 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.core.consoles; import gplx.*; import gplx.core.*; +public class Console_adp_ { + public static final Console_adp Noop = new Console_adp__noop(); + public static Console_adp__mem Dev() {return new Console_adp__mem();} +} +class Console_adp__noop implements Console_adp { + public boolean Enabled() {return false;} + public boolean Canceled_chk() {return false;} + public int Chars_per_line_max() {return 80;} public void Chars_per_line_max_(int v) {} + public void Write_str(String s) {} + public void Write_fmt_w_nl(String s, Object... args) {} + public void Write_tmp(String s) {} + public char Read_key(String msg) {return '\0';} + public String Read_line(String msg) {return "";} +} diff --git a/100_core/src/gplx/core/consoles/Console_adp__mem.java b/100_core/src/gplx/core/consoles/Console_adp__mem.java index a27517de8..51487bfe0 100644 --- a/100_core/src/gplx/core/consoles/Console_adp__mem.java +++ b/100_core/src/gplx/core/consoles/Console_adp__mem.java @@ -13,3 +13,36 @@ 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.core.consoles; import gplx.*; import gplx.core.*; +public class Console_adp__mem implements Console_adp { + private final List_adp written = List_adp_.New(); + private final Hash_adp ignored = Hash_adp_.New(); + public boolean Enabled() {return true;} + public boolean Canceled_chk() {return false;} + public int Chars_per_line_max() {return 80;} public void Chars_per_line_max_(int v) {} + public Console_adp__mem Ignore_add(String s) {ignored.Add_as_key_and_val(s); return this;} + public void Write_str(String s) {WriteString(s);} + public void Write_fmt_w_nl(String s, Object... args) {WriteString(String_.Format(s, args) + String_.CrLf);} + public void Write_tmp(String s) {WriteString(s);} + public String Read_line(String msg) {return "";} + public char Read_key(String msg) {return '\0';} + public Console_adp__mem CancelWhenTextWritten(String val) { + cancelVal = val; + return this; + } + void WriteString(String s) { + if (ignored.Has(s)) return; + written.Add(s); + if (cancelVal != null && String_.Has(s, cancelVal)) throw Err_.new_wo_type("canceled", "cancel_val", s); + } + String cancelVal; + + public List_adp Written() {return written;} + public void tst_WrittenStr(String... expd) { + String[] actl = new String[written.Count()]; + int actlLength = Array_.Len(actl); + for (int i = 0; i < actlLength; i++) + actl[i] = written.Get_at(i).toString(); + Tfds.Eq_ary(actl, expd); + } +} diff --git a/100_core/src/gplx/core/consoles/Console_adp__sys.java b/100_core/src/gplx/core/consoles/Console_adp__sys.java index a27517de8..f4e094431 100644 --- a/100_core/src/gplx/core/consoles/Console_adp__sys.java +++ b/100_core/src/gplx/core/consoles/Console_adp__sys.java @@ -13,3 +13,53 @@ 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.core.consoles; import gplx.*; import gplx.core.*; +import gplx.core.envs.*; +public class Console_adp__sys implements Console_adp { + private String tmp_txt; + public Console_adp__sys() { + this.backspace_by_bytes = Op_sys.Cur().Tid_is_lnx(); // bash shows UTF8 by default; backspace in bytes, else multi-byte characters don't show; DATE:2014-03-04 + } + public boolean Enabled() {return true;} + public boolean Canceled() {return canceled;} public void Canceled_set(boolean v) {canceled = v;} private boolean canceled = false; + public boolean Canceled_chk() {if (canceled) throw Err_.new_op_canceled(); return canceled;} + public int Chars_per_line_max() {return chars_per_line_max;} public void Chars_per_line_max_(int v) {chars_per_line_max = v;} int chars_per_line_max = 80; + public boolean Backspace_by_bytes() {return backspace_by_bytes;} public Console_adp__sys Backspace_by_bytes_(boolean v) {backspace_by_bytes = v; return this;} private boolean backspace_by_bytes; + public void Write_str(String s) {Clear_tmp(); Write_str_lang(s);} + public void Write_str_w_nl(String s) {Clear_tmp(); Write_str_w_nl_lang(s);} + public void Write_fmt_w_nl(String fmt, Object... args) {Clear_tmp(); Write_str_w_nl_lang(String_.Format(fmt, args));} + public char Read_key(String s) {Write_str(s); return Read_key_lang();} + public String Read_line(String s) {Write_str(s); return Read_line_lang();} + public void Write_tmp(String s) { + Clear_tmp(); + if (String_.Has(s, "\r")) s = String_.Replace(s, "\r", " "); + if (String_.Has(s, "\n")) s = String_.Replace(s, "\n", " "); + if (String_.Len(s) >= chars_per_line_max) s = String_.Mid(s, 0, chars_per_line_max - String_.Len("...") - 1) + "..."; // NOTE: >= and -1 needed b/c line needs to be 1 less than max; ex: default cmd is 80 width, but writing 80 chars will automatically create lineBreak + tmp_txt = s; + Write_str_lang(s); + } + private void Clear_tmp() { + if (tmp_txt == null) return; + if (Env_.Mode_debug()) {Write_str_lang(String_.CrLf); return;} + int count = backspace_by_bytes ? Bry_.new_u8(tmp_txt).length : String_.Len(tmp_txt); + String moveBack = String_.Repeat("\b", count); + this.Write_str_lang(moveBack); // move cursor back to beginning of line + this.Write_str_lang(String_.Repeat(" ", count)); // overwrite tmp_txt with space + this.Write_str_lang(moveBack); // move cursor back to beginning of line (so next Write will start at beginning) + tmp_txt = null; + } + private void Write_str_lang(String s) {System.out.print(s);} + private void Write_str_w_nl_lang(String s) {System.out.println(s);} + private String Read_line_lang() {return System.console() == null ? "" : System.console().readLine();} + private char Read_key_lang() { + String text = Read_line_lang(); + return String_.Len(text) == 0 ? '\0' : String_.CharAt(text, 0); + } + public void Write_str_w_nl_utf8(String s) { + java.io.PrintStream ps; + try {ps = new java.io.PrintStream(System.out, true, "UTF-8");} + catch (java.io.UnsupportedEncodingException e) {throw Err_.new_wo_type("unsupported exception");} + ps.println(s); + } + public static final Console_adp__sys Instance = new Console_adp__sys(); +} diff --git a/100_core/src/gplx/core/criterias/Criteria.java b/100_core/src/gplx/core/criterias/Criteria.java index a27517de8..8a2f06f2f 100644 --- a/100_core/src/gplx/core/criterias/Criteria.java +++ b/100_core/src/gplx/core/criterias/Criteria.java @@ -13,3 +13,10 @@ 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.core.criterias; import gplx.*; import gplx.core.*; +public interface Criteria extends To_str_able { + byte Tid(); + boolean Matches(Object obj); + void Val_from_args(Hash_adp args); + void Val_as_obj_(Object obj); +} diff --git a/100_core/src/gplx/core/criterias/Criteria_.java b/100_core/src/gplx/core/criterias/Criteria_.java index a27517de8..82c73bcdc 100644 --- a/100_core/src/gplx/core/criterias/Criteria_.java +++ b/100_core/src/gplx/core/criterias/Criteria_.java @@ -13,3 +13,39 @@ 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.core.criterias; import gplx.*; import gplx.core.*; +import gplx.core.texts.*; /*RegxPatn_cls_like*/ +public class Criteria_ { + public static final Criteria All = new Criteria_const(true); + public static final Criteria None = new Criteria_const(false); + public static Criteria Not(Criteria arg) {return new Criteria_not(arg);} + public static Criteria And(Criteria lhs, Criteria rhs) {return new Criteria_and(lhs, rhs);} + public static Criteria And_many(Criteria... ary) { + int len = Array_.Len(ary); if (len == 0) throw Err_.new_wo_type("cannot AND 0 criterias;"); + Criteria rv = ary[0]; + for (int i = 1; i < len; i++) + rv = And(rv, ary[i]); + return rv; + } + public static Criteria Or(Criteria lhs, Criteria rhs) {return new Criteria_or(lhs, rhs);} + public static Criteria Or_many(Criteria... ary) { + int len = Array_.Len(ary); if (len == 0) throw Err_.new_wo_type("cannot OR 0 criterias;"); + Criteria rv = ary[0]; + for (int i = 1; i < len; i++) + rv = Or(rv, ary[i]); + return rv; + } + public static Criteria eq_(Object arg) {return new Criteria_eq(false, arg);} + public static Criteria eqn_(Object arg) {return new Criteria_eq(true, arg);} + public static Criteria in_(Object... array) {return new Criteria_in(false, array);} + public static Criteria inn_(Object... array) {return new Criteria_in(true, array);} + public static Criteria lt_(Comparable val) {return new Criteria_comp(CompareAble_.Less, val);} + public static Criteria lte_(Comparable val) {return new Criteria_comp(CompareAble_.Less_or_same, val);} + public static Criteria mt_(Comparable val) {return new Criteria_comp(CompareAble_.More, val);} + public static Criteria mte_(Comparable val) {return new Criteria_comp(CompareAble_.More_or_same, val);} + public static Criteria between_(Comparable lhs, Comparable rhs) {return new Criteria_between(false, lhs, rhs);} + public static Criteria between_(boolean negated, Comparable lhs, Comparable rhs) {return new Criteria_between(negated, lhs, rhs);} + public static Criteria like_(String pattern) {return new Criteria_like(false, RegxPatn_cls_like_.parse(pattern, RegxPatn_cls_like.EscapeDefault));} + public static Criteria liken_(String pattern) {return new Criteria_like(true, RegxPatn_cls_like_.parse(pattern, RegxPatn_cls_like.EscapeDefault));} + public static final byte Tid_custom = 0, Tid_const = 1, Tid_not = 2, Tid_and = 3, Tid_or = 4, Tid_eq = 5, Tid_between = 6, Tid_in = 7, Tid_like = 8, Tid_comp = 9, Tid_wrapper = 10, Tid_iomatch = 11, Tid_db_obj_ary = 12; +} diff --git a/100_core/src/gplx/core/criterias/Criteria_between.java b/100_core/src/gplx/core/criterias/Criteria_between.java index a27517de8..311606a02 100644 --- a/100_core/src/gplx/core/criterias/Criteria_between.java +++ b/100_core/src/gplx/core/criterias/Criteria_between.java @@ -13,3 +13,26 @@ 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.core.criterias; import gplx.*; import gplx.core.*; +public class Criteria_between implements Criteria { + public Criteria_between(boolean neg, Comparable lo, Comparable hi) {this.neg = neg; this.lo = lo; this.hi = hi;} + public byte Tid() {return Criteria_.Tid_between;} + public boolean Neg() {return neg;} private final boolean neg; + public Comparable Lo() {return lo;} private Comparable lo; public void Lo_(Comparable v) {this.lo = v;} + public Comparable Hi() {return hi;} private Comparable hi; public void Hi_(Comparable v) {this.hi = v;} + public void Val_from_args(Hash_adp args) {throw Err_.new_unimplemented();} + public void Val_as_obj_(Object v) { + Object[] ary = (Object[])v; + lo = (Comparable)ary[0]; + hi = (Comparable)ary[1]; + } + public boolean Matches(Object comp_obj) { + Comparable comp = CompareAble_.as_(comp_obj); + int lo_rslt = CompareAble_.Compare_comp(lo, comp); + int hi_rslt = CompareAble_.Compare_comp(hi, comp); + boolean rv = (lo_rslt * hi_rslt) != 1; + return neg ? !rv : rv; + } + + public String To_str() {return String_.Concat_any("BETWEEN ", lo, " AND ", hi);} +} diff --git a/100_core/src/gplx/core/criterias/Criteria_bool_base.java b/100_core/src/gplx/core/criterias/Criteria_bool_base.java index a27517de8..26022e4c6 100644 --- a/100_core/src/gplx/core/criterias/Criteria_bool_base.java +++ b/100_core/src/gplx/core/criterias/Criteria_bool_base.java @@ -13,3 +13,34 @@ 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.core.criterias; import gplx.*; import gplx.core.*; +public abstract class Criteria_bool_base implements Criteria { + @gplx.Internal protected void Ctor(String op_literal, Criteria lhs, Criteria rhs) {this.op_literal = op_literal; this.lhs = lhs; this.rhs = rhs;} + public abstract byte Tid(); + public abstract boolean Matches(Object curVal); + public void Val_from_args(Hash_adp args) {lhs.Val_from_args(args); rhs.Val_from_args(args);} + public void Val_as_obj_(Object v) {throw Err_.new_unimplemented();} + public String To_str() {return String_.Concat(lhs.To_str(), " ", this.op_literal, " ", rhs.To_str());} + public String Op_literal() {return op_literal;} private String op_literal; + public Criteria Lhs() {return lhs;} private Criteria lhs; + public Criteria Rhs() {return rhs;} private Criteria rhs; + public static Criteria_bool_base as_(Object obj) {return obj instanceof Criteria_bool_base ? (Criteria_bool_base)obj : null;} +} +class Criteria_and extends Criteria_bool_base { + public Criteria_and(Criteria lhs, Criteria rhs) {this.Ctor("AND", lhs, rhs);} + @Override public byte Tid() {return Criteria_.Tid_and;} + @Override public boolean Matches(Object curVal) {return this.Lhs().Matches(curVal) && this.Rhs().Matches(curVal);} +} +class Criteria_or extends Criteria_bool_base { + public Criteria_or(Criteria lhs, Criteria rhs) {this.Ctor("OR", lhs, rhs);} + @Override public byte Tid() {return Criteria_.Tid_or;} + @Override public boolean Matches(Object curVal) {return this.Lhs().Matches(curVal) || this.Rhs().Matches(curVal);} +} +class Criteria_const implements Criteria { + public Criteria_const(boolean val) {this.val = val;} + public byte Tid() {return Criteria_.Tid_const;} + public boolean Matches(Object comp) {return val;} private final boolean val; + public void Val_from_args(Hash_adp args) {;} + public void Val_as_obj_(Object v) {throw Err_.new_unimplemented();} + public String To_str() {return String_.Concat(" IS ", Bool_.To_str_lower(val));} +} diff --git a/100_core/src/gplx/core/criterias/Criteria_comp.java b/100_core/src/gplx/core/criterias/Criteria_comp.java index a27517de8..847551cd2 100644 --- a/100_core/src/gplx/core/criterias/Criteria_comp.java +++ b/100_core/src/gplx/core/criterias/Criteria_comp.java @@ -13,3 +13,21 @@ 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.core.criterias; import gplx.*; import gplx.core.*; +public class Criteria_comp implements Criteria { + @gplx.Internal protected Criteria_comp(int comp_mode, Comparable val) {this.comp_mode = comp_mode; this.val = val;} + public byte Tid() {return Criteria_.Tid_comp;} + public Comparable Val() {return val;} private Comparable val; + public void Val_from_args(Hash_adp args) {throw Err_.new_unimplemented();} + public void Val_as_obj_(Object v) {val = (Comparable)v;} + public int Comp_mode() {return comp_mode;} private final int comp_mode; + public boolean Matches(Object comp_obj) { + return CompareAble_.Is(comp_mode, CompareAble_.as_(comp_obj), val); + } + + public String To_str() { + String comp_sym = comp_mode < CompareAble_.Same ? "<" : ">"; + String eq_sym = comp_mode % 2 == CompareAble_.Same ? "=" : ""; + return String_.Concat_any(comp_sym, eq_sym, " ", val); + } +} diff --git a/100_core/src/gplx/core/criterias/Criteria_eq.java b/100_core/src/gplx/core/criterias/Criteria_eq.java index a27517de8..c47f64df5 100644 --- a/100_core/src/gplx/core/criterias/Criteria_eq.java +++ b/100_core/src/gplx/core/criterias/Criteria_eq.java @@ -13,3 +13,20 @@ 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.core.criterias; import gplx.*; import gplx.core.*; +public class Criteria_eq implements Criteria { + @gplx.Internal protected Criteria_eq(boolean neg, Object val) {this.neg = neg; this.val = val;} + public byte Tid() {return Criteria_.Tid_eq;} + public boolean Neg() {return neg;} private final boolean neg; + public Object Val() {return val;} private Object val; + public void Val_as_obj_(Object v) {this.val = v;} + public void Val_from_args(Hash_adp args) {throw Err_.new_unimplemented();} + public boolean Matches(Object comp) { + Class val_type = Type_.Type_by_obj(val); + if (!Type_.Eq_by_obj(comp, val_type)) throw Err_.new_type_mismatch(val_type, comp); + boolean rv = Object_.Eq(val, comp); + return neg ? !rv : rv; + } + public String To_str() {return String_.Concat_any("= ", val);} + public static Criteria_eq as_(Object obj) {return obj instanceof Criteria_eq ? (Criteria_eq)obj : null;} +} diff --git a/100_core/src/gplx/core/criterias/Criteria_fld.java b/100_core/src/gplx/core/criterias/Criteria_fld.java index a27517de8..0e381faa1 100644 --- a/100_core/src/gplx/core/criterias/Criteria_fld.java +++ b/100_core/src/gplx/core/criterias/Criteria_fld.java @@ -13,3 +13,57 @@ 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.core.criterias; import gplx.*; import gplx.core.*; +public class Criteria_fld implements Criteria { + Criteria_fld(String pre, String key, Criteria crt) {this.pre = pre; this.key = key; this.crt = crt;} + public byte Tid() {return Criteria_.Tid_wrapper;} + public String Pre() {return pre;} private final String pre; + public String Key() {return key;} private final String key; + public Criteria Crt() {return crt;} private final Criteria crt; + public String Pre_w_key() {return pre == Pre_null ? key : String_.Concat(pre, ".", key);} + public void Val_as_obj_(Object v) {throw Err_.new_unimplemented();} + public void Val_from_args(Hash_adp args) { + List_adp list = (List_adp)args.Get_by(key); if (list == null) throw Err_.new_wo_type("criteria.fld key not found", "key", key); + Object o = Fill_val(key, crt.Tid(), list); + crt.Val_as_obj_(o); + } + public boolean Matches(Object invkObj) { + Gfo_invk invk = (Gfo_invk)invkObj; + if (key == Criteria_fld.Key_null) return crt.Matches(invkObj); + Object comp = Gfo_invk_.Invk_by_key(invk, this.Pre_w_key()); + return crt.Matches(comp); + } + public String To_str() {return String_.Concat(key, " ", crt.To_str());} + + public static final String Key_null = null, Pre_null = null; + public static Criteria_fld as_(Object obj) {return obj instanceof Criteria_fld ? (Criteria_fld)obj : null;} + public static Criteria_fld new_(String pre, String key, Criteria crt) {return new Criteria_fld(pre, key, crt);} + public static Criteria_fld new_(String key, Criteria crt) {return new Criteria_fld(Pre_null, key, crt);} + public static Object Fill_val(String key, byte tid, List_adp list) { + int len = list.Count(); + switch (tid) { + case Criteria_.Tid_eq: + case Criteria_.Tid_comp: + case Criteria_.Tid_like: + case Criteria_.Tid_iomatch: + if (len != 1) throw Err_.new_wo_type("list.len should be 1", "key", key, "tid", tid, "len", len); + return list.Get_at(0); + case Criteria_.Tid_between: + if (len != 2) throw Err_.new_wo_type("list.len should be 2", "key", key, "tid", tid, "len", len); + return new Object[] {list.Get_at(0), list.Get_at(1)}; + case Criteria_.Tid_in: + if (len == 0) throw Err_.new_wo_type("list.len should be > 0", "key", key, "tid", tid, "len", len); + return list.To_obj_ary(); + case Criteria_.Tid_const: + case Criteria_.Tid_not: + case Criteria_.Tid_and: + case Criteria_.Tid_or: + if (len != 0) throw Err_.new_wo_type("list.len should be 0", "key", key, "tid", tid, "len", len); + return key; // no values to fill in; return back key + case Criteria_.Tid_wrapper: // not recursive + case Criteria_.Tid_db_obj_ary: // unsupported + case Criteria_.Tid_custom: + default: throw Err_.new_unhandled(tid); + } + } +} diff --git a/100_core/src/gplx/core/criterias/Criteria_in.java b/100_core/src/gplx/core/criterias/Criteria_in.java index a27517de8..039b20bb6 100644 --- a/100_core/src/gplx/core/criterias/Criteria_in.java +++ b/100_core/src/gplx/core/criterias/Criteria_in.java @@ -13,3 +13,34 @@ 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.core.criterias; import gplx.*; import gplx.core.*; +public class Criteria_in implements Criteria { + public Criteria_in(boolean neg, Object[] ary) {this.neg = neg; Ary_(ary);} + public byte Tid() {return Criteria_.Tid_in;} + public boolean Neg() {return neg;} private final boolean neg; + public Object[] Ary() {return ary;} private Object[] ary; + public int Ary_len() {return ary_len;} private int ary_len; + public Class Itm_type() {return itm_type;} private Class itm_type; + private void Ary_(Object[] v) { + this.ary = v; + this.ary_len = ary.length; + this.itm_type = ary_len == 0 ? Object.class : Type_.Type_by_obj(ary[0]); + } + public void Val_as_obj_(Object v) {Ary_((Object[])v);} + public void Val_from_args(Hash_adp args) {throw Err_.new_unimplemented();} + public boolean Matches(Object comp) { + if (ary_len == 0) return false; // empty array never matches + if (!Type_.Eq_by_obj(comp, itm_type)) throw Err_.new_type_mismatch(itm_type, comp); + boolean rv = false; + for (int i = 0; i < ary_len; ++i) { + Object val = ary[i]; + if (Object_.Eq(val, comp)) { + rv = true; + break; + } + } + return neg ? !rv : rv; + } + + public String To_str() {return String_.Concat_any("IN ", String_.Concat_any(ary));} +} diff --git a/100_core/src/gplx/core/criterias/Criteria_ioItm_tst.java b/100_core/src/gplx/core/criterias/Criteria_ioItm_tst.java index a27517de8..c1bd2ec6b 100644 --- a/100_core/src/gplx/core/criterias/Criteria_ioItm_tst.java +++ b/100_core/src/gplx/core/criterias/Criteria_ioItm_tst.java @@ -13,3 +13,36 @@ 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.core.criterias; import gplx.*; import gplx.core.*; +import org.junit.*; +import gplx.core.ios.*; +public class Criteria_ioItm_tst { + IoItmFil fil; Criteria crt; IoItm_fxt fx = IoItm_fxt.new_(); + @Test public void IoType() { + crt = crt_(IoItm_base_.Prop_Type, Criteria_.eq_(IoItmFil.Type_Fil)); + tst_Match(true, crt, fx.fil_wnt_("C:\\fil.txt")); + tst_Match(false, crt, fx.dir_wnt_("C:\\dir")); + } + @Test public void Ext() { + crt = crt_(IoItm_base_.Prop_Ext, Criteria_.eq_(".txt")); + tst_Match(true, crt, fx.fil_wnt_("C:\\fil.txt")); + tst_Match(false, crt, fx.fil_wnt_("C:\\fil.xml"), fx.fil_wnt_("C:\\fil.txt1"), fx.fil_wnt_("C:\\fil1.txt.xml"), fx.dir_wnt_("C:\\.txt")); + } + @Test public void Modified() { + fil = fx.fil_wnt_("C:\\fil.txt"); + crt = crt_(IoItmFil_.Prop_Modified, Criteria_.mte_(DateAdp_.parse_gplx("2001-01-01"))); + tst_Match(true, crt, fil.ModifiedTime_(DateAdp_.parse_gplx("2001-01-02")), fil.ModifiedTime_(DateAdp_.parse_gplx("2001-01-01"))); + tst_Match(false, crt, fil.ModifiedTime_(DateAdp_.parse_gplx("2000-12-31"))); + } + @Test public void IoMatch() { + Criteria crt = Criteria_ioMatch.parse(true, "*.txt", false); + CriteriaFxt fx_crt = new CriteriaFxt(); + fx_crt.tst_Matches(crt, Io_url_.new_any_("file.txt")); + fx_crt.tst_MatchesNot(crt, Io_url_.new_any_("file.xml")); + } + Criteria crt_(String fld, Criteria crt) {return Criteria_fld.new_(fld, crt);} + void tst_Match(boolean expt, Criteria fieldCrt, IoItm_base... ary) { + for (IoItm_base itm : ary) + Tfds.Eq(expt, fieldCrt.Matches(itm)); + } +} diff --git a/100_core/src/gplx/core/criterias/Criteria_ioMatch.java b/100_core/src/gplx/core/criterias/Criteria_ioMatch.java index a27517de8..20859a6d7 100644 --- a/100_core/src/gplx/core/criterias/Criteria_ioMatch.java +++ b/100_core/src/gplx/core/criterias/Criteria_ioMatch.java @@ -13,3 +13,23 @@ 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.core.criterias; import gplx.*; import gplx.core.*; +import gplx.core.texts.*; +public class Criteria_ioMatch implements Criteria { // EX: url IOMATCH '*.xml|*.txt' + public Criteria_ioMatch(boolean match, RegxPatn_cls_ioMatch pattern) {this.match = match; this.pattern = pattern;} + public byte Tid() {return Criteria_.Tid_iomatch;} + public boolean Neg() {return !match;} private final boolean match; + public void Val_from_args(Hash_adp args) {throw Err_.new_unimplemented();} + public void Val_as_obj_(Object v) {this.pattern = (RegxPatn_cls_ioMatch)v;} + public RegxPatn_cls_ioMatch Pattern() {return pattern;} private RegxPatn_cls_ioMatch pattern; + public boolean Matches(Object compObj) { + Io_url comp = (Io_url)compObj; + boolean rv = pattern.Matches(comp.XtoCaseNormalized()); + return match ? rv : !rv; + } + public String To_str() {return String_.Concat_any("IOMATCH ", pattern);} + + public static final String TokenName = "IOMATCH"; + public static Criteria_ioMatch as_(Object obj) {return obj instanceof Criteria_ioMatch ? (Criteria_ioMatch)obj : null;} + public static Criteria_ioMatch parse(boolean match, String raw, boolean caseSensitive) {return new Criteria_ioMatch(match, RegxPatn_cls_ioMatch_.parse(raw, caseSensitive));} +} diff --git a/100_core/src/gplx/core/criterias/Criteria_like.java b/100_core/src/gplx/core/criterias/Criteria_like.java index a27517de8..8d53182ed 100644 --- a/100_core/src/gplx/core/criterias/Criteria_like.java +++ b/100_core/src/gplx/core/criterias/Criteria_like.java @@ -13,3 +13,20 @@ 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.core.criterias; import gplx.*; import gplx.core.*; +import gplx.core.texts.*; /*RegxPatn_cls_like*/ +public class Criteria_like implements Criteria { + @gplx.Internal protected Criteria_like(boolean neg, RegxPatn_cls_like pattern) {this.neg = neg; this.pattern = pattern;} + public byte Tid() {return Criteria_.Tid_like;} + public boolean Neg() {return neg;} private final boolean neg; + public RegxPatn_cls_like Pattern() {return pattern;} private RegxPatn_cls_like pattern; + public void Val_from_args(Hash_adp args) {throw Err_.new_unimplemented();} + public void Val_as_obj_(Object v) {this.pattern = RegxPatn_cls_like_.parse((String)v, RegxPatn_cls_like.EscapeDefault);} + public boolean Matches(Object compObj) { + String comp = String_.as_(compObj); if (comp == null) throw Err_.new_type_mismatch(String.class, compObj); + boolean rv = pattern.Matches(comp); + return neg ? !rv : rv; + } + + public String To_str() {return String_.Concat_any("LIKE ", pattern);} +} \ No newline at end of file diff --git a/100_core/src/gplx/core/criterias/Criteria_not.java b/100_core/src/gplx/core/criterias/Criteria_not.java index a27517de8..c13eb9744 100644 --- a/100_core/src/gplx/core/criterias/Criteria_not.java +++ b/100_core/src/gplx/core/criterias/Criteria_not.java @@ -13,3 +13,13 @@ 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.core.criterias; import gplx.*; import gplx.core.*; +public class Criteria_not implements Criteria { + public Criteria_not(Criteria v) {this.criteria = v;} + public byte Tid() {return Criteria_.Tid_not;} + public boolean Matches(Object obj) {return !criteria.Matches(obj);} + public void Val_from_args(Hash_adp args) {criteria.Val_from_args(args);} + public void Val_as_obj_(Object v) {criteria.Val_as_obj_(v);} + public String To_str() {return String_.Concat_any(" NOT ", criteria.To_str());} + public Criteria Crt() {return criteria;} private final Criteria criteria; +} diff --git a/100_core/src/gplx/core/criterias/Criteria_tst.java b/100_core/src/gplx/core/criterias/Criteria_tst.java index a27517de8..5d6a4c7c0 100644 --- a/100_core/src/gplx/core/criterias/Criteria_tst.java +++ b/100_core/src/gplx/core/criterias/Criteria_tst.java @@ -13,3 +13,78 @@ 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.core.criterias; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Criteria_tst { + @Test public void Equal() { + Criteria crt = Criteria_.eq_(true); + fx.tst_Matches(crt, true); + fx.tst_MatchesNot(crt, false); + fx.tst_MatchesFail(crt, "true"); + + fx.tst_Matches(Criteria_.eq_(1), 1); + fx.tst_Matches(Criteria_.eq_("equal"), "equal"); + fx.tst_Matches(Criteria_.eq_(date), date); + } + @Test public void Not() { + Criteria crt = Criteria_.eqn_(true); + fx.tst_Matches(crt, false); + fx.tst_MatchesNot(crt, true); + fx.tst_MatchesFail(crt, "false"); + + fx.tst_Matches(Criteria_.eqn_(1), -1); + fx.tst_Matches(Criteria_.eqn_("equal"), "not equal"); + fx.tst_Matches(Criteria_.eqn_(date), date.Add_minute(1)); + } + @Test public void MoreThan() { + Criteria crt = Criteria_.mt_(0); + fx.tst_Matches(crt, 1, 2); + fx.tst_MatchesNot(crt, 0, -1); + fx.tst_MatchesFail(crt, "1"); + + fx.tst_Matches(Criteria_.mt_(0), 1); + fx.tst_Matches(Criteria_.mt_("a"), "b"); + fx.tst_Matches(Criteria_.mt_(date), date.Add_minute(1)); + fx.tst_Matches(Criteria_.mt_(false), true); // MISC: thus truth is greater than falsehood + } + @Test public void MoreThanEq() { + Criteria crt = Criteria_.mte_(0); + fx.tst_Matches(crt, 0); + } + @Test public void Less() { + Criteria crt = Criteria_.lt_(0); + fx.tst_Matches(crt, -1, -2); + fx.tst_MatchesNot(crt, 0, 1); + fx.tst_MatchesFail(crt, "-1"); + } + @Test public void LessEq() { + Criteria crt = Criteria_.lte_(0); + fx.tst_Matches(crt, 0); + } + @Test public void Between() { + Criteria crt = Criteria_.between_(-1, 1); + fx.tst_Matches(crt, 0, 1, -1); + fx.tst_MatchesNot(crt, -2, 2); + fx.tst_MatchesFail(crt, "0"); + + fx.tst_Matches(Criteria_.between_(1, -1), 0); // reverse range + fx.tst_Matches(Criteria_.between_("a", "c"), "b"); + } + @Test public void In() { + Criteria crt = Criteria_.in_(0, 1, 2); + fx.tst_Matches(crt, 0, 1, 2); + fx.tst_MatchesNot(crt, 3, -1); + fx.tst_MatchesFail(crt, "0"); + } + CriteriaFxt fx = new CriteriaFxt(); + DateAdp date = DateAdp_.parse_gplx("2001-01-01"); +} +class CriteriaFxt { + public void tst_Matches(Criteria crt, Object... ary) {for (Object val : ary) Tfds.Eq(true, crt.Matches(val));} + public void tst_MatchesNot(Criteria crt, Object... ary) {for (Object val : ary) Tfds.Eq(false, crt.Matches(val));} + public void tst_MatchesFail(Criteria crt, Object val) { + try {crt.Matches(val);} + catch(Exception exc) {Err_.Noop(exc); return;} + Tfds.Fail_expdError(); + } +} diff --git a/100_core/src/gplx/core/encoders/B85_fp_.java b/100_core/src/gplx/core/encoders/B85_fp_.java index a27517de8..c7845e952 100644 --- a/100_core/src/gplx/core/encoders/B85_fp_.java +++ b/100_core/src/gplx/core/encoders/B85_fp_.java @@ -13,3 +13,42 @@ 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.core.encoders; import gplx.*; import gplx.core.*; +public class B85_fp_ { + public static byte[] To_bry(double v) { + String str = Double_.To_str(v); + byte[] bry = Bry_.new_a7(str); int len = bry.length; + int num_len = len; boolean neg = false; + int bgn = 0; int dot = -1; + if (bry[0] == Byte_ascii.Dash) {neg = true; bgn = 1; --num_len;} + boolean skip_zeros = true; + for (int i = bgn; i < len; ++i) { + byte b = bry[i]; + switch (b) { + case Byte_ascii.Num_0: + if (skip_zeros) + --num_len; + break; + case Byte_ascii.Dot: + skip_zeros = false; + dot = i; + --num_len; + break; + default: + skip_zeros = false; + break; + } + } + int pow = 0; + if (dot != -1) { + pow = len - (dot + 1); + for (int i = 0; i < pow; ++i) + v *= 10; + } + int num = (int)v; + byte[] rv = new byte[num_len + 1]; + rv[0] = (byte)(Byte_ascii.Dot + pow + (neg ? 45 : 0)); + Base85_.Set_bry(num, rv, 1, 1); + return rv; + } +} diff --git a/100_core/src/gplx/core/encoders/B85_fp__tst.java b/100_core/src/gplx/core/encoders/B85_fp__tst.java index a27517de8..f837e9584 100644 --- a/100_core/src/gplx/core/encoders/B85_fp__tst.java +++ b/100_core/src/gplx/core/encoders/B85_fp__tst.java @@ -13,3 +13,17 @@ 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.core.encoders; import gplx.*; import gplx.core.*; +import org.junit.*; +public class B85_fp__tst { + private final B85_fp__fxt fxt = new B85_fp__fxt(); + @Test public void Double_to_str() { + fxt.Test__to_str(.1d, "/\""); + } +} +class B85_fp__fxt { + public void Test__to_str(double val, String expd) { + byte[] actl = B85_fp_.To_bry(val); + Tfds.Eq_str(expd, String_.new_a7(actl)); + } +} diff --git a/100_core/src/gplx/core/encoders/Base85_.java b/100_core/src/gplx/core/encoders/Base85_.java index a27517de8..4d78a3c00 100644 --- a/100_core/src/gplx/core/encoders/Base85_.java +++ b/100_core/src/gplx/core/encoders/Base85_.java @@ -13,3 +13,52 @@ 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.core.encoders; import gplx.*; import gplx.core.*; +public class Base85_ { + public static String To_str(int val, int min_len) {return String_.new_u8(Set_bry(val, null, 0, min_len));} + public static byte[] To_bry(int val, int min_len) {return Set_bry(val, null, 0, min_len);} + public static byte[] Set_bry(int val, byte[] ary, int ary_pos, int min_len) { + int val_len = Bry_len(val); + int ary_len = val_len, pad_len = 0; + boolean pad = ary_len < min_len; + if (pad) { + pad_len = min_len - ary_len; + ary_len = min_len; + } + if (ary == null) ary = new byte[ary_len]; + if (pad) { + for (int i = 0; i < pad_len; i++) // fill ary with pad_len + ary[i + ary_pos] = A7_offset; + } + for (int i = ary_len - pad_len; i > 0; i--) { + int div = Pow85[i - 1]; + byte tmp = (byte)(val / div); + ary[ary_pos + ary_len - i] = (byte)(tmp + A7_offset); + val -= tmp * div; + } + return ary; + } + public static int To_int_by_str(String s) { + byte[] ary = Bry_.new_u8(s); + return To_int_by_bry(ary, 0, ary.length - 1); + } + public static int To_int_by_bry(byte[] ary, int bgn, int end) { + int rv = 0, factor = 1; + for (int i = end; i >= bgn; i--) { + rv += (ary[i] - A7_offset) * factor; + factor *= Radix; + } + return rv; + } + public static int Bry_len(int v) { + if (v == 0) return 1; + for (int i = Pow85_last; i > -1; i--) + if (v >= Pow85[i]) return i + 1; + throw Err_.new_wo_type("neg number not allowed", "v", v); + } + public static final int Len_int = 5; + private static final int Pow85_last = 4, Radix = 85; + public static final byte A7_offset = 33; + public static final int Pow85_0 = 1, Pow85_1 = 85, Pow85_2 = 7225, Pow85_3 = 614125, Pow85_4 = 52200625; + public static int[] Pow85 = new int[]{Pow85_0, Pow85_1, Pow85_2, Pow85_3, Pow85_4}; // NOTE: ary constructed to match index to exponent; Pow85[1] = 85^1 +} diff --git a/100_core/src/gplx/core/encoders/Base85__tst.java b/100_core/src/gplx/core/encoders/Base85__tst.java index a27517de8..b5f2e238c 100644 --- a/100_core/src/gplx/core/encoders/Base85__tst.java +++ b/100_core/src/gplx/core/encoders/Base85__tst.java @@ -13,3 +13,47 @@ 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.core.encoders; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Base85__tst { + private final Base85__fxt fxt = new Base85__fxt(); + @Test public void Log() { + fxt.Test_log( 0, 1); + fxt.Test_log( 84, 1); + fxt.Test_log( 85, 2); + fxt.Test_log( 7224, 2); + fxt.Test_log( 7225, 3); + fxt.Test_log( 614124, 3); + fxt.Test_log( 614125, 4); + fxt.Test_log( 52200624, 4); + fxt.Test_log( 52200625, 5); + fxt.Test_log(Int_.Max_value, 5); + } + @Test public void To_str() { + fxt.Test_to_str( 0, "!"); + fxt.Test_to_str( 84, "u"); + fxt.Test_to_str( 85, "\"!"); + fxt.Test_to_str( 7224, "uu"); + fxt.Test_to_str( 7225, "\"!!"); + fxt.Test_to_str( 614124, "uuu"); + fxt.Test_to_str( 614125, "\"!!!"); + fxt.Test_to_str( 52200624, "uuuu"); + fxt.Test_to_str( 52200625, "\"!!!!"); + } + @Test public void XtoStrAry() { + byte[] ary = new byte[9]; + fxt.Exec_to_str(ary, 0, 2); // !!# + fxt.Exec_to_str(ary, 3, 173); // !#$ + fxt.Exec_to_str(ary, 6, 14709); // #$% + Tfds.Eq("!!#!#$#$%", String_.new_u8(ary)); + } +} +class Base85__fxt { + public void Test_log(int val, int expd) {Tfds.Eq(expd, Base85_.Bry_len(val));} + public void Test_to_str(int val, String expd) { + String actl = Base85_.To_str(val, 0); + Tfds.Eq(expd, actl); + Tfds.Eq(val, Base85_.To_int_by_str(expd)); + } + public void Exec_to_str(byte[] ary, int aryPos, int val) {Base85_.Set_bry(val, ary, aryPos, 3);} +} diff --git a/100_core/src/gplx/core/encoders/Gfo_hzip_int_.java b/100_core/src/gplx/core/encoders/Gfo_hzip_int_.java index a27517de8..c8080c24d 100644 --- a/100_core/src/gplx/core/encoders/Gfo_hzip_int_.java +++ b/100_core/src/gplx/core/encoders/Gfo_hzip_int_.java @@ -13,3 +13,49 @@ 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.core.encoders; import gplx.*; import gplx.core.*; +import gplx.core.primitives.*; +public class Gfo_hzip_int_ { + public static final int Neg_1_adj = 1; + public static void Encode(int reqd, Bry_bfr bfr, int val) { + int bfr_len = bfr.Len(); + int len_in_base85 = Base85_.Bry_len(val); + boolean abrv = val < Base85_.Pow85[reqd]; + int adj = abrv ? 0 : 1; + int actl_len = len_in_base85 + adj; + if (actl_len < reqd) actl_len = reqd; + bfr.Add_byte_repeat(Byte_ascii.Bang, actl_len); // fill with 0s; this asserts that there underlying array will be large enough for following write + byte[] bfr_bry = bfr.Bfr(); // NOTE: set bry reference here b/c Add_byte_repeat may create a new one + Base85_.Set_bry(val, bfr_bry, bfr_len + adj, reqd); // calc base85 val for val; EX: 7224 -> "uu" + if (!abrv) { + byte base85_byte = Base85_len__2; + switch (len_in_base85) { + case 3: base85_byte = Base85_len__3; break; + case 4: base85_byte = Base85_len__4; break; + case 5: base85_byte = Base85_len__5; break; + } + bfr_bry[bfr_len] = base85_byte; + } + } + public static int Decode(int reqd, byte[] src, int src_len, int src_bgn, Int_obj_ref pos_ref) { + byte b0 = src[src_bgn]; + int base85_bgn = src_bgn + 1; + int len_in_base85 = 1; // default to 1 + switch (b0) { + case Base85_len__2: len_in_base85 = 2; break; + case Base85_len__3: len_in_base85 = 3; break; + case Base85_len__4: len_in_base85 = 4; break; + case Base85_len__5: len_in_base85 = 5; break; + default: --base85_bgn; break; + } + if (len_in_base85 < reqd) len_in_base85 = reqd; + int base85_end = base85_bgn + len_in_base85; + pos_ref.Val_(base85_end); + return Base85_.To_int_by_bry(src, base85_bgn, base85_end - 1); + } + private static final byte + Base85_len__2 = Byte_ascii.Curly_bgn + , Base85_len__3 = Byte_ascii.Pipe + , Base85_len__4 = Byte_ascii.Curly_end + , Base85_len__5 = Byte_ascii.Tilde; +} diff --git a/100_core/src/gplx/core/encoders/Hex_utl_.java b/100_core/src/gplx/core/encoders/Hex_utl_.java index a27517de8..ee8741835 100644 --- a/100_core/src/gplx/core/encoders/Hex_utl_.java +++ b/100_core/src/gplx/core/encoders/Hex_utl_.java @@ -13,3 +13,165 @@ 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.core.encoders; import gplx.*; import gplx.core.*; +public class Hex_utl_ { + public static int Parse_or(byte[] src, int or) {return Parse_or(src, 0, src.length, or);} + public static int Parse_or(byte[] src, int bgn, int end, int or) { + int rv = 0; int factor = 1; + byte b = Byte_.Max_value_127; + for (int i = end - 1; i >= bgn; i--) { + switch (src[i]) { + case Byte_ascii.Num_0: b = 0; break; case Byte_ascii.Num_1: b = 1; break; case Byte_ascii.Num_2: b = 2; break; case Byte_ascii.Num_3: b = 3; break; case Byte_ascii.Num_4: b = 4; break; + case Byte_ascii.Num_5: b = 5; break; case Byte_ascii.Num_6: b = 6; break; case Byte_ascii.Num_7: b = 7; break; case Byte_ascii.Num_8: b = 8; break; case Byte_ascii.Num_9: b = 9; break; + case Byte_ascii.Ltr_A: b = 10; break; case Byte_ascii.Ltr_B: b = 11; break; case Byte_ascii.Ltr_C: b = 12; break; case Byte_ascii.Ltr_D: b = 13; break; case Byte_ascii.Ltr_E: b = 14; break; case Byte_ascii.Ltr_F: b = 15; break; + case Byte_ascii.Ltr_a: b = 10; break; case Byte_ascii.Ltr_b: b = 11; break; case Byte_ascii.Ltr_c: b = 12; break; case Byte_ascii.Ltr_d: b = 13; break; case Byte_ascii.Ltr_e: b = 14; break; case Byte_ascii.Ltr_f: b = 15; break; + default: b = Byte_.Max_value_127; break; + } + if (b == Byte_.Max_value_127) return or; + rv += b * factor; + factor *= 16; + } + return rv; + } + public static int Parse(String src) { + int rv = Parse_or(src, -1); if (rv == -1) throw Err_.new_parse("HexDec", "src"); + return rv; + } + public static int Parse_or(String src, int or) { + int rv = 0; int digit = 0, factor = 1, len = String_.Len(src); + for (int i = len - 1; i > -1; --i) { + digit = To_int(String_.CharAt(src, i)); + if (digit == -1) return or; + rv += digit * factor; + factor *= 16; + } + return rv; + } + public static byte[] Parse_hex_to_bry(String src) {return Parse_hex_to_bry(Bry_.new_u8(src));} + public static byte[] Parse_hex_to_bry(byte[] src) { + int src_len = src.length; + if ((src_len % 2) != 0) throw Err_.new_wo_type("hex_utl: hex_string must have an even length; src=~{0}", src); + int ary_len = src_len / 2; + byte[] rv = new byte[ary_len]; + int rv_idx = 0; + + for (int i = 0; i < src_len; i += 2) { + int val = Parse_or(src, i, i + 2, -1); + if (val == -1) throw Err_.new_wo_type("hex_utl: hex_string has invalid char; src=~{0}", src); + rv[rv_idx] = (byte)val; + rv_idx++; + } + return rv; + } + public static byte[] Encode_bry(byte[] src) { + int src_len = src.length; + byte[] trg = new byte[src_len * 2]; + Encode_bry(src, trg); + return trg; + } + public static void Encode_bry(byte[] src, byte[] trg) { + int src_len = src.length, trg_len = trg.length; + if (trg_len != src_len * 2) throw Err_.new_("hex", "trg.len must be src.len * 2", "src_len", src_len, "trg_len", trg_len); + int trg_idx = -1; + for (int src_idx = 0; src_idx < src_len; ++src_idx) { + byte src_byte = src[src_idx]; + trg[++trg_idx] = To_byte_lcase(0xf & src_byte >>> 4); + trg[++trg_idx] = To_byte_lcase(0xf & src_byte); + } + } + public static void Encode_bfr(Bry_bfr bfr, byte[] src) { + int src_len = src.length; + for (int src_idx = 0; src_idx < src_len; ++src_idx) { + byte src_byte = src[src_idx]; + bfr.Add_byte(To_byte_lcase(0xf & src_byte >>> 4)); + bfr.Add_byte(To_byte_lcase(0xf & src_byte)); + } + } + public static String To_str(int val, int pad) { + char[] ary = new char[8]; int idx = 8; // 8 is max len of hexString; (2^4 * 8); EX: int.MaxValue = 7FFFFFFF + do { + int byt = val % 16; + ary[--idx] = To_char(byt); + val /= 16; + } while (val > 0); + while (8 - idx < pad) // pad left with zeros + ary[--idx] = '0'; + return String_.new_charAry_(ary, idx, 8-idx); + } + public static void Write(byte[] bry, int bgn, int end, int val) { + for (int i = end - 1; i > bgn - 1; i--) { + int b = val % 16; + bry[i] = To_byte_ucase(b); + val /= 16; + if (val == 0) break; + } + } + public static void Write_bfr(Bry_bfr bfr, boolean lcase, int val) { + // count bytes + int val_len = 0; + int tmp = val; + while (true) { + tmp /= 16; + val_len++; + if (tmp == 0) break; + } + + // fill bytes from right to left + int hex_bgn = bfr.Len(); + bfr.Add_byte_repeat(Byte_ascii.Null, val_len); + byte[] bry = bfr.Bfr(); + for (int i = 0; i < val_len; i++) { + int b = val % 16; + bry[hex_bgn + val_len - i - 1] = lcase ? To_byte_lcase(b) : To_byte_ucase(b); + val /= 16; + } + } + public static boolean Is_hex_many(byte... ary) { + for (byte itm : ary) { + switch (itm) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + case Byte_ascii.Ltr_A: case Byte_ascii.Ltr_B: case Byte_ascii.Ltr_C: case Byte_ascii.Ltr_D: case Byte_ascii.Ltr_E: case Byte_ascii.Ltr_F: + case Byte_ascii.Ltr_a: case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_e: case Byte_ascii.Ltr_f: + break; + default: + return false; + } + } + return true; + } + private static int To_int(char c) { + switch (c) { + case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; + case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; + case 'A': return 10; case 'B': return 11; case 'C': return 12; case 'D': return 13; case 'E': return 14; case 'F': return 15; + case 'a': return 10; case 'b': return 11; case 'c': return 12; case 'd': return 13; case 'e': return 14; case 'f': return 15; + default: return -1; + } + } + private static char To_char(int val) { + switch (val) { + case 0: return '0'; case 1: return '1'; case 2: return '2'; case 3: return '3'; case 4: return '4'; + case 5: return '5'; case 6: return '6'; case 7: return '7'; case 8: return '8'; case 9: return '9'; + case 10: return 'A'; case 11: return 'B'; case 12: return 'C'; case 13: return 'D'; case 14: return 'E'; case 15: return 'F'; + default: throw Err_.new_parse("hexstring", Int_.To_str(val)); + } + } + private static byte To_byte_ucase(int v) { + switch (v) { + case 0: return Byte_ascii.Num_0; case 1: return Byte_ascii.Num_1; case 2: return Byte_ascii.Num_2; case 3: return Byte_ascii.Num_3; case 4: return Byte_ascii.Num_4; + case 5: return Byte_ascii.Num_5; case 6: return Byte_ascii.Num_6; case 7: return Byte_ascii.Num_7; case 8: return Byte_ascii.Num_8; case 9: return Byte_ascii.Num_9; + case 10: return Byte_ascii.Ltr_A; case 11: return Byte_ascii.Ltr_B; case 12: return Byte_ascii.Ltr_C; case 13: return Byte_ascii.Ltr_D; case 14: return Byte_ascii.Ltr_E; case 15: return Byte_ascii.Ltr_F; + default: throw Err_.new_parse("hexstring", Int_.To_str(v)); + } + } + private static byte To_byte_lcase(int v) { + switch (v) { + case 0: return Byte_ascii.Num_0; case 1: return Byte_ascii.Num_1; case 2: return Byte_ascii.Num_2; case 3: return Byte_ascii.Num_3; + case 4: return Byte_ascii.Num_4; case 5: return Byte_ascii.Num_5; case 6: return Byte_ascii.Num_6; case 7: return Byte_ascii.Num_7; + case 8: return Byte_ascii.Num_8; case 9: return Byte_ascii.Num_9; case 10: return Byte_ascii.Ltr_a; case 11: return Byte_ascii.Ltr_b; + case 12: return Byte_ascii.Ltr_c; case 13: return Byte_ascii.Ltr_d; case 14: return Byte_ascii.Ltr_e; case 15: return Byte_ascii.Ltr_f; + default: throw Err_.new_parse("hexstring", Int_.To_str(v)); + } + } +} diff --git a/100_core/src/gplx/core/encoders/Hex_utl__tst.java b/100_core/src/gplx/core/encoders/Hex_utl__tst.java index a27517de8..ca414e615 100644 --- a/100_core/src/gplx/core/encoders/Hex_utl__tst.java +++ b/100_core/src/gplx/core/encoders/Hex_utl__tst.java @@ -13,3 +13,77 @@ 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.core.encoders; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.tests.*; +public class Hex_utl__tst { + private final Hex_utl__fxt fxt = new Hex_utl__fxt(); + @Test public void To_int() { + fxt.Test__to_int("0" , 0); + fxt.Test__to_int("F" , 15); + fxt.Test__to_int("0F" , 15); + fxt.Test__to_int("10" , 16); + fxt.Test__to_int("20" , 32); + fxt.Test__to_int("FF" , 255); + fxt.Test__to_int("100" , 256); + fxt.Test__to_int("0a" , 10); + fxt.Test__to_int("7FFFFFFF" , Int_.Max_value); + fxt.Test__to_int_bry("100" , 256); + } + @Test public void To_str() { + fxt.Test__to_str(0 , "0"); + fxt.Test__to_str(15 , "F"); + fxt.Test__to_str(16 , "10"); + fxt.Test__to_str(32 , "20"); + fxt.Test__to_str(255 , "FF"); + fxt.Test__to_str(Int_.Max_value, "7FFFFFFF"); + + fxt.Test__to_str(15, 2 , "0F"); + fxt.Test__to_str(15, 3 , "00F"); + } + @Test public void Write() { + fxt.Test__write("[00000000]", 1, 9, 15, "[0000000F]"); + fxt.Test__write("[00000000]", 1, 9, 255, "[000000FF]"); + } + @Test public void Write_bfr() { + fxt.Test__write_bfr(Bool_.Y, 0, "0"); + fxt.Test__write_bfr(Bool_.Y, 15, "f"); + fxt.Test__write_bfr(Bool_.Y, 16, "10"); + fxt.Test__write_bfr(Bool_.Y, 32, "20"); + fxt.Test__write_bfr(Bool_.Y, 255, "ff"); + fxt.Test__write_bfr(Bool_.Y, 256, "100"); + fxt.Test__write_bfr(Bool_.Y, Int_.Max_value, "7fffffff"); + } + @Test public void Encode() { + fxt.Test__parse_hex_to_bry("E2A7BC", 226, 167, 188); + } +} +class Hex_utl__fxt { + public void Test__write(String s, int bgn, int end, int val, String expd) { + byte[] bry = Bry_.new_a7(s); + Hex_utl_.Write(bry, bgn, end, val); + Tfds.Eq(expd, String_.new_a7(bry)); + } + public void Test__to_int(String raw, int expd) { + int actl = Hex_utl_.Parse(raw); + Tfds.Eq(expd, actl); + } + public void Test__to_int_bry(String raw, int expd) {Tfds.Eq(expd, Hex_utl_.Parse_or(Bry_.new_a7(raw), -1));} + public void Test__to_str(int val, String expd) {Test__to_str(val, 0, expd);} + public void Test__to_str(int val, int pad, String expd) { + String actl = Hex_utl_.To_str(val, pad); + Tfds.Eq(expd, actl); + } + private final Bry_bfr bfr = Bry_bfr_.New(); + public void Test__write_bfr(boolean lcase, int val, String expd) { + Hex_utl_.Write_bfr(bfr, lcase, val); + Gftest.Eq__str(expd, bfr.To_str_and_clear()); + } + public void Test__encode_bry(String val, int... expd) { + byte[] actl = Hex_utl_.Encode_bry(Bry_.new_u8(val)); + Gftest.Eq__ary(Byte_.Ary_by_ints(expd), actl, "encode"); + } + public void Test__parse_hex_to_bry(String val, int... expd) { + byte[] actl = Hex_utl_.Parse_hex_to_bry(Bry_.new_u8(val)); + Gftest.Eq__ary(Byte_.Ary_by_ints(expd), actl, "encode"); + } +} diff --git a/100_core/src/gplx/core/envs/Env_.java b/100_core/src/gplx/core/envs/Env_.java index a27517de8..536d8c8f0 100644 --- a/100_core/src/gplx/core/envs/Env_.java +++ b/100_core/src/gplx/core/envs/Env_.java @@ -13,3 +13,34 @@ 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.core.envs; import gplx.*; import gplx.core.*; +public class Env_ { + public static void Init(String[] args, String appNameAndExt, Class type) { + mode_testing = false; + mode_debug = String_.In("GPLX_DEBUG_MODE_ENABLED", args); + appArgs = args; + appUrl = Jar_adp_.Url_type(type).OwnerDir().GenSubFil(appNameAndExt); + } + public static void Init_swt(String[] args, Class type) { // DATE:2014-06-23 + mode_testing = false; + mode_debug = String_.In("GPLX_DEBUG_MODE_ENABLED", args); + appArgs = args; + appUrl = Jar_adp_.Url_type(type); + } + public static void Init_drd() { + mode_testing = mode_debug = false; + } + public static void Init_testing() {mode_testing = true;} + public static void Init_testing_n_() {mode_testing = false;} + public static boolean Mode_testing() {return mode_testing;} private static boolean mode_testing = true; + public static boolean Mode_debug() {return mode_debug;} private static boolean mode_debug = false; + public static String[] AppArgs() {return appArgs;} static String[] appArgs; + public static Io_url AppUrl() { + if (mode_testing) return Io_url_.mem_fil_("mem/testing.jar"); + if (appUrl == Io_url_.Empty) throw Err_.new_wo_type("Env_.Init was not called"); + return appUrl; + } static Io_url appUrl = Io_url_.Empty; + + public static final String LocalHost = "127.0.0.1"; + public static String NewLine_lang() {return mode_testing ? "\n" : "\n";} +} diff --git a/100_core/src/gplx/core/envs/Jar_adp_.java b/100_core/src/gplx/core/envs/Jar_adp_.java index a27517de8..8ec4cc862 100644 --- a/100_core/src/gplx/core/envs/Jar_adp_.java +++ b/100_core/src/gplx/core/envs/Jar_adp_.java @@ -13,3 +13,19 @@ 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.core.envs; import gplx.*; import gplx.core.*; +public class Jar_adp_ { + public static DateAdp ModifiedTime_type(Class type) {if (type == null) throw Err_.new_null(); + Io_url url = Url_type(type); + return Io_mgr.Instance.QueryFil(url).ModifiedTime(); + } + public static Io_url Url_type(Class type) {if (type == null) throw Err_.new_null(); + String codeBase = type.getProtectionDomain().getCodeSource().getLocation().getPath(); + if (Op_sys.Cur().Tid_is_wnt()) + codeBase = String_.Mid(codeBase, 1); // codebase always starts with /; remove for wnt + codeBase = String_.Replace(codeBase, "/", Op_sys.Cur().Fsys_dir_spr_str()); // java always returns DirSpr as /; change to Env_.DirSpr to handle windows + try {codeBase = java.net.URLDecoder.decode(codeBase, "UTF-8");} + catch (java.io.UnsupportedEncodingException e) {Err_.Noop(e);} + return Io_url_.new_fil_(codeBase); + } +} diff --git a/100_core/src/gplx/core/envs/Op_sys.java b/100_core/src/gplx/core/envs/Op_sys.java index a27517de8..f10adc4d0 100644 --- a/100_core/src/gplx/core/envs/Op_sys.java +++ b/100_core/src/gplx/core/envs/Op_sys.java @@ -13,3 +13,82 @@ 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.core.envs; import gplx.*; import gplx.core.*; +public class Op_sys { + Op_sys(byte tid, byte sub_tid, String os_name, byte bitness, String nl_str, byte fsys_dir_spr_byte, boolean fsys_case_match) { + this.tid = tid; this.sub_tid = sub_tid; this.os_name = os_name; this.bitness = bitness; this.nl_str = nl_str; this.fsys_dir_spr_byte = fsys_dir_spr_byte; this.fsys_dir_spr_str = Char_.To_str((char)fsys_dir_spr_byte); this.fsys_case_match = fsys_case_match; + } + public byte Tid() {return tid;} private final byte tid; + public byte Sub_tid() {return sub_tid;} private final byte sub_tid; + public String Os_name() {return os_name;} private String os_name; + public byte Bitness() {return bitness;} private final byte bitness; + public String Bitness_str() {return (bitness == Bitness_32 ? "32" : "64");} + public String Nl_str() {return nl_str;} private final String nl_str; + public String Fsys_dir_spr_str() {return fsys_dir_spr_str;} private final String fsys_dir_spr_str; + public byte Fsys_dir_spr_byte() {return fsys_dir_spr_byte;} private final byte fsys_dir_spr_byte; + public String Fsys_http_frag_to_url_str(String raw) {return fsys_dir_spr_byte == Byte_ascii.Slash ? raw : String_.Replace(raw, Lnx.Fsys_dir_spr_str(), fsys_dir_spr_str);} + public boolean Fsys_case_match() {return fsys_case_match;} private final boolean fsys_case_match; + public String Fsys_case_match_str(String s) {return String_.CaseNormalize(fsys_case_match, s);} + public boolean Tid_is_wnt() {return tid == Tid_wnt;} + public boolean Tid_is_lnx() {return tid == Tid_lnx;} + public boolean Tid_is_osx() {return tid == Tid_osx;} + public boolean Tid_is_drd() {return tid == Tid_drd;} + public String To_str() {return os_name + Bitness_str();} + + public static final byte Tid_nil = 0, Tid_wnt = 1, Tid_lnx = 2, Tid_osx = 3, Tid_drd = 4, Tid_arm = 5; + public static final byte Sub_tid_unknown = 0, Sub_tid_win_xp = 1, Sub_tid_win_7 = 2, Sub_tid_win_8 = 3; + public static final byte Bitness_32 = 1, Bitness_64 = 2; + public static final char Nl_char_lnx = '\n'; + public static final byte Dir_spr__lnx = Byte_ascii.Slash, Dir_spr__wnt = Byte_ascii.Backslash; + public static final Op_sys Lnx = new_unx_flavor_(Tid_lnx, "linux", Bitness_32); + public static final Op_sys Osx = new_unx_flavor_(Tid_osx, "macosx", Bitness_32); + public static final Op_sys Drd = new_unx_flavor_(Tid_drd, "android", Bitness_32); + public static final Op_sys Wnt = new_wnt_(Sub_tid_unknown, Bitness_32); + public static Op_sys Cur() {return cur_op_sys;} static Op_sys cur_op_sys = new_auto_identify_(); + public static String Fsys_path_to_lnx(String v) { + return cur_op_sys.Tid_is_wnt() ? String_.Replace(v, Wnt.fsys_dir_spr_str, Lnx.fsys_dir_spr_str) : v; + } + public static String Fsys_path_to_wnt(String v) { + return cur_op_sys.Tid_is_wnt() ? String_.Replace(v, Lnx.fsys_dir_spr_str, Wnt.fsys_dir_spr_str) : v; + } + private static Op_sys new_wnt_(byte bitness, byte sub_tid) {return new Op_sys(Tid_wnt , sub_tid , "windows", bitness, "\r\n", Dir_spr__wnt , Bool_.N);} + private static Op_sys new_unx_flavor_(byte tid, String os_name, byte bitness) {return new Op_sys(tid , Sub_tid_unknown , os_name , bitness, "\n" , Dir_spr__lnx , Bool_.Y);} + public static void Cur_(int tid) { + switch (tid) { + case Tid_wnt: cur_op_sys = Wnt; break; + case Tid_lnx: cur_op_sys = Lnx; break; + case Tid_osx: cur_op_sys = Osx; break; + case Tid_drd: cur_op_sys = Drd; break; + default: throw Err_.new_unhandled_default(tid); + } + } + static final String GRP_KEY = "gplx.op_sys"; +// public static Op_sys Cur_() {cur_op_sys = new_auto_identify_(); return cur_op_sys;} + static Op_sys new_auto_identify_() { + String os_name = ""; + try { + String bitness_str = System.getProperty("sun.arch.data.model"); if (bitness_str == null) return Drd; + bitness_str = bitness_str.toLowerCase(); + byte bitness_byte = Bitness_32; + if (String_.Eq(bitness_str, "32")) bitness_byte = Bitness_32; + else if (String_.Eq(bitness_str, "64")) bitness_byte = Bitness_64; + else throw Err_.new_wo_type("unknown bitness; expecting 32 or 64; System.getProperty(\"bit.level\")", "val", bitness_str); + + os_name = System.getProperty("os.name").toLowerCase(); + String os_arch = System.getProperty("os.arch").toLowerCase(); + if (String_.Eq(os_arch, "arm")) return new_unx_flavor_(Tid_arm, os_name, bitness_byte); // EX:arm; DATE:2016-09-23;"arm" and "32" + if (String_.Has_at_bgn(os_name, "win")) { + String os_version = System.getProperty("os.version").toLowerCase();// "Windows 7".equals(osName) && "6.1".equals(osVersion); + byte sub_tid = Sub_tid_unknown; + if (String_.Eq(os_name, "windows xp") && String_.Eq(os_version, "5.1")) sub_tid = Sub_tid_win_xp; + else if (String_.Eq(os_name, "windows 7") && String_.Eq(os_version, "6.1")) sub_tid = Sub_tid_win_7; + else if (String_.Eq(os_name, "windows 8")) sub_tid = Sub_tid_win_8; + return new_wnt_(bitness_byte, sub_tid); + } + else if (String_.Eq(os_name, "linux")) return new_unx_flavor_(Tid_lnx, os_name, bitness_byte); + else if (String_.Has_at_bgn(os_name, "mac")) return new_unx_flavor_(Tid_osx, os_name, bitness_byte); // EX:Mac OS X + else throw Err_.new_wo_type("unknown os_name; expecting windows, linux, mac; System.getProperty(\"os.name\")", "val", os_name); + } catch (Exception exc) {Drd.os_name = os_name; return Drd;} + } + public static void Cur_is_drd_() {cur_op_sys = Drd;} + } diff --git a/100_core/src/gplx/core/envs/Op_sys_.java b/100_core/src/gplx/core/envs/Op_sys_.java index a27517de8..f0c90576d 100644 --- a/100_core/src/gplx/core/envs/Op_sys_.java +++ b/100_core/src/gplx/core/envs/Op_sys_.java @@ -13,3 +13,20 @@ 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.core.envs; import gplx.*; import gplx.core.*; +public class Op_sys_ { + public static boolean Wnt_invalid_char(byte b) { + switch (b) { + case Byte_ascii.Slash: + case Byte_ascii.Backslash: + case Byte_ascii.Lt: + case Byte_ascii.Gt: + case Byte_ascii.Colon: + case Byte_ascii.Pipe: + case Byte_ascii.Question: + case Byte_ascii.Star: + case Byte_ascii.Quote: return true; + default: return false; + } + } +} diff --git a/100_core/src/gplx/core/envs/Process_adp.java b/100_core/src/gplx/core/envs/Process_adp.java index a27517de8..24a57e84c 100644 --- a/100_core/src/gplx/core/envs/Process_adp.java +++ b/100_core/src/gplx/core/envs/Process_adp.java @@ -13,3 +13,383 @@ 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.core.envs; import gplx.*; import gplx.core.*; +import gplx.Bool_; +import gplx.Bry_; +import gplx.Bry_bfr; +import gplx.Bry_bfr_; +import gplx.Err_; +import gplx.Gfo_invk; +import gplx.Gfo_invk_cmd; +import gplx.Gfo_invk_; +import gplx.GfoMsg; +import gplx.Gfo_usr_dlg; +import gplx.Gfo_usr_dlg_; +import gplx.GfsCtx; +import gplx.Io_url; +import gplx.Io_url_; +import gplx.List_adp; +import gplx.List_adp_; +import gplx.Rls_able; +import gplx.String_; +import gplx.Tfds; +import gplx.Virtual; +import gplx.core.threads.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import javax.management.RuntimeErrorException; +import gplx.core.brys.fmtrs.*; import gplx.core.strings.*; +import gplx.langs.gfs.*; +public class Process_adp implements Gfo_invk, Rls_able { + public boolean Enabled() {return enabled;} public Process_adp Enabled_(boolean v) {enabled = v; return this;} private boolean enabled = true; + public byte Exe_exists() {return exe_exists;} public Process_adp Exe_exists_(byte v) {exe_exists = v; return this;} private byte exe_exists = Bool_.__byte; + public Io_url Exe_url() {return exe_url;} public Process_adp Exe_url_(Io_url val) {exe_url = val; exe_exists = Bool_.__byte; return this;} Io_url exe_url; + public String Args_str() {return args_str;} public Process_adp Args_str_(String val) {args_str = val; return this;} private String args_str = ""; + public Bry_fmtr Args_fmtr() {return args_fmtr;} Bry_fmtr args_fmtr = Bry_fmtr.new_(""); + public boolean Args__include_quotes() {return args__include_quotes;} public void Args__include_quotes_(boolean v) {args__include_quotes = v;} private boolean args__include_quotes; + public byte Run_mode() {return run_mode;} public Process_adp Run_mode_(byte v) {run_mode = v; return this;} private byte run_mode = Run_mode_sync_block; + public static final byte Run_mode_async = 0, Run_mode_sync_block = 1, Run_mode_sync_timeout = 2; + public int Exit_code() {return exit_code;} int exit_code; + public boolean Exit_code_pass() {return exit_code == Exit_pass;} + public String Rslt_out() {return rslt_out;} private String rslt_out; + public Io_url Working_dir() {return working_dir;} public Process_adp Working_dir_(Io_url v) {working_dir = v; return this;} Io_url working_dir; + public Process_adp Cmd_args(String cmd, String args) {this.Exe_url_(Io_url_.new_fil_(cmd)); this.args_fmtr.Fmt_(args); return this;} + public Process_adp WhenBgn_add(Gfo_invk_cmd cmd) {whenBgnList.Add(cmd); return this;} + public Process_adp WhenBgn_del(Gfo_invk_cmd cmd) {whenBgnList.Del(cmd); return this;} + public int Thread_timeout() {return thread_timeout;} public Process_adp Thread_timeout_seconds_(int v) {thread_timeout = v * 1000; return this;} int thread_timeout = 0; + public int Thread_interval() {return thread_interval;} public Process_adp Thread_interval_(int v) {thread_interval = v; return this;} int thread_interval = 20; + public String Thread_kill_name() {return thread_kill_name;} public Process_adp Thread_kill_name_(String v) {thread_kill_name = v; return this;} private String thread_kill_name = ""; + public Io_url Tmp_dir() {return tmp_dir;} @gplx.Virtual public Process_adp Tmp_dir_(Io_url v) {tmp_dir = v; return this;} Io_url tmp_dir; + private Process_adp WhenBgn_run() {return Invk_cmds(whenBgnList);} List_adp whenBgnList = List_adp_.New(); + public Process_adp WhenEnd_add(Gfo_invk_cmd cmd) {whenEndList.Add(cmd); return this;} + public Process_adp WhenEnd_del(Gfo_invk_cmd cmd) {whenEndList.Del(cmd); return this;} + public Gfo_usr_dlg Prog_dlg() {return prog_dlg;} public Process_adp Prog_dlg_(Gfo_usr_dlg v) {prog_dlg = v; return this;} Gfo_usr_dlg prog_dlg; + public String Prog_fmt() {return prog_fmt;} public Process_adp Prog_fmt_(String v) {prog_fmt = v; return this;} private String prog_fmt = ""; // NOTE: set to "", else cmds that do not set prog_fmt will fail on fmtr.Fmt(null) + private Gfo_invk owner; + private Process_adp WhenEnd_run() {return Invk_cmds(whenEndList);} List_adp whenEndList = List_adp_.New(); + private Process_adp Invk_cmds(List_adp list) { + for (Object o : list) + ((Gfo_invk_cmd)o).Exec(); + return this; + } + public Process_adp Run(Object... args) { + if (String_.Len_eq_0(exe_url.Raw())) return this; // noop if exe_url is ""; + if (!args_fmtr.Fmt_null()) { + Bry_bfr tmp_bfr = Bry_bfr_.New(); + args_fmtr.Bld_bfr_many(tmp_bfr, args); + args_str = tmp_bfr.To_str_and_clear(); + } + prog_dlg.Log_many(GRP_KEY, "run", "running process: ~{0} ~{1}", exe_url.Raw(), args_str); + exit_code = Exit_init; + switch (run_mode) { + case Run_mode_async: return Run_async(); + case Run_mode_sync_timeout: return Run_wait(); + case Run_mode_sync_block: return Run_wait_sync(); + default: throw Err_.new_unhandled(run_mode); + } + } + public String[] Xto_process_bldr_args(String... args) { + String args_str = args_fmtr.Bld_str_many(args); + return To_process_bldr_args_utl(exe_url, args_str, false); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_enabled)) return enabled; + else if (ctx.Match(k, Invk_enabled_)) enabled = m.ReadBool("v"); + else if (ctx.Match(k, Invk_cmd)) return exe_url.Raw(); + else if (ctx.Match(k, Invk_cmd_)) this.Exe_url_(Bry_fmtr_eval_mgr_.Eval_url(cmd_url_eval, m.ReadBry("cmd"))); + else if (ctx.Match(k, Invk_args)) return String_.new_u8(args_fmtr.Fmt()); + else if (ctx.Match(k, Invk_args_)) args_fmtr.Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_cmd_args_)) {this.Exe_url_(Bry_fmtr_eval_mgr_.Eval_url(cmd_url_eval, m.ReadBry("cmd"))); args_fmtr.Fmt_(m.ReadBry("args"));} + else if (ctx.Match(k, Invk_mode_)) run_mode = m.ReadByte("v"); + else if (ctx.Match(k, Invk_timeout_)) thread_timeout = m.ReadInt("v"); + else if (ctx.Match(k, Invk_tmp_dir_)) tmp_dir = m.ReadIoUrl("v"); + else if (ctx.Match(k, Invk_owner)) return owner; + else return Gfo_invk_.Rv_unhandled; + return this; + } + static final String Invk_cmd = "cmd", Invk_cmd_ = "cmd_", Invk_args = "args", Invk_args_ = "args_", Invk_cmd_args_ = "cmd_args_", Invk_enabled = "enabled", Invk_enabled_ = "enabled_", Invk_mode_ = "mode_", Invk_timeout_ = "timeout_", Invk_tmp_dir_ = "tmp_dir_", Invk_owner = "owner"; + Bry_fmtr_eval_mgr cmd_url_eval; + public static Process_adp ini_(Gfo_invk owner, Gfo_usr_dlg usr_dlg, Process_adp process, Bry_fmtr_eval_mgr cmd_url_eval, byte run_mode, int timeout, String cmd_url_fmt, String args_fmt, String... args_keys) { + process.Run_mode_(run_mode).Thread_timeout_seconds_(timeout); + process.cmd_url_eval = cmd_url_eval; + Io_url cmd_url = Bry_fmtr_eval_mgr_.Eval_url(cmd_url_eval, Bry_.new_u8(cmd_url_fmt)); + process.Exe_url_(cmd_url).Tmp_dir_(cmd_url.OwnerDir()); + process.Args_fmtr().Fmt_(args_fmt).Keys_(args_keys); + process.owner = owner; + process.Prog_dlg_(usr_dlg); + return process; // return process for chaining + } + public static String Escape_ampersands_if_process_is_cmd(boolean os_is_wnt, String exe_url, String exe_args) { + return ( os_is_wnt + && String_.Eq(exe_url, "cmd")) + ? String_.Replace(exe_args, "&", "^&") // escape ampersands + : exe_args + ; + } + + public void Exe_and_args_(String exe, String args) { + Io_url exe_url = Bry_fmtr_eval_mgr_.Eval_url(this.cmd_url_eval, Bry_.new_u8(exe)); + this.Exe_url_(exe_url).Tmp_dir_(exe_url.OwnerDir()); + this.Args_fmtr().Fmt_(args); + } + public static Process_adp New(Gfo_usr_dlg usr_dlg, Bry_fmtr_eval_mgr eval_mgr, byte run_mode, int timeout, String exe, String args, String... args_keys) { + Process_adp rv = new Process_adp(); + rv.Prog_dlg_(usr_dlg); + rv.Run_mode_(run_mode); + rv.Thread_timeout_seconds_(timeout); + + rv.cmd_url_eval = eval_mgr; + Io_url exe_url = Bry_fmtr_eval_mgr_.Eval_url(eval_mgr, Bry_.new_u8(exe)); + rv.Exe_url_(exe_url).Tmp_dir_(exe_url.OwnerDir()); + rv.Args_fmtr().Fmt_(args).Keys_(args_keys); + return rv; + } + private Bry_fmtr notify_fmtr = Bry_fmtr.new_("", "process_exe_name", "process_exe_args", "process_seconds"); Bry_bfr notify_bfr = Bry_bfr_.Reset(255); + public Process UnderProcess() {return process;} Process process; + public void Rls() {if (process != null) process.destroy();} + public Process_adp Run_wait_sync() { + if (Env_.Mode_testing()) return Test_runs_add(); + Process_bgn(); + Process_start(); + Process_run_and_end(); + return this; + } + public Process_adp Run_start() { + if (Env_.Mode_testing()) return Test_runs_add(); + Process_bgn(); + Process_start(); + return this; + } + public Process_adp Run_async() { + if (Env_.Mode_testing()) return Test_runs_add(); + Process_bgn(); + Thread_ProcessAdp_async thread = new Thread_ProcessAdp_async(this); + thread.start(); + return this; + } + public Process_adp Run_wait() { + if (Env_.Mode_testing()) return Test_runs_add(); + int notify_interval = 100; int notify_checkpoint = notify_interval; + int elapsed = 0; + try { + Process_bgn(); + Thread_ProcessAdp_sync thread = new Thread_ProcessAdp_sync(this); + thread.start(); + // thread_timeout = 15000; + boolean thread_run = false; + notify_fmtr.Fmt_(prog_fmt); + while (thread.isAlive()) { + thread_run = true; + long prv = System_.Ticks(); + Thread_adp_.Sleep(thread_interval); +// try {thread.join(thread_interval);} +// catch (InterruptedException e) {throw Err_.err_key_(e, "gplx.ProcessAdp", "thread interrupted at join");} + long cur = System_.Ticks(); + int dif = (int)(cur - prv); + elapsed += dif; + if (prog_dlg != null) { + if (elapsed > notify_checkpoint) { + elapsed = notify_checkpoint; + notify_checkpoint += notify_interval; + notify_fmtr.Bld_bfr_many(notify_bfr, exe_url.NameAndExt(), args_str, elapsed / 1000); + prog_dlg.Prog_none(GRP_KEY, "notify.prog", notify_bfr.To_str_and_clear()); + } + } + if (thread_timeout == 0) break; + if (elapsed > thread_timeout) { + thread.interrupt(); + thread.Cancel(); + try {thread.join();} + catch (InterruptedException e) {throw Err_.new_exc(e, "core", "thread interrupted at timeout");} + break; + } + } + if (!thread_run) { + try {thread.join();} + catch (InterruptedException e) {throw Err_.new_exc(e, "core", "thread interrupted at join 2");} + } + } catch (Exception exc) { + Tfds.Write(Err_.Message_gplx_full(exc)); + } + if (elapsed != notify_checkpoint) { + notify_fmtr.Bld_bfr_many(notify_bfr, exe_url.NameAndExt(), args_str, elapsed / 1000); + if (prog_dlg != null) prog_dlg.Prog_none(GRP_KEY, "notify.prog", notify_bfr.To_str_and_clear()); + } + return this; + } + public synchronized void Process_post(String result) { + exit_code = process.exitValue(); + rslt_out = result; + WhenEnd_run(); + process.destroy(); + } + String Kill() { + if (thread_kill_name == String_.Empty) return ""; +// Runtime rt = Runtime.getRuntime(); + String kill_exe = "", kill_args = ""; + if (Op_sys.Cur().Tid_is_wnt()) { + kill_exe = "taskkill"; + kill_args = "/F /IM "; + } + else { + kill_exe = "kill"; + kill_args = "-9 "; + } + kill_args += thread_kill_name; + Process_adp kill_process = new Process_adp().Exe_url_(Io_url_.new_fil_(kill_exe)).Args_str_(kill_args).Thread_kill_name_(""); + boolean pass = kill_process.Run_wait().Exit_code_pass(); + return "killed|" + kill_exe + "|" + kill_args + "|" + pass + "|" + exe_url.Raw() + "|" + args_str; + } + synchronized void Process_bgn() { + exit_code = Exit_init; + rslt_out = ""; + WhenBgn_run(); + pb = new ProcessBuilder(To_process_bldr_args_utl(exe_url, args_str, args__include_quotes)); + pb.redirectErrorStream(true); // NOTE: need to redirectErrorStream or rdr.readLine() will hang; see inkscape and Ostfriesland Verkehr-de.svg + if (working_dir != null) + pb.directory(new File(working_dir.Xto_api())); + else if (!exe_url.OwnerDir().EqNull()) // only set workingDir if ownerDir is not null; NOTE: workingDir necessary for AdvMame; probably not a bad thing to do + pb.directory(new File(exe_url.OwnerDir().Xto_api())); + } ProcessBuilder pb; + protected Process Process_start() { + try {process = pb.start();} + catch (IOException e) { + java.util.List command_list = pb.command(); + String[] command_ary = new String[command_list.size()]; + command_ary = command_list.toArray(command_ary); + throw Err_.new_exc(e, "core", "process start failed", "args", String_.Concat_with_str(" ", command_ary)); + } + return process; + } + void Process_run_and_end() { + String_bldr sb = String_bldr_.new_(); + BufferedReader rdr = new BufferedReader(new InputStreamReader(process.getInputStream())); + try { + String line = ""; + while ((line = rdr.readLine()) != null) + sb.Add_str_w_crlf(line); + process.waitFor(); + } + catch (InterruptedException e) {throw Err_.new_exc(e, "core", "thread interrupted at wait_for", "exe_url", exe_url.Xto_api(), "exeArgs", args_str);} + catch (IOException e) {throw Err_.new_exc(e, "core", "io error", "exe_url", exe_url.Xto_api(), "exeArgs", args_str);} + exit_code = process.exitValue(); + WhenEnd_run(); + process.destroy(); + rslt_out = sb.To_str_and_clear(); + } + public void Process_term() { + try { + process.getInputStream().close(); + process.getErrorStream().close(); + } catch (IOException e) {} + process.destroy(); + } + public static void run_wait_(Io_url url) { + Process_adp process = new Process_adp().Exe_url_(url); + process.Run_start(); + process.Process_run_and_end(); + return; + } + public static final List_adp Test_runs = List_adp_.New(); + private Process_adp Test_runs_add() {Test_runs.Add(exe_url.Raw() + " " + args_str); exit_code = Exit_pass; return this;} + public static int run_wait_arg_(Io_url url, String arg) { + Process_adp process = new Process_adp(); + process.Exe_url_(url).Args_str_(arg).Run_wait(); + return process.Exit_code(); + } + private static final String GRP_KEY = "gplx.process"; + public static final int Exit_pass = 0, Exit_init = -1; + public static String[] To_process_bldr_args_utl(Io_url exe_url, String args_str, boolean include_quotes) { + List_adp list = List_adp_.New(); + list.Add(exe_url.Xto_api()); + String_bldr sb = String_bldr_.new_(); + int len = String_.Len(args_str); + boolean in_quotes = false; + for (int i = 0; i < len; i++) { + char c = String_.CharAt(args_str, i); + if (c == ' ' && !in_quotes) { // space encountered; assume arg done + list.Add(sb.To_str()); + sb.Clear(); + } + else if (c == '"') { // NOTE: ProcessBuilder seems to have issues with quotes; do not call sb.Add() + in_quotes = !in_quotes; + if (include_quotes) + sb.Add(c); + } + else + sb.Add(c); + } + if (sb.Has_some()) list.Add(sb.To_str()); + return list.To_str_ary(); + } +} +class Thread_ProcessAdp_async extends Thread { + public Thread_ProcessAdp_async(Process_adp process_adp) {this.process_adp = process_adp;} Process_adp process_adp; + public boolean Done() {return done;} boolean done = false; + public void Cancel() {process_adp.UnderProcess().destroy();} + public void run() { + process_adp.Run_wait(); + } +} +class Thread_ProcessAdp_sync extends Thread { + public Thread_ProcessAdp_sync(Process_adp process_adp) {this.process_adp = process_adp;} private final Process_adp process_adp; + public boolean Done() {return done;} private boolean done = false; + public void Cancel() { + process_adp.UnderProcess().destroy(); + } + public synchronized void run() { + done = false; + try { + Process process = process_adp.Process_start(); + StreamGobbler input_gobbler = new StreamGobbler("input", process.getInputStream()); + StreamGobbler error_gobbler = new StreamGobbler("error", process.getErrorStream()); + input_gobbler.start(); + error_gobbler.start(); + try {process.waitFor();} + catch (InterruptedException e) { + this.Cancel(); + String kill_rslt = process_adp.Kill(); + process_adp.Process_post(kill_rslt); + done = false; + return; + } + while (input_gobbler.isAlive()) { + try {input_gobbler.join(50);} + catch (InterruptedException e) {throw Err_.new_exc(e, "core", "thread interrupted at input gobbler");} + } + while (error_gobbler.isAlive()) { + try {error_gobbler.join(50);} + catch (InterruptedException e) {throw Err_.new_exc(e, "core", "thread interrupted at error gobbler");} + } + String result = input_gobbler.Rslt() + "\n" + error_gobbler.Rslt(); + process_adp.Process_post(result); + } catch (Exception e) { // NOTE: warn; do not throw, else multiple errors if timidity not available; PAGE:fr.u:Pentatoniques_altérées/Gammes_avec_deux_notes_altérées DATE:2015-05-08 + Gfo_usr_dlg_.Instance.Warn_many("", "", "process.sync failed; cmd=~{0} args=~{1}", process_adp.Exe_url().Raw(), process_adp.Args_str()); + } + finally {done = true;} + } +} +class StreamGobbler extends Thread { + private final String name; private final InputStream stream; + public StreamGobbler (String name, InputStream stream) {this.name = name; this.stream = stream;} + public String Rslt() {return rslt;} private String rslt; + public void run () { + try { + String_bldr sb = String_bldr_.new_(); + InputStreamReader isr = new InputStreamReader(stream); + BufferedReader br = new BufferedReader(isr); + while (true) { + String s = br.readLine(); + if (s == null) break; + sb.Add(s); + } + stream.close(); + rslt = sb.To_str_and_clear(); + } + catch (Exception e) {throw Err_.new_exc(e, "io", "failed reading stream", "name", name);} + } +} diff --git a/100_core/src/gplx/core/envs/Process_adp_tst.java b/100_core/src/gplx/core/envs/Process_adp_tst.java index a27517de8..d0aad74b6 100644 --- a/100_core/src/gplx/core/envs/Process_adp_tst.java +++ b/100_core/src/gplx/core/envs/Process_adp_tst.java @@ -13,3 +13,18 @@ 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.core.envs; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Process_adp_tst { + private Process_adp_fxt fxt = new Process_adp_fxt(); + @Test public void Escape_ampersands_if_process_is_cmd() { + fxt.Test_Escape_ampersands_if_process_is_cmd(Bool_.Y, "cmd" , "/c \"http://a.org?b=c&d=e\"", "/c \"http://a.org?b=c^&d=e\""); + fxt.Test_Escape_ampersands_if_process_is_cmd(Bool_.Y, "cmd1", "/c \"http://a.org?b=c&d=e\"", "/c \"http://a.org?b=c&d=e\""); + fxt.Test_Escape_ampersands_if_process_is_cmd(Bool_.N, "cmd" , "/c \"http://a.org?b=c&d=e\"", "/c \"http://a.org?b=c&d=e\""); + } +} +class Process_adp_fxt { + public void Test_Escape_ampersands_if_process_is_cmd(boolean os_is_wnt, String exe_url, String exe_args, String expd) { + Tfds.Eq(expd, Process_adp.Escape_ampersands_if_process_is_cmd(os_is_wnt, exe_url, exe_args)); + } +} diff --git a/100_core/src/gplx/core/envs/Runtime_.java b/100_core/src/gplx/core/envs/Runtime_.java index a27517de8..3481ace26 100644 --- a/100_core/src/gplx/core/envs/Runtime_.java +++ b/100_core/src/gplx/core/envs/Runtime_.java @@ -13,3 +13,20 @@ 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.core.envs; import gplx.*; import gplx.core.*; +public class Runtime_ { + // *** Hardware-related + public static int Cpu_count() {return Runtime.getRuntime().availableProcessors();} + public static long Memory_max() {return Runtime.getRuntime().maxMemory();} + public static long Memory_total() {return Runtime.getRuntime().totalMemory();} + public static long Memory_free() {return Runtime.getRuntime().freeMemory();} + public static long Memory_used() {return Memory_total() - Memory_free();} // REF:http://stackoverflow.com/questions/3571203/what-are-runtime-getruntime-totalmemory-and-freememory + + public static void Exec(String v) { + try { + Runtime.getRuntime().exec(v); + } catch (Exception e) { + Gfo_usr_dlg_.Instance.Log_many("", "", "runtime exec failed; err=~{0}", Err_.Message_gplx_log(e)); + } + } +} \ No newline at end of file diff --git a/100_core/src/gplx/core/envs/System_.java b/100_core/src/gplx/core/envs/System_.java index a27517de8..cbf4dfadf 100644 --- a/100_core/src/gplx/core/envs/System_.java +++ b/100_core/src/gplx/core/envs/System_.java @@ -13,3 +13,49 @@ 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.core.envs; import gplx.*; import gplx.core.*; +public class System_ { + // *** ticks + public static long Ticks() {return Ticks__test_val >= 0 ? Ticks__test_val : System.currentTimeMillis();} + public static int Ticks__elapsed_in_sec (long time_bgn) {return (int)(Ticks() - time_bgn) / 1000;} + public static int Ticks__elapsed_in_frac (long time_bgn) {return (int)(Ticks() - time_bgn);} + public static void Ticks__test_set(long v) {Ticks__test_val = v;} + public static void Ticks__test_add(long v) {Ticks__test_val += v;} + public static void Ticks__test_off() {Ticks__test_val = -1;} + private static long Ticks__test_val = -1; // in milliseconds + + // *** misc methods: Exit / GC + public static void Exit() {Exit_by_code(0);} + public static void Exit_by_code(int code) {System.exit(code);} + public static void Garbage_collect() {if (Env_.Mode_testing()) return; System.gc();} + + // *** java properties: getProperty; "-D" properties + public static String Prop__user_language() {return Prop__get(Prop_key__user_language);} + public static String Prop__user_name() {return Prop__get(Prop_key__user_name);} + public static String Prop__java_version() {return Prop__get(Prop_key__java_version);} + public static String Prop__java_home() {return Prop__get(Prop_key__java_home);} + private static String Prop__get(String key) { + return System.getProperty(key); + } + private static final String + Prop_key__user_language = "user.language" + , Prop_key__user_name = "user.name" + , Prop_key__java_version = "java.version" + , Prop_key__java_home = "java.version" + ; + + // *** environment variables: getenv + public static String Env__machine_name() { + String rv = ""; + rv = Env__get(Env_key__computername); if (String_.Len_gt_0(rv)) return rv; + rv = Env__get(Env_key__hostname); if (String_.Len_gt_0(rv)) return rv; + return "UNKNOWN_MACHINE_NAME"; + } + private static String Env__get(String key) { + return System.getenv(key); + } + private static final String + Env_key__computername = "COMPUTERNAME" + , Env_key__hostname = "HOSTNAME" + ; +} diff --git a/100_core/src/gplx/core/errs/Err_msg.java b/100_core/src/gplx/core/errs/Err_msg.java index a27517de8..4d79ca1e5 100644 --- a/100_core/src/gplx/core/errs/Err_msg.java +++ b/100_core/src/gplx/core/errs/Err_msg.java @@ -13,3 +13,35 @@ 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.core.errs; import gplx.*; import gplx.core.*; +public class Err_msg { + private final String msg; private Object[] args; + public Err_msg(String type, String msg, Object[] args) { + this.type = type; + this.msg = msg; + this.args = args; + } + public String Type() {return type;} private final String type; + public void Args_add(Object[] add) { + this.args = (Object[])Array_.Resize_add(args, add); + } + public String To_str() {return To_str_w_type(type, msg, args);} + public String To_str_wo_type() {return To_str(msg, args);} + public String To_str_wo_args() {return To_str(msg);} + + public static String To_str(String msg, Object... args) {return To_str_w_type(null, msg, args);} + public static String To_str_w_type(String type, String msg, Object... args) { + String rv = (type == null) ? "" : "<" + type + "> "; + rv += msg; + int len = args.length; + if (len > 0) { + rv += ":"; + for (int i = 0; i < len; i += 2) { + Object key = args[i]; + Object val = i + 1 < len ? args[i + 1] : "MISSING_VAL"; + rv += " " + Object_.Xto_str_strict_or_null_mark(key) + "=" + Object_.Xto_str_strict_or_null_mark(val); + } + } + return rv; + } +} diff --git a/100_core/src/gplx/core/gfo_ndes/GfoFld.java b/100_core/src/gplx/core/gfo_ndes/GfoFld.java index a27517de8..9980b3c64 100644 --- a/100_core/src/gplx/core/gfo_ndes/GfoFld.java +++ b/100_core/src/gplx/core/gfo_ndes/GfoFld.java @@ -13,3 +13,15 @@ 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.core.gfo_ndes; import gplx.*; import gplx.core.*; +import gplx.core.type_xtns.*; +public class GfoFld { + public String Key() {return key;} private String key; + public ClassXtn Type() {return type;} ClassXtn type; + public static final GfoFld Null = new_(String_.Null_mark, ObjectClassXtn.Instance); + public static GfoFld new_(String key, ClassXtn c) { + GfoFld rv = new GfoFld(); + rv.key = key; rv.type = c; + return rv; + } +} diff --git a/100_core/src/gplx/core/gfo_ndes/GfoFldList.java b/100_core/src/gplx/core/gfo_ndes/GfoFldList.java index a27517de8..0a26d2401 100644 --- a/100_core/src/gplx/core/gfo_ndes/GfoFldList.java +++ b/100_core/src/gplx/core/gfo_ndes/GfoFldList.java @@ -13,3 +13,14 @@ 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.core.gfo_ndes; import gplx.*; import gplx.core.*; +import gplx.core.type_xtns.*; +public interface GfoFldList { + int Count(); + boolean Has(String key); + int Idx_of(String key); + GfoFld Get_at(int i); + GfoFld FetchOrNull(String key); + GfoFldList Add(String key, ClassXtn c); + String To_str(); +} diff --git a/100_core/src/gplx/core/gfo_ndes/GfoFldList_.java b/100_core/src/gplx/core/gfo_ndes/GfoFldList_.java index a27517de8..994346dcb 100644 --- a/100_core/src/gplx/core/gfo_ndes/GfoFldList_.java +++ b/100_core/src/gplx/core/gfo_ndes/GfoFldList_.java @@ -13,3 +13,49 @@ 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.core.gfo_ndes; import gplx.*; import gplx.core.*; +import gplx.core.strings.*; import gplx.core.type_xtns.*; +public class GfoFldList_ { + public static final GfoFldList Null = new GfoFldList_null(); + public static GfoFldList new_() {return new GfoFldList_base();} + public static GfoFldList str_(String... names) { + GfoFldList rv = new GfoFldList_base(); + for (String name : names) + rv.Add(name, StringClassXtn.Instance); + return rv; + } +} +class GfoFldList_base implements GfoFldList { + public int Count() {return hash.Count();} + public boolean Has(String key) {return hash.Has(key);} + public int Idx_of(String key) { + Object rv = idxs.Get_by(key); + return rv == null ? List_adp_.Not_found : Int_.Cast(rv); + } + public GfoFld Get_at(int i) {return (GfoFld)hash.Get_at(i);} + public GfoFld FetchOrNull(String key) {return (GfoFld)hash.Get_by(key);} + public GfoFldList Add(String key, ClassXtn c) { + GfoFld fld = GfoFld.new_(key, c); + hash.Add(key, fld); + idxs.Add(key, idxs.Count()); + return this; + } + public String To_str() { + String_bldr sb = String_bldr_.new_(); + for (int i = 0; i < hash.Count(); i++) { + GfoFld fld = this.Get_at(i); + sb.Add(fld.Key()).Add("|"); + } + return sb.To_str(); + } + Ordered_hash hash = Ordered_hash_.New(); Hash_adp idxs = Hash_adp_.New(); // PERF: idxs used for Idx_of; need to recalc if Del ever added +} +class GfoFldList_null implements GfoFldList { + public int Count() {return 0;} + public boolean Has(String key) {return false;} + public int Idx_of(String key) {return List_adp_.Not_found;} + public GfoFld Get_at(int i) {return GfoFld.Null;} + public GfoFld FetchOrNull(String key) {return null;} + public GfoFldList Add(String key, ClassXtn typx) {return this;} + public String To_str() {return "<>";} +} \ No newline at end of file diff --git a/100_core/src/gplx/core/gfo_ndes/GfoNde.java b/100_core/src/gplx/core/gfo_ndes/GfoNde.java index a27517de8..307234e6e 100644 --- a/100_core/src/gplx/core/gfo_ndes/GfoNde.java +++ b/100_core/src/gplx/core/gfo_ndes/GfoNde.java @@ -13,3 +13,58 @@ 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.core.gfo_ndes; import gplx.*; import gplx.core.*; +import gplx.core.strings.*; import gplx.core.stores.*; +public class GfoNde implements Gfo_invk { + public GfoFldList Flds() {return flds;} GfoFldList flds; + public Hash_adp EnvVars() {return envVars;} Hash_adp envVars = Hash_adp_.New(); + public String Name() {return name;} public GfoNde Name_(String v) {name = v; return this;} private String name; + public Object ReadAt(int i) {ChkIdx(i); return ary[i];} + public void WriteAt(int i, Object val) {ChkIdx(i); ary[i] = val;} + public Object Read(String key) {int i = IndexOfOrFail(key); return ary[i];} + public void Write(String key, Object val) {int i = IndexOfOrFail(key); ary[i] = val;} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return Read(k);} + + public GfoNdeList Subs() {return subs;} GfoNdeList subs = GfoNdeList_.new_(); + public GfoFldList SubFlds() {return subFlds;} GfoFldList subFlds = GfoFldList_.new_(); + public void XtoStr_wtr(DataWtr wtr) {XtoStr_wtr(this, wtr);}// TEST + public String To_str() { + String_bldr sb = String_bldr_.new_(); + for (int i = 0; i < aryLen; i++) { + String key = i >= flds.Count() ? "<< NULL " + i + " >>" : flds.Get_at(i).Key(); + String val = i >= aryLen ? "<< NULL " + i + " >>" : Object_.Xto_str_strict_or_null_mark(ary[i]); + sb.Add(key).Add("=").Add(val); + } + return sb.To_str(); + } + int IndexOfOrFail(String key) { + int i = flds.Idx_of(key); + if ((i < 0 || i >= aryLen)) throw Err_.new_wo_type("field name not found", "name", key, "index", i, "count", this.Flds().Count()); + return i; + } + boolean ChkIdx(int i) {if (i < 0 || i >= aryLen) throw Err_.new_missing_idx(i, aryLen); return true;} + Object[] ary; int type; int aryLen; + @gplx.Internal protected GfoNde(int type, String name, GfoFldList flds, Object[] ary, GfoFldList subFlds, GfoNde[] subAry) { + this.type = type; this.name = name; this.flds = flds; this.ary = ary; aryLen = Array_.Len(ary); this.subFlds = subFlds; + for (GfoNde sub : subAry) + subs.Add(sub); + } + static void XtoStr_wtr(GfoNde nde, DataWtr wtr) { + if (nde.type == GfoNde_.Type_Leaf) { + wtr.WriteLeafBgn("flds"); + for (int i = 0; i < nde.ary.length; i++) + wtr.WriteData(nde.Flds().Get_at(i).Key(), nde.ReadAt(i)); + wtr.WriteLeafEnd(); + } + else { + if (nde.type == GfoNde_.Type_Node) // never write node info for root + wtr.WriteTableBgn(nde.Name(), nde.SubFlds()); + for (int i = 0; i < nde.Subs().Count(); i++) { + GfoNde sub = nde.Subs().FetchAt_asGfoNde(i); + XtoStr_wtr(sub, wtr); + } + if (nde.type == GfoNde_.Type_Node) + wtr.WriteNodeEnd(); + } + } +} diff --git a/100_core/src/gplx/core/gfo_ndes/GfoNdeFxt.java b/100_core/src/gplx/core/gfo_ndes/GfoNdeFxt.java index a27517de8..d00c73725 100644 --- a/100_core/src/gplx/core/gfo_ndes/GfoNdeFxt.java +++ b/100_core/src/gplx/core/gfo_ndes/GfoNdeFxt.java @@ -13,3 +13,22 @@ 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.core.gfo_ndes; import gplx.*; import gplx.core.*; +import gplx.core.type_xtns.*; +public class GfoNdeFxt { + public GfoNde root_(GfoNde... subs) {return GfoNde_.root_(subs);} + public GfoNde tbl_(String name, GfoNde... rows) {return GfoNde_.tbl_(name, GfoFldList_.Null, rows);} + public GfoNde nde_(String name, GfoFldList flds, GfoNde... subs) {return GfoNde_.tbl_(name, flds, subs);} + public GfoNde row_(GfoFldList flds, Object... vals) {return GfoNde_.vals_(flds, vals);} + public GfoNde row_vals_(Object... vals) {return GfoNde_.vals_(GfoFldList_by_count_(vals.length), vals);} + public GfoNde csv_dat_(GfoNde... rows) {return GfoNde_.tbl_("", GfoFldList_.Null, rows);} + public GfoNde csv_hdr_(GfoFldList flds, GfoNde... rows) {return GfoNde_.tbl_("", flds, rows);} + public static GfoNdeFxt new_() {return new GfoNdeFxt();} + + static GfoFldList GfoFldList_by_count_(int count) { + GfoFldList rv = GfoFldList_.new_(); + for (int i = 0; i < count; i++) + rv.Add("fld" + Int_.To_str(i), StringClassXtn.Instance); + return rv; + } +} diff --git a/100_core/src/gplx/core/gfo_ndes/GfoNdeList.java b/100_core/src/gplx/core/gfo_ndes/GfoNdeList.java index a27517de8..b16fc6fab 100644 --- a/100_core/src/gplx/core/gfo_ndes/GfoNdeList.java +++ b/100_core/src/gplx/core/gfo_ndes/GfoNdeList.java @@ -13,3 +13,13 @@ 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.core.gfo_ndes; import gplx.*; import gplx.core.*; +import gplx.core.lists.*; /*ComparerAble*/ +public interface GfoNdeList { + int Count(); + GfoNde FetchAt_asGfoNde(int index); + void Add(GfoNde rcd); + void Del(GfoNde rcd); + void Clear(); + void Sort_by(ComparerAble comparer); +} diff --git a/100_core/src/gplx/core/gfo_ndes/GfoNdeList_.java b/100_core/src/gplx/core/gfo_ndes/GfoNdeList_.java index a27517de8..76117aeac 100644 --- a/100_core/src/gplx/core/gfo_ndes/GfoNdeList_.java +++ b/100_core/src/gplx/core/gfo_ndes/GfoNdeList_.java @@ -13,3 +13,26 @@ 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.core.gfo_ndes; import gplx.*; import gplx.core.*; +import gplx.core.lists.*; /*ComparerAble*/ +public class GfoNdeList_ { + public static final GfoNdeList Null = new GfoNdeList_null(); + public static GfoNdeList new_() {return new GfoNdeList_base();} +} +class GfoNdeList_base implements GfoNdeList { + public int Count() {return list.Count();} + public GfoNde FetchAt_asGfoNde(int i) {return (GfoNde)list.Get_at(i);} + public void Add(GfoNde rcd) {list.Add(rcd);} + public void Del(GfoNde rcd) {list.Del(rcd);} + public void Clear() {list.Clear();} + public void Sort_by(ComparerAble comparer) {list.Sort_by(comparer);} + List_adp list = List_adp_.New(); +} +class GfoNdeList_null implements GfoNdeList { + public int Count() {return 0;} + public GfoNde FetchAt_asGfoNde(int index) {return null;} + public void Add(GfoNde rcd) {} + public void Del(GfoNde rcd) {} + public void Clear() {} + public void Sort_by(ComparerAble comparer) {} +} diff --git a/100_core/src/gplx/core/gfo_ndes/GfoNde_.java b/100_core/src/gplx/core/gfo_ndes/GfoNde_.java index a27517de8..3b92b339e 100644 --- a/100_core/src/gplx/core/gfo_ndes/GfoNde_.java +++ b/100_core/src/gplx/core/gfo_ndes/GfoNde_.java @@ -13,3 +13,33 @@ 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.core.gfo_ndes; import gplx.*; import gplx.core.*; +import gplx.core.type_xtns.*; import gplx.core.stores.*; +public class GfoNde_ { + public static final GfoNde[] Ary_empty = new GfoNde[0]; + public static GfoNde[] ary_(GfoNde... ary) {return ary;} + public static GfoNde as_(Object obj) {return obj instanceof GfoNde ? (GfoNde)obj : null;} + public static GfoNde root_(GfoNde... subs) {return new GfoNde(GfoNde_.Type_Root, "RootName", GfoFldList_.Null, Object_.Ary_empty, GfoFldList_.Null, subs);} + public static GfoNde tbl_(String name, GfoFldList flds, GfoNde... rows) {return new GfoNde(GfoNde_.Type_Node, name, flds, Object_.Ary_empty, flds, rows);} + public static GfoNde vals_(GfoFldList flds, Object[] ary) {return new GfoNde(GfoNde_.Type_Leaf, "row", flds, ary, GfoFldList_.Null, Ary_empty);} + public static GfoNde vals_params_(GfoFldList flds, Object... ary) {return new GfoNde(GfoNde_.Type_Leaf, "row", flds, ary, GfoFldList_.Null, Ary_empty);} + public static GfoNde nde_(String name, Object[] ary, GfoNde... subs) {return new GfoNde(GfoNde_.Type_Node, name, GfoFldList_.Null, ary, GfoFldList_.Null, subs);} + public static GfoNde rdr_(DataRdr rdr) { + try { + List_adp rows = List_adp_.New(); + GfoFldList flds = GfoFldList_.new_(); + int fldLen = rdr.FieldCount(); + for (int i = 0; i < fldLen; i++) + flds.Add(rdr.KeyAt(i), ObjectClassXtn.Instance); + while (rdr.MoveNextPeer()) { + Object[] valAry = new Object[fldLen]; + for (int i = 0; i < fldLen; i++) + valAry[i] = rdr.ReadAt(i); + rows.Add(GfoNde_.vals_(flds, valAry)); + } + return GfoNde_.tbl_("", flds, (GfoNde[])rows.To_ary(GfoNde.class)); + } + finally {rdr.Rls();} + } + @gplx.Internal protected static final int Type_Leaf = 1, Type_Node = 2, Type_Root = 3; +} diff --git a/100_core/src/gplx/core/gfo_regys/GfoMsgParser.java b/100_core/src/gplx/core/gfo_regys/GfoMsgParser.java index a27517de8..c04ba8833 100644 --- a/100_core/src/gplx/core/gfo_regys/GfoMsgParser.java +++ b/100_core/src/gplx/core/gfo_regys/GfoMsgParser.java @@ -13,3 +13,7 @@ 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.core.gfo_regys; import gplx.*; import gplx.core.*; +public interface GfoMsgParser { + GfoMsg ParseToMsg(String s); +} diff --git a/100_core/src/gplx/core/gfo_regys/GfoRegy.java b/100_core/src/gplx/core/gfo_regys/GfoRegy.java index a27517de8..cd82e866e 100644 --- a/100_core/src/gplx/core/gfo_regys/GfoRegy.java +++ b/100_core/src/gplx/core/gfo_regys/GfoRegy.java @@ -13,3 +13,81 @@ 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.core.gfo_regys; import gplx.*; import gplx.core.*; +import gplx.langs.gfs.*; import gplx.core.type_xtns.*; import gplx.core.interfaces.*; +public class GfoRegy implements Gfo_invk { + public int Count() {return hash.Count();} + public Hash_adp Parsers() {return parsers;} Hash_adp parsers = Hash_adp_.New(); + public GfoRegyItm FetchOrNull(String key) {return (GfoRegyItm)hash.Get_by(key);} + public Object FetchValOrFail(String key) { + GfoRegyItm rv = (GfoRegyItm)hash.Get_by(key); if (rv == null) throw Err_.new_wo_type("regy does not have key", "key", key); + return rv.Val(); + } + public Object FetchValOrNull(String key) {return FetchValOr(key, null);} + public Object FetchValOr(String key, Object or) { + GfoRegyItm itm = FetchOrNull(key); + return itm == null ? or : itm.Val(); + } + public void Del(String key) {hash.Del(key);} + public void RegObj(String key, Object val) {RegItm(key, val, GfoRegyItm.ValType_Obj, Io_url_.Empty);} + public void RegDir(Io_url dirUrl, String match, boolean recur, String chopBgn, String chopEnd) { + Io_url[] filUrls = Io_mgr.Instance.QueryDir_args(dirUrl).FilPath_(match).Recur_(recur).ExecAsUrlAry(); + if (filUrls.length == 0 && !Io_mgr.Instance.ExistsDir(dirUrl)) {UsrDlg_.Instance.Stop(UsrMsg.new_("dirUrl does not exist").Add("dirUrl", dirUrl.Xto_api())); return;} + for (Io_url filUrl : filUrls) { + String key = filUrl.NameAndExt(); + int pos = String_.Find_none; + if (String_.EqNot(chopBgn, "")) { + pos = String_.FindFwd(key, chopBgn); + if (pos == String_.Len(key) - 1) + throw Err_.new_wo_type(Err_ChopBgn, "key", key, "chopBgn", chopBgn); + else if (pos != String_.Find_none) + key = String_.Mid(key, pos + 1); + } + if (String_.EqNot(chopEnd, "")) { + pos = String_.FindBwd(key, chopEnd); + if (pos == 0) + throw Err_.new_wo_type(Err_ChopEnd, "key", key, "chopEnd", chopEnd); + else if (pos != String_.Find_none) + key = String_.MidByLen(key, 0, pos); + } + if (hash.Has(key)) throw Err_.new_wo_type(Err_Dupe, "key", key, "filUrl", filUrl); + RegItm(key, null, GfoRegyItm.ValType_Url, filUrl); + } + } + public void RegObjByType(String key, String val, String type) { + Object o = val; + if (String_.EqNot(type, StringClassXtn.Key_const)) { + ParseAble parser = (ParseAble)parsers.Get_by(type); + if (parser == null) throw Err_.new_wo_type("could not find parser", "type", type, "key", key, "val", val); + o = parser.ParseAsObj(val); + } + RegItm(key, o, GfoRegyItm.ValType_Obj, Io_url_.Empty); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_RegDir)) { + Io_url dir = m.ReadIoUrl("dir"); + String match = m.ReadStrOr("match", "*.*"); + boolean recur = m.ReadBoolOr("recur", false); + String chopBgn = m.ReadStrOr("chopBgn", ""); + String chopEnd = m.ReadStrOr("chopEnd", "."); + if (ctx.Deny()) return this; + RegDir(dir, match, recur, chopBgn, chopEnd); + } + else if (ctx.Match(k, Invk_RegObj)) { + String key = m.ReadStr("key"); + String val = m.ReadStr("val"); + String type = m.ReadStrOr("type", StringClassXtn.Key_const); + if (ctx.Deny()) return this; + RegObjByType(key, val, type); + } + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String Invk_RegDir = "RegDir", Invk_RegObj = "RegObj"; + void RegItm(String key, Object val, int valType, Io_url url) { + hash.Add_if_dupe_use_nth(key, new GfoRegyItm(key, val, valType, url)); + } + Hash_adp hash = Hash_adp_.New(); + public static final String Err_ChopBgn = "chopBgn results in null key", Err_ChopEnd = "chopEnd results in null key", Err_Dupe = "key already registered"; + public static final GfoRegy Instance = new GfoRegy(); GfoRegy() {} + @gplx.Internal protected static GfoRegy new_() {return new GfoRegy();} +} diff --git a/100_core/src/gplx/core/gfo_regys/GfoRegyItm.java b/100_core/src/gplx/core/gfo_regys/GfoRegyItm.java index a27517de8..7b8a74edd 100644 --- a/100_core/src/gplx/core/gfo_regys/GfoRegyItm.java +++ b/100_core/src/gplx/core/gfo_regys/GfoRegyItm.java @@ -13,3 +13,17 @@ 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.core.gfo_regys; import gplx.*; import gplx.core.*; +public class GfoRegyItm { + public String Key() {return key;} private String key; + public Object Val() {return val;} Object val; + public Io_url Url() {return url;} Io_url url; + public int ValType() {return valType;} int valType; + public GfoRegyItm(String key, Object val, int valType, Io_url url) {this.key = key; this.val = val; this.valType = valType; this.url = url;} + + public static final int + ValType_Obj = 1 + , ValType_Url = 2 + , ValType_B64 = 3 + ; +} diff --git a/100_core/src/gplx/core/gfo_regys/GfoRegy_RegDir_tst.java b/100_core/src/gplx/core/gfo_regys/GfoRegy_RegDir_tst.java index a27517de8..81cfeff7f 100644 --- a/100_core/src/gplx/core/gfo_regys/GfoRegy_RegDir_tst.java +++ b/100_core/src/gplx/core/gfo_regys/GfoRegy_RegDir_tst.java @@ -13,3 +13,47 @@ 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.core.gfo_regys; import gplx.*; import gplx.core.*; +import org.junit.*; +public class GfoRegy_RegDir_tst { + @Before public void setup() { + regy = GfoRegy.new_(); + Io_mgr.Instance.InitEngine_mem(); + root = Io_url_.mem_dir_("mem/root"); + } GfoRegy regy; Io_url root; + @Test public void Basic() { + ini_fil("101_tsta.txt"); + ini_fil("102_tstb.txt"); + ini_fil("103_tstc.png"); + ini_fil("dir1", "104_tstd.txt"); + regy.RegDir(root, "*.txt", false, "_", "."); + tst_Count(2); + tst_Exists("tsta"); + tst_Exists("tstb"); + } + @Test public void Err_dupe() { + ini_fil("101_tsta.txt"); + ini_fil("102_tsta.txt"); + try {regy.RegDir(root, "*.txt", false, "_", ".");} + catch (Exception e) {Tfds.Err_has(e, GfoRegy.Err_Dupe); return;} + Tfds.Fail_expdError(); + } + @Test public void Err_chopBgn() { + ini_fil("123_"); + try {regy.RegDir(root, "*", false, "_", ".");} + catch (Exception e) {Tfds.Err_has(e, GfoRegy.Err_ChopBgn); return;} + Tfds.Fail_expdError(); + } + @Test public void Err_chopEnd() { + ini_fil(".txt"); + try {regy.RegDir(root, "*.txt", false, "_", ".");} + catch (Exception e) {Tfds.Err_has(e, GfoRegy.Err_ChopEnd); return;} + Tfds.Fail_expdError(); + } + void tst_Count(int expd) {Tfds.Eq(expd, regy.Count());} + void tst_Exists(String expd) { + GfoRegyItm itm = regy.FetchOrNull(expd); + Tfds.Eq_nullNot(itm); + } + void ini_fil(String... nest) {Io_mgr.Instance.SaveFilStr(root.GenSubFil_nest(nest), "");} +} diff --git a/100_core/src/gplx/core/gfo_regys/GfoRegy_basic_tst.java b/100_core/src/gplx/core/gfo_regys/GfoRegy_basic_tst.java index a27517de8..5ffb383f3 100644 --- a/100_core/src/gplx/core/gfo_regys/GfoRegy_basic_tst.java +++ b/100_core/src/gplx/core/gfo_regys/GfoRegy_basic_tst.java @@ -13,3 +13,17 @@ 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.core.gfo_regys; import gplx.*; import gplx.core.*; +import org.junit.*; +public class GfoRegy_basic_tst { + @Before public void setup() { + regy = GfoRegy.new_(); + } GfoRegy regy; + @Test public void RegObjByType() { + regy.Parsers().Add("Io_url", Io_url_.Parser); + Io_url expd = Io_url_.new_any_("C:\\fil.txt"); + regy.RegObjByType("test", expd.Xto_api(), "Io_url"); + Io_url actl = (Io_url)regy.FetchValOr("test", Io_url_.Empty); + Tfds.Eq(expd.Xto_api(), actl.Xto_api()); + } +} diff --git a/100_core/src/gplx/core/interfaces/InjectAble.java b/100_core/src/gplx/core/interfaces/InjectAble.java index a27517de8..e24fc5b20 100644 --- a/100_core/src/gplx/core/interfaces/InjectAble.java +++ b/100_core/src/gplx/core/interfaces/InjectAble.java @@ -13,3 +13,7 @@ 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.core.interfaces; import gplx.*; import gplx.core.*; +public interface InjectAble { + void Inject(Object owner); +} diff --git a/100_core/src/gplx/core/interfaces/ParseAble.java b/100_core/src/gplx/core/interfaces/ParseAble.java index a27517de8..19ed66478 100644 --- a/100_core/src/gplx/core/interfaces/ParseAble.java +++ b/100_core/src/gplx/core/interfaces/ParseAble.java @@ -13,3 +13,7 @@ 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.core.interfaces; import gplx.*; import gplx.core.*; +public interface ParseAble { + Object ParseAsObj(String raw); +} diff --git a/100_core/src/gplx/core/interfaces/SrlAble.java b/100_core/src/gplx/core/interfaces/SrlAble.java index a27517de8..f39b8fcb5 100644 --- a/100_core/src/gplx/core/interfaces/SrlAble.java +++ b/100_core/src/gplx/core/interfaces/SrlAble.java @@ -13,3 +13,7 @@ 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.core.interfaces; import gplx.*; import gplx.core.*; +public interface SrlAble { + Object Srl(GfoMsg owner); +} diff --git a/100_core/src/gplx/core/interfaces/SrlAble_.java b/100_core/src/gplx/core/interfaces/SrlAble_.java index a27517de8..7e5add370 100644 --- a/100_core/src/gplx/core/interfaces/SrlAble_.java +++ b/100_core/src/gplx/core/interfaces/SrlAble_.java @@ -13,3 +13,51 @@ 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.core.interfaces; import gplx.*; import gplx.core.*; +import gplx.core.strings.*; +public class SrlAble_ { + public static SrlAble as_(Object obj) {return obj instanceof SrlAble ? (SrlAble)obj : null;} + public static String To_str(GfoMsg owner) { + String_bldr sb = String_bldr_.new_(); + To_str(owner, sb, 0, false); + return sb.To_str(); + } + public static String To_str(Object o) { + SrlAble s = SrlAble_.as_(o); if (s == null) return Object_.Xto_str_strict_or_null_mark(o); + GfoMsg m = GfoMsg_.new_parse_("root"); + s.Srl(m); + return To_str(m); + } + static void To_str(GfoMsg owner, String_bldr sb, int depth, boolean indentOn) { + String indent = String_.Repeat(" ", depth * 4); + if (indentOn) sb.Add(indent); + sb.Add(owner.Key()).Add(":"); + for (int i = 0; i < owner.Args_count(); i++) { + if (i != 0) sb.Add(" "); + Keyval kv = owner.Args_getAt(i); + sb.Add(kv.Key()).Add("=").Add("'").Add(Object_.Xto_str_strict_or_null_mark(kv.Val())).Add("'"); + } + int subsCount = owner.Subs_count(); + if (subsCount == 0) { + sb.Add(";"); + return; + } + else if (subsCount == 1) { + sb.Add("{"); + To_str(owner.Subs_getAt(0), sb, depth + 1, false); + sb.Add("}"); + return; + } + else { + sb.Add("{"); + if (subsCount > 1) sb.Add_char_crlf(); + for (int i = 0; i < subsCount; i++) { + GfoMsg sub = owner.Subs_getAt(i); + To_str(sub, sb, depth + 1, true); + sb.Add_char_crlf(); + } + sb.Add(indent); + sb.Add("}"); + } + } +} diff --git a/100_core/src/gplx/core/interfaces/SrlAble__tst.java b/100_core/src/gplx/core/interfaces/SrlAble__tst.java index a27517de8..118756e9e 100644 --- a/100_core/src/gplx/core/interfaces/SrlAble__tst.java +++ b/100_core/src/gplx/core/interfaces/SrlAble__tst.java @@ -13,3 +13,52 @@ 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.core.interfaces; import gplx.*; import gplx.core.*; +import org.junit.*; +public class SrlAble__tst { + @Test public void Basic() { + tst_Srl_ + ( GfoMsg_.new_cast_("itm").Add("key", "a").Add("val", 1) + , "itm:key='a' val='1';" + ); + + } + @Test public void Depth1_1() { + tst_Srl_ + ( GfoMsg_.new_cast_("itm").Add("key", "a").Add("val", 1).Subs_ + ( GfoMsg_.new_cast_("itm").Add("key", "aa").Add("val", 11) + ) + , String_.Concat_lines_crlf_skipLast + ( "itm:key='a' val='1'{itm:key='aa' val='11';}" + ) + ); + } + @Test public void Depth1_2() { + tst_Srl_ + ( GfoMsg_.new_cast_("itm").Add("key", "a").Add("val", 1).Subs_ + ( GfoMsg_.new_cast_("itm").Add("key", "aa").Add("val", 11) + , GfoMsg_.new_cast_("itm").Add("key", "ab").Add("val", 12) + ) + , String_.Concat_lines_crlf_skipLast + ( "itm:key='a' val='1'{" + , " itm:key='aa' val='11';" + , " itm:key='ab' val='12';" + , "}" + ) + ); + } + @Test public void Depth1_1_2() { + tst_Srl_ + ( GfoMsg_.new_cast_("itm").Add("key", "a").Add("val", 1).Subs_ + ( GfoMsg_.new_cast_("itm").Add("key", "aa").Add("val", 11).Subs_( + GfoMsg_.new_cast_("itm").Add("key", "aab").Add("val", 112) + ) + ) + , String_.Concat_lines_crlf_skipLast + ( "itm:key='a' val='1'{itm:key='aa' val='11'{itm:key='aab' val='112';}}" + ) + ); + } + void tst_Srl_(GfoMsg m, String expd) {Tfds.Eq(expd, SrlAble_.To_str(m));} +} +//class SrlAble__tst diff --git a/100_core/src/gplx/core/intls/Gfo_case_itm.java b/100_core/src/gplx/core/intls/Gfo_case_itm.java index a27517de8..81af43730 100644 --- a/100_core/src/gplx/core/intls/Gfo_case_itm.java +++ b/100_core/src/gplx/core/intls/Gfo_case_itm.java @@ -13,3 +13,10 @@ 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.core.intls; import gplx.*; import gplx.core.*; +public interface Gfo_case_itm { + int Hashcode_lo(); + int Len_lo(); + byte[] Asymmetric_bry(); + int Utf8_id_lo(); // lower-case byte or byte[] as single utf8 int +} diff --git a/100_core/src/gplx/core/intls/Gfo_case_mgr.java b/100_core/src/gplx/core/intls/Gfo_case_mgr.java index a27517de8..fcda5d993 100644 --- a/100_core/src/gplx/core/intls/Gfo_case_mgr.java +++ b/100_core/src/gplx/core/intls/Gfo_case_mgr.java @@ -13,3 +13,8 @@ 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.core.intls; import gplx.*; import gplx.core.*; +public interface Gfo_case_mgr { + byte Tid(); + Gfo_case_itm Get_or_null(byte bgn_byte, byte[] src, int bgn, int end); +} diff --git a/100_core/src/gplx/core/intls/Gfo_case_mgr_.java b/100_core/src/gplx/core/intls/Gfo_case_mgr_.java index a27517de8..c1264fad4 100644 --- a/100_core/src/gplx/core/intls/Gfo_case_mgr_.java +++ b/100_core/src/gplx/core/intls/Gfo_case_mgr_.java @@ -13,3 +13,7 @@ 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.core.intls; import gplx.*; import gplx.core.*; +public class Gfo_case_mgr_ { + public static final byte Tid_a7 = 0, Tid_u8 = 1, Tid_custom = 2; +} diff --git a/100_core/src/gplx/core/intls/Utf16_.java b/100_core/src/gplx/core/intls/Utf16_.java index a27517de8..616e39d35 100644 --- a/100_core/src/gplx/core/intls/Utf16_.java +++ b/100_core/src/gplx/core/intls/Utf16_.java @@ -13,3 +13,123 @@ 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.core.intls; import gplx.*; import gplx.core.*; +import gplx.core.primitives.*; +public class Utf16_ { + public static int Surrogate_merge(int hi, int lo) { // REF: http://perldoc.perl.org/Encode/Unicode.html + return 0x10000 + (hi - 0xD800) * 0x400 + (lo - 0xDC00); + } + public static void Surrogate_split(int v, Int_obj_ref hi, Int_obj_ref lo) { + hi.Val_((v - 0x10000) / 0x400 + 0xD800); + lo.Val_((v - 0x10000) % 0x400 + 0xDC00); + } + public static int Decode_to_int(byte[] ary, int pos) { + byte b0 = ary[pos]; + if ((b0 & 0x80) == 0) { + return b0; + } + else if ((b0 & 0xE0) == 0xC0) { + return ( b0 & 0x1f) << 6 + | ( ary[pos + 1] & 0x3f) + ; + } + else if ((b0 & 0xF0) == 0xE0) { + return ( b0 & 0x0f) << 12 + | ((ary[pos + 1] & 0x3f) << 6) + | ( ary[pos + 2] & 0x3f) + ; + } + else if ((b0 & 0xF8) == 0xF0) { + return ( b0 & 0x07) << 18 + | ((ary[pos + 1] & 0x3f) << 12) + | ((ary[pos + 2] & 0x3f) << 6) + | ( ary[pos + 3] & 0x3f) + ; + } + else throw Err_.new_wo_type("invalid utf8 byte", "byte", b0); + } + public static byte[] Encode_hex_to_bry(String raw) {return Encode_hex_to_bry(Bry_.new_a7(raw));} + public static byte[] Encode_hex_to_bry(byte[] raw) { + if (raw == null) return null; + int int_val = gplx.core.encoders.Hex_utl_.Parse_or(raw, Int_.Min_value); + return int_val == Int_.Min_value ? null : Encode_int_to_bry(int_val); + } + public static byte[] Encode_int_to_bry(int c) { + int bry_len = Len_by_int(c); + byte[] bry = new byte[bry_len]; + Encode_int(c, bry, 0); + return bry; + } + public static int Encode_char(int c, char[] c_ary, int c_pos, byte[] b_ary, int b_pos) { + if ((c > -1) + && (c < 128)) { + b_ary[ b_pos] = (byte)c; + return 1; + } + else if (c < 2048) { + b_ary[ b_pos] = (byte)(0xC0 | (c >> 6)); + b_ary[++b_pos] = (byte)(0x80 | (c & 0x3F)); + return 1; + } + else if((c > 55295) // 0xD800 + && (c < 56320)) { // 0xDFFF + if (c_pos >= c_ary.length) throw Err_.new_wo_type("incomplete surrogate pair at end of String", "char", c); + char nxt_char = c_ary[c_pos + 1]; + int v = Surrogate_merge(c, nxt_char); + b_ary[b_pos] = (byte)(0xF0 | (v >> 18)); + b_ary[++b_pos] = (byte)(0x80 | (v >> 12) & 0x3F); + b_ary[++b_pos] = (byte)(0x80 | (v >> 6) & 0x3F); + b_ary[++b_pos] = (byte)(0x80 | (v & 0x3F)); + return 2; + } + else { + b_ary[b_pos] = (byte)(0xE0 | (c >> 12)); + b_ary[++b_pos] = (byte)(0x80 | (c >> 6) & 0x3F); + b_ary[++b_pos] = (byte)(0x80 | (c & 0x3F)); + return 1; + } + } + public static int Encode_int(int c, byte[] src, int pos) { + if ((c > -1) + && (c < 128)) { + src[ pos] = (byte)c; + return 1; + } + else if (c < 2048) { + src[ pos] = (byte)(0xC0 | (c >> 6)); + src[++pos] = (byte)(0x80 | (c & 0x3F)); + return 2; + } + else if (c < 65536) { + src[pos] = (byte)(0xE0 | (c >> 12)); + src[++pos] = (byte)(0x80 | (c >> 6) & 0x3F); + src[++pos] = (byte)(0x80 | (c & 0x3F)); + return 3; + } + else if (c < 2097152) { + src[pos] = (byte)(0xF0 | (c >> 18)); + src[++pos] = (byte)(0x80 | (c >> 12) & 0x3F); + src[++pos] = (byte)(0x80 | (c >> 6) & 0x3F); + src[++pos] = (byte)(0x80 | (c & 0x3F)); + return 4; + } + else throw Err_.new_wo_type("UTF-16 int must be between 0 and 2097152", "char", c); + } + private static int Len_by_int(int c) { + if ((c > -1) + && (c < 128)) return 1; // 1 << 7 + else if (c < 2048) return 2; // 1 << 11 + else if (c < 65536) return 3; // 1 << 16 + else if (c < 2097152) return 4; + else throw Err_.new_wo_type("UTF-16 int must be between 0 and 2097152", "char", c); + } + public static int Len_by_char(int c) { + if ((c > -1) + && (c < 128)) return 1; // 1 << 7 + else if (c < 2048) return 2; // 1 << 11 + else if((c > 55295) // 0xD800 + && (c < 56320)) return 4; // 0xDFFF + else if (c < 65536) return 3; // 1 << 16 + else throw Err_.new_wo_type("UTF-16 int must be between 0 and 65536", "char", c); + } +} diff --git a/100_core/src/gplx/core/intls/Utf16__tst.java b/100_core/src/gplx/core/intls/Utf16__tst.java index a27517de8..2ebb8799b 100644 --- a/100_core/src/gplx/core/intls/Utf16__tst.java +++ b/100_core/src/gplx/core/intls/Utf16__tst.java @@ -13,3 +13,46 @@ 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.core.intls; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.primitives.*; +public class Utf16__tst { + private Utf16__fxt fxt = new Utf16__fxt(); + @Test public void Encode_decode() { +// fxt.Test_encode_decode(162, 194, 162); // cent +// fxt.Test_encode_decode(8364, 226, 130, 172); // euro + fxt.Test_encode_decode(150370, 240, 164, 173, 162); // example from [[UTF-8]]; should be encoded as two bytes + fxt.Test_encode_decode(143489, 240, 163, 130, 129); // EX: 駣𣂁脁 DATE:2017-04-22 + } + @Test public void Encode_as_bry_by_hex() { + fxt.Test_Encode_hex_to_bry("00", 0); + fxt.Test_Encode_hex_to_bry("41", 65); + fxt.Test_Encode_hex_to_bry("0041", 65); + fxt.Test_Encode_hex_to_bry("00C0", 195, 128); + } + @Test public void Surrogate() { + fxt.Test_surrogate(0x64321, 0xD950, 0xDF21); // example from w:UTF-16 + fxt.Test_surrogate(66643, 55297, 56403); // example from d:Boomerang + } +} +class Utf16__fxt { + private Int_obj_ref hi_ref = Int_obj_ref.New_neg1(), lo_ref = Int_obj_ref.New_neg1(); + public void Test_encode_decode(int expd_c_int, int... expd_int) { + byte[] expd = Bry_.New_by_ints(expd_int); + byte[] bfr = new byte[10]; + int bfr_len = Utf16_.Encode_int(expd_c_int, bfr, 0); + byte[] actl = Bry_.Mid_by_len(bfr, 0, bfr_len); + Tfds.Eq_ary(expd, actl); + int actl_c_int = Utf16_.Decode_to_int(bfr, 0); + Tfds.Eq(expd_c_int, actl_c_int); + } + public void Test_surrogate(int v, int hi, int lo) { + Tfds.Eq(v, Utf16_.Surrogate_merge((char)hi, (char)lo)); + Utf16_.Surrogate_split(v, hi_ref, lo_ref); + Tfds.Eq(hi, hi_ref.Val()); + Tfds.Eq(lo, lo_ref.Val()); + } + public void Test_Encode_hex_to_bry(String raw, int... expd) { + byte[] actl = Utf16_.Encode_hex_to_bry(raw); + Tfds.Eq_ary(Byte_.Ary_by_ints(expd), actl); + } +} diff --git a/100_core/src/gplx/core/intls/Utf8_.java b/100_core/src/gplx/core/intls/Utf8_.java index a27517de8..15a94bea1 100644 --- a/100_core/src/gplx/core/intls/Utf8_.java +++ b/100_core/src/gplx/core/intls/Utf8_.java @@ -13,3 +13,131 @@ 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.core.intls; import gplx.*; import gplx.core.*; +public class Utf8_ { + public static int Len_of_bry(byte[] ary) { + if (ary == null) return 0; + int rv = 0; + int pos = 0, len = ary.length; + while (pos < len) { + int char_len = Len_of_char_by_1st_byte(ary[pos]); + ++rv; + pos += char_len; + } + return rv; + } + public static int Len_of_char_by_1st_byte(byte b) {// SEE:w:UTF-8 + int i = b & 0xff; // PATCH.JAVA:need to convert to unsigned byte + switch (i) { + case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: + case 16: case 17: case 18: case 19: case 20: case 21: case 22: case 23: case 24: case 25: case 26: case 27: case 28: case 29: case 30: case 31: + case 32: case 33: case 34: case 35: case 36: case 37: case 38: case 39: case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47: + case 48: case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: case 58: case 59: case 60: case 61: case 62: case 63: + case 64: case 65: case 66: case 67: case 68: case 69: case 70: case 71: case 72: case 73: case 74: case 75: case 76: case 77: case 78: case 79: + case 80: case 81: case 82: case 83: case 84: case 85: case 86: case 87: case 88: case 89: case 90: case 91: case 92: case 93: case 94: case 95: + case 96: case 97: case 98: case 99: case 100: case 101: case 102: case 103: case 104: case 105: case 106: case 107: case 108: case 109: case 110: case 111: + case 112: case 113: case 114: case 115: case 116: case 117: case 118: case 119: case 120: case 121: case 122: case 123: case 124: case 125: case 126: case 127: + case 128: case 129: case 130: case 131: case 132: case 133: case 134: case 135: case 136: case 137: case 138: case 139: case 140: case 141: case 142: case 143: + case 144: case 145: case 146: case 147: case 148: case 149: case 150: case 151: case 152: case 153: case 154: case 155: case 156: case 157: case 158: case 159: + case 160: case 161: case 162: case 163: case 164: case 165: case 166: case 167: case 168: case 169: case 170: case 171: case 172: case 173: case 174: case 175: + case 176: case 177: case 178: case 179: case 180: case 181: case 182: case 183: case 184: case 185: case 186: case 187: case 188: case 189: case 190: case 191: + return 1; + case 192: case 193: case 194: case 195: case 196: case 197: case 198: case 199: case 200: case 201: case 202: case 203: case 204: case 205: case 206: case 207: + case 208: case 209: case 210: case 211: case 212: case 213: case 214: case 215: case 216: case 217: case 218: case 219: case 220: case 221: case 222: case 223: + return 2; + case 224: case 225: case 226: case 227: case 228: case 229: case 230: case 231: case 232: case 233: case 234: case 235: case 236: case 237: case 238: case 239: + return 3; + case 240: case 241: case 242: case 243: case 244: case 245: case 246: case 247: + return 4; + default: throw Err_.new_wo_type("invalid initial utf8 byte", "byte", b); + } + } + public static byte[] Get_char_at_pos_as_bry(byte[] bry, int pos) { + int len = Len_of_char_by_1st_byte(bry[pos]); + return Bry_.Mid(bry, pos, pos + len); + } + public static byte[] Increment_char_at_last_pos(byte[] bry) { // EX: abc -> abd; complexity is for multi-byte chars + int bry_len = bry.length; if (bry_len == 0) return bry; + int pos = bry_len - 1; + while (true) { // loop bwds + int cur_char_pos0 = Get_prv_char_pos0_old(bry, pos); // get byte0 of char + int cur_char_len = (pos - cur_char_pos0) + 1; // calc len of char + int nxt_char = Codepoint_max; + if (cur_char_len == 1) { // len=1; just change 1 byte + nxt_char = Increment_char(bry[cur_char_pos0]); // get next char + if (nxt_char < 128) { // single-byte char; just change pos + bry = Bry_.Copy(bry); // always return new bry; never reuse existing + bry[cur_char_pos0] = (byte)nxt_char; + return bry; + } + } + int cur_char = Utf16_.Decode_to_int(bry, cur_char_pos0); + nxt_char = Increment_char(cur_char); + if (nxt_char != Int_.Min_value) { + byte[] nxt_char_as_bry = Utf16_.Encode_int_to_bry(nxt_char); + bry = Bry_.Add(Bry_.Mid(bry, 0, cur_char_pos0), nxt_char_as_bry); + return bry; + } + pos = cur_char_pos0 - 1; + if (pos < 0) return null; + } + } + public static int Get_prv_char_pos0_old(byte[] bry, int pos) { // find pos0 of char while moving bwd through bry; see test + int stop = pos - 4; // UTF8 char has max of 4 bytes + if (stop < 0) stop = 0; // if at pos 0 - 3, stop at 0 + for (int i = pos - 1; i >= stop; i--) { // start at pos - 1, and move bwd; NOTE: pos - 1 to skip pos, b/c pos will never definitively yield any char_len info + byte b = bry[i]; + int char_len = Len_of_char_by_1st_byte(b); + switch (char_len) { // if char_len is multi-byte and pos is at correct multi-byte pos (pos - i = # of bytes - 1), then pos0 found; EX: � = {226,130,172}; 172 is skipped; 130 has len of 1 -> continue; 226 has len of 3 and is found at correct pos for 3 byte char -> return + case 2: if (pos - i == 1) return i; break; + case 3: if (pos - i == 2) return i; break; + case 4: if (pos - i == 3) return i; break; + } + } + return pos; // no mult-byte char found; return pos + } + public static int Get_prv_char_pos0(byte[] src, int cur) { // find pos0 of char while moving bwd through src; see test + // do bounds checks + if (cur == 0) return -1; + if (cur <= -1 || cur > src.length) throw Err_.new_wo_type("invalid index for get_prv_char_pos0", "src", src, "cur", cur); + + // start at cur - 1; note bounds checks above + int pos = cur - 1; + + // get 1st byte and check if ASCII for (a) error-checking (ASCII can only be in 1st byte); (b) performance + byte b = src[pos]; + if (b >= 0 && b <= Byte_.Max_value_127) return pos; + + // loop maximum of 4 times; note that UTF8 char has max of 4 bytes + for (int i = 0; i < 4; i++) { + int char_len = Len_of_char_by_1st_byte(b); + switch (char_len) { // if char_len is multi-byte and cur is at correct multi-byte pos (cur - i = # of bytes - 1), then pos0 found; EX: � = {226,130,172}; 172 is skipped; 130 has len of 1 -> continue; 226 has len of 3 and is found at correct cur for 3 byte char -> return + case 2: if (i == 1) return pos; break; + case 3: if (i == 2) return pos; break; + case 4: if (i == 3) return pos; break; + } + + // decrement and set byte + pos--; + b = src[pos]; + } + + throw Err_.new_wo_type("could not get prv_char", "src", src, "cur", cur); + } + @gplx.Internal protected static int Increment_char(int cur) { + while (cur++ < Codepoint_max) { + if (cur == Codepoint_surrogate_bgn) cur = Codepoint_surrogate_end + 1; // skip over surrogate range + if (!Codepoint_valid(cur)) continue; + return cur; + } + return Int_.Min_value; + } + private static boolean Codepoint_valid(int v) { + return Character.isDefined(v); + } + public static final int + Codepoint_max = 0x10FFFF //see http://unicode.org/glossary/ + , Codepoint_surrogate_bgn = 0xD800 + , Codepoint_surrogate_end = 0xDFFF + ; +} diff --git a/100_core/src/gplx/core/intls/Utf8__tst.java b/100_core/src/gplx/core/intls/Utf8__tst.java index a27517de8..5d32c1488 100644 --- a/100_core/src/gplx/core/intls/Utf8__tst.java +++ b/100_core/src/gplx/core/intls/Utf8__tst.java @@ -13,3 +13,55 @@ 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.core.intls; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Utf8__tst { + private Utf8__fxt fxt = new Utf8__fxt(); + @Test public void Get_prv_char_pos0() { + fxt.Test__Get_prv_char_pos0("abcd", 3); // len=1; (note that bry.len = 4) + fxt.Test__Get_prv_char_pos0("a", 0); // len=1; short-String + fxt.Test__Get_prv_char_pos0("abc¢", 3); // len=2; (note that bry.len = 5) + fxt.Test__Get_prv_char_pos0("abc€", 3); // len=3; (note that bry.len = 6) + fxt.Test__Get_prv_char_pos0("abc" + String_.new_u8(Byte_.Ary_by_ints(240, 164, 173, 162)), 3); // len=4; (note that bry.len = 7) + } + @Test public void Increment_char_at_last_pos() { + fxt.Test_Increment_char_at_last_pos("a", "b"); + fxt.Test_Increment_char_at_last_pos("abc", "abd"); + fxt.Test_Increment_char_at_last_pos("É", "Ê"); // len=2 + fxt.Test_Increment_char_at_last_pos("€", "₭"); // len=3 + } +// @Test public void Increment_char_at_last_pos_exhaustive_check() { // check all values; commented for perf +// Bry_bfr bfr = Bry_bfr_.New(); +// int bgn = 32; +// while (true) { +// byte[] bgn_bry = Utf16_.Encode_int_to_bry(bgn); +// int end = Utf8_.Increment_char(bgn); +// if (end == Utf8_.Codepoint_max) break; +//// if (bgn > 1024 * 1024) break; +// byte[] end_by_codepoint_next = Utf16_.Encode_int_to_bry(end); +// byte[] end_by_increment_char = Utf8_.Increment_char_at_last_pos(bgn_bry); +// if (!Bry_.Eq(end_by_codepoint_next, end_by_increment_char)) { +// Tfds.Write(bgn); +// } +//// bfr .Add_int_variable(bgn).Add_byte(Byte_ascii.Tab) +//// .Add(bgn_bry).Add_byte(Byte_ascii.Tab) +//// .Add(end_by_codepoint_next).Add_byte(Byte_ascii.Tab) +//// .Add(end_by_increment_char).Add_byte(Byte_ascii.Tab) +//// .Add_byte_nl() +//// ; +// bgn = end; +// bgn_bry = end_by_codepoint_next; +// } +// Tfds.WriteText(bfr.To_str_and_clear()); +// } +} +class Utf8__fxt { + public void Test__Get_prv_char_pos0(String src_str, int expd) { + byte[] src_bry = Bry_.new_u8(src_str); + Tfds.Eq(expd, Utf8_.Get_prv_char_pos0 (src_bry, src_bry.length)); + Tfds.Eq(expd, Utf8_.Get_prv_char_pos0_old(src_bry, src_bry.length - 1)); + } + public void Test_Increment_char_at_last_pos(String str, String expd) { + Tfds.Eq(expd, String_.new_u8(Utf8_.Increment_char_at_last_pos(Bry_.new_u8(str)))); + } +} diff --git a/100_core/src/gplx/core/ios/IoEngine.java b/100_core/src/gplx/core/ios/IoEngine.java index a27517de8..a960d263b 100644 --- a/100_core/src/gplx/core/ios/IoEngine.java +++ b/100_core/src/gplx/core/ios/IoEngine.java @@ -13,3 +13,142 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.consoles.*; import gplx.core.criterias.*; +import gplx.core.ios.streams.*; +public interface IoEngine { + String Key(); + boolean ExistsFil_api(Io_url url); + void SaveFilText_api(IoEngine_xrg_saveFilStr args); + String LoadFilStr(IoEngine_xrg_loadFilStr args); + void DeleteFil_api(IoEngine_xrg_deleteFil args); + void CopyFil(IoEngine_xrg_xferFil args); + void MoveFil(IoEngine_xrg_xferFil args); + IoItmFil QueryFil(Io_url url); + void UpdateFilAttrib(Io_url url, IoItmAttrib atr); // will fail if file does not exists + void UpdateFilModifiedTime(Io_url url, DateAdp modified); + IoStream OpenStreamRead(Io_url url); + IoStream OpenStreamWrite(IoEngine_xrg_openWrite args); + void XferFil(IoEngine_xrg_xferFil args); + void RecycleFil(IoEngine_xrg_recycleFil xrg); + boolean Truncate_fil(Io_url url, long size); + + boolean ExistsDir(Io_url url); + void CreateDir(Io_url url); // creates all folder levels (EX: C:\a\b\c\ will create C:\a\ and C:\a\b\). will not fail if called on already existing folders. + void DeleteDir(Io_url url); + void MoveDir(Io_url src, Io_url trg); // will fail if trg exists + void CopyDir(Io_url src, Io_url trg); + IoItmDir QueryDir(Io_url url); + + void DeleteDirDeep(IoEngine_xrg_deleteDir args); + void MoveDirDeep(IoEngine_xrg_xferDir args); // will fail if trg exists + IoItmDir QueryDirDeep(IoEngine_xrg_queryDir args); + void XferDir(IoEngine_xrg_xferDir args); + boolean DownloadFil(IoEngine_xrg_downloadFil xrg); + Io_stream_rdr DownloadFil_as_rdr(IoEngine_xrg_downloadFil xrg); +} +class IoEngineUtl { + public int BufferLength() {return bufferLength;} public void BufferLength_set(int v) {bufferLength = v;} int bufferLength = 4096; // 0x1000 + public void DeleteRecycleGplx(IoEngine engine, IoEngine_xrg_recycleFil xrg) { + Io_url recycleUrl = xrg.RecycleUrl(); + if (recycleUrl.Type_fil()) + engine.MoveFil(IoEngine_xrg_xferFil.move_(xrg.Url(), recycleUrl).Overwrite_(false).ReadOnlyFails_(true)); + else + engine.MoveDirDeep(IoEngine_xrg_xferDir.move_(xrg.Url(), recycleUrl).Overwrite_(false).ReadOnlyFails_(true)); + } + public void DeleteDirDeep(IoEngine engine, Io_url dirUrl, IoEngine_xrg_deleteDir args) { + Console_adp usrDlg = args.UsrDlg(); + IoItmDir dir = engine.QueryDir(dirUrl); if (!dir.Exists()) return; + for (Object subDirObj : dir.SubDirs()) { + IoItmDir subDir = (IoItmDir)subDirObj; + if (!args.SubDirScanCrt().Matches(subDir)) continue; + if (args.Recur()) DeleteDirDeep(engine, subDir.Url(), args); + } + for (Object subFilObj : dir.SubFils()) { + IoItmFil subFil = (IoItmFil)subFilObj; + if (!args.MatchCrt().Matches(subFil)) continue; + Io_url subFilUrl = subFil.Url(); + try {engine.DeleteFil_api(IoEngine_xrg_deleteFil.new_(subFilUrl).ReadOnlyFails_(args.ReadOnlyFails()));} + catch (Exception exc) {usrDlg.Write_fmt_w_nl(Err_.Message_lang(exc));} + } + // all subs deleted; now delete dir + if (!args.MatchCrt().Matches(dir)) return; + try {engine.DeleteDir(dir.Url());} + catch (Exception exc) {usrDlg.Write_fmt_w_nl(Err_.Message_lang(exc));} + } + public void XferDir(IoEngine srcEngine, Io_url src, IoEngine trgEngine, Io_url trg, IoEngine_xrg_xferDir args) { + trgEngine.CreateDir(trg); + IoItmDir srcDir = QueryDirDeep(srcEngine, IoEngine_xrg_queryDir.new_(src).Recur_(false)); + for (Object subSrcObj : srcDir.SubDirs()) { + IoItmDir subSrc = (IoItmDir)subSrcObj; + if (!args.SubDirScanCrt().Matches(subSrc)) continue; + if (!args.MatchCrt().Matches(subSrc)) continue; + Io_url subTrg = trg.GenSubDir_nest(subSrc.Url().NameOnly()); //EX: C:\abc\def\ -> C:\123\ + def\ + if (args.Recur()) XferDir(srcEngine, subSrc.Url(), trgEngine, subTrg, args); + } + IoItmList srcFils = IoItmList.list_(src.Info().CaseSensitive()); + for (Object srcFilObj : srcDir.SubFils()) { + IoItmFil srcFil = (IoItmFil)srcFilObj; + if (args.MatchCrt().Matches(srcFil)) srcFils.Add(srcFil); + } + for (Object srcFilObj : srcFils) { + IoItmFil srcFil = (IoItmFil)srcFilObj; + Io_url srcFilPath = srcFil.Url(); + Io_url trgFilPath = trg.GenSubFil(srcFilPath.NameAndExt()); //EX: C:\abc\fil.txt -> C:\123\ + fil.txt + IoEngine_xrg_xferFil xferArgs = args.Type_move() ? IoEngine_xrg_xferFil.move_(srcFilPath, trgFilPath).Overwrite_(args.Overwrite()) : IoEngine_xrg_xferFil.copy_(srcFilPath, trgFilPath).Overwrite_(args.Overwrite()); + XferFil(srcEngine, xferArgs); + } + if (args.Type_move()) srcEngine.DeleteDirDeep(IoEngine_xrg_deleteDir.new_(src).Recur_(args.Recur()).ReadOnlyFails_(args.ReadOnlyFails()));// this.DeleteDirDeep(srcEngine, src, IoEngine_xrg_deleteItm.new_(src).Recur_(args.Recur()).ReadOnlyIgnored_(args.ReadOnlyIgnored())); + } + public void XferFil(IoEngine srcEngine, IoEngine_xrg_xferFil args) { + Io_url src = args.Src(), trg = args.Trg(); + if (String_.Eq(srcEngine.Key(), trg.Info().EngineKey())) { + if (args.Type_move()) + srcEngine.MoveFil(args); + else + srcEngine.CopyFil(args); + } + else { + TransferStream(src, trg); + if (args.Type_move()) srcEngine.DeleteFil_api(IoEngine_xrg_deleteFil.new_(src)); + } + } + public IoItmDir QueryDirDeep(IoEngine engine, IoEngine_xrg_queryDir args) { + IoItmDir rv = IoItmDir_.top_(args.Url()); + rv.Exists_set(QueryDirDeepCore(rv, args.Url(), engine, args.Recur(), args.SubDirScanCrt(), args.DirCrt(), args.FilCrt(), args.UsrDlg(), args.DirInclude())); + return rv; + } + static boolean QueryDirDeepCore(IoItmDir ownerDir, Io_url url, IoEngine engine, boolean recur, Criteria subDirScanCrt, Criteria dirCrt, Criteria filCrt, Console_adp usrDlg, boolean dirInclude) { + if (usrDlg.Canceled_chk()) return false; + if (usrDlg.Enabled()) usrDlg.Write_tmp(String_.Concat("scan: ", url.Raw())); + IoItmDir scanDir = engine.QueryDir(url); + for (Object subDirObj : scanDir.SubDirs()) { + IoItmDir subDir = (IoItmDir)subDirObj; + if (!subDirScanCrt.Matches(subDir)) continue; + if (dirCrt.Matches(subDir)) { + ownerDir.SubDirs().Add(subDir); // NOTE: always add subDir; do not use dirCrt here, else its subFils will be added to non-existent subDir + } + if (recur) + QueryDirDeepCore(subDir, subDir.Url(), engine, recur, subDirScanCrt, dirCrt, filCrt, usrDlg, dirInclude); + } + for (Object subFilObj : scanDir.SubFils()) { + IoItmFil subFil = (IoItmFil)subFilObj; + if (filCrt.Matches(subFil)) ownerDir.SubFils().Add(subFil); + } + return scanDir.Exists(); + } + void TransferStream(Io_url src, Io_url trg) { + IoStream srcStream = null; + IoStream trgStream = null; + try { + srcStream = IoEnginePool.Instance.Get_by(src.Info().EngineKey()).OpenStreamRead(src); + trgStream = IoEngine_xrg_openWrite.new_(trg).Exec(); + srcStream.Transfer(trgStream, bufferLength); + } + finally { + if (srcStream != null) srcStream.Rls(); + if (trgStream != null) trgStream.Rls(); + } + } + public static IoEngineUtl new_() {return new IoEngineUtl();} IoEngineUtl() {} +} diff --git a/100_core/src/gplx/core/ios/IoEngineFxt.java b/100_core/src/gplx/core/ios/IoEngineFxt.java index a27517de8..74cfa7f5c 100644 --- a/100_core/src/gplx/core/ios/IoEngineFxt.java +++ b/100_core/src/gplx/core/ios/IoEngineFxt.java @@ -13,3 +13,40 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class IoEngineFxt { + IoEngine EngineOf(Io_url url) {return IoEnginePool.Instance.Get_by(url.Info().EngineKey());} + public void tst_ExistsPaths(boolean expd, Io_url... ary) { + for (Io_url fil : ary) { + if (fil.Type_dir()) + Tfds.Eq(expd, EngineOf(fil).ExistsDir(fil), "ExistsDir failed; dir={0}", fil); + else + Tfds.Eq(expd, EngineOf(fil).ExistsFil_api(fil), "ExistsFil failed; fil={0}", fil); + } + } + public void tst_LoadFilStr(Io_url fil, String expd) {Tfds.Eq(expd, EngineOf(fil).LoadFilStr(IoEngine_xrg_loadFilStr.new_(fil)));} + public void run_SaveFilText(Io_url fil, String expd) {EngineOf(fil).SaveFilText_api(IoEngine_xrg_saveFilStr.new_(fil, expd));} + public void run_UpdateFilModifiedTime(Io_url fil, DateAdp modifiedTime) {EngineOf(fil).UpdateFilModifiedTime(fil, modifiedTime);} + public void tst_QueryFilReadOnly(Io_url fil, boolean expd) {Tfds.Eq(expd, EngineOf(fil).QueryFil(fil).ReadOnly());} + public IoEngineFxt tst_QueryFil_size(Io_url fil, long expd) {Tfds.Eq(expd, EngineOf(fil).QueryFil(fil).Size()); return this;} + public IoEngineFxt tst_QueryFil_modifiedTime(Io_url fil, DateAdp expd) {Tfds.Eq_date(expd, EngineOf(fil).QueryFil(fil).ModifiedTime()); return this;} + public IoItmDir tst_ScanDir(Io_url dir, Io_url... expd) { + IoItmDir dirItem = EngineOf(dir).QueryDir(dir); + Io_url[] actl = new Io_url[dirItem.SubDirs().Count() + dirItem.SubFils().Count()]; + for (int i = 0; i < dirItem.SubDirs().Count(); i++) { + IoItmDir subDir = IoItmDir_.as_(dirItem.SubDirs().Get_at(i)); + actl[i] = subDir.Url(); + } + for (int i = 0; i < dirItem.SubFils().Count(); i++) { + IoItmFil subFil = IoItmFil_.as_(dirItem.SubFils().Get_at(i)); + actl[i + dirItem.SubDirs().Count()] = subFil.Url(); + } + Tfds.Eq_ary_str(expd, actl); + return dirItem; + } + public static IoEngineFxt new_() { + IoEngineFxt rv = new IoEngineFxt(); + return rv; + } + public IoEngineFxt() {} +} diff --git a/100_core/src/gplx/core/ios/IoEnginePool.java b/100_core/src/gplx/core/ios/IoEnginePool.java index a27517de8..e85628a69 100644 --- a/100_core/src/gplx/core/ios/IoEnginePool.java +++ b/100_core/src/gplx/core/ios/IoEnginePool.java @@ -13,3 +13,20 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class IoEnginePool { + private final Hash_adp hash = Hash_adp_.New(); + public void Add_if_dupe_use_nth(IoEngine engine) { + hash.Del(engine.Key()); + hash.Add(engine.Key(), engine); + } + public IoEngine Get_by(String key) { + IoEngine rv = (IoEngine)hash.Get_by(key); + return rv == null ? IoEngine_.Mem : rv; // rv == null when url is null or empty; return Mem which should be a noop; DATE:2013-06-04 + } + public static final IoEnginePool Instance = new IoEnginePool(); + IoEnginePool() { + this.Add_if_dupe_use_nth(IoEngine_.Sys); + this.Add_if_dupe_use_nth(IoEngine_.Mem); + } +} \ No newline at end of file diff --git a/100_core/src/gplx/core/ios/IoEngine_.java b/100_core/src/gplx/core/ios/IoEngine_.java index a27517de8..a49894d82 100644 --- a/100_core/src/gplx/core/ios/IoEngine_.java +++ b/100_core/src/gplx/core/ios/IoEngine_.java @@ -13,3 +13,16 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class IoEngine_ { + public static final String SysKey = "sys"; + public static final String MemKey = "mem"; + + public static final IoEngine Sys = IoEngine_system.new_(); + public static final IoEngine_memory Mem = IoEngine_memory.new_(MemKey); + public static IoEngine Mem_init_() { + Mem.Clear(); + return Mem; + } + public static IoEngine mem_new_(String key) {return IoEngine_memory.new_(key);} +} diff --git a/100_core/src/gplx/core/ios/IoEngine_base.java b/100_core/src/gplx/core/ios/IoEngine_base.java index a27517de8..140c87af6 100644 --- a/100_core/src/gplx/core/ios/IoEngine_base.java +++ b/100_core/src/gplx/core/ios/IoEngine_base.java @@ -13,3 +13,45 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.ios.streams.*; +public abstract class IoEngine_base implements IoEngine { + public abstract String Key(); + public abstract boolean ExistsFil_api(Io_url url); + public abstract void SaveFilText_api(IoEngine_xrg_saveFilStr args); + public abstract String LoadFilStr(IoEngine_xrg_loadFilStr args); + public abstract void DeleteFil_api(IoEngine_xrg_deleteFil args); + public abstract void CopyFil(IoEngine_xrg_xferFil args); + public abstract void MoveFil(IoEngine_xrg_xferFil args); + public abstract IoItmFil QueryFil(Io_url url); + public abstract void UpdateFilAttrib(Io_url url, IoItmAttrib atr); // will fail if file does not exists + public abstract void UpdateFilModifiedTime(Io_url url, DateAdp modified); + public abstract IoStream OpenStreamRead(Io_url url); + public abstract IoStream OpenStreamWrite(IoEngine_xrg_openWrite args); + public abstract void XferFil(IoEngine_xrg_xferFil args); + public abstract boolean Truncate_fil(Io_url url, long size); + + public abstract boolean ExistsDir(Io_url url); + public abstract void CreateDir(Io_url url); // creates all folder levels (EX: C:\a\b\c\ will create C:\a\ and C:\a\b\). will not fail if called on already existing folders. + public abstract void DeleteDir(Io_url url); + public abstract void MoveDir(Io_url src, Io_url trg); // will fail if trg exists + public abstract void CopyDir(Io_url src, Io_url trg); + public abstract IoItmDir QueryDir(Io_url url); + + public abstract void DeleteDirDeep(IoEngine_xrg_deleteDir args); + public abstract void MoveDirDeep(IoEngine_xrg_xferDir args); // will fail if trg exists + public abstract IoItmDir QueryDirDeep(IoEngine_xrg_queryDir args); + public abstract void XferDir(IoEngine_xrg_xferDir args); + public abstract boolean DownloadFil(IoEngine_xrg_downloadFil xrg); + public abstract Io_stream_rdr DownloadFil_as_rdr(IoEngine_xrg_downloadFil xrg); + + public void RecycleFil(IoEngine_xrg_recycleFil xrg) { + Io_url recycleUrl = xrg.RecycleUrl(); + if (recycleUrl.Type_fil()) { + this.MoveFil(IoEngine_xrg_xferFil.move_(xrg.Url(), recycleUrl).Overwrite_(false).ReadOnlyFails_(true).MissingFails_(xrg.MissingFails())); + IoRecycleBin.Instance.Regy_add(xrg); + } + else + this.MoveDirDeep(IoEngine_xrg_xferDir.move_(xrg.Url(), recycleUrl).Overwrite_(false).ReadOnlyFails_(true)); + } +} diff --git a/100_core/src/gplx/core/ios/IoEngine_memory.java b/100_core/src/gplx/core/ios/IoEngine_memory.java index a27517de8..198388eb9 100644 --- a/100_core/src/gplx/core/ios/IoEngine_memory.java +++ b/100_core/src/gplx/core/ios/IoEngine_memory.java @@ -13,3 +13,189 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.ios.streams.*; +public class IoEngine_memory extends IoEngine_base { + @Override public String Key() {return key;} private String key = IoEngine_.MemKey; + @Override public boolean ExistsFil_api(Io_url url) {return FetchFil(url) != IoItmFil_mem.Null;} + @Override public void DeleteFil_api(IoEngine_xrg_deleteFil args) { + Io_url url = args.Url(); + IoItmDir dir = FetchDir(url.OwnerDir()); if (dir == null) return; // url doesn't exist; just exit + IoItmFil fil = IoItmFil_.as_(dir.SubFils().Get_by(url.NameAndExt())); + if (fil != null && fil.ReadOnly() && args.ReadOnlyFails()) throw IoErr.FileIsReadOnly(url); + dir.SubFils().Del(url); + } + void DeleteFil(Io_url url) {DeleteFil_api(IoEngine_xrg_deleteFil.new_(url));} + @Override public void XferFil(IoEngine_xrg_xferFil args) {utl.XferFil(this, args);} + @Override public void MoveFil(IoEngine_xrg_xferFil args) { + Io_url src = args.Src(), trg = args.Trg(); boolean overwrite = args.Overwrite(); + if (String_.Eq(src.Xto_api(), trg.Xto_api())) throw Err_.new_wo_type("move failed; src is same as trg", "raw", src.Raw()); + CheckTransferArgs("move", src, trg, overwrite); + if (overwrite) DeleteFil(trg); + IoItmFil_mem curFil = FetchFil(src); curFil.Name_(trg.NameAndExt()); + AddFilToDir(trg.OwnerDir(), curFil); + DeleteFil(src); + } + @Override public void CopyFil(IoEngine_xrg_xferFil args) { + Io_url src = args.Src(), trg = args.Trg(); boolean overwrite = args.Overwrite(); + CheckTransferArgs("copy", src, trg, overwrite); + if (overwrite) DeleteFil(trg); + IoItmFil_mem srcFil = FetchFil(src); + IoItmFil_mem curFil = srcFil.Clone(); curFil.Name_(trg.NameAndExt()); + AddFilToDir(trg.OwnerDir(), curFil); + } + @Override public IoItmDir QueryDirDeep(IoEngine_xrg_queryDir args) {return utl.QueryDirDeep(this, args);} + @Override public void UpdateFilAttrib(Io_url url, IoItmAttrib atr) {FetchFil(url).ReadOnly_(atr.ReadOnly());} + @Override public void UpdateFilModifiedTime(Io_url url, DateAdp modified) {FetchFil(url).ModifiedTime_(modified);} + @Override public IoItmFil QueryFil(Io_url url) {return FetchFil(url);} + @Override public void SaveFilText_api(IoEngine_xrg_saveFilStr args) { + Io_url url = args.Url(); + IoItmDir dir = FetchDir(url.OwnerDir()); + if (dir != null) { + IoItmFil fil = IoItmFil_.as_(dir.SubFils().Get_by(url.NameAndExt())); + if (fil != null && fil.ReadOnly()) throw IoErr.FileIsReadOnly(url); + } + + if (args.Append()) + AppendFilStr(args); + else + SaveFilStr(args.Url(), args.Text()); + } + @Override public boolean Truncate_fil(Io_url url, long size) {throw Err_.new_unimplemented();} + @Override public String LoadFilStr(IoEngine_xrg_loadFilStr args) { + return FetchFil(args.Url()).Text(); + } + void SaveFilStr(Io_url url, String text) { + DateAdp time = Datetime_now.Get(); + IoItmFil_mem fil = IoItmFil_mem.new_(url, String_.Len(text), time, text); + AddFilToDir(url.OwnerDir(), fil); + } + void AppendFilStr(IoEngine_xrg_saveFilStr args) { + Io_url url = args.Url(); String text = args.Text(); + if (ExistsFil_api(url)) { + IoItmFil_mem fil = FetchFil(url); + fil.ModifiedTime_(Datetime_now.Get()); + fil.Text_set(fil.Text() + text); + } + else + SaveFilStr(args.Url(), args.Text()); + } + @Override public IoStream OpenStreamRead(Io_url url) { + IoItmFil_mem fil = FetchFil(url); + fil.Stream().Position_set(0); + return fil.Stream(); + } + @Override public IoStream OpenStreamWrite(IoEngine_xrg_openWrite args) { + Io_url url = args.Url(); + IoItmFil_mem fil = FetchFil(url); + if (fil == IoItmFil_mem.Null) { // file doesn't exist; create new one + SaveFilStr(url, ""); + fil = FetchFil(url); + } + else { + if (args.Mode() == IoStream_.Mode_wtr_create) + fil.Text_set(""); // NOTE: clear text b/c it still has pointer to existing stream + } + return fil.Stream(); + } + + @Override public boolean ExistsDir(Io_url url) {return FetchDir(url) != null;} + @Override public void CreateDir(Io_url url) { + IoItmDir dir = FetchDir(url); if (dir != null) return; // dir exists; exit + dir = IoItmDir_.top_(url); + dirs.Add(dir); + IoItmDir ownerDir = FetchDir(url.OwnerDir()); + if (ownerDir == null && !url.OwnerDir().Eq(Io_url_.Empty)) { // no owner dir && not "driveDir" -> create + CreateDir(url.OwnerDir()); // recursive + ownerDir = FetchDir(url.OwnerDir()); + } + if (ownerDir != null) + ownerDir.SubDirs().Add(dir); + } + @Override public void DeleteDir(Io_url url) { + FetchDir(url); // force creation if exists? + dirs.Del(url); + IoItmDir ownerDir = FetchDir(url.OwnerDir()); if (ownerDir == null) return; // no ownerDir; no need to unregister + ownerDir.SubDirs().Del(url); + } + @Override public void XferDir(IoEngine_xrg_xferDir args) {Io_url trg = args.Trg(); utl.XferDir(this, args.Src(), IoEnginePool.Instance.Get_by(trg.Info().EngineKey()), trg, args);} + @Override public void MoveDirDeep(IoEngine_xrg_xferDir args) {Io_url trg = args.Trg(); utl.XferDir(this, args.Src(), IoEnginePool.Instance.Get_by(trg.Info().EngineKey()), trg, args);} + @Override public void MoveDir(Io_url src, Io_url trg) {if (ExistsDir(trg)) throw Err_.new_wo_type("trg already exists", "trg", trg); + IoItmDir dir = FetchDir(src); dir.Name_(trg.NameAndExt()); + for (Object filObj : dir.SubFils()) { // move all subFiles + IoItmFil fil = (IoItmFil)filObj; + fil.OwnerDir_set(dir); + } + dirs.Add(dir); + DeleteDir(src); + } + @Override public IoItmDir QueryDir(Io_url url) { + IoItmDir dir = FetchDir(url); + IoItmDir rv = IoItmDir_.top_(url); // always return copy b/c caller may add/del itms directly + if (dir == null) { + rv.Exists_set(false); + return rv; + } + for (Object subDirObj : dir.SubDirs()) { + IoItmDir subDir = (IoItmDir)subDirObj; + rv.SubDirs().Add(IoItmDir_.scan_(subDir.Url())); + } + for (Object subFilObj : dir.SubFils()) { + IoItmFil subFil = (IoItmFil)subFilObj; + rv.SubFils().Add(subFil); + } + return rv; + } + @Override public void DeleteDirDeep(IoEngine_xrg_deleteDir args) {utl.DeleteDirDeep(this, args.Url(), args);} + @Override public void CopyDir(Io_url src, Io_url trg) { + IoEngine_xrg_xferDir.copy_(src, trg).Recur_().Exec(); + } + void AddFilToDir(Io_url dirPath, IoItmFil fil) { + IoItmDir dir = FetchDir(dirPath); + if (dir == null) { + CreateDir(dirPath); + dir = FetchDir(dirPath); + } + dir.SubFils().Del(fil.Url()); + dir.SubFils().Add(fil); + } + IoItmDir FetchDir(Io_url url) {return IoItmDir_.as_(dirs.Get_by(url));} + IoItmFil_mem FetchFil(Io_url url) { + IoItmDir ownerDir = FetchDir(url.OwnerDir()); + if (ownerDir == null) return IoItmFil_mem.Null; + IoItmFil_mem rv = IoItmFil_mem.as_(ownerDir.SubFils().Get_by(url.NameAndExt())); + if (rv == null) rv = IoItmFil_mem.Null; + return rv; + } + void CheckTransferArgs(String op, Io_url src, Io_url trg, boolean overwrite) { + if (!ExistsFil_api(src)) throw Err_.new_wo_type("src does not exist", "src", src); + if (ExistsFil_api(trg) && !overwrite) throw Err_.new_invalid_op("trg already exists").Args_add("op", op, "overwrite", false, "src", src, "trg", trg); + } + public void Clear() {dirs.Clear();} + @Override public boolean DownloadFil(IoEngine_xrg_downloadFil xrg) { + Io_url src = Io_url_.mem_fil_(xrg.Src()); + if (!ExistsFil_api(src)) { + xrg.Rslt_(IoEngine_xrg_downloadFil.Rslt_fail_file_not_found); + return false; + } + XferFil(IoEngine_xrg_xferFil.copy_(src, xrg.Trg()).Overwrite_()); + return true; + } + @Override public Io_stream_rdr DownloadFil_as_rdr(IoEngine_xrg_downloadFil xrg) { + Io_url src = Io_url_.mem_fil_(xrg.Src()); + if (!ExistsFil_api(src)) { + xrg.Rslt_(IoEngine_xrg_downloadFil.Rslt_fail_file_not_found); + return Io_stream_rdr_.Noop; + } + byte[] bry = Bry_.new_u8(FetchFil(Io_url_.mem_fil_(xrg.Src())).Text()); + return Io_stream_rdr_.New__mem(bry); + } + + IoItmHash dirs = IoItmHash.new_(); + IoEngineUtl utl = IoEngineUtl.new_(); + @gplx.Internal protected static IoEngine_memory new_(String key) { + IoEngine_memory rv = new IoEngine_memory(); + rv.key = key; + return rv; + } IoEngine_memory() {} +} diff --git a/100_core/src/gplx/core/ios/IoEngine_system.java b/100_core/src/gplx/core/ios/IoEngine_system.java index a27517de8..1fb34296b 100644 --- a/100_core/src/gplx/core/ios/IoEngine_system.java +++ b/100_core/src/gplx/core/ios/IoEngine_system.java @@ -13,3 +13,726 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.nio.*; +import java.nio.channels.*; +import java.util.Date; + +import javax.print.FlavorException; +import javax.tools.JavaCompiler; +import gplx.core.criterias.*; import gplx.core.bits.*; import gplx.core.envs.*; +import gplx.core.ios.streams.*; +import gplx.core.progs.*; +public class IoEngine_system extends IoEngine_base { + @Override public String Key() {return IoEngine_.SysKey;} + @Override public void DeleteDirDeep(IoEngine_xrg_deleteDir args) {utl.DeleteDirDeep(this, args.Url(), args);} + @Override public void XferDir(IoEngine_xrg_xferDir args) {Io_url trg = args.Trg(); utl.XferDir(this, args.Src(), IoEnginePool.Instance.Get_by(trg.Info().EngineKey()), trg, args);} + @Override public void XferFil(IoEngine_xrg_xferFil args) {utl.XferFil(this, args);} + @Override public IoItmDir QueryDirDeep(IoEngine_xrg_queryDir args) {return utl.QueryDirDeep(this, args);} + @Override public void CopyDir(Io_url src, Io_url trg) {IoEngine_xrg_xferDir.copy_(src, trg).Recur_().Exec();} + @Override public void MoveDirDeep(IoEngine_xrg_xferDir args) {Io_url trg = args.Trg(); utl.XferDir(this, args.Src(), IoEnginePool.Instance.Get_by(trg.Info().EngineKey()), trg, args);} + @Override public void DeleteFil_api(IoEngine_xrg_deleteFil args) { + Io_url url = args.Url(); + File fil = Fil_(url); + if (!Fil_Exists(fil)) return; + MarkFileWritable(fil, url, args.ReadOnlyFails(), "DeleteFile"); + DeleteFil_lang(fil, url); + } + @Override public boolean ExistsFil_api(Io_url url) { + File f = new File(url.Xto_api()); + return f.exists(); + } + @Override public void SaveFilText_api(IoEngine_xrg_saveFilStr mpo) { + Io_url url = mpo.Url(); + + // encode string + byte[] textBytes = null; + textBytes = Bry_.new_u8(mpo.Text()); + + FileChannel fc = null; FileOutputStream fos = null; + if (!ExistsDir(url.OwnerDir())) CreateDir(url.OwnerDir()); + try { + // open file + try {fos = new FileOutputStream(url.Xto_api(), mpo.Append());} + catch (FileNotFoundException e) {throw Err_Fil_NotFound(e, url);} + fc = fos.getChannel(); + + // write text + try {fc.write(ByteBuffer.wrap(textBytes));} + catch (IOException e) { + Closeable_close(fc, url, false); + Closeable_close(fos, url, false); + throw Err_.new_exc(e, "io", "write data to file failed", "url", url.Xto_api()); + } + if (!Op_sys.Cur().Tid_is_drd()) { + File fil = new File(url.Xto_api()); + IoEngine_system_xtn.SetExecutable(fil, true); + } + } + finally { + // cleanup + Closeable_close(fc, url, false); + Closeable_close(fos, url, false); + } + } + @Override public String LoadFilStr(IoEngine_xrg_loadFilStr args) { + Io_url url = args.Url(); String url_str = url.Xto_api(); + boolean file_exists = ExistsFil_api(url); // check if file exists first to avoid throwing exception; note that most callers pass Missing_ignored; DATE:2015-02-24 + if (!file_exists) { + if (args.MissingIgnored()) return ""; + else throw Err_Fil_NotFound(url); + } + // get reader for file + InputStream stream = null; + try {stream = new FileInputStream(url_str);} + catch (FileNotFoundException e) { + if (args.MissingIgnored()) return ""; + throw Err_Fil_NotFound(e, url); + } + return Load_from_stream_as_str(stream, url_str); + } + public static String Load_from_stream_as_str(InputStream stream, String url_str) { + InputStreamReader reader = null; + try {reader = new InputStreamReader(stream, IoEngineArgs.Instance.LoadFilStr_Encoding);} + catch (UnsupportedEncodingException e) { + Closeable_close(stream, url_str, false); + throw Err_text_unsupported_encoding(IoEngineArgs.Instance.LoadFilStr_Encoding, "", url_str, e); + } + + // make other objects + char[] readerBuffer = new char[IoEngineArgs.Instance.LoadFilStr_BufferSize]; + int pos = 0; + StringWriter sw = new StringWriter(); + + // transfer data + while (true) { + try {pos = reader.read(readerBuffer);} + catch (IOException e) { + try { + stream.close(); + reader.close(); + } + catch (IOException e2) {} + throw Err_.new_exc(e, "io", "read data from file failed", "url", url_str, "pos", pos); + } + if (pos == -1) break; + sw.write(readerBuffer, 0, pos); + } + + // cleanup + Closeable_close(stream, url_str, false); + Closeable_close(reader, url_str, false); + return sw.toString(); + } + public static byte[] Load_from_stream_as_bry(InputStream stream, String url_str) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + byte[] data = new byte[4096]; + int read = 0; + try { + while ((read = stream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, read); + } + buffer.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + return buffer.toByteArray(); + } + @Override public boolean ExistsDir(Io_url url) {return new File(url.Xto_api()).exists();} + @Override public void CreateDir(Io_url url) {new File(url.Xto_api()).mkdirs();} + @Override public void DeleteDir(Io_url url) { + File dir = new File(url.Xto_api()); + if (!dir.exists()) return; + boolean rv = dir.delete(); + if (!rv) throw Err_.new_(IoEngineArgs.Instance.Err_IoException, "delete dir failed", "url", url.Xto_api()); + } + @Override public IoItmDir QueryDir(Io_url url) { + IoItmDir rv = IoItmDir_.scan_(url); + String url_api = url.Xto_api(); + if ( gplx.core.envs.Op_sys.Cur().Tid_is_wnt() // op_sys is wnt + && String_.Eq(url.OwnerDir().Raw(), String_.Empty) // folder is drive; EX: "C:" + ) + url_api = url_api + "\\"; // add "\\"; else listFiles will return working folder's files, not C:; DATE:2016-04-07 + File dirInfo = new File(url_api); + if (!dirInfo.exists()) { + rv.Exists_set(false); + return rv; + } + IoUrlInfo urlInfo = url.Info(); + File[] subItmAry = dirInfo.listFiles(); + if (subItmAry == null) return rv; // directory has no files + for (int i = 0; i < subItmAry.length; i++) { + File subItm = subItmAry[i]; + if (subItm.isFile()) { + IoItmFil subFil = QueryMkr_fil(urlInfo, subItm); + rv.SubFils().Add(subFil); + } + else { + IoItmDir subDir = QueryMkr_dir(urlInfo, subItm); + rv.SubDirs().Add(subDir); + } + } + return rv; + } + IoItmFil QueryMkr_fil(IoUrlInfo urlInfo, File apiFil) { + Io_url filUrl = Io_url_.new_inf_(apiFil.getPath(), urlInfo); // NOTE: may throw PathTooLongException when url is > 248 (exception messages states 260) + long fil_len = apiFil.exists() ? apiFil.length() : IoItmFil.Size_invalid; // NOTE: if file doesn't exist, set len to -1; needed for "boolean Exists() {return size != Size_Invalid;}"; DATE:2014-06-21 + IoItmFil rv = IoItmFil_.new_(filUrl, fil_len, DateAdp_.MinValue, DateAdp_.unixtime_lcl_ms_(apiFil.lastModified())); + rv.ReadOnly_(!apiFil.canWrite()); + return rv; + } + IoItmDir QueryMkr_dir(IoUrlInfo urlInfo, File apiDir) { + Io_url dirUrl = Io_url_.new_inf_(apiDir.getPath() + urlInfo.DirSpr(), urlInfo); // NOTE: may throw PathTooLongException when url is > 248 (exception messages states 260) + return IoItmDir_.scan_(dirUrl); + } + @Override public IoItmFil QueryFil(Io_url url) { + File fil = new File(url.Xto_api()); + return QueryMkr_fil(url.Info(), fil); + } + @Override public void UpdateFilAttrib(Io_url url, IoItmAttrib atr) { + File f = new File(url.Xto_api()); + boolean rv = true; + if (atr.ReadOnly() != Fil_ReadOnly(f)) { + if (atr.ReadOnly()) + rv = f.setReadOnly(); + else { + if (!Op_sys.Cur().Tid_is_drd()) + IoEngine_system_xtn.SetWritable(f, true); + } + if (!rv) throw Err_.new_(IoEngineArgs.Instance.Err_IoException, "set file attribute failed", "attribute", "readOnly", "cur", Fil_ReadOnly(f), "new", atr.ReadOnly(), "url", url.Xto_api()); + } + if (atr.Hidden() != f.isHidden()) { + //Runtime.getRuntime().exec("attrib +H myHiddenFile.java"); + } + } + @Override public void UpdateFilModifiedTime(Io_url url, DateAdp modified) { + File f = new File(url.Xto_api()); + long timeInt = modified.UnderDateTime().getTimeInMillis(); +// if (timeInt < 0) { +// UsrDlg_._.Notify("{0} {1}", url.Xto_api(), timeInt); +// return; +// } + if (!f.setLastModified(timeInt)) { + if (Fil_ReadOnly(f)) { + boolean success = false; + try { + UpdateFilAttrib(url, IoItmAttrib.normal_()); + success = f.setLastModified(timeInt); + } + finally { + UpdateFilAttrib(url, IoItmAttrib.readOnly_()); + } + if (!success) throw Err_.new_wo_type("could not update file modified time", "url", url.Xto_api(), "modifiedTime", modified.XtoStr_gplx_long()); + } + } + } + @Override public IoStream OpenStreamRead(Io_url url) {return IoStream_base.new_(url, IoStream_.Mode_rdr);} + @Override public IoStream OpenStreamWrite(IoEngine_xrg_openWrite args) { + Io_url url = args.Url(); + if (!ExistsFil_api(url)) SaveFilText_api(IoEngine_xrg_saveFilStr.new_(url, "")); + return IoStream_base.new_(url, args.Mode()); + } + @Override public void CopyFil(IoEngine_xrg_xferFil args) { + // TODO:JAVA6 hidden property ignored; 1.6 does not allow OS-independent way of setting isHidden (wnt only possible through jni) + boolean overwrite = args.Overwrite(); + Io_url srcUrl = args.Src(), trgUrl = args.Trg(); + File srcFil = new File(srcUrl.Xto_api()), trgFil = new File(trgUrl.Xto_api()); + if (trgFil.isFile()) { // trgFil exists; check if overwrite set and trgFil is writable + Chk_TrgFil_Overwrite(overwrite, trgUrl); + MarkFileWritable(trgFil, trgUrl, args.ReadOnlyFails(), "copy"); + } + else { // trgFil doesn't exist; must create file first else fileNotFound exception thrown + boolean rv = true; + if (!ExistsDir(trgUrl.OwnerDir())) CreateDir(trgUrl.OwnerDir()); + try { + trgFil.createNewFile(); + if (!Op_sys.Cur().Tid_is_drd()) + IoEngine_system_xtn.SetExecutable(trgFil, true); + } + catch (IOException e) { + rv = false; + } + if (!rv) + throw Err_.new_wo_type("create file failed", "trg", trgUrl.Xto_api()); + } + FileInputStream srcStream = null; FileOutputStream trgStream = null; + FileChannel srcChannel = null, trgChannel = null; + try { + // make objects + try {srcStream = new FileInputStream(srcFil);} + catch (FileNotFoundException e) {throw IoErr.FileNotFound("copy", srcUrl);} + try {trgStream = new FileOutputStream(trgFil);} + catch (FileNotFoundException e) { + trgStream = TryToUnHideFile(trgFil, trgUrl); + if (trgStream == null) + throw IoErr.FileNotFound("copy", trgUrl); +// else +// wasHidden = true; + } + srcChannel = srcStream.getChannel(); + trgChannel = trgStream.getChannel(); + + // transfer data + long pos = 0, count = 0, read = 0; + try {count = srcChannel.size();} + catch (IOException e) {throw Err_.new_exc(e, "io", "size failed", "src", srcUrl.Xto_api());} + int totalBufferSize = IoEngineArgs.Instance.LoadFilStr_BufferSize; + long transferSize = (count > totalBufferSize) ? totalBufferSize : count; // transfer as much as fileSize, but limit to LoadFilStr_BufferSize + while (pos < count) { + try {read = trgChannel.transferFrom(srcChannel, pos, transferSize);} + catch (IOException e) { + Closeable_close(srcChannel, srcUrl, false); + Closeable_close(trgChannel, trgUrl, false); + Closeable_close(srcStream, srcUrl, false); + Closeable_close(trgStream, srcUrl, false); + throw Err_.new_exc(e, "io", "transfer data failed", "src", srcUrl.Xto_api(), "trg", trgUrl.Xto_api()); + } + if (read == -1) break; + pos += read; + } +// if (wasHidden) +// + } + finally { + // cleanup + Closeable_close(srcChannel, srcUrl, false); + Closeable_close(trgChannel, trgUrl, false); + Closeable_close(srcStream, srcUrl, false); + Closeable_close(trgStream, srcUrl, false); + } + UpdateFilModifiedTime(trgUrl, QueryFil(srcUrl).ModifiedTime()); // must happen after file is closed + } + public void CopyFil(gplx.core.progs.Gfo_prog_ui prog_ui, Io_url src_url, Io_url trg_url, boolean overwrite, boolean readonly_fails) { + // TODO:JAVA6 hidden property ignored; 1.6 does not allow OS-independent way of setting isHidden (wnt only possible through jni) + File src_fil = new File(src_url.Xto_api()), trg_fil = new File(trg_url.Xto_api()); + if (trg_fil.isFile()) { // trg_fil exists; check if overwrite set and trg_fil is writable + Chk_TrgFil_Overwrite(overwrite, trg_url); + MarkFileWritable(trg_fil, trg_url, readonly_fails, "copy"); + } + else { // trg_fil doesn't exist; must create file first else fileNotFound exception thrown + boolean rv = true; + if (!ExistsDir(trg_url.OwnerDir())) CreateDir(trg_url.OwnerDir()); + try { + trg_fil.createNewFile(); + if (!Op_sys.Cur().Tid_is_drd()) + IoEngine_system_xtn.SetExecutable(trg_fil, true); + } + catch (IOException e) { + rv = false; + } + if (!rv) + throw Err_.new_wo_type("create file failed", "trg", trg_url.Xto_api()); + } + FileInputStream src_stream = null; FileOutputStream trg_stream = null; + FileChannel src_channel = null, trg_channel = null; + try { + // make objects + try {src_stream = new FileInputStream(src_fil);} + catch (FileNotFoundException e) {throw IoErr.FileNotFound("copy", src_url);} + try {trg_stream = new FileOutputStream(trg_fil);} + catch (FileNotFoundException e) { + trg_stream = TryToUnHideFile(trg_fil, trg_url); + if (trg_stream == null) + throw IoErr.FileNotFound("copy", trg_url); +// else +// wasHidden = true; + } + src_channel = src_stream.getChannel(); + trg_channel = trg_stream.getChannel(); + + // transfer data + long pos = 0, count = 0, read = 0; + try {count = src_channel.size();} + catch (IOException e) {throw Err_.new_exc(e, "io", "size failed", "src", src_url.Xto_api());} + int buffer_size = IoEngineArgs.Instance.LoadFilStr_BufferSize; + long transfer_size = (count > buffer_size) ? buffer_size : count; // transfer as much as fileSize, but limit to LoadFilStr_BufferSize + while (pos < count) { + try {read = trg_channel.transferFrom(src_channel, pos, transfer_size);} + catch (IOException e) { + Closeable_close(src_channel, src_url, false); + Closeable_close(trg_channel, trg_url, false); + Closeable_close(src_stream, src_url, false); + Closeable_close(trg_stream, src_url, false); + throw Err_.new_exc(e, "io", "transfer data failed", "src", src_url.Xto_api(), "trg", trg_url.Xto_api()); + } + if (read == -1) break; + if (prog_ui.Prog_notify_and_chk_if_suspended(pos, count)) return; + pos += read; + } +// if (wasHidden) +// + } + finally { + // cleanup + Closeable_close(src_channel, src_url, false); + Closeable_close(trg_channel, trg_url, false); + Closeable_close(src_stream, src_url, false); + Closeable_close(trg_stream, src_url, false); + } + UpdateFilModifiedTime(trg_url, QueryFil(src_url).ModifiedTime()); // must happen after file is closed + } + FileOutputStream TryToUnHideFile(File trgFil, Io_url trgUrl) { + FileOutputStream trgStream = null; + if (trgFil.exists()) { // WORKAROUND: java fails when writing to hidden files; unmark hidden and try again + Process p = null; + try { + String d = "attrib -H \"" + trgUrl.Xto_api() + "\""; + p = Runtime.getRuntime().exec(d); + } catch (IOException e1) { + e1.printStackTrace(); + } + try { + p.waitFor(); + } catch (InterruptedException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + try {trgStream = new FileOutputStream(trgFil);} + catch (FileNotFoundException e) { + return null; + } + } + return trgStream; + } + @Override public void MoveFil(IoEngine_xrg_xferFil args) { + Io_url srcUrl = args.Src(), trgUrl = args.Trg(); + String src_api = srcUrl.Xto_api(), trg_api = trgUrl.Xto_api(); + if (String_.Eq(src_api, trg_api)) return; // ignore command if src and trg is same; EX: C:\a.txt -> C:\a.txt should be noop + File srcFil = new File(src_api), trgFil = new File(trg_api); + + // if drive is same, then rename file + if (String_.Eq(srcUrl.OwnerRoot().Raw(), trgUrl.OwnerRoot().Raw())) { + boolean overwrite = args.Overwrite(); + if (!srcFil.exists() && args.MissingFails()) throw IoErr.FileNotFound("move", srcUrl); + if (trgFil.exists()) { + Chk_TrgFil_Overwrite(overwrite, trgUrl); + MarkFileWritable(trgFil, trgUrl, args.ReadOnlyFails(), "move"); + DeleteFil_lang(trgFil, args.Trg()); // overwrite is specified and file is writable -> delete + } + if (!ExistsDir(trgUrl.OwnerDir())) CreateDir(trgUrl.OwnerDir()); + srcFil.renameTo(trgFil); + } + // else copy fil and delete + else { + if (!srcFil.exists() && !args.MissingFails()) return; + CopyFil(args); + DeleteFil_lang(srcFil, srcUrl); + } + } + void Chk_TrgFil_Overwrite(boolean overwrite, Io_url trg) { + if (!overwrite) + throw Err_.new_invalid_op("trgFile exists but overwriteFlag not set").Args_add("trg", trg.Xto_api()); + } + @Override public void MoveDir(Io_url src, Io_url trg) { + String srcStr = src.Xto_api(), trgStr = trg.Xto_api(); + File srcFil = new File(srcStr), trgFil = new File(trgStr); + if (trgFil.exists()) {throw Err_.new_invalid_op("cannot move dir if trg exists").Args_add("src", src, "trg", trg);} + if (String_.Eq(src.OwnerRoot().Raw(), trg.OwnerRoot().Raw())) { + srcFil.renameTo(trgFil); + } + else { + XferDir(IoEngine_xrg_xferDir.copy_(src, trg)); + } + } + public static void Closeable_close(Closeable closeable, Io_url url, boolean throwErr) {Closeable_close(closeable, url.Xto_api(), throwErr);} + public static void Closeable_close(Closeable closeable, String url_str, boolean throwErr) { + if (closeable == null) return; + try {closeable.close();} + catch (IOException e) { + if (throwErr) + throw Err_.new_exc(e, "io", "close object failed", "class", Type_.Name_by_obj(closeable), "url", url_str); +// else +// UsrDlg_._.Finally("failed to close FileChannel", "url", url, "apiErr", Err_.Message_err_arg(e)); + } + } + + File Fil_(Io_url url) {return new File(url.Xto_api());} + boolean Fil_Exists(File fil) {return fil.exists();} + boolean Fil_ReadOnly(File fil) {return !fil.canWrite();} + boolean Fil_Delete(File fil) {return fil.delete();} + void Fil_Writable(File fil) { + if (!Op_sys.Cur().Tid_is_drd()) + IoEngine_system_xtn.SetWritable(fil, true); + } + private static Err Err_text_unsupported_encoding(String encodingName, String text, String url_str, Exception e) { + return Err_.new_exc(e, "io", "text is in unsupported encoding").Args_add("encodingName", encodingName, "text", text, "url", url_str); + } + boolean user_agent_needs_resetting = true; + @Override public Io_stream_rdr DownloadFil_as_rdr(IoEngine_xrg_downloadFil xrg) { + Io_stream_rdr_http rdr = new Io_stream_rdr_http(xrg); + rdr.Open(); + return rdr; + } + @Override public boolean DownloadFil(IoEngine_xrg_downloadFil xrg) { + IoStream trg_stream = null; + java.io.BufferedInputStream src_stream = null; + java.net.URL src_url = null; + HttpURLConnection src_conn = null; + if (user_agent_needs_resetting) {user_agent_needs_resetting = false; System.setProperty("http.agent", "");} + boolean exists = Io_mgr.Instance.ExistsDir(xrg.Trg().OwnerDir()); + Gfo_usr_dlg prog_dlg = null; + String src_str = xrg.Src(); + Io_download_fmt xfer_fmt = xrg.Download_fmt(); + prog_dlg = xfer_fmt.Usr_dlg(); + if (!Web_access_enabled) { + if (prog_dlg != null) { + if (session_fil == null) session_fil = prog_dlg.Log_wkr().Session_dir().GenSubFil("internet.txt"); + prog_dlg.Log_wkr().Log_msg_to_url_fmt(session_fil, "download disabled: src='~{0}' trg='~{1}'", xrg.Src(), xrg.Trg().Raw()); + } + return false; + } + try { + trg_stream = Io_mgr.Instance.OpenStreamWrite(xrg.Trg()); + src_url = new java.net.URL(src_str); + src_conn = (HttpURLConnection)src_url.openConnection(); +// src_conn.setReadTimeout(5000); // do not set; if file does not exist, will wait 5 seconds before timing out; want to fail immediately + String user_agent = xrg.User_agent(); if (user_agent != null) src_conn.setRequestProperty("User-Agent", user_agent); + long content_length = Long_.parse_or(src_conn.getHeaderField("Content-Length"), IoItmFil.Size_invalid_int); + xrg.Src_content_length_(content_length); + if (xrg.Src_last_modified_query()) // NOTE: only files will have last modified (api calls will not); if no last_modified, then src_conn will throw get nullRef; avoid nullRef + xrg.Src_last_modified_(DateAdp_.unixtime_lcl_ms_(src_conn.getLastModified())); + if (xrg.Exec_meta_only()) return true; + src_stream = new java.io.BufferedInputStream(src_conn.getInputStream()); + if (!exists) { + Io_mgr.Instance.CreateDir(xrg.Trg().OwnerDir()); // dir must exist for OpenStreamWrite; create dir at last possible moment in case stream does not exist. + } + byte[] download_bfr = new byte[Download_bfr_len]; // NOTE: download_bfr was originally member variable; DATE:2013-05-03 + xfer_fmt.Bgn(content_length); + int count = 0; + while ((count = src_stream.read(download_bfr, 0, Download_bfr_len)) != -1) { + if (xrg.Prog_cancel()) { + src_stream.close(); + trg_stream.Rls(); + Io_mgr.Instance.DeleteFil(xrg.Trg()); + } + xfer_fmt.Prog(count); + trg_stream.Write(download_bfr, 0, count); + } + if (prog_dlg != null) { + xfer_fmt.Term(); + if (session_fil == null) session_fil = prog_dlg.Log_wkr().Session_dir().GenSubFil("internet.txt"); + prog_dlg.Log_wkr().Log_msg_to_url_fmt(session_fil, "download pass: src='~{0}' trg='~{1}'", src_str, xrg.Trg().Raw()); + } + return true; + } + catch (Exception exc) { + xrg.Rslt_err_(exc); + if (Type_.Eq_by_obj(exc, java.net.UnknownHostException.class)) xrg.Rslt_(IoEngine_xrg_downloadFil.Rslt_fail_host_not_found); + else if (Type_.Eq_by_obj(exc, java.io.FileNotFoundException.class)) xrg.Rslt_(IoEngine_xrg_downloadFil.Rslt_fail_file_not_found); + else xrg.Rslt_(IoEngine_xrg_downloadFil.Rslt_fail_unknown); + if (prog_dlg != null && !xrg.Prog_cancel()) { + if (session_fil == null) session_fil = prog_dlg.Log_wkr().Session_dir().GenSubFil("internet.txt"); + prog_dlg.Log_wkr().Log_msg_to_url_fmt(session_fil, "download fail: src='~{0}' trg='~{1}' error='~{2}'", src_str, xrg.Trg().Raw(), Err_.Message_lang(exc)); + } + if (trg_stream != null) { + try { + trg_stream.Rls(); + DeleteFil_api(IoEngine_xrg_deleteFil.new_(xrg.Trg())); + } + catch (Exception e2) {Err_.Noop(e2);} + } + return false; + } + finally { + xrg.Prog_running_(false); + try { + if (src_stream != null) src_stream.close(); + if (src_conn != null) { + src_conn.disconnect(); + src_conn.getInputStream().close(); + } + } catch (Exception exc) { + Err_.Noop(exc); + } + if (trg_stream != null) trg_stream.Rls(); + } + } Io_url session_fil; Bry_bfr prog_fmt_bfr; + @Override public boolean Truncate_fil(Io_url url, long size) { + FileOutputStream stream = null; + FileChannel channel = null; + try {stream = new FileOutputStream(url.Xto_api(), true);} + catch (FileNotFoundException e) {throw Err_.new_("io", "truncate: open failed", "url", url.Xto_api(), "err", Err_.Message_gplx_log(e));} + channel = stream.getChannel(); + try {channel.truncate(size); return true;} + catch (IOException e) {return false;} + finally { + try { + if (stream != null) stream.close(); + if (channel != null) channel.close(); + } catch (IOException e) {return false;} + } + } + byte[] download_bfr; static final int Download_bfr_len = Io_mgr.Len_kb * 128; + public static Err Err_Fil_NotFound(Io_url url) { + return Err_.new_(IoEngineArgs.Instance.Err_FileNotFound, "file not found", "url", url.Xto_api()).Trace_ignore_add_1_(); + } + public static Err Err_Fil_NotFound(Exception e, Io_url url) { + return Err_.new_exc(e, "io", "file not found", "url", url.Xto_api()).Trace_ignore_add_1_(); + } + void MarkFileWritable(File fil, Io_url url, boolean readOnlyFails, String op) { + if (Fil_ReadOnly(fil)) { + if (readOnlyFails) // NOTE: java will always allow final files to be deleted; programmer api is responsible for check + throw Err_.new_(IoEngineArgs.Instance.Err_ReadonlyFileNotWritable, "writable operation attempted on readOnly file", "op", op, "url", url.Xto_api()); + else + Fil_Writable(fil); + } + } + void DeleteFil_lang(File fil, Io_url url) { + boolean rv = Fil_Delete(fil); + if (!rv) + throw Err_.new_(IoEngineArgs.Instance.Err_IoException, "file not deleted", "url", url.Xto_api()); + } + IoEngineUtl utl = IoEngineUtl.new_(); + public static IoEngine_system new_() {return new IoEngine_system();} IoEngine_system() {} + static final String GRP_KEY = "Io_engine"; + public static boolean Web_access_enabled = true; +} +class IoEngineArgs { + public int LoadFilStr_BufferSize = 4096 * 256; + public String LoadFilStr_Encoding = "UTF-8"; + public String Err_ReadonlyFileNotWritable = "gplx.core.ios.ReadonlyFileNotWritable"; + public String Err_FileNotFound = "gplx.core.ios.FileNotFound"; + public String Err_IoException = "gplx.core.ios.IoException"; + public static final IoEngineArgs Instance = new IoEngineArgs(); +} +class IoEngine_system_xtn { + // PATCH.DROID:VerifyError if file.setExecutable is referenced directly in IoEngine_system. However, if placed in separate class + public static void SetExecutable(java.io.File file, boolean v) {file.setExecutable(v);} + public static void SetWritable(java.io.File file, boolean v) {file.setWritable(v);} +} +class Io_download_http { + public static boolean User_agent_reset_needed = true; + public static void User_agent_reset() { + User_agent_reset_needed = false; + System.setProperty("http.agent", ""); // need to set http.agent to '' in order for "User-agent" to take effect + } + public static void Save_to_fsys(IoEngine_xrg_downloadFil xrg) { + Io_stream_rdr_http rdr = new Io_stream_rdr_http(xrg); + IoStream trg_stream = null; + try { + boolean exists = Io_mgr.Instance.ExistsDir(xrg.Trg().OwnerDir()); + if (!exists) + Io_mgr.Instance.CreateDir(xrg.Trg().OwnerDir()); // dir must exist for OpenStreamWrite; create dir at last possible moment in case stream does not exist. + trg_stream = Io_mgr.Instance.OpenStreamWrite(xrg.Trg()); + byte[] bfr = new byte[Download_bfr_len]; + rdr.Open(); + while (rdr.Read(bfr, 0, Download_bfr_len) != Read_done) { + } + } + finally { + rdr.Rls(); + if (trg_stream != null) trg_stream.Rls(); + } + if (xrg.Rslt() != IoEngine_xrg_downloadFil.Rslt_pass) + Io_mgr.Instance.DeleteFil_args(xrg.Trg()).MissingFails_off().Exec(); + } + public static final int Read_done = -1; + public static final int Download_bfr_len = Io_mgr.Len_kb * 128; +} +class Io_stream_rdr_http implements Io_stream_rdr { + public Io_stream_rdr_http(IoEngine_xrg_downloadFil xrg) { + this.xrg = xrg; + } private IoEngine_xrg_downloadFil xrg; + public byte Tid() {return Io_stream_tid_.Tid__raw;} + public boolean Exists() {return exists;} private boolean exists = false; + public Io_url Url() {return url;} public Io_stream_rdr Url_(Io_url v) {url = v; return this;} private Io_url url; + public long Len() {return len;} public Io_stream_rdr Len_(long v) {len = v; return this;} private long len = IoItmFil.Size_invalid; // NOTE: must default size to -1; DATE:2014-06-21 + private String src_str; private HttpURLConnection src_conn; private java.io.BufferedInputStream src_stream; + private Io_download_fmt xfer_fmt; private Gfo_usr_dlg prog_dlg; + private boolean read_done = true, read_failed = false; + public Io_stream_rdr Open() { + if (Io_download_http.User_agent_reset_needed) Io_download_http.User_agent_reset(); + if (!IoEngine_system.Web_access_enabled) { + read_done = read_failed = true; + if (prog_dlg != null) + prog_dlg.Log_wkr().Log_msg_to_url_fmt(session_fil, "download disabled: src='~{0}' trg='~{1}'", xrg.Src(), xrg.Trg().Raw()); + return this; + } + src_str = xrg.Src(); + xfer_fmt = xrg.Download_fmt(); prog_dlg = xfer_fmt.Usr_dlg(); + try { + src_conn = (HttpURLConnection)new java.net.URL(src_str).openConnection(); + String user_agent = xrg.User_agent(); + if (user_agent != null) src_conn.setRequestProperty("User-Agent", user_agent); // NOTE: must be set right after openConnection +// src_conn.setReadTimeout(5000); // do not set; if file does not exist, will wait 5 seconds before timing out; want to fail immediately + long content_length = Long_.parse_or(src_conn.getHeaderField("Content-Length"), IoItmFil.Size_invalid_int); + xrg.Src_content_length_(content_length); + this.len = content_length; + if (xrg.Src_last_modified_query()) // NOTE: only files will have last modified (api calls will not); if no last_modified, then src_conn will throw get nullRef; avoid nullRef + xrg.Src_last_modified_(DateAdp_.unixtime_lcl_ms_(src_conn.getLastModified())); + if (xrg.Exec_meta_only()) { + read_done = true; + return this; + } + read_done = false; + this.exists = Int_.In(src_conn.getResponseCode(), 200, 301); // ASSUME: response code of 200 (OK) or 301 (Redirect) means that file exists; note that content_length seems to always be -1; DATE:2015-05-20 + src_stream = new java.io.BufferedInputStream(src_conn.getInputStream()); + xfer_fmt.Bgn(content_length); + } + catch (Exception e) {Err_handle(e);} + return this; + } + public void Open_mem(byte[] v) {} + public Object Under() {return src_stream;} + public int Read(byte[] bry, int bgn, int len) { + if (read_done) return Io_download_http.Read_done; + if (xrg.Prog_cancel()) {read_failed = true; return Io_download_http.Read_done;} + try { + int read = src_stream.read(bry, bgn, len); + xfer_fmt.Prog(read); + return read; + } + catch (Exception e) { + Err_handle(e); + return Io_download_http.Read_done; + } + } + private Io_url session_fil = null; + private boolean rls_done = false; + public long Skip(long len) {return 0;} + public void Rls() { + if (rls_done) return; + try { + read_done = true; + if (prog_dlg != null) { + xfer_fmt.Term(); + } + if (session_fil == null && prog_dlg != null) session_fil = prog_dlg.Log_wkr().Session_dir().GenSubFil("internet.txt"); + if (read_failed) { + } + else { + if (prog_dlg != null) + prog_dlg.Log_wkr().Log_msg_to_url_fmt(session_fil, "download pass: src='~{0}' trg='~{1}'", src_str, xrg.Trg().Raw()); + xrg.Rslt_(IoEngine_xrg_downloadFil.Rslt_pass); + } + xrg.Prog_running_(false); + } + catch (Exception e) {Err_.Noop(e);} // ignore close errors; also Err_handle calls Rls() so it would be circular + finally { + try {if (src_stream != null) src_stream.close();} + catch (Exception e) {Err_.Noop(e);} // ignore failures when cleaning up + if (src_conn != null) src_conn.disconnect(); + src_stream = null; + src_conn = null; + rls_done = true; + } + } + private void Err_handle(Exception exc) { + read_done = read_failed = true; + len = -1; + xrg.Rslt_err_(exc); + if (Type_.Eq_by_obj(exc, java.net.UnknownHostException.class)) xrg.Rslt_(IoEngine_xrg_downloadFil.Rslt_fail_host_not_found); + else if (Type_.Eq_by_obj(exc, java.io.FileNotFoundException.class)) xrg.Rslt_(IoEngine_xrg_downloadFil.Rslt_fail_file_not_found); + else xrg.Rslt_(IoEngine_xrg_downloadFil.Rslt_fail_unknown); + if (prog_dlg != null && !xrg.Prog_cancel()) { + if (session_fil == null) session_fil = prog_dlg.Log_wkr().Session_dir().GenSubFil("internet.txt"); + prog_dlg.Log_wkr().Log_msg_to_url_fmt(session_fil, "download fail: src='~{0}' trg='~{1}' error='~{2}'", src_str, xrg.Trg().Raw(), Err_.Message_lang(exc)); + } + this.Rls(); + } +} diff --git a/100_core/src/gplx/core/ios/IoEngine_xrg_deleteDir.java b/100_core/src/gplx/core/ios/IoEngine_xrg_deleteDir.java index a27517de8..3b75846c9 100644 --- a/100_core/src/gplx/core/ios/IoEngine_xrg_deleteDir.java +++ b/100_core/src/gplx/core/ios/IoEngine_xrg_deleteDir.java @@ -13,3 +13,20 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.consoles.*; import gplx.core.criterias.*; +public class IoEngine_xrg_deleteDir { + public Io_url Url() {return url;} public IoEngine_xrg_deleteDir Url_(Io_url val) {url = val; return this;} Io_url url; + public boolean Recur() {return recur;} public IoEngine_xrg_deleteDir Recur_() {return Recur_(true);} public IoEngine_xrg_deleteDir Recur_(boolean v) {recur = v; return this;} private boolean recur = false; + public boolean ReadOnlyFails() {return readOnlyFails;} public IoEngine_xrg_deleteDir ReadOnlyFails_off() {return ReadOnlyFails_(false);} public IoEngine_xrg_deleteDir ReadOnlyFails_(boolean v) {readOnlyFails = v; return this;} private boolean readOnlyFails = true; + public boolean MissingIgnored() {return missingIgnored;} public IoEngine_xrg_deleteDir MissingIgnored_() {return MissingIgnored_(true);} public IoEngine_xrg_deleteDir MissingIgnored_(boolean v) {missingIgnored = v; return this;} private boolean missingIgnored = true; + public Criteria MatchCrt() {return matchCrt;} public IoEngine_xrg_deleteDir MatchCrt_(Criteria v) {matchCrt = v; return this;} Criteria matchCrt = Criteria_.All; + public Criteria SubDirScanCrt() {return subDirScanCrt;} public IoEngine_xrg_deleteDir SubDirScanCrt_(Criteria v) {subDirScanCrt = v; return this;} Criteria subDirScanCrt = Criteria_.All; + public Console_adp UsrDlg() {return usrDlg;} public IoEngine_xrg_deleteDir UsrDlg_(Console_adp v) {usrDlg = v; return this;} Console_adp usrDlg = Console_adp_.Noop; + public void Exec() {IoEnginePool.Instance.Get_by(url.Info().EngineKey()).DeleteDirDeep(this);} + public static IoEngine_xrg_deleteDir new_(Io_url url) { + IoEngine_xrg_deleteDir rv = new IoEngine_xrg_deleteDir(); + rv.url = url; + return rv; + } IoEngine_xrg_deleteDir() {} +} diff --git a/100_core/src/gplx/core/ios/IoEngine_xrg_deleteFil.java b/100_core/src/gplx/core/ios/IoEngine_xrg_deleteFil.java index a27517de8..aba8be9d6 100644 --- a/100_core/src/gplx/core/ios/IoEngine_xrg_deleteFil.java +++ b/100_core/src/gplx/core/ios/IoEngine_xrg_deleteFil.java @@ -13,3 +13,16 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class IoEngine_xrg_deleteFil extends IoEngine_xrg_fil_affects1_base { + @gplx.New public IoEngine_xrg_deleteFil Url_(Io_url val) {Url_set(val); return this;} + public IoEngine_xrg_deleteFil ReadOnlyFails_off() {return ReadOnlyFails_(false);} public IoEngine_xrg_deleteFil ReadOnlyFails_(boolean v) {ReadOnlyFails_set(v); return this;} + public IoEngine_xrg_deleteFil MissingFails_off() {return MissingFails_(false);} public IoEngine_xrg_deleteFil MissingFails_(boolean v) {MissingFails_set(v); return this;} + @Override public void Exec() {IoEnginePool.Instance.Get_by(this.Url().Info().EngineKey()).DeleteFil_api(this);} + public static IoEngine_xrg_deleteFil proto_() {return new IoEngine_xrg_deleteFil();} + public static IoEngine_xrg_deleteFil new_(Io_url url) { + IoEngine_xrg_deleteFil rv = new IoEngine_xrg_deleteFil(); + rv.Url_set(url); + return rv; + } IoEngine_xrg_deleteFil() {} +} diff --git a/100_core/src/gplx/core/ios/IoEngine_xrg_downloadFil.java b/100_core/src/gplx/core/ios/IoEngine_xrg_downloadFil.java index a27517de8..2f13db432 100644 --- a/100_core/src/gplx/core/ios/IoEngine_xrg_downloadFil.java +++ b/100_core/src/gplx/core/ios/IoEngine_xrg_downloadFil.java @@ -13,3 +13,57 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.brys.fmtrs.*; +import gplx.core.ios.streams.*; +public class IoEngine_xrg_downloadFil { + public String Src() {return src;} public IoEngine_xrg_downloadFil Src_(String v) {src = v; return this;} private String src; + public Io_url Trg() {return trg;} public IoEngine_xrg_downloadFil Trg_(Io_url v) {trg = v; return this;} private Io_url trg; + public byte Rslt() {return rslt;} public IoEngine_xrg_downloadFil Rslt_(byte v) {rslt = v; return this;} private byte rslt = Rslt_pass; + public Exception Rslt_err() {return rslt_err;} public IoEngine_xrg_downloadFil Rslt_err_(Exception v) {rslt_err = v; return this;} private Exception rslt_err; + public String Rslt_err_str() { + return rslt_err == null ? "none" : Err_.Message_gplx_full(rslt_err); + } + public String User_agent() {return user_agent;} public IoEngine_xrg_downloadFil User_agent_(String v) {user_agent = v; return this;} private String user_agent; + public Gfo_usr_dlg Prog_dlg() {return prog_dlg;} public IoEngine_xrg_downloadFil Prog_dlg_(Gfo_usr_dlg v) {prog_dlg = v; download_fmt.Ctor(prog_dlg); return this;} private Gfo_usr_dlg prog_dlg; + public Bry_fmtr Prog_fmtr() {return prog_fmtr;} private final Bry_fmtr prog_fmtr = Bry_fmtr.new_("~{download_header}: ~{download_read} of ~{download_length} kb;", "download_header", "download_url", "download_read", "download_length"); + public String Prog_fmt_hdr() {return prog_fmt_hdr;} public IoEngine_xrg_downloadFil Prog_fmt_hdr_(String v) {prog_fmt_hdr = v; return this;} private String prog_fmt_hdr = ""; // NOTE: must init to "", else null ref when building String + public boolean Prog_cancel() {return prog_cancel;} public IoEngine_xrg_downloadFil Prog_cancel_y_() {prog_cancel = true; return this;} private volatile boolean prog_cancel; + public boolean Prog_running() {return prog_running;} public IoEngine_xrg_downloadFil Prog_running_(boolean v) {prog_running = v; return this;} private boolean prog_running; + public long Src_content_length() {return src_content_length;} public IoEngine_xrg_downloadFil Src_content_length_(long v) {src_content_length = v; return this;} private long src_content_length; + public DateAdp Src_last_modified() {return src_last_modified;} public IoEngine_xrg_downloadFil Src_last_modified_(DateAdp v) {src_last_modified = v; return this;} private DateAdp src_last_modified; + public boolean Src_last_modified_query() {return src_last_modified_query;} public IoEngine_xrg_downloadFil Src_last_modified_query_(boolean v) {src_last_modified_query = v; return this;} private boolean src_last_modified_query; + public String Trg_engine_key() {return trg_engine_key;} public IoEngine_xrg_downloadFil Trg_engine_key_(String v) {trg_engine_key = v; return this;} private String trg_engine_key = IoEngine_.SysKey; + public Io_download_fmt Download_fmt() {return download_fmt;} private final Io_download_fmt download_fmt = new Io_download_fmt(); + public boolean Exec() {return IoEnginePool.Instance.Get_by(trg.Info().EngineKey()).DownloadFil(this);} + public Io_stream_rdr Exec_as_rdr() {return IoEnginePool.Instance.Get_by(IoEngine_.SysKey).DownloadFil_as_rdr(this);} + public boolean Exec_meta_only() {return exec_meta_only;} private boolean exec_meta_only; + public byte[] Exec_as_bry(String src) { + this.Src_(src); this.Trg_(trg_mem); + download_fmt.Download_init(src, prog_fmt_hdr); // NOTE: must set src else NULL error + boolean pass = IoEnginePool.Instance.Get_by(trg_engine_key).DownloadFil(this); + return pass ? Io_mgr.Instance.LoadFilBry(trg_mem) : null; + } private Io_url trg_mem = Io_url_.mem_fil_("mem/download.tmp"); + public boolean Exec_meta(String src) { + this.Src_(src); this.Trg_(trg_mem); // NOTE: set Trg_ else error in download proc + download_fmt.Download_init(src, prog_fmt_hdr); // NOTE: must set src else NULL error + exec_meta_only = true; + boolean rv = IoEnginePool.Instance.Get_by(trg_engine_key).DownloadFil(this); + exec_meta_only = false; + return rv; + } + public void Init(String src, Io_url trg) { + this.src = src; this.trg = trg; + prog_cancel = false; + rslt_err = null; + rslt = Rslt_pass; + prog_running = true; + download_fmt.Download_init(src, "downloading ~{src_name}: ~{prog_left} left (@ ~{prog_rate}); ~{prog_done} of ~{src_len} (~{prog_pct}%)"); + } + public static IoEngine_xrg_downloadFil new_(String src, Io_url trg) { + IoEngine_xrg_downloadFil rv = new IoEngine_xrg_downloadFil(); + rv.src = src; rv.trg = trg; + return rv; + } IoEngine_xrg_downloadFil() {} + public static final byte Rslt_pass = 0, Rslt_fail_host_not_found = 1, Rslt_fail_file_not_found = 2, Rslt_fail_unknown = 3; +} diff --git a/100_core/src/gplx/core/ios/IoEngine_xrg_fil_affects1_base.java b/100_core/src/gplx/core/ios/IoEngine_xrg_fil_affects1_base.java index a27517de8..716cc8f0a 100644 --- a/100_core/src/gplx/core/ios/IoEngine_xrg_fil_affects1_base.java +++ b/100_core/src/gplx/core/ios/IoEngine_xrg_fil_affects1_base.java @@ -13,3 +13,11 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class IoEngine_xrg_fil_affects1_base { + public Io_url Url() {return url;} public void Url_set(Io_url v) {url = v;} Io_url url; + public IoEngine_xrg_fil_affects1_base Url_(Io_url v) {url = v; return this;} + public boolean MissingFails() {return missingFails;} public void MissingFails_set(boolean v) {missingFails = v;} private boolean missingFails = true; + public boolean ReadOnlyFails() {return readOnlyFails;} public void ReadOnlyFails_set(boolean v) {readOnlyFails = v;} private boolean readOnlyFails = true; + @gplx.Virtual public void Exec() {} +} diff --git a/100_core/src/gplx/core/ios/IoEngine_xrg_loadFilStr.java b/100_core/src/gplx/core/ios/IoEngine_xrg_loadFilStr.java index a27517de8..6d898e317 100644 --- a/100_core/src/gplx/core/ios/IoEngine_xrg_loadFilStr.java +++ b/100_core/src/gplx/core/ios/IoEngine_xrg_loadFilStr.java @@ -13,3 +13,30 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.texts.*; import gplx.core.envs.*; +public class IoEngine_xrg_loadFilStr { + public Io_url Url() {return url;} public IoEngine_xrg_loadFilStr Url_(Io_url val) {url = val; return this;} Io_url url; + public boolean MissingIgnored() {return missingIgnored;} public IoEngine_xrg_loadFilStr MissingIgnored_() {return MissingIgnored_(true);} public IoEngine_xrg_loadFilStr MissingIgnored_(boolean v) {missingIgnored = v; return this;} private boolean missingIgnored = false; + public boolean BomUtf8Convert() {return bomUtf8Convert;} public IoEngine_xrg_loadFilStr BomUtf8Convert_(boolean v) {bomUtf8Convert = v; return this;} private boolean bomUtf8Convert = true; + public String Exec() { + String s = IoEnginePool.Instance.Get_by(url.Info().EngineKey()).LoadFilStr(this); + if (bomUtf8Convert && String_.Len(s) > 0 && String_.CodePointAt(s, 0) == Bom_Utf8) { + s = String_.Mid(s, 1); + UsrDlg_.Instance.Warn(UsrMsg.new_("UTF8 BOM removed").Add("url", url.Xto_api())); + } + return s; + } + public String[] ExecAsStrAry() {return String_.Split(Exec(), String_.CrLf);} + public String[] ExecAsStrAryLnx() { + String raw = Exec(); + if (String_.Len(raw) == 0) return String_.Ary_empty; + return String_.Split(raw, Op_sys.Nl_char_lnx, false); + } + int Bom_Utf8 = 65279; // U+FEFF; see http://en.wikipedia.org/wiki/Byte_order_mark + public static IoEngine_xrg_loadFilStr new_(Io_url url) { + IoEngine_xrg_loadFilStr rv = new IoEngine_xrg_loadFilStr(); + rv.url = url; + return rv; + } IoEngine_xrg_loadFilStr() {} +} diff --git a/100_core/src/gplx/core/ios/IoEngine_xrg_openRead.java b/100_core/src/gplx/core/ios/IoEngine_xrg_openRead.java index a27517de8..96b5028ef 100644 --- a/100_core/src/gplx/core/ios/IoEngine_xrg_openRead.java +++ b/100_core/src/gplx/core/ios/IoEngine_xrg_openRead.java @@ -13,3 +13,22 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.ios.streams.*; +public class IoEngine_xrg_openRead { + public Io_url Url() {return url;} Io_url url; + public String ErrMsg() {return errMsg;} private String errMsg; + public IoStream ExecAsIoStreamOrFail() {return IoEnginePool.Instance.Get_by(url.Info().EngineKey()).OpenStreamRead(url);} + public IoStream ExecAsIoStreamOrNull() { + try {return IoEnginePool.Instance.Get_by(url.Info().EngineKey()).OpenStreamRead(url);} + catch (Exception exc) { + errMsg = Err_.Message_lang(exc); + return IoStream_.Null; + } + } + public static IoEngine_xrg_openRead new_(Io_url url) { + IoEngine_xrg_openRead rv = new IoEngine_xrg_openRead(); + rv.url = url; + return rv; + } IoEngine_xrg_openRead() {} +} diff --git a/100_core/src/gplx/core/ios/IoEngine_xrg_openWrite.java b/100_core/src/gplx/core/ios/IoEngine_xrg_openWrite.java index a27517de8..fff31a60e 100644 --- a/100_core/src/gplx/core/ios/IoEngine_xrg_openWrite.java +++ b/100_core/src/gplx/core/ios/IoEngine_xrg_openWrite.java @@ -13,3 +13,18 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.ios.streams.*; +public class IoEngine_xrg_openWrite { + public Io_url Url() {return url;} public IoEngine_xrg_openWrite Url_(Io_url val) {url = val; return this;} Io_url url; + public boolean ReadOnlyIgnored() {return readOnlyIgnored;} public IoEngine_xrg_openWrite ReadOnlyIgnored_() {return ReadOnlyIgnored_(true);} public IoEngine_xrg_openWrite ReadOnlyIgnored_(boolean v) {readOnlyIgnored = v; return this;} private boolean readOnlyIgnored = false; + public boolean MissingIgnored() {return missingIgnored;} public IoEngine_xrg_openWrite MissingIgnored_() {return MissingIgnored_(true);} public IoEngine_xrg_openWrite MissingIgnored_(boolean v) {missingIgnored = v; return this;} private boolean missingIgnored = false; + public byte Mode() {return mode;} public IoEngine_xrg_openWrite Mode_(byte v) {mode = v; return this;} private byte mode = IoStream_.Mode_wtr_create; + public IoEngine_xrg_openWrite Mode_update_() {return Mode_(IoStream_.Mode_wtr_update);} + public IoStream Exec() {return IoEnginePool.Instance.Get_by(url.Info().EngineKey()).OpenStreamWrite(this);} + public static IoEngine_xrg_openWrite new_(Io_url url) { + IoEngine_xrg_openWrite rv = new IoEngine_xrg_openWrite(); + rv.url = url; + return rv; + } IoEngine_xrg_openWrite() {} +} diff --git a/100_core/src/gplx/core/ios/IoEngine_xrg_queryDir.java b/100_core/src/gplx/core/ios/IoEngine_xrg_queryDir.java index a27517de8..c5883ed44 100644 --- a/100_core/src/gplx/core/ios/IoEngine_xrg_queryDir.java +++ b/100_core/src/gplx/core/ios/IoEngine_xrg_queryDir.java @@ -13,3 +13,41 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.consoles.*; import gplx.core.criterias.*; +public class IoEngine_xrg_queryDir { + public Io_url Url() {return url;} public IoEngine_xrg_queryDir Url_(Io_url val) {url = val; return this;} Io_url url; + public boolean Recur() {return recur;} public IoEngine_xrg_queryDir Recur_() {return Recur_(true);} public IoEngine_xrg_queryDir Recur_(boolean val) {recur = val; return this;} private boolean recur = false; + public boolean DirInclude() {return dirInclude;} public IoEngine_xrg_queryDir DirInclude_() {return DirInclude_(true);} public IoEngine_xrg_queryDir DirInclude_(boolean val) {dirInclude = val; return this;} private boolean dirInclude = false; + public Criteria FilCrt() {return filCrt;} public IoEngine_xrg_queryDir FilCrt_(Criteria val) {filCrt = val; return this;} Criteria filCrt; + public Criteria DirCrt() {return dirCrt;} public IoEngine_xrg_queryDir DirCrt_(Criteria val) {dirCrt = val; return this;} Criteria dirCrt; + public Criteria SubDirScanCrt() {return subDirScanCrt;} public IoEngine_xrg_queryDir SubDirScanCrt_(Criteria val) {subDirScanCrt = val; return this;} Criteria subDirScanCrt; + public IoEngine_xrg_queryDir DirOnly_() { + DirInclude_(true); + filCrt = Criteria_.None; + return this; + } + + public Console_adp UsrDlg() {return usrDlg;} public IoEngine_xrg_queryDir UsrDlg_(Console_adp val) {usrDlg = val; return this;} Console_adp usrDlg = Console_adp_.Noop; + public IoEngine_xrg_queryDir FilPath_(String val) { + Criteria_ioMatch crt = Criteria_ioMatch.parse(true, val, url.Info().CaseSensitive()); + filCrt = Criteria_fld.new_(IoItm_base_.Prop_Path, crt); + return this; + } + public IoItmDir ExecAsDir() {return IoEnginePool.Instance.Get_by(url.Info().EngineKey()).QueryDirDeep(this);} + public Io_url[] ExecAsUrlAry() {return ExecAsItmHash().XtoIoUrlAry();} + public IoItmHash ExecAsItmHash() { + Criteria crt = dirInclude ? Criteria_.All : Criteria_fld.new_(IoItm_base_.Prop_Type, Criteria_.eq_(IoItmFil.Type_Fil)); + IoItmHash list = ExecAsDir().XtoIoItmList(crt); + list.Sort_by(IoItmBase_comparer_nest.Instance); + return list; + } + public static IoEngine_xrg_queryDir new_(Io_url url) { + IoEngine_xrg_queryDir rv = new IoEngine_xrg_queryDir(); + rv.url = url; + rv.filCrt = Criteria_fld.new_(IoItm_base_.Prop_Path, Criteria_.All); + rv.dirCrt = Criteria_fld.new_(IoItm_base_.Prop_Path, Criteria_.All); + rv.subDirScanCrt = Criteria_fld.new_(IoItm_base_.Prop_Path, Criteria_.All); + return rv; + } IoEngine_xrg_queryDir() {} +} diff --git a/100_core/src/gplx/core/ios/IoEngine_xrg_recycleFil.java b/100_core/src/gplx/core/ios/IoEngine_xrg_recycleFil.java index a27517de8..edae474ea 100644 --- a/100_core/src/gplx/core/ios/IoEngine_xrg_recycleFil.java +++ b/100_core/src/gplx/core/ios/IoEngine_xrg_recycleFil.java @@ -13,3 +13,45 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.strings.*; +public class IoEngine_xrg_recycleFil extends IoEngine_xrg_fil_affects1_base { + public IoEngine_xrg_recycleFil MissingFails_off() {return MissingFails_(false);} public IoEngine_xrg_recycleFil MissingFails_(boolean v) {MissingFails_set(v); return this;} + + public int Mode() {return mode;} public IoEngine_xrg_recycleFil Mode_(int v) {mode = v; return this;} int mode; + public String AppName() {return appName;} public IoEngine_xrg_recycleFil AppName_(String val) {appName = val; return this;} private String appName = "unknown_app"; + public Guid_adp Uuid() {return uuid;} public IoEngine_xrg_recycleFil Uuid_(Guid_adp val) {uuid = val; return this;} Guid_adp uuid; + public boolean Uuid_include() {return uuid_include;} public IoEngine_xrg_recycleFil Uuid_include_() {uuid_include = true; return this;} private boolean uuid_include; + public DateAdp Time() {return time;} public IoEngine_xrg_recycleFil Time_(DateAdp val) {time = val; return this;} DateAdp time; + public List_adp RootDirNames() {return rootDirNames;} public IoEngine_xrg_recycleFil RootDirNames_(List_adp val) {rootDirNames = val; return this;} List_adp rootDirNames; + public Io_url RecycleUrl() { + String dayName = time.XtoStr_fmt("yyyyMMdd"), timeName = time.XtoStr_fmt("hhmmssfff"); + String rootDirStr = ConcatWith_ary(this.Url().Info().DirSpr(), rootDirNames); + Io_url recycleDir = this.Url().OwnerRoot().GenSubDir_nest(rootDirStr, dayName); + String uuidStr = uuid_include ? uuid.To_str() : ""; + return recycleDir.GenSubFil_ary(appName, ";", timeName, ";", uuidStr, ";", String_.LimitToFirst(this.Url().NameAndExt(), 128)); + } + String ConcatWith_ary(String separator, List_adp ary) { + String_bldr sb = String_bldr_.new_(); + int aryLen = ary.Count(); + for (int i = 0; i < aryLen; i++) { + if (i != 0) sb.Add(separator); + Object val = ary.Get_at(i); + sb.Add_obj(Object_.Xto_str_strict_or_empty(val)); + } + return sb.To_str(); + } + @Override public void Exec() { + IoEnginePool.Instance.Get_by(this.Url().Info().EngineKey()).RecycleFil(this); + } + public IoEngine_xrg_recycleFil(int v) { + mode = v; + time = Datetime_now.Get(); + uuid = Guid_adp_.New(); + rootDirNames = List_adp_.New(); rootDirNames.Add("z_trash"); + } + public static IoEngine_xrg_recycleFil sysm_(Io_url url) {return new IoEngine_xrg_recycleFil(SysmConst);} + public static IoEngine_xrg_recycleFil gplx_(Io_url url) {IoEngine_xrg_recycleFil rv = new IoEngine_xrg_recycleFil(GplxConst); rv.Url_set(url); return rv;} + public static IoEngine_xrg_recycleFil proto_() {return gplx_(Io_url_.Empty);} + public static final int GplxConst = 0, SysmConst = 1; +} diff --git a/100_core/src/gplx/core/ios/IoEngine_xrg_saveFilStr.java b/100_core/src/gplx/core/ios/IoEngine_xrg_saveFilStr.java index a27517de8..568bbe494 100644 --- a/100_core/src/gplx/core/ios/IoEngine_xrg_saveFilStr.java +++ b/100_core/src/gplx/core/ios/IoEngine_xrg_saveFilStr.java @@ -13,3 +13,19 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.texts.*; +public class IoEngine_xrg_saveFilStr { + public Io_url Url() {return url;} public IoEngine_xrg_saveFilStr Url_(Io_url val) {url = val; return this;} Io_url url; + public String Text() {return text;} public IoEngine_xrg_saveFilStr Text_(String val) {text = val; return this;} private String text = ""; + public boolean Append() {return append;} public IoEngine_xrg_saveFilStr Append_() {return Append_(true);} public IoEngine_xrg_saveFilStr Append_(boolean val) {append = val; return this;} private boolean append = false; + public void Exec() { + if (String_.Eq(text, "") && append) return; // no change; don't bother writing to disc + IoEnginePool.Instance.Get_by(url.Info().EngineKey()).SaveFilText_api(this); + } + public static IoEngine_xrg_saveFilStr new_(Io_url url, String text) { + IoEngine_xrg_saveFilStr rv = new IoEngine_xrg_saveFilStr(); + rv.url = url; rv.text = text; + return rv; + } IoEngine_xrg_saveFilStr() {} +} diff --git a/100_core/src/gplx/core/ios/IoEngine_xrg_xferDir.java b/100_core/src/gplx/core/ios/IoEngine_xrg_xferDir.java index a27517de8..0fd439e0e 100644 --- a/100_core/src/gplx/core/ios/IoEngine_xrg_xferDir.java +++ b/100_core/src/gplx/core/ios/IoEngine_xrg_xferDir.java @@ -13,3 +13,23 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.criterias.*; +public class IoEngine_xrg_xferDir { + public boolean Type_move() {return move;} public boolean Type_copy() {return !move;} private boolean move = false; + public Io_url Src() {return src;} public IoEngine_xrg_xferDir Src_(Io_url val) {src = val; return this;} Io_url src; + public Io_url Trg() {return trg;} public IoEngine_xrg_xferDir Trg_(Io_url val) {trg = val; return this;} Io_url trg; + public boolean Recur() {return recur;} public IoEngine_xrg_xferDir Recur_() {recur = true; return this;} private boolean recur = false; + public boolean Overwrite() {return overwrite;} public IoEngine_xrg_xferDir Overwrite_() {return Overwrite_(true);} public IoEngine_xrg_xferDir Overwrite_(boolean v) {overwrite = v; return this;} private boolean overwrite = false; + public boolean ReadOnlyFails() {return readOnlyFails;} public IoEngine_xrg_xferDir ReadOnlyFails_() {return ReadOnlyFails_(true);} public IoEngine_xrg_xferDir ReadOnlyFails_(boolean v) {readOnlyFails = v; return this;} private boolean readOnlyFails = false; + public Criteria MatchCrt() {return matchCrt;} public IoEngine_xrg_xferDir MatchCrt_(Criteria v) {matchCrt = v; return this;} Criteria matchCrt = Criteria_.All; + public Criteria SubDirScanCrt() {return subDirScanCrt;} public IoEngine_xrg_xferDir SubDirScanCrt_(Criteria v) {subDirScanCrt = v; return this;} Criteria subDirScanCrt = Criteria_.All; + public void Exec() {IoEnginePool.Instance.Get_by(src.Info().EngineKey()).XferDir(this);} + public static IoEngine_xrg_xferDir move_(Io_url src, Io_url trg) {return new_(src, trg, true);} + public static IoEngine_xrg_xferDir copy_(Io_url src, Io_url trg) {return new_(src, trg, false);} + static IoEngine_xrg_xferDir new_(Io_url src, Io_url trg, boolean move) { + IoEngine_xrg_xferDir rv = new IoEngine_xrg_xferDir(); + rv.src = src; rv.trg = trg; rv.move = move; + return rv; + } IoEngine_xrg_xferDir() {} +} diff --git a/100_core/src/gplx/core/ios/IoEngine_xrg_xferFil.java b/100_core/src/gplx/core/ios/IoEngine_xrg_xferFil.java index a27517de8..872d15698 100644 --- a/100_core/src/gplx/core/ios/IoEngine_xrg_xferFil.java +++ b/100_core/src/gplx/core/ios/IoEngine_xrg_xferFil.java @@ -13,3 +13,20 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class IoEngine_xrg_xferFil { + public boolean Type_move() {return move;} private boolean move = false; + public Io_url Src() {return src;} Io_url src; + public Io_url Trg() {return trg;} Io_url trg; + public boolean Overwrite() {return overwrite;} public IoEngine_xrg_xferFil Overwrite_() {return Overwrite_(true);} public IoEngine_xrg_xferFil Overwrite_(boolean v) {overwrite = v; return this;} private boolean overwrite = false; + public boolean ReadOnlyFails() {return readOnlyFails;} public IoEngine_xrg_xferFil ReadOnlyFails_off() {return ReadOnlyFails_(false);} public IoEngine_xrg_xferFil ReadOnlyFails_(boolean v) {readOnlyFails = v; return this;} private boolean readOnlyFails = true; + public boolean MissingFails() {return missingFails;} public IoEngine_xrg_xferFil MissingFails_off() {return MissingFails_(false);} public IoEngine_xrg_xferFil MissingFails_(boolean v) {missingFails = v; return this;} private boolean missingFails = true; + public void Exec() {IoEnginePool.Instance.Get_by(src.Info().EngineKey()).XferFil(this);} + public static IoEngine_xrg_xferFil move_(Io_url src, Io_url trg) {return new_(src, trg, true);} + public static IoEngine_xrg_xferFil copy_(Io_url src, Io_url trg) {return new_(src, trg, false);} + static IoEngine_xrg_xferFil new_(Io_url src, Io_url trg, boolean move) { + IoEngine_xrg_xferFil rv = new IoEngine_xrg_xferFil(); + rv.src = src; rv.trg = trg; rv.move = move; + return rv; + } IoEngine_xrg_xferFil() {} +} diff --git a/100_core/src/gplx/core/ios/IoErr.java b/100_core/src/gplx/core/ios/IoErr.java index a27517de8..9d6999c57 100644 --- a/100_core/src/gplx/core/ios/IoErr.java +++ b/100_core/src/gplx/core/ios/IoErr.java @@ -13,3 +13,16 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class IoErr { + public static String Namespace = "gplx.core.ios."; + public static String FileIsReadOnly_key = Namespace + "FileIsReadOnlyError"; + public static String FileNotFound_key = Namespace + "FileNotFoundError"; + public static Err FileIsReadOnly(Io_url url) { + return Err_.new_(FileIsReadOnly_key, "file is read-only", "url", url.Xto_api()).Trace_ignore_add_1_(); + } + public static Err FileNotFound(String op, Io_url url) { + // file is missing -- op='copy' file='C:\a.txt' copyFile_target='D:\a.txt' + return Err_.new_(FileNotFound_key, "file not found", "op", op, "file", url.Xto_api()).Trace_ignore_add_1_(); + } +} \ No newline at end of file diff --git a/100_core/src/gplx/core/ios/IoItmAttrib.java b/100_core/src/gplx/core/ios/IoItmAttrib.java index a27517de8..aa70f4f92 100644 --- a/100_core/src/gplx/core/ios/IoItmAttrib.java +++ b/100_core/src/gplx/core/ios/IoItmAttrib.java @@ -13,3 +13,11 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class IoItmAttrib { + public boolean ReadOnly() {return readOnly;} public IoItmAttrib ReadOnly_() {return ReadOnly_(true);} public IoItmAttrib ReadOnly_(boolean val) {readOnly = val; return this;} private boolean readOnly; + public boolean Hidden() {return hidden;} public IoItmAttrib Hidden_() {return Hidden_(true);} public IoItmAttrib Hidden_(boolean val) {hidden = val; return this;} private boolean hidden; + public static IoItmAttrib readOnly_() {return new IoItmAttrib().ReadOnly_();} + public static IoItmAttrib hidden_() {return new IoItmAttrib().Hidden_();} + public static IoItmAttrib normal_() {return new IoItmAttrib().ReadOnly_(false).Hidden_(false);} +} diff --git a/100_core/src/gplx/core/ios/IoItmClassXtn.java b/100_core/src/gplx/core/ios/IoItmClassXtn.java index a27517de8..214cbfb1c 100644 --- a/100_core/src/gplx/core/ios/IoItmClassXtn.java +++ b/100_core/src/gplx/core/ios/IoItmClassXtn.java @@ -13,3 +13,19 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.type_xtns.*; +public class IoItmClassXtn extends ClassXtn_base implements ClassXtn { + public String Key() {return Key_const;} public static final String Key_const = "ioItemType"; + @Override public Class UnderClass() {return int.class;} + public Object DefaultValue() {return IoItmDir.Type_Dir;} + public boolean Eq(Object lhs, Object rhs) {return ((IoItm_base)lhs).compareTo(rhs) == CompareAble_.Same;} + @Override public Object ParseOrNull(String raw) { + String rawLower = String_.Lower(raw); + if (String_.Eq(rawLower, "dir")) return IoItmDir.Type_Dir; + else if (String_.Eq(rawLower, "fil")) return IoItmFil.Type_Fil; + else throw Err_.new_unhandled(raw); + } + @Override public Object XtoDb(Object obj) {return Int_.Cast(obj);} + public static final IoItmClassXtn Instance = new IoItmClassXtn(); IoItmClassXtn() {} +} diff --git a/100_core/src/gplx/core/ios/IoItmDir.java b/100_core/src/gplx/core/ios/IoItmDir.java index a27517de8..50ee535bd 100644 --- a/100_core/src/gplx/core/ios/IoItmDir.java +++ b/100_core/src/gplx/core/ios/IoItmDir.java @@ -13,3 +13,57 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.criterias.*; +public class IoItmDir extends IoItm_base { + public boolean Exists() {return exists;} public void Exists_set(boolean v) {exists = v;} private boolean exists = true; + @Override public int TypeId() {return Type_Dir;} @Override public boolean Type_dir() {return true;} @Override public boolean Type_fil() {return false;} public static final int Type_Dir = 1; + @gplx.New public IoItmDir XtnProps_set(String key, Object val) {return (IoItmDir)super.XtnProps_set(key, val);} + public IoItmList SubDirs() {return subDirs;} IoItmList subDirs; + public IoItmList SubFils() {return subFils;} IoItmList subFils; + public IoItmHash XtoIoItmList(Criteria crt) { + IoItmHash rv = IoItmHash.list_(this.Url()); + XtoItmList_recur(rv, this, crt); + return rv; + } + Io_url[] XtoIoUrlAry() { + IoItmHash list = this.XtoIoItmList(Criteria_.All); +//#plat_wce list.Sort(); // NOTE: on wce, subFils retrieved in unexpected order; createTime vs pathString + int count = list.Count(); + Io_url[] rv = new Io_url[count]; + for (int i = 0; i < count; i++) + rv[i] = list.Get_at(i).Url(); + return rv; + } + public IoItmDir FetchDeepOrNull(Io_url findDirUrl) { + String dirSpr = this.Url().Info().DirSpr(); int dirSprLen = String_.Len(dirSpr); + String currDirStr = this.Url().Raw(); + String findDirStr = findDirUrl.Raw(); + if (!String_.Has_at_bgn(findDirStr, currDirStr)) return null; // findUrl must start with currUrl; + String findName = String_.DelEnd(currDirStr, dirSprLen); // seed findName for String_.MidByLen below; + IoItmDir curDir = this; + while (true) { + findDirStr = String_.DelBgn(findDirStr, String_.Len(findName) + dirSprLen); // NOTE: findName will never have trailingDirSpr; subDirs.Get_by() takes NameOnly; ex: "dir" not "dir\" + int nextDirSprPos = String_.FindFwd(findDirStr, dirSpr); if (nextDirSprPos == String_.Find_none) nextDirSprPos = String_.Len(findDirStr); + findName = String_.MidByLen(findDirStr, 0, nextDirSprPos); + if (String_.Eq(findDirStr, "")) return curDir; // findDirStr completely removed; all parts match; return curDir + curDir = IoItmDir_.as_(curDir.subDirs.Get_by(findName)); // try to find dir + if (curDir == null) return null; // dir not found; exit; NOTE: if dir found, loop restarts; with curDir as either findDir, or owner of findDir + } + } + void XtoItmList_recur(IoItmHash list, IoItmDir curDir, Criteria dirCrt) { + for (Object subFilObj : curDir.SubFils()) { + IoItmFil subFil = (IoItmFil)subFilObj; + list.Add(subFil); + } + for (Object subDirObj : curDir.SubDirs()) { + IoItmDir subDir = (IoItmDir)subDirObj; + if (dirCrt.Matches(subDir)) list.Add(subDir); + XtoItmList_recur(list, subDir, dirCrt); + } + } + @gplx.Internal protected IoItmDir(boolean caseSensitive) { + subDirs = IoItmList.new_(this, caseSensitive); + subFils = IoItmList.new_(this, caseSensitive); + } +} diff --git a/100_core/src/gplx/core/ios/IoItmDir_.java b/100_core/src/gplx/core/ios/IoItmDir_.java index a27517de8..402c92127 100644 --- a/100_core/src/gplx/core/ios/IoItmDir_.java +++ b/100_core/src/gplx/core/ios/IoItmDir_.java @@ -13,3 +13,41 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class IoItmDir_ { + public static IoItmDir as_(Object obj) {return obj instanceof IoItmDir ? (IoItmDir)obj : null;} + public static final IoItmDir Null = null_(); + public static IoItmDir top_(Io_url url) {return scan_(url);} + public static IoItmDir scan_(Io_url url) { + IoItmDir rv = new IoItmDir(url.Info().CaseSensitive()); + rv.ctor_IoItmBase_url(url); + return rv; + } + public static IoItmDir sub_(String name) { + IoItmDir rv = new IoItmDir(Bool_.Y); + rv.ctor_IoItmBase_url(Io_url_.mem_dir_("mem/" + name)); + return rv; + } + static IoItmDir null_() { + IoItmDir rv = new IoItmDir(true); // TODO_OLD: NULL should be removed + rv.ctor_IoItmBase_url(Io_url_.Empty); + rv.Exists_set(false); + return rv; + } + public static void Make(IoItmDir dir) { + Io_mgr.Instance.CreateDir(dir.Url()); + int len = dir.SubDirs().Count(); + for (int i = 0; i < len; ++i) { + IoItmDir sub_dir = (IoItmDir)dir.SubDirs().Get_at(i); + Make(sub_dir); + } + len = dir.SubFils().Count(); + for (int i = 0; i < len; ++i) { + IoItmFil sub_fil = (IoItmFil)dir.SubFils().Get_at(i); + String text = String_.Repeat("a", (int)sub_fil.Size()); + Io_url sub_url = sub_fil.Url(); + Io_mgr.Instance.SaveFilStr(sub_url, text); + Io_mgr.Instance.UpdateFilModifiedTime(sub_url, sub_fil.ModifiedTime()); + } + } +} diff --git a/100_core/src/gplx/core/ios/IoItmFil.java b/100_core/src/gplx/core/ios/IoItmFil.java index a27517de8..79e5fe73f 100644 --- a/100_core/src/gplx/core/ios/IoItmFil.java +++ b/100_core/src/gplx/core/ios/IoItmFil.java @@ -13,3 +13,29 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.langs.gfs.*; +public class IoItmFil extends IoItm_base { + @Override public int TypeId() {return IoItmFil.Type_Fil;} @Override public boolean Type_dir() {return false;} @Override public boolean Type_fil() {return true;} public static final int Type_Fil = 2; + public boolean Exists() {return size != Size_invalid;} // NOTE: questionable logic, but preserved for historical reasons; requires that length be set to -1 if !.exists + public DateAdp ModifiedTime() {return modifiedTime;} + public IoItmFil ModifiedTime_(DateAdp val) {modifiedTime = val; return this;} DateAdp modifiedTime; + public IoItmFil ModifiedTime_(String val) {return ModifiedTime_(DateAdp_.parse_gplx(val));} + @gplx.Virtual public long Size() {return size;} public IoItmFil Size_(long val) {size = val; return this;} private long size; + public IoItmAttrib Attrib() {return attrib;} public IoItmFil Attrib_(IoItmAttrib val) {attrib = val; return this;} IoItmAttrib attrib = IoItmAttrib.normal_(); + public boolean ReadOnly() {return attrib.ReadOnly();} public IoItmFil ReadOnly_(boolean val) {attrib.ReadOnly_(val); return this;} + @gplx.New public IoItmFil XtnProps_set(String key, Object val) {return (IoItmFil)super.XtnProps_set(key, val);} + + @gplx.Internal protected IoItmFil ctor_IoItmFil(Io_url url, long size, DateAdp modifiedTime) { + ctor_IoItmBase_url(url); this.size = size; this.modifiedTime = modifiedTime; + return this; + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, IoItmFil_.Prop_Size)) return size; + else if (ctx.Match(k, IoItmFil_.Prop_Modified)) return modifiedTime; + else return super.Invk(ctx, ikey, k, m); + } + @gplx.Internal protected IoItmFil() {} + public static final long Size_invalid = -1; + public static final int Size_invalid_int = -1; +} diff --git a/100_core/src/gplx/core/ios/IoItmFil_.java b/100_core/src/gplx/core/ios/IoItmFil_.java index a27517de8..e4d446235 100644 --- a/100_core/src/gplx/core/ios/IoItmFil_.java +++ b/100_core/src/gplx/core/ios/IoItmFil_.java @@ -13,3 +13,17 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class IoItmFil_ { + public static IoItmFil as_(Object obj) {return obj instanceof IoItmFil ? (IoItmFil)obj : null;} + public static final String + Prop_Size = "size" + , Prop_Modified = "modified"; + public static IoItmFil new_(Io_url url, long size, DateAdp created, DateAdp modified) {return new IoItmFil().ctor_IoItmFil(url, size, modified);} + public static IoItmFil sub_(String name, long size, DateAdp modified) { + IoItmFil rv = new IoItmFil(); + rv.ctor_IoItmFil(Io_url_.mem_fil_("mem/" + name), size, modified); + rv.Name_(name); + return rv; + } +} diff --git a/100_core/src/gplx/core/ios/IoItmFil_mem.java b/100_core/src/gplx/core/ios/IoItmFil_mem.java index a27517de8..b83bf26ad 100644 --- a/100_core/src/gplx/core/ios/IoItmFil_mem.java +++ b/100_core/src/gplx/core/ios/IoItmFil_mem.java @@ -13,3 +13,25 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.ios.streams.*; /*IoStream_mem*/ import gplx.core.texts.*; /*Encoding_*/ +class IoItmFil_mem extends IoItmFil { public static IoItmFil_mem as_(Object obj) {return obj instanceof IoItmFil_mem ? (IoItmFil_mem)obj : null;} + @gplx.Internal protected IoStream_mem Stream() {return stream;} IoStream_mem stream; // NOTE: using stream instead of Text, b/c no events for IoStream.Dispose; ex: stream.OpenStreamWrite; stream.Write("hi"); stream.Dispose(); "hi" would not be saved if Text is member variable + @Override public long Size() {return (int)stream.Len();} + public String Text() {return Text_get();} public void Text_set(String v) {stream = IoStream_mem.rdr_txt_(this.Url(), v);} + String Text_get() { + int len = (int)stream.Len(); + byte[] buffer = new byte[len]; + stream.Position_set(0); + stream.Read(buffer, 0, len); + return String_.new_u8(buffer); + } + public IoItmFil_mem Clone() {return new_(this.Url(), this.Size(), this.ModifiedTime(), this.Text());} + public static IoItmFil_mem new_(Io_url filPath, long size, DateAdp modified, String text) { + IoItmFil_mem rv = new IoItmFil_mem(); + rv.ctor_IoItmFil(filPath, size, modified); + rv.stream = IoStream_mem.rdr_txt_(filPath, text); + return rv; + } + public static final IoItmFil_mem Null = new_(Io_url_.Empty, -1, DateAdp_.MinValue, ""); // NOTE: size must be -1 for .Exists to be false; DATE:2015-05-16 +} diff --git a/100_core/src/gplx/core/ios/IoItmHash.java b/100_core/src/gplx/core/ios/IoItmHash.java index a27517de8..e22b0c495 100644 --- a/100_core/src/gplx/core/ios/IoItmHash.java +++ b/100_core/src/gplx/core/ios/IoItmHash.java @@ -13,3 +13,29 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class IoItmHash extends Ordered_hash_base { + public Io_url Url() {return url;} Io_url url; + public void Add(IoItm_base itm) {Add_base(MakeKey(itm.Url()), itm);} + public void Del(Io_url url) {Del(MakeKey(url));} + public IoItm_base Get_by(Io_url url) {return IoItm_base_.as_(Fetch_base(MakeKey(url)));} + @gplx.New public IoItm_base Get_at(int i) {return IoItm_base_.as_(Get_at_base(i));} + public Io_url[] XtoIoUrlAry() { + int count = this.Count(); + Io_url[] rv = new Io_url[count]; + for (int i = 0; i < count; i++) + rv[i] = this.Get_at(i).Url(); + return rv; + } + String MakeKey(Io_url url) {return url.XtoCaseNormalized();} + public static IoItmHash new_() { + IoItmHash rv = new IoItmHash(); + rv.url = null;//Io_url_.Empty; + return rv; + } IoItmHash() {} + public static IoItmHash list_(Io_url url) { + IoItmHash rv = new IoItmHash(); + rv.url = url; + return rv; + } +} diff --git a/100_core/src/gplx/core/ios/IoItmList.java b/100_core/src/gplx/core/ios/IoItmList.java index a27517de8..102dcdd85 100644 --- a/100_core/src/gplx/core/ios/IoItmList.java +++ b/100_core/src/gplx/core/ios/IoItmList.java @@ -13,3 +13,62 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.lists.*; /*Ordered_hash_base*/ +public class IoItmList extends Ordered_hash_base { + public boolean Has(Io_url url) {return Has_base(MakeKey(url));} + public void Add(IoItm_base itm) { + if (ownerDir != null) itm.OwnerDir_set(ownerDir); + Add_base(MakeKey(itm.Url()), itm); + } + public void Del(Io_url url) { + String key = MakeKey(url); + IoItm_base itm = IoItm_base_.as_(Fetch_base(key)); if (itm == null) return; + itm.OwnerDir_set(null); + super.Del(key); + } + public Io_url[] XtoIoUrlAry() { + int count = this.Count(); + Io_url[] rv = new Io_url[count]; + for (int i = 0; i < count; i++) + rv[i] = IoItm_base_.as_(i).Url(); + return rv; + } + @Override public void Sort() {Sort_by(IoItmBase_comparer_nest.Instance);} + @Override protected Object Fetch_base(Object keyObj) { + String key = MakeKey((String)keyObj); + return super.Fetch_base(key); + } + @Override public void Del(Object keyObj) { + String key = MakeKey((String)keyObj); + super.Del(key); + } + String MakeKey(Io_url url) { + String itmName = url.Type_dir() ? url.NameOnly() : url.NameAndExt(); + return MakeKey(itmName); + } + String MakeKey(String s) { + return caseSensitive ? s : String_.Lower(s); + } + IoItmDir ownerDir; boolean caseSensitive; + @gplx.Internal protected static IoItmList new_(IoItmDir v, boolean caseSensitive) { + IoItmList rv = new IoItmList(); + rv.ownerDir = v; rv.caseSensitive = caseSensitive; + return rv; + } + @gplx.Internal protected static IoItmList list_(boolean caseSensitive) {return new_(null, caseSensitive);} +} +class IoItmBase_comparer_nest implements ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + IoItm_base lhsItm = (IoItm_base)lhsObj, rhsItm = (IoItm_base)rhsObj; + Io_url lhsUrl = lhsItm.Url(), rhsUrl = rhsItm.Url(); + return String_.Eq(lhsUrl.OwnerDir().Raw(), rhsUrl.OwnerDir().Raw()) // is same dir + ? CompareAble_.Compare_obj(lhsUrl.NameAndExt(), rhsUrl.NameAndExt()) // same dir: compare name + : CompareAble_.Compare_obj(DepthOf(lhsItm), DepthOf(rhsItm)); // diff dir: compare by depth; ex: c:\fil.txt < c:\dir\fil.txt + } + int DepthOf(IoItm_base itm) { + Io_url url = itm.Url(); + return String_.Count(url.OwnerDir().Raw(), url.Info().DirSpr()); // use OwnerDir, else dir.Raw will return extra dirSeparator + } + public static final IoItmBase_comparer_nest Instance = new IoItmBase_comparer_nest(); IoItmBase_comparer_nest() {} +} diff --git a/100_core/src/gplx/core/ios/IoItm_base.java b/100_core/src/gplx/core/ios/IoItm_base.java index a27517de8..e715890f2 100644 --- a/100_core/src/gplx/core/ios/IoItm_base.java +++ b/100_core/src/gplx/core/ios/IoItm_base.java @@ -13,3 +13,40 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.langs.gfs.*; +public abstract class IoItm_base implements Gfo_invk, CompareAble { + public abstract int TypeId(); public abstract boolean Type_dir(); public abstract boolean Type_fil(); + public Io_url Url() {return ownerDir == null ? url : ownerDir.Url().GenSubFil(name); /*NOTE: must call .Url*/} Io_url url; + public IoItmDir OwnerDir() {return ownerDir;} IoItmDir ownerDir; + public void OwnerDir_set(IoItmDir v) {if (v == this) throw Err_.new_wo_type("dir cannot be its own owner", "url", v.url.Raw()); + url = v == null && ownerDir != null + ? ownerDir.url.GenSubFil(name) // create url, since ownerDir will soon be null; NOTE: must call .url + : Io_url_.Empty; // delete url, since ownerDir will be avail + ownerDir = v; + } + public String Name() {return name;} private String name; + public IoItm_base Name_(String v) { + name = v; + if (ownerDir == null) url = url.OwnerDir().GenSubFil(name); + return this; + } + public Object XtnProps_get(String key) {return props.Get_by(key);} Hash_adp props = Hash_adp_.Noop; + public IoItm_base XtnProps_set(String key, Object val) { + if (props == Hash_adp_.Noop) props = Hash_adp_.New(); + props.Del(key); + props.Add(key, val); + return this; + } + public int compareTo(Object comp) {return url.compareTo(((IoItm_base)comp).url);} // NOTE: needed for comic importer (sort done on IoItmHash which contains IoItm_base) +// public Object Data_get(String name) {return Gfo_invk_.Invk_by_key(this, name);} + @gplx.Virtual public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, IoItm_base_.Prop_Type)) return this.TypeId(); + else if (ctx.Match(k, IoItm_base_.Prop_Path)) return this.Url(); + else if (ctx.Match(k, IoItm_base_.Prop_Title)) return this.Url().NameOnly(); // needed for gfio script criteria; + else if (ctx.Match(k, IoItm_base_.Prop_Ext)) return this.Url().Ext(); // needed for gfio script criteria; EX: where "ext LIKE '.java'" + else return Gfo_invk_.Rv_unhandled; + } + @gplx.Internal protected void ctor_IoItmBase_url(Io_url url) {this.url = url; this.name = url.NameAndExt();} + @gplx.Internal protected void ctor_IoItmBase_name(String name) {this.name = name;} +} diff --git a/100_core/src/gplx/core/ios/IoItm_base_.java b/100_core/src/gplx/core/ios/IoItm_base_.java index a27517de8..83c64a726 100644 --- a/100_core/src/gplx/core/ios/IoItm_base_.java +++ b/100_core/src/gplx/core/ios/IoItm_base_.java @@ -13,3 +13,12 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class IoItm_base_ { + public static IoItm_base as_(Object obj) {return obj instanceof IoItm_base ? (IoItm_base)obj : null;} + public static final String + Prop_Type = "type" + , Prop_Path = "url" + , Prop_Title = "title" + , Prop_Ext = "ext"; +} diff --git a/100_core/src/gplx/core/ios/IoItm_fxt.java b/100_core/src/gplx/core/ios/IoItm_fxt.java index a27517de8..c6d41632b 100644 --- a/100_core/src/gplx/core/ios/IoItm_fxt.java +++ b/100_core/src/gplx/core/ios/IoItm_fxt.java @@ -13,3 +13,20 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class IoItm_fxt { + public IoItmFil fil_wnt_(String s) {return fil_(Io_url_.wnt_fil_(s));} + public IoItmFil fil_(Io_url url) {return IoItmFil_.new_(url, 1, DateAdp_.parse_gplx("2001-01-01"), DateAdp_.parse_gplx("2001-01-01"));} + public IoItmDir dir_wnt_(String s) {return dir_(Io_url_.wnt_dir_(s));} + public IoItmDir dir_(Io_url url, IoItm_base... ary) { + IoItmDir rv = IoItmDir_.top_(url); + for (IoItm_base itm : ary) { + if (itm.Type_dir()) + rv.SubDirs().Add(itm); + else + rv.SubFils().Add(itm); + } + return rv; + } + public static IoItm_fxt new_() {return new IoItm_fxt();} IoItm_fxt() {} +} diff --git a/100_core/src/gplx/core/ios/IoRecycleBin.java b/100_core/src/gplx/core/ios/IoRecycleBin.java index a27517de8..fc4172ec5 100644 --- a/100_core/src/gplx/core/ios/IoRecycleBin.java +++ b/100_core/src/gplx/core/ios/IoRecycleBin.java @@ -13,3 +13,46 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.strings.*; +public class IoRecycleBin { + public void Send(Io_url url) {Send_xrg(url).Exec();} + public IoEngine_xrg_recycleFil Send_xrg(Io_url url) {return IoEngine_xrg_recycleFil.gplx_(url);} + public void Recover(Io_url url) { + String_bldr sb = String_bldr_.new_(); + List_adp list = Regy_search(url, sb); + int listCount = list.Count(); if (listCount > 1) throw Err_.new_wo_type("found more than 1 url", "count", list.Count()); + Io_url trgUrl = (Io_url)list.Get_at(0); + IoEngine_xrg_xferFil.move_(url, trgUrl).ReadOnlyFails_(true).Overwrite_(false).Exec(); + IoEngine_xrg_saveFilStr.new_(FetchRegistryUrl(url), sb.To_str()).Exec(); + } + public void Regy_add(IoEngine_xrg_recycleFil xrg) { + Io_url url = xrg.RecycleUrl(); + Io_url regyUrl = FetchRegistryUrl(url); + String text = String_.Concat_with_obj("|", url.NameAndExt_noDirSpr(), xrg.Url().GenRelUrl_orEmpty(url.OwnerRoot()), xrg.Uuid().To_str(), xrg.AppName(), xrg.Time()); + IoEngine_xrg_saveFilStr.new_(regyUrl, text).Append_().Exec(); + } + public List_adp Regy_search(Io_url url, String_bldr sb) { + List_adp list = List_adp_.New(); + Io_url regyUrl = FetchRegistryUrl(url); + String[] lines = IoEngine_xrg_loadFilStr.new_(regyUrl).ExecAsStrAry(); + int linesLen = Array_.Len(lines); + String nameAndExt = url.NameAndExt_noDirSpr() + "|"; + for (int i = linesLen; i > 0; i--) { + String line = lines[i - 1]; + if (String_.Has_at_bgn(line, nameAndExt)) { + String[] terms = String_.Split(line, "|"); + Io_url origUrl = url.OwnerRoot().GenSubFil(terms[1]); + list.Add(origUrl); + } + else + sb.Add_str_w_crlf(line); + } + return list; + } + Io_url FetchRegistryUrl(Io_url url) { + String sourceApp = String_.GetStrBefore(url.NameAndExt_noDirSpr(), ";"); + return url.OwnerDir().GenSubFil_ary(sourceApp, ".recycle.csv"); + } + public static final IoRecycleBin Instance = new IoRecycleBin(); IoRecycleBin() {} +} diff --git a/100_core/src/gplx/core/ios/IoUrlInfo.java b/100_core/src/gplx/core/ios/IoUrlInfo.java index a27517de8..38fa8258b 100644 --- a/100_core/src/gplx/core/ios/IoUrlInfo.java +++ b/100_core/src/gplx/core/ios/IoUrlInfo.java @@ -13,3 +13,231 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.envs.*; +public interface IoUrlInfo { + String Key(); + byte DirSpr_byte(); + String DirSpr(); + boolean CaseSensitive(); + String EngineKey(); + + boolean Match(String raw); + boolean IsDir(String raw); + String Xto_api(String raw); + String OwnerDir(String raw); + String OwnerRoot(String raw); + String NameAndExt(String raw); + String NameOnly(String raw); + String Ext(String raw); + String XtoRootName(String raw, int rawLen); +} +class IoUrlInfo_nil implements IoUrlInfo { + public String Key() {return KeyConst;} public static final String KeyConst = String_.Null_mark; + public String EngineKey() {return "<>";} + public String DirSpr() {return "<>";} + public byte DirSpr_byte() {return Byte_ascii.Slash;} + public String VolSpr() {return "<>";} + public boolean CaseSensitive() {return false;} + public boolean Match(String raw) {return false;} + public boolean IsDir(String raw) {return false;} + public String Xto_api(String raw) {return "";} + public String OwnerDir(String raw) {return IoUrlInfo_base.NullString;} + public String OwnerRoot(String raw) {return IoUrlInfo_base.NullString;} + public String NameAndExt(String raw) {return "";} + public String NameOnly(String raw) {return "";} + public String Ext(String raw) {return "";} + public String XtoRootName(String raw, int rawLen) {return "";} + public static final IoUrlInfo_nil Instance = new IoUrlInfo_nil(); IoUrlInfo_nil() {} +} +abstract class IoUrlInfo_base implements IoUrlInfo { + @gplx.Internal protected static final int DirSprLen = 1; + @gplx.Internal protected static final String NullString = "", ExtSeparator = "."; + public abstract String Key(); + public abstract byte DirSpr_byte(); + public abstract String DirSpr(); + public abstract boolean CaseSensitive(); + public abstract boolean Match(String raw); + public abstract String EngineKey(); + public boolean IsDir(String raw) {return String_.Has_at_end(raw, DirSpr());} + public abstract String XtoRootName(String raw, int rawLen); + @gplx.Virtual public String Xto_api(String raw) { + return IsDir(raw) + ? String_.DelEnd(raw, IoUrlInfo_base.DirSprLen) // if Dir, remove trailing DirSpr, since most api will not expect it (ex: .Delete will malfunction) + : raw; + } + public String OwnerDir(String raw) { + int rawLen = String_.Len(raw); + int ownerDirSprPos = OwnerDirPos(raw, rawLen); + if (ownerDirSprPos <= OwnerDirPos_hasNoOwner) return IoUrlInfo_base.NullString; // no ownerDir found; return Null; only (a) NullUrls (b) RootUrls ("C:\") (c) relative ("fil.txt") + return String_.MidByLen(raw, 0, ownerDirSprPos + 1); // +1 to include backslash + } + @gplx.Virtual public String OwnerRoot(String raw) { + String temp = raw, rv = raw; + while (true) { + temp = OwnerDir(temp); + if (String_.Eq(temp, IoUrlInfo_base.NullString)) break; + rv = temp; + } + return rv; + } + public String NameAndExt(String raw) { // if Dir, will return \ as last char + int rawLen = String_.Len(raw); + int ownerDirSprPos = OwnerDirPos(raw, rawLen); + if (ownerDirSprPos == OwnerDirPos_isNull) return IoUrlInfo_base.NullString; // NullUrl and RootUrl return Null; + return ownerDirSprPos == OwnerDirPos_hasNoOwner || ownerDirSprPos == OwnerDirPos_isRoot + ? raw // no PathSeparator b/c (a) RootDir ("C:\"); (b) relative ("fil.txt") + : String_.DelBgn(raw, ownerDirSprPos + 1); // +1 to skip backslash + } + public String NameOnly(String raw) { + String nameAndExt = NameAndExt(raw); + if (IsDir(raw)) { + String rootName = XtoRootName(raw, String_.Len(raw)); // C:\ -> C; / -> root + return rootName == null + ? String_.DelEnd(nameAndExt, IoUrlInfo_base.DirSprLen) + : rootName; + } + int pos = String_.FindBwd(nameAndExt, IoUrlInfo_base.ExtSeparator); + return pos == String_.Find_none + ? nameAndExt // Ext not found; return entire NameAndExt + : String_.MidByLen(nameAndExt, 0, pos); + } + public String Ext(String raw) { // if Dir, return DirSpr; if Fil, return . as first char; ex: .txt; .png + if (IsDir(raw)) return this.DirSpr(); + String nameAndExt = NameAndExt(raw); + int pos = String_.FindBwd(nameAndExt, IoUrlInfo_base.ExtSeparator); + return pos == String_.Find_none ? "" : String_.DelBgn(nameAndExt, pos); + } + int OwnerDirPos(String raw, int rawLen) { + if (rawLen == 0) return OwnerDirPos_isNull; + else if (XtoRootName(raw, rawLen) != null) return OwnerDirPos_isRoot; + else {// NullUrls and RootUrls have no owners + int posAdj = IsDir(raw) ? IoUrlInfo_base.DirSprLen : 0; // Dir ends with DirSpr, adjust lastIndex by DirSprLen + return String_.FindBwd(raw, this.DirSpr(), rawLen - 1 - posAdj); // -1 to adjust for LastIdx + } + } + static final int + OwnerDirPos_hasNoOwner = -1 // List_adp_.Not_found + , OwnerDirPos_isNull = -2 + , OwnerDirPos_isRoot = -3; +} +class IoUrlInfo_wnt extends IoUrlInfo_base { + @Override public String Key() {return "wnt";} + @Override public String EngineKey() {return IoEngine_.SysKey;} + @Override public String DirSpr() {return Op_sys.Wnt.Fsys_dir_spr_str();} + @Override public byte DirSpr_byte() {return Byte_ascii.Backslash;} + @Override public boolean CaseSensitive() {return Op_sys.Wnt.Fsys_case_match();} + @Override public boolean Match(String raw) {return String_.Len(raw) > 1 && String_.CharAt(raw, 1) == ':';} // 2nd char is :; assumes 1 letter drives + @Override public String XtoRootName(String raw, int rawLen) { + return rawLen == 3 && String_.CharAt(raw, 1) == ':' // only allow single letter drives; ex: C:\; note, CharAt(raw, 1) to match Match + ? Char_.To_str(String_.CharAt(raw, 0)) + : null; + } + public static final IoUrlInfo_wnt Instance = new IoUrlInfo_wnt(); IoUrlInfo_wnt() {} +} +class IoUrlInfo_lnx extends IoUrlInfo_base { + @Override public String Key() {return "lnx";} + @Override public String EngineKey() {return IoEngine_.SysKey;} + @Override public String DirSpr() {return DirSprStr;} static final String DirSprStr = Op_sys.Lnx.Fsys_dir_spr_str(); + @Override public byte DirSpr_byte() {return Byte_ascii.Slash;} + @Override public boolean CaseSensitive() {return Op_sys.Lnx.Fsys_case_match();} + @Override public boolean Match(String raw) {return String_.Has_at_bgn(raw, DirSprStr);} // anything that starts with / + @Override public String XtoRootName(String raw, int rawLen) { + return rawLen == 1 && String_.Eq(raw, DirSprStr) + ? "root" + : null; + } + @Override public String OwnerRoot(String raw) {return DirSprStr;} // drive is always / + @Override public String Xto_api(String raw) { + return String_.Eq(raw, DirSprStr) // is root + ? DirSprStr + : super.Xto_api(raw); // NOTE: super.Xto_api will strip off last / + } + public static final IoUrlInfo_lnx Instance = new IoUrlInfo_lnx(); IoUrlInfo_lnx() {} +} +class IoUrlInfo_rel extends IoUrlInfo_base { + @Override public String Key() {return "rel";} + @Override public String EngineKey() {return IoEngine_.SysKey;} + @Override public String DirSpr() {return info.DirSpr();} + @Override public byte DirSpr_byte() {return info.DirSpr_byte();} + @Override public boolean CaseSensitive() {return info.CaseSensitive();} + @Override public String XtoRootName(String raw, int rawLen) {return info.XtoRootName(raw, rawLen);} + @Override public boolean Match(String raw) {return true;} // relPath is always lastResort; return true + IoUrlInfo info; + public static IoUrlInfo_rel new_(IoUrlInfo info) { + IoUrlInfo_rel rv = new IoUrlInfo_rel(); + rv.info = info; + return rv; + } IoUrlInfo_rel() {} +} +class IoUrlInfo_mem extends IoUrlInfo_base { + @Override public String Key() {return key;} private String key; + @Override public String EngineKey() {return engineKey;} private String engineKey; + @Override public String DirSpr() {return "/";} + @Override public byte DirSpr_byte() {return Byte_ascii.Slash;} + @Override public boolean CaseSensitive() {return false;} + @Override public String XtoRootName(String raw, int rawLen) { + return String_.Eq(raw, key) ? String_.DelEnd(key, 1) : null; + } + @Override public boolean Match(String raw) {return String_.Has_at_bgn(raw, key);} + public static IoUrlInfo_mem new_(String key, String engineKey) { + IoUrlInfo_mem rv = new IoUrlInfo_mem(); + rv.key = key; rv.engineKey = engineKey; + return rv; + } IoUrlInfo_mem() {} +} +class IoUrlInfo_alias extends IoUrlInfo_base { + @Override public String Key() {return srcDir;} + @Override public String EngineKey() {return engineKey;} private String engineKey; + @Override public String DirSpr() {return srcDirSpr;} + @Override public byte DirSpr_byte() {return srcDirSpr_byte;} private byte srcDirSpr_byte; + @Override public boolean CaseSensitive() {return false;} + @Override public String XtoRootName(String raw, int rawLen) { + return String_.Eq(raw, srcRootDir) ? srcRootName : null; + } + @Override public boolean Match(String raw) {return String_.Has_at_bgn(raw, srcDir);} + @Override public String Xto_api(String raw) { + String rv = String_.Replace(raw, srcDir, trgDir); // replace src with trg + if (!String_.Eq(srcDirSpr, trgDirSpr)) rv = String_.Replace(rv, srcDirSpr, trgDirSpr); // replace dirSprs + return IsDir(raw) + ? String_.DelEnd(rv, IoUrlInfo_base.DirSprLen) // remove trailingSeparator, else Directory.Delete will not work properly + : rv; + } + void SrcDir_set(String v) { + srcDir = v; + boolean lnx = DirSpr_lnx(v); + if (srcDirSpr == null) { + if (lnx) { + srcDirSpr = Op_sys.Lnx.Fsys_dir_spr_str(); + srcDirSpr_byte = Op_sys.Lnx.Fsys_dir_spr_byte(); + } + else { + srcDirSpr = Op_sys.Wnt.Fsys_dir_spr_str(); + srcDirSpr_byte = Op_sys.Wnt.Fsys_dir_spr_byte(); + } + } + if (srcRootName == null) srcRootName = lnx ? "root" : String_.Mid(srcDir, 0, String_.FindFwd(srcDir, ":")); + if (srcRootDir == null) srcRootDir = lnx ? "/" : srcDir; + } + void TrgDir_set(String v) { + trgDir = v; + boolean lnx = DirSpr_lnx(v); + if (trgDirSpr == null) trgDirSpr = lnx ? Op_sys.Lnx.Fsys_dir_spr_str() : Op_sys.Wnt.Fsys_dir_spr_str(); + } + boolean DirSpr_lnx(String s) {return String_.Has(s, Op_sys.Lnx.Fsys_dir_spr_str());} + void EngineKey_set(String v) {engineKey = v;} + String srcDir, trgDir, srcDirSpr, trgDirSpr, srcRootDir, srcRootName; + public static IoUrlInfo_alias new_(String srcDir, String trgDir, String engineKey) { + IoUrlInfo_alias rv = new IoUrlInfo_alias(); + rv.SrcDir_set(srcDir); + rv.TrgDir_set(trgDir); + rv.EngineKey_set(engineKey); + return rv; + } + public static final IoUrlInfo_alias KEYS = new IoUrlInfo_alias(); + public final String + Data_EngineKey = "engineKey" + , Data_SrcDir = "srcDir" + , Data_TrgDir = "trgDir" + ; +} diff --git a/100_core/src/gplx/core/ios/IoUrlInfoRegy.java b/100_core/src/gplx/core/ios/IoUrlInfoRegy.java index a27517de8..cddf6bb25 100644 --- a/100_core/src/gplx/core/ios/IoUrlInfoRegy.java +++ b/100_core/src/gplx/core/ios/IoUrlInfoRegy.java @@ -13,3 +13,40 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.envs.*; +import gplx.langs.gfs.*; +public class IoUrlInfoRegy implements Gfo_invk { + public void Reg(IoUrlInfo info) {hash.Add_if_dupe_use_nth(info.Key(), info);} + public IoUrlInfo Match(String raw) { + if (String_.Len(raw) == 0) return IoUrlInfo_.Nil; + for (int i = hash.Count(); i > 0; i--) { + IoUrlInfo info = (IoUrlInfo)hash.Get_at(i - 1); + if (info.Match(raw)) return info; + } + throw Err_.new_wo_type("could not match ioPathInfo", "raw", raw, "count", hash.Count()); + } + public void Reset() { + hash.Clear(); + Reg(IoUrlInfo_rel.new_(Op_sys.Cur().Tid_is_wnt() ? (IoUrlInfo)IoUrlInfo_wnt.Instance : (IoUrlInfo)IoUrlInfo_lnx.Instance)); + Reg(IoUrlInfo_.Mem); + Reg(IoUrlInfo_lnx.Instance); + Reg(IoUrlInfo_wnt.Instance); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_Add)) { + String srcDirStr = m.ReadStr("srcDir"); + String trgDirStr = m.ReadStr("trgDir"); + String engineKey = m.ReadStrOr("engineKey", IoEngine_.SysKey); + if (ctx.Deny()) return this; + IoUrlInfo_alias alias = IoUrlInfo_alias.new_(srcDirStr, trgDirStr, engineKey); + IoUrlInfoRegy.Instance.Reg(alias); + } + return this; + } public static final String Invk_Add = "Add"; + Ordered_hash hash = Ordered_hash_.New(); + public static final IoUrlInfoRegy Instance = new IoUrlInfoRegy(); + IoUrlInfoRegy() { + this.Reset(); + } +} \ No newline at end of file diff --git a/100_core/src/gplx/core/ios/IoUrlInfo_.java b/100_core/src/gplx/core/ios/IoUrlInfo_.java index a27517de8..3eca5ff59 100644 --- a/100_core/src/gplx/core/ios/IoUrlInfo_.java +++ b/100_core/src/gplx/core/ios/IoUrlInfo_.java @@ -13,3 +13,20 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class IoUrlInfo_ { + public static final IoUrlInfo Nil = IoUrlInfo_nil.Instance; + public static final IoUrlInfo Wnt = IoUrlInfo_wnt.Instance; + public static final IoUrlInfo Lnx = IoUrlInfo_lnx.Instance; + public static final IoUrlInfo Mem = IoUrlInfo_mem.new_("mem", IoEngine_.MemKey); + + public static IoUrlInfo mem_(String key, String engineKey) {return IoUrlInfo_mem.new_(key, engineKey);} + public static IoUrlInfo alias_(String srcRoot, String trgRoot, String engineKey) {return IoUrlInfo_alias.new_(srcRoot, trgRoot, engineKey);} +} +/* +wnt C:\dir\fil.txt +wce \dir\fil.txt +lnx /dir/fil.txt +mem mem/dir/fil.txt +alias app:\dir\fil.txt +*/ \ No newline at end of file diff --git a/100_core/src/gplx/core/ios/IoUrlTypeRegy.java b/100_core/src/gplx/core/ios/IoUrlTypeRegy.java index a27517de8..13c36aa2b 100644 --- a/100_core/src/gplx/core/ios/IoUrlTypeRegy.java +++ b/100_core/src/gplx/core/ios/IoUrlTypeRegy.java @@ -13,3 +13,62 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.strings.*; import gplx.langs.gfs.*; +public class IoUrlTypeRegy implements Gfo_invk { + public String[] FetchAryOr(String key, String... or) { + IoUrlTypeGrp itm = (IoUrlTypeGrp)hash.Get_by(key); + return itm == null ? or : itm.AsAry(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_Get)) { + String key = m.ReadStr(k); + if (ctx.Deny()) return this; + IoUrlTypeGrp itm = (IoUrlTypeGrp)hash.Get_by(key); + if (itm == null) { + itm = new IoUrlTypeGrp(key); + hash.Add(key, itm); + } + return itm; + } + else return Gfo_invk_.Rv_unhandled; +// return this; + } public static final String Invk_Get = "Get"; + Ordered_hash hash = Ordered_hash_.New(); + public static final IoUrlTypeRegy Instance = new IoUrlTypeRegy(); IoUrlTypeRegy() {} +} +class IoUrlTypeGrp implements Gfo_invk { + public String[] AsAry() { + String[] rv = new String[list.Count()]; + for (int i = 0; i < list.Count(); i++) + rv[i] = (String)list.Get_at(i); + return rv; + } + Ordered_hash list = Ordered_hash_.New(); + public IoUrlTypeGrp(String key) {this.key = key;} private String key; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_AddMany)) { + if (ctx.Deny()) return this; + for (int i = 0; i < m.Args_count(); i++) { + String s = m.ReadStr("v"); + if (list.Has(s)) { + ctx.Write_warn(UsrMsg.new_("itm already has filter").Add("key", key).Add("filter", s).To_str()); + list.Del(s); + } + list.Add(s, s); + } + } + else if (ctx.Match(k, Invk_Print)) { + if (ctx.Deny()) return this; + String_bldr sb = String_bldr_.new_(); + sb.Add(key).Add("{"); + for (int i = 0; i < list.Count(); i++) + sb.Add_spr_unless_first((String)list.Get_at(i), " ", i); + sb.Add("}"); + return sb.To_str(); + } + else if (ctx.Match(k, Invk_Clear)) {if (ctx.Deny()) return this; list.Clear();} + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String Invk_AddMany = "Add_many", Invk_Clear = "Clear", Invk_Print = "Print"; +} diff --git a/100_core/src/gplx/core/ios/IoZipWkr.java b/100_core/src/gplx/core/ios/IoZipWkr.java index a27517de8..13a62b3e0 100644 --- a/100_core/src/gplx/core/ios/IoZipWkr.java +++ b/100_core/src/gplx/core/ios/IoZipWkr.java @@ -13,3 +13,27 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.envs.*; import gplx.core.stores.*; /*GfoNdeRdr_*/ +import gplx.core.gfo_regys.*; +public class IoZipWkr { + public Io_url ExeUrl() {return (Io_url)GfoRegy.Instance.FetchValOrFail(Regy_ExeUrl);} + public String ExeArgFmt() {return (String)GfoRegy.Instance.FetchValOrFail(Regy_ExeArgFmt);} + public void Expand(Io_url srcUrl, Io_url trgUrl) { + String exeArgs = Expand_genCmdString(srcUrl, trgUrl); + process.Exe_url_(this.ExeUrl()).Args_str_(exeArgs); + process.Run_wait(); + } + @gplx.Internal protected String Expand_genCmdString(Io_url srcUrl, Io_url trgUrl) { + return String_.Format(this.ExeArgFmt(), srcUrl.Xto_api(), trgUrl.Xto_api()); + } + Process_adp process = new Process_adp(); + public static IoZipWkr regy_() {return new IoZipWkr();} + static final String Regy_ExeUrl = "gplx.core.ios.IoZipWkr.ExeUrl", Regy_ExeArgFmt = "gplx.core.ios.IoZipWkr.ExeArgFmt"; + public static IoZipWkr new_(Io_url exeUrl, String expandArgs) { + GfoRegy.Instance.RegObj(Regy_ExeUrl, exeUrl); + GfoRegy.Instance.RegObj(Regy_ExeArgFmt, expandArgs); + IoZipWkr rv = new IoZipWkr(); + return rv; + } +} diff --git a/100_core/src/gplx/core/ios/IoZipWkr_tst.java b/100_core/src/gplx/core/ios/IoZipWkr_tst.java index a27517de8..a2269374c 100644 --- a/100_core/src/gplx/core/ios/IoZipWkr_tst.java +++ b/100_core/src/gplx/core/ios/IoZipWkr_tst.java @@ -13,3 +13,14 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoZipWkr_tst { + @Test public void Basic() { + wkr = IoZipWkr.new_(Io_url_.Empty, "e \"{0}\" -o\"{1}\" -y"); + tst_Expand_genCmdString(Io_url_.wnt_fil_("C:\\fil1.zip"), Io_url_.wnt_dir_("D:\\out\\"), "e \"C:\\fil1.zip\" -o\"D:\\out\" -y"); // NOTE: not "D:\out\" because .Xto_api + } IoZipWkr wkr; + void tst_Expand_genCmdString(Io_url srcUrl, Io_url trgUrl, String expd) { + Tfds.Eq(expd, wkr.Expand_genCmdString(srcUrl, trgUrl)); + } +} diff --git a/100_core/src/gplx/core/ios/Io_download_fmt.java b/100_core/src/gplx/core/ios/Io_download_fmt.java index a27517de8..72c700ba7 100644 --- a/100_core/src/gplx/core/ios/Io_download_fmt.java +++ b/100_core/src/gplx/core/ios/Io_download_fmt.java @@ -13,3 +13,80 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.brys.args.*; import gplx.core.brys.fmtrs.*; import gplx.core.envs.*; +public class Io_download_fmt { + private final Io_size_fmtr_arg size_fmtr_arg = new Io_size_fmtr_arg(), rate_fmtr_arg = new Io_size_fmtr_arg().Suffix_(Bry_.new_a7("ps")); + private final Bfr_arg__time prog_left_fmtr_arg = new Bfr_arg__time(); private final Bfr_arg__decimal_int prog_pct_fmtr_arg = new Bfr_arg__decimal_int().Places_(2); + private long time_checkpoint_interval = 250; + private long time_checkpoint = 0; + private long time_prv = 0; + public Io_download_fmt() { + this.src_name = prog_msg_hdr = ""; // NOTE: set to "" else prog_mgr will fail with null ref + } + private final Bry_bfr prog_bfr = Bry_bfr_.New(); Bry_fmtr prog_fmtr = Bry_fmtr.new_().Fail_when_invalid_escapes_(false); // NOTE: prog_fmtr can be passed file_names with ~ which are not easy to escape; DATE:2013-02-19 + public long Time_bgn() {return time_bgn;} private long time_bgn; + public long Time_now() {return time_now;} private long time_now; + public long Time_dif() {return time_dif;} private long time_dif; + public long Time_end() {return time_end;} private long time_end; + public String Src_url() {return src_url;} private String src_url; + public String Src_name() {return src_name;} private String src_name; + public long Src_len() {return src_len;} private long src_len; + public long Prog_done() {return prog_done;} private long prog_done; + public long Prog_rate() {return prog_rate;} private long prog_rate; + public long Prog_left() {return prog_left;} private long prog_left; + public long Prog_pct() {return prog_pct;} private long prog_pct; + public String Prog_msg_hdr() {return prog_msg_hdr;} private String prog_msg_hdr; + public int Prog_num_units() {return prog_num_units;} private int prog_num_units = Io_mgr.Len_kb; + public String Prog_num_fmt() {return prog_num_fmt;} private String prog_num_fmt = "#,##0"; + public String Prog_msg() {return prog_msg;} private String prog_msg; + public Gfo_usr_dlg Usr_dlg() {return usr_dlg;} private Gfo_usr_dlg usr_dlg; + public void Ctor(Gfo_usr_dlg usr_dlg) { + this.usr_dlg = usr_dlg; + } + public void Download_init(String src_url, String prog_msg_hdr) { + this.src_url = src_url; + this.src_name = String_.Extract_after_bwd(src_url, "/"); + this.prog_msg_hdr = prog_msg_hdr; + } + public void Bgn(long src_len) { + this.src_len = src_len; + prog_fmtr.Fmt_(prog_msg_hdr).Keys_("src_name", "src_len").Bld_bfr_many_and_set_fmt(src_name, size_fmtr_arg.Val_(src_len)); + prog_fmtr.Keys_("prog_done", "prog_pct", "prog_rate", "prog_left"); + prog_done = 0; + prog_pct = 0; + prog_rate = 0; + prog_left = 0; + time_bgn = time_prv = System_.Ticks(); + time_checkpoint = 0; + } + public void Prog(int prog_read) { + time_now = System_.Ticks(); + time_dif = time_now - time_bgn; if (time_dif == 0) time_dif = 1; // avoid div by zero error below + prog_done += prog_read; + time_checkpoint += time_now - time_prv; + time_prv = time_now; + if ((time_checkpoint < time_checkpoint_interval)) return; // NOTE: using time_checkpoint instead of size_checkpoint b/c WMF dump servers transfer in spurts (sends 5 packets, and then waits); + time_checkpoint = 0; + prog_rate = (prog_done * 1000) / (time_dif); + prog_pct = (prog_done * 10000) / src_len; // 100 00 to get 2 decimal places; EX: .1234 -> 1234 -> 12.34% + prog_left = (1000 * (src_len - prog_done)) / prog_rate; + prog_fmtr.Bld_bfr_many(prog_bfr + , size_fmtr_arg.Val_(prog_done) + , prog_pct_fmtr_arg.Val_((int)prog_pct) + , rate_fmtr_arg.Val_(prog_rate) + , prog_left_fmtr_arg.Seconds_(prog_left / 1000) + ); + prog_msg = prog_bfr.To_str_and_clear(); + if (usr_dlg != null) + usr_dlg.Prog_none("", "prog", prog_msg); + } + public void Term() { + time_end = System_.Ticks(); +// prog_rate = (prog_done * 1000) / (time_dif); +// prog_pct = (prog_done * 10000) / src_len; // 100 00 to get 2 decimal places; EX: .1234 -> 1234 -> 12.34% +// prog_left = (1000 * (src_len - prog_done)) / prog_rate; +// if (usr_dlg != null) usr_dlg.Prog_none(GRP_KEY, "clear", ""); + } + public static final Io_download_fmt Null = null; +} diff --git a/100_core/src/gplx/core/ios/Io_download_fmt_tst.java b/100_core/src/gplx/core/ios/Io_download_fmt_tst.java index a27517de8..93659a9d4 100644 --- a/100_core/src/gplx/core/ios/Io_download_fmt_tst.java +++ b/100_core/src/gplx/core/ios/Io_download_fmt_tst.java @@ -13,3 +13,59 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.envs.*; +public class Io_download_fmt_tst { + Io_download_fmt_fxt fxt = new Io_download_fmt_fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Fmt() { + fxt.Clear().Ini("downloading ~{src_name}: ~{prog_left} left (@ ~{prog_rate}); ~{prog_done} of ~{src_len} (~{prog_pct}%)", "http://a.org/b.png", Io_mgr.Len_kb * 10); + fxt.Now_add_f(1000).Prog_done_(1 * Io_mgr.Len_kb).Prog_pct_(1 * 1000).Prog_rate_(Io_mgr.Len_kb).Prog_left_(9 * 1000) + .Prog_msg_("downloading b.png: 09s left (@ 1.000 KBps); 1.000 KB of 10.000 KB (10.00%)") + .Download_(Io_mgr.Len_kb); + fxt.Now_add_f(1000).Prog_done_(2 * Io_mgr.Len_kb).Prog_pct_(2 * 1000).Prog_rate_(Io_mgr.Len_kb).Prog_left_(8 * 1000) + .Prog_msg_("downloading b.png: 08s left (@ 1.000 KBps); 2.000 KB of 10.000 KB (20.00%)") + .Download_(Io_mgr.Len_kb) + ; + fxt.Now_add_f(2000).Prog_done_(3 * Io_mgr.Len_kb).Prog_pct_(3 * 1000).Prog_rate_(768).Prog_left_(9333) + .Prog_msg_("downloading b.png: 09s left (@ 768.000 Bps); 3.000 KB of 10.000 KB (30.00%)") + .Download_(Io_mgr.Len_kb) + ; + } + @Test public void Tilde() { + fxt.Clear().Ini("a~b", "http://a.org/b.png", Io_mgr.Len_kb * 10); + } +} +class Io_download_fmt_fxt { + public Io_download_fmt_fxt Clear() { + if (fmt == null) { + fmt = new Io_download_fmt(); + } + System_.Ticks__test_set(0); + prog_done = prog_rate = prog_pct = prog_left = -1; + prog_msg = null; + return this; + } Io_download_fmt fmt; + public Io_download_fmt_fxt Now_add_f(int v) {System_.Ticks__test_add(v); return this;} + public Io_download_fmt_fxt Prog_done_(int v) {prog_done = v; return this;} long prog_done = -1; + public Io_download_fmt_fxt Prog_pct_ (int v) {prog_pct = v; return this;} long prog_pct = -1; + public Io_download_fmt_fxt Prog_rate_(int v) {prog_rate = v; return this;} long prog_rate = -1; + public Io_download_fmt_fxt Prog_left_(int v) {prog_left = v; return this;} long prog_left = -1; + public Io_download_fmt_fxt Prog_msg_(String v) { + prog_msg = v; return this; + } String prog_msg; + public Io_download_fmt_fxt Download_(int v) { + fmt.Prog(v); + if (prog_done != -1) Tfds.Eq(prog_done, fmt.Prog_done(), "prog_done"); + if (prog_pct != -1) Tfds.Eq(prog_pct , fmt.Prog_pct(), "prog_pct"); + if (prog_rate != -1) Tfds.Eq(prog_rate, fmt.Prog_rate(), "prog_rate"); + if (prog_left != -1) Tfds.Eq(prog_left, fmt.Prog_left(), "prog_left"); + if (prog_msg != null) Tfds.Eq(prog_msg, fmt.Prog_msg(), "prog_msg"); + return this; + } + public Io_download_fmt_fxt Ini(String prog_msg_hdr, String src_url, int src_len) { + fmt.Download_init(src_url, prog_msg_hdr); + fmt.Bgn(src_len); + return this; + } +} diff --git a/100_core/src/gplx/core/ios/Io_fil.java b/100_core/src/gplx/core/ios/Io_fil.java index a27517de8..3c5406b84 100644 --- a/100_core/src/gplx/core/ios/Io_fil.java +++ b/100_core/src/gplx/core/ios/Io_fil.java @@ -13,3 +13,24 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class Io_fil implements gplx.CompareAble { + public Io_fil(Io_url url, String data) {this.url = url; this.data = data;} + public Io_url Url() {return url;} public Io_fil Url_(Io_url v) {url = v; return this;} Io_url url; + public String Data() {return data;} public Io_fil Data_(String v) {data = v; return this;} private String data; + public int compareTo(Object obj) { + return gplx.CompareAble_.Compare(url.Raw(), ((Io_fil)obj).Url().Raw()); + } + public static Io_fil[] new_ary_(Io_url[] url_ary) { + int url_ary_len = url_ary.length; + Io_fil[] rv = new Io_fil[url_ary_len]; + for (int i = 0; i < url_ary_len; i++) { + Io_url url = url_ary[i]; + String data = Io_mgr.Instance.LoadFilStr(url); + Io_fil fil = new Io_fil(url, data); + rv[i] = fil; + } + return rv; + } + public static final Io_fil[] Ary_empty = new Io_fil[0]; +} diff --git a/100_core/src/gplx/core/ios/Io_fil_mkr.java b/100_core/src/gplx/core/ios/Io_fil_mkr.java index a27517de8..52b2e7ed8 100644 --- a/100_core/src/gplx/core/ios/Io_fil_mkr.java +++ b/100_core/src/gplx/core/ios/Io_fil_mkr.java @@ -13,3 +13,10 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class Io_fil_mkr { + private final List_adp list = List_adp_.New(); + public Io_fil_mkr Add(String url, String data) {return Add(Io_url_.mem_fil_(url), data);} + public Io_fil_mkr Add(Io_url url, String data) {list.Add(new Io_fil(url, data)); return this;} + public Io_fil[] To_ary() {return (Io_fil[])list.To_ary(Io_fil.class);} +} diff --git a/100_core/src/gplx/core/ios/Io_size_.java b/100_core/src/gplx/core/ios/Io_size_.java index a27517de8..34a7d33dd 100644 --- a/100_core/src/gplx/core/ios/Io_size_.java +++ b/100_core/src/gplx/core/ios/Io_size_.java @@ -13,3 +13,158 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.brys.*; +public class Io_size_ { + public static String To_str(long val) {return To_str(val, "#,##0.000");} + public static String To_str(long val, String val_fmt) { + long cur = val; int pow = 0; + while (cur >= 1024) { + cur /= 1024; + pow++; + } + long div = (long)Math_.Pow((long)1024, (long)pow); + Decimal_adp valDecimal = Decimal_adp_.divide_(val, div); + String[] unit = Io_size_.Units[pow]; + return valDecimal.To_str(val_fmt) + " " + String_.PadBgn(unit[0], 2, " "); + } + public static String To_str(long val, int exp_1024, String val_fmt, String unit_pad, boolean round_0_to_1) { + long exp_val = (long)Math_.Pow(1024, exp_1024); + Decimal_adp val_as_decimal = Decimal_adp_.divide_(val, exp_val); + if (round_0_to_1 && val_as_decimal.Comp_lt(1)) val_as_decimal = Decimal_adp_.One; + String[] unit = Io_size_.Units[exp_1024]; + return val_as_decimal.To_str(val_fmt) + " " + String_.PadBgn(unit[0], 2, unit_pad); + } + public static String To_str_new(Bry_bfr bfr, long val, int decimal_places) {To_bfr_new(bfr, val, decimal_places); return bfr.To_str_and_clear();} + public static void To_bfr_new(Bry_bfr bfr, long val, int decimal_places) { + // init + int unit_idx = 0; + int mult = 1024; + long cur_val = val; + long cur_exp = 1; + long nxt_exp = mult; + + // get 1024 mult; EX: 1549 -> 1024 + for (unit_idx = 0; unit_idx < 6; ++unit_idx) { + if (cur_val < nxt_exp) break; + cur_exp = nxt_exp; + nxt_exp *= mult; + } + + // calc integer / decimal values + int int_val = (int)(val / cur_exp); + int dec_val = (int)(val % cur_exp); + if (decimal_places == 0) { // if 0 decimal places, round up + if (dec_val >= .5) ++int_val; + dec_val = 0; + } + else {// else, calculate decimal value as integer; EX: 549 -> .512 -> 512 + long dec_factor = 0; + switch (decimal_places) { + case 1: dec_factor = 10; break; + case 2: dec_factor = 100; break; + default: + case 3: dec_factor = 1000; break; + } + dec_val = (int)((dec_val * dec_factor) / cur_exp); + } + + // calc unit_str + String unit_str = ""; + switch (unit_idx) { + case 0: unit_str = " B"; break; + case 1: unit_str = " KB"; break; + case 2: unit_str = " MB"; break; + case 3: unit_str = " GB"; break; + case 4: unit_str = " PB"; break; + case 5: + default: unit_str = " EB"; break; + } + + // build String + bfr.Add_long_variable(int_val); + if (decimal_places > 0 && unit_idx != 0) { + bfr.Add_byte_dot(); + bfr.Add_long_variable(dec_val); + } + bfr.Add_str_a7(unit_str); + } + public static long parse_or(String raw, long or) { + if (raw == null || raw == String_.Empty) return or; + String[] terms = String_.Split(raw, " "); + int termsLen = Array_.Len(terms); if (termsLen > 2) return or; + + Decimal_adp val = null; + try {val = Decimal_adp_.parse(terms[0]);} catch (Exception exc) {Err_.Noop(exc); return or;} + + int unitPow = 0; + if (termsLen > 1) { + String unitStr = String_.Upper(terms[1]); + unitPow = parse_unitPow_(unitStr); if (unitPow == -1) return or; + } + int curPow = unitPow; + while (curPow > 0) { + val = val.Multiply(1024); + curPow--; + } + // DELETED:do not check for fractional bytes; EX: 10.7 GB DATE:2015-01-06 + // Decimal_adp comp = val.Op_truncate_decimal(); + // if (!val.Eq(comp)) return or; + return val.To_long(); + } + private static int parse_unitPow_(String unitStr) { + int unitLen = Array_.Len(Units); + int unitPow = -1; + for (int i = 0; i < unitLen; i++) { + if (String_.Eq(unitStr, String_.Upper(Units[i][0]))) return i; + if (String_.Eq(unitStr, String_.Upper(Units[i][1]))) return i; + } + return unitPow; + } + private static final String[][] Units = new String[][] + { String_.Ary("B", "BYTE") + , String_.Ary("KB", "KILOBYTE") + , String_.Ary("MB", "MEGABYTE") + , String_.Ary("GB", "GIGABYTE") + , String_.Ary("TB", "TERABYTE") + , String_.Ary("PB", "PETABYTE") + , String_.Ary("EB", "EXABYTE") + }; + public static final byte[][] Units_bry = new byte[][] + { Bry_.new_a7("B") + , Bry_.new_a7("KB") + , Bry_.new_a7("MB") + , Bry_.new_a7("GB") + , Bry_.new_a7("TB") + , Bry_.new_a7("PB") + , Bry_.new_a7("EB") + }; + public static int Load_int_(GfoMsg m) {return (int)Load_long_(m);} + public static long Load_long_(GfoMsg m) { + String v = m.ReadStr("v"); + long rv = parse_or(v, Long_.Min_value); if (rv == Long_.Min_value) throw Err_.new_wo_type("invalid val", "val", v); + return rv; + } + public static String To_str_mb(long v) {return Long_.To_str(v / Io_mgr.Len_mb_long);} + public static long To_long_by_int_mb(int v) {return (long)v * Io_mgr.Len_mb_long;} + public static long To_long_by_msg_mb(GfoMsg m, long cur) { + long val = m.ReadLongOr("v", Int_.Min_value); + return val == Int_.Min_value ? cur : (val * Io_mgr.Len_mb_long); + } +} +class Io_size_fmtr_arg implements Bfr_arg { + public long Val() {return val;} public Io_size_fmtr_arg Val_(long v) {val = v; return this;} long val; + public byte[] Suffix() {return suffix;} public Io_size_fmtr_arg Suffix_(byte[] v) {suffix = v; return this;} private byte[] suffix; + public void Bfr_arg__add(Bry_bfr bfr) { + long cur = val; int pow = 0; + while (cur >= 1024) { + cur /= 1024; + pow++; + } + long div = (long)Math_.Pow((long)1024, (long)pow); + Decimal_adp val_decimal = Decimal_adp_.divide_(val, div); + bfr.Add_str_a7(val_decimal.To_str("#,###.000")).Add_byte(Byte_ascii.Space).Add(gplx.core.ios.Io_size_.Units_bry[pow]); + if (suffix != null) + bfr.Add(suffix); + } +} diff --git a/100_core/src/gplx/core/ios/Io_size__tst.java b/100_core/src/gplx/core/ios/Io_size__tst.java index a27517de8..3c1aa7779 100644 --- a/100_core/src/gplx/core/ios/Io_size__tst.java +++ b/100_core/src/gplx/core/ios/Io_size__tst.java @@ -13,3 +13,71 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Io_size__tst { + private Io_size__fxt fxt = new Io_size__fxt(); + @Test public void XtoLong() { + fxt.Test_XtoLong("1", 1); + fxt.Test_XtoLong("1 KB", 1024); + fxt.Test_XtoLong("1 MB", 1024 * 1024); + fxt.Test_XtoLong("1 GB", 1024 * 1024 * 1024); + fxt.Test_XtoLong("12 kb", 12 * 1024); + fxt.Test_XtoLong("1.5 kb", 1024 + 512); // 1536 + fxt.Test_XtoLong("1.5 mb", (long)(1024 * 1024 * 1.5)); + fxt.Test_XtoLong("-1", -1); // NOTE: negative bytes allowed + + fxt.Test_XtoLongFail("1 kilobite"); + fxt.Test_XtoLongFail("1 BB"); + // fxt.Test_XtoLongFail("1.1"); // DELETED:do not check for fractional bytes; EX: 10.7 GB DATE:2015-01-06 + // fxt.Test_XtoLongFail("1.51 kb"); + } + @Test public void To_str() { + fxt.Test_XtoStr(1, "1.000 B"); + fxt.Test_XtoStr(1024, "1.000 KB"); + fxt.Test_XtoStr(1536, "1.500 KB"); + fxt.Test_XtoStr(1024 * 1024, "1.000 MB"); + fxt.Test_XtoStr(1016, "1,016.000 B"); // NOTE: 1016 is not 1.016 KB + } + @Test public void Xto_str_full() { + fxt.Test_Xto_str( 500, 1, "#,###", " ", Bool_.Y, "1 KB"); + fxt.Test_Xto_str( 1000, 1, "#,###", " ", Bool_.Y, "1 KB"); + fxt.Test_Xto_str( 2000, 1, "#,###", " ", Bool_.Y, "2 KB"); + fxt.Test_Xto_str( 1234567, 1, "#,###", " ", Bool_.Y, "1,206 KB"); + fxt.Test_Xto_str(1234567890, 1, "#,###", " ", Bool_.Y, "1,205,633 KB"); + } + @Test public void EqualsTest() { + fxt.Test_Equals("1", "1"); + fxt.Test_Equals("1 kb", "1 kb"); + fxt.Test_Equals("1024", "1 kb"); + fxt.Test_Equals("1048576", "1 mb"); + fxt.Test_Equals("1024 kb", "1 mb"); + fxt.Test_Equals("1.5 kb", "1536 b"); + } + @Test public void To_str_new___b___0() {fxt.Test__to_str_new( 0, 2, "0 B");} + @Test public void To_str_new___b___1() {fxt.Test__to_str_new( 1, 2, "1 B");} + @Test public void To_str_new__kb___1_501__1() {fxt.Test__to_str_new( 1538, 1, "1.5 KB");} + @Test public void To_str_new__kb___1_501__2() {fxt.Test__to_str_new( 1538, 2, "1.50 KB");} + @Test public void To_str_new__kb___1_501__3() {fxt.Test__to_str_new( 1538, 3, "1.501 KB");} + @Test public void To_str_new__kb___1_512__1() {fxt.Test__to_str_new( 1549, 1, "1.5 KB");} + @Test public void To_str_new__kb___1_512__2() {fxt.Test__to_str_new( 1549, 2, "1.51 KB");} + @Test public void To_str_new__kb___1_512__3() {fxt.Test__to_str_new( 1549, 3, "1.512 KB");} + @Test public void To_str_new__mb___1_512__1() {fxt.Test__to_str_new((1024 * 1024) + 536871, 1, "1.5 MB");} + @Test public void To_str_new__mb___1_512__2() {fxt.Test__to_str_new((1024 * 1024) + 536871, 2, "1.51 MB");} + @Test public void To_str_new__mb___1_512__3() {fxt.Test__to_str_new((1024 * 1024) + 536871, 3, "1.512 MB");} + @Test public void To_str_new__gb___1() {fxt.Test__to_str_new(1819264175, 2, "1.69 GB");} +} +class Io_size__fxt { + public void Test_XtoLong(String raw, long expd) {Tfds.Eq(expd, Io_size_.parse_or(raw, Long_.Min_value));} + public void Test_XtoLongFail(String raw) { + long val = Io_size_.parse_or(raw, Long_.Min_value); + if (val != Long_.Min_value) Tfds.Fail("expd parse failure; raw=" + raw); + } + public void Test_Equals(String lhs, String rhs) {Tfds.Eq(Io_size_.parse_or(lhs, Long_.Min_value), Io_size_.parse_or(rhs, Long_.Min_value));} + public void Test_XtoStr(long val, String expd) {Tfds.Eq(expd, Io_size_.To_str(val));} + public void Test_Xto_str(long val, int exp_1024, String val_fmt, String unit_pad, boolean round_0_to_1, String expd) {Tfds.Eq(expd, Io_size_.To_str(val, exp_1024, val_fmt, unit_pad, round_0_to_1));} + public void Test__to_str_new(long val, int decimal_places, String expd) { + Bry_bfr bfr = Bry_bfr_.New(); + Tfds.Eq_str(expd, Io_size_.To_str_new(bfr, val, decimal_places)); + } +} diff --git a/100_core/src/gplx/core/ios/Io_url_obj_ref.java b/100_core/src/gplx/core/ios/Io_url_obj_ref.java index a27517de8..777c0eda3 100644 --- a/100_core/src/gplx/core/ios/Io_url_obj_ref.java +++ b/100_core/src/gplx/core/ios/Io_url_obj_ref.java @@ -13,3 +13,16 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class Io_url_obj_ref { + public Io_url Val() {return val;} public Io_url_obj_ref Val_(Io_url v) {val = v; return this;} private Io_url val; + public String Val_as_str() {return val.Raw();} + @Override public String toString() {return val.Raw();} + @Override public int hashCode() {return val.hashCode();} + @Override public boolean equals(Object obj) {return String_.Eq(val.Raw(), ((Io_url_obj_ref)obj).val.Raw());} + public static Io_url_obj_ref new_(Io_url val) { + Io_url_obj_ref rv = new Io_url_obj_ref(); + rv.val = val; + return rv; + } Io_url_obj_ref() {} +} diff --git a/100_core/src/gplx/core/ios/drives/Io_drive.java b/100_core/src/gplx/core/ios/drives/Io_drive.java index a27517de8..62e524bf1 100644 --- a/100_core/src/gplx/core/ios/drives/Io_drive.java +++ b/100_core/src/gplx/core/ios/drives/Io_drive.java @@ -13,3 +13,8 @@ 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.core.ios.drives; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +public interface Io_drive { + long Get_space_total(Io_url drive); + long Get_space_free (Io_url drive); +} diff --git a/100_core/src/gplx/core/ios/drives/Io_drive_.java b/100_core/src/gplx/core/ios/drives/Io_drive_.java index a27517de8..4de005dba 100644 --- a/100_core/src/gplx/core/ios/drives/Io_drive_.java +++ b/100_core/src/gplx/core/ios/drives/Io_drive_.java @@ -13,3 +13,7 @@ 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.core.ios.drives; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +public class Io_drive_ { + public static Io_drive Instance = new Io_drive__jre(); +} diff --git a/100_core/src/gplx/core/ios/drives/Io_drive__jre.java b/100_core/src/gplx/core/ios/drives/Io_drive__jre.java index a27517de8..6ba47c2fb 100644 --- a/100_core/src/gplx/core/ios/drives/Io_drive__jre.java +++ b/100_core/src/gplx/core/ios/drives/Io_drive__jre.java @@ -13,3 +13,8 @@ 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.core.ios.drives; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +public class Io_drive__jre implements Io_drive { + public long Get_space_total(Io_url drive) {return new java.io.File(drive.Xto_api()).getTotalSpace();} + public long Get_space_free (Io_url drive) {return new java.io.File(drive.Xto_api()).getFreeSpace();} +} diff --git a/100_core/src/gplx/core/ios/loaders/Io_loader.java b/100_core/src/gplx/core/ios/loaders/Io_loader.java index a27517de8..33be1188c 100644 --- a/100_core/src/gplx/core/ios/loaders/Io_loader.java +++ b/100_core/src/gplx/core/ios/loaders/Io_loader.java @@ -13,3 +13,7 @@ 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.core.ios.loaders; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +public interface Io_loader { + byte[] Load_fil_as_bry(Io_url url); +} diff --git a/100_core/src/gplx/core/ios/streams/IoStream.java b/100_core/src/gplx/core/ios/streams/IoStream.java index a27517de8..2843fd196 100644 --- a/100_core/src/gplx/core/ios/streams/IoStream.java +++ b/100_core/src/gplx/core/ios/streams/IoStream.java @@ -13,3 +13,19 @@ 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.core.ios.streams; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +public interface IoStream extends Rls_able { + Object UnderRdr(); + Io_url Url(); + long Pos(); + long Len(); + + int ReadAry(byte[] array); + int Read(byte[] array, int offset, int count); + long Seek(long pos); + void WriteAry(byte[] ary); + void Write(byte[] array, int offset, int count); + void Transfer(IoStream trg, int bufferLength); + void Flush(); + void Write_and_flush(byte[] bry, int bgn, int end); +} \ No newline at end of file diff --git a/100_core/src/gplx/core/ios/streams/IoStream_.java b/100_core/src/gplx/core/ios/streams/IoStream_.java index a27517de8..018d51918 100644 --- a/100_core/src/gplx/core/ios/streams/IoStream_.java +++ b/100_core/src/gplx/core/ios/streams/IoStream_.java @@ -13,3 +13,50 @@ 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.core.ios.streams; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; +public class IoStream_ { + public static final IoStream Null = new IoStream_null(); + public static IoStream mem_txt_(Io_url url, String v) {return IoStream_mem.rdr_txt_(url, v);} + public static IoStream ary_(byte[] v) {return IoStream_mem.rdr_ary_(Io_url_.Empty, v);} + public static final byte Mode_rdr = 0, Mode_wtr_create = 1, Mode_wtr_append = 2, Mode_wtr_update = 3; + public static IoStream stream_rdr_() {return new IoStream_stream_rdr();} + public static IoStream stream_input_(Io_url url) {return new IoStream_stream_rdr().UnderRdr_(input_stream_(url));} + public static Object input_stream_(Io_url url) { + try { + return new java.io.FileInputStream(url.Raw()); + } catch (Exception e) {throw Err_.new_wo_type("file not found", "url", url.Raw());} + } +} +class IoStream_null implements IoStream { + public Object UnderRdr() {return null;} + public Io_url Url() {return Io_url_.Empty;} + public long Pos() {return -1;} + public long Len() {return -1;} + public int ReadAry(byte[] array) {return -1;} + public int Read(byte[] array, int offset, int count) {return -1;} + public long Seek(long pos) {return -1;} + public void WriteAry(byte[] ary) {} + public void Write(byte[] array, int offset, int count) {} + public void Transfer(IoStream trg, int bufferLength) {} + public void Flush() {} + public void Write_and_flush(byte[] bry, int bgn, int end) {} + public void Rls() {} +} +/* +NOTE_1:stream mode +my understanding of mode +rw: read/write async? +rws: read/write sync; write content + metadata changes +rwd: read/write sync; write content +*/ +//#} \ No newline at end of file diff --git a/100_core/src/gplx/core/ios/streams/IoStream_base.java b/100_core/src/gplx/core/ios/streams/IoStream_base.java index a27517de8..d7ce08a02 100644 --- a/100_core/src/gplx/core/ios/streams/IoStream_base.java +++ b/100_core/src/gplx/core/ios/streams/IoStream_base.java @@ -13,3 +13,124 @@ 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.core.ios.streams; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +public class IoStream_base implements IoStream { + @gplx.Virtual public Io_url Url() {return url;} Io_url url = Io_url_.Empty; + public void Transfer(IoStream trg, int bufferLength) { + byte[] buffer = new byte[bufferLength]; + int read = -1; + while (read != 0) { + read = this.Read(buffer, 0, bufferLength); + trg.Write(buffer, 0, read); + } + trg.Flush(); + } + public int ReadAry(byte[] ary) {return this.Read(ary, 0, ary.length);} + public void WriteAry(byte[] ary) {this.Write(ary, 0, ary.length);} + @gplx.Virtual public Object UnderRdr() {return under;} + @gplx.Virtual public void UnderRdr_(Object v) {this.under = (RandomAccessFile)v;} + @gplx.Virtual public long Pos() {return pos;} long pos; + @gplx.Virtual public long Len() {return length;} long length; + @gplx.Virtual public int Read(byte[] array, int offset, int count) { + try { + int rv = under.read(array, offset, count); + return rv == -1 ? 0 : rv; // NOTE: fis returns -1 if nothing read; .NET returned 0; Hash will fail if -1 returned (will try to create array of 0 length) + } // NOTE: fis keeps track of offset, only need to pass in array (20110606: this NOTE no longer seems to make sense; deprecate) + catch (IOException e) {throw Err_.new_exc(e, "io", "file read failed", "url", url);} + } + public long Seek(long seek_pos) { + try { + under.seek(seek_pos); + pos = under.getFilePointer(); + return pos; + } + catch (IOException e) {throw Err_.new_exc(e, "io", "seek failed", "url", url);} + } + @gplx.Virtual public void Write(byte[] array, int offset, int count) {bfr.Add_mid(array, offset, offset + count); this.Flush();} Bry_bfr bfr = Bry_bfr_.Reset(16); + public void Write_and_flush(byte[] bry, int bgn, int end) { +// ConsoleAdp._.WriteLine(bry.length +" " + bgn + " " + end); + Flush();// flush anything already in buffer + int buffer_len = Io_mgr.Len_kb * 16; + byte[] buffer = new byte[buffer_len]; + int buffer_bgn = bgn; boolean loop = true; + while (loop) { + int buffer_end = buffer_bgn + buffer_len; + if (buffer_end > end) { + buffer_end = end; + buffer_len = end - buffer_bgn; + loop = false; + } + for (int i = 0; i < buffer_len; i++) + buffer[i] = bry[i + buffer_bgn]; + try {under.write(buffer, 0, buffer_len);} + catch (IOException e) {throw Err_.new_exc(e, "io", "write failed", "url", url);} + buffer_bgn = buffer_end; + } +// this.Rls(); +// OutputStream output_stream = null; +// try { +// output_stream = new FileOutputStream(url.Xto_api()); +// bry = ByteAry_.Mid(bry, bgn, end); +// output_stream.write(bry, 0, bry.length); +// } +// catch (IOException e) {throw Err_.err_key_(e, IoEngineArgs._.Err_IoException, "write failed").Add("url", url);} +// finally { +// if (output_stream != null) { +// try {output_stream.close();} +// catch (IOException ignore) {} +// } +// } + } + @gplx.Virtual public void Flush() { + try { + if (mode_is_append) under.seek(under.length()); +// else under.seek(0); + } + catch (IOException e) {throw Err_.new_exc(e, "io", "seek failed", "url", url);} + try {under.write(bfr.Bfr(), 0, bfr.Len());} + catch (IOException e) {throw Err_.new_exc(e, "io", "write failed", "url", url);} + bfr.Clear(); + } + @gplx.Virtual public void Rls() { + IoEngine_system.Closeable_close(under, url, true); + } + RandomAccessFile under; boolean mode_is_append; byte mode; + public static IoStream_base rdr_wrapper_() {return new IoStream_base();} + public static IoStream_base new_(Io_url url, int mode) { + IoStream_base rv = new IoStream_base(); + rv.url = url; + rv.mode = (byte)mode; + File file = new File(url.Xto_api()); + String ctor_mode = ""; + switch (mode) { // mode; SEE:NOTE_1 + case IoStream_.Mode_wtr_append: + rv.mode_is_append = mode == IoStream_.Mode_wtr_append; + ctor_mode = "rws"; + break; + case IoStream_.Mode_wtr_create: + ctor_mode = "rws"; + break; + case IoStream_.Mode_rdr: + ctor_mode = "r"; + break; + } + try {rv.under = new RandomAccessFile(file, ctor_mode);} + catch (FileNotFoundException e) {throw Err_.new_exc(e, "io", "file open failed", "url", url);} + if (mode == IoStream_.Mode_wtr_create) { + try {rv.under.setLength(0);} + catch (IOException e) {throw Err_.new_exc(e, "io", "file truncate failed", "url", url);} + } + rv.length = file.length(); + return rv; + } + public static IoStream_base new_(Object stream) { + IoStream_base rv = new IoStream_base(); +// rv.stream = (System.IO.Stream)stream; + rv.url = Io_url_.Empty; + return rv; + } + } \ No newline at end of file diff --git a/100_core/src/gplx/core/ios/streams/IoStream_mem.java b/100_core/src/gplx/core/ios/streams/IoStream_mem.java index a27517de8..aaf2215c2 100644 --- a/100_core/src/gplx/core/ios/streams/IoStream_mem.java +++ b/100_core/src/gplx/core/ios/streams/IoStream_mem.java @@ -13,3 +13,56 @@ 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.core.ios.streams; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +import gplx.core.texts.*; /*Encoding_*/ +public class IoStream_mem extends IoStream_base { + @Override public Io_url Url() {return url;} Io_url url; + @Override public Object UnderRdr() {throw Err_.new_unimplemented();} // NOTE: should not use System.IO.MemoryStream, b/c resized data will not be captured in this instance's buffer + @Override public long Len() {return Array_.Len(buffer);} + public int Position() {return position;} public void Position_set(int v) {position = v;} int position; + public byte[] Buffer() {return buffer;} private byte[] buffer = new byte[0]; + + @Override public int Read(byte[] array, int offset, int count) { + int read = 0; + int len = Array_.Len(buffer); + for (int i = 0; i < count; i++) { + if (position + i >= len) break; + array[offset + i] = buffer[position + i]; + read++; + } + position += read; + return read; + } + @Override public void Write(byte[] array, int offset, int count) { + // expand buffer if needed; necessary to emulate fileStream writing; ex: FileStream fs = new FileStream(); fs.Write(data); where data may be unknown length + int length = (int)position + count + -offset; + int bufLen = Array_.Len(buffer); + if (bufLen < length) buffer = Bry_.Resize(buffer, length); + for (int i = 0; i < count; i++) + buffer[position + i] = array[offset + i]; + position += count +-offset; + } + @Override public long Pos() {return position;} + @Override public long Seek(long pos) { + this.position = (int)pos; + return pos; + } + + @Override public void Flush() {} + @Override public void Rls() {} + + public static IoStream_mem rdr_txt_(Io_url url, String v) {return rdr_ary_(url, Bry_.new_u8(v));} + public static IoStream_mem rdr_ary_(Io_url url, byte[] v) { + IoStream_mem rv = new IoStream_mem(); + rv.buffer = v; + rv.url = url; + return rv; + } + public static IoStream_mem wtr_data_(Io_url url, int length) { + IoStream_mem rv = new IoStream_mem(); + rv.buffer = new byte[length]; + rv.url = url; + return rv; + } + IoStream_mem() {} +} diff --git a/100_core/src/gplx/core/ios/streams/IoStream_mem_tst.java b/100_core/src/gplx/core/ios/streams/IoStream_mem_tst.java index a27517de8..bceed5e8d 100644 --- a/100_core/src/gplx/core/ios/streams/IoStream_mem_tst.java +++ b/100_core/src/gplx/core/ios/streams/IoStream_mem_tst.java @@ -13,3 +13,16 @@ 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.core.ios.streams; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +import org.junit.*; //using System.IO; /*Stream*/ +public class IoStream_mem_tst { + @Test public void Write() { // confirm that changes written to Stream acquired via .AdpObj are written to IoStream_mem.Buffer + IoStream_mem stream = IoStream_mem.wtr_data_(Io_url_.Empty, 0); + byte[] data = Bry_.New_by_ints(1); + stream.Write(data, 0, Array_.Len(data)); + + Tfds.Eq(1L , stream.Len()); + Tfds.Eq((byte)1 , stream.Buffer()[0]); + stream.Rls(); + } +} diff --git a/100_core/src/gplx/core/ios/streams/IoStream_mock.java b/100_core/src/gplx/core/ios/streams/IoStream_mock.java index a27517de8..1b8fd7ee2 100644 --- a/100_core/src/gplx/core/ios/streams/IoStream_mock.java +++ b/100_core/src/gplx/core/ios/streams/IoStream_mock.java @@ -13,3 +13,31 @@ 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.core.ios.streams; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +public class IoStream_mock implements IoStream { + public byte[] Data_bry() {return data_bry;} public IoStream_mock Data_bry_(byte[] v) {data_bry = v; data_bry_len = v.length; return this;} private byte[] data_bry; int data_bry_len; + public int Data_bry_pos() {return data_bry_pos;} int data_bry_pos; + public void Reset() {data_bry_pos = 0;} + public IoStream_mock Read_limit_(int v) {read_limit = v; return this;} int read_limit; + public int Read(byte[] bfr, int bfr_bgn, int bfr_len) { + int bytes_read = bfr_len; + if (bytes_read > read_limit) bytes_read = read_limit; // stream may limit maximum read; EX: bfr_len of 16k but only 2k will be filled + int bytes_left = data_bry_len - data_bry_pos; + if (bytes_read > bytes_left) bytes_read = bytes_left; // not enough bytes left in data_bry; bytes_read = whatever is left + Bry_.Copy_by_pos(data_bry, data_bry_pos, data_bry_pos + bytes_read, bfr, bfr_bgn); + data_bry_pos += bytes_read; + return bytes_read; + } + public Object UnderRdr() {return null;} + public Io_url Url() {return Io_url_.Empty;} + public long Pos() {return -1;} + public long Len() {return -1;} + public int ReadAry(byte[] array) {return -1;} + public long Seek(long pos) {return -1;} + public void WriteAry(byte[] ary) {} + public void Write(byte[] array, int offset, int count) {} + public void Transfer(IoStream trg, int bufferLength) {} + public void Flush() {} + public void Write_and_flush(byte[] bry, int bgn, int end) {} + public void Rls() {} +} diff --git a/100_core/src/gplx/core/ios/streams/IoStream_mock_tst.java b/100_core/src/gplx/core/ios/streams/IoStream_mock_tst.java index a27517de8..fc04c7a95 100644 --- a/100_core/src/gplx/core/ios/streams/IoStream_mock_tst.java +++ b/100_core/src/gplx/core/ios/streams/IoStream_mock_tst.java @@ -13,3 +13,34 @@ 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.core.ios.streams; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +import org.junit.*; +public class IoStream_mock_tst { + @Before public void init() {fxt.Clear();} IoStream_mock_fxt fxt = new IoStream_mock_fxt(); + @Test public void Basic() { + fxt.Init_src_str_("abcde").Init_trg_len_(5).Init_rdr_limit_(2).Init_read_len_(2); + fxt.Test_read("ab").Test_read("cd").Test_read("e"); + } + @Test public void Read_limit() { + fxt.Init_src_str_("abcde").Init_trg_len_(5).Init_rdr_limit_(2).Init_read_len_(4); + fxt.Test_read("ab").Test_read("cd").Test_read("e"); + } +} +class IoStream_mock_fxt { + public void Clear() { + if (rdr == null) + rdr = new IoStream_mock(); + rdr.Reset(); + trg_bgn = 0; + } IoStream_mock rdr; byte[] trg_bry; + public IoStream_mock_fxt Init_src_str_(String v) {rdr.Data_bry_(Bry_.new_a7(v)); return this;} + public IoStream_mock_fxt Init_trg_len_(int v) {trg_bry = new byte[v]; return this;} + public IoStream_mock_fxt Init_read_len_(int v) {read_len = v; return this;} int read_len; + public IoStream_mock_fxt Init_rdr_limit_(int v) {rdr.Read_limit_(v); return this;} + public IoStream_mock_fxt Test_read(String expd) { + int bytes_read = rdr.Read(trg_bry, trg_bgn, read_len); + Tfds.Eq(expd, String_.new_a7(trg_bry, trg_bgn, trg_bgn + bytes_read)); + trg_bgn += bytes_read; + return this; + } int trg_bgn; +} diff --git a/100_core/src/gplx/core/ios/streams/IoStream_stream_rdr.java b/100_core/src/gplx/core/ios/streams/IoStream_stream_rdr.java index a27517de8..ec962ffd6 100644 --- a/100_core/src/gplx/core/ios/streams/IoStream_stream_rdr.java +++ b/100_core/src/gplx/core/ios/streams/IoStream_stream_rdr.java @@ -13,3 +13,25 @@ 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.core.ios.streams; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +public class IoStream_stream_rdr implements IoStream { + public int Read(byte[] bfr, int bfr_bgn, int bfr_len) { + try { + return stream.read(bfr, bfr_bgn, bfr_len); + } + catch (Exception e) {throw Err_.new_exc(e, "core", "failed to read from stream");} + } + public IoStream UnderRdr_(Object v) {this.stream = (java.io.InputStream)v; return this;} java.io.InputStream stream; + public Object UnderRdr() {return stream;} + public Io_url Url() {return Io_url_.Empty;} + public long Pos() {return -1;} + public long Len() {return -1;} + public int ReadAry(byte[] array) {return -1;} + public long Seek(long pos) {return -1;} + public void WriteAry(byte[] ary) {} + public void Write(byte[] array, int offset, int count) {} + public void Transfer(IoStream trg, int bufferLength) {} + public void Flush() {} + public void Write_and_flush(byte[] bry, int bgn, int end) {} + public void Rls() {} +} diff --git a/100_core/src/gplx/core/ios/streams/Io_stream_rdr.java b/100_core/src/gplx/core/ios/streams/Io_stream_rdr.java index a27517de8..7806f1d08 100644 --- a/100_core/src/gplx/core/ios/streams/Io_stream_rdr.java +++ b/100_core/src/gplx/core/ios/streams/Io_stream_rdr.java @@ -13,3 +13,16 @@ 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.core.ios.streams; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +public interface Io_stream_rdr extends Rls_able { + byte Tid(); + boolean Exists(); + Io_url Url(); Io_stream_rdr Url_(Io_url v); + long Len(); Io_stream_rdr Len_(long v); + Io_stream_rdr Open(); + void Open_mem(byte[] v); + Object Under(); + + int Read(byte[] bry, int bgn, int len); + long Skip(long len); +} diff --git a/100_core/src/gplx/core/ios/streams/Io_stream_rdr_.java b/100_core/src/gplx/core/ios/streams/Io_stream_rdr_.java index a27517de8..8193c2943 100644 --- a/100_core/src/gplx/core/ios/streams/Io_stream_rdr_.java +++ b/100_core/src/gplx/core/ios/streams/Io_stream_rdr_.java @@ -13,3 +13,88 @@ 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.core.ios.streams; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +import gplx.core.ios.streams.rdrs.*; +public class Io_stream_rdr_ { + public static final int Read_done = -1, Read_done_compare = 1; + public static final Io_stream_rdr Noop = new Io_stream_rdr__noop(); + public static Io_stream_rdr New__raw(Io_url url) {return new Io_stream_rdr__raw().Url_(url);} + public static Io_stream_rdr New__raw(java.io.InputStream strm) {return new Io_stream_rdr__raw().Under_(strm);} + private static Io_stream_rdr New__zip(Io_url url) {return new Io_stream_rdr__zip().Url_(url);} + private static Io_stream_rdr New__gzip(Io_url url) {return new Io_stream_rdr__gzip().Url_(url);} + public static Io_stream_rdr New__bzip2(Io_url url) {return new Io_stream_rdr__bzip2().Url_(url);} + public static Io_stream_rdr New__mem(String v) {return New__mem(Bry_.new_u8(v));} + public static Io_stream_rdr New__mem(byte[] v) { + Io_stream_rdr rv = new Io_stream_rdr__adp(New__mem_as_stream(v)); + rv.Len_(v.length); + return rv; + } + public static java.io.InputStream New__mem_as_stream(byte[] v) { + return new java.io.ByteArrayInputStream(v); + } + public static Io_stream_rdr New_by_url(Io_url url) { + String ext = url.Ext(); + if (String_.Eq(ext, Io_stream_tid_.Ext__zip)) return Io_stream_rdr_.New__zip(url); + else if (String_.Eq(ext, Io_stream_tid_.Ext__gz)) return Io_stream_rdr_.New__gzip(url); + else if (String_.Eq(ext, Io_stream_tid_.Ext__bz2)) return Io_stream_rdr_.New__bzip2(url); + else if (String_.Eq(ext, Io_stream_tid_.Ext__xz)) return new Io_stream_rdr__xz().Url_(url); + else return Io_stream_rdr_.New__raw(url); + } + public static Io_stream_rdr New_by_tid(byte tid) { + switch (tid) { + case Io_stream_tid_.Tid__raw: return new Io_stream_rdr__raw(); + case Io_stream_tid_.Tid__zip: return new Io_stream_rdr__zip(); + case Io_stream_tid_.Tid__gzip: return new Io_stream_rdr__gzip(); + case Io_stream_tid_.Tid__bzip2: return new Io_stream_rdr__bzip2(); + case Io_stream_tid_.Tid__xz: return new Io_stream_rdr__xz(); + default: throw Err_.new_unhandled_default(tid); + } + } + public static String Load_all_as_str(Io_stream_rdr rdr) {return String_.new_u8(Load_all_as_bry(Bry_bfr_.New(), rdr));} + public static byte[] Load_all_as_bry(Bry_bfr rv, Io_stream_rdr rdr) { + Load_all_to_bfr(rv, rdr); + return rv.To_bry_and_clear(); + } + public static void Load_all_to_bfr(Bry_bfr rv, Io_stream_rdr rdr) { + try { + byte[] bry = new byte[4096]; + while (true) { + int read = rdr.Read(bry, 0, 4096); + if (read < gplx.core.ios.streams.Io_stream_rdr_.Read_done_compare) break; + rv.Add_mid(bry, 0, read); + } + } finally {rdr.Rls();} + } + public static int Read_by_parts(java.io.InputStream stream, int part_len, byte[] bry, int bgn, int len) { + /* + NOTE: BZip2CompressorInputStream will fail if large len is used + Instead, make smaller requests and fill bry + */ + try { + int rv = 0; + int end = bgn + len; + int cur = bgn; + while (true) { + int bry_len = part_len; // read in increments of part_len + if (cur + bry_len > end) // if cur + 8 kb > bry_len, trim to end; EX: 9 kb bry passed; 1st pass is 8kb, 2nd pass should be 1kb, not 8 kb; + bry_len = end - cur; + if (cur == end) break; // no more bytes needed; break; EX: 8 kb bry passed; 1st pass is 8kb; 2nd pass is 0 and cur == end + int read = stream.read(bry, cur, bry_len); + if (read == gplx.core.ios.streams.Io_stream_rdr_.Read_done) // read done; end + break; + rv += read; + cur += read; + } + return rv; + } + catch (Exception exc) { + throw Err_.new_exc(exc, "io", "read failed", "bgn", bgn, "len", len); + } + } + public static boolean Close(java.io.InputStream stream) { + try { + if (stream != null) stream.close(); + return true; + } catch (Exception e) {Err_.Noop(e); return false;} + } +} diff --git a/100_core/src/gplx/core/ios/streams/Io_stream_rdr__tst.java b/100_core/src/gplx/core/ios/streams/Io_stream_rdr__tst.java index a27517de8..32e5d1350 100644 --- a/100_core/src/gplx/core/ios/streams/Io_stream_rdr__tst.java +++ b/100_core/src/gplx/core/ios/streams/Io_stream_rdr__tst.java @@ -13,3 +13,42 @@ 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.core.ios.streams; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +import org.junit.*; +public class Io_stream_rdr__tst { + @Before public void init() {fxt.Clear();} private Io_stream_rdr__fxt fxt = new Io_stream_rdr__fxt(); + @After public void term() {fxt.Rls();} + @Test public void Bz2_read() { + fxt .Init_stream("abcd") // read everything at once + .Expd_bytes_read(4).Test_read(0, 4, "abcd"); + fxt .Init_stream("abcd") // read in steps + .Expd_bytes_read(1).Test_read(0, 1, "a") + .Expd_bytes_read(2).Test_read(1, 2, "bc") + .Expd_bytes_read(1).Test_read(3, 1, "d") + ; + } +} +class Io_stream_rdr__fxt { + private java.io.InputStream stream; + private int stream_bry_len; + public void Clear() { + expd_bytes_read = Int_.Min_value; + } + public Io_stream_rdr__fxt Expd_bytes_read(int v) {expd_bytes_read = v; return this;} private int expd_bytes_read = Int_.Min_value; + public Io_stream_rdr__fxt Init_stream(String v) { + byte[] stream_bry = Bry_.new_a7(v); + stream_bry_len = stream_bry.length; + stream = Io_stream_rdr_.New__mem_as_stream(stream_bry); + return this; + } + public Io_stream_rdr__fxt Test_read(int bgn, int len, String expd_str) { + byte[] bfr = new byte[stream_bry_len]; // allocate whole stream; may not use it all + int actl_bytes_read = Io_stream_rdr_.Read_by_parts(stream, 8, bfr, bgn, len); + Tfds.Eq(expd_bytes_read, actl_bytes_read, "bytes_read"); + Tfds.Eq(expd_str, String_.new_u8(bfr, bgn, bgn + actl_bytes_read), "str"); + return this; + } + public void Rls() { + Io_stream_rdr_.Close(stream); + } +} diff --git a/100_core/src/gplx/core/ios/streams/Io_stream_tid_.java b/100_core/src/gplx/core/ios/streams/Io_stream_tid_.java index a27517de8..2f943ed9c 100644 --- a/100_core/src/gplx/core/ios/streams/Io_stream_tid_.java +++ b/100_core/src/gplx/core/ios/streams/Io_stream_tid_.java @@ -13,3 +13,44 @@ 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.core.ios.streams; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +public class Io_stream_tid_ { + public static final byte Tid__null = 0, Tid__raw = 1, Tid__zip = 2, Tid__gzip = 3, Tid__bzip2 = 4, Tid__xz = 5; // SERIALIZED:xo.text_db;xo.html_db + public static final String Ext__zip = ".zip", Ext__gz = ".gz", Ext__bz2 = ".bz2", Ext__xz = ".xz"; + private static final String Key__raw = "raw", Key__zip = "zip", Key__gzip = "gzip", Key__bzip2 = "bzip2", Key__xz = "xz"; + + public static String To_key(byte v) { + switch (v) { + case Io_stream_tid_.Tid__raw : return Key__raw; + case Io_stream_tid_.Tid__zip : return Key__zip; + case Io_stream_tid_.Tid__gzip : return Key__gzip; + case Io_stream_tid_.Tid__bzip2 : return Key__bzip2; + case Io_stream_tid_.Tid__xz : return Key__xz; + default : throw Err_.new_unhandled_default(v); + } + } + public static byte To_tid(String v) { + if (String_.Eq(v, Key__raw)) return Io_stream_tid_.Tid__raw; + else if (String_.Eq(v, Key__zip)) return Io_stream_tid_.Tid__zip; + else if (String_.Eq(v, Key__gzip)) return Io_stream_tid_.Tid__gzip; + else if (String_.Eq(v, Key__bzip2)) return Io_stream_tid_.Tid__bzip2; + else if (String_.Eq(v, Key__xz)) return Io_stream_tid_.Tid__xz; + else throw Err_.new_unhandled_default(v); + } + public static String Obsolete_to_str(byte v) { + switch (v) { + case Io_stream_tid_.Tid__raw : return ".xdat"; + case Io_stream_tid_.Tid__zip : return ".zip"; + case Io_stream_tid_.Tid__gzip : return ".gz"; + case Io_stream_tid_.Tid__bzip2 : return ".bz2"; + default : throw Err_.new_unhandled_default(v); + } + } + public static byte Obsolete_to_tid(String v) { + if (String_.Eq(v, ".xdat")) return Io_stream_tid_.Tid__raw; + else if (String_.Eq(v, ".zip")) return Io_stream_tid_.Tid__zip; + else if (String_.Eq(v, ".gz")) return Io_stream_tid_.Tid__gzip; + else if (String_.Eq(v, ".bz2")) return Io_stream_tid_.Tid__bzip2; + else throw Err_.new_unhandled_default(v); + } +} diff --git a/100_core/src/gplx/core/ios/streams/Io_stream_wtr.java b/100_core/src/gplx/core/ios/streams/Io_stream_wtr.java index a27517de8..3e6e24867 100644 --- a/100_core/src/gplx/core/ios/streams/Io_stream_wtr.java +++ b/100_core/src/gplx/core/ios/streams/Io_stream_wtr.java @@ -13,3 +13,14 @@ 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.core.ios.streams; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +public interface Io_stream_wtr extends Rls_able { + byte Tid(); + Io_url Url(); Io_stream_wtr Url_(Io_url v); + void Trg_bfr_(Bry_bfr v); + Io_stream_wtr Open(); + byte[] To_ary_and_clear(); + + void Write(byte[] bry, int bgn, int len); + void Flush(); +} diff --git a/100_core/src/gplx/core/ios/streams/Io_stream_wtr_.java b/100_core/src/gplx/core/ios/streams/Io_stream_wtr_.java index a27517de8..82686baf2 100644 --- a/100_core/src/gplx/core/ios/streams/Io_stream_wtr_.java +++ b/100_core/src/gplx/core/ios/streams/Io_stream_wtr_.java @@ -13,3 +13,54 @@ 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.core.ios.streams; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +import gplx.core.ios.streams.wtrs.*; +public class Io_stream_wtr_ { + public static Io_stream_wtr New__raw(Io_url url) {return new Io_stream_wtr__raw().Url_(url);} + private static Io_stream_wtr New__zip(Io_url url) {return new Io_stream_wtr__zip().Url_(url);} + private static Io_stream_wtr New__gzip(Io_url url) {return new Io_stream_wtr__gzip().Url_(url);} + private static Io_stream_wtr New__bzip2(Io_url url) {return new Io_stream_wtr__bzip2().Url_(url);} + public static Io_stream_wtr New_by_url(Io_url url) { + String ext = url.Ext(); + if (String_.Eq(ext, Io_stream_tid_.Ext__zip)) return Io_stream_wtr_.New__zip(url); + else if (String_.Eq(ext, Io_stream_tid_.Ext__gz)) return Io_stream_wtr_.New__gzip(url); + else if (String_.Eq(ext, Io_stream_tid_.Ext__bz2)) return Io_stream_wtr_.New__bzip2(url); + else if (String_.Eq(ext, Io_stream_tid_.Ext__xz)) return new Io_stream_wtr__xz().Url_(url); + else return Io_stream_wtr_.New__raw(url); + } + public static Io_stream_wtr New_by_tid(byte v) { + switch (v) { + case Io_stream_tid_.Tid__raw: return new Io_stream_wtr__raw(); + case Io_stream_tid_.Tid__zip: return new Io_stream_wtr__zip(); + case Io_stream_tid_.Tid__gzip: return new Io_stream_wtr__gzip(); + case Io_stream_tid_.Tid__bzip2: return new Io_stream_wtr__bzip2(); + case Io_stream_tid_.Tid__xz: return new Io_stream_wtr__xz(); + default: throw Err_.new_unhandled(v); + } + } + public static Io_stream_wtr New_by_mem(Bry_bfr bfr, byte tid) { + Io_stream_wtr wtr = New_by_tid(tid).Url_(Io_url_.Empty); + wtr.Trg_bfr_(bfr); + return wtr; + } + public static void Save_rdr(Io_url url, Io_stream_rdr rdr, Io_download_fmt download_progress) { + byte[] bry = new byte[4096]; + Io_stream_wtr wtr = New_by_url(url); + try { + wtr.Open(); + if (download_progress != Io_download_fmt.Null) + download_progress.Bgn(rdr.Len()); + while (true) { + int read = rdr.Read(bry, 0, 4096); + if (read < gplx.core.ios.streams.Io_stream_rdr_.Read_done_compare) break; + if (download_progress != Io_download_fmt.Null) + download_progress.Prog(read); + wtr.Write(bry, 0, read); + } + wtr.Flush(); + if (download_progress != Io_download_fmt.Null) + download_progress.Term(); + } + finally {wtr.Rls(); rdr.Rls();} + } +} diff --git a/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__adp.java b/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__adp.java index a27517de8..c1eefbe0e 100644 --- a/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__adp.java +++ b/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__adp.java @@ -13,3 +13,27 @@ 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.core.ios.streams.rdrs; import gplx.*; import gplx.core.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Io_stream_rdr__adp implements Io_stream_rdr { + private java.io.InputStream strm; + public Io_stream_rdr__adp(java.io.InputStream strm) {this.strm = strm;} + public Object Under() {return strm;} + public byte Tid() {return Io_stream_tid_.Tid__raw;} + public boolean Exists() {return len > 0;} + public Io_url Url() {return url;} public Io_stream_rdr Url_(Io_url v) {this.url = v; return this;} private Io_url url; + public long Len() {return len;} public Io_stream_rdr Len_(long v) {len = v; return this;} private long len = Io_mgr.Len_null; + public void Open_mem(byte[] v) {} + public Io_stream_rdr Open() {return this;} + public int Read(byte[] bry, int bgn, int len) { + try {return strm.read(bry, bgn, len);} + catch (Exception e) {throw Err_.new_exc(e, "io", "read failed", "bgn", bgn, "len", len);} + } + public long Skip(long len) { + try {return strm.skip(len);} + catch (Exception e) {throw Err_.new_exc(e, "io", "skip failed", "len", len);} + } + public void Rls() { + try {strm.close();} + catch (Exception e) {throw Err_.new_exc(e, "io", "close failed", "url", url.Xto_api());} + } +} diff --git a/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__base.java b/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__base.java index a27517de8..3891577bb 100644 --- a/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__base.java +++ b/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__base.java @@ -13,3 +13,34 @@ 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.core.ios.streams.rdrs; import gplx.*; import gplx.core.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +public abstract class Io_stream_rdr__base implements Io_stream_rdr { + public abstract byte Tid(); + public Io_url Url() {return url;} protected Io_url url; + public long Len() {return len;} private long len = Io_mgr.Len_null; + public boolean Exists() {return this.Len() > 0;} + public Io_stream_rdr Url_(Io_url v) {this.url = v; return this;} + public Io_stream_rdr Len_(long v) {len = v; return this;} + public Object Under() {return stream;} public Io_stream_rdr Under_(java.io.InputStream v) {this.stream = v; return this;} protected java.io.InputStream stream; + public void Open_mem(byte[] v) { + stream = Wrap_stream(new java.io.ByteArrayInputStream(v)); + } + public Io_stream_rdr Open() { + try {stream = Wrap_stream(new java.io.FileInputStream(url.Xto_api()));} + catch (Exception e) {throw Err_.new_exc(e, "io", "open failed", "url", url.Xto_api());} + return this; + } + public int Read(byte[] bry, int bgn, int len) { + try {return stream.read(bry, bgn, len);} + catch (Exception e) {throw Err_.new_exc(e, "io", "read failed", "bgn", bgn, "len", len);} + } + public long Skip(long len) { + try {return stream.skip(len);} + catch (Exception e) {throw Err_.new_exc(e, "io", "skip failed", "len", len);} + } + public void Rls() { + try {stream.close();} + catch (Exception e) {throw Err_.new_exc(e, "io", "close failed", "url", url.Xto_api());} + } + public abstract java.io.InputStream Wrap_stream(java.io.InputStream stream); + } diff --git a/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__bzip2.java b/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__bzip2.java index a27517de8..244b1b425 100644 --- a/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__bzip2.java +++ b/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__bzip2.java @@ -13,3 +13,15 @@ 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.core.ios.streams.rdrs; import gplx.*; import gplx.core.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Io_stream_rdr__bzip2 extends Io_stream_rdr__base { + @Override public byte Tid() {return Io_stream_tid_.Tid__bzip2;} + @Override public int Read(byte[] bry, int bgn, int len) { + return Io_stream_rdr_.Read_by_parts(stream, Read_len, bry, bgn, len); + } + @Override public java.io.InputStream Wrap_stream(java.io.InputStream stream) { + try {return new org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream(stream, true);} + catch (Exception exc) {throw Err_.new_wo_type("failed to open bzip2 stream");} + } + private static final int Read_len = Io_mgr.Len_mb * 128; + } diff --git a/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__gzip.java b/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__gzip.java index a27517de8..6bcf0defe 100644 --- a/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__gzip.java +++ b/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__gzip.java @@ -13,3 +13,30 @@ 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.core.ios.streams.rdrs; import gplx.*; import gplx.core.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Io_stream_rdr__gzip extends Io_stream_rdr__base { + @Override public byte Tid() {return Io_stream_tid_.Tid__gzip;} + @Override public int Read(byte[] bry, int bgn, int len) { + synchronized (this) { + try { + int total_read = 0; + while (true) { // NOTE: the gz stream reads partially; (request 100; only get back 10); keep reading until entire bfr is full or -1 + int read = stream.read(bry, bgn, len); + if (read == Io_stream_rdr_.Read_done) break; + total_read += read; + if (total_read >= len) break; // entire bfr full; stop + bgn += read; // increase bgn by amount read + len -= read; // decrease len by amount read + } + return total_read == 0 ? Io_stream_rdr_.Read_done : total_read; // gzip seems to allow 0 bytes read (bz2 and zip return -1 instead); normalize return to -1; + } + catch (Exception e) { + throw Err_.new_exc(e, "io", "read failed", "bgn", bgn, "len", len); + } + } + } + @Override public java.io.InputStream Wrap_stream(java.io.InputStream stream) { + try {return new java.util.zip.GZIPInputStream(stream);} + catch (Exception exc) {throw Err_.new_wo_type("failed to open gz stream");} + } + } diff --git a/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__noop.java b/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__noop.java index a27517de8..113bcf29d 100644 --- a/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__noop.java +++ b/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__noop.java @@ -13,3 +13,16 @@ 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.core.ios.streams.rdrs; import gplx.*; import gplx.core.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Io_stream_rdr__noop implements Io_stream_rdr { + public Object Under() {return null;} + public byte Tid() {return Io_stream_tid_.Tid__null;} + public boolean Exists() {return false;} + public Io_url Url() {return Io_url_.Empty;} public Io_stream_rdr Url_(Io_url v) {return this;} + public long Len() {return Io_mgr.Len_null;} public Io_stream_rdr Len_(long v) {return this;} + public void Open_mem(byte[] v) {} + public Io_stream_rdr Open() {return this;} + public int Read(byte[] bry, int bgn, int len) {return Io_stream_rdr_.Read_done;} + public long Skip(long len) {return Io_stream_rdr_.Read_done;} + public void Rls() {} +} diff --git a/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__raw.java b/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__raw.java index a27517de8..3f118a4e1 100644 --- a/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__raw.java +++ b/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__raw.java @@ -13,3 +13,23 @@ 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.core.ios.streams.rdrs; import gplx.*; import gplx.core.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Io_stream_rdr__raw extends Io_stream_rdr__base { + public byte Tid() {return Io_stream_tid_.Tid__raw;} + public Io_stream_rdr Open() { + Io_url url = this.Url(); + try { + if (!Io_mgr.Instance.Exists(url)) + stream = Wrap_stream(new java.io.ByteArrayInputStream(Bry_.Empty)); + else { + if (url.Info().EngineKey() == IoEngine_.MemKey) + stream = Wrap_stream(new java.io.ByteArrayInputStream(Io_mgr.Instance.LoadFilBry(url.Xto_api()))); + else + stream = Wrap_stream(new java.io.FileInputStream(url.Xto_api())); + } + } + catch (Exception e) {throw Err_.new_exc(e, "io", "open failed", "url", url.Xto_api());} + return this; + } + @Override public java.io.InputStream Wrap_stream(java.io.InputStream stream) {return stream;} + } diff --git a/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__xz.java b/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__xz.java index a27517de8..e0c234958 100644 --- a/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__xz.java +++ b/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__xz.java @@ -13,3 +13,11 @@ 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.core.ios.streams.rdrs; import gplx.*; import gplx.core.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Io_stream_rdr__xz extends Io_stream_rdr__base { + @Override public byte Tid() {return Io_stream_tid_.Tid__xz;} + @Override public java.io.InputStream Wrap_stream(java.io.InputStream stream) { + try {return new org.tukaani.xz.XZInputStream(stream);} + catch (Exception exc) {throw Err_.new_wo_type("failed to open xz stream");} + } + } diff --git a/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__zip.java b/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__zip.java index a27517de8..563cbfa50 100644 --- a/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__zip.java +++ b/100_core/src/gplx/core/ios/streams/rdrs/Io_stream_rdr__zip.java @@ -13,3 +13,40 @@ 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.core.ios.streams.rdrs; import gplx.*; import gplx.core.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Io_stream_rdr__zip extends Io_stream_rdr__base { + @Override public byte Tid() {return Io_stream_tid_.Tid__gzip;} + public Object Under() {return zip_stream;} private java.util.zip.ZipInputStream zip_stream; + public void Src_bfr_(Bry_bfr v) {this.src_bfr = v;} Bry_bfr src_bfr; + public void Open_mem(byte[] v) { + this.zip_stream = (java.util.zip.ZipInputStream)Wrap_stream(new java.io.ByteArrayInputStream(v)); + } + public Io_stream_rdr Open() { + try {this.zip_stream = (java.util.zip.ZipInputStream)Wrap_stream(new java.io.FileInputStream(url.Xto_api()));} + catch (Exception e) {throw Err_.new_exc(e, "io", "open failed", "url", url.Xto_api());} + return this; + } + public int Read(byte[] bry, int bgn, int len) { + try { + while (true){ + int read = zip_stream.read(bry, bgn, len); + if (read == Io_stream_rdr_.Read_done) { + if (zip_stream.getNextEntry() == null) + return Io_stream_rdr_.Read_done; + } + else + return read; + } + } + catch (Exception e) {throw Err_.new_exc(e, "io", "read failed", "bgn", bgn, "len", len);} + } + public long Skip(long len) { + try {return zip_stream.skip(len);} + catch (Exception e) {throw Err_.new_exc(e, "io", "skip failed", "len", len);} + } + public void Rls() { + try {zip_stream.close();} + catch (Exception e) {throw Err_.new_exc(e, "io", "close failed", "url", url.Xto_api());} + } + @Override public java.io.InputStream Wrap_stream(java.io.InputStream input_stream) {return new java.util.zip.ZipInputStream(input_stream);} + } diff --git a/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__base.java b/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__base.java index a27517de8..e317ae163 100644 --- a/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__base.java +++ b/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__base.java @@ -13,3 +13,46 @@ 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.core.ios.streams.wtrs; import gplx.*; import gplx.core.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +public abstract class Io_stream_wtr__base implements Io_stream_wtr { + public abstract byte Tid(); + public Io_url Url() {return url;} private Io_url url; + public Io_stream_wtr Url_(Io_url v) {url = v; return this;} + public void Trg_bfr_(Bry_bfr v) {this.trg_bfr = v;} private Bry_bfr trg_bfr; + public byte[] To_ary_and_clear() {return trg_bfr.To_bry_and_clear();} + + private java.io.OutputStream zip_stream; + private java.io.ByteArrayOutputStream mem_stream; + @Virtual public Io_stream_wtr Open() { + java.io.OutputStream bry_stream = null; + if (trg_bfr == null) { + if (!Io_mgr.Instance.ExistsFil(url)) Io_mgr.Instance.SaveFilStr(url, ""); + try {bry_stream = new java.io.FileOutputStream(url.Raw());} + catch (Exception e) {throw Err_.new_exc(e, "io", "open failed", "url", url.Raw());} + } + else { + mem_stream = new java.io.ByteArrayOutputStream(); + bry_stream = mem_stream; + } + zip_stream = Wrap_stream(bry_stream); + return this; + } + public void Write(byte[] bry, int bgn, int len) { + try {zip_stream.write(bry, bgn, len);} + catch (Exception e) {Err_.new_exc(e, "io", "write failed", "bgn", bgn, "len", len);} + } + public void Flush() { + if (trg_bfr != null) { + try {zip_stream.close();} catch (Exception e) {throw Err_.new_exc(e, "io", "flush failed");} // must close zip_stream to flush all bytes + trg_bfr.Add(mem_stream.toByteArray()); + } + } + public void Rls() { + try { + if (zip_stream != null) zip_stream.close(); + if (mem_stream != null) mem_stream.close(); + } + catch (Exception e) {throw Err_.new_exc(e, "io", "close failed", "url", url.Raw());} + } + @Virtual protected java.io.OutputStream Wrap_stream(java.io.OutputStream stream) {throw Err_.new_unimplemented();} + } diff --git a/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__bzip2.java b/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__bzip2.java index a27517de8..43da8e6c0 100644 --- a/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__bzip2.java +++ b/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__bzip2.java @@ -13,3 +13,11 @@ 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.core.ios.streams.wtrs; import gplx.*; import gplx.core.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Io_stream_wtr__bzip2 extends Io_stream_wtr__base { + @Override public byte Tid() {return Io_stream_tid_.Tid__bzip2;} + @Override public java.io.OutputStream Wrap_stream(java.io.OutputStream stream) { + try {return new org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream(stream);} + catch (Exception e) {throw Err_.new_exc(e, "io", "failed to open bzip2 stream");} + } + } diff --git a/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__gzip.java b/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__gzip.java index a27517de8..b2b5ca995 100644 --- a/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__gzip.java +++ b/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__gzip.java @@ -13,3 +13,11 @@ 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.core.ios.streams.wtrs; import gplx.*; import gplx.core.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Io_stream_wtr__gzip extends Io_stream_wtr__base { + @Override public byte Tid() {return Io_stream_tid_.Tid__gzip;} + @Override public java.io.OutputStream Wrap_stream(java.io.OutputStream stream) { + try {return new java.util.zip.GZIPOutputStream(stream);} + catch (Exception e) {throw Err_.new_exc(e, "io", "failed to open gz stream");} + } + } diff --git a/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__raw.java b/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__raw.java index a27517de8..1fb86d235 100644 --- a/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__raw.java +++ b/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__raw.java @@ -13,3 +13,41 @@ 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.core.ios.streams.wtrs; import gplx.*; import gplx.core.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Io_stream_wtr__raw implements Io_stream_wtr { + public byte Tid() {return Io_stream_tid_.Tid__raw;} + public Io_url Url() {return url;} public Io_stream_wtr Url_(Io_url v) {url = v; return this;} private Io_url url; + public void Trg_bfr_(Bry_bfr v) {trg_bfr = v;} private Bry_bfr trg_bfr; + + private IoStream bry_stream; + @Override public Io_stream_wtr Open() { + try { + if (trg_bfr == null) + bry_stream = Io_mgr.Instance.OpenStreamWrite(url); + } + catch (Exception e) {throw Err_.new_exc(e, "io", "open failed", "url", url.Raw());} + return this; + } + public void Write(byte[] bry, int bgn, int len) { + if (trg_bfr == null) { + try {bry_stream.Write(bry, bgn, len);} + catch (Exception e) {throw Err_.new_exc(e, "io", "write failed", "url", url.Raw(), "bgn", bgn, "len", len);} + } + else + trg_bfr.Add_mid(bry, bgn, bgn + len); + } + public byte[] To_ary_and_clear() { + return trg_bfr == null ? Io_mgr.Instance.LoadFilBry(url) : trg_bfr.To_bry_and_clear(); + } + public void Flush() { + if (trg_bfr == null) + bry_stream.Flush(); + } + public void Rls() { + try { + if (trg_bfr == null) + bry_stream.Rls(); + } + catch (Exception e) {throw Err_.new_exc(e, "io", "close failed", "url", url.Raw());} + } + } diff --git a/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__xz.java b/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__xz.java index a27517de8..b146d4008 100644 --- a/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__xz.java +++ b/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__xz.java @@ -13,3 +13,11 @@ 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.core.ios.streams.wtrs; import gplx.*; import gplx.core.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Io_stream_wtr__xz extends Io_stream_wtr__base { + @Override public byte Tid() {return Io_stream_tid_.Tid__xz;} + @Override public java.io.OutputStream Wrap_stream(java.io.OutputStream stream) { + try {return new org.tukaani.xz.XZOutputStream(stream, new org.tukaani.xz.LZMA2Options(org.tukaani.xz.LZMA2Options.PRESET_DEFAULT));} + catch (Exception e) {throw Err_.new_exc(e, "io", "failed to open xz stream");} + } + } diff --git a/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__zip.java b/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__zip.java index a27517de8..383533aa4 100644 --- a/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__zip.java +++ b/100_core/src/gplx/core/ios/streams/wtrs/Io_stream_wtr__zip.java @@ -13,3 +13,54 @@ 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.core.ios.streams.wtrs; import gplx.*; import gplx.core.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Io_stream_wtr__zip extends Io_stream_wtr__base { + @Override public byte Tid() {return Io_stream_tid_.Tid__zip;} + private java.util.zip.ZipOutputStream zip_stream; + public Io_url Url() {return url;} public Io_stream_wtr Url_(Io_url v) {url = v; trg_bfr = null; return this;} private Io_url url = Io_url_.Empty; + public void Trg_bfr_(Bry_bfr v) {trg_bfr = v;} private Bry_bfr trg_bfr; private java.io.ByteArrayOutputStream mem_stream; + // rely on zip_stream to close bry_stream + @Override public Io_stream_wtr Open() { + java.io.OutputStream bry_stream; + if (trg_bfr == null) { + if (!Io_mgr.Instance.ExistsFil(url)) Io_mgr.Instance.SaveFilStr(url, ""); // create file if it doesn't exist + try {bry_stream = new java.io.FileOutputStream(url.Xto_api());} + catch (Exception e) {throw Err_.new_exc(e, "io", "open failed", "url", url.Raw());} + } + else { + mem_stream = new java.io.ByteArrayOutputStream(); + bry_stream = mem_stream; + } + zip_stream = new java.util.zip.ZipOutputStream(bry_stream); + java.util.zip.ZipEntry entry = new java.util.zip.ZipEntry("file"); + try {zip_stream.putNextEntry(entry);} + catch (Exception e) {throw Err_.new_exc(e, "io", "open failed", "url", url.Raw());} + return this; + } + public void Write(byte[] bry, int bgn, int len) { + try {zip_stream.write(bry, bgn, len);} + catch (Exception e) {throw Err_.new_exc(e, "io", "write failed", "url", url.Raw(), "bgn", bgn, "len", len);} + } + public void Flush() {// fixed as of DATE:2014-04-15 + try { + zip_stream.closeEntry(); + zip_stream.close(); + if (trg_bfr != null) + trg_bfr.Add(mem_stream.toByteArray()); + zip_stream.flush(); + } + catch (Exception e) {throw Err_.new_exc(e, "io", "flush failed", "url", url.Raw());} + } + public void Rls() { + try { + if (zip_stream != null) zip_stream.close(); + if (mem_stream != null) mem_stream.close(); + } + catch (Exception e) {throw Err_.new_exc(e, "io", "close failed", "url", url.Raw());} + } + public byte[] To_ary_and_clear() { + byte[] rv = trg_bfr.To_bry_and_clear(); + this.Rls(); + return rv; + } + } diff --git a/100_core/src/gplx/core/ios/zips/Io_zip_compress_cmd__jre.java b/100_core/src/gplx/core/ios/zips/Io_zip_compress_cmd__jre.java index a27517de8..9eba8b945 100644 --- a/100_core/src/gplx/core/ios/zips/Io_zip_compress_cmd__jre.java +++ b/100_core/src/gplx/core/ios/zips/Io_zip_compress_cmd__jre.java @@ -13,3 +13,60 @@ 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.core.ios.zips; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +import java.io.*; +import java.util.zip.*; +import gplx.core.envs.*; import gplx.core.threads.*; import gplx.core.progs.*; +public class Io_zip_compress_cmd__jre { + public Io_zip_compress_cmd__jre() {} + public Io_zip_compress_cmd__jre Make_new() {return new Io_zip_compress_cmd__jre();} + public byte Exec_hook(gplx.core.progs.Gfo_prog_ui prog_ui, Io_url[] src_urls, Io_url trg_url, String resume_name, long resume_file, long resume_item) { + OutputStream trg_out_stream; + Io_mgr.Instance.CreateDirIfAbsent(trg_url.OwnerDir()); + try {trg_out_stream = new FileOutputStream(trg_url.Xto_api());} + catch (Exception e) {throw Err_.new_exc(e, "io", "trg open failed", "url", trg_url.Raw());} + ZipOutputStream trg_stream = new ZipOutputStream(trg_out_stream); + int len = src_urls.length; + byte[] buffer = new byte[4096]; + for (int i = 0; i < len; ++i) { + Io_url src_url = src_urls[i]; + java.util.zip.ZipEntry trg_entry = new java.util.zip.ZipEntry(src_url.NameAndExt()); + try {trg_stream.putNextEntry(trg_entry);} + catch (Exception e) { + try {trg_stream.close();} + catch (IOException e1) {} + throw Err_.new_exc(e, "io", "zip entry failed", "url", src_url.Raw()); + } + FileInputStream src_stream = null; + try {src_stream = new FileInputStream(new File(src_url.Raw()));} + catch (Exception e) {throw Err_.new_exc(e, "io", "src open failed", "url", src_url.Raw());} + while (true) { // loop over bytes + int read_in_raw = -1; + try {read_in_raw = src_stream.read(buffer);} + catch (Exception e) { + try {src_stream.close();} + catch (IOException e1) {} + throw Err_.new_exc(e, "io", "src read failed", "url", src_url.Raw()); + } + if (read_in_raw < 1) break; + try {trg_stream.write(buffer, 0, read_in_raw);} + catch (Exception e) { + try {src_stream.close();} + catch (IOException e1) {} + throw Err_.new_exc(e, "io", "trg write failed", "url", trg_url.Raw()); + } + } + try { + trg_stream.closeEntry(); + src_stream.close(); + } + catch (Exception e) {throw Err_.new_exc(e, "io", "trg close entry failed", "url", src_url.Raw());} + } + try { + trg_stream.close(); + trg_stream.flush(); + } + catch (Exception e) {throw Err_.new_exc(e, "io", "trg close failed", "url", trg_url.Raw());} + return Gfo_prog_ui_.Status__done; + } +} diff --git a/100_core/src/gplx/core/ios/zips/Io_zip_decompress_cmd.java b/100_core/src/gplx/core/ios/zips/Io_zip_decompress_cmd.java index a27517de8..ec7e5e20b 100644 --- a/100_core/src/gplx/core/ios/zips/Io_zip_decompress_cmd.java +++ b/100_core/src/gplx/core/ios/zips/Io_zip_decompress_cmd.java @@ -13,3 +13,12 @@ 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.core.ios.zips; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +import gplx.core.progs.*; +public interface Io_zip_decompress_cmd { + String Fail_msg(); + Io_zip_decompress_cmd Make_new(); + long Checkpoint__load_by_src_fil(Io_url src_fil); + byte Exec(gplx.core.progs.Gfo_prog_ui prog_ui, Io_url src_fil, Io_url trg_dir, List_adp trg_fils); + void Exec_cleanup(); +} diff --git a/100_core/src/gplx/core/ios/zips/Io_zip_decompress_cmd_.java b/100_core/src/gplx/core/ios/zips/Io_zip_decompress_cmd_.java index a27517de8..eac43f4c3 100644 --- a/100_core/src/gplx/core/ios/zips/Io_zip_decompress_cmd_.java +++ b/100_core/src/gplx/core/ios/zips/Io_zip_decompress_cmd_.java @@ -13,3 +13,7 @@ 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.core.ios.zips; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +public class Io_zip_decompress_cmd_ { + public static Io_zip_decompress_cmd Proto = new Io_zip_decompress_cmd__jre(); +} diff --git a/100_core/src/gplx/core/ios/zips/Io_zip_decompress_cmd__base.java b/100_core/src/gplx/core/ios/zips/Io_zip_decompress_cmd__base.java index a27517de8..1fa2f0108 100644 --- a/100_core/src/gplx/core/ios/zips/Io_zip_decompress_cmd__base.java +++ b/100_core/src/gplx/core/ios/zips/Io_zip_decompress_cmd__base.java @@ -13,3 +13,60 @@ 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.core.ios.zips; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +import gplx.core.progs.*; +public abstract class Io_zip_decompress_cmd__base implements Io_zip_decompress_cmd { + private Io_url checkpoint_url; + private String resume_name; + private long resume_file, resume_item; + private long checkpoint_interval = 32 * Io_mgr.Len_mb, checkpoint_nxt = 0; + public String Fail_msg() {return fail_msg;} private String fail_msg; + public abstract Io_zip_decompress_cmd Make_new(); + private final Bry_bfr bfr = Bry_bfr_.New(); + public long Prog_data_cur() {return resume_file;} + public byte Exec(gplx.core.progs.Gfo_prog_ui prog_ui, Io_url src_fil, Io_url trg_dir, List_adp trg_fils) { + this.Checkpoint__load_by_src_fil(src_fil); + this.checkpoint_nxt = resume_file + checkpoint_interval; + this.fail_msg = null; + + byte status = this.Exec_hook(prog_ui, src_fil, trg_dir, trg_fils, resume_name, resume_file, resume_item); + switch (status) { + case Gfo_prog_ui_.Status__done: + case Gfo_prog_ui_.Status__fail: + this.Exec_cleanup(); + break; + case Gfo_prog_ui_.Status__suspended: + break; + } + return status; + } + protected abstract byte Exec_hook(gplx.core.progs.Gfo_prog_ui prog_ui, Io_url src_fil, Io_url trg_dir, List_adp trg_fils, String resume_name, long resume_file, long resume_item); + public void Exec_cleanup() { + if (checkpoint_url != null) Io_mgr.Instance.DeleteFil(checkpoint_url); + } + public long Checkpoint__load_by_src_fil(Io_url src_fil) { + this.checkpoint_url = src_fil.GenNewExt(".checkpoint"); + this.Checkpoint__load(); + return resume_file; + } + private void Checkpoint__load() { + this.resume_name = null; this.resume_file = resume_item = 0; + byte[] data = Io_mgr.Instance.LoadFilBryOrNull(checkpoint_url); if (data == null) return; + byte[][] lines = Bry_split_.Split_lines(data); if (lines.length != 3) return; + this.resume_name = String_.new_u8(lines[0]); + this.resume_file = Long_.parse_or(String_.new_a7(lines[1]), -1); if (resume_file == -1) return; + this.resume_item = Long_.parse_or(String_.new_a7(lines[2]), -1); + } + public boolean Checkpoint__save(String resume_name, long resume_file, long resume_item) { + if (resume_file < checkpoint_nxt) return false; + bfr.Add_str_u8(resume_name).Add_byte_nl(); + bfr.Add_long_variable(resume_file).Add_byte_nl(); + bfr.Add_long_variable(resume_item); + Io_mgr.Instance.SaveFilBry(checkpoint_url, bfr.To_bry_and_clear()); + this.resume_name = resume_name; + this.resume_file = resume_file; + this.resume_item = resume_item; + checkpoint_nxt += checkpoint_interval; + return true; + } +} diff --git a/100_core/src/gplx/core/ios/zips/Io_zip_decompress_cmd__jre.java b/100_core/src/gplx/core/ios/zips/Io_zip_decompress_cmd__jre.java index a27517de8..a30ad3865 100644 --- a/100_core/src/gplx/core/ios/zips/Io_zip_decompress_cmd__jre.java +++ b/100_core/src/gplx/core/ios/zips/Io_zip_decompress_cmd__jre.java @@ -13,3 +13,92 @@ 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.core.ios.zips; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +import java.io.*; +import java.util.zip.*; +import gplx.core.envs.*; import gplx.core.threads.*; import gplx.core.progs.*; +class Io_zip_decompress_cmd__jre extends Io_zip_decompress_cmd__base { + @Override public Io_zip_decompress_cmd Make_new() {return new Io_zip_decompress_cmd__jre();} + @Override protected byte Exec_hook(gplx.core.progs.Gfo_prog_ui prog_ui, Io_url src_fil, Io_url trg_dir, List_adp trg_fils, String resume_name, long resume_file, long resume_item) { + // open src_zip_stream + FileInputStream src_fil_stream = null; + try {src_fil_stream = new FileInputStream(src_fil.Raw());} + catch (FileNotFoundException e) {throw Err_.new_("ios.zip", "file not found", "path", src_fil.Raw());} + ZipInputStream src_zip_stream = new ZipInputStream(src_fil_stream); + + // init variables for entry loop + boolean resumed = resume_name != null; + long file_cur_in_raw = resumed ? resume_file : 0; + long file_max_in_raw = prog_ui.Prog_data_end(); + + // if no size provided, guess length as 5x orig + if (file_max_in_raw == -1) { + file_max_in_raw = 4 * Io_mgr.Instance.QueryFil(src_fil).Size(); + } + + ZipEntry entry = null; + byte[] buffer = new byte[4096]; + Io_mgr.Instance.CreateDirIfAbsent(trg_dir); // NOTE: assert that trg_dir exists + + try { + while (true) { // loop over entries + entry = src_zip_stream.getNextEntry(); + if (entry == null) break; // no more entries + if (resume_name != null) { // resume_entry_name will be null in most cases + if (String_.Eq(resume_name, entry.getName())) // if resume_entry_name is not null, keep reading until match + resume_name = null; + else + continue; + } + + // get entry name; also convert / to \ for wnt + String entry_name = entry.getName(); + if (Op_sys.Cur().Tid_is_wnt()) entry_name = String_.Replace(entry_name, "/", "\\"); + + // create file + Io_url trg_fil_url = Io_url_.new_any_(trg_dir.GenSubFil(entry_name).Raw()); + Io_url trg_tmp_url = trg_fil_url.GenNewNameAndExt(trg_fil_url.NameAndExt() + ".tmp"); + if (trg_fil_url.Type_fil()) { + // handle resume + long item_in_raw = 0; + if (resume_item > 0) { + src_zip_stream.skip(resume_item); + Io_mgr.Instance.Truncate_fil(trg_tmp_url, resume_item); + item_in_raw = resume_item; + resume_item = 0; + } + FileOutputStream trg_fil_stream = new FileOutputStream(new File(trg_tmp_url.Raw()), resumed); + if (resumed) resumed = false; + boolean loop = true; + while (loop) { // loop over bytes + int read_in_raw = src_zip_stream.read(buffer); if (read_in_raw < 1) break; + trg_fil_stream.write(buffer, 0, read_in_raw); + item_in_raw += read_in_raw; + file_cur_in_raw += read_in_raw; + Checkpoint__save(entry_name, file_cur_in_raw, item_in_raw); + if (prog_ui.Prog_notify_and_chk_if_suspended(file_cur_in_raw, file_max_in_raw)) { + loop = false; + break; + } + } + trg_fil_stream.close(); + if (!loop) return Gfo_prog_ui_.Status__suspended; // manually canceled + Io_mgr.Instance.MoveFil_args(trg_tmp_url, trg_fil_url, true).Exec(); + trg_fils.Add(trg_fil_url); + } + else { + Io_mgr.Instance.CreateDir(trg_fil_url); + } + } + Gfo_evt_mgr_.Pub_val(Io_mgr.Instance, Io_mgr.Evt__fil_created, trg_fils.To_ary(Io_url.class)); + } + catch(IOException e) {throw Err_.new_exc(e, "ios.zip", "error duing unzip", "src", src_fil.Raw(), "trg", trg_dir.Raw());} + finally { + try { + // src_zip_stream.closeEntry(); // TOMBSTONE: takes a long time to close; does not seem to be necessary + src_zip_stream.close(); + } catch (Exception e) {} + } + return Gfo_prog_ui_.Status__done; + } +} diff --git a/100_core/src/gplx/core/ios/zips/Io_zip_mgr.java b/100_core/src/gplx/core/ios/zips/Io_zip_mgr.java index a27517de8..f49f7fb98 100644 --- a/100_core/src/gplx/core/ios/zips/Io_zip_mgr.java +++ b/100_core/src/gplx/core/ios/zips/Io_zip_mgr.java @@ -13,3 +13,11 @@ 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.core.ios.zips; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +public interface Io_zip_mgr { + void Zip_fil(Io_url src_fil, Io_url trg_fil); + byte[] Zip_bry(byte[] src, int bgn, int len); + byte[] Unzip_bry(byte[] src, int bgn, int len); + void Unzip_to_dir(Io_url src_fil, Io_url trg_dir); + void Zip_dir(Io_url src_dir, Io_url trg_fil); +} diff --git a/100_core/src/gplx/core/ios/zips/Io_zip_mgr_base.java b/100_core/src/gplx/core/ios/zips/Io_zip_mgr_base.java index a27517de8..b53be91b2 100644 --- a/100_core/src/gplx/core/ios/zips/Io_zip_mgr_base.java +++ b/100_core/src/gplx/core/ios/zips/Io_zip_mgr_base.java @@ -13,3 +13,106 @@ 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.core.ios.zips; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +import java.io.*; +import java.util.zip.*; +import gplx.core.envs.*; +public class Io_zip_mgr_base implements Io_zip_mgr { + public void Zip_fil(Io_url src_fil, Io_url trg_fil) { + byte[] src_bry = Io_mgr.Instance.LoadFilBry(src_fil); + byte[] trg_bry = Zip_bry(src_bry, 0, src_bry.length); + Io_mgr.Instance.SaveFilBry(trg_fil, trg_bry); + } + public void Zip_dir(Io_url src_dir, Io_url trg_fil) { + try { + byte[] bry = new byte[4096]; + FileOutputStream fil_strm = new FileOutputStream(trg_fil.Raw()); + ZipOutputStream zip_strm = new ZipOutputStream(fil_strm); + Zip_dir__add_dir(zip_strm, bry, "", src_dir, Zip_dir__get_subs(src_dir)); + zip_strm.flush(); + zip_strm.close(); + } catch(IOException e) {Err_.new_exc(e, "io", "error duing zip", "src", src_dir.Raw(), "trg", trg_fil.Raw());} + } + private void Zip_dir__add_dir(ZipOutputStream zip_strm, byte[] bry, String zip_path, Io_url owner_dir, Io_url[] subs) { + int len = subs.length; + for (int i = 0; i < len; i++) { + Io_url sub = subs[i]; + String sub_path = zip_path + sub.NameAndExt_noDirSpr(); + if (sub.Type_dir()) + Zip_dir__add_dir(zip_strm, bry, sub_path + "/", sub, Zip_dir__get_subs(sub)); + else + Zip_dir__add_fil(zip_strm, bry, sub_path, sub); + } + } + private void Zip_dir__add_fil(ZipOutputStream zip_strm, byte[] bry, String zip_path, Io_url fil_url) { + try { + int len; + FileInputStream fil_strm = new FileInputStream(fil_url.Raw()); + zip_strm.putNextEntry(new ZipEntry(zip_path)); + while ((len = fil_strm.read(bry)) > 0) + zip_strm.write(bry, 0, len); + fil_strm.close(); + } catch(IOException e) {throw Err_.new_exc(e, "io", "error duing zip", "src", zip_path);} + } + private Io_url[] Zip_dir__get_subs(Io_url url) { + return Io_mgr.Instance.QueryDir_args(url).DirInclude_().ExecAsUrlAry(); + } + public byte[] Zip_bry(byte[] src, int bgn, int len) { + ByteArrayInputStream src_stream = new ByteArrayInputStream(src, bgn, len); + ByteArrayOutputStream trg_stream = new ByteArrayOutputStream(len); + try { + ZipOutputStream trgZip = new ZipOutputStream(trg_stream); + ZipEntry entry = new ZipEntry("file"); + trgZip.putNextEntry(entry); + int count; + while((count = src_stream.read(tmp, 0, tmpLen)) != -1) { + trgZip.write(tmp, 0, count); + } + trgZip.close(); + } catch(Exception e) {throw Err_.new_wo_type("failed to zip", "err", e.getMessage());} + return trg_stream.toByteArray(); + } + public byte[] Unzip_bry(byte[] src, int bgn, int len) { + ByteArrayInputStream src_stream = new ByteArrayInputStream(src, bgn, len); + ByteArrayOutputStream trg_stream = new ByteArrayOutputStream(len); + try { + ZipInputStream srcZip = new ZipInputStream(src_stream); + int count; + while(srcZip.getNextEntry() != null) { + while ((count = srcZip.read(tmp, 0, tmpLen)) != -1) { + trg_stream.write(tmp, 0, count); + } + } + } catch(Exception e) {throw Err_.new_wo_type("failed to unzip", "err", e.getMessage());} + return trg_stream.toByteArray(); + } + public void Unzip_to_dir(Io_url src_fil, Io_url trg_dir) { + byte[] buffer = new byte[4096]; + try{ + Io_mgr.Instance.CreateDirIfAbsent(trg_dir); + + ZipInputStream zip_strm = new ZipInputStream(new FileInputStream(src_fil.Raw())); + ZipEntry zip_eny = zip_strm.getNextEntry(); + while (zip_eny != null) { + String itm_name = zip_eny.getName(); + if (Op_sys.Cur().Tid_is_wnt()) itm_name = String_.Replace(itm_name, "/", "\\"); + Io_url itm_url = Io_url_.new_any_(trg_dir.GenSubFil(itm_name).Raw()); + Io_mgr.Instance.CreateDirIfAbsent(itm_url.OwnerDir()); // make sure owner dir exists + if (itm_url.Type_fil()) { + Io_mgr.Instance.SaveFilStr_args(itm_url, "").Exec(); + File itm_file = new File(itm_url.Raw()); + FileOutputStream itm_strm = new FileOutputStream(itm_file); + int len; + while ((len = zip_strm.read(buffer)) > 0) + itm_strm.write(buffer, 0, len); + itm_strm.close(); + } + zip_eny = zip_strm.getNextEntry(); + } + zip_strm.closeEntry(); + zip_strm.close(); + } catch(IOException e) {throw Err_.new_exc(e, "io", "error duing unzip", "src", src_fil.Raw(), "trg", trg_dir.Raw());} + } + byte[] tmp = new byte[4096]; int tmpLen = 4096; + public static final Io_zip_mgr Instance = new Io_zip_mgr_base(); +} diff --git a/100_core/src/gplx/core/ios/zips/Io_zip_mgr_mok.java b/100_core/src/gplx/core/ios/zips/Io_zip_mgr_mok.java index a27517de8..bf0cf4f43 100644 --- a/100_core/src/gplx/core/ios/zips/Io_zip_mgr_mok.java +++ b/100_core/src/gplx/core/ios/zips/Io_zip_mgr_mok.java @@ -13,3 +13,22 @@ 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.core.ios.zips; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +public class Io_zip_mgr_mok implements Io_zip_mgr { + public void Zip_fil(Io_url src_fil, Io_url trg_fil) { + byte[] src_bry = Io_mgr.Instance.LoadFilBry(src_fil); + byte[] zip_bry = Zip_bry(src_bry, 0, src_bry.length); + Io_mgr.Instance.SaveFilBry(trg_fil, zip_bry); + } + public void Zip_dir(Io_url src_dir, Io_url trg_fil) {} + public byte[] Zip_bry(byte[] src, int bgn, int len) {return Bry_.Add(Bry_zipped, Bry_.Mid(src, bgn, len));} + public byte[] Unzip_bry(byte[] src, int bgn, int len) { + if (src == Bry_.Empty) return src; + byte[] section = Bry_.Mid(src, bgn, bgn + len); + if (!Bry_.Has_at_bgn(section, Bry_zipped, 0, section.length)) throw Err_.new_wo_type("src not zipped", "section", String_.new_u8(section)); + return Bry_.Mid(section, Bry_zipped.length, section.length); + } + public void Unzip_to_dir(Io_url src_fil, Io_url trg_dir) {} + private static final byte[] Bry_zipped = Bry_.new_a7("zipped:"); + public static final Io_zip_mgr_mok Instance = new Io_zip_mgr_mok(); Io_zip_mgr_mok() {} +} diff --git a/100_core/src/gplx/core/ios/zips/Io_zip_mgr_tst.java b/100_core/src/gplx/core/ios/zips/Io_zip_mgr_tst.java index a27517de8..113d73ba7 100644 --- a/100_core/src/gplx/core/ios/zips/Io_zip_mgr_tst.java +++ b/100_core/src/gplx/core/ios/zips/Io_zip_mgr_tst.java @@ -13,3 +13,17 @@ 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.core.ios.zips; import gplx.*; import gplx.core.*; import gplx.core.ios.*; +import org.junit.*; +public class Io_zip_mgr_tst { + @Test public void Zip_unzip() { + Zip_unzip_tst("abcdefghijklmnopqrstuvwxyz"); + } + private void Zip_unzip_tst(String s) { + Io_zip_mgr zip_mgr = Io_zip_mgr_base.Instance; + byte[] src = Bry_.new_a7(s); + byte[] zip = zip_mgr.Zip_bry(src, 0, src.length); + byte[] unz = zip_mgr.Unzip_bry(zip, 0, zip.length); + Tfds.Eq_ary(src, unz); + } +} diff --git a/100_core/src/gplx/core/js/Js_wtr.java b/100_core/src/gplx/core/js/Js_wtr.java index a27517de8..35bb23c6d 100644 --- a/100_core/src/gplx/core/js/Js_wtr.java +++ b/100_core/src/gplx/core/js/Js_wtr.java @@ -13,3 +13,93 @@ 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.core.js; import gplx.*; import gplx.core.*; +public class Js_wtr { + private final Bry_bfr bfr = Bry_bfr_.Reset(32); + private int arg_idx = 0, ary_idx = 0; + public byte Quote_char() {return quote_char;} public Js_wtr Quote_char_(byte v) {quote_char = v; return this;} private byte quote_char = Byte_ascii.Quote; + public void Clear() {bfr.Clear();} + public String To_str() {return bfr.To_str();} + public String To_str_and_clear() {return bfr.To_str_and_clear();} + public Js_wtr Func_init(String name) {return Func_init(Bry_.new_u8(name));} + public Js_wtr Func_init(byte[] name) { + bfr.Add(name).Add_byte(Byte_ascii.Paren_bgn); + arg_idx = 0; + return this; + } + public Js_wtr Func_term() { + bfr.Add_byte(Byte_ascii.Paren_end).Add_byte_semic(); + return this; + } + public Js_wtr Prm_bry(byte[] bry) { + Prm_spr(); + Write_val(bry); + return this; + } + public Js_wtr Prm_obj_ary(Object[] ary) { + int ary_len = ary.length; + for (int i = 0; i < ary_len; ++i) { + Object itm = ary[i]; + if (i != 0) bfr.Add_byte(Byte_ascii.Comma); + boolean val_needs_quotes = true; + if ( Type_.Eq_by_obj(itm, Bool_.Cls_ref_type) + || Type_.Eq_by_obj(itm, Int_.Cls_ref_type) + || Type_.Eq_by_obj(itm, Long_.Cls_ref_type) + ) { + val_needs_quotes = false; + } + if (val_needs_quotes) + Write_val(Bry_.new_u8(Object_.Xto_str_strict_or_null_mark(itm))); + else + bfr.Add_obj_strict(itm); + } + return this; + } + public Js_wtr Ary_init() { + ary_idx = 0; + bfr.Add_byte(Byte_ascii.Brack_bgn); + return this; + } + public Js_wtr Ary_term() { + bfr.Add_byte(Byte_ascii.Brack_end); + return this; + } + public void Prm_spr() { + if (arg_idx != 0) bfr.Add_byte(Byte_ascii.Comma); + ++arg_idx; + } + private void Ary_spr() { + if (ary_idx != 0) bfr.Add_byte(Byte_ascii.Comma); + ++ary_idx; + } + public Js_wtr Ary_bry(byte[] bry) { + Ary_spr(); + Write_val(bry); + return this; + } + private Js_wtr Write_keyword_return() {bfr.Add(Keyword_return); return this;} + public Js_wtr Write_statement_return_func(String func, Object... args) { + this.Write_keyword_return(); + this.Func_init(func); + this.Prm_obj_ary(args); + this.Func_term(); + return this; + } + public void Write_val(byte[] bry) { + bfr.Add_byte(quote_char); + int len = bry.length; + for (int i = 0; i < len; i++) { + byte b = bry[i]; + switch (b) { + case Byte_ascii.Backslash: // "\" -> "\\"; needed else js will usurp \ as escape; EX: "\&" -> "&"; DATE:2014-06-24 + case Byte_ascii.Quote: + case Byte_ascii.Apos: bfr.Add_byte(Byte_ascii.Backslash).Add_byte(b); break; + case Byte_ascii.Nl: bfr.Add_byte(Byte_ascii.Backslash).Add_byte(Byte_ascii.Ltr_n); break; // "\n" -> "\\n" + case Byte_ascii.Cr: break;// skip + default: bfr.Add_byte(b); break; + } + } + bfr.Add_byte(quote_char); + } + private static final byte[] Keyword_return = Bry_.new_a7("return "); +} diff --git a/100_core/src/gplx/core/js/Js_wtr_tst.java b/100_core/src/gplx/core/js/Js_wtr_tst.java index a27517de8..b4d3053db 100644 --- a/100_core/src/gplx/core/js/Js_wtr_tst.java +++ b/100_core/src/gplx/core/js/Js_wtr_tst.java @@ -13,3 +13,27 @@ 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.core.js; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Js_wtr_tst { + @Before public void Init() {fxt.Clear();} private Js_wtr_fxt fxt = new Js_wtr_fxt(); + @Test public void Basic() { + fxt.Test_write_val_html("abc" , "'abc'"); + fxt.Test_write_val_html("a'b" , "'a\\'b'"); + fxt.Test_write_val_html("a\"b" , "'a\\\"b'"); + fxt.Test_write_val_html("a\nb" , "'a\\nb'"); + fxt.Test_write_val_html("a\rb" , "'ab'"); + fxt.Test_write_val_html("a\\&b" , "'a\\\\&b'"); // PURPOSE: backslashes need to be escaped; need for MathJax and "\&"; PAGE:Electromagnetic_field_tensor; DATE:2014-06-24 + } +} +class Js_wtr_fxt { + private Js_wtr wtr = new Js_wtr(); + public void Clear() { + wtr.Clear(); + wtr.Quote_char_(Byte_ascii.Apos); + } + public void Test_write_val_html(String raw, String expd) { + wtr.Write_val(Bry_.new_u8(raw)); + Tfds.Eq(expd, wtr.To_str_and_clear()); + } +} diff --git a/100_core/src/gplx/core/lists/ComparerAble.java b/100_core/src/gplx/core/lists/ComparerAble.java index a27517de8..c79706826 100644 --- a/100_core/src/gplx/core/lists/ComparerAble.java +++ b/100_core/src/gplx/core/lists/ComparerAble.java @@ -13,3 +13,6 @@ 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.core.lists; import gplx.*; import gplx.core.*; +public interface ComparerAble extends java.util.Comparator {} +// public int compare(Object lhsObj, Object rhsObj) {} diff --git a/100_core/src/gplx/core/lists/ComparerAble_.java b/100_core/src/gplx/core/lists/ComparerAble_.java index a27517de8..49644da0f 100644 --- a/100_core/src/gplx/core/lists/ComparerAble_.java +++ b/100_core/src/gplx/core/lists/ComparerAble_.java @@ -13,3 +13,7 @@ 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.core.lists; import gplx.*; import gplx.core.*; +public class ComparerAble_ { + public static int Compare(ComparerAble comparer, Object lhs, Object rhs) {return comparer.compare(lhs, rhs);} +} diff --git a/100_core/src/gplx/core/lists/EnumerAble.java b/100_core/src/gplx/core/lists/EnumerAble.java index a27517de8..db6a6447c 100644 --- a/100_core/src/gplx/core/lists/EnumerAble.java +++ b/100_core/src/gplx/core/lists/EnumerAble.java @@ -13,3 +13,5 @@ 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.core.lists; import gplx.*; import gplx.core.*; +public interface EnumerAble extends java.lang.Iterable {}//_20110320 diff --git a/100_core/src/gplx/core/lists/Hash_adp_base.java b/100_core/src/gplx/core/lists/Hash_adp_base.java index a27517de8..f819abc84 100644 --- a/100_core/src/gplx/core/lists/Hash_adp_base.java +++ b/100_core/src/gplx/core/lists/Hash_adp_base.java @@ -13,3 +13,38 @@ 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.core.lists; import gplx.*; import gplx.core.*; +public abstract class Hash_adp_base implements Hash_adp { + public boolean Has(Object key) {return Has_base(key);} + public Object Get_by(Object key) {return Fetch_base(key);} + public Object Get_by_or_fail(Object key) {return Get_by_or_fail_base(key);} + public void Add(Object key, Object val) {Add_base(key, val);} + public Hash_adp Add_and_more(Object key, Object val) {Add_base(key, val); return this;} + public void Add_as_key_and_val(Object val) {Add_base(val, val);} + public void Add_if_dupe_use_nth(Object key, Object val) { + Object existing = Fetch_base(key); if (existing != null) Del(key); // overwrite if exists + Add(key, val); + } + public boolean Add_if_dupe_use_1st(Object key, Object val) { + if (Has(key)) return false; + Add(key, val); + return true; + } + @gplx.Virtual public void Del(Object key) {Del_base(key);} + protected Object Get_by_or_fail_base(Object key) { + if (key == null) throw Err_.new_wo_type("key cannot be null"); + if (!Has_base(key)) throw Err_.new_wo_type("key not found", "key", key); + return Fetch_base(key); + } + + // private final java.util.HashMap hash = new java.util.HashMap(); + private final java.util.Hashtable hash = new java.util.Hashtable(); + @gplx.Virtual public int Len() {return hash.size();} + @gplx.Virtual public int Count() {return hash.size();} + @gplx.Virtual public void Clear() {hash.clear();} + @gplx.Virtual protected void Add_base(Object key, Object val) {hash.put(key, val);} + @gplx.Virtual protected void Del_base(Object key) {hash.remove(key);} + @gplx.Virtual protected boolean Has_base(Object key) {return hash.containsKey(key);} + @gplx.Virtual protected Object Fetch_base(Object key) {return hash.get(key);} + @gplx.Virtual public java.util.Iterator iterator() {return hash.values().iterator();} +} diff --git a/100_core/src/gplx/core/lists/Hash_adp_list.java b/100_core/src/gplx/core/lists/Hash_adp_list.java index a27517de8..b8ef24046 100644 --- a/100_core/src/gplx/core/lists/Hash_adp_list.java +++ b/100_core/src/gplx/core/lists/Hash_adp_list.java @@ -13,3 +13,26 @@ 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.core.lists; import gplx.*; import gplx.core.*; +public class Hash_adp_list extends Hash_adp_base { + @gplx.New public List_adp Get_by(Object key) {return ((List_adp)Fetch_base(key));} + public List_adp Get_by_or_new(Object key) { + List_adp rv = Get_by(key); + if (rv == null) { + rv = List_adp_.New(); + Add_base(key, rv); + } + return rv; + } + public void AddInList(Object key, Object val) { + List_adp list = Get_by_or_new(key); + list.Add(val); + } + public void DelInList(Object key, Object val) { + List_adp list = Get_by(key); + if (list == null) return; + list.Del(val); + if (list.Count() == 0) Del(key); + } + public static Hash_adp_list new_() {return new Hash_adp_list();} Hash_adp_list() {} +} diff --git a/100_core/src/gplx/core/lists/Iterator_null.java b/100_core/src/gplx/core/lists/Iterator_null.java index a27517de8..fcf4d1104 100644 --- a/100_core/src/gplx/core/lists/Iterator_null.java +++ b/100_core/src/gplx/core/lists/Iterator_null.java @@ -13,3 +13,10 @@ 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.core.lists; import gplx.*; import gplx.core.*; +public class Iterator_null implements java.util.Iterator { + public boolean hasNext() {return false;} + public Object next() {return null;} + public void remove() {} + public static final Iterator_null Instance = new Iterator_null(); +} diff --git a/100_core/src/gplx/core/lists/Iterator_objAry.java b/100_core/src/gplx/core/lists/Iterator_objAry.java index a27517de8..b91c0e5dc 100644 --- a/100_core/src/gplx/core/lists/Iterator_objAry.java +++ b/100_core/src/gplx/core/lists/Iterator_objAry.java @@ -13,3 +13,11 @@ 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.core.lists; import gplx.*; import gplx.core.*; +public class Iterator_objAry implements java.util.Iterator { + public boolean hasNext() {return ++pos < len;} + public Object next() {return ary[pos];} + public void remove() {pos = -1;} + Object[] ary; int pos = -1; int len = 0; + public Iterator_objAry(Object[] v, int count) {ary = v; len = count;} +} \ No newline at end of file diff --git a/100_core/src/gplx/core/lists/List_adp__getable.java b/100_core/src/gplx/core/lists/List_adp__getable.java index a27517de8..29c7b00b3 100644 --- a/100_core/src/gplx/core/lists/List_adp__getable.java +++ b/100_core/src/gplx/core/lists/List_adp__getable.java @@ -13,3 +13,8 @@ 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.core.lists; import gplx.*; import gplx.core.*; +public interface List_adp__getable { + int Len(); + Object Get_at(int i); +} diff --git a/100_core/src/gplx/core/lists/List_adp_sorter.java b/100_core/src/gplx/core/lists/List_adp_sorter.java index a27517de8..f65b5a3d3 100644 --- a/100_core/src/gplx/core/lists/List_adp_sorter.java +++ b/100_core/src/gplx/core/lists/List_adp_sorter.java @@ -13,3 +13,51 @@ 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.core.lists; import gplx.*; import gplx.core.*; +public class List_adp_sorter { + private ComparerAble comparer = null; + public void Sort(Object[] orig, int origLen) {Sort(orig, origLen, true, null);} + public void Sort(Object[] orig, int origLen, boolean asc, ComparerAble comparer) { + this.comparer = comparer; + Object[] temp = new Object[origLen]; + MergeSort(asc, orig, temp, 0, origLen - 1); + this.comparer = null; + } + void MergeSort(boolean asc, Object[] orig,Object[] temp, int lhs, int rhs) { + if (lhs < rhs) { + int mid = (lhs + rhs) / 2; + MergeSort(asc, orig, temp, lhs, mid); + MergeSort(asc, orig, temp, mid + 1, rhs); + Combine(asc, orig, temp, lhs, mid + 1, rhs); + } + } + private void Combine(boolean asc, Object[] orig, Object[] temp, int lhsPos, int rhsPos, int rhsEnd) { + int lhsEnd = rhsPos - 1; + int tmpPos = lhsPos; + int aryLen = rhsEnd - lhsPos + 1; + + while (lhsPos <= lhsEnd && rhsPos <= rhsEnd) { + int compareVal = 0; + if (comparer != null) + compareVal = ComparerAble_.Compare(comparer, orig[lhsPos], orig[rhsPos]); + else { + Comparable lhsComp = (Comparable)orig[lhsPos]; + compareVal = lhsComp == null ? CompareAble_.Less : lhsComp.compareTo(orig[rhsPos]); + } + if (!asc) compareVal *= -1; + if (compareVal <= CompareAble_.Same) // NOTE: (a) must be < 0; JAVA's String.compareTo returns -number based on position; (b) must be <= else sorting sorted list will change order; EX: sorting (a,1;a,2) on fld0 will switch to (a,2;a,1) + temp[tmpPos++] = orig[lhsPos++]; + else + temp[tmpPos++] = orig[rhsPos++]; + } + + while (lhsPos <= lhsEnd) // Copy rest of first half + temp[tmpPos++] = orig[lhsPos++]; + while (rhsPos <= rhsEnd) // Copy rest of right half + temp[tmpPos++] = orig[rhsPos++]; + for (int i = 0; i < aryLen; i++, rhsEnd--) + orig[rhsEnd] = temp[rhsEnd]; + } + + public static List_adp_sorter new_() {return new List_adp_sorter();} List_adp_sorter() {} +} diff --git a/100_core/src/gplx/core/lists/List_adp_sorter_tst.java b/100_core/src/gplx/core/lists/List_adp_sorter_tst.java index a27517de8..5eca0abe3 100644 --- a/100_core/src/gplx/core/lists/List_adp_sorter_tst.java +++ b/100_core/src/gplx/core/lists/List_adp_sorter_tst.java @@ -13,3 +13,23 @@ 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.core.lists; import gplx.*; import gplx.core.*; +import org.junit.*; +public class List_adp_sorter_tst { + @Test public void Basic() { + Object[] src = new Object[] {0,8,1,7,2,6,3,5,4}; + List_adp_sorter.new_().Sort(src, src.length); + Tfds.Eq_ary(src, Sequential(0, 8)); + } + @Test public void Basic2() { + Object[] src = new Object[] {"0","8","1","7","2","6","3","5","4"}; + List_adp_sorter.new_().Sort(src, src.length); + Tfds.Eq_ary(src, new Object[] {"0","1","2","3","4","5","6","7","8"}); + } + Object[] Sequential(int bgn, int end) { + Object[] rv = new Object[end - bgn + 1]; + for (int i = 0; i < Array_.Len(rv); i++) + rv[i] = i + bgn; + return rv; + } +} diff --git a/100_core/src/gplx/core/lists/StackAdp.java b/100_core/src/gplx/core/lists/StackAdp.java index a27517de8..d73b5db37 100644 --- a/100_core/src/gplx/core/lists/StackAdp.java +++ b/100_core/src/gplx/core/lists/StackAdp.java @@ -13,3 +13,12 @@ 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.core.lists; import gplx.*; import gplx.core.*; +public interface StackAdp extends EnumerAble { + int Count(); + void Clear(); + void Push(Object obj); + Object Pop(); + Object Peek(); + List_adp XtoList(); +} diff --git a/100_core/src/gplx/core/lists/StackAdp_.java b/100_core/src/gplx/core/lists/StackAdp_.java index a27517de8..964520991 100644 --- a/100_core/src/gplx/core/lists/StackAdp_.java +++ b/100_core/src/gplx/core/lists/StackAdp_.java @@ -13,3 +13,27 @@ 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.core.lists; import gplx.*; import gplx.core.*; +public class StackAdp_ { + public static StackAdp new_() {return new StackAdp_base();} +} +class StackAdp_base implements StackAdp { + public Object Peek() {return Peek_base();} + public Object Pop() {return Pop_base();} + public void Push(Object obj) {Push_base(obj);} + public List_adp XtoList() { + List_adp list = List_adp_.New(); + for (Object obj : stack) + list.Add(obj); + // NOTE: dotnet traverses last to first; java: first to last + return list; + } + final java.util.Stack stack = new java.util.Stack(); + public StackAdp_base() {} + public int Count() {return stack.size();} + public void Clear() {stack.clear();} + protected void Push_base(Object obj) {stack.push(obj);} + protected Object Pop_base() {return stack.pop();} + protected Object Peek_base() {return stack.peek();} + public java.util.Iterator iterator() {return stack.iterator();} +} diff --git a/100_core/src/gplx/core/lists/StackAdp_tst.java b/100_core/src/gplx/core/lists/StackAdp_tst.java index a27517de8..99a367b26 100644 --- a/100_core/src/gplx/core/lists/StackAdp_tst.java +++ b/100_core/src/gplx/core/lists/StackAdp_tst.java @@ -13,3 +13,19 @@ 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.core.lists; import gplx.*; import gplx.core.*; +import org.junit.*; +public class StackAdp_tst { + @Test public void XtoList() { + tst_XtoList(1, 2, 3); + } + void tst_XtoList(int... ary) { + StackAdp stack = StackAdp_.new_(); + for (int i : ary) + stack.Push(i); + List_adp list = stack.XtoList(); + int[] actl = (int[])list.To_ary(int.class); + for (int i = 0; i < ary.length; i++) + Tfds.Eq(ary[i], actl[i]); + } +} diff --git a/100_core/src/gplx/core/lists/rings/Ring__long.java b/100_core/src/gplx/core/lists/rings/Ring__long.java index a27517de8..27de7083d 100644 --- a/100_core/src/gplx/core/lists/rings/Ring__long.java +++ b/100_core/src/gplx/core/lists/rings/Ring__long.java @@ -13,3 +13,40 @@ 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.core.lists.rings; import gplx.*; import gplx.core.*; import gplx.core.lists.*; +public class Ring__long { + private final long[] ary; + private final int max; + private int nxt, idx_0; + public Ring__long(int max) { + this.max = max; + this.ary = new long[max]; + } + public int Len() {return len;} private int len; + public void Clear() { + for (int i = 0; i < max; ++i) + ary[i] = 0; + len = nxt = 0; + idx_0 = 0; + } + public long Get_at(int i) { + int idx = idx_0 + i; + if (idx >= max) idx -= max; + return ary[idx]; + } + public void Add(long val) { + ary[nxt] = val; // set ary idx + if (++nxt == max) // increment nxt; if max... + nxt = 0; // ...set to 0; + if (len == max) // set idx_0 + idx_0 = nxt == 0 ? 0 : nxt; + if (len < max) // increment len unless already at max + ++len; + } + public long[] To_ary() { + long[] rv = new long[len]; + for (int i = 0; i < len; ++i) + rv[i] = Get_at(i); + return rv; + } +} diff --git a/100_core/src/gplx/core/lists/rings/Ring__long__tst.java b/100_core/src/gplx/core/lists/rings/Ring__long__tst.java index a27517de8..0735bc495 100644 --- a/100_core/src/gplx/core/lists/rings/Ring__long__tst.java +++ b/100_core/src/gplx/core/lists/rings/Ring__long__tst.java @@ -13,3 +13,27 @@ 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.core.lists.rings; import gplx.*; import gplx.core.*; import gplx.core.lists.*; +import org.junit.*; import gplx.core.tests.*; +public class Ring__long__tst { + private final Ring__long__fxt fxt = new Ring__long__fxt(); + @Test public void Basic__1() {fxt.Clear().Add(1) .Test__to_ary(1);} + @Test public void Basic__2() {fxt.Clear().Add(1, 2) .Test__to_ary(1, 2);} + @Test public void Basic__3() {fxt.Clear().Add(1, 2, 3) .Test__to_ary(1, 2, 3);} + @Test public void Wrap__1() {fxt.Clear().Add(1, 2, 3, 4) .Test__to_ary(2, 3, 4);} + @Test public void Wrap__2() {fxt.Clear().Add(1, 2, 3, 4, 5) .Test__to_ary(3, 4, 5);} + @Test public void Wrap__3() {fxt.Clear().Add(1, 2, 3, 4, 5, 6) .Test__to_ary(4, 5, 6);} +} +class Ring__long__fxt { + private Ring__long ring = new Ring__long(3); + public Ring__long__fxt Clear() {ring.Clear(); return this;} + public Ring__long__fxt Add(long... ary) { + for (long itm : ary) + ring.Add(itm); + return this; + } + public Ring__long__fxt Test__to_ary(long... expd) { + Gftest.Eq__ary(expd, ring.To_ary(), "to_ary"); + return this; + } +} diff --git a/100_core/src/gplx/core/lists/rings/Ring__string.java b/100_core/src/gplx/core/lists/rings/Ring__string.java index a27517de8..e4d735245 100644 --- a/100_core/src/gplx/core/lists/rings/Ring__string.java +++ b/100_core/src/gplx/core/lists/rings/Ring__string.java @@ -13,3 +13,46 @@ 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.core.lists.rings; import gplx.*; import gplx.core.*; import gplx.core.lists.*; +public class Ring__string { + String[] ary = String_.Ary_empty; + public int Len() {return len;} int len; + public Ring__string Max_(int v) { + if (v != max) { + ary = new String[v]; + max = v; + } + return this; + } int max; + public void Clear() { + for (int i = 0; i < max; i++) { + ary[i] = null; + } + len = nxt = 0; + } + int nxt; + public void Push(String v) { + int idx = nxt++; + if (idx == max) { + idx = 0; + } + if (nxt == max) { + nxt = 0; + } + ary[idx] = v; + if (len < max) + ++len; + } + public String[] Xto_str_ary() { + String[] rv = new String[len]; + int ary_i = nxt - 1; + for (int rv_i = len - 1; rv_i > -1; rv_i--) { + if (ary_i == -1) { + ary_i = max - 1; + } + rv[rv_i] = ary[ary_i]; + --ary_i; + } + return rv; + } +} diff --git a/100_core/src/gplx/core/lists/rings/Ring__string__tst.java b/100_core/src/gplx/core/lists/rings/Ring__string__tst.java index a27517de8..aa22a6b71 100644 --- a/100_core/src/gplx/core/lists/rings/Ring__string__tst.java +++ b/100_core/src/gplx/core/lists/rings/Ring__string__tst.java @@ -13,3 +13,32 @@ 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.core.lists.rings; import gplx.*; import gplx.core.*; import gplx.core.lists.*; +import org.junit.*; +public class Ring__string__tst { + private final Ring__string__fxt fxt = new Ring__string__fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Basic() { + fxt.Clear().Max_(3).Push_many("a") .Expd("a"); + fxt.Clear().Max_(3).Push_many("a", "b") .Expd("a", "b"); + fxt.Clear().Max_(3).Push_many("a", "b", "c") .Expd("a", "b", "c"); + fxt.Clear().Max_(3).Push_many("a", "b", "c", "d") .Expd("b", "c", "d"); + fxt.Clear().Max_(3).Push_many("a", "b", "c", "d", "e") .Expd("c", "d", "e"); + fxt.Clear().Max_(3).Push_many("a", "b", "c", "d", "e", "f") .Expd("d", "e", "f"); + } +} +class Ring__string__fxt { + Ring__string ring = new Ring__string(); + public Ring__string__fxt Clear() {ring.Clear(); return this;} + public Ring__string__fxt Max_(int v) {ring.Max_(v); return this;} + public Ring__string__fxt Push_many(String... ary) { + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) + ring.Push(ary[i]); + return this; + } + public Ring__string__fxt Expd(String... expd) { + Tfds.Eq_ary_str(expd, ring.Xto_str_ary()); + return this; + } +} diff --git a/100_core/src/gplx/core/log_msgs/Gfo_msg_data.java b/100_core/src/gplx/core/log_msgs/Gfo_msg_data.java index a27517de8..888313260 100644 --- a/100_core/src/gplx/core/log_msgs/Gfo_msg_data.java +++ b/100_core/src/gplx/core/log_msgs/Gfo_msg_data.java @@ -13,3 +13,20 @@ 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.core.log_msgs; import gplx.*; import gplx.core.*; +public class Gfo_msg_data { + public int Uid() {return uid;} int uid = uid_next++; + public Gfo_msg_itm Item() {return item;} Gfo_msg_itm item; + public Object[] Vals() {return vals;} Object[] vals; + public byte[] Src_bry() {return src_bry;} private byte[] src_bry; + public int Src_bgn() {return src_bgn;} int src_bgn; + public int Src_end() {return src_end;} int src_end; + public Gfo_msg_data Ctor_val_many(Gfo_msg_itm item, Object[] vals) {this.item = item; this.vals = vals; return this;} + public Gfo_msg_data Ctor_src_many(Gfo_msg_itm item, byte[] src_bry, int src_bgn, int src_end, Object[] vals) {this.item = item; this.src_bry = src_bry; this.src_bgn = src_bgn; this.src_end = src_end; this.vals = vals; return this;} + public void Clear() { + item = null; vals = null; src_bry = null; + } + public String Gen_str_ary() {return item.Gen_str_ary(vals);} + static int uid_next = 0; + public static final Gfo_msg_data[] Ary_empty = new Gfo_msg_data[0]; +} diff --git a/100_core/src/gplx/core/log_msgs/Gfo_msg_grp.java b/100_core/src/gplx/core/log_msgs/Gfo_msg_grp.java index a27517de8..c2b9c4603 100644 --- a/100_core/src/gplx/core/log_msgs/Gfo_msg_grp.java +++ b/100_core/src/gplx/core/log_msgs/Gfo_msg_grp.java @@ -13,3 +13,32 @@ 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.core.log_msgs; import gplx.*; import gplx.core.*; +public class Gfo_msg_grp implements Gfo_msg_obj { + public Gfo_msg_grp(Gfo_msg_grp owner, int uid, byte[] key) { + this.owner = owner; this.uid = uid; this.key = key; this.key_str = String_.new_a7(key); + if (owner != null) { + owner.subs.Add(this); + path = Gfo_msg_grp_.Path(owner.path, key); + } + else + path = Bry_.Empty; + } + public void Subs_clear() {subs.Clear();} + public Gfo_msg_grp Owner() {return owner;} Gfo_msg_grp owner; + public int Uid() {return uid;} int uid; + public byte[] Key() {return key;} private byte[] key; + public String Key_str() {return key_str;} private String key_str; + public byte[] Path() {return path;} private byte[] path; + public String Path_str() {return String_.new_a7(path);} + public Gfo_msg_obj Subs_get_by_key(String sub_key) { + int subs_len = subs.Count(); + for (int i = 0; i < subs_len; i++) { + Gfo_msg_obj sub = (Gfo_msg_obj)subs.Get_at(i); + if (String_.Eq(sub_key, sub.Key_str())) return sub; + } + return null; + } + public void Subs_add(Gfo_msg_itm item) {subs.Add(item);} + List_adp subs = List_adp_.New(); +} diff --git a/100_core/src/gplx/core/log_msgs/Gfo_msg_grp_.java b/100_core/src/gplx/core/log_msgs/Gfo_msg_grp_.java index a27517de8..94340792b 100644 --- a/100_core/src/gplx/core/log_msgs/Gfo_msg_grp_.java +++ b/100_core/src/gplx/core/log_msgs/Gfo_msg_grp_.java @@ -13,3 +13,16 @@ 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.core.log_msgs; import gplx.*; import gplx.core.*; +public class Gfo_msg_grp_ { + public static final Gfo_msg_grp Root_gplx = new Gfo_msg_grp(null, Gfo_msg_grp_.Uid_next(), Bry_.new_a7("gplx")); + public static final Gfo_msg_grp Root = new Gfo_msg_grp(null, Gfo_msg_grp_.Uid_next(), Bry_.Empty); + public static Gfo_msg_grp prj_(String key) {return new Gfo_msg_grp(Root , Gfo_msg_grp_.Uid_next(), Bry_.new_a7(key));} + public static Gfo_msg_grp new_(Gfo_msg_grp owner, String key) {return new Gfo_msg_grp(owner , Gfo_msg_grp_.Uid_next(), Bry_.new_a7(key));} + public static int Uid_next() {return uid_next++;} static int uid_next = 0; + public static byte[] Path(byte[] owner_path, byte[] key) { + if (owner_path != Bry_.Empty) tmp_bfr.Add(owner_path).Add_byte(Byte_ascii.Dot); // only add "." if owner_path is available; prevents creating ".gplx" + return tmp_bfr.Add(key).To_bry_and_clear(); + } + static Bry_bfr tmp_bfr = Bry_bfr_.Reset(256); +} diff --git a/100_core/src/gplx/core/log_msgs/Gfo_msg_itm.java b/100_core/src/gplx/core/log_msgs/Gfo_msg_itm.java index a27517de8..38bf5be42 100644 --- a/100_core/src/gplx/core/log_msgs/Gfo_msg_itm.java +++ b/100_core/src/gplx/core/log_msgs/Gfo_msg_itm.java @@ -13,3 +13,44 @@ 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.core.log_msgs; import gplx.*; import gplx.core.*; +import gplx.core.brys.fmtrs.*; +public class Gfo_msg_itm implements Gfo_msg_obj { + public Gfo_msg_itm(Gfo_msg_grp owner, int uid, byte cmd, byte[] key_bry, byte[] fmt, boolean add_to_owner) { + this.owner = owner; this.uid = uid; this.cmd = cmd; this.key_bry = key_bry; this.fmt = fmt; + this.key_str = String_.new_a7(key_bry); + this.path_bry = Gfo_msg_grp_.Path(owner.Path(), key_bry); + if (add_to_owner) owner.Subs_add(this); + } + public Gfo_msg_grp Owner() {return owner;} Gfo_msg_grp owner; + public int Uid() {return uid;} int uid; + public byte[] Path_bry() {return path_bry;} private byte[] path_bry; + public String Path_str() {return String_.new_u8(path_bry);} + public byte[] Key_bry() {return key_bry;} private byte[] key_bry; + public String Key_str() {return key_str;} private String key_str; + public Gfo_msg_obj Subs_get_by_key(String sub_key) {return null;} + public byte Cmd() {return cmd;} private byte cmd; + public byte[] Fmt() {return fmt;} private byte[] fmt; + public Bry_fmtr Fmtr() {if (fmtr == null) fmtr = Bry_fmtr.new_bry_(fmt).Compile(); return fmtr;} Bry_fmtr fmtr; + public String Gen_str_many(Object... vals) {return Gen_str_ary(vals);} + public String Gen_str_ary(Object[] vals) { + if (fmtr == null) fmtr = Bry_fmtr.new_bry_(fmt).Compile(); + if (fmtr.Fmt_args_exist()) { + fmtr.Bld_bfr_many(tmp_bfr, vals); + return tmp_bfr.To_str_and_clear(); + } + else + return String_.new_u8(fmt); + } + public String Gen_str_one(Object val) { + if (fmtr == null) fmtr = Bry_fmtr.new_bry_(fmt).Compile(); + if (fmtr.Fmt_args_exist()) { + fmtr.Bld_bfr_one(tmp_bfr, val); + return tmp_bfr.To_str_and_clear(); + } + else + return String_.new_u8(fmt); + } + public String Gen_str_none() {return key_str;} + static Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); +} diff --git a/100_core/src/gplx/core/log_msgs/Gfo_msg_itm_.java b/100_core/src/gplx/core/log_msgs/Gfo_msg_itm_.java index a27517de8..c84928b51 100644 --- a/100_core/src/gplx/core/log_msgs/Gfo_msg_itm_.java +++ b/100_core/src/gplx/core/log_msgs/Gfo_msg_itm_.java @@ -13,3 +13,13 @@ 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.core.log_msgs; import gplx.*; import gplx.core.*; +public class Gfo_msg_itm_ { + public static final byte Cmd_null = 0, Cmd_log = 1, Cmd_note = 2, Cmd_warn = 3, Cmd_stop = 4, Cmd_fail = 5; + public static final byte[][] CmdBry = new byte[][] {Object_.Bry__null, Bry_.new_a7("log"), Bry_.new_a7("note"), Bry_.new_a7("warn"), Bry_.new_a7("stop"), Bry_.new_a7("fail")}; + public static Gfo_msg_itm new_note_(Gfo_msg_grp owner, String key) {return new_(owner, Cmd_note, key, key);} + public static Gfo_msg_itm new_fail_(Gfo_msg_grp owner, String key, String fmt) {return new_(owner, Cmd_warn, key, fmt);} + public static Gfo_msg_itm new_warn_(Gfo_msg_grp owner, String key) {return new_(owner, Cmd_warn, key, key);} + public static Gfo_msg_itm new_warn_(Gfo_msg_grp owner, String key, String fmt) {return new_(owner, Cmd_warn, key, fmt);} + public static Gfo_msg_itm new_(Gfo_msg_grp owner, byte cmd, String key, String fmt) {return new Gfo_msg_itm(owner, Gfo_msg_grp_.Uid_next(), cmd, Bry_.new_a7(key), Bry_.new_a7(fmt), false);} +} diff --git a/100_core/src/gplx/core/log_msgs/Gfo_msg_log.java b/100_core/src/gplx/core/log_msgs/Gfo_msg_log.java index a27517de8..f8dddb8eb 100644 --- a/100_core/src/gplx/core/log_msgs/Gfo_msg_log.java +++ b/100_core/src/gplx/core/log_msgs/Gfo_msg_log.java @@ -13,3 +13,46 @@ 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.core.log_msgs; import gplx.*; import gplx.core.*; +public class Gfo_msg_log { + public Gfo_msg_log(String root_key) {root = new Gfo_msg_root(root_key);} Gfo_msg_root root; + public int Ary_len() {return ary_idx;} + public Gfo_msg_data Ary_get(int i) {return ary[i];} + public Gfo_msg_log Clear() { + synchronized (this) { // TS: DATE:2016-07-06 + for (int i = 0; i < ary_idx; i++) + ary[i].Clear(); + ary_idx = 0; + return this; + } + } + public Gfo_msg_log Add_str_warn_key_none(String grp, String itm, byte[] src, int pos) {return Add_str(Gfo_msg_itm_.Cmd_warn, grp, itm, null, src, pos, pos + 1, null);} + public Gfo_msg_log Add_str_warn_key_none(String grp, String itm, byte[] src, int bgn, int end) {return Add_str(Gfo_msg_itm_.Cmd_warn, grp, itm, null, src, bgn, end, null);} + public Gfo_msg_log Add_str_warn_fmt_none(String grp, String itm, String fmt) {return Add_str(Gfo_msg_itm_.Cmd_warn, grp, itm, fmt , Bry_.Empty, -1, -1, null);} + public Gfo_msg_log Add_str_warn_fmt_none(String grp, String itm, String fmt, byte[] src, int pos) {return Add_str(Gfo_msg_itm_.Cmd_warn, grp, itm, fmt , src, pos, pos + 1, null);} + public Gfo_msg_log Add_str_warn_fmt_none(String grp, String itm, String fmt, byte[] src, int bgn, int end) {return Add_str(Gfo_msg_itm_.Cmd_warn, grp, itm, fmt , src, bgn, end, null);} + public Gfo_msg_log Add_str_warn_fmt_many(String grp, String itm, String fmt, Object... vals) {return Add_str(Gfo_msg_itm_.Cmd_warn, grp, itm, fmt , Bry_.Empty, -1, -1, vals);} + Gfo_msg_log Add_str(byte cmd, String owner_key, String itm, String fmt, byte[] src, int bgn, int end, Object[] vals) { + synchronized (this) { // TS: DATE:2016-07-06 + if (ary_idx >= ary_max) ary_expand(); + ary[ary_idx++] = root.Data_new_many(cmd, src, bgn, end, owner_key, itm, fmt, vals); + return this; + } + } + public Gfo_msg_log Add_itm_none(Gfo_msg_itm itm, byte[] src, int bgn, int end) {return Add_itm(itm, src, bgn, end, null);} + public Gfo_msg_log Add_itm_many(Gfo_msg_itm itm, byte[] src, int bgn, int end, Object... val_ary) {return Add_itm(itm, src, bgn, end, val_ary);} + Gfo_msg_log Add_itm(Gfo_msg_itm itm, byte[] src, int bgn, int end, Object[] vals) { + synchronized (this) { // TS: DATE:2016-07-06 + if (ary_idx >= ary_max) ary_expand(); + ary[ary_idx++] = root.Data_new_many(itm, src, bgn, end, vals); + return this; + } + } + void ary_expand() { + int new_max = ary_max == 0 ? 2 : ary_max * 2; + ary = (Gfo_msg_data[])Array_.Expand(ary, new Gfo_msg_data[new_max], ary_max); + ary_max = new_max; + } + Gfo_msg_data[] ary = Gfo_msg_data.Ary_empty; int ary_idx, ary_max; + public static Gfo_msg_log Test() {return new Gfo_msg_log("test");} +} diff --git a/100_core/src/gplx/core/log_msgs/Gfo_msg_obj.java b/100_core/src/gplx/core/log_msgs/Gfo_msg_obj.java index a27517de8..145ce45ed 100644 --- a/100_core/src/gplx/core/log_msgs/Gfo_msg_obj.java +++ b/100_core/src/gplx/core/log_msgs/Gfo_msg_obj.java @@ -13,3 +13,8 @@ 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.core.log_msgs; import gplx.*; import gplx.core.*; +public interface Gfo_msg_obj { + String Key_str(); + Gfo_msg_obj Subs_get_by_key(String sub_key); +} diff --git a/100_core/src/gplx/core/log_msgs/Gfo_msg_root.java b/100_core/src/gplx/core/log_msgs/Gfo_msg_root.java index a27517de8..bbafe271c 100644 --- a/100_core/src/gplx/core/log_msgs/Gfo_msg_root.java +++ b/100_core/src/gplx/core/log_msgs/Gfo_msg_root.java @@ -13,3 +13,66 @@ 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.core.log_msgs; import gplx.*; import gplx.core.*; +public class Gfo_msg_root { + public Gfo_msg_root(String root_key) { + this.root_key = root_key; + this.root = Gfo_msg_grp_.new_(Gfo_msg_grp_.Root, root_key); + } String root_key; + public void Data_ary_clear() { + for (int i = 0; i < data_ary_idx; i++) + data_ary[i].Clear(); + data_ary_idx = 0; + } + public void Data_ary_len_(int v) { + data_ary_len = v; + data_ary = new Gfo_msg_data[data_ary_len]; + for (int i = 0; i < data_ary_len; i++) + data_ary[i] = new Gfo_msg_data(); + data_ary_idx = 0; + } int data_ary_len; int data_ary_idx; Gfo_msg_data[] data_ary; + public void Reset() { + root.Subs_clear(); + owners.Clear(); + uid_list_next = uid_item_next = 0; + Data_ary_clear(); + } + public Gfo_msg_data Data_new_note_many(String owner_key, String key, String fmt, Object... vals) {return Data_new_many(Gfo_msg_itm_.Cmd_note, Bry_.Empty, -1, -1, owner_key, key, fmt, vals);} + public Gfo_msg_data Data_new_many(byte cmd, String owner_key, String key, String fmt, Object[] vals) {return Data_new_many(cmd, Bry_.Empty, -1, -1, owner_key, key, fmt, vals);} + public Gfo_msg_data Data_new_many(byte cmd, byte[] src, int bgn, int end, String owner_key, String key, String fmt, Object[] vals) { + Object owner_obj = owners.Get_by(owner_key); + Gfo_msg_grp owner = null; + if (owner_obj == null) { + owner = New_list_by_key(owner_key); + owners.Add(owner_key, owner); + } + else + owner = (Gfo_msg_grp)owner_obj; + Gfo_msg_itm itm = (Gfo_msg_itm)owner.Subs_get_by_key(key); + if (itm == null) + itm = new Gfo_msg_itm(owner, uid_item_next++, cmd, Bry_.new_u8(key), fmt == null ? Bry_.Empty : Bry_.new_a7(fmt), false); + return Data_new_many(itm, src, bgn, end, vals); + } + public Gfo_msg_data Data_new_many(Gfo_msg_itm itm, byte[] src, int bgn, int end, Object... vals) {return Data_get().Ctor_src_many(itm, src, bgn, end, vals);} + public Gfo_msg_data Data_get() { + return data_ary_idx < data_ary_len ? data_ary[data_ary_idx++] : new Gfo_msg_data(); + } + Gfo_msg_grp New_list_by_key(String key) { + String[] segs = String_.Split(key, '.'); + int segs_len = segs.length; int segs_last = segs_len - 1; + Gfo_msg_grp cur_list = root; + for (int i = 0; i < segs_last; i++) { + String seg = segs[i]; + Gfo_msg_grp sub_list = (Gfo_msg_grp)cur_list.Subs_get_by_key(seg); + if (sub_list == null) + sub_list = new Gfo_msg_grp(cur_list, uid_list_next++, Bry_.new_a7(key)); + cur_list = sub_list; + } + return cur_list; + } + Gfo_msg_grp root; + Ordered_hash owners = Ordered_hash_.New(); + int uid_list_next = 0; + int uid_item_next = 0; + public static final Gfo_msg_root Instance = new Gfo_msg_root("gplx"); +} diff --git a/100_core/src/gplx/core/log_msgs/Gfo_msg_root_tst.java b/100_core/src/gplx/core/log_msgs/Gfo_msg_root_tst.java index a27517de8..76bfd0ec9 100644 --- a/100_core/src/gplx/core/log_msgs/Gfo_msg_root_tst.java +++ b/100_core/src/gplx/core/log_msgs/Gfo_msg_root_tst.java @@ -13,3 +13,48 @@ 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.core.log_msgs; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Gfo_msg_root_tst { + Gfo_msg_root_fxt fxt = new Gfo_msg_root_fxt(); + @Before public void setup() {fxt.Reset();} + @Test public void Str() { + fxt.Clear().Expd_data_str_("failed a0 b0").Tst_data_new_many("proj.cls.proc", "err_0", "failed ~{0} ~{1}", "a0", "b0"); + fxt.Clear().Expd_data_str_("failed a1 b1").Tst_data_new_many("proj.cls.proc", "err_0", "failed ~{0} ~{1}", "a1", "b1"); + } +// @Test public void Item() { // DISABLED: no longer registering items with owner; +// fxt.Clear().Expd_item_uid_(0).Expd_item_fmtr_arg_exists_(Bool_.Y).Tst_data_new_many("proj.cls.proc", "err_0", "failed ~{0} ~{1}", "a0", "b0"); +// fxt.Clear().Expd_item_uid_(1).Expd_item_fmtr_arg_exists_(Bool_.N).Tst_data_new_many("proj.cls.proc", "err_1", "failed"); +// fxt.Clear().Expd_item_uid_(0).Tst_data_new_many("proj.cls.proc", "err_0", "failed ~{0} ~{1}", "a0", "b0"); // make sure item_uid stays the same +// } + @Test public void Cache() { + fxt.Mgr().Data_ary_len_(2); + fxt.Clear().Expd_data_uid_(0).Tst_data_new_many("x", "err_0", "a"); + fxt.Clear().Expd_data_uid_(1).Tst_data_new_many("x", "err_0", "b"); + fxt.Clear().Expd_data_uid_(2).Tst_data_new_many("x", "err_0", "a"); + fxt.Mgr().Data_ary_clear(); + fxt.Clear().Expd_data_uid_(0).Tst_data_new_many("x", "err_0", "a"); + } +} +class Gfo_msg_root_fxt { + Gfo_msg_root root = new Gfo_msg_root("tst"); + public Gfo_msg_root_fxt Reset() {root.Reset(); this.Clear(); return this;} + public Gfo_msg_root_fxt Clear() { + expd_item_uid = -1; + expd_item_fmtr_arg_exists = Bool_.__byte; + expd_data_uid = -1; + expd_data_str = null; + return this; + } + public Gfo_msg_root Mgr() {return root;} + public Gfo_msg_root_fxt Expd_data_uid_(int v) {this.expd_data_uid = v; return this;} int expd_data_uid; + public Gfo_msg_root_fxt Expd_data_str_(String v) {this.expd_data_str = v; return this;} private String expd_data_str; + public Gfo_msg_root_fxt Expd_item_uid_(int v) {this.expd_item_uid = v; return this;} int expd_item_uid; + public Gfo_msg_root_fxt Expd_item_fmtr_arg_exists_(boolean v) {this.expd_item_fmtr_arg_exists = v ? Bool_.Y_byte : Bool_.N_byte; return this;} private byte expd_item_fmtr_arg_exists; + public void Tst_data_new_many(String path, String key, String fmt, Object... vals) { + Gfo_msg_data data = root.Data_new_many(Gfo_msg_itm_.Cmd_note, path, key, fmt, vals); + if (expd_item_uid != -1) Tfds.Eq(expd_item_uid, data.Item().Uid());; + if (expd_item_fmtr_arg_exists != Bool_.__byte) Tfds.Eq(Bool_.By_int(expd_item_fmtr_arg_exists), data.Item().Fmtr().Fmt_args_exist()); + if (expd_data_str != null) Tfds.Eq(expd_data_str, data.Item().Gen_str_many(data.Vals())); + } +} diff --git a/100_core/src/gplx/core/logs/Gfo_log__base.java b/100_core/src/gplx/core/logs/Gfo_log__base.java index a27517de8..33d316c35 100644 --- a/100_core/src/gplx/core/logs/Gfo_log__base.java +++ b/100_core/src/gplx/core/logs/Gfo_log__base.java @@ -13,3 +13,30 @@ 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.core.logs; import gplx.*; import gplx.core.*; +public abstract class Gfo_log__base implements Gfo_log { + private long time_prv = 0; + public void Warn(String msg, Object... args) { + long time = gplx.core.envs.System_.Ticks(); + long elapsed = time_prv == 0 ? 0 : time - time_prv; + Exec(Gfo_log_itm.Type__warn, time, elapsed, msg, args); + } + public void Note(String msg, Object... args) { + long time = gplx.core.envs.System_.Ticks(); + long elapsed = time_prv == 0 ? 0 : time - time_prv; + Exec(Gfo_log_itm.Type__note, time, elapsed, msg, args); + } + public void Info(String msg, Object... args) { + long time = gplx.core.envs.System_.Ticks(); + long elapsed = time_prv == 0 ? 0 : time - time_prv; + Exec(Gfo_log_itm.Type__info, time, elapsed, msg, args); + } + public void Prog(String msg, Object... args) { + long time = gplx.core.envs.System_.Ticks(); + long elapsed = time_prv == 0 ? 0 : time - time_prv; + Exec(Gfo_log_itm.Type__prog, time, elapsed, msg, args); + } + public abstract List_adp Itms(); public abstract Gfo_log Itms_(List_adp v); + public abstract void Exec(byte type, long time, long elapsed, String msg, Object[] args); + public abstract void Flush(); +} diff --git a/100_core/src/gplx/core/logs/Gfo_log__file.java b/100_core/src/gplx/core/logs/Gfo_log__file.java index a27517de8..44fecb31a 100644 --- a/100_core/src/gplx/core/logs/Gfo_log__file.java +++ b/100_core/src/gplx/core/logs/Gfo_log__file.java @@ -13,3 +13,49 @@ 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.core.logs; import gplx.*; import gplx.core.*; +public class Gfo_log__file extends Gfo_log__base { + public final Gfo_log_itm_wtr fmtr; + private final Bry_bfr bfr = Bry_bfr_.New(); + public Gfo_log__file(Io_url url, Gfo_log_itm_wtr fmtr) { + this.url = url; this.fmtr = fmtr; + } + public Io_url Url() {return url;} private final Io_url url; + @Override public List_adp Itms() {return itms;} @Override public Gfo_log Itms_(List_adp v) {this.itms = v; return this;} private List_adp itms; + @Override public void Exec(byte type, long time, long elapsed, String msg, Object[] args) { + if (type == Gfo_log_itm.Type__prog) return; + + // add itm + Gfo_log_itm itm = new Gfo_log_itm(type, time, elapsed, msg, args); + itms.Add(itm); + + // flush if warning or failure; needed for download central + switch (type) { + case Gfo_log_itm.Type__note: + case Gfo_log_itm.Type__warn: + case Gfo_log_itm.Type__fail: this.Flush(); break; + } + } + @Override public void Flush() { + int len = itms.Len(); + for (int i = 0; i < len; ++i) { + Gfo_log_itm itm = (Gfo_log_itm)itms.Get_at(i); + fmtr.Write(bfr, itm); + } + byte[] bry = bfr.To_bry_and_clear(); if (bry.length == 0) return; // don't bother writing empty bfr; happens during Xolog.Delete + Io_mgr.Instance.AppendFilByt(url, bry); + itms.Clear(); + } + public static void Delete_old_files(Io_url dir, Gfo_log log) { + Io_url[] fils = Io_mgr.Instance.QueryDir_fils(dir); + int fils_len = fils.length; + if (fils_len < 9) return; // exit if less than 8 files + int cutoff = fils_len - 8; + Array_.Sort(fils); // sort by path + for (int i = 0; i < cutoff; ++i) { + Io_url fil = fils[i]; + log.Info("deleting old log file", "file", fil.Raw()); + Io_mgr.Instance.DeleteFil(fil); + } + } +} diff --git a/100_core/src/gplx/core/logs/Gfo_log__mem.java b/100_core/src/gplx/core/logs/Gfo_log__mem.java index a27517de8..d3c99bd49 100644 --- a/100_core/src/gplx/core/logs/Gfo_log__mem.java +++ b/100_core/src/gplx/core/logs/Gfo_log__mem.java @@ -13,3 +13,12 @@ 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.core.logs; import gplx.*; import gplx.core.*; +public class Gfo_log__mem extends Gfo_log__base { + @Override public List_adp Itms() {return itms;} @Override public Gfo_log Itms_(List_adp v) {this.itms = v; return this;} private List_adp itms = List_adp_.New(); + @Override public void Exec(byte type, long time, long elapsed, String msg, Object[] args) { + Gfo_log_itm itm = new Gfo_log_itm(type, time, elapsed, msg, args); + itms.Add(itm); + } + @Override public void Flush() {} +} diff --git a/100_core/src/gplx/core/logs/Gfo_log_itm.java b/100_core/src/gplx/core/logs/Gfo_log_itm.java index a27517de8..25eaf9332 100644 --- a/100_core/src/gplx/core/logs/Gfo_log_itm.java +++ b/100_core/src/gplx/core/logs/Gfo_log_itm.java @@ -13,3 +13,20 @@ 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.core.logs; import gplx.*; import gplx.core.*; +public class Gfo_log_itm { + public Gfo_log_itm(byte type, long time, long elapsed, String msg, Object[] args) { + this.Type = type; + this.Time = time; + this.Elapsed = elapsed; + this.Msg = msg; + this.Args = args; + } + public final byte Type; + public final long Time; + public final long Elapsed; + public final String Msg; + public final Object[] Args; + + public static final byte Type__fail = 0, Type__warn = 1, Type__note = 2, Type__info = 3, Type__prog = 4; +} diff --git a/100_core/src/gplx/core/logs/Gfo_log_itm_wtr.java b/100_core/src/gplx/core/logs/Gfo_log_itm_wtr.java index a27517de8..fb4eebb3f 100644 --- a/100_core/src/gplx/core/logs/Gfo_log_itm_wtr.java +++ b/100_core/src/gplx/core/logs/Gfo_log_itm_wtr.java @@ -13,3 +13,7 @@ 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.core.logs; import gplx.*; import gplx.core.*; +public interface Gfo_log_itm_wtr { + void Write(Bry_bfr bfr, Gfo_log_itm itm); +} diff --git a/100_core/src/gplx/core/logs/Gfo_log_itm_wtr__csv.java b/100_core/src/gplx/core/logs/Gfo_log_itm_wtr__csv.java index a27517de8..a1ea61ad6 100644 --- a/100_core/src/gplx/core/logs/Gfo_log_itm_wtr__csv.java +++ b/100_core/src/gplx/core/logs/Gfo_log_itm_wtr__csv.java @@ -13,3 +13,56 @@ 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.core.logs; import gplx.*; import gplx.core.*; +public class Gfo_log_itm_wtr__csv implements Gfo_log_itm_wtr { + private static final byte[] Type__info = Bry_.new_a7("INFO"), Type__note = Bry_.new_a7("NOTE"), Type__warn = Bry_.new_a7("WARN"); + private String time_fmt = "yyyyMMdd_HHmmss.fff"; + public void Write(Bry_bfr bfr, Gfo_log_itm itm) { + bfr.Add_str_a7(Int_.To_str_pad_bgn_space((int)itm.Elapsed, 6)).Add_byte_pipe(); + bfr.Add_str_a7(DateAdp_.unixtime_utc_ms_(itm.Time).XtoStr_fmt(time_fmt)).Add_byte_pipe(); + byte[] type = null; + switch (itm.Type) { + case Gfo_log_itm.Type__info: type = Type__info; break; + case Gfo_log_itm.Type__note: type = Type__note; break; + case Gfo_log_itm.Type__warn: type = Type__warn; break; + } + bfr.Add(type).Add_byte_pipe(); + Escape_str(bfr, itm.Msg); bfr.Add_byte_pipe(); + Object[] args = itm.Args; + int args_len = args.length; + for (int i = 0; i < args_len; i += 2) { + Object key = args[i]; + int val_idx = i + 1; + Object val = i < val_idx ? args[val_idx] : "<<>>"; + Escape_str(bfr, Object_.Xto_str_strict_or_null_mark(key)); bfr.Add_byte_eq(); + Escape_str(bfr, Object_.Xto_str_strict_or_null_mark(val)); bfr.Add_byte_pipe(); + } + bfr.Add_byte_nl(); + } + private void Escape_str(Bry_bfr bfr, String str) { + byte[] bry = Bry_.new_u8(str); + int len = bry.length; + boolean dirty = false; + for (int i = 0; i < len; ++i) { + byte b = bry[i]; + byte escape_byte = Byte_ascii.Null; + switch (b) { + case Byte_ascii.Pipe: escape_byte = Byte_ascii.Ltr_p; break; + case Byte_ascii.Nl: escape_byte = Byte_ascii.Ltr_n; break; + case Byte_ascii.Tick: escape_byte = Byte_ascii.Tick; break; + default: + if (dirty) + bfr.Add_byte(b); + break; + } + if (escape_byte != Byte_ascii.Null) { + if (!dirty) { + dirty = true; + bfr.Add_mid(bry, 0, i); + } + bfr.Add_byte(Byte_ascii.Tick).Add_byte(escape_byte); + } + } + if (!dirty) bfr.Add(bry); + } +} diff --git a/100_core/src/gplx/core/memorys/Gfo_memory_itm.java b/100_core/src/gplx/core/memorys/Gfo_memory_itm.java index a27517de8..ff753bd5f 100644 --- a/100_core/src/gplx/core/memorys/Gfo_memory_itm.java +++ b/100_core/src/gplx/core/memorys/Gfo_memory_itm.java @@ -13,3 +13,7 @@ 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.core.memorys; import gplx.*; import gplx.core.*; +public interface Gfo_memory_itm { + void Rls_mem(); +} diff --git a/100_core/src/gplx/core/memorys/Gfo_memory_mgr.java b/100_core/src/gplx/core/memorys/Gfo_memory_mgr.java index a27517de8..b92685765 100644 --- a/100_core/src/gplx/core/memorys/Gfo_memory_mgr.java +++ b/100_core/src/gplx/core/memorys/Gfo_memory_mgr.java @@ -13,3 +13,19 @@ 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.core.memorys; import gplx.*; import gplx.core.*; +public class Gfo_memory_mgr { + private final List_adp list = List_adp_.New(); + public void Reg_safe(Gfo_memory_itm itm) {synchronized (list) {Reg_fast(itm);}} + public void Reg_fast(Gfo_memory_itm itm) {list.Add(itm);} + public void Rls_safe() {synchronized (list) {Rls_fast();}} + public void Rls_fast() { + int len = list.Len(); + for (int i = 0; i < len; ++i) { + Gfo_memory_itm itm = (Gfo_memory_itm)list.Get_at(i); + itm.Rls_mem(); + } + list.Clear(); + } + public static final Gfo_memory_mgr Instance = new Gfo_memory_mgr(); Gfo_memory_mgr() {} +} diff --git a/100_core/src/gplx/core/primitives/Bool_obj_ref.java b/100_core/src/gplx/core/primitives/Bool_obj_ref.java index a27517de8..e1f8a9c87 100644 --- a/100_core/src/gplx/core/primitives/Bool_obj_ref.java +++ b/100_core/src/gplx/core/primitives/Bool_obj_ref.java @@ -13,3 +13,22 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Bool_obj_ref { + public boolean Val() {return val;} private boolean val; + public boolean Val_y() {return val;} + public boolean Val_n() {return !val;} + public String Val_as_str_yn() {return Yn.To_str(val);} + public Bool_obj_ref Val_y_() {val = true; return this;} + public Bool_obj_ref Val_n_() {val = false; return this;} + public Bool_obj_ref Val_(boolean v) {val = v; return this;} + public Bool_obj_ref Val_toggle_() {val = !val; return this;} + @Override public String toString() {return Bool_.To_str_lower(val);} + public static Bool_obj_ref n_() {return new_(false);} + public static Bool_obj_ref y_() {return new_(true);} + public static Bool_obj_ref new_(boolean val) { + Bool_obj_ref rv = new Bool_obj_ref(); + rv.val = val; + return rv; + } Bool_obj_ref() {} +} diff --git a/100_core/src/gplx/core/primitives/Bool_obj_val.java b/100_core/src/gplx/core/primitives/Bool_obj_val.java index a27517de8..8955126f4 100644 --- a/100_core/src/gplx/core/primitives/Bool_obj_val.java +++ b/100_core/src/gplx/core/primitives/Bool_obj_val.java @@ -13,3 +13,20 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Bool_obj_val { + Bool_obj_val(int v) {val = v;} private final int val; + public boolean Val() {return val == 1;} + public static final Bool_obj_val + Null = new Bool_obj_val(-1) + , False = new Bool_obj_val(0) + , True = new Bool_obj_val(1) + ; + public static Bool_obj_val read_(Object o) {String s = String_.as_(o); return s == null ? (Bool_obj_val)o : parse(s);} + public static Bool_obj_val parse(String raw) { + if (String_.Eq(raw, "y")) return Bool_obj_val.True; + else if (String_.Eq(raw, "n")) return Bool_obj_val.False; + else if (String_.Eq(raw, "")) return Bool_obj_val.Null; + else throw Err_.new_parse_type(Bool_obj_val.class, raw); + } +} diff --git a/100_core/src/gplx/core/primitives/Bry_obj_ref.java b/100_core/src/gplx/core/primitives/Bry_obj_ref.java index a27517de8..4bee657e9 100644 --- a/100_core/src/gplx/core/primitives/Bry_obj_ref.java +++ b/100_core/src/gplx/core/primitives/Bry_obj_ref.java @@ -13,3 +13,31 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +import gplx.core.brys.*; +public class Bry_obj_ref implements gplx.core.brys.Bfr_arg { + public byte[] Val() {return val;} private byte[] val; + public int Val_bgn() {return val_bgn;} private int val_bgn; + public int Val_end() {return val_end;} private int val_end; + public boolean Val_is_empty() {return val_bgn == val_end;} + public Bry_obj_ref Val_(byte[] val) {this.val = val; this.val_bgn = 0; this.val_end = val == null ? 0 : val.length; return this;} + public Bry_obj_ref Mid_(byte[] val, int val_bgn, int val_end) {this.val = val; this.val_bgn = val_bgn; this.val_end = val_end; return this;} + @Override public int hashCode() {return CalcHashCode(val, val_bgn, val_end);} + @Override public boolean equals(Object obj) { + if (obj == null) return false; // NOTE: strange, but null check needed; throws null error; EX.WP: File:Eug�ne Delacroix - La libert� guidant le peuple.jpg + Bry_obj_ref comp = (Bry_obj_ref)obj; + return Bry_.Match(val, val_bgn, val_end, comp.val, comp.val_bgn, comp.val_end); + } + public void Bfr_arg__add(Bry_bfr bfr) { + bfr.Add_mid(val, val_bgn, val_end); + } + public static int CalcHashCode(byte[] ary, int bgn, int end) { + int rv = 0; + for (int i = bgn; i < end; i++) + rv = (31 * rv) + ary[i]; + return rv; + } + public static Bry_obj_ref New_empty() {return New(Bry_.Empty);} + public static Bry_obj_ref New(byte[] val) {return new Bry_obj_ref().Val_(val);} + public static Bry_obj_ref New(String val) {return new Bry_obj_ref().Val_(Bry_.new_u8(val));} +} diff --git a/100_core/src/gplx/core/primitives/Byte_obj_ref.java b/100_core/src/gplx/core/primitives/Byte_obj_ref.java index a27517de8..f242ca39c 100644 --- a/100_core/src/gplx/core/primitives/Byte_obj_ref.java +++ b/100_core/src/gplx/core/primitives/Byte_obj_ref.java @@ -13,3 +13,17 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Byte_obj_ref { + public byte Val() {return val;} private byte val; + public Byte_obj_ref Val_(byte v) {val = v; return this;} + @Override public int hashCode() {return val;} + @Override public boolean equals(Object obj) {return obj == null ? false : val == ((Byte_obj_ref)obj).Val();} + @Override public String toString() {return Int_.To_str(val);} + public static Byte_obj_ref zero_() {return new_(Byte_.Zero);} + public static Byte_obj_ref new_(byte val) { + Byte_obj_ref rv = new Byte_obj_ref(); + rv.val = val; + return rv; + } private Byte_obj_ref() {} +} diff --git a/100_core/src/gplx/core/primitives/Byte_obj_val.java b/100_core/src/gplx/core/primitives/Byte_obj_val.java index a27517de8..1a1903de0 100644 --- a/100_core/src/gplx/core/primitives/Byte_obj_val.java +++ b/100_core/src/gplx/core/primitives/Byte_obj_val.java @@ -13,3 +13,15 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Byte_obj_val { + public byte Val() {return val;} private byte val; + @Override public String toString() {return Int_.To_str(val);} + @Override public int hashCode() {return val;} + @Override public boolean equals(Object obj) {return obj == null ? false : val == ((Byte_obj_val)obj).Val();} + public static Byte_obj_val new_(byte val) { + Byte_obj_val rv = new Byte_obj_val(); + rv.val = val; + return rv; + } private Byte_obj_val() {} +} diff --git a/100_core/src/gplx/core/primitives/Double_obj_val.java b/100_core/src/gplx/core/primitives/Double_obj_val.java index a27517de8..43d105589 100644 --- a/100_core/src/gplx/core/primitives/Double_obj_val.java +++ b/100_core/src/gplx/core/primitives/Double_obj_val.java @@ -13,3 +13,18 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Double_obj_val implements CompareAble { + public double Val() {return val;} double val; + @Override public String toString() {return Double_.To_str(val);} + @Override public int hashCode() {return (int)val;} + @Override public boolean equals(Object obj) {return obj == null ? false : val == ((Double_obj_val)obj).Val();} + public int compareTo(Object obj) {Double_obj_val comp = (Double_obj_val)obj; return Double_.Compare(val, comp.val);} + public static Double_obj_val neg1_() {return new_(-1);} + public static Double_obj_val zero_() {return new_(0);} + public static Double_obj_val new_(double val) { + Double_obj_val rv = new Double_obj_val(); + rv.val = val; + return rv; + } Double_obj_val() {} +} diff --git a/100_core/src/gplx/core/primitives/EnmMgr.java b/100_core/src/gplx/core/primitives/EnmMgr.java index a27517de8..cda0e19cc 100644 --- a/100_core/src/gplx/core/primitives/EnmMgr.java +++ b/100_core/src/gplx/core/primitives/EnmMgr.java @@ -13,3 +13,58 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +import gplx.core.strings.*; +public class EnmMgr { + public String BitRngSpr() {return bitRngSpr;} public EnmMgr BitRngSpr_(String val) {bitRngSpr = val; return this;} private String bitRngSpr = "+"; + public String Prefix() {return prefix;} public EnmMgr Prefix_(String val) {prefix = val; return this;} private String prefix; + public int BitRngBgn() {return bitRngBgn;} public EnmMgr BitRngBgn_(int val) {bitRngBgn = val; return this;} int bitRngBgn = 1; + public int BitRngEnd() {return bitRngEnd;} public EnmMgr BitRngEnd_(int val) {bitRngEnd = val; return this;} int bitRngEnd = Int_.Max_value; + public void RegObj(int val, String raw, Object o) { + rawRegy.Add(raw, val); + valRegy.Add(val, raw); + objRegy.Add(val, o); + } + public Object Get(int val) {return objRegy.Get_by(val);} + public int GetVal(String raw) { + String[] ary = String_.Split(raw, bitRngSpr); + int rv = 0; + for (int i = 0; i < ary.length; i++) { + String term = String_.Trim(ary[i]); // ex: key.ctrl + key.a + if (prefix != null) term = String_.Replace(term, prefix, ""); + int cur = -1; + if (String_.Has_at_bgn(term, "#")) + cur = Int_.Parse(String_.Mid(term, 1)); + else + cur = Int_.Cast(rawRegy.Get_by(term)); + rv |= cur; + } + return rv; + } + public String GetStr(int v) { + String_bldr sb = String_bldr_.new_(); + int cur = v, curModifier = bitRngBgn; + while (true) { + if (cur == 0 + || curModifier > bitRngEnd // loop until all Modifers have been shifted out + ) break; + if ((cur & curModifier) == curModifier) { // cur has Modifier + AppendRaw(sb, curModifier); + cur ^= curModifier; // shift Modifier out + } + curModifier *= 2; // move to next Modifier; relies on Shift, Ctrl, Alt enum values + } + if (cur > 0 // cur is non-Modifier; NOTE: check needed for args that are just a Modifier; + || sb.Count() == 0) // cur is IptKey.None; cur == 0, but sb.length will also be 0 + AppendRaw(sb, cur); + return sb.To_str(); + } + void AppendRaw(String_bldr sb, int key) { + String raw = (String)valRegy.Get_by(key); + if (sb.Count() > 0) sb.Add(bitRngSpr); + if (prefix != null) sb.Add(prefix); + sb.Add(raw); + } + Hash_adp rawRegy = Hash_adp_.New(), valRegy = Hash_adp_.New(), objRegy = Hash_adp_.New(); + public static EnmMgr new_() {return new EnmMgr();} EnmMgr() {} +} diff --git a/100_core/src/gplx/core/primitives/EnmParser_tst.java b/100_core/src/gplx/core/primitives/EnmParser_tst.java index a27517de8..b8480ac6f 100644 --- a/100_core/src/gplx/core/primitives/EnmParser_tst.java +++ b/100_core/src/gplx/core/primitives/EnmParser_tst.java @@ -13,3 +13,46 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +import org.junit.*; +public class EnmParser_tst { + @Before public void setup() { + parser = EnmMgr.new_(); + } + @Test public void Basic() { // 1,2,4,8 + parser.BitRngEnd_(8); + run_Reg(0, "zero"); + run_Reg(1, "one"); + run_Reg(2, "two"); + run_Reg(4, "four"); + run_Reg(8, "eight"); + + tst_Convert("zero", 0); + tst_Convert("one", 1); + tst_Convert("eight", 8); + tst_Convert("one+eight", 9); + } + @Test public void Keys() { + parser.BitRngBgn_(65536).BitRngEnd_(262144); + run_Reg( 65, "a"); + run_Reg( 65536, "shift"); + run_Reg(131072, "ctrl"); + run_Reg(262144, "alt"); + tst_Convert("a", 65); + tst_Convert("shift+a", 65 + 65536); + tst_Convert("ctrl+a", 65 + 131072); + tst_Convert("shift+ctrl+a", 65 + 65536 + 131072); + } + @Test public void Prefix() { + parser.Prefix_("key.").BitRngBgn_(128).BitRngEnd_(128); + run_Reg(65, "a"); + tst_Convert("key.a", 65); + } + void run_Reg(int i, String s) {parser.RegObj(i, s, "NULL");} + void tst_Convert(String raw, int val) { + int actlVal = parser.GetVal(raw); + Tfds.Eq(val, actlVal); + Tfds.Eq(raw, parser.GetStr(val)); + } + EnmMgr parser; +} diff --git a/100_core/src/gplx/core/primitives/Int_list.java b/100_core/src/gplx/core/primitives/Int_list.java index a27517de8..f29d701e5 100644 --- a/100_core/src/gplx/core/primitives/Int_list.java +++ b/100_core/src/gplx/core/primitives/Int_list.java @@ -13,3 +13,41 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Int_list { + private int capacity = 0; + private int[] ary = Int_ary_.Empty; private int ary_len, ary_max; + public Int_list() {this.capacity = 0; this.ary = Int_ary_.Empty;} + public Int_list(int capacity) {this.capacity = capacity; this.ary = new int[capacity];} + public void Add(int uid) { + int new_len = ary_len + 1; + if (new_len > ary_max) { + ary_max += 16; + int[] new_ary = new int[ary_max]; + Int_ary_.Copy_to(ary, ary_len, new_ary); + ary = new_ary; + } + ary[ary_len] = uid; + ary_len = new_len; + } + public int Len() {return ary_len;} + public int Get_at(int i) {return ary[i];} + public void Clear() { + if (ary_len > capacity) { + ary = (capacity == 0) ? Int_ary_.Empty : new int[capacity]; + } + ary_len = ary_max = 0; + } + public int[] To_ary() { + int[] rv = new int[ary_len]; + for (int i = 0; i < ary_len; i++) + rv[i] = ary[i]; + return rv; + } + public static Int_list new_(int... ary) { + Int_list rv = new Int_list(); + int len = ary.length; + rv.ary = ary; rv.ary_len = len; rv.ary_max = len; + return rv; + } +} diff --git a/100_core/src/gplx/core/primitives/Int_obj_ref.java b/100_core/src/gplx/core/primitives/Int_obj_ref.java index a27517de8..d09a62776 100644 --- a/100_core/src/gplx/core/primitives/Int_obj_ref.java +++ b/100_core/src/gplx/core/primitives/Int_obj_ref.java @@ -13,3 +13,23 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Int_obj_ref { + Int_obj_ref(int val) {this.val = val;} + public int Val() {return val;} public Int_obj_ref Val_(int v) {val = v; return this;} int val; + public int Val_add() {val++; return val;} + public int Val_add_post() {return val++;} + public int Val_add_pre() {return ++val;} + public int Val_add(int v) {val += v; return val;} + public Int_obj_ref Val_zero_() {val = 0; return this;} + public Int_obj_ref Val_neg1_() {val = -1; return this;} + public String Val_as_str() {return Int_.To_str(val);} + + @Override public String toString() {return Int_.To_str(val);} + @Override public int hashCode() {return val;} + @Override public boolean equals(Object obj) {return val == ((Int_obj_ref)obj).Val();} + + public static Int_obj_ref New_neg1() {return new Int_obj_ref(-1);} + public static Int_obj_ref New_zero() {return new Int_obj_ref(0);} + public static Int_obj_ref New(int val) {return new Int_obj_ref(val);} +} diff --git a/100_core/src/gplx/core/primitives/Int_obj_val.java b/100_core/src/gplx/core/primitives/Int_obj_val.java index a27517de8..efd806346 100644 --- a/100_core/src/gplx/core/primitives/Int_obj_val.java +++ b/100_core/src/gplx/core/primitives/Int_obj_val.java @@ -13,3 +13,12 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Int_obj_val implements CompareAble { + public Int_obj_val(int val) {this.val = val;} + public int Val() {return val;} private final int val; + @Override public String toString() {return Int_.To_str(val);} + @Override public int hashCode() {return val;} + @Override public boolean equals(Object obj) {return obj == null ? false : val == ((Int_obj_val)obj).Val();} + public int compareTo(Object obj) {Int_obj_val comp = (Int_obj_val)obj; return Int_.Compare(val, comp.val);} +} diff --git a/100_core/src/gplx/core/primitives/String_obj_ref.java b/100_core/src/gplx/core/primitives/String_obj_ref.java index a27517de8..2552d703f 100644 --- a/100_core/src/gplx/core/primitives/String_obj_ref.java +++ b/100_core/src/gplx/core/primitives/String_obj_ref.java @@ -13,3 +13,16 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class String_obj_ref { + public String Val() {return val;} public String_obj_ref Val_(String v) {val = v; return this;} private String val; + public String_obj_ref Val_null_() {return Val_(null);} + @Override public String toString() {return val;} + public static String_obj_ref empty_() {return new_("");} + public static String_obj_ref null_() {return new_(null);} + public static String_obj_ref new_(String val) { + String_obj_ref rv = new String_obj_ref(); + rv.val = val; + return rv; + } String_obj_ref() {} +} diff --git a/100_core/src/gplx/core/primitives/String_obj_val.java b/100_core/src/gplx/core/primitives/String_obj_val.java index a27517de8..1a6927a17 100644 --- a/100_core/src/gplx/core/primitives/String_obj_val.java +++ b/100_core/src/gplx/core/primitives/String_obj_val.java @@ -13,3 +13,13 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class String_obj_val implements CompareAble { + public String Val() {return val;} private final String val; + @Override public String toString() {return val;} + public int compareTo(Object obj) { + String_obj_val comp = (String_obj_val)obj; + return String_.Compare(val, comp.val); + } + public static String_obj_val new_(String val) {return new String_obj_val(val);} String_obj_val(String val) {this.val = val;} +} diff --git a/100_core/src/gplx/core/progs/Gfo_prog_ui.java b/100_core/src/gplx/core/progs/Gfo_prog_ui.java index a27517de8..23b8c95cd 100644 --- a/100_core/src/gplx/core/progs/Gfo_prog_ui.java +++ b/100_core/src/gplx/core/progs/Gfo_prog_ui.java @@ -13,3 +13,12 @@ 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.core.progs; import gplx.*; import gplx.core.*; +public interface Gfo_prog_ui extends Cancelable { + byte Prog_status(); + void Prog_status_(byte v); + long Prog_data_cur(); + long Prog_data_end(); + boolean Prog_notify_and_chk_if_suspended(long cur, long max); + void Prog_notify_by_msg(String msg); +} diff --git a/100_core/src/gplx/core/progs/Gfo_prog_ui_.java b/100_core/src/gplx/core/progs/Gfo_prog_ui_.java index a27517de8..39fa6d8e5 100644 --- a/100_core/src/gplx/core/progs/Gfo_prog_ui_.java +++ b/100_core/src/gplx/core/progs/Gfo_prog_ui_.java @@ -13,3 +13,35 @@ 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.core.progs; import gplx.*; import gplx.core.*; +public class Gfo_prog_ui_ { + public static final Gfo_prog_ui Noop = new Gfo_prog_ui__noop(), Always = new Gfo_prog_ui__always(); + public static final byte + Status__init = 1 + , Status__working = 2 + , Status__done = 4 + , Status__fail = 8 + , Status__suspended = 16 + , Status__runnable = Status__init | Status__suspended | Status__fail + ; +} +class Gfo_prog_ui__noop implements Gfo_prog_ui { + public boolean Canceled() {return true;} + public void Cancel() {} + public byte Prog_status() {return Gfo_prog_ui_.Status__init;} + public void Prog_status_(byte v) {} + public long Prog_data_cur() {return 0;} + public long Prog_data_end() {return 0;} + public boolean Prog_notify_and_chk_if_suspended(long cur, long max) {return false;} + public void Prog_notify_by_msg(String msg) {} +} +class Gfo_prog_ui__always implements Gfo_prog_ui { + public boolean Canceled() {return false;} + public void Cancel() {} + public byte Prog_status() {return Gfo_prog_ui_.Status__init;} + public void Prog_status_(byte v) {} + public long Prog_data_cur() {return 0;} + public long Prog_data_end() {return 0;} + public boolean Prog_notify_and_chk_if_suspended(long cur, long max) {return false;} + public void Prog_notify_by_msg(String msg) {} +} diff --git a/100_core/src/gplx/core/progs/Gfo_resume_wkr.java b/100_core/src/gplx/core/progs/Gfo_resume_wkr.java index a27517de8..f6d96172b 100644 --- a/100_core/src/gplx/core/progs/Gfo_resume_wkr.java +++ b/100_core/src/gplx/core/progs/Gfo_resume_wkr.java @@ -13,3 +13,39 @@ 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.core.progs; import gplx.*; import gplx.core.*; +public interface Gfo_resume_wkr { + boolean Resuming(); + long Get_long(byte tid); + void Set_long(byte tid, long v); +} +class Gfo_resume_wkr__download { + public boolean Resuming() {return resuming;} private boolean resuming = true; + public long Get_long(byte tid) {return val;} private long val; + public void Set_long(byte tid, long v) {val = v;} +} +class Gfo_resume_wkr__unzip { + public boolean Resuming() {return resuming;} private boolean resuming = true; + public long Get_long(byte tid) {return val;} private long val; + public void Set_long(byte tid, long v) {val = v;} + public String Get_str(byte tid) {return name;} private String name; + public void Set_str(byte tid, String v) {this.name = v;} +} +/* +[ + { "job_uid": + , "subs": + [ + { "download" + , "done": 0 + , "resume_bytes":123 + } + , { "unzip" + , "done": 0 + , "resume_file":abc + , "resume_bytes":123 + } + ] + } +] +*/ diff --git a/100_core/src/gplx/core/security/Hash_algo.java b/100_core/src/gplx/core/security/Hash_algo.java index a27517de8..be936e31a 100644 --- a/100_core/src/gplx/core/security/Hash_algo.java +++ b/100_core/src/gplx/core/security/Hash_algo.java @@ -13,3 +13,11 @@ 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.core.security; import gplx.*; import gplx.core.*; +public interface Hash_algo { + String Key(); + byte[] Hash_bry_as_bry(byte[] src); + String Hash_bry_as_str(byte[] src); + String Hash_stream_as_str(gplx.core.consoles.Console_adp console, gplx.core.ios.streams.IoStream src_stream); + byte[] Hash_stream_as_bry(gplx.core.progs.Gfo_prog_ui prog_ui, gplx.core.ios.streams.IoStream src_stream); +} diff --git a/100_core/src/gplx/core/security/Hash_algo_.java b/100_core/src/gplx/core/security/Hash_algo_.java index a27517de8..e6f8a3f7e 100644 --- a/100_core/src/gplx/core/security/Hash_algo_.java +++ b/100_core/src/gplx/core/security/Hash_algo_.java @@ -13,3 +13,133 @@ 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.core.security; import gplx.*; import gplx.core.*; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import gplx.core.consoles.*; import gplx.core.ios.streams.*; /*IoStream*/ +import gplx.core.texts.*; /*Base32Converter*/ import gplx.core.progs.*; +public class Hash_algo_ { + public static Hash_algo New__md5() {return new Hash_algo__md5();} + public static Hash_algo New__sha1() {return new Hash_algo__sha1();} + public static Hash_algo New__sha2_256() {return new Hash_algo__sha2_256();} + public static Hash_algo New__tth_192() {return new Hash_algo__tth_192();} + public static Hash_algo New_by_tid(byte tid) { + switch (tid) { + case Tid__md5: return New__md5(); + case Tid__sha1: return New__sha1(); + case Tid__sha2_256: return New__sha2_256(); + case Tid__tth_192: return New__tth_192(); + default: throw Err_.new_unhandled_default(tid); + } + } + public static Hash_algo New(String key) { + if (key == Hash_algo__md5.KEY) return New__md5(); + else if (key == Hash_algo__sha1.KEY) return New__sha1(); + else if (key == Hash_algo__sha2_256.KEY) return New__sha2_256(); + else if (key == Hash_algo__tth_192.KEY) return New__tth_192(); + else throw Err_.new_unhandled(key); + } + public static final byte Tid__md5 = 0, Tid__sha1 = 1, Tid__sha2_256 = 2, Tid__tth_192 = 3; +} +abstract class Hash_algo_base implements Hash_algo { + private final MessageDigest md; + private final byte[] trg_bry; + private byte[] tmp_bfr; private final int tmp_bfr_len = 4096; + public Hash_algo_base(MessageDigest md, int trg_bry_len) { + this.md = md; this.trg_bry = new byte[trg_bry_len]; + } + public String Hash_bry_as_str(byte[] src) {return String_.new_a7(Hash_bry_as_bry(src));} + public byte[] Hash_bry_as_bry(byte[] src) { + Hash_algo_utl_.Hash_bry(md, src, src.length, trg_bry); + return Bry_.Copy(trg_bry); // NOTE: must copy to return different instances to callers; else callers may hash same instance with different values + } + public String Hash_stream_as_str(Console_adp console, IoStream stream) {return String_.new_a7(Hash_stream_as_bry(console, stream));} + public byte[] Hash_stream_as_bry(Console_adp console, IoStream stream) { + if (tmp_bfr == null) tmp_bfr = new byte[4096]; + Hash_algo_utl_.Hash_stream(console, stream, md, tmp_bfr, tmp_bfr_len, trg_bry); + return trg_bry; + } + public byte[] Hash_stream_as_bry(Gfo_prog_ui prog_ui, IoStream stream) { + if (tmp_bfr == null) tmp_bfr = new byte[4096]; + Hash_algo_utl_.Hash_stream(prog_ui, stream, md, tmp_bfr, tmp_bfr_len, trg_bry); + return trg_bry; + } + protected static MessageDigest Get_message_digest(String key) { + try {return MessageDigest.getInstance(key);} + catch (NoSuchAlgorithmException e) {throw Err_.new_missing_key(key);} + } +} +class Hash_algo__md5 extends Hash_algo_base { + public Hash_algo__md5() {super(Get_message_digest_instance(), 32);} + public String Key() {return KEY;} public static final String KEY = "md5"; + + private static MessageDigest Get_message_digest_instance() { + if (md__md5 == null) + md__md5 = Get_message_digest(KEY); + return md__md5; + } private static MessageDigest md__md5; +} +class Hash_algo__sha1 extends Hash_algo_base { + public Hash_algo__sha1() {super(Get_message_digest_instance(), 40);} + public String Key() {return KEY;} public static final String KEY = "sha1"; + + private static MessageDigest Get_message_digest_instance() { + if (md__sha1 == null) + md__sha1 = Get_message_digest(KEY); + return md__sha1; + } private static MessageDigest md__sha1; +} +class Hash_algo__sha2_256 extends Hash_algo_base { + public Hash_algo__sha2_256() {super(Get_message_digest_instance(), 64);} + public String Key() {return KEY;} public static final String KEY = "sha-256"; + + private static MessageDigest Get_message_digest_instance() { + if (md__sha2_256 == null) + md__sha2_256 = Get_message_digest(KEY); + return md__sha2_256; + } private static MessageDigest md__sha2_256; +} +class Hash_algo_utl_ { + public static void Hash_bry(MessageDigest md, byte[] src_bry, int src_len, byte[] trg_bry) { + int pos = 0; + while (true) { + if (pos == src_len) break; + int len = 4096; + if (pos + len > src_len) { + len = src_len - pos; + } + md.update(src_bry, pos, len); + pos += len; + } + byte[] md_bry = md.digest(); + gplx.core.encoders.Hex_utl_.Encode_bry(md_bry, trg_bry); + } + public static void Hash_stream(Console_adp dialog, IoStream stream, MessageDigest md, byte[] tmp_bfr, int tmp_bfr_len, byte[] trg_bry) { +// long pos = 0, len = stream.Len(); // pos and len must be long, else will not hash files > 2 GB + while (true) { + int read = stream.Read(tmp_bfr, 0, tmp_bfr_len); // read stream into tmp_bfr + if (read < 1) break; + md.update(tmp_bfr, 0, read); +// pos += read; + } + byte[] md_bry = md.digest(); + gplx.core.encoders.Hex_utl_.Encode_bry(md_bry , trg_bry); + } + public static void Hash_stream(Gfo_prog_ui prog_ui, IoStream stream, MessageDigest md, byte[] tmp_bfr, int tmp_bfr_len, byte[] trg_bry) { + long pos = prog_ui.Prog_data_cur(), len = prog_ui.Prog_data_end(); // pos and len must be long, else will not hash files > 2 GB + try { + while (true) { + int read = stream.Read(tmp_bfr, 0, tmp_bfr_len); // read stream into tmp_bfr + if (read < 1) break; + md.update(tmp_bfr, 0, read); + if (prog_ui.Prog_notify_and_chk_if_suspended(pos, len)) return; + pos += read; + } + } + finally {stream.Rls();} + byte[] md_bry = md.digest(); + gplx.core.encoders.Hex_utl_.Encode_bry(md_bry , trg_bry); + } + public static String To_base_32_str(byte[] ary) {return Base32Converter.Encode(ary);} +} diff --git a/100_core/src/gplx/core/security/Hash_algo__md5__tst.java b/100_core/src/gplx/core/security/Hash_algo__md5__tst.java index a27517de8..719c63e6d 100644 --- a/100_core/src/gplx/core/security/Hash_algo__md5__tst.java +++ b/100_core/src/gplx/core/security/Hash_algo__md5__tst.java @@ -13,3 +13,27 @@ 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.core.security; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Hash_algo__md5__tst { // REF: https://www.cosic.esat.kuleuven.be/nessie/testvectors/hash/md5/Md5-128.unverified.test-vectors + private final Hash_algo__fxt fxt = new Hash_algo__fxt(Hash_algo_.New__md5()); + @Test public void Empty() {fxt.Test__hash("d41d8cd98f00b204e9800998ecf8427e", "");} + @Test public void a() {fxt.Test__hash("0cc175b9c0f1b6a831c399e269772661", "a");} + @Test public void abc() {fxt.Test__hash("900150983cd24fb0d6963f7d28e17f72", "abc");} + @Test public void message_digest() {fxt.Test__hash("f96b697d7cb7938d525a2f31aaf161d0", "message digest");} + @Test public void a_z() {fxt.Test__hash("c3fcd3d76192e4007dfb496cca67e13b", "abcdefghijklmnopqrstuvwxyz");} + @Test public void a_q__mixed() {fxt.Test__hash("8215ef0796a20bcaaae116d3876c664a", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");} + @Test public void A_Z__a_z__0_9() {fxt.Test__hash("d174ab98d277d9f5a5611c2c9f419d9f", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");} + //@Test + public void Num__x_8() {fxt.Test__hash("57edf4a22be3c955ac49da2e2107b67a", String_.Repeat("1234567890", 8));} + //@Test + public void A__x_1million() {fxt.Test__hash("7707d6ae4e027c70eea2a935c2296f21", String_.Repeat("a", 1000000));} +} +class Hash_algo__fxt { + private final Hash_algo algo; + public Hash_algo__fxt(Hash_algo algo) {this.algo = algo;} + public void Test__hash(String expd, String raw) { + Tfds.Eq(expd, algo.Hash_bry_as_str(Bry_.new_u8(raw))); + Tfds.Eq(expd, algo.Hash_stream_as_str(gplx.core.consoles.Console_adp_.Noop, gplx.core.ios.streams.IoStream_.mem_txt_(Io_url_.Empty, raw))); + } +} diff --git a/100_core/src/gplx/core/security/Hash_algo__sha1__tst.java b/100_core/src/gplx/core/security/Hash_algo__sha1__tst.java index a27517de8..b3912eaa6 100644 --- a/100_core/src/gplx/core/security/Hash_algo__sha1__tst.java +++ b/100_core/src/gplx/core/security/Hash_algo__sha1__tst.java @@ -13,3 +13,19 @@ 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.core.security; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Hash_algo__sha1__tst { // REF: https://www.cosic.esat.kuleuven.be/nessie/testvectors/ + private final Hash_algo__fxt fxt = new Hash_algo__fxt(Hash_algo_.New__sha1()); + @Test public void Empty() {fxt.Test__hash("da39a3ee5e6b4b0d3255bfef95601890afd80709", "");} + @Test public void a() {fxt.Test__hash("86f7e437faa5a7fce15d1ddcb9eaeaea377667b8", "a");} + @Test public void abc() {fxt.Test__hash("a9993e364706816aba3e25717850c26c9cd0d89d", "abc");} + @Test public void message_digest() {fxt.Test__hash("c12252ceda8be8994d5fa0290a47231c1d16aae3", "message digest");} + @Test public void a_z() {fxt.Test__hash("32d10c7b8cf96570ca04ce37f2a19d84240d3a89", "abcdefghijklmnopqrstuvwxyz");} + @Test public void a_q__mixed() {fxt.Test__hash("84983e441c3bd26ebaae4aa1f95129e5e54670f1", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");} + @Test public void A_Z__a_z__0_9() {fxt.Test__hash("761c457bf73b14d27e9e9265c46f4b4dda11f940", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");} + // @Test + public void Num() {fxt.Test__hash("50abf5706a150990a08b2c5ea40fa0e585554732", String_.Repeat("1234567890", 8));} + //@Test + public void A_1Million() {fxt.Test__hash("34aa973cd4c4daa4f61eeb2bdbad27316534016f", String_.Repeat("a", 1000000));} +} diff --git a/100_core/src/gplx/core/security/Hash_algo__sha2_256__tst.java b/100_core/src/gplx/core/security/Hash_algo__sha2_256__tst.java index a27517de8..528317452 100644 --- a/100_core/src/gplx/core/security/Hash_algo__sha2_256__tst.java +++ b/100_core/src/gplx/core/security/Hash_algo__sha2_256__tst.java @@ -13,3 +13,19 @@ 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.core.security; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Hash_algo__sha2_256__tst { // REF: https://www.cosic.esat.kuleuven.be/nessie/testvectors/ + private final Hash_algo__fxt fxt = new Hash_algo__fxt(Hash_algo_.New__sha2_256()); + @Test public void Empty() {fxt.Test__hash("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "");} + @Test public void a() {fxt.Test__hash("ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", "a");} + @Test public void abc() {fxt.Test__hash("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "abc");} + @Test public void message_digest() {fxt.Test__hash("f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650", "message digest");} + @Test public void a_z() {fxt.Test__hash("71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73", "abcdefghijklmnopqrstuvwxyz");} + @Test public void a_q__mixed() {fxt.Test__hash("248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");} + @Test public void A_Z__a_z__0_9() {fxt.Test__hash("db4bfcbd4da0cd85a60c3c37d3fbd8805c77f15fc6b1fdfe614ee0a7c8fdb4c0", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");} + // @Test + public void Num() {fxt.Test__hash("f371bc4a311f2b009eef952dd83ca80e2b60026c8e935592d0f9c308453c813e", String_.Repeat("1234567890", 8));} + //@Test + public void A_1Million() {fxt.Test__hash("cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0", String_.Repeat("a", 1000000));} +} diff --git a/100_core/src/gplx/core/security/Hash_algo__tth_192.java b/100_core/src/gplx/core/security/Hash_algo__tth_192.java index a27517de8..834a3852e 100644 --- a/100_core/src/gplx/core/security/Hash_algo__tth_192.java +++ b/100_core/src/gplx/core/security/Hash_algo__tth_192.java @@ -13,3 +13,993 @@ 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.core.security; import gplx.*; import gplx.core.*; +import gplx.core.consoles.*; import gplx.core.ios.streams.*; /*IoStream*/ +import gplx.core.progs.*; +public class Hash_algo__tth_192 implements Hash_algo { + public String Key() {return KEY;} public static final String KEY = "tth192"; + public int BlockSize() {return blockSize;} public void BlockSize_set(int v) {blockSize = v;} int blockSize = 1024; + public String Hash_bry_as_str(byte[] src) {return String_.new_a7(Hash_bry_as_bry(src));} + public byte[] Hash_bry_as_bry(byte[] v) {return Bry_.new_a7(Hash_stream_as_str(Console_adp_.Noop, gplx.core.ios.streams.IoStream_.ary_(v)));} + public byte[] Hash_stream_as_bry(Gfo_prog_ui prog_ui, IoStream stream) {return Bry_.new_a7(Hash_stream_as_str(Console_adp_.Noop, stream));} + public String Hash_stream_as_str(Console_adp dialog, IoStream stream) { + int leafCount = (int)(stream.Len() / blockSize); + HashDlgWtr dialogWtr = HashDlgWtr_.Current; + dialogWtr.Bgn(dialog, stream.Url(), CalcWorkUnits(stream.Len())); + if (stream.Len() % blockSize > 0) // if stream.length is not multiple of blockSize, add another leaf to hold leftover bytes + leafCount++; + else if (stream.Len() == 0) // if stream.length == 0, still allocate one leaf + leafCount = 1; + + hashMain = new byte[(leafCount / 2) + 1][]; // / 2 b/c HashAllBytes looks at pairs of slots; + 1 to avoid truncation + hashMainCount = 0; + HashAllBytes(dialogWtr, stream, leafCount); + byte[] rv = HashAllHashes(dialogWtr); + return Hash_algo_utl_.To_base_32_str(rv); + } + byte[] CalcHash_next(IoStream stream) { + if (blockA == null || blockA.length != blockSize) blockA = new byte[blockSize]; + if (blockB == null || blockB.length != blockSize) blockB = new byte[blockSize]; + + int dataSize = stream.Read(blockA, 0, blockSize); // if (dataSize < BlockSize) return (CalcHash_leaf(ShrinkArray(blockA, dataSize))); // TODO_OLD: reinstate? + dataSize = stream.Read(blockB, 0, blockSize); + if (dataSize < blockSize) blockB = ShrinkArray(blockB, dataSize); // shrink array to match data size (occurs at end of stream, when lastBytesCount < BlockSize) + return CalcHash_branch(CalcHash_leaf(blockA, 0), CalcHash_leaf(blockB, 1)); + } + void HashAllBytes(HashDlgWtr dialogWtr, IoStream stream, int leafCount) { + int total = leafCount / 2; + for (int i = 0; i < total; i++) { + if (dialogWtr.Canceled()) dialogWtr.Fail(stream); + hashMain[hashMainCount++] = CalcHash_next(stream); + dialogWtr.Do(2); + } + if (leafCount % 2 == 1) { // odd number of leaves => hash last leaf (since it was never in a pair) + byte[] block = new byte[blockSize]; + int dataSize = stream.Read(block, 0 , blockSize); + if (dataSize < blockSize) block = ShrinkArray(block,dataSize); // shrink array to match data size (occurs at end of stream, when lastBytesCount < BlockSize) + + hashMain[hashMainCount++] = CalcHash_leaf(block, 0); + dialogWtr.Do(1); + } + stream.Rls(); + } + byte[] HashAllHashes(HashDlgWtr dialogWtr) { + int currIdx = 0, lastIdx = hashMainCount - 1, reuseIdx = 0; + while (currIdx < lastIdx) { // calc till 1 is left; + while (currIdx < lastIdx) { // calc till 1 is left; EX: 8 slots; loop1 calcs 8; makes 4; loop0 restarts; loop1 calcs 4; makes 2; etc.. + byte[] hashA = hashMain[currIdx++]; + byte[] hashB = hashMain[currIdx++]; + hashMain[reuseIdx++] = CalcHash_branch(hashA, hashB); + dialogWtr.Do(2); + } + if (currIdx == lastIdx) // NOTE: currIdx == lastIdx only when odd number of leaves + hashMain[reuseIdx++] = hashMain[currIdx++]; +// for (int j = reuseIdx; j < lastIdx; j++) // PERF: free unused slots (may not be necessary) +// hashMain[j] = null; + lastIdx = reuseIdx - 1; + currIdx = 0; + reuseIdx = 0; + }; + dialogWtr.End(); + return (byte[])hashMain[lastIdx]; // return last hash as root hash + } + byte[] CalcHash_branch(byte[] blockA, byte[] blockB) { + // gen array: [0x01] + [blockA] + [blockB] + if (branchRv == null || branchRv.length != blockA.length + blockB.length + 1) + branchRv = new byte[blockA.length + blockB.length + 1]; + branchRv[0] = 0x01; // branch hash mark. + Array_.Copy_to(blockA, branchRv, 1); + Array_.Copy_to(blockB, branchRv, blockA.length + 1); + return CalcHash(branchRv); + } + byte[] CalcHash_leaf(byte[] raw, int i) { + // gen array: [0x00] + [raw] + byte[] rv = null; + if (i == 0) { + if (leaf0 == null || leaf0.length != raw.length + 1) { + leaf0 = new byte[raw.length + 1]; + } + rv = leaf0; + } + else { + if (leaf1 == null || leaf1.length != raw.length + 1) { + leaf1 = new byte[raw.length + 1]; + } + rv = leaf1; + } + + rv[0] = 0x00; // leaf hash mark. + Array_.Copy_to(raw, rv, 1); + return CalcHash(rv); + } + byte[] CalcHash(byte[] raw) { + algo.Initialize(); + return algo.ComputeHash(raw); + } + byte[] ShrinkArray(byte[] raw, int newLen) { + byte[] rv = new byte[newLen]; + for (int i = 0; i < newLen; i++) + rv[i] = raw[i]; + return rv; + } + public int CalcWorkUnits(long streamLength) { + int leafCount = (int)(streamLength / blockSize); + int phase1 = leafCount; + if (leafCount == 0) phase1 = 1; + else if (streamLength % blockSize != 0) phase1++; + int phase2 = phase1 <= 1 ? 0 : phase1 - 1; // see note + return phase1 + phase2; + } + Tiger192 algo = new Tiger192(); + byte[][] hashMain; int hashMainCount = 0; + byte[] blockA, blockB, branchRv, leaf0, leaf1; +} +interface HashDlgWtr { + boolean Canceled(); + void Bgn(Console_adp dialog, Io_url url, int total); + void Do(int increment); + void End(); + void Fail(IoStream stream); +} +class HashDlgWtr_ { + public static HashDlgWtr Current = HashDlgWtrDefault.new_(); +} +class HashDlgWtrDefault implements HashDlgWtr { + public int Total() {return total;} int total; + public int LastPercentage() {return lastPercentage;} int lastPercentage = 0; + public int Current() {return current;} int current = 0; + public boolean Canceled() {return dialog.Canceled_chk();} + String p; + public void Bgn(Console_adp dialog, Io_url url, int total) { + this.dialog = dialog; this.total = total; + p = url.Xto_api() + " - hash: "; + this.lastPercentage = 0; this.current = 0; + } + public void Do(int increment) { + current += increment; + int percentage = (current * 100) / total; + if (percentage <= lastPercentage) return; + dialog.Write_tmp(String_.LimitToFirst(p, dialog.Chars_per_line_max()) + Int_.To_str(percentage) + "%"); + lastPercentage = percentage; + } + public void End() {} + public void Fail(IoStream stream) { + // stream.Dispose(); + } + Console_adp dialog; + public static HashDlgWtrDefault new_() {return new HashDlgWtrDefault();} +} +class Tiger192 extends BaseHash { + public void Initialize() { + this.resetContext(); + } + public byte[] ComputeHash(byte[] input) { + this.update(input, 0, input.length); + return this.digest(); + } + protected byte[] padBuffer() { + int n = (int) (count % BLOCK_SIZE); + int padding = (n < 56) ? (56 - n) : (120 - n); + byte[] pad = new byte[padding + 8]; + + pad[0] = 1; + long bits = count << 3; + + pad[padding++] = (byte) bits; + pad[padding++] = (byte) (bits >>> 8); + pad[padding++] = (byte) (bits >>> 16); + pad[padding++] = (byte) (bits >>> 24); + pad[padding++] = (byte) (bits >>> 32); + pad[padding++] = (byte) (bits >>> 40); + pad[padding++] = (byte) (bits >>> 48); + pad[padding ] = (byte) (bits >>> 56); + + return pad; + } + + protected byte[] getResult() { + return new byte[] { + (byte) a , (byte)(a >>> 8), (byte)(a >>> 16), + (byte)(a >>> 24), (byte)(a >>> 32), (byte)(a >>> 40), + (byte)(a >>> 48), (byte)(a >>> 56), + (byte) b , (byte)(b >>> 8), (byte)(b >>> 16), + (byte)(b >>> 24), (byte)(b >>> 32), (byte)(b >>> 40), + (byte)(b >>> 48), (byte)(b >>> 56), + (byte) c , (byte)(c >>> 8), (byte)(c >>> 16), + (byte)(c >>> 24), (byte)(c >>> 32), (byte)(c >>> 40), + (byte)(c >>> 48), (byte)(c >>> 56) + }; + } + + protected void resetContext() { + a = A; + b = B; + c = C; + } + protected void transform(byte[] in, int offset) { + long x0, x1, x2, x3, x4, x5, x6, x7; + + x0 = ((long) in[offset++] & 0xFF) | ((long)(in[offset++] & 0xFF) << 8) + | ((long)(in[offset++] & 0xFF) << 16) | ((long)(in[offset++] & 0xFF) << 24) + | ((long)(in[offset++] & 0xFF) << 32) | ((long)(in[offset++] & 0xFF) << 40) + | ((long)(in[offset++] & 0xFF) << 48) | ((long)(in[offset++] & 0xFF) << 56); + x1 = ((long) in[offset++] & 0xFF) | ((long)(in[offset++] & 0xFF) << 8) + | ((long)(in[offset++] & 0xFF) << 16) | ((long)(in[offset++] & 0xFF) << 24) + | ((long)(in[offset++] & 0xFF) << 32) | ((long)(in[offset++] & 0xFF) << 40) + | ((long)(in[offset++] & 0xFF) << 48) | ((long)(in[offset++] & 0xFF) << 56); + x2 = ((long) in[offset++] & 0xFF) | ((long)(in[offset++] & 0xFF) << 8) + | ((long)(in[offset++] & 0xFF) << 16) | ((long)(in[offset++] & 0xFF) << 24) + | ((long)(in[offset++] & 0xFF) << 32) | ((long)(in[offset++] & 0xFF) << 40) + | ((long)(in[offset++] & 0xFF) << 48) | ((long)(in[offset++] & 0xFF) << 56); + x3 = ((long) in[offset++] & 0xFF) | ((long)(in[offset++] & 0xFF) << 8) + | ((long)(in[offset++] & 0xFF) << 16) | ((long)(in[offset++] & 0xFF) << 24) + | ((long)(in[offset++] & 0xFF) << 32) | ((long)(in[offset++] & 0xFF) << 40) + | ((long)(in[offset++] & 0xFF) << 48) | ((long)(in[offset++] & 0xFF) << 56); + x4 = ((long) in[offset++] & 0xFF) | ((long)(in[offset++] & 0xFF) << 8) + | ((long)(in[offset++] & 0xFF) << 16) | ((long)(in[offset++] & 0xFF) << 24) + | ((long)(in[offset++] & 0xFF) << 32) | ((long)(in[offset++] & 0xFF) << 40) + | ((long)(in[offset++] & 0xFF) << 48) | ((long)(in[offset++] & 0xFF) << 56); + x5 = ((long) in[offset++] & 0xFF) | ((long)(in[offset++] & 0xFF) << 8) + | ((long)(in[offset++] & 0xFF) << 16) | ((long)(in[offset++] & 0xFF) << 24) + | ((long)(in[offset++] & 0xFF) << 32) | ((long)(in[offset++] & 0xFF) << 40) + | ((long)(in[offset++] & 0xFF) << 48) | ((long)(in[offset++] & 0xFF) << 56); + x6 = ((long) in[offset++] & 0xFF) | ((long)(in[offset++] & 0xFF) << 8) + | ((long)(in[offset++] & 0xFF) << 16) | ((long)(in[offset++] & 0xFF) << 24) + | ((long)(in[offset++] & 0xFF) << 32) | ((long)(in[offset++] & 0xFF) << 40) + | ((long)(in[offset++] & 0xFF) << 48) | ((long)(in[offset++] & 0xFF) << 56); + x7 = ((long) in[offset++] & 0xFF) | ((long)(in[offset++] & 0xFF) << 8) + | ((long)(in[offset++] & 0xFF) << 16) | ((long)(in[offset++] & 0xFF) << 24) + | ((long)(in[offset++] & 0xFF) << 32) | ((long)(in[offset++] & 0xFF) << 40) + | ((long)(in[offset++] & 0xFF) << 48) | ((long)(in[offset ] & 0xFF) << 56); + + // save_abc ::= + long aa = a, bb = b, cc = c; + + // pass(aa, bb, cc, 5) ::= + cc ^= x0; + aa -= T1[(int) cc & 0xff] ^ T2[(int)(cc >> 16) & 0xff] + ^ T3[(int)(cc >> 32) & 0xff] ^ T4[(int)(cc >> 48) & 0xff]; + bb += T4[(int)(cc >> 8) & 0xff] ^ T3[(int)(cc >> 24) & 0xff] + ^ T2[(int)(cc >> 40) & 0xff] ^ T1[(int)(cc >> 56) & 0xff]; + bb *= 5; + aa ^= x1; + bb -= T1[(int) aa & 0xff] ^ T2[(int)(aa >> 16) & 0xff] + ^ T3[(int)(aa >> 32) & 0xff] ^ T4[(int)(aa >> 48) & 0xff]; + cc += T4[(int)(aa >> 8) & 0xff] ^ T3[(int)(aa >> 24) & 0xff] + ^ T2[(int)(aa >> 40) & 0xff] ^ T1[(int)(aa >> 56) & 0xff]; + cc *= 5; + bb ^= x2; + cc -= T1[(int) bb & 0xff] ^ T2[(int)(bb >> 16) & 0xff] + ^ T3[(int)(bb >> 32) & 0xff] ^ T4[(int)(bb >> 48) & 0xff]; + aa += T4[(int)(bb >> 8) & 0xff] ^ T3[(int)(bb >> 24) & 0xff] + ^ T2[(int)(bb >> 40) & 0xff] ^ T1[(int)(bb >> 56) & 0xff]; + aa *= 5; + cc ^= x3; + aa -= T1[(int) cc & 0xff] ^ T2[(int)(cc >> 16) & 0xff] + ^ T3[(int)(cc >> 32) & 0xff] ^ T4[(int)(cc >> 48) & 0xff]; + bb += T4[(int)(cc >> 8) & 0xff] ^ T3[(int)(cc >> 24) & 0xff] + ^ T2[(int)(cc >> 40) & 0xff] ^ T1[(int)(cc >> 56) & 0xff]; + bb *= 5; + aa ^= x4; + bb -= T1[(int) aa & 0xff] ^ T2[(int)(aa >> 16) & 0xff] + ^ T3[(int)(aa >> 32) & 0xff] ^ T4[(int)(aa >> 48) & 0xff]; + cc += T4[(int)(aa >> 8) & 0xff] ^ T3[(int)(aa >> 24) & 0xff] + ^ T2[(int)(aa >> 40) & 0xff] ^ T1[(int)(aa >> 56) & 0xff]; + cc *= 5; + bb ^= x5; + cc -= T1[(int) bb & 0xff] ^ T2[(int)(bb >> 16) & 0xff] + ^ T3[(int)(bb >> 32) & 0xff] ^ T4[(int)(bb >> 48) & 0xff]; + aa += T4[(int)(bb >> 8) & 0xff] ^ T3[(int)(bb >> 24) & 0xff] + ^ T2[(int)(bb >> 40) & 0xff] ^ T1[(int)(bb >> 56) & 0xff]; + aa *= 5; + cc ^= x6; + aa -= T1[(int) cc & 0xff] ^ T2[(int)(cc >> 16) & 0xff] + ^ T3[(int)(cc >> 32) & 0xff] ^ T4[(int)(cc >> 48) & 0xff]; + bb += T4[(int)(cc >> 8) & 0xff] ^ T3[(int)(cc >> 24) & 0xff] + ^ T2[(int)(cc >> 40) & 0xff] ^ T1[(int)(cc >> 56) & 0xff]; + bb *= 5; + aa ^= x7; + bb -= T1[(int) aa & 0xff] ^ T2[(int)(aa >> 16) & 0xff] + ^ T3[(int)(aa >> 32) & 0xff] ^ T4[(int)(aa >> 48) & 0xff]; + cc += T4[(int)(aa >> 8) & 0xff] ^ T3[(int)(aa >> 24) & 0xff] + ^ T2[(int)(aa >> 40) & 0xff] ^ T1[(int)(aa >> 56) & 0xff]; + cc *= 5; + + // key_schedule ::= + x0 -= x7 ^ 0xA5A5A5A5A5A5A5A5L; + x1 ^= x0; + x2 += x1; + x3 -= x2 ^ ((~x1) << 19); + x4 ^= x3; + x5 += x4; + x6 -= x5 ^ ((~x4) >>> 23); + x7 ^= x6; + x0 += x7; + x1 -= x0 ^ ((~x7) << 19); + x2 ^= x1; + x3 += x2; + x4 -= x3 ^ ((~x2) >>> 23); + x5 ^= x4; + x6 += x5; + x7 -= x6 ^ 0x0123456789ABCDEFL; + + // pass(cc, aa, bb, 7) ::= + bb ^= x0; + cc -= T1[(int) bb & 0xff] ^ T2[(int)(bb >> 16) & 0xff] + ^ T3[(int)(bb >> 32) & 0xff] ^ T4[(int)(bb >> 48) & 0xff]; + aa += T4[(int)(bb >> 8) & 0xff] ^ T3[(int)(bb >> 24) & 0xff] + ^ T2[(int)(bb >> 40) & 0xff] ^ T1[(int)(bb >> 56) & 0xff]; + aa *= 7; + cc ^= x1; + aa -= T1[(int) cc & 0xff] ^ T2[(int)(cc >> 16) & 0xff] + ^ T3[(int)(cc >> 32) & 0xff] ^ T4[(int)(cc >> 48) & 0xff]; + bb += T4[(int)(cc >> 8) & 0xff] ^ T3[(int)(cc >> 24) & 0xff] + ^ T2[(int)(cc >> 40) & 0xff] ^ T1[(int)(cc >> 56) & 0xff]; + bb *= 7; + aa ^= x2; + bb -= T1[(int) aa & 0xff] ^ T2[(int)(aa >> 16) & 0xff] + ^ T3[(int)(aa >> 32) & 0xff] ^ T4[(int)(aa >> 48) & 0xff]; + cc += T4[(int)(aa >> 8) & 0xff] ^ T3[(int)(aa >> 24) & 0xff] + ^ T2[(int)(aa >> 40) & 0xff] ^ T1[(int)(aa >> 56) & 0xff]; + cc *= 7; + bb ^= x3; + cc -= T1[(int) bb & 0xff] ^ T2[(int)(bb >> 16) & 0xff] + ^ T3[(int)(bb >> 32) & 0xff] ^ T4[(int)(bb >> 48) & 0xff]; + aa += T4[(int)(bb >> 8) & 0xff] ^ T3[(int)(bb >> 24) & 0xff] + ^ T2[(int)(bb >> 40) & 0xff] ^ T1[(int)(bb >> 56) & 0xff]; + aa *= 7; + cc ^= x4; + aa -= T1[(int) cc & 0xff] ^ T2[(int)(cc >> 16) & 0xff] + ^ T3[(int)(cc >> 32) & 0xff] ^ T4[(int)(cc >> 48) & 0xff]; + bb += T4[(int)(cc >> 8) & 0xff] ^ T3[(int)(cc >> 24) & 0xff] + ^ T2[(int)(cc >> 40) & 0xff] ^ T1[(int)(cc >> 56) & 0xff]; + bb *= 7; + aa ^= x5; + bb -= T1[(int) aa & 0xff] ^ T2[(int)(aa >> 16) & 0xff] + ^ T3[(int)(aa >> 32) & 0xff] ^ T4[(int)(aa >> 48) & 0xff]; + cc += T4[(int)(aa >> 8) & 0xff] ^ T3[(int)(aa >> 24) & 0xff] + ^ T2[(int)(aa >> 40) & 0xff] ^ T1[(int)(aa >> 56) & 0xff]; + cc *= 7; + bb ^= x6; + cc -= T1[(int) bb & 0xff] ^ T2[(int)(bb >> 16) & 0xff] + ^ T3[(int)(bb >> 32) & 0xff] ^ T4[(int)(bb >> 48) & 0xff]; + aa += T4[(int)(bb >> 8) & 0xff] ^ T3[(int)(bb >> 24) & 0xff] + ^ T2[(int)(bb >> 40) & 0xff] ^ T1[(int)(bb >> 56) & 0xff]; + aa *= 7; + cc ^= x7; + aa -= T1[(int) cc & 0xff] ^ T2[(int)(cc >> 16) & 0xff] + ^ T3[(int)(cc >> 32) & 0xff] ^ T4[(int)(cc >> 48) & 0xff]; + bb += T4[(int)(cc >> 8) & 0xff] ^ T3[(int)(cc >> 24) & 0xff] + ^ T2[(int)(cc >> 40) & 0xff] ^ T1[(int)(cc >> 56) & 0xff]; + bb *= 7; + + // key_schedule ::= + x0 -= x7 ^ 0xA5A5A5A5A5A5A5A5L; + x1 ^= x0; + x2 += x1; + x3 -= x2 ^ ((~x1) << 19); + x4 ^= x3; + x5 += x4; + x6 -= x5 ^ ((~x4) >>> 23); + x7 ^= x6; + x0 += x7; + x1 -= x0 ^ ((~x7) << 19); + x2 ^= x1; + x3 += x2; + x4 -= x3 ^ ((~x2) >>> 23); + x5 ^= x4; + x6 += x5; + x7 -= x6 ^ 0x0123456789ABCDEFL; + + // pass(bb,cc,aa,9) ::= + aa ^= x0; + bb -= T1[(int) aa & 0xff] ^ T2[(int)(aa >> 16) & 0xff] + ^ T3[(int)(aa >> 32) & 0xff] ^ T4[(int)(aa >> 48) & 0xff]; + cc += T4[(int)(aa >> 8) & 0xff] ^ T3[(int)(aa >> 24) & 0xff] + ^ T2[(int)(aa >> 40) & 0xff] ^ T1[(int)(aa >> 56) & 0xff]; + cc *= 9; + bb ^= x1; + cc -= T1[(int) bb & 0xff] ^ T2[(int)(bb >> 16) & 0xff] + ^ T3[(int)(bb >> 32) & 0xff] ^ T4[(int)(bb >> 48) & 0xff]; + aa += T4[(int)(bb >> 8) & 0xff] ^ T3[(int)(bb >> 24) & 0xff] + ^ T2[(int)(bb >> 40) & 0xff] ^ T1[(int)(bb >> 56) & 0xff]; + aa *= 9; + cc ^= x2; + aa -= T1[(int) cc & 0xff] ^ T2[(int)(cc >> 16) & 0xff] + ^ T3[(int)(cc >> 32) & 0xff] ^ T4[(int)(cc >> 48) & 0xff]; + bb += T4[(int)(cc >> 8) & 0xff] ^ T3[(int)(cc >> 24) & 0xff] + ^ T2[(int)(cc >> 40) & 0xff] ^ T1[(int)(cc >> 56) & 0xff]; + bb *= 9; + aa ^= x3; + bb -= T1[(int) aa & 0xff] ^ T2[(int)(aa >> 16) & 0xff] + ^ T3[(int)(aa >> 32) & 0xff] ^ T4[(int)(aa >> 48) & 0xff]; + cc += T4[(int)(aa >> 8) & 0xff] ^ T3[(int)(aa >> 24) & 0xff] + ^ T2[(int)(aa >> 40) & 0xff] ^ T1[(int)(aa >> 56) & 0xff]; + cc *= 9; + bb ^= x4; + cc -= T1[(int) bb & 0xff] ^ T2[(int)(bb >> 16) & 0xff] + ^ T3[(int)(bb >> 32) & 0xff] ^ T4[(int)(bb >> 48) & 0xff]; + aa += T4[(int)(bb >> 8) & 0xff] ^ T3[(int)(bb >> 24) & 0xff] + ^ T2[(int)(bb >> 40) & 0xff] ^ T1[(int)(bb >> 56) & 0xff]; + aa *= 9; + cc ^= x5; + aa -= T1[(int) cc & 0xff] ^ T2[(int)(cc >> 16) & 0xff] + ^ T3[(int)(cc >> 32) & 0xff] ^ T4[(int)(cc >> 48) & 0xff]; + bb += T4[(int)(cc >> 8) & 0xff] ^ T3[(int)(cc >> 24) & 0xff] + ^ T2[(int)(cc >> 40) & 0xff] ^ T1[(int)(cc >> 56) & 0xff]; + bb *= 9; + aa ^= x6; + bb -= T1[(int) aa & 0xff] ^ T2[(int)(aa >> 16) & 0xff] + ^ T3[(int)(aa >> 32) & 0xff] ^ T4[(int)(aa >> 48) & 0xff]; + cc += T4[(int)(aa >> 8) & 0xff] ^ T3[(int)(aa >> 24) & 0xff] + ^ T2[(int)(aa >> 40) & 0xff] ^ T1[(int)(aa >> 56) & 0xff]; + cc *= 9; + bb ^= x7; + cc -= T1[(int) bb & 0xff] ^ T2[(int)(bb >> 16) & 0xff] + ^ T3[(int)(bb >> 32) & 0xff] ^ T4[(int)(bb >> 48) & 0xff]; + aa += T4[(int)(bb >> 8) & 0xff] ^ T3[(int)(bb >> 24) & 0xff] + ^ T2[(int)(bb >> 40) & 0xff] ^ T1[(int)(bb >> 56) & 0xff]; + aa *= 9; + + // feedforward ::= + a ^= aa; + b = bb - b; + c += cc; + } + protected static final int HASH_SIZE = 24; + + protected static final int BLOCK_SIZE = 64; + + /** Result when no data has been input. */ + + private static final long A = 0x0123456789ABCDEFL; + private static final long B = 0xFEDCBA9876543210L; + private static final long C = 0xF096A5B4C3B2E187L; + + /** S-Box T1. */ + private static final long[] T1 = { + 0x02AAB17CF7E90C5EL, 0xAC424B03E243A8ECL, 0x72CD5BE30DD5FCD3L, 0x6D019B93F6F97F3AL, + 0xCD9978FFD21F9193L, 0x7573A1C9708029E2L, 0xB164326B922A83C3L, 0x46883EEE04915870L, + 0xEAACE3057103ECE6L, 0xC54169B808A3535CL, 0x4CE754918DDEC47CL, 0x0AA2F4DFDC0DF40CL, + 0x10B76F18A74DBEFAL, 0xC6CCB6235AD1AB6AL, 0x13726121572FE2FFL, 0x1A488C6F199D921EL, + 0x4BC9F9F4DA0007CAL, 0x26F5E6F6E85241C7L, 0x859079DBEA5947B6L, 0x4F1885C5C99E8C92L, + 0xD78E761EA96F864BL, 0x8E36428C52B5C17DL, 0x69CF6827373063C1L, 0xB607C93D9BB4C56EL, + 0x7D820E760E76B5EAL, 0x645C9CC6F07FDC42L, 0xBF38A078243342E0L, 0x5F6B343C9D2E7D04L, + 0xF2C28AEB600B0EC6L, 0x6C0ED85F7254BCACL, 0x71592281A4DB4FE5L, 0x1967FA69CE0FED9FL, + 0xFD5293F8B96545DBL, 0xC879E9D7F2A7600BL, 0x860248920193194EL, 0xA4F9533B2D9CC0B3L, + 0x9053836C15957613L, 0xDB6DCF8AFC357BF1L, 0x18BEEA7A7A370F57L, 0x037117CA50B99066L, + 0x6AB30A9774424A35L, 0xF4E92F02E325249BL, 0x7739DB07061CCAE1L, 0xD8F3B49CECA42A05L, + 0xBD56BE3F51382F73L, 0x45FAED5843B0BB28L, 0x1C813D5C11BF1F83L, 0x8AF0E4B6D75FA169L, + 0x33EE18A487AD9999L, 0x3C26E8EAB1C94410L, 0xB510102BC0A822F9L, 0x141EEF310CE6123BL, + 0xFC65B90059DDB154L, 0xE0158640C5E0E607L, 0x884E079826C3A3CFL, 0x930D0D9523C535FDL, + 0x35638D754E9A2B00L, 0x4085FCCF40469DD5L, 0xC4B17AD28BE23A4CL, 0xCAB2F0FC6A3E6A2EL, + 0x2860971A6B943FCDL, 0x3DDE6EE212E30446L, 0x6222F32AE01765AEL, 0x5D550BB5478308FEL, + 0xA9EFA98DA0EDA22AL, 0xC351A71686C40DA7L, 0x1105586D9C867C84L, 0xDCFFEE85FDA22853L, + 0xCCFBD0262C5EEF76L, 0xBAF294CB8990D201L, 0xE69464F52AFAD975L, 0x94B013AFDF133E14L, + 0x06A7D1A32823C958L, 0x6F95FE5130F61119L, 0xD92AB34E462C06C0L, 0xED7BDE33887C71D2L, + 0x79746D6E6518393EL, 0x5BA419385D713329L, 0x7C1BA6B948A97564L, 0x31987C197BFDAC67L, + 0xDE6C23C44B053D02L, 0x581C49FED002D64DL, 0xDD474D6338261571L, 0xAA4546C3E473D062L, + 0x928FCE349455F860L, 0x48161BBACAAB94D9L, 0x63912430770E6F68L, 0x6EC8A5E602C6641CL, + 0x87282515337DDD2BL, 0x2CDA6B42034B701BL, 0xB03D37C181CB096DL, 0xE108438266C71C6FL, + 0x2B3180C7EB51B255L, 0xDF92B82F96C08BBCL, 0x5C68C8C0A632F3BAL, 0x5504CC861C3D0556L, + 0xABBFA4E55FB26B8FL, 0x41848B0AB3BACEB4L, 0xB334A273AA445D32L, 0xBCA696F0A85AD881L, + 0x24F6EC65B528D56CL, 0x0CE1512E90F4524AL, 0x4E9DD79D5506D35AL, 0x258905FAC6CE9779L, + 0x2019295B3E109B33L, 0xF8A9478B73A054CCL, 0x2924F2F934417EB0L, 0x3993357D536D1BC4L, + 0x38A81AC21DB6FF8BL, 0x47C4FBF17D6016BFL, 0x1E0FAADD7667E3F5L, 0x7ABCFF62938BEB96L, + 0xA78DAD948FC179C9L, 0x8F1F98B72911E50DL, 0x61E48EAE27121A91L, 0x4D62F7AD31859808L, + 0xECEBA345EF5CEAEBL, 0xF5CEB25EBC9684CEL, 0xF633E20CB7F76221L, 0xA32CDF06AB8293E4L, + 0x985A202CA5EE2CA4L, 0xCF0B8447CC8A8FB1L, 0x9F765244979859A3L, 0xA8D516B1A1240017L, + 0x0BD7BA3EBB5DC726L, 0xE54BCA55B86ADB39L, 0x1D7A3AFD6C478063L, 0x519EC608E7669EDDL, + 0x0E5715A2D149AA23L, 0x177D4571848FF194L, 0xEEB55F3241014C22L, 0x0F5E5CA13A6E2EC2L, + 0x8029927B75F5C361L, 0xAD139FABC3D6E436L, 0x0D5DF1A94CCF402FL, 0x3E8BD948BEA5DFC8L, + 0xA5A0D357BD3FF77EL, 0xA2D12E251F74F645L, 0x66FD9E525E81A082L, 0x2E0C90CE7F687A49L, + 0xC2E8BCBEBA973BC5L, 0x000001BCE509745FL, 0x423777BBE6DAB3D6L, 0xD1661C7EAEF06EB5L, + 0xA1781F354DAACFD8L, 0x2D11284A2B16AFFCL, 0xF1FC4F67FA891D1FL, 0x73ECC25DCB920ADAL, + 0xAE610C22C2A12651L, 0x96E0A810D356B78AL, 0x5A9A381F2FE7870FL, 0xD5AD62EDE94E5530L, + 0xD225E5E8368D1427L, 0x65977B70C7AF4631L, 0x99F889B2DE39D74FL, 0x233F30BF54E1D143L, + 0x9A9675D3D9A63C97L, 0x5470554FF334F9A8L, 0x166ACB744A4F5688L, 0x70C74CAAB2E4AEADL, + 0xF0D091646F294D12L, 0x57B82A89684031D1L, 0xEFD95A5A61BE0B6BL, 0x2FBD12E969F2F29AL, + 0x9BD37013FEFF9FE8L, 0x3F9B0404D6085A06L, 0x4940C1F3166CFE15L, 0x09542C4DCDF3DEFBL, + 0xB4C5218385CD5CE3L, 0xC935B7DC4462A641L, 0x3417F8A68ED3B63FL, 0xB80959295B215B40L, + 0xF99CDAEF3B8C8572L, 0x018C0614F8FCB95DL, 0x1B14ACCD1A3ACDF3L, 0x84D471F200BB732DL, + 0xC1A3110E95E8DA16L, 0x430A7220BF1A82B8L, 0xB77E090D39DF210EL, 0x5EF4BD9F3CD05E9DL, + 0x9D4FF6DA7E57A444L, 0xDA1D60E183D4A5F8L, 0xB287C38417998E47L, 0xFE3EDC121BB31886L, + 0xC7FE3CCC980CCBEFL, 0xE46FB590189BFD03L, 0x3732FD469A4C57DCL, 0x7EF700A07CF1AD65L, + 0x59C64468A31D8859L, 0x762FB0B4D45B61F6L, 0x155BAED099047718L, 0x68755E4C3D50BAA6L, + 0xE9214E7F22D8B4DFL, 0x2ADDBF532EAC95F4L, 0x32AE3909B4BD0109L, 0x834DF537B08E3450L, + 0xFA209DA84220728DL, 0x9E691D9B9EFE23F7L, 0x0446D288C4AE8D7FL, 0x7B4CC524E169785BL, + 0x21D87F0135CA1385L, 0xCEBB400F137B8AA5L, 0x272E2B66580796BEL, 0x3612264125C2B0DEL, + 0x057702BDAD1EFBB2L, 0xD4BABB8EACF84BE9L, 0x91583139641BC67BL, 0x8BDC2DE08036E024L, + 0x603C8156F49F68EDL, 0xF7D236F7DBEF5111L, 0x9727C4598AD21E80L, 0xA08A0896670A5FD7L, + 0xCB4A8F4309EBA9CBL, 0x81AF564B0F7036A1L, 0xC0B99AA778199ABDL, 0x959F1EC83FC8E952L, + 0x8C505077794A81B9L, 0x3ACAAF8F056338F0L, 0x07B43F50627A6778L, 0x4A44AB49F5ECCC77L, + 0x3BC3D6E4B679EE98L, 0x9CC0D4D1CF14108CL, 0x4406C00B206BC8A0L, 0x82A18854C8D72D89L, + 0x67E366B35C3C432CL, 0xB923DD61102B37F2L, 0x56AB2779D884271DL, 0xBE83E1B0FF1525AFL, + 0xFB7C65D4217E49A9L, 0x6BDBE0E76D48E7D4L, 0x08DF828745D9179EL, 0x22EA6A9ADD53BD34L, + 0xE36E141C5622200AL, 0x7F805D1B8CB750EEL, 0xAFE5C7A59F58E837L, 0xE27F996A4FB1C23CL, + 0xD3867DFB0775F0D0L, 0xD0E673DE6E88891AL, 0x123AEB9EAFB86C25L, 0x30F1D5D5C145B895L, + 0xBB434A2DEE7269E7L, 0x78CB67ECF931FA38L, 0xF33B0372323BBF9CL, 0x52D66336FB279C74L, + 0x505F33AC0AFB4EAAL, 0xE8A5CD99A2CCE187L, 0x534974801E2D30BBL, 0x8D2D5711D5876D90L, + 0x1F1A412891BC038EL, 0xD6E2E71D82E56648L, 0x74036C3A497732B7L, 0x89B67ED96361F5ABL, + 0xFFED95D8F1EA02A2L, 0xE72B3BD61464D43DL, 0xA6300F170BDC4820L, 0xEBC18760ED78A77AL + }; + + /** S-Box T2. */ + private static final long[] T2 = { + 0xE6A6BE5A05A12138L, 0xB5A122A5B4F87C98L, 0x563C6089140B6990L, 0x4C46CB2E391F5DD5L, + 0xD932ADDBC9B79434L, 0x08EA70E42015AFF5L, 0xD765A6673E478CF1L, 0xC4FB757EAB278D99L, + 0xDF11C6862D6E0692L, 0xDDEB84F10D7F3B16L, 0x6F2EF604A665EA04L, 0x4A8E0F0FF0E0DFB3L, + 0xA5EDEEF83DBCBA51L, 0xFC4F0A2A0EA4371EL, 0xE83E1DA85CB38429L, 0xDC8FF882BA1B1CE2L, + 0xCD45505E8353E80DL, 0x18D19A00D4DB0717L, 0x34A0CFEDA5F38101L, 0x0BE77E518887CAF2L, + 0x1E341438B3C45136L, 0xE05797F49089CCF9L, 0xFFD23F9DF2591D14L, 0x543DDA228595C5CDL, + 0x661F81FD99052A33L, 0x8736E641DB0F7B76L, 0x15227725418E5307L, 0xE25F7F46162EB2FAL, + 0x48A8B2126C13D9FEL, 0xAFDC541792E76EEAL, 0x03D912BFC6D1898FL, 0x31B1AAFA1B83F51BL, + 0xF1AC2796E42AB7D9L, 0x40A3A7D7FCD2EBACL, 0x1056136D0AFBBCC5L, 0x7889E1DD9A6D0C85L, + 0xD33525782A7974AAL, 0xA7E25D09078AC09BL, 0xBD4138B3EAC6EDD0L, 0x920ABFBE71EB9E70L, + 0xA2A5D0F54FC2625CL, 0xC054E36B0B1290A3L, 0xF6DD59FF62FE932BL, 0x3537354511A8AC7DL, + 0xCA845E9172FADCD4L, 0x84F82B60329D20DCL, 0x79C62CE1CD672F18L, 0x8B09A2ADD124642CL, + 0xD0C1E96A19D9E726L, 0x5A786A9B4BA9500CL, 0x0E020336634C43F3L, 0xC17B474AEB66D822L, + 0x6A731AE3EC9BAAC2L, 0x8226667AE0840258L, 0x67D4567691CAECA5L, 0x1D94155C4875ADB5L, + 0x6D00FD985B813FDFL, 0x51286EFCB774CD06L, 0x5E8834471FA744AFL, 0xF72CA0AEE761AE2EL, + 0xBE40E4CDAEE8E09AL, 0xE9970BBB5118F665L, 0x726E4BEB33DF1964L, 0x703B000729199762L, + 0x4631D816F5EF30A7L, 0xB880B5B51504A6BEL, 0x641793C37ED84B6CL, 0x7B21ED77F6E97D96L, + 0x776306312EF96B73L, 0xAE528948E86FF3F4L, 0x53DBD7F286A3F8F8L, 0x16CADCE74CFC1063L, + 0x005C19BDFA52C6DDL, 0x68868F5D64D46AD3L, 0x3A9D512CCF1E186AL, 0x367E62C2385660AEL, + 0xE359E7EA77DCB1D7L, 0x526C0773749ABE6EL, 0x735AE5F9D09F734BL, 0x493FC7CC8A558BA8L, + 0xB0B9C1533041AB45L, 0x321958BA470A59BDL, 0x852DB00B5F46C393L, 0x91209B2BD336B0E5L, + 0x6E604F7D659EF19FL, 0xB99A8AE2782CCB24L, 0xCCF52AB6C814C4C7L, 0x4727D9AFBE11727BL, + 0x7E950D0C0121B34DL, 0x756F435670AD471FL, 0xF5ADD442615A6849L, 0x4E87E09980B9957AL, + 0x2ACFA1DF50AEE355L, 0xD898263AFD2FD556L, 0xC8F4924DD80C8FD6L, 0xCF99CA3D754A173AL, + 0xFE477BACAF91BF3CL, 0xED5371F6D690C12DL, 0x831A5C285E687094L, 0xC5D3C90A3708A0A4L, + 0x0F7F903717D06580L, 0x19F9BB13B8FDF27FL, 0xB1BD6F1B4D502843L, 0x1C761BA38FFF4012L, + 0x0D1530C4E2E21F3BL, 0x8943CE69A7372C8AL, 0xE5184E11FEB5CE66L, 0x618BDB80BD736621L, + 0x7D29BAD68B574D0BL, 0x81BB613E25E6FE5BL, 0x071C9C10BC07913FL, 0xC7BEEB7909AC2D97L, + 0xC3E58D353BC5D757L, 0xEB017892F38F61E8L, 0xD4EFFB9C9B1CC21AL, 0x99727D26F494F7ABL, + 0xA3E063A2956B3E03L, 0x9D4A8B9A4AA09C30L, 0x3F6AB7D500090FB4L, 0x9CC0F2A057268AC0L, + 0x3DEE9D2DEDBF42D1L, 0x330F49C87960A972L, 0xC6B2720287421B41L, 0x0AC59EC07C00369CL, + 0xEF4EAC49CB353425L, 0xF450244EEF0129D8L, 0x8ACC46E5CAF4DEB6L, 0x2FFEAB63989263F7L, + 0x8F7CB9FE5D7A4578L, 0x5BD8F7644E634635L, 0x427A7315BF2DC900L, 0x17D0C4AA2125261CL, + 0x3992486C93518E50L, 0xB4CBFEE0A2D7D4C3L, 0x7C75D6202C5DDD8DL, 0xDBC295D8E35B6C61L, + 0x60B369D302032B19L, 0xCE42685FDCE44132L, 0x06F3DDB9DDF65610L, 0x8EA4D21DB5E148F0L, + 0x20B0FCE62FCD496FL, 0x2C1B912358B0EE31L, 0xB28317B818F5A308L, 0xA89C1E189CA6D2CFL, + 0x0C6B18576AAADBC8L, 0xB65DEAA91299FAE3L, 0xFB2B794B7F1027E7L, 0x04E4317F443B5BEBL, + 0x4B852D325939D0A6L, 0xD5AE6BEEFB207FFCL, 0x309682B281C7D374L, 0xBAE309A194C3B475L, + 0x8CC3F97B13B49F05L, 0x98A9422FF8293967L, 0x244B16B01076FF7CL, 0xF8BF571C663D67EEL, + 0x1F0D6758EEE30DA1L, 0xC9B611D97ADEB9B7L, 0xB7AFD5887B6C57A2L, 0x6290AE846B984FE1L, + 0x94DF4CDEACC1A5FDL, 0x058A5BD1C5483AFFL, 0x63166CC142BA3C37L, 0x8DB8526EB2F76F40L, + 0xE10880036F0D6D4EL, 0x9E0523C9971D311DL, 0x45EC2824CC7CD691L, 0x575B8359E62382C9L, + 0xFA9E400DC4889995L, 0xD1823ECB45721568L, 0xDAFD983B8206082FL, 0xAA7D29082386A8CBL, + 0x269FCD4403B87588L, 0x1B91F5F728BDD1E0L, 0xE4669F39040201F6L, 0x7A1D7C218CF04ADEL, + 0x65623C29D79CE5CEL, 0x2368449096C00BB1L, 0xAB9BF1879DA503BAL, 0xBC23ECB1A458058EL, + 0x9A58DF01BB401ECCL, 0xA070E868A85F143DL, 0x4FF188307DF2239EL, 0x14D565B41A641183L, + 0xEE13337452701602L, 0x950E3DCF3F285E09L, 0x59930254B9C80953L, 0x3BF299408930DA6DL, + 0xA955943F53691387L, 0xA15EDECAA9CB8784L, 0x29142127352BE9A0L, 0x76F0371FFF4E7AFBL, + 0x0239F450274F2228L, 0xBB073AF01D5E868BL, 0xBFC80571C10E96C1L, 0xD267088568222E23L, + 0x9671A3D48E80B5B0L, 0x55B5D38AE193BB81L, 0x693AE2D0A18B04B8L, 0x5C48B4ECADD5335FL, + 0xFD743B194916A1CAL, 0x2577018134BE98C4L, 0xE77987E83C54A4ADL, 0x28E11014DA33E1B9L, + 0x270CC59E226AA213L, 0x71495F756D1A5F60L, 0x9BE853FB60AFEF77L, 0xADC786A7F7443DBFL, + 0x0904456173B29A82L, 0x58BC7A66C232BD5EL, 0xF306558C673AC8B2L, 0x41F639C6B6C9772AL, + 0x216DEFE99FDA35DAL, 0x11640CC71C7BE615L, 0x93C43694565C5527L, 0xEA038E6246777839L, + 0xF9ABF3CE5A3E2469L, 0x741E768D0FD312D2L, 0x0144B883CED652C6L, 0xC20B5A5BA33F8552L, + 0x1AE69633C3435A9DL, 0x97A28CA4088CFDECL, 0x8824A43C1E96F420L, 0x37612FA66EEEA746L, + 0x6B4CB165F9CF0E5AL, 0x43AA1C06A0ABFB4AL, 0x7F4DC26FF162796BL, 0x6CBACC8E54ED9B0FL, + 0xA6B7FFEFD2BB253EL, 0x2E25BC95B0A29D4FL, 0x86D6A58BDEF1388CL, 0xDED74AC576B6F054L, + 0x8030BDBC2B45805DL, 0x3C81AF70E94D9289L, 0x3EFF6DDA9E3100DBL, 0xB38DC39FDFCC8847L, + 0x123885528D17B87EL, 0xF2DA0ED240B1B642L, 0x44CEFADCD54BF9A9L, 0x1312200E433C7EE6L, + 0x9FFCC84F3A78C748L, 0xF0CD1F72248576BBL, 0xEC6974053638CFE4L, 0x2BA7B67C0CEC4E4CL, + 0xAC2F4DF3E5CE32EDL, 0xCB33D14326EA4C11L, 0xA4E9044CC77E58BCL, 0x5F513293D934FCEFL, + 0x5DC9645506E55444L, 0x50DE418F317DE40AL, 0x388CB31A69DDE259L, 0x2DB4A83455820A86L, + 0x9010A91E84711AE9L, 0x4DF7F0B7B1498371L, 0xD62A2EABC0977179L, 0x22FAC097AA8D5C0EL + }; + + /** S-Box T3. */ + private static final long[] T3 = { + 0xF49FCC2FF1DAF39BL, 0x487FD5C66FF29281L, 0xE8A30667FCDCA83FL, 0x2C9B4BE3D2FCCE63L, + 0xDA3FF74B93FBBBC2L, 0x2FA165D2FE70BA66L, 0xA103E279970E93D4L, 0xBECDEC77B0E45E71L, + 0xCFB41E723985E497L, 0xB70AAA025EF75017L, 0xD42309F03840B8E0L, 0x8EFC1AD035898579L, + 0x96C6920BE2B2ABC5L, 0x66AF4163375A9172L, 0x2174ABDCCA7127FBL, 0xB33CCEA64A72FF41L, + 0xF04A4933083066A5L, 0x8D970ACDD7289AF5L, 0x8F96E8E031C8C25EL, 0xF3FEC02276875D47L, + 0xEC7BF310056190DDL, 0xF5ADB0AEBB0F1491L, 0x9B50F8850FD58892L, 0x4975488358B74DE8L, + 0xA3354FF691531C61L, 0x0702BBE481D2C6EEL, 0x89FB24057DEDED98L, 0xAC3075138596E902L, + 0x1D2D3580172772EDL, 0xEB738FC28E6BC30DL, 0x5854EF8F63044326L, 0x9E5C52325ADD3BBEL, + 0x90AA53CF325C4623L, 0xC1D24D51349DD067L, 0x2051CFEEA69EA624L, 0x13220F0A862E7E4FL, + 0xCE39399404E04864L, 0xD9C42CA47086FCB7L, 0x685AD2238A03E7CCL, 0x066484B2AB2FF1DBL, + 0xFE9D5D70EFBF79ECL, 0x5B13B9DD9C481854L, 0x15F0D475ED1509ADL, 0x0BEBCD060EC79851L, + 0xD58C6791183AB7F8L, 0xD1187C5052F3EEE4L, 0xC95D1192E54E82FFL, 0x86EEA14CB9AC6CA2L, + 0x3485BEB153677D5DL, 0xDD191D781F8C492AL, 0xF60866BAA784EBF9L, 0x518F643BA2D08C74L, + 0x8852E956E1087C22L, 0xA768CB8DC410AE8DL, 0x38047726BFEC8E1AL, 0xA67738B4CD3B45AAL, + 0xAD16691CEC0DDE19L, 0xC6D4319380462E07L, 0xC5A5876D0BA61938L, 0x16B9FA1FA58FD840L, + 0x188AB1173CA74F18L, 0xABDA2F98C99C021FL, 0x3E0580AB134AE816L, 0x5F3B05B773645ABBL, + 0x2501A2BE5575F2F6L, 0x1B2F74004E7E8BA9L, 0x1CD7580371E8D953L, 0x7F6ED89562764E30L, + 0xB15926FF596F003DL, 0x9F65293DA8C5D6B9L, 0x6ECEF04DD690F84CL, 0x4782275FFF33AF88L, + 0xE41433083F820801L, 0xFD0DFE409A1AF9B5L, 0x4325A3342CDB396BL, 0x8AE77E62B301B252L, + 0xC36F9E9F6655615AL, 0x85455A2D92D32C09L, 0xF2C7DEA949477485L, 0x63CFB4C133A39EBAL, + 0x83B040CC6EBC5462L, 0x3B9454C8FDB326B0L, 0x56F56A9E87FFD78CL, 0x2DC2940D99F42BC6L, + 0x98F7DF096B096E2DL, 0x19A6E01E3AD852BFL, 0x42A99CCBDBD4B40BL, 0xA59998AF45E9C559L, + 0x366295E807D93186L, 0x6B48181BFAA1F773L, 0x1FEC57E2157A0A1DL, 0x4667446AF6201AD5L, + 0xE615EBCACFB0F075L, 0xB8F31F4F68290778L, 0x22713ED6CE22D11EL, 0x3057C1A72EC3C93BL, + 0xCB46ACC37C3F1F2FL, 0xDBB893FD02AAF50EL, 0x331FD92E600B9FCFL, 0xA498F96148EA3AD6L, + 0xA8D8426E8B6A83EAL, 0xA089B274B7735CDCL, 0x87F6B3731E524A11L, 0x118808E5CBC96749L, + 0x9906E4C7B19BD394L, 0xAFED7F7E9B24A20CL, 0x6509EADEEB3644A7L, 0x6C1EF1D3E8EF0EDEL, + 0xB9C97D43E9798FB4L, 0xA2F2D784740C28A3L, 0x7B8496476197566FL, 0x7A5BE3E6B65F069DL, + 0xF96330ED78BE6F10L, 0xEEE60DE77A076A15L, 0x2B4BEE4AA08B9BD0L, 0x6A56A63EC7B8894EL, + 0x02121359BA34FEF4L, 0x4CBF99F8283703FCL, 0x398071350CAF30C8L, 0xD0A77A89F017687AL, + 0xF1C1A9EB9E423569L, 0x8C7976282DEE8199L, 0x5D1737A5DD1F7ABDL, 0x4F53433C09A9FA80L, + 0xFA8B0C53DF7CA1D9L, 0x3FD9DCBC886CCB77L, 0xC040917CA91B4720L, 0x7DD00142F9D1DCDFL, + 0x8476FC1D4F387B58L, 0x23F8E7C5F3316503L, 0x032A2244E7E37339L, 0x5C87A5D750F5A74BL, + 0x082B4CC43698992EL, 0xDF917BECB858F63CL, 0x3270B8FC5BF86DDAL, 0x10AE72BB29B5DD76L, + 0x576AC94E7700362BL, 0x1AD112DAC61EFB8FL, 0x691BC30EC5FAA427L, 0xFF246311CC327143L, + 0x3142368E30E53206L, 0x71380E31E02CA396L, 0x958D5C960AAD76F1L, 0xF8D6F430C16DA536L, + 0xC8FFD13F1BE7E1D2L, 0x7578AE66004DDBE1L, 0x05833F01067BE646L, 0xBB34B5AD3BFE586DL, + 0x095F34C9A12B97F0L, 0x247AB64525D60CA8L, 0xDCDBC6F3017477D1L, 0x4A2E14D4DECAD24DL, + 0xBDB5E6D9BE0A1EEBL, 0x2A7E70F7794301ABL, 0xDEF42D8A270540FDL, 0x01078EC0A34C22C1L, + 0xE5DE511AF4C16387L, 0x7EBB3A52BD9A330AL, 0x77697857AA7D6435L, 0x004E831603AE4C32L, + 0xE7A21020AD78E312L, 0x9D41A70C6AB420F2L, 0x28E06C18EA1141E6L, 0xD2B28CBD984F6B28L, + 0x26B75F6C446E9D83L, 0xBA47568C4D418D7FL, 0xD80BADBFE6183D8EL, 0x0E206D7F5F166044L, + 0xE258A43911CBCA3EL, 0x723A1746B21DC0BCL, 0xC7CAA854F5D7CDD3L, 0x7CAC32883D261D9CL, + 0x7690C26423BA942CL, 0x17E55524478042B8L, 0xE0BE477656A2389FL, 0x4D289B5E67AB2DA0L, + 0x44862B9C8FBBFD31L, 0xB47CC8049D141365L, 0x822C1B362B91C793L, 0x4EB14655FB13DFD8L, + 0x1ECBBA0714E2A97BL, 0x6143459D5CDE5F14L, 0x53A8FBF1D5F0AC89L, 0x97EA04D81C5E5B00L, + 0x622181A8D4FDB3F3L, 0xE9BCD341572A1208L, 0x1411258643CCE58AL, 0x9144C5FEA4C6E0A4L, + 0x0D33D06565CF620FL, 0x54A48D489F219CA1L, 0xC43E5EAC6D63C821L, 0xA9728B3A72770DAFL, + 0xD7934E7B20DF87EFL, 0xE35503B61A3E86E5L, 0xCAE321FBC819D504L, 0x129A50B3AC60BFA6L, + 0xCD5E68EA7E9FB6C3L, 0xB01C90199483B1C7L, 0x3DE93CD5C295376CL, 0xAED52EDF2AB9AD13L, + 0x2E60F512C0A07884L, 0xBC3D86A3E36210C9L, 0x35269D9B163951CEL, 0x0C7D6E2AD0CDB5FAL, + 0x59E86297D87F5733L, 0x298EF221898DB0E7L, 0x55000029D1A5AA7EL, 0x8BC08AE1B5061B45L, + 0xC2C31C2B6C92703AL, 0x94CC596BAF25EF42L, 0x0A1D73DB22540456L, 0x04B6A0F9D9C4179AL, + 0xEFFDAFA2AE3D3C60L, 0xF7C8075BB49496C4L, 0x9CC5C7141D1CD4E3L, 0x78BD1638218E5534L, + 0xB2F11568F850246AL, 0xEDFABCFA9502BC29L, 0x796CE5F2DA23051BL, 0xAAE128B0DC93537CL, + 0x3A493DA0EE4B29AEL, 0xB5DF6B2C416895D7L, 0xFCABBD25122D7F37L, 0x70810B58105DC4B1L, + 0xE10FDD37F7882A90L, 0x524DCAB5518A3F5CL, 0x3C9E85878451255BL, 0x4029828119BD34E2L, + 0x74A05B6F5D3CECCBL, 0xB610021542E13ECAL, 0x0FF979D12F59E2ACL, 0x6037DA27E4F9CC50L, + 0x5E92975A0DF1847DL, 0xD66DE190D3E623FEL, 0x5032D6B87B568048L, 0x9A36B7CE8235216EL, + 0x80272A7A24F64B4AL, 0x93EFED8B8C6916F7L, 0x37DDBFF44CCE1555L, 0x4B95DB5D4B99BD25L, + 0x92D3FDA169812FC0L, 0xFB1A4A9A90660BB6L, 0x730C196946A4B9B2L, 0x81E289AA7F49DA68L, + 0x64669A0F83B1A05FL, 0x27B3FF7D9644F48BL, 0xCC6B615C8DB675B3L, 0x674F20B9BCEBBE95L, + 0x6F31238275655982L, 0x5AE488713E45CF05L, 0xBF619F9954C21157L, 0xEABAC46040A8EAE9L, + 0x454C6FE9F2C0C1CDL, 0x419CF6496412691CL, 0xD3DC3BEF265B0F70L, 0x6D0E60F5C3578A9EL + }; + + /** S-Box T4. */ + private static final long[] T4 = { + 0x5B0E608526323C55L, 0x1A46C1A9FA1B59F5L, 0xA9E245A17C4C8FFAL, 0x65CA5159DB2955D7L, + 0x05DB0A76CE35AFC2L, 0x81EAC77EA9113D45L, 0x528EF88AB6AC0A0DL, 0xA09EA253597BE3FFL, + 0x430DDFB3AC48CD56L, 0xC4B3A67AF45CE46FL, 0x4ECECFD8FBE2D05EL, 0x3EF56F10B39935F0L, + 0x0B22D6829CD619C6L, 0x17FD460A74DF2069L, 0x6CF8CC8E8510ED40L, 0xD6C824BF3A6ECAA7L, + 0x61243D581A817049L, 0x048BACB6BBC163A2L, 0xD9A38AC27D44CC32L, 0x7FDDFF5BAAF410ABL, + 0xAD6D495AA804824BL, 0xE1A6A74F2D8C9F94L, 0xD4F7851235DEE8E3L, 0xFD4B7F886540D893L, + 0x247C20042AA4BFDAL, 0x096EA1C517D1327CL, 0xD56966B4361A6685L, 0x277DA5C31221057DL, + 0x94D59893A43ACFF7L, 0x64F0C51CCDC02281L, 0x3D33BCC4FF6189DBL, 0xE005CB184CE66AF1L, + 0xFF5CCD1D1DB99BEAL, 0xB0B854A7FE42980FL, 0x7BD46A6A718D4B9FL, 0xD10FA8CC22A5FD8CL, + 0xD31484952BE4BD31L, 0xC7FA975FCB243847L, 0x4886ED1E5846C407L, 0x28CDDB791EB70B04L, + 0xC2B00BE2F573417FL, 0x5C9590452180F877L, 0x7A6BDDFFF370EB00L, 0xCE509E38D6D9D6A4L, + 0xEBEB0F00647FA702L, 0x1DCC06CF76606F06L, 0xE4D9F28BA286FF0AL, 0xD85A305DC918C262L, + 0x475B1D8732225F54L, 0x2D4FB51668CCB5FEL, 0xA679B9D9D72BBA20L, 0x53841C0D912D43A5L, + 0x3B7EAA48BF12A4E8L, 0x781E0E47F22F1DDFL, 0xEFF20CE60AB50973L, 0x20D261D19DFFB742L, + 0x16A12B03062A2E39L, 0x1960EB2239650495L, 0x251C16FED50EB8B8L, 0x9AC0C330F826016EL, + 0xED152665953E7671L, 0x02D63194A6369570L, 0x5074F08394B1C987L, 0x70BA598C90B25CE1L, + 0x794A15810B9742F6L, 0x0D5925E9FCAF8C6CL, 0x3067716CD868744EL, 0x910AB077E8D7731BL, + 0x6A61BBDB5AC42F61L, 0x93513EFBF0851567L, 0xF494724B9E83E9D5L, 0xE887E1985C09648DL, + 0x34B1D3C675370CFDL, 0xDC35E433BC0D255DL, 0xD0AAB84234131BE0L, 0x08042A50B48B7EAFL, + 0x9997C4EE44A3AB35L, 0x829A7B49201799D0L, 0x263B8307B7C54441L, 0x752F95F4FD6A6CA6L, + 0x927217402C08C6E5L, 0x2A8AB754A795D9EEL, 0xA442F7552F72943DL, 0x2C31334E19781208L, + 0x4FA98D7CEAEE6291L, 0x55C3862F665DB309L, 0xBD0610175D53B1F3L, 0x46FE6CB840413F27L, + 0x3FE03792DF0CFA59L, 0xCFE700372EB85E8FL, 0xA7BE29E7ADBCE118L, 0xE544EE5CDE8431DDL, + 0x8A781B1B41F1873EL, 0xA5C94C78A0D2F0E7L, 0x39412E2877B60728L, 0xA1265EF3AFC9A62CL, + 0xBCC2770C6A2506C5L, 0x3AB66DD5DCE1CE12L, 0xE65499D04A675B37L, 0x7D8F523481BFD216L, + 0x0F6F64FCEC15F389L, 0x74EFBE618B5B13C8L, 0xACDC82B714273E1DL, 0xDD40BFE003199D17L, + 0x37E99257E7E061F8L, 0xFA52626904775AAAL, 0x8BBBF63A463D56F9L, 0xF0013F1543A26E64L, + 0xA8307E9F879EC898L, 0xCC4C27A4150177CCL, 0x1B432F2CCA1D3348L, 0xDE1D1F8F9F6FA013L, + 0x606602A047A7DDD6L, 0xD237AB64CC1CB2C7L, 0x9B938E7225FCD1D3L, 0xEC4E03708E0FF476L, + 0xFEB2FBDA3D03C12DL, 0xAE0BCED2EE43889AL, 0x22CB8923EBFB4F43L, 0x69360D013CF7396DL, + 0x855E3602D2D4E022L, 0x073805BAD01F784CL, 0x33E17A133852F546L, 0xDF4874058AC7B638L, + 0xBA92B29C678AA14AL, 0x0CE89FC76CFAADCDL, 0x5F9D4E0908339E34L, 0xF1AFE9291F5923B9L, + 0x6E3480F60F4A265FL, 0xEEBF3A2AB29B841CL, 0xE21938A88F91B4ADL, 0x57DFEFF845C6D3C3L, + 0x2F006B0BF62CAAF2L, 0x62F479EF6F75EE78L, 0x11A55AD41C8916A9L, 0xF229D29084FED453L, + 0x42F1C27B16B000E6L, 0x2B1F76749823C074L, 0x4B76ECA3C2745360L, 0x8C98F463B91691BDL, + 0x14BCC93CF1ADE66AL, 0x8885213E6D458397L, 0x8E177DF0274D4711L, 0xB49B73B5503F2951L, + 0x10168168C3F96B6BL, 0x0E3D963B63CAB0AEL, 0x8DFC4B5655A1DB14L, 0xF789F1356E14DE5CL, + 0x683E68AF4E51DAC1L, 0xC9A84F9D8D4B0FD9L, 0x3691E03F52A0F9D1L, 0x5ED86E46E1878E80L, + 0x3C711A0E99D07150L, 0x5A0865B20C4E9310L, 0x56FBFC1FE4F0682EL, 0xEA8D5DE3105EDF9BL, + 0x71ABFDB12379187AL, 0x2EB99DE1BEE77B9CL, 0x21ECC0EA33CF4523L, 0x59A4D7521805C7A1L, + 0x3896F5EB56AE7C72L, 0xAA638F3DB18F75DCL, 0x9F39358DABE9808EL, 0xB7DEFA91C00B72ACL, + 0x6B5541FD62492D92L, 0x6DC6DEE8F92E4D5BL, 0x353F57ABC4BEEA7EL, 0x735769D6DA5690CEL, + 0x0A234AA642391484L, 0xF6F9508028F80D9DL, 0xB8E319A27AB3F215L, 0x31AD9C1151341A4DL, + 0x773C22A57BEF5805L, 0x45C7561A07968633L, 0xF913DA9E249DBE36L, 0xDA652D9B78A64C68L, + 0x4C27A97F3BC334EFL, 0x76621220E66B17F4L, 0x967743899ACD7D0BL, 0xF3EE5BCAE0ED6782L, + 0x409F753600C879FCL, 0x06D09A39B5926DB6L, 0x6F83AEB0317AC588L, 0x01E6CA4A86381F21L, + 0x66FF3462D19F3025L, 0x72207C24DDFD3BFBL, 0x4AF6B6D3E2ECE2EBL, 0x9C994DBEC7EA08DEL, + 0x49ACE597B09A8BC4L, 0xB38C4766CF0797BAL, 0x131B9373C57C2A75L, 0xB1822CCE61931E58L, + 0x9D7555B909BA1C0CL, 0x127FAFDD937D11D2L, 0x29DA3BADC66D92E4L, 0xA2C1D57154C2ECBCL, + 0x58C5134D82F6FE24L, 0x1C3AE3515B62274FL, 0xE907C82E01CB8126L, 0xF8ED091913E37FCBL, + 0x3249D8F9C80046C9L, 0x80CF9BEDE388FB63L, 0x1881539A116CF19EL, 0x5103F3F76BD52457L, + 0x15B7E6F5AE47F7A8L, 0xDBD7C6DED47E9CCFL, 0x44E55C410228BB1AL, 0xB647D4255EDB4E99L, + 0x5D11882BB8AAFC30L, 0xF5098BBB29D3212AL, 0x8FB5EA14E90296B3L, 0x677B942157DD025AL, + 0xFB58E7C0A390ACB5L, 0x89D3674C83BD4A01L, 0x9E2DA4DF4BF3B93BL, 0xFCC41E328CAB4829L, + 0x03F38C96BA582C52L, 0xCAD1BDBD7FD85DB2L, 0xBBB442C16082AE83L, 0xB95FE86BA5DA9AB0L, + 0xB22E04673771A93FL, 0x845358C9493152D8L, 0xBE2A488697B4541EL, 0x95A2DC2DD38E6966L, + 0xC02C11AC923C852BL, 0x2388B1990DF2A87BL, 0x7C8008FA1B4F37BEL, 0x1F70D0C84D54E503L, + 0x5490ADEC7ECE57D4L, 0x002B3C27D9063A3AL, 0x7EAEA3848030A2BFL, 0xC602326DED2003C0L, + 0x83A7287D69A94086L, 0xC57A5FCB30F57A8AL, 0xB56844E479EBE779L, 0xA373B40F05DCBCE9L, + 0xD71A786E88570EE2L, 0x879CBACDBDE8F6A0L, 0x976AD1BCC164A32FL, 0xAB21E25E9666D78BL, + 0x901063AAE5E5C33CL, 0x9818B34448698D90L, 0xE36487AE3E1E8ABBL, 0xAFBDF931893BDCB4L, + 0x6345A0DC5FBBD519L, 0x8628FE269B9465CAL, 0x1E5D01603F9C51ECL, 0x4DE44006A15049B7L, + 0xBF6C70E5F776CBB1L, 0x411218F2EF552BEDL, 0xCB0C0708705A36A3L, 0xE74D14754F986044L, + 0xCD56D9430EA8280EL, 0xC12591D7535F5065L, 0xC83223F1720AEF96L, 0xC3A0396F7363A51FL + }; + protected static Boolean valid; // The cached self-test result. + + protected long a, b, c; // The context. + + public Tiger192() {super("tiger192", HASH_SIZE, BLOCK_SIZE);} + + /** + * Private copying constructor for cloning. + * + * @param that The instance being cloned. + */ + private Tiger192(Tiger192 that) { + this(); + this.a = that.a; + this.b = that.b; + this.c = that.c; + this.count = that.count; + this.buffer = (that.buffer != null) ? (byte[]) that.buffer.clone() : null; + } + + // Instance methods implementing BaseHash. + // ----------------------------------------------------------------------- + + public Object clone() { + return new Tiger192(this); + } +} + +//-------------------------------------------------------------------------- +//$Id: Tiger.java,v 1.1 2003/03/22 03:09:57 rsdio Exp $ +// +//Copyright (C) 2003, Free Software Foundation, Inc. +// +//This file is part of GNU Crypto. +// +//GNU Crypto is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2, or (at your option) +//any later version. +// +//GNU Crypto is distributed in the hope that it will be useful, but +//WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +//General Public License for more details. +// +//You should have received a copy of the GNU General Public License +//along with this program; see the file COPYING. If not, write to the +// +// Free Software Foundation Inc., +// 59 Temple Place - Suite 330, +// Boston, MA 02111-1307 +// USA +// +//Linking this library statically or dynamically with other modules is +//making a combined work based on this library. Thus, the terms and +//conditions of the GNU General Public License cover the whole +//combination. +// +//As a special exception, the copyright holders of this library give +//you permission to link this library with independent modules to +//produce an executable, regardless of the license terms of these +//independent modules, and to copy and distribute the resulting +//executable under terms of your choice, provided that you also meet, +//for each linked independent module, the terms and conditions of the +//license of that module. An independent module is a module which is +//not derived from or based on this library. If you modify this +//library, you may extend this exception to your version of the +//library, but you are not obligated to do so. If you do not wish to +//do so, delete this exception statement from your version. +// +//-------------------------------------------------------------------------- + +/** +* The Tiger message digest. Tiger was designed by Ross Anderson and Eli +* Biham, with the goal of producing a secure, fast hash function that +* performs especially well on next-generation 64-bit architectures, but +* is still efficient on 32- and 16-bit architectures. +* +*

Tiger processes data in 512-bit blocks and produces a 192-bit +* digest.

+* +*

References:

+*
    +*
  1. Tiger: A +* Fast New Hash Function, Ross Anderson and Eli Biham.
  2. +*
+* +* @version $Revision: 1.1 $ +*/ +abstract class BaseHash { + /** The canonical name prefix of the hash. */ + protected String name; + + /** The hash (output) size in bytes. */ + protected int hashSize; + + /** The hash (inner) block size in bytes. */ + protected int blockSize; + + /** Number of bytes processed so far. */ + protected long count; + + /** Temporary input buffer. */ + protected byte[] buffer; + + // Constructor(s) + // ------------------------------------------------------------------------- + + /** + *

Trivial constructor for use by concrete subclasses.

+ * + * @param name the canonical name prefix of this instance. + * @param hashSize the block size of the output in bytes. + * @param blockSize the block size of the internal transform. + */ + protected BaseHash(String name, int hashSize, int blockSize) { + super(); + + this.name = name; + this.hashSize = hashSize; + this.blockSize = blockSize; + this.buffer = new byte[blockSize]; + + resetContext(); + } + + // Class methods + // ------------------------------------------------------------------------- + + // Instance methods + // ------------------------------------------------------------------------- + + // IMessageDigest interface implementation --------------------------------- + + public String name() { + return name; + } + + public int hashSize() { + return hashSize; + } + + public int blockSize() { + return blockSize; + } + + public void update(byte b) { + // compute number of bytes still unhashed; ie. present in buffer + int i = (int)(count % blockSize); + count++; + buffer[i] = b; + if (i == (blockSize - 1)) { + transform(buffer, 0); + } + } + + public void update(byte[] b, int offset, int len) { + int n = (int)(count % blockSize); + count += len; + int partLen = blockSize - n; + int i = 0; + + if (len >= partLen) { + System.arraycopy(b, offset, buffer, n, partLen); + transform(buffer, 0); + for (i = partLen; i + blockSize - 1 < len; i+= blockSize) { + transform(b, offset + i); + } + n = 0; + } + + if (i < len) { + System.arraycopy(b, offset + i, buffer, n, len - i); + } + } + + public byte[] digest() { + byte[] tail = padBuffer(); // pad remaining bytes in buffer + update(tail, 0, tail.length); // last transform of a message + byte[] result = getResult(); // make a result out of context + + reset(); // reset this instance for future re-use + + return result; + } + + public void reset() { // reset this instance for future re-use + count = 0L; + for (int i = 0; i < blockSize; ) { + buffer[i++] = 0; + } + + resetContext(); + } + + // methods to be implemented by concrete subclasses ------------------------ + + public abstract Object clone(); + + /** + *

Returns the byte array to use as padding before completing a hash + * operation.

+ * + * @return the bytes to pad the remaining bytes in the buffer before + * completing a hash operation. + */ + protected abstract byte[] padBuffer(); + + /** + *

Constructs the result from the contents of the current context.

+ * + * @return the output of the completed hash operation. + */ + protected abstract byte[] getResult(); + + /** Resets the instance for future re-use. */ + protected abstract void resetContext(); + + /** + *

The block digest transformation per se.

+ * + * @param in the blockSize long block, as an array of bytes to digest. + * @param offset the index where the data to digest is located within the + * input buffer. + */ + protected abstract void transform(byte[] in, int offset); +} + +//---------------------------------------------------------------------------- +//$Id: BaseHash.java,v 1.8 2002/11/07 17:17:45 raif Exp $ +// +//Copyright (C) 2001, 2002, Free Software Foundation, Inc. +// +//This file is part of GNU Crypto. +// +//GNU Crypto is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2, or (at your option) +//any later version. +// +//GNU Crypto is distributed in the hope that it will be useful, but +//WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +//General Public License for more details. +// +//You should have received a copy of the GNU General Public License +//along with this program; see the file COPYING. If not, write to the +// +// Free Software Foundation Inc., +// 59 Temple Place - Suite 330, +// Boston, MA 02111-1307 +// USA +// +//Linking this library statically or dynamically with other modules is +//making a combined work based on this library. Thus, the terms and +//conditions of the GNU General Public License cover the whole +//combination. +// +//As a special exception, the copyright holders of this library give +//you permission to link this library with independent modules to +//produce an executable, regardless of the license terms of these +//independent modules, and to copy and distribute the resulting +//executable under terms of your choice, provided that you also meet, +//for each linked independent module, the terms and conditions of the +//license of that module. An independent module is a module which is +//not derived from or based on this library. If you modify this +//library, you may extend this exception to your version of the +//library, but you are not obligated to do so. If you do not wish to +//do so, delete this exception statement from your version. +//---------------------------------------------------------------------------- +/** + *

A base abstract class to facilitate hash implementations.

+ * + * @version $Revision: 1.8 $ + */ diff --git a/100_core/src/gplx/core/security/Hash_algo__tth_192__tst.java b/100_core/src/gplx/core/security/Hash_algo__tth_192__tst.java index a27517de8..9d19d6065 100644 --- a/100_core/src/gplx/core/security/Hash_algo__tth_192__tst.java +++ b/100_core/src/gplx/core/security/Hash_algo__tth_192__tst.java @@ -13,3 +13,22 @@ 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.core.security; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.consoles.*; import gplx.core.ios.*; /*IoStream*/ +public class Hash_algo__tth_192__tst { // REF: http://open-content.net/specs/draft-jchapweske-thex-02.html; DC++ 0.698 + private final Hash_algo__fxt fxt = new Hash_algo__fxt(Hash_algo_.New__tth_192()); + @Test public void Empty() {fxt.Test__hash("LWPNACQDBZRYXW3VHJVCJ64QBZNGHOHHHZWCLNQ", "");} + @Test public void Null__1() {fxt.Test__hash("VK54ZIEEVTWNAUI5D5RDFIL37LX2IQNSTAXFKSA", "\0");} + @Test public void ab() {fxt.Test__hash("XQXRSGMB3PSN2VGZYJMNJG6SOOQ3JIGQHD2I6PQ", "ab");} + @Test public void abc() {fxt.Test__hash("ASD4UJSEH5M47PDYB46KBTSQTSGDKLBHYXOMUIA", "abc");} + @Test public void abde() {fxt.Test__hash("SQF2PFTVIFRR5KJSI45IDENXMB43NI7EIXYGHGI", "abcd");} + @Test public void abcde() {fxt.Test__hash("SKGLNP5WV7ZUMF6IUK5CYXBE3PI4C6PHWNVM2YQ", "abcde");} + @Test public void abcdefghi() {fxt.Test__hash("RUIKHZFO4NIY6NNUHJMAC2I26U3U65FZWCO3UFY", "abcdefghi");} + // @Test + public void a__x_1024() {fxt.Test__hash("L66Q4YVNAFWVS23X2HJIRA5ZJ7WXR3F26RSASFA", String_.Repeat("A", 1024));} + // @Test + public void a__x_1025() {fxt.Test__hash("PZMRYHGY6LTBEH63ZWAHDORHSYTLO4LEFUIKHWY", String_.Repeat("A", 1025));} + // @Test + public void A__Pow27() {fxt.Test__hash("QNIJO36QDIQREUT3HWK4MDVKD2T6OENAEKYADTQ", String_.Repeat("A", (int)Math_.Pow(2, 27))); + } +} diff --git a/100_core/src/gplx/core/security/Hash_algo__tth_192_tree_tst.java b/100_core/src/gplx/core/security/Hash_algo__tth_192_tree_tst.java index a27517de8..e2c744568 100644 --- a/100_core/src/gplx/core/security/Hash_algo__tth_192_tree_tst.java +++ b/100_core/src/gplx/core/security/Hash_algo__tth_192_tree_tst.java @@ -13,3 +13,51 @@ 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.core.security; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Hash_algo__tth_192_tree_tst { + @Test public void CalcRecursiveHalves() { + tst_CalcRecursiveHalves(129, 128); + tst_CalcRecursiveHalves(128, 127); + tst_CalcRecursiveHalves(100, 99); + tst_CalcRecursiveHalves(20, 19); + tst_CalcRecursiveHalves(6, 5); + tst_CalcRecursiveHalves(5, 4); + tst_CalcRecursiveHalves(4, 3); + tst_CalcRecursiveHalves(3, 2); + tst_CalcRecursiveHalves(2, 1); + tst_CalcRecursiveHalves(1, 0); + tst_CalcRecursiveHalves(0, 0); + } + @Test public void CalcWorkUnits() { + tst_CalcWorkUnits(101, 21); // leafs; 10 full, 1 part (+11) -> reduce 11 to 5+1 (+5) -> reduce 6 to 3 (+3) -> reduce 3 to 1+1 (+1) -> reduce 2 to 1 (+1) + tst_CalcWorkUnits(100, 19); // leafs; 10 full (+10) -> reduce 10 to 5 (+5) -> reduce 5 to 2+1 (+2) -> reduce 3 to 1+1 (+1) -> reduce 2 to 1 (+1) + tst_CalcWorkUnits(30, 5); // leafs; 3 full (+3) -> reduce 3 to 1+1 (+1) -> reduce 2 to 1 (+1) + tst_CalcWorkUnits(11, 3); // leafs: 1 full, 1 part (+2) -> reduce 2 to 1 (+1) + tst_CalcWorkUnits(10, 1); + tst_CalcWorkUnits(9, 1); + tst_CalcWorkUnits(1, 1); + tst_CalcWorkUnits(0, 1); + } + void tst_CalcWorkUnits(int length, int expd) { + Hash_algo__tth_192 algo = new Hash_algo__tth_192(); algo.BlockSize_set(10); + int actl = algo.CalcWorkUnits(length); + Tfds.Eq(expd, actl); + } + void tst_CalcRecursiveHalves(int val, int expd) { + int actl = CalcRecursiveHalvesMock(val); + Tfds.Eq(expd, actl); + } + int CalcRecursiveHalvesMock(int val) { + if (val <= 1) return 0; + int rv = 0; + while (true) { + int multiple = val / 2; + int remainder = val % 2; + rv += multiple; + val = multiple + remainder; + if (val == 1) + return remainder == 0 ? rv : ++rv; + } + } +} diff --git a/100_core/src/gplx/core/security/Hash_console_wtr_tst.java b/100_core/src/gplx/core/security/Hash_console_wtr_tst.java index a27517de8..3e0cd5431 100644 --- a/100_core/src/gplx/core/security/Hash_console_wtr_tst.java +++ b/100_core/src/gplx/core/security/Hash_console_wtr_tst.java @@ -13,3 +13,27 @@ 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.core.security; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.consoles.*; import gplx.core.ios.streams.*; /*IoStream*/ +public class Hash_console_wtr_tst { + @Before public void setup() { + Hash_algo__tth_192 algo = new Hash_algo__tth_192(); + algo.BlockSize_set(10); + calc = algo; + } + @Test public void Basic() { + tst_Status(10, stringAry_(" - hash: 100%")); + tst_Status(11, stringAry_(" - hash: 66%")); + tst_Status(30, stringAry_(" - hash: 40%", " - hash: 60%", " - hash: 100%")); + } + void tst_Status(int count, String[] expdWritten) { + Console_adp__mem dialog = Console_adp_.Dev(); + String data = String_.Repeat("A", count); + IoStream stream = IoStream_.mem_txt_(Io_url_.Empty, data); + calc.Hash_stream_as_str(dialog, stream); + String[] actlWritten = dialog.Written().To_str_ary(); + Tfds.Eq_ary(actlWritten, expdWritten); + } + String[] stringAry_(String... ary) {return ary;} + Hash_algo calc; +} diff --git a/100_core/src/gplx/core/stores/DataRdr.java b/100_core/src/gplx/core/stores/DataRdr.java index a27517de8..025694370 100644 --- a/100_core/src/gplx/core/stores/DataRdr.java +++ b/100_core/src/gplx/core/stores/DataRdr.java @@ -13,3 +13,37 @@ 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.core.stores; import gplx.*; import gplx.core.*; +import gplx.core.strings.*; +public interface DataRdr extends SrlMgr, Rls_able { + String NameOfNode(); String To_str(); + Io_url Uri(); void Uri_set(Io_url s); + Hash_adp EnvVars(); + boolean Parse(); void Parse_set(boolean v); + + int FieldCount(); + String KeyAt(int i); + Object ReadAt(int i); + Keyval KeyValAt(int i); + + Object Read(String key); + String ReadStr(String key); String ReadStrOr(String key, String or); + byte[] ReadBryByStr(String key); byte[] ReadBryByStrOr(String key, byte[] or); + byte[] ReadBry(String key); byte[] ReadBryOr(String key, byte[] or); + char ReadChar(String key); char ReadCharOr(String key, char or); + int ReadInt(String key); int ReadIntOr(String key, int or); + boolean ReadBool(String key); boolean ReadBoolOr(String key, boolean or); + long ReadLong(String key); long ReadLongOr(String key, long or); + double ReadDouble(String key); double ReadDoubleOr(String key, double or); + float ReadFloat(String key); float ReadFloatOr(String key, float or); + byte ReadByte(String key); byte ReadByteOr(String key, byte or); + Decimal_adp ReadDecimal(String key); Decimal_adp ReadDecimalOr(String key, Decimal_adp or); + DateAdp ReadDate(String key); DateAdp ReadDateOr(String key, DateAdp or); + gplx.core.ios.streams.Io_stream_rdr ReadRdr(String key); + + boolean MoveNextPeer(); + DataRdr Subs(); + DataRdr Subs_byName(String name); + DataRdr Subs_byName_moveFirst(String name); + void XtoStr_gfml(String_bldr sb); +} diff --git a/100_core/src/gplx/core/stores/DataRdr_.java b/100_core/src/gplx/core/stores/DataRdr_.java index a27517de8..42aa7c6ec 100644 --- a/100_core/src/gplx/core/stores/DataRdr_.java +++ b/100_core/src/gplx/core/stores/DataRdr_.java @@ -13,3 +13,60 @@ 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.core.stores; import gplx.*; import gplx.core.*; +import gplx.core.strings.*; +public class DataRdr_ { + public static final DataRdr Null = new DataRdr_null(); + public static DataRdr as_(Object obj) {return obj instanceof DataRdr ? (DataRdr)obj : null;} + public static DataRdr cast(Object obj) {try {return (DataRdr)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, DataRdr.class, obj);}} + + public static Object Read_1st_row_and_1st_fld(DataRdr rdr) { + try {return rdr.MoveNextPeer() ? rdr.ReadAt(0) : null;} + finally {rdr.Rls();} + } + +} +class DataRdr_null implements DataRdr { + public String NameOfNode() {return To_str();} public String To_str() {return "<< NULL READER >>";} + public boolean Type_rdr() {return true;} + public Hash_adp EnvVars() {return Hash_adp_.Noop;} + public Io_url Uri() {return Io_url_.Empty;} public void Uri_set(Io_url s) {} + public boolean Parse() {return parse;} public void Parse_set(boolean v) {parse = v;} private boolean parse; + public int FieldCount() {return 0;} + public String KeyAt(int i) {return To_str();} + public Object ReadAt(int i) {return null;} + public Keyval KeyValAt(int i) {return Keyval_.new_(this.KeyAt(i), this.ReadAt(i));} + public Object Read(String name) {return null;} + public String ReadStr(String key) {return String_.Empty;} public String ReadStrOr(String key, String or) {return or;} + public byte[] ReadBryByStr(String key) {return Bry_.Empty;} public byte[] ReadBryByStrOr(String key, byte[] or) {return or;} + public byte[] ReadBry(String key) {return Bry_.Empty;} public byte[] ReadBryOr(String key, byte[] or) {return or;} + public char ReadChar(String key) {return Char_.Null;} public char ReadCharOr(String key, char or) {return or;} + public int ReadInt(String key) {return Int_.Min_value;} public int ReadIntOr(String key, int or) {return or;} + public boolean ReadBool(String key) {return false;} public boolean ReadBoolOr(String key, boolean or) {return or;} + public long ReadLong(String key) {return Long_.Min_value;} public long ReadLongOr(String key, long or) {return or;} + public double ReadDouble(String key) {return Double_.NaN;} public double ReadDoubleOr(String key, double or) {return or;} + public float ReadFloat(String key) {return Float_.NaN;} public float ReadFloatOr(String key, float or) {return or;} + public byte ReadByte(String key) {return Byte_.Min_value;} public byte ReadByteOr(String key, byte or) {return or;} + public Decimal_adp ReadDecimal(String key) {return Decimal_adp_.Zero;}public Decimal_adp ReadDecimalOr(String key, Decimal_adp or) {return or;} + public DateAdp ReadDate(String key) {return DateAdp_.MinValue;} public DateAdp ReadDateOr(String key, DateAdp or) {return or;} + public gplx.core.ios.streams.Io_stream_rdr ReadRdr(String key) {return gplx.core.ios.streams.Io_stream_rdr_.Noop;} + public boolean MoveNextPeer() {return false;} + public DataRdr Subs() {return this;} + public DataRdr Subs_byName(String name) {return this;} + public DataRdr Subs_byName_moveFirst(String name) {return this;} + public Object StoreRoot(SrlObj root, String key) {return null;} + public boolean SrlBoolOr(String key, boolean v) {return v;} + public byte SrlByteOr(String key, byte v) {return v;} + public int SrlIntOr(String key, int or) {return or;} + public long SrlLongOr(String key, long or) {return or;} + public String SrlStrOr(String key, String or) {return or;} + public DateAdp SrlDateOr(String key, DateAdp or) {return or;} + public Decimal_adp SrlDecimalOr(String key, Decimal_adp or) {return or;} + public double SrlDoubleOr(String key, double or) {return or;} + public Object SrlObjOr(String key, Object or) {return or;} + public void SrlList(String key, List_adp list, SrlObj proto, String itmKey) {} + public void TypeKey_(String v) {} + public void XtoStr_gfml(String_bldr sb) {sb.Add_str_w_crlf("NULL:;");} + public SrlMgr SrlMgr_new(Object o) {return this;} + public void Rls() {} +} diff --git a/100_core/src/gplx/core/stores/DataRdr_base.java b/100_core/src/gplx/core/stores/DataRdr_base.java index a27517de8..2d500e941 100644 --- a/100_core/src/gplx/core/stores/DataRdr_base.java +++ b/100_core/src/gplx/core/stores/DataRdr_base.java @@ -13,3 +13,201 @@ 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.core.stores; import gplx.*; import gplx.core.*; +import gplx.core.strings.*; import gplx.core.type_xtns.*; +import gplx.core.ios.streams.*; +public abstract class DataRdr_base implements SrlMgr { + public boolean Parse() {return parse;} public void Parse_set(boolean v) {parse = v;} private boolean parse; + public Io_url Uri() {return uri;} public void Uri_set(Io_url s) {uri = s;} Io_url uri = Io_url_.Empty; + public abstract String NameOfNode(); + public boolean Type_rdr() {return true;} + public Hash_adp EnvVars() {return envVars;} Hash_adp envVars = Hash_adp_.New(); + public abstract Object Read(String key); + public abstract int FieldCount(); + public abstract String KeyAt(int i); + public abstract Object ReadAt(int i); + @gplx.Virtual public Keyval KeyValAt(int idx) {return Keyval_.new_(this.KeyAt(idx), ReadAt(idx));} + public String ReadStr(String key) { + Object val = Read(key); + try {return (String)val;} + catch (Exception exc) {throw Err_dataRdr_ReadFailed_err(String.class, key, val, exc);} + } + public String ReadStrOr(String key, String or) { + Object val = Read(key); if (val == null) return or; + try {return (String)val;} + catch (Exception exc) {Err_dataRdr_ReadFailed_useOr(exc, String.class, key, val, or); return or;} + } + public byte[] ReadBryByStr(String key) {return Bry_.new_u8(ReadStr(key));} + public byte[] ReadBryByStrOr(String key, byte[] or) { + Object val = Read(key); if (val == null) return or; + try {return Bry_.new_u8((String)val);} + catch (Exception exc) {Err_dataRdr_ReadFailed_useOr(exc, byte[].class, key, val, or); return or;} + } + @gplx.Virtual public void SrlList(String key, List_adp list, SrlObj proto, String itmKey) { + list.Clear(); + DataRdr subRdr = this.Subs_byName_moveFirst(key); // collection node + subRdr = subRdr.Subs(); + while (subRdr.MoveNextPeer()) { + SrlObj itm = proto.SrlObj_New(null); + itm.SrlObj_Srl(subRdr); + list.Add(itm); + } + } + @gplx.Virtual public Object StoreRoot(SrlObj root, String key) { + SrlObj clone = root.SrlObj_New(null); + clone.SrlObj_Srl(this); + return clone; + } + public abstract DataRdr Subs_byName_moveFirst(String name); + + public int ReadInt(String key) { + Object val = Read(key); + try {return (parse) ? Int_.Parse(String_.as_(val)) : Int_.Cast(val);} + catch (Exception exc) {throw Err_dataRdr_ReadFailed_err(int.class, key, val, exc);} + } + public int ReadIntOr(String key, int or) { + Object val = Read(key); if (val == null) return or; + try {return (parse) ? Int_.Parse(String_.as_(val)) : Int_.Cast(val);} + catch (Exception exc) {Err_dataRdr_ReadFailed_useOr(exc, int.class, key, val, or); return or;} + } + public long ReadLongOr(String key, long or) { + Object val = Read(key); if (val == null) return or; + try {return (parse) ? Long_.parse(String_.as_(val)) : Long_.cast(val);} + catch (Exception exc) {Err_dataRdr_ReadFailed_useOr(exc, long.class, key, val, or); return or;} + } + @gplx.Virtual public boolean ReadBool(String key) { + Object val = Read(key); + try {return (parse) ? Bool_.Cast(BoolClassXtn.Instance.ParseOrNull(String_.as_(val))) : Bool_.Cast(val);} + catch (Exception exc) {throw Err_dataRdr_ReadFailed_err(boolean.class, key, val, exc);} + } + @gplx.Virtual public boolean ReadBoolOr(String key, boolean or) { + Object val = Read(key); if (val == null) return or; + try {return (parse) ? Bool_.Parse(String_.as_(val)) : Bool_.Cast(val);} + catch (Exception exc) {Err_dataRdr_ReadFailed_useOr(exc, boolean.class, key, val, or); return or;} + } + public long ReadLong(String key) { + Object val = Read(key); + try {return (parse) ? Long_.parse(String_.as_(val)) : Long_.cast(val);} + catch (Exception exc) {throw Err_dataRdr_ReadFailed_err(long.class, key, val, exc);} + } + public float ReadFloat(String key) { + Object val = Read(key); + try {return (parse) ? Float_.parse(String_.as_(val)) : Float_.cast(val);} + catch (Exception exc) {throw Err_dataRdr_ReadFailed_err(float.class, key, val, exc);} + } + public float ReadFloatOr(String key, float or) { + Object val = Read(key); if (val == null) return or; + try {return (parse) ? Float_.parse(String_.as_(val)) : Float_.cast(val);} + catch (Exception exc) {Err_dataRdr_ReadFailed_useOr(exc, float.class, key, val, or); return or;} + } + public double ReadDouble(String key) { + Object val = Read(key); + try {return (parse) ? Double_.parse(String_.as_(val)) : Double_.cast(val);} + catch (Exception exc) {throw Err_dataRdr_ReadFailed_err(double.class, key, val, exc);} + } + public double ReadDoubleOr(String key, double or) { + Object val = Read(key); if (val == null) return or; + try {return (parse) ? Double_.parse(String_.as_(val)) : Double_.cast(val);} + catch (Exception exc) {Err_dataRdr_ReadFailed_useOr(exc, double.class, key, val, or); return or;} + } + @gplx.Virtual public byte ReadByte(String key) { + Object val = Read(key); + try {return (parse) ? Byte_.Parse(String_.as_(val)) : Byte_.Cast(val);} + catch (Exception exc) {throw Err_dataRdr_ReadFailed_err(byte.class, key, val, exc);} + } + @gplx.Virtual public byte ReadByteOr(String key, byte or) { + Object val = Read(key); if (val == null) return or; + try {return (parse) ? Byte_.Parse(String_.as_(val)) : Byte_.Cast(val);} + catch (Exception exc) {Err_dataRdr_ReadFailed_useOr(exc, byte.class, key, val, or); return or;} + } + @gplx.Virtual public DateAdp ReadDate(String key) { + Object val = Read(key); + try {return (parse) ? DateAdp_.parse_gplx(String_.as_(val)) : (DateAdp)val;} + catch (Exception exc) {throw Err_dataRdr_ReadFailed_err(DateAdp.class, key, val, exc);} + } + @gplx.Virtual public DateAdp ReadDateOr(String key, DateAdp or) { + Object val = Read(key); if (val == null) return or; + try {return (parse) ? DateAdp_.parse_gplx(String_.as_(val)) : (DateAdp)val;} + catch (Exception exc) {throw Err_dataRdr_ReadFailed_err(DateAdp.class, key, val, exc);} + } + @gplx.Virtual public Decimal_adp ReadDecimal(String key) { + Object val = Read(key); + try { + if (parse) return Decimal_adp_.parse(String_.as_(val)); + Decimal_adp rv = Decimal_adp_.as_(val); + return (rv == null) + ? Decimal_adp_.db_(val) // HACK: GfoNde_.rdr_ will call ReadAt(int i) on Db_data_rdr; since no Db_data_rdr knows about Decimal_adp, it will always return decimalType + : rv; + } + catch (Exception exc) {throw Err_dataRdr_ReadFailed_err(Decimal_adp.class, key, val, exc);} + } + @gplx.Virtual public Decimal_adp ReadDecimalOr(String key, Decimal_adp or) { + Object val = Read(key); if (val == null) return or; + try { + if (parse) return Decimal_adp_.parse(String_.as_(val)); + Decimal_adp rv = Decimal_adp_.as_(val); + return (rv == null) + ? Decimal_adp_.db_(val) // HACK: GfoNde_.rdr_ will call ReadAt(int i) on Db_data_rdr; since no Db_data_rdr knows about Decimal_adp, it will always return decimalType + : rv; + } + catch (Exception exc) {throw Err_dataRdr_ReadFailed_err(Decimal_adp.class, key, val, exc);} + } + public char ReadChar(String key) { + Object val = Read(key); + try { + if (parse) return Char_.parse(String_.as_(val)); + return Char_.cast(val); + } + catch (Exception exc) {throw Err_dataRdr_ReadFailed_err(char.class, key, val, exc);} + } + public char ReadCharOr(String key, char or) { + Object val = Read(key); if (val == null) return or; + try { + if (parse) return Char_.parse(String_.as_(val)); + return Char_.cast(val); + } + catch (Exception exc) {Err_.Noop(exc); return or;} + } + public byte[] ReadBry(String key) { + Object val = Read(key); + try {return (byte[])val;} + catch (Exception exc) {throw Err_dataRdr_ReadFailed_err(byte[].class, key, val, exc);} + } + public byte[] ReadBryOr(String key, byte[] or) { + Object val = Read(key); if (val == null) return or; + try {return (byte[])val;} + catch (Exception exc) {Err_dataRdr_ReadFailed_useOr(exc, byte[].class, key, val, or); return or;} + } + public gplx.core.ios.streams.Io_stream_rdr ReadRdr(String key) {return gplx.core.ios.streams.Io_stream_rdr_.Noop;} + public boolean SrlBoolOr(String key, boolean or) {return ReadBoolOr(key, or);} + public byte SrlByteOr(String key, byte or) {return ReadByteOr(key, or);} + public int SrlIntOr(String key, int or) {return ReadIntOr(key, or);} + public long SrlLongOr(String key, long or) {return ReadLongOr(key, or);} + public String SrlStrOr(String key, String or) {return ReadStrOr(key, or);} + public DateAdp SrlDateOr(String key, DateAdp or) {return ReadDateOr(key, or);} + public Decimal_adp SrlDecimalOr(String key, Decimal_adp or) {return ReadDecimalOr(key, or);} + public double SrlDoubleOr(String key, double or) {return ReadDoubleOr(key, or);} + public Object SrlObjOr(String key, Object or) {throw Err_.new_unimplemented();} + public void XtoStr_gfml(String_bldr sb) { + sb.Add(this.NameOfNode()).Add(":"); + for (int i = 0; i < this.FieldCount(); i++) { + Keyval kv = this.KeyValAt(i); + if (i != 0) sb.Add(" "); + sb.Add_fmt("{0}='{1}'", kv.Key(), String_.Replace(kv.Val_to_str_or_empty(), "'", "\"")); + } + sb.Add(";"); + } + public abstract DataRdr Subs(); + public void TypeKey_(String v) {} + public abstract SrlMgr SrlMgr_new(Object o); + static Err Err_dataRdr_ReadFailed_err(Class type, String key, Object val, Exception inner) { + String innerMsg = inner == null ? "" : Err_.Message_lang(inner); + return Err_.new_("DataRdr_ReadFailed", "failed to read data", "key", key, "val", val, "type", type, "innerMsg", innerMsg).Trace_ignore_add_1_(); + } + static void Err_dataRdr_ReadFailed_useOr(Class type, String key, Object val, Object or) { + UsrDlg_.Instance.Warn(UsrMsg.new_("failed to read data; substituting default").Add("key", key).Add("val", val).Add("default", or).Add("type", type)); + } + static void Err_dataRdr_ReadFailed_useOr(Exception exc, Class type, String key, Object val, Object or) { + UsrDlg_.Instance.Warn(UsrMsg.new_("failed to read data; substituting default").Add("key", key).Add("val", val).Add("default", or).Add("type", type)); + } +} diff --git a/100_core/src/gplx/core/stores/DataRdr_mem.java b/100_core/src/gplx/core/stores/DataRdr_mem.java index a27517de8..89a4171fa 100644 --- a/100_core/src/gplx/core/stores/DataRdr_mem.java +++ b/100_core/src/gplx/core/stores/DataRdr_mem.java @@ -13,3 +13,65 @@ 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.core.stores; import gplx.*; import gplx.core.*; +import gplx.core.gfo_ndes.*; +public class DataRdr_mem extends DataRdr_base implements GfoNdeRdr { + @Override public String NameOfNode() {return cur.Name();} + public GfoNde UnderNde() {return cur;} + @Override public int FieldCount() {return flds.Count();} + @Override public String KeyAt(int i) {return flds.Get_at(i).Key();} + @Override public Object ReadAt(int i) {return cur.ReadAt(i);} + @Override public Object Read(String key) { + int i = flds.Idx_of(key); if (i == List_adp_.Not_found) return null; + return cur.ReadAt(i); + } + public boolean MoveNextPeer() { + if (++peerPos >= peerList.Count()) { + cur = null; + return false; + } + cur = peerList.FetchAt_asGfoNde(peerPos); + return true; + } + @Override public DataRdr Subs() { + if (cur == null && peerList.Count() == 0) return DataRdr_.Null; + return GfoNdeRdr_.peers_(Peers_get(), this.Parse()); + } + public DataRdr Subs_byName(String name) { + if (cur == null && peerList.Count() == 0) return DataRdr_.Null; + String[] names = String_.Split(name, "/"); + GfoNdeList list = GfoNdeList_.new_(); + Subs_byName(list, names, 0, Peers_get()); + return GfoNdeRdr_.peers_(list, this.Parse()); + } + @Override public DataRdr Subs_byName_moveFirst(String name) { + DataRdr subRdr = Subs_byName(name); + boolean hasFirst = subRdr.MoveNextPeer(); + return (hasFirst) ? subRdr : DataRdr_.Null; + } + public String To_str() {return cur.To_str();} + public void Rls() {this.cur = null; this.peerList = null;} + @Override public SrlMgr SrlMgr_new(Object o) {return new DataRdr_mem();} + GfoNdeList Peers_get() { + boolean initialized = cur == null && peerPos == -1 && peerList.Count() > 0; // initialized = no current, at bof, subs available + return initialized ? peerList.FetchAt_asGfoNde(0).Subs() : cur.Subs(); + } + void Subs_byName(GfoNdeList list, String[] names, int depth, GfoNdeList peers) { + String name = names[depth]; + for (int i = 0; i < peers.Count(); i++) { + GfoNde sub = peers.FetchAt_asGfoNde(i); if (sub == null) continue; + if (!String_.Eq(name, sub.Name())) continue; + if (depth == names.length - 1) + list.Add(sub); + else + Subs_byName(list, names, depth + 1, sub.Subs()); + } + } + + GfoNde cur; GfoNdeList peerList; int peerPos = -1; GfoFldList flds; + public static DataRdr_mem new_(GfoNde cur, GfoFldList flds, GfoNdeList peerList) { + DataRdr_mem rv = new DataRdr_mem(); + rv.cur = cur; rv.peerList = peerList; rv.flds = flds; + return rv; + } @gplx.Internal protected DataRdr_mem() {} +} diff --git a/100_core/src/gplx/core/stores/DataWtr.java b/100_core/src/gplx/core/stores/DataWtr.java index a27517de8..b0c130647 100644 --- a/100_core/src/gplx/core/stores/DataWtr.java +++ b/100_core/src/gplx/core/stores/DataWtr.java @@ -13,3 +13,19 @@ 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.core.stores; import gplx.*; import gplx.core.*; +import gplx.core.gfo_ndes.*; +public interface DataWtr extends SrlMgr { + Hash_adp EnvVars(); + + void InitWtr(String key, Object val); + void WriteTableBgn(String name, GfoFldList fields); + void WriteNodeBgn(String nodeName); + void WriteLeafBgn(String leafName); + void WriteData(String name, Object val); + void WriteNodeEnd(); + void WriteLeafEnd(); + + void Clear(); + String To_str(); +} diff --git a/100_core/src/gplx/core/stores/DataWtr_.java b/100_core/src/gplx/core/stores/DataWtr_.java index a27517de8..4aa21b7d9 100644 --- a/100_core/src/gplx/core/stores/DataWtr_.java +++ b/100_core/src/gplx/core/stores/DataWtr_.java @@ -13,3 +13,34 @@ 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.core.stores; import gplx.*; import gplx.core.*; +import gplx.core.gfo_ndes.*; +public class DataWtr_ { + public static final DataWtr Null = new DataWtr_null(); +} +class DataWtr_null implements DataWtr { + public boolean Type_rdr() {return false;} + public Hash_adp EnvVars() {return envVars;} Hash_adp envVars = Hash_adp_.New(); + public void InitWtr(String key, Object val) {} + public void WriteTableBgn(String name, GfoFldList fields) {} + public void WriteNodeBgn(String nodeName) {} + public void WriteLeafBgn(String leafName) {} + public void WriteData(String name, Object val) {} + public void WriteNodeEnd() {} + public void WriteLeafEnd() {} + public void Clear() {} + public String To_str() {return "";} + public Object StoreRoot(SrlObj root, String key) {return null;} + public boolean SrlBoolOr(String key, boolean v) {return v;} + public byte SrlByteOr(String key, byte v) {return v;} + public int SrlIntOr(String key, int or) {return or;} + public long SrlLongOr(String key, long or) {return or;} + public String SrlStrOr(String key, String or) {return or;} + public DateAdp SrlDateOr(String key, DateAdp or) {return or;} + public Decimal_adp SrlDecimalOr(String key, Decimal_adp or) {return or;} + public double SrlDoubleOr(String key, double or) {return or;} + public Object SrlObjOr(String key, Object or) {return or;} + public void SrlList(String key, List_adp list, SrlObj proto, String itmKey) {} + public void TypeKey_(String v) {} + public SrlMgr SrlMgr_new(Object o) {return this;} +} diff --git a/100_core/src/gplx/core/stores/DataWtr_base.java b/100_core/src/gplx/core/stores/DataWtr_base.java index a27517de8..c57a57884 100644 --- a/100_core/src/gplx/core/stores/DataWtr_base.java +++ b/100_core/src/gplx/core/stores/DataWtr_base.java @@ -13,3 +13,38 @@ 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.core.stores; import gplx.*; import gplx.core.*; +public abstract class DataWtr_base implements SrlMgr { + @gplx.Virtual public Hash_adp EnvVars() {return envVars;} Hash_adp envVars = Hash_adp_.New(); + public boolean Type_rdr() {return false;} + public abstract void WriteData(String key, Object o); + public abstract void WriteNodeBgn(String nodeName); + public abstract void WriteNodeEnd(); + @gplx.Virtual public void SrlList(String key, List_adp list, SrlObj proto, String itmKey) { + this.WriteNodeBgn(key); + for (Object itmObj : list) { + SrlObj itm = (SrlObj)itmObj; + this.WriteNodeBgn(itmKey); + itm.SrlObj_Srl(this); + this.WriteNodeEnd(); + } + this.WriteNodeEnd(); + } + @gplx.Virtual public Object StoreRoot(SrlObj root, String key) { + this.WriteNodeBgn(key); + root.SrlObj_Srl(this); + this.WriteNodeEnd(); + return root; + } + public boolean SrlBoolOr(String key, boolean v) {WriteData(key, v); return v;} + public byte SrlByteOr(String key, byte v) {WriteData(key, v); return v;} + public int SrlIntOr(String key, int or) {WriteData(key, or); return or;} + public long SrlLongOr(String key, long or) {WriteData(key, or); return or;} + public String SrlStrOr(String key, String or) {WriteData(key, or); return or;} + public DateAdp SrlDateOr(String key, DateAdp or) {WriteData(key, or.XtoStr_gplx()); return or;} + public Decimal_adp SrlDecimalOr(String key, Decimal_adp or) {WriteData(key, or.Under()); return or;} + public double SrlDoubleOr(String key, double or) {WriteData(key, or); return or;} + public Object SrlObjOr(String key, Object or) {throw Err_.new_unimplemented();} + public void TypeKey_(String v) {} + public abstract SrlMgr SrlMgr_new(Object o); +} diff --git a/100_core/src/gplx/core/stores/GfoNdeRdr.java b/100_core/src/gplx/core/stores/GfoNdeRdr.java index a27517de8..0f7baf277 100644 --- a/100_core/src/gplx/core/stores/GfoNdeRdr.java +++ b/100_core/src/gplx/core/stores/GfoNdeRdr.java @@ -13,3 +13,8 @@ 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.core.stores; import gplx.*; import gplx.core.*; +import gplx.core.gfo_ndes.*; +public interface GfoNdeRdr extends DataRdr { + GfoNde UnderNde(); +} diff --git a/100_core/src/gplx/core/stores/GfoNdeRdr_.java b/100_core/src/gplx/core/stores/GfoNdeRdr_.java index a27517de8..37edbbcf0 100644 --- a/100_core/src/gplx/core/stores/GfoNdeRdr_.java +++ b/100_core/src/gplx/core/stores/GfoNdeRdr_.java @@ -13,3 +13,35 @@ 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.core.stores; import gplx.*; import gplx.core.*; +import gplx.core.gfo_ndes.*; import gplx.core.type_xtns.*; +public class GfoNdeRdr_ { + public static GfoNdeRdr kvs_(Keyval_list kvList) { + GfoFldList flds = GfoFldList_.new_(); + int pairsLen = kvList.Count(); + Object[] vals = new Object[pairsLen]; + for (int i = 0; i < pairsLen; i++) { + Keyval pair = kvList.Get_at(i); + flds.Add(pair.Key(), StringClassXtn.Instance); + vals[i] = pair.Val_to_str_or_empty(); + } + GfoNde nde = GfoNde_.vals_(flds, vals); + return root_(nde, true); + } + public static GfoNdeRdr root_parseNot_(GfoNde root) {return root_(root, true);} + public static GfoNdeRdr root_(GfoNde root, boolean parse) { + DataRdr_mem rv = DataRdr_mem.new_(root, root.Flds(), root.Subs()); rv.Parse_set(parse); + return rv; + } + public static GfoNdeRdr leaf_(GfoNde cur, boolean parse) { + DataRdr_mem rv = DataRdr_mem.new_(cur, cur.Flds(), GfoNdeList_.Null); rv.Parse_set(parse); + return rv; + } + public static GfoNdeRdr peers_(GfoNdeList peers, boolean parse) { + GfoFldList flds = peers.Count() == 0 ? GfoFldList_.Null : peers.FetchAt_asGfoNde(0).Flds(); + DataRdr_mem rv = DataRdr_mem.new_(null, flds, peers); rv.Parse_set(parse); + return rv; + } + public static GfoNdeRdr as_(Object obj) {return obj instanceof GfoNdeRdr ? (GfoNdeRdr)obj : null;} + public static GfoNdeRdr cast(Object obj) {try {return (GfoNdeRdr)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, GfoNdeRdr.class, obj);}} +} diff --git a/100_core/src/gplx/core/stores/SrlMgr.java b/100_core/src/gplx/core/stores/SrlMgr.java index a27517de8..64e892ee0 100644 --- a/100_core/src/gplx/core/stores/SrlMgr.java +++ b/100_core/src/gplx/core/stores/SrlMgr.java @@ -13,3 +13,21 @@ 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.core.stores; import gplx.*; import gplx.core.*; +public interface SrlMgr { + boolean Type_rdr(); + Object StoreRoot(SrlObj root, String key); + + boolean SrlBoolOr(String key, boolean v); + byte SrlByteOr(String key, byte v); + int SrlIntOr(String key, int v); + long SrlLongOr(String key, long v); + String SrlStrOr(String key, String v); + double SrlDoubleOr(String key, double v); + Decimal_adp SrlDecimalOr(String key, Decimal_adp v); + DateAdp SrlDateOr(String key, DateAdp v); + Object SrlObjOr(String key, Object v); + void SrlList(String key, List_adp list, SrlObj proto, String itmKey); + void TypeKey_(String v); + SrlMgr SrlMgr_new(Object o); +} diff --git a/100_core/src/gplx/core/stores/SrlObj.java b/100_core/src/gplx/core/stores/SrlObj.java index a27517de8..71414a158 100644 --- a/100_core/src/gplx/core/stores/SrlObj.java +++ b/100_core/src/gplx/core/stores/SrlObj.java @@ -13,3 +13,8 @@ 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.core.stores; import gplx.*; import gplx.core.*; +public interface SrlObj { + SrlObj SrlObj_New(Object o); + void SrlObj_Srl(SrlMgr mgr); +} diff --git a/100_core/src/gplx/core/stores/xmls/XmlDataRdr.java b/100_core/src/gplx/core/stores/xmls/XmlDataRdr.java index a27517de8..f28bf1dd3 100644 --- a/100_core/src/gplx/core/stores/xmls/XmlDataRdr.java +++ b/100_core/src/gplx/core/stores/xmls/XmlDataRdr.java @@ -13,3 +13,63 @@ 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.core.stores.xmls; import gplx.*; import gplx.core.*; import gplx.core.stores.*; +import gplx.langs.xmls.*; /*Xpath_*/ +public class XmlDataRdr extends DataRdr_base implements DataRdr { + @Override public String NameOfNode() {return nde.Name();} public String To_str() {return nde.Xml_outer();} + @Override public int FieldCount() {return nde.Atrs() == null ? 0 : nde.Atrs().Count();} // nde.Attributes == null when nde is XmlText; ex: val + @Override public String KeyAt(int i) {return nde.Atrs().Get_at(i).Name();} + @Override public Object ReadAt(int i) { + XmlAtr attrib = nde.Atrs().Get_at(i); + return (attrib == null) ? null : attrib.Value(); + } + @Override public Object Read(String key) { + return nde.Atrs().FetchValOr(key, null); + } + public boolean MoveNextPeer() { + if (++pos >= peerList.Count()){ // moved out Of range + nde = null; + return false; + } + nde = peerList.Get_at(pos); + return true; + } + @Override public DataRdr Subs() { + XmlNdeList list = Xpath_.SelectElements(nde); + XmlDataRdr rv = new XmlDataRdr(); + rv.ctor_(list, null); + return rv; + } + @Override public DataRdr Subs_byName_moveFirst(String name) { + DataRdr subRdr = Subs_byName(name); + boolean hasFirst = subRdr.MoveNextPeer(); + return (hasFirst) ? subRdr : DataRdr_.Null; + } + public DataRdr Subs_byName(String name) { + XmlNdeList list = Xpath_.SelectAll(nde, name); + XmlDataRdr rv = new XmlDataRdr(); + rv.ctor_(list, null); + return rv; + } + public void Rls() {nde = null; peerList = null;} + public String NodeValue_get() { + if (nde.SubNdes().Count() != 1) return ""; + XmlNde sub = nde.SubNdes().Get_at(0); + return (sub.NdeType_textOrEntityReference()) ? sub.Text_inner() : ""; + } + public String Node_OuterXml() {return nde.Xml_outer();} + @Override public SrlMgr SrlMgr_new(Object o) {return new XmlDataRdr();} + void LoadString(String raw) { + XmlDoc xdoc = XmlDoc_.parse(raw); + XmlNdeList list = Xpath_.SelectElements(xdoc.Root()); + ctor_(list, xdoc.Root()); + } + void ctor_(XmlNdeList peerList, XmlNde nde) { + this.peerList = peerList; this.nde = nde; pos = -1; + } + + XmlNde nde = null; + XmlNdeList peerList = null; int pos = -1; + @gplx.Internal protected XmlDataRdr(String raw) {this.LoadString(raw); this.Parse_set(true);} + XmlDataRdr() {this.Parse_set(true);} +} diff --git a/100_core/src/gplx/core/stores/xmls/XmlDataRdr_.java b/100_core/src/gplx/core/stores/xmls/XmlDataRdr_.java index a27517de8..a2f5d1a13 100644 --- a/100_core/src/gplx/core/stores/xmls/XmlDataRdr_.java +++ b/100_core/src/gplx/core/stores/xmls/XmlDataRdr_.java @@ -13,3 +13,11 @@ 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.core.stores.xmls; import gplx.*; import gplx.core.*; import gplx.core.stores.*; +public class XmlDataRdr_ { + public static XmlDataRdr file_(Io_url url) { + String text = Io_mgr.Instance.LoadFilStr(url); + return new XmlDataRdr(text); + } + public static XmlDataRdr text_(String text) {return new XmlDataRdr(text);} +} diff --git a/100_core/src/gplx/core/stores/xmls/XmlDataWtr_.java b/100_core/src/gplx/core/stores/xmls/XmlDataWtr_.java index a27517de8..09fdc728c 100644 --- a/100_core/src/gplx/core/stores/xmls/XmlDataWtr_.java +++ b/100_core/src/gplx/core/stores/xmls/XmlDataWtr_.java @@ -13,3 +13,99 @@ 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.core.stores.xmls; import gplx.*; import gplx.core.*; import gplx.core.stores.*; +import gplx.core.strings.*; import gplx.core.gfo_ndes.*; +public class XmlDataWtr_ { + public static DataWtr new_() {return XmlDataWtr.new_();} +} +class XmlDataWtr extends DataWtr_base implements DataWtr { + public void InitWtr(String key, Object val) {} + @Override public void WriteData(String name, Object val) { +// if (val == null) return; + String valString = Object_.Xto_str_strict_or_empty(val); + int valStringLen = String_.Len(valString); + sb.Add(" ").Add(name).Add("=\""); + for (int i = 0; i < valStringLen; i++) { + char c = String_.CharAt(valString, i); + if (c == '<') sb.Add("<"); + else if (c == '>') sb.Add(">"); + else if (c == '&') sb.Add("&"); + else if (c == '\"') sb.Add(""e;"); + else sb.Add(c); + } + sb.Add("\""); +// XmlAttribute atr = doc.CreateAttribute(name); +// atr.Value = (val == null) ? String_.Empty : val.toString(); +// nde.Attributes.Append(atr); + } + public void WriteLeafBgn(String leafName) {this.WriteXmlNodeBegin(leafName);} + public void WriteLeafEnd() {} + public void WriteTableBgn(String name, GfoFldList fields) {this.WriteXmlNodeBegin(name);} + @Override public void WriteNodeBgn(String nodeName) {this.WriteXmlNodeBegin(nodeName);} + @Override public void WriteNodeEnd() {this.WriteXmlNodeEnd();} + public String To_str() { + while (names.Count() > 0) { + WriteXmlNodeEnd(); + } + return sb.To_str(); +// while (nde.ParentNode != null) +// WriteXmlNodeEnd(); // close all open ndes automatically +// return doc.OuterXml; + } + public void WriteComment(String comment) { + sb.Add(""); +// XmlComment xmlComment = doc.CreateComment(comment); +// nde.AppendChild(xmlComment); + } + public void Clear() { + sb.Clear(); +// doc = new XmlDocument(); + } + void WriteXmlNodeBegin(String name) { + if (ndeOpened) { + sb.Add(">" + String_.CrLf); + } + ndeOpened = true; + names.Add(name); + sb.Add("<" + name); +// XmlNode owner = nde; +// nde = doc.CreateElement(name); +// if (owner == null) // first call to WriteXmlNodeBegin(); append child to doc +// doc.AppendChild(nde); +// else { +// WriteLineFeedIfNeeded(doc, owner); +// owner.AppendChild(nde); +// } + } + void WriteXmlNodeEnd() { + if (ndeOpened) { + sb.Add(" />" + String_.CrLf); + ndeOpened = false; + } + else { + String name = (String)names.Get_at_last(); + sb.Add("" + String_.CrLf); + } + names.Del_at(names.Count() - 1); + // if (nde.ParentNode == null) throw Err_.new_wo_type("WriteXmlNodeEnd() called on root node"); +// nde = nde.ParentNode; +// WriteLineFeed(doc, nde); + } +// void WriteLineFeed(XmlDocument doc, XmlNode owner) { +// XmlSignificantWhitespace crlf = doc.CreateSignificantWhitespace(String_.CrLf); +// owner.AppendChild(crlf); +// } +// void WriteLineFeedIfNeeded(XmlDocument doc, XmlNode owner) { +// XmlSignificantWhitespace lastSubNode = owner.ChildNodes[owner.ChildNodes.Count - 1] as XmlSignificantWhitespace; +// if (lastSubNode == null) +// WriteLineFeed(doc, owner); // write LineFeed for consecutive WriteXmlNodeBegin calls; ex: +// } + @Override public SrlMgr SrlMgr_new(Object o) {return new XmlDataWtr();} + boolean ndeOpened = false; +// int atrCount = 0; +// int ndeState = -1; static final int NdeState0_Opened = 0, NdeState0_H = 1; +// XmlDocument doc = new XmlDocument(); XmlNode nde; + List_adp names = List_adp_.New(); + String_bldr sb = String_bldr_.new_(); + public static XmlDataWtr new_() {return new XmlDataWtr();} XmlDataWtr() {} +} diff --git a/100_core/src/gplx/core/strings/String_bldr.java b/100_core/src/gplx/core/strings/String_bldr.java index a27517de8..466f5080d 100644 --- a/100_core/src/gplx/core/strings/String_bldr.java +++ b/100_core/src/gplx/core/strings/String_bldr.java @@ -13,3 +13,102 @@ 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.core.strings; import gplx.*; import gplx.core.*; +import gplx.core.envs.*; +public interface String_bldr { + boolean Has_none(); + boolean Has_some(); + String_bldr Add_many(String... array); + String_bldr Add_fmt(String format, Object... args); + String_bldr Add_fmt_line(String format, Object... args); + String_bldr Add_kv(String hdr, String val); + String_bldr Add_kv_obj(String k, Object v); + String_bldr Add_char_pipe(); + String_bldr Add_char_nl(); + String_bldr Add_char_crlf(); + String_bldr Add_str_w_crlf(String v); + String_bldr Add_spr_unless_first(String s, String spr, int i); + String_bldr Clear(); + String To_str_and_clear(); + String To_str(); + int Count(); + String_bldr Add(byte[] v); + String_bldr Add(String s); + String_bldr Add(char c); + String_bldr Add(int i); + String_bldr Add_obj(Object o); + String_bldr Add_mid(char[] ary, int bgn, int count); + String_bldr Add_at(int idx, String s); + String_bldr Del(int bgn, int len); +} +abstract class String_bldr_base implements String_bldr { + public boolean Has_none() {return this.Count() == 0;} + public boolean Has_some() {return this.Count() > 0;} + public String_bldr Add_many(String... array) {for (String s : array) Add(s); return this;} + public String_bldr Add_fmt(String format, Object... args) {Add(String_.Format(format, args)); return this;} + public String_bldr Add_fmt_line(String format, Object... args) {Add_str_w_crlf(String_.Format(format, args)); return this;} + public String_bldr Add_kv_obj(String k, Object v) { + if (this.Count() != 0) this.Add(" "); + this.Add_fmt("{0}={1}", k, Object_.Xto_str_strict_or_null_mark(v)); + return this; + } + public String_bldr Add_char_pipe() {return Add("|");} + public String_bldr Add_char_nl() {Add(Op_sys.Lnx.Nl_str()); return this;} + public String_bldr Add_char_crlf() {Add(Op_sys.Wnt.Nl_str()); return this;} + public String_bldr Add_str_w_crlf(String line) {Add(line); Add(String_.CrLf); return this;} + public String_bldr Add_spr_unless_first(String s, String spr, int i) { + if (i != 0) Add(spr); + Add(s); + return this; + } + public String_bldr Add_kv(String hdr, String val) { + if (String_.Len_eq_0(val)) return this; + if (this.Count() != 0) this.Add(' '); + this.Add(hdr); + this.Add(val); + return this; + } + public String_bldr Clear() {Del(0, Count()); return this;} + public String To_str_and_clear() { + String rv = To_str(); + Clear(); + return rv; + } + @Override public String toString() {return To_str();} + public abstract String To_str(); + public abstract int Count(); + public abstract String_bldr Add_at(int idx, String s); + public abstract String_bldr Add(byte[] v); + public abstract String_bldr Add(String s); + public abstract String_bldr Add(char c); + public abstract String_bldr Add(int i); + public abstract String_bldr Add_mid(char[] ary, int bgn, int count); + public abstract String_bldr Add_obj(Object o); + public abstract String_bldr Del(int bgn, int len); +} +class String_bldr_thread_single extends String_bldr_base { + private java.lang.StringBuilder sb = new java.lang.StringBuilder(); + @Override public String To_str() {return sb.toString();} + @Override public int Count() {return sb.length();} + @Override public String_bldr Add_at(int idx, String s) {sb.insert(idx, s); return this;} + @Override public String_bldr Add(byte[] v) {sb.append(String_.new_u8(v)); return this;} + @Override public String_bldr Add(String s) {sb.append(s); return this;} + @Override public String_bldr Add(char c) {sb.append(c); return this;} + @Override public String_bldr Add(int i) {sb.append(i); return this;} + @Override public String_bldr Add_mid(char[] ary, int bgn, int count) {sb.append(ary, bgn, count); return this;} + @Override public String_bldr Add_obj(Object o) {sb.append(o); return this;} + @Override public String_bldr Del(int bgn, int len) {sb.delete(bgn, len); return this;} +} +class String_bldr_thread_multiple extends String_bldr_base { + private java.lang.StringBuffer sb = new java.lang.StringBuffer(); + @Override public String To_str() {return sb.toString();} + @Override public int Count() {return sb.length();} + @Override public String_bldr Add_at(int idx, String s) {sb.insert(idx, s); return this;} + @Override public String_bldr Add(byte[] v) {sb.append(String_.new_u8(v)); return this;} + @Override public String_bldr Add(String s) {sb.append(s); return this;} + @Override public String_bldr Add(char c) {sb.append(c); return this;} + @Override public String_bldr Add(int i) {sb.append(i); return this;} + @Override public String_bldr Add_mid(char[] ary, int bgn, int count) {sb.append(ary, bgn, count); return this;} + @Override public String_bldr Add_obj(Object o) {sb.append(o); return this;} + @Override public String_bldr Del(int bgn, int len) {sb.delete(bgn, len); return this;} +} diff --git a/100_core/src/gplx/core/strings/String_bldr_.java b/100_core/src/gplx/core/strings/String_bldr_.java index a27517de8..52a550fe2 100644 --- a/100_core/src/gplx/core/strings/String_bldr_.java +++ b/100_core/src/gplx/core/strings/String_bldr_.java @@ -13,3 +13,8 @@ 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.core.strings; import gplx.*; import gplx.core.*; +public class String_bldr_ { + public static String_bldr new_() {return new String_bldr_thread_single();} + public static String_bldr new_thread() {return new String_bldr_thread_multiple();} +} diff --git a/100_core/src/gplx/core/tests/Gftest.java b/100_core/src/gplx/core/tests/Gftest.java index a27517de8..5ac28e36d 100644 --- a/100_core/src/gplx/core/tests/Gftest.java +++ b/100_core/src/gplx/core/tests/Gftest.java @@ -13,3 +13,202 @@ 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.core.tests; import gplx.*; import gplx.core.*; +import gplx.core.brys.*; +public class Gftest { + private static final Bry_bfr bfr = Bry_bfr_.New(); + public static void Eq__ary(boolean[] expd, boolean[] actl, String msg_fmt, Object... msg_args) {Eq__array(Type_ids_.Id__bool, expd, actl, msg_fmt, msg_args);} + public static void Eq__ary(int[] expd, int[] actl, String msg_fmt, Object... msg_args) {Eq__array(Type_ids_.Id__int, expd, actl, msg_fmt, msg_args);} + public static void Eq__ary(long[] expd, long[] actl, String msg_fmt, Object... msg_args) {Eq__array(Type_ids_.Id__long, expd, actl, msg_fmt, msg_args);} + public static void Eq__ary(byte[] expd, byte[] actl, String msg_fmt, Object... msg_args) {Eq__array(Type_ids_.Id__byte, expd, actl, msg_fmt, msg_args);} + public static void Eq__ary__lines(String expd, byte[] actl) {Eq__ary__lines(expd, String_.new_u8(actl), "no_msg");} + public static void Eq__ary__lines(String expd, byte[] actl, String msg_fmt, Object... msg_args) {Eq__ary__lines(expd, String_.new_u8(actl), msg_fmt, msg_args);} + public static void Eq__ary__lines(String expd, String actl, String msg_fmt, Object... msg_args) {Eq__array(Type_ids_.Id__bry, Bry_split_.Split_lines(Bry_.new_u8_safe(expd)), Bry_split_.Split_lines(Bry_.new_u8_safe(actl)), msg_fmt, msg_args);} + public static void Eq__ary(String[] expd, String[] actl) {Eq__array(Type_ids_.Id__bry, Bry_.Ary(expd), Bry_.Ary(actl), "no_msg");} + public static void Eq__ary(String[] expd, String[] actl, String msg_fmt, Object... msg_args) {Eq__array(Type_ids_.Id__bry, Bry_.Ary(expd), Bry_.Ary(actl), msg_fmt, msg_args);} + public static void Eq__ary(String[] expd, byte[][] actl, String msg_fmt, Object... msg_args) {Eq__array(Type_ids_.Id__bry, Bry_.Ary(expd), actl, msg_fmt, msg_args);} + public static void Eq__ary(byte[][] expd, byte[][] actl, String msg_fmt, Object... msg_args) {Eq__array(Type_ids_.Id__bry, expd, actl, msg_fmt, msg_args);} + public static void Eq__ary(Bry_bfr_able[] expd_ary, Bry_bfr_able[] actl_ary) {Eq__ary(expd_ary, actl_ary, null);} + public static void Eq__ary(Bry_bfr_able[] expd_ary, Bry_bfr_able[] actl_ary, String msg_fmt, Object... msg_args) { + Eq__array(Type_ids_.Id__bry, Bry_bfr_able_.To_bry_ary(bfr, expd_ary), Bry_bfr_able_.To_bry_ary(bfr, actl_ary), msg_fmt, msg_args); + } + private static void Eq__array(int type_tid, Object expd_ary, Object actl_ary, String msg_fmt, Object... msg_args) { + boolean[] failures = Calc__failures(type_tid, expd_ary, actl_ary); + if (failures != null) { + Write_fail_head(bfr, msg_fmt, msg_args); + Write_fail_ary(bfr, failures, type_tid, expd_ary, actl_ary); + throw Err_.new_wo_type(bfr.To_str_and_clear()); + } + } + public static void Eq__null(boolean expd, Object actl) {Eq__null(expd, actl, null);} + public static void Eq__null(boolean expd, Object actl, String msg_fmt, Object... msg_args) { + if ( expd && actl == null + || !expd && actl != null + ) return; + Write_fail_head(bfr, msg_fmt, msg_args); + String expd_str = expd ? "null" : "not null"; + String actl_str = actl == null ? "null" : "not null"; + bfr.Add_str_a7("expd: ").Add_str_a7(expd_str).Add_byte_nl(); + bfr.Add_str_a7("actl: ").Add_str_a7(actl_str).Add_byte_nl(); + bfr.Add(Bry__line_end); + throw Err_.new_wo_type(bfr.To_str_and_clear()); + } + public static void Eq__obj_or_null(Object expd, Object actl) { + if (expd == null) expd = Str__null; + if (actl == null) actl = Str__null; + Eq__str(Object_.Xto_str_or(expd, Str__null), Object_.Xto_str_or(actl, null), Str__null); + } + public static void Eq__str(String expd, byte[] actl, String msg_fmt, Object... msg_args) {Eq__str(expd, String_.new_u8(actl), msg_fmt, msg_args);} + public static void Eq__str(String expd, byte[] actl) {Eq__str(expd, String_.new_u8(actl), null);} + public static void Eq__str(String expd, String actl) {Eq__str(expd, actl, null);} + public static void Eq__str(String expd, String actl, String msg_fmt, Object... msg_args) { + if (String_.Eq(expd, actl)) return; + Write_fail_head(bfr, msg_fmt, msg_args); + bfr.Add_str_a7("expd: ").Add_str_u8_null(expd).Add_byte_nl(); + bfr.Add_str_a7("actl: ").Add_str_u8_null(actl).Add_byte_nl(); + bfr.Add(Bry__line_end); + throw Err_.new_wo_type(bfr.To_str_and_clear()); + } + public static void Eq__bry(byte[] expd, byte[] actl) {Eq__bry(expd, actl, null);} + public static void Eq__bry(byte[] expd, byte[] actl, String msg_fmt, Object... msg_args) { + if (Bry_.Eq(expd, actl)) return; + Write_fail_head(bfr, msg_fmt, msg_args); + bfr.Add_str_a7("expd: ").Add_safe(expd).Add_byte_nl(); + bfr.Add_str_a7("actl: ").Add_safe(actl).Add_byte_nl(); + bfr.Add(Bry__line_end); + throw Err_.new_wo_type(bfr.To_str_and_clear()); + } + public static void Eq__long(long expd, long actl) {Eq__long(expd, actl, null);} + public static void Eq__long(long expd, long actl, String msg_fmt, Object... msg_args) { + if (expd == actl) return; + Write_fail_head(bfr, msg_fmt, msg_args); + bfr.Add_str_a7("expd: ").Add_long_variable(expd).Add_byte_nl(); + bfr.Add_str_a7("actl: ").Add_long_variable(actl).Add_byte_nl(); + bfr.Add(Bry__line_end); + throw Err_.new_wo_type(bfr.To_str_and_clear()); + } + public static void Eq__byte(byte expd, byte actl) {Eq__byte(expd, actl, null);} + public static void Eq__byte(byte expd, byte actl, String msg_fmt, Object... msg_args) { + if (expd == actl) return; + Write_fail_head(bfr, msg_fmt, msg_args); + bfr.Add_str_a7("expd: ").Add_byte_as_a7(expd).Add_byte_nl(); + bfr.Add_str_a7("actl: ").Add_byte_as_a7(actl).Add_byte_nl(); + bfr.Add(Bry__line_end); + throw Err_.new_wo_type(bfr.To_str_and_clear()); + } + public static void Eq__int(int expd, int actl) {Eq__int(expd, actl, null);} + public static void Eq__int(int expd, int actl, String msg_fmt, Object... msg_args) { + if (expd == actl) return; + Write_fail_head(bfr, msg_fmt, msg_args); + bfr.Add_str_a7("expd: ").Add_int_variable(expd).Add_byte_nl(); + bfr.Add_str_a7("actl: ").Add_int_variable(actl).Add_byte_nl(); + bfr.Add(Bry__line_end); + throw Err_.new_wo_type(bfr.To_str_and_clear()); + } + public static void Eq__bool_y(boolean actl) {Eq__bool(Bool_.Y, actl, null);} + public static void Eq__bool_y(boolean actl, String msg_fmt, Object... msg_args) {Eq__bool(Bool_.Y, actl, msg_fmt, msg_args);} + public static void Eq__bool(boolean expd, boolean actl) {Eq__bool(expd, actl, null);} + public static void Eq__bool(boolean expd, boolean actl, String msg_fmt, Object... msg_args) { + if (expd == actl) return; + Write_fail_head(bfr, msg_fmt, msg_args); + bfr.Add_str_a7("expd: ").Add_bool(expd).Add_byte_nl(); + bfr.Add_str_a7("actl: ").Add_bool(actl).Add_byte_nl(); + bfr.Add(Bry__line_end); + throw Err_.new_wo_type(bfr.To_str_and_clear()); + } + public static void Eq__double(double expd, double actl) {Eq__double(expd, actl, null);} + public static void Eq__double(double expd, double actl, String msg_fmt, Object... msg_args) { + if (expd == actl) return; + Write_fail_head(bfr, msg_fmt, msg_args); + bfr.Add_str_a7("expd: ").Add_double(expd).Add_byte_nl(); + bfr.Add_str_a7("actl: ").Add_double(actl).Add_byte_nl(); + bfr.Add(Bry__line_end); + throw Err_.new_wo_type(bfr.To_str_and_clear()); + } + private static void Write_fail_head(Bry_bfr bfr, String msg_fmt, Object[] msg_args) { + bfr.Add(Bry__line_bgn); + if (msg_fmt != null) { + bfr.Add_str_u8(String_.Format(msg_fmt, msg_args)); + bfr.Add(Bry__line_mid); + } + } + private static void Write_fail_ary(Bry_bfr bfr, boolean[] failures, int type_id, Object expd_ary, Object actl_ary) { + int len = failures.length; + int expd_len = Array_.Len(expd_ary); + int actl_len = Array_.Len(actl_ary); + for (int i = 0; i < len; ++i) { + boolean failure = failures[i]; + int pad_len = 5 - Int_.DigitCount(i); + bfr.Add_int_pad_bgn(Byte_ascii.Num_0, pad_len, i).Add_byte_colon().Add_byte_space(); + Write__itm(bfr, type_id, expd_ary, expd_len, i); + if (failure) { + bfr.Add(Bry__item__eq_n).Add_byte_repeat(Byte_ascii.Space, pad_len - 1); + Write__itm(bfr, type_id, actl_ary, actl_len, i); + } + } + bfr.Add(Bry__line_end); + } + private static void Write__itm(Bry_bfr bfr, int type_id, Object ary, int len, int idx) { + if (idx < len) { + switch (type_id) { + case Type_ids_.Id__bool: bfr.Add_yn(Bool_.Cast(Array_.Get_at(ary, idx))); break; + case Type_ids_.Id__bry: bfr.Add_safe((byte[])Array_.Get_at(ary, idx)); break; + case Type_ids_.Id__long: bfr.Add_long_variable(Long_.cast(Array_.Get_at(ary, idx))); break; + case Type_ids_.Id__int: bfr.Add_int_variable(Int_.Cast(Array_.Get_at(ary, idx))); break; + case Type_ids_.Id__byte: bfr.Add_int_variable((int)(Byte_.Cast(Array_.Get_at(ary, idx)))); break; + default: throw Err_.new_unhandled_default(type_id); + } + } + else + bfr.Add(Bry__null); + bfr.Add_byte_nl(); + } + private static boolean[] Calc__failures(int tid, Object expd_ary, Object actl_ary) { + int expd_len = Array_.Len(expd_ary); + int actl_len = Array_.Len(actl_ary); + int max_len = expd_len > actl_len ? expd_len : actl_len; if (max_len == 0) return null; + boolean[] rv = null; + for (int i = 0; i < max_len; ++i) { + Object expd_obj = i < expd_len ? Array_.Get_at(expd_ary, i) : null; + Object actl_obj = i < actl_len ? Array_.Get_at(actl_ary, i) : null; + boolean eq = false; + if (expd_obj == null && actl_obj == null) eq = true; + else if (expd_obj == null || actl_obj == null) eq = false; + else { + switch (tid) { + case Type_ids_.Id__bool: eq = Bool_.Cast(expd_obj) == Bool_.Cast(actl_obj); break; + case Type_ids_.Id__bry: eq = Bry_.Eq((byte[])expd_obj, (byte[])actl_obj); break; + case Type_ids_.Id__long: eq = Long_.cast(expd_obj) == Long_.cast(actl_obj); break; + case Type_ids_.Id__int: eq = Int_.Cast(expd_obj) == Int_.Cast(actl_obj); break; + case Type_ids_.Id__byte: eq = Byte_.Cast(expd_obj) == Byte_.Cast(actl_obj); break; + } + } + if (!eq) { + if (rv == null) { + rv = new boolean[max_len]; + } + rv[i] = true; + } + } + return rv; + } + private static final String Str__null = "<>"; + private static final byte[] Bry__item__eq_n = Bry_.new_a7("!= ") // Bry__item__eq_y = Bry_.new_a7("== "), + , Bry__null = Bry_.new_a7(Str__null) + , Bry__line_bgn = Bry_.new_a7("\n************************************************************************************************\n") + , Bry__line_mid = Bry_.new_a7("\n------------------------------------------------------------------------------------------------\n") + , Bry__line_end = Bry_.new_a7( "________________________________________________________________________________________________") + ; +} +/* +package ns; +import org.junit.*; import gplx.core.tests.*; +public class Cls1_tst { + private final Cls1_fxt fxt = new Cls1_fxt(); + @Test public void Basic() {} +} +class Cls1_fxt { + private final Cls1 mgr = new Cls1(); + public Cls1_fxt Test() {return this;} +} +*/ \ No newline at end of file diff --git a/100_core/src/gplx/core/tests/PerfLogMgr_fxt.java b/100_core/src/gplx/core/tests/PerfLogMgr_fxt.java index a27517de8..4a53129d6 100644 --- a/100_core/src/gplx/core/tests/PerfLogMgr_fxt.java +++ b/100_core/src/gplx/core/tests/PerfLogMgr_fxt.java @@ -13,3 +13,52 @@ 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.core.tests; import gplx.*; import gplx.core.*; +import gplx.core.strings.*; import gplx.core.envs.*; +public class PerfLogMgr_fxt { + public void Init(Io_url url, String text) { + this.url = url; + entries.Resize_bounds(1000); + entries.Add(new PerfLogItm(0, text + "|" + Datetime_now.Get().XtoStr_gplx())); + tmr.Bgn(); + } + public void Write(String text) { + long milliseconds = tmr.ElapsedMilliseconds(); + entries.Add(new PerfLogItm(milliseconds, text)); + tmr.Bgn(); + } + public void WriteFormat(String fmt, Object... ary) { + long milliseconds = tmr.ElapsedMilliseconds(); + String text = String_.Format(fmt, ary); + entries.Add(new PerfLogItm(milliseconds, text)); + tmr.Bgn(); + } + public void Flush() { + String_bldr sb = String_bldr_.new_(); + for (Object itmObj : entries) { + PerfLogItm itm = (PerfLogItm)itmObj; + sb.Add(itm.To_str()).Add_char_crlf(); + } + Io_mgr.Instance.AppendFilStr(url, sb.To_str()); + entries.Clear(); + } + List_adp entries = List_adp_.New(); PerfLogTmr tmr = PerfLogTmr.new_(); Io_url url = Io_url_.Empty; + public static final PerfLogMgr_fxt Instance = new PerfLogMgr_fxt(); PerfLogMgr_fxt() {} + class PerfLogItm { + public String To_str() { + String secondsStr = Time_span_.To_str(milliseconds, Time_span_.Fmt_Default); + secondsStr = String_.PadBgn(secondsStr, 7, "0"); // 7=000.000; left-aligns all times + return String_.Concat(secondsStr, "|", text); + } + long milliseconds; String text; + @gplx.Internal protected PerfLogItm(long milliseconds, String text) { + this.milliseconds = milliseconds; this.text = text; + } + } + +} +class PerfLogTmr { + public void Bgn() {bgn = System_.Ticks();} long bgn; + public long ElapsedMilliseconds() {return System_.Ticks() - bgn; } + public static PerfLogTmr new_() {return new PerfLogTmr();} PerfLogTmr() {} +} diff --git a/100_core/src/gplx/core/tests/TfdsEqListItmStr.java b/100_core/src/gplx/core/tests/TfdsEqListItmStr.java index a27517de8..f1a05e5e5 100644 --- a/100_core/src/gplx/core/tests/TfdsEqListItmStr.java +++ b/100_core/src/gplx/core/tests/TfdsEqListItmStr.java @@ -13,3 +13,7 @@ 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.core.tests; import gplx.*; import gplx.core.*; +public interface TfdsEqListItmStr { + String To_str(Object cur, Object actl); +} diff --git a/100_core/src/gplx/core/texts/Base32Converter.java b/100_core/src/gplx/core/texts/Base32Converter.java index a27517de8..e81968d7c 100644 --- a/100_core/src/gplx/core/texts/Base32Converter.java +++ b/100_core/src/gplx/core/texts/Base32Converter.java @@ -13,3 +13,85 @@ 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.core.texts; import gplx.*; import gplx.core.*; +public class Base32Converter { + public static String EncodeString(String orig) {return Encode(Bry_.new_u8(orig));} + public static String Encode(byte[] raw) { + int i = 0, index = 0, digit = 0; int currByte, nextByte; + int rawLen = Array_.Len(raw); + char[] ary = new char[(rawLen + 7) * 8 / 5]; int aryPos = 0; + while (i < rawLen) { + currByte = (raw[i] >= 0) ? raw[i] : raw[i] + 256; // unsign; java converts char 128+ -> byte -128 + if (index > 3) { /* Is the curPath digit going to span a byte boundary? */ + if ((i+1) < rawLen) + nextByte = (raw[i+1] >= 0) ? raw[i+1] : (raw[i+1] + 256); + else + nextByte = 0; + + digit = currByte & (0xFF >> index); + index = (index + 5) % 8; + digit <<= index; + digit |= nextByte >> (8 - index); + i++; + } + else { + digit = (currByte >> (8 - (index + 5))) & 0x1F; + index = (index + 5) % 8; + if (index == 0) i++; + } + ary[aryPos++] = String_.CharAt(chars, digit); + } + return new String(ary, 0, aryPos); + } + public static String DecodeString(String orig) {return String_.new_u8(Decode(orig));} + public static byte[] Decode(String raw) { + int i, index, lookup, offset; byte digit; + int rawLen = String_.Len(raw); + int rvLen = rawLen * 5 / 8; + byte[] rv = new byte[rvLen]; + for (i = 0, index = 0, offset = 0; i < rawLen; i++) { + lookup = String_.CharAt(raw, i) - '0'; + if (lookup < 0 || lookup >= valsLen) continue; /* Skip any char outside the lookup table */ + digit = vals[lookup]; + if (digit == 0x7F) continue; /* If this digit is not in the table, ignore it */ + + if (index <= 3) { + index = (index + 5) % 8; + if (index == 0) { + rv[offset] |= digit; + offset++; + if(offset >= rvLen) break; + } + else + rv[offset] |= (byte)(digit << (8 - index)); + } + else { + index = (index + 5) % 8; + rv[offset] |= (byte)(digit >> index); + offset++; + + if(offset >= rvLen) break; + rv[offset] |= (byte)(digit << (8 - index)); + } + } + return rv; + } + static String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + static int valsLen = 80; + static byte[] vals = { + 0x7F,0x7F,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, // '0', '1', '2', '3', '4', '5', '6', '7' + 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F, // '8', '9', ':', ';', '<', '=', '>', '?' + 0x7F,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G' + 0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O' + 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W' + 0x17,0x18,0x19,0x7F,0x7F,0x7F,0x7F,0x7F, // 'X', 'Y', 'Z', '[', '\', ']', '^', '_' + 0x7F,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g' + 0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o' + 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'p', 'q', 'r', 's', 't', 'u', 'v', 'w' + 0x17,0x18,0x19,0x7F,0x7F,0x7F,0x7F,0x7F // 'x', 'y', 'z', '{', '|', '}', '~', 'DEL' + }; +} +/* +source: Azraeus excerpt in java + http://www.koders.com/java/fidA4D6F0DF43E6E9A6B518762366AB7232C14E9DB7.aspx +*/ diff --git a/100_core/src/gplx/core/texts/Base64Converter.java b/100_core/src/gplx/core/texts/Base64Converter.java index a27517de8..d551064dc 100644 --- a/100_core/src/gplx/core/texts/Base64Converter.java +++ b/100_core/src/gplx/core/texts/Base64Converter.java @@ -13,3 +13,65 @@ 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.core.texts; import gplx.*; import gplx.core.*; +public class Base64Converter { + private final static char[] ALPHABET = String_.XtoCharAry("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); + private static int[] toInt = null;//new int[128]; + static void Init() { + toInt = new int[128]; + for(int i=0; i< ALPHABET.length; i++){ + toInt[ALPHABET[i]]= i; + } + } + public static String EncodeString(String orig) {return Encode(Bry_.new_u8(orig));} + public static String Encode(byte[] buf){ + if (toInt == null) Init(); + int size = buf.length; + char[] ar = new char[((size + 2) / 3) * 4]; + int a = 0; + int i=0; + while(i < size){ + byte b0 = buf[i++]; + byte b1 = (i < size) ? buf[i++] : (byte)0; + byte b2 = (i < size) ? buf[i++] : (byte)0; + + int mask = 0x3F; + ar[a++] = ALPHABET[(b0 >> 2) & mask]; + ar[a++] = ALPHABET[((b0 << 4) | ((b1 & 0xFF) >> 4)) & mask]; + ar[a++] = ALPHABET[((b1 << 2) | ((b2 & 0xFF) >> 6)) & mask]; + ar[a++] = ALPHABET[b2 & mask]; + } + switch(size % 3){ + case 1: ar[--a] = '='; + ar[--a] = '='; + break; + case 2: ar[--a] = '='; break; + } + return new String(ar); + } + public static String DecodeString(String orig) {return String_.new_u8(Decode(orig));} + public static byte[] Decode(String s){ + if (toInt == null) Init(); + int sLen = String_.Len(s); + int delta = String_.Has_at_end(s, "==") ? 2 : String_.Has_at_end(s, "=") ? 1 : 0; + byte[] buffer = new byte[sLen *3/4 - delta]; + int mask = 0xFF; + int index = 0; + for(int i=0; i< sLen; i+=4){ + int c0 = toInt[String_.CharAt(s, i)]; + int c1 = toInt[String_.CharAt(s, i + 1)]; + buffer[index++]= (byte)(((c0 << 2) | (c1 >> 4)) & mask); + if(index >= buffer.length){ + return buffer; + } + int c2 = toInt[String_.CharAt(s, i + 2)]; + buffer[index++]= (byte)(((c1 << 4) | (c2 >> 2)) & mask); + if(index >= buffer.length){ + return buffer; + } + int c3 = toInt[String_.CharAt(s, i + 3)]; + buffer[index++]= (byte)(((c2 << 6) | c3) & mask); + } + return buffer; + } +} diff --git a/100_core/src/gplx/core/texts/BaseXXConverter_tst.java b/100_core/src/gplx/core/texts/BaseXXConverter_tst.java index a27517de8..f8287cc83 100644 --- a/100_core/src/gplx/core/texts/BaseXXConverter_tst.java +++ b/100_core/src/gplx/core/texts/BaseXXConverter_tst.java @@ -13,3 +13,44 @@ 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.core.texts; import gplx.*; import gplx.core.*; +import org.junit.*; +public class BaseXXConverter_tst { + @Test public void Base32() { + tst_Base32("", ""); + tst_Base32("f", "MY"); + tst_Base32("fo", "MZXQ"); + tst_Base32("foo", "MZXW6"); + tst_Base32("foob", "MZXW6YQ"); + tst_Base32("fooba", "MZXW6YTB"); + tst_Base32("foobar", "MZXW6YTBOI"); + tst_Base32("A", "IE"); + tst_Base32("a", "ME"); + tst_Base32("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", "IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLIZDGNBVGY3Q"); + } + @Test public void Base64() { + tst_Base64("", ""); + tst_Base64("f", "Zg=="); + tst_Base64("fo", "Zm8="); + tst_Base64("foo", "Zm9v"); + tst_Base64("foob", "Zm9vYg=="); + tst_Base64("fooba", "Zm9vYmE="); + tst_Base64("foobar", "Zm9vYmFy"); +// tst_Base64("A", "IE"); +// tst_Base64("a", "ME"); +// tst_Base64("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", "IFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLIZDGNBVGY3Q"); + } + void tst_Base32(String orig, String expd) { + String actl = Base32Converter.EncodeString(orig); + Tfds.Eq(expd, actl); + String decode = Base32Converter.DecodeString(actl); + Tfds.Eq(orig, decode); + } + void tst_Base64(String orig, String expd) { + String actl = Base64Converter.EncodeString(orig); + Tfds.Eq(expd, actl); + String decode = Base64Converter.DecodeString(actl); + Tfds.Eq(orig, decode); + } +} +//http://tools.ietf.org/html/rfc4648: test vectors for "foobar" diff --git a/100_core/src/gplx/core/texts/CharStream.java b/100_core/src/gplx/core/texts/CharStream.java index a27517de8..8481973ff 100644 --- a/100_core/src/gplx/core/texts/CharStream.java +++ b/100_core/src/gplx/core/texts/CharStream.java @@ -13,3 +13,53 @@ 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.core.texts; import gplx.*; import gplx.core.*; +public class CharStream { + public char[] Ary() {return ary;} char[] ary; + public int Len() {return len;} int len; + public int Pos() {return pos;} int pos = BgnPos; static final int BgnPos = -1; + public boolean AtBgn() {return pos <= BgnPos;} + public boolean AtEnd() {return pos >= len;} + public boolean AtMid() {return pos > BgnPos && pos < len;} + public char Cur() {try {return ary[pos];} catch (Exception exc) {Err_.Noop(exc); throw Err_.new_missing_idx(pos, this.Len());}} + public void MoveNext() {pos++;} + public void MoveNextBy(int offset) {pos += offset;} + public void MoveBack() {pos--;} + public void MoveBackBy(int offset) {pos -= offset;} + public void Move_to(int val) {pos = val;} + public boolean Match(String match) { + int matchLen = String_.Len(match); + for (int i = 0; i < matchLen; i++) { + int cur = pos + i; + if (cur >= len || ary[cur] != String_.CharAt(match, i)) return false; + } + return true; + } + public boolean MatchAndMove(String match) { + int matchLen = String_.Len(match); + boolean rv = Match(match); + if (rv) MoveNextBy(matchLen); + return rv; + } + public boolean MatchAndMove(char match) { + boolean rv = ary[pos] == match; + if (rv) pos++; + return rv; + } + public String To_str() {return Char_.To_str(ary, 0, len);} + public String XtoStrAtCur(int length) { + length = (pos + length > len) ? len - pos : length; + return Char_.To_str(ary, pos, length); + } + public String To_str_by_pos(int bgn, int end) { + if (bgn < 0) bgn = 0; if (end > len - 1) end = len - 1; + return Char_.To_str(ary, bgn, end - bgn + 1); + } + public static CharStream pos0_(String text) { + CharStream rv = new CharStream(); + rv.ary = String_.XtoCharAry(text); + rv.len = Array_.Len(rv.ary); + rv.MoveNext(); // bgn at pos=0 + return rv; + } CharStream(){} +} diff --git a/100_core/src/gplx/core/texts/CharStream_tst.java b/100_core/src/gplx/core/texts/CharStream_tst.java index a27517de8..3466f7c85 100644 --- a/100_core/src/gplx/core/texts/CharStream_tst.java +++ b/100_core/src/gplx/core/texts/CharStream_tst.java @@ -13,3 +13,47 @@ 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.core.texts; import gplx.*; import gplx.core.*; +import org.junit.*; +public class CharStream_tst { + @Before public void setup() { + stream = CharStream.pos0_("abcdefgh"); + } + @Test public void To_str() { + Tfds.Eq(stream.To_str(), "abcdefgh"); + } + @Test public void CurrentText() { + stream.MoveNextBy(1); + Tfds.Eq(stream.XtoStrAtCur(2), "bc"); + Tfds.Eq(stream.To_str(), "abcdefgh"); + } + @Test public void CurrentText_outOfBounds() { + stream.MoveNextBy(7); + Tfds.Eq(stream.XtoStrAtCur(2), "h"); + } + @Test public void Match() { + stream.MoveNextBy(6); + tst_Match(true, "g"); + tst_Match(false, "z"); + tst_Match(true, "gh"); + tst_Match(false, "gz"); + tst_Match(false, "ghi"); + } + @Test public void AtBounds() { + stream.Move_to(-1); + tst_AtBounds(true, false, false); + + stream.Move_to(0); + tst_AtBounds(false, true, false); + + stream.Move_to(stream.Len()); + tst_AtBounds(false, false, true); + } + void tst_Match(boolean expd, String text) {Tfds.Eq(expd, stream.Match(text));} + void tst_AtBounds(boolean atBgn, boolean atMid, boolean atEnd) { + Tfds.Eq(atBgn, stream.AtBgn()); + Tfds.Eq(atMid, stream.AtMid()); + Tfds.Eq(atEnd, stream.AtEnd()); + } + CharStream stream; +} diff --git a/100_core/src/gplx/core/texts/RegxPatn_cls_ioMatch.java b/100_core/src/gplx/core/texts/RegxPatn_cls_ioMatch.java index a27517de8..1af82cf2b 100644 --- a/100_core/src/gplx/core/texts/RegxPatn_cls_ioMatch.java +++ b/100_core/src/gplx/core/texts/RegxPatn_cls_ioMatch.java @@ -13,3 +13,21 @@ 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.core.texts; import gplx.*; import gplx.core.*; +import gplx.langs.regxs.*; +public class RegxPatn_cls_ioMatch { + public String Raw() {return raw;} private String raw; + public boolean CaseSensitive() {return caseSensitive;} private boolean caseSensitive; + public boolean Matches(String text) { + text = String_.CaseNormalize(caseSensitive, text); + return Regx_adp_.Match(text, compiled);} // WNT-centric: Io_mgr paths are case-insensitive; + @Override public String toString() {return raw;} + + String compiled; + @gplx.Internal protected RegxPatn_cls_ioMatch(String raw, String compiled, boolean caseSensitive) { + this.caseSensitive = caseSensitive; + this.raw = raw; + compiled = String_.CaseNormalize(caseSensitive, compiled); + this.compiled = compiled; + } +} diff --git a/100_core/src/gplx/core/texts/RegxPatn_cls_ioMatch_.java b/100_core/src/gplx/core/texts/RegxPatn_cls_ioMatch_.java index a27517de8..081c13c10 100644 --- a/100_core/src/gplx/core/texts/RegxPatn_cls_ioMatch_.java +++ b/100_core/src/gplx/core/texts/RegxPatn_cls_ioMatch_.java @@ -13,3 +13,39 @@ 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.core.texts; import gplx.*; import gplx.core.*; +import gplx.core.strings.*; import gplx.langs.regxs.*; +public class RegxPatn_cls_ioMatch_ { + public static final String Wildcard = "*"; + public static final String OrDelimiter = "|"; + public static final RegxPatn_cls_ioMatch All = RegxPatn_cls_ioMatch_.parse(Wildcard, false); + public static final String ImpossiblePath = "<>"; //"<>" should be an impossible url; NOTE: do not pick * or | or : or \ + public static final RegxPatn_cls_ioMatch None = RegxPatn_cls_ioMatch_.parse(RegxPatn_cls_ioMatch_.ImpossiblePath, false); + public static RegxPatn_cls_ioMatch cast(Object obj) {try {return (RegxPatn_cls_ioMatch)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, RegxPatn_cls_ioMatch.class, obj);}} + public static RegxPatn_cls_ioMatch parse(String raw, boolean caseSensitive) { + String compiled = RegxPatn_cls_ioMatch_.Compile(raw); + return new RegxPatn_cls_ioMatch(raw, compiled, caseSensitive); + } + @gplx.Internal protected static String Compile(String raw) { + if (raw == ImpossiblePath) return ImpossiblePath; + + String_bldr sb = String_bldr_.new_(); + sb.Add(Regx_bldr.Tkn_LineBegin); // Char_LineBegin for exact match (else "LIKE a" would match "abc") + int rawLen = String_.Len(raw); + for (int i = 0; i < rawLen; i++) { + char c = String_.CharAt(raw, i); + if (c == '\\') + sb.Add("\\\\"); + else if (c == '*') + sb.Add(".").Add(Regx_bldr.Tkn_Wild_0Plus); + else if (c == '|') + sb.Add(Regx_bldr.Tkn_LineEnd).Add("|").Add(Regx_bldr.Tkn_LineBegin); // each term must be bracketed by lineBgn/lineEnd; ex: A|B -> ^A$|^B$ + else + sb.Add(c); + } + sb.Add(Regx_bldr.Tkn_LineEnd); + return sb.To_str(); + } + public static final String InvalidCharacters = "|*?\"<>"; // : / \ are omitted b/c they will cause full paths to fail + public static final String ValidCharacters = Regx_bldr.Excludes(InvalidCharacters); +} diff --git a/100_core/src/gplx/core/texts/RegxPatn_cls_ioMatch_tst.java b/100_core/src/gplx/core/texts/RegxPatn_cls_ioMatch_tst.java index a27517de8..62dfc663c 100644 --- a/100_core/src/gplx/core/texts/RegxPatn_cls_ioMatch_tst.java +++ b/100_core/src/gplx/core/texts/RegxPatn_cls_ioMatch_tst.java @@ -13,3 +13,44 @@ 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.core.texts; import gplx.*; import gplx.core.*; +import org.junit.*; +public class RegxPatn_cls_ioMatch_tst { + @Test public void SimpleMatches() { + tst_Matches("file.cs", "file.cs", true); // basic + tst_Matches("file.cs", "file.cs.exe", false); // fail: must match name precisely + tst_Matches("file.cs", "tst_file.cs", false); // fail: must match name precisely + } + @Test public void Wildcard() { + tst_Matches("*.cs", "file.cs", true); // pass: before + tst_Matches("file*", "file_valid.cs", true); // pass: after + tst_Matches("*.exe", "file.cs", false); // fail: before + tst_Matches("file*", "invalid_file.cs", false); // fail: after + } + @Test public void DoubleWildcard() { + tst_Matches("*cs*", "file.cs", true); // pass: after + tst_Matches("*cs*", "csFile.exe", true); // pass: before + tst_Matches("*cs*", "file.cs.exe", true); // pass: middle + tst_Matches("*cs*", "file.exe", false); // fail + } + @Test public void Compound() { + tst_Matches("*.cs|*.exe", "file.cs", true); // pass: match first + tst_Matches("*.cs|*.exe", "file.exe", true); // pass: match second + tst_Matches("*.cs|*.exe", "file.dll", false); // fail: match neither + tst_Matches("*.cs|*.exe", "file.cs.exe.dll", false); // fail: match neither (though both are embedded) + } + @Test public void Backslash() { + tst_Matches("*\\bin\\*", "C:\\project\\bin\\", true); // pass: dir + tst_Matches("*\\bin\\*", "C:\\project\\bin\\file.dll", true); // pass: fil + tst_Matches("*\\bin\\*", "C:\\project\\binFiles\\", false); // fail + } + @Test public void MixedCase() { + tst_Matches("file.cs", "file.cs", true); // pass: same case + tst_Matches("file.cs", "File.cS", true); // pass: diff case + } + void tst_Matches(String regx, String raw, boolean expd) { + RegxPatn_cls_ioMatch pattern = RegxPatn_cls_ioMatch_.parse(regx, false); + boolean actl = pattern.Matches(raw); + Tfds.Eq(expd, actl); + } +} diff --git a/100_core/src/gplx/core/texts/RegxPatn_cls_like.java b/100_core/src/gplx/core/texts/RegxPatn_cls_like.java index a27517de8..d1e5f859b 100644 --- a/100_core/src/gplx/core/texts/RegxPatn_cls_like.java +++ b/100_core/src/gplx/core/texts/RegxPatn_cls_like.java @@ -13,3 +13,18 @@ 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.core.texts; import gplx.*; import gplx.core.*; +import gplx.langs.regxs.*; +public class RegxPatn_cls_like { + public char Escape() {return escape;} char escape; public static final char EscapeDefault = '|'; + public String Raw() {return raw;} private String raw; + public boolean Matches(String text) {return Regx_adp_.Match(text, compiled);} + @Override public String toString() {return String_.Format("LIKE {0} ESCAPE {1} -> {2}", raw, escape, compiled);} + + String compiled; + @gplx.Internal protected RegxPatn_cls_like(String raw, String compiled, char escape) { + this.raw = raw; + this.compiled = compiled; + this.escape = escape; + } +} diff --git a/100_core/src/gplx/core/texts/RegxPatn_cls_like_.java b/100_core/src/gplx/core/texts/RegxPatn_cls_like_.java index a27517de8..5a133fbf5 100644 --- a/100_core/src/gplx/core/texts/RegxPatn_cls_like_.java +++ b/100_core/src/gplx/core/texts/RegxPatn_cls_like_.java @@ -13,3 +13,49 @@ 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.core.texts; import gplx.*; import gplx.core.*; +import gplx.core.strings.*; import gplx.langs.regxs.*; +public class RegxPatn_cls_like_ { + public static RegxPatn_cls_like parse(String regxRaw, char escape) { + String regx = Compile(regxRaw, escape); + return new RegxPatn_cls_like(regxRaw, regx, escape); + } + static String Compile(String raw, char escape) { + char Wildcard = '%', AnyChar = '_'; + boolean insideCharSet = false; + String_bldr sb = String_bldr_.new_(); + sb.Add(Regx_bldr.Tkn_LineBegin); + int rawLen = String_.Len(raw); + for (int i = 0; i < rawLen; i++) { + char c = String_.CharAt(raw, i); + if (c == escape) { // escape: ignore cur, append next + i++; + if (i < rawLen) sb.Add(String_.CharAt(raw, i)); + else throw Err_.new_wo_type("escape cannot be last char", "raw", raw, "escape", escape, "i", i); + } + else if (c == Wildcard) { // % -> .* + sb.Add(Regx_bldr.Tkn_AnyChar).Add(Regx_bldr.Tkn_Wild_0Plus); + } + else if (c == AnyChar) // _ -> . + sb.Add(Regx_bldr.Tkn_AnyChar); + else if (c == Regx_bldr.Tkn_CharSetBegin) { // toggle insideCharSet for ^ + insideCharSet = true; + sb.Add(c); + } + else if (c == Regx_bldr.Tkn_CharSetEnd) { // toggle insideCharSet for ^ + insideCharSet = false; + sb.Add(c); + } + else if (c == Regx_bldr.Tkn_Not && insideCharSet) { // ^ is used for Not in CharSet, but also used for LineStart; do not escape if insideCharSet + insideCharSet = false; + sb.Add(c); + } + else if (Regx_bldr.RegxChar_chk(c)) + sb.Add(Regx_bldr.Tkn_Escape).Add(c); + else // regular text + sb.Add(c); + } + sb.Add(Regx_bldr.Tkn_LineEnd); + return sb.To_str(); + } +} diff --git a/100_core/src/gplx/core/texts/RegxPatn_cls_like_tst.java b/100_core/src/gplx/core/texts/RegxPatn_cls_like_tst.java index a27517de8..c381e8dbc 100644 --- a/100_core/src/gplx/core/texts/RegxPatn_cls_like_tst.java +++ b/100_core/src/gplx/core/texts/RegxPatn_cls_like_tst.java @@ -13,3 +13,72 @@ 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.core.texts; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.langs.regxs.*; +public class RegxPatn_cls_like_tst { + @Test public void Basic() { + tst_Match("abcd", "abcd", true); // basic; pass + tst_Match("abcd", "zbcd", false); // basic; fail + tst_Match("abcd", "abc", false); // no wildcard; must be exact match + tst_Match("a cd", "a cd", true); // check space works + } + @Test public void Wildcard() { + tst_Match("abcd", "a%", true); // bgn; pass + tst_Match("abcd", "b%", false); // bgn; fail + tst_Match("abcd", "%d", true); // end; pass + tst_Match("abcd", "%c", false); // end; fail + tst_Match("abcd", "%b%", true); // flank; pass + tst_Match("abcd", "%e%", false); // flank; fail + tst_Match("abcd", "%a%", true); // flank; bgn; pass + tst_Match("abcd", "%d%", true); // flank; end; pass + } + @Test public void Any() { + tst_Match("abcd", "a_cd", true); // basic; pass + tst_Match("abcd", "z_cd", false); // basic; fail + tst_Match("abcd", "a_c", false); // fail; check no wildcard + } + @Test public void CharSet() { + tst_Match("abcd", "a[b]cd", true); // pass + tst_Match("abcd", "a[x]cd", false); // fail + tst_Match("abcd", "a[bcde]cd", true); // multiple; pass + tst_Match("abcd", "a[xyz]cd", false); // multiple; fail + tst_Match("abcd", "a[^z]cd", true); // not; pass + tst_Match("abcd", "a[^b]cd", false); // not; fail + } + @Test public void Escape() { + tst_Match("a%b", "a|%b", true); // escape wildcard; pass + tst_Match("a%bc", "a|%b", false); // escape wildcard; fail + tst_Match("a|b", "a|b", false); // escape char; fail + tst_Match("a|b", "a||b", true); // escape char; pass + } + @Test public void Escape_diffChar() { + tst_Match("a%b", "a~%b", '~', true); // escape wildcard; pass + tst_Match("a%bc", "a~%b", '~', false); // escape wildcard; fail + tst_Match("a|b", "a|b", '~', true); // no escape needed + tst_Match("a~b", "a~b", '~', false); // escape char; fail + tst_Match("a~b", "a~~b", '~', true); // escape char; pass + } + @Test public void Chars() { // Escape Regx_bldr; ex: LIKE 'a{' -> a\{ + tst_EscapeRegxChar(Regx_bldr.Tkn_Escape); // \ + tst_EscapeRegxChar(Regx_bldr.Tkn_GroupBegin); // [ + tst_EscapeRegxChar(Regx_bldr.Tkn_GroupEnd); // ] + tst_EscapeRegxChar(Regx_bldr.Tkn_LineBegin); // ^ + tst_EscapeRegxChar(Regx_bldr.Tkn_LineEnd); // $ + tst_EscapeRegxChar(Regx_bldr.Tkn_RepBegin); // { + tst_EscapeRegxChar(Regx_bldr.Tkn_RepEnd); // } + tst_EscapeRegxChar(Regx_bldr.Tkn_Wild_0or1); // ? + tst_EscapeRegxChar(Regx_bldr.Tkn_Wild_0Plus); // * + tst_EscapeRegxChar(Regx_bldr.Tkn_Wild_1Plus); // + + } + void tst_Match(String raw, String regx, boolean expd) {tst_Match(raw, regx, RegxPatn_cls_like.EscapeDefault, expd);} + void tst_Match(String raw, String regx, char escape, boolean expd) { + RegxPatn_cls_like like = RegxPatn_cls_like_.parse(regx, escape); + boolean actl = like.Matches(raw); + Tfds.Eq(expd, actl, "raw={0} regx={1} expd={2}", raw, regx, expd); + } + void tst_EscapeRegxChar(char regexChar) { + RegxPatn_cls_like like = RegxPatn_cls_like_.parse(Object_.Xto_str_strict_or_empty(regexChar), '|'); + Tfds.Eq(true, like.Matches(Object_.Xto_str_strict_or_empty(regexChar))); + Tfds.Eq(false, like.Matches("a")); // catches errors for improper escaping of wildcard + } +} diff --git a/100_core/src/gplx/core/texts/StringTableBldr.java b/100_core/src/gplx/core/texts/StringTableBldr.java index a27517de8..7eecbf241 100644 --- a/100_core/src/gplx/core/texts/StringTableBldr.java +++ b/100_core/src/gplx/core/texts/StringTableBldr.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.core.texts; import gplx.*; import gplx.core.*; +import gplx.core.strings.*; +public class StringTableBldr { + public void ClearRows() {rows.Clear();} + public StringTableCol Col_(int i) {return FetchAtOrNew(i);} + public StringTableBldr DefaultHalign_(StringTableColAlign v) {defaultHalign = v; return this;} StringTableColAlign defaultHalign = StringTableColAlign.Left; + public StringTableBldr Add(String... row) { + rows.Add(row); + for (int i = 0; i < row.length; i++) { + StringTableCol col = FetchAtOrNew(i); + col.AdjustFor(row[i]); + } + return this; + } + public StringTableCol FetchAtOrNew(int i) { + if (i < cols.Count()) return StringTableCol.as_(cols.Get_at(i)); + StringTableCol col = StringTableCol.new_(); + col.Halign_(defaultHalign); + cols.Add(i, col); + return col; + } + public String To_str() { + sb.Clear(); + for (int rowI = 0; rowI < rows.Count(); rowI++) { + String[] row = (String[])rows.Get_at(rowI); + for (int colI = 0; colI < row.length; colI++) { + if (colI != 0) sb.Add(" "); + StringTableCol col = StringTableCol.as_(cols.Get_at(colI)); if (col == null) throw Err_.new_missing_idx(colI, cols.Count()); + sb.Add(col.PadCell(row[colI])); + } + sb.Add(String_.CrLf); + } + return sb.To_str_and_clear(); + } + + public static StringTableBldr new_() {return new StringTableBldr();} StringTableBldr() {} + Ordered_hash cols = Ordered_hash_.New(); + List_adp rows = List_adp_.New(); + String_bldr sb = String_bldr_.new_(); +} diff --git a/100_core/src/gplx/core/texts/StringTableBldr_tst.java b/100_core/src/gplx/core/texts/StringTableBldr_tst.java index a27517de8..d01f972fe 100644 --- a/100_core/src/gplx/core/texts/StringTableBldr_tst.java +++ b/100_core/src/gplx/core/texts/StringTableBldr_tst.java @@ -13,3 +13,45 @@ 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.core.texts; import gplx.*; import gplx.core.*; +import org.junit.*; +public class StringTableBldr_tst { + @Before public void setup() { + bldr = StringTableBldr.new_(); + } StringTableBldr bldr; + @Test public void TwoCols() { + bldr.Add("a", "aa") + .Add("bb", "b"); + tst_XtoStr + ( "a aa" + , "bb b " + , "" + ); + } + @Test public void RightAlign() { + bldr.Add("a", "aa") + .Add("bb", "b"); + bldr.FetchAtOrNew(0).Halign_(StringTableColAlign.Right); + bldr.FetchAtOrNew(1).Halign_(StringTableColAlign.Right); + tst_XtoStr + ( " a aa" + , "bb b" + , "" + ); + } + @Test public void CenterAlign() { + bldr.Add("aaaa", "a") + .Add("b", "bbbb"); + bldr.FetchAtOrNew(0).Halign_(StringTableColAlign.Mid); + bldr.FetchAtOrNew(1).Halign_(StringTableColAlign.Mid); + tst_XtoStr + ( "aaaa a " + , " b bbbb" + , "" + ); + } + void tst_XtoStr(String... expdLines) { + String expd = String_.Concat_with_obj(String_.CrLf, (Object[])expdLines); + Tfds.Eq(expd, bldr.To_str()); + } +} diff --git a/100_core/src/gplx/core/texts/StringTableCol.java b/100_core/src/gplx/core/texts/StringTableCol.java index a27517de8..6d2c13648 100644 --- a/100_core/src/gplx/core/texts/StringTableCol.java +++ b/100_core/src/gplx/core/texts/StringTableCol.java @@ -13,3 +13,25 @@ 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.core.texts; import gplx.*; import gplx.core.*; +public class StringTableCol { + public StringTableColAlign Halign() {return halign;} public StringTableCol Halign_(StringTableColAlign val) {halign = val; return this;} StringTableColAlign halign = StringTableColAlign.Left; + public int LengthMax() {return lengthMax;} int lengthMax = Int_.Min_value; + public int LengthMin() {return lengthMin;} int lengthMin = Int_.Max_value; + public void AdjustFor(String s) { + int length = String_.Len(s); + if (length > lengthMax) lengthMax = length; + if (length < lengthMin) lengthMin = length; + } + public String PadCell(String cell) { + int diff = lengthMax - String_.Len(cell); + int val = halign.Val(); + if (val == StringTableColAlign.Left.Val()) return cell + String_.Repeat(" ", diff); + else if (val == StringTableColAlign.Right.Val()) return String_.Repeat(" ", diff) + cell; + else if (val == StringTableColAlign.Mid.Val()) return String_.Concat(String_.Repeat(" ", diff / 2), cell, String_.Repeat(" ", (diff / 2) + (diff % 2))); + else throw Err_.new_unhandled(halign.Val()); + } + public static StringTableCol new_() {return new StringTableCol();} StringTableCol() {} + public static StringTableCol as_(Object obj) {return obj instanceof StringTableCol ? (StringTableCol)obj : null;} + public static StringTableCol cast(Object obj) {try {return (StringTableCol)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, StringTableCol.class, obj);}} +} diff --git a/100_core/src/gplx/core/texts/StringTableColAlign.java b/100_core/src/gplx/core/texts/StringTableColAlign.java index a27517de8..c75ee1498 100644 --- a/100_core/src/gplx/core/texts/StringTableColAlign.java +++ b/100_core/src/gplx/core/texts/StringTableColAlign.java @@ -13,3 +13,15 @@ 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.core.texts; import gplx.*; import gplx.core.*; +public class StringTableColAlign { + public int Val() {return val;} int val = 0; + public static StringTableColAlign new_(int v) { + StringTableColAlign rv = new StringTableColAlign(); + rv.val = v; + return rv; + } StringTableColAlign() {} + public static final StringTableColAlign Left = new_(0); + public static final StringTableColAlign Mid = new_(1); + public static final StringTableColAlign Right = new_(2); +} diff --git a/100_core/src/gplx/core/threads/Gfo_lock.java b/100_core/src/gplx/core/threads/Gfo_lock.java index a27517de8..be723b1bc 100644 --- a/100_core/src/gplx/core/threads/Gfo_lock.java +++ b/100_core/src/gplx/core/threads/Gfo_lock.java @@ -13,3 +13,14 @@ 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.core.threads; import gplx.*; import gplx.core.*; +import java.util.concurrent.locks.*; +public class Gfo_lock { + private final ReentrantLock lock = new ReentrantLock(true); + public void Lock() { + lock.lock(); + } + public void Unlock() { + lock.unlock(); + } +} diff --git a/100_core/src/gplx/core/threads/Thread_adp.java b/100_core/src/gplx/core/threads/Thread_adp.java index a27517de8..b1e5451e9 100644 --- a/100_core/src/gplx/core/threads/Thread_adp.java +++ b/100_core/src/gplx/core/threads/Thread_adp.java @@ -13,3 +13,32 @@ 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.core.threads; import gplx.*; import gplx.core.*; +import java.lang.*; +public class Thread_adp implements Runnable { + private final String thread_name; private final Cancelable cxl; private final boolean cxlable; + private final Gfo_invk invk_itm; private final String invk_cmd; private final GfoMsg invk_msg; + private Thread thread; + @gplx.Internal protected Thread_adp(String thread_name, Cancelable cxl, Gfo_invk invk_itm, String invk_cmd, GfoMsg invk_msg) { + this.thread_name = thread_name; this.cxl = cxl; this.cxlable = cxl != Cancelable_.Never; + this.invk_itm = invk_itm; this.invk_cmd = invk_cmd; this.invk_msg = invk_msg; + } + public String Thread__name() {return thread_name;} + public void Thread__cancel() {cxl.Cancel();} + public boolean Thread__cancelable() {return cxlable;} + public boolean Thread__is_alive() {return thread == null ? false : thread.isAlive();} + public void Thread__interrupt() {thread.interrupt();} + public void run() { + try { + Gfo_invk_.Invk_by_msg(invk_itm, invk_cmd, invk_msg); + } + catch (Exception e) { // catch exception + Gfo_log_.Instance.Warn("thread.failed", "thread_name", thread_name, "cmd", invk_cmd, "err", Err_.Message_gplx_log(e)); + } + } + public void Thread__start() { + this.thread = (thread_name == null) ? new Thread(this) : new Thread(this, thread_name); + thread.start(); + } + public static final Thread_adp Noop = new Thread_adp(Thread_adp_.Name_null, Cancelable_.Never, Gfo_invk_.Noop, "", GfoMsg_.Null); +} diff --git a/100_core/src/gplx/core/threads/Thread_adp_.java b/100_core/src/gplx/core/threads/Thread_adp_.java index a27517de8..d2b55e1bf 100644 --- a/100_core/src/gplx/core/threads/Thread_adp_.java +++ b/100_core/src/gplx/core/threads/Thread_adp_.java @@ -13,3 +13,20 @@ 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.core.threads; import gplx.*; import gplx.core.*; +public class Thread_adp_ { + public static void Sleep(int milliseconds) { + try {Thread.sleep(milliseconds);} catch (InterruptedException e) {throw Err_.new_exc(e, "core", "thread interrupted", "milliseconds", milliseconds);} + } + public static Thread_adp Start_by_key(String thread_name, Gfo_invk invk_itm, String invk_cmd) {return Start(thread_name, Cancelable_.Never, invk_itm, invk_cmd , GfoMsg_.new_cast_(invk_cmd));} + public static Thread_adp Start_by_val(String thread_name, Gfo_invk invk_itm, String invk_cmd, Object val) {return Start(thread_name, Cancelable_.Never, invk_itm, invk_cmd , GfoMsg_.new_cast_(invk_cmd).Add("v", val));} + public static Thread_adp Start_by_msg(String thread_name, Gfo_invk invk_itm, GfoMsg invk_msg) {return Start(thread_name, Cancelable_.Never, invk_itm, invk_msg.Key() , invk_msg);} + public static Thread_adp Start_by_key(String thread_name, Cancelable cxl, Gfo_invk invk_itm, String invk_cmd) {return Start(thread_name, cxl, invk_itm, invk_cmd , GfoMsg_.new_cast_(invk_cmd));} + public static Thread_adp Start_by_val(String thread_name, Cancelable cxl, Gfo_invk invk_itm, String invk_cmd, Object val) {return Start(thread_name, cxl, invk_itm, invk_cmd , GfoMsg_.new_cast_(invk_cmd).Add("v", val));} + private static Thread_adp Start(String thread_name, Cancelable cxl, Gfo_invk invk_itm, String invk_key, GfoMsg invk_msg) { + Thread_adp rv = new Thread_adp(thread_name, cxl, invk_itm, invk_key, invk_msg); + rv.Thread__start(); + return rv; + } + public static final String Name_null = null; +} diff --git a/100_core/src/gplx/core/threads/Thread_adp_mgr.java b/100_core/src/gplx/core/threads/Thread_adp_mgr.java index a27517de8..2c3e0ff00 100644 --- a/100_core/src/gplx/core/threads/Thread_adp_mgr.java +++ b/100_core/src/gplx/core/threads/Thread_adp_mgr.java @@ -13,3 +13,47 @@ 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.core.threads; import gplx.*; import gplx.core.*; +public class Thread_adp_mgr { + private final Ordered_hash hash = Ordered_hash_.New(); + private final int sleep_time, quit_time; + public Thread_adp_mgr(int sleep_time, int quit_time) {this.sleep_time = sleep_time; this.quit_time = quit_time;} + public void Add(String key, Thread_adp thread) { + Thread_halt_itm itm = null; + synchronized (hash) { + itm = (Thread_halt_itm)hash.Get_by(key); + if (itm != null && !itm.Thread.Thread__is_alive()) + hash.Del(key); + } + itm = new Thread_halt_itm(key, thread); + hash.Add(key, itm); + } + public void Halt(String uid, Thread_halt_cbk cbk) { + Thread_halt_itm itm = (Thread_halt_itm)hash.Get_by(uid); + Halt_by_wkr(itm, cbk); + } + public void Halt_all(Thread_halt_cbk cbk) { + int len = hash.Len(); + for (int i = 0; i < len; ++i) { + Thread_halt_itm itm = (Thread_halt_itm)hash.Get_at(i); + Halt_by_wkr(itm, cbk); + } + } + private void Halt_by_wkr(Thread_halt_itm itm, Thread_halt_cbk cbk) { + Thread_halt_wkr halt_wkr = new Thread_halt_wkr(this, itm, cbk, sleep_time, quit_time); + Thread_adp_.Start_by_key("thread_mgr.halt", halt_wkr, Thread_halt_wkr.Invk__halt); + synchronized (hash) { + hash.Del(itm.Key); + } + } + public void Del(String key) { + synchronized (hash) { + hash.Del(key); + } + } +} +class Thread_halt_itm { + public Thread_halt_itm(String key, Thread_adp thread) {this.Key = key; this.Thread = thread;} + public final String Key; + public final Thread_adp Thread; +} diff --git a/100_core/src/gplx/core/threads/Thread_halt_cbk.java b/100_core/src/gplx/core/threads/Thread_halt_cbk.java index a27517de8..645e881e5 100644 --- a/100_core/src/gplx/core/threads/Thread_halt_cbk.java +++ b/100_core/src/gplx/core/threads/Thread_halt_cbk.java @@ -13,3 +13,7 @@ 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.core.threads; import gplx.*; import gplx.core.*; +public interface Thread_halt_cbk { + void Thread__on_halt(boolean interrupted); +} diff --git a/100_core/src/gplx/core/threads/Thread_halt_cbk_.java b/100_core/src/gplx/core/threads/Thread_halt_cbk_.java index a27517de8..127488859 100644 --- a/100_core/src/gplx/core/threads/Thread_halt_cbk_.java +++ b/100_core/src/gplx/core/threads/Thread_halt_cbk_.java @@ -13,3 +13,10 @@ 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.core.threads; import gplx.*; import gplx.core.*; +public class Thread_halt_cbk_ { + public static final Thread_halt_cbk Noop = new Thread_halt_cbk_noop(); +} +class Thread_halt_cbk_noop implements Thread_halt_cbk { + public void Thread__on_halt(boolean interrupted) {} +} diff --git a/100_core/src/gplx/core/threads/Thread_halt_wkr.java b/100_core/src/gplx/core/threads/Thread_halt_wkr.java index a27517de8..4f01af931 100644 --- a/100_core/src/gplx/core/threads/Thread_halt_wkr.java +++ b/100_core/src/gplx/core/threads/Thread_halt_wkr.java @@ -13,3 +13,49 @@ 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.core.threads; import gplx.*; import gplx.core.*; +class Thread_halt_wkr implements Gfo_invk { + private final Thread_adp_mgr mgr; + private final Thread_adp thread; private final String thread_key; private final Thread_halt_cbk cbk; + private final long bgn_time; + private final int sleep_time, quit_time; + public Thread_halt_wkr(Thread_adp_mgr mgr, Thread_halt_itm itm, Thread_halt_cbk cbk, int sleep_time, int quit_time) { + this.mgr = mgr; this.thread = itm.Thread; this.thread_key = itm.Key; this.cbk = cbk; + this.sleep_time = sleep_time; this.quit_time = quit_time; + this.bgn_time = gplx.core.envs.System_.Ticks(); + } + private void Halt() { + // first, cancel the thread + thread.Thread__cancel(); + + // now check if canceled; interrupt if not; + while (true) { + long time_now = gplx.core.envs.System_.Ticks(); + boolean halted = false, interrupted = false; + if (thread.Thread__is_alive()) { // thread is still alive + if ( !thread.Thread__cancelable() // itm is not cancelable + || time_now > bgn_time + quit_time // itm is cancelable, but too much time passed + ) { + thread.Thread__interrupt(); // interrupt it + halted = interrupted = true; + } + } + else + halted = true; + + if (halted) { // thread halted; call cbk; + cbk.Thread__on_halt(interrupted); + mgr.Del(thread_key); + break; + } + else // else sleep and try again + Thread_adp_.Sleep(sleep_time); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__halt)) Halt(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk__halt = "halt"; +} diff --git a/100_core/src/gplx/core/threads/poolables/Gfo_poolable_itm.java b/100_core/src/gplx/core/threads/poolables/Gfo_poolable_itm.java index a27517de8..9963454d9 100644 --- a/100_core/src/gplx/core/threads/poolables/Gfo_poolable_itm.java +++ b/100_core/src/gplx/core/threads/poolables/Gfo_poolable_itm.java @@ -13,3 +13,8 @@ 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.core.threads.poolables; import gplx.*; import gplx.core.*; import gplx.core.threads.*; +public interface Gfo_poolable_itm { + Gfo_poolable_itm Pool__make (Gfo_poolable_mgr mgr, int idx, Object[] args); + void Pool__rls(); +} diff --git a/100_core/src/gplx/core/threads/poolables/Gfo_poolable_mgr.java b/100_core/src/gplx/core/threads/poolables/Gfo_poolable_mgr.java index a27517de8..1a5adb980 100644 --- a/100_core/src/gplx/core/threads/poolables/Gfo_poolable_mgr.java +++ b/100_core/src/gplx/core/threads/poolables/Gfo_poolable_mgr.java @@ -13,3 +13,84 @@ 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.core.threads.poolables; import gplx.*; import gplx.core.*; import gplx.core.threads.*; +import gplx.core.memorys.*; +public class Gfo_poolable_mgr implements Gfo_memory_itm { + private final Object thread_lock = new Object(); + private final Gfo_poolable_itm prototype; private final Object[] make_args; + private Gfo_poolable_itm[] pool; private int pool_nxt, pool_len; + public Gfo_poolable_mgr(Gfo_poolable_itm prototype, Object[] make_args, int init_pool_len, int pool_max) {// NOTE: random IndexOutOfBounds errors in Get around free_ary[--free_len] with free_len being -1; put member variable initialization within thread_lock to try to avoid; DATE:2014-09-21 + this.prototype = prototype; this.make_args = make_args; + this.pool_len = init_pool_len; + this.Clear_fast(); + } + public void Rls_mem() {Clear_safe();} + public void Clear_safe() {synchronized (thread_lock) {Clear_fast();}} + public void Clear_fast() { + this.pool = new Gfo_poolable_itm[pool_len]; + for (int i = 0; i < pool_len; ++i) + pool[i] = prototype.Pool__make(this, i, make_args); + this.free_ary = new int[pool_len]; + pool_nxt = free_len = 0; + } + public Gfo_poolable_itm Get_safe() {synchronized (thread_lock) {return Get_fast();}} + public Gfo_poolable_itm Get_fast() { + Gfo_poolable_itm rv = null; + int pool_idx = -1; + if (free_len > 0) { // free_itms in pool; use it + pool_idx = free_ary[--free_len]; + rv = pool[pool_idx]; + } + else { // nothing in pool; take next + if (pool_nxt == pool_len) + Expand_pool(); + pool_idx = pool_nxt++; + rv = pool[pool_idx]; + if (rv == null) { + rv = prototype.Pool__make(this, pool_idx, make_args); + pool[pool_idx] = rv; + } + } + return rv; + } + public void Rls_safe(int idx) {synchronized (thread_lock) {Rls_fast(idx);}} + public void Rls_fast(int idx) { + if (idx == -1) throw Err_.new_wo_type("rls called on poolable that was not created by pool_mgr"); + int pool_idx = pool_nxt - 1; + if (idx == pool_idx) // in-sequence; decrement count + if (free_len == 0) + this.pool_nxt = pool_idx; + else { // idx == pool_idx; assume entire pool released, but out of order + for (int i = 0; i < free_len; ++i) + free_ary[i] = 0; + free_len = 0; + } + else { // out-of-sequence + free_ary[free_len] = idx; + ++free_len; + } + } + private void Expand_pool() { + // expand pool + int new_pool_len = pool_len == 0 ? 2 : pool_len * 2; + Gfo_poolable_itm[] new_pool = new Gfo_poolable_itm[new_pool_len]; + Array_.Copy_to(pool, 0, new_pool, 0, pool_len); + this.pool = new_pool; + this.pool_len = new_pool_len; + + // expand free_ary to same len + int[] new_free = new int[pool_len]; + Array_.Copy_to(free_ary, 0, new_free, 0, free_len); + this.free_ary = new_free; + } + @gplx.Internal protected int[] Free_ary() {return free_ary;} private int[] free_ary; private int free_len; + @gplx.Internal protected int Free_len() {return free_len;} + @gplx.Internal protected int Pool_len() {return pool_len;} + @gplx.Internal protected int Pool_nxt() {return pool_nxt;} + + public static Gfo_poolable_mgr New_rlsable(Gfo_poolable_itm prototype, Object[] make_args, int init_pool_len, int pool_max) { + Gfo_poolable_mgr rv = new Gfo_poolable_mgr(prototype, make_args, init_pool_len, pool_max); + Gfo_memory_mgr.Instance.Reg_safe(rv); + return rv; + } +} diff --git a/100_core/src/gplx/core/threads/poolables/Gfo_poolable_mgr_.java b/100_core/src/gplx/core/threads/poolables/Gfo_poolable_mgr_.java index a27517de8..16717e7a3 100644 --- a/100_core/src/gplx/core/threads/poolables/Gfo_poolable_mgr_.java +++ b/100_core/src/gplx/core/threads/poolables/Gfo_poolable_mgr_.java @@ -13,3 +13,8 @@ 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.core.threads.poolables; import gplx.*; import gplx.core.*; import gplx.core.threads.*; +public class Gfo_poolable_mgr_ { + public static Gfo_poolable_mgr New(int len, int max, Gfo_poolable_itm prototype) {return new Gfo_poolable_mgr(prototype, Object_.Ary_empty, len, max);} + public static Gfo_poolable_mgr New(int len, int max, Gfo_poolable_itm prototype, Object[] make_args) {return new Gfo_poolable_mgr(prototype, make_args, len, max);} +} diff --git a/100_core/src/gplx/core/threads/poolables/Gfo_poolable_mgr_tst.java b/100_core/src/gplx/core/threads/poolables/Gfo_poolable_mgr_tst.java index a27517de8..040af5ccc 100644 --- a/100_core/src/gplx/core/threads/poolables/Gfo_poolable_mgr_tst.java +++ b/100_core/src/gplx/core/threads/poolables/Gfo_poolable_mgr_tst.java @@ -13,3 +13,69 @@ 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.core.threads.poolables; import gplx.*; import gplx.core.*; import gplx.core.threads.*; +import org.junit.*; +public class Gfo_poolable_mgr_tst { + private final Gfo_poolable_mgr_tstr tstr = new Gfo_poolable_mgr_tstr(); + @Before public void init() {tstr.Clear();} + @Test public void Get__one() { + tstr.Test__get(0); + tstr.Test__free__len(0); + tstr.Test__pool__len(2); + } + @Test public void Get__many__expand() { + tstr.Test__get(0); + tstr.Test__get(1); + tstr.Test__get(2); + tstr.Test__free__len(0); + tstr.Test__pool__len(4); + } + @Test public void Rls__lifo() { + tstr.Test__get(0); + tstr.Test__get(1); + tstr.Test__get(2); + tstr.Exec__rls(2); + tstr.Exec__rls(1); + tstr.Exec__rls(0); + tstr.Test__pool__nxt(0); + tstr.Test__free__len(0); + } + @Test public void Rls__fifo() { + tstr.Test__get(0); + tstr.Test__get(1); + tstr.Test__get(2); + tstr.Exec__rls(0); + tstr.Exec__rls(1); + tstr.Test__pool__nxt(3); + tstr.Test__free__len(2); // 2 items in free_ary + tstr.Test__free__ary(0, 1, 0, 0); + + tstr.Test__get(1); + tstr.Exec__rls(1); + + tstr.Exec__rls(2); + tstr.Test__free__len(0); // 0 items in free_ary + tstr.Test__free__ary(0, 0, 0, 0); + } +} +class Gfo_poolable_mgr_tstr { + private final Gfo_poolable_mgr mgr = new Gfo_poolable_mgr(new Sample_poolable_itm(null, -1, Object_.Ary_empty), Object_.Ary("make"), 2, 8); + public void Clear() {mgr.Clear_fast();} + public void Test__get(int expd_idx) { + Sample_poolable_itm actl_itm = (Sample_poolable_itm)mgr.Get_fast(); + Tfds.Eq(expd_idx, actl_itm.Pool__idx(), "pool_idx"); + } + public void Test__free__ary(int... expd) {Tfds.Eq_ary(expd, mgr.Free_ary(), "mgr.Free_ary()");} + public void Test__free__len(int expd) {Tfds.Eq(expd, mgr.Free_len(), "mgr.Free_len()");} + public void Test__pool__len(int expd) {Tfds.Eq(expd, mgr.Pool_len(), "mgr.Pool_len()");} + public void Test__pool__nxt(int expd) {Tfds.Eq(expd, mgr.Pool_nxt(), "mgr.Pool_nxt()");} + public void Exec__rls(int idx) {mgr.Rls_fast(idx);} +} +class Sample_poolable_itm implements Gfo_poolable_itm { + private Gfo_poolable_mgr pool_mgr; + public Sample_poolable_itm(Gfo_poolable_mgr pool_mgr, int pool_idx, Object[] make_args) {this.pool_mgr = pool_mgr; this.pool_idx = pool_idx; this.pool__make_args = make_args;} + public int Pool__idx() {return pool_idx;} private final int pool_idx; + public Object[] Pool__make_args() {return pool__make_args;} private final Object[] pool__make_args; + public void Pool__rls() {pool_mgr.Rls_safe(pool_idx);} + public Gfo_poolable_itm Pool__make (Gfo_poolable_mgr mgr, int idx, Object[] args) {return new Sample_poolable_itm(pool_mgr, idx, args);} +} diff --git a/100_core/src/gplx/core/threads/utils/Gfo_blocking_queue.java b/100_core/src/gplx/core/threads/utils/Gfo_blocking_queue.java index a27517de8..873659c38 100644 --- a/100_core/src/gplx/core/threads/utils/Gfo_blocking_queue.java +++ b/100_core/src/gplx/core/threads/utils/Gfo_blocking_queue.java @@ -13,3 +13,21 @@ 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.core.threads.utils; import gplx.*; import gplx.core.*; import gplx.core.threads.*; +import java.util.concurrent.ArrayBlockingQueue; +public class Gfo_blocking_queue { + private final ArrayBlockingQueue queue; + public Gfo_blocking_queue(int capacity) { + this.capacity = capacity; + this.queue = new ArrayBlockingQueue(capacity); + } + public int Capacity() {return capacity;} private final int capacity; + public void Put(Object o) { + try {queue.put(o);} + catch (InterruptedException e) {throw Err_.new_exc(e, "threads", "put interrupted");} + } + public Object Take() { + try {return queue.take();} + catch (InterruptedException e) {throw Err_.new_exc(e, "threads", "take interrupted");} + } +} diff --git a/100_core/src/gplx/core/threads/utils/Gfo_countdown_latch.java b/100_core/src/gplx/core/threads/utils/Gfo_countdown_latch.java index a27517de8..0a7c7dcf3 100644 --- a/100_core/src/gplx/core/threads/utils/Gfo_countdown_latch.java +++ b/100_core/src/gplx/core/threads/utils/Gfo_countdown_latch.java @@ -13,3 +13,18 @@ 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.core.threads.utils; import gplx.*; import gplx.core.*; import gplx.core.threads.*; +import java.util.concurrent.CountDownLatch; +public class Gfo_countdown_latch { + private final CountDownLatch latch; + public Gfo_countdown_latch(int count) { + latch = new CountDownLatch(count); + } + public void Countdown() { + latch.countDown(); + } + public void Await() { + try {latch.await();} + catch (InterruptedException e) {throw Err_.new_exc(e, "threads", "await interrupted");} + } +} diff --git a/100_core/src/gplx/core/times/DateAdp_parser.java b/100_core/src/gplx/core/times/DateAdp_parser.java index a27517de8..00b906659 100644 --- a/100_core/src/gplx/core/times/DateAdp_parser.java +++ b/100_core/src/gplx/core/times/DateAdp_parser.java @@ -13,3 +13,106 @@ 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.core.times; import gplx.*; import gplx.core.*; +public class DateAdp_parser { + public int[] Parse_iso8651_like(String raw_str) {Parse_iso8651_like(tmp_rv, raw_str); return tmp_rv;} int[] tmp_rv = new int[7]; + public void Parse_iso8651_like(int[] rv, String raw_str) { + byte[] raw_bry = Bry_.new_u8(raw_str); + Parse_iso8651_like(rv, raw_bry, 0, raw_bry.length); + } + public void Parse_iso8651_like(int[] rv, byte[] src, int bgn, int end) { + /* 1981-04-05T14:30:30.000-05:00 ISO 8601: http://en.wikipedia.org/wiki/ISO_8601 + 1981.04.05 14.30.30.000-05.00 ISO 8601 loose + 99991231_235959.999 gplx + yyyy-MM-ddTHH:mm:ss.fffzzzz Format String */ + int rv_len = rv.length; + for (int i = 0; i < rv_len; i++) + rv[i] = 0; // .Clear + int pos = bgn, rv_idx = 0, int_len = 0, max_len = max_lens[rv_idx]; + while (true) { + int int_val = -1; + byte b = pos < end ? src[pos] : Byte_ascii.Null; + switch (b) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + int_val = b - Byte_ascii.Num_0; // convert ascii to val; EX: 49 -> 1 + int_len = int_bldr.Add(int_val); + break; + } + if ( (int_val == -1 && int_len > 0) // char is not number && number exists (ignore consecutive delimiters: EX: "1981 01") + || int_len == max_len) { // max_len reached; necessary for gplxFormat + rv[rv_idx++] = int_bldr.BldAndClear(); + if (rv_idx == 7) break; // past frac; break; + int_len = 0; + max_len = max_lens[rv_idx]; + } + if (pos == end) break; + ++pos; + } + } + int[] max_lens = new int[] {4/*y*/,2/*M*/,2/*d*/,2/*H*/,2/*m*/,2/*s*/,3/*S*/,0}; + IntBldr int_bldr = IntBldr.new_(4); + public static DateAdp_parser new_() {return new DateAdp_parser();} DateAdp_parser() {} +} +class IntBldr { + public int Add(char c) { + if (idx > digitsLen - 1) throw Err_.new_missing_idx(idx, digitsLen); + digits[idx++] = XbyChar(c); + return idx; + } + public int Add(int i) { + if (idx > digitsLen - 1) throw Err_.new_missing_idx(idx, digitsLen); + digits[idx++] = i; + return idx; + } + public int Parse(String raw) { + ParseStr(raw); + try {return Bld();} + catch (Exception exc) {throw Err_.new_parse_exc(exc, int.class, raw);} + } + public boolean ParseTry(String raw) { + ParseStr(raw); + for (int i = 0; i < idx; i++) + if (digits[i] < 0) return false; + return true; + } + public int Bld() { + int rv = 0, exponent = 1; + for (int i = idx - 1; i > -1; i--) { + int digit = digits[i]; + if (digit < 0) throw Err_.new_wo_type("invalid char", "char", (char)-digits[i], "ascii", -digits[i]); + rv += digit * exponent; + exponent *= 10; + } + return sign * rv; + } + public int BldAndClear() { + int rv = Bld(); + this.Clear(); + return rv; + } + public void Clear() {idx = 0; sign = 1;} + void ParseStr(String raw) { + this.Clear(); + int rawLength = String_.Len(raw); + for (int i = 0; i < rawLength; i++) { + char c = String_.CharAt(raw, i); + if (i == 0 && c == '-') + sign = -1; + else + Add(c); + } + } + int XbyChar(char c) { + if (c == '0') return 0; else if (c == '1') return 1; else if (c == '2') return 2; else if (c == '3') return 3; else if (c == '4') return 4; + else if (c == '5') return 5; else if (c == '6') return 6; else if (c == '7') return 7; else if (c == '8') return 8; else if (c == '9') return 9; + else return -(int)c; // error handling: don't throw error; store ascii value in digit and throw if needed + } + int[] digits; int idx, digitsLen; int sign = 1; + public static IntBldr new_(int digitsMax) { + IntBldr rv = new IntBldr(); + rv.digits = new int[digitsMax]; + rv.digitsLen = digitsMax; + return rv; + } +} diff --git a/100_core/src/gplx/core/times/DateAdp_parser_tst.java b/100_core/src/gplx/core/times/DateAdp_parser_tst.java index a27517de8..dc5a6d10a 100644 --- a/100_core/src/gplx/core/times/DateAdp_parser_tst.java +++ b/100_core/src/gplx/core/times/DateAdp_parser_tst.java @@ -13,3 +13,20 @@ 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.core.times; import gplx.*; import gplx.core.*; +import org.junit.*; +public class DateAdp_parser_tst { + @Before public void init() {} DateAdp_parser_fxt fxt = new DateAdp_parser_fxt(); + @Test public void Parse_gplx() { + fxt.Test_Parse_iso8651_like("2000-01-02T03:04:05.006-05:00" , 2000, 1, 2, 3, 4, 5, 6); + fxt.Test_Parse_iso8651_like("2000-01-02" , 2000, 1, 2, 0, 0, 0, 0); + } +} +class DateAdp_parser_fxt { + DateAdp_parser parser = DateAdp_parser.new_(); int[] actl = new int[7]; + public void Test_Parse_iso8651_like(String s, int... expd) { + byte[] bry = Bry_.new_a7(s); + parser.Parse_iso8651_like(actl, bry, 0, bry.length); + Tfds.Eq_ary(expd, actl, s); + } +} diff --git a/100_core/src/gplx/core/times/Time_span__basic_tst.java b/100_core/src/gplx/core/times/Time_span__basic_tst.java index a27517de8..e4e643064 100644 --- a/100_core/src/gplx/core/times/Time_span__basic_tst.java +++ b/100_core/src/gplx/core/times/Time_span__basic_tst.java @@ -13,3 +13,73 @@ 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.core.times; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Time_span__basic_tst { + @Test public void seconds_() { + Time_span expd = Time_span_.fracs_(123987); + Time_span actl = Time_span_.seconds_(123.987); + Tfds.Eq(expd, actl); + } + @Test public void TotalSecs() { + Time_span val = Time_span_.fracs_(1987); + Tfds.Eq_decimal(Decimal_adp_.parts_(1, 987), val.Total_secs()); + } + @Test public void Units() { + tst_Units("01:02:03.987", 1, 2, 3, 987); + tst_Units("01:00:03", 1, 0, 3, 0); + tst_Units("01:00:00.987", 1, 0, 0, 987); + tst_Units("02:00.987", 0, 2, 0, 987); + } + @Test public void Add() { + Time_span val = Time_span_.fracs_(3); + Time_span arg = Time_span_.fracs_(2); + Time_span expd = Time_span_.fracs_(5); + Time_span actl = val.Add(arg); + Tfds.Eq(expd, actl); + } + @Test public void Subtract() { + Time_span val = Time_span_.fracs_(3); + Time_span arg = Time_span_.fracs_(2); + Time_span expd = Time_span_.fracs_(1); + Time_span actl = val.Subtract(arg); + Tfds.Eq(expd, actl); + } + @Test public void Add_unit_identity() { + tst_AddUnit("00:00:01.000", 0, 0, "00:00:01.000"); + } + @Test public void Add_unit_basic() { + tst_AddUnit("01:59:58.987", 0, 1013, "02:00:00.000"); + tst_AddUnit("01:59:58.987", 1, 2, "02:00:00.987"); + tst_AddUnit("01:59:58.987", 2, 1, "02:00:58.987"); + tst_AddUnit("01:59:58.987", 3, 1, "02:59:58.987"); + } + @Test public void Add_unit_negative() { + tst_AddUnit("01:00:00.00", 0, -1, "00:59:59.999"); + tst_AddUnit("01:00:00.00", 1, -1, "00:59:59.000"); + tst_AddUnit("01:00:00.00", 2, -1, "00:59:00.000"); + tst_AddUnit("01:00:00.00", 3, -1, "00:00:00.000"); + } + @Test public void XtoStrUiAbbrv() { + tst_XtoStrUiAbbrv("01:02:03.004", "1h 2m 3s 4f"); + tst_XtoStrUiAbbrv("00:00:03.004", "3s 4f"); + tst_XtoStrUiAbbrv("00:00:03.000", "3s 0f"); + tst_XtoStrUiAbbrv("11:22:33.444", "11h 22m 33s 444f"); + tst_XtoStrUiAbbrv("00:00:00.000", "0f"); + } void tst_XtoStrUiAbbrv(String raw, String expd) {Tfds.Eq(expd, Time_span_.parse(raw).XtoStrUiAbbrv());} + void tst_AddUnit(String valRaw, int unitIdx, int delta, String expdRaw) { + Time_span val = Time_span_.parse(valRaw); + Time_span actl = val.Add_unit(unitIdx, delta); + Tfds.Eq(Time_span_.parse(expdRaw), actl); + } + void tst_Units(String text, int... expd) { + Time_span val = Time_span_.parse(text); + int hour = 0, min = 0, sec = 0, frac = 0; + int[] ary = val.Units(); + hour = ary[Time_span_.Idx_Hour]; min = ary[Time_span_.Idx_Min]; sec = ary[Time_span_.Idx_Sec]; frac = ary[Time_span_.Idx_Frac]; + Tfds.Eq(expd[0], hour); + Tfds.Eq(expd[1], min); + Tfds.Eq(expd[2], sec); + Tfds.Eq(expd[3], frac); + } +} diff --git a/100_core/src/gplx/core/times/Time_span__parse_tst.java b/100_core/src/gplx/core/times/Time_span__parse_tst.java index a27517de8..8d997d849 100644 --- a/100_core/src/gplx/core/times/Time_span__parse_tst.java +++ b/100_core/src/gplx/core/times/Time_span__parse_tst.java @@ -13,3 +13,40 @@ 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.core.times; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Time_span__parse_tst { + @Test public void Zero() { + tst_Parse("0", 0); + } + @Test public void Milliseconds() { + tst_Parse("0.987", 987); + tst_Parse("0.00199", 1); // do not parse as 199 + tst_Parse("0.1", 100); // do not parse as 1 + } + @Test public void Seconds() { + tst_Parse("1.987", 1987); + } + @Test public void Minutes() { + tst_Parse("1:02.987", 62987); + } + @Test public void MinuteSecondOnly() { + tst_Parse("1:02", 62000); + } + @Test public void Hour() { + tst_Parse("1:02:03.987", 3723987); + } + @Test public void Negative() { + tst_Parse("-1:02:03.987", -3723987); + } + @Test public void Loopholes() { + tst_Parse("001:02", 62000); // multiple leading zeroes + tst_Parse("1.2.3.4", 1200); // ignore all decimals except first + tst_Parse("60:60.9999", 3660999); // value does not need to be bounded to limits (except fracs, which is always < 1000) + tst_Parse(" 01 : 02 : 03 . 987", 3723987); // whitespace + } + void tst_Parse(String text, long expd) { + Time_span val = Time_span_.parse(text); + Tfds.Eq(expd, val.Fracs()); + } +} diff --git a/100_core/src/gplx/core/times/Time_span__to_str_tst.java b/100_core/src/gplx/core/times/Time_span__to_str_tst.java index a27517de8..dc265f2c4 100644 --- a/100_core/src/gplx/core/times/Time_span__to_str_tst.java +++ b/100_core/src/gplx/core/times/Time_span__to_str_tst.java @@ -13,3 +13,45 @@ 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.core.times; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Time_span__to_str_tst { + @Test public void Zero() { + tst_Default(0, "0"); + } + @Test public void MinuteSeconds() { + tst_Default(77000, "1:17"); + } + @Test public void ZeroSuppression() { + tst_Default(660000, "11:00"); //fractional 0 and leading 0s are suppressed; i.e.: not 00:11:00.000 + } + @Test public void HourTest() { + tst_Default(3723987, "1:02:03.987"); + } + @Test public void NegSeconds() { + tst_Default(-2000, "-2"); + } + @Test public void NegMins() { + tst_Default(-60000, "-1:00"); + } + @Test public void NegHours() { + tst_Default(-3723981, "-1:02:03.981"); + } + @Test public void ZeroPadding() { + tst_ZeroPadding("0", "00:00:00.000"); + tst_ZeroPadding("1:02:03.123", "01:02:03.123"); + tst_ZeroPadding("1", "00:00:01.000"); + tst_ZeroPadding(".987", "00:00:00.987"); + tst_ZeroPadding("2:01.456", "00:02:01.456"); + } + void tst_Default(long fractionals, String expd) { + Time_span ts = Time_span_.fracs_(fractionals); + String actl = ts.To_str(Time_span_.Fmt_Default); + Tfds.Eq(expd, actl); + } + void tst_ZeroPadding(String val, String expd) { + Time_span timeSpan = Time_span_.parse(val); + String actl = timeSpan.To_str(Time_span_.Fmt_PadZeros); + Tfds.Eq(expd, actl); + } +} diff --git a/100_core/src/gplx/core/type_xtns/BoolClassXtn.java b/100_core/src/gplx/core/type_xtns/BoolClassXtn.java index a27517de8..955044f10 100644 --- a/100_core/src/gplx/core/type_xtns/BoolClassXtn.java +++ b/100_core/src/gplx/core/type_xtns/BoolClassXtn.java @@ -13,3 +13,27 @@ 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.core.type_xtns; import gplx.*; import gplx.core.*; +public class BoolClassXtn extends ClassXtn_base implements ClassXtn { + public static final String Key_const = "bo" + "ol"; + public String Key() {return Key_const;} + @Override public Class UnderClass() {return boolean.class;} + public Object DefaultValue() {return false;} + public boolean Eq(Object lhs, Object rhs) {try {return Bool_.Cast(lhs) == Bool_.Cast(rhs);} catch (Exception e) {Err_.Noop(e); return false;}} + @Override public Object ParseOrNull(String raw) { + if ( String_.Eq(raw, "true") + || String_.Eq(raw, "True") // needed for Store_Wtr() {boolVal.toString();} + || String_.Eq(raw, "1") // needed for db; gplx field for boolean is int; need simple way to convert from dbInt to langBool + ) + return true; + else if + ( String_.Eq(raw, "false") + || String_.Eq(raw, "False") + || String_.Eq(raw, "0") + ) + return false; + throw Err_.new_parse_type(boolean.class, raw); + } + @Override public Object XtoDb(Object obj) {return obj;} + public static final BoolClassXtn Instance = new BoolClassXtn(); BoolClassXtn() {} // added to ClassXtnPool by default +} \ No newline at end of file diff --git a/100_core/src/gplx/core/type_xtns/ByteClassXtn.java b/100_core/src/gplx/core/type_xtns/ByteClassXtn.java index a27517de8..eb933c26b 100644 --- a/100_core/src/gplx/core/type_xtns/ByteClassXtn.java +++ b/100_core/src/gplx/core/type_xtns/ByteClassXtn.java @@ -13,3 +13,14 @@ 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.core.type_xtns; import gplx.*; import gplx.core.*; +public class ByteClassXtn extends ClassXtn_base implements ClassXtn { + public static final String Key_const = "byte"; + public String Key() {return Key_const;} + @Override public Class UnderClass() {return byte.class;} + public Object DefaultValue() {return 0;} + public boolean Eq(Object lhs, Object rhs) {try {return Byte_.Cast(lhs) == Byte_.Cast(rhs);} catch (Exception e) {Err_.Noop(e); return false;}} + @Override public Object ParseOrNull(String raw) {return raw == null ? (Object)null : Byte_.Parse(raw);} + @Override public Object XtoDb(Object obj) {return Byte_.Cast(obj);} + public static final ByteClassXtn Instance = new ByteClassXtn(); ByteClassXtn() {} // added to ClassXtnPool by default +} \ No newline at end of file diff --git a/100_core/src/gplx/core/type_xtns/ClassXtn.java b/100_core/src/gplx/core/type_xtns/ClassXtn.java index a27517de8..df1471a54 100644 --- a/100_core/src/gplx/core/type_xtns/ClassXtn.java +++ b/100_core/src/gplx/core/type_xtns/ClassXtn.java @@ -13,3 +13,15 @@ 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.core.type_xtns; import gplx.*; import gplx.core.*; +public interface ClassXtn { + String Key(); + Class UnderClass(); + Object DefaultValue(); + Object ParseOrNull(String raw); + Object XtoDb(Object obj); + String XtoUi(Object obj, String fmt); + boolean MatchesClass(Object obj); + boolean Eq(Object lhs, Object rhs); + int compareTo(Object lhs, Object rhs); +} diff --git a/100_core/src/gplx/core/type_xtns/ClassXtnPool.java b/100_core/src/gplx/core/type_xtns/ClassXtnPool.java index a27517de8..b6ce5dce2 100644 --- a/100_core/src/gplx/core/type_xtns/ClassXtnPool.java +++ b/100_core/src/gplx/core/type_xtns/ClassXtnPool.java @@ -13,3 +13,25 @@ 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.core.type_xtns; import gplx.*; import gplx.core.*; +import gplx.core.lists.*; +public class ClassXtnPool extends Hash_adp_base { + public void Add(ClassXtn typx) {Add_base(typx.Key(), typx);} + public ClassXtn Get_by_or_fail(String key) {return (ClassXtn)Get_by_or_fail_base(key);} + + public static final ClassXtnPool Instance = new ClassXtnPool(); + public static final String Format_null = ""; + public static ClassXtnPool new_() {return new ClassXtnPool();} + ClassXtnPool() { + Add(ObjectClassXtn.Instance); + Add(StringClassXtn.Instance); + Add(IntClassXtn.Instance); + Add(BoolClassXtn.Instance); + Add(ByteClassXtn.Instance); + Add(DateAdpClassXtn.Instance); + Add(TimeSpanAdpClassXtn.Instance); + Add(IoUrlClassXtn.Instance); + Add(DecimalAdpClassXtn.Instance); + Add(FloatClassXtn.Instance); + } +} diff --git a/100_core/src/gplx/core/type_xtns/ClassXtn_base.java b/100_core/src/gplx/core/type_xtns/ClassXtn_base.java index a27517de8..0aaf79e4f 100644 --- a/100_core/src/gplx/core/type_xtns/ClassXtn_base.java +++ b/100_core/src/gplx/core/type_xtns/ClassXtn_base.java @@ -13,3 +13,14 @@ 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.core.type_xtns; import gplx.*; import gplx.core.*; +public abstract class ClassXtn_base { + public abstract Class UnderClass(); + public abstract Object ParseOrNull(String raw); + @gplx.Virtual public Object XtoDb(Object obj) {return obj;} + @gplx.Virtual public String XtoUi(Object obj, String fmt) {return Object_.Xto_str_strict_or_null_mark(obj);} + @gplx.Virtual public boolean MatchesClass(Object obj) {if (obj == null) throw Err_.new_null(); + return Type_.Eq_by_obj(obj, UnderClass()); + } + @gplx.Virtual public int compareTo(Object lhs, Object rhs) {return CompareAble_.Compare_obj(lhs, rhs);} +} diff --git a/100_core/src/gplx/core/type_xtns/DateAdpClassXtn.java b/100_core/src/gplx/core/type_xtns/DateAdpClassXtn.java index a27517de8..8c30ac5f5 100644 --- a/100_core/src/gplx/core/type_xtns/DateAdpClassXtn.java +++ b/100_core/src/gplx/core/type_xtns/DateAdpClassXtn.java @@ -13,3 +13,14 @@ 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.core.type_xtns; import gplx.*; import gplx.core.*; +public class DateAdpClassXtn extends ClassXtn_base implements ClassXtn { + public String Key() {return Key_const;} public static final String Key_const = "datetime"; + public boolean Eq(Object lhs, Object rhs) {try {return DateAdp_.cast(lhs).Eq(DateAdp_.cast(rhs));} catch (Exception e) {Err_.Noop(e); return false;}} + @Override public Class UnderClass() {return DateAdp.class;} + public Object DefaultValue() {return DateAdp_.MinValue;} + @Override public Object ParseOrNull(String raw) {return DateAdp_.parse_gplx(raw);} + @Override public Object XtoDb(Object obj) {return DateAdp_.cast(obj).XtoStr_gplx_long();} + @Override public String XtoUi(Object obj, String fmt) {return DateAdp_.cast(obj).XtoStr_fmt(fmt);} + public static final DateAdpClassXtn Instance = new DateAdpClassXtn(); DateAdpClassXtn() {} // added to ClassXtnPool by default +} diff --git a/100_core/src/gplx/core/type_xtns/DateAdpClassXtn_tst.java b/100_core/src/gplx/core/type_xtns/DateAdpClassXtn_tst.java index a27517de8..5760066ee 100644 --- a/100_core/src/gplx/core/type_xtns/DateAdpClassXtn_tst.java +++ b/100_core/src/gplx/core/type_xtns/DateAdpClassXtn_tst.java @@ -13,3 +13,14 @@ 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.core.type_xtns; import gplx.*; import gplx.core.*; +import org.junit.*; +public class DateAdpClassXtn_tst { + @Test public void XtoDb() { + tst_XtoDb("20091115 220102.999", "2009-11-15 22:01:02.999"); + } + void tst_XtoDb(String val, String expdRaw) { + String actlRaw = (String)DateAdpClassXtn.Instance.XtoDb(DateAdp_.parse_gplx(val)); + Tfds.Eq(expdRaw, actlRaw); + } +} diff --git a/100_core/src/gplx/core/type_xtns/DecimalAdpClassXtn.java b/100_core/src/gplx/core/type_xtns/DecimalAdpClassXtn.java index a27517de8..9013b978d 100644 --- a/100_core/src/gplx/core/type_xtns/DecimalAdpClassXtn.java +++ b/100_core/src/gplx/core/type_xtns/DecimalAdpClassXtn.java @@ -13,3 +13,13 @@ 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.core.type_xtns; import gplx.*; import gplx.core.*; +public class DecimalAdpClassXtn extends ClassXtn_base implements ClassXtn { + public String Key() {return Key_const;} public static final String Key_const = "decimal"; // current dsv files reference "decimal" + @Override public Class UnderClass() {return Decimal_adp.class;} + public Object DefaultValue() {return 0;} + public boolean Eq(Object lhs, Object rhs) {try {return Decimal_adp_.cast(lhs).Eq(Decimal_adp_.cast(rhs));} catch (Exception e) {Err_.Noop(e); return false;}} + @Override public Object ParseOrNull(String raw) {return Decimal_adp_.parse(raw);} + @Override public String XtoUi(Object obj, String fmt) {return Decimal_adp_.cast(obj).To_str();} + public static final DecimalAdpClassXtn Instance = new DecimalAdpClassXtn(); DecimalAdpClassXtn() {} // added to ClassXtnPool by default +} diff --git a/100_core/src/gplx/core/type_xtns/DoubleClassXtn.java b/100_core/src/gplx/core/type_xtns/DoubleClassXtn.java index a27517de8..062ee43fd 100644 --- a/100_core/src/gplx/core/type_xtns/DoubleClassXtn.java +++ b/100_core/src/gplx/core/type_xtns/DoubleClassXtn.java @@ -13,3 +13,12 @@ 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.core.type_xtns; import gplx.*; import gplx.core.*; +public class DoubleClassXtn extends ClassXtn_base implements ClassXtn { + public String Key() {return Key_const;} public static final String Key_const = "double"; + @Override public Class UnderClass() {return double.class;} + public Object DefaultValue() {return 0;} + public boolean Eq(Object lhs, Object rhs) {try {return Double_.cast(lhs) == Double_.cast(rhs);} catch (Exception e) {Err_.Noop(e); return false;}} + @Override public Object ParseOrNull(String raw) {return Double_.parse(raw);} + public static final DoubleClassXtn Instance = new DoubleClassXtn(); DoubleClassXtn() {} // added to ClassXtnPool by default +} \ No newline at end of file diff --git a/100_core/src/gplx/core/type_xtns/FloatClassXtn.java b/100_core/src/gplx/core/type_xtns/FloatClassXtn.java index a27517de8..ebcf95bce 100644 --- a/100_core/src/gplx/core/type_xtns/FloatClassXtn.java +++ b/100_core/src/gplx/core/type_xtns/FloatClassXtn.java @@ -13,3 +13,12 @@ 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.core.type_xtns; import gplx.*; import gplx.core.*; +public class FloatClassXtn extends ClassXtn_base implements ClassXtn { + public String Key() {return Key_const;} public static final String Key_const = "float"; + @Override public Class UnderClass() {return float.class;} + public Object DefaultValue() {return 0;} + public boolean Eq(Object lhs, Object rhs) {try {return Float_.cast(lhs) == Float_.cast(rhs);} catch (Exception e) {Err_.Noop(e); return false;}} + @Override public Object ParseOrNull(String raw) {return Float_.parse(raw);} + public static final FloatClassXtn Instance = new FloatClassXtn(); FloatClassXtn() {} // added to ClassXtnPool by default +} \ No newline at end of file diff --git a/100_core/src/gplx/core/type_xtns/IntClassXtn.java b/100_core/src/gplx/core/type_xtns/IntClassXtn.java index a27517de8..ecc007ba2 100644 --- a/100_core/src/gplx/core/type_xtns/IntClassXtn.java +++ b/100_core/src/gplx/core/type_xtns/IntClassXtn.java @@ -13,3 +13,14 @@ 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.core.type_xtns; import gplx.*; import gplx.core.*; +public class IntClassXtn extends ClassXtn_base implements ClassXtn { + public String Key() {return Key_const;} public static final String Key_const = "int"; + @Override public Class UnderClass() {return Integer.class;} + public Object DefaultValue() {return 0;} + @Override public Object ParseOrNull(String raw) {return raw == null ? (Object)null : Int_.Parse(raw);} + public boolean Eq(Object lhs, Object rhs) {try {return Int_.Cast(lhs) == Int_.Cast(rhs);} catch (Exception e) {Err_.Noop(e); return false;}} + @Override public Object XtoDb(Object obj) {return Int_.Cast(obj);} // necessary for enums + + public static final IntClassXtn Instance = new IntClassXtn(); IntClassXtn() {} // added to ClassXtnPool by default +} diff --git a/100_core/src/gplx/core/type_xtns/IoUrlClassXtn.java b/100_core/src/gplx/core/type_xtns/IoUrlClassXtn.java index a27517de8..57f0b75ea 100644 --- a/100_core/src/gplx/core/type_xtns/IoUrlClassXtn.java +++ b/100_core/src/gplx/core/type_xtns/IoUrlClassXtn.java @@ -13,3 +13,15 @@ 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.core.type_xtns; import gplx.*; import gplx.core.*; +public class IoUrlClassXtn extends ClassXtn_base implements ClassXtn { + public String Key() {return Key_const;} public static final String Key_const = "ioPath"; + @Override public Class UnderClass() {return Io_url.class;} + public Object DefaultValue() {return Io_url_.Empty;} + @Override public Object ParseOrNull(String raw) {return Io_url_.new_any_(raw);} + @Override public Object XtoDb(Object obj) {return Io_url_.cast(obj).Raw();} + @Override public String XtoUi(Object obj, String fmt) {return Io_url_.cast(obj).Raw();} + @Override public boolean MatchesClass(Object obj) {return Io_url_.as_(obj) != null;} + public boolean Eq(Object lhs, Object rhs) {try {return Io_url_.cast(lhs).Eq(Io_url_.cast(rhs));} catch (Exception e) {Err_.Noop(e); return false;}} + public static final IoUrlClassXtn Instance = new IoUrlClassXtn(); IoUrlClassXtn() {} // added to ClassXtnPool by default +} diff --git a/100_core/src/gplx/core/type_xtns/LongClassXtn.java b/100_core/src/gplx/core/type_xtns/LongClassXtn.java index a27517de8..d96076f4d 100644 --- a/100_core/src/gplx/core/type_xtns/LongClassXtn.java +++ b/100_core/src/gplx/core/type_xtns/LongClassXtn.java @@ -13,3 +13,13 @@ 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.core.type_xtns; import gplx.*; import gplx.core.*; +public class LongClassXtn extends ClassXtn_base implements ClassXtn { + public String Key() {return Key_const;} public static final String Key_const = "long"; + @Override public Class UnderClass() {return long.class;} + public Object DefaultValue() {return 0;} + public boolean Eq(Object lhs, Object rhs) {try {return Long_.cast(lhs) == Long_.cast(rhs);} catch (Exception e) {Err_.Noop(e); return false;}} + @Override public Object ParseOrNull(String raw) {return raw == null ? (Object)null : Long_.parse(raw);} + @Override public Object XtoDb(Object obj) {return Long_.cast(obj);} // necessary for enums + public static final LongClassXtn Instance = new LongClassXtn(); LongClassXtn() {} // added to ClassXtnPool by default +} \ No newline at end of file diff --git a/100_core/src/gplx/core/type_xtns/ObjectClassXtn.java b/100_core/src/gplx/core/type_xtns/ObjectClassXtn.java index a27517de8..db7f9f1c5 100644 --- a/100_core/src/gplx/core/type_xtns/ObjectClassXtn.java +++ b/100_core/src/gplx/core/type_xtns/ObjectClassXtn.java @@ -13,3 +13,13 @@ 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.core.type_xtns; import gplx.*; import gplx.core.*; +public class ObjectClassXtn extends ClassXtn_base implements ClassXtn { + public String Key() {return Key_const;} public static final String Key_const = "Object"; + @Override public Class UnderClass() {return Object.class;} + public Object DefaultValue() {return null;} + @Override public Object ParseOrNull(String raw) {throw Err_.new_unimplemented();} + @Override public Object XtoDb(Object obj) {throw Err_.new_unimplemented();} + public boolean Eq(Object lhs, Object rhs) {return lhs == rhs;} + public static final ObjectClassXtn Instance = new ObjectClassXtn(); ObjectClassXtn() {} // added to ClassXtnPool by default +} diff --git a/100_core/src/gplx/core/type_xtns/StringClassXtn.java b/100_core/src/gplx/core/type_xtns/StringClassXtn.java index a27517de8..780ba4fc6 100644 --- a/100_core/src/gplx/core/type_xtns/StringClassXtn.java +++ b/100_core/src/gplx/core/type_xtns/StringClassXtn.java @@ -13,3 +13,14 @@ 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.core.type_xtns; import gplx.*; import gplx.core.*; +public class StringClassXtn extends ClassXtn_base implements ClassXtn { + public static final String Key_const = "string"; + public String Key() {return Key_const;} + @Override public Class UnderClass() {return String.class;} + public Object DefaultValue() {return "";} + @Override public Object ParseOrNull(String raw) {return raw;} + @Override public String XtoUi(Object obj, String fmt) {return String_.as_(obj);} + public boolean Eq(Object lhs, Object rhs) {try {return String_.Eq(String_.cast(lhs), String_.cast(rhs));} catch (Exception e) {Err_.Noop(e); return false;}} + public static final StringClassXtn Instance = new StringClassXtn(); StringClassXtn() {} // added to ClassXtnPool by default +} \ No newline at end of file diff --git a/100_core/src/gplx/core/type_xtns/TimeSpanAdpClassXtn.java b/100_core/src/gplx/core/type_xtns/TimeSpanAdpClassXtn.java index a27517de8..4b7e6e9b7 100644 --- a/100_core/src/gplx/core/type_xtns/TimeSpanAdpClassXtn.java +++ b/100_core/src/gplx/core/type_xtns/TimeSpanAdpClassXtn.java @@ -13,3 +13,14 @@ 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.core.type_xtns; import gplx.*; import gplx.core.*; +public class TimeSpanAdpClassXtn extends ClassXtn_base implements ClassXtn { + public String Key() {return Key_const;} public static final String Key_const = "timeSpan"; + @Override public Class UnderClass() {return Time_span.class;} + public Object DefaultValue() {return Time_span_.Zero;} + @Override public Object ParseOrNull(String raw) {return Time_span_.parse(raw);} + @Override public Object XtoDb(Object obj) {return Time_span_.cast(obj).Total_secs();} + @Override public String XtoUi(Object obj, String fmt) {return Time_span_.cast(obj).To_str(fmt);} + public boolean Eq(Object lhs, Object rhs) {try {return Time_span_.cast(lhs).Eq(rhs);} catch (Exception e) {Err_.Noop(e); return false;}} + public static final TimeSpanAdpClassXtn Instance = new TimeSpanAdpClassXtn(); TimeSpanAdpClassXtn() {} // added to ClassXtnPool by default +} \ No newline at end of file diff --git a/100_core/src/gplx/langs/dsvs/DsvDataRdrOpts.java b/100_core/src/gplx/langs/dsvs/DsvDataRdrOpts.java index a27517de8..e08f4a99d 100644 --- a/100_core/src/gplx/langs/dsvs/DsvDataRdrOpts.java +++ b/100_core/src/gplx/langs/dsvs/DsvDataRdrOpts.java @@ -13,3 +13,12 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +import gplx.core.gfo_ndes.*; +public class DsvDataRdrOpts { + public boolean HasHeader() {return hasHeader;} public DsvDataRdrOpts HasHeader_(boolean val) {hasHeader = val; return this;} private boolean hasHeader = false; + public String NewLineSep() {return newLineSep;} public DsvDataRdrOpts NewLineSep_(String val) {newLineSep = val; return this;} private String newLineSep = String_.CrLf; + public String FldSep() {return fldSep;} public DsvDataRdrOpts FldSep_(String val) {fldSep = val; return this;} private String fldSep = ","; + public GfoFldList Flds() {return flds;} public DsvDataRdrOpts Flds_(GfoFldList val) {flds = val; return this;} GfoFldList flds = GfoFldList_.Null; + public static DsvDataRdrOpts new_() {return new DsvDataRdrOpts();} DsvDataRdrOpts() {} +} diff --git a/100_core/src/gplx/langs/dsvs/DsvDataRdr_.java b/100_core/src/gplx/langs/dsvs/DsvDataRdr_.java index a27517de8..fb135f222 100644 --- a/100_core/src/gplx/langs/dsvs/DsvDataRdr_.java +++ b/100_core/src/gplx/langs/dsvs/DsvDataRdr_.java @@ -13,3 +13,234 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +import gplx.core.strings.*; import gplx.core.gfo_ndes.*; import gplx.core.stores.*; import gplx.core.type_xtns.*; +import gplx.core.texts.*; /*CharStream*/ +public class DsvDataRdr_ { + public static DataRdr dsv_(String text) {return DsvParser.dsv_().ParseAsRdr(text);} + public static DataRdr csv_hdr_(String text) {return csv_opts_(text, DsvDataRdrOpts.new_().HasHeader_(true));} + public static DataRdr csv_dat_(String text) {return csv_opts_(text, DsvDataRdrOpts.new_());} + public static DataRdr csv_dat_with_flds_(String text, String... flds) {return csv_opts_(text, DsvDataRdrOpts.new_().Flds_(GfoFldList_.str_(flds)));} + public static DataRdr csv_opts_(String text, DsvDataRdrOpts opts) { + DsvParser parser = DsvParser.csv_(opts.HasHeader(), opts.Flds()); // NOTE: this sets up the bldr; do not call .Init after this + parser.Symbols().RowSep_(opts.NewLineSep()).FldSep_(opts.FldSep()); + + DataRdr root = parser.ParseAsRdr(text); // don't return root; callers expect csv to return rdr for rows + DataRdr csvTable = root.Subs(); + return csvTable.Subs(); + } +} +class DsvParser { + @gplx.Internal protected DsvSymbols Symbols() {return sym;} DsvSymbols sym = DsvSymbols.default_(); + @gplx.Internal protected void Init() {sb.Clear(); bldr.Init(); qteOn = false;} + @gplx.Internal protected DataRdr ParseAsRdr(String raw) {return GfoNdeRdr_.root_(ParseAsNde(raw), csvOn);} // NOTE: csvOn means parse; assume manual typed flds not passed in (ex: id,Int) + @gplx.Internal protected GfoNde ParseAsNde(String raw) { + if (String_.Eq(raw, "")) return bldr.BldRoot(); + CharStream strm = CharStream.pos0_(raw); + while (true) { + if (strm.AtEnd()) { + ProcessLine(strm, true); + break; + } + if (qteOn) + ReadStreamInQte(strm); + else if (strm.MatchAndMove(sym.QteDlm())) + qteOn = true; + else if (strm.MatchAndMove(sym.FldSep())) + ProcessFld(strm); + else if (strm.MatchAndMove(sym.RowSep())) + ProcessLine(strm, false); + else { + sb.Add(strm.Cur()); + strm.MoveNext(); + } + } + return bldr.BldRoot(); + } + void ReadStreamInQte(CharStream strm) { + if (strm.MatchAndMove(sym.QteDlm())) { // is quote + if (strm.MatchAndMove(sym.QteDlm())) // double quote -> quote; "a"" + sb.Add(sym.QteDlm()); + else if (strm.MatchAndMove(sym.FldSep())) { // closing quote before field; "a", + ProcessFld(strm); + qteOn = false; + } + else if (strm.MatchAndMove(sym.RowSep()) || strm.AtEnd()) { // closing quote before record; "a"\r\n + ProcessLine(strm, false); + qteOn = false; + } + else + throw Err_.new_wo_type("invalid quote in quoted field; quote must be followed by quote, fieldSpr, or recordSpr", "sym", strm.Cur(), "text", strm.To_str_by_pos(strm.Pos() - 10, strm.Pos() + 10)); + } + else { // regular char; append and continue + sb.Add(strm.Cur()); + strm.MoveNext(); + } + } + void ProcessFld(CharStream strm) { + String val = sb.To_str_and_clear(); + if (cmdSeqOn) { + cmdSeqOn = false; + if (String_.Eq(val, sym.CmdDlm()) && qteOn) { // 2 cmdDlms in a row; cmdSeq encountered; next fld must be cmdName + nextValType = ValType_CmdName; + return; + } + tkns.Add(sym.CmdDlm()); // curTkn is not cmdDlm; prevTkn happened to be cmdDlm; add prev to tkns and continue; ex: a, ,b + } + if (String_.Eq(val, sym.CmdDlm())) // val is cmdDlm; do not add now; wait til next fld to decide + cmdSeqOn = true; + else if (nextValType == ValType_Data) { + if (String_.Len(val) == 0) val = qteOn ? "" : null; // differentiate between null and emptyString; ,, vs ,"", + tkns.Add(val); + } + else if (nextValType == ValType_CmdName) { + if (String_.Eq(val, sym.TblNameSym())) lineMode = LineType_TblBgn; // # + else if (String_.Eq(val, sym.FldNamesSym())) lineMode = LineType_FldNames; // @ + else if (String_.Eq(val, sym.FldTypesSym())) lineMode = LineType_FldTypes; // $ + else if (String_.Eq(val, sym.CommentSym())) lineMode = LineType_Comment; // ' + else throw Err_.new_wo_type("unknown dsv cmd", "cmd", val); + } + else + throw Err_.new_wo_type("unable to process field value", "value", val); + } + void ProcessLine(CharStream strm, boolean cleanup) { + if (sb.Count() == 0 && tkns.Count() == 0) + if (csvOn) { // csvOn b/c csvMode allows blank lines as empty data + if (cleanup) // cleanup b/c blankLine should not be added when called by cleanup, else will always add extra row at end + return; // cleanup, so no further action needed; return; + else + ProcessFld(strm); + } + else + lineMode = LineType_BlankLine; + else + ProcessFld(strm); // always process fld; either (1) chars waiting in sb "a,b"; or (2) last char was fldSep "a," + if (cmdSeqOn) { // only happens if last fld is comma space (, ); do not let cmds span lines + cmdSeqOn = false; + tkns.Add(sym.CmdDlm()); + } + if (lineMode == LineType_TblBgn) bldr.MakeTblBgn(tkns); + else if (lineMode == LineType_FldNames) bldr.MakeFldNames(tkns); + else if (lineMode == LineType_FldTypes) bldr.MakeFldTypes(tkns); + else if (lineMode == LineType_Comment) bldr.MakeComment(tkns); + else if (lineMode == LineType_BlankLine) bldr.MakeBlankLine(); + else bldr.MakeVals(tkns); + nextValType = ValType_Data; + lineMode = LineType_Data; + } + String_bldr sb = String_bldr_.new_(); List_adp tkns = List_adp_.New(); DsvTblBldr bldr = DsvTblBldr.new_(); + boolean cmdSeqOn = false, qteOn = false, csvOn = false; + int nextValType = ValType_Data, lineMode = LineType_Data; + @gplx.Internal protected static DsvParser dsv_() {return new DsvParser();} + @gplx.Internal protected static DsvParser csv_(boolean hasHdr, GfoFldList flds) { + DsvParser rv = new DsvParser(); + rv.csvOn = true; + rv.lineMode = hasHdr ? LineType_FldNames : LineType_Data; + List_adp names = List_adp_.New(), types = List_adp_.New(); + for (int i = 0; i < flds.Count(); i++) { + GfoFld fld = flds.Get_at(i); + names.Add(fld.Key()); types.Add(fld.Type().Key()); + } + rv.bldr.MakeFldNames(names); rv.bldr.MakeFldTypes(types); + return rv; + } + static final int ValType_Data = 0, ValType_CmdName = 1; + static final int LineType_Data = 0, LineType_Comment = 1, LineType_TblBgn = 2, LineType_FldNames = 3, LineType_FldTypes = 4, LineType_BlankLine = 5; +} +class DsvTblBldr { + public void Init() { + root = GfoNde_.root_(); tbl = GfoNde_.tbl_(NullTblName, GfoFldList_.new_()); + fldNames.Clear(); fldTypes.Clear(); + stage = Stage_Init; + } + public GfoNde BldRoot() { + if (stage != Stage_Init) CreateTbl(); // CreateTbl if HDR or ROW is in progress + return root; + } + public void MakeTblBgn(List_adp tkns) { + if (stage != Stage_Init) CreateTbl(); // CreateTbl if HDR or ROW is in progress + tbl.Name_((String)tkns.Get_at(0)); + layout.HeaderList().Add_TableName(); + stage = Stage_Hdr; tkns.Clear(); + } + public void MakeFldNames(List_adp tkns) { + if (stage == Stage_Row) CreateTbl(); // CreateTbl if ROW is in progress; NOTE: exclude HDR, as first HDR would have called CreateTbl + fldNames.Clear(); + for (Object fldNameObj : tkns) + fldNames.Add(fldNameObj); + layout.HeaderList().Add_LeafNames(); + stage = Stage_Hdr; tkns.Clear(); + } + public void MakeFldTypes(List_adp tkns) { + if (stage == Stage_Row) CreateTbl(); // CreateTbl if ROW is in progress; NOTE: exclude HDR, as first HDR would have called CreateTbl + fldTypes.Clear(); + for (Object fldTypeObj : tkns) { + ClassXtn type = ClassXtnPool.Instance.Get_by_or_fail((String)fldTypeObj); + fldTypes.Add(type); + } + layout.HeaderList().Add_LeafTypes(); + stage = Stage_Hdr; tkns.Clear(); + } + public void MakeComment(List_adp tkns) { + if (stage == Stage_Row) // comments in ROW; ignore; NOTE: tkns.Clear() could be merged, but this seems clearer + tkns.Clear(); + else { // comments in HDR + String_bldr sb = String_bldr_.new_(); + for (int i = 0; i < tkns.Count(); i++) + sb.Add((String)tkns.Get_at(i)); + layout.HeaderList().Add_Comment(sb.To_str()); + tkns.Clear(); + } + } + public void MakeBlankLine() { + if (stage != Stage_Init) CreateTbl(); // CreateTbl if HDR or ROW is in progress; + layout.HeaderList().Add_BlankLine(); + stage = Stage_Init; // NOTE: mark stage as INIT; + } + public void MakeVals(List_adp tkns) { + if (stage != Stage_Row) CreateFlds(tkns.Count()); // stage != Stage_Row means if (noRowsCreated) + GfoNde row = GfoNde_.vals_(tbl.SubFlds(), MakeValsAry(tkns)); + tbl.Subs().Add(row); + stage = Stage_Row; tkns.Clear(); + } + Object[] MakeValsAry(List_adp tkns) { + GfoFldList subFlds = tbl.SubFlds(); int subFldsCount = subFlds.Count(); + if (tkns.Count() > subFldsCount) throw Err_.new_wo_type("values.Count cannot be greater than fields.Count", "values.Count", tkns.Count(), "fields.Count", subFldsCount); + Object[] rv = new Object[subFldsCount]; + for (int i = 0; i < subFldsCount; i++) { + ClassXtn typx = subFlds.Get_at(i).Type(); + String val = i < tkns.Count() ? (String)tkns.Get_at(i) : null; + rv[i] = typx.ParseOrNull(val); + } + return rv; + } + void CreateTbl() { + if (tbl.SubFlds().Count() == 0) CreateFlds(0); // this check occurs when tbl has no ROW; (TOMB: tdb test fails) + tbl.EnvVars().Add(DsvStoreLayout.Key_const, layout); + root.Subs().Add(tbl); // add pending table + layout = DsvStoreLayout.dsv_brief_(); + tbl = GfoNde_.tbl_(NullTblName, GfoFldList_.new_()); + stage = Stage_Hdr; + } + void CreateFlds(int valCount) { + int fldNamesCount = fldNames.Count(), fldTypesCount = fldTypes.Count(); + if (fldNamesCount == 0 && fldTypesCount == 0) { // csv tbls where no names or types, just values + for (int i = 0; i < valCount; i++) + tbl.SubFlds().Add("fld" + i, StringClassXtn.Instance); + } + else { // all else, where either names or types is defined + int maxCount = fldNamesCount > fldTypesCount ? fldNamesCount : fldTypesCount; + for (int i = 0; i < maxCount; i++) { + String name = i < fldNamesCount ? (String)fldNames.Get_at(i) : "fld" + i; + ClassXtn typx = i < fldTypesCount ? (ClassXtn)fldTypes.Get_at(i) : StringClassXtn.Instance; + tbl.SubFlds().Add(name, typx); + } + } + } + GfoNde root; GfoNde tbl; DsvStoreLayout layout = DsvStoreLayout.dsv_brief_(); + List_adp fldNames = List_adp_.New(); List_adp fldTypes = List_adp_.New(); + int stage = Stage_Init; + public static DsvTblBldr new_() {return new DsvTblBldr();} DsvTblBldr() {this.Init();} + @gplx.Internal protected static final String NullTblName = ""; + static final int Stage_Init = 0, Stage_Hdr = 1, Stage_Row = 2; +} diff --git a/100_core/src/gplx/langs/dsvs/DsvDataRdr_csv_dat_tst.java b/100_core/src/gplx/langs/dsvs/DsvDataRdr_csv_dat_tst.java index a27517de8..e04413cec 100644 --- a/100_core/src/gplx/langs/dsvs/DsvDataRdr_csv_dat_tst.java +++ b/100_core/src/gplx/langs/dsvs/DsvDataRdr_csv_dat_tst.java @@ -13,3 +13,202 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +import org.junit.*; import gplx.core.strings.*; import gplx.core.gfo_ndes.*; +public class DsvDataRdr_csv_dat_tst { + @Before public void setup() { + fx.Parser_(DsvParser.csv_(false, GfoFldList_.Null)); + fx.Clear(); + } DsvDataRdr_fxt fx = DsvDataRdr_fxt.new_(); + @Test public void Empty() { + fx.run_parse_(""); + fx.tst_DatNull(); + } + @Test public void Fld_0() { + fx.run_parse_("a"); + fx.tst_DatCsv(fx.ary_("a")); + } + @Test public void Fld_N() { + fx.run_parse_("a,b,c"); + fx.tst_FldListCsv("fld0", "fld1", "fld2"); + fx.tst_DatCsv(fx.ary_("a", "b", "c")); + } + @Test public void Row_N() { + fx.run_parse_ + ( "a,b,c", String_.CrLf + , "1,2,3" + ); + fx.tst_DatCsv + ( fx.ary_("a", "b", "c") + , fx.ary_("1", "2", "3") + ); + } + @Test public void Escape_WhiteSpace() { + fx.run_parse_("a,\" \t\",c"); + fx.tst_DatCsv(fx.ary_("a", " \t", "c")); + } + @Test public void Escape_FldSep() { + fx.run_parse_("a,\",\",c"); + fx.tst_DatCsv(fx.ary_("a", ",", "c")); + } + @Test public void Escape_RowSep() { + fx.run_parse_("a,\"" + String_.CrLf + "\",c"); + fx.tst_DatCsv(fx.ary_("a", String_.CrLf, "c")); + } + @Test public void Escape_Quote() { + fx.run_parse_("a,\"\"\"\",c"); + fx.tst_DatCsv(fx.ary_("a", "\"", "c")); + } + @Test public void Blank_Null() { + fx.run_parse_("a,,c"); + fx.tst_DatCsv(fx.ary_("a", null, "c")); + } + @Test public void Blank_EmptyString() { + fx.run_parse_("a,\"\",c"); + fx.tst_DatCsv(fx.ary_("a", "", "c")); + } + @Test public void Blank_Null_Multiple() { + fx.run_parse_(",,"); + fx.tst_DatCsv(fx.ary_(null, null, null)); + } + @Test public void TrailingNull() { + fx.run_parse_("a,"); + fx.tst_DatCsv(fx.ary_("a", null)); + } + @Test public void TrailingEmpty() { + fx.run_parse_("a,\"\""); + fx.tst_DatCsv(fx.ary_("a", "")); + } + @Test public void Quote_Error() { + try { + fx.run_parse_("a,\"\" ,c"); + Tfds.Fail_expdError(); + } + catch (Err e) { + Tfds.Eq_true(String_.Has(Err_.Message_lang(e), "invalid quote in quoted field")); + } + } + @Test public void Misc_AllowValsLessThanFields() { + // assume null when vals.Count < fields.Count; PURPOSE: MsExcel will not save trailing commas for csvExport; ex: a, -> a + fx.run_parse_ + ( "a0,a1", String_.CrLf + , "b0" + ); + fx.tst_DatCsv + ( fx.ary_("a0", "a1") + , fx.ary_("b0", null) + ); + } + @Test public void Misc_NewLineValidForSingleColumnTables() { + fx.run_parse_ + ( "a", String_.CrLf + , String_.CrLf + , "c" , String_.CrLf + , String_.CrLf + ); + fx.tst_DatCsv + ( fx.ary_("a") + , fx.ary_null_() + , fx.ary_("c") + , fx.ary_null_() + ); + } + @Test public void Misc_NewLineValidForSingleColumnTables_FirstLine() { + fx.run_parse_ + ( String_.CrLf + , "b", String_.CrLf + , "c" + ); + fx.tst_DatCsv + ( fx.ary_null_() + , fx.ary_("b") + , fx.ary_("c") + ); + } + @Test public void Hdr_Basic() { + fx.Parser_(DsvParser.csv_(true, GfoFldList_.Null)); + fx.run_parse_ + ( "id,name", String_.CrLf + , "0,me" + ); + fx.tst_FldListCsv("id", "name"); + fx.tst_DatCsv(fx.ary_("0", "me")); + } +// @Test public void Hdr_Manual() { +// fx.Parser_(DsvParser.csv_(false, GfoFldList_.new_().Add("id", IntClassXtn.Instance).Add("name", StringClassXtn.Instance), true)); +// fx.run_parse_("0,me"); +// fx.tst_DatCsv(fx.ary_(0, "me")); // NOTE: testing auto-parsing of id to int b/c id fld is IntClassXtn.Instance; +// } +} +class DsvDataRdr_fxt { + public Object[] ary_(Object... ary) {return ary;} + public Object[] ary_null_() {return new Object[] {null};} + public void Clear() {parser.Init(); root = null;} + public DsvParser Parser() {return parser;} public DsvDataRdr_fxt Parser_(DsvParser val) {parser = val; return this;} DsvParser parser = DsvParser.dsv_(); + public GfoNde Root() {return root;} GfoNde root; + public void run_parse_(String... ary) {root = parser.ParseAsNde(String_.Concat(ary));} + public void run_parse_lines_(String... ary) {root = parser.ParseAsNde(String_.Concat_lines_crlf(ary));} + public DsvDataRdr_fxt tst_FldListCsv(String... names) {return tst_Flds(TblIdx0, GfoFldList_.str_(names));} + public DsvDataRdr_fxt tst_Flds(int tblIdx, GfoFldList expdFlds) { + GfoNde tbl = root.Subs().FetchAt_asGfoNde(tblIdx); + List_adp expdList = List_adp_.New(), actlList = List_adp_.New(); + String_bldr sb = String_bldr_.new_(); + GfoFldList_BldDbgList(expdFlds, expdList, sb); + GfoFldList_BldDbgList(tbl.SubFlds(), actlList, sb); + Tfds.Eq_list(expdList, actlList); + return this; + } + void GfoFldList_BldDbgList(GfoFldList flds, List_adp list, String_bldr sb) { + for (int i = 0; i < flds.Count(); i++) { + GfoFld fld = flds.Get_at(i); + sb.Add(fld.Key()).Add(",").Add(fld.Type().Key()); + list.Add(sb.To_str_and_clear()); + } + } + public DsvDataRdr_fxt tst_Tbls(String... expdNames) { + List_adp actlList = List_adp_.New(); + for (int i = 0; i < root.Subs().Count(); i++) { + GfoNde tbl = root.Subs().FetchAt_asGfoNde(i); + actlList.Add(tbl.Name()); + } + Tfds.Eq_ary(expdNames, actlList.To_str_ary()); + return this; + } + public DsvDataRdr_fxt tst_DatNull() { + Tfds.Eq(0, root.Subs().Count()); + return this; + } + public DsvDataRdr_fxt tst_DatCsv(Object[]... expdRows) {return tst_Dat(0, expdRows);} + public DsvDataRdr_fxt tst_Dat(int tblIdx, Object[]... expdRows) { + GfoNde tbl = root.Subs().FetchAt_asGfoNde(tblIdx); + if (expdRows.length == 0) { + Tfds.Eq(0, tbl.Subs().Count()); + return this; + } + List_adp expdList = List_adp_.New(), actlList = List_adp_.New(); + String_bldr sb = String_bldr_.new_(); + for (int i = 0; i < tbl.Subs().Count(); i++) { + GfoNde row = tbl.Subs().FetchAt_asGfoNde(i); + for (int j = 0; j < row.Flds().Count(); j++) { + if (j != 0) sb.Add("~"); + sb.Add_obj(Object_.Xto_str_strict_or_null_mark(row.ReadAt(j))); + } + expdList.Add(sb.To_str_and_clear()); + } + for (Object[] expdRow : expdRows) { + if (expdRow == null) { + actlList.Add(""); + continue; + } + for (int j = 0; j < expdRow.length; j++) { + if (j != 0) sb.Add("~"); + sb.Add_obj(Object_.Xto_str_strict_or_null_mark(expdRow[j])); + } + actlList.Add(sb.To_str_and_clear()); + } + Tfds.Eq_list(expdList, actlList); + return this; + } + public static DsvDataRdr_fxt new_() {return new DsvDataRdr_fxt();} + static final int TblIdx0 = 0; +} diff --git a/100_core/src/gplx/langs/dsvs/DsvDataRdr_dsv_dat_tst.java b/100_core/src/gplx/langs/dsvs/DsvDataRdr_dsv_dat_tst.java index a27517de8..296f36eac 100644 --- a/100_core/src/gplx/langs/dsvs/DsvDataRdr_dsv_dat_tst.java +++ b/100_core/src/gplx/langs/dsvs/DsvDataRdr_dsv_dat_tst.java @@ -13,3 +13,56 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +import org.junit.*; +public class DsvDataRdr_dsv_dat_tst { + @Before public void setup() {fx.Clear();} DsvDataRdr_fxt fx = DsvDataRdr_fxt.new_(); + @Test public void NameOnly() { + fx.run_parse_("tableName, ,\" \",#"); + fx.tst_Tbls("tableName"); + fx.tst_Dat(0); + } + @Test public void Rows_N() { + fx.run_parse_lines_ + ( "numbers, ,\" \",#" + , "1,2,3" + , "4,5,6" + ); + fx.tst_Tbls("numbers"); + fx.tst_Dat(0 + , fx.ary_("1", "2", "3") + , fx.ary_("4", "5", "6") + ); + } + @Test public void Tbls_N() { + fx.run_parse_lines_ + ( "letters, ,\" \",#" + , "a,b,c" + , "numbers, ,\" \",#" + , "1,2,3" + , "4,5,6" + ); + fx.tst_Tbls("letters", "numbers"); + fx.tst_Dat(0, fx.ary_("a", "b", "c")); + fx.tst_Dat(1, fx.ary_("1", "2", "3"), fx.ary_("4", "5", "6")); + } + @Test public void IgnoreTrailingBlankRow() { + fx.run_parse_lines_ + ( "letters, ,\" \",#" + , "a,b,c" + , "" // ignored + ); + fx.tst_Tbls("letters"); + fx.tst_Dat(0, fx.ary_("a", "b", "c")); + } + @Test public void AllowCommentsDuringData() { + fx.run_parse_lines_ + ( "letters, ,\" \",#" + , "a,b,c" + , "// letters omitted, ,\" \",//" // these comments are not preserved + , "x,y,z" + ); + fx.tst_Tbls("letters"); + fx.tst_Dat(0, fx.ary_("a", "b", "c"), fx.ary_("x", "y", "z")); + } +} diff --git a/100_core/src/gplx/langs/dsvs/DsvDataRdr_dsv_hdr_tst.java b/100_core/src/gplx/langs/dsvs/DsvDataRdr_dsv_hdr_tst.java index a27517de8..164f3a6ee 100644 --- a/100_core/src/gplx/langs/dsvs/DsvDataRdr_dsv_hdr_tst.java +++ b/100_core/src/gplx/langs/dsvs/DsvDataRdr_dsv_hdr_tst.java @@ -13,3 +13,68 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +import org.junit.*; import gplx.core.gfo_ndes.*; import gplx.core.type_xtns.*; +public class DsvDataRdr_dsv_hdr_tst { + @Before public void setup() {fx.Clear();} DsvDataRdr_fxt fx = DsvDataRdr_fxt.new_(); + @Test public void Names() { + fx.run_parse_lines_ + ( "id,name, ,\" \",@" + , "0,me" + , "1,you" + ); + fx.tst_Flds(0, GfoFldList_.str_("id", "name")); + fx.tst_Tbls(DsvTblBldr.NullTblName); + fx.tst_Dat(0 + , fx.ary_("0", "me") + , fx.ary_("1", "you") + ); + } + @Test public void Types() { + fx.run_parse_lines_ + ( "int," + StringClassXtn.Key_const + ", ,\" \",$" + , "0,me" + , "1,you" + ); + fx.tst_Flds(0, GfoFldList_.new_().Add("fld0", IntClassXtn.Instance).Add("fld1", StringClassXtn.Instance)); + fx.tst_Dat(0 + , fx.ary_(0, "me") + , fx.ary_(1, "you") + ); + } + @Test public void NamesAndTypes() { + fx.run_parse_lines_ + ( "id,name, ,\" \",@" + , "int," + StringClassXtn.Key_const + ", ,\" \",$" + , "0,me" + , "1,you" + ); + fx.tst_Flds(0, GfoFldList_.new_().Add("id", IntClassXtn.Instance).Add("name", StringClassXtn.Instance)); + fx.tst_Dat(0 + , fx.ary_(0, "me") + , fx.ary_(1, "you") + ); + } + @Test public void MultipleTables_NoData() { + fx.run_parse_lines_ + ( "persons, ,\" \",#" + , "id,name, ,\" \",@" + , "things, ,\" \",#" + , "id,data, ,\" \",@" + ); + fx.tst_Tbls("persons", "things"); + fx.tst_Flds(0, GfoFldList_.str_("id", "name")); + fx.tst_Flds(1, GfoFldList_.str_("id", "data")); + fx.tst_Dat(0); + fx.tst_Dat(1); + } + @Test public void Comment() { + fx.run_parse_lines_ + ( "--------------------, ,\" \",//" + , "tbl0, ,\" \",#" + , "a0,a1" + ); + fx.tst_Tbls("tbl0"); + fx.tst_Dat(0, fx.ary_("a0", "a1")); + } +} diff --git a/100_core/src/gplx/langs/dsvs/DsvDataRdr_dsv_misc_tst.java b/100_core/src/gplx/langs/dsvs/DsvDataRdr_dsv_misc_tst.java index a27517de8..28f1a7b79 100644 --- a/100_core/src/gplx/langs/dsvs/DsvDataRdr_dsv_misc_tst.java +++ b/100_core/src/gplx/langs/dsvs/DsvDataRdr_dsv_misc_tst.java @@ -13,3 +13,62 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +import org.junit.*; import gplx.core.gfo_ndes.*; import gplx.core.type_xtns.*; +public class DsvDataRdr_dsv_misc_tst { + @Before public void setup() {fx.Clear();} DsvDataRdr_fxt fx = DsvDataRdr_fxt.new_(); + @Test public void CmdDlm_NearMatches() { + fx.run_parse_("a, ,b"); + fx.tst_DatCsv(fx.ary_("a", " ", "b")); + fx.Clear(); + + fx.run_parse_("a,\" \",b"); + fx.tst_DatCsv(fx.ary_("a", " ", "b")); + fx.Clear(); + + fx.run_parse_("a, ,b,\" \",c"); + fx.tst_DatCsv(fx.ary_("a", " ", "b", " ", "c")); + fx.Clear(); + } + @Test public void CmdDlm_DoNotSpanLines() { + fx.run_parse_lines_ + ( "a, " + , "\" \",b" + ); + fx.tst_DatCsv + ( fx.ary_("a", " ") + , fx.ary_(" ", "b") + ); + } + @Test public void CmdDlm_SecondFldMustBeQuoted() { + fx.run_parse_lines_("a, , ,b"); // will fail with "invalid command: b", if second , , is interpreted as command delimiter + fx.tst_DatCsv(fx.ary_("a", " ", " ", "b")); + } + @Test public void Null_Int() { + fx.run_parse_ // not using run_parse_lines_ b/c (a) will have extra lineBreak; (b) test will look funny; + ( "int," + StringClassXtn.Key_const + ", ,\" \",$", String_.CrLf + , ",val1" + ); + fx.tst_Tbls(DsvTblBldr.NullTblName); + fx.tst_Flds(0, GfoFldList_.new_().Add("fld0", IntClassXtn.Instance).Add("fld1", StringClassXtn.Instance)); + fx.tst_Dat(0, fx.ary_(null, "val1")); + } + @Test public void Null_String() { + fx.run_parse_ // not using run_parse_lines_ b/c (a) will have extra lineBreak; (b) test will look funny; + ( StringClassXtn.Key_const + "," + StringClassXtn.Key_const + ", ,\" \",$", String_.CrLf + , ",val1" + ); + fx.tst_Tbls(DsvTblBldr.NullTblName); + fx.tst_Flds(0, GfoFldList_.new_().Add("fld0", StringClassXtn.Instance).Add("fld1", StringClassXtn.Instance)); + fx.tst_Dat(0, fx.ary_(null, "val1")); + } + @Test public void EmptyString() { + fx.run_parse_ // not using run_parse_lines_ b/c (a) will have extra lineBreak; (b) test will look funny; + ( StringClassXtn.Key_const + "," + StringClassXtn.Key_const + ", ,\" \",$", String_.CrLf + , "\"\",val1" + ); + fx.tst_Tbls(DsvTblBldr.NullTblName); + fx.tst_Flds(0, GfoFldList_.new_().Add("fld0", StringClassXtn.Instance).Add("fld1", StringClassXtn.Instance)); + fx.tst_Dat(0, fx.ary_("", "val1")); + } +} diff --git a/100_core/src/gplx/langs/dsvs/DsvDataRdr_layout_tst.java b/100_core/src/gplx/langs/dsvs/DsvDataRdr_layout_tst.java index a27517de8..1f70bcd79 100644 --- a/100_core/src/gplx/langs/dsvs/DsvDataRdr_layout_tst.java +++ b/100_core/src/gplx/langs/dsvs/DsvDataRdr_layout_tst.java @@ -13,3 +13,117 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +import org.junit.*; import gplx.core.gfo_ndes.*; import gplx.core.type_xtns.*; +public class DsvDataRdr_layout_tst { + @Test public void TableName() { + run_parse_lines("table0, ,\" \",#"); + tst_Layout(0, DsvHeaderItm.Id_TableName); + } + @Test public void Comment() { + run_parse_lines("-------------, ,\" \",//", "data"); // need dataLine or parser will throw away standalone header + tst_Layout(0, DsvHeaderItm.Id_Comment); + } + @Test public void BlankLine() { + run_parse_lines("", "data"); // need dataLine or parser will throw away standalone header + tst_Layout(0, DsvHeaderItm.Id_BlankLine); + } + @Test public void LeafNames() { + run_parse_lines("id,name, ,\" \",@"); + tst_Layout(0, DsvHeaderItm.Id_LeafNames); + } + @Test public void LeafTypes() { + run_parse_lines("int," + StringClassXtn.Key_const + ", ,\" \",$"); + tst_Layout(0, DsvHeaderItm.Id_LeafTypes); + } + @Test public void Combined() { + run_parse_lines + ( "" + , "-------------, ,\" \",//" + , "table0, ,\" \",#" + , "int," + StringClassXtn.Key_const + ", ,\" \",$" + , "id,name, ,\" \",@" + , "-------------, ,\" \",//" + , "0,me" + ); + tst_Layout(0 + , DsvHeaderItm.Id_BlankLine + , DsvHeaderItm.Id_Comment + , DsvHeaderItm.Id_TableName + , DsvHeaderItm.Id_LeafTypes + , DsvHeaderItm.Id_LeafNames + , DsvHeaderItm.Id_Comment + ); + } + @Test public void Tbl_N() { + run_parse_lines + ( "" + , "*************, ,\" \",//" + , "table0, ,\" \",#" + , "-------------, ,\" \",//" + , "0,me" + , "" + , "*************, ,\" \",//" + , "table1, ,\" \",#" + , " extended data, ,\" \",//" + , "-------------, ,\" \",//" + , "1,you,more" + ); + tst_Layout(0 + , DsvHeaderItm.Id_BlankLine + , DsvHeaderItm.Id_Comment + , DsvHeaderItm.Id_TableName + , DsvHeaderItm.Id_Comment + ); + tst_Layout(1 + , DsvHeaderItm.Id_BlankLine + , DsvHeaderItm.Id_Comment + , DsvHeaderItm.Id_TableName + , DsvHeaderItm.Id_Comment + , DsvHeaderItm.Id_Comment + ); + } + @Test public void Tbl_N_FirstIsEmpty() { + run_parse_lines + ( "" + , "*************, ,\" \",//" + , "table0, ,\" \",#" + , "-------------, ,\" \",//" + , "" + , "" + , "*************, ,\" \",//" + , "table1, ,\" \",#" + , " extended data, ,\" \",//" + , "-------------, ,\" \",//" + , "1,you,more" + ); + tst_Layout(0 + , DsvHeaderItm.Id_BlankLine + , DsvHeaderItm.Id_Comment + , DsvHeaderItm.Id_TableName + , DsvHeaderItm.Id_Comment + ); + tst_Layout(1 + , DsvHeaderItm.Id_BlankLine + , DsvHeaderItm.Id_BlankLine + , DsvHeaderItm.Id_Comment + , DsvHeaderItm.Id_TableName + , DsvHeaderItm.Id_Comment + , DsvHeaderItm.Id_Comment + ); + } + void run_parse_lines(String... ary) { + String raw = String_.Concat_lines_crlf(ary); + DsvParser parser = DsvParser.dsv_(); + root = parser.ParseAsNde(raw); + } + void tst_Layout(int subIdx, int... expd) { + GfoNde tbl = root.Subs().FetchAt_asGfoNde(subIdx); + DsvStoreLayout layout = (DsvStoreLayout)tbl.EnvVars().Get_by(DsvStoreLayout.Key_const); + int[] actl = new int[layout.HeaderList().Count()]; + for (int i = 0; i < actl.length; i++) + actl[i] = layout.HeaderList().Get_at(i).Id(); + Tfds.Eq_ary(expd, actl); + } + GfoNde root; +} diff --git a/100_core/src/gplx/langs/dsvs/DsvDataWtr.java b/100_core/src/gplx/langs/dsvs/DsvDataWtr.java index a27517de8..9d05e8317 100644 --- a/100_core/src/gplx/langs/dsvs/DsvDataWtr.java +++ b/100_core/src/gplx/langs/dsvs/DsvDataWtr.java @@ -13,3 +13,101 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +import gplx.core.strings.*; import gplx.core.gfo_ndes.*; import gplx.core.stores.*; +public class DsvDataWtr extends DataWtr_base implements DataWtr { + public void InitWtr(String key, Object val) { + if (key == DsvStoreLayout.Key_const) layout = (DsvStoreLayout)val; + } + @Override public void WriteData(String name, Object val) {sb.WriteFld(val == null ? null : val.toString());} + public void WriteLeafBgn(String leafName) {} + public void WriteLeafEnd() {sb.WriteRowSep();} + @Override public void WriteNodeBgn(String name) {WriteTableBgn(name, GfoFldList_.Null);} + public void WriteTableBgn(String name, GfoFldList flds) { + for (int i = 0; i < layout.HeaderList().Count(); i++) { + DsvHeaderItm data = layout.HeaderList().Get_at(i); + int id = data.Id(); + if (id == DsvHeaderItm.Id_TableName) WriteTableName(name); + else if (id == DsvHeaderItm.Id_LeafNames) WriteMeta(flds, true, sym.FldNamesSym()); + else if (id == DsvHeaderItm.Id_LeafTypes) WriteMeta(flds, false, sym.FldTypesSym()); + else if (id == DsvHeaderItm.Id_BlankLine) sb.WriteRowSep(); + else if (id == DsvHeaderItm.Id_Comment) WriteComment(data.Val().toString()); + } + } + @Override public void WriteNodeEnd() {} + public void Clear() {sb.Clear();} + public String To_str() {return sb.To_str();} + void WriteTableName(String tableName) { + sb.WriteFld(tableName); + sb.WriteCmd(sym.TblNameSym()); + sb.WriteRowSep(); + } + void WriteMeta(GfoFldList flds, boolean isName, String cmd) { + for (int i = 0; i < flds.Count(); i++) { + GfoFld fld = flds.Get_at(i); + String val = isName ? fld.Key(): fld.Type().Key(); + sb.WriteFld(val); + } + if (layout.WriteCmdSequence()) sb.WriteCmd(cmd); + sb.WriteRowSep(); + } + void WriteComment(String comment) { + sb.WriteFld(comment); + sb.WriteCmd(sym.CommentSym()); + sb.WriteRowSep(); + } + @Override public SrlMgr SrlMgr_new(Object o) {return new DsvDataWtr();} + DsvStringBldr sb; DsvSymbols sym = DsvSymbols.default_(); DsvStoreLayout layout = DsvStoreLayout.csv_dat_(); + @gplx.Internal protected DsvDataWtr() {sb = DsvStringBldr.new_(sym);} +} +class DsvStringBldr { + public void Clear() {sb.Clear();} + public String To_str() {return sb.To_str();} + public void WriteCmd(String cmd) { + WriteFld(sym.CmdSequence(), true); + WriteFld(cmd); + } + public void WriteFldSep() {sb.Add(sym.FldSep());} + public void WriteRowSep() { + sb.Add(sym.RowSep()); + isNewRow = true; + } + public void WriteFld(String val) {WriteFld(val, false);} + void WriteFld(String val, boolean writeRaw) { + if (isNewRow) // if isNewRow, then fld is first, and no fldSpr needed (RowSep serves as fldSpr) + isNewRow = false; + else + sb.Add(sym.FldSep()); + + if (val == null) {} // null -> append nothing + else if (String_.Eq(val, String_.Empty))// "" -> append "" + sb.Add("\"\""); + else if (writeRaw) // only cmds should be writeRaw (will append ," ") + sb.Add(val); + else { // escape as necessary; ex: "the quote "" char"; "the comma , char" + boolean quoteField = false; + if (String_.Has(val, sym.QteDlm())) { + val = String_.Replace(val, "\"", "\"\""); + quoteField = true; + } + else if (String_.Has(val, sym.FldSep())) + quoteField = true; + else if (sym.RowSepIsNewLine() + && (String_.Has(val, "\n") || String_.Has(val, "\r"))) + quoteField = true; + else if (String_.Has(val, sym.RowSep())) + quoteField = true; + + if (quoteField) sb.Add(sym.QteDlm()); + sb.Add(val); + if (quoteField) sb.Add(sym.QteDlm()); + } + } + + String_bldr sb = String_bldr_.new_(); DsvSymbols sym; boolean isNewRow = true; + public static DsvStringBldr new_(DsvSymbols sym) { + DsvStringBldr rv = new DsvStringBldr(); + rv.sym = sym; + return rv; + } DsvStringBldr() {} +} diff --git a/100_core/src/gplx/langs/dsvs/DsvDataWtr_.java b/100_core/src/gplx/langs/dsvs/DsvDataWtr_.java index a27517de8..aab55fd37 100644 --- a/100_core/src/gplx/langs/dsvs/DsvDataWtr_.java +++ b/100_core/src/gplx/langs/dsvs/DsvDataWtr_.java @@ -13,3 +13,17 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +public class DsvDataWtr_ { + public static DsvDataWtr csv_hdr_() { + DsvDataWtr rv = new DsvDataWtr(); + rv.InitWtr(DsvStoreLayout.Key_const, DsvStoreLayout.csv_hdr_()); + return rv; + } + public static DsvDataWtr csv_dat_() { + DsvDataWtr rv = new DsvDataWtr(); + rv.InitWtr(DsvStoreLayout.Key_const, DsvStoreLayout.csv_dat_()); + return rv; + } + public static DsvDataWtr new_() {return new DsvDataWtr();} +} diff --git a/100_core/src/gplx/langs/dsvs/DsvDataWtr_csv_tst.java b/100_core/src/gplx/langs/dsvs/DsvDataWtr_csv_tst.java index a27517de8..8ddf6cd77 100644 --- a/100_core/src/gplx/langs/dsvs/DsvDataWtr_csv_tst.java +++ b/100_core/src/gplx/langs/dsvs/DsvDataWtr_csv_tst.java @@ -13,3 +13,86 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +import org.junit.*; import gplx.core.gfo_ndes.*; import gplx.core.type_xtns.*; +public class DsvDataWtr_csv_tst { + @Test public void Dat_Val_0() { + root = fx_nde.csv_dat_(); this.AddCsvRow(root); + expd = String_.Concat_lines_crlf(""); + fx.tst_XtoStr(wtr, root, expd); + } + @Test public void Dat_Val_1() { + root = fx_nde.csv_dat_(); this.AddCsvRow(root, "a"); + expd = String_.Concat_lines_crlf("a"); + fx.tst_XtoStr(wtr, root, expd); + } + @Test public void Dat_Val_N() { + root = fx_nde.csv_dat_(); this.AddCsvRow(root, "a", "b", "c"); + expd = String_.Concat_lines_crlf("a,b,c"); + fx.tst_XtoStr(wtr, root, expd); + } + @Test public void Dat_Row_N() { + root = fx_nde.csv_dat_(); + this.AddCsvRow(root, "a", "b", "c"); + this.AddCsvRow(root, "d", "e", "f"); + expd = String_.Concat_lines_crlf + ( "a,b,c" + , "d,e,f" + ); + fx.tst_XtoStr(wtr, root, expd); + } + @Test public void Dat_Escape_FldSpr() { // , + root = fx_nde.csv_dat_(); this.AddCsvRow(root, "a", ",", "c"); + expd = String_.Concat_lines_crlf("a,\",\",c"); + fx.tst_XtoStr(wtr, root, expd); + } + @Test public void Dat_Escape_RcdSpr() { // NewLine + root = fx_nde.csv_dat_(); this.AddCsvRow(root, "a", String_.CrLf, "c"); + expd = String_.Concat_lines_crlf("a,\"" + String_.CrLf + "\",c"); + fx.tst_XtoStr(wtr, root, expd); + } + @Test public void Dat_Escape_Quote() { // " -> "" + root = fx_nde.csv_dat_(); this.AddCsvRow(root, "a", "\"", "c"); + expd = String_.Concat_lines_crlf("a,\"\"\"\",c"); + fx.tst_XtoStr(wtr, root, expd); + } + @Test public void Dat_Whitespace() { + root = fx_nde.csv_dat_(); this.AddCsvRow(root, "a", " b\t", "c"); + expd = String_.Concat_lines_crlf("a, b\t,c"); + fx.tst_XtoStr(wtr, root, expd); + } + @Test public void Dat_Null() { + root = fx_nde.csv_dat_(); this.AddCsvRow(root, "a", null, "c"); + expd = String_.Concat_lines_crlf("a,,c"); + fx.tst_XtoStr(wtr, root, expd); + } + @Test public void Dat_EmptyString() { + root = fx_nde.csv_dat_(); this.AddCsvRow(root, "a", "", "c"); + expd = String_.Concat_lines_crlf("a,\"\",c"); + fx.tst_XtoStr(wtr, root, expd); + } + @Test public void Hdr_Flds() { + wtr = DsvDataWtr_.csv_hdr_(); + GfoFldList flds = GfoFldList_.new_().Add("id", StringClassXtn.Instance).Add("name", StringClassXtn.Instance); + root = fx_nde.csv_hdr_(flds); this.AddCsvRow(root, "0", "me"); + expd = String_.Concat_lines_crlf + ( "id,name" + , "0,me" + ); + fx.tst_XtoStr(wtr, root, expd); + } + void AddCsvRow(GfoNde root, String... ary) { + GfoNde sub = GfoNde_.vals_(root.SubFlds(), ary); + root.Subs().Add(sub); + } + GfoNde root; String expd; DsvDataWtr wtr = DsvDataWtr_.csv_dat_(); DsvDataWtr_fxt fx = DsvDataWtr_fxt.new_(); GfoNdeFxt fx_nde = GfoNdeFxt.new_(); +} +class DsvDataWtr_fxt { + public void tst_XtoStr(DsvDataWtr wtr, GfoNde root, String expd) { + wtr.Clear(); + root.XtoStr_wtr(wtr); + String actl = wtr.To_str(); + Tfds.Eq(expd, actl); + } + public static DsvDataWtr_fxt new_() {return new DsvDataWtr_fxt();} DsvDataWtr_fxt() {} +} diff --git a/100_core/src/gplx/langs/dsvs/DsvDataWtr_tbls_tst.java b/100_core/src/gplx/langs/dsvs/DsvDataWtr_tbls_tst.java index a27517de8..a3472b0f4 100644 --- a/100_core/src/gplx/langs/dsvs/DsvDataWtr_tbls_tst.java +++ b/100_core/src/gplx/langs/dsvs/DsvDataWtr_tbls_tst.java @@ -13,3 +13,59 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +import org.junit.*; import gplx.core.gfo_ndes.*; +public class DsvDataWtr_tbls_tst { + @Before public void setup() { + DsvStoreLayout layout = DsvStoreLayout.dsv_brief_(); + layout.HeaderList().Add_TableName(); + wtr.InitWtr(DsvStoreLayout.Key_const, layout); + } + @Test public void Rows_0() { + root = fx_nde.tbl_("tbl0"); + expd = String_.Concat_lines_crlf( "tbl0, ,\" \",#"); + fx.tst_XtoStr(wtr, root, expd); + } + @Test public void Rows_N() { + root = fx_nde.tbl_ + ( "numbers" + , fx_nde.row_vals_(1, 2, 3) + , fx_nde.row_vals_(4, 5, 6) + ); + expd = String_.Concat_lines_crlf + ( "numbers, ,\" \",#" + , "1,2,3" + , "4,5,6" + ); + fx.tst_XtoStr(wtr, root, expd); + } + @Test public void Tbls_N_Empty() { + root = fx_nde.root_ + ( fx_nde.tbl_("tbl0") + , fx_nde.tbl_("tbl1") + ); + expd = String_.Concat_lines_crlf + ( "tbl0, ,\" \",#" + , "tbl1, ,\" \",#" + ); + fx.tst_XtoStr(wtr, root, expd); + } + @Test public void Tbls_N() { + root = fx_nde.root_ + ( fx_nde.tbl_("letters" + , fx_nde.row_vals_("a", "b", "c")) + , fx_nde.tbl_("numbers" + , fx_nde.row_vals_(1, 2, 3) + , fx_nde.row_vals_(4, 5, 6) + )); + expd = String_.Concat_lines_crlf + ( "letters, ,\" \",#" + , "a,b,c" + , "numbers, ,\" \",#" + , "1,2,3" + , "4,5,6" + ); + fx.tst_XtoStr(wtr, root, expd); + } + GfoNde root; String expd; DsvDataWtr wtr = DsvDataWtr_.csv_dat_(); DsvDataWtr_fxt fx = DsvDataWtr_fxt.new_(); GfoNdeFxt fx_nde = GfoNdeFxt.new_(); +} diff --git a/100_core/src/gplx/langs/dsvs/DsvHeaderList.java b/100_core/src/gplx/langs/dsvs/DsvHeaderList.java index a27517de8..c06f6be00 100644 --- a/100_core/src/gplx/langs/dsvs/DsvHeaderList.java +++ b/100_core/src/gplx/langs/dsvs/DsvHeaderList.java @@ -13,3 +13,30 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +public class DsvHeaderList { + @gplx.Internal protected int Count() {return list.Count();} + @gplx.Internal protected DsvHeaderItm Get_at(int i) {return (DsvHeaderItm)list.Get_at(i);} + public DsvHeaderList Add_LeafTypes() {this.Add(new DsvHeaderItm(DsvHeaderItm.Id_LeafTypes, null)); return this;} + public DsvHeaderList Add_LeafNames() {this.Add(new DsvHeaderItm(DsvHeaderItm.Id_LeafNames, null)); return this;} + public DsvHeaderList Add_TableName() {this.Add(new DsvHeaderItm(DsvHeaderItm.Id_TableName, null)); return this;} + public DsvHeaderList Add_BlankLine() {this.Add(new DsvHeaderItm(DsvHeaderItm.Id_BlankLine, null)); return this;} + public DsvHeaderList Add_Comment(String comment) {this.Add(new DsvHeaderItm(DsvHeaderItm.Id_Comment, comment)); return this;} + void Add(DsvHeaderItm data) {list.Add(data);} + + List_adp list = List_adp_.New(); + public static DsvHeaderList new_() {return new DsvHeaderList();} DsvHeaderList() {} +} +class DsvHeaderItm { + public int Id() {return id;} int id; + public Object Val() {return val;} Object val; + @gplx.Internal protected DsvHeaderItm(int id, Object val) {this.id = id; this.val = val;} + + public static final int + Id_Comment = 1 + , Id_TableName = 2 + , Id_BlankLine = 3 + , Id_LeafTypes = 4 + , Id_LeafNames = 5 + ; +} diff --git a/100_core/src/gplx/langs/dsvs/DsvStoreLayout.java b/100_core/src/gplx/langs/dsvs/DsvStoreLayout.java index a27517de8..3ff10f82d 100644 --- a/100_core/src/gplx/langs/dsvs/DsvStoreLayout.java +++ b/100_core/src/gplx/langs/dsvs/DsvStoreLayout.java @@ -13,3 +13,37 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +public class DsvStoreLayout { + public DsvHeaderList HeaderList() {return headerList;} DsvHeaderList headerList = DsvHeaderList.new_(); + @gplx.Internal protected boolean WriteCmdSequence() {return writeCmdSequence;} @gplx.Internal protected DsvStoreLayout WriteCmdSequence_(boolean val) {writeCmdSequence = val; return this;} private boolean writeCmdSequence; + + static DsvStoreLayout new_() {return new DsvStoreLayout();} + public static DsvStoreLayout csv_dat_() {return new_();} + public static DsvStoreLayout csv_hdr_() { + DsvStoreLayout rv = new_(); + rv.HeaderList().Add_LeafNames(); + return rv; + } + public static DsvStoreLayout dsv_brief_() { + DsvStoreLayout rv = new_(); + rv.writeCmdSequence = true; + return rv; + } + public static DsvStoreLayout dsv_full_() { + DsvStoreLayout rv = DsvStoreLayout.new_(); + rv.writeCmdSequence = true; + rv.HeaderList().Add_BlankLine(); + rv.HeaderList().Add_BlankLine(); + rv.HeaderList().Add_Comment("================================"); + rv.HeaderList().Add_TableName(); + rv.HeaderList().Add_Comment("================================"); + rv.HeaderList().Add_LeafTypes(); + rv.HeaderList().Add_LeafNames(); + rv.HeaderList().Add_Comment("================================"); + return rv; + } + public static DsvStoreLayout as_(Object obj) {return obj instanceof DsvStoreLayout ? (DsvStoreLayout)obj : null;} + public static DsvStoreLayout cast(Object obj) {try {return (DsvStoreLayout)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, DsvStoreLayout.class, obj);}} + public static final String Key_const = "StoreLayoutWtr"; +} diff --git a/100_core/src/gplx/langs/dsvs/DsvSymbols.java b/100_core/src/gplx/langs/dsvs/DsvSymbols.java index a27517de8..a1db9fb43 100644 --- a/100_core/src/gplx/langs/dsvs/DsvSymbols.java +++ b/100_core/src/gplx/langs/dsvs/DsvSymbols.java @@ -13,3 +13,38 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +class DsvSymbols { + public String FldSep() {return fldSep;} public DsvSymbols FldSep_(String v) {fldSep = v; CmdSequence_set(); return this;} private String fldSep; + public String RowSep() {return rowSep;} + public DsvSymbols RowSep_(String v) { + rowSep = v; + rowSepIsNewLine = String_.Has(v, "\n") || String_.Has(v, "\r"); + return this; + } String rowSep; + public String QteDlm() {return qteDlm;} public void QteDlm_set(String v) {qteDlm = v; CmdSequence_set();} private String qteDlm; + public String CmdDlm() {return cmdDlm;} public void CmdDlm_set(String v) {cmdDlm = v; CmdSequence_set();} private String cmdDlm; + public String CmdSequence() {return cmdSequence;} private String cmdSequence; + public String CommentSym() {return commentSym;} public void CommentSym_set(String v) {commentSym = v;} private String commentSym; + public String TblNameSym() {return tblNameSym;} public void TblNamesSym_set(String v) {tblNameSym = v;} private String tblNameSym; + public String FldNamesSym() {return fldNamesSym;} public void FldNamesSym_set(String v) {fldNamesSym = v;} private String fldNamesSym; + public String FldTypesSym() {return fldTypesSym;} public void FldTypesSym_set(String v) {fldTypesSym = v;} private String fldTypesSym; + public boolean RowSepIsNewLine() {return rowSepIsNewLine;} private boolean rowSepIsNewLine; + public void Reset() { + fldSep = ","; + RowSep_ ("\r\n"); + + qteDlm = "\""; + cmdDlm = " "; + CmdSequence_set(); + + commentSym = "//"; + tblNameSym = "#"; + fldNamesSym = "@"; + fldTypesSym = "$"; + } + void CmdSequence_set() { // commandDelimiters are repeated; once without quotes and once with quotes; ex: , ," ", + cmdSequence = String_.Concat(cmdDlm, fldSep, qteDlm, cmdDlm, qteDlm); + } + public static DsvSymbols default_() {return new DsvSymbols();} DsvSymbols() {this.Reset();} +} diff --git a/100_core/src/gplx/langs/gfs/GfsCore.java b/100_core/src/gplx/langs/gfs/GfsCore.java index a27517de8..11982383a 100644 --- a/100_core/src/gplx/langs/gfs/GfsCore.java +++ b/100_core/src/gplx/langs/gfs/GfsCore.java @@ -13,3 +13,76 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +import gplx.core.gfo_regys.*; +public class GfsCore implements Gfo_invk { + public Gfo_invk Root() {return root;} + @gplx.Internal protected GfsRegy Root_as_regy() {return root;} GfsRegy root = GfsRegy.new_(); + public void Clear() {root.Clear();} + public GfoMsgParser MsgParser() {return msgParser;} public GfsCore MsgParser_(GfoMsgParser v) {msgParser = v; return this;} GfoMsgParser msgParser; + public void Del(String key) {root.Del(key);} + public void AddLib(GfsLibIni... ary) {for (GfsLibIni itm : ary) itm.Ini(this);} + public void AddCmd(Gfo_invk invk, String key) {root.AddCmd(invk, key);} + public void AddObj(Gfo_invk invk, String key) {root.AddObj(invk, key);} + public void AddDeep(Gfo_invk invk, String... ary) { + Gfo_invk_cmd_mgr_owner cur = (Gfo_invk_cmd_mgr_owner)((GfsRegyItm)root.Get_by(ary[0])).InvkAble(); + for (int i = 1; i < ary.length - 1; i++) + cur = (Gfo_invk_cmd_mgr_owner)cur.InvkMgr().Invk(GfsCtx.Instance, 0, ary[i], GfoMsg_.Null, cur); + cur.InvkMgr().Add_cmd(ary[ary.length - 1], invk); + } + public String FetchKey(Gfo_invk invk) {return root.FetchByType(invk).Key();} + public Object ExecOne(GfsCtx ctx, GfoMsg msg) {return GfsCore_.Exec(ctx, root, msg, null, 0);} + public Object ExecOne_to(GfsCtx ctx, Gfo_invk invk, GfoMsg msg) {return GfsCore_.Exec(ctx, invk, msg, null, 0);} + public Object ExecMany(GfsCtx ctx, GfoMsg rootMsg) { + Object rv = null; + for (int i = 0; i < rootMsg.Subs_count(); i++) { + GfoMsg subMsg = (GfoMsg)rootMsg.Subs_getAt(i); + rv = GfsCore_.Exec(ctx, root, subMsg, null, 0); + } + return rv; + } + public void ExecRegy(String key) { + GfoRegyItm itm = GfoRegy.Instance.FetchOrNull(key); + if (itm == null) {UsrDlg_.Instance.Warn(UsrMsg.new_("could not find script for key").Add("key", key)); return;} + Io_url url = itm.Url(); + if (!Io_mgr.Instance.ExistsFil(url)) { + UsrDlg_.Instance.Warn(UsrMsg.new_("script url does not exist").Add("key", key).Add("url", url)); + return; + } + this.ExecText(Io_mgr.Instance.LoadFilStr(url)); + } + public Object ExecFile_ignoreMissing(Io_url url) {if (!Io_mgr.Instance.ExistsFil(url)) return null; return ExecText(Io_mgr.Instance.LoadFilStr(url));} + public Object ExecFile(Io_url url) {return ExecText(Io_mgr.Instance.LoadFilStr(url));} + public Object ExecFile_ignoreMissing(Gfo_invk root, Io_url url) { + if (!Io_mgr.Instance.ExistsFil(url)) return null; + if (msgParser == null) throw Err_.new_wo_type("msgParser is null"); + return Exec_bry(Io_mgr.Instance.LoadFilBry(url), root); + } + public Object Exec_bry(byte[] bry) {return Exec_bry(bry, root);} + public Object Exec_bry(byte[] bry, Gfo_invk root) { + GfoMsg rootMsg = msgParser.ParseToMsg(String_.new_u8(bry)); + Object rv = null; + GfsCtx ctx = GfsCtx.new_(); + for (int i = 0; i < rootMsg.Subs_count(); i++) { + GfoMsg subMsg = (GfoMsg)rootMsg.Subs_getAt(i); + rv = GfsCore_.Exec(ctx, root, subMsg, null, 0); + } + return rv; + } + public Object ExecText(String text) { + if (msgParser == null) throw Err_.new_wo_type("msgParser is null"); + GfsCtx ctx = GfsCtx.new_(); + return ExecMany(ctx, msgParser.ParseToMsg(text)); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_ExecFil)) { + Io_url url = m.ReadIoUrl("url"); + if (ctx.Deny()) return this; + return ExecFile(url); + } + else return Gfo_invk_.Rv_unhandled; +// return this; + } public static final String Invk_ExecFil = "ExecFil"; + public static final GfsCore Instance = new GfsCore(); + @gplx.Internal protected static GfsCore new_() {return new GfsCore();} +} diff --git a/100_core/src/gplx/langs/gfs/GfsCoreHelp.java b/100_core/src/gplx/langs/gfs/GfsCoreHelp.java index a27517de8..2c0cb7f27 100644 --- a/100_core/src/gplx/langs/gfs/GfsCoreHelp.java +++ b/100_core/src/gplx/langs/gfs/GfsCoreHelp.java @@ -13,3 +13,59 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +import gplx.core.strings.*; +class GfsCoreHelp implements Gfo_invk { + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + String path = m.ReadStrOr("path", ""); + if (String_.Eq(path, "")) { + String_bldr sb = String_bldr_.new_(); + for (int i = 0; i < core.Root_as_regy().Count(); i++) { + GfsRegyItm itm = (GfsRegyItm)core.Root_as_regy().Get_at(i); + sb.Add_spr_unless_first(itm.Key(), String_.CrLf, i); + } + return sb.To_str(); + } + else return Exec(ctx, core.Root_as_regy(), path); + } + public static Err Err_Unhandled(String objPath, String key) {return Err_.new_wo_type("obj does not handle msgKey", "objPath", objPath, "key", key).Trace_ignore_add_1_();} + static Err Err_Unhandled(String[] itmAry, int i) { + String_bldr sb = String_bldr_.new_(); + for (int j = 0; j < i; j++) + sb.Add_spr_unless_first(itmAry[j], ".", j); + return Err_Unhandled(sb.To_str(), itmAry[i]); + } + static Object Exec(GfsCtx rootCtx, Gfo_invk rootInvk, String path) { + String[] itmAry = String_.Split(path, "."); + Gfo_invk invk = rootInvk; + GfsCtx ctx = GfsCtx.new_(); + Object curRv = null; + for (int i = 0; i < itmAry.length; i++) { + String itm = itmAry[i]; + curRv = invk.Invk(ctx, 0, itm, GfoMsg_.Null); + if (curRv == Gfo_invk_.Rv_unhandled) throw Err_Unhandled(itmAry, i); + invk = (Gfo_invk)curRv; + } + GfsCoreHelp helpData = GfsCoreHelp.as_(curRv); + if (helpData != null) { // last itm is actually Method + return ""; + } + else { + ctx = GfsCtx.new_().Help_browseMode_(true); + invk.Invk(ctx, 0, "", GfoMsg_.Null); + String_bldr sb = String_bldr_.new_(); + for (int i = 0; i < ctx.Help_browseList().Count(); i++) { + String s = (String)ctx.Help_browseList().Get_at(i); + sb.Add_spr_unless_first(s, String_.CrLf, i); + } + return sb.To_str(); + } + } + public static GfsCoreHelp as_(Object obj) {return obj instanceof GfsCoreHelp ? (GfsCoreHelp)obj : null;} + public static GfsCoreHelp new_(GfsCore core) { + GfsCoreHelp rv = new GfsCoreHelp(); + rv.core = core; + return rv; + } GfsCoreHelp() {} + GfsCore core; +} diff --git a/100_core/src/gplx/langs/gfs/GfsCore_.java b/100_core/src/gplx/langs/gfs/GfsCore_.java index a27517de8..fd97ecbeb 100644 --- a/100_core/src/gplx/langs/gfs/GfsCore_.java +++ b/100_core/src/gplx/langs/gfs/GfsCore_.java @@ -13,3 +13,77 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +public class GfsCore_ { + public static final String Arg_primitive = "v"; + public static Object Exec(GfsCtx ctx, Gfo_invk owner_invk, GfoMsg owner_msg, Object owner_primitive, int depth) { + if (owner_msg.Args_count() == 0 && owner_msg.Subs_count() == 0 && String_.Eq(owner_msg.Key(), "")) {UsrDlg_.Instance.Warn("empty msg"); return Gfo_invk_.Rv_unhandled;} + if (owner_primitive != null) owner_msg.Parse_(false).Add(GfsCore_.Arg_primitive, owner_primitive); + Object rv = owner_invk.Invk(ctx, 0, owner_msg.Key(), owner_msg); + if (rv == Gfo_invk_.Rv_cancel) return rv; + else if (rv == Gfo_invk_.Rv_unhandled) { + if (ctx.Fail_if_unhandled()) + throw Err_.new_wo_type("Object does not support key", "key", owner_msg.Key(), "ownerType", Type_.Canonical_name_by_obj(owner_invk)); + else { + Gfo_usr_dlg usr_dlg = ctx.Usr_dlg(); + if (usr_dlg != null) usr_dlg.Warn_many(GRP_KEY, "unhandled_key", "Object does not support key: key=~{0} ownerType=~{1}", owner_msg.Key(), Type_.Canonical_name_by_obj(owner_invk)); + return Gfo_invk_.Noop; + } + } + if (owner_msg.Subs_count() == 0) { // msg is leaf + GfsRegyItm regyItm = GfsRegyItm.as_(rv); + if (regyItm == null) return rv; // rv is primitive or other non-regy Object + if (regyItm.IsCmd()) // rv is cmd; invk cmd + return regyItm.InvkAble().Invk(ctx, 0, owner_msg.Key(), owner_msg); + else // rv is host + return regyItm.InvkAble(); + } + else { // intermediate; cast to invk and call Exec + Gfo_invk invk = Gfo_invk_.as_(rv); + Object primitive = null; + if (invk == null) { // rv is primitive; find appropriate mgr + throw Err_.new_wo_type("unknown primitive", "type", Type_.Name(rv.getClass()), "obj", Object_.Xto_str_strict_or_null_mark(rv)); + } + Object exec_rv = null; + int len = owner_msg.Subs_count(); + for (int i = 0; i < len; i++) // iterate over subs; needed for a{b;c;d;} + exec_rv = Exec(ctx, invk, owner_msg.Subs_getAt(i), primitive, depth + 1); + return exec_rv; + } + } + static final String GRP_KEY = "gplx.gfs_core"; +} +// class GfsRegyMgr : Gfo_invk { +// public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { +// if (ctx.Match(k, Invk_Add)) { +// String libKey = m.ReadStr("libKey"), regKey = m.ReadStr("regKey"); +// if (ctx.Deny()) return this; +// GfsRegyItm itm = regy.Get_by(libKey); +// if (regy.Has(regKey)) {ctx.Write_warn("'{0}' already exists", regKey); return this;} +// regy.Add(regKey, itm.InvkAble(), itm.Type_cmd()); +// ctx.Write_note("added '{0}' as '{1}'", regKey, libKey); +// } +// else if (ctx.Match(k, Invk_Del)) { +// String regKey = m.ReadStr("regKey"); +// if (ctx.Deny()) return this; +// if (!regy.Has(regKey)) {ctx.Write_warn("{0} does not exist", regKey); return this;} +// regy.Del(regKey); +// ctx.Write_note("removed '{0}'", regKey); +// } +// else if (ctx.Match(k, Invk_Load)) { +// Io_url url = (Io_url)m.ReadObj("url", Io_url_.Parser); +// if (ctx.Deny()) return this; +// String loadText = Io_mgr.Instance.LoadFilStr(url); +// GfoMsg loadMsg = core.MsgParser().ParseToMsg(loadText); +// return core.Exec(ctx, loadMsg); +// } +// else return Gfo_invk_.Rv_unhandled; +// return this; +// } public static final String Invk_Add = "Add", Invk_Del = "Del", Invk_Load = "Load"; +// GfsCore core; GfsRegy regy; +// public static GfsRegyMgr new_(GfsCore core, GfsRegy regy) { +// GfsRegyMgr rv = new GfsRegyMgr(); +// rv.core = core; rv.regy = regy; +// return rv; +// } GfsRegyMgr() {} +// } diff --git a/100_core/src/gplx/langs/gfs/GfsLibIni.java b/100_core/src/gplx/langs/gfs/GfsLibIni.java index a27517de8..43dab5427 100644 --- a/100_core/src/gplx/langs/gfs/GfsLibIni.java +++ b/100_core/src/gplx/langs/gfs/GfsLibIni.java @@ -13,3 +13,7 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +public interface GfsLibIni { + void Ini(GfsCore core); +} diff --git a/100_core/src/gplx/langs/gfs/GfsLibIni_core.java b/100_core/src/gplx/langs/gfs/GfsLibIni_core.java index a27517de8..217618b68 100644 --- a/100_core/src/gplx/langs/gfs/GfsLibIni_core.java +++ b/100_core/src/gplx/langs/gfs/GfsLibIni_core.java @@ -13,3 +13,20 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +import gplx.core.gfo_regys.*; +public class GfsLibIni_core implements GfsLibIni { + public void Ini(GfsCore core) { + core.AddCmd(GfsCoreHelp.new_(core), "help"); + core.AddObj(DateAdp_.Gfs, "Date_"); + core.AddObj(RandomAdp_.Gfs, "RandomAdp_"); + core.AddObj(GfoTemplateFactory.Instance, "factory"); + core.AddObj(GfoRegy.Instance, "GfoRegy_"); + core.AddObj(GfsCore.Instance, "GfsCore_"); + core.AddObj(gplx.core.ios.IoUrlInfoRegy.Instance, "IoUrlInfoRegy_"); + core.AddObj(gplx.core.ios.IoUrlTypeRegy.Instance, "IoUrlTypeRegy_"); + + GfoRegy.Instance.Parsers().Add("Io_url", Io_url_.Parser); + } + public static final GfsLibIni_core Instance = new GfsLibIni_core(); GfsLibIni_core() {} +} diff --git a/100_core/src/gplx/langs/gfs/GfsRegy.java b/100_core/src/gplx/langs/gfs/GfsRegy.java index a27517de8..8718fd634 100644 --- a/100_core/src/gplx/langs/gfs/GfsRegy.java +++ b/100_core/src/gplx/langs/gfs/GfsRegy.java @@ -13,3 +13,40 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +class GfsRegy implements Gfo_invk { + public int Count() {return hash.Count();} + public void Clear() {hash.Clear(); typeHash.Clear();} + public boolean Has(String k) {return hash.Has(k);} + public GfsRegyItm Get_at(int i) {return (GfsRegyItm)hash.Get_at(i);} + public GfsRegyItm Get_by(String key) {return (GfsRegyItm)hash.Get_by(key);} + public GfsRegyItm FetchByType(Gfo_invk invk) {return (GfsRegyItm)typeHash.Get_by(Type_.Canonical_name_by_obj(invk));} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + Object rv = (GfsRegyItm)hash.Get_by(k); if (rv == null) throw Err_.new_missing_key(k); + return rv; + } + public void AddObj(Gfo_invk invk, String key) {Add(key, invk, false);} + public void AddCmd(Gfo_invk invk, String key) {Add(key, invk, true);} + public void Add(String key, Gfo_invk invk, boolean typeCmd) { + if (hash.Has(key)) return; + GfsRegyItm regyItm = new GfsRegyItm().Key_(key).InvkAble_(invk).IsCmd_(typeCmd).TypeKey_(Type_.Canonical_name_by_obj(invk)); + hash.Add(key, regyItm); + typeHash.Add_if_dupe_use_1st(regyItm.TypeKey(), regyItm); // NOTE: changed to allow same Object to be added under different aliases (app, xowa) DATE:2014-06-09; + } + public void Del(String k) { + GfsRegyItm itm =(GfsRegyItm)hash.Get_by(k); + if (itm != null) typeHash.Del(itm.TypeKey()); + hash.Del(k); + } + Hash_adp typeHash = Hash_adp_.New(); + Ordered_hash hash = Ordered_hash_.New(); + public static GfsRegy new_() {return new GfsRegy();} GfsRegy() {} +} +class GfsRegyItm implements Gfo_invk { + public String Key() {return key;} public GfsRegyItm Key_(String v) {key = v; return this;} private String key; + public String TypeKey() {return typeKey;} public GfsRegyItm TypeKey_(String v) {typeKey = v; return this;} private String typeKey; + public boolean IsCmd() {return isCmd;} public GfsRegyItm IsCmd_(boolean v) {isCmd = v; return this;} private boolean isCmd; + public Gfo_invk InvkAble() {return invkAble;} public GfsRegyItm InvkAble_(Gfo_invk v) {invkAble = v; return this;} Gfo_invk invkAble; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return invkAble.Invk(ctx, ikey, k, m);} + public static GfsRegyItm as_(Object obj) {return obj instanceof GfsRegyItm ? (GfsRegyItm)obj : null;} +} diff --git a/100_core/src/gplx/langs/gfs/GfsTypeNames.java b/100_core/src/gplx/langs/gfs/GfsTypeNames.java index a27517de8..8a9fb096f 100644 --- a/100_core/src/gplx/langs/gfs/GfsTypeNames.java +++ b/100_core/src/gplx/langs/gfs/GfsTypeNames.java @@ -13,3 +13,14 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +public class GfsTypeNames { + public static final String + String = "string" + , Int = "int" + , Bool = "bool" + , Float = "float" + , YesNo = "yn" + , Date = "date" + ; +} diff --git a/100_core/src/gplx/langs/gfs/Gfs_Date_tst.java b/100_core/src/gplx/langs/gfs/Gfs_Date_tst.java index a27517de8..c6a6ea836 100644 --- a/100_core/src/gplx/langs/gfs/Gfs_Date_tst.java +++ b/100_core/src/gplx/langs/gfs/Gfs_Date_tst.java @@ -13,3 +13,28 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +import org.junit.*; +public class Gfs_Date_tst { + @Before public void setup() { + fx = new GfsCoreFxt(); + fx.AddObj(DateAdp_.Gfs, "Date_"); + Datetime_now.Manual_y_(); + } GfsCoreFxt fx; + @Test public void Now() { + fx.tst_MsgStr(fx.msg_(String_.Ary("Date_", "Now")), DateAdp_.parse_gplx("2001-01-01 00:00:00.000")); + } + @Test public void Add_day() { + fx.tst_MsgStr(fx.msg_(String_.Ary("Date_", "Now", "Add_day"), Keyval_.new_("days", 1)), DateAdp_.parse_gplx("2001-01-02 00:00:00.000")); + } +} +class GfsCoreFxt { + public GfsCore Core() {return core;} GfsCore core = GfsCore.new_(); + public GfoMsg msg_(String[] ary, Keyval... kvAry) {return GfoMsg_.root_leafArgs_(ary, kvAry);} + public void AddObj(Gfo_invk invk, String s) {core.AddObj(invk, s);} + public void tst_MsgStr(GfoMsg msg, Object expd) { + GfsCtx ctx = GfsCtx.new_(); + Object actl = core.ExecOne(ctx, msg); + Tfds.Eq(Object_.Xto_str_strict_or_null_mark(expd), Object_.Xto_str_strict_or_null_mark(actl)); + } +} diff --git a/100_core/src/gplx/langs/htmls/Url_encoder_interface.java b/100_core/src/gplx/langs/htmls/Url_encoder_interface.java index a27517de8..6873ebe7d 100644 --- a/100_core/src/gplx/langs/htmls/Url_encoder_interface.java +++ b/100_core/src/gplx/langs/htmls/Url_encoder_interface.java @@ -13,3 +13,8 @@ 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.langs.htmls; import gplx.*; import gplx.langs.*; +public interface Url_encoder_interface { + String Encode_str(String v); + byte[] Encode_bry(String v); +} diff --git a/100_core/src/gplx/langs/htmls/Url_encoder_interface_same.java b/100_core/src/gplx/langs/htmls/Url_encoder_interface_same.java index a27517de8..4ef2d01f9 100644 --- a/100_core/src/gplx/langs/htmls/Url_encoder_interface_same.java +++ b/100_core/src/gplx/langs/htmls/Url_encoder_interface_same.java @@ -13,3 +13,9 @@ 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.langs.htmls; import gplx.*; import gplx.langs.*; +public class Url_encoder_interface_same implements Url_encoder_interface { + public String Encode_str(String v) {return v;} + public byte[] Encode_bry(String v) {return Bry_.new_u8(v);} + public static final Url_encoder_interface_same Instance = new Url_encoder_interface_same(); Url_encoder_interface_same() {} +} diff --git a/100_core/src/gplx/langs/htmls/entitys/Gfh_entity_.java b/100_core/src/gplx/langs/htmls/entitys/Gfh_entity_.java index a27517de8..a6712ebf1 100644 --- a/100_core/src/gplx/langs/htmls/entitys/Gfh_entity_.java +++ b/100_core/src/gplx/langs/htmls/entitys/Gfh_entity_.java @@ -13,3 +13,21 @@ 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.langs.htmls.entitys; import gplx.*; import gplx.langs.*; import gplx.langs.htmls.*; +public class Gfh_entity_ { + public static final String + Nl_str = " " + ; + public static final byte[] + Lt_bry = Bry_.new_a7("<"), Gt_bry = Bry_.new_a7(">") + , Amp_bry = Bry_.new_a7("&"), Quote_bry = Bry_.new_a7(""") + , Apos_num_bry = Bry_.new_a7("'") + , Apos_key_bry = Bry_.new_a7("'") + , Eq_bry = Bry_.new_a7("=") + , Nl_bry = Bry_.new_a7(Nl_str), Cr_bry = Bry_.new_a7(" "), Tab_bry = Bry_.new_a7(" "), Space_bry = Bry_.new_a7(" ") + , Pipe_bry = Bry_.new_a7("|") + , Colon_bry = Bry_.new_a7(":"), Underline_bry = Bry_.new_a7("_"), Asterisk_bry = Bry_.new_a7("*") + , Brack_bgn_bry = Bry_.new_a7("["), Brack_end_bry = Bry_.new_a7("]") + , Nbsp_num_bry = Bry_.new_a7(" ") + ; +} diff --git a/100_core/src/gplx/langs/htmls/entitys/Gfh_entity_itm.java b/100_core/src/gplx/langs/htmls/entitys/Gfh_entity_itm.java index a27517de8..281d4b879 100644 --- a/100_core/src/gplx/langs/htmls/entitys/Gfh_entity_itm.java +++ b/100_core/src/gplx/langs/htmls/entitys/Gfh_entity_itm.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.langs.htmls.entitys; import gplx.*; import gplx.langs.*; import gplx.langs.htmls.*; +public class Gfh_entity_itm { // TS:immutable + public Gfh_entity_itm(byte tid, int char_int, byte[] xml_name_bry) { + this.tid = tid; + this.char_int = char_int; + this.u8_bry = gplx.core.intls.Utf16_.Encode_int_to_bry(char_int); + this.xml_name_bry = xml_name_bry; + this.key_name_len = xml_name_bry.length - 2; // 2 for & and ; + } + public byte Tid() {return tid;} private final byte tid; + public int Char_int() {return char_int;} private final int char_int; // val; EX: 160 + public byte[] U8_bry() {return u8_bry;} private final byte[] u8_bry; // EX: new byte[] {192, 160}; (C2, A0) + public byte[] Xml_name_bry() {return xml_name_bry;} private final byte[] xml_name_bry; // EX: " " + public int Key_name_len() {return key_name_len;} private final int key_name_len; // EX: "nbsp".Len + + public void Print_ncr(Bry_bfr bfr) { + switch (char_int) { + case Byte_ascii.Lt: case Byte_ascii.Gt: case Byte_ascii.Quote: case Byte_ascii.Amp: + bfr.Add(xml_name_bry); // NOTE: never write actual char; EX: "<" should be written as "<", not "<" + break; + default: + bfr.Add(Escape_bgn); // &# + bfr.Add_int_variable(char_int); // 160 + bfr.Add_byte(Byte_ascii.Semic); // ; + break; + } + } + public void Print_literal(Bry_bfr bfr) { + switch (char_int) { + case Byte_ascii.Lt: bfr.Add(Gfh_entity_.Lt_bry); break; // NOTE: never write actual char; EX: "<" should be written as "<", not "<"; MW does same; DATE:2014-11-07 + case Byte_ascii.Gt: bfr.Add(Gfh_entity_.Gt_bry); break; + case Byte_ascii.Quote: bfr.Add(Gfh_entity_.Quote_bry); break; + case Byte_ascii.Amp: bfr.Add(Gfh_entity_.Amp_bry); break; + default: bfr.Add(u8_bry); break; // write literal; EX: "[" not "[" + } + } + private static final byte[] Escape_bgn = Bry_.new_a7("&#"); + public static final byte Tid_name_std = 1, Tid_name_xowa = 2, Tid_num_hex = 3, Tid_num_dec = 4; + public static final int Char_int_null = -1; +} diff --git a/100_core/src/gplx/langs/htmls/entitys/Gfh_entity_trie.java b/100_core/src/gplx/langs/htmls/entitys/Gfh_entity_trie.java index a27517de8..142c31541 100644 --- a/100_core/src/gplx/langs/htmls/entitys/Gfh_entity_trie.java +++ b/100_core/src/gplx/langs/htmls/entitys/Gfh_entity_trie.java @@ -13,3 +13,303 @@ 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.langs.htmls.entitys; import gplx.*; import gplx.langs.*; import gplx.langs.htmls.*; +import gplx.core.btries.*; +public class Gfh_entity_trie { // TS + public static final String // NOTE: top_define; entities needed for escaping + Str__xowa_lt = "&xowa_lt;" + , Str__xowa_brack_bgn = "&xowa_brack_bgn;" + , Str__xowa_brack_end = "&xowa_brack_end;" + , Str__xowa_pipe = "&xowa_pipe;" + , Str__xowa_apos = "&xowa_apos;" + , Str__xowa_colon = "&xowa_colon;" + , Str__xowa_underline = "&xowa_underline;" + , Str__xowa_asterisk = "&xowa_asterisk;" + , Str__xowa_space = "&xowa_space;" + , Str__xowa_nl = "&xowa_nl;" + , Str__xowa_dash = "&xowa_dash;" + ; + public static final Btrie_slim_mgr Instance = New(); Gfh_entity_trie() {} + private static Btrie_slim_mgr New() {// REF.MW: Sanitizer|$wgHtmlEntities; NOTE:added apos + Btrie_slim_mgr rv = Btrie_slim_mgr.cs(); + Add_name(rv, Bool_.Y, 60, Str__xowa_lt); + Add_name(rv, Bool_.Y, 91, Str__xowa_brack_bgn); + Add_name(rv, Bool_.Y, 93, Str__xowa_brack_end); + Add_name(rv, Bool_.Y, 124, Str__xowa_pipe); + Add_name(rv, Bool_.Y, 39, Str__xowa_apos); + Add_name(rv, Bool_.Y, 58, Str__xowa_colon); + Add_name(rv, Bool_.Y, 95, Str__xowa_underline); + Add_name(rv, Bool_.Y, 42, Str__xowa_asterisk); + Add_name(rv, Bool_.Y, 32, Str__xowa_space); + Add_name(rv, Bool_.Y, 10, Str__xowa_nl); + Add_name(rv, Bool_.Y, 45, Str__xowa_dash); + Add_name(rv, Bool_.N, 39, "'"); + Add_name(rv, Bool_.N, 193, "Á"); + Add_name(rv, Bool_.N, 225, "á"); + Add_name(rv, Bool_.N, 194, "Â"); + Add_name(rv, Bool_.N, 226, "â"); + Add_name(rv, Bool_.N, 180, "´"); + Add_name(rv, Bool_.N, 198, "Æ"); + Add_name(rv, Bool_.N, 230, "æ"); + Add_name(rv, Bool_.N, 192, "À"); + Add_name(rv, Bool_.N, 224, "à"); + Add_name(rv, Bool_.N, 8501, "ℵ"); + Add_name(rv, Bool_.N, 913, "Α"); + Add_name(rv, Bool_.N, 945, "α"); + Add_name(rv, Bool_.N, 38, "&"); + Add_name(rv, Bool_.N, 8743, "∧"); + Add_name(rv, Bool_.N, 8736, "∠"); + Add_name(rv, Bool_.N, 197, "Å"); + Add_name(rv, Bool_.N, 229, "å"); + Add_name(rv, Bool_.N, 8776, "≈"); + Add_name(rv, Bool_.N, 195, "Ã"); + Add_name(rv, Bool_.N, 227, "ã"); + Add_name(rv, Bool_.N, 196, "Ä"); + Add_name(rv, Bool_.N, 228, "ä"); + Add_name(rv, Bool_.N, 8222, "„"); + Add_name(rv, Bool_.N, 914, "Β"); + Add_name(rv, Bool_.N, 946, "β"); + Add_name(rv, Bool_.N, 166, "¦"); + Add_name(rv, Bool_.N, 8226, "•"); + Add_name(rv, Bool_.N, 8745, "∩"); + Add_name(rv, Bool_.N, 199, "Ç"); + Add_name(rv, Bool_.N, 231, "ç"); + Add_name(rv, Bool_.N, 184, "¸"); + Add_name(rv, Bool_.N, 162, "¢"); + Add_name(rv, Bool_.N, 935, "Χ"); + Add_name(rv, Bool_.N, 967, "χ"); + Add_name(rv, Bool_.N, 710, "ˆ"); + Add_name(rv, Bool_.N, 9827, "♣"); + Add_name(rv, Bool_.N, 8773, "≅"); + Add_name(rv, Bool_.N, 169, "©"); + Add_name(rv, Bool_.N, 8629, "↵"); + Add_name(rv, Bool_.N, 8746, "∪"); + Add_name(rv, Bool_.N, 164, "¤"); + Add_name(rv, Bool_.N, 8224, "†"); + Add_name(rv, Bool_.N, 8225, "‡"); + Add_name(rv, Bool_.N, 8595, "↓"); + Add_name(rv, Bool_.N, 8659, "⇓"); + Add_name(rv, Bool_.N, 176, "°"); + Add_name(rv, Bool_.N, 916, "Δ"); + Add_name(rv, Bool_.N, 948, "δ"); + Add_name(rv, Bool_.N, 9830, "♦"); + Add_name(rv, Bool_.N, 247, "÷"); + Add_name(rv, Bool_.N, 201, "É"); + Add_name(rv, Bool_.N, 233, "é"); + Add_name(rv, Bool_.N, 202, "Ê"); + Add_name(rv, Bool_.N, 234, "ê"); + Add_name(rv, Bool_.N, 200, "È"); + Add_name(rv, Bool_.N, 232, "è"); + Add_name(rv, Bool_.N, 8709, "∅"); + Add_name(rv, Bool_.N, 8195, " "); + Add_name(rv, Bool_.N, 8194, " "); + Add_name(rv, Bool_.N, 917, "Ε"); + Add_name(rv, Bool_.N, 949, "ε"); + Add_name(rv, Bool_.N, 8801, "≡"); + Add_name(rv, Bool_.N, 919, "Η"); + Add_name(rv, Bool_.N, 951, "η"); + Add_name(rv, Bool_.N, 208, "Ð"); + Add_name(rv, Bool_.N, 240, "ð"); + Add_name(rv, Bool_.N, 203, "Ë"); + Add_name(rv, Bool_.N, 235, "ë"); + Add_name(rv, Bool_.N, 8364, "€"); + Add_name(rv, Bool_.N, 8707, "∃"); + Add_name(rv, Bool_.N, 402, "ƒ"); + Add_name(rv, Bool_.N, 8704, "∀"); + Add_name(rv, Bool_.N, 189, "½"); + Add_name(rv, Bool_.N, 188, "¼"); + Add_name(rv, Bool_.N, 190, "¾"); + Add_name(rv, Bool_.N, 8260, "⁄"); + Add_name(rv, Bool_.N, 915, "Γ"); + Add_name(rv, Bool_.N, 947, "γ"); + Add_name(rv, Bool_.N, 8805, "≥"); + Add_name(rv, Bool_.N, 62, ">"); + Add_name(rv, Bool_.N, 8596, "↔"); + Add_name(rv, Bool_.N, 8660, "⇔"); + Add_name(rv, Bool_.N, 9829, "♥"); + Add_name(rv, Bool_.N, 8230, "…"); + Add_name(rv, Bool_.N, 205, "Í"); + Add_name(rv, Bool_.N, 237, "í"); + Add_name(rv, Bool_.N, 206, "Î"); + Add_name(rv, Bool_.N, 238, "î"); + Add_name(rv, Bool_.N, 161, "¡"); + Add_name(rv, Bool_.N, 204, "Ì"); + Add_name(rv, Bool_.N, 236, "ì"); + Add_name(rv, Bool_.N, 8465, "ℑ"); + Add_name(rv, Bool_.N, 8734, "∞"); + Add_name(rv, Bool_.N, 8747, "∫"); + Add_name(rv, Bool_.N, 921, "Ι"); + Add_name(rv, Bool_.N, 953, "ι"); + Add_name(rv, Bool_.N, 191, "¿"); + Add_name(rv, Bool_.N, 8712, "∈"); + Add_name(rv, Bool_.N, 207, "Ï"); + Add_name(rv, Bool_.N, 239, "ï"); + Add_name(rv, Bool_.N, 922, "Κ"); + Add_name(rv, Bool_.N, 954, "κ"); + Add_name(rv, Bool_.N, 923, "Λ"); + Add_name(rv, Bool_.N, 955, "λ"); + Add_name(rv, Bool_.N, 9001, "⟨"); + Add_name(rv, Bool_.N, 171, "«"); + Add_name(rv, Bool_.N, 8592, "←"); + Add_name(rv, Bool_.N, 8656, "⇐"); + Add_name(rv, Bool_.N, 8968, "⌈"); + Add_name(rv, Bool_.N, 8220, "“"); + Add_name(rv, Bool_.N, 8804, "≤"); + Add_name(rv, Bool_.N, 8970, "⌊"); + Add_name(rv, Bool_.N, 8727, "∗"); + Add_name(rv, Bool_.N, 9674, "◊"); + Add_name(rv, Bool_.N, 8206, "‎"); + Add_name(rv, Bool_.N, 8249, "‹"); + Add_name(rv, Bool_.N, 8216, "‘"); + Add_name(rv, Bool_.N, 60, "<"); + Add_name(rv, Bool_.N, 175, "¯"); + Add_name(rv, Bool_.N, 8212, "—"); + Add_name(rv, Bool_.N, 181, "µ"); + Add_name(rv, Bool_.N, 183, "·"); + Add_name(rv, Bool_.N, 8722, "−"); + Add_name(rv, Bool_.N, 924, "Μ"); + Add_name(rv, Bool_.N, 956, "μ"); + Add_name(rv, Bool_.N, 8711, "∇"); + Add_name(rv, Bool_.N, 160, " "); + Add_name(rv, Bool_.N, 8211, "–"); + Add_name(rv, Bool_.N, 8800, "≠"); + Add_name(rv, Bool_.N, 8715, "∋"); + Add_name(rv, Bool_.N, 172, "¬"); + Add_name(rv, Bool_.N, 8713, "∉"); + Add_name(rv, Bool_.N, 8836, "⊄"); + Add_name(rv, Bool_.N, 209, "Ñ"); + Add_name(rv, Bool_.N, 241, "ñ"); + Add_name(rv, Bool_.N, 925, "Ν"); + Add_name(rv, Bool_.N, 957, "ν"); + Add_name(rv, Bool_.N, 211, "Ó"); + Add_name(rv, Bool_.N, 243, "ó"); + Add_name(rv, Bool_.N, 212, "Ô"); + Add_name(rv, Bool_.N, 244, "ô"); + Add_name(rv, Bool_.N, 338, "Œ"); + Add_name(rv, Bool_.N, 339, "œ"); + Add_name(rv, Bool_.N, 210, "Ò"); + Add_name(rv, Bool_.N, 242, "ò"); + Add_name(rv, Bool_.N, 8254, "‾"); + Add_name(rv, Bool_.N, 937, "Ω"); + Add_name(rv, Bool_.N, 969, "ω"); + Add_name(rv, Bool_.N, 927, "Ο"); + Add_name(rv, Bool_.N, 959, "ο"); + Add_name(rv, Bool_.N, 8853, "⊕"); + Add_name(rv, Bool_.N, 8744, "∨"); + Add_name(rv, Bool_.N, 170, "ª"); + Add_name(rv, Bool_.N, 186, "º"); + Add_name(rv, Bool_.N, 216, "Ø"); + Add_name(rv, Bool_.N, 248, "ø"); + Add_name(rv, Bool_.N, 213, "Õ"); + Add_name(rv, Bool_.N, 245, "õ"); + Add_name(rv, Bool_.N, 8855, "⊗"); + Add_name(rv, Bool_.N, 214, "Ö"); + Add_name(rv, Bool_.N, 246, "ö"); + Add_name(rv, Bool_.N, 182, "¶"); + Add_name(rv, Bool_.N, 8706, "∂"); + Add_name(rv, Bool_.N, 8240, "‰"); + Add_name(rv, Bool_.N, 8869, "⊥"); + Add_name(rv, Bool_.N, 934, "Φ"); + Add_name(rv, Bool_.N, 966, "φ"); + Add_name(rv, Bool_.N, 928, "Π"); + Add_name(rv, Bool_.N, 960, "π"); + Add_name(rv, Bool_.N, 982, "ϖ"); + Add_name(rv, Bool_.N, 177, "±"); + Add_name(rv, Bool_.N, 163, "£"); + Add_name(rv, Bool_.N, 8242, "′"); + Add_name(rv, Bool_.N, 8243, "″"); + Add_name(rv, Bool_.N, 8719, "∏"); + Add_name(rv, Bool_.N, 8733, "∝"); + Add_name(rv, Bool_.N, 936, "Ψ"); + Add_name(rv, Bool_.N, 968, "ψ"); + Add_name(rv, Bool_.N, 34, """); + Add_name(rv, Bool_.N, 8730, "√"); + Add_name(rv, Bool_.N, 9002, "⟩"); + Add_name(rv, Bool_.N, 187, "»"); + Add_name(rv, Bool_.N, 8594, "→"); + Add_name(rv, Bool_.N, 8658, "⇒"); + Add_name(rv, Bool_.N, 8969, "⌉"); + Add_name(rv, Bool_.N, 8221, "”"); + Add_name(rv, Bool_.N, 8476, "ℜ"); + Add_name(rv, Bool_.N, 174, "®"); + Add_name(rv, Bool_.N, 8971, "⌋"); + Add_name(rv, Bool_.N, 929, "Ρ"); + Add_name(rv, Bool_.N, 961, "ρ"); + Add_name(rv, Bool_.N, 8207, "‏"); + Add_name(rv, Bool_.N, 8250, "›"); + Add_name(rv, Bool_.N, 8217, "’"); + Add_name(rv, Bool_.N, 8218, "‚"); + Add_name(rv, Bool_.N, 352, "Š"); + Add_name(rv, Bool_.N, 353, "š"); + Add_name(rv, Bool_.N, 8901, "⋅"); + Add_name(rv, Bool_.N, 167, "§"); + Add_name(rv, Bool_.N, 173, "­"); + Add_name(rv, Bool_.N, 931, "Σ"); + Add_name(rv, Bool_.N, 963, "σ"); + Add_name(rv, Bool_.N, 962, "ς"); + Add_name(rv, Bool_.N, 8764, "∼"); + Add_name(rv, Bool_.N, 9824, "♠"); + Add_name(rv, Bool_.N, 8834, "⊂"); + Add_name(rv, Bool_.N, 8838, "⊆"); + Add_name(rv, Bool_.N, 8721, "∑"); + Add_name(rv, Bool_.N, 8835, "⊃"); + Add_name(rv, Bool_.N, 185, "¹"); + Add_name(rv, Bool_.N, 178, "²"); + Add_name(rv, Bool_.N, 179, "³"); + Add_name(rv, Bool_.N, 8839, "⊇"); + Add_name(rv, Bool_.N, 223, "ß"); + Add_name(rv, Bool_.N, 932, "Τ"); + Add_name(rv, Bool_.N, 964, "τ"); + Add_name(rv, Bool_.N, 8756, "∴"); + Add_name(rv, Bool_.N, 920, "Θ"); + Add_name(rv, Bool_.N, 952, "θ"); + Add_name(rv, Bool_.N, 977, "ϑ"); + Add_name(rv, Bool_.N, 8201, " "); + Add_name(rv, Bool_.N, 222, "Þ"); + Add_name(rv, Bool_.N, 254, "þ"); + Add_name(rv, Bool_.N, 732, "˜"); + Add_name(rv, Bool_.N, 215, "×"); + Add_name(rv, Bool_.N, 8482, "™"); + Add_name(rv, Bool_.N, 218, "Ú"); + Add_name(rv, Bool_.N, 250, "ú"); + Add_name(rv, Bool_.N, 8593, "↑"); + Add_name(rv, Bool_.N, 8657, "⇑"); + Add_name(rv, Bool_.N, 219, "Û"); + Add_name(rv, Bool_.N, 251, "û"); + Add_name(rv, Bool_.N, 217, "Ù"); + Add_name(rv, Bool_.N, 249, "ù"); + Add_name(rv, Bool_.N, 168, "¨"); + Add_name(rv, Bool_.N, 978, "ϒ"); + Add_name(rv, Bool_.N, 933, "Υ"); + Add_name(rv, Bool_.N, 965, "υ"); + Add_name(rv, Bool_.N, 220, "Ü"); + Add_name(rv, Bool_.N, 252, "ü"); + Add_name(rv, Bool_.N, 8472, "℘"); + Add_name(rv, Bool_.N, 926, "Ξ"); + Add_name(rv, Bool_.N, 958, "ξ"); + Add_name(rv, Bool_.N, 221, "Ý"); + Add_name(rv, Bool_.N, 253, "ý"); + Add_name(rv, Bool_.N, 165, "¥"); + Add_name(rv, Bool_.N, 376, "Ÿ"); + Add_name(rv, Bool_.N, 255, "ÿ"); + Add_name(rv, Bool_.N, 918, "Ζ"); + Add_name(rv, Bool_.N, 950, "ζ"); + Add_name(rv, Bool_.N, 8205, "‍"); + Add_name(rv, Bool_.N, 8204, "‌"); + Add_prefix(rv, Gfh_entity_itm.Tid_num_hex, "#x"); + Add_prefix(rv, Gfh_entity_itm.Tid_num_hex, "#X"); + Add_prefix(rv, Gfh_entity_itm.Tid_num_dec, "#"); + return rv; + } + private static void Add_name(Btrie_slim_mgr trie, boolean tid_is_xowa, int char_int, String xml_name_str) { + byte itm_tid = tid_is_xowa ? Gfh_entity_itm.Tid_name_xowa : Gfh_entity_itm.Tid_name_std; + byte[] xml_name_bry = Bry_.new_a7(xml_name_str); + byte[] key = Bry_.Mid(xml_name_bry, 1, xml_name_bry.length); // ignore & for purpose of trie; EX: "amp;"; NOTE: must keep trailing ";" else "& " will be valid; + trie.Add_obj(key, new Gfh_entity_itm(itm_tid, char_int, xml_name_bry)); + } + private static void Add_prefix(Btrie_slim_mgr trie, byte prefix_type, String prefix) { + byte[] prefix_ary = Bry_.new_u8(prefix); + Gfh_entity_itm itm = new Gfh_entity_itm(prefix_type, Gfh_entity_itm.Char_int_null, prefix_ary); + trie.Add_obj(prefix_ary, itm); + } +} diff --git a/100_core/src/gplx/langs/regxs/Regx_adp.java b/100_core/src/gplx/langs/regxs/Regx_adp.java index a27517de8..b0b240aef 100644 --- a/100_core/src/gplx/langs/regxs/Regx_adp.java +++ b/100_core/src/gplx/langs/regxs/Regx_adp.java @@ -13,3 +13,54 @@ 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.langs.regxs; import gplx.*; import gplx.langs.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +public class Regx_adp { + @gplx.Internal protected Regx_adp(String regx) {Pattern_(regx);} + public String Pattern() {return pattern;} public Regx_adp Pattern_(String val) {pattern = val; Under_sync(); return this;} private String pattern; + public boolean Pattern_is_invalid() {return pattern_is_invalid;} private boolean pattern_is_invalid = false; + public Exception Pattern_is_invalid_exception() {return pattern_is_invalid_exception;} private Exception pattern_is_invalid_exception = null; + public Regx_match[] Match_all(String text, int bgn) { + int idx = bgn; + List_adp rv = List_adp_.New(); + int len = String_.Len(text); + while (idx <= len) { // NOTE: must be <= not < else "a?" will return null instead of ""; PAGE:en.d:民; DATE:2015-01-30 + Regx_match match = this.Match(text, idx); + if (match.Rslt_none()) break; + rv.Add(match); + int find_bgn = match.Find_bgn(); + int find_len = match.Find_len(); + idx = find_len == 0 // find_bgn == find_end + ? find_bgn + 1 // add 1 to resume search from next char; DATE:2014-09-02 + : find_bgn + find_len // otherwise search after find_end + ; + } + return (Regx_match[])rv.To_ary(Regx_match.class); + } + private Pattern under; + public Pattern Under() {return under;} + private void Under_sync() { + try {under = Pattern.compile(pattern, Pattern.DOTALL | Pattern.UNICODE_CHARACTER_CLASS);} // JRE.7:UNICODE_CHARACTER_CLASS; added during %w fix for en.w:A#; DATE:2015-06-10 + catch (Exception e) { // NOTE: if invalid, then default to empty pattern (which should return nothing); EX:d:〆る generates [^]; DATE:2013-10-20 + pattern_is_invalid = true; + pattern_is_invalid_exception = e; + under = Pattern.compile("", Pattern.DOTALL | Pattern.UNICODE_CHARACTER_CLASS); + } + } + public Regx_match Match(String input, int bgn) { + Matcher match = under.matcher(input); + boolean success = match.find(bgn); + int match_bgn = success ? match.start() : String_.Find_none; + int match_end = success ? match.end() : String_.Find_none; + Regx_group[] ary = Regx_group.Ary_empty; + int groups_len = match.groupCount(); + if (success && groups_len > 0) { + ary = new Regx_group[groups_len]; + for (int i = 0; i < groups_len; i++) + ary[i] = new Regx_group(true, match.start(i + 1), match.end(i + 1), match.group(i + 1)); + } + return new Regx_match(success, match_bgn, match_end, ary); + } + public String ReplaceAll(String input, String replace) {return under.matcher(input).replaceAll(replace);} + } diff --git a/100_core/src/gplx/langs/regxs/Regx_adp_.java b/100_core/src/gplx/langs/regxs/Regx_adp_.java index a27517de8..76bb29224 100644 --- a/100_core/src/gplx/langs/regxs/Regx_adp_.java +++ b/100_core/src/gplx/langs/regxs/Regx_adp_.java @@ -13,3 +13,29 @@ 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.langs.regxs; import gplx.*; import gplx.langs.*; +public class Regx_adp_ { + public static Regx_adp new_(String pattern) {return new Regx_adp(pattern);} + public static List_adp Find_all(String input, String find) { + Regx_adp regx = Regx_adp_.new_(find); + int idx = 0; + List_adp rv = List_adp_.New(); + while (true) { + Regx_match match = regx.Match(input, idx); + if (match.Rslt_none()) break; + rv.Add(match); + int findBgn = match.Find_bgn(); + idx = findBgn + match.Find_len(); + if (idx > String_.Len(input)) break; + } + return rv; + } + public static String Replace(String raw, String regx_str, String replace) { + Regx_adp regx = Regx_adp_.new_(regx_str); + return regx.ReplaceAll(raw, replace); + } + public static boolean Match(String input, String pattern) { + Regx_adp rv = new Regx_adp(pattern); + return rv.Match(input, 0).Rslt(); + } +} diff --git a/100_core/src/gplx/langs/regxs/Regx_adp__tst.java b/100_core/src/gplx/langs/regxs/Regx_adp__tst.java index a27517de8..a9ebfd8fb 100644 --- a/100_core/src/gplx/langs/regxs/Regx_adp__tst.java +++ b/100_core/src/gplx/langs/regxs/Regx_adp__tst.java @@ -13,3 +13,79 @@ 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.langs.regxs; import gplx.*; import gplx.langs.*; +import org.junit.*; import gplx.core.tests.*; +public class Regx_adp__tst implements TfdsEqListItmStr { + @Test public void Match() { + tst_Match("a", "a", true); // basic + tst_Match("a", "b", false); // matchNot + tst_Match("a", "ab", true); // matchPart + tst_Match("a\\+b", "a+b", true); // matchEscape + tst_Match("[^a]", "b", true); // charSet_negate + } void tst_Match(String find, String input, boolean expd) {Tfds.Eq(expd, Regx_adp_.Match(input, find));} + @Test public void Match_all() { + tst_Match_all("#REDIRECT [[Template:Error]]", "^\\p{Nd}*", 1); // handle match = true but len = 0; DATE:2013-04-11 + tst_Match_all("a", "$", 1); // $ should match once, not zero; DATE:2014-09-02 + } void tst_Match_all(String input, String regx, int expd) {Tfds.Eq(expd, Regx_adp_.new_(regx).Match_all(input, 0).length);} + @Test public void Replace() { + tst_Replace("ab", "a", "b", "bb"); // basic + tst_Replace("ab", "c", "b", "ab"); // replaceNot + tst_Replace("aba", "a", "b", "bbb"); // replaceMultiple + } void tst_Replace(String input, String find, String replace, String expd) {Tfds.Eq(expd, Regx_adp_.Replace(input, find, replace));} + @Test public void Match_WholeWord() { + tst_WholeWord("a", "ab a", true); // pass a + tst_WholeWord("a", "ab c", false); // fail ab + tst_WholeWord("a", "a_", false); // fail a_ + tst_WholeWord("[a]", "a [a] c", true); // pass [a] + tst_WholeWord("[a]", "a[a]c", false); // fail a[a]c + } void tst_WholeWord(String regx, String text, boolean expd) {Tfds.Eq(expd, Regx_adp_.Match(text, Regx_bldr.WholeWord(regx)));} + @Test public void Match_As() { + tst_Regx("public static [A-Za-z0-9_]+ as_\\(Object obj\\)", "public static Obj1 as_(Object obj) {return obj instanceof Obj1 ? (Obj1)obj : null;}", true); + tst_Regx("public static [A-Za-z0-9_]+ as_\\(Object obj\\)", "public static boolean Asterisk(Object obj) {}", false); + } void tst_Regx(String regx, String text, boolean expd) {Tfds.Eq(expd, Regx_adp_.Match(text, regx));} + @Test public void Find() { + tst_Matches("b", "a b c b a", match_(2, 1), match_(6, 1)); + tst_Matches("d", "a b c b a"); + tst_Matches("b", "a b c b a b b", matches_(2, 6, 10, 12)); // BUGFIX: multiple entries did not work b/c of += instead of + + } + @Test public void Groups() { + tst_Groups("abc def ghi dz", "(d\\p{L}+)", "def", "dz"); + } + Regx_match[] matches_(int... bgnAry) { + int aryLen = Array_.Len(bgnAry); + Regx_match[] rv = new Regx_match[aryLen]; + for (int i = 0; i < aryLen; i++) + rv[i] = match_(bgnAry[i]); + return rv; + } + Regx_match match_(int bgn) {return match_(bgn, Int_.Min_value);} + Regx_match match_(int bgn, int len) {return new Regx_match(true, bgn, bgn + len, Regx_group.Ary_empty);} + void tst_Matches(String find, String input, Regx_match... expd) { + List_adp expdList = Array_.To_list(expd); + List_adp actlList = Regx_adp_.Find_all(input, find); + Tfds.Eq_list(expdList, actlList, this); + } + void tst_Groups(String text, String regx, String... expd) { + Regx_adp regx_mgr = Regx_adp_.new_(regx); + Regx_match[] rslts = regx_mgr.Match_all(text, 0); + Tfds.Eq_ary_str(expd, To_ary(rslts)); + } + String[] To_ary(Regx_match[] ary) { + List_adp rv = List_adp_.New(); + int len = ary.length; + for (int i = 0; i < len; i++) { + Regx_match itm = ary[i]; + int cap_len = itm.Groups().length; + for (int j = 0; j < cap_len; j++) { + rv.Add(itm.Groups()[j].Val()); + } + } + return rv.To_str_ary(); + } + public String To_str(Object curObj, Object expdObj) { + Regx_match cur = (Regx_match)curObj, expd = (Regx_match)expdObj; + String rv = "bgn=" + cur.Find_bgn(); + if (expd != null && expd.Find_len() != Int_.Min_value) rv += " len=" + cur.Find_len(); + return rv; + } +} diff --git a/100_core/src/gplx/langs/regxs/Regx_bldr.java b/100_core/src/gplx/langs/regxs/Regx_bldr.java index a27517de8..f0089e3f3 100644 --- a/100_core/src/gplx/langs/regxs/Regx_bldr.java +++ b/100_core/src/gplx/langs/regxs/Regx_bldr.java @@ -13,3 +13,48 @@ 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.langs.regxs; import gplx.*; import gplx.langs.*; +import gplx.core.strings.*; +public class Regx_bldr { + public static String Includes(String characters) {return String_.Concat_any(Regx_bldr.Tkn_CharSetBegin, characters, Regx_bldr.Tkn_CharSetEnd);} + public static String Excludes(String characters) {return String_.Concat_any(Regx_bldr.Tkn_CharSetBegin, Regx_bldr.Tkn_Not, characters, Regx_bldr.Tkn_CharSetEnd);} + public static String WholeWord(String word) {return String_.Concat_any("(?= 0; i--) { + multiple *= filCountMaxs[i]; + multipleAry[i] = (idx / multiple) * multiple; // NOTE: rounds down to multiple; EX: 11 -> 10 + } + for (int i = 0; i < multipleAry.length; i++) + sb.Add_fmt(dirFmt, Int_.To_str_fmt(multipleAry[i], numFmt)); + sb.Add_fmt(filFmt, Int_.To_str_fmt(idx, numFmt)); + return sb.To_str(); + } + public HierStrBldr Ctor_io(Io_url root, String dirFmt, String filFmt, String numFmt, int... filCountMaxs) { + this.Ctor(root.Raw(), dirFmt + dirSpr, filFmt, numFmt, filCountMaxs); + return this; + } + public void Ctor(String root, String dirFmt, String filFmt, String numFmt, int... filCountMaxs) { + this.root = root; this.dirFmt = dirFmt; this.filFmt = filFmt; this.numFmt = numFmt; this.filCountMaxs = filCountMaxs; + } +} diff --git a/100_core/src/gplx/langs/xmls/HierStrBldr_tst.java b/100_core/src/gplx/langs/xmls/HierStrBldr_tst.java index a27517de8..5d5044034 100644 --- a/100_core/src/gplx/langs/xmls/HierStrBldr_tst.java +++ b/100_core/src/gplx/langs/xmls/HierStrBldr_tst.java @@ -13,3 +13,33 @@ 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.langs.xmls; import gplx.*; import gplx.langs.*; +import org.junit.*; +import gplx.core.ios.*; import gplx.core.texts.*; +public class HierStrBldr_tst { + @Before public void setup() {bldr = new HierStrBldr();} HierStrBldr bldr; + @Test public void Hier0() { + bldr.Ctor("/root/", "dir_{0}/", "idx_{0}.csv", "000"); + tst_MakeName( 0, "/root/idx_000.csv"); + tst_MakeName( 1, "/root/idx_001.csv"); + tst_MakeName(10, "/root/idx_010.csv"); + } + @Test public void Hier1() { + bldr.Ctor("/root/", "dir_{0}/", "idx_{0}.csv", "000", 10); + tst_MakeName( 0, "/root/dir_000/idx_000.csv"); + tst_MakeName( 1, "/root/dir_000/idx_001.csv"); + tst_MakeName(10, "/root/dir_010/idx_010.csv"); + } + @Test public void Hier2() { + bldr.Ctor("/root/", "dir_{0}/", "idx_{0}.csv", "000", 5, 10); + tst_MakeName( 0, "/root/dir_000/dir_000/idx_000.csv"); + tst_MakeName( 1, "/root/dir_000/dir_000/idx_001.csv"); + tst_MakeName( 10, "/root/dir_000/dir_010/idx_010.csv"); + tst_MakeName( 49, "/root/dir_000/dir_040/idx_049.csv"); + tst_MakeName( 50, "/root/dir_050/dir_050/idx_050.csv"); + tst_MakeName( 99, "/root/dir_050/dir_090/idx_099.csv"); + tst_MakeName(100, "/root/dir_100/dir_100/idx_100.csv"); + tst_MakeName(110, "/root/dir_100/dir_110/idx_110.csv"); + } + void tst_MakeName(int val, String expd) {Tfds.Eq(expd, bldr.GenStrIdxOnly(val));} +} diff --git a/100_core/src/gplx/langs/xmls/XmlAtr.java b/100_core/src/gplx/langs/xmls/XmlAtr.java index a27517de8..7352cf338 100644 --- a/100_core/src/gplx/langs/xmls/XmlAtr.java +++ b/100_core/src/gplx/langs/xmls/XmlAtr.java @@ -13,3 +13,10 @@ 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.langs.xmls; import gplx.*; import gplx.langs.*; +import org.w3c.dom.Node; +public class XmlAtr { + public String Name() {return xatr.getNodeName();} + public String Value() {return xatr.getNodeValue();} + @gplx.Internal protected XmlAtr(Node xatr) {this.xatr = xatr;} Node xatr; +} diff --git a/100_core/src/gplx/langs/xmls/XmlAtrList.java b/100_core/src/gplx/langs/xmls/XmlAtrList.java index a27517de8..ff7914c27 100644 --- a/100_core/src/gplx/langs/xmls/XmlAtrList.java +++ b/100_core/src/gplx/langs/xmls/XmlAtrList.java @@ -13,3 +13,23 @@ 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.langs.xmls; import gplx.*; import gplx.langs.*; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +public class XmlAtrList { + public int Count() {return list == null ? 0 : list.getLength();} + public String FetchValOr(String key, String or) { + Node xatr = list.getNamedItem(key); + return (xatr == null) ? or : xatr.getNodeValue(); + } + public XmlAtr Fetch(String key) { + Node xatr = list.getNamedItem(key); if (xatr == null) throw Err_.new_missing_key(key); + return new XmlAtr(xatr); + } + public XmlAtr Fetch_or_null(String key) { + Node xatr = list.getNamedItem(key); if (xatr == null) return null; + return new XmlAtr(xatr); + } + public XmlAtr Get_at(int i) {return list == null ? null : new XmlAtr(list.item(i));} + @gplx.Internal protected XmlAtrList(NamedNodeMap list) {this.list = list;} NamedNodeMap list; +} diff --git a/100_core/src/gplx/langs/xmls/XmlDoc.java b/100_core/src/gplx/langs/xmls/XmlDoc.java index a27517de8..78abe6b1f 100644 --- a/100_core/src/gplx/langs/xmls/XmlDoc.java +++ b/100_core/src/gplx/langs/xmls/XmlDoc.java @@ -13,3 +13,10 @@ 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.langs.xmls; import gplx.*; import gplx.langs.*; +import org.w3c.dom.Document; +public class XmlDoc { + public XmlNde Root() {return new XmlNde(xdoc.getDocumentElement());} + @gplx.Internal protected XmlDoc(Document xdoc) {this.xdoc = xdoc;} Document xdoc; +} +//#} \ No newline at end of file diff --git a/100_core/src/gplx/langs/xmls/XmlDoc_.java b/100_core/src/gplx/langs/xmls/XmlDoc_.java index a27517de8..a38f9d5c6 100644 --- a/100_core/src/gplx/langs/xmls/XmlDoc_.java +++ b/100_core/src/gplx/langs/xmls/XmlDoc_.java @@ -13,3 +13,39 @@ 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.langs.xmls; import gplx.*; import gplx.langs.*; +import gplx.Io_url; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +public class XmlDoc_ { + public static XmlDoc parse(String raw) {return new XmlDoc(doc_(raw));} + static Document doc_(String raw) { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder bldr = null; + try {bldr = factory.newDocumentBuilder();} + catch (ParserConfigurationException e) {throw Err_.new_exc(e, "xml", "failed to create newDocumentBuilder");} + StringReader reader = new StringReader(raw); + InputSource source = new InputSource(reader); + Document doc = null; + try {doc = bldr.parse(source);} + catch (SAXException e) {throw Err_.new_exc(e, "xml", "failed to parse xml", "raw", raw);} + catch (IOException e) {throw Err_.new_exc(e, "xml", "failed to parse xml", "raw", raw);} + return doc; + } + public static final String Err_XmlException = "gplx.xmls.XmlException"; +} +//#} \ No newline at end of file diff --git a/100_core/src/gplx/langs/xmls/XmlDoc_tst.java b/100_core/src/gplx/langs/xmls/XmlDoc_tst.java index a27517de8..0a6792344 100644 --- a/100_core/src/gplx/langs/xmls/XmlDoc_tst.java +++ b/100_core/src/gplx/langs/xmls/XmlDoc_tst.java @@ -13,3 +13,57 @@ 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.langs.xmls; import gplx.*; import gplx.langs.*; +import org.junit.*; +public class XmlDoc_tst { + String xml; XmlDoc xdoc; XmlNde xnde; + @Test public void parse() { + xml = String_.Concat(""); + xdoc = XmlDoc_.parse(xml); + Tfds.Eq("root", xdoc.Root().Name()); + Tfds.Eq(true, xdoc.Root().NdeType_element()); + } + @Test public void Xml_outer() { + xml = String_.Concat + ( "" + , "" + , "" + , "" + , "" + , "" + ); + xdoc = XmlDoc_.parse(xml); + xnde = xdoc.Root().SubNdes().Get_at(0); + Tfds.Eq("a", xnde.Name()); + Tfds.Eq("", xnde.Xml_outer()); + } + @Test public void Text_inner() { + xml = String_.Concat + ( "" + , "" + , "test me" + , "" + , "" + ); + xdoc = XmlDoc_.parse(xml); + xnde = xdoc.Root().SubNdes().Get_at(0); + Tfds.Eq("a", xnde.Name()); + Tfds.Eq("test me", xnde.Text_inner()); + } + @Test public void Atrs() { + xml = String_.Concat + ( "" + , "" + ); + xdoc = XmlDoc_.parse(xml); + XmlAtrList atrs = xdoc.Root().Atrs(); + XmlAtr atr = atrs.Get_at(1); + tst_Atr(atr, "atr1", "1"); + atr = atrs.Get_at(1); + tst_Atr(atr, "atr1", "1"); + } + void tst_Atr(XmlAtr atr, String expdName, String expdVal) { + Tfds.Eq(expdName, atr.Name()); + Tfds.Eq(expdVal, atr.Value()); + } +} diff --git a/100_core/src/gplx/langs/xmls/XmlFileSplitter.java b/100_core/src/gplx/langs/xmls/XmlFileSplitter.java index a27517de8..b1a6d6171 100644 --- a/100_core/src/gplx/langs/xmls/XmlFileSplitter.java +++ b/100_core/src/gplx/langs/xmls/XmlFileSplitter.java @@ -13,3 +13,127 @@ 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.langs.xmls; import gplx.*; import gplx.langs.*; +import gplx.core.consoles.*; +import gplx.core.ios.*; +import gplx.core.texts.*; +public class XmlFileSplitter { + public XmlFileSplitterOpts Opts() {return opts;} XmlFileSplitterOpts opts = new XmlFileSplitterOpts(); + public byte[] Hdr() {return hdr;} private byte[] hdr; + public void Clear() {hdr = null;} + public void Split(Io_url xmlUrl) { + Io_url partDir = opts.PartDir(); + byte[] xmlEndTagAry = Bry_.new_u8(opts.XmlEnd()); + byte[][] nameAry = XtoByteAry(opts.XmlNames()); + int partIdx = 0; + + // bgn reading file + XmlSplitRdr rdr = new XmlSplitRdr().Init_(xmlUrl, opts.FileSizeMax()); + + // split hdr: includes , xmlNamespaces, and any DTD headers; will be prepended to each partFile + rdr.Read(); + int findPos = FindMatchPos(rdr.CurAry(), nameAry); if (findPos == String_.Find_none) throw Err_.new_wo_type("could not find any names in first segment"); + byte[] dataAry = SplitHdr(rdr.CurAry(), findPos); + if (opts.XmlBgn() != null) + hdr = Bry_.new_u8(opts.XmlBgn()); + byte[] tempAry = new byte[0]; + int newFindPos = FindMatchPosRev(dataAry, nameAry); + findPos = (newFindPos <= findPos) ? String_.Find_none : newFindPos; + boolean first = true; + + // split files + XmlSplitWtr partWtr = new XmlSplitWtr().Init_(partDir, hdr, opts); + while (true) { + partWtr.Bgn(partIdx++); + if (opts.StatusFmt() != null) Console_adp__sys.Instance.Write_str_w_nl(String_.Format(opts.StatusFmt(), partWtr.Url().NameOnly())); + partWtr.Write(tempAry); + if (!first) { + rdr.Read(); + dataAry = rdr.CurAry(); + findPos = FindMatchPosRev(dataAry, nameAry); + } + else + first = false; + + // find last closing node + while (findPos == String_.Find_none) { + if (rdr.Done()) { + findPos = rdr.CurRead(); + break; + } + else { + partWtr.Write(dataAry); + rdr.Read(); + dataAry = rdr.CurAry(); + findPos = FindMatchPosRev(dataAry, nameAry); + } + } + + byte[][] rv = SplitRest(dataAry, findPos); + partWtr.Write(rv[0]); + tempAry = rv[1]; + boolean done = rdr.Done() && tempAry.length == 0; + if (!done) + partWtr.Write(xmlEndTagAry); + partWtr.Rls(); + if (done) break; + } + rdr.Rls(); + } + public byte[] SplitHdr(byte[] src, int findPos) { + hdr = new byte[findPos]; + Array_.Copy_to(src, 0, hdr, 0, findPos); + byte[] rv = new byte[src.length - findPos]; + Array_.Copy_to(src, findPos, rv, 0, rv.length); + return rv; + } + public byte[][] SplitRest(byte[] src, int findPos) { + byte[][] rv = new byte[2][]; + rv[0] = new byte[findPos]; + Array_.Copy_to(src, 0, rv[0], 0, findPos); + rv[1] = new byte[src.length - findPos]; + Array_.Copy_to(src, findPos, rv[1], 0, rv[1].length); + return rv; + } + public int FindMatchPos(byte[] src, byte[][] wordAry) {return FindMatchPos(src, wordAry, true);} + public int FindMatchPosRev(byte[] src, byte[][] wordAry) {return FindMatchPos(src, wordAry, false);} + int FindMatchPos(byte[] src, byte[][] wordAry, boolean fwd) { + int[] findAry = new int[wordAry.length]; + for (int i = 0; i < findAry.length; i++) + findAry[i] = fwd ? -1 : Int_.Max_value; + for (int i = 0; i < wordAry.length; i++) { // look at each word in wordAry + int srcLen = src.length, srcPos, srcEnd, srcDif; + if (fwd) {srcPos = 0; srcEnd = srcLen; srcDif = 1;} + else {srcPos = srcLen - 1; srcEnd = -1; srcDif = -1;} + while (srcPos != srcEnd) { // look at each byte in src + byte[] ary = wordAry[i]; + int aryLen = ary.length, aryPos, aryEnd, aryDif; + if (fwd) {aryPos = 0; aryEnd = aryLen; aryDif = 1;} + else {aryPos = aryLen - 1; aryEnd = -1; aryDif = -1;} + boolean found = true; + while (aryPos != aryEnd) { // look at each byte in word + int lkpPos = srcPos + aryPos; + if (lkpPos >= srcLen) {found = false; break;} // outside bounds; exit + if (ary[aryPos] != src[lkpPos]) {found = false; break;} // srcByte doesn't match wordByte; exit + aryPos += aryDif; + } + if (found) {findAry[i] = srcPos; break;} // result found; stop now and keep "best" result + srcPos += srcDif; + } + } + int best = fwd ? -1 : Int_.Max_value; + for (int find : findAry) { + if ((fwd && find > best) + || (!fwd && find < best)) + best = find; + } + if (best == Int_.Max_value) best = -1; + return best; + } + byte[][] XtoByteAry(String[] names) { + byte[][] rv = new byte[names.length][]; + for (int i = 0; i < names.length; i++) + rv[i] = Bry_.new_u8(names[i]); + return rv; + } +} diff --git a/100_core/src/gplx/langs/xmls/XmlFileSplitterOpts.java b/100_core/src/gplx/langs/xmls/XmlFileSplitterOpts.java index a27517de8..26eddd72b 100644 --- a/100_core/src/gplx/langs/xmls/XmlFileSplitterOpts.java +++ b/100_core/src/gplx/langs/xmls/XmlFileSplitterOpts.java @@ -13,3 +13,13 @@ 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.langs.xmls; import gplx.*; import gplx.langs.*; +public class XmlFileSplitterOpts { + public int FileSizeMax() {return fileSizeMax;} public XmlFileSplitterOpts FileSizeMax_(int v) {fileSizeMax = v; return this;} int fileSizeMax = 1024 * 1024; + public String[] XmlNames() {return xmlNames;} public XmlFileSplitterOpts XmlNames_(String... v) {xmlNames = v; return this;} private String[] xmlNames; + public String XmlBgn() {return xmlBgn;} public XmlFileSplitterOpts XmlBgn_(String v) {xmlBgn = v; return this;} private String xmlBgn; + public String XmlEnd() {return xmlEnd;} public XmlFileSplitterOpts XmlEnd_(String v) {xmlEnd = v; return this;} private String xmlEnd; + public Io_url PartDir() {return partDir;} public XmlFileSplitterOpts PartDir_(Io_url v) {partDir = v; return this;} Io_url partDir; + public String StatusFmt() {return statusFmt;} public XmlFileSplitterOpts StatusFmt_(String v) {statusFmt = v; return this;} private String statusFmt = "splitting {0}"; + public HierStrBldr Namer() {return namer;} HierStrBldr namer = new HierStrBldr(); +} diff --git a/100_core/src/gplx/langs/xmls/XmlFileSplitter_tst.java b/100_core/src/gplx/langs/xmls/XmlFileSplitter_tst.java index a27517de8..a2c9129b3 100644 --- a/100_core/src/gplx/langs/xmls/XmlFileSplitter_tst.java +++ b/100_core/src/gplx/langs/xmls/XmlFileSplitter_tst.java @@ -13,3 +13,74 @@ 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.langs.xmls; import gplx.*; import gplx.langs.*; +import org.junit.*; +import gplx.core.ios.*; import gplx.core.texts.*; +public class XmlFileSplitter_tst { + @Before public void setup() { + splitter = new XmlFileSplitter(); + Io_mgr.Instance.InitEngine_mem(); + } XmlFileSplitter splitter; + @Test public void FindMatchPos() { + tst_FindMatchPos("abcde", "a", 0); + tst_FindMatchPos("abcde", "b", 1); + tst_FindMatchPos("abcde", "cd", 2); + tst_FindMatchPos("abcde", "f", -1); + tst_FindMatchPos("abcde", "fg", -1); + } void tst_FindMatchPos(String src, String find, int expd) {Tfds.Eq(expd, splitter.FindMatchPos(byte_(src), byteAry_(find)));} + @Test public void FindMatchPosRev() { + tst_FindMatchPosRev("abcde", "a", 0); + tst_FindMatchPosRev("abcde", "b", 1); + tst_FindMatchPosRev("abcde", "cd", 2); + tst_FindMatchPosRev("abcde", "f", -1); + tst_FindMatchPosRev("abcde", "ef", -1); + tst_FindMatchPosRev("abcde", "za", -1); + tst_FindMatchPosRev("dbcde", "d", 3); + } void tst_FindMatchPosRev(String src, String find, int expd) {Tfds.Eq(expd, splitter.FindMatchPosRev(byte_(src), byteAry_(find)));} + @Test public void ExtractHdr() { + tst_ExtractHdr("", "", ""); + } + @Test public void Split() { + splitter.Opts().FileSizeMax_(30).XmlNames_(""); + tst_Split + ( "" + , "" + , "" + , "" + ); + tst_Split + ( "" + , "" + , "" + ); + } + void tst_Split(String txt, String... expd) { + Io_url xmlFil = Io_url_.mem_fil_("mem/800_misc/txt.xml"); + Io_url tmpDir = xmlFil.OwnerDir().GenSubDir("temp_xml"); + Io_mgr.Instance.DeleteDirDeep(tmpDir); + splitter.Opts().StatusFmt_(null).PartDir_(tmpDir); + splitter.Opts().Namer().Ctor_io(tmpDir, "", "fil_{0}.xml", "000"); + Io_mgr.Instance.SaveFilStr(xmlFil, txt); + splitter.Split(xmlFil); + Io_url[] tmpFilAry = Io_mgr.Instance.QueryDir_fils(tmpDir); + Tfds.Eq(expd.length, tmpFilAry.length); + for (int i = 0; i < tmpFilAry.length; i++) { + Io_url tmpFil = tmpFilAry[i]; + Tfds.Eq(expd[i], Io_mgr.Instance.LoadFilStr(tmpFil)); + } + } + byte[] byte_(String s) {return Bry_.new_u8(s);} + byte[][] byteAry_(String s) { + byte[][] rv = new byte[1][]; + rv[0] = Bry_.new_u8(s); + return rv; + } + void tst_ExtractHdr(String src, String find, String expdHdr, String expdSrc) { + splitter.Clear(); + byte[] srcAry = byte_(src); + int findPos = splitter.FindMatchPos(srcAry, byteAry_(find)); + srcAry = splitter.SplitHdr(srcAry, findPos); + Tfds.Eq(String_.new_u8(splitter.Hdr()), expdHdr); + Tfds.Eq(String_.new_u8(srcAry), expdSrc); + } +} diff --git a/100_core/src/gplx/langs/xmls/XmlNde.java b/100_core/src/gplx/langs/xmls/XmlNde.java index a27517de8..73a7466c5 100644 --- a/100_core/src/gplx/langs/xmls/XmlNde.java +++ b/100_core/src/gplx/langs/xmls/XmlNde.java @@ -13,3 +13,37 @@ 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.langs.xmls; import gplx.*; import gplx.langs.*; +import java.io.StringWriter; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerFactoryConfigurationError; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.w3c.dom.Node; +public class XmlNde { + public XmlAtrList Atrs() {return new XmlAtrList(xnde.getAttributes());} + public XmlNdeList SubNdes() {return new XmlNdeList_cls_xml(xnde.getChildNodes());} + public String Name() {return xnde.getNodeName();} + public String Xml_outer() { + Transformer transformer = transformer_(); + StringWriter writer = new StringWriter(); + try {transformer.transform(new DOMSource(xnde), new StreamResult(writer));} + catch (TransformerException e) {throw Err_.new_exc(e, "xml", "failed to get xml string");} + return writer.toString(); + } + public String Text_inner() {return xnde.getTextContent();} + public boolean NdeType_element() {return xnde.getNodeType() == Node.ELEMENT_NODE;} + public boolean NdeType_textOrEntityReference() {return xnde.getNodeType() == Node.TEXT_NODE || xnde.getNodeType() == Node.ENTITY_REFERENCE_NODE;} + @gplx.Internal protected XmlNde(Node xnde) {this.xnde = xnde;} Node xnde; + static Transformer transformer_() { + TransformerFactory transformerfactory = TransformerFactory.newInstance(); + Transformer transformer = null; + try {transformer = transformerfactory.newTransformer();} + catch (TransformerConfigurationException e) {throw Err_.new_exc(e, "xml", "failed to get create transformer");} + transformer.setOutputProperty("omit-xml-declaration", "yes"); + return transformer; + } + } diff --git a/100_core/src/gplx/langs/xmls/XmlNdeList.java b/100_core/src/gplx/langs/xmls/XmlNdeList.java index a27517de8..8c2a77223 100644 --- a/100_core/src/gplx/langs/xmls/XmlNdeList.java +++ b/100_core/src/gplx/langs/xmls/XmlNdeList.java @@ -13,3 +13,21 @@ 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.langs.xmls; import gplx.*; import gplx.langs.*; +import org.w3c.dom.NodeList; +public interface XmlNdeList { + int Count(); + XmlNde Get_at(int i); +} +class XmlNdeList_cls_xml implements XmlNdeList { + public int Count() {return list.getLength();} + public XmlNde Get_at(int i) {return new XmlNde(list.item(i));} + @gplx.Internal protected XmlNdeList_cls_xml(NodeList list) {this.list = list;} NodeList list; +} +class XmlNdeList_cls_list implements XmlNdeList { + public int Count() {return list.Count();} + public XmlNde Get_at(int i) {return (XmlNde)list.Get_at(i);} + public void Add(XmlNde xnde) {list.Add(xnde);} + @gplx.Internal protected XmlNdeList_cls_list(int count) {list = List_adp_.New(); list.Resize_bounds(count);} List_adp list; +} +//#} \ No newline at end of file diff --git a/100_core/src/gplx/langs/xmls/XmlSplitRdr.java b/100_core/src/gplx/langs/xmls/XmlSplitRdr.java index a27517de8..975bef95b 100644 --- a/100_core/src/gplx/langs/xmls/XmlSplitRdr.java +++ b/100_core/src/gplx/langs/xmls/XmlSplitRdr.java @@ -13,3 +13,37 @@ 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.langs.xmls; import gplx.*; import gplx.langs.*; +import gplx.core.ios.streams.*; +public class XmlSplitRdr { + public byte[] CurAry() {return curAry;} private byte[] curAry; + public long CurSum() {return curSum;} long curSum; + public int CurRead() {return curRead;} int curRead; + public boolean Done() {return done;} private boolean done; + public XmlSplitRdr InitAll_(Io_url url) { + stream = Io_mgr.Instance.OpenStreamRead(url); + curLen = stream.Len(); + curAry = new byte[(int)curLen]; + curSum = 0; + curRead = 0; + done = false; + return this; + } + public XmlSplitRdr Init_(Io_url url, int curArySize) { + stream = Io_mgr.Instance.OpenStreamRead(url); + curLen = Io_mgr.Instance.QueryFil(url).Size(); + curAry = new byte[curArySize]; + curSum = 0; + curRead = 0; + done = false; + return this; + } IoStream stream; long curLen; + public void Read() { + curRead = stream.ReadAry(curAry); + curSum += curRead; + done = curSum == curLen; + if (done && curRead != curAry.length) // on last pass, readAry may have garbage at end, remove + curAry = Bry_.Resize(curAry, curRead); + } + public void Rls() {stream.Rls();} +} diff --git a/100_core/src/gplx/langs/xmls/XmlSplitWtr.java b/100_core/src/gplx/langs/xmls/XmlSplitWtr.java index a27517de8..96cfa4308 100644 --- a/100_core/src/gplx/langs/xmls/XmlSplitWtr.java +++ b/100_core/src/gplx/langs/xmls/XmlSplitWtr.java @@ -13,3 +13,26 @@ 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.langs.xmls; import gplx.*; import gplx.langs.*; +import gplx.core.ios.streams.*; +public class XmlSplitWtr { + public Io_url Url() {return url;} Io_url url; + public XmlSplitWtr Init_(Io_url partDir, byte[] hdr, XmlFileSplitterOpts opts) { + this.partDir = partDir; this.hdr = hdr; this.opts = opts; + return this; + } + public void Bgn(int partIdx) { + String partStr = opts.Namer().GenStrIdxOnly(partIdx); + url = Io_url_.mem_fil_(partStr); + stream = Io_mgr.Instance.OpenStreamWrite(url); + init = true; + } boolean init = true; byte[] hdr; XmlFileSplitterOpts opts; Io_url partDir; IoStream stream; + public void Write(byte[] ary) { + if (init) { + stream.WriteAry(hdr); + init = false; + } + stream.WriteAry(ary); + } + public void Rls() {stream.Rls();} +} diff --git a/100_core/src/gplx/langs/xmls/Xpath_.java b/100_core/src/gplx/langs/xmls/Xpath_.java index a27517de8..6971175ed 100644 --- a/100_core/src/gplx/langs/xmls/Xpath_.java +++ b/100_core/src/gplx/langs/xmls/Xpath_.java @@ -13,3 +13,92 @@ 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.langs.xmls; import gplx.*; import gplx.langs.*; +import gplx.core.primitives.*; +public class Xpath_ { + public static XmlNdeList SelectAll(XmlNde owner, String xpath) {return Select(owner, xpath, Xpath_Args.all_());} + public static XmlNde SelectFirst(XmlNde owner, String xpath) { + XmlNdeList rv = Select(owner, xpath, Xpath_Args.first_()); + return rv.Count() == 0 ? null : rv.Get_at(0); // selects first + } + public static XmlNdeList SelectElements(XmlNde owner) { + XmlNdeList subNdes = owner.SubNdes(); int count = subNdes.Count(); + XmlNdeList_cls_list list = new XmlNdeList_cls_list(count); + for (int i = 0; i < count; i++) { + XmlNde sub = subNdes.Get_at(i); + if (sub.NdeType_element()) + list.Add(sub); + } + return list; + } + static XmlNdeList Select(XmlNde owner, String xpath, Xpath_Args args) { + XmlNdeList_cls_list rv = new XmlNdeList_cls_list(8); + String[] parts = String_.Split(xpath, "/"); + TraverseSubs(owner, parts, 0, rv, args); + return rv; + } + static void TraverseSubs(XmlNde owner, String[] parts, int depth, XmlNdeList_cls_list results, Xpath_Args args) { + int partsLen = Array_.Len(parts); + if (depth == partsLen) return; + String name = parts[depth]; + XmlNdeList subNdes = owner.SubNdes(); int count = subNdes.Count(); + for (int i = 0; i < count; i++) { + XmlNde sub = subNdes.Get_at(i); + if (args.Cancel) return; + if (!String_.Eq(name, sub.Name())) continue; + if (depth == partsLen - 1) { + results.Add(sub); + if (args.SelectFirst) args.Cancel = true; + } + else + TraverseSubs(sub, parts, depth + 1, results, args); + } + } + public static final String InnetTextKey = "&innerText"; + public static Keyval_hash ExtractKeyVals(String xml, Int_obj_ref posRef, String nodeName) { + int pos = posRef.Val(); + Err xmlErr = Err_.new_wo_type("error parsing xml", "xml", xml, "pos", pos); + String headBgnFind = "<" + nodeName + " "; int headBgnFindLen = String_.Len(headBgnFind); + int headBgn = String_.FindFwd(xml, headBgnFind, pos); if (headBgn == String_.Find_none) return null; + int headEnd = String_.FindFwd(xml, ">", headBgn + headBgnFindLen); if (headEnd == String_.Find_none) throw xmlErr; + String atrXml = String_.Mid(xml, headBgn, headEnd); + Keyval_hash rv = ExtractNodeVals(atrXml, xmlErr); + boolean noInnerText = String_.CharAt(xml, headEnd - 1) == '/'; // if />, then no inner text + if (!noInnerText) { + int tail = String_.FindFwd(xml, "", headBgn); if (tail == String_.Find_none) throw Err_.new_wo_type("could not find tailPos", "headBgn", headBgn); + String innerText = String_.Mid(xml, headEnd + 1, tail); + rv.Add(InnetTextKey, innerText); + } + posRef.Val_(headEnd); + return rv; + } + static Keyval_hash ExtractNodeVals(String xml, Err xmlErr) { + Keyval_hash rv = new Keyval_hash(); + int pos = 0; + while (true) { + int eqPos = String_.FindFwd(xml, "=", pos); if (eqPos == String_.Find_none) break; + int q0Pos = String_.FindFwd(xml, "\"", eqPos + 1); if (q0Pos == String_.Find_none) throw xmlErr.Args_add("eqPos", eqPos); + int q1Pos = String_.FindFwd(xml, "\"", q0Pos + 1); if (q1Pos == String_.Find_none) throw xmlErr.Args_add("q1Pos", q1Pos); + int spPos = eqPos - 1; + while (spPos > -1) { + char c = String_.CharAt(xml, spPos); + if (Char_.IsWhitespace(c)) break; + spPos--; + } + if (spPos == String_.Find_none) throw xmlErr.Args_add("sub_msg", "could not find hdr", "eqPos", eqPos); + String key = String_.Mid(xml, spPos + 1, eqPos); + String val = String_.Mid(xml, q0Pos + 1, q1Pos); + rv.Add(key, val); + pos = q1Pos; + } + return rv; + } +} +class Xpath_Args { + public boolean SelectFirst; // false=SelectAll + public boolean Cancel; + public static Xpath_Args all_() {return new Xpath_Args(false);} + public static Xpath_Args first_() {return new Xpath_Args(true);} + Xpath_Args(boolean selectFirst) {this.SelectFirst = selectFirst;} +} +enum Xpath_SelectMode {All, First} diff --git a/100_core/src/gplx/langs/xmls/Xpath__tst.java b/100_core/src/gplx/langs/xmls/Xpath__tst.java index a27517de8..251f6470c 100644 --- a/100_core/src/gplx/langs/xmls/Xpath__tst.java +++ b/100_core/src/gplx/langs/xmls/Xpath__tst.java @@ -13,3 +13,30 @@ 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.langs.xmls; import gplx.*; import gplx.langs.*; +import org.junit.*; +public class Xpath__tst { + @Test public void Select_all() { + String xml = String_.Concat + ( "" + , "" + , "" + , "" + , "" + , "" + , "" + , "" + , "" + , "" + , "" + ); + tst_SelectAll(xml, "a", 2); + tst_SelectAll(xml, "b", 1); + tst_SelectAll(xml, "b/c", 3); + } + void tst_SelectAll(String raw, String xpath, int expdCount) { + XmlDoc xdoc = XmlDoc_.parse(raw); + XmlNdeList xndeList = Xpath_.SelectAll(xdoc.Root(), xpath); + Tfds.Eq(expdCount, xndeList.Count()); + } +} diff --git a/100_core/tst/gplx/GfoMsg_rdr_tst.java b/100_core/tst/gplx/GfoMsg_rdr_tst.java new file mode 100644 index 000000000..d183398a4 --- /dev/null +++ b/100_core/tst/gplx/GfoMsg_rdr_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; +import org.junit.*; +public class GfoMsg_rdr_tst { + @Before public void setup() { + msg = msg_().Add("a", "1").Add("b", "2").Add("c", "3"); + ctx.Match("init", "init"); + } GfoMsg msg; GfsCtx ctx = GfsCtx.new_(); + @Test public void Key() { + tst_Msg(msg, "a", "1"); + tst_Msg(msg, "b", "2"); + tst_Msg(msg, "c", "3"); + tst_Msg(msg, "d", null); + } + @Test public void Pos() { + msg = msg_().Add("", "1").Add("", "2").Add("", "3"); + tst_Msg(msg, "", "1"); + tst_Msg(msg, "", "2"); + tst_Msg(msg, "", "3"); + tst_Msg(msg, "", null); + } + @Test public void OutOfOrder() { + tst_Msg(msg, "c", "3"); + tst_Msg(msg, "b", "2"); + tst_Msg(msg, "a", "1"); + } + @Test public void Key3_Pos1_Pos2() { + msg = msg_().Add("", "1").Add("", "2").Add("c", "3"); + tst_Msg(msg, "c", "3"); + tst_Msg(msg, "", "1"); + tst_Msg(msg, "", "2"); + } + @Test public void MultipleEmpty() { + msg = msg_().Add("", "1").Add("", "2").Add("", "3"); + tst_Msg(msg, "", "1"); + tst_Msg(msg, "", "2"); + tst_Msg(msg, "", "3"); + } + GfoMsg msg_() {return GfoMsg_.new_parse_("test");} + void tst_Msg(GfoMsg m, String k, String expd) {Tfds.Eq(expd, m.ReadStrOr(k, null));} +} diff --git a/100_core/tst/gplx/GfoTreeBldr_fxt.java b/100_core/tst/gplx/GfoTreeBldr_fxt.java new file mode 100644 index 000000000..c4398bd40 --- /dev/null +++ b/100_core/tst/gplx/GfoTreeBldr_fxt.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; +public class GfoTreeBldr_fxt { + public List_adp Atrs() {return atrs;} List_adp atrs = List_adp_.New(); + public List_adp Subs() {return subs;} List_adp subs = List_adp_.New(); + public GfoTreeBldr_fxt atr_(Object key, Object val) { + atrs.Add(new Object[] {key, val}); + return this; + } + public GfoTreeBldr_fxt sub_(GfoTreeBldr_fxt... ary) { + for (GfoTreeBldr_fxt sub : ary) + subs.Add(sub); + return this; + } + public static GfoTreeBldr_fxt new_() {return new GfoTreeBldr_fxt();} GfoTreeBldr_fxt() {} +} diff --git a/100_core/tst/gplx/core/ios/IoEngine_dir_basic_base.java b/100_core/tst/gplx/core/ios/IoEngine_dir_basic_base.java index a27517de8..673a24673 100644 --- a/100_core/tst/gplx/core/ios/IoEngine_dir_basic_base.java +++ b/100_core/tst/gplx/core/ios/IoEngine_dir_basic_base.java @@ -13,3 +13,65 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public abstract class IoEngine_dir_basic_base { + @Before public void setup() { + engine = engine_(); + fx = IoEngineFxt.new_(); + setup_hook(); + } protected IoEngine engine; @gplx.Internal protected IoEngineFxt fx; protected Io_url fil, root; + protected abstract IoEngine engine_(); + protected abstract void setup_hook(); + @Test @gplx.Virtual public void CreateDir() { + fx.tst_ExistsPaths(false, root); + + engine.CreateDir(root); + fx.tst_ExistsPaths(true, root); + } + @Test public void DeleteDir() { + engine.CreateDir(root); + fx.tst_ExistsPaths(true, root); + + engine.DeleteDir(root); + fx.tst_ExistsPaths(false, root); + } + @Test public void CreateDir_createAllOwners() { + Io_url subDir = root.GenSubDir_nest("sub1"); + fx.tst_ExistsPaths(false, subDir, subDir.OwnerDir()); + + engine.CreateDir(subDir); + fx.tst_ExistsPaths(true, subDir, subDir.OwnerDir()); + } +// @Test public void DeleteDir_missing_fail() { +// try {engine.DeleteDir(root);} +// catch {return;} +// Tfds.Fail_expdError(); +// } + @Test public void DeleteDir_missing_pass() { + engine.DeleteDir(root); + } + @Test @gplx.Virtual public void ScanDir() { + Io_url fil = root.GenSubFil("fil1.txt"); fx.run_SaveFilText(fil, "test"); + Io_url dir1 = root.GenSubDir_nest("dir1"); engine.CreateDir(dir1); + Io_url dir1_1 = dir1.GenSubDir_nest("dir1_1"); engine.CreateDir(dir1_1); // NOTE: QueryDir should not recurse by default; dir1_1 should not be returned below + + fx.tst_ScanDir(root, dir1, fil); + } + @Test public void MoveDir() { + Io_url src = root.GenSubDir_nest("src"), trg = root.GenSubDir_nest("trg"); + engine.CreateDir(src); + fx.tst_ExistsPaths(true, src); fx.tst_ExistsPaths(false, trg); + + engine.MoveDir(src, trg); + fx.tst_ExistsPaths(false, src); fx.tst_ExistsPaths(true, trg); +} +@Test @gplx.Virtual public void CopyDir() { + Io_url src = root.GenSubDir_nest("src"), trg = root.GenSubDir_nest("trg"); + engine.CreateDir(src); + fx.tst_ExistsPaths(true, src); fx.tst_ExistsPaths(false, trg); + + engine.CopyDir(src, trg); + fx.tst_ExistsPaths(true, src, trg); +} +} \ No newline at end of file diff --git a/100_core/tst/gplx/core/ios/IoEngine_dir_basic_memory_tst.java b/100_core/tst/gplx/core/ios/IoEngine_dir_basic_memory_tst.java index a27517de8..f29e81d02 100644 --- a/100_core/tst/gplx/core/ios/IoEngine_dir_basic_memory_tst.java +++ b/100_core/tst/gplx/core/ios/IoEngine_dir_basic_memory_tst.java @@ -13,3 +13,10 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoEngine_dir_basic_memory_tst extends IoEngine_dir_basic_base { + @Override protected void setup_hook() { + root = Io_url_.mem_dir_("mem"); + } @Override protected IoEngine engine_() {return IoEngine_.Mem_init_();} +} diff --git a/100_core/tst/gplx/core/ios/IoEngine_dir_basic_system_tst.java b/100_core/tst/gplx/core/ios/IoEngine_dir_basic_system_tst.java index a27517de8..7b4585908 100644 --- a/100_core/tst/gplx/core/ios/IoEngine_dir_basic_system_tst.java +++ b/100_core/tst/gplx/core/ios/IoEngine_dir_basic_system_tst.java @@ -13,3 +13,14 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoEngine_dir_basic_system_tst extends IoEngine_dir_basic_base { + @Override protected void setup_hook() { + root = Tfds.RscDir.GenSubDir_nest("100_core", "ioEngineTest", "_temp"); + IoEngine_xrg_deleteDir.new_(root).Recur_().ReadOnlyFails_off().Exec(); + } @Override protected IoEngine engine_() {return IoEngine_system.new_();} + @Test @Override public void ScanDir() { + super.ScanDir(); + } +} diff --git a/100_core/tst/gplx/core/ios/IoEngine_dir_deep_base.java b/100_core/tst/gplx/core/ios/IoEngine_dir_deep_base.java index a27517de8..d7370c58d 100644 --- a/100_core/tst/gplx/core/ios/IoEngine_dir_deep_base.java +++ b/100_core/tst/gplx/core/ios/IoEngine_dir_deep_base.java @@ -13,3 +13,112 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public abstract class IoEngine_dir_deep_base { + @Before public void setup() { + engine = engine_(); + fx = IoEngineFxt.new_(); + setup_hook(); + setup_paths(); + setup_objs(); + } protected IoEngine engine; protected Io_url fil, root; @gplx.Internal protected IoEngineFxt fx; + protected abstract IoEngine engine_(); + protected abstract void setup_hook(); + @Test @gplx.Virtual public void SearchDir() { + Io_url[] expd = paths_(src_dir0a, src_fil0a, src_dir0a_dir0a, src_dir0a_fil0a); + Io_url[] actl = IoEngine_xrg_queryDir.new_(src).Recur_().DirInclude_().ExecAsUrlAry(); + Tfds.Eq_ary(expd, actl); + } + @Test @gplx.Virtual public void MoveDirDeep() { + fx.tst_ExistsPaths(true, srcTree); fx.tst_ExistsPaths(false, trgTree); + + engine.MoveDirDeep(IoEngine_xrg_xferDir.move_(src, trg).Recur_()); + fx.tst_ExistsPaths(false, srcTree); + fx.tst_ExistsPaths(true, trgTree); + } + @Test @gplx.Virtual public void CopyDir() { + fx.tst_ExistsPaths(true, srcTree); fx.tst_ExistsPaths(false, trgTree); + + engine.CopyDir(src, trg); + fx.tst_ExistsPaths(true, srcTree); + fx.tst_ExistsPaths(true, trgTree); + } + @Test @gplx.Virtual public void DeleteDir() { + fx.tst_ExistsPaths(true, srcTree); + + engine.DeleteDirDeep(IoEngine_xrg_deleteDir.new_(src).Recur_()); + fx.tst_ExistsPaths(false, srcTree); + } +// @Test public virtual void CopyDir_IgnoreExisting() { +// fx.tst_ExistsPaths(true, srcTree); fx.tst_ExistsPaths(false, trgTree); +// engine.SaveFilStr(trg_dir0a_fil0a, "x"); // NOTE: this file is different than src counterpart; should be overwritten by Copy +// fx.tst_ExistsPaths(true, trg_dir0a, trg_dir0a_fil0a); +// +// engine.CopyDir(src, trg); +// fx.tst_ExistsPaths(true, srcTree); +// fx.tst_ExistsPaths(true, trgTree); +// } +// @Test public virtual void CopyDir_IgnoreExistingReadOnlyFile() { +// fx.tst_ExistsPaths(true, srcTree); fx.tst_ExistsPaths(false, trgTree); +// engine.SaveFilStr(trg_fil0a, "x"); // NOTE: this file is different than src counterpart; should be overwritten by Copy +// fx.tst_ExistsPaths(true, trg_fil0a); +// engine.UpdateFilAttrib(trg_fil0a, IoItmAttrib.ReadOnlyFile); +// +// engine.CopyDir(src, trg); +// fx.tst_ExistsPaths(true, srcTree); +// fx.tst_ExistsPaths(true, trgTree); +// } +// @Test public void MoveDir_IgnoreExisting() { +// fx.tst_ExistsPaths(true, srcTree); +// fx.tst_ExistsPaths(false, trgTree); +// engine.SaveFilStr(trg_dir0a_fil0a, @"x"); // NOTE: this file is different than src counterpart; should be overwritten by Copy +// fx.tst_ExistsPaths(true, trg_dir0a, trg_dir0a_fil0a); +// +// engine.MoveDir(src, trg); +// +// fx.tst_ExistsPaths(true, srcTree); +// fx.tst_ExistsPaths(true, trgTree); +// } +// @Test public virtual void ProgressUi() { +// Console_adp__mem dialog = Console_adp__mem.new_(); +// engine.SearchDir(src).Recur_().Prog_(dialog).ExecAsDir(); +// +// Tfds.Eq(dialog.Written.Count, 3); // 3 levels +// tst_(dialog, 0, "scan", src); +// tst_(dialog, 1, "scan", src_dir0a); +// tst_(dialog, 2, "scan", src_dir0a_dir0a); +// } +// void tst_(Console_adp__mem dialog, int i, String s, Io_url root) { +// Object o = dialog.Written.Get_at(i); +// IoStatusArgs args = (IoStatusArgs)o; +// Tfds.Eq(s, args.Op); +// Tfds.Eq(root, args.Path); +// } + protected Io_url src, src_dir0a, src_dir0a_dir0a; + Io_url src_fil0a, src_dir0a_fil0a; + protected Io_url trg, trg_dir0a, trg_dir0a_dir0a; + Io_url trg_fil0a, trg_dir0a_fil0a; + Io_url[] srcTree, trgTree; + Io_url[] paths_(Io_url... ary) {return ary;} + protected void setup_paths() { + src = root.GenSubDir_nest("src"); + src_dir0a = root.GenSubDir_nest("src", "dir0a"); + src_dir0a_dir0a = root.GenSubDir_nest("src", "dir0a", "dir0a"); + src_fil0a = root.GenSubFil_nest("src", "fil0a.txt"); + src_dir0a_fil0a = root.GenSubFil_nest("src", "dir0a", "fil0a.txt"); + trg = root.GenSubDir_nest("trg"); + trg_dir0a = root.GenSubDir_nest("trg", "dir0a"); + trg_dir0a_dir0a = root.GenSubDir_nest("trg", "dir0a", "dir0a"); + trg_fil0a = root.GenSubFil_nest("trg", "fil0a.txt"); + trg_dir0a_fil0a = root.GenSubFil_nest("trg", "dir0a", "fil0a.txt"); + srcTree = new Io_url[] {src, src_dir0a, src_dir0a_dir0a, src_fil0a, src_dir0a_fil0a}; + trgTree = new Io_url[] {trg, trg_dir0a, trg_dir0a_dir0a, trg_fil0a, trg_dir0a_fil0a}; + } + void setup_objs() { + fx.run_SaveFilText(src_fil0a, "src_fil0a"); // NOTE: automatically creates src + fx.run_SaveFilText(src_dir0a_fil0a, "src_dir0a_fil0a"); // NOTE: automatically creates src_dir0a_dir0a + fx.tst_ExistsPaths(true, src_fil0a); + engine.CreateDir(src_dir0a_dir0a); + } +} diff --git a/100_core/tst/gplx/core/ios/IoEngine_dir_deep_memory_tst.java b/100_core/tst/gplx/core/ios/IoEngine_dir_deep_memory_tst.java index a27517de8..42ada730e 100644 --- a/100_core/tst/gplx/core/ios/IoEngine_dir_deep_memory_tst.java +++ b/100_core/tst/gplx/core/ios/IoEngine_dir_deep_memory_tst.java @@ -13,3 +13,22 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoEngine_dir_deep_memory_tst extends IoEngine_dir_deep_base { + @Override protected void setup_hook() { + root = Io_url_.mem_dir_("mem/root"); + } @Override protected IoEngine engine_() {return IoEngine_.Mem_init_();} + @Test @Override public void SearchDir() { + super.SearchDir(); + } + @Test @Override public void MoveDirDeep() { + super.MoveDirDeep(); + } + @Test @Override public void CopyDir() { + super.CopyDir(); + } + @Test @Override public void DeleteDir() { + super.DeleteDir(); + } +} diff --git a/100_core/tst/gplx/core/ios/IoEngine_dir_deep_system_tst.java b/100_core/tst/gplx/core/ios/IoEngine_dir_deep_system_tst.java index a27517de8..81d4809d3 100644 --- a/100_core/tst/gplx/core/ios/IoEngine_dir_deep_system_tst.java +++ b/100_core/tst/gplx/core/ios/IoEngine_dir_deep_system_tst.java @@ -13,3 +13,11 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoEngine_dir_deep_system_tst extends IoEngine_dir_deep_base { + @Override protected void setup_hook() { + root = Tfds.RscDir.GenSubDir_nest("100_core", "ioEngineTest", "_temp"); + IoEngine_xrg_deleteDir.new_(root).Recur_().ReadOnlyFails_off().Exec(); + } @Override protected IoEngine engine_() {return IoEngine_.Sys;} +} diff --git a/100_core/tst/gplx/core/ios/IoEngine_fil_basic_base.java b/100_core/tst/gplx/core/ios/IoEngine_fil_basic_base.java index a27517de8..3a9873dd3 100644 --- a/100_core/tst/gplx/core/ios/IoEngine_fil_basic_base.java +++ b/100_core/tst/gplx/core/ios/IoEngine_fil_basic_base.java @@ -13,3 +13,162 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.texts.*;/*EncodingAdp_*/ import gplx.core.ios.streams.*; +public abstract class IoEngine_fil_basic_base { + @Before public void setup() { + engine = engine_(); + fx = IoEngineFxt.new_(); + setup_hook(); + } protected IoEngine engine; protected IoEngineFxt fx; protected Io_url fil, root; + protected abstract IoEngine engine_(); + protected abstract void setup_hook(); + @Test @gplx.Virtual public void ExistsFil() { + fx.tst_ExistsPaths(false, fil); + } + @Test @gplx.Virtual public void ExistsFil_deep() { + fx.tst_ExistsPaths(false, root.GenSubFil_nest("dir1", "dir2", "fil1.txt")); + } + @Test @gplx.Virtual public void SaveFilStr() { + fx.tst_ExistsPaths(false, fil, fil.OwnerDir()); + + fx.run_SaveFilText(fil, "text"); + fx.tst_ExistsPaths(true, fil, fil.OwnerDir()); + } + @Test @gplx.Virtual public void SaveFilText_autoCreateOwnerDir() { + fil = fil.OwnerDir().GenSubFil_nest("sub1", "fil1.txt"); + fx.tst_ExistsPaths(false, fil, fil.OwnerDir()); + + fx.run_SaveFilText(fil, "text"); + fx.tst_ExistsPaths(true, fil, fil.OwnerDir()); + } + @Test @gplx.Virtual public void SaveFilText_overwrite() { + fx.run_SaveFilText(fil, "text"); + fx.tst_ExistsPaths(true, fil); + + fx.run_SaveFilText(fil, "changed"); + fx.tst_LoadFilStr(fil, "changed"); + } + @Test @gplx.Virtual public void SaveFilText_append() { + fx.run_SaveFilText(fil, "text"); + + engine.SaveFilText_api(IoEngine_xrg_saveFilStr.new_(fil, "appended").Append_()); + fx.tst_LoadFilStr(fil, "text" + "appended"); + } + @Test @gplx.Virtual public void SaveFilText_caseInsensitive() { + if (root.Info().CaseSensitive()) return; + Io_url lcase = root.GenSubFil_nest("dir", "fil.txt"); + Io_url ucase = root.GenSubFil_nest("DIR", "FIL.TXT"); + fx.run_SaveFilText(lcase, "text"); + + fx.tst_ExistsPaths(true, lcase, ucase); + fx.tst_LoadFilStr(lcase, "text"); + fx.tst_LoadFilStr(ucase, "text"); + } + @Test @gplx.Virtual public void SaveFilText_readOnlyFails() { + fx.run_SaveFilText(fil, "text"); + engine.UpdateFilAttrib(fil, IoItmAttrib.readOnly_()); + + try {fx.run_SaveFilText(fil, "changed");} + catch (Exception exc) { + fx.tst_LoadFilStr(fil, "text"); + Err_.Noop(exc); + return; + } + Tfds.Fail_expdError(); + } + @Test @gplx.Virtual public void LoadFilStr() { + fx.run_SaveFilText(fil, "text"); + fx.tst_LoadFilStr(fil, "text"); + } + @Test @gplx.Virtual public void LoadFilStr_missingIgnored() { + Tfds.Eq("", engine.LoadFilStr(IoEngine_xrg_loadFilStr.new_(fil).MissingIgnored_())); + } + @Test @gplx.Virtual public void UpdateFilAttrib() { + fx.run_SaveFilText(fil, "text"); + fx.tst_QueryFilReadOnly(fil, false); + + engine.UpdateFilAttrib(fil, IoItmAttrib.readOnly_()); + fx.tst_QueryFilReadOnly(fil, true); + } + @Test @gplx.Virtual public void DeleteFil() { + fx.run_SaveFilText(fil, "text"); + fx.tst_ExistsPaths(true, fil); + + engine.DeleteFil_api(IoEngine_xrg_deleteFil.new_(fil)); + fx.tst_ExistsPaths(false, fil); + } + @Test @gplx.Virtual public void DeleteFil_missing_pass() { + fil = root.GenSubFil("fileThatDoesntExist.txt"); + + engine.DeleteFil_api(IoEngine_xrg_deleteFil.new_(fil).MissingFails_off()); + fx.tst_ExistsPaths(false, fil); + } + @Test @gplx.Virtual public void DeleteFil_readOnly_fail() { + fx.run_SaveFilText(fil, "text"); + + engine.UpdateFilAttrib(fil, IoItmAttrib.readOnly_()); + try {engine.DeleteFil_api(IoEngine_xrg_deleteFil.new_(fil));} + catch (Exception exc) {Err_.Noop(exc); + fx.tst_ExistsPaths(true, fil); + return; + } + Tfds.Fail_expdError(); + } + @Test @gplx.Virtual public void DeleteFil_readOnly_pass() { + fx.run_SaveFilText(fil, "text"); + engine.UpdateFilAttrib(fil, IoItmAttrib.readOnly_()); + + engine.DeleteFil_api(IoEngine_xrg_deleteFil.new_(fil).ReadOnlyFails_off()); + fx.tst_ExistsPaths(false, fil); + } + @Test @gplx.Virtual public void QueryFil_size() { + fx.run_SaveFilText(fil, "text"); + + fx.tst_QueryFil_size(fil, String_.Len("text")); + } + @Test @gplx.Virtual public void UpdateFilModifiedTime() { + fx.run_SaveFilText(fil, "text"); + + DateAdp time = Datetime_now.Dflt_add_min_(10); + engine.UpdateFilModifiedTime(fil, time); + fx.tst_QueryFil_modifiedTime(fil, time); + } + @Test @gplx.Virtual public void OpenStreamRead() { + fx.run_SaveFilText(fil, "text"); + + int textLen = String_.Len("text"); + byte[] buffer = new byte[textLen]; + IoStream stream = IoStream_.Null; + try { + stream = engine.OpenStreamRead(fil); + stream.Read(buffer, 0, textLen); + } + finally {stream.Rls();} + String actl = String_.new_u8(buffer); + Tfds.Eq("text", actl); + } + @Test @gplx.Virtual public void OpenStreamWrite() { + IoStream stream = IoEngine_xrg_openWrite.new_(fil).Exec(); + byte[] buffer = Bry_.new_u8("text"); + int textLen = String_.Len("text"); + stream.Write(buffer, 0, textLen); + stream.Rls(); + + fx.tst_LoadFilStr(fil, "text"); + } +// @Test public virtual void OpenStreamWrite_in_place() { +// byte[] buffer = Bry_.new_u8("a|b|c"); +// IoStream stream = IoEngine_xrg_openWrite.new_(fil).Exec(); +// stream.Write(buffer, 0, buffer.length); +// stream.Rls(); +// +// buffer = Bry_.new_u8("B"); +// stream = IoEngine_xrg_openWrite.new_(fil).Exec(); +// stream.Seek(2); +// stream.Write(buffer, 0, buffer.length); +// stream.Rls(); +// +// fx.tst_LoadFilStr(fil, "a|B|c"); +// } +} diff --git a/100_core/tst/gplx/core/ios/IoEngine_fil_basic_memory_tst.java b/100_core/tst/gplx/core/ios/IoEngine_fil_basic_memory_tst.java index a27517de8..654d6cc88 100644 --- a/100_core/tst/gplx/core/ios/IoEngine_fil_basic_memory_tst.java +++ b/100_core/tst/gplx/core/ios/IoEngine_fil_basic_memory_tst.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoEngine_fil_basic_memory_tst extends IoEngine_fil_basic_base { + @Override protected IoEngine engine_() {return IoEngine_.Mem_init_();} + @Override protected void setup_hook() { + root = Io_url_.mem_dir_("mem"); + fil = root.GenSubFil_nest("root", "fil.txt"); + } + @Test @Override public void OpenStreamRead() { + super.OpenStreamRead (); + } + @Test @Override public void SaveFilText_overwrite() { + super.SaveFilText_overwrite(); + + // bugfix: verify changed file in ownerDir's hash + IoItmDir dirItm = fx.tst_ScanDir(fil.OwnerDir(), fil); + IoItmFil_mem filItm = (IoItmFil_mem)dirItm.SubFils().Get_at(0); + Tfds.Eq(filItm.Text(), "changed"); + } + @Test public void RecycleFil() { + fx.run_SaveFilText(fil, "text"); + fx.tst_ExistsPaths(true, fil); + + IoRecycleBin bin = IoRecycleBin.Instance; + List_adp list = Tfds.RscDir.XtoNames(); +// foreach (String s in list) +// Tfds.Write(s); + list.Del_at(0); // remove drive + IoEngine_xrg_recycleFil recycleXrg = bin.Send_xrg(fil) + .RootDirNames_(list) + .AppName_("gplx.test").Time_(DateAdp_.parse_gplx("20100102_115559123")).Uuid_(Guid_adp_.Parse("467ffb41-cdfe-402f-b22b-be855425784b")); + recycleXrg.Exec(); + fx.tst_ExistsPaths(false, fil); + fx.tst_ExistsPaths(true, recycleXrg.RecycleUrl()); + + bin.Recover(recycleXrg.RecycleUrl()); + fx.tst_ExistsPaths(true, fil); + fx.tst_ExistsPaths(false, recycleXrg.RecycleUrl()); + } +} diff --git a/100_core/tst/gplx/core/ios/IoEngine_fil_basic_system_tst.java b/100_core/tst/gplx/core/ios/IoEngine_fil_basic_system_tst.java index a27517de8..edd7593bb 100644 --- a/100_core/tst/gplx/core/ios/IoEngine_fil_basic_system_tst.java +++ b/100_core/tst/gplx/core/ios/IoEngine_fil_basic_system_tst.java @@ -13,3 +13,44 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoEngine_fil_basic_system_tst extends IoEngine_fil_basic_base { + @Override protected void setup_hook() { + root = Tfds.RscDir.GenSubDir_nest("100_core", "ioEngineTest", "_temp"); + fil = root.GenSubFil("fil.txt"); + IoEngine_xrg_deleteDir.new_(fil.OwnerDir()).Recur_().ReadOnlyFails_off().Exec(); + } @Override protected IoEngine engine_() {return IoEngine_system.new_();} + @Test public void ExistsFil_IgnoreDifferentCasing() { + if (root.Info().CaseSensitive()) return; + fx.run_SaveFilText(fil, "text"); + fx.tst_ExistsPaths(true, fil); + fx.tst_ExistsPaths(true, fil.OwnerDir().GenSubFil("FIL.txt")); + } + @Test @gplx.Virtual public void RecycleFil() { + fx.run_SaveFilText(fil, "text"); + fx.tst_ExistsPaths(true, fil); + + IoRecycleBin bin = IoRecycleBin.Instance; + List_adp list = root.XtoNames(); list.Del_at(0); // remove drive + IoEngine_xrg_recycleFil recycleXrg = bin.Send_xrg(fil) + .RootDirNames_(list) + .AppName_("gplx.test").Time_(DateAdp_.parse_gplx("20100102_115559123")).Uuid_(Guid_adp_.Parse("467ffb41-cdfe-402f-b22b-be855425784b")); + recycleXrg.Exec(); + fx.tst_ExistsPaths(false, fil); + fx.tst_ExistsPaths(true, recycleXrg.RecycleUrl()); + + bin.Recover(recycleXrg.RecycleUrl()); + fx.tst_ExistsPaths(true, fil); + fx.tst_ExistsPaths(false, recycleXrg.RecycleUrl()); + } + @Test @Override public void DeleteFil_missing_pass() { + super.DeleteFil_missing_pass(); + } + @Test @Override public void DeleteFil_readOnly_pass() { + super.DeleteFil_readOnly_pass (); + } + @Test @Override public void SaveFilText_readOnlyFails() { + super.SaveFilText_readOnlyFails(); + } +} diff --git a/100_core/tst/gplx/core/ios/IoEngine_fil_xfer_base.java b/100_core/tst/gplx/core/ios/IoEngine_fil_xfer_base.java index a27517de8..f917183c0 100644 --- a/100_core/tst/gplx/core/ios/IoEngine_fil_xfer_base.java +++ b/100_core/tst/gplx/core/ios/IoEngine_fil_xfer_base.java @@ -13,3 +13,92 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public abstract class IoEngine_fil_xfer_base { + @Before public void setup() { + engine = engine_(); + fx = IoEngineFxt.new_(); + setup_hook(); + src = root.GenSubFil("src.txt"); trg = root.GenSubFil("trg.txt"); + } protected IoEngine engine; @gplx.Internal protected IoEngineFxt fx; protected Io_url src, trg, root; + DateAdp srcModifiedTime = DateAdp_.parse_gplx("2010.04.12 20.26.01.000"), trgModifiedTime = DateAdp_.parse_gplx("2010.04.01 01.01.01.000"); + protected abstract IoEngine engine_(); + protected abstract void setup_hook(); + protected abstract Io_url AltRoot(); + @Test @gplx.Virtual public void CopyFil() { + fx.run_SaveFilText(src, "src"); fx.run_UpdateFilModifiedTime(src, srcModifiedTime); + fx.tst_ExistsPaths(true, src); + fx.tst_ExistsPaths(false, trg); + + IoEngine_xrg_xferFil.copy_(src, trg).Exec(); + fx.tst_ExistsPaths(true, src, trg); + fx.tst_LoadFilStr(trg, "src"); + fx.tst_QueryFil_modifiedTime(trg, srcModifiedTime); + } + @Test @gplx.Virtual public void CopyFil_overwrite_fail() { + fx.run_SaveFilText(src, "src"); + fx.run_SaveFilText(trg, "trg"); + + try {IoEngine_xrg_xferFil.copy_(src, trg).Exec();} + catch (Exception exc) {Err_.Noop(exc); + fx.tst_ExistsPaths(true, src, trg); + fx.tst_LoadFilStr(trg, "trg"); + return; + } + Tfds.Fail_expdError(); + } + @Test @gplx.Virtual public void CopyFil_overwrite_pass() { + fx.run_SaveFilText(src, "src"); fx.run_UpdateFilModifiedTime(src, srcModifiedTime); + fx.run_SaveFilText(trg, "trg"); fx.run_UpdateFilModifiedTime(trg, trgModifiedTime); + + IoEngine_xrg_xferFil.copy_(src, trg).Overwrite_().Exec(); + fx.tst_ExistsPaths(true, src, trg); + fx.tst_LoadFilStr(trg, "src"); + fx.tst_QueryFil_modifiedTime(trg, srcModifiedTime); + } + @Test @gplx.Virtual public void MoveFil() { + fx.run_SaveFilText(src, "src"); + fx.tst_ExistsPaths(true, src); + fx.tst_ExistsPaths(false, trg); + + IoEngine_xrg_xferFil.move_(src, trg).Exec(); + fx.tst_ExistsPaths(false, src); + fx.tst_ExistsPaths(true, trg); + } + @Test @gplx.Virtual public void MoveFil_overwrite_fail() { + fx.run_SaveFilText(src, "src"); + fx.run_SaveFilText(trg, "trg"); + + try {IoEngine_xrg_xferFil.move_(src, trg).Exec();} + catch (Exception exc) {Err_.Noop(exc); + fx.tst_ExistsPaths(true, src); + fx.tst_ExistsPaths(true, trg); + fx.tst_LoadFilStr(trg, "trg"); + return; + } + Tfds.Fail_expdError(); + } + @Test @gplx.Virtual public void MoveFil_overwrite_pass() { + fx.run_SaveFilText(src, "src"); fx.run_UpdateFilModifiedTime(src, srcModifiedTime); + fx.run_SaveFilText(trg, "trg"); fx.run_UpdateFilModifiedTime(trg, trgModifiedTime); + + IoEngine_xrg_xferFil.move_(src, trg).Overwrite_().Exec(); + fx.tst_ExistsPaths(false, src); + fx.tst_ExistsPaths(true, trg); + fx.tst_LoadFilStr(trg, "src"); + fx.tst_QueryFil_modifiedTime(trg, srcModifiedTime); + } + @Test @gplx.Virtual public void MoveFil_betweenDrives() { + IoEngine_xrg_deleteDir.new_(AltRoot()).Recur_().ReadOnlyFails_off().Exec(); + src = root.GenSubFil_nest("dir", "fil1a.txt"); + trg = AltRoot().GenSubFil_nest("dir", "fil1b.txt"); + fx.run_SaveFilText(src, "src"); + fx.tst_ExistsPaths(true, src); + fx.tst_ExistsPaths(false, trg); + + IoEngine_xrg_xferFil.move_(src, trg).Exec(); + fx.tst_ExistsPaths(false, src); + fx.tst_ExistsPaths(true, trg); + } +} diff --git a/100_core/tst/gplx/core/ios/IoEngine_fil_xfer_memory_tst.java b/100_core/tst/gplx/core/ios/IoEngine_fil_xfer_memory_tst.java index a27517de8..7be19bb76 100644 --- a/100_core/tst/gplx/core/ios/IoEngine_fil_xfer_memory_tst.java +++ b/100_core/tst/gplx/core/ios/IoEngine_fil_xfer_memory_tst.java @@ -13,3 +13,14 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoEngine_fil_xfer_memory_tst extends IoEngine_fil_xfer_base { + @Override protected void setup_hook() { + root = Io_url_.mem_dir_("mem"); + } @Override protected IoEngine engine_() {return IoEngine_.Mem_init_();} + @Override protected Io_url AltRoot() { + Io_mgr.Instance.InitEngine_mem_("mem2"); + return Io_url_.mem_dir_("mem2"); + } +} diff --git a/100_core/tst/gplx/core/ios/IoEngine_fil_xfer_system_tst.java b/100_core/tst/gplx/core/ios/IoEngine_fil_xfer_system_tst.java index a27517de8..5c6a5955c 100644 --- a/100_core/tst/gplx/core/ios/IoEngine_fil_xfer_system_tst.java +++ b/100_core/tst/gplx/core/ios/IoEngine_fil_xfer_system_tst.java @@ -13,3 +13,14 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoEngine_fil_xfer_system_tst extends IoEngine_fil_xfer_base { + @Override protected void setup_hook() { + root = Tfds.RscDir.GenSubDir_nest("100_core", "ioEngineTest", "_temp"); + IoEngine_xrg_deleteDir.new_(root.OwnerDir()).Recur_().ReadOnlyFails_off().Exec(); + } @Override protected IoEngine engine_() {return IoEngine_system.new_();} + @Override protected Io_url AltRoot() { + return Tfds.RscDir.GenSubDir_nest("100_core", "ioEngineTest", "_temp"); + } +} diff --git a/100_core/tst/gplx/core/ios/IoEngine_stream_xfer_tst.java b/100_core/tst/gplx/core/ios/IoEngine_stream_xfer_tst.java index a27517de8..ebea34466 100644 --- a/100_core/tst/gplx/core/ios/IoEngine_stream_xfer_tst.java +++ b/100_core/tst/gplx/core/ios/IoEngine_stream_xfer_tst.java @@ -13,3 +13,35 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoEngine_stream_xfer_tst { + @Before public void setup() { + srcEngine = IoEngine_memory.new_("mock1"); + trgEngine = IoEngine_memory.new_("mock2"); + IoEnginePool.Instance.Add_if_dupe_use_nth(srcEngine); IoEnginePool.Instance.Add_if_dupe_use_nth(trgEngine); + IoUrlInfoRegy.Instance.Reg(IoUrlInfo_.mem_("mem1/", srcEngine.Key())); + IoUrlInfoRegy.Instance.Reg(IoUrlInfo_.mem_("mem2/", trgEngine.Key())); + srcDir = Io_url_.mem_dir_("mem1/dir"); trgDir = Io_url_.mem_dir_("mem2/dir"); + } + @Test public void TransferBetween() { + Io_url srcPath = srcDir.GenSubFil("fil.txt"); + Io_url trgPath = trgDir.GenSubFil("fil.txt"); + tst_TransferStreams(srcEngine, srcPath, trgEngine, trgPath); + } + void tst_TransferStreams(IoEngine srcEngine, Io_url srcPath, IoEngine trgEngine, Io_url trgPath) { + srcEngine.SaveFilText_api(IoEngine_xrg_saveFilStr.new_(srcPath, "test1")); + trgEngine.DeleteFil_api(IoEngine_xrg_deleteFil.new_(trgPath)); // make sure file is deleted + fx.tst_ExistsPaths(true, srcPath); + fx.tst_ExistsPaths(false, trgPath); + + IoEngineUtl utl = IoEngineUtl.new_(); + utl.BufferLength_set(4); + utl.XferFil(srcEngine, IoEngine_xrg_xferFil.copy_(srcPath, trgPath)); + fx.tst_ExistsPaths(true, srcPath, trgPath); + fx.tst_LoadFilStr(trgPath, "test1"); + } + IoEngineFxt fx = IoEngineFxt.new_(); + Io_url srcDir, trgDir; + IoEngine srcEngine, trgEngine; +} diff --git a/100_core/tst/gplx/core/ios/IoEngine_xrg_queryDir_tst.java b/100_core/tst/gplx/core/ios/IoEngine_xrg_queryDir_tst.java index a27517de8..79bfce9fa 100644 --- a/100_core/tst/gplx/core/ios/IoEngine_xrg_queryDir_tst.java +++ b/100_core/tst/gplx/core/ios/IoEngine_xrg_queryDir_tst.java @@ -13,3 +13,51 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoEngine_xrg_queryDir_tst { + @Before public void setup() { + engine = IoEngine_.Mem_init_(); + } IoEngine engine; Io_url[] ary; + @Test public void Basic() { + ary = save_text_(fil_("fil1.txt")); + + tst_ExecPathAry(finder_(), ary); + } + @Test public void FilPath() { + ary = save_text_(fil_("fil1.txt"), fil_("fil2.jpg"), fil_("fil3.txt")); + + tst_ExecPathAry(finder_(), ary); // default: all files + tst_ExecPathAry(finder_().FilPath_("*.txt") // findPattern of *.txt + , fil_("fil1.txt"), fil_("fil3.txt")); + } + @Test public void Recur() { + ary = save_text_(fil_("fil1.txt"), fil_("dirA", "fil1A.jpg")); + + tst_ExecPathAry(finder_(), fil_("fil1.txt")); // default: no recursion + tst_ExecPathAry(finder_().Recur_(), ary); // recurse + } + @Test public void DirPattern() { + save_text_(fil_("fil1.txt"), fil_("dirA", "fil1A.jpg")); + + tst_ExecPathAry(finder_(), fil_("fil1.txt")); // default: files only + tst_ExecPathAry(finder_().DirInclude_() // include dirs; NOTE: fil1A not returned b/c Recur_ is not true + , dir_("dirA"), fil_("fil1.txt")); + } + @Test public void Sort_by() { + save_text_(fil_("fil2a.txt"), fil_("fil1.txt")); + + tst_ExecPathAry(finder_() // default: sortByAscOrder + , fil_("fil1.txt"), fil_("fil2a.txt")); + } + IoEngine_xrg_queryDir finder_() {return IoEngine_xrg_queryDir.new_(Io_url_.mem_dir_("mem/root"));}// NOTE: not in setup b/c finder must be newed several times inside test method + Io_url fil_(String... ary) {return Io_url_.mem_dir_("mem/root").GenSubFil_nest(ary);} + Io_url dir_(String... ary) {return Io_url_.mem_dir_("mem/root").GenSubDir_nest(ary);} + + Io_url[] save_text_(Io_url... ary) { + for (Io_url url : ary) + Io_mgr.Instance.SaveFilStr(url, url.Raw()); + return ary; + } + void tst_ExecPathAry(IoEngine_xrg_queryDir finder, Io_url... expd) {Tfds.Eq_ary(expd, finder.ExecAsUrlAry());} +} diff --git a/100_core/tst/gplx/core/ios/IoEngine_xrg_recycleFil_tst.java b/100_core/tst/gplx/core/ios/IoEngine_xrg_recycleFil_tst.java index a27517de8..39d0b2779 100644 --- a/100_core/tst/gplx/core/ios/IoEngine_xrg_recycleFil_tst.java +++ b/100_core/tst/gplx/core/ios/IoEngine_xrg_recycleFil_tst.java @@ -13,3 +13,18 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoEngine_xrg_recycleFil_tst { + @Before public void setup() { + IoEngine_.Mem_init_(); + } + @Test public void GenRecycleUrl() { + tst_GenRecycleUrl(recycle_(), Io_url_.mem_fil_("mem/z_trash/20100102/gplx.images;115559123;;fil.txt")); + tst_GenRecycleUrl(recycle_().Uuid_include_(), Io_url_.mem_fil_("mem/z_trash/20100102/gplx.images;115559123;467ffb41-cdfe-402f-b22b-be855425784b;fil.txt")); + } + IoEngine_xrg_recycleFil recycle_() {return IoEngine_xrg_recycleFil.gplx_(Io_url_.mem_fil_("mem/dir/fil.txt")).AppName_("gplx.images").Uuid_(Guid_adp_.Parse("467ffb41-cdfe-402f-b22b-be855425784b")).Time_(DateAdp_.parse_gplx("20100102_115559123"));} + void tst_GenRecycleUrl(IoEngine_xrg_recycleFil xrg, Io_url expd) { + Tfds.Eq(expd, xrg.RecycleUrl()); + } +} diff --git a/100_core/tst/gplx/core/ios/IoItmDir_FetchDeepOrNull_tst.java b/100_core/tst/gplx/core/ios/IoItmDir_FetchDeepOrNull_tst.java index a27517de8..a21798af1 100644 --- a/100_core/tst/gplx/core/ios/IoItmDir_FetchDeepOrNull_tst.java +++ b/100_core/tst/gplx/core/ios/IoItmDir_FetchDeepOrNull_tst.java @@ -13,3 +13,26 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoItmDir_FetchDeepOrNull_tst { + @Before public void setup() { + drive = Io_url_.mem_dir_("mem"); + rootDir = bldr.dir_(drive, bldr.dir_(drive.GenSubDir("sub1"))); + } IoItm_fxt bldr = IoItm_fxt.new_(); Io_url drive; IoItmDir rootDir; + @Test public void FetchDeepOrNull() { + tst_FetchDeepOrNull(rootDir, drive.GenSubDir("sub1"), true); + tst_FetchDeepOrNull(rootDir, drive.GenSubDir("sub2"), false); + tst_FetchDeepOrNull(rootDir.SubDirs().Get_at(0), drive.GenSubDir("sub1"), true); + tst_FetchDeepOrNull(rootDir.SubDirs().Get_at(0), drive.GenSubDir("sub2"), false); + } + void tst_FetchDeepOrNull(Object rootDirObj, Io_url find, boolean expdFound) { + IoItmDir rootDir = IoItmDir_.as_(rootDirObj); + IoItmDir actlDir = rootDir.FetchDeepOrNull(find); + if (actlDir == null) { + if (expdFound) Tfds.Fail("actlDir is null, but expd dir to be found"); + else return; // actlDir is null but expdFound was false; return; + } + Tfds.Eq(find.Raw(), actlDir.Url().Raw()); + } +} diff --git a/100_core/tst/gplx/core/ios/IoUrlInfo_alias_tst.java b/100_core/tst/gplx/core/ios/IoUrlInfo_alias_tst.java index a27517de8..e6c743874 100644 --- a/100_core/tst/gplx/core/ios/IoUrlInfo_alias_tst.java +++ b/100_core/tst/gplx/core/ios/IoUrlInfo_alias_tst.java @@ -13,3 +13,44 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoUrlInfo_alias_tst { + IoUrlInfo_alias alias; + @Test public void MapWntToWnt() { + Make("usr:\\", "D:\\usr\\"); + tst_Xto_api("usr:\\dir\\fil.txt", "D:\\usr\\dir\\fil.txt"); + tst_OwnerDir("usr:\\dir\\", "usr:\\"); + tst_OwnerDir("usr:\\", ""); + tst_NameOnly("usr:\\", "usr"); + } + @Test public void MapToLnx() { + Make("usr:\\", "/home/"); + tst_Xto_api("usr:\\dir\\fil.txt", "/home/dir/fil.txt"); + } + @Test public void MapLnxToWnt() { + Make("usr:/", "C:\\usr\\"); + tst_Xto_api("usr:/dir/fil.txt", "C:\\usr\\dir\\fil.txt"); + } + @Test public void WntToWnt() { + Make("C:\\", "X:\\"); + tst_Xto_api("C:\\dir\\fil.txt", "X:\\dir\\fil.txt"); + tst_NameOnly("C:\\", "C"); + } + @Test public void WntToLnx() { + Make("C:\\", "/home/"); + tst_Xto_api("C:\\dir\\fil.txt", "/home/dir/fil.txt"); + } + @Test public void LnxToWnt() { + Make("/home/", "C:\\"); + tst_Xto_api("/home/dir/fil.txt", "C:\\dir\\fil.txt"); + tst_NameOnly("/home/", "home"); + tst_NameOnly("/", "root"); + } + void tst_Xto_api(String raw, String expd) {Tfds.Eq(expd, alias.Xto_api(raw));} + void tst_OwnerDir(String raw, String expd) {Tfds.Eq(expd, alias.OwnerDir(raw));} + void tst_NameOnly(String raw, String expd) {Tfds.Eq(expd, alias.NameOnly(raw));} + void Make(String srcDir, String trgDir) { + alias = IoUrlInfo_alias.new_(srcDir, trgDir, IoEngine_.SysKey); + } +} diff --git a/100_core/tst/gplx/core/ios/IoUrl_lnx_tst.java b/100_core/tst/gplx/core/ios/IoUrl_lnx_tst.java index a27517de8..4bc122ca4 100644 --- a/100_core/tst/gplx/core/ios/IoUrl_lnx_tst.java +++ b/100_core/tst/gplx/core/ios/IoUrl_lnx_tst.java @@ -13,3 +13,41 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoUrl_lnx_tst { + IoUrlFxt fx = IoUrlFxt.new_(); + @Test public void Raw() { + fx.tst_Xto_gplx(Io_url_.lnx_dir_("/home/"), "/home/"); + fx.tst_Xto_gplx(Io_url_.lnx_dir_("/home"), "/home/"); // add / + fx.tst_Xto_gplx(Io_url_.lnx_dir_("/"), "/"); + fx.tst_Xto_gplx(Io_url_.lnx_fil_("/home/fil.txt"), "/home/fil.txt"); + } + @Test public void Xto_api() { + fx.tst_Xto_api(Io_url_.lnx_fil_("/home/fil.txt"), "/home/fil.txt"); + fx.tst_Xto_api(Io_url_.lnx_dir_("/home/"), "/home"); // del / + fx.tst_Xto_api(Io_url_.lnx_dir_("/"), "/"); + } + @Test public void OwnerRoot() { + fx.tst_OwnerRoot(Io_url_.lnx_dir_("/home/fil.txt"), "/"); + fx.tst_OwnerRoot(Io_url_.lnx_dir_("/home"), "/"); + fx.tst_OwnerRoot(Io_url_.lnx_dir_("root"), "/"); + } + @Test public void XtoNames() { + fx.tst_XtoNames(Io_url_.lnx_dir_("/home/fil.txt"), fx.ary_("root", "home", "fil.txt")); + fx.tst_XtoNames(Io_url_.lnx_dir_("/home"), fx.ary_("root", "home")); + } + @Test public void IsDir() { + fx.tst_IsDir(Io_url_.lnx_dir_("/home"), true); + fx.tst_IsDir(Io_url_.lnx_fil_("/home/file.txt"), false); + } + @Test public void OwnerDir() { + fx.tst_OwnerDir(Io_url_.lnx_dir_("/home/lnxusr"), Io_url_.lnx_dir_("/home")); + fx.tst_OwnerDir(Io_url_.lnx_dir_("/fil.txt"), Io_url_.lnx_dir_("/")); + fx.tst_OwnerDir(Io_url_.lnx_dir_("/"), Io_url_.Empty); + } + @Test public void NameAndExt() { + fx.tst_NameAndExt(Io_url_.lnx_fil_("/fil.txt"), "fil.txt"); + fx.tst_NameAndExt(Io_url_.lnx_dir_("/dir"), "dir/"); + } +} diff --git a/100_core/tst/gplx/core/ios/IoUrl_map_tst.java b/100_core/tst/gplx/core/ios/IoUrl_map_tst.java index a27517de8..fa8c2ce43 100644 --- a/100_core/tst/gplx/core/ios/IoUrl_map_tst.java +++ b/100_core/tst/gplx/core/ios/IoUrl_map_tst.java @@ -13,3 +13,17 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoUrl_map_tst { + IoUrlFxt fx = IoUrlFxt.new_(); + @Test public void Xto_api() { + IoUrlInfo inf = IoUrlInfo_.alias_("tst:\\", "C:\\tst\\", IoEngine_.SysKey); + fx.tst_Xto_api(Io_url_.new_inf_("tst:\\dir\\fil.txt", inf), "C:\\tst\\dir\\fil.txt"); + fx.tst_Xto_api(Io_url_.new_inf_("tst:\\dir\\", inf), "C:\\tst\\dir"); // no trailing \ + } + @Test public void Xto_api_wce() { + IoUrlInfo inf = IoUrlInfo_.alias_("wce:\\", "\\SD Card\\", IoEngine_.SysKey); + fx.tst_Xto_api(Io_url_.new_inf_("wce:\\dir\\", inf), "\\SD Card\\dir"); + } +} diff --git a/100_core/tst/gplx/core/ios/IoUrl_wnt_tst.java b/100_core/tst/gplx/core/ios/IoUrl_wnt_tst.java index a27517de8..2f051fc85 100644 --- a/100_core/tst/gplx/core/ios/IoUrl_wnt_tst.java +++ b/100_core/tst/gplx/core/ios/IoUrl_wnt_tst.java @@ -13,3 +13,84 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class IoUrl_wnt_tst { + IoUrlFxt fx = IoUrlFxt.new_(); + @Test public void Raw() { + fx.tst_Xto_gplx(Io_url_.wnt_fil_("C:\\dir\\fil.txt"), "C:\\dir\\fil.txt"); + fx.tst_Xto_gplx(Io_url_.wnt_dir_("C:\\dir\\"), "C:\\dir\\"); + fx.tst_Xto_gplx(Io_url_.wnt_dir_("C:\\dir") , "C:\\dir\\"); // add \ + } + @Test public void Xto_api() { + fx.tst_Xto_api(Io_url_.wnt_fil_("C:\\fil.txt"), "C:\\fil.txt"); + fx.tst_Xto_api(Io_url_.wnt_dir_("C:\\dir\\"), "C:\\dir"); // del \ + fx.tst_Xto_api(Io_url_.wnt_dir_("C:"), "C:"); + } + @Test public void OwnerRoot() { + fx.tst_OwnerRoot(Io_url_.wnt_dir_("C:\\dir") , "C:\\"); + fx.tst_OwnerRoot(Io_url_.wnt_dir_("C:\\fil.png") , "C:\\"); + fx.tst_OwnerRoot(Io_url_.wnt_dir_("C:") , "C:\\"); + } + @Test public void IsDir() { + fx.tst_IsDir(Io_url_.wnt_dir_("C:\\dir\\"), true); + fx.tst_IsDir(Io_url_.wnt_fil_("C:\\dir"), false); + fx.tst_IsDir(Io_url_.wnt_fil_("C:\\fil.txt"), false); + } + @Test public void OwnerDir() { + fx.tst_OwnerDir(Io_url_.wnt_dir_("C:\\dir\\sub1"), Io_url_.wnt_dir_("C:\\dir")); + fx.tst_OwnerDir(Io_url_.wnt_fil_("C:\\fil.txt"), Io_url_.wnt_dir_("C:")); + fx.tst_OwnerDir(Io_url_.wnt_dir_("C:"), Io_url_.Empty); +// fx.tst_OwnerDir(Io_url_.wnt_fil_("press enter to select this folder"), Io_url_.Empty); + } + @Test public void NameAndExt() { + fx.tst_NameAndExt(Io_url_.wnt_fil_("C:\\fil.txt"), "fil.txt"); + fx.tst_NameAndExt(Io_url_.wnt_dir_("C:\\dir"), "dir\\"); + } + @Test public void NameOnly() { + fx.tst_NameOnly(Io_url_.wnt_fil_("C:\\fil.txt"), "fil"); + fx.tst_NameOnly(Io_url_.wnt_dir_("C:\\dir"), "dir"); + fx.tst_NameOnly(Io_url_.wnt_dir_("C:"), "C"); + } + @Test public void Ext() { + fx.tst_Ext(Io_url_.wnt_fil_("C:\\fil.txt"), ".txt"); // fil + fx.tst_Ext(Io_url_.wnt_fil_("C:\\fil.multiple.txt"), ".txt"); // multiple ext + fx.tst_Ext(Io_url_.wnt_fil_("C:\\fil"), ""); // no ext + fx.tst_Ext(Io_url_.wnt_dir_("C:\\dir"), "\\"); // dir + } + @Test public void GenSubDir_nest() { + fx.tst_GenSubDir_nest(Io_url_.wnt_dir_("C:"), fx.ary_("dir1", "sub1"), Io_url_.wnt_dir_("C:\\dir1\\sub1")); + } + @Test public void GenNewExt() { + fx.tst_GenNewExt(Io_url_.wnt_fil_("C:\\fil.gif"), ".png", Io_url_.wnt_fil_("C:\\fil.png")); // basic + fx.tst_GenNewExt(Io_url_.wnt_fil_("C:\\fil.tst.gif"), ".png", Io_url_.wnt_fil_("C:\\fil.tst.png")); // last in multiple dotted + } + @Test public void GenRelUrl_orEmpty() { + fx.tst_GenRelUrl_orEmpty(Io_url_.wnt_fil_("C:\\root\\fil.txt") , Io_url_.wnt_dir_("C:\\root") , "fil.txt"); // fil + fx.tst_GenRelUrl_orEmpty(Io_url_.wnt_dir_("C:\\root\\dir") , Io_url_.wnt_dir_("C:\\root") , "dir\\"); // dir + fx.tst_GenRelUrl_orEmpty(Io_url_.wnt_fil_("C:\\root\\dir\\fil.txt") , Io_url_.wnt_dir_("C:\\root") , "dir\\fil.txt"); // fil: nested1 + fx.tst_GenRelUrl_orEmpty(Io_url_.wnt_fil_("C:\\root\\dir\\fil.txt") , Io_url_.wnt_dir_("C:") , "root\\dir\\fil.txt"); // fil: nested2 + } + @Test public void GenParallel() { + fx.tst_GenParallel(Io_url_.wnt_fil_("C:\\root1\\fil.txt"), Io_url_.wnt_dir_("C:\\root1"), Io_url_.wnt_dir_("D:\\root2"), Io_url_.wnt_fil_("D:\\root2\\fil.txt")); + fx.tst_GenParallel(Io_url_.wnt_dir_("C:\\root1\\dir") , Io_url_.wnt_dir_("C:\\root1"), Io_url_.wnt_dir_("D:\\root2"), Io_url_.wnt_dir_("D:\\root2\\dir")); + } +} +class IoUrlFxt { + public void tst_Xto_api(Io_url url, String expd) {Tfds.Eq(expd, url.Xto_api());} + public void tst_OwnerRoot(Io_url url, String expd) {Tfds.Eq(expd, url.OwnerRoot().Raw());} + public void tst_XtoNames(Io_url url, String... expdAry) {Tfds.Eq_ary(expdAry, url.XtoNames().To_str_ary());} + public void tst_NameAndExt(Io_url url, String expd) {Tfds.Eq(expd, url.NameAndExt());} + public void tst_Xto_gplx(Io_url url, String expd) {Tfds.Eq(expd, url.Raw());} + public void tst_IsDir(Io_url url, boolean expd) {Tfds.Eq(expd, url.Type_dir());} + public void tst_OwnerDir(Io_url url, Io_url expd) {Tfds.Eq_url(expd, url.OwnerDir());} + public void tst_NameOnly(Io_url url, String expd) {Tfds.Eq(expd, url.NameOnly());} + public void tst_Ext(Io_url url, String expd) {Tfds.Eq(expd, url.Ext());} + public void tst_GenSubDir_nest(Io_url rootDir, String[] parts, Io_url expd) {Tfds.Eq(expd, rootDir.GenSubDir_nest(parts));} + public void tst_GenNewExt(Io_url url, String ext, Io_url expd) {Tfds.Eq_url(expd, url.GenNewExt(ext));} + public void tst_GenRelUrl_orEmpty(Io_url url, Io_url rootDir, String expd) {Tfds.Eq(expd, url.GenRelUrl_orEmpty(rootDir));} + public void tst_GenParallel(Io_url url, Io_url oldRoot, Io_url newRoot, Io_url expd) {Tfds.Eq_url(expd, url.GenParallel(oldRoot, newRoot));} + + public String[] ary_(String... ary) {return String_.Ary(ary);} + public static IoUrlFxt new_() {return new IoUrlFxt();} IoUrlFxt() {} +} diff --git a/100_core/tst/gplx/core/stores/GfoNdeRdr_read_tst.java b/100_core/tst/gplx/core/stores/GfoNdeRdr_read_tst.java index a27517de8..94ea997e5 100644 --- a/100_core/tst/gplx/core/stores/GfoNdeRdr_read_tst.java +++ b/100_core/tst/gplx/core/stores/GfoNdeRdr_read_tst.java @@ -13,3 +13,34 @@ 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.core.stores; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.gfo_ndes.*; import gplx.core.type_xtns.*; +public class GfoNdeRdr_read_tst { + @Test public void ReadInt() { + rdr = rdr_(IntClassXtn.Instance, "id", 1); + Tfds.Eq(rdr.ReadInt("id"), 1); + } + @Test public void ReadIntOr() { + rdr = rdr_(IntClassXtn.Instance, "id", 1); + Tfds.Eq(rdr.ReadIntOr("id", -1), 1); + } + @Test public void ReadIntElse_minus1() { + rdr = rdr_(IntClassXtn.Instance, "id", null); + Tfds.Eq(rdr.ReadIntOr("id", -1), -1); + } + @Test public void ReadInt_parse() { + rdr = rdr_(StringClassXtn.Instance, "id", "1"); + Tfds.Eq(rdr.ReadInt("id"), 1); + } + @Test public void ReadIntElse_parse() { + rdr = rdr_(StringClassXtn.Instance, "id", "2"); + Tfds.Eq(rdr.ReadIntOr("id", -1), 2); + } + GfoNdeRdr rdr_(ClassXtn type, String key, Object val) { // makes rdr with one row and one val + GfoFldList flds = GfoFldList_.new_().Add(key, type); + GfoNde row = GfoNde_.vals_(flds, new Object[] {val}); + boolean parse = type == StringClassXtn.Instance; // assumes type is either StringClassXtn or IntClassXtn + return GfoNdeRdr_.leaf_(row, parse); + } + GfoNdeRdr rdr; +} diff --git a/100_core/tst/gplx/core/stores/GfoNdeRdr_tst.java b/100_core/tst/gplx/core/stores/GfoNdeRdr_tst.java index a27517de8..eeda8c6d3 100644 --- a/100_core/tst/gplx/core/stores/GfoNdeRdr_tst.java +++ b/100_core/tst/gplx/core/stores/GfoNdeRdr_tst.java @@ -13,3 +13,175 @@ 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.core.stores; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.gfo_ndes.*; +public class GfoNdeRdr_tst { + @Test public void Subs_leafs() { + root = + fx.root_ + ( fx.row_vals_(0) + , fx.row_vals_(1) + , fx.row_vals_(2) + ); + tst_NdeVals(root, 0, 1, 2); + } + @Test public void Subs_ndes() { + root = + fx.root_ + ( leaf_("", 0) + , leaf_("", 1) + , leaf_("", 2) + ); + tst_NdeVals(root, 0, 1, 2); + } + @Test public void Subs_mix() { + root = + fx.root_ + ( leaf_("", 0) + , fx.row_vals_(1) + , fx.row_vals_(2) + ); + tst_NdeVals(root, 0, 1, 2); + } + @Test public void Subs_rdr() { + root = + fx.root_ + ( fx.row_vals_(0) + ); + rootRdr = GfoNdeRdr_.root_parseNot_(root); + DataRdr rdr = rootRdr.Subs(); + Tfds.Eq_true(rdr.MoveNextPeer()); + Tfds.Eq(0, rdr.ReadAt(0)); + Tfds.Eq_false(rdr.MoveNextPeer()); + } + @Test public void MoveNextPeer_implicit() { + root = + fx.root_ + ( fx.csv_dat_ + ( fx.row_vals_(0) + , fx.row_vals_(1) + , fx.row_vals_(2) + ) + ); + GfoNdeRdr rootRdr = GfoNdeRdr_.root_parseNot_(root); + DataRdr subsRdr = rootRdr.Subs(); // pos=-1; bof + DataRdr subRdr = subsRdr.Subs(); // MoveNextPeer not needed; implicitly moves to pos=0 + tst_RdrVals(subRdr, Object_.Ary(0, 1, 2)); + } + @Test public void MoveNextPeer_explicit() { + root = + fx.root_ + ( fx.csv_dat_ + ( fx.row_vals_(0) + , fx.row_vals_(1) + , fx.row_vals_(2) + ) + ); + GfoNdeRdr rootRdr = GfoNdeRdr_.root_parseNot_(root); + DataRdr subsRdr = rootRdr.Subs(); // pos=-1; bof + Tfds.Eq_true(subsRdr.MoveNextPeer()); // explicitly moves to pos=0 + DataRdr subRdr = subsRdr.Subs(); + tst_RdrVals(subRdr, Object_.Ary(0, 1, 2)); + } + @Test public void Xpath_basic() { + root = fx.root_ + ( leaf_("root", 0) + , leaf_("root", 1) + , leaf_("root", 2) + ); + tst_Xpath_all(root, "root", 0, 1, 2); + } + @Test public void Xpath_nested() { + root = fx.root_ + ( fx.tbl_("owner" + , leaf_("root", 0) + , leaf_("root", 1) + , leaf_("root", 2) + )); + tst_Xpath_all(root, "owner/root", 0, 1, 2); + } + @Test public void Xpath_null() { + root = fx.root_ + ( leaf_("match", 0) + ); + rootRdr = GfoNdeRdr_.root_parseNot_(root); + DataRdr sub = rootRdr.Subs_byName("no_match"); + Tfds.Eq_false(sub.MoveNextPeer()); + } + @Test public void Xpath_moveFirst_basic() { + root = fx.root_ + ( leaf_("nde0", 0) + ); + tst_Xpath_first(root, "nde0", 0); + } + @Test public void Xpath_moveFirst_shallow() { + root = fx.root_ + ( leaf_("nde0", 0) + , leaf_("nde1", 1) + , leaf_("nde2", 2) + ); + tst_Xpath_first(root, "nde2", 2); + } + @Test public void Xpath_moveFirst_nested() { + root = fx.root_ + ( node_("nde0", Object_.Ary("0") + , leaf_("nde00", "00") + )); + tst_Xpath_first(root, "nde0", "0"); + tst_Xpath_first(root, "nde0/nde00", "00"); + } + @Test public void Xpath_moveFirst_nested_similarName() { + root = fx.root_ + ( node_("nde0", Object_.Ary("0") + , leaf_("nde00", "00") + ) + , node_("nde1", Object_.Ary("1") + , leaf_("nde00", "10") + )); + tst_Xpath_first(root, "nde1/nde00", "10"); + } + @Test public void Xpath_moveFirst_many() { + root = fx.root_ + ( leaf_("root", 0) + , leaf_("root", 1) + , leaf_("root", 2) + ); + tst_Xpath_first(root, "root", 0); // returns first + } + @Test public void Xpath_moveFirst_null() { + root = fx.root_ + ( leaf_("nde0", 0) + , leaf_("nde1", 1) + , leaf_("nde2", 2) + ); + rootRdr = GfoNdeRdr_.root_parseNot_(root); + DataRdr rdr = rootRdr.Subs_byName("nde3"); + Tfds.Eq_false(rdr.MoveNextPeer()); + } + + GfoNde leaf_(String name, Object... vals) {return GfoNde_.nde_(name, vals, GfoNde_.Ary_empty);} + GfoNde node_(String name, Object[] vals, GfoNde... subs) {return GfoNde_.nde_(name, vals, subs);} + void tst_NdeVals(GfoNde nde, Object... exptVals) { + DataRdr rdr = GfoNdeRdr_.root_parseNot_(nde); + tst_RdrVals(rdr.Subs(), exptVals); + } + void tst_RdrVals(DataRdr rdr, Object[] exptVals) { + int count = 0; + while (rdr.MoveNextPeer()) { + Object actl = rdr.ReadAt(0); + Tfds.Eq(actl, exptVals[count++]); + } + Tfds.Eq(count, exptVals.length); + } + void tst_Xpath_first(GfoNde root, String xpath, Object expt) { + DataRdr rdr = GfoNdeRdr_.root_parseNot_(root); + DataRdr sel = rdr.Subs_byName_moveFirst(xpath); + Object actl = sel.ReadAt(0); + Tfds.Eq(actl, expt); + } + void tst_Xpath_all(GfoNde root, String xpath, Object... exptVals) { + DataRdr rdr = GfoNdeRdr_.root_parseNot_(root); + tst_RdrVals(rdr.Subs_byName(xpath), exptVals); + } + GfoNde root; DataRdr rootRdr; GfoNdeFxt fx = GfoNdeFxt.new_(); +} diff --git a/100_core/tst/gplx/core/stores/xmls/XmlDataRdr_tst.java b/100_core/tst/gplx/core/stores/xmls/XmlDataRdr_tst.java index a27517de8..ce4822ff4 100644 --- a/100_core/tst/gplx/core/stores/xmls/XmlDataRdr_tst.java +++ b/100_core/tst/gplx/core/stores/xmls/XmlDataRdr_tst.java @@ -13,3 +13,88 @@ 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.core.stores.xmls; import gplx.*; import gplx.core.*; import gplx.core.stores.*; +import org.junit.*; +public class XmlDataRdr_tst { + @Test public void Read() { + DataRdr rdr = fx.rdr_(""); + Tfds.Eq(rdr.NameOfNode(), "title"); + Tfds.Eq(rdr.ReadStr("name"), "first"); + Tfds.Eq(rdr.ReadInt("id"), 1); + Tfds.Eq(rdr.ReadBool("profiled"), false); + } + @Test public void None() { + DataRdr rdr = fx.rdr_ + ( "<root>" + , "<find/>" + , "</root>" + ); + fx.tst_Subs_ByName(rdr, "no_nde", "no_atr"); + } + @Test public void One() { + DataRdr rdr = fx.rdr_ + ( "<root>" + , "<find id=\"f0\" />" + , "</root>" + ); + fx.tst_Subs_ByName(rdr, "find", "id", "f0"); + } + @Test public void One_IgnoreOthers() { + DataRdr rdr = fx.rdr_ + ( "<root>" + , "<find id=\"f0\" />" + , "<skip id=\"s0\" />" + , "</root>" + ); + fx.tst_Subs_ByName(rdr, "find", "id", "f0"); + } + @Test public void Many() { + DataRdr rdr = fx.rdr_ + ( "<root>" + , "<find id=\"f0\" />" + , "<find id=\"f1\" />" + , "</root>" + ); + fx.tst_Subs_ByName(rdr, "find", "id", "f0", "f1"); + } + @Test public void Nested() { + DataRdr rdr = fx.rdr_ + ( "<root>" + , "<sub1>" + , "<find id=\"f0\" />" + , "<find id=\"f1\" />" + , "</sub1>" + , "</root>" + ); + fx.tst_Subs_ByName(rdr, "sub1/find", "id", "f0", "f1"); + } + @Test public void Nested_IgnoreOthers() { + DataRdr rdr = fx.rdr_ + ( "<root>" + , "<sub1>" + , "<find id=\"f0\" />" + , "<skip id=\"s0\" />" + , "</sub1>" + , "<sub1>" + , "<find id=\"f1\" />" // NOTE: find across ndes + , "<skip id=\"s1\" />" + , "</sub1>" + , "</root>" + ); + fx.tst_Subs_ByName(rdr, "sub1/find", "id", "f0", "f1"); + } + XmlDataRdr_fxt fx = XmlDataRdr_fxt.new_(); +} +class XmlDataRdr_fxt { + public DataRdr rdr_(String... ary) {return XmlDataRdr_.text_(String_.Concat(ary));} + public void tst_Subs_ByName(DataRdr rdr, String xpath, String key, String... expdAry) { + DataRdr subRdr = rdr.Subs_byName(xpath); + List_adp list = List_adp_.New(); + while (subRdr.MoveNextPeer()) + list.Add(subRdr.Read(key)); + + String[] actlAry = list.To_str_ary(); + Tfds.Eq_ary(actlAry, expdAry); + } + public static XmlDataRdr_fxt new_() {return new XmlDataRdr_fxt();} XmlDataRdr_fxt() {} +} diff --git a/100_core/tst/gplx/core/stores/xmls/XmlDataWtr_tst.java b/100_core/tst/gplx/core/stores/xmls/XmlDataWtr_tst.java index a27517de8..5627f345c 100644 --- a/100_core/tst/gplx/core/stores/xmls/XmlDataWtr_tst.java +++ b/100_core/tst/gplx/core/stores/xmls/XmlDataWtr_tst.java @@ -13,3 +13,81 @@ 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.core.stores.xmls; import gplx.*; import gplx.core.*; import gplx.core.stores.*; +import org.junit.*; +public class XmlDataWtr_tst { + @Before public void setup() { + wtr = XmlDataWtr.new_(); + } + @Test public void WriteNodeBgn() { + wtr.WriteNodeBgn("chapter"); + tst_XStr(wtr, "<chapter />", String_.CrLf); + } + @Test public void Attributes() { + wtr.WriteNodeBgn("chapter"); + wtr.WriteData("id", 1); + wtr.WriteData("name", "first"); + tst_XStr(wtr, "<chapter id=\"1\" name=\"first\" />", String_.CrLf); + } + @Test public void Subs() { + wtr.WriteNodeBgn("title"); + wtr.WriteNodeBgn("chapters"); + wtr.WriteNodeBgn("chapter"); + tst_XStr(wtr + , "<title>", String_.CrLf + , "<chapters>", String_.CrLf + , "<chapter />", String_.CrLf + , "</chapters>", String_.CrLf + , "", String_.CrLf + ); + } + @Test public void Subs_Iterate() { + wtr.WriteNodeBgn("titles"); + for (int title = 1; title <= 2; title++) { + wtr.WriteNodeBgn("title"); + wtr.WriteData("id", title); + wtr.WriteNodeBgn("chapters"); + wtr.WriteNodeEnd(); // chapters + wtr.WriteNodeEnd(); // title + } + wtr.WriteNodeEnd(); //titles + tst_XStr(wtr + , "", String_.CrLf + , "", String_.CrLf + , "<chapters />", String_.CrLf + , "", String_.CrLf + , "", String_.CrLf + , "<chapters />", String_.CrLf + , "", String_.CrLf + , "", String_.CrLf + ); + } + @Test public void Peers() { + wtr.WriteNodeBgn("title"); + wtr.WriteNodeBgn("chapters"); + wtr.WriteNodeEnd(); + wtr.WriteNodeBgn("audioStreams"); + tst_XStr(wtr + , "", String_.CrLf + , "<chapters />", String_.CrLf + , "<audioStreams />", String_.CrLf + , "", String_.CrLf + ); + } + @Test public void AtrsWithNesting() { + wtr.WriteNodeBgn("title"); + wtr.WriteData("id", 1); + wtr.WriteData("name", "first"); + wtr.WriteNodeBgn("chapters"); + tst_XStr(wtr + , "", String_.CrLf + , "<chapters />", String_.CrLf + , "", String_.CrLf + ); + } + void tst_XStr(XmlDataWtr wtr, String... parts) { + String expd = String_.Concat(parts); + Tfds.Eq(expd, wtr.To_str()); + } + XmlDataWtr wtr; +} diff --git a/110_gfml/src_100_tkn/gplx/gfml/GfmlLxr.java b/110_gfml/src_100_tkn/gplx/gfml/GfmlLxr.java new file mode 100644 index 000000000..864b0d55b --- /dev/null +++ b/110_gfml/src_100_tkn/gplx/gfml/GfmlLxr.java @@ -0,0 +1,32 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.gfml; import gplx.*; +import gplx.core.texts.*; /*CharStream*/ +public interface GfmlLxr extends Gfo_evt_itm { + String Key(); + String[] Hooks(); + GfmlTkn CmdTkn(); + void CmdTkn_set(GfmlTkn val); // needed for lxr pragma + GfmlTkn MakeTkn(CharStream stream, int hookLength); + GfmlLxr SubLxr(); + void SubLxr_Add(GfmlLxr... lexer); +} +class GfmlLxrRegy { + public int Count() {return hash.Count();} + public void Add(GfmlLxr lxr) {hash.Add(lxr.Key(), lxr);} + public GfmlLxr Get_by(String key) {return (GfmlLxr)hash.Get_by(key);} + Hash_adp hash = Hash_adp_.New(); +} diff --git a/110_gfml/src_100_tkn/gplx/gfml/GfmlLxr_.java b/110_gfml/src_100_tkn/gplx/gfml/GfmlLxr_.java new file mode 100644 index 000000000..286c014f0 --- /dev/null +++ b/110_gfml/src_100_tkn/gplx/gfml/GfmlLxr_.java @@ -0,0 +1,215 @@ +/* +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.gfml; import gplx.*; +import gplx.core.strings.*; +import gplx.core.texts.*; /*CharStream*/ +public class GfmlLxr_ { + public static GfmlLxr general_(String key, GfmlTkn protoTkn) {return GfmlLxr_general.new_(key, protoTkn);} + public static GfmlLxr solo_(String key, GfmlTkn singletonTkn) {return GfmlLxr_singleton.new_(key, singletonTkn.Raw(), singletonTkn);} + public static GfmlLxr range_(String key, String[] ary, GfmlTkn protoTkn, boolean ignoreOutput) {return GfmlLxr_group.new_(key, ary, protoTkn, ignoreOutput);} + + @gplx.Internal protected static GfmlLxr symbol_(String key, String raw, String val, GfmlBldrCmd cmd) { + GfmlTkn tkn = GfmlTkn_.singleton_(key, raw, val, cmd); + return GfmlLxr_.solo_(key, tkn); + } + @gplx.Internal protected static GfmlLxr frame_(String key, GfmlFrame frame, String bgn, String end) {return GfmlLxr_frame.new_(key, frame, bgn, end, GfmlBldrCmd_pendingTkns_add.Instance, GfmlBldrCmd_frameEnd.data_());} + public static final GfmlLxr Null = new GfmlLxr_null(); + public static final String CmdTknChanged_evt = "Changed"; + public static GfmlLxr as_(Object obj) {return obj instanceof GfmlLxr ? (GfmlLxr)obj : null;} + public static GfmlLxr cast(Object obj) {try {return (GfmlLxr)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, GfmlLxr.class, obj);}} +} +class GfmlLxr_null implements GfmlLxr { + public String Key() {return "gfml.nullLxr";} + public Gfo_evt_mgr Evt_mgr() {if (evt_mgr == null) evt_mgr = new Gfo_evt_mgr(this); return evt_mgr;} Gfo_evt_mgr evt_mgr; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return Gfo_invk_.Rv_unhandled;} + public GfmlTkn CmdTkn() {return GfmlTkn_.Null;} public void CmdTkn_set(GfmlTkn val) {} + public String[] Hooks() {return String_.Ary_empty;} + public GfmlTkn MakeTkn(CharStream stream, int hookLength) {return GfmlTkn_.Null;} + public void SubLxr_Add(GfmlLxr... lexer) {} + public GfmlLxr SubLxr() {return this;} +} +class GfmlLxr_singleton implements GfmlLxr, Gfo_evt_itm { + public Gfo_evt_mgr Evt_mgr() {if (evt_mgr == null) evt_mgr = new Gfo_evt_mgr(this); return evt_mgr;} Gfo_evt_mgr evt_mgr; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return Gfo_invk_.Rv_unhandled;} + public String Key() {return key;} private String key; + public GfmlTkn CmdTkn() {return singletonTkn;} GfmlTkn singletonTkn; + public void CmdTkn_set(GfmlTkn val) { + String oldRaw = singletonTkn.Raw(); + singletonTkn = val; + hooks = String_.Ary(val.Raw()); + Gfo_evt_mgr_.Pub_vals(this, GfmlLxr_.CmdTknChanged_evt, Keyval_.new_("old", oldRaw), Keyval_.new_("new", val.Raw()), Keyval_.new_("lxr", this)); + } + public String[] Hooks() {return hooks;} private String[] hooks; + public GfmlTkn MakeTkn(CharStream stream, int hookLength) { + stream.MoveNextBy(hookLength); + return singletonTkn; + } + public GfmlLxr SubLxr() {return subLxr;} GfmlLxr subLxr; + public void SubLxr_Add(GfmlLxr... lexer) {subLxr.SubLxr_Add(lexer);} + public static GfmlLxr_singleton new_(String key, String hook, GfmlTkn singletonTkn) { + GfmlLxr_singleton rv = new GfmlLxr_singleton(); + rv.ctor_(key, hook, singletonTkn, GfmlLxr_.Null); + return rv; + } protected GfmlLxr_singleton() {} + @gplx.Internal protected void ctor_(String key, String hook, GfmlTkn singletonTkn, GfmlLxr subLxr) { + this.key = key; + this.hooks = String_.Ary(hook); + this.subLxr = subLxr; + this.singletonTkn = singletonTkn; + } +} +class GfmlLxr_group implements GfmlLxr { + public String Key() {return key;} private String key; + public Gfo_evt_mgr Evt_mgr() {if (evt_mgr == null) evt_mgr = new Gfo_evt_mgr(this); return evt_mgr;} Gfo_evt_mgr evt_mgr; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return Gfo_invk_.Rv_unhandled;} + public GfmlTkn CmdTkn() {return outputTkn;} public void CmdTkn_set(GfmlTkn val) {} GfmlTkn outputTkn; + public String[] Hooks() {return trie.Symbols();} + public GfmlTkn MakeTkn(CharStream stream, int hookLength) { + while (stream.AtMid()) { + if (!ignoreOutput) + sb.Add_mid(stream.Ary(), stream.Pos(), hookLength); + stream.MoveNextBy(hookLength); + + String found = String_.cast(trie.FindMatch(stream)); + if (found == null) break; + hookLength = trie.LastMatchCount; + } + if (ignoreOutput) return GfmlTkn_.IgnoreOutput; + String raw = sb.To_str_and_clear(); + return outputTkn.MakeNew(raw, raw); + } + public GfmlLxr SubLxr() {throw Err_sublxr();} + public void SubLxr_Add(GfmlLxr... lexer) {throw Err_sublxr();} + Err Err_sublxr() {return Err_.new_unimplemented_w_msg("group lxr does not have subLxrs", "key", key, "output_tkn", outputTkn.Raw()).Trace_ignore_add_1_();} + GfmlTrie trie = GfmlTrie.new_(); String_bldr sb = String_bldr_.new_(); boolean ignoreOutput; + public static GfmlLxr_group new_(String key, String[] hooks, GfmlTkn outputTkn, boolean ignoreOutput) { + GfmlLxr_group rv = new GfmlLxr_group(); + rv.key = key; + for (String hook : hooks) + rv.trie.Add(hook, hook); + rv.outputTkn = outputTkn; rv.ignoreOutput = ignoreOutput; + return rv; + } GfmlLxr_group() {} +} +class GfmlLxr_general implements GfmlLxr, Gfo_invk { + public Gfo_evt_mgr Evt_mgr() {if (evt_mgr == null) evt_mgr = new Gfo_evt_mgr(this); return evt_mgr;} Gfo_evt_mgr evt_mgr; + public String Key() {return key;} private String key; + public GfmlTkn CmdTkn() {return txtTkn;} public void CmdTkn_set(GfmlTkn val) {} GfmlTkn txtTkn; + public String[] Hooks() {return symTrie.Symbols();} + public GfmlTkn MakeTkn(CharStream stream, int firstTknLength) { + GfmlTkn rv = null; + if (symLxr != null) { // symLxr has something; produce + rv = MakeTkn_symLxr(stream); + if (rv != GfmlTkn_.IgnoreOutput) return rv; + } + while (stream.AtMid()) { // keep moving til (a) symChar or (b) endOfStream + Object result = symTrie.FindMatch(stream); + symTknLen = symTrie.LastMatchCount; + if (result == null) { // no match; must be txtChar; + txtBfr.Add(stream); + stream.MoveNext(); + } + else { // symChar + symLxr = (GfmlLxr)result; // set symLxr for next pass + if (txtBfr.Has()) // txtBfr has something: gen txtTkn + rv = txtBfr.MakeTkn(stream, txtTkn); + else { // txtBfr empty: gen symbol + rv = MakeTkn_symLxr(stream); + if (rv == GfmlTkn_.IgnoreOutput) continue; + } + return rv; + } + } + if (txtBfr.Has()) // endOfStream, but txtBfr has chars + return txtBfr.MakeTkn(stream, txtTkn); + return GfmlTkn_.EndOfStream; + } + public void SubLxr_Add(GfmlLxr... lxrs) { + for (GfmlLxr lxr : lxrs) { + for (String hook : lxr.Hooks()) + symTrie.Add(hook, lxr); + Gfo_evt_mgr_.Sub_same(lxr, GfmlLxr_.CmdTknChanged_evt, this); + } + } + public GfmlLxr SubLxr() {return this;} + GfmlTkn MakeTkn_symLxr(CharStream stream) { + GfmlLxr lexer = symLxr; symLxr = null; + int length = symTknLen; symTknLen = 0; + return lexer.MakeTkn(stream, length); + } + GfmlLxr_general_txtBfr txtBfr = new GfmlLxr_general_txtBfr(); GfmlTrie symTrie = GfmlTrie.new_(); GfmlLxr symLxr; int symTknLen; + @gplx.Internal protected static GfmlLxr_general new_(String key, GfmlTkn txtTkn) { + GfmlLxr_general rv = new GfmlLxr_general(); + rv.key = key; rv.txtTkn = txtTkn; + return rv; + } protected GfmlLxr_general() {} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, GfmlLxr_.CmdTknChanged_evt)) { + symTrie.Del(m.ReadStr("old")); + symTrie.Add(m.ReadStr("new"), m.CastObj("lxr")); + } + else return Gfo_invk_.Rv_unhandled; + return this; + } +} +class GfmlLxr_general_txtBfr { + public int Bgn = NullPos; + public int Len; + public boolean Has() {return Bgn != NullPos;} + public void Add(CharStream stream) { + if (Bgn == NullPos) Bgn = stream.Pos(); + Len++; + } static final int NullPos = -1; + public GfmlTkn MakeTkn(CharStream stream, GfmlTkn textTkn) { + String raw = String_.new_charAry_(stream.Ary(), Bgn, Len); + Bgn = -1; Len = 0; + return textTkn.MakeNew(raw, raw); + } +} +class GfmlLxr_frame extends GfmlLxr_singleton { GfmlFrame frame; GfmlLxr endLxr, txtLxr; + public void BgnRaw_set(String val) {// needed for lxr pragma + GfmlBldrCmd_frameBgn bgnCmd = GfmlBldrCmd_frameBgn.new_(frame, txtLxr); + GfmlTkn bgnTkn = GfmlTkn_.singleton_(this.Key() + "_bgn", val, GfmlTkn_.NullVal, bgnCmd); + this.CmdTkn_set(bgnTkn); + } + public void EndRaw_set(String val) {// needed for lxr pragma + GfmlBldrCmd_frameEnd endCmd = GfmlBldrCmd_frameEnd.data_(); + GfmlTkn endTkn = GfmlTkn_.singleton_(this.Key() + "_end", val, GfmlTkn_.NullVal, endCmd); + endLxr.CmdTkn_set(endTkn); + } + public static GfmlLxr new_(String key, GfmlFrame frame, String bgn, String end, GfmlBldrCmd txtCmd, GfmlBldrCmd endCmd) { + GfmlLxr_frame rv = new GfmlLxr_frame(); + GfmlTkn txtTkn = frame.FrameType() == GfmlFrame_.Type_comment + ? GfmlTkn_.valConst_(key + "_txt", GfmlTkn_.NullVal, txtCmd) + : GfmlTkn_.cmd_(key + "_txt", txtCmd) + ; + GfmlLxr txtLxr = GfmlLxr_.general_(key + "_txt", txtTkn); + + GfmlTkn bgnTkn = GfmlTkn_.singleton_(key + "_bgn", bgn, GfmlTkn_.NullVal, GfmlBldrCmd_frameBgn.new_(frame, txtLxr)); + rv.ctor_(key, bgn, bgnTkn, txtLxr); + + GfmlTkn endTkn = GfmlTkn_.singleton_(key + "_end", end, GfmlTkn_.NullVal, endCmd); + GfmlLxr endLxr = GfmlLxr_.solo_(key + "_end", endTkn); + rv.SubLxr_Add(endLxr); + + rv.frame = frame; + rv.endLxr = endLxr; + rv.txtLxr = txtLxr; + return rv; + } GfmlLxr_frame() {} + public static GfmlLxr_frame as_(Object obj) {return obj instanceof GfmlLxr_frame ? (GfmlLxr_frame)obj : null;} + public static GfmlLxr_frame cast(Object obj) {try {return (GfmlLxr_frame)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, GfmlLxr_frame.class, obj);}} +} diff --git a/110_gfml/src_100_tkn/gplx/gfml/GfmlObj.java b/110_gfml/src_100_tkn/gplx/gfml/GfmlObj.java new file mode 100644 index 000000000..06a3459bf --- /dev/null +++ b/110_gfml/src_100_tkn/gplx/gfml/GfmlObj.java @@ -0,0 +1,27 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.gfml; import gplx.*; +public interface GfmlObj { + int ObjType(); +} +class GfmlObj_ { + public static final int + Type_tkn = 1 + , Type_atr = 2 + , Type_nde = 3 + , Type_prg = 4 + ; +} diff --git a/110_gfml/src_100_tkn/gplx/gfml/GfmlObjList.java b/110_gfml/src_100_tkn/gplx/gfml/GfmlObjList.java new file mode 100644 index 000000000..1bf128603 --- /dev/null +++ b/110_gfml/src_100_tkn/gplx/gfml/GfmlObjList.java @@ -0,0 +1,23 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.gfml; import gplx.*; +public class GfmlObjList extends List_adp_base { + @gplx.New public GfmlObj Get_at(int idx) {return (GfmlObj)Get_at_base(idx);} + public void Add(GfmlObj tkn) {Add_base(tkn);} + public void Add_at(GfmlObj tkn, int idx) {super.AddAt_base(idx, tkn);} + public void Del(GfmlObj tkn) {Del_base(tkn);} + public static GfmlObjList new_() {return new GfmlObjList();} GfmlObjList() {} +} diff --git a/110_gfml/src_100_tkn/gplx/gfml/GfmlTkn.java b/110_gfml/src_100_tkn/gplx/gfml/GfmlTkn.java new file mode 100644 index 000000000..90ed1fff1 --- /dev/null +++ b/110_gfml/src_100_tkn/gplx/gfml/GfmlTkn.java @@ -0,0 +1,44 @@ +/* +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.gfml; import gplx.*; +import gplx.core.strings.*; +public interface GfmlTkn extends GfmlObj { + String TknType(); + String Raw(); + String Val(); + GfmlTkn[] SubTkns(); + GfmlBldrCmd Cmd_of_Tkn(); + GfmlTkn MakeNew(String raw, String val); +} +class GfmlTknAry_ { + public static final GfmlTkn[] Empty = new GfmlTkn[0]; + public static GfmlTkn[] ary_(GfmlTkn... ary) {return ary;} + @gplx.Internal protected static String XtoRaw(GfmlTkn[] ary) { + String_bldr sb = String_bldr_.new_(); + for (GfmlTkn tkn : ary) + sb.Add(tkn.Raw()); + return sb.To_str(); + } + @gplx.Internal protected static String XtoVal(GfmlTkn[] ary) {return XtoVal(ary, 0, ary.length);} + static String XtoVal(GfmlTkn[] ary, int bgn, int end) { + String_bldr sb = String_bldr_.new_(); + for (int i = bgn; i < end; i++) { + GfmlTkn tkn = ary[i]; + sb.Add(tkn.Val()); + } + return sb.To_str(); + } +} diff --git a/110_gfml/src_100_tkn/gplx/gfml/GfmlTkn_.java b/110_gfml/src_100_tkn/gplx/gfml/GfmlTkn_.java new file mode 100644 index 000000000..22c968c88 --- /dev/null +++ b/110_gfml/src_100_tkn/gplx/gfml/GfmlTkn_.java @@ -0,0 +1,63 @@ +/* +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.gfml; import gplx.*; +public class GfmlTkn_ { + @gplx.Internal protected static final String NullRaw = "", NullVal = ""; static final String Type_base = "gfml.baseTkn"; + @gplx.Internal protected static final GfmlTkn + Null = new GfmlTkn_base().ctor_GfmlTkn_base("gfml.nullTkn", NullRaw, NullVal, GfmlBldrCmd_.Null) + , EndOfStream = GfmlTkn_.raw_("<>") + , IgnoreOutput = GfmlTkn_.raw_("<>"); + public static GfmlTkn as_(Object obj) {return obj instanceof GfmlTkn ? (GfmlTkn)obj : null;} + @gplx.Internal protected static GfmlTkn new_(String raw, String val) {return new GfmlTkn_base().ctor_GfmlTkn_base(Type_base, raw, val, GfmlBldrCmd_.Null);} + public static GfmlTkn raw_(String raw) {return new GfmlTkn_base().ctor_GfmlTkn_base(Type_base, raw, raw, GfmlBldrCmd_.Null);} + @gplx.Internal protected static GfmlTkn val_(String val) {return new GfmlTkn_base().ctor_GfmlTkn_base(Type_base, NullRaw, val, GfmlBldrCmd_.Null);} + @gplx.Internal protected static GfmlTkn cmd_(String tknType, GfmlBldrCmd cmd) {return new GfmlTkn_base().ctor_GfmlTkn_base(tknType, NullRaw, NullVal, cmd);} + @gplx.Internal protected static GfmlTkn valConst_(String tknType, String val, GfmlBldrCmd cmd) {return new GfmlTkn_valConst().ctor_GfmlTkn_base(tknType, GfmlTkn_.NullRaw, val, cmd);} + @gplx.Internal protected static GfmlTkn singleton_(String tknType, String raw, String val, GfmlBldrCmd cmd) {return new GfmlTkn_singleton().ctor_GfmlTkn_base(tknType, raw, val, cmd);} + @gplx.Internal protected static GfmlTkn composite_(String tknType, GfmlTkn[] ary) {return new GfmlTkn_composite(tknType, ary);} + @gplx.Internal protected static GfmlTkn composite_list_(String tknType, GfmlObjList list) { + GfmlTkn[] ary = new GfmlTkn[list.Count()]; + for (int i = 0; i < list.Count(); i++) + ary[i] = (GfmlTkn)list.Get_at(i); + return GfmlTkn_.composite_(tknType, ary); + } +} +class GfmlTkn_base implements GfmlTkn { + public int ObjType() {return GfmlObj_.Type_tkn;} + public String TknType() {return tknType;} private String tknType; + public String Raw() {return raw;} private String raw; + public String Val() {return val;} private String val; + public GfmlBldrCmd Cmd_of_Tkn() {return cmd;} GfmlBldrCmd cmd; + public GfmlTkn[] SubTkns() {return GfmlTknAry_.Empty;} + @gplx.Virtual public GfmlTkn MakeNew(String rawNew, String valNew) {return new GfmlTkn_base().ctor_GfmlTkn_base(tknType, rawNew, valNew, cmd);} + @gplx.Internal protected GfmlTkn_base ctor_GfmlTkn_base(String tknType, String raw, String val, GfmlBldrCmd cmd) {this.tknType = tknType; this.raw = raw; this.val = val; this.cmd = cmd; return this;} +} +class GfmlTkn_valConst extends GfmlTkn_base { + @Override public GfmlTkn MakeNew(String rawNew, String valNew) {return new GfmlTkn_base().ctor_GfmlTkn_base(this.TknType(), rawNew, this.Val(), this.Cmd_of_Tkn());} +} +class GfmlTkn_singleton extends GfmlTkn_base { + @Override public GfmlTkn MakeNew(String rawNew, String valNew) {return this;} +} +class GfmlTkn_composite implements GfmlTkn { + public int ObjType() {return GfmlObj_.Type_tkn;} + public String TknType() {return tknType;} private String tknType; + public String Raw() {return GfmlTknAry_.XtoRaw(ary);} + public String Val() {return GfmlTknAry_.XtoVal(ary);} + public GfmlBldrCmd Cmd_of_Tkn() {return GfmlBldrCmd_.Null;} + public GfmlTkn[] SubTkns() {return ary;} GfmlTkn[] ary; + public GfmlTkn MakeNew(String rawNew, String valNew) {throw Err_.new_unimplemented_w_msg(".MakeNew cannot be invoked on GfmlTkn_composite (raw is available, but not val)", "tknType", tknType, "rawNew", rawNew, "valNew", valNew);} + @gplx.Internal protected GfmlTkn_composite(String tknType, GfmlTkn[] ary) {this.tknType = tknType; this.ary = ary;} +} diff --git a/110_gfml/src_100_tkn/gplx/gfml/GfmlTrie.java b/110_gfml/src_100_tkn/gplx/gfml/GfmlTrie.java new file mode 100644 index 000000000..01c4cf450 --- /dev/null +++ b/110_gfml/src_100_tkn/gplx/gfml/GfmlTrie.java @@ -0,0 +1,112 @@ +/* +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.gfml; import gplx.*; +import gplx.core.texts.*; /*CharStream*/ +public class GfmlTrie { + public String[] Symbols() { + String[] rv = new String[symbols.Count()]; + for (int i = 0; i < rv.length; i++) + rv[i] = String_.cast(symbols.Get_at(i)); + return rv; + } Ordered_hash symbols = Ordered_hash_.New(); + public int LastMatchCount; // PERF: prop is faster than method + public Object FindMatch(CharStream stream) { + Object result = null; int moveCount = 0; LastMatchCount = 0; + IntObjHash_base link = rootLink; + while (stream.AtMid()) { + Object found = link.Get_by(stream.Cur()); + if (found == null) break; // found is null; can happen for false matches; ex: #IF=aiv7fDxKg{%ZQT`)Tm;Nzdw3?5mWS_a97QY}XGvfO+95x32J zq1M@IAN%EM={@y~9cU8j?axk2M?tK&;PV^v@4O97ZhQVkL6M@59bs{lC}{qZ$%9-BP- zJz445a+AZKMZ?mwfY-D~Vp-|o@9II2BJ+>Yx0NsS3XOgWA#4T9sUIOQ`HQ-_P3$AP z4r?&>5ir3Yf%)RMhatn4z`^_O?Nm40`x4l0DDmDGMhC^ZiJ}dTk`VveaD{UvEEt7z zRKq{8rid(Gv?(1+BcD}hzMp;?ko@L$6WgZwC#FxpN)NdjHe31uAbj5^>AInrUXR#r z`5;@K+T8bFdfbwf*_>li_Ydb@=XC;@H!D>+-%T-XSiWI&=^mk)tyC_wp$6Rf;F8<+ zS14$h;DCb%{8^Ux19yor`%Q_Ub&{d!C<}gavBCC+MV(9ddZH;}4nyMhD+t0UCx?{+ zY|jd)0`|daBkwmTh)+>(mGo>1T1+XP57YaNMEN!YY?R0Aqes=RJCNDF)VArR=2p17 zC&@ux+_5_OB06@mp-G5GULspEK0-RUp1X9%KE8liRUxx0b8}tjffkF4_E+pT)FTJM z06+6_J%ZRx)QcjoqA`vDt~qBCpJ8BuHk8t*w#TxD_o-<7t8cL|OITp{3-FtU5|DK7 z8x_ix_7RqBgtD$2HYb%@1a zi_TcgFnuTY@~2n!1R3>TO$A;>Ye08|-kDc7EG4jZ-QIfKUhjg&=axq_ z2tDWxts}Yg&b;H~#!Iryj0j#Ge(d6y&sbP3%{Q|E8?28|hozbd7vOHs9&f>_g%FQ( zc&XDauZ=Mx!?GO|WqFq}AJoAHJwP+h`!Kgx-1V{%0{z}c$Z&?^UdH%IoPWO4Bw$ay zi{KWeF5a{8h-EWo7q2l!biZaLSZU>p*G;gvCu|iYzaOL55K$}|w8DS;mNrsBu=`84 z8-iY4mR`I8bYux(tmSHs(IT%pF#6cZzix@9L?P5`QdWhoC$e3Ry|Urq|Atr?#%ohp z8RDGM4H4Pz46k|HVT^5ayjkbeqVS}lP7QUhqcB%!X1pz*EuhHgU3upQbEvCKk|wb} zoE!TY^}&R8;inl}qKOLVB!4ff zquoxPMBDKi%dNl{BJl^WTaw|lb1?s4HgXmFH$DcB5++NxVNLaz1qvvI>R}OU?sM|A z5)w}R_SsqW6Cj<@&cbqOE`*{gPDEbO^Bj?=`pz9oYFeG7VL@^f*pp`Xygd z7_YFPVP6L&_ADQZ`|Xwy6jL5_)!a+)1-Hlvv~A*nDV3dvVvY2lUYRH96EDU@$VvT$ zVHCz=BeM7UJRn?t+=`8LR`9QW@^C`oYrV@@>qpmW7WlDW$}0ocoJx`I8gHCeVCPT9 zC_(^;YgU(iUUFPqf=C{*f!PGKnzkG4uNs-VIR!ph6&0(%cDb7oolCCH7O`QLv)zm` z*4SR_KIRK`fnHpB<-p30gKCnG=9(y}0R{?tINZWZ-%km{+$$++5TD}|p-ag(c3Uig zZWKK{7Micj0LJa0xF68k0Wz4Y(`niju*;_G%^Ww48&IpD3sTw8IschFc2JWy$x@db z!8H-8K~MsS**-S;+P61cwz(()>^RDE99PN0li9FqN);Uv%{Ih5sLD`37%7Rb{{kGl z(Z}IAp6zEz?KEhKxP~(%;B%I{W?`4#mpO8&}j!Eb}`FM}o+d3Yl zO{_w1rz?v+OKJt3z;~@-j-qvTCU(bN13iL5+aj>~xrAIEyB@85t$fOJU81t)dTcn| znL>A$3}#Fvra~ zyDr8I6Ks}v@GngRaIw!s_qtm3v2wrKN8@0^=^)MvZCgeQgn?V9@Uar;^@-a4=cUqh z3#@htDo}GKJxI7FZ|sXt{1iv#93A45~p0+ z2=`pa>X~@O{{Jx zZ6qW0YG|JwRSC)L%NGuUl8_7)OS=qobpcV;tN-GK{Z^0EdGNPIvt#|Jr>%!}Tvlrm^s` zbMwk67QN9p8!-jk)RT*3!UNuAnGp2=$)lx-o@pm1W1SP|UPc(-BmA8`ODm^Apg|d9 zz~sv=;rTJBqT|SQlr$u9-6~&tIuZ4RxYM$xcN)CrCB_8z=0;66o5oc z_KYNJQ8rIE^;oiCbMmqF<-3j8-R==tya*zM5Tk)vo{7~YXzgd<^#s}8O=)i&Z)6W; z8fU<<)nlT~qB&jKtYqiYPK`CzBh4I=ueD51o#AZu36|OW?Y6PD zQvqsD+es+TF{v7L#JcO%T+jWC3d5NWW3L|15OFO2zj6;2Csek^BGBhk`<-`} z&v-^N9~+n$jIECuhkbShtA@e#Pt5iJ4lo|OO+%vJ#?)w7SVkVy6ZHUI7~Ua)efNse z&X-f0*QKICYkZtBeHSP-@j6As*!MU{24 zT8UWVf;uT5!MimXDPuc2P03qy^U}j_rx2Y%j#q_XY->3YEEU>Q1y~~|*2oWp5Bnws z#RYd6F``=UGT#=ik1M%*G(?b?Pu+F(`<$iT*|Yp$VyMqsl95eWbUXLI*g{R^%Lg#m z0BkwSnJ0P4Z5o7DxiW*??fx%xw87=qPyH>_FQ z$X!4LxNT6uSr_-LT4JC1Ga44Db8f!r97H~!qQrLjnl^;t4PPMwd&4CEZ24zZ6|)n3hY z+@J|cBJni+l;J6=p*_`3hH&Ln77Pi57qOT8?JW$<{Ci#HBm0`UN(@k5+`j3%mNVlY zUeNEbv0lO>2N_7&Im=Hn`89r^D8ls?(>zIMjK)Z9>$k3{ubI(+uW{z4*L>5JnQd<- ze>3Ffk9A_4KFMNJcR6>@RSWy$%cJfTc-a*;E4*w@?tyh{d;p8q$S7c(9m?vSPuG*J zV}WyFYzTRiRk(2hv}tFa*1RI=IZ~+1Qsg(TxdGpplSw>N09i{>F&1>d+M z@`=*~*wmpr(^g(rFXwGYR}k~(D`9Jg$xM~}A>?jt;jK4kUDvLw{>`G<486>kA!WiP zu#Lz4aY)8_acp!4Gyn1O>jcK+f(^EYxG?TGM;QaUNQIUsB}FQxH-5ngpT+5rzR%(Q zoa*UAA9YTc<5To3%Ea)SCypcJZGt7Hx(UCHJL89@-=Me@2xV5Kyq*nq$o=Gwt!@e3 zBL11njq(yMY2CLH9mz#0$BkH5_Xk92)c3JfLbZ5TuEl<7hmcnhwUuj&8t<7^NdiFB z;wD$rvZ||H#|4p>L2OCe8&OM#yXz^$+QRJ_q6-b8Evs&W-pS;^#2~3G_Wim?x|E0n zLT+%yKpI`KY8|Uek2ei{C8wSrmPb#f;1;n2TPQ~Ew9ChU8<*Ph=>1iei0J{{yHtmW z$;Id15s7-w#Y)K=OmpCg3x*TKu8rD|i@Um9bcr|_x?xqG`O=hD861fIC4anHnH z_RLxxj6GBm+eKIgwJi%ipxwyLi)3YrevMyRkyCv<^^G1YFy~9c_TqW{_!IJiulO64 zbG`;BJm`vM)PaRw8G}NtjT?00(#J^TwVxcG>bC4{95?%{nwTEV9~F^XA#Nv`N70rN zmduuDnP~>h^67X5L{U~>_So_$qt#yML7Sl#+Fi55?*64_8o4TbyZ3#y@1@T2pVX5@ zbhx20;;APo4l?^wi=^r=HtoC`H(a_zZuujMR5TKMw41+v7M~WOUElXx?yL3dx*OW# z;>hsqW01#RT(y{`N={Ji&x>nw?UrChi#eKOG5W%}B^JE>UUw^$eB*?PUb%#V_&~r# zb|qH`|8OTTxgtVqlJnknTn4JtK@|VujqIIw)OLzdA^Q$P%zyfd;gPffU31~5eSeY= zg1VB#AOdiVznPIpLci?$IF(S?l7Q^AL?8(BW4>g<&m%%QlXvyX>^nvJv;7l{kM({q zXb^=79;Rd$&x-APZ2a&yZDk?NqOZ{1((T6_87UtpS~i~lJsE%G{>X{ClCg8_f!c8* zmhVLpPcH*HHX&*>pC7Qgo$s;^&)5F;myk2#2=Ye_x;Kj?nnd?<( zehKZmXyMUMM&YEP%z)6?=JV6FpV%O+lft_;*B0s!!t$@|QbbF9`apS==-=0XU%m;$ znirDS(+Q`;0mPrL&RAdHN)JLO2ZY-4Aaf+QQ2QlXr~Ah>RcPUsqXYSJ_r9UWl_1gC zph2q-gsVtRezVzqfne-G&HJFV>j5ppTBOh!h-Y6Q>r@JiD5$Z3tQ8)W^u?V$;s(9y z_lD(4EpAoXOYiXJ=AW%O% ztn#Sv)Bx}&8+dBWsRg`$nzkrMBKW;P?NfRl1w2(~6y9C?h52cAYE^&$YtUCnZv(6W zkFRcm%!I)gPs7c6E%K)wJyhlC& z&+r;F35mm7TKuuYyg9{`^8~bHnfdr^79nG`f6a~(zyZX5N2Hd^2mI~7P3NaKCKjfd zCTFK;Jba9j@Oax^SiKAmuduD5aXth)U9V%UM5`HW&dl5NB+Ea`7hQLj=68FI*_QrL zGrMO3Zr%n02NyvAh?R#GjCEss%iKf5`_|;_z%`Hh#nBm1`1cs$i|V-ZQ-%o^UiBk< z^}=Fu3Z$$awl%SkSNsdqrdTgL3L!^n;NdfGwY4n`t*JL?<3pEc3A2*_2@oFFeLT?g zw+T@pHQk#Gy*53&tY~A^a%-B~ZXO%-mIc2Lc+gTdJaedZgua=HGbmjLhuFztlBxoa z8hj`<@;1{KAO`(l^dJo~mujf#i=QPj$HO%bS!V?p5$9jzdH8E~z}f~tC3m}5b+h;p zdUO4j`84a)E@n0S2HLL9@pS+5(CM;=Ad*y*adH$ErUfw>1a0L5&nc@OqipW_03;56 z?`Q8G;g$7HYqQ4Tg2osAH)lqVRX2C-1u$@c80PISo1;;CX$+J_Fv-_s?f9zd(ShcB zNN&f*Oo&U^``f1Q`k0Bu#n4}8A?%xNj|4He+jmcQKL&#@>@jYAn44omzn}F&J9`Oi z(wNK+wWGsFS4rRkpD+y-nifkTZZb^}x= zW(S09>dS)2q}Q7e1$|XrW9RStBsY!`gC4=}b|v}emihevPNiJPWedx@9ch?s?k+om zHDF?BR38mk>&^S~29F8^%h87ur#eUupBg;Q26wo113mEPGguAY@2eR;nZkLKBo~?k zggLuvW!`FDSl}>flW{+4&QdxLJW%Wn-%P=bkmvl~v`^6lrtd-uQ(p(e;q2^e+RDSf z)^}A@J^n0I-e=O^v@^wyB&BDaKMkmb%**$Kz0fQqoF}D=5MOyc^v6U>h5WA+=Ydn{ z4jmG1v#2edeB92?@2?>9vZB7eA+Se$8zc-g)Np!cMml9lgQN{?@J_l2``@gEv^iLm!_&7cH}nQFp*=*sc7M^V5n6mJ9XLtm=ZAS=)VFK=buTVfiM#aFU$Os0t+i zAI=MZgeGQ9D$7Kh;0n{m6ak^8slMTohq%@2z3PT*zYQ7_&*VK=`$RWpiYx+g7s_Dv z$!{Sd^$jUnN*B}C-#}S;N~6I3?=$P%*|f9*CpD2uqFTypKR+n}zNs8MuXll9omYOJ zr(z(LWS|85TMqwbLUPzAAnJ)zpf;PeNjmY1OylV;Tq1FnCEM+c|D?un0s@i$QV(mH zUuw+TSG#D3DJ@tpKG1mhyrwI7VnO}p44T>{56u4}QCX>tSLFcm`)io` z?iP;cuXi7%ItC*eXya2%{wAZVh^WKL@wb7N&^K*BA9@$yq4Gfx(iI~5Vg*w%5E`~! z`;Xf#0h)#9;No9hdg3Woc5A=i8UQPrhtczinhV0&eNe%ZCgZ2)u%u9XeSk*ZO#h5U z*10vRf97TS$?@7t5<%_j;!l?G6u*aSzw3K7Lmsagg2Z8#mZ#2s;um$cYke;bw9BiG znhgxdm;SE(JTcIgEk7z8HaLHobTo-I0BqkN?T;#gHA|1+Z2?<-Mv|nB7okW|`!vY< z^P^^j5{OnHxbf3Oigd2xG`Eucy262vJSCQpqzDe-lO*lTxSsm{84N|%#x96x4-yx8 z`iJ~8f>G zML&f%6`wiXUoD#+T-lQ5CL^|8nVRNiCyl9@FvydQtm(oV6;*=UTgk(1j^x-Po*hRUC*gD=gaN8g+geSoT}+eDcdt#w zk4^L!Z@&+2$w*U`xwc&ppGK>3Ex20FVBx0q+HsHpq%6JWgRcR^tFde{Gj|FEyZ>jP zh#MSo|Hih^q@uePn+ zKy;DPfrD1l?|lq4?)1AfY2#GMSy1Of^Te_5T>OXLu^lQF9MV72)Z(4zQkCzTpNI2Ta>f(>Ep(Q4jgUTeFL2qCY1BjLx=JcO}pRUw(jt zD|jl=_dfJYY9GHa(8w$PyioCCAgJp2D=^RY#&*y^`YCSy{aCV9!Y{bb3x1Qwe8DZl zO5StI(`tpTxre!F;UQv&Lj{tj{$(F6drKL>Cz3;_#Z75hA@YYq55(nXYvUA8nE8yV>qQBCuDDeD_}2pBN;V{pv?&yx%V_PwRWP zBaIG)AVqoyo1{|G`i7*@!$IOH_0M`8LFBx(>V-;tKLUTeiYcMAIzqRlpD4adgB#yK zj|MoyoW3TG4v~i<3PA1u*u{a*r4GyL)A~ZLujATL^8GUCVbTy7`Lpz8pQO*!XnjVa zBz0m7D3o87VPt$Lyn8hH*uEsoO-D^buCyepx;0U9ienQ!0BghsRf(VO zvku)+{!zMMVmuV2q#3K7$CQrSQg%i*>GfvovV+?M#V{5=;_x8j>H63tqKg>6DhD6h zfRf+sRN$o7P$^|a`QSr`lJ;EV_Y~Hjt~uY`RwM)an^Y(IO?Xm$msX`ory{ArU)$1P z53iv|E0)1p>8ZZX6=|?%d=7o@1W$$|v#~+Z+F-3~srzD0jSC`~gDurBBOo~|O4^z~ zzPCXt%R)W~oDAE&o>z7ogNKOoi&m{o!qLJutXawju*fPAZ=V&lsSB%)skXW;Ty=AK z8O&^BdpTh^&}szJ=OcyIR}}}@TA!YMP(8)H6eA%E(k{NLHUAx*on1{JfkI+Kefk9L z$y9RM+?~C>IkR6p7@&Ff5P6<8c>2;$47u>$xGnyEDQuTiRppKS27>E&ta}1B*Qf6G zMm}G~ffqh6CSS0DL}~N-@6-|HNiA;&31hEeK!EJmT&VZVXyCgYYXvXXb%b~((-%El^1@c_l^l1o%51_Inx)ay*(puT8(Y!G!g8bXfe)L^0c z%h7S7L3q83J<{Vl&_}{mXe0AsTk_DS*?l$m5$3(#>E?(ZIE-fMz3=D*=FmezBk%bq zppR_PV%WylnbBn@CGD(nOmRjT%u3+oWMX0$+=$s;i7$i6@CBbGSDnu}u51tWhn-S8 zlc7Iqes9obmu7Z4#Ddfw?FXL;ijTq`RKQclO{@mN&Gl*Za4F1GlEHa+`qAW&V=pOo zg`y=%GyG#&7o%YCImZN?kw5sXs0spcD}xyhfi-^hue)X9)@{WB0GsL2#w23!ResvK ztQm}QV@39726`rNf!Q;}+a%Es<(zYF$OK1J(jo10Klq=mdxC>)`y0{+ z`VURga2GkhPW`=ZqAfI?TYg~O`qoK5!gs`M*4GvB)Y^kBi+j-!B>uhzD0c)Eo*t}l zVu8#v9tdh!G=aljEA7+0FO^B;PvTB zkHLIrT%LbW`%Ol;(}$91myP8@)Jw_dHoR=6OQo@mFYT`A zBG4#&l+SU+!o?mz_y{}?P-2lJ-UBRtc%Q}S0nhSlo(mLGz`M3)?5o+(BXiDN!u;nO zcKapKuUMShp69MC8iY;2XF?ly=~VE(&xh`4BC8j?mHY1yUz9>xdt8J@oUms%Gtbg2 zds|cL6T7UEw`O|u|M<Xloq^`r~76vD?P@m zlE*PX8nQ#5uSS$%E+>+5{ggy*lZ@c1f&6`O-&F9fE}9P=v9z-3XXz=PIJ~JvFLD}r z1jfjc4R}2`#9I1TzP?!S#M*;(bYg3J`BT|DgJ%k)Lsg^U}w+ z#N9S$hQ!bVMLVasPvXbo64D#E%^T=mCeg6D63MpD8@6U}==Q-`Ji-KrnYHTuAW3@! z?k9g>t@%Vg{5iks4*xM@qf2PKQE8v#P*_YoViymbretvzeVI9z70h?$y)ywv^Pjoq zx{^TiC=f>W*hafo`;S_q{Dv1c%l^a3)ilI~g<5-ZiYk8SstalNB{qC2`KOiOnX8>E z(ZmrO?wWBPbfq2=R@{FYP83IyG`@zrD&c!tiU8k!md;!VHNpm8VzZdj&!Q{`aF~N9 zYkgZ}D?q;qvJ2VT-pU+KF$Q5~ddN+`qw`{Uml0n(4z~ zwG+F^n&W5l!r3!}*lCPpVc94n-&q{zD#S4b;qwuAx|vN6)@sQ(DQQFi?&$3>wj_}{ z3=bMp5_f0cq~rFK(Z_FuSI<@wAQPj>U<&Bn5gV2xVc{YV0w0u@9a%v)dKL7Jpk@lwIG*a z2!KmIJ&z5bVL$Ox&K;-b}ps|Jn&LI%LiDf1&Aj5T42lG#c7*Afx|pOZ-4j#VO$y|I)d zS^JWWisvd>`)*j#UflcOx@LsWhAjG_rgM%5*?(xGkK<;(=8(zv3RVWK{pl(8>aO@3Ez8hI;;q+!Az4i-FnL!mTsq=2VTCHsO%gvs&! zwbjV#Bg}$T?3Fi(DMofve)9DOX*E97r1dO>)bf1bw5}K-za~d=Pe#aRK01AZL7oO@r0q`G`5gt_ z+LRwn^!M_yIc_y60xe3}^i8Yc8!HBa76wu4gL0&+ov6_t8vv4mMoENvM zhvi90VWo{#5I!HQ2`&{M8W0Ec$#NC(RmKVJrBUwEh?A-4zA z@h>!yq{URo`~vxH$?fv*2tWq8f-WP$B(E%-&P!)eX;{sNs$T71q1kXVfiuH}iMGcU zle5M3&yEcTa0jgE5MJVaA6WfYQGem$`qIb=5q!?{5XkOyJbo89DoZ{)9XSrld=Rdd zHUAOxwe4=pSkuMGd=!*Fyjz~6d2!PN%AXWqJv^G+r+Pp% zahWsrTsgFQ&>VJV=~KTmp?!>gY7s;C*f<5gma?3cvgjgNyxpm@R zC#U=0d+3DBRecXR>T^NnjJgnyZXMv8B>rnKtU z#RGAl6TO{x(4W7CM8yuVNGVd2OUA$Nd@ys#^XEGmNSRo)f)s+X9I{O`1cUo4UL*e> zimnBo>Gl7gPUn}?QFf|R$YqsVC<{l(Y^PL6>~xXjvI?ohj9kWSr_(9PWz0RZost|8 zAvT#UVGM1j6uE8L#!iOW7TfIh-~aXUeSKfg*VkV5Jm2r<`MfXB^D#fkaVeUH)7|DL z3_oB){(mO@y)t&dOv%V}VLx+|KC}_M23XXNC#NYPy7saRH6D$n-6`i-?jA;We;9Ok zDqVAZ`L}l6+XYwdBF+Cam#I+gG7~x7KxDxdB>R$dsxd_tJgbtsF&};TpGE^G6D(W= zt8AvbOELZeTDpEudIA$9Qih-8ASy@}Brh1<$uL@*6}#A`8l+@IS4AScYw3DyNk8gB zy03?;j`54>`>fz0=hkzZi^{tW^8LN^kT7(9qU0oPIkPercfpx)SxTcK9C@re0%jttE0 z_^)sf`Rg3jS{Aj}id`14{=Tv(1AD_s^H0fp-17jedAxvocnF=*qp1FJ;H&S{PycED z)0YhFBxZp1^dCKOywwc0GO;B|%A2?1Xs=E5x?wyAw z!%}V_Trd46DRp6iUDtxo1!T$&OM}d6BO+w~PJOz5BYXLz*T~+b0q%8ZO^ijwr$Mjo zLRCrf!0I@?5c%xSBAmm)rJLDB@S6vh5+4=8kJ$1@Qut!zi^A2Do2NxQa$7r0X&YY>h zy85YC$g*N@lP$oR-8>_R7+djsLwO>ASXw^56P$}_aOZ+9i$wKrS< zO|DYtf+*5Ki~a`_L2!wT`Bi#sCWclOEx6~zRG0P-8U~lAe7S7}64+~QzbrBy+Pn1p z`TM48eq9R-3Db4MXyFw>(fTRfs=M@(HPqyadgPwjzzf8QDcAA;SB40agVIs}};-LqJyS9X3h=ed_C+xdRtLnDVDD z%LQqgTJ)-j7_lDZX~lMp7$fl^3rklz8x6kCM+ZeIX}o)@|IgcLoIec8&}Cvf()1IO z82$m)W}>0>Qheq6v5ZCF0IxJRzz2uK=j2B=r<=U#5|LDHaYTd-3VQCTO z^^_v>-2Visfa*Lk=tT*No?T>oX+PKZb;U`#)7==&q47abZ5iefW6;W`P;CFng;sal zm!Y#hX<7gH+rayik=I`}0#k!Vn}?l3p;Y&^x{tyxhL6(X!l@i%ymo48(D;9PZKptz z*>L=W_$L$PlgF(=*W(1a%S^QMK$zG!zZPgQ*<4O<`<82Lk+07Cmb*=l7Ml+&dER+X z#GO)^pS=KOxqmPcYir59!5^A-Z*`_|_b)}q0yG}DX!O)ALA7mw7xU_?V)B`0tO#zLioy5NiO2--O5@)k_E_=KCrn2r8^(1IRYs{KBE@FAMKN6 z+FQ{-9%~Zc1ZdVVC6efJ{d=dMSwEh{)VGAoCmAVX2N@>bN__k1ygYfHn_ZsTYL^qK zJMmOI+~kTe-lMD?JBeZ9QMx;l%rb6mM75T2k>p3a-ckg5ZY{%jTfu(~9NX-jk3M60 zgQUz~`&uG-npXr`51w42^sJ-q4QK2rE=n8PCt5RL&|qm$FH80yNV89-wY8#`9A7`N zsRoWNltjNOO1pVbGxukU*!A>!K=#M=)gxmko0?n1qbJ4ytNM7fv6E)@B#%4p2R#zC zs3Mdj54053J>MtFPCW^^+1>~YGNtqV95u^-NxJgZ9%Gpp;!V&tAKJ?ZSENegt) zpfi~smra!|0ZA4$2fGO&F~sLr1@CV9L$`__!^;o0auj2ORdZ#Sv|sOV#Mg#1_y$&p zwNMO6(I)XOwv=Y!zte-s@;Q~YB}4AN;72#p6aO8|bg%b=^qoZ+1vi?pP4sUy_w;P- z`Smm1eHq^D(p6c8m2MYo{k;)iZs`A1>ofk}oERh$o@0oANf=C5yiruYP5J6;Xtyxe zY_&wk`gaphwZK4E>8;hLaDj{T*3?t@N7_?8-;q}wpRb+*hx*POUQeYj$ zUSNRk^~K<3ci>l? z5|bhsQFxuv4RBNgVV<=*ot?i;;E&FM=WP9tsU|Z?-D#&E7Fzp1QV-d`JL6BU{qVd0 zIsb>&*2>af{r!&NZ8rxxY~5RL&1^2Tehlj*8OL>7;>2kjErP|5s+O-WM#ggEx{2eL zM3>~<%e$vZ=K_}5H*kQu#yq<2`f@Ky@*jl@ysA6WuTA;iLRzaO_4IN>=w|qoF9R!w z%%5qq#(p+gCOQ`AmQQ=U)>?GJHbzF4qWLK!<%b7p9J>)Lrcl#GpVd9bBhZy7)@D57 zG@mV=gD6#%Y;-wJH3v|vOwBlp#j_2s6brzSSXw1eK0KiiLc^}=z<04tRQm+d&^J}e zIAi_pe_Ty1mb`=e$RC3+)MSd@UZn3(ClpJvY>+n|3b?ywvoG+n87rTUNt0alNz0(- zQ#Ow@hc9=Ftb5X&*X_snf!?R+@=!r zjFG+!J6oltJl$!WSLI`bdJ+7)!>_#tLgo0AGbzbM?ZoM}Ip1)5ordA9Q~^a536 zL8&V8HBZLEi!+Q#^lxX?S&&&wMXvx;@u`pWsTH1Tt3}u?{6gA?YTgQ%CA^L&@A0T;@&7WxL~8-EY; z?u)yk;3TKOfb|xdpljZFc}u4K^1uPv>qmGUs!*Y8SsZVyg`bSvAs_A;(O0AA%>xf)&NQChnEM;OnjS)z_7UWv`2z?NOto%Yqrcq1G!893xnuDD-G@M zjr?i>%IS*v1WXFH<7MaI*{D*az|<2PwVgkBkvS!(Fu;I>2=ItUQmY`|f!DANFZ3)y zeSvZwsJfy2;mb_ft*hqs_DaHGMSK~d?M$U&jZ;X$8oSVbEkm>REZ3wf{-SWrzOd)m z1KeaodBC?qm5rqh%YBS$K+B3)v*$3fV{-tAeMEApx|cYA;KSCW3TW;TCW{x z442hfl1##s$^W6{!j|E?$inL!SGTq2PdVJSe}5*CoP` zwYEt?QqoFnj$lW^HFH$B^g9rrtqNl-$-E^3yNOtTe($;d>=0gWvJEs5ipkWCQL?lF z(-qdrJG4AQujp_O3oOP#z7g8vvD!8bpl-0_mV&X4j9gS}lg<@`ImU9Q+(Rz?6e?!N zhF2UAY1SL%2E!RT1K!A$)+UgF@i_rlKqDC6{)>=_c9+m=1RD+~M*eH!3FriiH1}IM zy6>G5EX?JZP@XPKN=SE9Ou|_{S}XYtbTYEBt;plwFzF_%PAq>ohLYpK8Hd(S$ufmXannH@~Xl-(s{Chx{Ll_jS+ejz9KBicN; z&&mlkUAsuwBpE99ST7SE1bS&)95y@G7&x98ibg zn3H=WMu03ldbsu#%;E-V;}>$S;7Vl8Ob1itA$`R@h9WJxjrb&J_pbGxpS;res+afS zDW;l0lI@k14a_sftM(e9_lRfyIpy`LJT?7~OZ;2ySV`Tnr-)r&823;xKiRe^a$+g$hr?n{#&rR%!_ zh$2*aV;XnfpH)@OrpEP>Td|#&RmOYktq@P|ERlHMBekTsli&aNx^}pcmmP**+o*}a9>M=iQx+)od z^ysq4qz@fcOPGS*&ni6KtH?~)yPRb!l#zS0=LF&JA{KGs>$Jrkr2z}stGRUc)%lHX zbJTaIi=WaS6B?A?@ukH}ytU>(5mUpSmE*3g3nj@eP%Gj{_Z|(WT9VLC7}aiN?}%`VSoV6$jwa%D49y!9<0>L*Ex+W>$+M_^?x#2iLoMoZ;3BCr zudaJ8d>|?D_EW*;sadk;c;C`F1A04NW;x)RK-@2MUyDJb(W#5YE{c~g2wk6xB~6bB z&6G0ai~EF{&));fijudHdoy87T*_7IMUe0Ydmolksk(RbEFG#{Ogu4-b2Not3g9#X zAk^76pR@BNOZ2(!e_Tz|T=4WuIH)-5*96^yV(ALgH~%Vlw;igkNs*Wg(P|qc%z61$ z(&8SYa3|FBvy7S@*02;?$i%d}me^o|!&I2c1H#s08ub(UUa=-|{J79{!FU@2UIxa! z*LuyN`G`RonCndA7=0Ta-Z_Wa!dvmj>*`O_Z{yV?X5WU*Mx(>8<3Dn3P*n#NU`J(H z>MI!&9};?+8oy6jyk!YhCV-7?M~F^g{yt$a@7fBGnj=%18o44Dy@s%=GUo{ogyLX( zf({q2X2!3J&V=I1V<5ku7~_k+HupZIXY2rE4rQnh^Mo5)F;JY=`}g%U3uNo@F>zN; zwtVnNw~gdwX=7K{4mo4^wd#NB->oqheZbTcW8<<1+4(URGv*|!mAHry00ti$>rYu> z-fPubz`#^Z&qew+$PHW3kpHx?r2i{Y#oiOtgwOh`W(Ahh=YwiU(9yG%eN5!gxw#^&~N@|syRN5GuVzWOXl+Le1yeDC;_1% z7kZ)Gk9l-qB4gC4HE8;RW4-PRM`53-Ly+C)@#9GYS+LqWNs%Q`W9D_>2wqSQCujWFvV14`cD&0mp6v~?!(x| zL%D^@z29W*4igS*3KdW3jp9w)gdsY|1RZHb_hTsiwiPD`zv_owpSv^46J?bvJOjq8 zs;=u|s(5P%A1|@FIaYV=+weXV=(9Oba1#Z(7(c0WMd`-P*JA=mip;TR)x-W?+>-?A z`7sdrJK*Kqi z+06A?Z;e;rn0>xUtdN;mU$$MoX*eT#6H@$BD6WK;9TCV&jXXgQDaAk^O5{PIxL^CA zOVOIvyp*A?YJSSl4MiPIp&vDwUnu>w73Xka%1~;0r?87cHyS|9#dy2qQC$Bln(1f7buxc&P0{>F4`N2erSb+IGu5KWc0f*v&cYt~u}DeI)T!#=JHA9`Z1* zjPfpP+bE-t1V0HTwd&E*xs_zLy+qDE4n`ah)p!rz^Ik1|_&VBX|4+K{O(b2wSfF8! zFnjpOKN}#xeWGe+pO7u+hL)}@arIw&6Sre0*Qt+ejQxcy`;E88Y&E+12Vjc3NAo=8&&iJ-`~~HB3Dppe^LX?z4-9 zc`LnL2%QHzVsJj_== zuMm9dvLAgUD>6Nr`gt$EKMV_7Xcz>qmFOXSa%*H2%frQQEG{rd2OCCdVJ`IUi>%pZ z#(GmxqxilnZLthJex6a&)4JT?kJg5=>06;Ogz8XF!i}v4{W{}^2g7#V$6pW!49H<2(Ub(gP`Mk*>6HjJ>Bb}{F&RK?1Zb-xILnYq!qUl zyf^1PGX4W1Ra@qj%MAi`Ev@ekow-ilN*z(gE*67Wrx*0I{?X@eXJS*LmhZm}nQy0P zK=;b&L!Lo~rdS~vm(_kJwV_c(Uy-&_#WfqyFTl=gQ*q!B?akqfSQq~4v-lQ(a7XMW z=4&qid|;^a?CPxJL6PKC;;ZDc>5$*u>*-%L3h{*(r5~~BzG`Jxj@`hJes^wkzTN{0 zrD(tPUc|zZwP)#nsB(jzV63q6@KAc%UtJDwXObv!2d3Tr>>|1kXdw4dCnBZabv=;z z#B@XN2q|IAPXCzMWndEIY{>lHfH`e92Rz^%!8&^Bom!B{1Q0f-+29Q-h9S2G=>1EF zHY{Uy|1#dpr%jXRG<#A$lV7NdN4Q@G|Eie?RA}+B*Ei#0=E9$$rm-9sNo5h3!rd7K~q^Wf#CwI3I(mr!upiea0Y}hD4SO?l|eHVxN3V;UeZd^ z_`=0H5WAFQEd*s9CET(@;Q}OK^#bq6D74s7nGc4TIw&8`e#`XwDz8^WM~2nMQZ4n@ zpDP}}!?gfAoJnHrt;Ou;fhKj*YKrx8^W(hnIcu?ViM}>LlR_4jV^?D>eow?S z9zfx0^Tx^E=433CG4aBM70FJ&6v+Y|sS7*vzUKs7+|-}EUrW!=8|GWXK0HE>S;M5J zk}IzWX}6vEemgzs*EjIRRY~;GoDqcTls0-Z|7*IZ_#YdL2!yIY<+Z)D;hr5)oDF1o zJ?c|rRtT;*xPapIU?W4(@mdgUd6iIYEwr_G>+#_j%XfiIQBtV%%zgYtWBdT8kD|0= zHk9-V4F$CNp1i7`$W7K_2;G5FSNPAs`K+I@B$t&Gxf2uS;>1;- zU!2NMbcb^?KkueRW}cXLZC_#bU5wqh1dfm%_MUCJpE&;0@+ljHD+#H-&SZbg?rN9P z7VE)v7cLNcUkqWE|2vJ2KsY;v6row``_hH9Z&v^4o$uixEVtc5w8UT(glw;lv{I7n zOq;o-XpEs^$zEvTJ>UV+-|>7iz3vq4`WC*z2M>B~Nbi~>^hN~Y*MGwMK6>LZb((e5 z!4MubN3Chfs{=AM5e=q5L>c-Ff-(9+O3Qnk_+ECbGUQM~Q?cBGv>9j5^m=y?_)gu$WQcAedBH=Kph~x1pirnfvGuf zRbh}?oP5VA9{*9dUmh9{y^B1~GF<4RCR7j%zwPF${Ft~Gg3jlXq{!bbfsyv8w)#AA ztRJV*4uTHk#EzQ)S;caT=KheBzK~$aEZmxXxL8)t6 z!yFCku%+mk)sbYUQgno`$C8eFk?t<5%)rOSy;H{)fd|%2kqQG8d+^BQ(vQr#h!KkV zJ{6a~K}LFyFAsZ5jp?T16vw4W@z$VmyvOesq)D<0GTKJoxzm+~wG!JC`_xZg3%CnC zs?B(8=06wC(&+-OtxK~5EDiDov^FyVGC;CqJaC)K3-SNH)5jDj0=W8IcRolZo>IZu*YM zN4*cl!@5j@22MdqqY1K!SV!wD)(oVGvGKeh7ETMh@H68{n2j{GMicsvmtY z7L$@Kn0Wq1{ADAL4ae!s9_WBK-+I`fI!3?5lMs?{Vaq^cMVQvVlJ&Ucc_O%Ltd^)GutS8_-U$s3II5ChDV#IAMG?Ob8Lq zB{^MnL$3lob%`Fdj6hC6YrZfQUL?ogehM^Zs(i~_ff#>IR9BhubOclE_Cy0Oluvd1 zk-G^muj&CS?NMTV%_U(?c3JME&~x4n#oSTwx3@TKCpjzv&ABMej+%W!Omx1Z^l@eG z&>pvpLUw*;$5b6^E=FH=M{^{5^+SxUuwjQZMG=2LC&e6;OnpAsY8g#@8AdEW5JPgtTjd-5Vwx8=*o)bcgifkF`d7ZzF$ub`Wqxz8 zO|u6PNv#!V3f_`M(ZHqi%k+$+EZ}E<>iYTRIEx++h>!G6jEMnxIP>BWR~l(As$V40 zy<9^=Y5g7;{no>Xf$i9iq0S#jJtu5tpV;viXFl=iUDc_}ASJUsPnB%Dn>%DJqW2g#Yr;d&lpwu zRv)9@+>aXmUb4{2Z~Q8NY?Z3wy5`_PTj&4{h{ZcUf@AXgKA*{GZ%AG7ilMl zAG~5293XAz?<=mOJ=CC`xXIo9?3WIB)!;% zeAX@qX`ZA0StCGqn2rntl~6^#U~CqN(3`p;e7r3Ab0jbPPv1Fed-0dsSWm=KB+G_! ziWTPdb5O`&X12jc|ApkFrpaR-@p}k|{|3hWR98IP@^Iy%0VZu2Ek2&!Xp8zH8If!E zq4+L|Y0m_icH6S!{4k1(imdsM3RTEoqmy``UXyOl6=aM5;1&UgJU5QWPX=%(P}yt- z-kk#o0x7c_xmDJyxBv)h2eRvqpt!4-&-UhvZf>Oa<6|)esHjWQk+}Z`$_pNh0uos-#o#TKgSts{q}(Y31`JDSJmnGi@K+qWs<{64$5ImoYx;pcG;g zo8ErsNpIQ?HZr#mp@(E*!#VZq+%MmL^Q>Fm$@0H$C43(<`z_KlZTTomU%{1C?PU_g z#wQD|xd2G}l`Cc*&{-=KAZ68P#LNZ;$yO>4FpI~pX;B>lrmr&qkDc)&^n!N3lD^nF@L}8YCx8miu!q3??Id*m4+f+9_WU@zi0Qa3Dy;?98P$`s` zP}dJn&VI+tOp7Ik21_{&73}dMwQ~3T63*XJ^{bRKVxN{%xNjfeeBL7rDgF z@F-WR+ws}Wz3A25OcMLDrS@;ZREV<^)LQs!!8Huz6pIYbM_zfUXPamj&T@qb69Aj0 zNNeQkex7hjI=y~iURXZVSzuMcrt8fcpnjo&A2r@jjb5Lic#}sVVx~;&W&)QcB?$5f zs`sZ@n-}}Qx-3*A=2w0|uqRO3U^J3B7v3|RQyif`Rgd`S^Mk*SxJKSCmhIK`VkX*? zjAp||U>(d%Cd`&&R4m(EwOwcM;z6_M4AINdPPohRsJTAfI@oxcTJ5N^az{d#Z~?gY zXd~$KnlJ56jd;#`K6^)Y`7mTSnzz7UwQxHQu`Bng4)k8EBkWNfuz1jmp#Dtm9Un

{|h6k5M%jIneeTQTzebfsHxBtk`}C!pB^z$AeoW zYx^hMLmBI}^^3SgUp4jlnQ5Y-X`1aIW?*9@+^9%XbnbyrK8!iFk#}>)gh3@MLkMd{ zoV`cU6cZZ$&GkT6^v$MKg5t|ohgTc#b_{xM`ShT{cn~vW&?hu*&YeQJ@F!zknz9(` zg@c}HueKu;C(SMQ3QrovX8oEJsP;B+35u{&9Z0_D{^^rkusxf!uuaZCJdXes z;ON3@le0Ha2~Q47P&rgYoTU~jAN;H%w`dm*2v3h$L9pNuqL(cMYtM!scwYJuXs3d7 zRRYV(We#0CpuU63;c&tfK#0^eY2BY2NY3Wv=)T*9$8MDVBZWv$62Y)nVB{N$~2Ad`CZ!GXz6Ks|bmJ4Tg9nwbbQC;BcV?k*QaN#&F7(Rh`)R(bj zG=XsN34dfI%sy|;qJ9K7iJ2Ko5uPDWZ6r^rgwfBW)p7q;?khbJ1xqsWH{|ucZw0vF zQZBfAjw5&QrQL{uBdcGsI;o(>t>M7^stfrKHX8VwS8Sk@LvmHfeWLa}f;{~fwWYnf z?5;c~$tW7`p?}{Dt47qR4;O2K-|EtaAd{Qs%~t282QMT>gt79?ALs@KX;asDp95RP zhe@lKQ#@m(2Y9{PwgVS>GlM;`(i^;9sJd1fYBc@0R6V{_Zkfs zhB`~Oej%3nsh2L071{KL4#b3kFf;n+xyCTcrrf~7T z`PnW*$!bR|(KB!j*ruS5Pqe_cMDEr<9|4si<0YCQgV`qo#?Hp)DiGy^sM4*Ku|XwS z%)7I53q?|}RrvYhEXIwIhK4~1e|Cg>Qh&0ZXUEOaYSvbyi*G0v_nql82icO~J{^Dw z;h*)$5L8suG@Ig{4brY)=a~^Pq?WzZ>6VKu@5|b;-_}$G)*Pq8tlq#^J{uRR*p-q?iF;MIxh|NU!x-+dc`}qK%L@6KOvfj)%-)FOTe;*v zCD-pSiUO|@Lw(m)AI`q6B*;QHV@SoxaF`7QZm;?SkaBstB>5=dj{H%Q(|=JTA%GH1 z4|F>jSET9bX_PxMJ)gh}%cu5=OU;VHHu~ywZn6okalsN^j>TNDn^{SlV`ha&=H>X_*Nn?z=;S?*VB@f80 zXf)TB?Mh^2eZiKf-iWpVBT=4Bh(NvDUzKBMGT_LhaSo1Tjx(up14h?y3|xGi*ZVw; zr(OD995-h!^&bhb%g_i1GPDMt2URi38TE2;`qzB`(nxZ62s8)80fsAYzO^Sn}Ra&MbB``*N_~Ke!#;njWhScC7 zBly_B6dNS6hI^7#qZr8H;I*ob?RJK(u#ZGk|5;R1vc0T!I0jp{m#>9K>uN1zWjUx@i5^d>Ze_cX@nn*n+H`TumRmbuFh^&w*i}@fi~)4p|*)Kr}XE>9Yl&g@B(83h04!lN3G3BZ{+0`{kpMU zJ!jd{ij|lmQ}Pzi5dg+^Lo`P2NLXtt4C##gJ8v=Wz!lC1*cvG4kD7DQx+f+W@&VkG z&T`(hZdiNbe~R@WPGMtmOH zu&1|U*GzYjr@-pefV-a&wIi@ye#A~o2s%1u+Nj|{l$e=yv~HHXl%!)H;IVt~<1g-T z7Xq}Yzsj=p4yDY??lhQ#k~l6d|0uRj2)9vAF%*gI({`hk$~VK={Y%s9IzIItl-~;w zXg<1DLVpP$t{vpDNj9)dLr6GC{GzDDls2~6Uy@>K)q z`~eb>u#?=oB^@n$s*w5i%y>ESFkq6Z0amDL82%z0Fv^*#vx88V-%+xzxh{(fk-F<@ zbcx{LfJ*^spOQ&38JG&dw3=be4k(#vrdVL1I@%TO_Pew=*#v9nhiN^GNpIaxDavUlh8NH3aO0qGMgPsd#l{1UfQMn!e+R^(vH^xqh}U z+sX!)2IQ}fdh4|iLu0eK?(FVao*Rljd$jM45QdvtI>L~b8$Hxe3&5NMOn&=cHJkND zt-sP0L)bo{Ky=fO)?|rZEkndNjm*I2VJSkTItG}-?7%`rB* z3ma+ox8!?H9t9)zE6K9i<(dFKoBkun&Ja{HE0XSr(e{F$)(PfPTZ!wUq)OeP$kV0j zsm+P*%UXr3u4mj>I?dbq|E>#2{4R;ULkt=>q1#K*lfu3wzP0Y~Bc;y?JrDJL8o*Dx z6g!9OIPMM8~78Keoyc8liu>F08FE9q9fqj*}5>unl476{GHiWeTq(U z&}dCGkZ3vutMj9)Gfz!}xIY8S0%-qiTtXEJ?s5K*&*Ozk$E7tGQ>M;ECKgfUOMa6t z{G?!Aw7L2WN40j4uvP?S?|e07r|MridwSqrXwve9WJWa$FRUD-$o?jjr+km0i142Q$);&iywKh^ z^7gzO%W02Y)62pY6GES?dO(HN7LKKB}4}ayV0w{y-4FyLVWB4;$`*gqf&5f9-K@y zCMRckyl_$yg7e_=X(v@$9=y#Oan6$V=oID|kUxGCrH!*fQmO>8mwJbH>l+*RA#f&} z@IdG2kGZy;Kkn1q&!rpFynWEB4YVvVpan6->utf4JdJ^CkL=}!+sZA=)FWYv_^Nj_0GNaXc-0QVu;g}W8LSs-@sr&;Z*=QDh9=RL~p z3IS>hFsK;JPG62SyM26`xU?d)!RzdMHMh)!eO#DEZ$lb2B9;3=V$G&4K&Yzz5uZ%~ zM7z7XFn>s$6~-%E>Ipamt+z*2%_Uy4MiEac7FtiAy9z_=N;4%&Kj3_2qB z%d3KW1-ZL<>`6T65knCPm!8Px_vYy@Il~fcl~1nZjo0b@ z>YMngTTJ8XTmW5{InDBQ#x~ibo(d(irKS8-%N_;flJvw>4#`WEa<09Ked`#qIrp!3x@8tjYPvlpU;0~zAj})oO$y6zzVnK77xr{Ju)7)My z46gH!6vgs%N<4kxhjA-wZi*XHe482f!n*lWsPt0)Yf2Ofo5?xB)xARkNBpY}1@Q}K0=zNd?aRP9h(Bedr!h6V4buixV-!9p4=u`VCc99Z%VZSDW$^mRwV6%Z3@*XV$ZYzorao;BlQe%r zGe=M-=WMLS12c)r&t6jVzSMQi^;wNaI$|%XEhuqD`cjc0jwY`0mXO#NPq_xH0=iDS z(nS(XtiSoX86O+Q_E5O%gRlYICo*U1iWgh+3F=5|3YsQ$NJc5ZkR;&b(^-j}T9%Fp zkb4m6ce$)x^sM7x!ah`I+X{0Vuek0j7sn}5*{m?9T1l(hWyM4IfvMwAqt_1lzIm^g zTQJRAH6FB^>a67$h^wCzWB7Hz6xPp`{kE>DZ$Z)lmx;0mf{jpHT5uuy%e`;nU_4;j z4{8K;r8Qw)*-CZ*l1_ex75VF&{>GNiSj$c?S6xbtZ-nNlYK~Z1klEvnZ5^Pzl%Ojb zd^weSn;~(*4>T}dN7d_{IY($2iP7q;`>v6&LiN-MMf@GXL`M%IM=S!ixAE8odE=|rII?+@`(W0WP_&n?G$sFH8ezZkwSj3lsSrKWqe#9ln7ei; z5>Nu>Y|R_S`O%j5kAZTz4>U4QzSI-n-1P$&b6)=rgqW5i3e{moSWCU^RH{JDm?CwD z8Dwv=pXC|eL>gqTT*OuL34vW#M}w>(#{m_aLTgLR%?3B zd5vwtDEF}jbo_z2wO^CIPvaphrU32U<7_pQYUSF$n3kZ><*A8}4o%{BSsu;txK*$B zV))0rsdyU=MiL<5Mhs{UyCQN$ar|s89``z5@}C>icSmzen-DsY&x1)it^f5($ij*EZ_n^VbMt81Kxx%$!yFSpDfD*)ad=c6i>7UxuXfN80 z#fH?rUb;Ts9>H*I#L2*WB(E70RqPH-WO87m&W&yS>n zFv{;GHTj{?9$7G2T5r7jvKt6YdM;WJQK^ zOEBgNAq5!r0T)ltSG9cStIxe3Z`ZhmNH@bih-L6RRhp%~r@pDI@<0s&+F0)w5fC41 z@_BDPJy5C_unNu>f_tw?x6D|xS9T)jqdZBE4{ANnNw?$<+I=iRznxoAwGyUQfIP7Q%YhpCEF zdY{T&Sr{g0U_5MS)zW&r>!Dcbt0acJx#7r!Htuoi-lM#8ZuS+iQB>Z;?9~Akbie^ z3rdsgs=nwzdJ*6RfqpxW;2W=4^Yrx1@RA9)-sPuOy+!#7nW*>GE1xRX@xmUGK5=fk z>rr?84}y%ko$eN{H;(UO$tgap*o15@$ID(X$P#qt0$9PUFJ#BLa2`3Q!P^Fe+=S_Z zOSi~Yf&(O<&gJ18=P;9(6gQzv*N?r~X0)6vk8SYPF1Lj2FjJT-9;?`f(K(^+H>^x4 z@v-UIl-uxL$_(o*f;24CCO4g4h-ISQ!dzLz1E~9xiWh$p?9gVy&$mmG{{YNFNcP49 zOPy88*2Hod#(kE3u8$PIGW(n#1zgVB5{t;f5&bhQk7cE?8tNT{n*j5e$Dt=$SdVP| zHJ_3oE631NOHd>>t4C&QXgln=X+$_20D0;O0SzkOBzrc`SMvGVM}5?o`o+5+dvu|m z29$j=WeYyUl#OzOy?dZ)fyxRG<3lEngBxv?#mQf;HwwZg4>e!3l5-(bcaXkPhk>{6 ze`97Jh^9GTlG<&p5Q6Z+|HPN47x$v@%n@KJQzljQgxE1j=dWr>Mfz2*lrBH19qWNxl!8iocS02mt~TExk0Rn@HN^=oTYe_?BFou`Nn1?rIEnx zs#;xTb6B28xzEGTeJ*@YAagx!}_bfzb1Pd3j1r*kdLn*(fP5uZ!SHT_$ z?APG$)ZjB*==&00>2HVqH9*tfUYnTqFj2l6{b?6%lQVqM*=t$uum5(B3GS-zz)skR zwQXNoDh2Nx-tvF;wCjKhyT296E@wLf%|NJ*W>e7ER~kUR5pFB&>A2IJEI^NMPj3XF zI%Dz-H8!BMpQaDuRo!*2;!8H{)sVYgNAa;{ENVlCV6824cs!aQdGRfX#R-qTlNO9_MP z?eJjpcg3=u?xUTL5=y7?^rrBOM0|un=4)^e|k`uNP8!8dP`nvt`?jnXFlPY+3(v@CqD>S7I zQ_#-^?^2U=H)B1?F98^@Q1#dr_$nS87CYaaGB^EpRk5^1G8G2)?Fi*0XINsq#E&Qz z^M`0qMf|k5T{49w=@&fNW2*EY24Tvsj|!+gj}$MO2#y~2QT$i=67EcJE_>MXP7D*& zu3r_Ozh0k5ziWfIu=D@edlRUpuI_IbM8uIe76&lxV-<%gjv%5$O09Lop^8)>wHB!& zq9CFY5(E`Ts3TRB;D`zm1(_iMQBe@1f^ULWI2?%(lk z+PE#WGV!(Lso4)+-^=GMq%O>E-Er(rDJ?{aLZGDoxRQKZj#s ztzMd~k?;F#AN}Fz>0s@J_aRbS-D}sE3ToRgXc-MY^yU%eR6)`tE7L)U)|8O?)GS!v|dUV2~+tGDT@Cyk2iK(>i3oR*Vg67 zCm&8si=iE=iUVSpLd~oXu~+ViZG9gYo*(bTFVG5i-4nk{6^{5?#|!-d4W-m*C+}*} z$X3y}A0G`mm~_e~EM%DB`MLwI66fn2POH46V-|!3cu30<4fOa{HosGJ@!MC_hy$5J zeCE77)M;lc7v^rHkG1>c&$XU3N?xeFL%Wr~>Es=r={c{`V~Nw`fMa>hC+^kc`n>-B zMDWvwRLkM6=aMJNPoBB=;$UjZ^`)YiW2%RfIMl&Bev$=urkj59p_z`0E5~^qO!9r0 z(-@{N_gnd%AEYd51%at`$+HiCqKLD-lG*BF|+S; zKQNjeNSp?VURT+s@RwLqTb3dWUW)Sa0zCnVEJ#JYrL^#HlyW znT~khCk?%YD!Y_D0gTw-@?YNU3i5CmQeV>}b<6GpS%&1~ILpG!#7Ej+CbxA4@3LFr zqjz+#bYb_msD8M+X@$N^m?v(Ys_(j%XBFDNeNSzw3Bi-+24;bOMXCEjU+&GdPdE-MeV7h@!@e%A#}+IZ*LUX?B<+FIT7 z>&3oG`hKeOjlB#}GXA`cX(QvN&+85g4yBcB3te59+3xqCRmsp-{k-^-@ju3C#BZdWOHTaZ zP0hO0EoUWPUT1%yNWUH>-Eu6r{F?`t81nnAt1VfBe||lEKQ(8*R=t|@ap&PIp-ktRsGOwSRH>Mag3CX$*Q@tO<-&A9i6BNUS&71A^Xx0`Big$bI ztC%bI>VkZ1)C_G>M2BSjoAyvfu|YM?pvn}yp>hs%@^)_LUqJ#pKenKYy=;6du?3)$We0Ib6It^pVV)mZhC&sda3YA z*x@IRf?W%eMmBZ6*QhTx`Y-IH*sG6E%DK9TA8%Nz%DwvH)Wk)2!n9k>=b|M$zIs=c z7_?wxVp48r%i15FXVNo5splgv%&8u|WYH6mGGd@Sw<)G0?<0?!p_mbtGO4vq#9F<3 z$;KEzx~VuPc89e65nWoGqt45?bNTX@<&Ecp4ry3CG0VwU%+lE~1H6wr8q|hJX(F#u zq0CsEs@kO2YxU|deX>;DHjOJS=DE4Xx9b*n@=~`J zCkZ}Oz2k9{+v%fECg@I5ZVx;4g%f7&XvuK@_;{p^AS~_(kn?zZGm8a5D&ILCl zPw@3}tS=en{v<2#!KrB}=e9K*nc(YbJhe18u=-Sa%DEj4cP1oyIkuK;bT^(VJN2Jm zD^tz|Hk_Q0=;_!{GR}Qzc3{aVocg+IxP1q^H?^pi_o&P6KDDTl_ef*6k-o3Da-WnH zSa_;Wis#mbhzVAn*|HMzPp8^b&V@AOO=$9RRFv#;f07$md&+p~+=M1i$EFfT_b1tb zPfi7;oC|6or?Ow3I_zcqr)t=A->2tXf_jwwpZH+oV*Au|J7vvE6xRiHTF zc1nD5A*j*X(e}xnpjvB(#IkikmDV$C%UptnXO6O@oBic8R5Rff?2l zZA-z&tS2OvTnM~sJ$viC0y0dc)Oe$KHGYI)RO_*Tk^S1rNJN|H(e|X2A z4)Ztf_|sv2cM^EY`TtWcV)V-a+q>D!Z4$QyX|0`o#m|C1TF-8JayzKSdY12#V?l4M zXEv2>3#zuZ_bpo<^xS%S)8i>YCDv1Y9}ft+YdyKCv@I~ldXjJHv%pmAubN732i~wA z?^|*#@S^qDrbpWX&svZ1eY8CAxb>G!52pkkvi_Iv!vTR2)}J>Ow{-<~Z!at;?#ArR z`BD>Ha77>7d{H0Vzu&ON{VbPQw&DH}PraRwr?wECc-iY$)o&(r>dHliL zQ=z#NSKWh+M%Rlj8?4&dEA*+E?Z^L!>_3pi=q~?>Bu01nt8e^{B;=G&(0_jYCz2T5 zWf|r3?Q#7ICHo__=m~FJ=U?FMe*-3c{x@I(cln>d1nzP_#q<5Af1V^bMmF|6JMEYM z6Cav;&e~&{(B7nR4722?H8nf#vWyREdg&N!d45{cL&t5F=h_>y9Q`fh(i*QhZnQib z)cBjDkL8(ZjRzdpS^n1Eu+!1YGB&N@7e^1v(?Jbu99LPMn%3~0<8sTB?Qdr~F0nk3 z_I9k}0!zF(In;5U<*{jRdpf#U{@Pxz(a(zL`cbxi55wGfpnjHY1vP8?a%xu3AMdg8 z3$%hSVm?Y_bwi!DM;{n5ocR-T_xUFmk8T{Ea=`Ac$gQ`iWX|i#TVv1s`lZkQsY^DV zyngvrx&H!>gvToz$382)7(37Iu=i*AU+o^&F}2y}X=y^Ni(RC5kNm#I6IY%sx)l4Z z-BIu1`O|lA?0DWxPWUzt+r2;j!e0*KI#x7adAjIA?ALbtynE*l-i;FnYirNUTk+Z0 z_LGh!Kb3s->EX>oo=!+`pDPIWHqGz0+q|Q<@kH!=yBP10`LlQL>iF2)@T~Nb@q}f* z^=`+Gz~+Xhr59pd?V`NNiTlk%F1a7^p0ZoH?!WqFI`XNh^)1ukznTu+YWmqS)4`KX z2lhAZuQaiIYSQ49v{uToiU%=ZNj!ww-$b{ zav3*n_FLP178Um#MpDP`MlM=0GpE0KPmNX0=!^w9bKYfKt~#+{(Ils?>PP$~RFxgw zFspMq@50z&$4*RjJ#%$>$@eQhyxV*Et7VPjf=)L3{Auq`=KY7gKbiL*_MX7R#B=KZ_9uYVJuIB@QydC6>_=+xh)E;@5z@yg+&|1j|1EdGaq|7P)j8~AS)pYprC zM{Z*Hmrt4HT-=yteWHHb zSJ6dNW>ODovql8f`zN;^;}P%;}9gPxQR?3bKj-K?5X*cTr zj)VTkx47j@+}L@!>Uil=;f{~KCwQHuyzJ;XWqiAZ-@d*4c-hr)N2i`T89M8WaeYqi zDzP5D+oL1(-xmKfp#Ng=KLh$N7XOE>=eYhE(0^Kd{IeRTf7$x%nd)}t(pOg>uh=lQ z{N%R>{}$4JSp07x{fEW>8q)u^xG}6rd|NPk{8ztZrZxqpBN9x9g_~OaXgXxB>7dU| z2bh>LWG4LzO!_34^a?kz_|e3Cu8G;_CZ;Cg^ljFMV*O60yq;Q@ zkKc9WuGl$u38%i!ZrtpwFZW&QvphVUciZcyxXU#swk$e3XJf|O)Dwd9`l`2l!$)?? zdbjqbL&pa=j+=IJoVT{jQ{&PyE#R2aOQ^yT$(w;lEq_?-2gU;(w28^4;Gd z{FB8Oc63HR{5@eD9+!4_?*+zT+ zGi4j?{m+z5?ESZlZOq^QOxeWVqj!hSfBSpFxPQ{4q_5A}=VPD!4@uj7?*EjuNB&Pq z+p*)&vkBh{hUWj&AuIZ?ewocqFq`?K+4RrNCd;~gmC$YMkKMlfyxZq8Q|koNp+A}q z{@k>`%%pFENzWfmx_@qBqGWXz8-8Od)`y*oX=|I4QLOrHqzKMWrya$uzu5@Z z2cL_6*5+tm-0+)q%=&HC(S>aeoey6BHX>lXe~iBS>Nn%pox0W%xnjl6hi<(Y*A52E z){ow25qo*-_Ah$(SK4JP+I(Vr=yU%CX|`pjJ$HOeKf#LAr?IKh;P)G;&C90(RsSGq zz5Y8S4RB&i7(P>cP8&JH!rxihKO+2hR`!nw|HR7v5#c}mZtppo$^X?a6GpR%SsEKJ$6BB)mVH>NlSht4R zw4TF>Y27yGYH|CTk;UtG>Z4z8bJ|cWUt^QFemf_+Y@6el;`%k#QR}zrkLGW4Xnat! zre+jt&#A11YewJDCY&5R^5coSW}DuOVDJ{y6^ z{wepK{en!}*Qck4KHvPv$CmBXmG+3GYY=%B$2YnyJ@n`kO**r%WX}0tKQZNjD7*e( zX@4!d{xJAo%dS5R{`cAo-~F}h`nSPPHMiOvJCQo~Oyc9^pRL^Ri86fikHYJBga4!O z`eg8b6keYU{yT~vdKSs}?|v!6EoI^To`&}=2=8+%ymvx)uV2G^hKE~h4e#+|xcRd1 z?sLPvm^BSO}1$vc`p=i{I?2b1=$x_E=Ja6#P{tZ}mw#t7S# zv4cl?9>4o+-`KeYqL-6yFL~a&AaitC{PeYfRgWY~sm&k7+%7xGZS|Co7k@^W858Hq z1=;`p9+lDmxAry!K3`zWAmD@lH@yGL;obZHhWGy)K=}Vjcq?joIv!`iBK~u_Xwi9n zkzu#zv-D!R>&g@nr?X_F*}_?!@5;CR`0Rz3pn3ZWX}oR2p@j?S+P1UN*h_QoXpPHT z>gV<%UTp7_+4XM8qUW|(+!ofV(wVfOHSw{KAI8#gI}Pu3Rq8Z_uT=d1>;%nXh0>Xs zo1fpj)-1dDmS><|roXB6N}P~0npSH=TBsGP#~z{;lN6&HOhYMl)kP^KnQ%?Yy2k8Z z5zvcb?eDH?h!n^4ZJBRQGt(<88bYmx;#uqm6`?atwo#_?J z=v7Zef*vZiMeD79T@wuOlsLH<99*1|87-oI&h&z1ET>HzYs>+|4F+jT zZV$<-FRlexcPI*7!lbgC=)Nt9=7zNsu8HijQtBhL77_A==Bn2b^6}kOHWBho11l0E zB9ud zAZy2Xs=_8k?-pjS6gic^_1x+e(?c~Uf(rZBHU7X3MVgCYt&3CM;0n>;J4Hz@hCY~l z&z6ARDf06P++|z6tPjky{j**UdHp=>Q!v6 z%8w*gDKlEdx4WU{%WF()E$f=-^A6rJ>FlT3V>d_1+HE4VAraz`UM(kiFHA36Ng?F^Ch$pMrM!Y1nc1VSyXLTKwKsCs6M)qsLYE|DXlTOV_@r~5Z z6*gZ<;EJ|VgDONLINW*5M6)z{tOM49cPaWAm=*LW^6b%BDT+vKQKWqQ59+msPFeeq z2yIni6T`A4!18DKR#bP%stMNupand2q6>XBLVM!^4QJj*FFt(yJhs^ry|72qr%{$eOjVmWcOvfpSF6b zqgp+>GPMX2D1bGfLJSY8N}!G<*m(Cg^zTr>wJs*utnRLY8#%dzjmb(;xftAuHCot+ zlvT@EGmYzLEDRoUlksNLRxd1LRHS^?%xi9V$j{4YNdnK$T;&%*&6#wKKgiR4{52-# zh(H?b>3$-C`YnMcwrmmga^`WDi8?Y{E*_51x<~A2?q9(&`W2OwV6)p?6>CnyT?Ci; z!o|QXRKv`Ie$FfmIg~@CBnU&X6bqGCgnak(Yjz~um+^LO^%D0`@fce5l?19dfyYMh zGpg6kTSjl)>ZRze!vB5!1G9pOh1t3oTo5cnDtOi$N|!(t$=c_Q;3i3j-f;yq{Yq@f zKo~wu5YFG~l^&rTUKng+RiWtX%gD zm-AEv@7h+VemAg!J&eOgj5HLghxM;ms|*7=`bR3JO}!@iO5==($6pqBHKd{`a@RXU zowQ%$6-0cl(JB^omz%2+gqw%L{xb@juo7uMXVJf|@oayI3W?AjDh$5KXvyr}s$8&) z-lpsVa&5$Vm=&zycU+%Exma6jGrvYaDBBPt7clu@73`6o?l%D|Peq84%UpkJ+!q*BBx6FHbGS`wc@n6o~Xvpx5vU)_^-j zhm?x_xKzk9qE@^SwH*3r)kz7$d0V}t7AoI;ZM^yb6|XNI8i3hbS2(~+VGS>%HTD%s ziE9nhzy5w1z5F+t(Gt7Ai@xRjn6^-@FVgdtv8IxbHsm932l+XpcRTG8c376vqq`~$ zr`IqWp;$RSX~Mq7r92X)?v~`DQ=l)-~x!V@yOFV`6?3LQ&MGoxZS? z4BLdqCQ$px9Wi(U)3BPiikx@D6)s`4F=H?p_-u$IL(#m)uS3oad6AK+vtQpmC~mF) zkR!iMiM+l1`^Wwdjtm=;UN-jPodZAN&9uY#FsEJA0DQ{!&F%FAqz3B@9AFB@917k85I?vprX{6mMtC`@Q(J1#n5iD zvtu+V9Jiq+iV{I_q0%2~;$wXav^=R5rmzrG>SJKNiV?5YZ0FK@L}jY=b@f?XYyFZB zMLRV^KJYZXAe5G;$LKi6@ zj7qvFH#Nms*|5f)y|bUZ#e%;826Ckt0Z^adDP80S)ww^VLs^(pCg$YSO>(CoK<0es zZd4%iK>;7yb;ls?xQvI(`MAvjnv6?LIopeqlA2{{b)t67M-EUyq$+BCO4Xr za;$(+hl+?M!Nn3p>w6w(Vay7EO&cA=Q3so+vYzHRMHXWT11jX5l!1B6B| z90YG1L`7rB5ZT$~O1u_7QMv9_)M`yR#`J>B(YozCfgk^dyAWaOpS(`w)r#qZh4}p@ zrtlEXdcoWzN)iKnHLV@3y=hM<)cccCi4QX`(ou@vgH?5WUoKpGKliFv`h z8vqzeMD)d$)4U=bfK75MWvOyC>`#DaHnfPPNEZV%*3i9J_YI7I?R1fYgwFLA+N$@6 zNMw_TGPADss*O_gGLubclW&{MvB1r~5C_OcI1iRCynSxZs2EQoO-AZ|)48n4Dy`q)-Uen4Le2#)K zTp_1C+?kq>$w_vc@s%$a&T&vPG{zM{@JuccvKElfC!q7yU>!F7Uu^M|6L znw3z_ct{H*2GS0B1Zhl8YRr|*@o=}$L?kB-;`m@0bFiE+h$+Mqy2-_|PQpO&G-vy* zOGfD(Ke#eAqh3V`m24GD>5O5PLhnQyuAj8Pohct~$~qZ*bg!T+=i5Rxuu2W}C%y@e zZgU0>BPxwhqfhqh#@SJ+ve8dSL3LE1njy8IBXmzqNz^&R?g|)Df{Q8F_7tTpw6SPs z*Xuf~)RZ2Y!sH|eqH!+r1Q(IXH4!8-ISg;NL7XApViV01EiVXa8-<(FJLt|AbST|5 zaz^jkaZpTNB%f9?vbcTqVJS?x>Q&S>Wi(dduH~t3VkCGY_b^3A$tL1twr6cG=L3iq z#Bs2)4~ATavp$dy5OUiJ$c^Nr0ktoH8`FUsPTDSp0Be>Ywwe$FzWB_B3rZ^>$gzrk zp3{1Br>QU8Hxz{ZvX6z-i|mmppayXf#a!Y>*CC_jXWy#06g`wPfJN6OK@D^8^z)Es z9j$#uCxl*ggYZo(#0sk2l#RKDoVf2Lq58QI*g^kykc3m#uLc_jCgj>$a^0rU#PDOw^s zftz%&cKdkCe)4zDezfsp0aU|^g-A!!T7sFpc^oqJM<)hxp;7AHjZvMX8!0k<3vQj zgPd6fRvWy|%aU-b9Kysf=rzq%I1AO2-}4@qze zBswuN1!19VcoFk{&bI{&U$0SNqBWAS8UpJskP3(#QU;fW?+JU#&zkV#g2OE6Xt`~n zZD3egK%uD4d3!hkeLnD=_&Z^|gmx0g!r#JHSTdjF43|ZvrPLCQD@!}*MocnFxm#PP zu2W4fbZ=7i4Oif11#VzM1Qj4nv4Wei;36tv4$zHK0NIOy;7Quq9=`x*D5AS+#T<}~HOEmn0i7;mZ?h#jX-&&Bh0GzZ?Qk8cV z@FYTf2mmnfnG_rZBy%x!Hk?q3X)VH$IAlJiaUau}2XX75gh+xODVug1cf=;0{5T1!s@2oPT?vYai2NNuoS8v z4?&g283OUfXEP*<(S&d8Abf*E_(mU0{|BRCh^|q)F`WtX%y~e|&$W3cCz)}kRKDmX z+>fb2)6_8t*d^fGSfvm$gAsgO=Z1{p~(8uI{S9?=7>I-JZ!UXHl55;3i z5%2>(dq9ShB4sowQU;SE#RI-V{Dml}7)1)}H&Ub=CPhjj)&a`G1(zk{p1lNF21fSt zaPPyhNAhL|8is0YJz1Yo!_@1nIYfygAbyY?1`NsyCTx^5lhi1ss8K9#p+*^kC3b0f z0t`e7SOOTaa6QQeucOLU27Q$HO%sbV5~KHl4-oJTA_B)0z^@d|tU5kc&ghXc%v=9cM0VXzo2uOI8lZ> z0edSPZE>t=VS%f#>U^Qx++4a2*LlJDs6AK8^nV2 zHWZa~K~xa92K$Zkk$p(Iq&XzQlSqPE3Gw%EUlJu-3vH-KiQ-?mst@Wk22Gki5GIl; zL3}${h9(F*JnQNJDwVRJzEoeZ)4P3eWf`>rnBF-b0f)v8=Q8jpJosohDMZjYS*|QX zr0OT*C!gVpi4cS+C&VDq{fgEG>6OtXsuORu{9%#w-LPg8HoOA7n888^gpI}%AD+S^ zRP%JK)W&FpuLxqCLPp`9qDb0j781ljpMxDs0K-AxJ2?+VtHs0xNDl&B;s&R+E6Djl z7NqH7(5L0a1uO|&n$4(}ft-~A1xUYVK4u)!O#AjGMYiZjsZ*gCKQW?t(k`A;ksWbC zq*zjOg+SG@I7-$7F<$8PQ6V2mLjyE}B-W#lU65?3nD|}@M2yc--6TgMNL|Vsn2?rqiJEW#8P}XuO@#Cg645&iSiB#S~!iWe8el1N=D&E!fqiz_N z&&F-2>Z1ra2f!1=z@Y#!x*Cb8DW*z7Xur_%*fBb$@E}+Q-*}G*j}hT|w^k}jj82}N zg2EpTVeoN^I8+Rr32PS*)Ps-bky?1XB4DEaiw~|gdSXRF7J9;pJp7cDuz&}OR0WqX zyM4i`eV}&6sYu{%hY?l;Ev28dYYep|aA$vl7jD@EFH{6C^j~2xS+xzP$PEKe#aITSSyHc~^j0mH|l^00@OuB2IOm1pq7VUQ`9ILZ>z z+tUAxL7i~XG;qkpC{GxqL@|iu@CqYXVN9Jx%EkyI6RVTd|EO_(U$agr>Y%qWpE`9z z7%{n6m5`YBbNQxyA#g8Io%XiU4*DRyP!)K~b;tlb^rDuI=7?|wAJerPef2Xgfux@D zmcF7x>dXmwwE+Fc0DSb02tg+;fzVs%g}%UQqLY)D9Oudx-Pt6p5mF+weh^{`c?GT8 zjS7i0S?UUAOjLemVCV-Djxm8Wx(FAT1tGOr5`+|oww0^O#~BmT3IIl)ll;cM0H6>L z;6OUs1V{Vf(o_f=;Igi1~AqorMjA7T+m%- zJV*+rE+yoJbalgnXWW&Lq9hl=-378?2Y_+_1h;?cLXw)q41RSH&-jkCK?$!cC5hEN zo3S1x5DR4>3%xnMlE8Q^)<21G3_lj#^w!8RdJxkP-UQ7Vo0t8=a_{mI0vu5D@HBA{ z5K|-D?~f&eEpeP77Vr?}y__BeqzDzm)iQ&BXBjXmDc;fICoH9|9LaGSx@{A1EJxR4 zB@$%}t`WD;Vc@|*Z939oK`+wzfI9OtYO?x*I73qcC*!68f?)xmKv_pd|S;|NQ|U;M_L-_ z|LLdVV$z(Aqy|AjgPbc23~4!pv=`AQrAf1%1m~=+O9thk|5zJIx*B0snME=l)=IQ3 zK$wwGbHew*$w@<;iIL4DG5dIMZQgxCGztw2Rv82?%LW0qlM0lRZDfS&xc6V#M}!2= zI)Y6UcCoHzh;T0wLzd21XOjjq7wt%)v%JMjfjl5ap;Bn|v)2;kZcKh01c*wA9jPvy zMJQHr5euZi968K_i_)mCe#VEQHKh3^jK{p9j75ijI_`3>T(z=Py$azmP2SR-%}3kM z0Z>VrR!ry{2986rCc9m3;DBs~XL(QF655Pegq{W+r+dmEO@H8ku}~%fX!NP!q#H`2 z>JTYUN|E7_TS$LS?*-X{=d$p`K4@wXgVHh}2TR3jZ*vaB77A&kfifEHOz6L{nTTUR zV?#(38=Qy#FlTp>q7m*PP5892){d42>8&|5qNAa%SM}Y`gc}>N3os}kd5i1-KEXuN z(R|>&(N0l(~UBY}V-4)3qpj6gyUKR_R&L{gPNApDI>)c!DR^)Le z=#ep2NN&HEGrZ2PtBb)e74vqU2Y5WeQppydGpTbJytQ;MdKm-`#GAdfY^=r=4_QUF zPe#0onxZW3;47ox#V*DI#Z8lepMD^~5z3;idJ2zVz@r|Z8rVl7N;8&e%e6c9(O*lq zC>;=0A&|rH{=+!B;z+jk%puu^JNgY+fQ?ofwGXuF4sdd!JbJsU}RGM|(uHF#JJ9x@7I1|j>TFK|wQBk6wm zV6)HIdMC>{4tWD1)hh|RNf45%Ny@e)O${rejl;NN5rk}1k(~`^s1b`M`364CyCAE% zg{{8x7`zCALJciSW9ydl?pw<^p83Rk}YZZIFnhxKJ4WLcSCdK zDeo!_O#7N-I8Yl5sM$gvd2k%>u%;KU*v>o6Ho%6397LYAVjQTrH_ z!vafff|odD(P3=t(2>G@)+fTw!_GJ#4KzOX4%t;WK7)Yn@{rwl3yl0-(jJDkyObo( z^Mr8>C5!`}FoKPE0nhvK)odVqlV9|DHNt0TI-AsoWFt68;2FNyu z7pYSY65J@ocLYhu-o$1E6RCbOKjkBA9`Jf`**|*O$Z|0bQNj$!DJ(;Tqb2SnTi9WU zaUY0Np6IBNs2s$}CdhXf@>>XA)IjNK>;Vg45WX0=!Oeh6f=CL%OG&ZdEik4cCZ3}J z4^%_Q4g(@D_O0YE448-2iby>G$3oS!H$Z>^QSUL|Q^roYvED!&1wh<-dX{%*fi^21 zk(V|oQm70^UpiLDq{!ou$93>gyTY6wj4d(IR>K<2mzc0EDO8REI6NS2Sjh@4eI-JS zv?YQ_=i;S}-neXN<%?n5K8gT$&593bDFI~G;w*ZR#@*q)!KyvR5GW#a!v}Q32TDU( zs2l-FgQP>y&>@uyXFEZTA|OXF$!gn3r9$d$k{D21q28vI8*mu|m5Sp|V7ky6$Pw_P zSGtiZhhUBm~oSN0^GX94l5yDEdMjAXh+T}^C_zb9*`&{@KWjH%PsDd%K! zyK`nsV)pVMs4mIM)0jValCDgjozmh0-wmvoebPEADr5&3W)Q{V*g#W@(6Uw(sb-Y+ z;2$-Vn{vOBgha$Ng$edCe&SaY@?Ovd^P73HbgIjW>Mse25`5tE>X^vIx~*ZOIKzb^ z)gHSi>N`SEBbL$;dvRUbY%X>cQkd`MP1<$JT2GcWb(lx(V-~4K)wQEmVtve(bd!n| zz+)_}4=_0-1l35BwoU1U6CqiYljb18$<33U=Bebq7h5XNRI$#>BB2bjm+=Df1H|Ad z?I*m12uWW zt;Et+V2LGo7E>mrQ~K*ir6^38_f+SRFnO{jNV%LR8b@6_DD`ghWXNZmcCyV4*%C3eMqNKYc9ETUTW`Hb^*^& zbfxoq@hnXThuzwjg>E$afIPQQycLzP5{kZbj%UsuLta7&7J*&Wg?`80=|CIe)KT&{ zSuX1RX&{`gn9mE$%?ZcW*K~@6N1I8X^5|alL2=jWoow{L2-n2knDKpwg=&#niV2{% zD1BlOtx!(jPy;w4X@i;G6qoG6r5-7YzHBjC;|eKlR07ym4^18P>crXNa91r0UBuKR z<693;DMNSzi<=AwQWUEq>jq_Uhw2xmFf9edsBeNozsx&knBAM2D#_f*!XyO*41dCo zGT{rNa?FwqtMFpvqDVobh~6U=H*v~J3XN#+QOYWc*$!s;Ryzx?nSI&=DaNr9LONj2 zAY?P93UUbI5BarV#uDWdE*hq9w1vukK(MB4?o2`q^n+k8^w@gwkFi50lC3>X7V7;t zI8O)?HrcyJlW`|O>Jea60r+nP3!6YSNy`D2UWz@rBpeADB83}a6frmwcJU4} z8s|$<)OCUM7Bb6ucO`Y0g&WSaVitJMBvCO+64T*KAg5iosE^(RIge~Yfbs=kBZ8Gc z?^SQ3i==ps%L2U|Q48(fpq_!Leg+3{gHCgCRsze*Aq?z#IbkT$cZtIJ7^K73dWyjV znUcc$X|M-rDWZV9x(0odCJMQ(&n^D^y8>AdcW_=5jMrC^c{ipWRWXaaiQ$bmF%&IS z6aasatOow2KzYE|(k~)t06iPjoxJ6V6cQx>b0HlLa~K~v_ust&~kT) zS`~~tMi8KazlGwn0(GMqq5_>$42pLVsEZi%8je@c zSr<}4M`fh*l&Le2S28Swneb!~O(Qgtt>vqLMszE|5(f!A$-eRkghb}YIhXA?C_3>1 zT0n#X?Eo=M$A>uu@2Ds#L;)Wp8xdrOiB6{XlFczOgn=+51?K!LI-kbm@*$QV=oyjh zEA(D5sZnr|077~vYQPr@Y$y{cl*Taz-;)=?)OEm(owfiN>lB3rc|7ZZ%1mYvX!sEc zi`hdIk4zYgiwVmL!HsM%7abHVh00 z9xZwKfoyCN`yqsdya>@v!Z2>9yUDc2 zltD1bnXX_-HxV{W(WC3de+`$&g&z^xn2DeE!%=}F-X`(0;NwMf7Tl2j20b#A3iJhf z4;ytHX(qE$Xd|EIr9i1rt{8n8vP1!#)T*~<`CB;>VQvsK(H+<`9{@@NsgOcP)dgd< z0eb3rH}WuSax5b(g0G@M!@1Ez`_cX_9&jwSWaaG)~^MC+`5EQr|0RqM|Z!oi{um zJ(mIrbj&KJv)r)IGq32^kqn$5s7*+7Kb#CEP;mu=P9x;wCcTG2vg;dI`6$ST0$iAN zUlkU19w#1PqL(n~T71^wNVcBXTBan4-YdyAZi5St>!b!GjS#pG6HgC;rP+IYpDIrB?Os#uogtdT16Fc{Yjon8fI_RHt9lOA$c)-e%0Jpe$4}$GHJKiwtg{7&R$*jOa0r7 z=1kTvhrQQ#EXS{VRo%*G%fIheRz9byN9f{s`N0gDDqzuuW;g!X549Q0rRhO2Uh%{5 z7ft6;XO3rjtu2}uqi-_r;EP&6hD2+dPCcote1 z=`PoM75?XlP|Zb%EH!;*!&Ck_Jyv< zNBO$eSHhNY(HpnBw#V`8XB*b&!u&S!hOk~$D&?j9%tXK4*tNKZS968C*|pt8Rlj?N z!iyzzuZbDcvFQUFLDE<+w@rU7-r_$e)LvAZ*41_NEdN8>!;`tQn@>^g>G{zX)x76P zU2%_!eyFLv9$)5`E;?O5a!Xy-7*{hx}p6Y+rt*Y^VsSkB+cgR1> zJHJe=e*0+7Sw5Bc(^H9|$$vsFZ(&@c9qm;meCH6L3s|(pxjB|POz9%L%UE}U#gl$} zd7=1H>&`dw5%zAIhNdnGt64qsnsBAyi%^NAbCYhlm*5M7+(|t=kP{}!@OtLoakA{W zenb1={Ig8{twy%LB2?$SQS?Q9b-+VS=ZL8n4Es3}u4}boUQ>J-&wje7I<4z|Ssb6E z;k`Pvzfo<=@Z&u$5q;*kR?s;_|NN=Gb+1o#y+wXG??UI0%1-O4{svL(%heT0UG0yG z#-vO7dE62jW`z90D?4A!4)WLerTB%eJ3nFfx+dz(+kFi$zZR%AbJw-M{93H}=&iR~ z+kA}<>tm42(^GXrCkT}x@hX)E9xVqD&3g-N_37=9pt>ZOjD&BTj)~u(i0S|Z|GYBUc~#&kZIK^(B-_PIqO!^^Yd<95b2|YxA-db0{2I2-h0g2T-zchfZrMG%L*bS^ zk19=k#gZP0Z*CK;O7CQ+w60pBxjFHUukNR6?_Ij6VE>|Dk8Yw=j{485A91y8nZBs#R~tD-On*nIW|-x^xI?d7 zfBs$K;0(i)7gWX_+9lcG=It*!7`$cZ^sa}7rQS`Yv3LP-W07qJTVGk~`HI>-M*ZQm zSKfvi^NDnOMCbkh)%oYQ>dkN3DE$3%SEl_uFOAz4R#UclZSCN?r>kdVhpcpMulILa zw}#$DW#4EO?=1^hJhGBX%(biLPn)_m$Ezl1@r%{d8rU~83<^)L#8>ZE%H>z{ri`eR z*!O*Xt2MJOgZaXLdyB1&n;~prCu?0R`}1%0Y4Ylv?X!cAU4J7o=yXMI*VQL_KAY8W zs8h6fScmH12)Nvg2Jw%s88;QvmFkrFkE>>97{u4gJ8lR@xq;m3Q_fYjn z8*0}u*J`T!97923;-;aR@1tf2=X`v`bI?k>xtxW9oQ$KhWg6`nKdqg8gGfDms;i)p zL3hpJmGC!)-tn5|uPgc1Em_AF44vZBdWn9$ZegH~7x%KB|9)RuuHzl|8o{Y?=_jIs zDfbT%bTK{aBmeQ9(j}bhtBRr?rY|#e-k{gE{j-|(KXR8NPMq%&Ek^k&C7k_*lG5Bp2xa4cB;MEjm_;r zl^cZPYxF16v)d12oZr%`l1uDaKl?kMS+*`tk!ElEeN|&_a@lN*WA7KFYZ{W#HO8-e znNOS`&7R3!y)I6WYG1s%YQs;J8=j1>;hag&ZYb|v!#S2-b*;pHP^!Ixw_;tKIL&^( zdsSmv@{&Ycb2dG@tsd7LPxla&*fV|oYrC%9wP*VHYjI6ca#`}KjA8urY^n&?{F?3| zD6v=g`ad`-xK$SF5m$scZ>ijHb$m_UiS+Eo$lf)1=hHoMOY8%D{JnXiyY}^|_EDH~ zS#nw5RT;ZR`AvJ1k89%7Jua2l3vkV@%)9o24gQwNsbv#9;x;C2ZV#+%{1`HR^E7+E zv>#6SdzVz_b?n&Z@BO&C<7R4EACI`}cqFH?@p$I=&F!9*8yd#f%#KUn#r53hAOEQO z{Ysy>DIRemY4WYIa*w#fxc2+XtA3~a;~!VgZvPh7Jgk22;S=}FBaY>bCDePwwd0v< zDmP@0uer0qKYsVkyY?{~{C5aa%SL&`EfB1J*;toc=7_nE^-J4+%75hJ>Lc7MckLNJ z`|r4(T4wNw6Am^{AbRcyKk?5^J6|3fiuj886$UX_vU7Zz-+ro03{~(<5#kuEC&tnkH-x zf>Xp{@RR9XE!n+mUj3Fn?0cWMV;*t)aE)taqtF-EgjHS*Ipv>HTFq%&yw5+Stoqyv zpE$o%`#w;9OLE!k_2V}Ot*YG6KECG2!)i|Fc3ktg`W(iYkZPZfNzV2QDqe|6e&v_8 z@s$6hvTBb0=Y9T@9#vbjec}$M*$25-ZP;1am|=@?mRD{#F}}tV^I+xNwYU1&KN_0o z@`!W1u{p@Qvazuk^MOZi6$%jZCeLYO_i!ImOrA(=oluRDRT7wIYev*-p)C1M;|}&d z0SskH)>k1}6RXv}2M{x;We_otBlKD=zemtwX@`?8zvqGlQ6|XBzNz=Jo>4?pju6d&jzPrfT%kU- zo+Mt2rA7tBoUtG}2U+De_qMC`7~^Hx5k@Qe(i=O0bJ2qT5r!bYsW(!~C?W&Hgo_TH zV}qyIEpv@JjqBO_EYYrqZZE8antLO4j3V?jBR0}3b2U51GN%w@)|sRcWaVsZC0UyP zRp_W@1T7A4bs7)iVq_kpodPb_^)6%gJvAfzGc!Ll9@LB&CRygXcN%A+x^KPx&T22L zicGA8uocdl5i~NY-Npmy3WOl5&f4Avl$Dn&B&>_~GcQNzlxmML-kD+z#Jo;#3{cCT z2+j-3s1r&vv9kVD@>M9!$f^hucBD6Ihd{!h^v0iR`I;gY{KOzD^}1di7qb^u>J7c= zctbKZBZRpye}^?Au7k5{XVUc`tKj-x9lJ*_tb!YR)1kUij!+gg-jG(!i035BwhI>g zxYGTr8n-X<)=3H%Ig|MROJeV=;1xD$`MKf=a%%w zBjXq!E{j~7AS?RXUU^B(-w_ilPmq0|-slaIJQhpSg>?Cvm>VcM*4(@8r1-*W421CZ z_og>4s^y0XTP!VqINaBOxj_YmxkBYpx&{%2IYQ(32SQVNBOhYUSu9m*(&dLqSmfeS z6g9GPXT;q2X-4FxS@LUlCP4|~#$IuI;}=%Ocug_uVYxz5)KEfRGlCy6hMmT**!yh1 z^@=~nYif+w6a*%6g@yz*x{Zs{6#$Ub*n3&sD8dW_M~#-YYerb%qFU$JR2zpSAuvy5 zro4cSg;}X|;;p23wB6FSS#8DeAz`m18vpU$h5-1CiOsPiKNqMXs+WH*jD$-mDlkj3 z9VtKS<|v++B)d89DAdSG5$IJzYo@5g!rc5`Qxqp|V=oAAy=wSPHWO+5SsNR|0K`q4 zs|9!}pBTVRYPUuHadH&DHdY7N{?L^+ubQ^|k`iq2oilz2SiT0niHcz-9`5K+aq;N= z#JljV*6YCcihC{U4SY*E1o(>T^=nuGSmb*Xbv*9*$8CN6@^Y2B3q~61WI#foN;!zt>wTTR_ zq{#&zztOY`>bS)wn*fpG8v;?A_v5_)_%tsS!^avcKN|ynE|2ZNueVkPoEceZ{cbWd zD+w`@bvDUy#ff)qy%(8z$TEaex*R49@Z^!F6(G@e%O0GY&!^tP$=Sjr{l)pt%dW$a zsy%?4T7j9l`J@Ox!VMn0fKMktgZdW0X+`+pyPhBfD>Cxsd7xLpgP+CdK=Oa`K}dV_ z@ys=_eDzNRnfVh}r+bU@j+-^#0ny-}36{55vY5%r&OK5EKT?e=e45NOGvsQ1WV^s; zUDFPq+3BtoGKt@&WLd^SKY^bkbB!jB*qAuT?X@Y%w}J0hbqjnVreU)97ogvR$SAP&$-V>M$?7SWivYYQK%ON1%WouI z9qD(!G1np$gA3{~(Uy-uwCSVkF=#K7j{>>99A$$NfX%&6o|-#Vbs({E+-5o$b=-a= z4wLF8YU`nLfn=sKeR-8h^^?k{^uo*jI_Ul-szbl8X7-;3g9YVc`&m4uJ+(CJoWI%Yi^g?ha{NF)8> zuWQ<5X;~s;ki_wWGki^7`m{ka`BWr1v6*rR>;oR>rIDX*IrDCimT<&a9!ju(O4I=O zzh8m@Uihb!C0SaTht~jc@LB=-JMz>)NP&4U^hHc4AoxC*;w1kwtpw_&J+LYVh-0B_ z5KiJprzeDKcrpl}Tl=J}-vWr{p#j3#Kc9q8^N%^}NcjGEh;azrDn~~{SNBF_YAXz@ zJ%*Nl^7eR)!8Y&Z9|iXDy$sYjy&EHhZKK`d#wrsxyTG^&|Mel|)ckuxy)J#GP$ zt6UM5P0BqF1>ADKw>e*Q*|io02=0cdL5KA9=g{H5bw4EI+Kx&?PtE50Fe;4mf$EA# zGsLaHq8ITZ3&=?&9-r<2dBz<>8Tz{nyB|8dJM9h5&RbsqVC<3%VsR^ejb zvf%)x=5z=!4^2T8onn~P+R0|l5OMLbR2IB?Nh!_IlH$h6L#38T z7Fu>4X##h*_<$+8ISR~+&TB|>XlFPv(5vgg&*Wo+A{<%ub;&pE>(3VNW(DxBxQ=?9 z6$|n6)f^u#KJV>&X#6@sx>{#R5LRHRS&Y@&mvL-7+1s~vRN{`bV%^ztO?H(B@@(Y- zY7ZJN+T5gLq^T?SVYY5FZYh_K2@;1n`<|SE~l_>bq{is=0>4%DJk# z#eEC?r_6%%E%cNm0hZo<=+~>WhBE7esb>xO)~8{a*U$M}@b+c^KCjtLVg!)ZXWC9&`W$5&Gp}~^NpEi#Q@C2qxdSo z!rU}L^W6YHz-o4lb^%7WMK0z@u>%gC>~y(;|LlqaX!fp9!&a|+gw0tworiEI) zUTJ`}x2rFK>;8%)z9_CR!HTZLFG2UPcFIK5(!P}FZP|D>CR_|6M@gPV%J57%h@7M! z$^nssB!y5|pdLF8-nd&efs4rIh-v_*=Ex$zZZC@w0oL>VIofyQw;C+dZ+~N@`VU2Qu$fCKqa%{$W7=SR0p6pxFX_30M| zm_jsJzT4JOY{+s`@(R94M@!-0y1R2VvQQLyKugyWQ_{Denj=1^0rqZq7%-N@O(-mG zVTvDxiBu<}@e@@XuwhkmiZSRIoiHY1qcRQhrB<>ig}A(2I!2;bd880h zo+`V9lqUvxkdl7aJsc^O*1f@8+A$Ya%(1oo6&f>dhh&QLov@cc>2G5Z@Ggt-W<$;i0`QwcI4*q}+v@ZtcMv={Sz$Lzhp!e@gh7>ER`6vc4%|7zufbpZl9%kiY z^K1Zn4zEunCxFA;=4$|{oo@rMOJd|DIJdP<^Xjaj^YnYb7*9Wgy)(@}1h9<{vgFQu z@Ifxk4~FG(#~EqwDdF89>W5$!+w_LE(VqrK2|*y+>+*s!(QPjR_*Hf{fjrPlPHDNb zJOx+sN4eCVJJ#e^Vq}dBu^+k z17zg^^7<%7VSNfNFXnUO{=4=l^un^88kEmYO3=c^)Gh(na+@DqV-we7P{pm}E%4t+ zT7o30N%2ysB3FJ3g&j&d0$Z5a>>iGe1u;jVG4DDNTbsb`@fIBmh$b)D8?Hbc`1@^L z0T-sz@9@U>X2K@+NrdyM4Gcfm+%ZNdl|-Ta7Rc|q+1O69{xsZ75K^Y$x3ab%kL&{1WvdCXury6er2x^X)?MW=|aa~H%%Or4J zGS=N)mPw5bedt6ij19FYG=Vi1(T6nDoLIa116A+pV)Qxaj_0!3 zurVB`Wm{usI8MAY&dqSVjaA=0PgS`gl1Yc_(nW<#4X#ULa=VMS0nF}X0k8{&4zT|e zUWGliQCFY%?7r3ma>iplr8>&;p4)boh^2JWhHHPJ670^Wd$8;4lX8e{8fF}QcE8Bb z|M4~6wnwc#aJ?O#8GTl5a&0U4h zNe#TXXEnG?b?84n1aQv!CJ`w4zrp|<_v;|6->YT@eX_ChJq6+rRxq< zxmF5Cv(X(V9P>gh&6lTO554s8g^NftcNtlz*N}^!dJ3@*DuiRW=#DQ@HA5T%l`w4X zkv&(Es$4D412}BR-#JT6=)9Z#dc)#&%vd9Y%F_^I5F74^*XpDMtGL zzEHsGe5nGkz?TA5T)iL%o2wjbkIS5-4iuh^A~$&2N()mor2uJ-C-cNraWHXOi_7!|{l^DL8@@L9 zJn`9m^}!8*{X1w3%Qe^zdlG+o3c!qxJLF1=6YCBfZGJXbUJi*0iy=-!{uH@ey$Yym ztXtyy9hBI0S{+$B?La$lQu0$4dYWAP0YWDBmEcBPL_R*py37w^VjX7ZA*5BN97ymA z??%I=IU5I;lC=-4v~>}z*zEgaxdT;|pU#0q!ngX;=#4-dLPFQGTR>Le#BX2^UtPS>Qsl$&vEFHldXiB?eAvQ@~xtKg4 z3VlV$C>N29&o?bf6LBCT23Ny%?J^u(+gct2iLL1zkXW#cfE~9?f*qL7j<|iGNqAs>{tTD=LAKl||%6e(6mGaCtbMp@x!^L{0SB*6t2UwaL?hHD6gt1aNRi z9w47}G{IVTFu`hd@Q0A6Sd9XWd_d~J0Ev@qxUl<#luU9XlkLfmWbzpD5|g4&lNVJk z3eThQZ95zXCX|(?s4DBy+qxmk$vpBSNhhcQ``1eU3uNSKXG4~g?I&RmwmUE&SFMup zyCbB=XZB>lqQ}T992G4^-W%JKcd;v1GBh!)!fqaVD56Io2X&eElIt=FPsV2>=b!zM z&1a$=x;!<@fOVMgB4SQHCY}O)-$Y5|%4scujc)a+z`^mvYxrW{+6#NX6~BvvDZV9) zMo!#xAwQBg$*TO_DrXXOm{j{r@to;xr}6ZXSr1PCRN07ag5H*g8gs6n=y5jxL$b|O zoXX6^*G-r1Ft_;%aol0%_cdm_!yM?NcP01gMnd{_`q@$5MALfMh!jG=V(87~#W(v$ zdA)x3!{ufg0ZTN^gf;uU!hiT(jqCd(KJRK&-=Fq=S9AG&FAlE#&KTQ2$}9Z)ExvG? zj>Dca{Q}GQyY$O}Iz#5~bkcQ(hriFlt}5vw$NJLG2^=&vcfeKDtOuK<R8KpP#KO=De*X!cbn`6!Xnn@$bkW@rMfvGL_Q%tWJTiT5}NWW19gZv1VT z9UU<==DaibHmKau6|Daz+JouQwqg|WUl{rJQ>W2S`$u_qMoYkS$*2!@!e|zDX!Nxq zSgVHkkUJ7m4c8Z=GT4IA8Nvz{2=%Rta$KR1P+b|bKqb)ND>14s*8hd zs*{yvxa2><-i12D*j>hu5yo{xz* zc=}iw)-^{bVDYEshVTw(y$kF^63ak$z{k~k!}`_I$U@AC3l5Mm?7|L?6W*%9y1vz? z=2m7V+?vIh=H3hk(?8z|!uAIY(QrUt8D>bwFXP7zwWK>S#Y;DKXEkEQlU|BzK}Ihs zvcA`y!CmAG0o*74iKoHz^Iucs#Eq*$>~|#p;fN^%_Z`Pg@{ms%J_O(SmR#t&5}JyG zCCAwxIH(wIfPjUDOJ_$FG&d~4`gV;RB?hpEsRD#<_+`K{`H8?Tzt*6+j3mNMWLiS` z;Jzd{266GA|74A&GJ2 z0FvCP41kraG>2uc%yOZ> zfm=A}Ihui}`xUjYiWQr%q7?$L7b|FsF=w}^o_oJ@Xs#0mUjln#qcT2a^=%BG(7UD1cuf2X%(#eQziRL%FKa(W^&xo3kjOjMt2WhRs*dA z^4(>i^}kQqpmN|!END^OO~U6t*3FCek3L%5>pVM}r@3)Px^DR0-eFkFJ%>JIr`>Tt zM&h;?IJ9kX!18ZNS)rARlS70A@uqPWz$=bsVW^sZDfJdzb&C94UVhUlvREn~HeWvt zHhYhlh9o(=KhKUnir%e+^>#0XwJ!sQ$WV`Tho_5ISUvC@UO8QU4VG5Q3ii<6NbrF= zi=4%_U6&2~Ujvc{3+nWbn+z~C=pSdrA1FHOpb}|O&hMcTX)JXH*1+Z)4!Xu0^aa>B zoIRHMr61kisos`;Bvk6L8+Rq3Ob^yek^t+r>zsw-G1nXTV&;AiR=-ROR!ve3R&ICO zxX|a$?> z8$Q)W(owQWc6nGqKNpGVeY4I>zu^^cE-mG}GuCGVF>;XqeX z5ccjMA1oo7>`~mc-RYNpbkRu_w_myH?8bGbn+}8Sq~j`V=lTG1q0g`X7LJ0n&caVv zfdx@msz+WpevBXGD)iCHiQ`ygsABuMv&2x<^Fs}vU;U_^8s+jPer`r-ZRHH8fWO=g z2e*cdU^fQ!I~#S6Cp)v$qvcM`WV((H=0%fLkaT2(0|*7~p=KCFjWIio)lt-F{B6(zI$s z5;ZUISxW622dh*casyNJGoo=jb}$4tqN3m8Muau_c4#M#0~1-Yg3-Xh!C)K&Fq2dG zS(BUTYmf5BZw%=P0-U;(3tc%h0}RDynL*{ISJef`v$wLb$X@94D}CGubpIQ7ht(Os z4J$CN&oR#B%^zt9U27Yc78wMMq>G^vAzT-po`x9pI^E-}d@LY6&eIHyler7Xs&h?| z+fo0DV~optDQFE82|=u=Cih(_4i42hL7l={{~{c>y)D2o|62v*doXekR(!+~_V|c4 z#|W2qu74MfY5l!miGCKaZLizFfd92&NHcOXayRO?vvTwbqcSZWURbqlVqrh}u`ogI z$3j2s$wytV2eXMN)@gk>r_kqQZk#NbuvLuVApc<>Y}~^DSikvtqsI(Y6JK0#9QI`& zm?Zo#hIRcR4y*dZOAd1QlRXqd1_Axjj~1sgx25wmTVA2EcN)#8EZ+j2M{!fnUtp6&G9y57BI6v&49l2B zP(o(|tZV0~BX|S|ni?RQdzW&Qd(-1Y@!sd~slB~D6T5%&-q7E_C3-9G68-%{QFkNp z%NfqwnC`nTue&$+e>@+oDwmkp`u6Ty`l>r-w=Un3le&Cc?*6o!ZmItG*v7J>WQ^Nh z;j-JHjk8^IcgxPItH<&|^|Za+=gWf~Y~7JtMyNULuCXMn zrG}G#DM;DP+>7yeGZ@9bJ|}h4M!sq7i{6tnVddsdH&&YD-y^>?SiP;aj4(-bx?aMDG2ZH=@ z)o|1degfl9x{f}!C&Adg-<}y>&Mko3|-FB zE78)N2+UkxpaPlHZ;(yFyQU;gF0SPZ!UbD8IY#WZhq=BQWT2Tqs(W%t|@AtY7VTqa#9pj80A49s6*419`$1LY$>LyrgEAUXl*%0c>C` z1Yjjn({yUhrCu7UGplkXag!fIG?<9~q}ts=4cmJHMeQ7<&dnw%z;3x;f9<^hW98;7$^(y{U!$$k~XFWS?iry`imGVh)5FC&IzMVWJSYF(QJy}xj6{fZ}VX~?jKLtVU#W>LaDyxUU4^L&K z^nMnEl7ED3|2(F)w01hnbimX$9|8}FVIo%Zp<|Kelwm~X>@mhxefyBjlD0I?yfb>1PA$g1%LeT<9a{D#iHTu|tEXpx z@E`&*`(;ehsj=p-Kao{c=oT)CD>Jp7e5K+dKhU+$ns-G{ySS>|NurN4scedSOZWa{ zk^2vNLbD(oepAn_vnyKe3KxVHWrTdHYPVP^tZFyg+0+OL-Aw1`zdxDNWb2b!6Fx>y z@LJI-2;EG}8@NB2$$ZJ~RcgUzfVO;8hY1{Mes(^oX}xWnNzvxdwB=8CwBc&7sFBBBxohv^9{dGXrvy6u}Frd;p-=vC_T%~#r%4g&e@oJq42eNqHjW5wd zo#+tAw<6vF9ZVj*l9NB(T+n|%EiStU5Mo}4ow7vs;YdBKZw@k zg@SBWAN!;h_cU-O4H^p;go;KC%|N59!d}j#%&}qc6=*}0=^#2NmHSj%exRYejQA=w zWFri{Xb8cXp&}4`dYS=i90%Bj-2VG=aoMHCC|<8@oSrcE826&;DN&}j{Ue`L3n%nZ zqJGf^0e9Ue=(YcP&FSEis=bk-Eniyq6x7+X5T>MA&zZzETBZxBFCD>UWyu+Og2&@` zAW=IDW)~w+V3;pLxeJF+g@hKpwSj1X4d~E0rx0!Vop1KQIyZ$VpY4q93rUJ2HMn*r z2C5zrX#(E|-r)NUV@DgG&5l@>@_~uJ33HEvz_+FbDaEKUXH$-);9KJeb^JVLAHI71 zsyZK-R<13aNqWOK;C@|zHkeqVg&_;_m?+;1`o)#)2CiM4NxQ#X(Knw82*r63d!bqK z9eeRBV1|p=j;{QnPik`L6g@%q5t!^$?t%lYHn8Nip~lQv9h8<}6nRIg_mK*`VO38o)jzKERnI`6D_5$@XncE>6o&}7w;=<_ z(J6%ab7LC4=DL%)cvsf)L2baL5%W7JfL6FTAgap(8=^7C1ZK{O+K}kC+i%RTO*U{# zT7b1^jg)~7sgSIM01Cfmwkt0rG3!NR9!fN=vprk7??GROE`S%Op+V!bYyUzW`v?U0 zN zQCoO?2K(ips&Z}VZl^U2_N}vd&cttCmPc$zSygZ5jc@9E|5}leZZKcexZPfR zHdLj<%F}S{gZrLCL&`{J3vaxqHbsr^q?N4D6{B{mzOhi1Y^xWBG8y?jevE3pp15T) zH4>^4mJwhmqmVz$B5I^+*!MG3#mg$uQ073slOJPVm#2uas_xXUP!*euY(tsEJOUV` z&#*tNOUcad<&Dqmb*=IBHa`b%svUU6*@qb#I|vY}c;g@UM$<&{nI~n~HKYXRf9H)? z?7dXud)Q0@vGIa%dfR*A50wezI+PM*E___^V-);OFJ`PNb!sY9Wi36#P^Kz(3>2D3 zaEfe=x+VK=8lPg~XOEB=4^?TiEHRW3%q{a{v}@23HCE+G8x2({w%ij<_9YobF!4_Y zaUzAPP%Uq~jHU-vcwhzR+YbANl$-YR63M<=544&1NpCob4e36{NkCve1ucl_QHC;k z+0KBirp~adOX11y;Em7keq7^Ay3fzVpB5MP#g8$lQWWC-W$`0aMPm9}u_B{gMLu+* z(uQ~(midMiv%Lp=pA5H`JvpSOf5rG=EEJ6)sIaI*dMaGU&bur`imGHSxl@^Qt3}ra8tkJU)E@H#wSH`3(#;UWuxxH-h#$8%9 zzUI0|PV-ONzJA=0^1Y*iH(p4C0Jb_NSB&Xz@Vg8>s!OTN8{~~Y*mWN=Xd5EddIhnc zI{JCzJJs!KeA%_j(;wHRWaWL~jnC;Mq7ix3pwl*qM#!d-XCXrijmTXspLwSuXc1^A zQ<|O#6>=_$7`K0$1jo~9uMK5%(|i3G>9VOaZR=8AS*h_?}chiuP!nt|iu`Xy-NZ#^3Ka zT;r>ymV26?qpU;Q*jx@gfr%K z^2Tem|6AiLsuGXEyRm}UqaE$M@$Bu4eqoX=4TD`#eC*dUuy6bFz}w>^gK({sQrE~7V?Ezm|>Ee06*q)vGpIo%FRGA&JI+a zYkR4SE&gk(I&g2-BN=<+!-kK3#s>j*XYzNb%JtNzhB7M&=d$oHDD>iC;58#Z3<>0i z;Vt=Lkevh%##HjdkT6^y$wxXfgW8{e1l|3u;((Q{Kr&{#M-3?s?Wnx9g$-=|>`SNL zsSKFrVHn~!FbuC{pgG?p5>)->tC6oTP4dPp#7S^Nkjq*auME@ohB8rcDv0%VM(joN z$xxM`-&8g71E$7vK8lP%FFUaa|7cBzs<@e^8_Ep4*F&sVIGiU*m>Z+tA&H8ND=&nw z6ZFnayzxoj9kCMsx?#-!muK6M@{ejgZ@m5YT?Q0!&gTUDODE>2bh#ASZkS-OOwk)Vy&A4#(nqExs(Y}wohsrQKz&n zC5XgIHeVuMHHy}5+$ZStcjLYe$0lCN_Mal_>AbP~o5#nV^onIgP>^OD;O%2HrcU`t z3>1m2R(vKBThtUb_M}L({4(!B)%J~UCS|$t3&g8*QL>Fs2U4*jr)mLgCbh+6U9QM4g2s@Y~cU#6+fkF?#8g0;nmX-8Dmcl3)zHHn0GY@ zQmSS!T2rTJv^6p*r;Z&aUVR;Ymw44K+^whjNs&NA!#+AQJ=~8ALn;6AS2EpYIBLsB zsTy4f{={k<>J*`+owJ5lqrz4I%HJGLVO}pUK&cwUc#k^W`(SSv1$%8IqRtS?zMoK* zFiwW74d%qFRiViZ`||Erhfb z1C#RBs5|j$N675h6K8IP#(hD0O?;H?j%>R3s8VKgdYF{EjKV}>!<)y-4_uW zI-&eL5w*D)y%vd`t@{^LgSj2d|?u~p0+i$WVm8?@0lQM&mr%3GCI*G9-T9<0ap0HmE38kQo zf65N3tWx6ER4Gn(?op@MFP4bJHr1XdUVRZLhWrY>n5uA{d(^(K4*E6oA5`VrsK&6K zAHuLc2!N`!Xj|3WK7`)ZLg>483?Qq>awu;sUv0?PlkoFSpnBdvf@1QGFG~HV5L8@7 zZi8yTNl-nvHGYzU{k7|T>J;9$7St(Rd~Hn18V^#S>bKXRdRC!fpV|#lI0Z%g(^8`E zzM$)p0+j7kwY-$7N5U+rQ)v0VF)5!(crFqvbn`m4>Wz0#_0<^Ddm$pRe}5|;dvfxh zp0OvBEW-`^?4&lsDcIe045?FARJxdyM~AaCtDkH#gL;s}AOv3ih5=hTfq@uqsbEt6 zGR#iAI^!3P`9Dp`_j=!oXg)UBuEd-=r6mACO^>ofV!31)h*zckMX29t6$9-!1RMB>#0zPSM9c8@To zN^xFD6^R|JdQ7~U=%b19875FZy>8<^J5DZ4%Ev2sYU+O_%L4SWP&mcW_@{{&06GF5 z0FKs%eI4wX!j$c)rVpr7l#f9vg=`lRZ!CM2%h=Tc(|g$vwW!7txo-o5m#mY{a>Fco2iG5BN>JHI@<4^EhelB)kgq=6|ueO7McQFHnE zqcSj=#C4o8X_7NtKweGjE2YKo4vJZk&~o(3eXe~S!AivJ2%sJ{4*<0v8Tm^ot->Fm4LQcgt@q65aEr2jtD z@u*Q4yf+|iUPJusHIE9_s^_bosalk;fl9sYnYww?lUiTy;sWhhDadq0m_-akz|YFR&&t7{2Chx1P*nIBIVl2>E>CM4f+;e<^n=_b#2 z9yOoXcmvYmYoB>^8KTsA<<*$i&H8Hzf|NpFb#&xp(j-*_E}i%^#G|Gi>t#S9U5zs+ zVi(P@3MHL?=s1ngKkZK>ktjz_B~2c@j8Ljj(@+u}&o>^m3;*B~&G&8|HOAO*1JcFM z+%!?*YDZ&{mGWMp0crndoeI^iy~%$!>9NRuJ_^8bUuaoJ>15|r@K(%KWGm%3H zs`niP`f98vlQQDldJ)?0 zH7DUjIY7lrRu~3f-5{K)J7Wjege>qTU~YH#P*` ztIg=48?!;^kRn(c=iErrPGvlQT>7(LmqV;MTxS<=bV@!si`0f}Sje1$6Om_Wrx z1KZwOo{mR_aMmv}qNch;2ar~9!X!LjoWk_gE>RJp-d1>;4gCHXVn90g*aGE`I3u)X z^g5vJOE7Lf9r3WFEf679^|1=owO?OQR8_5KDpyqq=+o9fo?~+?u8XRaznak#FAdax z)Pymq=!idrKR#<1I(fDqb0u;GbIG~Bu8TqX-ynHVOUQ$IrG{rkI+>8sM(ba`)+<}I zJ!r`?G3}D)mJnehv2_#wBMgDQn_Yu{Z{18D^q+~ZXlZ%8byIe*X6Br(V@y#<6Ad{``@nyP=6Z0 zbsO55b5YjKx&F2V5|ZD9T3c3Kwr(=?`A?sVve3`-w@r&OVVO`C>y>N`66%m_eU<;* z?W35eIp|$+Npef5pSE%6mJzLYH)EN&Sd6G^tBhMWySiVxB@m`1R$Eqor$o3VoU=Rv z(C(tufTpaPgS|3ErL)=owvGd)EEALA7AzAZo1tzAE>oAbZq{_VuLf{s%z%_8cfP-^ zp+tpb>(iCbf1v!1vnapAVfx&F8bhC~C`l3LDy4mrts*NIw{8}+kD;biyXkYfD)n1^ zGNSL@O;|E~{DdG+blH8DiN~T{lC3?<*HC_28A6k~dSr`&52Dc9N(KJv(z07N1+Q(- z?|hYPePJ8qmaz1@Z8g9;K5+WnX&J9>*&=UGgx<)?@wcVPuwt1w;VjT0)Ox|T0Gj^x zLS$?`BCn1k(%lpEC2gSTnkCfx?G)ga;A0}Vb#uH)f2&8P$j%zV{&ofPAbyxvOUJfM zZ`lfTOSV4BO@pRmK@ci@9CKjCJYjKaB6cIgEy2S0DKs6cUu}8z+cY1Vj@^W&4Uz~A z=|ZTk5SV>RH)okJ*L04sN(dYsUk#WI;+Q#inu7tAsw#f;w@oUkmu!8VL3-6T`<%>3CnfU7XDa0ya>6b7< zeWnmfuMa+I4KR0HkyIi(#4W+_zV_D5kV;R~^fDQo&+I-E*Ne9EV_cs<3T6XxnC)Kj!s%wAW$}Zlm}qCIf%~T> zBG<5i+JoThRl*9;1wMez&wlXlm6eTx(sNw6Uzi3CmTTjU`2p7UV(?B6Vv;iIfl7+7 zX36vO^Mt;wD=6x{1qM;vq0B9zub&M}iBk3$;UEqiy>-FjNLb^@updnh`ScW`J`_gL zUv%*x{Goy4m5g)@N?TtS2F2O}JsF5b{znlw@3)TuQ*9bB%~yx|mJ3kdl6O_5;LFg;1skJamF|}Cy!Q{x)W=xNX>a1H%E0fGBFt$o%bC8L3?FBZ-((Jh z8YO|9c(|J4mXM~2^S~ZXq&MP3`ua%>+Pe*CQtSTP{M3X@el3=N8jI7FeHnwA?FFH$ zHql*gT^p8(vz?XL{b`&Oe0Z*FdG7SrGK}zndT`qB(kZzpElW^hih8(x(%31pUW2=y z7**P<89g=D(Y>fcnKcGGl?7CnPF%s#UglSCDz)XrZF24`;K()D&*i zq#A&O{7Q~^mnJC+9PYdZ_#2Y7Q>I1xdqw1AGY=Wvuy@X)bUE-#I>^-AH};4nPrb|i2ff3 zM}dY#osf7va5$BhE8f+Zk_`?r9bnBVYP<8Q*^a%}$f@kM{vIO!<^;lAh?=Cl73=I1#(XHSU6=~Z>ERaNgcqIx!sGdugZ3Dqo0vH5{l%J zup^H|QX6?BqR1mL#$@jCix~CTUNf5edsq2rq2HBy-1rg(WtJKpp{4af=q%h{ne zZE9T?5Y_@=|CawFe3?wx;Qt8MkqN7l34drM6AmI1P9+nTNF)5c z*N&!X|341rDsY2w!pJEz_of&)+&Bpi|J)f^)aiP2-3mDg-?-|RBsVuP496Z6LWzHt z0JeJqGWXTogJ_aPXv}0w>!J>;|3T1kP{pcb8}12W{u2$!JI7>!8^d?WJ6A*AIbrh7 zeQY7`+-vgAnF~Nq>3H(aG1h34cg~l*a}O*5L}y6exf4E|&3*rW=X}UJr?ZTbd*aAD zM^E0l4=LoGt9q}FoT>LiMx8AFZ{BZalXuRWymLyvD8TAIdFN=!I~Qb1-nkp(ojXU~ zxf9vtqnO24-55CjN-enm35Lvn@M5vLyi|Z7u?&QP_krjJwE}WJO0javBvve>DY<59 zSLi{BllcIu`#@I0vnqJ}=*kL28ve?IlIfjMa{Bistj3FKfL&4rnBY(4U&wj8+sH{z z}9wQ7?5|4lDuU zo~WwjZ@v|sBPf2}Y3yj(_vM}1HX79i-I^iR9Ik$TCu5%xHm|QHYE8S0s=HnOr%XI4 z1eM$)OBUKacE^SF^k?=mHugGqJWlNnkI;nwm$@A=r?oIE{Rq7q7-`~YHm97+i zJhb<&wvDjgE-=|^zo1R{u7T<1*h!A(Pm7ZePzbW`{c_)pb6y;=pltu;;D^8Mp*cNV zd}=a!dr!4t06$YRVo|(fGe0NQ*LXBJCb%-bwo7lTIrjbNkm{-E9os6k8g`**^_X(s zjd2!%6YJwK%SmkIo>OYOR_uGU8q4|$d{PUwZLe(C*XDSQOg>o)yc2iR5U?FyK3W^d zzpibUvT)SEeY5{lGRRns@u zrm|vd_sW(MvBJRgZ0|GMt_bnF+?<wL zS5l6+4A-2fXW7o0t}B;v{X1~}O41MA_l9&6cV8Z}d_=o7{BJ`=-QtZigRd{FvjrR0 z&rW-kH?Ij?jNLO{t#RR{E4Y6mtMJ9nqeEl&I+&;ZD?a?M90Vq7cWKsLn+?UI1HSIA z|Ka&iEa|6RL5>tx)n)NChAqPrM;@}$8`ca?`*Ry!*6$G9`XJ?3eLme%Jo(4bf^4a= z`+qi1)bC!QUz!)YTsh)C?Vn{J(y3^)7a%!Q5&Go9iOS4^T&c{@Jkeb-JCDwe{qPU< zA2|3ys_aGL38^hZkXdBkq~Gb_}G%^^ohy)5aj@9WlX zmSn@}=aNG*@xNTQ-3GK3UlGLRxFRZt$a z3d#^5m7xw1p>otnkQoA`)(UYVDhh2K5V1lvG9@9kAcK4roFF8R0u@b;UdADq?m(!!cb}awM`*zAQ zi9t8;X&keW3&V-{S*qno&^a{`uQpfGF~NUn0(7j=Y?CBT`V>o=r0-YLs|hg9Y&0>x z`$=nGnjFX4k4#3&@zUw1f<>CbEyLuqYBVfTSg;qKN%g~ZkhoMor6fEi@9Dd{r^`pvt})9X_j$!3C$s%2rr z^G0%Es<+O~o>T_cJhRcz`0j_ZQ>=5^2CJ&EN#kLyB}bZLB8p}ge5grg&=(I9kF8Kj zzUk?5Vi!BvHIiEkR>wGBRA<<9ZTmFW+M#O_@>HW(T92X zALPPEjV5INaQIw~3}B!e*_j;KabV9vZ4EW2Um(*LVX%@{)SMA&0(83@7fI^Sxd~6X z5XJH@v&(Kr-y?^dK95*r-c`PuV*L?QP~VWuSXQo?q-#2mg9x;A`#-;qZn6gcXq<)GO zF+eUJ)rL?R%SuWWUPX6D#-~`dL>;ELal=c%yQA?DydC^Q6?k;wR9uL;4WO`nRgchy zB#n>@cVIgR-i{5gyI>5AQH)1NF9oq>CmJ1zQ z_iKf7Q0iOlt0Lb3TFvUHAEd+h@MOQ7qT`4FSj#>pkDMH+-U!3mek-IEw` zg-N7Y1G&(WozF=QahclBxKw$g1*2ANQP1OF;Hwi?;&>Kvb*UOX)YIO=fm*i5z0T7^ z<_J@)!Mi4m_Tf3z@kG2*;=?X@%g$>)caMCBZ&)+!u9T?R#hkQT+r{cRb z?BczMoU@=Ti~OlXzd*hwK#$7#(&2|~!}unmp0+D%>Ib$O-PY4}fzd=BDK2a=ije&T zOlDag`zMFxz4(>Weqi2T5iAcm5U7+a?dfn34&w(K4ar?Q57IBGG3YqHT=<4vG6cin z#rIVi`*;hoxhsuF+v9HGGP&0lNd?I&!)}>)S7kImW%~Fc9fhY$(z9QM?mJ~}ksuEp zH5!TSix!({8O?*=pDuLYDzoZc9+rQKUH0KOT!W#6$}U~a&cFSe zQ^TLonS#+ow}j*K=u&O6T!_3bDn%ykh3*^Jr_&W~DT%c{Pf>`TyxyV&ML z`-GTcHAgZVVItZb*?Hmz7;j!yT(Ko-oFo@^u#2UME4rS;kmSO@vzFA1D-WEK3q!E{ zXm48<6-z4xBJ5bEGM;Yio&%wzy1}3ITb83e(5?x}{nOc}OH{B+x}cGCif;5s*Q(VcSP!APDC`?M2* z%AhaFL|r}FA5=Gyd%M0;nNca?>77fIl3I4bdT+gkmOOA7n`Op5U0vH27-fS8-->$$ zsM)amJun0M$u&*22jwc$ux}VIrR5;n<=sbLF9Slbg zuc6Vx`VO!Y6xmjCbWZ&{kS*3koDwO}KeRBLksj^KZk>hsuwlyLJp>nq?rCq*H3B5A zv=lk9UM~DLk~ie76ZJ=>8r{<)sJ^?1l}tQ#piiUl=<*uxi@>(`I>Ce{FV&Py4!1fh zB@%YUNuqhfA2q;A5wchh;k!^dqe9xi&V5BJsUC&y;dqL`l;kVm64QEw+hPjFvr`yc zyt-j}VVqt*nPSZ*8cq{U54`?U6JXdR^Xw6PA3uy&CK0`aUzJb(rJ*hKRt7L-?4|lIBp*NogN+UA2yPEc`df|)=u4nZ!W7dTp+%h&?6n{w1Qi( z_|{9Nx{**Py2(!1`{&1}nqliEqT{s8z=L0RYM9*csSOVXlh@L$nCDig8x8a`EbEeu ziqNuF;D6{XkACYpOO4uj2nr`CPiD#G!h&zXhY<@75KV6axg$O)W4qYQlXn*pq@7W)g3&+?6H@)?!A7LKyEJ1@CvC}f~)dg179qsz`SJ*#~r`n37 zQS97l?~?UITb)VhU6RWk{<2%`^vG6tQ=*5!X`*lZJp-$x2Dyl1v7Hij(dA;87lDV$ zA+vEn4L^}ge3xX8;|*ueeO4_3;~d%fft`n`Ok^x8vs`eO%cf;TXQo(9jk;}}M-O6C zw7;t~1x77P@)4U|L#~{h1}CQfWoQ*9;Dqj4R8x&K z@K-D}Npc3Kub!P7ODy>;Y$DesVJU)0Qb8c{MprIOk160#-JW0kQ}cdKQ;f2ir3%-g zyKfe3ak73+(OzU^m0TDaliQF;tV*&^Sbttk7AXVuWxFCfA6m+V>%E<-lXSK12O}?Z zf>0S}7*{1;{&k9_L%fsQ=gQ7CAeJ1$?`M=JV#ytmw}LCw8H?UYB4P^KsBRC=_iFH; z7A9vyBtJ71$U^8#xAc+u_jDdexkujKx#V@Z09*Y8>_FFHQC}vuU58z8JGBu=a3orOGL>YuFtyeFB#HO)i=J=#yjl$S=`C5MzZ;-`<}bYkFFO;YiZ z9jQboMu*`81Y(QMv@Fbn|Bvx7{vCk7hubT^KD|7DQROcsiQlOw}ETKhn*v=X>HdaPAMX!r!$*~0SElbYd@TAzrw65 z=PzkCgfqfDa%HCt-FUFk#Cce}dci9d(;y!etsb7*FBc|ZXYJeCl9p5@TM#HvR~w$< zs}yk*JHMI>?Z+}@dL9C+8Ak7H8?+#lUDB6qm`!y^kgZix89kl#L=3ZW(R%`%5}s+( zb@;8)N&jx|fhpEwZ#_b#M-{S3bM)IJSfA?RXKaK7Elf`h!P@PE6&t(Io95I)tGf z!9on=9t1%OW|tC@%rqr)!wBe5d`wy3gtn}&4a=%nF#qiqUjNCT9gn71%~Y=x>f(?5 zEU{K26^p3}WNnpQ^Qlb5WMB^{H_x2C)(Zm(K0xOkGcjLTrO1q9jBQEwKmO%XL zi|NxK&M9Kwo{kDOYj;Q@gS!~wlg3E}Q;KX|tfU&4jBSkM&3fywsCEg;6-uhUoyftX z%Q>cK=wPDuvMQ%|G2QX3%%V>8x8n;u58L5SbTm)KC49C7yBd=R8-|aAu~BzCG4OCl3vEs# z4+JXD53t^qVO@mI+@4}RVdt}wLk_pEBz-WJ3zyGlPtO@hGJ;)_BQ2HGlaV|l_UQve zV^)%v%c&=t05fLBx~j2JtkQ{-{Q#nW*Q&TuH-&2*RU8dELUfEy#AzxEk+;V{2x2O` ziHFTOH1EaMtuTCGS?r6@)E>SI>`ICgL%aklRZ!TwXVq=v(}XTbCc9)b$APf=KNzgLSWL2gPDSHt`Us?vVaKyaLh3}-?8=PMdtc_W=(xhmP8{md5pk@E zvE{A0T+I+EbzDUHCgL5D0{1rPys=Nlig%)VHcJyc#TrUw#8O`CT~u#x;| zV!=so)Ay=Q4Yt#RuA7;$+D;VW(N&}z#?u7D6a}ehG2Q&^U6Wn1XW$)SBO)X^k~>MH zqQYTSv|P9%rt$^0nblX!&fP6t7BNOG(t#~{vfO5C63*8jNI!Q)(A`^m< zw2Mn|m%>}cHtOE2Y9zZPz0L{2Cob`V$>e`~YtB_EF=dxhi3SuN-J@eANot{_`p@7+ z3l?QJyQD4I&{4h6E+22}CfTVTL0C*~H9iI6?S6%Bm!?Z9#I`Su?But>40mM3m0PNo zG&Q~iVLQ-h5uRuSxjCjhqitF}wnj;HH4z6y3WkW~ji^ zXN^vqkAT0AU>A=ju6SPd>o03MdSfc>vC&-|bMM8o$rkT9J9nHZ^QU6#6J73fEhFX5 zVFP}@cFmB++j*%%L!MLRqX^a-UCP+B$e%G(v_N8v_N0}!pn z;E{k7(T1K*b0UTTt2_q$H&+jyfw%lVmg1*>#MxymG_qqFT+4dtswjcg-$$5m|i+X<^tdW5*_Pk+(rOH6)~XlftM)c^Z=o>AMWcpiddj$BCsnc!>?>b4um*oW0N( zm&JTTBI96@{V=BK$kKP>_c6Ka8?iTiAtx=R_LZYIk{96xtK#5^rghZcQ>Iv+-i}Ht zZo3d}1*}wd-DtV{3$kBV_Bt2Q*Cyh~NWnR5m(<&u3hv=x;aq?r^N3xQcbk9ZfPvx^|?M^o)Ixqrkofq{wj=<0|mVkI5?jHQI=%fcf&_mzSmBnORDEM%1o z&v#>eF(nz%P2_~)w*fYi@A-gj;3#t_v~Q+6Y!*;YAJ zj>fal026D}%_M1Y-Pq!u_7k^X;A!n~T)goSOFN0a2XB-ppTbg9>!w(jiALdxOG7Fq z6_#Jt|N4i3^!Xc1s74|AZ(EEc-wPBPzV5-{sxR(xjftd*U0TS_XC#IAZ3Tm16p>*v z-&cwTv(Of}cA%LgiS{i7z0*{pF{M3DWnv7MLR87I1%GoeuAHFrCG_rz?4YtMx80#K zrb=Hd+;!~r;Ir=PnkNQ6KisDo|MWTEh+Py47dP8sv)5s*CEFUOSEuY&%RHGWoTau+YF*wO-=3d{YS<)T1; zpg&md>lkM^REZA6RKD`Q6G9=gb5DCsvEqrg;fYvIXJlM)pu+hb_46G-Db=m+8o6dv zOT<+tt<0%hK`Kv2LQB+*)+uAB*v!2#xd-lIXA|vfa;%jcyyTIU&Vv0Q)rpMRm4#pI z>-!&|sJVrRf0YPsZ_Z(da;127uIA%^rlQ?Dq~LsuZsrl^caj{~<1e_n4O%_AhKF1_ zU8>MuxUNdlOG%W(0t!0gOsI>!Iu~v)6h2*k|5k>X%Y}Q`1!|&RePR&YRJFAqB;_4P zlA`3o?>sudUriG~ZU`YKFR4nj=!*o#tW4sX`Yha1G1;tGPz?Ug^0sqoM^s7flEClu zgxk5B-llB=lUvW2YIJFjz{aCJBc{9$;(K``2A#9eiK+KjQrAazJo0w(A?DGN{XVvn z$|rf6Vcm$k>#HPd%8g}e9qYS`@>|SzH9kMI;H6y5_{-=v(;0zwW7RQbZztN=eT-8O zl@eEm|=4Qb%7#i0ssd%<7*QR7cgt3lGLs z99KBmCI9~F1^y!Zj^)ml2!*P|2(F`Emw&m0Okl``a&|u4$)r*(;SMgQT;oD)-DPl> zoB-QQw zabBGp=;jf!lODp~!iKd=bJ}>iQqnky5F@*!Xb-{I1nsXO z5d2qc*p?L7vWqZppdI#2vVLvZU6SxSulcjKCl-M!QTqNG#dI--Ed!Pchj|x zS?Wexmt?ZZm~JF*oT%fUZ1<5DcZ*mK18qm8dGIo@uP#yivF+yGMhw0UDHAToe99A} zdb$SK#mu`Fqd?iX?#6%AQ=@qtMr8X(EdLC@ZqJC3J`l( zlD2u9zb}4)ZvY*%?nZZ%A9&J7b`jNM`DAa~_oRjP|Nmc);hv)jZzMnH+&ERd;u64< zlp@;P(+R`A4i-NT+LNa$_6L|zW2dKCi&cxxf<$!q!2PEm&v3!YR2*+HG)99xI;4%u zZietRL$Ya>(Xdl~fYmN^i;2XchfgCKL^*Z;x9;)!VEF#O?o|L?lLC(aTJz`iVii{KvdFN3Q{WOKgW3)+f;rqL~QvOl8jpQfAoKRO~ldmwS zOTLUwu?`Wf4HC_^chTvV%p*Qk;>9zR?mjK-miDkq)ZPWw-liO($?KB6EpR*8wNWm# ziz$EHXd+BHxE^Yri%+!Dl(-@9VB_@ke*ov%VQmn6Z1E$iMR@YskkX{M{PPOWccNd< z!qTi`tE(l0vTKI_Q}aHw_i5oC_dU_5IB{t&KZ|a|Ax(>GYeRWB9<@`*FHb~v%1kUMZBn|;|ZHZNVYuOwUd+!o3c4h7g%2FMtsqc zC3R!dFzZC)SqN=x!15Aiqkq*{K)LCs|JFQMZ~dovCSbNt7q(bM$lN`IJZ!cOM+(cX z2OZ17`1Sx%SDbILTUdRDQ(Tmi!k99rfzeV1Y#A&g`55m4M{iRG-{h7b5a-Swfmci} ztRPd8?OV$lFV9Wu>&q6oBt3lrf|3*@Z#S&uAMEKQ(T$BW9IHf>n93iquss{rZIW&75nPSQ z^=ZTczmPkIy;R4oVKatPO3uMOSYl9RyJ1M>3pVViR@;eJcyt7@0L5J`cT{s`)<-

L-u$m8}pQ4iN5?5++SKRufyP)@{pRfyO6TN!dm(cZ? zjTYUq=<>&IUD2n&H2Xw$UL@+_+p{hee_5{@PRpV^_%EZNew|0B@-yBjrh{EX8%)Fw zk%D9s39*N-4?DUB9(`HKCWT>@ICDbU+YnVQw2I^p!d>{m^7LVQAKBra&Pg`2`^$A( zRkD4}??0+(mFPObqGQ5UT>LH>F;Zyz#d~&poA<>p@N>jn|0)T#{1Lca8yC^{CgSap zf}wVq@^3;z>?0Nc5pT@>c6Q=eqlTK-3i(U+Ul zDX`7%-VajC8yxkI1h|aFMRxdmJ2fZkYFQ21%A+?!W~5S|m~WZi@9_L>)9;2BObhV} z8)HM{7dubf^2C8nhRc7`D~S2l*s$>zyT7jquIX7Wv0I!xki2+B$)eD#9i9(=v6H=; ze*FHZ ztKv5rq|cn_jcUHCcf!JRuqmw~A?o6c<;$zIXZ<1EkdgMe^m3Pp%B)=6_Q4s_h1%w4 zD^LBjXTsqA_VCd^=h817n>_5xB@A8k-AuTup7FYHt?!wMy8PWFJMa?6CGFhDFvpqi zUe-nTOsH9Ecpb}_fy0jY5Ju*8Sdlt)|1PfuJ1je@7aGK{d~yA z12aFoynd=VBj+K`blY3CQN=UgZT*=Oiyoh^o0Ry5kJfH_ID-!)zbSY=`Q6KF_AhT> z24snpBL)=%scB?&LES`IW*}Kjd^+fMeaV-tThXKL~l^Mh;NFkL1xvT_qP4rX~h9dJ!Q z(;DHPg{}`EJB7z)oqT!2U{KaU$ETI|)~RQ5E?gt@o2c`k!0X{;d3z>t_d{7X4h5Z+ zPaN|7y7e6}^Wk=cWfs?{7B0vhBpu z5B=R~Yo5L*R6I*f&&!E4n7B84G$*L$-GtZuZ&+7sPng{v47|Sye>HyI!4%*1-n(L; zk?~HIb>PA^<$y_W>AOjfeb=%F*Pbf!n<&1&EqqisH`(QTG@wD~yWPBYOHjVqGsm>5 zkd4MOw)^UsX%%-g7=ZINr-$aIP94f9ha1(;bRVBJ>#t9{;B`Il;f(qc=kPb~S>gNY z2h*4}+aApXIfVQ)d)0R{1?uZ~;o8lR_PNO_->CY2tKgbFldJEC(&`UA%#}|leUI9Z zwbKFQc#g~Ds-SPoukJlz2T)2o-wOb?I`pg|t@uuyL=`?g9aWn}xNt=X0JhvV_=j&e z=lZ63uiO0s{J&LM%MJ~ENUz34@`SSKC~B79hx*w;n^UJ#CNREHLKW1_6UHXgaVF+v zoliPzWV5q$bC5|0nDc=T1aZR_wkj2G72_y#2t;+Lza72h2{@6ji<*=WL!V zGtbO|ZyYnGSvoK{#9_CCw(&_H-vb?auH5Gq2= znyQ7$Z`+kczHrrbfOL6N#LVG+H~RYZ)jvL%oDjmZ*_*eE#yMS+NxoZB`$JFYJbhN2 zcDi*#cb4Vtfxq$Nsx0@HSC#$!rEsz}kAHTVxSW+6vS~1D#nXWs9FZz3Y~R)O&w?&* zj-5I9^2X4>kt#S`EnzTc-gMi*ZVpsF;zIqdv{KE5J(GI(L%Z*9!(R)Yr#S?cavUA% z-Bp*T2hpq_KW=QAvDkMda364%gWCGgv--5+*6=Xb>FK82>A~OA@(yn^NGq$G%7hoA~6zT%lr zdS1dNgS2yXlRm!T)u@n>y5MuWWhvquI07HE&f}@%yf*??=wot~;^h&@*OQ z@yOP&z#4Me&#hanXB>mRcGg1Gd@j^JdbTpSe9r_e{c>-e=X~l*Xt~Tj&U_FX|0xi7 zrq=DD>C=97dQHtY5i`mAZZr?*-yZZkRUw~f_C4y-5W5XtsD4Y(d9!D{^s1U|#`iaN z&xGu&)n4B=zv4sxjhb;)DmuTZc4pi+)clH$Z^Yn?)rDFHjLe$b{X^-Oy{>sa{HqP{ zkypg%{%lb(;0)~Rv~A!(T9KykKj^)-`{9i7B~Dk`#lsONXR3XV`t@D(-Oh+Qo8{$n zoi*TbD&H=RRW?g#2txaEZ%AdRDGo`h}4#nKRP+-;CDTI&gdECSzV+BVUbwh%u$Ke+KfMP9r}) zQ#X2m*@?CZAk?m)fi%!KWa zW)3@e&FGmr)Jow!V=hh)u1hb{{QPdxX5Teo|Juu;j#++(22ZB3ysn&K4!c(jZt~qC z_4&c_Q3i3TgLm%E{BIoXpfxJndRvzB@eP}9!!UNH^2_+T>lemJK8PmkUxNtWfdXGG(`|K)kV zfZ_Vt>$*|UZ#?MQg*xlB%I$TVf-3e*WbLbON@Mokh!5fv5hN<#&E$G@)=V)Fa;<&P zng6k%-GN{L7Ya%TvHzb78Vv`(VVs^mXfoOdr zTnCn1-r7C0abK-Z`q|#APC=}qJMUD!TQSdcoj%)-*HhCkY`+p1blz^#ci+`PV4iE9 zLDk15=?*nd|Lb{uP^ka)d8_}cobgcfL66HDjkDHz4qB&I^j_Zqr zH`ZDI2Lpw=ksfsV-NbkMt`DXaZm<6#i1}b5?&Xc?bj7~jsQ|}2Ls|a zp3UlsUXa1PL8W#R-|nk(edcg^%f&3S5BZx@ADP5ZO>h*>1+fBWFkca6;Oc$>8zIUlWjs+P@juPRTI=Mib~ zRi#Yt$=f}O(LTrd-;1Wvt4g|ofmidKfEn-mn?+DIY9FEMlWf!c!$|(O@R8~V0*mmo z_G7=qvRC`cHC&_8Wih5HxF}R%#K6y?9xQ^2z?%MKcO6r%8o&8Tky4>OnuBZm+(sIC zi~hiIm_rRm`)+ExB}O>>^b8}A%))58;g1kgROc5yNX{$}79ZR37HvT2e`}kVD`+B$ z*j*z`c&QL7`cM6xudaTjx+Yj!+iUtXM#Uf#e7~QV{iM}0MzpWUIjdU#591?#6i@j? zrY{<`r@Vu@1P2Z(o-2M2%br|+@Q?QxW!q@Kc6v?^*Eapfh<`hprNQ)NHmFW{JdGbgI+zmU#mDL)=@kXr>k7?e`?T7J`>r+ z&11!`Q3|6hXk)CBY}7XBo^~Mg$X&#wEsV>U>6h;KNiEUA!#BSWA6Mz1R;{1I?=e(# zR9};aIF5>_FUUDRA**_Zz9m(%<~G7%*M^jgde#W?qu_Bk@qX1l)IoCsm?+83Eck4aSg^nNW4^Oe7=CJnQaEcz8}o+)Me%vvdL0ia zZ~~@4iAM8xAu70k3W+DNwDH`62F>qK<(=Hqqpl+`Q=8>1sWWbG&C^mL+9x=xFdK!R zi-6+17wgfRHo?gw;W$4d)+{+xjwoJn2fjyu>d_Ou?8xTLvb6iSc*eg-g~iIEllq*sHA3y0gw8h0260== z!4`j+JO7npy*QZOy@MBg&x+xK252rJRuqEoYXejQC*r}0<^;tj?aAM8i$=Xh;?gHx zrnF?so?4)65o+2Q5C44x-`-~(CDB^-jlCzVd(A}laP#^w4H9?H;McK= z72#6&;D> z3X9KGpHnxGW-fRtDaR*f|AaReRGn2zPL`&k?*+yRA+&D1yBn&`zlcO|^I4QodD~Wt zOI|nu$0ka2^EQBnWf3J|*!7Ju8&)ZWyTrEiO2kPU$t|RjH_O}p>Ob}3JMB&Vjss$% zD%=uqQiOc}_f=15NTWM^Z>=Cgp%(9^|GFDKWv0f3z$cmzbL|GwJqO04-=^@K)iwN4 z8$7=`W}BR)-NVf#J12Av?TZJDxsUJuqt^>;0<=qRud3s`7>m-+0H)?&n-P&Op1=3i9o%YX^lM!J8@;`LgbfDHX2k9-Sn zv9hM?fH;sVg%PH^qDw<|aVxc9+`Cv~*ndq4}IzGF}!f z(-Z$dFZ=-S69S|4Ju1_W)0PQk!*T${8q_AI7}3|p0sCYbz|R*Uw%Ut`4&~@iQ+T|E zbga58+|MMB0rbH#2?jOI0TyIgHb}Bdn`H$4BSqh_Gius0-PBT{>>-HO%pLfAHozr< z#A*ZP8M@>FZ)i(9NXo(=d_f)oxWtz+)^!_45$br&D1VOHjv5)kJ_g2qp|cwZ5v1x&HPNo zLjyn+59Bir4bs3Z=gC9V`+@=Ba+FzCp zM^SwZuMAT?ygW+5w(0V1bSCeDpta|2W}5f<=wC*x-j zY}c}Jw5_CCCgtB$m=!}=runasFm7=J(lW)^U@_&66Q%-ihXA;rSwE^J;Y^HdJ?f^F zT+}vHqvrerVOouI@kwi7p45px0&eS($IduGH0k>mSJ`58v5JKHYW&eyIP51jYTtOC4J0#neA7+;QT1NK0tf5ES!H6iQ=B7{V)Yi z$F*cu_DbMPHp-g?d(lEMpIw#nqhfi^ImC_zsCml*g?I!clL?c}0|q{Wq}^PuctV3= zLB&Q~xM*&3ujv|Ep8zDiCV}5z(;(_A9YWu3hnIQ;YR*Lz^<1qbYizV^RuWqS-C;P_ zbc{oJviT8;hkF+|VWhnQ##(D59qX=Hr5p`1%vk~DDRD*HfbK?1Qc?aBXnFv6u5i%u z2Z%Lg+y;JbH)*D6c}pCrjOi97)Ky@+6F%wpH0 z>XQ$@@E#cuy;KWxjSmKOk*-SU?6YeSuLF;B4sq0GY+q0u+@yJ4EDTbtcnkfu54`VU z@m}tlgm#W40e&^B+rnhg#BH`x(KJ8om2L-U5}n!fofg>)=-ouBXmhLK4XJJeoGBpQ zRt=IUX0z-@A7En(D(lM^d4LtoMaY^$Wa+3X8{Q9#l0XiP7-8J9@Kiv(8tep~e^Rka zeCAMo;;&p_LqKmI=%WwjCfG&;AnZ9)6jb0mz}8Yhy}jA zniK`Ex4eee2c*leY!GpnLn!_-0cPGxD&CLEygXnhsj?3YaRc<`CI;SjD>s+2GNB8miHB)}(W1GI zbksA8UGzFQf?7qr*oc(V2pjIz=h1HEJ5u!Vw;EFyNqCtKW zvYVUB@JQ(LcLOBw0Ir|KPOANA|Mt+kf9(gBAfUPlP?81v$&3dt+6*VD2l7A3f3FA^ zd(tKD@G|>I+)Rr&R~%34Il^k7-+=b*BIWnl^hsEy)w0%`@R2q|hrEefq20nQW~{-@ zZL&C+wV+fkHT(u!JfC9KCqDB69%qhzm2(v_Wqiw>V_qeIiQufkyukF5fZtA^&RsS*0O zEDYPq1VIEeM-TODeP#;J^GE;u_woP=GzSwU9tFCM9fe_Q0zuqPuhz=G(mX^=DH**- zXyWxH*)nTEn2ySyUke;&L@)gWhO-&SKfG*Qc9YLki~`}VM1yc`=`GDN9gt^%c+3#;wFe6 zOGA#`SrA&5r3*IA}xrG$x5dp`}d^%4By%|+SL2oQj8;t>IA%(=s2IDk8 z3uik8F7CMakcuDS!?+G7l7px@fMOR#3ip90mYUe>K=6m99lPw3Be&afT`es|eJR~pgwqF6CUWazDEOEzb2K=LbTt;fx;P-Vq_5z?RnyLs$Hl{>&PIKrwo~} zXf*(OPk`{Vz##wlz?Wdefhu2gC5Yov%{|1)9RA@niv}oVc;Z5Q4ewa9#? z3mN?VAb%QCZUK31f}qWYCUVs50>Bf1RV`SN#(4(d0ycnkJOvskb*T~b*$^Tf=hfO# z_1zCzMUeLWeia3dv>tWA9Wa|U3)xHh*(}c*h-A0eNR`wW9q=y= zHya;ajT4fA(++S0UK_y7H!05i$K^ve9W-T#wfSQRqe5nJrD5J;jHzc0z+=5Y;1c7$N2!sQgXATDK_?L}XRZNRe4q7kKRWN zIBvM0Ktl~k3gGkp8o1853PcI!DJZi)*yfWEwP4Al_#Mb>2l{8$gOxa;kPgbmfc*)6 zC2$CaAj%fUYKp7TB5*_$ZYiNL4HjP+;;YM~<1j3zVOX+YWS2r12FdL!!=NlE$i&Ez z28kntVJA4qRq>b)qIJgcBH$QmNS_q3y($w5(wWVBTtEXh%Z82Vg7k9{^R`eD+_hrF z8DIcc!J24!>!(>>FjE`D&6Tc5=<)#uTLS*03ED!y1u@4n(KQgImQW;+@LSIz4pFcj zkbuGnpdNP+Cs+&@wt0>SFvhL;R_&Xi{-Q!1pZ}6ki%Gdm{cp^$nz%N(9rkgG6#R#96V%JmD0Ms7>qE zZ@?T_^hr*F6QT-kf-0G+>`=c1pbc6{_@_|WV`LfrQ47SMU!fc<#Eu-w{bJnRp%`0G z7fmJb*Y5>W+Dpr0!eLngqXIzsVuP3h?&*b^^$)VZGLbI7Y$HHVK@^GafI;3CBxaQ4F|t0>5e8 z^i2Z@5X~i_%K&2d4v67#FtXvKtQ-rH2Mh}s5ZHQ&ZhAQ@$&85_6uy!GXIY4Mu*@0- z4#V3@#k8_wf?*z>Peros199&o6-r@hqrkVfVF+W*%xJU?hIU~gum%Qs$d(m{)g@zC zl3lRL`c<5-*bj@WD3BF<^iyHj>|t$36OPBHvw#Ha)g2jp~2L;qrSMkd!dIb68PDe29Xew-84wtHdv<7#A9ULg@rKJ z74lRoEQ_?IFfOpx14r9Quxb!9z=N>6N%3BLVn(A(7s zdsysEU_(Fa2NtaUTrV1W+-WxVbD_c3gb*X1*vLxU2{f|8w=^ zFEEpfV+1GF9kIx9n*$Z_04j?0L^SUmRP%chfDIxAs8>c+tEZa_(J-)2O~ z0QZ2irio(V4k{kP)M@bJ%pWnk~%VAQYf!WDrvoZsL^H4^x7fv_|_JaaTh=>Ll)LOV(@j<_8 z%|}c}!?~q+w=X-0JLY*zkQY-}RILrkC41F$X+iYKHUx3U37E8U#A)-aH=4_x&3#kuBK? z*+Q~c$Tm!tvKvAoOvsjyEn}VRyCEXkA`+5h&oK5qQ-nl@u}qR?#=g&U*7ttB?&pvD zcmICR^M}vt({)YLc^$`j9LIY(&Bz02TrDBce1+~B=pY&08M|#DCuVqSh`4>b98iap zfL<2Tle^3Q9|0myXjc!CxYGmbNC(J?rjQhh5g4}@?Yofx1Hm||fhyMscsKY)PKfQ< z$T+Yopg@ps1nhPau-najuv8h)PGb<9FfeNW>Ox<@s69jOH`XMDfYG80NuXGVvH;aQ zsj>T?krP_O1hCT}-Z)eQG*dH(zaPtQ;NviZW@-&87O02e^@y_=J9Io$+P+Kn_F{Ge;F0%eLrupP$Z}Cd=4!kZi5_V1;$XNvL2zT^<0$9;$==ZToduzgFoEj{m0dSR8#P0S#cLXOqi-KM%=;Q-{!x>aZ1cDw& z*NJkRf`6DoErI?Lfndhng80%RfvQ^usI@j2ZGR0vZ$Kr*{$q#&F`2y@tbhhzq}Hqf zhCC5eMs*J@NlXbq#7a<~TRlk#Ex=OPsux0lQm73Gt#@}f2$d*khySlkz(FQ@lmJL)1!L6&oeF?bj5nC@I~>e7c4}JCGT}g&bB!+} zMIugv3GvVQByv~dVSt`+gAECM&qGGC-ZSHefX4il7q$SAmFcbpEr;fx6qbfyg5E|a z5W3ZNjmV!{Z3`21$m=>)x<#Zr=Bk4*?;i! z59pK;cnJuQkIRw>WJaL0{I&0HlTU+z%!f2W19BtKjKYH5SO}!`p1+9QiUW&ZjprtP z0!$hUV6Ydc2vaoJ&I0>}|C~)Z7TBB>fESH`m_U;VBwdgN;E-d01q@~Po)7zLaxviN z52EgQ0`W_P93Bd=mWB{m0#LC38k+843BVf%It?Gj8Xbp|5(s)yU5+@9G=U^w0Nz7N z7KGh%(*Y$M1lm&;XipaCgqB4F+wKwDu1Ko2AW441e< zQGuZ6&wzy^-u^Bp_Wtz zIHVt#i8ml%FSvq90R^pz50rx)ejY>9p$*JscYv)p$sDt+*tsO29bCN&2HiNoyf^Vo zp;DkAGeAM=Lcni*RCoNmZq%GI`J9FwS`g$c z2Fr$>n@k{(n?Wix$dVv$5m1rYUO14th>5xa3#wL-R0^PFy0G(qqIN65=*~lKNJ zLFb=W1HzvK7!&u%pBHSolXS`EAQfjoi)JG$XxxRw;V#&B6IrY0m;s8R0jZz?smMa? z($6A+L3smI#M7gHgX}c?E(C=FQ1>oiAo?H-%;aktmgqP^VS%8hgdnnVcou@K5bQ^| za6u0NRD@d35U>b&kDGwfW`d5wTol+>!%{$hmLgMYJm3Q~2#w$slq%>_0@IQmkg*2P zpFbcNaWHfejGb9-ZVwKLg>e)6u9L@c*AAfAoDCJq5-Pts3= z_w_lXC5vk86To6E$d^Y?&;-^Fb1x#xRph(98%#eWK1)ZkL zn^g{cWjuS%}~)l$!-gxA%e^U_F67B9uiCt^aHSZ=iTsabocc zU{Jf@okNR3N3cdGz*vcMv`KLV3*d!`03Nry#xu`lgQheExEB}sLO2RRgVaN(%CUIQ z%f$zh8@s!VLA0CzZ3qt?;0Imm9QjOJ142Mh&c3q|!0HUZLIv=ip%n;bV7;tE{^dX# zwWkmtETAYOX zkJO8JKlTVaZEmz>(L-_$nsR3qh4T7c+aPSOZSSgWZ!2z?~0DD2O57I#|AOjxZ4y=&a_#f)f`Th#Tl=oRmEU z9_r~LJ_?f*=$EQ;8l-C2PR=y~$@lj}{=tXM0PN&)1Or%T6p9z*(gBc$4+u`UAv(hQ z(rm91kb>?e>1v#b0IUd+5_O!6hQ4| zfDVNPD*%p*hLC;d4mhDlo1jDz0XWqgXmalObl|@S;(bD2BiQ5sgPa5cc5V;jZvL(f zfCj)^;njcvXmDY+Ly(&}AgEACpr?=>HYW!J>}ueHH3DFu1th3)1P$PWsYvnJPTjRD zGcCKL_<6a=dqKce_<(?F+=j$d0}V-Z=;Z22Yr|tU?hOsJv%@Z zc-13jNhNQ$QU4&Lu+-1T%C3AORhLT+Cs7)GYufg3%1}cM_l# z1>owPh2BKRgL%^d180_80Z8Kkf)5p$bm1R383Kuz0uAXj`3&el0-#i)aU%9TM1T#L z0cX(#3#6Jx%Fz|nA1%m%C0RYB?A`?{4g)K$0W0nVnf-9I5EcRiHnARU92rqn0jdLu5)>`~r<~;G z#ldDA1C!AjSfc020zh$D*JiPm6zHpFfq`wnjQ0bZ(ep2N1nzw`f(bP5Zb1+;)bw#w z+nfYgTHcxl=agW&@d-Gz-pD2m)&fkiq#e zfRCmCLWGb%ziIoQ?}NDgNLLWi4X`$6u(nvR>`AZ(+z$GnjYkVO5O6_o8*dJ}uN%1^ zAJ&lw>cJ9FM=%&POdv_#()R62>~aft0YpmxtSN+i7Rb1632pb3z*f8gi4YXhcp?aF z6lQkpd|x~_AiPolx&;7Pf=fdiodg#Wu>T;}qyveCUz|l@L1R_|jrlHsNNWHm3y>wl z!8r#EiBNr18nIskOyth6j*_ba0iU%VlbYK(Gi7Wv%OeW;=Fv1 z$g6`$L%^RoK^}NP9#Rop7-RGkTmtHdS#ALdQn8b$1faq{o$V2vWCe7#cm!27*p4W$ z9Wj7CmH{^y3R;sNhyxs;buqH+EE}`j&{h)S3{c1fk#`|681Vw&&k=yZVF(V#SfZt7 z|5uyUz@|;^jv*uw zbeMK001;vVMEI*?05KS6i;je`RR28*0or{aP-938wopJ9_(4VT15rs1MBzR#PFfK3 zI74(|8K{SFP!HLlIbSFDCx&%YgPoxOPV6_W2|@?K$#n2XAFz+8!KJ6*(l{_08jq^M zFN(My^kG2}1c4g`Yl0ZiWZ?IK?E^ZI>(nuM2-JpU0}_4zK(K481>FC#jlzNZ-k{?B zz&(EOo?g>Xd+^5{@N!!4&qOs)^x&lTYl45fnPBbF0_^)S`S0HC1s>JdHVO(Z!|Oo9 zm_D}p!}#}adV`(Z@V_?qJ+QgK%|dVq3*J8)yg%PGbhh@WdX)PR+~l4H-kF?iL1#9g zY_9-y^##=R23b;o6&x-~Y$Pj!;qU^VZg~(H0P0a0q#y#Mpa#K(0x9?_A)o+E%K|X1 z3><_jM!40t%@F`j3n5b%039We3kFWg5lBD4CMfui(2BD{#|R3|_R#ipRqoLIl~J_8 zx*Wi|^gzgxL6@rs@tg%A2A3tkWq)uvb`6=*HrLnyPBMd=plIP>>5Ske8>nMi@PZr= z%29B%zTpV9#`q4zKnXnX2XL_m1nF;#L8B*v*0}>NL#L0)2c8tfr)|MCMgedU34)3O zK@|tlrUGwR2Vys|jZy*&`1^0*zz^p%D)>Jc@PbtE0^Bw#5WKz*T#^TuV!*<` z|7*|HU#$%wP?+vxae0F(YY0hU1L{RPf=NebAv$lbXMiLL(Y z1b}mrN+1afAPIj9feUX50UmQB@R*_O#QEuX-H8>(2KN7`t0rJL{#CrMqyL$AI{>O4 z#4C}Hz;Lu6fBqQOp$TX!%QUnAY(-u0%&)++)PSWbfgc?ZFAy5&r)?DBA!T)o=I_SD zfgeBcJbNIlmuSS0A3Grg6Z9czLRSLnZbhYuLOcP{g8qH@x>)nZwuNB zba;p${HTe^b4AZX{HbBdbEiZQ!PMB4xgJ*Z)yPR*(gfWse@EA9OW#e>F)Ktd()A|k zkj|dBqf!LnMD2~5Yh{IehzJR#hNaF$i69g2gCROosCgqgisqizq(xi>{>$ylp$cW%JxvN}r``WQFX;4twIGiFVnLTr>=M3Ae zp1^6^&K{m6&1V07dzx^LT@I38yWC04nNZGM~;c z!U)&@i%?hp?>Bn7OHe)3Cz$&jqEaapVzEbIUB?Su$8%{=g+BO`B=-;jm<(g3-i{bRm(A^4-`m=Ff{!b&$zYu9WqU2nK>+=2(K1BROasLgKY*^a4 z8l)RumiHA^BCB_F>sR0{qTW$Zr_&FV?onGbix!>m|NsB=Q=T%4h!7%+7^~r`tjV#k zxPz#p^!|H`4@G&;wC0%5$X)cDEaW|>+%ZNu_HTMF@tsCZ#UZA8QG?F)X-_hziQN!q z^{1wB*(Nosu^7C+#$^4uz-@+=haUn=k8?kLzl~5;T05*C6I4Z(<;B!y!^GA~I89u8 z%NPc7HW3#scU_%h#MeTLe$ITBAsVaZR$X6xVbX9UI*_xXPxuqma@5zF9`YrjI3s5m zJ6z(ojr%Kb(-|Zii~-Uhn7mvP*#`SHeKXw9 z*9Ll28q#iGIBq=JznTZBI;MqCI0try_A3_aG@SCF_rIbKt6|NeDpe6H2RO>dbV|d_ zv}IV$AMjm$1onYdh34t2Wq-C~)pD?hZNrC=>G15seeGqa(mLN$TpBuP))wio7$A$E z&|b#W%;4-dRLi{Yt_u!LmL0s9+}WRPLf%{!a{2RdkEIDSAqW<3W}A7qM7u~l7qnV1 z%!4K@N9Ftp{oG`4`L1+tPEW5QByOXZ@%dii+bCD22jLUn6tIugLuiwHlqjnw32J+c zriQ1e8BUy_qdS4pxoR*IV0C%o)`=7EU!FX{d*Z~2hwhGkf&Ma{-ofq*u|u&UBBXXFNoh{-l$y-V^OT6;gZrxh@nNrvjAL>nDvlJuj=V7MsySJx67h z^zT9)zNa%ESXA73HEtkdX;ERpdO=7>C;7>_bjP~Pi-$a{Uv3SDdq1B2ja#$d{=oh6 zh}UlRQ*L2?(8lto+$kmRaK%}9;bE7BZ*cBEs;SX7LvG4E0#-gSowLdQ<5G}U;TDyo}m)YKCCU=L142W5Om@u4fnWwWL7%c)(lmgh^l;|-^J;S z=J!eF0w0X|yVdU}eSDVt>t}#_wwsb`;B8e;iN~ppeRB3^epuXDI%8M|AC8IR))Y3& z>uOU@%)HHG`MK6t(rWEpoh7+>s8fVnt_uHgT9P0(J#UPf>ZE%n(lq#jkv2~5J+bO{ z*4L-#d-G9H6!-|;+`fa8p0}0R4`Q60vVH8H7L}*o7Pq<=cX8R5`_a6QqR;${DeTU> z9}{8^rn0^)$!aBO@Y-y}5H28I>Uo{O>&cysB{-gGnd4!AK_J-|yv{-Bchy)6(sx4? zAWoz}Hi*-F^Vxz``lC4>W-N6wVLpcri=7*ILb!>z*ySe=X>9kCdfj4dPH-p96m=|h zW}q;{XxcZQe$Zg$INS|bMxx6&1g@e7=gY4jN+Zyu;aGw|_oo z$hmZ?;o8lkd|>SxB^TCw5e1P19z-}Twl*Glc7B6| ze&bFtBptFMF=w!a$`p(x>5!+uYR;Mo>p?=N`gpf*a6vrg_oN`+T^q8H0MbK?%$l56 z3l#zd%=3G61^nkGPhiD&LzEyXq~9?FM#OeUtzdy1r*l}`pot(CovCuXQ%iYL2tFI?ef9W`V%>jeEN@-mUDGZ!LqmI9>k7S6%iEbWs zwMEH0gnD=?xAMI>}maRQIyaCPJ(z8a~_a4|@pbwhg*+HQR(}F@jUKqv_r;hxyN7d!w zyp5~a$?b6V&O)lgxzSQ9RIERA?auAC;7wN*<22$@lEBfjYqjzEA%D1%(X6S|+at&C z{g)5mef+SE!;Pvk!Grt_R3pl|A;`+8a7P?Y>CW7tx7B#urw)e}sun$(eiOvxJ$yWN zayQI-^lNmNX>ae6llsn&TQeWHeto15XC8?6E~iBtWznQhp9p{aaWyvzes(ACPS$bw z6uEL60k!ARK;T*?TTTQntqJUS_k})&1ozZJLwM&Va&$9hdEkM?(*aKE>L#hJrQ#3_ z?O_ShWZg)GbOe8wJac#N{=%(Q zvX-H(etITjW;N%dmr@JTy4N$rVJ1ajQjY7!@z!@sW~9=3^>qR&&=a-0xT~(ncmabu z2=5(Yp={5g3Xcvegy}2icGS?*3r+7=b5Y8krHeeHl+qHzxxIs(+J^APKa;F|^Em_D z#MTQ=iy;flvdM@N-RXvXKH~Fi1YtHpjB@N?)u?Z0-%4H?+Ma3oH7V|yK_PAG=+!y} zW*-ne_eo8(;n-8p1r^85PhgA7&Tb7y)@A3b2CSRwjVbKNbpnSIUC~N?0@B1V8w5|3!jDv^ihg=hcKN}= zpRQ_R3X}o+!rl%Csq^=&&fd-sUAxqN7ux&~yS_)nD3BtC2Uq1(R46CktnM_1QnDqV z@O^9)<@{l%cA)-5zQJ4l*za8SVh|KJmeukyw2xyqOeGm^)$!WQ&c>TGb7r3TgjW>d z_U@06#c${~eT>N$)|gQ^_d7nTyA{2y6H!t07RP+CY4J>DCOoH@fM8NFKP3F5d#u0K8$jH|Pm>av9r?JaDLA;JJhJ)A> zNz|sPE6m3e<`#+Fo6bKZ!pB<`Lh13ymDjsJSLzP$bnmPflMe)o=apEHQ$ZY(tH8&DPFK;h%meKP;U4K}yt)U{Y0*S&i z3m>P5{;qc5z%i|w=Nphy?@HkPjG*@}^ojR9nT(bLu=#Dt`E;smb(XP8*oU{?eShPnlSGx;&J=9sc zXQSU+odriQNW?HmtxHezB^=dD;KTnK9Y}?jdVG)}F>zoi68iRA+deUy`xqvHf{e z6~?Me@Tn15&8vj2VdU^9Lt-_g$g5f;2Hz~5%Rp(Qk8Z#*^5e`7bCIv+nOTT8g5T-amk&y4hih=a$c*2vo?9Ab|3>6?*eBNH^?1E_e|9DG z%-*klXJ3@f+Y~v#i)`U&L zgq1iZt1=8jpIwid!r|2{N^}%n7mufnyV7`eNgJA3c;n7-B=usKQ{J!E#$@g0;8tVH zv+k*v;>_%8f;<^U=f6DQbvg2F%)lp2jFPyzDO4*#bSpvZc$CnO@Urw%uuZMzHS_EZ zZQcgPrCHu?-T6?3>Nw+P;a9WdUBP#^eAs_=$b4X6cZ^9t)=&6FJ~%VN7-;*@{F&d% zQor{nh2iDwQs<#d7{vlSbNIV=m{TjJfqY@} zY`^;?vP}=W?G%Rh#rXC@6$fE?y-jj)FGIh*GlBl$EuqjU3Ld*l54QSnkXi78Jwd+| z)(4#LjGW=_!CAt(0-g5zOvA5t zEg4nBjtB}-sy#~`u2rYpI3&G#a0pKuVfT}86!+l%#TR0RySK5c#yxjvso(Vwo#%hy z$Lwnd+AQQoDF^(RhKX`|jHt6mGNm?FX5P9aJ`&jCTDg)J?<=%I&#Mu$>s^C6YwK!0 zANaB$Ekt~f_AS<9fCKsKVq*KGUvw+|G4F3&+nte1-PKZM^9AvNQ;+P&eyTLbV+^Aj z7Z7zy4y~*d{t?uv?{2$diyNGGOvJJl+BbVxIgpfxipQ*ZaLFB!Fjfjf7UocasWu#W zYES0m)~$vU`zQNC)>(OPwzodIvVCx%aK3Bt+B!kQBCUpJ24`AeyHreHdHJZXpK|bs z>OTAGrLP%+J^PpU18*E#W3pr6+J}Tm5p{XDj@5K8xnlc1rOzbWQ13Gn1WpSJC1DNj zy%g=E#1%XFwCcxj5A!c;L75@d7_F{Lu}v4w>lUcFbzU>VzM3&3tMlA0YJkK`oaXweT2J^QiDp2hQCcd!YU8h7B%fAex2be=Pg z->6`-BZx=o!Bp_PXPztjJJV`ih8i5LkjR3<@X@KQj1rE-CT22iO##;1EF1b#uqPWI zE1SzLpcy_L#qfI}9?7}~Vca|45*Na{6e=|sbP@L8QMmr+pEXyS?3-RE#`^4a8*8`a zvs6wLj-`A3d6;-_VM#M@XGgJOdh6<_E1Q>Ut*=2EGpb0ddznn5j8aOr7~5liOE{(- zU&&4L;+|9esop(#L4#eXXDa+LJWscR;p-AEaW#MN)B{!U3F_+G;k+)ZJBBxJg(3nK z!wfQN3ZP~2Nzadmg()J?!nh?!@@jSpu6(+_L28s8YVe-CML)u*6jpA6Wa*nsZTl5~ zk9$+Lgx6){Xqm~ol!(1~6unY2i4XB0wHkR3+z7s1bOH5?JCJRx(P6&`Jnm8#`z66| z%J~Tkt9`oe&sW@c98>~!-i$H#Y^J|)JKwj76FJK8!6)jV`L_t1nA?pz0Y-vI4deJB z3t7&|AKJ*$*Ik$2BJ-_`2~z1^vL$@exLpAPZarjtX;}N%nqtW7awva(52?HkU6}4@ zG6@Z#s}j0fZG1B<0Q=*CDlTQCpL#~wA@o3Uq}Jihb9r@{$5cI&uc)@+6!oL-wnLvje2?hc`Eh2jZS4-Vm3s(%Wmp0 za-n8ThzUBdY-}rsd1p;9xqcKuaMw~AP0>BZHC;D$Rr+JN*!Jd~tK?u!dEuAK3)qxP z+1shNs^v7g!%BK1IBLMhq7v#WRC!(YZ!t~y4Pd_pe>Hxt;d_dy_Gn{)@0}fkR_o(v34ND1z9o-TtN0x6OEJlZNw5)i+^s4O#CVtP zGp$pEZXzFgfAvxfdysa_#P@aAF_Z7?;gfSop|_$A=yMF+p08x+nhC0fRe#J=zm$!C zRw25UY?q3*0bf|^<_(hpRdq!sFIxPO&@oAeKJ~0))k+Z zm&*Wt=V?jit)A3Cu43gQE+wB^!6PZ91s~8`*O`~ z84`q%Bf}qpdc?$nRHky=m0T7Vll6wABnAd@zSY>0xbOGowvHLPE&EGZo69T)-0fHE z(3)Ub{2kxiUT>Ss*`X@Tm&@ z{i*2t30z5+453l=`Jz@o%ln)9mik$z$8?gk2Y3zdta4T=HhwSSn)a$8-^O{VW45T? zKD!5g{dlRqS1F^)_=L)#|H zJ|~y@B}xq%xN>F1zIpWKLj|kmGu7Hh%fla3VpLQL9?QocLD;$(mRG)oTznCh{_M)V zk5^ph__Mm-Pi5cvGNro0Zy}w@HQTjhB-85Z66ed!k;M`0;HgpnQq<4nvsiMWO6&W3 z9~z6BHce_Sy#HCKpb~eX|IUDvEJ%}_%-Qhs6$Wj!pQW-HqrT)5KGohXE_=Ue;+E+sle3M22vRnQ?_R&FLQs;&f?eZ`w}wL{Li=&!N(*LC1n)VzFL`% z(2X)VB19!_vwjdt^1WHwLE$od|2k9alcMXsKk*+r65?_Q z*co{sCCzour@G7LaX-_zIOVRSsNDuKo`Lbkm8_xnx7&F>pTQ2_9jW^MeBxS_WuGH6 z-d6nBJN=o(4Qn}ZJF~p4!5m_;#V*C@ZPw4+Q3+)~V(rA0@~o|5izM@e(zcv)R19~% z4oGib6N|9?G0Mv-WA0GAWtQcY!#yUm?)dSBy+Py69{<&++R~l)9cZP_d!v}m)B(i- zDos&Ml3&tn0pq-`J9n{@R8ai`7ay{YxS7XyhZj9^<|Wh~Er%0U_$)=XLB}GASK2en z@U$(qx19GpZ)69%_%8_A-%sf?#`HRRNJ}1Y5o{~o*|_3ZG9UHsimNNgG-MqOSA}Q^ z^J-~c_MkC(o+dAO=cahbKn4SmoVi4JW^_rGRjJ%^elAI*P>NA%nS;RXviNNIhxHxJ z!A;%AZ|3SgKP++6 z)ZBFo6P!{lE4iC}-Su@x5%_iq_IefV?wXH%mqxG?hI&pJW^_zNTDC8?v>tu4erH;- z+1y^Cm^yx8ga!3NK>qeLd*tEUS5<#@Y}B8*wMMgW2orhlsyi82ss7H@(Hms6bm0GT z#UEQteN^t7)mU8eUcpso+1T75X(MSp@?b7A3r{Y2U2-Jt+TA^{Deah+@?2TPwR=}3 zNUU}P9b-QFbE=)*nKS2e$syj?*P!-g$y@Y$j`p`9QR>eX)5BRy;+WOac4D>0$vtO7 z3Av_b-&hly7+pECxlEqhy&%fSN--(w>)NVEN=X!GG(s>BT{_=hU2pNf9n|A?XlH-x zvf*()Hhg5AYZ3mKhnnr(g^rxjYPF3GKbQ2hI}h5T;|%Om16mGEh;p)mVdMRk`qm4z z^4@3(4|HxzM@GSmEcvSr_dB0$20nf~@`if(AnCAX^=YTd7QBgrZyQ57EO7@)Dyx2q&E(p!~(op|+8r~E-D-YYuytRx;Oui3WR z9+a%`!r`mam?M2&FRjp$$i4x1EmbvsPuw|FiROF4 z?z{7GnMFu41A1MfR)@wui22S_Dx75m?DpI%4upM#)r7ct1o2(Fs}zYKL270Qt)qgM zv=>&lXG@W;FdW*_XxxsdwwQaiV)T6%P=`A zKa1ASZv2`s@1ghn{jH$~S0GQL=^5+4CeAyE8r>;!&N(Mw)EP}ovuF3#XG>8L-klbowcj3b!CIvGeNq$~qy< zCHtyklg|?mbPXmJr^fbT7I=ka2r0|isUf3^bo(a_7P4>7&|=gL<1$$uxwBJdQFgnf zQ&b#b-1iC9vkHAYYM+f~ty~wvSj|u5TD5#$Gv906^%iju#EjCCH%PZQu_oOeS`Z|;(-$U$1`}a z^Ok!m`&ITJT!?5Otc~~A`PyA4;q zn;!Os9-Fj%zu3LvVD32C_^a=E*T=BCUsw>aj)!C89Z5IT}%>rgC%@RZ04bArQJMxYU<-*!ggr6nbMKWi`{FEOMR=$ z66?)&CQq6*p7yK_o_3>sPJfv}2<3b+<7&p7?u9HK*x>MlMD3s@E#$=1-Zv)8clDcr zS6^C<73!1x#Ex?4pdKq&#BOumv41Dld9&irikY!2Jz=^9u$hNAcmJVSiv ztx5nFCxNGd@FZ!F&QfU#&q~gb*I2R?pS*$Xc+UQMobZ5}mDj3_K-8-iU?#&`!aN0V zN?Nw+rjmK^&6q}HZ}BO6x8DeIuh!G(oi&2+z*V(j)^SnGEd}oQ?`+vW%Bt)+~TFT^Y7j|xt_!j1j;x#QMbuN!E)aBj>0QF1UgS0$>^C|R z^|?}kD6`nNh!r-(aVpZXPpm8yfAz4+Ca zN4Gx5i*b%0-o0<|LS=J9j6F8t0_N!*`UHU*$g^|fm8Gc=i=Ui)UxR53v<*(Pt>Wwk z1N&DXsw=}oCMa)q_Pl`Eh!1l!7opzd85y)G=a|87Hu)Du5 zYf$bAj}?7w)DSVa7jP#yJSuI)!Zg2x>ncI=ilbJRU5cN9ys1c$BYTmfe37G6QL$dT zKb(&^EoP#Tiz}u6PCxkO+vYNVmCgtn=5|d%9og6yc;?NQ^N*=EJ%p@a;a|)?2ZzN8 zmG|vvZ;W^gDVp}1Tv`<3@MUA@VP`PI*I8xw^ z!P#dmjH!HYUt@+A>&$*Xi+(nL@YAl>&LrHO>(L3LgwLyHpOA+)-@X{OPr$!Dqn-Sf z_SYWl`Lpz{;Pk}r?l;{3xTfmoal#(t>OIKSd5~-LV2FKWkR!(H;FYF1f!rH#eEeK9 z|A))38DE9XfMbqdd77dlam1@zapOU>VIGMl`Opgiu@ym~;?w0D&+;t@w@WY#fX}t2OLa4Vd}s%NWKkS9lBAXDVbUDeK3EKJB0tarD@* zVIz2;Q$|E|zDsX?{9P>SIctKyt}~s}P?Z3`F%-i?oz>J5&ry>x$;6s;_+c;fNuxgT zaG7r({^Uur^CnsZ%WW6j>GQ*!Z|?^)(bBs@z>Hs2`hGQVs4^_YTT zqx5rcFb0Hb?lRd6+m4m}>io}tmoy5(f6!#)i{{ce8gQfq6@)6Gi$6*cJ%q2)*|RK> z>B}!8PgQ=IX@3y*lFp~#)q<|HnB-FqWV@QeDYi3?^bpYwO^J^AOM}=r=#6E6lK%y1 zA-3E1&E*`%_Fno)&kX0@IazvOGG-0YwmqreeQr_rj9anqNq@VW$6Qx4*cIUqPg_%` z#~M@$jozZ=@p)U0=ODwrvyD{>u^%x6_q!Gg;4d!Vw7&Dp`;Cs!Lk&V@tsZ}C$)fjl%jsPFv=+k}?8PN1X|#9sHyG~o zL~~eG(NF#%&jXbPW7}GXp&5e7(DuEddzZtF1_!ggE9zC-wTJt>b#~|RKg$2(5q;A+ zIJE51dsYEaDRR^HG*xuG{Fig)A1|lN%&+JioeeB<)4wKS!+~h#)*8!2fk&{mxQ7qoRn6~w zWR{$zig+aStjRI6KJ|oojmWKv=@VD^b}WZ`efjy79mMZTv!SyG^-XH%b3Y%x&%gF; zMJEo^=BtUyev_sAJf;q+z6RzZ~xh zWt&2YuD>e1oE)2Hq-QH0*sFQN^*Y0?H}a;|VO^O-o2C&<{cuEembF7l{4GvCB0TLI zaoRI6Fm3jnwAEW^fNOaWky|bJ%#=%gK=ST_!7>*W!akQJEyNlGyI}^ zAk<*c_?EA{aN=`Gvkb{}v-ow$YFv@1C*(cmswcX_{M5{n%lCzC`_B4A9WqIUo+(&12A#yytV2R1u zZnNBLj$O`eQrYP7kzT#XKj&%;7xZqKI96VLSV0cBKA`jK%N)yC#$7G<$D=$Z>Sv92 zJXtdY2%_P+M9ERQ{tHl(>1ao}7uBhLSIT#)^zQlm=(j=z6wUZKB%DH&-dQNWBXz6B z5ZY-ScE9Ga?SRCsJ~ah?;^)VQm!1xe1j(7d{!pnpYcfDp=bv!FS5vXv?ci>(ONgzE zaLtfdjq+zx>E7G?bBh{kFMroYg@-jnWVlE~%{u?sYWim9ReJBqGrZ5Ub3%hJ4S&;6 zR1AJFs{SY$J9*ZaR+;K?yWvGOp*NgLrO;QeZ)=)9^^GtZJT+Lqt;rZvxJ#ASE z-jvL(yuewtx>?=Uqk)RTMCtu1>K4z8rVQPWGB&>(S_i$@V=9#>I>LxdNb+zEg5P!NNZqh0v@T5Zq!eE3-%fv@x`{~|uy78FYq+yS$vllYfnjXiy z1Xm8jFQl{6ryD*V_EC*?4Jp!&>{UmL6?84}na0@rW=4{E3I zw#2o?w)E!e(Up8LgW^xWFQ~9hiXELRq_xS`xzffxDYhp2Vr8QK*m3ckclZ@H&fkvF zo(1rfq;%GgN$HoOoO=-)LuYE`4L&L^dveI>I{+2lFbvuR}}#?k|utD&Dx-f^v?x7!x|;*63EupVz@eMs)#!OYCJ5*f}@3S7_(vEus=bP~Qs z=NAmC9=ZH5fn8)18F!?WN00 z9!1|y*W7t_+50nB?kYU3pVAm7G0STDoI7E~&LNSF>W|X1q!r|?KT3*8oR(`!inWQ7 zw9e{-TQ!^0TJ$_I*7r^Cn>~9S&)xWN-bz-kqJhN7)$ddM8|!anE^mD`Hi+$+QLM#< zAD`na`wo09{dY=AB+Iu;1{JB=Yy)8xdKndZYD}No*vcYoKPjYD{!-2=vL8vS8c3^h z6v`?x8A&T2NGr#?WjWO6MNHnGvvf)scy&e8{VJhVD@Vav422gJD2gn1Hs-s6cfZJv z^=vFNGHg~~O3ggKp=Q@x$?Y>gQEPAC8lPBCzNwq<7+_L&)14-xaw>1|*;Dxj+d;GX z7gdgsP5IJb33F`L)#lTNJT|hEvjw!=<(StwA8)uCZ*Vufu4x*ond^6~DaO~ydHS<- zz=z6R4pi?&mO7SsyN5d_Su1V|4!RfCh1b~=sYlh3)nC==H)Pp!cGybwtb2FsO-f^z z>srk1%Wi3BWIpvM#Vp#Chch{+%NmJrHd34N1iX^bVAV?)Jx?s@<-lKy-#l#>A=;WC zU#~xMLsDO!quzYH$;9GI0vRv#Ro9}Y*MsNk$@=u7H!<~iqsq@R&&<~4SHuLzouQ6XLyRYry!l=L5$n7VBnp81ng(hT&sY_E9?>6ex$ zOVr=^{;u0zts;}7PvPq1UA;w_bPjy@vXuqNU^FsZR;5B*Cbj%Ew#bdMyQF7wFsC-7 zq(@+kt3JR@?P8d6&T~y@UK3MFv{?$>!?NxHxViMS$It0e7vAT#<9pMiAHOUnDvG-~ zeK#GvK2+6ge=W*fsYJGf=}N1OZ)rv?gV(ydg@Q#M%7B|MInwuXLpkk&xeM*9$qeYH zrNK9UjNd8}*gTAVR^KKJ(>6EdE{j?2YJPa|Fz_Kmuv4wE%C*Vbnkn8yZzoL~GpX@h zzxG5ME87^iYx`%aByxN+&Ucsi=i4-!<;E0r9 z_u>n!fRY;3P`m~U?f!`ZpO@cHI2Z?Aeq1cc!jQj>Lw9n7N{1uebF%(q49~E? zP!fU^B_OQUo!m@=@Cjjv5K)e4cd{{1Bxmv_b~TYN2d=llrj(|^=Ul&+s*QZA(aydRVJ^wl1bX z3$>aSV{Z|{6`b$SW}=_)$;&p~Ft|!XefVd6%6_nnk!mr0u0(5t-L*fz$07u97s=1# zbW$S1znbzd!1{u2AAbDFDlT4p?rH70uZlM+I$vCw)9k3lam~V9e|~)(-J4Z6`%%XI zQhBwi{9^iK%4oXq(OBa$>WoC+t0fU_d}_Cw)f?2An(^}a^rA~ry0NNV0tI2S^3KfF zhnM|Frn+jYH6zyMn@8&7tgPe8u@B2NIK+QGj_i~+(unhI**TF%@@3+jX=xu_M6to&O{F{gmHb z91lO6J4Vf|b!n9aN*v%dWz%L$?^rEfRoC7(^1D^;Ms3>Gw4GU<`tW7ISw$+3q5!V? z&nCh;m(>!Akrn*wn;Lp?EEPX0Jw`X6y)xbE51%rxZ9lW#d#&PR^-k5P41AYmkL*$A zhT4sYYq_6jFxesDDegI&EGDvFDzrHUoqPgbOYd))m_|drm*aC?ZWi4h%KxPaQ?2{AlX!kxMRRp*H`^;l0O64xbVW^*I|Z?&HUj zs}dZ)RMaW52Gp_0-c|EKopW}(X?k_@L1vMEX5lBf6PgICHwToTb`h(#Lqsp=|8j6z!o0iP7RatBm``16J2k_Q{m7g_nc@CW51;?+b2#To-dq2E;h0K z8c#AE|C7#ab`8F3@o4zmj3~}0A znV#St&(ojYH<>m&k|SLn9Z;*tfA9BoMc}5YuG&H=@h+}s`%%kplH40tuS+Ktij1Gm zRusH>A*v=jaCzf&=Fa;M0oq@_;CjBR<1xj;f0UGSXpg@wohhwXAGTR(Q{1U>EJXf{bQ^5 zYp+kSf_vIFucA<~6*o1l+YMDc@2NFj9J@#|j=f02$4v_v&E>E0d{j|;pTJtzy1U!X zHDUb!Q1#V8aXdlWhabTXcXuZtxE$`mgS!SuaCdiicZUGM9fAZ69tZ??cRApI?|$|E z@x5==?Nr^~Zuj&vPtRWMOn1Yv*RK84(?NM381fC3yOPCmkA&!q$0qJR^S!uWv(!g_ zYXWs~;v3d*&%;^}!oN)3u<>6D#N%#ay!ykD$o3qK-+=TzRQ};#D-`K;)tCei&OWVM z9$e|s9lhpuOk%z7A(n5n_YnJJm@NJ8*{ZWfKvOT+YeLG4DXjm)`^fl{QVq$ta%roQ zqEYnwg~Sq(q^c8(v&npU8}zd*JlO;J$$RMeN%IhUm-W8Tt|vHh-__r)cAW+^*Zb3O zMyhc?h7rTL81VVI*o>E8GG^99{y}0y+BRjJH(B}vJFg>_0?O6FxcKQuilHB$>z!Ti zrU@QROAW<%bsisk>5IKMl4g@DRA=dEjd;htcm&26NnkM%Mi`%TiIYl)iU*r9?;iA^ zrbn8cWVH-$9Vlh%Wg3T2-ta$OHu4$d2k|1v!Rcl^3GzRQzz(V>Ct)Lo_H9UQvC=C4aoK(RS}B z)W;7Mu^STVv3H_WpokvfFkCWYS^HE7lacdZ5>I-I3n(Gf5t6i=gc%Hb7xny$FLSbR zuiE75(FC6B=Xeg)ib(GLQht6~F9nJJ3LoFy&3>%En40nR^Kd=);7!Y8I|@6b>Sh{m z108y*<$j%tDfZn1jO_wXrPY>8Hk_IlXk`}1*W9GBFQJ25&d)5$L=!Ag>KlA>=qgk&=9>4rO^*1-!_$_|Q z7v6cF=W>xB#Qs^8f>!XrB5xdAE3rhUJ{eUZI%@6OIIjDlmQad}%bv>T89aR@(+YD39ulAYO zEvjF?>(q|x+kJ2SYeBx$5gO(l4*UN*qtD7&zG0`43YxidFVB3%C^HQAbn95*F#q0J zoLYY&+lAiA!c?RoU%s1}W*x7aBC_pvn_Pr!JKV05xKU|WLEWRfmm{QMt_q`ZPR>&4 zc8jvEO$NGjTRDqeyv66Y2K-ZJ0m)fa|8S&+qO5fUFcCe%7ms9RSlWWq#HP-Bi zEvqgW;-0x%okc2LybVhHktKimOZQBd+eotpi7hgN!W`3=toi`+blb|$wh_+V9~WYy z$#Ke)Krt^7D`sb^iVN77p*5s`7(T&;lknJhFP1Y=Z>lO1%uj#DY|(owDo;Xntt=a3 zP~BFIDQ1gCP&&CY>LJP4C=u${6z0Hau=qVAzesK(xUYT;*V;~gZ(Qr^716zyAb$7y z&hXBNf^#hTYEHj!haQj58L57w;7syoUeSifd6CIYvQpFH`A`neeb}{CoNREn?#@1y z!(#iu?QarnD~H7yj?*`fAK}C{rZy@}o4-jI9zqxR?3$YxOH+jp&huvkJbuW4?l%Wf zY5CW-$1qtMY}HJ@O9c%RXhj7fX%*8opf3usvnym#*Oapa?X|@!%;|0=iW(JZdBd-2 zDWv85=GeaAh?&gZP3X+SpTOhk3|`HpbIFS&FHtIhPvwi*f-p~Hb!MX2GA)g~kJwmT zOEcnMOJ51fb%+k@=%571+9j%FMcC1LN5i!LteDHR(`{Uw?y4#G{y4gRCQMM$OPE|P zv{Zi4HJ&Pj(cow zyJGEH;Ovgq^-ql+wDwQ>wBpW6t+xZXb5*xkDZE`sLq_6K^ zhR0cL;Md=!u$F_r8$drs<9}xNC()Holk{aJ-LBR*buQC4ZG;`Syp*Gx##Q9dt7Ape zFP~g9WYyy|GX{2T!#(QG%ea=aHJA19%asY>_v^muvRcpdG+KV=4cU$-TqBSFvx45@ zjBMDUSu;t+-jBnB)*$tUlsQV{pD}>P%*!^FzqHQXi1M%MKo{eqLYxN0cbTf4RM|zQ zimZ92KlxtBNQ+EqhR)gCHEQR(t_1Xx%qrH^>A7BilDW(DoC!Wp#QRrl{*J{iSbm>; z@e^12Xj^>QVvoNHEISf4FcO}#LLN}+bUjvwus#btKZ|8rH|`k((io$#Q537g+Q${p zcYCZctzSy8RZ`SR1#EnYF>v`VLDOTMO`aoT104RiCChDE<>lhXmK+V2)KOXl9=+YT zX?k-ae0dTLe`J*-U-9YSQ(i_RxcQmVI$~S)gMv{{0F+B8y&U;I|?Q>Im~U1`X@0txHXm_n_wTEm{s zp}b;dp1bCr9!l^94Th46LVMd zsK+}M3(qc&+A{n~l$;k(C*8Lk$e@RooOJ_l!@c5~ zoY-ymK~Ae?6>{34O&T{vQ` zAbk0x#-Oi}`8;59Z`VtzZ7~*l_X}sS<{;I&>W~rdk8ZVP?!D4B2F~CpX&no1rA8y% zPM#=llk+x{Eb*^AwY7+5=A>q3qkWW$Ni59OC>f3_X%6vUpygft0frdn98GmzV#QW| z(;&I!(if!S)k*P5?pO<=ny+=tV`HJyhu4h-?Iz=&r%s9;)A!rht@hgzCl{+-f{H`n=eQV&-<8&*axNiH zPl$XiXY8cS)kR*9M9-Bg#cy6zUfH8MYfNE~rk0FG$;GpuxvHgFcEYbGie!Wmca{XF zQgq59{V`acEeRI)$?V5OKOfRjc4YjO`xBDR6<|`E0_7O9#*jGnU>>Tt|7^sV)rKm` z*NkC(Lq*0ZsERRw@huW_VEH@IK~~=HKd*b4mK2x=-`{Dw*nf`rSoCy|DB3I}p!{e$ zzVWf1OX+U*B=AA7gAcs)M>PdGW)&hqdu`VX|JT z>PlV1Ev>plU|7t{3EkgNn}21#4WiMpj{E6#cj>UYmc<-$Z^+gd@-yiBTkcQ2Hv!t^ zI{X)|3Kp#=t^=v>Pqw!Rm5q%~Ow$}E%Ro%@vN#hI=zA6v`L0)@96SL0B?jRm<{>77gysPrl8b@)n7nW~VWH1$hJ)tSB zV0%f|!4tdp8xrj;c>2JlB4I|kaZ=D4notn-_>_AI_CH^(@*?ow4DhgZ|CN_L9)H>_ z#ItMPB1F~a85R_dXMXW@O|-HHTVW3S(;D!Oo)98a z*(E#rv}-KTL^_nFG_s#rogm!G}rq156J_I%nqUvYoWt^E)03W<&A8V809N7aisf6z*v;xh?yWpxSyO>}i4N~ew34e@b(He%2b=7e^k}tf z5a)#EPRDtbX==XY5V7_y^Hq8UN_dr3+n1!elbT^vKEx01b+4x~Zi;&%O^U+_Z2H+J z;js6}dhgqp88gKB`{WCBvMDJTurByMo!~3ND?2grXc%P`|!KH`($Q{IC{IXsl&x; z_hzc(GQkbmcU?em`Cx6u#CM$;`~l_OD}yk0S_sDL>8^GvzjZ(C(&;W9@++^08qBBe zdC$?;1V`@?#2T9l54J&FSMUEWzM=?Ie(-vjn7Z=f`$$ya(Wo2M=LmAZ2F@KAeoGX+ zI6sS=7V=OPz3{w4jei4Hz+S>N<`P$U{QOQNa?O0xw0S+1>*Lky-ORe6<(7AH&GCg& zT>+_#AWV1EPmcUu8CI3`${qEeQXCp2@ux%*>y-%2Y0SsOWKpSHFGh7i+%thaDVFvN z^6@`mmi6@2(-c-c>FB%{EkoPZSPA({I8J|XSFID?*xKmH4ehLQob($*xjPnfiyV6c z3CEcUbI-gvY&?EeJ^QUbv4gMcsIu%31UOJH?8cp<%p6pA+Cv|1JStJV+DpGDtW?AaC@5+3b0j%HHD^oxrs-uqdX;-HdU2p`*NyhKogn*ZTgW*q z-Sur?J}`&ePdVjspXKe)a?B7jmodGGL_5=7kX1S&yZt2R_JCFKbwTtvDZ`A=?H%M! zZELVQca$MO+r~VIx~BF{aE)kMeJtu5TCPQhOv3gpA290Xl-{IdMc<@QsO=)z>I%s+ z<&`I>X1_WoDOpJ1*ccScc3Y}7A+Qz{)zX6(teH?1*eRc~z><$4HjkG{OqAtGFWh`K zrT6Ic(*#(a(aGn$vmt51wxP*p>2elwI20AG`YF_>{Pgs3=T75#%0FaO<#4VnVmx!_ zCV!;6FV!?ZmP%G^E$TBIDd)SIb64Cli5Y5D3%{$~|44!4EFeEwqpY>pC~>%7Jzf&n zTs7_*aGcyM*dQCkfFwDoYH!{W?V$S2yrq$k$;%wk8!xl`j%j8{Edf4MpfW?vTaQcx zgWuDYN9>!=%yOWsh*#Y}eZPW5iJk?m(Q27$7eV2lKWpeI)xSj_NjeEkPL#t)=L~*# za%)T5?&}5KSJm0UrJ<;sFTqO-8-61g#gq_k&-iukV0tIt&X(De!e-!j8A_N!e?jmw zDpe^hCI~s8_lxQ^k~xF57$JfJreM?@z2b>kpg^(ehln|Rq=a6SM4Lo43t5DTB!_g< z4f>k}`Y>i=gjm8aK5l*(a%b+O7e79cZ!g0-Y{qv@W-u^eIrJz-|F@oIz`)P z8tzRa`Ng?P#EZZ(dw2OB`>bXIF)niZM&(_PXx|*FQlr4T@YypNY1qSeLSwUfT;P{$bJ@67O=gglvy0$&9i_RGj6jndC~3 zMs!?JqTNuk9dzt5SJB$0))3knMKL~If;yiI* zTcYL~mlQ8k*mFI+WkJ;}tHOl=B^`4kie26FSv03o8Osx~5j~m?Vcx|pJ@)f!l?n@A zHR-F#DX?3~;z;!dz5ax!&uKQJoB?im{0hYUK1G;hME`ZU>@{4Fv#~>t?Qn~&=SY9F z1)uEyj{Y_lW`Q0XLnPh#t@Q6V3w@ISkB)hx_~JbG%ACJ8T{6ka&lYrNAA1GOcH}C$ zr;p-xs&)*m3g|vY3Q+{bN&>$Lr-H1$N}|(a(o1wU-XS2guKP?6I1$Eawn4@CI`%Hy z%h(;_aP-$d1UEwqXiL-i;#FJ8(&NF1^D6)emW8w2Hpf*j+J!S=a{9I=f#g>1=+>JX zsky0XoHF-CMwWkG`G2QUJ%&f!bB^sRs>=KZ?%FuctC+k<$z`)p-2R{~z+Qrb4~ zz0pY%$KzI(hTkV9SC%Hubhp(g6_I17N}$k6zZ;Im6ErSY{fDotE8&AOcJ8nFuVY(T z&@_o%__NiYu3ZJ)pnn~~&O#5VRzid?5HE=XV&Flw=M{rZ&SG9$@W_mjrBu;CZ5$Cj-QRnEm zKZpyA#Zn(L<`JzPkL|PWp@Je@;pvR>ZCT+(7YEzW&#~*ht zDSv;Wm`G>G{r0O^?oG>h-Jv}NmMc9z#_+Zo5??paNIBeTzvZ_RB-wSTI1N@-;!nR9 zBnkia1wSCRE#>d=vRdM{Ms~4y$^dnRXGpo{r<4Ka3JCkBt9Axy=;D>DX3#IUe`hos zyjQ9EXu!5~ISH;c4;pf!R>9yt^p(`1-|=610sERp-FO{uOk&r6QqGwF5R++qivik+ zxhkB7$2vy3HbfdoOVLU3w_pdxShrISGW7uRez^KB7@PX1t5QmKKWnjC z=UOE3$XUo9Fm2{ZPR6`bxQerOOCgasy|EIL;+KJmNc!2ruiq#q@^id>B*++a**qg_X?%f-;Xnzoz*5{}H=Ik%+)ZrnaD{Uq1WaIDT zWi-YXpECD}rTgI5kq7?1kNjr1#03Vu<@}8Th{wixDl~dP0%wmSSIt0=-_+fvx2?T4 zRq?=z!8xTPi?%(Bw&S0AM^=fPS8GhCfe-vG;M&WLTyqKY_L0a$xJZLgTn?ECgNw8f z%|xKz4#0t&iPS)_X$V+?uSNU|a1`jb3bX?_ukqVsiJ|n*190JfMIh(`lo2w~9C`XX z0A7h-CIA$0?a8GGRoWj@zN`Vmo0yr|XZL9bFHUK-gDWrA+6O4Z3z#5p~)xWsoF?;RR z{F|hK9X)~efchhhJwWU+!XDuC0N@R`iq$}|8Qq&mxtWVb-Iuq+V2Jn#_v0B9M)p8+!Qux^p~uhkDZ0~Nzj z2W$hA27@R7dfXfJQz&Y@nHUc?Zx)Cez6QJgo1o$@e zDK^9xz}b#@4Y!KHMYvhABnbxiiVz1V~iMj%T>e%vYSu^+{9eDZ`f5X4ce zOQL>-z#98ND4^{IW0R|8-Mv6?B!MCD z;0}|2hY8c0Mg|k_O0kPFsuBSOoLp0b`=i1VH{dSv;2z=X5HisnZQtMW_4|2<2OtPT z&<-5}PZSIL;L@iN{o#nT-a+RGk5RcOHghcdIRUH_u)A~4VM@>N6DdvpvcZ&4m~i)7 zaqjkko~(IyQFH`ghUB_p?V|kUCA>!PHG?VUHx-2q$Acmaok#!C0PNePdMa@L5-Utr z3znFF;L>N9QL8ktZqoL92c}_3_y_Xzg+pHgDXWXt02yHPM*yY{@*}|B3zjlz^PYsr z8bIld1Hm(ayxY8ZB|Gp$hLD;-oBB5ZDRx19aLS&rvB`ZB-bLWQc;FJ1gQ9VXBKNm@ zVP7M#d7)k-blcMJGD5&GEvp5vevrc)C;gT98bSD)Wpp@*YzacLK)A_TF9}Ad)j@bc zzl`q_10!@Fe>$`dbm&8ZotXJIqpWJFxCoaAnDQb#M0EV?{e`ptGihND0Cych5Kd_Y z=nBtogFAz2GH5jl20~8!V79Gj!I}UO7%<%!Fo-(IVdM3Jz00_j0K)+RBQb!h^bPSE zL1_gEHZ02+XUIBN%*~{m+afbC(in}RIF4-u8WAGA`J&#!x9_lKY6H&Sghr79D=-5M z0HfrHUO=w%0xA_b;eJZEDH}NN4wG-5z+D!x5mOim_+j(QNj!WHz+11V7r;n?B8YJM zK;G>EY$i3K>DK^w1RY>Q9GbV*!XNlL`4q zzQL7cBYz3Q)w)sgTLQren``gg(|ZB=p93#Seoo8!a$@9rrUnpS`td+MBOmdZd$apf z3bVpp%xtPq^%fvgek<9-n%guC48XmVFF8^r_*Nc5w!sEG30o$zI#3Y)u zRRbu1T=1F-3?FIfk8x`o9uD|jK8m{~d*y#HH2@--F0Ds8|@NaK@Gd*i){oR+( z(O++OQ-7e-?_hIt5X07gVLyKq(BIbj_#lbDsRH+kgW->~*@gBB;R)H|@&BV4`b4Qk=jUN*uphQFFdiG;P zx%&gSenPn32;=~g%Xv5zO}sL)gpm75K)1#q=OCQ%_#Eq{eZTf$DmTWZ%SX>SOwP>% zxL1bY0vBILdFS0&VlkV6E|kx_^z=a54PBmsTfj>?~-R@Z0ccB2n@ zTd_Fyq>Xgj+~lm9v10gM5?XEl>nQ6A_5dNQ4Mv8fkCk;I3<8ra?mi^_$R#mX ziE}Fb#jjz$tzm|vVV1o5zUCWRmncyOotX_}(2xQ(W&C@v?_NVV=X?=&)dre{1M=d1 zAFb{?^K{}r4nn0}$2c#}a!~BZ0Iqe4=G=#Im zM0mo8fD}djavqbsua=S9dv2rL=1brzpT>zSct4jZT0T-!94&pX$$V9w6-OYN)78)(l z8%Gc%o6g*;@!j@C-%94|zp?xco?mA|@K*?RJn0j&?;EVT7j=v<7<~fGk&4DEUqHru zQ#wZJV$jW#B0s@v$*rHE1y~q6)hE#TVp4_Kv$=SF zCz3c<;>aMi|8Gz6FWiw2V$c%Y*gZwmAUHR?6*+Qk27h$ogEMolnR; zc@X4R0c!BH+-p#HYPP9C-xg>iwP>1BO4)~MU8u@W;4I8F;zQhHDe|@mAMwKtNo1dG z;}8Ev((MFss2%bdKipNCZtx{(h!YV?^z{`0#`Rv4Xe#s%h2{i9)9uSTQ{L3;{xMXi zA0z97AaQMoGXYeheEOhXp3T8Ei~46Ra{+hwZBhrG^b)6rMB7|jgST1GTmQFTH%bIU zjLO`L4n3eg%581ZOB;-3^FB#X{>RM(9#8<+2>+XXWI>8G^c5F$kUNx2UzQrQRTmMmT%}1$6TV!U~5> zoX=&@N+raE3~T)1L^MU1Ex5aFw|w=^<$t9%cFm$HZ7K2ap*Hjl`|)aoaD_FE6PmFy zE4w7#nJMt(%CL7D0>OO%ki(DC&aGb>kv+@^&y>X7F6#O9xUud2^Y8m1oR56v)`t_S zgCLrTbyY*y|Fl5$_Ab)+5zssxr~>n?LA0`m=i!SG2ugMA$fFHT->8oA?Fq)qZ6TGg zY4d9kWaC8-M8OwXfPCd7YVKz)I#eG4vBtQr4}_dlBbjE3zbuIh_emv7`tQMnq$u7_ zFhZS3whx5afPLuwlyrGDShqJH5>d`-n}ieXmrH^hM#Roj#h{@hh^8q)t9tNP5w?h1 z4MD5ANLLP`_QDt6A|P{&w|Crg@i5xojNLB{J7LEEdrrNA3X^P))N$n(G*S56^7%MmiArPtZIEVwNw$ssfAR+q z49ULz0{9aU=6Okq*8ni==0VT}X*_u02$LF`l|$URjA78iW3{gA`ftluso^ zbUAMosj~h>7>g_rGMf{7*Wn1U1`l!ajjBMe*Py*WdJn<_2HJ=&6!H4OcGRyfd7cs@ zERpqp-|eAn5+>W@m%8{;0Tr2g?BeFm!d-8`p>Emij(xe|0rpR$I#?G=lq)TgpvZcw z^ZQJxJ^wz)3(9uJ#pRoS5)vfC0}_zH1P4V6O!!<%2zw^70*o#CDLHQ|HX7n~XRRVP zuA-=*8Ws8fJrt@aNfnEk9pI|qaHt6k_ZB;FTHh-JRbb54ie3{8(g;)J+49Q1 z49UK9__(F_31a!llRdmd_euTT^oZlVQug&TZ;=wZxy<6<6!> z2OAJf2}9LX=I$d*J$cfndvCa3FG3nxq~fNB9r405s87qiHf(|x`yL5DLUF^Feg1Ks z4qYRD@?{Ng!<)bC4U`UQvNRhuaEduPCQxI^7){n$)2fjxr39V9>FLfoU?{!hQ&Pd0E^^AhF%@C2 zaMt6&UC|Jwb66bWA>Zeeg5R|0jb0H$9!p~?UkBuHc&C4OC5xxWzl7Nh2xLR;6#sCk z&6~3Ik#8AhL%&fELX_8F4zj`KV&cTbrp3EdThJj$B218?3#+8IPjDD4(sAUU%I ztSwcWQFXb;D$lR<*--FQYt<$i!tKWNYkoiUYj54KS5s^F{?eRc&KvQk*FTv7En`s5 zFV{<^uY$1G;`JDBYAFRrad!+ixZsndXsD9I7n3{L0R?YjiN!jM><2!P_dx33ln$5v z$b5F!+&!Gf^4S5FLXY5q4V)LQH`~cRy_AhA)QerHW$}kMSn$G1^LQug8^cR_IaGqN z=lbFe@PD&mj%;Tuy3E4?5^Ycb`2Rl}rlZaOPJ{_d%_E$qatnT4J=sp)Oa3m+L8DMs zTzrOOSyid3J}dEMwwR7RgNc1Soq%4t3`4p`POUkTS0TFDW<$Gd12UO)(Ux^__xAAA z4L;~_7!kgidpf4=%Z72`L6=r99~4i7FGIV=+~HZWB;_zPm$gE_(DWHV4oSr4}=t9a@19@ ze1Tc;bQx2skPB{B$!F(Oy#9+g>As4~w=3UMurpX%v!6-;T4?)*UpeFyZ-D0w$e$6; z8?kxUGS_h{TKqn#!#_Ui+8G1>L2fE@r+FydJ??%MeoC4tF*eQdm%1HX#p%eB1igHU zVp@l1e&$6*v+^Ws>y6zvj3BKOQk#|WC{2V8vwWsJ&Mmgx$J}P3m!WvZ`n^ekno?)x zt1MsB?vNWEzPsKaM7&Lc^1bFo*YiExG%)cc5%uUdpzNAmHQ6 z3xAPXqK;hxK}J3mlk!2RgliuY1`_rGBSB#WwtOtQ2!F?}+tr%aENW%;_ZDpDYaIL_ zxq&?efhwJ`(uj8t)$QD1bsERj&F?f~f6}i*qiDdi;4-{}#nx6%d%vG%a4G{fl~OUT zi?Z^&UKqLQLpxPAshQIqT}WXp@6ytw(q+iNs-QGBijjl^vXbFkO9IBMaR01n`Os*y zK7M@7W~rfv`2>7maEUHL!yWzJZLrZV)DQ-R(5RSkoVQz7-6^jkQpgl;&? zlz1+F=4X2X`D6vS(H%bz2}2FE!9_FTD|*sJnZ{&$Ljv+0o<-ploq78*ipuhINp#T| zVc}{e@u*Mhzdy7|x$WB6Ucm}-b)v$fsx139&`Bw;G-y-7_B@ErY5tis9Gc(0$|aMk zEFp@4gyW!K8|w|L$T}#6jSN4dqDx2hJ8hX|wP7>>w0ANi`b%laNmk{Bbg-SrXpW@bTQ9NU1YUk&a^F z7p8qFc{1zT3^MxKahYSi9?a!!o(Y?$n zbTFKrSvaz-Ir-V~9rL1Fq;P_=q%3TwcUH>KymBjZafw;taE5@n&7Y-`Bz$fQtG`U@ zMN6x(9Gwm2;)xVh)}%6$Oto-J7*42D`@6Z8ipfuoxy9qjLvqE;s)||&Me1doHTz28!i5M@zWZvdQ z%`>A{@1e^VIwn)6E~p>Nicv!JEy`y~uCS!WIsBU?<9v@pZLF_0epJ$EhKR-+omSGm z1&?|xark;I!Mwq|l51^M)q%&;6!#@3QlmkN+;HXdzzN&OHPGi_T-uw3mHXni(;Bg$ zc`-wPpNyYjbT#umZq=#uUS6V9);r#7p2tJcrd=|8NV;%Qsff2aZ5=CP^X8+I*{t-E zD+dWNjq+2$>|IK9n6P3{C5wN^!gVxN(7ZEY965H*clyP|9lCJ$>)aw$y2HwmEAr-T zeaau1DK3d}NCduwPK|Q%b44|oDUoqcz*t0yh0Akyz1;5SR@bxF0Xh2;U>3t?fsg4t z0!8G6R=-~|&yZLzf59pv)&}?h2mXS+Ep~Oyh zQaF9UiyGQz$?ZoH(Pm~`nAg}`#prlLruYzW?i(a`S~{~LFfSX`sZh$zGb}-EOM*dr zbC+vEm0(Si{4P$F2Vy32o86c?dvPUY)~hp+0qiXHa5&`1=M?*H#<++6ZFp5!S+F?3 z_kI`=-}L=-KIO#kHVI_Z-xbQanlUjSG8D?jttdtthtNFDEU?rRNs5=KuDCuXx5tDY z3G;mFB_ByEA;dokwJl@ZlTxm9dy@MMQIH4m{X0|yP0_#8XLa% zUzKJ|5W}uuizBonEQx4ZvL(OzPCN7hKB134NWhnI$%d!4J})eu^>)F(O3(D{<YWV`q7i(fZ}DNTbQ`V^GA2ys9No06eKr77^{beJ+H-z zu+??184;fRhYphh!Ai#^`A2%iyg42osYB$^?jq5r0{NtIX;K!**p=QtiMqcrxQ1;T zl{DWNIivBGo#F@NyzonRIQPg-lRh^pUTqGUv!(6f@uQ$7H0^AEmVV+<vqZy*5qoG6SL$Y|6tdHGFeJUj<`!}S4&_6%Og2AAzGg%iX3yP;9 zljM(2j7Y>1tVm)LW0K;>T1BE$1aVNOQl}zpJYBGK_J*qlsYW@X6_FGnZ;48H5t-8WG9z=5gU3N^Z{qnM+VqYM!em@;Hnr~O`R4$TxN7;69z(HOGa9B|A9uhXU>4x157T%sDHsg=}807W#QnsQ6POW+I; zKzhDYc~E6VH{eoZw4>8K&{|NF!E=y~k%bzB3WxfJrT{Cjr>O8~@Td*M+JSu<(YuzR ztiUA@3&@^IlX_LEJAIHP9qzGN?_gkuR8D!g%wE+9qs<@+W~QXuu?t`trt9 zPT`fA@b}cJBO=PC49yXAdfITNg>`@k(IfN8NE+kkIM zD#0?q5&AK74d{b?PfaE^D#0J=2&5*ZCfwqe5FJ4M54$Z2Bsl^aJX!-l9pWxM-2|wP zMw3dDM)T*iJ&hO27KJY?(pM6OnoKAoaC*~vI>{Gqi8IOfKitS2DHEZQm#AB;16bw1 zb7l|HwJ_N;y@0-9Z@I$CaN?1Lv4z2f(S?{Xwit9Ruev5D#df=dPI;i)pbL2Q*i!7^ z_rc5(e26ZHXrcLq^Jt~?a;%9jNo0{GAto_gKvS*(P-qblCv>Y&XuIHv|7Q!DoHm{8 z;2ja&2u-Z)CE^t6n7rc|nlnlAJT}!re+x1mJP00pAD%FH7d|u$SOhc$Ze2F*$<+m) z$kA!jN(~;g$o-=ED)kr906tkB$PD}+-}@WZ@LXW6D7)md<$|3247&f1VTi9>$+DYI zOru}vTs8%<#!`URpm(5m*wLV9Yz~@KDgzlN*-EiWiS{tn5J#*n(bOBjm@M!Pn1G#6 z+Z|ZYBXKP^n9ex(T(4yFnIAR&Z^kH`ZQunAm{Qb#L72o{wfM5IMD zN5l=a2U-F5fdoJiZ12GfJ(}f*07;Wv{7#C%|3M>o4GeCRU5W?NzHP~roV*9xzd4>C zdar^DL}pqT2M{+If^U%pP)o6rQ?Sdal%z8;eeE)*D5*izMC}4^sT)rY98YAy4FFT> z0UC%GQ?epx0F+L{CK)NF6W;(&jYExW%6>TVs&S6&N*>WbObthkO^r%T?Al7xfnkaw z!6HF2U?188%mV5HBS7&~WYVM3{PB)B{%?56Fb|}L`JWV%bbG8L#+Kj!MySF}U(_yp zC`qUu%;_S437}(a5)g^_18VX#b~ea}x?3FbA9c77eWkA%4c_3FV|N`w(-)@)-nOyH zD8(_w|Km?+p;B=0VF630mgJX&na=2wz!~f!++fjQY8Z7;gPDT`Bs@abRm16%{Xn(Y zPqcq&&}9<)r_Yke;x_{~|B@VuJ4!l66eLJGL&0}f5=*$)-Ppb$CBGia*#959VAbTSNo$q1-?up!6G5`MrU$R3qXRj)cGf z{$_nhtyJT?Ee79SgvO_WM!mN`h0_lpTkK=d@zw!`9Phv#E?ohQufs8g+?QM7+f}KnVk>fVD`pB2;oR8aNI-11*3Sus>oqW4mJCf&^(?#ko|U zHoRkITd0F!4kn=uqm_tQ1z&PH@IQ=@sbCmAR>T$^dK1A2l`GOo5x+S@@+w0VwHeEW z{1f9RxhdIzCxl#ut_Q?G<4LQ8?9YAW|dJP9nWL%5j9ANXv1^ z040Ga_Qc89KvrxTY%|be|MVhF2c{{4?|P_(ump|72Z?d;^wN_9>r*#5HNGjKgx~;0 zDEiF5gk9=((O3&!g992b^q(!(F$zwnO|$c)0kQ5phTb&oIUkqw&T_xV^cVd@Xjx!K zSrbhw5pgKey0m+KLFR_;c~6}=ZtYI*eofn%D{o&`{MQ$4kN)Sd*TY$%AN4tX_@oCR zmr=#e$i`Q(p6X2bcgdKYV*TOk+FPfO*2d69@VKNspBp@F{w`K*9c#@cg^$Q$-}BPi z4yf1#(kUe-e8o;#)gOyk4>-H~H)MKP@Mk1VA68aoDB7Goe8FHvs3am>uUz>L(=aRAb~y*8 z$0n?I~~H&Q!u6XL*lJvNm5cbKU7q z5sSQivkkT;>b1soyl*`*pA6Z?h_rHu@ezfYbHL~OulkflTZz;v^}|&*nH$Gz9o}!Z zLOa1}@N?lVl-A1@_r{M>{M>dwtiR2!yUY9_$=pA$N|p}w<{BO%ai<5|7LAQ&!}$?}nv>)T(<=e9 z?bqj9?H4kZ=YAxy6!j$675%$|1&8FTjw4+TtqF=0_n)8a6x_rf;0U5Z?6nJkU!6$q zn%2(M#(hXo>Zbpi6({69cXfp3e8HdqcuB=;seIqLPB zIemUAnu>(p30@lRH~zyd*jaNg(FwA#tDo)O^BltRKNWOFhY<3xRD0s9{CTpSO}hBn zYnuK^ZM85+rP{~tw{Dq(epZWJ(w)lEFBgt*6263KMWYk9j}O1){+wTs5l;G*kbm*M zkCeE9$b!j~Dr9q3Z&VE%&ko+L9j0oynOaYlldzCM7Zpib<~*}K{W|2vG>Y8D3yBdEC`k3`Zsy{^;J(~Lx+3=2U*Cgs zW_e3k{!8kUzw}<33fdGM(QCJW^_31^hKfd~tSGpF;imK3!TP%Z%FrCf!&R0pzmcC8 z2lhG7%a+fa`91;FyodQ3L1WuqSYTEKiBom!k2%J^L*GR=YX6oZV|0d4ycgV zZSRR^PtEpBr?q*tEh;>lv8XaCa)rt)ujkq;$I?Vif@Nf`)6 zMhU*hmHLABR|VX%2cNj!AL8vmYB}nb)+RhOdIg=vrP<+*aC2)shDEN4$$6TmPe$I0 zcF!xGp9;Uhig$|jp=zk{|cjDOI9v$ zM6LU4_%&6X^jF;0%5)X^hR9L#0djqo&jpXcVM=zIXq%^s($3~>WCzxh-MPTW?U4Jm zwR?wpO1{3?)5<5)Gu`i-lDG>SXUj#Rb%x(7KOP^}nxe{n6f|PV=c#xa-dwj9IX}UC zx_9ZgOX#Ks@1nn>Ef(td>}#|cQ0qJWwWK!->p8rxg)Ka2{tkHkFNYif=4^O`-9*VntD&W%6Jh% zo{`u#RpBp-EeRJdr?noHHwBy>cSriE+Sy*Fmjw}9M${|OBL~%w_5oh%H=!-i<7Gc# z8s~Qxs00w-^Rrx!`K_3XcDff`Y(cy5i)_c1Q@hZ!ZLOTxhD6We2$G_rr`c4LKLQd}N$i1yr`97YWxV`3Y z`tQa2AF!t^+P<7Zd|FmskZ^@qEkAo^thSFU>WffiX|NM5`TLK(Hs1-gQcT|YfhA6o zkK{vMalNcPTI_UNH1u?!w~Fn4(6L`d4t%l7Rk^Eg>T4Z8y7HlU7CmkuFX1syRzC?1 z^}wF7JFYdBdPne1HnGnMym{pzYPHEzki z_?=YZHY|nq>dSl`VYg43$!k9w9 zHtQ+Gi*|QEXLo3oO~rV2nOec8ztD8Uf5d^`IU22led^{e%DCVo6Wf=Y>Guyt6S5wl zwR*N`4;+a1J%?v{iW%rKplcrhk zKWVJrqrOQDDe+mJ%0qr9nR=%G``%VsZVqMU4@IE#(E(Q$susUtt8KUYEFGfX%=%8l%^PPOZ^o$qbJbplh6dE$`CWy>v=`38O3x3petLR+1xF;z zd;qpYS9q;lGwXKkZZ@WtnyLdRtp0$Qs}p!^c6lwv2!o7dQmV|ZQ;Ec3r=p@04Hquful zSj&A@z-q_4lM6DYn_iuY%?}83Jagg1^5Q%~eZO7ubQK4k^f>8skXGR340J}`%B2Qa zR@r}9K`L^!caMe;7XQ;qvAJje5dAvs8S@SE_}?{QytbcV#w<+NJX?2N_w`WpT(GvT zFN~ti!^O{*8#vXfML*rDzxIGMg>flzo(&x<MRp$}7J5 zSSohP5yEVLx+tw-ek7*e;C7VfzAsba(JSyJiq1aDq0-{{%9!7OdQQQ+>eB;u=BURD zt$x9+etjQliwjGIM}=8BxlmC*^B4T88-)usC0j$7{o0L<36bP`-(kb@9ZKlAh{6iJ zM-QjR9w)>odeBZ66uM(Ig%QupA-_)uGr`wh;gBUV9=7Y$_Yew+0^#%hbF*5Mhsea7 zyJ`6cdZS~`>@#|PKO*eJW|*j1C7PTZy#jMYRek$({-%?U=5hH$cZ>?EJ;pWT2_bGO z6Jbj!#G!USaRBQz^rK-k_oM!rXwmcBbfnIOTi>?QM-uu*8B_Z);n~pYw$N)!^ml+5 z0i0wEG<*#M2D~Ass;d(kV^UJ~WgYnoJ-2nQ%6I+Uk9(i%_;kaCIRIf=p_Ie!>EL}C zA`Taiu&#{tN`;k%f+D>Z5D1@6`d|~RwKhr1N;cF{ zD66a3xbjDZhP(_zJ8m6uP>c#=&BJ9Ep%r(P?$%aJ3m_ntxln#x&ZtRgreflG0L~)>P#ehZr&%b zLlK?!m#zTsw3u1hs$$xBZ%^2|U89s78~38L$d$8i6v?YxRs4V{fJ|4c@Q&K|ZuHaa z8W((AIlug5z#(CEB{!{G!q;OCv0$Qi<(h}LAe7|WOea(}Wd<1DhF#y!B`~84SH3p$ zheh0{3tud&Ney1e>ror^&YeZks1x5_tek;L2h6T4a;tP!e}Dum`L_e-l!DH0HVAu= z4$3FkxFfoe$i_@pE$UI=rfb2XFDPUREpfgHPq5EK%LE>VjuOt5>Yf8O+z()Kz9wX) zCKI@kYqFaCe$)4b_26QO(3*3{o=z1!+M?3Jdxxz?`#0bx9GXqNZ9jKn4N><4WS%*! z7vJHH_|t#HM*T^daxC}92ZHVu&2davF-d*7A)=XY*FarAq1n~E=aF3-K1ExLf40ic zX*H0QkI?OIt*})~5_|XE%@QjoDG!9U6q^3N$?03qw>^M6nvliRGx%;^MpzCT`me6L z;CM<(_+-(UJ&Q{WAbHS_U~r?(TufLbqe*9;RjIu%`c_=&;eC+x*>w_4K8v*}STB?G zr??R1rgeo|tRpGcDi3k)8e-s#^6cM|$rbBCUXfd0cJI72^X2U+>js?R>aN*Lu#-~N zeeGhAA%16zCh5P20XdN#n4;nNL0sd~)BA}Gc;cThg}?4C-E`%qF?zJhUo$wV-(cih zAjb5*pH?>bD)<@y$BC!JZL|7^U0$LZCzB~7yo_nlo)pBUwADl~@6u8chr>bdIFT4u4XXSUSMx(oPskGttpE?U%a(k-w zuc>=?TM}9d6_u$pftMinBQlF@gW=}~J5}kgdUbF<-ZV72+W`#87`SrH4wu~j%grFL zuB9{C@CTqs%05jd#0XJ79rKtjIp+YgPivvaB<9~$QQycT{@uDX+`@Oj>MDy;;iVyR zf-BG%y~seEv;OV?Z75BnNAHFW9zq>g)V?j$6+JsPT>j`uT~Gjho?2!ReHQjgbiP}9 z!6ojn_*hCK#LLeF>u0$n8z1+lNK>*Gn4n`?Uus)9jgKxXI3j$>)C)d_5BzbRc&(u3 zoN{EAv!c-!S1G?6gE4%v)}cT4(7xa54k8TlV!H1|i(;lyX)~7_@O5#$g&G8*LjIns zZx}6f^cYW;SB{ZzvoH+ZO0q+@?~H#-d?(W!E;@&F%l7>yr`$LgoFBenh1ahf;rD+)Qzf2j5^=Dj-G|W7g|Sq{Uo}E{ zJa#|W2hASBz_#_^Z}oE8{ivT372^z=Tm;OMNjSshPY-9w{cwc|eht0@ zM~wH3p2mQxHl9%hiA$x$sM&+%khjb0T!)BSNK1A|8r4`(>*R%BLC?QsAhFtSn??Aw z&_Sw3*4OOdn-8-TQtsY7a7!uLwUPB>Jx+Stk>Pq~=S$b)M*i!+{Z}8FY?Wi)e=#Yw zE)``R8r3E%yXG;{G{?^Q(eg*~z-8mb%T2#Oh2Jsy_9egW*#cyC!O~c6DYT|JoLuVu zXftyoe~4zOg${*z%o&^_4bZ-kbxga1o?h5@S0aiFtwOvHf{y;^IwfbE<$pk^0%TD7 zT41@!hJ}A&F;QMa|5j<&tRCdpdx#_7~LFwbo$n_XfqzzX9;3 zmIkuizj(jnQF00G@hT6Rm1;vHuMZr+F55VkCx}1HZz=e}t~w3F8%B5692$_wbV>-_ z>)2xkUmeQNpZhKvLo17XXD5pDEt_CCL$_NIr}zS$-PPlD>J6vEptE7P?ki%3f;88w zRxvK2x1-&fI8V2oXx)&;&mhne_$=zplkGiA5JY)2=T`WCr zisSa*EHyV@ZC<zO+n%v96De%IYL^({a-Bh8C?|B!bxsI>BztJkf-MZIW^Y% zEbB%oT4y6RKA_e$6i-vy8jt*<@5>4ZeSrT%|M*L7#z!!H#3g%C&lNiG(nISKm6&;^%qizK@@M2l zy+_aE$c3-I2lx@C7ZOxd!h0Or{@M)O`6UAit@tZo;iufS6pLJrjvqrW=M)v917FNA zzRP+ijev}Nk`FDiE<76yZ{Z#LhWL~cfVM5LZH^vPI^tdzc#$s0(&nwPi0hLJ%C&wg z9pS}k$0Dy-@(t`<&sSf+9~w*>v|V=yNs$L{P8b#bNBw|w_IDcL^U(K6QCzN!VLhJg z*=#*BoW8#yEjuLn(kvc%7Io4@x317D{$b1L++9Qb_-baVXnY;lGgFS~vP?BZjGo%! z#q!wmDKSJj~^xuY}*T{;=+tgA7RezT2M!2s_HWNq}=aq{`r`}KQ|ckNaQ%binvsUj3oWo{U4J5C9(QVA@)Ds-#M$TiYGS*E|~>Cfvj zH5s)rVRi#oPgTC%Cs|>IvGiE_8>=;<)8Fv*&(w-j%wkeuo=@B`rTz{k{)(1c$E=4;(~{S8q%1#Hp)#&KS~g*>}4lxy_pQbf^k z=}(P0Ezq7GZG)}Gsb$%+Kw0ydh|ynf*9VX-h9V2CH-WIcUv03U2HWrRNoU4Jt=({k z>2_Sw$;6wd1L0jO#kazEQ!~sms2}vbcj3x@cpjY3`E>Ggo$1{4Tl@SljTHYexW=so zF=@lTH*X2N{?H$|#MTNemc8hD%E@MT&A-B>_i2_evn}|SM*+Ic z4>JbCKF)p7My5rrDHX#)I4xaUZIxxQHZ7#w+-CTlRa1*6LS%{iN_cyt!t}A4s{!jO zqi*`ZqX7rxK@*!x{eNoO3oiY=>0)qH5t!!um_=S`%HvR9cI|I}Z>){_BM|X37GApi zb1{1}C2TNSW5Y#8mjavuH2QHnI4f-U%BYA0?!P<{b6eKcUe_u!S9jwyTBhfoZH|e! z0wztfIwsfj0Y+@G>S7L1k`(r#Y?zTG@#LQ!IfJa3@dSB(bEqZMtlGMaY)|fvo&JR& zM#mjlFNoaNK7b6@jlI|6xQbZ$XRJs;B2Prz*oofD0_Br zV;)mgkj>2AvDtrSf?<|w<0I!<`v0a%(_YNPHdyqx|!Gke@13q9vl7gF$MYWKo>B#W z9B$rMGxcT3znK+wp1PasK^S5wOAP-oDFq%~TsU){pz!BR`Ml?>$%;aMpxDYpq7Lau z%0Oo4xcOetJ!5d_Quiw}Ef_RBWSx!cg`7txV72z7?ZO)7QUFU^G`DM6NJY-?R- zO04Ogup#H`^!!hjz?G30(~T4G9DvktXS`;0ezgo18Lm_~9HZRxF?@vt6$>U^n-EOD z&jxXBffUgcmX>zLOhA5`9!}EWTc!>W@U`+Ah2|BOH-_8w!&kEjHQjKH9HIx#DlKG` z&NaO9FN!?1tV!-zx8JCsrqHsF)b4K z42#3JR9uDr>1BO%NCITt(isBpwXmnpd=ep!V3`j)lA-3}o4n&hv?@TK+9|pC*DgoX z#{rrA!5=Qo{Kh$uPCDx~Rfm4-3xy67wtR)AJ(s%59<y*0bLHxs&PSI0Q_R zHIsFA3Q1VejE-5GvZH6Kwu7DEgTbjKse|r z(^al!#Rs`FRl%RyLtoasC^e+_o)wi=V(Igre5lhRD1d)YGB4Rc#aqAp+^_+BweT^>Fx63eB)KR z&Mo1Jph_USw6n&Fmch8=3p4$&P;jaV+HB-SUnqHEb<}fCF9)}CqWVREgkk!6A9pft zy6zq`q$MQF6}32YzTBQI)^}mo;!s&1_t2#KLt|G^)okVCv1-rlT&ZW6`p7DO{1xEi zpOW6!B1%wF#srG4&~zT~H87O|lf$v2Ki`7Q+{{7kt2%M97FBWn0mK|!Z%4pOEq?c^ z!utvBEE2~@!F^G0V#IE84(4bE9s6h)YBsB?nVc?|)dUtX05+UM`A?wl(bR}UVpd!; z{XR45mZ>8o&mHG9!R#Kp4Y5Qazi|>&yUX6Y4w!nosgnyJ$>9IyFM$xGQ{*@*-iwu( z*4LQEo)T}Ga*|Na=%@ZjcZC&+PtX^}jjE>TdJBmM&8IV*ef)Qr8N3+-odgD|!;T%l zLEoo}G8CT7W(gF4MXx&G$xmP>dlcIhFo(8^6_>fZcuMPEO&QO<)$1B`kL9B|1Zjgu zz<%2oF8zHxsx`!{JsFH(QR@zorY}6Kor$0&WK6(+E10W`PZ&Daiu5|~LO4&BLOGMF zte7lI`{L%6;HTE>Dkq~Yjsw2-zKe6|vye}csn;hT2?lF+p=1)7&-NE_W(NWLk42-E zBPZksr%jpTPu@Q))RosPKK(}#$k27usv6e z3PtB$(Wc5`Z6|EzfnV3ZF<9xOoU8QML1Z_A(G&GgdC3ci|8jmp;QTJB-7)swqcOh@ z`@+k5kbR3SBj>k^Qa~a29W+r{KUs7jcxUzctyrPnPbv66u@TT0YZcZ9L31yHVd`AI zH1IVNlrHU6#cO}3Qq_wi>qa9PEhn3l+UzC-kWfV$Al|u(+LTH&Azp-!yZ!A#uS!oW zH5-4l1SscTp=VfIxzFcac}{p`JU{Dq>4|`@YI_e0!-zYFijsHz!1n1%UYyj2bw;F5 z)Oq1CNN4%G@fhZE8^jf%;I&t!HDsxVL=tSx-JDunoYKJ6rVsU6rDt3nhi_g$GVz^ zjB+%YwVHlX_;W- z0?@qiWFPv9SPjTqMUyUg@S!I_ggE>FEae%hDB#E&>b#1-H-73N98it>C?0Gp2_*nj zqi-DJiQ~KmO8iuIiPWz!p}AUKQ;Fk|RSSkYcYEO^XGc8Kx=(nc<`4B0^jR&OOV2g% zg;vd$`xoTjKt#r9SWr*eg61Vl zQH{Qc={@f%uIh(q7BhgG8A4w5rPY1$s>`LF;-WnX@XpW)Y7qB!j*K`bo2FRQZmL`j zVw%qD2gy~z(tL>BCbtGGyuq((FvOUy_1Ka*tO~Wf*Q+jmm#m3u5LM_u|*xRlJbBhW3eZF?UZ!yBapUr9=#68L#JdmJnp!oA+j$Zaht& zl<-WmMHy4zW9p4c?`6EP9kN%E?qF}em4rffN%%eMvo)2egUMA z;o6=Qkym=VPH({WpbXT_8lKy}g}bZ@KRv`fsq=HX3k_1OE{nN@4zEn%;~MO?i94`n zp63*x*m(0)A^Q)Jq!+Z?HGOuwL(_bTjaH)plr>V7B^J1f)zW7Zg&fwNez*iw)?|XF zRWMaW;AN(s2TLv;@Y+0$Zf`~Qlt`RX4L1mSa`SXi(C24B%=;%2RY1Sij-6Ls>JpOt z-m&LOJ-vV`kvGf1f>0JPZEB>0vRlk~u>P`CkmyK0tAWtmq4Z09p$n-D=a zH`8x=V#>q)DIs$CAC<$S@K=mV=b=9Ra|O50U2as4h#N zcB%@n^%_gk6W+Xb%|uE zexy-{5dKWv&2i#NvsQJAspqiY#6s_fO3jny>aSN&p77=fZIA}0T@%?ghj%WvQC zY*}fg$i9mSCL7P^Wn|MsJK$rLUpRq-R{JkC@|RQ#QiozLET?&Fh9y~M3=^ z(#?`ZW3qqX_$wBF-eH^6d8V?65O+_)bM*{T`o5(iy&$vqwkDCK6fWq|rU!P`*1(W3 zb0TOZwTdiiNU<2bVTaz@7u-gXcn1xM_7-wcJ*7 z-mM{)kLO_>fi9inJA-F`erwsjnM#>RJV64;PCDTw3`s82pRE+LBhHy&IJwxsX6Y)h zVkANWQNMIBT_IU?g@5#}hVHw?o2T?Rizju?y3cQ%EGr&p4xa}CTGXQRgR_$ASaGEO zN1H0xW;ka@sYrH9BCm*njZ8m%e_{<)c^%SZ>Ah*8nO?7CID5_fyFuiTC*VI#M+gx2 zc+(IczvZXCzS!46yBcHuiqV}8#-s7*_xBlsbQmI03`t&-_QhzMN;c}L(7|F|z; z%6NYF!bh(xDz*fGWx$9Q$FWpK|I>DqC-a?2T+O4+u@7^RH?m=m_iu*k^n*0ufY;az zwV?MteXtW|K0jx#xOKP`5~O!M>0V4pkAzTVA7GAniiZgO_MSUAJXO)2)Q2`SbL{mQ z0KE(QFVGvNMuMOWIhq9Pp9VZ-&zaA?6q|R@#az3*yu9*5HScughpt4!+y~;*gYHe) zZ^Hx+WEQ*fP9ZRzU42ScLY>!%N5l+;Znw>m{%5fCXCAPHX;3vB((dv+&xJBstd?mA;tq9Wi8qB_RH=v5U=XzTTGG z-fqn)^*8lHR8W1qg7J_+)ht7SyO{;mO9PYD5#pvFgeRzKhJA?)o`ty9QfLqVw1voK!Y_qX3+)1r3`k+ zfoivhyHbA`c_dQfpFN(B_O$xr?=cga!kLZFQjIo*Njj5YkNGDI%2-K{X5)i>)z?!>uhEjscMh-QuJY{lz6Q<5j$zq?<9_MJeGL)wf%1Bh@(#Cz4&HG8Q$2Hi+rF?d zFHURd#<58z2-xPwztyosR2-$NNy(abWN3AY~e+ULI* zuTQc`BA8;VYlS)*(ie#ZGj^C$yi$9IDl+^9HUuWMsks2MKSBDDO;uClg{mtwT_13D zd%UUtdIpg>b;a%UhzM(iw#bnGkN>6FxQpuIZFzbAURHL(y7~!?b=yr)M<4p4@m1J1g|a->Wp(w=yJx^iixBCAoIJv!3><;B#R;wNrpVY-%9&F~yKp%rX$}PO45Y8MH7B2$v(CssRjTxNqe-LJZAqX5tA@QC21U1w?2qcB-kB8 zz^^^u*DN zXtU90pf%=|z5qt(?_T;~_laj?e~2h4y8;nL%hA^imVQ8BBQlfSIXbpAGK4zz&lf}^ zJq+_8OlMR3HD@P+fR8UDbs4w_f6mm$R0$jPrO1FIlHVc7>_m=4_%iPSp)IZe$ z3c3tYZhDzDWj+|V^lOu2kgP8C`a*Yv^rEU=mf=p9R@`U~NSz%j6Q>Qu&Jw0XS;Y%Zv z0&Mo8))1WK0V|S$685Fm$8<)ySkVGowv}u~t~V-+3tPETPZ64tJp5_}%!ggj;0e6y ze}t%eeVL1Zwk`w1t{C&+9Yf43^p6U4UXzQ}Qr zM*hPlGAwvBX)gb;iBa-Xbd@VhJ#K>Ei`7j!!z!cz_D14~pV(S;EpX1$X; z`ME~;cgjgL?a^p8Nqc1CJt!b3tO9m2jg{G%k4i*d7Epp?%tljd>O1~-6iJT?OG5|d zDG~!k9o&=PnGWeJs{4&Bg5CjolJVv}C}g^WUnf9lavlUY)W=-gr-Ne0>i6n_*W<}H z^C!t#dT`buA~q&}hj92r7dR?JmSRz(`NQYmlRiKEAdbvFL=QRg+!D)?l=U@{^EC+$ zh{`r3l8oGui^?r%G3M%yHOH?nT7qvC5Eh(#QHKkm>!1$)w?m=8p!lE`)g)wA`D{DA z3VdV@J_cyzLeK8`3uU;g9B?P*-#Z4j$)*|Y;LA$vc9AwSnS&_t^}B$P5%R0m$MLoY zVs4++!H``VV|H(dNGk0X&gG|CE!YyP*T#5-{{qJyhp4RNQzh!$p=}lDjhljH-i7b; z!3VM-<4<2Q`lE(~7tgcXGv7F6!u?xdijr5K2gE`pl2&BiXog9^|{p zY(!tn`#OD`=;aqBZ0YE3&NZGXpKJ>WdyLeQq1cIe_^50Tab~i<^*&K~#aJp{30t~X zVOV#}-0<0cBEE5ODLF=5IBebsIP2{$h(22X>SVd9jTG73fI({G?$TBb63n$s?|~Z~ z<=+pM{coR%2JJ+T9#_A^C0lWpJI;)9OsXnG!T1;~3i z$;Wi@kDn%pk9mQz!zSKbN8ndiSO!1P%eFR@}%=JBmlm!>_8Wm(UK$&qS?vynH(LtuZB$G8+uFo8 z8?e&*LC~viK@YTRr#iKKfwrH`bwvJv#+gMmmC-|!s17Obl$Sw*Jp`8W2Dw%Eys^ge zkbf7qUytxEa%4=7Ty$+2CMUr)+gGC;;rWU|=M8eT50?KUkE}6|PL}SQOzH4=%d=^w z8LZ(FwL6=I|8db^<2dkQVy{{dW$Rc8JkqRG6rnSQB3@(F-CRP*^n4=YB->(M$}2m% zJlxMcn0AWfV&8dDDfq1GKwzX3&S1g!d^3S)wPm^)&y6r(9QxxStTOHg5OxSF0IsO+ zcp6l;cmrw^>nIi-yeXL+zM&{7^9X$|4+ai5)T17|&T2n-OgCfT5i-y4y4iuCuZlG& z$bvZW=IWDZ_I}D7hrA7nj-?g&SeqSn0`KQ~|HFJdJ|v_+4eY}lw@!QOimlIcZhCH( zzl~@p`hblW%o~>%^x$i{#A~p>vEc}l2wUD0s|w(-$59j`Ce+C56n&V~aJnER7~uJ=U(oPt$qY2?HkV+_)oNFJfsWG-p^A$MXXW2RUgj)=+oZB zZI9h!4vlG&J|1yL3lH6->Lwev@&z$bDwNM}dsl`N0&qK5_0qz>3hMlopPNP5J^^}O z(a#JcO8&NLm)8^^!(r%8@hJ$xKL}On;jN zcB-aDiu`By7SzV^62~gkl60~8gfdqSn|uFfD;H<55{#CP<*XkK02~IF8l9j$ECCT| zSiLgYVsA zM`>(iCQbHBOORRa<$B~|i=s{&m)G({bkjp~EZph_6*qasL-6hHw(3arA+lz95gsZ~ z4Sh>5K+E%Wh$Ex{+!btyuw57hBp~5 z1a=3+MjRRs30~LjRG}NkFW0w?9zo!}ht~d0$&C5QukaHeMd-2+tiuX#7<8BC0*@Eq z^u0m9v$q<5i1F*j4@9+;SMH~Sq05_7a*xsAT|_J=OZ!n)bLt!~H4er`ixYJLdu{mX@$9e0mMY=m))vpVAz40s^-!q!OXWC{t35d|7Mk8~hnbOxJX7tD;7e^dek*>0 z2{2?wOZhF+f_KjH?rp$(SSnfkIN>LIJbhTLFU?~<@S!_k)pK79W`biy6l6zo{G`S+ zNaxFU@Gy1m`46v8`lxo4|5*v6dA;__Zs*_F%u{~^XMVV#On~jBE*aC^h-0&Y9 zP3Wdk1(?Y_D)Ul+9;zVNqUDz-cJdfJ`gubDo1s=|hMDe&Lu zhUjR1v!`Xf@+>ugC{Wqar0tx@`wZA1`B%cG!?*&S##C}-0!>luHbNI+hsV?($*O)m zp_`r(+=XYmu-QLMY0uX6|ayW0MBN6HU(Bzc?;E`v}Wu4Lg-?`jZAA7dI*)b_s5Q= za6mWO0$)OWB{=&&MnA#$?d#Bg@bfvx?Gq|ont(qUi>6+#AZb(tULLr{8R%R(Fa3qC zQ_FrN7_zSsQ6xBO?tQRghMkiVQ}PS&|%Z!+XZkcDrmD&q%L<9>W9=8U-mg^a8ubv9v=6 z>$3mL%J}{!k}92y3fxP}Qwepsp*;3ED?8?=N`)?G(}Gw$7irQK^%=AirZb=RF%EOQ zt3$E-x79yO1o*03i*!ew9DGpNx_bjR6^rqg-%tRIR2BDq_*nW;87 z@+Qx$xQ(FY&2RaZ06y= zmh`Z0r5>%T4y~usscW3$ZyD7E%Ot1_=Qc385_Vz(l8#pN^v|!l(-$PidhLX`-iv*><(oKO(7&Rv`nSEHlLXMzF4~2g+y4?!V{4n?`(8)-HN&c?)uKAmrqP)WObatQphz)d14Bk{5xH2Cs;|`pR$3|O7 z2^OCl>2xbhf~7afjgBL9VVwOx!10l_mF`&7?x3)F0J7}&c1CZm|F4JqkZ>_e^v|GV zVRe~yOlHYZZ^!|eDHRga?W0Qg^-C4IrtT7b!qlr4kWa>PJHa+ zg>N+|*@~lMURo=R@(UJJ7IcfwOzq_FC`9!o3i|al>}OZKYc@R;qRn^BKNF8%j(DHXm#o?!_rO&n zP32X~qDOLJx;`<_%~mEuqtBZBZd{O3?iNbZ2ZT=}b*Zg&j-dAFl&I&Qka zHwCfA73hkfFWpVRK6xJm=Zl3rz4+@kCGj!D=P~;60ZUMVMJFr8Ym;?c)vCR;04EhT z97kHU%c675N~%98JtjPxo1O?JPCKh~gn{k9p?YQc-(R+eT3u^D`&zT_^TMOzSO0>i zC5EYfVjbB<17Ao2w{(-@;QV!&2kDydSJ({JuIOR?gLLL%bc!nKR%$lh{-Tfs*^uZV zIBz6GY<}OvjVyR1P@KDh2TJkzSGsUGw^#UsTD`cpfZwpZ9!E?2b384-a;Hda-xc zKEI0zb{ZL;m#$tqdiY@8^a?tpC>yz6nB^I_%i22bllbkupAKK%V=Z?1=xw!UyXNEK zju|Ia6{`lkKrO}cb{A0p) zxbyZiQ@9F(b{xm+rA~X4Cc^fSRps49pWIj$0A53v)4~iny=mnKF$4{5e$PuBB{t4N zCA69s0;&n)vI0_ppir4t=CvQw;ydX`Qkz{Ux_UkuIq)zz`KJgg%7OK4b|0A)=+K*v zC9)Sw51~r%KX5>|QNq)Q_rWCj3#B_n!v``*xB7SiznFn;^$w%(uHi&Keb9t&$T#fd z0QHi~p!HO?1*$EoLoEwIme$M{6YZFf3HVPCz8dD_2VEp?i$e|vjWrW>-}tl&(yBWz)&##rrXZy(MsCro58h zny#q~m(J5Zl7_@sgkyfE)$rFGJz_+&6NUMlRg5i`(~}d@$GNIZJ6l{L{P|U|qxT4A z7Op*ZTg>exv=}vY`R!ac!haS&^Ez-shNi9S2cv+I5kEOIGl}f3Up|Nh9-%_z9_lIg z7bJeBX+OET_=RXM_j&iI_cXidpCf-vnkblk!65sUGRV8;^@Z-7QIdy%ywUPk@U%&A zh$6Z`D*uU2&6?Ux)-Zp#{X7D+eZie^;U#prqcI;_M64TwiM@S{CX2BdE57asj(L)4 zq~tZRn`)!-dBuRrmgU<&r6%Rja2!e7SenJ(ZVkF<>1Vrpj;r)(ElD|0RR4R%(isbq{xC zzx<(D_Xv!&ao}&9?C^m-H%TB{z;2`i)G6mt^h@KFJ`W%iz#&`iD>)qV921``OgF=E z6}I@5KK)^RsCFQoEwvv{Lh{4py0W7C!kFdl0eKtl)!_WFvfm^f$NGVgs)x>VP0CL; zDKF~t>j0isqK6fzW!btM+5?bs`6|_{zXT(&6-UG6ffz2Onse2a3yWX)7vhwzt}gT=7QxGm=xGJaOxP=?1||24K$cj4k( zhO|MX4ndr5N;bFL9n?#xo6n?nH_{ig`oc(>`npKS`xS|)h@AO}$6v7E?x=nrh+`o0 zl#57LpCFpz^#yH6R1mrd?y0$&kA%O(>AC{tCEYs~^1pEff;t%w%XxZ*BLfPROm5&{lXk7VN2-A55r1Qp_o21QGt^EiS zNy}MoHB3;;tNL!q)H@bFJK?&Nxj?JJGJ3}Aw-4i~^FRFi$!2n#1eMxNN#!`x?2tjx za?skE6Ki7mEZwYGjx3fnQ@&9dfg$|oLOx2$AuMQ&gQ85B+X1OoCpyR!MS6=i_Ks!2 z{W*c8*0QOoDzZb1obZ%Zd9PUo8A8DR#Vurh7Af81W6ezIN6=BSBPdai;}O^*^kkq#A6frmI!ttG`TOv{%NgIua%H%8 z7RO-3*`UmsIDtbV&V)zmttck}0q05bMJLt;uM}_cp3AfQ2U>cSK;<@hB16eO+WPk0 zmT2V7l<34Jmd`s+kAfvrx!`vkbb#eu>Pn5oJIndqfciowupH?D3)vuikP-Y>J!^=BHdF zNoHrcgKKA50=Eva7NOw?ZbT9zh}-F>{L^L*!_EHzEN?|yc2))6NIb)Dw6WuRZYlcV z3>iLj%OfIy%6;}sD>z1yE2ZV;dz5*oPbY`Z^Alm8Y!@BdjtSWB9Gp-V@>gCX2lDrB zk7$I`_#1aoRB=?``5LyV-5S{9=898W)&$BR*AAhW!`=O4fD~r%P4*j7@P<+Xqbj5{ zaxkz`hk3skEI7{P6jh)_ZW}T8cyCKAf@Nuz12z^&D@=6%+WzfL=?oyyl_ulNb9wa$ zr)xg72$z_TW>wBdgFDa_f^|I}R{J0rdS`U2K;^Y5&a3yP04Njb2!q9P(8rg{k-xab zMhFbGviwOMzxhjqG4gA~mMQxHai$kyOu!Qc31dtn!J>=@bj{5VK!A?Ri_9o*@j=PD zZD~RDcyGADPT>^X)dPlEuqufd0RF9s2EP<7cIfZNlRwYc7Qky~@#dc#eVn6=cGH+m z{YJc&Aw>8zq_)w%Mda(l<`nT~{$`L40SqE*Ied65hw{wNDq3pO5=QDS#Q5%Fn)5ko zhGh%pnA_}A4(Ja}2eayp{s)jiZ@=UD&ZzO3X<88qMH?$;{(-KU%V#)Lc24_Nwt zBj(Fru9Ar9eCYn**I;*M_ms;aN!Irqf=*-I`p{h6;~AIJ3@nfbdGLH}2sW9Ijp zb13?sd(8ES1?W!>wEWE9I05?HZQk!0TdMl6TKZ7Odiqa}v-(@a1I)7g!2iX#cJTd* zM~|oDu>bT&7XK!&dv`@Ye<$#V;lEw`HPbJ*8|Gh|TJx_H5Fhy4Kbg7|BpWd{ym-N9OKls)_#`5uTl8B zPBroKu)pln);_6sVBFmL5YrFy(JK6+Sm)L2^}9n4ceeEKoA5_nVa4Hp40^cq%jP_H zuYcn^!hQ|Cj(IWGxk?T5I`_nQz5mVTeCJ)r8?)DKrk`OE;^fb^))%%${MGALT){t1 zP&`X^H0MD#Vt)OoHJ+}8A8Co@N7@PVo#%dmep1Jiqn~`66%W=8x##U*+CM)+9*lDj zR=AFTegNa+xmJJs2gDaWdAh2v{cn3^bUqRu!k2me= zuVG*R&#s;)wU(#oe zkB=hn@uOqZbrzv-uj9L$_>k$Zz6$%te`&=T?uYoUzh7nc!xtex=fm)u?GF4Y%wJyq zg=xQi9r;YIwe4S&&hEI;9FU`-r+RLCf`1I+R1f&B zX~%YAeB8+Lhx`fBG16d?~?hUuNYiz7qR(zy2%J|MS0C&ke2bVj=cloP_-sI-g7r=7XnT zzmo1x#$moT_H;9U(Tj`VzuMKj&dtwIJj<>9YnLFu(^BhuJavD3UzVS0XqCbT?pFMI zeeF;1zfG{#RgXaanKyoK#*G}0^`P5sSM{slIS%`;JFZjpx7YdkUsw3=p>H}K-~`ME zx2!2#=L_mXeB(sq<(mlnHN*ivX2k){!}{#b=b3m7aj=uEIM@!vQw5ut=tXt`8ln@UPrp+Obb!+&RiRZ{lG16aM&X)1T0T?|l{Q935A)1>|o03v)j8 zPUKR{INPO0pjZSw)PQR2tUJH&QbOC zxc_&|YxcpsSf5LB@npr*ccf|O+=KlfJN(hq&(rgmKfd3z!}}0daM3O1bMC=>XHmn{ zhqq(?^@imqUktzB%1%5BUoIW#ucFh<&ohU5@@1 z&vQQf=dWCC&f_jcUV{H3kJB~4=Oga@*}s_m)proTclu4RufYFv#K}y$(6pnrMf~iHSiC$jHoRbQ|BJP7~x@-=3^vBRYbf9^)}IlP8>+4-24y#o9w#GgDA zDqOG2Y=M2*ld!&^`@O#*ALgbrO+DEK^T!pZnQ{+A9Or7k+-a`F`gkFUkUI9Y-wq%J0aUH{FhX z1Xp1GrO%l?0_)oIv7cIB=g+Wj_c<%B{w&xh?|9N2U$#P?v>fuJ?E(BA_`zPc?4=0v zufHHZc@6O2!hiCq%gpP)aIC^Ff4g}e&HFJQ={wI{-+3?cMBQQKi8>H@Dwf@A_G1U` zqUt}syXmLB1M~FAnx`L&yeMNwnR-6&bMOQ7n)PSh4?i&ad7T$%KH@Ns{Jh!E{08IQ z2duc72>#XmPB-uC@I?yWYlhkXe*^vMLDs&-pTeHG>4)aLV-@WAFaAm41>Dy|h-VmY z+2IRT!>)M5w3pW2r|RE+xv4*8_?Lca`ImNp|L2?VZ(IO-brsgjeu4F}WvKr(^aDp* zacCFgT!U9OHT7YtKKBRf9XjvFhY^prr*%%jZxPRb_@(B$=RsKa+#PwSv_1A9)=&R^ zs5uUqtv8|;c3EdSoF*q1$TBgKCv@Gruzd)C*??`22idH9ay zKYSANg|C0#)VF!qukj4Vkt2cs5c^g4wfdP?pjTZB&H2|($aj6|JIwpt74zsP-)f%U zaeNNz1v5>%XaM@QtEF$JVxPkOkD2z^Q&5J7b;ku+Ny!?NaRT zn6{niKfE0KfKI{v>V3+8fuHSA%g-jxNs!|s{6ade<{PjRUj2(X&s~XeaQw}tfBsjW zQuwDAo9i!oA^xG)TDQFlhC;9pv%@DG&C=e956 z?p9oH;;V7Lf5QDD+-B4v@bh2&8s-=1PfmnBui41_UiQX09pjN7TYuM6U&QtQ!?cGV zLmbhaRvghb@c*1R7JdofTOoe?eVyiW*a7p|Q_eN}!vg$!uUY=RL!i%hKV$Yw+hYE8 z(Tiq(xC`PsCL^xn8^C{#e8pRQ!MvV_;b%DA8aGG7e|yhECjS@UpP6RuD|r(0uLV|o z_|+JvdN5A?5Aauz$KcPF-0g5)#x7R=mJX~tf97^o|2W`Z#koR{J#JplXzcgj8-A5N zfp3L4*au%S@9R@IuVKLQKl~eZ`}@CXuJ?WGG4%7+{Bb4h`KPZ@^`C|wmXL4rap;vk zhiE&Tmvqm`=6ksf&*vHYd_ClyIK{G;?%hY>M_B%f58^zXFE23d)&F4pz4&hmzX;d! z!dXi0Zq~lh%i(|6cz1;ddOZ&FxKoZa`_DPpk9n?@pJ4&^Lv2zu@qWZVe{!@rUVI91 zQ%75Qv9^SrU$@pTmLm@Koz}kHw_(2Uo41;F)cF5Vc+JXhwj6Px16KakQ<0zZd}|&2 z0_?9m#M)o^r9WVv_+N7ze;Ib^_1Bx@=2P%f?P1xC6JfW1+Opf{!hTqLv1x~V8+O`9 zXPEc(8T5NcTIT`;*vCED+Qfc}0{|)fxv0vc16HPne)ic3^_&0r?&TPav?_=$Mxf}Ui`>l1-3pY_bCtBb2q1f-X zqqR;^!MeksweBzqe%jN1q;S0+`U2MJK8^jedf&>v@Nc|;{T=#T>76jYJI1oR9>zM( z+pYKV9=!Kmer)>peu@1uM_`@q3gCZ)fB2cn{9AIK;{~z6j@ty=3J9 z_%q^)54OIS`;pJ~)NK^c!@##j{KEk+sQNm7>}ZVF^Krg_j&q)f_?dI8_?aIdAM)2b z%zpLKMX)R0t?Fw(#sut-xcOqUf4Jxzg`d@9ju$T>uhfU%qu$hA`njRsyJC&`y?+An z65}mDSRQ`qNmbKNvKZ^RxBjof_4;xiaeZI5_D39uJYAEmJY6Bi!Ex3&xF6yb?>W!x zH+F@e^XES??csZ|Z}fZCclR~u=Lsj7Jda{N^~iq|{wnYz5GQ=VPZZvZ_xm>ZMNb;7 zp65!`@5Omu+gj&&ZHG9hgDw2f?dr8lC zp2xWS)6LcM(RstZj6B#UTKPJDe7?eu&zsNxK+Myxu;%HzBA>v}LFPOthrFh*PB88E z$Khwb^Bi+tumb+52X;2`e)yAriu^*eas5ZYU-UPt|M?Eir`yRo*Y81m*Q4%H*ZFqv zY=*p@d(ShU+aHkc^t|WH^|iCHk8%m@JpB=K zemd~D!nb?gv=e@VyiD)0_N5*RKlAe4%;z=@@l@wtWPW!CAA#rp8`F<>8{&diTI1dC z(QiD7b4c~M7{dJ`@DEwfe+}lRV}GdRegMyTPnpY-s5bqjd{`EFo z&sE45{qT6RKl~xigL`(adB5*O-r8#|d;2eld;GzproN4a-4ZNOxZY=R+Rqfe^a=C6 zwn6;=C$0GZzhOVZudVpBgAp&Z2KfP!xI27D+gbZT{tN%)`S4HbeJB?r{=fHa=J-Al zcHmdB9-*&)4%W>tw${y0g* z-sk%%?CbpQ6!Sg4A8|xaTm9r~=(jgM!+g$tJE-ft#oC{{3HCWwta#2RFyDWh6$iUu zk>a`J-R65)gX@3zGp2vwF3fB0vgS35kvHA9{mvUu4w9 zJKfCsPJal0)a@^tewYWae$lhBx*qNK_%-rW&pN^Md%u8qgB`Ihrv1-nVLV;>oXOt} z|HE&OFz0JO#{Svy)_Tg>$Sb(F761Gp?C;ypI@jP;jCV&Pj$N+{KaaSkzgg!t9*Mj_ zW30SDHz5zzBE-w-Jg&Q7-Qia3>(u)Kj>LT7psh`wQHX2Z!HR1=1#!ZkzuJrodK~%o zziIg~rX!#H306M&en3RkIm<~662=)t|vU`r_BkxWw7W*ZNJIKdAEyt-!j*x2<)J7ZGQE zk#&yAM(~Rs3cu)F;CbXZU)gDX7xyBr{{C+(d^x_yuOQy<9_yUMPhfrUot@^m_B{5F zO!|k}PhJTB)!o*|^;=&c;5?inY#OgkSU%&naBTS)Gl2F$c~zuRp>$CYxG%-j960v#oqk zFCdO+`;EeH`^~**&b#l%df9iayoL85zG!pAMd-Zu=VQO#+Oy2}wHxxH=Podx^9?v}_grfq z^!CUX`Yhr%bl#!!kdNYPRz8Xq@B{B{#e3X{_p;*&X8os;xAPw#GUuE7Vjp^LZ&MGi zxdQWdoCh}v_>YiZ>&oAm_T*b`QTRu#^^s>0Zi@5h@8uI%H`j484|l;neA4V!#~|)&mbK62 zN66=V={rrk>t>Aa*FI>D?>izc{T<&n`@`F@fB&DB-gaZX<`H`x4gSgdt$y;`_)c%N z;<#>r{ePEb{}eNULrxpbtee})w&`6T?t`~OVg6?|XYz;Cdz)n6^c z`0~{4=6Lr5JLv9Djpe|{6|+6P+e+Fi&8xYjxc_DOtS zXIS6Y8u&`Qf-(K6Xs}YC!J1gJX^*EROi`Kf#Ey&Y;g%ua~l|5Ab^KUli<8L4y<76wZ zMIYuDoq6+qt9YIzyT8IYxjS0(j$5!EI_(N`J@iz>!*68GJJw)7;|ti&cpsi;7yADQ z-;w@aCS#x1yx%}?QU4F{Cr>)nv}cY%ew8ooZ21r2=PX+G*h^Tqo%ktJul|I2?zn%L z^W5XnZyd9gxK7j|_4WtY zU$FXYb6x&{_hG(ylli@LBd_n()_VFM;Qy)YWRA;!S*v(n`Lj8mb|O#a2lg|+i-%z6 zUuK=la1-X^Is3dhd>4WBUEB$O#hF$*@O-KXX@Pu3yZ9 zpLWW-%<~R@f^#Q-Z=JI_73=K(oodQG7kPW`>@o5CkjH*cYhTZI5ob6F_J+2j`mo=8 zk~O~U4}Eyr(uZ##Zl@39YzX`YoU?E(&RNj*@ca*996!XAdk*$zEw}b(eF=FyR-JCf z^<9a1-_-Hu{g%#w^xtlZ{imyO zo!bLH3~}jyw)V5kKwQE2I~A_?i`|u1_$k);n0LefznvA&um=0rM!%}AQ=eDZahJk3 zvi7rFi+=v3zpMH`1wI}9dD&X`ybXCu54842?1O!yFIn@oY1kLA6YOElvorKzm6gZi z%Q(O3-4B@S>~F#Tm)ouLxUa(b7lYP*=U-!;dZBfm_ypwL`jzFM+!1!>b(WpErJi?N z^SD#6-gMHV=DO02uPOYEtt|gD;`~>BPx0%#Qj^fn-({^+Y>V@wr&;;!H^O^-2K7$^ zzS-pppJL^KnGOGN0RQlv@B_@nIZZEG=QLf3_2nI{yfFtM&p@A*|Kv)<6>q$c(yM!c z{}6tv`TLvi4M;5qygdAvVr_0I>vKA-Yd#j_UpTI4af-SXSqhIO3dVaFZ{{A+kG{nj}? zpG5xucO(D*cDVkpBOdaD*8aXh%x6Q)XZ1S5sfZJPzcp@t0r}q^xzhX|x54*ypmjdl zuHZlV2THEa-}MQc7c&><#prp;aX1HarsbDA6Y-y$?PfmDV-PRBw-qn_QN)d3xT)E{ zT?D`GMmw5%^2G^?|Mi1Sz1kA^pDg@;aSrCkEdT2MXDR%+k1Kv1zjP|}YT!m(KiqV;`Jer^bGPwVyQYgbRMF_;-0)$<_Vm)95Ep zvhpPS3wGlrmS5r`#37HGZ1!(o$NJ;%thkXku)lAGwZHEy%s2n}JB5D)*Z)805AR2R zsN6Vlqk4Lx!E&jmRu8K6o}N)XeT7mX26ID!_-9ZooDz=enZCGZP8bzx z^|0Dm3~IHoHfs9J?w(nt8RZ~9s(Wsa_%*)gfKm1SYI(`5MtxS_(cxgZx^&d^U~$k> zu0}m!u2`tl!k*fadQYJg6UE#;QGK9i*4&=XpjhnbnLfR{+p+%ik=36%`KX@pUB`CK zoYymc-f^?L+*>W^o;i8e0*7|-Ga0{d{P>>nle_26p3!mK2%B~bpfAXU-KD;A&-hyW z^viuCY&*MLEKZs1*l1pStsR}6T{C*d&*+#W{xh{}UINXXH>Z2%RBx)eS*YUYKIgdj zK_1;RzH`>hd0h+VIW(TrF>`8HEObWqaxX2cldTe*s0^e(2&^=kc z1NlFlGdkwZ^=Le&>zMi7bGzqt&zjjYep*-e)M-AKV6epCylEpPSz7T?VbScEMp#|y z*sf>%tVz?mI`#9CPd!$+`JMA-&GDs>%Im`;dYGQ^lV;7D(bX~2vDM^R^C!(1Y5$}f zO_?#PW2A3ax9Ohg(c+MUM%?Dujv4d2oO;{*KxesFt~$Q?{J`XD5OtPge5mf+wrg=% zs&|xJ)eQ|NHR|NO8N4Vx%Hs#`KX;+O%2O~Vcog=%MZ+%I;q3GQJAX-rKsqB zck0P>)a$OFYki<2e^R4XA9Q~@anoY4oC`cOsXj2NT+N5>7uy*Yi*v$K+%&AtE)4ZH z`lR{W2A)h5^WwfP`-RMN?=dZcauh318!klhI8)3CPi_=y1uuP=7gVFL-c@q{*5bDD zbL*~qXw$rE7`iu{9e<)_@u_(Xk4f8Z*Uv3!S?F!Jc7L*Me4~RN&1M8k%Z>VsLdi#_ zYCNams1K@bIdbpMWJ}L(bk4jP@nmi4j9HU9X1KJN(Q*6;XOpV_%#qGp7siH=$FQ}~ ztBo{Zd|FELrL#YN;9kvDpEJ9Vt2e4)qTi1F*Fq0(!a^@!LOcTrip7!EojNzZA7N7# z!;*8GTD>~rHe!xAciybd_&=Q;GvoQaPs>?Hjkr}hIdh#98;wW)wq!Fk?{RTl^o*a~ zF>~sy_&?nXyJn2|5_9lp#eY1Scs3ewt5`R}YE=w9@sQawelV=nf+%!oT`bh%cKM(l zI5#TAW36+eN;O2(u{f;OVq0vK1Qn(sH@ZSRXS3-f=1Es-zB-Ay(#8L1 zy4M)!*j z+gA*tk$)=|QLUX_U)ZXgpwKB#6R=*xBoePv;i<-?jkMX?a|*E3zj zU?HEWz49dnK^sUi5G?60h?!RghM{_YAvaKp2W)SOrH&M_6`32XjI3@v^b{*Wy?Q#@;Q5V^FXR%o0Yn;;Scqh7 zgNaP$+=ft%ZdkF>PtYt^z#B@i^lPw40c3Cg_F~+ns7IV;fSWa&Xx~CAtB` zPOT3tDCFZ#PB}0SkWx)>!$_S{7z*>LJo$6ONMa|eb(c*=8|Dh&cgH(TfDI#zmKKux zOtB3iGe2-_q1K3hL7x7-VPvI_M*Tbq+b|-#2ji)JdUx{YhLL29mC0J=h7-k$dzMrx z+JF~;l}SssLN}n;WI6Q!L#~E5pkyopTQ{uG4JcMDT3a$>XafpV3+%Qg3vdGpMMQul zRiPVDY!b#`3D)=q6piQ$ORz#WpjZ(tV#$o54I_|4TT+>XZWytK9j1S~h#F=#oXsPU z!K0DMo083$lfjeZ-!!O=&=odPok-*Iv{od@hETy-y(h(HjbX78N5sFQW9u}Ly<{6>b#PVRV84Qp0i`64C1o{rh_L7J5pllO3 z4z11WH*J1WN!a-T-SScC-y7t$7^9RFw}A&KYj+6l(EweWV!f56$_C$HRtr>(LCh6a1(tEz^PYj{jTCH`k|p;qWEx&@z@1VOP7Ww?NP zxm>Kvm+NZJ88kKF@b+(B4Rc{(u?VGg?0A}~;%=wn5QNs$zCWmGZOKBC?XC2weO6*~ zLfq2P6J#*uf)eO5a%6ciCpW@I{ITS#f!{M7EawY-OJVib-svw(Z(E=5iyu|DMLRW>Y5js2Yhoe$h_kHx&WCfDDkh3wHb5dDdB zyWaAUd#idi3`JKOYLmR$C<^1t*{*LQimD=fR!n2ux+5lv+I2<32NhO@udq-`X8^)O zqiW^K<*K6>)TA#q21|O~OuBG0bLB#@;aqiGj*brEtcArer}nm@e;2<+7g#MX@ws6l z%#|CZY?K%}t8!5zwrp~a_h=5iF+oX8g7U5t5Su2*=cOBPaY0^|93@{h=(I{qmd)4 zG7VKPE7U~|~t+m*8qfMHk8m1-Y#1aoNQ8g5^1NrqUmvprd zmg<4?_tlE4AdODPNm->T3-m{B`z&0$T}da_6beIPIWj1gBV1iL*eklxiU`dXh@&4I z52`m=hWCAPgN0g7FPaKpsfaRhBar5cFo;rX#I;LS9JPLFbSncZ7zlm3fnv}bYL2Du z&R=dml&!g1LRwN4t8atlyqn$>2DM&@UbH+I!_MY8IMlN(s7x;MyBKuY`9BO(Nlcd1 zurF`gjZa#@_5M%{F2!V!H>x@P)GIQ>QwA2-Z@_Hb7teQm znkyGzI-$#QBQ}=@M?t2KES5#ef=m?7&m%VOl$95odyWnLXCwU?tl54~bGF~h^Q6|v zc~-!KlPYy~+6_zEwyTRWUtO-hT&{&xy3s5oQ9DSypHe~SOZ>m}AioucX3jF-> zCWrFU1a(o>gT{QjjcOq?hf>0L@M{Q%kK;V56`}Nxh$ZEsn{%ZVmwIaLpb0(Sn@JQ; zzN&$?nKQHHSZooiI9p?6Tpo9(Jr25eP%{XRFUTUoPq*1`%HcW^q%9+V=_-?_ zUQEb`L&}2Fk)9IrVN?cvG027e_&%Lu1(->j$3AQI%{*#)r~d7%giPl*eFuYzZ7G$c zH%e~rcVfv`gUEb34H?evLDCyWYL&3wkCnkfDX2SsPzhf_IaYb$9@_u&GSzg7C)MO?nHpUc-FTv(GE8G7`#2xrHO;3%4F zzY-cTiP%X!B#s%>F;#j=RF0GA&i&cseGL^xKI2OmRMcg9J&R>tJbxbAcp^H+Qmj2y zN2f=^DfTc0>3j3KKbyn*Q71!gquw_`S?Y3+P@|N0oTUiLWRqsLJ&p#I42@2rjNF%4 zT9WC+-Ciu!FgCXwCbisFDWq4^G)Otq1J#{{gK5;%99yWeUx&>1;LvwA5hQhDy(pCG zYSG=F&T*?yQ+uE9&?E>K7jhnn6$~c5+r3s8EEJ_R>*&*DvcU5-sV+CUt%{?*wWU8Y zx^MR+3C$dpY9Tg@m&&OedLD2#Esjzdx+3lotqdehDS60*%wwv9*EB@Iyg<{`hhK%iAdjEAo|I{ zpypllqy>~4^pEa%6fGhunQF2|jV+nxZkeZgUA&S)8m#bT z(JG={%j%FowV&1bQ8AIV|2%LXnOYI%!`v+-D_P`jVjP=IGK5J*+LnaaOoIqL9JHK z6$1Ggr~)T0tzL~%HFWE8bxRq$qAYt4|7mL$*Y00_95j(zTC0a{zKWEg4@o?nd-Iec zC>F~(pHCLi6!Mrow}!|=D(@bft~~m&Ry01ZqXmt}!%!@-<*w~mzvjxPq5Ypq$9$Zh z2BA+&bqP)_dkb|V;i*U5q*(H;76P?#*X1LphH~StPeb`xH{v>n9OT`+-f)@u+N~X9 z%=a{o!Dcj{A;_+v}q8ntd>#+ZUa-^9Y=> znme>Z4gmM?=w#cCG^NW`4}$)nCerM)_e|XN$nD^C_ZiI^okC*tR1ss>uuDMb2pR)xYQvRT`PjVxHGJ|ydImvw`ner8DuhCzhcOC138yn zQMzrZoKF-X)zhKDY)pDolhvIjaGGr}toN7Y__nyLb-WtZ8`Y9~53d+w4KL~a9%61- zlNMZAp1b2hCiB=8N|lDPV8UQf^=FRm>0y}Mw9xo!m09UB;k25ECpQ9zO$1^UUD&Zp zT(^*-AzVOCA5BoHXdO#kuTERcO^*I@Ll}YLTo^xwV$dk%`Y8#r#~{;0=hxOf5k_-! zr))XR&BLKE*Qh(KSu~TFkJ52GvdP@~L~3n%a%kWlDP6u;TRLLjW)S}aeNnn`vKkg; z&sXaoxl7F;lGy=#SQe@y2j_FItnxuM??uq54~rOSyD7O&xtF1%QkfDD z3my6Vlo1Efn5VME6XM0;D1m86AtR&0@GhL7VpCAmqkgJijuc49D7++^M?emKlV&cP z$&072SPo1kh4^s64Z>tDo{^MIx)L zNA8b%FesF05qe255H>0?p~N)|bc7$TK;v<Nf%_`7Rp>|@qU44U{?7Q^I_F=MoC~H5aq+n7joFugtQbe&Xji1tN zQ6ZV*xE-=>iRZ=DYQxejSt!dyQFZ}xRy&LYmlN(Im$RbN3F-_DBJjKn}W>h zKR!ppj?Yo%@!7U7nb_U9G*~NzA(=p+CAlihDnce25<^sr2QJD~Q5FF>H@RUCUCD>9 z$});*&7Fm{rQs80HhfeU6<2B|mS`5%7Uq=h^s+r+eU@euwcp|42KR8bQ`8RIDO!i^ z6t%&2N<0O*esS7K-jtwAb)l<_Qq+*?R1j~eA`!F%6v{HUAO-N_Ech^Po2%)-MpY(@ zbcs{MV{2}`Ga#3~1Ve=M)Sx)KpkSSHg0_jNh{fH)pj?WGUvbqOW}5|5eOcXS^i>z4 zf(#TlixaqpeR%tV!9sDV-eV#bhYflZ%TiUCMM%0P)+|Dlxh$7i0z{;fn%ebR>~Tp_ z;KNN5MkBz8gm9I(Muub>5 z6;=)Fx&C#FQ&rj|Fg4^_OLI5oh7+Z}qjd>Gjm+Xw_d$~wcSk}C{%HMrAGJ~MqY>zR z)K0yR)~okX`}95Au*0|j)AgRAwU<3vZN>re{WEe0khQ>0%Vye@)V%$p;&VYiAkhOVGg0p zQ$h=iPM7%`btd~TFCc2e3y9YB0wVtxkj(!K-Aqvrr4)_zc*291!~wgHTZ*+IqC1qs z!y-;ge7Qw0G7#4LtK~-2-^6d0ddpDh1x52{7?(NAl(l_jRSUJ|$0R{tM)i^MG*;pg z_L;*nt96|c%yykP4!|aLJ0+k6Z{lAP=hZdQYd2HikT)!?2f9HvVqNXp2 zihRGP)R1Yx4lBl+oHL|asqHTXp{znp~9dre+3ShP(RYXMzN@p;F+|9`w*+x z2^o&wFT=l5ZW%uzO8Q3}2iB5Ygt8WJ#96gTKvLT0cs_+Gq);!0P23Xn=B$SC97e`g zTfAw3H}_yU9~5m~1$uL;U2dY&wuxnlEbu1R*(8;k)PyZDH9s&FC=3ZpY|=(G@s^El zN+%Sm1mZ{&TbQmS>!3;oiE1-tlMMGmhba-V)g=J&Bg45&Dk-_>u_`9DELX*<)JKdo z8dFx^9#NkBL_E$XL8ZyWn>*G_-74}fl`F+?aac4ISQe!vKJ0RPP);5mN5nA-(2Fzv zl*K|N69V#~le&@W6H=oPxvGG2$IK}$1X&uTP+z?Qr`&Zh+?9*MESH_Qx|rF~z17T^ zq&KoyozosQF*gk?QKo@Kah5`ny*H=#z=qW(0lG#~!CP4CQ7piMV1d+eB&wTLn*(I;l>J4n026#9hzJ-Z835tg?__lK1!^}qFR=v(;}4^StYIE zdz_?}CH?YBV|u3d95|`x!0FRjk}7w}Ua)TV?&*F+aTg@I?vooLB|vPSOsx;hiGLMT zAL14dX)>z_iz0RcOJlD-(2)RHsO3e2J2alEC`k&uxcie4Kry=PGHtql^YJpRzF1x6 zFCsZy)~5G5b=KgnhAK0q=uKNbZaI;Wb>b@7p1T~<0(H=!F%Tz`Q0g@jlMop`U{O1t zrfV>&y+k@ed6>7^QJxh03o2A`yrC^2De&jkJ9vi|f*NfQCim>h14L+UoCp%7X`!DG z;g^xwsV(*x2kgUJ4ULJT1QX1UkS1YpzZ%@>9L&u*Ye@-;sr?CN1^Lj$Jys*MW}~u1 z6?n}_^OlvU2YjYaN|Qn_89~J$bx5koG-6`Lrl3S)(8m%~jQxeYal7)j50OZr+T!9Q z?0q|8BrlAmy;V+>Xc|k2&u~i)Pf4hD&oGoyribH z%;)vkBkRd(s+O!I>*QUBAQp)kTTsoZLk${(B}<%|xycGB&m>Fi@-4Oyq3}%9{b3M~ zc{Q`2G^OY&KAmNgyz~vpch*8&@_xkY$|g*uPH!Ot1>WR1dPQyO(~G4fLY2#neXc8} z!^sgQ%CKT&p|2Eqo^w2%3S#RgZ;23PNs+ezOpjWADk#kOnaBisYEiJ&$0CYlX|^wh zZzJwK`|6>sTy!rzs@zwW3KbgWp`kG2Lz4pm9sZNE^;lHU^3nOEadg+JHH>f6TM0cMQc!iZg5xP+Rqwx4OX$u=9OE<= z)fE-h35~ADf@Axw3=j~B+KkVdk&=Otfn+Ff2%17wL~RZ$0@~Y zlSo*dIw6->bhgB)8pzD$x+p!YtkxC|3Cn8W7}9FecTle8n@nqxC@ZNtZ4?@hb|R^J zlR;t&PV2Qtc;OVWfoyo0s=O_-%s|$b;`~Bd*CA29r}fE9E44-Hpt5bm2K-@#4oVBh zVUPa0MfMej!u)!Ls#wN2DtTs+YI#TRq-`xxF7qT+GDs{2qJ9fYJ1O(CwQI4BgH)}lnEVel% z=FN!Bwic(`7Tji5tFKb1Z!ZPTpi(ggegy;dVAalawK`gqCsGuQ#fr2_Qo|)b-TH(D zyjImhmdn{7Ekz~EqOHW}rO~V+*8Skx_Sn|*(AxFL)Y^y$c+;oCw~{7n#g&pe?lP4i zY%P$GL>xC1Fju7-F1FYt3WNB78#&^gP{Jut$oDNxTDWk{v4CY}Z zty4^PAu4272T&2e4B7RIonmTvvStfvrxQ0@Qv{7CdaOX^otyufzZoz6YX=H9u=DzS{ z3Q$X5>3o~`Ws&&PmyMkay6rYsDKsLr5NZy_u$Pm6seRYi`pP{d!Y@BWgS?YhZmBu_PJ%Tw)b)a%Lo zrXjp4Np7)J9x91CqDopLqr76!EBXyyT(rcg8a}Iw!$=!mN>+OH{jcL*WQEMP!kvL; zckd7)dHhd{yP%{IsAb=VM+S-3^3pqLlGC0Wiq?NaQMMa09$o4kP^I+D+?@lh1QbfK z2GzuA#!0Vd3o@!?7TEmk+M@@? zh>~V@my=6c6RR^P3VM%ylLWIolc+FXNO$X+Qj#KX0Tix27*q#r8A-UhENq9A)FN*o z#oa6?Dap5x5IcVd#D;azG}ba=IZ0qDPLrg z6}w|Z#kd}-p_aQ~I{T~K*H;TkyOV>zP>_i{wXqFqcf(o|>j$LOL;%1|VS4rf1? zMXpv3RO-}b0kYI7uWAb+m7+XwgJbnch${7o*EfX(HJQ*SZq{24s`+L)vO-$!FA95S zka0&ZDU&FlIe2Rc(;{$l;eqHt)-;27Amz_>PUoUmB}ZSwG!uk zSlMur((%OZuSlF8PrQ4sVMLmCR||ojw%0J?%ucg~xQyK=R+LgdnJp&kx2lF?xu{~; zk|aYetN7WHhLm-DV3|LweEhN0o8JUrf8Jc9zOG(b6t|;K*75W$wHJjM6;~W4u+Cc% z<(~HvRMEwxDe5J?!rfEUw_r`?BgE-CBSF47>s$xK+RN+}ljIMNjas#OP;b-{ue%+b ziDKn=_l(PIAwsQ!R|-S%J8UUORzSJGEdA`6-Tm1!V^j1hQ~L(4wh*E7F1M7C6gmm1 zh|qfN+i6ttI=hoB01nunH*x7SQEPWjR`SI`vC(|tQa5EuM*7qux9Q!@@fP%jQrsyH zCMiK|8C0B6P`(fqh7p1ye*wv7Ma0gZmBARTEuTUMNNB38HDj*rZ5>_PA$@t%-bo z>fDQ_^-7WI5>j*}bHP$pXoIxmX}6H${OD5==^A7!5y|qShlpfarndL!$xB)wan}h; z6K@i2Wf41vTq$R7m`|Nit+Ng#^(ckEZ}OpGWLC<>LT>4>(omdDQ1Y@Z0nRVFiCGyW z3hzaHvn@s1C25s*2(-J>RuX;t)7B!_s~S;TRU=xDYDDc)4V9H-{V%Y;tuL^D1TV1P z`vnHYka}#xW|L*QtM)P3n4AoTl-J!>qM$Lteq2mb}H;^18{&cE9LKwH#H$T1{l3&Yz$?iRG#|3o@Y5xk(^b$u3ZxkX{)6{^Tj?o?V?KE8nOT zv4g9HAe3eoBlq9e!{OxU;;e#(;m^iv_Q?tE*&$o5#@J#i7aPOr7BYG08rj9I?|Y5f z`MpLX`Cg-Tey`DbKcA>=pHH;z&nIfr=M$~x^NHH^`9!ThAJ$o;I|7WmMEr_pYFnh;&FwmxLk$5~E0WcNi$$BFzZWsZ!F?ElNtm z2vQ;{AWEmCFd#9)FmuoG-gVbq>wbCvAKnk(`JH^uv!A`sKI1Ucv@Fs2n7}_|`K5M- zLV}a+b8-<2$F4rp4YVl)IWqsFZ*5xJmt^$G+?9 zZAx-|WIMf#M(x*C@&=8oW;d}SYv+Hu@tPZRJz1sa&3v~uKjWoL%%@DmL_Xl69D)AW zD=^GgDwMn5!c%_U^@Z|Cb!yQ*!K0K>yPhnsHVO>i?cTTUIU_o0O)Fmy(l&j*dQ*y# zOV(;=w*Cm8X^-RWaF8ElZBWlBJP`68yYVnlEu;CFlv24@o?8f$dbL7UnRXt<(AAa| zhL>$OS09-Zn)iK(L*0;<4XxTtTg-E9F7$bPQ@{0Qjze&c&e$6rehXwN?Zb`78Y=-m zEJZ2T7Id<2mg$bD4KAxOroU&;zW1WCigG|flo=+}dl_n3H0;!SW%oiG?mEwIlBsl^%z#Iz?MVzd0Dfme$AJSv-IEqwC(=Uw4+`&F`1c z_#SFBhw+mCwLIM~7)Vb~xxhvC-mtia2e)#>C2+3x`x?F3;Hw^m4I10yv;|sMvTO(X zs|y^$U+H+h9=}k^V2^ocB*vNd@VNU{IPvOG(Bsmc`~9a|I$y8ldpMOpDY%fHYMOTZ zaK%-9>s4wKZ(roV;FLki1C!Y_)j01pRZ{{78D@13N3X>nY6n zGl&YUn_3TJ>Q}#uSV_rl;HN*-5%XAm=!7r2V?~>X zt9CYgU|o#{$(wM${rT2g?Y{x>VOyVW(!UiKipaNc79?r-rtwCR)_?LyUwF#jf6=Lh zf%S4hqG@h615F%}%tGpUrUwlz28jW>k+p8{U%E@vs`#w z&yIZKHaptH;4ytt_EMyrzoOlU-;6{uo$=zdY|~}2>+8uv9@)jO#v%$XFej1}$C4(y zy?i~Ge4qbzEyo7A*E{R?MnQBd1PgChq@B!_CvFktJ>4i{Ui(5U?a$L5KJaJhqu`I_ z{jX}tf)X}YO!88^-Lz)=1S9th#=hhVP4@pxrjz#OXBW46{{y_`chkVQ?BA=Zzk4Q4 z6I2U(2;5u|h7p7K-%acB>sb=-OR%%emYlbe9&VIwk`ljkur|H)utmei$=EA(uk??x z&`UeM>JVAo{3RRZPwFQ_E16oKUj`VjbW;sFa^a9Si+aX=Ty;%11C$3lbyD7a6=yPe zXu}XLTDdLJOZy|iBv;o`Gt8^!AqiQNK-K!&YJizka?wwGMZ8T?t7s?Ihei*0X2xhw zy{eyoa({VE=IaFSr5_2)dBm-z+q#cbZ}y8jdF~2bDiYdTa?dEbD){+j{?*icQ^!$C z4aWSQ(>jg!j;1L&yW-_3ZjpOhz78?Y)f)OwjAJ8Biv?PR%6mMkZO&?3Ya<@ck^7ZK z_{{(zC~|FrHv;Fl8QrO@&}{S+c}>PAvyHi7-`W35)=kAj{L872MWSG4n<3SE_v z^_Jf>dUrst5n+MUh?v_}`m0wwGMdN~qoH@NBYrr@{Kw+d(B^UwL-iHK7}@rH?Y>W^ z<8Qy)e*9`#tCm_sq%-}B)QDw6;)DC{Xp~B>aAo6yM9$ODmbd4{wDbgc{a+f3DkiFE zIhXZYm~XQus)ae^TvwJ)Q;#udrod3Wt=DV07?gOX?(f8bOTi8KtPU?@4#kXuGxCBD zhTme&Evnr3Axv{8C2GG&pZ%a~jIFVnrN%yj&oxa8yPTx^QkqVeZ0+NQs2)yOEPEK#8c6vNrIF5E zyschKODFh4;1_!0 zXhpCvkgG)xJ9^M>e5eo`|0BZjRjG_pk|WL^QDrdgMn z|2#|2Qkcz-dGFU2)9jb4k798=BOQqhpQBMfX$l8B|{Gha2%Qs$_f9U2DQ!BlAAJ z`OIqiY=qilOR@^J+>dV++tr69H-5aNpFNzsmCR)GmZ8Cp*S_3@H9yQ@#gLOuPXHNM zh0Of+QtI};O-+F+??T-r`Zd(6q)SmBlhTL8uYoU-qP(*)A)tRnLtkL<@r$}Me~2jW zdHZvP%{d#aUE%pT`o`@8LqXh1f*(2AatXro`**#|hTKoB7q3?7F;4P4d^a6EAz=JW zQG5ToXNHc+6^)mKwr*KplhouUBP~YKW;GTsto&>;mMVQn>o0QyUm4MMA#a3n?5lst z^P+z*7uy!{uuV!@s#weqixe7UMu&oL3HV^u%v|60NOZ7kPWW17*%FsUG*xlS7t3gc z_n&xt<=%a|n;Q7{f?@>bx$zx(o$QwcyWEDds>ViH>Oz`&^SvTB&pk6BR4__)lHr%g zX!P4{xiaLei#dCB!aRyfKUeuVEA5BlF>DuM40vz(F`D(|ubUc9PYy)0=#>^P)hqq& z)K(BHxbGiR{_s3HpXM*}{}F=GIOaG%V_%?r}^p1*{TeE0tF*4DMem^R~47_h>ksCjmMq*1!`MX`#WR(ZSu~swDlXZfoL4FK;EIbEG z8?}v9S2m764H&t+QhU!HEl=G0x}<~c5)-jmzD`xM6I0qtvmdt)Vnc;IzWgR)iPv_q zF}mrlJGk0>cxnH z1;%fQqzgv9GEN+Q8tnI>TO(MIR;Vi~Hp=L(!X2kc>djE@+g5$#?S#ci%Yqk7(4Mc) zMePYpM`au)#=6pc%5h;yCCXi9$Y<1k$6AKYb;@_M;yh268}YM6ugko~RGWaR;75MU%yv`T{<{*(~SPJ5;M5AzXk3~Stx zeHUKa!@%b4E8z@b*?z+Z#ZJvyC!>FUshFCty_d9(?cY^&rO^3U zp3^s&m$>TfjEW|8HYYoYO~~Ak9q+W7yuagCoNp6#Z0`Akv4`iLbk~)|Qj!FA)^Xj5 z+tfU#w*_sL#3kN2{urMns+_bnVY(1wuq`Gm?NV0v_nVj~?_)gb)m2kb(}g1)L4sq? zOe+N*p)u}zp0WFvnXk4`C1BT;466r|Lmy6iDI=xMr{3-TOMZ=MD3uSj*Z(1gh~q|1 z|Jg;}^hw8pkXEYo3!HUJTvN*R_YB-yjqC{VE9V2g0z=KxX&wYi9;{+d3Ni#m*FJmb609&&gRpz=T zPqK)=|Kzt*O0RizDQ&f|wo5W&grT)HMM(3@`GvB$ZygtV%iTQl! z{d5H-LXv+|lzc^K`UmK^nfFFq@xfV;X!_6T)3w&%qi^&=;_)Os_Yx)4frnI??B zdXJ;@S1T`~J0Bxsx*U2-#EJa(B;P%pcRILite+O8wme+)*~N?ZxlPAkpV)8pUruQg zT#@T_&!;v6_I1;%eQML+q$_)4Ka>}Xr!Z7DZsc989!hXZy!QR@jK_5NCED_0L=In} z>gf}$#1~%#G^u!Wp4?T;m-ragbJe&C9jr=7BU>kg+O#bjH!28Q-YQ}@dLjApe0!oQ zfAP!5J!V3comT8>E3f4Q(~7t68*S93Fm%quh^MEW8oIx`(nL#iV1<08JCJ;{CHBn= z<>`Qk2KAK5yR$uFM``x=5>_pmy~=sgTV33Qywj;FRpiI@QeDh`v3xhMBSKQR_IA$J z&wB3%IE@gv(yTxK?I*9~y14tgThnAykmALng&u?2)ME;3PEj#TS&oRK85wSRI7NrR zOE8bMENT<4l<&J*ic;p9@g5R*(Z4Gp-ArvnQXg9SC+JYXC@e8Uera88M}2SH;VfmR zb%Lxm_s*UjeM4FExhJ(tn|BpvVzi_L>YlUh?ERE%U|+vVm=P|0Ag*1_=5^BGAm3Z+ zQzf=2EJRV{u6~PV_r8RmoPfr|)MM+q`6iMVW)x~>tPE!jV#|8%sUKpl6ztc%QkZ%D zN#$3u)GNVqBk7xH?sfs=E1Jou7vz^N#J&@xuUp^{6(Q&42A{2UAw1oTE$tBoE&(O>G=vAs zS`6LV1{8sB2OaA>#tN7%c}T0)ZoC&z*rhM`Z^l+5TfU_zy%!3297EdmPFD9-^oM5u zMKt=4?^B+#aoY2{?xutbTF@aRrW7QA&t^XtzT~g*b50JVzd;`8Jggq z)IXqQ{^P`2!+TGk&tY$>n{=2l2gTJJf_t%JX7>JanaQJ85o3{T#_t=3VWH@?T5;(X zE1ETa*QE35$CBAR$#35`h zW}YJ&kyg!Al`CKG&84|WR%(XFX?e30C~MX{@wQXR|Lnvx_TguQmGyBr>ZZqKwt;&V zZUm0xNz@;kDos8|_r?8m3NT>$i<^sh$Mrq!;%DJLyboEW)en*Ui&5betC43_0&3XO`Np!Q>Kge`b?nwMUu3Kx)8;I_psT@F*jxT&KlcK#9xwMT75j6JuO-4tP}q^^QMo=1iG2H zUCC#J%|wsojTvH|A%=X)w?R?u&^c$O-Qycp@WbjYm#G8A=fcBb+ZoCBB3-K~Tezc^ zg66uLNvL#l;Z@?GPA&Cu8T;??m#mW$HVxN3?yrmlQSf6jETl~ z8Rw5xGIZtJ%JjDSE@aU^`j8}O6R%5Cc>l3mMJh_RMm)!(g!<-^hEoRj4pLlzd zD{qgLS3Rw#c_y>f>-45fa5;$EwJK!ek(I;sH35-^NzxvNG?S|qLL^b+Om{z5O`41J zaVQDa3h}??{9NHeJJb$T{o+UX)6w50?~Z;Q6!`5UyO zzTb?r8)_&Y^Qb%f1fCr#rjI-Jcu?``zGJWYwQdg!vd+=-Iq&jiNS>5@)ov8h$kL{k zh^wzS&R`gUfBb1=QUiE!9QVK0u4@`&(?M2I`n{r{#O(6wlkejaq>gnF6PI_<`4vQy z#;wV|_ijCr+#VItbJ8MLP-*tpm}Xi%vd-o)qlImpnlPxN=+qiA=o`1~C z%=a0|W}ZSWslGRGma_WdIUS#v!Tp$rp76*KyPq%R*6A*n^yBF1>Brxu!aGtr_fE&f`&oXV0;)wr zSh+%$$2tQPFCo5;j#^g6Sc{Jzmz7Jj@cSIHzMTsl=)Cx2XI|eO=l^E}ua3M?fON;= ze;Q-+{@NEFnEpi`BuyP)@xM2w{;c9Zw_42QHMRXc8t!z*1^?N}Le61Ne$7eYJN|DK zz85*(W?!h^R2@82R(+LnvQf{k75zZ&V%6>&)UW$Gm@NxqTRixv2j=5H4Y!i-)xRWCw=?VmWaN&AVu5p9Ahd$xo}i#wdns8J@@UPh7;>m(V2$k z82y9HMLWl=K19{zp9CkM6Mh(f%UBH5JN+6yciXFp4=dX8`D*yc>*EJxbLq?5%9!r` z**u?g-U_ z)E93v6^u+zT{c>CJ-~!2gvokD1J$WK;!VGhPCa5Ao_G7$n{C0_98cDn;P?FuBbRyg zcbd<$E8w%ij`hW8b0kW%Be+L$-tbDEeyQsh%~do+?_ zL3qvo4R6quT{&HhTX?4yCXfu7fNsVTJTAtZkg{uIcADq-f_y6wk!X2R4oyrh1b9Nm zp$cpc0G@>a-2p&_v~AxT3nzM~5#h+^0O5|NA7bHz+J-X-iHafx-LIj}6C9SN2494L6nw<<5uILz&VlgrWn<5b1j=6qXcWl)_YLXK!P^hwE|$U0BEoPvQ-_wh%}?tBjFGl~RFuPSyjn z)i6dChy=7__y}KH7CItamp_PlL6v$|7jpq69L}MQp<1vXecB$6&M-pCEYy+O-p06~ zOld+cEL4*2*tbTYWsLAQQL8i|5(|Gw0e?ZL9bPQeH9GdoShI@GNHkG+4Q~+V!U$~$ zD}*Np;NkR9s@mGSw2;4;aCdD?A1TKjKgxv%WVT9xHxgjqwsbUE_$q0hnqMw}X8ZYx z>;mVwt(qUlLL6xl=?;He9{T(O5ugqc18hr4(~*sCRY#go;dGA^O=xVma*e~&$vR^} zrwQtiYrr{dc!MMtBmuTI9n9HqDN@@znER*+nvQTl+?-Dx9Wn#WS%GI>&?MuK6zSFF z^t-;m!5`#|LfpnMp>%-)7D}l*7#B)u6%xUMz_b@o{4}`RnP@F;Y*2tg9T}cy_X-cz zFkFl)$bJKHLWW4*FeOFAp*c|qZD8FIz95-}Au_<8On40sRstH=3K$nFhXTfB4#<_F z#hF)hW}-)^L&O&H_^|{iQ>u>0ImQKNGCU!qj{wkzQ%2cxV^8I7|JKt85VD0O(#{mXAJ|h&I5Gkdh)hNQ47XLmD56ZMJEGD2Y8=3Ena69W za6W*;&TOzH#L4a1VSqaA0@dHbsnUI3;yQCcxLJxZ^9c&}4W@ z4`LPoxbqKS9q{eeGMrENoHk}S9G`+751+e(X@@?1HhfMM!;C_zU@TBO{8)b!QU@~> zjt>L)5K*TH)nbeo84zF%DPWKUGFU?f9N3+7E)Gos9rzBkauaZ1>^&G^WMPDf1OO3= z7%mhCFHm3~DIidK;Z8UZ$u$&G7X+9*o*<%yop~Q&6y@u`C?TLQ5ccq)=uJ{QH&7vt zZlRJ4nF$;i83`6erU3>KssJD`xIfDFGWH<~uY_?9$7i7RB@D1)=aTra$|w+^c_D+Q zz{EJ{!3vSH6VfQW$^kzWjuPrn7a|k`2yy2@3! zS;(Tn$t@i1wSbcT=>#bV4rPUgLEb1;+72RM=MLV52c$?A;KxW=fU<)g2ocYnU`R`f zBpJ_BgMo+}o4keR3}UJ1e2(^|0aA1&+u;wA1(C*G7epFas*ZRx8=0*RhV@v~1e~E8 zK!^N^mY1>CFjRl8!iVpmj&(7u;0T#ZJ;*~JQQ^)kJb+IL@TnF@i&H?^f{6Sbi8{J_ zng>ofSQi2w5nW*5ES%v3D3HK0Fv#rmfhQcOPT~fK@e-&;JOxvog@tHPxV>={Ns2$e zFhq&~AK8b#0|Q__=&pPBlpiY&=B2a;g*%`I3EPw2;y?`EL@XeRb^<6CRKe;BFF^f8 z(3Afij&^q77$MxtUk7>-9pqWk&h~=k=|E#msi%_|isQja6uKNv|KOCrEei}>2M-*u z(*@8e>Cq{F5FePm?meDA=*AH~yz}<)+idg$Gdx)y+?EySv&=)gH zf%lq$21bDG^RBxyDIyWH{@ZFODA0nf;+Zz_E-g9%xI(g>1zaBbL_M#Mssrx-=?M}Q z*o^0w13dZVf{N$hM*+;n4Sr6*(9ZZ=UBrT==QQ&I5vbC=4Ounxg3%*(zz|nJ0Wl1^Ba1_76)``-=+2LxL*Zi3O02LE($owm zg@(6>duIjH<-vBZfSn_njxaFkA6QQa>LF)1WiYO@2Xv39!h0KFxxk|-ST*D-=%A{H zkV!*p0yLc|V4xVVERG)bhx>R4)~Nye$2x-oG6%E|{lsx%S-_KRHvl;YAO}&ekDo`? zK=4A}k-zTYK{e!%p}jAQtTPjgaR$c5PQe+Xz|#&~Sp=*+0jR*q!=*-!5mLQ1a0euy z*Blgjf(!W2D_YP?3Ie(Vdb!^N?W%B==&f-OY@u57;O_jtMS)=Z5VYQa8-vo61p|FO z@Vb~B(0`fJc1F39`eMxzmI;gc5XvJ{Q6P3J9QBEOa#FXRy8)AOz35 ziEpFB(}PAzxR5h!g!DfH(;y(9?to^o7wkG9#=^EfK#4OLhztkVRH5~+g6gEAkickk zALxpNrkR5q;sJ9y!w!@X5p*!{jn^Qu0^M~g{do=JE2x9kMMABw!UEJf0&-^m9sd_k z>1$7Ds{FG9$>VvOT*YfG{|Nt(EEMONx&}!??<`* zj}k8-1W#;xbO&&ZyQ8&uWo-Nwk{TflszzXy;!iwRh|M;r*99e9I%u@zRA5wz!4Sa~ z{lm^^+n1+ei^!qV&S*wB!nb#KfQA}ru{Dw&hj|D)DT7XUZZKobAZWi2suXOtkd1Ng z+dMbPsS%KajZah7`@oumCIsA~il zXSuco7b%q6(xOk|{9OI#3Zmx(7Q4AZlB1(y-={|(F?hODH^um|Q72J*a!_;dE+V2s zvMw+B^lp~M`pHx02`+YUDOmPw4xKP4w}nQFQSU}_by)SV#9*ALk7##&xMt0IZ0UFT zxH|NEvSKj|)b4zXQ_5J1-42XmPgdLn2c0Jp>|jI> zi$`s#fFpMiuwC~w(BNtZ8Z5yGb{<&u0#yBmRU~i{NFT`%AVr5u;daj*SZ@#N-I9-f z$NDkC9>$=DZ!#D(0~WxzdYvXkM?n_ZfvM!QK;xVcm^N?_upc#qOZS0oHlVGoX&Oux z4xsEn=1&H391lNoLy!nKtt}iW3L5xhMviWP1;Gku;qob91cv}27MLGU|&v9 z^#F9hB!kI=qCu~uR50ca>?IC+Spkg%!Sy(xoK&F_zCt-UE*z0lCxQ7-w1YmgQ+rZ2 zd`a$zMI8!zl(P?;ycaTu90qf{OE}I5cZ$x9PL9-lGUQhw(lLy)3hg7w~CF$Uj zkQ-fyoD9y?0()^LA(gP~aEBJzVq!0*lxOk0i${=@69`@lw1jkmj=Cve7$ro((~M`_ zHHS!V!&1~T&bG%Mt`h~xs)dtspCCTRBe(#7RXCIC3h2TC*QJa((qy5|@LC|`gHZLfd<>HWZDN6>Yi08qJnplTHE%m6%f zK*J)SdGa71r)iBVWR9kT3i{z8L!whLo|Af2VIy zBcPiiA%UmLxd~hyx8C6CcCFw(vfvcfa18XZqyb#|65kG8%&;|8CTAicu|0F~U z@utY(sCWOlVJFkbjcZ9@JOY|~1fr^gJ)l!@2-hHE{PIC*M?|5sBo+g{q=pMu3ZBK% zHbEaT@ECHN9UqNRhPu#%vhU)klZ8cU0yC5Q-Bi$ zZ(EVY4j2I^pcE*Sf(oOhz+q4A&I2O!v~Eu))$YeVoGP_D7f?l8IXp`qfo-+b{7A~7 z-~9!O)3%5Ba}w;rhwhR}ZP0oC-`EW%g2J$bo}2}Z%K|8xg+?cVTCs#4Eeoq)pfXMW z14_{HzR**;;X281LP$^!9R2@?Z@)6oVFwcOVfl%cyg7HxgIC|t=9tpdC);+KGv*Ie zJtq|A8XpV0(U+fYj<+=((!SE=8bVl9oz@sLF$qiWQNFy>g|Qo$)451^M-a&^b0%IS ztut|pvZ>K9hIGm^cgk*^=1rciX*z5tT`LDj`agMJoLiH*kAC4LqMA`G)vOIUS1|P?-O| zuX}iIP7816>%nr&&lp_jX$k%bd$yKmb*3W%H_$x)WMB6)K~pKx>&E-Dr^$6)Z0q>* zO$g~%5N{5|%c=OIAJXWG@Wqf+`X?Od29xHJo#QUD|H2GOqFnB(bScI(Qc+YW~9Ncxfp0`ET z0jAx)%W|3=chOM}Y%5}30Dyi9wk5;Js*PV}MPCLGo{@|qWXOX^P8mHE*(4 z16f_~_H}2^uxsF#59aTIy`7^3dn5KccbYuf#SKU%WLL&7+o2r*=&+@=yzDdVz#e{R zGeB}^AJ`N_5dUd%Vb{_+-rWs}K6lI?6}nRZ`OSs=#z1~`3jkifYY?yRDB>rr93XOg zzIXab82>mXeCNFg-K|#o+LxNQS zi9{jsI5Dr2XRBbu1-U%j0Ovg2F+WMDAtc!57TyklRy^iEdCK?|0B!@;-Ja(KAdf8` z$U?x55O66Vl(7wHOlJNeV2&7}gI@;U^u3P^o#JQ%rnKzV{f6+Tm41dBVCmBRitv~B zBLdp(76#A=LNaj!j`PO*x;g|k0Od75iqmA;?hNpJrluI_C5O2L8O{U6D;Sw$e$~*t zQhew8)9y{64&snd2>1YyONfud4G_#T0v(Wq#6StBLrH}Equ6vk&+4(>(gaX1=`0|O z5WNX-5_R5&n#h8XJArYppr`in{7ncy?$hL^u(^EX9v_+ya@?s2IX(hLCkp`#U78;T z=*}Xc)1eulh!H@vTz#{cB(6R00ugf*Aol{$QlQ5<4nQg~26%62XlEhP>pX_^G @4w#HO8z}f3 zni9GlUJbu2jn)N4BD@xUSpY4zk4&862m(gEH*X8HK=>J0fC?i8wDA;K3UrL2gou-% zEP{ZIx98=6ul=5o1_VXS6GFe!1^Z_VdI@D=2szFJNEk2|PV-g{Nq{}~Owhod?u3j$ z8)q?cr^yq~@PJ1?j2Mg)Vqh>u&Q}H^(l*BV+t9p2LN#E}8(15~S zxv(op_Mf!zs_`o$Jgv`m9o}KdmJ5QJmtNfedv#e+oSipqF3al7NA8}zX9LZ5ZE3%>_WCKVbC!U};EaI($r11OtA z0Fn5;0(4CZ@J#3i=(h9VhOy)n#u7Kcxt|9uRC*SORt(eexB>n7U;D_|DUL1R?BX8e zAfP-w3-of%O&z~{#sj$dX;<}H-qu++ZM>Z>8c6a9NmD-3>#Heq6IhK?IvO`XG;axJ zCwBwK0jozKjD50zR`Tf=I9=fK&(=Zgn+Cp;)x`;8-!=eoFfR&4AedJJ zY6(37Y9W~h3dxncpxGA)-nHTSRq*QEV~kZ*#&(UfDYYx zjVE+NdR%~R8TAE5k~A1e?g0Gka)7Cf`2_&_OcFRAnEiN8lMTBH!L~23|C0?e0# z5b~=FXwXmlI11y44p@!A?u#FNF#QkIb)@SK}S%;dBPvF@NQJ>NIcea3-c93cB_ml(`8uz=^_ zE-`o-V$Yu@&ve&(1Ck(5umz!t(+?yT|ThU1I7dTWF^J{adFO%K+Ln?tT=<#fhzWeDsKCy3VEmsBxov-XWjv3;FJcZ z5YfCCpoAO;5-FMaU>Ny!0P75xGpDP->nK6E6UI2h$Y&1%##sy%MBE8Hmk%QjGrBpj zNeV0*M6CJ`F-Ra{=2-#NPmw@#au`+Uc1Vz0T*27G0}+{zw*@H@Jk5!5sSq)c3`r;w zkZ=a$3lZaABWo!!OsC26-F?vSK7*#OZV7+Du}CTkb&LUNj(0ad^g9J0O&7Ey^t+KS zcztlVm;>m#=bZIj(G4c z#n0zXpwz8EkaC8%SOSJiL$bG5_;e zt&=bX^iIVJXd^_6fYphbfN3timj@E>W&_qI+E&9O{yVW6f3^wnUJ7O;)FA8;903JV zy$7l0*{&BL`9FJ92sHB^L@)X7Cg7fIE>JTU|C3nF`rnDw$Pp0wK+YwOwuX57fMb)& zfdqkIULE4qErA+$JLV4zEBylS$^lPM&U2OnCsV{cNXHqU{!3HkKoTl59|)+W=l}WXK~a1E`sR19%@l zMjD=o{_whGv;@w3#xQbthAV*c#tN+tGNq^BG6n93;09$rFR_n&4ia{d8CiC(!uz)s z$T2DA|G@h44VZbT8)P)B*5knC0 zApQCK$B3@qlpjUz|5Y8hcXyikf9KUA_x?@|+`c<~KJ8zu7P2In3(&}ofzun7L;%*CrFjRYP znsi9Xu*6$7?#=X@-<_^VMLt0^A^sb$fCNwD?jk|{6lw|fd5#rOa@j)@-fYf-(~-nY z8E?no0Z!<^Fn17CWZ#1uX`I6M_agAZdmCPO=Rmq({kMzNtm>-bl#Iq=%4O%&n9A26S zr-RPfByb3x_oIVXN0^125z>&tC%P zA;BC-8(h%sph!5Uinj&Xr2W4HDg*-N|95pHIOeY*;r|C1WM7x&NdQ3N5ODnwoc{^1 z6C=QGl{~=9F0@_V@E#BXUheUnZw6S25a1pofPS#w88yWr2X58(=KsR81sotUq}ibD z3>w^IPeQ>J6=Zy1#@zW{nDL3hdsY-P$PvLk05Cxy{SR<41S|%gZ(uxO{*?C`KLeOp z`In=DdvI14*wXBo5Re#db_)YTklBE{+y(R~WVi;TQs@07A;Zp~iS#pIoX`(wZmD|& zz`2OMa+>_0djxFrJXY>>XYr7vrVyFnIaek!^WfnlO{po=Z}sh7691_+uReU_2>H{1eCS=*0lphB$;^ zjLooTTuj!Khcnx2olJM<-boc;n%XS}Cf$+WLRzEdMk{mf znR-rL9edR2qFLi+`Q7Iemv7VJtGijJ5?CK=%NCP7_kQwii^bC$Ct|%Q2* zU!l&osC1s4;Wt8aKmD$H!J+LD+j?+-6P|MBUGF2lZ@3=IlM{g*Hp`8xlgaqV^W`_( zj_2IVwG+XAh9Q!KcBS$co3Me_qn$mQGnksD=)UOaW5%WJ=!yA}y55oYVS=x=9NhKH zvB+`vz__^zwQ(8?r?MI4)2ZG$;;h86kGrc3V>fK;gQeJH8igBKB6T*Gy2+8ozjW6f3F-jBK;Ol3kg zTzbAUy#t?P*3)h=ZjQrn%3l*9#wRpxhN{VOAHEA!bv?Wk>i_jnJ=DIiAy3rtvt%Lu z!ds5Sw@7-|xzIq{X<|qBZz_|r>8}&icJ~U|e~<+w`nsPG(E46fvwcf$`oiUj?-Yuo z-ul_2zKOKik>7VR>+mjhd4e|Ew%R8$!7i^Gld`DS*m4vE-`=Y~Ozyl9;A*DD9xD@f zUr+VU_uYhH+=_{$Qs6LG{b_h{^yTWXTdbg5 zyE7!`hE@KVXfDgZXQ?=x;7jf+TDvUZU)7Y_JXM7BQka9EV2#H&)yhkPR{O# z88q};>H9{XndfQDS4!D_7W(T1{n>FF}g>l1-5B2`hLi$N;a7W)(FdNfw5C9!Jb?9=;-(y&Kqj7bdP7{B;NJka1U8s z{VXN8YjWkhr+36Lfdy}mbo9zZz?e+6DcC8!vZKpr$bm$m5HXkEP*z!3>KT{a- z)%4fBQZ_RQeCqo1+x6*UC(WcXr#e&c+jYm5dtj=B?V{6;y+6qG#dg-y=hmw6R}wm@z104? z!G6O9sqPb$=-av@1(!2 zHT$9_HI2gJ7yE$P_(ydiE985=@oVzNce4BwV`Ilvrt+Fx+#MTk()!`*Xb67~Qajw2 zDezJ{*%1|qJ@Dg=%h)JlI&$TW^|zUuN>sf1OSEX(75!z0*MLIS?C^$%$vC$Rufh10 zQX%`o;E58Nas_!kulgG8hj@cP^1HQ#IcfYgF&RZMu8S9^YAf1ZhZwHN^|Rd$&XO;A zavaTk_$olu-ZieOb?$2Ijcc_k;4_{LOdbwSik5Cgr%=8U%!*5?7+l$`4|w78x6;k& zxYzu?OM1TnvBY{A)2Emy@mEJXV$Ifevbf_rj=z~Ul;$I|X$WUqZn2QQYK?3A>8D%N zdZ~FRv-I6hO64C%GQS*aSLB9Aiiby)DrSs3ZLjt&pyagc$^TrBJc^w54#{A~ZPkg) zOh58jVjKhqZ0FUP84)oS?y6M1xyCAIr0g5#-vcc1qol2UbiY>u<@lWOdz9bgRpcr~ z3&y`_$~frcN+04S4L-OxRrK6`7^!zj%DQ~nHD80R!tat$hudV*$KuG1G;-02may_R z`zu}@&renv4c~N#9=&}zwSQsxhQfna0SlLAQYL==Db8T7a2*;$U#J|MBFh{U%J&+g zDK;=Ik1y89&R>*8U%EGV)=>ZFgZ;54OGEJI_qkjRS-Am2Bu;IwCWo|s|B^dMZAcqK z>W&)VkLxmy(f3mWvNhTRMGSVVoQpz`)uUrOW`>BUu{fm-iU23y)PReI2$vEa0TG{d zS6P&hB)dUE#MrWijw1Rx+P9N)(sH`Gnr)L zA{mhz&KdMZBKsAFl2khzV`w6i-+`}ZOdLk6i|TCy@+O~^v$;A&+24HgWGd?%Lx8`t ze@=(>lvzW?t<7W(?domAR1?Z#<7mwlcKZn?mc0o{ow+xkkah{Ycyhyf?2;00hp_@X z*V%{N%s}R3;MGh28gq-j`;~MHhYS(_)A@Q8X||H|5XU5e>|rjIXLr*M-f|N;2zb%5 zNtfCJzVRTZT>jG-qe<)ZQgfwA03XfGDn_@u7K=bX z?OPf#*1KZ0qSvT2fmAY7yW5woUG4RniO+!cfn~)5)-CUim z{Uv9`OFNTmQ&n~r%EE%X>ONTwVWdXlMn>q{6&3SmlFH?r-iA4;5V14niJl}^RLN@` zjxzkVrj`7?llW?qoKNs~zQ##LX@1V%`urSRy>MVke&$;#S&^?5Qmg@n;jy0N~2V~WLnHGjFb+ORxIDz*po?E&e0 z8zM?GkCB>lB@V)|jn|botE=vk-`+=^k1JyJ{7Ek+cI3K%bD6nM5c6wu`8e03~wuzIKzXra9GY%Pe>Le3@z_vfZ^rG9;d;PFH<{alf1 zZoPvId#U5Bz;)X%lD{>)Pm|?x1HOF;+8fYd3hokWAuXt^)BY-8U7t%sQMy(gS?1o# z-y1TXpZ&YRzg6?pj=qL$_oE{AN6zsso_Jh$=%?<`aEQhB;HBBa6^2w~!nn~ae$vRi zvhuNkkfV|L{R+9NO0?n9>pIM%z?EXMIw|Yzs9QE5ct1iKLtUvMQ-C_8DQ) zJ~(++vQh4-;q%^L27k_|GXSM`x*W4-toaG!Ok(r(aK`VPB;&5%gBF23f6}d6Zx+fI zyz}0s(8t#vN7rtD9Xj4OPV*A^QZeKfLgE>`GMaVlA{@zI`lZHq*bWyMhR zXhq=eQ}daf2gW|QiWW)^RUZyHH>0DCB~jzj5izq~i`!MLRDo8Efk*U`owwde-}&S_ zzr=gds@^w!>8-%G%-|6R7yRj?LxO}gzF^gfzhF)P#{;`$o0B8<7=(l2hz?P35E4zc{>t@ z{OjfJhUM;$HcTFE96w~(emKk?x0S4Pco=Y4D0cPaNb|*MYq83qSlUgIpj89SjX3Ux zF-hf(QBR=*#nfZZ5*w$I+)cTI58uZ|E+1IhW|T{9ls_}t=+I)jS8~xax8<3n&p881 z`f}R+F%lcQ4b646V9pt}V7VE`jk#~t_8Z*wW9c2&4N2C^lZ`f{GLC&D4kUOD<3yUl zrpapUW4Dm;COs=j!yim^sXaGjf^UjHIv^0i5KV1li7Jp*OjNpS$VHlmDC)65Nan*GY;sBXMF!& ze5X?`gk|e{M$5fPjC`Z%CwHFm-Bv7NpV&a+u4X;UlGIn6Eh}PthB4jjiVIVPIR-;M zs`%%{pki4q`X`OqkH((0?F7y24w1RHB^_tWcWBC!;`Pdr7JXxs*L+@P1l0Z+^EEfb zU#cvA!)H)4Zdh72)+Fv@DbZj%Ft*5MJCS+nXovJk$_OsYmDhWScf4AQfBT~!H-4Xg z$4@6_+rnjlJ$6g~e{poxUro4uRKEr)0xA-cBGR3sry!uDbc2F)NyA{EQc6lUNWV+sk=>xPQQN&T~F}?|s#lk_`sLCd*`hIICgWs@3CCk{N} z3+i+cAsTe+5!&hnHTlS( zVlo0A*P8I2v7$IIfy99+gN6m`Z-Y*uPLdTIGdyvP?7ZIQcY}$m<^G+#gjy1YZZD~Ml-w0h|7_+s1_0rQm7khn3o=JR|>(=_)>>(KT zM1FyWPX3TL(Q2dNvKC)2epCH~$$qBN`2H^^pjWWUv) zpl6vk{aHDoI3t(86-)D#;QO5gx9~%FtQy%U-TwF0G5VAG7Yw=1m!JHT>c3<(<7~;E zqWWock588R#)?0GUq0LUJCEPiQ8hYfv*^dakoLg_Wg>o*i>HEjE;>!Q0 z8>Dx0J6JRY6~IqEb*RU0H9fhxtlPYp;P7}eSywC(dw))h=Q8f> zRRv;Re1EE^?c$|_-H)-?M{bFjl2B&lP4J(z;XD8QkoR*8I%%Um2AG#xALFpvWwCj7 zaAoJ!JDOg>v21B*5Et0@g4L7fvk7uMmc22_OSC53DrnYSboKDsuFJ9W zzDv;@7qx%%=PJBy5p>3e)4!@B7kbl_&Peyps%6zP!;|j8WGXpp?e7>Vpmf`x_Jrm~ zP6ty{5BqaQY)th2qElJ#S$$9iZ!??q-bBqEJHPO0WI!6I1+4wU+BsZYTqb|#j+(p= zL3?}rF-HnqkD0)~J4iF%@j3~x`a|acOn>O;Hh*q;^wuT1)+P|&LU>g*f~A4zoWtvM zX@eiFH>FW|kMmsnQSq3HQBYN8+Iph%{D^_Y4*p4jXLwH6A21!sBt&fwnWXp?fB(2+ zbaQ<4?4mKa@773AfP+5Jp<0fZ3mFS~jARZPp+&LI$0}*7Pc6p)3S%PFVjoBSs?Uk6 zZ!B!|BRTSI>#304%95Tq%jZ92w~@2qBK}tAA;xNs9HW>DhB`3z$;^Gn3h?6vIemAz z!}Q3=b_F#vv&YpC^{{ybdFNJ`@yfLdBrD*?$u{=pX)+K^YRP2&wfvzZ(#*WDCZ-0& zKg_-{*)~wz(eBU8*Br=aaYx_r^3M^&Vy5B4VqWFF-fwI0iZ%QBdNny!pK5?3iZ!S) zu?Tp&Kcm3Rzm=0;Ey)qn=QG@(LsqxDfRB35%em>`5lZP)b06cTtW};)@5K$s$$trY zKbDPuUN>WTmTq&Fr#u7%x!X5=w7O*@=R;N-R450l&NWV{%{`kvdv_m1e|0w?=GQ=J z!8b?X5F+rM|2>P?DsRtBXX%y>%?{F!3rRkHJ;D-z^QrsA{rVnXx0WmJZ{r`6k0K>$ zGkH9sIwi%F$zn9?@tJ&P3s}W<8C=|7Itf9pb(uMJprHL{Rpi-Q7du06N5Uw+xMllA zft*mJb^5CoNB2$E-_GI=F};|Ixb&H~%<1M+_c^Qla|&i|fv|vf+;rOdDFaCxG=2IM zc!r$JS+*>qn401!ISr4GBVfL;;iFhB*Rdl!@3!PK09|v*9pLADh#ln;YP6pV zx99Y;GRyhGM9KvAP}JH~!&6bBll%I^4W0AvRo)biXZ5>EGij)T+G;ZYqm&jNro#P* zmgHWOD*zqF5RD++2;Y|5@A=PvE{y!wRnW^O6;%s-SPs5Aj9|1D+Jb8pRQyJpHFwXyY2_Um*-Kdl7=V~pUyJ6R&DY+#|8Ul<>vP%T*JOZ1+(!5Ws3xR?hB1sLUEf&5%=O z4J%o%L@vyKDjj@?K{`(aT)h3I+CPB~EGi%lG#X!i>qVkX#0==g(<=j`KUNy^7_2j| zU@J);8w4|72zYI9GOXa%+naJ1J6_!pUlP1d?P2a;>wh63yIeZ)8l2$(9Q_+R@2b;% zqdc2U3|qZEcdoI^YYHT^h2#tJJUy+ns!wggTZj205k~`ROtB-PruTQHNG4qqEr_Q2G`hu$;1hoJ8I#Y1sxNlO+!S7_`xV9|CN^);uK!Qr5#FWsO2 ztY$WcE$ynV3>Oj`nGiPP%8L5pb!`NIALY*U_P8~>2lW3ZCFd@{5aeIA>N^G{(ilsg znnw;Zw>t1^IzJ(CmQ5PAFGc%i^Dn8TY#&^9tOygBB8NL#?fA<`Q*xFdrbR{L)@}YP z!aGde*6Tz{{*=+`=F=a;)K$M!T|N(O)}8FS_dH&%66*ds=h_!EYCT9|vs$FQ9@DQ> zxODW>_?b?(k9w0i@gNF=s!DHknQ-3EF0KsH+{Pa6hE*sRV|3~qVkSd>b!giPnfd-s zYAl6CG#*9>9?&~Gq;Vw-D`k6lc+Cwc%x|mzz+MCHZg*6rvOZZ>020A!;N{oTG;UJMBo)fS$p~&fGAa~k zirZ5FlT0@&7eIoDmTl0*t(2?!NaOA9A#b1v&NvE44hoG!15sFkO% zb42d(+CQZOt5OM*jqOgFpO19N>7P(>-gk8<4x4FAo@6NWd{!8BSacN>QW&60M5PjZ zb_w+M>Bg4(z>1q#eHaFVR6=4+Sf{xX@ zVxIb9G9G}#*tUgsU}e16r+9)20{wKThr}J>neN9^(|JM!S8S{9K z6d-nQL%VR(u<*24g!ZsQS-X2ovv!&IEab`}8^D9t=`c3Z)WZ$F*2PV{&YuLZ4+1>N z$P)-IE=ACcl#~=2!}I1K>h z4d-O<6}RX^^PNRyE1RaU5De&?8@faF8>xewaq$KW`gBg~)Im*m3;Tpuc2+aodbhwk-dhtfVT z_S2&y8REV}8OIbTdM6V;&2`RM!U`d!+`mZ}lWXsWilS7=&`aY~zF6Se$x&hyU`DBktS4 z>D+!ynUh7iz9QKgJ)%vmS&#nyLX<$9BK^F`!rwXtA{wYJ5*v)wvKyxkiRptAzgj5s zMJ_a~!e=p~wLU+W$}b&X1zE)bx2f?2%_LOxWmSyGan6hFQ>m+8ZpjGOdv>KwRl+*9 zJEi^*#9^!g+6RshNKS68*IRg@pBwmujw?joD_7xM%Z@j6O)dBPx~WM67(8aB5!0ZH ze@9w@rpXpB%PzM_cY;8cWs)&R{d8dB za^j)y6RwrzdMKm;I+ZG4Yh$p566P3YfCi}ze9rk6#LUil@7~--Mz_8JD2)$Z|7iDI zz?cVD^I{q(Ypt)VS;hHQ50OHpNPA;2-Xr;<BLMW27!o-?s^5~yTxGTQpG?Esa-a^d4|Sh?jB1TmVL)bk#Mw-kj(Vg|Qfh&)e)aEaf_6$i`!WmD6vK zT>CcNk$&Sqc+`UDlI$h_;rC-@L$$2aDK#^&d}j1LZk<6Ld-T#%LG0**ZUpWrakU>7 zRZA`Lh1&EuP-y>ApjFSQaT5SLGmx_)Y~dv zjoPw+sO|Oc$H@F{?tI*wV5Gw{U7CkTQ~ZAgZQAO^kR@I9$`bB({liQdFp0Wt;vmJOMf$gt>6iy3e?Bn}-a{E3(Dxd@OJ?b&Ua{mzn%xcz=fv4E+n zc$sz#;g||aN(22mL6DZ0o+G+XH1}-KMIiJlH}+FejJQs?wB}NGI_SPOkXzezyh?$3 zsTTnd^r8~+`W!WdD7NA)#k}-Vjgx9fQ^B{ZK-un>0{Ur7Ex2wb>F|?8J@jU4GokLC z+SeUzvqXf1O0-acOr8~Y9fh;29$Alb*3#>em- z@Oa|gIiI*94La{mqTPMPl#INS!;(|E`d7t;9@^c~hCCI)Dkc#sM29?A;!*kO`+ z>uRmtqfQM?RWpgVoF}{(i(tM&Swm$W=fPKX^}8T@Jw5yL>iS(Jdzcl8bca=I4>iV> zs_bD+pS)8muA6K{9CNy;)b^l7RUqbcB1UTIvIGEF3kpOUAVYzBt;D zNS1PZq5}T(yJnD`tQE#g$+nHh+6OT^<{tXnhaMPxqHdkiq?$LN!dZzvg8veKgcmmM z*xPQn{5Y3_A4-dh_`75$%>byixH1cx78&x-<5O(QwPV&g9mZk1`ul5l4tQJ3j65-R zR!B9M0xL7Gdj=FqK4NC(y32rZ5FW#BN>mZUfD7UR@s5u%W@5k3iR7YaGtu$F$RQlQ zQojb}(z6{Dks6BZJutBR{G3#*Ct!ix^KvRLNzXyNU@`>ZLzbi~#^%NTNV_=iu?0|B z@1V5;D?hQIHGS3LxK41@>h;DupxPz$j|9tnIft4*ii)Y2ayo=z-km|d$2F@xH>(+b zx0zx*W{lqF^g0dDa=4?1UY+2@Zr|p__TP@tc3lMg<@9om*Zg%1SI%DT+rbw-s->?OPS~hoADqlO^`k(pS08MtL7v zx1IfZ-25M_zI@90yZglB?12^iIS%3G>buVjA{e{V`ItT3Mx@Ful4Z*mdaEnftIt}O zgv(jP8a_t{AQ+~9fE)~)VppoC+Znr!@!nR30@##p<4C~J^YsVCd!3$Tv!9ML#7zU@ zrxyv+m$7uI#8gKqbh)&YtZ4;sXADb%hu>h>FNE`q&AI$lO4<4)P=4c!8U7NeQc%tO zZuCs#UEsdKnF9-~f&yD7M|||EO3pH(y~JXYRQJ`G_LmBlwYGYTIg3ZX9pEUkOXpWX zAug8Nf?Lcz*LUF8vRT_5E@L4J{QP%PPShmoTHF?4l9QG$o?`IDQ^}$hNx7lLqg+vd zz_Tr;Xwk-Ta9olvd!~sWX+RbuPiyVp^D)>o2F27&$a0dxy?)<8s9*rk)5ORfI7)!( zT-mk+bE+e7Z$v--B(KbWH`A-_DXOTPp4SND7X_v2ZNMSv1C8P8arB;XCQ0x0BAV%6 z!M3jwkw0gSX3s`ftYdBM)l|{@a1;@`*uH%m~ zq!)P0$5pA$d>)eyN5A>3%o_FdS}IRowE!4ZJz(ZGv*Gg0-3^C@in67sv|NT|DqFsr ztB?pN;XjL8f8TYar=^NxWz8X%5fzi^GFEj=jD!Zq*5$2Y&yu7~#ui(!VCsj!eJNAn z)XfVS8g3mru!DFOsQ6E5*2M=#&E;eK(+I;~F?eQ!29+7vPx^G)svSfHy;fm{BsnUV zsQw@ShG;{EAp7 zgYJI#N3bW9`m{9f722SbnPlzCWGVhfbbiC>`F}`*R)QrvY2#Kiciq}!mcOQqzR2V7 z+yH#wJ|IWpbXuz_31MQ3)Y%3)E`BAP^i)ireSax`4l7#c>r7tOUhN%BV=YodO`1?v^N{u2Dk56hWXy9Lue1ceif+X;wu>`!ufyo^e&hNz z0{jk^k|Js~&U)BOAlr1LaU?WTT{xj!^eUU|0fbst)=IU|%ZF+0>xxfnQbq)g1KlVVt5+d;YKK_zMrA?$k z`o%opz9;3mGHp(g#Yvf~>D7F5q*(<9sP&dotMd)?2NG=5X=`>F@TYR?|^Z}BGMFV<1cmjY;IQZ4Wu%Cyil~huZ7{?^ceaVd9CdI@1dkQV|Tn59Zo`tF{ zCZ|%ek0*|JQIF&vPq-znOm;tkXR*dew)xa76Hki8#rKQStP+eAJBS=VtaJ)V`-+hOdriz7oyYN$;BS4A^q|f<}3`Aq%@04tHqqx1uT0RlY?yQqp)q^QEuXtkvd`Iiw>+N(B8c@o}wV& z+isf+M+!``DeBSj4?e1>xA)F}K9ZZN`nefi_d~t2~WD+&!_ zPXkF3OKt}l?)GX6^dTZ2RQ>}vOBG}o7uw28DZU?@F048$I6aE_O~}t@!8`Y>sJf!< zz=UCY_14zk8WmMQqT&aj{O%VW5NNZ|wMm6T0dyll4%(;MYz>RARInG&nXfpW1bSCj z5@iZM%sibuyh3Auu7#(=j4$z)Bnlwwa1~XQYm)h`-Qsoi-7x{4895c4zRI~^VK6uM z{#t$&!x$9Q6L6pxf4NxpVyk-@!CI{Xo?KAfuQ(@WBHEpy!>9VD1?^s>Iy3zXCpcgK z?uD#1;NT}_aO45efFrCRytEs>f$I0sCT&eoeh=o@uIU1A_QL?3i?x9-#pwCe$? zKF!+yh}+(eTH0^hH@#A_JxQ=`&~qbyp7KzU!mOr9JF0l?uEK=X`?g0xwX~Rf6_XsK zqV~)z_`1N)`$r#DEZRdc_gsZWnA#KrNee4Z{l1D-&NUepY$>3Vj*4_+JCcTzRnRX~ ztkbo3o0_FIr4}HFTPmyayR&SCr=NYevANne+t!<~)N?YbF4mv|jjQ@#jj~tSpBC^Y z)7S0~Ztfr#E`X7_gP2kozkHCrTgv>bwA+hjyRG!d60IiU)p~-6tSA-OSiM#*fCXq3 znpbF3d`3%;AfT$M`;!%e&1y1#VL`va2K8X(m~%&o(WfqaHLW360(&dhaLPOD1nd5# zhRgZlc)ztjE~8?j_eM53&^@_57ALxE8Qj8dM#W6%<(&q-FZcHc0)Loj|B+NkdoI!W z3Nr?5t%|V9CJhoos{gbL5^CuAVo=4Mx+k^NSDSmEaYh_*#|ln{4GCoXr$*ya!spy< z-V$A7>suJzt~wv|8*iXyhqIEe%atP0@~~*8X3EWqFP;2%{rJT6p2v>TFNeJNWD|6g z3ZQYB+xg-=g#$=dC3$J@yFaM8Tl{_^c~goezI9zsY%yMH$N>3uWXvt?1Ju!_eUw?( z)RbYh!7ny)?2jHF($h^-WzDhHe1)?*x3< zRQq%PTAh-I);i)6ZRx{P)6XeB^W+5Dlr&6ok36VGe~{&O!29X7MXT`s?9v^^<3m+A$RLRrlWNF|@^KGh4HC{2I!S$fz+N-34fszQ8d6 z_Qp^S_RyJa17}`>SQkU&iiZPly3dB;h|A!WqDlIBqyuosudV<$aptj=qUH7>A^nxK zp1VFIareS;JEJ@$x^>5Y!Fy)U87|6!-pS#QN!MtP(xEuSxIOK`KJ3jWu2V+P7#q&u zh(9i?K5uzv=2_S=%w;dQ`I9>JYnsq59PLvRkq;G#5o(+`Bj^GC#R*VBy-G7nUsf8* z2qhehT}@rXZ;*w`*>VL>-`1{rQc8j;h3cWj)SyFZ(1LnIB_J7YER>Zh1Gl#zOlPS z8&3s~9yOjdWjYe0`!eQHpUMYS{j+R!2a#+3m;$RYr~v)1PTZ}bZV&&S1NvgY++&9w zHh;h_W`oQBYCgpry57aqH8^1y;wO3r5*hw>4_`uNIP%2f?|<0gK#PXWZ*YgctKjq( zW0o0+y?NUj#bhFHV^C05(={!ZZB~<0yq8nFta`-^C~#aRHd;uw37ESKP@W$@Nl;(C z{8glsFyd%j>a|RDP1hY!esWeVm7bU{snVuU-pl(j2EwIGlKyuB#@?5`GR2-G%c@5a z>%Dg$a$hPwBrO92Sr4dp_~RyJ+hp1;vdKQl!hB-0v8!y_=-qZ4!-2=;ofBM{B`yS3ei4Sqf`leBqdvSnxgte zMY1ljUy24%=3sily*@H&VbPn<_!RrtQ`Ny0_8=AybI1lyi#6VbZ&A!>$U()s^)795 zwno0*0So;BS?RPy5G2c*i3at7#AK4Aok%1xQeRT^jgpO?Xxm; z@PGX=xE~T$S^G6%(~gYc4eoT2?yZ;%WALmO#M_@@m>D~1T^Y0oHASt~Z9-}uG8OWSmAfw&THCC~iW6ax&MU|n|R*EXJY z71JA5VZr$iQ7=RKF>cK$%i4a(GztuKA(l5BRmg% zjCt%L9^oP$wVys}ysY~nUjC;+t23GmozhxgRNwNV(yayU2%TXynUeNwE1fw76#9fu zUUkuc+Q4M}%ohY{0Hd31EB+1e%EjZhyXx?N8>}C<%cCb#Rq+)bT@@ zO=e00f188#JgA})JmLanJbSKyvyjW{+wDYFb$FW>{6V47#vQn-T;KlGrQF)tu9A7i zSVoh^LbbM7)3J|6BxR8rvuaY%;S9uyA99VO(c7G7FL!@d76pKu(xOk8r9X2QCynWQ zv?L)_=Z09dyMZNEHKt_A3s^C}9GsC+V1p3fSk;~3Sfo(;XPc}?jmL$@#+UIJ9`j*_ zrqZp~bAetQ-%?NJGoBk+8mvgTrXm{?%Ra}@--}`+UJA5ECXhBm;d97_u)fl`I@A2c zP->;(;RX)zg=ke9HbOf6l3Rr)UPby-jvqX!DJ?mE{$Fg2Zk&BRFdYnMhBsbo2@-dr zx!2f3{4P2{lwC40rAM}CwOy8Mu3kT*bZ2;qah)^9S$rXaS+8zs{(VKt@fwIrzyq3? zlG)iZ=f)#1cy`D6>-0vIhDZGn6nn{F-s*ucLgODOK?%sHw|1o{n@=s@Xq;kxt`~}6 zQyN>g3Y|xuHf1^{`3BUsTX_GTI%(9d!unZwRE(Kq$CWyQ7b|fMuv=~w0*Io~8unEN zN2O1XmyPrPO{f2XHWW!M7{zGM;PS9pn{@zV@ht=YhG-+-j20me4@6&k(`~?3B7Kbz z2g+`5QrQk@=_)4*zI{0M*1!SCZvezO0{OLo;)uY^;=t2s@l&VFTzg_xDe#RIcXpjz zCjq*j&S7WjIJq@HLq$iU{HZ<%|BZ))Vso+DS#INY69Eo!e$hl_@d>ZlEa@VEp#ird zx0|sLIQFO^8t^uJ9!Itich-T_mjXih7U)nrH{tcIDr!nR}+GYEQ90t>S;CUssQU=@g1-7xvcH z(`tHv3vGW{i4@zuldUjr_=EglSBNoooi5ZDbI=oOAV7?68Wf9}`u75;!Vj+GB84Wq zlPoGAXZ810rS|WOk>w+lD@7)H_&@U|B`#O`rGa}2$V%OPC&+z$6=XLAx2B;kRu*0g zDz2QjC}k??yj-9QLl=v2$nfZQZOCqOb7+0A(srL5XNq9B(ew<^I=Ga}R6W@N*}{dH8U zHs8S4aGTg?yJ}GpHzzOGbXMf;j@~^xAXLkyqn`r} zsMc&DbZg3G`XCO1n_lE;%7W;whnd>0TiM)7>9!Dd?nmDifK$@|>vb8oAA(6#mPadra$ zh=eA_iuzaGk6b^(51D^bpZA|4J2UqY(V)q8E8E!pflhs0c1`x%m1l#vT)bfc$*n|b z!-_K|g!Y#*J=HET){e%7DNe`&6xvRu3Q-uGc$&L*OC9I9Z-H0a@YwHvMv+*)avsxi z{6g+#S;ahE7_@uw7{vqZJ~xr;K2#$+%uiw)Bg*w$T963E;*tUh24?WsspMi8(vx+A zJF`_@euU@FH=KjBeJ|WgfgntQMZ+xTY~I{IYH>P@c(Q(e^s2#Us~x>=r2?03EOKOX ztW~#ZTJ%3L*~#Vd-CY4JCWonf7Zdbx>U&UB%p5`UFLBg}1`6kPFEPY04;#J%|B3D$ z@!m50VpCcpVU%R$cJZihZJf&LYOnp-OA0C?DWf%D%aSOXW4|;s*ue*P3g6Zx@o2J|4Z=IxOpr z$q;3l3B<I@KP*8T0%?Ub+V&AD`sGt||6G`c)Fat`A|w%cT$ zN$Aig&+)s|XDKGfQ$UN8u6<1EHflhFb&E#F>x!Iy_HkOri?8(DvvwXiR~>IRxHM2x zhjp?4>>}US3dI|ShWb@k(?>*syK{m_Oi^5XGehP_O@(_1pN3isN2_FTq`5bb-dR>q z{$5QGy)^|chi_rxelfq%5W#_RtHV`%*u35(hv;oJ$80J;cW5Cz6nGiW-4;x0-6k3| z5S+#}4Qb7V)Z{^eymv9okyXd|9*C5=yJ&G$$604V##dKK!L>faE{^9y<&dD+OaohkK^#- zvRCTdS0psE7Bo?N0x2U0zAmFkf}PyQ^Csy7-;;|5<`0tw-mp!EzG4ac={nq@vJk>g=5~}ry-WU^ ziM@bQ&R~*6KnHY~^LzQExW;Gx*!G)WbbiasmH<}d@e#alX7{=))Gvm$a?FE)OK5EM zGvlwnfTQdFTSsz}RcCwrYwsB(V&i6gmS-n(9Dq`3+ZO_rj}E<-*Bt)Qr_e+j#Dp2i0Lj99cAiwDSJ-9q}`YS#% zjbye;B8K3z{`!Qne*#Fs3q9G&?EGFhUqZUXFicV3eTvI`(sN>ZqLlqsEK?{glWutd zoy#io_#7b1yHq3-0RF`!vzr^XyL#v`6rAH%Y1RGVhZ`NJOCfq0+v;(4>5=?{dFc<; zwWUYh3TY1H2VSppYBYC+Yn$QFAXaPpze*#}b2GvuYap#>1Ghn_`|pqDg*+VraYMbf zxz}Sx`3V|1e38QQvGq(jIJ7jv~Wa-;~k&?WdH=tI>0*ocNT4J?Z|zCx_oX zn&^DD^+tk3#mGi*?{bLEZ|+!Reg9rA681gz=599x_S8J%+@46JZlmglBxl(XTW7Bp zzf3t(Z|@JZMTjv;o9zv`C)1^zJ>MD^p|(Xqe{2&)g|eXMHH7P&j-8RL(M2`eZ4Fi1 z2E+3gOPYhozU|nIz#(%tQv&_o(NywKM&2>N~ktW~Ua`EyEXvndBgo*)TQ z$KA=Czvlo%Oy_KPViOBtEduL=nZ7m8HC8;qKo3Pz{1J7aqXcHooIE$gC1lpOpLwI> zG>Cn>n^ah^ZjR|y7<>$H);OipZB@|_boMNpxM@2b=u}3yt~x1)K6E@6ajYtwF;|cc zF;VX{%((Jht^rmiEZkq0(El77KPOTm`bjP#7qEyVOFH zHmp{&*>Y$6VWi4xhiFomymew_t$eABYEna3#Ni~R#^V-YlAYO^K&sE8X8GEcd3%$^ z>1`&C`?1EhqN0xYFOh-x6JE!PF5^hjsHc)9E2$2BydJrJ9jH-CB!e`!21$0U8VU8l z?q9nrt(4dWeJaCkJHenUw>x#qOdx13WV!9p$3x$`<<$Q$ok>GdJ?U7VksC@llA(S9h3Nx|+Lr3z9jch2A=ax99@}tkJth2KYh=<@_zk40xpVYH^H>pOF<!yysVzO8$v#457p8zTkcb#rf#fKw&Jc3v&OVy?W*iC>mH!@i#^ z!z0GP#j5rAWdOGFL8Jb~ts-M0#8Lm^${*5o<>$Gh<@uP(T*{`M=QuVA7p5w^yWdO` zHx_Y4NL4Hn|H4Egtce!RR@t&h^y^F<{4ETSVxleH+4VAgXW2MKVH7midV1wK4 zNr7DJ>60Xd+aynq-u2KN8(2*BkL+m-moK#Uw6kkx4z$EZq`-AgM{(rXql|alMu6+rU&z=PYV^D>EH+Z z6z98jTUp?>a(k6Z&9~W)=%zkf9e=AK#5wEvW<C#e@k2)5LkGy4^ z9cb+#&jEVn0OZRP>HPE8J;NH4!8IxOH>qBmWen27B29Ca?mmv?`?K8v>!&@yQGdR` zHc5qL!1M|*x36J3WVEjfe6#*lgjkA%tmUiUJ!Dg?;d*;?Kgu#9#`5Ff>5f$W zZB-xs(TleNw{)l3fus_*VGc#J`ty~^)f+%ScOf86D;8gWxl)|CHdfkS3+Z|Su@Ee4 z^mZAkFyO%F4Lsr3fn^M-$4MO!|d4X?3_sQEcrV`pv~ zyTd@r4hWp*Ye3L7oL;wd^(|pM@tVfQ?ev(sCDWXJag-XQvy~@0hnl>?gZ)$1dSVSz z=yiEGy?eQ9v7tYV^|aGadDjq7QfIQkc1%8eyY)Y5uR4V8QaemlKlbNe@IBoQaP*(n zJ(DQ(tl0x(t(g?Wugzg+I+?ou?4iE^yHi1wv(82m_<|v(B3NoQ|3RZgU~u&kAv$Ki z)%9JM(yQ}T`#-!#FRx&6U*QS_V?Oy=lETrmMJVlzIqeLSR3&^NP-kktC%r1uJBV+! z`BBT4gnJA@;Lz-#*SIU)q*9dYsJF-U~3UU43U%V>T2w7Y||osKyxXJ2oyyMCymzvu;py zq?8G_@c#tAucQIZXw^B66s@G0HR{mi-QCy$SADM*6N}9zE`)}cjA@9zgL|-RNuWvkBIqeG#mrecu>c zLCXbwtp$N?S%4USm=fd{L0a_>D#ItYBTEu{9w}X*AsIJzwhn67QMJgs`KNtZ+;iWh z^y(Fu51&f&ODE9}I$T8Em?ma-EbwR8{0xpoZpuo2k0|otFw%7C z#tb^+QejQ%&*}ydZ>h4`5;vDxN_YN_k2@dZwvFluPe0}y{bxoD_@x!wSz?iwzbxUN zp|Og+Xnkq>JL9WH{@A*lOp8s>O`aNO*inyDSV`HwY3>hRULW7vN)b5yPJ0?E@r0%* z-q&OB=&6PWm($kBj|%swK9TW5;pYs>R~53#IQ+2p;-Pe7USNtV@@jt$ls=O7&(rmA z`xwXUbvTjG?$2j`o*sVy#Yv)=-Ux+h014OLFa?>TkPil{Jv59lBiM`zw znPc7u@_K_JB|p%8;0D|7)?|yCmU~EB_<$CC{pS%Lpxf-MsEQ&$IkwnZb6tTE&sx3qRn!+@Z4!ZmBTZ2cL-I7 zjE)%iHC#GaZgJNt(Eo8+u<2cq8uAY6R^xWE>jLr-jW+A)RZSV*-tY&Bs#XIF&^ASz zh)iES#@B0Av#%y3)*gBicilABPm6c)#W_}V%$Ij3F8*ZsedThLYQp*K?(6Lnod{9!CA*fCcKXMf8HL+3?Nafo6 zvzL7&v3Y|DC7>r0^bM`g(MlE)iS$ffXx?ONkLiWLYl$a98$_8tWry7a1o4uKsWbzy zexjXm=~oIb9;>nQ*Y52sh)_)6PKO~)B5bLHs+*!e8?)Rk98Mj5T1>fie>HnPpIu~E zl_Y3)#+^M*@+j;M{8voIYZam#DW~j~rp9iZ<`Wen?qSg>ivDRKalT~i7qfVL9SJz= zbdSaeF*#-N#hzFF>9vGPfWyj5SnPw6ABV#+!4Ksn&B4?c1@$sc>T_ns@A_07dpQ&Y^UG^Ow&qg!U-_zFo%Cxsmr6V?ti11*otfKiTphYgh#g0Ss!YYJj;H59 zds%c)4i={tYoTcaGO6FydSfddW5Zr^{o-fJ%y$-kn^BUKX258BJV_?<14yHM_}Uz zcH{c5aX{dw3XLo)M~$ld+2(K}g6^ReN!P`4j%1&J*7nMv7w!7B*aNF`I>z-}#~=N2 z^V3Wd7uAnyT0^%dM@mMSB~{DU$E*13zgTqb(#$+v8Y+x1X+iuw2p4Qa5#!!vLn&_g zxyL-f@S_tKpvgUeppf+p;Mx(K`PW{cqQkev3d3*DgY+F%1n_b=T2MB%+40+dB3Q8$ z6}tWvX8mGSWsq#%sdkvB>{>2+NTBCdd#0L=hD3b=A5Ef`LR@tQk6hvdi?6^Zgz)an ze}K`~A!8;A(;Q_-xGxPF7iML_p-UlJsuL%_%dCkg&kZ8wIDX|Vnt1gyGrVVKxg>v~ zeGFYq!j(E$%v!dA*9_U}2D*zb=!oV&NLSTo5k;95>Ub?frf3VE5e}3Fm!JW3p69&^ z?QOxfzYiJ-Pkel~RS-*zJlKOu3%~R>`pNd&owJW!y=F$DuVk_nDh!5fPa)%%DCQ9C z$eq*cLx)AY0?)^Dipued%q*TBvB37Eir_CsAa2-;Tby?(^Iy?o2i^LbRkl*``)H28hNw|L_+FGUs0TQ}SBt&y)$u$CW4l4Wi!E>Tj!eJw@yO0%Ir>1 zeEo1h7aV}FpS(V#;FZk`3Vf*tW`?xJee@Ep0CAUsHc3j*o6I|U0@al`u@*FW2^v80 z52C3%aiOV=r8RBHG1wO`lN#{{pX;7E@aIc<3ICb<_~xXr_(6I?%4bm@0Y$sOI$aug zMoL6p%hUG@f=c-bw=(@xhSq@(qltkrYeiQ(7eU{&)OtD1Z~l9G|Kz9J5-e8YvwJ=4 zZVpZ%S(^-?V8H=*m(Wir*bluP`a@N4c`(kfCHB0A!i!&Dpcl*=#Ef+txxxur^`HE` z_`P6x4E5-sqJI}pbGXiOY4wmU9+kY4=Zkvw_Bp^4?FunewN%xm=8gL(`QytklQ)~i zTz8Y8hjI9iQ7V$ky*gI9^0Pq~)*77)Y$&VVoVv3VgEI&REAT%SpsRp#poF4a^2-i; zA^E^=%dCbJeppl*7-i`~5J6PFAioVs8tfNvxd>s1R%&>yP3e>)eAM(n;I{7C97CO9 z485W}HcPgBShd8YYW>;bCiAkQ+4M59?RvAVV3=mj#9jM8V`0#}4FlOn5l)DoB58KI zGFtgvr(He3uYQU>ZO^iBETsT&(_gxT2U(*=YHOF&tG+NrnQYg%%}uIY{@wD`O{&30 zMkAuQIxs*vnJS5s0H!J4?WMH8zIs*5d`-Sg5g3j_VMl?AIvF%M=~u(#0*UA9S^!sE zn9m7L1x*Yf@i|!vy&(Jv1pzFKY3D6$oV+3y(Ifu3$f}O5iohTNd$HhpsRvC`A6*;# z*5v6qoYfStCWCy+`N?&m$s7~OK72iVK}2x}KjH#tcww2G5yHF?)At%i=X>S+FyV^h z7P~ZTEmgEQ^)X9oRCns)Wg{$|FCgP$Po#{!Z`5sthj!i3-^_~J=s|v{Vr|70T{i}0 z_%FHruk!0lI}VNy84p8Ve{iM5UsF$yxgPlzHr=Q_--)t&b{M8^ezE!3X=W6pHTq_T z=@k5SG`M`zl@n!Ft2Rh`{=FtILi72{eNr~7)2Y4=7Er$anXY0|If4G)*)8{PMm zjHkk^3tzvBB+jCxd~pS4!6|8Qn* zwssjg+9LBjg8F^PC@p8azq==E-?iYYC6j&(eB?fHXFo9EOS!^CmLPbXa4-F&WO#y0 z4{LRl^cW}0vKKb}%7pzxM=>NHrG4Vqsqu#4w)o43xX|x@t8ZF_25E?ByltzmVTW zLu7nABnAd9p?5t7yM#StEIY;U-i->%b<}RhsNH_RM2DaI*uP*In{%9F@juLQ9aPS_ zUs(fbY3mF;dF$(jtZO$LvSQyiWIgztA#3yI5L{-wrOwihJeOQBr;VI#&xj1dUfR&N z6*`nDwxssqJS8JXovN#zR5CiMb8O4n^v3rIAm6lH%M{w%e5OOY*lwZL| zqaBs<&E^@TyhE1qj!;sbuJs>($GHAKYq7BxUhCPG!FI{lBnLLU#}?=?*S^(KA4YR& z6+*o+^3dW5Hg_#q4kaSgoCr1Qo8=fZd*6t`o>?lnlVONvFh%lQ7@?QXKa=bDB0_jcZeYu4bp!%w(!LxjS6Fh{iSXS2r${l%5NvhI*({Wbkz*7tvp&-x=-WY({oiD&(P?m)9X?hbv{ zx41(LvE%PV>N_g5)sBzR%?(7PCgfx-%5U))5nZ)CtyGXg-ertP0n3BD$SQ31kyjSi zXoqHT0zE1bD4G%xBm3bjokYIMUgIhy5=A8gi7s2hNi<+IA<@$hViH|fiAdDHQYTT@ zNd7H#Eeeo%r>XfFS7`pLY?$x^GBV(gVY&b`kuNTRUv+wBHMs9Q+!j( zC93W03sn2@T~4)yBM8+BW?-rfD#!6oKM zISsMLKg|zU>@n}h9suZu?9{+2SPHDyo-}xm& zKRQo8M2sY?a90HnEn-1PKk_xH$*mULT8Nyu3KO+$Y#OkC6>144L=3$#rx`vLtSxx+ zWcAeK*lY1U6h>3xq_jRY#L0^5Q}L#pIOV3CIQfJz5;ND(1H@OG9w0_ghoT5U)&P2r zgLhR|1AoGwHBdK$pEHWnK9dxNzVSKRBItC7(ft<67ai^}x>W`rqV~-6&@&dz_*9^R zI&Z(!M&E5dZcsR9tOc7Ym0s(_L-??51sZO%_P`we7U6aW zzYqpTJN0+)S$0Ie&f`@FzOT?HnG3IW2XoH8XUz#gCkgIaDDM9Y!Wx9AKfRH_8c`)&J1mG9}hO#4kP1?K6a!f-${sW;}z{7 zB%ko(JUqr&T4o@srz3HhihXIX#V)`dT^PGUpzUEo|1982E%F)u$oN@xgz!m&j9Vc{ zuCFom|KpXY->w@_zt0<-`W#*pm+699r`N;{c|k z0le`kK7jijb_ei%Jb)FY@_l>PqW6zp;{!NpFn#}MA|AkgrTD(RQhfkBm68D*$VVcE zj|3W@6?}XaE8{ay9G{*}93uzg<2=>z`C}{@p8_{Qy-Zl#nQ-p)&)jp-z$7%{yT__OjacxnI*;ezJ_9bWabs0K_fGKO!!A;Dj%5y zG&1pMWZH^T5e*NFlrl1jXk;#WoR7>mwnye|JTmSg`M&Ya=>6lD`N;Uv>HEhM@W^y1 zLL<|zNFSL?ipa=Z2d4ffPD*_z9h3!pQ06IvGMfwvj7VE2eybBkB;iy?WWAq^NQRRr zoLr=E@Ysk3_Jj|o+8B$P>-;BX*GA|6K1zOXr4tOm`nh}n>J20V(0@E0fMuibd&;Bq z0eF4Xzo_%mec{ph2KEQ4_YZ7F7ii9)Drt^`d#a9-r=JtGm|2|jk2r+Qvlw;rOyvBJ zFyX64N`&v-5eT3D5-0rQX@u|_@5Y3`c_hA%a->f95*vuaR+q%?*&ft zYg6g@zj2u6zZ4*vA1TmjzNdgn8&1dQP<)?)mZOaC_a^JU$2hwbt}S!dO6&H1_|&!T z`h1Dnx3vdq7eCLb{pIz9+CSWhsr^tsqV|LNI<@c3KZCUH{^B#t9(rLWADN$RkIWi8 zGJW#o_h#C{$ZUO3NYsDjD)j#G)13OQ0rdUjv6%Yjza|_msquOj{G|P)SV+A zxa9r6IJE!w6d#%G$#nm(43Eqd7aEyKE`4Oixc--%Bjp8Wq;sU>v{Rp2jk2Tx9M}fE z|M~`koqSxi<{=?av*qsyU z{fDKP*e_%uVn3IqZ_YmifAR{O;JY(O>_-wB{;$xs#9^FI!x}EjJz1R`*i*}kzeu>j zHaR+r1e2Yo_R?S3tC_sa?ROdU(ld+qGOrKm<^B@f%UvG)p09_$=j#!BNsdVUY1zd- zIVY=upFUJ}UsB0=55Mgj(NhA|lJG|~olxX;5536rJxW$Xl>FfwsVcG^&hNLEk0f+prITC&2v!h_4Iq`4PYW6#Ko9|0DB#h5nn9$HxM2aNwGl?!G}&fAT~g41 zI(EOj6i}B88n@RyHUjEmY7|gMFM;~*enqOjWsDU=!B}_tjlJG}yYlUU(FU#b{8Zz2 zX2Zy42-HqviY0ozPs8T*bj=8Y&6hNI>urp<39xaxkh6Vu8V*u9nDYmv&@8!m+) zMam#(sVc;%2}Y8QMO)spl>z8G-fTS4(65_dYWF<*fNAzcGferS-!vIod#=ogSo?_z z_Tx-!s}>n%?V>h1ObNf_+Y?&yB}@t>`Rs;9g$d^!p>du#F%-@6uv->!1qyj zDS)erO2z+ACMyUWe;kvGLekOf{0PrzX0~9J8N&}h5Jx{kdqlgCxnw3DV`9b)DcEe8YV06 zi=nl%^1|kMYWCQj6?r;OmKzfyo01O@HZO6AJ3{V%%9hE#6St^XjOoK~;s*jvtk z!LfeZdAvj4#|=I}x8g8pK0GH48aVcE+)Q$(t^VK4ht!`P z`ab9ycHQoM(9|rsNm(#yzm(L-$52zJ!EtFq^#U8&u*&p)eqn!Eok&BL8aqi<9MaNLf-;ALZ%%UGz(56QxNX>vr#_gZqM!6T!Fxp2SPwbvV6ZmXT5@2-L3L(-C6 zHQ3t7a3Y^q3Vjb?by{GfPNf)psSD5vU$~+j&LJ^Ey7RMrYG5x}0HbNtdpOeC*ag}g zbvTN2n0)K%gQI)a*z}8`UbN3JIuY-?N$KiA1xTA(tYmC2G%!vxuOVxt;@H%(a+Hkf zB-71@9U`3ecyHyaik>H{1HwkuU&+Q!y^||earPkbm)bvhe_1;|Kxwaqv1?s>t62!| zc&CxAUdvNzJJesv*x5_T2=qQ#0~vN&FPbX${lhm*j_ackW7;U=;2tv$$RjEdUr_0u zbDgoG<%%58dMga66^Z)fH0*J_J|w(*99m1Y2Tu%9GOky5sBcka-Be{lY1>DL^XIBt zmE!2(MknM8X$sJ9{fv`icd9Y(?>xic&KqRN`eKC5ad8Gdhv^Z@p(c(CcJM#aBh2H{ zS!L|s)QnZ?xICLA^f62}^z`e(vHJ%Dkep^`0BkHpc9P~c6QUIsx*!iTjV``YGUyb%i0@{_c{nlycH>oc?zQA8%< z&b9SpS?8`z#aFE18>5bB7j;G^ZT(m~Vg{gU_n=tlp)d{ALqpN@hA@We0rzyuu}f(u zmK9&51U4GB+!>nEbL>CKi%{WS>fq8N$FTKIrR&(l)3q9{?FOqkY5E7&+9AvoyM%Ei z2acPNrF780NS1Kz2A~hELZGHe~Hr8qhi`gwZO+RfP6pf|)`>!K}m8 zg~7<~Ch^1iVl8YT4Dvny;!*SIj$rrv3t}VRJdA+^bk^%Zg-3`xEP~$ke@enb@|}Er zjY8g6jcY7o8dN36#?Tk1qsb|w6)DQM^F=p@Hriz7HSQn`RJcT#)-OyVOuIz!$Y^QZ z#U9O9T}#E16L3K%yIdH)Qyd$IoazFt2B$dkhX@@rgu_{#;&`^*$zZ2Anhr5|V4WdO zaqJ#!;3&f>j`xi)+Sxq?{7=Ivjz6=)eu@Ll%|z8VbB|FYs9L?NmqP1InI>>E0$Atsc;9#e~;24JQeOtj+mHeqs(neWptzS7<# zvf&iDJ>9+^HK`_k)MVmEJ={Bt{toRPQGjY4`x`0$6w~jPPrTfTRJ`!S%LXDd_!BP( zXv`G~yLag!wmkOQnwqB(`hjJ_Xb7V=Cdz0nKbMS5Q+yL2KaPBupAu%=%We{nLLIuz z*8WacusY~bUh?dNHg}-SZ62#5!9NL84M*!%iyp}1%)DjCui^@A&0dAsC*DcJeOAo7^@StY)&Jt{HLp^sf$u<|x0;cw7m;0Eni}{8W^GG;!x=HO zSD`gGx}qlNa{vx{IRM{fsLRx;>>%q*-vVgMVDMW2l9%i}sxJW5c&vS2a-wBLIPL{- z5@$9VnJ9eFX7dMz;Sk-~obQHl$|c8U0ohVvft-N|m!7I$3JRL#2%*}v0h~iEAE*{M z)b?VYz1Pv>V#D-X+|y|KE=&(G{Z7GHUvOENHVfw}KA*^Uf6;s~CjKdDhOSh4uFZ%y zdWw4fM*$js=0s1#U-l12Ph1+O5aT2mqLJ^a{@ca9jilda@Ry<$>l>=-YHZ&n@=u*~ zyD05UIq6nYh6)nIEp%51;(j0|^m->c)OyGKJyE;yIhQL8@!yUnI|PVza#Yt_UDekaMg(5ZMwrC||gUX?|ngkwA?yCSzGH#$J)BCq{_xPK} zv8Il_?-gC94ON4Ct`Nf~*c6$eOaLT@r(G#9Pq@tJv3e6p{;gtAos2cYCw()<$#=@-h1E zuqwoURM)6CG*dD@Rwjk^nAi(#R7YXJ|K<0@JO6|Bik5BSINRL(G;qJCN4U6ucT1K3 zMC(&aSKRs95mgqwp&DqnD>6@e%_N4n-~ywZsBhc^ez1!n>kXqw?IC7GE;_E=+R51B zO9oAeW``rmEcd2hN)E)gfIwty*Y{5~`+uD?hQtk(HFnmX`qy$U<< z$Q1#=hYis-)Dg``7DWZaZ#UP}pPz4r{0Y^-{ZRqa{%$7wmwt9~UX<`-4n`9HglFKn zk&8Y@xaBuKDO`6cIVHZnAOy#UTDR>nuG=#Dhg!E;-G}51-$AKby>Hy^A6_^Oz4UYm z;Tqfc7xxUijh}4$jH+QT*!pUehmdkMxf*3dqmw~j`>SfIOt8mW8)Vd#>b`te)s%0* z0*oyE{13&;e*T!W|kgVqGjJ_#>FR9-PUtxmo0V4Ny{F$WlXe;tkHR}+*tmS~dKb~wm z{7vS(XyU%peReI(e0B)O{~+-i-O!$&O59$8hNH=l-yyC}w3d>IddWqh*sN`Pna#d# zVPrGy+IB`2dySwULACwm3~X7>x_dFMe2p{kiMA03U96?ruG-%~+etQ`qK!B)4LQ4= zq9GAtl}+iXDUKRZfp>JaSiwIiE{L^$Cg$2FT?A&Vf$TWDvAOJcAR@Tzc&4{;wO!T7 z-h_~zwMs;DA|VxdyHnw>IPpq(ge;u|mE_iQ2fswvN#A~x_UoLeM8B0ga^hX|5nv~) zWFD^MN`Axd!dP;@y>KJ26Mx#Hj(Cl|;SDE#^G@W%3xd8Gu}9bs_z|~-C>lVVUxgY& zk#eblp0UaF&8t-U77DswI1ah${2NxZjb8U zx;7;TP~6M>yZ5!n$|iI$Rt;R4+9Y*#(z&b9vD0wcpjm7EByY%Gtg#ikaj&u})XqCc z$=K3R$(UbX$=DSw3DjtT`(eZr@Eb`e(2wkb#UVC`(H?6-6_Cv%CCy@!3~`Y%3s6g# zvp`QFIx?B^L3S+nbp|~ZW}m&aCgO@6=y6Gm{Co5HcGePt5-$u#j@`88CTfcA(b zT#*(y21zq?i98_E5~++BZQnKQZTTQ7Bif$Iz`ecP@@;v;1^0Ld`)Wy3hq>VdvgowK2j)aE#JlKLdQ;IrMj-GUpl$_bZhP;G?FKXOs zG}$V9y)I)o4Om5cs}slY@0|q0_5{PR7{fA-VIIXWLdOqAy%|j98R9*TTbi8={v3_| zdJv-gsl9_?5Nuh;;EhN**Y=WI zpF!LVlo#354CI;)-F8X`<8!(^LRQ~9f1MykkMXwC`;MQmaoZ`Qqs>seFGR~gn|n;5 z<4afDoD{*sM1MX@nG}(L<{xO~SJuHSPPDE(D~{FRwC}#N?G9Q7o+aC`mxR74!(%W% z<4PKz8$vyhUw_euGIC-HRnpN+s`U5Z2e>1GfNgV1GLu)gAH-^-DjK_PaF`Wkl02&$^t%Qa~ zvyLC5p`o2=0I3ue7RBs=Vn(E#@p@ON@p{&y^@}=MFE^s26*MqU7%0^xE)P^^I#6vb2TBagBSY|# z?IGaKp_#^b3a!bB6NbF*j{iSXEobVcnE#tIl@I^>ml)aP&t}HF%HNEv^U28*Hd%*X z+?^(0+}+wV2vWS4ud3^;O3?mpZlu)| zm}aVgE zt=6&#LWo0MsZqB7(v#J{MxKZKByB@52RS#`Hul%ZD02>xh|*T^NE9|Ry1*mUpqvX$ z=E~rDwcKbn2@t-gj$z})B=R}LmKH*8TdaNi@>H&K+IF$oqN}cvPm}GLm&{2<EMP}mU?2A0lggmx{Gv5!9M*kn!AX6qt&21|JLS{M_y2aHrk51y#|Ogx z>coH91&nrHow$MeXb$@?z&;wxHUpdLZknt0XDCQdU-7}1fm23tnf=j`zLOt@5P1Rf zv>IUEFA1ZSw3r}wQ0T<$#%4Q2rqbR1#+Up6Z`sAd*wiLgq3tF1RHiXjA&h3*th3Bc z2bvNrwApsY;S-7uL&ny^A8niFHPlR*wlq^WVXr}#c75vA_zt~OS5Lux$A+MPv;N{I zc6y-qwCI#PpbJHRUCKv6$FFw&fi~cL*sdf5Fe9Q>zK!aJip(dNFArF zNJEcBnys~z`oK_uv& z5E69L-ysbAn19N^4`Ziygy{5+;~_e23`ag)YU7kH9bFs|=F5z?|EM^o+Wb)yM;r|a z!8hacw~1u8yG2MyDt@Mjg@hbA%B|O+AtCmy*VC}$+wIR2;o9-#CntvdZglab9~Xv2 zn|a*CE<-U~SADOHp7Y!qKHH3U`D|SiyB0(|qX)evHkn)#TM0I307^p}!&L~XRjy^3hPA0(nT5p9Sok-f=0vbW^#6Ew0{-i{zX zu+|1$1j-G+x;1ON(bOO%X^L(_rpsdoO$qJ&(JVV9XT4T{MrO8BmQ2bD|5S$OiC>kB zTkG2xuW}+Rf%{Vfo6y06@j!3t&Nxc0Kkf9D?#LKX*=y9^)?!5D(D`WndX5kg`KAL$ zM7I1Eebt}~fUj+E4g8-*M8?9O2=@Gx0N`{`7rfP54mBXLm%!WUXlygk*k+-zEl9<2 ztd$>@uTC14G&Jevq#^jEER&rv`0jopIq5M97BY;4XPT+-M4`fEo%B1H&>soxk3{x| zll_s7{y2DEJqN$F3GwK4=28G%<^;80WvkMqb;Z^zm*R*|hrgeAY6b^^ih<4vsQ#E> z)w=77%|f-aakbsVYGL9wVLq6Fv2UjMY?3trhMo#u@ts_*1B%Ba(a#Vs( z$MCP$(c?&#;s@=3>seCTl>vXz*KW^w{L;}cJ><2p&g zwfCPSD=EFL)=qjEHV-rr+GOu?<4}@xC6pvxdYoG)lJ$X~IbyYZi0}uA$_I(kc(D&= z@DCD{4-zE4iKhA(BOb3H9Bfri{QtC$&_+-plY!x9*AZ$!#JMew9cBgZ}<{BkW? zG;~tVGAhBdC6Z#z;VE=v|*rW|RbOImJ4(!(2{(Pc3@WY>#7RCUi+slSWd{SKcJv1wYy*H*B}T8*&aT{3XE-U|&Wm3o#pDdO zcK>H09kz&OwD#*a;;-MOvdQ0_VCT}I`f~IOUCjR#qS^T}&CW&CgoaoDfGI~Ce&lzt zf+@e-sNi0y0{;QAg1iGZDoA1#;7{J(YvYq9(kEHFY<%+7LGhDs{dJ~{WGjZel%pJ@ARe9}+)q{{() zMm!GdKFU;=iqb`U^K0?f`MVWSRCg=Qn1L5T5lDwuwaa~?wWD$zEX^uFC0EZlhGiE4VwqN=?+DoWvu;_8XHo;NWK%2M;dIN@zv#Qmzkm;-%9K|luh^vPz2~>ps4USgw zBWQk%k{@-74-KRQ{y=A#sv5M=k^6q5j$D0;TuN1=SZoMGgQaLW2*oL|u81HQ8VjNb zieiBUZsKrGe~b)gLA?;2e?ol9OI7Bpo7^y(S zafqXDVu*$cL_CMsN+8-R5D6UOEdmjzKqShnrLX)WL}qf$X5;nlNBVfB&NgMHC<|ta zwqT|hnjfd+$I<+FB|o0#Cn)&|G(S$wn{UbIHdtd<7z%L*x>O!3so%o!NBgVa99~YxLQ)?O}N~MZ<{ZC7pt7wwO-Q7Sk!l zVmigC(}^L(=ATn7=tK+BEhc8hsZ7k}4>4M$QOT1Oxx7&<5bVW=L13%qAuvN9J;(iWV;EMCfKjIDyd3e3oa}nB`Z_WV8GUht({9>ov@?9Uifq<@dje zA*x=poaM!&jzh0n&hk_Oah_5~T+L0G!4I;rU%6kOC%qq(=Sd8r$?TdKG3bjVlZis} zo7L91d2$9ryWxJOOEAY)&X75_AQZ`Q*M=@CZ(R)sjzs zco{>Ky=uuP>j*@)HJ`jhAO^i!Yd-Pa&j|k4w>qDUxZlVpX5y4dulg#Ru!6HxK4AqB zwPlp|r!%~hINmQ!CwM0bR&aqRL+Xp1B1xCCL{^bXB1B%vvdb$^OlQ2(-_mjzF%OSo z&OA$A8AKpb=UMVf^jr+_)cuyca`Yt((Nw8JupG7$h#u1|Er*4qm97eeU^&br5R2zp zS`MWIVvYhKSPnN5h)D`Wx*faV>S>H0_O93Y;eu(Vd>pOvu>HBQ zG{2{67|{Gg)h?j<$#(Srg;k6uo7PeNPqcNb6Ze@*)1=5nspzU8K1rP6x6G4N%UqPq zJw^R579@)5Uo1!#)qjDk&s(Dg9wb9rA0*ZuivuRc27vFKnD3v^8+aKjOqff}r{D%| zimFP}_X`NYY*r4~r&e9j5$Kdu*)!HrmanDJy4hiLLbDXIk%@@*;B%O0t?miSek>tvO^(;-Q-7*Xf(tgDs7|GkD@Qc#Yh4hyrox(KF!$4GR=687L^xFnhcHcgSD50sAO2|RR_U~scmCz&4Vu;Ta2tf(0ClJ3V z5P}k#OCYu>5P}kFM=%|)KnO~xF{z`T-%<%3B6TDwbqGpm?K2pnjRGMoUr&%a&Q%~X zIK)>3;^#RsL>3hxNH|%D?0u6M9Go?lf5}@GMBy?UxSn;Yw!lGA3Kv?z4?Fg%0xu7%GZgN$^vR7@MX~ZmJkrPQ! zOQlGq#k~_5h0B+V(xUs9Y-*;VnK^u-EHQ+cnHD9AjC5TUr8N@`2d1JBeF43w{`wc= zc}YAjiBU@chd9xA;F>cnUNj!K8cgdf8V`JNl$IbG52d{24BT>}2~JtK#3_{sL={EN z)0IrrEJC8@%H6?5P2rPxS{$pi6g7`Lg&}4@hP(B%A$6`+#&nW)t5>fm=wNQ!K3YPtH%u3%$4`*o4kM{d?x zy-!8$Se-ddo7Q$&x~QDt`r~UNKla`%^W**HHRea;DQ#->q1sunE+}U*7+t5W?cEw& z2Dh1s2l6dNZV57Y;6oVV`iCrK@IF#UYXw4(!K(4oeSWJIyPT98Fh!8hqXs-;eq9 zxUy0a=IyKpFvQFUEaz=o0^w00gn64qAf_r1!o2NGAc_hH z`Tpk}O;im0JD#SgUPj_e=8NKW%Nohk8_G=`d6#Vek^uv{Ay`FT~$)U`2^oZi~SSk zK#bTw!P^=u_AhAsh@>$m7c-6NMH=(Z^}#h}&ncKk)+wGlL1Vh7Vu&nd^G48^$4MR6 zDiDIkY)|SKsz3-Dvk`&lp+E>4^I_7;eYaX_Oz#xaIs}b*<75o+`V>ozIg!*cQ-KgP zW;THst3YJf(1(SLxqf+9)Q8Wn;QFxe4AY0KI!k@nR@H|L<#<&c@>YT=L_1pVxlxRe z^A?G-bmwwumfkt)U(|X@iq;cmc3U&8mqN6m(?q$Y;}#N3+BQ>r_LSQckFMP2BjxtV zMqQ#@YEAQ*E^^^d%tB#4n1ILnje9K> zP%8rQlma0rpufix)u2EK3g}A$QK3Kx3aATdC0~IM6wo82j-d*Kpn!Y?qMrgGD4;mRr15wL>)s~<)LQ|XDgn(RKvyV0GbA7{0cxZG&6a@D2+$w$=I*39 z5>Olg+NA)^lYr_Gpp6R90tsl>-5BUC1!%DZw1fc7R)AJWKo1k3aS9MRq#iVQkqVB< zIr@3C8M?F^og+&L7AB@z8~PfzzG`mhC1SzwKQzarjBXo0G|!}L2rXM+QkFr>7MqmaM$1;1lx5MfbtYxGLG3)sv!7)# zp4~EC=h+Xk{+R%5qWBt_`a{ttQ6lF`l{Ix*oiAx-<`HGv=7#en%`PAP$? zR3HR1=SBiis6Ys2PFn(TvjQQQISmO!9|b}%bF{G-;z|WVFmqaxI-(Q^!OS^M>iA39 zgA>f0dkMt13PhHjSpM&CW1{7wCAw(&>^6I6QfR*6ND9qo3QslunZ8rKR!rNeJ5fxd zse$}Vp{el#Y)4CXkm|8VOw(l;N|;8=QtGl5d+^+FM#S-pbs|n59z;G@DdJfyh_G6G zG_u;Pin7|QYG$@s#nv&Vq5mzNCZYdq?G2`aNuW`NMdT6-UC6Q_I@aC7M8~U^qT>la zX7lLV9af@atPevR7-A_phLJi}kFgXTT?oWVC9YWz9p@8>R}=_AbodA!<1;Kp#~=bR zK*2*09UTZnoB|<;j>ZI{p#mX@j(jhM_*2;i6hudV0`aAFNaN)MVub=xV8_<}br_o$ z(X;h=@y#$ZTbrAw`kY{xEX>pb_&PoEcwUR*brV*R*VS<@?h)cSQ_cUeN-#u@ z0wIW|r3AvMKnSAgaRSj%fjEVesE-X{{IdHIePT`=a(cCXZC!r*1~a7p!;$`E@IRns zZMgWx4rW|(#8QFnEW+bAY^WudEG7`w-DJrnj}nNw)?DHz5SOT2!Xa)V5DgUwL4m~+ zh(Ag!6<8Aj@wEaWD6k(3F~o-ogrLAaCJ?VG5P|}mM<5jPym zC~R1@6vd#BA7i#zMz^lbwzT7%hAnMMHZ8QUd^I0Wxlcw}3d^bj4Dq%CAqYzkf%v|_QdnL`AT}xx zg0Q@TK)k6y2*R=+fp}bj5QODk(wAlmrh>5iAP+;dby^C`CkRA`kq#?ic_(S*9R)%V zmcvLL&nOUruCHkWm&AvYCa1jJA-FEzKlk z3X#l)Py!l=XNaGplGY$0X}n#QNj-_;cnJBDgyeh9i_+4?0dZQ3$UZ4dMDDg+b?nK( z6K!liOA)z@KwPRo2qN-H0^v-x6p?ojh^O-`MPvqns8S#V5!sbMEap=>XfZ1V;oPku_%s<3AHGmOA4!y9g~=$L@qB(V_0hwj~9O(>-JAC=-s~f zI?`?Pz#G(SToPwe5`#PGdmgyt4r2m<{v~Hmz9m$cd-~Y^5T0fZ_noI5( zfyeK{Y)dY=oj@G7=8_%+VwcO3OIi?!6|P!y$+~`w;Kh@;ozw5k=92Suq?b(8kv`e? z%;1vy`v$-Dc=&KUcIReUa!5A<@mQiIhg?V?HfJgvQuFcOlf+1Qk~;-9B$?T{G}=aH z0a`>hiE9_?eY1{=Z!(LWn@-wn)h)?9augs)pb*C>=zYd!cSDU!41PD%E4N@`&sA)O zQ@a}~Dv@=v$$ji@sQ>nn?}lO?_tI0k9O{ietcBMCC#q8s&!oYksKKMO6%u)##0X_t zy-K2)y(x)?nUg3$NYwmROP$ z=t$)DVv>k2O@t*vedE%^hFCefS8%aXe;6k4RUIscU>|{4*49$2tRfJ*l4>nhZt2PB zav|SvOYRw-<#TP-$I>1QxjSa*$j$9xeVIDb5~k{-kx_jF!ObfCTkFm*UYqC%5&9i{ zGajds;lennq2Ip`!7$5jQDAC&@minm`u)Nq#gOgDM|D!R>~5+)wpgo=W3^EqbW~sJ z#t>WR5d*Q?JkFHKv{-^dt2?_{T~R(Q_L#0tehRHl#0k2rTkr{*GZ+s@&kV~6+K&ub zOortIZA~B=W`vxeR(!Ug8{@Nn+FeB<`0Oo$^1_}fpM~HdtHygI$oD|}T^Is^w{OA- z#10F>=noK>hANDB#$gnJ`E{rQQ`>tWKE9UmTzsL<{_kA-&xpAoRtCqb;BGt@2#K}p z?_f5^ylc(wqr|U1S=k(+h;e&YeShg;Q8Ii{p_8V2SMxjD4NoBGw_m8lVFk6mbUISU zwrr%1-|;U0o)Vz;p^nxYcZnmH=*6}i4YF!*u!}f+NkQ-W3`L0-JF_OQen=FYn?~#R z$CH;G?&@rQfxI4LYaZk)onZ-jLx?C&t2>9jOS$PlJgwUdwiKy9--sbPwy_kcn+e20 zWv5$MY`>C*AwE?g1i#}{0`ZXoA^08h2*hFqLfD#lfZ*|h0+GleE+ch3s6Yr?GY$eV zL4mM!m+}MGFfRBbU!M&E+t6^Id?B z@u6K`_g;^=Z$~T3*_eF;hUhcUayBLshzXZj&c>FcmA8~SPQmqcdj~ea{kX5u+2QnT z@iC6Ct=gK`o*{j|Uq||(_Tf`omRwl_kIHxLgS!hdoOld>OS9yZt^{J60#OTh!9bmG zUGC%dNc);S?^RoO!N=_w5(@&{UTkN*lbNFIWZGVhtZN(k5hLw7Oyu%Z%VAhUAaYYJ zC+rIZ;%4h@yL$;lk^&*9&nyBFr$7kmvp0cgp+E@gvn7FOpg;)f^Y{P^apVR|^|^yU ze5*hRkqwIo#Agab@JEd3%>_j1iYzlOb1e9(h+>qEhlydMFk8CiK4g;Q`fOdkL{a3LOyeylU#)}%oR_?76 zxoABM=8&u8!F2NN_C~Q=4g#Dwic;x{gLO%9j74!hQXFSdT%Q!jTNF1S#R(S0XOZGW zi{i6!_xOMa#i>9;+I>D2ayjdGhE!~BW_)&KXfvZ#KRgGYunso(yDx?qZyjv#C4q1& z5Q5Ztmq6U8K%9b^ak35Ln*Z`0wu5cX>>aig9qH>h(w*DT{HQcyKt-9hrZRE>2e)WZLT#f02n_kjiXj*k#4I${wPE=q-$5ne$&)n7uOf+3} zSd-t|7m`Pjqbi-gR}6%RYE%Jj?`gr9JJo zCpg!LC~YWSRkDs_cOM=ok-Ql&tOdNV5M^z!!L)$pRfM3Y{+uAHXK-KRtw?BzQs&v-rQf1 zd8xGIDg0}0j`ESa@-yP8or3TgWbhn~T;_QLlL+Y}9Aj`g7b%5>N76y>bdBTh~gG~=IzhvyW&sT-_=ld7S`3@X$$VSH6QP3i!?~H zbq}7~zuAtHm9_3*vFNLRDAYX|qWQI?{HkbG{jF&>?p5w<(I}q5Vtmd*i9)gma$cwi9guO0-M^`|||3%s+fdHVF> z^W)nRHiQR%i_|5e*1@B;al_xVH9x#LJOEJr!i~lQWu3!yUXkA2E}dUttY9H~WW6;` zb*ZQ*?X)%e+$SE$$<~rU?;xS8B7`rf3kQ8Xtpp(}*(aZ>K;EQE)AF~$IX(@);$hA|sd7I-0A2i$X6T!uOJAi$+lVOZrU?{?URwgUqShc%4wc&{Zuc< zSeSO11jS3>-^^on4)&SvUbd>0FVnkx?qajT4)ou5uX~2zdg*}5|4G`vMQQ0p*i@Bf zomRUx44dG)z^Nqr-1~Mw`(*jbGe}gsR^m5%&*lF6kEFUeU$(PtHC%nB{a;a%^z!GD z;dlP$K^12mBZEadpDsRcY`k5$>su2mY2@XYK<_VyKO3`he7&B=;Fak*KYbrjt`d3$ zUq2Do@dNN& zW3W;5HD~ZPW3Ji<&VHcmWfv-Y#v!<8bq_MAAeNf0_o;7Of&DxUXb1o+oxH>|M-Ws#k2akmDPND zE<>~$EdTpo3xUQ0>iD1zT;KonqwTJ+G&eeQQ4W#!l%`-n5F<4bH^^>q^Gmg08bWt? zq;X{AJnO@M6T|i&V!rXBj>3L0F2;LX7fT>){$R5|aN#w-*AC?$>==Eh&_D~7mL&)odw z_~boipfcL=N&oLpIFCq^AN41H6dkm_UQd%(Dle`0`Frzd=%;p|f>WxZX{`Y{`Oc6E zu!|t#8uv9eoWY3Oj)z}~9k9XOavT*e76LjQRze;!`}q`avNR8ELFy{|uOvO#^pE&s zr}BfniTmjqt zZsCdJ8Y3?_Ihn;wZd{WwbJyc@d&)|Pd45Am7R%-CmW?Bx4z2wVYn;i{8(ax-X4nA} zzfKHo$^~w&;ID83oXxTU3=T;Xx*`5r{f0>sZ5+NCL&tWfbW%!8?==jT2;L&vZZW=0 zxCivM7Kz_DUyu12A)0TLsnJxgE$A}b8fDzg%sse$_xVFU{8Txg!gCL~fxXrt9idL3 zi=$ag-k{FMjJ-j=xZe@})j61}rVrHfsBiaSUiT$y$Kf%Olze|TjgqkCxPZgAJh8RaIc8v#2z2IU6yO;WLi0G{iv z3(*0^L0@eXmfq*@NyTRzN#Fd*y!dAA=A$HA<*&U}Z`mwyRkgpp?tTeKm%PalXVFa; z%p*Sm;8U}yQJRVq^WNz6Eh@fIKDH-@dUwspU(V`^(Ua&3*|LYWvI9X+`*HU^%aru5 z3%$5jg8wJG>B4jfxTdZz+NLZib$cKL&}uvws^vxRb?@3u!o*O3VHS???sY=*?roXZ zdE6gjQnv3(OZI+@du$^mBg5xKxXue6&4g@xfuPaRa`%<*{aVA~k{({CX^)4gJ8|bR zP>wQgTkQ}0t=E}tUF;q68>skU@tm05e4*lDi*k`ofDF(y3qw3b`BUvZH4}#EOE(kK z*SnKCxei;?ySnO)FfJ^Ocd4|10-(0^Gl8SD{09Jeo}v#~(Z7=VrQHEdnm8Kh-I-Ez zx#sJ-(%-g>r4(O~+OFM*I^iq7tzrD!_+yN9F5dhl?#FUq+-(vUlwDJC8YM6CY`8q@Xju`dsIn@UtpX?Z z&shJ869(Gp;FG`LY*NK;eTvhU;VpY!9g$3Df~}MD{d!@y;_$JAJR(IY#y9r!wB_VC zQ)$zYH`|T!2khdaw-UANq$6u3Gfpzr<=>mhUx_HY0Vkkoa$OlUpt_1X;rf5y2fb4$ zV0s~D@G^Jon%o;too3s?DfgJIZ@>BGgu%Kw_v8~Qv>9Kc)36`>Al_OPt~Gr$_U2!| zk>#*1JX0|7hgmvSGb0bn5KKvQQfapDE!;9H#q4PAR4O!<%FI7<4USt*r!jVDth5=X ziTz`l!|3v~|1=$I+Uyg?Yn&YB`d5Z^i}!9?6*|XHteg^mI zIBpJcjc7}FKM}ji8=m+u89j(aUj!-9!eSP&G@W;v#gftm4X&=}gGEp!PbOg38wx%! zfCt2i&!~6m$~3;8I^}mICK=vjU!r((w$suG52WEr|Ov9rA&jS$9}@YOi-hA5??bcPVYA)_ynk>{Moq zU$5?~Ms(A&I6{|`dZ(DIwcK`Jg9_W{em;F3?FxvH5On~474qQA7wS)vQYsM@x!ls$9R@kjq1K3%Es?+reBqN?mR9A`W?>rRvH%K*=Mc2zdvZ^E^oHgc1?(Osy$ z>;0D zyy&Pb8HIMtJ6+HAeQT*=lq1l@w8sZ%;aj3rZ3E=nTG-n-JF@JefiLcdh@&C3f$b56^(#1~wr=EzptuuJJi@z~Z{m ztw%-lq(3{W>e97u`~3lI-tQz;C>8v?Jbw6OT=uCv%^9W9Pb;5YFW0#?QO&UAjUMtp zk!7=B^?`W6dvf2Rjr@#GU(>o$fxlGirnBvT@=HelCOfehV%Vo&Z}{PqF`Q>sZ~n`q z$$4_4+ju@r>nHZfvlT_DQI#y?Jhk6Cd#(-B;v=ZL0 z*&ZAiVED&)LVd?LSdvEA{x{oDW0m=mT5$kZM%*iwe!n-3g-{Fh?fAs@!1vpE5+4LN zB4}7V1$w7^KtAkRrgeU361Jkn))>f^dAu+LP@PYT$l^6lgTGz_);?4er>e}0s z1J3>%MD~>?L%XHRPXZFPgJRHd$27E_%3PVEYC({|(0@JNtd>poHtQc({)C!@JbnaF z%yhV&uWS&a3+#y(#B$Z8(d6QB z64zLA>ONgl6YG04b2Sh!{j+!KprWVC17Nuqv$fjeCULgAg_RP%rRkD^qh|KIN~6FQ zbaiK4td}M{J^b1X2eVyzo3R1h0-k#Yj&o_l_aO+&3KK-&c^iNpMv|i=i6>-kz73G2( zD|ErOjo8@iwMkDND!df#WQxnq{FeCGz0P=`&)APSbSoiqHe;>R>_L_K6+;-qutVQ> zmbA1Vb$a>ujvs``i-C2T)G3jLIY0#!2g3o~z4t=Z0ld^L&r3OL1g!#YLJesi1S~?c z%g17#2e_#{xNW36Ohu4zz^%~~yMAcBsu04})Yj};%(b_;@#)b$))a*xoN;3GisQ@e zM~a+$4@~qw|M44BS$4jE%*At8m$nvup#-eC9hdBV=e2&^J83q>M>O5$7BT5D(qoOO z541&$Mgtmz7-816h`QKHd;FK|^$3SAH2w8GA#Wb!h?a?vGT(A;ia=OMUN`8U;N97k z5$recN$*?wpN=T5%707<%RsMzJ(b6EoyJrvKyZ?NP;=xLBfn4lTSa4*Kc&=Y<1R{~ zzM8CM49UsH!L-#`1Dii=DT}cW)I3YFco`8Oj?%m0_&mr43E3OTkc>Xb|roh)GF>V!G#q%%s}^4fdX0K|R7G1b9)$Mfr5 z*Cf5^x@&mn1O~$?#T0s|O$CGGs%LtZ2Ud;LhgL5%ar#)Mi_2Y-#J5eIk5{&@8~Mfu z(v>9%lBd`*WkYzrEN=(X@p5Yv5P zPT5NNOA*>TTWfv<2G2XCFg<=&&jKomZw2Xv%$_`@DWbCQ(?}zC?SEG%!qOeDQ_$5! zQ_xxK;gw2-L{qkQ{Ol=|ENvGtjs6KN-A*sles*PB+o(@3T&c8PREULw&LO=z_6~4D zGo42#oXG-M`grD^AU6eEIBRJ_y4jmM)DyRjj76i^DBwok1hSNx>rlXLiY{K=9!h_i zJ_6?|n1Rtvco;3i?+8!)S3yp%zE6Y)a_)dji%Lj(_?wh3t&ho~i)nb)ZP6*15ln}8 zKpRN8=kyxoCv0^rNiUqGlqTIk_nz=XS^7x)sjd+~`yb0hPD<Of0y)R|wa9s+)Y|{-+162)l`KiAGC$1Uw z>3n3_zG1{D(hi(B`rq1>OY5YfB}!k5EK+&4Vv3j3JLo(E<>9W}rAO&3ZB#okr(P6F zVN0)4YA418Qk5><*^(&brVuF=J~ckS`mUM%Fy<#t`;$PatLUFMr^XaE{1X@2qS>}g zua5r7Phmj|ZP$5}6Q!2}^)|Ok*Lk{G%pFDwt!3eXYNbjd#Oo9$pYb!H=(lAl(nJwI z*K{BFoqOY}7t9l(M!v#ViT@KZW6@XsN>kLH0E*zY; zDdM)7{+SUa*DQn-vS2M`6mekLzGsw|dPPH2h-MpP1kmCCZ`aTJ?H!g0MkOk93cNqj zfkLJ0>1QKXHAJKMDN8OSlV@8|wUUF%`DOuA9wAa6f9<{FE-N_ftOH3 z%#rGGCgSzRM=!VP+ycI=#rvn6)=o;$cMe-o>cspm9CiDlB<<>t-cwFkkA9n0A#}~y zCR)yTOF|BLk8s@%BoYH%(>fgk|2k&**V72%D8Dfw+>tP+`cewT)aZ2cWr(69h(`Ue z<;Cg2y%EE$IQ-@!-Rf)JgiX0}&4olhRc{(%57R=5VI7NmO)2a`?riP%^9I+ZG9i7K zeSPBDp_0k(A?rzDt+zC@F|8nP=Dk&=*~F;Xq!UXI-?Fkj9Y|nn67h|+0xiVs1!85j z)sxk|Y+RVfRfwe>ex|?@QOCdN29lee5MGKEz2HVVb>2gYYbDRAY833f+qOw?n2mnD z|3ZCj5;K=tujVtx=sWO9lZet+@mp9kGwEU7FNcLL8Q|gSwN&yW&)b|>TkiX5Xlf%4 zO_{ppmB(2+Qt))Gh5!Az=y__Bh_y*^1wJRN5@+L}bH znDoZy_1(SC61l*}?d7GIa>}FLLkhG55MY!0hhr!g<`8UNR{+}Z97)T_PaCI31&;|U zyQm_$Kplsi!=2eh&Isp=dT*diuUngw1hE^t6+nGb0$8#dK0{g-mOy5c_*Bc^663S8 ze~^!yy*nXw)b1MQ2%qQVPjp%Box#ZO@`{Cr02Aal#c9@2Xacq;)6tE1D6rc(P%}27 zVLD{UYTu9-eE84WlMd4J{B*IW>34Y@{UW3U(!?h)mMC-)riKCZ9ue-L7)C3dhzi`U z6s{T|R6JN!oY_h5{^PZL5OKi}ymH!pdT`5BfOu=3QAvTG+-+0^OFHxO;l?CbxBUt2 zoT+13Jo6WH;W^vEhS-dHn0j@J2wIz}H_>p`ojBm*bPnp<{iLl>WBh00OQ{YQ!Y#uR zH7_)ee3-iQcw_6&C*|9!QlYDQHs8$2qI(o3C6b-wJHn%(y$U1X&yY}haZq(3bX9A( z^GDj8qbiB>Uss60tXb#=rCF(4)+G(Gi-AAbthWS#8sq24N$eud zCWRz`l02omvgd7~v-nUosz3 z1@P(ejy#HJV7=G)!!al2yXr0-K9+Gz)@t(OIG~1*v6o^8qJ?!mm${@+Jv|s7%WVVC zgq4A9Qsf#za_YS1Hg82QSdmVr@;_sR;U09$JEcMz7?porPiGS|Qx)U@eu5={aH5%d z6N~HNginJC1y6<07oj$8%we}MK9;6J7TvOD-k>7+eaZ*?ds3iWmnoIv`Zb&icZz<|bD4!@rg?R@w7>fbwTmju#s7 zD{<94UQGL)cL)Nn{rBmUf>b)9gDy};|GJoce%9xl$cL{I%O_c?HDgID@07M#NpJ4s z1NW=49f7^!tK#k;n8qc_JLxX~*6Am6SZl-SKcdFtrL2{9e=AT96U$gfwfvjqRCUzl zZ&t0)I5k2WMs$B!$m4Yb8k@iyszvMq))|q{<4);-U2X)v*4o7(mmuSn$YAz8C+T=p zREa0uqm#jkd|sZX~i2jjbTZJo$8L(@p!RmC1zX*+*)&n{OcMs-eTSF zd`J)ws6|=eV=w#0i}XkGpr>?;awXs=b7oM?46`LG%=4umfnz)oSVim4E7^(H_F>MX8`b^o1f)M48FDs#md z^`uvZBjf#ezYsl zMflUTd|%Z_>}Ue&9eO2q_2eY$W!C}yxp<0Mt=7IgIAvvZl83^5r0nTRDb1(xW7hdd%p)49oRJ&Y)phYE4ZQTWx9K95~OCM zS)J%MO0ot?I;>UtFalf8QP$sNYx{VO*!CJaghN%)84Aua| z`e_)fahd!M%ZZs;(u)Wcw_0=Y?tHS_Xr~CtU6v)(7#~Ygo;@fmlmP3jWm@EB_ZV`1uc6EIN2Ueg| z?Fv_86W_LGtO*Fi5X2CoFGP}Lq3^K6+abdYS0B^gA4d77pbl`a8-IdqPcq%LAPSUAtVXB+O=!1e>B!{0o2J zv(Ctian5AMRYLF+%n8hoji2 z*~DcHb8!9Ly=p)X>uV5w5{N?@#Et<`Bl@lqs6$FllB!e+lfJ78NBG}v(d@^l<6Xq! zS8gSDT{S^2=A;DKyfw8+0J~C?Vue(HBtw1N5G8c9pTHxXekPr&CQ-T2&ok0L;hc3G ztjn*Q}ft20}yly)B6}q6TWiiw2zmm?*%V>E+<}H0RW*0 zIB(^VfHzWnHtv}kabXb47rx5P`xZ>m?xTAYm%HI|H){=GlLB^9I&%SNS#$+Q=3oG) zz1JzN|5UbsO9`G$!mr1_y!VeSw1GdJjbW}>4-k4Id;vK%+-$FsFHG`R6`oY!eGTF; z_6J#^7H5<5>KKa*vJJNY;m!kjr>sLD+H+S$eB1qqP&10w7S&P#p?Sf65-l2oky~+> zXX7j*QE;fUwA+g2-(>Y|k`WG5PB3XNmzOeE^Z2Y283?Z)UR#1I#t#ICv-EOyB;2e^G{TXY@ z`v&2{bmpSi0yT+fVl_UptU{8Vl!9FWg{KUA>t6qCx8ky$*l1mEIHn~uT~Ruelj%%p z5;&=2Nvh+Pt^;_D@A;d0=|c$AdSly`_yl)XMaTF#RqmuigR0-Xv(ZwsF^mFR?o@p$ z;j6Sv-LH5PzT~`%08z6#Q9)tG8+hh&w{St9d}zPhtup>SedgwAFAr0fTyocfye(uQ zuK7YPYpunt0a$p>eM#L{e8x3H1F0_zomcr83kR{l8^%~tyX{ZAEb`y|Rb|XOvfAyw z5g7In!*JSd2%>KRa;okc9v9d`)d(W{bM&jfgKZ9dEfenikCTG-B??+Ni>I=gO8qi z_ny}RV0w-2p+dTlz*k?2w&Fk+x64-5bCQ^C5|^Ft3U|5f_Q^%zfgT68Bdl9Vnb1;L z(-qVq)TAeJxVMvBD|8fIHCf(cWuMYM3l~;zmh*B&tYq>g$h(SlB=Lo^F)FM|Gyq8n zf3_yf5Ql<+QXAM~cYIt4=o&SM7TkNsyVrhr>l37j8&k_>=dW_M1{*`EWyO-Vju|lt z?#_a@hz}8mjxwF!n*@q_?lEcnhW0+1de{WkX0=Zyg>s4e+erNHK3c~_nw$6&hiOhs z9Tft}vTN`PFr+>lid0XnEer)CSCmbVn;?OjT^E=OuN6vmHd(rsadQ022NY-fAs zCU1f{(JbOZ(G3-0^OmlT1J*_R(}k{um`&0g4JYbEc=WObV#;7!DFdnu_5R8{&N5bO z0p6XavoWKe&aTeTY{dXez{1^rg5e*CF#uP7S|>Tu+txY&X6zT7n6&9CpK90$Xm-?x zwDIXFFt{FSnIP?lk?yth=T0{}(wHx920PKce36U*Z%g1mfz5vVG4-joF;Dt&Z>)_u z;uCyq;7hZKoV5VGvAvtsoZ|pj&^2oprlq8TChmHFI-XVGs^4zjo#L}GKCto8?7Mqn zr=1L2X_Fwq5(O6DzB_FsU!?d_Vypj6ic+xdtS%ylVfh)n9GenJ;*SYRYl_ z!jP39M1=f>$?1SW94J5bvQ<%;&RdN0>@XxPVf-Q4@0;7Nmc7-;C@PcR zZo{3B+AAtWChAQ>0<}!)hp(n?hc4=|64;63M6Q25qYCVZJ|>MfG#hFb-6=s_a+D1}^; zyV5{>h%Rq}P}10T;J+UEIzIJU#)z>OHD%1;Sdz0;{EB08mrF?HUFN>R-te{O3n{Pb zSOBilBS%h}%ji&jR+Ia%h2+(XJ4okYHE$J@?yGH-kkhKF$DI;PxyTD^H#VwPkABfK z>Go>_p!}rER#}F7ugmm?S0T|Slnm8Lexulqy9TzE#LfGLPJy;W!?WfgxtVKk_3&gH z=$H`HMA0wCvo)5$h&*Bd6|v_jKoL4VlthfaQ~=Z<-UTZ#V8-0;b@f{;_A}vJK{BnYIAtA#iHQ|EFZDqljmo?|z ze@!e{UwfmD55ax|s{g*a30{t~6vnNp@}O#&l@4QuJm?tfI3V4$k&NoK?7u?z+_z#z zQ#UUR5JPh#s??cBZo|lRo40&632DyMJg#&dssADttc&TO38YZ^p7BKgt&P#M?(fzL z9e#t?DEl}y&lg-@T)AWh>o=Y^JL*6*-ioT8-u|D3RP2av4&^l-znktKr!|`_Ive+R z%c3WWDSY)7Q@1Q4j8P0ECJyB!6EO%Pg6gc7ioSuxlqZQw%I4dpDO zD{b}Jj((m|zK110`I+pw?N-dmI568uRV&{ALZfUoEhlNyz}?GurO= z<=60lfIdwHIE4t%C3RzOJ{9&~IcxIM#aw z>Lx_&y}%|L3;v2BCLb)A!^WjD_mVPL6@cMBE*+z)tS^+z4##3;SVLa9X(caqM}~&8 zU=jeYz95RW4>DaZBS(Uk#bsDGHIQdrH7B!LO_D{pK%jfaD0Xvp818IdTL|%khQ7!C zR#6blY^b+Cm3`iPA)B>UVh%zSg8vXM_*?D-*zY?*Bd_a?CHgmgdbf^BvxyGT=sD|# z)bbrfWMK_P3#!g_rm15OH(c;(NLMB3Cz5R15tc-b7t%^;a=CcyYOUsxk+)NtT1u5gK!VUJ8Qsl{Q*h zN6JHeAELZr$)RxEk;-mo^2-hK$C|$+kf`A>xvW0rE3`2N6(-Ayf(Jxr5Q!Lr0C#wP z_B~ns3{0h}V@3!DW3a!L-uh#yI?RKPbEr{9NG9w{zr$STC3mw54wI;nLxGeg2Gh@w z$XG7#R~pEcKGr?9;|^=iB(=vO+X^E;i}{KJOZqSSN1vegnFykZ3u%OY`rZo(vK0j+ ztds)s{PwZnu|r~8PGp`~jo5NUzrcXC)UnxdT;f8)O8EW#2MV?sbZqvY`^D);tk6f- zB&3c5-+;|5lVNXwTw#1&<%wrYp5W`0Q?&vQ*mWQ^YX6DmA(gH_|tMav_j{gVEnfl-T}mgZWgcTgBJfv$sU%3?{H z&6RrlBUce3z`a^WD0=z*r5d_e?(D24px<@CP_jYqIu2ZoByFaXP4+j^0j;x_+d0-5 zuSLajF9(;Z#LDF1NQT`|DE4hHh_N96V@&?g(m6=>vT}op83CJ9MdFfq-j`W zWM`OLqVH0;Afb+yz2<74f-298{NNI9%^w`xkM+{_JqDZaHSYKpjJX(GfbZOKx5=7B zX438yPoZ+n5rvZ*DTjl6AMbjXOd?MDENg=A7J)PIFUr4S8i?k^2XK^jQB7iG{Xk@R z`~8D+5v*4GviLBjaQ9-A_A8wwBDgfX{S~SRpDCWw=XxC%%hJ5$$P&gMmLa^L+zrEw z!Q@trp3?|1h`Qw4*qgB= zPnY_rlfFVi-4IWU&Sp_ErcV5M|x)29|3c2PrhL4vI zd^(COD~dD*+aO_6?rDleh1;i5vTp&8kvOK$J;DnqG1bj$l$?&#`h3Z`b;LG%%ePopblUEcLv7M zv!0-OGc|>`odx@-2oNruw`Pu#n4H%Z(z#&G_JC_766kRn zaE%1Q#OurND(dK5^w7v%xyE$D_UjbPQ!~elgHuuJ$(c;pLnt0JiMgy!`Lu73#U$<# zLgLHfie`LGe>HC29V?UwV+hMfktDz-(CnU5c67bw<@d%N9B4FdZJ_ZsMP^62!i1YI zamcphND>YSolZwo_uKW4wtZR-N9Zk?LJw$=*Z@t-Nv%idC3Lxi$F8|TSn2<)#ICL@ z?0r8hBxfm@B*A*P)95q(x)*B+!j_xvG+@j312X(!Khx!#w=S@a1>}D4O^5_><-@0t zb=Y3>$;_9M6wG=bWXMopM*0Ini~p59R0f7~T<|sZw}D;{yM+UUS2ryUp=uJA`bOR7 z$pmapqrci+!rw3bb{7~t2-%j0aO8FMUJl}#L1-bj+Xn!d%b0Y!%o(Y&G%=u()d*vs zH{wQnK^<~0`vIw1uiCd<_n0y2^1J!`dFyI;MIzqvR?s}&5`6F;in$7h24+$WWOcqr zuvCEW@v_|GWLN;%IAzi>qulXjzE(nHjo9do150!q+7iik5^#Lk$Cqtq?zovgKLSIG z6Y$?f@!9{RtMdv*rhgt~Q&bgOuI1kLKN)?DevD2*r4Wt9z~=$3g%5;7nvdEnjU2z_ zbmS8b*OBYfnUGaqZ;FTs!%(MD=%{Z0}>xjtT(a&1KV-8|Ldz+HCi58fGJ%H|BrI|;dK*< zb+<89c{@!73*t-z7rr`&p_2xb_WFue+k}jx$RgOl9n_oQ(b%{tM2XgnGb}4Jtp$HU zbh3jT|3#~tPM2ij#~&b%Rs*Zc9F%OUjZ^vP3egw|fj+}4xYk6CHw#iWM|g8+ftb0( zauvnXfLW9aC5HO@9439+`$TI*NB#R>orBSc46@U*uzxD*OB#O2690YgW0{70XkniX zGpL5nvgppQBv-aoD5P$nhVR2JKWqG5J`%ys^VeRW0m+pPG zcdo9LjFVna7%1#Nic32$@Y;olhi%wn!}`wf=IyT4N8vr#tEP;T;3Sh#?$K64h5GWC zZje*u4sTM_s-Zii(YXgB9@hn1$2MQ+}NASI0s0aN+KtV!Y&5+UN z8NkPcDa~CZ$h8;U@Co@2E`Dr2#Dv}BvvhaB+wc*H|_|5L} zyXmdHvPa;+O8Hcls#_)ITq;nfCa+<@3RpWJ5$#Gz@B3q&zfX&WcO z8Yo&bQR9Qg%VJR4&E@*Bi+csST8d_ikogn&orBz|S2g6RgJ;j}9W`-)q-4~$6hhTJ z_Y4fJA{}r(o*%*848)yN{vUbj(u>b@mptY=KHw2(G-kn}FQ6Do6F>(8;z1}1>&G0{ z&(Vtkp&!1IkHyEedf&K zm7Tc<$jf>9<9|0$7=I1i);bcUSwI+BN0Qbv4s3m(lst=g!i_{V6cUdAL#{Vw_}Y5f zn*nhE=|AT+o4ls;SyiE=`=nPt{0Ro>=Fk56{z>0c40>z0MNdxY`9XUI#!6W8^?BaH zfzjM&L;;ss@)Xea71Vy}QJQ_pEE%2l{jmmFhT_S#O`Hfv^9a=%c(Zt5gSv)UqOzrK~?6Fw#*Gdbg4Onfe_vi%t0^ z?FE4+mklmE_0s~E4)+V&mzrP#128efyt7&Xy7-*pfcNly*_RoVGU=niWMimB{E^yA z1>7JL>ZDilxJGem6!7Y@D7=&QBwQE^r~8_1Ne)U67sv9Q=gqW!1u$ELAl{-fTCxX7 z^kW!-lo=^@^o-j_-{l)S_j7+NxE7H!SR=HL2uT{rk>7hx*U?`Rw=n~(Qpd{2N4AL= z@bOdh0qfrn=S79tisV@0v__OxZ;!jCOMEx3$ z6$gwRwl>#vOQP7|M&HL(U|b0LAU7Yb(dRE5)=y|+D%09^?mYn?*TPeVjKyH-1ij4l z^+!dA@9?C-2GZ1!FbL*QUA{!9@Na&F-qf7-jp}_4R>yh%W1Js`39pzEOCx(v%#RNv zQOBSA8`CidLqU;pF0G^zC=P|(R8>ParwKrSNdqw0LR&lk&iwUMa5aTH=yr6T3DOvd zjWg%1VO)@({ON%1F>!=lt?-3T5SsP5uq87_2p_l69$IB+|gO05J z8ju^0&!v=ovQ3WXlm3k6^-g&urkt}sMVAyQZKR<2M)^jK3((6M;NmUB9`+!>G!NpE zKbl9ta-3jV9_^F_OBKR0YyN#{B8!vX5?D<&p~n?q%a^-`e9gb6BGvy|LQdQKJCu(@ z3RMmelyJ%6;?0rj*$qSi;Z~-D3uciVr>Wv~_-|uk*fwc$~=8NB#d?|)&k@yrBmalh-$NF<% z4Zjw>T9)H;o9PRqbRka*&;M5qt00>anS1@+T1myEey{)w_1#mkt&X_TcO9E{+OoFEPc;RE%@b4;KSOsq zpqQasSgsxO-ONo=;!(C?)UkSE5qayR-F2bkE?PEm+pb^YslfSb6J^&pbR_q_uhCLwR#v;M-7DXYQ1iV@y zeuyr4rga-F4pyW*HWo*0i(|3nd!)4RNA3O*Q<>%O)pLhu9uTwAQZ^aOMXIgA%0T$i z6d+2mA8&~uOhPNmiKK47A};f&1$X4$N3)JKrIX8C-5qmibtst**H|PlUny~p?C zD4>K(&Ll1AnaaC+8%Stn-W*mQn@jhn(eheQ_N|lfiN9x~rz(}3k4EtnMT7BSEAzNF z!N;d=s51{y7@WXz2?Z^%o<0sCAtMptW^4Ak?XTX2lbm*FmIIdV;&4GS*q-IwWy3F) zoc;3Q^r0E>l89&8#4A7 zMq|wWJ{`yZ!~ZzmKeNoBFF8c9x*II8EPDL4yjWw-XNS^#vOs9smj#q{Vp~JDmj#iBib-N!VsV4JE&0@vmEDW(Xbf=o7fIhG~Mnju!tfDJ?JFj49 zWf*H#Wvv!`ns|#NIFBae#W6LP0Kq-Q>3j;&n0^SGcXU)MSIaA{*oXh*$f8iXy-htVs-y378#1 zT`R$gnpk3XA1Zq`6$*iHtB^}AE|S4tVQYiI8hG0-x;m$>8jviv*h1o|l45X6%qj+p zzeF}Oq0L+#-7MB3%Jm^hukj}XY`ZSV0*SUD7tGC!|jy~;39{edn*d)0gNC7Qn4J3{%ni^JGm}`T(qnmf9 z+bC&0(c2Q!kICi^<_R7E4eZR^19K8ZL-Kx1jAaEi??BKN@!~%N%wm62^t=0=HNfUW4ct@fiD-Rr zjgEt1_icr69->|U-+0Vs%%=J<)*NF@3XHx++BE2%cAhr|ShDNDE9b_W@XSd?Q$%(1 z47wfba_-;=GB}NiWrCCEwn>-ccL!0TV@ziXrL6ym{NA{MFDhnJa~Nt9x1!{fPWx3m z5O8G(V(tN8eC+iBpKRmBG8-@9;CF?&ia1W5DO4gP{{W9F$LMxm~mJ{`m)gsxY>yuPW(3UJeA9_5N} zFIA*XqHURCvRm__G;ZD~3AKS_!o+a0{eQDafhYb^h$2G1b4buI;y+b&ut0f6t#IB|B%!?=6q?+> zRT)&{Hb(~*WwN-sKqy0j*Knr5ED)}Qpz5xe{D~5e6I#Lv0_{Pv+fx^IK~{(|k%;*lQjF#@K%lfWl32Y*F^?D4)6{lB>WlNphlS zb6WFJ+@P*7F-tb)K%}kGnRE%aEDKalEkl3_wda`|#j_eUB*NAcLsK|?6lwzZ&K?E` zmI7?Tma_-QU{;}%mO_v|bpJ0HS_oUk=mSPkLBXoTJDR5R@IZ6KWdBX!9It86+_3YG z>Mh~Ua#&bQ9r_C?&<0j`lQt|PA&`-Bm$p8z5`AU zeh+ZCoYt6{?-V~^W6740maam$Jn?D&Uo*^`n9Yveah>gto!vk;MGi}+!$ajxjZtU#!U)(-(xbNCYFU?!?3|jy=^|?8Uzg8Zp z+~xX=0>{DUl+eU&7-y9lgx4JUd3UFkKFkHwi{KHy6@N>CU>x7Uwoqwyo2-yar}3c} z9m}_Jc=j^r#=ud#;BDGAUq2oc+)b?(P4#XqJCoDr| z8lpestl9tPz`LxJRF0f#MnSefoaTY?qTPn!8nby2Gx{IJgLT>S(`N=Np}cc=Isf%h znM1Un)fh~Nnlw7z63CAag5VDut3mkQ>Boj(4zxkn=IPib07Npm0yqByGsNBR z{~L*slg9bten$_GJ40~1Jlj96PN^R@K*I*_uHcKv7mPyhe>VZ)B)E5?Wb2^%y?8ld zdi21GYNUo=A@N@$Q{&v3niM4s2v6lPKBzljqeP#sj^TIQJFQn=%<{y{XH(2|$zL#9 zF`Gq0a_ovVffw5J6N&z29C)UGIT9*~yM`N&=}ZbMx`H+QhW7Is%iUcHG5@D^om5Of zM5zBIG)jYM`og6zB=RWFe*oH6QDLqI?p6#yYNtCxW)hJn74!7%hjdtfErw55@=$$E zrW?Be@hS`ofDnjT9l~We_T3m9HdPL7wetbka^lMaRKM>Q!O?S`1CIeTEfw4i-9`QG zwU_-wNO?(h=Pc!jJzvW$+2_&SM6!uF5Xld&0)+bsYAK^hl=>okY~ zy2Q43*9SL^ch|2DOH}9#^)~#SsL%{sR%51puMD&A%IK5OZTT zr-pf@ah=adp{4OVsYys{K#5YlHd7fHmf%eC!ezzGD@Q}F0UWm!>Y4+67;bJTqPq(R zK*i`W*^sWse9KRXC&%Pyp9;Zk^}I^4-~q_uPn02!Ff@2;7lL~ivpI?fOAWYa29G!Wt0ef# z>nx>dj|hhU2s06NT^~wHC_wdJ20c&+*C5`D+Vot9;8IF5K^fA;v_9??KY*l9mRduO z3zXB<`j5goQ#$pN)WzLrzn$wpknB;*D`i$cURVK>*N%_ry41T0^u_2i1ij!MjO- zs$hUwo#G+G!U(Sq2%$U=A&_<-kW~^w=`k=kJjY98QEk}qDpci`cU#R9`^am%K#{S; ze8CtM46M(ZVc2xW2X#*4YxxIuQgnnhbbRPK;2-QOB#R`dC}jHSEtzSU`E>w2*YU^f z!wTK(T_p!V#5MkR6iO5c6ayYi4wO!Z-Jxx>ulPUX2wrC=26z5mPkA=YRnuUDn4BiV z^GK1)12AYy5|qRG5j_PaQqxL9JeZm|r3ylyFh!TWkb4^z-ujPpvjb3U-X*Vjtze7+ z@CJvtdU*(ROzhYALGLp}ndLQtLiqIHn8iH(vv02E!+)LZd{zw5wwT3ix(u7Z+<*JYauvPx{hvD}Hl*f-gm#(1?hr3EI(r@o-+5=1oxciEn7|6Z(H`NtzvX_=B|YgcNrw8*138zQ+z>;lEzH$RIaL2l zHA^5vWZ>Qn+yDvaIIal70-}eqJ)oQj7tYc#Ut%_uh5`Z1!!RB}hs+wWcpL6Qex8j0p3%6$W5hYP_W=^&iDbz^yTx7&1?=FflZS zmYRh4Y~yv7D9{iOcag!srEy+36!*%ce^<$l*>zMlMYrGg-EiQzNfxUNP&Q)g?dUy7yO?5K2}GxZF5(hu4mGz=o>d!Ge=gv-p0hjEDDpfT z4GDP&6M6y;dv6Zf4(UJe#wB6sl4}hM^oMkO$N|Bf*BtRQ;Wv?u_(InKAp8aUi6&S8 ziD-2}{Nktq$R&*%gB0-f%e~!#y=3$dGS@|rb06d7H-==(npPcC(BhO8Lqt(T$%jfB z*y_SvO_VcT<4pg0L8B=mu;w={DlGm=;6lFJgY`>ftWo!?8sg!}&)p$4zeyQkkwC+O zNTIw8-Bqs55yZ)0QVi1{>QQfPCBS&ibQ~0|ft3S&)i)ZeWN0@Zm^%N;LP1KcU% z=4?>TY9dU>p@Qvu9B&lXU#5u0?mxHAfL1GfF{B`I;UD^sBgPOQNhW#loAe|$$&x^b z_LOX{_dEv}Q?xFC3@yO$j#~o|5t$1YR&722;dc(kv94#z(LzP7G;v4aqMP&-U=pYR z%S8f6LjZTNYumZl&xe!%W0YLtK=8BToZ|Ga-NFM%ND-~H&~X#)kdhMCItP$5rhj z|FG9jrhYPgxKs+j8twpQB^I*`tOh2t$;yM|+Mm1Iy(`qjdQiL4V%A|85lNgR4&aiK zhtQ9lG{B0(1X=mrmOD7jGh=XpiZaJeQy(g<8WH%=hiamAKS^D)%uSTLO@g==IY47g zRF~7B9o2Txljfs4e-J9cWX4QZI(<-~s5S^qVe-GsCL=R4lc707);Kh1p#4Bk0K$jk|lCUp@BpDg_(%N5Szl_t{BpryTO{KRLVQjpU*J_ zP;!*4a-R1ik;z8)^iLuZ;ORgaWd}%9a)zuWg{DND_`EhJmm4yhyN18Bg_2VO(JIOy zoreF);^XI!5XQK`^m6za&uv7SmP8h!n`A~;F#wX;VsbGMPM}QSQiUj2I=0^I^tnrhG?aR)V52%?-1F$I{1+`-p_zuB!|1xgYf8?5#z z*_8Xb4u;@=4g}V-sU`!Y3N|q?8EWs|3HKXmXz>me_WwfwZhgA~lMGJyw}1^b;*YFD z)AjQ@ttJ`9KdX{{9`6x>h9(t)g*mE zxU_!)^?@3{H83Ju@S)m29XdoeF9lBbFjSxoV;{L{I9dYBz1By4JwdK}B!XYDon)91 zRjR9oL^Ihsn39>flhVGv^68Mpe~$1~y%7$Bye${$L^)G$!@hvEa@4-MZ=H0cPE{F? z_>S-&elrbk4=R~!Uv#FrC2o2k5t&|x;TPSThjAlii1aVisl+cCsG-;196bPk)0=Qt*H+F;Q&``$_Evn(3r>S!+9`4@C|l`t95lA^+i9 z*vhTV5K^PgM)JQLWTsoZ>wkjKL5Vx7e@_syeqF2_ygc=505r8g&F>HCSbXE61yWC? zJoY8#Jn9X%)xZ~I%goFfZe>lEdZcfA>2_s?DB}kWn|03OrCTylb7dK(avAvSm-dKw zW7}TeKil-;5~Glhc(pG9xKMLMK+8`MG%V4Fr~frl!=nz;-R&GKHN6gUuB*}df;){5 z5N@(T0i^``%C=*U4CwlL45UTbxl9`@Cl4j#EZQyRIMpdkhGQyC)N^kLGdUkD2e zdWb$s?QcIiF&gXC9&R#9mS(ecI6dXRyA-B%w~LA^gV;aiKN6N*Avl`J1?eram652{ zTdErFby_d$wHBt)Xt%lvjW47i+}H0`9(4|WZ}F#|wIlKxY#>U2{DVrjslsOeRZSsH zKE`R>!_!>1MDNSYkfX|xe#+A+j!)@lg~GzsvTCs6HN9+E6Nd-rQstdD3ji^7b^Qm! zmMFodO3ApE>YO!!RgR^S)md#Fu1@Ku`=OuIcRFu{!|kgcnACQLlyt0}4c6p)8oXfG zQZq^M9}bTy$?ZZ>{AD6S07Xf1>mfQ7`OB=z>$!nTRdtr;rOchryJ#(TS z85Zn0&1pTl9(^`|COz3GfNro+&E233zJ_;u*S(CPJ_FzYblRp0@+p^ICIP+-OZluu zH{+~QMuS$TL2zX>Eq)sN)7t@Cve*Jiq$1Dw++Mg0sJ72UIO^%;2tonT-|aL$pZOx0 z5K}e-BN=b~ifP{=!ARvI{BWHu*ms2Lec%4zvJ}#a2o<|d9>7Atzz*#)SGC4%BdzL1 zXZqsfA6SqnT9*n=3DLl(fK<@JS<_hZPl_54dg#G5)TZ+El$HVl{IZCKk^n;C;p^FQ zw@Ofx%^Ab3#c2q2x|0s9q`Y)}Ce$ch#Du!Tdaxa7xOED4ww^Xk*U|j-ly+&$8tkP& z?e0?9!h*fJsW~yXyunZEYUJI}*`##2u{qAqVB;d9e~<<~l5G_aPCPK-n2J;##s+fu z(xhD5qa8nqAmXPHN4|*wa&T^F_v)aB8&8q4;|Je}5GUbd(03=dU}4X|f`IT&pQX@b zqe8x@0xmJ*tj`_E^7`6x(S7n%^TRV!pTVx(xui1KeitzScYV24rF!uZ-^d;j)h zAhnbv4uGnJ1KbQH>J8;5fQ`muHgoe(jV3%(p~(P^#CaKFuMmDBxXl@uIlRN$f?PaS148q15LdrIC~XoZBJz!^Y165YWS!rXcryAh)Ni1k+(k zLkFF2KvqPTL+l&S@_)@}=i0|twje4k9JhASChC+>XfFbt1nC$-aAj~d1b?D*1b2p8 z2sPPQZw(B^Nt`vrx^c1&IN;I(Op+jU>sgQ`ZRbA@uFBFmGN^V-_+Y1f6u}1We$~tu zJX5=Zb5JEC*am##UjKO-wQ5^+NJ9<(wIULF0ydH%=t*zvupICe_g@x`v3RSr?%Z2qO37boPi74OJZ!DyKSymeQa`0`6+w&aia1mu@K59 z`62qd^V`g8&!dUX(Ki3h#27hu{1jD#r?19pIp_0T)r&plT#+fh7Ax)?X(O!?z2gks z=+sM>BYk}1oXID1^<%kndZt|ZM;_m#~H(N+&_XP9y2JV_&5*l9QWKa$HfXr{FqrmbUV%%_2WFq zJABNn03Vk@4;qd$Ch)iDW=dxmJlk(d+<|cg&IkJ9 z{uTjAq~E18$e{%?`a&b+?$@nnJof(~PWRrTBT9u4SAC2|wo-Ll+*hzRKB%KUUH1!3 z`}>3{8$YCPhg9WG+O&8j>iXlh->s1A#mylGlUKs;5D}PSZcoZ2&jX_RGlNSIi9ODj z(kr~5uTKuh89d6Y4f&1}Kf|?RO()BFQhukVRibLO)dOWq%8>aj!=24}*X_DrD?D(_ zawd;<=g*{P= z`;Nc1rFU08>$wx{4WTp#VrWEH+Ux$r$hN4jv^TgPE=6)h*+Vz!J!Bi!&V#WPGPbNc zB;93ABnUPZL7PQTMGE;*kZbf?LQ{)9WM5XnAjgE2LjfE5FOCDwPNZwk8v@HFK85E1 zEk?Z*Br@4U&SVwL^jd1dO!8QpW!PKpltHrm)fF_@)zTYVS0g2Vb}BHXhN zd`MmS4zwPaks_iq`R}R#Tu}4@5 zA;2U@ha>tbrQ-&*MAc1Fq?nF`q0)_pUtP z7Fce`msG79k?3tQnuj zq>~wxox$`0^NG_83@1-AK<_HvuU&c^LSA5GhzMb0xX8f3;Nj-%7x-G{m79``>uYzJ z8!C#oWc|Fu!cS?WKK@Hra*>a@WZpP*S=Yd>%2OxxbG?`pPx zaH#Ta5Kg+Nrcec2SwW5u!m^>bdh3jEX-Db1CR- zw>0KIA<=yLHhFFs{OwKjn~CL9@k+sQA7Y+g6`kRVkDnm;-LtX?y7DUaIlFe-&#~r3 zS>v9YXIL%-o^rXY%q#CnIr@}u@F~RV=d5U0^sD$x>nGc&*=VNm83~$V?)h>SMIH^a ztBzmGP9McJGIiIjO1)H5Fs|S?v~#{FYS^f1vECM)^Zw3%=(}Sd`H)QFEUw#LagwGc zll`g{aeMR1%c=w4OZgTAL%PhC^wFF3aqYi4~x0qlH~ci!MH z{supI>r>xiXR8?49eppqgpSIQMnzbqZ+3(riC69AcRTz=Ab$CERfFGT0_+ob#a z_AH<8Mr(U;rpfEYC0Ps^RzB_XQg)5SXKg-D@cg^X)^5N}G`RNdiFuPN*gPoaLf*X< zgu(>hzSD&VdL6FMUdCq9Lo(05u1%3oh`X6yml&gLKPT2?{rePO{B7sxK9kAWa?KN> zN9Ic2(LJ$Ar`4fO8pQh!7p|WAP+v48Yd^;RvE|{@pwnRk24n1RM9wKP)tSDK^Ykn< z9fN=Lc`Eoh?%vSUhpihk*$UULM2p#aT%V5Re|(wm)pkT3e`8W&_GlD+vQW8hTr7B!QZnL9r7;B<(_fH6bYl?xPWw3eRaxl^AF zBU`I;wNHI0?y@;i?R;(aK?XkQ+P(MAMsMnc$C9`|D10-HKktwh6Dt;`s`UtwL_ZP&ROCZ~|&Af^s8g(w6?XA6*R}%(#F%MV_QKyZP?mCH`$JeeX zcq&C|=vj<^{A;0ihMQmC$kMFw8^`nHOQKyb2R=GmDDm{gaqpT=>x^*4JI!wOe!Czg zKG3Io#-cCoPRom=_sEkwW7Eoc(;l_|T85@?N{=hruyz(5!;eg(#`75!?y_?-TYfln0zfo@I@)7+QCiAGo5!=vXEb zv*z~cW2SMlW)Z)7bj;hr z#PW$N@oh{BUvN-;g7jCg4*q@G!PySQCI$58K9u-$g7LtlIN zC8S%=J=<04rsV$kl0jg^dJXdL5`Weng)cl_NjA$+knf5f3FdQ{5)-MR^k(DbZ?$sU z_{-7XUvsrV<3LqXR=^=oAUcF z`goRf<}b^gjUZKfqmrVzwXuYYUtc`$I%$~liD~TFS-lr$-sy)Kj-31=t4PH^37VCS z?0CPbw$a|_lBM(T*N2dbA8d{@xI0krRp$xvMNYJC&{9Wb9^bbX<0$& z1v6-n3+Ss)IbPXdL^>diG#nYaoTZ=rt?C=o(0l2c6m(>mTv7Yhbr9;h#>w>V#CDUY z=#Qrpd=EZPHtXd%*HOY`P(eo1my0*= zVE<-R;4I(24Y(sJT5GA!^7dvC&1ur92N%2t@~P;XfZkf!;Folsy~ourB)zmIR{K+d zK7Ou+UX^h(2`d}ta^u9Efu9bRT}{50`z2}7VLn%&*mEn^%Mik{By-xFQ?GH+tE-~1x=_;Y>FCXqMLd?bpO>W8 zwJ=vc&0ZV+IQXf_%+&dhxW+)bfdv;8_1LMuIC(KH+0=P$`X!W-Z=ZGkYCs)xe_U>L zqFLgL_BI2S=(wSY>L0wBCo-A#SdJJej8oA9gJbU%8P1)^JbUuC7WZhs%?Fn@?vZCf zU4kP#K|-Iug#@y5vDuumIs1{VirIs~3FeOl6F1#c^fWPHiMo7BPdOlIjCxck(qP>*I$I7avmk#CQC zk9|*&I0KPeB8e|4FP+UlwRe8+)E>(o&ojhrA{U&l{fm96fa_=brACt5HHJMaK4on( z-RdN+OD*+M1P6nRwyQQTrA9hcEn=79G3IanG#eiSJEIK?Wx8TjZ-7T5#FE=RcyP zOiP%X859}j7}Xin89Nw58EhlMBbX!JMF?uk(!#V-St3Luu3!>$f-jhIlxf#o7-@*` zi+HJhcInEJ;?n6QwI$Xil_go4p-?J;5z1h~fRAvEVC^~6Bi-Y!&9x-7#JnWB#I+>7 zB(S8u#I~fG&;M<*LO{hSEaHk+UuwR^fct-`o(!Ih6%hduhex?rQ-v6F8SIAqztjV_ zl-EtaGZ%q}QW}=GSy2C6B%B*zhR;SJA223G@I**bv@VU<^xW^crmcZ<&ws@Bli}WpVg?|N7&arSBRnI7 zBw?pkWjiO{`pP+hE(^Rmmw4tTyCefQQw&3Ygi8chgrfGXCGLEtR~KIio1SfDXg%3_ zqV=u+qjxygrHe}{zJax!Y|n(2{#!bgFTBT&2<3Ptx^xx{KC{Z!qu;~ZquRqs*_B8| z?2$NA$0IH>aH@_lNv1K3M5r(*x1DV~sTIiZ`owFd7mRAm?M$y3+8Hz>wDQ?#9ww6U z^zSZAsBp&T_p8r~z*i(}{UR;&xUH$_)r6m6i2s)8`BUOacF8u&iont1Mk!I(L~fk@F@F z*~RnKUjeLjdR3vvr{`3U0cNy7BND}9llK9~?f{FbXMJ_*70WB0S7%=dz2bUx<`u6g zTM&C0n+97aQzvWZZA~LnFhkghFjfi!r4LKf9)9?ok0s(<{%KR@Mz%8M8$jgDM%3^P zY_UEI?40Q}7a0)-Vg`tqe2^1^)43idr&F~|?5ymJ?ENqy*3TG-jz^r(qd8_hMnOj7 zh&p;0_B2YE0SRd}c(ucvN#RswsR^eOHO|&h8I=;Vqn|OPH`KDfS3C(g1xP($t<*CN zv5ZfKbSXs$Jlgz7@RSXQ4dchJ*rI|smZ}p~OnuBV3^PnKj4Dj#4B4hyJ0e~7OKQxu zLbY6pr+%{hj8_aTm}k4mFvN;s@MR2+VA4LnB)P=5baP28U+@(xr2E2%U&N&zU2Px> zEvbPaY$Kr&Y7`2?NK{0KHgo>@y$gG%_b%I}V;AF9 zhKJgq9)=$62%fFl50k9&?DB&0OiSM`egA^ZEqKis#~8=b%G}AwwI3=XDM6nSc5KaK z-D4YLIKjBac!x3epEn{-_Lx)Jp3y0k)13()6ff4EJ_nswlSDDAFkN6Ypi>@QbMXGE zcy>f4g1yJMN4CeIhc=TdyL9<78!&<5Kq+j`zJS!AJ^ttOPrPC`J$;i=k~xPlhuMNr zmU5t!u|ztZe-Q|f{}&#wIG>4V6VL6P1cCbNX(ErS>3O~DtlFYWJWDcQ^oiBbP8!3i zevfO9b&o0rqD?%rC-Cj9pkrj}X}~S1jCU{=`5w%sERAd*8H5;sv=cEAA|5`2{4|m_IOxMVyaFVkm{YzS-M!#%T;RZ_^sU z(#kl`n8zRooI$GSbb5s3eCzCe;5^tK!=CGyclqqBW32BP)|l@wo?$ox`04?}c}WEO z$ZAj*(|xJStmmTU?@L-U&oEtJh>j4W$nuX^XkP*^GCgB;Qg@PYLV*Y^=IhcG+WjROgbjrT1KLLm8*v`(nhSMvo`c&ig;dF(8&#pl}ujJgigF8|RHyPQy zZQPnw1;UEN;47UhpT2niIovdcm46HLTA$K~ln7gCUJ1{VSMTiIZYAk%bp@IjpOI`P zcLeS9F_aEZf#LG4HQ~;Ka4q|yAN5!xEHiU5;>=6Vc86_lK4R6-wcXo4R-<<_ ze!Cvh+LadFCqCg@4i0k;l@Z~UzkFZP;OYKmLb3$%)J+62$^1wM`wliu?{1EplN^sf zXL74k-f8GAHT2d8BI+S$H~l4P@Eh31?@y8nL?}=)I&Hz|!{V)$&=3)PfNgF~f>q5a zqs;c3Ukd`?N(+(s z&3NG`1mV>}Afo@Cv^w=u;VtpzsM@G=ztS>r->k&vDU*A7C(^|B#}NtlwU&doeQ4tt z76FcK4E)S}_0&F;UYsIa$=qGv4^OF+Z`Ni6X(CA|_RwgsY z=g;GgL;l6}1nzstC^_=CFCB{##b36m>AiVlaSI=)G!?J5$6WKYhPnO743$2)PY2b= zrrRwrI3e3s{N9c8?_FIz<9r`EVUnvvg3GPuu;WdU1a%~_An4hzHa0KgXYZ8MyKj7Q z&zX{MEHG#sH^SC8-=__X?FKrU2%9h5pmuF_kk0%9acg*GsQeJ@`mX5g46_aXr7v5N z_DCSNYcG8Z{cc7tooBUl_Ru75W;yB+Sbo2l%;bY>p~%?nTMPfPLU;O+_khb}%k2+; z3_?gh*TWNE>t=7rDLwbU=`e?^)J{g=CZ~6hMQkMY+<84Bt|qF~GJXe{J{xWZ4<>wD z=>VtM(O9(4V7@VaxSW1448-(3a@uLuh1cHMlE z0qjnLM3t50#);%zYQHw#aad&Z4A}kL$DH9?I>W&^+&#U}{3m&*>NY=Pm$i(6;g)x! z=iEr6`gUM^*(LKeq1u4ot3BrU2F&e9mZRp^U;662D{B@y z!hD{lzlPzal8TxUD36D>u8PVVM&G+(b|X3$XQV4bEDe89UQR}0pLTks`v-q@s&s83 zyG=w-)|G44h8GY`GDs;CAzP?~u?LTL(z59Rsdj)SUEqffVE<$LspxO$e38f@aO6Ha zHl#x47sap5>XF^>yV@1KhxpWbMAm&5D?URmc=g5dzZS$*!Y@rPIySo03PN4}yj?%t zSKknQpIChD!Qud=B(8ReHzrc3yK25(2R*3gGx^gWL3)JXnm+e4r+I!->1mEt`S5a# zjOdBmEtbe&772US9gz(Sd^38rCe(}55Y0d9RyieB03IN(HYvmIKqWS*ohq(%9hC>A zQe8!ke;m{NhfC~1pVP_b2Mls+tQT6{umrzSd{?g}dF$=2TAutP4eYY(GlP$w1Aoy2 zuEJNZNF(rx=Axjk%t8xwgo7kPHl(>49lV?}3|Kbzum zE$ORihB1b(LK|*O=zgBuz)tXE5zo3ZU(LDs%<+*qQ)Z1eY*xrBpW6o;x&k5IHYu9Q za-y!JO?-FK>K7hAL;YONAb%-R+q)TCLh1OR-@1|f^iKO^!Se05o7D#`lig~Ho+EVD zr^c_^yEK=RkeQiJsA!GHo4xAIn6>7IXf){7EsqS(FwDeLA$b26>6MUN+6(EE0#(1; z4Q9nQ1baUY2q;-w%^J6;yB`TFJRwh(b0|AE{WKvqyv{j@QCrswa^3yp zdE>ArM!?!wZZpld>gTN^+{9^WW4rwG>qQEsUh}414vSq66XG4$m0bb{-i)>JbXd)B z4#(w$qVKq%z9fmLEzY_D1qR5>=Sw}MUF_gY}1V-Ri*YC*pJMSj!|JZ}A47R!&0=i8h^Aur5!e)vH% zbkY~aqVny%q3n-@KivfXh0V)K$EDmo~D`b$T3e z)fTeI6@>cM;nT7Pm)UvvfGTt206Eq|Qmg!4U(r!v56j6I3Dtz=O2&_weawj4bfMv? z-D|;vMes$BseaG+YO{9B#)I=+{%e8R*6ocp0i6vLaij$qe$&+3GP}1#lQ(y-Bj_r8 zAujL9?`r>M7wue6KiONeE^#Q-X%G^*Ki`R4 zN$BSdR};CVzPYl9jsln5MHyOH`+@kC20ylFYXr^oL$Oqk@s{RwgpDtCs)Y|OH1O=p zIHzdm9kpvBS#uMCqm@OM`qyIW8pxq~YiSES?@3|1>lQ)dv{<-)%tY0YaiyLI>{Y>* zd+~U(FpTvN=GWeK$W!l+LFOWsiuv2~E*w*OTVSKmt#0}Q7i=uEcSg6;kyV@H_8*q& zqa^nhNKjIjRp~@GHZL?Jgt^mrfoHyQL2WP9cI4$a>@lscxr29W!DzK-H9E~&b}yjM zu0-*rgofG}e7?nHXXfvpFf>6_8Hv}Za9pAL*lDFk=>vQ6V*-=H+R+L?&Y_&gX-a- z8Cr;B!fU%c2SUbp*VSk*kCQ*PxO4x6Tv_|{Qa}sx%sSTfgT#$~x#zPUoE=XcoTjO_ z7v|f9{=hhl9|Q$|^;@W{UZ5Dhof81>&3?)Z=y3V!G#F>i^D@#W_frtE!!Uhg16^yk zAX`T4P3h2-Ww&r#EmC|v7K_Q-$<2Gpt98b)O{dHF*(&pDlMeLhqU3&7UFYZ*O3WfM z%^5=Zdrw5oLS_g_->Y_BhZ|5Bjms~aAaE3Ez6xfl@LIIDnq zUGNME%UF1vlAf@W`m^QfLMx7IW(JNYT$iPJVRACtCpyW3KYmTgG;!vJoNqp~uIs09 zRS?=O=(aDP47<0c+Jr+E??syScBU=-aZ$755T;e%@_C%l`VA%F<2kZan;#DvTnsPb zL)pHDH@6R&Eva{-qg{tb6~PY>aX&M#4&LV)mPhMvw={cSHz1kiHA04oJ2NDc(dPa< z>0J#Ob(h@dG+weN(}K9w7IomRA*XEzh(c<=)xvveOSSY*yQ})PS+|>8-S7L0F$;Cn zfT}S(rCZZTls+C$*fFjVrZj}(zg{mrim=>X9|zAVJl%e?z_})wYh0EKY5wzed%4AA z#TDUA8GiB0ldyk{<|osYfApQx2U9%TaciAbxAWd>`&8k*=adQC-pp^kj^^Dq{?N`c z@Wa`8x0g>3Rb?XIpvi;)2m8W#zc{Dl-V&Fuoz~2Smd1-RbFHqu&Q!~==r_M*zUQW# zkq-2C@X`J;{ZmQWBHV4czUi8T3vw|hU~rmL7ib%`axL$ZNY`&~P=M|F^IEwh_s%0# zt{~@aw3z~;@Xf)n_5{3ww`Wwr zJC?R~6vFFZSgnS(=ud^VYOV+y!W}r#cZhiT=%=XXHt@r#-xS&0i1 zW5jp%J%0;FW+kyLOpB2*;5I+bN--HKMZ#k%UiF%Ad%JaM4wXnYfvdn47tq{bi!V5soH1}O;_B$5P zEaj_u^h3K%%PPvZl ziiYrdJKO>lj!5Q4qq?a6#lP^>4qLllSWIZLQ|*vVYD*Gc!_4cv@mjPBXiwdhTLGU&H7mhp)Vsk)`AHcX)GSb_u!`2>Q&A`XCSvUnp%JP`Q5HF`0bAv z(bVeT2zw>c1ScoFg*D9KVpY5!&EA;&J6n-lnh^3MyxT%{(m&Y6?58V%nfYNgcBj-s z%_ksg?@jfPs3K=_{!2@C7O42G;O>UC| z*#5j}1MORTtcpp6m-rTnfAwA-L?jqg|J$o(CZCPMxkhLvd?Ih}+_>FO#SxTF$nDf* z4p4+}Rr16ksLY&|9h`#}a8L+8tgN}lSox@CT;08uL`Xg4H5Vx6!m5=tLaV4AMO|_Q zH+S;Co}O!_hzql(i8k`4J~o&WUu^n@Y|*oqvO-q2@Y1vM_uf_Gepayj@KPnUFXRN1 zyolcG^Zv`6;qCq^^``Bv)WYHygZ<(X3G-$1u1Xt*)wl*Xfk1p`iF>P5@7ha;A1>Yp zkvq9-+cI83(hh|1)MjMsr4_@$U&%vJ%EJq(%pD!*$Fxr(yN=C}!KK1#f8ky#VJ+UI zZPV3(+@vq!xN=y-rg8#5*IcczOzqBr_Z!UGw^Vzqw5dq@7wUpHzAxTaLm$SYzf0}> z;EjDy$GY0_9O*ZH&7h6`x23`aOIy9hNOPk$sCxOm2KITbwINrq@n@iNd|3k2JFXV! z+qt5|vm@&R8ILp`m2P~xx%Ie9tOZ^-9zng-C4#<})(s7kB2_e)ky@)rFWSHUc_?*e zVm0moy_1wxG@!f2gkG_(ddAzp)$2jZ;X81hudWb+2^+$QhE-wD|I8?+6b*tZ7i&oi z&m&$gn4sdtt!6#9TKa0d)L&T}qR#iD0>Xm>y(X0i0gh{bG%UJY@9IP!7SlL%cq(4M zOt)J6#MfAV`&IukIP?Bff>3x)Y^YjPcUGX}=xQPE9!g~}&};O&v<&p-oW3LB%lX_8 zxY7r^aNz|rM@U02$LIfH=-lI(`u{lo`_{Ll3xz^1D-tS}q`7TNZlPQ&xvm>>54p^2 z>!RekTxtnh5-LfExo+P`Zn5M#w}r8=85_IU?dR|F$LDd*=ktEOKc916=k+VNLtLd@UsejUUD)*vtIkP{{ z{cNa-Z0VV`#n*kD@B_c&lykhv%07gut*oAv`%4QGrQr4gqA9N5H*9q=`ez1ju1U1f z)wDLYQQpfLUi>&%Ws^l>fWS^i|2D+ES%UZSpFWi%f&Ntuct35F3gw6#yKI`9z>$;F zI?b`NW*=vNBtZ*Lhan5xTi;xT_xE4^u^CL=-N`betr8+?y?@vge;`S+AygO`)qE?d zH_tj}!>CHjr82?HTKlrd7+QMP?l-3aK@{Y`Etr?eu)Apvts<|$ ziR~`+swY*TeQN|u-7(@sqVBqPQ(Z7}Z2d<+@mf%pyqZU02Z36J_+h#!`CbG)DGJS% z3I1W=eWO0`)~Y|YdTnVi)-SBXW6~F=94RVMx_82swffsg&EB%Y`PTTschq;KQ&>3smx%F* zH&mWY+^%p9ks*f`2_$z)tBen$uUq=(DjylTzquNC>sNcMmDdz7332Uu>l)JOJ?BL2 z%XMYlc9>uPUr9yByWH7F)|-Z#wqlPbrDCpz`&p7$lx8|&QbTac{qi-UGTQ^s8rcro zXD&LwYy$f<_2$djP5<=e>Nikekw>6j%O%)HR`E;qHw={iSI);&;7RP~y%wOv@yQ(p z6Xlh)3F?O}4c{HVj2O?2I|Q?vmNbuiqd3qnvWrU5NV>@I^Is=Q2T+=je2JLL53Mr+9?8-V!dANYPsakY;h z>G6D&3Fa6b?;QDVxA+l%OXC)b@tw!ltW&NB)GB4BsE$ABQ{>g_>D7TwIC#dJ;h#(N zZc;KcT)yo*NESH<$`K0Q-xhxJDVYrC*0By)$zSr{=OVd=_>p8s^yJrkZCT7Qk^%bq zDp|f?FMGQHjJryUU;= zHG$+esz(e~QXKS8i4PWeh_`$)hYFp+_igT%&9&5XCZ=M|6H()<6wF-Ep>r@C_n8+r z4Dop(S;p)&W+z4ir87A&2cK=C$e%2E)RXu+KIZSxP-6>~0zk;~S{r7gMV(d>fSSw{ z&NQN@LV2R5%1$b``yNMMwcY1j_&s-OUp)6EzCl`u9`i7X{`Q%YnXL#qQR%lhK%&p& zPINpR9lr|p8$6TvYX&IRnmY@mj7Ss+byMd866~iYSFk$5j1lkHqJ$gO5Rb-#D_cAL zG5Xs4&S_@_~!5*j*VEPRtrzG*m4RI4qqXMPyR zt33+!V2CHk`L17*1}a4-=I3V+Ima#=R~Vn=?m3lmtT8M>VK8)fa;C*L#ZqouZ)5$+ zX^&;HVgNoy$cbgcAJ)~iS5+luf_G2UQA2~bf(FL)C?pyWSN%w9(7I{+NK@ujFV(Np z!yr%utyxHVtQF4lmVAzy_)3n5mvH?Q+v`&hr1ZW40D!aFyUKm5>N#vWzZagdq}8-_elpuU%35~(sH^|8m&A!L zTAs5?aSZ%aGhlA9a(iOI%|h{hP%La6DQ$hyr(E?Tbmh7V|4Yc06@JU+FRR#RNmE9iJHWpol)ydy)>uFd zVp+Npq^r1(TT(w+sMg;!r7|-%vKK#a{ zlv`TWT~5-;8R)Km(oXR7Q^@ywFA@C?DqBRB>ht1?`;tt7P=NgT;0=)$z48) zOyy1TCO@PoTuqnZBogHe@gIiHeNK{v8nFHw1N|6zF%m-3hPFS5fLSr({+AH##kgXw49^y>!vkTaOUc(_1= zoN_i(bDht{F-L-Pgpox%tt|Zi+-uz~o7zm;*vk{_SGg9$8>^!Is8G%JgpuBFdO%ih z<6+!Gq>@(=mhco$v zw!}q$)fU&AD~2Y6FQA7$VfMRF*5{dN8Yy-uUqSb$Ysda{^yt)+aHQS>sq@`8XDtKT zYd-vx((&6$+MZoFc)f7tie|~TqX^+K5Dedb+eSU9H!wo*(6|vk{Lfxp7_+a>UPN2a z_Ke~D18fvn$u~?Gx90dvTc-LzB#H!4if~fY8XW;nZ;Igh&H6dfD{7YC4>SxD$9qm3OwQ8`*_GUKb8w3 zf;guT0iG#ufOi8TrvGdQQS;CA^OB=D&^`K0R&QTH)t`}EP@xY>HGs${=P(%r6|& zn~T{rkAO$QA_OLRs6B5isr=4K5#4+x5mq_#QmS$h?*DqSr7y9^w0NvSR=!a{A~Xt4 z9-S2F+xGB_*UPOfL1^B7LaX=RF26{|X-6nMx_5csn~W6i2*K38$)q8b2uLbBNx36x z6cGgNi0}}l9lYHrNK;xtaa1pRxnQG~yP4nnSUsiuqv*yB=sQd5>&ugDvhqDndTirH zOM<;BfuqL+{&@+gep-N!Pbt8kedPm{o+XGLCPbi^_acbR$v)C)@FaU*MdN&WOM_s9 zpx2?^BAY4xoV9Ye_b~PZD%7SjGzcf<>B~N*l0Az z%D!>q6M^;;f4({wI_*AA#n*f;npwb-=?mBo(W5>Jy#_ElFH(}6yXZG+QnW=4GdcCp zM^gNt07m~&SsrQzV$uJrEQe_mMEf)%;8j7B>{it@{CNrw`DL?5M5rIux^;Ax{sGQP+*?T9t;&7=bG#qe_+}7@Rk(U z5#=;M!;Lc?)UvT$+I-%|x*3RcGT%p9ovMW98|gsKuK|b}^9y*lmC$1y{;?Tzp9v3c z$v3+)Vty(*s=v5}%`(5&i`iXniAClisY(&xB9#?n=)~>_czloGLZwLnY_6@((@m62 zRgyM>I>=^sIt9q9|(PSfVz8sud}KLPPo?*p-0~{CGRF8N96IB93%W6-|BpA(g@e-;qQHlY%-T2 zr6@o#i9LNjq(2KlivQZ&l-$5jU{Thf_$#H7CD$N3)9 z>asDZVrZklI4_~5+K@nvE>#QAC}za}n-}vO?%62t$wTsoFYAZ9R$R;AZHAgtbMyg%k2E;t`wW(VU=d z4I6sAvQHKuy=CP9s<|Q1_!FQ%b`=3raO~7gOZ1M}Ei81r0 zIx9B$FgNqbxsYu-S?g&vE-c4j%#IGSG|)P=Dy!^!q&{aLJK7oax(3EC=`=tW{+936 zpR3JNAmGoZ3OC*qvc6O2UXrPHsqH@(MoJLL1)cz>RD`Rj=|G<854#?-I@JzLUpWm~ zSvD2}?2*i?&inp|)+4lR)4_jyyn`UBmQ1p%^Afj;H{e=cLW9=4@0^{?3iotgRP>Fb zAX0$>_3w@I6#K2q`|`y7`8tz{|A}c_tRAoK<{=Qv)(d0j#<&!p26$m*hR!Janf_V3 z5$>kX7v{JLK_iD)&G<(R)DX!WyWC)nZCV9{=fa9mDc_uWDd#z8k|n(H78w zF&uzi)H@v|5bK*&~#1y5bGwswJb8qD$h3>xMH1M*3SGw*~yj!Q=t{VY&UOO4# zwh*kXL%g4Hs}tfbqW2h0E+81! z;Id||t`Aq@=81;M>H1M~4oVj1yGNs2dizp{#f&+<<&JR2LioM2gd}KB<8_sQqA6#y z&Vf?e&2hVX%hdcIeIyQTBnA^L2YH+$!+t&+>FLq$(Cpr?dRbq%8JZ{YvNI!{ETj3U zm(z4@TxSV9y|uz3ZN0T{M-n{GbvD_rLBKPg6y4~FX}N8e>f{qWp}I#mLa-w*hF+{h z3OzjEidoH^6yI=$LuZRmf&g(jK2pUVk-1$rZ~A<9F4{R&_q{6{{VuOzc4r#$?mL>b z|L~uY!EU?{3VupQV%@zQGj^U5?=h=@e4!_VIjT<--U2P@!AC)QaaS*PZx`5f_aYrt zQ#|#>#;FPGHkhG^T|I!Y8__up%TqRH(yKAF1-KIF zV(2k~M3`rJ$HWo+y7JEm%NY*%hW}0W&Vys^;g~AsKM_P+4mI}qtsrOD&C97Za>Oz| zAu4ZUd7lm$eRSUPWELAmxz{6%Lggi~S=7djJD9g)4vtCDiXD*q@5Xct9M!FP>K&im zXa5(H$4lI%C)f+pgLgINiGEY^TCn@mNI~b}Rdh`q>YwBDNk`M!{tx!)NboLZ06`7L z2lC{%6xZ1x*tam5cy2tZm*6^E=%X(Yg*zhZ-Ps1=%C56V^Z4OTO7lcLxbi$)30%Tf1V%~7p<9n8BSi23-DVlDclPC8mUX@ zF=J@+Y;Cr+w2rY`wOuM`x7l(aR5>wQ!)MpcFi2BbA`FuWB%;V*AwgK@`+lK0- zxpc&&L2I$M=z=X^!3lYmt&4fQ5B4714U@d2)Y7~L*TXtEbjUdn@NGu! z&Ys4nka?cLd-vI}fjrA5gS?op0eQTR9z7O4(6#?XPvO>%uDp=6 zI{LGQQ)+>khiC?n$Llrsn9bZCyk?yS_-g~F8s|j&C#B_(uuiF?I<5@nsU8+5|Du`H z+j;bhA} z1AHOde>f}+J$p&e`VUDD)$1!uzcuttZ0Yec z1^WFmes`oEOLgN7%@$hx{mF@!(Hvr)ME=dE^2Iz;vQCfC#Jz__-Bt2w&r>C+eP%Q< zE;}dX9yVw6Y~n$J=$%#%G2=H>TR-Y_Y>!#6au3UXcll3VU=Irz{f43Jt`{{_otLm- zoQBLQVPJ)>vsbqDz~c|i^B&B*POH_K4UXgLGZ5zl)&;kpkFqW#?AhKXJVI@GAi$93 z>?9YNdYUuoPO|9v(=&oDq3{5z{m4H~E+T`s_(tb-mc75(qb>m6k!F%jrDD1ebAShZ z=TXc1v`Cvfvtf@!ZwqW7fA(hctWog_Gqod%#69dC3T^h7<1LJ-(rIw2#9U-OP{Wnk zEgP9-S6k$1aVnWVr+zxws?5Jjz~y z-{w1i9_O9E0j;xDC&^p8nfmU94QbfQ_GMDv9W8VNFq`#2^w-`7;&o}|J(!J&R}lQ( zXM@_urjTW!MBFO#xjg9!)8!ZwbitoxH~^CreGLM$`+?6i+kyou_tTU1}xheUc1&)u)Mn*trWTFa1>;W;uCI zSM&|D`x8n+$zLs5mCuR4Ao2G3jf-NA%`J_}wgIL)7s#p4G>_x|W{akrrG@MlCm#aNvk#Wkh($&+5Nyy7gp8&S+ z9dh*sYgy6>2qf=;0U1GlaYDqT?wn1b-H+6R>;pS&&}+_d76$Q(;C{aSqmpdW3wzpD zHj_=9{q(%$OpwMc0`*PK-(XY6@xgjY>iDWp{-w>yp2T)dx+A}3x|{Ma#Z(Tn zPiG^pT{3y<2%G)|eFuGf)7Ly3`ZfD>Xm(Yz1MLZ6Z0=smzHHKr4J}~K)$K)BXICBl ze_4byNV2{)r2b?!G;OYa9~+H7FnuAy9|Snw(xzG8TJ+g&J$()HWa5t)mGqdIUF+1z zx0x>8myM+W+D~aowTk>AefMW$URlwwTF~FAT`ac%9$6t$OL|u!d(>7Er!fP3pNrDN zZcHHF>aukvG|Mg@XZs|Li7iF?+4#+5B#bx~>Ak_={(SY28~*CSF!ZLr4as{OqHtpG zdEvQ?Rd`pk{cKFW;f)RJ3+OiaP%U!Q_9fEOxDuve9O5`qyM-?^qwOX++mGEkTGvPp z&!G1>kUEY;(j3+=EuFa0P56eAvlm>a#nkOW`{R)$KkHrSlh2rupKQTpT5ok#1W&~G z#7L_Z!astZz_w2P+6|U1xWJ7Zn~(Cv#_pl;5Wz684#cV_V_)v(eK(xpMJPXeU48i2NX}nz$0vE#xnc; z16_Vg6a+{;(m}y94;x4b)CaZVt_6)hJOS;7xzKz@_QNSnpOi3jUZ1INxe5?)Q6FCP zNGyH%m-Lhqpp9RZ9fO{MzWesz?+26PiN}wSY#(>|9Qq>u7yQ+@B5924TC1JyVt#>U z@Eov~ygC7>#d{v=x6h4W#EyvNyn;@;k4f$X%YKOO*Nkf&olpb==SQ+*O@UYD7djs8 zZj;x)B~*h(-?Quzs7*xPLi{2f)RtN%|2>cyK*d5fIh@{9+%_6){Ro>-;efdG9FUaU zMa?e2behOUo(v>c5j3LIm#XK{vx!YtKLgDFBsNVZpB87MgD&vnUjB{j{UN*ADn=`! zWO|HEF*|$vw4rM5EEbS~@_%=MRyXvlB@9_;^mMfuJVvhGHi17eiqah4r`)*4+A8hC zJHxTr2FUmI?o>Sv;mJZ9v%=BboeJQ&pf4M0%`@f^=eMkw>}Ke=fw4 z{8k+JLqNuw;OIf#fZ7D;?%zcNkn?ZAtf)i$nn6a( zN4oG;qlG2Gd!o?h_*Hy1x$ihLxci7k%pt*VIU9);s2&V$$>}0^ow~6&uw+e(pOGj_ zeZh)>DP*_oF1(dw(RY&2v|qA~T|D;;H<@aK(PYQ>zxC^Y=Vc|CZE_$wHkhA){j-*d z1|Uq@JyOz_Y%qlE(gr_DXN9B352Gbx>}V_C0u@axd6raq(gNoild|dwJz29^J9o}k zW;7=;K)9ycVFQT+HBWD`;;Z6nnK#2vk3amM7EC|XMYm14i&Cw+HMXdq5qopb4>Thr zRpdhJ8;}>pWvpVJlf;L``F3##egl!}ajR-nM_QySp^{vxIm+4S@4D>PsOcwf!RiK} z{yhWz%Ed)kv0REiL6$Ust{J>Q*hTe0xD(3@z&A%9DND zkP#+`mdbB*2xEJ;{5Ro}G`A~$rgj5Qbxh!xY~8sFS9+OWoE8Sxn1sIU5!}W`QI}Gd zYr-r!ccO3*8`8G=&iy?76VbwENq;i5#4aHpo#W<`&|01hvLRX>5dRw{|N3fRSHN$1 zGJ8yXO~Hz9G?Pq96)pMwCF?5g!;LwoTFe|Yk)`5X_>LJ)d-Dci7Ee|+%D>PD^gtP; zOzk9j8lr@Bxa3=XWH>N4v1<5vV!*N!*Y6hISdoJ2{kUR7ruZ{r1?f8mI7)b@mQO{7 zHmK@mEhn_@y7iBA%JL}y^Ahf)kci8)hc;RS(~r?uGpM*xr$(e+4|?e4#KS9oZ>65V z#)ju6GcmN;z0RML>6qIStq|2@kn>F;y%(FESQJ>ZTzCC4-{@vCAiZVoY({*3?YuJn zKgrMA81fyBHZ`SMrs!gZljj`7Nw#zN#P+W#D)!7Q_IT=D>dR_j<^MY2t&=UaZFGOV z(a7O3oK-Z_;r?-UW<(fVa`VCSaGtvITc;3xVZT3g$#SiWB%fCcbzHdM2Q7nQBhmQw$R(cauCFa^N?@EPtDEq+W9 zvbryuY%A%rY-`Rs%C^XsrZ4BYo~QM#5&yl_hKAUVYp1ujcpOgxnUU819RBSrH3<_w zUz3~AR*rAtq^%xq=47|=J@ zuetqU)Bh75Jt(wNgDYx&E{~2H^-93q%8GlV#-66wkjE;cxxX@2)t-J4{Cts2@a?G2 z^^_@=T0UA;%Z6nxvm1yERI|4et@+TG(YpO7+d3_3Ix+u}D-ZjPp+@U-|Dt<}fd5`P z*3#r#IHNL-&pN=MUsbf_r`?FNcyStKVrz&B5Nhx)qbX52XBF2wS8hwSSx?qVr}(~i zHq2Q^l75+UTke)$PN>tW7kw#JA*9~mP3&Ix45x{P?XzuAf8Dv9CUqwoD`h_%L<@x~ zNXjQI>s*f~mVKz%NKiTepL=Wrk%2?kWllhAI*SMF+RAMd1ZTarRDFi|LlatMaX{en zgt5hrj(e;dPm(!h6p=b-9|vnJ*u|=S2>$FFq#((G3W-BeQ@&3*wt-k1wP%)`M_s&J zN~ypG_3w|EJ9D)dqiojM=Up!LsC}D=*F|GYXsYjg-e8mCA8Yxz5CI(SMwm5ZKGj>i1fsRiCWTU_2PxZ?s5VdnGsQMj( zh#u`jv4(6a`x#Enivb^v-Y%{_6W1#K^*Jq#|mee11tHzsYG} zy+2P3k)0u33glrzb_`VAf6&#-mN_)bIr9?XpFVm3Wi+0DwQKbAktF`9w%%yty}vM< zZr?Q{8z*F0Em@V)BAu*UvI^bGKbcD(^J~L=Tg#6xSTDF@Xc&?fweU;KfV+K2joR6m zz}TOGoqh?G`MwtRY|bl?r}T^Do$s(7Z$p&doT;LT9Oya;n#YvU1vwdO`x}#Xp3I;- z&Cd#2R}0TZ$oQcBE!uR`##(s)zSXtHgXQQhR>H_0V%5--ySm_APz{23cEASxTn3@O zg!^H7{lLWMrkmKMD@dSv0Anc<0Q7H>(VZTYKi(hjz*`#L&M(f^$0 z{q6T!x&_^=0UYT(g183`u$1?Ex~7_sUa7Pi2M*j~o$T#79Z`#)B8Q{*gZEjUqb5sM zUw4L~rw_1}Qzpg!ol#m|->1>7-(pgXb}*tWG&ttd$G+fMQD1dYiwyXH8eSN@?sJE3 zPwLD~q&wdtNNoxjq)!-%qEYXtd5r$JD}w}#7F z&48xE3UJiEMVT0y=glp6_O|@SZR+x!1NcULp6~HqHt4j3BrDKVDIfRi@P`$ysxS2& zjuTd?D8j$Rav=i>6Sdnoj~0&Nzdl>lR{kQabW$BKZhipPJXCnJh3Cc<9Zpc2E0o__ zHC=$tn6HruS@nAb&ictvU;rj}?0nk<=EkT{k)x%=rl6e9{Wv-icXN5*J10Qxr>>b+ zXwX@lg-x|E{LnZv&EQVbX7^I2m4#v-YDMQS=y0zfv(D!iv7>f^`UvIr+;pf&0NN=IitT!NqG0k()nJSuve&#Y4K_Bk7L=W+As!H)U&(R^PV! zDr@fdU+{!}w@h)X|RdKYK*m*)>XG{8xpM*7Q_~i@&DR%-^aMR-IBt^uXv60e93_4qr zW2=LD=kMj;8S=FPUsXN7hx-;e{c|NKz{0(Xsp4K0M%o|Zn6O=~t(O5eTqySHo!4*u zhWz&Yuoeu+;oYOozc9j_(vkEo{973KYL*S;+KW~f*>;Y29mX*IDYK5v+PF<+`TVqaU1b|`$;Mw}Zo-o@ z7H6TghX-2>lbq?N?m;-T)`7ZYQ1@V%R{RDgm&V+?1p zlFU;h6RF!9sv2XTPvJI9HCr$>ot&^q7FM}dusy`aC2P{uER%kh0Q@pa0R^g*CtA~T zzZCVo!q(h6h?ov=>!*gF#(TM}Opa8`&7I?2Y^~w#h!ZFZYJ$VWO)|f5kvFUMgc_ku zYz$qF=ssMP%MP24IDtc7zS)Vdnr#%Rg1P3x!xJS;as)|wZ=L5mMy^m>$mL;sbpgg2 z%Ns$RytXLIu*6Gz;uzWGG?{tcycz6w+8_DdIeR#=a;@tc?we`l&&Z$cqOXCC2^V)R zjPCdxxQtE~e4d(T`;kjf8fM~rKGC7*Ic0j*Nyr z>Wwy-Y6osq?k_a2TOhv^dAc8-zM45E@3g8e{e0L$BGAcRx6UY(24dhDQPJ?NnT;i zff*7K>@r`woFYCKWcZ_Q8-?>eP9>YVn)21-WmT*jzKif@Q*SQil&(lA!>Nng@mhXh z1$<*7^R%UX!~Ql+`Ip+V)Ul_lHAx-sy?tSZ)uUMVp9twGEO2xuXa75&G1h4EzT@oh zB8u?W!GHeBU(#)1AX6Tr-c5T(vx594&-343G=r*&@%n$IyPgv|qL$C(XvoZQF1Fr0 z!QjT9N58-bZ&4D#JeAEzFUHytJLyc8?=;_OGX4%i5xl(W+wjRC%cU{u`Xk{EB5SBH?$@pUlmeUnq@j4Poi!D7Ifk|V`x5I`1Tm) zB!wZ=sCls3x}ZR`sFyk#ZK|0u1Splclr$A+hBZ-cZe|Lw#oKjtk;yoDuBYQ5G{PQ1 zOr6^LhYt9ot0*$76Zs22n2eog`c%wHY$L=cmM<*rJ9SH#S+xoVdi*4>*-SJ4hogqy zoKUQws9>UxZK^o@y>JC?j8QP$tJ7xVTvaZHD+^{94^N|oMivNDgDKn(2g z=N&ZMFTlRA@jnE3)wp|ZyVF==+Ny70)ua$J7zMp`=;sQ-eKKAoXy;BqZ=symOzWr8 zzw=#6w+#S$Q$DmOsb%k&adIVUFO-~#W*yb+;M5%yj?LZ41#G=WxWa9sHfRbBwi-4( zt1EK&nDP4SPyWzJau21n@0Cjh=^+w^SOuTyw)@?GkEXa?59dByv`&%6m#+h}Cq zXhkY#_N_09#Xs8y?2u$LT@>RKi5}(?a-_Bf50xK9cUb%OL7z!heX2ui+_r9Dj~{$h*cR z9@)mRtM8f5{8Z$)700d1%xeXzw(TcsVT+(!XP$(MyM)A^(*W&#^~vy0v(IL|zzEBQQ!J&B7xJnrCy+Sr{T)bE~uv11m#ZrjND>uAtM z=$e;TdV{Xh6raq8X=Kn3BR^8;hj~U+Nye)#&=KiQ-Y6=X?(4t(S<5hjYZi6}hxNK! zJLk=Fk_A5ZL3^yVKB$f6#LF)n^zCffMuC4lvTLp)!TD&fb!ZXbaK;Hms%P0lQNfHA z%`>EWZl~KT8TNt>Lc`@hcw)G*5M}!ce_DZ~c!x{!#OQw-OvnQ(xHQFw2gV$4I&nV9=O^hu+W9@3w&A2&H?ULYpCzWnR}z7Fjyk?M0?R@4;0*~e|jj$Y6b z66k&5UwCHAwiTX%?^x7=U~5AJ!Gk45_!7J}5BK58zjFi?8J|P8I=skY5^^%)?<5(FI_+{V9gNdsKrw=@qjIH;>n(BhsQ&oGUN4U1nh0-w%*xj z<26g!M)SqK^|J`0kFc%I9xH;ZC|)kBrGEetb#)r;_`7NLsZr=V-*Fh1 zh(D6EnMjCFvc&p@FKVuxau*v|-RW>3i;xvbk%Yryw4f8X)muqfjSW>Gv@QQuUS#l* zmIY&O(g64ZKQkH?hW@#Sc>+tXSDQN*lt)IAuM8n21%J&c6#9dDpN)NfT4 zaEypaurOo`$(ewhl1kEegI;H_;bHqiOml^u^!>=)^ zCoPCKpO64$Kc8w4&bFJn>l4eNdjyqy6q0dI3)ou{@xwARD_Rny{PXd6RcAe(`m)&1 zyN64!P1;2|Z-@?7T%hv~7Pqz~*eb5bq$F2&7Hcg8^hp3*;=j&WZ^O`L$@gTQTc4MW z|KF3a2CZj-wUlFO=VTm?^qS=tEf$5x88!vw`2D-LC-b6da@SESxyR}|ckJy+`N!<- z1H1FPjvQY3quRLl2%GgWu|oLaHSW&GL^cWaoMoI}{t-uL-mF}%BupRv$QC^m;ZJ%u zqf$hn9AcHujBUgDGxF6kdP`|G3M4sw}G@ z=ogWoNE3D9j?f$}xQ}TV9_}1|@)?)qTrT*semg0hX$u}MJ#^eQP^MJ@cD_-7%B&HdE)qKaQ(`Mpd^4upz z2k$$_tk`YCMj1C{?dNN-DwwT~!Zz@0F&Dc&1x>Kgkii^9@D=*xrKGP?h^fOc-9C6) zQeHg^mt5xWP7|)L4K8u0)@!f12=}^Nz}mC_B1Mf|q$^wkVOQhmPtW;jhvh0Px-tv% z_`YP_+W`0bAdQ;vG!QhF{-dPg<*uXyGgyzL2Qs{?!s^F&rMGHrL(mA7MGp3}=cxAb zfZ3DyvliF6L0ZyWLd9}c`d#pbr0?S-E8Kt`8G~)o6un}!Vnc;fK^_)NQ>$=$#V#`a zXZsaI*Dy>WLAv`Jz7)j@GY=ALgtzD!?B|mW4_MZ;CN1*rfDrED74jh8Wbs@b)U05g zo?N|@7_w`F0Pq}+!{kk>AyQM>-(d0*B$doxBTE+DpYW_t)rjsyY;aNoATf#r**#caR>`gGeQ{}|czh}lWukrgc>OqP(TPw6j1xUvo zHDp?A9prnrk@6F~@A}mimyv^>Q#a!4Q0(L5LFfW5Wgg-Uq@=&}{WzZf(p@-C@4c|H ztu4jhX?gkN70JZ40rLwM1a|CeJmIIOfLs>ySEmog(;DaDiLOi@ zET2bwgz(&xa%`1=7yBb&C6b_5BhpjHn>VvdhPeE1FSMj?L3=cV(alR0cK{>YKA0|I z;SbLirAnYXrUa`ixw=Xg*`cIqU0W9X=3Ht&a|`@6Y6U$?5*L?O%=z)? zJ5U_|5U?n7vo&%GxQhIMY`kx{u%tG`YS5)raVM-Q3B4ciq=oG@x%N4l-VF0gu&5et2nLf!Tm&37Nu`3tjS{C0$ zo)rfN>sb8$C;kIx2A_4j`FrA0VvOX{IR0(Sp=f;6;mpw6+$(ra6uSiCl1^=a^xZBO zUG!tIoKTGRI%IP0R-wQ+G7XzX-e|GAU%4pvM<03bRSrg1NdE9zOow}yi|Af8zyY2) zZnOPbB~;x>qKPqd8;wuNh4nzYs8=_S5(Z59ikU$cZ0VZ)$GBSFV&U9z%m*RRp8g)7 zSyH)W=@IR%6caC^G*ZCDvpzXsOJgslRDL@5$C`LZvQz=eg$zQnCWA;XW(_r~iYAuc ziNBLVIIZDb@;60Smy-B)=2Daw_P>CnE}zAu6)gi|=L=dq$eq({@mMtY+z$AwUvvqbc&<5GCz%2;wgXkQ5ZkN>-ksS;j=xc&HjNChk%UkffuS4C8P z+@dQ$X%6FPtmOT2Hfcp3qzu0Ea(&4#1O_{T?xpKX&(zSNm3`JV_vMg3ivUEu_b3J<<5w&`if3fUb;YxE8Msv1lxMRK<>0o|yD>4JY6jiyYH=dfY|CRW0{)|Zk zdGRb7#fjK3!)Qq}VX!w`HIKWRlKOM<=o_#8^N`OsfGs==t(FFsh3Qi3mRn4WsxBOG$3%gwhcJ(K(tW zx=zEQOaN|Ts_?nDwDXr4aqTtfS2zvN{Z;j^AsMD_we}yN1YWo9qUYxOMzM~=kd_Jl zn`VAQGS0DG3aTjca>vo5g9LZ7*~&$sJ$00e=`TiBlXxFDjbF~DOm5g7<;f(IEuH?z z&kt|#0bTMO^FDbFgncq}u)nd;j{CVJO!K9_>p&TOG74OqNa#w#aa5TRX2^YUAuE`XvJjg1!K5$ zd1ylzdX_l3M*MHyJke+i_4v8zv)Pl`w_kB@`#aCQGhIyyrl&s5o!YqCXUNl{P>GVQ zuPK`Vq6=z6CRjrE@*X8MW10Zsqm@DOr45^B`o|`e;#VrXCmTN)-xZlVi&y9aW}SN? zuF>0-2Z$|*W^c0RK-29_+($^53zv=$;nFL*`_Dz+t|>Smx&2xm@Sy|bV-t1p+ONi) zjkxiYej(v5>SO!>{I|jM6=8S&9W;&;6g$-G!^_D!j|(leyKhG#ETwS)|>Nz4I&O!=x(c-xG(FIImY z2`EQge+OmC-$iphNgi}t=rQEP)j>jgH(vT*`mkW^5?k$3DC+U;MfGo2%RelOgSzlH zF~}hn3S9Jf>`A}XMGKt$oaCD~_}{+Qb1u1nhL)7SyI;%aMK2{ZZO;sy0GFb$?PrFZ zO5H09K)?Hz-M{r?c%)2HS=VZ-omZ>|kY8$0gUblIDR2G3aZd^k;A*u3rk%iKs|^9wgbAdJg2;wE)W;`J*cRD(^5)@@GO+Hq?3)_(aQ2oBi&bLW^^G!B6@OK=R=p`Ke&nu zKMj*BK-uCuMDvvv+cm8?iqkNC`(H~|i^ULpffOX0^yv_mf2u|;DXw@+6jz$RO2*O1 zI-Jbk!tvXRLv%04wY;og&$Uzq$OP>qtcE8;&V)8YB^KcMGDCTjtjocrQKmA+G}7*HfwJnRa02r z(Y|HF!G&0hfJI~-d}I62X9U5OelR{;gKba#xN-W)CgJTNw8Y<>Bmyx-Z0E0r)7N~I z(HBv7>330g<8w*Z(q^ZVqYS!OcVo5a|(QR-w<3FGla&OI{(2OkRa25O?jLD zsLIof(nv_f4(dxuCjD0vOO9Q6MmU~i>Z$z&UuZys8=y{ zYr+pe(1V*~>#oRy0mzmhiRwzr4Rq6%4Ud`aOEVs_)J$l3*)5|MX zG+6wX1sEueI0vl>k=7b*{ zB#kHR01Znxysy+z7a{UDnytg;un_+Tgg|@0RGrTcr#PjlLAC$?fZ`p!v5ojrO9;P* z`nL4E+{TFQ=eFdG1 zuWqTXFRr8boa4Tu&cpX2JMfwNsOzAwJS^~g7pr)g;s8HLlNaBm*4tc)oAErw&5-lkGMy7VjN&P^l6_!nig(eM)#lF> zKYy|wKa>0(CtsniA6o_leo$4-KR|KkKGEXNJxKQIH*Z(#?N;(T`h@(BdSv~RzHKl^ z)wkc$Ioutz@$N0^H_oSiL*BFS2KiUqt=02G6z}B*ic>B9LO-N9X65CoUL8R3nQx)- zLi)WNN`ClTJS=!Nk@j#}w{G&Zx^CT`#^q(&^ZbqGSBtfH;17_0`(Nn1)z1lUYZ3X& zL)7oGIr(+?+C4QdlV9k0bZ?#9KYEhx*LZ3lbzad%_m2JaCu%*+A$$1y>uKwn`GTio zo~nnR$oUNU)5`M&BPmYK3Ex)V*Q0t~aiThJe3j03yiDgiq}?w~ak_50MjfxeN_N5{ zwSA)dC|=pJC)E7y6u*6s@x(9BE7AQyi?w~~n`r**YxCzn(fsPEi&XpP?`H{C?z{)yVSe=O~HcTZBsoh3(#{NMjjyzgqluUaASJO8YWgXFif>IZ7S z`dRA#zrLwj|L;+J+8@$@Qs~CJ;?LySJHSlQXB8?Cx7e(6mNL~;agEZe5TeOZ=iFji?n-S z_oj2`OSJjhJ`|r}XDvR%e6kx%)%>j9B!BN+=>D%42!DwD;(kl>r6UNxmEyJ)wE5T` zG=9ywSRKD+(>VA?Z5%v@#;-5lr}o1uXr0nQeq!>RYL#{&4P*BEP&ZW+dGY_!BKY^S`KF@2{O(Igs}E zU(ojVTWQ~+XBFA^=sCYg@tL7$y<{>R{DEnY5W~e$`}-ka9@ z4`}y^oIub2g+=Q4ejwdX`qrW9_kEAz8jXBFt)FjFzxPkA-}^GHe^+Ssjo0a1?Kz_b z|6PQiMEiP=Xnq?1B!7>eYJSK*#bNu0Ha}lR>+%1g^|0HJMC#%mnk^HItuGzb`+)&`FHGjhI(E56ZM^ybEPW~-_)%K@8mT_sW zQvE{je_ilw^r~79dy(JE`E*amtu)bDK<5kY)ciI_&^gCN`>M~mF|BhCd{f|KsD5Tq zywFE}qw4v|bl=C9wfjCUAb;QkD=PmY(&y*2I9&%(Jiwj$)Oq_&bPnPG-EK_#<4=)X zX_wfJ?3VWsk33H_h5V>zkstL)!oN)Kd(&Ukb^U=fE= zb*1{fo}&Hq6{}P`#&76;*ot;u_#E=n=qQTkk#Tp@x<95YYVpB}Q0l{Nd=%M|x!SMsw>5&mCVpO&@t>9KTQ_K<(6 zejvAyzsI)Z?;-1FTe7zwrPHj@`oI)`9oe$ z{<=p$E%4K+9i2({k^Oy8&HpjYt54DHOFE6>as5k+$2FGDFTdWdj>~(Kzf(8)J4yTV zREmSQM7y7KHMOICwf6EA+HX2k+i$v%_P=h?;@N&gc8SL+zKtBex@i9>Py74w-nZB2 zyI(w8?Qeg+MC4y`w>sb6km5EEr?^;h9$TdQJ9gIA3wzVO*q_t(Yc8dDp(}S++vCx6 zUZX(%71A&9U9xNSYjGP+o-5?;+pmt-F7-cO(du(P?SovStve1Tzr^;})&0=nlbt*@Vye&E!f_fmV2?P5dn!=L%0kSphV!^r-8(3a}_q=U}Mo~VevRUxow;0{&wsH^ z@LblV*5}sLufD5|cRSNQ&SI_KxQ^BxKc;oZKJ+}B-%ab;Th;#X6!IVbnbuBUA-|6O zk5u!2ne3?Dn%wWwJzSUS_i$01jW;$_$M^4%U(iS77bN%nJIT&=rCVUq< zUwQT8YX5c)t#dy-ROQ)-&PV@!zS{r%g6_3>OFQRMI7j3kLw=qzZp;?sk1>t>F=Rb- z(Z1V$wC^U*#a~K(CLhziBr>kf&J>^XUV5JG2%k-U1=DC>`%=O)G)|rNgurFLI*R=E z54c&g3z5I*JDQ)vPIQj!`0WMH8-%|>@ne5SdMo|m$5B5&jO>|@5`Guie?HRoNoP|0 z&oBQ_?N9QwE<0UYmo26F|ByX}+=mF?p5oDcMDt$Bzc=Z_TrEDvS18`vhuS&xuTnhi ze{1Jl4xsxTPSoyq$XzJ%pRL_(%1D|B$}pg>>#o_LG~@cNwn5(Rzl?Hx=l7)1B0z z??UU=Q&+0%)*I;_qT*-OaerIdr%r48)SspM7aq7lZI9=Y|L}vmiSI7;>LnVlpFLF7 zpVefaAFb_gu1Dt-9@qTSUZMM}x2Aqc#(%qy;#BOe-H&q;`S+ee`{|>}QSwsqJKaJ% z|2~WM^%XIh_T2p>uQzzekTaT@vQ9r&th#~Yw{p5HoCZAXXEJY+n@b(ema*VDZxZ8R<{ zAl#+%t!HX_z5)3!U8|kL-InYaFa1M(o)gJG{EJ$=ua$JpbgC8y;CYJA@W^JW{@g+S zRFgMX@fXSe@OQ7McGMMg|MF^WKX*B;@BXW;@7^T4{a4--&(lrcD^KwOzVo6wPCYiB z?21#==PA=X^Ls~#{L)`yG~M_2uJ#@0(ft3Dmj#c!2kjH`TN|(4hjkkH$DaDOz-2%G zIr1z1v$jw4?gD}L%~R)HWtzXau4Wga`TyP&-+Kw+yVJbrj7{j?E2^J+$y1!qbxHQ*auC}9=R4P{}6;pmC?Nw5#mQ=4>bX!uLGbW|7 zj_2fDzv6f^Cbj2FMQ293C+~=~_>@#;Ak7HUdH1y7=fv5mX_T)$pZ0ynZ<#o`Gc~2y zRZ3@DI;W@Duh!JamP(&jnlq(ZnbJGSDU`gymWk=v=~T(frJPLOE&EQ&pHoS>#egW_ zPUR~7sVUP_?dg0zm6|xQv(qsD#K!WEAA3})wd2^1$um-|Gme|uVP0xx=j5?dW*U?W z>ZG;1wKdf`wsZQ_uD0V^a@qdWKmcT2zno7GrgBatRT(ThEjj*ID%0m=`U9@u^A$5r z%Joy}2E}zUpf{ayI*YxfRI49+u~Kh?Wv7<%`Eg?n3(W}LudThkqbt?g)i#FxHNIm; z2u+_ct#k5tYpUr*^faF)jFblgHW@o<5^(+6;r@OtHk1PoLg7rmLfc zC8jeO$M@Z!O+_EPamtyUV|mT;|7q`Pn?AkK!hswMZD7TYt~%bJVYyW6lra-K+T}wA ze@&Sje7j@XXH1!9OTqQPhDUsdF;k{=b+k=3EH!q@G3?8k6&lwyrL9p#k!3n3Ta-BT z;D*Z_+tzhVhf!yj?QbvTOP=AE&-Rb?(z*6hfDcrR%XZ9mij}sask%|YG1W>X81Jmg zwtJR;z)we5plw(;_lS&oWwx*_frLyByEG19Rc)DYT%bHnco5EGOfk(lwd(q=l|IZ!d%<+5qiEjNg0exqbyh62X@=)G<^`t)O|-%Bk~$A*i@ z_9H z$JnqRWLbQ^N^`QQIecM}fPG5)Q5`~|{8FahNuWgVcuAg$I@I{~m7IvTpC-_V7Gh2` zuCu#iEU%2eL7Pt zK8LF*7R;{OCWSMFV!F_{281nfLPtC6aVAfn(Kfl=$YcsECL0w&@6$GU{1n~^b#ygc zV^oNm{S-En9orG~=ABb#bOy7KGFu9zdV^+~$~r-R?w0w&+_YuHNEz4G-huRNr>Qx- zlRCSOQ<)2HelQt#SKDz@j+sHMdA=YuChTr!O`@}xFK`+zG}h@&2VLc~Qi&{=rX{Cm zJg4y~cK5h^)$eOOcfj3n=J8Htx|0vq)qx64nG?*YOiGHEmuBOJOY&XJVq2wDXt>Bx z&Y(Y7m{AJ;&Xlr^zh0N?OS87icCs5R#&^c1mUv3Tg~Woly;KMm+`$CUw4CRpD-JD} zTLL|S4cOm>=D7Su3t^(?TQCFDc{YkB>xm0 z0~M#}2P$e)_*loYC_3G#&;z#zxkJm7X~jo0GrV-s?+q%iVTPb=hv${N3Bh;E8z^E> zu5*fY$zr)GcCJiHS@t18NnR~3-!1bdxCWi@^uc1L&np#~*Gi*hW;lLjT)N*8`-x_q zzAPJbL3Rbj)|^?{nQ5)NrY6K9eu`4Xax=9}GMV_dD5qFv2yOITzGJ??f0aX;F^p&#g@n(l^6rK}I0)xM2r}I_w$14Vlp0HCWmHMl^ zMOK6}Xd59pR)r;Fy@2SEwmsO5&S=I>}7pgdoTZ5?(8;%1tCLedx(K%uelU7zsxm1X9 z(7fgp#!By~T1p1ma8^n+&lo_qPsrJ{=VNq+Psa>>0fT|PFDTWQ_JbKEpH5T5<3p0k z@XzMbsNz0%0WcQk0I2enB4w4j;omKry=g$1RZB`?+#93(7nwyqF-zGM4IcuAZ0DCJ;n+egOA;8g-# z!hL2YOvx%MM0 zG$wUW=fsFSfRm9mZaUV=x1LHvQ1m3JQw>KU$tTQP^vi4=8&Z@iZZFh2{Wd2OW-0n* zW|IPn&`uT+nVQNZn=e1sIQZ_q)prl68a;~~U)oA%!qQ!9EEXQ^8#$3}A+(VPL8x_BAZa>kZgX(14{imwuZXb5E_Ny?(jsrS()UB~T6; zud!%8m*Z=CG0Bm?j7TbhSYh_JEoW{ojf$Ifg;6;_m?9O@!QZ8-myv5^^9fFULSeu) zp>lH!68p1G!S#J^BI^ry*yjo#t~tWkWK2x$oAgXxmy=OBD`A?(Ll~PueW&dcrWTj8+XuCz% zkny1GIQ`)n z2M%#00UO)(^O&Pv4Qq-LEXl=8A2`kC`U;m^HvpUI+ClJ%r33X`9O8&!Uq!wk(Fc#K z$S65UWeSHAN!f7xEMTyDH}zF4pIzGX_%BmCo}h>vRMa*V;>OLXTIp>)G+2@L_VTqK z+nFnq+i5N(5-BZ6Dw{c^#?!OCPKm_G;K`I~EQcOY2+~Bzv>?frKIq5U0cpudKaFW% z6IdYTJHS|pAz@V0f_ao$tzk7A%c)zfqmtI@D78{YS=H$%tbx?TU|~T2CrRb|C!uAl zs))F8B5uYAyTq_*30QQ6#;t00+H=#)^Cs)Eb+GgcGqsq6G~Ce9Q!3@1v`N*go@CtfsJiv*X3Bm6n$-qz8NKW!pD6 zUc6E*CwOuD=2{M(+Dc)~Ci>Zac1DLy@FLPMnr?OWAoBIRs{>`Vmjw|QP<0l z9hk10kg-~{ewaCF&+LZ~n@$4f#eHY6%MxT(!+G7DYR=Uo>E=(iyeYmQ#ycD|^TTQE5;6qOTU zR;&xMPs#kREd~&xF*)0(7PAp&(f?DylF7Ut%jE}i=9)PNPIF|PvccY341t|)0D*51 zk|D1AY*$>PU^6wQQ*jlKO*P6TvBnS}#~5VK$HMUFga)B`1fz(hnkkjV%tid>KP_m& z`KkDAJU=BG=Asp`m^f^yB<#IPUe;I1QUuy^`V?9|>*Oox1T-wZy!K=Qvt{Im6jef_ zbBn+i`il6_boQGzx195-9((|HAG!8{wSY}zzY;9P} zPt|R@aMXp!shdh}I)&3&=Xh=m20?E`|nl!ZjlD1}2>ujwEz$we4?X^9P5 zG&^i)JdM-Y*yxa`qw>?UDHufXv)Az&_bOFbGHG!mN};UQn@AwskQq$%rmOkTO~`mo z5|*@>&y>T<%few*BBmp%+*f19N3S&4B6gGrm7oowQz%z7I@eDL)P^q{>w8KADlJQK zSkP2MEmk!U?GrBUfPd=Grrg>ni+| zxmhq`Ha85)vK~JsRrjIncCV3Kl#cl<3Az1hPw+i-1qx9c%3$7J(keng)@0JA?bT?y zNVcqkTu)kV8=IH2h^)g3`#dap%6^ARAt;oJZiN|7({h)tNn93_`gpP&T>{wzDjI}) z1HCldK8xq$)8F8?*&cZvu9DYpyeLzsiXofXP@s^MJ-3i%Va6kQod@ug(o>+ z-9?banoE*Jix)!77cL4F|K>Vf%2W?TeV2k+RDq6*2u^;ITfhP}VU=-CE)PK%xB7t6!WWvvkrRyNeJm%rRV)!~>X6Bi7K(JHx{FSK0EQV&Y1wlK6Bbn%Eu| z&qogEltd{e^s{;N{jhU3-Q-h>llu)zAe`@om^ah1i zMurN|hO?L73;=Y!CRYV0RMBw(<}eME+&*dQNL|A${bQ_rl~g8+eo7(WJ6^v2iiBFT z_If;)q+I4{>>|}>4X6mQ7#WrFVIJ8EjbfVC(9uSW_O2G&GYYFb*Qc@?p6{z=P0(`@ z0co!OV2L%l>hrhsTJ}h?i&h``AkX{?~{gV0{Tqt(~`(722%+)%e#1e z{D3s;;X~!Ot}b@!hpJt9LO&%@8&<>c3u@6h>P*0V4^C2vv-=T~(b;{Sh_4-(K~p14 z5p2WD4d6VwiMS7oUDZV4O+bpNU0kvU(`R;{3-{g*8g7~Cib5i28VnrssU#z^*YgmL24G)dZ z3fPkguVdvU+2l{4)~s2lr<#jZWS@IAu+$`~OhPgx5i>MKLX>39Upr~^lT8|h6Hm36 ziGAiM!J0d0c^=ydW_q7>_2H6b33G%QtcAr-Vf@5X7{4#gLu`}^J=|+4!Kloy*};c1 zxpqe<;%7lvkD1jeQ(=M$_dsi_Jj%yR76a^`oS7wXJb(piOsgJL7(kN{OP9$kb--qE zr3z_^@kUUpe#i-)O(QZ0I~Q`O4rDczhz?}-52~0|!-8?r+3T(F7^_4{DX4oM}m5yVU zbj8}Iu)kDB0VZSg#hIo6Y6=No+H%XV z_LMA(_)Qs%rLi#P;k7_v?@=g8f0s}qWyEfT>jyf&nV-#XW_9~fU1{&jQj%37#VgA6rHeT>RY(Ig&2E)P~1~ayjmkp!ia4G>99l8=k1Ljr03zhj2p2q^^@&{5J zF^L8ma(NED57bS7OW3A<2IR6{1c74D#-ouFwJ)nZB~$F^`$~mU&M7)3x6P;we((VL zlS#~okzXXuo$xaK)J_PKkPRJqIpkdy-!Y=({?-DuVU-;t{WexWqVeM&&bNCw<~PHx zrDt{Mu)hjvuixymMigS!W8%H1mM`!~`dNd?yIS6~N~4nEY%Z3>U;zY-=UlO5WzXq# zJdY8IeO6jQnr(bFLN9FEPGx+hf0}b@)fF3v5Sl#>Py7IhYe_j{5MHbk!UL< z?&*^}^_;XT2OzrB+1KcK4wfNIHxeZxm}UA(CBApVeFn?H+@oSTU(<-PZkpfEXxz9p z9x?0A^)$Y5RF|ddJM!FU5-z_fPvj?mA~CrXr=qB~DcXmc+$3(xF9}RJ+Fka@{$~=@ zDxQ%v)#`9XJfq+AXY|7}%XaOm&{LvSa_~r=yqMl-GfgL|8S{A>OR^p9b@DhGmz+c|TQkoIAI^4$qmy%Q z^KL2}#Vm)EEXitKq#vLOR1+c-BP1o#PCd z_~A#IhS0~8kjZ5?8d)$oVvnNo5ok%t`#5QOMqQIRSO!YB(Kc03*%3QrO#QGz+Q zh>9w%%$8@&Bs)*zNrq?4bb7yr)pYj_*{a1_K7pAt25;y3eGwV`HYbwX1?h;3`$L#E zOA6`A&%0#!T7hE2NLH4_Qw-z6#FCqyFk>W6)~yJKG!eKdT+{Ps3{!bxlx8VGSpMS$ zR4a-3gIT@9kKBcePDKPI+bv0JrSJ)DoB-pQZp1APh0#xyoF5F6DGAu4vqL(&@#&!$ zc35)uFo#ROR!o$Ia@dJemSKtzN=fh;%4xsi)KyiHnp9ckuAGd;h7u^v(8E~gud7Oa za#ix{tCDZ6O2V>`i#jx;*aS5cMLrW0le`2h;i>^2|SkOz; zSk@V!$gk8Ba>$UeT41iq4&BG)gwHDR5H_M6D5r~&<$!(~P9#DO;~pn0={G^T2;(gL z*e!gqS>Q%}=g;trW(*DN7Kw{FJNX~}?RZw1lQNoC;abH7|HJ>k@ryuXXvwW8=w3`a z+BFfGscIdjgx^Q(gnDi>I1;^&y3y9lJK<*YC4N(tG^sKOPri~kl*xm;(kTe9kT#yO z$8iVUua|B{XW%zW|NZ7@t?24XnIKIH9P@_U56}7`<73@9sRAO?2h%3fKb!;Pvch?Br(&th|_Pv zQ;H6+FU?QI@;j(ieo8XBotn|B?mRpQ0%zv?k^~DhA!~R?MI5=|-4vmS@Em47UCdS4 z?OKvvri2N`-1OA&HN)6qHs6uO-Bv3`a55O`!-%ub>bz4Qg;cdD$HJua*qq1c^PO^< z&a#@9oh=rkUas-T<*Xo?TzKTz!RRb`cqdDchi8#FD1&Ky-bfS+!De{JyIxrAVl%{o zf({Ugm1VOyj4$g0F1jVyvPNWaQ-x7W3nP*3D^&WNa1^pv3}TTQ*uzY!=HI7~imtek zbG3eFu*a zxFSGGO+|zf5>^Di5lfzoRMEzk5FV*y+47$aw)=&%6% znJ9d^!X73YhEWGGvc=g&-ib(LCn6t7B^5`g!ZZsJVmCIrl`^8|?&jv#RqEUURiYENjqPZ`6RI%rHXiuzh5AF)ZQHNbcJ zt&0y@6zBIa@Kb)8Fux6rwBP>bAT|UqN9V7$neylpz-^7T#Ky3 zPxVQQJ5LiykYdda;|efaj4KMOm`SAj9eEpyIa8%n%B$fv%s70XuVZi{OuVE>u=ID) zgNDl9$0}~9AS}jqXV#HJ3$8D(Ww-O9ES4%xPpMS1_fwXeGGPYX{3)B79yBpxM_c)q!6TMEXLcMf#}n|%lclxHl#+nkUyJVf z&Cxx7O}ghdL-%OAmv8o_d;Ay)ER8265Kv)TlI2niJxgH;CgdfL*^1$O0|CZ{+_JPK zRpnL5J*5f{s9s2WIhs(G$*2_M5$AMy4$6>BTHfictVtG6Z;B+Wp%_8Ut3ZLs`x$E` z@t=lQQmsi+bYwVAkok$>I5}mu|Fx!9Vi}tBPFz-K(TtU3Y}TwRXAPBCwmNTJE6Ec< z@#^ci$j}reuawGblEjje%8|nC|+~F@l=X>G*4D zHhzIF$O=ZpZU#Orw`(RhL!cIIH zYbGz;n@`(UmHC2{3ibsrYd8&*CE2EdO+kx&EGj7i)7q32{)hG@t@UuWMJM9+_%Y!oJr8&D+KuRVj7nH4*^Ugp$Ih2}QOp%9- zamhU*Mp7|F>B(h@d92576{#zZ=b(_)Tj*@hj$9Y1XUeSb=M6uYNPz ztKUd_^_$sVBZ(BkyPh!9RiY47ExH4GIOUo>0g;zyrJRX2rs|6Z8^Rg{M|OlJi4~{u z*ip*biq?gPd7E45@_VjPGG$3DQSFpbj%B~B(%0Eps+E>#i>bb7r_jT{B`8yKhzsc= zzYH{!E~j}+617X!UyZUWxtsGKYi}ohliP{kfRg%&lr#!6o6htrGkO_bmT-IKX!#7Z z=Idt3$(FTvKu#tExXtmWkXKli^PI=c@@Go-d7rDxtjN#T2=uk z;N{BJ>!mX=ivWI>9Hu4};}&N-Ud1pbO&pQ!tj0@c-I8|e8oz*+zqm++tJg$Q0s&GH z6lPOJAkG?ZJpI8{k38#Vk8{h@1vKFmVtw>t1CRMi+SW=F^6wHV}zDVC$FioB2|HQ(vk#*-IIpHIb*u&9^sY zNj6Pl9j)O^NftNXtCS_JHY=mTHoKLwwE2!>SkgF9rBc#-D^3=-*z1%w zvY^For2O&hV@ckVw9x` z0?Vbmn;EPtjFOums3y2koH*MsM|EV*Zr}qZQ{Y;WYyW_=oAm+ZvO*V=shPEVdU-FTto!V_`vwC+jV-=#@<%vWVpzPh@e& zN)3_RPt(4m>3$l7u8Lc;N6Gf<_5{(fpr|V&D#n8j@N29k`|J2fl0Y!9nBfdmav2W3cb3#lf^bNH=J z1=cKTq=>|VX_L`J)LwR~2(iBJR23uc9Ic@z*q)b$#_$SHBp2}tjPLG@5x}F{x)pNn z)Mc<#o|sSyMElW2ScLqE6Tn@>(iPgvixtGOq(vyk=822dUlf;6i8XQJy2v#Oq$~+V zig1&eK*!>x@pO@#P?COOZLB!$%GzejjBjCy?#Ydj8r}}53o;%eRdNa@l$tn^N7vIO zByM3Le?V^>zfYx#z1J5XHp$h#nkaUwql_6|D~G32qP>vt4&l(gghJ#k!p0^hT}VCU z@$HpI^eJImkEnTqY~sm?_1-F^w(g_Y-g{Ir%#A{B^JPlGDiYc;&q*1Be3XrH*$Nh8 zGAGp}@ex@hBqCSti&p8aE{v`b03#(v(N{%DoCayWM#JI&?Z!ks>PYn{y z?2a`;!V7P`pn1oLk?A}+ktDcRK^0+fp8}GH>yePucr^lSVl!Q|M|CdA(Qg>niR_^7fD$|EA*Su3b_CgXxxo$5iEn6o=8*}{Y{l%9Qmz=7-Esv((^>)IOUa6 zF2~xHy3v>6_fvBjA|1E+GRBuz>q5e|ppYB42aVNVh{d33oKS1S6!>R)b!){0WH zBo-Kb@)|+bUtA5lTIaj-OwIZDwP9ZC$g$M}RX`nWKta|j5&t4d-k|2m#nug6WT!tk z&tP$gjEOG9{Afu%`Zd0b^+k&@(yBp;)+#e0Mw4t!9Lo~rFqa4CjLKeVU=V$b zB7xy)BatU`G_n@xITg=!co^2TNDXr~OSDi)wxd{Jez@skMU%)i(oaRni6pJ<{ro1> zD)&-GD;Z%nYj;V9eSyuf#*hfQfG`rDObi>paTGq4UPP)D=R0(Mo@RdZLleO4`L)y> zHDdJ~M+|{nnUfuO0)~FJqY&@6ihcS7Q$&@=B>3lv;7$vjRrif8zD>ZT~CoPH~ z#e$(ru`yxjBC5r;R9cuJakWLF>Q!8r!?x;bZmbgmY;UYnSfd;3fYkiPI)xKgQ4y)l zLJC>K8|#46_{KVg5pJvlPKz7s6jt9+Zma`l;|;esTC3H_|CSr;VnrF=SQpD-zT?eV zgrS@VMP&I~!-XwN<%waM@xoRW5G;Mj!8p7ZwN?_##PUR0l1j|*8@K3$ukdPbCTC=6 z&|iaR;{F-NCWpErMR*Kx?-@H?Szj=Z&c<&+Gd+bFD_!jX6={~;pelfNteLg3Ib6N& zfH+;7#mPNBILjt-nFq+OE7o(klX$c#7bg`nHA!lqV{4Nsvou8_o{Wf{eDPc{!cw=U$BQB6zah6eUiMUrFQCqDGQPPby1n7KNAS#fsy*MOOFh z)s+}^e{?RXuHdl$OBtVEsxLk7X8P+lH7+*H(iDkn@5%fo_he%2;TG2_l;vrn$hCH! zev>;-zk$xvZ))f1)0}~sQ0j`K^ybJadD0NY&aAIND9ae-Yoc&Eq_sxDB2QC3v*J42r4%8~Uh4ZvdE~DeEp^CyR&8VxK=VpbIXk2~qRFRlSN}rPs z9@wwfWwJanOVU0w+_@A^uktEZRhZa*suG9qVWp4bd2<&s;o7>vY-RBJhPJ9T$isc98dah_sRUZotgc(EJ)E?c z>8sOv`MN`7NZ5kezLn4wAGJr5Cvx#jq5c?&oXu}AHMK-B*=4>4i;pfCrY8w2INLJf zYZUy^rmTG&Ie9UI*ow#m9iQWcOJhppTM&?B&F{<(gHHi9p*F1C??=W=23moehf)rC zSri&Mm1h4kj^Z6M(V8+CxpE~L@0e{-DUT~Mi98k|k=G{7)Tc!9v4-iD2JulPWJPBh z6Hpf98iG$D02?Ygq>yp^Xl$S~aX2ANv1iYRr{YB5owg9L2)?6GXVS$e#GyTlXf-%I zLKTQqn~_lwLQg4uc&&MA`;1Tl5>#Yg+7GQ1)+7byNnth{Rm+#MZm-J^3n!~1>)weL zqP(}DXg_*Qi&UAIK!~TcVL|RCYRICzVsENpx3M>=LOiF}@jSNeAZ}KsP+;b^{(7{= zd*bzo_#@HdS=<7x&<0~kK6tJK`t7r85RA`8sTBO8Vb3|WDyNa~eR-lxvlmg}W2X82 z8X8NY7OY0xvge^te<||-P8v-)A9S&Ky~ab_nmO^flB6|z>7JO$B8TngihL+ZGA46^ z1oGXiGKTZtHbic*Tovo0GFQby4?mXgIXP3aUdZSR-*@I|WZ99pe#{qh{RH2uKw(+w zcfl|iYe-Xc3Z60LXv6RfKePCSW9Tn5H8f;y&}8lgNvmN194 zyl7FV2}!nEl&)m)t3|&Vt3}Hw$V&0#>oeiim9Xt4S%o!meZXP_D4(g%wo&j8AFIAB zC7ISLN@c|m1vaK|jGc=4U!>;@9pmb=Pn>8)RPoY9KhKRJ+>y>L2LD#pT~Q9qU?z)h z=A&hidSMTxhKY8a8{GZJ=anhA%2`T~J6aa^I75YCcaF3;-vf zHl(?5*hf-^+DyKH)4uA&%AUt2VF9r9=i(_m!J0f;ol7ITE+otmX0R7l%BJ%*{2a9A zES1w4*YJud?E*(S-laX4)v+)2#>QxQ7+uh4zCI*IrbOXQoiJq*)I60XTFsag5%NTf zLT$cXu5ilLZM`R_^aKwd)5j#~VVF4$BAQ7?-zJEe|aNlhGa^EF+3o%Z?ug{lvqdA3HMniAE+r>Btmw z#0VbD?qHF6Wo$oRT&4Yf?n!0rIH!tla1+Yv}pntzx#16C;GezUb^`(O_bDwU%Uh zpHmE@xVcH^G`i18Vf9?!sd-Kg+CC>)>-b#NsZ3=_)0|8t_&e{=#0Er(+}5%xp4;zK z`hwmm*GC(yw7q#al+pV?K4V{El6@~F*|TKHGKr{&BuPkVQB?MQ>`5V#Bzuu9p{OX^ zNVc*hA`z1%Aq>J`%>3@t_xgOkzw3Mbet&$g>;3touJfGdocnp5`+nW8bDsw@#u9C= zPCasmj=fIixkWxkeMNk#clCkOC%x#JPIae<+0y}9!X7?C?i>#yFF$}^5<8@S^GSD6 z>tt;E(7EqiGdDvj*xC+#Z~t^=Vrzosx$wWznNP+}In;?MJ->fH)zw%rI^$)^qBU=E z;zyJC-zB>ZY^_;%u6~=#y}DXjE}=2=vB5OI+$gL0{(ZJq)^q#B<7-Tx3R{;2sv{d?TNkcsJAAVjVm!$Lw9VUUp)zOD~L> zJgFM&`oP#WP5NHI*AmN&@WCHvZg!XlDaC%QXYo)q^PQ>!P+4>@Q&vi|`FdML z^56x^=Ifc-5u@7G{`}RDp1;Fx2j({~PuJF;s@1I9eopayU(RZX+VZet_3*~A-XPxt z!5u$B@9i|09Qicw$w$!p$T}u_K0;j6RY6vZ{`B+Ghm0p`HqSBIiXU%!N%0vhNkx(- zV(vW>7Pokj-Pzsi@F3Z^T(x@YgW~PvqmKJ^tC_Z4V!xpgN!6|HK2oqx?{>CFZ~oKU z{6%d|dz;Qgilt`Q-@BZ%_s1Exi~)+q-+jj;^K$Oswmd%;c=}a|?8xWln%&N{mleZ% zyF=c{+f@BLVRZUT#S^A11*=OrnOnLayx)62hG{t7Y^PcCqN(dW$(`Genr=5pGjsVp zdrg)@v$}L=sSBIMd$Ef#KTFi-@O<#XI9}|z^h0)b_!7WN`#$Vmefaf4vd!I=`{_T_ zB;<1*SZVqnKX~tQMRhLQo@&PSF^2q)+YNS|F-zj-Ss1!wJ0PIz?Z~4dy^HkACa!O5 zy1lN*Cy~J~Ng7@mO_-7n??a1=u^|`#>@JZq5=j^wulUUUvRd|`lRWiAY3r|@hg65& z59Kc23LZVMrTUMcXx(sO9b307lVmch^KHSVrB;PI(t%%{#I+Tq*@F|~asnrX&$=c* z;~s>S&`r^-`^$`_pEj8ziN3CUIm- zJHxe!8t>Eyg}#qDahCc?@^_r1TeucOMh%7&B*!-|M%GB;TQ}NR0_r=r&&!C7GrN(K zovze2k72Jo|5G=xfAieEivz5uPPfLqn#^NOp29{lyQpX%Fb{qx!eeKC(zVr~|D-3U zkKy8#lee#Qu(Diya4h%MLlKkQ-Ys0~U$b1y#ftyHZ}|UNnSN!n<@@!$J{gVstsl1g ze)D&FbXh85?qKVY6bo|&V#Wnu3)w}FXv*!6AMg@aTWfWtRF^dS3+1NF>OVFmva~(Z zYdG^tt}HUY`iZEoLA#10TP0gxruI}wbih=BmeToyq7xaJoi-D@j%CbqvoW`JJYXn4 zKhu%&g+Iz{yN3PH9ic}eE8?XS+JDbw${yJAb|^S327LsJrg7bi8E8UCLLXLl2{F%`Rnl_u+0Gmg(UB!Sv`RlaqloLHFn%CrRIg zyW#ReI*b{9-^8~)_v@@;?vst(+HQH`!b#==B5va8?$U{~HRda^vp64tAk#m-y1uWB zux-~4#2pjB=S}j-SY4g$HZ$=Y2zw-?Ao5Uar;s<WWG__n@mwnJB=+heVKHW&}G9fPxRU4`< zj@Zh$otnx{Tww?oKjeELPVOYOKk-{CHKT6*V^|4i(Gc5rfpzZK6zc|`9dGPjv6klU z;@csgxI1&oNMca)s@wMi&Vk-vf9_XLx|!`k)MQQs}xMYj&xdT_)T zu{@VQ??5QdiJkqH)5>hOiJM)g>#5dNHQ5@IAZ};}e+mG)*`@(xE)kbG^ z-^qz-_TBGo_mY~s7S{Nct@c!2{F16K(>IqFE<7C6bcv7eHK!8u$Kx6u(|ntqH$N4K zxC9?#KCoN;(s>rkxKpO(_o%7tNiT0TW(&W1d4=#jhK(oh^n*jPMbBN;j|a(Jmp;KL zKl-YjBeA^v^MTsk=d>R?xnv&D$Rns%ni$E7Ua~wYr{>MoD{&(IWIr)ROR?hMQ?vW~ zj_iL`77kiyVcHDnbRQmAH!;5d;74p&ft<&b13JzFmEZBc;eD;VC zk#;SIV3u)>|I-VuOaX2?{W8Y zd^rljcPaX+=7b0KT`7NhAW-Aap!(iip>?S``^^GyLvm`c*3=O zTA};tK}MhWJv)!@v0`G57(JR__+r>x{hmv`s7cff?Q^%EKJKwO(KsS}o*_zo#qQIb zt1b1r!e6F+YP`(%Ev%fSEMsE}ijy4N?`UX#?Kt@?i&w6+m-?=)Mz|!;UCDYh>E?r~ ze&w3_CuVwn_rh75x3wt@T^uNW`z3M{A8RWi`Aw%r@rZo*OWM~50|fSxr4J()rJbmw zTn-!uqbDz!6$_Oa{!+d|QhIcxg7ezWk;k`U?xH`%_4)V5w-I#VY?s*^*I&zPRm@hE zzG=H4D*s2|`ypxXJkh%k1O-z6w6n9!n>~A7TT<+5X18H^H(A+4&uH5%j|RiIu@<(Z zii=`)+zC6as)7oH>=aV>JQc0p|F#Z)P070CZ~W1hdxM`_3#h%nvvXo^Ifmz{tiYuo z9j&>id-vNu_P`(Jw)$}}b#&>@Co9|E#MUMDsW$ona#d4UnDoL0iPv1UXP(!Z?TY!~ z=v83+gXL_QX`z>`gMVy#NFHaxz@9nn!r6nPH{2hjHmn)`4doc;D~`gnrg+BK#6MJH z)y~~+UnCLT^kgg|(uG}J>d^;^VnC(LfjaNc*pR6(9;%H%=)!5GKmCUjld8Q5SI4nC z-;4d=uYVsR_OT*1G%w@YyZbkeuaZDl-z7v#h_lG?i^ZC#+BQ z;InCKf4d4o|92PtE&iHfg~6u@<+6U&2Ynhg_JI?|*J@wgesSHwkMnXjg?Y1dZv4jk zzOaOv8i_9a^6y~Eh+!eMM(5^ou3clnw&8-Gi>pIZqw@#+Kk;VY={qyYShw?e!>=86 z9yhyKsp3RI3~e`AnTfiqwVsW<^W0jM5Sc@_Ya6jROqrRWL{?@bL?4qPXp%rK2DGnX2@#W5Q+a+?Hg0fZ3F*sHggvC_1Hp4_l*6s~E#zE`BzG0g z(*%QaTaQ^(#!$-)r3gDhlu5Qs6b1jXrZ7BC*Ax?qoeq%Iml=0t zp=56N(l9;Srg&b`W|G0iT|66 ziEg ztIt9Hfsy2*%!OHCsC)0}@RMCh)!vMCtmJkEf(3B{Gr~z$B?U3+V<`GzUCdO*-)x<% z1g-IGR!Uu17b^h|QLe+2II(moQY?nPjTDQ;W608Bl5CU{L=#Ni7P8@s5tbVM#WDI)Xs+I1ALM!fX^*za)gI$c`sCmoLW5pr@^+0Bp({G=Y_yaLAIFXBJd zFcJ(Dl`tGL^)%6siLjg0h?z&w`bTTT$z$8;dx#0Fz&ja?K9^v6G2@@w7vzQ535;r&wxXVM*CJHjbYPJy_ z{)LwG2{X?MnGry=!JNeqq)CT}HrTTagngvSv75_PY|4zfOk^vpBnO3X#ZQM}&VY74 zY$FWEO3)zf#DsGEy~syOf6s7Eo#Ykf%m4r&CAN}p5G@!1&Lrs_Ke^11rb;S6eiKGf zH6ud8iiUMD5|l_wSp2y$Hb)G__Fm`?(g+iv8(|=*kenIom}XeXd12X{6jdS&{YO>? zGAG87oe~vR#z2)K+A!)12;OTkp)4PFGsMs}E&{@)uz5C$9|nTV_S+_!F@$|w5=+q~ znqWg@Nx9hiEoAmEM{deVVi~4JlswO%&qFu*$DGVPB@PZ5>_;E;F zI)cZFab%zLsF`c?{VGB}Xwz4)}#DCaMo;35^8-iG#N|5kmyXnx?&7ok{pVK_*SCS)j{!f zK=G9TSqYUSH-*tfK~~CG2(lzeBr7-s-6RZTMUxUQkEP?r9hoUD2w#0zWro<#Yz(A` zz)&SKK@TNwFlsTvf7HFdosBVdJLU!%Xu_!TTQd*j@7?Y6lSEAxpx}3VF=759R~1V7ZyC|*X@QSXKoKQm3!~ULSQ3RKc+F#0(wJ<^ z>L)bDECxL!%=bbWNI@{FPG%ZU3WEMRO=xUDkG|NlBstiUjvc{4V-pLSEC^ThZE&9z z?uTp#-${nASN;kS^xXDWG-#e$kO4tu0E2Jc$&W# zwkwCmI0viGf=AYX&)fxvGXiaEwUZ??p$V+H(1hf76VF=7l;ozPVDFll7WaI?#Z zk_RovTbp=NxJbR5M3%+O`g72piPL1PsbNK=D(8|OoGILS?DUNkI6=DnXJhB^tC zbOw0-20)`r!Z1nT95n^Hl@Un{P!D>t4fNpSU|s@vj)o|nfY`tXQ{nq67-^!tp)&6! zQB>GrNQ!BjS~$qWrgpei*12G-w%TiIx#YA|0O8ll24)dn?yNaTLh5Qm25aeB~B>M@vW zgPb4$1fnBa)uSs-=%&ZRg6N1rsa14l3LXGEMuG8qWC0P!4>b^RRuOR!Iq7%dKEn%K zJ1b!yJY5obV+U%0H)zOzc*LW6fEfyb)RK5`21^M&?QUMLqXM*Z+gk!A9ov0a3-H8O!GR%#I0=p0^fq`U)(dQ>`EKoT~s|V=1oMgrD z0vx#nqs*w!N5&E>SwQ6nNpBAkmqblqqpXagE&=TC@cWeTjX{D8b>bjaP{~5MfPpO% zBwLo0%?SID4z^B=oC&KQL2l_nAwiz@JG40;OFj>4n7;+9okI;o_AFEeDZ_vf6S^dE z6&6ZBD~^VFb;_`X&!Zi36~!wSt)CriGJ*r~TcN=N`(q7?PSfYJ0vLUiGLh(>5Ht)? z12L%)8j>gg|3m>v@J{HVbAW$Ma%j~&G;4UA2|R8X;Sz$2K>#lq3zEqS!A$_3=`xOe zVj{M{7=6Sia~LCoQXEn3|64A*(Qbn@r3Y+fqjjnPdSsMh2D9NKyRdj}FoiT&`TP(I z(E??NQ7_#}inWY8k&NVe9(<(>qT!FA z4w8Z=8P6JRwglc<@ZLePi=-(q)Bx4}5xK44;j1tMDM%U8F0q>Vqr_rRUwy<=?Lzt{ zk3JHV?XZvn#6=$j4aEWpK9c$WC6G;PI1`7!^ z2{llUKKRuU8IFQyh48@Dzr=_I9CU6Nlmesm(H?0EJ@s2QLW#=gp&ang`q=gC1hPEp zM>`=enW{rtLi2FzbPP=oMwFpk5kU>4+xwxROk7qbjkG{U&cMij59R{z(J^3P;qzQ4 zl$|ij4Jp!P)Ib^)4Fi4zIUBoxtG1~pJFjO5S+cT)Y3kN5pGHN-k1g3p6JN!HCqcVadI zdKIQ;qXpv-Vg)eY1<$ENc9cigl>e7f+Q$vDP&mOO^|!%-Byu~WMJgi{~+J$ywKL#am*MFUu2 zfCVEB%KF(yP;_E4O3? zQSDWIrJ5dBNVujNy?8j%GPV2Sm8a@(Zf(k;X7w{L5b`>Wo;<_; z&j5Ppy5iCGtdpgV#lPqZGl4nirs3BWSVTH%_9OJe4#=+6rA?L?(m&UE7tw z&|vi7O8Bv?eE$Y|P*x7YfJdXOzQQdFw4mDhe^${-^ba5_;mB$Mx(>!L?Z7>EmH%YGg4^f_`QeWy<-Hcqyyl+q(W|TY z;`mKEm&dp5J#)EJdfaKRlY6I38A~pcwsT@E@wbO|L6Tr(x09#&cEO1A`CbZdF>&|D z#0)xFbDO5c4srEjn|dwIZmGMu-WEmbgHf2uCn28tRf@|=OT^j2>3U7n%v^|rAFae%{I7q?D7zaFV$iPz>OXaj2ELa&86tE0i@IG?-ow2Vx6^>Ms5 zmLR>HG}d5a34p8Qmy<3u*Z@|Mj02nFhspZ@E2E?7=J+{xspTYjU(Pw>&2dk%PcL1w zM{+20y)BO<0f4YFFk5ISX}ztup$)DXR&zoYo!s4~WdtKOz!R8jxd^Z_0xa8W(aF4; z8?vbGF1MT{6VX$ctt&BgWYmMJax!hp&8)@z3N(#>urgoCkQVN9=6wC3*lw2i&}4+AlVhsmGklb5INI^ zN`T6gab$DciF|k(SdsvScqqQpGRiC?;F*KuOyuDNu&GZ`nwFUiZviI>5;T{S3c}}~ zge)47YkMsuaCHd?@kSASSC6Fb!}YdQ5-UUk+X>e8Qw*kM3c@N7B|>hmx3QC=i?YRV zArQY=;#gs}7$*(4oRr%z2EFVd5W4fE7U#ufq&l3ocITrOSY2SbH9+09eA1 z1&nbb??R9*0344$$1<=w8=5;P!r!$UE{uM2q^zh!#?a7WJ??b$|GhjGYaRfO`vd-*Qr9 z!w4W5wP_eRfqyGTg>%V;^~e}Aw+e-#gvPOo&ttQ_F z9i$Y)M>b7TX(R_g$vh9%*sFj&O(Z>#2d-gwvJ_b#@pv9wwS$5|F6IWmMgpTd8709T zN>$*r9%4PvL5kuxEt9}1349fx>|K@wp-6y`@)E$JFv%0)O#rVkQ&|z-7VtPm{~sr^ z5hwKjal#RC!Ukq-qe6N#66uH&1Q0#`K_)~@?W+6hIJM0Nt#_SoW9>Pb6Mg=Pbo?_f zr5%Ei9!8m7`T_fd;6v?^Olt*pYG>96#pO<9@U}*s6O)}3GgZQj?a`iO^VAnIY1L-E zw?x$1-#L((!P%VX(P=(@^_&?0WZVu5rm@>v&P)NiLMBA*$X45PVw=BoJ#SCp;(-gh ztMrlZB_iR|glmqf9cVpwk^gTX|GV=d|C_*41%_bh-J~lJgot!Rm?6l=1`?3X-cqUx zIPj@Kv54-MkJq||C?1O_9uL7$sp3Zzhs3sLs(ZBF7TbUbwRI?My^Wcq0j_7Qgi#Vz z;wULoPT&JXs(@=U5jw{qDXV}E6+}2;xtWE!%b=uGhTySiWuc^$2b2tRfO3~RP(voH z3__?&kwbv}{t?Cn5r!8LrVcQjC%J;6Y%zyXP0A6~pmSmb@#Pei&4wSa297xh-!}Jc zXlv0cf~_S33`@ByBf_jguy#~|C~*=s5MeezN^ltmH^=SW@rd9cEdf$mcTq}i0uz|) zvet2`n+=SBk6i+EX^T5(LQjJx#GhFgGWQ6n0UVAQfm{CMV~8*kVC*!x0};j<5k~i) z?43tjAr2PYbgH2eoF+z)U)BMF^B{u5J0QRd3KQF~6XDebVjUt4LQ{wUq~vVygB-@_ zf}7V!)qsRm!VmzHe<0bbi$=1y3b1u4kP^<}kP@u_S)sOL`Y)jl4V%Jc!KQVBYz4Tn z7T|vsfAF(Sq>L5F9vnx36Bf|iO5K87Yy~dFy#aB+>4Hn&lEjdU@sPfENRwzYr0OD0 zC?BVXz@7z;Rcla&cra$b2ye0}I2K!vI6;<2KJEfi>r$){C%WLl3Y2|_6QHcHwZQ&- zo}~G2Yk{qqKb^V$5ZYP>d+QSbF|<#Rbr9l7z$T=dj&^S^#NrUx5@H+Xfk0bzp~*}N zKrF7e*c{h&hib>GK@zAdLNNr~OrfBC3#0_Z3fj%}Rba&uwApF@WCSF+AQBIP&jE*O zNV{xUG`8m6dfNg?xiEW(y9T)OlB5i*Ahu&%Qeg}ZEZRyCOnz}j&8Qx(P=v_wKty4 z!yKGFc4g}%ljja0lL)Pr`?(F98Dr2yG%_&9HXBR$NDHvrQe5;xk|=KziZ z`BHCv<`ixYIB!OtfQVpP0Gc=fHU}$WCQN3U18OjllK~~GBRtKQREhvQ0;8|EGa$nB z1I@M&pmdLHxPu6@y-E}jrr(mTk&my^K^3YV+F8tDV^*n>K`N4j2r~!Pnn=S4@G2|| z`weJMaCbxnsuj44a}H2nC7ncsDL~a;6&{I5zf~wZ$X*z#Ps0)3f`51!5#C6c#v}oR zXCmK4ct>DWzN8%p?+El>ac4nz4S_%`67aBdWOs1zb3pA(Uo65s;@*#ST>CcpxeC^wMojeus~Awf#$N)RLw z;#Dj9DUdxl0jV|MP)cTiHE%K`g*nz4z_U}gB8uY>C8%u6NrT}nXz!*1rA;DHG`3Oq zpjreKFG5sw2EU0j~HDR@8kQ|5Z_k^8OE1)NU8oBPYRIyi{3K zA4P$CSwkVpd}Ic6FvLSfG5ABNG}hn<`?5IvfCvtTjvzTAHBjOtlFE?o(I5pINNzya zm&W8upeAdm$tE5OKB!^sIcQMBNRuG@1S3KH0`|eH)1+A9C$xXpKtQ5MkR?hOI&7Q- z_}LQh7@a!>sZbCVgoncU(TFU9D%3Jab^(9!WkIkzgSM7on6bqj>iV7rJj`IONkj$5 zF|--FK`QI|13?GFD*>wj6`HvXkmd1Ah@?XPkYAAv^`OP0GXGssi$3}vsi@1~1n?iM zsPP>7kU*a9(#vIVWOM8CKCzuwN!8P-s3{?Q!jzHY;h&`I;X~= zH{2V&_;|V8t^Lp*)BMonUqNjrp2^r6aPFFiH`3{sQQZS~bh+XbV>*QvjrP9Ty?ALFxXCly9P2D{R$(|pa9{t_nK;|5-oJ-4w zsG5V=Fgd~&xy2nys-6ZL&=cmNe}`NpL>EUm#rTu05#YI_s6PD)SXpFX)HVX_2aUvj zAdyf8D1VIgf7jFW*1xQNkRGyCu+y6okH9)uR-eL&(OnhsyB6Ns2n|6k6-ClV{DuM{ z12^{%FdW#u8aAN>;mUxg-65$Xdar^h0ZQPsW5-`s9Bkpc{kNlpoI(jXf*efVjvUMi zOoyLPp@h@~$B6rb^j&DsgkF&h2+Lve@oAap@Mt7@x*1UG;o1J{%||FdNC@Ox9tohp zq5NNU3frtrq{%-ILrMrn&cl;;P|VS51QW0+Nf1P@YA&FSr4H88LtKT5Pl|FDAs&I9 zMuEK9OJ@Yb|G_sO^8fwjqyGN?P(jV{pDn26nf|+=&iEfHsL|=||E{2BzEEGPC^Npg zdWLx*U!je=)V}L(wcpLk4+80rw-0*lcruC|^t!|U$&Q)p&1=1@74sFx_S?=Ul75;t zMNW1k;~wJ5hN3oWM`>K20_ZLiw0VoZR|B!vMkH%GPhF_(sC)nB=U+oYUAM2XecYt! z0`=yH)g~_k-0L^Tj@rx6c1OIJ|5O@DajxX>9ICD@|DtlHRj5wtVno%;=9hVXq)_o= zK{C>`?Kif3ydfLsdbQAH?`d7fJxZ}J>4FDC5I1(kLS@n-_>ZVuvh#hVSb!bkajM?0RsAHct=lK` z(j{AQeS3lT3F2H1@7Q$wQbDl4a4=>=uM6&-cW8keknITObO z)EEc*PnkPzi;y%u)Eu;@{Z8%mZ=t;EvJ1&l5AMpZ?T}5(8t(&7O$vYaG>IvIr#swBCS~TN~bv?p6>UMsmIZv!htW2~L1PAk} zS>ipKlQmV!dD7Av8&6JdgnyjU3NL*Akf1s+_hLdlp<-f)S1>J2O-@P5nhKwD+!i0p zlQ!5&?JNF}JaT~b&7-%&7Ogsd!`GVJu zGetMYLb3vf`mH#*Hv*m|loi|<7@sU!ugh|&_}XPP=o_HlWM)_8(D9y=`@lnIR z!rnROWZK%1JU1JWr>8!*e4zg<8{t0pWN`FK#gU@tP4q#}i;A7u4&s{gmJv6zu22mg zI6sl+d)Fs*FHPdz#ShQnb7pQcYmH=YNxHnbmgzFy5pDhMa^z7TT%pHc*LbpA*o zvUbZg(qh=U_x8jTnHuH%e(?0W1@ZYMSHjf;D+;b8PnNmNx4ZYUe=U4*?F;#&rGrn@ zvZ8vAUpIV%N&AJV+wC21j!0F$kn-YRxzzm-%W{Rh-v@H(N1zOUOIl_6iLW_Is^$7K z@I6E&#_u1o&wLa&4u4nlQS{UPjdytp-)EICP1*^b4%Sk$?E1C6!RXncN696>{hONd zem#vjV`IPVb*a^rtrH@zi#ifz(oFS?n)0ymRFq9Op5;t>-SAQ537yFHDmKU#D z^Hc{aD<80N8)c{{*1Q@nwjq?dq?#9VG(U-=Z9%)XGoD%HaH13s!r zyrVL9N>@ks^{aP;7=66AR%$np`u^Ii%9P zTl(xXyHnm}FBA^Gyf#w%rF{RF@~vllpMSn&+bS8Af627mkSpl-x8p+Fb~wc^#tqym zOyP5}Ts7z|(|TSgBWhV+b<=W^UuwGORIl&8>80ymV}kBUtR}49u1c5lXc6+zyzk+6 zaj1i{!#?o*llHtP8q)G_o2Dnl_fKi2FEDV;#y)@b#cR){;_vw_FNH=97bWN1S3k>9 zw9=DxYzEiv;(TJ1H`_&S^@rlgvmV3~slEv(0|xv~YL8nL{@E-lDo>zX?e!hK!>0Z` z%HA^irqiEG!Xl)O^On4MQ?sfcuZTN4O3nBb>t#H9V{!1VoR?L3u%NvVvtaY~)i(xn zvp0tZ_gR1V=1}!@CG|D zn0Mpn$0?rI7iagx?h)fjmUFnzm2&%5lDknqrdcofVW3*V<#$hNeFw&Fk+~&Z-<#ao zU6SjbAWi*nMV2~Nwx>oTN|N5n7c!cAs+6Z|+W;xWMcGK;_M*mU8`aW>^-}Cux8n~M zm1g>H6{9bHP3zA4O^)^7u5Z4+>w2=_F!j*TUd%DY1xJnO&npKf;RDM{ z+pg@=mXQCI3m^CFV&tA4cv+}i z;TDyvUG`T+e!XZD(>fa99DA$f>4#d$E5fc{#icI%s2Tit8b7o{t&gOf`m$~lBRtcH zYiDUPk=(--!)dQ5)qVf-Qv90I($&@4~|2A(upq zr9_M>=O+;_Hgd-0Y+;?v_`{C|ChIa!SqxjOEL?shrqU4m(h<`rZ6jAE18kqWu=`a{3~U%;gIjqSTqq`C9wA0>~2DBSNNCH{Wrk@^i^^b=_OS^U!H z*TR1n{5l?6)XYd<8L`g#s+{%U^xsl}Wv<)nvD@UQ-|oEFo6WB}tvP(Cw`bU=R!C%` z#gCuj+OAGXQ}5uu{)ZcbHz%rC;#2+5$m(3e0sU7 zQYw?p!moS1+GE8d4b;d{sIfL)`&kgTxTJmCb45&z`B(cwYE{qje9G1fMT_^{VcbH&x;E{=kQ}p+oyVnQZ>7_h|8D_{bs0KR@A{lHhT( z$IVs1W>`T%;%#?1#sAhTN+0g(LSNe?&ijY2_1vpi$w;8-^*xma`-CT+?RFSjG?~!7 zFUB7G^RQ5}cTSx4uB$Z~(R_Kb_bLoS&J3y;yH?lQZz1KrcIbNKmvVhB;V$-7sfO)B z{K2MAYZ2GBl;iW;1^u~IN9g&#USl=c%M`zz3%LHl@QdrxNJ0O49ebI>a{kooQnyoj ztpugwQXjV~E1mBwzXfGE&G%iq>UQtd>Zazo`C_+6E1iT!*RMY2c4OgNtVJ?giY5Er-bQrgtLYDX(h8$E8eZ0_fTfPm|n*PDf$YTii2 z^`Cl>-G2T-k6t@#^IN^WonA)Q!|xv-bpEZT$7pmg zxc>Xm!ez!w2ZNKkZl2L%WZ&m}EA{w%=e;KV?#pIVYDoieM}l)j{0ID}phl~ZJjN@W z_DS@n$L!tw$W;F^U0%10RE=K|NiVZF&70pPbj|vhj&8ba`}FdQ1;e#H8Ex`GDG4?! z7s{^CXtnj^RigF7dP$+}o0%)zo55r9WWPoE{Q96;&&y@k667YXogFCE6_;N0&gf<< zm9r^hQ@0UK^^X6tZ6&+-?u3h$?YGzVv#!7NEAuPb?;Y*YKB$pdEjkk~b&*4yaK!XU z$W^H*ecP*fpTlXYQTqPrvakL~KRy1_^fE{A%Kfy13x!2eH4h@|zx>I;PG1x~!xm$B zvk33xETacmrFBOwOs6iUYDk$L$vNkI zO8Q~Ueg;{aQt=~6+h*UUHJYxoNBh~G*QO4rPmym{{w62Bb*B*RE9}yq)Yrw zVhil@l>a?v{8?w@!C3T`cMti!ue%v+WEHNpq!nNNRjg6ypfOy?p&x9iR;qqTsNYUS zX-es;{e5+%$wNY`*@7K$8niZ_p?qMx zRM0H3+L1y(aIs5JJ+Vz=hv(Tu>BWzHvYzG=ht3{O6g(|8Lmiar3jBQ3D$7+CUKkt; zI4b#sIySF3(Z8Dfk@IZsqI_2LVr-UXa*KYMT*CV+ZDB46_5lk1sf;)kQ zm0OoTZOgm=`iN!vi-skr)ZWi2kRE!Gdg+n~Om!k$qb~?#R<@`E@e7{1o})>=pJn@|SJr zyvh^0S-#y=>~|l?ju*w>FTPr0eE3=Uhw49!HfP3$eHVwzg^N6oEZHUmpEhOhXU9Js z=wF}yBYNxv!L*Cq%gFidg)+={yV6>QoEbqr{WZ?rkG@KYKUP=lify#2e8_cc z<4nxwMmv_GNTa+`Ehl@O`}u9^?Ag=@Tl#8UkIvtHD9|ckGw`QZ?D^|&9o6O)XFY26 z^C~%9-6ts8Z2QI|aZ{-K;LOXzX;1X_o9R#gvAa6A!v6TZcB7(dj9cxs#J9f>Il9_7 z>AqZsW%yh)Tj};;J3w5g%-pF;mIa0n@qnncK+|*O3D{hgVT(C5y zR;Ej@U6_n3*k>K;$NTKY=9d#b@CA)7zhh5+O?~Ec%z8&j7Oh^5rqFZs7hmlr=bK;vALR9Z0nGrw>OMA zN$(Qpw-<}$3|?PLGz~>fM{cL*)fzC<1V-Lf^2R-=8~k+iVqnd;Qfk9Oi|7sQap~oV zm)q9gJ=@mSC3Q@B(7+*9-K6X9Yl_xU$EN{5+QUvs+U(&Y;HnB8R-9miIFs#PFKGaQb@C)SPe|`U>Ea}~OuYAZZJNysd33KVgOBa0W zl`oZ?x*^oAPB?eVEi(5mYmAxI+F?z7&tJtual$lW`?0Sbih+4>@chvc{4*KT&UG&K zz&U39W(9KPz~`~>;CM|5wbiNeLg$=9-0Hp~iGNB8%L0YP`EqaTlnTonU6>p;r3{XC zmiKoyjYjTpF4kE~t>~-$DQ)f76-}7Q+PH7(hv^v(PtK=>lsk)bu24?M($%F_q({~& zgjZ67i}-`}z4okLQ~RwQxKmMkL-WM7(tgbo_p&v={1hx+Fure^FzYH#7oI=W7_EQC z$B~JPHmol~?Gu`rxH6&RxH$N+OkQ-=)Ny}Brq8Qg-4-doPkeh(L-f1Mk;F1CST&i6 z>9451+>q*gN@OsLs#FxvIe+x{b!%4rA=i>=HlK7l_j1NwN8zXDj}q8E)}0PMchh(4 z9hId`RoayfLRskHnSR`#J?qQaT@~xg@#f`@^eLv58BU3V^&!6ue;sae#kH}w84t~; zy5M783wz!*=ZoJMf8{IR`0nxzukT8{xxGcx$GhO)b?w_5qdIdQfSj}AI?kGnyuG;O8OgId5uyR{AOx{dw`yqmoe$& z;xQeWc4{*3pzHUbhke@Od3|*j$6bF#e%?X~#;2AWH3v?~Elx~L>|yH2_WhJ?=zYP_RVGevUyJIBQscLJ ziQJb9>Ke`J8sj+%U$cLX@>fr^FIk++l!_1K7he7#XELGlF}C2N<(BK_e*PG}%_n+S z-njlx>v7pL-_m0x!lV)E)d4BBP+&j9;becpThaCPNkZ)oT{Hx zYoA_=%Pw6d+#*?rM3&`yN7%^+emniTr-y!{`@q>h-8brv4xQB45EHNRWcd+YT|BKn z#M047xH>nL58pgj7sQ<%c-vyDU&;jYnZG8U+o{b(r&4x&e9rx+I4-ii)2ef?*F|dd zsxh)9jj} z3^&pj9MVb_YO(YpPeMZO{iSy9$ErnrUU7D1d#7wetuEV%y`8IYXLRJc^7!-2)+Z^+ z*x)U*7cQSX_3(nb3ZK)#U`5<*)gSJ!X_y?GVhiPbVVC7W?S_GIr4oa$a^~uV`h*`Y z#~rrGh@M=MxEMM8`-*y@Z@h-+$!gZr15)NxeK$kY4E@qP*5dnzCgroe zt6%)_spM=KDmW73s8vu)k)K=C(R2DcIx}p&JOtkmyDpzRcj0F|?ZOWu>i(~@2ba3D zEIQIpY+laot`QMBu=LEi{QJqFm8Xt)b@RUAym(yp-^s0IcjaBu2=xV>>^t?!^fa25 zzW)|vSdJEZICQh8*zoSlShlVSoQaF<1wkKbyaVZ!SHq{D+kF~LZw`JtbaOFG#rLJc zVaaN??Doy)5@Uj2w!fX)NBAf@>h^~7PMr*F;nb*y{Vbe1>388&+lQ;YaOz(u*L4Fg*GH;b zu9th1>(c?0YwSvOPBU^ytc+^<|CNG0+DE}2mK{LBzSLL2ZsB^`8CvavE!uncRO3Bp zj4*jUY*QOyQue|&9-%deu8I;3qSte+22rbKvO)Cf7&eHG+{*JHZ?(>Yyj9x^%afn~ zwE-sChI{Z<{rsmUIREKIWx;#2asJa?G&zkXUt^kF_u5;1EGG0gk+-QHQ#4|FLW!xX z5z}}X)3I9+)9zc$V{uDw3E)ItDeBu>^z+Xew^-+&iJy0b z-?Rkx*Jk6iENJK*;VqM^KDNKwI~`+nTWiABJEvQ=4lryTIhwZqGaR-a9B$fLHvHe; z=_tKAb+~5f&5eYm-@IU1`c;&)^vgTtcr+~?U|L#t;BcQC&=U2aPd5}nXDUHIXas$W z47v$}4zYr+!|lyttvJrOyu4 z`+VL{7`a!EkErJD;6(nBrWbFE$&N;uQDwT>6+p9 zxS%sR%BzY7ne_C^n>D+mVE1D=mfbOi-Qz~m?ssm6-7nm1dV2cJK1=yydeGlQh@dZ0 zg5KFs27RFn`bGwwYz5uRYtRTeUJrRBLS8w|3OUaRd1)p?uEUTkhL|D$Jwy#TeBs|_ z$c`ae$mb*EdzFxHG(x^hhFmlRAGKMH~baj!jjnN@a*G0HeAVH7j8btH9-HK*@0~yWhw=tTAj3(Gp zQ*+1})UzDIti}$xcW=^+oKa60SuxczvZZ0g;LHU2i5_^Ar zqx|1Wu`32c?1I6j*v|)_S;AJo6_u`A+5nb5^0Z~?FA>tx2XCRJ?P+PWX=&YNgMC(l zwe_GEU4Z?cO3*JEK}X4;cMn3)>js%YuOH+y=&uLq=bs?x@26Nn+l-*U8P1?*FzCr< z&>>R>dA|02jJX=SI8xn*U6n4jYvi$OkMM=Z5$(fIn}d#SL;f6|hzMVJRF1!u|pQN;*47=YNO1oc8huu@s&7Aji zdNnJ5(Lq0DHL@Gokl5<)wtb9xu$E%m*1E>~Pug|?ZToAW>C=jV z+Qu@gZM_V>YzAL8cDeAdqfYXjo;}5HG@x~ zh8R52GC0{Vcwhz%9y5^V-&zLWJkbB~ddB-&kL6Xwa`-7_CDkP|ma?1W`3)np~A$#rB@6J%6h4nS0M2bhB94XFCOK=1Qs%1I-xJo4!EN*)<& z&i^U? zu8!5lk=ecM%X%UG!*wg$!^+7|T2`(PGt&PiTIuW$E4TDFt=!sQT3IjI?o2kT_VrcP z;IjrPb+*r%ghRm_jn;Lr+ zqqvWpICqV)J9gVdRk6^RhCS>klz%UqV|1zfR~vpR#?wZox9<`8UKY{t)!@58M4y2A2rQPhTGCSE5gHOMX2ZYKOHq~_y}776<}3|VW0AR znMT{5<7rWA9Ut{GOS9G<{5nLh!u{_FPfsQv?3A*(OiMkA;fg10KI6)8%kHPq1P=%=rRttB*ec+ArHZEdNs?I78I z?8p9NKdb-PuN5or3s|OhV7v4Nx8zcmdQQ`0yA!c}_ox-yT}EtQ-N4vJF}8FwwnjJf z^S0E}-1+tLjaAOZt5r%J-I`xQgf*8d)*P!Pt?3}G*`ESyHl~=T@NY^{PvKYVm8wqR z_f_yjv+p!}EkC8&f5fnp%%rLI6^_upvUSS2o|l5m1?wXJL@|NUqHvVjlr(L_I0dysXrwHDt8eKo^|z_9uMu?(9RVx0d!kcLg^3&S4lYZkEVzIum1hI+dmYA{00 zP(rQO!f5}>P_JXC9n4VccI|5A6ELZry7i&t{li4f_d9Dv+KibF4 zz>oD&GVn#B4qn4=V9AwkHg2MJFS>qKEOpVX$*+O&pNB1LP8$<+C;HQxa9VS!x0%0A z_YQQnYg=zErml!-x)Rg-!7`?2WlW#<=Jzqom_p|CmgS>%wVJ$cwd5VT5uXJKBQ8*k zxY979wltzUjp%3@aar%`H@qe#2I;XTBGy+PQkr`;jPGBjG1ih^i1mkF=9^+wFE4Kj znSP)5($4~{tP$i$P=db0m_xi$2K@qqe%uVY?i0Oyj&O|8gMJ1<@65J>zSIbMTPlN2 zX3+6g&{y~lx}_d;djvgM33^MA(f*Y||CNlO4#MCwse>B=`2Azsl8S9#u!P8|!BO{EsmE zQpM~Qr={7grP)W4xZWViT5ph44YO0v+-$dM>HZ|m?B*~#`vJ@B7KYhl`^xz%nthdJ zc2~pf23TOyDbTtpfYW;QZ-MeyST2te4Y?Rc(sfE#<26@T!}^3nOW7$gKizQH5y2;n z?&FwmWIJGD4^4c_zlHcm##-VpKPAOK$mU?kupSVf(!&(ruZI*LZL_zJ>noaZ_{xOa zcS8d^ues`k+TUHX^}z~Z>yP(aw!ZU^v~^`4+Pb7WZ2h>qY3nE5rL9rc2P)51YIRk= zTG~sV)Z%S5osnJ>tghijx~RAQ5~5DrXNfBLTZ$_0O;LYe2T{)JOi>4~Ghd2-`Tl&V zmx0v)cZ+yQmLYnvzyCT-!S<6vL1#rlYePXNsh}?vTxBWfYACqD`qFqC>r3M?)|bYk zTwfZORl_)=Y8Y!&4X$J6-Kz%oyl3`vtIlzAx@ppCK-$gsTGED9NNIz6QQ8yTAnl%R zrnGyzRkOtscs}ceb%JX=kMGv(~>D=X(FV2nrKQX zOf+gaHgZLWEt#LXKX;kUn2*xUeBp#J^T6GfnPG;RdwRAi1}V6=D8~GP#kFSF;L~fBu0aE(b_u8-AMiG%6~3l#jQ-%eLCQki z7Ty=?^48F@$9otrfm7G$YvGOy=~-he=}Z5V((g;6^exvw`tobcys_e%R<4Q4gL1DD zJ`V7^$8z3-Zp(Rb!(QtKWyrExd8WT^{?I>!`Jdfong2+*@%^(NG`|haZ)}=hx5+g= z+HZ~yebq^~@arq}-3_Nx-7XC8qTYYly#FkZf<2-R{HB`w(6Zh4p&hY~g6&uqCmcN8 z_gS!=)U#kWT4%v7?uBt*Z)d^si30U3*yrWFWv?Wk^Rr;Rk9-^neB|SZuaS>VSL^dx z$SqUuv~o+uA2PE)#pY5-*wx4_C%c-N{jaWP{#p4>RgHXf(ndZ4|E#?C6&h!9x?SGj z08h7jU~qsN>b5wp>|g0^+tkh*mCNF``)^W9&%is~3fHWD>YpPM3>ticd9sK#LLJvx z8ND20yaIb)rKS88M?}i!+-{|O^J6mQpGjoO$6SS!uXmMMPlR73N2o*1;ps`sqJ|Fl zG0o_>Qj2Z*VG-MfN^Hx@Wo%7lY(-b{eR8Fl&py4f>S;!A-wW@BgTo)yBex;)S)-JO zixK%7*D~_KjJ&56d9o3CnCtz8=&OI&iqTx@QT=A|>Mr{D$03Y=jI;z49+d*NT|)tL zxqf*0GU5jr@oiSbnMTCt87r_5 z!}C1?Z7`W1Mzg2JMJ3yl_Ck$T=1h#uQ-21j zo2R^g0copx0@P1I`ggs;Cx_Jt(l$?xbDNn7pE6tgg)?zge=94>*g18PVSiKgMQuum za&4db*02McxC$A++2qg$y+!d+AGOI) zd27J!arx4+`q=uZyWU$+X-3cZd(ksCPSCxupRe7=%Bj1?S(C13oGI1Yf~+3AAcE_P zL_$UCI({LU$1m*ZR_XWn1-GHxA3JKLJM@4k-6MusrF+9cS-OX@ofYzKN0jcDI+~r~ zmpdw*;Z7J6#QA07zoQNFMhNq~kLMaa~g?QO1mqPShFTS5D);Bi9>$;O)O& zU(K_VIDr$s@F;RYgeYK+vdgO1r{8FtY2s%km-THIA15WN-*&kbm4Ak>MP5CuZ@a9( zkpD-m{eIZxV{^6FWO-xR8|VJ3ESDZMCz@}nNr^q%>FwWAw1021%K96| z^u>b9Sk^CW$M0RWv%YuLPPRfKcxc2yt0(Vw_$sEV;Vaiy+s>@#?moWWR@3;{ZlQ7G zjh4p8_eqWGJIeNDTedITTJ6iW|D*ABzd2-_kwZi=k)O83uJ<;O6G~|)>cw9*BX}|pQSfi&uu(g&uz>~(){jbl;?Lhqm;1r zWFLB zjX>=&8nwq*QN+e+U&s(|DMv(n^hE#)~{LEh$8W&?9e zE2V+i6q|-PAhSiVylvAB)wf76KDT3P+?R(k(C z(E7R!PATbx8oMiY37ZAKmDTH(W!~)tpJ;WwzvgSmtzMn!D#=>+yHO3R!v8vMmgi%$ z^v8@^d~c9ii_gQ4@X1}Ze)s}jcB{$5r+h9hGS4HwwYDN{uGJ?>ysi#;YfD#itxv8< zGrQ%-)XF}5^4=h~p1E7CDL>NgyK_Fk=$uC(*&?`O0?RRybDm{onwm426=kaY0((~* z7rcv1-^S5C)w9d4c!rUepO(G$D0xRUW59YW^yNRa`tpI*YmTu^+`2?Nqm>HI9ETGb z@<&;RVQv0?&NoECAI2(jM`+W^O68cnrud`THajO1uv$j0I>_uN}r-*azS%?dLr&=zdqN1$zpS9^i2lJvxr>)`6P@OJe; z<7)I6jn9zssAc!0`48$!gTMAEwqDh z}? zDC3~}O8I0Rx$E=*c7>P^MX8DSPZuqLcx~%du5n*4mgOWUmen;aOYY8RA_91^4AH*i z7bQ=Ub<~VA7psLie+a7j0Dhw(j#H>yGrjLM*OI;GMv?5V_qCF}bgNAES1)3+PiT&0 zzpuHO?Dsc6qi^`R4za%wB{0gI+;vQ-FWl~$+ue~G@OB(;qjlVe#NGEYAr3i;W0e`t zaYn{p&`k3{kDrAHUb9&qIJ`xA;FTD9;Q40oz+=r!4?NyX^?+;Bz1OWq`4QGuBOlsq zuCGW=O1xAzY+kW2EJ!izF2k^YTGFr(8g?w&T$KGsw2zb8eNL3Diq_XZA;#zXSTW}P zZmfTjG0u+W`2*4Br<8M}%|@Htqg2J0ls)H(ZqhASut8W*N3o!*VL_<0;Bs2f(zGBX z##@pBfJ?`Z;`yLUHvLSC}Q_a4HuU%=Jahz$32)y;coy)bW0FU!0hhIy+lqMz8Xyf zIIzoW%xk0cZ-K58G2fuXe7H=;e7%f$UKC=U5oN~wYE*zRpIN$Hr<*qn<}FCJ%o}T% zH@`W}YfAHKN15ps8WmW&EeWqG-Q;qt@0w`#{Ipis6Q|fS=Vxh8jI`&qCa~w3Cgw7W zsZD&Y)X&y~egHwQ>S+c2t}(Q~ycvV;&!Dfhf==`ubXz^>#t8a8CFoGY>M=6tnhg4A zV{87tv6n;Irpw0A3ANeda5Psbx~0hNWZ z+XFG4W8*nB)Y<2;Zl4pfrmN9gY$HbzhZv)XZDf<-@`hSDsQ05N2X}P0%0cvcSq?JU z#|pWiAiHPpucj21r~;k>$14(f%U)lP`&oc6I&m^qUeb=?QD3^cxycdh_~_9%f1p zxuE_T^&!q|i|iTnEPrh8wg2{VTebgI<&=B-2z~$i3gM;8ueH3?!I*mJ(2!oL6#*~( z5pIr%9uHSWMC(;^w%vBA+c?o&Z50Unu-R9;-E&3rKy#S6)Cli}s5J9K%=$V;EwA;c z5pZ5>uDufcfB!91R@ly1Sar7ry^I!Sekt#$fxWk@S6DJ@|fVJu`>e z>!~?38Z~xks{N?(W|k{*WUtTlMLRE_f(!Nh6kL^gyS*G@^^v#hX$Lxg99VbRIB&PE z=8=BOghysytxO89k{)@xK0PwCF3;PoYgSj!)b+ie({V5?z(L4Q8>oYjlg`ryAzyCb zF$gJbkJN2{VJY^%bhT{%^9N)9O9XBItqyE2sbku{trt7t_ zc#Np`FiS(Mx6o^?QAR0i<4QqQ62p()FR=GlB+Cdv@qs4Hiy$0hcAFLe!alV z+B+_gJ)8QroH}vZ^uBGK>6z)*g{sm;2fy!Y4jZ_lT5ql6ypE1h_}U^?j7c z>ekOua?^;94|;0#^RDn{yVk$$mZ_WvzBG zKOS{g7q6ZB82#f>J-bx;{&ZAbyp<7G70HY^HH;bY`&!6|pVTs6 zE%R&n96WhL5Bis{(f(6{e%1)OgADp%20h9O`gU(YyQ+fF5Fc+`%*m^5*~*JNW=Xn- zFWkiWh>FwRCjC4|i*3emaY=}#s5TVcdby=&-S<+_(DSKiK?oG(hnS^yc8GlS)aenv zaHGxt%6f?WtcE$s0*BtMM?du|5&f=CR`mB8(QglB^t~AU6=w8xJBRpjXzL(vvv55w z(yz)j#jbUapH-e#wp$JHbbfI|59f0sgTvlNooyO|{Zi=prK<~G!x!>9b%!o@Yo&Ln z=e2vdKiHdqn14?D8A&r!Hs(CdwH+2?|KDYnYvYy}`~PawwG}ntT1QPYWt}yvm9o;d zL6=qrV%y6#HQQeNQrPC~XxaAqcha_fwP@RYv~8GW+weeb^EyFsi5~kNWV*YR*tcww zvEM0U4`S?xg3Z_u2UoF?#hO!mxuur+6^<*KdGy)l*Ydb@%JWjig`WASx1N=GU(#tM zclhYgDm(w%1?%O15z2oD%NNHs8s$HPzBn%!z9_F@`r=rPGdm>m(N1nfAS1->!d>BR z7v`xwk}3x|xrT?w!trCUsrY)2->;un1UkrRj9Ko`#w`5}avDvnue2tX_boQsAm?;# zkaL_{n{1rcCNq{<4mSolV`Q6bSdf;wZ5N8vedRuUy)Qt-=Q}^N^ zWstMEu|m?puLD&W4YM}ZNN0@EG8^aCGKh%S>6 zMKPjURz$VEM&xZKa_=cErUwwysy0?kYrZt*zk(Rkx2F)(ho@p)Gm#&iidCnOM;`Y+ z6=_r~FVxXwKB7ndN1=#3Rf+swBXXOJd>|vg?vyFB`>8-=@=erSkIIgycDJ^o+P6+d z^($H>_K*|*AgaCpm^%0U6C21>c}~dQ4Y5HvLHIST*!AlN;}=r8dDU-?>Ws^$H4kK5 zzIF4ePlfll)Zx(In)jL^$3#<7C6?4c`Cnx_tE-1lZ-<7a3rePnML&E3^d2L-vim*h+xxg~s~lQ%r!HDv$uZ zD$J77yTV6FscI1KXL&NW__vNouJ&=_2Nv+h;YV}vgID6jkIwkfUi`Q=Zc^fozch2p zKE?TWahAC|KR3?5`-kQ(`U~d1_m`Pp=Kdw~OV?EUwIbXI5w0B(M2MK|h%!RNZHEyN z4dZO`N}V{@nchAQ^!9qNf3j|A4_LaYm1Svv!_wt{)6y2Sw2ozIUGdXnlHLgWea{y-)CI)?peGW`92 zBK(bin&EHy(|`D1|EYz47s5Y!krn>(&y4l2e=+sIXf82ul`iUY>H!JX2I7+O)!vLgIXSq>r)L#WsM?Rj{ChZXBjXjTk|75ic= zE9{09yHC=Jf)lV}<_Xh^*G>dzh4Gee``2l`KQT4jo42B53nWRm?z@kKb$t};Iu}ao zdP?h>)4H$|=9~F~6Uv*pw*8Q=rBFU!ZX1qk26Te~D_U9xeE+F5py*E;@WF8y@Y-?H zJ$c6if4OaO3@dYU&d>&aoYPCUE&oGdTRX+JD-7E%k+!v@ZFP^E&Z&1i0Ov?U#0ygN z@mGoeU(YcbM{KOem{d?`D<<93L6u-^b{I?~lR*pByzk zF#l+*-?MVKusK(cwRIf=XzK@rY?x-n`kSh z?nX>smNTYE#uQ}9sZoU-f0gi0N3^)VoGaq)p~SuYLm79XjQhPKi2LOuu8OYiD@S6_ zd@(h|57iCm3Imoj^_+0{`X~+PLIYYIF~!Fn@he_d9kujGetu6xGE|8q!ccjWjAYkg zM6&j<8OgfCdetFAU2<3p^>Kt6qJ+Bs0~u-!8EPIwooa?!*M8VX=fL-{@^C%MFhrRf zWkvaj5#{qo80A%r@?uNKC5DhdhLz7|AC*=up2tU zO#5APW65mc#>~c+8=o+yA#OQDH@5yAZfx|s>Bh#tOE)%1vx`jWlqbb12P<4;*yKLS zIiPwzJ=+Bpqxp>#`gC0?)^)8hKa>)K52f7CI@;JyhK)sExrQKIA5-B92b;Lfbx29s zf8?zAq{ODbX+E5hFMQadk>$gzZ>0~Tf2R+RmBEL*%S<1Zmc_abnB2{Collz%+$$W5 zrTMAcDg_g6hoxSY?57=*)1Cn=It^&^Zw%JJwMj$%TMHL-vUVc$D&i7|Dstui8pMlGAYbN&`~$MTx1asJ{jR2IqrCmG%IROJ)jY=gTz(z8 ziLPYO8$!wUdX|z#hLWxOspRNBDA~Es%-6fb&;QZap>1Bjb$OY?e4kq1y6mw}Q+eO( z`2Im%OJ(Xi#`h2QQDrl#Jl|3owlCJT=)m{4E<-lU_n_Ausj^wVYOiL;{ygmeQtXH{ zCQZgjJ7({N9WU-Ro8`HCeKgAhc2C3sFYZaw&AJ|DZK-3KWt%0<`gyM$Z=+dZrdc5u z>(G7QH6S>aLh|o-xC5tu*VEJuqw19@DJJd#V@`l8G`(k7gjE z`Q-vDns1Ep{!M!rO*cl<-i)ShhdqHOO0~aezNJ(%Zoy1p+_j2v0}SK3O5;8)g>i3} znm(9S8vE~l(cFtj`Jv*C-oD2frOVm(D&{}JEIIEOWBhlOQcn49$l0~ql(T!cn$x`Q zAwBGPY~yMFAD#8cX)o^9%<2!bHlJ^q6>peTyqjj-O|x#^ZH}pC?2fH^^R3r2#CL5` z%A@NH@krgA?`B~9TQTQHW2T~~H0QTnFsEdfnIpIDiVf@xacKX1mljz%A}a~CB7541 zZ1XNg_5ve&+>9*biCqBzf{cem0I^7pQI_LdN|& zO>d3e8LRAr_Ih5pm%`}(^+^A=&<)FZSs2zpF>G_5G%Q>ic5(*{+rPu~?|~hD{5yW5 zTNCPL$7w%f`T09ETPj}?wmehIvgJ0zmPtEk%VV@F_s?ob!qlxqL1sj&r4ex=l{@3 z?OZ|i(8_MVYT_Tw72<1#D0_TnO7WH3DL(2~t{1Yz*Z$QU8gPnES+X)T;Af~RsH$O= zpa6$eCYZ|_IQE0gCJWE5Gq2U^-VbpeLNZ)Se=^M8D$kUS&uhWAbhM@P>w1VDc z%v9{!#-N)r=-O7$prtVf7@`iBIAD>u6C4^6|M1H|Pwu;RaX_HH_;kPeu*(I)$itwZyMiwKE7lQM}sd7d`(!hx{L8 zdG#%0Uf|)a^y=ZQ@anHyO|S0Qs(Q5{c8zi`D5eIljjQ@|I5E|8Z-Ae}VI`8XRg=~P z(%!~uK|@-Tm!-7V*~kltqqIm=f zdh*^XJN-s2a{u9KEvetKS@YZE9Gri2+Vb0iT;u$s&9c9=nf;~B=E%&f&C1A3LywPb zRy8!^tv93_*jqPn&or$6Ib|96$vk8I&nCJ4hX&TO3=CHdj4?h8ja3%5RQX|O4`aP| zgS_s)+2pmV|9YE#8SQ23p>Nl?)%%a{*Dqih2u?Q__r@RbGF-PxgyuMT(A!^d;_NM_ ze6{hoinrD#&7UvXg+H79WBIdqy7Xt0jdK2UBj-;yTJxtH%^szn{Ulo)&$z$u7P@J1 zT53)Z zSM}aq)vDgx;D}+qHY)U#-`#MnN^!h|^=Ceo_I~R6;)Z$m?c(mTyZ~P=$Qb8s_m-jF z+M!%E2GyfadaO`-#t~?)vT?X(a}PN`Ib{jQCNMSG(5Z|~JYjTz-`k*-w^mP!^7g?= ztGqq-oGfp1*R#A$*nskO-v+b1-M>M7o!4VG__W9-e35%Q^Y7!Or_D3}0(&{jH)vhm z(D74*p}~rwWzR}OPp_k)2iC*TP3z4!=;rl4c4lxr<2tuO+-80C3i1DLJ$koQzB8@1 z9=(ycF|aMjh7T6(b#uxU>)kG-Z&qik&ime5&5jrPF|1XsBciG^DRJ;REgcIdiF8aj zVWnfY9GQ++u4Ou2whrld;W{(JwOA)B_;3+^onu(3TF2K)RiiZ3Q7hzgIe3(}R+DmI zqL6a`aZAe2#yI=kYbYgqEu>_wHKmMPd+rC1{7rgRtkLYf3if{Yhh^_JyR`SKpJ?yK zHL$m6jcM=FHHN)d(&K;DGyDs>e)rP<$K8{_$8>!?mXH!A2%@$iNQ;W4gi;9!6Gp`z zOT{FaBtw!JGm}Wvo=_uFYAaext5v$_qDAey+FDgqwM8jL)0WbrG|6|j_ujqty*Kkh z{M&y0{VFr}o;l~-v)y~o-DH7m)bB-}2Y}2vhf+5{v@n5ap*`Cw(=>Sg^_JACjFsg4 z>k-tds;E_e9~E0wcvNAv!M>gd6*-Qa2e;uVTQS1Cj;g(ewUd0d?sdY z+#$r=;lq%*&4)#s`|R)|UdE9r^*NT~XCTLWZ%7G%Mh<>%6DMg$I3f^n*SrI(_a*q&EId z#$EIkJ8=br;fPAgbQ6(rnQndVUOYn6=WfAB$$PI%dg*s?q|<`LDat&NT?4^Bj>;NBGhXT>V*>OMe3-hv8by;)Qc(9vk2<>81)E* zI$A>ATOD-=7WIZKfchB9d223?cdv}6%(Q_ODEreS+wn|kdP+ho{Vx^0q;2Y%bbFxUbzxWO$k*pM*z!7jw$ z=X=n3?>*9a?>!GR9(a)@`$-1e|3Z;HLsINvn5+qr?I)3q+4CUdfkrIVXCc)CSE<~= zBy#?5C!%_PH>7%Pw@CHp-48-_>u!eX=T1O1hN8NFP&HzzixAZuiE3_1sY0SdSfT?V z(eJKEL|2l9hHrKtqM?Xr1BqzE-6hp4q zpjsM!Ih19zJ!Ev-Wr@+c6EUNkwPDAV=Ts5IecEJ5+LY zIb9FzU{E_C>id@@)N2XqJ6jRzRS5MTqZF-+UwC<%&c(Q2p_6r`xdSnk>xed!HdrXKaIc>fBbc3`2=bsR7Ge zUOL?WMKRZeFsH}N-Q9}ze{B`Tcy()OJN0W@8IJx+gZE!ukT}XGD+QA_BaSi=M~Naw zRm@u-VM0dFGB*-3_tSZaxvJyI{+~^VxocY>bKh(c#dT)OBTmS8&O4tbhM}}wL+)a2 zw=j*hro#KL&Pk1JL6$;pZA6XrLyf)nnb_F-pD`|mECemsl%Tzl8rS=aKk>TWUy>s% z$2WQ0nLv)PytAoD%Mli`iK&Y`N!@7+9V@pUc)&KCQEWrKcZ*cs9}lG3aQmGiEpMs2 zpW9l*F3P-rfaZPf)*_`;uj`2GR8H5wHZg|OYz#1@_1{Z|blZUq>77rIA$`3G8q)4f zVy1V`Ci==OFm@|+os*|}arjuy(%NDHw7OHYULmxe#k9sFTINk+_(8 zuYO0RiV5mV8xZQ22=(I<>bgp(@o=Lei@H2SJ%>VFj-Z}}QUACRqW*HDINUhCQDwN{ z_0C0fze^za*BBfG{zVrsoydoRvg$YaAAl`C}xaFFu7L+V-g!jkbf27gvW$S4b3dn_G)S0CI8lPu#`TS44n&aX9|C1M4 z27aq0*tPoBYp}y^GU5~V{(~fV|G^naQNwKH{(}#ZqMrT)?T7qC+7J1O{8p0(>DDf& zbZbX8F#Ihs1O9?2{*GEPfAuhbZ*745E!ZIPw{U}Ex8}7Q4|@2sYuu)=YHUlsNQCpB zZ={wyPFgbH13drv7|nk^7B_{R{g~Pm20S7Xo^r=IWkY0%wuO1T;$gza44cg%n;)E( z*sPq3*?f0BVsj8;v%AP?19^zfn>rlO=U49*}y0 zA~lVW8jnfEAX3jt4e0if_x))(U4vLE`H)IYib@AUr5dJk??Xt%{h`>PYaf7b_XrwI>qI1+>DJ=5*LySroQ3 zqX-p4wlsf&FH^cGZbaL5MA|OE-=S16lkPeYcs>BXmP$_4YuLKx{{ACQ{v$qR3Xs-b zSt+jG%O8wdhBEiE2cuTMUL-siB_XQ%35DE!VTbqKA|;wAUU{D}?{;y(ytjTOnfK5s z*t|EbL*~8feQ4h6-xvM&gZKZJEB7em^k7+@ItsAtOR*ej!7P`@EZ0XYSC&}zd*6G= zaUcB5`y3|%_U0#?eLSpX3!BM1L_%5z-7y6) z7^(Gs9!TxPmy*<4reLXk@ivm$op+(sF25^E?aI4kvlDX@yW;gl+VF>l7S0mFrd66t}`VT7xLw;Rs*!gxasmOgpIHN$*j%@!K1z+uq3@ zTKlQm24w`kp2U>v?kO0s;I)!H9kh8d7vl~d_wElR7|O3* zu4O1ceYtlpC>3@-+o6S>1v^TTMwzixW@C+Cr;5mSw8@K$JnTIizn1aZg~Nc?);lVB z?c7A{wSjLUukF7U-50)A^xE*XUXPj*b~nE6wPz{Gp1tJP)!kOwvloG&9_i-6s&8pw z#QmlhMpXCjUS7kV{~8LxhRwh=)!R51lpgK;0+vF&7^?zR4Dl3vA}S=f$cbL}m6-f5%DcMuAcz z;d@R;)%+EOKRtdr2iVuql6Y*5HEbIU%<&<-r|A54RT!fHvvs>_k1S0t`jYXW5lCqZ zi~+9QxYwvJZfXh@yIPDlVS5q(c(`n7YY*H$irB%wX}w)@w}#H1!i({Y@DjH}&KOD| zM$WtXqiHujFJeSnwK2I}+tj_SKKhMtZiN2T>}|^4r_edMS1jc$Z_)?!0NJ{X{?_O% z4tN(mnR3rxU#gv&W1W^>r0=?t*ra|T&G58kTzhP*rGh|g14eW~6f_@laxMg(oC|Wl zg&Qj!`3n9>(Pir|@zf#JdV>iytd)m|GI{q z4W+p_l<_)y|HlA$|HnRw;g6pu_kX;G819D{?kX|-?CTGfMJwqIP+D(QfWzk~{EU2z z&RL&+Lq3K(7?vRBv14`m+{HR+P7a}C`JJR)3fh9!B+b+3E+jvT0Y96Ce@4GFA0ydx z8VSq?qFuYAUG7M`JRGEnH7v+cRU9K+jBQHW+M6E?n{wizgrFkduX{R*u%>;UZC>T zdos>m;%7G7cgSGQ&XpByS}ZF%xt_z+E>8v%zFbzceVeRkI4x-R_bwU0w@YM2735I2 z*2?oxE9DY+Z=S5^vsWo~d5A>Qep!OT018Ioa5uXl%C$cV*r9&ow%}bTY-|f&1s&Jf z5MFK?1MkFy-`#6aZl9zT1IkcWn0|=BnY&0oL{#{V?&oyEF7uqOU%blR|6+vqzwD%P z^|9ps7c{i;S^Fw9p~bI?CiKdy*n~`Z3xhAf!Q!CcQ1I~#{uly29>*Vpz{k(=$G|B4 z#^OjI2!Bwv8JGFOM_G({bAs0jA?)`^93>4fh=t?3W}8dxF5TzVp;DZ68#XcZJT96dD#S^)ABAsYr|A z!i}l!3-41}EYeY0TqW1yDn%`RPH8c48KcEddjl}O`4)y&%kI((ce0}M`^0z1NqxEN>h`UFq*0y4K%f2 ztE8#V24PLjU4S$dy9B*ou|#^mVhPq%Do}(QD?gbRO=QBy6_Xc2v?syp~^p(X>p)(eX3Z1$5U+sEBb4$NrJLVq-FUiao4MQ!3QoA(tj!`uH!GrmZklbDbd%m6>!$HMq?^i%pl<$J zDC*|kLadu-faq6AT3AA~06s!7;XAaAdAV2?^d0jd_#QMZcVY{=r~#E%Rm@wvPfst> zbVqgRI!XB3lp-E0^B_+0HRU9m7ch#b83`1ze50g@yZx{tmcE1(F@FJ6#MA|%BBm|C ziWnkAvXFnLzcim#nq5`Nl$Q5asYbRM^V$9X-QoWKPpBo$=g9v5IY?>0z6_Q2-OHlV zzJK{&9oZ&LDb2{1B3wF$;+hcDf?!blW3~7uL*;939ev0&i+c+ri#lLR^tegTV@}uk z^B9GE84eUObc3XjZ;eMZ?Gz z38aF~g&M`%B*?=VS=7TBB&(6dT;TX{uRW-FfS^XS@d5gPd+r;QkYT`*-e~Y<{GzB& zNTTjIml1VhHz4X?K9WQ|xepe#+l543Z7vjb@k^qpb#ossV8NFu1vlvHy1=`ELFiOC zH(&=_k{13qy?5o;OSSjjo6qRrLRX-Jln<$?X>Y89#92rO%k!ZQ^72I;%*p@fd+)#( zN~8B?L{IesQ>9)YRCqMQJ!2)Mh1;|i=E=1%kJSS4pIuZh(02}_h2dR*7A~%rw6Ms4 zwQz1G(n9k&=>45J()&Aeuoikm=r;n##U^ebmam`|p#zvDbO3_chfvXfo?`T$$7`d3 zBqSmFPbJZRO7=f7th%I!RM!dSFfcHVtVi`9-Ly!7qxM8uH?QJ3*7=EbRo1_kqKS zC8auWI6H$;;oDCG6}Db0sqo3}ScNU7A{Blz1FG9^Mne@GW9}#aqPsYuI_+!|3`;xCiv0dup0Tx2*>NxVDA2Dp14-t zJBzm9pjG*=UtnVpxo^5&=7Xz3p@D)b{><~(G)Wal^EB@*|9v*U-^j+tYj0uILu1aU zH)gGBcnxG5GbKlJW7=nOy!V($FS`$RXx=I3`HEYW%b8_Y+)@uUc)sFR$6ayz-RvTz zD{ghND{eoYU4koa$!3n<0gnJk7C|2Ycy~dO(x$>d*%5&0 z2++$B0L<;x$(rp}%P!;1S&A%jyNu^&6?wj*6aw|Nb7pFs0GB{$zt0ss!#iPTtM{=@ zwD!X)9Z9gfHO6y9F$kws4v*2~gk=9qKEt;~}Ib9pEjr!sVc>eV@ zsZnD{MsR==HR^sgx^F96JihpIw#q|4?$8%G$~*J8f(GL}iTjmo_Wmcx*|t|D&JI3J z?tgM1&c-3m;zZ7TMrS|B^;m6Ksyjkp{)eJEJ(}cyFxAS4>R(yX`ev4fQw`s^o4mxy z;^G{x;KwOkL3rvT>6`0J(bzZNn5?-w+rO;n0s5;)@nXDvJfqI*&44<`FO}4}svTCR zV;oYaaXeIK=kcOCpBev8y%q?ywDq?z;~@RHt`j$J0JFysNT%w7fP<%RpYrlWj0bAaR) z#BOvEGLbHUPH%NOtIU{A9g)tU1)~w6-sK^c?tVN8h_J6Z!hQr{JrUu;1;-Jm5T_r) z86aa@!^{&N>p*vuAgTJ|i6=BVUvyiEFEl;)aU}dWF&Oj;Bb@B}uT9|luZtzMF9{{@ zzosL#KkY>KPdKIfC!ARA?I9O%b`4Rx5EXxvdPB7FL=ti29~sP?c`b$ccF9|v;b2<5 zr!i=C??qCpH?+pB?wN*K-Oqvc<2$7N_>TW#tLNI;`%fFe`%f22t^WEca{p;6YPH1< zTRqe+wmR1SUu^ZdaqRtnLGb>+1yZYjYenw=8;e?lbXy3x-Lh0PvwD{7?_2nW9_=bGja}F|Bns1g*8rms%S@TAMlswe~F=y0671-PdBn zt!;@CvMBWfu)81`e|U=lB<$}OEVL3mef!Vp_E#&@+ItN^Yp1^~wf32oxV4ilsI>>J zu(h9B#nx`L{`0ymd8;e3tu7B+J!hWO>LD%2{Z}cd)jy`A`>)c)Rv%CQhg%&>w;DCr zAGI}**m6r?%lp9RU?F`9_j7a!&goh|ooRWc`k>|W=SnS4dlI+&rDW9dJ89_tfHdj; zfHd6lFr%vrv|)f3+jwwrBYIzmZ^82Y`%l}S&^g36|w}g3WFqjSGbTy|kt!rBsv~Ekj)VggUxOE%NsCC^^ zVe48+t$T{Jt`W*ad20%F)(Nr@0-K`!U9^(x?v=4j>jpgzTKD}NsdYD+40woi$sM_WGt++Rk_+?N$~j?Yb6G+Vw2|KQH{FZr8*Y_jFES1a+_$5L7>xB&dbK zSWtaTNKgSOP*7!4#OpQ7rO*#40ee$>NQs=!8!VsI9~aVN`1-ZY$qYwNLyor3k~nJA z5p(p}XvER{WVF9KS-dH8TC&&ICrH~zv6PO~1e69+l-4xCl=@*xVTe+*WU=kdleM&6 z@xG*6Net;2Nczl+66uj0FzJ(Vi1e`}^nO*6XrJ4Xv^^pE$#gD3Qe#Ldt*AWmM+29- z6jxN_j|SRYC44mS`T$NSMg6&gwBhoU^AlV`89$9VaHMR}WfzaF-%Q59E1KE+10SYYKI%_T>I+jCwIbohzZ9p^kbS zi+Tk_-Hby0b`UxLf>HNDs5?ohJFBB^#-i>4Q6I^rQt<@!!I22H4x#=nK}3B$K^66P z3GDo@8l3-8s9O=#sTlRA3Fv(p3H6&wsOh8L;x!}gYq9joW!@M;=a7v=mJJhR<64fy z#=eH+{l5{2jb{-XEhRQu(QK5LxNrx=%j4;d3}&I@ZahQ#vOl1mM$sNlXj?Gt6Y-Gt zu6S{wV|TpTLdPqEwZ;DsBcN{X?=Yz|5e`<>i7CzMpg9X^9Hs%KwyQnJ;t6W2S zr4E)4#-oH^O^j#`>w#znOqWE{tPK{;bHnj`*@WiHCTYHG!cn*yzV7g6;r>Dpj&r&M z>zwr=M}bQB9_plVfO0xr*vr)nhRa533(&%v#wR7%b@Fm=+D%0PutK)SB8BuE4HeRUw5X7$ zM{8JVglyEC$njNRmOD~%90zndQM19Laq$;M*Ir=jaCp!?+Eqx>-Jxj7058~(`!YKl zU4I$i9+%rj*HyPQ8Qu@K4{oMJ4E1JMpw~sb zl080Q8pXbU2AN$vSz`9+vFbwyE41e*0#PH*U;ll$F!y86GhF>2cGW^B}?bsWvSm8OO%BO)XHH>PT&RTw|Nsc7GT> zskRBlU%pNG!`Sgp1vvi6mfG|~O)~!JkJ^+x44qFHCY?_hrman*BDAzgHX103W#9jU z>}|-RMy`at4`OhC6pQ+!SaE)SFjmVW&kthttrJ`J0W(P z$BNPniPa{}dGX$?9?yNw4`ulIr99xrIbPyt5aGwx5ApNaP{_~QLq&ep4rTAL$5@xL zSQkUA4JfR23D(CkRtLgrlCZ`rVjak04To4i&y=v9uYs}ddk*L85Z1a9)_NRP<*ZBL z5QfUxa)3%aMP(kLG73}qW(cIRe~3usz!1+_m%eECAtab^$dF$?8vH-qkT-8?8Q8@J z1m{hi1wNrvDK?{?;b2@Bq6pj++7$Ug`KIuJ-Mu1-mJEr==`n`9nDU4~xF3bx-3?%G zq5y|+16Ud{9F8AR2&WBf3q z0TiR%Al7EepoG@|uR;Lc5aWw+`%}0tJKkz1^@G_{~3c9TKEGHrgR8gII(*t2c@}}1-tL0QxtN~6>1_cL3)0Y|A zbj~39GD918rQ<4{c%|dV0~yzv0$j#j(IL53e}C*+<@zAknlli()`WqgYrOzIl4JI8 z;mY8uhn^BiK@UkYN7PWheIE>rPZA!%oIgq@4rC#pa2Z~>?Qt8PGhNWKRw>R~TaMyP6}mv*rHBO3XOv~f>gB}!9eo-_bGH}8l_k9BMX~H zGY@?8yVl==@Isyy|6(ToMZs+Ha<{fgC%|(Y1`pq{rIdPpz6Qu(@O*=d>pO>nr+ShKF;B ztqaga#L2(|ljb=`tNCCrvWWY&;EQ+M=Le`gB!PahPIAXP?hP2WV4=phBuc%fit%=) z6$PWVU~~jPGJ!Dk4I_0r@7>jLzPri}^VnUjl6P0V@GlBxXzZ>u-o|c3Vu9l~`dl&? zr7?!yHBw6HUU%2)sc!2vBI(5Lb@z=Z$dkz4&rFdQk>?kn4*vHOZKY3Ny1P6(FZ_Jd)g)t>C7Z$D@^+WV8erNYjZ z7%l89jM2nS>0HFmY*}eRUBvI4Aisp4ItY!rt_*@SP4VJ=v@2zR(uMo(*ZZ<|B2qxV zc~j-0efJpfvx1o=SlSyllpFl4jn_Q*5$&IJ5Tca>)MXS< zzu&>@D*J;R3BKJz3`#VQzPxO>5o3ekH#%#)K!?dL=$aX7czo~m=f0_GriB<5g0WvV3 z-dZD}_m{<}dq31(7V9ubQ5=DuUl!91DxLYQ_aMm<=xq_51LPW6!^h=5bGB@EL$ zxJ`uaw3b}Kd(7t86@2xtv0egB$>ts_VzpcZmldly%Dn$H$MBqXTtN##cz8W~Lm0Ch zX1fgN-5n||hb^p#m&5*Ojh4gm!{Bn*3t{4h--%%=hZr8@8MX^5&#*OMnLP3nU~(YE zWNpG^Kg{Iaj*!VK9mO@fs~sQg8MdI3Ji{g#-rZ+7nI}iet#iRJnGuKbTUU36$$$55 zu%cH5@HAHsQgfemgT2`BgFUO+I7-{In)f10G9)n?6`H|4t7+)33L`$N8Qh<0G`lM= zMDbb8geFB=p4B`WBirLbJEY>XnoDAeO0DzL@T}$&9+ofEdk&bd(%f?(RW<5R!e%{4 ze%i0I_WbmPPNkWj4hV)%midC|JlQGTfZ>?A2#5zxeM%g*%O z6c`h(#%b0UJ#|1&%Bca9uowK4#vR>S&TFDA9Ynt9)CGJ;0z@F(wdkYvz+FCGgx<*G zxlJlfR{2`RGW3HKc=w2uSF_2lE9xMf^32ldb59^h{t1gS)JaeMjN>|-0%=4@)R6@K zvqUAZ0=^8C*U3d*C+qeM~k4P;Y9F-~mJiFa+IL^<;qO$_h*m9Nd#Nw;k8YsFYv1PJu*Lq^Hc3jfBIwm%qI z+KN`t(&n@hBXoW%yfL!|a3Xwrb|k(%+ZW$j)`;-L2a6a!7I?n8Oj9Nv$=NSJ+2-0m zu37%LGw%z>C`X{^JEHr`&>-qJI4VVQbhZZr6QeN1aPNn6Lvn2Bs{3j!5SnU`_6j}y z8{O%=s@GM84YetT>11x9;2?;(x(3&ea1fjrNT+m@AOlqd2Hd>@J*0FWZ_at+V=c9c z%FANq1&xc97ql)`262m(Q^FL}=RvZ?${}GTJgI!S6DO26>+$oEPFkfTj0b|iaG)h# zZ;*TR1=^$Qm7zS^#(A`jk{Rj#@9Dh1N5u@%z7z*(;^Qj%+^S{hL4f$miZl)gMB{)J zuzP(;C!7%F-j9MQJEpJ1$0%h(hKo{Df^Zbj=Wf!Gi^tdF;RrukgMioOi>#AjTLtnB ziq-Kr{Un1BR40^=Y9vYftBA?OmJ5yTWx<LMJ1%10f-rASAl_AE|Zz5f`@tK35<)LjVb0T}hG2(?Q@U2}G@(zO5@cN+8+cN)Za zSa|6l3FbuxluC(24+fA`n4KXWGcW9K#3dP5h~~@-R&xrSXvak}Iy0KjZ^FoMHt5gX z)w@eFtb75>uVV)LY;^XHZFduK#MtnSl_^2oHQKfz(4}JAKx+Bc#dN+uD|NSUj z@1kfeCA2zWS|@_geIY^8eIY?A_k}=O?blqLqngC5p7F zn1fV9XMYy;Z$|*?ObYd51hpNbZh=tOkx)Obj=BPidI3aTmqNYodyKj^Mtz|nMEylW z5%rf1RZ(wk$oBsb^{%cG>K6&>Z8Z_OXdQKz0uk&=H3zHd`u9GqYTN*F|X?_?8~2jZ=1a3k8Ab;PG?eG8nzyK{@`GeW!hIS|@}XC$EwK8uBxSpx~pS|1AS`TC;J zM%5QDf4_(#S22DTt_GxD)hJRw09lkpv^(zgb#O#0g{wjTCq|Z0kG=o;Adpw@&XT;& z{fy<+vpSMjzk2BY>w41t*Y*BMBg2tncU`6pMF&6|W_FU=Fy#zx!}Mx+JXaTu=jw`) zV|HEDpp?m?UJX&Vpip-qsGDQdF$ndu66$VBsQ>l%&mC&0nAMTrKewu(#;gvOX4vD5 zGzaeo()^*LB+ZK7U}=7V=uXWh_pZ5XO(<#&kPh-@RG3tYL z(0x{QMARGWsG@#@MZFTDZcd?|Mo>4ws3#)SV|NUdl1y0SH<%u zgu0=GI!Fn%(4!3$wz6Sb_X8PPXZ8YGqbXXqPGMRjF|AX9kk;Nnk=DLI)nPxD2C=lNL0U_nmS_zlv=&vt<3U8LoP(Mqdu1QdL#;7mVhN!=)EuubA+e0(<(?fCjEMa+VhIVI2dry0b_RPze_6|Qp zdpx3T7HRt=)%H9~z%A^>QaQOBP#H;4F`UFyVlkCQh>Binp}(gVHqmiIs`+#(gXCV$ zRi(rr+cj}20efw*tIVE%D_)FIA@0BV1QAFM!Se?tuAUiqi;^HS3Q#9TR}$ zBXK$`K-+aSroRdw#f3&&Dk-$-XbtxMQ!p%WPYadW^y6{drb)hdzFq^(*K0`g^%~mR zlz&%Co8;Efh2?J;Px}IO+)-A1oT6eP=Y~6)wV)*{CT4NNseaUkjUqQ)@ zc%Fℜ0blgTGykVPGNP%UzFR;QSYufk4c_G{k^aWWdK(?LppWi(siXfK=DCqEf+x z>Ki_YYF$M2v1(#8tyGOla#tdxRkGaQxWD(23<*{4f{kr2r7)F`X?B`yC+dp=M$>gm}+%IRVPs`>Y1aM>fCaOssT~$AW`k8 zh3dnfSFtrxPuELpeZ3ngr|0>dt9$Nw^3AbT8Ij!G1Vl3RNl7FNzr!M#h{jYtQB|Qx z+Eo=r(!Q#OH7a3$O-s2$EUdsrM7&B^RdExA!1KW>?EDAveDn#4=jKPq{09xPd|s~t zd7fKEv;820$r6JpekFJOXIPrEAWeUY=EsLHO+QStCZbtRqFLUr zq>hiq5hp9N^FK&-X>*C}F_Mv4R9uMSO=T2sDogREvbN^~1`CD@duM-0o$8%qQU7)T zTZ!Pv=iR^K-}sMsfb|wXwq2b*0qxq|OlsGJ&vCo97om1tsf5-$D~atoRY}8oi@%SD z^_E9;GlI=qT52pzOC_lt*zHS7Np1>nCI9?Zp=yHaJyc3%sqjx0fu9u2`{(Yfl8J%e zbM*jX*nLw`3_%{m@Y2oF5(9OHy99R+jH2!y(C5xUP#*3d=vdGSZA^NR{3u2@4_&sS zat%QOS?)bTUq@gRPH)Fol^siFZzNdA-AEw*G6etfnynaaG;)_<6X8?r;TqJBqx=!G z|M!iB033ny#f;~{62*DNi>#%g?S$i7W3|*U~#Adx^KFII1$)f!9yNr zEPpWX$(tN|uIq&~H{)1tCVv39sX%e_%U;Y)SS!PTiS1|Vm z^(%K7Zj$Z}zf-6%M(tddp78$NW`)G*hpT2!cMuD7wiEDN#V z78i!6U*-zRTq&dtIYPhbxPH^``c1{|w|UR0P^(Aoav^tSq_D!HoZzE0|e{ zQt7`srqxp(M9E{?t>3kVaY>CUeL1;5RaPD`debK+*!Tgfk5pyDQ{5|tYQZE79h5kY z_8OmX2A~ZU?#gH}he)suIkfy8+DbuAOI)i=O=oHmTfOc+0sAdKG)^e+!NUbM@hf&( zEEj>$Z_$$!FBUaoCTlQZ`AZ$ibyLXv-u*jrT@wI5=+h7Ui!*msV!%g|xjW>n5~1ID zQor+0{m%Ol#a}7COo1ZGJ@@Qts?k{)>`$@wCz&$$#jKq!gskl=6j|F} zSW?#De!TmPgSs!=<8)noff-pPvrJxsX~g#eB_?fz$+b5SlM#r?)`Uqg&I=WoRDTdq zm{(kJ>g3;z#Q7)q=siEb@o$Dobx7sA+7gw(EttwT*AbO@ zh{|MMkB9?PQM1XF@jDbx!|K4Kz99feT0lTf!;LXGzY2C=9|Lexzu)Fy(u z5k`IQK1A)lFQBe@?S3QWeStY$Pu*uwmw~7c*OE{-`V6B!@Ebz?0Ybf8LcKx}wZ{(L z&XRly{M>LjYUyN92{mLzAM#_N z>wcUl*Sk50Dt{LKM?9;>eT@UppYN4?;F)=rlS@VcR}lFaC(4|2%-G|FKbep@_+#J& zq5e|H+_ndY%$9BxG6(+&LuQXZ#r`z%Pt~qEltq2-BY^spUPApcLH*TLgt`(!efJL$ z^)RTeT;cZrmnOH=+T?PYn@lYI;ziE%$NkL} zOyv)JH7ui&<10jCPxSQC<6zfApSs+>M{FXscnYn44m zE(3c073#+b)Q}JNFj5PjfT4_r1z|8D(H*^zpmTOMxXuD$qw`^qt0ys+?gi%w!)Ph< zG8jMuN=WkYTPl?Zu9D}N_t9E{bes(F#gKLmym#ok^#;7?*f8p6Bc-xtD zCd6IchdK7oJIfjYnfQYr%cCC@%!w??qf72(cV)xPT?I?Er3J>=;gjc%KdISz7?9VLtX9YRr zr5lZY@CM&q>@N5p51k(PUYSj2)OR*8o$5mN;zGJyP1g7aUP(*v#b{`qLvdbwW$?nTfuF=eO066%}~qELM)JuC7Q0*+&GK z0V0zQ)_US>{kLkIC3@j3OpdILDze%Io|Sy(yrPm7^z!RjH5wa!mg|M4k#JEf4cB=! z8vPV#cu`mC4{FHD{;Z&`(=b{Qo)5eG{jJDz7}|e%0zQsfh%@L0*H!l$Mf40N7nmu! zi5}GA^v3*aawYJRkYhD6SXUh6v39xUStQE5-Y%rEh8^IsUU7ToHANBYDhBJteLU8A zZqGKIrHHjRgY~_OJl4(xYYN0_16Z@^J-wQZuySK8s>#pWo_NOPh$-@!ehO7ML}Dve|)e{(La>+IGv&A{F0|*;$mR>QA6)&rMiyeh;sIo`<(-s3V4Nxv-fW=(jTF?jD@ z;_=p!$ywbI~+OIB@EUl&+%AiU3!q%(w)J&@DCnq+e;53fnV;>Sl9o_WBvZ3X9*}{ zUCdzhy~AUj=>@B@T*Dcx17vayz35r421Tqt-=?v4`I*Oh_Qyi%Zf9-zE<9m984VXb zPtdddE2__n%XZ{bFW8caejJ>y4p-ZgA0bTi;cy4Er21*NoTB=8?Rs%z)wNRyu;4YL z=9g=g7kN*5z5)$jHBN5n#8;qQb&tFZlL`Ci9E9=}Xzef{xszRabtung`^rYToG_h! z#wzqPc5()pDxP;2&9Pc70FVY(l&>+boFTu;$hYB4t>#ep2a4}}MDe^EW#N}7f54j| zFT)o=b-+;J2BH7Uc#ePbU= zLm2Z~od#vmd4v500|+UH$iUMJpg52#_Ql0PT(Q65f!?0HZ*ct?jW~-DIU6Ey*0c-a zY*4U&bY5Sbph_Dg2zlu18;x#Pm6iL$@*uWcUCUoCCt3~!LPZCbeL3yEzns?YUG<3PZ|#j395G0gj%ZKwUlX);e4AiTlV4M`Cx2yo3RJeIAY2^66^G#BP_8%> z7l(1hVYoP)D-MUYikcNMGU9o*WuiT9epP7M?X9R~(6E3_u6mW&q}=WTRqQcH!5(3G z2rLhgmxsdgP)0PT2+G1^FpdQrM7RX;6o#0>K^P>6 zS(u82gNTtJk}$+z4kA{QA3eELu(r>)3w~5u{Kf(C_T41KxP^70w$+KTz)=dar^v2B~;sh>|x# zzCsyy94%F}g%t)lr^v5kRQx(t-LKzTO#J#|or+)QET;YXz)EDHZ#X^r^@eZF1 zemxoExxYfyuSa5tMjVb1&KI^EJE1Ck8ABQE#ZdBJ#ypMmIM+#jnKl;tt4M{< z*%XY$!#ssMx&_$CZWkPFqWFP<^h^{17w=ypBXd=+vMf=k>pPT{BO?_@Yfgd zFCh3M^8A8X!OOAd`9!@;9BPi@ywLThd+!B|S=cdvC0B)!T!*Dda!tp1bhF@P2&&;S z&dnZT4meX3fZsuwtspah_fuyC$4Q*0ID+@(JjWLl&w99-A5_TgxdJ~E4-^)Q8!g-o z&X94p5z4>Smvb>wvL1Jb5l#>Xi;fZF#mraCCF)zx42OZ*{LU+>EqyMnwkfY5g``_O zsx55^f=XGU8XY@fi2fV|6&;_!5LOO?8XVWb5Q8`fYHl*kRiaK#uL0ENkQXg4F^>8d~|P@!fjT@wUF%GE#~LZC=xLnUKyD3qye zsALRo52Y#_av6iepjOTa<|rC+ETSkBEy5J4OS6h}j$j z6^a$l)T=3!INeoNXz(pTiYUE;%4wULQ z?!w9f6)7zEAT1%uI7^W2iBIT|RQ@;LOLChJW8mnew9A!Bi22C;IlU%`$XTM3uh;2uO7%@M$)Q2aylpmbn(t2Eg zV8RKXE)P*0nuKa`;H^1A!(U!aJ8+Mqi~}#sDV;Gz1aWNMfnj+)+I>@4hYwR2AB51Z z%Z)P(>VEa|bYXI#HaC3xgpz*^n@;=J<9W#19!v1(U%qn?RJl2-{xt;S2}tGq>mg>3 zA5IfgTC|q7+meBI_JB`8BY18wT zRPmig3}Or)XjdFpGK4czX+x;yLJHKGJQ_kVhPvlcHH0G=;t~fz1=@8OVr8akpnVZT z_{>%fv}O$PwoNtAM$AGGx1LuGw5u`18je$HP)KAlmg6)=Qz*`!BG^SgCi&6kpkTg3 zr~G$9F}n~G!}8WvON6#e7Ua>5Nxr;(fbo;=lm8+GB`l4nkXEsQN^4 zzstL{yLaC!xciM4{#%J5;`0B?%+Rq3f)L+*hmH}G_AoJG&4m9@W(dZK_6l+05v7LS z$R?rXqK*qKOFmZ$E%t0Wv{ask`}bInq2(_O^~W^T(6VSWcfZxY6l$3akC-v|t0;~=zc z7>dpi3~kc}+IKACHoWE;|9HoSu_*UzExCS;-N_fjqgL7kdmEu5kI~@lSCLoI* zFu-FR=>GzOGQOZ12kK*p!yE(^2kv7^pK=fnvU8;0IKeo6SVVhL!_NdyYBlbW%?KZ| zyIEipcyF|bHtb8A8N*JsJ<{2mdbje7P1r%Nw)3=(jW(d*Y^2uf*&Yq3C<{S-ovms> z-(!ev*{TM#1w*{aK~NpZ5)6^gK~Nn@CWgr5AgGRH5QZ4dK~Nn@TMS{~AgGR{J%(t* zK~Nn@bqw)12SLrDj*dqVng-d-EJ;G0MSmv+T zuLLOeD}h|U!em)w-Aa&3w-Un5mo(!s>syc6s7m!}Q9;aBRjOx+3Sl;@ioF`%4Lh2_#q>C{ z#(LYGm^79S5lu6YDOVWpF+>zQ5!BCIj*towKVXQH90V02wql4$Sr6aGtR5rSMD#qt zh31Z7T}UuJ-id^AeiN%u&TnIt25AquMe#R_!1Thof+3%^5JN^f*C`nSyP1`)VYjo= zHSFH&sa|@Ow=7=wJzHp#`NEDA+05b5WY%J+iVjtinTa8Ov8$R)B8E6+e|VGGl_E&L z)^c&*bIPNNx({h8HAH4DuL-77oczdRp1O$)PbMZ(p{$CDgeB7^vd)SW_nYUWguzCX#?5w%SxpBmzUM;vlGjM?(xTgM*+39)+0OboF(X?=VDbj#Fyju_+xv#2Hlw z9*Z!47YyH(y09IdWd(~(HVr-BqmG9ij1nNY=%Oqk+GCR}kO!_S-ffebVC9?b!hNp=cX zOy<}&*$Ev~oF&`Y<288<#d|zK5byd`LTK2Qpnk41fM0F}gcCsgL_30<0OB|0MDs%)?)+Ygyd0hLvB@T98~YkyMHaKUhZ#HhC3x!ye~zc4 z-2So1PwJ<7jB+b6R1udSqoUj-3~`BrprYIe3~`i$c#w{;dAwjiU1tdXStI^m+7V7L z3CzyR6|6nU^bdA~WlY{oO3w0ufYN~D`!!g8T>LwC~AqvJkyouBs zElBGUpiw#Bt{tspCIo6%9o= z7bLS3;arerQ~7O_*6R-TjiOB^EeR>|35!RQ>4l-%g{zuOQw-rw;Y{WsHY?Q~CCIt| zV!<#fj#BE|QNoim0#Y*%iR0lEKL2^~eD5NXn#+e2fy&1}uXdn3A5t#ST?Xw&VltCG zs;|mOT74gyk=Sc_-pe@~L)|xX>U)@TNFxN9BrX+{V;@nH!2Xa*@evugv_uHjzYi~Q zuvS;cYs0mAlzlj@lDY{<*pvErRLLC-wdXlik2-}R^c_pjqYe!dWLCUZP}--%N@2A8 zkh*Lm@|7&`)wKSwQX6Kf>o_ac+hM?s*-9%X;jy&FpD-a!%`tn_csUGpHlg&K@Ux+U z+;&c&S3iDVBWU-Pp^EWxvluVA)7CT2JY>9t3mc6^wn8S*3mcW*CDC(E@MaV~TiqR^ zxV8Zy&YXavUUl_9lgs z0|k;FzeSU5KUX81s?G?rvZ)Fo1EIzXUU?oFDadAcyiFv3Q(D(ugOOzFY^ytiH9 ziUDpt*xRP}gaITzj8t3qjTk^jf|etY2OB-_ovnbO4Bb^D!B4{xL}hM!IW_CtH4H)A z8p+K%C5ROm;wlG0rC%pvh_f67m3~dfd>r5)sPtbDgDUdc#yQuWuu_r)naDwD`Tl=?nNtq$X=?0$nGCv zZm_Yj^fKWp_NX4EGmKiY1^tY)p-dfw)YsheR^;IrszPk(O;kSbBS^2=tAesW=~J?s zD<4vUQ6dYgmkA1N)2GB&RH`dvWN&X5^>yw|E2A$K^BT|lZkuDMmxic%tq+EnGDOvD zFAYQxV>t+_+uV&I25=Bmx49BS^xz<;plpS8jD`Ytr%IG)N$hlbb*zsBAAe?BC z5?LUCvYm`~H}=)#{8$Ef zX}Mlpj#W4)4?&J!13-+Q(zUy;l_k|d%GKpab%=6xc~Tv!Tc^gBdM$yqIxgYYYo&7vUqX-POs-J2!YKS3ba1c~9 zDtr!)LpX>B8AGg#6s$)7iqNeriu{+ZF8iH@ei&pj*ZlEV_Z}uZ6@J; zk@D&_RxlIwjMKM0)HbGH%~i@Yt?NP4JlqF&8!;YDbBz(t$&9L|IUVB(3{f@B(HNp% z6Yr+!A=gtwRP6syAd}i0`9Syri?k4CgcRFp)Jzo~*3*D3gdx)Yrw^R)5p^AaMJ(mE%rC|@d2;w6)Xu~wx z2v3a_Gl(IeRp*{%jgUJzBoF@hfiOK_HOW>;@3VhhuhjP$v#w2{{7 zg$yIKg-0X3)e}K=?Nxdc&4XRU`+FDAW>hSO#kabY#Ec%&2*N~mo?1W~!Ep;?1l78f z#0b=tm;DS;-cM>%(4o&TJ!=$FzyX6tHT8}}P;VPlW66^k&s+|IiY2}nVj>4Y#gZ$S ziiLxqrl9*U#9$7Bnu4zCfgmo_SDk{M!8jUnIH)P;C=B7xK~PiBE*PSaGh1ro?Bf{X zG6ykQg1FrsK^)^CsExBvVH_WG5H?L2WLqck{R1Ie)oNrX*6_uhpsE=cr5LJq@3vA5 z<#Wi)9$5Z%lprpLEV38EWs#W(AFCMQM=M5nZ#!m1is4!k)K$U+5iiJ*W~^XL|3^pV z>1rum6)x_9RoEL_Pib$gkhu=$=>mR36z89LeOoefPl-UN(xzEZ-d=uANPW{RB+Txb z<)*Jm`i9W>fxwGvRz+u2eTv65BJZ1`QeQjswG8|9o(MeSwzI;)3h3dW87IKUvhd?A z)$)Nr!S38Su;cJK29f0Z^!0E!!7Cp23hOE23}kI1%{P3x5m8-@d4TzQJ{h9YKWnmtbf-VlT4t6ext&V9YXO zS4Ly^;^zy|tC@LSx7ix$+_mB81*#14cWu9pYJ2KH$UlBH+I>Btu=rqPa0nDaUSx2L zD>B#*{2c`T`h&l*;BNrY(*;0p`tWEB=~ zX^B9uz*lpZqM_n`+;_Q042Iv(+g-bc13-v#M|JSePAGPF7YICKm$L?h?FL8M(E1L# zPo@;YC+$*3Jim8fam_VWXm5MTV^as+i(L@#%m|MUlmm}AZg72Rgb!IAfsbU3hTU=s z?4WJ1tIjgygE>Oq8HeygSo)~1jQOC^eP`T8PhCapqdqqRh`_!xe#P%>_ti)3Hs%is z4(L1Mr-;u;$zfyuNDx&3%obAewb2z2TzuRx<2z?HLmrY^IrtZZj2^xKyR#zrQZK?) z0X{3{3qA)%fN&4r4uf9;B3$Kz;cH?L${+OuBSBOF{Dwi!!(kf?M)VFid_Zk8RPuVVy)3XgRCWpJG}x^5d?*O4?Liob#ys1HR6B6Im* z@+jL`1AGenDt^CA%X4ZoZtHX3L@}vYpZhBMa67Bb)NVR`?gAa`oAf~Lt=cvMJb^E} zqxB$mL<4T}11cIi-I$VvxG9JMKXBUN{-U;URvn5;^&4yF_x7LqS5-sKaq#7YLBAuQ zmZ8iyhMZkx0DQ*zTYL2zE9p1>1(0?LGnBb!aD8KZ;!8uPqKPeYb_+j5(4BP|CvOou zko}8eYfsPuXM~~C9Y=S_Ob@_JW*fw|A~S$yfInddP|OU39Gz950kIc5dm8h50^Qx7 z+yf%-4v^PrgIa2E-PLdO&F}9&^fc+3?s$W$@8Q@{v!LLqC4wwph?QaC^ z;5YDpjM*q)j)s_f1I#-^X&LHOaDNFo7w{wQzgFeGk0E~uqG5nE`T!auLTS|NSY@Q(XYP&kRBL=9uC*f99?x-)8F@}M+_$2 zOr?7Q(k&s9A3#ckO=%IPAPu7@p(x!QDkwulkX)ch4+AlXNsS&MF$VkX``h1pp7*`y z-t&s{+@@XIB)hY4<~2K*ciB=75*+@!a|6ISkU6z_{rWzC8gcUafn6fgQhNGeoaD zaF5kzsU#5oXUQoPxc>T@CHh4zz#FbrUX`gQLF#!AZ~OFJau5zFcfb>j2>qlT)`-;f z6|bo`cef3BSj0WH*#OsuXN(sb3SjOAq9Jtf+4O)Jah4zjp*@RZ!F&LfEOL9moUe-M zyRgwKP8Lkm@A&AJ>%Jw7W4n8f=<4+o3-s zm@JZ4{|Mh*D9a+DRf+Q#-fV2QlKBL1RoSGprK72m_=Y8q8qA~7vkfSA=uzY&fZ2!1 z+1M60=WLgoj;0Z&^xfWIE=2}V`l-nY_Z0~sh>kJyZwld@ltbSZiJl!}H>vx)gs#Ze zy!-j>L+)#m>5p^i8SyHZsW*DtxVxWcYOGPQYIiOQjEQFAkHjsG#p7nGn8&At)BA%Z zQu^OL9%(0=FH4_VEV++bEQnV8CiZBenuqge0e1LtD2+q{v4}ZaQwEe2JSv= z%=)Xz@Ur;{_JNsKKKPh78v?YfS3BU2<{wRb^|;q-88g4|S-&0OGN!^_r6`%f=(}i*E~b0xxvwGMU!d z=wpJ|tW%(Y3i))<@J|kMUKdDhHzY002e;C48E4C4GnlP_zpl?eaD%@1k<2{cllkP) zdkYrPWF@9w)?7Td=qR51%-^m%_lNLZF4^Ue@eb2x*h5ySUbNRI+6l9y$0t7Ndux-^ zlWG2L!$6bPDqbc$1T3|0)xSM(mpvisL4`fF!eD?B0 z_hs{U-;*>qTXc>_b#EmjKDmy4@Rqp?PeM$bJ)DqzxLq{)cv=higP-nFD)tkQDfvVC zmz&X=-fnntK0cf2(03=Rp7M+wuS!fzM3h*K&PC-FZJ2y<3k))(xT8Pe8oAjr-c7Zw z&k))NOby26%PEqc^CJ84d%tR;ih0skRz)#Y;t;x8**pKSMhw|2*U!zTdGYvtJHHbg ztRXPE7Vx8JTQD!f#4|Fo?Q&~+CY|Zj>YMbtq8BShjCIMv28ktn-&lCQtQM*=DvoUU zzheQvSsOLp|B$CKd1lJP7S&T;0IaA*zqrh1C!XjC8(3NlnF8IicAyII`htSm1S!*W<%W*%)-j~Kgcr9bE>@%b;&V0lcvaF>C9jO9Juh7ZQ(_3Utw*XETuWrau82~NXNX3O4 zS6dVYZ?+}V+gM2-I&de57LAl%QymeLd2g};T}#aK5noz?z~8>rK@+Ozxu~RLkH^)Y z^zX5*i>3Z|k@Sr9hhTc|bh@3eZj-RK?7AJklVB>ldn-v&S5@-D)BaMSJp~~>W?yFo zp-dy4Q{g=|`fpp=8_fI*L&>iK5_p*tM{(YSexmulM+}glm zT^}X+8ODTSjmeWhjLD_cq&Gc+0bWrTogdu2sCKRCsqlC9xZ$SEH#Pll5)$YX{s=yu znC&EjiD6B+B$jP`Q%O(R+d~UGdm=Rc7!h)Da3|l%4wpXdX`z?X>5$3#8MTqzKAdT^9k;-1C=uD;X>C0_()dwJBpjP0MiNR)toVmDO1 zpJN0N*^|r=H;!AV%D^bkUg5=ysRg(8UmCepy_v#A)%uhl7*r8-!#-qI757v1WvkA3 z(B#v%zh6h(ekE-?`kh z1YKj6c%tDUs_|zFSJqc0fthicCY?{Y3uCCPGy~4B3F83?h)1Cmo zC#gP|KEATTMC-D9<;p~e-BlfWcFW9K}zQ%f4@i5Bkn1b2)HV z5sX>If3C4DH9v~$P6Ru!U>|-BgL|J84 z->XfGk|$IYWQ9EDz0?sbNm<~KTJrW(hEdB2B^BwxO#5AX{YWNMg3%#JrT^vXXc zpsDZZF*jZmc9~`23WG)j!t?JutRK9DGl}ypbyOd7u*L_Qts5T}9{ALJ3oKw4^K0Ub zGj&17f9xWf%9(#IRcC%^6Na=lu2Z^T-8dX_g`ObA0M4}T@EG!&G4nAMm%0$z(O7D` z<{4<3$?W!qC$9D?qaN#RIn6W`sUSjDev-P<+633uOH=5#X(wkw`QWtI>tG$_e z0sJlMn%_WIxSZdiA#P1)cBA`3fTN0vbLo~wn7Nt`#7z}tSM|- zG84Rj4ChtpV6BJq6DHF@IKZ zRMuQjV5=6rZr7l=wc;bxv&H~OYGQ{y$CbkDA&jfG%;H6N3dVE!tDe_;YDt7&g_L7; zA72WI%QRo$NkBefAPkin^IlM>US0_0de4CLMt&_%A64JvY8H=G$p?-<>t(-K-HaN9 znbhihWpJ~5U-oxxM|dr6I4V!P?!sU8hZ(Mz>^m_S;cC#XJs1vaL29+KFW) zn{?M(#KHb=Wv_cH1vdJ$AMoSb{j^VZjPVnE9xh#6|AY^;UghTUoQIe<%EjA`+sVi< za16pr0^VcX#s%AhGe2^TGO2SVW*d~LkDDspK;1H-<^|Y2DuqN2vwcqfYs+>wZ)-aH zz^t3kJ-l@Iii=A?$E{rDweSa*AMg9IH0f{v%h4CoqOB8GJg*e_z5GCQfFd^8^2yLso23RfE1e53u{e_%N%=E8Y(xX6OVSw* zp5%LF)|#|#?CbEG?#Up(e6`R=6NWRp>`PqT>F>E?ANI0^FGe8U$v6I$RkE3MuWK&Xsey5Xf;CBxTu_8^lKD6#87o!!!2sB4scIAujhRY3I%p zMvfE>@hACE+rPy?q&g?ru#$p)$>K_n$PE$)F)OvAk}h7$cJpFmSsxj=jAM4AU-4et zb+|@Zxj6RmR{NmHv9PAX$5KCA&8D)~!d%W=pGN$&>liK#0ncsKe-1t5|6J~1Z&PGy z>`*>l$o_4R0ja}p_tYn`UMvcraoO#zoh`qq(H(P_%x(l}_EK}3Ng4gLPvni8+AoT? zoS(tjZ%3GBeqX1%2_Nxe6w|4=(UBqG^y;d65RX%ar;r&_ngZZffk3D^7VxTl@g;M_ z{l+i3TnTH=ul_qp61VAxdFKD9_7YljdiA^%$9Few{c2P<`#tf1!u&uhDaS;oXJ*j_ z*O4mq*rvQ}(ZAy_{JHyeKl=#UIBr=rGL3NyR6sy179pCu%WG?N@x9+GuTz*^FR9vf zNHtv>B8*(&FJ^&zTpu;@54s`v%e-Ie?N!ru+6)!s)}#M})cj>GotruYu$QO$H>wC9 zE&A!>oL=5MlP(Q><*dDQl`CgZOyaRl)E%K`T>@~W*zOj)8(wh!WcZpIfH64jfy+;$ z5?v{<;*^fl;RtBI-#PGjkEN@WSJbZDpehfH~Khlo#Nsd259}L4^Tq=~RFDAS&qo^FK zJ4w}p8s64B^XA<8D~7PAk8&Ijr>~rNJfBwb%njP1KaIx(vZN;sU8u*qCXLp%w)BklRb+}l`m9*2WIDQ~ z%8OiHGC$WU$xh)5+6^xKws(o;q?}(ZYv7mCT5@bPzT~;z`&&Hlimyv!aOjTZTEul` z-GUYY9WY#@>7&K^TiBSaQ=HImUPoIlq^bA>F=H0ho9koFw=vkI)cTgbHd*{_$Cr24 zYIy9_S|S=mv|l?bScKgX5C$h#K(g`zuPpvdF<>0KwCJdm`sgK3?G4(;(lpXqa!-9_ zW@mlg9u~=Kp=~}WFFatG<`mF8R*a~u7qBZL?)WG(6h9-fJKHA9SFC|wC^%-P4Z1cK zi~Y5!dQH!(Cz@dt{_^Tnu20fgJC9~R=jY3tD^l&VA$Iu|N6yMZmm8Ds2#ZIcEnT($ zlTiDfzxm8n9yM6Z>1`D!YxnZ$*sGH}A5hh-{AQr6239L}Wv+6a#CriYWzRj^O&&)S z7i8w~vzx2>WcfPMwKM2JLe-bQ#Bt^Xd8X^&l-iAvub6eeDLcH#&0qkLcor_Z=#JGr zw|o4`_Bo4~&CA@6x_ruoPk5YbO8<&SO}sI7!H88GFf!6q1?%-ePv%Yk`*Ah=Hi^m; z!QC?^{7oKzO(x5N3ksFfc=BfpGyfCLix=#6=w{(8g^@Ya#k)<*pQOCWRzYs$C|zdA z;H#qFPY~!)ku;Q?ElC1IM+tL9#E^`B#sud$XWdWCAE*<64f_PY8%bdZsaMqvlao!ws1H;q`TYWBi=W&N>|sVkq3EF zEz6+`f;+#>@hXSatB&nlk_dXq;E{@h*`nysW{$6TkNpTlF*%8`&je!}I{TxFk~rp| z)VhqI`i!CVLwI4?b!Do1`-%LX}yek=LLt$Ix5Px9dkk8EGNU@ zzHzR`y2D_4J4^rEj0PR0z(Qy3{b492)wbt@WXzqLM2!*k=sth^6y7(DY@RRn%zhSPMz{= z6(Dy_9UOgL%X_|#{I`yMN{^%`Q1uEGvQaZ>iqn#2!qw4hc2}n5G0zWl3Vz;94+|KS z%^pmB*DIxZN&YpY|L)R=f6uO|+Q{h_c({j1!`usHRy$oSWhJu|tJl)AOH%4ODmmE+ z*^$4C>k|r-7z@jwc5RKd`q~czURK)p@Q%AXJo5=p?dOPc4#RP!#dyqEJ+8Im&JnZI zd0>Nku$70A_Lw3edd0`gNV!5bagQV9QUJ=GWvRdqfL^&Fdgsd0*Pg0Ma=z%@$XCRi z5n-=OKS_wqzF0SBU(wD!GshdIHyjnz6r2^56cijCHEe7gZJafnoz0wSKMhA`M%_DlU`XzDiYlVl^g__hvdAWt@7q>4dvIq#w*WV{uf1Nb>NK8fk<+8|Rct*Ed~LaW zwQM+RWx}RyeB(gJsc9%MhwtY#8TUme%5Rn4&52ig)6<_O30S;}Z*x@)7`Fe({dZi* zJz4d_+a8|f9(E^(*XFF;|8jfR{ahOP>;E&*uCsSbdi81`IeDoeBB8rXVN@=3@=d>F zlhW(--EoxnN=L@nPvzn*i?%J6&mP3U!Mp7MPe|ynX-(Cv-GBey?mC6!Uk{in|FF(b z-(o*~mi^g&xb~s_i*`lV!Mr8~r>ZYeV^+iKW!Cvtkp^G38G?wd+Z3P3tv;rTfy3z+ z0Y7|Je?%8(~16hJR^=1e`9(7$nf$&+SYN0_*{qh+hvAP{3Kz_pV%}V z{98}i#pruV3U(}z?_${Uyu2OJZ0e+A@EJJ!M(oxalS#4FH0Hh6xloOd)rA{ zI@%-oqgtM!p0Y`T8L#YkMUlRG!b=SgN7dMZtp=K%UNJjyy(Qx;__6x@E2h@xzKl+p zT4d>=%7|xlzCz?fj@JUxEX$6j@ju)|WaM|gDr8CCySVWKn)wm$s6>p*@Cwx8S3pdg@9`bu1Tu~F8(q(sj4+0y>p5+A?y%@^vKd-nI=JIL4v9v(C- zS=}gpdohH~<%GE-a4&DDV8Bz5n%6dyC^k`3Es^Z{HaY-`VSx z3S95?`QY)*He0H=KzfDrTZQu#l8`}b-JGHz8%|6W>Hwyy5cb?OZD%k$l zS+FP8 z^TVxI{*z2)Pb#+>?PI~Us@!Ac@0~q@sjEck;hd2nXkevh3ddSZr0s?v-@&u9dCZYj z{OJTybTT}Drl?Kdb&ljdaZj@~R{qDQ=}z|sFOJibAba}wSmlEPD&o0n5BvPBSmoaD zkzT#6`ayAbW0f~k%1j?6?7;61oj*9b@r7iU>-={r$Zm0d3U_Oq8Lp|6qJ!d+NF66> z9Q4}f8Y0h0Kww%W_v;n68SzGAa4~Z%h9OE&Da8cEa+vaDZ&kJC)-x${WA9@oVEPn< z$1ar(u3(eG1_4Z@nL~tK(7q_HMvPxEm%&a^y?>3-SaJ`k}?zC}eF&iU6Mu;m6suai8h}JJ=24NVw{F!z! zJW&D?NJk?pvQP}p33B0_Ns{nHau_Ve+`i!9#*~9rh~n17{+5*YM_82t83_VHY)0u; zD4ZM?2Y_*xGd3?ck(Ba{qEynZf8oH3ZYL*^Xs-iKk>s+|nxO0y&!?aSPL2gF6fgmB zZ^0YI?Ufz^QvY08eY-{wm`;H3JWXT+jZQ`cxC(*7plV9V%~0mCjOA z5j2ogLy8pQylvqWq8+8Qi?MKH(aVuUkZk2Rr6_r+NaP(k*|?C)ga!UQIj|>N*1|lB z{pn-r_^x~G%WS^oHqv5o2_l3AMF?X*+x~(qim5W?rbA|3e~n;1%N4M3s1O$;Odp8U zgDP!mRm3C>K_#*uH;KVjT9Q<;7E$cooQ3teM2@0~YC~uz)88Zxzg*R0wy~6*$yzUh zZTm3DCMhK6`WJsZcp}f}B9NNI1|Ji{;x4<TMI zy>=e0yl(~8#Hz_5p5WY%!_>BPQg^!^$0R<_3bgEsXIn-B9Ip$G40yRP?<8bKB;Kx) z>9dr6Sx0&9qb>O)A~6HPn83=h@LF+LwU=oU+v@s!zGlq7Szh&zG@@Bdp8_PnW<-90 zSy~&;7W~{8JALsXgxv6<_S6(_^mU;ktCNMx#mvo6?%qH~0+c;Cy#ya7w9jxrQ<{7d zsZ%6vn`Cb+FiKzr4uA@NZU7?7(%Mg(BKqPkGj`GH&#(pM4V& ze>#Xn0Mp8dEEw!kqQ8?+=MFuGB~nz-h} z3-fY&oM(gz2;U)c3c?^`!aJ7yU+hbeHoXg4$_?-X>sQQZQ1z_-N3~I$XgW3rrcv&2 zotoGV6Y!+xfC?z3=s`L4Sw#u8@duq#EuxSMAxHv=E;|j3<~#nG@JsNV!Fpi;#U7zt zJGTS}e0I&3;_tVL$4NqX`n_$UqQ07}i}ur5FZjAKBJ|AkMvY2H0$adHtV2L@8_AUI z%|JWB{{N*H-e{6t$%ab9i zFY&(h99n3%C>mxZS;}kaGmb7qyD_TbEQk@=q^Yqgh!B^&tbQDz5wi`xAmN8q6N)ZB zOsz1i|Fc-)U8DL3unZ2p@%o~E+yT>aI?nL&JW0PL_g{{@mKaFK)f!>qeQYni#Ug10 zt3<}aSi*J8quiL9p+yRNoUX=`veIka_Aj-HnQ(+52>qd>pSEC(U9A`%?1ynUR?ANB>6eb5*%M#Gy?#^=@=kk1x;+E5H=P{L z2a$2}P&taA1+Hm8Rw9af1@qD98CZMOa-II1b@q;Qpr-)=CYg`Oy>c$Q&OMCHMJ4StzFIcaE*_oDA|~e}gMdTwU}mxMrz59g)h^z`ao3NJz>4 z!YM7|9$YDz#O~Dm23oPN1*o0k?nA`-+3yleiWzb+NX2$PlvBEX^K>E? zUT`i_oIrpHaTq1bqg*4uAVKKMLiCCYN^XE7lTNc0b0UX52I>ZoB+NtTy=GG==y3?B zpT&A%cJ70FIQ6{j&#)2@#yJ`b{O87Ej}tkT3{JJh-&gq; z&v=p&O2*IO7iIRC{jtC@y#ww`-y*#OjZ^KJ(9RgxM7*cC4(FY|nH7L=n-%K93JP@*&d$ z^Q)kL%%i$kWH4`g*g$>THTrOLUp>f(^YWj|6CHT4U0>B#rw(!$Vs$AF-ILt*e1CO3q0l>OIZW)fNj`hydg`MenqqGYaJl% z4<)@xPQ`SLWL^m`UekmH0E&Stu9!rUJFtP>mN1S-+d{->50as1kT1xRYuHO?4IOe= z=a0d(Lm0c23+juiReSv|EGH#EL?$F`^l*z1z}z0|7Goz(eMo4Us)yhD6vuC&j{*VH zF%TXLG?TV)BL;gZpCdx}hCW21GR?B)w^K_IA)iBq2C^%#RAd2!@tl1hiX4(F3P#?9 zT&PUL)#Q)p()SVAg;=S{&rx6De^U&gA_qtvIGs|mB#b#b?GeNf<$&Mpf z2ysMW^P84qFO#{IgiQLOL(jAAiOixfRpR^deJiP5rJ|kq$AH6>Hn_G;(jrWN!Xkh% zf-$#9nCp)&p9=g!Ft7R$L9S>A?HG++v_~5C{n~z}8X=gBHKJ#3Yn(I?nFFH%1D; z4}In9S}5r-YMr~0#*{0-kt!7{US3wvDi%J&&bx3`{iP)l5Vp{VVwFg{N#fJz*r+}L zo{AGWdHw#Ahyq&RD%<60;b~vU3zGv;_@~kM5BUI=@w=23*c`G39>TMnxD4T()31ta zdA4l(+zh}RqtgPkn0W}xa4go6NKJVJ2M4E@ z3H(6p;#x<`QgkhDS|ZzO1lQ;2~YuG`i~|a&>t?R1!a3)P%l^jV+ms}XWdwkmNT#% zaJSPZsR*%FB@#$9+?>a1blqiMPCkVRE2Uf|Nd>2N;Fa^@X%J7vLXU$*PLfq|Qf{p3 z3*5zkNi0v4u#3rL!8buq#@T7>FK=%yak#X|NE|cUB>#gj`qk&Dc5xPB3{WhEG*Kkd zeZLxP;yuHZ0$cSDFp6TdNsPp3XbY{Nub@~f6K|pfD$@|gfsz~BBI}$HDM=_TAQFUe zI~KA@;)My=Bs0UWDy2-~M2bP8gj;y!tP=(@HWUV-@LHJu=vd-Ss5FU#e{BCInmtx; zKrq^1qw24gJy`qS5wus{DzF#9zts4H@Hjd;i#L?4#BNlxN zCO}&QG~A5^k?K#7mb+iD)AJbMhk(7c(58m7Y77LNkno#YW{R=1J041_7TF|4{Bd{GW5bIO0Ojm zgwri%EF**vPmB(Y!1y;177MI9g#JxxAe?tBNsq>{Oo|I^Zh%Sj3U&DCe!hB67!a7s z0oC?oyi4GKNZX%}gsgKsMM}e2T9S9L?Iex-h-U-rBW&~O7&wmYmvF@C<0vTCSW*Vq z$sY19mxSNo9r?$;ZvaepvghG4glF!dZHfViL|;f(=0o(4ns<4$?xOV^{xrcQkexPb z&nq#J5)EflO7e$t#u3%m>C;Uo(%Nd}{V$NXa{0~nrw*n1OS zRZxQ2X&4CeImiA&4`)7a0@ucZ&J4{RR;J}H?TRj^8bbKy0Cfb1Vy3E8VL10#iZF_) zpH?YIh!Q9?2?VdY5jh8FP{5sz;~)6C0~N5L`3bH#MgdGS^`hZS&2tK7qk47YK#>SK zr3GI*J3SA|<4LE!pj!;^PL)OpxeT8pzO*jz5#LvZ=^doCVxYw6q~nD%2#r_7hzYa{ z$dORaUV`d~s~O_7jDn65VlP4mM2R$_!7zc~WM)#P-!JY6>?!xrS-m=LM_q)<0NYqw1wrV_u;d3cHkb0yWl^|RAY&o3E9@vWd+ zq1>Ki)svJ-tc4rEVBUwyc~*0eV;4ODV+g+|EfHlfmK2G;to_MGAH}MPtHZY!Cn%JR zC*i^AxFm>x1(r_;&<MG!x`3r^bmkfQzWG9jG*!V<31L(Ny_Ee#oU9^ z$I&pM2qxYOY`C^*sarbU4>tm)b9fF5#<4V_#fq6#v6p8~*#9Q!Ew~@EW5Ry+oz zqoK4Ari$y9jaxxj{4hEwIv2FsF@H;vNr+#V`XAOlYi%$Vs=ah$gvM7=+Khf^zGA?9 zPbLK&+U;P3-wDEk{dzIbC>9Hxz5dvW264Us{y)^7$LbzGlUV4T@tEz}$PIUe>%| zPm`hBcE^rZ-93OUj;f?KxS~x`D^@9rPXIj$6V|6ySAs)F!ct=r=SZqTz*TSi=!F}_ zOzkihrNn5|D(4`h7DksCA-UT-dJZIGCk$x>!CFVyhcIs^yOSVFiA_-9xFwmtNd_@I zld%Wah#Y!nlf%9I2099dL4#jPkxF=Zn!vrzIbS-E2=uuL*V;)L<-<%j9#a9&_q4^%^+DQ^KuEBM6A?qc##3<-dZ z1@jr89@B++KyElq)OKPW-01ES`r?PdaZD>{dng-)#f5e{(mf#g%Y&8W9wLau2TY6n zW2;XRE@s()a&3aI_tRVhGq@K?PCl((MILYvLH4xFQ_oKKp0(CT(XV_Sa0vtNqVGYO zd-Gu1{KLfe-t1e5%XG5DX|sD{y#GrGy_B=Rmu|W@nElb zqmvW^m`1Ot4920DekV4Of~i3sDV`(XxP^q#yn z`XpvI00&fEA824ySf5jEL=j2HvIueBg7F;0J&wcNf=V2$TgIWn5bNM`#y&-zPUJH% zkr4-@(J}zd*Tubp)^}q9q1lg*aR&fxwi{KbR^%LnlLcGG;$RrHWBfNM2d$(nAb@s9 z0jDv#GfZW$Rfmzt+-P<(t_LP32CO5v6*Ey&b?}caoFp4SnK!kH_b>$f7T<>8L7F7E zCJwHUoo0{W>!SC=c)HPnFluhh_w0rzIUa)yMJ(cOZX#+_Z4xnX`Rvq5EYmTMP0A^Z zODV;iW_jp0VB*2)~3JY)oLgvC!Tt@1B1K1~UPgU3?Dkxuq- z{g#x|8s~9+3EDIB>cI3Yn%bs0m0J=`VkAvBXJw-zs8^yrrk&(OJk(iE?<-?jIua|s0*4{mFM3!1UIW-7vnfAu+5g-5?=q9P7+U0Oi#sa;}b3! zA*u}t?f-pSE!rF!!b`w$ZX+PxAp+=DEv=CEfMLr87rklq3*7zjf~r2ydSNqTkYlIP z>_h(@uk>?BtlF+AuT5g!9pP3KE$P{u0>Hr+(k9X9P(v z09q@|DcG$oA$|R@9S2fO~*+$n?Noo8CyxQuV13SeC*S`Qn z`g4f?)m<#;YlfiLhxBvjAzda$!mEyB%roFG(2b49g4-#+@V)jyP>~N0^(gn?V+@7x z?8yOdl&?Snt%+$0+axa)RNkb;P4np%*zvLqD&VBg6iZ86)~B(+a0qQ*b-TWd>}pPg z!xtp}BK3}PTfILcE+ydaffGpDV<}0kPE~kcqjR{zfxehyEhcHMp_lPnt}9kVjxZSH4~Hb-N-^C) zdZ60;rQ0P9-@p`6P3($f_Rju04q)n=9W}0Mz3enCEMF90EE$AJ-rwt!K0z)*ImM_F zC!x@OMM=YB4PdG_&9_nIb}{55C@ZaQeKBGl^BA6oPf{D$^ z3)LoFTR(|B6qJ?I>fM(7o0fxBI=$+)xbld?Z4Z2Y9`R2!=gtux?cd+9=z;PSizo)Q0bsx=&s!wa$<2pvS3=noVBV=&;2hjb%dwqYJ0 z_NP*&Zi`c_mm_!vot`ATHV%D;SemG*^qKvhcJ&?7>&V3D>ou_bg_tzs9AhIX{33pz zIYxmjlT`Vna{8M0oaupC5<-W}l;ovIE$%T6HANU#_k3-mFm;2;9SjC$+m9Ix*w2o( zV+^?aT&kPu5K{QCDQl*fE98(QuZ&7%{Kk|k+kwD=RMNKjM|&P3{hPfWc#HwCugW;b zOq^wok?C$Wp0pf*S9qzKxAGFd6J?oW7-L=<)4J{Ka1#xQIt&J^@lR+2v=<;g zAUe?o7*lUNX#u{7=a^z-iqRo+GV}?|+E#1Tc<=EXAW~zJwvlj;YUIUywgb7;AIveA zP2U*XTSmE5uU@WaJ-Cw^MH|}%Y5WE^3&(*#s%-`=N9kbR(V3z8hn#dyNmDT^o zCiirO)92XwtfpmSnBHu^TgkNf@lRZCDLZ4&m-CrUPuT#(!75XX z3i(EoS9ZTywQKDpH}N0wD^m;yIVx%UQGwJGzA;gH*O&jA%BWL2W|0r@ck#544io*5 z59-IL#MfTrM@HZ?u^4|qA73BKtaX-uy@36{n|nQTuU9VGRyD105}zbTqdPeKr4O3L z!odd$m@c5HkqTZCZ_OMdMiyl-;P0b5W3dI_{YkCeF&VJ3ghjfp5*(sU%eS9@hdo-oTA|kJw$oj+Xi@Z3D?DGXeN!-XUU4(l zs|fddGSdD1{%v5w`KDHEz?yzdLtgeApw9uEkRsi?S(G*JMR^m+zr0$T3y%I%uvoP5 zHR${%m(bSpcjU~gZ%V{}^mN6o6J&;*atzKJUG`UlVgkj3dE%_C9fYZ_){dQ(uUCBE zLa?~VM?+UtRN8L>d?|OUQVhON8x95Kpc5EQ@7y{CepgwQMR`^J|(@BcpmYQ zZM|B1B;n`r6Vms)zc|;w))#7OC;6Z8S33WQPh29#l+K1|6>$u+Cq|^qWe(TN^sO13x3zhb#@rEqebJ zTlrw6#;q2QAK;hcG%jX;_m=M4~(Gpzg~Hhe7^hz zT2b|7cO!;2Bf;;g!*3EszLWnuL>yQ2{*Fp`J-976lWx;MD<0Mq+(murKv{`JY( zXy2H1Erl9rs|6h3I%=~Q9LE{Wj8Tu{v*sQDGZMO|PL0f~5`SO1VfOFApx^n>P7&aj zYN$a%sNqad;SMZ(m-AuZ24b5s4+gF6Oi^ge_mH*-M@0YN9Nan6VURmYylUsgqPlo& z)A{R_DzUACR|eqIA-m1xnRe5`hz82ckp5<`c)>ra?pq^cef!V5Oi)!Z{CU@7tg3JI zZrAls=Q4+OwdZ?v9Ro{$kKzKFPok$N`agbwMs`pA9ZpYQvQP>^Ay-2$eAN1_3yN;L z(uyz}m}9C&>_2|+;W8=~sRMR>s6Tvcpl>FY^OpLtQq61tEN4bT&t;<3wS(1kkAvb% z$!Dr$p``7JK|Ryz(;5&z@e;9ySjn|!Q3C)?ZR2*qu%FcGd0862?C|6(3Z8NOZ@YG~ zQxvCSxu)g?ztu%@6O|2?{Kqz1JECgeMI7!G){hc}--hT_uDo27XFcWntC+`h7TNPF zcjVmisii~cFi|jlrm49FK@>!?ZVyaNvi+4er($X(mME@u2))6m`LC%4q!^U9Fu@PlGAxsd`6FBRN&`ka)za zey{W3n*Q6O7vhkCr+mLDZBx!`HI^E(<^4{Sl$3D>xAK)gFt<4A6;_L3lD!KgV@Z2n!5gd-#h|&1?R-)hz zRU+12FkVOR{N8Qw`tGUP+p|Zb71ZB4H+r8c92w2(fvyRev!IQJeQoE}OSC3~~8q_ggs3!wEcrCrc&=L=>NVUVk zVwZiDc!Sp+TOgC;3hWL_4t(qN$rFrfL~!#-89!>4_s)aYD=z=V`L`@94DQT7 zNd5;p+j($*)yjJ``mI2p^*gUn(}ABRuD_~M_T#7io>&JWHcxA}Rz-cONUsa`^;-vF z1Ae`R^Dl|6IX_x||0EXGF&}vo1^-9HCjn==?pfz`=0k|C1`5&Ja$~4YPrOm&jI%>* z`XsnaSAr2WlsDh@_V#S&yW1$mMR4qP6lNcM;6Jpnn&y0vy-VKr(j@=dP2Q_(Vmo+v z&_HAMcuZ8hP8r@mM{rVaizv#+D@RHI@M$!kzjZ85Yc(#%YxhDzUPI*rn^se ztm;sRjQCeqDNH^4pu>%(cLNg3e~-TMEym^FIjRT>ZvA*(pLE-u+7?>zE5VQwQrF#> zG1R9P>{Dl{;QOEM`kp)n`hb`FcbN{Sc0VoKEKxNFJH6+B9@`ef;wn{Fs3CR%MwK3v z30a{ZmK=w3>QA-|`?OcbG(wIpY^p*L!&j=BzeQ^_7E#_bK+mu3sEwYN*U`vF+^=IE zl)0;?H`-O%+%~V-^PQ67VN|2}&nd(;fzn^1+;8tRGGBIPT>IcG6TPJ7iuvKtmb))w|>S3>@{!yD9{t~Js+C8-+y-Qu0yTM zsc4;dU9Qb3Rqg-ba!8#qDrp^GQ(?*QmMk;R)`0%yeCTzy&(VZOJcK_h;K4|kA=jO;6;P9sP=26ZF8CjWrUg!aS ze*fR@+d~Q*3rl71;5k+tWc4VcGt#MeM~6 zpUhATrOx{6eu>!2B;-{FT}8b9#QR%d@-!%cW9Qj`sQI7sk+Xqvu0w(UbiLib24X{h zef_jWc+@1-mwkqKy4EtaJ%E~j*ZcjCefZ>0oUX(jQcrB!gPz7aRUM{?`GO}BiZpdR z^yb1Qb;^?cz~g|#Pkz+M7LU$6+^LDvzBrdZeclDasorVW{cwfrbhm&^X{a6ObcCOq zAR-&i|F}F(2x#c{8kyo?-u+R#;~_6+M7Ab2zl(5RRQrsJeKZ8gF)nJ5m`nL}sBRc9 zz1CXrY2y~_A)(o0yxA8MW28R$(1c zp~|8Vv4M@-x#7bZv!K{P^}i1lR*g1KtJ+%q8>n?Vra5m9sj{17qr=w9sZM)Mviw1; z*{T?5WB-*SC1`s6)aq(XfpMsDt6Q`EJVR-oUp`p4+Wt)>A$fQfh4MARQL z(~U2o$;ZifgjZAR5bMFOM_X0%dArsL)8_`9zSOM#P50F!=*T&E;Q3@nDKc|K+GwNK zIcxrp&2CKXCKWZ`-~V0zbsdj=>Dj4yjkxmJ?;VL>kkNC)+tF0|_!yVd4P&tc>&nop zI}t6b|9qT;3Kj@yffoFaldR4bB_Zj~dLOj2=Jute zxlwXe>&E#OzYe%|m7?WwYOav*MD<;%z21Z<^80R4)>(nrS;BjvvnH{{quf_*IWsc- zIS$iCo3ZZ$SJh6#CPRAmRr&uBvpyZ^Uz*tmZqm}_tV-~=y{!1aYgL_SD&Vx~f0Af6 z?AAE%>%;e_G7Z$b;6lYHDt~;n@lh>oXYzZiKRq#WMZ_p416^6TLD5P7Lfz%~6Ea{Z zzYp#@sbb1wSR>4eN?-TuLH1R^nJ+#kMSy zVjOa(w>91jT-@B*;nSy@BmA493~!F)ob{UY-Z&i{xRgI1G$-xx!^M3kDQoxlhvu`@ z`yI!x|GaKR${hF-`Dt45Rrg$qX1|7fw2^a^`;{;_`o z{pOv~;oc9X9iv*X2^i)yulO!;gq(Axe|Yt9@lld*UogfnD96yN6)CmWW?=UVe5xLM zaCU_u{zL+^?|tyifP7}BKvp=gd>8&};*!;<($Iwt%JKdAI&fI)BVwTb4->cF=Y-jWezEg~P)Anz2JA8z9YwPCMwQRpfM90-(#!3(L3agOY zO*!sE_zG>o*z@-S41EQH9TdywExs;TXI)wl=ko&~GCo63h)aUc)3gr6+<8lh(mBCi zJOhLDn-XppLYx;0AUFT;^xa=V|LvZq742q+joJ9?)3K6mU%Tgj?2qn~sa%u_&=-HW z0uf?AR-j*4g?b@1s}+1OJR6^wD|CwO>W{I86Jzh_8N-O1gup|j;+a2-=lfYLU9N$Q zxK;M6ACDI}NN$lgcl)3TR&|syFGZzo2JJ7UlybvAYqmPBLL_7uY3{3Yg8PUFs*xaV z$tj(<%oCmy$E5Yoz)azT0! zvJ!)^&1@U?PdPd=Z*U7E#W`a&X~?*PF^u@iDw@~&d#yaGb_0I4bPZl0hUp|P^+f2f zc#NbTdjonvzW@;ut`)cbG!uUEr#UDN zp7Ytll@1|w_6x8<)Jt)muX(|laCfb|#82!8th@a@g(RlA7!t(#q}I+;xSmWP=~y15 z4}GDdhKuJjLh9CZLJH@@{>HBEChO?{J}63C$aeIqM~Y4(AlDHc`Kxyevf9{_1VM<& zCxU}CCCeYLgkdx4_~sg>k8i*Y)gU>axWpp~+pY9CN{ideX-*uE8EU|qoo79MWJE@| zW6;#-^vm!TE>`6EETbm`7Hv;rz)S4FN4Nu`w3S=BO@zemtw)CRbWz$COp(#bc$OZ7%CP`7LR9wV2i>ETM+tLg(L##R~MY}3~u<-0+@dW zm(9or;KmzFFcm7mXTWk!!STV%%g@?!V{sV$K_0QNVM)0Z(~9E!&CE!9;CR9}E2>NJCD#I!LmR%=&QdkpzFK z$?S_3i|)4M8dwOhq~j2>tXFBQD0vBkg}ZFj)4mLZ3Ei=L+>GkUe}jQ!C+ZEQ;7EfUeBiXZZ$sb2LOQfiQk?ek=ToEKnT31!=+-v!eSz zRlKi7wOc3$IA>XaGFpH*k8{J>iG-ttXbtKS3GK~B_vr@<7j%rig zZ1tda>D-`atjTOE&M3^?VUhz*<{jb)Dk{%LWrhiSUwU`$)55Op>Ls1U>er9 zv>&-t+JS`(zPO@11y~#Y6kT)@Vzs$!t*229$dh#BFOWEHkG}#6k=}_2Y{8WF1crJ? zo;1?IT;jP`g6IZgdM~;O4Yx2toLh(`jwjzH@f$qOLV!LWH z)|?x%$-`c=j9yI&f$j6c>iJBe^Q$djj~?1}uipxgMO>j<{%^Z^oikQm1EnR`v_2?g zP1c$}Mqjhk{nsV;Y?|w)u+ZbV*p-`Pz|>Pf0bOl?9@j8WjN1|U_sKh^D0^L(8|{Mh zyitsC4lqJAu0vV?1FO{gF!Dbxe^+nQQVo)&Qy|)WoebE5^NgIWg$>A8fa~yXn^q>d zionH;%Aia<_-VT|T0D_JT3Mzsw2eqhG#2gj9$02t_|Y0vIUZF$JcucmoB}@Kg@zQ( zAud52_a^CPz|OWCLVvLT@jb4L4Q9Ky4NzQxuoxuBi*@yx+JPYZ?N;h*3O87dUOpgA zxOvY<)L;^hQT~DDX44xjCt)kM*0^>$AYu}Rk-smd7@CYdYiM6lj#=*U5@WrB=1c)z zfV-ILk!g0zHtarsz8bJwW;suDBRn@alCbG0F1%D4Cc!#37!5)35pdO@sBjXV(0`6MAw8K@4dUHwK|1$=R4lbTF3k`Hy`WJ> zH{jG_u+RX4^cp;dN_)3i0B*W71q!PHRIe3Faq%K7cE1(X`f$@1US6~NLrv@rHhLlK zM1czN0G^VnaWDp~;$E@9+Y3`>UA71MYey}l;0Vt@*t3^O82A6#J81E3-+aVr+}5Dd zK1zB%BG?`>P~OvzS-U%h$f_m`7NVUQ!l{3nEm3WtCcCs*HW-Hv7-}PADuQQ1fw*<> zhMg&<$dVvBGziN|!h)D%l3MkEf!4e2L%i}4ePazW;Xw`~Ys326xhKHOeE~z~VK7#n zMILdIfl{^xggZUMEH{|WRJ(oAYAlNkD*MlMs+CdOCZB=oy7`Ki!lw(tZH1^rX&gf& z94|FmZynDC@na8i&vKrOW zeJ>Tleq!S|*)s^)#oZex_uNw+=ObUXRuBV7aR2)b5pTWmiCBQSh_hBGTbms-93g>U zPk>;P=efEox-Sg`S;d>*GS}bw0-Q%dg8jOn{|KGGD;EvQP@Rm+pBtEFbBvh<`n>fI z@En0Y-`_*Vzvo*y%ikV9F_fBKbXFu{|M(kTNDA`Ff{MdInUi*SUo}|M(_UGX2{Iul z3@~uvsfMNL+?0aGy2ZZS9*NMXcT0K5S#R#RJ0nkZn_6^!1%)fzY+fkoK{vRu;p;Wo zX=si5$v~s+HcX<_*+8#C{5u62BS}9{xk(nPLtSA57a1x zYu2-wulzaZ?9u{Z53WWUMYus5-f<&GY#^dX@^} zF*&E&VyS3>Xhqh8@Sd0)f9e4$eZAjpV=^|`5&=MLQm=RxkRiKC*aOr{RA1Tf1*TTC zbaV_cKb)hg`z&LH&qqdv7qFKcR_CiEs0iw{xS9V9uYYRpt_frE|~!TtP5(&r##EOj)1#&7dM!|6G zaP_58@+oRo)6)FQtK=CQC^CfP(fo47WE}8DrA+$~(vjTiQ>_kEErDoFmM6dQT<}j+ zfc{p9mI9yB5(Tq^`-mu`1!j3n$q(dWZZ zik5j=nZJ7Owez;D_I)M(I>l*gE#+=|-|r*1jI=>@;r)SE>SNIEFQ7OT`A*{NcVk*p z`6%a=LHxA$(Dd2*Pmr+Jz1wWN`yo}Auh~TbBJ4vA(%7xbV`+gBs{qb-rVM4LHds6} z!E5qJf)XaFlw0|X|E)|derztBJSngOJme*=Z?|3j{*W1i+s7|5*#opKhlA0@lSwOD z66{k7bobR#+ts2mLZ~%o)+nRyqi=q4;593hTO&zApm|+`R0>Nn%!fB!&=X2Fntu$s>&RI)0I*l{Cx+6r#PrlVN-Cvrk3^hQ(a%g3@{5%=c;Yse2 zd&+&@)=hU@eG5xw{Ae7D^g+psbf%Ntu5D}_3UCv(u0gGo|F#$t*Pr@4J`qquh|cKc ze7lcYp1U$QIuj2&NALZZbMaL}J^Pn$Tcfosh93_>Jjs9qQVG0Kk8Y-IHxFsl)W=EW z<~7?i5t&TUSPSWMwI0gCqK=0HzMWW zVqVM$`hRf&C~Of(kP637CHBi_JvZVzB4Yd9^!$|FI55wcYV9Wei(a*1EE5}#Qwi%4 za6{*H#BnQ!RoEKaOgSCl*23L_tHF``h1W4S!Tn2HnMk>$b@^z!O@%Bj1U|#a`em5^ zxbdMnPqGh1yHZL%q}`qIpcWurZ8`z*C3{?CBRGrtV_!X=y&4r+;0a=^MPE%?jsspO z*(o^YLN_@jF&kg2#-mK~-fVl?odCwB*(V5WrgCawaW(r@AHjc(=(Vk~iVng`QM-67 z`gL>E42H|X{y^`>h>nC#3=E^k>CK^>T#=9&+rBM?&UB(p2e!DM8R2_oX0sX&-!Q>O z-^PGMjG$Z9TU#0Y^$x!OA6Ng=Dni2}l4pUG`-HVP&GP$Sy#87oLWSkCm%29ejQ4XF zm)8Xo-l{qUhfvx3qJ>xn{Fr?!`qZ>3btXQ1co4QS>`fxyv#JR?`mEfXaOwLK z!-pNbhEs%f#nflJ*qh$evN%?B zg(<}F2A?m0=-#LX%phtv5J4;Tj;U7rKV};;oLLZffk_Tqkqg2QOlS+&%m5Wf959GY zIKUqggxGdQ0Yat>jdZ2@8#URGP{Myhd^?|5@uO1+*)l|~SUk_Nq^+$0r({ec5>2V| z@u5Wp7={0vZ?M}aN@);!XarTfJh)~2W2@y05M`CxboeOWqMa7tdo77Ztnf29Kv)9R z@Qw{$Wr$Z<63A;a^S|EDgDQd&jj6Yc;C+MOLqlwQK2)kO2qjM?2vEKihuTVX%_s6Y zPr)Dui~vtDyJM_OBl&6K&7*?>gUK_D-Y!G)8|$%Ddr}ISot|$Oj^i8QF!`oIwxP|~ z>uS9=@;pytt9_Q<6D9^9M3RX0LevtA2`D~|!j^{kqB0yt5uB=%-f9Fz7FZk>cTPMs_YpTTtDJ|F)cy`bz`|}7#s~~tY zzv$dTB>u}6r8OksYjFEk5Y=XG0v2}v4@cTyD?(g>0aEC$?Na#aaGZ<%Y!2cUOx5@&| zk1>*@?emE4>*&Nh=TCZY_~rfF`8gKssv1i;4)OUoW}Yp~bk*IEaFs z8RXd9KOTsU;yW+APPvV5mJFQ)e`*x60h3Zc8#y8b(`g+#zx3(|i?jbHSO9rxz)pb9 zJ4>aVOytc9&2O3L()aR9A78imJ`2%%*T+0j8pQCY9yf|TL_*|AmNRiMTLSllrK{G^ zx7gid{T2sCA=I;HY1qr&|3v;>gBhj{f@KN|rU1xUM0ekqXA&$|JVujlz!wL2$}PUu z&I>w36pCAKFz0bEv~MA(7sGu~1zeIPIRDII}Z++dqfxX<+_vS1`4CH9BVIs}zQ zc#9{VHRav$ND49Zu0|#wA-knhkT7K0Mq?;PJbA6?e_zQ={~C;zJwJGe=sD$C#8odv zS^m$Z7};ZRklSY|Kh3z>{V6i{Um<4r)5 z3-HYp=j-BAhxa{4mW0TWKPTw$M4kijwA5wS6 zZ;NR4k#i5M130!V9#!iDDXX^bP z5x%cv-ZIM*g!#9$!T#b<$4I3ldkTxDAp@)UjuC?ue#gkd(!XPlb1xeiX5kWrIMZ5# z3zLMn#0Dm2gN1k!39zu6N5Ppa3P_bu)!$(gK4xPk^}BTY5KP ziFqjmlc{)vbdF*9gbJCd{FQnW^}7PAPTjg8#0>uNbeo-52I7`1C5;464^($TnolQ#EUOog7R;h^AXG@LKM&8zJZ{uG5$Q01@lc zIE!v$M%J3#Zgf!d;tElBQ6*|=yW;E#{= zk=%K!c>ZyoFW^?6_^0JLT)-eiEWJRO>k+rki$B+@>2`_FSN-OR^2s;5 z^9L3o)blUnnW6@kpL`3j*N^d!!eV)8B++VhGG|Pt8n0c`OuXjMpkvFAB_cnZ9Ru=2 zb9|e2i>?M40e5oS3|c+!#{L>MM0;oU z2)Yf6cJLDa=hf84Z~w(%EU-@OL>k+ro(!|>F}!TK!2!W4u7b@$nT2-6M-60Q%|5zI z%Z(Mg*@9ze@m7V1c2`4m#<`_rCSOMwIkue*keZ1r4Af;3`j?g`)}clV`#(Y}OJM0( zK5%*iYykoDdkk@;Yxo(jGnU($q<4tA#-S7Xg*$@&>}lso8hFR?U>~)qd8CGy1L#y5 z=i^JlYyvA9YeB}`fbgsapC11T@m!{;gZ~g4_HjYslLVT>1nMvq{hiMp3>kCVG!C|U z<-wtHPkGH(?aYJRFFfHTz(exjn0H9t)i1L=aV|eGGI<@0lfeZYAyt8`hYszT7HoC< z!e_B&oMu6B>+<|0Oucg=qX_Ezgm@luMwuj-KtibgAw&8G5iWlE1Ism#>k1n|EgcQE{5qGu1*Rw8iuqJva3ZEOZDV{4>#|mp zLib*smOPP&?&keGGastyW#?K5&WOo9_uK8H+)nRvVDAYvVwljq8WRbo^>n6 zRZvE7fhWn9u%1}0NtyGhW~BerE2J#$$Ii_XlL)pw-taQf1-^mc9Vn7c@s_xWu)-3(}I~}$j@ULHx(hQ%Zc0R z{PBmrYV+jNKzfP|Y+FlK?@NPS>}2a?wwnOm-a2_w{Rn%asGH*J4f%VHXS^zzGp1BS zX#1tj-M^YvNx$aS{DnshYwPBpDWiNajEmh%6p^0_LXTbz`boE(h;6atUajS4Za}mB zWesIw3gQfQmEN`m9;~so&Byg2n`@-;_RbjGWl^kAPd)Se3t(g()G&3|@T|yF1ATmL zCT(C<`)6ll^2#YuV8r>SU9Rnu?XzM>4zETm1RFM=D;++N#&q+YGprF+AcFn*$rC8$oI)Z~lYi0Um7impd0ty^a znd^&<108nZLjW}G99B6kD&7#2m(M&W1i-ObzcO-ui}=#EEYHmG&QD3$ZY~Yo1=%hE z&&tONVPs?xV!?&&tV4hHTYjrNh3ort6sQ5>V8{Y)R6U~6`}W62YQ@X_5U1O44rI(* zEk;)Vi8^?U>7IW)s)KoJLCEH+-}VCR8MAHCK}7@$ed2(wv4%dXw?*W;)5G~`5GgF=?_ zSr6KQrx`YjZ3BHoA9u*OCKuI1RvZD!LH;f%6g7yV)nvAAbeuO+=Q^=6Skdz2$&Gs~D$1b>41ZMCr84Z1?zO?I+Y1TuTl&6p3MU#YzQYG3 z_yf=FnuU-e=2@0M;G6u9-XC(!0ARwNC)5$1Ku4>gypxt`%r^zFW;#zvrb0$kiafcu znyhyUK2VU2sE$N*-W>?8!w$CrGVIXR%rDblM6JJcqh4~ISCvk7D){l2<6MwGIeC1- z*MA;5K*;K527NcI9=kgbau8#z1d(9es-ZNV+r>5zOlCwK8Dsb$+AniLD>-8^AT?nG z_!i%?oigcWg1g{&H+dT4qzX;ar{38cy7iu7m}g{*>9S;6Zr@uCyVo3n(#xyl)@a=U z2Oq@P2Ow5^6O&iKN7&>s;#d!)jsGhKV)E|HiWEFeSMER!2;nwK9LGx9=fkX2cvVI6 zwdJ+2J~OW2?-d)%vNP_^zH`@EVNiiNPlHWDPryYUm!Rp=h9S6muCdu+H3(|e6Zj4O zA>7mAtDoy%<9r9YCIx>_XGX3@zJJ-1<}QcwwseKk3D}w=+rZ;=dgK*Mm3l5pT^K_B z&?`$+=J?Gs8&fxG`+*p4!)%bMRxS#5`hmjp?8OZJ6FJpiDFw zo0l*K>f}}Vz#l*0cWW(38g)~qno)&Y6r9{Z`>#MmIw`M!Ht3;tU|d{&t2l-7&Tj=m z=YcYRX{OK_bu>}-4`dQ?*nM>ia)rlx?ywpGdW{_kjqknd@;LX*2 zpk@~~3gli|#~gWvAf@-}6I`}$hdfzeiq55#QOSgb0<0OiYzgVI4jC*Kq54y${zv9E zS+S>bZJMgiM|&&*A60u7>&R#8UN%Tg zb26_CJnBap7EavFoxKgG){{a~7A$~+uqG2;i}tB)c%T{vEMmXR#^$L;X%H_r(+{?j zA92>-?kh4N){ielXzXYAjQRG*59;a~VR-j3Du8EZZ%c>Ew?s#V7!rtu`gg@_pm z{e0$)bajTe=twuwTxaIcNx_)ys>qY8bEv2vr;y(@bM{9Gpx0#=-~Oqw6GYxQ=FsI+ zeWz78Dp7}N{Rs0)^X57b@GrLN@rYsXrLo6t*Sjz@>$TR~{b7AauJCPH_Q;1{&i%|< z7$VP->J_wW(#Iogq+vz`!o!50TWz=P75PS-&jmVjsQfZ}e34{p^ki_!nBW{_l%j zsZnCQA{Vun{vKWbxc&il7ZpF)P(llEp1Fj)_W2$(`h`|l8FAJsix?B~Y{j8p=k{@u z)g!R@26VJ7<-l1@SDv-hh=rHn8hL}PH^wy>og-@}VkEUWt=)6v-xGxO2}17H2o*ib z8z7YroWTbmEc}^`_UuWAco_}zfX5&~HdI#Y-g`e`sFxzvrtD5;xx@O+sCRTmu`931 z58<|W-&&J!H+E+&t-Jpz)qc@+-!>PY$Z0BC0N`5Ih1bv3GB6p$eE; z^%xb}SP2*Qlzwde3%vmrz{3E%AU4=?g`5Hyd`d2`&`cSTLWk6JYf3lxPgPRhbk9p; zaND)r*3x<^H^x1Df|5o3VjbsiCsU*)12~Y%$$sQ{r;~ykA|K8+-8ee!f8WdMTH#)C zhqf#7T^;WzW(n_boz#%-OX`KqI%+Rol~}|U{5?Y0)-`li?MoK9C7~tXW%g*Qy6h+9 zhTH0Ah{R{G-|Z)-n|-_G|ZW6b66 zAHQ{2Rwrb!V?$yG_?MkBjqH#~ct^Fi&pTwo({?EQd;#AI059G-R6A##*^X+U`3nyvMmTHk@0$L#URnHV}uBACcd zY$jgO%RtMnuf;BJn&=Vbnzp|)-N3nomb&KZm|(BkFy8XQ)A9^k{V7P4{Ms7Gy(N_S zXK|*0NN3268MNVA1wq|U+Tf#q2TT7B5x;^hYGXiZdVo%CGe7uecT^gEM z===?Moy3O8V((Oh$=G>DPDDQR0GcYflSue`NodQLMSNrGi0`4bsaa-#?@S8*_dqrO zM`0B3lX?^_nU0!9H%zm*7pLRC~yPe9I77Z2i{uMiB4iwIT142)D%g=zORYqeSN;=?M# zAS+XY%UNFsnz?KYLJ46WNXulP+IXK;z(ETr8W~0-BR$x7DwsGL@$DUySa^$29l@nD zL2n@eK;6`GSY&n^oXZ&{56-Wni(7(Kvo|^8zf0?J% zY4@y-$;~Z!5*>Z<>+lv;;K-o^ui_;nOoYUQg+Je@xKLtxwI=e%OpLRm<7z8oraGIe zZ(y($gNf8?R!{3{77ay`$;j8ph%e|&MvO0tJU*V;D}Ru&`5;5fPA-%7&W>Jwkl`6k zVXw!_tzUc>lA)QQ(+&`RUJSjLb2kMFyZA59A@f%P8^kye+#J8|kZct|T!@T0{?7h! z)IL3}_5qs-=lGtje9F&pGzw~PEz=?-j zqi0keD+GmTMU_4oFB5(lb>L(_*|GU{?AuvZ(cQGkV~X-=2?xXEj?B7Od*zwArDm`E zn|=fFT+GEhq}?-%W-YvqOsfx3bt-!k12iWfCDHF2wucmd91x*>cMch|IuN0>*UiNU zEb6vz>|)0);jb6tc{y*IzRR0hE*@~R8EccIBmS#A{4m0+9PD@K*`GsSyQ?)N=!m03 zi`CpcZq{RPiMmm>w%<`luTOT4?*5um&HTP7;-)aBE5d{uPwcyb7>oWsD)M!vdSAz= zV)r~<5doR4L)smXmZ!snLDqKX;i*3Z~vSz-gdD1gxBlibRRDnfyp~# z&xI$1P7vgeddVEhI`OsU8PajATbSI_`F{VTz?l4jiF0~AKkx^~?sr_V9zz|PlpR~_ zxFY&BJbyLzvFX^O+OK|)I0=pEU+vFV_sx9m&Kl?b#h=QB{Ayn*SE7M z-M{hk$Oy7n%R}>4%qu&^NFKhd_off|ee5Wu_^Abp=gAoSW`z||*4esr5#trn#*JK1 z*wZ0r~#Mur%8kc_!GPp>}(!}EaS4NoyBR0QZ}2q z=cFiwD9m0LZQ`{dgyMwfAwGb5j` zHkA(?CNMzjkk*C>qtKk?FWolf{Q=}}S|L9_d_^OFXwEEOempjeSs#_sQ4RFzdK-B{ zobd2A5I8>^2DU0Z$)Y?~NAko~R~2i+S<@AnZRaJkR~z$hPoR|ui(AyDjbD1r|Wy3uw=br<5gO4M&VUnNIBkp6FU9#@?HZt-muCwnm4Zdd!6XAP5gZ&|`g<$JzCcs)hhy?TVJ>v8WS9@rTEH8PTi5=+Knw0cS?M-$^iHdC=luX| zA0_(E#Y&%t){_qs+XiBpV@7qr;=ZWqO3_L?)l*%ql9h>lPdwx|YEn+q7w2i0+d86z z=Vd)qJQ>=ZWM^?sZ14i@zM$X0v;a^%L1B-^1d7K2=|93@WkMmMx_#szNvgeZZ2x77 z3U@DBtI?QEQSDecYqG?8HaZ)T6TIN2gB9P7`HW+A4;iy%E!ja%Y53N|LoDmL7LERG zZvGxE**|%K>m6i_L?1zXVlqdl8-p8&S^q>k`tAvrqU;KCw)iF&L}4!^%zqZ5twz02 z1zp@`YGjSB90RvG$UsQT0|O4WB@Xn?-j7GF%ctc{Y+9 zWwF2Sj+?VXj!ulswQ}GvBaq6*%P~tfme{q@ySq;AU&qSU6&z91l!R7wpJim+Mi-Q| zw8d(|rk)*WnSmYkz`kFIn!c1mM=4YH?yJAf{M+IQel?0X{_RJ_rxk*eA1Wg41ke$Z z)-z0V6vmse%j*!}JkYm4X`_hp?v>M%+pqw3to)y0sMHNtHvgKW2zO-91lA%CaXUOk z6=LZ}R1m~1WHfKEMw7#tk;XB%rED|Ew?E+Z6d$yPD|({zo-KZLBsDOS@*7VcTh|l$ zJP6Mg7`WFxv_J{{{&?kPPEf;WlyhUyGdd)PzE$u&kNN1{lpfxAh;P^xb*=_#l`ShTVwi%GKRKRR~h~{!S z*mERD6=C}gY%06RsKm21@?f5>XM>MXk9o;zx(|@qBGG1f9<&PU2Bk_|t|;~sGC*t{ zOxD|E&sTEGaO7myUb5Sy`arEcR;*j}o&aEIEbG>V5=MzG5um*5~?z)FUQgf={F#L+}+N10881iY7*}lLyER(A+`dF#1e8rB* zo$KK0dT6irCe{H39Weim^Za_Mp`pPZU`bYTi1`FIAP>+e^D}HnU_EL5#{dblK*sFx zv)6?x)|e&1AV<5;+@NFKIwsr)Kz{-qs{NYK@Rdk*e*)`>{%p9Xp?0RO?WR;idbTyF zp)PppDr5rTG8bKHh*X6*eQSeHSY?1tQ=KOg!kM}qOu3KW`N}>jyEH>zwa&1U)K)U~ zYb^@S3H-+I1oUue2W%8}%7a&PNe&*8N@$n?0b;DlNIa8!&;n~cl-7wm@U^C?JqD6I(Q&;UunI6f%1i8M}InWU)-oPE|4os z-52rV@N(IKn=(lkTZ_%TE|nboayX~uscrx){J$TQ5Fv*2e$CVqowTM81M)t^8&Ic0 zTad!arx&*bmZ)Av7mLhK-n+5ygm+T#xnO~|r{0e3NBn(mh&lyZ)YrhS_Ix_b%Lcw) ztaxsJXe7YzKWUON;A~-Zh#T?wf?bZPapeL3l2^$ND>XwpA%Dx&q>g#bo_Dx+5T6iR zB;M-($@1Q%{h9|y6FAr>vdja{ipGvCfXO-I^!9C&-#g{&_v&^NY%7PJ{rm63DKF_i zms~4f#R*AA*c%+tKdM=$&g3#^wO$h@p-_3rh|A9EYkCiRjebHAa+pQ=`9 zk*9b5<@a1mkdCgn(RxNr(rzl+$Yi+pM_EUGMT(=8)9KN@{|uAGJkIfoHO$KD8izwl zyq&Dljs&JPJ~6p*G1&BIN``NtuEV?6&bx^^4SEWL4^*i)>~)h}PY7OiP#oDGc=z++ z;bkf`hW<4$Z)A(CJ zx0D)Jj_C_3eRt^;`{1satn*=AM^_9Q=2X&A$b2Q$A@Rz@-Xz^Np!)C6WKXvZrC+UW z{&(z}SuFUE6ijfz<%>P)roHMK7lR8fo#?zgb0Aq&**RKOQU9yfo`Kf-@0jQp=FSGeu{{4|E4Gt}LIoL}{qoZK zn2bx6&aIDBZSj7CFIEY+N?EUpoC2r21qy7PG@QXz?~jyeuRbzqD|`N!^SmVKKt!PP ztrJx^qSd3b5%Q~X*9gypBQ9X)p&ONd${lfOoYz%8VT6dn1H~o40kI-`G&93pFe|AW z-Dt0~S&sW>wIb%;G5v}mee{gKH0RH)@*yHe2zT80`#Wj&7P@KrO^R_|+`^Q&t;t}T z!?(;=!~0~%mYllLE-!;g?0^`n#FUpwvX|x*7N@!hCH+j_sApNS`!cJsE)SywJKk)R zj~-u*I%;^G?e7~8;@v9z)qZODEhOQxGbbdYjijNyz9MDriqa5!kNl2sOt;E7W^(m$5Wa8n z&VJX+cI7}K0m>ht(X;J=ctLKnuLecG=yNrtDeo{(|7R7>>7O6>=koW-SxsCa zPJq<3uP$IcbBna`KG?8vp~v}1d5VVF(REIkhLM6htEA3L$EEvA)5f39OIJrs9vuYw z(*DUHF@2G$T&Dt@M?IobC{0V{dg4PpFW z%)$b1E;12dGcerwOc=+Wl~P-p+H~n?r2~?HR^jA6r!kZ(*ehj}2fI%IZnC5JE5=w0 zQs%Rnr#Mk@T_*c}njoXmbfr|1S^l-AF_>RFnC3r<-GAcwzkuY5xmZ)?jv?q&6+ zA#|@v<`n1q+3$p7%65M54&S@i?Y;Tu7Q#K`?Y+o- zL7R@n0UI+4^@}0b;onpUY(tcOeephbh!Gw_Mm|<_saKH$t0SjY!*`pCT8}ILg!B=6 z^tDOhVl#`vR8rVogkNu2Yn~AWi)^-{wt2S%+IUpz@RnsG^ZgI-MKNr& zoBr5(hC)NgI9MyPZRhmbe)-y? zw%JF9>pq6FK8Ejc{tSuKQQAT<&JK0kRUzYMom$50y04$wYwVOOb@~-u=QD=u9B}?y z3aO(4>BnQe{S@tOn5IM0&mE=|o{!#fe1|&c`aVLxQWB)&S`0p0nU-PZ8l$>Z*SCGH zAtK>(tXUxZuMeE$1K*wTC=}%UEmgB>z_9mpl>}@yN5s9aK4Jfr6}6WSnQGdbZ;{Gh zT>3BEE$uhGE9`fPd-nCi?`j8JQ1>*xc=~DbpKPuGSv4UKs%bgKz|OsmFfE<7*q+~IfZdI8d! z@`KFa*OV*1Mu)8AN1t|{tJ`e8*1?Ss{!>E`JYF?<^gtl({F$j&emBT_Metuf#IyY` zA7bdN`Z{l4?SMUX;E5ib+FN3Xu%AO z`#r5=E&p}#qeJ7?-J8+1VrLu{MZYQ<+`gt+WG(XJbNHS^>uo++L!T2IZ@PYY6KEwe zxV)|RMN!4%0bB30;vwURIey8;NOA4taJqcHE-B|v`V9DAmc#CBOubX92whyfq zuoRop7BsU+e_T*XUhtc7GfUYnaOsta#^Jfhwt|@xnB6JH!_g{v|D+{Ni=>Z7ytgdE zy{*+Wy?x;6z`KiG=@;)l+8tX;NO}jea7mIV?Bp()d~1L2e#GsI$k%@pW2w`zpLBo2 zQWL*5nPk0B+gtL?S7N~(or){_?V{E-AX1G0at?*e>~nlU7?3pK2+Z7foB49uP#txE z!efTNP#;{TxT$ps-e}r>FHF&JR41gKp80QRx#9W?e7^2l<*~odMYhH8v&)Oeg7=$D z(UPRRvFgS_r>TNnhh+VnIBdC3I@Db=i`Jo4dpE=q^XSpOywv67j9Jx{gmrJSG2;qo z_SKcysv!=^`w+jFZblmpP}GnaX5!AZE*gjZJNA4Lr+8Uo(w35^xcs!U zJks#~>jjN702A8Y%hh zl^M#(G=5Qph|?GvT>fdD+U=eGwdJl~*QbxX)WXY0ivDCAOs14{x1{@BAZ}`GhD3xN z(23KCw|k+%u^THm5*%L`5_v`YH#@Z`1b)SAe)i#8^ZnAPg$Ks_`G?NU&c%}9TF|_ z#9Y3%$DYQqF+QsC+9GuML}DReZ#qOSc~Q7!Z!s*>R$8ZL73!j~m)+p2oKdQNLDf>- zRJ9X#XGs11YybT}ih2%ywBF@<2xcC-lrZ$4@4<|wlKuK;8|zGZ=TCJ05Fcs6o>S%e zZn}RpT=+)Sk9ThZ9ciqC{M}fl=o_EOymV3@jp#dL`N8(|WgE%2rk{*BH;-H5myV@= z9e!|5&QEhz@nzvjD}!9MYZvpEDe0y|QbjDg6Xya;ZpB_aEu7VU)`j}aWN7Wu845)C z%!8OQ)zoLc1G4YmCt4~${T#>0w7aeR@xih8@L^Ns-F~0o9p21)(g{T~8q(G$>oyU_ z-VgpuWsC2(mUCRuKV9Q!UfS`0s5w!W}|w^h`x*s&vKWA9A_LCvD7N{w2jw%Te7 zH4>}#u2rij+S|J~BRV&{7-hbZD`-danBc7bxbMHO(dCoH^@cPAzH@rdSh>Rgi zHU9zgZTBbD{}=#=QstyB9~mhEk7YbIKFC#SB)4Cg31K3H+osqvj~$`OHFZ=5lCYH> z394A&@t3X%d!DeeHhuD!nj*GXG;rVv+dZWOPN_~0d-^|LLFMM_#nGAARR8U908BY? zy|WQ+$jb2%d);35jx?^!Oe~_TO|jG9FlRcdS$gZJz0^G(hGvkx9I!@?r$SjvWRzig zKoa|RHE_A-U*lympR@I4ecsI01YMC74#&}geRT!{V4%h*beJkJN~8F|8_YVxKc6fm zw-SHr^#D08$pX5%XrH{UmB{<(+Ms~S=1ca^Mf`R>X zwb!Sth;@*99Fveya<0z?E8?BJ`Y&c7BO%>47dq{(CJmyZ-o54}sbNO;RtOrynQshl zjGQhL8cel2uLhNZ{BG4IDYZONDk1XoICB_7df(Vs=?y;{TDibU8ymzqSZmFZBmcrBdYhNFUUg zyx`C=iO_u72pnzo%U%ac1g-2y2W`wgEGNEHtH{*d3CvzLCYd0`0 zhRNoW`lg9XiHcvBpsCMI7Q!Y?Q|wcdT)(c|n3)K7C2%fKuys3~>LXvsD778sDDxOB zZ(=lOMmE`YStM$!=U8O)a`pK35DB}7mUPO7Z*l7{EwLly6t~;ngm1CwYcC{4syz8P z(E5vElrW%@_(xiibe$%b2$&mTVLldmbQj!Jp%l_Fs;dDJO2W=$ zd87N}qi0L2M+eh3WQYQQ&j*(XWjzOmur;^R$Ti;IgM5Aw%C5XgP*+friYsOL;yed3 z1G;_Th}Y+*AGgSXw92VH7YnhHEJpA>~z-@9@{c8J>~^{>+54xfXdfp3Q9^xo$Tlz+7iO z-`#jn_vs3Ufjc0enNN5T6*X|{FWlfhYoHpyxV%LRq^4|l;DM+1v{Y?M4IH8)^u{K> zGY9DY2C65Us(Nw;jDXePAc?10K?L*5&ND?lq=3F4@b8v4mPiN9ym*K<06Cx(qB&&* z&QIV1SF4EtD2xZ2R6um0dMBcI2(r?z@4+a6#NbLnI8^7nk%P5FHd)p6iYEfr#E1-a zq(Pp|W7gkg56!9ZmG!*6_e@$Baw6_}*G`MH0lMk~|h2Cg5hDLIZHnifaWN~Ubb@qZshVgoCu zKL@IgVM}|W|Iv^Pyfgv_J!+FIX68-_m{|3_1Zc{zQ|U&>gavuc)@_Qt%YrP%Nn zh}A9;{5g={4SwyY4EFyK1^26$f~_y>WP=KI1EKeb1s~-Zm;v!Uw z))hfn+fLjIAqv>ln^Wk(>^hE~sjw3q^$IL3h3Spw=s~p4wsjBowHlc4TYF?Q1aSYD5b-iN za*amh=vObOBujynJ6(Z|JIs)m4+Z~|ufGcl0sDfp?v(**a*%&lk3t^bUl@5_;1VEI z8Yt-bkFc^D&e57!xf~Jjd2u9M%ABf}#t3}C#tn{9iGthbE0z&KP$(>VtDxa>ajmB< z3ZTA-*gvLk<4szFvxgJlrg>*)G9hc?`urIw@t>a_bVi*rpepFe5lbrMh#r*)xUnM` zc^jOCGh74TiOGrAXJiF&N8ou3VONo^7x1i}F?d$@>Qy!;@;8io*c-1;PCq+~4LPr{ zeIkk1XP|$XnGpY+>Yy`o$OSItqyTQFhk;u@RE30^a@y7g!B&G+o#lJ-xEZx``hx8ig~UZXYCKHbjj5B{cFHKcPEnh|>y-v*a}LW+KoPSdcB|lYN$(%0B;@ zK6`zn_D!7a>^GWG5!3rHk4FKwhVNw&-2`zur;zB4ICILho(`Bu{a?q6IbldzNC%h5 zT==N1SpLMIFc^6I8a&f8Bw(wjv)~sFtD!8!wV@S>*3pz?%bnqvPkiR;;dbrKm}mDx z3--cyA*IL3UrOoo(JY4^(%N6xLsJeL_-^ZQb~dPTfU~S=}u)#1LOQ!Gt=e z_E@B&D{?1e44iVz6(c+7tfn~OWxRrt4BrH;?re-Z^Satq8Lq8-zX&vXd@dIF$msS| z)~NYs?IWKTehsR8Y{;}iY%o42_m__3xVC?qb+3lnl1?PdvWjFo$33z+8camRR7#PL>HYIA0I8;*z2C{Vh94&5RqA zT7_PIF6+ULSbKeT+wVfHb}T`NU^j%FNMn~W*9>J>*<`4zP_l|Earq(>8!|&>dq@zk z?~#5SBO7uLYBz@kQhgeRg!tRER<@GB&q_j0xS-S;C`{->SBmeo?lU+aHe5Ew5|XlH z$;B6E0pkRN|kd$wMnR;fwu50pEw1#I98U& zrA<-Lm3w$%&~DasP3T=?@4GYCh?m2?>tIyM=--(uuIRN7B4KNOwUKLFzpKeY-*}yY^ueZOFFZzp^AVI`)l%@EAd3VI01NfKF|$@z3T<5qTU_t>UUQ{r zO)62{D*13rN#LGbCmJrSC!zwh19pts&#?YC#IFn%w(hDP|Na+5B4~le`oEY_w#2}p zu?FAxd_g+`^p`4FfuJjaCR+-gie_VwZa+JCToJE$ZXg7r9}^US>0yz*55e0BfsC6G zpPkC%ZCo+ep2)+oov0f{JbUbOI730!y<$nbBHj-6Ib4!UkGa*zigYwP~mh5g*{!_e&y1 z&v#BVD7bq5rphY_>K2C^e3%CV59bLqW6@2Q5`}|Zi~McR-Q^K$Pt{W{h2hpYKb_ zM_9?J(_aX|iHnC8H9&L`C~y7{Fr7h%DS_H(kPJYBBLXJIjP%o@LH>q+{O6$@+uz}i z>c4_BELndyAyv^c`Jo(TGMUqcd7j*#2#@Oj0#DNb8J7=%8^7qLrS>@!z(fTT)&Mc+ z*5~2T4<}lmBQ$ckvikF78B#7!9}_kQi&uD9PT(pnGMBX|(l4eTz3+ba(0^mI0Ban@ou4gxF12d-0z2 zb(6Idzlz@V$$5Tle!e00Mo83y%U8c%@x5TfDy!t66mwevJMn&HDn*xVaz!@+g|ggV zC0J3ws`O<&nu$*fpRzVMTXz)^2KUgFsz+8$M&wHu{o6Y8zsRg z0-V;<>o*Wo=tD}t*aLF4$H2}*3#2=v0;`?$CzX{TLmW@t;lTWHOPY`ZP$|=||HfO| z60NurZr}9=fYNSPu+hbfSZi~DxjOxbT)TCMfNKqNAcH60j0lh{1?HeO$Gf?Elyr>-Dm&Ka?G0D-ys zCP%IfK!7Lo#M7u#I9sZ?D0XjI&gxf5j4a6T=rq@_0JMoPj`2mHQu zQS}x_n}**C=V(tvKdb1la%1MIYXF|Bq_oo7_rUIy#=S3!u6D5pz8`EF#|a7>^hv22 z6IDWt=U(wK>rp?b)QvYRTTmqBk9hQ)G*+o$|8+Rkz#4!u#3vFC7vZ7)Uo8 z-RHYqB;^uQa}SSRPBD4 zFhk}adf9=wXU@+$-f@=})&B>z@3BBus!^|Q5*vDK0(4q$u+miGyZMSR5!1R6N}(Eq z^M#H0@0UYTrP4ls^jA3grW$Q@d1DJyeVwmUF^$B zA{E4SHVCgstR`av|3wA|?pOwT#Qw$_HAb13>|iYsmYen?bxj0Nn432xs`!Ik9w0;+iaJh*;7FEr$^|Mc>plN+4rPbks5pb5#;B#asYgu!jZfZvun z(P}z6?#~I+VwuXV9YNcjIf=E1+|`)U017qE$AvkX#EiJ5i|Jq~BB+-XjhL(ZS1lE( z1kPx86;018)o63cf2E;4R)CTU`5%Wd5IyBU!jiy1@)B|2*<3HkV^^ECd@)lOLU=^#h)g07Y9p)Rd1_fh|z%J<~bJD{nBM*?0hThd3GrL*K+_8``u9Q&657o z+q9{H`_hcuDoMn4%@ohBU&hyBCdj5*#Hzr|7x_~y*Mhrdw&&imb(k>p*!kWyO)1Uf zi(NCW{nx`4fw@2Jn_wq%Z?mjU&l&ohI6!@b6kU83Bd`d|{aXq(aN($1{yA3e-PUyceE|1anK zwLgC+V>`E7jXm#(ChFdoobxAISH7ES(P3;dgzCX)b&|1ndVLNOWTH9+a!u1Ma_!U$ zfW7PgZl3xM|A2?yH48#t(*R9gTh?Hx;FW>6LfAv#@s{MjLl7?d-yx`((!m~r)Tt`$ zuz{0sIL{MHwSIe2#Hkld&*FDvr_c~9DsB}He(AIN?}KRcnvoM>U&SH_6=Un{B^qsI z?L?@i?gT{8m5Y7jKz>%TTeQbh3)CMh!)mm;QKo||2<5tadxE~6W*L-CnFP42hZU8# z3Ik{BF@n6a)j^WeL_PIoM&LOgUAW;H09laVwqXE42{v(oU7Bpcqqd?>R0t~pb?!1+ zaC5OrB?uXsUtrS3qMN((OC5R=BM|)iJ4#6pOogOWr$YL{T_V>mDFJ=mS%0tQ)v5bd zvM5n@vdZ8R+p4$h(Vf!?P{3@0N*=5%F$s)vPFyh*f*a?Qny7)c!%<7>cxo^8Uw=!C zAAflaC$0D!Px)LCti=sNee5Ge{?_6^7F<3UPJLzsrtsB)1AHUlc)4x=s2~7kGD1Wk z84$s2+*QtkbXVeb#|Wb0!3)6GYXfNGV_)vGQW=Em4lKKL}v5H-6WM( z>GkIzoOU9LC2g+ZDoXk-ulPo7OV{imrro1l0?9YGcHhpf5@wD!wXFetM<2ZW`7M#e zdn&BDo)pM4D9*kRHNsb%P%Zz12nx@3!i^45K%j2gPYcG?ajg0b(<3STgN3DBA0z2BzXIEpi z5%8>7R^(YbPbaTA(*3L`zV?I$yRP9}0jSm30}0gx1A%Jhwf424SV@JY8nod0Un^Jq ztVloD_JRdopRc|Xe{#I-^(kB=S`r)>qXK4DjfQ8%ct(PKMX)AfAJR7iAiKpRs9sT7 zWG6038lN?ia!;N0Y9vhMq=G2x-U864ni$1v9iO3az5oKKiK$;w3&NRld@PWmRzU=&`eK)!wR8Kww$Xg%^Yi*Xj;kQ7J?_n*jBUpCEl(;h}+RPGf%~h4R3>Gv|lDFo>OP-0^Ep^XW0$VQoMn_6KTTzH^;tdww>lzDYD}E=O=sRTxOKifxd?pJSrZF%u zKi1;#A4cYAZtSofXD^yC_6{Qz&!W2BYe@ z-hCB@tK|FyWx|B$3+=XvQFO(z@RWFyCu>N`&7F-izsHxpTWm0}xXLt3>W6o(-jDm% za>*8##$Iux_;Ycn`olOX%%$1_x2JG&pvG=j`qD9VTbdIk&~d}nt*kIZ{h&!+iH z%-ADU16>h;u5_cDO(xBm%)(MAZvWsr+m{LxLaKR9RW#bt-lVG1?3C#ob~lo%pIZyNeE4J^Hlvqu zcyr`pDiIxh`Zbq!lSHXYf}cTMX8VnSfPu+~tBI|&f4(EwbRdcv z@)0++EStC6%J^|%vv=!rhY_giUjB#|u9;okD{p*xsAu-OjOHEUR&P=rAf&ZL7S(mK zjM1959+n(*(feGCnSGrgx(Q9;J|l>t9p*U)HIc4n7IznP4jHG6i?!AcM!bat072b( zlTUr!PG^ni;rpV90oxjxi6{ot!S7H5j9nEwte+&XWvQ3<{ zTpX^MQp|N=OE9>CmmT(Q_0J)b|8{G^z{c;oMYjz z{OH>|kz_6C>+93uAOA@geGmLky4o16)MD{YkuLW0DMey(^A#q1(_ozF>>@XF9@Who ziGo!iCz_jP^Q22VqU(@_4Xgnnu#4r4bt?KE-|edxui_OIZ!yC1j)$lun-jNhI3av3 z+HmxR8vKo}p4K~-#=ITrq|k9-A@vJ7IIpfpCXeEEg)Euf0wt+liBYJ&km2tttLn9U zyaDlex6}89?=9f1ymJb&H_y!QB?Km{v_u?%Tdll`P0_pVM@T! zv&0iE^9P5j=QCD-BhAE$=hhF*lsxXO2#)OdnVdKK82%2}KK?Cj>=5=o|GvC3$v#gl zPkTPsLYbomR{84%O#5dvtY+lzA8_)P`p2j(hL34m&ueGp5nw5@LXQO6JRIFjNd!18 z82v?!SXa0dhf@ufFn0pGPwORTw1(y>JqL}#DaLkPh6(L7!7;n#E*NQKq04tsWwx3+ zxFr#gA-xy8D2B#}AGePdpN zLHXoPFkr{$72I#aQa*-}n3%8t6_j>&rSq3v3M0Aq0fqkx?s-m9qwt<_R{4cv)Q?B+ z)K5oqCK3t$(;H(lT91xl2Esu;82Um}3R=0KVjnFQ!U?l#Q3j1_;W7any`8t34OGt84A=prh9tG)O}=5og98 zw9iy|_&o65+ard=@PZ{abQsuE}@CoVD60=K2h;LpI$B>f@a68yO3j7LPy%A^e?X95ziJ1+0?*8TQiQ3 zXt0woVc%Rf46Gf{el9B?g$s@DRHwpR9~4GgpkVzqxZ1my zf(+%4$mdT9$wB2y_zh12&y#QRvmNgb;sQW}{qR#U^ngkle|0qbhWjEK^-um*@Ak_> zu%7{iM1-!T(LdQVouj7o$fMW!imkNNm`hEre^lusNBy}Im6q^-Njjy{;X(J`1c&as ztr3Kj# z>niWjhe^1crkYKFr!Bld^0C%!0UuyN{xk5$H&x_|ORCjxwMFt;5A0weg9>H%`S2=V zT`B3PhtdQSpLN00qFlVpU7y|{b_#R>)7yhyRR)beC9lHRrnQzW>Zhe{HJ1E)lTXXV zLYWP`(}(+yLq6RRls6LZF$#3=enBR7lKN0^JV4|Hk zPC(osRea4E|M$0!tM=R0I>kDz;KzIg++-j6;iM*QItSjZI>&?oMb{^hi7iy}dPsK_ z(N7@|ram;nPGMA6{OR`N+#naQM$sFbD6LaGk<72Q6HZ{YB3yMwRU!AN+*x-lP^(X1 z!94JH#&Kq($re9W{gqdZbH3f}e_(po=k@&hq1OA=r(#U`0XZw=ry;a)*qtBA&=}QL zGJToZJB6MO?ADJ`uUD_`j%X-Cm0e#pBL3Jm3N`nXg9*F$BLa)+$-nlKNhaP{8D)F2 zI?4$)D6zgVV$bpl-gkdySI!lI7+NI zVG~cOS?}W~!ryazn$SFdTTm$?zpXK#t^SntZjL)DdX+4D@A1%R=y%D>a&+{fgeIrO zhCRQv*H`5${%pC6wLH%vGP*j{Qb(V7q-B2crZ11ZlYNw~FR@dNe9w1X={E^cv(0Jl z;}X?@#gqk%#ku(n53Gp=8sjxa@9-WA?t|LG*?~19=ev~*3SWF$0 zJTi_6QWYNT%vIY7vsJkSjCP(~w{Kd5K(>ijZ(K@KM@pUe0s3z}Bo^ON5dEo_!Y%di zAlH8(vh~(N(V#*rh(|XQ&w#Q#?F(FIh|54&Fz3Lq<5V1(gMn^0As?P|mVS%@6zkP_ z>lm})t4}4|_`+lhTU5sr9vDDBRO}H>F)CC?5YZ>T)JDCql*wp8uItwTyGo#<$QLdm z#UHFBn>=zQWZiQm7u?#evXMR*=0Y;Oa0*Og&Ymyc&Q)Nd7Co(&jqZYZyoCR4*5>$7 zYaG9i^J+8`sK`^c`L+PDP{_z^GFvhl0#3#!GZ}0J_`XO2UF4L)_kh0#14?WV`(^6n zbNWdfKbXoZuvr;i>qr&YM6&1pG}Q*YjzpWtDnS@0z)i3{AI&VIS6CfF^A}rn_d-`8 zzVdP)O5+=-&TIS@0}L`s^oM3GT{!`jV-2LfW}QMNbE#9Z5r!s zCl(J*#(LWY5m#fq?U>;J`bd;Di_&wfw_OmRx~W2@`j;+4wa`MtAvT=7nSXKL>HcWt z*8{-dS03lkQ(Z_Jy8)NTSXrcPcWzrd7j;YtVHn3Z!WfRn<$C#W5&J|uWkK5jMNx1R zY>csxPnQ@Ns2_+xjW|@av@QG!Y`)!Xsp#8n99qf1Y(A%zd zA(XzJ_mXq@qL|p@^Jukm)3X=OfhCZ3$4I<=cgTEH5_f|#&zLh&UQ&8&=xgFa9x1$o zK_^AtVz%V~h3)d>pc~SObYp@KULH&b4|wrE+PnGT`fPbCA9MPHQ8AO>)b(rio*_on6+*U=4z+mtd3h5> z0LF5xJGKz&v!Q&VJMww$yv{zAno1d)`aUStwolh1Ix)|qFD%Y5JUrSkiI2A2=~v_^ z&tzB@ebdIH3+)XRGcSSR1DmPY}-d=Fi8agW3GP2?ONNul6E7 zx_dUv*A)t5?Or~lk#CWHW=N=BbzWq_W8gRzZ%u=sC&iCq)&g*4?wN$ zox6u%R5Vv6T`P-Z#Lmz|0IK283%rl-3@7~v1>;L2;6FUCFEP$U_qyM{Qm9?nd&|g+ zYFv1EN9`S)fMzsponeEhOoJ>?x^WhVGLDcKM!ziC&NJ-KP35iJmAr9Or&EG=y0+rdE>VbqGYd@$aQl#LSueuG z$mTYTXo`SY$i$<+?bSzOn+}6Q2C53wdZ)8wu419Hn91-Tz2%>;l1ZmTr-z4_wZeDB z)GOP+I6Eqw8`IKNP;fDlTDE&oPLP=EHK1G8JI>$J(p0==6f_Z+idvSMK{7|`ABo9- zh&E$C6_nq&{yTj8vadl@RkCq|D`k@54dx1K)x8S+&ZZXNPxrf7>D38Qzs_1wB6%NY zzM>D8hN)RNh*>Y3qf())Iv0GUlV|~@I~mk92t}>k>F!YeiN92;^X3xFeyYO#iYNAW z2@Xo!{pltJDc*ILsq9|r>DMwP0rEX?7Oh)I&GVBCqM{~~_1tU4@+Quo3K`Go zNCDbm?;)l1O1-wcD%`T6h{;F#J>l?C^^#uIGQTJHSCJVvelR{$PW*LPz2Q*AO>-eb znxx1bYsFCDeUeN{%Z++gbY|t6LE`Dl7f(zzfiJ~>SEQ|atdo{QG2&AOZzSnTOoyD? zUDU<&7&A-Yb*|KG6SO32SWL<&fm6ACWiy~H2XdDzwJ4O7q7dWzs2plTy_Ucr^sl!Z zdWX7Aj1c7=M{ha3w6}bE<+GLr0e1YRFXev>ckTYUW_;IB=9NlzZB+EOc`auVAOBp8 z*{gPqFNJr%mnDNg=#(CpJ~V{An8o24E1o$CaPMW6;upPO!uBr!_oskStYw-wn65CD zkmE^EF$YgC3oKtmr|oguC;J>IVSBu*-&3aZ&7PO0^Z)*6{5JXFL!P2x*HBc;e!$&$ zo7*E#lH;WHXlqRwuj;wW-;oA+dWE7D{Rj749C%0KUg??j^7O5zUBwTWAS(FI^2V}6 zARI5+!e@%lAK2vml#e9-Yn?cWEBJ8!)6M`3_a~bEqldS0kwkC9e$ckMI(9vvw(WmB zvbjXSB2V&aS543K4JD6n9v`6ON5Dw*~xgGecbqCt`Lwbe3Xv^5wwi&$u!VJ9JticMjwL=k|qOJB-)P<2bSn zEk*mRKlVwPI?1^7k42Z-f-19{e6It{Fdr$um?f@%*x~;+czYt>U_jK;vZnP&-ss8B zoj^QpbNN$TKV?>OFDak{sZ~%MH|$~DGZ;>sN||@i_#ty%BB1xdrLw40{PmquB6hKI zrYda?>V!J0G5t$gWH^$4?bgyVZhU=LI2julSR#tBvQ#|ss}PU2a#1Ags|^OB^hJzD z(v>YBa8?3dIGb*sfx6SqC7Ev2?jVa@Q0aH^^P32+t8?pkcjY=|CxX6QfD9_6Ndg>r z`{xdWu0$X5-`V4k<-F~W-5H1q zlcitLdeFj}J5kpBHUpYsRKb9be*l;DZ2(mvz`hyw$Nq>g+zp z>|#Rf*Bb0;$Lt#d?Cphs^?v}%RDiA?Lj2uTg8;ilA;9JzAlDtCdu(!3O>Nvob7Dit zt{c{U6lN?ChRzvb5-i5wbrC zWwkiES$l^N&#PIiX)BWu2VIxCG(`wX!>D_hjy7PnRBce=Z)&uYh$ zsS-#UzBTrlzG%P+dT*^w@x!U^#n$RF7aE7&n2m8<^Rs()xL<)RD!S3QDSWfzVx9dI z>j2iPzWl@5OG$-QXkHf19C?AAvI>Ly>Q+3m^&9X{(`hcw#wsGd{(6sLDb55WO zuxjZ0ob_LW`LA*2n71;er1&7jpG+X0!Wkw>V!E)lfsZ*E=XrI`5=(nzRkS}pcS&uz zjWSps9E1L=b(hnBUMaE5_7?H3X7fr(8e3eY*9wPnGzt$GCiYFyCZXD`xROV&DDD)! z>*13c822gX;vpk_KhV|5*Q;)Oi*W@0RTsafIGAm`TgALP(noXW*M#`ij`#+O~WL`z8g|m=9{;%B$ztN zcf1{+I~`u%Ho(#32^kHrZV=S7OrZEmL^!c+N$E3Js%TgRr86%p`|SBX;wLt;{GGg# z_H4l}sFBS2`Gmt?>VO_zU$ybaXp6??VsV|SuX3ApMN>z%D{?5H-CCmpInd>0ESxa@o)IvtZ;;mVK(w2{u zQu*Y^sArTG3+{0-{-)K$ONZD()RU$!l6(xFgld$J#A66V^(*!Ih^1ao(g^1d`Sc+G zX$WaOyXE-;Ppka!(wqPv4yC6Lk-E9W_1Q%N>fXrsq9-qK!^$XGpN6t1V?&AQp_{K* zbuFs+zM;M2~6mQNOH|^twTY3RcmK$tzaJRGW@wifl-nkBa z$M%U|(yf7td=sB~p?Eg}m=%N#1J0pxDQdD>F+anDRbPC!)npu8y^IQ{h^WV?H%a|@ z5?rM9UcbvC_X7B)3feLE77w+u%U9FXkyfiHAtrG2VQ*2}V%&I(>RU?E!_6koer29< z#;`Ff3XI z{|%meX5e{C-smS$tIjCSbCd4cpuIsf(JB}Eo+9_3JI#b?rB$7o8Mv^XvIzMi&Q>haRmj5Tu)kAj6kX+Mk?D#h- zdfj0*I*6Vd%K5xU8TiO023LxdH_-F>U2#yXu1#oJXAV=r_iQJDu&|(KTY=CCU{dm4 zPq8bpdqmF*Z18?3Rc!CLyijiesh%=64mC!n>tSMi3va|KV_mmPvk=1vOekBPNx{%# zg~Gi50`@=tZFz>DFOww3b>MV&yl!z{VFJzwKCd$4DH7Qcy)Q7S>az`PJ6_7FgtBGv z8pQHHRI;mh`Z?W8F+cokPT1&^S6Q?;=I?7vJx4yD^RRi^oAGgeF1ep%79C$P9e+Uc z%#LvluSfTeTF~d%ptF=s6WM9hWo93_=2eiXR@T|E+S^Tmmo$H?BAqp`k0A;BM?hk>c0MQkURQ5#i)4_Z%kXPgTXuJK;SFzc+iD zFQS%@Nrc$P$2;E2##Rx9uzdOW<%l!{|2SM0#up-oyJ6b>;LWF2p5gkdfYeMgj`Syo z>0ZW=xxfCtaBU?Zk)2pU2TPJJYYZ7< z-5(*nRh+Gn{TCvlPKsfv^Lh;lBkZ->^n)bN#&2v}RW~SI``R4g*fsM}Yd{JLe;PyV z;(uPhhV-*IQopUXg3dOkce&D`{~l2HYrr*STg?ZDR=Pg zehD1m+f_Or;g_h8oQJsodr-_=K*5$15YIQMaL2MhO>q7UlE64-4B?BH!&WV3;T3qg zgTF2A6U|PDCc(pw;UU|!}Ws)u|bt;_{6c&{OYtg@1i zZxwxPxUlcs7l>vjKnEXFYe*Fss4B>?IEn#==+Q$2&WrJQobBE;xUubk!+G| zLvNMo;m4eaBg;)UYY8WKc5IjrG>sp3hrq5wznHV2yhl+MD}2$yn`}E%B)Y4ScHHt& zCoVcT0V|?hgbw?@CT<;V_(Bm>7VF1^cOI%Q$hbpL;(|9$fff|b&59q&z&(fXC6s58 zoQI0Ikz%{u{#l;I%Vpr7hn^`(VR?lpCoBFZB-97$6Nv0`rA5w(MWlRYd%K2Fav_zNn3v|T}?0>hH7*hnWj=trsxaoB63aR@fgB<6oH<^JGk4^t4|~J zgk~-%{^Fy{IC-hD1IiA+R+zme9>>>Ps%a%ai*M%0#aH}%a3177tX!;Zm%6vdUQAXn z`)p*9rQD5Vh)8eHqZq${%?T>YGWZks{FD1QBbN6M6n#I{PWczB(na1VVLJ}u{`#?y z^oE}Xoy}8h$+Ik+6YuDTAAam7ymQJ@eDluRBJBLqmsWs?Q&!9~N z4pJk#`G0lWC0Y3^Z(5Td`#k(AGbEFz_|f8wwTgRxE5ZDmOXM`aZgg`8r< zD(1t7)zBthxyJ6BQ^ls3E}_L(`GKiVm`B$~r+-zcP1>FO7G19NPKzXB_~Lb*-mO?B z6Xj}G#$9BpH~h8BSpZEhk^0}x$z)_ozfZySDXn*utz12&XLRs5qnILJ`LorZ& zQxU)8w|%PI#J-d1(2uVml_Om4wnrEk`p)enD9SV1?%Ggo^#LdY$3uXMb8y7OXOz^_Sdyx zSotF0jHK6TeV7; zFJ4G&D!!z5IJe1(pmjXI9WlpGbr`M`+8O=fLosgY;K4j@xnGMdKHM|2qoi<%XZh{K z;Rb&pu&VstpqQ+BqDF_=zUVGrZon`w`r7$*6YXc+yVY$>O0@je4*8E`I|82(_|4su z+4D9**Wi8cRX0^tfa5vkNR7(O_PxTA@T}qR1OFac>0^9or0pA_3IC6%ys>kkwaLn#Z zDaM5ey1e>vkFy{$^J9q%&+?v&R@i2yao7SA7jH)0J)(R%yMdkOtK{t;3`z?3{^5?& z%#Wlnsr2a>=Chs}hn|OJBl4dI%VxGY1@9%+`ZuHwE{_QQG#NYt zDdS~7FRNKg>xB=&16jM=|K{ibGx84U)KPyXGGzTNN=@z0Anv zhD@j6TjVcDvY6*eYZvng&A#XShb1TmE&F5tEA7Cm&DIQOM5bpO_D}EkYKEhu<=1TX z>alauEZUzu_H;y?p(-$N^WZ+->^{B=wag+%7aw?5Wl1{4dOt(H^>J@Nyy2e`X2CLc z|5-l{(lb`qlh~drZa+)a&>-}Yo=XKzSSys(? z3hs5yDRuwR?;ppWxZ84TA440@DQe%pXm8X+L@}|O$f%Ehc-_w1X6KGc^egeMHmODl zy>j{DD}N1@oC&PFH@$DdmcY2Y#->W#ZJD|+;*UOZt#$IB@GR*r^bWm?lRvQ&G+!Ey z){Z8+S+2Y~yBwxw{3?s9w43IqCn;AKfH(5O>jey3go!Den(E>iVTnf*##OXgZZc?6 zh_x<2OIHW}%;i^^DJn#^kIW5%xSwu=ZCS2?mgU@k)zq8KK z8SPI(u_B@c-z3}yvM%rzaE6_IjsG+2>+{38WTE**J+n#LxRX%}QiDyre&fU7d|Jc! zK`EgN^4aI#rj^_n>*K(}mLKl^*i;L};yl6c&O2(qUM~|WHGB(zXG}h&=_^u^&9?Lp zPoF0UmT6MYZw+q$#TBD#$eisTKe5U5r;M3UI=*O?DW$BEkS432h-_5D_xts~wcv-O zy_`6S@d&&v3WvrtF3^O!#LuDLyB=n)=?8^LKjA03y_G)Z@<*fE>R%4}9leJr$@)xAf6yzx*yt)G^=5>?t4yZuwLrji?i8L+~#J}vHs$@ zrjwXOpO&hxldh951FVv?%#=P(^hM>US>6Wv+v1x&v8u?i#~xK6nm>- z3a?JfFD6fodl&*U1p;2Z)Qk)XS$#b=__@jB*6mf*@*0)%SNLKa_rdo%*YRX~}HMW5T9e{{Oj{)4nNe+zCH;&=CLN0mJ|0 zV)k;jbaJ%&@c+{>@B91vQBE}8JD}L3p`JlG&+;mMPUk$Oc${vO_v*U_hbL0s^x0d0 z8mJ_wpzm=$jmS@ecRwK;FCF=rEBDtIXOn$jSI(OT(vGj76aaoDh9aiP-tuNb^s`a0m?D2QeL&cLAD%`t9*z@b&b|+OIuXz3TWU9>U z->nUyQ7#f=lD^N;&v>x&r7&#Jy;L-rHR@nvl{9+HuzEZPUm zxE%t}e|n;;mZ@?nM5>Jo8Y}-YgTpu7OzM^8SO5JFU2h!}$MXb^LU2!TCqV-Q2*LF} z1Pku&!QEXhNFaESK+q#3xExN^@L)<@GMlG~-->sT8w3ikX9(6~g_U+pKm}A45 zBT>P!9eJH_kEO!R$ivOY_d=7MVsv66ON=G?%{kn$ z-kdGP$Qh6Et>q9j(@;jdZGs2PnI-S5r31e!ssE$ERUHtp1kHXq90On@)iX?KurbeS zh%zvx$=>WHEwfqj`W0kkOOeV&QLY>}%>9O4S4D4^ZRFd#>X4{Ub6aeBFSdBNc`KM} zKNrT2aF?qbMDJwiY#fsIXo0?%DQ27@>IY9mQ86Q<8-&m`AIEK@N;KNhI52fiFHN0Xrle-Cy51ps=8-?|Q_xYnvz_4qfB@h9mZ~ z0k1u^M-KXg@P;hWUxzL}fekn|!V5zh`h`Sb<12Q^Uv!JlU1@{?Md;0RFk=k2Yt#|p zU(ds)yBLmv@l+butanN!uv+-_JJ=J_Fch5e@DI$XfuU%pf}NYY>SUsb-4>}sAHT7 zZ}4JBkAEqlhUE|jWTT_<(I?(-aAKPh(${ioSpDM^u@2y5^MBPaHeaZorN2Wu>uHW? zw8%`9z~}q*cFl~d+S6b2r)k6)F=f7-6>aO96=i2ukDyJPNF9uby!h90NB-9V&7Z0Y z{w&W5c<-~mo{3X-CiMJq^JgO%(E1te=KqEO#4GnR+Qna*04Q%oTfVmR4k7l{@ta>s zEi4RwX7a?Z78XNZ{ATIYax?E4<04NDjP4m;T=P!q86)?rxvYnAlQVV?PxJtbVW0HF zD3KcY$)9)aH-RM{AGiSo0F}+DO2j(^0lvlmDgjDhKmG@@e$D$|^7bqEe|5R=ldYZ; zkmkM9GlsA)J=R8WqkCz5i{9rZciQ*H&$*pBJqxv$pdy$cew*QQDKWmq#B*ud+YpTB z+dx$}>HgOS8^6s6{)&>llRn)5FvN~SH_z~G4_F)sw+qe9MJ=q+UVQF9bGp#Uo$8{7@%573Nk z1k5{o*jUie+M`$ z#O(a>!5Eo*1u+?!;p*z-Jh7_kH{{hJ#~IKWPu`+$fKY>5+b{z;62=(H1uGjG8nkp* z(B`g}MTp8sR19LXm~=VfosN&>-V2W6R~~(GF8t0|-KJ9mAanZ_@Bo;QOb!_q5YY&O z3JkD7PP0zapxGGPA}S$UpW-5ey6|ze*;wbL@i{+E5qewc;~!Onhah(IPpE2{P*R!A0y|D#PG zH;8gq7<}T#ZFZ5aAp5r%&+~-b{lsB`i{M1b% zOMDC@TS^*KnL;XLxT|oz-&J~htU{tg^*4Le?5*>yjm)BI{%zJa#Qvh)&wY_QMuD{^UBAei`OmT+E&%|A|Z<&=^iDDeX&CL<43rwOqFyT2xg?pM7k zVzN{_a2D09k2=~NBQq8;MZ0q**V_s${Tv0bJx$^0H8@a6JB3t%hlJ@3^i=nMq;sC6 z34F>b#M7}Gw5T(bZZS~*@6p7;4@w!BeVNs_Tui_ckuH=Fvv>;|?Gyb&nZ+_}p)>V% zV#9Ry)+^@&*_a}ZR;hdN6Yb<{8koGgr?Wu>K(PC8B0<-Fr@Iw%6I?|usYXs?7Pp#F z!cj+=n`l>`a$I|lNm1V)Z|D8md@g6bfKQEZ{F96oBu|dbM9I6V&a%+&UOr_2d;dQh zHo4-H$Zy&gJSyAD{fa|xC0bh0Hx-}?1@36BtC@57x+U%gt;?^fT1}ap^%Iuj9RwWF|qQ9Ce*om)vOq2gQ<{#v_ z&kW@2Blw-6u=W~bY%!dCx!tV^iv8Av29TnRqE%rOUg8!m6|&~^f3c&Uo<=UvP>>o= z{0c6YEPL7eNg5PKn}pDVGhRR}Z`*h>Pe1>oy@~(pfWWk87da`dt+V>o9(T>m(divB zk$e3fo2XbyczTG$QFCQc{pX@^-syjhYW@zaS-)K@M(7I7@GII7YO`J>M>9W{3?j zSCWj8KxCp-9|r|OzNMH~1b-TQRo46Ag#Axhzbq)A*e9v#Q6>SXJ+%HlWejaz>!oA- zz|(dDKxWR>fu-5LREE>YMRZB-*n6mau{5sKbMQX50ri0%UN?c15oRL5SG(oCza^vf z`{V`=O=Cc9QJ;(~wtimSb{o+j-7H*oas1)paGp%3K5v@A=(ZTnY3k~3g`J8j3h`qG zWlZ;v>b#4ksDWmc;5sEV@@3y?pXGZ)u9d6;ni2pu z76dT)Lcjk@Mh1LHkJo<%@n$G*kA%PP?qN4g`N)WNW8f#jV&UJHmorC)U;CIMY#pTRoEQOxvSk)|@y^$qjRid!ZQgZ+0Fne_5vb(`h39sd~-In?ngJ-XRt) zbl{b!9p5-T6{<9WE*gojvSHf8(2`Dk6F+q3Oc*{lXqdyexN7;E|1LMjFMH_Z_g1vl z=0~0N?5_3z+bODc#^#mdLoKvx)I{@lQGSA0yqG9EvY^#iV@`)x7$#&N6OUTF_tkw( zp@@G-ann?0e-}OalI&y4jgxg^{+(FaizhmsKW>`jIKG|tca@5A7!aL9Owz5$2mqVl}oVX|fg46`<>| zB$bocnU^xAuSnR#gJfBOb%_XPrK_Jyex4Pxhkgvnz?rjvv~HxN@~eBV(A1h9#9fGBDM>)xH@ z8B3MclYQ6f!RgE^SEggW#wSrJC&e9I)khNpudvk8G2g?@(v85|lpPYUz?el!`|o_N zatYw)sKM2MqKV2HnLrHS-y3B2Ba|whcM?TuR)8YvS9~IZ0AUZ#hJpc4JZ3(gH+MaS z@f84QVfXR*@z=-&C`rz^J z7b3%Tpn(&0O{C`B_ye4VQ)D`$#!jE#bhBcb+10_#|I*KLm~1z|v34<|E6LRXq`02L z!K8V`f5W7I@CJWWwP9_sIK(5nifQ-syY>|Wk;QD%D-4~r2*EfxFm3nBM7Ms_FiXjh z3zPo4E?YLQPev~aIF&wC#)k|Jr&V*UpmW0Sd+DUbf!N-zPKM)&)%!mI1eG1(li9~Z zbYqQEQOd#N^PX=hxRY{A$r<9Yq;oh~? zl%CTL9!q8mRz4h3>)%bA}d=5ZuNR5C}o41MBM3*I{)2S z$azoa6^iTs4D?e#lM*)ZmxeL&72GW%Q^p5Y0nSfg2#OJ*XXas;tBj$M2mwg z4Ar@BH#2EkgI&wG=H-^Q$TMBKel7b(Blg*W z-(-k>`=&>#6K!+};%6&*aa=8(+{jR_M@nMfDw=ahVS6Z>w=}k&pa+%5wsAzh|; z#6^9e>J@Q9+lh?JJ2k9nbmv}is&4Sm@M)mVJ)(@%aq=?#wCnv@+){tC%YbCz1N0^_ zV`;Z{iCMIUr)@&2?OS}tZT4Z%2HJVz7^zn?_P*9RHOuhKq^}}a&Ot_n1xtRhLZILrk3OBmi)PPba99Ix{<*OiM^<}NZZ z&>23rBar7NuaNGCZxP(Qyt8W|gb zwT-*wh2M>)cVJE>eVJg#{(uQu3;smt5|i~ywsdUgW?;(AW#P3{{BXRv;Y?rV;-G}{xw7dCL%DGmZ-8`bpkS|Lq;=@DL>)iK3G+!;DX+sSA~d9(&pr<#451yV$;+MN*QOq78X&G=whPA z26laSS7}x|k_(NGpGVhAqoz7%GBP6l+)@A;)5Gp>_PSNSlm;8MKY_HL(rju(#bX4s zk8y6x-;J;}P$O!|msz}bhxW}itRt-}bM{DiWW_~1gLc*o6NlZo2c&J+am0j~lnjC$ z7J`^6arF0WMuGo?iP)%)=RQSMZ75NRFMCYCDhhksY#?^eId+;t@-tME*refbgQ0+c z@JBLgzXoGp50fxV72w4$lWvur@Fin!AcP+kcZ>WW}*nbbftubSa4*UyeT#qb)IsZ69y2 zK0e;Qdt~z%uc$K6+`N}S_i{qIsTg z7pEXt;@Tj1>`A1kH@|NG?^EY3_=NaaZQ=6|H;pwX6pd>JL$gGGBGzY(HCGY@`j=4i z#r);Zs3nMlsX+;n948b09w&Vz6i67&3P z6UpHnM$0mND_{jE2eHaChVUE(&gg!gN`YFLAv0TzJ8DavfZaWHEbFK z5KGjmO(KN5)K$axM6jDCRh~_C-KrCkWq)NUtRt7$Vde}8iJ>MB=f?^9*%p+bAsx$0 zcF-t!a$CCclmUJPJ|a*vWWMsJW=Kd(I+JurqMQohU*af;aOoE7*(+t4FPHSyzngPN zWEZIza{I$Opcv*XdOnnVELyK=-&H9cGs-u}Xj*_m$l%;EBRu|KcIcOf-r zC9^;g8L{_I2~KLn2~%0Z){P0B<7%BT z&dnwmo9U-MuCd5(7CvgGtr%Y;b4slrOZ zbsA)PS0& zk&Aw_;>V0wlg;W?Z&J=m%GhxwgPeB@)1^vbZp)C?iV3J4#^Nh6#(E~%aNi;a5ul#imDeuj=w;SWkcNZ3264{FA#<=gH`8t3P(CyIpq z(h4e+!sZ_a4>g8<$B2?#RSp4TXlzOj9p6Vaoc9x@UxK*u_8AI`&ditf@zs52>c4a8 z%l~m{wpdSYe4Vr70NB){_BMW<-nMl3Cis3xo;!-%_{|@gL&BNBarq;GVhvDMKZK%QJQO)TJg;!(T(=G0 zwm-TwNTF`JzCJEQ%%DJz3*7dF4~sZ6F?V1QNQ+_Ur1?4RUW_F#-DCHF>mRHZ;i_S; zJ;}MGUpS<28{73h*DJ>r0Mp%_WY^be=(T?_lq^Ty;c1mT~`wQw~L97M!lS2(s-D4n8Oiy)iq3S7DLCTPKDMiMjm$5w;7#u+x8?{ z{FGf#)_Q@I7h(P!fS?Gfm;;vdvht=GNz&1|e}wFHiipD!3Ol~)3&i!_&lw4?YQBzy zpV!*hs$lnu_anp8+Y&*Syjn*xEU273bm6kE3s3mx=VY{ z&_~8L+4G3Lbm;7d>@I;;{GLW*1;%BU1A&!D08W+b+(DWl^ls7ZH$rn*5%$6bC@Q_R^t7XN+l7~{7w%jBFrnF{^bB61m{ACG33&X;_h zYY&y5?#GgE68$Oq?Cs)S@Q9K|(AsV$MMdRBV?Vc#s_FJLS09_x4C4^ClJHwkoJ|Vj zwW4~n9jw##V*afaJnETsgPXuh;X~j*%_@1%=9vwPEcW3}!R(N#VUYi%!!xvaoVM+t zsH}4C701Z8Gd~qMlUm;Q8N#{TJ3g7tFY))w-*M2At^43YSnu~e6~LjR+NS~>)KYD( zYz6&gqb%vuP!MBMW8H@7^^j0W2hx#1YU{jIBJ_7d$=Nl@pS9(*@#M5WySaXT6RaY5 z4oPY{REx)oMh9Yl)24x%bw2vJ8a;R+tCCORu3X&Pe&1(m&C>IBP9?6ELsau5Z;vsS zHw&kyRDRq~WTA0Z0ADKbKITWS-f_{8)H zc_$g0&>(vPr<3MD-H80gFnQ>IIoEg+TZ=&C)q3FaId4-|-BSv`NwbFZjuzBurv+90 zWA}`f)gOr~EOq)s@#In2^5cQO)mh!S2b0y*S%(oyJoh2E8b4X0!YwR>`6)K)F*v_u zxHw6ULAsaS`H{U>r)ZQBr%Gj@Wf9}vgXvlBg*B~|Nkvz=R-bWjm*q1zzEvnn%-6%SD?R_U7$ffrp6IS}A6p+hI}by)bBC5p z!^C_e3aYGd@@CU|d&7X4Z?Q{cwgZ|*uohxvvT2^}l6UtPx(Cv%tVmnF?Tf)p*pBn%;Fe|E5yiN29NL908!S+ zXyiK{far&1+xuUPQSYWGXoW4ZH|^HFTC*_czj$@}t(=B{PabfYcL^DP`Gm!|R;~#8 z8o0&cO4w7%4TOKeCJL&drv37TLwbRpO6~(IUMW>-6{Ro^YykLf(Xd$ohYiLTzB* zLx}Hsw_C8DE^G&iV9Ce&d0)TSL!Uc`E45~F_^M;dd6Z`p3 z0xI_D@2X0J{u$`$X|b*H=w2>Z;Elu&J1a+B#>J?VM?DH=H@kQSs`)NmlKv_t{kRO3 z{=SaKHlDjlwl{i7ac{pDfVif8XVpLY_>FRBwB5ed!#TF$ejcYnS^HV16OUf5!Rp- zZKpt^M%JIziSE~(K?7%IEc1c{Ur~to^Qx(f8inG0{T~5J$C41J&U-0$UOv4bCj}>C zryv>Q>#YmZ+d@>j$m17`NK%%ech~c9d^TrP|IoXYgbUb7kl$dY`wdygWJp zDZb>A!D^kL3CsG<_BR^;S-Msyq0l=pQW=Q>^?G{y+v`bom6&7LW<=r)K2oJj4S3f& zlj@T!1yTjqE9m<#W!}bgC}Bw9XOnPQ1(ji{^^VTqmVa@>T>Dm5AK5WWS|MEMQEc|J zf?KAZtZcjh$AQIkS7EZXSyxCE(X7{)d0#**Dlb*QS+I2e8ZfD>8AK+FVKRgotUjqc$+#7{9L-{;N#(H1m`>M#;Un#UjF}{9fUz7X zB9ebmJ$FPs731{RJuHDwa+4rs(v7Sn0P} z@{23OX(9Z`r~W9NWp0=|k;kpu2wchW*I%KF3ep$)QjAiI zLBt`qj2!NyS7i=uJ8TFE^xO{atMEVKBRZbW+p>7hl@-yLq(ty(iBBu2!t#?b<~!CF z{XVQMPAHjfG*9`14FFCGpxH%H+!JBI4n+#=yMT$A(6pUv_LdhH6Q zMCe~mdP>gh?v{`YJO#WK+j1sjp6tB!k9D6bj>__!S@p|0=T8g)q8n9sB33?0NXevo zSpFYDlHIw;c6}nF-yKB><~SmB7eTMUIRiNvJGmJ<#%V9(Ea>*tl#S$r_Sjv0u%aE{ zv$CCd-fn+TA%-pA$#$Ou83DBf51(ka^gbsEclsy5*18tZA=9(&2U4+C8#94VfoZvn z*Ktuo7jLGyEyvWD1>Ud@ID0?c5nkYd{v+TVeY(rJyqfOB^wuti&UqGlv2)<|IBhIskC3##y7y&YB?vJCToQzX z&v7!pxF6tHV1!r$?ytIX9=a_s7|(E^MC~0+2wYfF7z}e((H?MbxMB9Rkv3$1f^&y) zeu%?|y}qK+QC08;!{UOA3`yug2cuKH2YqS#(x!GVyg}0i7T#n^BSG}TA9B%nJ3Tw4 zd*3r&J*N0$fS@GO6aiL%!%^(cKOnCkBvT$HXN zb-PevH1{J#Bq7WL5b#0OJ{WM1xxw&su#L68MAG>(AhV3IG=cQ=7#*1M#nA=%g&`t^ zfVDEt;+dN%KS1+@?)}UB`e7T5U5V#IYA5`XN<8zaF%jb|J*E05dPzTBxz@G&cSF8Stahy5B^DU z$*!0Bs&KQq5wIhI(9`2cZSMvJ5C>4--xOcAW44ps^UI7ZG+C*ZB1xG9r3mwc3! zoIb|2(2?rDp|vzuE(nZG1nPQ7&zlqLJZWI_`gsJq)1yq+@1PykZbF;f4zBZ_ zosern82nk2ZWFk52>o1M;wl+>74R5P+Ta=UCT z0gp4ZWX@}ML~wpo@?~$fzoUf&?Y<}NC;e;-K(r~&&R+gLpBf98IlT)}l>3L!UHjLW zsCK(erBHA!JCZRuvt(lA!KD(;vi>I|yRb1}7J~S;5ZMT;mt^z}?1-sF{=gWEzMfmb zH)gTs$(dMrBl3O2f~@4mdU{(7M%N8aIs&lhr-fZxJ~3U0*Y0)n&oU)VW$qocN)L$2-D1d5`|&k3y48u5`I4IJD)RU!-6*G9#Viw z^AAsh5X{w<{SbJwLn7ok!RRu$iv*!FuJ@j{|FU4WR)coNJfhbBxLCYJb~bt%1MPNk z0NcGt_$@pRh^8T`5TcRv)Y=BJ#SBU-;|8A$Aku< zL(0_Lkpo`e@2T_z88H5_ah5<8rjD(z#xs?!}!suZ) zpz9j}@AieR^8=1`w~f5We-bheVddo_bPqyTz>@<&mw@VDx&T+erH&&>^lb>Cs0j)k ziiU9OfY9ujG$An{-Y<~?8!NZvMs;G-pS3ts*x>!Y9JGG@eA?+iWh2 zx?&fEnG+&YCv_U zjc~^AlKr-McmMN|$|*h$j5`0idx5NryT<*ue{?nWUiILZ1Qs}D$+XtB5)QTMDS2@& z=7D^06!uu@zS@8E@}efrKNp$x4y^A8OS+wXW+L8~E6YlYeOJel>(8kp|8&(Q#FOw!N8T?%L7F8L^Y zg*fjVM@lg`ri%kNvsz`L=y=-qP#Z3!dH*pJX+|}IX7%*Qi(&R~Cv<)8cmVaBf~*t* zOXN|E(MSf$r(mjwqWxwmUg7o?a^x(zbWiU?^Xu#LqeJf%1e4?SvG?-rK?tK3C_XPF zb!tD`Hx~?UJl{!5L=xdY<={`b+rZ8#4xrPeLMOi`i$H(_sKY0fxW4*8g$LT!n1O{m zuWQ6O>~xKNTy&&2sB=HyZ_Je*86+lV-wP=s>4Na<23*F40b+m?awzE}_%tN>H;_aT za(%vW0PUE992Wu?6i`WOk7!R3y8FJ5c!{?d&#sSHNrmVWMG0izhpeP3w{zm~ zjk<=Hc$EJr_cd|QVXKc>E3euAO~U^-2wW=y;uxaNz{oskD>|{6={oYlAxJFpikq*? z-}DO@c;{a*(hevCLJd&)VC0i|C{P$pOE1h#2MWqbQ5a+xRPVMAzRbKuh?Fd||%QjAw@EktP+%k>x z4$B1wKvb*DwR!ZEka;pPwzSeg1QRLG8SJyD%3ko zS^yR%9e*hS1=zfS^Div`22+mFlMv*??yClY%meuFq+vEN{QjTD`-9&gj6+=j&W8nv zIq)uP>v&&Tyh`XFq!M(0{qE&lS=d<-DCcnY{?tR}+taOt8CLQA=BwI<_q_V%$zY() z6G!p=(opSe76lz(B^_AA*%>xoGQ*nIZiL#E?5wuiP5yl7uF5d}ijJI7pwE9au1Ke< z_Hqxg@N;W_BDsh!G2w^E9mwM1e#)v46sdO$28$uVY8MQWo!b)qz;$wH-*y|;tUf;xrGQfpKKrT0;x1qk}U=%6f*@KHraKMd-r)gaN#*> zlm=A|gx^-gbf`|@?uNy^9m(|nmRxW&Q0^QnL%I)RfF-u46?KHQIk(cmFKsqyEGr4Nqh5RGPM|VXKvNfORMB zkLS+hwb&Ms^#7~Rf}g?Rd+Xl&`N8%YOoyTTtV#)Tb`u|}IVtkjWBrPb+;A6{ji}Ea z7tKY4M~6q2JYH@po#kC|ZHgz~URkkyaTfif#yLpv<{baB+EQ|pu%ApJWv$tXBS86d zkgPTz>(z`s7#)1cbV>I0AuUUUFMYoeOa3!Op57{DxHFEa#rj{L^2foO8*iuv6mj@( zqnj^|PN+EyQDxUAZF68NPrSV9-z0y+VtazK88le}NVvKoHuZJs&5Fe|+I9fFJelZj zq^mO;!22C2p~c=go(SM0K+$5x^?{5Wle^8h%c9UV1locUOQxJBm^KoLWHWs_V zE6%~|72f#8lCkTHQ|uCPbIlmqY2vPBGTYP^iQgTP8D=UxBZZS zA0#oZ!FDw-tW+{_2I3mC2g8=q9FlSUPDg`$PTvd8hix=NOlP!xrW}KH^ncC-(4RFD zU6GRvtlwmx;2T9FKJ#l*LOe!jR85@j=uQ#22u50WSaH>Mdv}EXJ&fl<@T=k zd*jkrANn_mVhWN_G~Uruv*wn=#WrWU0#9yyG4t;&MK#@N3|=iEjLv~vlA!@moSl;6 zECYW~xLEX+{9}yZ+QgqS@x%XoS%b7LPulm?Whzlm-$mc<`j8rnH1H6!=(A{>oqo(a zI=qumPuhM>S~e=(FRvK9O8?+rfPAy1{x9@f1N)=L+E+}#m@K9z-DxKs@6Eeq~va@;c@*er>8peM@X%7zTgFk~lr0_YaCa$!!V=)B2XT;%vuEOhQ zL&h1xQu%8W;oC5a`Y+N0CR#Fq(!mz;MJRnsg*W7Zjx> zM`Qg>!`FLofBWN&`llN;=6MyCE2qSJU5lvTsEhc;p7v$7yrWD_tD*~)lXXPfPyhT^ zpj(0-t6uAM_6%m3uS)wteLo>+~ zzEbH^>1Mrw9ZJ3?2MG6FB)~=h*b|Tb^ash z;WBJBVCIYN?SJZZV_MmfDa(veM#W)rFLHid;A3iQtpei+Ek)?_9 zA!4G%f4aEQcJ2Hz6w0LbVSlt^Ki~UI>{)j1kGuX_5*J9Y66^+UW`mqwI?>8+5>h~6 zo(8YFikz7@ga6oJa?<@eL|F_dN&$tkz3-$y97D^D6pt!=#d`(p-icsKUpRF*+$VgAXRh*l_`CC% zDrDWlY&OLwQ}>3pgO5R%vSMhF(TXu0?x%XW)#k@4`I7Q>SD0PdIz)fg!8d`imEJKi zYT^{uf+4L0gDc?z7uVNdN?MzqFeN=ON0&Ww2zxgNgETt@fJRzpQ-DZ%W-~IuzDH5> zd?I2(7B&5_EpS?|)({vU{}3vaeD7aD_o)T6&qZ4xh&=_US2Z2s{tF zyt!XWc5x4Qtj4nzY6Y=Q{MGgHlzBM50I|FC@gjWs4&WLSp)eBQ%4IV!4}KetP(i(i zI6rJuKtYz(8KXfDji66$1c2826yGils6TIFknQ@>aU*xHg1*6eAJ3ER>CC=?f_YB+ z^+DHdw8cAB5B5-q&65cZ3VGqrM``0OCAC|7A`#ue)rj1_K)-`Lr0FFqPP_;DT3v}a z?i$6bSb&kvZmWYU^r~l|RB+PWXi(5?19Y}BLCoF|c%7Ab8&FLs{-*-^1ae`g50XYE z<^MgNT090IGRbB{7#|Dx5V>J6_nSyX5#fxWQ*ggUe2=pznnoSSFn0Ea+Vq#lFYo8*4Ke+Ye=KO z{?+LNR{y#Os26*9{2e#A@qWl857RikyWo;m@6*s8u`sxquRc0Wh(mQOL-?J5uKE{R z<)+$vE#~^W*&lxN=;*c-mxY9VjCYQp8EbZeoM`Sclx=}@;PBJ>@=o25l5$XkD>-BN z7D)HdROe|I%oSz_2;0#+WYRsYQtN{hKTqCIn?hX3KXIsdCsCmOMNxTJoZb`WRXpTo zCYfc{&%O)+tk{YnL1UUS)l$|C#YRb$E$b!FGKE!d0rW16=hh=Uts9g}s;Kf5^^jFg z5J>sfLMh^%{!L|e%AT?4DYW1G>9Kg(Epb>w6I4_l>NIz6sdMoVwi9P?b48^G2%>;k z?AGhd0c)5z?#MDb7jD%!Om|CkiJ~^kyBQ7!*3UF|jT?TKnCdyT+0R{K=(Zcpnbegx z7cf~p%05uvfhJ4CO1SRf8;QBOCG(XgIce?|srC~?Q#+NWZPV35M#KP*ktRnxb6%s4 z%R^JO@k%1G~dM)hl?)Bb~ zO3VsQgbp@&?V^-SwmGJn0$x7>}>Wta6*(+!$fWMWZ7~_v_EO@7>M5AyME#_3UQ1yo!f-n)DlFtU)>>jvZQgIko8JY?A{vV4CIQy?w@P2ygo@Ij$1g7lHp z8!#K@qxA=$?U8L}qJwQ|?T+y%)caJY0T||C-IH1bcUrheNR|mUZ{`Jqa?(61 z&6mJf?^Y%ur6Y_TUy-VTEzTL0rt#rNlwVzr=PNBzaXev^U-O4M=F}c}+^ReE#^##K zTW5O__kPl7BKCh8=Z8y^?WR98{ znZ5kf&$4fB$G&@4#pHULt*4fF&3#j4V1;mh_Q&wR>A3?s*jKCR$%SRRu04$5f)BxQ zM;LU1fQhkgb2Ix9&mF$*O<^W%flpT68ShqVY^rz+>VtNtLmoe@EY@ycE+ht{bqkpp zXe_yi!EtF`)oF#O^CqaPyzVf~25F>y{;$fcc&Nd+cw&$S;D-1cn@vMNW~>7=kYf z!A}ptmxSO$pJVI_!A}dpzZQZYAA)}~1RouOe>()96@vdD1V14JzdHnfX9(UKf{zKo zSBBtchu~{M@B>2d4I%i+A^64+d~66_3c+(B_@)s2zz}>}2>yW(yk>7VKGdFdA$T?f zuMffNL-6q-cvA>IF$5pFo}3(lH-z9*L-3*N$>|~Z^bmYT2>!MZybyxFKLkHF1RocI zUl@WP7J^?Kf=>v+uL!{p55cbw!Sf;bO(FRELhwZ)_@q$&ofmY{A@E%3=irV~?gm8d z*ZGx+=YNx^QN~<=Z+27$Tp>&rr2>r#{2Ox$VGO>38Ui0d7(+n7OW-<$(ZvHF5V#s) zbm7381TI4uT{p0cz<{}EwywZL2g{~BR*jX(y0 zUql#9Kafh`?Fgf32NDVVEW&8I0X>1AMi@;qpdoM`!f29#rcQ)^j|KY{BI9!=yYa5cgPB0qu45FSJ1C-7c`?;`RO_(O#6Ch`;b9fXsK z`~?0Z!YM?40{JxY6O?p9;Va~v)&c_H@|4=Ca9nh#53HyY{yc; zIkg7LYV5IsXKL%O5;f90pasEm$5FVpdd7SXKR_=F>R4006MQ$q1*gmiriT1Pb`+0J z<7EUf;y$>~DRKLu=uhAh?&qB;j~E1(%;VgmD*tkJRI%!JQ%iuPWJ3$6_}LMn<|Kxxe~3xqU@Lc4%)K1;U4dXa=h4R6+2TJpa5_37F6?4Sx>7z z4^^#VyC7b|+Twm`9Wa5F^q3hG5gN-m=a(AJxj)rn^YSHzaDi!%hX_h#(I)0W$DmG4 zc?OfpDV1iIw=hsN`9ViydAN~NBF$n7^hfgJPjHL4C%7lM9RIFnb}(;f6WsVEM)`ML zMYsdN4uH4?Y;q;{C^tV#{DPY|-zpx+7Q}jC+l99wB8UYBgK|i;8;oX!jslaVJjpghEIp2lh+_nq9f~Mq1?h`CXnroFNVKc>~N;z*B zr}-Zw&$`ZP$WDWzLbmv*h9p`=ngBk60$!StMd=b+pITGctB`*z2m;LtNo6Q!ys(#s z@TNk+Rbyub^*Op;BQU@&Fd%q7A@vDG%O}Co(u+XbI{Gd39#FIowD>Nhfdmt@RDy#y zhy+0b(&9e3Are8`j*%g(B-Pjl2tFxIsbpV)`%dT^20Jxg*we+I8@T7#UsX688D|p% z-zB`^elSEiLSX8YO2JiaZ*h9ln%R52&Fs%264xW2i^#9^-E~DlqG8QEdvBA&z>7_- zHIsQm^41a1N^*I)rh;r7(X=*jqFEEb2V;V#z>p!0iomN+3=46z8=?a1;kFc^0z}p} zz?fa9*^)QVP2G0joK)X2KqbV3`5a=qY4Kt+@lI(rj0Wang)R0l-FL|YN$Cw%txUmP zYip{njksj2OX6y#RV>e`UDfz2#q@ZF*ANa$2 zNKvEf&DH9d-XhEzL}@35F#Euu-t`QCDJlR2lUIN zp5XZ>^#iPBkCh$+<7ut&Nu%T#X*OKDyzCp^Vy``#Q_9VvLnA*yO5&yTKs$~pRN^2V z=@l(j3ZfkelAe_Mr&>zY*q@+jd5CoFI;B8x=Yqb#7C#Kk7RDl=**r9G6d=3~XI(XL z8F&Z2f+5lBd5=)Gif66jWobBRUGG;UyNs3k@oA zx?@^bBw5;qJ<_XPb2!SgNe`nPs1^;0Q=r~Ms-ETP;RS|dix{wo4T5Kfs*-tz3{bB; z(hc`|U}-_PCn>FpB1cGgbv*3-Q z_6WRQ;CEi;GKv%eO!Ci_{sMHI6-8SkACZ0omwrLqhqL&K=X7B>B*7HgTk)J;o!3`9 z7pGnc?pH}MZ5T~uyjo!@A{=M^?q6adW1UC}%fkitFY%5$c9-CO4zEPGQp()FM5?Bm z!V`k6(pV?>PKL?xR$4;wS{TUI%CuaCmMoP_CM*;*(n=zM{JVHJxq!^VF+{0mpT7gN zTppQPK9kXW!1&!ev3<2op7B8rl4bKMRWseOMP{RSRaKN6#rXnSPVt&uRgP;=#OO8E zvL4(>AlQ6Rrd%7T@o5V;xtn9g-?2)q=DAx}(`CMJYZDd`~6~ z798c~8LNzS^Roq8gTP#sCkvi?2be*8AZQjp0pi*<+i;Id4Rp**RY#8#7Dh zMQAKarN!BH-SIUjNN@(Cc=t|}koUytRMl`w6=$j~d^gKhWwBKo>t?vMB?5C$P7o`c z=UC62RNj+0$*Dwh?3_xUq%DaZ28@D&8b4soZKlHo ztg5Y)!-B2Ft^EL!F3d4IA;+8t+2!}yy!)@D;v0l3_x}=r^a;;=$1{@2Uod$$+Irzn z3PRK=>*Qx5xQar-S%pt8`A; z4l@;hw@3P^SID1JRn5<~+Wx91vFOGO&%Fi^WoQQ|e_Mxw-7{#mM@u%|7`?_i`F81z zCQR?#gnXGhjj=L5F^xgLp8OJVy;H^(uxhWIktLQXhb$>|neMp3SrpM_~7e4Io+(6K7CGsM_<)A@oI#CNf^+CPy{A)W~8 zKH!AT1uwyyA26;u`)#UcfYaORoKtpb*jK!MP{-h|KuC742#VK4x}oq~Ur`gLW}PtL z;Dm&mC%9T5Mu;~Y(b#I>eRK`6$>4_lcki&!Q2PH6>SAc0d0GGFl`e1Bnb5_Ue6@3-3igqIwQs*Dtk2?kY>Af6`; zkY1WD#=WVKDuU~x<8vA<2-6#5h@AG-U`ZFn7Wu9q-=0{)>R{v%*y+)X zI9TPYt`}dwxc*=8MM4sABjw}d#C6d=E2OG;LTySfh*FRzumYf&K8Dp706JeT` zEY;t{dE+ue@*c8SCpL1*=dZV37i^Me3nOiT^nn+b84|K=`{kcbXG^Etg;ik+&chVk z2zR+vpLbZ4&oMdHTOkS2)i-iXg~i!%-BF{mG*g`NwnaI>n=Wp>&9dr1)I*-MNVD;@ z*{BF9AJ{6T9LrBmNV4qVK`cK`ns7pmnB}4hjY-bffEl{l>HF9w9+2PGa#b# z;wgx0ys3TbahKPwR~0F0-%)$Rxj#L`Z@8R*1tyNWPzy8^gDz-7U^=k%7chKarIZ7l zXUe^j3tHejyaDoU=Nt za=rtFGDCz*c^4b)X(!wxk_~6$9=tWyP12MWW1ZFJGgi&^#FG85^8K?sQw9m)A0uI_ z`xVl@&mF~$)sDyTl~N;?sfIONEc2ZoHQ7VrW0hd5GPYQlN{g>COirBT(MAg4)EF`7 z>6L1+7D`j^6m_mT$1(0_M00`R?WnQf5K9d16C7LIBr5UlzmXyCQ*6fVq+{)6o@q2y zu+}!Ef=@XoO>RL8x{PTDfL2?D*hbhGXLWm!g;^em<>CLJsTCI<7cDq_+VEi8AThP9T7 zgiY>oY8HOt5Zf-Ks#$+LTDMMm@q2W2xFR^|{tlW%!`%~f?PJ@1tns*i7eq>XMBpQ6 z%fE};oj?r4=;czH=|l`e*xcC+R`({_h;a<%L4&@1peWFID6| z1zCm@M&Oe;le;mp@T{yaeI>t4An1WoQ9Ghz3uV{Qt$O7%zzVjb?zG=ZPhkXi?#E?4 zm=lw5JVl`>YW2aC=~|X}CK!_-7N!{>BLIW1(f!nNN3-;17v{LcB(aWcta^4(L_%FfM3?a}H zdyj>=CJzOwX<9<`VvO?=5C!-6-jfz)I#_TT7ZgvBde~&nZaUod}nWP8iw&U>mJqRP^Ld2A#A(X-No2$LZwH#nL_TLBW7a*AreFO#+)QQ!0igR{^ zk?zIlv2}=i2U)B=!6^qMEren@9C`%O>;fr(8`|B^li0VD#y&HqbXHrf#dj)9<^>w< zATu0hrJ;+1$QMV#9_LQCuGNrp22zH<+C3c!r(;eTyQ7M%96=ArgWYl4Rpl^WNuPqj(U9eKk?((y+GK2C z4l_Q}LC-8Dm^av}3d120PI1i@J`4HWT3GkZ@3u7=OWwbE;kfx!DtD@rqaueTJZ&k>JixT5hCfVzu@c{)EV77R$Wp~Ri%&|#%Q{F{Yb?G%BDhJ76*5-HUwIKQ z=Mx3|Fg5N+bu@u*aF%5-3xIMp-Vv)TOr5|~&hcntAcIS^GG2T*%1>G$HA0A-c^kFp9hv z#ynf5-ZPgG7r-zzJfkEn%s*fJ3gR*uq6Ax^UI?EoiO|c;Dn0fXJ_h=Vo5!=IXGxSa z9)(HrKwK>qFDriOZ}{ZGBo|y#1E;^4)kpbv6-{*&CTcW}ErMri0bD_bbl?=4nth4j ziI}h$$Fr$6xKReZ&ZC@@IayV&Td^pG~eLH=tqDD3I#1y{9wtehx_t%9>kY5rfRCbZTHo`)5w5NdzG$OEBvG*Hn)O5*sMKEEO( zt$3K5ZxO%8@wUS%R?HW_P@lx<+0qB3`=K@mvncx=rQ=j~5)kEiRIzv*48nIJ%$BP8 zPbb88*Tv0)gWR`%E6IS8@8(*x2F5<71HWK<-CE_7_0W- zI=m=(exZH6RdJT3!njxxrxTPam)9ZfjUYJ-Z-{})Da{bm1g2bi;U*o7gM%#0a9mZv zIS$k(V=)o&GhCff3azAlt9VG_p{m(+k$v6=h%RZz0aiR8WU}>$l!Y@Q@syfKB;j!8 znRlNC5(#+%3?2fK8qlK>+>(Y<;#*gA5&o)!q#)dr0FaU)Gm!w?6RVMu53ahZ{1FpP z+xO5qihSP6wD2isq~CX_EOcq|oet8NV?5Uq6$UB~)?bPxTcWh~5!@4fv^oQCFA811 zdK4dAc0H(_Zw1BSlS}7?47BiMV&OQ~i?_lQaXmh}l$uXr2f$7GbPnfy@tY#Ka+sg! z0Ta;(u^1p`IlG|J45m1gbKVShu-%Wo0+S$qQzxi=`VPnK<>9I2GZ;;9CAtEv2ulry zZ(N_UXBWnRtoD(CBN~DUvpbL^>#D|L_(ql+_+pcNM#b2YC7!gvoLW3WoijfUatWo_ zOIT%#aoIeuJETU-K5VO3rJq8jVlTNvA3&}Q;A3&{aje6Otywr)24MW7y8kY zwq5u@L#HvR(2Uifmu6zsu2BJ+0;C|838Uv3QUz0)<2Ldj7~`-s0$L9ogL0m1#prdm zZ)Bf4sQOQ%wy8%{U)Rg^m!9AontGFOPyAy4MWR6K|#XL5ZS|XDxm#@bhmmNW#4B1Jxy9 zR6^UI*F%51d+9H-@ki_Lo1>t=@5rj9^M=CiXn;%SjZN&%L*zmsigb+cN0s{XOh$bM z^^9Y6mE=68s&RK$WPvdzXoocI?ZXaBkN-$Lo{(NR0($%d)#J@6>`r`a3rr4t{ZR-S z&JyY|5idPTu)h4K|D%CkQXP{pVmlTz(D46cpTk;zpoEhgI^_xJj-$PeiRyq(1HV@% zv~y7d=^;JZj|S3%8ldU5wDTKP11MUmDq0$fc0Y=ij-nY`_6#ANe@>;p{U{Oa{{J8t zTI$pmVj`&^SQ_}7>7j%|!{mn&w~P{NRp&`h@mVmwodECgi9YE*=o48_?pUY>o;gA^ z;M_l#`Vx+`IQg-V4KUe)?TEmjL%jy3SwZ$dyYI)Pm2|#@H_XASg`9W^<_f+`;XX<0 zY-Bj+Sw=7&hueZ9Odboe2pUHOK0hTx(L#u}(w||*QtWk%z#SV4g|WOL9iMkLbF4m7 z7x-rkK5uVeB)Wnm=*Mvb%|iTI zAV7nbWjd9^v``K&G*b@W0%Txh2#0Nj_@ugsSY|Q#l{!J1Kvh&X z+o5H4&@xA`j9;gAt+AyjJ(YDGB5QbaL4+fev!4iK7a7vQCcqOo@d*uEyh}&J$D&7o z4?L8CbF(czhAl3Jy4&Y6&Qm9f+M%Bvgw%>K!St;FArrK59vEUvH-(kr9!qJGD$gpw z1`b70?U)+SDi%BLgcb#HkzoZi%NCzzXp@15q3G8PWqC0$Z2!!75!Vgg1(=MG>-PO>wja)@8>X6=SfB z?~GP>jW4+_FbD84Lwy_edAmm4IwFVFwmI7v@rq{yQ+N%!oaa6yTcH;7VDP-)S�B zp1Rn+h~K`DA(BJ6c+w~jSIK+{L|T{`+~7+V84{%=!UxojM-s_|A$~g);kUzW*0nqm zb40G_MyfsBY^-zg23{-gAVU2tvRkM=+K}FD3X-N&kv<{Ky71Nh0KRbV7z7T>V9cm< zgbT_6EBmLL`AJlo+MAr>lj8)OFH@md7tWi^#wsGS*;PdXfFm9wPOJd4gyNYvD{K9> zSzzxufHSYlym(N0x{2;^>0z=DU7;!Q3NW$zBFT;%L1Q0ILrk!jq|0PX*BVFw2|S|> zwVH(xa%SVq2JIP*<8vIBMbjp+zxN4Bgv^7cmwZCxfpPY&RZ;zzqMVhdU|9As=yb)G$uQPgRMI7!6_lQTmMdLaxjlQOoi zXQ0e|_DJ|13t`^r4eRamXvSr_tmZ!cmh{k8CN+ zIRmLvbC)lV+&XaTa`!0Nl(3s;k8eU@s(;^tq1_hRcs{wX#cz1xsjin{%cf=33UL?)} zxb29e&X)qk1NZh@XBO1yQ9}+h)`ibA@WoYD+ZSx{5fXK%y^{e~Z()4E+8l=Xjd_H^ zInRB^IV&d0cY#@3O_kK2M$quf7XMO>gD0)P$7VGSu0T_-F&@P@xFQ$_qc$c%9K^u& zn&3%*9%)cxR{|4^UH20W{8N2sv%)GKq&`-BAtVyUp>6KMN9ZAejw1vhOQ}E0DF=D5 z27&vCNm^tOg!&tVC5ZPV`dLi}(O6d4=aT5B3ypr*fyv#0FZn_+I$1*kA5nWXG$#)V z#=I8ifpGbC!u&p_oB1xfl3(r-=N#FASD~C^V2hswwiXO*eh6$gSm&?chH^lty@3n| zN{E}52bpGF{{zu0@FHSeN7>?MNh~{xvCQ8ydkPfbPR-(@_JPyOi(|2SjmiO~8oO50 z73gdS%8Rvl1?e<2w+TIby*)|_8wS!vOXr8-p5w>7-IRe9a)4l0&!F;8Wom1$y!e!Pb&(buTi+3rwpurNPP^u|PCUIgpi7W-)p5qAaGG zyr0N3W_j#GHPH8Ymml&Z=me8LZx-gYDdiSZbN)0LQo2fO3OZ$8d=_pcvzTi0#tY(M zOG>RZ1&wB-;UQ1l?M5h4ws8dRd$Fc`V=+~-zw*j4oorT0wMA@@J&!-($GVmI%PnFx zu#jac+weGCT2ksPrn(J71dn!zkm7sDqtyr|UtX9zg;3luDT^tyBFA+b?**}JyT<^xM3hpS>ALQYg0sAU?gcublxB@yMb-+w#u%~Ic|JyPHQAfxaDnlP)k+m4znic5yOU&Lbd()e@6+zX zwQv7!TqXDK$`08!{&Ckbtn)%5GL+=UUF`!yceVSacW^sdSvL+hj333NcE7y^qnLAl zqVxO!U~0hQ>b{JSmD&0DNLG-RldU!t7B}PxrCqItY+x^khGC8gZGYO`_V+MVyidb< za)`a9;*(U=k`5v*YAyArEn+0D+i>DcU6z=u;l%kmIUd^Xw0aDGBhMadakWrqQ9hR| zKxiSK}a2^kN`ynVS z#DH`Q%YR|@Oo_(ni_YT7WT@0#3nnYxQ^P>x7|RJjwE>; zDko74z$Cc-oFEPZC|P_IWc?|MEXN@SAnOzlk+sgE9Fo82B_??isP0Y7hG}r62A|rK zcS!VX3BG6MRDi#)tfD_l{9@}MrxM1x9>#t1uam7E%b;H%_I^N?BHkju(rQc(Q3&iP zGjZrqC4=;bOgt*hGUQW;IE;e6Sv;@r(PU4kPyu3N*8i0nf|Xt;Mw5 zluojEun@NslcB+5HCRjWowJ%YS-Ui|2eDu~|u1Ic2{b&MBV@o`}vFP}=sTd&(}(q~ysJjc|`)>bIy z^rmuR4JUqG!cWw=Cs^tmvzP{@Jj+xgPRNhv#OqeESx}e8g|YJtdf-KVOl%`>G8pSx zTf_>p>vZ9}WJJnZbu>n>-3DZ{R@KD_kb+vpE#XFVj_i`Kh~6HZDP9-E zPe{i&E4bEgEcM@6>ZO$m_F9(bAtg(!_9?NJq%x&K4pR<64=tLGNzT{qH0YtGybH94 zIHFs594~rtBi7{B7UdA{$w-}9!cWyG*tgJQjz~y_ta6gr1T!b-g>sitco2F5vGxHR z=?*^SoIKSGJl3DZr93Nit{`540as7&%RK2OTR7=3?5w)1q-vr5yt6D7cU~*uFwjo- zXz$(-fqh&DqFRk!xO5C9^sFMxDC*o3u)`L$(d~Rz>@`7-kd_?4jMC2X;iJtEt-&nH z`Jo!zmybayBxZ?i7O|H1%v8ujIeECTj&~1kZ8bAxWOsTUFf*ehZm=0QxG_drTz!Hk z-v+`nm5Q&mHOuCOg40XlB3)jyn{k|-?kQjti>)SGV4875$JbWousjm7nC%IX${|bZ zk(t{QhD7{Zq%L_$hn7R$JlGS1XqaFk=BVW6D9r|4I&fho0-jTK*k%d}p>$FLUh zOLFb7>jND8`HDr~*jgKzk?d ztpp_5+i^Q?#{(%GdPqkq!my(%BJtZ#U&4rT4}w&}=EGcOa4?q{M&Bt^s$5kxmx+MN zN*H+r-i$J|Lc52!d7QI7V?M_J>g>XqyjTmN7iQ4+K>}5jWzz{_IXEisc@j4!fz`|` zNnkleBcwF(;^CBo9*bhSRB%vW|5V93vv8J!ujz3gNK5^BUaWC8GD@Xm4ecbWSRPnKwsqCw#tcsR zOz^~tb3Ab`3NVGL!oA60TNcnWTv>dl5-m)%h`YQKu^g&aU?8WyBH%s`#s>l4cM>it zK}plt=LJw+V4Q*$K%Q!F3oKn*Ak@J;3m-3czp1D$^?wE_44u6Rn6%;kK@sC~Qkf-b zzx^(JcM()e-(KXL9dYc=t4#L-s9s}%xxe~SBb0M?LV-H+=(A%0jYI77f6)+luuytn z+$VqhJ3w1+=fLwXLtlUoKsae_0e673(RX^VeCM9Qw3~3p z`j@0B_}m#i%S!eK!F}-?i}*FU#A6;cYT@(JcQ?>qU=V~JIY&H2s_8a6_a3;kk4&YME5MIN}`3M+yCTl)m90R%5@9d zXpRV3|K|_`vU$-DDPjgLUx`x;WQs#Jp$`0vj(*9I5=xrSXz~sT37rK=yAOBYwu)z^ z?X|ewLcFvoirklDf;U@_OKo4ly`$pstzAf&mV$eK{EDV!$?9r~Ui#zbWLAnCk*7Pj z6VI~ewb#kN5vD7gSXrRu#V|iiC^xq9Y^fIJaz0EmIH!~fBY>6h$_+-Ze4Eu17X@>W zqJk(T)gHm89G7EFO`Pa2IBOBxjV&x!vCy+y#~T%Hd)$L1aVA`5+@=fwq~oHeI1;Dl<=YT6aw6nmI<6u^hgBfChUr9x zjuVakUF!5ueW(D_!zi2{lF4DE{Tj~Iyf|0$ZU#??5*O&O#_-^t9ya753U23dRl@|V zYwwi6)Zl!vdqRo3S>2;x9_|Q}69m`M&8&p)P+%}!;I)r{#uPtHM=^pGK!3sp4Z z1|ESvoQm5Cw?qV%;cJ(jtVO&D5hfhz@P=W|wxJs{Fzru}hLSfGyqltMmB=d(g@h2| zuTXnlh%Q4^Su?xP%e5YZe5t?$eq}q|Je_?~+bF*XUN~&yR7&O0%^yJpNSJ^G)c={b zF)#<`ccplk1((u7UT&U%i=5&)b4n}cREDuTM~BmQ4HKmGKa%{BbmXul%chV22H`Z%X6t3bZKI1~rxP%H0P; z5E;Lf7w)bNLCp2bnY+IXK^U&d5A1H}M!;AgkCDcItSGzphf;o1W_MSIBHGpWNeeLH zo@dAa9SPF!AsX$*?bw`WzBZ)+gy320c_K9_DQ!87;~UkW>mvJ#m%Mbsc@{ z(F|u@;)&gO(3I!VjFc<&=eD)uD6nO)=lMJ^8dA@i8yNxL_=>u!|BY6915P{Lulfg3I*@vu@RK+wCkt&?oH>{^#8{F$+CTPN9Y zIj1`=P>guRK2SL*xN6v9Ja!l&U1_iYjJB>Od$i{Tvgy)R7qZ{acPdRtssyVhKAx3w zf#_Ol1T|Vr$B2Kk&XJ%U;E{C>#=+7=EDc`*yo~0RsG1isY-v(ES>R7F)w3?#y%zW$ z_nI-@zygJ?Qf7p1Prz)31e5iL$y2LX!v_r6Ob})dgI;v*Ul=lyktL3W;W!Pp8WM1$ zLMl!J`5)Ri&fVOL3<=QL=~yHYCvmCb5h?nHx^U0qqB))K;d{oP23eAx`2^S;4Sr{j z6?_-dwp}ErK}39t8YX3>Eb`tM&{L^v7~W%6fcT3rCvvuFDD{E|NNIf4TDtp_VBNt` z_icuju*Xqcbl2Uab}}F?45zPD(KVg*s=#Dt#$1$U845gC6?h>LxV4SE%8&*uEyN<} z$lzQ^s^Z&;UU3n=WIvt&auxS+BwS@5tD>7W*y3;0Cj^32_%Y=~kY2s6b{1Vrq7X)SDf$0Vz`!9&Ce0sLSO7;y- z2#UK42wA$gwvGm_@eB|tjoT)vXMpSucDulnH_;v@(_XleC*%4az6TXtO|0R3(r~D$ zt|sor)kJcR3QTKVb@Uh&*6j1}8isTZ=_=6&ee_rhv73TngjXtag2Rjryv;bKQVA#) z6GcB&Hw@;2WU2=bE9+D{OZu>t4*tb|#Z~`b!`D?*Tml%?dUfsJZ^0!5NVJ@dZSo|+ z9Xs4tdAr~&(-|vq!m`r4XY-`r1Ik$0b?I!}TZwRL5 zynuh#Bm3|OC28PCXfH#bpPLA2jq?8SCbDT+48tWLn4l(A3+{nG!<#Oc+YPo4fyf7; zB@F6BJc4LZzYy{rccgpHm%MvH1Mki}#=GYGGnNN6Mk%#q-P&u8VS%J8EVZ;UCDC&{ICGJ5xKeU-=Sy%BOQ@>iU<@5R^~MjCE(m z_9O*WiZ#a}s*XNaf4a@$Q@^1}2P$ z!NMiq!7yV3kN?;BDo0ThcjqJmTckA+<}|e^tFP+zZn=R-W~#wEE~o@52?4Zt zcaCAE=eMd7%ph75@OnE@g|Ufe8fJPDG|&-Hw+%e3iBtx{q(1t9J4fO@J5s4$c=5Oq zPMhbSsgwftn-P`HltYg~%~+nn zPSbW*Z4Oo?zU9v8KvN~Gqe7AMFrqFAYC}k@6>Ct^>f4P? zph}{t3s5)C%+x^L*zUSzT`i;kfkuK2HyLX}l+^^4A!1;|`fj$XyDFnYf=arz1|8Y0 zu4_;`fv9UiTu*&b#{`x1(iW5_&GeQB)QyZh1@@ouUysSgVa`gh+pMx2@| z?LuD~>+n1h`NUNZDQ1)`%xEhXsRx4U+U^|=)TmY`g6?>{h3Q@4!Sh7 zd+d|jcY?stO%VR0QK8uK1(o+Wsj;%E{FQ%AYVNghRk@)BAIkxfT>giOc~^#!2m-OS2eWop5N*Y z`<7N??pRB6PC8I=jGtr`&`Zo`?l}oGZKY|a8c-l%)ni?pRf& z64jB6f@Io~S~ql}Fx~S{m1WRUHDUJl|2CfhJ0gvfskAz(m#O&#wVtM)-QFXYP!)!w zBh4g&p+U_iLS{I9ClqRLp-MqNKR%!6R$5m+!FA0KsGW86@<39(JE>3=XRPVcSYmfd z(QiJ{kGj+eLS4@ZLS0|^M2O06oltbEt1F}Up|&oBq!N9pOLg=9)J0s|+;fIt?qi0~ zm%6U?nKX18Sa6Qex5nD&9HFjfu22{1&3zeI?*ySA1M5x_exS0bJ~D$)jj8V3hsJoO za;7I%ADktGqy*HR5#!GM@^Q{n9EMeKWB`&(xvDn?a)25!^GkPT11KK@t~jT`oq3G$ ziF1y*GaErbUd(KCXP#!t#097QyM&5Nd?2AIf9_4P{F^B-qs>oMWLm{{zqnkHIk)rJ z-q$KJ=WUobUSE;9u<2x-AK%P()zJ-tVm*B_AhnPGN8 zgJt${yRj8M#cT4jGyN8(P4rpCiWL9Vx0sr@r$H@8tl-&V$7k+OUQm=Q@ytw5+Gu=p zZ;Rb3Ua~k3WJu-<_)w~ePm!E$(>9(FkNYk~3cjiXQWF9Wkqq%z>|XjwFlqq2Yk4Dh81sX-81Imug*PV9{$FwVjR#fGhm)RVSaqO-1-@qe`LMvdCNT?n$^dee4dhrjO^_Yr39l z2*Jl1WGx<#W*`^I&HqbJlA^~m;HrO|bhKPima~U(OD=hW-@PWER3@0p*jK{Io85l( z)zS6zZGbUv-~uwf%}NiSsvs`~;G>-kBF#KQys@t93D4bs!1u&)6C~Z-d!k$$7Cf|g zo?$qa!)Hsf|LNZ)gNDC9;bL&s*UQr;SyBxq!voeC!3m zcPdect|4_qKDI+}2DF0ndXwXnn>Sno>6!#zBbI>6HYAE?wyP*SoJDN&i&w;U!FL&Y zuoAZpfi;xD=MXs@5Zh(&EW=KGCg2qy&m#MWupDzp@MtHY_mjV+v%De`k9%JbD{j>L zfdz&Wp@qs3bpSh0g((m!VmZ$)C0wJ~*4_zKHYUn3#yU>%g1O2pZub_S*krab#E=D3 z)5cTWwl*xjb!d+!Dl*C6K=H#}1|N_9;4bci0gs4U4pB6HK)Zu!Bg!6ve*m^}AXG+riB-LhCo`dxCflUEp4_eW8ljEz8#g z5&+(V2dG^Avp@Ko?cA!zL-p*Df_sBb4j0@H;A;?veuvG0WucEo(4xEMha9Hjd?q%G zY?Q#w5Yn5!!w1FAXY>qOy^ejwi~Id?Z{sh2M|Mag3!?NmJ-JQFr?e|(5Zikk_m`75 zk$HAso{mOWxLgZfH%@SmG@CA&#S3P3_F1#(`>nH)<~uj=NEx;mcPjJZB|fEv_t;Y} znIUov#QXcmPNxI%Dt!MhrNzu#link!6Xfix1CovfY zV2RzgMrRdUtzu^u`+h5PKw8#8H){aaZqUnviG7ewxyk{l>IQx1Qm1j8!|nWf+@40> zpFi`o03l+boEk zs>sQLXEpBExhy^Q8)5`7)T$;;4YPrqxzlydW`=t$U%3>mo$+4mA9pG)#^dq;7>^yFO;z*gYxLB|WI;V1AzkpqrV1(L zR57@%`^PEfiPw56re7=KZ=snVrz1ZA(#8k5-)`;i&Nsd>c^yJpVSmHNOLVei)4F(2i z4;O>!oUKMFTaQvrT7aOFv_uTaPa;iWPHg zQ%bX#R!%tw=o_5#aw_LkqS@zWl1;Sjg9{@#rA(frlm*-jJtb+-ufThv@L;}>3LBF- zlB{4AcIRTOtdzmfys9b&U+vAZRau#;fWY*s6(%3#oZp?`if)+MVthM8{vGGMK9()6 zBebsHN3Y}uIj5Y8XR5Q$ZH5k6)e^&b?8cgHorATw!xvAb18ZW7J2AQ)VqMc2JP;;u zg-{9BoQ50aJin+3?4W2Ppmo546&gyVCr*%U>mj#{$b)c@)XDEw(Co0jV$}5Qmg^qn#QRc{;r!ha|}pIg;PW_i?*1 z75J1O6DeT4C=cUyqut7b)Yly%x!q{DoU-36p0rN3Lg;^X6yc^#=ZK{Nx&37@z2#hs zcYq!A-xdZM!2_LJ8Bf791%`)|a;AA(JElMEjxXVk=fg0#cC~Cx=9p%Dfxy{5WUET6 z^Ch4)QVG97-lZ-zx(5>>Z;wf6J^MU&o;dmZ#cE%)3jjS0eFR9> zc}{SSW{46jIQ1;QEx%$scs*-agJAr7yT<*-+7n=3F@W}UBKzZ-nvq*l*%)~#i2UHc zfI36`ALv#YmY50_NR5vXZF~9yesrOiPDb~d%pr|sKKy>BIZ10;6)q}vH&_PRBR5+< zjpLOB_stUE*(k5T{hmvdxxaVrUxSwFKPzm6YSe#L$1+PL|USpC7ANpT!oxKy)P zaXeq5G=4}?S7(&8>++lYrXpG3S5+?s`pB$ofvS?m92fi%GIXpW&O>nl>$UU~cvISX zo05t?y`7;tCVAsk#43Je@}hN3Mz`^Ia#{IV7`>kzcw zDLuQS`kX4Uyq0-&a{Yv@nuRTfK`Ev;y+nYJyTy{rZ<%gyxXnNTX(`${*oNv<3R4gL#y^Y_gPuh+z<+>10Tv)S zk`%ZodvEgq9VwvRv#$ZLr)Ac=sYgM#(P;x=3rk) zMjZurjTRDo!*j5@9jj@;Rn=lHMLs)4&1A5p%i+ID%Tq5YkZO&HKZllb%#@FINm$zo zN{%Ko`pzDC_%~i}Yc~gZ%`RINj!bxeZ%jQoD{Y~1lNWDv?+hTyyiTl$JEKiMvkIPj zb#`3g9-P|Y-WkjuML8^6El7gur1z1${_^5c)B^NhN!eC*mKqXngdzUzG2rl-$hgZZ2wm}d!NO`5@Mp(>`5$-m)lcT1St-QP} zKKLUG5Vl$0{PBzW(Tf*|i#Iidu|hf$-~XP-P2E3CEBLADpfUHojEds*(Dkp#f*2eV zCjcYtoIumk^~?TH9OfHVzf>@!89mK6d$N$dRe7#owGEw6Qa@?Ady;Ek?+O!O-!#PJ z?f6r;%^7oZs)ce7rQB6qCVZaA_o3yK#Vb8*D{de3Q#atWQi%)5{U!>Q@ARI40&I2hWtMb5#Gn7eH z$1-j?;pxdKBl%Q#tf<%;ZtZg^C*z=xScZKbxUw|`HAMwEmr=QSuY}DN3AH*HGF`Ni zy~gd#EwPSy=S32kzT{#{04yY!nF2h!5)u2P@&4^3%Qtu8m=~I@4=Pd=Ny-kN^iNnY zAas9AE(oX5c#qSl`{@$JX^mk?{<4W-c&Uh<6({L0a#G;(V=L%^_`$~TlFlQ1t`01z z>*2#q&<-EbZR>+lY&DVEy~+7kNj(*+tn7XHh^=)-;qO=*+f#h+_m1&o+q6Lb>WBD7 z1QG}7diK2@V?EdE)TPjCmoHDyQS9XLkO5BzdA@-uzEJ`TzS1Xk$M=S=!#TnsIso`@ zUeAXjI&oPhSZJy@6nxcRWreyK2B8%SCgo9s9QRB&6CgO|7Rcjo+xf?vGJd-nXb{O; zHK&nHac(~GL}H0s828yFUQ!0ZnS|CB7hQB0Qmks&+kFHRO*%|wGpqaG_Cq$^5;mf) z3XE`t27}@2-xCSY!VY>xgLZX5!N_a^KI9m~RL+SOm4JyXYSYU1H%UN$e^S9R;KO)R z*!dS0SgDHp2N_sn?@3c41{x$#OV!FBY1}pKM~Q8IvDIWJ>-`tJxYnK7w`93~cSI2j zx<5D-pTiD0=n0Milc)Lx?o4rEfRKY<@74mip9VX`1JQH}j4b_kCdPLR#eZRJJSshn zcHO%Zw!2NU!0M`oLqcE)h5|i--o)W?sBWJ)o10*jyA5;Ytqc}PbPE2kI~}WabFHne zT6hw8-tXvWKi3cKoL_Gv@X;OhV65rzB7D6G*GOfLH`GnjK`6XQKh!BMU8kzt_lyyS z=(_E#AAIv@oaEc9xuw%qUL)?(4I0?EH$yvL3;lRUQ&<3>n4WIKMqH8yj}_&ioE{6O z@32GuiMyYNILUw=!k|E$?oXUub-&cwp58(^Fp#5;llj1dNP$}aGY(jAG-N)lFy}TX z?@}_Hg(IoQ0SuZs^c>H`s0|Ye+dqCeC{4ak8IEP>oHvCEIxE+b)UpoT+oH%koDOL; z?2y}nXdKkWArYylrM15!hKBZ`h&68lZI4EWf2qmhPGM1i@zF> z;o~R282&>WtM(yJfo)Ly$6Lj+6zX!-{FrYlGg(vy!mNCHdZMB#_i!=o`+$SC+rLnc z>5dWNBh&zTgykryEK=yy87#^=0|PzR~t8n!NS2qq^Kw(`n2H9 z9Jke##dW@F_qzi9uJhH1wqJx3p;qXzvxu0>6>wxL=FI z8WFIN$p`z&_P|aOkG2L?8lxC95(DZ(U_WC)y-v|Y1G9vooAY9B6Ki2^+wK*$fn6P} z5~l&{u?n;rVcD&Aqul2@8Fa5nOgls1b$KXyq{|!|Q+8QuFIA_j=J2lbSz~GAbw|3S zqMm?BZ@}pHvDpRzbKXt($3^lAw!*SZAS(BP0`IIl&Bb(JRsbH>;^=K``-DfnAKDds zYY1p6hiC7+$sox9?fvzgdMroPbS&zXU$=dF@*le?SpwNsemK-`suBaWb6vuhGCpM> z>z-+VY}#!}?H2NyoLxq8q&6 zc4KWt@BnK!h7{Dc8fvyYG+ZBp(sRLe^`RnzQt!sV!oJf&!E&yNzLXjQz~<14NvGNY zp$SWEjd78VZB zVb9jAt3yRh-O;xq<*T?1GOXB!L*z{Jf#Of6`F-R_aHdXZrD+DbM^B8iaIJ|sWNX|d z!@eJO7^t~D`Yu|B2jAkC$BW2Z>kJZ})8he~aMVE3|l7i`vxc2HI1o9m{CQ&i0=>5iSwCfkueW}*s0XSqt|Be0tO1x~K>v2M6 zS~E!yuKl<8*>~$djN2(|+CRZw!$ii9i$f$gYF1Je4NQ;Za&_tW-r-h-l zqmhaA)6>2Xi@f;p*_E028cQdFpuQXTcI*2TjG;Xk@A6!M*MM)LssG_ua-?|M*ro2f z8hL5fG!*#}7U_|F2N?kC>}Bdk=Dv}KF)!j=QWVBy_X;!31n+Q+d_}(%z~k{33-oW@KH!d_T}#D$bv74O11k2^fwBPb=O~#(bUg8)DmrVGRe9pdDL_Gdf6~z)e;4{`ndk)e+S~{ zk!WFsGk@2Jz7`)J4&u6$z`D1Pl3Y+D9>y6R397tNe%GV@YeVqYhDqfW{V|RY4l4ioPzHXvdOQTCD}t-7!o7Nz#t&~Q<|GoexeO4EU{6hDNhSe1E77~>&iB#7 z;POg@;rw*E0q85%H?DF&{;Ou>Br|4IHqGb*;iIHP@~RfnEj&baZ&&NaSKboi{?TP1 zxyvG_izUAzVnMyD83vN#Lmi;e$4>ESSJT0W&&^4aH6w{OUh-(8&L-`R{mWw7{Z^z*lQ7T*T` zS^YzdSF^X9ptdAUmstYryq7=4i;$H`2$@@!o#&~|Kv>jdiyn;qp^08Dj?C=(zGAGf zPafP}`q_S7_JYABd2s&rPmcb}F7V))kR?)B$mrAcsQvykj}R%j_n0%naJiezalAL5 z$Un;PmRCf{*`2awiS4g`C@V_Ul?dUxE4M3)FEpdN+*5wf7LM3q2Dg`D2o0$;s#KnR zh50tPc{rqm3ZLLvgRMLYp}H%8qyfHWQ?K^;)cl8cIFGsY48;j`$K-cK6BTujf{Vt0 zfxVxCdK?wLS1-pm6PC}aJVT0|LIxZ|-t{gY+0p*MwyMzmBh9c&L^J(1o|6h%(v)<= zftKI+Ue@Yi=eN6avgK9T*(F%3Y@+d#&%O( z7O{6=JxhnmA-|}};$PB_K3hJ?Cqv%WNXv0l_>IUHVBBN77ARf%aX5O%9oWfU?-W7* zAn4^mNq^4p;Gr>NV=kyj3)Yh}!Ilb~OU6;-lr5Yw_5&`^$!A1T^7-rvsnTf?PMe`C z(UgAB1cL1MKaQ3_YWXWsh1`nmBY>;*ZdqZ)b#p)FBf)YkCA89MlhAmihm!QouxqjH zY2JcPOcUxfXR3>xRjg)zb6|3X;a5xHDF=VqljNdToEetSaTLQe8Obzb();;G8TGDB zyHcaLn-TISzS>`;{L8P_{&W>yT=+QcQnC2HuPNaRT^BcSCq7Zx(zJWCIHB;_WIir^ z)tqnDnA+N(z)u|W?shgJ?7f5X-DBXtbNP3ek1)1`_&lxs zbB{$&#Wg4WIJ(akE=DI*r8!Rk#EyTVPa=k7EcBs z_B|a%WUXJ}Z|7r6)^v9|rx|0SQ=VzDO~JjoptPntI#X?8!>}Q$5$~L{ycFwKMDE*R zohX3E569}%ec-3N6b*lrZx9Qse;4X!Aya)EqUjNtiXF(kSFLraIV#P_;P9P79&`#p z$o@$%X4?auk9*aK)$Ou9GVdQS14S9bm@kjxugdG-jC$NHCdQk#8-Id!IB*6!^BY4_ zmPU30rp=K$-vQaVZ;2UAa$~}ong+(`3Y*aPRYcFT*58{c=Xadl|h}+Jt~B{ z&MIDar~lJvo!r?Y%L3t|47v|AoPE!~b@UR$BCwg?Uk-$Rc^YDFJk#EpFY>!3)D?R# z$wuOHIeqeeW=kNk+EecE&JN%XG$aoueo)kWQjZRSytr$(!`XWzpoDnxBBK$bFF=WQ zPx*MJ!pX!@^@D)4BDLEO>$7J;E4MFZ8H^~4y)dPSXPJtsBws5;@_Ji}+f~|W7$--kVFriO&0Dp^ z`*D{a2>v~YGSVOv)cRfL6h*!|cV#}fYyxQbe>1gI|ualoB*T_Ux?E{a0 zof_ZS>+GiDJhCsZHcMolHl>WiS-%)-?dsz^9YNfIXIg)%BR6dO-_??1Rs0)g16qV5 z{r?o!F+3Bd4>+p)8Qn~lbdj|3V8WPhI1g72*w?s;m$x1NGA=(JD>Cod1N%TY*Ex^% z0+HQ#msDrBLRB({Z{*zjgiI5?=ghe8V|kEFJ^cT)0K^_e7rK_VTsQi5Iwg6yZZ}?Nzxp4SuL1wIQuBseW+-$YyBddt7 zd%L%+P{4F_-ca7LM4RW?-WufZbYRRCbp;eVI#NQ#Q+qFdFXX#7Jyc=ZcG@>@Rl?uv z4C(>}3|6f%^xB(&GWEi@` zQZhazX^$E2*>M07gT5mPB3wGSzjLUwG7B;ba|?x4uqQ2(3(4W;w{_@HgcOU2uZwb3 zpWY!?;Ni3OX7Ne4k}2+$cY0p5Q%4{bUgN8;rlS*D6Pip)NPX>(y+o?uGYYh;Ib4{) z(-}h`o}T`tepFxcwds|Ye&g)NvWkP-@~FO!dpxhJcSRBR-YIG?4cJ{wHS`*V4~lOw z?@t)YiX{X86m&82@bH=4>i|($4DVI`Z5S`b>BXh6XgwUXq?gaVSzE2@=`5%`*)dqc z48_+j{BPssf~Cc|K}^uyBG1vug70Ymgwg53jsCu9D2%_o4tNc{X{>!Z3My;V7HqW= zP3iseSUzoOL1=h@-E-Q8d-kL=f0H14^ibV6V*u_ziD=y(1=Z?VpvE>T2!n&j-^%LV z(fERA9Y56tBA_`Zbb|>lWC;&dJwbjGVt?<}-1lYxB|TGFWS7rCWR+g0)+2>?uq(n+ zWM<;0do7ABHPkLT0@e7!s;sM zM`*&>#`NiF*TbTC#irQb>I>sO!sZHhg}eE`)sG#Kt4kL&mN#9W>TaFuDv{Ice%fqw zYdTNdxmvfM5c?3TgJiq+9+S6)mxYFpvxH=iuMs>Ob6PjKBPIDu=YnH~kN4~yWrXja zyPnzH!ipJ!Z?677Tc)J9!s`9p*2duA|Na{#M*ENU|FUIr@Nwo76%&?xwir1HePvBH zbOX1P-6Db!V0$qBD~eTC(j6mD4ttjvvm(x(RpD0R9F2`Ju`IBI2V?jg1C+C*; zRjxMqkQYvDZ3sysK(zPkJIhQPYxG-n9cxD^zz&o z9Kx(gFp*p14Op-@Q=@)m;*`C@km%RgL`J{hYu=WTG7|_X3CY2KBu&;${E$filKgB~PzdQ{#IJ8zjy5%Nv@Q5!tg0F$$|WxmrV&!GK;~XsOJ}jy z*V}HgWSrUO%?03s#WOGO!^~em(#dopq1*w(10spmOMM~*psuZKIj~XyNEy5i+w1d| z0qAdKzk~>Eb$orcM%AGQh-{C=R{U4c0V5j_AuPa5J6r0Mq8Qwr=_eoX<^cE)dJ_Wf z2AVJm9u1thIiqY|b8p|j0^AL<5F=g$bi)8Y$FGtaM^=E& z+&OTt$^7QbGysVU+1v^wD1dI?WA!|808n~mp*RI$gIC-DRJa3Mzp*Y{3gCXU0)9!) z@MMVv2`W0ZdIg}tkQF%Es*H6Y?ODZ#hy`qfSG*|j-~NjO35J~*ZU-u>wi*WJC<;KZ>sSXyK;)JrUIEkZuo#5^0)St@bo7Wk?24)Tr2Lh1!B?6PNc;Y| z1JF9??q^!l1hDB7eDr#PJ2k9}?d9L#U90qh{iAhRY_ZvYv4C`<%hx{#$F;+); z{s-F3_M031*w=!*XgKR~%Uf;6Ju*iJ$2xF`FDM4Mo_=@BWePcuJ>!9dGW9?C`YsM* zs?;r?214(nynzA7@3H#$!9%Ax!neN&kpcU`D#1^O5MVILKxZJ<+{Wm2==yF>!

J z$i^~Zfx`>XI>k~)1s(hVnCZW2jEt4xN!;MP`>&%WC>ZXaXquEk zE%2DlX=?AJu;DJNTgk`7YoyM7(ArjKaCrTF-7b3<9k}Y`_F+Ll*2iW3_z(2_WcfS% zY(|>iV)isQl%~n*^IbJ06bcEhqm;c=fUR7%Q9U_X1D|(%17p znR+Bm8W7RR>pGu3(LELh)~O;~3m2zuQr#`mydHbKalt4*m89bxs~p7MP}h0L1#-8X zD=f^5r1WpA2(mz$W>fYkW;2d)Qhal-08?k5)ao`D*GWJ&@Dk3iI%jfQ7-w_`pKrvN z&HMb2ruc9DzO9L7Gx&JroC$fW^{j)&WQF|9r(zY+*#qy3;o@OTTZG1E|Cw*D4!Qd7 z7DjF2H6^KN_4x0_=%dNQP}go9t?cD@4FPF8)b`i2c#7e|hT6;1lGSsX=0~{MEjojh zWZ=Cqo|sq$xv!N|o%z*Dd+>BGlCkva>3*TK&_ZOl%>Uy#==z%q0?HpP?9km|-Djb# zva`v7d}vw)-gCQ(0Wc44q;st{GDC<8j31xp%zmK?2?_N;Y+^AXhPo)uK_M5z(CH3= z3d9OtqChX<5n?J~6XC!WnS_?2eGN$X%gC8&uJ@lTor|f;!HY? z)Z|R;n6Y=de}=PTiw|Dm!DX!Hm$M^h&-`PolXAcTMX`UpV##mE-)_CBf}6?&np8wu zX(8C;vF={}rB>5h0ei26yDw~0DQ`>BA&8<;t)I3p&trWLQE#_5eLS$fW^=GRZach# zXKHSBF&+J_(m-L{40_Qav9Jq@BE@wj)UY5wV77GT86x%k(5fvcDYD7u5G9Ycen_s8m+l$JnMI75o*q`HG_ z9pUv8#5y|LSKa|CZtU>0e%fBBBt1B8_&As+Rv_r}$Y~lfT0{b|SOx&S=nwwr1&_Ou z0O9ppQT1(GeTTtu`yKA~V5{6MPq4q(1a-j4T^`3Z0ol00ieA+lCU}#v7u=Xd;zu?N z{?Ze^3%P_ds~3`vY&TKpgWo;aDN2U)Ilo76&M}4DSpc*@6~WxNV|D&bk@hO>Zb%ml z-Vq~b(m>U5gKWxGba;pdls^@P?|Q6v4-}*l-RC_3{;RxPJqd(nIEOh6WP3>$ zm=`FXG;wuNd`84Y%U9-K59C z?gWqh#(F?%triaTNQB@`&9O!ReTic;Q)XF?^_(k#Dtck#0Wo(6usWP1mpKzHAK7E! z0uauEw+jP7Sj4|=UUm1Lu-E5q{xz*bR8X|o(}4{wC zy_8_rlL5aU*=@lPzuSi?&KE9`A|l@AP)PL(eS>XY|0LI8yBIT!?vAJ<2^n`RE^N}w zxsH9DLlj4Q680DNWDSaoI$!9te;c&wRhQm;YEC@q_+8q++YvSRYb3zjGHHDJ048Dy z^$bMzs`&I?ivzURnIygl7#d%+^BRY!--K-OxISGyFYBtm-F;Y2w8y-(NMb6t50bF% z4?8prSM31HBqE011bDqm*FDECipaZ9a{?vEO7MG)!V!CSzVFElaH3P|yR_U=5;m?o+}}TOz8pMCxCF??OSU z98w+8=p;Cxg0BZ}F2d!burisV!0H~RmYwliZV`7Z0sPyCttP8L9g>hQ1R$Onlpje* zA`yamr!Xlu5VMfwAuQ?nmlGhSjxyd6;o5%>kDW*Ds`Y;#xsZM@{^$pV?|TB@UN5R% zv-3>se$_TgB~B`@ey8cvbzngc)L#tyD%;gFvfF4OkiFRTnk8_cEDgz(Be3pw)!r=! z;Rsw+x@-mTOFPkp9C*`nTqt;yk^(zYSS%HvjJ#gNQjqR)m3W28hX~x;68vO>r@oC9 z&<`8GTm^q6G#8g&bvGsgc1Pg1h$ciEe@R*(dC{u(pz@`#j$Lv890w`2{Z}!>FoUNi z4Ft6=c$WduAr1q5A(Q_UQy;fjGQ>JEe*E}SI(F6RDJXYKYaRlgIefFE_mI{SQNH$w zU9h`*;m`RL$SS_!_s$H(FLq6WmOO`H1^7`cT+ z?IFaEn@^8a5P>99nM@d{?|a!Zcb~Q?NMyk3ua4wzLgL}KJ{4-{3~zVMB;t+~pHj6D zN8(}Z01TbdoBeyC_dN&F)OQX>R{^;F`l((tA(j8Z&lvO#9ydd+&U?4qK0jkK@hMs#6Fa z!_Gx$8gldFcXX$o;cJsjn9A^NU?@`{O=c zF4wIwul4&lU$0Is=z04pbPN`lHd{d&TFV|@M$>P_Diw(IU%4$Pz6korsG}O5to-n( zMCkZf^!dmtq^`9v?q^R|WkF*;Ycw3zq0th6=HLY%um|wZB_$mU;VSUXodVuFsF1Rx z+_cAjVc<8bIfaTl(Ora2?Fz4Ns9p|2rS4C;zSkWjGhIm^#p*a&#$6;k`vzVxPiwszQj%FhhWO1~ApB z2f3ujS76&HvOI?5O*-e^F>X8hha%Yt9uu9hmkRp=cfIlALjq?zoBpMVJqcj<9%0dE zE=@U)A?Lh0)FK@y$6i;f)h4?la~)-J@+arQ*y#YyOle+Crmy@b|ich&)bxU$YKfya|!(xwfQD*uB=u< zI%gHv_YNKZwQJ2zMRrTi<0>D*jkkq@ZX z;S8;ubH6;}vsjC@pF!5a>&e_UYa4mD+O9$Tcw6Nck;Q;~9tXWrOsZ@xV(|kN&Osu@ zq-G+PS*y%6;`M;o;)9=9vEI%1nEu!u;!|Kd%L*?DHzzNQ?7s-pXi2=1;bh74)(BI5 zz>qg|bDH~mt~377Ddvm_%_}tje#EMbcl`Db{zJ0FcuJoba+UeZ6nc${M^P5?{2ZH~ z#-$$$O#!$!9w)4mTFNg1!!^SSTm@)uvhssPI4 z7~Vm;Z2Nka1Tn@5n1mpi@>HadP12uVT zgs1ww(*nCK)4Yi@!P>3!7(`Rgs^6u=}RY}2}VA9s^cNv-Mq*m z!S)8nW>Wk+1tTatXJ@UilkuU2Eluh7PY>63DmxRkLQH~#TLcugf~{uO=G%$s_@sDa z(WDM89zNf+SDA$q`zg`vq_*G;DmjV8jCONuJGS_(!#{$oJz5STYCSI#Uc@jYfEsP* zZ0-Jr0TL?pYnU8wl9_fYg0(#)l9<9Si+JSoC);Ez zcAFCA4wL3#i3k%z){md2+4}wB)gshzuPZ)$m?bu#nX5u z=&NUV5?t?!O57rqfy%C+(rKYl-*&^%=RpSweG5-eix$jSj%O)ya%flBeQnmG%Rybj z31X#A2$-gH)xp1yX%nrAWp8Od!-pq=z);suid10q8!Z94Y}e)?LS0h zcDS=-{6XO%w`_|RIa2I~oLmt<9a;r*ea}u-@c?WJVg97$$IrLfwVk0+9~ID01^hH^Oca z(sQ;Gp7HZ!1|sXTV4PQES<1#6%@WO+6d^stqB}i3oI*n z7=Ug~1tuxg4ik-~I6PK^3Db4wZOtVe9;uy9&GlnOWFBs>-%rKwO9j}76Ihi;wsntQ z7#wdUq=7mJ+;sL}(3^d|hu2a84`2zTp;UYNQK15y3?52bP#*dCkGac-t~-3ISXO3x zY_9)wf!4Bic&(z2qo2kX_L?wT^r@&qv5A0xuE-EepcvG2xV4Y*Jmd+S zf||$)pRD4@N6ye3dSIE(Qa=sX6D_q0LObf=u|SEt{h=!>se0?l049TR&X0{NV7vcw z>w>%83RCA@znKB-j2n*gdaWh`&kAc>#=yD>7Zii*X4oHGwI%ktI}_K{TSAXTTmJSt=xJtgh_J4|9jo7iplUZ zBD3;;Rm?FNN+SgH`?0FG7P#T2-0)p)IMM5n4qRCS%rHw#pc$rBQAA-$M4ev$DR;XL zCPR6|QuWr+Iq1#Lt>==#1|G4v8$Tlmjr*UTO^dk$;&4IXxF7{g9i#qe_XrrCp>@L! zPZJ81^%d!DvvLpA6nWto*}?8{f7BI&6Ta;}fZ~Ie4hqCW`gyeyUchOP!ow+&qHpTq zc$6bt>w9(=4uXmL9Zr~Wz;MsJ0j&vBgg>Q<(X7GL4xu&eUnPCXxXoD48aI97IDr8P z?Za{axxlc5_I^1nIf`xA6IZ;f3GPZcQn{Koup@3mTj7$LKU6m&p}oM)6Q}bGm|+he z8nL6T_^7YJGh8>&p59V8q+-)e=nRarin{2ndEy1}4?i~CHo>jqIi5p+Rs5a9sl$bc zpzP=8)LlW@XQ`IiTo z#V-aMZ6Zt#uYO}vQ%V|5gw2hQ+;_jL;tV~=lPbFwYp^wEDSZv-`CaANfftK|ym%Mf zz}>)s@H2jeDk6)(qZ#t$*cqxid?D9^-56*0iF}0*+FlPRR7)el`|lqoNnS(c$JTvXP|B$&C#*%!@&&|N74&A|T9Xq6fiY`T{oTV4>BOnRcNYk6 z^VR%e8BO9lMW*{;dF@A0&0p8C#){#k_tuH-;EW@v2=o+J8+FLxN?MkLE~zALSKRD10{ahnRGlf^%Ow>q3-JjE?M+l8`!{VPgiD?@W5m|Q1ErN@z!`Llj( zo_f z!-L?30vjyt4eFR{mT2KADmOVB+CBBcH-NBdo#MpS_8diA37Ws(plfNnDR~Mm8{_&?_gm21Av~xRilP{2# z&&{b}7h_L{v`k;y$v>m{jeeE4hP(7%!>hmPxq!jX>GkK{?PadvM=wn@n@V5#@?(*v zrTmH?8{f>>pNhJBw9u)!1#Q*r9MS8EhT=M8_ zkJdLfyTt9)FBlFZ(ov+3?qf#hYIYeg<$Vl2cx$#4|A&-oSJMbxp3JHKH|Z0pqU#)K z+l|d~m-R{4Iu~92fKjPV7L6s9kqptE#a1O^PZ6_!A&07h9}(&7cB-BtA=F~jRT)h1 zBK2C6>U5qjG?#9DZLYBz7RrM8(r-IQ11rp`Qm85*-rC8Yi!ZC5@;s%M#$B#1YY^*s zuAu3FkCFOSco5;#aQn_u(i9I(_qB!c*{1GY(po?EwA-shajR3&%-ie4bKjPx%za%e zZ{|naO#8B=hYOh*8jb@7oW14w%f(<$_MqLyOBysuKf^k^BRTDC>H(7Rzb%3Vq~IRL zdS6GvTYHwe9<3&D(q%TC4Jo_H&#XtS-`dO0mL9OfU;i&)__6?4P)tnOC<|A1vCMU1i%0q^xonONz?msVh;{|U& zq6YbDKFKp{*f@)5iq;U$U*VbLew}`>Q~K3i4DjE#iSGU0(8^`9Dq7YeWTG5vSce*>gJ9UShLnwKT$RZ*6Hw!_e^L45t-*Nx@8WkPgm00il#X-Ty z|G=*)zk&AU&R)GEX49qz2y*bvtxBkG~gXgaeg*NG5BgQ1Nyq z%LTdj<57s}@!fjijT5WH^UsH7_V*Tdy$DJk>_ygh)&7hsjkcbq`1>PBAU!^Yo6$JJ zuzqr{E?u|?h>!^3b2}oV5u~VSj)ry!XF-qcmd75c+#i>%CWO9hFcF-%i1_&hx6mSd z;YNA*>))t94JFo@@MPulw<#?(IrnnNjm&d?o4uPgP|+jjud4s!Ft~?xH0sIyR??tE zYraW|*tF7~3#9n4z%O_tgKM>zI_D{L`k3WK?2} zfADbF>>}QR&%P`D#kq5~)~09$b1##%jh7|Y`POun-q9e@OiC^Fl@pOKqBL<8jV^^u z-j4ZuV@ezAha&63Eyl&~^f=Cy@o6o4%+6}>Oyc~TOEjqBwgaj5c(aEk;*NbhGgMEt zZyFkjTuhD=VEFe=YsDam=QD$H~Pwc_aDj2d|8*4JHkP zFK)C`G>*|4Qe%`+!miA=F)2ir(DzKr8@LrQ4Hea9de0Pnl3~AD>oq^ygf%}BrTnH> z%j4@7A9#}9DP!GlCJ6Vx%v-_vVlQ=Wb%HEn>I5h9l}-J(r%`M2g1T@^(%4d4S9H|I zd7XY?woAL`dgVsroZkMhg?Gq3xbte=k<&(4-#i23wR?mrn$PPwg-AV>tg_`rt;*HE z?bL*NzlQszRvi23vn{A#=9HRVdY1JKklbJ0m5rM72=mF(Y~k57M=~*C&jYQG82-Ul z_I{qk-&)34c4p(T&&5T%jULnmen7z%{s^#anvMIBRDnQeD_hf2B8Rd&~`hu*)T)H8Wd(d5Qg#ev3@ zbBhYuJt&asG8$cb8qI7UeUq*I#mOuGkIgIqz#}@zL{W#UgE5prs-MTEKGQtRLE^4N>4eojC{$24PNW}z8 zq(}EU(U)k`n`pR6>5Unwq*-nWQznzDDSD*K>CYU!wD7jWmmB2_@Cm_mLzsDuw*3Nr zce+2`HQSY_v*pgvH$NBlc%R=bwBKXQZQ`KK6XbjvgxLK;(m&{%Agl3z-^-hptx#D6 zw$s}U?(2!X{6m)XY570gNCGij*CozV&M3-qB6RDySD*P#{sT~K|M#}c^9q?6tS`X$ zv&&1Zo`>+K?PeM7dn}r_0O74;Yv2uZBFEE-oS{kiA_7gEzV#Xz=M;LkKF&yghzltG7nhANsij z_1FF|cx;U^#4kFN|9)lF#G|?@@Y6Jy*vk6#qGjnb7coUA>+3IXv3?BGVMvO~wceko zEvOQ;@(khQsb;i1{O)w?Nit7}m%X)7Ch|sFlr~&4NxbgD*7Q$<6Z84B>2kh{%r_)YQZiU9t4hCf4%zg~}jx}wLohxUayb+Lf9fuYst@eO*kd2JCf`oASkRYpiX zUdAworFX=sFof0Pi&*+oYCYc1Dyq@r{c=Usld_)l__5q(i+%+_@h~Z-@}**q$V_4I zD^Fx11N;KTBZ=wyIW8}EY{WS%;;VF=QYqX}3ew!hU{ylu`bw2Fx_*&P>naMV>(9}p znj`n3xtn^dn696v)AjQ-11D#w4`!II%P?I-Kyn6OFhhw)2I-JFCm<`E zF0ZG@)vC>LWj$4d3|b2gs#TjqddaTr)`zM#W`7ecJu8q ztq5H&i_m%Ph|nCb(2=e!bPFv@FRTwCOaDifr44G=OTJphD$2wnMZv~}ak-$8hI}wd zs@SBc^}k#%OG0fdv}y7WW=3uLOf42+>qmzg9mOoG&CQ13S+!XfCwDcQ+!v@>ZnZq0 zWn|n!o7LGuIiF0l8ormJ=_i>MhmpLdL7S{6$dM$ioljC4qsqpOGE-#T%jYON zok)COy2$m5S*TwWqT{?;%wDnq=`L2>U|8+x;0CVK!42kY=t6D~z2}_or{*~)bj6ya z_0591zU{KShP~2`_Ulu$U&V(hJv+Y+g+Hfd?KV_zgAPn+f=LRm1PhTnFvZFX(AU;i z=UUn4pc8I@LM->{JKX{UevDBxAa*VB9>r%F_U10jwIMF#HpS-V4swf0=#nR6Ks^~-wUhCQNGGGmoj=3j$o39E zFTpbO5@e>uMqCf_$lB<^DtxPW({(sE1EJ<-t9ov(k>_ShEcRrg^xjpAH$MUI-H_}m zMyt_#PEpCxJ^9+7t?lcB=W#xkq3{J)_6a``-(|#|iT#)NX8pw-88dw%q8c-SGZ-Z`O2M+krr7vYC z;s6xfkw9RJP9Shc%%vJd-W#gJpvkayiqbB5PkTGs54+3WH_B%88#1}O_^DCTf7q!} zRN9C>sfHacPHoc)ake)@W zEZci^v!w~eLOcP$FIgC`@}UcRJni(iMQkH6bZJv!ah7YPRd&Fo*tPiTlx@o%yRm?^XszmDXr}x~FPpZ7g_O{&exdycp*VXMf zadmXKFmZnK;MLPNn96h}N$x!~v1a$Pb8g#c2U~ z6FQ>P0_yF{pWJBydCxNZX#t^YPYY;#Jhdm{L-%f37>%uwHB$bjJAqiFIe{2@fw8h7 z-6ga+V`7(2b379q`Bx+6Ne|HC(o1v1M15$`7twi#Q0Ge){UwqEfVvfhA9+|IA9*m$ zN5Y(78r06r@!X}HVzMzWC2sZbm;FVM4>0|L^%>3>wtXg_Vj>2DIr|||ovEB&^3Pk& zJL3Z8%cioa&?zR}eAyYZyWKj`{zdJyf4S2B>2mucZT|c}cSZA~+0)3DHqd6i$|EW? z`;5dRN@ZM8dqgoAEr#ZmF5Jl=9-18$4;@21l+W_hxla|v-4nk5i+4{5U3>RLlvc~g z_u_vPt7X^7CUZicZ1gzFBB9KWG?dAXbXJinj{>~^yC;HOxGG|*d`Jj$sIgUds4)Vn zgnISIUbVV|M@;?Ie~gY8qg&)V&k^z>>G#8$exI)62-DkegpCpR{gVP$_*5JLPxH|x zo0=;$M9dYYx5E{t|5Ug_XQgv_WpSOpl_etkw$HeIJ9fMHojL)s;`fo7QKLKo62f-+ zUwHz={lEVN$VZRF>I6ur-DsFcqO^n0Al!{UC%7B^QE&#y2e3VtpoZ`wP$iy*j9}*Y z4eUF2wT<_G!HqCoY~$?$TUM9f2-9`8tSo7_GwgcPs9$d`V?Kh1@VfwJHfK6mZS2vl zHqp=L#*p**t4AZ4&-O^>bF6B#C}uT^#y>qXynlLQSpRfawEpS#So){y&O(qnGs}_J zj>}`C-sMqt#>j$}u%iRAe_2#VAyfn$J0H&Q9=eUO^3b)%Qa+t7OV`Xj(1GSG!1PzP zJ=VdHC^MmeU8y?&%d(2}nIz?VI%V?h!Td`}%Gm%L)YPH)18$6DxcsqND}f7a#x~yhgE@3~R&pxu3hj7$%aYAxei_fV$T)i`C8k z&kiabCN(m=J5Ur1BE~0i_Qduij;-rBT}QXvhiHi_cp0+TscY_|XinF+#^{~>F&!JS zs->XBo{SB$6XVnR56OG>vggUUibO!pCR$h0R?MO}RJU^(C-Q6@^AEPZ1kI^$U zMs^wo=7o_heUgoi>9F1@;t39pjRwaNi@{;-O~H}5STi!}dskJ%(nY>Cs(FOtGNcpp zXwT`NM>tePUgk@4`sR_XOXpE&J)-km+;v*Hxc;mw(q58^C$w|6MNL29&$cvb&bDAl z-flFtmCt^9*=5}Pm@8L3!k=xicXYPJ)`?@T(a8DgX&P3}-=mlF^Hg$vM_(;g=24^~ zzAa)AKf1jecCfA^2kkvwwwv3iVnOXYXybXk$aiz?vDwYFb?v^5#_tkGp;r`lYXMz^F(FbL>n1@6f43%7rdJHF)>Q47c67C-JpAFW*0gmq zcD3ez)m)71utaooF-D#hT|5^fJO{t0?Kyb;9Y4_=gpcB?-Y#r`fs_0(y=yp9J7M^n zlB2K_#=7=h^Od`Hd~>3@C!=dl6l!h_R>_*k#q4y70JmjP%UL04cC|NovO}IS!lj7c z;j7!%uA>Ww+g>Q#G%!BocDLpZUu7FteQJo`;oBkp*W%O_Q9cr*xx-iYEgHT(&iDD- zLz@?;E*8)(XJ|FYrIzc_riZvt8A+&LoT`kl$E8G;exFH&A?$If9G3nS^*%rQ1i5yf zf0eePoUZSoeSWSL6!N7cN4c(7l&|YeceBsRD;xhL6_c_MEHUK1zf!X1JXMZvgFYwZ zc`8wtO4OgH;zM#iU3RPS4oKIGkF3Ey9?X!Y%aGRQBSKO6A82mgP1fDKo2;3~Ny^A_ zC8_B%cJpovDhE;Byj!L?xv)8Hqd8qobGpsRL08B*ZC9pRJ1#HEENEIB`ukLxsaCF^ z%E`)9YY*-4I30&r)^@5b(@(XO5X=FodXLscCd^dhE;4^MsW60{YUwO}nR==%Z#&g= zJb_QOjG#*mn`&lGqBOzuCl$5Yc(9z16HG(n$^=W-O`Y_R6D)-Ljdp@9>wx^1sSEGa z3CAl*yuUQ0cpscE>8klMAi(EKEWUpgy?lSV4?}l_eBZT)p(E#HI$gU9PeXqY%hS-m zjq)_~4N;zkzBJ;~(20?rhSn?g|Nk&_B;3M~O(^X5RwA?sX}`D9flWxwd49S!p`YC6 zR-*7P9)W~lcd?TKoM9~|hV?u#ELpKi3D2-n6o%D~zSGpC<{3fJRKu{^s8MMlG#nh` zKWT-YFy98{LQlR)qrZel8 zBj;Wrl!_4CD}?-{-3wDG1|cHTRktvHP)B>nDCSf-dMw zCL*mT;%v|pXXtMQG2*;B5@o#YM|YYw#p~#HIqNyiw}O_prCZs~5P3m)@#?B6Uro{2 z8QOf5C|yy0I>ayNKYGNqUXAjzN{?Su=5>2$|0hmetnzgj&Aj&M(dM*8#AefsI8_;8 z1(E42{dwvS6@~p)P##PFgIf97!z!v#e)h-}RZsCcd5UYk6_lrZD`>j%t)NiG2b}!R z>nDG*_ESN;CLz@$rAC@h1?B0ccV5U(1x?qbnywdNd_g7<*@t{q1v7;Fkk2Z82Gy5; zLRvUh=rb%2W(dibma8);3RsFx0ZVDCfH4wj^QoXYDuc!8yQ}y|vB7rf`N(!8O5G(d zdWkZ`B`-mVazk{ZDkBsP1gQSz>leTXk2P${*j|=Ku6oxl=s%z9Dx= z@Z-De2#ogQyUN`S%Q>; z>byYEWiAHi1!B2>uJNG7aZYk+z6_!Apv}{H(B^5qoLhvj-kJ3(x_DcN)I0MpLI@99 z=24a{L2HiS!Ut7JilGCoY&Xek$ALC4lwWgn4zxx_B}%{M!{J9oJzF0Q`3hC=}3BGk@V^X^;s&ug`zo2RW0bx zBu-QaCTMWgkbzH41~TmoG)2lM4ho!LT-n}%*rpV0Q*w|V>_K|4ktLd_WI0g@5e6fZ zBSj8!*RyAhR&tY(CU)#&!1n$M(B=i24t zv=SJ-A6!&@Yc04800DKdu*sKEd$P0h-{b1#5T-EYsn>PJoR#&hHj?!%CabmPTp+tx z02^kW23_$}J-HEZ>=uV8;qq!3z@&jWL$l;y2O(A&J zAf~q|#Q4;{<`zTw%e?JgYua!+55kG;Le40zHI30hII9uoyhvAb?d@Q)XC6)GI@LvC zF3o^6lK5w9>(FnPe`tO5nhN4Hn_dSz*96DnbJbX=-Tczp!8Itf$8u;lKN)SZ24f0M z4$IOl9lH6+7`U6?BOTt>C#UCbeuZj@m%FNKL>=8J22P7t((bzInzZ)6e_~T#uIUOE z6H;W=IKy{*WuJU7IK20tJzDQSdpFr?#W&_OXqAmQJ4w}J=oUAPmyJz*b~%)f;5o7M z5$wcmdg@8i;ca@2Dv2WeZF*{!6`R}iKl9i7tGNAz8ibHe1>+B zXVy>V_UO~FUjHO+kN)X=qwzG1!2kb7V^@5mQ9Y1d_Kil_{Pn--8;xD~HM?G;BmJ74 zdh}xPH9L6{>c3|9;waVJJ6&J1(|=(|kr5;M!qCS;_%96oVN_6_d|imtK>S-l2=#>_ zk}ae7#E@!=wEe`;C8-@4h7EmUD9W?*UGmkj##o-6@1n1c$&=%BeP77fm!4WQqY(N_ zPwG*qjd&FPM9YTmOHcBB_g(R+r>-3eBj1&j4=2?6CyWg7P0653zDS>bYz(^OPeEmX zA5Pe(Rt8wcHct350$Cdviv4O+J}T)yF`^63B@hob2Uooy?G2(etk(atOM-OwcH4E% zN~?^dvT#NYZf|2Wy&(wOF^TiAu-5P+h6N^7xVrc&Q@GT zQ|zlj&tI%r-FmTpUE^g7ZVY&AS?JY`%a-czmR7M%mRkSk7YBQ9ziM*JTQ0+BeN(nr z{I6adoZc%VmMb0%yTl#WEpZ)6fpYb1*vI|cdB|@@%NbpHzeQs?3wPyRrz~g0ip{6fa@O9RcW%}FSU;WS=)7tNh_>^llwe&^lYU32H%`O(~Y7j!FW%N*EO1fsn2Zh4e<-rd=mJMUak z-FYW!PjLKKTzVI^FUyZ}>0NXhqdR!Y{4`v77uoglqIa|-)?Rg8d*xl!cSxxPl;yZuOzkZRO0ky%mRAvG@IJEZz`>JBMoBjU%uL@LBW`;)pvD!Tm+wRU0n z))LLON2IrwoE!PAB?h-W+@WqQVOmdg+aCX2*Oqiv*PI^7wIvS<5$c+=RhKW&2j$cX zXNNbJbf|EK+#fgom7kzh5ZBsp7nfW#IHHS7RJK9g#U)~!`;OYbrGqh{5Vv%^XX}bv zIzsnC$csxHgS297lzSZ|B%5ggYapX64TkD{t?14iY${`oNv+?*5a$pCh`jUWTAB!{6}i zx^Jt>Bo@WDmnQ`e(YuvzF9!!d_8(u4ln1=mrcLdgpOo9AoS)25ou70wmKUyN9UP&w zM|^}b;+>0h|G<$+F7a%&SmSSgZ3s0d~$$V1ZTIHk)N50Q^k@~#PaoN~FK(h*dggq>_*$0;K@ z*3zNA%A1b^I)hAaIXIjm4x?XC-zdPZtkm2nutLz?D3B*8Y+iG+Do;kjjjNg)1r(#I zEc^2-Nxk836lhct#B8-0tA&-IF=MsJC8%j5m2MQ6p}$d}QQ$WUAaU?pM)_%Rx@o{B z(p3Z8w}&npP(CZ3r@B|*c_O3bL`EXBQfRLh;MEOj3{$EtZ~3-+wScU5s;(i;)9Iag zny!|w7T{wV@@fIi4W#W~Eigyq+UBg_1tD&-rOEbN&_0pMqOqM!C51yoT1;1Nw}_6V z+Z*z(0j1q`4I2u1*MOD{g}!SbHns=DQb3mWDxK+FEd)fZJu4nO z;cqZhQ=?ai@H?;tZEi43Xrn`SxMGF5!Z74Hwn$`&J=jBy*h7Bop%&~R?3`p!97SoD zQ`#n5iMAuh>MncBD8__0$q4-%p(BFUVmH5QBlz8kWVwGvw&@@3 z*Gp7x#&4?6Gn34XA7i7DuN5<&=5yBW{WFxTp~%vG^*s>%6$$W<}!t3SX%mX2Oy zVo4V}lKmw;VN9WC2Z&Xd=Z?pWb^e!n;5a9)g2Z)+E8+hpwx;xd4t~n!h3%eH>{Zoe z7<3C%nX<)e4cdd9f$em$cy;^CQK`2|h|_zhw}v$5rRdCgDcWnHkTge=*>8lXG5e_< zYWwg3pc}dG3h*1bV_?feuNP6;!CkrPvSHr6Dbv+?LAv@#9_9@y<81}bfpLky$6*`A zh7FA;YkC9+ZF@Q&w3FT0pzU}J2kqU*_@Ig2#PyHagVtbAlE|+omc6(F4a_bS&V>g1 z#?3n-5)Yboxeo7yEOqP83EO(l9TnrwdQ98$4d!6WKf9K6eqiJn8Mit|BE~*&U^HFU(Kj&Du-_YrL8dygpXtbat^&YrBD&bQdh z4x8%gps5k9v{};5;I;)iX|9zUK2#$&sJ&!A6oQ)+DrrxikZN=G%WMufA5r&Ez`wd% zu!oM1<2`gdf%TAu^-%9%54HZz9^}xTVZlq0G)8Z>-+&}iD{_%vyO*Hm6=d`ds2A*$ zI-@t;cIb}X{YkV|CA06fyP087J_(T$g49$CRZ4K>Xn&DWOYm1}7Mb$4i%hx9tbM9Q z#>5txJ08|9GAaHu6N6)Q@@hU-lj7M}Wju^y<$PFKWJW!#USzIBA)PM_F70CTiTa$3 zO*to%99(M3+h1yw+6x^mHS+qAO6$yp+I2>~ew?XZKbrq1vc&^r9_CqR#_{!Dl5n?O zsJL4YER|2PGP(znxT%?25IicNlzr+U#1UfA_7 z0G2GnPQ>y40&sR!rulU<)Rtxhye-G^46&3c+^(b6+;*4T8ds~*n(VuHYZO%m>gAL& z<+>>iK`+MH@FIa;*!3^EMc112_Sfd%O4TA#J8YcBRxRQ*R(fM-`r4YtM!r6+UHbY5 zPw|Q_g-xanr8-avTSUF;SS0uAq#%u07V%!qQqf2ejX1!}>Yn9r1LOM4opKH8gX3Ct zr*>SE*4F74${mQA@rO%sA|SatI07TIMgF)Y=Lay!`Ad z7$x`%PPHPV2hG^B^zVQk=gfu!I#orWfyYx?Ag^`lUT7Xp{hF2+S>YU!?5*B z_Lsw-$1gRk^IN}NUk;4g;%X49oN>_ndSSa*u{U0-zi;kpTmS-4mIUkQCqSZ?Ld94GQhok8KePtmoinhjsQ`$qb}7psG`}M**?>)xohECl zv1a@qa%TU)0np2FJrN1?-GyH2JM1~8cy;C)pG?@PnK?|sVyVcG}pGsC~{ ze@O@B_wV5SDJlH>&pLR&HQal*H&Lm7cBM}lp9W=o5+8&7qVfvfe5)D%#JlPJnPOcx zflV_dA*U5By(E4{>8)a2oGSh7YRK_TFo#lq#1CmSl~=v*ohXH;zk|y@Z!O|Gkqa`hN^kx;H+MW34<` zHdw3}>kvHsFzMcIG=5fO$Tj{uAvLPR>i{nKn?Lu@0l=y=h3@6e`&fHY1OANw^5?++ zXyBFg(>u`|@JSOHkCy2LVfB>=Egc5yYu!XME~rpA(9y5$yg5>L|J>uPGCB>t*)mgo z-ZS}82b3K8r(q_LFw9q<;hB?(tKavfpi1;F1307wXlm;F2)AT@4E?k!4wh<9Pu@e& zJy3>pi(Y2(j!zgfA)&O+JKj8Ig1L09cYLog6MC(}in=T=U-Q2xK{X{eswo#*b}h{ZZRpKJi{&$80jMp`_C zH1gy(EQzN(Yf|$*ZnW@Eg`CY_sm5c;SC0eI@^Qt`*?{{`j%tW{2E)=nlMPFQ=;bin z3$x;G2naA_Y4l$cK;q`Mew0k4kJ^_<)?z){5lU8HS~&N%vZDM2B%ZXe+CG}V%h_MI zGyyl3=8Y^_07A!~SWiCrHAtCgkAsv|`-TO5L8i$@w~Vmb)tP33$a- z>8f$_3nAH^7MLH~%g2=$VT-F0?}QLYtD(sPS)DhmiidGT`Rx?V<#eEN%Tm-C{M(?+ zH`q8!8>Ky<)>@W6gX(S-8h)xg{4_+0KsG2V%%{}>6vw&CRwJ8;-}+Ag&B%r-QCjae zpTJ%O`EnvmUxfikpZVWOBm)3bU_jLLF$HpHQ-6&uon;#m}bQ(!W3*0tzsqQ>IU9H^P))`ekSm^54AZPRhHNWiUSJB}!?&tn@hRBJEQ+Do-wvH{yw?J4lo0P4f>$pm*E=ObYB5>fRH6 z`nzFeyorHwj%`=-3VzlwGxfkU=r^Ww;5t-st>DSYl0I{;i5DgSlgdWz^Tvx%v}fdU z7^M3LT38WpATIAMc;A@+3j|4^w1Q>}s$HhV^|@HF5!ehWzptazABPsu#xRt{+j}jx z?+iAx_)ZhjYxCDYFH@LG&E6;R_ueUHw8ORz5rZ+9_Gm z*C;p9I^ll-sEipC>ip*%LBq>f2f7~zJbgqu1C7o$fgGDugX_T?KLi5EwhGbaJA)fp z>}X_3b~8^gwhyI|*P^V?;9z}z+Y+Wec3z*0qppvLrKV+}eVNtA#y;T8JXqY^mu0ad zJxltA?W$~5>UCc$*6Rzb*Fa6Zl0@0{fFF1Wc* z?l>I2ekr_uEhjrwzbMCZ(kW#;KR(nkecgPri|K3i4@!L|hpP|K*W2GkTOSy&cU9|y zcOskn-h0h#`{*dhV*Z7v7&jh_CQMqa^ip_TCSSfkXrpgOfPbYy{4Lrdg6O>^5L@r| zc6Y*nMPGtmUkPINKO3yFwIhUW>Kg*zr=7z$1(~a~!~g0@mK|gWVztfRv{(OYvACrt zmEz3N^oLyD&M3?KYZT>m%J8j`zsB9-;WB1-Qpjn|sY^`vkeKc z5{G%zwhm?<|2yz_|F8M$IC$NBKqH@3jXPKZ`KvKO#kfZJEimnv?Ymeq*ON^kD6frs zt3)jWE46#3glgy4(jV)}Q;@H+giHq@#XhRQ`#3^&cT=7?(700V47{Dxazj} zF4yDxsx^qurooroEHt)F!m_g4Qp7suy^09RwS<-IMQ>3k+sz_U=xuuP$sw6Y(#$pE=T&_ABSpDsvDY{mV@-)Fx`D+tXLyT05Q$W;zQ3VrTmq&aOzF>~i!+X7NllrO-zA-drNkvU9R9|5 z!~vAP3i7-)6!Lt2Kx=`?ZJW1h+$%#-34;8~DZf->sVru@aBUMmDkpvxnj-D3^s$Ly zan(1aV~#5Hy#-3B>c4O(vmY{7ZD#iY743`ttpUX+%&NxBP}-?{OXb&W2W~M^HR2jG z6g54La0)5ro&far>QO_;X1<*-pXixi@A?bqQL4w0jCc946K4MJKS5o2 zBNlMbvb2)fn_P|*(8SpgW>y|3orC?-Z$Br)o5iZ>{jd{nVp^i-BQ_pv->nIT?6twc zA!})D4S1J2im*^Gl;y=j(Q_;toi`v?rgTC|fbmD46KO;9BUsKq*w{3}jKIpO=7V-T zxV->3D1kRIdjX{O-T=M5(CnWK!?zkcMm`klMF)plY~s4c1b5X4n|n-iNgB#x-hQYx zJ6AE2n{))Yn14zV8^NhCY-Xl+K>xrvxnS6=kgxb{mp2nwa&|H__SwPMvb%AQ+J6@0 zqcY2ec&9;$r`r6T}%H9v!(8bSY` zfgqYcl=EQvUYH*5vPtzdZ}Z=a?+-HmT{n>(oa@}?Imr*+$w|sCivQ~FFu%TzIKOjR zst+a&Ge*Ho$JM?v!1tuYx(Q4(uA5-xK@vgP1<{^DK5a8TX7eRW+K?q}^u*T+Liydo zMV6(T*kF~IQ#Y+XL*SOXJs-6O&|38?vt?zbu+&#NTBIG8x(vGM_MoS)6K!$XRRtz? zu_C1mtG@t?%m587 z!-ng_(@-F*ee>!ph^T(6;PJ^yfh(xNHW@=eXrDU-OY5|AVwSAYk&e*WxjW z%1s`J%d*k}PaFehrmb5%{9cD?9jxJEV7;l8L7U?O|XOq>T4K-r2n&> zF({{IcJ-KV8H&ZgzsG!wAiaH8C;y_c&3lFST7L~^dlvn^&CjeT5_?M_xZCU>$5NH< z^Ohue?@sbR#G`KmQ-SsCZksl96Oc+kSH9}@HAa5Z{pZ4HWJz_bCFr^KYh=Q`|5`9@X@9@3*qW*zAApea@4$7ffNAr{E1=y7sr7f51J*y-!2TJ>9#&a0UNB z220(Jk{QLQx`~_dO;QQl{ zV*g$xg?fK1C{*7A2#@)HwXZec^s+1FKW6z}z*s5 zt#>lwsTv=sY>*n9mdyHfeyb<3Vx3i5kDfqg{B7pH6YzHe{~d?Fm=XBk;zx&KN2GVu|7nY{U!yvaFqGWp#NS>wV!msbNQ>v`+FJ6D=3*k)R{Z zkbGao3BfX9y=jx=lQ!WS9~7_$e?Td>?!m&(WhMQh0Un5tSifvyUkGZJP3%{#8dAoK z*lNfWU(j>YENe5Z-f2m!c+p#$j8y;%-_ZynR*HMfp^_vZz>2F)cMNlzK1I>#7^Y$# zbkbqmun4YQ9LT)Wb5fccNZ)z~SRm84n4Bl=n!;W8fF3Qc-C?z?t%c^g97A>-l>C;L zCcz*lnb73u`Ly%&n zBhDS4Sys9oL!5y32+&l;R#Tw9;-m@hy!G?|N!(cxFnLD0EIsA)XS|ctx$*8F!I5x5RAbSc*u23D9_l_iVyY_a^gbC&X&D=^e zr^d708347A%|Dm){ur7KwoC+LI2-&!s3Qs-OU_xDoeuK8u;st zeEe`{LQEFrdyz{9XG+I`WGYUW?r=B*8{MXFAYbu8o|v6H`~nD!&5ouls5y)X-^-RV z1?V8Ncbf3JvXNcu|LrMxubZ86TOUYr0Cp+9`B{~|ktH2hlOj$5n^ZIR{nzij^6852 zO;s7KmI-?*K5eZyWwI=N6p+J@;PNc^v(vyyyQir)&wSEQL1 z!dTWqoFC!vUxZqbWi50AO>iJgXT>S&!hbt0D_xF$KzAEqk~_z^9JV{2T7%LIB0CRc ziROo@r6vXazkk5iZEPw-H-R8Rr*sh4AsZk@e0>qU7wI1Gnl=OGG3$#*xg;1gKmsgx z%(`^T?`r)u%)VJ_bK>2=&Uh%?Sv3t5Y*3?@4uTR?1*h?Xy*N&m`@e&V1?oK$Xhz~q zW~IHQTLdH$TN5m=0X+Z8j}11!y90ccthze^S+7{}WrF|S?WoE(y$orc0jO%l+ot9f zDtQg<=tCUdvxDm(ck~6OQt_$DGt60+;K05OWDW^r+d^2YoMfzTZlw9)WZoYk=7{Lc z<@Cse03bQ}%3{KR!}WzrI$?Qfy{vJcc&Q?=4gDsgY z%%2uk%UacnTB}-7YgH?1EniV5m|zHSQElPWQsqf$^l$l|U2&Cw-K3iStV&HKc>mQ@ zM?wG-02}wR%MckW`X5H=8%xiu_zb2+H%QpjgV|<~SNSKvNYa=zf25ZmlY^U3LGQ9| z?3C4h+|sPNe0dK%fWAU@E;*B3ZU~CYWVBJ$Fjo^aCaU+Em?Yyr9|$REn+>&JT6K8_ zAoo`_z=BUM)_7dpV?&n;U>RSr+Rzpb6tNE-j)Fc^)&81yu?PjfW#vFo`fllVkIiLS z(+%Ql);q`eJZFl5b(WQVmToTv$-JcoC%JGT* zNew7>-<=}xdy-SPb9|3r_IP1pIU4>v%&K{J3OfY1m-#UHUw(%Yh>yF+G+HWi`1ymB zOjxCA6B5wXKSlZ048J#^3J;4_jr$sObN|iUK+P^1;NK^ya&x^#idg!zA~}Bi(t=+wgRnJQV&%+>YDC^^@>qr(}GhfEgRYS77%EeP-*497WF3iC<*&2yZ35Xjr?zH zMfDI5lHJUmUEzJ{r`%~$c2z}N;c9sSet8Faqc-nvYp=}W`vx0y#r|AftRqwU+!@$X zO{I_HrQ5@lP9{XORvvZ35Z<&EX9$pMMs30fKwM__cZZ&Jk7*g?8-6PA`n{Ln|C)WF z{Hcr0fxgg(k1|s-=-)MJ{+NvZ*%R!{fDG&3Li-(*wIg-A zKl3;ef!AEQ2|XoI=kJHT++FmJhZ@JTRr2N<4&rPOhVBs0|GoF#B;YXJDUlP;b6);n zrX5(!>o%?_`uiVY($EKW8A+6RchLSJdM8+!+VA~2w5m4Hr=roc6rEp{-c_x4XwT5u zPtaa+z{HQ-=?$SAKbjPF`)zL{k*G&4nBOCW$&dbIEuNmtcd5=l7m}_rwUa*DKM;^} z*`&vv<*K`^{$Ej#_1pxryt!JG?l6mW6RqtTc{TYV+gAb3EVk66A80#o;o+lI^^C|L4|RlUZGV>ZJ|S+PFO^4j>;bu|g8HRw=Wb$y&TroOb9 zEe6at)q1l`-#UDkyrafvMST4&YvKSEtiNok0Ln3&OPgghmX+Px*uU2J#6TU|zq*tC zOEq8dvju`mKexO#F~K`9bra-+9Is96#(qfk5TJzNN;9XfulTO}!h?tg{$M_SHbtx& zy7O9ed}qF$o7m420Z(7cIq;*|9B4pab%2x}VY>Pz?&oFp-@A!SU4ruW%b5#jURpSF z{_U4eF7-^Fon0_*LGhiL#l;JX`TI#zax)jsEG(VrDVUZqCBJxXk!Qk!;+*2S^YiBx z%`7aD@j$xk3htPE+b;|9Js6o-;F(ys;I^5C1iEfY?u6ok0(|8vm_2uX!GzNJ`JTB8 z<_lTEycA)IFfUb@H%hp9yfE)FVUjSL-d;CzUV#!3hL>DAtDvZ0{;Y!ep2E4em5eN$ zTjD|JaHSqAHlKLos+~LfJh)!S`@`=pcucramNae+(7o_tf5+wIJqkaa_5t~fb6 zQ%D_|HZo;oYHo`1NBQiQnJ-As1HL7KFsq=%a~*5igyIE@3g*jAC=n)JPk5$H$;@?6 z&TzVO0fgmXn42;Ax(ULi1)ls%Z!azwnZIEE>|ksb)_ax+7UvXvHn2RqFRDX@v3PL$PPC+PwP{7|Az0VNF3HBbsEdVf`6@T>) zy#(QDU>GA{QrUV#A_!9;EP_yka502=5IzcF8H6h#tcDQh@beIU4xtaiA0XTVVP7Dt z76>nb(0nFzGK6*r=RlYS;X@GSK)4#hIS?L#upGkin_B~qL%0vZ6%gJ7{IDL=LD&f4 zQ3y{!m;}q9)glNf5GF%-J%s5HT4BY^gD@GwG6-D|Rzo-&!sj9U9fUpzKZkG+gykSw zwm^732xn&CVAn%vhj2cGxM^7pVGe{VA)Eu@mk^di*b5|8Jc=+H!W9tCfUq9IWe_$( z=!5VCgmIucSo;Wq1HxnouYoWf!r2f`hwvc?iy#~iT1+*Bg%Cau;e8PLAbbMCJrKSK zVGD#l2+e&3VJC!k2=_sl2H~t7fDgh$5SBss%uc8ugg5MJ4XlFjjNO0-!Zi?{!1#Mm zuYNGz5GF%-4TR|s-UQ)v2p2(E1mW)?Tn6FG5H5#s8-%MMwC@3Y5T--e0^tk@&9H8k zL1>5YHxQ;l_&9_)5dLc~;DfMeAG8xf_xsRq5T5@*YoGzbuOV!K(EJh9tG^&@hA;)f zWgkO+2ybhI{19IC3FL=R*bn(J9fS=K-t#Hshj8ox$Ui_3)_(^1Av_8~?Q{qadZ`Hnz;K)B*4&=Z8KzJ_s!aP)VO55lP@ zAs>VrzK47e-qs5321#N&$kyq&XD0*#c@Vy80&Nb$g>iwvatPDn0|6g|$02Nla6n=p zAPfS!g3u1(Dp0HcUwdaC7S~nh@e51{jdVJuG{uCbla#h3jdV1S?IkOGCt138fJ$8%dn4Y{f37G{r8uNuVu8j99Uv#)@64Y)xx6(nuqX zIA=fS-gCon=N@pMXZJtu^OR5L`#ryVUhlc*zRV;C?w(3KJd#8_Ji(QA7}icB9Lva6P#LMUSTm*#Suii^Me0VzXaPQ^B&oYdL6yjmg6~x1Z zGl++8zzVn}m3TOPmJnUAE=`Ej@X%}_lF}HTR|}B=pZ9aJOp`y^AVd*-W3CW;C9e^p z2@a$a4=-FxJZ!s;_^TLy8N|bN^N5Eh=MxX}7Z4Apzn^$`0(QZ|fDnW5JoL?`-xdlH zfV&q75rkP;LX^N0uof2FBt#3GyI6=GI0Ogbk(-IX+AtckiHBQ0NIYD=gm{?zA>!ey zumv_PB_0;sLOdM&2=RWy2rnZZZd*<~Ja8-Vuqu~$n7opBIDIwou>LmU;rcbi&oPX& z+lhy-!XP}GM?5^trEC*CcL#B>?Bm2ie}NFGb2)z23y}>6Hqc-2u8l&}!yTK1XoI^8 zg*XKdY!+e|hVLcr8ph>)#KGPoAqwDu`-LcjfzJ>RPZSdmYqt_F_3gyN^(DloGrxY8 zc-Xarc)0R&j1#!<^OP4RmC;YI`$75{w(X*yuVsG^(a$jD3-mM0t)QOp#3Qs5?Ac8_ z!9A7K7bbp@`og_cw9|Er=P%JtuxT&t1m{0WJk0tE@$mU-;$e0T@o;%9@v!xA;;%Q1 z%&!s;TkD92w>&{S%>Fv@aDP4V(0hP-!)`bXfBy~Yox!~NP2%7iPtsm+OC#+CGrrAn z1+%}yaRu|A=D3V1ocSE>HQz9v z`X1$ljsH$N!w1^P7uJ8Dd|{Al)i$^b_P}a*8a6=V2E*_iBY)^UPX6#b48i_(@`tNm zAb*(q68S@;ll?;;-heoVW;033utn8bRd z1o~ku%!Vy62zy`=9E26ncanO+0BnIl*d^m(zl?{&G9LOeiH8|*5avMNPbeSEfhDj6 z?uE553|nBEjDtNg4xW~A-PHF+xt^e(r2Pe0Tuak73KJkmun-_Xu;A_q4ih{;kU)aN z;O_1d+#w0>9zt+;26y-1F2P}dVHjo(&nNG{-~I2i&v&jf7i+DWwd(HbuCA_WR(Dq$ zKgTe7s*k5(==6NPo8e^~=Olrzk;l6V(M1~E9=^jK zT_LQD!f{#o6n&=~HODmSS2RW-K(QYW{Ml}hI1hR(u!=?q>&Un&5&xTxbxj2tz8GG;gf~QR+)Se(!K;H0%4A7 zXBiiHk4e~(2`LBgppO_~mL*9%zLCgCq=BvHulz0yFX6#Lt3s^^tLH)QslsjO)LkUV z*a3XHVjd=VI48#KGS)`*r!fQYpuN(!s|*Vu3+h1rpxSs{7M@pojOhVS^C}@wk7L{c z#X+iQG&?7s58xdK78`9)!Bqe797^|g;V$xi2;e#AdcgEG(uP$j{~@&kJnBR(faa|= z>+U_p305!@IW^$%SsTl@}$C?1}LuW^ioASA-qN)*?NVI0WGCK%$Mg0JxaGiId87rEk`pcmzLRIN6OE8B<$}3@NJGa% zTD=tH=;lfR;ncz8uBru%9MI-4KriKX%}^5j``Z~7Oc>IPZy*ssCj4p{A7`gM-PCku zlO|3WX2RXi3bp9CA zQL|cvoD!@bxRFNl;e}n}x0AH5$ezhndaxo3)z3OU4)OchTrucSlj?$Q*n#i%|s8FRMp$1s)sXFu;)JP zK2g%DkG;cwSyg#QIn@}p__6_Y2--os;--qDiZZ+-#71>4aad+#{mYrR+2O{uM5hv5YBfL+2T1=@As(0h_gkez zY~lsmMFfCiegP!y9gz>$9kc2#<=%IYh|lxYqo23xkfbcrW$9#g(66Mk7@^wL;PC*~ zJ|Mg&!mH~~3S}`c9P|u4;S}%KS1C$JSZM<{gx3S2B64itv~WWB$o9&Ws)EVbbP+f3 zB?3XyI&o|9kg)SMro*oY>bRLR4RwdAA{gjYtZvrXK>g*SbSnQz!FBcYDq05w|CRG+ zB*z8YAK@b7H&uk(Iik>k#v?fB96sJDjCA9D%``c?(9m|mi->;5 zPQb-TM3c;nsyz3%1s0gk&E`HrgpP~s^q!_G$DfT(!u$l>t20q&9rp1&BVv(6FhJZ@ z;TBQ~yW!{sJ}gJ1zM{#97b89GB7k$zQGXveb*Xf07Ibf30Iy_Jk5Xvwjg4P7^nO+; zS1ik}nb1f$*M|4`#lO&ki3~LE2^}RM&ge~AmF|aOu^XNUS~~n81BaXWKYvojgFJ>P zPVae-;*ke5*8{$^0ey!N2lFwv;!vEmmDDOeWVV6=Iz!5bz2j15w`7#_h;D> z1vIh@ySdP}Aye3goFs5PfMt3o-dr?8?V*6FSP;CM_Q9_K6SO2+X8ZO|aBD(n0w%ZZ zx#f#|MR=KHSI|lEb-#;Eb(h!|j=ur8(0UNt+U(WjEptS=!KXd1uSfcBfs+#hp-^V- zgZ&b5sOJMG-Hp}WvK*XLo-Us?0Pg)$PePgqb_c-?D4^|Sv=WPMp7vKKp^ae$r++PqThcokzv&eVGQVd2XH#BiXJ5e~RF?y#dCqAI_yzl7nW< zyWJ^=oTppydM}zkiX$_~Km+KPb&Fbsptol4u?@6}o$Am_@xdU=0$1U%mGnUUz zU=_s3oe%zZN9RhG*e7>CNI(whlNFA>7ry6uZ?>JV0B`gnDs5qcyDszVT1VTp=h1ry zR}%v{yjqJ_-lGVmi+w}-UUKF8`OTbmIS^&T);^G3Cf|bks1Nw)r~|M7nY49BvBX>s z$rD7dYXG8C>;XD7ur9vtpZ)cyT^)|;@I2q3B4>5px4!yF+}lr(h;)c}YaV-kwY@N- zd9oeh0+5E1M2A3c(DxJGco!H~`-+Y{aP)z;EtZTk@8sqizq;)zg)A27t%t2HG~)GA zfn@>f)O~!cMmA!fv=k74pCU8Tp@i_x6RFXKM8Jt+TtQp(mD(sG zWh-agbsbDeLvbEG|Lh{vxj+0vW4PNu@F*sczUA8lhg%WafTA?dmW$f6=Ua?pZSELg zZeRq!dM|6|(>Ri5VPYBQTsr6ZlDjT-z&>+d$UnuwCyzi?zF51<-5=o#wKbX$LrUivw4Iyj zc6&NaqlyP*rrGi_3A(mpK9nNB*_?sgq6Ab)Md-8~fe<_5biKr5UfwZ;*Cz!VtPKsKUURuRZ{{JM1}jgL5ID;*$Ol z54`$JyEYB=-4V5M->Gax2GStXpP$+66{x^3D{-7Zor2O98b>qIkFK?tK9%gFqd`lq zZTy<`RlO^Iz@Y%oi?Mqo)ExO3J|(o?5bUR7l8v0gob2c&8?B}5sp8bI5F2;9OcKPE}>1X7`6|7XedGEM(czLVzLs@!$Ht3r~y<~(f)x&By z^}C#t$?zS-RHetkP(X#P!E|v!rN>lCKBfW04}wbCPA0ySe>3D#xnmz~JC99N4RN8vkTz zM;XA0dUnAKQ0ReqmAMt^3XZ_#VvhFkdrc`40_%y+N)8N zh7Yh2$!)hh`l+)|0n|iM!pNZqe~k!CxyK2#rO1*Gw*Qu9(8FyE0qgWvEzL5 ztNyg$2zCdrz@x%EOzmJ2kQXTs=>ughpG)^qK=I&V*`82# z6@GqnvCs@j@qXy}BJ?&cCp2e?;rKQdE*YK^5Bvg_y0fJWU_}_lI^F;R^b#Vx*Yn8R z3#{ETg&(zzX|RPc89oqsWu~@v_S4-A0*^Ng%08K_ojfv_-p^~ywUjyBgAwH6_{WN` zhIm*>%%Kv)d+Wxz=?IGYT$`~SN0FfTrq&inC5>PK# zuh{?DXY`sNVA?YjYV=8T5FRy!w*Gkmt@=(!E2*?CIN<2l^r-5%XKZg4-KjE3aoQQ& zr6b`E^1TC5)iO~~LMl9@Kkwzc#T6H9cBk^dFU25RpWuy<=JvHXz8euk=7mpb{kj=> zI|2Hf{U^k~Mn6eI(;ICMF5gn|lJF`(Ti6ExGl+x#ndHwVnfHC$e;{NfN01BBj14|; z<~Ul71xAC+K8PB$8OC>L0(Z-)${Y7F;`Zq?k(9ei(zjHEyl0~3C6EP?;jI5O@2yFk zDgH?!Dj(q%8vpcKgbi}fH}2zpkwrZ+^%gi)8y{oPn0gDCbe#m)HELZiZU>+PLTL5} z0ilvwPG-kKJZH$Evp2cOgnDpiR6103SH0XUZJ98f+$gXASz+N^d(Ma!+xkx?$*xtK?%CC?F&uCWT7R8 z(838TBD$Y2AK(4bqLXHf=tXEtq@1EIWGNSAHOS@(0H9D|7X-Rq!B1WSIYp;k<;0fh9FkaQHUp zvv+>n0$h_zwWfZX&gE zYzry`cP-Y>+SGYJiD+`Oe!Q+4soXlTH53ICx>FoOJdUo`=PX6nBVfl`_eA(0HUBm6 z91LCo^J~9A);^sR;*0BTW=^$PqvDMs;=~uwJA}Qr>a&j`V4r89Ru?Pt(!Vh8TOgiblOt{?_ys57Cvp6!U+R^zCD0d6VoOZG zO06kY0j=4xjj|-6k6JTe1ND9A(<5GBTPtc(c6P~FVQL(*|EJ5?nqTXjf4e-#4U5OI z2Y6z;a&86rQE$R@`<{^q+JOyEPQUQqe;9e;4l0r2_47J@7>VmKJpBYx>Q8f;By z*WYYk!FAGT2K5hsWLRdH4)|tw{PXr)kjgb}o5M5k;ScYG66W{qEUw0TbZI^xH-2<> zPtAJYI^OH2?LNL{=}X{E;H~CG?r-zCT275mQx#DNlBeC;-|JgU^=HY-S!@O`dq_0R znH=JecpF{CWnEf^7XVfagl^~+JngvFShi=8NyI6y(rz7FbHGIzBT!#%n)3!y2aZ)M zGY77g`!yjhgU`)>ql@oq1d zm-%{p!b}I2C!Fmtm?Kg;n16}gxH4uu9+vmZCyi@_j)3AC9SRb_7^9(tX^OoKK)yDo z=Xc%A?~H+qI|q3`_21gbz46M91NX#@LR;(}vE~n3)#BOPgW%prtYFZ#m-Xom?CrUCCda-2Vy6)7s3^{4YKW3*|qkH88G# z!($Epd^`vq4^u0b$9BSHZ|q=gVeaY{JIae6#6$eJ99RQ_`cyX;m&>roAO4oiKBGgP zfp>+98IyUz5jxNCbBaj&V?c7|*AUSFg;rAQj>5He`CPKtI9D==kWOhkMX-SD*-JrA zGfZDNwI_mD;TQ8qtw~eP7=Z=7aH=|NuDyvS;V1w5XAbLs{1BR>cBn{EqM$U&qoDkg z_B6CUahci~ySm2eWBXC`&8<@j-@sz%_uYhgsjwJ!_PE%s?YY0zNrlJGNVDAZzx>Fc z^*!O;ef_4*;cDfrevt@VR7W0|L!e?FXC*h z&HHL^Y}B_FpWtDCUi6LfGO_jyW5;|!QSX9p9o`m281x5ihk1k}#g=Fow1T$7GUAM4 z3o{IwM@ucx8;^ToDh=;_4tN~H#=UTrhIc=YC)-L1gC@}i*hj3BZ8e5@%{~Y7_nzTh zU`oTMo`d*$O~brao`VE>iNc`KvzB>J$G16a4|p?Wsr$YV^;g-y{crrp6iCCveFi1+DCpGX+@`2PabvPhl+= z1z`DQ#rpC<#22AL>(v9n=aX{nOC~S)t=gV{dR2pQtpa+wqBFf^>fs#V$(4V0l%kVAr29sHK7vb#$EYdCNQ% z_k`9ehTxa}?;97Qui#$ZmBbD27kn7H*ydKAeH5WkE_(=lMcCqI|2S|3u04>83N2Zc z5#({=1F{Ust*W)q*2*u4XgJh6rOC8UzSjLYo>cncM$&JIvqhs`O2=lA;NqjFRzQcg zZ2>{<`g?Di4jZK=?FC%yEN|hB*BI3|6T3#Nj{-HRUkQ?%ONXwvgZ@8o^FyC1am{s{kfw>?S@*QK7 z*npzyw&~j?b(2h9^^&c-eWV6!6Itf_Z@>Ml<#e|{>1M7z{eAGp;K+4vMV&aHYT%mE zkxG+KBReOZv5H!Q+P(B`jOw{t$r;Rp=QN=dWdpM!}nLm1BNWS z(ce`%GAg|<1&@~4esA8rvI`VA%s(1e@Juaj(l`&ZhA57u*o2CodgbYMQu8Sl)d)`g zs^E+__`aS21op-(G{Q3RJTnH*JoagAShji-RF=jURp>~``H2cNiA#q{#s}Xo4IeD= zseI7mWtV!p$dk?|KScRmX0=r=^Syv_eRdW3Z;5o58f;X0m0_DNovCPP4{3?Q4n@CW26`*Knj$7l%Pg$y3ChN8=E{owxclE(ZC5;MtT|jL#ElKm@7UAd@F%%2X&=#s zN}mjk?z>7}gphc?@CYHpG#Y23w~o_QHhv-^Xk6z}!L9aX24GW^*P4W*S0G?9x!n0u z$0RpUU8R3iyUg3CJDJI9qe(cuN}eh0OJUk;BH`bI+UX+u`Qsr{k)qoS2_I_|mb^9T zaHx~9KVUcLB^VEUn1a2fGX6OGs#@L6c5wIby_qSmh+8=!Kg9@ob`MBf(JP_?`!e+1 zXj*R15H%GVRtBpGm4?dnLMeYF8{T?W!*`3}Q6Vdy$+CU9W9^s_1g0|o_<8xks*#26Lvw$-o~bS;-u2D4XLM`HFeWpG~}rM^#7*wV3J z<71-eOPC#epMD4lk)AnKiBFs4msgtIUdZBTvzo-z#0ZvS1SyCr2pnBoXP3iHWM+r8 zOPaF2mM(pBrmQjO9x_+=$mHTEpU@sSt-tzFIB= zVArIjhirZ1$3Q&6(lKF5;1x9)PLSbqg8g8gA!lFvf@bP5yHz~eiKSn8vPd48My@;9 zv~5#(&D=iz|YaS>vRsyd&0wX@oLKn((=t_{4HX#cWbcX6K2GN<}h# zpi>qi_3igS9T!$1Po~#}!Z60yY|dy@->rJ>n>hNPkx@5n8MD87I^X`>c16X!=FLdo zM`nt+P5r?*ql`Ss_W}of3g6$GYG_A(*zi+$^(xPOc;my6>`9_!aOabFa|_;%?mvkf zl2HWRl$y_uaC~8+!QaI2auuV^zTF5-XoGQT8yYDvb!Fh4h@&e%jua0QfwHxuo}UUC z^~He)Wdxl?2My;cK~)JuLW@tScHGfgqxHM(ovW*QH7Jq$v*Ix6Lbj3uz*$uXTCN%&SOST8ma3qu`lKvgS~rptTs& zM|oM|;Qfx-&Sp_EoDlDMT;U;c6yv~8B7s_Qu8CM)J69{oVG(DMa9J`u_vbR`&(N!$ z9;|sW{%RV17aRMPaFmBN<#GGnKI`h3Pjf$pJZ1P;MQG_#Eru^_oZrVyW1q;J5K{bz z$*>l=?$$}=uFlh0UM#b>nHLbW)w41-8#`Nn;!V*8q-Y0bv;i{?$r1)I%PZ%k+=`!U zevdG6-S0LNLVN!##B@g1$V{>{>g*wRurY#24rexAkLe2N%LSX~f$%B)jwmQ?|gg+sR zi=DC3ZfdBafs=v`KBQV;27^>tEN17NULuQOQBS{v!Um;9c4K_(!-sruF?1&jGjLFc!iJzWPo5{4Z^i51cKu!*R zCS8yj4i?+?*q5P~dz207&-KR)ea-6=ToJP#gQ^wE`B&aT6yRRgbOoM=5ZbfQuT3 zZ7n%4J})(Ge5Z}4T-JbYB1iuv{pMHs9h4*t@mHvNg`QJnR21y#g+guvT`!`iI9LHU z!o=rHmOZ<>-p6a0o|cuY;i>)X!|uTqGiP6CFb^Kmmfboqg^^T zb`vP@V8ZA&h>wk}+om%RHIa;L<{iPIC!m?UT@dhTQMkQM=8G^@hpeUh2WQr>P@$QA zo{4da*-549!ZQumV9IJAc_nrr!^-?S8de55;JapGQcBSBEgMb!V3g4xCu63#i({85 zs+c11N02vB&eDTo9HFl{!SBW4{%e1ynFJD96eb@WtxQhNf?hg^#Ol7j^j6)KDS{M z7RAH(w%@PWj(f3|-wz(x{fRcyu(sL&%tK&K-GyR#T{1?pa(DtuObLfEksnn0g(bd8 zy?yptOvbxK!Ftg|?!ERQcFP$#V|iWb*9qMiKI8h?ubfN9EmUl=b5q2>|7eF>3r?l0 z2^{MEq+Q3?v&hV6CQv6^coX{J;p+^O>zW>BUqy4jDcjt)lnmneYyFMI_a|)>y=bu( zFG9+lpW*nn>2;6Hwzflndxv32;&--l3`$L@(h7%3iSGqCXKp(R)5sGl)th5xzSPL# zNDciAd3YgLV{FIRElz!7*bZY|4^z^M%p?j8@PDDY82J+t-q z;r-K`=m)!MU!=C4-@I2o>v(M)D?qOB;*6h9K-?TQoe7asg}r;T7Lafg_9f> zlFXz^(hih&Q;8TfD{!P31DRVn9uys);3eG1*5yB#dAEE#ILV%leHmvQI!L9f+@>|o zXus$iL~T<%WWWk!KBx|@$gc9+pv>JCljZK747~Y3iUm=VcWgl3KrdIvhhE#iC%V=d z)_L3sP`Kkz{8VE5y7hS@aaZx-N5W>6aezzkafWP*CTpU_xBzPDK`_zVngTha)NG*n z_b^cg%rOmKkus49*2HJ^SGl+82?K|3i^NW(Jn~+#?-yT*Mpx_{f06YdQJm2%Qm-&u zd3a0t%l0^dfQI;Fx*k4z>J`AXeSMndZ0p>pJ2rum*|c}gg`+6!8W_fH#$dV6c>2kH zv!k7iS7&s=BZ=MDsk%a^im>GEsphXx7lrZ3{^pbEV~a5k!a(Age-p@Q26}RPt!R=9sj&~J?CG4eHi&6M~K1UkfY&yuL+*KH3_`$^7IzimAme91{x~(DUZj~5CONF?Tm`WuU0Y2#+n|K1)B%Z3?+~16t z->>m&xhZ_iFDrDc$`il(T;eG4#(;Gq^vB43?igPy12g5XG0hdY8w=p=Q$T1n)3X`% z9viErdlh`)9`(3SuiQ#xf08qEnGe?-UJAd<@s?Tf)2wwE&aSOt$z{WM*)Xh2ZyX}+a9iN@u6U6vzDdlpSUH}dfTzt4Zxv}6FjmY&N!4Dht+*b8I3 zD*En;T5kN|(tVLn=DtqkGwbNwq;W-%`z_@g_ItG%x^b{tbye;6O8q6>O)2Y2y)K)* z>egF&R?Vdeud9*<5&W3MWlsZ8LtQI6^6JA9iBP`k3ghVyn%jdV-ao(9SiET(78>#E zIzQNZYHbX5bbfp_9*fx<4B4jjf}!q5?Q$*?;#A+L8vFAeNjSGQp_LX=8*JW5IWLt% zp2p#az)%Mw?yFYH1w5(`#&@D)`2!V1iG`8_C>KbDB%Gvj0)*>kfpWjrl;~b6ClwES zXGZ!55}`3)A8I|nSA;o}JT#>n;$zOaQscysmt9#_;qz;2ts1bDCm24RBQ^V8jX9^V z`OL?VXa@Lh$2K%8)Fdw~2}j(qN88ld*LZ-^zdae-*fPLjoYQ!A22rXv-pGAQSD@)& z)o7Pj(d3leXdo0bWnil>anQDrh2FQ4GQJ=ES=ML!*=inD!J)3=48+l2N#Iima$29 zP93JyX&$!!1vu>Vv}xE0&ntVgz4M-Ijlhq!C*=t^W-|p1NzhY?62RDl`GwmO`fP)p zyyF?vCJgu#ZWC7dBml3c@@W87PbFpmT8|@o08x)41_BtijtbWdTgQNVgsne;tAwpT zg*$|;qrnZs)-mA$VE`1kWEcP)ZX5F|-qZL5^lfuF@XQFRve8P`K>?-Sc$Wa; zeh|*{pm~_1(e;l+R4BLR)HnHdy(D1qg^UbS`>XvfAYeTK=hq>B)r5n891m5p`|eOg zP55aDOLr6vA{*$CqEYZOwo7pFAX1_SfP$dyAwWY$Y|@}1LpF0zksqe^_oF1hY7lRk zsr{5kN*!`nZ?eA5vi+b*e)h4?5Uc@18Xh7si;y%)2$1@@Dehz8EJKkYd}}~rxRyDb znFKBQR5FjEG702(?QO4%JcX!oPWfn!J{;Rl5LohOW;Gj!cU*bNs#zF{U36=XB_xZ9d<+ zxKByBf0}Xc0Zbp~i2G>;kA#VS`!J6SN_#(#3%Y-{@hitlKBWIJ>_qEkzvx-Vhuw4%=j$#P#54S3F3_XVO{GETzLZmE9z8gu^+j{ZpCw0 zPJZJ*csvhf{}=f`Z~XNz!N{to*Z-paZe5g0=H-32`G7U z;t42wQ{?;_cOAv=>n1nK`rx^8ry#!{7W8G9>u+VNB0MVmChp`GA=1u6M zTgENmUY9WTPBDp!CzWy@^a+WQTc2F2onznwR-;L+cu z_RT>08RCqguZ&&-XDAYc;*3{FgZh>qZ>kuP-G&-n2tINqkoEi1dWqqKNI0mA1(}?m z^)zv#$4@u|v8Z#lJDzh$6bmwRZM$AFzsykrZCwc85yVdn(P;arTm&xm!YOWB7Rv_L z^9L<2S|JPe!(L+9%pmiNHpoIPF-Tl*>Ws1vsME&%a4q(Dg+E^X9kj>nXo7h#$4f#I(R+n`$(*hF6@2a& z1P|ROir_|{V3xoiy^6FA)RB;Mj0@T&g6y$q{|^$J2PAlW65J`4(?pp1KOT5VIOFh4 zFgu{_-SwV4-p{>C0e2;U4a?52p5^QkP3II7OZ-Qcfo%47)rd!@RyN&{TL+f0p18tZ zQk6CLL+q*EmlPf|Bk;c5%w*Q&Kn0&HsgjrjE*#P8a%_eJw$^d202hA2H$)SH|3w_Y z_wO*Wki&u)U6=UHNT?v@5~xz`_)f7e8IC_*li_^;(1T&c+B zc(PvYu74T?tQW)}4b9=VtlMB9ITB3~Nx=31D1;!Z^3UWGZ)iR*fj(GITyi%*mVn~& z%$CGE=hN}nCBF~B%j1l=+^Z!28A7*?8b^_!ajhF=`N=z?ND%5jQpB zO*KRT7FdJ5JHP^v_D0`bV2RHc3%f)gPGC{Zvwi_ahF)?EyLLv*Udysrt7_Ix0>+MI z$x~*8 zCvWkld}@6if{I_5;YVF_*BgY_8{e)sK3;E3T^s$R7W+Xh)<7**K`qunEtW?uR!uEd zLanbskgZRUtxix;j;Z?iSBkkXH}rE$1+HIN?k{&A39Ur-D6TP{O4#KQSRWlBq}CZv zFMdlxF&=E^#x>@tr&S!!&q!|X;C&tR=s^?|qui1ApShbQm))`^{NMSdMKT5KR|ULZ z_VZ6b=1$N|?;yX)9KAA(#?aHnqrO}{HrEZ8bKn&gYdQ^H`cv;|tG8ZGZhJP}uoeju zsO&fy-;`b=kJLC@NM8SuCG1`6F>ftsZJ870lHj2dMEU8=`1FpLoq)Ya5?M4IH>IFi z@WveKHtv{E9Mh5<9UtX%lsvX;8*5xCyEi(R43s|0oLjjA^CE zI0mua+|PS!V5fHU>_(!r{PYFTTh4cp>{WLF(&nma@Q$4vUk=}9D%V|&Lbx#2fgmo+ zCBXu$M}cqXq_#{QLp8hA)hjuM%|u{BYFNryydFb|at+&yXixXj)C<4J4lA%iV*dAm z$Da$WuI#8ND642FDE~AM=5gRr*2Trq<$sPox~tndPOFgx;5SNXtze-25PIs!zG^`= zxMJoY_XcBIB9II~oo7;St)Q0)z#%Gh43$@G`us2j*oqg$>{;uIH{U(<;bS_!VyZ$YWDn2fFNS->0 zRd8ed}uQH|uLWt*Qm$<85l`!a_wMH7AxA z+Ol0D^JJR)b$&Z7aY|QNFY)LPwSKJTEKzN@bE_<+w^|1ggomwKE~a~4fuuI85>vBW z4Pa;;{Pg)B;yI-q`4k<$s=Rwir1iQz3e_KXe<*GPm7&Jk^Lq-*_dGKCtVxq6bTnVA znGE=PYd`Ty=}wzaJjpK(QGY)ej;_Q05&s+bZM9mhJ`73+6)hrdQvS z6`~ckmgL`WKmP$D>yGb{EJa1c6~25ejEI_z{-T>8-X$eROS^$R-7PL}64ZB5Hy!)i znV4?@6c(&M1zpr-PSQ_W| zS#6a@0*1XX8m8GKMw|WDn@7{`?v9Q;Q5eN0jsB#{8DcA8_C&=IYZC3eOl&Gz$q!o= zd7-UgA|CfW4li-LsQ_MMVrSD-Y8*{F?UWG|_?i60W$Zrng_iv#zv}uWLEGYagJ=oS6Dzpl7a0bS(hy<6GyLu2d9$1W`*X&dZC8&-_5GVVm& zmnqyvQZ0vR6em1%lb+aVCJm?0U3j%I?ogk>KG9k3A^G!F3eh(2+hps~T&4w*x9Y24K*_A#(L9EbfSjzIszI8h~AH)zH&TWISSN*g1d!X-6mZPY^Hx{^N zU*9@W30hTwm&K2lPJWyd`*w8-obrdfRK^p<@c4{6LZRJ{+5=Bbz8}Dw`c`KX_WR?e zrGfm?Ssyvi-I7Q8efGZ3TW5~Buu@ZQ>g}vJr*zyz5FiACvQmMWr~gph%md>9aVV^{3WU zjwc^A@BiZNXWjf3Je!^k6q{o)`q~v!9|_#?iX#ziP!mn%mHAw|BmC5~^qg7&Vlx(P zU}DBM`_6>Jwe%(9x2RDv29p<75m1agqM{CQ-8!~7cNW5M2zu7C03#@kmElILk>-&|!MjhxXiWH+$8zP9YEE4J=CtXeu=~62#@$eg=hffI7x%zjLa+WOLVcAf)<_V=DyOv{a&tH9LxlT zkQHecaZ>stCKm>#m5~^Ul8L5{G0TuT4BM~ja~HOZ{AT^HV!PME#pi-3L_gEsn9i%m zWOm|?rG`sUeatf%ukL+kg|;-)O+Y~N+(vS&$3r<@728_eLt8Oh$3-mfShBzH#caM} z$;$z4iXB(Tl1yyOOxZ})t0`5vT!Y99WOfb9|7fXA$HC{D(#5Oks#b-Z zYpQwHZ77ZVy}WEn=GyXkSM?pJ4ee5ulm_&!%9&n0rSl!wtIW*N&jgBy{HCs@*`33G zb(wZ^J3fuwNlVy`UoSxWv;KsEoz<0_%J<#=FbC^|fek`(uvn(}y{lK?uP<10S-9&9 zDy5+JSbCo@lt1uk=DhK5v>BEPAL(O@G2hCS$q(h>95TbMRJOqr&?Wn$?XZ-ae)7o* zgJj{wlEZah$=3z52Ymhl=8|7KO?H~HX=ol zkA&JhOCI1%3sZ8-qa*0Z3dx>Tl46fo{k2$XQmpHfcBRc!-=xV{AEPIOB|R*E=ItCj z9P)LtE<~WL|LI<0jCh_V3x3;&G_}T`d4#9m%v7UT;sQ#yd<2$Wa;3U#;=|?N ziUd!qr5{IH8A*Yu%uC(wY90Cyf*bhR%Np}MYBlX!lE_!BXDsDXj^t}>TI}>)coQX@ ze;g^Qy#Fl%r9H+H=J>L&@u$M(bpP0U_7XjwA zofOt5lwZLa)lMN9E7UN094r>ljo(L|`X1D>pIDvB%a-EgdTAr>R?K$6Mo;Oh_`xcP zFw#6kR|fitrl7`9o2Kfv z;!j;(uD;0_T7wv>8VaZqwsetmPbUTj+*@2P9?F-FPFROR$l|$-hqfb zT{!tY%Cl>1c%e+&{B8Wb^Q=X0gEtF){s zcn9Y5?%Nij;USJ=qT6Cl3u{E8M4HLZ3S~70R1AVjOqXYiZuxjOyIX1}^1e6h7-&5P z8f_f@sagKxLA7~D5O#kgwOzeJ}PaQT+z` z;S+awTNHJwhA9^psAy>40U@gX$=3Q_voBlbk7pC}m8xfT7PvXFsF?Tw3~u zNdUObu+xvKW?3Zvt2+8*uc^%H_H^`Za}jO5299 zqSQpNqUwdxmVEx&E8<+_+^3tOvnNEpvmit;XaFmIdqO8fFi?;v8^w|K@x6^^_j2bl z_8H0wo1QX= zzMHp6&g+$!wAV{9d8o_z1J*hX9HpB8eE}PQB}TwyCBcDs+=TD>8J!muA$XY_k8i7M zqacI^)!~H%MgYsE>^VtDO6LG}4hkUVT;ZH=^Ck4tX^6f8lBMY6{ z7EJf!wpieoaFOmxOmN^iMVBKT+~Qo% zg=(w386Vb=`EVk9FY0o06#F<9@dRFxiEMR6oDI2$-0rDB?p>(v2e@S0P{&1l=ZgAov56 zeWUUP;uZ8L1pEZSwCNZKR)&ghoV`F$L+?XCkFSn5X#znXpv)UNFA(I={SeR-1mh+! z5Tpzh+*p5spn~2uQ(O^*Ah8fcn}A@X6tr>!Oo$ML(u5$f5u}@-V5Bq@yn!U_An~Kv zT<)^OTBk*vl#Su4($9Y@2WSq?(-d&On58)yoqvpV|9Fi5=LXpR}e>dOC6>MX8lav4bzr^jU zHdC!9C@7qdU3kHN=)V806ZOB=PS}|%hMe9j56W) zM{JzaQxV+>xmYArEwRh5^?GbhKS3+q{+N++OTKW6Xt@!Tg%Hyo>~!A0ZFB56Yrlhe z`(jqbOTA)B*4P+SUWp2A3&q2zz!V?q{DZ5&38N|@Z9ox2Tvt!%nbZp!1xU*wB z9oy`~qouj_-E}k$cB$!~@)CT;Qlvu?~lP?_p%a;Q3FJGAc#aaFDxVK6R-V6rcJh}a**tBc)wdU`J+Yu0u) zhMEy%AsZ zNhT5V@TDNMgOr|Rd;x>3Si_X^kjhbo2RuPCuxemWH{ZWd-_Yi~S>@cJL{CY3rmab7 z`=$&YY5V33VryspxS@$=u75&{9;Wc+4V3f^o9P+NsDZk6^9||PGQD9Gjuo2RU9-L9 zuY6$&@M8|V+Jo+lCzgFeZ4oxVNB7r#FUlSAC)@cA_KF~EaRZOlV|qi6)pH2yNx0TC zyycgC_WKg>hZy1s{e8T>=b8z@cx7?;qQL_f^`q3uD zNrG6R9Q>>yTxZHQp z(3}uvmR8=PV5f$Bio}m^VK)qGij!`GuY$_}R=dg$+Xc^x2bvTmt^87sgErE80cR9C&<%8Qjiuu=*wTh;9RkqDpvdwn>A1Lgf9@6MeV zV|fQ|K}_m{u3n(R91p9n{nX8NGUhqK)X6Ye@QsmxEAtA+uNr(-m&5z?nc{3fkhQmmZ zIdNJDDATnA6f1WVK*sn&V}9=EI{BiRr&-4_?Qcc!kCc8gCQ5)jio#% z-6d6fwywTbq7x<8_Y0pth6)bbb~I&lDkdHsG-=`? z<$b-jGUYF5FAGPs0f;N?=lO@{q#j{uxq?{+MXUAH`_&v(D|5~mQC>M!tu^h?g3aWv;Z4jjt-5%aK&8`OF#{y zGl3CwRXJZ8Z#PxNHQ}ZdAm8K2DV{m!INz#M7;@oZ*ok2XG{)s-zm)|3JvQrH_?UXp z8DjdnI=%~srKe+SQkOlL%uxNq2{BsG_WBjK=a>i2Avp|1g3X3SM;e;r63)s_UG0)nYgo=`nX(y;>YmeB&V{v?LEw98Y#Ab|?yo`EMfv=CXewaPb{ zw5#Vwx{if33TrbJ>fm~qx>9V!`m`~l*a8`>3ES2#<=mA86`gVPJIWcdXs7}WGHCv-RO!e*uy`7__9S6&d-q6Ms}SsDgGy zht>xI?jSm~$P9U^V7GuY+9WPpk}A&Isl-dD(S?a^tG}l zp%gT*KVBVAYjS#DIxVn@wD?R_vX_(kV>z1KkWTxv^`1{~zY#;c(ksjy<01cwhj?J7 zGJ2sZ8N2Nb!XexgQJ=vqPwebNh+y^WG4~p3@K|ZsQ>QxL%qg-~*iq%in@UE0b>B`K zN~${w;MY#n6rAEUb9|jwxU(_nU`Lm~pj#T0o~ryALj}@uX=k=7yYwS}#?h?<-}q8~ z9B)kx$u*;e%0#_#cKn#Ietzsg>J?V_fd-#!Xf||$k%w;-!o{bI2T02ouYtmaoyoa&zsT40M=PxVOry#+iTdLj>8mD zafLkVP6*w5*D7kaG<5um1+GW1(!(l!8P^!2e}f7eWxo@T@S<2a5=BuAoCwqUjypq+ ze=WB{yFq6phmeYWi^aJ;0BXs8ld&F4L#-lg(uN1$ks_5zoF^L5rVFPj43{nAY)jyp zTC3q`QSw1jDp=C^J$J)$nAWbBXCJRMDs|j!bHLFb0dh`TppBxrRd_m zL;mIiH^@te#Zi2o0 z-?fhErn};a3B)R@ZY0&YlMcqxBO8_d=wF4L+;M0)ZmL5L9xX&Pn>Y1!qEcJ3h+i;z#_#=nn}4{Kj5EH`wI}WceA8ZFi!4rU zesVo_&{oJ>~z$!5Gl`t)sf)k(ef_BEMu&mzY%d*C+Zh3&z5+Xo5W zS7^(M1C$f~LE9hpXfB429Z|C}azFwcvvGCV`vg@vqhS#aSk)v$Fs-n9MSbAH^v**@{(| zen~CZ_#j|W`h-hs`X9x5@AJJTQCF+Vn zhHOdbG5RNykzv5MUWBHD{z?{BdfRb0YV$DcyKlI%vy9^vT}h2UVUID|5s5}cv~;HB zaNDidTN4%zzm{Lz<5Zl+tI_f>eWE2cuZq;M zFjBf`LXE5dNuB-AvB&BiQC8;qBx$1eMgQvG#Y`+#`=xR z{BI=g?GJyBemxM&nZmd?e#2&@&eh$M%qk>3R;Mzt#LGu&6rH(szrPGXb zcHO1DjX~d!4Z?@^35CZriTKSMZsoS_{K@U-^?*$0*D6j#wI=U6yEY7osk|knE+00i zx{dO`P0crfI){tx&Ev%vh6K%;jP*#>lZG1h+F5`2mg;q%J8OQKrFy4q#6wq_5o!jC zJ=DXQ=&;*0j5Ym&J(LEUta{|}NLLv-;pTjkTo0`O_^*hbeV$$S=`)hQ`n-Yh|BHxT z)Z{-`FHuut$Iqtf{~g{ZE7vLZvtS6cPz22;r3ingXCOj>fepogL&vl|ufD@*gLB8o zEbzxdNBbrm7%$fOi_N^0^2OD|+oJ2`9klv|XZYreUkn-nB@T5O%mDXLh9muSZcaDM z`GmeAcww+uNr;pC;ifH0YsRF3f%I%~nSjg(QPFHhCpWxWMs3PUO7i@qVCCQN&(yB4 zN7ek$X5vy)nJ%f>PQs$c(BTjkIP4wuz3eyfpHNKhFYIF)n59@a%nPz3sw-kb%qc}) zL1P;QheOcy>U)ra_A${r&p(EauN2Z0aG*?FA7F2S4zKHZmRD)F3=7}%LhR3K*YMo+ ztWv{_{#g@{C!yn+{{-{+^E%7>uK|_#Pe1(sEmA2s%!#1*C2~cg>+7y3fzwfjiDf1I zAnC$HDRzVr+f{}MUR%_W(5qTZ=%O{+{ToCa0*NL|`RfVn74sc}Zc zW$0yZY3b_A>T>xHA{#UP%7Q>SqD4Z(>TQZ@PfH$z>{;Nnb8mP=1=A+Oo-QtGe$uFI zDI=+;D+!WBQP_!;50^$sj{?om$h0GXZ#?;AL@P8(@kjmw{))*Plrd>y8CpC2oIK^B z)sfH`GOSBS4X( z1nf7=f%j!f)0b$b8F$^wj3zv+m1TZnY-Q@k#75!~?Fvz(Tfuh*i(eAkSm85B& zZaG|fHFDR+$8#A&b|MI4D%V7s$X*e=oI7)#ivVv5^rP1;&A!QxIqkg&mZ_TGFwT?d z2@P2$cENHy6P{nkI$E+sf`dl9L1Xz)i}UoFJbO_W8Yp5??nuiI$(k`|ilc zAsfgs^o*2$z>Jz)#wx>>oA~!R<$_>ba&PCZ|EoR@RJanK{4^1XPjeCY*QO$G<>G2) z|Nort*MuUtC`xDr2>-SB8pkmMB9iqcbX0~6IwtFLPOTN&10WlYiZdtF-7f#X71R zTojVcrMJDxz1C2&b_1W^Lu*qO=eK=Kzp~Ir0w3?&ioWp^3-YrU;BhqcY;JObl1j50 zroU>9A3qDry>7F9>R|YzK^$G{G}xdzCm{NL#RpQ1UWvb_XpPi$8j8&aQK8?zzkwKh z>z^+5mj^5T?h~KpPn3A}rkoV>(YGW?Prn-Wl{tL8dV&6fN#)nbD$gS8nDt%$cw@P5 zNKeei`$6n@d@Kf5X7Wa*N5-AkAVo;aHmeG};uS7t;@wOTr?N>cf77Ko6QrBG-=UtxyI`Ps)_3#IHLEU^dvFOk z3q{fUg#U?R?R=Q;{wI1vpC}6b8x&n#ovnVjxtjfx?0@xI_lcl{f6Q3xW@`I|ZrU>Y z92Wy-(PSOOn0NiqBWlqKOr_R@QmFGI{sU)rAXX@mtMP-yp@mR*K*!5F*ma;Gv_dFY z!IzCM+@TMN^w|RF86h38JG*W|W@1SV6lBSAseL&p$dY$-+*z3RJ1bAftXyNuKB?_} zBdIbE2E73`4)jcv)%kwKv7o!3PtJTdmiHovGh&(7o)0=B^Gu=iVh-k7ix|!06Fz)< z@-Y1kR^&%va@CH};Zzl1*5gg5ZqLxG_Zl)yZarXCD#Cq~)er7P3#As&6(MIIjQ=e& zh@Nefn*Jnp1*m`Nt^XIl|H18kNo%Te${67v24Uf<%JiP%v1H0foH~ zGGc3Vea7&&dkeB^=A=>$enHFN6EVyherAl-(|mW0)mD0DN0F{cA`J&g;E6Oo_DyWG zsl^7&LmQ{M)N*?{D#mmNI$`C2#)rkQ5lWX84%<0eeBROQ+uA?oM&2n_N5w~a)$EB~ z;vI@GjjPy34?!fVfHvsz) z&i#Ure6w$79*Vmditx4{4Xm0?=Kx@Y9d~2b~$f0}t*tdp{b^u&Ozj8vdb< zYwpUCVpoQ_X9P_-{dgokrZVdyzxH=R5!yt|8ytTYp?0NGy+{r1Z_$#8o)R0lq{njd z)`NXqol_0H@s!Y_v?K~HKa!B*-dF!<2*>l>0TP*C=T?KeCb$e6xZ*zb2 zIF?Y9tnwXqqM#n)3uZrY8X45RwVG{XNsO!MELwhp*Ys*$*|<`6;wGB>XF$*M}j zbp(cqo+;q;jn>P$$2{9$`3DkHGVMZCHR6P~IZh(DwB<~G89ENP46}?lh5^FurnSwl z#Erm;_nWF=0h|k4;)>Q9)V&lZPvr3@q;Q3)zA@WsCLCBcV&Hh{$wnaW0X_DhpPLom zyJ=?PMpS91jVfei3T;V`Rx+HiaQx01&FHGwoa^sddee(X9yve3*!!f9fAgXL=3wvY>|py(;!T+NjIt4iv;8oee$GZ(4^~n{2bm59SDNn&PQR=gGwd9qOHr$y>H3(^$YL?|3gdAoR8E& zbnaDq)f*JJn9LSc4K*Jzj1Ct2mx?AYl}utvmYP&h+f4e;prdI6#~e_&Lkbb4v}xE#g-8Zl3Xc8m8i8UR`AD~_NLMG(z$#1zJRb2miMvl z2B!r0>t(;jjJ3NJ{KBCCUK2p5oCld(&{%oTE-oH%#x$B^^C65e*->KyQt|9(;97;F|7+Rl4Ve&Z^jPGCA=l?m5|H0$2 ziFQipq8MRQ7?u&b7}zMvNl@ytG6wq+>V}YT)~FI8KVouI+-H$iY%@1@7*7Lc2a?3m z0|Gy#WEK{fVhc~kj{+8#*&7`et5?F^z7Q;7*jD;Orf_Oj6f6_0h4+g5&#VAF;R5iR0cIhDK*@`w) zWkU1UZy>+f7h^}3XHgalKCwYUrIGP~{y2fqa%&}fxU%{l5K+MjEDLS)@Ci(2a z)!?|{`zDot7%_Uk(0q0g+58F|I3M3XN9eLQd{$*-1Da+Mb2m>+>y{>;(6^PRlj5{D zm`oI!{TgNr-)sm=7R=CsZ13#-o*OC(QI>aDbpda*a<%J;&E^?pfveveSfUUf;|Ji> zsM*b660oqWLmol?A+m>>U&U%B)e9h;O^|ewt1*%kq|(XYt>{;1zi+=DLjc*j6iv_$8_%*Ln?`@o9euc_ z1zh~Dd_L)cEb4Uyipx_Io_s&Q(2n>)80SWzIm$eni9>nu4a;QTc>p$MZV-qZdWlq} zy^mfrvi`)nOii*X(>r|ss3NTV5jBu$(x_d9*mxjBz9KTZ8@`4wt#^cA0rZa8xyxY2 zxt#Z%sV#0Bb8I;z>Q9xfG;HUbeq_yn6#lO(`c!?k0Qe_+{3mb|{P*Vi-()KKNgpT@ zJ^37Ajg5aOk4NFLVCtg44J5!()fcOAgXza4M~Z${)NOwufZJ(~6FJw`zKZ8~4=@+% z*%bbgwL{TM(W??{667S2C5-=)c2mV22gRfaZ=?li=KD;hhuSV6Rz|B!Ru1)I-Dh zssSiA5~QRrsmh{Xo8@vdF;qL|{Wt=KS)~yy7?&37(pn`~>`(l_WrHdSnCOcvbsrtQ z-SKbmc(sYTzkZ*^KlKQ=~&IhUeJ2>JW=H{>seGY)T*Kny_*tnYtu0q3>E#@A0^ zg+KW~^k0B0VQS_2xv1An-OSVVAN-uH((#`xh@icUn9+0}b}eQKLQGW@*?bim{11<; zicyUu@f*AR+`-U+KhW1IrQCLWB&e_p06$+}U*ZSQJLnbMG5(BcwH1K{fd$U7cw5QN z+^b}oXseqhOYrbhF_YQ3EHsBtKRy)QsUL2ts>cJjK_9V-<2*UU)zpbtm{H83#9-4) z=h&SeL#;9TrxNt5T+MRWb@@@2PSwtu%ek=!oi&xqkexRy;sg5~)4Os#0@}#fS@uz- z0KP=kxOEnvu!K)9TDAMvlxu&J*D=)Ku*Lmzuo!-8Szu0`)>k*{|WN{U|l~|ga5Fu zV9T^SZFC>_qC<44ZFDQ0|G+jj88Rn@0EV?M!Kw_qDSK6i+B{A`Y=8*5S^?Gj7ypEt zUQdf=z2B30N!J+ZW zt{XBvmV@U{+gEecmZ(;RzrQ6Z!bRNr(&p{NNI4^C3B_ugn32RW;5Q=i?CoL;BjsOM zspWsOsgT)^QfL@-=><%38lseNwP(m#&tW(j9rnCG^Mam7hRGFuOB>nZVefW~+*%$V z%*HA;GDB}-?e)+E=Ijvg)8H!#8J}7MqeE|R{xoz)t+CbZX!PxRS5VHWUko}oRK zPjHwx44N;w-OL6h#PheFk^iYQdt8o&!~Zkpl6D|OmyTR{CcBj;jSx`v!)#OH!z#4aRY1#%9G!8u^u~GkVgzA#C3LYc`1pSPmu8=e zh2#XDcb^GA@!|*J5cpOMZnvh_uci=O9dv@*9l{jCoWmo8iiU9)o?+${uj@y&8pukQ zn6TAb(qr~*wv}RI-?0nm9Dk|H1=6A58?f6ZW*P)#gw7ZxZNt$gif`K%zN5G3uejU$ z)LOU!U;g>AhDRgWU;K2;_)q&0`~P&%e^OxfuQBUjR+KQMQ%XbYGKc&MhW1Kr{Z{mL z<#WCO0t&QUB{plnCZ^{vel%9|(q;5ctiJ3l7L&&QNJd*<46i9>QaqE6VK~Oy*QbsX zp~AaH_!uy8EykqjF14*pXe9NtExc+Zy_`7=wHG6$c0A>Bj9y&8thjuI78u-2`5rGs zisz`5Hl%-Nqq@`%hSS1zZrHynJs%0QQa=rp#N-73xs!Eha$aR#@Tnt9bbhE|)}6xW zbhmzH<;OMJcLNKGP)`P~>uTgh;r-H{N=lC%r_!}T@A)Tcb7SM_1)qo|f6^NJzd`MP z<{$r$mxW!bwnWiGqRj7Nul;Diqhp4NEI{U@63XKgLRym{WJ|Ym|BYkvs+5riu-rx7 zmO{O>eGyrGs&$#lb~(!7^*DOnof7`SZ>gcuA0EN4k>t2EEQoyu4^w+G?MIBPe>*)) zo+|uMdq7I&If7m6lf49aib9t@9wpKqlv-nO|qwD8~hp2tG>3y+|_VlCFaP|zcA*glGrLN(VLi=hsMPV`O#Pp*~1?E z#jXC?E0RG3FVVndk*$Pqd(2%h`C(;yn_G3mP7{(PC%GTvq%ZMPFN=|t$v9ouxqsg+ z)lx%%yI1ZHi z=aDBnaLK%Uw%Hh~|GO-iq_K&sgR|HFPC6H7Li=J!ybN1-aJ6-fQMIW%=R+wYkfJ~$ z$j(tf!hnnZv=oVza)zb0DhXWnxgvZyJEQMz)*=^UdD#4qD1GzS$6aQ6$J*K&$qlhk zu3tC%l)%*Z`Q+rdkc+_k5}^nC5EZW0b0a>Gm&mU={=^%T=}zD|M<}Ie>hS~~7}l!3 zSO2&0>R=B0OwM=w_mby|w4XzK_x$iI&`%ZUcdwhIb6C9ZeEe{C{6c>8g_!bG1o|16 z*WEW(fA7tVU(HQ|8{9lo$Sqn8JT@k_Lb_E+*C6Uznb!-fsudS|IcY1kg(2IUO5c?Ak@^oD_2d2=NbAS-9azTu$HXU}y2!t2|J`%s-JDlq zxkp!cqfirGnO9=HS5tW7aD8%}5BR$3TT+%AhLv++b_aC%<;TQyL$qTx61fr{ z_vD?4atGQ&Za}u<0)?j;iQY7c`C;tfPX+(BO~{QPN^f4ya6fBz%-aQ5FUgHervDYwA7 z;JuN7LdOu(5qa^-PbTs2k?~cjDY;^g>8XI+knS&ju+MNn&SU)Pmb@#Gtq}h&Z!piW zKz9AN-I;*o}Whxr@B*am$gW#`0w<2K5LY6F+FqSlyIF>x6 zUpT-y&^X{4AuOe)EvFUp#HMkSQ7sutaX64Th}6L|zO3SMpmM-+;5NdSVsJniY%KJ9 z+|@DGZ>i;H9-8SrxlSWY%THhC35uBIIV(e1VwIwC;B!E8V5>uAK(C^65I25lL{Uf7 zgKmHBmrs0+6W-N;;u)rinY&`4| zr4)4@QWqAk@LIIBvac976)UkNqZ83=H9-g`6-l4Z>7*}=>aQQjIRmsw-BI9o5$N z+ckymnf*-pO`F>Zs-FIRKdPR&eUJ24`@}7iTOS4g+>tA85Cm1g^geR>t8t?6k6S5) z_p}jWZV)jL}Pqxm$}f>S*N-NG%@WvV2Rf$)S23VuV$_hXBw5{E@tz zGPH)q&Y|PdDnNU1&b**trT4kIIgTYmEOKFk28h# z%o3@YE1BWs6sCnsrEWf|d2zD(yUo(ap&vaukGRE>Fc=ISQ1 zV&#s0yQ=850mAuIEh`4{d6Y{y)hTPS2v=H()zPMl{5U2(NsCfmQ||lKBq3uWnMz3; zb+X@;HX7+H6*M(AmKBLjHn!>?9{FU;>z!oUJyCo(wR+rP?WelQ5Vr!$Wi~KwiyonC z#UH-E-8C#Pq8x1?Hh5yqs9O=6LyHt7^yGZ>Y)YHttV-jPloxoDFYL07bv8(_?JS;@ zo%vStPhdC~p$bjPm|&Ra-C{39py=&Bg%Iw5)HwT77i_^B%ngIq{KEC<9pm|hmZtJW zBn1Xc`->uII=InI9^k#*g-tTomc$w%tDDn?$hSiJBH%Z)`-|SmQalf9dsEvo{sjVi z14$j>^<%*J!dV}6FoR{O?ZeoKGC+Fe!cjoDt=An`a7fZcl z2u_88iocC&LOiK>2YR%t90SF&nS4w6m*@6YuhsVG+PFK0X6v5Ln=tBlT7OpP z4ouBxmQhZBDXm)(&+v8-c%{^39=H-Ql3uMSXX25IX%gb2fo{C#3DpjV=uO~!8iElK zZ|u>ZW6)TK#RS>xOPd<2klLPbaMNGAQ90K2CTA#9`w<=eHe){8{aW&6f@ffBLE z+JzLW)xyc;9+k?(CWjn#G9#tee`|ub;G8*YzKHAhLy?^qfxlp3h?c*lIH^W{-o`>d zOyLI8e8^_MQb2&Ufob{Y8eW;*PrXQO6FB{Oda+nL!hFjF)Hgxi%*_0^sLk+sgl$R0Kb(-O`!$KDduHqZnp)@L_nYxvezbKBWxye0`amAIk#`OonB z8} zY`7DXnQGs>G^%3l+~|X|Ni*X6fj?>Fbi}cLmWC0p>`KI#7e_O6=&4ArCs;0iZ^^(j z>tP)mwob>i4pl{vu_J0??ap<--wy3wZSuN#i&Y!+7_T0UbDPY`KG2nLuS|S6-#_`2 zj<*+Pl43nxYH5?lpD~N4i@wrlZEKisx5pIG+c;3=yO4IViu9Z5ge*@9Va79FDAl?! zC`zwe`CQr=?kLvjO*v0Bdf>1~Qot=_bmH!K{y0H&_*kAGlgKaNmZ5>Zwu`0#aqSJh z<)dSyIQ1=A!y{@at#ZlmpcPC#@@QA3McTwHERx&e#c!gbPtfM^dP(5fkm$W#>a+dd zNc^)brDq~Er9i*c6;nG#PwXCW(ErwbYrLdoXxh=av@`I?mvwCzfxdY*9;d99#&tvZ z0G+V0ahhVM_`4$#yMGoQh{|#GaWP#I{E8UeGqP2{J*|@=6vp_@6)@bt)%4`!oiMm{ zM6CV#?H#pg9>gQ~Y7!mLCmc3B#c+XjL8YtK4C;ynu}hdtV_TF!q1_8fGc6}|Jv2%S8uN^60U&lIt- z#LF+i-LLjH!j4OF%}cKCrbSedscX9ds{A0VE@_j*mL(}DKhIfi#RF@+2Fn2>tSxJn zv1x>CL+r8baM8yl>DlEZu=cF@odvwp+8EP*6I?2<1umI7rz)@qDxpEfEQxEw9N^!XjiBTCH2|> zPZI|61?h$u*M0|FTuCZkd~W;0zUd6`ad379^0B|oF$C+G`gn9hB=3ns?x!AsTy9l# zsPw0X8~Jff9LKQ&V=&iKoLtXWb}UEQ-r?>wX8Utb>|YBPdxucQaNzJ{#gQU(h8zq3 zut|P*{ir$C_uHM>g#!sAS>;TpN8Fj^m=X zP5f*-fFIUv4^zb0qkNvm`CYg19=TT9bCdGG_L{uS7y!lr21K*8n6Ai+%+nG{6sZZJ z3NeJd0QW{zhx3Nufb!eIyqZX}6DE-Iac=-(EGs3yWrmLd!8~T0Ia z2Dw9TC>03??h{E1NsEV#ad7#4ZyKyozE-ifLhf~ddKE&*fbxb^hxNwb(0g!-8*(Ss z4uiEJULS929Nun%dnbmTfDi;*MF&z?3LVod@EYwRxfAxjmUDAJA9-GYx z8yj?d7qEmq=2ecZG`|AVLhlLhA6Av~L1%glx@!@6kv(d{TXXw9X~GY-%-qn}vSZIn z&;zlG3}pHYp?vZnM6NJds4C?+; z1?dM17t#$*2>FcH2nR$g#RW2!LIDX&F@b`mh(L%^LLm9^cdrUQea(WNHjM}B4J#|* zem(5hcgWMPZ{WI!&zOy0f%wNL(^q*-dBP$Q!S9fSUsu8TvZT31qyhmVQerRI$28NL z(=*d-)2t+jt_;%9gwVTrd!i$G=%zhN1lK5$7ZAn)fMTEROH&>$(cgicd1|6U(^mEM zbBuckx^OY{AC%@78(}G=vJGAUx?lLCksu=1rIt7 zp2IK4lmtwNGDTd1iF)B9W6?FfJ@=?z#9Dux!haE|!^;36NvSi?1N>Pin1pwLBq+b* zg!%u*&aEgS)f2#BLU>e+xHJ69RZdn&Cv+g-#rZiEM?`_(Q9<|4LXuzqR4B_0oiG!y^>tsoF|Hx7Q`&!5Mr3}#3F#O1Qb?;vK+Z-H zvBoZ}NSuLL#b1XhEvfcN2B~vlVHIMVzFNp)@=M|Mx0Pjm1b62AAMZ|0{N(S-pPQ!i zI)x2Sem?Lg-PFCO%`(nz?PHy^0M#!HV`DcST8uDNmlYywT=tyX)Y|)I=Pa)ZfA8%M zQ=+Fb=+`@q1ljbk1LR+_mYcsOpuOf5>YIeE;kp{i^0S@#>OfQ}W@nSdebR_beq3BL z5@e)Ao`i@RW9&$_0pW!9If`lZY}#+i`QX&RQb#XPr`I)a{l~BGdS})Q z1L{33W)glD3Ts05^YsF$e=lxtS;y#0+rq}^#h<#ATNPtp7?JMFV99M8#|Uu~)54_N z&gD!Q6;!7L+!PJmK%Jv-paz1~2(E>uk-t?aT6}E%XT&{1`WS`<;ZgedV~2m z(U4Mi?c4^QIJk_rRe<}+y^CMZIqMJS&uIg+)iqUA+Dv6MK)$Erkma^7d)s5vOws0@ zgZYPf8Md#ksTu?J)#Y>|ewBLY_J7tWxd@t3oZ{B@L`n^91r?S@g ziMJn{6E=;Z&U~l>y5`7GQtjW>vb!fu=B{ROa&3>`Kgdd{(8UG4qWc`NaBeYVNwF(q zUW%LR>`RoE8rGJ28z1!HW$jCUt32HKD^A#?SkfX6B>NUBI8YND7PLx zE-tSqTW=XMoX~G)a5rZv%Q7m{FOpyn6Wj>X4zG>E)Yjz+aG+!H!S@K@@2{8>4`9=e zE3|Mp%W2@J@GA40M`#ux*HO;xgr@ zC$%caEMa-Gn&hV>wN6J(5y_SjIM$%+y~Y#$@U`6Fg8!?mn87Zdh=x6h`_%6|{}{hY zK>S+^y+V$LasgEk*VBfOGru4m!p@H2nprP{0Cq$B1~rpmNhDU7RVeWkX6)&FCe8Md zL_S>ZoRX4hAAt6eIPnh7pi}LXjF9Rmo53ChxF8Z3ag0niZ4Z;3MJJ=6bqY$bee{$L zm%}kt?wN_Veassk2SX-YGZ|LPW!*ML|4=k<6<2e9j{IZVE}>PgUr1LLXBj2T8U;B0 zne&8RNcBEoIg;m3j-peY03Gl{{qsc?SWQ%tviu9E)9qCf*WU{$Kc8{95COAJ4J(01 z5zA%q7c%^PB)CkXpKqlt8!iW9w*75sk&FW-h#Aw{0l}Tyqimx4X%JIAp@;_kjdk(+ zd45I8JY!{bg(1euc-aRzIXTSO{*?eacY|BPRKBQUlLGomKEGgmv)@6*5KnjHf>K}j zP*WHCqI>bt#Ri9@`-Z!(h29OKQrBzJ!t&2hXcFN?e!2OjOO9cws%X6*4!*ZnsoxN+R;|46G^j8A1cpSde54&kCrVe z-Vn34TC!XAa`6SgA#L4E`4a_QW+x(lGm3pe*#}y$-vAFi05H>oq5w5?pbS6_ zJt!RDKnF?zIM9RQ09SON9KaPlC)A4HlH+~yf2Fku;ru0=^{^ zlM)cUH~7R+g3g4iWC0X1=H1BhN;4Mb1HvctGi$F^^V%icS)E)wubq?td|_Wsh|LG z&N1lT=`Haf>n0IZX1SGtNWjS%Iff}b%foIlgxA@ zi$~QTz2ppG)_<4%-WFS;h>c}7Go!d`$1@9J?^xfd=LDaZ2^0T%In9I2=52{b`hB!h z`mG+oos;AgUA%RwTT&6}h(#kiQLhO+(~3oGzk|5*#X&H1?3A?Ju-rrEW+A^RR3?Az zu2XcSB4+NMnDpDY2=+&Ts5Ds?^a!puC&35tSAGU!6BSg+f1@Av20B^kX;&L7%MG7V zmKPV67JFiZut{O6K!uDr;I3nNMkCzfwK<>zvy*#CGMJpURHL}+2_JAa$K2N+H8m^? zgk-STPEU5GOSgyb>t>ovxa+d7^_^qZbap=Dm+^U1!&6ua;XVOh!8<4bcLP~$at3b> z!JV}xiuB}-t+|O)P1J91h)t1Mb1p@TVV}l(y)DvjhSs;L%*^*UvlxcfH?#91vtPOV zvk#Aot$3{jEB7xeR+JN@vJ$!8=?*z%1<%qSpE#Uvs_8g;HLb;pl1>V}Ji+SV}nv*jKXw7)#E`HEA$rY(vH_m&fSj?xCET-r-71K5q zcdx2C*p=zXLXdVyToCVIbMXbQBk}`Q2%h|HaPgIhZ$}&(j zKw0JKDjHF0O+M;oyc#~@8fA(aI^xApvoVI6LY#uXHpl2no?PC5)t0n^wu^l7%=I0~ z>blDbGR&D9{fE<#tRAxFqW0#@6{jY5rSmyz4G=4B>8jksQBZ!iq_pL~4M(5d|4S8;fUS1uJ1s)RPTY`umb5 z?}tl*e(TH_*(OQlp3iv9n9o&~TlLhb=&d-W9L~sDd9!d7s|Um&g0d-RAb%r@pRNSd zqdBKBzc<&)5cx&bC1;P;8`z+xt0D9kUY*W(6qeBt2I%SV-8ocrra6{KQm>O`B(l&W zo|!<;u= zcdg5FyAF_<|A;C5XdC?i3c1sQKBoutWSv)|xND&qP#znJYr#Kr&u|96L)0uuc1^~3 z+o>C--q+pl-}CL<_n3Ng)pi6OONw%%ZZI4Nh_}GBCznc1+lk{tZYVaYOjFIT;8(dk zT4u9rWTU?Ekr*Mv86hWZ z^!Ht&7Dzz}enpO#5=@d1upAlfzxvGX-(DA8Ut_EYGM?v6r520bg<4JKCD#Lav{`AhqT=BO`<}fe=jK|vcIFI@?W!Fg&%v303?T*|7trHhn z_cWUx%&ZcvHu|eos?rP@w=wBUn70ct(fP+32CaE{tn1)Yc*Ei(Z6%4o7h+;A2p>V?q#H0w0gjUo4xujIe@2^S9XUukCL zfpQB^KZ61g)FmuONUS3rFFyCDQb!he(ZH?A6jYm1Q1?57A6x)<1>`cb=PM@KVzUu* zY+!`2^(pVBpr;ajNj;TDutSbs5+!!wD6l*sji3x^K-P{;`!;`~DU6Ue<@nlBJGBqD z5DdxuHo>bC)*&|aZogiuT6Lgy#_#j@zKkNR!&fJRJVH`OUKOvMM>e+KC~Cm2n7tUE zzKlc!oly{j2`S&IZ07+35In=ig^~1`?})#0OZ#IG04@UX)@eXGj)@F+0-nYD*uBOl zcV^e303)axKyyt~$)?Ze;t^SJJ2NBg7&66ajFTNw2)Ea<{45 z_9I3iT5|7Los!KvKG&pdA5z!4Bf=rb$*=TJEF(Q3$dA#`H7C*p#&G7_#X<3@X0O}mrv6M%;yOaez5rl4vXD-*G0YRjNdKST zvP@rcsr<}?l~RQodM*C4k@s4RvW0CoP+$j5rRXCFN!@y0x7`M119;iw_?2$Su0gW_ zv`^saTj1Twheq?i4diW>+XbV&8>0un-DKk3X%Ao4mfwMt-&Rr|tW$e;ovob0CU)T> z2HBpm>UH#K8LK~k4{A#bYsfPPdx{1ZxS}fvu|%K!>vdlBboEUdT8E5Pl#H~wqCcdg zZS1g?_!z{1h~^F2%#76yI{HVomIXB&pJNq@2k*I}&j_)MpX1c9*v9+bk^fmuC#gR9 z3H%gJ%zla}{!O=pw3nlqv#piA%|Aq8$;xB05TYoRit>vBT=7cRSY6}2(P*eCD9SLN z%AHE?nQpsm9peZyzA4nbA;jBKkzHtA7o>=9rA(#vr)MvF=zn-i{2EcEa3pY~(oov9 zK6~R7ETh)4kTjzA{ zVRchujB6}4P{SXx$g3mrKZ`e^FZ!w`o|0KmIvZD*@d`qic6PTzWJ)Uj++LU#w&-H6 z9-i8F?WiLe5>+;{3DufKpf?UM*GY_@NvB>FNd}k9mTl&Ag={KA@8EO^63R87ZV)dx zlzg3-uZ`^cFFa2!FBrE!v6T9hfc+Z@^#8;2f9StrRcEaKqyOSoaBpERkwk}_^ zS3N7Q@`{Yog(8O{E=MJ989^yQYB2Vg)fK;nc*TK|5$)CLCBAkT;LlvN4&LoQ=k=J( z=6}q|b`btwlzml9ok6!YTHK4fmm@&z!$PM$P2@4qN7z&3>BAOx4xA*(xlaPwJ^N*iy9h7w2b?0 zRJd8RhF%C$HAa+D2jiTo{deK=s$nUvuXfblAozJ=ERu3%sRqK$m(+DKK&0f$82amd zl_f8tT5-otm{jp)y>>!7r9MWv)VDH<#6;^zs?<8aC7<5)cm$PCtq!?jIeKMZB}?Cp zXMO+2G;e2nDaJ|SKeGRHPH3}r3hf9MamagcJX-ap2l`*&=Y`!mqcIO1)0la`|Gkn~ z#E49-!NNf;^J-kjicA6@^xGB49o!diwaByvreenRx7eoss43QBQI|K&+E%ZRkG$2WE&?lhis=@jR z;XffBEgdjGMe6?rbs%kiPlr!6pT1M^2GOB(z$ z4TDt`i+n+PLZ)&HqxJtFglkIN8%I7vMEw~e&i?}K^`DW6jqm$^47pEAmz_yd#7uf^ znWXo*R4tk2og(PSmZ({4?mS6W18hy=13@Ys)2`3wiP!D_s(LL!*4(`6>ogR-1W+=@jePMh_V;((?Lo+kk zz4clbtMeuY9-W}j66(=cBZU0^xYTT;A_8DCSI?Gw z89dsMV!}(b#oeBX3x8uC31U76dFA?GJi+{D`UHu128n2YVh5igAlUzl*eDoVo0X{&?nirlef^sxK1{N6ba*o3gxZlfvS=u#07P;Ga&%1DpeVPhx%P$T z%iSOwxhg|!1Q;iW!K$XqDxKeIHci?#i&1zJAFt+@U00heVnGit{-j{@j)ojIiz#k4 z7xSAtu1CQEJl+K$v8uEh9a&HLSXf1`#*U@1dd(?3K71xT>^2Mv$5&JGMMRL6H6Gn+ z7y&}Pts3hh(UTIOtuzZ)5&WA)>_)B$!jByID`D&ozFgeeN$~>ofC%bZOd{)$xqvSF zp?wBd)>D8AU*BC3G9m;B-bUtJBrh-c+Jq)HQ`N_AK7ypT6GU^!KD=xC!ULFiWk0rEtzXrftBb3LzZ3aw)Qd8G+J(m=q%5% zX!Wm*@(6h!7`aJgRo)O}i^IbzlN0+_f}DL#@hD`4&#f9XB?@}}=Eg2f9IT9l>}Hfe z9IZTD(XgW!C{Et2$&W+KGo=`hR*wZsARo}MqMB|g`>HZqS7LcO0Aa?1u%+;Gja9Xg zNS+#q?O1`_M4)A(_$;kbHRxE$y2zuVXIEh(+q9{=oMjDdZ7n8uCVT%?EK9a&RaHmP zmj62<3}~yYuws5@>13n<5_XDtffQyHwy?qC5BIq0^p}()m_?Vx+k}_!FfYG^H?dUY zd#C}fKhKE;?|=~|N)f6fk*}Tb1<^aY#ub7ri!#AtEP{jSd9bMg;9Cear%aH(eT4?u zuaW|N*bi%!t|8SLjzzgruN;tmd?R7dY3!*vyNilqw`kX@qJvUd&N!a3J;&m@?7h3n z8?3QMhBf`GLDp$bDVScRYh1M@<5XRxYhBgfuHtcfugkF+68xh0;tsG2Ylj}?4YuxR zN1}TqJiR3ICh7;bf+2u#JA5!lMxwq{1G!41;+Mlg=h*iwRPjG^dJxdx{g8ruiKa*Z z3ca3lig_YSWpelUqHlT$T~X}@YNSG-Ey_DGpoP*kDLO%HRxaD(5%fYGF1qKes==S5 zbPP|}*4HA?I|5w|#Q}$k?}qA@&_6kqH@3Ntpb?#s=jMWUju8PY(HB&phul3s^2HYN z{TtZ}V%IaG?EV$`Qh{H9B!u1zvrx&qQ3Waavk6ta=$N3~J(k#e=)OYYjf3bL!#7wl zq`dy;KdK+W-@B0&Z&W7=x=O*Rpi9wLo5l^J@|R%cm^GRsLO{;GC2|~$L|9R;(vGF0 zt0eiLVM*&atWml&ToD6mA~7lzte$G`#@sCk@ySEaeF>X-MKp}rx=2--y9T*z!zyf(g0%n%Kk#bH@^;n9)fJ>M!Jb|{l5Iz9d*pL2YrL&`|4IG(4KI!M@1!6$ zm~C@`CVwyz@&xh%zL9-&frgS5R+SIivlM^rF8{9h8wd21tO_sY0O$COIb4Yai(Bqu zToBvv%wbqDZlmYd9T$yk(uCZFIL;=MgieUjy@dLi1)M&?rG>a)|4vX2gnu1~F|gJ+@CprQeU{?tHAgY1Q^ z4=-Gzi`)g@lc3npZK6*g$WCl59{srvq!%$FzToNeuGq1%z|@zXNA|3&@W)v5PU##9(u*8Xi_k^cH!!hW{&L`F4B+f*n_8%bBlUrp3LZdiB1G3lEyJ8b1gPP~ z5<)u*+OZ*SzyR#ST#!aNFVt&m*3r8*7W|AXJzx&{3+9RC7ZX$vEMS4(0q8Z_^rv8I z5s$338ZzH^*tB2iAUpDcVmp)sQ&z>j4M#teBa;2FfAh_KZD>>ONs=jqqFqjR-zNYog9@h-u4W^d27=x_FA44A9$`#4U=OR?vm;K7v>-dsErA zU~GzokX(-_b_2@ABe`eOLkx&NI`<7SFR$pw+AU-og5qXLm!aj4FLV^-&(x;BGP|v6 zdY>tQf__m^5{f_cWeyWejh0SoZ%rf7A zneftw`22^&hdR(3R`3G?#RqGX0>(2Ja9Z}l<;fs}pg8-X1au)jS|e+h2lBPECXTq^ zYI4{XyW%PMx^z0{J@5KVlE!~XfseVKqc|4d8)7P%quB~$*A2{zm(4oXp0A&nYg78{fG1-xQN;!DXOzM+ef z!aldBng;rPW8v^^1d)cBkM9JepN12HSMp!mkCnxR{Pak}3-_dEXKC{vv37`zj+29K zcqx|m17705o;wJiG-lPzAXQ!H*4y1JOlN zpb~gEFDWsrfOblF$AlN6X@?W+v+9`ze~SiaCEu zP+=oq_KzWQ)NK(HNnKrSk)3O~N+@oQJ65iV(z@dGY>8tz#!{flEabMSy1Uw}KVsB= z(@FKJeXKMq3@0*{Z(I3Rqp})gT~KenSkVr){?1e-W$wzJ@HyMTvzF&{yOysfUFbi~77gU>382S7=UxgD;l?fxFWpT4M;c(-#F_JO+L?7K0 zN`u8z(j~*!w3k)G6Jt7veNmL?WB^e(fB#vz0sg)WV&EV(;%chz|C`&{?p{ zJ~+EwDL#m*tE@67RLrX?D+}9cP;>dI*p^;v7n&>aS=!8E%lLL^Tjr#+)HhmXkp+ZB zGVoc~XRKzm&C8*w)DuR2>S-<+9u;G#gW%XHci)joDiSpdK6HWtkW&q#Xw>BPP0NuY zf!{croG#jjzGRVbwkCI=Rrf?W6>`~AIMg5B;UHo%Ktd6Iz`Nf6f=m`XY9Bh}flOx6 zG%cQ$AEgd!>-o_IGC!z?iPuz*YzNfsD@C3xx-pU!i!Wdi&RK1QvzOpNH9M3D`f9P= z*I4n#8X(OcMBOuTOosV&7vwq|NZVcg4Ej&7}7y=KK;>*R_z+%Xpuk_LR z@>Er4ky(1$jaN64F1?q3PFeeHb84e)eO|+#)KmT2)?F}2J^pWP z95@NDFF0S#AA1kRt}PPSe0oN(gm?N92G)R#{;+}m*kmx2Vr-qj(J`b9l8~>ev9%ZY z#<@!;D8H>=Or_v2`X+m~aJwq%bLuC$WqB-!>C>AG=R;Ov*k2zqaCY)BAWZ3zNvUxA zx|OqEY@S6xUgv@pnNi+}(RarOnq*96-xkX>?!;YMnRwnFUN?X(sM9E+b<)ynI7VoS zEY7mlMnn>k-QOX0F{a8WH_@e8&B>a1lc$bS39qNWfa&Yiw!D_5w-L^u;17I}7Ll#{ zk@GYRZrbKR-5K=F%O+5`^%;NkwW&qoitfAi%Qv1hep1oK1Ogvj+ImxZI37F5&=j7f zb=04evzKf1bXFUMjl1TX4zZm1=MFU0RJ8QwQ(|!L1~i(<4jNQu&`Lz%5dPjte(2?w zkG#Q4WAZ!KM}Q9e#Iqw6JU100_r*+77Z-CWh{AG7q_x(6S<4+>eLFux`Or_&72t@5 z-IZrvxrP&7C)ph1-FFTSW`x1FmN<9Q_(JjzYg zH{aZ~H=Qj4(>7_oHxHumSEIdmO^%y3v6r5~7FS#+8Z>YkL(Gi-Yb^b9RvX0RR`a@rzz5`DXNI@JGeZgkVjA^0cAR=s0af6$3g+`4WLyZCY0XgpQ&4 zIGlMG*$U1##tmH_n-hi^X?JKm{B~~t1Jx`HOhRDTVsYs|c`^RbBaY^Xo3^toYxyc0 z-L3QA9D;O`Y{58pimXNrUgaRqMt)pB(;NvbSf$STGE-Uz%;;CP(Db7(JX}Z zuku(bOPXJSb7KjzT9_7C96XrDs?0s0JVcKbq2$&kE`oBJjt5p?cvF5WOJkM77afr- z4Wc^()hIIE5L(OAOO@6|Vaj}DzPkX@G3I1yAtj&$zU~<_DTGJqS~iV8%4R-+tKxLnL>(p(R^! z6@HLMs9mY~-{`Y5%(42F^))tY?+i{ol(|xhA{dFjg+^roN+9!kSno)+!Fphv$W)J- zC~mh#5!-UB`nxLd24?aRso6=-(pBA^_q|;jt45~9$)iz33;kK74!HN9UA?APwqHxh z71`;GM--GqvV5j;Z;^_&7caKb5XB?Y8>Y-Es8I*}HpYxuMt6Wo$ePwoNKGQNiWow& zaFwFD8W_arSSkp^YOmrhX}ceoCYyh1^oFrp2^z^i$S)C$65tQpB#xVLnCX~>TN_dp z9_2*BpPw|2Bzaf&|2y}1+>-DGtk+z~@P;7VuP|4M^RILx?N3OjsQ;L9+IL!Q@VxQs zmFOBsr<|FtWAa~As3QzOkf|(qlPK6l^J%2yB z3Qwp$XX%3C7&;%?0+}lXEUE($bouMwJ^Lvy%`Jvru*&#DHgZ!vdxzPvv6CrH{+Ltz zR&(}4Yb<*#kMYfn><+u}K>~RiW6SAdV(x?L=doHO3OfHwS{J*Fzg*A!%;%n+@v!7^ z%|>_Qd-N!d{z*ew35cL#vy%B}c*m9Tm*M6_0r?>{xVD>|I0S#YocXXmxq_g-f*K|$ z#gxKy(L`7tj7aP$`IH6%y% z4YuY~BkcxGv%<_J&u#y-3sP?*glF3Ux#GPFRK|}i#SpT87U>x3bP+lX@Ujb zWMn?Fh)R^SEKZ2Qelj0Fw-g{~=^xo3w8rn|e_PwWZ-9toqWQ)R!%Xq(3nVLdg}8B! z$EczDl2KB>_04P_hZ*6Q`Uw*}q$bvQmv0^f|Lld={t1sL-=u7UkE4{4SkfsGhtV8k zOKFVH4UTQ(JN0@=H?71-)%-xlnrJ}+{&WH)2&~t?GAgUZ=wkN%%*AMk@tFwIx5~Od z|E}{B(osF4a?q;2U1uTd3|4eTuOIEXx5f7b9I=7h`FP#9R$0Nq?<8}?^^KudkSZiUpt&XI>4ZJsVj*Y+yN@~!U;}ek=?GDu;X~U1Vl<4 z=pOKu+Y~=oqW4f=Dwb`qlwl4z#@xV@ee`n7QTZ`r=955Kd>)&3V!~YTfX)DuZ6ee| zzux32J8BknlUT%YJc$+Nq5e!mSs4Ae+88NC<6y?vPZ@l5&c!98TQ6}F8JRt8UVHpx za+ifvxBG$XMQ~QC7(hE2P_gveni*1fwa8^*)P}`T&95fUwRfWC0#%hMzODz1 z&>Wji$O6*@V%A7D?X`cB z4au^J0ke<1f3k=78$%;KfeWmAkzo4u`Veau;HV^@Fc zCaXQOeaL$c{%p3cQSynct?pw-XFz_adC(~{w!m^tif;^0S{EZRJ7xm*8Go7-K96%O6mm9NzW%!AwZ@uzUBAvGPsh0DF?7Yax9VDy<7#S!?=O@*UD6MFe4S&>mFULZ-p{xB zL4&4N?69cR(25Mfbrr-_Hr(H{wZ;rZf9W2Z%=fVtr%)*OupSukF%P)D= zd+t>hP!&kX&=r}DM^`>hE_sC(rnAKNzZpxolPERzPq-VQAz6^I^o5(;QLCXydKOw_ z)rT&xDe`bQPJeQrF1@z{0@Rpn!jRexF=ayD6ZyDFrt*ong-=zqCkajJ9TGlhRY)vu|#T zKvSQ<$Atn>?w ze>$ja73m5bu&Df#K3uCbFcn5MyAiHa3{)x=Z2V;#rY|_mwauS9cn(fm_i3K9Zaf{_gu`>4Q#1@LShXomCsO3VB&yv|q${5^9t ze7osP__{=WK=lek{Y0+$kMZ*=fAdUg^B-APG8S6qqzzXsP&!>S;0G?-hoXegPjlei@zwIlo4ndZ0z4;&PGZXd#s*t3T^r~&CB zHGHs-!l3r_Dx$HCxVE6THhq~jTbeMtEt|*{aGU*P=fvlt{w_9SBwSi-rD$gq&&uMk zCmdSKYEtx}b{DI_o(wby*xtuc05XHoF0Do}GWYVF_L3^Wy6ad_^=Uezec*2>2w97t zGFc2He%tPXBswsgVVkP+o!unn6DKrH`YvPOG4LU>9+fT}p9kMUg|uxYO?8Sa_7}G- zJI(Y|*I8)%GS4dY`!zHAY-{U~la#G|j&m-0^i*QC2BRpfX}SEr!M4E;mwjRM8v6#b znyac^j%p?kwDd&=dU%Nz=>{M^v?s*J_8;)qB8AfD_fpB}|*K1RH? zR%nOa0}8Wg?!2ZMp%M0r6nEx_<~t#a`R-}7jSN@YAg3E4$5Z|}m`H0@wIN9{?HX0C zJ9_72-kdRu&ur?>EV?vLNR#OgoIw`u{Cr3B$pRrKQEC@fjg*3T47eNuT%) zBSGPeTgdH@8GU3{r;E(Utm8%fp;v{Umam*B<4^P;7o6b?$(m|g(7wf!#R7Zvz+2qq zjid#o>0ev>x6&wt;?z_HTOF%-aSil!BTe)807+%mL|z6FbA%XZrZWOC;!AP!_} zU7eYF{hb)Hf^)IYi#gyTuoT=LMnKffYqVZUF(doDyx_>4VH~m(q%eQ4N(9HL6gXdz zh08IerzB3vxgT&&myWE(6Kd)oR`BP>)a+ob9gOk{H8)4sE0h2|jGKqwOQ~1yfy=?k zOMoR|DW$;;O9FOJJXjbjZI>o%|1BFboRwCgrU!khOMzlspuZjxOL)4`V(^AoU0$iZ zx9@S%5=iPxC>9&ET~>-&t25u2rE>P9Ist?oI|cx$ol6=YTg(@pHen)NtJ%M3V}|j( zv8B6BdP1lf(eY<65q zI=Qt<3-MY0kgcVhb?tSzohF+!O3dl&I>o)w(u8Tn4;}e>r++3Rc}Y2{MP`ba{@4DM zSL(9(#c%WvRnE0&LqbOxIU5ofbw*f;+<#DoB+3#A@)s@b z`n5?=okWA%n$1Mem*C_FFwGI=*=Ocjrw~LZ@275@HfHWrELycXkPmHR)%YYED!up_ z!%&!lS?Wwq?i!q4Igf;+y^XN0YK<&bZ7cP+{wlXP2a^#6;_eAmgq;CIUVUCUxtDQs-8 zj`4|9BJAs0`H7}BIxFAf&xR!XXhfhI6zAVN86QkqzFj2TW0_7qi=8{}`HOqDBks?z zT?31G%Y2LvidWQDG*g7fb5Ol8}?poHf%8z)yU)dCd z3#xu$sHRno9pIYl1zEY0C$g-0@3dKn(Tw=W3iXiD-n{n&O*N;ZZU_LcPL z;?i91oQV}X&9eLBm|U-7|7O0(VPd=SYsBN3i!3N5m?!b@{Rc=jm&o(v2c!-K-(qf= z=SdAzcuob-2Nu5u#)7&PRbvX~QFd;e#x?usNv)pYg<|_FgK}+0+R{Rx;`U#>!xoPM zoaNWWdv(1GvJH1d*t>HebXb`(o@{{1uRgksv}1vFIPyi8IZf65 zg;FND2XoIndsZQ-G{sd~f zi$De|8-AHJ;Y=roykFeWtB`Oh7~y=qRgDR0#q!07eo?J54B+H$Y9qal6UN`2 zHq-3UI@J+F<)r9%2oYO4L&f1#87o0C*S34S;89^O$KpL`Crek=mc4{rEz|@n4eJqN zvp5xjN|_biBOvAMF|y4E*b!MZOIOh7xJhkcDY;^eq~{uFROXAXX23Sn(VpS9t_=g5HWpmg($w!O+q~s8KY!JHNu6s(jve=Kh|1i#% z9S#etsTp{-v!t;!1_y=ul-RVAJKg%F4eYMKJ^)`FXD7HI+GThIg7If5H$WhWs)z_? zUK?GDEv!gtUknETS(jHVF@}1MoSP5U$iV8l^7STk;3Z!ZM7M@#yDyA!O->t$ouJoD zXN5hnAZ^BA{GhjIJ@;7S4~w{QyS|Y@8yMC&O5>(thegaUX^WLrlwoas0(zqk7!Nsg zC7p5{?8bS+n}z7yVr5Ua;YQ?GSmJ1bQQ)dmB@u#~VR}9jI z{P2f{2p1##I(Om+*T|BaUu-DbJnuo!1+kbBEh1CAgefRKo5^@u>Z^)P6^TFcHq262 zzM>#-^m($yUODWtzb;7E>Fy##!YU`0S^WM>Z;tZtZpB`(LS1{bdZstT)tiIe7>gQt zFEg2Fe)5-Ig~=k705e)=yG$Fsyo&;%V{vthVgKvPsZXk`G3F+U|bvzSo) zeie#W>StCnz@x;bZp2C}JSm1%aH zybj=_6m|~~o%Uj0%`2>;SW}&4i&iFOO};!^q+OaSgZ>Vg0<1xELydErf$pU(EJd!o zY~V(=2KMTvIw7l=xkfvN*6dK6y?J@_)JtP6L|#8aBnRX0OY_xek9tXV3%Kar7t}Pu z$s$MH^(kXsO~1wf3Km~D`oDd<7nQ?Z3*E)Cv|MAw@(<9Z_+!Xw94VQY;b)R&X&dW& zG;F!evDm4XhJ%k$%lFr5@O@E4&?mgkrs)EG$R(*>9ntqGD>y9(v7?8saJ(+%=elOO zdN039ca?lN%Qbk5KD=8RlH8Xpu;~9zf-!~O4pEC1-Y;y0cnJaLBqt;-@}W}D<)zPK zv+i>K7CcCa{)7?{_{3t0%By|Y*18-0r@N=_IOQ_|@#NTEx(*g42R|oPCJwL6MRA9l za%?q2@wsG}Y)#ChJ;`t)@LTGWT1k-3jr+|W;0cv5V&L`;nK+frabb#=rh3HCBD2?5 z%Zblt=N@(CYnV>}v$6lP*_NHcQa-!W+%#00NKCh|cMu}!(oSK?s zA}makofD3+Bo#1_xn7#QQ6w+O2aE}_Hs{?J^@;>}B|2B4+>`t<2zVu3RgAAI$mO*~ z(_xMg%GLN1fB*;a$pXw?1wiP2`Q--tEy4;yH5Q*KKP0@hJDONNfB=HM+QGtv%N|8` zrxpYCu@;J~9;(*t8r>J4t3&~+c1M=7H;jdnST~1VwH{g}5@p`$Qqs>ZeqWxj^twBd zkA6>5-OYoF7C<>Ve2q{F+iY)wb-RKA^J$23Vaxh4H&qJ!Mf!Qa?^@Pg#LF>x9CzyM z3cZ>$AjPrNm zJ9oV-8U0tZ)scE*bq07-SmU(l%F18re2vkQWTsj~TI3?5B!-rq+V2hH@KH@^Iy0V2L)Lp|1e_CZOU0RM8EMY*O2PYsyWJj?sIRx32AMTXh#8{ z7{V|YuU^|CTT$VxGU$o9sqgnj{uvET#+|0yv0E4_dafWIn_Rw7)bFMf0TTSl57Xoe z!x2LHI;IrXQm6JcfW@>53B)Uaq*2nD0 zSs!-rbQhLSy(|TH2+;&1LCw8#hWa<)E&9S3MNB=Ej9$s?N=$CCJL{V|Z7oT>(ig$Q zMAf5Vfh6@82?!~hb1H#>c_6uch*zk!N3Gke%DH3?*C%p`hPOV9B8UJA;k#fQ{@}~% zcP8RAw|ZJF)q@YQAkm{6i^z{uYKqn33IqO+VgQd z=aOsvD}h~s5CYU0T|y0o>vxg%JfBIX(R8eDPF4Js0Fsp{dFa5Oo|5R6yu5EAtq7bJ zK>#TT?x z9k#eVY&gNV-#JdYMFB?Kl$cHuRJ6H!F_JKpNkWgOs;)Is;*VeFr1_7ljkGdnFUl)G zlGVg)13k4{ENvdlRG85nxuK2 z_{yZa>BDvBPPP`A{b=X&#kno=1_@4-#g*YvV-l0$O$_f!Te%Th!!Cw)Ky$;bqyeg< z7sbs=zn93_6ZtV+dbq9^0oKSP@@yglb76AMqzKcTomQUg$1G}^45@`uuKZjVJFg)7 ziSYNZu>lud{DZgWxXzw{Fk8y>N5S2j_u-TMH))HJro8!tM^o9Z*<1}=uW%K;L;R`e z&VG=*1ZLI1kLudBRce!ZOJw(kJjL~Nb+Hts(V?i6|tYiM3;zpg!g8qaZg*kfoKmJZtQ;5gFP=;3jESK;TI8p2Lcq0z# z{KEQ8Uj9xy#4CXko+bA-p_!-(7XvODm+q^BMfUDj?{6dqBff5)op7O+mxGirQ*H}i z46cK;y&4lxfZo2MR_Md-AcS_v-MAVbmpQUWiEEC_uDBRADaB8m8(LQK6>92Z6 z`~IOQN@{=WKl2pMeYfN8lTZdAb$#DawBERB)R)Hh@6-8_KSOLUXuluIq2Jo1cA9U@ zoonnww;7x^+Ee*$YB~zHrGa5C~1HB;qPr0q}-@4Lj5C2rP zaicff1y4u$*x?P@KBrH8<^gQWn zcML8J!Ia&OUwn8wuwdmsZ|+b(5=9X@2Aib@?jFIAcUe{W`WD_^dcAa2otpce2}hZd zp3eN9h1>cgkBWret*Ha9e3Qx86V#i>^8BX`erEVD?)$~MCXb+)`R0O z4a3}z8_>S#Z0ocm%cK5Ds_T-3-iTukgx=b8)A0wq6YN{&rPHck2axhX?{OJc0v4qM zU;UA)5&Yp^_+P^>p89!L8vc>i?ZqqddBLX)2|jV9_sZwp*3KX4xr2Q$KTEo740^2p zTq!%gYYY{^t=>eFDzj>-Vm|>&yG)wunn#Oq{OmiDucqBMn`3|GT}rw-gVx&G+6f}& z^5FZ_`YK-rou4Bprh5idNuj@b^u<+>OS+TW)920TV|)iINBlS!6qkZlixBJZ(-@ zRZQ3C+IsuzO+02?-lZhm&F1c?JEgPR>{@jSs9FhW?D% zqp3O87#|sE6?@baGU z)Xb)8K))tL1hWY_E`|`{?7|j2200-{-&Zj_A`G7r#SkM;(uk2AaEn(cahbo@#aQp2DL8{e61y0F#5g%bZ`DKfjKI2hOt@ zZ|_gwMZgu2-g3`kQ@qbHioMQ@4+wS$4J2+z44mMjc?*pGT5(ajF^Uggbrvh;r}#C* zP4SfeQG2ALc~Zti|901l=AG3JIEP#yOkrcKHvV)YHx{7e|8VJ!dJd{FeowZ?zJ=3o7kK6H`q#1?-ecm?Py={>;C_1HVSMKydcAlgsq>+Xph9+XN3Fu(*F2Xs zR5C`zp-%e}9bPuPn+X+7%#fEfS8&51(z|-uwu<%8wi?e31=W_6a=C5maMQP{0RXJ0 z?`&803jmT$r~5LCSKusiB7@na+)$^eI41itQ@tGB&BKD(Al%}3D;!pf+E*c^Z&7U2 zhKzDj`6^e+l6&kNsrz|b4IE_IQc8JK%(DuJMveF>`W2y87RdQDxTnLK1rs^UMR)A! zYJZh|-Cvk43;k-HUTO5W0=FpW`b95GEEpC00xK?$L@3~Z!ZA>a&D`Ex<64D@xJ*~GV6iHO??FcdHiDiN?8GsEgRC z&OfN5UztMwTMk>wEjQ;6$663JMwNJ^0vC-D+ZqR_i=OS_+KOs;$4bgg=ZdOHM+sgn z-2rs==*OtcS}_zDd+vvP56K;Up#jUA*IbQ8_5v?)bqOZivM-S?uMWNNAK*fAT9HXh z9X)5WjPG#Bt}ynvB*=iU<2zG#i_9BBo89@T4>&3o&q^rIr?4$hwp1JG)I89~)(;q5aq$ zsEv9Jfk34kg3jPNhv=CVY8aTT-aB7!yyR9%p5{h;LO1|t3~=&RCFqW1j12dXq@+k} z;HrCukNZnbtejYtW&t|D*;lwYL6r}2B&loXrlz6zmhp_~t z1PStRvZIM@<^LHqlwFNv9Q$;7<4l+kc8ydRwxemO%qcec0u~9sCy^@PcKqVOBxKtU zPx{Bsf~^n|T@^RhQ7!_zUwQ@6}{-x-z=l@ zSUbyi+e_733<|lqm}57pIuVLc?GJVGf*LxpGMX4gHX)h!a_^0@3fqA`q(x}btT zpQf;2hl06u5ur@j+|6!~Wuna6PLdfxQ|3{JYGg|ClMtvwJ)yp7IObiEegP7uWZjUxRK@d|j(OWq28?b^qkeI(zwwyJ1bPFOj?1o>+k8V&+zE0LF_|<5?zdp?{ z1?kxWSY_H`SOLSHaFn@UUE#-WfU z*Ta36EzwhS(h}eoee6N>CZE5B3C@x1Nj_--`q3Y2$n~V&>4^6%-u*5!fPPz=tr@K@ z3iT_SyK?QQW;?&JNHXN{ri7g;*z&yN&GU=+$ne9Ry}|_RN%!PM@>M3ijvsr#zEu}) zaonwo^_-n}P`=q0ZZY0v8-hn_^|Mdb4LcPpT==lTQCwaE;6n z+jyuqm@v&6OmBxn6Fpb^5cIX^Zkr=+z80=t$g++Z;)1b1Q1_*RN^`=@^6^Pyae?~wJzeYS;-%{ z4K;iCQ8^*r7nm5dM%g(C8>CMF>wk;1aMBMaxa&s{Yqc?=FO`c*Ec6pNi;iEywKOR=0sOM7xFEc z-kI@`=+q5mId(k~uS>&oMY+Un!+$>q=F;%F1hK z9?cltN7c!xX}E1fT)Q^TH4>+phjpfzht}xkVSdZ?3=BJ)Zmc!TZ@aB$)RonfK3VzR zj^7Y2e?g%=C2(Y(YzV2dv;YxlDUYQODq_BZLqrP{Fhd0f>F%uL3#N;UW`5_m&`Z@% zSCP0XzVUALEw)7l)1N$c2mem(D6w#Iq<-rYXidt+g43A0J&~_0#y!WPKM_9~_)hYd zsf!BLcJCS;CLuLM*e`&(3kc{<(sx3wE!LPLF^t`PSpv~|E&~XceUlVws;OOVooAJV zk0B0xxAXVn1zc@MBjUz;1YP<1khMD;?p41<%vbk0+dw!eqxd^VG{C5Jw%0KkRLKUn zFY7LJrQx_W-d!QO>2_tV_SI9x4J-2S=^ssP2R0wx(hQpjYJ~LU+WKc!KIvx6c)<7T z`<4Y#ib0D^xDud}4=g#p@6S3GYt)!PidfJirT#k}QW=7ot6G4HOwIDW5nqvU%;eH# zy8W58m&7tbv?4WNQ7W#tB%2D8h?ux1$N@Q3{hW@sX7&Z8P##haGSe)9gy|*6bRO7gae<4Nam1u0}l?dnVMd1SV zs6|N4mwpqL{hKTi^t?-TF2y_}CBu7e$~i~(SU%P!oylp;cYS(m0&Z<(;7_!$1wCuv zPW1SF79%c+CKcPZZQB()so1XA zR>ih$+qP{R6{lh+-@DKG?!J4ic3L}k-5*bW<;UAbAAM+!Is3cs#s1g9SHtty?=SH8 zoqf5x6zheM3fy54U6H+~Q4-nw1K zmVMnGU{ckcacIac`0H**aDg9!V?hPD#FAKf@UDT}%-G|`^M>4Oy{u;w49p4RpY#$( zI4vYQxTIygE3!(b?_aXiuEwNjX&=`lXc`XRC6c=-lq{Sjiv(C&p`XhGfYw6%rm&Fg z`uvFnfAB)as@84MR3O+VKvSDl?t_05db4EJ3mk;_q1u00CA|nC9Fg?CJGWtMZPX!C zWhE0pIUtr82d-$OkqaX#MlSiLp(hz-aLtEi)H4~$bmuvuzUvEXB!pqqca3A@>cL39 zI>!e;ES$C$XVu16&wF9+{oSQ;aU>d)f>|ZE9B`>YOe5t&0^V~qF0+YhtN0N5y7;hZ z)l7QAX;&YD`BEG!^jsgkRf*D~M5(q&&tzEL1p_mp6*Iz157V>SGVWh|01vsl4n6x}s#zV;Z!++K zhU3=sp!SAi%HJ)8EHzw=KRFCoyU8*lA0#0bcn?xKCD!((_7SWky*gO@Ueaja6IRc> z5VX)Jl4hKzbYQ&DKj%}Z&_BD^R8OX>`|r^|e_(GorHB+WNkiCfrej?^`@&q{Z++_+ z1?xC@19`ie)W;xTU7c}-bD6R6-Ho2fwy{1O5^JXj|sD zl!M-zw&YcdTgfKDZ>d|LOqwx^9j}ezO|yoV1@%CjvwKIx6aLp>u5%u@Zxle&;V=k; zlKz5**%@of@BJd|^t^~Wq@d#gV=zCI^h1@haVDaUfD+==t37`cF=S)JT*XU6CA9S# z?}KG|N%M9gM*kkZ`*SbsbmQVqx!qT0Lqbqz;07CFIFR{7m7hp*dWj{4oqYcYOP0hX zDa2vX`c@0JXA$TYmB-LW88g_5cB`h0n%rT}_)8!tLjAd_9RcUwDXoBGpE z)+EKDM;4wUjvSt+nl=PCbL9^s++*!;dsjLjA$W({ee_Wxn&hUm%fmy&Z(N9bkPeU{ z*#X%49hqQ1Yd2e%xu3PL420m;;Mt7tR?IR(IDe7ekmLq4mIPa6VE1QY17_+j6Co&l zF-OxcHlD$C?C7b%87hpq{$lwAoA(?%) zDHNU%KEwpiWBcT1EwFEHH)mliCm@5l*Dg7Ilx9Mcyh~iXDJ9R_eVh#T25RGXT>*+2 zt?ITnBQl3&`%p}8{5Hv+;v&36pe4O<8<#tj?6Dk~~m*CDirbjQXduq9u4O zV;rC=VQp3ihk4LXxNbt-riC}&8XUw!DWczwXJrgXG{y^lV3FT-43YY0)@nY0!F*blTmsyM4cRp~emS?Xu=}PX6P{ zdh_<4-$Oh_%ohfFgCq-SW{qg3Rqm%lIZ>2ir{5kSky!_CgARZENX>MN2X(~lcqdqc z63q0tqK5akFr5^J+r${uHMU8clo;;ZWluNhr&{S5uGx8e+tXGZk1PM=X}KUTT{Q(G zNc`a3zH`7kU$^h)s~#o%*seE`K{`3!k5K%+_bxA~+U|X8R|$s1)&>|B@0p2D#48IC z+ASC{PID0_Uos5lKKxxFS%cp80;J-dJz~hN#U%o~BD80l2=@D7Z$xFzdWcC{JR5fd z``O(Cc=qyox`&$!DHtZLC3;i&+Eq~GEF(?7xO@ht*%MXoO&)xL(I&8UR+4Je?Zraw zMMT_l1FTsSA$kmPUYuiVR;_TUDRxdE5WGjd@AO06_`H>J*7=3|x^tGgukrci=T<#oplgb&XvEc@5e2T{l#jNsL3_ zQRN)JdWKl@kuW^!_@<;3YWq?clQyFyw{Jj6v*`HBTYhsiRa@Ed|0T}MfGaoY_T_DR z`|)J6wUaD=ONSdTw8D837Bnz+)xUQLqQ{xJHq;NSFL3tjBO2~e7JhXC9^Fdv8vEi% z$8XBu__t@!V!Sp~>Hse(Yyya`NS%$~W}gR)AAuuRpeQbJMtvg>P|Y9vP&h2E!Fpr( zzV2qOLH%22q1!S|45HrU_Hx{+h@M~ZyjNp)J?mLtOIXKWn9*SxWt&Se5l$>$99K!j zWsUm1lV2SH(bzTq;Vjgg&Pi(ACDpf5BZ5cWfoYGrIsW5r(X@D@(1l&gO9ooSqfOII z^e0V&P-XP)Nevm}=jkEsv$0bqtwSx^7zmF@q7dvx;od44Q==7l>AW-ety^0+&!HeRqVTBd(Vc4w$tWWMXd3!itvke_bJ z0RC9a{2J33oaJr|tdTVz3t%$`)4L*Mt%l17&%3d9NQ0C$PY8)n!@q1&9Ta2NgS(91 zQp2x%wg_bO_+|Sm{RZ+BPJP5SVWldcY@Z5%=@I#vh$Gnc^Z-sEQFchFD&0)Xkl{;< z6mOd=?f2L3zz>ATG?>$B?vP~@RJ?(K1{sBe1Ev|9`H2{V(3#n2t$Z|*82M)BWoBTV zX6`2&yX-G2NZ9k_Ipo69xrL)8BLhkqouv43?T%h*p@#o6WeFD3C$ESIQL6PO#0FKggFzfMH8!5&I#h0+9i8 zP2+-^NHO!xN#NX)C4gd6Bu=-P(QPYli^aQt7XvG(FWuNc&{%)nQ^3s+reG(@O8uHI zT8kLeH?n88DUDN3aE&OZDeg}4CK}`SBA9(bw=Hd(1B=|X6uoG+v5-SEjl{GZ5^0EC z!x{+-zsG#S{hZ{PTI8ikmk!r$p(Vnd=LY^UG=$o3w+n;vo()Ti+cwFXELf05Z8Vr+ zXTd4pEVb|I)Eo3UBMf9Dz1Ont`nZ#-gRl%ah-w=X4&3Sp!0XD@GMJLNU}g7O@a6*d zel9DnpqKBYv*W#2)|&flQ;+qY@l@;`YJ{+&RUVU0FTA={j4ops3RUDaNvK6p8ENR7 z>BK!ghvPK_WLj{thmEmV@;kLk3{MEpRiS=eC3s*WK5AA6qn3CVAFHa2663BPqVDnV zc6k*b+9z|sq0c6tgbg3u8Ashr*?`ZceJ1JpfnRMJ{{V#zb}?Q8QA$Q`Q;G9ndQTo) zB&u(H&WDos44!b?W_pcG@33=AW~f$)fHxFepOy<_fV0V3C$puY;Tz=!xG=#00?BtJcW(YV0Gev9T4Lj4{Ns03> z=qgk7n-AoOeOc|tf6VqGM!pY~wsIgm?60wVFxwU;Ax^p0{7@h0FI>hwDrZx@dRx(M zsLL^wGj&0G2el_)=`mnIN#rjMkR_p45C1S2P=--+&$gEjt{6H~q~1lu1){0+og;$Mu)`&D2T)CP2hb3RH$FsSZ+o*1-$J!02Y4qSg3|4Yk;3qtL zD56|vX{h`+kvr=b9`k^_;2 zjELV_sMJCtAu3O`RZ`bc`1#AgGk3<04LRbU71BTPWJWZ9UTZh!$Hx`1{h29d31(nP z6b?c2Z$kS(fRTE`UUzSC-9*L<3=P+zGrm$OiG$~}Q(e9ax$G`J;fi+x#`tU8jC`KQ zaSDm&e$ZQ#aN#bhqq2(P1r(=ZOgLgsJK_xInAGA-qx}Brgx94_o}gjkLLB#BnH#59 zbE9jij{sez;ye3poiA>9GFw0dF-HZe;qIu z^zP2fqxR`e8;Q*juo-J>Dmof5;W$oGbxX5v*{IWvkO;YDV9NN%d=ym%%{yYb<~MR?ofo4M5?`VeM|_Uw{m?yubR zWHh5*eX@Z@3_?I54_+p+m>H6OTCib%il=CuebRa#AF6~;s?tv|d%~DqLoa{0QcyYdg zMl{%Xu+CMd@)aVH??8w{j3CrZ{1;{~-o?-%eQieUQ~JG*z5uFD?!02+16#cks?#v4 zB#f96x565xO3+fPRv%D@iL*O2kdR`I;mol5pa7Z?*EH{BF}5S2f2%GB*~{du<`dwX zNh=bEmSo0}{&w7$$RZ7$E%__!YoXf!N%9xGLVFHe9O;|XqgrK7XAa!`FwdGD{Y)p@ zUj3}Y8zXOAIEpE6sP){s6g6PIOodlw0)Z-Vt1moOi5Tt5WY2b0;%7T0su22S@sx^U zK59)VW*l)jD>MQO4)l4JmGQdc$_`Jz3Ij&JidKKnsf3#M+9B^Fq2BNJ%Vg~r4^Y`2 zaFDbLpESi|xfk}d3Q}X6s1PCey^gF|Y|Mdh6!{2T@g9?}5wPml9=YA|uMp7ccV!~G zBLY0X-XsKR5<7+>qQL|{iiK-gUcixmHMl>Gvqxa)u354n%Wqd51COTm%}-2)KS!s= zisuQ{^E%C(Kr0>dNXm+8#yUjL22!4M60ULAN?4x8iki{1w~b7*6Oxwu8! zzt`*75BlGZ;%lvnDFGYS6n%4AY5^IvImae+@xk637N~rLX(_6nY+En8#-5X>pRzIG zuFAELpgbQHT|V3%%oK7wQk-^8;^Szo{~df^AHp{kjDMD`@%a_U$5;5wzg!WJBwad$ zg!i^;?yDpqH6Q0mFfm3dlI?6F(M%^Il?ApI#lQDwCbHjPy#)&5<*WbKg&$@*T#!?u zcyMpDnQQ%Z9MKS#u|!LX#ExpP_TFf01j}gbD92ZWFlmBY?TXQDOZABHwV_pLm6`S= zqi<_w3VzX!Su9+O#GBFH)ciIKaaPrQ4Z_ONYhpJGJs~cD} zMxO2Q;I1AO(t>uX(4ZHYsF3R3rrka=`P-JbZQsYx(xmT$DC%9n1!f z;Aww|ah9VVMpvwK%T+KBQ=m;X%j}fCm&JlW5iY~L{4vL460IchFqYb;h<~ihm+9X_ z7uV|BKAHxA=6 za#m(ki@^`>oj&>XvxT-!-_wfv&xIxW#jZNQu z;}Zu^RL4INJx#`!1CF6FpF=${pCJWcd4bK+llEm$$YE~__)&t>I*vy4`w73mLu5cN zY&Q-^7S@yDF7dGDmC9Wy}&8a-9Vb@vJRA#8}}5Q`sGvYJMw!Y+Dq>@Ba;yjkL?2=(T*P{K<8sbe zS@ixVt4CGmXHRum(oK(a^~n9cmd14+6@uzS%_@Rw58Od4N@whYk?1!#nS({(dG!H# z#HR*bH%?oE^M;gKQ)*vlI+|*s%y;rHn*3XW1YM-0uSNPE?lkj{CZ;o+7iVOg&F|kR zoNW#JzpzT)DGKwbg*M!~8GPf`Zn94^Cl0f-tbjSPSrT12Zm-eS1xoSWXUYB12xyx3cF+A0gIj;EE@`^Noj8kpC*~w0*j_ z*-$vTwZ7HTB-OJ>+35}n0t5SShNc5^ow!E4+HMNTP}%|Uec4FlpM@w+y#3ug(#J#X z4VP78TAtT*N2D9(VVmRf;*DtBF(aDZ|q8EX-QS1E`$FH+L|1-LnT^+ueI z@w^dWBxOOzqDZA?MEzO2TV*vno7yhIU!hGCWihl`LNT6bYuJ~~l$Fel!k`d_s&|?u zs2oc5W)Um}@?>xLHfwDFUzuNhd6Bv=0CHtqvyZ$7ttr&+wvXlR<}O z9(4Y(Zz~%^%NxIBwz?O{f`<*&kuUuXB?2l4pTb3~z&-E{0`MS#r-znupDDMxAU2`q z+o-iQyUf8DgP7KBvSE{l;mo0=q;6LoyjsAt*zb8ZpBsu7jx`w~L6MHu>{=62Wss+xSzy{LzX7s1ie? z5RS-i{g26S!=4izM=6vPeT4b#`Hz0w-h{r_j3LB-0ab4$e1)&Jsc!cL@!sYT3*fk1 z9{yJFLU~cVwI%EcWr9N}d_v_PjFoaX9`aGo94{3aEAc4}Ns#|gfHF>d&GVtfH&=U# zlmD2dlr8o#ci;DSy>yMt!a?-r#)#=dAk67!ez?=VkJ0DD_#i2ErDqZi!rW?8`9Up1 zSgA^b5$Ah1im18h<$@Pivh2A^a%*<~)yZt))}~nu&#+>gj!A8x$em4+L!#J!1?K00 z>Ngitp>*$=QUM(K5pdIE7QkQch_$n{8`I}YUEk0}%rgv}17-Vuy(M8ECriE8#P~@5 zJ?fK>>XSQ~b@a1=(-+Z~Ogz0B`U_F{*+`VhBA=2Ru>18M6sOKL)jdJt{)awHbXNxv zs*i^TInebdDRj-8pB4sGuNF~f5%#8$bL$<+CJ(a`tgS;Kkggah#&%wnm2qGSL4N-w z234RM?pQEJL}D+D_-ii}!%hUbpE=yE}amPPj%jAdVH3n&vR-(0`1BzMfOF0>W>KMm!wpGtve>xW&$jqS3Z zQi02jV{f2QUU(FJb93dC#O*ygpT(-5F`-VkqY%9LjQH*%qNEDR?+{3F?uSfBif)Bf zs@u!dU+t>f#7e5V&*VsRZify?bMD3kp_sf5?V+ii#cTuks^4;T+ZWMnIk?u|+lr3Zv%zl^$o7*Q9hvKU> z?HzsBUJ6OcuwTNIOYb57EEh@OlmBxq!Q`rrlnmRJ|7k9>gUM2w(N^1>PKtbIcs#5Q z6joI^nZR{CVMTFF!7QCbTn3btj2 z1e~?l*3_i`Q+dSjyL8i+_Q9&}eCMw7SOqQ(Gu{wF6i!hz9u>M+J0k{6>w~_c@d`@q zr36{lQ^czVs`3kGJ;A&nh7soKDeLUey#PV5qT%d|KBoa}7z+DEU+NiFMBJnLJ)-RW zVQLiM&8XAI-KX0U>T2Wo;D#4H>0}cy?lpomA>?q>a|V5|=nq;zoqpmA2dwcs+i;(e zkE*6pNQJD9usdzg9)@ZptlfO@ZFoL95`txqSeZ{+IE#8V=S9ga;4SW)TG{~zHbWw`$O^Fm7s{@LLtp>cFVCc!i(Bzn^N{Z>eklz3x3hBl z1JAKZpdhS2AjO3T75U}MD|(u(4Tfngf=~mV#;?yv?H5sT>{m^lZt4-P`+%j zZfE4pWcaldijZ1-ZA>iHaX&D>pKH^Rr(djs;2AI!6!q0GzE}hQKEb0DBfKnx3+}4(Zi!!iU1SkI665w} z8xrH3lrX9SjG{rHzYUpU3knRUl`g5p#sFdb6ExpLDW3qO0+l-rNm)*(GLG2uNXf_x z$nYzrBkfaFb-Xxm5&K!PZ;gp?u^hTSDiozDOGY_+YD>ILCDS_D&>i)5<5!90Nb3zukg9oD;xgC<>~Vb z7jHRVphgdJ3iyetm~xkdl`Xju`5c)tj(S&Ykb-pu7F*Ur=2~0QTxzsB18pqpUxrtN zIuG6IQhDFY%X>*tL2bHRDLT4h||Xu zyR!}yXy8xXm}iUYxi$6{h~d-SoY2H1yEpb1*z#%~&0)tZyWeMt3%E7*70BXS-q3s5tVy~V?N1$Vn%f^>FUkYj-EEItNweE2TW*%DoShn5pcGE)yl5Z4 z0-zCKInZ>DaY|Pj1%k{T$crI&sc#0~>8BqyD$*v4kTBtg-Q&a(&DmN|*NP&H<71nC zmNz8f648%qVDo&3myNSK$(9r7>nYO}zw5l!0KKCwRI zuW-ijDY^(`C5xVi$2N!7zU2K)ndB%A7Q>YQgJoe+e`3#lkpFFkM-7MOd{n`oWzlV^ zQ*rwZ*fTrYy0?5rx7JqMOV}%y_r1(nDNLlhiTN=`(`WIqAetb^XYrvxMBwZN@$+x| zB)@CgK>=8N_aBN?{M+jPAy$#3tRw#~u?l7LjzlqgB%v}*fwkuSa!Mam&`SPA)|)(a z5PrJLLM-MqYnPHzrcVgpusKob{4jxD-`xs5Yh$F$mPgDi*XLFd7njKWuF$*sk-f6R|fq zaQI?|$_~N8Y>-;`wi0vEtr%Ui+u78Gvo)PdJ56N4b3SH+3SOTAU06sZPY zTiWuq(j5`Foo73BVx&MGJX7o>yqC8)wX5HChPhrFDNYb1{jX3H0#ogIqmAuw#}+wF z4>+S1I7KdK_dp%)867Vhbqga!cnuzDP+fcZf$S-`dMeZoJoJxXkk5HA9fCU~(D{j% z#MbLP+zKNiS=I+!FpQ?-@cDoK;oPH-?uv08X80?`^E_qNP5?43naj*cdIu#9GgVe#cTgRj=l(SV>#G;i>YSiBbpkqH7J) zvOhyp)Wp4tjCO5(1Ehm7haZVNYXk2*9!DP2waioPmd1cGY~*z{dO~wfHAWLpT3NA# zYTm?aIys`3%;51FO>G#$WpgxK%X@bGr&?La)DKvwg&vn-Fq_O~YG2))=r`+Y6iBn% z=_8T_{Jant!POWU;)PM6Lq-Qw_ZN{}oWC0J`}Lcq1Awuupa7wef0|MM!qyEr6h%~> z5<#BCd`<^t1qfiQaAVX?jMxfIQN;-($`rH%dDoS)2sEYoSvQ!Aja?`)^o^^U*rY1;%O4 zygi}=(hOznVvWh#)FsA|{kY(fNO{&ntlwLW>mZw5*tJLD&uQ*MS?DU2+gz_ zPFeBs$?I@cAS?D+f2!D=)iY4c%bltH^B%rVWxQR-0X|CYrkFDiFb=TZU}r zTqkUKf#Wa$t6Mt$!TEZRrTmwrznn~%5;Kfzpv=z{$d}7H#m0K;w$QLBN1l@J8dTUB zW_b@hsT;fOhFm7<^F9`~#T_5c8D6WIMjZR0{MqkDDI;(;oGfhY#BzOxcM8oLXJdY& zn6p91%Vqfo;e5jzi8`%6uo?He6!Xv4gqCLKJ9a6=z`p(|8Ey9G8w1P9FqvtMxx<0P zw6WeWTKcfU++`u$J*07@CcTpj9LT25Za=3qcW1aRzc#W2mSVcoa*Xcya&D;PeVqhO zxl2r#Q24b7I0+^i=F1A$icflP?+y6~jLGg=wW_*K_|N@Hi#hUeB#sRW{& z$u4Z$EA@}QPr0&d_7%C7JKvT$ks2k8uDU41PHMwn7$`(^q{8Do{oT}$YH`UZL_yN4 zVYI;#IuzNxjNYJM2`BCea!&J5{l+fl1p6$A&hzZ$pKqo3l-}a|5Bz8Muh7`_da;#+ zMV3sq#@W&CDE`@D?v7w)_YF+8WxJWxBk#O-%c(6)wlVGyI}4JXzhFG)NS{Ie>cY9$7!iX<6tLT8gNiQRe8z!^;4>A3_7$MQh&eMB%Of{I zhv-Aq4thcUC_qwtZWJF4620(#t$sx&ynMdA6X&yVKc4g!h{Qx=s&Wg%kbPT-RO!KB zZs)WRvtPmRkFU{ue%*TJg$!K2)u6WEf2R?Z;o?IRPuS5+_;RdP3`#(Uyr)L_mYGk%&f%lZMPfp2&av3(Z8{n((7j$@y;)C3vj0pHI9W4 zGQnWk`T6E~;7m2^`-g@s^)l@)376DE1c6h#AaCcs_SI9U1GB~JDo(P$yj$cg567VhNXyt~hVeA*?~ zs5I0C&1n)}$^LC^Bhdl+nK@$c${%ve4itLGuBt7qgCryLiYg^2`4zuodT0pxpHHPH z6!-ex(q-)l%c33K)Md*A^0_4AgqypMhAnI~M;9|cKh5_3X0;BkLm_AYGXlYYfCT@k z)tsChEDW8UO#UO4NR5=74*?*0k@xU-jJdfNPeeEVu(`quZTQteLH4r zQ~FXxSDOzI+O5{5!9-EyWcqi6Pl%Ru%4{BG!8)RNyJ=Tl$7#Nu(-gg&W?=2X14y|% zWg#_CV5f>!B4>F_`_06Y7({Hm6M6+I4wZy&0m+XG=#_$Xg3+e)~6F(-nOS|v^7gNpu4Kf>1VcsL6L=Mt0GDPXx^)tt&)$)?->$J^mxV}An3yK19Rc5Xl+ z;rYPP2&RIUq+PQFQ|h%Lg0+Xb-UlK0Ow_V&I*E9rgXi7mr*!@(O8AeIlAt(uX2V1B zNU(^7`K}Lkn$+-pT&Ljn05NaN;-*nNtZY;Biz(yQ^K_L?j-g%dmY_%BJFIr3Az0G_ zPB__oaoF(VIWd25bJ6VYm2q>FQOwpcybf%8k(Fsip+DbX6?bXV3!MB@%!SftV7Rnm zbE|s7_rGyt_A0zxUr0+k++zWr-lzCjfaXTf4OMiDw@BM>>;4jSXY>__XZNmMZ3J_* z8kk>LK+%n!q0$-cGdR&ki9d~vn{apC$bKf6hkh&RlP3>`hiSp+s1&(X{#w(mu8k;- zSg0XTFrN{nJM6}RZm-J?_M5gloX7zzOqSsUA2n-*`nB+}E4dMg;`!wLeZ$H9aR+qp!k zVV+w~_N?%fWA@zP=$-<6io)N{v0WQ4>Ej27p4Z+flFh`Z)vsZ-zi6zI2jO3&QMFa= zWZMx5%XVK80{wUX8l6OVXgBx()zl||YaIWOXa7krrGK|Ri^Pq7xxdiSsKm6j5C?yG zor=JPh3QA_S**cd#!gu^@PJ_-_|5~kh2rZcFz#l?lbgqMWAK#eIWzf@nbXt#0aP1$ zi5jd=aYGt}8ICT6jR9eYC!NmMR9hK^1~bE2t?5?%aYVWtuWlyM4!5^N&48sIM6^eM z7o?4J(^^>pE;4SfgCD#0i)_B+;mwr(O?3N^r!YnvGU@o z&T)S=o8nUsm%I4TDzcpPf<>Y#pjwq{*+d_+Ru8d+XU79+vjPSN({ z_8auDNboAKY8G^B#ps^J%%qTf+(WMgdmzrbEyJ`T@Bk4);DM?;BD7w21o@wI1G|wF zm2ef+5zCurDf>2c>MDAraWXrvq z=u|Vz9%`!=a|GU5X0o(bmT_;NV%;peG6#aG>g9#iUQ_IWQS)XoNyn|BxOlPe8(SZ7 zeEHUoR8e5Orj(!8-xwO0Cw03>3ith;cs;K_e4L7Tz%+Gr5s3F#Gv?4SZo4j@H-`AWsHd6i}lu3;rs)PrWQg~;JA-^ZwrB(Efpw|_CF$YWw zr?4+cP{4WS2^3El&gzoBaa6qJqJZZia$v$bm5ZOFwz7hEMCM_UN z+ldwcqBS722><^C@ozP5ob119+#o=WtNEI6>kF!G0&ED9flTDDQUnHbhJ1cC@SNH|99+epw@#$FfL$U1X__Q)95dT8hNg`n+^V@# z7|W{iN81UVbbTU1QftmM&UM+lx_zciOx&tWlyNh&mWHpbN zd)v<>y_J2-WTT<_)5NJrDm?B)l4KXOu-6WOkD6tVntl=;W-N1Yltxr@az`wzab&m= z7G^ZN?&yQ@GJ3&Y@F>~W{aW%|a4n!gZWetT7Mw|P$B;F{<}^Ih6f9=XNu6VSM_)-l zg1?4%#UY&0IsgU`06hND2pUj;{tbf}K4`$e0c@A!Q;bg3)}WjY6^90?Lr_rcTsAc@ z$t3AwcV>x}*DnNFZEkql&`sus-}EZx9Ih8~3sN!AB@jMOt$xyZC>}k?j7TPUteYC` z_cAUojW2T1U1k?YTHSa0pO0&_H7~RgBs^)uIQ@hYG|ib2YaK-{WGKU53Sl?>(KN!o zB{7<7`lYNJU~!0FQo~Sca#~I6)jQ%|=XBrl(2Mc2tJomnSp8$G)V|68)eu|{d9N1$ zL;PzV|4$6@!@$bQB%Bpk{S$#G?pW+gQ)yiR{+ z+RP?cQ>$h!OnNQEzlIGc~Cy#yFOHMUgWcYy+}KD z4TjX%V_fngNA1A0=Azh+KBr5Ep)@v}neiU*qK3#W4o4$dH?;#1vlrKW3z~+fIp&A2 zuKc$KAC?M-hm=ub@p&N-+5Ra>y=V*%lvN}T>8_>mGh#J{J2EqWJJ0xq#phly9_>7G zcS%}gU6FDtGFxIDJ!&!TS5R+4{C+M?h&GxQ^fkqwYSI`>N+nYL_>C1oF@IML$XGK|g9418XgxDDyN;tqit6osNEOjF?t_aP|oJd7DzMl#ocNiKL z-M0A(lyE67=Z&K#6c`k#T;?-V*Aw#6HYgNX6+$)#i|z2;a7@>RCr}1u@y)jvP}7A_ zefqGKVRR)4qwU-vQuHVtDSa+~jzIO0Hfy<2A1+h8<5n5=tdB>@s5i8J(O&PqP3k9* zy|Ds$PtaGH(F+e|1)1{lvZ#-?_Ri<5Yd*w%En(;fe?}7l%cmxn$@6lha^zu`u3#V9 zvQCgAm^ISFi+Prx0-AMIJsYF(8J_~!!YK`BnKug9A0@(Y}&L&^fl!fhEB_xKt$FzU%ZWxT7 zR*;HXldw)}($V7tI(w>Ym+t%5jw60qIDQ=&VIBA6`6fpGh`Plbk-n4-oKrLSZmYbb z%pJevBf8=CvITd7F-g>rBD4+vBDe=1cg+!MI?^7RRD*O)94Gs8Q|!!4J*ele7(bv3 z+;;+a!VAFppG+qH3*)~NS9vjGeSTiT3=rg97Tf)w<*$i10TXV7nKVC?84?FALJqY&_2i58^bv<*Ttzb23Y@lBy z4)Mvm!3JoI(4FTSr>V-H;A==uXcOsW(jFdISa@wU%6^d#xa|XN`4UrLU?x(HAi~OC zmCL5aRwe_^=V%-|(|=DSE7%9*S+CP;M4PQ2aqX$$iVVnZ&f9^GusRipix7abZ4MWP z^I#X-sSU7Rm9^Fwe3vqfQ@jxrL3ccq9}tai8O zVHKgJ9N%}*$kFqmlHwN`Ay`gO-AEQ=guOVoKyaU89q)g-*A`PpcNVOK+UT+LP`yf_ z?$pXsmIz9%(+Lo#8bMEN;hOWBhJl26J!Z!f>5PE}p90OyliZ=-Z1 z{#k#QRW8<$hkk#Rib-EH2AEV5#|dpUJfHhnhGKXv-3EMDYW&VhfeEf2%KURj z-@=y=>@j0+Cd0L1N1QN5YQ`k$Do!B*m!~e14dM6c#ChTaHMbJt6Aurn0x5fMD;;GM zP&C|(B;_r}Pxf7-G-!0|BH<B#on+q%8t=Iob48{KBOew&h+*E=pa#f*vah}H zK4rE!&Rost>2(8-?uik++Uu|-aD&^4jG)^5O#R`lG(d-kZFAlVqC5}@k4J^qAYs{B zv6l@V(WjoEvv+ng8)k5()8K^K&4hb8my>YKWTfV{ zcN@thSQO@9{&+Zi9f9O^1;nO@;pPl{$3^udyitcZF)1F>rL#Ub(x3JUC)8-P*8~;^ zQ%G(A1jG^08S)&!iB-vpCwF;J>hBWaBOPJwJI(YO@y^9^)cTdcCR~}zu-5vw@1RjMoekS6 z0)=FL)>A?nG5?D>Yyx2{fAGF0l|0nkG3)fp`Rm0{t4<`rIk7b=)nWfLuThO3GQ(ie zYV7r)J8?X1FaXxO6NFHN2swgE;M-yX*(pfOEYB~{%jkLY%wxqf2<3(m{jz%|;xZJ` zm|Xl|n$~sD>U3^OHUms#1K&_U`_6xI`YzUzG?*dpQ7K{okTI^S`6K(uNeE z!Q|7bsI#c7TleS@k9r`|#YYno6;c32i;%Bpx5NpdD3hk)24nOXP%o)6QJ z$@ecQ@OlQ@m>HmD{gPawk^-C;)OpNB#zK-X13VJUCB|X{z%Le2Q@=@Usx*l2TTM_> zY4NU3RDex*6Kc8$jlbTa+$;4sv0EP*O1P`q4{K_d>%+;u@0Sh6dLy{prxJ1Cu>%tk zfA}$PA+B;AP5a`1Yy2V@nAFS07-Rv$?Ez~X%f=X>5gbROdtfiDh{Eg?p6wLyDi=tS zR%9^U%ZomGn?qlT2F@~lOre{|BsA zMokYkU7`S7^JBsZ^9d<`g%44z{To-%rrceK6M?Ve@lJn1#qW(tWiQUeeed@0*ubFgTQ<$^rCFWquz=kD@wQF^7Is+lY_C^GKQbTd_93S4!>R{~3i-Ok&) zDT&*gZx!5j!$R zD7uJ%171{=jc~8qDf$JvPK-phi@t#F`boF=jHk*s5GV4}Cb)jWL=tj5-%~P*8;`#o zp(UXZ-2MizRU0t8{NJ+5{{yPBB49@Y@iQ5p)>YS{jv88pT7|C419}fED$IhAh>A#p zwG!jpw#e>R+ z6xt2HJT+z+T*r|I{h$@BeR_Me4*_0+%|Z2N*$CbJGH>9x3Qj6dX$I ze!x`vjIbh&A3L_=F2DY>RW^%-_^O?abjL4VdS72R{xNy_2st=L1Tw<7&By|`R zI!S}zG#qtFH?lX+bC5l?YPCvif)+`1Sr5HSxpL#e&f8a*pUMgMzuCzQaK5ajf|RT+ z9enT9e_d8zq~nK_V-Sp`&lWeA?QyEhZo4Ki>lB)I7Fyg3+SOVu-$w^K1=0r4X35Q8 z;lCUY(x`O@B>Czef|@dIezyr-`#x^?gJvi8^=C~Tv+;Ob(%4n(RCeEs1Zsf*(<2y) z+3qk7#q}UF_9UfJP@{OB>zF;|2ddY)tTOZpa?An&v-?wO- z!k8=w9zh-pigiN7TP(hbyHv-_@IC3ieZD_@K={d4c3_H31w>HL1AK&ch{clc>CZPu zm$!MLt-j6xu;97Q8kyQF{W$-g6-`r~FvjoL!5%zQ9&s1sXN#kwnZM(5+gf&BZe4m% ztZd@$n&+u~2Wm0-d#$}pDX$Cbj4{WiS*!Abs5{y-3csIjL!><|q#8j1k8qN6QkOc= z8V&VPi-&@XqtHQE;@130ClO9k>g4!Y+qX*)<{yfj=Q%eLK(aQ4RUAB9!csLPB&;#D zRWZo8jlm7rgA_b_?|I0sXr*vOL5s}SbtR17WkjJMy>5xf{c)cqc5+Mp`5lw@_PWr* zp^~!SX%(}5Gxt!XE*-QfX^e~yeV{*<-1;OGncktb$ZAWspFj$>NhZ4xgQ*nfwB3qvZ|&kid9 z@c$JA{^=~Dl?>#U1Q2`{xhZ&8Ivpb-!uB+tYC==S#9<)`0s`TWJGQovy2Lt9 z#^2RSz{&pEgTE8UG>Z&pBcsNyZb}&U5iiH6 z|7Wodjp6vPG&(JMEyQ#D)NkJuDjn-1DWu=o26*BT`O1YvPP!rvbn=N!b_F`i^hGhu zKTL!MNjc;%9T1TDMa|>PbQUqpfdg2uai?4RZ}UTYe)Yys{t_@w>4RZ^wK>3|6-`c~7#*(3di%Ti4)z!~so0;8S=^V6i*#>f?$u)W{~ZQR_tceG zKM;8Ok>mbPCkFFB^AOc_rGKdUWPyaqfI$KYGOG-c`V0`+5rzSvLRe`IpqGQ#U|i62swj}5fl+c zTH$%mRPRALe)FOJ#fZxk1eI5PV=Owr%$ZC?r_w!0{%wE?7)9Y;t*_FL4Q4aZAVOy8 zxQWIVm~m`smSGlj%-}qvlwL%KR1)73YqX%+h=o>HM))&Lrpf@cQq%^#=$bs_ zL~A(=z*bk*mh`zj+9p0Ps2PnKPWX^kH(WnuO)qx}(fW+e83Mgw4mrVuQoyg=QN{k0 zKvoc=xUfpu9YI`y zOiIWVgeMb_-#mf(Ba@Rn zT-O#WWH;R(AnNS(1i`}>3$!?Xa8I!M*?#;-ForV}kJI_%VITh&sT0e;4%uiW8+jB) z1YgrzeY;uD)|?3XFp4VAt9RL;6})vci9c#&;AFmc;BSRd zi-AKJ$o2^{O^c5H2ukWRkK2|TQCuf4T5Hzjr72kn~XZ)gn)y+B|5#%QjQE-nl6iIyse2YW7+ zuiZ=)Vyf8Qy^bU1753OnK{VS43G8+94pV36VCzZZ3drlCbi+=8=;I)(^h0(XDZ7o6 z5%9;1(vxLKKkNMZiXgK2%d3fvj(o?LPUxyd!v2VRR~(s*jASN4;nnXm)2t6H3n7%q zJp5^M3+k>(VPE*x-(|w12}~thJ(JyXdcBCsvlMU$lw!lN8}eW z^1ct?wzk^taZ8l5g?YE76}T>Y#s2S()wOLViuE7#=>LM?Vfh!q^DVcifbw<2MtK>L zDJ+-`(H5>1`U?dikdZKuGz~EsHvCJNZrcTA!e-HRv+)f>S|p5&&mZ1|=Q>JE%%1(c zy?4gV*ZXwq>+SuS+rQL3Cn!Rj;-*Z(Kx$Y=Ce%=DnC6CZtWrTi!2#b13Xg(yS*;I1=ZzBz*y@l9-)(-n6YYKTghDaiZj ztzLg@3C<%FJ4>Z#8Z#`GJ{rnUGAJiBuUZk|fi zt#+f-#RB6(DsXG|3tbeo^^ePBX|}o8l`ny-WT~On60lXy9*ooQg8z5Q=r-`=Q?!j# z+(Uz~AM_}sn{Y%caH*}M)>Ru!Ef{iV-(B2JW#GtH`P79%Pg&>4 zH$wY7A<|BZXl9s{M#CXaI96#A23cfte+E(p7qz$VQys#j@xTkPc?n#R8df2Fq(euy zTqVx+Axr7=17sSX-|sZv2zU5>{kDMc-3V$4k-4rf%qwW5AHCdjs@e>L(kIw)w=UoC z8D>H^1eYWeTnf>WtR|T~bSonoXRM<8K1Ok_AdByR-TxHWv;NEH z1gl)j{o_Cfrj=d=g(5lt#}`^|DT5|@Qv}H$E{rFe!IaKM@}dc0uy1ps5u-H6 zitO>>qdd$9g;vDiJOZ&A@5TmxkppA8) z)o#V;N)^}DKY4oVQ+~QjPcX}kStc10x`8sa%p_0-EA-86t|%HbkT1$awmuuCF0H(o z^O}ErH)wuJMGFlPe-#(TXiy+a4fmO7Z(wO!)7?}*N(nB{C^R%ZZWoXRCJ%ky%ASj! z=Y|esaRkj2hkLXJxYNkfL3FSOI5Y(pg+Y8pca#4Vh_GiQdpowhs?)BWEhN3+87FM@ z4TX!!&!SeMtDK7t0MKQjTY;|beg>Fb6(InVX|7`L^ z&MoPvML{#9!roELtN=z*3HcqSN~U!Tz2QW8!nj47Ef_90Rqih`>@aX^lhPN0q|$D` zTS48yy+G1{&Vkn3H&D*CAE6tnf5bfbaFniWYm9-XUv{1{pmgapZn4?AP$4=y#fG>p z{U6Qjgcy$ndDSL zQ|_zdC?p8(0pAqDoVbG2n^C3j*V}iuIbYp;zdl}o`;4xZ6oz5Bx0@C#BC%F*syfpK zY0JB1n+b$X~AtsqvAvA7*HxO$;p%tg6f)6;Xd-CR1z>TGZgV+658mUkbBYy z3w zfd?%^Gu5eR#>8|Wmv?;6@05Q{C$(-h$Xd#rew|83$jLpQ$an}XXPuQj3W^0KE88T^ z{k8w@UO9+P$O40ZcU^s(NcGvJ0;n@Vgqq~4D;<)@l_t>MUnaYywoVPqW~6B27o&7% z5aWD{m>^#hR3BMZ_KeoRz6H+}++}>tsDG!oCo_gzb+6F}UEP1M4Bu6OmQVI;H2m`) zeO|&Lh<5*I0J;D1TmKUr*!~FzB^_y?ACJ|Tk-3?&;kF){9-vVtxEF~S^o)SPi}8$+ z2-m?h6VX`p)cBh(FVt6npQ@u%RM3!>f!Qt1=_ZTwwWo)-7hp}CFr0BJWHKul5cuXU;8vv_s;!1$jZmOt16}F~^I;bO5n;UCb33P$ zqQb{8ERn-L`4XQgDN-2o`@~LJO_dE>jd?H#&^|OKYbCMWFk`!$9xg{q)f~$6N#0Gu`2T{9+O?X7AOJD^0z5ppe|GkO({W20$_n_du zSo?tq(oM20b90Ay@pDpV%^#Vr3y7R!hZ<9tH1TLz`vD8HcKA|zEeIK=Hr1jlb=C_Q z+@}!QZomg;pX8g9bblJgE`EPh%cN>#6JzJ5?`jth^R|y0bBDAL@%AKfm-w)|EqA{u z3fCP!b!*mdXZ8*dv-3_66MrC-^Gck=J83h=fP=aaQ<-UIpJ#B?xQS?*brFdP1|L1I z2{Xi&jl4-kN*>bwk{H;Nr9xaw)<&dtnBTR!1p})BKVE|@C<@3gX1Bigh<&aD^%m=@;rHAYe4Nqq{x^K`F4yt)61gz!6Tp|cd zw7lS-x5_Z*LAq-mW*%Gp_R{#wL9I4HB`CKo=fOt|fhrowHiDM*#j$t>?xos4XWsnF z(-VOv22$8Kwu+-)%z+`ZnS=#IdGKY^z{T^9HpLL;&aP!L>cM;%5bdyT>B8jZflOEq3VRX}5_o&S)7~$c3uVI0 z^s`%yw51UJwDlVXf??tryN97QB+78KLv25l+cOFpx|0M&P6=r+@X% z1vL8%sg8}+^iA@Rzsn@@+uq#wD`8>L?96j%ac5zF{lM;tL{@%kiB+22Zoj7|f6r8I zh5ep3#E$w6u7_IxSbNGQqG$FCdI~gG<=40%Gq=-kdhAZ}izEm$2>dtL??9)Z-cIg}D-)HKU zVHtz-MeVa>wnl`f*^v`Dt-_k>skX4B<-50(45fDCS??5_AEt;9w$sPX&KX0|_eTuD zErosI^J;qt8dOMob*VDd3!5Y5N2h{o8Am?xGi7W<{;izT3n121LIQXVJgJWD$g+8h zl{1?w8hL-gpl{38kU0?w3h4W8yIz)(WXPPS31U%>eg|!y=-^F1l7Y6b-snDPF1HVX zQc9l_SP@%IV-%6vywIiU&u`AC73rG()gD#ijA2LLYxB(G) zmo6)R1q0kn)kaHknn{FCH|A~n+HV`K#d(bRs$xm2T<~BjkdG8|;^kVsIGqU1ywF~* zTO%`nRB0yJ@YbBsC1P`r)PaO*ngIoCN<}%)HqSJ-qGKn-$B8v`^mZ#9(Zn z9&?+ts_7HFAv3Z2!!$tK*CH(~ixrrE_>t$o{K(wkHN|k!kiuogj~eWMa`4*ma*(;w z7`3^d%$TB!M>%D=Vdd2>7VoWXXe^(r$E&e)ESpqb20xHc*J_P;yINxJ7`?jc zeWvPtgR+VP^1jL)j(Up}Gpv-*wJjU7uCa(-l(b?muavmu5NV}bev&U&JgXOo&yqY{|43i?!F^q91l4%7`dgL>U_+B;s*c(TeI2mNV1Eh_oM$*q zjUrx;sa&Ux|La%xj`-L5TR)T z0mZW~B#4V5k|$s^iJ#b_u{m@K(ng{y+(WiH>Qhp3wGJ#fNL(SIP?16~4hnD^iWD62 zCef2NXI%QuZMj2LT`3cb0#h+WcP)FR&1Ygd8xCWx*3xkQHXeTeM`qnY&Hm>f74wFl zD3$5|5z!br+POKIIMNH++lyIPo5S{R!+{wvK+j@OaK7DNfz9VgRBw3e-C zUMYEI7UB!4zBZ921c8P`N;y60y0>jIzsy8LD5=TxCZz z@T{&m9?T}N^=VhrZuGJn5nl`|%WwYr{L3@eAq|-tl2||yIwojsO1_DdgLP#x-921qk z?)v619GV^K>lyg4mlgRVTZ523<|(;+E~qHLub*4Su z2TUa|7)qIVoSf3;X#>34MX2`Dr9i)bk6MEv56fGAYz+F7nEoGa{Lcbea)PYg;!kVE z?zFh9lCaQ*l2jgToX~*^Q?WRL5=uZ4h3Y0wg%oG|3(NLIeWA`G)XiT`UX_c`p#c28 zSfMGgcrQRo^#^yJ$Ee-s&d*Z$ldY1G@q%U1Z zM!#R}h;-->_i^56mOWypJ?+{2o4VZ2Vwa_3TP(CS4;93RE9dTy%js2iN_WzE8e^$tn6K8FIP%zRv&Ch-=Bn*v0fdUmk=GvF! z2u!GoC=ssN{Nzvde6+FX9B(Dw83J{~0|!n*fdr1{A1XAZIqwLlLgT@+^B&#%+}g?S z>-!0(j|-oX)}M@)(Y7&QqTOgP9&*XHRcEk1C<{*w6n!9(J|i6PWm+>zI9#5A0W~d& zhM!-y*GF>vsF z^Hs*~NNh#tb=2UJ%8Qofl{0K%tHQQJ70R=f(iRva0^Nv& z^^lw6(}Vjfd60&fSAy7R>`%Uy3FtYE)ZpQtZmC6ztxZg8I~)A2k~W09Svs;h1gi+B zgO~ijX9g%*!qsXoCHgcl%QP>@D{FG}rIz-K;15HJQEl4heC4 zq?E;zUFSt9tpwFmml$0qJ}S_vmrk`;=lR4f~0TsyhRRg?YO;`b1l zUP|cQgp*AuAMrxJJijG#^uLKx-!y-F$2h+$=6cJYZX4e4v4Z}Vvi##$1CM%Ce_O?N zi@z1m{8%H#-aF5}i{5isYCq0CpE6j!=?8RE=dr1}%NJlyWS|-^=Y0)>tyo;^v482u zih&PP>>g_dqejEkM}+jyMZ2iQPUt4HCi_rbQmvL>skQ4KgC(n#<0!N%9xqdYtJ*5P z|8zu^;V5}j4_vZLJVsOP7Cm?-XR7YxJ%}aSg@C#zPW;>yJp4MF-c+$wY*!7jq1r8Y za1C~2IIkD#rx16vSY(kQTh5|LjyQ%2xxP2_=o%b5Y9L)aV~|(lY6y-{q$_g5eLo;x z*QVQc;6OaTyvF7=FsABEUx_lA#6X=iOAvckVKX9`7*<|qESy5=SXq$6(O`=ajX(~D zLLN=?j=2-*aLl4a;E(4^z8NcqKExfD;?%+z73@)Jcc;l@wodtw;c~(`Y)~v)L#a;6 z#F$0vXf)9-bSSC_tMYJX*x@Hk9c>?E5MjzVyST+VyX0R;4T>83`s&#^i@#ClSJo*~ zXP`b8I#%FfH(+>oxE9$BI130Un$Ek8C9P+6F}9<@3zoX?kx@ zAY^%YfgL@#uOSY9+v`~iAP%ny_^U9P!>NP~mN@@K94Q(8b+%*f_atB}mw?2aSnSL> zsN&_;5?8%7h+?bZdz!2eq6N>`PCU%GRZpiOX(snlA`N^#pA`@%$V0gWhawPnSXEPB z?|X=mSXqo(yOKtUeI5bWIW#00Q_OrIs~*_5Y^2es!jej7c_;=dG!bIq^wK8Wi*KyT$X;GsbMU`)jVh9AZy^7JL-b@(ZEq2s_zaKjb?R zUkx@MD02or>&TUI9?)*_BK>0R8P3Tz)Nb|TCT482i-|$WW4CjF#VC|q^l)Ulvz#?y z=uW8<`tAI=#YdA|E+Xb;>I4QYx_qWsdts$Yg3cNrc5bE}Dk5UGZ`97?3fs-ndAFSl z{FGnHPN6;fVoLH>qt+?K?Xm~{?P>rnFVt?WQ`Qdsk#4pxB>M?)!%AofA2j+N2!4_( zWO1=?p1sm3AYKyopiOg45Gb*!oAxOoU)7@gTxip^!fA#B<0022)JXBwWQ(|uvKobjg#qkraj)~)^boZ62vma%0HnVpui;=x|HY~-+ z=#jAz*IfDqOOo#OD+ShQfpWQ4*h8WNcy3P_KqfnCiJ}w9c1}eR!|s@(b_#U`AGH~3&BbtHzg+!@m* z)Mol#^;%B`TL9?0Ed}1|5mbcgvPS-*yhu-ghfg?n168!V^@!Bata{XNo@V$HMoE(o zuKg+M%UYCjV8OS|Ph$?vh}7FszoUkaqO`P9cn6vtEM6sKg0%6W(U#m4i0qCYhm|>; zKOY7w3LbCpF0;Va$Wm}*NIpIk>4y#P_HC$9{4>SyGX(O0+|)eFcosLTIstR|80Y** zFtKA^uA6f#ibfmG!4#22f3XlH<51NK*GsU>@48W}L{m*Ec^$$Vty?@UWh6V!P^`(L zCIXAStbtp1#^5^__F=Z_E@$FTrvVS=!`yV2M2tpXS>T3`9ma~#*fHQ>(>}yX168B3W2Yi{89eY9{esc2hM&5tOHfRC+vq5m zL))D}8;J`2{L!(hwy4isvyzL+lHX(w?N^RF6p#8-*fM`Yz?}W2n;fvX5iWl2hzUxS zk+SWJe#Jz+)IncfT6smfX;h-ubr@O;+n1G|m_?B`qv_Ri4OT}@K(*x7+9AT2B;$45 z8q0E4N(7}GJYXp+5U+v<(ksTG&lwRfQ1!8TLl~IJuWA!i<-jR{ePi%tN30&%Vp74|+$o5t_| zeh7Us%JBUT`UuYW9Sr)oo%Vh3+XLq@mg6)>OMqXhB1sFw>do(W&d`K7By9UQ^n}W$ z$pPCc$#Rnpq1pvgyInfbENO)(x99{(dE?V{7@gYSBwT$MA*t@&>*8|5=) z<|D#cBDl+L1?SC`JeMP+L?Z zcPLvc>5moa*%C*a z>xS}%qvOocBLTHs&^veKg9G27m{ZGE_k4i50d{H*=_QnN5)xuz3EMAuC(@c#coJG;J@e4fvj=mfo%I3r)O8*{2%o9E zK*GBSpNrAU>8skgeCQadPUPp!KyrmF6?Q2xK#8?5j!~l?!2bpejg})lL7%+QPTkY} zrh+c+Vv|}n+LH~s00u8D=Re9WD1s{1`qoJ@&mhdM7aQIG!ex=xC01$bpIuoq8hguFcL(#N z0az>TzRoQ?eWWu2O93KFqN>p-cW?d9XY)8U_v?XnziwfmvEU24Ng4@cK^S`>@SC;c zUbUk4Ty+DeeRjSG`H&p(@cC@}R}?WaAw_&fM&N{ZRHdAZCudb~R{&zTgC_s&imt;Y zxV0Os6}mf^%x&4k(;m;_YM*lv)#AKIi6lcYEt$@*;3pJ`Le*E3lh z^cAwWGkdy3szIgYg%vF7ugC>F%^A-b<0e*lY|E$it*jjBLDE~wO+lwK=6|zZHZ|*4 zsLQ|-yaPb8K_4t-JGO(DSQLymf;2T%J{e0i?!7qx(&vIIXXq!P9`{k?>Z|C~$q|&y z44~{Da7pD>7kaY^y)yy`z1svmKnkhu+7c!eCMcXXJwTh&4j^F_&B08_E<@FT&Pz9W zI0^V-V*_)qfZg@V*?1;?WEsNLuMpO=30qZOi|Vkls$F*>A4|Y~#P?CU&o!!WXM)Mi z9{aDW+|%Gf@K}?EGQWc3)q~%+^l_J-iO(FG0NoA%%-T!uiB|_CM`+I~&3bblk*{K- zG(OMW8Y$o>7XVQD6)J;PyM4I0zW#@fu-7U+8SIC36#d~N82`Jx)5Y1`#Maru$iUgc z&i4P;T5h8>`9nvb@FtcfcEp9SQNjyKxpS}$&ppU#K|%=$#;c?J);}F8TuQDdo-IO* zq+5GrBoHJ*g?#Y_-u>N%+=(Qmeze1EHZ$G*8s+zw^9xWPnOihTkzOBC4W%Pvw3&#h zO*beY1geBf6U&xjx+5f$Y7@)0+*@lfq@%Q9n&*bW+;`2X2ptUO_$9#UY^?|R>6#bQ zL$G%|4igG5-g*y$5sEk!gZ+sF*lSdwfP40yn+NF$zE(><+_%534I4~|E8vRbE6$AH zr|SD9)`=g}QA>Ak95HMvb+`M&;cVQ zdgM%7}T(zoRr24q(7|bHX*)kE@TU;cl`G zuHilG1kfcs1Vc%Bi`tS*-GIlcS;L%M+2mORpdXBmUr57PjH+Cv6!d8;AX8J`!WYmC zw56-kB3?^k;W?SeMVq_v9^8T}Qd&kwOg9+sj7^RKzu!?sgV7DEsJk!@m7#J=7{tyW zn*hIn{+;Udo{gSJ{Q=YV&&Be;2Gzg!vPZ`<{!G6>5jnM*qEdH?dFwSZ5@kwp_y8!C14#Gpqn2Qr;mcG`|p3$u*G#_7773$6zl)3i2E0BFZ5sh{Xf+R>X7c( z%ZOjOq#mRm*J}@mn+`%ioG9b<0dW8h2}s`l{q@^x;gS@vRvw4e1rnxisG}Cx3n@Fa zv~y@k6FxzMO>iU;(6qef0Sli6uRgnO7L-HT8r8;H)SM5mCNI}skKcB#J6-9Dx*UIx z76E=mM*F_S89PkGS}~j#&X4{r8%WgUIUXgylg#ne94PI2Yr@a%H`922I86NS_>OrdA~ppN>iz}FjG?S2EZ@>`6wd%w5oqS)uw^V-o8OwiJZLbJzDQkw%_ot->cYc|7J>TY`Nt&-y z;(MsuZt`COM$bQshvF5&14p3k zG7KshW73!9UW)?B*o%VsYeO(NO_u;^*--GaOSswjb>Ms=D-+r}4%S_(C*+f=t?N~g zrDwyiCfUk3`D2xNjpS?b@K?_7pc=@vIE~;z%hDM>mVr$LUpSU&OB@X^R;S9iWRWK1 znWPs7=n4L?P0xWfmzl-HC8ufplH?PM||o(#;lg8C96s$6_(QW3}Lil@a3rhLBJ>>ILC)#xK;g@l~7& zC*_>+ZD2AcpH%$jq}N70*AZM46Im6Tdci2g`t4=0F#~)AIFXMedpE8qR5<^OS4u9w zzc1>F$15~Swm8FqvYkm-rvwxCUSc56+w#KUfbtY_@+vB(sIrsdz!jS1?4sw*mv#5L zMw<4ttAe$h^>n#XmiDMamTkm?*IK3~tX78BB>1ji$@;6WipkM)2Y0J~pB&S|Z}o`B z2ddt1kT1*ylysuf3A|#H;gHyQ@ACY&eSU%it}`Z>45n9|+DNm4F=C%$M38|KJt~2Q zN(7aLN+ec5LR>Q$eJ3+!sHKPWYb|YHGU8wA1?$|qY zhq&&gQEvO&Ogn+E6Ylu7NkgIyZR%BtqR=rDhP+|DY`MoweEylnTQXlm1Vl`>{nbpi zhpWU44ZK-OvMh^Uu;A>mp>pe!+yTaDHgVwvm*1Lt)yBwRZ7@OFL>O3fyYypWZL&&1cHd zGCuh|Jbux#iw&y7l$B?RA!mnG4`+%lyfvj6vI}=sVp^6K6w694w`5}qt~Qd>EmK_c zO{Nwk`A$YzzPYI`*vg^}TI=GS^xVRtEdY(J=h%gRx@Tixc=-x0PS(<;y-K#w#Fy9V zBwb_+Ec5Wr){#kaAJmCnpF+tKj<=VvV43N z5A_9qYO)l^>-9+&z}Bs*U{38JpTB>0@|llZ$@r-r;|N#y#soqjsK=-!Vi-o}HS|y3V@qO5AQly^5NbFsf1~C}y++ z(zSWSD!3yR7$Aw3?m@9;1$iOm+z8xPkQ5P9z>@F7zKJ4{=93)3=9emT{6(3-N=S1< zI?hL?@IzbvlXnc|8)89epS459n^?vt#|?2stPxy539_GpkcfbeB<3&nfmTt}BC0h; z^kESTJ?>v4z<5oA8%)lE=!rHNR(nE$Hb2{VUrV9-n+-k8W?rsJUFpl^XR8Iwm+_Xlzbe~>Ff!(bMqI-Ov;s>zfJ zIKS>#p?6Y~vht;^cDt=^!)JEsno^lWw+ucA9yho)rv$F`PMW}*z3Vw(MEI#CE9K8~ zz+OJ_-BN5Rq|Y-dy(}(PEq-Y&jUec|NB(sPT9R!2Caqqg#2yQkIn59?#9VJMOHEm3 z!I(pRgbNYS(pZlf!sZ>SX*!VV{LmZOndN_l88h zn#gR%oKqYLRCwLrqc*D?J{Iuq^Zjotmo?&$K_EJbd=WV*~!%r)&Z7j7Z<2HO=R`uTC_RBoR7dTDM^IP`J|P zn=Gr@$wjXn*3@5SnvokPe>ByKns2fv(bY<>JLn5h8=5Ohn>#2FJ4esG*k-vl9kF)! z(Y@R<{szcUoX%Zv+FJ3dD`;N2XqOs+7B{bQ*~6nIt+a61!=opyeBiW+LrGcvh1)9p zc~%X#2ZSj$Wo+Tpwkw(sxPC<>^me^*=Q|P?)D}x!* z+U=-vl=-Szo1Qrz4Q*-jT4Hh0ZdhjBRqJ}+8{CK3B}9$;O@0b>qE_0c5S`rY8RgF@ zWkCyhbJX{L=(Epziv6z98vz?>Ie+9-qia)|@MFif%j+*!6 zY7>01XBDh|zNfWO;Q#_sUJA+XZ5{dsR|gX}$qT{{+^>l1afvhVr%WO&We6Z_4AUE4 zvy+*u&->>WxIKholm!N5i2{9n4d9&o003ZWN)ml63ORw8O0X4(aHH1KkW!Fp?RK)E z+3psSBV4@*g_a_p6R$vlkDzYHYRB_;TxqU}Yw!gquzVAF#miK8kuf%*{jFuuUP^0H z<}rfIel9kM;NIuxUx{_RwN~3+&dEqY)qXqO_QC@ z+sSaNC02sf=s`l1>tFHvXxwRK9Q7F7v$Ieq99josnnJty0lEy$qDN--$>&uZaJq=x zl=W=lKTHLLbZyHGPWv)FPw%WTV^oW1^^||-#nX!@R5k$LcY7r` z`bL3znMWi#5^jg4lusknegSUT_Y;{r?u5*;XX>S`GjG&c_j>MxJ!Xx7&<en0A%;gA%|}QgA|T9sVqVE8iT0X$p?cF**b}ax)GO3TYKtr2%>Tk znK+SHAIGD#-Xv=H(hgc!bfRGf8iV9*#I<9S-SWq$I_*^URrcN^eUUYd^9Xc<b`ZWRwk2qbJ?z31879EC34@5uMF=A?*3*q(ZtS2*#(xmV% z&IG+L;5hxpDHpF-zoC2PhRMf04!?=0C0cq&XKAXpSuQ6{>&gBg`OMsdiYHXe+><6} zQvGVdaw!#z4@&S=m&C7hU6{3}d!L22MO70W&y>a8D(F%RPQ7I5J2$={)$~U;p1=KV zG_KMrq*zYh%1iS}hvr;#mw~dVviTRC<~Y^G8#Iw<`JRvNg2GU9lp164o@&G;t3N2l zNrGwU&*TbkWlFcrhRJD94ANOl{$IJH6>8LdM+jV06_>5RVk1;1t{JHj)+G)I>%n=( zU^nNiAxd1AcMqV$q+a0$dl&Y_`oEke82liAfXbMuc&RzD!1g<0sG+&&p|NPUk&BzV zR7--GMXc-3kMTSm1w0d}IV~9L#98dT3aG{lbDU(YUEL2wmR-`OJayKRI%OK9_3+m$ z$&>6WPMyn|VHLMvEe`as$$PCt@03He=DC#|L3xqABxi8AjBvI5NMe&j2c5D*vt=u8 z96WUalfRbK;`l9T^>QItT>NY0jRscDfPC0EO2J4l0SnpKBudV$APhWf0H-C)P)e)= zZQffJ$dK1y5@qSnDb}M%lX!{8@*$#p$+4oJ+(y_u7kYm`mMSm>$hFRWLReZCb;Dtw zleNWL2(gZ2gkeAyLtbMRxV7fb1{u}sEn((Bkh??s%+ASoikmLYsgU4F-ktL6v*eSx z^DuGxXWQivtqcf!Z9;f;J%+|BXkl0%m}l8WmgMfG%?o)?!k@Y%pd;)A#4w1b9Kyyt zMjgt6-JQXi=5@DCGmhV*jcyv>k&he>F$SL#YzRKhL7{{%3@DIevxN40z(HvMU>-t- z^oBODen9+fI3wixRQnev}={8=;mP?a*6xsT|wB|8UbW~{E zkn0*i$dH}Vh+=_FUP=5H=lpl#1;7*UR5y+aFFi+?^g4zsQeRA z_V<4bau7#dc1r$nA(KDXe@71rTiZFAs9D$=+qwNajpGti|1**hBj|RuWqx|p++l2P zUU3TwE+TvJLs?0PNYZ?Fx*z;5f2g7j0uG#g%g60?^cgH~NQnR$0SH=X0}DMZ?lk!^ z`)K7%S|4+S0{eEPPj_V_XS)r1ZA8|AsOlTEm41m_=Nw_ZfxOC-mBJ$F`l#o;Jv!(B zJ5E`#Hx|1s%!^m|jyI=V%3cXs2~74*Sj8H5sV6NM!_(NhxHCL0p+xLCx_Uk*GF z{Jnnoy-*}G;c>w@x~rS389Ub#{?EUU@1XkdnUW+7iNAlZqm(d`r+T#0P^hcaO~u`P zru>u$kK}b~OC1JVZa`Lt`c}DexDmD%W3~f~Iy>6sp8}56zX%6^S+MNbCe>mXx~v|- zOwhi5)<}jQ6uUNV!PvP9S)~&C9ALCi)0DcQX;Z2z)<>|S8c9@080d(?VN*R&lu;B2 zxWrZzN?) zA&XSzw z#~?}gAd)l^b+qUg$+csy~wr$()X?xnXZM)yzz0bb)?sFslclTp`si=s|$~?b3 zCr|#%rzk;N>1&7`w8_txBMj*JAp(L>2+z#_j4WJ>U!(|$jIdAH0M##nL<+&^b~$<7 zi!p#f_xAmRe8^F9Icfr(bGG<$*?Yn!UFPMVs!GNsOHnXfk|_utUxf&UK@6k_Xu~*$ zffSQ*dJ_!Ch@lAgL7-g-!`N%)C{ufGHF&5f>YsMfJ>6-iI45~VnnPVv@3Z`yk84W0 zZZCD7CrectS!A9)4i$O|J@PLW-jnDX3h*Os#<}=92tBcyMUi>OoojYq8!ThY1WKYs z5~)Q-X-VdyMH;CG$jtMqxaC9F9vLM=(mb*YMCS=w_L5DfniKk6oTy@|1B|N5PTOr^ zHXZV{Vab%xd&(UK6$I4=I3Ork5i*0Q`NK;l9VTb=8}u$1wiuW4IlwI^ey}raYA{0x zIt#4cx=)azpXaq1{BBX^rzns4#;ZkL?Xfg_P_LIF6!KF)=TRX!C9~*(1!O?!ob{62 zvIm**NHVqV#WmUF0$fQC-3gPiSdSjmDU+M~1?GzxyGX3`Ux zh&=Z1f)JW>G+28C`)QA2_1@BChdqSn5oh3^szzBO$SYC7&}x2sIc!v>qu`Q%Fy-8! z`W}C=dC4@&2a#`K+t@8=^O%*iM}CSbZ{>Ndy}a6~aLJCzC@;scX9dMd5`|ADIdh_y zHh26zkro#GMBYsiW}7(?5k6vlXjf7_GIt9FI>HjbaYVYgW|3{E)05Lr$Br@`Bm=ZF z>Q*M#*a)R-AK8eBqd28z;;M;VFD?M(MT0if#YR$XsR6Us6sxoNNS-^T7KKWdEF#*< z5BPEkfU8i2i8)MwI)wQ;R4t%@1>K-@rVY)T+czKbT@J$xQ6$Ou z_XobmkBLrm*V*@v+gr465dx*CJM=jIuqmNE#A{^ZhzH6p{gN)s75%0)o7hM~CX&`zfxaf#OPy=1{4(;<#`eiNf2Z)F=L^&euF%Ox0 z%;RQ}i_K-35a*OtaUXU=@WkHP4~FddWS{J3qLq9y9`b*$l3y&e80|~1vQ!ARaia#p zVAR!6^syWzHDLHO0kk;B7+gKo!M zW$6hjfQHAxv_JXlJOJNspO2*(Ly(!DR8eU`1btjGP|L9=*_4UA=If`mwRI+q7chAS z6CO%TIWD6jHtb>NdIF_011xkWr+IUK;J|Q0>=Vt@_z-6xWfg}iTk!YoHpNWZhP>BB z8#@W52)ZhOTe?A$gygDqnKij+xM{o&=YsS?;I7PN`r)J9rrBxr4dgvn3`2}btqjCb za9de)_lWe-q`r)l=@j^${RC#T-ad|MHka23HU>!1nJ;vCt5$BE0O66VF8`#=7(3gJ zf41X2RNI!8P)F=nkT_A5O*w?4GbFjD-{!qO3sl+;aPi3x53*SS8DGAI=6TXN?pBMK zn-eC3SHkjX+u{_r16JZv76AW!3YvI{4+H!DtE`$o`N#T_7pb!#i^aD{|4n{H!7@Zu zIfA=mDUps511gKJ8xwAj`l=puie(zD17Sds#)Ye!vZbntA!WiUg`z5Hge~3ySg$P% zQO$yp4u#jqc~i^LTm#OOij-ovmPgs55H%e^=$vl>9Z{y&(TK`$^$E7znxNQ=36-{SGs0d`CXWqgGPDW(puF!|sQw|1p!K{N#NUN7 z59zl|q~d&^No8pkHDzPLT%977WF=)|(Hy7HN119aC#P*pYz# zq|#^SZd=l|iEVr1DyIk4Sx7E(1&GizYyb)bV`|-8t??LdXc;!?A6nd#r*bBQjPed? z=5&^L)Fb!yPP!<=ESpB}ZAdC&y^4Jt;kvWXedj%om>y;lB{O$V-%SW%bRE)qd&NRm zuhlT@&35Tx3LICgPwhKu?2?tURoDP;RQBoTO;*ZKL~B*q*CZ53hgfmrM4Mhr>3Q^zB=Up%gQLB#+JML);`LPhc!*IL zpZSOjV<-a036O|pf36%;>H{#G_6+*YRQ#)^Q)5MCFl@%x8)J}vv4@8E9=Rl!A1Y55Yr<6q>7Qi=^uv;Q6Pc<@=g{Nto1 z^naR*f0-U+Drwsyer>r6))a;NLObi-GJ;q=r@?UoS@Jow5J6mp((-*IUpSg;ugXO*fBcgk^f zd}cM11d7{I$$_q@t>Yj(_(cWg`6G_PDzabVM}`Hq!(ME60i&J5;%?;z!hl7~+sh9a zLZ!oI>5cU$St%fx&g+&ivcnYi=g_t;2r?NxJbia1QPY03_LOsTN_uOEXZFT{boc<< zHzJKI6{ure8erR1u%I7DiG-$NO3aIjupVVpnO!$yS-)+$3CI}mZp91oCA1NI3?R0! zp=>k;Z^S@p8bm-WvYzV}VSF{X%Ga}c-oN~(JsWQmg7`h zIQhvD2YMe0N=z4O)rq8NM-vn5&OK*eBR!}^;i1a#GBZ4NL6}RuY%)Zv7S%nv3C|UP z0gYy7-}}tI%}hx&o+zeA)c>21MKsiKJUaFsX$;9J!m<6QKBM0mc<6`)jwl4}G`5%( zg;)=r7<)LCI@7?EE*B;6&%ar)qfqKn@I!wk7S^|Kr2oOQ=&$jS8icymJjw?hQ(w9} zw;m&Xz;_@GX`qxJEKnx&X+OX*0-*K9?flZXQ@|o%nb>c^LmL;G0FCntO~-M}>uBx$ zO#ltb>8hg2^|PY&$9dYO#&C1XGmgtneT?CNYTZMB$4j19p2s!E3(uW3qUi5S_pp8O{+CL zV!dd_$cBnRV7d+nZhpzSF56U3l)GJnO$e;Qo11eh9I^<|U1iS+6kYXu&gg;~+!$G*x%AdJCCjHA7{$n;O3QcW#1PREZzoMh7i+jdOlIk)1!uZzA&M{>xJ^)m zVH_LF6NVS&DI@ZJCxMT)92rbWlQgIJZfarV#vVIo-j+)fi(NANVlcg(pqh+1f0}2n zEj2i=zAnFWU`AcEpglW{ZUy(Sk#m0xLyRR6F=zpW6Jw=#t=c|!x zR2I+BYS>5#G4)-A2{Qq|eM#uTU*girERl!4+1Bfx?;4Uv2`lx5DwA^Z6_k6kHI?^i zTi8Z8jTptu*~`OtS0X=m5&>?G8sGzS+Zr25`CS$HPv#8L(g+K9__JcUrz7x~xJ91v zLmAstoN|d3WSawN!+w>E%nF6Z5>wTyHP_8XoG8W{WjalD2uT$k$%Akod@W0#Muz5Y zrxeA@e6&rit_jJVH$fj3gqKHRJt0x51uoaTP z6`M0rdD^LmLnR4Ff-S}g>seo2uEL^KJ5M#2U86!1>fo^rcl!m?CV zB2~qCjfl~UXBKJ1#;`A*R>_VH?B}_`+}Y`#64EJ3?e|CWJXkUk`%pq!lw=A6B3Vj< z_@!|j8wKG`b-Cc5{vPU;;nTH?fpJtkizL-AmNM)mP!U+?u&W zb`2Cwm@eBS^fVSMWIf+AXERcieL_Tq>QnL15fZ-S|WF|+mY z-*MJfPTZ|I<2!T7y4Rn3lg$?lgC*jKXRX$~453hqf>itRJmrq`@EfZ0 ziI$c{>c8zjtcXzNoNRi{hG6Db>9(uqvw-~=ofwl;lfO7R;YNqVHiNxj1jxZT7dE8J z^jz>*_u$ABrISRQ96>iZu?7pu_~KaipvKEP3aEw92$KZAqBW`t4ePG6#={N@HRPI1 z+i!tEa2(e{O66;4zz#MltN?2)Wp0^{bfm}0`nf1!b;dOqF$!oL6?SbVY1ocLvGwt% zqvB109b&O&37;ub4b_+yWAsTMhnRFlVE5}2ZyLbW4!OZx4RR{8^0l*I7e+A)N8>yA zwjpctzLVe2ojkW|*!jC7yYawBn~C|oV+VosKk+1B9?M`zooW}Az8@e>l#&Ue=C*hU zK$WA}qeZ$pG}`k_d}3}Tlc3COm{E^v?uprPH5w)AifE$QbrjRochF%2QHWFn&O>~% z&g11q;dwmruh#)idNE5k7Yz$?;zzozC*QV02h zoT#LQyJm`uI9K+7N893;KCt5DPWsJV2s3&(5>}-rQGokZz06>|d}#O}u>4%1vO&a1 zml+DCMuw3+T}KFq?GI-Ds2f!xicNidOgrg*ZZ@`04VG-OEg;bE7}+-~8>z@f*liDd z`xB|*Tc?Jn_nlYe6wBA2?Kfe;pD&#&)Y=U+DkCE!cCH#%(kz-c(l@mCSt|55F007= z1-E|8QcDb?&YH+rT!caK^JVl>Q^bmbpB^DXTd-+~1i>c!0_Q)3E3Kh98h2}Wz;DIm zaY03(!3HPK23i-rN^#=)T}DYHC@3bnuF5iKih!nD>NgoyI zfv771@n?9|LUd6&m-UP)wR_;BuveEe(0LmLN&WJ1dhpGdH`^3spht~03Z~-ehmC%D z7o#Q)7Hx58xqqeF)2I~ro3mr3TFL?yYLgDbkVk3toZ|>FEzXd!CZ%`bhU9Cmy%n+3 zGdrvEW^|>}(s-5ib+TM;mcyO{AI5#`bmi{VWbN26Xk5W|7=;y-()YF*U0K)TvMa>H zT!=IDK3Tc^cLnA^>#mt8;|f@rC$ht+o~D?>G#zt%w; z9k3T;+?J4CWw*M~JD>nr}kEP zKXAGIxp4;(&JRqOhb6p02TW`>?SSey+G9hxOwZT9ug*Y9>Ud$p0bXni+ol9kzT>`>OU*;6J4?lB}Mi1p{I*DPUJ=!8lcj+qRQZl)Fd^v|JnJ|EV_(I zV%d=1_w#SQ7*iJ}yAfa1Owrc^m+n7!gZw2Y_!mPH`R@$P>8tfX7d>xO7`{rbh=M14 zkCL=%WPgPbQQ_YS8u+%F_Ij@0%c-AdkCTUJzhtP3V|&FxF(EOa4V7sOEie}8bM?ar zP}bP%S_b8Lkv|%HXck+O*J94QTkb?O9X6MctS3>5y!8OSS6rIN4d-sMHGMUJ6ONNS z1ztn(xxhad4P3Zu;OR4@QfPl1nA$X=D`0}ox=dRIIXCVV+zRypL(nG9cLbG0{v0^B z>Ql8LBse0}v0=Dl6pDnM+wN=C?L`S%jB_7BoI2g!lTMLb*GDZ?<{8;@(a}+Kwyh}i z6?ZA21h&s>(Ch=ObKz=5g);+EWq8DVXJcE(z>i$9x*Y%DX#ky;t290+H%@bawf&;K z0xpr?kTqJe&i)lY{y@J7^bTVRLEEpqQq8l~NS*`NqLuW}|LUP-4kXs+Z@Ph0jl`M{ zxQ=?7bwnE%)$^qXDMERQ5)Rn=5Q@_#4$7@*(%)miP!&%Tg!}utdmH1M_3Bq(<-g|D z|4m^3;%g$~r)_5Gk%N9Vn+s{$Qen{#>d})^n~wrTaKR%|;uo$kFAW4Vlv7_f@qZmY zF2Q^h?|?=VmnWum`-Tf?kF6pqC|FjQk+HOSxykGDHNLn;>PGEFo!EB3qN!-GhftGN zXW1@;Sd>YRmCX2AqDYC0u4@HzNEzmLs|Vd4N8;5j9Q!s+u{z zklZLExcEdU>Cpb^W#8`0592CQTrwyUBNII-PO!fa-`dE8I}FKC?NPe1gk5F+K(`ZZ zAQguVI6oF|T7X+-md9TC`tJB5RI5Mj*Rwv0C9yMxRK8hUUYt{t%FRL>m(HG8i@;-{ zh+437dr?FJ%kg#!@E_r2MvIc!_F1?t-+Qo4g;${2Bkrk8!E!TrQ0lwiejxYscD)cM zczkj_x2PTBDc?0gGrDVscRpi+M3SiMK$`_oKUY9t9IOBje#UBM_d%KIb=jnL#p;nA z--3NYjVXc6UL(R38u|Wx(t3S`_F4UvBd}kb#{VQ``4>0)Z+fCc6>X(eRd}AoI2tL{ z2qp>->?+p>`#8YIEWaE zRNjZ&-=(w1I#LhPxvB7fjxH=Nm+RYI7Z-oN-@f8~>*|MtoY=~Xh_;i+-N%C1q=aV} zpqWLarHaXmrh=^6!PKpiN1`0$f>5W_4t{f^bn2vXQvrk#)63+n*Vvtl7rBy zD@}3B_WPOxcDY-%A|q<#JeUhnhDppqh3L_9bm&0yqU1&r79)KofphDAQ*Sx6V3%F8 z0JXbJdIi%TlX)FWO^CV0D{HETQ29gsknqFVO)HRe9lIs3JXAnraQK4BI>0$ZN~2jQ z@Ae$u>^2djuDRLj(|$3OAeAl|iZc`27|@8b3OSrY)0lr^0}r4TD+E_Lcs4@q80>qN z6*17EJEEM>mmbjY%FFx35Y)Sk1i0@!?|ulc-)K5@Q)PPKV;j+iLFq$xhpUF76lEaH z&D&vxP}5Ltsm%>1fo zf-8b(7dP{S*TMHNqcvDFH`r^z{o@rF@I(X_SBC@)rQMq@-t4>B031@3Pmy!_TQp)^j36OXy0-IE0cBtJ=J9J3F|yi0_}Ecxd#$UZ?j7{wVX zrT^My0*Da5cu34lQWl*=m(Iir9W=8qxg#6)S36M@i}PY6W%K%TQ}TI7DPM+W0vrAD zzLO>ai|=b)v)luXr@QXK^~lzDQ}&8y2qdqR-Y0qSagX%j{1Y$L3mcrt`6E8MH;<>) zu<+M9B_sTN|7HvEHdNM@Q8Uv(3zLw7X_~!hmJs0#9G6U(IJkRUKgf$_SO!_?1oKEO zFtohBztT)jLvU`P(ufP`+pZHCR_^Rs^_BBk7D+08YRDrxZ&72im0|=6(KX$_|MSh6 z&zNJsIz-b~>u37UTfZ)YF8jaQe`4&2B(T8O-Pj3M(kRl4*EH~DBQ07#LUCnrc`aB% zw;21Pe4=>sR9py;M|*B?+O2;oQBDZP1+HR(E~+kG%F0hJFS94^URH6x+0sm-3I?wH@03K1fq^7?FR+x^Vmc$TQ0V z*qOH1o@M8&!ok7SUU#6}x0DpVW{mqd#rm@EF=%X)?T!T^|& zikU{6wdYD3(C(ZT#s9znb3em;F!Wk86a)W(0Xzi$#Ik(Zn>9QpQQ7M4pMV+A$@}NR zz2lfcO7#A+dPb>0WTS-G#knp%giVR;M(9OOQQhJ~M2LHZlW>Va2kl(}We6Cl5j>%| z+d}*I2s5gUckO*eSnsR8|F;qTmm^mEro|jT^5E~~pZ2cB=PPB6s^#-4@qZSU7N7(W zj8uka6wgHvJQQZm8X}9B;zFtKb*_v%;GIC|yuRT{awGME`=N2g#K)&@x=y4r#q0k5 z`SznGG$+)tWla!Cibks!#V{$d)nc5*P!(ItwPti`SPpT$bF!wXux^*X-~2{4oIm<@ zi(bi)rf%yC2`G!?qbX#RpPlKwZ*3DH^2!>;@#!zw6)Ro@Z{P}9=ZO0CaG*SgVk*3} zwe#HtxtOebU@fdRYybzf9x}=%%I;meFzj!U4$(Mq0L&ci)(%?y+p%f6qp}!yU$8A( z#5^6v&uLJ8(@^C&+6&S3!?f|qv`JRXGMp1v3&0IQlOwu^m*hxZx=en8E}Z@p z+lLy@aTF*HNKzn!PC4}kxh?nAg(5BSdDHScq;ZR-2WEv~7x+VX`zdsSs!=lxnE{mg znLbgiG#F$|r|uG3#&#w>;*-`*Q46^bWN{Zxt%$|HYC0ET@bRKr zCcSLLo+P34OG5I8e+MzsJJ=mT>bKOmV?(@`bk59;Mx#mO6;S5ObYxYjTE_-Z0J>M@S%tJBoO8$A+3|R> zaQ=P#BGzX*>Z{`2V8lYT;NjnrAj5^vIr;F8s_RSO?YjDq<%G)6&lD)ATAWE#NYbva z$qO?3tg5m$aYMsW@rOF6Ytoo$+fQV5Q1Hnz2HjUGv7&wA8lvo4w%UGgsOC`!IW2^TFD%;FuQ_YnlL zXnDqKsswGUbeSnwMhWWq3^+KpCRdfTH1;;$P|qo23{*KWi(4KrrX4?^5*^v2WRT7d zg1VzYJSwXC@mrKoN=7zcn(dyuWPKP*ID#9$66cqBFRsA~Z$L6Q-AOFCYZf|3l@yaM z5vk9l@7|ecw^i^JYaGp%%sG5sDai(95j>l;0{wW49yWeg@DCY@qNKOOgj8|#B4R}1 zR_Ys|u1!rGy{~k?)~Yh`hT%WL#wN;j$+5 z+^GO3Mml_iu)+VdWbm&d@PC4W$XJOl z)h6=L=PXT=sAjoyxo5ePe~Z=+k(iU*fxHKNKQM{-X;A~hRPpI{ydnQBAl!dQovvlb zIlu+M4*sM+`q*wTZt`X8eru{p69z(}ARjJ_87M$jQk14jQHU`Wf6|shUc^|4(z*of z23fB|RE1_$J99UawG`vD@C>;)-#Y9%d})7NXohYc(aQ>Z1Zgq%nS#{USvdS1#@?$e z6?@%|^E7ONk|O0-jl}UdgU%txT5Ui`K^!R$YI6+0B~U1+%hY@8!cdTvkri@Yz)B0n zHU3SyY5H%{&9?%dHO`AQyFOEL*tj75wX2p3M@XyT!zw4CgRrDM;*x7U!u;aLHBa#O znQ5yeEaq+RS*zRfU&ZA!2ds-A#ZgFi@!!Ub_OQjnr)Blg=TOX*zO-?E%kw>#MRuLHrj%{ntDo>0EJD6g@DyTaO+H8=L@7#tcakCJ#ZcYo@@wl%P}# z8_90Ap&?gLCjwE!fut4r!;~~R7Hc?qqTxdN=N^!ULS|RBl`1?YT-Nor*&~PN+5Bi9 z-{;3WDnF`NXNw;fh1P&61@$)V7*Eoq#znnX&ke~KK?4T*KbcLsn2wIZiB2X?ESZ>#;Cz$Wmy~<^`Gk1Ll zQ+vHvGUB7vBJ3sA6yssI6;`|)GO>e>2D!MZNXunnX$@614tj6&{!I&f)-MXJs z_zaCib4AWiD&|Twka|@{*UXmdD8DS#S!pX%LoT#cE2~4pcKFqSCE5WOj;1-*mIFW( z+LBEnd?bm2;x$cHNlOr%veu9*W!8=-3ZI_lKr}Vqc4`>TTSaew6(p1~3Xmry<&v8I zZfh8towD+F*_FJ~HB|T{Vod{(wPXiHYr;m(fF@lA9+%b*-7hcU-o9+km#UH$9e7Bo+!FM zdajLZnJJ1FdB&ivCwa0iijGUHb6U-K*c&anP*xFD*PHwL;%m)DVqLM&4qfXSI^R2N zP6yVeG^(DLqkIbl3AkFc%l$vt(EE8)P{E{k;toOQI>HhMX3n@CJJ(QGqk*M4u)Y4lGs z#N}lyXU=h;XMV3Sx;4hrMvdrA+rDsrit)LrnyJo?lfBL7w+Yx-v$xvXQU_t`GgdxWq3=*t`+! zBw?gQ*gSvQF^2#JWN-qsfw z#y)ukkn1bZ3Y^`PI}_Ko9@K_U(FwysT$;){S5siJDvw6OvKWLJtK0UWs3hKkD|wy+=&3{zo-=dx-Xb?s)S{XJeVEG zk53&VNHMqIN3+0R<7-!0sRG>qDj@{1Knc)_ORhOLVY{Fex$+UX_F1fc2Yczkh-C6P z!rQ{zcbh=m2qfe(B88jmy1dLVbv^9B_;`N24E`2$tx4ozN8?9!W$1P9rUvs0@C0$CV9HGMJJ8;x<2I1VbrinUR0V>lixOqZX^aBO^A zk5JW_N`m3L&hrbhn8~pX_Da7yudx;L;yPJn*o=C0>23A_%*aW-m`m`{92b+kHxy(i z6*MfEGuf&~*`P9Q0MOgQxLV40s3%H~0(0|zC4Uldq zT+?nSUGwtFUlZ^uUNi72TvIeDdkx-jjIoZPQR~)u4cxGdu~Bcz6|Lo z&v_(gfLyt4n$i*oG`DHa+vg}}9qf~+KY}qIi)7Ka9roTxNj;XDosvU^yfqluJ!a&l zgU;pn!E7p%#%9r8mMW6PN-18P8fx-~W0i8H*WQHdM@UKgIr^3R0YyXu6Auy*Aaf-)LZ!Njns$F6*>0X=bWy zIu+`=ViUDAh<4q`g`UwLWE{$!c86e_K!c6;{itQ*v@*}$qVlzk@-M|Zb01~g;wTC4hM5C%Ph8Y>V(jZjTV>dyAX40hdnpQdN!)D)g`) zJE4xiV~?L(N*9b4dQza=ST7T}XQ1^fB$Zjr8OTUh<;Hv{FaZj)Brn19*R1v7-3tT( z0#4Fx*^`c+^n)I=H5%&Gg4_?+KZ_8T1dRmCcvHMbMicG>uaf`X02w-VK~N%2X0&6K z{b0Y4qI#Qm0_;#_%1a%V2&Lg{@`Q|wy1vaUw0_Pa7!t6Lg$|LE7l#Rx6D46m{mUYL z&h6VgZE5JYla>NnPit#6{$mLHuTy^iTSlA^2_;QBF27moN1uV-pj4uMsM?lEzwJ12mjbO02$W zP#!d#aZ3DlY*1Vfq!1@}qyG+2-&)jRGqgU(!z(|!02y3*9>UCCUW;+SM5d1KjJn&K&R zezn%vHPEW{xo9-=5%#UyI70+NlmhoQeNFcl?tmqQAkYT37-$(F7gFb5!5R>E5GNH2gkqc@2#WL9ThF%Ps%|o_@^^PEg}v z&Pmfjdsx0)f^^aD(W`nt9KS`{B(Fg7(k%&NCCf8aR*Cb3^`_X2>!=hEZ4K6I@#^CC zQ5`91kj6?^>^<(l(W+j+=oCc=7KBv*iO%m=C4}-$rEdIz1KIvs;YbQQB?aOPms`hb9pip&ML)%$OUa0WB)NZOg@DtaW6*n-|`|h@2oG z#>yzI&2og9=|59Ku}fo|5rB#@i(F1m7kL3>isO607W~Vw`&kVO2(F40}M74Ba}T9 zWOUMwsAH2b=rNR4bIUyQRIsY4wW?wk?DkVgF;5fMLSwATuidFm;RqO|v<*}tT2EHV z3l^aQtL7hlMnf?)QfJtZ#|+dkTt{h@O;=i#(ML?XER$wkm6%}F5kVrfMJ`6Ob=pcz z<-Mm%G$76FHim(QkPuPkW{_dn;nn9 z-uBA-D6{uJVlfy^Lq#j+C162WUb)dZG6^#7HJj<1+6#sjg{MbYkAjb$ug1iHOrA|C z+m7}gCEoLcf=kq?hEnYPn!Qy3u7+`agNjP!96t{^Z# z7Ixwhc}eo80A7GW(<^xYNlCXQd&t#WVlP?IZ+(oCSMG4WbXPYBa9tsE3>RZoS5(qZ zl>TBQUNVD=*D#_#<9nKky)*|MxCcSIh_>4qRks;X-*G3q=yt)L2gYk!I%mi&=mM_Fi$8CO8gA>Klqgl`x|S`_Jy zcUrERSgP-jTW28_(0i>48jEI=Ish*ub!NQtVUB)*Qva#+Q95fJ9hkV4C1E0c6{?+b z)v{bdkW(__abCKf`B*Vi1N{fIXx?p)H$*V=SxCj)JsAfYQ+)42M{Hc$qgt5qNc`>GkT>^>@jAO!bN}@ zZxxm~>U|^)0tr>aR3L4(y*qGibMoHTEXeUDEkM|O5#8>+gdZD&Q4~ToQEEX=i4_V% z#s&Z)TE0wS>SSf^{$Da?fr=ud4gsD#RWgdgvV2j}*b_rTERL{TI=w0$QrlyyWFu3eUvs>a(B-Q1(rBtXM}c|TJKMmr!(wmA ze>r?IzZ&YI@%E;8j%8@P))qnyGW>Ql3+DD}+Kl zOW+BmCaGhem(xOZsw?+XqQJz9l84gIW>njnGD$T_E(;#4hXQBAq&qyh5LGEGp5UYT zxFSEaoTLrLPVv~xZf0N_K-nZa_iWAcn_h7mM-rGTpf-aRXR#tUGMqhSBCDyig07t( zw>VA~r{b5BVk+Jg!p~7xIQFKS7IdZ zQlCgZ56C$;uNzAX6=Syxnq{p8-?(!ubMf?O9gbW!Kp?G+kHFQC)C_D~{mnP1tI!oV zci6RYDlVHrld|l6Oj$z$B7b0ek7xr5Zw%Y~Xw}q@<4iN3?xHgW>uBHyaX1vm0@o4_ z#Zy)>i?9Rn%&;vWAAuP+jQ81k6d=0XWx>5^Xz~h7-x3FCf@%x&D9;Ky6QB5HuYJ1| zFAGvvx27>m39!00tgEy#&;ZX_NB9Vg{QF`p)#u1o? z_wot=q9c0xv6fteC)hhifM92c@IEcvYyN_i95PAVW=eqAc=Z!)zFy=q=QoE)F=DZ% zH*H?St|sY>X8mk?VjRn-{-_RSkb)KoJJhAWRjv^h$?TeCZU^hAlw)?_&2mY_)Ip!d z&f}}O=%!TDh~jF&>demXu`a^u{&K4*GJ;s*E3?4Erur-D+cg*1=Z~3NL-2l4_zK5B zEfn5O@=?JmD0TXXDGg<6M;+=A4(i8uYqXlHJMz)vGTIZT4AZ88*`>YZEs&|NXn1h%N*V`LP{#bKa*(NEJ^V%bTC6$-iDceL$>VXnVKeQ|-N=-nz5r5!FZ#|HfVqjCbQhUNe~ zQB2I4WaBx!Fb6W28KBSHh0>n99=a2XR@Uh?*Vk8hU8!k z*C2M+Inh$zO0SBNypY+0E>{@ptXBO9eUK_+bF+=PS*as90#Z||@@VO)c*i=TYcxJ* z0BoA;(_^H;Cp~AqaH)*eBr&SN2W&Q*Fo#b!GR)N-zFhD%x20Q`A*1yI;Uz{z@|7rA zVi+s3lD!>(@2jc1#Rst6_DUzN+6_)r6Jp|IxP72S98POGAZ(_EKQ(Ff*SCfoetpOa ze~QxTcRe8NqC&e~Zw+ZaB{wPTVFMBe2{ian6-%V+9=PUYxvv!{msi$Vum9^M@ndu9@can3u1@w$o{7A ziJUhHrSOeHp$!bpYDJb4HEL_+*VrMKJ1SKxPgO$(b*7zSlL-Xrf@!KFp)JX38``7s zvV-ZegP~bod^M}mw=0GASexLz4kX#3PCGePL$n*nrrEJ?Y%_UbNi0o$fe-3>(Q!P0 zX1ve8ErSC}WN!k#$jJYfeT;~kld*%1zSY0fN&lyP43@R`+)Gmv$}9Xo{bRTr)XQ*% zk0@lCS^nW4}qCqLeDFBjf=JHDiB= zloE0?`pr<(>SBw2Dx06>&crNdl_Ob8InKK0TBEaN^pW>0YbS6}>UF;Ym}_tbZon0+qP}n zwma_Fwr$(CZJQn2H_w?lGjpEz-ZSt0n0&4Muf40*s^2pH7TX2C*97Gh4r+?AqWr=L zAY~p(*urBuOV;V_%#vt+P(hR3^hF=z7Ryul=IU)O_6nBFnwgX~uF$RW%u5 zIc=V0#XlKjTZlOM{61u)GPWF^@UJCB^X01)RpHt<8Mp4-I4Ddv_S$v|m_&HNYMeCA zNGSyveTly5G3o*EMcnG8UB1dJ+X(KySkU_^=mOy(h@b@&-wI@nN~9T3Kz3HJ9t8kn zfH)mZ?+=0>ai4IZu(9wYA8SQ)ks#dya|a&Re`D7XSc*{Ses4cOLH_+&|9@5TKh?+o zKI?}8&9C`EfL9Wt_*eikl#j>ul%vU)fHoAMTG$?uR;9g$xbQ^cjiH-DM$&Z+^qd=U zyx>Ov?M?f5`aS)!>F9nX{kDJHd<6jM%HX44uqU=7gXP#V{;eCBjMf;bJ5aeLc~Cv6 zJ0%68qZ*Qd--D1QWoy8vtUGrW(MYdbyeT|Q1AdG+PJ5o7CbIh8WA$$lxsQKiV>I=6 zyx6_f#ygNhk~V^nLmG|j_E<{fZC`7$O3O#`93;n35;Rq8gjRqF`X)YD)FP5AoreYx8#z!Z2~_6V%42P?9LZ z8uXK``NB{O7O_O#iPx{aPN5FIG7Z_g)jJQn>aEsibh&9St}bvplT7gFJk+6l>F47` z@XQq=C9!{!JcSmxotqc;6QX)HdF+8_# zp$^tucih+AOHW&XC7pv0ddrexdPYPaUW8?e)ammPdSn!$$ve(_Whc1eb?Jcsi9#CM0q{Q#p)k7r zEmkd1)Yg2ixWf!Gg@@@k3j2&tWEhsAazI2KCtfL-m9Hz$s9o|ySo=y~5XCtkin*jD zU7Ji=zFxZ|7k1HKvq1h;d23Dg0Y{%d12~>2ue0q8)dQBoc5{o|NQ=OcmhVgaG0T14&zc?>w z-T=G_HyJi=;!+LLulJ|y-HtNuYoD(l`=mZJO%inW;{(t(=`EqgNt{&7N$ua-um=?k z&=X7rxD+v|_l6ouNq5LXEn#O5?%-VLG~%(GuxYK*)MKzv*$fO0(#HVY$3bb21mjQM zlV%+#WcOJ)Nc3U`_Rt`vk`*rW22hT=qRloJ?F*KM^#vQroH}fGe&n~^8yQT7nT8pG zb-zG$DB~4W4p*~X1T3g>DofAysH#AiBX(F+rMn{nfHjk8cX25ZnEO6u}TZaDxy`xjr~s!UM#| z`#t6302Vt}knoEXwkaD-nZ`x;?=?C==2-#K;gp6c^R- zE?c!wQy{n5JhDA*Ff`waN@y<>GLyrDu9!R_)E6#mVXq~oR#2>;V{(o*(bCKx0}{^4 z)$>#<^S&cH{|3I_`-nI(jHOdjjL(1TZ1zFsd4FY(Zhv>T|Eq`QpTLqhY1#FEB#tJ> z@37!A6;e|l5+ajEH8k9d1S$z*S)s_YDT!sEn*PWitm>fAeUkWK#G+{QVf$`w@*%76 zFLI?}h1k)iSvM11?Z2K_n*ow$OMCk6D8^DM_AqqN@8(iehosb9t@YD#6L13B8VBf} zh-DsIp_RT}G*z++UFid)8YG?tLvkmBUZZ;0qxX21 ziurl<`^_Izw>)<|Mq3Pm*3^(n75!$vz2NJvmxiECtd-;%>4VsR-kGhc^>F?SA7V3` zXAy~9d>zsAkSB`ILe!9)ocU-biNI0!M$QhFfav;YY_9F9^&s<4&^J+n3JS%GB}h~{ zNY%Xv?YST26oFs+;!7z`=QVOjI+X5vTE_`}Z{6OG^8m`?siZC~m;p`z%1xk+0s+CE zW@QpLm_-1p&|w{mQ7vEw{Ru5Qk}d@6hIHEUyO(2=^1!>FiwVF&&@3#>;l9fG)2G$# z67`w*mX|h2j~ggG0HemBM-rLA=>4}Cy&xtAgEZsM0{%*GdD`ZYO~h^y^N}Mu{wBWV zhEUc6<1^~6>m(iK=Hrw;cK~;0 zvXvCd2*EDxitr$3jQw%*xV#n#aOf588;H=4t#ZZo;gH4|`6jLA1eQaZ ztXn}hTa3soguD^P@QuxF_&*)7?#S#9=tp{M{}Pj6!HlEASd=>}^;2~gtHsIiKF3y8DELn-VG05$RrSRBBe zgAl*%{BKCu3Y*j*L)7n(if_;Czk_-F^R4k8UfDwqH&3O-#bcu+*1IEP1`Gxy9X^3F z0S0))5F+QAMXQs6n1WO>@P@(B1BATzRJK%~65(1kOV*3B+;S&z%9*@AB1k{$%BH2u z#>Vo-x31GCW%87(43BLo5-9XH)!*IGx<_4am)yr2_1lhFJTJHXqyRafU!sAF*BwZg zL1;>tCyTIO<$c{d!XP?z8-U4&IN)GOJCm;02td#nPuw7B(aY7QD3Z!FdcU)O z6>3tm&rv!uvIPcJuc-?3!XcM;5@qJJ%Ar{G()})ELJYx>y!<8VWkC8V zh-xJV%k2oSoXcxqst`ak&vD`fG;>eArR$3Z7?vLKpewr32^$;)NH7-N zRLyd!9qsydlsHZhc8bGJ2+*$@d>0B=?yD<-8G)OYSgQ!(px{V-VA;E(s-vl<*QLr_ zx%nkKRZ_p^hqRF>F=Z;mt7^#NIG~$PXYiNE&}TaeL|N;I0TF0|`0(JG4(8ZD)eL#H zzPbAO7Otjc7I;|zca%@=Au+W7>fsdI) z?DFE6>zGJ{9g+C%))lta)}`BOvI5K^2DPY&U4*G21R1|wKGADf5j&Go>U0_4am6}u zdKGkP5qj5BP6$8P@<_-5$!yKdOU>}HgA@bm@EI0RP9rfA@Q^!v6(ES=L2Q+-NkFy* za-(t*g3L1I3tY8DDlM?lcy!8e-e)VU|4Jm!;(ysD5xoX7xWF;Nh81!SyAWt z>8cZK+}Mea7=8a!oa15fTI@800XLAH7!~A~qDmfHQHih}+`uoQD(0o=f&5Zmp$rrZ z17QUK#Q}woISDUa-Qrs5ldOeCyfh{Pa(^w70aBZAq1D0gSJ0qf#D2!)MQ=jk6LCXV zwVin14O?{jxb`|e@FoXyu)_Hej97gQbv`u$*EHx;4L?T9RscoDbS%U`MOy37SG5Rk zitJ^?8zlxrY5EBNt>s~Yj|pPwYB~c!5eF}VRS6Mvd}9VMe147MigB&Q4(@25k<(J=|Ro=>&zWM+qt`(H`?HoI)P}G&o-WLePeFK zy`dWcDUYZ{n0z3%x-B@ya zr?2s0x}c-+J50jd$xsCRt^zrQk7Hbp`|#ue`>KEuFthi((dG*5KtF}<^J`PaC@;@71an0M#z{<`s) z@9;s&!nh-4e&^w-+<{|y$46{EWE_1ULE;=OEXO=xbj&w7^k$w(dh7Wa$)s?ZfzLxg0y@|!4RPH9M zC`W)RLxQLjuLdbTj|%Px)wxgRu41Z1LA6*&7<{_=)+$YRa-~7WVqz1q$UBcxp9F)( zOH<<@DO_e+D^XxjUtWLKf}35l%p_6``X)~|&G?A>sKz)*_!eqz=D1<#mKc@d=V~ao?+Jr4Z90HTxBAT-f`2Hs zND=hGl!@0|&acM&az{(HhSpZEPQDd_UzvRjo~(DH=^z4bNW6diVSZz@b~0+n9u`u-%+60-<1iA40r%8AXMqdbcr1xLxFXqAuzbvg9hY$7I7 zv+lAXLL}S-ISN;;Ri`s5Gus*#zhs)NNqcH%>Lgmm8?BviIKq%2HWcx+{Eln7@jZDI zQxKS!39Z-@%;U^H4OEsoQ?4xtQcs-p7u1?xKOOm8a9J&8nk&ul92d-`j@&!RyUs|; zV~L#jj=Ud}n0zEk2?-iq2HEl zOCzNaWvJP-s;$E#+Q~%fY$L9TG%FU-u-)uiB|M9anmFy&pV5eU!s0%88&Vf~Uux1- zL39MIFYVx%l>-aSdWEML))AJj9;Z%{=C)bAPB3vdW-T}MgvXGxx}xZM9f;9YmY?5Q zPf67+MvWpl8L8?V)aH&^ZD!L^*BWZaxwf9R-bNHc!-vT!h^>7AL+v!*^sAZq*BG~q z6^seI0t-StgH4|h+9bV+R`y=Un#klcY`+$sYM#})$)Aau6XaSd-Y?|a;G&?{AK9C%qkUTKp*}2VJ z&upEFwIn>f3b_Exf@~+&eoCkw@VtqMN)I1?DOO9%8b; z^swmk*dg6~nba*E zroQbL=+!>JhsmIX9Q>Vw`nW|WJEIVt^c7*^mh&>b8)iYNQXwp8>sZKaR zzoWa&u)^kRYBW1sC($-DK|3`e|9AqXsf`mKt1J*GJ`WaMm$Z}abmYW~Q=f|1mO29_ z&1Ds}cN!O6>N?WIwIv1Jh1#WxKLgGc>Xr1E%Jv63<8%)T?8)}jNl$TH0aNKR{5UK? zp9@s|Rg~K&I|j4R<|T9TwfLPuz#cZf#ch*KzxBrXJpK}Zm!(ieFE=>*C)sX zEBHy2;NNf2n5XO;jKU^DbIt>k2#NJewhuQK?^ie~imOmZhTIO3#t$9f3*s7ThvclF zI~3OhyQb;LyAnX%0_XXcZX?i92f2|CawiE1anml2L0iJ=tJ>$pD6pKc=jtXbCc>lf z>lC3nURW13XuVC*&t*(C{;8+7!TcSy2?=d|bq}Af^f4RX{u_vzcyG^~WC>7GI7CZFx3pdI9xhZaO7Cq!+4io11^9Yo&} zH5Eo2MHf2;J5VPu%{OPombU)7vzEyDfXfD@n{;c=(u;@T9R-JS`xdDXcqbHopzO*Q z=I|7n^#P?#0>9J-Qa+u$Q6K9u{HxIxt9s<(E>o-}FsKKC?xP#2oKur{XbgJcSHMm&k%(l1qYC7EkX5^zF}oWG4)@$-^jx(z17w`J%_L<3 z9m&e=)6BeRQ1T0yn-E_O0e=;di);~DxN*7;Hs^um9d zz>SAp#EXMY7Y?4iT%nh0D2;Pb>>Y-IosRSlM8Xbss_0$+P&s7yr@#veDT=_ZaHAzo zX&Ek~)PtS0UDC2#RGlZ4L4YqOJxh?eqMnAk_No#H6ia3a?be6%iQ+iwL5G=Q#xOP7 zC&_CEyJ5fq9d)u^ZFR`X(;%d>Ohc7V_W+KiKiirKpPOGR0A?025#1d?jK!1UPyb+@ z!9C8}Fb>xmQq*G?ga}X40lY9KBPl`4b&5NKT?|0w5qe8-We-gBC2psONcJH`lj3_+ z2f2sv-QfZH0n#Plm{y4p`X-;ItavoUIKy`i$t9EAuRt(g+Ji(ZFiKu?sGJFkcB7I$ zJT0yUHF(S+f`b>zsPA4=<8@M_CHkA{t$e6idKZcCQM|;pkx9nH^_l471B@$U;=1SA zicCeSN=JtO*Os67^^sb@WpQ=i@&v^-d?w!$Jdb2m+mFu849s;EC63N?vgS%DLD|X& zbgMJnVl6mR6!lko%hgf|S@(~xdYvC3rWItcg-MJj?VvW7X#xPlY z(O)Mn;j+1s1TeO9)?AYP>Bl`q&pP}o%mLPh@0F=1(*28$KB6MKo`BItJs$J$nLrh` z$e+S`UiZURnR-Q!;t0?XpAEpc{>5Z6UXY^ugU+y$m1l}xW8Ps-?XucPPN!8UT5jgv zVE(1ZyLl!%?qvjuED`)rKr@E}_iydgjW1oE*=@LG>}V~{%|cVz_Gm4G+CA#twmgF$ z%}y5wI*BA66K{$S<$F!L?Y8YUcU0F%rrrwse3gr`>bHpY2TrrZKv|fK^gA!E>`gr^ z^}w8f2dK%u+P6pe)B8rQxNa$W>0->ogMhe%RRWYleC=BKJI^kA-y8yc?8nM`*pdfOOqbboH=eUNtkVZ+&&M;wI+`UoC^9M2X-!F25oT zY(oM#rUm>*sAmfJ53uL=zXW@lAAwDJDH=YO@@tZtY|kOsjtB;aJ1@vKyt~N{qw5te z89biK#UWUEWHfR`F}HB|k}kpjBuTbU7H%?a6DB{VNP3ucsnT39CqK@;yR|aSTC_>vUmb2oUf%S%Ifq|XPnq@Yqd07E`>`Q%d=(r`phF2 za_PZ%NplKc5RKFAb1Zd$8`%zeDJ7rd=q1uAiC>5#%yu`S( z&lQthdD#N>X{;i`%1yoDp~hhURqpub;}y%Jd-O6>^m5cAZ~!+jXLy!o>;=w<|2I_k zxF1E{1GyH*WY?nl%(iJM4NIvz(W1K0R}4&y1HoCYDGsS?7-&<*rm5-RdSRJqb192C zXCR5<5Ryi+by;NPFIa*K_j<)f@i%r5^3Btmn$?n!oSj(WfL0+dc95`EqvNJP)lKi_ zhO)HT!0kyS^{eN`i85&LZca-N(siA)p``M3ELk$trvVH#%cYlw&*6xrlR9}{)skl| zoQ6c19T6k4m01+jrCmq5Y}nBitMATR5l^Dn2MJqP0}15;OE6JHUO^D(4547W zL1@VnsS)hS-91q}3aZXD{IY=Mh<(v1nqUcnF^C-D8?+TY+Il$$C6ltv@Bo-rAIFgW z%%x7P(xzEXl$x(*pbBIz`pYuXNr{R{0ZT->L*m4oRJ4K`V4kL82I0 z5NbrAapi?Y3ygN%_gC@|W9@+eJZ8%1XvS5x<7a&JRkSR?x|%eBZ!I!%L~+buzy`@7 zOQD`dFO^_lcb$;8f3E9+dom`g)wzhB^v26t;PDp3r8(HsUvW7+3ozRcZJb1FjacK` zhHA9;l^oS5vKR)+-?=;z!EdTn;>P=dckZ<~Syn-_sDiIO^j4~BQ^tgB3UtK=I8YRz zF-oz0d11s<>v!WY*F;YAb)%X*J*-^FR8xLT7I{RR26O&eNe?GFx~nTHU+wDV8l9 zw621Z`Ho6g{gE^}VlIcApV)^tf-W5Yec650tME;AQF0SKz_`^%@3VtT6V9N+({}$s zDca~DCW8|0%l+r=l`Qlv?3%5{5u#kU7+D8wJ z+xY@#4uHV*2;xTx8WRx}XpD`9etdjh7#<&2U-1H15m@aYRx!`^7$B+IZw3$}*`(Ob z2N zo)tr%Zw-|BkNh6oxxdTRvy%0>8=i<5mituAl=z!y*ZdQI zvW{)z_w4rODz_&!;$#g=E>gdzLWmmFoQTO#J{!G@^0ZQTn4*gH>mjFn#=1Qt9r`l@ z_CWovoh()&msgty0a7i2UYqU|rg)*DIkK}tLfn!|R!8k+6xs`d&AgZS%%c896FH%= zvpZW6AESJe4`kWBjkCY4-sTnvg{~fECfJ&x83c2mPwJ&=O%5ig44c5i{xLn9WiREo zLaE<_fE7OR0avPSv_&v(Cuww9tu045+-6Lx=@P4AyHZO3W)A!-LU{zr?j%_)hry?5 zx&+<$?QdaYQh<~UmES7m{|0;e+hg)SxGw(+do%kB_Lle`u(z3ifxX3Qi4#d@-_HGx zNj56_^5J1Z!c%1M`Xl&Tv3R~S=})QvBYxWK=Hgbp>94D*>aW-1Ctd*0pey8zUJ#6{ z-8_NuWW+87JcA%j6Jjj|JYn2q6s=z6F0FVvQm&!KfPiWgBYS?|uso@8t}m^a+7R28 z3(YCB>qx}qDoHg3x`7)(x@Q5YaYP%slAv=A?^c|ok!Cx$4TTS1vH-CKR z(o>dx%D-T5>HJjgFu@g$ks^buCu$H(a!=TtP9ZZpf5F}uSIj`IY1yoJz6eL3avQsXJ2ICot`D2SJT&{zDkYTH*7%JJQhhe{AZ!US5Ht=izfW7U# z7$tSQehk_O8*GuGQ%o5M41zt&3I9NX4(umwxb%Pn8eT^8j*y{vi>Ij1(=mXVq!kp6<>hz~t~k$oPc$w94!sJ-4tPLH59%MKafvQAvIE<1ZcP z^NN2mi37orwd_~qS*62j#ex{)QR#sp;*1^jdyRGTf1utf7$Q1QH3~*)mnnXg51sk7 ziYXsTTKAM4d)7{GX_|=>Lg&1O6A(o44TojMhNQ{J@C4SK0$0#El0m zkufPKs(Zk4>?J_81sFUv^@BuhK!JIvpE)szBkrhnfth4o!~O5(3Z=0IqFB6B?U7CB z2ArEiwt4Ze^w)Sq{8}(!!;EW?gAtDQCDXKnfgeF7HM5((mkH^mhF4J*RF;W8_&{y+ovMl67 zVX@DD1A%c9eH{K3z!&%JPWg9dULxjJ#&YI{mc|bM^iW3rAA~pc0DE={Kj1*%!9zU1 zVta+5C3}$~QK2iID6n1-u4e$A1U2pecJMqHZEfub<>k3--78!GmD(6#P=9uG^s!vr zevJq-dJDaYo&Y`)uH>=?BDu92|0dxm%4!t#GnNJ!pf449J!{G2$3jQ(Ud+4mx%k~6 zzQXrxpLmf&F+AlbO(H!ldDqKkHuW~oxe6x3Z~t8-_(3z*@I&JLT6^qhPeCZTRQid) zk`OE-i3z=_0gf;dF>>o;Hzm0}Z2hhq@`AP|xdLD~Z+-!q!p>@y^WnDSa%1KG$LrSR z)+q>kZqNI+I5232&z~5!id6{Ki7y;QB)!4`yR!ZUq8Lx(-c=)f1Wnk=a*iV(>~haCJS z!d6pev59f$gA@ZHA@|&a#4@RNw-eADj07vuW{ndeB*pvKrtbxWm95?z;5zP#Zx1YJ zlKY~JCi}b#jHP84dN@HPfxjq4+Dt{>U%a0iwLw5sWvW^#!ty-`6{QM?J@O?bE$-Q`WN@+TSQ4|t48ijDpoB}6qo9C| zuOy|gELcJzz6qYIB)$oqTSlA+t{I6LN-P+*C{e~35GF~1B9Ixcqzdk=qtGl+!Xds1 zn|tO}28?MQpd-PZLX4J=05b^f{Wn)0M@vC~B~_G9 z+oMjm`VJyNJieZ9XC9rKQ7&>R2{HumiIBjY6ePCXZmb&wP<-m?1hCj*SY_j-ro7}L zswT5Vslu8RLGD6ZfyQ~))l#F4bE&0t>G8&s$%w@MQ|eW^N0P_o>QyUkbS9@4Y7a`@ zMDPXM(O_dszv%cIQ^z(K2(I2#Cg$ZWe|_jak0<=EJBR2XxYqBRi`XEzH#t8;o|EU0 zM*Y-!7=x@-5*~WhyBfPSb<#lk3x6shv@`8pv)h35k z_w{vfc8wGI5pX=k`}cX69E2jD8M0p?4RsfjW{IDHkXXrinq?fh7~Z%c^t_!aV3o^o%$k{pGa&&2KP-qoG*T^Ypg$BHn-J9 z0(M=hZCFG*W7o_TkXbw1N<9+S0TK}9+>_*^=IYm_FwCnEcr>Pl45cm-V?K z`Z(gR=dmGiKkHw0qM8+SU({iA^l_2QS7}~Fo3l9WO$95-~J)AWOY0&R^5pT0d@;4y+{L zi5Lz6T!Pk{pIT;9`a5gP)U232Td+NAp-pLyI%$F%FHlA3M#DL1vOzSRzaraeRU zLt@xDWa6@i0-Fx3v? zdo|g;X^y>4exE+GX3fq{ zHhuN0p*^0V_t$geO>ho(9G4&kzzcIot)bAQNviS0`wHBxsQ`<)_8xFH=hC7^YnW@3 z2`tt|Q$es=W=b2dK3q2DHDSW;VrZN7-QJ&@nZCxNsMMRJeGeF$zBpZ;r3I%b{gPPlg96ALJj%o9U|Dc~Vc4Q* z#iYWfoZ8d_EosZ`WQZNjTimWJ6oS!LNtc6J6G_;}xFI6(qM;>o++Pt_GKp;JKsU>U zu{|1hkW1TDzl7z^f8Mj^+P<6G#yUQmTq14@`PCg<=8fT;``DQAkNiV}F8?4m61PM8 zZDF1-oi@0l+7ugsaF!Lr?+?jR!DjVf$aw)0o`&+kZJkAl;&}5%Ql<|uVY#JX+~4cD zH~WRW@r@@4H)*F0tJ{fE592&pp3^@C=WC37-;u36(&mz;=4OrS!)g%F=0)#ZUIU)y zqqj6)*t_R_?=2+7qGJQ@8N|i*b^2()=Hl`6n}Ixtv_^n3b_iZ)MFApjfZ`|!pvmZi z@SvCR!XmJ=o;bh6W*E;#sDmWO5;0@J<*5HN2Br5a?lQTBwQo2thW|6`f4xRk-5&=Dtk6I~mZm8Z!()i0 ze21ls^XBu#Gpq)xF1kna+C43OT|{v;Yj{m_gVPCgS+|EoNEQm6+rVEbR}Sc_4@|M3 zm=Z6zO0PsMKlMyVvrhf{jeMfh82zpU>yT56lwzUorC*Ise1=nbX6x>#)u;|W7&lCZ zDTtE!9wd}Yjm)M~aYNX}K3~TE%11;3?daT!5`WYI@ZS6Yt1IAkpFmA(kiKlsXpxRW zCMx*}KF5|+v?~B_94$0nW=SVhHFADXJ>6CbCXV%RfhruvP#l&PU8s<9+p2mfvZ;ek zY)vwwcyxI1%w8#`8?CZjVak=TYHYn}8tA2Nz}fFt;{NqiD>{(fG=v_uAO#%91Y1pG^L{hyH5Bv6P_t=s_#&Ask_eoHa!P9>{470E3M~ zhQmsi?6^w!{gS!5N*(+Wv&-A{Y8AW8m{G1n=pNSQ=BMZ?7A@x7$xlZDy|%?%l)#EF zE6rg`DEZgbIW3xCJHPsDuzSzMA}zsbH%x~AR5uFh7zR^`hdoQ=vjn3_L-QFhV_}CL z?6Y0nd$wc`0OCO_pkqDa3eIUT%{bf0=^__5j?YyGlRXaGOF*d zkPB>UI0d-(upuQiP@rc>`1xEl@;;KasuH#N1{i7nr${0ew{zgE_VJKgRu6}Es0=@`N+1BN=<1+%MvD;mF(@oM$g_0Ef(5w%n`7C znc4&q8XSyOO+Q?RXDW6((`0^c#U&nmT?DeWXo*0aMP1Wz;P!C5rJ}CzTVe!Ov7ahf zUH>grJ=8i*JL!8qulgSA{vC7qzrR!ewwF(g)t3Cp|2^4_F0#s|!}3DU2+dI3kL&wi}y-ue^O`=zy~X>fD3fi-wve)u4gk!%j@3vQMWvPH}!+ ze!hcTE3?w%Cg~9?85h|;2kTzmk7eIocCPv7W} zuJ{Ew*xz#}NbN69+f`R%zE7;P>gv}K38=Ha($^G;{b#L2Kbcd#B?q&(j+39S?4n&G z9(Co7R}B=QwC5qy{8{_8>)eyw{K?~E7sM@=&u~`@usn-^l_2Ueg>|^f6tU=B1Stt` z$>imZRbe@Fo2_kABp8GjJyOOT>&~-5W|UMF=_zY}8$_to2w9)9zut$~&eBRbMj-8} z(BvFV$nlC*ft5~hhdk+64n=*)$KVb%FVRHb#x{Ga1{V*K`BnJh!R|DDP5P8q`l z0yl&W4xub1(P(an25zpZer9cw>c5v`zCn1ypoPaD+p_b+m2ALX2?GMcT?VUyaF@L{jLedP$L>`y@*+Ff;29{}v(eQ!*9xtZ*WS$~4+z`(0f}g;3obteLLqVQZwp5McUfpe6>D znh>{8o9W0aUa8MrgOU%ZoS5$QB7<6NvjH0+qAxXk8Ke~Q9?s!*CYj%&d8$i&2t_`t;Vpgtpz?yQgi#-Zqq3UHyu?FeJm@ckKU z@1r@6xh4MUVc#jsaKsOmJCg5BG=zPTE>&`Gp!{=9R#WEUUUZyZ-Co=-`upo`I2*vG zM+Lq=Cc?(Ea3&^>uA(Ozp$a4Mx)E?x+cTd}Fb&QSacB^&^DZ zav$G8i**#^&eJe#x`=p!>>0cs(Fp{>o5qg|cgSc$U#G7bGEWQp@QjT0KGRR8>hvH@ zrs_qf&ksPEQQD+=&!rsEu z{Ksza`GmK(ofscplFTr3o5d~D@YsZ%7>3MZg+-Afvs4r$BE{r~IeIf>dHzGlJ3Vz( zsaT%upt6>tJ(3roJM8vMo;9~)N+uR{qI#2(nsL*xU28V3cL9NK))U7sZDCq>B6JGW z)|>TbExzSd2Ev`OQoi}wAZ27ZrQVtX1B__m1f_~=JSa6~du&#vgqJO99LHOwF$h%| z#&vS_qA=!^VOIyHgTPN7h@>#cEG(ow`)820@ThrTz&+D0yC2e&LyCmzEE9(Xnrncs z2BPk}LVmq&k!WsLZ%lB?1**qPnueB^ONwa^IF0UBub}hH;nHvJq2y>%Jmh%({v$uw1UC7^I%3w4YEIM(^z;0Ih?zQ) z!E2!(enz$)+>;5YFH{>G!v1I10PMi_?q|s*_lEFy_{&hhCnnDqA#A~D5m<*CA6t+d zj4N#|Tz3|Dm%I~w%FRZf2KV<;HfNW0dIb&r8oqmmS2^Y& zyyV>h$f5oH7R7J!nB@s!>DMe9ycv{GdfMa60_sX17^m45U&H2C5kH!CGiIHp*^4<` zrBnK`>3}qByB#NraHwX zvvgB_{%w^;If#=R`u+CG`u;@ucR5l2-H>$Y>Hfb|1RVNsIvP>on4FlL7A&D_y#0cO z|92HZ0JE*V?Yz9|wY>W`Pd7k?0Z%yet0Ns*5@=lVDS`G$Na)P-fYg!LrBYLdB0|+CQppUWP#Tn_V&_ww zr}J};)+AyLuzv#;{)d#X&nxi{DPiqfN?1ktmJ&QQ-GE>JAtlsRZF4qbXHu!pb?TkU z%eKK?lDUR_tj(GwoNpXVn%u=q5oSdX;kO!58cbq*O7kH@wjo&+x*p9yBn>XXBZ}Q_ zed8GTl~8$OTzzo>wNGsL4aFEi4=AhQ7Ymf7)vv^OxSM-Cp<(HYdMiM;W5Z(mev7pQ z63rkjE`{?%h@jLJjr={?{&c>Q7V7(Jr09Ri{`%LN{+F1rt}rk6Z3UTG;)9U`0hQx& zBtwQVm6PY^vj$;Tf)}2hu`y#JkwOH$8T)D4@xaO@3#UEa(=A$j<>7ULZou`6T>f7iMO|0+O3&rhohnU5h%I zRYOZtmTP>3VMy&11b-$m9oXykGpI0e>8TG2!bH~;?b4HG>1p>XCYe%!j)O}2#u9M$ z){WK;D)hDaP5BS&eX~)m`{#5ys-?#n?iT|~U)W(t=Cy-48eH5tG?tnIIgK&0NVzz! z`WsWXU1=b@{GtROto6}KSr>C&Z=90)*Ru6NO4s|cQPYQ_4v)Q( zW#W1z@|8ZTr3AsJJ8lCnQlq42-@Egi2QFDh*Mx7v0AGN7C79>en<6^R=L<4@gnuxr38LIbwv=y*(5tres~vI0=;Tulnrp?J;_n`%0M&$KQsVP<%=k2BPb~- zoPwW~6toeNNh@$8C{-3P$jj!-7<3{Go)J6K`$>qODF8c5eY4M7@`i4!4B z{vXobDZKJ-%O0+zVmlRAZ0*<;+qP|^V%xTD+qUgg#jM!he*5(4?(<%p=imKZ>}TKq z=2~-(Ip$bnQf!A7@2vTap6dyP9Oeq52fLxavqkGgYsITHDgplP9}z1_iEjzal!3rZ zN&Rm;HUH&S{R6f|#ZLS~US)1$4RppFdL0f*!5&oGf(x(%T8!=b9$$vc^W_PS>T=V#%avV z)Mjf(^_&Vq$+Ko96|(`QBm8Jadq|w@*V^mKx7hD-nCp)>hDgELUTdfTjsVR|Q)>vXCoZHj zfk#lY--8&Zet9IF<+W03L+Soqdd=fKC#G{EC*)`F94iVt&Z-Kys>!&jaha5b&_;0z zS`$`pQ*Gs^t?B2j?)E0-{fv}=+dxo$ax`d&JRu5HDzF4O(nTm|_03|L+^KB}UUD_m z9C`2U%#5_Z`JhU}XM`sLW0nt$8QuS5%>H`~T2!3oU)}e}XTnCh5lfkhB2JTz!kDnS zqApChoWFqo{_i{}-qCiBu!1w2HFfBVpL{X|U>O1gZ(rW!hL|K*A^AspCo|J-xg4iI zCwo49a=us{7-C>`PWAO#AuOaWNQ%>6!A;^&z#8>imQiVfE(!7-YyN8elR+d2FK|Y6 z4H*u4_M_d2C`%KDE+Zw!mEArOfrSob z)eVcXD;9LdrHbn_#7TRDlg}KF08e)-78Ry4CPhqd7TOmO3+FZ<%yYOMScA|j?8 z;OrsW_qbYvmnBm*<@GUtku6WZ|7?~SC)rG-lC?(wE>u_gvri@56v4d~Xm}#_ee7rV z_7lAP&QrkP*E^)t!9^%4;;C*H`el7&U1Yc!@A`gAWKputESY6 z=RaoWUgj3|`u1mk&CWTeSH$?Wr;*#q&CJqaZO%9@Rnm8@OMshPeWw2S*+m;?qozrj zrXwsG@%9ikM{sY6s0LCxrTB`aBVhjyn{X)haPZMpbGMLTDfK3aJFI;%Rpa-Raa%CZ zR? z;473OvPLl%Yz$q5yofhm9~%K3C-u^vr@ecVQhmJ>2aylkg31vL0<)E&?Z#UQW!%z^)GA}ERBdih0Z%A zaV8Hee=I{WD@1+~yVsA_iWFf+WBR*OIhV!e>l830vI7C)zw^)kpJnWyE8wCOwJm{L z19=*rH_scV9t6)!o0jA0R^_~qRlwBIRH!JShIR$ViWf*(q?6Ok`pA8J`9ug#{n3H{ z5}6%!`6_nwzc$m_Ob@r)O#fWG9@Tug)Zh9#k{>{d2(M+99~6O*%%gVyD?VTXUrURy z^>jBq;0&%)7fs;dj}T?vw{uAKIAuhvNIk=pzVoD4e|^FvqB0?Rk7ar)Ld5vQR?kE+ zQhc5oBNaHTWd9PJjgYYnlF0;vZ_5+f(dv_oU`4pWL{klMD0!UK{eCntNwIqgG?-lK zFA}3&Rb8)^nuEG~t$rB`w(S#j5^qyI&sfTuB<^lX-BW+Kp<(Zz%)URIty3(~Mzd_1 zsO8Y151f96!MXZhoJ^l}pDSi)-+M4q85d==yW1SKNp`Nz2?zxU#;IsSTlZRnXYSQF|LX(E`OkV*XSYy?KsQKg>pS$(z8}l zutrfdN1DMMfE6RC+mGaYGGT1EpmCX?b$Cp*-1nBGU7;{TbNgd}kf#!+lBN=0L$4Q4 z>*w@xR9NA|@=w|0&b@(M4FnD_FzK`2`N(M&xbXc=X?zh~&Xo>47Q?`7 z%Jy$6rI@jmov{PIot>1vvyGvdv4fDgzLl-%KRd#t|4}kqSr~OHs_x?j==zmlyVbzN zgUBc}2qgMSoE_t>uv`Zw!;k-}m@rk~z>4Wjags)QE|`aim72!n=F(j}1_q?7 zBPjH`!60EtmFmn=NIPh_+)nhAbCD3gD*hy|b;LdKBhdvX7M$N4{G%Ys7TowIWFhoo z$ISUqE$AcmVz^soRyz~J2d0li0Cm1Z?(H>a?uk6wRDci7u8M|OfS(dqqw9pTb!S4} zfjfARZ_^15>a{oGBayn-70ueSK2G;2%Sh zfVIMAs>9lNrBNPI{I|cEq466kLjD1U+Z&h#{x9MFFX~#}c7XwCb!4+(asf(>wyM`V zOfky}&J^qhLMR;+CX~V23{7}JDcQwJ5k*X1;O|P(4TlKaCyxL0#gk&NtvvF_eqNkI z+mm-U*QfR8+xI_O*)Kf z+H0tA4|2c0t`)FKOcD3|&WLk9;9&qdAK?RNxMlm*OyuWx(%{zA9PLoyA(bb&?Cd7` ztT*jvg(mnlaWL^9UF~C*xeXWKE&6Zbg-UI3jnYQHaefwu#%q<5gg(o4$LzE3);zX% zaCMB>3Z*h#V^ISZ1xfw2q29Hjq!d!oh zdf{vru9VX~f+AOJ=ehY6y>jb0xZm_R#09De$Yi7F_X@nirfUAG9vY$W{)@@R0y5dy zm1cHu6W@*hXQ_p%p$Ar)h6u3=`o!`+DIu5`$Ad{I>wE))w>G(-d;rz4@?> zV)4F(-{Z)T-~NWb^yTJaae&xM4B^Wcmj8X$_(yk?rShtZvx5AQoYXjP;SMcc+m8^- z+$`r+$!Q))j5{KvrwI^&=_dnN-w-W-*ECDg1`&U8STDX()p!Ikf3evLSzxWEHM5)l zSj=ANDI>p(RMCsP_Cv*MjY+%mW^y&T?0Vfn+u^h};-lH=jUX-cfk*aU9eK6)dfubhp8<$IfZWtFJ|y^4+RG{5 z4-d_Ul1m~+UC%diuh&lk2^XT3{oh@Yix4d;-V$RCcp1%t7Vd&Cg4N2sXg0OEa+AgO& z1v(WucoapvqQ@9L7ywyW^vgzkr19*j%8rRU&T6l7g ziw?D{8I$mS{LvlETw1&F(vt$HhK|JoUS3fdFY;S*3CdsGTzM>!fF0ri5wC5QWB{{{8}y>G-Y(|eJ= zhFpdmhS(FyHX<{Z_zf|5hT%C>-}+$*^zRj6P~7CVwDsg_l6RaT`H|zZDyx)O#bK$eZCSvfi zdT9;Zp>7T1qZr+df%ypJqaO7S-%X1VqWs=|)PC#YBhOalFK=+xg4P+(W5@nMm3vX% zTqqo&VNRD`l^QgUdv#+Aif~DyLpP~#p)p5;M^-*LLQr&svdOK3$Hl)k27TzG+GqWy z-G{fSEC=j?o#}#oc6n?RP*~A62VR{PACZ&eg0Q?E{H}yvK|b0fjUFhbLIm`6@j%my z6*fu02K(V#rtt#<6vexR9_VVVQ_!=XUU&owz`gYQ)bi)zL^e7wKJ>~>>a1zFvLB zc+L;k;^rntpXA7xIGyxq>|KmJ**Tcgc@2MFK&|+`|9s&;wi;r1uI4RJwXi(cn}a;5 z>>>(ye?{o;{>q!QkICOmzq9A>T;=kUT%CRO_=m$Mexo{iLupDe(*dSL&|9a;`ka`< zml710FImj{A7LW41e}!tP36}b9zipQK)Vim7^}x=UhaU%(^~moi=))&U4yC+O9Bks zOA%o7pS2?OwG{*P@{a4N3jD#-7ux& zs0Lm2KaZPA58A`x?5pB7@t8U2m|`JXPJ10S1CMsAL2_cOdgCHmFUL6M(xy2Eou(;^ zS9UvLG0O_I2H$I`jvR^{<1@Zrc3fL|FtiK5X>BbG7_HpfIs~>;{R-_3blt5J&TcOi z`M%5Dguv|lR9Z69Ec3fYvF!qU+x2u9;8qvymch%Xy{LP2v#I7+GUI57PN#cY1>g8< zc^2EX9B7!|?A%y3yh-B0`mW5AzFFIyzF>efnP zYxUK>FLDaUlCzJy!~ z=q{;vGNQ3V{iq5}Xbd-X8&E3o>*X|1672?&7`w^z(1V{@4{a5^uQ6o6*c}VA)kkVl4zpj@?ei^PPR6i}Q(*$2f{u_!@NBQR$^OiYjTb<(N=pWC5#}%Vsd%$TJhuir+ zbas0x|C}&14jt>wZ)&?sPGR5}UeW+Egrz!Yi20Va2ImTlh=fws&*%6%=hK#Go0hpf*R`w1 z81vX`Pp4?{IOf4}v?tLo`3iL!bx8;6{WPtz%_?#H$&YI$XIw&#uaJP2T0uR8TU0YV z_hDa$TZH_idku~&{54xP)Y?q{iAeQYv%a6d6bk?Fk?`>Y++r7-eTBItsD5RmAbx=U z>-&(mDbM~3VDuw_kp2Hh^#2pNXC+M8Ec`$Vnjw*qLTQ1gm6OPOWA?>^ghL}F@h+s2 z+oMo6$Al`YC}Wa_yJW}iC?CfUOVRfO<2UI0dMy{qrNS&GW(A0^so~k#bo4TLJ9~`D z`NE2{Xa|9LR;phV4cl5cP>zt|+?qd-9#z3w&7^cIpV@TD$bDI2@hy}hY$Oe;K7pHE z?nInS=~{xsFpqWQfR`_MPOKD@QFeY0Bj%4%7H=N5tRdFX3|4kq1I8L+7aN=k8y*zI ztJtpsrytVFoA22xOtzO9=YC_XQV+5Edqt7)%n`rsI^2v8f&-izaTn+8c6W;dG+Qrv zgN|~m0%D>AxLPFnl`55jBe)kXO;kU`S5k9l?eJinE>50>>CC{IudqS}@Yt(e-BtwM55A^R6`D?>KeGT4AHu%#j}hO~1i}eoH|f ziiHD5MPYe#XCo$zfaFd-h;-*=80!ilDmL=;H%E>!8hq;f_NS9jL|cR_rtwycD`s-^ zct4H~|71T-9a)!&BHcPeW*nfk;)F$X=~1NMQvOQ^m!3m`>q-~_lChmfR_ zBFKb8B8D>C)FMzyi*{2*mPiDm=moX3WG(A{sf=HC?4drF%d2HEP$dle3v2y}S108h zaoWPxO(%TO+j7djlr?}FS~v+5DUp(JEH=ib11~6BFHgTvfzK%D50r4^fj{`&f2!ss z+Si&qi>4J=o{NczHr_OD_r8UyVK*+TwIF|$t}^5lOr2zJxJ5^kF)cP$J1)t)wZ=Pr z7v&EYPaxx<-sest>{+1~J<%R+3AZIWfi=)3=+|5kMUfhkrw0NKE}Tk)flhOCF=MFC zPaq~37Nfmal7}zBvA0IKU@vK`*$1Pt!*mh*&U8oMIIOj^2_HN$lIuQ3PhY0v$FLbrP+D402S07mY2y*27~?edK2K;nrKL&DM^^Kg8~AwW#h|TOcjE0W zGr>1$!qg`Irx7_gn;1ooG|IchMolSZtbi^(RX_Bdo>|y9J8N?4cT?KjL&V_S$lF44v*CJ@ zoe2}?0t7@5q0{gJn~{nMs)0dE-G2XM->>l4mN}in` zt*?qI9MEZ;Fw4roX5hM< z;-xwv9Y%@YQ_!~dL_mNg_3pHi8RTZP2fU`qzfTT!PNKa}TKyoIk2w?nBL?LjKPk;A zW&20AaRzFQK%fJtKQTaLT;DkXsgvFuE?FoMVBzn)Ro7mm9~C{_bpdsC+hwv1;M(!+ zc$^h3o#9zrYmq-XrPzz=M+$`P(*CtMjHhduXyt45xg)|Lh$R+D0$=TL2v|q1-(nxwbMH( zd_k-w?dB<@kmDv|Ke}8wYd&ZLOW@cjBT2Ktc1|JP3Ql3BiQz~#A+vly9bj(yHE@5o zKED1yx6tM8f2(Y=oYo*$bJXZ1SpzVgo0aP5ui~^Kx4zQE2N^@U+Xm1KbVz?E9mYp` zns-UNq10`{(MlUNVKT%;S9d+v*wSpy>U8O4y)Yj5Rr*EM@rTNHe|Oo1N0r8x;%4i5 z87N^H{G%#C_2>|pn=*fUDGD(?sXo)x@2Rgdtu$#MCdsTY;o)Y~;>Pg20cbP;G(&k2!lS#BpWGx=ecyzT|%2Sx`K*wgwWQ&c;H+sTNRm^OEFI);U zJ^%eW{~93$CvL)4`_27kLpH5L5?PY`2`Tea&c3@(UZr&6W6I*7Q3nU-5Oe&48{@!F zu5{qb*H;vE`DBNda}0RbnBJUY4CB#z+ywQa`>5CCJXMww*duHW!=`kvUdo~{1I`V` zy(4NyUGYpe$(qG}Pm$agt;4Xz8VeR1(&SORnhJNna3}fAt)db92CaeS)|nfjn|r$) zZBq<>W=0o<8N5a5jo&HvE|LOZ3($^F%m%9MC%?(l4pyV>m~J&D05LXsS}v()F54WY zKlNC2HH@P&t1L|(85O53>Lzs7?9T91gJo!Gvsv7|919X>xHU91FEi<2vKjj`PAINw zQ9gU3Dv%Dc3exyqiQ%t6zqbyn3LfuK=lXPl1L)oZvv0@>31;!G4E{j5hCLdOAKBq_ z_uIjKDAlav#HJs6c?U}GdtgeM>>YWbJrQ5>fn~`XjKlg+l|meRr0&;vJBB)1gTYwT zf^#epl++j;i%0}g>ARaB?-R(I zToF)~kLXX2wYt{e=>0as@ws;715^f7?rY+6NTKQJ*ZVR`rCG0AF=1?LrlO7t^XR6e zfGs}=EIE%Rc73Bk4 zrWT~cWvk!=2Sn8hf-Zv#chwGD7*UT@Sx|GR%jBc`?aS4Ayp-{m2Sa-D!`vuNNecb6h#sU6@sdAV(!`MN~}CP&zfDe z$j*#KfT@I(QZZaoIuILV70dHE@+fj@GxFBa*69|6q!d;sbc#1>ERpGssFXMgnKI7g zEF+6Mu+$N7e87H@r@~5MAHP=Yg>n;%E{+pO-yv>#;SsXFK`zrwCB4Qd_-cmmmqPyn zAwS#Y6zd4&gpchjP@$jj9}0a)Bb35B?0CHIl${YjaR%5;4iMan*#kcRwR4Ic-6Vbk z20j7UIsIP)|6gs>2C!{H3jUlLleRGX{=*MroX{5)G8v_H_xoR6Q>n6K1d;+h)yf)~ zNHm*9hmc*&V#*#UwEsUWbxf+vki|BQNMlC0a!gG=x89%6UJ<`oUjq*9y8QukN_GgC zP3rR|>KT&jTFtcvdsKpyc!A>YbOQnKWiMJ_^6U~|vcr^oat@hLL%N}6B#Y~2je7(` zeLqS~^9aU1K2ARdMiO_$zlEGA28&KhVLfyXTLue=K7a+Vpq>19D9y@mSh04Rnk>J$ z#Hz9Ge1(IZIrttg{_T;iff9DbN=tN01pJoOYO~fJwZ)3vY}L%-Am8|a^>TLI)T~n` zYE=gpb}8(Q1Q}ZJY=>Dt69)YinP41IAMN>V%Ei>u@ocvWN+I|GP8rL>55=2`BBrE) z;Kh$F%e66_M5Fj{EJakHi_ISI*aZjQdRe2Ug&2Vexb_1N8JdqUx0Wl`FdX&hM9doK z5=x<+VzJ^wt}}MeKvXWJ4@!YvJ&R1eU~afnu9Dh`!ow=#JD$)Iiu>m-+qPY|FTKik z)GHXjQMptm@2{0G80osbo7}Fjp8jtmvWnM8B@zn;?`od&G_Cb&bn|tWN@TbgYn)f9KPxIpx7&lF}g5#){?e_cB z)%Ih@)zecmWJmNO%I>o_k%2mPmCc}Gv%kYqf>0r`pnx~F{PQ+ek5S)7ga0BHgBgB+#dI(1BGVwwYJf@$;Uz1248`%(Yyy?3X#adH!9k1i5|rUoo&8=?4mVefJAo5QgiEmn zeFV?`Z%XD(UUs#jJjOLB6sc^=MTH8!B(Ogv#v0xP0>CTG)ErYR>q$v{UVIhKl_8k- zQ9>3`=F-ZI*F3dxEfAy24xiIf*xiVyZI;3pM2f*3hvD=fFE@2%}Y?H|hlYZDQFqkHR_hun&?g zh978YG(L+z@GRLF(^W~VgeL?6R>`&Sw4gdkc5$S4%qY0a^wqVv%kSiJE%A6LbbBn& zH3#s}w)?aowvuik)^8}=Nwu)D;%+g|>!}D_7=j$OqKzpRQW1}&o;WZE=vuh2f;y;=jC%$xr2cret-MT+J2Zg>3NDtjl6IGM2H_$S71ITBBB zsw7%NM=0IBEYP|D`=0(4(C)Zv+UkXS^+6FXQOL!$<*xuy<4(I$9d?-rIiohCq;tz zEVg0qQd4+kYCl@C(WHHXgkF(W8+FpyC6jLlT zQ(jqRQl_#$$r3F17PD4Y))wNdWQ!gF@0pX&q3{@42=nr_G8}U+btw%>zFpHBI7Uj#4Iks&5cWQ1M=})vZ>43 zl^L8+DhcOX7&6(y%VoCM=9zb7p$}O)Hn#)wkCdaV`?j?X0-`rTL_IweS)>Py%*il~ zs)_wNmTnT`RGCZe6&iZz%;tj@jb8w|m zGX4?mwgc$21MjGrh|Xm&HXMuqEp293Sh^3Auk*h|z*~O?GXzoLtYTz==0td0g>$r! zXB}*vWR7HI2OhC@{z7q>D_^V5T*;V*_NLb_w;5o|64-#dt(GXqp{RXi8xITy3{s4I zv+X;gd8U$N1qpGf=ho``61Di7S}6}kIq6p5J|;;I@3``N^;{1?2b&z*E2XBlW!?3e zs?#qWN3nEDI-l+`jwj0d6Yt4*MtU&?xz&Go>22#Yb3?p>(-E~V!WD^#V?cD14JhPA zm}Db3eDYkVK(jN!T-qgF*;ysioAL123Z`+Y*L>|w_4pA6h& z`hEp%zvyZ7jpX4F8+wAR{%M9WOZ{9(%hHN+34H1~$b1 zPGBr*KP(}gRlu1GnyhuNkFX2A=BEz z?tI*(Y!|(zWxu!~3?*AF+VGx!$kQ&{7U)H?aeQC?fzBc9uvZ>2Xe0cGjEF1IBDS zD?gX2=xY`9@)XT(`u<{5(YVlW*q^~|-ZTO`50l>4bNAdEp11XFJuN%#)S0RJ_JK5T z#v#LMa+##AT=jTqR8N~-!nzhE8`cp8fhA?j7}>SN(9C73 z%!jVq8`Y+++<&lm!p0hdwu>yHa0+4z>^f}SeoHtzaWA0mj9S9(P+LOD3@{Zw;7s$yag2~+ zJ}=y0HI#PY4A6(9>_0>Z6;lY+gQRqWf1v>iV%4cMh&(D@*m z+VS9D#~@rW(O!+4?hdvvy=Fyqjs_|r_Q?r^Sn7LZ9$x{$%FO$X z;SiaMp^?|W-N7+q59;M@jHHHz(R22hx?4#O>cg!W{7bzACL%}Chn0WH%jp~V)04N2 zBOO)SDI_wI8m99v&`vZek`CBQh2-U#O-;6|udqe|1Vw#@vZd0W73_x@6Q)_%)El)H znj;nJwc6xqk|KWv^(mQrXW}(J+`zhNuD`Zd*p4Shsun+VzI5szb@Xi%#g-1rXHvA+ z*|C~z7xDJ!O~qmzmNb&jV%7#&WXMP}P1Ww5SDMe%P}n1#IBAt!GmQ$Gj4|WVNhMKv zszgs=4Jk~eev@=m+mAIzgR-Kj`styFCP+7)o?yJ-Fcv3;VfmBPzmUt6N=3IUK8H>t z0Xv?(l+dpWo0EMli zxA^o)-Er38?f~1wPB{QxG$2A9%MrWUzL%-Bde6%VK(bQ@b$2B%KdG^FEnCBEjW*})aCRK_{DZHqd5*mGR@f7Ch_WnIx8Q>mk%)~io$Br5nGn=tB(-wwvntzMSzOVZ-9>$l%{6S@jbXW$V` zf`h{rVwgHRWWFjjkylS+la^ePo}CSfVrhKbnuTw#?q1IpRq}ceInGnRGkVB?F@1>K zg7KWGSbbc}rd7MfU8;RrD0F5oqUt_wH7=qv@m;9G`c!j{Oe3G*=fhVNL&816umn2H z?O*AxJa>2(;nv?qTPg%m1Y6_WK95g!V%Dyl6^Ygm|8W@1dML82nxc@$oGm4d6NMHjdqyPhG@BP{5?akc1Vyie?Utj{%tz;@sCprTlt5c{3f z6m*}I3>u*AJA7C$jfn#xWdf`u09k{_?+q%?xJH|Lm|e}LGR!l`oBW8GCpcd3!#r3} z2zm`8y)@<7Lb_X{)J5lgjBOKW9X9xN^ezBq(?aobGibhgvklU`q4^Fis*QYg7B3QF zqkoTzRU`lr**j9&b#9rC*K!oPd#o>HwNq{gBV1*{U7r$r6Spk?c?#0<=XiahR<%|E zNIr&}pCABX#O(P-;yl6oT+^bC{;949GA+_ZTV_ClhKLzYU4Hv;MBsqG!dOjhdR_kR zrG!p_+l}--p5+E{w4Vr@<25r>jvXbrJak*Qa7u8eonbW)L!c*Ank4V7*(ANTE4(F0 z`8(AhL{`@iSj)mC#=GZi42ny_%etoO3aa(?!cZrISK|2wxt2-SClMtKA}|9{O{Hit zv^E!fSQL3en5le7BBCncI&=ZPpU~j>k%^;1VBhple}QmBqw*G#UVlpVUbKg{3&iQ| zf;2)+;c8Q$oGK8Fd)>vY*fd2sTmwySCPPt_D^}f0^V<9{lN&=X@MlY@oa{N0Pa<_MlVsqjB)Xm8eA44u@ZgDIXDCdgc50 z0C5{I^rsVnF$@apPh@ziogN0y%jot-_Mp}U?BzYNR@YY(O)gA^NI1%GSP!(XiDp6e z*s>o_Vv=cGEpxftbPX+~9XLetLIkeyIH;#v|N7y!o|-k+)ceA;V}}fV!o%JmZ8Vbi z=pS7ee?V$bQLpJuy464t_He|P`*odskD+rFwM>SqLt~5cKpMpQ906Z0%JIN#{s z-et|uuK!7S)Av~z<|l*-6t<7EyY>zGnlvz(9!{Q5r~UN#czO8x<7(X-xSxx=CAdZKZ)aPF0I*I zT(PAEOm^7o#cD?qxIC8J8qCA_dM!xYfq8sK9&BQQn=E^G?!B* zn?8<#0e-`teGU0XP-~9tSH3)bx}tEy*2hS2>j*wV&=peiq_IbvVK<9Sr+M~Ajz~~V zT%Rf{m=&$-wJ#CG`k;P0+1Ti3 z)h0@mtShnZ93o}eWpc5SOlSbA5qy;N1+@drk?B)ESbH5{0294?(9OU9=n3-gZzI+& z3?$Qmmud(2`*+WY{&{?)%pIMKZT{<4&D0_IGkgQveMP3^eEzmO-#51wHOOD!*vF?P zps~PuZG!-O)Ms2tLPv*?lF_Yo`P>fl)2>BB0&VN{k7ZLe*09#-Z!fL1BxURBc2D+( zt9Nm^J2x`ML2qQ~@RMFmCihRp)rA`3Q!*!5ttGDHWT99SrFe86k^Y|8qmj`&U8;jW zJz{=2!30VemNtYcE+NJUX<(WIvHh=?M66(D_bc?5FAS((zL5VL)F*B0VQyum|IZ$y zNDbN(Z3*kM=lsTn9QA7j|B8RU!sZXwiv1x_N~5o>=AeDTM1|<&9rR=U^$pWfC?Ff_ z9Vc5BF{>&U;#bLfDN3bXc|{>zSFhccmWb9MuU}{+_bjl0}yYVD+tUhm@ zFOE#GiFL{kpq!_;q3Z5T8i@@kJh0Q)#{~agtgCI+ck<9g`9=CrFNyMFJkW-M*GxHfS*p4KyfY8%lRS*SH+*4wI0km)F z*!7N#k4f~L<5bu|gZPfR-Es^l!dzSWW!aJS5P#H#x1l-uBkWL0wRI;kq%tC#s7q!h zp|!Y|bXtrN*2g6pe;zt<1sp>)&KIxSP!T#8xSV76M&?WqV@vxSS) z)UO_>Qq(q~hl#N2yC_P5iEUO}ok@h%oVtC}MO9tKpz0>+u0FtdDvq|@+W>VY zoj4!ERO-~cPfZRtr5xx=JaKo??MYyQjJS!fKkvMdD|hmQ+*!Ke@zfnbycWdc+y5PN zgX(wdiuF|_|3`aRq#^S7)cdnQbjzLMo7Bk64XmHBDX6D*S)-qT8B;^zoga4@bf83{ zc!zyT)j`Tr(vJh&=5}w{mT~t>ta075CwT7)AjQ=c&9{r;_oQ{5&kzYZLX4CV!5wk> zMEoHH&q(Rm`PI10KxOS<3{{MKjB{B&uI)$!jl)<=17BP*%4HB{&zOas>HSuBaE_JYH(V+HD;lG z@L{$d+22-dig07jj__sHQZ4buAcSCtm`O}#8XY*2+)UU%Gvj?H%kT?&c8~KVo}BhD zLA1ew7KOzE!HE)BUT{^?42GLwL1By2w$%Z&_2pp}b1LLykFp`p}zBN~w;ZNh(woYwzC?!kL^DZ#h6g(&k%ZMtgQYV;@m5GaR{{Y9x za?V%$Hl<@m*^rY$L~FNQJUJaTp2j@^23l9W?BRhcmux)WPM2J5n{jG~jz?Rk!b?t< z10H~(EY}q*Xch$7@SxJr#U;tGI8vOEIlU%7qgkEPG(LZl94V+68_l2P8)2jsOpI5`(MJZ&Q{&u9 zl3)sK%8(-8X>hgL?LgG*NbgXnB(f#>0-qtuEJ3PVXvoSd7+k!<|GV}3P^@9}(!P5g7HqK%Swy2()DQKP3H9-2?Mw}OU9Yd}88AMFuu?Scl^aOd~;t4#ph&z@;~_ABA%l zYS}Pe&_M`YW<%}W%{>~hk7m5_uQSy4Khq6W)OZv*3ZWAbV740{N;g?~Yo-j!WsO}G z%hgD);HCQqXnt*CR^Ui&tkpIJgzFO>1y@a`lO&EcO7A+&!evwrXHr^BIxO8WPOG?% z2bJM;G6gOCa6aj8?9ISmh>3<(OwRX~#k3A_DrT1Rzthaof-Kv3iSp+u^6Xq|E6;XN zOvwMJwaL#vQfj}gNJBqbkTi?f`A}C%J(U}1*EQ4DwAIR7 z|D)*W87|(Toi~d5I3hoS_e-{LjwtWTTn0sz%lwvSJ4`!7GM>m1|8-7p6+`EU@(Qu9 zW9a=gl;>l=&#X^ldzU`iN3rk#6fL%JZbM%R)Q%ToKk`@wF_bm=w0>a12ZU~`pb+yX zMR|f>DIa#|aY3m$#X%;MUO=debtDq7 zXZ9VN>Y&rUU``LqIg41$4lV}2?|B39091q9*9;3&1OtWAP#)k3hG`A7;XT+d#8?_v zJ1_Cl1_&CZw3CVN*AuJl2R+^yv4mh&Mbq57I6bGipjFWki@a)_QZAD_+v-k`<<@(u zC?)?~habaU*1@QvB*w1_J2hrbjx*c8N6+f)tYdN)ui%3CqVP$~s&g9NIPv`kZ@T) z%Zms0fhIbvNK`G@kfE|Ciu9TC_&-V-@qo)i)2@shxx{iB+_fLjL?t*g7Wx6Ed%;Gj zw6h{HdkDzl9WHN;k&VeWllofU;2px(xiz`CH6d$*NLmM*IHO=Po4vYfWFk~L(D4sD z0PEy!rc5?^b9Pu63Y|>F~EM%=VgzAkhc%&Kuuwm)AE-GEMYhD` zc~HLR!LX)1bO?SV+#UD?Uf3OZ9{c<&jv`m+DG&nkwYEUMmg4^fNByT*DJb#3$XX7o zjUQ$c96a*zt-HmdxkV`9t#*r4MM+2sHCm<_-`ZH~wUvMWD_P5VGVdTq;|_T6+==F~ z{5>*by8}XFNF4%tdDJIAeLz=il$xwB60DAOXyo>Npc4*}MTvRw8$f;q`UnH7xaB?9 z5NgADJE$K@miU0-T2>JG8`sCr5$7s+e>S|Cu;y=v)Pg5p8(z{0m##Pb`MEIUS8NqC zoPOu3TbFY=C()K!sT)&MX)3R!k1t~l>v%Mug+#Bch1r_Va$sDC8iEzz8oEvD>%|pB zlgb3-*ZM0*vs5~A_bEo3uI{c5ItHf^+A^z_j2smoh2`#P=u-#;2l3&JL`Q87gzSn>>u0T0q}Q`C zFCMbl!`<5#9|EZ>V}0Y2TL{LCJCax8Hu(L`C5l)7xP0wF4Q1%0INZ;tvVCnv=je7_ zvmPFr`zR;hOES**YOA|S?uVMCjmDIuHh|_;p4qLTLNhL%Pa}HtEnKfvWQ_zxY~D7n z*axJ7)>C*N^;e%AUJ-~Bqsw6!h z2s*761!Znp&~<|`Zi^~B5uZUabJsvAUm{vVsEplFVC}(s{>J0Bo()T90Z(EYkjwpl zI*E+`$pa*A$e_vty?*k7<^hQg3P`a5p**<*SZG3dxS0~a<%r3&;4gxsu}l)u`d}W( zx+8%tJSx5C7lQHD6{k%K=pymY5A8F}pH*8)Cp9~K-k_<&HfYc!hGwEv!Hk9C0Fna| zL~;B$GZIvi1QJD(1rjEb1`CghTo-9dm2D_I zL?w=4Nwr*;XOQpzGsF4zUZ`hDznnKmCg}8Qn!lH zI)*SZI^1W+my}wacdm={g-l2yxgd zQu;*RWs+Fb(_~4(et{&IHNTrz4ygu%Bz|Le(#60kQquAu9@WO8-%I%gNw_Jh4quz! zbE>1#2Z1%6X3?TR3RdKehCg{aQS+*iak?Jbm7>2FGXxmRe|pciLTY~g4UZ80F$t}S z%t$GX>JRnLRs{yF5o8BQ*}(%TJO3)h|2wb~mZYbEri2XKp08~_q*haeSb$Ug)xtry zM*s|EPj9OC)x$=uNxS%D)6!`=BNXZr5Qjw`5sCHwDImp3p5eH^=v2ym`qv~IFR#PV zV_qJJRko*w;&Ig$N@_0S8Om*P6zgR?sbZ4LUG_y2nffGIQOy9J-dA$YBtDp$Pwk-G z<$|DOti532obnrJOvVP#1HZ3W3s1j1JO^#}SopOj^-i+)G+98(dxO$|_oR&gPpf_l z@4Q<3vby)c9A7jNZh!wb@qwX|F|1l7DkV9&z9jvAu_!`~=w>>QMXn7z+Qh%1qhHUA zH;%?mtXsJVBJ*Q8w7!J>fvonUc65E)%Fd$|Nl@uKB&~@yj4UM0b7otKUAOB2bctMx z`GFA!6zk?Xf8kyKkzB#N(|XmDcx|hM#OGXt9R8?L4M55^RbR$h0h2ddfxyrUXH)Xr zu@TIS7gAIgYJo1NbG^n62u9sT;lnmcJn<;{sg>n&&!W723-iBR}s@TlYIxl8PkgyZcNA28eO?=^p1KEhZINt6Zh6{oLqdq`kiO! zW2XN-Y$44gzBz$P9PGa>acqB=I2#4fmJ@J0pFw9ljWi-K0_1B6srMVJ4?Zp0j+`2s zeOJW_8oTjV<|YM1Pu7*in_&URb%)v$2K)%^P~i|C*xEr-7U!ph_S!L&y%}WnlpP9zR56)a~x4zoiA> zYQE~*U+PMRpDdsp<++yOI@ZqcY%GYXYvj?Uer+fc{ud^KCKqE-!st*)HL#i`1 z&Nw6?%@{`zRc0BVQhcv?tSXPIfR&V zp>1*7fYEZ$M{nh9eb^qiCT4aNgAbanprQ*Zl8vMzP_5XYOgqdejYZu4x;}w+nse%= zPyNk>!aIYmd98&H#;b#)rI>+E0PK_ir>er_^l99!OrKR}FFOBjI6&smxllJr`P}|~ z&_S_tT;p|Iy$^>V*jCbUv!`?*Q>wtAyf-ZtH}Mdcmo}j`RmDYtMe>+mM8cq{^|q7D zV(#QC6+}%k2>WY%x1P!58#EdpAU02RMKK$Ohq4e3Vf2;q1E&DF5zeVHZY4Y(Wu>~0 zu^-#ks4lKe+?!Dt758*e5>Y(jPU2#^eA|mes4xR)l(YP^%7n|!7QLK#=~W^|8NEQ3 z02p_tcW04=+g_YVxJD>GS!PYZka@PO>L6Mi@K{4uT)+Me-DYv2mm;OSyicagRg}Xh zf?eLAk~f|7TY;!fTkt<*df!Wbt-pX_lK(bs+5b*k9Z=aq4_ls1qcM}Nj0pZu+7kT6 z5<(syievvPZOfaWznG`9u!+EQgVNTQ`DfK3uEuKg+8EcHG<1_RcHExLc51vT&Bz=LUI-mPX()&&s@Lg`1 zmtAZq&B4#;r8KDhvc0tXYBK3ne$Cx0cL(xx)KF=_@krXcHhpsunW0TBUq}$MFHxtM zvwku+wRvGO-Fbgpj+(1{BgJ`se2SW@aw8k)S$K2eAfu;jBR}NscZ#3Z&rQGv1u}Bl z!L7lw93lIBtI2$Y9SxPHnQOS(3kL_K{_Ku$4#j!3@HH`K=qAPtt%>Zj#;=hbZ2lM} zSNvTJP^$voU|_Td=M{ z9Qgp15FSyk&RRu$mV=zCj1#Vzfn+ zFH6eClfziU+2LM_wg(-Rf6iKJ_r|vt!1RekA}nmp>!YhCoBW*&&PsuPp;7yA9AJ>{ zRCQA&SmPHOlHVO;(P{JSoRxrtvlQ+{8yEt?fqgzK%8TvPp`ge_tgq0##8vSwCBss_ z{UFR|<#vVH8x%Go4x5OskJ(SW7%M{9Aq;>eWHrfUF-cjHR*a-DLQ?)%_v3|2KBB^( zvLakFHkRu3_854=q%m%PPzuq*s8vF0NIe)|Se?S|sEd+Y!chv9nt_;Nn`){WA=2+> zdLqI`KR_w0rn*g$l3(^NQ&v;}#pvDN0yP)vQ>65ghu4qZ4zXMA2r2lFdB@Nr4Ww_N z19%>k&Hw5E=J;yEC}5_5s?T`yfDQoK;J6vaVDT_(HP%X>k)9|cGr zco{`}V1ce4q9*hi|H1_AuN#ji2q^Kr$sO|qf zU8xZGCF?=KUH`Yi<@`Ii1~Ti0=)-xfaOwAkMf^XL=eL>lsoC+^TwMe7~cKf#PiMeiTWp2G;xGhKYzJZXz=AZ+mR9 zylugEJ%#kfaisp{q%(ks5nmw#yUpHftQ#?5Rc}?ZqDcKg?vrf#3jQ3@doOYb8S>C( zYAmvcA&shaKz>w5Qy?Ss7nPT-vO1%>C(wwO(pf|D|5ML&n-`$$nt10{xLCoy zx)=Xri{*ZVZ##43Ah{5m8ejP>HT!a-C<45ig+8s0fil1pgoa%sm}d2z782fpwWyRN zsis?8V~Vi;gIJDt64LtB9~?s~WzVMD8(v9fonwHI#LtZ%3?8;^G99WB7r#$3A?$-B z142tSfn!UO%$e0bGy^uZA5|hG;HO#F0sRP^%SIp3|AxKcQB>})OZDNu4LjH0VV{s+ z|I03TY)Z~i%Cap*MhZ5F2Q~?wUK5=pKO(G9oJOc3L6dv>Ys_TZ4L0Noh*+;j?3Ac^ z^@&e)k&W^O9Cm09pD6G4&ux7CpPS25oL)+6Ez9)j-OuLE>4O#+=4$CGM*0K6Krx`~ ziig=D@4%db;1`zm{q_KIu0vw>&LF`AK4zWw)N`)~q~KP)T_7)@a#-?ZwBp@dlH-lg zVs_ed<a)%V?1W#_TF2lf4^1nB;U$wrMtucykN_RXE88?ZRiWc1ik z^1T-c$-~OL1nY4PQn=QHcP~G6haYFW9C04iKxTs4?0cK~wFB{FxrEJ#k8KRF>6XzU z-GY`+MeQ&;y4h&9n$lv)KTmi+S1BrfE})mQ0q|f8D07BDBvmL^Zig%`5hk{Ub(|27 zBISFbQk>-0ZU!wToAa9q5PSXcfB~@*qnIJhF`fcdV`RI+i%bqwVp0+!6ZU3*1K-Z9 z8YTg|5K-tVca8G9@Mheo)=JPH|ETFKQijEWpxCqg+uO;P|HePrPGMdV8+bXu*%8|y zm%yd81+k&t)YStMtcl$56^I-Qn>X$rXl*5>BXZXn88D1Q$%7{N^!ZI`_&DVVnIndx z)8~}+`Fg|2ubY)a!%mUa9lW9LcfL^q;Th);H{=>in4* ztcBr{uA$V+fI*MrubFbMj#JE+gJ zPb7fcgW_@9?i5drhpKcrE!jf&^N3uwkKL>_V!pux5AAjAH+NZ!L`jrvxA%7AEEW-F zd~D{cOs}1vcP}KHAr&@RSHP85K9&#(0vFIn}PLp+Q$NEiw>?`YxInR|!LbD=G6(V=9plxAOaA zjVmGXjeqQMP99D__8F=n3D;7%S<1Q&wpiM_40e%Fg#nvVZjN_jguR`f!ddD6F~!(g zB-bzrs$w+%RvO^`yNdmXW(eF~t!-N`*RyN3Ui5EUGHv8o@+ONSjy6nVTvtcQ*RW!} zh%3rSDx&vhx|{JNv4muL1AC{wmIb(wq!5f|B!kRVxV*S~KHlCS4dIcp+CuRd*qs`~ z&fAIyk`P#4xY9XC)-{l&8(ZmoT*a4h5~Bh!#?p}u!Gkg3CecK zWa5Xo4-T(OkC(#TlbsM47vLccD&W?L7Q`xxuXW%72s5ZOH}fBe8_geXswDceyXj#A z@bmBkaKwD}>UK^S*U;)=Si#LD#4`G8^F0lwyJsHT9FzbX1XN$@WFIwC| zU1WazMq;&bOeLYzhm4w6f~xh`pmB{8v86&iVVN>Cs7be;pmw#HYOmum|99nJ4S;7X zGXf`vXI!RGjeh6?0;@wpsnQALyHssy9R zQ7@C}i#ht8R4I~}l{xnj`rSmTzc0<0!`NyUJB8Ia>SOALX2S0#2*}WRe2h4XI5XfA z0?1o63y^60n^UC8k_(c1I(KMR@BVIBUTj-$@w{Z`Df7;`DnS}`K#2kjaC}q+1OZ?d zD_|TZ?47GBu=~SlyZh`gII7CqLfn8dE%W2iK&xLJq?d%AUIRCwCa31zcH3OCDl@eV zSl?&?Xr+Wr8_H^5Ud2UJU1c0#CB!!jvD5vXjAfPCZGA?NFRlg;zKpceb`LxP_AOu) z0kcd`+?;9~VKoJDz$nVlD5APy$9q}r*oPXLqpBY*j;=G5%?!5X%;bGntIcIAX=58f z?1I6PdKtle%g<+Vemihf2C%0liFk2LiMdYQDme49f0sTg(&V05Xdv1DplGhmbbcQobaLc%XSnwwn|T_2e7Bx#$} z#}TZQ_#>1(f;!_2uH>rx;cGTY9c(&jaxXt=5-q}Lv^yk{lA#ei7HbypT&Hv&$PdR- z@3XrMe=k&|NH?m{QVd1RhmD$0ci|F`{x-GWr9aFmt~BGZ%6Klf)X6R60*wcxZKiXR z$)t;R3okG0p>)k1c%ZqIKAwc-EN{ZmtXfOV+$2FLbPKlDfT^hUq)?5nSAaoFERwB-g$Rx!i-K6N13E<@G z8zaQ{ZI}_ia4NA>B!F~YY^Y0il={yjSTsm)b__9gsly4s`=#qU>39?37p`oPYs2l}mN@~Vtzrb`eYz}Oq#&btb z3s8$t;R(MZskzxlbbS@BKe)c0vz>KoWhYNYCSiA ztWw8M2=o&=^}@eO4|dro_h^_5m$fsq*7@F0`$oN#&vX8u!kI?CWu#RQktB*khgw-C z)ishi*a3@p22LlUyV$Z4Y)dC5MR(Ig$xwIQMWsqZF&@1UT}t`5e!OJ#cy+-0T;4mE zfC7%@FRU-UG0&;MbMMPWd718ab^iEx^D==!A=ubK9Ptp4V2qVE!0s@3bO|3}y+{vK3z}gihtRLj6hib$wqm~|!Nm(mctvoj15<^X z1?M_^(zecvOChX@GL2JOtSyE?7DX7v+$jWm` zgQ(>62QlrdhtXMayT!sa#eq zIp)0Jhd;hVpb`w1{*f9Jni_&%&_h>pW(jx0V*2S=A0&rWYin6RQ72$hG0cevGW?=k zjP$LRg16mV46P@@cumCrz#3Bs&k1?&+sOteQ_M0Vsl%qeL8vdW$z#9yY?Exa28y}D z!_CzReECF^1Ktn)$_ctH0>pSL_TA?t?ve=KsowtZ!O+49L>iZ}Bq33g-wmVVo*ak) zmS0tA-9@qwpR+^YgS@8G5`>o2*S44<9+n(T{d)=dV8>M~Q^jz))7Aw^x~lG+Z{SuP zwo45uAS?gF&!Rg}Hr*8<&m)ajE}Bl(F7mcHJ}lNSobN}UF-$ax`&g#Uby7DjXFJ{b z$Mg3jP}30-)V%iqT@(J*_x|@iK~+uz1U>p;0zZKYt_ugDA{~fo3{drLnibB3icvYp zTp2=yolS!&!bZ_@rx%(&Ex4e}s%ex`|MQtX6HP$d`Q`IV*312{^9JTzpzqs}^;V9T z!*8F$?f17sjZevr6nBP6SxBb3!{PXHk?HnN(YcpSWW;-9I7Ytog^`SbXtfbwBFdCDu zk#jZsEX=X_P71BiEpnU8bc`b8KMF5T&uT1It=F7?XaN&i?b<(^0Q^psi@#^;w5Yku zE&3glXQj@OEmxJv6g$wbFL6I-9%^p60n1m~f4N<_*5zYBktngsLe*P(uTzBda}F-4 zh|mB_vP&KKHRmS0{N_2Ont80c;waB7lkzD}ZC5Flo#A8_X;3EMoJZ-fdodvYcq;S} z+tZzh5e@8|x-yKg(Z$(UiU}Ir+jAyr8Miqz{&Z@F?7oi*=54Dq#IS)|x-Jf`{qmBT zg|LB^Pq!jUWMN0c{AGL44-(S$mOFY}31f6ACphz#))lq;0&$>t{`R&rO%6l#>JulI z7$l-J={Ou=LH}%kJPQ=1B#)R2eq&Od+cHNvmN;XHq`Aw;MuB;UuRP2dpTu?}T!+LV zhB1x4ZAJC)h+&KltzNz68rkFM>Tj>=@y7DAo6+hGU|MI+lzG~1XcHu`5^Ez+oAcRG zqxH1wIvR>V+a%&9A^O?ZorSwKL0A8$v$jCvuaV#WKOB4~JtiRMv)E#yPCFKpRUAD2 zIP5@NH$ET6X1AnGZji?sm*1&{1fo z>AIA;zF0a|sg-7#8A;G*%!=z{#+AwY-LRU^rqVo2M$l;z$<;ofKbB`~8J$CcTKL>! z@Oe(0i#q-x;CPn{Pc4wq4#`hGi1^Pb_~%5Jp1H-u-&|9ivjNB&eaK`B;h!LI*Y^jY z4X=NGhS(#MfEf;fM)n5}zy>1M{tO6}6x`|D{m#QhUp6mXdv`!{fGFgB12D!Dex-t` zB^1hp6%+Z9ILdNhdBTm0l+P!UGEhQmdsLUPqjKi~(?2o1X!wTAuzDQ9CorsYy@-Vz z>XQ=EWZ)uG>&qv}QEk+~fc*Z6CAh~#cTf^ZE`F&oHVFWRn`OOT{nJ1G(I(uxM;@>G zCy!rdpOq{2cqzwwK}&x;CWH+|+0wW7ugA;@N1 z{=spJWv-zzY~-4$BjH@Acr+osusl+8mZ}1x8m33cm!eWLH6pGbF&>}W$etzO3}dPS zon(n+BunspDhS^Y{#Qxks*r+v0v#kB|MCsW`rr0stcr{Sh$r#BgvjB(Nm<TG%E6prrYiqw-CHDOVpdU6F>|RbBm0soK@4* z*vPisVL{LLJcH$sgrcZCP-(!u!wBIow-pm}tKSH3#)?`&QuIunu-P2=K zO*QhOl3yo8Dqv3BNQNG_DY*_hD1Ri6U8nixOea#qCR-*?EmH}Ar^;mC^8g(+IG$wa z`QD>fD>%^tCQygp0PWfvdjRharU;%4%d_^I5H18kuU#&(xx-dbPwpqT^wuj zP_0}JhTskh6)Ecvs^4w8jnk!o4&7+dL*-v-t8ct*!^JJ(&a*Kl456;q$@ggCFLU{* zKH|ZStmfnt-)G^%stP~lEa<-=;9l^-73GlBNP6%mx22LXdmO@KzCwB(Bi^Fj$Lb$HIU0Rw&iqZKHB`+6&B3J5cq&xXph2A!$So=QeSV{xkA5HMS5ypDSQ!y-+5mlu z(GC`#b?KDmHPm1ew~Btz4ELvMuPQQ$^rz#OEsmq^nP-xX$);p+y;rpl=k>_<^4N2O zc6sE_CngSEUQEk`G)>RFdm|4rw!Gzw)&7wp!avQaXa=Mq*u%vmIaZZgaD_ zyl-s(fw}^_CxtlDMQ1h`q|fct>@DbxmhDE@MD?Vo6CN!+&adtwtMZFXj;jr!{-u4f zKC$3Hab3I^xg8S#R4Qu0=|an7-lR~iK>OK@7TieG%0cq&4v@Dvsm3F%cJ-sYW26GZ z>DOrxY{3e!WPah)YXeggG)~tM8Vw3>?jRH`crC@&4rqV=qhF$Q1XXPT<{Ztt!q$&` z6v)D}8vj>#yWkf!{EK8q1a0+Bh$$xDT>Q>)ZdZ=SA?(dx@S*u%Iez{Lk^GpYB+zdc zLMA(DI0eT?Y zO2FkiE7fG(+0`ijfV23F5DWS6Kl~FPl>kXiAo;u(gn{{2cgX*C&YIQ#5A}r~m354n zT3l#8tpl9{f;`IYvkZrj9W|J=BE;I9)D2AG0AL!BzcTgvwZ^W#`L)~k3jcGq_Mgu$ zRQLFHmomZXpM?coot-bccs;y4{4QtCdki4!(0ZO{L*>Etz+GWy8NSAUiieq{`zkDs z?I+;6Qx;=#g5@w`Ry7!i6@m&h<%=32=VKH=oX`xTf>1Q85>uw!N(pT`2Oy?M?XZGl zUz3uVpl61nvC0c$4YGg%aGJ4Ef=gnYvQcGZ(i_7FC4AG7*+pg>f`<~AX$pm4I3k9U zQRQVkqlCsQyf;IO*`9b6%CtDDD#aW)>bL;l$;6N|xXc}?f6=cK@&;v&h|3x_BgD4@#{2S= zhQR%gsYT^y&8m0o5MJvN?vRO0W*8{Qa@O6l@?$(O%x+BZAJ4CSi0pK#qY%GG7$H0BCHnO1H^MO?tHGN{3U<7@hK3E!_dVL(^I&k*HyaX=lgv}B-?DI!IV1wQM7X(Ff|eB6|AGhFtlGG^x6_Sq~Izrc2izLlO~O2(BO(rWWPZ-3mc0-c(cwbvZBUjYwG(T zAVfz=ui4IiJE`yY0c>u8{OvaRy*xIM`U|ij9}$ccaQ>}M*rZ^>wJDJXC2BAGJ2%>| z@1?n!nzT3D$#i@Op&yycJ2<*;g0 z0hJ*+tv(yHqL@sr)TxzcmQr%1d&Dj{zcT zTN&34cwriEtMWA2o1vH#2zRa)1Cpa>+MhptAgRcSy2xo5Kf$zn$Skv@K!@K@D9Zma z(k8{JhZNaIbcbZ|zrv z*ICV^(n3h2a0CIVEXwNompHSyq-T80iZ??ZJEi>N`(&Wo58s$%u=DgX=CiP87Kvi~ zUlDGzs}yl3MVYnT`qp0<;~d37(DL3ox8I)8Ie$D2O&ZO7$N@Ujc!K=iavs1JH z;Kh}?T4XYSxT`VZlyAa>XYR(+AyFP$hp-isg&@TB8(m=l9wE6M^o@hr6^cC~$3d@f zEO$y+%i#_jGNg=F>e)x>5_gRWM znFI?(7KBGJ?NkivaR6LuBa=;k>a8kP9>E6=#xc#N1G+_uabagz>@5K z)N6c{=RSVLx^?Mpb&iN!Kq1{PULQh%51#stnBGrH=1v;Xoj(uHjov#qUD`^E`F0Rf zUZr-nhH1Q?6 zr%y?Mf7fLA?~OlIJu(1K0^@x+?Z=$=p-B0ry$v;j73ty^Yh1~w?51+lQpqQnG=gQ8 zotmQhYSA?{4-F?&n3O_v>3jzCvH>OHuWfyZqojztC5mxrSv`=F6F%C_Rs>lvr>$%b zTV0PoT#mSAx*c~>12>;aqdrSQGsnSVFr=$S@>CgCJPYAlDm&)ELC%A%aTgeh53OG# z9(AdNjsMcxsO|p>RBN1^V4=v!G->AsTB$SF7C_JY(^HSKeSkb?<|p}vRfEF0_2(2r z$A~dsRgFCe6%8v_fb2+!>n<6zw~ia}Vi>Lap~1;~dB%$#ETf$m+2*T{%&$M-2=Dg& zXd&PH2?crwpuLVs2n7ZQ2JolDGhSej;;(KB;Rw~PoFL<2n|@DZc|9@)9XTG<1u_{O zk~92jph1@H6n|KWP|9Rxg)HB38pl7B=|e;39nuZqmJ>)K0w)kLZ!zTda`6}KfLE9A zqhcRSoV}9bE8kUlY68ye&;(ti@e`%43U?ojmNlM;-2G5HvYW8;cD2<6-^8?N3ryU6R>akX)j|m`cB%%1v9JN|ut3rlsY4n>NiF%^zpg7=grNcPU2<~my`0EJ%ehT(Ho&C17h=CYN^TY$!KOQuYK(@rr z7khkX6_G-j@sYy zX3}MJ zSxV^n8Z(u;>I0jkU6WmnHkFChuSG`DXQ>g8bGuOYxaha2t!|ClvSYFvWRT!nS{Y67 zs_U_HK8s^|8b5`!5^jMqN)VWh)1e$mW6H;({Te2EQ{1S#xv&}2iY#OYL2DT_Nma)PS?bj4%aTo zpFZd^&bnML#|~{zzi>F-RE6}ObIDD?vl7WK5_CDK{DKHOXmc0J)IU$EQ%)HwaLfKf z)vBz|`wieh;y;%<{mb&@JA+kYj&2#%A7s0^K3E4&V(nOEsZ7_l7cT1lMcjKE`(8HY z?3opypYsBB($ED0Sc193m+PLuKQ;HtQE#S&n3xzAWUlrw25p=)t_43v+i{dpI%s`e zn%%yq@2Y3*$#)ukPRZ~zrBvz^Ld~8aK;R&gfx-e+e=aMMV4@^rANmZ<=;G?v4b&|9`!JzzaEm?%c%S)J%Hw zQAdA%$Xq?D?`ECZ#6jiqg?ueb)v!@4iEM=XurzSlQT%|jD^qK_SNm>PQ|QgpmmZ%w z2)CA1c@oila@V6OcD$v&xjat`9y-_D6P_a6Q5(#l0)c%}yVFsA_zk6#e~=LA`X#*w zh~3dQ5*Xyqk37fkVZX6Ku&L5bQ_|2-bM_ABz4~PHaBdA*o1kkzkE1L7k@2cwJP2^1zAAJ74Y|&@TT{zziAkkd<_9JS9>#r2ZQ3t)4ge0N^X}MR&nHSqzA*x+ zd~Kaejqe}-aGOs=00F;|R!ZPN(kgvGZ!BZ^ zyVL`}Z;=t$W3iR?4QrBiimLjV8HH@bz6kO__5 zqzk=Em9sAh~3n3J#4)W=OwCJYjrHk$HsgW6(15VQ}~cNN#|y8 zHm{j3ZGZ7XTmMNRn|35K&Ze!#gaD66+OupQg^*3xV?$e6wdy68DoKA{Vad**t;LGM zBd64Df;AaQgN@D$m;sMrH$x5Q(W8&zHP30&pGwH(<{QU^P1(u9rOxvS-{5SeUxP z4!owx33Z-su#1(D+QJ||0y#wxG;o-vQ+^)Jo|ULq_s7(CycULy7&GtOD{QEiDL*!= z40nchc8O4t~xMep>eta%e8LF-5ZW#s+i3{wRu^N10bXx6}IwR8t9UPOyv$_SjGfY@A zr~CpVdQ88O;wd1Fx=4TN9#h!-^r~)wP5jze98T0V<3)!mJSvyJK7({YHlidH$4R!^ za>3fWhW8BnZFv+UoXmo3o_jtg-BxBYlSY{f>UE>01$R7Jg9Xr*sZ$4YQMPxpK`gle ze_Iw0*1Gbl!E&xu`09GYoCnL+?kPnoH8_Ff3LK?`YE1&;R4ioEE?qsulZ=X6A>f;n z?KTHpchvzp_p3qqqIG|dL3Ky}?OF}>^n1Y-uYnOSB~X%&iY)ELo0tZs*67(2Y2WpX zO}Citd$>Liz{)M8Oehm)>IYX|5P|SJ_Fh1=)@PIxb_JsR<4@!HH#)9Xd)WEU^+YH* zyg=nl7>Y$A#P5hRY zCU<;-!@W6k)IaFFd0)3KZNiRktM(fmz5I{_s*k4RdKYW zg|lL;L<)rr9kPj5EI+ZOMMBLWlOKikf5uM8$bWNtfIx#Jb0)x|=>V^A( zYZTiWjrI)xzhW2|pAkYSNDKo5m6889;@sct5sts`*wt&#$Stjjn z@X$|d;%W-j-hs|#3uUO)UmRT;)QvU03h#x<-=zY?i$0-3`+pLwI3v%h2$wD*^D=+q z^T+*>5pLk|_-V_i2PTLD36hVlAoN(Qt5*i8w(ghHgZ55rZ&WX>5zhs-Mmc;H$^n** zn=>}eaTA%XZ)~#rcb&vj9)dP~T=wL)V^>u6Jvub8iktV(Xuj-AMR_nfiyvj%+$0A` z`-*7JV_{|n1j20FDvLZ0WxtUYW1j4OkG2%7;H=x9G@*r~p06ZaF_W>t8x#b~1u;b0 zsY1GwTd$<9gv{tE*cK>LrOYf8!;zY;_q85n*cU^5FuD2~*)5H(iE;{;7usT<7RuOV znEx@r@usvrUw|x(N`%NbbI1rtGOxNC4@C{eCX2r&Fui8!4iTi!b;^r9AC?asQ$%<^naDlRP}QWGzs(%1X$aKs^EzDGIZn$SmNtd=oM8GOOZ%T;n=8Zy(E?@ zpx%bw*QV+RE1lORf!;NbtX%(F{MhWd`S?Gx3V$SCusp4OEVu$8Ib3HlI@vt@AE$G8 zdw4qoHl7c;KJi|K5LKU~polNTGyb|YVS0eSPKm+ppx|Rz;Agek6UIj_&=_{cNB?0# ze&@w#s1O+%8Y6x8dQ}x8A}Rfaejg?d?l2H}#8kD=AmW&ukI5tlyJ~=?e#r@k>1!%8 zb)kBhO(N=EdT3J&RKC?GtbQkFVVE;BW^&?GjQ?BnETjdIf?66l5+AH!Qqy4DKnLp?s54Rk`$?;t&8%FwlxBqWvs*Zq+M{zjUo(Y}_d1Iwy^N&csCr4Bzm}$ZfG}Kq}QD3rDuvy=& z424-54j<#tNzE8*iHQ^7SYf6Zb3X4lF^0OgVva}Ka%RDJ1=?ej2_}IX33~YU?6JF3Je@^;kY=yNiQmTDbW)gu!}!v0wyipmB>wiy z#m5r9h?V1aUeHL+VTskOry4WnS;q0#e$G?x{4ZU7_PahViTlwv^fO=1pGQ>Gdr*jE zm5hb^h8VGI;(!oA!eW(Vn9-EBJbDFib`jzLI}fHE-Cq~^V(lFk89$fQXG)7r3g&0+ zROD9sZEPIBn?&kwkW2eD`KjS!up|xBjW)VKU%P6gA@M>I$KUpGBiiXDy&hykimktV z(4XX%5)al`-nVB(2uK#?{4m6>_>DUyb|!;ffJJ{A7m2`75e4~HSTTwolP!v?rGyb3 zZ`>V6fNUod=j;|&nHA-)gXh7m`dGOXntR4GpaQV@ee`v^tR{MfGG9?bkkv@Bp^uhs zs5J52LAvZ>ka12x_{SnUCT@HAGo|J{LyB&=CpO`b2?%r`*mX7ES@jIXC&)Wb5hKdo ze80e`a_-?4_rB&EU}O@&fc+tVF;jwP$hDI*==}3`Ba~~TCaUIJ$8Xbe%}(z42J_4Z z7y=q!uP2vZP1uJd$C|?zsXhS=@vX8Vkqff@8f>vuz>LWd}G8~st7 z4$&h432JIyrS%QfpjFlsbu|SOH5tN6GB5{1nXwe-axaRSwFL)c<vKU-N)YaYRtR^54|; zzqf@Fr=$n|<2h$FKWtgKv<*j#ZdZsb+z?8s_GgDDbODQmDbQg)JHK#NU-AGZYxATJTiEkctk z?wm9VD{|G-T)H<(F71A-o9S*|>uN@yLTQqhRgT-a^5_&OLPU|`hrauArAYOHpFNBa z@3Fi+OcoXX7*bGEFnBI7)q*6J!w3^y7`kYm6>K`EzdCBzd_gKPJB5ZI%4|6$ zV6nvzpR3lsP9jluzBkxQjvpV|JZ4_fu4ox!l}D(qOTQ{FBam1`dV@JT61p01M|sxcsM1rSk`Rd8O5J z2bjx{R5>g!9D&8k%KTm^nXoY-5Q$vj$o)-m-^ObVi^q9|XA(k;z>`HRVU_v=O-~`K z@R!C>E)aG{N=ecIy*T~k*xz>WNWqd`kRlNikxSmT=0**FUM8?VqrClt+hMgdyY^RW z_^+MHfAbyvy?&E4COx1CTEANFTfw!P%MP zLo^b$^NA5e?2zW;N~4Fc+sVz&zWwuf@IH3D{0j0~M^~9;P7>!5pU3V`huf!>En_Y> zk{*cps^Mrf@mA5h(S6(sXuyBfP9kymyua~iPw_0i+1W}kvSIAG?tWn?VOTz!C{T&1 z0D+28D+IoV0g0s_GC~e-(QM36Q6;wOoni-L`k*@jTB&R~Gm;XKAE7^TElODW8RA6% zKY9-8|HqkOz>VIL@N}v9t9-5PV0^|HNl7Z|9e0SlfI8?bG12{F88bgHF7#*TEvf1& z@v>2H@At!G9Aujc>(Uada+d4hh7bj!wK)%0k$1fXjacn!O|x(hyjT-!AfKT1$Uy=P z&Itm>25H_#;E&m-32@RZwKT`HgT>DTG0vP(*n1VAmCCOv>RZS@!5yBQtag@3Vl3Sj z(lf}1ZA$XkwGo1REq%5Sh-@XjDc}Z)5ee@-38tZdH^G`pgkk1UmcE`N<`Qn1MoDFR^0N`d zalX?ppW~pbSpLh9i!Vo*ed{eY;k*F&!~8p?!W$ro{^J@54}`W`zFfi?d85=gr>c;a zqOEQ%R<2gOl%lO+9YXRr-i*&ww>hnUCmAxWG=LVT6ytPol`_yvI)a+UjS$sOyYywL zzV8~4g+Eh2R3D4Q9!O%q-ZR#VS3~#KVFDEdgKhnxZOet;ZNoFXnmXdyY2a|t9JdWi zXG)E5+N)USYXl7DL(n=MY%a|;yylD~qV=%g%InNAI%_FOx$XSuSGylg+$LXASR{SvorbfP>ysm|%yY)+uGWMH zP_=uo3-3RBareA#jg>ae?Oijmu%yRT!f(OS=CQSRbBZd`aux@}Zv0m3fz-{>DG_ z?WGw$#1|<;fKS*3a_`Q?GXkz0fYYc1^-rUk?U7eX2lr2(+z7xvQT%^D!v4>0)UO}V zp1MPe%zPe?Tf8n38VwykYFIg2s*Kym#i2|%CTuG6jkP$5m*&>AyVfu|>9DlRq$C4p z`%1nILP#3h(!iYw=8Pg89lC-vGh zT)9j871f@_#9w3P=cx8PCv&uI===tq7tr}FLcfSmFL7{QM$lIwB2tFHCzMzaKE|uZaFbs(pmt zzahlO==+3gpK5={Qa?lIbEXQu$>YS4x2BzvM zOgHhHicT6jqtHo5fDFu%0Y&MV7?*{HhJ$8mDLKHh)=CZIEs z>XW#hr%y&C$8d77&JcPEVw#E#JB+?#F>V^wrz2E8CztDt>N5~JAM34gawWzTAf`eL z6j8mHlN+$|H(&+N*Jon1@Rd+~7ALo0U*Dn~X8LTV&%vt9g|I#kk>oL>+`67BGzvS#+0M40s$(iUd72n zdNoEbg}&6sqOXSP%Q$%qtNWPlMn}+dIn`HCy_S=w^*W~4BPANB-pI+b`bn7HMD=D) zp4U&JdJ88n$jDZzujJ$<8EB*WDo%bU1MO5_&B<#ru!iagzc92n%1&$;UFXo9aGJ{w@QjQ+)#`U&w$T#Dd;~ zzJTGlQV(*+ReEoVeuf?ba%o>;Y8V?BL8p)E8#y_sGp28%`ex*^EnFek$7eGAET*5$ z6{mgsSsD3q9rXap4`ZefeV9K@VT!+Ey zF$=KA4NTvPc(x&%+=!if6V~fyrr$#KTe&g^yZxYk8~ScX2kX6rvC)fwjAd_51O=6Q#l~ zra!>+-N@DbRNupuCM5d+)gMIfL+C*9t=LW{j02$iJPhu|;BV0$3 zevImm<8b{YS2{6sr~U*wPa=JuLg#4=W?%#MQ5{6GC3k*>zGu<*YjmCiM|%f-&tpOc z*84Y1e}U=0MemE~yo3YkWvs?4O#dCze^2#SLEh`HQT-2Ge_j70)Bj{B7wK{$lMg)!(A}+f@HE)!(7|yIi>pCNTXyrvC*5xBfml9~jDI`d`uckm(=c z_iyNYO!ZH=a)Y*w>7N?P4LS;J{WGS2j)5VM@S96?7aJbWrM~RAp2Ho?w(R3Us=h8kBOb?7=SC1EPth zFls_4(o_iR^B7HIbQFfu8O>lc6TMlOltF1WSDwavPh-9uMsqQ7G=z~dPh*FkPRCH{ zf^kR3a^(eJ2s)0@@!+Ks7@dewfMya`UPUyoBAPr#CxaI;yo!m(aOF)*d=nF=Fgg{H z9t&ZFcoP_ePGfXBqxnD!I)l>!T1aUTqs5HQWVD3SS#&m|a~Pe=D70oiqacV+rwdTt z`(oS_AnYiZ;;Vm{GP#R2(g4Q5J6ub zqZ=`>iP6pY-NNXZ_&tl!voRfNcP^vfVe~v~9Ta>%r5A8V9X9krMlS*{y_nJOVicr) z4{7y%q~s;o#Xq3*hupDBNGp0N_~>QmTn+<`{)o~mxZ_kHkM<9GC8t->s||3jLFZb; za2;m49zf^~7~G1!ZI}aSeIs|AF0@B_6Z#H=gRC@%-puGNjDm96N^e79@q2{19pnnV z1D!i5y^GNwBdvbI9h>xX3@Cg%*5Gag{3$y3VD0XOu}km6B7cUXI0GdG$~O9Q?!2Gg z4UYCD27iIhjud)7-I+pn(Ff4kO=-X3xK#fF)Aqo$Mh8;pgLEgQ4^g@o%YK+UuB4A( zs~$y|$0&WAJFZ7gxt{(KeHrL`g3>3s<0c&DPvJM8J8nUkTj7jNfxsDv259&KckH0RYd3(*qc3vDF2t~lzJ$IE%(fK>2pK-@4Sg%*;=jh7-#gTr2 zRrv>{q67Yk9QQ9q|Bd|iA4*tVV)sqY_XLv@L3x=tFZ8P zbXGg_FcomE!Gx0$?iBP1=Tw+mSOdm&V6X$m2s;gf9t^I^zW$>^x{TIOk(^E@13J#xA026HdLZ1cR}G0xPr1PoeGlJ+0U**ieHVsYtXqCo$D~}dIY@z zovrAc5A9;x(76#Qa1&)WbLUf7?IdIls_h7=kSk`@r{%1(YpCj#lfwkL#!TZtKiO%il+=0%W z=-h?bb|HcX(An)I3`ZZHW*r8;_dYNdc)q5 z@TN$KFA(vD0v>-!Pw-5i-|s02gxzh?9E(5*#PT<+J>A7I?A62B*~zC^|W z9P9G>z3V*@D0arMQr1cYC3UipDhm{|h5LGYgP{o2edf_Cx2mzSp)Oiv>L#yeLz8!% zH{=a;Lb+K`)y7b8eaIUQ*ZRT{Zvfkzn;^Dh&b&b_tLzR1djLbu(5R)s&OWGGmLO}F zFR;GZ7xA|F0$ssPfH^^|z?>;6zRuI>ZStPc=M6`KP>&qDrENoM-5CVv&5=59pf6VI z%)XGX5)%TESmdZkFzAo?dSfx^UBRB}je~*Kdb%pTp}3foPT3Z}XFaqfGM8SH`t6GN5Vo{$V0p1w%81xQ1( znF>j>3U9I_m_OL*!4edbH>`ixhdjOAzRqw-V-P3?J*tL%X98}z(HHiu6&$=8Q24^- zP|1y$;0srF`}|$d_rCRkV8~nL3-<=YNJU?`$r}mzyc?lADr6K)?&pkpM)BBW#_g7qHX~jyXP#W^QQwdZ^`y zRkA8zEFH4%c`iks*>R#S+Ago=7*;Cg|@1YH!}MEEtX~ZE1uq zi;r2>f>DEjnwx9mVj4lJ4bFx!L$YC%$Y$CxEn9kli_=aAsk{~|V)0vgukG)sZ(bVm z0*qQySveQl3j#45@dP3(J0K zwE%oo_0saz+Lo2&wXM}1%`Hvt`Wk?5u5K)EDsO3M>S$;NK~@P=FRy55ZRx0}Ew6_v zjjnPxH#RgwUER$s)%8%_Wep80s;d-|y{x*t3c{5Q^^nj~TU}q%vJ6J*;OLh2#%jP) z=dP#G{tnp?_S zTAP=-tDw-S!vW)gpiCgXLdM5|#p+jGkMKy8xMUz;O>22gHPCfTTr~91s`h0K%`F{* z8;2!fcgZAI!d#MLhoy;ZGA(gwf~^ymCX%v;CdsaY=!T{4+Ukz-r7hU479Dcz=!$CS z(rPQ7u|EI<*-2IICefKzs3Dp;Gs>mWIt+`%I>RuF@n>yAWqEBiMlEY?fqtp)0O)4m zO2%G?ITyOAsihT%cMI@my?b?aEQ{LG)CwOH_@V>r($vrZxU+56vuoH`jtrcJGOe{9 zSzShCFD@8pb8%cTC)|jUyK$ZX()i+XoGu6+GL*fqD(-HM<_ICDR z-EtD41JYeg_3u%=n6kI2el-k)b&xY^)lAJ_KC;OXvt&4Qq7|b0tyH^_>UTidFtcv* zgkY*ni>1q{DFY&wHxHhj)chH1Dk%Fi)$gJD{W4SBWJ>itRDTe90isdW7@}wZAHdu} zAraL~sy_~l2LVtmss0SGCivUp%(cOs9VKOKCj|(G(>-VtoxzRXP*i82`nyzrkLsVm z(8Q5by;*8#BM=yv`5mesl$#)^TSJzmo=#AqeAwo|a1KnQ@-=%y8-1PLI#0lpP$5c` zM+~U|*e`aj)leDADb1$TMd>7~GRvXLLb;(jn7z=pKTui(Y=CJN^^eYqG3^$!33On? zB^(S;hcb{u2-g_0O*NoI&;N^*z)I9p+DK^=W$#hC#%eFGhBs1Z4JWZ7>i>|(6EkM|}n9_r2 z%SVF1TIp!lf>mpqI^m=l7cz^&+(wksi#pO(|K?y!+eFvEUaHU!+ETSuM zDFe!IyJxs5n@!nVO8Y7OlCt9|E2r#3K-3Vh7p?3ZhRA_=M6zvF+sQg4i;@uYasibl z%j^peDs;Jppc&@ekT0?&9)ZYaGEcA+Vw2oZPO2%GbCxbMLq_(tYtrhT8T?hK0nY&B8(3+NN@2) zP#9w_;ic>hnK9npRxA8{xLPOhqFzw#_x1?>kx?+oY>Y-_K-WNXD`U%o*f(yh9c7y- z+d|pdlzn7(ZzuxFcSEl?K-u4*@2Y~4y86Gy?R z0Jo`wT5=@}HJ?|uJ3c%H(i=TE0ER75j%gk-DOI+sMXq6rHo5JgvCP-yjV7eXib-L| zZl>&GAWD5O;#;=`m-S#=fN1Zr7+p|h4`mNh_5?Kfco_a@DwygW>YWOi_wClZCAXUD zY;}&Q&PAt21+6_Rw)#^Z3dv;~Q(d8gZAXLQnI14hDEq`@pK3bvwQugiQj`6ieTM$h zg{7eOZAMq~^mLWZgLXrx13j9%ymVeYu4;Np=XK1TW5u*#J`VyQ@_+}`Y^@e1YEU3G zzx+DO-K)U$_%@?~7W9KHgM+oXFMuWv4M$^^H5Ru5q}tt!*;t*YGq0gJ4`TElPiHWU zsDMdL_PP23KyUH|uo{z&vVIRn2|&!WAZ7x@ObcRWbaIYc&!X%Lll?<|!Bjg`u;Gq9 zYF^Z&P1UP5e!GG~Ci{~8Qz3b=DM@UM0P8Fb_6544){M#irT)ZZ|5pE{kclzi)<75} zYIvQ`3-)1PgIF>))ic$zO!go4l|n`jo$4z3eEu#h_G`8uoo`Hb01+S5bW=S|1t$C) zgNM}jO?KEIrlA-P({O5D(@+hK8oFsvfHIh20MR==fj}^l*BAEYb$KJ8>U+ELqOB~; zqXsvP6!l-!Fij)XNHdL5+6vQ1H!@%}53QMLWExp828?VF37h-;A%Vm+a*SMP@;VPF zkX?C^U|tyIN1i7fUy!MGVmD1saudfiMjKlLD$qL7ljHI>0We^(w7N`mFtDpNeXh3OPin`lfl zH4Q6~KN2U^HLM=AP+=OApx2B%^(Ub8B)?9LB$#Zft?EisTM9jH9HTax>Oa+gnHtrY zsS)rQQ;exlCk zH;qzbfvJvD$D8V7$nm-0s2&_arQfROBn?9zAbypxFv;BR1RIS-NvXlXPO(3OZ8*-< z+*o2#8Jn$LOw%Yc79kOjS4iPVqogXhDc}diK{Cpgj@cbh%;+T+Ize%0aUcwe$s*{2 z6v*3Rg-zoG?3%^KiKel{C{I8SyO3pGf3G(L1G3fwR#Ue(R0Y#PaJ^L~jI=@Jbqi=u zFIaODx=TP~@nZq&2*^yM0(nG@g!(|>RT@>MQH}jPMLk9sbPJMXo}nAjrm@thG1X%A z7}HpW;@piAaq>|_@6`DyY?hm<8`xhxNo`Wd>?A8-?fr=XumVSAtx+dsW5NP$BO|88 z9kp(ySU1pWrf(9;?@W6GcxK28H1G80Pxq`1_C?Cp!gRG^`iw=U`Ww_ej*52y;%zY1 z$?7plKrc)J<~UQGipY*R8sO|%rh060kh4v7T5^z}Tux68a;~Z7CkHvtRA(dy2^>_A z93=E-VRDerLq*9!E=&dpx@Trm=;lE8lq3fU-7^cun)nc z74uIWBuK-ZAzyDKY%L=6SgUl<&f}7rMnrCV3Bp!8j1E*-lA?Gcw7JvQi^hLaGo>uB z#vfek@ynHT(>TrWn8sS8GfD9&QCN1lX>=LTQ|pZNrurf3i`|CLRNqkFG>y}Z4Zw?o zh;@3nWOHw4iIrFEOd+X~Qq7h($7eGQKh6_91_mfletV| zGw8U+7Q>h1tXMt#5S6KRfmx&pL}#k+gF$P-n`)2RWEy7~XPd@3sGpm{@bLvpP#HJX zcc3F+mN?fmzN0>D8s{O1*4nbPc%f;WZ(IN@Y{P@CClwiQS9PE>h~>x3L*snx#tYH@ zBw()&q86^bmvs3;Ua8T9rQW; z+w(HhxEzZfW-KPc*xKYajUS;xcLnCY5}m7X5M2$n3Uu2w3SmgzvXYV#VCgtD!8EQl zt^=c4n`9c-gVv%>P$w$Hg%*U}WEft-9@}<+-0N{&7Eg=d8tkYcD*~o*gRymx*(^l@ zW$&8CHseOfu?cjqzGs12gbofM0x4>tF8cGr0$-ND#W0|LV^?kV17YCF#9H_Gs^u#!j!Wc-%yUq`FDscxkTFRAN&5nbff7A}aWwMf*rfSO` zYLbh<6XV+>XjSE1Cs$A8J_?;ZbK%U{GlB7L!LCa;ZZ(bD)Ul>B-IXbV|6|qLe7F^d_3oLdQ;81@ohH&xv&Q375eyom7#?TW8qi3N^<0r;;)3{rk z4v3-h3gMl$%?0BxZ{$OB;*0<;^I|+O7@+#8agS--t3HftY*rjF?tKaw6EBDw`Xar3 zAg)7RPYN zhl6cBnJ;GRHCkcQ*oDBe1_J}{Z3>3EEX|?>&znjF>ty{9%P7E74hb<#4-ERak?*QczRd!{cqbE(+O7TeKsvN4T^jYp{QsA)WAJU&=7%Hw}N zA@bq|#oW9#t$_^zXilCr0p#H!!o2)pN8rT5Vb~j)aSF)tcvxIv92b)dC4S#px%Jzz zKC)qEI7p3On#L2xlL{%wTVrReX<0ER56?dPaP=`Cf*=^9%a>RNlDW9Yo|Mw#LPDe| zBnPk!t>g(HsT|W!F^#8;r%huYTItUize-Net=bas^!PgM{o`8Ey_u`~T9k4`zu|^48Qh4=x0{j^>GN{ydX? zZTxx3~(|89nOc_SIV!vI1u-xGXY51-IMg7G9-8@?~ud~||f^jvo z%e&6g=Z7hXyPF$|7cQKCTrtW86caPOmZA*|8Sq9xwr`l(iwA)_x`Q}r4T(hM+%!Hg z{%RT@q79HSD0$|(NUy1`LlvDy%N&ehW_JW()+5Z2%;B)#RJ$=DcNkWy;E#;InW_(m z$H&GeAW4U#mVoN%xR{+g48N7S0aJ(6E3%~*Y!yGIjPC-awaXVq?(P8j9_Z-mLlSqO zTsDnQvFN;b7-^z)gvIuJN2j%QKP>@>z_!jSE(^>ITif}WF*^~u!#W}}X;6;`*WwuQ zhT?}xQqx|8mC+h5K=fXDRH?WtSZvpniXEpuuv`Cbdq(QmYsrg6YHXlfD7n_!P8@ihfgZBtj7#v!azdTbBNo|bS@EspKs zX%mgZrn(jkWKOtZatBvH{YwM<<^*#{$+NT$lV_tZ8@VHg=bC&p zo<%tN;wUiU9ehlZ)m;V>FxpBNu0|XWWaVS|IFpY@PIi=)nA&mLBFZP2d?KGjd7jB9 zYd(`7!>5>hDvTU{EIQNpbd%?6L6gtWyrwo={iVqZcp-2uh)u!7GdJUXBvY+KR$Yij z;IT*Bun>nwusua4FXl5%UczS~BR86SHnRR4KG)>)&^Q0TV5DX*iZEV^ty#bqA~${e z!zQ+USdB*je#DQ1DIHh&N<>?E849_iv@myrt)TEl{CJqaN1{y}hnf5Y4$aOVB>Amb zs-(x)<1O|faB)~bCp`t=Wt2gsoQOg@2W4Ser#uf-?Dq!N%_?B)qE)!#(kDmWAp_3 z!d|p`#0nz#@vN(i?Fr(gp0uilx;hMksTIV_Z7E$L&w9|#aMCn+4I26q9*Lz}JhLdQcDWVi`#d-@zvlLGo&qCSQi~A}P%vD?`Q?0t z$!oP!5)6}sh(Jl_btbO|E0;HL7$GO2uL+%IbXw49MQ0^CZQ!W$@Nm=8&ary3rUcc9E4aTbzC9`Qu26dwFElolSomhw)McX96##ExAJkuw=tI~Qfs zI+L&G-6r>;b2`fG4IBv2gHkb|&Y?Vr<@TCdo7QgfGmyqzAh)2qLPr?pk}Iq>gn5MW zK2v>^Z&b*{p;-oBz{M(UGWlk{#pGwAjGo5NGWpp!*`I?NPdY!>Ev!+V z{HnVUNLKZ)!SQ@8YIwsmE&F7;1$iCJo&0)!gUPq@ZIpdv@*6={QWv0=ku8?e@$6*W zbZV{jTAN1U?%*bSH?G-|CZKZdP{`E5GI?X5E4t(96Lk}>xA*ac2;x~hdUE}TC5G=NioEGDq zZ*K@>!Y%yPBT&vV4esE#C16s&XA95_7r^|r!fW!|`5i~VR_j^oMQrNNO@1f8%j7@i zKQZ}saJ2dAzf68N4wvuXi25n2*!RH9$nV85e;?`&Ny$54juqZ5R)_x#S>xx>>*{=z z7r(&Z4s`BEXD2$lFyR4ocB9je!*mZMsHGSjP@ygl;;4Oy?=|_uSeNVgBPM^8KW6gB z5y3C{6DEHWtNRq@cv|e0^Lo-`*q?9@P!_V1GdoM-Y^5pcxD z$KOH7u@?{4dSMpvMpqBg!`*!m)Xj=}ac>Jvw_zumhurJIXAYJpsNb-wp9tty$oNF| zc5Fe~u?E!BtOR#s;H}~X5qP#MCb7)$O%XgC^MGN4IC`F zAZ_rI5y_0nxJ^(Ssi-HeQh3B89m9xWQ%&AoaI9I4PV?Yn%3FZTd*nWCY~xP{Q`_SY z54omjU3|n#NL(x*h1+~+LBy>RR0M;5uP0z#^0WhZx3gZHpFj2}cLT*vC-hDZWQVTA z!5&e-`cQMjaGI6`l)7kBn*MMYXayJhXUd098>y5{sTi@JK zlW0c~dPQ6(%9a3wPqaf9S#wMy3`;;Q?=nF@XW)&cA#qvxL!aIe-HmkjL_G59rSv7b zOrJn>VJkdcU9n@d(}2h$YLfQ5K$y)GkG)ujG~^XJkb^n62i8_yQPI@U)?D4xF?V)H zrMn8MliVFhd~s+929bT(KJc*IZg*8P%p@pn@a7|C8q~K^kPc_bk*bj<7Qwx-qdF}~ zUgKP}JV`jo6bW8YI1>2r!`tLuoqVg1rz-nG$p3?SZl$==xEgzK6wn{v+Q7$DB=K=r zmv?o^l_Y0A3ae4}#fs?edr8vmr~sjcO_>MiEKB?BnA!zUk#p!i0H z7iX5-!S~kf=cW{rI_OC%XjjNu)SQ+`cS`iUM{Z0?R%}74hJeeEBWA*c_^pPESq=4# zJqps5^i^!hE=j$R5O>tsDs|Y=t)ZuUfJmLe0N8qwmLMLeQAl}mHM0&uCnj6AB=k^D z7c_{yX_AS=6-sX0N#){%2a!9f1L3}qS0+>kHu^%rfV>K<<;xq{(-P;yBI3^^TN%)h z8G2k9Ve38kie3~JFQx-1!}%I_@8t#QHI;%9N7dllC(){h#r1GW_RQsCevPjQrlPn3 zbZTQCzGRRGK-JbmFRhibDocZ*c!cRf?OF~6uBmWK)KiX@YxZ$?=qo;>$meXt``#&$ zAT~r?(UmW?RN^~@80-x8ZW;1~fz_Yx1W|$l6wl6pxWS9}P?U9%8fU#0EG#p2+dUYv zJKd-WF14SNiM}3VE9ky-Q`1Rj_hVGw5wffA3M0V;gQW>ZKkKcTTI1(aFd7YW)5qBMINn;PmGTkP>F zw7XH!Xj{q8f|&BwmSuxu&FWR)uP?8aLA0_e%Uh~zVEosW*O%AWa6G0oD5N=AZS-4> z?{v@^?K8C1ASm$VjeOK+vE`Nsfa>9e?M8Ry3cF?~M5~|-&=b4FOOc_zPO<1UH$SZ(WHeb+J0Sxqs?1wN%bTr0?V+d_il=w&6E#& zNV1JjctErRUm^;wLvHo)@f5wFFdiA=pCup`kYaNkI2b`pEy({-A7pq7Jlz$>+Mn&o4Ukuj(vnN z`s7>Og6|@^Oj>Il9hEu6gSI#{ijrJl#8aP(gpwrQLah|f870yOqyz+ik2GX)p#z1o zJq_ylpd^Y`H4kl7aq@adI+B~KXI=N5$m3^SW-91L%7SJ-|a4F?sN&&^;+TLMzAv>uGqZJ5kvsR?;} z0dG_z#3%hK{XWn<$^jmYv*8>&eD!{jckr&r9An|xh@?%iJB`tFF-8|)|ExjXv4 z8VFALs$}fQBFes``XiP_*xTv&y3^oC+gu~83W}Ks+1nGa4Qo(++)=K_=%`K%%(u+D z*=Bsh7q0XN!*US#!qziv05+ycT389z4XE{Y6!JiHS0i`Ops{y5kcizL@!S&db3lBI zbun?G3;jc^^u}k89j8HCFHt<|a;t44NuKw!bpy3UypgTq(L5-ve51$bmzyMNlbFUC zvZ5r9)6)eJrO7M}BwCHk!k4J*ht`(?a>2ng6141}{Dd=7f;g%AN&!FeZK$e7cPo^S z>zJT+G^1Q<1*Tsoi=NpRGa1Vg^+c~4=gbG4xdmC@G-(_93uGsoSgla zhNP{Je!BvyTWogX%qc3IB?a6N#7VDFFBeQKsMg%th>tusOXFOxw)RgG^!Es>tFg_5 z@)faXss*+QgSQAOmw~}hy|kgSwK*=`u0VqP<1k&kR=%pGuDZT8`p~&Z9gC^BoNK*u zJ}7ZCCbl#*)V8=A2d9XoiHh>7%Ic>E5~WJq_<}qe3?#6_|{n_RQ2$6 z=CGW<;ubFwZayRX;VXMK`=ynO4PK$fiHH=O%e>KvK(4W6g`WlG$O#D^N&)9c*0u(o%o^!^kzIk?Q;VHip79% zWlfBDyf!lEk(?v1%MX202j{a+x!41$NF*4+h1U@uxEcO?F-H)U!&~VTLITYnw=gZT z#rxipk>aX9bL8q*4$rXY2rnun02i8b5{E$-S=&!Sc!*^v`GX#lk07k=-|$GmtC4*X zYXVL%zYUpvg?5As7cjfx9ENX0K;dD(*V`+XlI6~B7$26je;`H}InmHDN79t-IUcuU zWt|NCsTb>4V@zCF^96i8^4B!DNA9Xsh*iNJZ=g^33^_JsEdH{N@VPcdANU;BjdMrt z#+4)Ff;!Inwt4XF+-~t~XtxjFxlfDWf!4Tsei6W( zf#z=Cx>!=3H{uZiH6qHzn-$h?OyLOi)_KBEuQYrk7dIK~m6DViZ~V_u;WE3YQ>=uV z_+)lV$b&Y$*9zL_W>Kyjjn!7mwu3p~!5@UO+Fo&Vp?7q71qKbszl)Ca8xvAH{`LUh zzB1THRX}{S>cqNLUnGK@0QP&cu-|2Gi}UDmGfT|EDN^9y`_KtNnvvc2mNA_$yPfs^6T1I`!YZ z#-5aFZy!-kfKqAz*;Xwo{a#O~8E7n5MY0pAYA_%yXwg6N6=*xE*^VN4WU4xv5YkHs z-iH;x20Bh1FTNAhiQ+p+%@g0r>M`Ow#rjUQzQN4?ltINfAg<4A#wGO-vwI0F^YNLohNo|4;!wb#g+p4Y< z-!^rX__m9BGkhCLeAlQaNAa8j;Zp^EhEFkz?`hW8V|~|J-%jh>WqrM(e1=!<#dp2+ z?Y6!?tGv^#?*{AZx4u1Ay#wMqMh!wfMeW6UDK|s-jJWU>5DvwK?|^U^>S2Y|2!#9M z(yxW^#v$pO;?lQ4cr(<)3OnXOcuOqZu>itns%MFMogJ5N6NJx+g~{0vJ~uAk=Mer* zT=)wJpBESY62j-l<*$VB1#$QbA$(yRd@+PCiVH7=@WpXqH-x_%7hVS8@5P0eL-_k~ z;amt`5*MBi;UC0>kB9INFxf?{5vYTiHdx_q@htS=G-H!trhQyE6pI8|?02yX1&ol-IUr~^m(of87#4N~6 z1dwNJ&Y7!hRYpY>QP` zduEO-EZ53E8gPxV{4Vg1wJR~siX0D-6D}FUpr6bZ88R@UBpR`o%z;jr3z7X~o`{{_ zPfCX;%ordGz$4S3S_{W+Rn`rV<7CD9N!cw>Td0l`l6I1XTNSqg@#+9sgw;C!gz;OI zVu*0=M0hCWgnqJEv#3jYfv8@KXjaqs%$4VECFPi{pR_^Bz8Dwe*=<@C zZBu5uY}j_9Wv(vMG{vP2kTnwH$$}iyw~>vRrvOivX)bLaneEa9t{E1kGhLcZ=rV0P z84babSda~nQ_BEjN148toYoF#U7AaWYVRSQU1aS}vbLXewn1@gA0S=*#9JVGV_`p8 zCz{4(Mb^8Zle>|nu(h&fIe^vI=2D#{$a;2c<_3)2OZ@GavWN7P>EJOM5kcVFAP8Cr zddsLlP~g)0$r+g;82hCxhixN9fs6K&Fu0<=5%3^LAEu>=fQa9SP@CE?X*2Z3mVLwk zcYvI!>>^uGgzO||si*BCXBB6j-A~Q|X#pPeo?D!5?j+yoC+Gdo8?UF+0dm1sEc(Lj z1S)oskqM*nyDqh#d@p&-z>gPOsK39PTq5bxPksP@KWqc%(w*e8ZOS9is>@t@=H*ra zKl(ORzudxqg++FY;J`)~Y$Nvq@Fkg7T6nJ-j5qV@BjLT$!h21bA@I^t?#$tLlDf=m zb2!lVy0R2k%09AC(#vqAUFy6eCv>-1u>?OCrn7b7U27uezl+4>vVwG_v^A6!jK@PeTO5_M|S3mi2 zLV`isPuh@~wzpYm?*_C#m1yq)wD;O*?~9@RnT_`6M?m|Fw#-oGjsbGN#ig3y6%KGG z^^=`QbNJiUPaeqJ4W#cU{fQ7+V0!?ppA3uu0r(ySCVHqHRF1v7$;0XNVczacHyhg3 z^pu8nEqzo&yPiI_p`E6WYiMWbxee{W!RewR%9mkc!Y|e}WWEOnW)cs_x32KItbAS|3_5PfLWCm4tDtV(1 z6G}7?=~R6{kvR#OEFce)$%GC{fzSN}ox>pg6C^pnq3Z65Ip2`wPy*DTUx9BnRri3v zckd_Ke!>sotS|s`0sQ|J(GHPDsy-MEeobiBw8;m_VyZr*>?aeTWqZ|!ErsX|qQXBy z>QGU86sCh(F)AK|u7L^SDwxa(8;|rZlq12ghrna)g~+*&*BAGbU!DNY6KybIO2y#5 zDETspdXVH{V^R;0@l<_ei2^B)MvFgzFoM5HJX?KOZFf3bu=FSE3e-o)Q|->Z1=2^MHbS5g>>W~2&GdUvix>8c@eU_ zbi^!lP?nc>lUE?i?;uO+@LIG}6>_MOO(6$543g3fNK5DFOTjo(1ysM^O`$?E{}M<^|2C=3G__!H>kN<+s$hhrIk+D+a71aATY^S?j<)x~POwVS*R zSpGZ$<=@#&-i74%5+q|C{<53A56K@SNJfhObvOACl0Qn+M$M{XtlJW_@o&4y9zgJM z0y^pz9Y5JkJ_V4!k5I$Uc9YK``HKX}6q5h3n|yh{#jcO5zqC027#Qyk_~s&q|8pn# zSK$NX-vi`750bAOq|JtZ!e(!!Tv{&5L>M8@?IWY`(@(y}Vf!2kR2j9)irNMK7iH9o zR@95&e?>;UVnw|I{#Rwxt5(#j;D234y>3Ol4*oY})EidR8{mIiM!juCy$$|%Wz@S? z)Vtt+Uq-!eMZFLH4`tMcR@8?m=VjC$D{2q;Kb29RT2Y^Z|8p7jxfS&}_`j4#~) zS)@{_R4V_Uk`I)OImz!I0&=Ka$@5NRGj)~zeEgSN08j8<--j7E7x>|6&J+$Y%!{SL zvhvUU$LJ60D*ILeobvU_djkH;LM>6m@wpLX0be|*7h|)Krd2;bk zSgG}0rWy%~ybN&Ck4y9-JxArOa7_5g=&(tb>2ec1G-poGk_Z?61>-N8;tg@&{#mRm4NYOJo-v6oabTZgxSOS zRA-wy3zW{lq&9!|u+{9NKk{Bi87H2_w(k_~(Cv=z=H&<~Olatk$DXA-W337j!(`uo zUGP!i&Lc-el|JXf74_qJRt1p@t&$aTaM4LOR0PpTr6ti-S9L0-r<9lqRd@fU(?_|X z+^RB}EMzoh(BTR+q6PKVnh4*+Edi;#QgI?Nv_=20^OBbD8);6SQM_jm{IZ?EX@)k! z_Cmu1Pq8)wn!*^(2u)F})n64}N6M3QpcNmN8ldPHJq7I?juY5Y;I~ zGKeZ}N$tyHDvZ)gOvw&C>0r>AmYj2*!&{qThRkNWsE0P`XXK+iQ6qzXGwHIh!`91b z+0cv!(oM8}z*;sDK;Oesc$#w7W^ezS_aSh5TSvwR=FCrq=eun9Oum{d$<{lCGO=2D zY7_!0W-n@e;KQUvr5YY5PElN;IcS~|N-o;p8 zBCbUGN+AFYa7ETT)YkGGebvsy#)z|Hur*&lArVnI%EQqZJbxSjxZAv zuwkRrN}5Wz52n#TB$tSsD7>nEMz@Hfu5>c6NWfqe1U@Dx;b#P-8rGLz{4_8D=`%tF zfHY)7C4ErO@Ey1fO%3DFB34TT@MJaas@6x9TccABD==G8_;;$2u^Z3UEN5hK)0)iIRI{G{^A=v}DJG9CJ!& zQ`tD|hEj&26hT?^$?T{}C5Ez2Y0ljgpm#ZqE09|WPeB&?(RRR-;q_>ataNZkL;Unx zf(+VE$bA<=$c`Yr7#q30P$UZ_^C zfhQLO9i3k&SX~8XO|>U*q_qUbVTA@M)pswWnvzexCnI&GM%)sWONFp6g7?}b6kG9k zK~Jvzy>diWQ;`pRQp9F(FiVT*tf{|;K4yLHdOE-WMRSBpUZ|s!;l4DgjNor#UaosK6;U+ z;XU~zv;l5i0tS(-94dvw+L$cIsFp&vP1ISgAa`MYtI8#+Hll52pr#s~L(~P{J~ghY zh#`nyJrs=tEaCfBe_f2S1bFTxlcgBWRMwSz>X1&TtV4+~d!Y3?0vrkd+N+cQ)lNt~ zom8ehRW0OD*}8AiAg3+!CYXqwMXe|lqipyAt+|9WD>+x$J{$gsB%|bqn$wmy&-rO+ zLHVX^uLiFwjNwgj(ub&RR9AuFp}T<)gk~f&U@4y1U|45#%}y~73M^#+SZ_z?V8M_| zo(4zJQv&8YtI_Wq9fo;BdJ+hKhCF!2(C7>@I?U`e4xMS~oy@1g-IyF>bjwC6y`fTS zPaY*IqDk437`q__rRxQk+`;KHX=|dS^0@(0Bt&MDunBJzg~*l1uw}<9K^B4eXN-_Z zUE!Jd**b9!Acc$7>%s;TuD4e%$r$fC1!u^}> zT%B&W=np!eLtUx8r1T2$8DB;47TV?K*63>2(y zof;kD-en4Y_-)K2{K%`?iBc5N5d?P11O8#rMUSh>cX486-r(Ne!7X#P8(-YR%2Dv( zun0cwAh+x73~BUnVH0%zYXWwaRgx-HJ1dXvBPQ=ra;Omsp?D8o3U{H zms5lvhKxHrgns^A998vJ8J?PJGKyYd0Mzilq>`#Ku<{AXz|z3rYsKE0^heCV0<4D< zeFfNHj$4s)T1Oej z;x%=_D0}0^nqC#REzt#C<8T%SfslX8_l-Ea-50L$8+{9szGy2VR0&IJ!}CeW?R!qz zv!AMj5RRF}jh*GCZ|o1hOn_-|mW}^H=$_t*uM@UsKsSitS1;7Tw^XkZgyGk0VEq}3 zDb@ssL@DIV*q6h@x-FI00&@d~9B4%>B(X|llY{Z{C)VqN-eZ-MxKQ!J= z>EF|O;G!G#zJ%xt5CwRe243u;7wZ66fn01z>|)+MwRYuq+0=(XlC}qJNDG)!XBXLP zP-%P*L=-~R3lsVXB-y(jnm-k_DC(6{E{tW75c-rWHsgD^l(^SjduyvF4cehY5(wU4)Tj>OWdH)g2eO3EDK3^7tI_7BEM4E1Fg1^thOOu<`rq|MJ!zF zVqba#QGfgpp>$o_Hqgc-E!<^CLW#`+vfO{bCty|L#bfu7)A0xxB=T}_UNdY6ibWXK zBPVl-Qh9tU3iuaX5N`Qlpz>a0D@hu-wUM?n;SD3CD;$eecrK%ku*7{BX+QcSCY5yK z$ACpnzWs|nT3`q35Hurw=>cg$`qBf}fb^*css-*@)3(=4 z?$J2W5sVr(&Tm%qWm!S3;HWqkHosj_AJziYlsJX2DoL^~udv82(Mq9CCjB{{SwC-i z;88J2J2?oh{#&lZJu{_P#LSc%F-Y05%JrD6XMyhtBud>f&vyqBAQRQZvY#3UK91#> zX(M#SqM7;-(LzKHpEU6Gc<%r3S`)w1VUMVhffg?vbNDIl z5^^gYfy#tlSm^-W5oH68dQf(ZZep&YN%~eA}{pUtF za<>it$vD4dl!o*G8DZ7(t_Q-z-p8dt2WD=?>fZlB9;e6v6BgfKM2XKtBSCYruQ=Pn zmcEr{&j@4dsEw))hT@m5gi~c*R-MkEW7-n*r^%}B0X2H6Zi)lJqR(|Oq)RCns#W`a zbOaxOTvSZw1gPrSUxqWB61-scS|K!=H+jn8^>}ZeH+u>UMA>bre?p{K{ zu}?UGlG4NXV)WXiLS`kb^n!>LUkMkk|Fn4tNd}S~ZL15?;0IMat*hbex>&1Os5~9+ z*}@iQ7oysv!J^ipOc?kJT4sSNT7-0Q5FWFi5&21~fNOvLWSNz44J6=i{@_58*E(B~ z%9h+HygXwzFdW_a0P`dCJqscPP;~+$g&&kdghq$J#dHi)&&({`l9{wE)zBMJ1(;@$b36FZeTJhPiT1?VTFyOXL#|> zGk5V&6K@oDqXzJFGG&CZgq2j5TAK)J@L>kIJk!uX;Nb*o+rWU__!A7;$}A#SoGm+TXc(mh7dQfqaqKI6(KA}CCAxQH`%9BsBq z#mt1Hf?T!C8zurm@}50LSYw>F1VuXFz{%9c>1z_$Z%)i_6GzQbYUOOZ98>yg)6uo2 zJw{vtF+&nOUAX`w<_4Bw505HUvSRE*nvXc$qU>5lf4O7&OQEkK5fMu>xzcs13846F z8FjQ9la4p$V$csd{U=?w1nRB&Pr36Cv@6zTU_`$@V=?V$Oo0+-lqYVefUP$^Bcu9q zSj;jSUPv-o8|~+M90)3kT_!Vj>dX&|3!5n z=zz)%&W*rIPJ#Js8}(U-%D-qGPQPj8P;YS}s=`XFQ;g76nb#CE6He1^FdToMxc{d9 z<#KkqB`I=pYQlNEAz>kJV;M{7nRB#j&Mk`4DGFOz!cY-34$d?sCLKMF2IOkj*_h2{ zq)RIX{>UE4TWt!m9;vY$zL3$3bF6h-&oDmZa>m&O zc1X4(pPvNXN7O)$cXH50|Ix3$bac~=ldi2b{YAg*9Iw4BB)Oo%RP{+ui znZ(Dh>b7?2I|?4a6yr9(aiR&0YXT&6G9bw2XnvQDjV<^Z$DsPxJwRaSY|QziW2=CU zSH{Lxb4AL?DrA)~g3S7g%NhmkG|*5|xGO1487o{sDA29mY9K0yCaXCO5^k?<2eipF z!Y3+KX6(=KiNuy^Rn})LHkzBz^T=#E@-H)S(0V{(hZJp6G|&M5xHPzjO|dHX+OmmO zb2!~ylRv^G@L6B-dh=sGvu4D50mvKd!K!k-FmT31|FpK7jt@$Phos;Wz5k=vMdj=w zBOC)b0>?Ign}oVeOZ?Q`;b{S?;%D+e{@hD?jt%=>;Q<0Q5!?1JWR^2BdK=*6md3#a2p4BqX|8`R^%2eqiJTP`4qmSY1N7b8QOP)dDGxYA(z7ws{Es4b_gYA5?QRL6wf;cVcL! zR*!guiha?IUGXq5EHiE?c;a5A!OO%&`oJZJkBVg_goOvmmn<@wd&@i)s&Os~ixfu| zwt4k4+G+DZz18NB8=y79&RVBzl~*$3lxvCofC0uVz$}|Ib6M6N8^ij*ErX=qI{*Zs zQ{B1Npx&MZa^#v{XxTcTrSs@NV9l0G`v55A+dsQp~WJ z&uBlX5DYrA!{SwFeEb$R>5 zhx`$1nZ*c7odM!wTZ;LakUAR z!{Xkrf60F3nEfN82RHxFJh5uDNii-_K)^GV{L9vymXv`bxM3p*)0Yv ze2w!-7lZumQ2;i&)j@&uoA*)+G8C z9}+-EDa>A3%)&R?{rHcVs%GZM`vSy&19@*RLJAqtfdF68Ks-~xJjTI$m4Nn3garzS zK5A>9oS=Mjz-795S2|T}fZ>%A1P}ZqG!96HVGbG@+P|*4EX_`YrX*f+_Gaoa_*f^Qd0Z6?;=T%t z0z&+o%zNesU>AHQQRrrF8~o z<2>|d-oRmaa}+E4ZfzsN<193k(?d?HMH>pygHp7sX#oje&E5xBhi3KkNPYqd_r&iHw>9o^Lo58@`Oq5v_v&TBY+|9XE>ajXZ?{thJTZc;Z;8%h(lwYxpE!!&W zz=HSW27JY`JCFvnx2WH-VGhs*kC}Jadv7^5?uZKPLw$ZI^wY(W1t-Z=_i(O-bywOU zf-Kq^QeS(*eMN=%^Uu4cs^Pu>TalSJEDg+voF`{U!Cp<;rmdA~L+sE)4>W%WOIhY( z#1sw7=#-Xt3pz*mx_j@9I>~L)y6vL0)*yS!wB4)b4PVf34Yx;kL#V&{lwoG!`|Y1(BOXx zIrNWuI%Z+^Y8F%)Sw(&OJW*oafS2))qUA6#8JhMq!1RuGZLcngzXV;EL4M;E&F8L+|JAJ7F)tF^tGTC0=cbim zk}Pj49mhe`S2XgrHPR@J=`8b3_svOi5f47Xpn4a}Z&me^SHv1NwknL_mHhIzhxBlV zHIL)dGBd_ml9f`e7o-Mw$}lqU(`YC`w9u%W-v^Wa4RGsnz(RTFZwL@WL($bo? z-t*E(URr?*VYD;RO(dsoS!tz(-58I>Fm0_*X13HsLFov^Ap+*%c%uheu&Agde}D;+QfF9+&85?9DZl+9sl@= zhvCGxA`CDZ!aM(F6E8P#Ct?sIvSt2(2=>ajhH0!grwzDO2t!Sf`#@3+Hw;HQsiu3C z!xXaOr1(L+^1&=P!~)vzi+Pu?H1&RfmaXTFdM*1Q?D9!1dxz2imbPWV3MFt@ z&-I#3uAC#zj0l7xPXKw}YeLa=KmX~`cU--3^U{LJxo?i9V)#$rA zi|eiX${@Q|FJ2w;EvzE~>DT&NV?Y7FG;|l}@rA&z0imFkTd~W8gCb7t0{r<%E79z4B%-9bjIU9Yot)qSkQk z>fQSs;#HQjj8zKt2)5diU#RAW#lnZ?@#w0aCs!%fo+)}cv^eAbiKx!()u4h)_$`Ja6;SoTZcuETEbG?hie4Ay~~G8X1H5_4N1;j-=2_DC}3=)3L< ziaKAkg>~ayRqU-4M8OCvA{N5Rh+{mUh6w0U)d7)#L`Ch_ zV9UM0y<`csa$H~|K#@<|D=AO6WaGU_wvJTf;!0&@@$w3P2KJ|mj{u80XRl*G)htO9 z1WVL4HN0C<2yHh~V}?ny3JH3o?YNi#5v{R=?!G(^VPh>Bu`)i4t)ah0X4NHLX0>F7 zamTVpiw{EFB~4XEiPA(%OR6o?WK(zpASBMPY-zFzQsU(pjR9d71Z zU8RF9<@A)BmosHUt>w{?-v+EN#j3}B?q3u=$ps9F{9?V}?JV=TbB6Mxp; z&;foC;jNE$6xlKR`2)1ib^T?zmo6}dlH=Bx8zqbr-T?6>3>r;QuY7q21uxZu* zqk(TD2D=V@G_Xlsb2&)5{4gO!EG^c7A|C0?bvl=|l#;~`?gt?#e|O}a<@(2-Xst`R zIm5qRLFv(s`jCV=@_3YM43?8nTspq)6bcLQF9UWVjk?y?|9@v zn2==OQZw-Di;*J11=+?h+8*lXO4Q7iEa zDUsxw3b0B%x$VMj*xdfKILVL#T{Vo&k9I`=fT2L)w&Pj`^@8r`60S1yLv{ZJr5$K= zq~KJ!YLW+m6$^P)bMz&VN%ycxFoT3R3Eh(Qvm^UXWehbA77EJ!@`)0*bPG)%)A?MpI1yb22#l=($YL&xaA=j{`s)ewshbBkc z1oGAXLJMk8qT=qk{1q;dQZecOp8ZM;raPb1FHo>`$+s+m>q+u*uUI$4o~G|zBU=|K zJ9h=VBaN`BN+-Cc^5&i0L<%I)URLU6=)O~8tQ@oU)4#T8Y{%Dy;-p$t6(mKG87`^bx@fmTvA+dSl$bSob$2+;9t_z~>|i*RYiHnI9?U+Stg!46= zo$Gka>Ci}j{0`w1P*J({6j5#{v$*({l++O)UYNWii3Q|+kdZfv=%kA@-mw_Xf8rpb zl8YX{S@gCyjgiP%Ct~NK(VN>y#ef4`HHB_KbnLEu1gTJrLmi2*(@}k;Cq69R#kux2 z(j+_(`Ouh-35ce_H!yT{eE@*xOzmlaAJDm=IF9BJeF4zEEn3EAEbG2C`nzvf;v- z%YBO7U@4rYW(~5Ww_fxTo7UtmWzvA!7)LoX+k&MVJ$6V_c?eQFoSh3DXbb<@4ICuJ#{G9rw;Fg&@V?Mhx^@h=vK4Rd) z4m;HPccHF==3!A%Yuw+RKdvDy9g`0r>oe!6KYYqdt$X+iBQU5YMeK`obsa(W#<*-+ zDz+ghg?Epox#d;`Gs~>Tgyv=#`7ONPk>i(?Z#34VfQbZ{L~n1TkoClrJH925^wQL` zrjEtVWfhA(*547hH)@p%L&zW|e_$VCJcm!S#Nn(Jtx9<{cO|5PWHdT+%cXjuF>OTY z(~%0)OLJ>1LzxwGa?xRiL1BcleO{~V;Oz>y{IjanwbM3-QB0_#qyX`ZeRP%zzVInO z-vm@r^g*Z7e1_4@1M~~<+=Hr2M$CynJZ&V2gUzl0ac?R%m|YAryEu@!#&4x@7#xzG zC^=VD<#^ef^jAl+RLE|e+=sl=!T5XZ--&&TF#vWIEADHmU+0Zf^-Ajkdtm`9@cV6x(-=-R3Bf9|htN+Ph>+o3eN6U<=-tSg45!&96W zS7p^G{7JP%o?IJ!jm97x6^+)xopcXj;XO`~Uxw`32Y86?O}T!a?ykqI_AdM8WDDPrrirli}%a%IFW?>_JNOCNz`@?Ez)-Zr! zH7QElA;7GIdJVI?RXFhwj;^(n3BS2D8g>rK0mws;RTKfwq&^5E5GXHc!j?H2Sh(ui z_RD8)nywRe>(16I_dgggxvQ@d~(_=VzR;0}9A9Zl1D?o#}~l<9cQhA&_L`!TFZOD)VC>_Ne7BZwNs%&$a6CvZCj7t8sx?WxOHO5=W zy7+5k>a9*}k-7M@=dxp-OWIyIDck|e)66KDHvs!*R-IEN8MIZXr$CPWbYGD@~A>t11dJ>M5D0}cOI^uq5UV{)qq#q+Mi>30v~Kh2fnvjPp$8$WVp~{2Wl>D zz+-KmV_tMmactSPS#byB!k0PCm-5o{3f)s<*+Z`Lb1tl__VjVCSzo!LmzctbUy z(a7vV=UqNFgNLW8QNO&aTsiK%sP(9nRk@RY@-ZtL-8lDXYn*!926?Fpz>7;-NOl)s zc9vuykJ4jH>VXuLTn35~JN}5Y0$Yh^9+rx3%nb^RdH|VETi3 zh_alkYx7BVbs?sh6|ynO{)`K6{?-L(2U|~8}{@z|E6~J4Ousz8~q|YER z(mx?AwWnHOgiq>+v+1snz=W8MQE+Q;5lg$#lMOhEa=Pcl)qb{uZ>Re71I@NAaj@{W+5oKdoI z{w=IEY}jyq5{VvB`_f_T=OVDx7im@)3VN`!0(~n1p%d_=4n47Y)5)WZizN#^wuRx} z)m(f41n0yvo>}yRk%xh7QW;9glj>i(;MY_AZ7{Nv$c~6@^luPf{EGV80NX)ObvZKP zzh(0wTOhmGHpN@IBjokgp8RP)bUKNn=Gz8~4G?q0&XVoQC!HaQtymAW^YI|#Aix@s zV2_n3lLkZ+?iGanG>vU3_tvl}acpL|QZ+}?CgG)Ns*#5APIh#O!usOt8^C=tKq{$y zaBqL$FeR|$Z%Gsu!2g}mw{!G~cI^1QeSI`LW2gk%SwOr`*xk;5vyh~DE#PN>d{XuA z?L_(rTJ5_ub%YWeqm+#ck9(fQK*6U6DV`jHH5q2rigCC$p<>~{2);wcLtL{68Fb|+ z2ML^gX03FlV6ZFqR6}Bai4K>4f{Vd0 zlE6dCb1C8;Df>9~3OZ^^mO?3bYkolC4kRILsLAUAO9AMFKg|kWas8#Gi78rkD|G6l zjnLjs*F@T)F7@V{ZO^dR#xBut5D*hM%4?xyj)d5p0h=9ZrHfh0)=u+mKCPbkG2GM3W3f zx2xu~CWiF6;%kSCw^3@ZRJ%#y$RdcF!VCThYmol4DnJ9)0uIbUkFV@-x2!Bu9-3Ke zsAdsevsCTZ(!aze2ITCa6FP9V`+@MC1h@GNKm!lp;uYn}Nu76!3-&x7KIeJ%VJOdW z>TF@k+TS94eBDZT@43IoW*mdevbHD@vaOJXMZqaXcmM)E0zi@<{RR{(#0WQbf_`Eu z7eXOwkPtrT$9ODqIk|kAC`Ou9f38K)XBGb2Zm0(*5FZN$N ze3wLg=%yOcUKF32VlZN2zz*4wk@)813cW~rpOR~%v?)JXkml*_Uy~HFqw(H70AWLv z)PjEsE4J(z1X=g3pK>j)1dPZhDt$NmXIXatPT;BE8@C4gP_rp@5Dr@dP}-?;v{f&F zQoVj1L%XI7n~S*SC~<%+zA)*?`3W4Pc=r-YOO2$xUwUKNtCOpq3vV7`7WP)u%e?Cd+B9@q0SJTU1htOyMgU_^Sn2nm}3>D`w!6kTvSKl1yH5Ek7b58na4BLWfN zy(IS~sBeXi#T%b>K2&<7q0+St(QZ%&V=;Q|aE+oD1lrARAIIJ0l<$hL3|zTG24GiMjdcYqjiRsu}ggcuPlLg2EDPMi&yVsT;(G61!l?V2=E zp;l*s{@V(|zH2X1n(?Uv z`5fjFU(E;$-N}Sw0eAc6riCIHcPg-kkeg=)6=S# zziW0}0LP>WnmBM}>wEL2C?-!CfX0HM`1AW=RbPL=GB`XESBsJ$ZN^p)OKmknk`hL8 zz);NQ%-_<-6gw;n3*h1F^GrdO=CF2L8iji@thyD@I@X2=IVy^i05$(cnc55rYx8vz z62pAoNqMbnZH43T8c274?p1uQdA!b5 zeq!!^-> cKrtRZ7*urkzD#TD!B&yXl`wkO&R;`?3p)O8U!Lw+qzm$;dmWGx`E0i z1(_Xpg?(3I$aC5vEo><{xGHC*^#Z zdfF=wX{()8P`V8xJ0ys{Kfgtw@^X^ufq9ESVYt6V{L8NA1knqEpIZ)7y|;8M(>5`m zy-~?Ab2R8Td&IviZT7Aw_Z@2@ZWrJUB(;aA!|3H~;&i9^GhGL}(W~pkCxwI+k=(o#5Hg{F&}o`|eksMibeR z!LiUcYcnOvJNb7of1wq>Ez$1Qd~Dx=VEUJ3s}m0*zVdNab~g)ca_{nTUr4Y+uqQe& z#o}IS@&n_63aYP>J+18NcdrWl+8j5uAk9KnNG@px9HBH331dK}nD9u%w#A^bcc=lh&1wC6n#@nN4! z#VK?w4Ql^^yJmttil^sxzVu2v={tWcO@0h+Hp_k#Ai8d zMdaKbTbHa|lg)yKD(H-bB6Q-u$Ncw^pf+mxPdYf?Q34|PzW!Ea9}m;c?N0j5j>`_W z_VcU>@5xQZ=&-T*gXHKe3ahdCYP9YIi+8r=J6RcGlGq7^3P+6aD3GU#VbtlRfmRP^ z+el2u%+qdgl1nDojDRG~+@`6rM@Wbz@>L3YRn0QX)Trfe5_=rh;y+k*D_;VJan;?< z1|%WmXsg}eLVy)?>RxsZyZXnZHv3W!5BE1TeS>Myl|ttTdU2pyo*&f&Sxv>3!41PK z9^>_{Aai?A&$#u4XGM zAhb;*^J&)mtn2LbEkD06kVbzO2h8@eBP6k7v=L1#VKlX+kT6rMcKR|y32|uhW`>%u z))-H!vX@-@p+)#HYDC{c+-3RKH^em%HT~|vZW#nS5*WeRYuV#4%to-tH_vc3H z;v^L1WGX#VakB|`>u<6lo2y;T`76u^ z)4BG@PPgh#BUGXciCRvf3-1=?X^6g~G}etXPd|}3eyzikvEq<$thmQ?!T{13vO>Gh z7_P0Iy`{(KJq|sF*sMf|u_mX6V5%{yHI|-Jw~*YBbu7Ea>3G!vEn9i|X|YnQWhY-h#!gY6!{+pRr{ip})ygA!sf@np;&;)^UbJt`9;jZ-kVOdD(G_ z<=j?>McU}rtxu|}NfkQcO*M89bTYRIyM1sG6RjM+t&m>IFkWUDbU~P*ohnZNt_yo{ zfLc9LivWf#g=OY&{fm1GE%48bIz!L#kDv+uP25W&Bs(D1L?5sVK7r^kKd3KF zk?>@svXu+NZ71vfn=^DByd>v-i~K>sMu$P?N$@|4rOp!9Nwis4(^u?Gk2BLZ&olq- z&9*k3>7v{*WK!{WuO+@uU z92TNx2BIh|Gwu~A_7J38JZP?+PkL6Eeg^B+(|@YGhU0fxZ3h_8pIIlQfKK3y@O-Tw zb_wk%A7*aP6)>q?%P0?PExA3ek6@wr@CIKKq^p^BxilUdVk6_ItYD^oD+b5Fpq7uK zM^g6~-#)dXEYuyXLrll>9+>ecYPko}Rx2QkLqq!J*Lqjmp6li$1asUv?5G!5e5kR^ z1dts|8(^wlV_i?x)_xZrXY*)sn_(@zM&t_u5l^|6GeeV46z;)Scr9NV*16>yM%nL zViJA=o*cZe4hHB=Mjb+vK%hyMBvOzjMHGJ*`+@(hyx)G7-%~&SL7vB(IpEI-?8sdm(ANa4-Wxmoz!x|CFJZr5O*_Gouz@GcF9T@KuPSNkh+V!b266kQYpC#dC)`vL06{*iv9lJa{G3nRz}6ZKwuM->okMu;3_=h!q9uo zOoO)7p=^H(a>C$x&yH6J8+qLBFBs}NJvj!46v=$J`+UN{;|$@0wkZpzU(Qe&NsQ9d zBE!c-uaN8U1)<6-3)w|XhZJBy3e?v^VMWC?u$P~Dtw>Q~hG9xJG1#hdsxG{|cLVkn zLs3?2Zq*AFgIOODLWvLfMp!Co0f6W+mTzK3iXMD5VZ~8FY=kt5VP2>XmLt~bvAtUq zvN;Bbn`6Xaloc~_T%ncVxfs6uN92QWVqPW-0E3?&1FQHCen13&Y61BZF1Tb&LB#n`g46L>I}w`O^XTyG`7Hn)bRA1w{lz|zi` z;8^cq7OVDK#7%H9X7@1{icRZ6U{BUr)kg|L)Pm8v> zS5#CKO7=rw{`C-nT`TK06!ugeVdV%;qgf;f`$$pKG|!nN>sU{Gh_rb~MX*u!W>~D6 z`ZyExjNhqi?mo0&TW{>B=dI}+%%mxfiZ%|uoH{)+_tfi0P6n|3)A)5Sq0*2+TN3nA z8w)_`inddtC=0h1GK9!nnDD#qj`+8OW-3aY3eHh%1Q4(&x&EqKWEAHL786%XJ$ydZ zRFGRs-?2GBrH)D}9R^47h%tEk0SXHAmBEc9bAQwiHH?lqKS znl|zna)eMfLAFGX4FwyZz^xa)1b8%J$7%&)I{&JiV9Y&fQVCF|n6e!D>^R>>EHwFL zd|v#Z zqpIAt>a}9*)P4B|BbKtA+494Yex%7L##;3UyU@%}?dvHRu0?nl}bzxt%S5^rjMwov;s zZ6Qgz!EaOcRXwBv%?$#-Tx!jeBqfiC?TAaBE-=>kz;l+W7n0Dj?ngLhgY= zQUSF|z1jVN_uTkNHLHBNO}P{NlNWp|zPKj*CJ3n)SjW5pugH}_t>VLj1lyFz7Y#4{ zhR8Ef6_+@s%9%>)=VB6nXIz~Igk%~uQgeOEXSB-|DegKos2wXVW@Sx7s;bXgjjM}8 zv{nW}elT{A3?2Vpl)Yngq+Pc)+_6=$ZQHg{v2EMw*tTuk>Dac}QO8bq(#e;1Jm;Km z+|Q4LG3xqRqsG40Tyw8I*P6R(ItNHH4QA3;0C58$A?21t69fkb$;-_bDh zM7)3@9|c6cR0qfIngRZZ`3VUmNJeTDqrx-W)gvp8M^aLe zPC*o(@;gx4i{*g0p^)$S>9=sNBXoHg=1KPoZnE7W1|rUb;Ri#2yirEkS9D+PL6}{I zLu{D2K^y4m`ka-FPZ#Jly}L@kgFuN{X8;A49$yF$Kw$+z2;v-Up(%{Mb~c1-{W@Yn z{z~O5H)!Rn5EQ;23@X5Q0Ebs;F!BKC8y{NJgjkb%mVo*Csoi+6&XLV(cQt@N7$XxU zCb=)RexhMa>$AK}W{QaAD+i+VD?ZM?sPCPgr~IG>6V1WP?Hl0ZyGH%}ttf&2@Z|^A z9Na4^OhA-i@-4e(?w~Mhm1@-t;3H=!uzeXt8D@_4u*zKO74Ds!(urOsbaUUN+hlT_ zlD?NRB_VSpqH?!sDyuST_5zfZUrxT8Rs<1q#|@5F3Ru~dv8@tmhd;p252e#Zb)z}u zH<0#+$NKtyNLl8cCOxztw4#>WbS1 zdcQK7x0<`t+fD)?s5d$-C61MU-FH?-UvRAcx&IyNiwGHj<@k`Hvirxj_sAB3b+TcR zcgdb%-NwDN5Lyzx9QZQ0bB?D$(Gt71EZBH`pNjIQ%kXqV2h+A(VsB+(RXTau#wT#) z*Bpbg`V~d;W^Acif99;76Sp;c$3W+Gd+=8GF`N&)5h}enJ_*A(@w)l%uu91d(e<$9 zK$zf3B2Kmzk_d|GG2(SdS#WNNSu}}tXZhSlV7Qd5ijRqno{pZRtd62AuNtSZshQVM zDw7ycR?V`q&;SO3Yb7KT2G5AuB_pYN7ItaT_8rgI1IGO18G;VT=CWz%;Sen?YZp~G z@|2)rQF6$vQ?H9M4d@yU_}CZXDnhMH;-=FiQ~*bFmlL9y!(AmUD|YPWoQ4yxC1{R# zt+*&{LFLSKh^qQD0LsU(Y1VeGC_t^JQw907CK9M&x4Bx8hn7|Xt${ujs0)A2Xf=;k zx$1k)p+Uani%Wk3=V& zOxP>UlzA_kzPN$u(XEPBP9D0v&qHPkOzTH)`dk|yF88vcQH_g=*7DlWkM741Ghp5Z z{u+9y?0kY6WYn8?j1-|(R%L}c3i?!b&9*3_4qHLY^KeEc0aRazqH7a{m)Ec9EnH4{ z`CJ65jeK)z9kk6YY^)%By_lJJIh8A`dLDWWKP-0`eDlo+;1q0~U*K*;b#PqyD&8g4vA*(rpD!K{J;7K0e)HQ~9_jo5!e--?6ufh$?UJfu8`L4!LkZFlg%KyV(XAM+-^ z{iGgu$yX3D(((t5YKs=Q1Fddi6c@q7r~3KOOMFz^H#t?*ugbYNm^v(w!64@~HE$@_{|E%@RozC0^^MbMrz3%$nh%>|bn3-|B_1FE#XZFLpb_ zs*@W&VRcX%&2arx4EltBg(d;9854SBzz^rUnJiyw!Vub>^ad$&hW!OD1^8>R7{4c5 z)YK5=p$Awcta!AS>UT3~K<`~k7Q;hp9qGpAa%>l?3ev z-f9KQL6{LnzXMcK%l!UJx`P;meHYgvt+hq2e-%4UtY5Za%lu@6F3S@rgSvE^;};p9 zOfins0H3Xq6I8oa7f-%xH*=&!1t@nQ2==|1naO4hvI$bgFPm^3F7V(*kX{!yVRq6< z4$0-%0y<6!hwV0b!MZoTKdw^G_q6kzVLY1Sa)5NVpZ>mQ5W2vClPRgJt1iKkav5Y> z_IT3;&I`?EouUF7%6)YFXN#3CQT;^hsXLvHUXS!%8xIEjUO=UqnwC-!g z^x2$z?`W}6IE01&(7l6vK)==gRz!AG^#g@v7~oN9Jq8ISSi@x^>VH`M#N7Q3fUU2G zb^KtVgW?n>{##nV&w%V&{6J_j zZ|^(YtY4wYCEglvc1PTEqf_Uy1ijFCNhWDU0p6pMyV_hG@o>QHS<%!1w z2RYGeipyU;&yh}h571v2w3PI<(=+me11|F_UKrYz8$5_fZtmHB6uF@seKI25{BE~w z$r8E=&AKJOeQy88Bv|b7FU@cC2I)R1Mg1i#$)qAi%#7?oWBz!t<~iPxAf6Cwmlwt4 zUO%`BQq<&y*+FxY^c~dVqTz$o7wP?rcC2#94C3LMdhEqT?9JkmlkKui0pdSC?4P2p zzpe~2^gnNBs_>!zzyXdWLP(FLSh`9 zC-WntUQI)$`bWsxlG>t~#)O)5M}P)(X~|o=veEv?7G=)&54Asuq6#eQCw@?=nczJS z-(Ns{P--6gXhCiS#EnkWH>@}?HO&vVstXy2p?9H72r6stH4>}2(ZryIgEyV${7pOVf~Jr&wMRR^h+oE z@01cHX9o*sGZzA$pfpDfxAwowm#4&9FVIBlWk&ZOyn>$A91?XslWvI+y>lSt3L z*Fhs;{ar9Q*Z1gFJ_tao`{#QRx|}fG`FbbsUurn*e-rrsd@theX7;ZCin*Q2mi>|t zGCwY5BdRWR872b+KxROi%DwAE30C~6=wLXS+Dy}D6g6~$5h*%Od5^28} zxj8OTX%lL0I+L||)1~Lf=ld(fK69R`@(?|`{Y8aFVWIK}zHNo0#(+H73r$DJ8|*q{ z7HkWA3wl{D6i*x0;{utqCmFV8d?A_1c^EwVKyp6<6d#H(UhfS)z;C$0691|YbT~NU{JbuuSu7k8}F%vRxuGCS$v~vrO2@T0Oo)wG5 zpEjB39y{&I{BxM!Eq6ZF*JAf7t0OF&V$~_x$S%>38%89a(t32E(XJ?1lHzxhi7}=R ze5!ZdWoP76gQ`dV&sEmY!B9`PtlvwTIY(8DIUf0A z+fx?mm!GP5NQ$qP44TbqHT2d*MhGKUUnIIHfT+ZRS*u0U~c)qK&n!z?3NcC}-Fm7OIxM;-n&Ly^OW8s`Ls4r#l;W zst?eAq9hKRay$169s6Go!~X&$|0IXd`2R-^XyOhn9IF#^mU=8)Jl*JMXt_(T74*#1 z(w3hy14y^U!>W!>P8~VtjvaGre?bgmA)rn(TukA0^-3$x2OB13FRX=SICCiv!ii`s zpcEA@b8GRveq2q2$U7O2r}VmI4N{$awlv)Z!gGh2(fUxk(naa5B*DUu)bC^n%}UWD zo*q%Yt^gs!q_^4nGRWXz%!N#O0G|KGgAPlyJ`rE(koG^PgPo(Ru$!wT%YQY65~XE_ zgwV!5m+0FhbT0ZB?Fyrj(~C)@?&ga$eUJ;wGh*z>0EdIYPRCkeI)^fXAGTOY=!#(9 zIV?05d7`MkE_zB)DIxP7 zdhk^8kSFdiT>rp5@l1lsJIC?+TyWA`>abp!o13k7=p@I6B{en5XalvEQE)4*~E?s z&LXiRU>-ilt6$yiui7Gv=oO+5gONilG$VrnYg*B0(B7^fkMS7J&kHx?0JVSt8fpjL zNQp^Z3zBh*8E3%SOqt-w8ig(c&j$XD_Z4H=*Y>^ws`b?y`5*Z0zXJN7eUaon`z0o{ zQNb63wAuN%0Z3b>Hn#){h@nw@=*p@Nso|DJxR91=x~J6ww#-Ahr4Gr>DaSx?(Ze99 z{X*n1yJHfv679)}$hn)>W~I(G zt;WdJtH7{YPAz{D)0E+I8CW;{h+;lo4HnEdFAhHXE{hSvHwgBgn57QSk$oOkH@rVr z9`LF9tLgBoN*KX4iq2}MOqNjQCz%s%@@*zfr|_|BRw<@53X1NMKnxBpj)lVtle}P) z=p$kRCoW5u4j#`lN4`;1o-KG>yF{j!CDoH&GF2Ixu-PAWPTJ#q2&l}{0;AG^sF%0i z-ksiJBo-Ph4htnC%@}8FjHExly_-&0R+UY~dL%#zd%XZ3iU&C0S}2j_o2h^Mo?yZl zX`VnWSmwAAic-Gyd<*KUN#BARrq!dzOj+uv8ef5q@lvSKOc_UF6AnM#nhhZO`oCwm|HR3^cEMZ?I9=cx_9r30IXPRlUuYiAx-5yveGOfkzUv%~;(7=hUt8&dx90W)^>EthEi(azk%J)DBC@+dan!Eid6 zuh!^H&6PeM^#LtQ+kQ2lZ!9S#nXld;CwPh@OJ(^5K%1i20z4jH34BXS&Kh+88DEye03)#0=K)VyVlZK>C(QNT8on4vs9V4YinoVjtL`k z!F!?NH&OE)8jj1mwZ)ze$I{7<4|{Hx$gsL%o5HYOcEojXb<~*tj{=x1-iV%+-qf!W zqG@XnYdu>7X}Y{~d9$)2I1;T|n>q8^cC`z+1iDV0xulMkya}v(smjwd0sdZx9BHB- zMTLbda_D44t!FTLQ<^cjiqy)u7TpVOGX62hGD9GSY1N0;FuwetOcz?}y+Tm6w(8j#Tj6dbmGrh2^FW+$Wi5KF> zm=;7pzZOh@>MiBF)_}n+-P^MUM8Hw+k0Uz#RxK{z-cscl$%ZT6#!S;^;eI|B<$(cg z-p~WAem{3$(T?^H3^~h*>jG(NB?2p(s|;HM$>_dz#=KP%*pJu;Scd(apy-YgXk;>Q z*{l=my4%&O#R@Mnv_wi<)363Ox_pGa)wBkZ`$F2m0a$W^oI!CT6qer<+fhnf%1$zU zRQ-sP03YSKBAzyLP5t=YLUI%QndLS}mGcZTzMnBvWJur`a;-*01jQz~FcR+PFimYz z(^`h|m|Z*ux??!P>)Vu8&XLplh2w#d+` z)9cX`no3IqCy*_N0aiQ9MKyIYO7Xi($-gKqM_m25=N#mN3mYwEOGxoHHn+Ez7iE+@ zNmmBxAe1iZ_qNK(S-5kjXMoZuwAqO*aXOARvXW#yy?y5r#SrbBSh<}UGc`M#U6q}s zRxU0!saEGI)?3YIkN4kqx${~|Sy@_Qfp=F0%Gx#2j&&U8cpRgwTDc4c_daTGF<*}K z`{mm!wpfdr`K_4FFnorRZ2oTXi+GPEv#!=s<^JsI*__CfN+LF4Iq2r*HJ(GzzNqsw)>7Ugi?OaCFNlW+qbh`D;j`Jb zrE}G+$U&2wnWOvP*L!k|khR$kow}3G;=^j&FZ#B*9o zvKfRFS7C4hEG+5Z^={b9nHg~<0z%{p+u17EQAG}u?!|eRC#OTI9QUg+>R^Nk&j=?V z#K*Xvc{Jn_jAXNQq(+h1zp<4hcOn4W*owo=Z{VvZ5kH6ZEqV!Ex_*(7#@5j{aE+PR zs+dsTSDOm}C}4cbvgVc>b5Aa0|o6? zPDbJ})~_L>oR+KnpJhm1way z5CV?Z4BhG!80&}=asgJ{APE)+bY4!|Lu=j2D*D}4X7n>ZB)F}_vY*?F_E$nykxF8) zWe(*54r`%{?mf@c@^Q(!Qy5*L6|}QZ!?19NT*{OPHYnArgxq~pFWZvr5Nn0*b`~s z$GZO{eru+AXSQO&YaL385|^J{n~9t{Pv|sz?)0`xtdaCV2d4dcYR`ivsnHsX_UIT8 zwM7MJUPfVFdcs5o;PIf7EkiDPTo9{Qi|!068k!yvam=orm@P(5dWAdhygzy>X_A`o|!y%2Pz5(D071i4P{>dp&s;QS$G zpJxHWVnBx|y%!$q|_>Y-1#PxKBu=X!k0ap;2n}+I%5d z(#s=lL>hrpI!r+qTM~^7lNCnW$TZjkvTLu)Nh*8DtBOCVpJEH#pLFv$YBEo=eI~JK zfKxIsMSwcok*W%9j+od&=dub7*)T_wY#GUuvbRPtdd?H)n=;=h_2shJNUh5d?P z$cak$uky&(OOo3vI)ZsOXMW&8uX` z?o-e-8PC$c0QS4i&seXVXm0R@-Y6#9m{@ZpE!Q#fF7lI41LK&l^Sr*D(u=a7|3HVE ze(+y)52_NagSLa-ksd;D?bxzJN%@uc^_g?Qe^ztBCo|=5UseC{SJhAVKUQ;MRz|iC z7XNjQ1Ijvl$r>LV?$$|Nv4zJAi;`4@CH7IcT#!ygkKa>ZOH}#E%43^lav0oy?oj-w z22cZ_;_zOTBeWiPRPCAm+LO4#Bwnhm?7K%_)_2IZ3E z6&>zp2A?8U!I#3Mr#ey%Hn*Q@Q!48ELUqPUwY$is`;^1Gv!0_4jS$Dbso=?r)nL2Vx!+=Dikps?ww)YoFcaqA=9Qq8 z7IURUMjZ|`&JUH`7|w;p`(b)G&5e-gV#;c|D+iq#L$E_ZG3XOHI-V89;mlYQ?56Sr zzX`;NZ>H!{7h?6Gip#)(vZ|g`jw=0BDe1Dx0~0OY{9{mE z3pC!!I4#*?CT*^gu;1}@7>?^{OBg0)VJJNNZMW;04)4Y48_4oE9JIn_JtwfJol#|$KxIk+ml=(p6DHG~MJP$7poI?cN^q#523vj`(lD)p^Bbki zMYY_OSLb9f$9YV8^g5zenl=w!U=mfh6U$+WG38xTqJ@7W2?{@qrv&2ZIX(Xhq6JMc z$ED(1-sLu+v+2v3i2O-EGe8reR>Nem(wjgu;^yM^+oh_5!sW9-gsvA#m3da<^XJ}e z&MlN>_h}hchg+01kd-L4+Q#s)1njQMXPA8>PtQZ|Z;f-mKdg6m7XFvfNf zAvwvsyv;8HFS{1kxf?$|eqSMd!@LC|TR4eFah77G9MVIez*AceHUybq9Z0=h8&w37 z77GnnqVB3YqTe{0qQ@NQb#gI?D+tp{aMw{=j)}2S-035)F^@3glNG$6PaCG0C@GhmwUTugLywprRf)0EDDh-jldGTchuB(6 zj?GUQp7j-y1qL8`%!XngS#qk+eHI%v)_P(JGu?%?b(!gga7{Z1Yv)Y&N)YRA2TP1u z;Xhh(F~x40%#Y=nyNKyXPUYPC#Yqj2^Rnq|Qs{Nc_~d`2F0Gcek^>#-lv^FoR7vr? z!bFSH>^JL-(47Z$+8*#kM&!{{$>wVh`HEwAc zf8>Rfhcp|Vk(6s`3v(@Q&Qxe7w^I#v=0t{~&( zS2*rh-8R%2gNcV(^0y+N$3IR8IiRM~a5PRkNsg4i#vG{9zv+?#DxbN>FlnxhLkqj} z##vE2wT33Q#L-`F$;UKk`bIfv`bIqP_Nre&1g{&ZeV`O_+l%ZhrGd?`6OpCpg+Ot) z)v9u*p{Ee?>}De;xhRyddDgd!i!}17XkwLk4;s^+8sLN^yNCVa9@dFzmXC3U#Z%|Xq7>2l!CN|iyMk{}^tXMVP(vz4t>6C}JOHYZ6 zA|dfHWV&D1Ex9_2=fg4LFXP45X2cZO@mJXf9NC%ow&A|uRK!`#B_9_BzDh{Lzes3A zEAjm#rpC=T+xR4x_#-z__3$cDOGQY2D4g5WQMo8Q)csM|k_z~Z_P~9y3g>P8@=Gbo z*vbP?d2Zyc5$$|gOB;VG=Z$DSFt5NT^XE);*UmU9&D(}6-y`HA&8l!#2CpRKyefxT ztnq#w3!fI1UYIqk+uSpro82Hr zf@g;T!hn!bs06_*Diaj#t~kboh^VxS+y<9R4sC zMcVlJoO?>&0ci}Czgw2l>|uWoMwKo&5XogMtIhDA({BMrE$U1nmEVUwW1hWWFC3%7 zf7f#jA_qY4iQ@f59V0m8ItaCqTv8VH=3XI791L^>ei_^Oxp|ZaCxpFisSZ>{Gt%d& z4f663pk%mM-wLMtA(ALjJcE$OY-)~SlJGFO85Wk9#xC$2T9C8TrKjT2AR}~$#Aq#) zf46Fc{rhBimH3eq_t*Ai_(gF4>pF<_U&2-_Q1Abl9QctwhipfVZxF*GF4QQq z(rI~t4{>*F!D^}pwJ-)J?M{NqXqN?#aWBqU6h9n_)2#D8aiCJnZI)>hUQO@5mP14J zK_me3vm#P*5U)C`!MzE$?RkiFBH0{MFNRglwlXWGMJZO#K1+FLBrE(Bns0kR52^T; zFUDwl2;7-~LfQFla7Z>Omh!@_p0%u@%fw^9p z{q8-# zg9E(66J%LrAc`EV7z}*ee->=?we`HYT0kE3!ZY ztNuswVTU2CtsKRt-N(((AHEaq1_lb>_(oN5cqmFTQV#3Tyr{3w&xTUs-l$5L5rAXx zQ8?!8nm7b3k5^kH27A#YdvMqk*64zk+S@4C_T#k;_*!=D3qO@Dd!t-r;K2X~Egj9h zM$!DU2^DrMXDm3kj*4O|D&|sCQtO8zOffHfZ8WH7%c{%HLwx9LZWOHquU83BtJ(c~ z8?pM`ZMWHg#xrmGmf;3q2j0)YUF+=Ddx%g42d0WC|KQDQn+<)@+f=`6<*8(g=-22N zR%6ou_NLq`H6M?iI=H$6l%f5oQ)ofg-V>U5ZGX|B9O1VdPDe0Kgh!M9ej*mgI^*$v zc3n=U7zhMKc)k%-(HZ#mtK6D817iko4)2#yY2KwKJqrT zeIxkR)N9p+QMeL0{qQEeD!Zdu!~vPk5wvkPy)MDIsW0%#h4=Mo)@bvVGM|hpX|n0v z%CIWWwiU7{IJjPF43%*V@jCO!&Jk-|XSG(Y%TrQZ55|T4*Hw{{ zGGj_$gt9UKHYBn&k&KNL3=0ki%fbnK&f+I$^(qL!Lo)ddUfvO3a^AXM)^2VDzwrrC z1Ywnx&X;5)8mmWfQ(sFLN-l)|rn)vEmWc>Qp*vJYi*Fph3o*$w2`y|l*PIwkJmqei zVoIFjT4`d4O7l8{-Qj7b;IFs))_row1)n~Mw*RuF3xIuNgs{GcCW_G>i&hIX1KIaeH z4+9nvzo^4^n?}0Iue0S%M0bBgKeGCo(p_MW1lt`su2E?YQVy`jfrQaobz5>X*g5KW zqL&XhRvmKdS9F9Lp*!iSEyRPM2X~Ym^4M2kCE0^=s>K+_n&^!{a&Rxw=pUl5sNLb{ z;5n%FjNs&oa zaXFoJ!}C^RP;Q8qK!8M`b9-UP$V@KG;uSe1q@!c7+smXYxvqurCPoA%?=(OHmul^e zI}#O(ZDq4w`t+}vCT))x!pNM!N6&lD!I)wA2+~79q);t<dXSnXh)`nTU zmq8z64O6OO5?jUmky*K>-XKo1?M+Z&G=&UV!G>wd99en zjw$1u5%6dqx@*dW;b3qAW0pWkPrNa-GE~nXYzRg4X_^dFO97^u$JM^COrT{-FmR{Y z>Y~#$-$?E9IXe$Gchd2$6TlpngK2S;C?tdH&JJs3gb?|iBZTVnqNjyoImdW|1r=tG z{oI!KL{ru)Z}m&7KkR)U<}uF)xn6OFgASKLEtQWIDOaX3E6-3vyz31kkPEDxj9#|j zX4|hBPY8FcC+WOw3>@+kcBhIfT(z)I-RXI9H^@p8F`Xcr!)_@XlndgDhZ_c z1l<+3g<%t7%Biv+{X8|NC

Q+Ow_*=`YRZd|eS(E=}EjRTM*ss;)_Ob}fN^N1Q%7 zm}lXBX%u;n%B}VYi~1Qjx&0?x zk^bJ7Nyr^Bo-7mKU{a`Kpz>^RY|QlYYt|jzVLsMQ!(BqCvgfr33jyCaE!VP5>5&x@ z@|MH=pzVewrEs56R#i|4QRva1P*zl!37P3jUzJ)^ljAAq(O*#3`iPvsKTt#+tJ{tw z#e!K^TTMa+kMEuxNNUSYK3 zf|4Pn5lOb)VDX6|+R;rlks^V8x-%%|Q2hJzwLV@x&WR9s4Zhwk|7|%mS+X{$_x+A%QZ!9)jW?V+IDx;Yklg z>ZN5FkJDMmPHHZ(PC>k3D<$Rbvgd@DWDCkY(da2w)UO}ua!s+}lir)e?|hXz6RW&i zeT^M2QQWw1@&iIyZ|P~by6VOMM(t@#Bk#}U(YLm!3k0+~bufr}uk7J1@?Dhg5!>cO zP?xcTq84-onBl)irCNxOMo9HhWG>r>E#>Vso7fg1ji?=kOI-% z_5zp#CXfUcseA16a~Q?1{MWtN;Q{#;Z(xyvYz^p0;NVwT8t+~oznM4<3|%ZbUc~Jc z8c%|^=K()V4JmQABN<&DAng07?$E)8dQOdeQ?u)x9whmm#{dSLOhF#fzC-;_ujVQK z?~%j#I2|AXqkw?fnXj*_e~KJ3BH#@gI6ZuOJE`xKBn=?F3kh_3w*wELynBJP9}sMy z{Yb=oqmOjJZMaob-aHq4u>T14`J9{zP&(v=q`2FG?!z2-0b55897clgc;$=?NPk5F zaVb%1kR7U1W7skayj4>esJuke+-tAb%))DUsAJV@gPI(p1sltal1D&u| z^EdB{&Qf?yaa}EQ>CSukZ~#i~Fj*qi-JV$L@F(z#3#)`Nk-BdUY3aw|n_+DkVPFlajeMhi4f^;! zJn1RmepOTp1)4kU`?GW`V12nxNaLoU$ljm z9^nt8J`I;1vIORthq=#jU5Dtgj1;U-&K#GmQ?vo=$pK$| zkt7tFWK8xcxwM*d6NNrL$66ofKkNS4WfSRM8yXphorjYcXRqoWa~*ZHoEfYm2^~@D zTbFAGB!wNt?#gYiGHs@p$LJQQ{D}~*v^{HdSg!Ja)b_?n=-lr;yV~OS0eRIjQ_Zcp zcKim0L(U~zWG3=N3)6W*V~KYdjWyKQjEjwWbghZ&M=9l%WlAwRs*-`Sd9zNd#!zl4 zl4W$}eltnpbJQBPUCy_0<8L9)`nOshEP zXVC=0LH)KRq-SZ@>~47TB38RStt!0}@9iZZduyP~{IbR0L_Rn?FfCwFU6HodMT)Nv zgHzU8h}S4e-A>a1&ZGXfSLLvt8C2qZBh@1@{XB%j-n*Mg+rHkANNUkt7DbXEpeIw9_xM4I_x`l7Y4Gs;e7@Q79S{#A3{<<Tqs}5G@z*X)`Je$*2*N44mu1uN#E1c{ojD;x7Y}x3 zt3;{0?5!)CNlEJ-jXY0iQ+pnY9{k`cTWDC#BO@H|XOVEV<_hPwJ(mjgYFxOSfvoS4 z-v{7libr<=PTQjdPCcfI;?EuI zk0ReOR{3u6J!eIog1;Wy@^d26w}3u*@mrg)XOzz8ERx@G3=qpZCVTcX9q5V$SCuP< zCf1p70yoOjgi%veCL7cj3fW1<=2c3&K5H?@1kZ8#hWQ?B80KZ~%7HI@(Kb4xlHNxz z1NYYVYoCIcuK^9DHNqg`w}MF@#WzNO{?Gv#W54~Cb>MW+Vu!n<_Waxql?fIKr$o*W zmy|bz^zZ>ae2O3vp0mrYhW;ty0v%VWM8gM{3Fp8aTmkD)Nx~9-woVt5qiRH69WgV2 z2Rsw9j@5{gsYwv^F_QEa_wj*(~AdoVAYT(ao; zK+5E&WpX8be9)i>!;?vQ@v2~1(eQu^7vH;pm>^_TM+(0Bh#5Cy17vS(wI+J7%essp z_iXy9oLLj9nYB*BXuF0}C<~YxpGM~8ByIILb+S303S{^n8br6A6E|2fj=b1c=9}gJ z_u*8vK<7PaNLC+x;EUhhN$nsK3Q)UL(OcH9?Rv*{hiap2iu@ScQxyJ{HN>>ByOb{v zuinThjN4NdWmDa8-526%`4Pz-Gk^Qw0+X6C^kf3k#1-vV-WY($8B;k-XFp9o;rn?+ zqcsAuG7|lfYR1qNjp?VvOG)Fm$JR~4UIoTf!au1HPnb7E{7Xfk*ARpASOwuBVP%MB zMBTw2T1C5*Tk4>Iu!PN~q^-0KqUYP?d`&t3Dl1L#5b56mI5i6Pvh)URvyA(@#u*(; zL>BiDA_vu(D%)i1w51b3F30I`mzqbirpGT0{>J^&pw2w*ZIMVcHGUf2XoIPzDqE5| zZ|=gbi(DFeemRoIo>)CM6i(dEh2QAU544x3>AzrX|7!A&=|}GOFeNNky5-MSr_1(; zoUX`n7rWm0M{3CQ$ehu=$;&k5A02w#p;VL?Q{3qQ>Q@h6E_xn6sA3Jk9HaU$>K{~E zjzU$FxW$us#1V4C!qUT-(QWAR%A%;7)%i^H?@9(&HTifuttq%6a8!5;51$S*$taRH z#Na57uE@ypBB`WKdL4|ps$cNX~et! z%94>AWlPS4#MYBy^#}FwAxn{BRdi?;vo-W-hs`Urm4bMh#zp|l;NhtX^Se1F%uBK2 zESoJ&d^2YjcG+E{SCzU5!}t{j+4)g>qM*mwqRCm~HU#H36g<6ZeZA~96z$fmE@ zqJ`aK$Fx@LJ=lEh=gPGW&mB*hpyf&N8X#iw^hX^k zWEhvtQSrcj<{)}_em+^V5-Bz$K)naW;a)gL2o2j?B$$dlViAnFNbz2ZG)DQR<;gI{ zlWDPV9@-=ZDT8dS4HD}}-)oJ@y-G7prUc1EZV2n4n#ap5lp!I}6kM02`)rwj4-9x7 z#}LzM<$Betr(^0vXlP+^G{e!oeu8cUnM3o33?xs9n5XsJQyp#Bq_ZT9??9N~j#%+E zVF?(DAw;m`?(-y71L-9unrEG`kWj7}1dz(lY<14LEl~LFNFEg2ItBTHDKx?e-9ZMH z4o$BLhv1Co9*97Uoc#XcpHEB%pFn2*--G0vVfqvxva%=a$5#+^$j* zoH3Z4m}YJpgPD1EQ?7tr8O6}D=~+|P;kNLUh5zJ&z||w7RsMi!?0_k1@Cm4obeo5G zaBqrC>9u7`dROvHX(W}Mf=3!#`sxW=hbq4wXu1Lq=-15*WvQwQT5`utIJ+@W4$I-3 zM?p)^C*d)S=WCoJ5$HxDBU^x*E#c;33dp_$|C&#(<;FWj1E4$4^; zYJ>G;ntQT-tvWTfr44Uy~?_va(>z$YY=9Hg;* z>*#D*Y6zhYHxJ`Zcctwopv+puZizY@X2s?^fs$>-pm!az#6ER5ReoWy#`(-M+UG&U z$_Se|180tIs4diHP3tx3zZPcHP3>4sk^IKsN-hlYLo^^yvPXMmP6+w{$euQZqbOmclkaP=Drb?{NA^mOK*Yh#$JDyJ;3)w!k$Ywf#d zis260iUOL-|L`#5<$vx5Xy%i3;EJP&uyi! z-~dXOzLZNuE6f@!oIuxfA3?0}X4rX;sJ{CubcycG1aH&U?;7KM!rbF~rL+O5@smj-!8-g?8?Ps5J2<|of(C)Md3mT_sp zBz6pr2B&Z;*->Y-Odp`GCUnC)!;Y!M{a=A4Y)gZm&d3UcRFZ5qlb)h`6Tl>&OG}d7T-}pc)6jiwvsel8S$nX(c<*p^^>p=`!XIf^Pec?Q zMcG!*;hFm5qtRA8Qp9bCz`f8^aq}}S+FWxjREFGuV$P##1?azWc)r4QOwdja66sgx z9|BF1VaYO9u!q>-s_i4Dm2*vyVR%+*3yvc8Bvbebs;=oJ`V;0)5lDiyDC#m=p+(g# zyn2lGNXApt*^mjYT=+h+5ssYYEqWvJ`6p6j__$Dn+|o1-_G>EbsB~lXj6^~qR&gVQZ0K?Rxpc*Y|**>r(gmda<_mE#@#U2reUmIiebtJ{i!bPDC<84g{2;OYO#E zL##RGGIoW?-J;Y%l14vf=Ug1>VxD$!A^naGae%1s?w!4KU2(-bfsWwPi48OBcE!tUzKlhq zRii}+FU^6nuTt^I)Z?tK%zE-G)n*5_JD*?ry4xkEZaaZ2c4;}&zY!O%+w$Alt>}RS zF%bE7s?>LAfPWP=X3sF{=H(xpGx_EM7<=OxX^rK1$g4k2TlF6q>9Axv>I2&g&E zEbe$U|9_O7QupBt{@Z!E|`Oey-SD1Pom!hn;xk z{SD^!Eh7pr+2kXpVj&@|u56zP@(rB>)3K<-00!T+&~SU8L?v5MNmfuBsHZa&9>LAc zj);tm6YOU1uuJ}3*#dHQOt4=YNo56mP$!?dPIX?d!lHoYaP>HvqfULxQ`wn%w z!E&8dQK`8r;W0yLKyfR@o_EUATX>I#`^H}*|9Rv_nPXM0)^yc`Oq{C`aVx`Yv?^q% zN>6j*xbB@G_lD9Uo%Lh6W9hkdnyRID#cuwn(SOY^v(&QCapc2FP(OEeRzWRd8af7i z;qs8h>GjgPmV-Hi&g?#PjP-T2-b5{*ei|artoDp0HT}IzX@=T@XWezSQ8>wZBU+!y zs8F&tEGbKeM(-Ii*1^ho6|fir3wl;U3F^n8kdiugIR%<0JRT4j7jto=M2NlI-6*lnc89#og=AZG9=>j!OK>0BbPkWW_Bwp` zI&!@x9zPgY)0|QTG3gaXjc4BUZzbIp@$z{Q9ya2x;Iz6V$zDTHdj9+yxi&hEikkF& z4i$c%L;u52=J(w~+0@b1(%AI>@`(w$(%6D1p}Uf7>tySh=`1a(n{SAu`lhy?nusLs z3Hcq;t}Sl_2^)PAk0o?vrZ@3&(nQjU0y%% z8&ZVP=~t_CyeA%M!oqger?J;!Qg6 zM94|_-s_xuAEfKj-xTr&JUc<;JYwmCM%!@^0XfMmsB#<>XS`|lEy&yPk*JC1yCbyw zmUr*nQS>r3SnUILwA@3pLwI&HHHJDv05)3XhDof`>asES8a)IA>yNl45>U;5;~oOT zP4-@%I6n4YdyVtpx`K)ZURgU5`k%ZbK&fg;$cf5CH=}Y>` ze{3vVvwLp+{#-asr!&fpJ7Ii1qA>cU`K2h3EY*o_gUNGG;qTJW5{$Cgjhz)289dabrk+)>$mnc;(3t5l z0>Qc_F_XET;r>to03~kH8Hv*}lB~49qE(udW+YkZe+8;UDbX@Iw{pq1lFCX}Yl_Xd z%3c@@%~5J7G=!6)rp?zBLb$2W7>kbRerIj+L7yX49s$7-;pz(a;4!M){PM!!GOCq^ zf`bf&EQ%#ly-O`6XPFiY%`M7hs@f^t&@Ia{OOLGvD%Ku5KL+cT$gf77wXL-+uMt3; z;iXX!h9_*8I*;5+p+&ns<8ZTpR&_}0ULASwV<`8vsV<6C$#-cvw@$6?wRdhw-85^} zeFRmYcNF&x(-7E)>6{!oY|C91ovv}FTdBUrXQ?7dD(`4ntyWY#vspi~S<3nvqf?}J ztu`b}yhxKaDDRKGSzgyc<2jGL>XdWQSgW#Wc#c0GrhVccNB6K;<7^h1H;m9TO6f(A zu|&z+Jc3`M*LXVM!t@nYrvmF;v`Dmf=9zO?~5)P z*3E_>Y^+f@*qUQ8HI`D_Z_U^$e&IvXo3EE3JwsDP5)WMkQvc z87qq@uDI1>5nZ_z(wVbNr|QIu&Dp6}hO)!Xe!vzAM>H`oZ#!xHfNcQH73B2K_9!pl z=LF>O!+?I_X&9ctz}QGw%DanNStE{daWuqK4;bv z?1`<&5_6Qv(~a*tbScm$=SHrEzJ{ME&A@=)aBLDohEqu)0!W#Mq1a$iWvpc{Yp2PB zhZo)-VxHWUvmxHTB;xEMr5ZYk*adlC=hBTNFnridq-P7J!{ZoS$c98+lr^i(kJ&XR zkJT48YkCn$D}!jHjA$s#X}(wIzS0dLY}VQFmws)kgOnYV@Uvau6^X#N!>?c~-WV zB-+y`=|21!P_*C>JIBbJm+AXcG>UFfbXI5a2Kr@jDynaCE>67;iTepVW5E`K))aUG zk57E~)AW)M8~f0vI$u=t?YXI$Y6{Af6O6and36l4hJ~@Tn>@u0<4-}`GkhH)Gav_2 z7qJY5G1y(=fdvV*neyGgF;86F;(FKEe?d$^M=%IFvmnQeVjJ?08YQu$ccMw6OLixT zN*dv>k^H6uIuNh86Ef6h>YHX@SuSVrxl8!F2k79yxsTmtI50SBhL~9doR6tO>Qj}Y zb-(y70CN|Lhg`yvvlDa1#-WiLHd|+)$#Ox29I8cu`VwSqqLf0LupwCAzrjvPE++%K zNI!mve0Qk-XV~eV*NXr8(5caY@z(z3@mKDleep5nvHq$hZrAZrgi%fg$01V45{Vok zi-RpSiJ+?{;n-T7Kw3-*37Ga;Tzj$zMJlmWV11>UdRt4vrVTFI4xu&H*7o_d^=iR( zJE^BrT|0HpXSYMW(PUzv{?$Ck>*{|`Mt|g8Z`t?V<}fO#*3eIC4!YloPfTI(hp zupc)F*)2}6m%h4$Hijy5XJLuJ?%n?IRCD*Gn4!^j#01 zz@I+C=iKczPMp>?emwv%3FO! zW&TawBWKcq;wQD^FWT8$e#cMDLd=61ek}yU@CWlV&k>0$i9Jc4X-hUCLAI%y=$i)y>hZL- z$@}ls+D6)>Pe9;O_TPirUPucvVsi`*CUA4p#fzGkb{knz@-F8MBHm<0Zf3D&+=mm@ zxpHF2IZG9rhY=#p_oD;30bc>PS&tg7*dVJiT&%oFfQvKY+w|G|#?u~{jIzgx+~s|f zRGvBoZzufjRdlr834&-yKZAMw!#o|S{k`+!=%sQ~) z%FEdf4fg}wNFG=+`(v?}W{<+FNYDni`eQ~+<_LLRVH`=Ig*)>kzS+g=aUAJU@DNQm zoLM}fXQ)KIAXwH@NmBB|Gs{abCuzTEm6Q63Rm&Sx>g(61WBu~A-Ci-`Cb$3N)25NMsp9fA=6Tqv>`8S*7l`Vi0;^vM76r1 zj|Ch~uU>%oo+5ab`cS!qbHpE24gSrtdY~hixTKr0eb7ukZwL2k^33Qns1QIS_Bb_1 zBfY6Bc;sd8je-7!=&W8~!}RJI8s=wP{CCz zZ&S|-{!9_v#peAQ*cCf=N;br(O*5m4vsp_Yt`xpjM&o(=O`3-0K_*zIav#?Bhp1y{ z?&(;H8y23*`l98nQHM)cR|v}+#t@(E5KJ3Q0KZW9&XEZfFYe5!a`{~jO1WD@7p6{Y zqNQa_?)0d!(Zm(;8hOaI;#$uoU&!{{Rni&=v{yWDalL$g!aBL-i}EEO&wv`?($(2d zA0tY*g(Z09V&6>X%A!-+9X}h1zDEb1nj&p{!cOsZV`#5z&%6RuQmy1Bp}7Kz+>X;n za@aazvTFUA&QIa=4f#QH)F%|%7f!2Ov#|*na*3xT=yx74A>@djER5)516P{NY>X+w zieWj$D_r=&hb<7D!NN?BOb6+fmo}t1ZeB55d9#9^m1^+~9Ff|EzDDriVpRA-ged1eO3P zlo|U^X=i*@FY~)3c!HF`aRtpxbLB&2{NNv=M%fFWM^{TYiUZ`%{90k%EJ5^SU@y{I9u@Vy#ht2g78^g|%GK#;HAdy*TaL;Kd**0-C?EM_4^VZ{dy>dKZSgm!1=Qh`b0|PH2xbuN zFDCApRtnHAzucwBS2C4EAuMtwL>Bo}hk?m)nx)bS81nitGwndriLav0ayC*5W5P}t zXl{|bmHdM9#$GInumm}7NGAAkPTRF;t|rPmIZOD^SYx{6HRG?@3#)5n z;vb&0kmFIlBRO}hL&g9xo=v?Q>&Z~D5Px_p!s8V^plZ5U#PD-w4#|}Dh?!bn$oOwu zegjkNT3J!D#60YCf6qe?w4XaF>TWF_OCEfNeD{{o#TfpMOTa)`gF1}$=>@t<>l~1E zTXqQRpC*IX&eum6dml1g-0(V+J-fK<0M?bPa0G7p^x(K-%w8xs!56D{U=89wPfm`* zM%dvRv>=FCrUq~}<^!?HO2yknD08BhPXXaa7~ven0pVj;1MG1}2@Njp7Q<%)oN+X7 zsB5zUhcp}sY0=$Of9<=8@I;&G;*ej}vcX8r1IO+&w+%9>f+IKQib!u5VQO8)uyTOP z7>`)Ove(q32}f{{crd-ewXroSz46b`reM24VjPTGZ^cn6;|7N4)Uo5Ph zh`BstLhT%Mv!%He#uja*5~tSqAa3AF6YgMeYi)lQ#H;k6$D;{S z+ARXz;2xxGBe^%2y?OLKOy#2M0-BT;@75kmv_L}1de?F|lRrnZ{^t}1e@Ipu&8UeM zY)V*Ab-3Vv>2QHy>xDeU52i3XtV7j*S9R?Dt%0~0cGz;kA=1nYl_fO&XCc*j1~}Jn z9DkyxnHRv|qc+utbeZi#N5^!t0oBtCl5mi^uUNH6-QXncMU~@x5kvn;-1E4D|L88j zZz}Q)*61@kRVREoWwDc`Dov2GtLull zSi2isr!e0Ir**lMlM>w|4)=Y1%DX$Mn-hFpW(a(`a%PhH%#bP=*(vY^(H0=mFwj&C z!Igly8$@IMeMy8=x;Csd!s=W3MPFl+>Ua{oGQKTReGVDw|@$U1aje&}~W1UEoE zH^7#>ze zx@6p;$P^_KmK|&a?P_%V^a)Z?C9dq1GrqcPzzP$tz<*2Irg3w?6)4c6ToeO*on@nm+vU1q5diirrRo^9yC z*HBYcb-@N}6}VE%fHkIC%~pd=J}{NGpu&)_R#l0lW|bN^gj>?%1VU&vtKyhXH0&mS|=O5xZ{Dn`bW!_kLb^Z?jB5 zE10|A+C4~9eH_ZM##1u$ga1vYmSwO2^@VEnC`lH0ct;K8^5Uj&UvtWp$7Az`!-A3L z!i*4J_=h{@N=PMev4M6#-RGA6rFCC_l8`>}1d~1t7Cm16U{m7H;H!holB6~JVz~uJ ztZ~LY)l{Ci`T#CmQ1QSIOp96jGn(zivF#B!x1^z}kfExHebyrfIoTRDLyl}~)T0}$ zVNQA?JycE~G*@dny(qqumQ-5uJzA8?3!aZ~mdZnydk0&$9xXp$9IHA`t_0)1j;2D zh$zhr>$@~N7kCB2LU`ck$)82VCXghrE#t$weMVjLg#bH4^oGx0( znjNBjh|UdL1CACSUc(s{QgshKeC1_?@odw7RPKMJ9Col_633t=%cde^d1jtld+)h? zIKUGDOcK}i1CV*W!^$Zhtl=UHsaZ4dvRK3!=eeZ<@Ra3~7-1yyIm5$Ll0{J)x3+33 zuqFx3}{YZnk$W1ZRO4^LObTK2Pvm!rb~PxB~kP|7TG1B^W3 z@dvJf`P^`KR?|=B2DCZ6FV*)x)1hXu8tyI}M#lj+Jl;IZj#wnSIR&YW<85jLyIMlM zYL05y_M-K+g5|{d_jj$D?b6Eb*g(49YYh@g8eMdltN9lfvomyyei}?p<*~Cesk0dD zcxHDsxRIX187#}Bm|5`-yXX_`UVvM`d7cxxVH7BR)1LgmGjlzy5XM%?QJIKVFUglY zq8!U&W^AmRK~cIl*nQg_QM1%dl}352(4VgQ{9msj&EcA0M%hFa^?O{VL~Fj47Q6Um zI)-KhlT$w~cEPC&CaZ2}K~!z%L{Pi2HFKHrhoc}^hEN`J!w$EsfuZpNMg1MiLK>sr z0RkC_{s*K21Jy-}Kol%hmAX32KSY^NuTf&c9b$Q#rkXqH_Nr2L*SzE5eaV`cZK$ zMT*wp&cVZjk|$gEgH8T}P4NTEq~eSz3YlzX6m*ipjGumsaiKPyx%Psmq52dYceBhN456Eg*}Re%Chp8)#SF-q;5@d@j`TBDLA(@ zX^en)QSm}~v1S5T-9#EorsCoRE)*0;^n6?xrd=YQMkMh!lf*x#hh%y!mW zwYnb5U>%wztz8tsKp~+K=>imQEq3ysrcKn$OOZa;W4D49v=0UN{ohcC<{%#=pNWCl z?DXqQ$H(ul{awEwdG%@{K+L2Z>9mH>^naMl(>x$x;8H=MPk82k!2{npRlg;EFtjg| zJfdy-!Rj(|H z3oEl{ZN(8Q)J`pBwG2Kj{RxVUPCXs+|Ine{tc7Z^mp93H_ z3gL(4Oh`!REy+p9?`~f&P5;%BVJ$X_$?=A47Q~nLzqXHeAYL zdLnpu#?1ERONPCYI8XAHpk3|`3gEA2j;n67>j7NYGEMW&9k8fNx_0Zzb+pg?gusm@ z)55`V0NKGbW@rWy2L#go;<3y@dad5A;g_& z+INu0OX2b9^kPa$ATnsxHv=%26itgPQx?Kn+8*C(Yj58Q@ek8>iUT^8s<+t^6V=PY zjKPz?9RyVrO5Ccjy+JOXdW-JB%;wkgc>_`viDA<(e8FHUi5!M8e#PaH?}@_bkxlyp zKw|ebOuqaiW{6sb<_9|;?665^FpiNyb$w$njZei4ecsptD=0dWtlWhp)q_Y2!Nq%kN`I*)P!h7 z0-b|fERuxKl{-tK!IG@EcgW>=iLgHeqfxvoCf(RkKJx<%Tn++$7gJL zIbON--GA54J6_-O_`g6I0xN^7Fwhs`x{v_Kz`cok;(;DyU^0>jTlh;zSNqm%6u^le z%v+C8=?U}UKcSe+M6piR=H7{vB$#(WM$-*pG|i6F9~Jt$gTZ)^@}*9Vvby38g9cO4 z!Du8;4X^M{t^x*yeggL*-q1p5R;V~LD=yMVw{Zkl&shCpuw@D}131yv{+1r8EE%gI zckR|_aA_(`IWRpOGpZ;a!7o9s=vbcY%xxRvfUMHmVVf=+Haf3@GHx<*+CzaG*c_2s z=p71(6Zy^gf-ELTel1cEzo3pwAE0$tTD#7LNJRttlaaS3q)?T$dg+Q%%942NP$q^UJ(C^Qn) z!~@GT)@#g}me^M~lBx-;k;EuDMm%FQA>8c}d9x9gJk54=nQW0xOH*fu>h#4Bp7gGZ z*iB9}WMSG4Hw|%UxxXo9Tt@iZ4QE&tdKE(0R~s;ODifvfP-HX@G?Q~Rv_{e`ue6%5 z;%FJc=Y@@JFw~krz|dpVO|FE5fTS2^iqmkO-w!5Ov@;oS@`XnsfZn+VCrh2RwpMqs z>dL{D=!pZd`u(~Xd;e7B|vs*B^s2GN!N_3__mRy1cKHobYeYK`#U zQjBEcsZ1058(>hUh4P>E6)EMqM(6-6-J|)}ym?R;L#{N2VM4du8q6}G? zlLrx^w8`??N!Jreb0w!U9ak#mI+D`R?T}OL6a*USuvxChTs!V8G2*5%QD?ozhSQL^ zf>CLhW{TMSAzo}~2qn11dq5L&hw)`WHOTYkLPzz~U zcje}?q}6?sPY?k?v7Zd56ipz^U1=HpmDl)pt^A1w}tP&rYQ4SzJk#2b1m}w z21D^brYKX#e}c&&-(d2Pz&Dt@scwU9TeDL6sAxOVOTZT!NiZTDb#O=^B+t0rn!rN2 z9)7wVcPKayjrZ}xm*OW|G7jk3;MMHahne|T(=LCW{*R{yRB_mQD2j2i_<=wjM@LPS z`eK4Pg!{Kria!iCxmZnl&TB)_$IxMquv-a2R`-~`L zB?jrtI<;Qefthy|wRYn?`;C`Bpq{uHojm5Y$NK4UYt%|4b$PFxYu#6WpOx$UjJgO+ zTEzIo>0h+0$g75&0Zk+jH91BQN`EqGd4f}`@gsPE=ENvvWDdOxM*=Udv^Qv}SBtVB z4$jjbHK6*@6fDU=H-Jb4l=uZ75u1Qu_PN27KGdjBXnDu`0`{-5^s*%!Z2gYo*0)B* z|5q&k2_z?ddl@UDgsc{%Sxx~_g$EL_SIKanI`l#6S&7<$Rba$G$(QiJ1SIcmFha&9DrO0IY7Zi~eeb^D%rA2RN-d#d+^% zn9kMUvjw+FJ!Bqh#B{+Xy}iE3F}_Clv#E49$(snvjU`XrZo23{q<(O*a~eSF0FdTcPQFI+R@!+^qiSQNgy3 z_T<0Ug&sxs<`K5kPJRi*cnz1_~*r<+*EojysmxFU-o+3dd0x>1MY&!+&Q{%vi+K5DkSe8zf zEhis}lGRW}Hodqh0XbAob1A++4i)SDFM@i(PgJH;eL~@)vaq8XwuO>FWUBHYh=F}( zyvB$thF<~J#flr_7a9a`l=ND_#|x=<6bcrS zlNREurrPUbmPTD1rXend)?IcNI~SeiZFU!(E*E&T(l@iO*C{lbjz%vhb8pwLf4#o$ z^PV~Xe!gwi^@9JlWHD*%)6{fR4%kl+U?%OR?qA#huzD#6 zt~IX0u(3#9QepAjg~7pmS0je57apE-ZfsvbwBCgaU&N6<8esi^{iKrkiKYDLhQwZC zLB51SsrS0zUZf#DQb4{ofc%8BzEVT`lJ>lqs>AxqWdH63Uk~w1-N?&rJ z;K~3^5UuFZL=QblFk2^Uwlr#$xn6|J{#(;vT=uNJ)5pq6WtUv-r#L~%YLX~!#MtS$ z&jiXXTDBCrnNvv0)RjA91{Xte_IA!zmY~*Mvz~~fM~?!+!;QjR4aDQe!W|iS>-e65 zIjpdM086F$gUP_wTHYKk-Khuo1#u4R-8HM;Q@R92BS_n&)YUI$*i52^HG|)^U5R0d zycs60o#cAcWlc*y7_hL}U_Bg0Rs&d=Bonrq%$~gz(>Hh#k47d05Jt8JeYsrz(S&i} zY5b{cY}xQfjqi)En9FLz-nYWdc^y`Ej^VOF#Uf>|X1@`K2anhCU!{!HiDM9#G-k)| z-dK^MjJzeP;fsLR868^jX|B*Ohhb^h#hBlCxLDrExQIdQ2(BpOy{1n$o0N%9<-?p_ zDM^eL(Rn;zhmYHyIIQ?h8xfH;puJJ>6!!?-H3G)=5y8`{%^-r(b9d`8pX;qrUl4fe z(H>}_c_85>F1bQ<<;8iSb0t-!WZ-23wnaLjd)-NShV)*oq`N zy^;aq`;6ea)`TVuv<+FPuqA?Exdfs7u=t95NtZq4+b?TZ69Fb{CFUrtQGf)opU0`$ zP_V?YLH_P{2%YUB214`{muPG<-O&s;HO`t!Cp8lId?B?J^@?cb2wJig!r)cVn=?-g z7Wxsc9K0cfN~aOmtIXs3wl>wV0a6me7>yH_J^W#K*cuVR*~rFi)HDO5sBD5m?-DWC zbT-$xXC`FiCZ7CI-eH4e>p7r=b6)FsV7Wnu(9#W*T1%E|VOImCsI7S=sIKTk0)*ajBJPz#7z}jGIP;kL6P)&=7GxKmeC$L5J6&U97v#7Cv#>%6dZ z`m!y}5J{R`q^*}c!3>r!AkgmwUFwEoYduI^@`le%@RXN8DokGSsrW|EMw*%+^4#W6 z=gL#F;iCO@(pDZ(0K$qd5UXIN05X@UgU`BG$j)gCazYE z$>!}Ed6r-{x%3|r$Vmo2gXxlGl4B&-r;WaxMp~9$e3FeXoSoJ7On?GD0ji2V) zaJBGfuw!LmMDQ>ggxd&*=iV9m*stHSRZlKzV8Wb1EQToJ!Y{$^dwfn*0NF&>2jQ@L z(kaSe1YfXNs-l*Ci@rqVLy{-f3vSln4-hYVVi6ERqgpkdn7K@^rs+hp**1%ljfQX? znTWcrs{c-Jsg{YxW`O|YGxFw$rmg2rq<0rkb1P59=-=w%-CwC0As>pN5wyhtu;z#J z_W{J$Y6qTD48`z9r^e+f>U#TbgUGg?e0j*RlmfN1ARGpA*$9Mj3RKU8ZcsR&R)s4#Qsm9ZV07eRgHLuaa&dc` z;j`%1$e2D4TUczrIME5XTaH}qk_pr70LU5@o9&Y$e;zMy$vuuUig3vY2!hzLmeKYx^X3y z6AXHk6B5}YMZUdd8UW@1J#bzn(m>XIVs;!f!j{vZ9fHa-b;q+Q8;Gqd7DTfS6x$`E?V8b=!({V5RD8UJ(?m8n z;;_nXfVzc~6X>IG7D)J*qztxKMn9N^M~7n?48A)dtD6tNW^efSZ;j=Z4M=I*_qY~Q z&p(x)Frj35!+E+k5EujaKHxoS2*Bw~+pl6D3+f9$KN6zMKWkv-(OPnX3H`dD057W< z^Q>R-P>(O%GN+3-z`53zKm7 zsiGZdLz6aVg^=5F0;hVRv5T0}^nIsL zn`$7_BFI-4!Yt)+vMMIW*x$h_9f3~hKWl@l7zSG~>}J$L3fHZE@@#3+mL?59&<&=7 zKF9wwvxDV1*lokPg$83v4r1xX4U+2MO)M?q{t+ zy0A-rwcHhKMJ%*p4oM|W?I7f;QG2^e4Qxbm0b~|+j136pc1+-ui8K59yVR9f(X%Hg zaplDY%_3~4%sh7nKBMh zC2gqYK4bM{8eZD+fe_}a2h(;Jw#Q#qFmDrqwu3pF<2joXIjfp;;|uX8jNYC(Cxm%+ zzo}N;k?l{i+sbRaap7kMT*wkn_R~SUSn=V(_TRwT3kECYgPK9b`^jkrs#o4-<-`Db zkR=~+CyBhF_%{A0Ew+?n@t_*w>;&q4(0yiW%Z{w63y& z1Gj`XQ^l(|uB%G+51De+;4)Q)7O^t>!hAI+K$+cAj;STIVk%0qI$@d@;xjXfy~xF* z$@Jpq3lLMwF^zW|iIBR6f4~k}%P|MXaw^!`0Oy1YFpe+#ESjNtpxVj)s0B6fEaFMu zMCT87@lAJX`}?u_?Y(awa&__n?caMn#!(2&P=6v@VT6W9a9|&6l~*;dMQDR91_}$+ zwNCcX@H}T)``Uuqs&Jhcx@oIJJS6@IfW;^%4WEd@q;;kxl5U;uL(UG=7_P48bCoc!Oo=7cSfNl*=J@NPz`NMCu` zK|MWqzl3W~2!9W@Fdg9j3Rh+gKk+c*^%)+K7tXAw(FCt$qPH39qKJd zm-GaiIMd5JCr;!es?f0T@XH=mp>^zpUiUFP>Z*)~V&1SQ#FGd$Bo?qmd77K!LcxFRwO67uho#f5z{1LK4=84kAS|w|avw*f!NEnRh zqHvU->cN3xOBy}I!vP^aT8@e33ACataM<+AZ#i^BO2gfl$MCr3+b@@V!xf3-F9ZYU zvUTT%=($zda!af;-XPM^zO4@0Ols8R0t72JykaS|Vre9HYKslwkZXP|mkfM)a&OTQ z(6?@lGOIf(r@x>9R4bM&bdh@oI$@Ll$QceRr9L0LHmQ4xs4wtWUZ5>hEzlhF7BW%1 z9@<}WNc8-!ciVQMX*9+yp5KN+pyu_`+5V+bwshBpxYJaD@1Tr9+w)M&zb+XUhq=j= z&oiRu{ZQ-L>?5WY)c074-)sRGpM=xE@=@{6MnI-V{^t9fSF9lTQ!1)^_)Q9khrYx& z=AwIElsAmTOO=q`o>kzjH~EM!g3LGDssZ>Y0r&;v;J#t$#;-+|&B4@xPb3`p&<4wV zlRoy1R%Wq~anAB3p_RtkGsHQCula+r-#>M>Khg945wYK~J!5Y`ebA~M|uI!CercIy~dS9?7kenfMQ3U=vP zZ%{V|PUZq$I9zwsHBEyaoFih z1~BQ1#Hg@n{?3Q#BK?cR(+kb}#-iA%94=kl^@jAS>fUouqV$?L@Of}32Vynz(u5WF z@3zWoUp+nl7}2f#F3kSFY?YNQZ5?b(MeST{O&txtB|!dJXpi~lknY4P+eGH@8cS>I z6EWqHaJm{jbs#7`64eGIk$a0>teI&OwwYS-&bw1L!Ukl|KW21OzWD$>NFi{ulj-c= z)11F^`u=|L0Iv=H=5ch_^bgSjQtf7v-lnNS)6>>K9v3y1XYYe_?hQK^O1$t=mQ~Uh zzmo}B%}nOGBU8?M3ao;nB!~XR9=q`bOuvB`Uo$c!_x{H?K!zSC!JoCj0wjF zK;>_4tt7NHcdQFA#ESmgW0LM$@7)CQu%4jLpeAWY;((RH3Q#p2Q8^KuEq3`_?%Iho zJ4{gxe~paAIBF$+Y*loqITxi za|yy3uZE>hz{0w_z&V~7BuxQJ*h;)9EQOn|pz~5=RswUe{q>0fCy;eCCWC!HCKHsB z2i(ML0!r%PMy19;R=dy@<PA`Um zLa`|DMg``O>lf$SPH0v$4nd-z zcIV#xKHbgTbfMqp_W@cT&;`iuw?YQ!SRC-sX*HS6kXqAeJB^H}qF(^JI3Zlj@&f%8 zt$hNCOngyL&X1F=uevjqrCN(!8VU|wSCpb+>F^v@j60-ZlLrO1zpc+ve2(#F9xo%= zD>Z?;ql8)Q&iU!4GrL9K15whZ=m`&|HAExjh-?0yWHdSA5`ldx!O}oN_*3}+%}(0= zRO)5rVLq)rh)=!a)Q-EFPFz#WDn9r-+^2tWvCTtO(8vTc{i2gdV0U`4Y~2N>Ng)}c8Wp; zX8Dgi%Sk#53y@YfGM23??Sg&6(&oGLz1(NyX;0l@O@EBgPM;Qsfw z|DVnOZDnM2l)u}TWYVds6!@xqHpReXFf1uXw#Q50%OV8n1Q-@kFdg%IWCdM7ws~B* z@Axl;^sa9QFh27MaTU>Vd=K(x%6YhD4_lW=G|Pl0X4jn0J5GMT-;d_}zTCm~n2%JO z4d&yTFp#ED8@x-10u58z5y8||DcZ?m>!`yk6>?-{MgY;!1B?SP)Zx>lM$e%J;f|Ma4Mv#sYqs&2Rm)=ElQV=8i_U2*oP+6`#0L^F zx488mhzBHvDFJs{&_Yd~$2wdrV0n?*6LhJj0RjYR?RXNILX$W&+8;%v!f~NTj0z~p z;)SFfl5p7Z)O9&78@h%iWOI+XkbJl>QN$@%0fyPT3>IgCaiMP6tNBr6;qig?Y&wFi z;@~TT`2^7;T`9fh&NSJ)@u@4YAhz;@Tuu0=SX5RbRe?O%aE7vViXU9`OigJTRB*P( zCN`Rbyy&dO{cN;GQvL4O%me2hv@8$FDUYBdG;LM=qUwWUhOfL%aQ#kvWea-jE7A3q zN2vqy?JO?%WzXdYL1C4TJ{O+mMTH}=>NoOlN+oS%QaY*Tzh2l5xDjYRcK!d3L}FKs zCOOX^)kH)9j1t?<7zbaNfy=$bd-U04?^GEyEGKc?*GJX*D@xIMqs4SoFpis04p z*4hKT%i<}xn7*O~a8p8%64LB1g9C_&5Ey~AJgv!qL?V|+ogJ$v&0K@KT zX0uo7-`Q)D^}-gYJ>g!U(W!K}sq%HVeFM?&KvplQ)%i>MG9!#H#e#9I~307;0(R->k6O6`lL% zyJ^m~-(Tk>P9zGX#Iwz6>nN8CT5f7fo36>379~wIWlfAKLN$3XRQ-UGT0uD%>3wU< zd;gaHc0ZsAq;3aRKPpqT1_kzzke4ziZ*>&kfV>-Uwvx2KqZE=;ngd&k2USxGd`&U> zJKtdt60#6o&R*fwC4*|8f5zAlz?ZkHL_DZMqx{Hp%a+W#ESG!`|L=vjzMs}U4d2UO z&3AnBpW2(LxEQ*)I;)xjT*TZ=?OguH!PzQL+M)bIA?-RCS`O55va=vW9yyTawi4Aq z$S{E#IVzPfH7S-&mhDc|xGVtt3k+GBH2xWgAI6by8iWkscD=CzK%cIM~X7z`9OR?t+n}LPzcoPM~g#Defv~TW^e)n zN|j@j1(!w9;T~|u4}9VJ5?k$ESje=7k*i+Br_`L7M8HJIT|jQe8A@s|S6Zakf{wa$ z*6P90z2+;d+~CLP&E|XtZ+AJ}Z>?&&O;eEs)qt0D>et}L+~|kE4TranuccY9u3Js& zyg65m^JToK+=sa%$hy0)F@Kwnt_@-M-?+<*mnq4X20@NVEUvLEyciofTxU3!gqzRc zyCVF@VqURRdlZcXCu6RSIaqgGexUWoH z$aAL$KmnAFNH#XK5T)sH_PKR$pnd4DA(}!|Q_z*wF*VP!Yt;)oYMGO@?SK_uRNXe; zhf5c7@ml`VtV_t2Mw!^)$1leXfZ=3pz0I_N3x@2gVv`OG(aSLo&1Lnui`#@iCIdbS zR8qVpKmmzpfR?rE-x^Jzm4npP{WE<3Pn-%>OPBBH{r`{be?)bt*=YzbB-f=#ke@0v1)?Ak1buPBmmOUBU<77=yE7hV zc;Ei!5%dCe4cTF#Z8bQo_IEQkw&{<6@;22QPKqOCk+rQ+8*Q$LKaKSp3+ut+ISSXf zU+F*7-kFa5a3wZbBT_|E2qt*+C={iQ>m-1Fh)`UQD_Q7S@)5wkea|}e@~U&!?;=Ls zfuVZv8c1D@N{YYzB?*USZdJXm2}ZOUnNw_CwoY;Mbl9h(yL9^)6O2~Zel{qEr=U}P z$Zlwx`@x8;)Q3`ai_5CpH0O4U%0r(UfRr7>y2pdT;W)YWCBBy3$}V}(AT<&DE$0v@ zBWE!c)%J(R?ZI^{D5Lc1!}#fbFFQ~7bixz8>Rx@?o0wkYgG|i@nv4y*9IN@lwexG- zZsvKAC=J+fRJ#~MVd`XFXwdtccEQC=h_ijUqBNXXVP)ygWqP5>)@H`dreZ#&Wzk%g zPqP+wIcM$-`I2m2L&e;kH-pQQ7)Y!(-!P%6-+1-V~$jRQ_+0=>Q{}}yWA?*{(^kaY#Rpg_9 zwcZgLEa$UtY)U;M0Oq90J4IC~GO@fH2NZHoF&ru)B%nVLQebm-^#j--Dnz(8AN2%m zS*O|hSXk43VyBfhhOLzlcVw*jdXzAi2Bqc_($P^P8>*_eeIb}qp4V{wT^|(X-)|)K4e9?xV^+6xwluP| zv2^j!u(UI=cNa1-ba46KX&VzaYXu?%H`4oTuU*yb6TH)+Cw?|hoV<{=CpX(^H#f#i z!V(k}x2#eCNzfNYJ#FQM3P&oC7{@?5ns*KAj{(G`ASBSwS8z|Qeh@q-n|B(# z(n+J)kcw@|?dod^_&LE|53R=9?yhEFk8T93;d8k*ayyQ>QhQMl^P^vfd74a;zGUcQ z{Zrj!b1e6!e%vjCilvkJm-pbLB&Kes2{lop>8_`+NZz8#!(ygq)QV`Q1R2%lAj2HR z1>x4-lfsoX`^9?iQa9=Tze#>J__fIwet)5z@4@~bZ2#|H=wD&r8rS_JkP$UR0ZAyw z8!p}I;OtWTAU~kJ4_-$&(h?`U?qm=2S#hM&k;#$sCG~zg@9oeNw0@Xc9M>4hmCfjW3T*iihiGreULrNq+}^>4)UTpM@=)7CiER>0aNNRGF)FU4FBXl>~B< z`m3dKlR6g?1XGC=6iL6bdN-4%i{l2)Zg<~DNkPJ!j2D8yx!nM+J*;jh-hXp*oujkw zB;PAI&-Y%1>p#4;hN+S8|A3!V%+1io)zHOM+||z5#nRsHe_VQus;>QlBBt*TQ`Gt( zn2whQISxmfJT(Ulh{F(Aa?!E~T9U@9K#7%g)CGL@&k4J(M|BjAXz`-fPZ9>!9#a#R zBCxEdhjZuL``o`>1RpP__Z&cE)st=5LU5}lI&s9YA&lrn@%5Y|e^xR}8;v4*Cp&Op+)Svz?;V=Q$@$AkK8OFgs z8gk#jUVeUJ?zg7TdW-JC5;H6t`TE03DDDt>jKDL>pn6?1nV5!$ZB8#@hCSf?HPtEL z`HYf;h_Se~M=UajzhwAV(xL%?iZcrx2&nsevi+w8_WwiPKljF&(B4bKXrsH6c8y;) zpU_!yAfh0&VWdY#Jwk*KF^T@cgNRD(;{t|;>oJ`;gv3_b)qFKiBYG<>3xh2%q>)e? zQ9EgTw$=zWHQm>fR)5~6-@me^@qyo%>_6{6dOhyVK3VK{Y+fe5@cAS}Vehx}JcG4) zyZU83wv;91yhFn3MAt9&s}%MA?8i`U@@~aCC53Smb&U*aE4l#yG!wS?JF!lXL(Pae zh&jw28c6!H9D9}=qKJh!g^-091A%8jXJKoH`-(`{$3Er3L{CN7R0os@g5On% zbPb2CL1+dx1HvE(g1$qca1p)CX%R#Yv|GcT_V74|p~2RCr>Zb}n&RVk>Mmcl>Mn=_ z%C$cL&zi97n*}Z3oc=nA7l@$U@H;t(UMy*M?058_YvyOWpk1q*&Ay!6dnGZ2t>QZ0iK)s;|3nE5Cqi6 z%wKSa4(TUIoD#C2wt0b{urQ8;n2;*-9`8<{?*XqMxif=X7<}(!4BN38pN&x~ku8*+ zgjI1ch*gxP>4_#6zUv1nLEnd$wvOTGw{Wk`T zhPIf|52V8LP)Y095toQk< zJCUgv=GjVU$*;9>00`Gu)1gALHUdEbn@AUNU?@RMQ5CkAn`#}{Kg3_Nv`C<<^rv*o z1x8O(tcRY8_Y92U(4>Mr0}XIQZiC4V5d}`%k6D}Ot#KOEcTi$SYVZmq?H>#xD@L|T zGg)xLa~2#}2lG0%#M#AJc&Va(XePi(U^{KDB^MwFKp4$`rlZB_XA@*CHtkFB66w?T zEORHfaSCuVKMYGt5hp`wQ$bic;jCp=QrprX1RoS!DRL%bPNc_$v`^d2&chgQ;Mhli zbq=aumr8bWc5tmR&?LD}NXzvkv`3B6nAbFmd)9~)E>aseTz3B&jzkDWk^M|&>#Fdo zTTY7Sk4~l2X18rGc)V-4=0R>yEY{z_;FvU%0wam|VUEW(E=&2{QE3k}j*(4M(nx3!fBxmH@)T(BdTz@uZK7=!C+y&LeZgdOk^rNCa z4ADY|$}VR(0~nV!?a2=0rsqFE=K++4H8x^N3uep3Wav>NO?0JPKgAPWpUoxmLuqtes(ft&MuvP7N2{7LHL9Xo$JwI&H6KK1x>zIwszuai3B`31 zrp+^~2DylqicMZ{*MGp-PZr=?_sta0v*NhXv{Q~93)ccsSKL2{8AAp!adk+Wto#wO z!^1}8n6Pe~IVPR&+=9_Ax8hn6L$QR_u1!tN#9E%_rLtJqf@zV!c^J#2yYE}j9*OLT-X8VC> ziEj>@q{7lQbI_H^2eCG8^QH-ec&A*8rbelL33#1l=E)B!GadG-Y#X|7>aHw{^EG`* z0eUV+?)#POFNQYYrO(HqoH#C~-&$zDz#ZRc>e|X3lE_s1BEKbMevGx5{1rbiT*qVP zEMTkARQ@EjjT##g73OP%jaED0k#|b^6!c4OW5Q0Vg&X-RX~K3+o0IhZo`SbS?i0#S z6LCYsStR$0>!WvYFq}I=?h`OGiR5})Rx71n%n-9E@|wwVKKYR1=sBhnN$!)X5hu}ATRCmuI`-;YQx^JKPPWM_Q)mwZj z`^v>kq)cTt%EMytTi5K=IUi@_o&`yJ?m4P3$#Zu+M*B@!UANZhUxDOX{7q zxkG4EXV*_1wr~RWB7H9D)fB*l`D=7KHN|$e1jV72jBQ&Yu4L7ef=!3}t&$p_$NVC} zJ9$KKkDKH-py2gfM*bD;ceSt2Y0dSxV|~Z+UY(7J;bE2WE|bCND{tmc(>8S^Ge2E- z=0(oom~nYBwg33I|Mni>N7u<%&%L*`4(jmaQ7U6~0`Y?YY6OHS3Oo$^I4~oe{3zZsI!uI5og{M!%=jbd&2)G8bVe5o>Ji*3 zAMuY2wjN%2EKOb|sPoBM6H_pWnaVd&QD?jARM;~2Ph6&wBZUzjT)A=Yh#fM)<%*lt zQBgtN5Pk+}P48VeraasTamz=F#L@dYoVgjUDox8gR?b7BxH1I=7vx0ms~~a5Y~&K8 zNw}jWWy9!|7<(pK6xd*lC45tlvcAVrIBn1Fe3BEtk&X{jyIb?q2vbwPgXYdhF4Bw2 zv*FY`0jaEdUAn&g9l^3jpCqx<8l;s08L8F1NhWAjQ$N6XE%Jfl397OR1p}p_pwE!) zs(3rg{>PHVcI{m4NI35v4`*HHNY4hFM$G+%63T_z#HFdQBbBZ6v2%3gxEsHds2ThG zud)$jP%FL9;A7qhimZ3@&!};~V{^IOX_WMpuEv5`w;KeB${^V zD<6!V^Iwa8)GX(4-_(##6N-=NquxVj`SZ1gX0fsmZMtz+ne<9C^F@h z2{lZy1tE;ZkM1SYOPn?D%K;t!%aqP|2v6D;fT7~+MFdhRnLIT%9yd1{Dg8QWF1$F| zPl_*C0e~s{-A%9DCxY+>EaEiO@2_JFIp2ufhnp{1U#_?*-c)xF4-fhzUy0t)I-YS_ zp%BV!*+$EoL4O85vJ|CgtJVq3AZQ(GWrQpuuGOc>8X72VzWD$m*W0NvTkT(juIsnv zpX9`O{OsWom|mmEv}sFC)*-esx-`O4kQChKkC>yTY#vWwhkvNEoF!aB@p0o-i!^k% z84dzX^8%iMX^>p$LBT!K#z{#*>AP&EJ3ts2tzUoX z*T)0l;xYWNb|S}m=7Eyik}0r4Mm_%{~aqt#k4p=r+l9)NufQvlz4cQPhpUv4UDTup zYNW^8)n4#Alc%&(kdwsZO9>WK7Ix!Q{qBoD50gCwu*QBReZH$+2*^~)RMO8vAiJvZ z8>h`ye!?BySH7p{es0j`wNCD?9owsMVa;nzh}}`N@|>S5+%+kD5xBdE{23f-Hm9t+ zH*|te>RfS6y$h=VDlt!oOAZ-468p(2OFnU6NG-%=nC?v}6E+^#Xp=r+y*;Q3%!FjL zAYx^d{weF%z@sPsvcRS!@`s9=^K^%9yLo?J?OOQ=DN{!q0aE_F*ZFFvs%Zcoi=JwebYzC#LT_U_Q%VMLI#V;& zB9n1pp_=m4d)8;tuxtdg3fwoDQZ~Z46b!8<(dY#H)gGON-_#6b>bVIk&v+y(QeBGa zp13=CI9fl0Dg>*S{m^~tS!te{%Is5u!JJ8@b}G4Fww+z&DxFF~tcufYi?#tGsww_H z5!SsxtEI7DLGjpw8rdV0-jG{JMb$R4vVe=l(^W-JssYo%xyRO^^OCvir?yvO<%Rr$ zD!}&jH?e?hdQ#yha!Qx!l zZ_;T_A(mN{2uagx&gp9I&{*@FvT@@3*@a7a4O@w_&e}s(-AoA{`8nJY0rP6#=7qYM zC5@emhA9Byxi)Znnf$#RNf11Ja>8II`~l9+bPb^!PReAPRO>ZKsy3)Yce!6p~|GFvS`RVtmg{!Nja$P;jdF zR~J&IK8+tq)1J^HF#Ho)GsVWnj@B6+Zh@FqSUak<;CV74O%Cs0&?g>&v~zdw-sm#H z36VzstujldMN6%LC3X8a8_RCK#*#mC7MyaE4yd&H`?W54g=nsK9Ktkl<0sWFadVZ2 zst)hR_pS$57lv9Y+8n}ZIR(u*ieBj}%^c6GafkMQyzFdv`N zx>Ji{){QNSRr=um`0pA~KAgw9P^1tWu}Y35orb|1z(>R6Z^I||E;XukLd(shy~G5U zTRyC13xBHc3+M`Al@ogV?A8MAEcglhUf!f1KaQ$gjN7!gyGfehsW)wx@!ULW=v6$g zRlAGS-WdN>qlxfHlv|IUfm9G5ie(bQnt1HUJg6h2P5Ln?mu%5oO!5_~?t8OjlJD{= zNDuvJ>o`H`xMKa27-BPun47+&TlRGhO;@|@?CX=2S%b5*TE4p2jw4p{)NCzb+nsoK zuFlmUTH7^YL#A(J57vtZ1Cx)TDid2)4nLi&n5m|fbe#+j|JOaQ;A%$ewrzCxjq#_g zCb9KJ*#vb(+F>n1x86zhv+vJ8DiRiP)z4^l$M#VA9KFm0_Y{j6b*oC!K6UiJFE9An ziYL6iWGBQqw?}++<%g9MFCcVlT32)9!*yqVOI(6>6tyUn)y_EmK!bZKNZmCvb?lit-pYx%d-) ztxT?3>VnWtFrocNi$!hus%YF5klwN9%UoK^FCQYI+xU`KHPNZ@;9})kCw~@yvtF!W z^@p0k+CgpKB6XDd?^P)EV@KucHtd=|#JGZ*Fn9<OlhI!X1 zaBzHuac=sblk~lQuQH^gzqO=xc~Qjd!Rwk<19$ng$Xu~Y@hOJ2hO5-tLm5<#wcY^Q zqra0fvgO<9U2AH&g(OV3^UJmNtg@XL=(X?8?7a6Za;tO9b&6&@#vcoD8`8S`6#{n4 zoONK}dbZlBJU?Tj=cSA@1AwT+S7QSke>(sgQwLhB71=mXFi9X;e3czsqA zkS0(Fwhho3=#7;FxS)Vw0)z(0B`_nK1vnHis z8?ZE+1!OqTGus*%A~@xu0ZSi{U^~Pr&=! zzchRcC@xT&Xb>I{Byg#P16?1HKQ%lza41lzRXqw2!3u|5kO$B`P^mQ|fdRjuU=TPE zERdN+J)!|opDa8&FfB-WNxzZ*FwieHO^|x9J!SY*5L)o`qJCz7Q=nyRT1d?wibX;| zgg}fMK`B5qz*cOU@b%dFGkT?Q0ZTzkn@6~kB$7dL_>h($mQCYvLLGvcHg~LJ^zk0- zFI19jW8PW3z>PR20dl?|PIzO&e+)49%>2c$bs}vaJpG5URYALe4I&_%fp(zkRfDko zIe=*w^MUuKfhO6~;TU=5lfSb(n}G>ZZP$RYYn zu~9)$L93VZ5%UoZav)TIN`b0Z`()iVtNVdGJ!txlvA3XZ*!|JJTm0EWuH_5i?%@Gx zSi}PXDEd;c;Xt}z^VNbtfvzFiq=SNhMuBQq_7MB{fN!GXSr8n+T^RYgL4!co zplikb(?HjdYt{Wo{s=%iMg4R@e?e^K_tb&umw3J#8)t#GZ0xE1SThUK;4$cg*E{sb zW9Pi^x3+cY1m(89(Sp2e>c{(Ww&43Gw1@z#O+JXQ&j+ZJ^$g+}vF{Q40mvJ34+7p3 zm>;BG8x(fW*FPV^2WS+ygY69RnNlzg!V8EWVy;REYVQyz6@m;X59n;sfS?Z)=mYx_ z@|m;`AN~o%8-}o02z74}$crr;h!?6}A;`%;3~1mfdK=sc#H3g53W5t7c|PU>SRPoE zjS>hG0&^}O&H!vr96Whouzb{}D2|*&XN%lCk%-=|YE zJ@>r#{473iAJ4xzfEG3D69$*zUgzwMfmT`JtS}cCZH&Q+ut!_~x`S<@INa_kgLJSS zuu154C@xcA#PPqt{=7ucffzGYI-m`?3dL{7SL^5+H_NHRv?C)O=|wtWk_FtXwrxdF zqhBfNh$4KSKl!z%yfunC3<|#pki3Nghs7T@<}Cn3f(*03vjF6oqPQYG@Ot`i`z2A| z=o!L<==SXzDYlhy|B&&z?cOX%6ONWaoh2gj>cRa?ya~?#)+sl(8w^`3iTv;` z*If%+5Q75u;b-o;u$+(j>)C+K2u+WO&>ot6CJKbegI4qG>QkRs*isvOk9V&uph-p_n}S=4VUaES8^f%ZVBKdMg(J z_g3hkHHzCo2?|n!UoMxEm7P68bH^A-jFKiyM+0d*a>t1stBc+tQl<2cB5K+;=!b93 zgezhYv)bi1@iIFTP1Tj?7yOf(v0V1*>)a}0H$EQZOQg#jx|KC4I*))X?8@(EI8-?m z2!%T*M;P%WXJLDHdB-A{ks#r-7H?38lyGIc`%B+`N+w*!3`2 zqj#52Ab(1_Gel&$+(2{mR>s-mZ=K}jX>3N98fx|f$rWdP6~@bw2t1=Ml`aZ3_AzCD zW5ya{QcS3orO3k@QBL+Unqdm0bh7Ix$%=e5_61|NEVz|WoWH~WD|BJXHZNPgVS5h& z1Vr(ltAhUrx{Uv<0jk;lqi2yXJ@k70R!ckx+8er7qz7>8^8$5^8mKK9;v9LTKQ_lW znU3aFX(vu0!TVXB!#he2RX_ua??aH^IM%3h(wtRdi<#MMyVq0uX|~slv%=^5Tk9@x z7wl}`79#z=EgdbLU1nSK3lU?NG4dWC=(V-aWS=dHl6U@am=bMzTTtigF$vuj*7MpB zGQv2@L$#E3UQz6OGv(`$wvUDlYY_t8X^0JNJ zc09shwZ_3amI1T2i<@xNRn}QYEZZ1nmD7%J{1(as6g-weD4^EW%VJDg4Xo!;klRRV zBUbx(t#1|Xv@_u)Ch)0kydlIk6v>mJ#jPFwxXo?2j{|r2fG3u?g{voQ+M%EmB%{NH zdcZjRYHkI5vXN7o(pl922N9R29DD6Je(i8AY|s&CcwZQ++>sYz+2&%|G(ERW{?4={ zxbHhHoR0^cyp!I(Hbg8-SP^E-1o=UCM@ud!I5P zjhS$9WhkwxE`Tw8K|j%V`q35fU=Up`%rkKpEmnjHc|4c0OwL+}a^z8u`V*tET8jNL zN|3`Qh8}7VP?(mB&9?YQs!|Xz;Z`m7H{T4hkrWT>UD7h?EBnT$Q+mhj)#JDO3!HQz zp>O_c*mh>VSuEM>0kK;QzlzYwXWHqHIt$}(c~*PByAlaY_x8cGun@FDFZaU~3&q$7 z^-(Xo>0YbCA0ZfL>_6BheEX+UCPZ6J@`}Caw8#IzvDNV~tCedK%}9wYyQwu?NmWV^ zcjnB0M4UI~?6kvISZMPoyt3~cQrCCqBS7Yz>cccLN7(qH>@26SL z=m}Yvh$^7^v%;sOmNJPL`}ybMq!TS^H${>J3oRH|JRm_R|B`?zDUC&x$1}cxeuyIq zFoFD_8DNzB%Jz&5C<|vbd*v< z%`nH@<`t)P>xpRK^3254CjmTzp|lR##6E# zj+99u`#%R1TpC%sgU30eaBCg}W=dADZV>);>T8$juU~vm{gUsg|6idJ9=5ufeuOV{K|qG?Mc#3<*SJ+a?v!Y+Ntt!~uCcoc`)ddp#(c z-QyxE^Lz*OzWY*|=fTlxmh+n8@-n~r`?1Z#^76R1&j3V!XpL9qKorPIe*r+Nqq^%L z&VbtK%i^ZrZ#3FfKC%@Xah2V}!@DM~a!bP1NM2UM6KNZ{)x%tMg*)^SEB&sgPs59q zqP+-$_f)tMP$z2G1Q>@J#~z{&_Mt7Ze^|qPR6HODr8-w8Vzyecz|lX8m++h0&|{m* zI8CWINslAuw9up;X1LkPzy6j_IV1^S;iS1?vU8?|qCJT1OC+9r4mK7uKog5q0Oub( z1tHr)V^l;h_wOp{!?wcx4MNTic2Uke#2!sYVvOZR_iL{+S*A}|-9*tiEOq4;B3C5SHvsd5;DaYlhQ~j)W$){d_!n z^^V1K)zV{Ons)Lo`aG|5i14n3Zado)ko)kdvghp~qV64<;3wLe)l#!DGN+Mk#QFGs zvvm3M*CqDVOtrBpS-*J|+&M*#>}Fc6t!73l?W)|@+{?)U+~93(xoVPc1@&rl>2X^5 z_aby@&;}l=G;3*>N*$}hVQM+(0IKb0uEMX3_suC~vwW6IQ!P*}$nP4>4Kk?gGz~ur zJagBH%4B*w0;BK}^?;)~UDIB845F-wY(1Mwp;cbQJ4)2_Tb zNUnSrW!Tb9MTSG{>0-w5zU)4c2B|H9xscBGH5+E@&CSryhswRyTDbE3CSlHwjP>C( z&O5YF@|WGg2t@{L7*0qQRmU^X>=p6(qvRUV1Es3?o_=ZACm-*1>`!ym!qrA#;6ich zBWc^c1%&TwQg2)9Z8cRndHKo(Owu?Y)%|<|p%Woanf?2)V6kX0*IBloI!|Z=dZ)Ko zPwD=?tLAP$Q(Q@&dx3W;$uW^=$VS$!M{y2aQZeVXAwkg1Jrpx-eo*ZX#hJlVKAB8@SJ2o9`RF{4eL zw%LTkaRA4^VZHcQ5S+6}c%Y|{k`|dy6yjWZE1!fXKq%3NXtDRb#vO55sl+ldpZ@+o zUtR9_upX%2IsVmK&|@Vdm43&#D&O>>|H51RQ-;e&)mG*E`@ zFrk_|@)lxcgKXd-tmnD*772Kh#2K=J!LPljXurbe{#b%{;7@YXE|~%-PP3CIx4-L~ zm%p+mXOpf4fMkiS^HDyS9`;KEwA*Ti{E>sC;f5KbkwD!UE2laqy2_f~q)C%AR5#LN zA-VC-0kU(<#$2fa5_8#-cE}c3tz#ubAYu8o+vXwNYU(=%9$CK`p}Ens1OIrYs%Ra5 zQnZ#EXBXnEQsx>6kCG0n@D>4TMPV2y^^BxPS%=}iJ1r=64e$^#wSr(jzT91VSsUQ8zma!B)KVh6=W%yLKh0%vqvW@WRu z$nJf7(=faPubfx4_BbPfgcjX04fejy$^M|xFr;&8I{r++fF45^y1OHqylr<#w^axD z&fX~?Fxb(eJgn-rBmZ?4>lp;uoE>N8n{*vuH;4A*0t7 zQNO9FR;EVgO7tqB!Jbg9JS@Kq*e?Kr`tKg5oWxuu8|&<9ehFllb$BuHt>onm6yAhY z``$w35oU+Fh>P^~qIGyl;jINI5NxOqGb4tlvmS`0tR;`p<}1 z<-5b05b9^C?%G1F3teQ|gj}T^YzK8XMtB5BCc1SD8T-C%XU&_Yay}e&R^})?d#qlv?HKcH|WK1y`;Zx*HVVlP3 z67r*Xcw?umh#jy1Uj=c9XG!J*-@osq8oH6zahtDbeN@+dc5|&vidE-NQ#Zh2zwUZZRT2+ZB(p)40JE zx9#W?-h30yqwbaRD2tyQ!QT)}R1P0sv(Q^**COSszn3DIZLF|Ws?^6a=Bh46$Z%2$ zc7Voj)#Eb`_b^$xTH>s0T*fKw7nQ{e0DPdN7nBy?VJ-?{CSrv>h`W9*q;N=ppYI7# zII}i}E?Ov31+LhKkyvOjOb$B&Sw*JW)b-qw%7dqcGa}2h1r5+a6XwI1kqdJ=;Wb)f zWASK287%CmLCCQ25X>&(HSsHEt&tN{H6kO7E){OTV?Lhn?;k@6_~L#>smW_dE`KJ8 zkj)Ng#ATi7Kuo+OS2{jyR>LpmlvV21Fv&XQD?@SGB6y{wK#E^i$v^)Mchp`JO`6}h z?ww*!N(n;r52M_$H+MtK z&L!TptSHVk#2nW-=Ixn4%(Z=^W+8@vEF_@!4f(MaQBvC3=3u?e$5Wy`>o zf>w5>6dvoLcK5DG?JjjD*K4y+KgzdtdM21ygwhfM9SaEg_2OYH^v}gKfP9kLZ>UegJ_W)$@WP*keMy~OM{wv3~hc+ zEr4imDrp#f1sb$W8EkkqAmOX>wI~UvvoCC4=1s@(q`-qP{c!YU3}Mvss-32WHmpWf zsk%SF#Z|IEtQc$F>A9&#&sZG(vo1`>9ki^iXFtu2%9-_-fpF{VMK{!OKt9@j6F1x+ zgYjZ4a;-}8LU|n$TMS1&QXDznEFR!VUy((msnNL@)xlE~Vz#`XEhb`pE3->vvT*Kj zx9|n{oPxM@<9cIJfue1P1QbyOChIlI23O->5h-txskj*=;u6BYi9COwtho;%Bv_oG zNHj-!o;A&7pW`2+qq*b^QP#Y{M`uBd1*}@Xf&T02uRD_?(Dh9yC;9I(Ty%8ShOo5s*!c3WXuq6R2ve!12(H^&T1H}O~Pckmu+B}wla z$mgPHcN*X%=?s&TnbC|4ubK8<->+xrJ+yCKfj#*jKT4h3VlrsWn9|_f@wS|A1PP5h z3#htLDcN1NZYjeDa*1XiYE~m%cNH)HDE*eR@EPg(p=PlP?v_>us_&uqg7Yy_z?-T^ zsy1r9!KsSpXR1M{yKIY`&iPypf@4Ki-}L@TaOUhJb|YK~T6))L~C4|8%}4LQKdIMR}!l-uCCHy9COh(kf~ z{)OA(i04o=NdFB>QPwoqNW4S@O2aaB5nJPA4VR%iYp>mSzNSk?$2O-v_!mO@Rb7tV z&8Aywjc&8~5hqmVli8uYeio6+gKix0$&3EndoQ!fYM!?`{3_Ri(y06)B^9(TW`CN2haeF?6@d9^I}ckXgGD<88+A<0%MiTlyKn|{ZU z%mRSvTxyTnCq8q}Bj|8qiDrh^1&yT%$sV0@5|QW= z!azck%>FH4tq8Co&qC$|gIxK#d3rgw^c54wN7U*zK&0X=&438lfCC;Z${s&(P*q(N zg#kr)G^2C$*i9T8Pv9-+&qk4owNqo!=`)O}^ph2^1biYroYO1)y zXY)^vQqiiOqP1iGD-(A2bbYF%jqDf; z_z%kF$hHq1VJwA=1aACkbK1wl15pD*7nht^@H!MrTY3q@~{>ctGY)p#ma( zJOiw|GY-v>$P?M-oBH-d8!_5oe|VC8i!^OmFO2tiQDCi_KOp=0CP+m65pXBd2PLW< z)Qlg7ZXn|R)sp#$4tF}=mNfmgu=A(>u_k$FpQQenF z9bX+;m3ZB*Jieq!pU$x!^I)kGVM7N`jGpK5L~2nyvI{WV7GOQxr%Z(jG)C(%rgHJl@{0?0I6ZOhsSK2)rVfKxe9B%&dB+V zL5h~}$4M%b2Pg6S3HrvGjg25Adb1qY#BI;110#rpZftq&lma0)y)yU<-3McfMHa~; zXak}3(bA{|?C^+84l%1~P|_^&*I?EhQq-@&GRH~A6bC=XR4B?a4X>cuL|(Euu_!`V zA2~mmOEXf^D2a_u-Tk_O{#Ofa3XIH9eTSJFQ2*6j#}2J%IpMVX435LruW~C%eNNi z0^YDonTm|mC*oLn`$n-(81wo9LvhjByaUP5@~ocW5Y&#?9t?l};7{A2VFFV?b!y7& z!=PSKe$Z(exf^%d6yYEcz}BMHEwO;A0qG zna2|Ou1W*!@4{N4!knELTqYm9TL-hxjS@2*@|=APE03e$@MyvU4`(?t+;LoH&;CpK zVJmaC`;wZ0UQ^hJf^HO91;%%>31qev%4D*w8A_=tzG>%mEHdz{$=V4VsqR8CD?ug4 z!!Vg0BW9vesp(vMwOXZ7ch_kmN~gNBG!kTxgAuc_ZTtpe0dAFX>V}*dj${t2_K+?V z2asimY78fqteOsoyGxD9b&+-qb;T8$17EQoz8iE59?CqTTFs)lrr0oUFqkiUs*wf@ z2}VEVnftq9&B$S|vYgQya>DysiKe)Cz#Oq0nL&P5*OQlzT6fp1ykMd}hyqSQ$mZH- z+KoTIDewy3cynwcRAq)9jRPP>$?43Q;+)f{GaXOwtH5e)n%x2@|ERyXN-EVQd4x?{hB{?zewtU~-0mP#~kllbTH1a`CZ)(D5i1WE+I$ zo~JvLRTJ0L-pbnECj-Y|ac)2Kg2VK%De!wyLeE++;T>+#aR_3>sEi$rP#&bHllzp(Pu@OP%utIAM*pA6C%oU+l)(aR62%h z@LPZ+K7koLKBQ@48F!<|TG0-Xd^h(=VUYM@Eo4b*ih$zC?;q-b;tpiVN`4pEmU*5? zcZ(G5B^(K`Kcx0&tG^W=RPk&*nYAQD6-F+?FUA3QOnen9NUKQ^+^Qeo$esPJm3z9; zY^g7x(B-81=d<%IaZgf-349g;g}($oQIJ0x%+C-`F(WLpdctN9z06vB|6r9`ApSnz zkob!~gD(~z?mijN5w_!SgY08{G||qNy$i2|@#^6CTW_tG^sTjs@1sGVu^!zcqQ7|w z7Vdbpu+fX3`t7aFn}WWE{186LJVl^ne%rwQS}9BLhyjig<4Sq%TcQB*TBWa6xh~91 zd0!v=!wPKn-@4~SngA89$O8c-)B*vK{$ERs|Kn=5eV_r!%a4D3R^Mi(vRs)#2Q$Nk zp}93>$zp`qo;30vr0hr4UE`{A3}?3%Ep!VA?mBU?6>>D_+4 z^|Wqu_I zf6TR?SlScl&QDS7EmZY0zxt&1bD+Nf_$9!v(9d5Z^*4z97O~&?Xk5Ps`UgK%|3|L< z$Crn{DW)%^s_4MUl`}xTzkjQPDg>$wSRN%KM3Cicn{!x zfDbgLFs3mTKxcxLhcTEVXy8M1<0#jepVjM1Kv#9mM=_m7sT%+$^=4Fb7~Rz{voo0L%lJ4{$2LM*u1S zDgjOdSO8E3un=Go!0A5y4E;=>zF4p3`Vv38Q5&@M8VdEL0JWCB4B>LWzCy3#`bs~$ z4LMd36ZCo?yG>t>;WdB*jX>8Rb{1ky1Tg8_^k#q-OJ9q$btu^Y%6fpa0a^jhK}F{x z%Xx^M586k8MgcBBu^19I0JMR#?MR3lO#YVx`bN+v1s^*mf7o1-8S*OkfuS>;$;P zrRM|t7{JF}QUS0_0WNb%g}^R%u^_N3T&xJ#CtOVT5x1^HPp?AkYJh71t_8RbU>9cK zdX%~Wti2Jjn-IGhv0DIc<@zW6imHE#R2rGMAbzh~*+xAY%a`kR*iLsax5bnV9gKS7m0 zwe+|A`p@*ATlz1!{!2^$m8JjM(tl&=zqRz=S^DoS{STJ@M@#>crT^K||6=KXwe-JP z`rj@6A1MD%uK&wVYIfVJp%L;WSAE)<{PJS<0F3M1>|}Gxn4l77mNyIs0^ziJpe2!WnC&A6Bw zJN>H9xCFEJF?8wU0GFammjPT3a0NF$;a3avm$(64XIy0&S3@?h0k{_6I?LFF5PHtI z0cCCkxCu>-K=~JiE^r_Qn`iOjIz_`o!3`V{i z341`hJ7C;n+)Jn~1pPj4-0xTC8Blu00{{;KJj9L9`qfHQScwWNsX~f<4mCU+FnWzg zkn2%^#{eFO&`pA@XlK@Wv^r5#;Beox)A7B9B0Kg!?GXT#595ffV zIvW}Z-*aH`^BBwve&dVANdPYbyo7eX1n^~muK>IZ01MTCg=)Y;HDI9{uuzTH0A2^b zzit`dpgqC(re(l7HNFk-9rXSU)bL%f?R!AKkJt|odlRuABK9N1evH^p0DcPa7QoMt z@NG%F#h19HuV(>ZWO#+%jRBnmzzOHv@k4VOo@?ZJ9ZKGuO=H zX1-+>5ckYN%cM>IG_#1CAxm3BR=OFsvpO`PBn3rze}^xf$`R&q`zh zHz)el=Or=;fK=;bbBe<3y3WpcvZf=}-5u{%7~c`!ur3kbp)h5o!U7JqG?t1fEVsKY z+11ezZ(pD2Z118h_QqJEqbC`!>FViBQHeS#K^3J|#dp?pwZ|y~u$s-Wq(rT4UEL{J zS7H5yvF)+B9kI?$bDLAiMCYc3)cEFjtUaEjZUoQGmY$Q?)Y+AcFHLk)MO2@14jSx= zwNr9S{9@`t4s|5i)18X9OWe0PwY6i(&J>2Gp1XuH#J9&gQ_Fff+fs?HPKC{^?@Df( z8*l4KZ0nBC?cR}^yCK=NqdT6QyF`X=R%$+v8jfwC@vm%lMx0Z7aVnndjCBb9Da^ly z1}M8rJzFoC*RpdP^|hd`oA6bM)G4QSB^%;9;z=rApV*L$C3n`-5aR8`V9D6kv2EL^ z2C6Sla-k=c=$K1X>VvPDco0hw(-qb@G4wpx0E_*RQNzS528$)K)L8ZHm@3Hnh|>w6wIYsa4px)pe_D5sfZu zY+AjxslK+Mrg3TQQp=o5BG9;GWo=E%u=?}XG&QcNZE9(aQs(6?E2!o)g(7FSu4rs- z89`jr*hIt}inlb@G}cF#)z#N}@f9sCYiN9Ho9aYTQ)44#_pfcH4p%Ry?vm)Pu0A^| ztFcA2sd0HzZF4hOwTxyiu)3}xx~{gVnMMvtqm@w5Oa<#2YU`t`YFpQcyf8IsmYZwW zR5w+(G&VW?P-;MIXfy zg9ToaXp3s}($=o=R<^p4h9*nq4<%;Fx4Lm%t+xe1Bq_1X;t_c%Ym;S`(BuuJuU}DH zTMr>XhPsC3QH0U@I!IYwLt{(bveqFSRTXw(L-o2kVg)gj_+DRE(;8h`TT=%HN1JNT zT3g%PQo9s+HJTBpik7;TdciuKhQFq9&DwfmWfYM$pc*7`#KV@g&Cw;*P0<#Tswjzc z!*W@XNqwnlszs6YwM(Mw>T1`6%&{&VoLa74+6t;gqj6fTZ>(M#mGvUUr?3c(qj7Ch zO|9G0sF*q$I5PMJ+txRTazlm^P}npqiKti;V~*OUUe(k$HV^G<4lTg-bq!Q|RIZ+A zO?_>3lPr>p@ijIyH`doiR}-U2vO&%x{)w)5SdmYQ1MMz#djVpB!ivOfMrkn1YlrZz z5ZVDtn^?NKwqY%3L84W!ZCQb3P~*sS5s3zgBNeT!Sru(wyF~U@VaA$dSKB-q|L9t( zCf3ohA=b8Kp_6;Q6Q1gXA92D8n&y?wjTuKk2MT`E!SPh5M4>x^qZpsvfI8Kp=fl~?LiLXD7Z(CsjTYX`X=rg$t^px2vn?4Hxn`7(80n5gJF%>LZcW!#+T0VV_`$pxMn8x*cxp^Y#s#trwn1U6t&h`#rCs1F{LAEba?;#sRY3mTSfnIvf%yZ}zOkVv~;xosDfW}P}t`J{zmQfzIqgNkkzy>i7w^sNrH zkCO?4x-8kXHDi2)OTA`}ls?fRr!KOioVq4v>3LnToO0%ENVZ?4=ci>@?Gxk|Zsd z$BsDFD9X{Pm$Y7{J(B!H_hvMgljvST6Ia_QEGrsIqI;Q~jWo&E)tNT=P8eNsL__F{ zc@hb4S7%eaJ8?;zo71UfTIiiiyLNQKSahvdofaG|Se}e;qz-SUShqW=;Vgp`oHo0i z*{;fxV9NsV`>y0+20()!qlHgp^6<7a z%gNjs+n(4I!)h2Scahs?N^5uPl}fEn=qI;p*_i0+DM3sRh7_PI8Xj77nOxmf zvMug%0i#K##BMUh$s)%W-4IJgMNiU0b9>^>In@zs-^tZ?6gJ6C5=$W3B{U9+S3H(P zNvn;vk(JahZnV+im>Z3B8m2Qa>2Vq_&33A#Yh^Q*$I2{viTpY^_TD7B^3^&3Wd0 z+dS1I^)vMto2RY*ME#a+R+yExc^be1fGU860E-ByKc+0|+v+>E`lk97+dSPo!#2;P z_RPiFr?^>dn@iMJ(ajoLTdWbf)Fk70%4jXi6Q8X;p;0cs%iR~RAT3L0sPfZnbD6oE zwtg|-MAzK4o!vd#wsj?;Hapw8aIUSrxGla73&b{8n02^$|=ELZU+3nfwt1O(xouv73B64Hu5Et8ywWzWQeU^t zs{yVtueHtVNCdPF?Lyn!rCq16nWGL|>H=p%mbZ``U2ooCn>U&_+2+mEW9?QNymp^< zzpZ@|<9-z2cF^tsxEEkAzykoE1$YEt9l%}S&z%5wYxmgNZNw?w_-Ve(l$S3ewy^-a0RfWC(+RkBRNm@wwRO*Hi=@}yv@8Ffc8RVj&0tlUC7P5 zZ1XePB?>#`SVLdgEc`Z0;~Sxigh^((9v$1R_7E5f_Na%D`Z@Dq+w7$T^~;2+FUxu!vCT)3?=gtV z<21M0$!O&XbDwQ~9_Xn+zkt|0#Gb_TKLyYS+DAZp8t8t2et-ca%tyijpo0L<06dF? zHh@;32LYY~cpl&dfG+~P2=EfXmjJ$u5(`k@SAf0@a0uWPfL8&&3YL8hvDZwJ$FIxS zH*E8ph`!m0XX!gc@r6Qo$=I;uH=?Ev|nGm zlT4CsS}ymRKOB()Wony0GJkBFKQSvwePu+ep=axcc#@3Gt+7;E%fbnnN^Pr}OZ&`E zHQH{KD%*UEbgua`Ihdc@<}a|Wjx`QCg%-$jiX_M-O=J6|t$t7azQX2G1#_GV=1>K5 zTU2fbzo-_)Ks#=iFR*JY;?(6!}OD|mr zCGl@t+lV=>$P7f7y4i=~O%k;z?DPNiIJ(IIk9VG(iMArzPQ6TYAhw|=mFnvJZ-$0w zG7b~7y+v5)#U-Z~clLC2j2tr^Q`)=QddNmi&2i7p^$D`6J848Sh{ZSr%$PlN-ewo4 zl0EU6RCwXog}1>@9=-HwW0&sPHhR%B#waRgCDU*3BxNdCbgT;8*0UEsX8T*|=rdLY zXCAA99h>9vj{j*F+LN(O;yf@`1v58xC5ub9$CAYf648?vQn09495uV=bi_M1r8X03 z)+`~`t0ev0#QAfMOJ1_+flSNw$6K2#Kx0=|cf8e!IUT#M#^b8XD`MF-MW)8p$6w>H z{$;Q4zv!VOg#U{*;y`~KH8%VgHTI;Aqrx@EU!l8j{&z<0T}flti0$J)w)rlOQSZSx zZqwi}q1@5N&LweLZ1J_ypR!uIU{2xY`?mQ3Op-Bj%~RM3V;B-?=P=fJt#o6y#J7@B z6HD%N{fOckKw-Z0-UZh!jfR%CUtu*+0UJ9>2M}paSLX)NiyqMn(f}KaODWOaL5e>n zHaHaNPKo`nvvlS{sxXL*AZ2nAHH?h3h}P}M?)#hpIK%^c67eN4547D zEmv>bJcQnb0mkw1Hjkj6696UxOahpUZg*=mt|?^1^Qp9Ocri+pAmJh{ImFqsw5w-B zN8IKo@M*UCRWf7MuMyxU!fSCXbGs7kImzbJ`HU?7m`%ypw#|vQ?zwBa62c1Sr8b|* z%WOUiEzCw+Z@~6FX|!hQySyiJUe4#(d@h~C`N{kg&ga>DK0kGgwVd&w+x#PRROc1E zlJnD0Y{3|=yZ(7wyB=qTlHuh#VmnFb z=Ppks+UrHs=IgwK(|5NdX7jk+^9MC7DHlJqqH=ome%k8Ox| z;3#uW7XO#0TX@=H^C-W7a|$=`CY!hMcFswrH}Xw3-;7Qs04~HtY~eKgq9L7Z#xjk! zLqfM^kz=<*OT?*x7Ap$Z#5&usSdXK2ig8Vt{VVhrW-emJD(2q|*#axMm?b;5T zUxnCZ+75+H8+C9(V>_$qYJLsp*V0bTsjdc$ZWm_ddVT}vH_~`Pz6rb#dzzgyeCQ9cpIJa+im_uRQw_UoKO5q z{L41~3dRnX6F-C)Tu%H|#J-By*WeNT6Pz4>0r)e(UjaVOU$gn^0AJ_d;QX65{}!h8 z+o=3I+A5N{xa04GQ{HkBo4>)S+V9b{Xi@C~n}46CL))+Q5837GXjq%#v_5{IzCP^9 znsd=%^EWvefWb^cW-;&|X&2i3$1o62@YLDS5#JQ+s7`JYHmfK5PoU)|WpRRM@@Re9 z(>DL9c9zZGGJA$7I_d1P`Oo;zZT<`XOPl|S|Jvri;ahF~TmCx;a3jF)ZT<&NV(}+% z=+FEwp#7CNBaJLD$&^u>|Be6M=KtXTba^7)p{LJ` zclmqA&PP3X|9JAvxA_PBJ)0lqM{NEfKT2IarmPcV-D8q#kKqi47i255w3yzHoC8c2 zZj5+Fp|HZt?9>sfOg&2iz>xQ=Omag?}-3l9@h!OMhmYfxusQ))|8b|s2G z5;5^KVZ_$#b{DS)XqzS(B($a{DYH*29r@m7SP3%t{Tpe|STD#ZlI4w#TSjK0BIBJ~ zR`hLa?82^O)VZz@?{n&gWk_^-GYm~?OLk?F+*|NrRi?>9E6R|ER1#jev=KthVEB1cF*PW7w9aXmi~;wT;V!M68q& z;ospo*Pb})i>J)#7uUsjmLp&BW_y}36EAyxJ0L@=H$aGH9@p!Pb40nxE1M-=Bc4jE zPoy^EeQS;TdW)oP3GTKbJJs#&@^JvoBNnL36Y$Eli&25Ais(%1*4`AS6Cqx(OmZo8 z-L)6>#5!8KhBQa>K<&{ygqOFhiq+5TmM%|}!SZ!p8PFOCV3CgE*bq&oe z)eSZB>7et53opSg>`p(U$z8s5Ra8F8l$mnz)TXVcyGGtOU_mD?iFa*O*xZq?Y(~2! zUMOePq;ui+7}a6mT|!*4E4}U-M%Fj{W!v#Lh~|8;?wy@&@QC0wg=cD;dOF2;MJ{uP zvu=xzy=e86c<<%IZ9}Y0s3pF|eaG$-gIXQyrWv)n#yL_BIe)y{oG$F?n@g&K|Mwzt9S<^Dlg+kq3J=>zr z^FL3yB0+ba8wq+(GC^8GkSQ8Xbw#_=D`;gMmW#QGnut^3aj_w{-R^cq>*|zqM-`On ztYzSm+2CrElH+(kIc@a$a%2ILV|AoYi*x?F4v~NieGz%`=(3;X(qFW$w|6D*hHq1} z8?XI4qIfJ8jc-lJS=g{M+DS9z>IxL-5#zVEkt*NOg@=q`F%js=um_qJ%E(gFN50_n zJiLk~x+$7U((*lV?2K*B0W|$wEg5e|HtKjMWXLK-i$Vm-#;$2-SA`N=H{>mwTd$O( z5gUHmh*=uno@hfAOKX>`T|V?l(N3BuI$UeQ=nB=lZ*OQ*>%^1Ycx)?)iAlB_t#gHy zXWMT^kemk!RB%#h-B`(1s>{{%!7MG4G;ageL4s#)rCkKiW_@kgq8r4Nb4;J$N?>#5 zZ8Kq;GVhrcc3SDm(e>$Qj~^C_YI7WVjq_A@ibp}XkKVA{$uNlZbPxwm$&#U)GHi^| z6wutZ$5OEkwDU#V6G>88T@a{=9xG>fzRV-I5jAuRV=uLtGzj#l`#F;PoJ|PZlx!)Y zpz~@FFaPQ=(%^87DawPvQtS#)6Gjvx3n&?lwUadDdGvSpX1pTJ9sW2oKfP+HY$uJ_ zS##35${X=gGjGHb&BAQY7;}d{=c7e=d~YYG9oKuz$QTa}3$r~HEgbm@l=?XQ@nhbI zSCyero*s@H^*N(G^x-Nx>poQ}4J}SK7?q*)(5R=l{bIEyRNes}-rX-`Y(alH`!1c*oJU zd9>76N~TUft}O(XjIiYzH;NA=#w_Bvp0J@L(%9Z zo5iO@;-k3e+NQdUZi4Dh7m?fRx?|4Ov90>7ubho#)%Cct40@8CE3<|28NU2TG#eMP zn8oYF`{b?La5mV=iccLzp43PBIGj&8n(5pFQ+|vh;^v65D;HD$N&3KJ1>4Ey zcw*D$leK~}V z^fD7m9IxU@DrO(2)q{s&xYsSM87p=8NTHZEbw%X~dE5v-xGB@e@EvU>J0P(k(UC|w zZk^Tcerm4YlE~PQsRS@{FTkM&aj%DM*O0Yx^u->3?rIxSe*W_X+jn*rxS~__5o&B zR9XdOYJ0MH64Dum4rF+a%tjGdF1Z;Mj>9xYM@1au&s~yTVT_a!_rPh!;X~;j_Fa*2@)@J!kQE(}zE#&& z$=%ex#mO#rl;Y89f;H48TrDM|Wx+f;dF)g&*4Yh1a~1lBnI)a&Pjowd(S z2i!={$VgAOCca23a6KO$p3EBED^uS!!XGo@1C!D6QU}wLo#OlRCVN|4$&*#=H4tAsjd`!p#|>Y;rMvDW?1gAcsV>TyaetS zmt%5s$deM$?-ARxb9$0G(w2t5>ob~=b*pgZ!A)3VON(pe7?xqC;^S<6qSqIJKQcbu zBRb97R6ix!R|V&0db(OTsZtBWZ0eLIe}t;s zfE)ep?iJ~D%o5}@JxB6*l-1-}C-=j3$Fl_r3pL{fNXF^p`SP(#jmm2AqHFZ6&fRLp zkgg5!G0MwVPh%AK`T~5Z#MXFH-VlwaJ$;lbB7K`UYUuEjWIW9tUmGNL=oLyvN8+_z zmYWxmCIp-(I@{wH<3Jx4@{>i5^^`cW=1V)aPW-@8jc|jC=TK4c94gZ@oX%YrojBM} z%aVWvhOd~S>EBZt;WhI5^Rgt13kT_}M%n|5*GSnOitEj`LNXnEVrlFASWk;dsvc%z<-HRX6kmJlxmr&R^y%|gAv2#V_Uf(x%!opc2JRG@>vsJnqaQ01KIXGV}MDb`vxCrX%>YR5SC1dnX{;nFa z-tF7yR}1K7TJ9!{qI|?S+^gl$8k@Z%{aXjy$V?o0KOviviJAV(wwxmJnM`^nrjF8N zhWk^8Pv{6uHbT{V4BT1S(nh^7z_EVmC?5Mvt^)Zcsk@`vM;`D?ur)LVv287d@3_FrplU1#`3qv_u9UuIBaS|ar);8*mM*sjE^?uMy>zL#Qcq`bp45qz z7VjA}Y#uXu%IAip@3Fc%N62>?O%4nLr(aL`l<-O>V#i&!q?Q-Mfi2~8zTIs z`YjQD+X=tpgm0+d72)^P@8dTX@%uF*d{fkK;pa6(_#-F$u_$NZhkZo&Q}r#Ts6Rs$ z>d)DD^%vBxqW)6+qU7a-|0;vic*d*0_TYCA{u>WIjqu-QaCIZ$zw_X~C;Sf{9QFUv zgFi(0pFHjR3IDSPw+a7?Cx3|Wzk1RO2>+W0zk=|;d-6vJ{|E8MP0u6zpBY^J7vXQK z?@)Za`fm^YKZL)lz9;DKd*~k!e%RCAHH07W;5QKdp$ET-@S`65MmAn!9{gIu6%W3P zaMgofN4S>3HJxz7gPVkNPyXu(w}$pt^JQ?2_^SmxIPq7@@!+|H=Xvma!V5fjA>ly} zUPO4vgNF$p=fTGl9`WE42%qS|ClNl`gHIuRss|_e(@H$}34~Ac;3pD(k_Vqo_zVwT zO8862~Tsv1x{Ec z)?XcC_$e=@Xs6@nLYabpxP=*I2bl7d_;qm$A1OJ>Gns*Z&4o>#GRV}^Cp~91luwy- z%^=ebGQCwPA7sV>WP|TY{f(WvX?U<>^#YjkeUO=}D$B{3PiOeaxnsv?r*N-K=ns_?53vHo@+b1WEHLSA#`RvNYfrP_gg#cJ&3TBOEF*iF zJ&__hQKq<*h@?>INfa^WJm8dmBHiI47O;vwWC2A{a&xrB%H#>prL_N9A~!_s(2%eb~&O#cd}`8YEvnXyWx$|{^% zNE|PzvU*u=l}{<0Mjbe@%8FQn?4(NHl(4UlO&?%023cukAnY4tGsA&>BpDjBSsptf zJ!17Jp@vTXajlLiTwCcElDEofat#yzJ_DPkRGDs*CYt1KlV(_tn1ihBnvvB9W%a{r zU*OiRJWJx}qp*i~IBPefs`gFkW3vZY`EG@#YtDU2a0=LGM$AKs6)}l<``O$*><9_n z$&U*$Wq$_eAg>rt#2jF!>}GEqWb;}Nu=!Oy!i)RZsWcTIiSYfb!YMPz@DEL}4~r>0 zk+GvbC!9!OEizW-+n4QOH=()(2U%5X#EkF*Y~cV~)XTOXWT#W*XMouev!9(AvGjgc zO^B60@tl6Pq@UHuI;_ifv%HAax}PmAti7V2Efr+lLtU1pau0R6pklNUYk;j-@81Wc zjt0GQy>DM1TXm4tw@x|0RzJfURQ4jP2ZN-1QNBiSj0P?V+z~kH9m(6S0k&p0<3196 ze@5iyvt0jSb~=gMVOGPn`r^ZE0oPWCLhmvAedbdRvx!`5ILz`zjtJKp7h_eg(av(D zWC6{iN#Rs6TY5y7ZR<|kM#Pvxl5F5>+2I(HM_7=}M=Rr-6GIi=Dc_M?6~ zV$1q%r~aIPmP4#u5d@*HibQ;i zh=;;Pq$omqydz!m>O!UW=(WBK3uZDiFaIc8$ZX=OF8?D!p#Qagv)BCf4{pDlPF z|NISph0kI68?<5C6h1~g*g6)E$_7|xm7!IbA=4S~#ohtaLXH^jfX#UkE(grTfPGYX zmt5pD+A4%J+Ifl~$PGcnyZ{mrsAiWm8KASZW^yyTZl*>)GdN#bDYb zlXBzd9@BCv{uLosRwAlo)15k@+l$5C$k>FksRL~LKENHrt1}Eyo!iA1oi6UBE6*)hTkrRc=0|F?3BSJo&gKO$7Jwv8C)uZ z%Vco5cM!SE{{abxY5Gk3Cr0CHr1OXkVD8c|7u-2hZen8dD-ot2G&y61U?5eE_OICI zL_Q%SyTW{cUFmJnWcrDg6%Z}C3M)$O5QQxd`$S7ioR*+MX47Au(_#>|h^QCUUbQbB zy*d-UW_V+Mzh5*~n>K0!VqRB=1ER4NPGk6+!>FU$?(-nmd68Yi zo3w44uvYCH*`)avNd<|g86z7G3hbRecKvR)R+yqiWQv|arf3B%nY;?4mlcpPN`wLs z3M-60cEdq-Bklh;72Z6+Zkas5ZY}&IMM8QyM(n$ z5uK`hv}}+)*3TXm{wfj|~Xgfeh`LKK87j9n8?4cWEzVXfL|;y_BJSxsQEC&|c2a zUU6x!W@uk?X|H8yUw7O1MuzsSKK5;*`7@O7xVhfQ(7xx=zMrAJ>5lA&8QPECd_MuL zm%W+cmcu*B^i~!&{VWShe(utKk)i!63rl`2hfu_R0~!fKANyUFk^bJz_J<7ZPwp80 zoT2?S%UCzNw7FK%c_792?*wu4=P2Kx#3*&E=c`WLKGiV!r|OOWn9@oWqfNH#vW862b2l@%0!7w z>Qg2UC{rYxNrzW(C~-O+lXaO7q@cnIaeXiA3|aa92iPVQ(=@{vP^MP-fD9$r9<*^>IZtYF*+O8f)%-47J?2rCCI_#s=*wexhn_(L|IOfgtvOSpEv z;x9SM7BiE~QLcRyrYN1R&tq)f5jv_VAFyq-cSf}f zXvW}f_R`crWd=kPjDFfVRfcssRw6PL$4jMDW9uQDGo_GgMM~MAGFyyl1!Nu)I#(!4b3WtTKRO?ty6oth^7(ePaRZFYYi$34k%UF27}7N0cFwp zvWPyQoIaqOv0f&hnND6jG?}>2Dzj9lvn)XtL~7i~Qa4iTMwYpe1JHzM(W+jYB$p0MjGA78aHy58)44JWZ8&Z65@6;gE>aTK;LO7Mo{^v8;K$^aW^{y$OSGnCL^<5 ze1nXXyLg)$X?G)WH?mPiGEHrABb(*K84mx-(kd$fm8A;}b)i&T1Im^cl@7ge*4ji&7+B7w?_<9BgHVGUFej{cO2Zb5PGDeTG6(oQ(vcJ zAWkQ@IAdK*I&S-t5*YbBsO)&MWKg*n2DO*4Q|y{dt?-3>&so?4!aU?lJH;b@$0-pY z>sz_R%?0FRl{RIzky-gTGDrN8zyambC#!OY6n&C%hK#Sk!eTRI9)%?^7W%^ zE%Q0P1;?=<8Ao|L(j)t<<6`@S9NA@yr#fS}{K=`*`7tJdl4JmctKM{}V(u2kt-g)te2yE|~0$3MtN;U}H5>lMzae3x_RYr*W& z--Y5(kJBh4NfQ?x>?Hc@2`blA={nA4OM}X_PIN&~xz35s2`ams=(M16y%QZDRBosu zsm>27HzKrx%1u>z$k@$NBqTTQsxT?IWmg5K;8y*D3X8%|x(lJgN7$#*n4hpur?CKG zx1}+gu-kW4&9fhXNrxlsll@@q|`EHrk!;k!*?Ut@cHHu+yZw znv99NgcGF1;{=(&q=RJwC9}#36Q9(JXgq+nilv9G&Yi>0h0t&3lpQt7p?Jk)-Zn<0y8Mh~-f1Mj^e6%Vy8g=e?-h zGoai%%WxwmMLI|!&*E;xawGX}8pbwiVG-f;4zWB@1yJxuzaUFRv+~oe1OW8 zuy7Hh<$V&dWYJMJM`}uS7ombz;FO|Z8F&)RbOM-kk(PA)6>l*7)8I_^&yw8PtyY#z z8c^;#y=+pya{nYqi8|ep6p|99P)dmiCpzKfB2{S$h%b|IzMIkE>j8n_+DBXg zO;WGCOdu4m@KKEe%7eREjy$G3wADYWeJCbo7!53l1uuYb^`; z4k!`jcO6Bi>f8~5c68QR6|7$SQ=O}zRavB+gdfy0hHuC*c;#UwGZNoebS zVi}f5nKnwdjQ`DUQ7uxI76@4qYmH<{bwb?<7t2siGz!VWbY!I2uEyAU%gVHKsK?ZE zqA8DL8LF#g)C26~50uiQwDx@&?V*q+UJV;!H8jkK1eJhtDp`kS%a~b?(kd7&B}*9% zDU{^@pA-uG_k|9#GDUfh%~c93Cmm%K|1AUlA0OR>#R?tZ0tNs7jci^{xv&DdwUlEj z?xn>+8-kj5PsR*4 zH7b`b_bgq@vvhr)r3*(_D2LV{>Bnn@e!Ms_Kh6CySr(Vt%SUCMBs$UtqP$zJvnk**h{BI?FT znL@qnVM@A2B;7+vlVuVrq9&CuJSLRVHpe7~**%TP>!cbcHd%BH3M;5Q34PVe7Q6UU z5?_PeAZ<%s&O%361J`;;fEFutLb`Ii(j^3} zM@XNdtUN~gs$--dCIj{ZwpJ-FoN#6y{b!TWfnU3JY*tggT46uI!K*?K>2&m>gVEHG zo;iCN&e_Y1m_yE9W-mKKp1mq`Cy5SUvm$0lcTQhZhn~Jff_wZjC+}wCQ0#J1j3i9y zKZHx_bbmtc*N{io{ezY@egVhRtTHjb1yt7w(#dd7H*c6 z=T&yKrSu;CjpqVwJTpFImCgyTYTOZa3fC?s)ijx@_>Ft#czOXDirCfDGVL)r*XLwrzp;!K_Sco5@(pnp$eqiFDopG*ce8v- zxm@1+_p%&9u8;`)2dIk#!ix9?m2Z_P{YpPkzP^W@N)63+-9+Lp;oI^e0e6KF^MLZ5 zjNbF@D?6aP0n6&^9%(6I+#@YVSha}N9mZZz_bv;~*C^xVX)n+W#|xD2I3stDc$8-_ zRl1I@7_CtEsD+sJ3GGU!(5+MmB9ly8QbPOeSH8Q4`O4_=atJ9rpptXa}&6l0~*`V_C)xwV_ykoQXD<{z3iG#{7dKq5DJmR5b zYWFeqFq_7;YiK9cuGOw{#P)F#X=+Wm|Df{A)&t6~8iLBN2bETF7d@c-=AiOhqW%t& zoS)aP{9XosprEK<`C~cUqkoz@sQkIo#Ct0hii6}Ho(eN$Wdz^n2tGU`e}QXpXx>1! zyg5PTug3Gr-&(bZzqwV9(;`jz)1dAwiwAd*MR@^#wftfO51dFA|g)RYsk#4ya01QKTrSs>rEo>5fG54AfmjnN|IuYP6P3 zJ)oLZp-54FP9!v-@~MNWb+)6Cs=|?Qf%5i2)koRNBVlTzC=xoL`UliN`G9KEUygjM zt7yB@O0LKeb4!T1n(OAcUc14WT7|?0KEXSchEZI}4ZX~+(xorCL23}k`J-V`~28rtiOB3oG~Nb8S8!exy2~rpVYF{L;+hR8~ZhTnEK%BumGd_S|BqI z8yrO#brT#}n5CyW)Lnr@`tTM9rEK+vWGQ<<6(&K^q&-eXCl$l~B}1l983Fid82UrY z3)5V4$3gHi{Ebg}hI4Y|0lot+%Z|W#2jo;Wrfk0|+_t zX+=d4%HjhKmA{$CKI45|w;-n|j%ZK{PCF(diym@?LE$DiU0ysebv2lV7AxSW#j?^G zY$22ChXQ8w48Lk)yY`L>kFzKs4>{fVsq|l88NijdPe<4-Y)d+=> z{>cVXyH?09*_UO0{_T_*^qbvgQW;yZSOSYbb4K=Z^{07h$YVJpv*m-)+X6${8eLxu zclA4l6KX0vyHuZz5R_9>F_#{HMo*(mJd`hsjReomSfO5zz2e}W2Hox>6JeI)<&^dP zJ%l>j93e(AjSy_7PT^;%1AHxVX0rb&w%K^8!?pOdt8k_SPC&xDFuujWa&?wRNF>^h z7t0B;Fjl9I?yePcgm^9KVW`S5ZQUr7mNqK*&S)lSFw+ZN`dH(^a_^<+(`{3AHXEetx%=JgQTq4B6pGuPuQ^}fA^QpMN#4J!U#5el~6Fmas~#SY`1`rk5Jqq`}R*Hw;io=jKAXVQNb7Cb#N zxk{AT=j<^rLuTjdc!%B=${;Iuy`1nfE*;&J(Abdc$WT(yZg}#uq>+)(jWwrwU|y7; zUWe(^TOKju+*A~hv}|=G_(q$(Tz4P!kge`5 z791jy?x9Mr)(Ch;3xE#7HiAABy3LkL{@D!Eb0PZa z)#%pTf|%gEwa}7N^ry5no0>vkxmxndz+u0!aXa6wcdn<&5W zjCcn)F84O>{U+^jyhB;Rg3=G4eHS$m)_Y)aoN8i}~CPcYO4^tu^Y)&&EqV zrGf0kPD?LZj#T~fm_M?W)hMLNwp}greH&0PXj zMe2w1iFlP(nOd-#=(B0cBX3)}=PhRJ#&*=kA4tCN@2SG8!MW(%Viv-+(nxns&P%PZ z`y19LiZKa9%D$b$UJa<>8UHG8w*7Rp+Jf@o-xX(inn_VEG?LLZYO`nlpFAl|Sy?}` zlj)r^=k|TtlA4T$FHVOq&sx-kV6ByJgzYgcPd7unyKf;+$iBGEYAVa21%ZQ?^F(!d zwYq~dDWD1Gklod~$Pf&-J}T&tej571mzanE6Y*4jA}-Eg%zC?~OdhNXv>J|@WjnD| zOD23;Ju=En!8IQl`ot2GxYOuoWu+f~L!h{$9sAknF0|UcfSY0+C_#uD)XI;3uyxOh zXa)*h%jvkK>=M#Q(U5*y zc%1yq_fO`Oj_s8gE(IV8z@+}Q3loWmN#&M~eGfI57i*Eqx2@yvN8F@kI2Ih>Vcrdw zeai0(=^j7(BE-p`sU@LN`D621UVJxt4XXgTbs(dYYp~#TP$?kJz3lmeiiE20y4*=~)Dz`hzu}7Ley?p5e8jG~2CIpzl?X@m zHb~x4s&wLc>+?+PK^C9leyqe^f zD)`yy?A|Vix)RB5AN3L>fJJN$iK+i-QHUi)iM2INIOxWX9$n~IMx8CZyj{n@2p0ie z@QXb{J=AM(1z0*vc#(Yrd&_@J$(FRFz z{-9|QT#5C)a}tS+_9WJ1_VEOE3G@U0NVf?4of}l)7h7h`-Y%6ZN7*!Sx{*S5w|Oq! zvZOt0)-QAj(NZhgius45_hC_+-bV|D@#%Q2`BIk^b7OeLn3Ou2rTHnKi;FEuM5#ZgKH zkRSy8Sf1S@vACJfBZDW?2S(i7PSX{pyD~3p%iB2{EYmn#kGw2qwzLjwj7vN(PAk5| zhQ`__4%K=Z&`(tYll_W(S*$TOS%5e!$C?-fI21ZRSbHLCy(GQnK0=oR`CELhctPF1GA}Aislu)6SuBKTPHMv2X@!@X zS6zQq`+EUvx$}JK+~sr~sJhev_Ied+JM};J-y|$T<>5=oT_nUKe75dQ%bSC>0XyX;F1p0O7@w$mBgx0H}4> z`F!VdijLX-l|Fu0NbCxLnZ|EZe3b;nD+F4*s=rp>@H8V;Ej$fVhWvtdxhrc&xyBLq zT5YF`yP5jSUh$s!t}d$v6g{VO+|oj&GL~^iml4wWcMF!JJ81x8f2Ndpey820ln#Lc zr_wd?SE?g#=aE=ygOB{B8IBFE>{VjI+xlLJYdY-I;YUrEb11~H@c;s&1+AQ^PCEG( zmPJBYGa4$MES@V80#QeGR3d321!Dw3Xyp2WRAQjtOa!2TXgJc<9Fku=}qD0&uB#CTv-l z>-}f4Nlq7p+DpOi@!Q!YNc*iM=yS4iN12X3kxJbrjPyrt)a=ve<$!o~EL)tVF&^9C z5SJEIGo*e!`D zQEzpUtEBA8DA7$?tZogjadv{%ty0gn_sBTJ6A*?MoFk1c$=e@kVZFr*Ch%ob^5@%g zrw$McN92!vbEjx;^JoaZxwPnX*nyyoo+ZuN}zTX8{m~&))yy z_x^63sT%rilbZK!lS=bH`Qn*b8``)C*c<6N+SoJv!z?v2R!pLw7iIWsj=D)$L*q=@ zrQ9*T1slK@7K|D`&Icw#pV}156f0S9yb-;}XZr)e3y3G-nIr)lG#7;N&Uo``(*5iA z7EiVgkaKO8AaFc6*+_BBV1j1R0Ykp7GM_L0jtjNix;`0BySB()kktlMRcKfBhO4!X ztxS6Z_T0ty*I{o}&GjPmZ%GC(5U}Y25L&;-&|quZEbPGxhd-(;$36=FGrUpbwR(bmayi<0#}+k-SDxO4DOgf<2* z=c&%%Xvl0KNNX0=zeMa0V@}7G?#Q2xj*>>R3tzrlL8u#2!&9@o(Ew6Ms4PtPb9uY-;t*3c8FXi2_e=D)M1UGfo!t~fl$GLliA(K6X7~NV8nPP|Qj3Y|FkB}s&ktM;9 z7SHDbq#GH8i)LixR;=r{LvI8_mQzBxv5BMVa>fDudm14V^nt!XG7q@&;gVXl+Vn~M zkt|iAR3;zH+bu^Y)k)hya>9S3QWPA*rCP%DR5bY2A6j^!f z^a%UE+NB>IAE)nquLA3TZx#GEG$%5COM0FcWvF|_yKtP0vOJG~ppCy>3mfc;iKOTz zFA{VgShR)Wq9@5wV{Xy%elsBXrcaRmrVrveH^OAhI07?1v!J-x!ONtJ(WHpS%j*qZ z7rAc$*1AD&*BEQbP9jyv$eOagU;+nZ99$V){eX_fEgHQ-Ma_%AFlE37nmo*oJnGg` zb42re*eoG(LlfL6$%eBq!^93Vc?x5(SepI*oGU0OT_~;>ebhch=-U?;-g9kjiib&qL?}c|K>4&jgq{ zPYj)I=?nI7K5*{mQHl36>yK*FYfL?8CV*>HknrJGFcb6a!U)4cNzA29D`T-(95tNI zLXA|q{Pk@1b?O{a6n+MVI3tM}GZF;meLt0~4)2HVe5Uz3@INPwNMy8qp6^q@^V^d7 z-$)w&N}j2tq6SgCj#@9!*O(USp$th53TF8k$^6{0A>HObE?v(kf1?x2E9G$=3-QhV zm{9%`1G%xU`Ka0DVEi{DySwX~Ipg&%hh2Ca`%Ysr} zxvt_|x3||50s+15O0CB|K|MH~%W#mJ>Y-FHn@1<|p{8l1v-4vz3-$PGa8o zln!<&^2mGjaTTvaSQIipJr<9GI)AZ=&expXmM!TzW{+WrRXFwteYnma5T(Rvf1R=i zm1WOa&ojlgSo8O$uMVzb-_^*ox>mCD!R5G*0;F0vHiR;_xN#cxMayQ(L4el0)ds7* z?>DT(7sy(yI*TFMBp-saggGH(gkD$oE9_n<1qENlqu`X-qGhde<)V3`d?LP9!*AB5 zVI@Y`ps}Y2Ma2Gz)SU+XY8+9W_N#<~RyxTn4!aWM}2?e!JAFaPEAhIjG!Om?0G zUr+qJxmu92z4FdS6T(w5^IX-+BpU0@+(S1gj0IH0O59>ALv%ih>}DB=Ut(7wNi0%K zQIz|g;;W$Yp+*fIwy?_DoWf=Aa3=!G(L0z`Ur^X2;%aL;OOXmo?uJMa4}pY+(sc6N z{9|QWngsN=e&m!*l`FHmh}e0(G<9106@}rs{h)UwH?Z;s^@^v;c{1B9sAA)>$)t%l zSpU2f5y(G~?Y=Wt`@dI|`~ybw-wy5?+|a6EU_6L?02k}4F2*jK?i`G%)mPCnKu4@&1c7nvEDoXul)+Z#$IAtBc&cAa+if~Cc^bM<`g{k*P3d<2JVflbn1(9iKy3d z_m2I~2S-I%>>Tr7!2l2pLpb!36IP}ek;sPR@iwnsT~@sj1CHx}ZNFT)d*LjAtKL?d zC-!ZXq3H*)aXG(n7>z|5RxpmsgnlAY#~6+3Kb5JlImjfq@7 zo0vaeurgVRGLT^@Et3SM6-YUX7GbsI6X;6EN}5k9DYEGy2NJDpcv?gswkkn+3C0~@ z6)TQ0;CtvNewi21eg6P5F-`dTUnm{lE` zBqzxptx~g&P#%_72*cHce(2O+wyKlpX$9lB^MC04ERyC1!dvfg-(vK%g3k2c>%>Yp|d zwvlS32Z5{|e+4a-&Ua|{VkxTL2p@Kc^`|h)#cUcv80J&e`@a_@T?pF9c$}cjyfG zc#iQyC|3W6w`~q%VQB(n%h8PzDy;7VP!uf(m|QO9Y^%e@C;vwHAyYJ6J6f*~&wogI zCZ$8Pykz)Im4|G@M0^rB;3{6ngk`#>u<3lSDI;s#okL3aUC#yI>YX!b50EXx&1Uo` zE3mEFz4nR|cj&IGU&&rQlL%~NJiCXeiysDHJaX3xKQP1!?O)nYm?Ai0y$iq;jyP794>&F_d*pp zxp4m;*J`p8Zd05z$K6J7qew>=+5;01$ad1litlUx(uHEWLaYgzv&(Gxww|%}9N9)H zv5pyZN+a#7M;HWOnJ~H$bY{w=Hd(sg*Y}5T84`9k01=xlgg;}GPoAk)6VQRca5_lI z`u=v0NEgP@kx7K?12n;!t8}<-;67+l?a5@8&@FohTn+UWl^8k_hEn+f}bE>X^s zl$;j8DUKmJDnkAZ@I4a?r?LZrAbupoj~(Lk<^7WZ!Eka@k!5y58M<{Eu6ApJWOtkh z(k9H_pw2;JJY1M5zF#tGh| zf7jLrB@gAfUdC(1FruvLr39s^6#SFTT{Cz;p@|!{f{f(dmreZX*DbHk^P3v*P33H$ zCoJ8zLGm8vA+moaoqC}SwR1k%vh4G|COVL59m?v`_6@SXbS02}Nw+##sV0p-hcJ7k z&C)+L^G!UsHaxd=qgtxVlw8$|L7Lhw;N6p_)VY`4Rb#mVGNzo0lYo+G6o8adYMpi? zr|P4qX^Qi?yo?gn6HpE_&lS}>8p2l`L)<~R#ty*=iybcANy^eBiN!Q_FF;}LBM!up zfvjNflv{&8DaD5QQ&0lXCO;iuAou8_9aP)3BxkDCM0}9XAjCwmt3rv?4@99H{3Bn1 zRE`Gq*#V<9s%lM^iIMBZn886N6tC)#M!amsPBo~W#RQ}kW{hY^I;CT{M2))?XPPpo znp2i-z5(V7M(kk+xcES<>N(4j=qk0?a4T)K==7}cbro69T4O!Qn9h?>j~3xlJ^Wun|aC!P}HoJ2P< zf^&G*zgh^^cY-MXAt?R3_@uU_=%`4)K`!pmcw%FO4QK7EiAv)>!dUZ ziJ{?D%z`yFi=*hX`Z})=K;!q@@sGla6(NWlr={_ESAukSl`O-3#HqjWqe9&Cm@LQr zysh;YznT43!pSz*QupAxLxW1wb#t1stqaL$y3vmE4xARZR0fS5+egFrhKUE9%5zvt z;wgG&7FC_&CV=l^u+3y!McJo{MGctpuVIuOUzZzI?984cAIa4Gje_~`X(6K}t`Nmv z4#qIrU-@ojj#{M=+SA?pD48(VOiDHOOqH}UVlIJFyr9$yCD4a@tuTkt zG~Gg)oiEP;9O#*Xi4PRuNOm2Nl2eqNknKO#(K39QF)O%pg{hnVmMqL-G^(+~)JWx~ z1tplulH-IatIXASLc?P!=kvYw?Ud~5ML0m)|LvQ339Y7bLn3G|!y^zli5HQQy$*=C zO`#HrJ@}=bMx|zm!46hcvMO?)Z4!%#PEXXM;b7%f4&BSia z4LvMAJUJ|T@qnGpXVm~?Gg7pI$(3a`Z`RaaONFo{$5hpjWh+cwR9aQlzSFITX)*(y zv0=jYm#z}_P!+qW1C|7>Z-RapJNZ2ANx6@tJgXNREO@aZ_w)I^tQkecv$gf~X15H_ zpI`m#2&reDUlh+%`x_j(Q@_Ksi|-!^hZ@{(Gr5=1>XD0<5L(A6Eyn><(pDV1r!=?C z^~4UZv1f;9kLlZC0}eMc1gsoD)cUh*ra(Dc; zjG^3T0slA5AExGJGfWF68C(k~F%AiECYjKj!@v2d1L=93vb=PP5&`+)POgpWlj*}R zg~;v_2Nrj#CUnJid?RL7jPGn=!_-z);5IOS4)0sdFH~@LE`^wKUNtwvON-g?g51F? z<+96tBuHe(8xAM-4)J`4x6T8kvrK$02 z7~Hsr6X`jA_u&(=Q0X1NGDfKRCouc1s~ldwv*dOW#`*Wx#>)VEGa8ms2WTxxsU7i2 zqfGwh!pRH5deRxj(9Jg+kq_((hi3CC_nLO-rS`#r^ZYamB==>&Y7XXsJsxp}6R@&` zMr8FN*`ixM_D>t8=uRu#>?3r|h~1yq)IWro#E?0CyjVi1T%jnv$S)${tfeWs)9cHW z%dJNCjD}V(&h9uOJa=8DtP-G}KcFEdFH|Z>E9Al(T`*N@od+3yU=g3L!~c9Ry`I?h zUEVaKg=>MHO!1P16I9$m(n|owl+0%v!9{y5PFhk583m~EjGdLp(_wnWDwJ(IU7@Aw z8_Y5fm40$u5~bcw(3Uc4t)+h)JJ}C*%GNU6>j{RyIzq0huc=(q_bl5> zI5Z4>8&M9OMTd&QdBDATtcGF%97edMC?$dktgrH`?P!@nK^zS~k+v$U02 z21Y@hriY3-`NDI9(Msaif~>av{g`j-mHAnd*&oG?^R%l1Ud`XIB5l^v#aemQg-Q=< z6AzBnm}Oa3$)1aRSgCHUWH`x9D`T{C^eSMh$(G@#)YT=)<#k5p&i2;kZpP9%PMZyq zHgdY6R;;pv_!T55UQ(bPlep7>^SVle|gDN}MN3YcB4KaPt5-zIi7 z7L+2hx5xMyRdE&VtA}f0OnqrI543ZOkMIXx+cts+je8A7j^%X?;+wuYu}3>9PwR9G zm4|mL6T=#gbEQinPllpwY37%UFF$FyZmRpU)uQ=cBVJG1Bl)gbL!oeUqM7LN zf(5Xl?(*@Y0!v<(!|e*l$9~%MIlV%;31U|M788?S7hF#Ji!I;*{x0q2ulGr){{9|( zPoMUUYF%Lyb|8r>%`aRj$#0A@mCq&q0IOx+9c}Uf6=gx4OO5l4*0mmh09?7FlFszoKz%*o*LPj|EoOG@z1h0-;w)`aP8~?`&0m8IC ziy7-o6Vf%FiI__q*kA+_`)g~04*D>up=UHrhR$ATn_rmzprD@Ztd=Ro@HK2>LYYa3 zU3EJoSo!cJ)R=K@SlibHzzo@P-pKmbdj0bROYjRM5Y13R7i1I9IC-k0>|44S|1?(WT1;E@XhBd%I8 zN=JtHFyP_GYihIph!|n#^Q$XUm3avb-WTkf#RRB3Om0W^s~wZ(MtlSp)8k(WywLNJIn{x&Qiu=&hm)CG9i0B zM;Ua;`yG~kK}n{khl%H%a2E6PEpd8y*l1h@K(Xc-JeU>?`2<|Qk-1XQ6_+Cg3jY%A}jn9`-MqQa$0@jJPS|Lo}QVzpmIYk+Cr4}48;9S zmu5~;CD<_hYt`*A+WP+d^1$H(;Iy}6M2ONzl2ntY&Ivf-M7M5lQSb>@_f2S5P9xVv zbwYEXIn<(}Lun_dXiSjize&||qm*9jg5^&%Jvqy|aW7j)5`MGj#*fChUFSR6Hc87U7ECAv z8GD*mJDhRV_p~;{t_ZKk!AiH`Ay#TAm<@+)?=^{Ts-pBCyP*WsD$BWRq)D&ustN|` zIgjjqQ%5rlP>iA^(Dzb!1NU$}f=0as8Wdt36>~l!g9fSX)`uC2Z^XY`P7h-K zSd_zhFoqefLIy6%GbVho>=-$yfCuSW*3iNdu7)Id9SZ6yCc~a{^-pZGN{?$v2wgQ4 zaw1YNaF8g4N~wQDZwcpL?W|6QGZUw=o5VD@d}L~T5rG6*JWSRfg;C(}hBkc=WZ17f z;{KApxN^XR#E}Zx1g-jcN+G5+TR$+|`9f&e5lG0oxrELd7WR5cCJRuu-l6`!b4EJ6@i}ch7Kt4tks>ZP4BNEps zw>cI3FE^eYpB$Vp5I{gB@IXM+|L@1*f2)qlkvdo=IA6G}`*C}^_*TZ`xs=oNqO(KJ zBMpukWo8;+U|OSWfrXjky2#f!;Ond$6KcZE*uEIQXlr)9*?vC*GDK9N5O zy~4V;h(V!=t?pj-pE#$kUOKzEp0eA&-hcZP*HREh(T=c&(F&VBG5BMA8_p(Upb(of z^i8K;hXuq%9G>ba`{Cy|y154SDuI*uvq2?+Y7A*75Q26M^u+G;8PMVN0;8s0WBQ*( zxHJ1{7o7$6Z;!)v5$u(>^b+Y9TsuK+#Xx;X6?sSYp22nvx@;L6bQA8tL2V`4f!Z*f zX&`lx@{Xz{T^dR)I60~C*2Li9Wd8iM587a8HU6MD-d>V+=jOxDAUqN~Q@aE6GMZd$ zPZ)24-b$3?1oFq2$>nu|$iM)Y-+YD<)e*^AjJKY{f)L|$&9yi`!;+y@)zFv*#Da8r&J-xd_?7*bE zTd!-PXx91o0nNjetd{-!ZH1g8BXX18AAtmM`4YC%U2;#p-&1EoGCoanyXjv|lCFy#}u0r|xz9(a(l=#J-*v?j1mzTATKk!TQ z)on#HkKLS8ciHliaXL%f+DLLf-a8I39A1U`naK|OlZ;g2#b@!Ry7OpCFcccaQ>C)- z!HVsKd+DUf(fgplrgj5-xYppvC8a1_aY3M^Z#};r-*!1u8Iv)ZBG^zlc9w}nTs8zm z)b68E-qEi(ftTpPu69YId#)S&vUg3Oy`t)&i!jy))P6ub*AC8erZ1b$(lW2+*G+^WOTW%>a)89!l0=G!#uBfYeh} zlHG-Tlw_t84f!+7XW6zvl1n<^6V6-Z8XFvRfaOlithr?r7hfNQ>NCm@pm>Q~>}FEF z=qEln_t3M5Ha(N$3dUKR9)Q?SoqqZtJbLRc#p`C`eZHHyl}xWRXX@w{WpxO*G)IkV zQmU2ih*e5UAWrapxPdag)iQpiX9Ba#wYGpaFdF19&>-BSKBFw1{bF)A}tlV8p${|(r4%ct7|^fR4>ym2c(?c5drR@<4BH#Kp} zXOie@jDrh2OYmz|UwN1&e8@4Kr7DKCB`Yr5eKy*=Z|xE|sMAXV z&c38lAIZ$UFlziAhMLs?`TWKxB^Ay^+|yh#aYnC?n^ zidOpBDdh$l@$+0rHBrie{$uV0Ksw zTcF%33VQN&E0rFn@a#jQ%>!(}nZtvQ?W(;$ZsprT9pj{3u0j3%VcP^Flda`Aup^bG z9a!6vw<+L>=K!3o7=y}?F7zZj9RYp;Vq1mbN|Sw9F(VF;h_m>cey-^cQPsIF>{4ZZ zjGzdwH4?Ls&JwE>S(q|ll*baWNnTCNZQ1i#5;#@AMq5D0`HRSqaFgM;GwAzOlR% zzd(ZmYMG;cV?!@8w{Jt;nQb)k90-kQ^MM?Ux`9S1G%&B>w*66G$`=%{73mVLg@EnoMy% z&ct}C^(l+v=XB7_H0Lqg+}S-R(18bcMd%RwygNj zn**Buk-ouedDSvxagX*zRg%A@OPF@W>mlX~E&VaCyvo%qOPC3|SyjyD8-Ie31Ba(m zy^<3)O@-$d6?5NHpi&F1RX?6Bm)X&@ONLL2Xz8!%!oaC90`1j;)Ek9SbhO_?6rm5y z%o#h8oRyLIDu4M(a%fv=he4#8jZtN1C#lSGG|G_EJe5@aWkV09OisqALlB|v6nUpQ zEMbEvwj1h2Mz^PnK!gC!haaLd)Z6T(bY19`!!{V<;Xiq3bmJni3C5k!zv&xn5uc9z zYLwJUsG!?*ZeNj(i1}MF#PpY9!}V0BiMF|a5!$Y>`K)tsY=y}767N-_Q~I~IQBA)y z$GK8^C9c1Ys%uMc>#a9$K{VmLCc-T8@16o$NBvpMMiV>8TM?R4Pstlp`n{1Ex0J5B z>6o~Z>g*2Rd1e3^$hudLhM+}*%in^8`4#-WrSBD^4$55FLonf#)U$+q1h2B$C$f_Z^pkeh!Le^C1@gp=m z787*T|IQl@NMT<)6j_P@u3oaoBp5i8O zXza9#)D0yBA6)Nl>8Mk>RmOzOPgHoTMH3BCuVK49o>20%fgk){@#;E#x@%*aFNGq5 z<|)k}gSr!ZoV zG;6r%uGmprjtKAu=Q1r3ku#37OsW0e+foclY3=3MnV7B|lI{1(+yt9X_y3}^+<84H3ti^!EC8H5NrRZNJAKq@eNt^~Z^Z6H`%L>3&XBbajhj7zDg@^61=p z_(i}tM^%VJ9^am+=|kpQxW+2|BBS!yLiu);`&Wh-gMo29)5{~~AM(9@mNC;6N9-Rl zeRlM}5;a3kQdj`9$WcrgOdH6gHjLNByWG^A`mCY_f?87}HG$ai=~X6GO7)k3A~o~k6@Cd2U!Cyi4>NYuzXY(*qYyW8S0mR zU|<~ID&Ce2vbNn3QgqDXCnQ==wBYL!gVChjuL7D)h|lE;qN7N`n;MO}jTfILd;rfz zQ3P~dA~kU z^wXwA6+4*6oo8v1Qdcp^j*(rDBB!PY+DTNAX}^n%G1tH9dEkR$U5r~Vsr5%0sUMFY z<0?Q7uIR&1nxel#Ygvcv=zSeT>DveN{T9Sf6{u+HTl%8RjLlFP;Thno*jq@ZEhuwS z$Dj%p9XroQb1)Yp?j`%(dZTLH*2Z~m6XPC)!nB~3Oq0P#=DuLE{@_n%E)kPY>)a@n zQmD8T)&V1nM6>kCtEuk^AczY1_q^!n^o6`{K7d#T3f~~V^@$aTUCcV(GYqv@wUe2D||F`ld4~b zL<#9y4z6Cc7gUU{Mh}x<&voLkof}axDNky8o^Q4gQ$C+8Z{6C3L2B%4dqBbp)uM1X z#M*$`b@ER_{dhIY6Q^aN7oQ;C9O=~o4^gp{ZMmRsvw>s}xlT^B4c!fSn~8EsqH85k zj)@>;+oHV(Z93%vzBjl>Cxfi&m2dbQ3Wi)#=ddT1Vup3uzZkM-7JzB_vSOtrVQR?^1s;KFqhqnIpy>T1Mv%3UwVltg zGC!5{o%xr@qDnb*qT6U=RjdBY5|6$iH1EV ziA_ueQfXOcFkDEY3QxbtiJBxq3RT2s??tcf#jhAUrZUha-w z8Fgf`Jnxx+Dh$e*T3~UfbzwXvLYF~(qmiEjptI+D;Ij#K$$TN8I1BoU&A5cOS$%_{ zIQezu1IgjW@C8nb#BQMAxIv<4o8u`mW^<6ZB?io1AK8GjNpE35yF|AoC^(CH`Jd~+ zO}6bw^<`q_RUkP}0;>C#MhdEtU*Ldc%&w)LQK7ukN3LR`u4YrW2sfWH$=(~Fd8W66 zuL*&9ND^h@k7YzpG2qg+i>042-ZOe=A>ZW#br9c{h`zA;yd;*dAm6i`KYbN>%K4z) zV{3R2UBh~^PtJ58-<1Np>8IEB>=;*&V2b69!+fzBT?#R%E=7rgPbYg-9hr<4iyw>5 zo`eYnjMl^X7 z#o}>it19xX+fb8vV@ZpZ9Ra(J_!~&!`ubI3X?oz}CM(PqsiiHo{#!pF^f#1y6%FO6 z@{UKAMG=${j9XLwRtYj?p$0jkp%g17JC%s6qwa5e+L7!kN{6DTa25w!xlb3up=`$q zb}-T>F=!yW@40T!g$1rRdCoAYX!V>)p7+9X4o&SwoQceA33gCo7rtsa?v%3GurzO{ zE;}ebbAXc|ULB%AG8f*4mQpKYDQk~luFfLj%0v{BCp+k%(Db>Klxm90za$hGON>%M zPmM#8I~M+(gi=bap-M+-t;!Czwq!z*Rw&NVK3ooQJ0Uo}#&oTO7ZW|H0r0as_BydU z3LqR_?kWjmedP+!YF78N9M9-+SXHok8%#ZF<5=fhd4j$|tqTTkNbWWsg(E~2(!o7Y zPwJT;D?c9>v^%jv20K2JzDopppIzg*N(W=@D5YHQQkXBzK7O6nfPg!?;%ZOtco$S6 zS1s%$g0gR`gEJ3;NV8wc?+D>&`_;2gwEzWq4G5sQ!P9y;G2FQM0XEwr#GmZF7}v+qP}n zwr$(CZJVoZoqPYd5qm$Jy(4DCe3@^(%^W@3$ggE3XQkewvx#2YI*>|vBW0VmDV|C- zW9^Jr(V}dgiHh?(MO+w3!Wo2SS)+(m{Y;7;&YFUP_GHSr9p6@IJq2`kb|+Syd`yF=c==?r=$7}7=@ z?vmj>M91(*wM-{PJacHm5cN(j4ek*tPs@vo@L>rHZ{|H{yt16riAizGI&iFGQLG4Zu=mj2U7h# zhO1P0XME$bxXXoN6_hrMi-=+U^tfG+4nzCC|IWQ=-zai$Cso#wb92>MC;2DqVBoY- z0#b#D;z7T^Y4Ti{G)&0iJjjQMnC!foT2wDm%bw<3;LqBf|3A*54GI+;U$0@vF;$!Z zui^8NRqKEq+7;}f4YC!V`UByEOKHK~>44ZlaDCLEc$2zByngyHry@Vr_#I__ctRd6 zezEwTV!xeerz$@@0Zt#hZ1Oy@egR;JdyAv}C9yQo3rsnIerHC6r)&UL*cLx-Pz8R@ zVrhSzxDtNIED+D!Am7D28GquvG$7MHZx^U0KWiwb1baks5;@VnXw8Yw68#-!B@iIY zv|AUGzO(?)vDzzWDWPQ)x2+3`YFUTNeBO1u3&kyqve_Gm=khkrtr@x8%^O-s>trk= zAsz1z2n_#UT9=d_G6zQKHg$bys&Vw(6)1|Z3S<)o2S_BChw{pp@~RLpn$T|U45@}U zGzouov;v-)^Sb9^?9P7#$+MXdp1en&NGqh9)8G{reht>0-65}j$}_Ki>*jT#6{>Sp z4h)44of&2syZ-a99AUr2upJB)aA^~1?2??d5_ZhzzDePS-#&Ys*^>u%gglSdaTR6x z!wp`Q1L2W)dN6{I>lji3c*R5|<(n!om5MrfOD`5K-_{F$q z*@!N4nfQ#Vu}uA51a%Nb^mJJwGJSpgi1xc)CoDAM;$5It=<`bXjSx$M$~$|xqQbDV zmd`qTt|m|yugSwSROzRc)CTjv$RUjSPSq3%Q)0Jg!cknvRf2jF19Hrtew#bKaK za@a#ub<}-jJBV+PO9*$k=n^+Uok0n(WkEg_v|_MuwRFIf@By(ldg=fi5o`f-9De+f zd(9K*T_9GNw6>w0!388{(3nzylw3irNyd7^91ondw_~_p1f0^Z|6~&QAX@Wsoi#~r z5y|gsMgVZZ-re|#qB8elviy>rRvCaXW>8UPtXLTl%o2d{FpViHQuaWmpdkSJA@%M8 zN1?Z3It08CxhcDlYbd<|lRz0Cc9I>PbL^I9KBZYpZbM$xcCAP2L?`Ww zJ?{D7NK_c|_|6Q-oC`zsmBCbOi=p@e!v{!!ElKlrV#eaZINCUYEdvt>YXxIrIDk_V zbC}ky8$3t_c&}R)ol}d!f*+RjUdKWjboE2D@gN{^UTYJH3CjgolzPO%as4M90)`Y| zoZX~tgi?s%t04F3!ot(Zpo6nHILh~eNZd&~UK&V~ai;nvw~a&9tHpSuMLFlh%lpmQ zc~T%Jx#5HmJ6H1jgN@93k!QU(HqMT)S%fC7ozSd=9kYV5SvJDzEzK6VDQQUXtdVN! zJS7kum>H`^z_6SKRvlkFVl=GK_oK76$K)fmJ&-a!#>j-&w7_(}VWo`)WHF1!4#)aN z^1#I=MGdqlra3fmR8C;Ubky+b z?5Ke*3&Ah-4SVD^`RP0%R}70Q?sU-qF=|1>@dn=1t1Sg$-VR*O4m1bc1A*uQY3t1n z3IQrT^=LV(hF`3d$1m@nW43*?ow?kG!b4CX*U~-1N_4QfflbuiY9YG;D2+mzwLp0$ zE3eUnzI6*%H7Bk-q(3XMB3f#P)-;0#tYubxs;?oPt`f)eb`HlMw<+{0o{d$B;CC5C zex-=~%M$U?kN1U({Bl%#%9rGA7~yOnbv24+Z!n1=wme~f7`#KYKSF~61l@PAHv*ML z-qa`Wch0^JW6wx?A}1pCMbEBnhWcdKf_{apt_)tGT)3DnzdzY9(Z_cs^uUM-tNu2y zlys4EzYP}Mpm2v5bnn-+2D-M>l_?6;v3Wl@AR}m}@QnakP^boq|8ykX0mh1bw(NE| zlHfcga01^dehNyw13eu?&x(R)*HQ+>x8uE{dJ7l~&DMOT2;zcpJz#4Mq2qRil<0K! zx^351fY8lAmcx$odhqK619awcl@78gO$<SHB0Hc+dBWpuTECIbVOE0`PsH& z&HVioT??v04@aamMGKV615+v}RaBFU#C=R8z)I6gTi_{q@!h?nhv_=unBD>78-WE7 z7X~uPcz=meD$KTb{?1~Sr9a(tpXQ9-W_kL!F7na-+#&l}ze$Z|yU7g-A%fh|!*jX2 z2@lfZ5=LFF1#DyCE#G6{oetyec?7?Vn+ zybAqqEjnOTBih4RI-dnc{ja5OAtPDc`$^6sGSEl7MW;HRYsB%whLuTKh4QIe_cl_S zBTaGUU;Mm6#yCL@y~E~@VV6eb+{u_*hhOk~Nm+0E=hG}6=>FGfE{W}**B2N~moE-#Vsbz5eV!49Vu z8>4@X@?x9wcRiMcvJH61dx$?9$C9SoX5Y6swj1~@mK*$IWt*Cevu^pCgGJ*NQSd9Q zmme}1uhC`fUA@2T9R==oG&I+F?wa%S)xj0zmdheADn{4eM7dIY=NOK4O8=P~9Aa%Vpd2Y+Rq`H7H@y&Swzjv|Gd=1<}YE{kZ7Mst11RAEO6rl zkuQV1rtmL@j@*adWt&_>xJZ|udWx%v(J7!kuz*Mdk!Owkn2k%gQLC)iA zBjzd0Q~#9}kad3J9>Od~_)`+?vFSgXp8z#F2@BE_fW{6gR9~Pn^^NB-Q^Yd~ zqt*{s;BGifKccfa*fJ7gLV$i6Cb}oNMMjo#o)A`G+i;+gB*LI7$UGSwbhGgC8g_aR zfqF_{|6n-$O%QY-xg{Z5-zY9V8DM+fp(@C>&LaTN5b&lvcZlQ>@KP0YyWAx0GRh0x zUS^#o!dU9~0Lk@4HIOBJ7F1I=d)@Lvg_JUbHZCZ`7i|46K>taOAe_cMVoHs_zDOmi z$si{jl_T7T@0QjL%2RNay&VC!AIxXL+>SR$tu(5q5EyN2?v1#L#3C>(q?6ew7y+xr zqHXgNh-(4*4^pWx9LBhL-xLws4$S9BNstlh6i=v4oB8Bz0EFS}Ytcz3K1 zjJNKYYB*1qId!gbUnddmDdiD2Hr_BeCyFXij?jWm219)b>bTlmyheVrpFcdE|ES2-S?Tvcz`ctd#2P!GN|6^Gu?%zSn zln^26?+f@}pM@`uZ}B^RU1YNVi*+YtYi(frhvEOXkQFv1e#ew~3?u8!tLk@d zTg$Ay!-mC%Q!5C_10n)!%^RBL+cZ1MeIj$^?|^vU0DY3!%|!%$7yN3?rADd}q7Z3Ba@T;CR^wEd z&Y=1tw=&zm_*{F|*Hg0!iHK%hSo-D#ssx zddm=}NV>MVlYJ_hQ`JtkY`H%g^aby%Xid2mr|o3 zYp5i{f8?R&IztRQWC_d;6lPHfsljo1}(QIM6K4{or#zwIX&6d zI>$+eO=6@q`e{%Rrz+c`xu9+++e-I({8|h)8xoqGR+DS^TU8mi*RuNDcFIu{7wV=O zs1$E{QW-01POnND)JS+Xfgoa8|93W>)7JGN}I2`Nj@PZnWHd01h!C7KGs}cTC)4qw4(S!7dDJg7H^Ju zC8_1_4>HA+dnrz&u*I-GV)~#vV5wxSQr5T1H;ppSY<)W)AIn}jSS2n9PtqBc;OHOT zO%>*3qLi%#c+e6}gn&Nsx+?;N^FVhE#W zUr}<=+r&W$VrL&v(S=709l&blL7qVDTM(6iTbmG;yjx+<%NM!4s-fVQ@;~sxymFJL zu?%vLo_NIAAq!l>#GEqHvB(HSn55?UeE@{=OJYU5-~X*-Nt*^M;{J;J>i4DB|19qR za23Id(zc8I2;Q4&_K8*z=7jwD)(Sw=V*SNli4nm0dE(LRMFmJ~qdFwCHMqv9Dql%E z?Dnxr0YiZO$-6-ajWWSXD1jumGc#T5>F!LQhpVZz05=9yf!sC5457u;X!TkBP!XgO z9=a^{+EkOIHb(LN(%xaIEXb(HEXd9wO&6aHUHx?p$grL*cEG*_!8EZ+*FA}poOPMg zXL;Bx5FN*%JsuNv8t?Ee9~u2-qB(kF&)IX(LofLd%SyAvyO)q8*BFSNCtMTuyDi!M zHH)x8;m#qBoB7zn3-F%S`5*D72WX?*HjZlSwIrE_z^+nO4hF|~g9E$zoSvOHulIiW zNl5~uxS|4gQ0@^di2cuwX35zmN_1VW{TK&Pj_XJ*Gb%+Ufq% zb*Vj{;8SF^tjXPiFS)K1_@}3pH4K$`-phbIjAu!C^wCRhEM`a~TNHT0n17`=pd&6z zQYe*ypq!ge`I~@GH9Td_MW*Nb;z1X6^DFqzXe3rI^Z76H8k&H7Gid$U1UW<`68W6m zfLy~UH^eCXfP|s?0R%-_*wx1)F0FoMFlnawhD~Q^xPA_oydiE&p|*tKji~JvwuBS4 zLFB8qI$#`b6Qm5>IZ;+}76q9YZQo=2uf_8SVzu5vA_PSFEIp1)u01?UjkQn}#EY@uj z6e8Bpcbli{Z!fqNn~vYx;{{w7M%FF9CyE1%5o?HoEV^|y{Kp27dDG zA8wFGSEk`>P&SpN-o_gJQHFru;AGU%@920W)sp5>k2)Vj)GI!ugp1$MxYTH$c! zdKkrhZFW#@2?|hEf*kC$zAAc+t96(eRnH zKEjgHLQP(v4MEO0o}V$?d_BCQJfacTgE}ERGRsIr`m7$Tlg)9@?^U_RUyXsw)vxB; zWR8BEw=Nf>gX2Xbh6%Ln|u>4+BFpcgVY9AECG$v3*;R5r1+yAQ5MzY`y@?V{v{IBZZe}I`} zg-x5^I>?h5vSGi?{0C6N+?*o+NRXlFmt{ommw8^C2-u79vQw4z!aedr`98V&V+MEEgG zO@Yxt#!D?mn@!#TeffuuR>yE%8*yO{vDPlM;r@{4gnejz0&wYkTwu)BpEF>(yCtEJ z;|JZkWOSJGkSi##Lb4?$UINQ&HxyTG#8I8mN1o=}r9?hmtJ?_vXtff}h)&x~ZY#$J z?6R1P8U~cG*YdSnaXe>DZ%hi;G9Ob<>aRF`6D~V%OizJY3ME|^*sZ$yh)h;1sm(Eq zNxO8-%1a+fB7W&cJf-Ss_F@L2T^bvAycgiWZXBfMO4naUN_}`&eNv<0B5uYWBZT=RggF`1{&*xBxW@{rPH>4quT|l=&NGV;;e9CC{VQ6ewr?qZ z+SFM?yM<7u&g~NntR}$xTR#p zO&eqe1Rfc|R9I5TRtNJ;9f&x|$(EoLa0G?s3n3Cp%Ce81ZD+QW7MCX0rf=#ZkUx08 zXciO$oVh>*%96Ibi&ZX%VQ%{O`{gG*fOgk|{Kz+>&7Oa(r<>Xc@zi_TXu#tIFIxT4 zRD-7G{oUFiatP*TVoCi>-J8s*Il+O{OEkn(IqZS#$VDOn%=37>kMtay!r#vn{o;3g ztIN22lnY^cB?bLKa`cz)`+CF_eM?662t)lS?u7KF)BYE?lyG2s6IWhnW3iytu=Bz( zVXI;le4=C|PiL{=bXJ98&w&@GgMEroCT0$H8waJVRl!Z6P(xHpyc|amwbQjeXn5_I zRGWtLDYPsI#oOgIqrFavaPBE0!yx@LonzX~ze}w|rfO*!A;OM=KhehItYQfoMEYg2 z5N?#nNTVRMkf;_u8@xi-1%Ez^vXC8zdME4bj|b!w&+N4^5$}8@k3Nc6?3b8hKX@=R z^>ihV;WTq&2o9LAPLn8*DVuqy`=8nSELc>HA0n73%K~adcc`JBfR0^EAy)o{44}7# zazeC@rnNJaY?#CMfsy-&n1@-S_i?56u@JdFO{TV`ch6h^;N@?>c048$e(~jlZjopf z?AoHix`O@g+i|)3Z{b{j)w;_;e+7d6Ypwpz&rR0xJOpR9V&|s9P>xu_!4aj# z_fbTTx0;?ZQ#ORklZT^VGLtumK^=ct`=#)z>(Nn+4wY^%(bE*eQ?Hs&aKTonp1T*P z!MhiiZcK;lW^K(Wh9^?md)zG(2yC4UCT8O{`i7Ro9Q4baYp9U(3$w+Bw%g6l7Q;wl zZ(a(TmUYN`2j_4#^=!)PiH?GdUD4w@0G3SIr0s9bJhV)pvZhlwy23(oF4 zc2c{iyn{!4XimJ=Cm^#-TfF#bSp>Ei4>hGZ)Muc-S^uU&{?l^d~mfqkAtWnF0F!G`BapK_B%l6<-~%j<6Ph( zuQhzw;DI-;MDB7(8i7gNQYg%afU^j-KZKXq3FaNKeh&3*DnG=BfY%AABs(ueqb+D7 z2`5#KO)KC5@>!!SY|NgcCDVv!&Gm`mEZ_cCy~cO~rNoCd4Awz7RCbPEnkL^~8k?g( znTSTnTMG(EJZQM+f+R>PxuZZlD%ypM;c->v|*_3}ad#X&$7BHW{qNG1$q^twYpXrL(|DiFvo z!s#2>0}rO#)}M?LN~7YtT55cpY%00$0(rAB&txSw6-w(g0~+6mHivO>@J`f_1vJ;_ z+4LW8^psIV=24s~;SH+)Ugji>@)S3h|6W!~T@$P)dSi>0dg`AN7`66Mrpl&S70VYy zOKf^B#zxP z80HYSKljd?_G|SGl5VejyMk&s>ID1dB`dpn18UxRk;$nz-B0{c(5Q_nBYb*%mEH6c zvgAxQ!xJIC3i$ScLkl64gmbhiZLd&UAx9nQSWyn#lXiU;j1_g+*-2d{_($7>u8~%D z+i5rsE+JX#C9A;C^e?dQmMB`cV23t^vEiEnev_7{?cV<7x>hG3ISf-T6fN}AH{7p3 zFe#0nX@p>G`~R)ym<8vx+WI|n)&JF*|8llm$WDKdWRu_BGl?_=KE;+3s+{8NnjNuayvLok2 z5k)KGItipF-gDU93YZmBD3sgm3F+lkGVoDhJ1-(VG;f=NjT48g`vanA#S&!dTd5|x zuryU}ZGkD^=!$q<3KoSzc3`zFu0kPw zk<>5HjEwhfDAS!k7EKKS|5J*gwZ?WJ4pKgLpr;2llUZkHG8Ob_{PS;(SgL6vgEL%* zPAO+nzj&Q+DvNdKnXqkZg4a=GYAJc;a9Q#ow4JKy}6j9^T($<0=~@MtX){_Jd>euwiU3YTNJ-v4HKaCS%by5Io-T!{W( z(Hf!u*QNdc#L=t)?UkjR%=bETn3?XCD77K8$qeb;OZrz+1Iz3q&-w%Ek2Fa!33t(= z(FTk(6Csl~pJ;D`r(@ReKoT$_ocIh6 zHjFM3{p%@%9SxzID7yZT_Esl%R)m+xNf#zg)e96uSHyk*Nf&xf={_Ns7L^Wo?~+_c z;8huA7b46y!uI_f%*l3-cD5(v;Ezdf^a0mdPs{-fV

L6 z$Z)bb4{+{(FvGoiDl`v{P5+jWe$etr@1b+wT$g?TD|&@5wVQl`9luZkd_j|5sqZfV zZtvOBK4m4p(9M3(n0g)C-*vPT?Cg8N13D^yL@(jZz7eYx&AuV47yG>6Xq2-A?9r5F z^Pfp0R^_{Hkx$*#9;Szi^!`w|bDRtAi>?Nyq|zyA0Nm%_6+-9Udi;c{AM^IH8}oDPO9l3;RhFPO3%$gP&$6zml?r9 z3w5L5PL>uRzKNXB? zf2r@xegNwC22jw#2VNa1??%JH);G!tKTMLA8EdUn*oGpWn%@i{0vi!bi$o3hg-rg3 z9}Uo&!6cQxNXDL!ZuCQvp%Z128dTQCt`!A&a!w_(q zYau*E3wVpO>4Lur4ns9B4R((Y;0@j^TksNPPR4&JxvxxY-p_!hj5&axxFh;ZLb3*W z&b%M&vgr#TqSDT47nmQpr4?_otg#Ji7z_p5-#wkA7SU+c*=eZ5VzY4Eh?-(7JK|af zvZIW5zS-HOs+eadJ!)F!Mq+vg)pi?8lEk$3^gS466;BopQg!wb27Lolc7&~U^g^Nj zx;Y#dH;VWI#GMm4n~M`P8Xu*%oMvTb284UQre=s%02wS;tA{vQ!im1DmL@|h*=)Uk z6JgZ>oqEL0F@O|yoKz2EyOU%m2gpfJvVf|kIkMe)y7gwI#>`Z+i-e<-bv_5cK+mzs zSQBigo5WulZYwRYB?7Q+EBddMyxe|=akDC|X7|I0vud@L@~A3f!M~ETnX{`Gv(c4x z>GB%4Q57)w6;jTGax*atp(by!sCo2Zu7h%tfRb4ZC&%XS>+EnNZd6S*Hb>w_l}t0^ zDLuuAck3{hn%IZox_%RmIwPjr!eW}0hH8rD((L>#rVEn?k@kY>uJXGme`^1OGwwr+ zasyFI@urd1t8!ObLdLlHwHY<=$8~fCdAwne41??X6eaHY_+oh$@@T+5XeKYKFlX4r z*q*oue*W-XiK#{mSf3@R0ERWd`rRX;71MS@M`-5AS$nYAzbO*~DKBbGlly3;Icq%} z`FHD9(3E>Vr!h)Z6h6?pgG-ps&Z{#xT4^gJ2VFHJwT67-sVfO*Tfj)#^=6bUGM8&< zn5(c3k~Jx~T~xZ3)a7gQsWXS5>G8&KvbvV41&$&Hs^BNJ!>l!?i<3J6uI`5^Zeo@~ zVpg67#5;^(SxXJVkC-%)q{uY(CY8;+9ULQ0V$*3H2pb7(cONi1n`pMS*Ae#xiY8NC zHV}xn;?5*`e17={`4*3^J-^-CU(-)J~ZTV9+^;7%1RHd$CRDXNJo!&7!YYqFBu< z8STTAo2`%U#`Mgh!ACEXpW!o9OnzFh@1n{k#fj`vNcy0kR&q_pGOm^)QGqYtJ9XycEbR3CAiyZCkzr7sF+Y8 z=0JzR2L_<)-*3ZOR>fZbuBaThP35&xt!$tgpB@>~1n1bu8EztT zVJ-|BV%%K<7E2U5b@u+o2ZcVaaRtup2DvjqVM~oDOgeVW9{FotpVlP#kU1)o`00Re zNcD~4ItHg$z!|)LcrYQrQr=Y^f^@8O&of;-bi(T{%va98M)<>sm&h8-&o1hK0A(iOiv8ibT>(2qQYq z`vyv5uR>E|Qvx*w%f4}ZVRMo;iio2)QZM003{xeq@piVL<=9RPPbGil(TIlMf$E=b z7&Jj_K8UuDqfHJuXo3j)(_{>@v_wrzr&!e+S4OQnL_%mdRMKo3ZJ|Nhm($n^)=O74 z4ND;k$oKC!&+**oKPJqx-L2Y8Ub8o6rukjPL29}!VuYsO`X#a{1C>qMN&k_pi~ZHN$yxoZXR8yeAUss zMu^5xSh<7mR&^=zo}a%k}AGYplu~KZ!eIXZ8~XsgiiBb zMFT&1N(~1n`4GnKAy6kt2VtL1(;G$Q3s~wZ_0qtHqvTa<(ArPh z&I8tO6fZt`*y0sWQ~d=sbW}NaDmHA`!cEv~GMP&M!Z_k`fYw8+TYp^nfZg&%dKj^d zFtlwDOJm~?umhCb2KwMusWNA~{=nkr1(MJn3+;}?=3T2tD@Psp0d_HO--tJyO{Nx9pH*Ljp#cZyC$?#5Zhz9lR7t8v-N*;*e!d zuG8U+psmI_wy{`uL>;RQlW5KL2ofI5niPj_D#t{Kd3i*A^5x71*=~jKQ|O{yzIw8d zK}OGgT^)Fq0{M1emuhv<^zjDeliwR`CK|>W3@i-OIG?0mtbX|XGzD607ae3TTAQ7a z@EV)4LhHSqz zjqI~nDFZuu?(B$=>P@9`hfJ*8!y?)Jo^r#Yz=tdr>!PG_g3UT?&kfX6RGd;uuDVU9 zovS!tQSw2w3B61I&-x^?wpF8A2jQF09}v#bWlS~hbKxzYoFz!29hHy`!;^EV-2|WL z`>TaZA`0~wW^mUshh8a-X5M*o2m7BD>ZxDL1Q#PF9;1W=l3|}EyylHYiW6#-_eq`o zpKrd5)06t!dWp%==qBFfT14P|OOr@vN_uLMhDs1l{As#bBoLiA$pvz!fp)WecFqzI zbD54Dr$M?HxbF;dl51+cNnkIKrDrRBFVA%+GD)&W|CVd;1b3J`6TWS1>C4oNAk#cX9J{JQ36W-xT@Dng!b^a{jrTq)xr-}Uq@)~8Au zz#DFVmcigONx83vI``JyBbnn- z=1Weh6qZR!PP`PCjiG$2$kEZ`5f0mF%2QSxJOk3kj{o36#D@aGe0mfWT!|n}N5&>b zy?KnIhU1XA(z#cW7!&SZe~zo-JzQ%m{IZPKh$}lJ@yJJ(F-iDjzI31w+cy@iIj}+7 z+}hVC?*S*`Yu;H`I#3R^b?N44&kA9i!)CE(#qlevK+`pG+iO>Fs4T3Wg8m~Wwq<$s zBZn0)A>AZ7skue-@kp|3TvQHGg63Gwvs4~4KSWAZcToPSGj2AbkIbx^`Qy>kJ#%X# zCvmHj!N&H#xlV0Qq4Wq4`b7lH{*u^({c}pR&_ajp)IiYra%DyJBbOB|11jOEsdW3n zz$80$#_SNcjcI|op+FJ9I$PJEhdT5&{-7n-z~*!*4Jh8a6=2(ho8Xlf((>7|dd-G{exnO+d88zo?h=gkh+qKI=xcfF4>pa|=@$OjX~&n0tJ`Q% zuR_%h>?z;HL0iJ~QAsW)0fr?POc2blgdMksk(pUI4%=s}AV5Y}8&P2T@kvgxzCpwM zOA@#X?28S8dQ3)EzK!2|1tp*4$dAObH!#@H4 z;?=*y!!`N`)T`C;zLJM8A}C^>F;BeJHJ={02H_ZmuWM&4By!A9adFJM+UQXZguLi( z;u2h$TJk!#IXDOA$ck=EoNq@(VDmJlQO^f!A zS$jcm}FOo;4T{%q3izSFv-9-93oAY5nkz=!nV|Jr83uc8^f98=Q`JZ z<>xr~XYhtnwurA}7U-Q)`!}wtU?r~zpp?v0wD92*tCRRtD9i_KoTAE&>x!2%)S8O> zoV*f~D*LRJRW*{8u*$_If04Ef*mfO{Ld?6)3gCS|2b^P)EB*ohb`R1kQ;zDFTj;v* zAM^0!5=%dFl1Up!X#h>7YfDS0x-tu0RYNAKRaJ0Fpej-lP%Xa@va8o1Z|h8Yx#n?X zIETd((vz*?2LN};b@W2o%9pG>?2JJhP?VRl3wH0jFh|PN#82)?L%<-EAL{xcq*Q3P z$VKLRZjti39~9`Wf3x&xiRu^P2WrIOzi}eAQ(=3I^;x?-72^yLv}2d2y>`5y2$B1p}~=dI_(`O8$NQD-%}wAHWgJc zpiE#vw;{`3Y*Vgm(R&uNKF)3v7yySUHqq*}unVDZ^FW15meV&C+wQQcBh1m!O2O+w zQTkY|SV=V&u4~)UavG}e>y%KAjg#|9>>ALWn@!ho`mxX+S1kUTuvNoqt(_6e%ZnNZ z#}z}IqOILQyj7#GD{oZkd$_;E`r=c327frdFsm|p zp8NpHQNq(9LOYpwOFD&|l(Ee>bf2S$S%BmP_3p z-DDIX#nAG0T5=O!4TVcf1m6m^Nme} zE_TpSWk6Dzgz}6cIU8l?Z#XIeX|{;=t-b4Cxq%UH%xU)%dFEs^VP3Hf@+r}$n3^MI zwmC8@LagSFxu%G!A51JCP}B71dap!+6@qB0LEgRgIpZ*RXoYG=svq9!DyaPX6D_>; zc|X?#e6&lpPWqKG+3-LHa6GkE@l51>P?y8~({WkXSPS&vHXE-RCjSqSyG4aLgQZh# zwEhG&E}hxCVW+vRD!_VvAfll?zSLZ0{gAn|U}~Gw)q1(|2_V;Snd*r@1CM)PJJjhl zpV|9DJa+$zCH@(|aNIElw_I&hGke14nHhCran8rfkoht+n&g2B`w@x9YbY(BwBEwDY8f6tm5C$ugaD^L1=`YxYhW+-QoT{-(*N%SgES!D~ zV~`C3J~KffOqlHL4HG62TZD9AZa*<**_oUb8B?V-8V#65H^U_~3X18&Gb+p_hw!3Y zc9DIV(h`{TqtRg_6kHE|LbpEOc;g*IHAIR59RA~!b`sq}>;=5Jw>^km(tXbX4wmxw4@`6+7eV zees86^{go^{p{jX(i_7s`rMdayi_ct$2RD=gYJxOzgrddV{Ks$c)Eb8-7XbMsE;zZEiK&TizLdb3KSH-6s7!O#W2Y+dcGU~ajqhyO^lTHH72 zvW%-`$T%s=C4O)CJhZ_x(a{;7KjbS7@HmGTJ+cyIi$!x`QOYW`H3)7q9zJyUZp}IV z>&6)YI^vYX>)Fj47Alvz5@lnUlq;-sE+`=h+|;$*^ZIEa(Ml>;qaYu=Q8?nR;{Z&p z>>3(Gl&3nVR;9Q8pjFP_TRU3@w58-?Y`D-EsC?*nKxxpd?tgHl@8I8e=~gjS&tt7^ z)GXo07|p!Ajjq@=QTnzK^pHNN*aA@CwfN!>WayQ2Fc_AQ*&KNq<2jiZb}_)#i0zFp zv1L7j{y?KTww$I#AK(|(89o1_lp_1K`MF#P5arx6{(Z7un?hhdm^}%RmW#Ty5S04 zK7*;*HqP#66H#KVZoLvp4$(o zx6nV6fHprLRU`AF#F%dt@+Dui@$<>u6Fl|EADPWjcfcvNy&~5R{j_at{q%dFSD`#8 zOSazL+EAURkd9wl>(_PdeRUkc(wn>HtUo$b>wIRx-(uGV?)Ui5^*D*B={zA*7}gx@ zq2x4F@SWSE=edmI4>U3jS#;~OAeog`DW?SK?;(f>( zr>ti@-=&bDV=fw{AS%C{`Y2nuW{(_UG|Y9I2iL$P*5Muh~dPp={f;TE%h4TSmXk` z1OsNGQ}D?2hLxeO0G-AYPr@4hc+Na*eaG@n#wtwi9tS~3Mx{uxKu*x*qnLjvvn@zw z^AK~+-IBKZhyKnQ+Mn9o%L|n+*s%kaKHpD*k@ga-VwEzOChPj+WfFVv^nox7?=)ItBQ^~J{}$*hX^y| z{t^G>avKN8vZ-*aRUpbxC8EE9rhpFw$dKpheHtHkjrsAAav2HGv8Bl`O>y=(_VYQA zKE44&Jl5)IGNlPrQ|X3O_WQ?hrYrhln4*332LPt)#WzgLaqiDb!C9i?=6+_bE|}mn z7h%@eK}Q_!z#Th%nJaTA_P7sl&Y%JnA+z5SUs5Pr^tb8zt#L&qIvXnzj#IW3>4ibJ zKC1?=P=xBm)zifUs+-tWJs<2F9+5spTKm`f8Nu0BxwF7CBZz0FijbW$v_CMt*3G3C z87KaLz8wzRbHyuJQ~_N& z!n`?*owp|q`OZFpt$y%%Mte&iW{>|JNovhrhgzC-z`qpJsb=2KlY+SPa%B;ejd~Aw zk;8m(-W9JlIiMDR*m@~F3wr$&1#WpLpZQE8=Y*lPKso1t});{arv(I_hceT8vwY<&I zGv*lM@BQm6vRU>qHR_yeZ-#n*Xrc$rAVq&C5;{tzOj7dnn)QB)eAIme23g2x$Tdf( zBrJ{d@KNDA6~NwlXk%-QepIrca+L)VxUAXXn~GEOc;|#$gZZNqRz50f>CxaZH;%v@ zyDMj!sotwo7lDwy*D9!g{#)bUZ(<24(PK*>p)Xeh;g?JGKRLo=3=B=I|6jC7QC7b1 z%c&1Lu&Pz$v+c?fLeZCx9g9g12ZbOF%!dU3i~UHk+u%stE9=6< z{Azm8nSGf%MGwTz;izxWFAAMWN45Oxr%j=yQL4cJAgD%%G0S;@1llbt;^%>XPWm^W zMX~6BI-GmARidNnxW0A9tcA~BT|P#Q{1Yb#J6vcBA<_wvV!&Z!vDawvOs`Rhm6?yO zuEiLKs@CvcPNx6K+Vbrm{jI?0uiN#NeHhcVu2iv$EGn1MjbxhGU&RLqg%+qq@iGCS zn{h)S`At#E>gN6B!2Q>=iU5qr9n*qb`xDfPnY4M#%@gC3i2LzXsRI;ysZ&aSAI$Mb zX8(pp5FJQ5lcnKGr7V+mv0J#RYyUpjU&1E}IYurAn5EY+i(cZ_2ueL%nvo9|C}8f= z)V}}D2>WXhPQ0&iV!jrk^dBw4-*Jg(rQW~nMBc*YDb^`ugtXHj;Ik@0dv&UwMLWn2 zVM%3>L>%1hh5E~Gh09~%>djtPN&4(5@ zghPP8iC!0lhEU0uay8lSiExU~2IcNrVDrvwP@gFmLMR@NuMK}53|r`QI;*&RQ2|Wu z3G!YN?eZF=wF`cBfn?B?JtzY;`SgolQ8`B=2D)87_a-U2ew%+<@L&=X9U(hn(!+9H zJy4`f3X)7psdA!j=z=7g*9%JFx2Ye<+xY5D9bR5YY|zOrP3X`&(2t~|88fR|-}nsf z!KA0<8E{mVye9x6>*|UA}cmcT?Iopbs&Ox;4PzXWb3awetb(oB5fj=9r|`A5rqjQ&IHwcxs0=5^=2d^D{2Vp$ z84T_IZ-3Zd3wjF6H-;P^4QzQ4M={S9p>mF(PNf5(N@l|f5KWsU}b z?M`}Ikef)PBbx>tGF>OMTmgGg>{^&ma*W7KW<5`ie@;EWd^~|o_kQ_1*Vv?%e<}{t zg!6tSTOa{A9waKe8QWfQlZLThwIWm(<-reqrWOK99?T6t@`K5B2g~B?t^|_S=sOE2-!en|(_Qu3opu4Kp9;c@MJaBYcfPsUN8ph<$+^@Fqpf-}`T@v%fxl!>P$dp07{dTf(|s38mhzv zd)gSx6vLL9F7~7m%ETpwY5PP2rKWQRZ9|P3cfm2l5~J6AhCcRLhW7kBF0sbJ$Dn%2 zY1*~MefWN<3smb5Ea8R|%>Y3w=;tKYVQrk~of68v>CJjPZG6n+?ObA)$6)GLO_e9& z?I!^99xoE4Cq6=z*cxonO7d5K?B2mC*JEbZ4rYm~&fK-on$1G6wP#9*Dqgexk;HaZ zOv8;7g_XvfWFH(}v*Mxyr0b9Me(#VKb+23;cvo#PVThyygC!RlBn4hHar7TNJta)q zTuq8(3xhE68~W1(?KMyp@Cd?4Oqib)f&)8UD!(I*C@~W%sJnMCiCT^JGj?b#ocU5~ z-!;)2o$kBa1kVvfmVXRO<|fgWAMs3CtbX{h@=2u9CLh^X=Xw~*ji$R@&$?8%QFG;3 zWdbl93(LHcT?W1NTF{f7k@bx&mz9Wy_bPT%jAn6UW+tR&c|kR(is6yn6+PzCfJdY^ zMP;Uec7k^ZHMXHpF7xqMnXBO#oP?uvX}o|=2_3_Z7%d-a%b2_K4}1zL!lr>srV)W2J--_p~4qLq=5cABLPw7 zt@})_Es>^O))mnmQg1M5;5PxEAA(7TvKB)V@Xv`&PqxQ(Zr91|?0@xeU%^}^4(k&I z8^~A=lQ9M#qvmY4GI5&RP@*cx7(&!jaaB%d2|2-Fef>;O&$H08bj;WF#KqM>)!zb) z7V;FCTh7BhhNG`Uupb;K0*U*pv_jXZk$%WN3Dq#m?AiV~;Ai?h(X$N@W~Gx=cazh+ zfb2^JP-lOY{N6I4hW4S{%u_UP^4LOjS%Ym(l3$FO&iGWQ!C6a3^q66qFR=w0scIHJ z2KE%HzCwA&rKrH2JvCtj_0^s`97Du}h~i0WEIMF3I!EcJ8@z>t$SC();ZbWWDE4K< zuxl*+^p}M*!PCyWYKp-_^Qj!KVY*Bz*H13$RI5HC3e>&!-elmV2DlH`IZOMG#;B%t z^fGvyO928c1JsWJyqu(`eV>4!yQbu)WE%)I1Bv*9HKHodMaq6-x-G_ilx;*(7A+Dk zMM;mXX6y+=Ec3PYtJ21bc@e){8d4ERwA7j867%HI?4?DmWCklN8ykfc)#Mg zlW`QOY36`eeYa^5#uw0XAx02tFX1!l`Pgkj{6>y+baKTg(Xi&-So+=eu)vY(89MY38_)mnvJ~MAmPf#zL=H&o(Abq71=V6BmC_+FB*O%V1sQ^K9_Vi@IA#tTw4cRm2=6{zrNZ;!icHdZ~=HB1NmpVol=p#nCbyu~V=@ z#Z1wHW>5FdUL0eSLIve7^pMQbaG9`JE!(rpMba@@x-FaJAuZD?l~5Hbc5VDI9>|i( z6`N+)+>mQ{lOA#6IiF8;FSkrvtgBSLbdMhSS}pq%9NGC{d+~7!866PZ>{&|5(4Qb6 zvf>pZNBT#sEW=-1FRzI!56(QP(g*r5I7`+hJMbj}o+bRLv5$wB5v7l$thlrNsNb7? z`ZKt>n>RWwCh%~$*~X}3CY!tJ>y50`#W9p+4|%WOuLq9Zd@pxuyx11D)&}>&OO-p{ zWlXtR$-60Y&SedDrpzEBc1Nu%n(d5-R2-bBAp%&F&<5KLA}d!)_36jfx7j*^!2sL5;s$@mrLwH^>atamWmxg({atO(jm8!{I|65rdG+jeWa#6DN)HqgL9<4vO`As8vK(GsYkCK(z*HViZqZN$a0g z5O>GJ&4JK-ehuf-B37flW0DWuPOFcey?{baiLpG$df#?v<)!HVk`^7-p7qe zBJRRO<<_2?OB~-uUQTHDYAXg!wmmooLGPBFH zOoi%Eol^)1H8$hD?-33AYJQWOJN{l+(F^3%wj4m8NbiLiPPE#<%0^1B29Rg zMX{_JS{cQ((@)2YZ%2I0oGK#mhYAX#O8;boZ@s&&UeCi$V z^2$V!a{@h9*(5C8d3Y*3r#j3x{qDZCPAZ+kKua)HXQl_g)Y#I=+{K-4OMSZ6h31#~ z*jIos-A?Nfk<)ZUfoD|FnWDAyV1wQn3eU(YH#%JKI7=9bpM2mw+o5M~Di^`*O4)Mb zjc&%tPI|@^dD~$pRkx3RdH71};ZLXK1qA0E$7ejFNu}Y1=y;7&?2hujyu&_6PvZm2 zYjq9!rWQ!h@I>f?GZ!@?ToLx>@*61<;kqA+2A!V>b&GJQI`?-C*kv|4FJaH+jw{X^ ze&p6ztkxT!Pj@dt-BTdZNT|rpE#^$91LK>)$8ZvP*UF!-KQn$jCB-#syg~C>&xjut zLJ<6XVvOHUd}|RUD3#Z6PK(jFW1Lx(e97vS*YHY;)qEo%5SP-wE`18>mDhY@LC|`$ z0GXPC`d}^y?nL)Zk=MGDzk`hQk^a=5u{&Bq_l@DVn~4=!n!2Hkl)uGbK(6*xWWz_n zHhZcP%`F-q{8 z=@ZO6cX{-P?wd%@xM5#?SiO-dwOfv)9%!BVEE=g-a7go!lGfY&tXLGhRG{&OxAI2j zyQGi%{`wZkREjBiwe$YEn~)}#4ou`0>q{%8@cwK~7R|wEcEVl@Xm+5#GLCTF3R(S? zZ=1!7A)54^7>{4$0j`bgW7{2S00o*<){H7tT>$?zoH>Tk52h3SXn?0kB;S2B%LEWF zg-A&_Wrv-+jXuSOF1x?IwtxOKQtUC)=rV&vdB#(SHsltzXEKC0CdFzdNcQ;UP zwSEVxX=@|deZCz2xF;Gn>Q%z*#81`XCP(^m4sv4QsB73g>fcL9jx3*GmBRGx+b(W| zubvYA^)JpZs&Ko8B&RWDYeMHnelEcKmN*T09@t#8N^(sQ)5fWd9bF2P2U zK&Pl>q*`C3Tt6}(#WRvEGlb;e;7S>*f9GGZvPZjj&>Top5*&RE1m#ds^(D()b*c-? zNPPQYh(VQ%wR=(ZUB{pMl(H49ncKC6HAmI+c65FIDFM4CDEWa;#4XR?VXp5qybeW- z1LL=39=q{Z2)w)z1zXZs32`h_;$XMD!ASGm;yI;`EHjE(^{$}6GK*#4N1PZ2aiUto z06n}vZ@IIF${zh~b-uMCn75RlqwsqNB`gB@3z)|c1gCb3oil%CR6<`VRcLm_vO@Bk z06_y{Cp_IGe=x-j=R$AvK1eL6Nx<9inA04I={88~zVnhG=DBXMh)L5@zD2nnNsgVX zVL5sF1k5k?OjJQeiOpvEoJk#JB%2j)g!x%AF`^8O(5iBI8i$0Z%=O$8hgyneJJ(5C zu@cD!n#XURyP%+wtx+kC`=c$S?A!4X#JQAeT#mJw`6q@GDzsfG!QaxoTX!)~M#D4ZVx)II2dTZLloVtKR}=U=3;mlm$ErX8aWuJU~zsv)_d8_kS&e zhO`+vr1Pvw+L$y5)T^_y;FqqN3;Q1g9T1Ry=ELCQeh{yQ43dLe8V@NI-a5}onmIj@ zp6`_zxp%1VPjUYEz^3ue$@$@~R+!3<;+}^-B}s}nSN?0w)ZvxIJAd5eoznF+(&T3} z(L?&LH6OEfAt_haSCFJ_QQs$!iH&@VL-WmpBhn1W;&HnL?Y*x47c8xBuC(=7OjZez zodU1}V#L)Rx1?H208&0d>VA(~45*fgYJAA0K(Xy6C^}Q-NoCl&UgErOUNLC0PC$1c zv{ciEkYc9NT2g8#$d#HPl~@I9a9rwyTzveFlkGZVdXV`kHHf0VYIqOHqq7!`QK{5W z1)0k~Bh0;J|Ma2-s_5t$NnX}sVCX99wJCL9S)2A>>2FKlea9+xyq2PivR=Dr(&L=P z(ozF+?>cLIideb<(Q$4jZb6U#0YO1?8Ks+-DZ0{(N)d8h&EzBMudCT`K|iPdy~AcQ zm*|)+&L(-dFuQnh4hheO#3N+Yo}Ict!z%JhQ;Twf%EC4=q_{lQRFiV&n*`XjcXHQ( zqZ{CEK%WWuTZ=%YIvXzGJ4;xtzcvf*047uI#=2uTTQh1|r|Oa+{u;_6Jw7>i)mn~8 zeCY_R4#7>&kWcL5Mu(yey7`6yj0oYCN4yiLg!MWPhgS*7jPnhgVjOj-ggBr>Dn(H5 zP-|+OKhb}~ZkioWkhfb@KP#jQWT(SYnK=scCx%X9SYDDOS-UE4b4P~CnBO!0 zY#nps!KLKV@%P&ZPqa-)bQzMlj>kU5R2@4;%MKzxr-O`9%&tWB?ORdiZq_MSk|y8I zXF3bCWXCXQ6`2emej2Md7zoNY4x zmB(!g2d{^}7YfvTgKP))3gawpRXk32GGv|)XfR2oD5LmFM_ZmO28sK(L2#02tZC6g z7O*11|Dozjt_G$>pEp!>L1R-Be7p>BAsm+Rudlqjn8}-5M7Q2>PfLLc&}o$5xC=(M z;7$wk4>yGjuEi5_ZX1JN&A-sFkJFOsffWN@6P$pD2CfG4DuZE9a&lv#G*p-!S8Q|U z9EKSFV-Mo_J6MMS9UYLw(i>C>h1*L7CDDQm6r;aJuMC7)TfO4XQY?rL({Kx2(R%O! zVF8h(8`SQi&SseGQtZ#`OqeTWQs)|@J5-HMx31{C>=a(nkI#rmZ7oPa^Gx5X;w@(# z3;qYhB~RW@SgT4aa$#B3ZZtOw>icwg=kdWJ0eMG8J z#|D!3Sft7{PE9rpPi2`^^bEUQEDraKsuwYo6DSjbYT-l!QN~>nIL`C2`&t~)k^O&4M@vebvf2FZzD^PB{Np=F7gza|_ ztn1K81pKlRyw;n#ZHKlySsjVSLTj8$woYMQyTdRLymuLTlgpNc;l*@D1I4+c(UEk6 zbC@Mun@E2{x))V;M-(N_U4bBd{wrVxG5BiSmgPuWoiZ#X)Et(rM&TaqMg>QhC9_}M zqA3qfk#y=AMH5XJmGNk8LH#QW_~e_+Gw!5~0YZ%Zj^bV$$M3wW71+h>9d@$JEyEl% z;&!1=gR7~Ab-CA&YJmlj)5e)|f`$mdRlohkuS~34cscvH-hAvp5_R!uL=bZSrUO`yeKnKNo2RZVO1v>Ob0rjpD z@;;Acrx@g~!W#&S{&|rQF2ucVDA*&LHV zhDBo%wz=gc8G}6bvf@1`F<=@>f5WT@dPD6J%*5&hIHdkLk3Zr(Ne=T|3C~Kkhyg1d zQ;Cggo`r_YpHn0U+~K7>a(SS0`ZB`V*9TawOw_8Jee{{Ahq6+}c}>FwXrDA>Zoh^3 z>>$m1uF%VQUox!;Wv|6c zF%vmtjz}3!CYG-jqg?6%=ol+_;@vlgD;yhsm+4U3AePciEZuy8U|VT0OflO7)xl5Yz>kFF z4R!qhIQ-`P5ePB$VQvvJy?h*J6F%R4Y>iB-soSLIVG}-D3$l|CMXB(J30~R9ie+$CfmN^s2BN&jsDtkha*C)&nTaZ0(qwy+3QMYBrW|9Q<23oSmtU9t9Rv= z=_Abnr>Qh!EvdlnYy~!>C~GnIm5&&o?Skfj0@Sle)HbzRB?00B)-r$k0!~T73LtHD zVyA|;?koksCnn?D{=gI3`>JSc>rwsuHGN{Ush!z;Is8j3N5mfo7Ah;oj|r&gW4h?P zvR38*5c1tc!hqXHl!y}?;)>d=vbMMTdBH<+q@;kO!ilnvXO#xiyt}rO=%x?K81MJX4?vj@~-8yufM-RhYZMI!&q` zl;Nt7;SlaABH!zH&-ev?Nepbj%aFlIva!K9e#*kwM}gl2e#lJ5F5#8o)2s$i?$*rZ z#+of~l~>izLb$jy*-C6hy^I;Ls4cHw`6|Vp(9Z(20nu+i*G1pUZ{MbYJ80}yimD?* z#s@)ozr{)g&+|;USrllvTP=OFQLL3z(M5iqM49i(WCf=T+I-O^AXL*-DdaWRJ;(0Mt0q?-MjY+H@6WW*t)!OHP^bl=FPqH z|IaBRHGi8G)z={5=NEB+;y=E}{)N95F>p5c`%zYDOMXrP^}}|_*7{VR31adc#JLTv zk?Ms=j6e(^&5#jQH>`a;szr19Ya{$A_X&)?%c?I{1jYL?mMMpsJ20(`y>-h~?sek} zdcA75#qSMV7X=Ajq!?oaa|?h4L;}=Nz?KZlNbK>x;`#-n3X#1Mi1a@X$oi`>ZHM$> zj%>_^uwQ=dHx^TW!v-n8`dYFd-zAYfmoK?>RvbGoTAj}ySy&{Zw?aY-V>@cd31CGk z8fAeS@f+~`i}23wLuM^J>?l38bP!(f=6-tDvfGdMcCkEmx(vmG*5f;y@L#E8M^~>B z6v%CzwC3ylwYXYd4)9wM1pbNT)5v8>E?}8Bq|D z$F-VoTHE}#7Z=C>nrsK-C>AY%(3)Pquj_0uf&-N z=ioR5vB4pMs8du3%xa(>4c5Nd3oIMQ{yOdmS|^l>$kY087r8d@?m^s?Ub6KVARh6n4lj zQCWw;LJ8uztBe0VxPo>KM)LX6Tlv@Al=465O~t@a%+A{QzuU@^ljLQ-hF62#z{VEs z5~2ccfhqik7Bk?AUyMC1i7*yyLb2E}7bWz?^pg7EKNG*(U3YsUSAHjA&i@vf8mx4^ z-0Vy*vum=lWQnl7>3Z%=w=;Fww0?MBTCxLK7Bl|LUDG}xGoj1l!@s}9p5kb{6MW(WLTGO}r#oq&``Ejo+z zd8Xq1Unv%}%vv?(>7wjt22;#%QA}x(LRV6g^~pOCgsrxAt0`uY2|sLpWOF-r&F9KB z?@{H>8Ha50HW#cO8!9L2&flBMSe-vxZ4aN>rIu@0#}kr?ZmOD;Q?Q3qyHHNOUxC9> zqLZ9sX4KrAcdR4lm-mR?hn8n6X{z$jS2_c8PW@o(OE)l+Sh7*STp=2Ot66WK$4WIU zHnh8ma3LwuIKZ#om-0vTZj)rd1(GJaPoBZp5Sa0oJ2@vhDfs^Qn|TF8KV98?`OD@4 zpWl99@Mwwe!rdM@SlYOqJPmrM+!1*rKVL$@70$2hRrn;~-o!RJNa-tbuC(VXcTI-< z8Y_T!O(XhN`hk$HvwB9F0m2h$ujfaO#0Kf6F$9T(4SXTKjI7VyrvZdyz~iSpr2T_t z-k~N2Ot+%)45H{G4StQU%3LjDi!mcsugVut^_ZT`!D!3&zasznoE=M@OypcpM3jq@c^ z!+_Y}_F1_)jnW{Cd+j#>GC?>je@`saCU7(?#)|gw z2|i|L13=*y?e0ciJ-vVtB66C>WUfIiOmo8nqOyxtT?bRFj}I@v$lfBv>Y zC9CKt&uL)#$XRf*K$PhZ)Tjvxj)g*;)>QhnVG6V%i)#PC%{CBnv@Rq(Ch3ihd`I(a z+#TTBbUO#AnYk`XyPAF`5?n%~A#$CNCGL%QPP%U%zH=OQBlvcL+ru=^E(aRH{Gupg zj6#Z;VF_U%(;Gs|@+(2aPBkJYrIONCEKtUKir*!VnS*J&t`A`5So4Q|&fZJg;h37n zU&ykz&)OaiAW&g7XqDqd)q0|sty*BXQD^Biq?>xKp$(AC;M;eT6FH4R?=t$hk_{=k zSPSr17j+)^PN=fpI@>^l6f*g1mum*artSGKVkNXM(lBMx(O3%sP%pPv^QWcfS{k?S zj_ba|{uIy|L>Eau)`hjapFtq9X!(1Z_f)fnfJ=7)QE{S~ldfW#vCxqIMdhp?5MQ1) zfZN~0UUb|RA)Tho7>iu4a7|5Dso|Hu15;C>k<`6ppKaj!tY%^{Qx!qlk3!vbr~ycS zJrGuQlC7=K(dnY6L0)RNn6)z1*NTTKDq#{A9e`6f^G+y#8xF718h_+u;4#lY zm6LK2x}Rv9HC<+`-0yo32&$w*dThWxPw?^Hs^+-=@t7_BhmuRyuUdpRBZ35%b z94Z#wpNCi}I8v}^z_AvvJ=yorWKD|y?hG(F7BNhZTIUE#xH}Vu2k;}VMuKhlF)_Ws zSnshS(-YE%ymL%v-Eyz)sJf;zwuLm^KL2J0L{<4$6DB{Q?4H(jT2Nxs$4&8z(rq<# zd&f+YjblX5{PgWzfpwOg(*W)8k5QNCj`BV@<_+5Obl1f~1S!X-YBew|vPL!7`}KjD2iBzCK3Mo#ax zKDIP%tB3`?nzP4;N8)X!A zR3F=4Et1O;P!Q6x6__gD^sAb>2rUTnN|06RRxP#LNtTR%tPGritg5fBK0ZKl_&8)8 zU#(yXWy_t4^WQ<ygv;b#B&8w^GytVRwldbEFcv(1a~!*+cxD3$ zMbHN9iYttiXIa}xn3K;}ra*@df;5}R4|cGj7@f<&Zb%@;-(miIu|@!!i4K|}qje*6 zmTF6UAx~+VR3z?lOQtkA4F~7Owu`?ej*VZkxPGk9-*&JF91O7t*DC|s+dqT7r`#bZ zZ4NkvrTy2A3rY?{&zMV0mjU97#AU6x%``SrbF8nXj>9^~JT}-aBlF^iEe70qy^`7! zI%5^gd(wwYC72l_y=m#nHWMtyd&8y+Mypw9A3_cI+v@Y08~CHDRW|^>S$y_zu0WZc zpmG$=;3CNyr==*|?=>0;+L`KJoYSOowY3V)g<^8-v=bD@tvW2XOfWDJ)2L^I_~0AU zP@=u6-<9(s4X`1E51n@8OVQHO;vIAZq0vQ)dj_ilIDr`vqGt>R#&DBQ07B^8hMA_oJ37X1L#?8X_DsC3sUM6 zE>>2aVOS+6$K;>SY8d%Iuu*KaWieQzpDP8LT)9Nvj@eEp z<{y#;Zn5du0dzLfWv1GiG;ny`ZD+0dWT7|Rk2Ph}2#J|CKXAX`*hB7V86Atrqel3FLJhd>rU}CF+@u+qH8uXI)&r#$s9U@j&co3rbR+~QCg0UT~?q( zw^}(gRmQ7XIr+Br&tj??W_v|$BK4-u$+NWDqE4HffG7#p_(mNPM|Zhi1@Kku?K1bW zU?@guRu$Ow))#E~MS&>~H2;(q(PlVdx5DO3*0EN%ihDY$O za3+n*sjKzSCnJfxsm5n$e%T(z+OZlhDAX_%;r4OvD%s81*vlg!|nX(HKD zxj@Ei(8BvZ4L~lZ$lfI*RdYTB@(Rj8=xjQ3tNj#%8k2S+YGBeBQ9fg9Qti<#AbH^l zj5K#y3JgEW_%(d>7L1u_@ob6aFmIB21+TV@6v6*xOIr;$Z;3f7nWlsMSr~E@cnIAW z6>Q;M2S0ndN19PajIO0wl=xk<)}RqBr=AI(cYw^z7?(_OJXB;|ydrK%C*|-|Z(af(Y40tE@A*@J7f;z{f2&FZt&r1!audMy}(O^;Sh; z?~&aRt}7Rf2g3FT6f{De)9i5(KgzqwZ>u~(H#I1gK_Jg4TGSCnC8;}peVDHsxBM2y z;ryR}<5K^%mxCEAzO8;8k}bbZTmS5&^|wnlT1ngL>!$Z%in3wc-a0ScIWN5`*1n>q zKFuylAr0e?4qlAVCF>Gue8Qp4WK_XFz`o^0*MR1A1^HC?n^_u-P^!MptSkLC`}O%> zJj2-Qx)|&lZU=*knt(PBW1rRcS^}?n1Rh8m8YHcHJzm1?Y2w4BcsEenUeHt*0u=? zr0<7_hbLg6VVLqgm~p8U`MrEP#o!0qWd68#Uq+E}QcARXikY43GjOmAp85Tc5*Xrp zbM@Blz#M z4U+8==IIw`kH~geegm20S39+3xV_$hRQuiy~{LB6C@4WgLn8(&}Xgg@MLIvkp^+XChhR|$>P9&l;*$G({V8PLwRi2b!9eE3gC zFB;?xB|?uP8B$gY3erLix$jg5}dIa^hPJ2^AcN6*3BxP z-aIeaoX9YoYA=IInIoX8B1}5_xuSFoyb!wJzj}9fG1Yvze2li|QT1 zDbgpJ<%)(NcY=yxAy?bU7FtzJoxg2xOnF&@EsM|#_?h}apo2C8v?lf)iq55|f9cFG@)t;f9xoH8K=F-Cs$RtgAix%AQl7k|cgAE=HkFb)^kX z>ebf<6%(jgro^xG7~g>ULj>7xY9d;ywRL9gyOC|@`G(Q)_hCc*aLbl6Am}0Y96?yv zT@cEw$BS8f(%r7d<81{Km8ahp)l=jYzeY;2--T(J=m}^;)A6HIxaZ3s$UpS%?K7i6 z+g1(n%}U)f*{r-$%arNR9HiD55RSplx|fE21Nom9GPU{@C+e?z^!I=KAoMpfI$2r! zud0QQ648_tfAs2mI$G2-@b6#)u!CUKWU^&^(@%UGc&jt#w)O1--uJObnNuX9C`|Hl>vG>BxnEi`fAcs> z?)}WOiPTbOs5Ab9-*3FECTI|C;IMS7UDANN09S;C8(i`OF-Ui)icRFU@o;%|i|EB^ zzv2qK-|-scZI(MEyuwTxqL8$oy+C-@V_sP!1pu<35(C4Zu_nAXoGpAD5=g=9nP0~@ zFPa*_jY`tEs}+Mm(X5(kuvQO;$|*(RG_IdQ9Q?~4W(Y&9qSwG#Kyz_DuE-~Wf) zxtIALq#?4e`i@@Z7&C@)VI%tQUxabirDUH4B*AZ>5Vr-c8%?6@Ngtg1VzUg!_oyE) zi^ThE0wH!@JTrh<;p2%|5L}8g;pz(JdtI zkPiyF8USZ5B7uFD6v6ef*;(qVzV~;ofr!Pd{E_?{oGAKX6*2P@u<+Vtj{XtlBqF*I z|FQ2TK@DTqc_DyolrWq0q|Xz9$sO#JADzb8_$m?q>&g4A?zI!iT&ErmMgLpKn8bZBCDYR7@8YY-aHsujG z0P1#!S;ruP(Hi6!uGtvhGUs)N^^hemyik9M9Hh6_s3M=QF-IFv*ISCgcr<9Qeq+$) zBbSIx2`%pyhSW$2p!nm+8lhF;>`tTzQkM*&;m~*awr!7xgnW)Wo?uW)teQ7}%|YKF zdY4WL7dH`CnL?AoNkzegA`0rT4fnBb(t#i*7%m2Kxj|vqE$l0vdmWU){A-aY-;Fmy zWG5s?Nr;gHgVu@rs!fZ;cZ3Bk#Gn`OXMNEoQWse~noPLKn5*$aZ=$&O$p#DJUL8<1 z$*`UKTxm69pdlxFA_2i7vCp!@!Gft3p5>b(w4Dn<&s#4PN&GD-AqvFd?EtUe(nDPd zD~a;FSdk)xatCfHZX<-Fr0-B_f8$Yi%1Mack`t0E--HP#2kHlAf)87D@hRgHc%9cK z4YnfIODO|f2#@0JZ=7(Q@|*zGE+Qw8TgGvHm#`Ia7e9BcbK=bBAgyAA#i3#X3Y-xv zc0N=e|9dRv1^)GY1YMcLiKzJYVsco z>Thy$w2JIk)fOsmtQ_|_g8*n>C!y3 zcNjXuZz5xPbs?w#Bat3OBs^t%Xhz^6(QkAV&{!x;HN(@GAQu(eQ8G*bGn)3i>+drZ z+~1wpzSF8Ze6K7j{)(@XR;C?3;LaSv&DkdcjD9j-4dpUPP*P7}dASS}v66kKmH*EB zNL85{uDPenoXAwe)IgijTCk64N;tNmN=&sq^73zwY0~G|FZg%>6B}jHS)*bCuw<>_tFR`fqa{=rIE-7rB}x4Iso6@}v6kLydMnX*~oAh!M%MDwbnR z2%3D9c7Vf?16xBR+u%ESK>2(0)SY`TB1~c~`m@qVMWADyGfPY`d4GPnK)(8CBAPsJ z>2XMHBhPPxpjtuby0$q(%xSVYyZUo7CXyX>UENprNg+Y+o*M&VXf!xzV#vLdWu0B~ z5<3ouBM{N?NpqGVB7Mc?$bO67Dpd780~3JUyNeFuk&2$KR(ZK{{1Mf zylwSW{_v@jNoy0Olg*XQwX|YuHG2&AMw0A54;`G`ONR2s^lMLs`B%}NZRKYQ1ND>O z!wSEYSE?2&>?w%5>*K}7<;H90*8AK2JEULgGF^XPLa?@*!k{osrkhHi5iCSbn#NCi z1o=iyRz>FPCY)B$2<)>4M@Ok)t zv0I4iNuM5_Z_*d;|4!+9x#jhC}jUo(4fDB>BbCW6c;WFx-)vC!%t z$GIccy8#V&NMJzcEFCCmI%T&6Vf7g&n&A%S-+mP4OOTMk-t-t9Q*}JiapNgzcvCQ$ z?`gV?wffvk`gKGcZ|eJrBD||*U~#i1m->{1!Z?G6b&#>U&cV8&;&z%2UI{E*`DE}pw0dHt8m4*Ay z&dbhpc4l`;vN2dJrhfQ78Dy57JOd=CG73avBqVC+FYh#AFG<*Zg?YJ)qLr1ltTnJh zxax`0h@WVAHSO|Zr=_d*npUG$`@2td)pPD;=VNF3h}g^P`)97N@kLjb=SAybj?+cw z6hZFWq~PFk`XIjYtFE7_XVd% zzTX{uveI6cxiPoTcVm9j?~^PmxA&PdORwjdG)u4dnKf%GZB-kXP3V=Ds5|hMEo#jCfvR6>aS3wp_@^UwkH4@763W}$(ihsCz`|E_bnDOC#{378zUnGJ z(lb5Br;X)jz^5GtK|iOVrT8|1Fv^hZxrpCH9GVaUIHY`V>V6%j66~hw;(?LJiRhKFf%~`y)t^H`&FRRZ>xDp1E{m~A@yK3 zbfOa`C0#r{E*77Fqtqj6nfFQysP}O z-aq_!VG7)|Ye5w|YMllF99%$Ieo-Doyx!5}>|o1$yxZjD;d_lx(Bwcv0lT4zpk_Mo z)U!*VJv zy?fsSA3*GB{@7Cs<%)PN6*S8edf;W9Zm;Jt7uYCoq3heB=$7|HOLRfQd1KFKKEB+s@1jC6yrE)(>bFYy!Kt`mNxK5$(yBpAK3J4;{F#9(kH2MI_PL`I zccEbEZ@xSNFF75Za4LTS!@MEJxBpp>1_=csKr*X&a1uf2cgN?=4upP1$|491`-$A^ zLv^3q;|@F-X|Ee_%ao=5rvv7YRp41UNEeb#H~1E!z%IaSYSvTk?k??RD(=Q00vP;#W%;kw1K?c#2>TCf-Yz*ByvPXMaV` z5x}q&_{F{HGtKkWKOum4B9r{#E4V3M@}aK3ZzqtDmRd{1f)8(BBv4l5lU-wei0>VWNSw3&fSsLO+bZK^Z8gCrYu~!c zPG@PkqkwUyD~Fjc7iKsKHX-_SvDqo5-TtFsVa#+&v(xZn-2}rPJuP#6KEt}qPX4Rj zn094-eY1tpUD8NS zdBP?-d@tel(4NguQ2MlOWF(|yP}XK-#729Syw<9EtD);>z75lUX(f%d3`vG@a-K~2 z#`;Cmj+I5u-0bG&ulPX+EVIp~!Gj;Nl1R%f(Ec5b(Mu#KV<$@-Mz_tS{aw+~6RfAF z+R!)OSDLUq~s%FXu zm)Yyv7+Gom9{_SdjlcVRs(cNB(3WgUHW0p4+_$Ne@z&P}eZ-`v!`#2sV) zS}_i%9>HHRDExBRRT=$Qgx8Gfj=(K0~3fSLA8dtwa#y? zPc$Z*+iO~yTG0LIzTLaEMH#9c>Qj4hvbBM{w*K-iNw&5(HqKisV74z2hf<3sd4>)d8CKT&iwk&UUX+|Q=kJOr`7EE-n5-Qb1>cYlm zDofPgxUun*SkyAL{u-p#U*n`=eNf`EHH6RsX>D0cOVgZsjL)R1And^?6QvdLo7B22 z?n6a60CcH>=K2;QS#<6fp(Qj7&c+SGH>f~8);~4w6IF;Ca8@AOA(qujtgBh1HLYVN0(I=ZX_s5nT$dOMC`x|5}z?GxIauk+|)zG{Wpm{3lvJ@xZ8EJdVrX`XO z4UF8Hmge@>mL}@Az$reviB|N+B)YKV+Fp`uqxqs18)WJJxlcb$s`74&Z2(fH0hbgV zKgc@DM0dWbHfMtv-6>X}vwm|nZ%WVxab8pXI;!DaGH>pZ*(-<<5{PZobo(XsO`DTC zZPgl;mATlKmLka@_?Iu9JA2tYu~S_zZ~plgEJKEm=18_nv&~s?#+HQXuU{ho_BS@q zZEV{lmjrR6r2>P4EaJs2jm_=TWa0B#fc=pJ7e)apn>%;jd9#-6GdX~ z(sp8H3+p#6171nbHT7dAe}F(CVsF^6w0$cme~6vX9#>MINc6M6a5lMb6N79=0L{*7 zD&11$5L(J;$mEaGG*DYh9p;^Gs-kaG$*ZxBCif{UKq_va(Qa-}swFrF8vqghg?*dp zMRu!JT{dY*E79a{k^XLK z1r4o9T7GjjlVIj>ySW3C0M=ns{nlxNH7_}asfSJNsHu+zeThCK=N&)jl#1 z8df6NM#$JID~>yLMXIO$OX6`(wuq=toJ1mL00?$AlCcx}j-l^9S&**cHkOODCT#Ju zX<(d9=%=NAk!?~%XE6O>u&>NnbL#0ILMxW)R&92K3!)jHN^Ao#{Sa*5X7Z`Vg5+9J zYhD6`GI|j3&aaVNB6s!L#^%Jr=H$kf=EeqE>Ju}V{L*%8`_m@XK%%DbrgXUp{gvyR zBuBR}EgcnRMBN!Hb`m#lgbq6=p=UCVSzMO|LWX9SC!>q8orp03t;<{?Yp}JMmWhfp>SRb-AxiEgO1ra{ohLYzn`bs+G_`Ao z#%kt6U8C)Fc0)t5EzMBCJDf35G@6ebv6(v(%c=MaQPW&gKMBn$+3wIxW83Uz zqSN-4)_zw*@3jb`F-3JOz&J3 zrJ-ID*Qo{$LRXb)fl9O`=P*YlL(wqxS#qlS>>nrq(?MZ}n`w(R|; zt)H#RYRlTrirf0#xqz*#U8yZ|R~m}g>~2aBn*JgC%~+WwZT;>ZCjU0gZ)V;F#C@B< zfwRmyy&A}zXrRsAEZWkWMNV4!UCG%+r_=?_??=MYp^PpFf|vEHpY=0ttm+<+3DwJ()%?|Bpe;?y8aFLVUehj? zk0g1ihAW8>8Y3-_e3_VmIdB=Q8)Xa-ltk0my199|NFm(Hth8|zrb&`_-$}*hx0Xlm&Naf(*dUwX=X-!+ z%&7^I@M18F|Cs;8;xD1%>u`RK(R@H5jcXcB`Ke(Oyp$T#R#@8Y+7~Ttfp&&va6_~B zNBpmrp&JINC8uo1)Q?$)$M9Obo2M+kkEe!$8HZ(JFs8+K@SPTaxZfG z@+|F1Ab9+M`tDYu{q)42H1aJiWCSgvfM&=jgi{2knBO`W@PaX0hOM1u86|Lr8Ks!4 zM=fobR%&TGwOy7G;&++GaLXt&!WO>=twxLymbMTRIg+B+|Se& zTiV^)%@%)^KQ{=0Dno~*t;CAjuRUe)*Z2=fb~@FW0N^>7HUeUNtCy!1fS|#)njDs%2b)1+v1p)G}5Y zwDC?jWhJ4;0p?Yfak=pc%eVq9tOk?)rcp-@NV*=2a}BWzqrpg6MiPAZW&VoA_w#RC z{0TTuf>}MrAGi2nIM2g*1I{7-LyP}_9AhnUbDHRNma*Pww2Uih(zLinKyN^oO>j1X zVMGR1IW4V1qXpFrSX)qMhOx;qt^!Cu!Y1(oK!1z?<}ZTR4gqavTSluvOSm00{Vn1? z#%3D1aW$MR#A^tZ*N|#z8C#7{TE?};r!3<-?R?9)o*eB)?Iuh6G&sNw#x~2i(YVPp zK24l}wl8BloX=n*`vrj72;TD{|BGeZOuWpv1?_(p&gaPC-=reO=dlj!wKbNuR$B#> zf5FnO(lpDs6|i-~Y1KAc#%);UPXXoMLCaTbTP*ETP%E&EJ23rU((b1ajYGRZ+h%EVwRx6yy>{9l zGqrSmvZ={3zHHoSX;ZYbENzZfV`&pL;*4K0zG@j?1LOWOF(T~_ICs+!wTHBEmKGy6 zN)udZY4;FLwGJ9A--{u30L6DFbv9 z4dRN%4r3TwERf2@qzw6($+JaWV7i7g?|P+V@vXwr zq)eCzIb&&)hC0-er@iAQ4%(M{;)}H{Xe%#0(uMWtTHlB(6GGpeFG)$ZJg7O`X*Z`~ zbL-Vf@C#61KlAO@nA=*vMVwm$OEuKn;724w8dSnD@($W@^6JKhq?%9fM73n*oVv!~ zm%Gr2UmU1Ob7Jn=#-=8BYB@7tv*ql(K}VVsU7kHwAGB%205A`|E(S`4eW8wLTkMZ;e78OvxFP$eI7Yndvn01l275ztWbY#fW+J%JC6P9RjbU$!Tz`IDxhd zrjXa$e%0@B;w8e|F9|b{HY*Nl5ps@v#rRQaVY01FUj6|n^^LeQIfUbQ+-z)0Hm?KA z@iwflZ=J1fVAOC{lm^u`<`QpdZ&}c?CD~e2-zMz%H7M$D+q|YtZIfP@reHd)7}A-j z(x;}0YfH{nTRgFUs~Zh8^8~BBpA}ZG@*A5P+qX_mm!|3yhgw!%@kqzho8Vb}Q94i~ zSsPF|t35Tt>1L-7z1d!MrwywJq~M zFVLUicxe0iAm<%YOp%LlixOG--qq?=n&=+)l(C((;L1{L2>5qSLvb~?rL`fc&aU#L zAvjG6`OPiujcd1NTzDNjh~SlO)o9@1rjW~tFN^Y-jPcE!)+kgvMLQKmSO$t7_v|U)ueL|d)2U=}1$q$6I&bubrz**_U z`Tbpu6FAsRabkhtLSrX+3SBZ7r?obFsDm7f5MHa*H>4)#iHKaJ2goMS(_b z9gQtAs7N4LROWeY?z-%Q!+_Wt+vFX{A)bz0CS@AAmG!S2^xp6KQImQVA<(epWXMK7 z#1}L+<7&W0oIRVZj{fd*uI|cfv?`dtP94FzJJ#fWNOf?@+?KbkW;eGt);BfQw>ejp zePY0yurJ}HSDiQaBT@F*5yh71Z%fO7ek`!rYsis!T&%YC@q=F5a4rwZW2sg^oREvA zu)nRTFsqPy0CAcUg->z(sD>-77X2%WS%wYjZPFlj-jWS1t@71`ln%!CcVDU>#dVOI z9O8nq)M?us*5O;{&dQ)ViE|?tHKn|HT0e3@KPyh1Y^OOv-nw4Wl)t^jrM$s3hk@>% z^Zj3Yf{JP1zA@j7K+{Sg8!8UlW`&{kC+!X)wk1jv5}2tmAsuX zR?9ff>nOx=WGDRF$=CQ786tO(n_e?a&Ts`6d@A>n^h#UEDwBYcnWzr`OF{$u=c z;XlE@E&L}{J9}06Zk`hUKIQLM<(^XIzN5+=Q2x`(e@6KSmH%DkKdb!jDgXQYIpP0+ z|4{gclz&*k^}O|sQh2@UkU%$YMj5}Zwddm{CC2CTa+*5zbBvbKQP>a|0BiU$%y}n;_qg}-=li( z^FNFFAE@8`;f9A#F4YkBJtJ?PlW;A0G{;*n}sT_`~V491MSq8MOQuKa<)x zZRI{TaUYu$-N&LcqR|wKb+htlxWHi7cQDiFVul!3I5;aKUK$OX8Bt%ftiYF9-lBFY z7DX$9r71SKo1IRfkO-YYJyxxxzRv7sQvmH*7;(ItO_kU<8_^Nn>>T*j-E3M^PqFDM zsMZYBn%T|H4F;bMtki>6?MfpUsa@#_=GU(D27|RLO`~?DFF3Y#WzZY+k!yO%@de@f zgC@Cw;3#r)kY^#D8}yT#7tA3yKRB|s+R)=3+Y>Sl?xqUhifZr{^^>-+pS0S3(oV9cP0eF!UZT+9%!gYQ ziW2LYCMfZ|!Z|a_!q-nPD`49Q_5QMgV?7k}P}o-%JVwF7nr-CFr36pe9s)jEIFG_s zS>butcOt2*@ceCi-0}FCah%yakXuLzel4K7Tf{t~yK|_43u1?vY3^t92`$A3jXI*) zg$U~9x=7j2E{v9giucOjK_d}cn#P#UxJAJn&5|$vSyHuqVXwAJMB6snE|G1QIBl08 zB8yPl6e#Uvxgey{s3*lPdLm;unsuB_H?`b;_8g8Q3^l-W2n19ZmkGoHwqRv!A6vLE z8q6&x0*D6l;Pn~{!)cOi#`gg8u`u<}Krl@`M2nuhgPqI=dVX9)D6mM68@5qF^i^76 z8yO3D>2`xC>|H81C3pg9MD~lSjgYaI)xud^7xxIiJ?@2nF`OlJaZ~v1ai8j8X|-R3 zo;IojdOXMWdh6nr9?#X|d3rqG&KH@x*)t)(4BPotDYmSWjdUZ+JBS;}h^D33C0)!q zz?QBAlq*&(tqxM&*>-+qyudCfn1wZ8TR}xb!M*I#ZW)e-f+@DLn_Y~EZFRH7A~jZF zo3_u+PqE9o*%I3lRR}seM}=~_*hsq|IG2bs*UnXmxptlk|oyQIbIr$ z9uL}pCyz!n9=;u{+RIka0L0*A^zyoR{`Mf%{8@~evAqd9Ct-UM@t|%8?fitDo7m4j zL8Hk}uD+M~Ljk+cShl^8 zhO)VfpOyg=AMV2Bz#>6AE*kA*^Zu8xb+K<#OV|Kbin?~eZdUuh9j`miF1E}r!gg}5 z=%kZPMREN<1F%2=n1QB~zqbo^ux}EgbWE0-zjA@AqRM!&T`Z7f+r{WSk17_O*-1oD z$hQ{}l3%e4JJ<%hz%Fcx7wPe0J#O2!YGj#c#IB5&*d?No8FmR88AXlQ)CjF^yJ{t^ zUAw4`#$nTh)WwTcW`W9FY!@eJINNRN>>;dt&0Y9~p+YSDLb33Rgx?-77PMKMu!|CL zTZAcKQAM+Zgo6~T1FfW3eZqqZOV-zQ5jF}1dnmE(c!3@trpHU|QbjZ)h*nCgszU@- z>6snePZJWNM(<~9Xl6ss%$8P(u$YX}D%n|{U4q0b5?#z=mk|1$VpoW1DwWfu+M@}! zhlxzAvteCqmkYXGVh^i}mk?Y4&+Xs8y|kx`Z52$Y6zr*k<-07Xiv?ANZG$~kheU_= ze+D2xkkAnzfK`?!V78YM;k-r7jP77}5W2K@NMNjhW_=Qv3c_}2rDQb2?2wF8wMvXH z)YbD-JEZMmV8cPG8`5^N3j{L?RaFlU4d2Vwz`4AR7;_n=`#V{gXe79c*cMWz>}3r> zGm(gwRfw4~5><8?!B~EX`O5WBU}?qU&Mu^#&d$m=POuzPDpNvZ9R;bt306-oFF3;dN14Yw!91Lgr_c#D zj+0D1jDD>IVKF6X$4L}D!8V}UG4_dLY|asuD~bjpsR=`ndCRMO|NJJ!=~SQ1*o+C=~*tXDvQ zSyydTq}Y0y;_z33t$Jk)veTs@4{_QAf>$7Tabh29toEX%ke8aOHp9UqyIAP~cICka!6R$C%1L>EZiWPM9Y2Q};8{ zo=6yuMZ4K`Le7F%o(t^e=@mMu!53D9g%F+>t_T}-qDVLaNxh<5aLQo#UXspfLW4$K zx8U3b)W*{Rp=pU&oCG=3HWh~j4Hb4WTQF6#GQ5igY!4}45PC(x$+(y0tDr&KTtJ0u zx0|$=?xnKca4jit(5?m8yGSDq21q~XB3;$Y(i5&=fm~92E-B2wCyb*(NJXRa0aQvpkF<(JYjqhN_6VX_TT|XmCYmdST(#5W& zK-mXeg8?na8x~a@V)+$9X7^PPEpLnR6uZ$O{AzVbUB1o0z z(wMgsN6;iekdOr#ra-TxYI@iZ;LO&Y>yLf%8H07Os2^@qF|ZFyo){a3neCJ8PHdkXIj@Yk@NqVg8a@x3uFwD{ zXs0QbXcHg8hoJn5C0cuw`DLOuNfo(@SSgCEqU@Uwv(eE5?3R_$!q4twpX+8fQ@zhe zkyAKUqXB<`g0~)K6;$Z9l}HW}hq=9*-HPrjX#~b zj}8do7zrWAEk#vi5Ak(;aOzfE`Xr9FiOgBVy0x~!5A2JM6&W>h%AYKk*AZX zTp7tqV16-3vN6b{LhXILoH$?%3^;C3@QN51aNLt!(k4nnS$9ZzOh3uaP+LI}_M!@{ zQb9gh?P;O1dze)w$opQ!#ttMLsR|1!A$sbP=bu9eZ3+;S}wxj5)?&uglsR z7gY#dBXjXX;h85C9&e>o`Ogp{xL2AE#<`K@5Qsf0DvSq0arX!&u+R3;@V9%sn9e%t zz&2>5r=^PsGkRE=T5}OsRy)S#L7*ha}xO2jELbHE~Dr^vYj7AbU!%+h8LP-KcjrE|0z zRpbXs!u(qH`dcXV-Aid1)|Nv{PR!Kf(tN8ZcDbNfPbG+0SQTd4UQll*J4;ZgM^f)Z zH$v1~rXqrNt4-SonSz21VgK#24N$P*C83tOi)4{+8032z^GfnfQ}1#?y&Sj8)rck$ z^bBZi*JGn#g;3YUym|j+J?1t;7m&wh-Utq8<<}F~`woNY!9+Bz34qEnI z%jr)4ro z!#Yj~hbB)rzC>H$w(1p=KAl?Kxq$SOU64rcO|jh+z7OI1Q|y6gF!um^aAh>Mk97&N zdj0|SkcfU$nb$FMm%6QvB&LhuKs9|h#U44p_MoY6QL|3rmHigJSrmR0g&)f(e3`aN z$>PVUutol7vBQ&`@%SSWQ8zI0@h*X(FuqL-U!Z12xKoPhK`F1N*c0M##aH<4F6JqG zawb}d>|&-yv~0u;smnkt4%!B7gI!#1-y~Z@>CxgoJ?^*t3hfobNaUAxrQsquNNw3> z+!ylNCTTXdzjnKyrsHMXNA2B1h2Y!fZWg2x2v<@k>|SZS>VQm`VtcFo1XedN5`|)! zs`ejX`+%1H&@M@{dny#565r`&2T0lU6EE#%Ps`AfZuX1})poOkGIVh_`)(*e;{3BX zUcf0wF#$gezMWGQ3OvQWr?Krh1m-)?T%RTRkP!8K65mDl&ply#?qPo0LmC{R(Dp#e zrvg7%krv+9u;OvYS%CZUX45QLd=|OIw4@7IKycT~!CD>VY%J_eBTlkLAr5|m1oFrc zR*d!UbqjOssXauxnoJUuaD__=Z8X<3%ltgjBBTiYLkiI@e<-GRv%@%C!s){EhZyX1 z&!^ar1q;~hBERwvdti&Jr&T&L%; zjOCpz1ep?{Fiqg2$v47~Rflh>$!<$5%IJ^0Iiv5BX zn=zw{#4X_9=V6M9DN6TgL3T1YA4grm*j49tGMVX8F;I*#cn=MjG-PTms)GvCjgVqc z2f`J>{luFYltEpYy?ROciWy_%qC#- zqqgDFyRk>;0Z(NJp9KKk2h-e zlXCVITIq`_GBDT4F2nSqSo(NT$W$Mvtzt0<|IljcNTzzzv5PIPlQyz$YOF$;t(GK& z1&h*tp2i#LEXUaG1arcLc^yHFtcwVYw+QB8tn3(KRh z89Gq!V=GeZ58dpK1k4{np5hGlP`bYW)5GjID;G;zOFEr;Z=+5F1kT^UdC)#uoPj36 zq7Q@7dwzj}VviRGxu8G&O)&;Wdb-T3rXMtCZq<>|~MfM?XKS9?TKa3?zl zsCg&o+sP(}JV9SKdnbyNH7io!e!AKm56a*vDQr3R(#*Fr1Hl&`HaW*xm<( zzz)t{2K~LM3Y-}-JYvv>8nmGXZKy#T=|R(A{{Dop zk6kYh(`lm}0chf0wGjz*mr&}HJ5n^ge*Tf6T@bkZOAcVPlN&HleMJGZ-#|PK;53Rf(!BqFsFB~EL5=)IMBX88FYiHt%G68`fjn(br(lIjIHPWbaOQP{GcSy&{|tGh z>?LjHkiE);>}6I}`$8lsKE?ivs6r(fh5p+q1YDAk=EJi1iImj7gYdsP z*tfBpRGT=H_&3!vc{Jq1fdx5Nq}a#Rz9^0*{!r}?`46$rKsbC8310=#`yHb9yF~9- zsPHRP_!TPr3KjlrqW3GJ_v=Hg^+bAS_yVi&onYs4ZXOqR`YtxLlF3KeG#o!p^BreX zC@#(+CsN8WHv2e>@*|9$z{%rr)=s%qlaH~QDM#5XDsYr(z3YsCPxw(f6S^r7P97ASJu3rHX0mN&F`gA%#4su;nQb!@fddZ0*xsm(i}aa zQ0Wojy6`3{W;m(@%ye4Z)Z;$em!(Sh996>Ss1n||8S>cP?OtyCYPb7@Dq#v$A|!m< zyPM@x34|+WcClT(Gzni?lkh>4@VUfFBp`^@?+~jWR3bD95fYk&2nkI>goGyHm((1v z16i6xpep2-nnXZo68>JA1a<#hG?Fc~&1bpNwHM~AYExbS6GkRDI0mf7Y)#REr6C`cys83g@ni*dn4SvQlADFh zc5={sUng6fsan=3)e_t+AT`UKY+@|MStyX=yhG@>Q*3{*)YU=X-4+U9SI=eBZWUa9bwR9S~BIJzkGy-+A*Z%brj>g+5xBhzG=FS33|4CcFtx5QI?g zNyv+@6%{G21q)N0s`o&Up(^o!3@nM~h-^dTHdojxy|ylhi(o!qiW~hUdU_{%>ar4i zbx2BaFO{Pj9@&fxdHRktO{C{AAF+}WQ(Irs!#?RKv=kLaLvkC%KO6)I^I?tPPc_Hb z`$U3Qx@=^%w2Ja7mnipgQxWHf9n6fTxKEv0aQ|VY=x`j?QK`~$ajA?GZm-zsC_(Sw zKtm>8x}OJTMziiC85y}dS!pEWULqAV5}rD78sx|IDg9VR`_xq2lht3T?a8>nNR>Q^ zgecbjR+^d;avn4$#|CM)hf2Mls z_nCp?xLs0Ic7n~qk%Rv@tKuKC2151*7umNH&nC5iqpc4*XmRU~WE|VeVF7vLL00Rq zLegP~5tb`HDAif;wHWykd9|`QQdRyGw=~uz%m^}?OS}$B?Q31^ebwyiLu|HekY@AJ z&3;)md+USZ4kI}FLYJWv6H6_0I$KnM&MH)A73GqX!v`OS4=xTLJiJ1(aH$a$i>uRM z;YL-3{!}3tc)n!dK6Lk7dL}AZUcm`A2F!Rw&Jo(8vmz(i6)vX^A7MC_X>#Xg zEX@s?_=t!lUo8Mw^L`_(d8D{Y6%K{xN;OaTKII3Mzm0w?nXn$RwX!%{3;8F-gM|AB z*`%1!%?rBN=vXwSr+A@=6vqUI-QnDO=LMuI@%(6TB&>?3Kqi!s-e8<>D#nu91BaYP@`mlz*jCjg~ zJU=CFbZw+t2p!@a3+!P*)+%2p*+UfbTJA2+nNc0rfFcQTG3e5WQ70v|;ZYqKIDuKx zE=tOrOj`_1YQ}P2Z(4Ny-m0U&J;1yyIS&D3(_{ zlqpK3Bq)Z<4#nxq4#m!8hvLl34#jC$^O;k2dEu(E6fY6S1b@!De1Dvocw9pXty-IEX|y^RvZJwm z9FpFY6fdQ{DkMZDu^>>qT@^1@i`y5CmD8FVo-VOjE&sP@xOm9&B{8r=tcip&tNdV$ zu)33(KAdZSF_javXO`PWk-=IDr9=||c?qXleFcQ2#T|#7hf%L`0#3+QP)IxHLR2I@x zJPaT^ajnKLG|Ojk(s-t7ibtra3Gi(X!Re`V)ywQkZ=w>ZsC+~O!Pq&0AT=MwW>%2&7!6l zGGT|BcP3Mk!ZcQ9>(Gu$bQ^;A2{xQ_QlE@`Q<#NKUN(#Sg_e~YfdGG$4TJTcykctN zYHf?6hKM4b0uawAmw3{IbB(q&MZ&3J^!z2(# zD}u*Te2lcri_{b!tAZpRSVer?es%{op6rUh1^n2Ox z4$=3J0DS#0Ys~;q-^Wku7Gn6T3c{lTLL3x`R0-khQqstTaH0$0>%Aa+T|)S}f)I5d zJ|xaG9!~K|ois5B(wdISq?kNp5bEa5g1y9*5l38&hHh*V&6`-IIOCd}c`>al>fE^T zs%zPPapUA|+pxU}wTQ~0tk|Az4*O>2`3H^nh!`(UL2ebPe5|4WkXLBcMzow{d0ttK z6^(OZ5J4c0jz8#R*Fg~cGa-0#wN$@D9+-|ohF)d+@CblZX(-_r+ur+G4y>qu##)8m z{ic*zr=_c%?p7O>S*=J!pd8i)i|g=O0M%d8)wM}cb&RNLQq@8z@3N|pqJ@d)*HjW> ziEARj?TBmPQ_JEG#3I#qhdBF1vgkF%Pw!$?Dut)`8JU^Fz%2YF%CEJ(DNb-W1E$JuCJ!;KR-_aBXWecsH1Q20r#w0qc@%2=Q>kNxUA7F*C@A0!cSq?RP&H`e`F%nee-jHq!;@&Xd zEL=ypQM;*EOQ)%QGaq*WNM=1EFQx9|)$U8sKKv3~`P1%}UZPqGpw_fZcD!BtOva@X zaXy6J(%Cn=w<@H3QG)C;?Ov7xIptIL%FQ_ISgrt%9&XV-tC;n5gsCD*`IkN;s&4G* zd$R2bxX*TRg?KAxREW1Q9$|a&DqYwX*7_@KBS8tP6XM=GaSU-&9cN=Pd54@tkl=|F zpC0#1w)8hSsWY%H19kC$a~KY}2#hn&&Z(-lL{e}U+XFTi>}L5PYcE?9&xQA1JTGL~ z{%*F0JhPj<7qW;$z*b#KGfE}+%no)LO1>#fHdE{zXH2yA@MuNk$zhDj8U!3+rA>I6 z>bh8=5J=J~xMUAF?A%>Uf@0_GBqAh%HAt`o1W3f%R*7T$SQoz=lzT3WbhbYc&(Xz; zLOWNn-x`$PrSM#-_Gq6^f*i;?I5fWO73%C39s6o3+oc0_9Rf7n~l>v zah`e*_j~i5w~IJ>2?+G&S5*h`FmP2T3&CHb#t;hHc{G47_8U~ETKjnbh!4604r0Kj zQ#co+4T=fS;{~D05NOZ03)0WBf-rKUEjX9gWjxbL2;;bzm*D?+UgWb! zLLQLoCr;`BlrPYYr$LE$E*?Uj{72Yb<9suV;G#qio0ENi5>H3iY)Y?URxNDym4dYgPHt!zGd{%A`acsH&dAZ4NG_^4t z&q2am4+M`8r)Kzus~nTZVm{I|nSO}atFT~r!gxkr2cCNA4i+Hqif_n7J`0bP4>3Q9 z5-C1gevCkH%0FTqj_Hsl;)BNh?&=>c#XYKFpoTOk?~S@=au}DeEqf#nrapzx2VSJO zaRTN=uQ(CMO2X>K5xMQ%$u`jH_K@Z?2dCn^CM1?xH+vrkAl>p5{Y~NMmvzI8RqHTb zk6^szlKrc$t4&$nlrJIO2%Zy$_c&Wmc#ob997Hf(>?Go(#=Y(ZuDMunOn+rOP{ilb z<`HlhPYyAjoRCi>Oo2_=9epjydz4;T2gwh@p-}x`px-0oYfpjdQrjCcs5L%Myvy`T z%!hme)xf?0La3I0D(WHdcC$SAx)3O9yErj_zcZe!O^-xbcf0f377fA*1w;aET(*@l z2)xkEp+We?_VPX350Vdr0@Wnf?}3dsA)0J*HX1#iXXmM2;#4=UlCY3hY3J14&q|14 zstEDS?H2D6PsdRR@jU9_kWI_!UP4q(PZz18RtHt`+PMka+AR?XDDHN~6c&>wWiWk{7c{k7>(n0u_5zx#G9Kxs!6F<>MpfZ zsbClU(txNK?xJG&0I2x#fMYGo9BV|4^?bBdmLBWta;%xx8s%93L$k$@$9ku037x9O zI$CHdUr)a)%5evs)9ZO59vR`wEnjg5eJA0;L;jV{y9l=~PH_r586H&TJ0|1&89GkZ z@-uFn!BSTc*{*ZjkY>2|goQ5LPtJ$Epc%%@+~SjXL5$DJ$U6%qM{sRg`>OUe1>m*H zTD-`4l@WVD?8#^0y5(UBR_1ll*kO00IZGC^oR9pF&WpRG*>VV>dl<{TKrsk;U~!j3 zJ3c*gxAR<5nDKPSjOUuI#goZsIK?mMW3#^7ovHWLh0TLvrZS#_$#<4zRWRTN%kWOW zJjLhBI~bUQV1)KT+zQK!RLQ$fA}FphoxoM5a@E9R}hu!U}(#sWX%~0bZB!$&QsK`$7{Q_+Bqt$&&h-^YLP4SDm zNl**s0>+FBmR?+wpAOSOhpX_fRBHedn{yxHf+$aM944I+dr+F5g)S$)`6Ni-jHoi# z0)YEr?#UdSml?i-++m`d0c`&Z9Y)#V*#5OosmHYM0sz8&>=5fLKg4Xu{2xTXlRgI^ z*-@PXc;z{OH{%?@E6xGDmGsLv2Y?mv1=-9lRxHi`)Dd~G;hQm7DbuF}Bh@JZjFeSN z7a$iW1zikx1Zm_i9~P3ri-*_)1S_uTzj)9#cH#3f?jz_I)v>{gwkeMd%=EE=R~{Q6 z3+XsEpfRZv08<Y`|W!O&XS&J~qg%^d>6d*kF-3HoyaETurAr9FuWd zog|h;>iD2mOm-JbM#ZrR6%kF^hU7ml+TNJZM!e}W1oo1P{$c@XkEY1)Xfec?1 z;}>TLwNY9Tf};8%mrt|NCrAb{wZ@Edg-{Pxyx=9BY9DWudwfPBQ;AyNlGNgW#}b+` zCi`Gwcn;CuJ?@g2;GRslw=sxmrugD42IUjf>zrn+P<3Xx%Lhk?FN@`Kh_IAKy;wOH zV->wT=qmCsy|5UI=<6&H0ch%l|T@`EgYNTOB{ZCpZ0(% z@pbjFwwnNExNz)FRunb~)Nm1Bsz4>mEs2SpOqisj54a=PL6irzYFt>!H9HQ8gQ6Xx zw1wrrLmUSYHSU1%VjMNQ?D`ooT(TpG!rSCq_+CWjW8UF~dN4GC;tvi;q#;+r3iWsV z$F;5js}6Ik4(q?_L$0)PkoF_&y&YK=AaRpcV!{2^gR}7Dc8bG!fSj2o* z02d|2TbbdsXlCMt^*6PL)w%jed9Kbr6l=0W$aN`95%pAaE_MT)Nud5U-)BK0B90ltQ0P2P~=3DNjN_;LUqdMv@$ z1nT4#9p&PyG#?97$MZ1Yh!4L|TX^gLJhikojULi(^I0%-0_x!-q+u4Ez@tjiHc9$A z%FaF_l)*jjm71t}Q0lx2g@~0C#TgGI%S?hPCM17LdsNk%rG&LHRCL{vL>!lUV z3K=9+G>WUP1oe%*?$i(**U>*bRe3Dq;isk^eoBe%O6R*1k7q%Qk0j zH+#Bz9_wP?mr&0vGwPI(1QProLw|1?`fkK`vwGP9$%p?>Mi7zB)4oZ(Ba-o6L4GjD zAf91+YK0y6-H0KMzVGRs*8{DPR`X0u?gqhvDA2Sm4xim5IMQfR7Ubr#4Fx(#b08AM z0Y0Q%_HSB6kk2*>-(5=VAJo8l+emMhpJT&&*hS(4Irs(ZJO*c(3AFuH5Y<1SeOoP+ zcd&1e4{}&NBmL2+sJL3KJ{u)$XRFnxqwsaCo1)^wp5o;E;11U2ux}ivuZ`kk14gCz z&;a!Jsp1_VZEfedk>wrYg(f2E+Zb1<8APY<(cZ;I9Zh~zo(=71pCBN_R`9j5PQiCq z(-whKPTtM^M%LTGlj06*gdbyT346HK@}w(coU1G?(df#(yt!;!H*Y49zvU2E`T@QP z4D_mQ-Xd5frKfmn7UP1Jxz}ZQH&A0Z-M*ZPHp^XsH&2I-&aw-C%h?nP0f5yh7X$D2@hmlv0X|lPTWzMCQnRCsD|wY5e7w5DpW?2OVro`S*Bx2g@mcmba7OvALW?@T<$I=PfCI zO&8D4$TpCj;#*-4sTbQxotQf*HHLAjj6>!+EZD?uv2biinaHqRE4);@u#vuSm=$Gr z$97c&1JdGacH%}m4yy6lLVQBUgV=f_kq{r)iHJ1Y?T$lb_hLHbZ9r5LYR`#|xY%B&>LIUgdm!kAyxpiR zzMO{XI$IB!PL|n?&zzvz2=R9096jW7@_e{uBl4=ncY&wrA-|L7?`G5R=&028V8_3i z1Z`-bH$Q+Y{CGz(H6k3+fIN|e6gSU~2q!b?oQQBTlctF`>{&@uBjPi0XGg-dqIl+) zkFme>(H<+Ae~PB(L%%OQ3Tu9cI&&B=4{5(c8;SVZ;Q_po*i3Wzw5!StQ>sjkoN`*e zQD?%R)}HCbHm>VEYiYTBs*=mWv1#72Ss>^tBc4f-=rQ(vh^hzOk5%4AYY(^fuP~nBH!h6Eh?`X|D&n7ZK2;^mUih;1 zjB;@su7g$DMuoTzw*<$;2JuW8litO3IG+_#jJADP?{G62Ir9djhqd1>{wc zi2%jd7pj1adke~MY`;c-aF|bm|DKt6r^eB7_5;so`AJ)1_)j1j%tEF{j;?0T|2se@ zEU{l6gb6YHJGc`K6l|vtXSXm4pN%y9G%6J4PT!qEv$2!R4(ve3i}|ka*RA46Wza!5 z`IP{G;aj%+eJ39;&_}m*eyDUPO;n+XA(20OBD6u!h0zy{GiNmyG`YGy@5!C@zsl3-G!wynr_Frq#nHI-bG!Xs{9j?IY_9@jJ8#v&Y*>CG zzC`r?RN4@mM_ZBYB-Sg%G2Tm+5?kD%|0DPBs^50D=SMBfAJrKDBh_TBTwKlU|F3Kb z4f2DGh~WW?r1XlU_#w?9WSCeqnZ#~{!E0>gOKBz~8*+#VBXba^B1E>R=;+~#FBh&m zXV;HUUZ9Q*Dw7 z*msW$U%HRKHDF;aD$UO(8x1KHL8AVO@;mY0@uK_#|L-eEuL{6D`lA8yj~4X*i5CBT z0VOop4+tWPS`dv40i*H<&5#eHQH2nTsaGUg62C+^gu!DG+~Xa0RuGr&{`yCm3P;SQ zb7{%uy`J@ObOO4IRt%~~%9LjeYYYq3omFG!N#VL!O~`vV!${N+)arG_i!&h8;W(}B zq_qaHI6@Fg^C8$&KQi!U^GMq}ZaU)bG8+a$@(z8ZzcqTKcNFp5K2`rMy<+o6*RBmV z^pd{2p5JjAj{H=TXq@hfyXaoeopwbtRkZkw<216D=<&|8DMAD28{HrHze{q)-Wa0x zlj6tslj2AFpGYF+Zf5WLUtg4p-T$H?`O}+JPZe=`sV#v-o1w(OgW@qv0O83i3B~$e zEa%j4uA5zXzU~Gr45f;q1Oy69c*^r2vq1W{z3|R*&DzcQ&0O6G2>1h!8S_M4c=qKa z^|>N5@yhNaHFixi0@QBXa0W9?nCo_-Qp#nWq0uvGnYEPbrw+4%PjEF*54mcoH4nJ` z{P=<}KmaWaFJR3@8x@$1x=j~(tX(2IoXs`Sk9-Fvm@XyFx*HgXoz3x|LsF+2 zU>#KpwW?Bc7vte}%kHLwO?oFHMrp1%AbegccfN~v9#h&XBNk;R=H@3&F0PLK?zRC^ zhPvMdC@DNk;@f{p0{1(0{0H5sLHyoKoo0C%$^J^nr+f; z>&p`R!lQq9MV@gS)a-ZZ+6${`VrU$U1C<`R`xh$bZkpFI3#{Nxk-!HY>q-cAijHbEa)(!~GS_r#2Fgz$)xL%i^TTN}rI@79$pVW`nQ1xt3@PBl^)JPJ}CqU||+6RK4 zxDugW;H1r1Ikf0qig(0S8$ZV!bxLD5IN>bRtyKT*4{NB{(L?lys696oYWwj3HN5m9 zGM1l`q?;_G^~Wx7zpEFo6b~EXk)YO?+YKwN;Tzcxpgxfe7i`hR&3BvvzCiZFJ3nX8 zxQAlVsUNXpF;d`;lA_7rz~f0G?rgN5plHC}Y4l$I~mJ&i(G41>;! zIf9BPbYZKAI1s_+o*S<>YH)HNaGw|!jZVa;#Beqj8^mnHrp^892oDziN1^MwKQ&f+ zNl`e9sAJWB_mEIeLj?B#ds`$ZBzVJ-Rd$zGslW&sgH<#R`)>o)zu(Cv9_-R#{|NW5{)YcHUYY zGf%59z!FkiydsR9gF?UHEi}t5az0q9oe#9hlI0&Y3{?v~#Ot@`A?vDn(0*1stDT;q zp;dVNaf_bhw#K;ZMEhA%@%bORsE{s5=PDY2f8$O+YTOBRSqf74vV@)s=|L9#>03_A8UMJHkA1 z)?qt0ij3=i(Fpm*rLTg$CfH<7r~1nWcyyNZHZ`F7;0{Ihx1<7ZT3Hxz88kXtL94O3 zDGoi#wUhEk%h^;@_fSe_sDdx#MMKn=*hGHL*Xbvi2!@qWa%_pkBwalYTnJ~&QbHQM zBV=FN`WuF>RKj&T6xmtY0N^lsFC}~Q7N+-tyDNIH_ty>8mJ1ATCAGL9sO!y^fupSU zYuP-xFE<8oLT!g`lcEE$BTn{Sj!=fNB{AX0${t4c3ANu6Y}oUd^U|#x*sAOk$B!K- zdcFv=#xB)Olqr~)lBHR(YLHC~v@?Z>k2Pl5<}{s|Gmh?_lr(r_@ZHf1+rM?5`B&YB zC0Qa-7Mbqkk3~m09f#%L89Z_M`nm&AHf+bYXe@~LV*xCA^4^$=xF}OmG!hCEpR5-h zUx{nX>WtMTG7=xH@fOG3qur^vsOX6Rj`MUymz%1n7^#J&>nnHts&d~sz71J5X8)T~ zv+N)b+HLf5_#@PKZ0$@@oTp-JqH(BL+laUHOEjZbD+P)7A%4q65;g&zcS((2n?(aX z7TI=D95twz5A1hj!sRyNJ+{zPMpHZcKCb)+AE(%d$+``ttN}tF(rCCX>=(?`J z;D&kVMyV|aak8k;sOZ7XlJ=wS;v2&H@1;QMQ5W0|OcUGkIX>lhjZSrCd9f5zoDoIg{K`(MZ{TA( zKnVp}Lmoih4a8#!Mk3Ua)I{!KztAPkS-d-Lt`D)hG-Z!U`3JNI8ez&21uy|@>E@Wa zIK*$6a*UdDXz747t8IP4<4LY=AHILxygwECZ~gt>kJ{&36hr>&k`z#z;wuE;Npvsg zvJ7&mA&2U$)RchCFZEHSF+W*{605+JaUy&*h?3Er2F)SH@Xnoj+o+zW_>y7uH`E?O z@g@B;c9jnungK=-+2xbcGq`(VGKcbT4&^INNVk@S1JSNdvE;R#(zE+BJoUER)wWc< zPtj%F8Ze-nCR%h`F6>2g%_j*$c3VPpn;;18QeQe;1Xkh)iW2JagLxE~{};1k~RPfSKY3S(AFrF!8I-5X4Oha@8)o56?cw-HkyC_X$$ z^$kuQb$#|k)$$6)=94n!#15mo#QYnl=$xBe9yvlm^->Ax3jh=xT1oTQG^K=oV+sGW zP5q>MCq;VWNq#!1*te(l&r4=dyoV*5RlXOK?hzf&pn8Qy`u35|D?a?4`tfW06iHfN zf%3besZMOq92GqN^&<5hoNT9Lzm@WpLb^w8+=KGf9&!L^Xy`Z8jXm+}tmvta>bsw` zzu-6m)H4b8TY?oTLF~ZAnnKZ~!K#AWGYj=cE_83f@wc?_7e~lvGWm%{=;ux92Sm~< z8(DARVe8VVJ#zmI<*P#QXP)!7*m!`-J%sd^B;Gdyl4qWxe!(WCTC9?Dbac5?|BRv& zE(#c>(O6WGBE6^-<_0zO+3dR`F&bPHH8&YJ8PcI<6tZ-zGdY=4l_X*sshZ|$QgV@Q zwl#~Yj!>$dG&NOaQAsv(L^4}*s1wT+lw5rBx^FUjA=(qb+KQ9`L4J+JKk5bH@x-R%*LB+rU<4w{^7Ve!86mm_KP(y54>1J^Tok@NQSSd~;i5F;dt^ z*=ZTS{B%{HIvhT>hU6hMzG+X^}3c(}IMN|;4)JiWldyFJ0#U7hpI;SKoB z-7B$sG&dtf66JwL`9o#i-&I(EaD|Q{(F^kHZ2Z`#*^r+a<6=te+(zHh<3QCFA-FW- z<(CR-oe43b5^&8E_Z^9T5qKONAIHc2*70z)*WOxcbmG&_cB}cl;t~GOA70$W#%8i? z20c}&Q}2RY@mcwjg#YI_;C&1Reu$RV?|N4 zLY2WC?>cD7!a_@_+zh~7OTl1v4h?oj#~ohX^Hm*uuj~t1yNVNgQ;(z7?P_#=+y1e{ z^@unX+*(LM!$Dz%Fa$7J>yR~_-TnCXBsUgUt6M$jG3PX&#=G|T<@Y{M?*6o(aru<<=JOU#|2IwJbb_p-5n7KX|LZs?&kxpFGT+~va3KgCE3LE@=V@p= zEZnog=MN2m(KA+P`aC0j9~2@apve}sO-~+BDWzO&wIIV6^8OB?gqX(6QF>9@Xxu{Y z_K!;5$Ex6h9sI&PwMt-Di#%Q^__Rg{ZJlKjw?G@Hb;96#JA$F128N9A%s@?hU+EN-w z6*a^4RgYUiuYv}>*bRmTZGRV4S*7nQ$ce2g%HHzzmWa-qF2fLBxRjcmUhB){fsHci z{C;GS$T0l=F)bYrS>nakcbvE5xGlPW(vonmOD^dB2lt%Sg+!g*lS~F8(yq7t^%=hQ ziBAel#`LC!71tqO|2czeFeJ&g1hnt$wjPNnY1bLPa+1RA+7 z%-6VTt!gcE+TRK6X!$pfCyzaUQ);;hZ51o^7 z4Ki|O#S8Y-IrRod+DB-X4J+<9*W7b4EcL$2fVN^Bl8m5hV3jjPZn>6VBg;Q)sx6c{ za_(60vhDEiW?Qf?wK8-0IbJy;T9kFHX?7Ftn`g2|-bdgnUT6)zBzG+#$;4!O6WA(i zQlTDAe3U06BjPG+rMp#?*}QT#@Q;W&WeYWz>}h;Uj&%zr)Er2FoHTuvwerU-X@RFk z0u*;9k;+ZsVPIh_(grxCYap^#k^2(2}dux?fnOyNB z`Xjcod+l6XM>E(Lz z6g|0d8mhArQx-D2R9mx^EL$A%37tKjvU_&vZTS#y2snQ@od}^r+T|hDYxQj_>X*cY z54NLivGGo+r0tLcwe8N&goR}q1+Mbb#H^uAkOr!P%LIHt~xApfx_Aq7l z1;H1^*VTM=7Z~eziIup8SYDa`{6mk4nyuBARrxD?$5baopByM(>+g|NUMub^Dt%#5 z{&5V22b7T$NPGt0DBlTCy?RLdM?r1$Y0Dm=G*{r0&=ZW}en9;Fiq7UM*I8H$MNshyg(7YgYx)9~vNh(OSslm{2DmZCgy}D8iq5rlv{}LX;Unzk zGo@LiF=S65gRPZ#qhv$z1l~Qu<^a*3RDLN1+DU^VT-|#v+S0cmK3^E zRTGn7Yp`;)#O0k&H_%T^XDN&xLF)NCF*NrBD}rXz)wKXA04~ls(Rj}V_MG{Y6Kqpr zE#Etb(_-ma+6nXB`z`y~U1>@zg>aL$rnS=np->DQ;Sx9XcWH84zPeV&^UX~em1U-Q zw2IEyX9lZr#g1O|-qt5+p_c^#P9?Y8=T-2$8*V_q>_Q^lqY#EqSzj2*;-RRdJ+F2a zu?SS$&7o7@a$bKPpt*`zq{n@J$sK-6Xm_=zHi(bU@7{0)mFx!9BR`f^+Sh9#m#CcA z_C#C8^cwT9YH({;Y|>m=Vufp^9t&F<{J~-g*6<5|+sIUo*AAMlP&rYfkLOTIuodVo>Uvir_@Q_KJlJ*75w&Y{+4GS-x$ z$~S*=T-zf9U?f*wXyDdiB9h$=;c@_?(e^3}Bs;q2kB?!6g@%R$&=y1zoc+Mpi_4Y) z@1W(cdF_hdp~=w&d;QZX4n7V2gjmdxLQ11zZIpDuu!Bdr<`$>Qveq#lg?V;Ghw!w5 z^Sf^y0LA?;tbj_K49nQ;M7C{_erf?&%OiKL<7_K^A?Qwbm6*iH-mJo;oIS^ZVUo#6 z6Zedzx3#omT+0sWBf8ViUwl79#3PYN$fi%MW=Fbw{HoI`Aa|Qw{TgbhrQeRa$6~)_ zSV3KvMw4%gofI_pxJk+`Sfyhszl2rbCvb$YWJ_c+gq`H2t(Y`H7o?(VokJQu3mF;s z%3zwz9@0EM+iDQzs+dE1ev9u{h=?*uQQiRjOjrHqbPR6n7(H_fp6)D0{ziQ3A#SSn zX%(lkU~Axw`x%s(Rh{1hW-eXrSkd1{7qV)P+1(C*SI`{AO7J(IBUVSm5K;onlX8~| z8%uN>3`A%y_%=KQIM*cE`s5J%zGM_ZLnA#6HUtx*IwNLKFa_{EP(WUgiw)p&2;o{EuX?W^ z7Ox-pzWD+Hg!uxg3b8@24;1&WKR?E=k1X16UH$-~{O^hUzo;blCk#gq8uza^_pb!^ zuQsnAalT*b`~ejC-&Fa%8MEKXv)}2n0%IoxM-Ksq4_>dnLi|jY2eT@i^x)-ZWGK1) z_iY9vK(*Nof?EB~l#1{sT5zdsv=>2*K}TaH&a8^@Mr*mM@e0%wAV;L>bm{m1^q}Bn zAkJAdYavtZXf?nm)HG}1Q}JkLp;B$?b#W+HjJ)g%co8NaejR~n>SHDv{GttI)EPqm zWg>&P5W)zSe{qozqD;UIP9#0>gt`3t=@Qd2^)wYTRg^MH8F`W)W^c5K#1KBjo)_%y z?UG57cV>q^PofS_2up%a-L*y4V)AqVa*iwTRGlmM98Y9y>q&yh2{n08@`9=!+M4^Q zf+VqwSuIY`9F)46bYEcm7M2%%!e+0F9(!%osUjB7rE8*CF^!{XEAy$MWSH$Pjzz~tq=|YIG=7fHEF7o5Q38;XX;7=Era(o5 zcu_fxCLVnYMBf!eaW(IkJH;j=$Y1%@sMx?1k#H^%>}2SZxTZ$FB%=__<#J}+ab#DJ zLF7ZG*k5x|dy&O!V_p7=65&bsb?S*m1eZ*?D6lqT^&CEJvDU`rgq2OU_pGW+%L6ll zsXwk;^-yGuhA~c4!jW?J@+R=FI;14515U)E)HsrMti%D_s5oJBT-muYVvyQr?)aqv z`Qqi7&DH{TnH~BCuE#jMTEi0=zfY1=#L<&?MPMgp8~)%Yl1JnWeQJ|0qL0rC--VmV z4?X&0hxmJ-sgyMl-#0leXp9GMtt=90Hn2e^dHmq2aMzkJY*vgk5xS~Skf%c5p?Je5 zq)dy8CwI~%!w5TFng_%)s?7Wq1aU{CupDET*c}jF67dGjMRA;OIdEh`nm}O{ z0I8!ColI;r=_faf1b^#ZTZaEdzg2I78zz$%_JEyBnc+ngE)sI{I`F8SX&(bl5w2fYMhv zO50D)j?$E59;q$)7y>Nzo~_N-D!n55c)Y0t$LQLMGr!>+j@B0%{roamr41@}ph#(s zcwWV^XGT3Q+#8@*(G)`r$qUEUOuAy+Xv=4#C{9#sY5plzS@OkO;s~cZb?*$f35qQ& z7zH^~hy6TNQUej2V|0Owon?5fIur#xcJ3RyjBt^B0g*v%uY?lNdHVwL0f;cMMuhGp zcUU*?IHO;yNl)k$BPCFOfO7Ct8;>rrD}xo(I!I32*UwRimUY>#YDyvFQEGfzKx1#z zXpSqgrg$+p32uR-Y=7h)1{HP=q&%MKg%N_lR1g z)dgcsDih?MjYnmy^0(!A;@LeorL7M31s^t!05*X@H7x}Dn%$uirtyq9W2le_{A8kB z0<~pNIbwQSIRah#+$}d$e<=hMJ#y0<73Z9cNzRSv114ZME(=bCM3@w}Am|{5Q8=~$9O}ml0?<|SQ z?J0W_f@XHXBjlpnl+?9PinVBzIP+_(0J=~d=qeO`XuPUo^M~YeK zdimFB+^#GOu+<6yV4W7JWvlu15vSE?el>a5l=nq<6r_R+@!d4mV}aS#q89SSh3q_T?s57-wxZBMz$j;=G+9BQe6Xy#|f_rVNSb|ZaVw65*w?%knT@I^Q z>#b&yrSmepLVb=aAUG_VB#P+xq!UTWQNmqR)Q1Me?cB?Pp~V%0JzykBZ5qq$hiD>1lSDl9V!lvGLoG`pu|N^&&tKMD+4}l&~@JQDfs$JX{k_ z_bX>S%SpkzKeMiNv$TX^rJ8dLna+kyn)gE#)CXB)FXWS^ww5IKxP{PYUUHYzc4N(Z z!4IsK3M7729Wg}2p`m6l(W$8AuO!rya6e4i0~HUMkL*Jaa+}?%6>GeR`+z*gLsZDJ zr`Jb6GtE3C3={R&rN=ug4k?k6FL+IlJ)p>PfEKv?ozuLsnxs&k$){W8`Y!Nuq< zwFO%(!8>Yo|5MWOZ&eeQKFM^zi0wv8ikR{^gnUMGaForn^ZghI$ z4n`7=;IKB0MZBj8+c`clBQuwK&9K$>^)1c;f>FhZk)waKU{1B;BZ9Sv=vDrm7X@`A zn72owI;iYQAGnY?vaOT@Gn;&VP<@ITj-4{R~rNF9ez317VE86sf$z=t7umn zw9_+$ep9DimTkk4fRR;579~~kyr_1>#jqi-6i_!qGA4uiiCh2v^cpm~CQdw;#d@+a zsTon=EEHeRSClNmnB3l2!+4kUtaLgZHT*zv24*eXNZG2R84pP0C#}(eKfV%JEruZw zTi`3+aJ6u(t0Xv~o*~0p+roJa_V+FG?AomHY#<(QF*cadS`Kzw`{85hIK<_1S*@PB zInT%I!a>Fb(KcKv$6)k&`ZN30hM_qPDwvP!Qa#B!!2rGELX%YLG@3g|+HvrM6rDz* zoRy#aJ~C3%i-^6i@tDcwL1l^8W^z9KVs}#6^kk2WmnT*EF7Y>6NG1g&Wv#>2ft@AD z4>9!V6tQRM2mOuazCP^GbRwzYRMGpm>DGd0SY5#!<{V@M4Q2-PG7d~C4~gDLhy5=E z`wJkG+v|tfozSNP*K=l+ssS5pabUpBZ>)ojFU~j1s56|sV*&7KOC&6vm^LM{HNnnzznVL9{tFpts%BZ_`&ESj8{6{L|=&);$+~=NEPxEQ3+%8k4x? zhN^qB>Um~$x1MM=kUe7cIAVjrBHPs}v8T3Bww_Qm8e-Yl5se1g5i;)qu~EM?0XmGp zcU+2G_Z&k;T5D?E$T-BjN>?1U_C)LP^aJ*6lGPS#UBMd66txrfESbSS$L+(fU2T10 z29)y{QZ49{_sHWfUbuc#1nT``MZ0)CRdeD60v;4QroX+u{vIhleP`TErZRxP2XJ*e zu0d(E?Ga-z@G16XBQmDB_IC+6l!r#r$xUdwnAu8VMZ_piC{Z0bDot=vA62(IvzO$; zY0QMDHWlQ#ZzLALY0N48|0A5{v;wu+^17|&SaN=)El`_@lm4tCmG#6q4iJU?&zN;y0a1Bm0BBp4&0l;E~t?N@a|3e_z}(B97e{$NMy* zR3E1fce(HdqK*f+ZYTV%$_aN{(22DUHz=fQTHi** zfnQpkJEF6YUe=XgJYARWSgvu~y*e|4e^}V#x1K<3rv)YeZcsj5s5PaTAY%_1p9M0N z!+4i4A=8EwbTBkcVoz=m(@ur)7H?gp0RX6X=K~RctFt_i=LK`1*K}JJUI$GTxF2M^ zYZ9~tzVsM)93#1vNJq@u8@)HlH$?@ z7E9q)IW@S(n}N;KVkjW&Vu2_i?4Kde-rnNM1pgs^^L3!1*{d$T_e*@JeM`?mFu7Ms z2$L}e-R1|zr&p3|BR$`~`<|N9I#ghEH6{pnb!_gNW94EcAxy5v4M)eM->U@X71`Gk z5}h1o1+qQVt`S+;$ObYE6d_)%m%Iy*MEws(y$|Usr|ZrBTgEY9{IWLV{w>Y z?&MR_ZZT{cLwQp{G2Tt$BF2)kVr74SYFc6HT-Bt|$0HKQ9^Z`F;xuLS)fm-C2~|xa z`ti{1BVW~B>dQJb`y~A883`MPvZq0|%!$NPcx^XDAr@}WpBPb#!F2rRlrKvvg8Y@! z-FDP#);(4q{QIdN6gy90&52n2gyaE|gotUN#cR$~4C87vxtA9ZzH0iqJ?yh1%Ecq~ zsbincDXD}jgj)g%LA4LG68wy4X~zAKIVFS4^9m^tG&F`yQcN-@S4;|6T~Z^^KRTYMj`!!9^%D2#2XleG#)J)?aFe*v zAQfbTlBOrNu`73b_BNC%GA#@AgOr#aru+QfwfCj>5?cp`sZ%$UOZVP}vc3t}_jUs8 zWO7>sFYpb0$TMhH#123)MnM9`o07oEmB*QJ;L@=(M`f_o-G4TGPEi%Zi!#1&eZD(R zi~wy=99VP>tpTr>=0VIGfvI%oWoDfLIcM%J8jJhc2hERPV^%NY<&~uYn)sj|BsTEY zY^)7@k)=7)g>)r288xsJ*^x>4)D^787i^~o!bKa z5%ZGlkOOb5!Cz1=BT>}sQXJ@!g)<^R=eGXJuK(=<=Os;CLc=p$rnycHXJiTwX z>JZ&tzwipgU%jXeKIPN}o`>=QwDD4V@WeRS7b|)KSEZl`Vf6O-#nfDK6MY?i8V=|N zO4@aM94XlqWX)k)lDl@GC32ib2h#Ytx|z*&d?A+D21myzZGZf$Gjj|$$q?UMVOD}? zqv%eL>R@s;BkI4gQKI4ic@Y335R{;|#qxl&aVhUv5$|QsR`43a_5P!6HY&5AL~jJD zSN3k-7@OzDFUR@&6iv1|b=Fa$O&H%7)WE|uxQ>VUW0%BEBQhibRp8(5aPU=kBV^3U zijH_Cg)-eak?P@=EXT8pgQB?uy!I228XUI4$DSy|KWs~vx; zThxGkZN?MoG&`i>MbSOTGGkSH`l9;_>voNeH|liE$tGW;#{t6e_vAF)*BNvo9?uz0 zo`>r1aF6m|9`{ufBKpI@y7yB`mO&x2`oXxBR+IjQA0*xiwO5CU3!3Kjsxx_MC;{js z*w)->?S%s`{S)T-PQV>pf8(22X0o zxy^PPX(YEwhqQy^KgU*|S=k*Q@>QRa8C-K2XIH*nVhOYi-V1z)-f;V=iv0n8J_0}R z{l||?fESQ8E}9yIS;N3gY~N@?e{sCEMSTA#XOhJ5l|K_E4#y@IIEi5XZj+dt5)Mt} zs|i=RuslV z$YXH2<_qGcesf&#)0;2G+rMMd_FXCX z&$gbi+A@2Y$ZwHHYK;!&5Ykk1pl@opZnHa?{h-mAJFJ_k-QjxCG6tHXPSB0Kv1<>n z@$fL`<=VNLP|&_l@nHByi0+E8+YJ!tyr}es4TLp{3*to3I8Qd`u4tc)*V_iQKTgh0 zy(Gyg6i$5k0n?jo-z*9zlvA%P6P{=JawFxyz!-bIDuSZwgPSfy&Pf$;y2Pa`;A>7l zIke=SK7mecm&3c0L5&!T;;^jp21&F8U~0NUI1#TO(<54zK)K;+pd7P(fA)DOF4-Z} zEF6dhF{1w|_cZWXxQ5;SCSsxC@1q(cvBz6&+TVBJVf25e@U1=F$5-?b`^#xM8{}t= z(q`~MjD72|LdR{x7l4ahX#5-wM5W!zB9Cp)Q|F2x-MkV6>;i;yew%1hJ! zuAcX=zT(wCE`4&J%} z96f{s6!87;QltC#Q~LZavrJb%{@4?ou-%9~ULF>rvbw}0h{LE%y>KyP*Vqto-qeGx z57mBmYWboj{Us_*RS_>(|I3MxG}zM1W7=Gv$MfHp)I(@Dn0mP7!he2yo*wU&#ej?z zbMOQw7k4;9ZTZC>SDoS&7myK$nkS?KsFf`l({lM(+7E=FQL^Zp4o#KX3;)nWL>v+6 z3##TH7ASa8Qh2_exuB?J_h=j{B{I~Zv5e6W((-4Y1P^F&H93ML@7eYGqD!^$O67AK z7-`I%m%0&O1ssouz0CEArBB`AdD=i@UEx)S=auMsFSZALa96mz&%j^pwmxS=z6 z5nL36(4voEFGtUxcu^~VR|uckyCB35x*>)^1C@T`!p9%&ng4V0??QGff+M*l!fL~5 zW5=AMWZw;%MS9{OfWi+Db%u=ui2e#;x2z;kD@%k{PoO!AKb z68mY;F_>`j2#MvKm~G-dUF;_B5pA&c6=5W#&Hk5@TXMjt2}Ocnf?d&c8G`q}8pmJ7 zuF&C&EdeZ>aN&zZO3?vOsTb20&how;3?Y_(7R>IDFq`FgMD}{Q=mw*(u;f9pe!4b; zj+q3Yq_eMtgiL#cgnM|Pq~v&@njeIH&IlmN?$#5lD)$9%HD5x3R^^96f(!39K#K@R zwhf$}1k__;0Cx0~og_V04nBC1MK8^~&Va((b(90gFV~rKQQjQ{ehSs=Uj{Qb3HfLl zoZW$BW8Z+JX{g$p%S@E6?MK^F;E0z^N3`fB^yS2N?3@jgh`t*YgTAqdZcMIs4IZ7g)Y32- zA6LQ?6D;6^gk~*1F@mtQA4;Tl0PYO{V6J)0i=litsbvcnx5pKM9?ASWD^7R=s?+%R z+g^{;ymv_63=Z1^#@u|E`OyN5me)s0_=dm4$$uvd=SPb`x_Q6YI@TW4HaJ1e=|jQ4 z2bfhS1k8d{A07+YKFLr5ASZm_GmX5oiu*^~65oKM5fO>U*?|Fw11D#KfEt#fn#Z(rrnPR-0#~{#A>W z5M5q4CGWK#_Uvao#qXdMrK^h8QqpIp1K1Z#Di1`@J-JNol1)Z0Y`JJj>o9eA*q?Q& z5>>8s`ITP&iC(Nl&((rwm~Ke!QWc5(lRgaNrkZI}>S=^uINi$JRUZfqQv5Kc9m;s2#JOgaW-{nvG+pZ5FwZ7KMCS+23+X_ZVdQRDFDpqZ5Rp^Bn7o-NNg7j zR-yec=YX(o)vh+mz-t#dSS(!NZKGjj50-;H9hTkHkG!# z)RLu9mq^TJlB&AdmMg6+cS4#gO}P05^qW*@wTPj6bQR2GVj6O5`dKnY9ry#AaqJ-R zu-Mm!X;ps~*Eju}2pkgck1TQ`(TP~bLvS#wWG43ac~Hr8IhF80eE93XKhG3Wr8NPh zJy=Af@t}RD0@=e|zD1{kHe5=4h}?9;AVpn#@uVRlnVjmi>yC2#_(UuFXZjdd-FpTWqgLVdrUVA(+JZYT&K zplSplAhQ3r8%)a8%0A?0GPRJ!iBv68d9!EqL5Hy-=x2^x>Vz%V-R_Is!cpQD;jhEx&)eyyejbn992&r|)fUZR3?C*QOw zgp_6gWtV)*KliEEbaJRJJz*Q~Zw@u)5#k|#ns!u2t?$l8+qMP1dD+>aHF%ILdUa~s zv_P_(>Yv9%nQlk6O!98#=X;DBL@T_~y?-x0)Z_{1Ucijzhg=*FOLQYHf0?lB6SjO2+j7vc z>jTx``o}QL)stGyU`ylRWLhU$r>`f(VL$~6AOuS8K+Pj%7@Yz9VrVepj=p$fQET_e z!a_J{4-=p}8&Y0<9c7~5@=@V;`C;zD&IgN0pkGYNg|)}ZZ>_?Jh*p>?Z!Hc{`J)r4 zKEN1gVs|SB_*RP<0~IJQ(VQ8;f80g~2m8DBM4h3kM_c~B%6kw0f(KaT*F44c>O9j% zi*B*?HO6+~!Z`M5KHvg0{zahvQ;4PhfcaMUgyDcJ7~phfH(xCzSha_Ch}rPC4HU)# z3j;!deh00{10Ol-!)u-ERuu``h{2^j<$$@s;ZXBL8(JfjkS?eb?l7WAu*Q$)>|_AI zOJt196c1qa6{3$7FrmY6?r!2E)I6m@f@4a zhMtA7klG%t4u~JKdJtrdp?-kb`JWzIwVc>vfhn~rQ>h*5pNd|@rD>InY6W4O&W~;w zk>j!4MsvMv7OJ$W!wMM?iHy@RLd=XdV?Lx1x^(V3wOx_)-G^o4eYx?AxtaI!Q@D&I zOLy#rE{$tyt~ln^TamM#8ilyD98TK`F?_dg4u9J+@ply`65JFVd*Jj;-fo3I+*{AQ z|9;gUe97|LHlOx#U^|(c`|xkd|H)hxeju7YsOL1{7x9k#y@4pi;-y2p2B;p$7GEq; zM=u=|KXY1DH36cQE9RQ#4U^nw6HVZF;D|F*sYhu@r)r*vCn=GOly)6`=66SOKcptU zXU@{p5PL6q{?b~gvGmUWD{hP1z+`*Z3s(Bl&h%aug}gJ#$}$dH6op*Rr!;&Y6Ddos zFAi~}X^c&Syo`!LN)t8|R*k&Ch0~0s4l`_m$p)XIS%&u->xCI(qQaOWG!(VzN;zxcnvK#8R`Pep!UB)b)!LdYg z|Nkg^$LP$uZCkiv+pgHQom6bwd19kt+qP}nwr#88RFVpB_C0r>yWidJe!ufQtF6CJ zYjgEJ<{WdN4_@io*OyIALsVuCfV3LsLTTyp{1{P-$guTFWO#&)(lc!P znCg}Q`A#Jca#~dV2XelI1O*>`#uVzkRIpN$ozYF?I5#XhsgJC3JbP_+x5p|IZH+$2 zkZ#!lw5B0WD|#p?921p}VS<81wV@FaB}BC=0oztBK+ZLE`FMisGilF43aw>=N~oe@ z)j%1%QYX$?My?^K)ilDUXt+A`v?@wF4UTz3*q$cBwbDH%r~ETNL&oEUKh}{Wz@PK! z5`3tsxbmp7XeN<#&1LFh#4Rh>*HZ%S2RB@FmqC;cJCZt*sGQb!(pd=VIWUz_tbDc@@ip>WTd#OG%75 zgBeF5&kf4EnV`Dv8TIQ~iRs>Eux|jHW@p+gyuH)i#TUD*K_YWS>qLUCGp8o|^V7so z^)D%~=YP|MXux)VK>5-^$oP86|K=<;a4>QDm!AKq*eS^YdBmWZLK&m=#^ulRYPvzT zABAA-;Tfm-M6I=^1nGb_#DsVc%S)qY3V)*38z4`LTQW#GWji*nXxp;17Bv!`9o8?;!7BzEjg63ei1MO z9;5r+(iQGsuK18iC~=U^l6Ft@3s(EL6zEE0*_qU~UuRL*@9HI@WAo9c^S5Xe=S?U` z6aJ`GLA(s~X52=7&eC3@#!vPAliZ;%ndFQQu5mgMF z6)xv3-|2nvFmuv>U-^Ci4*C6Xen0+A+siDLYNdX$6WMZ#K@1$tbKH|DLQ3&oF#;0{ zv>tf^`L`qT{rvCugnGpUDNHPuIcL-3q(bx~(&JNzkPu&5Ux1mBvA(fBu%T&g0hR_P zmO7@o9}W1ozb>tJ4!K?Mi!O{`dJ%u86=7@Rj+02>I%E`|>l0(vtdh}5QQ%wp= z{POe0RCvSvrN6>4SA?J)2WGFU#LxH{)cwLag}rdcIgSRvQiAsmef)|-QEVFZzC)WD zQ4}Zx2otz%hN+tEck_dVG1$u6R$S}@*`U-*(yqV)Jg7-${8<*$Ex~P(>4f;4xk=N~rn4MPKqtZ2L8RZwS&X_u5HLi|ah~aV;H*F{K(U z?I^U!Yeem{?ZM4z^aETIv*YOFL#Ftp6Q*&M@H&0F^4=mS3?eE;KYIssb^YX3?F0Tz z3uh++4Z!;a1=If@D46~U#f0R504nc7@o-^TRLzSvD&_nLQNGlW%&Aamt0O@IVOIQf z;^eA#$}WiZGmv+p8%jrl5X6_R<}|14^xpK-%n61sbtYGB?PY#29)`@X_0@*Cdi&m5 zgJB_8I?QQtDmf5*Gs9x{Tnw_%oF;{Z@5=LsR5)b)rElq&>|aPs%wANBpYbxO=SgyW zAu&dBFu?hR#4n$KpdX4hPVz*Orj8T`(g4B*k1r(Z>`(jp;vjG`M4f)^fxMyF1b z2Dp-w&J57Ze<9(6JXP>L!Wf%}N5)OMf4#yuDv&L66gJCQmsp{KHrRrTST8t*tzv6` zbCxlVxe=htDi44B8l-@26x0loQtlkCnTUbb!tVxh;gfskZy3kRlAQWZI(*jS54WxG z9-v$+S|Uo&M-Dljtm+=1_g9Os_?lUi`z22y^d(R6cgFs&7V*zpiG~`g8sbN~#!vH~ zh~S7CsD0LKnY+ngEYODE{SzSlB2DYk&L$hDr4!EZhL1O-DOibuAvat>!L4^(65Q%si-3semg%QSTmBZ@bn zBqv13Dw1ganIz%I3xhkVZzxG|CxWCTT}(QkWpaj_aL?lzRkR}7Nw_Zqbf?c(gZ@$< zZcrNR+a9SqJAL7r%*$jLdnk zY5A6Mbuo()J*2tb5RWbT0}a+IljM15HqCt7GO6`ttZ6D0p1QyY2nfxpe4r+=gzOqr zk-W>pW6W;?96Tg>`*{7^$65DD5a4DAg8grKj6hd>yhX@}#KrME%$3s7R45utk|SC~ zbh9GylM%)16&UxF0U#mCqGZzK%$oC|cxnm7@p5KMHtkHngNdfIim2OYhfP#~cW;O~ zTIxZK=rs0<%!P zpY9?i+ISqLGh{m0PS6D%1W!BjEa_1@uUk~8cx1SakE`7Qzg88jd~{`0)oz^x)79E$ zbERG0HtS0x&QJMLkG-`e45pGN>yo_fsLb0Z_|uJbrg3Ywd?FQ&y3+0lmFY3oAT)yi zz==_U8Ga)vrCu^d)5uwH>P*SvPpP#fDwybAltr-EyYCz8z`0IDgSH_Iufd2zubMfO zYEoRv0to4W&)!xaN*-h5!qa!j940JaHEaoBjEo^y&Vw!ZorBXSO(VNYjm&E5Iq2>> z^b+8PXYUAh$10?!xD!EKqIIbP^TSd@&>s>u#wZU+fCy1em^6a=>AU=)5a$RuunyWQd?4q+TL`8nOUu5&ywGDOjn!~%)ZrO=nmkW8I+amVz~ z#7VW@r|8%yaX702wkT(v531WD_YC$ku)qrQEwvvQjnf+OGjle^Qc@G~ui`MO{TT`* zv!`cCW5vj7W+qIX2c^FR3lUu zRq8Tx7ZKl=gkKnUqmlh1oqJiRn zSlP#~xJcw4z5}$D;Y29}) z6kWz_M9cDh{(_&&Ks5_OUH;yi>vBm`A0U6-=f#{D{hz*qPV5BVzES^Yr%uGg)WF%w zN%(8K|8J43D)lcNhZSc&*JIlI?M^`$?SUK9S~#{)q<980Ba8s(QI2Go;busYT@q$< ze8XRY@vOw?B@&t;EKv)q5}Ct5N|8a9L$ua)MsSFs=X7NYEL$1HQkpH!312ZPOsUe( zw(BV(62qrgF3(Jlowk!~k4MLvZttTB6cMM2Shg)Wa+#mY#fVvrr+i#PyPV(!u(Hf< z*?n@%ZheJaJkgBLL;)CBZ_~)KGQih_rK^d-$(;`3^#FLKhpbL)$gzCzxfR0>_p7&)LVZ{ z7xu895lFv?3)WTr0lJp0iRF)_cOURiZ{bViO}(TrhIrxrDLL@R?}J-F`3^p%`8a~q zwikyE+TtNhO!AKqM1i2;@55J!`l2`kx!K@~y+^g%G7wA8E`6~)M%IBvzl*!-- zyS~I=^eqL^fAF#v*`rmK7Pr|QF3b|leB|jJ#R9_q8J|M__D zsF&XpaCtTmlr^}K-zw4lh6ajoy%z9}?&Ay>4eOh8goi&4f7pzR@2@$qEl;>{VqKOO z`TLKbXBi5&mg*I1p)=gEU3$XQ(evh&U#2VOrdE50J1aQWEBJ|x?G&{Y7&=J~z~9LGoo=8pp($GKk|C;BYT1g7DmG2+MjDE!fsmZ341ZbREqHG^ zWVmo+_Xs(0J?mj!BHY!uk;nUxD|~Hq;|)%rA8Qa>)CK?hmS&{400Jydc;pN#;bSb% zr+7r>1ta7m=>slAyGm-N8V{aVLJm^%Pjmzd%-c2Ddx-F-#OKj01Yk6ehxc$+{8SwG z1()||)@R|9p?Y^VBk~oL_iFb0!aL{;pKJ{010|9E#df#pUpc zju?q{)DBO*ErHZMx(pYKD1qZCbk3Hr{8__8qQn9`%`Krg%_qK9>A(VDvSR|KH5n})3o(4qU+P#8@1vGX9UaLs( zKEs%kM)CBsj3KhMYw!z5;w+(J;w)jN&3M43_$k+q`Rs{!)A*y??Ayi zPj`ft=xMq!nkjBTq9 zxshAiUV|Y*X3PAv`P9&|qw;a=XghO2d42oaQmG+t5r!~Yby9;)yL?Lrpkov(ho8U> zUIT7H`ck39lucP5!FJm89~Rw+KML*+0^cocF`4tT7des3B2dqH#rN#z75AiGIL^vu zIj~@*!Q+$@)LCU!%k8H))_h;oD4{zO`jgcqaZ21_u@$GHOe}tey%AVA*{wpJJwt#g z*2FT;L19W|=SP!}-A^kyiq&bA+*)u{2PK>Uys4@gKXv8Jv2IrA*SZW`J!+CyuTYp0 zT9*e<8H+=2>-_P-S$;^*bLELpfD*YOe)qr4ZVqeQZk~=!UzAgbSnMmwBMazz!%o!` z*5a@UEP~fKpJ6*##b)=|A6`3mM-QXqDVGihw)X}1C$;{KPH;KnMYu{dW zY_!9HUsBb{6(w9(LBWBqW5~4ygN08ho=kQYw9U*v!j#TFMs8!l=}TS*giB;=mPmL2 zB5mwZuyWi)_d#hrI^ZbT<7kvR$pWGy6iVR|#LMIE{s@E{x^-P&sr|@V-E|>DDOj8! z7;6H^SVS>CmL>za!%5!mJs9hj>ZZWd-NFqvpZCjB;Q9M!W(4};iSeZWA2PfgdB zDb&jYC){0inR81+uN5)h@r$G#O+_3wO&OYPVy3$v#^h5n$y1;(1kN7-@8||k$iUQ- z#x04uvTveSAyd1QZqt;dRwB|aHWuv>6;PFLUtGf4V>U{x8GxXow~v|I$Kr7lmbUg? zVs5~&Fq?!n3@egQBXJTZsvNSP|UmrASm-+M6;gF;bX}i|j83#L0mAO0s z5GE~MTqtJQ%P%Q9Go44m+B=U6v9SmYK}IVA4K8)qHv@>oc)sr39lF8i1;N$A^+Ci1$(y ze<{1%Skbr*W!YDi<586AD>9Z|5b_T|Ap8*<<|~rm1OjZ@q<%+6yQCt72Nn4M2}~Gn zKUB`7Q@$pghPKM38&#;1g%dA8)ksDkfz8jfv3$Nk1Wp^$$*on z0UGRJLed~9wmbNQ8zs zIy0qn$gMBR^29grt0%Vxn7oapXBRv^tUkC7bkbERXo8zQrr!iy&W~D|r583bLqkYr zep-he><$%jIe}*-DRA^;?nm774Am(VAC-PKiF9UVj-t|{8WyA$v(>NelJQb4#+)*d zmY9QW5Y%A_Tal4b1{!d><{+q2X_m_@Gl-(^GwS=l5kgY_eC8sdArOkKx%W0#+n%)rSXJ-dGR1;;l@ zjX~;tOZ>f)A$iA}droa>W2>GP&W@Pyd%G6IcJ&vWe3`Fj7rI&%YF2)s1b<{8>#Yx% zzv-IRqKVv`7~yt)mF?Uy2jtG)?6s&7-T(7p$`}lGpS@$dj zi=y!DJyitUM?RIu6NS=+rAn9Rnq|VzreAFz*ePAP*jnPw_R1)m)v@$qMPrPTAz#7* zmPJ2-L?fviLC@;kkXt~v+NB9~%5L@qf{>LN7;F%1%|(Cf!^zu=0gNgZ&#?!i6c?BT zW^9k3(BG8ojekhwz3vbu8(O_AiO2;0+2ple;#GrsXA%U(lXBySK&B~sm?^JXre50G z#BxqTc_?^L7vKidSliuk@cK-@f&v9E&oqYUxVZ-R$!~Hci#K;_buUHK%)P>6@L=g1 z;Ss--pnZZJ$0jJESV=AmiqKD;SQph?yQs-5SX_%(497VycV@LMj%ux5{zL6|A+$k9 z(1$7kIP~7TZ#&t!}=RSvj#Yd@xFR0&fXr8t&F3=HtMI=JX$frx-qe3^j zr0}9)5G5AtHnpH%NN?z?dKxJY=EA}}FZfjJV0rhc=x+b0#%>pC5h%jsyX%pe9lZ|;X=A}8dTv;!E$C7Igw|eB8$*DBdX|k;h^Zb`?^140mgiLIU^UFeLgtB zDwI(M6d;pK>O9Mcg`aS0d~-PK2Tn$}d`ex!mq9VdV)A3#S+%7FDAsB0V>&vy0CSXRar@(OKE&(%yW zj?RiZ<2M$s;w!NkoOE?*^ry95K2;R868QnK;y*OJXumWk5ild~sjv>ozH&jkZ`w&$ zLA!!491ERdVvYwIjR^R5_g4=KOz95?jQ}!wvG#)2Y1IUU3gqvQD8I2P%7QB@d|ltI z7Rv$UY_F|YSQZI3I0=h1h7-Pj#?Bp?Kq6{!+RJCgYVU$vfaW9$7KiT#!P;p>YUVGW znv>2TH1*^kjec?Qup((~QWf_o+)F32SjGpcU%Cd)EG+Z^aG$anmkPFv%gv9I1Pii;|J5v)(MPGF+g>A3`tATRJRN|o0styE>?F~ z=R&_cDYD(#|0LU8K_i*BrnFk;x}W)e9)oMLu8QQiQC@sX3fWfPhIC%63KoBNEpn7i zEnf#o6rmG#jsrsK*YTq0twzEWDX?0_eCq@B zi)WB866U@Y3aE-C`{Qn)@(Dj%icb-?$)V?9_+~(6*Tufoh$kz)L>g7Y(Z%#L)1_3- ziAY>#Hhhs)VBK;-R+6jSptw+{14AMNOlucq@8{u;fK^^m!MVR{yW*|i(};H-(L_kUb*rhL*I9C9UrHRx{B$kwST? zvS*oB=_0x1j&Q^9kIh75k5`&LqUT+RXN7$$G*D4Hg6YgOyy@vQFApDgF!?@JWG-~) z!`eb4NkRCD@G6St^40WUXE+?#TX{`CV|}>bz>y&0Ys^SrSKf+&OLT&Hoo092-?R%2 zfkwI$y=byYgeZ5tO`U8ALMs^TQ84qZ#y^%k1>=R9X=_FfJW58yLy{7_kog^hsAsUo zxn=n6q_=N3ZAPc;zE8>0Cw_ncVmfEoFTCiCom-|5EB>&tGla;%xr0bZ<4{mAprXdH z#R{e8J%pHgQMA>zc^z$BU7F`l#D1<#s_?RC70|Wx{Dkfq!EJ3GD7*A%zx?Dh2%SeX z7E=U+X!ObAqXI8}L;R}IB8kTc=bZd5yhd{iSwDzrt)Fe@M@5#e3>IE=$E;rV7C4Hj zfMyiFVKk@`k16aicd-;&iC>s-yVx5Z` zourna9haJ%Lx4vB+5lac9vm&VFDoF`Z0cmnS+fmNO=1O9FTTd6r%8)Yx78*<1fGdE-lqRFeYPvq(WC-q-=2Qs0cmSFN>kDY8LS+@%Un;b7ISvdWV-iPWc0WpmvM@unLDCntR-cVrgL1 zRg^l`n)WLjs?EQDNxe32Lkrs! z03{5MixbS)FaM{&>^-BwTQB%-#?Zj|k_~@-zrl3xDb;oU9xTaArsa;G{BRp3c*6G$ z6Ir47dVIELxUUk&aPsFF@fsn`_Yj5gyFnvN;^9*yG78yvMM7FJ^-ATOKB^`1!JnA5 znAd~`jBCQ@NbkS@O+Xj2Nu(y@i=4ZEPtw0a_g9ie#r(I3uG2zGZNQg^F3MohZtf}W zft*BC842M3i0B4<<)a4r;C8m`V@@vN^@G1^x&ehOm|3e$6T{u@oLdUJ9KzbM)S^0(2~f2>u>$=%B2Ul|&2DTktf z7`&sDS7f3vh~rj43kIk_mnt)qYC~O2CnNKJ{~k8hpUqaH;gYeXBH`<3=(WirU>6dT4Q09G$|gUDA2I;}XJATEWy8v5 z<;>Ph)=aFa*wR{G&&JooEzJ$}i)V9t?54mgg7W>R(yfs(X zyx(KuLIu9EgoR?+oaUmk`En-Z+{GK7xtRxf*7r;E7)?8HD5bw2YLu@WWsS`5p9MNV z+Se=Tc}>>w&i;%$Z?`;_{>Jf3E53MMcXAqO3~{J$?`~J1OQCi$)!~$62MwqG%pBXN zz08@bviuX>E3(j3=%@0FwAOYPhzb0P*m{@r3bS?U80`wHb%Jc-3ON~$hUsXPXE5B4 zM+pevR|GR{v+x8pgAjv0`j$q{d6fb`0s;`_TP%obHi z84CL@2lGITgUCY`w@WBo1QsZ{0m+Kfr5DM9L0Ltlye?S!he8+f;zVJP_zoXs4H&@GHe-ZNI+w-j}9VCni{5EAtgU=j@_pkj@vZ_Je`Ale35aCS+~ zbiJ(07eF?(+pu;QDE%koe?6xm3vjKHzAWk=vebVwAmqQ9_E&=nie`lF7eEy`v7Dq* zcZ(b}g^{wXgHji1)EywVQfH5U7wb$9g1C|2N3N}_!%W@hHPyWyLFt8N4pJ7Rl!kIN zE+rKQCs`QP}JyWLDp^4 zb{2MZVC?7mGK}jshYtSJz^IxWRG`8n$973ztUfXh-(MF9vvE|L{I%mpeVNnWB&=m@ zT}-|jy#G@8qo^f?BZ&GzmR^BF&!{=qjBVLPa=wmERr)aW*q8q-pYcTM0ZW|mHNAuc z`h&7h^rp}3Ry--Y!HTjGl?!3#DQh5&v&H@6_6BknWLr&ssDeq?s16oFDjz#PS+onO ziRiqlR7Jrue>q~6BEL)?sxfcj{yw$RpG%P<>>#pwwGvOy4P*w6XTiB=?MKE{w-Di^ z|3q+b4_)%lJ>Q?k>axSA0D5yJ)Qee9{+G!aZu#BLsQh_+|}C0N0VGshpX%oI5;o zs>ub|1+G>ndiFN(fz;!#I{J~$EvqeIRxQ_2QcNU#nOF=u5(?-Pzzs9R`f;PUtjOvOw0Huy8p)BnL2oX22`+FEs=6EvKO@R#lE>UxFL}O=6DD{0n2rN z?~DL~U2&`ksECMx4p*)=_nM~zDE%NPh!afvNUUPcHM}kBOp{<5;nv8+X|N&{dsdRk zsqjWd)g{9U&2ou#nF~X_Y|P{7E7qnU3+ZbNA17r@sd$!wO6heG<*-RnPfE(mVIO-! zT0olez8#;ps|P*D3vc zJjPoPeqRC;o)Q+qIDMj^2zanO9SL!-yA1_s4#5PB4T8>b-GUN^Z3&S2TigwF7pu(?5F7+X%K|b3+5XNzaW^X?AUUhWzAaT7^V_uu zk9p3^$evCq7%y(EkL?-1I01#XF}aS|wUm z&9(`hz#LsV9DzAjo&3Shc3{PiX$vGJ8=|*JJAgKO|0uUpVkr+W)_+ z?ymd?Cp(cp3o927zkGGP|KzK`e+y*#zdkx`f4JBGtL)BT8IXSgVIlS3?ztAozqscF z|It0~+5hIR^F{B#ug#j`Yk9T~X7naTRscIk6M9EiCwe1iM<-irdU;zrXS;ta>YufC zLGfR;b_|#!pFw@b(rLEObdagv@QZIlZ-qEw^;sDAcC8~#smpNk!1z+2EcW{lc0-t4 z5)#5$0QV1P*Y211w|^etcae`^6;~NzhfTrJn_;bpYmCH4vZKBH8z0h2Gf@(`_C_1Z zCf4NTwWNzKqk~`xE<5Zv@Lf`4;Y9W7e8(*Pzb40aE|bot8{{Axdy$_yuBXoml*{)- zl~i6UQ2t5{rFE?8J<8Mfa}_Qkcyz{1Ww0|h1WWtD#bh7pADthDl9;$+0;NI+Iq8Bp ztB}AgBrm8Bml>(lS;tHVTBd%sJL)3&hyJ7MWmI_!tA zyt<)~-2=UVJ|tC#bS=Ib?@BpB6J7ZE>%#Y0K^kg{tn~1}b?|ygODf8mtGZ(EM_5H4 z(XUykMCwE9nQK|hre5Go!c2dFFEb>+m&nRQEw|?r^_1s6gZ*0p*fbJl%IOzG|8PzJ zM-cs=8oVH-4V#6p3w=T%E1M0r&Czmkkxaw^x_19%9wd~gSLh<-qGTtInTf5*uh9+} z(hq1#)G!D>-x#4uGCz!n!R2UE*W=7A=Jfa9uOo8bW^UJmqdMSmHyi9Bp73~jg%7GW zRs|MPzYZ@qV}lK$iGDf*4RXaMc&H6>cO(cR$s_qN0iE;j?ebioxSpdS7y~F}4__S+ zacPUTP#xR*50BzPRT&7NeNlJP81Cyc;)Vh@8@5TDE=?7?o^3yhP4$V z8i~h}ruKTLBn0ZKjtV127<8(UCvD~fEw$JC-{iD;MD_P(hnStTZ4^iPVKC@#!%6Am zXV2}URx@gb5TS~{ThX@1)GA#~7U2&1pG_mj z6^rvOB$k`~if1AZoB~7aAKnD*hqi4mJ1pNpBQh4%?HI(u*kg!`gO=4`nUDiRS(%qu zn)*~_$jDGEnXsx15#%D2kAa{?Vv4neSrzn(jtIx20VQ=f!Zk_GZ?<`+nAm*

H!|%h0is1F% z;F2M{PfWiMUd$BFK*R{f16MV9?**6 zr+U@amz~Oy|08A^9>gDQXvItDk>uu0ek|9E&WYSgW0bqL?n)mRfP#jLT4#`VsE=ae zy~ELUbO(rNk zPC}+Lho(RzxmkIKT<$rHAp%1{Y~Lbe!k!7kgUPXXdS%#! zxM-|NLsG>7HW4&W;J+R@wsV3YHD53y{ZEaa<)1K4$!-WBegp@$y5QPr<@O|^0?T9u zP&cuc>k`42=H@FN^;+ADh4rl}xB%U+JN^n1fA*%tvN7Q^8^Bhi_nw-ufwb)6x)0-lZJYMg}T=E-hL%9z1t4KpZ{AZ~U>{8(upLHN`~V zzy3}$ozNF6YT+{oXm7JFC~drSEh1s{$B9`K85&Mkieadz3TBX!hMf2W8=aZ8dI^&a zzp1zkkR_18qK62C$o*bhH?gQlw>ScCPy>55*pqs+EoNL7GuPHp+->~uEq8xDmoRdpjqORhXR7PE47{%ez!`uYw2%<0vV1DI~~{WS6$b1 z*WG{KzY2YUR|DqpnR#h_kKjX+%o!Uxaz&68HU5qRC7&=sj!#A}is+Y#c*u5QfDv zbx0@8K3-#%&Nit-BAdA##?tlB9fom>TWgTkHN0a=P&4MZDGyvB>7>gAJ+Nn8Lx#Z$ zwt#GnEX9t1Qf@vU>-NQt20#hJVFsA$%SSwv6(gCO^{N7yNi$3Pg_HVKHOg@m=|xz+ zgS=;H4n{(U<_bKrX)Cyam| z+9|{3Sh0r~6^@?4EYA~?Aa50nj0I8YSqGnY8&e=H^^5ru9tLI13MyTeAi8@siEGLQ z$_4?~$#>Gv+K@H55eVKv&9lv@g~Y&d{tm>(SdDl8&>#+3td(dtJYQayoyTIUI6VM-G(jdK2;)L+KKKr7GJ%G?;U?aP&Lbd83mOaot8|TkJBtDW1zxi@dgF@L z8S-=ux}CcV;^{^{Dg({eAH*$aqskB-0gc~}4~B4n1SYeqO!bVZg{Iw44$U`YmZ%Dr za{vRD;{Hc!I8(_kD6QtE$-j%EMjv*E;yHQDRl3{EA!!Vu@>x8yWM2LTrJAxeItL9` zB*lNgOIH&TpxOyI!DA`~2xCySAKWU~V$K2)~x()VJCmOO-BZeJedKG${Ba zt+^ul95AOP<{Cb3|2{vJD|#(=`c;DMBCMVfrhVF~vdfwGBM>X8J+GHb&u*=Im_o`5 zEUal(RPwxz^Xx@B;sqtM@7K#C`3@Tq*ju|HyvjP{K` zmGm6DvBp6*)sRiU{Bj+TM2rsWLcf4im8nQ(96oIIVS#>@2NN$%gum~4267wPN2ohI ztSwv^jiJ)iO`#I^o!c<95z|qVgv*M7V`1p=G*PQUto_V0y)W5WI4U;M)X6+}dEzKN zM_D*7>lX4lc+xIWuPe2j?6fNXzDes{WueSO3P~&53ColMfSrXMVmUW01x`wfuMqM` zpa(1{=O-mY_M4>qbZp_y%##MOGgLk&;$j~@$-4BWq>$+x#nKWt8DX`S z0JxPL2wkrzJs*QSdst;ZjhUlHUhgOc&k;*DpgP09p`!a2xOT#5_IFk#JJ+QsVXB-! zGhik;7|D{@1<^Z6=(bLVARg0uC5rE=*+PU3)QakW2GPl%O6tyAT$AwmL5K_LU?qWB z$SzO@UYGI#InKlrE=EH-80+qG zNOVGplc}o_svcUA@`VoOn|;vdBMRw9Kk_Q0@MQCNg2KIUh7}}mD$exQP$)}7S-8@U z!Y1ZYFP+`9)Mif!#L?u~)W3SGDZFbefmMt~f4k1bSA<|DAH4rCdc-_86@fg{h6P|* zO+Mb2k;%*4SUm1AIlS@ABVR7w*dSbsVw3VUsoj|}Ppm7c8*$e!!_Tx&H|ojQc+`Io zQTBfcoNPQ zV<(|LvqpYILd*1!v&j>uYYQSHH26&S*-fzXHHP1p=TZ?btJGoPOO-X%dbeFYG^cyo z>5Tm8<1Xw4jU%Qo-Z5BYCs&YgqH6~2$LKcAHMsgf;JB0mGeUbPVUBU}N!I00(A!9A z+kVHRx~$addJH{x-wJAc0DEZfR7A)M_@R47`r``6ytIHPX%ep&Xs`Ipi>0dgLLKx~ zEfxQM-YA;)4dSo9gufCNV%?V?Q3m<#+gEY%f4bu-DgW1bVzOlEimieQzRN4{olrtJ zy%D>nO4a%TtVy?gnF^M89+sJSQs4rPGE-5JNy*>`%KNVEUBbFWm)E^`+soC!?61Z# zqKFe-=GR}2$CKkepF3T0A{eJ5JVgM0aR|Y#B|I6G z_3uoun)4~jo6Q4)E2~~j`7Uq3gpR32K3mCrtL2Bu3WkU7E7DCV~l(g62H{d zerI7#UMH3F@H+xI0;W&K^9!0cN<+D@sv8OiG?HM|#$KQKt|MvPLN{wghNFfCl$Ltv*Lx}Y3&+D21tN;{0PGD$w67_D1_)yOvJMsBOq zXp0|spSqJ|J9dt?a!lUPf1{4_)e>imE+bUc;vNkzpV*0A zEU+QBCYgy1Cz_r=^1C(jC@--Z(V`$kmvG>0<&^Cm$mt%76FAms*}Qm`vaWPnID|@u zG*FzdOCnvbiLCef=n*CFh4XPcDLO$QR-_EerzZ?LrybwgrcYlV;zMc3G9+=JZBmA2axp={u z_>T@s;;#$f-!$DS8917Y**aMNcR)wsuN`}!6{wY$MBx+R8JcfMbK2p29>xsqOoy;XdG!5( zPsQ5i!3m$68n|NG)b!$qlNNHIVaj!(SFQNtvxC(jFdp#Im8NUm0U*jh9+B~)V8Bc0 zLFGEaYEac+uSRoTboV1hJAANjeOZ<;D6t169f_E6Y28Ejoa*YTapgv%jHf7HRd^Sq(>+?g3#&(=Cg-7{@-l@hwRT_f8BE<_#Z)?p}&1=L;G79k$f&pKvteds>XOwiK0~DU=vT$8>UI z$6)gAfr61%_sl9UHHEdJC~Wx4N*EQ28Pl32j-_zlbPI$3E}dJS_KlJLio$D4kh z_kPCtGovTz9TFyRYilUiTPrD3zvRe4lc#5|CWawi6S~@tAEl`hOy0GaH3|y#<}xHs z8q!eRHC+I5m&M5R_@M-i-Js|6Mt;*cXVS-a;FnpM+71#DAuCRsomazKI(0I2)mrVK zo5r*$F(vmK`(^LJLaQst0@hs_(_*d7Gq1m#L?cHwK;iwZCsP8tJoaj& ztl{!%w)EkcJeW_qw}tsC8h5Z-%d?&u+l5VcdCznnZ_O>IZO0^K*m=OZafmf)BNT>(N9IXyfZ~#m-TEn#sPBsG z&x-`*&m-x91&7AQ^I(av^TO-tXL(Z}Z&jEhns};tAsHJsyOfzEIx@sg^%)sX=IxDT zX|g6(&ekQ4AB4}?Tu;Nc5$q-VXWhqFo@{AX2^ZRJ5+#PVtD=ynZu~A6ZU`=?tw>HQ z7sryscsRFocv+|Obx?`#QpZqgBc@h!w>F#75WG4qu0C`*Sh2xSlRzkV(MKL!HY0eT z^ABiS8!hLd_Nl+`F}1qG;dl!Qa2gN4!JlL9A8UB(jfB{%k09I#wS;o(eWmBna77Xux20m~?#I8o*)Cn8&NZ zN@#dP4>-}~{0HVAHzpG%Gs=36@FN<(I@&5ibY_KGoHeW9ctI%=$Hdh)4hapBN=1XZ zA2VFwiwh{`AW7!rU4KF^4^e1Me-B#KlRG55lU0xdC-B;*{tA;X)d|!x&+28v-l)Y} zVj)8=av_(<&TT>C2ZImkcRu+prVqmjEiW{_AP-X;A_4?&!;-9wx_gMAcKRiZ8!15G z$E|$Qv^T53(FHtUH!BVVbKZ$YqlLI`j}Bw*nQF3xkE|l0&=f*yJk&wV;(rcoF)a@Z z+)=6n<(%1EY3^1N{4_^6tDi|k4`aoG2-EdYUg)oHT@Z}=%vz?h(o74F%Vp9HNVB8~ zx-ieFtUc#h#LGh*VgS}NO9hW6LU*V^yG^z$4HXrA9>8apun1Sj<)ejzGS!(-4uqW( z#^q~f^13e>f2|!j){)yIR?s~Qh^5XtBonAeQcAWjsH{(r4LFEIq;s|gQiP3PLOJXs zaf2OCfijmooea1<_+6&eM8HEd>I9lO5f*$(esSxe%h$V+2bV7*X-43( zzA7mX(bpy1l<~=!`;H=$&zaU*`==YJIXBR6SQ)N>IaQTM3%SOnE42u7bLZ#FO+XwG zwidN{LY`_#{W@uk&Te4q!Zk-kZOJ22>w7wGR$wu_FexcIiv$3{l9g0dP0?KKmHJTq ziTkuT$m^#ZkU@{w0U4)Fyiq*4`cmNem0 zwr$(CZQHi(Q+CxU+qP}nwr%4Srtb9ZiLdW`aie=8cElU|{hMp&%FG8@#HNdsU8vBz zWKi_`*wGrtH?(G6DdUe6k`sF3wkb=IqhZ_UHB{+i2Sr^>-Z6JF;)b@6;?fPRAxdC# zVbi$Y5T}ZgOZkVZAIe251xnFb2X{6>PXd@jiUBMrWMY#_Wv-5i?!EdXH zQ?Zf)Jef%YdC!cc#c!-nbBN8K=T!NTu4vZ@M~H=B-5XhEu}g&~1_k+7-P1XE2}hvJ z_qOQww(cO;C>_GmBf}?ur53EvK9$2$nPnaZsd3|6Tvs665 zNh$!t@PkrFc^7urs(tKgCaLSy*Sr7~Um(>_auxFG&T$tPRGYEx7_2;Mu6T6_ z4qcO#exJbE2+Ho4DBBLeQY66};gADI8t`!&Ln61jr=d|zDX_HLi)0A4r&81VOaa8Y_sAnY`Kl6!%qd&L2Mo9y3if;@h za2l{~ch0XI+5;*NfS8jb@IWkkJ;RT3Xk|K_-!EwxPLdumMs6}f>rj8u0g?9!qZANM zbmf7I7R~FkIU^-w!PCI=etS2`Fck8)T8Q@$R2A7Qvz}wL#9$Z%pB)(mbED8rLGH_X z57s~CtEAvj_rSIHqwPY*ii%coD^H$})&%pfmgs)|p@6+_HLI$Ot^tkJXfKM#WukgT zq>ObnMe8s|(+k(Ri&4(`>%EDAgKt_{!jPt{L?*9{F_<9Kf4EE180N(s9jwY zFKid+k-s{1=Z$eM+UHo%D<=bYidZKOP4p*y>nt>tX%3ZY^NP0GylEl81gdTQKrSL;I4lh{;r zQwegq^U;?MJ9G19wFmRWyeRJ_oY;6}CXD8Ii-`IwkMB$)-N(O-=k(UABWDM=3m9=m zL)%|`GCBF;IUxFt7M?*E>yOmN6blyqg~yIhK(#Ze)SjgjTe%nO23^^Pq#~vx6t=~q ztdvYSf1E^RN&c=V{BMJHquGWYrBTkkf;biR`%O8Gk%pR=LYz{=-_nL<`DOUZu& zW*b&J2TW5OizP7n{)=3FV<=C!=Lf5pg!#9G$^UF{qzydmT%7+Cu-J0?ndp62xfXS- zcg4%xa%vbF(d)UE<_{7}5t1?MhiXy)V=90bljsr~(8Q65#T#YLkg~u?yx+~I9QvD* zvn_p;MgPV_sT&Sb7#G&j8!Ov#{FdYNtsbHw@{wr^~6D zS&gmXv!&IjBoGa%Rf9Ht*mLsmuJ*HP+?7BHG^cZG!%8+8z|=`k&2)Rt=J4+4P#z~C z)W}Z)lagx#cK1@iET8R5H_`8#K>>HW(A+9kDbm8i#U$W~_*HW&Ez}VPAvQ9G+W46}bunI{{B*%wmC#plLkwg}t zluAV;my$kU35P7lY~CD*Qs_CBTBIks<1{6it_O0eynq2lwA-h-m)y7M_6SXJV<5PL z>Lk14^`&5lb?W?(6q>PQFeLYfBc47G3Xi?58?50F+_*Ma_cwjj3UCm+!wH#snT!ef zLVB5O3z-c^V|LEI(|bt}>0wUec0QNpmHPErQG7t+Nj6PL@K_boiyP;e5k`t`V!F}$ zy8DTaf-7Fq7Q654SBiKj#nOYCC{^AyZ*y)P3<}!bBRYS4Du&tA0PM;J4)&?(-&_3A zAfpvVA_^XT>hx)Tiiyr$%V=H5z?cC?3Wdxcy^mQzo8kbfDY7~vo|KBhF%3|MrniW|XQs!XtQc;%r-jTvK zBsfBQ0K>Z9^?hRiFVL5HmVeY)Q>;VO>ov}5 zmX-~}bssGHWL5#((Es@+OOGfKvctd#z>_lHOYlXS&GA#|@hRx*w|Fd>r^n6HWDPv; zHTXzp$Y{>iE#P1~JZ~WVIlVJoxmWV_T00qz#6FsWYktnI1l#^-UiKfM^(p1aXdQvY zIy!=D@fpQLGp+!*<&+=SzmY%j;7`7C{hFi(aY1Fm8vU6mnm%`KL9c3DBn2Ht?JIlE z4KQ=APhNmBxg@nGXF*tezt{<43VUcfJ(;&{}X2%!( z?FpeZgxgi6g*{xXI|A-G2qq*2Jw3k{Y+oUl^?H7XzRnhrXYps*8|1?(>BNg5M@+@6 z;W%A7`NwB*%CTZ{loZf2?4|t}WDHu?1ON)pU6V#Tv!+ZRYrf@KTZzwV1~i2E3WMOH zh+C$U-lp~*U?zps9?V!o=KAq!;=TcG3M=ZLg6^};)xbW(1lb8eUNSajP(KVN-^|t- zC5Bb4!&sjpDI3Z4TP5pfKEq!T97LLaUJWZnvmLZ3`{DflqO0yfLxOUrH)$P4;WpL| zI1IGqMWebhh&wP(6mieZFd<^tMjK*LeRwf0yXNS$vsiJoKqQW1#C?ogzx!) z*gEgDy`y+Pe+TruNHpkngfKYh%&q)-a13xN6ZAvyTKZypECSZMsGePVNLej(ohj4? z7xd^YN=*RDV0s|6c(dzAQLFo^E|*GG{cKdiD88m+-G=Z1_=Nn+2a%O4ULE68)f*&K zd!B8Oaz>v7`?Cms6mn9&yHL1*Ve-`WTyCQ-1#>_(nm}hPnjjJiQ)l`6}N*BuBZqf4M(W&?)}!W^W=Rr#K4No9PQ!xKm5{V z8f$Cqc4rKYL7>`*-bekQN#{=qU}B-R|I1{cU~j|_?!@(V`~K-?_@0WKcpxux z#ApE{Ti(pmD#dRoCOT$}3vAG4SxXDDxagI(xK1IKnB(fV+lf+B_K!Kn$wLq;l&M6= zX-(NtzzAc9$rsGAHkWOs`a8kV^>^vCu(QMj6{w5d`u2Yn?(V~eDEJh#tTHUiq7j?VK_&BNfjw{&rQtU7Hvop_ ztK4HYvLTG{`G7p^D7c4U5F=jnq;gh)Dn0A5#F5tz5&AP=p;r}HbxW?r^)9{Z+#4sM2W=2Co zEDHu7g^k$3FL%Pn9CDnF%*m7s9})G{OmSyeAxwTxjR|0&6+}qW@;JhTV;*kf!KUo* za*Px4iB>4n2oIB*YJ(gI;g87|{?gX}zwz)AypSW#qB;HwA$esW@aYw3pageoFr?fwa$^YJzofLDM*Ga9kwT(j*jY>UWqsuKSOBg-ZVxl2{xw( z#mk#Vuq)s1st$+LerO%iLSoIE_e7pDf?bFLtBK{i5Yi$`LE{bj8}79_5X&$@&``6J z>(2?`x?(0#Q*)>fL*~6Ib^E&R=SHc}cC4JFxPi;k#F%uH2Q4ciZAv*%d6Vpi_<1`} z0MQmbsu6_7vBr-9*Q{VcT=-!<*jUlHkR23VeDtu^|2^&=s#iY1Q{LxolW^L88>W!l zBu51Y!e5cWIs7_1(D*&DCJ;mFo@07BevDLlAJ7duFM!^Ei#GhXADIm=&H)N;)Ri(e zcarIyOQ&?g3e=d%GH8IB%?P2Pj1up--SZXrzoxwGRxSR(pK!wexDx)4QrriF+1?uS;hLTW_rh?!050m1HPrrn75WarA=*Oyd?H356riF_$6P zJ_aAXSLkpWrxj-Gg({31)|McV0g2`cv#IAIeEI?Lku!#<<_A0AZ|E~yDbrL-KUF{3 z_Ci?dOjua$YZmZfN$3IJEvs(io1=2Ej|diYa*mx@Q5LNx;*I+p6{(FCGlcm@9ZdCd zB@O@|UZG2dq<3yTHhfAQqKSwrHf*O@1I8>1C`*tdF6tQ$xy(fqsr8P0MKo9xrfq+A za0QqI)KT$yEIcq9Zgs-E<_un7f_5+l;pIqTn1pI-P1kpH>c1FyLZy*=h;P$|un5I? zv#Gp->rIwr;U^IGf#1n)M*#>8_PL?YTczcIx(do>DHOq-wD(%a9blpk-YGb~8G}a$eL-oq z@a2MdO$&gFpTkP|%%OY!Md*l3C0>#FlcN-WND}{}1I_Z!hF0mH(&_R#2FuQ!n}L*xX|V#dcTR)#zZ}75iB8fJ5eQQW~lY*Y=0X5gbInc zWq7-W_LU1G)T9W?Nz!6mV%6-m<)Nu;B}`ylQ-y4PvtUD%u`9^8ZEa8du~R$N;y!CG ztTH#4kI|hHUYYgMS9gN+6D7m{OjjdR=B!%c>*Fg!@+<=)fKWMUYVCtVTFz8Do77?- zVRW(PLub@)uEI;e>sbqa>pBLABMz@x7QBY;pC>?8(_55COES7qKO6ds z4Cs#GMQmMw-NUuQu#Z^38;Q5v?|IZ%t1%gdC3*$1GdgtG%;(~NByuX_djt$KHlEZ&o57!+-WA0!&v86? zGW-BpZs&8#IlppPnRAB=w2&k|e_<#X4bBnet_t4F##8Y0i|1fsDThW^cMs~T1$7cC zrwN48gpcwHMXq;4mKqwDuqLL<-Zq{(@H7eZmiJ<_UE_(PdrE4psJ00GZlyq20!DTz zS{&VmbEYiZ++B-S%Lb~M7%VQX1H%~~xvW>N{{IvKq8(C}O#$F$6d^cRUT`^KN zd;glM&`6UvqI77O7l)$G8fp-kljQ=oK8Z}nYzozlerf=^HVU!o%_zxuB)T1i!NfEi z(^4@I?o-woW44_*W_Yd|1$95BV&)~WO8fasC4XgJ)XBeanS0S@JQI#>9HrS7TX4oxy)) z-v!b)sN{@=;*|kb;k5YTY`sl~-@bc2MQ<$KFMqehU9@lJ_OWPG*M(wI2GUk=rAS=6 zO8;+>gm%I1$t%c_L?2z9SYOSp6+omJReUvX^ujY1>?Lcba9;y^?k>k5%15%(3)g1e z?#pXn03YMET-0BthL2!3Kaiw36Q>^R7@9No(OtV%t-c)x@jkv=Xr6^gKG)>0k^sNG z9mubN`EL_@xa&W+d>~5hx_um(CH>04^3TBjeSn+QxoAF+*8@5cyMzb=$eR(ndy&uy ze**^N!R{o_IUb^^Wx66`esj1p+6eZ>ueq~c4D(ClWCs#0i2FECvO+3dMR0IB4Zm|< zY~9Sl_^`?4hNQt5H~&hl8mtg4SPSoFoOQiRn>HK@a^y@sv&J~vyC8+8si^iXW?J{P z%vUvbRF}!Bw_QCgUHWP%lV~`YMhUyLIi)=&-?mV!6j;p;E2ZE;Xc5&*P3S6eF<7hyD5_(*lGjp@F-Q>`9H+0>g zlePQ||Kx~EFlW{n2rmgdP|L1K=C(C4_2(>nxw$BrLEyB$*;whxfWA_Asn|nj2;owG z0Ls(DsV6hYcry7zvEJ4VZA}X4Z`js7^fq3=Y5z^nxiKa-y-OVYwnR(4s4JEHjRdtLZlHToz9gYpsU+1+N_qJ|lL+`gb|!Jlpw z+qG?7)3SO|b*u^HPe#?JJV6C4%mIY%PzSNuT1sDGD6Z$MuOyUwkv^Fg%nMo+}{UU3R!6yfAayb8(fM48hs$G5 zaK_~LO}h{tgaWpxr5f@P79s9AU)Hgt%u5~w8nQx;F;eT>g=kwOKvB?6gb!WS4w{SzzSTjMDA#2ZjmUWdHu~P5gIdZE2yp3wh zXO1CgviyD#SD*p6#8g#t+$Rrg9l1tZg$2HCy_a1Oo{hSEkQfpu3%N3upt0=dB@0&>u(d|gsMb$y?( zVq`m~RXuQZKE>Uf3nMT-VYm5L>AnAR5l-Gp|bUFs}QU~~Q+V!!f zrW-cP;FZm;)SQoa8k_M=!^(YM!?OqQsoeUnC*HRik>*|(M9FlkolK4yGt$Y^p z+%roTNfSHwi*dPg)0C2qUq}cd zSQum#afz{3{qtEcvWJXCiMy6Wx|v|$L5DlCBn_X?18jBrg1Q~-pr+G7x(9(Tlilb6 z1C`T}`^T_!C+s>*i5Fm4ivxZHr=p{iKVdlGC2DcNuxG6E* zgfO*QJ@r;4^sO4uF-0~$Zx>2WwwFdnbWB?@V-GYfy$FV9^~_g*)Lj73h|>0$bsHmB zN=Zad(acalU(G3?)gcF>@_OuJ1S2EkCiSVHwIG26t{Jb^YL)pFjc+(Ai*zl&y4%W{ z!*A`RBL*8Z69 zGyfYn^k1iY{}t|f``MX^{oQ%IYTQtUB5sgZv{^WWIHXA;Vv~W2X#-$k{v(EvNj8>> zNw+rd``bOF6-k&+j$2AE6iCDr!?zdpB{FIM`gu)SG_SZm*7?%4{qpMFb7S+}_CAvH z>v|Xi!;SDf7z$3pU0tvyh!;cF`la6=htYK(LDzL`@YzcO!N`(L3CNm}{6OK-z*4;5 zsM0Glh0K`S0&jh285K$#DD+J)sc@Y^_TAr&;QFJvEsT zvK&LkNl|a)m{gqHu)5cuG5}WGgV_T_SP1th>l#;bHD-{(Kfik_ysSp9w^z6O%+)GG zjTPcW9M=NMOr`*6bQW=fWIOm>hH6W=+N;P&cef3VVIBYg#A+S*dojjHoB{N>Q!r@K zJ$oEdTeJ1;Xmg;??iv|1XN7_pn3zq8Z4hMNs6a;U zngWnA$JnHOA`=k1m2P=tDPH|a`0nqK8u*x8(#!PFAN1}vsQFdbRnjh!S}sqpSy|V! zGhQ;U0pG zw|RKWO^|rnIvrB;Ei5h|7n0x)09)kFz#cm-1~sT&*GAFT1c(fNY}e4|U@h~Qm+;OP z)OSbC4@~e+pKjw#Vr_|)fJIb+0@FE zrU>X*G(DxcluzZ3hOJK?1x;Ld=9KmVnC!l(V?xkzFLwElaEug?6D~@1S2P zhQ!x%DNaW(Fhy3(@UJkb0!w#()pMx~TW?`wxEwVx_pd3IRn2YU)~48mH~RKT%I-3F0FQuSdyml`Vsa0BE} zW(D3LbC-ucOYGA`D=xoIV15&S6gnipaFu=Gw5&A~-!&8t$G={b#|%`oUx;hiB|I&U zF(sDtem2JNw};relW^nj2@nJYxkBJP!wtG)W2&_t6^w&zc=n7X!n$zaD zOCaZ70k4(PCU;P`%_RR--##o{X3;8L3E2{+=8c#(UAiJSkf;zA{>^MzL57br6!6*6KG7Wqu%9PNz3_J$?E(QWXoxJS9<-e%QF*DMpa;)!N43f(jD&n^Jl*BZ*=-f&ObJ1|K)yAk#Iu#S<{kjT8II2Kmsa&Y}`vN8qXk! zuYiap)T?0)FGbH#H@08LEDkgum5*TnL}2Xj{F$nx$Pt9?_?r^GOn!(&h2t<@%NH62 zf2*JmG>uU;$Y1*L^P-iOXn1s%Hj_mJ7WL;M4y3&-cQH!QZXJZlXb9_#oZjXRL@-E&X93y*I z8i98}RK&>Lmc{&B0x@*AMZM|E5zk9ij(wG9uaJ6BKYN8%?f_k9^l-m9Vd|c++Lf_R z6R&%eg`9CvZIf=Et72~4ds=^#$W7(mTThvi!+fXO_WY5OJ#O4D*q*o^zKLgpjMoo@ z><==$md8An5^020e!|%P)61xeNTWur!qJs!ta@{cj=R39@bukv@|h$|FYc&|W9>eIRE-GeM*6{^x*1#6 z)KpI8tcpAd5%Lxh%~aHq#G*)fcnA{4OSZ>bYo{>gNDM5fT`?#rJ|A4+&-~qy zu)-+ZRl67jK>^eXyQr?)qHGt8*+WhM=dSw%`vh+-x%npy@T;Y;mZ0<#C^&@qaH9?$ z`;_bGn~N-sZ<<^OL{(cWoav)bE3Fb*BkH?+Z_b~9 zBHh3mmikB9sqMEnNpJbh0^Nb1%HaV53|0CoI=m&hgZdZ*nrJP#@!{DAxgi9c=Haiy z&OjBIVMp(rEo`-(_i zgD%_x2rI0APR~i_d)u*BK7m+4mU&sL$oeOK=nv-__6eGstgKsws4dk!xXv4k&KH*Y z51m*T&4<@+=Ury%rDB1Pcz!w0kI#VL{~vUAfJj`Dl1Xsm2;Up<^8mgbj?2lQR$7*mON3hd!iesfTHkOl_ma{$qrJ zd|?NO$;RH~2aecXsWJOR$9PO{#1T{q<_gI9cgs3eJoV@8x#fW1tIT*3W0F6cde?W3GQM&5r$U6HLSqK0S4qaQw3mFIBEW zk{OyTIhfR1%h9&R?nwNrJ1bCXjvCiHRq0(UllU9+IZ(b2l|d<0TK1^ToBMt{wJTJ& zZi8Zsz?>K^?JdwL;oukz(X)7MBeFL(7!I``Xi{O+sG^ZWY(e4CrmH3aUP;8S2 zKUyl(w1udktZsO=GCe6uw16aDX@`uSn7w9>p#kEY5v$+ZT#6WKDZ~-kf(5xzMNO5*NIojcX`%21#28~}c zyBr5CYUEVQ2STKs;8=o^oo&h{OOrKB-JURl7bg@>EDrku28N3tS-`y^4kwK>4{7W= z#fJzhVI@$^0|WmAcQ;QT{L3fnAQmhnfEKNM(|oH!`_rD#yNr!l-XR0@WhpP1$K z*M0L4mH!a_9?@Wj5>Q~a1oIM-?mb5KLDG2QSKN zVTE$Jnc>RSTiAY_oemd$fgOwziGmeX{Ud^=T{N4DGk{Bap8eSHNP zXjYfCm6y6{k57Krk%Do=Re?96hX{T!GDC)SqdVwvcdSF-X#W)*hqd|* zP5*yPBz=BvJI_0m^1kANjpu2FaIF zMZrbOeYdx5HiizW+cqLTu)5Lt*aVn;Z^2$Oq8yq-5w*ivF0g5L9 zing)Z>)t}nYBzs99bvUw$M*Qi~Uwv7VrP5XkC z7uE?wxrjouc4HMya1tSYPDV+{NLlF01h|kW&%(|?k@T%I95f5$B*!YU@}0%#V%cxr zbE?{ak>Q@}XViCq1%y$1oK=ivsjKv{#%dUJ#?7?urkF zNl3rJ3M5n^0L8DQg#SowfnIN092cJ_`#ntg z3t^d*DpWtT7WU62#uNA%K>VA16!hdJf+$;bJG%$)_645bHnK>3O#|L8wHz@me0(8H zXV6=BtZSYe$iI5WsB!9ai2q>N|1h!rH?Eui+r0Ll$BzD==ylxxc76)^?%y5#6~NX> zD<3YP9yMGv3_~ynd;oahn`x0-*lemP;6sH(7Y>Ej8_Jjspp6kIv!0pl``z~F?c3$| zi+#`(7513NxOO)$Oax7ecAQof-PBqx^_+J=R%=8jCsc70^ExB%X@JYpJ9_e@x2fK)+D$6Yse)hR9W?? zCV#zOUDhz9s;rH?UVa!<@vD?vqdnPK8P~7BEM}-cHORSNT`C+hnhfY&r&f{TET%X) zKPy;l8(YJqoeZ~T+Z~{yhu|s5SV4_kI`s%5p^7h%MK=zYh+=Z?Y<5#BNFR9~qiYxI zU($dilz`1oEJl8+o&RP${<+3BNlE7?4|2_6LvN_1)y*a=Pw1*-m?V`+MQ`E7E;F zkatru7taVHN?$u89CvfLP!z-F8a-7KKccs?CnLQe-btCFkx)osHS038F{>VvQL)S&< zcr``H7tD<7r?}@*y?jTvQ72@+hb^?n}i58%rA7<1z^ zaip6h2}v=zknW6wpeP}tb|h1|U&m@ix?DHzz*?ac%9q3tBR_VAt@5h2iJO&1vl zmpqHdMHOveFt`k(`aC#cGbv}#LOx`=($+4WQ$31sCs`Z9Ea3(`g}a_9!^}xRFCHN; z_cEMkC&AT-EYqmTh5BQqenA&XMA-?Va6{yRXbfe?jf&A6HP!n;)pNS6y3e&;5;+;n z{}y}RiDIuxr6h`IZN8`YVB8MYAQRS)I2g)&4pZ1m9T4Zkx1n>@E=a|6c7}+xg zYNcI0Ox}UeT@ML>zIfb-x_fe$ zO8Q;}RL|&Vt>KRTZE3`avbR*P#?RRFB$JB1gDd00iV5j#k$>BILQlzD>T(pVeq+NE zvSY#2F{`bjHfU5YVZ0FX3h*JFl)p#6>-T(`Ogi8fZ^JCcOj_`gR?G2J0KaqRZ+Ak2 z$1Ki+y6bp#x+`Ld+?4QCTxrv10tJM;m|s>CNmdi*=Y7GE^|8pQ#zE{%6B>dO_L_!+BxyO zW%6G4;o7WO&Cfs+OySO~IpdF2%ut_e#5l?WFSkB07xrmC7r;hX$*DA^@9z>8>tQCq zlS~5GtSrFhr4-9o0^hfZSB#nF{S!=Ie>CQg_>`Xh z8kUa`v!08U|z6akloCCm&M za!}HkR+2LzF6pyl$_&B&w8isL0z41shUuim4bPyU2B1vnlg9Ls64M5=eUrpf#$t@T{2YGq)R-koyPXZy?*45V zBTwOXdj-?XPSi^*WyDER-8LGOe1BqYim|shMM#`oyv;gkWVDSnVkkxQ8Cx2axwG1r zD>d--?e=Yj{fTS$yId^ZWV5dW4{S}T{*B(xcJk{VgP zFkf$!z!koZS(i^I-WFOfSIF=|)EWs&hHkQpg#(9<=~1FA%vzK7$2^K}AVWuLf(T0D zdOfa|xg_+XVNo4632G@}Jd$^nXc+78uXp1rS4+3t0yul7w8PYP6kD}h?1xXQyH zn8M0x>@=pbJW$-JnZV>$T+h|IBp*BqS%F=?N!{dVo3!Jv)H*9$ys9$_tBlCPA4=fG zTcn4dIDkOS!O^ zV$mr6KoCZ-DkV>;oKd07G_FiQ8i5_a2rNcin7ms)tE*6yC6(xk2_u#3Wm0$q&rO^x zW(T1ed<3JC96h+3>%h=ZX^jzJr$Ti%d$JYVRyc#F;yl!hXS)t1oP;U};=-6;Z5q^@ z1PoM*GvbY+R(6UaWfY(FhIP)TS+vQpiv=PU-B3ao6N^@ z9>$DRuj>ZS>^uTroJ5}`Ud&gIwg)bE2_*$So+eBsq4_t`+>E2eAdXw6P^BGzl&GzD z=J}xxR-aO?0P^8)8o!!(hbu1J<-n@oN!<{5Ly>2q)~FF=^n@5rQJr_9#XWgtx&KUp zvZoX{+MImDpkt*aogjhBUV8hGdFj9A#AaAkDyp&8a)hRQ*5 zoI@sn%sl%991}HG6Y3Ce5Qa#rrg|uP2!}gs z8e-7a6QuJ6_CRR?8_K(^2C9F3Mruco)+XlnQVJnS}oD;VhIHkc}#)u?6-^gDnTXx zFIXbP2aIl1oq|I;fs!+VQ57o)7~#v!ScxEF5GWJL6*L91@byzJHVN?LR=frEFhd0l z-g5kP#t8H3FfO`dHt!HW@Dn8+6$tl~I5c$HEv8e1kQNd2Zh}rrqsbMcR=fdgR^@o6 zSq$-OP}WTyVjbIJzo%T%2%-nVkcyzvJzwMpqL7SKNo1VtQxfL!aD{$C4hwPv2O19A z5HsK_dsQc$*&4y9T#W8Hasd&CrU)Bm`N&~mIdVu{DV7VHh+qH*K? z&cV894rs9fc-kmV7_g4Be#^vs+O4BgzfqzCVTAj0-I!T~0S9n~EiYgx>oAL@B`fQ& zcjp`dS|1y`MY;LT&1S$5qjuas;ixd@0+irBoDYp#ASu^W{ds>21cDnEF=A?Q>CcHx z#NiN3eB7x`O2K8|FkGI4yM&Pzg}~rv1Lq_>!-C`@LZDlsl5w?l(t=gtJShOtu&%u- zWViZdZ30Dc8@1h%f;1+YQu1s+LLvYNban^^sZu9eVe?uAiRgE6#AsP;YE(~P?14tvi$krdN-VQ?9V|*)GyN)lS!u@1Hrw?7Ky7#ilnBKev! zVQFO26irNxUO1^miITDuN4czy?UB6{97rp{|NcXWPtrXD9a|h5AN{rE0d|a(HMkU& zI;lnLIE$NaXq0UvsQ1d+IzT<8hGe7Bk}N|+b*cz0wuGuvv|YwkhI`c>DcB$_kqoqJ zIj19}zD*k%X>?t?mpoJgpLHl%&+bL6-8Dnho^TE4`#nhRzITYwIUR#!TYxK#m-n)n z`lH%Gx9MkvR)IE3x5o?K;EVEXx6`3?L5JGmmvZCB8&*z#wf4<-Y^OsUIdw!FRf!}J z8tO$`4-scN!C7iysjLm5wQ41{h)&TIa=X7&%AvhBdIUuoRkLfNIxE}HuTwGYbjho> zCd;XgADghCN_1Qc|txg@F;*YaGC6@jPU_KgM6uY*X<69PZoB;gB_3V>`YW=~g!F_rRVomW4J#-J%y` za6zwfoOg%bLS6yno2&)mx|4Z}Kzy9w|N8tFz_eF^#LeWVMP~mY*!(wJveIR#zQt(zwqTu1^J#-rq81>hzSrif9(a!>3fE=C;HPbZl8Bef4;%AUS& zp1j8pI6*%`f3q7NSOS|3;k?FUGw%e5l%ddcNy#^hQlR~?)b2aAdK%eb1P7$@Uf8{c zBB{5O*K7rtkVbVV=Z6}0QV@@L$~1nz8PcTErPVMFUa7Zu>#c`T;O3HhmDe%n4W*3L zWS`rQbeCOT4xGKCj;h|#uA5g_wX$e4MnaigC&MsOvzAc05lb0LW?)C~7&v-b+gWs} ztL%8Iy$^y)0!mNL5G3M%CpGUUTkuuleCUo}X|Xbyb~JqiT%d8HyeW-<>wP zA1fNza_>od8&~w)E%Y`&Ty~5JX37Mo#zyEl?AWC8XFKp1LSNIp&Eq*$VX0cbOeZ@_ zTNLn@`T@WN2C>Gl4*TG-l!SUR>e!WD_xC&*wQ7731m=xHC-qWMUX$V%gWd)dNbaz= z8je^ZLL~s@(hR3_anQy8k0h=#HEE{t?H+VHuTGz=M6hy~Y3>L>{|FPLT_ z6lZ2-f{2NUIqrBUCCZ)GE>VanzQa%73M7&4{!T82&0$E&*?gYT>-n?o_xyWuM`Jn< zvK}L#qXEYbX4x<~NPY8q7&L-;eey&|{4$0ci*L{pwkRCi&TMh4JX(pPNCL~wYje56DIFPLe?ZrQRN}zCjA$aeFM- zzhHd>Nm*?IP4M2x<0LiR?JP-Z5T4GNSSC~JIb?S^<)Ly0sRI}2aBfojR6mO-;cXtMNv#19zO#GN+;6`(-{*kI5G&*Era0 zaL6JoX-Z0Tk}Iw5u4@16Y|W!+K+@CG8=dArle2Dnp3lx@wsc>f)ia6ei}4hpVBFo# zEfxC$G z2zK04!+k&PI8Tw{_pO@Ec6l4B1^i%TgT+R&N> zhuC8dy%Ntvtvt%Q&cJcH`KX4Om@Wq-yh^ryyq8*$J#O0UpBWr)^rZ2LSB2PwV1M{_ zR9AH5FZPt*_Y(e$KvjXbLZurCfSzXh^C=l(+`6g&Lkmw4dQc`i*8%Kwk_dS{Y@=)zZI@h0>7V$uEKeE#_$xIE}Bz>-vVQTbcoo% z34<*W1x5=w(NEq$DknyC@+~!%p_ev#oel)KZN@L=Y4YY~~pNy9*N39dsT z!7!X@(V(vo4@UndN4PL1@*$%?&d6nKlmYQbG?IU)7IV3USbcszIfNGSI5m|91o6Bg zPTn%~BBUQVzXxLKfX2ne?HFYP5tgxJRqKMDKu6R|<|UKV)Zp?~Uk& zj<*$PI4zD)#6B!IlG{TTt~~|lj-}MBZ-^kroe1M)JIExa4dWfDjAc{BhWX;OF!l}_ zI=t~atQ1#$iMvkx)rx?h;alwfNWb`DJHlwrO|DD5eJ>D*RVb!1I#kvCbN$-s5@xKA z9UDb>$xp?%4%023n0|5Mr!sV|cY+?WeNHjN5?{fe%f`_4QV6MSB!5l{T81K z?)gU@*L?PZ$=#L??j+M0y=LR6VF8)_DQ4^i<2~btI3PFHa3roo2m#Jt=R>&acl1OB z#%Le}h47?hL<3Fc9kwxO-;_%qbmpnC(GP9<0Ej<9HhP2i9R>1K+IpH^Hbg@LSSH3q z2)Io5#zYidl1DD?Wp8u&%DL9$gjZvZ(UvG80*Pi7uH3k!OPBX#6aczQX#vGIIMNSQ z($VK>zDOR=)vmM2ceKej`2YP=7KG*)`hRTzoBsI*kp15qKxMrjpkM8DTS?VrA=O#- z7j-k79OZ{734COTC(-uGRdKHQg|vn5VsFxyMbN(ptR7XjvnUjqW4S!Xn;p-a|Kt7w zTO~!tm>3jli(-<|%$N#bb;dBKo7BpnB$L%U+k+@@#ACOKyGiL@2F!U3-`Y1r*X6J* zC<6pApUuO8UCasQ@%8F&kUhRaSmj|oM8)U!yR=S@v>+m*&rR3G2B_jZ_@d!l-bkJ_ zi_GP@P%4Oiu(0eH4W^`#DY34m^d_P8^SBd_yag@YDbiVKO$IgEqZ?HDl2o6WZcZ-Q zhJ@gOmg=chEwdrA1Hawy4{zbH3B`p-g>bJy9J~zIQ#$6QWeTwP-@_oVPhx9}lVZs$N5-+?DQuyCd zd~@aVAO4Eq?pJB~XXgX|5w1{`Qu$Y9;RZ7n1~p3Lqr)KwV}-hE9HkInBZC_6ch0Z^ z^f4xnyK3@!-3d4@BwJK|qeuMw_BTl_t&Syw3U9CY%lWph_xbIdqwaoxrS1A#SUp}} zD$$vfnh?uVl?>K1~OovtH-1o35%u(;@ z)Mu?k`uke#Nw@wGgJmN@*#%oxBC+iSeEs2%x{LbF{ zF6Ab-Bn`bq-m)<{nC-cz8B>g;ZW;=!P1nXFo{_X;4m;wU7YTEw@}S0=3Kfm6*9?Ki z`#;qcO)EI^#P&)u(dpRM^_o4(x#U@i#8gxGiMiXD+#)V?Tt2rfiUWnw3=W`rJrg&f z1C=e`a4Kd_;{Kf2iQ(3?CNGKKGvnCo`BB~s{ZUZF_zUtEd2s+qXaxHf_5A^qe=r`% zBlb$1nBo-^rQQ8EB8H>GHNq<%8Sja=fZb%UiN-AZ9flu=OEmt@bDPGYoPC|t;iHT! zq!gJMQ1wemr&ulT$ifWR|3ZBhcX!4O9ZXgC9?8*DNt9b0jpwg@GR)VwxTE0rN%Znx zC9UGxiw@`Oj;8Tz@cL)T@E_F>)ivdR0R*{lgCP(gQ9s*3tf8#Y&^ScbLZ~88zy@0~ zt&#(c!m_Nu-obl+a`@e4(!`{hy?+1vN`^KTIvPYKH7>tD4;_4d3O@w={XP)#an0c@ z_ACdr;48qgW)r`OaLdnP4{&YetxSUBo3ZN3?xJ+MF4h2@K{RHIfR$JJ3yyjgo3oB8 zA`T#JvzP;ySZ^j+hUMO*Iqnk(Y4{D(j@Fa+_t;k4PtHUv{gva@>+1TIL@a31_ROl4 zLHNxErj{F&>>414^k&Z1TZzjlx+>b+HeIb}CRkD*;XN16Jf+w%LNiHlN&!RUvR0Mc zWg#7$3~6P9-6AF}17Dk;z{{hN5*km85u&j2o<`bT&L|T+iCKUXUJc$r`vg;vQdsa` zievLq&c^l;CqfgveXK*zpOIJu&b;%J#pcdfv4J7+OjArnM(PU+tdl_M5f>r$y z7M}6K3rJF@tRYd|^ZA}YtWsrji-Lw~?MI)i#5YI>w9*R9w{Itha$Mb|k~rqYilILpjImBb|?D<%BS?WFHKt*^r zTh&)4pW-U}sDqZ~;( zcS5+u*lZ8m+b;;-LO8Dzfu__TM1pT$&^X=a(+$f5$!-`P<>@d=rd_RLq^bty&;V#( zKZ6a+R1!nfP`ZO!x_tib~@73pi27RDvsI`j*2{A3HJ7n6YT%faM^KD z5kvcAm%>eAu%m-YQ7o93&LR;K5~5z3SC3#<&0ZHyA>8n38v|Va&h5gb`o!*eoZ`sK z3><&i4UYemnP$vM-XNsf57`{^nD+O1y2x_~c>g$y7x=FGbIh1Ge4mO%_CRTHjTel6 z8aZd8FbP924EGqD-{>U;C`^EPi|zA^MR$CkxySG&DO-bcgls@9j5@3itH(TJdLPHU zF=J{ko30l2fls-=+~GnzKMyKqQo^aJ@OPiBrd~t5-eJRS_WIhmEsC>P{z^*>KHm?= zw;IDbJH2N8iZ05)r}T%?{TnG%91e2F-wz14ddGwY7^9P-Js6TBmkITQi~XG{+XGqa zn#xt*AK@oM^^vqt*=^QaNDTHMDpd_u&?UdHfJh@>BBmgwNQc|pL0)SRk-r?CQhO8_ zWIYmcdyrvbi%mpt5_B@vj4rS(boMf=H_3xDa2^>w6W^a54TO!<0Q=Z}5U~w(%Vb%W ziSv5teBMfe>H~@+EP;*cI)MPtM@cmhbaqyhbLZAB5@j@Pvh6zOmmk{k^R^S*?!$i^8bm@?KFL=;%k`z# z4Ig1!S&oMD40EAzyJ4FRCWc$p9FxaUsgygl7hAo_qdtD8EEGr>s}KmBx%M=$QFiGv z1?I4vbLVX==JhowyM11&h^!!$oh;GJCp&{w2nYUv6HK#5l@ zAHeK%z>aXG9C`U%M=>qi1axaR&ZJbVhaK>xuia_XINrKp`ha|HKlC6@riQxB&rUk`{$_9!nm$f{LC% zXyE}C3IJ&xdR}*v)uPf6{eSkFrkW{W1gAGLem!!fUB|K>7R&~{0iPg-NQa#A6tht( z5b~6BVX`Q27ARnAoNq;N5m`f8pvQ1I90@YU-!S32Ebk!Ub(tO4`p`i&;B{s8xa|j- z&_<>>?U`7sVcTZhgV;1|nTjb>dsC9r&a1(hF>Oya!%Tii;UC-?jU$Qt)^`u;`5w02 zn^Ys6zhqro+@@> zeZX~vO+`dF;ui;*aX^JLBO>d>3owbZO5rq*c|Zkik{n?gVTuRGIqi9T)HB}&pGp4A zpbZj)fU7`nU~sOzNpn_-rk$UMoARkWjXDlx0k+~)SgJMp9(KaHWeWGlFr0#!x6>rJ zyJeY$4HTxOd-y85qRC~UW8q-xneqLQYCEF)>pHDKBNH;cY<9@=Bc&`9w^EkR2SZY2 zr4A5?ktgQHc7?8k=A9X(iJsl{xD=dm~fL&vmDGN(;}ldPH|WjZ0)D^e?0M|VCw-rB8b$nC$C!L;>H$BP2t9fkwI)KODSy!m{K@b{MQ@H?{Z$ zPBPtV?gvV|?zNWK1@o=7M^;ykutqs|TS)4Gt_Zv=pWr9PS-h-)0xM!vIr1hR&bwOe zV`u=_8BULos5Z)?$`3AMw37iwru(oKbsfNet&F|;X52qt8S~E?!vBo8Niqrm zLA1~rBIQ7m-9I%@IAU^gvYVEhM6jqaun~XO;}n_YV_9Bav?%ydFh9QqqYty?g?^!0UrkIgK+U`v9gOCK=2V&01gct#(FReIL?7tRQ+T_Fd{t<&~oX zSb7xCnK+NW%;^oF;*6*N1E+V%C*&HEc@`3`%I5FxiUM|F$?P#)D@DPgqZwah|U@6@42>OouXAGqv~tx@u6oNGyGe>Id^WnCuItgDuDsIvhsE*t0gIQ{DlCvwFs74$IO9Tx0i z(28c-L1F<99pQ9gDf4;A<;hV})j7=WC)$S#!a32*GwT#2GvuN_07kI1u?)o%3b9ie z00o;^7qsXJILc70MExEM$ulG30OP!!dH2*{G@>D{0@eu;wy6)%90Kz^D)zfD3D(zD zPRviP1t?&{jKIr(X)vilthP#g#n1E~Pq-Za15=c!&U|hCu>3$%TP|D0l+J!M&qkGi zw?%2AAACC$ES{5O(@(!F*30@QZ;rpa9aTJ;SPy#>`t;~rc40V(WOM|h1sEVj;w>n4){21*3UDk@q zGGm!>fRQY_bu<;Lu}Z(o$Z?`UCkWSA1xH;%^17gbq76^K?pDG%Zv6QFRgFu24JiNB zIfA5hca}rE&AQb&klwYK3WwV3;qc{sn+`Zpd*DX3nQtagol>9D^{|XwY_#FZtKSk& zoha2r}jl<)5j!j)Wj{}Y!? z%W^_nJFy`XM-7GvH-KyYFLgLOthQy2cCjL}PuA5+da^R}uv@>j!c;pA$pxo3z2-GT z{R-wM4WOh$#}Y}yDhxE1rGA6h1gh^!?^qknc4?On#leoyA4J zl0K3Td8xUr@XRd$@4;?C-Jl3^8=v7~Bt;g&zQ)eoiYyLp>j2Htp&n@D7US9vIV$cL zQYaA_n^Z^AAbbn12}FlzEg%rjbX6QM55g(M^k96UleGDbLnL#Q%tz@X@f3Jrl5ma7 zA9_7ws^CBU5f9w>@tF-k(7Yul;jd00xN#CF7U8cXUh<_2d&rM>mMr2g6~Vb@BA9QF zpG!F7-y@c@eLfNp&k%lx%^QA;7gxqFa}4nZqf}CzS2k)a!|D02_cwH2ownnvqP+g& zio*FH&}`>lbdArjWrE)f-k&rP`6a4Umjl>-Ox6ztz(6p7NF?tMj|n%YHJerRhaV)j zv0!Ys{Q`Xg_m=rOL0 zewKWT2jQhye3$H+Rso#3`zH<2FnM37UQGMu_&V|n#D#w*E1l-Ooft5^x3sSJ5?8zZ z1?|0!Q^Y^0AYm1sFBP}TqdAAZw0g>vXF1f-1BMU4;kqS%BCh-jtN*K3=Sp zW)~H8+uDkvU8qr?-1SYxIIJ(?L?~1(q{c_Uf5ks+4F(|b6@P+%9DlBV$6rbA>qSBG zgB{Z5FQ(Z${+@~zA|l6&9g7L*2iyNX@VgaPv(aA81N=ja!k>)b{re}?e)H}a)}|>3 zOVjmKcjoK6_up@hG5C-eY4@W>`rZ_*n)ZPMtUgy z*lud=#d(R;T6$UCLfLah;ksO8l*|j7Z%f{dlp1VFt;(5mg?^2QgY{g1JCTuFH)rEn zHXGJvH;dCw%(^9%Sq4_qyh@Sn*#7>>wv*r`>|9QnVxG~0J#57v+@hbDE6l7}xKdcs z1tyd=FCB36Ux3eAsV;ftmu1@XkN>+8>{7+DFxuJbih90k{2zCgX zI&MJuiJifSDV3_LAYwxu#Xtv<3Vwe15z95UUkq=-het_>-S(_o7tm|x%h{c;nIkHW+UT#&^JZr^oO9#I1WM6MwgwaX8A4`~TPj){aO=WvbVfwot{89MUN4*D2a54gI z?}0 zR+}k|dKF=21X%Hh-MalOsRTilp7!h_OZHhjk>Qm0XLyLLd(<)S*!rBQ#MZ+_1_KLN zKv#ks;1EvE#8f7)M;flpt?kjR7QysB(3fDDsrhF?Xm@=?c(hl0NP&0Qw9$!f7!H>9 z_$+f+n#;oIzw{XZhHoxQAqZqEP)x-(J(`IN;W44^s-XY zp(L{yhX^j~=1HJ>PT$$u;FP&N!~T)8csl@8V;>{!Qu>E}sD{ri&O0y1igo)EsM01t zbEUynNArc=_vD?sEU=55wln2cl=y2%B$^mH#N6|KL=-*9$7*C)%G5uD*dX zZ11pwUmZ7E6rWs0pFs+bGMM~hfcGd5uej{v4keEp8~A`Cpab#+JS-*~sUcrb9Rr3O z8R*=>xL`4lDXG}^peB1@<58#_uP~T|%Euo06fY8#wgxYrT-YAe0d*=8z~ z8YMeL)siyjy3&^A-lW)Q+ibS*oFUb;Df+f0Im*i@ooJy$bzrZ|On#Ywsc z`2ZP(@Z-r~tQOc{pGbRwrL#USnqUS~OgAaP$r)T5d1bb+U2$lfWQ3*eWrL-&5Rnub z`ADgzDKT>33OBg|u`G6G*v;0hBFQqo1MFPl9quwXJ6L5`GEnkyUJ$dc7Ro~ubnMC# z%4lhJiA93@=a}=?>fPEa-88?280}Khog^ZTJ!H=Wm7&%os8&$IQjK#I%$_;ZvoVok zl;osUb~Mp4+AVax3LaSAVlA8}n(=6Z;0Rw7N>c3BNflBtvfa&up)T!GgE+F^;7KL< z7KfCKt{2F@m$CdgFW5~go0DS^Rs=Mq2Mo{8#hSG@hOOO{HMcjW9_YI@5arIbkziYP zh1l0{&Eh~OTG+hszy~M*e>URLw}G#7r*g}xWOHG7O{gpLFWw;Hd@2nHSiM+`W8(DW z1EW*mCDC2KXO9&n6SEJdn(SH<&(P!Y(CAq+=A1;4TZ-sV%RHduX12&T#8(SeBmio{ z0ty_n;=aQQ+ckLhbzk1izp;(4=CNtGpNU>2AdoRBjNIl+Om50PDCf^Xe%K==z z)QEOKGFPw3dSdE`VtBH)qSU_3#KS$vd!p*3Jj01R@$-bJCaD_p7&=JN(r+MmMV4u9 z-F!s=*)eP;tQJNhX)hE#p^EHBR5KQyI)FML2g;66(o`Hm4$)Ss>*F z`BQIC;N};~CsaVv4Kpw4tpQzzQ}t99`6upQ(%aq_Ca%~)g#@fmL_{gx7>Ut9G6bju zkkAA`m_%_sY&e>HffNwMo|+dx8+rgvXh(4;+JgoTht8}j`i8nc=%jpN+>M4hC`t|I zD-^Y#IYGvx6=m!cL{TcqlnUYtQuDx(B)ZEYNeMj4d}MHyo& zuRMuTQp~ADA=XizP^he`#F${r4y!T6@mz>T*$<1hEFmjCxo=8+RF5GK-1iMDP{F=) zalyIg$<1bIuRwNTb1Fr%xXn+?ZmI}G#CoICSqS&VrzF_t#t2yl-AZ)DOEf0vAsOEnA^?k`J}T#sPM1KDih*WUYtp0<%i^*PNWHMk^3h?&W^pP+ zQ8WlgnXgC4`}CmE4$(mOh6_k~#)ALFo8YwqJGN+CQm#g}>F5xQg4H^T9vo6`Rs?lu zK-e}{|ISQ5N3&iG1tVQSZgOK`PLNcX8&q^nL6hio4tf$e!c^n3Y;F=oIr}adNA)6#H7wd# zVpRAx%mgHvR(PsXG19*sgA4iGQq4o-4{3?O!KiASWQvOMNz&@-2hmP?eEstSX;U&u z;_Czk;cCwu?2DEoJYnx?bXw$cH8!kL+^l&S3X6|^CP=% zpr{C^(1>EK;g1>%(%JQSYpTDhZVZto>q+vy&Y9NyMsyAilwvbYj9wCrVp}A+3JO2t zI3qDwXI#~-J{tcaK z1&X6giIO+eMfnMqlg$m=aQ+fHa(bsH;w+tfak##oX7^9yMih126Of%w`sDCBUn^@8 z%pEkoyN;bG$CK;QOjyMaiC7I~uk;I}5Rr-)(6l^J{ZK(vkwyeEDe zt8RWgZRWEH@o+bF(I`vZIX?1~I1nwaLL^(4{ec)pH`*}wuBgUnX+6=o(iX-!D=BlC z=Ik{}Ol4D^l#zKka-Vwu^A3KC^eUr61?vF<83=61{qP43&@K5*nU-eeH%l5&Zw+hg zsaxw6)e7u4wt7d}b44Dqs$@yfER~k=QFR+hJ!0i_(6Qyiwkzi%(68eMI9}jwP|leM zbTFT0H61qgz0J=3K|Gt3gOU*j#>p=@h`aUU8F;8F192zbDtTN)!)Lfdnikf5mnE}D zKyf?LepNlqqnzHEy+N^7dK*0?%_Tq}_F-f!gO8y@ITxs<$kJ!KHZMh(AqtCF)@V zP|jj8_Isc4QVZdw))?a{_xDM7H#M!()J=C^9aLn&ACqBDEvw_hi-fZt^&P$CgAe;? zME*f@USS!U=cR7HYhyzcuVU>;$qcL8SvFDWN&@6q+!a)D%2B(Jfpa6V)2m*J@nd)# ziFYxs8JVT~EuLd~9r8^DsB{7u0?PQE08$PYPl zZ*cxHP;nLj+pF?%4MeQ`OZqPL7!Zc3`j@0z^dUg$A)w^RY;Q!MuCJ;xU<#+#{?S46 zBU}A%+ETCG<8@zGWdP!5>DzP7E#&fBc;)l#wZx6b`YRzA{&&b5LKBrkpYIb5h@xI* zgsbCKZ+-gui69b*?uYKP7kq|okAcx;n|{00ZO?&i-J>gzUa8chbq(pbPx@3R!GJq` zs^%TYvWiHjn4vzW5J}qi^DXd0xHtSwFE1evBz?pf6YUW{QQ@u=x^aK3-cFQfKnQ<^ zWHhQ*zhwU{b;jWCfy5%A#9yfz!V6GF{0p3Sncx7M8$$R`SjnRA+IZZwmLcK!V!`qE zR=22}mS+I|U#`U8ZR0f_gm34iw;91HZ^TuU8JF105KB`R<{$W}fGzqNN;*m>Tj@cW zJcb}b3N)h=-b9NT)=g?on<`Kr2x&>cI$Dlg+AM41&=6F96jtH^mctYy%o3D8|rRx!8WGFS{GFKCGF`8(Ab zVzdT}h=@#o(r-nBedam16@JbUh$>lsGMdveiA+qy$bb^)B(mc-y-ph*?3j+yE;xxF ztgek}?4pvGI=N*2v)z>?Z1UL#3v4dUwwU@BoNjW|4VY=k2<_+LO{qxsGRwm_@j#_U z_N>Mv<8x*W< zp|}G9IX=Ol#B+A0^CM)fUw=%gBpeBwHw+0~SA{$l?OT%P z_|;!;c8#L@{K0&ErE|O@4*q~q%q1lw*o-?v9)2~hq)f_s76Jz@=|PMV@A5sb{+yXz ze)R}e$udYdl0Tt9Dl)!#=kS8hm7txO@PS@4D&ZS>Wcqe0J5npSHLPT)pmFT;vN5^f zzV70AK)J%}4GYy{qR(~n(C1Q-$yu=`8z&$9LLQ) zz-B3f*N@HV)0ysF9te{e{_J0*yyreTv)g-Z!WX%0^9djIKg&M&x5|9rUk6PbR~Rzv zB)2%q2uqda4XMBlc6D#XFgO*SPrTy$)8F%I_?<5;e0Co`9G}k$eVzd6HW3nml#T1x zThLx%btidfwgyWqH~bB^C5960xbf2@;M!QT_&$OQ_+aa93b5b=;aAW!Q?^Yv_N%co z=hemCTQ9B+CDnhZ%4yQy#w(rWYX;Y4Hwd;$SRRL{U$UezS=IV8bun=DTLi2!5UVf4 z_MEq-;dp4_rTVvORI=78{#g6m@DBe@pO*3T$rqj3fTpya!kzUC)0mOz_Ztjl>e)tl z_~Q+szj*K7hm$mrmKF@QAx6zZ-#SWOWrfN`HVUP^0Sf7A;57To;gw+{z9eYM+aJdL z_)CnAAKXXzCe~yEY|dn?_!!b=0p$g@k)6P}Ec+2e7*8+A_;-GZdaCi@UA7Ki3LWhN zNlrq~6d#&cTv7@w7pB64Dc-a8EF=*O!C`#Pw&I7SM^(Kc$9DK754~! z(+R!M#Slw&jnbqyVf_hsPgYjbX>GaH4iM`_RBlysf+WhLDrYPDY>AoH$~t1U<^=g4 zcjenB998QrzMOBC=Q9V*?zu2I7>R*o>V zAG_TCxE}(?iS$x5-}CZVj|__!_0WV>->(-_fqp-V614L1gS=F+rV6@fv&spBoHzv$ zDdm1QBIpbkeD0ia@=kZoJWsZv^V)H8eoG?h(cG`yTXx6T&K!kKa31P#1@{FUMdX+01&dyrUTI3#(YNXJQ8kRhWUx?88avBZ< zfv;%2?bx!#jM#^St@EboehHABVHfjjEZFL*OHo^qB%}!qW=XkN7e+{O;TL-X1^MeC zQdPO937!4Q5}zjV&)V9nz^*I5I-i|qzaKiM)7^i4=A<`Fw!Z-1Jg@uhfb-3X zy6=65FDbZtH%#D915D8TwLkIo_fq%adD7Ti5lw*U~qwAPc+X;KDb2*gZ4Jgrhei*#2Y#@tL2L%6qUP9q|Q-B(> z_cqx61EufkK|5nNKG2^;1BB=qumMTH8QxE^u->~D4(!jbPcp0xXq*m^x^VzM+ZC{P ze(Cjc#P}SkeeML}bB^T4e**zEH6-P3X1V18$3HQxj%UJj_-29vni;4g96G{_5=ql^ zoGedgs7O^5K0*Tin^S1mIM0$fZIn}KSc>`0G=~l-*6qtAhF8XD)Pc*ss|t}m>Pa1s z4p%zni5x=!?lK}X-9 ztH)_;`E6-xLW8wVf2SGlJ1)}Q`Fuq+vK%&U;TXe~d<&RJdHV9Qo}KbNf-;=(sVR38 z!+O#pGH>igQCH!To;ZotR>mmKJ$0H5s$E6-Tx!AP+OgdaOIeHc%12G+xy+@irmont zR@vqzKI94Ym4?OFXiY)hTxPo0rr{s?dF^1molMD$U&D|n-Ig?oBWlourGhD~H4+1U zKjHZ}&S~6P%kXybcBlN#07E+lnKHBqa?p+d!dqC_I6PDwcfl04OlcGA`bT$0?{dVj zdv%n%_SHe#X_#X(@lxfrP&pR$-|nBRs>)%i#wO2nwK5ES5EIqx$&puw+WR#lYVD|3%0T^g8GslK!!S9@a%DT(xvFlMjH+(jBMq-e zNIQr|r6U$W85Ql;R^7D*5UD#KVA8$vQ=`I@jh#gD^kP!AIXJ0NdWb?-E>r0iM^^;9 zC|A#$B3rV|vXLjzTJCx}tqcx5+lf9}vuw|sY@egjlU_fZ(Tq`2P7j~nyOcloGy-Sh zdGJBrq&>yUB~1PZA=CAQW)l&y+*OvHbd^MpM%)p0EpA1naYkWazSdKBro0`!O{wA{ z+BD{+T9P$Yt{iI!gXYGQq+(`KMb%wY?#?(DjJuI$c-2d5c9`3$jBXx18{KMY|2E4H zng3Q?CMh9Hk~dW{O=YcObzG_c)mAO%@ZN|)gDGy(zaLdF%+-VT4@sd~qG3q%4_!=G ze^TtG~3}@i?t-TV8`9T zbGcIAO+d`SHW?bX@+@r><^_g0IL!^JE%h~ia5G6cBK9wZsY=f*k{7L2=cxguJf(;J za3vwummxay8avXGhs;Y@1w>Sjgx}iJtO`q_e^N;Ldrrv4o>%Th;@oXzpEXf z-h8`uC0FxJQld^OA=&QV;+j_;U{o52yp~>ew93j3aTYX>x%_f0E=wsuop~)YPl@;?`XBluISZ>DZ`CZq7g3lqE$Md-C#bgRi)kek!sSS6cPl)WD#Ro zU-#BeHrQ&^i=T6A9$RckxdQm(C%Kg*hGNcB)Urs+ljw{*TUWJfO68>NRyn%8>!B(8 z;;jwBAMtH6@{;;Ag~^-cNkHf(OL?4OoBSB^Z|<@>Kxx1}9e%K{+@adJ8~Rj=MDvz6 zi@6vMnfqi|A?Vn>iSF9Uy|fhvvw|1=rLwmV&|~NQj7NLAd@Pq)(9z;!}dr>{2F}L=`rvf zLwCq+>v~)I`Xi0UY}t_Pms!WFtUomt=Juhoe5((ZW%DyL-M4I`Br;30N2(IHghk)u zibSkQ$+FkzCG*N^eQmWG9TT&p1NPBV8)(KYhPRQoO#Dd~=bY+DGdE*-P*fkLtH+84 z!<#)y%p&Y>8^}R~!-*Jh?ot{yoC5kHd!rY!(PrG+yn?@GWU4ZU>yL-uCqYtmCh3ItwHlkH_`9r8~kJo z+=cEFtZ%*`TLr26`O%rY%lUm;Xc3(gVcHO(c!6lcu!zkCaZ;C6rCJN&E-vsDU$)Le zMlE4d2uH?J#}6mUaum@M)>#pY6FSc%=nXo^T`(5Hdqp$mad4Df9efAO;xtn%-4*)gQbRdTNmqrY1*9Gy|@q_$ie{rzlp-b-f-kHP9 zOi%;KWz!Afn^#?Tx#|9cuQr++95b|-z%gv=yD<%EX&JHP>JUh_^eS4Q93Il?GTAAW zWE_Fxs)FDvQ=`TAMWsGzdK=8(rub5O=xtCEwCfUqV$3YCYaZB)!bk46>moicPWV;J zY5V!?73X{S@t|n@AMjkP5^KoqeC1rujBE#sIeFRf0NyCgSD}m=F6nsT?@K7x@<;F9 zKa9$`2>HO%nUAz`f?=kBtF85rfWb{as3^--4=6Mok{LjAA~P@Y9x?Vu4kX^{LR($R z=UlH1OX|L+m`BTuOTVEEKbNWkzxE`r!%nWEaIaSPU}l{=6r@lPKL~>K9PD({8_G zyF_mBDBLur)K}Pi_5na#jDURr5*H)I85g1JJ^%1D&W&&!8RUwVW5ZL!#fd&QgxZwd z2(ari8k$pZ!o1K@eaD6R(1oyA0w%g=!Dwm|wM;JdLqoSwCf~~}ylQB! z@H)0*L%UJ4ng(U*qt1F(*Far8!Zg3T?A_L07+ipJFIZ((tWn9%x{aL8Z`(OH3qfR3 zJ4hM7B!wrXP#c7))L^+_Bb{&ZE%6Ao!WPrK`A1Uk-vkMD1=?i=P#z?2gc?c|on&r! z$6k=S4Rhz#T*d8jB8-&a*X6?5A-fZe%0j$rq(S{Qv)~UXsRqXu;cr$v|!nCvOd0_qtY3&Eb)H^e{xJj_e3sxZ4751(=12fb3}5w zIiW@69+_f>M~hx#H1RFl)@5ulA}QY0{v=f^Otqx3s1xN>1IRx{Z&}>BYbQD2QFQqH zKa71tkS4&=?bo(#+db``w(Xv_?P=S#ZQHhO+qR8=?tPmV|K`0-MODRCq=wXADi>!5ATt#y zevfi5_Rn5r&%!j^;;9Mzd&yL$W^RJ0X(wlH3ZnPwq6L4LT=5~iOOt#XVzb%RM8i9k zJ^zmCQ|(N!OoP4tufAqF^?7vNf9)VG-~a&g|5q9I9|7b4yADfIwsb%-M)lslq=T~E zc#ID>A;G9vv+4CU>-1HD(#fYoQ-sVV*d*U@WZGKXs_Kc*M|Hg^Ck0v_oV&fWC!6NQwm(SICdzF<@kKSEu{T^CEFQ zdWU)`zxP4&3IJrRQ^&_)NB-}unOVP`_vxiSx}NQg=2qn3JLWCgLmk0HY_LbA?Qid&73=~!bJo6RjE_JnuD(c-v6{()h?`=WEe zI}%B=2lT^E?)-(L!=myBq3IF@5C6ViJxdp}I$f^8^c*JzBKzKS<;moKKD+2|01uvQi* zZt}LBzf>C~{7*vY5$+0AzH}iu)5jJnlSl{*@jVh75`lBb0)gH^eXvuy#5Jn4 zcW=13rqdVbmmlvP>YP4;Ts!`$i&}-W7<@(Q zK=9B#broZO5yg}IV&FL&zvvE5A7_xkzG{akjp{dl@P+|f3T4f(VwsqDs2E3omxuLYnPBGr zq~Glj2;yBR-+^SP=^Yd=*!TbPEvDC(;ui@304(T${-16N|J8i{KUhg)7nGjzQWu~1 z_2$uL+U8|jpguXRKO#AhQ3N(2Fc2tNzAvR3E73207SdEG!X}js150-6iY5PW$kj$w z%_o7`eCL(&8I6vb=5;S!-3rLVm#<8RYvu$A@tu#KAiY{o4lE7KfuhIUi+Ucc9$_O5Cf=tR z66)Ss3?zMW3>%mU#)zVa-fN{DL5u2@GceW0S4 zPv7N=$$&AeT+<3{9on>tt}{L+7G?la8=o>}a`tocroC5i;1pB^XaVf>C$-F*bL%kr zI-qwAHaY_{Dhw|b#|6Jx0hWfeHag=|PE5^xosk(SBz9hLZGD}<)h$3;VfNMj7ZxlP zXw_`3Z1xR2!)$3OM5K7XejSsyvVm9uodR0Hch^(b%x!`yV7ktNEpd9Cm>NbV^BcEI z+qO3Qrm~ot7b+X4F7CF#Zk;zefp%P0kGZWML^nFe*le4pT-J^|tRHxq+i>Dq1}9(A zZ-fL|fID>wbca&CE%fvNhOa<@UWjPkkh=qb7NAY|TO{8YU{BcHXiELvEC9iXcxd)k zP-q7buV393$`X0Hhr%fj(}PZVe6JVa1Mx2(@iWM8Ut9ic{1uOfO&-@fodk(ErOK@%GU2 z)*hg_0^15}Lb9*k3JXy1h0^P@1XdHj$E^Y_1`-Bv0Fn&)j3rWla!L9@Yj;_JXkFm| z+NMa(D9};;I7?VffL(HBtRdhx_sFjWPyA|(UH7<*pQszD%}>$DuL)1Ah8LMu9iUhA zUS(ffkj_vYABf!!zz)bOZ!bdY+dyh@2A|+Ffx`m@(-gDM;8e~q3(n3TLk-xSoFOw; zC^w&IeQ&s3Ctuy%NL>R$AF#-G)?QnHcZAz}*7T|zre6*m;wz9AA3lKj3xDk!a4Y7^ z2lUA)Y4(KC`aM3?qfh(;Kf?$3hPveg{0M6Q1Yf|}bcabs4?LjvLir{DN&seFlZ}|= z9sa&WKz@9(YgLfMjsTEe;YyLO77pL2TFUt$$hj(FSAhz;S&ItLZAD*ZUwdC@i#8Rg zKWKo`CRXTb3*WiY7unW*fT6r5aMe3Tr-U!IZyn$OFbpEie`HE{C+sW1bRliY>`S=b zC9W7@EH6=DEh7?-%8y4J>dDX$>X~CGBc{pUTh(B&&bmBFY?;iOq3N)j7J-com~s#EjTA*vMg(YrO)5gZAl&>cF~}L zM9d;u0RvzFR|>Dvjc)jN5*{v7x6&UklQCU=En5UuhQ(Xb^%mV$gS5hKPsBNRDR;nN zx~4P{i2_)*G-j$=&e);aIdVQWEGtt4eNPY=7__gjx(YzUVN3PhGEWr5qwr{WIVa ztQBHc$M?f`&Lh@{z}|OE?PJdi@WlfFkQ?FwyCP}>GU|FwD&_P)d95`O)|O2TE*+K! z2AQkG5FgZiXY$lzh<6MDyKLyKQBE8dcy;eki0E+_l)WebIb!tBVXk23-(VQ6kx#Jq z-hP_8ks{@!yq;7hUTg2rE#24=k;z25LZc0v?)2yqx4)+C%;&H>`j zuj&E*gaZh#1IIP<7t$e`U+D5L`&_WeOJ3mTD6m3q_=;o|6Z6f*4qVXXlIGiA}0Ac{wi70J-PzC^9(a??qqPF07`2O{N9VLVR zC36BdU`QB@6KdR;9&5nrI7suL~2vmG<*Uy384m<1+~NP74#JWL;$77fgp41Lf1w@GVu}UaZ2g{ z*#XTlcoU^}AWLWf=mlLeth&QrA$w8>9lgV*{sbiO**>sl)-0)6Ss%Y|Pq=fr|Kh=R zP<3=Nc2DQts9%#-HJn?rOmS0q9^%2%?!wc~U@flEZd5bWXc!L9=;)~LvexdpSm$an z4TY~S9o~%K-f(Akda=@8n@duUKf7F|mMT?qli7%F#>%qR&M>V!E1GAUSx{TsN0U<3 z+UhiWns54}+iWnlex!U0jVdgp(`j^8FOpKNj0CAwT8GSF_co7q!cYQ_TGQb!^Kh<0 z^GB!4eQv!-oHYVCb1=15hx=T35)C~U!JY89l=AI( zQABd7yo_PenzU(gS4%;MnPaT9=jmwcwaLiK>20hGkM(Bj zGqag#v+L{2>okn%*2)fw1LMQ1E$yzfYMrPeaJ0E@o{f#JByiI|rsPp>=E>$|j2#pU zNz;#dxEHcHwoQ_eky&`^)F4uJMmEaC-jIx~v$!{$jaRJBwy8)UA=nU^ zovp6({WP=2oI81h@XoHboBi9Jc=n1;wB*vt6j+2K+?lN>e&J=b$h#E@%o^jJZPSg; zHvcs)w*0jT-Uih|@~4vU@MhBL+}iwNZF7!H>S#BHc zBaAf$C~r;sANNV2EP}^JC9G*`$Pd7hHQ@ZSd z<{^j25gs!55rEO6b3h$lin~B+p$@j8Qf|R6YHrSTpgFL@R#&aAH*W#|JRz;$Z+&@^tSdd zbShnn<9u9W#3a%|twfs*a*idMuTtwpIz=Y!|%NL{^yP?*UXDG^nr^`h|3 zN;J~`0~8q&`eu1=YIoEkvDx_xkG}qS?*NR*)@ljb@oT$r4 zKq(%MYVIo@I%%KxNn2O=7Nf6(y&84L=gA^d^MAkBVaAwx3MvX(Bn*`-LUiP|G+UfZ z!+y_bGCAQfH5^~FRK*$)D_^Z3muW{Z9Ua3?GaO~^A6TC?vSZS1nfE{G)tkUoi~N1+ z=RD5qS5V$HAm*@S&ZtB7pErS3*YG>cFJ-2dq)pC^EfYeIPmnhfTLHy;D>pk{vokaI&z&O2cEpU&9iDy&JM71+83ng(KP!4{qhR?~&pi zhumiFT3FAiFDr=t9c4iEKsc+Rr^SW zorB5^f+b;UDDP^hGORFKop5LNFXCTC?5pT|u^0Mf%@grZ8l~Q$4-(FRRF?<&&&~#; z2}Pffx=VC)+;q8Gtxo^^%t(4#!i;sjqXh|sc9^1M6-|lV?tzKeD#M{r4IAWKp$T#_ zY5j|7J$K)w(aF&tUt+o2jAROwN@=%CfJFp#)aTT_mA0Jz%I1fomvP<&G?^; zFo?9Amc2)yV4)kY-?7~zj=?j!FxiRU7mdc(OIcr`9fy<_#&hp@#_XOF@H$V+f`tFZ zj)cS~ZWb#Ts|_8=popX<7HBmNq1Y^IZq1UNH+$4QFN&%e6mm|Om&(q)CG#uI4_@(@b4~|K6Ad1EJhZ4Is4vf> z#0velP7GNQdeg?d3sqR_(b+ZnIP~974RxNAKcU+bdPC(!MzX>}wB=5lk>Ms`EO~La zl0p?mwUfk@0M=^*_a+n~4WyVi#BXv{B*G3V{<(Y}`IloD==}N9lDJovuRsKibm(Uk z&HQl7$4hx>5Nb+nb8mNO$y!v5{m&$Xr!;ox6s5^{G8dCCht#7jF89v26)R}U ze{-(C43bz-3eQg`?g>oP4O0j^9h>|PbC3)SmWaQks2dKftzWfM;ShoHAe^4~C7wt1 zP$cwj2Cg$zS*L%863jiEoXgR#BqG1sH_gs6`mm1(2e6ZgMD8nafR(DE5{`I4|K$Iv zPVO9|g?e{h&`=Fy_<7q251^XJJ)KRAa|FS`f#Rd5KEB z7$+}Lfu!52$6B(@WG*`~QGlb0bzj{B*NHmm?;dt_vhf?aRE%9s=+1UwA!CFt^ztzm zL1P_)Nw_jwOZSczW3X?zprHASabu!}Kr{JKyRf2TrV2ghkQ|Ab@z8YRXc6cl>H(te zX0E@rk@0`fRV~#iYaq$Aih?|Y)W+FT^>;h{Q_r9|T{+oMcSnZ?fw|YY975GZ5b+_m zV#&L%f`9MX-o;W2`0^$APsRBA6XlSuoLPti)Wtar6nmG0K%#WB45^BJ5nV7YR ze`lIDC9+b32N{#iL2(G1%^fG^F7wOoGoDeyh>hmmrxwl6H*B^CKG3ixPr`)U`I$&) zNQ7FWA=WU_XX*~oHLVY+MZklma=Kw!AQk@6B7z)+j&>gkwU;HTGBcvG9MX!WJX0nD zMAOoVKWfy?$&+eZXh+yFofgR7$v;9!{Kn#Mv@?oaQ&2}kchfN*z@WQJi6-&-E)IUA zepeRZl_P-s+j1^%ca0B^s5)*>RIl2bdBCpOSYiF=<$2r9pES{fSa_7{(p5j6xetb` z)N0o3FCl~EnP-sjvDVm}6lDufxC#|M>ySI56cfLEMFCBQA0n>?7c`W50W=}s(#+cu z;r5hZuM2lA_qv2d8B4MUu~ZuLixqP0Y#C5mc6IagQktXOhAgY!QlWdH{$i3SU-_ox zeyRWyQT;D>`NBp0f3^2SDH)!6__&^u>}$%2^%T-t0upKHYuYH?k{|I7f=I6Py9TrN z22WnOGME5Cym~(oT|c5cE;N!x^dpQC`c%`STZYfkJdRajVMxPUU_2PKqlgZYzY>CV zKXeHUG!;CwItklbVo!-RgbWF5qa^qtJu-B#aS8{96FDP23ahxsgYQ!VWyY1wjcRg96&*}LKwFg<;T~sz<#w^G zz$D@b(fuH}B6ZL^V~t(`gfUN-C$VDnIyve>o<+So=#c#V5;UhTGpqxDW6|%!2U6#T zXXv>j11yR*{^gmnQ@Kn?KDuRp@E=L66w^Mr@!7m*T;)f3=Z|wjt{G@uGQ;}`B{*6$ z`aeNvwLTU7_ttYud%L(&FPKq-dkiByp&NqM_7;gKZU59{LlFYYB1G zEw2V9h5kq=-^b0*bctQUu7bFI0 zY@1MsIEyQoYbJAv5@ahf!@CoW9wt?j*813}r#Cp|9Aqmfn;+kwC)teAB8`REO7ez+ z`^YKu&qPd|#PwT;%ax!7ohQ+orGOOrpDXgMR%M48YL%SO@NIjr3uE`C*;Xn=A_{$< zs5M93li<{A>h&*F*sQwZziaD&7g9{qsAu4th4djTNojI9GAzT=l__=UD_%rhv^C`* zMCnGB6Q&xTj2^6c)_!ZsnX~OU7SQ5f_(umV!J)=BjG;Y%P}%}#Z)yk5sxur;@z};s zwea~~dKeYu0w2?H@h$ysnvWPzZzwPIt&Hpi1a@6qB9ez^hGss*&Tg=|79`n1AVuA` z6|K(UV9C*q9jC#c4^~DdlF3!!xvjq=rO>u>7C9G!88Xq}2(pa?$39s>8jqtbr!};3 z`LKzd%p9M8uGLN>=!(lsAD55GCUdCr+gpkaN(E9PiO3Fth3~=Jngm4$g>_*XYfvZc z?SVu+lu_fx{C%{1k`?3A)x8$}#(axLc|+;VA@TW3>=Sfh3-|%NQuO`^cNf&v4HVEz zc5Kb1RdcpWfSsWOAZmf~0ZgdM__$VD+sRw=^UZK>?A-2g*BtKVhfZP)5uLJ`WIg?? zDOFC7)E2SVz@z{R`Aapg%z_w*RNvZv`1N@L?w9nSEKq7t?SIR3pk%`4@8=>H0D1iZgHMVhk%Z zxQ0Qpg?8tmGh`%|At-N7hwQS4DRH`-!wbW|;Cq5~FEDkF0>i`f;XjFPRK;qRO3=;p zqQ&#*D#=;eIEdy!?2Y%B*pCb;=xmgc-)bI{=1e zTbDn_ykUHgU#g(*cE8GBI$_ZEc$zn!NM@^9TAf@_Z7VGC*wE0380=%WCKh&aO-5Oj zJ$k@kb@K0kbsCpNYbwws5$B2@aZV{QR?Yq;+2H7#T5{9&TGbbyuka05B!>b zEg0A-+mWSpE$!3pu+${SQ~uQa0-v^Ze3f}ez&jWiwubLX>zUZQA=mrF4RvKh);hj@ zIbGp+w^a5xya_ZAE%}xm;ehztf#(flyu35Z&FND!urp#ycE+rmuhZF8@QW_7JNF~% zpsQf_z@^!Egxfv6hhP^38%LYZJC>)w`TP}(cn%`$W;sUAl*?m-G7z!ayOS%0kEhdx zbRH!sf3;KTn7Fgj+tbB#our1i^f!fB7o^m33_-sQb$*0MGTB};YJK;3^8>=!&++E~ ze2>fw$EP#4^f!|9uGro?rpdF-0sROid5Sl(G@Cec~)8sRhFGTDCd^(6XMehjH zUyS*~hYtDit@jA{A5h}w+pbLA>st@A{nswYZiK#%*RNEXavFb&?f~hJHxL%%pwprF zh-8j;*?tBuBC|)DI9twlcE|L4kkohQ5Q4)HGs*|{AoEq|q~_+5Dw>Y4*=W8|zB5cY z%$Hed48mq9{XBbD0?2i!*rS@$8)7OWMp^51Qgj6Q2i^A`yl*OO*l%+~@Y+M7kyTOg zalWdb?Y$fzbx){(TSlKi$_DQW!&T8ZLr0((T~H8pNxZ|HAw)0uT?OBf*8}(;2Jh89 zG1neLE6_E}bi6Q-HMI#b7{tEVB^ZfpyVr#G9J~XiLBQ)tlo+meq;K7#+V$xN)6@eK zH-YFt_isUD4wxg}quY|VUnG)z9>QhEk(5iD`lD@%O=$x?{HFswp?+V;W~01cY!vp; zzaGSP+n^$U@JucyK55{8s1ZLLh6|#!MN( zKQZgXYFE2$l(|0L3fA`+ZZYt>5Wor_xk7%}A)ll^aiVp?bZ|)PF{gqBwicl@`79B= zZkAg(KaE3ynvt9y#U5w4;($oKF^6;^l3h`-f0&oA*ymCLAU{ZZPaLr;2LN(JcMAaB zA<3NRraR~#^phRuR<*kwFy}7*i@sEmiXfjDpB%G5riDGe-9~@!z~-OSpTW73f^%~< zEWV=}`|n>NKbbwp5{i2;gz!FiBHuuC?bpTR%NbEbNztc4k2wL4e}(@+TCyfA;=x4xYY)WUxOzN#5}Z!DqpY39=62oL{Xk`l1Mtyo8OYeE>7D=B1aC=_q%ul3(=s@ zSt9CL_?DG0&Vc-r6R=H~F!4+=v~(V-qd%G}azb^a31Q>5(uN*=HU;wl*-y((;OLB;Bs9EBCJc>KfJTKMx7JTuR{{a0R@#V88Z&M;yZ+$WCwIdeFJPTRlD~(k=>E5#2~C zIy4y86eOAI2Cp~I2V&(A`RCpE1wWBMQaIQUo2X$)t`3h$LWZglGT#K^$kO}m? z;)MIt=l;ZagV5{82XpXbtSv7uEK9iud*MNP#mHW6S>4#&*es-Wx4onfuP_z`TJ;6A+WHl643v?NYiTpNqO|kFJUFuilM7{U@!E_0 zQW%NLsw(fypRFtV1#SvItf&>9$v!DxPKsTDced0E3@Slc%95HLpT45})A-QZCj)oU z{BY=}=i^L)&LNT-0{j4dO>;!yukiM%58ER354c$m;iX2R)iclDzPw_U!Msi_Shd3T z{^2J@%0L`&6s*QfyJXn_2;k8CEr}WS_RxIg3;;gix>qQgRfnk)3L^>YD9x?v^WLtd zDDLtYVO*ccdZz-LzJMJ@b}NIyh^PMtWRA!C$p{%PK(uUAoYs0?j`m!slG3xeXXR*d zy9QXzawCqinUTRZ4N*-aja3>*NpI^-?99>J2`wG9#^=t^S#!*m*jjh!WRJi!jB!P`L)v&E!ezrIQqU^xU^7vi8GEtI{?wtB9)hWLY%;gwPR z`&NB7l?a~C!=nIOCb}7BVKGlq8AYA8#{Jb;T~mWp%MU zZ<}QiT@RsT5AJ^5rK9zE>dvC?se(;E#`@3=0Za~^hsOw+UKeO)1IyvSPB0MM1 z((p4(?ngHyZ@zB#)E2a%$j?t{%&-aaEw@7xgfByP^Hh`A&u=TYx?wOr8#q+yvw#HVnq+*Q> zHjrSrJT}|L3Hkjx{2t)3pwwV)lwUtm?_L;Xr#|ZhhW5^LtRIW0zxqC^NlIgL(<(M| zxb7=P(mF_{QM9hbKpwi^G4R_ZzX`*zhEFJ13!q?&c|=7vd4uo#0wZRjPRVe)d7p(m z`3(WOMVa8ky+HyIWKqk#2x5qBNl(~$m;=rA_0%K+COO7U;+XFG*X9V?4Qlk-dgHXN zyg56&S^HkptDOHu6%{i6j~{!8Y!R4wt=-0Yka>WMQHrD0wfssY`>2F&+8Pi}%;Atf zhw#vbOuRf=dp@B>#VtzEv{S}a);=khf}z`nSSFAHhEH=SiGV6PDEw(gkye&9k7b9L zJ1N8ozh|G2A=LT6EbSPXpVRGx0T|g%-VPB;h%xtJ8;0U|i^rcA@$o$T3Qfi6#0X}K zr$pD2m@aIoj&=_r)4+y(jV(x<3!JWw=Dj))b)CHKNJSMmMcH5_Kh!7G^_8YH?fbhF z%DbR#X(>(g!djkQOsVX=Jaf|SE`zo3qKX1oZ`hVKbdHpKna7S1kBt?$EjPUh1JuKIVl+!K zKz$#BP%pkMLX?_`+du-c+MjTq%^YAMV5F4R<935e5o8<8$GZHUd9U!Q6jS@yYDX$sJQn zMB!%<;0cQyu^Q9~1~<@vB3M%~nZwWtjFT&O#ie3LxOe<LY2nhsIg?0l=^ z2Ai$9ntk1TqxA+o!xr3rD2X!ap~CwGceBk7HRsDZ-A0Q|;z;40mCPt=Xd&+dc#5h- z-{Kc>U`c_Ld*&2p^I&PK0KX*EpuSU?CZj^oONAghf5FhW&`HC54eVgKXL(ND(vx3<+F;#6_p)@bIq#mf+hCqZo@_Pyl#o7DupY|iWrogyK`=V zp`5&KlqZROk77wab+3|A+bUiaPbK%P*X0V&1?5l2qksFRWjE4KSw-CqOD@s8e>`y> z8-`x~WtVr>izi_>|MJgQwzGc|L6sTeB%iu>wu&3%JG`idn<;ma5A3dgwmL*g3# zt^bHnt`mFZ;v&(tl9C!t-;@q9wF*>MkQLyVQKw7*C|^>3IO1X8$N-mrghJODem@i- za-vJ=L@q2nXnwlhMdk9GqUic|RMxWW%>{bF_p>qPk6|7&7ru-*!Sse_Mw@3Qp&h%x zDXxAK3eKSzspO5w?xjRhW5r3_!TB96$v8<>mQB~dtSqj{>1`=I2HnwJ?%`>5zAH-Z z7f8!OgOfO)Z}&y1C^fr4Rt1aCtd$vbXj48-;T<(TO=+r(m4Y;>=-Q$~b@JaQO6&>7 zFd~Lg-pyKUP3gQe(RDD(O8*1{u*+KSKsD3y{ijb&=}=vezN#c;XeusEF$j!PVr^N! zsw7k@d4}mlik-eL>qpGqS-*h&C$I?gdW^tV7!Kbhqb}$o;TS0>2#Gf_;N|b_YUu-- zG+yYOpD?L!h`o}8iP<#2>mg*c`8qnUIfvg+tiG6<^}V(foJFL{J5R&jy`7C`p|i}@ z-Y_mwN`a>-a0h3+CZRF--@13v1lN?fL?0xalZM~dmJ(}|+*jC2XP4fwRZgW<%$RFt zYXYJ161}Rt%f`3!>vOVFzQ(+p|1wncX9jC7Mwj}N;n42ubuOQ!l;yL}<_pb9-G_g7 zQnr&9U8IXr#!lCe1hDK8dnG&}#tgKYHPb+GRA1*4fy<=_!60>1o`@+4Wm0x0gj9GIY**VeD7RhQQ~xNsUj8HU|JKr+ zcNa}!i!cGNC?GFs^lldM@pqyXCwpr5c9zlc4u)Z+{$aL&xnEJtw)}&9Y6axCs_{ch z1DtnpZo1`kC~Daf$4xm-q%)1IwIle0p^)|u`U=0~6DJcr>)@^Py^RQv*Iv<+2jcNq z<(4-II7|0PyThb3S!T`A>)Hh*IubN}F*dv)sTX2$XyVWcw;m^hUu)3Iv9DHNr9*@g zjZS7IHpQ5|>MQsQA47z)zT%Tap=wC%<3IO-rpIT;48j%Xt&3J%YRKaOKU+lU;T+R`u2Wj+d&&#}%$fIc0y^QI%Lpt6>rmK`US zvB#mu-AtQ@SYDG9lR&f0Czkg~wyIw6vJ`<dZUIeQ;+;J|DyHtyf6+t#}tf;u#!9P&*Yph!kHylP$!7$2lX z!9;%NNA9v0s#(BdHvcOA99&?ZzA#iVoXj|hLh}5@Rs6Zw8}%@(?CC2=&FgwFpg*ss zAqt~c(NnrzjeaC*@iOoIoHzQ#@9g*E)P6Y;!sKkOQScxjQV${Cj1$(e9Lw&7b{Q2%ebl;f>Mlx((G|S===^3 zKd{Xs6Gbjk@V4H0Ba1oxm5~vj2kyKYNz6ig+Y4hyP2Vz3zRf=^mM#(6IWxYE70ieP zac3VPq@cuW{iZuLgBnWqxLjn{!Wb3IRNpnV{su;-FDt(K-?OK!s+)QCngW@-T>N!# zCclr-6|fh59U;gR(Ufkkga|R`wiK#tcdwXRl`ARJUxYN}p;fe-V@IRN@bYO?^ZTUt zGf25MX7Gi%HOB77mcc70+6d{Y;2O^zu?@Dv-64rSunS^m4iqfVd%Z|Y_|6etRVo`j z+nmFb7SwVK&`7t<*8v&hxIW;*h$}6Yuy&kZaJBLNcSyaX8CHFNO}fXZtuhRconJ9^ zs+O(eQaJ?=Ay^j6 z(ZS7kE*@O|`nudhN9G_{8!6`Pjr!-HAuNrU%|CJ>~%0!?l^tV6`wuSL-!MzX~ z*W?|T@WU;4PE^D0UZ-;I;<@ggG`7N|g%@DbNGR}V?l{F$NzLAByXyiq|Md)d$PC_= zr$b_ZTcTlmZ`t)n60Q;=F@_W)-$QUp!k^6G>FB-#JfajpbV$Sqzfvwy0+j$4)@(7h z4z==oaJYT538B_vOhl_YBcQ5RdDQ<<$7GpDQlsT` zpUK(L>z>jhA1UH)OcdE89%A*1HPAy=Tb*h3`y@jvpmWH{m}$?NakM7~Z3!{=X-5YH z+Gqd+4N2)hHOms&fp>W(yM2ZWh77|nsc49) zA^4A|p$a0=PRUMr@GCxYe4q7p5_JWVI}F`$`@p4?sG0h5xiM7m!%?z(_&4;IS5uBx zRI5|C=!?3BO2i_wl9XM~=rvgo{^KUFEfIcVYlIO*C-5VH-O2<_#F7M!%Hs5c2e>dM ze%<`=WihlS!CCvxli(MPPU?-DuJ6kmSXnp*jn1X%Pgc2W**@7j?{s%vh_yHwfhog| z)}4$nY2X7|+;Ill4)V?)>TbGjhHwt7=Xg6GP2;!eDYTuSUsRbpBoyt^V4g~7xzMm^ zx013z3=m+Qv@qf-o;~{?`yT<}1mZoRA9=7S*92jkXbhp5XfqyBpLbJee0SIq*E#NZ zULCYLpxx(xXgm2H*2S)KJg!JyECoh{{xZCMuP8I|b*n#qIxRsQeFb5LKI!z;1VewT zGZMBX9ivrt)_eqzboShW2X6K}{%3#&mwffXgH68u8Md_GMzi4qE4f&-DeQ+v-?p)r>s{}Xm+f) zkr9|<2nqLAN0)h{`4-&aug4xRm?nnnXWdn`?$f*sYFRC^t@A=LH}>q+Bv}U=hviu7 zuTNbTjIg?FkX}v8HdeDYEE+qj`MR_fR@o&&{&Eq1Z1AB(DyDLZ{BrQ}%0H(3$RV-1 zWO`;O4L6Lb_hHBWTaZ0EgY$Jc)tYw!m5(_>HHs);OC(;Bi$Xjtelc&38Ep61kf1!> zn)w`wQ_gcRn{CHET;?2!Bf%~I@0PNF;S{Wb8gB8&qF8ej7h0BZrt#tv_vfT{rXqIB z{@l~dqY>XTjWOez8>SY)!HeH~U!xjJ1Ggu=lXz|0M7U@v>(^00eGxC$HH6{EL+i;( z;2l&Pi{|g0pJMD|CB0oc3|m%PM}^0xD(ysBZ6qRr?i!Y zpCI`{rkPYrqM!-h5wQu7<0h255>bd27e|}NjR?*Mx%p46kV|Khcer$%Z!?`U?-6cf zNcK~DrbbCzQWQydh=<;Z`fIf3)JiU_lH|nkNNWCMnw!bfg4gs(^~H zr~g$-jAt_|F2|{qv~IM-GkVarA3~_-WS*Da5fThPb5kB?5?2pT3=0r6WHwrh1%DB^ zh(FU7THA>7rd!j4X$mDx3UI2{MzP5_bzP%5TWsE-{)iyh365GDg72Q|XiO2`@XoVhQCd!01D+j#A=NGEd#TCwQ+C z4Voj^yL|R1BeKLGN|ah11wz6V{+LK(WU3Gp`~6YXyvbgMNw={7O5a%U?P_J1?Uq-y z^7jEYheTK5z$$$ZHj;`Yr?g;cvo6lg2%FNi0qmN-wO|gRgWVH!K!%onwgvP8(4rhX zw_ufJQ+wv13ABQ>+_HR`DeeM!wcPUQyMnc1L00$%s)TyUu++hFP5-_v=U#wUP+<;vmL`eCpd zL4`1-`7dTONukN8F|0^9&VUuv>^^48i67kr*XGng9CZaV~=HZG8@3AMYbrXJ{8%qTl^dj(bSZ$M1y3G z0XTHsy?Mxic>Zl#xpFN0Ih2uO9_rHMNg~M1A|OT@Or*oPZ0gaxq}8ZJ`yy_w%u#4a z)t9$+LnP@2Ya@_0!G3{ZCNm!3*gR1S*IHz)%wEFD3e zur5PTv-IlKKrtSAX)w#DZO#eAR*ZeAkI{!Xk}#O|y7YQ;rvz^`KP1b|IhDm#LG@f4=7}L*RKcUblMW&)jx7FAX z6D+aEp>9A$GCfpwBM%(-_z9T5UUrb=wzM<<_?q{w6rL*hp@6yq4|}(`gs-&>L0bA9 z2lLM}Gd>RH*Nht3cj&x?l_lC)P$?T%sDD7{)oPys@)?1mG}lmd;gRN3aI7W%Ew?)- zm)y?K8&8D5+Nt4_8%V4s#q0JiSJ~j)DlrFRzD8qOkcC2#9F_*)(Sq|-=^l>7I2PJ5 z@B0;3c+>{lu?|Q`qg)F+AiKHt>1!b); z2HA9K3rSJ8obvCY+11K1?(#7kW(S-MWF093ZsDi^RxOlT()}DTOdu)@BP`68ja?Ke z6VjEHMa;xOGB2K))IZNXM(kEZ%=ab-@s#tGX)wVKtyxFp?4Z9tq05M{3yR0EwPAZ( zO+d@IvkvA3C9^@;3^=)aFl3Q*i?A)nq!9xFD=mvw+hvtG+>Fz!03HS+QG0=DM3Q_e zzd6*?Owwy<>I6#zWxDzJreV?SNQV_>jgl5<8YbnFfbexv|Do8zAb!t31i~)3#W_ZX ztXG*RmB@6Sv$p!l|Nhbomk~Dl*K}YQ6yaDD7qmdGewF08p-%-^%G!!l7n`q^Nni(H z>kpvHMx_eCy%xrV!xROJode4&qy{^bchd*@2-*_+iA>fgMk``sHV!&pby-Q;*VOR0 zfxo$kfh(_HiME@!$ig(m+po^f`v#&GzJ)6j&>$R@RL-{IN~C?Px)NqnA=Z#fScTnA z6SIV80BxrM-_VZvLEV)8K&6_}WK(=P(1m&YCL4^$-Ly0sDp@sy8(-2whqH#W#(-om zMujvjw;Sfz)BJl+DpRZ~U9ff{XPUL%f(s$yLl)=fM(2e6y78H;%oB(5-qE3}xm%aY zyJKKSA_H=}8?H=D1v?}&ugt$?f$Z^HhIBeh%mR|?)`qX6bq;j|v9b6h&})yVEG^wQ zsaGu>v5=6bMOX8c@QKd`-#7)di_Y|zN)&WHBt9Pcn%~VHKI@noRg3|3;gHZgY|!|Ij_pWyj*M$S~1!{Ok7VOf}T{JtVmU6P#4f z@}`--JEs%hbP%EBt8oA9`2PS{K&QV%+@b`Br~$4W=OmJr*-&9@FRKfsVM!mm%8xID zKI6sT?Zr2F1y!en1K{oimdR40XO~dhS)*{Z6+*DupFdkFQRnYr)08?zMAPMFXR9X_lSaWiUA_hW zR)N1H*vNLc#yzB!3h5{68v96)3hM6HmN$sn1gj{Abxh7fNh(2I&wC-5^*~ zF1Vk@5GQPC<<`j6dLdS8&0d%oWO#~NxiUc7b*$zDTdzUuTOBM>u75_Ve&dpFu$oBp zIEi%TNj4dQjc+nu#!j*6LJc@U+@VTeNZ%4|wD{I&r8!Y02u_!%Mt(2u}MzD!;Gr3zY(Q?V)|kdWk5X-7gg?|cDnnk34NAu35ey@JfPGTxi-#IG_KU%(Z0cx|mI{##lO!Y~ z?bl*%ZUMojPypjQ;?c|3+yQj~Iw;xWNOB&)!@x!8{E+R}g%8FoW46zYZ& zm-Jl`+}cp;mb%5Qu5*Y~T*oa=i+~cWE_{_~K>PQg_M^4X6>k?36SbiMbhHW1?bPz< z0utVAanuTnk(@LE=^S>v&P%_qQFPmlrW=a%2FG>Ko!JLieVm#^ytT17PX7*TEjYsQ zHJZ{Sd~;D^bho4R4SX}@9qWL;C@6inQ9+eghN?^ss&DoX^|@|TR!ETD7gYOvsM<0& z=zi;hWL~|CMVzI&D%BHZ@S--P#NiZ@J2+Jo1Hphe(JBX2loNibCc zhY}({rr*sqm8x&xM**r(3XX1`T^LI+Xcq}fAWb&}7hwT_lxs*y!%Wq8}T7aim zQe)aFRw=?~i(@WDZ!+g3twgN}$71rzs6EMQQRe_H=%6-Qy^r}@1Phep^7|Y`{MDSP z+%HdAD53EpUsMq1E=ZasA$B&#%Qb0&ra|(a(@0#3c(gVbjQYOT2jqDpoK5_xYtk~B zlH4bR5`VwIcdN~U`F+ypAZ1q_axzVP>vRJrpIYa8k}{ZozqTr*;^t_$NT^jN4v++p z7%WmRVzH9q0XA9aBFs}zh~{zqaT?sIu<%PYMMVv;7?NFxL^axRnnQ-?71dC>#1Wvb zJexCkdsE^B3~0ML6+^Nh+m6sQEvu50cj{G~l|dXSG9+O^bD8a(HXSP!nmIy~p;0}k z#*?dq1#s#8)YcQip2>*&}+#yJ?Heq!( za05z!6jcS!AW~prf5B@u(XEIuym}AE>r1sCG3C4*YWo(5g#Ix*a1dc}`4yR80MD z?_g+%XXMtqAyltxH^lUYA|jx&?paiK5$bm0^bd8%MBP=QH#UOiwx&hJMO5(%p^A%S zlJtUPUVRs))Invc&*v4U;uW!!>@9eOjglUqzEP0k8R=HFmxnrBGiQKZ8eU==<6WAk zgz(6;yZL}h(KqmFD1CrIuy3S^eMG@N3ZoDazmF38=pDpAYCQJg8u|QVKTFu)3gJUz zU>`Y#5}$ia39|?Yz%x#4y*lvKC!tRNad4r)P>P`IYfV&l0qpYP?}2C;Dq(w!8-avjyEclNkf7)fa%6XJZ+l4jVEM z*RU9#&i9QYy0CG#57pOwq!J7~CGZXvRwB6c3C|5FPXj*eJ1nrDsQOu#% zOry3u;kTt$?K>n>qQ-u$nI?AwtAM{5GyqZEWxj=JO?!tx1KIdo_6E+FzL4bC8-kElPzy?H4{l z74G`>^7*-W(UxcY&o1#*1xKiP+g5Mt~Z5 zJ4Vu0Y{ZR3O&Y@FdRliRdlBJ*mzz! zfi0+q_6YGDyTiCv@a2_#Y?Fsow7gq9q$Br5f%{5Xy=QlxCck&3r@<_0v?44`jalh) zIK{xk^}A0VVF&8glXQkglMK=V&s=n$Jag-U9uS?ZKEXDUG*5&0lHdIE)JEEDLiMXw z)}`twH_3^}TX&d>rjpaB0>`886Zejgf`aZ7Wn)IZ;hkbp`lR6&c%uT znVR+{n{phJiC8GG4PqAe-2ap(dnjI)j};16Cof zC#5n=ONkV=g(*+1mgiaKC!kv-yqgUPakBw?wc>79LY#4-h`FR%6MswuORbdYr&v9S zgKxm(Iv;5H%^@#+%<4A4>&%kb1rSkVbwX?lDX<^T3jQ=nCPQ}7)>$uvrU z)dxn{zOwE)aigJMOVb#I?--C8PHMwuz6L&jT^KhS@u z(#=;w*;xfiJJgId=*cY{H zTI!!8R42iwzF)gG=XTZ!R>Sq*G=s?eHq<#Sr*l&3@ap`X{(GOv$dg;*OnTxTRz)Pw z4b+ndJTb+W_k$rP?i4r3{Rh7T^S%44s<5)4phJw?vYaLap53o?=G@42X#(+EWU8bF z)vWQVq#|6l-Et_mHAEG2$|yr?{-pm|m9bK_X6DeXkqesFd3g%*-FT~IZ>;M~$kohvJCe!o{{3Ns`T+XZo4(@Ns zgEMck$rLdF^PtkYOmPo*!|Bn=)E@PSQZG0Q^+($dN4fD-Y`rdd=8jd5)<5%9?4o*o znqL3R6WY#cVXez_btzljS-)7`g^(LRmO2E-CW%pdds>kdToGg5dohvV z!I#0@gQT(M95x2Vf#1OVU-3jYj|+h(8kWITVHc=LnVt$g_(Fr+0=4Zf`n$Vc99F^= zU@xluTiXe1tTYMAF4}pt?(rJTawKY^0Xp z-30Mok=W%j#d{JM%5>{|aV#-zCd84%8%RAco@F+!-mSvP#69f!xG4`Qwt}eII3v^O z+S41ND%6ecXL040x$nlkxmA7a_Ec~x8ao+?YjHWYd-kzWa{s&f3GE$wu*HoP?mBzf zgm8G3yr8S>sYOsqOvJv^xREr_aGev;N*Up}MXvL1Tzrzyt%}DSv}5G~)AF z>=ZkT^aqUjNwyG1{c3S%qB^-yGmf*^30A8O;9#VGb3&z7<6WfY=(m&xO82AU6kbwL zmwT8a#=P}jW=0+sCmBp!5`q5SU?E{q61U_Lsg4vtuae=? z?rot%#S%sftWw8crR9nRnW>}NjUEyf`_XF^`#Db%-)Wv$B1DoWSnq=1kt@*GBy@A3 zq(7YU)pHt`pp7Is&ZHVj{2H3o`mK{SRQNT}_$N9AFS<``d+|PT3XgbAP?|-OK=D$n zwNj%@75GJU2=-&70NzA@H;ZGU=fM|T07%MXVE%OxaRb2fh_lBv;+B_jkj<>~=5&;_ z2CM1q*WQH$2Ux5QFIQ;q-c91;z&Eg?DDDs(@*}9~_t4+&fQ%(!Kv^WOycy-eEuZPf znOQ;eQ(;v4q(VHuXxg)^V%lNtL(j4ze4Y{6>cxS1 z__2ue>9Cs%0fK@)3@hNX9=GHM@GEXUbAT;?1<)l9X-@|*($h%>?_}S7r8%`h~^LIm%;|x|_Sxw3% zH^vcT84YYZ!AiI>ej#}cO&ePh)sN#=c!`!M5;kA_PP7`w=j7oP?Y(%6rAfn8Pa=c2 zOEN;uKCFGD(NIFAxb*f>XoSzQXxzZVCt^|Pv>a+yJ*qcY2HvQn7?R#`D{7ATg2jGs@WZErK z$6D&7I!eyyK6ahUX>hM~i*WVCOizD+*0ykq;ZL}ZAAY`cQ}{tPFCIgEMtWAicIO^e zFB7SxsA$hBnO5a+9jcqeo{6Edi{WyMRuRDvE(b302-S=lKar@iW|cV87q_3;PXyY2 z)a5vt5x4btRChUP1Mz5hQcF*Qt(B^<3I=k2?})B=%?~ zT=O8q3x~MhX$<0z)sjEf0DZ+mCh(~Kw02h>ha68FvW6^uCq&>UaQ;yfoi+IrJ?~g--Wr=s2}^Tj|*gboADzifF-fV}?q6kv!AF<_0pu zJ#18><^U^C-ouKMV1#xN(29{|CR@S(pw1(^+fB? zn7`5BowZkJ)&3iw(lc?d5EsVp5hFPMVVoD4Sl}+9+W+wH4^G2M7Z2kq+eEZd+uO&i z@`dbSx!qcXJ4M!RV*sbP_J_Uum@94{dNvPOHUEg)e&a*Ig^CRhpdsBmLuby2b>Eni z*dMH^tR(7GK*skDQVYd!vBpYDM%BnXxdX(M)yYuiP0s*3BRHZe9^WH1+gLc% z5Z&hu(L!&Cw5S@QZzS$xXX$}q;xxVPWA7i?@FalL)77A))ypY?7a1TY+wz29ADe~Y z5n~G8W#^5ZYtzUj3y<8F3y$1WX3QKQ?adC490qRspn=njMMG{pmwHrp@sOJ+*L#E8 z?3tJ&3u*WvXM89aD{nta5()pxuc5=NFJ6NcB*O~7&2>P$B<&pE&V!@x&onrCL$O=zItO4@bc?5pYhHvI zQ3H>dM0XbxBY7RS7|X)dRu^t}g?OiyD%gkLl4z9V4VUZVe7$UhC!4UlM9|&plnA>! z&I7JZ<2OLUHxKBYuxHH1VUeU`<5JyZ5>+Zl?+m4o_RKq#`D4rgNe;36c*e?c-69$a z7Z)hE1Mwm`;Y@{=6iiJBx5R%H{(QBV&kAo6NK-^{s*c9&{>%FCNQw5o;1YE1L-C~k zIA!I8qDzfs3PoR#qHrW(G>N<9b>bBBk~+a*z`3BclL7*jy>_>*N!M6BCMuIw*1=7Q z@k9zSE=_FEJz4X#Rwu5<;HtajS~BsvI$UC}hKW;_%TZb*@p&q`PCRqPj;)cQWAPAx zUxyW{!_wewFooXT=V$I`Us#je!)~Ygbkrb}Z;~NgK1+~jTou3yvxLF)^&riaB2Dn1 zQxrFO7UTY6x*>++Zm_Ji;ssY+3*UTB{XDc<+CE!RHU*gmC7vA z7JF%nRod^nWK9LtT68bk$ss~1zS*zdYUdlOcmGc{=gT55Q| zQnQWldK9($)zAYzc&=T$>Y{1lQI2Vt19KXtz?y~`kS{dpLXS(3V3qsOjvCq`OztBz z{Dz=s=0Q!o^5Ygu&Q{`iK+{Xz(s+sBYh@|77^-49F2$Cs7oaNKiim&qztXMT!$ylc zMzcxzbW8h~>6SHA^fAY+kWWNaq(~9-GFQ1(lzFt+oC;^CLWU}tp~|grt6&hWXmH&s zs?U`{``P(!kn?|Wh@kAqii*eMSnn4x1Ww@S4-(oyWSA`aj z6&-YA53tF?MXF_)>$C!!UCQjpd!p)Ib42rcrX^S+&hAxY5#hI05#I6Jx7A$V4=8Au-Ij#EHfwF$oy8Tj(ZD$1;LDRX|YOQIkx}j4?B2 z)yYgIX0qu_V%Y!ZckX?+UNw!7Z|3{{eA@TceebUCF6W$k&hOaluNm0R4b=JN@ZWL8 ze9HU~3i?(-KJ3+2Qs#%@QpEKmAwN=kq#q0Wk%%9O+LvNBR$*gJevEjZ+Uz#D0rhPx z%15$0L|wMFYMXo%DUTK9<1LiZf|Mta@+75h2$8>Vcy`vN?8glML&yr@eBZqNFI@t0qAc#j+5rK~ zBeprOrhZlX{oX*pyhe%CnY+QTtg&4?}e_N)4}WHDged ztl@S~rgm4O-L=V@WUXl}V!E|8f!5Zr4l!Gy>cOf(>uS)t8nmuv^kCVRucd9BUrf|O zTM4r%QR|hV?U~1YD4?`H=8NhO;BU%I(?v~HT@{SWt4854X@!B^;_}^M=^k++?K>3N zD^A4@v`2u{bkDMIzaB28DVzk=k*q~TXWfy4EM!}p)`Beh`^|itZ@}y~H)J=-TKd0k zNL&DpxjXA1C9M^kfV_p@b@q2%ma*sRJwIPjZe|!V|2IPVG+P+E?jX=;Sz6|R(+eUq zx_hXHL>{ONwR3^rPFbVzyu{n6cRmWoeQBM~)NAkzM*r`@bOHzI@(oy+Pu7nV<+sQM2KimM`@^%^ruAYxN9F*4 zEcoizoE`>rpP;jR??zt@sfV$zk@ks8*ida$pfTqtsz>L65nuy-LSvNp6et*$Sr%$2ZI|Kj*->_Z2 zm*pEsxX;QrVqORyXE0aapv0;Dq5}bq{*Q;mQFbs~^t866)X@s8^{GYq6CS`aM^7_C z$D>h;Y@^OFA7d7E{-^FbRu0U0(q|3!b<& zc3wi5TGua|%8{2!Z^@!O-A7t2Q?z2qWV)Y$ksk)~Ej#pNMem3EjUO3bBMUs6brXd{ zB|^tXhH;!Uj75Z4X9jNK`!&sNNBbPND1S-c=<9j*5k|#MX0pKG=WD_MKQz`^PpSMc zL?~9^H_XatCnC+qqcpCt9LLmfuyuJ-`ROr7)F*a}MU7(899i1{+wa*d-q&#FhS5pO z)9EZ}J93YhL1FEF+?(ikcoV(A&c}J??i*(Ka|cBdTMg#TzuVELj=k;i1@V9M^Oj_s zw`e3jtNfZtP_WpRn=^L<@b5}9p(vG^FJ_UI&E*M{^`mw!*VFL;fX{Eyy!lnXh*R*N z0~)O))~}IB2J|dLE2bLjupN2o5EC7CuNno~0jaedlhs)y)3272v1_KM5gK$IiH1#W6cW5$GeV}fGE`&!g}27fW*!!Y1o zRsJ{J2y4?u`9JI(5A_T&ye9iSzM9=`(#NFzgt1eK;trD}|D-1=G~ln$m!3255&zuJ z#32Bo<&Gh!Vc}O%e&BIRfi2MPq5OZL|91w#Cd2z+1512Cd#xMZEl#76xX?AOS>+dL z93m8c+|aGvfX1MH$zADTru{ww%)8yR3&}k+f%Y0|aF0rEtTO0ZR@g3#0W+=}saH zgkXNL$#Zzqj_I8dJQ{C3$}K9s43P!p>gDAFxi{d1+=U*Zt3`z(BWoF9sl?nzcxdUJ zL5}{(&$HVps;IYQlhES?|I_Bjp1uOhZUY}93H9%3w7D*q75l_=2^GoI_?mGJsMwFY z#^J53H<E)gYWU=*P>M$kMXT zE$0r^UFHx-@{~tu+8nrY3p=#J<_A*;3&xrRsB~2DX;Ea5R;FOxZ!-LnCpS zRUNy=FmUszmSc;m?JTD|o!vexPROR)yaz>VdQcpbO}l+<3v$n^ne?S{ S3HS2Nu z*{*-K7gfjOxNC&c%F6+z!11`Vh)3K$_(zSnf3r(eD}9}?)FLCSFa|nHoNiVsg^E}M zR97a{9yMgj{5=e^GT(qYynWJacEv0(1lSSZ%4}LY?psM4;Nv>W{-*+Jb8$F`iby#w zHl3Pqxx36XVJQtXnsA0TGe4Y$I2-MgN{($&vp1S5Uvq*g7NPK75k{S`0olCV$ zLG3GPJy&|EqB@R`NZ=Kxu)fqUg}ykZ)ucuQ-y*0DloR1sv|WUHlES%E;~ywQ`c}!y zO?FCNZt^rBc2l@q^6He=OTiAtz94z!%2fb_rf`wudStH@?2!670FzVrhUAGTe=T|S z%RfstlvWcYkDHo8A(f|&rI5-~$5V*%_DWX%QE>`U-df31%;^Z7Re{D`WS_%Z<*%+k_hO#c{zk1 z=b9g@Xp$dq;WQ- zcr(RMH1Y8ipJU>2iqF+?K{G&|louaRaB^P!k%Ck5;$3d2!Q%ZXx0De(^Wt@QXYilA z0Otz+lY*y;irAk=5T2SB-vmaF|K|OG|MB0vi<@Yk+{kS-Pp;-hnkRkSO7mnf{WvX8 z>}ye-4(}&Lbw*yCqNuPX|9UCEpDx{|BSy1GKIE4kXNtE^d9S+lOPW?f~?y2_e$l{M=s zYt~iPtfQG`9d+t;Bp2#+BrnkGNZz2=5r@b>SFa=DCV3sno6I`Onst;l>nLm1QP!-Z ztXW4{y^bg!uOoSpUPrP|uOq4_Z0ggL?3H>Q$rbR1Cr@=Y(o9wJ-Ho)8{9u90i3=S5 zwNU?BsGps!e^H>Ho|6|xBZ0zcoaW;IIjN%97f8|5BIrakDQHr(M9hZ|%Lwh>HL?OO zs)T!m)hVhq3PbJ|b*`@~wA?-|8N_W8t^VUHtu^>UqoWE2bPUt>RE8~~y&$IQh~Lz} z(W00~kZ78Fm92ZaIm1C?JxpP0&U3dQ=Rq0_&Dc`2(NwT9S+rd3#|p23F={6od-uKL5GklnKy9#~E=E~^34J!G z4<&bt^J!AAu;lEo3`_Qmr`>O|5EMp^k7ij2n%CpirmaTnJm0Z^6+{x5HAMU}-zC8g zr?4o09zQdZzw^8*%l}cRS4)nq!5G?;V#szJ;S+-K!ys9yd;|GXRU1G2rufD zsaki$^o?}{lIsMGL)jH|Efl*ZLqZ&l?-fm0y6)e*{$9M*wDU{!Sy`p(sctmY=noQ8 zwj!HwTiNyAdU07IloI&D7hRsTgy(k$JL))mZ6 zg6Ru!Sz1f?TD0hLD*qf7k#wG=TUv$*2^bD z(q2Sq2jsiOS$K$iTgJ@y56=0A$hUROd|z-D3*W3;=Sz;t_igSqw(ik+Z*`Vf@sv|C z2DEu`QjSsOZO+FI(bVV0%=bm-q7SdGYRqi6JC}TTw&a-E?r<))6qn9c7ii%#2jq!T zo;#h(OrAEACo`&N&+}l`RlG1}op-rkGWlnlC3!>ubO4fuoL^KbEpxOj_my2I)kLU} zFT1{sZMlJqItPW=oQE>2^mP-%$#4ofiD4onWL+}MXKFG`48k=*>7G-`NTQJLeZ?Lz z*O)VJQ9y0a@s&!9klTSY)zMP?)#kW4u_)%yffj`h_XjN$X+eaYe2=lzSii1I>(|ki z;eOTD-!6RD;m}_hW65q3Fb2Jvk9ZqJw zNM^j!T_xK#!if5Q|E}X2ZJQcN(bxtrNP-)Xvq#L7jWAYOWt%_4C{3dDDldQy4j-VZ zAZPfv-8hU_yZ4xEU#H@#h2Yu2hjAk8Y*s5^iGDiRqG&q7I(|jNaWh}*~ZB*%tGI*`f@4C;9(|I&b5CJsXadOmK_b7$$Ch2((nGQ!S3}DBH*KDUPwyCP;=*auZQKa!a|A zzJpi7qo+r%tdiV;m0Wj_TKM)Q#aAz>qN>hov1_%VU4x_BBx2z`Vp3pmkM;-mn4O{y zJH-9|@|F!yr=m>nrP5G`Pp4QTwrmtLgN%V3O__d$@&Z>n(X{`YTbkzCgVl!aIkczt z4l_DTJFR*apv}y%IHDc&kCQGvPy5fE(sN5S>2juW{UZQY*W;)kW_mfttg;-sWwKW}kG!0O{-GgQsIYX0nE}&KpF|N35C@4r@>siG{{ax7^ zhAZa**LWKioyJjY6JGChGYl-{rZPo!Lt2AO(RQkCCinn2Tz7*~q$Oha-(h6=fK5*98t_jL5k_;z;q4ep-;7cattL@;4^k zTtO{PqLs4?>Lx_w(t^5~Bi9$y=Q(nDLEYk|0Nwa((hXW*)Aw8$ahs^-k}q4|c`;-q z3ynx3LTn5S<$bTu6`sX6(>5d(iOSw39Z`BU1YIE?Zmo)K_$}|l_V~c(h`=*eZ$|`c zmv}a~X-PX7#{P*qSf`UM&%mW7we5w*rFnWWZ7xw*gG6BePj$xOkEdVE#zuz;qJ1I4 zd5x#RaF-hoWbn09myDH}ImG~u`SfC+YKp@(7|13n8e|jv--zd!bcz>cJuIDS@rR{K zrM>HR5#KLXmi*4&YVf=x&OO>vuI!2MpVXhmQl$9kY&uUcSqPJy9_#J;ed0*Injc4> zd&K>wnKk#(tN{$q3Nu-+3{KVv{IFuSMv@^+*9cABFC-(Gf;yj`xCM0q&CUpBC$Y8{ z^w0tuVhpWi#E`EMQK*BaGWZb0clQ8;hBk&<`wghy#)KObh`M{lMs{jTtsK2~*Dqm6_Gk~2TzrYaFqoejE=|1Fs%Ln}rf`cC-58!C76@{IIQgwE-x(%AhW=SlWGp-NdfbpQEPVwB=!sUC095FE>AB zoyhihEwtj#-Ym|cv}P~h!3Iur(itebq~YiI6kyQtHAksG4I)+@jqDMP5oi8f8uD0^ zqTSlvKvPIJu)|=u98t!N0bmSpW7<1_SX)%LAHb1qa33*S-^UG7O3sItV5;Zg0nx?4 zf_+_d{xX`RWLKzOD%0!sLHb2hF;c8B!dG4F#kjQd&Nfp66Zj{R6;WZ9Zec}D$f+Qr zGI+B37>h&lV@wd>B)Nj$$n*x^spNOK8KoDiw9Iy~=gVy8n~kocn)f{45p4DHM0LOs zYzKT#R+syV9sNqBmxyCKPgIp#rDbro&!gSyhsKCF@Q)9fN38_tT7v?#m`9loO0KCO zrn4}yAZCp6IvaG!ERLe1hH0Pa1%0DAqadbfx`o&#+G6eqilF2^agH%8aUZop&Et)O z!5lXrY#VSCH6Yx}<>}ANMQWcz&HiEem}@-8i7U)$e>NkiE-<~xX+>4tA>$Z>`egjZ z9I6B9vJS+A+$-iqoDFFIV|D;fG`fB>{Q-oN;gFXMm8w39f9blX!5PpDR`bjFi~9-p zNmE#h*;Xd#yKg-J!k@KaZ`m%kQwm-dy`>1f-luiM@3NcbL0Vb;X z!GPad?Y}RS2*K(O9Q-6efF^b6>TbrwhN2ztP`c9{5sf-BIT2x{{E7xS08FCVM16U!bSC!yolzqMfFv17?X#H*-F#6 z?xMXl046({ps5tkBJp)$+2 zG65HGt)hC5NJN}B=g@jNMrzN-m6MWgbE3YsYCq0jzQ)=h*|h_G&dv67emWotVnm_t z^q}|%gCz`!?i2`tuDnPx##FisWI(D!26!n)FACPSVQ<&C!=v!5^0^ns<<2A-t4Kf& zwb=@B78%;0hkbPoiB?SY1_)o7o@pw_5@hBEH*GKCJ`ZZ4(5;j6%1+&xG2B}dlcTtX zrwpLw9T_@q<4>y9pg*>VM?2kZ>K+JUHmk4m+AFGiJHvP>?LH$OTjC_c=XFLX`Zz`1 z^E#t>(X~b0*BRTU?$_D+*=7paDndF(#d%9y&KA+Dge3McMhzBgbJl^t!k3$4Vm zQL1d9qMvo2Geuu&zQbH|V^vM_dcgn4*@En$>YAA(@Pe-8OH=_qd$!D_e2oY-l#cvL zNbsMx6v+WwPo2N9y>Re%rugr?c<^_s{dZn+U$)eb-sV?&CFuTcY?Ke+2PMv>FkXc2 z4G!IDp6_*ss7nBFUYl8#UhWykI}k64yBCk9yrAMN7Wl?wMRA6fYaT#&GRE7Paxyh= zho$ajnH_SvPu(?w45y6Q4=3Uqu9b8?9()^12>hlo%; zOsUGHhq~zj5<14|J9fJGH>)#DftULooLR-{ezqkhB5KgAd%^i{S04uVO2kzvut!kYTi}MT!sZ-LN`VB`c zMy}a^b*{Yd(gTciqg}3;R}+3h>CLjsNZ<{WNrvm2cZzy74)=fGJIMK;@(+Iy#`gS| z?mt>7(+VSHx^UD@N$LSnpGuc(iGm(gh}2QTd>?o*!$GO_Y`dy)?rIXa9T~RHQFjLE zEw`rL*&|w^{_}nJb-O$?|8u%&!qyLOLQe0eJwP5;Bfh^AYRqM(F~Rj5q6%wqxAX(| zpG;mhI7%(+5MgTBesQ!# zNqdhzsl#z7_61XolTDah(yIAqOS*YR#E6O0%6vQRYTCO*@Q%b>uDxMD`R4!C0MNI8 z@c&={+C~k49HaYkROhApLrbJzZRYI-f91T*AJ)7*$eexCF0<1_z$XtJpOo!=*8+|B zaQD9-xxOc_mksAOaCm*9g>W*_+_~-G=NKC)w|!VN=Mv#oSc>sG$q=Pd!jcYmA)_4q zOpgQzs)zD?JJthcOg;2hj!RO+Xk1`f1^%yLm#Et*s$zS@6pYiq{@=~DZ~cFpYj0T! zpv{mmnE?6qaEbg9g#*qakZ(WsWgp^vL*i(ZSSJO@1LDh^G)LM!C&Q68&!J-pv00<$ zj&J)Flw)BA{lxACi_Bio!}hEr0Nup3gFK0SGy_;1(ff!|&9kYId3N6jyLaZ-=vmq1 zoYF|0_^F-oZN_Q<)|x-=_EMYG2E?+QuOM>=2A;s_bAA?$6y{*gNnD zbKxI#J3_Ec@Uk!{Dym1aX$~JX;bR`#+s(w~!Q*+6rEl{Dz)mVD@+2lol9J>O`t?+T z*is5NC1~;&MqMHwE$d)R`nFGu$gH_G`2a%8Fc|%?raAgI}Ab* zi}T+PyTscMv&45S>6tXfW>p@5q>E}Zt&T1J1b@f<4>Q5;q}QNTe44LJG9s!VHc1h6 z8uq=a69qg|NAJU=o<`h%U76qXM0$;k1l*Yqu|Y0%pqZs6={2;#6QNXwMZZ7ys2Ux8 z!<4`R)S!x}#^BTnV^Na`xVwIaBjJYduQ(Om%)VYOP}3zEr(@f;o!m($9ox2TTOHfBZQHhO+fJt6>zz4sWBqX)!u7W?W$G6KQ+n7=aTO4U}E-#ACaA|VF|plB`O}#Bt@FVyQt(6s|4mMi57jNT&T1KkODR>B zwE1bOEl!$;ndSG#40FoLdw-a4Dgvi!C(^y!LdZ~A1>VR2!{n0$ALmF^VWT>hlA$=t zSc3#i*SLn2SU5c@hD;i0`N9*=rX{rv_RFytkx)`FcC%K2SPg{^55Vf+-hmOLp$yw&a zXT0kPuPT7ptv!_?%a(Jcc6POR@>?Xg(4R$IC_?NDx;IEvE1>J)7=w?5zq$(x54faM zb?w3}O+qohUS7UJkzeP+#$IF5X&esw5hisFKh>EdK0@*wq^f6Ur*F%7j_2M9Q6A*2 z|C~4ZGvFg{DGwnz87<7B8h&B!wN{$%-LZt^O4{T$mmL;OW6BXX#OGxg#Ni17=KriaEOS9x;Y7XfK!%W0ktzz>v z4;K@Q*axpX0gQ(oR2Efu&669}3WwUuZ|_!zoK%rYK{7fDEs0&~cVE805n@q9+wTe9 zKYWfUu1Orpw2L=7Y3W8WZO;dM96XpG70Je|h3tyvdqSlV-loT4A;PKHh2Mx(9e&*0 z<-bvcN}0h}=YX2(B!&2ye%8i$37A1PXeQ8rdLarWpn=%Zs|UWKxM2tyO<<`4<$ra9 zm`TFXrN`ZJ9gRqO8Lc^F0M~dv$pM^U?GYL)jCZ4~13ICs_fQDdbr9Ntl-8kXH9=BMX(XomGo4T@VvN`%+sQhhPT_U~AZ+au& z(^MgvL%*!#DkWY9B#7%X8Q2BY56X?yFiFX|dJtOud?y}Ra$oqJXPtWB9URNAw5srl zf<3LzNb)~=2l=UjISe_f>be!;vr;~^IRQ(e?(f0Yoa`^i=&(8<|86{^Kd=Hlfea! z^#GJ+LpHrV)sDmpD`E(CM8!oFLnI%CG-v~T$vx^CUNa{{L2LP!IkLX*gcN3w^Vq1E z9xD-Rbm_ng2@brv4pU&nIE{FxZ-3FNJaS7S@!)amlpM-55By!Zxb{2SR3}XjU*H@M zWkz>4yHto@(3{YB_FW$06fWKrllPX*j(BPhUW~{tN=y=Wz$*9PLB+hN1Fp@}) zuWM0Y+^G8jyEUc`d4IN|j7%1y$nN1&^psc9V-`-@ei# z$}Q@H>XUvKjq)=)wEQeRP*qWibM3D=3VXU;CVbJ}>rnwY>nqbe5ej?;qfLJ))Bw4Y z6Xo&Q?hmk>Bq$g(5YVq*Km#OV6882s-9*4ZK-3^WK=eRBK(-F149135=5~(8434f& z42I5*PPWzzicaoU#zsoUZcc)>HcrMiPV|OW`i_pyaT78FjEKTDM6CQUaI-5EoH+G* zYs$*(am32`q26L8M9TO{SLpASw68#X(E1d~{1z8eNj0X^fX}aoH}G9x5)4ob!C;?G zHNZkhdiRjAggiM~5-H}jyHXfyF6qp2Jjh#hfDwtZGT6tQ;ZIOrLVVp&<9cHFUg8H) zV8s+Ajrhu59zoT1`SgzKjBz)!930Ot96F}7s{iB(;4x$l|lXB(M(R#_+F>$f-iX&zr{i*kP zsmsi?tsX`muVByN$l~fSoyPOBoo4%+&li|6MjCv;{!vdzm_g(~4^we$Q-QHM9|dK8 zGujZ@wNRW&tYTD8Vu;mbzZFJ1UHxpDs^o~%s%7MjA;!${`DUd8UXwmm}VFPxqveYhkf^DFR z6X4ss)B9-ry_M** zgcEMl6i;`yl9Q&#eILXPRI$^(HpUxxX`6}NWP^v@V$zb_SzRN89WbjnM7RFED`b-T z8R8MFd@2^y#Q4G+1Kl8B^`7aX2M&=$+RzUN*kg<=nWvNVfX>H#iPJ?bMq2IH{-$Hk zrp_eO%$BcNE3UDH-bkuSjn&#XvfOO87z{^_$j9XVCow>rES|f3U!gv)Lfx$E8fkbQ zkX$nQ6@chb2=_NHV)LHq$b*3fcdv=Z;1}^;pJRR@m|VTesfb$ksw<5`Lsov}=IvWp zkpli)ZYqlMhW)m}j~vC=MR6hYV^Ir}M2rGMmyoyKSYapkqn5B4>@Ibc4vcA>o$!>D zw-D(A6dpw^!GQB7=g=Zz0fAeBIpI7%>pifg!@AHV4Y%Y%$%HiJh3|0+dcGpupm}K> zc_TUUe3p4`gotZp4&hjWU4cM7Q|--ap_olPO3@IJhYA&S`qSULU5fuR;;kVFp#30# zfJoqgfH?mDMErlpdy?vhBgzWeceM^p)rwx^lAZw%A}+BJYS6koN(O)Ar5;(mG%YZb zc?8Cp#I{iK;#N~sLNfb}>u4Z=5Fz9*dkmj>)G2G0jPJFS={tc_@s9|z_>B74J&F{d zz;UMSg=dHB#r|6R=VGJh3zVOOn);atua_Oy*gkT|9vq?K)*v$qg%)34{8Oz@R_PO! zLS2e-w(TJ?TqF~gFp$jJhrUL8*c@3c85#kOG`TE%RLOjxVPKh1 zIAlGDbd&$1nS))WFDc*X7u{fd8oRW5qo^a7WxcG@c@612}%e>Mp?NxKY2`WG~qjDE~9a!;L%3ONbIYHGV ztJIQ3hLp2By-4Yu1xvk|4Yxl6`+c@x4=5t=6zOz3)_l##fh(vJm zITHZRU7fZbhrHz9TaCk+phUJ(h*iMo!K52|QK$sDrAxg&FwZVdvAT}Zrsm+pv8q%e z12<^1yIY-Ib5I(uPLh8X^lXJGy_*b22$CVirJNoo`7(ShXK;`axL`iputvt=D>Hsc zMSW_K4H~RWf9kk0(*ThV7&3pK(x%wq&heCBfH`E>Ifnyw;;8Ja1PCRj4qNh-W!%lP z5@5KRA!t8I$DFfxH1d~_q^B2vr{+g=yVyS9@mirm2Os&JRofT^2M8E`6)(Db*Pt}} zoW3u2#b2}nE9M+&F06%0qv{U{z-luLGLwSK*EiOZNpi0tuc+*wm%ymaNM=9VlUT|^ zvnt-mjj`fW=yur+a?+h0vT9y9dHk7dEo?G%30NLL>)b= z36icNDut0L5o`-S7oiOdB58E7uObz%H4;`j_9TGZqzwFtWuHlV;M{%dSigu;dZ?K1 zpjpLv0QWB3V(Npw`37k8BdG+ien{^TW(lv%7gNmL(p@7pUO?k-E_Z$L;0Bcwmg%)F zm+1-hl?IUf0pjTvC^>zF*WA?x23hYez6bLCUW7M}h8_~Vifev$hi#F^?`U7A4t?}! zZ6i6p(vaA~IovF`f-miYw&;N{qjIJ0w4*pH^B{6kL_FtSChl%B&=pScB1O)=3{ZBwm*yaCW%u(@EG8_D;K{Ief zL_|j|jo=akzM#L{`*{Vl z?wuZNC2_fdH)Q!FU~)A3|DtWNWIS_iC#s0xho4f9&HINzRrtD*;qEF3yCW8JPpw*! zCwS8lW9fv`9@5LqGCFW7@KJGF@#SMug&89LhzcLh9n=iX_e6D6kyrsN52y(e<)2CP zd!A&yKgGA#8QGFo%x=8M69-t6;aKaodOaI|ZTwjOZnppY;iGjovXPk*4kgIjdH5$N#Tk$XRHbOZX|?*1>< zP6U%o(Dje8lfwf6G5ja)mNIwzmm(QE{7>$p0_BRLjQU-?;glQz4o*T0E|Et^Mog^1 z@9$@2Euhc~Lcd^wU0f(GR*X{}TG~p}Y8xtnW-ovu8ESx*S zb=;nGJ@IyRzu9_W^Zj5S?i~u8R(Y0@#p%_M1 zyJr|^R{fI5SPT(v!h;nc<0_c;1D%?^`QXxFO01=>gONrW2*n?hxkwyON?H-%13a25 zPv`axV`3Q|TC#LCTSU2}iJ_+;iCr21z6GZtd`Eh$L4;3IV;_NHj>DUEt% z%a}9jX1$8g6{XJT0+93Jch|?lDIr%o?cYVwr*X$ZgQvK5^GmO>1IPSQF^%V6#lnZE zBTAkA9N?^4%3iKT)Z6_V-@-oOvufE7$6 zl1kAIs?LOgnp`9)hCawwxgk-`>V3mZM8+C^@8w}GE_AK16XRE~obuO5mdwH2@ELoh zA&gJXp%?~xjUm-njUf&zpNNBR$*pqv6>IaGd7kRVMI@=3SeDo5bi2xs>A3kmJdbf} zyz9`~A7txe6K^2`ub9YiTA7cO=_~a74a6l9nP>vW!<>POe>DmkGz)h;KH)u%% zTt8qbc#&?8_xTUhK7+E<(+#FgF{Y0BsswXZJ}@rkI=sIP)rJ_nMtklLK!U+1pYPz! zskWweRYQmF`hS9Vlt25Z_gciai`#Wj9y=<;szAkg_wP*5mFSTH39lf3qd?9_CN>Ah z4KK3z6aZr%*oi8Bo7PmwA*hIt(g1%xjWHTSC+(Gs;u1hmz7(OEN}_n3JPJtpuyAN` zO=-d#%BYUkAtCe>VY_hs%q@gFil^V4Z9u@Jq=Hf>klDPyI z(?j@Vp=a_xQMPWfT36aFotK!U`8!i|yFJ9%j|VUy{|1zUtiBagRDXl*MGhg>!s&Z< zpJIOHC!S@M&g7mZ;^o4~P0zRT4^v}%njMZ_kw*2HnX$qzvt!t$StT7G-aV28t*OoFPjHM-LUUC%?K{oyRtRo5xrzMAj{yAh?%hO&_GkBl| zCF7E+3-gbMEjF?5im)9uSkH|eGi*O_HZ&}|Li?3?Y?pALoZc5nYvXb^No!+&S4ms0 z1>lL`qR-9{Zi+ZDmOt2kQ>V4Fw?=oSubwg3%auzJ4OASjB+>Ux zJH1fO^Gb0uk%u257vicg?djoHWc^ubZ6M0lzOYJ5>}#jJ#v7FRef(ZG84O_OoQ$G( zL}B6ZoFtD>_ZqesQ1Orv9wMvEl5coKj-JfaRMuA3)P(u`e*yo0ApsI+D`eU~;E(uM z!(sVPkU-AC+{Q`P&dJ=?#__+=z&cJ+c0&#^Xa;cBjjyC`Lr6nkw3;x+Cdw3&Ykz`#|MJni(Q0Wt#U7E zGdNFxb{|cjWgkvoww|us*dAd>%U`bo4@@y1|rtJ6XP5z+ljm9_v4}xEcCFGG+ z&{s*&!Q3%|fAZXyOLbHQ|$J!$+{H% z{dmPh)X?H9jQN{Q`$`WF2Yx)&g^?l^gH~^@FJ4c*mRJp~a}axPRw%oI$NzU|6V8rC z3rl3xS%>&xHSKJy+mrv(+mHQv3GI`MtZStZ7Nx8sDz#oj&EQ@|=3$JqKK5UX%VEX= zAD6+D*8NPa1R-fBK4tjKQ-$)0vg*S2bm|k?L?i2C@(#mn>B<}G9)7g;kyJtw%o?L> zCZbVjhk(vp!mWK-(wX1?C+5pnIY96H^D^5%+-LevJgs1CV`S`L?C`%TbV*8@vMBP1 z-!dJ>R}Roe#267E8jYw$TBvA-Sd`KU~NCB~kV5fjOmHbLDVC|)WDFx~M$*aVTX z>90ARljbyLjQP?o%nwsXlU`Yl-CmwQz`ca%%1&T&ell=ToH$?uTyXME;#Zxh7=@wc zr0t-TKM<^ga|L_E^x~#9tSAbP;&V0nK!Mcs6_^V4NqOU%Y+Ggof-_dzV`P8z({QTA zS~GO|DO+`Xc;oKElSk=8>1=gY)%Sjx{+$<*-gq1?^|}0g-;RNmXv9Z@`6<&!j+G!s zd2lwI(AZs<%bF3eMuJ<=+Q$@} zf!o43%|s-OoIqX7X>$-AZXS(l!g+0wOcLdZ>)1MT2lPPAj0@u?M*8IlGv@=5U2nZd zO1I@4G=ZqIkaG?t+2R=1cEqOd2Ky8;>($phaS#H##SEQ%9j2tRp6)4^g}&?7F6zTW zt?0yCH!Ww1iYvOh9neNUUu>La6ZVZ2oNK&JoW`)KlwEWRFqeg4X_4@|u`fry8=ds1 zC?s$iK@2TYxJS4IG*gtPqih$R*{FHM6^A+#q!}(uDuiZ~g3X~`jr^5kY1eFwDx=e9 zWF0YysaMx%vq*4D{AGK$5vOxSRocgvDIMfzj!!XeQ9> zyzwn%keRQg{*P%QrOcOqE{U+2rjcxaei!3IvsGBBFyz*^Nc%8FU%!6alGA|qV^eO@ ztYRTs932wfb_1mDC*3Dck*gn3K(oZ8DP`=Hk#Zfw!pgEZ zogK-)6T?u#lAH5F{Iw#%}@Ab16H|4h4R#R(=X%lW+c z{9g>YbnFsy=HI&r^)Dgf{Qq=Yq|9xM#cfP%|0h+B7q?yDM;-Y&ppD*wasq}nhLIzL z(MtvmA*SaOMy)H{2Pz3;vB%rAH|j|AsgzI?!{v0?6$HMC+C;&qz5KD=?ks7Ud_AeL z-2n#lkq{ua04nt|j@T#gk~?U($n~D zIVKMHMa}DJo#b$n1Trg*e2~!uQj65PNp{Ov!>0V%i}-n%SUPTFD6O<`uJ>yx6W*AP zd>Lk`oF?a#rr-FV+DlwoYaBYT6y(Z^6PPvQw|tUQ3%%%PPZ74ESO=_%sq{FQJgkn> z{BUs!9vGE{^QgYfe`>Nx%5(6*67D&d8hF3jyei6?v`#ik)Ll58T@~g}8I~Mmr4^-3 zN$jyyN!vL|?9nTbEju+#*YvnT9*{9=sqB$bNSkZV_VaoVergzM+vReUVyRIeYl>m; z=Gx%Cxez>@GUC5MV)!gDKIKM|Et|S@91F;uZN)*==3$dHmt6?mJmxFdA+_=A*gm1{ z4F*=XDz>w;`n%?E8egAew}yIelF%Vf3sytM>R{x9&1=(hJgWds7+u=o$tsQ>k({&#FFd3x$C z4JG}g-Mo0-yzq6qpJW_POa=K7?j8F>w4Dkg{tB(*U(5}SNssk}n&US^RytiiXMe70 zvLT4DMgz58r?;+?3{WX*Ue2xFx^}T_nXz8sv$AZtx^C`(_P>0aoP0SB{>x9$^V`Sl zm?wtCbUKsEaXO8~Q5W!3R3OwiYSlQjBYX;N&q&Ni!AF3kix?6MK+pRf$`C9b+9T?K zIffXoPbwM$iXql|Fhu?4{nHYm8v6kip98Wt6Z^pxx)&U{uPpZsL)sHLi!f9Z z>j~GU8#3q%w9C=_YAyB&pZP9J`ePjN6D?QpN*uofx{omW6+`!pA*L&O4-tPLCIhrX zIm{cjUsj8W!eBQ|m|aX1iwU3_fW=8h;zt&mJH{9J)n>OEiYZS%YL{v)0@MazJtU#x zR|qW}We=A|79z(IgAQYqf|^;T6y6s(&6N*szYmqycM~%q49oK*gNQ6}Hc8D!>EX9Q zkEbSW@YjpB&V(#&n^ZEyx0n{S@fj>-N6#e!7Gmps6rqdfM!f)TL0stk_et)~PAixR z=!;~+j-~yx6XAvyH3TF>+yXW>Bt|a~|7SpfuoX2mC2z!G6!UPg6&8-Bwu9dChLufLv8>Jsf2V;Yu`V6lq|4)u^+d z>qe{-$XgZB0sHP%ebg1wdvzVCI0pso`mjvsmS*fDzmbQa2my4*pW!QhJ4p6PnpGFw zzlmE=UL8!s_=nr!hc*4f8*0*+wk+3w?p;V-N!lQ*eBq$nA>W@#+E&=NsVEz(9Yu@H zN0w~_WEP_F(z1uEo42>6YI60s9X(AwMP@IZw|MbjSgy3QcsyBPJa0}lSLYU*9W_lo zZ~B*zjmmPxY=9SC!x&lX0NZB8V?5SjJZEdW{;`2zs>UcuQK1uM&RW)3XBjO^@^=q|~ zQ26J@t5bdv@vv+=IrLVS>qp0Sh^#(Z@SXU+ed7mgbu&#(h8Pzdh_!WhQ*v)*Q(Yce zcH5<-sIu$y)k<@WrgyZNER7Fy z*r!->RGPddU8FDDsP&i(h%{mYUW(x=oW~4wblt5dhKnaS9#}P&E5R{iFxFcglqZId zD@eI$Qj0RG)!moq$_Zu!LAU4KhQ=*{`YS|CJU3(nV9IC2-_y}+Bh{m}&R}bwnwoZ+ zyRA=#o9VQ*;C0s93L0$xshchA&U^mgKEDTCxN%#bJ-$oHLK@aob=RF~C3lvEnHy1z zqq{ek8p(#T2|sbU8dbNnI5#zvpB^xQE@_PzB^q5|cW}Ea)?AJ?G98r9C8tuX@-Xz&EwFE!;@q@EeXQcuMtxWxfOcYWGkaI8d$4H7BkJ(! z*koKJlIJLHHSSir5S90-wmj8=O`gy%m{}9pm01IN1BSG{hpa4z20X*$YxijnMQ%-v zRZT~tujXyU_6)_pk!tE}rjkmB&&^v)vFzD_l643glKqkt^|B~I65_I}SG+U7jhKdt z?<_D8CTDUM$&1HoCRUtPna8KAI>=CLfPA5yKr7PG*|RHaug;5c(mt^3Hv89U!7IM1 zE;$~{C7_~+hF!fGSanfi>}{4wZU2}Gs7m!XE4Q#XSRtQ>dAf1iXYL{{Dotd$%FH&0 zt`0+59|$}vyX)TPuJ=NEFn7iJiP;XnWPmMY1zOx^(VC%jtn5giNEiHDS{k2jz$58% zLpr7kGkO~CK{4^wd49GSg6?&Cv>g`D>sszp66PY)QknQHLsZmNB5p1hj`%`9OGlRm zFC`pCb%-irWPr0N(9^}#a?=`Wn7JVIv}r{+%jK<>VXc+Cy_TlxUVNptRpkXJa-rx zTZ_w-fZ~z0ZFkCAh`#&va&m_m7Wn;v zdmz6EOSs0s96gHR9^IDJHG{TDcgHQOPw1o+<1 zKCy#)IEAU*wRmo=j#anKvzIBGsXO4XB4FIsx}(clAEO2cWo00s$wJjs6+7am@{Vo9 zZ5q=)(r`FLT{<&xx*s>&H4-Bd1Mh$!G6nbGLhKs}re4{JbC@I2*C1zr>3i+9*zgm^ z>@~S{i%EyQdrHR7@RNq~4Ucq(t@|Xj*1gE;fvH)$>`M} zrtRG{tP!RW6fqH^d3Fa1fmNyl$Y>^V3A}{f#+I-STtz~R!$H>AR54mVuv!!oA`By1 z^4i;Bw-gdmu~7LmeF!F!VMsid@q={uaCeh#L-bs=U#-OG^bMF4`{IrnxsG@ZV7o;C zlm)g4+=k6cJ1K-AEHQ)93L*MvBl384lO)6G;msYQcRa`FvFzi(XfBMy>48ZGBq|-i zgV*y}F&aagZED@I*GMHh2B!&#(u!ZMM%@V01!I=SBAw6{_$s44WghhHDPLW(cLw!0 zBq|0pbq4LDW=s9#!+2dMpXGfR1AEjC3~dDN1F>VBAvuwI{sdD>Du$y;0!eu-aSHzk zY`83sN%$KvJqk^1rM~r5s-(bvkytX3dbIOLBF>?^2&AA#btoDGlqYTD0l&fA>=|=8^Pd%5Y zM|EMyG*V|S{lH%-4gJ2`sE1k2f{23_7h7Eg-cwm=F6>op6#=_A0Sxm8FjiL<7yOi# zlF=Pn&d)4*xm^`Fko#fvg{rEv@xO@yO!$CAu%~jhk;gdgA0Q*!mzV8N^L|h+Xei*r zsx4kD-*Wf+TPB_C>tD=1GPTX;3qR`9G|0`iFEZIN-aP$&Q<~syoQ2YLKbz;nWiQ4X z(^*)vDyqC~?hXi7Y;;4T4k)OyL9B}_SC$)XrZJswBdkpon`Ni58(a%53Xrw-K0Pvc zu&N&dSB$O4A@!ez<7)+>5|-3a`b6Zcya&c|;>eXPiO^uIwp-BmPH@edlEo20O%0xq zx6%!JMZ)^YP}_3`E#U%{=jS#(bddh)a7%|W&~2r$Gxg`iWh&R3l?Vab+9g^4)()YJ zYw~ooJHTdC!M{xY^};Hj0M4>$at6SEjXPi^b_f(3C)MDQKThNEF2-p;Pp(0|IR<)2 zz;fqB*4=&b%P;FNa9TdvXj%61z4pUdchWB;CvS+Bo9GS}@NB55c@dNy$foWa(YYoA z{jD$L)6IjbD2ZS=ekW3CinQr7tGckURm=xq%r!LJKO6~Uz}W>-(VfMp8hTG!dH560-&&!C*%CBAeTW6CgMrqZwe1YJ6=3D zw@-le?~6Y@3TPU;7h9*{l3gXs*_h32)8~Q|(f11TBf`mRD7U>G8Ll4F&s}TFT4DDQ zVTkm0G|`~SY`wnX>G0&4n>vx}F5lih+o7EPZMnF-Q~NM8rZgT~XdB)r$@7KT#&`AF zm87w%9KWIe^X$&DEXA%>vO?h6-W4lyCezR}H%)J;E0V0@{A_KT0J0Tr01wlr48>gS z-kdp=_1?hXp2}2L)=BGps>6LmWuz5V_iFNW?B!jO&_uShur?G{TnJWdH2>ATKqId1 zSi+5{#ah(hs?vmOE?LsxdLYc@df+3ru@=_`T41q8b2)bFp2wS@2p*bQ5Q-3|2u_!_ zKvYA~(Dm^w<+2(Z{?^~9)|KVStRx)p%`epS`NG{L@amc=GkQarmAIqG8jO%1U1&43 zv@)x!sCJJtyOaC9{*E)hQyV&JtCWHlN73{4u1N@e)FRe`abX)qnWU?lIA+@xTr;aH z>uYNEnp&E;)A~KV_|(9fP5oAd*wCgE%5~5D4WOSn^5_A?Oq}GXWaohDP91@B9f74- zI*d1IRZmbA_XD*d(LS*k1^m867NtyO3?7w9 z6-1R-gq7!%=MjnMwtHrSk1Z54mz~j)n zG8bFRoN@$x8jBrQt9lx<%j~Ok;>NNMv?)Y`6D25e9cM^^QElWy6Bm|e?J7NE{(9pd zwO-C%?k`_1DI+|}9e%{MX?|t|GK9v>6}CWRIM=vSQ7UrQMLggtZF=@IOEk{Fz{~Ia zdjcEBA~u1ROa>hxRnkk+lfc-f!fZi~w-Sah%WLzfZxR1h;P2Emu6FjmjJ;B}BMM`w z9pX-MXUAh2Qf9|clNH=imc5rMUo{oz6y_6)4o4VkAOP8ojo7@K1;7P{kkz^UwSbCuJ;vC<(UntLW{ zi--R7b%DChYQ!j-eb=vsD$J*$$(DTqYB{_Q>%Ab&cR>R?E5-wPtuBD7w(?{qLJTI+v_-i?(xE!50w-{W z6?^C@ZgF5Dk+LwF7{$aDt34%6ez_OG5wvLDS(8NIk|r zxJ{v)k3Q6(II)SmN{x9J!p-#xh_V9p0I5ymX!QD^>INdXj2P`PVBm0!&6se8Pl`pW zqDdV%AJb9>s8GRhPxccCp(H{O)l^83wc&TKl+SJ5rBcKgei3mWI}5px)t*!~CWkgr z`eUSrdYA*8wlQ_MY2E&igDS5;12~;xlB3(}k;!~`;MG!69jHR=c+7C^FDA>>>4bbG z{0ofy>=FRybCx}H_$X#U8|p4zVAr3--DH{uFs*(t{+JnKFvk~WlAJ>K4E2(n9+@a! zUvKLvMrDdhqVx~7YOY;pMBDLdsg7(&_OSrNNb+XDbObA*b68DEmBy& zqAOgTMBO0bk{h}zKnMwnT;pB(c<12&RCo+J%Hf-MsMUE5c&8Fl%l+LCt_^fr0Tew- zyHq3-u)IPM(N2t{uBe>xKDl^>?3b+>aNn&A+PG!`$9tYP~>X=&m>(sFI|0 zOWl?}W(6NFH+;Qyld+Q5GA9p&?VW*?)D|I@^^Z8}MRu{rb!+d<>C$jc;ro<@hNxNI z`CB;{UVgj%`+?C6pE!NuzJ0oVrj@$c@t;T-SZ8IIr3HW>IylH3Mx&0v1vIY#rQ4f9)nBkx}_#g5$^hv z#;Xz$?mnvx5i~d!d_xhV=f-xIDYSND_C`p|qExgJi{*`>Q5qpH59z3PEoH}ybo$oA z+?V@Gm^qRPNDRz>_B1GmQ7w{-^L3P#g+k8e_l>DSc<2~tTWQixj=fdfsWlThXwW9X zKU;+UX|dG&wA8r?hdaCTcE*pGlifRR^6y1v1zA@i@X1uj$rnIzE@@V|W5UuP7OY_l8{vUN;NKOmRtSJnZxqo3GuV7O@MO$nSqQ8i# zm`fhG2S?=-ZczJL-`CL88o{1K&ybmS(9~iGnCl0iH^4QoT1DWrjzsN~HxVB&y#D%+E3`s4UJD12nJ1;6Du;Bnj_>8y6j%7LGdsJsK+VP-31nL@zNvoAQO zR*e~O>>tkYOxp09{yAtH3n~6Kt;fV*XR7A(H1)z*8}zeTCsyhVELV<1*C#E@ic0goBcA77 zlG)bv5ZJ-aobIq!8yBzx^;Z7BF*IR^kHQDGOKo-HF#u@DNg^4e5Xi>ve!)yWKRM~>pAjB`Aj19nC!v|>pJ01fuWMo zI$<8qDT8CEcFnTY#!<5(ZPK0S20qi}OYTwxCr#lH8+^Tv(1U{x1BsyR0yfG=ugIU| zyB)gme!U-EJIXuKEqFon;DFYHUt43te=UoafQ1GU5#d4-$v_74sff^l4PNptJ^qeW zN4CGlST={rNZ&9Qqq}&a=Z2Wa*y_Vr9>7IjR?XLbSG(ZG2o8&9ve_8pXiO%x7;`!P zm9G;oLa4|4tdLyl_Sw)mLs=PWreb)kR(bOFybw_IbZ{MrN|@Hz@_S4g*RH^ar|!sa zx(hMKUo5l~&w|3ka>LeT)f{+T?6#Z=}$epd6q@c~PqHbo7h#%%@#k3s< z?dFffeF_e*_`CB2=^gC;rPwNygkw}O(v-zf^)?uGY2FiC2Y` zCz)&dLm0Jb#EduS3(tesR>)H@8gt;lXQ@Dahn{Cw7aQ|w?!n!REtc;S{F6GZ9tlA^` zuKwmfh8@25>y}owD6ZyKIVM`KL?>ITA_#98CVv&~#+`P7wy@LZ!hbh*D>oYC`S|{|+giE#V7Pp2=v_;L552RR7cchb~>Ny8XglMXM;pX92A%e*L0;c>+*smM|A~%m;Ig*zlX+sz* z)0&JUlo$@Yx4))g3ItXsgkw)Hd6wvu`&2dx5mYc;IdEndi+`-oNDKizs;0k4Fm4Z; z+Kw+BPc{N`EBe$MDgcG`Nc4cj)pVE}Z}S{%N58cTp+p#Vwkivgh;BHfJ`#?K-OvWl zt#&?j?4v2B{Bfn0rS9uTw!CkfrM!bJ$+41FXR=aW!GkTf;uvn0n{! zJ=GGIv5PDpOD>Nw8YQw#v>3cNj8aIW#4Ung8a`G!6EPD)OTj#V!y8?`o1ZpLd$tn=Vq|=YGkc8EOb=yR|^v6uU;CH{q?L^@H zCQVd?XGPFsz()DA*uw8DCLt8YWHQYJsRp5kI27X zNJW~EgtQM%8$ztuk!Qg0 zmReriWpd%NV(QblZ&(G;&E@Oa##leS*y8`^9ERk zMklDU5e@d7lI>L)7KQ2zmGUaQe_K}|Tjd@RlWt31iTd?@cq834JB-U#MMn(>JMuVJNCZ3$l)bhs#CvOYlAJZ3ISPo#f467 z!AnR_Xh%3+x-k+vN@=VRo~g|em$w+`;|-;>Y4zi9XE=e@^)5ITEobYvzi6;r20DpI zuw~;0QIO~y61v#MtZhnVTeMS$u2FIrDc_2H_7HPh=5l6)x4yI(qSCuN zqwQRP-WdM~#cCU6HLXvotHK>gAS$Jgny?G}Lbp-(IDnhjRx4H#%`f;y|JL6KEaz*C zk`FQ-o-ar0_~4;TiHPr|n6&Au%)ko*>9sE<4mD=>Nat>@5We%a%_)CA zn6@})o5t`B$uN|!pqHZErnD)p8q5 zPJzAmO*GXMYNiXN+(E7fGTd@xKTIJgtiFfA9AxO;74fiH~&>Q9hK^&p!5jbJs z9Sxah@JZl5Fnsf(Mj?JZ?Ph}M7MS|eQ-`s&I-1wbgFhZl~2wFXp37gmh%bGm!7&ib#G*7ZTezygyO50=B0269!2*wb$U<;kULcC5KxfXuZGc~Pa2oS|tD_IOwYwS`ga?;yR zS_CJmOBr{E$}H|n4Eu;Ie7}?u;crr(3DYetj;;JOW}+)&T#E6F)@e(egJo{(VAkH@ zsQdB$U8tZdB7{2u#8A#~o#AAm-urDzzvUj~-5Gw}W>vn>Z*JCu?7xSeLT|E2skXKo zhV+-*N$PZUXEmio9-*eHAF}5oi*Gv~9VV#|z9^`AW)~JU2VY7>jjqS79GDaY4crS3 zdj(e>SZ94%52DwIV5BiyUxC(y4rT8X-AAH~QqG(N`8gOWD&9FzQxBhP>DY4dD{+eb z7VSUOxuZeoxgniFB-Y^(b3)|de4h0_6E1%*OkzR#)GwoUeP|01{1rQd{(Gji`BM0_ zn;#E|>Gu#&k##ih6F&yh*IfDGNRi6KMq%NR`!@2+19|xTAr(5re%z7gL7d4eW)6ha z-*hsuwvBtW1K@3Hz{wR=rwn+`(0%dO*r72Xm?YfHs!3x(<)13mpP`VDix1J|4X)%r*iP-rMB^V+I3@7D(z<3o}idqyT1hCsrWQZ(^8Y5Ww^Pm1te-o!t)#4{#ZJwE4Q; z2RRJl3Vr^`cYX+T`0h9>f^*Sarok-4FZD5jfR#SD<1+F!8EcA{cZRSs081{z*AC+l z+-rOYuDhb?n`3Hv3GAy&7+(YP<}yHdaXeFz(qA)SBhk#;)c7<_p`px`Az~)!Sn0tp zxwoU^5!{=0_~6qIrvguis5~C}`kr;nUA(tym`rdxjqGx9;}^~cYI5n{59~)%Cz4hz(Uas_#S!IT5QJ%U!O$Zx_53}@$&rrg+Mv?F z`lXTgo2Lt6K&6y*tiC|t%kLAzw-ObHqlbwydqoxkW@-cge2UYb-M){;O_a1hKIVoO z!kW;wYpB+_v4JPQ1b_(EC>C#hC5M=m7a77X`N;WdE!Rsd+kLsPQ*%JzLJA^Cw<#oF zCX-30Pdnwq!@dqlRb)|r&*g;#ix2nqy+}c3N(^DlHX_IU72$c(u4c5K z!EQY939M%7`4%dd=yih+P^uk<*`J3CzYps+kj_zI$1D(aG?m7~Cln(nG_@WyC$)+l zBpR;*%H+uyXHRhqp@Vl&UpgLrC;s~YTv|b2IEmxhSYnd9A6Kv--tg+}tR%ln8wvO>K3EfLc zOh^A^#D@ermtQ1pd3hf^Mp>tZOc;ti_`SEi^Dk8UfR9R|LSL}GaNMc$&J?hQvbIyQ z;X3M<)snF-Ar=7kyKN8>sDX%9U(3lwsP%JO!iVWX?3Jd@5Rd(W>)qXE#I!UTwM)mr*fXm`l!7#9c75Uk@ z=Dt#6B2e4*w(>)|yuI1=BLX&6mp67o(?z|`Q@|!hY~o|$`9{OLg?+ilnN6LC?#C@- zU-A3?1nrq=a+LeB#FII6wEp`MieuiUVm&=Y1emC8lmVBTYHr{*9V5XTFSfh^kB6XY7p( zck+@Qs`CxB$2j_WB%>dRouSDl*zIQ%D$vhH z&~;89M!vbxgtps`zGB7kQ{~-a&utB8Nh}oBIwl6Os&WHr%2y{`f!utS8~rkTBjTRv zKOl!mlZaY&^-WoGd)KUC=WA0OuW={YALNz}Z(An*OM7HNP#@0b>3^9Ea?6N}tfyks z|A2m`<@-$9)5T+a-(+}^h}|>KKKPNTFg7drKR-B0s5x}ow>zUwy$QkQ-#2@Pgbd<}#o6rLn;XQCZjm>8eDldveN^OJW`2#V=uQ(rbB)x0FlQDmc&HOTVC9MCYujUDi-VsCdJxw~iNW8HTdoC(dWC^5HBdmKhRsn zf1kP!w{w~ZyVZS3D~b4mjQhneIvSsg%QRm2_#U=T9Ql_}_;VPJM<$C^_gy0wF&DGA zrSS@U5fT6@`w5^;@anLZ_6up?mGN*tm^#JeS8qN!X~ZyLIr`}YbP{Hroh!FUjP~Ud zgl%$_5tBHU(WvZ>{)I(!+A9czmibZbcZ{GjN>NRTlZvr#dR1aU2IKKTG_ui+bhH}m zDQ0$nl~JPWy0#gX|I?Hv3zzENa>Znf<}R9SfAf794eF$C6nvOzg1nnU`eiO$8)DcS zzXVXwbC`8OvoY5kBj`EP0(=oR4PnvpQK`>pd~`ejU9QhK1a9!fCH_ZV{-lZ7M!&Gv zM-kp$fXErdl_on!;PS;T9^@~*UB}GVHSGlp=rhzsFDE=t){q&217LUas%;p#cPe*2 zw`V1D{O#p4mB{ZsB1pe^uXTG~4{7w0ll@BTVvNee_Jy2prJ3zH>=pN}O7P7zH9`d= zOJvOdZ^1)w!K*tkgr|Vs?+JC^0U$O$Puyv6BYp&eP_a$r?bhOldZ z-}Rkv>~T#ECygJ-&m)^td{@_^pwYK%ao>%mK;(uWdXFc#kb#IyICXO0Qzy~NZ|4AW z#*U@OqJHaGJYyn_RUSAPw{~PYwpL1BsuS(c+*f~ZzK%7xl zXZ?cs_c^qK3AF4V-*adl-;*`e|KF1}DuzbZrot|cPImui04+vI_kT-72}`f-(6vUc z$`=n(+hFL%zko8zLNmr78lmDx(+j&T!dG|A%nA(2-;J#Cy@&v(TO1~o5SgLvz_#McX{ddgGQs_V81np>1Mq>$_w7A-Ogfk9z?jwtjPAPAcPYpH$)tk zj5mH#+vt`pshJ>Lk2FjQ)T%gWw_tH1Y@c>7O5_)Vs${3s8!MLdXP{xh9Y5HNZm1Cj z6qv9_{}BT+jyUlutdn0_oXI+d7FY&xRkml5$8UougeDR!Y8(f8spRN)Z6Soj`9Wh0 zG(da$plT8;fJL`?ze{7dIf}xf-QB9Oe;zqimnTSmNzd2KE@R+&v_XO!NnMIE^n#E7 zP;mIjx4x&1-jhZAvyh7I`=lCaxjf~?IIVvrUaEbKEes7e#QfzP3%fQZ>=@;Obw#R| zW|dC)MqffK^BO+Dg*bLOL1~tb>zJV$98uiAn?Q}4IR)vGz9xe1uUa*#?`e(_3|-mm z8PkaUnT1w%k{8g@Yj6m9YTO=suJb^<=6x@(_>ygNL{gA^?!N|_b(_17yxIugFD=9x z*A(n~i8{?M?huWalxyr5t^R6c8SR>@Wz6#&W!^6IdDX3-q(F!Yg;)Tg=m&ZE3w9#1+9_0K3FA*V z!M1$?R%+i;mtoI9%e|4vG%nAIuf6Jnb^~dU7h#}8r2>nU4!h~jU|FIr>?ng(m)0Dz*fq*dL&KgRf*q`R{}lu?!*WgLiy>oJQ_imloPSC zw%^xC{k_v~M%yy0OnKCpye}6i79F3h!ijq7p?J3A%qz-X`mR8(NR+Tdt8(aL3mgCg ztiNfKnL#tDj|mn>GG4Jo;0?KP+N8We3C1IDKW<{^))4L=hOHBE(wif5cis}v4crWA zHA>#Vl)Qv);Zlqz{$VZ6`%j5u>nMpKXURpWSff>MnYjx38To%F%l1^+ANWmn`9CMi z{9k0J(+8xWqsGah)ZnT!jygl&V6@R&u}WRm)JI4{ zjif{I!n^|f>e`FIH}Br0rgELmcR{-s5K>?-hxwmE>f|w{PQD=F>oAewHcnyBYiTxt z_}%;emvnc<2Zh4E@!sJ@GDU|u3e1wfTUqTg6P$=zg?>&vDk36ltP!dIB@vJH zGzW*3td|+>W@%B0E{)M_oL!)kHaC`3trV`Gq%o3b2=ax^U@YT0y;92^&_iU0!69bU zD-%0CPhbcI8wCAxip&;c>}Vv){sR2(B>k#b1cLtmk!1OQBYB9Tq7pokeg=OO+D^ET zj(M~i%?uX}Uz33@`9XuSFdRa#FOFpgp@X$R&TC^g*Ae&R>)qP}!qIPwjeg3U-Y*2+ zG+qXU4u?AJFfrH|q6+g9_NWuD{GLzvRYi<8%*)XJ8>Q#wU8mGk>^I8b?uD4-=!;=~ zXp#IlELWN@q$^# zZ#yKmAO^14M6DH)gc{*0Y(eZkOqpgccN1Ya#97M&t#D<;s1?jcS@6Z4#Fq^R%+uZOk_rwF0|U zbRI-VT3<_28z>%&{AU&vbJ3t9+r!eT0#p_!$t0&xH+Ob4v10YjU`3rY*8uzz(@|K? zW}HebZ$K}e1?G~3Rc8$R^e%uQ0BaES&nd22^f$_p{|8C}7diE634HtVZUIMpO%BHm2l4k4n0o8> zUEGEniY4A|q zRCPQJRV+^Q_@TfW@8*aJ5}R^J#QxFGlGNIhf^TuVQejv#hiYK z7PZogHG_gzol0J@u%niHKL)G*4BbYVCj6=KIih08nr`%tb%pjyAOimrmG+`HjMfRH zp!y1Q@845O8@>QZ;hV!hL^uD#WsmKj9CDQY`EnG^hmU27JVdz@Bpx)9VIc)K79s~o zI2%X58sop z9%(i|QZX+U6Y!?-n;>*h?Jwm|LIbHgIt-n@&KgY<6O&?Ns!e*pB1(fdH*~Pdx7|1M zP8!ijEgXy&8)Yg=v%Gz`z|I3Dymhj@#iX8&NQt0pRo#YUwnioS<%(Z6%N{1vwUeL#7YEZ!0;`V@E(PO4esTu7BcTRk zxy7Lgg?d$-pw(}Q9wMw!wCY)o5UjA(oJGuYDxwh9rqYD4plgx-c;+OOkN-9>-lp-w z^z_XI8}vWs!v0S#Dazma83KrZ(Zkx=?gwNvEM1-pv~> zirNlf-Fwi`W-J=wbFmx{hywgqK`Ej^}0p66~ z&f~sgra=Rs#;Gv&CBj0N>GFM9;dHC+hw4%jA7#Nz-hA?d!*-!MH9|y8$9WD_-K5P~ zjaIFih)2pStPAYQcOY$+@N_I>&r~;v$xFv3^1!T2pW(!#d<3o;C0i#;#z_{#0_+H= z`Wo;pl|BspOx|O6x>;mhrrE|Sq5CC~S}?7`#|-V6*Nj~A3cQ*Gde)a3I{M$lIV=s^ zI}s6o{75DF@q_X|Iu!jgc1pBhypz^kz6dUp_|6~9wJ|}_TLXSTiM28Y7$84DYVa5V zryBvYQbmN2T7zj4Cq@(5EU}bGW|}9F+gvq6nM!8!LC~gJN;I}!j<4FxG~Rq(?b2MX z_M|D5U1h)fc>0?jeU2jJc>V795pk3a0-m1%FD6>0_nGxxsy~{uLYB!`85%{VUh{wfp0M!1<#nfb6Bg zfDZbF0wMK?f>?z1OVdOTb-VBErDEWyZ*sQ}>cvg)D=gq^*bnO559Y?tCQlx}f*XM-gqJ(fD&1V{@M;nzOYgWUZfOt!H0Ani+Qa>F1MEo82TnjjR2I?< zwcbGDDQe@I^e>;iZixqYPw3)`FhLPD4Z?b9TFkS z9W;;W&XULG5DSRnRuW}z)J?`4V~+^Xo0)||k#yBI#2GI|p&Vt0>VPB56FDYhRkr`w zu9ohO8+SL8f&VInB7fL$zOZX@SaH5s%XAmK-V?$0@X2ZkW5Z@iv%|)Fqn5)Zvnkdl zALrd7xn&t9$?Y&(EVnu0!Di_nM-U2~CjZ_PF&cxI}6E3snTXF`PBq{FR}f|LNvSBM(x+pv6cc#y4#M+)bcgI+5r^0qNT=rvbdIYL24k4F`nj+CGv#K)%;yynbQL50==`IR;V0yXMvIHd>39}Qw;-c=8YL<{V>_GSjSHNxgC3ZfL(IV&+5m^O29%B8Vu1;4ZO z9btxoiK{}w@f25(dN6VoC0H2;ZimPkQYVlY=w~{%h@knKE7JI75bw``!L=!b3 zn=cU>ZWDJ+enD635iILTB<@bor|{_Fje0ji9HqyRfw@K@E6!n&`BN4SrJ~G%S~6Dm zP9d~!f}^nsTk0};mm4v%Y^1#KdN$~}UV9C&V|Q{f>R!#hP-o3}eB6OyCSI6AzTP@W+-0{NvMG~7v{JGOMeH&XH@gEzEo9vWG|lz1Ik za!t=|`@!4b{1<1uh7TH57OiRnUs?nv>%P}oc7!-^$GqhDlqzu`a_X@*jT>O621v+# zyBtC9(4Smxp|d^MLtXTl7}$2nlS5M>2=xi6n>$Yxc_o#>NQ*c}g*tF7DMZEP%IJqOR98q^z;(U%V!xUzd_>XS8<2My1LKojvoBW zT`zR*{>@4B^4G!9D`F^C?+*oTmUj1QNS4)@0)Rk~J>)zn2Ut6BOwl|@a)`D+g+0EQ zn*r=e5kpTcd$euX+SS!+w`$IgTwPn!R*8%2rJlTb+ZK19Q(2MfgXN4}P9XjK)lw?h z?BI0aVXzoU8hvB2RDudCG`-!~E_LWGz-)XlB`Z(71#t#<1d#h~!smtRJHX(^`{8R%6W7^!<8=DMvuBSb z-i$J`TtT|iWWKxr*_FV8vBJlb4$>c(YEOeiC;ACpL%GXNnM-@DrirH4XKarP7aK>FvpSa^} zW{fsjyhcuP0#uOwq5sYt2IY+1-!N{Er{*H13?__3y6{+C!Hq#u+cc++O#T6!mC{FH zzCpnd{q68r80^!2ZJT?GaF1ksLHeQt$92&<##LKs@?S;b!Yg0$tRAXC|CnL>>jC2y zb-c%*L-JT3V)4_7OG}q8ZI4Rw-0e+g>O%gWkB34hJDFdNJKBKQ^)&OMx5pNA z_To=X=lbQ6yyM%-YYu##v}WVNUH5&+<9WoU;c zd?{zt*0MccTJ9s$)G%}6)_}6L<=ofw4hZ!91h9#UAvH^|w{^1iCEfA4W!G5#;qcC! zLqWn(L!Kk<5pWv(4Yld(s=rw!%2;Y2%1m))|!rKzx*<>-Fo zn>>k~amG%;!QzdhGiGCPdDlz1x_udS@TVJSj}|0qx%H+OckUjI4?bgxHp2h=P~uJf zJ4o4zPItP*@qAckEMT{>DTCSICwkk&dcsJn+34^4A~BxWY)bZYvBsZ4c*H-)8O}+;sWAmkIXdR&NJ~$8{@T&%v?D_ ztH*3X*G&2DZ}!G^C&(aITLi&6oYUczNwM2n)7LPqDW8+4mF*g87`Ab5#x>tplzQAi z7YdfySFye|`JoJX!S@*d8-z_fGKC?){x%PPfl z70WX@<+B?@(6oJwrP3d2v%Us>Ddr9s-0wF;5+a_%ytd}(L{JHPH5D7JW?{nV2$WEo zxPapAoN`y3Eshd+C(YJH;jy&WXEZ$hl;(|i`3hjYu{9OfyN{p#PPkL!nm$lXlaI`#GVOtcUUuh2;m3Jil zA!&N3f0)VQ5*sL@F9h>yRvCo*56^PV!kk2MFq89qAq))gK+q z_bQVxh3^}7G-4X9x_FMNl`APUD4lZ~7f&5NwaV*d)S=qcp<}-l(S|G%C9rhpd&50m zJNymgedeezedTc=I(LOG*&v^~r+;xAoF=|!>U#ujW_KjcoOodVgcd_Mvg|WUqsWtH<>(zme-`u!yG6atF}`X)zJ3Fnpaz!u&2B4h|#`1$ZGNjM<~O>X_7m? zNpbIqdr!=!0jg5v3|Gk_2sesDm(8BG>KCr(OjiiAEj*8!*cnBy9Uq%U$fbF@2h{BIzBfUZOE$T0^ppi{Q6(?n8MfE3Y*kmSFSK>DG08w!mF>I* z8L_7Jsze9)mUYH%6-}nrJPBr1ZRmVs@}lFG$@%uNdGU{I*Z1qGGy9%edY7#i`dp;B zRGn6ygDzk1K>uJhR(z#wdh#%QMTqGHtXd7WE?-nkl!2lOI^vYI8hltQ`4{@4JDXmA zQR{xkq9+%&Q=zgW_D#TI>pI%@0RV=DQC?Zjy_AX~!@PAp+6MtVvzklawocvg+$MRg zjyI|ZcYNTD=tj5)Ed*X{bFdK*i=m?i{~Qfw1Ec+?rrBT7oY88`6iVN{<9=oFdBxH2 z^sZOn8k5gkHBEf{G;8D}8o6}y&@6#=pV>E9n+l8kG2EEL+{w+sKDcX!nr0;cE6wh- z4_Lpj!tG|;)!>%fIs=e1&!(&#J zzDEBVro4e>>O#4^+zTj|EANTPxlN?6HTGytD_jPw*RsRxnFsN z`QT8n!fw#ZySiR9hhKHwu!s-$^F{yPYlWu2S3DKJYf`5FL9I~K#M1fyZnerv4+$Xp zY?aF57~{0~stFcb)PzyuqtTZ@6o?|;<&sNxl9`cp*(~^xy#V2V0R5$OK)$Rn*fBLX zciYR#7`ytl_735%e3fiyuptCQgO0l_Q=O^7-Y}rB3{rqS1{+f=Bb4@@(5}9%igNu% zQgV{xb@Yd17b-Y)0_$K-6z~W(B>2D|Jfsa?L<=%&@XoXv5$)+X4H9|^$cyu+PTBf9 z;IgQUp3h{e=R}6DY}-E5lY@zlp0!mcok42}PV^`XW>4D%_ z+#rny@Sin^O`0qoJ+(|RT(}1I_&L4+TD`geqj3q>K~WrZZ0HbKv5;#dRbyVSe(Z;o z!WoWRX#)4gEu9RZDM#Wp!2a)Gt|6&3X}`f-e1HC@BJRJy{8#T%aY_p0yTfH!mgAbe z*|7Sy8ID@*!dNIhB6}{R0E+ayl))g2g_2ggE7YBQ2&q@Bc?DV#P@QL%GY<=CsoKz1zP4wL3w)cDI-RpVBjo|C!d2R29>mdR{Ye5|< z9LGeV{xB6nwtE5u2(g?Op;l0uM)&j~8iL*k7E%ztJJG{FAWDM@?g)U2peH;SL{!eh zGZr=5B+)3*KsUoHJ6sP!_5(rB`~wRe3%wHpyivw>Sh%UEx@&(xR0sJE{O7Jozcy_9 z^iZ3oj-f+_OA+xd5~1jlCD~F!vWxBlSUc>iwGxuTR*fk#5?QKBm<(3#@YO$n3yzr zJngi+>|G-3&aY!6&cx)Ui17$(E}JCe_d?#BF}WwG+eNLN_$9Hv>}hKF{y+W}SISG3 z3*-h5LTOIVYs>Q7SM!Ic&~`G?aOQ9){;9HKR3dN#rC?DgSRU>YG3N;=QqkGuGEBRA z!m&BO6T)PZl3|DGYcGZ}<<#&5T@|J1zJDM-G>{~b+)Av~7l}V=l&$FOf{aPCat_l8 zyht{=+%8_Q$DrOYLpJM4MyS~EG&6yTKJK%vb%aP(FN^3QKKGKbh zCq@@JchJ>{$jJHm8C=5k#U@Vnz zGMAp?Lwum@O@82nuFtSR-yl7#j@&!SPTZSvi?>PUJ;%&n|B^5we|2Mi6Jwa>Kl$40 zDgQ?j^Ve$?2jBqF7wN7l_-8xa0U;OJV`Ft-(@b262DU z1w>63DtjF~ElQB$8}wSGy)MocfikM8u)2TY|oEIEQZjM26=QNz-CNqU1Gk zp0~}<)MA+ye|k@)lFHRwMH42E1lQa`tK`X2IZeIpr+R5c6k+hCf_`U9tUt&(Kw+c2 zCUYwAXf4;0tO;@nnn1ls+~G~mCnIl*f?mX;_6-)(2UY`AxY}wzQ1<62scb|t)GNS( z)peF%`*}>#GMP0$kxM{F4_uXj-84(gum$LNn%eG+_8nq zNy8EqI3UE&@UuY*EZZIJmGzJRqHrdMW^=li8u+{~*k&M+N5n^MP8D=fb|LVyl98<) zoEYLSVyf)~x%CU~u}o5EPKEQRxy(JPKU?>}>|thBR6s7#-@Krt*cjs9PaDK8gDh7t zir?XSH_s=GWfn!-w)*xCKm`w`g6$b7dcgd(cQC*%JDHvcWjbnk)E57E;;Lx!g-rpX zz&XGoHtKjlT?PWrvbwP;FYgV21N!BE&9P64WIo!q(Erf;se-!GhLj%XK!zn6kDZ4C zhU4qn{{Yl_&}O~-VsDxnqlvi`%Dn|vM;d- zc@F9|SL9+UFb*hij@;*lE$GXnqhvJi*fxc^nv$BFey$r$^VSA#89X=qEFoa?$o>l9 z!s$uU0A@4ntg^^qTtuyMFUT;PJw7WiD_!f`*bKEt3zE)p=Z)eVH}S}6ZNt9^kp`4; zwCL|zQRcdC=~!u}iUdy``0G2IBG?tucF{H$@K}yYSuM{STD)?aimdPp4mfg7+#W1) z8%PyLt)9RuO-2sQ9f&Yu>bjyHDK?kn*~$f#*=R!^XZe<`<5l=K;r|=Ml{rbuDpoL+owU2Cd@X>5RLjnm^3wdMgevuZk2h*>hY}*Ayl`t5Pc`Vdq&g%?}9$9}QXY z3zfmHX(Lu#i>ik~cKfX6#|I>1#;%I96Uoz_<5czS3vmw;IF)> z#;HIi7+J)bneNSC=ia^C>#P0IJ)#AKEYBlTsMD{^^S}_qX?~jHL3bx~STal#0JSA8 z<05QeDUQ%dG-A2Pj+$8jcVuk*N3k6Pueh0=aMR@JU4-l@s9);Z@NE+#z+2f(B7Z$f zg`(sdMJct!76TzkF0C4hSxilHvqkZv2JcT(C~#bxYPMBUxowuUyc;PspEuy`R>65U zakw##TFtv#Y~Oe#0~>hN%VPNg2dJ+E6)w}#1Qsh`pMfi4kVR?d_F;f%gva%yx4;eA zI3ntI{KV6y>*D>7ZqVf&DgddDECf;t@@8>oiVROnN?-M%j7h zk^mezlG6t0Ft!5Z<9(Zc|E(cBUa+oM^UeS0Kjr_g9o|3q;|Rj~fLO9^0O~&pLzFC} zgrO!3tDe-T;fobSfr)@FTC~{In4B{rn3NGeh)8WEno~G#LB1Qpo~gWdieinXUb%U^ z_+PJH9$@<*sgt)1=S9I*SaXagM^vRbo(Id(VQKiMJl)8Yo4qW5+<+f&JlCTrB~SVl zOk%B*KN!>qM7r+uq`HI)O~w2Yr8dz5LPm#fOX`3_T5l2sN(icKrGADL1JXKkyh*!w z8_6dTHBZ-LW0HPn-4T+YrfVomR>r9G$Ts@y{84~G+;{mwfM!19<&Kgc8}h>r30%Xu zG9dnvciHN*M_^ttGW42-Ei_u7oq%9e5xYvSxus1Ws!g}6?i|h%;Bb!@pOpfN{@c%6 zn*jDDBsG=W7Pttm%>0X+I=7w|pP}XqGP&gq0H<89zi5>e%~JsAuI)&O6^dCa1cet2 zF2KRHn}U?~XN-+)QZ?NZ{V=~Q{`!R_7Ks)AbscY~E=o+PtGR`uH(75oJ zo)|j?6S>5IE-ODhEmBr-Y;w#ur)ZK}oUeXhOy_-`qnPI8Au ziuB#SrTDg1`X9Qu|48|N?k{V&VBA&AQ+}D;Bxe zqjZos+q_|M#QW;FAR7WBXx6+OIz=so2e981!qU%The36wW(Y>tx}-3Yk3U*{dVr0e zdRw0y_R~Y7bl-(dr#6s4{o9r>Ov|iIF2q&GQk?!xrFsk;UyX5qjEAMN9eeGzQfq)5 zY^|~#ey?y)Os8&NTh(IZ@|tBk-++0^(l8PSb-R2}ZF_iV%iWzDp@zr$v>+zT75v&@ zQrmrfGH`pmu!Xt6mG83oNVYvDjLxi0d*Ir8dnlh)(4-Y5Yq1$ZjnL)M9>VzX(URBZ#)ElM#`?Rj@UexQ=8V5m_+LF2-v+WMi zBp&e<2F8lDc*UbDuI=5OwZ95TujRfnMLnn++XR?yH`IeB^pgiyfnjhH>yW6@09Noa zTI`RzHfg-X85AfdRy%DW3E(_(1Ooey0Vz-RJkIx}%b2ji0wCTDiz{P)^gvjOKSYD& zJIpXF9ElR6p|InyBT4zUe21=+e}Fnif?Z4f9zjiClde1jKbZ}5EnYIDDHb$T&(^~P$yS+M)b zul?;G5HKc1#Iv}P9UWQ#D$LKo+^Ny#hCJKcAf7wiojm5=LehDK+dK4k2tA5ehy(lLZfi=mZ%O+Cq&sQ3VS_^F|0n zh78eohTPpSdBCo4GA#psCRRZ-tHWOiny?3KrYO1%Su?!}@=8t50NWbk|3bnl%_&h( zj~AlL7ElWc^1KzWb4>nJtRz_-*AP@1lu-IjW+$MbxVEv;*H};MIy|^wB8^@}kB{DY zR-8v{!7Yj0n0kA^7@vz+*dj&@naVYg?(5Eh$jaK~7@KJ-0v~nmo{$}URMe# z5_@m%#hge8|AOe(DT7N>kCb{ z@DM^jDOzn{V+{OdL30z+FM{QLOOB!e&GdOfuq13>NdD>p&X>+mf&mijU8p*%m+yP+ zp#(wdQ5Letn5!%a?J)ec2NbuqaJc~w*gP;8Ea`_U?E9Q>xfG!X#Jj?>n=7Ox(EP#| zv#xOb5qXRITQW!7Jh&n6tRE~o`Gl*tdfoDZoBaObtRJulc_R>Lq^EHQcRZOhr{w}7 z^5lP>1QMz9%j{pbIyDEvSUz}s15%#t@waBDA7$c+2o~&Ne+##la&QFkRSx*r^9!j8 z&ezz$isOHxyi|n79YMkQj#g1vgvj69CszMJh&S8!vW_de%@5TderNeW_SGCv#`XZGbcG?GB;7ydE;iKE91?X)3rF$tGqR0-9^E%$c*Jfa zY4%`kg7=_~)f;^7=L_H!wcG#M!X2oL>(QQ0d~FXwuQOdjLL$A5UI8oAwEJhxbaX?;m~MP}v*{Lcfjg zDLrTEV*tVdL!pc9N70~Sn(2czjZh*ObRE#&+F>#6_VNp$P_#1#2TB}inI_dDX-sSx zm1jw2muB$0JhHQ}h^^AReOHkzog2i&{W%lvgqaN4PU0{1_*A{Bt-F6ii(^qH`>H2n>DsbkNlID~!|pNX zPKXlloU=O`{)B?O7j&e@MT2u*6%_VlLX5@qT^p4Q1W|x0Rmb}ZGZ*jWXpHd9Kn6qN z&yU0Yx>w|i$NAIZflDgS#;kw9?fAbad&lTZw{2^*V%xTDS1QgMyJFimDz)U6ad)994p1Z#G{CWSqZO(x?XCHm^!K|Q#|A^{d2?_yn`-5@Re29GwhfAE7 zoKqqIq99RGgH^&_=d6j44;EPODqe*18ApAqw7wGw`<}FL4~t{EoA|J`$)9@%|j>zhF-tzRjnNQn3SH;{x8vu&U% z^9*+$zlMfS4}KH+3*M7xXCLG4kS4fhgO7uXYhxQ;yVbmPW&z~Ldz|qvzGU7@))P&X zy9d|ylQW|5yDw@T zdH`Tqg1gm?F=y4~X?$Z-_eSet z7h^sI$NGuSOwA7~&763ELB_hS70y9h)s}B`OSP*q!a#yj5XL&foiWT&JD{9(hN_gw z4s`lx2&@v%KgybX{7prI>ZU;8G}6eAJ}(s(LLHkGY;j-redhOgz+F1CHTi3SUG+7s zX1pI+7b-0uJUfAWS5j0GUhpBS7RMu_>x<>9o9b?MK-!i2Hs}I&Sjo8g5Abl_?j-gH zUY6=o#?)G@hzhBCNt@<(#4XkVB_ixDR?yD3JE4VQq7yg^!4@gVa@&RmqO57p>HLNu zR#ZN2sS<4DSh_YZvcA9vU22j~AH}fF1R{nd>;k zTR1^4@j_H>k)Vf7{0xK*Qa=_LO_$5<2|u_(4YcO|aAq1xpNsG*##~rqU2xf*mM*bJ zkuVOKFBRK-U*aj!|b@5s1)onHC}PGOfbf4svwMXmvGQ$}#5_ny@_>BdS$n=<+Q zsR~_ESsChvum^GTWTiXrBC3H_L-Jsk!rh36S;-bnu)>V=w^TYYOuPc!-=8?~eB|+b zKG^ZEt`ZQBIz2jR;F@~aGo$J`9U{+HJ4Q>%lmI&_PbvlpS@gnVXw(CfrqDV4qA0SY zNYO2CSZXg~Wez^5y5ogdWO_O>mbpjP1l>U#;=n&}@0EZP?L|@!gz1+eR<#QwKIgREKz*xTxrof7Dh)eA*RlCDlK-rwL(^IKx*mr{WWM zEwVvAof*(BK8#DZq=^^jc4dkUV3X$bqa98$X&ogodX=v=xq&=3kbS!xfVT#z+>R&S z)xlrL8>ntTz&^BneRAfLcL`Y7%7t&*79woT%<~J+?5a?r{U*)W%I%dEm#Ux>q=djE z{3iHQ4VTgkH%^5GmRwz2{)7|mn=#nUXAd?5$@F{b2QGb`tOplcJ2?J>6Oa8v z&-)L2lTw8Pc_~{fwC#uBRqKh!CW*MiWr1~Ol!bTpQ0TD+dTKPOOM&GQT1UOtdcFp; z$n0a?#u0bOT+p9CBZY$GgIY5d<8%8T7o`WbJ<`ZH?lFqHg`wkycZ%LD6D^XYi`qP` zQ(f)HCy;NeVwyG=!+~FQlYZ0XXaXmmk!X`Yvv98v+^G2a zpy&zq8&&gQShGF$VGVcnPZgpQ}X*$6nv=3Dip5i+B!f4zAvF$EGWA!u$hhHC= z#2oqHD&rbf&^{vbo4|e*NCX zc-{GK*yqIw_bmC62<4I>d`uaBM7_e%IO_mQJ<%kc+i-XzPEoQd-77Z4m%zh0D(`ZG zS#hf@A#Fyh9%kLOU_qPAswB~|N9!O=%;6DA6*u=}f~Eo##tsJ~-zF?fDU)$%^DDmV z54Lx_Y#d1usY?;rtJTmXM@nFUaS-Xyec$Yq z+y@Ovqg2l~+8Viv3?LY3mrUR2`2}zA?|MBNJKclt{28#2AJ44YAn%-%K*4JA7efzX ze}`86`$1=yzo50euNLC3@oV2~>`fSq3@puT9gG+pTpSq;oE#i&tQh`J3-Mn#%fD;e z|Ats26{T%Zm=L{WnzWo(o6O#NP57aV>aA9T`k)5Llz>B_p~`UG1=h7_oYXE%FB{He zcTI$$1@?MkLHMDW(i=cSg_hzjQ;RvK_ita8udn&PUF@fVU^HuO4B$c3x18_bU$v|D znp;=4st%V)E{R52t-0#v8}AeKH*le~E!ETx?sUe+nM|_IrC9{+RF16?MJvwE@-ASI zN3^e%Oz+A&#@*#d-1`e-CN5%61C9e@`Z^3(9>aqdHDFZ~$ue>?oX9G@;U_&A<=;;k z&tOR~kKhe6sOb!U$D}JJx~2#W_VXt3^7`52UlH{*MdBf}yIv3tWJLxAQ*ap&$ez-R zIFzwrQk%@Wp%%qj24BlexmseD8Ju|i9Fy1fmY+RmzLuBjJ(B}8hcN*%bg*zfLv=z5 zCL`g~CDNn{3)u)O&hLcxPovSc_9ba$>d8e7Ou6Wr6Cw(ne$g6?OHT{Q)b2sQCl}Z(%w@UOk0-Og76DbPJ55-bLp^_u|xzGER(9 ziwNRb`}hz1waIQunE4BPo%m|Vq5r>dlK%qb6#vz0K2Jxh1+E5E8YbcEcd4RD?d{*u z;6+HqG+PwfLDwE%*K)7mH9Q< z-W#GOG!vHGZu{FH2wpfi>w%IXl|L9Qj;esRbU9YVZmutI7&k2*8D2Exi8W?j3o>U0 ziwq+YL2#}EBBNjHTdc$0iZ4pjg^T|QdT$HogPXU1j$HI!(&R>ujf3&MqF(#3rf~K} zkUqQqka-qEo~l{XpBW4u(85@eL2`jMWH;tmDx&^GN=V$&TSyg#^coa&Xln|0FqGE? zkKGo6=M3lkPa&n(g{d`}6RPx6^%HQU=l&&pl;GH6gG_z&Xew)cQKIx7Y`S$%E*+IH zxG=L6R3qNmTEH#pG-cL)U{o?{^^N*4`XKw(NI%YEO3%ASP&0(s1R5xf&`0-`L$4tK zYDsi`e+=YLavE-liroEM3EM<6$|5I@WxXT7<;;VQ{QiqT9g_dUd2Ejj6}bOcIlDQ( z0L@VJH}gQM>Dz{ESqA+@xt7CnBc$&YAiIZ)AW5xJ7Ar~ZF%<~^` zer|>k?`eiKT~?Nn`iZ!$lvQu{VR z8Fuik#aT&6;!hANKi~eNJAHLGoJ{5`MY(=?;Qxj9{hQ_SRRaM2N>G7;flh(Swc9(7A2K;`zz>ttVPwT{=5EttRA=6SZDI(FGFnJP!AHE_#`Nq9RIxsXSeB_^{b6?I zHmUrQ@{+XP7hM27m^1z;2TtB(g<5OSFHAIB#_lXn7B@Ioktye%EO^s`<|bh`xD(M4 zdMuiS+4A!b(lcBC?&qtsU?Epv3Q)%6m@Q*>lo?Rnqwo>QwLlcxQ)wwz4BQdt=vOC= zn!1RpW1H|AI5R6y`K%|9MF-J+fZ+^HE$^U`sVrmQ5X%ZpU3bVFt}wV!9Jp68+Md9e zSpmm?(m)c9!#MwNMCp1|P-!jO&m1^lA1&e&h3$6fzRx5##?M zgKW7pyHLE}Z)f`et@!D9aYlF(u zMi3=%i_Aq&a@4OdkeXjNQm(BI&y1y?$MgXjA1{Ucoiuh2E;G5GS=%8ObVnp@5RCvf zJw=N7dxUNRhfHl{u~z_3f5)=h6L>sFYR6!^iD5>fos3^dfr5D(O)h4Tx%f@m*lobt z*Ay$LbN9ywUDZgk20bA-oP1->Bqk|7tgC5*HL?~qMQMqZ8bWqK&OQ%)cj9;{KnyYR z*o8>7aSF8>QLbAOwv(`Qs%VR(Y^oVwtXLeeUff*X!P_h8?`!|tSDuChW9OKji+bbM zWn?ml_6JTd62$rVek{e!d=mvb=!Kx8B(*z#Gb2~=aI9S+^9dyde<*mlotn}CnJ23L zB=s^ND0@woASeHZwls}A=B=U@gfin~6fAuOOT#_kiWT;0y9M6aM{E(ct0PDne#x?k zjvW&Qc*S`lGrIF2@W*Hc}3LAv@Mje5M&uSxqgvUXRK?;j__yDrhPBp;Tq)~dCR;Q zI>U%d)qV1$_aC}AS*S0F#~2K)YCCqkL^nwEkd{N}SOhShezIWS`;Q%6(5v9Szrpx2 z+$Q!cpCG;jO?HnQXpnC+y=IM)X~r;-+hXxUzK>+ktWf8k^|HVdoMNl~5y1EoV?wE($=iPV%y^Sz!*6KN|Wf3Flh^L`*^XA5uQ(C|}YQ{jzY;ByQhTI$> z{6cpl9U1#|DOCln?lU1kfqMiu@72tn0YJx=3c#;b12S37H}@BE935Zzjn7w^nY*U@ zIUeTHDys+eQ$a7$keuWDbPS0qn-b!gl&OCysTyIf{s7d0S4W-T^9Av>l*|ZGi-JrJ6eqhII>_d`02(tLR&#f&{}O z-;Ge`2zDu!I*AjNMFBd*$1S1!x*0{AzsjcVJp;5QOptv{ksLDZXdfD^(JuU!=;;qz z=q%a;xf}EY+$6;5ab6wtj~pHtCaB-5q3{~{j>!0PP-YmRphdN5;6F!|X^)NIjSL`f zF3`t5MPIFG@oZ;V0>jcLj4a_3J6YQtO3q+5`|4DFSAJ)YBCHnTWni?);Z{<;apny- zS2_P-A&y4S=@-TMV}DGt#L@t5;uEC2Zb^!BLtRu|Giy=#yFDDu8hD1#`bi|gxc(|A zwp+74hm-0wXwtNB0y(D8uSGO#&O5e>bY}~-uAw9yEnf$bSK_|7YO{8LKw?e!3H1xk zyGyO?ko8-;KYQNdW8N8(ya?pkkqkA10(YbZ~7FwS6as9_NYMBeZ;=34h`0pJijPa1)l#! z+OPFGvfv`Xa5p&q`LO)@8E5lm=Uk>sbZ=wwZnY8L@@^LF60!+<-Rhj`4L#^IpG26( z(DzVJ(s(oHX!fuSWQ(N(6Qs8;?x*FLKc35;o}IvMg%PLyKoE*zkZpXb*rfUDeve$B zuf{=lBVVXnH1}!L+9^EeY1e?aDhI>P24}Fr&u?yR_0|!0VlsE4n`lb6?{$-9^?i(i zOL^^wF*GOqF<&?QDrG--uOa3mHFrKxZefFzEeYn4^9Fwfj~9Z!zgIMCCX`*jjvEhx z|9`sYU+)!wqmk9W4xG%NP@eOTXrCW%vUfIdKJGI?3UOmp4FHSSeBuUWOBh21!v>*V zEB1z5VGlZ}WNr=)c9+wmg-JgLXo<{zwU{E)#Lb%4)2r52kCszMhpQ}D!|OjE-MD0@ zxtA2(_nprk&mNz}+pc?et!tlQk#Zuk<)tUw)TIInr9|CGHy zhv5C3@fCaziT0`3r)Yhly3RI8<2P8yW|_F>_U^rBz`NP` z$?bjFH`t6}ge=DpPjD(jz0JHHBIrXE zsomU=0JK5)U3A6NF_<>W-F-7D1MKa@J9F;s&JhfJEmyFM+MS~+IYRU^^pyG{jyW|v z^K0m55uwFdyxKFo-KZ%!TJ_DeZ|d~A=P;szd`!NpSUZ>Nd9_>CC~a)W&`1sa0qg`R z97I9x{n$lZ)Qq2J0D}QR9)kdR$Hz1a|8@UGt=_Duvm{m0jrmdD;CBB^qPDC=Kn)A> z#X7civIk1c@atglL+xwL9rCZ z(Le@CmeY&7u+WII@Ow&D%xL))q;Zga!b*h3;gRjOf@ee(p}wSndGh!fY9l3z%JjIT zWwT}l?um-qlsNkQaI7L)n)wa7)q&tY0{)Bw!Sir+TH&J$+-`lBGRWPsPzm-k5TGKp-JooZA<) zckl9u1Wu!03y(C9IdKaq>giGR)JlwWVY8htf$@Vh`}kL66nr~ZYRj=Oo&i^$*{(r! z5txjl(vC2SFN+6m9kVrZM+9`bDYNBK5(GIkWDz6&GZm%rIP0?#ak`!gxLvXdze#UA zxC?908*%QFZiEq?mMl4xOYp*FSXS1SDkJ*UWiDr0nka2x{YH=WhLtWv)$wj8?}q8l zeM_{8ei4x|k) zEbLwk5j`pl9Iv3wE+sw%mmNqVyb$dkNB9)kg1u3>uT=;ZXGAcZ%1YbFoq=IwvL|pe_80ZynNWX6 zv1RiI!F;PfStm+1(~X*QfcJ)lEF@KHYxvf( zAuRN|q%A@c^ei7qwm*L|YlSfyu~JVEvEi(`S84Z$99Ek_Dp<+HQDId1D6z|YPH z?_v_)1?$1~9^NAmP5yi8Ym_a?l8tXARKbMnaWMJqynd z@ScAclXC~QfYNu5oa6^a0P)ylZ??iT5~9TGb;H3j#$-u4E#%gkiZD8CEDuRIEXtuYmKYN`lIznWHgFfR z%l5F;;g`yB>3fJcU##PXi)Ie!_^tThKqQkdJ`7>j$z$d#pnya3tnvwU@+Pn3{@izi zT=!=MWX9q2N7b{guS>Kh2Ep(vtMX$!HyH{>4C7Imdu2jx7-)icQd4owujvc*#3Q~i znDUMf^ey-DjwE)dy~`W}9W)JzxrxVk1@xj6dQyB?t0@UQg1T(c%qWC;Feier-y*E> zNsO7oTEeL4+2Xcr4!=Bq!Gp&aI3Bd%IC>^DE#OiDONx7{eD!1hDqwNF8>lt31!cow zd&3zLp<>1sW}++AlOrudMVLn>l_5;Nw2fJ|qvcD9QvWf&PA>RJwGhrC$Db-pr&OY{ zv@U?gvd|Qfn_>hQq~$=Xrxjw#F1o}sFmGZ9t~LH&Vmee#$!OQT}#T|3^n*F&^yFPo4jCKyX2}$Zg`8uohw`>+b6@cELc@5R0_d_EMk06pAy5{jKM7Go zj7MRp6aJ|~8#dVUr4gqS<`Wvd=r4m|-dDBJwv89Ay`uhiiH%M%D`25<| zIgS8w6>R}MBU~Cq1dLP!#UWL)Y%i|F&WxIs<615mc_a{*le1On2TF~0mGb$sanxKe z-efAMiN=Z0S8WHB;j<=gcl~D3YTkJA44`o=)qpPzTr$a>-h$#P`Nu}}a%m>sVMETS zANerw`@B646H-z^Kz2Q>WdEpidh!XC3Ji2Pe$!o^=ZSL%yvL8a<5!`rNTy5{< zCWPn^!rfGp=2~HzHs)slhl<80^xQc%G=uk6WlN8SnA4wx7e%)d;lCAtGZp(tI|?hu z%Eyw)I)BwdpcN`3Ny|}5)h0o}@a(dN%_tj_<;$85YIM$-aEYa4KM3i4Jbf5~k%!5o zqU@S-`%F~b|&4_jh~7M(Ik zpR+_7PE5*gDcf>jSgf!6Egjezm3Y}7QCGa9He`1-Cqb9SYg|m%2n%p(K}M8WcoYjU1fy!mjOLnr)2U0f5=Y@7b~3tCpsXJGgD850$g@o|BK3 zE8d<--JK;AlKL-~D|T?}k{KjX8jIa1sTgmkzPNHG-3Z-QyxtNzr(c5J3a!ewvX`E}eU&;Yby+Nzdb+A(m(++Hv}z6BeTnxII>c@!+mH8acQ9Ap+rf9OO@(>& zW`f{wQYnpBe*!mOQlJ=LSw2ZGP&lm_DqEZp3tDeONN%?(JFzd`aa>2o7xSIwEr5ff zU#17!k$|uT{EBS1LKPJ=bbZWUwH#%LyUp(l)AZkp|Hj76SAHF@xeAqun!C4s>wDrkp6rynD0 z-;3HFwB?;uDS|K~BtrdA52*VQ7=3&RuM01xOUZ~_0~K{tu6-qxSU^oQL9Q)N z9+%I{r$SpEo-|uJHz)wk-?^BYsNI6&_h%OurD!v4p8*~2NeyK~=BO~yc#uplBk)-6 z=ux4{1xxJHwTv~HdZMr!Zq38S8VRz~J>m@|2*5ZCan?Y%hRX`o@jr?vf34hEP)z#t zYsK_mE2sM>D>oEzHnMj7*Sei#`6NNUbXFb6T`*DLfO^>z<)LU)2ZR79bP2x#Z{HrR zLvWTlZQDbSOw^#zKfd`Sa02v*I0rjYhdu6ZAOGA!?Z94wH_b@RNYqH0dQ?R;Vg1}n zxY|7m`_cTB8XdozVlZ1_XkHilJKDNuA@51rCD|OxEda69@ z8MUU|Si}(dYPE2VK{m>(*`lnkkl=poZ@6YPq}$tdqLSqP{yA!~RX%F|QNDT|Y2sd* zh?d{xkzv=h6+%z!hDwN=6V#?ls}&$j9mj(m=Mr!Vi2*e;L;9F5gLb@Ah^XQ0 z-DA_PE+>EC5Ym0A5L5&1fZi8k;UGKq0ZCC7VfHB(gumlF?taAZs9#ox{wgm}{V&n- z1@ZicvbB<=;=CN{M-?5Mw7=2-1~jk=D71=_r|++%JZ0*V7eSMZl?^izt2qF$@@ME( zCcP#Th|^G@B*q8kGreaan|ol5Bz6x9Ut#E*d5%g!P!U@YIs7l;cqf! zn0-`<4SMJ#mN?3>LzoN^^_-JUu+>?i2EZn2Ji~ju;Q(|D*InXpNoabS6a_{)#QU+L zbZ!z`{aI%TEVYUhwx7luZOK$s?O9rr6`Tsj%XrJqMIqm<)QdPs7+E`DK1PImO_$gU ze&khXh>9Gs7aT&teq3sCM8-XAL`>iC@mzVh+g0W)t2|(!m3|%mCR5ZZmdgziV5f;z zLcI||Bq@uwkilx{^R{ryq2)y3Zg5;pv^8H1kI;A6*0rzf=xnH}?fztN<1OVoSwX*5 zXK%iBV&m71lPuxayZR}^$<_*5z1b|5LIKg)sLhS4I4&nv{kSXa$~B>GQy!!3bfpSk z@cr;v>^5!7B0WZg9v<_kqh`F>wiNW3Y$)Y@=4QT-9IlENqRa-F7m<^8*>bdKqJi7? zP~N#|Q*OF!^l;|0__8ZTI|4rmle@mS$Xme8D zOXsPTj~>$TW24DWF$WpfjFUSvyN+CEdfiE)ys{|0D_Ad`6_UFkg@|vnA}70x5rWVb zkvR3BIruxT2c+1H&{BQ@UICZJ!t4Y4z=_z!D(L5l<;?nC2$k@u?+Ny%--_S?OquVJ z@B^K@+nlh5MMz^(0{I$&6?j7*JfFA8J51Yfy4-fIK?mbd(onUH0njerQ$4yA%0W(H z^UQ6pER+ot6FK*Zz(zVS z{o9ZC$3GCiC{}3n(3}X!HfVRzt$(ld;L*FZZtT~c?sf$S1sC;%r4KYUGSqmP&e@gZ zk!Go*t+m4Br#c<6@=7G0O)6N-f#mUrJ6&YKTr_<8cmI~jSz#d|lgkq4lTiAUGNY(@ ze177^k4&uB8yaD!vB`+|4tw|@j!*Fr*4Pz=Y+%5LPtCzZf}jCJy#g;M+M_kz-~Wxr z6*VVP{g)O!9=+DW2~0KXhC3}HY=ep!L+lbYjT~RB3A0oIesFcPtdj5%&S#xBrbe-; zT8>-cLLnC8rEU)v>Y64816(+>b9NDG3}%eiFfuuS)DFxO|2IJ~>i0h&_}W-^%rmGA zvO+BNXN9R2tS2i8KX4EisVO9i%|hjR+1lDgwK9#n_FdX^2)Vaw9O1htNf+#+Cb_QL zF(^H!@b~F*#VHYkK#w{J7a(P!&I3B9z=WS^!eChMo!xxhx2kC6rZld36%X(b#(#HR zdyu;F#jo_h@#^{utNzLVgY$P+K#kWs`YX%k{^KnBZ*_2`bp;dwL_UyoS||`XT9B?Q?LP~}@QWDw#Epr!;B!V6O6Mv)EzItWMa?cviK5BlE?w&6}ICg;! zq_{OH+2@S;&G~tj?5=JPgd73{PsqH%XcE)MTqrAQ8Hv@aV1(iW#M@kuOzO6R)^%B- z=#0!Kk}&Pkr_og`t_R6?H8Y;#y^gSOYI-KuOtMYub0bqcw#va0aia$|4dF{TinOt{ zs8;OErhpI=y7dNd$nPaMZ4QCCd1Mrhtp!-85t!N-LH#p#3?4$V4A8?- zZIH=oY=@?$i3LsUm2`%nm$A9VE8<2{8XV~HeQM0})cUwYu(k`i+Ed_M^!{@&AD7u3Y2tq71`%$F(MQ)=@BE>ubER9a&dD|fZNG%n(JE}zcGH8FaAMx(Ko&FW0ho%| zz2IGP&9Hs0XLl|`T~7ITPb}=PO2shNWZ!*b6%~JVqF%%?Y{{b5JDSH$ha44UClWgR zXnuW8wLa?tmPSMYW@IwFRGsGFJGdlgmNXIy1UkKQSx`QG0e4g^^Il*ex7X8F+I$~K zy*m6tHN39@@z)pbAWN_4M%6?zV#( z8}GNR7BFZ8J2>Y=d_@6?Rtu7Hs-r@GB;XU%U1OJB;g!cEl>l_XXVSbzOtN~RxC

Ol%cLwkKTTL%e#^Vh?j;?^(k?yIGDt`&TAM@5N+31kv5>UK+n< z)UdMW?cq=>Wk6o$WP*A<@|=+Mz6t8=7M`&oOXaF_?E6)aBKcdx-J<+Gw91{FXhXjk zD>z{PG|T>5u3bs`uL}%cwKR?fzT5)wB6=>WX*e>X@L|5U61tOaiu_O@JuB-pGA&em z!Ghi8(q~UgjfHW}REzFu-)C-juN~3WPiXpuAIz-|+aAXoF4Jy+u8%j*Z@SP3Bs=0F zv!Nf5RXb&{Ao8%pbp0`~SX>;m^|6#j`OJpO2TBp8EklS;OVS)Hy^IZpJZRDh1$;q!nH>&O)O$JZu88Y!2RM+urC{m;Dx3*ez^u(%@Jj)8|X>HF(2t) z*Ojmv&B{PiCZ23p_t(d|uEw%zWs&mZbLt_iHjGO9MQ7>qI@%7Wb-7%-2VK8>9Hvxh z`;8vdVOHz1l--uVbzB$5ai(CmC@@sct3CW-FWwzW{kVhdGIEy59?2xK41Hx6tzG5; z_6IAi@-r0?4YhSdBL(&wel5wOb0}h#0?$Gg(5z%kaM{b@Wf7=!8UTjB8dkqiyZU+q zCUaMUK8(n5g)6K6$@BIvqSNN78If1{$vmz2wGtfU;3_z1a&)D zv;4S8K)zp;76pyxcmi)NU{X{@VkATXNUrPIUnz%676D z803M-#ZSp^Q2&mt=$hw;EW*_M1=yIo8Ldg)Ypqw!I|ZmS5kpXTNHfzmViC^D^tJCL z(F+(sG;?8SOr1YHm;P@^_M5KG8Bum5ZE!Guziti0Uq1cMZVms| ztdE`k8YF=@)cxP0^>U#CycMCh0k?vjF}fX?)yA7vNpX!9O<>5|V*~u55ClEJ%rdj- z@|JU_j&JGCT~oh@&vAYkSSIW0tqK6qz*DO>$~Vf_*G|>s1b@v&!F&QYL;uYyHITX2 zm1fS5OOv_vmW*}0X;(B?A@oujBbd!}(frX%pvk1V$+`!7s7II|@Jz009<-Ugs)#hG zN#i3r!A!%4MP7=`{QcNHMUPXhhIHl~!E6p7@=F(> z*?&(eoX3{LvR_6>`o~82Z^3$LNsuoVT&dOm(qfg$xyc_|h4p@D2?1(BHFwGrk%8ZB zI8DASROt(Q(|2;92!A06DV3edCD?5$IO5Ik0-O~RR> zVlpB`l7&65zNr+{s%}30Ns|BC7h{$U13Wj_l^WiGg|x?6>IZH#(Ioe+*H6G`znCJ> z-2hauMVV_?*kG(LJ3~Z+B^A|fbDF_1ryar*%HsacFxH)4C!tdBvookZLyVP%g!Jis35Q%I@P7M$f`s-C!6kSS}-Rsw7r@ zPZKF=Oy=;W+~a7?7svUcD6AEav+DBGoS{mV){tMfhS3a8B#RNOHoG6s z7KK^em~WM-yiro_k>V&w{JS9j^H2skbH^|DM_@KHE_ z90!=6GYobfDQTLzsUpVTpHo*(y_Q(fNol2+x}Cpp0&VUxZ=xuApY@ljPt)3 zlnRqFe+@fG!j#I8wlkCTp*kpzDS-HS22`NZ*@F8ck{W2tH{DJp)W06bZTUd$4KtSV z%Y8r|a?7T$mEc3-5}lompVA4uSh_#o-@$hHxQ$v|_w=wV@bP!eQU_RQvNY}k!;|Tk zv^tDcnfrw~wwYMFN-PmWIHk$eLEX*$IG05u{lCy0#_5K!?}+aP#3=WndgW{oU7H5B zf{Pfig|x|}8cWK2%<`T~k>ta(W)eO$9?UX{G6y*{6LODQmbR98L%X?sy}J$zvpc4H zaKq?LT_F$*#ze zy1l&*w{&0-d6-#+lQ+V__63)%!hWH4s5S~N3OsG$E*@`zpXmmVN(A3Hc1e)s<;z=8 z4P!6B`Qf8QEHRBgTA!e)Y|_=;n7RB-Mod4?#F+I;_zVqW$hGtwXTGD&C0M$aex#js z%@I?ra@I_g0EzHpTf$sn&emTHQ`5K)82srzsS!rXve0{4AnXW#dvXna7Ric#$yv?4 zZE4VgRc737cF60*$sK1B*aFNQjW*#|H@X2Wwe^i4KFxIOZtH#TBZ4bwoa&{Q=kjN1 zDC<17wa&(vA`=ZZI$#}zUg0FmB3jHf@(y)rg7j)La(@T=yUnM&?|*clef!q=)n)Ug zY4ZQsX!GBI=D#dns1D_su-M2?aX;zFvh9f?IPy&h*(i}Q9uX{(nHCyKk`YmYlHHt1 z$QY8OA2t(<-Re@be6wNET31A3ap4k8oq1-i%xg`qv3>Jr`&y@$M)qBv+owaf4f+Ua z!S$`dmHpM~71wc=#}&kM=Tjg>h$+#hT5&gyBf+2aVD#B;$$Nsi_hR3p?t<4~nNO>j zbZzt-8ep#S`A!aAma^z0A=ZfO%hqL!dx>Whh8X1=dADJ+r+p);V`O zTv>54+*3_%{c_If(m;Yyw!lOpw=AP6{7)=L3fY7r^)!H=90^HxFvUG;stno?5Q0}i zF{EW$k2#nMh3g}@F2?ACzFE<@V^Ge-f?HB=y<1d?TWG~3nyo+LWIQ>gv^csFi+q}! z-^NL<@b%&C29Q(qB!g>(@OoOWTxaJkU~f|@GHB&yk3VQLVV zHjTL^4f>VsJ-7d|LouQi8fe)cibKsbeedXDr8L z;bF_IDu<1nb|#>;$+?ReF1;U>nSI1x^SL2vDUaCWu(HMzK}3V;1(&_4pP?- zw>7XRa1qntZsfr-O8YM8t;1bs;nBe{58IX?&e2pjSIgT~tM7K+SkB?x3niXku2Q;d-Qt~MH1*U(nz?-+<^84&3dCp zIpj9tX*^^qOHHV71*5tt|x~%AW{Orp2A~rY<%@k|TjAz0pB=RsWT`8|miY>68 z*5ZTltAqFS6TlSE#t@bwfQSi8OaK?L1TUd^W3+F+o_717u`=L9$zbbiqdA6I>{{m6 zR?coMEpfwG+<;UG){`4ws<5LGhWnj?zv0Z)^4%_6MGUTijfjrI)sL?f!a{h7S^8+0 z+t^R6=uioSg$#58YZS#LAoK^kI6-Z zn_15d1fA2polqAR?t&JPXtynxd$8y0M$EUtHK6y)0db>KnRO z5p_>-?E0>?Y{h&Ex@|s}|BwQRmUQ69V>-on$OCj2VCcQwn-Q|7ubE-#xp2ozOm|7S zcB~daoD#)v_ccd`oB9I&JRr{PwbWpwJuvdI(l3llyBlVd{7iW;n|-Z(XE!vC-=4Bz zdBuC5vO{V<@$5NpxosZ~^eP-}zQ-f)4)(Ub^LX_dSs)eXn(~&(1_=k%4Rwpf2bz+5w7`8JzqVOGfJ=@4YMQ=k8s;b?jtyI zjgrvs**z-u5--Iva3V(lC`U6A$as7cV-dm)b?KBtiyTJkJJ0MhSr<&=!OC(~%Nd#7!?n|g|ESLae>w)dt0H!n$$`q>MFBotDO)vsBs?`^aQiKBd7v4 zlcvP&ElHgksd|*jA_^vV0}QJPHFBiNjdR@yRmiTswT?5s@~mutC5q(1mlb02%z;tc zTdmj85<+U(#3@#r6{tSA$$(|nFzcErR&Ke+AM6o(iMDUz0ncx9{iJ#wb)EnfE3bd* zNKMz=YJc+{D`nTEe`-idUPa3?Z!0`f90Wp?}+TIWye2iLZoiemv#KTExyPEAygx;o*6a*&bhJ zPOTW5*h@g6Nae+|H>z4Br!tD9ni(Yq22Ekq-2bc79(QkaV&$>$53uhb+)d0GJJKox zX+8lV4pf+?ClKnmpVn`dv1KTzR5gP%`cKo@{X56=4R+%+Dk`NzwfuGMgkMx7`BFV0 z+Z@7kAu{1p^X+_bQ4hw0^AQ_dM5$v#&6f;ROeT@B@plYh=X!d434E@;CI;uksdyoo zPMKE8L@O+Xz1k6sziGucT#TN>6=}j#-L!-?&sMNjO)z%y!Sq(Av7<6?3mVs#gmES4 zWrvBAx;YA+)ikOb@EHb$bB*MScLmSSMMhcL`c<08u-1yhV9}hrtZs$#4xHz5D<5^` zDAw8>hTwt!AI9D>x{`2P|Lr6l+qP}1V`s&-ZQFJ_wrzB5+qP|YaI??e``)|H_}_Ex z7*(}CtuOV?s(R;~&-^{XM*KMJBt)y$Wh^X}0>at8%A@4jD;X|gNOsZPc}A^{=aX^* z!A(ITDIQ+YuUgGhKs;zNVoz%p$*)^>*$GQi!UJR-b|T?VcN$~{qkN39Y4W*AfAvgu zbhj9gJaHr?Kee*Mo=IJ)=rn9;vf~aG*lJ*Xl5h`d?kJ6as!6I2F`va7Z{s~pp@x;U zN@vDq&GDug+|fFkjFWQ9wazidN9>GP0WK$Vw(PwK<*vsr2bZro=7Zt}nV+!nh5?b( z@x=Vihv8M&fIXsw)tn3MR{AB+Yaf&&sv{i3Lr(W7m|l2%u%lx0pXT)qqOF71s|Hl1VhtC!VnEsmW9 zBKMXu031@^+R>Ux_-q1Vu`Lj+XZSC~OzGVN>|9A2j*9YGl_Rr&bZMF_X^OO#yZw13 z(gS4hQmlOmrfN*T+<6xDAw@8=@||M}geBlL0%fp9DtrP(N0v#AQdn*fS#>)!bO$BZ z^t0ez5ze-iblH^CD4aSnb| zS11c_rMSOkW7k?Lwlba!_&!{PILJr}JJ7rG9>1cCS{sK07Szr5wD`~($qZu0W7O9} z4>>R3P5?N?aO$upF|V8&7*A@*G-mRk48YkxVzv)|xwr;)i}(x*+^H8N`^@3+f;gG; zr@1fsLCLG)kq8m|+A+-YXv(jUkxW!|5|pFOs>sOb&-TM^s~R-uu`V+yW$bA-+44iY zi7?6bI|fd7x4!LZM7jTw`#|;b46byKrCk5gtp56Rsyb4Mkq?TI*54T4akju$G>=ID z6gFR>@hXLigx0UeOqMJJI!%4x0B8YY(jmmk=k$|MU=TxyA=(uT110n*g6MtflAK-> zYry2pJk*UL$?1`dVP;T?sV*;{lvk^~=1@r&dB@-rn_eG)w%XIkaAUlSFW8psXPTT% z*t54+7z}DEr9IqVqUdYtxG`_GyX4s=*}k*dJ9HuI5Jtl2laO5qaE?BEkQHQqW=Iow zxOZrLP&15=52ex)QlbwbcnvXaXvd#8*p7H!xj@M&kK5aaEyq#2G;BKS?)! zL>2qlpkrh{TX0}qx$N2<6hmM-3v|MW35LNFg;PT$%>VT4Wg`#4!?#RgX2 zuM-t%uyRTd81gZDr;-Wmn_!+of{!1aimZLX_2^wv?Gwehti#=OnfN@uyqU}eJo5CN&O`D&e(y2k}1j;zF>Gi6X83RVIwo%W}rYX4^ejTh$VACvLQVo91$bpc(Y;xRra}Sb(bi5*i zE~2s;G&A*$7xJQX@TUDk^nve?f!u2W>j?~GD}cwNcXsd5^J7Rpy_D{)N&mwSJ>-kp zM5J=~>r3NswsyVNJ|yn}*|>23-Xdsw-VTa|(Etp)=9z@J3lNTXeI?FF#`7?Qr%U8-gtGlRhYCAy4DN%b^3htTp$kMs}tzxi@TvriDgT<6|ckWvG zutj&wJ|F++(a8%Y7T2gM!idi@i62DjJozxTQ~4H*1?O)W(ByTdu4@oa>B+&4RPZY| z&R!S~MefZK&SN)M4%~(dhPut0()W?!ftY#{5}$fwcukaldw7;5B0_gyh!sZ9A?}d zKDByPj&!Z(+Xb!_9-x7BN>LdAGh3z1RB@+1A1-5Gb%1;=6N-f9OB`U?BJbXW)p)B(@hNRc(7urAWcEegHBIhmutb#&ed`89g z;m9QT)e$vXBd13=aX;Ue@v6|$NTgeQv_0WQMVz$}VjUJ z&E)Vx{p7MM>1fKKqx9WLDV0KDedxpKtQyfGx2`8qlJ*s#UjwEC_C%DU%@M~D;Cv=O zy*$~n*BZnf|FWV8t$?(7An9xflez;;vR$E_E5Za@$}i^?6SRJ0HaVbLIG4hE2Rq8E z4diV_RnYlamgD`Al(+A=;jO4jK&b-h-=M!9H; zOv0TsR^jMGkSlSS{|Nsjzy~W)Rp%C0{k!^Bz#>^E@6GXnj+L=8(@Lh}6;d2M6zr5w zQzbFAkd{SotzD{6{qXj(xe&Ak{0JPox0sRsI%cSYA<9a^6#_Hj{h39>=V{Hc%<3k+ z$%}@rbT|-5Us)jw?Lt*d$JvtynL|p$EsRWEs8d9Da^C-*+hg=;4f~RwdHg~_ou?5d zO^Kxa?65%KvXiB0$wwTARClxUpzPifo`6w8ZFz~1p@v%WDV|N3wFB7rmBocZzWa~J zWfMrDrII{@EiBB_mJsnk6dG5aAByI&a11rQFB7*Hm4n)+lz-L0hM=%exW)$7$u@c= zuf#RJJ=vEM1$_RD)>vS3!f+pWc0Tv=s&n1OVUtWW{H`<_)cd|n{S5zmLgCuHQ;JvA zU%ePUC68IlD^>G=M_h0`qBlnY1Kfk~bY-;1O+oKi8Cu^@g&r+G9foti*WEytL4{e% zLr4?|&NuOJ&mQOci-Q_>k1lAw+kD$U+FlUCC8q_Ouj^kV-d0(-{`k7vP9axyH*hNW zZI^PN)$IbZ{=%Ja44jNRG!uVAzp5oYW$<9=+S_QosM2zJGJc}|0_MZZwwZkd?@%Z_wEq0nt z)=ftm{3uU(>G}k-W1)50n~iS%m4VNB=$)ic?|h0r#WdS01yz1vC*y6}D{nN&T^$4`xN+@wFkL=#^8A6Y${fjt7@hcXI*lS#37wA(w=UN!xF`YbGh6 zYN>8I_M~{xwnbS%bxK61&aGGDBSRsPOl;rg?`g`DgJ>2c<|e_9hUFJT5^ie|Aphv3 zgY{C}>=T^6ru+fHvzU8xEyMjU`=EAaWF+P)FPeP35;w4Ml4I;4wEZ0cI98XFTc}*# z)ML!(hhlduV-6=Su?ppbQV`ku8_>$vZv%K~V*At_2IX{EMGL;bPNFXt6w zpd)Hi*+`-X5QVh>5(%@AOi7sQUep8N?RLRmbs#V+np@Wi9hOGt9`st!!s(EmIu$gMFnJsVC%u zP>y4@>tEo$*CD-F<*Cd{4O$78=yH}M1xn_GiO@D=RLylq`Qgs!Y*-=(tt)40P6RQ^DmVYF6T(6qaSigOcC2a_Yv8zYHJi!(gx(^E)QP7$vtUohP&zF;Ov#=GsrgZrTD zwc(;b3RsRlbqV`Peuy@cwv zE-XJ5weqzkx<_PE4A+zH>ZK*plTYxX-Rz~s7V{fhM4y_MX%U#`hSnL|$C8kBb2y3_ z3oB#}+<7L!NoVc})~TC(h3wsiYou#T6;ARhBKInFr$vHmoxOL!S{oW!|!xNJ(Rwpuo+~1A<^WQWsOwik{bl>mD z^8Z~;@$c~X=XjG!buHJ7shdZ|cT5>c z8J62waNpmzQ_aH2aZkMxE>9Dij?R3~oSMGHIL4)Fod~?R&F(%mcN7j>i5Cfn?ZlClqxIBSOQ>*Yf#A0wc^yhupYi$?` z54z~@EMdkUOE()^v0rF=t?g;7pXEkQ5fH7ho`s|8TJp5 z=_odd&KQlnQ@*Zuf?SPB;e5TFx6zZ7JTcP1C6P}JLgD?@NrWHRJ2OvSt3vchq92B% z^r`*?+)AR1xuDE2(7@y|V04wf<)aSLmV8f!leAd9^u@NPHM%G-Y}Cr@&G61aX3keI zRXkCu8msWCN^tM7I9ZZ!7>{VAY(@)$aJIRSnL)(=5{(9n8!a8p_bi`f4pe{h2%JGm z7yvqoC}OVNLnn#Y&h;jUiqxo!TjUWY9Sy zk{PbUi;|^rOmu-tQGQwaN|xSni8{6iUxtuLGzR`Cder^znt0)UnC@swBeoxG_2N@Gi>KrUOZTAcd zYP(k%^=IXdtzb+4QXB2M&MoIQnS*#A1d<3N)^c>I)B+=&u-BT5RiSi)` z2CRvLv5{=ggb6Y07gD_5u>-;>WfcZsUZ6jG9O4pto5nCKn z6K=Q?kF>n?4eL}YwnRC|hsTK7T1l|kWZ0q9t&bus?zs|p+bX9dkpq1tHV9;(nl_(K zew>PxGq5BYRGD;gt}~8vLnZUNKMAW4zerTDXw3K}G~1Kh9zqUduPXmnGA-9QdHO8c z(&SGO_va1AOk``FRF;uSXJ}?dtL3jc0%TO-#l-O6l(oqCP&^Tn!0>y7ke0>Ma78f8 zhz(@x?bYi&iF``+$yPa*>BkXD0+OnhiBKXahGLL7nN_YpC3puPO*Pm$;{ zH7>SPfrT*8IHvb3w>H^n=yJ7ph%8gov`p-SmtWAdnW~|@La#wZSKdL`q$qN_);_mv z{PEp8koiYn)OWCa5MPqAyn%6nL)G3x9oLfI)#`UOhRsIjLz&?IrRl-EQtL^7trr(8!-8`KP|9_2TCV?w8r%q&x)6^| zW_gU0Owr+@*xd?FPzlJ#=Ds{bXpR7yOz;+XlR=3H2X0KJd)}jxgG5hE#Ts3rJN>CF zr?GZXayQUKUOtbek&rHFNIrUxu{f+owRHX~)26*x>`msVAi9Y>;f<0DZ%ad>*{5j^ zDr!XeP&SEDi!o`rFxt+U`ouML>~8}NtvoMMtU1p*^T|)^@4o7$Y)p2i&pnN*DVbyd zd)XjT;9BEzd{9rmuC^DBG28e8jJz2mpFlZ}63kKA7F?KCwe}A|aNEx7HCZ!4$fT|v zD_h=i!m(xZ7z0#qLaTgR!Zf^c?;ZR_tcs!lvnHke%H;BdMFm1)^>8$-l-j@to<_mA zE@W#G*Z87DzJe2CFxt-^4w7!_XdYhz*px4@4ew9;gA(~)Z4+<2<|ql(wK~5;AYN*O zjN~p{OXQir{{F$fWNUhWhl&GSat0)A1*N2SbVU9vu>q3`k@En_=0pFkE3u8Kh#JXl z#m}d>fGufeqVbYvKA?S5}DB*Mg^FM=BnntUR#$Dbp0FDQ7 z%}C{yXB#qcRxikkJRoG)yeDt094j_DeoVh1Q(LJ@hRE5ZsWQbIn^ZMhEt1mII6QO8 zDz~nBZjD1=xotFLjd}J$louO*<5cS@I<0OsGI0hM@z}}4soh2JbVdu4*iJ3rWdTIr zyMJ-&ij4v%1*8BHRY4`x&0fE#;ma>;mPDGWQq(QjJ}C+hWsXsYWQE{FV?RSjWC3nr z%aFQBuJwx2<0Z%qR2suczTq}F+2nwzU<7vgJs9~nmKV|~Mx=n?NRhIvFeLH~j-?pm z&LSi0r?{s7(kw$htaq4Z!);ZXujk!ziAXM1qE0qQ)FW(wex6*tI~z>k-0K&4(QX z9Fbp7PMm+Z0jm)rhJgn4WX3sJJ+^K;0Mv@zEvZM13ufQzXB|@3eyUJFWO11Xe2M#;&5a4%YupDS{QYEx#w3KG~vMwLI#|_$@x< zJ*@#5M+=Hx2@w#ClDS3sbAR}>=*NxEjh{!VVc+0Uy{HEu_+G$YzFp8v@q*+U82Tr! zCfK(prfRnNeSqB}#OTV^%6bQ3(JNZi`&l>Ym#i%}>ntXFwZXdrVM8KLp>cUkR#p`I zS@tBz943js^^tcH*sNi2p!l6C{k?Rc{OAg)=r96~HV;#TLakLEnfi73<2pm!C0bY^ zldBu?Cq)}4rC}6I=hrfsUK_FZ@VEA&2vWFdMv~BJSB~?lyx!iJ9Ao34LjUqC9OdX2HX4el>Nb?lV`v1GY`pE*hrKWmZxBV|+- zXKQOwb%U*T&MkRd8#)wmXg)E^Qj8o^chlrHB!!eRKop|*g&Dq$6&Q^Cd%9L5)le%v z*Fh9?B%_CuzNaRxIu~c*3^kxjQYY={gKLQv)uZOK>h8Zqe}3Frss+@KA9LUGJJSE} zg!A8#@vo|jDzulTGUiv#k$vJru}r&3f#vg=hFM~C8zJa2P_;HYlsfZz-7HYG>!`LT z;bc|osigd|u$SMS*N&kg0{Oa}Tv{b1lduSG7`+!YRhRCI4@gYXHTN^wWiy$k@8s9t zspl-muHn}y*939^&pU-5v~X|${7*YRK8zlZi$~-?vAaSr_$DohyL2J33>)8?sE`jB ze8ZN|UB7@ppdh<2_O_Hi?=1m_<)-u zz>WZ(PtvYLNDZS`*0FEkZU&GSGys&~#`>tM|3+KIE4KCfKhypAEx-IJUi5$9z94FT zG4{{H@XOyQ0yS}GEiq?NPS!g~<$9@&!(g4#iZXGN5VV;G(6(x^gN4=uD+`$nwTw!e zHYsg~B{R$g0w-}lN-v2|{I+6emf)SwYK|)0YVQ)#)1R1tX+8`8&1i61wWQYU%uo;w zg9%xY^l%C6-}ELf3NTaWrIcIePM^Z+e>3T4YA$(TJ#9=|tPe>eLq8s>UUsplqNL?r z;p!~z)N=}qmoBy{Ak;*{pG13?t3MK#-WG_b;hZdBB<3tNty+?WqBkrylj5JeT9{Yo zCEg+KT9yq+$i*Znc2au_OC|(olL4S;Qes(-;GD-TnWLK~jCNci$&@Utd4;A;ruR1v z;jL{Yt%D5b`i7Z}G1?R-cn`_6_3Z6&Zkl18(tt7y&nbEYFfZqk+F(qi7MvQ524|U! zP0R@Dj@42KyL9K7Xj#kKi;Iit$n?cJT&-2F zBdoJ-)jF0%Qra|fi)*Nt7F)}>w;HMpWsZ~1J@-`~D~+|Q{9K#j+3HiEGMBaX3PZ%C z$4AaCH)%6KH?m9;?vO6V6>6~?<7Kc=;Wz2aoj~~uV8}xr24KBcZ&4;4*j@j~d8d$@ z#maN_LvMY!{><|@U zua>hX6^haa=~vMz*wY9(cYCo2v4#PG!RmL>BAjtL+X|PD^bk8Y5yICrH3_-Ys>{<< z?g05y;YNRztKu4B98K*uO0NWCAU;TcOO4t+RHejcT5Y29f{j`;c&_xG{PB+4e~tOs zGZQ;W)y$wZXvf-*zuMFA4R+zLc!8dlBq$Rch6D9gzGZ*c9^}6PpnmmrgMNcl+(EzO zZ*|`RgN`dgi)DN6PbZ)k^0&S?s<%Y&06S4xJ;^*K1i{~BeWV-ZHxM;cDCq=VEkdY1 zh;kyWa-e5Yys9XT5alNVS|a%UWCNz7cjnrXxffU#oX}Fs7G;q2HPasbvZE0+FAlV6 zQK)Y(t&^LkR|gJhR*9O05o>{)ND_25h0`s5rW2nYJGc&AC`v{T`i>=h*5wIXmWIP_ zI(oOTv^HLCsI(NE!F2jAUP1M0s`*l@)@cg4#~DIGWzCts*C-lB-Q}bu$J@$R-5bV= zYi_4-SJDEjdm82KD4?28FU{6AP~y z>3MxNU1Ip<;R*xMbTl_+E_iXWSe>WN{4H+WtTj$pb_+~*SsBvp#`g@Fp-1Tuki$Qz zx`_P_+0}(!YRzl0!r*^pSPx`4Ye3o0Q`6$MAvl5CdAu|E{#utbIZby=*4xzubzN2; z)1y9kr?t`B2VmD0GKSj3Elj&&8LvQmdZK!F6rLxgcjc$ch_LKrlb@o6>RitFgeq*O zJZOaKkazyz|B+zhc;Y9-8fxQ8@hBrv<}4}sk#U2AQHb7>{lc{IYkwi?mLYncd5G-e z0r8y$(M0?XX5$8-lNqVX7s5EvPDBgYj@gaq3epV|1@8^xJ-#0DXDYR+9A*FvDoj#* z0mLI9-vVhd{*4OXJ7!;>?r}20rD1d1Jdyw1Qf)g9buxN z+=I|4?XlYiMW9yu$HmoLk!H@&+j3VJwf9F9m%cs{&B;&G0aB9(1P3Y3y-0^`nGD2f63&({0!rCYoqk*> zv11oL(1eNnjn&KB@CAl;uNA-I{vJ?Lo4_*X;PfA)QSY-bPa=`ZcSo)nrW+PUl^6K^ z4Oy<(W~lHm*Xi_BaW>TX7*b!9X~t^7v~^gpJPBnDb=4;+Tr>5cv!BJ8id5)j5gYi4 zg_+F6!pOqV$RG^Iy}lX1zP=8b7*mpI27igL`HNC>NvM`bDNE>_YO33pK09`)&CyML z;ZVcpC-sIzU(%Hk9QsY#MsUfdHJ)q6@=_CJbMqv6`EELlXDL~~PWfmp()BY_3Bxh; z-@Ok)`U;_l1Mh_A(9VwYA9en<hhKs~SAuAEJ(T1e?xtYdB3y)9>b2C|; z+A(^N>}eqI{3d@K9lWZ?V~Soe1d4WW>dccYrm_PM7`xh6jp|y<)IrWhk(Akk)4$5$ z^~m`ey_zT;NjHK>ov?qnOkZ(Gw`CqEr#z$B%s4~brOlo@=dkRlpXGf=cJqZePTB_K zMNb>>q4S&3Y2ssG#J}h`gW53fuj(q+RYcSja-0^CwIyT@43>*xP|+@iy~tunC~N$* z*c}r(bIwMzX*A*vtkG|L;wcu_oL(IBaE1*cHa&4@4ZfSaBuBil2(J;+#MEz0 zUA41@rG*M!A_qTEwuRIN*)K3!lvxgDx?s!Tj#5%=iQ*d(93O%QJ% zzFqaQs1jWxqz~wOx$B1I|8RR(N;F3ihY7=fo2$HWh>R7&Ia{J^D08k;jz8xjtg({oI*(UZKT**9!Fm2A^asT(Y62wIYSZ=YT>DzL7v)2p9l zTvZz|GDJ+g=Cn+dX&x;O7$qAvQbbIY5s{v&p&vfr*hJyWyPSf2J-;eOj##(_q4HFc z3i{m8JOwQRfO?OSADnj9drVe)qa=pn%?l}sJsdlj{Jc~z|Cp%3+`-YgG9bjL%!FgbA>$a?(AGrR|Tap&m_Wf`oQ~3F7!f0!t{5&WyuQL?>q>Nli^Jjz+&Upp7RLOJJN~ z%cCVVPxN6om%-|HO5G)epNukOGA;(8zmopS?qYEfoNjHehjH->AL4u7x_@my&UP&P z@_L;11#*K@WxeQ)i?|j+=piKQm)0@5ONc;(r4m)fS(*tYp77!n%v5!ARqDrxxxH29 zi(%%RI{1r4W+(U!2r6V82wir27DDBps|BqQoAH-0(VXBb(?`GaYFu5>Kc$w|G2Wtt z<0;DT(Wt4gV@=33xy7#3HiDV&pf(pC>&V#M%%Gy8DhJLp-AvG2r=M9^Wu-&Fp;?dl z#iE;?omRzZc;(K8dxk@)TKtyjHJeq&<-ORdv7KoN;B2zkCOA#1u$er?$K#uA60j~e zm_*s|YPvqG*DQa89vHGv`HYsgJW6%0KM_X&9jlJOB2~;?jpbIei)=>sI`Qe1YLaYy zk;_w_V5@en+>?dA@Tfhm)(5&&vTxSFXuJbG-nEjIQ;A$^y|a?p84Z=#46nBL;KcQ; z1LS__e7svVsbpVayJofKIT&jUV@{lMj33}6-lrnFU3@N^Zu6s=wW?ZPyfETU?{VVt z_X3sgO_sp;BfJ?lAx!p3{e zCd!xPu+FM`i}+c4Ju*7~Q~6g_lBsB>V&nR44-raO+*7V%IVY+=!)0unBB)COIjjLR zMOJ_R#@%Q+v!OE0Swx6`E*6^UY0eqHI)&=Sy^%5Mut{ZyW;#+B`%JV(6R=FI`FZ7a zg7g5V{{Uu*g5&*pRQ|k*@fah1V*6s$RJ4Xy znT?>JT9*%1WhWn}@)3`?eV<*fR$W#^Y`n={Ex2{YwBML)^X4#?JfAGfnW z_OsPDWrT7Lm%;p2#AJK(ANt(h@fSe8f)9WKh=8syKa#2dm7>3310@QC;;Qz_>oB0eqIQrPvX&$H z#Z<-?+Gy4zPRumMyKU08BL+C69+=iu{?soc3ut*6@F_m-Fm0KVQ!!0`1+iQK zK{2I@9_?+K6lTlK#XYm`pVQIOBi3xwsqkyxGcUE|f&2%v%QD0P8-DSM9@55;v8iLUg`OJg z9F(zawX@SD%8jKf_$46Tpsz7GWWzFz&CeS{$?i-Y!L%|D>H(Z0P{;N=IMwCFY+UIQ?6^TwwMvD#os0t^Udhg2;U%BU*xXH*e2 z4ywZ;YNSf4kgrto%=IVitA(mJFdKs}~c+U_?`JYbB}cd}%T)#u>~GL<2D94Ug$=LzanQ%Veu0Alv64 z*H9)IWpx}3p3s=~LM}-*Bq_D?VUBKSazjHOQMhD@He3AJGMGp`F6}_0;;XA@NZ7Rg zWNAvw6v}G-{bu46K7L2;Z6dVRBw^YJ;N)-6wc!3Oxm(v zx`kNh-@`IYWXe~53f(^iM5eG>YoB%5KcTS6FJ+i@g%im%${_pn0$$Xa z7z6**S!Rm@ZqQuZwVwu@2W;~V0|4wdMmCAV42n*UV+rvG+9g>s<0*`D+Xv=mmt$ni z{?jvqii>Q@;a9B8BmF9}zHKGhj7Wv{jD*t81<%nn6ws*>xzfgJnIR0-m&pxH2!3XQ{XWPb(H9o z83}cN=bI!PQP;2ZAkz86*(Y|d-wNDHaQdXX2W$jDT3)Fi=67@k{nUExxuS1SuDb(q z5}qB8J|I8+$S-SGmT!r|RqPI+S!(iyf>dt=J>R4-1m9zQ>UO^V^PA6jNb%<%WlN)P zR?PoN5n*IwL@(&>WGvv|pzp5apl{=7@^2t!bfOdh`5PAd_2-_-I2Yv`u9^R0#;qtO zA|N7{$7hBhV;x<>@hXo#{B;ioc|@0Q5kFEC~J-JQMQ)1s6o1r;8jOs5Q{a)hnXlk0yxh1X_jw z*SoXF@Q1T+P@c}C^(r-BALPN6C4OnZ7HU4K-5_ z0==Rei1YfZaeySyW4*)BB+GuDdW-PS=L?1^MzcB{hN{h0v%^*o*)OFGjVDBPa-mNc z?~L#-SDKKejmD%5%;XS_j|`H=NE!Z~awi_~c;Zjc3;U!N!;4bm z%}Dn{kb%FxHOn3nEvzF1%-&~AF|jyQD`z$FuEpupYc1Qiu2NjjUh&@!)|<8(&$B^o zMcEq%5URDvGg+^X+UH0IxJiyuCE@U4OY$(YX71vwHjjbYrt8>n+Kd+JIUJ^&Prr#8 z35o9EwK3rC;A2LyM;f7D5;Zbu6dgdVB-s9{>S-R@RwbN6XGj=_h%$d%MxcWhVyyba zVB~#5>U%^DL-x2RMdNl7dY>nZ@ZW+FbDZ&2b9oq;>ZsjdA^|%cBeY>(*#$S0MR4`{&>Z+F~vM4!ne%0t|B`@ruSi&@1-ERDxz zJy`utsmeSVazgr#bg`1gF={Ivk<^s=Xx{Ts^4G)1Ta1Ign(OdtZ073yUNz)2VDK}Q3mQMAc*rMgq(o@WhPc@bX#G3VF& zVF$@iml@hV*Z+L#NdDrOd3~3K9lp!L#Q%q~@IRh9p>HQd16zFuqkk5K2UXqOlonCH zHYe6i?ts-(;|TQv2uzYmR4huvMZ|%f1O?-S83wR)$YPE3n2=4$dY$T;l2q2$WZHje zN;M_ZSqK=XLdAUNTJ08^n(eDgR~4I!P!E~xw)Ko8UZ3+`uUFj9TMh414%40YFJ<7& zKU5-K=^70;KyhmvcOTtA;B60fJbaqL#J=;*x{c` zTb6^9FqUuJKk*!P8LkXXuZ~Yauwod#$35_jdCg&Ugn$fsIl=*-Q`2EC z@wrw3DkEjngF7a;u`%FN+e1|xKny-Q^Wv;qJh0m z<2`}K80{uSlg?5gVUTDMP`T#7noMvRJiylKeVaA{mJjl{X-}NKVm_47aoHZlSiol> z41Ui2*EKDVM8x0_N3fSifeH`RE*QB=9S0;CWF1o0fGShU!ebicG7E&Lb@_(rAONR( z_@^WVf#qCrz%ctQE$;TwF1MTDU?NvnBzftZ>Dy*g zmFDoX-M2r7)!eN&PthJYmrqy;Xz~H{+)oW${=Qge7(XZ{eZHwBnJO-w@$woIx!BVe zNT2e3Q#a9JxQFu>&OI)!I`v_SA*c6%TV4N2;6+Dcv0?h#${5|ci<-1ISjB&2A@6v; zGq+;Jp92dyx9GUOgN|G-2I9lp7Kjq06{+i~2UVVOc#duu5mLjCY#MsS{o$M6^+2 zo&*SsGS=X-5~<*GH-I{ZF8>u5VMrIpnnfsEwS#lj-zt>_kgF$GRmAjjar-7ZgE*+Q zQi6daZQO&qJd;+Pki3bHV*phgsiZLw2XW&>KEcXyGThO`lStUKEl`~%W+_h*{j$eg zwGb8!w;okrAu@4d;QXyDNm_YuMC9gzX|dT4mK2;o{7@PX6PvbK#|Fi_9q5?iwngo& zj~B?jUwp|N48Ue4($^?PO{%_786T!Tj(8t$P5J|{&D;+!Q4I7TWm9q+htEUGP zf!@=oyVLI}VUMoZ=`K(ilEz0VmX1d2JGyZUrKwtaWUSNBCkd=u4q{+37C1RsSWeNr zF;Dw%)@9r|<%bVR)MPR;XCHK3;atI!wi&JqDn+2W!#uEgdTPE{Tv^u{>)0Fno zEF^4T!oz3a?uwISdLt{i>!gYqW*4+RTDcauYcr+9TsYd_-Ox?X_nM5c+}@@?oz>zM zPw`$ji1Giu$imI*tlwN;m!>r*@?8~Iu%Xai@3dBvOwr@+d?H1;2@Ir&*8Q>c40V3K z#~s&Igz%7mT6`;+?u_IMHEuqVw*5FRCPp=s0xxc>3647v>8nOB#N& zK)q4%Q3mhzFVT~3bq{UT5beNpM&+i|Zn8*n19y#~j5$nrBadNO87~fW=;0Uk(=ZU* z7-(!z$Y?;$a_g;djg8=Nm%Y6Qjd+i+30`w%>>`f*mfHD2ymukz!D7nXs=Y};^Uv)gR#GdhC9jY~-DFJYw6 zu-vL6N+dRFY&}afshC~1h!U*zOUixM>!eg4)vU)*#OeMw)EOZ0L(dfd5L9 z9=n2%g0jW`Z0%-=E1SF^fQ@$O*^S5bm&R)HmkvCL=Z5P`Q>1G*R_O|0X*X&@Rm&`^_=qBiep8lg?GuqK+R&;lw4b%FlD{`Qj1U{2`J{G> zCKl}F2uV8Vr z&>EMplluKLwxi62$p^jH9ZM=#t&_#*BNl=RgNpQ2q_R1p{Fn3;nmq3;fTrE!N#W6s zp~eZvwdww}3tq;lad~dG`lz70EnU_+P z7SdIRRV`(;s{<-?3VJQSqjj4SCxt61@5}={F>_0F#B&cE^UCXTqV4c`3p*y~SnI*;G`RL`f#o|8-u+5e^)lcuVh=h>xZ*_dUmBr=#2la4TYcWJYX8N-FxuJ#h0`&M*9U z{DK=)$IT&j6wwqqEW<9w5Db}8sdt(HA3b%wwEk^__8#A1s3j&!Q515bLEZn1h}&XC z6Ku$DuoTjd9~A$;BH~{fW>s?~&1J;T?B)`3ZAb)&DQdla?Jx|axd;`PJ&amyeV7FV z^3>uNe>}D3xRXRH!SuF_O;YXomdH(7`}ObZT1+ns%ZjyPiOifgV1sn?^V!tPU_OnU z(2zvPLzXi%&GUH5%hlv+qZKf%>+1p5llAfknEI(5VK^)*D3VMfJ8I0W1g0O9lNr!z zhdxJ#(<8u!VR%Q7Xio+|>Xw3hNr$qppWU4y7OG{p!z=4ld(g?WRkHK(@hq7aoR$7xs5?e}Pj`x{1Lfiz!#d5-ZNCuWXm zu%UEJyWDj0<4XXv?m&v#?znVUU;VJQ8!)-sgL1NW^yqRL&*^{!=5`zbQFxF|<3N*= zBtYT@X2`GrL}D_m*}_~#&|LQ~-(~I+!ZK1nY!=Ahhu#M;$gYe=k0G&DCwE=XISiW_ zciJNM!l60XS>eXK;Tfbd!cBsPdU;~De9V1X_Uk+}BvXOT)-0~!5dfq@#!pTxspz35 zdr0cLW}HZo&_}28Jk|oib~N+A(-i^UP}yuWCzg_5Y=@W`)u1u6OF#cF#@;bX)^6z% zE$^~z+ji};ZQHi1cGd(ZcEzi0I6z9YwzKXT-sT#+kct(Y_B?0))f zAlF~c9jiuIg#|&gJ)1vt0KC5I1Ec_U&~9S%qiv-kvDNA2QWB+RklHB3*C&El)|iaG zNMZ#=jiGY2PvC?x_KU+sQ<72QPxdu)TZ16h)g0J9=N1q|bX1)XwRcP4QjKAQHe)wM zpUtp9+==F9W{=nv?L2H_)6z7K3Zt-&8ZdQ=_%xN$BV)SpreJQ8yKVDTB9T zN90$AW`%Z8+5-;1oZS+jnFCNiH1~|0yVDrvO7KN)ypM zfbpB^Q)8FywW{~#$3uDt>J!ix`4uS}#SMZEN^39coB9^*Q+e06b4k)oc=zTtq?hlG zu6>>6Dz5r?dWuWavfagHdq;C&ZL_&32)4>i-&nptZQn$!bf%_T__G8{z01s8tW<-# zZBg)@5GviFIxKNJ_UV!(-q34_35LqCWf|p+&e~K7>)RlNgz2UpnY-eh&G_OR# zCxyZE>BzQcnLBXi8i_e z?qAz@ON!=1;z0^i{fW3_F@|G8zKPL+89u)2mC=*Dhxs(pH@IEubxJROb904p%LP2e zM$&uJ?BDCwrCZYc_N*T9IG8=Ui0P0dR_6HOs|<$5w3Gpt3QjMyoI(?uq-4pAUz6GFh*-*z#1RgjNTcOXK>;F^842w^Wp)!84W_16~Qnuvhdp z`A2Lk)3u=qbvVn_mI?J03-rp+2dA`ni(*UA2zF-_40&M{CHsLAESuSOxoBJ_lp$ml zFci+jh6kDXJ>>oqsMMsp=t|F;_|DMRB9u&z4hN5)Z?I4-sBL)yTK3y4Y0J=CHbCf~ zo+reH(ZE4TjvB(af9WVsX8vxX{M;5<;t6v;^xwg{&kS@}rOL|m9=fUHo}PX9UCxL< z(QkD5!d$LHYJ?@Xbt12M=>@cXY4(_UhBC99PDsj6>a5s0l_{nF)k&oGtK|VxWy9#P zWpvdMhf!XTU{_*mtYK{lG61xOO$!(S`||~f_Z!2bzV<))Lo5WA?1s;8#~0m#eS~xB!5h6 zn3TlxDt*(5`3P2kN_BiI?HvMdg!Ez);(?XQ9N5;dRg8u+1|BVa16%%i%CKAES}(65 zv--xP=LDI6auI{oj_LN*`k4bRx$f5ll)K!U?@KP@iQfvULM1`UP7<&Ki-YsYpG|dq zRoqwy%FBjeiLWA#h52E-G>v2?nu+veT4rT$w6kX_g|Hxt2p=UrlY6uxs!19jNzezY zwZFfTRtSuQtmmoA-G#2AeMBs@x{B3?V)lTd93KA5IgcpN~=yc0L-mYgKDMsBz_PjaVVX0e>hmro3e`c?rppx z7d9NL5sm<$*{@}CeM9~8fW4? z^4#$F_~;~ZTn`?9{HoT`^Bvj9#pL;Rw`1D_{Aa?B4XWVQ2i2oDt~aSKL5~6oBc8FA zdH}}4cHU6fAFiGrpvCA_E;z>MRn1=mHF!@2l^0OgUm5I6W36&}t-OFqk2HXv2AZ=D zZ*aoLGT~)6G_tSMG7)Bq$-1KXMy43)wflN?Dcx8T45F6I%*ENbMz<~{s>^4s5p}FC zrO%Ggt-L|cEe!6AF+eN-Vz{$i=~)cR1ph3q#{>jM=!^^uIFL5RneSQrJ&iZVZIGLA zFGV|V@{0yi;62Gt%D-#L`8BFft=}5=&7KLm(V0)}U<{y#>2#@}R{Dz%-KeZ)`Jxji z;r`^hWNS4BagR=Fy;J|xvAj8z*uQU<*A;0*&r6qqX2q^^J(Izf>(qnNTJD5rfa4Kj zs&2wqp20>%8R~+?hCTXfk$I`!3T}t3EB(3@Du#Yl87xQ;3=N4ck#C13eYswP^(npD zgEE>@#eA+e|9ArHZ?dk1ez$}THcLR0I>vceNxPim6^SU*sA4c!K-`!55@qm3In=P9 zh@_!x2DgpGuVV&#gq%ndJOD1B9dG`lY zXUvn}hE1cDyMYtjeH6!C!^z9d9QHZ{C!_d;WY+*CzK?gFi zxx(UQyRbZ=vpQS7bXPWDGcn6H!(c1?!QgO+(HNh(EjEo7h zKHUknIs$~QL^g53f1&4lEsQ41S+qa(#STFli3~z%S_09XM5x; z2EN7nXKGQv2K##dNhxtZH+sVVoLc@DZ@Z}24w-?UnVcK=NF*fuy*WQ*eqd(JLXe_9 z8TwgAJc;j?`np;7P| zfz@+5GuaZPm)R)F#q4b@MTSSmI%%7w>P3zz4k!^fp|caZmzMT2_hZ3Hehw}o8 z>S>G_SXrA0Kp(kG>OAi(etICnKS}w2H{0<)K)j!%{Lg`=A|**#MS1wY zGW@WcI$i+&;gI8kxkKTyKVlki?#OZx++Mn#a7}gQ8PAaRH`6`(2KiJleXpSe4EB5D zMNqDPLD{I8r?oU}{4_)D8BV6t-nuy)PbM>Ke7Ajp>>%Y~q}kO)*3HA15KUQ-_p~rD z=P2xT0tP{0_?_yG@`YN^betH9L#9&PJj0rFmOV&lElYbj!g%S*XXzMn^DqN2t8;UB zc&u>uuiz~QDq$K}``5uO3o4`PQ4MH_8AusuR(6zexTG3GQf}|QQ}sZZqwdw*0|Ya|V&jx!Ev--+*Swf<3TeVtBOqrlz2 z$u`a;@+7x-PiC=&5fdFePl!YQ&QO>C*~u*D^+2uVE5M7BkQ;-$eCMQnU^`IKI1cfw z(&;m4PGRENE@n$xS{nyS9c2IVcx({PdcA4s5;dpxZ)hm&wN1UZ73&vO5_`6j`T6;C znXNz2_t14yjPtD4T*(V7l)hphUf=Zc%wjQ$#rj9-8YjqL=B!L`N#ZhGMDH{MjP-9Y zc9k^unk$y>&TDF){m$w!lS?Fpk?!r03d>~296<&Nxj>k{9z+O9`K8LuTt)N2D3YY_ zr*WZ_NKcR4taAaBVrInVPqDL;2}Mzjkn0Uag;Ou6`z(5f-nTXHJhrF8G{v?dwg}lN z%^L^54%y;w8p9H}$=>)IDdXXP3|UKv@y2}Kfx}I_C&+ywa15;@$kl_j1kT((&=Q+J zQshKftub`IxlVCkG1>y}(e@u$JF{SIRe5jLSUb01ZDo1sJ^@kGb&ggki^J(XdDLJO zx&qUi@IUH1zvzUjxNiPX)wo23hbDfV{ z+PMkWT!~neu!N0I@02F6(;}$xOY3jZ*$dXzT1?X#8Kj#~=l1S*#x$64*qNFhw8`vxVSdczoidw@;qxbODL;GzI30Mh}pnI>D~nyYYvkMXDW<%GjDMsEC@0(33JjySe|>%x627J*YYFc4yjV*L3Z|nY^Hh} zTke=hv#_Qrk#$X0J8gxJ1JXyZN-bU^KZt18>|7D{68_r`u`X6e<|ez#m&g} z(<6`c1nsS+GNQ=Oq35UWa!;Nj9Dx4V|8S?e({w#>3ITs%y z_4c;ryLs~d*xuIF0nF52@e5K$BdKgFHP{lF64@Fi?Tg1`SmPuxgB8vWW}aRZnwOha zO1&k%Jd*dIxxP@a38o&Cf^(0oP6CH1Czk7 zxcmwBZ%2hVcc?Ek1OR}IAIb{x|8?X2f0vOYbqGDBqo%*Gj;7a>Q!o$^O!Nd0fm7n7 z4B#wt_>=;SN_fGgz@y4QL7}47Mj-9 zOV>>h;ICOBeK$O2BY4c4p?nsb9|_Rk)Vp+) z+l#wHngZXdD85MXJr%ETY|mw{vT0u-;ok~(JDA7&Ih21zhrTURV|>fpGzSDaN%p_A zd%q#rX?FwvxH4xFn+Y*X{}KE{IN~NqQW8vZcuz0K;*cOp6a~pVUnW;{hKX4^I*_|6 z$W$uiK{8L3q*L~2A~6G^vmNF|O)?d~Z3XC~PJS>jhs{fs1XT(zN8#wm!yHp?#I9Qa z+9>A{0YZ&x7yReX9}7ShAb-jw2~DzHDZnS7WN>!Lq9V-Z&`wRWw8>6U4^t>JczsOa zR7aydEkJ3YWGHsoA{0!{p!$SqG&{dPP@t8_`4PEfHx?|G?Y!@ zq;$(>I8`ZUFjl4^-+8(Q8w@mQxIeysvTQo2qT6I zo~~Fi6U^p}S}WQb7D5XlA|4ECOd^|wbQulQ+ys(f$=n^+(tlThygU!W8alJ%9PgnD zteqdTP|<0w;A(iEjW^{rN2O@Dmvbz3nn*vCy9kW6R}66VKk@9jsd{U2Vnnzyj0o_0 zQQ}0GumyR(Lksv_wl9lma-&HhQlrXZjk&d!_$arQln`N`nc$YsGLIBgf}cU0Srm+j zF>$T9*SS%9^Xkj*HlEv-Z41e%kmO6Lt|psoUkr3z?M34T1ZWq5pG0^xoUd2 z!WmNdC7+}EGtI7R(2VakFP+QiB1MRbqKuM3G^*r?OiU<4X3#1jN2fOy=)h-Ef4Qw^ zGL2$D(qMdW67C0qh~|1ENZ?S{pl5koSZFif=>5{5JzY=^ueB&pUrG-#ItHDq17nQY zoF@dKVt={sD?p;oW!O<(Lsx@OtqdHKmzzAOJCO|oDu3v3!bK@XRt|HeZCZ z?nqUu^hj5$?8q0;^8s&vWoN#vTGhhR;*<`92V^vLmWNq~jJ?F^V#Lh36AX#7guF%x z?|R;X48}Df?^>s#rO}=`;fD=Q%K@y)h0tO1#D;Tway5}sb~>E?UWa^FbjRHpclda_ zljppiir=RPbL7}1ZY~@mWw8-iTj&=d9iRHd$gNbe3>%&t7n%&s_&EI4|VWVmZ)~QIhPBFdBa)I$q%!Lz@x4&bt4BG;ka=UPe>|9l5uv2@K zO-_GDYDk)2Dj@F48LYltiHz&q7N@sHuPKOEa#r*;!E@(KXv1Z}%$ZyERx+>b%ehld z?xyUAv&SFY#WSc!;jHdJ9~o0lZ}nTaUGXH``7{0Za2eeqQ74bY?&5`&vs{@N2zKIK zd%%!0z3heK>)dK5*RNsLEHE><5~9T_MUh@4fp=mU`*lu|vvso;HGagLx(M$tinvd6 z%;htTxCe5$JO9iJb@f*;n6pP|z#YWDJE(K_8mw6P@j=d*2r45WUZSmDs=YsE12S~8 zFHY~gU%yMx&PcL+$f5Z1Q=H%H%nryNAy+_eXb;XGDZ7g2Wu4zKzO+ho!N2IvI01V& zt6=B1u1uuoCf9shWO|BsP9HhHREpS~-f_NkO87?2zsY~OrT;Bl$e|(m5-wpoqnREr zp@AR7_)aI=Z;*FxTRUcW2#g53DVlpXHXKiWQnvpY80R7~}QD7HT8FLtu*jZoTK*zYx}Ub-MXS2=H2NI$vES^l$eXdnit#63=LvBC-^<0`1>%9P?*;QF=y zd*UpL%1BwULY)>4=>$0`)14qy%#gd*TXP;~IcVa!pu%QHiq$iCI4rrn9r?4kX3^F57D{^VdU{jSkkq<4IVw4a3dj z=axc=#*@PtyLD>9J!=NNaoc0?t&3s)Ux&DT#_BVu7y4qh*bz;2xazFtKZ@IL$8E3n zO%cAAF@r90^9*Z>VWb9~3dpjBhFKjWi{nr-d-`kd+nhCDk@^{x*9IniIn?mBCswC}!pixHVHG30ku$m(Y^N$1?)N4hT!|RAp83Z;4?2{uB#t4!0Hw(i?O!c`!Jdlpx)`|^pjk31b z5W?yZ^Pj2phtU(e*)4MiydKg{DC1Z%)NSgNNIRmoaKh{u(=(b}5P5_v>xYxMSUk>y z+~pNA6^RJt*yE!~_LCh}leEfKn?wW_JGWM8Wy$*I6$|~xoR{t|JDe+_t1oG*OXFZi zUL9)SvD5`_1+MqOpV05BADhcoxRA!K^qZ)f{M2W(?6xn4A#ibD3dKNF0Q1B)k{#-vt{uUPKhZfl1EA2s*SXd*O9RL&r;R$ z8Dw=l(K1GPrd;iGWi>K=>rE6y)~5lJzuVdcvW^ucTKIU^(xPwXxrODj6~w#ABRdGd zo!z^ErM&YZYYto*Vo^aw4-~9HK zl+$<`iZm$cT+zr%v$KT)tJ0i3mMR-2Yi#P=Oa*{Cp1zff(;JJBk}%j@*2^lCd0!6i zX2lunmF0pMH-Ua1>{PYF+@H8?>No$CjE19=?zQ`l1*;o=zJayXkQu*Q_iNb!?A9Yg z@RzN7o;TDzRMsJOu0mJtxG*okG@iM6kU;zfJ@o`6!n?PuffqR%xq!* zR3M^MQ`%SeymCB}PI_I`@@PFkx<%v*K{awN8qC4{k=MgOI!5Y%3*z%#xjPaRUD95T zFZOpO?9?mcDW1R(8t*)RO+@SDtAK?!u{9zNSlHMJrF&JYMH0Jv-CotSJ`Cw_T@J94 z6Zswm4qJG@nF0<0wo`UUcf$pxo8Wd2$aBv-<&1Z1ms}tlDrt+U8udkuBkr7i$E`!h z9p=z>-jrPwZ$<8o|^jjGrQof4U&8G5<3B$fE<;u zkA1nZdvm9bK`y2St|p3K;PUhz5SY^ivJ@v|Jg))L3$=OwJQQj({CTiU0@Tuz^wPi5 z*TR=^MIQ%;EX3IUb;NL8`M7EQtkxX?R(I|-thH5$EznS*=4e!V3^=JzjrJ=TUzI%D ze1TDIp@cK_ z%>(bkZQ%6Il%FU1i88v^5okh8I9R$!Y5K5;?6xoqqkQ!X(*2kv;XA$!!)kIDag8(J zg~9d*EiV`uxo>1aedX?)GVTWQ`-MM6`d9nf%uXdNS6;SYVs4j}BT32FPPiaq?-=X_ zkQ%!mRpow}G-gN98T_V)AOP-_C&b>kKr&q}VB-W4RoTnSl#hEFOl52mlkOzG0y9&m z#E~!6d09!CwnsAZ$kiP~T^aVbzMu~N0AS!y%qJ~tg87!bH*;^7l=m+vu{`&pZ-G1U z*S$kMIb=i<2=*~E!{TCwSZ4ab5-aDdp*SMQ&yZ9RY8sDovs*DF^V!0%`s`K)OepUl z)MGK}IeFGEf$qZ; z+fd;fdJhpTGO*{A-XrN_DnQIP1E8v@T@=SCK1cbeM(Y+?9YA^+MbKeSJW5zO zD~fExxJ)Q)2*oYyi45HVU&0e>qTQ!NyWY$SV*5 zdxFSCSmp9&x5GUd#S+7fHf0epbKmFYyME3x`=sBHpU z$>`T$O8I2s=^$Dov^G$p`~b(E9U9I4^g~<5wE$NEtxIKU!KM7OAdRa|k3qC!jAA>b z>k`koPwPa))=CXIUe~X!_*W?Lgo~3P?3N_f7A9;CREgxEq1M%|l2z-5=>eIOyB7yE zF<_>QVQb}QAcPLb>K&Rpy%stNuDx-5FFZc<1BV5vlmo&N!V3qweG;yGE_Es|@cDWT zI?NJhQU!k^yI)h#Gr|i^7dR)XARLD_kSf$kSe_sqpX2z|mJSjReWcTh<*H9y7$nSr zmK>#gi-)As(&q}#H9VPsTRt=NWW8{3yB<9s^uRGJcL+N69!pHWZs*qZk0`e*#4DU% zSwydD*^5=UE?ryRnw~tBpLaiAIMJ`mad-n`tdVTc(h%e(HdK_{vyQD=lMVemJ3CjL zK2|Kt16N?Y>aktQ@avGV_erNS3dXI3{aRuV(t&qFoDd&&S&8;U84tl#@9M+x8ZNSh zK*moIdl4Lff*`x1+(+^p4-BEciBImtnIHf(L+lQYdvlKRLl zmol=B%Qvd2$tr#3HJ(^n&x>C#`M!|tXgcfwDY4nDlKDIKLU1yZEZsTTZlV0Bc;Apb**4HTr_S6H4k~VUv(am}r_CjKevk$Z4)X zH@`FIRBmKh>tW7QC>V4g7QdqX_UNM>N|@*cSmPUnVV>|On15nhl!l0@Em$WN6OQNShXWf~T*4&86fp(X!4?Jwe{0Y} zJbKi^iVTWX6AM1f^3C$iuebyon#`whALsFvpH*LKfo&==t>0|$cs6>vyqwT+%jB{=D2x9wx6DIz}poD~GN8)_@v>P3qJPoWs_< zOV{3eQ_Z zhL-;xzuO8u1?%dW{ZPZd7!|ALwxRHoWZ^SgE#G$1%&Q;x8g=0w(SayLvnk6J@L2#d zcU5H_ZL^zfLl|9d)`YvF^o*_8v$YPQ^mvW0f`SgBv@>>;2F<(htXP6HW}u^Izwp#2 z?lO~R-Js;*b$8xxC<~*I_%6O^o1^iIGuXMLj7VzZITBtlv~=oJ-^p79Tc_bn48L;f z9o3BnTCoJrq=;T}Im~X%n73TjraIzi#U6Vv0A16F%aBGvtClHhu>+0uj;h41Cw)M6Z0NS=}6&C>geJ6r6$_vwm!u5ip z`IK5i`%H#35;{uLSNttZF0`170h|{EzOuG%LgmWzyjDQ@my&+HMZnLB`r|71Z~l+3 zR~PbzU`9yq*&8&@RC@7&8}PGsNuJqB1uS$=nJBj}vk=hRN_%Z%TfsSkGr_`_M$ecB z!U=!mVMh3-wuMvuC;&>P#x+Ut#Rh`#c=2F8Q+|@iO#Tm7yHH0Jm9oh=a8Gq!Oww&s4sO{i~$o9Lqw5i zf;~6R5PPu}UDhH~kGqIr&`pDA2ZrKR;#uHtuoJdJlT+wJQ;(hg7rbps!V&DYI0p{2 z>#faS9z^WcfQUDNl#H2EEs$U6Z#WbU0g`WX*Wq)0N1q>qT(7$^?5k+RqH%6AcZgu! z%ywA)y_b%(&az~g(tzJU|J=L^z<>~yKjq}*=b`?;_UZqLPkYDwOK=lBJT$0;Ud~$I zJVJ#S6}?I|br-Qd)p)2w-3WbcX}injb%qPY@puk>hKSHz4NA7qT+axL;uZJG9Pmy z9Y$Y%TqRuMT;NiM#B>X8uBM{VxWJfX{=ld8dx;FBzm%#3>Zlb2%`o@iP_TyV7VMwD zI!1LR#`vc=sQ(`!B3nBpeFsxxr~e5H{?tzfWS%1y#z_CtSCBgt6tn{SFneHXF??EP z+D#ZH7-TXWM(bVOPbeh3Fetv>7{+Z3=c0XS4^K~r>w`vbPhWRXyYMRNc{TdDzWgwE zYT;@ly|vzQKbGJ~T<|8(Nx3T!{hi}#I+5@Vi+pQY4;LnA5mlMhlXjU+CW0Vd(^Zbw z7%#V}SPKwb(@_}gv$sx`V+AIwZ>cQs7==U}rv1q^$*a~D?U3>(FUmMu@fEnl*e}|Z zfL^9ro4&!|APl2|(jSw1X(;7H@v+A>7g_Ym=0t9**JuST2IS!B)jDs{=c|bA*pBxU z!W~7I!1*72P0EE2`pd7XCIm4YFrO}*F=a>7X~I-UIF(l#4K3aLFM}i)c+aF`|m*g7nUb4EerI6-v$jC>nABAphOXb zL+tBE;WFs_0frckxa+Q;Qm8i{YmVD7-WT2P!`qEvl#!REWIc1@eAssSF;V|ILg~eD z;bB*!$@|lUNU7#3?=0_FH&$yjEWjp2LE6+kj~QuTL}$<&mh541+U z7;7cn0Sb<0uxw2$LRVpIq>0~KO;2`kpV7LD4=~14j7-N|OhdnbjlEQwr)_ftN5Aq< zZy`a?$6DJ3TbTCcv(QR48D?j9vE(OgC`C`CLNm6B6frKsXs%^mr7ur{znEAH3Zhjr zVf%Uz!(_1&Xdz+p4vK=5?HO*)+99<^QoyLgBaSo%V@z7m4>5*VD%p|j+KaYqnqL<> z$*$NzaReKpAhl*<>Kvj5<%AUVp_aN&GBRHsRP7<{9rP~kh8gphMi{8~*GKvX%ig60 z&wy^CwN~v(%7MAwCI#2rMMZXCb`QCJ6m~7dO!I=#SgUVhjGt9cXa|pGc9)VlwzDK) zXQdyg$K?5pI2rfV57MJ+JQLETff2Ni5p3LJXK2jXckOSwvPGFW?2Efx@?%OtF^;Y= z+HCm2(U%ryFm82TIS}a~N!KphJ*LeGU&B{mmxpV=>(&{9t{gl-&t3W-%vKIhx$_zv zI}~F9Xsv_7PsX>Zq%9YzD(22fkn%((e7fj4#SKD+g zn?rjvmDysylj^$ov0D~x1btIlN3K@Fq^~?v2VV9XujNq4ufTX(>rkkjqFV7;lu@=@ zHbB-ruUU}NUv~PIafXq3WFbkfoorU(o8ZdH-Sv+!;eL04ZwlqR^B!pZu($=Ww%Vdl zOicNppi+&17kYaf`MVNJJ{V=1&yVng1&l(mDbkPHM5+6q_I!%MO-}&xCxk<&xNV=Z zCZnzwqH{?Ds_#5q4Slw73pWQCiNcp+xFRF9fOXvl8azz!Apxu&0jyZVyz^A4ohZ@L zFVGM&ynGvcY$)3f5icQ^GI_i?ZHum$ckOZkKtuhpkvKJW(hqheUd!9L)(P3CyyPWP zyeVOd=CJB|mS!Y-vQ^ILcQ={SNzs7S3UB5x9?EYFsG0+{$W2T(`{H(8bGg#N96Lr- z3ptOKTQ55yE@wtFja;Nj$ixmv&9(P}#52<9@Pgyu^o^H@FStd8+<7-8hU^b!#Pt}q zAAj>?=f-NVhO4|M?!^(aID5qJTx|o*S15f9x_EvAsbR@JHVAGJG|A7XbQ*&MB^Q8m z&;%KTXdyQ`Ljk+c!sb6buW&r=*c@iXT7PEZ*%2*I+2l6m{Ic>Hzl1k!^vuiLRO$$vcc~1>%EDy0J z8lGAp;}j)kffzj=k5aRn0o?VEZsbGXn~XIgql8z%`|c0#8=o62xBDki5kS4LMyUV` zC-Yn3MHFf|bGgynP`?a*h5$scB4`ux)4ARt9kS^~J5)?)lM0aIQuuyG7~EcmC=ZlQ zPudw}hnjHDhgQ|8uD4Ci++6{f<6T1BMRK)?Rxik9bGfjHRC>%suDuIL*4yMENLI>M zQW%R{LeLCkt$cnQoyuJjOy*Rz^fx7_07(q3;oPJ?1sJ*!V@w@CTN68yfTPLgyj@5b zjXG(P*=qe?)r)p0Iz{?q)hf3ZFx7)Bk@{5ojDI}>ELhjwj1cSc&T_s?E_%3A;& z<12gs9F`N$;{V40>=1w|f@!6^$N&`D;BnVbVm9>)))NpP2NF2c z!?jHb&Xym#w#jvY;uCMUHdDiXG+e+p(zCxhT}`Uhy{5A z4cZVfbWAu#b@JPDWc+BQ>g>72R?Ro6$MY>%8E5NnbP^TUdiz;8Hds4ion2wf7#UY6 z8BpBd@b7PUah0pfdpg^T>&;dE#AaKyZi}zsXqdkhtggE9o!U6WsR`I=;Y9nxsNhi- zr7Q=sny?TKo6Tb3(PA+@T`OT?QR4i`k*QvdLE+|}x;6y6xY`jrKFm|J7Stt+bC)Vv zzZc`@vsluq3x>~;tD!~x)ia1yELB!y3|l2pyu{p$in~2wIz##}+DBTiMi+IjL`TZOi86EpmFrykn4g%)b^X%ud=AQ1JsMp9@m~(B! zshQ!oFfePJRJmXB-B}hk2hKmrfr!I^Kx5+Q_um5Lxw1&q>-Cz>1NnvUT-aR5Sp&-k zkTb@}&aU(L>+9t_IWZA!{ezYkGK(d%QWhvZYhh;wFmST_hy~V^GB0lFv6uJQ3!PpW zvFG<7UYlWM8QsGB7H+Kr(-O2Q4IyJ9&=XJr-nycRURiMP`j9wzA$1q_;9gUs3tn5J z$?g(i3%r(Y%wTa(wk0>Y#=3SKZjcM%DNB;?KO<5bYA$Z!V|9~tTH7oYTcUS*ZDnFY z3@nBgHQ|qY5TY*ZKt|gq$Q)G=P{(}kE&NwV3BAC2l0=;`0_))SD#mKo+LDWH60H3CymxT_A3UacXUIC;Zl&t92%3l3awJCs7C3<%9* zj3B#94qoom$DufRLuP)|!AH<;g(0gs{{r~3hvgMtWx)5+IW-mtj^IgK5VtLbM)M8l zoW3f8@eSl$yh4HX5GM?#jBekmZho-5_yqJU-5GlchY-X0j_khJNsaLB;yHgJ{Z$|7 z+0}La#QLi};=4Nl`B!nocdy6!mGb5t?yssSSvlulxSXXs<>F}NuGM@S2Kg5DtV}e` zpq+g??%51*M@sVPWN@l{`EfV|99_e)>>)=fJIWY0rnYXnx&nLe6#tqFw`>q%zd)tq zuDaD4$#|oMpI~3a2Vg5HhzZ0e_>KQg&}uJ~1Z`I$M>&hvq+BjS{3Hb|ON^gPWUi99 ztfh`!-KvsgB95VTFG>q%mP~;*Z>F8vqZ${3P-s)ilXMh6S0D*+D3eGz?(1=KLz$M^ zQ?{P-%Pyr=X3Bn>2h!FU1)<&7hxu!y0TjT+KIni8vlYLqY3V#Ra=2*E>=AWv!WRZ! z_A`@DuQr)0@1dAoJBJ1ZZ3Fi*j8zjB98@QPMxl^EOA|-CTqK!VVFE@2O`Al!P^7|$ zg1-0qO3qy@DS2l<9CPmmrSD6;tD#x%j#&YPC)Ics!W-*N_3uQ^FLW#6EG;%CW%O1BpSX@rQDqoH0 z=>T2f=}Ca`WjdNhCz;2_Q#!s+J z!#ota@Hg=V)WI(*mP6){*)UouDU_R4#>ax*Q~2odHS3d|K8K{}ppby{t)d@Pu{f?8 z3ObNBQFr(mC$9w-^xeFqgf!->4mS5&%?t}aVk5Pi#N zqgmJx__H#HY0N|J6^w|WqdL>k2x6}bZQ3O zJ-ZnR2=$>#6eu&hqo6{Z5`4<`P>nao6+|%XcPe2uQ|enHVMb)~;f5}9Ude9r7Dr9n zImIS}=7*|L|8H&toW~Hb{yX-GD$^D|Lg?N;em_^#I=!Ka1SxK8vNj5qiWaLgW}10I zMqb|a8&V4}GYWgTNup?(Y9LQ*$scw>_K6r$`qG9F~HD<)o?GYC54^z0+Lw6Pqv{3R0es;dY3@NLM zvu+m%U`uTo(v|ld`eIFSA)GE_ zk8mR_T0EG1x+kb=%z<~cfKHrOZ@alVSM9X~t%2m|ec1N{nz}iq8nuuL z<5B`Fu41NhH4^?zJ`gsJF%4}&f9T4b>|RT+VNR`;4x6C@pLJD+RXkw_B4!vm>1xGE zCuNsj8y;OksKW#Jkv!&_keZ6Lp0#^*Mo~|z){n4{=26n-Y7-R_v@Mp6$*}~y{2{IN ztf2=RwIgD|89{iwY0oDf&bKH7-IYv(OdOy*7qc520Qdci5gYLv{Sn?vxDjqZe-78L zAirp&%Ir*@di~&s@rpXf3q&R)35Pxxt#J{fc_m)KL$a=Z>--9a@0IY5I9YPn;xNoM z?-u!7G*Z$xmXlw4b66vv?>%mjgL;L%{sHY3vg-bPAip3t+Rp>IaUmC#Y6lOntGW;$^oR8zs0$hH0p0fD>MtrON>2YNw%v_j?WmVq1$NJfd;9So|o3zNKGrSzziMk0)L2YMO&M; z+NnC;Z{wp!palf^1xgPh5VA*)zzNciOq~{(PxjXrS(}?im@g1d9lVAVBEHMt7K9eV z@3`s7Fn6gW*BJfMt+o5dg1y?3ed-I0bz(wB;Aszfo=`+1zc(gKAq9vp?<}W(hI-JJ zVDR%sxZ+$UAU~EM=g;z#^N9Z0x`!?z?F$Ok%skISsrR%+PRlxz%7_0__vlreR#Zd8 z7q*Z7mJ>!B#g3AQPdzoLY9&P(X=`&gEvW4_oQx}uwhyr=q8scn`K{!8-k#(w)r4aX z@zi8t^E-%E+Z#Kzt+PAI)3!eOld}6IZipz87KK_Jp3>f^C8FTxr|3ZQHhO z+rD{kyyr%p9kr{z%pWjg#2CG`{`A)92{+7;+($oEFaxvlh_s~h_lPQTV>ux#ttWA! zgSq5!ynbP{Ji4Z7bOyOVL-$)E73po#A*FUcR)T=>Z=U!1A9q_K$>rS1r?d{)&6Zlm z*;Vc`K*rbjruh*$g)*a1IL=FyMZGfj6zPqr_;?7qOAhx}%r_O?s;%vkHFE}RY#9V}%To&gi0W0!*A zhFwM2+Tj;2cH@}YaL4)5c}Rag>ca-z_@*S)tI}tb4Ext=%}GNFDRV@A+klPhiD*NQ z&1!X@@X4P{pb93!!3$G$kIv48Pg~34cKp$}>+RP=N)Lk=e>28>Zb%ROT<^m`9 zBbC+4tYV2;_bQ5JMW?_3wrN&xn3AdK+B8@)ZYboEtiV7cj^iBymQ0WG%0&zqoK?GwfF+~=*}kW$PKK+-_!pgr5o55P zExQ%M`BxPW|H+zlE{69q?(_;1pgu{}`>d2FTb26EL-zr+F3Vn;Qxj}z8E5E_M z(t|zOrN6!zS{+h86W7+&$%|JHOAF^>($EQSmM%s(ZI~=K%$nHaM1w~oCsz?dYMV#2 z1fxQwqn!BBQM+{iY7NYxfcf89eaQ73dBSj*%0Dkkb-ENL&Y`X)O~yWtBzTTpG}ERV zZuh&DNaa@5iL(EiVrag9GB#F6XBSTO$4C~dHm;#=uA#0omK!lgA~9%oj9xd-o${9{ zzFn0vu8)cl*K#SK#B9=|U_r#fu6%MU=QeDlRl5w|(2*O*%kW>W+{ERT8L>|PeRNqU>juQBB`)WqLt&Z?p={!3#5^ms6`kbmd`p6xX%I=FU8?rK^O z4p+RR-7nYQiPVvvP>XwHgtoQ5cf-+QsS>M0EGcIw?4GY=W z9NFXX#BmbFYckk@qBXVWsFj~pe3pxtfdzbWC@B~yxYmuFFhV&cNISAh%p&&AP8LI6 zsGJojIl3uPataJ3B=u+yz~hd;{x%PB7IVy`aiys}AD@r=Gk@9GoYHwl)eOd|$@h@w+}bT23H zmLs~}uPoyJB{lxXL)5V5Brm5g(AH-kMS)gi7&jY!s>5eo4d;rCc3jFO}f)H z8MNNi@~hZkc{M_@VCe?wJ_@O;-R=IlbE+@G_-Do7gs` z3lg3{rNrOtDy}pnM4&i-g9S1O(G(@0T)MKa+ULE#J-q>1hJ_<|rU)kpCJ3p(^N0IT zyv0vQo2LVM+nA)#FpKXaA1}bgd}~&`Pv>FS3{lvYF3I)8U}i;tlYts1GG(>xCffzPWZM&MI@RT2*6K!tPZx z(RxgA?H;+BYb<0@b86+1hR2{AQO1@$B(lbBYb6K;yWr-y1Q043egqdgc;G)r@7Xx5 zMUrfqtwm0?!F&JX-cv~!`19>&K`8fTR?2OgAn0TLwZyuR9H|IVf&&@B6Tl%Sl9Jc+F@JxHp zJK7mupdgIt)e3WRrb>>?49Bb2wquv6PM7y{-Dhv$8!jH^8suuR4vQRsc4w}&Y7xZ^Y4N~r7K-3s18XWt+V$xUSp%&QSpp?H;D+yi_-X6 z=cH6R{8$ulgk9{qMq#{xg`Wr%U z?jRgzEHgs9O;8QX>lt2uz$|2meI+5>dn5JF-9 z?M8ySn?^==`ZE*L+d}~FGptw9rS>T|b(-#ZY31XjuY@K`d;Wz}_u3o~Z86z1^b`Oy z(`P=l;@~(qI*Djgax<`+Kz{^gS$0{^lsV)cMn2KnyP5keTOU57U#T$*k5XVRy?7yl zsjw{NhhloVxm3hno|Lzike$=LOT@48Xw{IHWt)taIfOX2@b%~0<0W!~<~lS#0@G*M zH|pH15`-H_BJF^PW(@!wRDL5qbxX5j*#FJM%;-wG*D9$#Z40KJF2)`=PFinn8Wz9Y zT5?}LCte?XR{+%j0aA8wAU$(=n9P_sit=)+Q3p^A9Q5_m@p@H*&;y>;YdlEzFrdUE zJTvdjS&}j7j7f(&uE-weU{0|}Z5)Kj#x;bVbW24%E4^J<`H+hAkdGQzLc|^cKFjlc zzsl#+Q+A^rG6G%j0UNnHf=#ylCchri#HXZ}$G?XjT2k$eG7SQuf_Qvj;ff&vaWnau z&}^{<2NF5(zWTwqfT@OVa!XorzBWE1q1>4QS>*Reo@88*QC5q41JP*a;d1+Pkek8K)h_p7k)MosI5>lS91lk z_JKjgUJ1S2Fvz;*I*n6JF|mTPMl5eacJrow4fL@7Ky@Z;hId}L_U^MI>^*nNAb$V~ z5eT^=c(?gV7{81nG&vxri2Gp>j1G5(7aOaQu;&?bmodbr7Jhr}Z?kUgT-e31w9Q9ZJC#23jxl~h z@rb>pBH|)dn~2UNEVji*-Gq$D0$WnS1in&4AOPLw$XBm)SW&ns0Jg3on9ieAYPx%AUt`}@YLjNRgFL@=`i!1TG@jmKD;`5P)xqP4wlHGk zaVZ#Ng~70}yf6>>6gA^(FBW2C%SmwW;}!mP4ahM-gZQughi$|Vw7JE}IKB$4*ruyA z<8cHz)dHrZhr@S3{rN;oz|zq=?TOLDICXoYsZ~2i%p&=Os(8v5HmaF6tww9}R=3T? z<0cA)od;jxAmf80t4#-$g2{U`^@Wn@;+9UAuj#M=LTg1@YO(d){i*2VQ;Oe!xAYwC z<(gN1I%Q)=^+3tfx@`?(p6{Y?o|bvgIYUkWCmUD z+TH`Divn7gdKDHccLtlR(J1;^oh7>)csxzVUAQ=Ogvw{fulU$oRQLy4HSFsU%i<_o zQZQ{x;Jr3mOi@*i8QT~yu3YYl)zDA6Oft{qGF@YniFb#3td#NaJGhv=ETdR-Q}X)@@&%@UoQa?u49{e!J^>%75=^pN0~`F+Wx3pX64n zNAYmVgWI?n__c2h_j154-U?us9(olV?+|u^_wBcYAt>Yzq~Fj83{>xOtkU0Ykm&y2 zb@DL*%j2z&B!R;pstGn33IPk0e}p%Va5K|i;2J=Wb~7`Uay2uQayR>K{hFP)9*TWr z3s3#Bc5-zMtex*KkHCkMj+n&hiwFw_H(c-^TiL@OLVM1kZ*pfFS~T`Gf#k0x?-re&X1eXjA)hF+nW>-?e zORQIDNBB3o#Kb>xRx*4))P{eiSf-yT_TMqm|GYu|iIKL$_R&EL+qyO+B%{563Kc^~ zNP(|V(ErtgjZ_XRYmmslvxWBjg&Qa)r-p`BVW`fr{B-Z^^Gi(+%K+X0&0qjtoVu=( zp#c5pzF+~pxM8F`&DD!hi(-_utpZlS)TjxDygnjYM^cPwfX?}|ihx>5r|eOksx|9LB{<}P`gQXyYt2IpIrmyJkCnvqsd0&^+1&&Pq=JWK1{jMAg z8k$?j@}*NHoM$OcwE;HaR4m-tvuaW)o!fML#i33oI^LQ>3bSfEjy;+;H);NT>$@)< z^)J0`_&d@YKV?p;<23gA(xOV4aNmH7QT77cNho6P?psz zfCt6Xzb8web>|WpPsAe{JSK>O{ZE`r=YmDndR`}7TxsuWA!UA)Dw}jMXujnfHmCR8 zq*ZTd7v&SoD*O+;BvZ%>sFA0ke3!&Uxbn1gl3_dk@Pq=QX#)vdbDd zibIqr$r+ABRJt{MOg7z2uy@%&&$#PE&v6XH4YH?lPtfBT8V)wg4YTKF59^II5a(^m z-zeue20N5(h8yb)MZ2vYmm6)M>KzZzu4q-5k6*k@H-sN6J(e$_e%Tl==rm|A?B4o2 z$uG#|$Iugw0reRR)$CF)TX>+}{>v~Oy*Aqi`KYNLFt+yE$Bb3I5g+H^7{u?wDNDayu14^w6trYmMQ@=Sf!I#7L9d$)?C3GdS; zgZZd32ErpC%A3Q0=cIRja1Hqp9;iCMT^%_ zbX#{(gSm3L`tTmiOf-zto9}0ZjHwSI)*30ftc)sU3F&e@C!tXy;Ja2Fgn^W&5m;44jJ`Kk9OU9(L0K|mP%C&erQ+gG%=j$E z&fnR&^Rf(hBuI+2K;y{)IGlPEobhFqU&y{7_*Uht@KR<)EYo><7LpDM&C0V2R@xl^ zf*Oz{{!KRLOdER|a% zQPc$f`sL1%gXJnM=JrdFw0ZvXxfTR}1|ZYdUWJ|N+BTDtwr@(QX=PVq4@*IS&BUEj zu#Gm!Nu;(3OeFI*$q~E3=Z!XzE=LLo`E4kSbi!$wCT2xI%1G0?Agck?r-) z-3`wCg)0Cn*8qfOL2!v1z!blv51Psq9QEa*Z;5)8^Lzf0!)OBO^w*51?O6s-%dchU$=JmwM|Oi*9ka|4n$?uD<23jV?l42sr)R!Wj;G zfn}!uc(IaVs^D4yaq^6G=jt(@qf=ro0HCE9lXDvirCD$qC?-Tt1Flrl8o;a#viN}q zq4_E#PLpOlfCHH;hU>WRqV z(1O!AX-N$A*Gfn5RRnfxXyquZW?>)OvrL*@7DA)1r^aQJecG?b43Wz4SgvWh+C|E8 zF>mxI5x*OiA)I}6)F%WA7&9IUFEL1=CrIE?8hfDhfWTnnYI+FVedI=tm4DtP*6vaGtRHgPYRFyoT$A{{}ZLktf)7KV|is(p+H<@&F3 z^L5fiaf}89rd))=P0_5-y*|HLVCo;nBpa@-eQ-^_b#tb38(*CqesXE0ci}sS>D#WV zR*EvO`|%`fi*4;>3YtDe+d9@N=>Sm@%j^*{0g?FuL{xx8$n>Bp0lpdSR%Jy89m{dk zL_f95cDSv^HwN;rbBO2aLryqdiRw0W8;6d;zW;I6nBID5wEN?8rT-s5d@&_ysegWDdftynDLLc*7bVg)eqZl4$Ykps!d(J&bZh1**?tpX#6@S{6%`B{A*gj zCmI&|%^sl|#LOjh)EH#+m!}xHEB9Y~XfQfIc@o>2y>zK9gSaI5PdBxcTflSK5H58% zzQOg0_P!_8SJ%iK`Gr=Wj z{qT>Q8HCjun|ciFWO#9az8vRlr@Y)PkX?)*; zEg>C{h8+lvB*-0T?I_w{sx@=T@FD=l80+O&;#wgOaU!MVS{pEIg=jGbU=W(+cRyFr z2{|<{rAqkAYV84>by%U;H*>|#(EElHmu4bcf@HAr5}2e2%XXDBmSct^daguDp`)NJ z$adQJ&Q0(jO_)>2C~BAzc% z7-y%A{*o-SP&ksPHzHA-wmP&Laf(D?io!QBA}-*r)?CI-X3E}eINPvLs72{(wx#As zve?G>(DX!F5i4S3jm=pN*g^^b8fQFNWgX)lob=(hfO21yqp1y!iZlj76~o!32l30I8xq!8Sm z|Me#169H9@RFPrt=+vfx+J^8owxKkrj9?Z`5??Fw?7Icm#nlu`t3+07K_~!|HxT#0 z$(V!{O5E$(wnC6I&20W8DDxwA=`2Kke1} z>53DY*0P8UIOpYxvYFWf?8_b5cfRXS_V@aF%vIxiU);E5SnvU{*{zB*#N_RtZNxtV#15X* zFdi9Zz*{jN0gyRlY`HrHe#uA~3U(S|Ra4`xg`k(Xenm5HjQMf4{O4aFoNQTCDCX-@ z*(~#AX?5h&8IhjDR_4+;p<=uSqHyO|0^<~G?j5B5y|^<2=Tn?)NU@X5t*Wr&6^Qr~ z)^NE|frrQ!XNlf@vm)&5CV{{kD>TqG!cZc|_#2@{keV?23{s0%SG{RotlQjyj`nPS6@Hugw~D5vTUF zr0esGRtdgETswNj+@&C|Bx#qv>Q1RE$#IGo`$0E-3}l~i;+}1= z%b{goxTLvg7B2mbCZBj*NUqwplpmIhuTm(9zu{)-oBV*_b|6Gl>~8@2dcCgNnmNK> zQ2$$ZX~>5cto_(nI6uAgr2lfyC~mE9`cL7d8tS2>iu~O<;jk@*W9(YtF9v~>!Dm;o z2neBxBEd+M+7M5IUK2&jNE`1vI`Lu>_gA@GC1FWh9?D`b>h7 z?uFcasu;((Iqb?UDDc+2BT{mRSj?JRKomrXyf|4EM*fK^|)zmQJy+_=MMi@PUnvB*LLTQ;8%I< zYvV5->}%7nnkd7G=<}Cf_i$$~@b{ulo++MDf$~uUzr8H$CJe#UNAQ8H@HZ!v#^~*vHON=P!9tP#iR8Ed~4ZJRe?C zG*Dbs_%UP1W60$(P0SUi6dbE1nN!CS#+({iB|?mWQVos^V{2Yf%4BfsGU1@M6)wv* zFec01mK$M5l>#hjC2Xw%Nkd1CsD~Db-juu|l#or*&VS5iIGA1CJ)0QLFebzcYg&_$>$eK+(~QvZHL-md;h~+72V{>537d=}>oBE2GMaZMf4tbOFiaG2 zIIWWa!&T_-TNMfQ<$m;AHD z$2y*+;5*QQ6f7bMdKH(aO@S#!=FgOQjzgRz0s}2JI|_>~CQ%z#2!9d3*Ne~4q8e(I zZcux*)6(+{!{v_){xQ=NePam3+baw65uyFY{)F}d60ghfiVXI!{1WNAz2^ez9nFjO zLilC0GyM|o+r4LtAyBValDpfsD2*x{^=KA)shujrMK~W@ikL6~(8hxAb(5xrkr{is z!N`F9kv757Mj4Iy!uiFI&6ezM9Zd13d|xfNie0_AI#T@NTc)&3uD|S@8EkQ~QQhlD z-TSS}_J3%x)2mP*XNG0&dhcNFf^tNbZJBb)%Z z^gq!L*FqiKq_JORsxy{+UR69A7gcdy+-j{=bvj#kh-`5(cP3IQSrzLYCTgozN$H@Q z4;S%VO=4qVV49Ft*Skv+7WQk#+RqzIH$rgZ?6ZKRmRl2OUs3*nD=aO;AZ%=wp{{F= zb^LJxQwQr4fo9pb>3#xgflA|?c$AxlE|m&X2vX}tTF30Ff_$eto*)P z%#ER{#Ll#HxT}g7FvuK>WsjJ1Yi;4(duz*E3J4n%PB?B@7k~u0&cgT;me1(lUZpFN zS-a+*k!bm7rnEV5epIr{UKV}C{wL{d{Et#Fbr#`iWTsgTTr%t%n}$R-=Yvg516I2| zDU(y2RxrxkX;PA_DsorqgAM9+XbC`jAF8^D zIm(b0dS;n^DwJk0f>6j#P=bA0>r@RjIlrqcmpYTr(t)J>^gkgxij!aZeCbBhilp~J zfFwQVLzn`AvQCIF();*joe-8ZMTK-Cr1DBfIYT3*bCu&=^;b9pPt4K3sAg3H1iF0s z;n7;dG(jA6d_RxsdGD(YQhNY=xjsLWF?wm~DCFSD=75`$pb~E+SjRx=UwGBfkq=M;>(+1nc%%{`TCh`9PfkZ3g`PtUB&Cc!^t}u(#;d32uR6BIiU1E ze=63L?Cwj@TVI}9CJUnw{Em4~@UlBxz}dd@Ebj6m!p;}*%(c98zeiVue3@%>cb39X3f0aX#8;n%p0eQh&23^mV^^tO5gp49#wV=fXkA_*9or8mC$ZzI8C)c~ z)~pggP@FM$eExepu*es7>if}94nG>|-*WgBltlg^q5fyfCBqP(8ju?(2AKOGqM6P| zCxMKlL7m(Lir-7p1XIE$9^^D;L?Q#K(a}iz1%zvbtJU>b2p2eJyt-CM%ky%!ZLj={ z9yH=tu~=1c;dr$Ee(`xZ&hFFs^2q+zu?4k{J_2Mm^cIhV4n1#^n1)8O1ySG^u7p@H zU0}38$`CXvx=qTVK~i`$`=7-01A&;>|R`1Ekk&c3WjK<*$gMbWX0P7N~8Q6=sOh==LcjCCampv zg(g=?O7?u|&WpAbC2Z#eN3%(G3v+Xk%EQIHT={3Bl7lTL?Bh!EyD>T>9}rV8bU7$u zB;o`#fdoVE#-hS;=P}N#lwx-ZoFw^jj9dhPbY>Bn%>@Zb8+k(MIjM=*(J+e>gMz+B!d;2THc6-vj*o=%z?ozj`rH&^gi%8B#Rnx;cu%f6b)q1UQ-lP1KJqvd%J@a>%SlP#g0Zq}Ox(j*}g7P=*6`JZ#U~I-F zziTwg(cMGc4O6v7Aw0`>I5&*h<|mF{%JL&uo7&#GO}9VT`a_c&O6`a0RnC@hode;lxx6;Uy z%%~7={RQpeQzdU2U>6#9JNBpw>)0twjC0E{f|U0HiVzi{6@I2tYVXj3mxYfPBvN!b zruw}ZEMOGs_A-Sp-?V2cI&UJGKUMlQ8V6F#vw!Vr$Y5me9<7?)J-b0=Lu6XuY}Th#B*%o*ZcJDWyAq5C-YV5H99lx z0v@<+C+R`6gSy-P!zb?*!Nboxn-7vLt}$IFBC#amx|aI*$Ea!guf=2)7_n5@-o_ zGI$Tx9s9AbvxJHVTm$cNZ$7>FIKoEJ=IatZhbHM(S||3oS*8vqf72i2Zu+hCFs%vu z6h{J6Y671S=trnkP=gfB0OLlZcMVt5!E?72K7TyBpDk2Du=)!123bV6yGxd-mDTV{#k;TL5)s*1I1ZWnPef%yIh zFa>R@fA9aJktkt*{i6LBjijV+U}gNj&&Y~Y3H8BLMfh5Zt6O?gPcpV7B#*)-B_9!` z76=28(Ga*G8i_nd3=UZOTNkTxH&kb&a!z;{G?Va75eQ6Xo!cT8MKmzgL|%AER(RO; zs<@`>&CJ+dckxzYsF}T;p2~VZd8_h1XDQ1IrAPZ!oCD|bdJ79p*@Ag3Z{|D^jeupk z>S3G_W3S*F@%##LC2vP7g1$9<0|0_6l-5}6T27x~!iuhAYFlJ>zUNVSOU4(AZg!T8mzg<139{QQyGi8_1^DQ45 z*YKq!LY8dBKO=i5p6V5;zZiXr-ZN;|8~xar3ojSn(&9CCE+KVR@wbLfa?QTk=mhD5 zRBsB^dFWk4;z;`1K}sw+DOR`_V$%g{SdepYK;|v;5Tgi7wdUb~{iPHC_OiWzUvqZ? zakn2Kc9H)fJvSwO*OOUOiZry+DOTE2c+!53_rM&({*U3iujseEYh_+Uj4#v+s#0us zK;mVaRLb~Usd#$qB_m>b`~{FX?Tpfnvx}?-?%mi^d}sV6tmhYOY;KQgt?rKHOIyID ztr`Y|bonD8X*3j9h%Go*fbG~zQV)n0(hEx$;f*#I)xHPdPoTB$@Qs%*?vA}Lu7S)B z#E00t8ly5gfY4`3m;CBx%$XB2b)JO2= zr^{y94IP`b8a+ZQR-#E}feMVwpw+VUL~=mdEI))bW@PVQ`7A0k(^V>AU)HXlBDXx_ zB*-akGq3&FL_E_`>ryF7*$lXsh%l~w_t=>(b36!Vk=GLWriVQFiFuf)VvegqOWq>n zdal+vck|9F=6*uNrda~()V8cFmWR|Ix+nP&Fmaj;dG%NitaVkJx$I0u?QFF{J&j!P zSF-Kj2whg!_b`ir0M60s>yyH58>|38mOuV zYmO`?x|eR)l-1V*jPky$LfI)+A3QC>IIf!Tj|#eSNFM;%o&o+Qo5^rElYDWg?#d(-zGp5@6g*v$Rt@1)=3>%+Errm z*@Y$a$%&4nWepp!G=YUhuTJw+saG3{@Vht~3{b7>3?P)!XGJEprHoicnCdD?nWDW4 zNMq4w+;lx6FjcXwN*bhvz5uGFEn^w-Iqeu#E#-8RXU1(g3Cf+6)mP2-*leE_S@T7Z zD3OMtn(`A#CK;pv#>i^&QlV+B>{<4MJaw~BiED8&j-!cE+_p(YVGom=$lIP4 z8OL>u^tpwwl~Ss?3I#90)EMO`l92^*_f|z^B)*<)PH)heva-n1+|3)xINlBO@0z%s z_~H+_*G2-gmA1p!0oMhaDho5Ta?7jEFbc&?(P`sTl!*aAuBwA2`34IcZph5Ee7inS zH%gVnSVRwekTZ4N?oz}ExQcBY?I=5$!PgFlUXeQBTA0)O@GAfd9UW1Y8n$k*cXBRt zUBAACL$35lscCvhwa>aB5B1t3+X`gQ;+1ukn(*TqEh@iz!zas+QcXfa87tFze;hqhCZUc`W8^i-h>qRhU;DvB=u?%&+fwiJU?)Fa;s^ z#rR7HID1Vl&w5TX2fX)X=`dFWB@W*J4=crMt}j-8nXL>hnK`Nz!U(s%LlU6QV8FPm zCDkBcN3qaU8umM$wE0?=Ta)rg@hk)isvNTQS>JK(CW8slYGi56g36Z3ljbK%*P>cS(>~hu-3CoPAhLQ;fHuXDD4bGfn zF(i`aI3@DRk$#VEI!`mRtvs=9de;q;TdHhG#7}+IRgys=)=eEn(U8irHHus5rTM8* zM3eh|rMyW<(|V(5XaS|VIBsb@uX4*cvACIfW9R`cUQ;HAdiT~f#5#SvDM;X{?KnA5 z>6k>Lo|Z?C(5))6gm-Wu-U7;ZJX@UIU(s~`lB+AZjCD^q0FZTtV!x&#gk^*wYQ&Y* zx>E<8oc0C-R;&R_YoO9hqtF>Cj%a@h*@qlm^ps+|hyS{%P1SA()L;wKtU$K%fg(tU z|DHeup;cW~hZYk$c33r5qafI~ZcDtY_WK_pS;7n2w`D(~hWVxR|+~( zc~kKxs_A>v)0#ycPgo$dp-`<%oNvjZ5K@g2ml`uyPmrH4a{LIfDPYs79#|tWdFln# zDZ|`r=C+Ww%PFV{{gRF6btn7axiuYlTy*f_;DY`6V``ZDc%!}h>#<+<*H+IlLPl>+ z06uy#J=F-OpH{+6RRlbZxGSw6orqC{0hK_SJw$S#op4MjIFcCxpb3F1ZahP%DsU>i zh@zhsOx_a|xH*R(x+flV6@-64SqyZWX}j$G?}mCDWroKlan<#2H~qD9VMu%G>%N= z8*-;Hf!;d14Y`&6SlWak zEd*X?m#bUqUPMt!S-%<0Pkd5ww2FHToY#SFyAvBj0vkk+?Q^3SEq%7Con$cd46`be z`GKHYC(nMp9d6C0s-V4`J#*_%YA$=~Y(pV5_gY+%hLP9>hAW%Co`&1}jfznF{~p7Dc;f<`tM>zkP}B-e1|&!8~!C5Aofc&<=}$ z)EyK3#YdIXnWn2qEeF>^6o=ctQ+t{=(xSPt7nX@GjR#tsrZ)tOf<-*WD73mLGhQ|- zA%d)%S*9m(clkivY*0bT;&;2qc}`ZHP|;I#vv{ZOv!V~maT*Z&`7UIe@4y%OpB4rA zYRlQ>=bW5C>(B6)5uaVy?~WcVjA9CEV*Q!h@oBrv_E7!HSomL9I@Rg8#Q_0sOw_!7 zyq6vUeAJ-%(}F+=MHvHyo)n$_JJaCOkC%KHr%9j6BVd^&oBYM<8%|R zw*(dFlm}=>F6&}A0c8pOe;VGkZ3I1#u|rF`eekg0hWgf@5#Cwn!CG+P-n)9S$0*_9 ze8SfUUIK&Dm5G3To-M81_1|&!2PrtZ1s$oELSMl`7FOaTnHFrK??v;KGs;fePKc+m zlD_KI8w$y_Fx#AKk>KVv`VWb>|6;*~MFsi!{oaIr1tEo?;meT13~`IyXL#HNIM5LM zlNwa*-lh%bGMs};?XwRRK-@vD&(|0DEB+=apc#FJ1&gD4;EOL~)ZyF~{LPQLhxfm| zoSFu#b^D*pS@)m2kADlMQ!;jS`iGhG6E^?b80lM@FBSgp)ZJ1*&_O*?CK#HMd%^v&+Q%4G^yG}>_RQeSICibm^u)uEe$6I1kS|qq_IB*9n+BoP^V&ZTh}Dm<#ISp znT>!)4+HN_gDwxxX`|9Eq#(deSB*mzVp9}G1Q5%kC=6K&v-f9L4G#GSt=^&z_ZW<^ z8#tHS1U%@&Jy-<{nbc7+1Q<)JB_+fS$cGG5`j$^pgM#O_-S!1@%zM_X*7ype!2V>X*&Clnr`VW z(gT^+3>FsAnHakwHB*IOg6$w=hAK4aO|tNE6a9ekG;L431D4M?h@VpBr;q(L`rS4plN-6xF?0#^tY|o29zFD z9+bP`GD;g~GWJt>)xn@aiGrgl^N3!NLA8#LNqY28Mqa-M;-R*-XRUnG&YDc`nDEI| z)apUD)1p_YL){8%LY_1v2<>LRBzwDHM(2rpDTGo#Ombwry(qyg@a8J@DCcBmdJ}2h ztvA3O(h;0U{w%z+o;L}%K~epo!W0IpdFNyUx0TywPP;7rnp&67U>JF(yo+n(Daxc0 zy)H!*%3RU;DgOwxXc!TFRv*o!xfHzM5C(oXo?$U zGG$RqEB%z%)cUJuEbi}A!=aX4M!&>Po7Ad9Ic_FQ$e};pd-3mOxMdxg)@-=-2S}1k z3HB^c83%R7%LN%E;YG`MXyL+!33Qqk>v8LoLQc;`FD5bubSU?6copqo=IAm8E47!k z7HoT^ulQNdG&nBfRQp9lZs_FeIMeD)F-_0I!R4I7{EK%da*YfClT4j6RUn{AYC_+|f8Hj?W?n||tpJkr*@1}Wqy+BBG*GN_HT_W}u=Vl6g`U;B5qf9Tmi0`>oj!&md zq&PsfEzInd>rIK8mFiXDW5n&U8VQ!}l*v4z0nW3-WLm_#BkhgW;J*@><^;lY+rFYR z1;GSP^R2|B`Z%iJCb5uLMngb4)#fdp-rw}hg z1$FyjwjDUifkMAhq5L6d3V@*6Ltpd)&x^{-8LuINS zfss<-JsUzPOJ0hB3uytG)rbra-Fvip%3cQbWNLc(b!qP9$(KhyP<~E95X5Qu24(qn z|G}$})Wv6Mzz5wx3|i1LC!~z|AzE#|EoF+Gx$N}emlTzqiu$|8{bC!7z zDB0=g{iw6|4^UoYJh5)>2a-4VIkzGBH}B0s-^S78KdJqZitP^bd`R4wqbgPjr~~kL z@QC1IGP!=}WPJrte43_4zRuvUYb9>M}G12yd!a;sX=saP&ReOL|IP$fo z4II%PF4bM%FL%g2k{xDwQNge(>8=68(i3`$!15xZ>=uwILi;kaR>j@x)62>}^_}y~-)du<1?3BAu2Q{l+S^E8`eBv<RmEp!1C<~^n1&lZQ^Z(epTsR(_5VyXVkzudUZd`ce@h9u zzk@Q*0JN0Vf!71gF?O_xpU$fvD250nX&^0&&fFDo-j-gqMjg%WgdF2v7Z0Rk-zg1g z$w~}$c`pb0s|PiXmKEet4bJ1u`tKC%>-7&Rayiz99z&OZTlneQGlb~xR|9yYVnDWO z%`wyDF-%0gA!rAD2me2`eRWjb%d#f!?oM!bcMWdAU4pwigg}7c?(Xic3GVK$!4sU| zFq_;v?<9HWe@CQ5iT4R zO%|mT0-I>&*9+N7`-fG{J;8k+^lCImvMUwFbo6K6tyb71o`hfPONU9EEP*@OT3Z~h3&2NJ20%G}P;n8DH2iQ(n-0A&Z`|HMVp7?)qw(hkpw zDQffBC`LtU`lG?7)%h<-M&Ztru|t;w8`&!)Oofq_<761LCAlMG6? zR+1WiBQ@-OQCRo+!NNrK2N&J1xAreCI^*)cxab@J7k!EVSNg(5n>&wuL=LO@+71&z z! zwDxUGc87vdq7wfL7p(~+Wo1!X)XQ3Ff@RW(FL?XW)e+7V>)7yyv0MD+QXCsFozfmW zQ=uACgRqlABP*|jI%8{hkoW`1z;wp{EU~e)fJ{0$$jZ1}j zD_?^rqxQ1co<%3572}@NYS|V;<{kn1TeWjvtqHm5LjFyWG1fhIg!JhpCd(I9yR2@f z3RW8<(w@1@5j5sAU+Pp57O_qj{F%rF50AGJnK-lZJA<*~`Bp~>bVL%>5OXGWUm(0~ zLHSl862`8IAsTWOM>YNtk~@{b4j}E(qclE(qdgD$FM`I|59w zPvIv}UYLk9HyIryZps@>UV0mlZrU3klQ7*t1W<06GU6tx0Rw`o;7haiwz)is-;uJ| zk4P+g5G8VOf)YQXBAhBc$ZYI2QQAQx!fKn`fLB1#>c82b#`7)2%Gh0)S3iTJmsn^w zud1`r>~#~!?O-@7>!>_U>$t%C3g~v}08G~Q#XR-&#XQUFAjHyr>xS)vc7ysvdy0u* zKdgFtiFxr_dZSY2(DtUaL*P{2*JpDb$dt0KK(~n`OxrErAp?e0X_Bk0@DVOz85o&}O(>b>AQS%v8_l`X zBJAn{9jIgxi?sM?j5XSbaKy1K?V>%y>aBfy)aDU|b==$$Hw!PZ0VJIwy9yVZAxM0F?A{3rgzA=GN+HOenv&uVZ>GM?-B zbNeq?7te^&ch2)9h1#l(VrHp7BM$?}Xf2;_^Oyf3qpSLruvh067xiPPRvzx!rV_H3 zAueRjO5>QPUvr#i4XYV!`!UgMp5DEX(fT7v=7xGPV^)us5T$2@l(Win-qKA7Gv8WZ zj+nRMxue+6Z{y_R$i5PVd^qZ|zSED>J15C?JklaJQhh&5(*OC@;%kUx8AV zvgwdbE$WA7;gEC9jo{6XAmnC`!TS`7rx+V|XoFin`b|rTo+Ubkd5`?6_*^;Z`b|YW zE8Ji;=>>E(aJ+Yd0H$j4K%iBn_Oen~ zGW|#CCI7nL}wcgzRk>H}( zJ3J5`-;_iSJJ}=aaUtwOgdU8|R-j4z{k&dxxBSpmwJZWFSB1n=DesWoa7qNx9M#e* zJ~!VhV3ks5g_DkT=~7XhYhs+cLy?j_4{wMsUaW24z_Dbz!pbtLPr#nH8ff-dq+oo z_!hkzC59W~aBb{Ap38<3rGep!gdM?3Uzyu|vELGgzb2UULPuwn27wWWBiN*fcb8aO z4#>-S;MHB-kz~^>NZNSFiTOFm7c>%~z?oOraZdeAN0&-oNKGou{BMzAp0LJ8;C9z~;B+U~o`;N8m-fxTA~-4lLt z6eU^`Ns*)bnl;R~n{{s$$4OhYuiAL5nSXrYwkCuaTVju7s*11{1Epp}knp;ma#*v% z01^GE+`Bwl)g9QgK0%`N!*X5G+*UM=c{|KX zfUk6!ddQ0Dsb-^}Zr*?BS!B<7LQ|4AqXRR3k{QBJIQlE%}M1()7EJ9X8zM7}) zw_a|;`w2Ncw}ViYqU6JcPfi8XjZd&XALQb zeT?+6oFhj5;ks|nnPb(GdR90=Rl59f>w_-jgvo9EgZqq0lk<}HQN~m|?pnKW zmBRTfgXIIwar@J-;ui-U`*>x-DOD>2T5F_jgVvhrxfI$R$-85Xvv zQZ6q>zb`}@H=RDPBhOApT`-fx*Hj#bJDkRG4~vR$p)WK+F~*nH%-*(|XKAq)%_5qJ z9id6IU`F3<$mbq(LXY z8pkNxolhk-Fu{mcM_jt0@m19hs1>|{m$jpq9-MBW63H#-%V~+=YHo>gRR23)YsABS zZE-Q#k1>79MGPfbKp&VD8M~qDHmf>f5tm3WEa>z5VY8oTlWCHSXSvABISV&<=_TA` zJCB;OHsD8S+)!<)+;C4Q-7rrin~FBPyb-T^t7b> zkm_JCDjR69(f&i!2qk~ZzE$=^sgW#?x3w+om^5Z$VFqZ@X5b_V<73eluTY>J!RGS{ z$jG7UomBQ6f-XSNeXHC_aTmuNZX)V$D>A-qZfv80HB|ESRy>_XWh>!DavK`u+Ow6z z?|>bu%?s`HIg~_)6xW%+g2ekuaPksl>$&mb;h~-~U?+K4WtpOQXw#hHhl3SgXw8+7 z)}=+)pzWhbPcL0MC7fiSVIhhWufUvWIIF_vLmOo8-}+_CPKTmp4(^#}J@{W99@NPZ zeFcsXjFYjL-zAuzI!H>HSN3h6c^*iKC=azjg&T`2piuJ9ny{GnNA@5LTky{a9(~ii z*qjtyKeoMS#V-`xJSt`%8#wPY>%qK8+V99*Z2+8Q!}prr|Fn}W3}0ZZBvsATIfk<| z+o{319{1>0Cva2U4i5g(-6AJ(Ex6pQ&j#u%(O@9b;CNZoHA?>V*hAQaqe%s%ltU<$ zT9ge90t%m~BL3on*%dqcs=ryC&d2cJN6mv)(MnZGM_b9-hB+o5^bE}ty^FQad&`zI z5)Rcfy7#E~uyv@;9jXW9Ph5CZ$s8RvDfc)#M@5W=jC-sinv-A=^u;HIytkHEX4?Lj zG}`T>p;0_IMxMwS#;wMB#)#vId<*nKeGrTZFy_am;miTIJKkKP;v99RrD) zVDq^!C=lo4=t=bn)mAY$TzN@k{TQu^27Pq}+Q>1{5x%N7k9qVx;Zj;Wn+k{G`Rqde zdBXT>&nsQqW;;t0*nJjQ0SUIH=2IjlbUfEY6*qA7HLH9*;5RrvYR8m!paNf;0ksZ^iYzPG^1ki!)lmdJ>Y)qa088u6foUkGV{3pS->g zD*>H!e9y)-)b+O;4L!P<5U<5uS&6e7hD20E>GO_a>Kqm4U+L>#$>ZYg9;H~3aac$D zXw*Dte?evX7?#R|a?T9HT)=gK#leEYZ+bd*WHPNf8q+r+pwUPzS#Go+UXWn*&YEJ_ zgp14Dv_`+!#8I+V3_Gr?$I9RxcdFb@9|aaj?C@bq3afH$%*b+rjVjc1MhQ>a_b`hx z3vs4erRLV2-U;`ZxBPr#9aG|K7z1Y>eNIY^l`Ksplh_bMDh#L^TCZ!T`(q%9Cu@Ut ziD9qGERHPESn;^Bxf`3x;=fKRX+%)=sc7Nv;4EDBBBY;iMjJ5@?7Y7b1o-2a?E5;)Fw=f?uK;(5 z=L>E$T0xbnpcwxe5O`@A>LLe@v%?iy!+?9ZcXqPA!7Y9e$5c%{u6q4I-Mxuu3EACq zbKkyreXr;+pu!DQLnD#?m4f`rvekRcq$uy_ng%gO}N)E(wT z+kfihtFrC@7DckQzE77GbOW1DU@^oJEvvtOZ{%j)hUt3J!H2aRP!-(Jc|+fGf9fLX z^Gzh=5rQjqA;47v3Z|>emy?)$MTm&D#dQ-lRKE8xiemrPh0wZNQR4ia4`G*!1Wagz zd@v**k6GmX4!%>5$P@ziHVA(GH>lv)b|6=UhmTJLM$pjEwo(#OZlRKHsn7`f)Zg$+ z9wTNv%(ORzdu1uBGbaZT(H1dedG_aZuZ9E7f@Uqf1?*fi&G3R2!63g!<2|De^!5<6 zi1}9u-hqm|RdC{akB9M27`NGPi(Yq;8w1mCq4!BFh)*O9&EBAKP_U!btuntPQkA#= z-C2j4lR#Iqc61E>2)>C{2U4Kcz#2x4(nGva6X@z~4$}T0AxLLtHpVG+I6VQKSBob2XIgDH*0GbLEz~TO#|1y3Vj^xN76y!0*AjBBNAawU0+O}eP z+G47D(x#MEbsR-lL`amR#UR*IN7_bEc*G#c;4>d`oE~Ssq>cD(DL-zVqdcCE#9hwB zy!D>1nQ23@9Ay-Pz}S0;Qzn3rgP<3KaCh1%er4Qy7xB7TA3h8&gj3mH86&UY{VXFE zGb6Chct^*;tIm$D4w$^f_q^l~F0Vq7jS!>0K!iX6_a?n(O-=aj0?`)^rw{bg<6tjv zJf!(BMg!pJ0W3!}faOzUZL@_x=MoB4tw1|jR@o$O9l z{MtR3>#lQ?NPpX4X%Pz>3p?q3688REF$@4o5UQ_2fqxRK2OyTdMK1X`KpgRZ7|Qr> zu|#ZboE&VeejUmvFKY=R1m|r(Ywi5FFTXFe=#eZE8SfXw@%nVbyA{{SC^k3xuB^;x@@jSaMYL; z9KA&cV@rA{^Om|lMab500L&50kpmXofHKOr( z)%-X4OIcJ*ObX`+J;(SAy!2XT#%ec9Zo>lAQ6aZjvL51bq_0UO{K^NRokXF!KnW_H zMeAWuaC!qi~om~3~?a0wi5mY+eI#0!04 zQjj%~m;5WXg$v!g0;cD@+GwFJrObNgI5TlF^KKI?yFVV7u_yDRbeUS$WujKtu3J^B zwhdkNRXgxhx^QdNx&j7@^a)&{3bQp}bG#A%VCNWef$S(1<7IGh9yGxt((Yr;NrN4R zJOBx_38OM2DzXS7$~?&=eHXx)xCNfGxw-*)8D8`BQ#Cw}#fS#J5vz%dj+`mHMD-yzw@C#IJ>9T9E-p#}h5E(}f&V=sS;G@80zzVU zz=eJDb^jHFeh4t+-oE`Mc3=Qor$&1n8$R|FYhwkPvYGQa=>r#3$sicR?#Wuts>x!6 z!DY1N`Qg;kpieRl;XsrjmRe1Mu(VRhY<3PYqtR#8WWg~BI`;c-N4tp>aBy!}(svUn zO@b!DQ)HIFGJe&+afdI)=zrF~sJWw^t)uZT`fs-Yd4m)%Ue*%YCs1Y;F8dBjjYO&= zN{kLJ!Q`HdXCPo_&3F%I&`<+h@dn7fDC#HyxG;&u@dly$;o)5-kj#X6mY<2gsy}-+ zjrKliXfu-a=(2}omYT4oL3mS76X)P}ZO4|)*Y)B2?6UbxrZVd>`j49=ZXCh#jJy-#s2u>ee7{KX&C|8GowQ-1^#OqU>< z=$_?>1xbc*%poR~bv>*)rDi4ImM#)IQ7fry?I{p}RM^WCsvU4`2D?v9Yd}Wolszy~ z26E~M4Xh{)bB*DNF;S%M)7S|)4ds~MMf4AP!N&&#&UFIoUDLVG^61sPVRcAS-V(Rxhq1GbJrnLm{n1 zPg^%fFC#f7LsPNrL^B~p?QJ=B-$2J!T4u~pcgJ8y??6^y1uuGDnfiJ@CDSTdJt^v-WEOlR2+^GJZ4g@16zcSF66+vX8EG*P0 zlj*6ov5ux_V7Q;6#EappCabAN0>F6zhyk$vA^5%AqWBj$zbgMPe149B8F4v1PKh%y zGhbp}f}r(320_dwZndcQ^(I&(AIOF3`ICFeirtG}hrxkp1RDq6aRs{{+&xKZ2vX55 zv0O+ghMuE1UVi8n>1gPvt7T$l1Vnu%mI=@>+Aug;xH3Tu(4S;`iGVbev~(i?nM44J z|GOvD|LxRXyV)7o82uIihb>-SnFlo2x_W#0W%alvi$p}f58~)dy`D5TGGb}{ia`E3 zUa8{);fA=RheG7igpL#XY~;m`8^~#d4XjEXNe^-i6uz*bMfz2y|KW|bY|$u7w||ev zQ;Mm1$wdtOrgPg!NcxtwMS#Liqnelgo^<`qU_DFdgqJ_Cy`8QkJJX>VQlNZ0ORnQM zcy{AFWVzxu7mNDEi#b#=`E+S54;Sqjh@t^4hxP|7g$Akcyd&5ceeZL~yVp>{s0}@F@`@RJO9Gu|CJ%AI)b8#7=ljoguz*{C%Y$;xrD(< z;qc$SKDa9$%FTmE_NrIOECbbb?la^m=`V$PFoOue@IDa z=zR$d49bOKhX)q6vOOh?+^D21J#Y(BS%gruFZ4b%1cVp_^pA9C6yggLQx&L2*sG)W zq#O#OP?UXOd5d**jVz2dMnJ#sluDJH+n4aS4Oo9S!1|Y+@~Z`Sk5>bt9}>idKs^pR zxSnvG5`?(;fvTN?Q^dD7if=Vj6uj9X2|GTbp!W85Zr(H#@<({`+PVVKgmH29M;Z1o zszi*XYFaL;Q)6Kam)S@QHLHF?`o0C`Y?Ow6P#;|&(On~l02lj8!1(>eXIiDX=HJI$5fQ!Vri4s0T%3ym~Qf; z`H>Myu_#93Na)5SipONGUvRuP821H zr}`Yku)X^I%pi_5}ypro@)s%QZdDpP)PV)9boSJ>-$5pJ|9H5kzH0O zdxduW#OAzsKyaMW)juC5REi=&CSM>y4aM>ij^MMOf0bOUe1soh5SJaQEcd34&3q)(>(`{XUU4(*3&2^YMDLY2?3cS3J|nmzJ@hFTrUy9@vtD}eO}pZNog-wol%OX}bg z0zqYF+eBf}7O>Km^+eSZX6;=P2~oXOG;5<>pl+1g!fy=?m~NFG5(nGXA}QXJ^??ZM zlOmy#CvvwgmQMvbc6mk5$r5puUe4Ckxl%M6ZthtsW|v>U-p0EB}M`f0DTrhLJ@`o&U};D@sk&Pky3Bt z`@(m?yXgjiGoC+Kfw-}wle5FmIiPo}meRBWQvY*5h^$CdD6y^}tQ&N>I+`tsGIEH* zPEJmd8~hh_KQiMOpl=^4`ES4UpGulY*gN$hZiEr`obhlTv%iT8MO=Kgz5dE&TD$1= zczXc*UUz}oNjua4Xx@;s*97Q;%m*0}z=vG`H_Zt6;0thNNC00h;L|9eCG=ql(V|Xa zDtL>BYDK=-UcM0#(wd}b7x{P(Q-IE^a2nMv4r7AGt9mNhse~{=HJ;-}kL1YL9lITP zFC}OMZXt7kt(uWW%tr>?OQjH~QZ+aVRn!}{Gtm4hE8)rE5FnnAwzI&ay zSXO6zgz=Zc{Ozf!N7{4}o2dxE+w1eoYjx3j3A4656rUf}@1HV4_8YB+Td60qBhk~e zrN(y3$9yRIxV;LHd&>g3 zl5j|MEd~rSbm^W7D-SZ>q9p`^D}=_oO{}x-y-6fFdz)e%T!!ZzM~UX%F^82ky!WTJ zI>$9HGLm7E2HvAUX~;_}myfn0o?-c9=BXg7ci(ru@pL^+_w|kDmwg*da{{wy?<%5w zqA%1~za5%O-%5pSJWO6qoRoXcl@n-(>AVmnb&I0*;GQ{FFmIqZ~|j>27X> zb7iE*90Xx_nJoYJa^QFvvw?2Uwd@v$@zfrO?~&eO7rF3w5@IpNb;`-cc(mIJ(+14J z&Z7KVP_5;@xHP%t2WB0!BmE!|%>AGhbh+)tBpcijt`OSeUc4J(V_ZVoeS?Vccc-ww z77H|}h9(gJ_b>ohf5@l*Z9Km^iNDVmzhBO5#Z2WWi$TEuW2)#HgrbHt{(~@q|1A6; z2$S|-2ooCpp9m9+za>mwoPzpZk--o^CNMxC_`4>ye+L2yV=KE~lh3@&9K=a;fzxBI8E zVD=x&0$nSuw0?k8?0*jU(#Gz;BtXuQd@vwFZ~=M`S~|~>S?WcjXbB;&-sWeiFGSiz z%^Jt23!SN}`UOCqD!hfLTj}^BdwwHv{=};X1W|N9kn=TrEBi28j54f?v(8YsVR;$J zVOpsJ?0KM3rK`_QaNh~D(5TW4Gr+WaNV|LswtudOYu)6S!^un|iA}r=yyR6RNOt^~ zEQohQ8WIkq_aybynJ!%;C0oN$rC=D!P4FPYIbOo4?ueahT21JRlHW0FJZ%Km`M^_-V^~D=jNdE1>BV3dSb93es-N zLIZ>TCMYDjqo{lsAdHM&T=}=404w#%oJk9NCpAJIELR_>g zPxt=LWoo?FKhCfY4A%*jgFg@2i0LB#HA7YNqLLEbG(!84;!X;*LCedl=E9fywiDNE~0WPX7 zsFBEX<|P&*1vL{RwL2YXxJy@3kKY|#8`w^-kUp4-fKCU?x=>?=(;P>;q}-6yBz<7+ z+v2vC1D+7e?0{QSU~A`gx->9R_1~BDl?15!NR0x z>XbvDhf>{}9oCZAOxKWtcyzNV&sf`VS&|f%GOM~6{M^S~e3Ev4OEiqI*)*qJ_{u=q=d7Cu4c;b-)fgM+Rh z-^6udzDD4uNApz3gWx|GsV`6sgsDy2UHXAwK3jekj33kM$c$B;({MEQE7Uif{f<+& zAKVj6*lmjjGc^QtdC;#%sJH1Z3mY$+k%rtqgeZ0;h`AE$oJ=LYT##aU1mN!khK1)Z}EAUsJt7)hL`|Ts3cN&=DQ`^vi8X z8Qr$9A?_J7{W}MF*Z8*_UXWhq;36;;LX=qiuP)wMb{U90fc)zJyt8pXr31XgB4GWY zPzG4WukO!z?1c_P0;IuACT#2(LbNjV0r~auz^`8)u^|xIYml{yISOSZa;cuC-UKXwqn~j>dx` zv27^Ra5g*7%nNX~Y9bDkR-q}5AKbZ9cG!g)tfUFs9-^dk?f2&RzJu(GbaQlj{UHh| z2QVRE35@?Q5nuR`E7Z55FH9&0z!CmIL|J1S=U+UbxBRe0mjV*sHVygJEp!9g%&3mj zB5@EDJuyAZYT;m(+Ei-P%Au<>S<@-#-J7Xqg+vqm-I0;x?vV%GaZDhFeaNEiPLY0* zd6CEok7==G>}>uFFC?5$S_9$J-d)f9Sbz9UcRZ6`Rr|EVHX{{+@-0wYt&(~fI-R@2 zx=*O>CpEbTS-}{a)xzxZIV%oASYLBR@}>r?W_KOOtI2p(_Jwsc*|k~4iUw&MsvmX~ z*rhJ>aA0Ee9X&uEJFv6ipEHj`=Cx16z^^GEe&fMATy86v0RFHAaJAtNxy=hC|BWm3 zj#;%q5kl*K?iSbBS4Fl2{LI6f-aA=8CMeb`p!b$i2e&g>y82nN!y0^ir|q_#o8(q0 z=_`s#C|KzYnc3ww))kOlj_EjNyF22uo5pgnJwAT|RI#lA!ZOes-9O7YRy&y6p{%cI zqG`gMEVJfC7Z?xLSDo_e7M>k&!CboA`nxOZ%B)>vb&-LrNN}|15z`7L(miVlD*_m? z%k&%nWe~##<%De*Qtw3<+D7{2Pt%vWsx)Xm)QoRzJU-R%sx+=dggxDzUR+b$=gN1n z^*heAbi}-|Y``s8u}@E6koA#I_KaAK8sgw^%`XgDMnDh-1q8{!?^fwWZ4N+58053LcSUYzUQ zqCLl(%7}$ZlFZE_{+^t%Qt+HZUM?Ng1msG45O{l6R9gfE0+5ND41ZbUOZu5MSGe;( zU}FE;dcXRezt5~QR%Y51|0hhW-v2iy_C+>TB-7d#*6BZ_)-SUClxH|c{QJO7`xueQ z!Y|&qZYrqkV^%Vo5gw*dFZ?YN=4Cpk%PP820sD4aa? z+>>F}jB6%nFFS)i$SAUFmVMGl)({|W3gzA8CU`^AwzlonU8i2Vqy{a#M`(1RWT(gk zwYhK&ER0LfTl#4g56mTJnP@q)Y`P}HaMlMmO;B9<>NonqM%XRzg=Be&Eq}bzeVJ8Fc<)R#Y*(8RT0y?t2`WBpog2ou0xw(OI%kq!T=!8(4@24E6d06gAP>g9U zZJ?*AkC-(~TjE-kdYj35J}a0HmycN_6I^F%=@B47Wu!)Xu3td&_q`w1m)L;+Yd^n4 z7q*EE;+NYu;HEs6(|(8xNDO-HOGH?7lI-2pzA+v zXj^ATrmw6)fPU1j?0TF)p@tR(7SF>I|k z5{;{!vCo-XCEgi++Uh|i7{Vy*b8pZV^;Q;54?-%A&U%O|%4>K#}zahFSInouAyFx1yyy>Ki1!L`4@Z4Dc`c z?;CCgreKDp(WKtEQ})hbN^SKAtj)~s+Dx|xoOeFwLDPw$`n(qy;;b!?0lG67=W^T| z8yOpUyggb*1iDxVHiT1RlIadMh%>-pDKk+Ytw}Lv>$ixQh#;WtvnNEv zN?6clk2cU{iVinKN0jAdK3nKX33hf-SPAr%^{r@6nfru-#O14=r9%g6a~l1X+gluD z())8kouKWj(hRM&_}Pw&+2s|8=04WD6x3OA$ z+MIcxU$`WwJT0l+s&I@!+9pIdm{QLo9aJ~r_hYHK zA+!xAVb8AW%FDa%R~gn(5V{E^dFvlJx^(8g`!!e&jP1}Tk@q&mhbX`HrMhps_Y(CC zLE|*hi#nZIbv+!SqLZ$no+y8$(|EpU{D4U|Fd-&>paQCc)TXZJ=Zfo)GdjpF@6YR( z+VpzPdJ4ZuBy&StI4LLAxP~XFQ0V|QNcj#6A6_(x2RDe>l}(>96}~5F8{WwWxC`bo zVaXjW#~`D92Dgr5qiPK`mzDtf4 zqdHo!JO0REk1UO!TDy`)pT^mBIj`JjZ}XO}v0sPpezp}%cvx>)UOPV{E+id%bXde|KBc_+RaBKjf9Qbus=;<_IQ$%xEHy*{n|6XhrEy9RnkC zA;4gcMW0Gk$%G^+T@5#Y@L-~es~H*9&o@l(Z(9U_%A%<9Ub-Cy#=als*L*-qa5vZK zR=ob!3wy7nkKF?{K8v*!_D%;6>h|-(f=UB_5VE+slgGr0<@D+I8A*JoUWV0_S7ZAb zuKkauUqTDPx=1Z>e3XPG7()?$d$Vg#h`MkAAea@v6aOdS07FF#Eq`&xe?=MKpPKw! zAm07dw`T(XUyXTU5P$##XhN88i+Sl?WBXG8LR!YjOGeEpC9VZC@e_ZiVS$VYN&|{Z z@&SThL^uQ`wiaIr1-647#)vF|>~N2CH~;9K!Z@ECbaa}uXqlq6iIk0@KIrw!DX(Of z-g|x%;3>gnUs9eZ3aC76U)IvOAH99A0G$?{9nkT+;BU#Juw+7D8__uEsUUva0MMDh z^?3k{t^n%~Jr6%Nzz>Xm-2s1rWCQ5N9wPs?}-RLFiG2+F(Z1L+K6$a;z$U5W0BMY|E@w9i6*pQ9hKwi#*zDj1mC{M_E-9c!&LEeJTy zn^%lUc%_c4AOv%cwAA??styB*DyWy5QlURFyIt}_dfjv$Z`&!)0UR9*_coxlkL$SL zN>)gB<;kaUad~;U_2KDa8}t2ZuMmB>@h;KvZnqH=WN8^`nPI?31NbBhNR>jUdZBeG zCB!E_&O*&qjqQ@jb11jHT~&@YSXHbp->MJ6N4HfrS`?RdMQ=lgQU~{))Xq1-u2ECr&gT*#M zwF1RBkP3-KZ->WJ&LHR8>JBZMcYhf%$#6db2nj_u&LfL@Hc3lG{!s|V@ zc^;3Ef-6C`)A!=M_~#cmtotv<@1B#oLrZ8I48?4}VWQ_b4&U)ait{l@_irudLbZ%n zX$iP2XF>qRVgv?G;nZrz%e~Q;`}}l!7vh3Ajeq9ZPf@e6iX)J-uF&y$Ts=!oKujPK zq|;TJ^v1(G-RekLt8kfn`-stLWQtY}PtY%LLrr+@)j0VmXx{Ky54tF?eXt;?&|C$C zaM>z6Wtj!2!JGsrUzsLo`#P`>g@^n%FH@-7+fO}}b`;mB-kFsXnNZ{@P0K1)#7jNx z`#{MzWPY+Mkq8Z(Gd&7UO}#J|A22>96E-vfUbqPLOcoDH#d_Su^!@y6+&v>VbBY02 zFFcU$lB-u zCrRV1q_gi`rOb=1+~3qM3~!fCeYFYh7m0wXJEU*Opl}`p?S)Z(nuIb;o?;u z+rYul1+3&0600P-nSbrMmkRXGI@b2(>~;jO{=jekTQk4zI&V2Zr;{Ms@br?B_5+N0 zAqOXSD>@!Ecjk_LU0UG^{?$4j;6+ENwh(#*1Ydtr(#XAU2>JoZ!WCI)1^&&^>Y-f0 zp}q=evx~S1eGQ4P@G7G732C2<*gt3{dUl=rR9T3iQDju@J5?@Pn7Vm>%?tKFZ(MGn zTr(fOGPusTnR5g&h+~u+ry?;*A`SehZRJHS>Vw(nmlj4cE>{QpB6qmr=|puf}H z*5;S2CqSOh7BGjzn_N(HXm5gWqL^wJ9$!eJsEUZ`RW)HSt`3@xf}Ar zJaSOoW^dw(w>UYLLsaTrLtL$N@+)`>svOrN+XgPKJo+;4Xr58cbA8D-uQJA1C$;*? zvNI3TRht`%HEo!QpeNh61B&sq=#LnRf|Zy^8~BE7i?f|Tg;1>EBC|lfkO)q1q+LlZ z-?`*!KEJv7Ejl_hf-^w@80~gIyUQPVhyOz7XN!!xeL3sv`wSabKn8_|@{OHG}+fwNx^W~iAOnjqi>P1s>Nfvt#_eg^DC+3y%b$ztX6#p`pbr^E5!eC*SY z2q{#=IfWHr0G7*iX^+*3HH|f`Q_w1Nopj6;&JxbDlYB0N3iz&*k6F-@V6>lv&=9xx z`;71+Pm{V)b>+;HOwuPAhq((kiv!)^VR16d-3a<&+h$Isx-Hk(*=TDN>+>l?Kx7kQ zPQYRU9O{h*$d*f*rtAc$w{ul!C3kQfKOsP~V&*f`X$gKgD4DD$N6Kccmq{I-qNbzq zO)8ZkBTPEhDk(P#w;_X}*@c|0k}WinDK6QGE7DY|wwlaPM>2CYo@M@~q2XL!!xBrF zzl;h}PGbBqgv(;CPR;m>)c3c~`rj=C(w(~+AS_<8SNB&L_$p3~g1O2A5OU=>-DOoM7?<(HlY+ zvruff7CuFw*$9DB+xs-KqzAy-y=QGe5G=xgg?~K_jNG1APX)!|QVV^lB^ff~(!PqD$Us z+*aSGfJsSQUa2|))EQ3(FDFV@+z|gGsyK*E`L@}XdI$3EHn4bdEO394C#a19KeFAj zD`1LSp`S72#ld@?BhFY(X>IYt(si1r-wp=_sR-;33UUw2 zQ3lJTdU$RsJTCK_;QB51YvQY-K#QMK=lbsoln{WJaZ1`%j?$M}Q*I}{d37*M4_VaHE> z0x$mc`jaNm2|(C^{}9p5jIDmVN~9nw4a$U;Ht*v4t+4!AkT&0Aurrr9Zs~)<=_|L$ z)=D-qqha#0&%?1BorD{aD6zBoh1Rc<+mc6k&p-L_e+JUtF8jzX1}%pghAIPfR?a-M zioA3&Y$|M;Y$}7bs5a*j&8;HUk8QxNx7Vv`S2DX+Q)Me>H`u3fhNfH5^+*nONwcT8 z$G*^~;yivHrL06f8opE{_~`6e6buY_jUm(!9y57`hu&*NE9U5_f+7!V=$lCZ1Ro8Ry41e8ex{J|7%X6YdwOyRTyo$tCakAwX9|dfwCy? ze)GF6nxz;m0L111mB}B7KcyGce)hSFvbKP##5*;T8`=g}I6J>CpH44AR$W+&A8kW$ z6DdxM2&Emn%>h&6XrR@+*Y_Dr(VjHw2Ka%pr)-ca|Aw*Fc6an{^y|aj<0Hj;{I6_P zLG|2o)o=Pi*lF92^=o~p8*mo-;4N)5-AV(O=hllmYctHOA^Anfr1A~)Kf=kKJHk8)d?TNr=KV<#&YI zEpLPdn|oVom@QMumq+-mFj~JT`Le@Eu@_ES*YlK#7r`B+BaSP7#9&v~K#UOqd_>q+M`Af7y{X_u>!936`-mhQPSL4yGEcXp&Pc}VX3 zd!vQXK?P}7&+J1A->oV~H9ASDkg3_l!E?tI=C`jsQd+jI?w^yuX8m)c6_7>xc~bec zn9^a;E8IWs6;9^sXyk!Y`z{hqn}gG76W1w=GGJnRK-8#F)krmozUxFJ*MDahGQ|=1 zx$E7?r-``v-1ZvYV|fMa2f?dVFh?P-flcKcyn4JV)em7lAr29+bcthJyPwf8Hxd6z zwqA%P1*_=Cc7V|^0=B>(68s`a_lKs%t`Hpag>(tcqpl zPm@fi+jGD|+EFs`nGjc36?v&JTpjMc@JwM7yJmK*T$*tBp5)~e*-F7qPWt1pGMNqg;;Kkg%*cFL)ONYpa0Aos)0!~E z&5TxhtvK_`FzZuqPwh|Wn2S0ci3{+b3g(!UyVcxP0>I!H_oj!Q*cPnVS z378}>V#wETWo{bVn@H>yLR(?_z{%T^qL`?*2|ifuh0v6)yoKVI-GRXluQUp8>t(a- zo?{Pl3S3TTc*RNr$3Hx0&7Z4f2}dhgDry;<-MIxWGERTyXZ&TP_X}7+A@)OXnk05X zd!A|N%0-Rq_oK-26I<*2+{$sA@FoqmV3M3{0vO6G=(>3 zSiE)SYg&>gE#7cv_^i#T&6!+2_uR2@wOI0cVkwA&8rWn&52$JM%_75rTCdyiz63xx#pa13V{`yJUjz!N)$Lx>^YJoK}b}beU?&?8NvN3>)zPTl^35@-md_yq)YCei`qeuW>k8 zg*_)F;Pf$8?(2E-t&$8XwYjY&#Y5f$)TWX!$y|DpVc>wv9zi$X?3N65s3A%-GFnER zwzYG<-KR>&d;bF9yZV{6M|~gnf$!@-9QXgq z^uHhXf8WoqBz!C!!ThI29wF{qMEt*QTZpYF*) z-^JMBU(l<+6)`EPUM(ngR{i&69UvTX9H4dqB70^xW3=xQwo`QWw-Sa;YhDh|^lq|K z^?Q2envc&1kX^Vl#Jx(TJ|>#-xK4ksB)A%DVy#9W{5g7^ zI=`8&C`{F)yqpN(pE9}x<&m?d_k@93OcObSWDDKX9=vZSh-F@O&K)irrCX))BA=PM z!-oUnqXR=?@bbOW^kY*-h+x^*%6R#X#S5k+*#mxhWv35SGR0sRXxYZ+b-Y*ZJC!lI z!XMaD>MeWgWrr=%s8x*AGL1Sg#wcM!c6eH5>ux|chVbvKaCYK+>m5svQ+q@Sx54_Z z9U}UgQm>suc<~1Dt`l{ycpKREy!utUWy$#`810XzIp#X?^7)@hOlDilPA>%6MOU2L zS1f~hSibZKlzL_yYKv-Aq=ESZEWb0Lzl!VSpqb>flmVC=OBNZi+JwvqVrh>tJs=yN z$r}XQ_2DCC1)Dj8Y0`U){;$lGyt(40zW|fJ#>fAmx&9uN|5p5}X$b8B?%BoVGg7QU zx5P6Mi3f%m;zG%~vbVw2RLinpY`ofT?kCQf*FUueN&id!8-2HUvSlxDlKm)Bsl1uE zfji^n6f}>mos|?rgWe2Cc^9`uv+JG|jRai&7C@|?yUnlWM#Mb-R!npoas5Z(@{9am zC1TUQ`ni9F1mZg+|A7bpS4jS;U^~ZtOC}XyLcXM(Lp*4-oi)CfdXMRM$>$%8xXdqT zJ3j@2anNVQbBKAM3}dQP zVtRiioTJO#e3b^qBfBhZwTlQ*_Bk1M`)bAz$D59Xp2^XmR zq(j<4Bg^!bTcLM(C(vc=@SnQ9znUNRw}zbj?||%nU;lyK@K-?op8Nl#4P>Q+>IsB! znbEpKr|o=HRP(R?%S{%mF!y_ID&DL9t5)U_gA%8e^AK3S{2Ig zG0}f$SCrlDjE(;3pZ8XZl>LjRfWBd)h1P$k90A08BBY*t*YN$SrcRyLf^r+*HfO2* zvsK0wF#J~c14Q~H!R!G1MGoutRf;fFf{e+`R2G-()zn2T->xqZ#$YD?n;jcJIl+({ zK&tkB8ZwX!1T)2)5rR@0+`+f7ZVI1ds0!hkgd3s&7CvxD5{mh^nC6K)A6k&l(P#l) zEa;TDdcsYR0HuYAh}ttISmQ4XiG8gORfcwhD~*X9RciZ5lwKnZSw^007N?Y9|Y${cJcR>Ur7ty@$ z!mp|dlccham8@bLZ*^5FcW0&FNDVMPl@}q#!ib4=)Q3EBLHB=F#8!nsF;;* zl>6yXh*1j?n6vST@uL}0Kr5IYSl5}G^ACiz5nXgfR(mAP2aN!=C+olk*%bQSXelRV z5eK?PTm9rRwYT*@xfhi0-cfa&aHr_E#^zxn)nHZB9M0WC4_o3x7SyqXzS_6B?T^?a za}Uh5S!6kFOP5`lC&Wo5&kq;d6&iM!n8kuH>0Mc;Jyf>i zrH`lj5tEP4m8ei{P%uRaOC?HYm%-z#_VDOdIX>);q8MWaF^c#aVtt(H($tX!r7xli z%?)v6q=Y)qSM39C=u$1~ex7vwVTG9yAc!$;%_S`{@dn;cGvzLJlP z1RmIxS{o?_ZteOftmdyr0E8k?1i<&vF!`RM{)aN5YV7cLGMl%ew&TM0;`2!{pijqDhc&{sDzt`${Mz1%-DDD2(Xyc<#^f`C6 zzzDYf?lteh->33_4a|{yf2p{115T$^tK4j!kd@K1S$))QCaW`V@eo)|x#nrbljP!7 zcnaSqAO`t+H#2umrO?Na?#{ z_VwNK_<>n>Rv$tJcDBYc*O%j8>PDJ}Y5PRq9ldcXPWJt}zx5Is0CX^L( z-{%Esmw#`BPrUXQrr~8T?i_%03jv!qu*FECkD7NBCUcoIyBoS}O3IYUJx-$d;luka z|K#z-U6p|WGNjtHBP+L-Xznj-17r@yu*{pG zLUvx4O|mG0>M2S%K|+?2P!Y#0Y`!=6!4LByvvt&H31Xe;^(S`D$ zXFmX&#taqSXCK(3!|n(t)+gTaBUSizC;b%3AeHDitNnV1?fLVnb93_J_Ib7!U@h!g z8zOHqoY1K=?17$x^3WLF%54}D-Xk5yuz<53%3HcagYRevD=2+2i#-M+(TA)6!wwPC z2r1D%p!q-=Njf3QS5Y)#l{pb6kpO)^H^Dw5n2FZGKBe7Jy2B{THU1P-wCkj=E=o&ypbk!k(?>eyql=h@Ul97 z0wzpT2zVxPy>NvSm{1o8Q zL~UYNpEN@5K_l0ylR&v(V5X=OIzdryEgGl;eSHTr1PXqW9d`GX(z=lsvvZC7D&QY4 zq_Dw?7#YBd?y#z$dO_P|Xs6ICug=51E(joU3+MFwM7BH~lBX1AQtA;8oENDmR)oAG zdwnRWD`~^FLNtUa=JlqpE*HmBF{saPBq=m)!hp3{8`9}E^IgY^HOkrzj1;+4MeMG9Ij`S0^+I19FxMxx6|yZ>}6KLW6JAzbZSH ztzFklBBNXO*jZiU|2hPG+o|KfsS5CAz7>u<$%poPYDM@>01I91{+U4m;R}c>+jm!3 zLo04wS$YpQ7$%nyw`IXK^L(ew1I>MYCk#04AUKVz zW(v*OZ;r)T+C@3=*Lbr>IxGcP=w+-WW2U zQOZOr9!*X~ z@4_l?#21?N)MeV19Ls;&qw`n|O513hY%%cxf={> zh_ayKI0lqNLtMNuOv3{(@%bf(M$33iZ=K-m&=uc&S0q@xt^K`KvK}UR9!O0Wn3xZZ zX=XN%1`#Jz3!t%(Ls zBUm;Ps2U5*De(qJi0Nf9AIxUb+iV=1kDk`%?_C=_{^(xTC|*?7kfGf2z49bKyOY~5 z4|HZ(YIfGaD&rLT@$tj3EbA)d&A@(2 zjgZU9sO}<5aPy`$E;cs&Iq|Oj@xn4>8xN}}=F`kN)zcb=O`Fw>PWUd(eE)++Wr4<~ z1zn4$6%y=b2O`R>TiQDJ!SfnF)d?8!?qcS zr~a>O&$0ppui)iGr#}i@(gh`TD|=GEZ+1AmIPRT?dYrg?jzjJl8Jc5ioXcWZ55ZkxlSfk42#uuG&wXCXsG!9o~@ zvw>zu2U|&zna6`9T(xoCnrO`lOZ;ecw?FUjXZI%$Pi|}LXSIce8@5JTXp(tLc%V6z z+9-;CKWLI!T)`;h?%n3Hhv<&;`7kA!b~Q20Nq2WScF##)W~=c&B}80?2uW>O1WbpU za2k^$pg7pL7ULfTmx_{}-3qev2m=ENO z@sc;jHgCXaY{3J=9_F5!az(MO$Y3M+pA7a9$^3^baYNbDRgr@NX}nQ>F@Hmyn$I9f zsuTCU#JFOjA! zARWdLJcc)NSFWTMikaT=x*QbiEpc1bn=!SEBppmOkWGhFwd8g?eFZbY z_06(t9mqTzlPbM&1>U5g9EML+Yqhz?axXV~63cxQG@-M~iZ+pxJPuy`2aJ;9A zU)1UT>Zr%aS=S-2(?e_2rjn`EVqNxXE_-v4RqqnrY;X#TjW$zrXx==TUs8^7^Y8-a zd=5XZl}&3mQ>L^*HC21Kk+NOck#>=5jz-?xNh97wmL|5q)%DW+ylK=58)rXvm`Ud@wgz9Cz^0AEla*xq7!B1~F= zqPBvec#FN34|or`Sbspezo1_zZVcaSqd&nnKIm|Ffok?{tvf2_xtwGU$C~5}TT$IE zLC~KOg!vah*t+|DIEvw9#(Aky8=DcztR(9%u~+qt;PHVYNXr7$kiCH9I*m56H`ba> zy({RF`&;fDzLd%LU4eZD5IzL?3k5dV#(m`I1!U%e^F%$ve-K|NC-dP}+M3yHtaRn` zJ{9pdl5T8v^zZc0M*d0;=pqdYqL-hjd@K^xn1F1uJ9}a< zA8omAVZ>}}MQJ|F&?&m=x0*Ra7ytGT9S_VdXXf&-LYlQnE1JNyMBA!}?5?MCudCji zXm-}#g_|?JnvS(*YV0thz8Ji6F0_5%rI>2H za79ZMZSK83a|95IUP&?DKarWc>T@HKEx%a@GrBo&5IhA}vRE!ntEJv9yH<}}hhVT6 zbALlAU9A)!Sg-06m2<^2$IvzjIV0JmPgZigoU6SQ@?)y#$#Lvjq-!{+%Vq=wl2(%) zcy0=qJZu!R1yaXYRqmWUt+7Ey)r6^+)iSfbBgh~EL!Zp(wB0^&ibbEpZOurxgA7tv@G5NlkE*Y)1zrll##+Y=udR(kxO4?yZ%2!I zVI$4wRZG&PdGMj+<=U?D+RKU4vG3tZZA8OoE9sVA@7s%~(E4WSHkfI9)^PZAQ`GRl z!pKkwmdlu#Plm`m<`bOYGT&h)}%cCHa+Pww8zpHrxV~$!Av{3>g?&VTEJFID)X7PJZ!o_ci_mb z)P}p+4e3`0@9!?3D2Nh$+rd(e_RaP8KU{PM!fU0rdJ?4wdL}yApNA-C;%^xD#rB?5 zOSRc&av5=FHZNW5W!58GAa<3r@B$ZcWj#55>@5wNr!6jK@s6Bw$j&-q?LWB7>K-|T zEP~3`I=*q1+B&?kmR>u&@s{G*V__A;pFUcOp8gT=h-oByc%AZovoO*04Qkiw&0H_W z)8j1}O@rV3b?JfU=opuCY!z6;biKTpPWAYn&LZFu(I~k+`?JDw>JHyKj!9{jeo)|z z-85q?F8Q8#noATu$z68tO>NBkmZezs;3qT5XQx{ZcWqphB-`(T@kblw5rYz=jz@>? zt;>{|1c#6%1*oB!MAsegnZ#H;!%_*ATprPeOw+Jzl1pRHZ9IL|`7H*?MB>FlMjlcK zVcOQw?5|t)@xv$agUts&xkk3i>2>O!k@BNMd!1&s$~(q!MtEK4bV;gN#qjsG`zhnE zm5N1_vLmN}kDp8JWq7VTt8wytf`~$IAfT{p8KUA0TeDo3$-gPuiX-ZiwFKSVGe$7+jG!0W;|5=R z*kernTf0BK4lB@8Q%Hab>X>Ho#g@Q2B70{4q+83;l9s`yaG$c=;#Q$@p!b4lZeCix zefGI-s%8D-WGvpbm8esDvlC zC%I=5el%nOc?MuN4w6HxlQuImpE9l{al~))aq@1n3R83QY@0LmLjj_(#$c-u>y!xl zLHai9Ak~Zxed^lCwD`#*%;I5_Fl@roQy13gI;E&@y95eaLu`gM(H_q12?8dol%0$c z2A`@nY2F!1?NJI?+wY;}FZ_lEw=rb+NP96uQzf>D+b~3Xmv=ZOy`bql1q)&B&<#ss zZC1h#?j>iv6Y}vIg@mCYJ9Vs>x|v;re0k5VB_IFB!lpp6vIB`!d|%QU&pLS%zg6Mi z0)Zhp7;ut>hPE+^s8UCT2lJj>UQnKVF7y%;V&FwEY1<{EX5)y$TSI53Z)qq z*{O-MtQylxR_~{2HcZTGv0(4>5f@_naQqnLOsJjk6M%lzGY=WOR&(2hP9sZtk|ZYM z!)?1agLc+utbqpHMnP?`CCUatDXYQLb~{NxTL>=F60Q;S#d%6W6u~Bp#eU_P{kdAF z&uJ4Wg6wM29J-3uyN?m}x%*7rRo8(ft2fI-EO4c~LR3QvG%Pk@{7Z^ zu5NFl>Z~2cbQDk;Km`QHu9CuP=1KiyUynAMJfkKkyc(mnsg+h?89pgRj2dd>Ur`-m zZ|xNrMfeE6Fp_1Mthh#yK|IBC=|vSNvlUeF(IsDOpQ|AvT~49O4U1$7%g%p5K@Dgr zPXq&Wbcq~e&`@%Mw1ad*!BX_tpL&NIPR*n zdNA1ji<0JO*$lHVBmBDLHsY$l=64qw@=-B;?PRzbxzAE@xc#s^mVgR;bDPduQcl#hP_K+9YnEA>>z!=Kc0vnJRg|)fZ_}vFgk{lN`gQPQg zOeUvO5HZslk%SEaS5KV^7JuE8M{L0F9XSE)KM7&qUxWD=s~D8pDAz|fE;MNhw0kMc z+bCY+r^{i5fw&8H<1P~8YIP8&?XYoqp)3RA+NhAcB&zGg+DlaxsP0K>Cxj@^8pX;{ zUF!;y+W0LsXXd+|eS~;N$ZGIb#^k>@OJXo18(59X&_K{ z7!*z7Om>aU(0elP#dcIi?~So`C#w+YSe8|j4Si>W|&Ipg~>Q_mOAAv%IE zKdb01{p&>M1z<@RGWpY{=8=u`T0)y`UMU%}(eT6FU0JXTJs(5AK>WG%9rRG&j{J`)&Y>WQlU#miMaK^s#~ei&Z{w` z796-I=V?ks5@RIG)$@2tnC@A-3f;J4D<|W>F?aF{2ISvNu3Epgv9h7%X1_d87Ld11H!42TF*GM+A1;`w7P0^LQoa0r9p>k)4~{$DpFG;3gaEZN@?8%dr4uH z^Q5KE5fYPN`8n}W{*kLB|H1lWVQ05kyIWa%q=i^+srt( z(eRcOh)rvq#>IuIQ^vSeYyF4IleiS$Bb3F zvIi;3KM|62i99;_r++e*USa)r6$h`!5|=; zo;{*d{z;a!l|PlC>^2T=ExTj&cMZE^Oa8MB=i1jc*whaePR06Fs@jlP6}oh~ zuf61JLIF<0wGX9FDwSu5;{Q;KH_g-JPON~VG*+O%{@uq^c0?Q(I1=xb2q7(s?U*`I zy0*`O^qPf|E`=Y<&+A`q=BuOkE0{93oy6j-F;8J_*9GfS`w%^mAbqEX=|me>2;;)pi?y}3_10-M+=t+fiSw|q z!ixQ;X?9{((`XwTG-az*qyVfzX4Fw(?iSjwlngt07{#XBsl>|&Yl>Pn^?EG}a8%8Q znG8E!@)18o zv0^sh&rJX5_HV>;Rbj`+P%8M*lh?DfiU=(>NAj5S?FFr>%LTWFzP#FcLePtwFQLrR z#O#TkS!rRuQqJNXABGz-&SFe|$(^E|SX>N9!Qh!h)%p-D*z=S}`;?lK#93(e;w#NIYoE#Ax7ln zbpAuj!pmP={TCBG7k?n6qM(?RnDFy(T^?2@=JqnVyYZaf9yKRVLY(ZAY?FxK9H_|T ztZ9o2bIuMbzgn1luH#dly>c&Zw8#Kgv2!H@kV0PDjn#^^zz+qjdgAnPS=f9P{~sRo zlA{6!>0?$fCfT0lLEgr*8F~QOrBF1XxMakSBOSw%`pQiR`lx6nxiTgT-tn0~dk|#? z_8JNnfsaDgO(7+hRLDa($|vaL_hFM~m0yF?67xq3tl(>EagpAN6jZDP5!1%_uxASN z86U_x$Gf>Mtri(w=%y{QqYf4H(YXmw?@W_t2T8*gAu}+#kWKI(g>iNVn)gx?fuBxBwh7I-;&Q?#FZ}hWY1TS^L z>;~M;IjTny6ILgX&*Fy6P1}&kyyB7B5uR#ZopdGveNYOEF2B4Z!2byj%Eq{-z=#2h zHj_!~S1}gd;VHQheSvESIHd0ekfMZuD(F+h(lE^xgl#6-$b&qU9SmD>j?>A`oA6MW^JE1-w+-37fF0yAbn*_kKy!iD&0^uRDOYEWe$3nso z^-YKPA?|^o0GR-x0Z{@{0b&DkNzL!!u}i4-ADc@XmSYitYGBE1g-VTXnj>9%jy$+}LVf`s$;829orET0>ncQivBnmn{tnVRgw+ z+bXb3dCr>P+l$Z9Ye5P#7UlJfeoTYY;z4$UjI2WHV0?;F!k5hYl?Pd`Je=q6J;dp&xiiUs|wiZ*k) zU7trUU=BZOu#YRzwZdBgk5>6!N0W3xlLkdMJlnUHUlr@nW#-C`LnX9N5?lxw&#&fc zMJ)lxmV&m#Wf;6VN9iO&{z?w}@Y3-xF(A)glv}bW$+P5|p@Uw?i@tBFcQK@Hec_w! zRUq^ZvZ!Nsxwg`;O4pS7Mat& zUU!|2iL}DYG|FK$tGDn%)X+hPFm#308@?Mg3T5INpB=pV03ukt&3PJlT#=_?{(k_}X}sa&8_dSMGy81uLDdA_ zGjYALhu+lh6Y?G~ZDIqKNCt6v7k2b~Q(#0vg} zAwMjIB=-lDn284RgGix_zBJ1`jx!f0y*_M^kZI&dGh(j&)lKv=2t8f`K->(A6!3Y72F~ehOA3g28-i4`Cy_ zmdhXWD>oBQa7%_yAntoXm>ilz3kcdR7O*h}x8Q@_9$|N_(aW$&z2%>JV)9$XHcaJAY2eN(h>@x{wUX8R$F$V=9BajPWkR?Kg zC$;A9q9@s)7@kzBX>{ZULpqL&$-Bj_ zPa$m!CvE0MC7a{*Ba)WF6N-YenPb?9t!(-&P2*J2UMop@t4p)DjDNUK9zmuU89rV_ zvm`y(elL86nHuxzHxp7L$C54WbeTKwJGZQUb2)Y?e273p)HvuB>C50s!DwWXkx*odSswi&Lb+^({+ z@VH)9>g@i@v$T~CW&Rd7L4+0&^;v^%q(i;675rIY#Eo+1h8Ma9zxIJC>>cK_#Hl!*QX!9?^i0pS*3ju@o8BRLYgQ)~r#QSE?U|k9D z(PRWH8LVB&j^Pt`_}XLG$>1R74Kzl`xNM3vecjNa#r%`#lb*b^3dP-y1Ej5>!i|&2 z5f`vWVebAi{%J$AfJ!ACT{9X2K2Ji-vODozVZ8lI(;WP;7`+oFj9^n z53`gk1?<4L_eWV>U3!y~ZNz8~PojNmzy~GsW|XEVS8MIa()@PF=}c-3a?;398eKYE z$2M)rS$mXdiE^|#JobU`(6EbwMa+1^r_-mAAqQarZqVrRF*PKH{(!|}0WnI^Si9I& zI>8Ls)}JgnpiLBxDK2~YyO<{xXP3o#A)Y4Y{M=1Q$o56g1*{CWFq>Gn^MS!iSKxyV zM4o~#Ibo`W-qf#>9O2Y-pLz?2=gF3)IB$ugP4ZcZEL$BU$lUreAFCJ)uNSx2LSWNY z<@Um*jyA&j>FIMEa%zJ^Qph#smRX5<7?9xaMmFMr?I}~-DaGJ{qA;t2==VFbZGMeXJgs$d9|2FVA3~z+IF#mN#PG%8gp+D2dL#vPE!RC;ps(${L2Pb8s_iDw&s6_i)IU{Djc6~sV4t!6U(nZ)pWhU4pj`SJ zF)&{9JteTuwZ1{5MJ?d5UR%j2SS!XlgKwQ84VWsd6-(W8pGyj1-H76N&I1&|*{Q?l z!wbL1A`_0mEf$3xt0Q3#LWDY8X3&}Rh$clvDAd4_fZiwp=st2ECQN`iC>H?fGjDzy zbUvgVz9@W#s2Vsen{#v9y1x8VVAdf@ifL0W0Mj2S2=+5x7?W0W2uyH+A3&ADtgfOH zN_w_T9E#l55kmJ(=|9SISR;w17fU!7c&t^$LP}`{55|PcIB_7VW2l2z#aIP92Tw5r za9ZE-k8r#fctH|z5;s+OwY+c|wuZs`MGoR#lA?50Qa6Na)W5H1xfBGq4p2b9(+N#82|P0sXaR70|R!lX`rZth*3 z*I$szV-0kCT$=5J1T~to7q1Gl*uKr`%ZzM3&evoCa!iPWh3w;Sps0*2elzMt@@LzK z>HJ;tA3X}*7Cb5F8?CTi!k>}DHk$}PuT(?ydVY>!hC$htZ%3iU&3h1%V`wxOP_I)Z z9~jbtrq06L>tR;)`K#vfKx7iYuHegtqWQ1#XZATjG4*>PE!cB}>ig?w0crjuO>I%d z|0%~BiHgbeTEvh@5@J#^{HmVe@c`K7XZM+P5K5qxlm@PVTaaM*?wYyQcSA*@xQGkP1%4GbF97+>Qio z@%!)z-A*l`R<7+cfYcZ_M zJSI(Qb?%~_i)O8una=KlLtZZF^)T0YxNbXj`1MDZpj5Kf!@R%7lSn@(QGrZ;Nq80q zOHw9R*_&mVPS&?i(MsW{W>6~9XH=d6l+i|(!+5G$ltd_-QZD2AwMuE5D77;=BV=^c zJ`;5D!c9wxU_G^0Ed}-4FLym;J+N2Rj5(R`npZou;c%SJNFP~n1dPHtavbb&z*Hw| z4A)c%)s+xVr?0@b8mgt93977QP%dPjek1SA!^LgNIMm)#dbGhI8ff`{SC?CWSR16|hHn`D@ zd(kG(4>(sFl+E{F|7PfI+)Ntb4T_2wN2<8I(vM^`H9D2(M8Ul(KXl>XxJWE;L_{Mf zBZO9>jr4btBlFIj&rH|3j_NNS5baq`&ho)rLZ+4*EQd_$!4%35vbRfL6&CyfCNi5E zy5{-71(c))#2vj}eSrlci2w+__0-OyOhN$S22Oeb#2LK?f9WCVrQw%H~(;H`2e|rk3fbk=IYQY(Y;r50!d3gx^xh~ZX4oq>JQR3TCzu7{tChIne za3KuIb!f*@FiAmSjvHk8BnWJCAo`(-)ZRu6mwOW0u5Mc}U5!w`6nr|>e7;p&($LxO zZ%1deM@u6ovQ*e z2^(k^sMl_fU?bgt?}@|f3Sc8>fMdf8(3%t>Z$ry-%*ZF>@pMQL!g(NVtDP&~vMp1v z9kZ#7!UNK(wBf-8g%`k8=Om`sap6m$ORN-zleIuC8vMbL1OY`QJK;*~%+WXgf$Db334fv&y&by8?9HtBPI`j7n@7liI9dOMJ652xHXfr6_V zte;TA6@enoRW)VQptnj>s2%8SQx$V(QlMS$WYnI{*zgKCC3UcDWS$GO1AWZ?a5T+% zC_ZV(Y4~8^{hS1DhN*Lr@`fYzk@~q~RgfzDz+g(06^{2FU>z53c@sQ;&-bg-6VVO% zG2gD;I()xj%qSvGfUW9=_R}Ff%V>h(^wIg@O*IacH{emc8rII`w{{iiJl&6!ItqiFDXewL zd_yJ1gXMj}}^h6=fnL+k{K zafIb#NQXML-E1cl$7|Yy2#dN7f5QAJyTDl^vg6yLu{#)&+Om(JZ+63DC9&nmIVwJ) z zX{lvQLuiSp5jmlmWO3-T1{O8_B671Cp-bnZ{~`GDxQ9Mu3+LXL~|EOhQ4HrL_Vde2_Pd3Vu0(BoRHhSfFQt_W4l2-O?)r z?ZD+P0pcLeoP9Wp6Y3-gHp(RI>oU}T%&!x2h$5{>|G}Zsatw0h4RI_{Zs^MkbS)un zYJ3u+M2}zJY?ZzLP^-r(Itvp>$a57+wPlULg~BI`K{Uq0=tBN(EI1F}nt}E!I>%7Q zA;#SxMy5EyVu}I&x41139h{#S#7>?SQo#8L^yltkA|f_>Kj$Mcm}>^EYYvc1kh@2a z@p-iQPKal=poX0VPRw}r0zWuWMT3CwI7-Dh{VR@W`ZaFAA@Wg_Q{pc42L2W^s@(kV z)$nH)=bv~$T$47f+nw)cA@)BFUWSF*<6iH^LR%bIe;J-+I=KH#t&^KeiN8U3q zs0XB0UvIatf7#R-S!q%?0Ad>h!w(z*A*1!^=A*FNN9&Z)|K^(Bj=Vp-35;Kyfcii& zqI8>~x?yq&Ch!HM%wJfh8udD4LqNw;V{nhf@I=O7Ap>=D!BSsO9uLixikb`+8WxXY zSiKeUs2dW|tj9a0EGk&`HF{9>uT@-EQGet6JC4|5T_p7Wt&y?$me>9}HR=CHJ^lxd zD6H?K{|_utJFZ@KkRBoAtFVZIgg}GHF`Rn&C+1H1UH!%BPKL)T7LkYtZq=f!Yt=o6QX}XL!Z94a1_hIMs zT@TB%yDa+smg*=2+l-ELsrQ76H4Z`LlVMAVJkNnKE!A0vZ2F}YR@IM-d~}~?G!iZw zG^y&YwZ$H9WaZmX^b%GdQ4E~iEkstjOhK}>!63V(HOAEn#_Gj(&wF?Cc5lXsUyj|3 zlC$fLFkgHqp=)t?00!c?gdF2cKMrGvtUzP9Y=|JY`fc#5#o~C9DPdUuntf2ULZ?+7~RyXqTQ{U0p$za>=Pdt3jpwDoi9dvk3;*HG3=*V3Jv ztLeWrY=0;i3W%CIGVpdaO&v-Qwv_p{x7TkQN&2?7`1m&9HBo`38|WCsz4lT!d!@fiDG}|Od&}fT81(vNiDTBD={Ut`YbmgDMPDoXryPPX8-_b zf|e)oZD9+;pONejE8WOs=U4PED{C)m#37%)1y&w#007^V_5WF5CHigW{*QFYR=ZTg zQN#Swb}=z^ZG)gDFiNya9SWkIJlIe+50WIBW46LZ>NF=3D4t~0NTKH1%!XtxH!o~a z*V3(MX%25LQNh-gM<$uvDB^e<&N(Feg6e(UJ}i%(Wnz+-R3E#=Ic}frzVF)Jy>536 z?e%@Rk^y)=;K?2`#KK)w2tDL1ctMI(KqCtv9&r-)N*`7WAnvIsL_NgH*-M6zBULxj zkhY+A$`Y0pV=8eYsnyrS1h@8D;|HuH5g60qMc*V{`i;jr12KZNRb2wqLCyV6GjRL%&*nM@wZU|CPR3VK1lKZfQ_r9ele?L^m|1DiyK}npCSiv zpDB}G$_M5ix`dr<4!ssQ$3|rdq~por3&`oW=ufr7o39A%+$csm#g0{J{BClj}q?# zg|D1}tu5R*9JvfK*rxEj5?5!P65Y{%Y3=569ps2(jd7M=DhxBrCpC@nWL8`-x2fG1 z`hZ=HL4e(tn|Z8|*MKV(>9b8Q1~RiV zv5d_|b4oR|Y-|XRLpF7rY{91wsujy31su$Xt?`)S&Cku&T4(+gIkSqn{BCFvjKkV4 z5TWr;YvVcJ@3UT%4gh4+9)oRIYjfQu7n;a4fvP#4QQEq@+XR0YuZtt<1jTH!SFRsh zk{q4lx><)eOy8NNeR0OmwYuI=hNoo38q8D=6E#C*9AB}<JB zJ?}a1e4whlT3kBpQ0#=4sqRNpP%E4%p5IZR7pfvgs3Ue?c)V~qj?hURFADTkzF5C@ zW3=r?f1r-2TKK#gJEE{N2*D&A{Ea8I>y1qlG1>jNo^a-Kd|T;5?)4?Pvy+mGmx}N6 z?m6Nv@T2AQJqF_oK#ctwVH-~&U$y;NWkI0t0*M2PQX9C@Xpi_-H+p}OP%6j%nTd|dJ~9B6?S`;~|js7F>U zLNcp9?}9g7OkAykq$MO>cY$q}iC{~2{jykMi?r#*szwd@0>1IN-%s2+D<95^AN3Jj zI~0e%t?Kr?^xkhq7$>O9_MH{iZYS%tFJKB%ZAKg)IzcXo3~^fLH4|6lYOgt$z(pbZ1ag;y@b-8{QJLb}^x- zkBcY*bY0+wi6=&!XUZL!b;~;>05_Jwa8<%RW&FJNpuriFq)IN_K@MX1V=zdZ$(P8gQhQ@zrhJ(32d%-!1P?5JT>BW=> zr|uxRDvebS{oi0|IeUM!;Y4GbY?c3$td&GoIM+t=+{$@mdS!d|sfygxDvAv{!^;`c zQzp!974LwAGPgCfB{UV#!SHb%tA=QKF(SiVe4G;MfWWOV?(Lq<8^9f<|7V1aDCoWK zoS%6>KL}q350qwTxn1cot5sETb&rM47t52+cZc)bA$x`3qSFa4a-Pg3oKICSiA44G ze2%w7z5a$|;g2-L_y2I-U{xx{M*WvIV_^OtmBN40=Krp=d{zG{qo||)=r(5S9^6BN zia<&!;;=2GY$!!bp`n%%Yy8usR7z_Ip(bU}M$y&8w>}&0s%h6~@w-@=PjE1i3|N|T zU14x9_Cd9r+OP={O@w&Q@SeKQ@YZ?1^o9F>f*J7IRd`4b03l2;o(V!i@KW|~;&%rN zcjG?qLBfIwcSCvqf=w)+@xdp|r{I`1Vy=>7>J=AC!1RLTOi;y9w}%2m zWfqeSymMVV3Pr+VHcVW|M}rkB$_CvoK5b#Lgp8OA>@>l}Dkd>68kGWvM9HxxN53W| z05wKoq;`+$ND*VQ6p>iyt;~YnvH_|SIQkWG8G(=tqi=9-SxtY@6m#3KEmpLPiOu|5 z0Gh8f30mct@$AAV*(Xa*`;aYO9@F@@X!#R79_!G`=%`@eV1(tB3v$UVC7rz7f@L%p zbHTO8+TIimaI7>!v7h2#$VJ273gp#vsuV7(5;0~dj574NBw#lWrGjKA>&EX?l^JQP zfoQZF=P0o?ptH(Z9t*M$NXlUB>&vaNy|BEY zRxR-zS4<~jYF)s7_bm+=tr-+Q362&R#8 z3u|D!e@fo5DUD%lGIAa((l#!VtpUR$`Q9+G56$r72B&6=vKK0ozMvp;2%ZNPKDJV* zMk-hQ&$fY;;P8>M8a6r-P+c%B_QhIcWXAyUmnfxaR*2P@SW_8oa1y&gFobzxUwEZk zeaZ>m@!IKuh2Jb&w%r`=8D;9=Z8&^(TZP9G4NOMqEDFmna z#Gfi<`jO0?pt;SwIK62wEddz6W!y(1p{@u!sEad3HQJ5iVbn! zSJ2DzGveI4rByDkx+=)}lG{mkf=;rVejO6nq@uavD5f2uN{8i>oRi|_cP8jwRcp(0 zp_UXLUR}75@l3)d;9G7*+o5*izKKl9k)uDu_aM#{*Yv#InF%!4LpUZ`V$%W%R?6H0 z+T=u{yqvDQEkVnC+qG}bUf6J@7e}08`ICd^ z!bkl$zw^Mh&a1 z+ww6h-5c;rh?OJ%M9Z5^F;FfBPraIGU%q^pq@^XOm|<9-ZG~c56Y`5R?MZ9UOHmWd zD`$1(fUc)U^X?t#Q}UoF|AR44j*!a7=WDr%#QGb049!-zexT2x;# zvNj?QB(_a|(JW)Y`m}APsifNPM8OYCrk)gXpxcQL2YwvwDZE>Cb&madkb(NCAoH3X z5fopDf*ld4{K#|=6N{!jp9oi6-w< zHG7-Xp>#LW>$l4y$}xE4S!iN@Cqh?)m1L?y@3aD>DE0-36s2L<{^-wCs)9fRsCbtp zD%J@_{HX2wDO*pJq}cRae)8BktrdynT$MtrK%!!^mAOHF?Pqb`s>8hXoWb$sXpJGSxa|^8ImauS z7d~>BTAnhR(j40w z|6BVmF8trOq{kP^KxKLP$M}?@hLDFSYjfsr!wu{f-Ga(8tq9vGdOM zO~+}T^L5AR&ddEpraYc;jO^3M*So#Y#?9kySVq~;Ox+LPaQinz-}e~e!=GV(FsjGX z`frS{I>;}&lD^s>UI{FJ;cI@tH`c;W>5qJb?(ZL*z1V{9GC$zLWFogbKj=M{B69#Z zY_1oZ96T&6CLeU+KjpMHc<~7woB+fx07n4S9y72ChsA3wpv4^YOAKX*fDbJX$cX4vg;XVBfhxof_!A)q#IfuCXZ9#S zz*u3v9k3_V$A~r?4WI{?Kt7Jq3MmVXITy>_%|J4OD=-aaMETQ%m<8tmrIi;vw}tqT z!iZ4#R?!SNMhB*a#*U7a76!fe8cjm9W~U#rNC&54!P!H0X`_`8O)sv<2t@WgeYJClKB8h5*U3h<*fIu5p0nLSlcz`Pa9v~O63OqvJJP$VqQjfC76`%{~0(u7M zAT(|V%*8_X;1y>7n#)G&1lz*{v;lXc7%({5gRDW+6BK)3^mj2c=_ej)?_4@6?l*)KOso zLh&F8fIwh29yq)M5abt6bmeI93flQf*dGqygS|xx&h+phrpCGE#>MLIV4bb;qw({#2j(wiio9r&`h4pk$ zmeMYnePw@60c^n@0nj(-t$9En;FGnahOwYLc9pTZkK_>{88JF12PMSPNyC})iy+VfYl1964IWO7fTb+>{`4+!94|EH zUl8yU;4QR5Kkxm2kpN?*{pJzyfS4OFClXSr(8E=6b(B2kDjOkxqWt;!iCC%wAf5&b zn7f4mDl7VLFaR$B%YbCW5xYW8L*8YEkmdg|FAQIo=n7eQ#SJ+Do*LRaA!!aE&14Yi z5RITO8%fiTD!WK*1udLJ)Pk;uHQ?-r4oC&0gH+!?ELZe`qlklY4XuV*>{lb4+hZ3I zQJTV+P8FtbrZ9yoo!}^5Q3q5Z`DWY8h3mxnIDd29GMB1zEV1l{gkb|o$olKIZXTUZ z`|Sut1=s^2wGlhw%&Yt`LvBLXQ3He|ER9M2JPF6t#L?3W~cHl|vgwD(iykY(w_n{TKb7hG@WO>h@+X6oN zZ=Qg9cV~Ce7W}Gz*!p*6<-hR*HbFjnJaz_76Vi^IZ^(b0l7$q-yuIaOKiFE^*xOmp zXS9kK2cFc9>c(ax;WafETmMZ-D{EbuxlW_n?(!9!i#IEJZc@`^p4zOlHuwzRp?TwQBz3YEbxQ~d4ee5y|_ zO(a@Qk6xqE3vMXaA^kU}w%Z0t#`I*z|iVHA0*f9rcck<|3V>aPYU2=vNSH zIe> zm44@vpwU1SEA}Egy;Fk5FZZWX?53^y@!1zKQK>xT4#S~ART>s_N9l?XV0*MwHJ%Er z)XVJkjhzi^jjIqc={9z~RgTh{2H(G^nOw&6MTH+)OY=Lz|7@6e z{l59K2Fq;eWLK-N*Fk!ZP%bEtA70y@br$D#8l{b$e?K)j?hfxuA=nQCiuQHC$?;}7e8sDlVH!?$WdM|)aXsjrQ9{ zS$tbQ{WbS!K%VPt$+-3BR8Oasz>zU$bETsO3DpTXjnz$G_|ff9z1HfVbItj}+M9i8 zGFcE&@6joO#nh#p6=+pBIjtgzi^{{o$}{>hMv$ddADNn@b{iOkP-} ze?ZM^K~`QGZmmtr=V_=sOH|AAvLU7#+u4;kx0EePDRfVzMN>M87FpQhH5}RWdz6n! zO=K3m5gJ1bjTYx9do1Y*V>w7BOCaV`EnLEK(UnGfiRf>UT$O&X2E zt+!d_idBkxbFJLnnpvNlgEV0%p`_gG5T1HH&E=ytQo<~0n|`rk&sq%>tE=pMj><{O ziJ-H%!Pw=rH}K`QhoIKxxD2PD9f_bJpn4bM3znFu0y#H#K6!;mh)dJO3JU-ASi`!i zJ-4#C*;$xxG4`0gKu5t1ywmA0T4pkI0B@ZaD7nJ2uqCHk*k}@ui}Y+6}rC{C83Q!T@FtZ&hF;40x-+UOD3U}I9cDH6Xh1R~|zx>^SI37T82 zvLxNEC9m&fb`@|hgU4i^nk#_@tc+}PruI94=M{s5jEF6dWRxDoV$DFSJ+q9qizv_@ zsH8E`B_csp!UU2x|4DjpiY?^>!9mno_*Ikq3OaA8=-xVd_YgdsxE=S6Im6}14usyNt8W&mHp_DD`t+JLuTk=Q?_IL6R#_0ZueI|Fa1y!?3>YvV^59t0_dmQ~V zXOA$S(dhnYHU5$Q>j(OR8|sQL){wqOX{(FAScZx@Ot0F&@iI~E(=o1xqDWzxg zHs!!4SOZ>c4SmR8%S>a4HyOtB&l~}FfKwou{tWV>bm{;r=OojWg1$ch<*5g{2jB;x z$s8*^lT3_k$;oQga?KMLWacJ#l1v~L^P+275%b~=GRzs=r3~wtrm43xW1tB-77Wc3 zqq#Gsh>sD_DKF>9QhA6nW1@q^W@JsV52DHLDIKfKNkCfg1x>({)Ps|BzT_c`dg1!= znO|(j3e)PHH|+!TgVdx>d^6mMm~_6ZbNTGTsm6)w`zGCk)zqKOIcE^W^^#qi%-D>E zX|rTgw&FK*=V(*A5yDoa!|a-+3O@OK>&(sH?7n=V6}(Y;!XS$nXE1bMNvnD$W)u>RHS4CC+h*~m*CBD9+uiU6>896gFB&ILls>_p#iqU6-G7+uSM&*mPi%k* zrywS~!6N#Gx|f-DBdt(o)NJp3_8G2Qr+Q9o(>f5Gn+cRZ=pVMz-tgxR+@|Ker@1ZY zFk4xVD>c_QmYSL-n}W1YaxM1hAM7T($9(clcdU2JGhecA+NL0Wi6Ue{&^{ErC7ANb z^!!aXyv85!@$2_lT1Y=<{`?e__D8PZ3Cr}x<=_)cF03ObV&Ruq5aE%<_Q~yCS*V*^ z)jkQIj@NikMP>D#j@LRFQ}77(OD(leF$gQ|gR@yKuy1`73qbdcE@cO4R#@^$D%1N( z`?j6@ zxxhe5Vsh&wRb=b8vEX0%Gt3l^q<@}z5}o!6^xm7eH^%Su0psSgy`XW+*;2Ayh+VwqVN6xj!QYdX%WhW}6n&==lVMS2IX`0@Ml zQ|fOvK;+wf%&yZsX`8ZNOgqDk6^^;#PJ7d3dFKtd0huQteEe^ZwMPNq7rmaH!tZ-? z*6Lt9h(GH|dxOyeo6^tfYn;;0?rWIZ&FU+LonZAx2>b>Sc_x#&BCtea{sHy+%*0;M zAI|v+bqW5i=9^iF2k?}a(|$z*>WJTU18&XFj{@k;il6e5DOFE?=4erMJ0t_ zp%c_1@}eSqdW}SEWtPh80Y5rFVEyte|D>CKBXFuP7wUC74PN^})LLWG;4l;2xiz+f z`BRE3_ai=JfKu$m9lP_sCI8Zf2}8Ao>$qH9S-PQAN4L!(%@P=`T5D07_fbzkb8H>Q zCOz8m1yCBSc}@ii!1wS1S2=$_b7p|^+>kzx9rt;L-u(>z=vjG}Bma4vPFHvBBWv4< zIqk*)EG`?{!HYR3d5ROdNbn;IZ9a?>FV!WNNLLL$G2B(NgSByrBHjCRa)|U+#z9^A zB`eM}d*3`e!&pBhx6zQejAIAt+8(jYH<$;<%0f9@aBXJs)<#pLjBO6Su~3|&X}>R@ zA_q8CldKgn6DLc%GrLoZ`xw4k#7(f+-eO~YSrn%uOwRKHv7d2m6zqUd0DWWLQCC`6 zKAeL{n~+JW+oIBs3#?A0mCXe*-eYLyxi*c?h|3c3(IzRbXbwGoY_FTv>y$+;;sQ*& z5f7$#cElQI&1S~!^R>&}&DL`GQ+@o-Wdb++bICDSyM(0)(qc2n)}iV44)WrZ zvavYv#G}o5kWvmx6zho7YjCEz4E~$N%w>R};2-r}TT&5a(1$tG5Y;wCj?vpe8%|f7 zojuA^r(QrVeb%&)PH0CgcUkk&-g%26^!y}I%vmlnkZmK-ZDe$WaN`(fa<9O{|P!#%)q8pn1;tfHkzl! z*^^X$%&iFwGP~E78<3|4wM2-MuF&Zu4e%%^WpxFGr+L>LpQAkT_}sA#k7+PYx`cQ~ zUDwll8oUzmHy#~#L8ue{HGIcFK2dHh_PjcYq!gtsxx~)gVf#LA4@M54h2nV8B|`!! zLSBPVEzOp$M!Rj`=XjuRk<&CdSU(TTyGd8@+17TAn3is*sXQ5g1;HASiZihgO)_zP|J9-o{pCS7&{-Be@cJ<|6Md zKM7ApRdMiVlWu7E(TS&PQ;VIO#nC+Q7EM^|fWDJ{*8|l-++14S7$K&lzDWymMU~Fo z&1C{A7}{|K4Yzz61jGCDuyDy~#l;`Qj?1y0!mAJW<1YqBaKl*j~fWLMEd6L(QmU2p~`=F$lkn$K? z+4j{o)U;r'J)h4#X^Dyr*bvK7_ByavjmruO8Hs7wjE%ICaGq*~y!1mrE6dZGrJ2VYDaXn3=HEeQgYb~~= zb&|D@H;p+4IrH?dS~zNGM;s-stFts-_`4v8W7SkiDNJ_yeUpw-G&J1SI;67BIF*LF z1%&7P@9}N;-+9An7F!)UgW9LmiKF!6?<=EuY7t@zS`iB}PB85gqT69gf5DpPSr0(B z6d{VLSqBtBgZ>Vh@HDsRR@SzX{}Nw_CtN0-stMXfIuKIS3t(0c?zDGITwU@kKB#~!)BSfuwXz@$qOgLHSJdW|>d|3vI1&O&hA%+|dF-mNWeuv!uXR;POXQoiSbx!oEvu_cA4` zbZ%s%D~zj%R%5Gce{Fr|Xlpw0w-FR-IgrNTOzm@SQ!rMrI&NStt|j^co^Fk{_9nAC zgb$0 zan`L4e*hMpIEsy3h!GOZN<}qwJ5S?8lW=lOqhm|=)KN^0F+`12jS-08(DQh+hCi;1 zCB#vj{%^j*y?wn2=ME)36*%fJ5s-IG=suNJzRPh!h~WbU5G zQ=?jB!#%NBFX}4aJOS%san%mW$?d;Mu`{X;rk+L(X|Hb^S{rRqlfera3i!ybj)zJu zT~oKUuJ$B(E}!fZfUW6{0-SMRP~O+$GD?6a+0~-4kd-dKEf6_F-L>aQCZ*#MoW^+k**&L={uCt7}`d z=R&POR~aR3w6jlqbZ2YJd|N~v8jr_L?wj@S=Z0=P6PdKN-|+ks`_8Ej5jd@Xb@Xw| ziWz;+Ej}DB>5({Bq4+=0Hj#pJd6N*1;rDf#-A+dCg2-JRr;A&oz_FNbJF2Who~*F$ z+SBIxPvW+*h6mUh3Z=IUaS~f@ZZFXe)-$ir>aq~OJUey%Uci;5n*HMJFu;)2N%0)7u5K_eR#!j7udt3)J#J^v&nJwn((5@jy3YCG=KkJWPM^Q1iy_8r~sHvqX-j-S?-NHC6O}$K+J$UDPBF=O0t< z^NFx2e_x9!#z%;b>^M79Y5Q6#3}JVy4?w7?;!!d-mzaiETCT~p^*#z654EM^)&k`Q zrJzOrDY=B(VKH!Hqu1E8!xlHOr_o}*d8(b{AiN#dt?={w!XBeL=Y)5}J-FXyZpn@- z>j|7mE2lLszuq{tT0A~q6fT!SsPl#&BAv%^Zw|s;J8s)gF`9VT=6_hz5N;cRNov<1m&y zq~**X35)R4BjH|~m6di?UDt4}^M6yOHaQHpv8NpOTO9iMj_8tEd$55k7j|R0hmzY+ z(ux|P#4rq3iFrM@w7tz!-XIe%@@{Iz7_hG==th-H-Ed(U$^HovZGuC|Sy zs3zTTJ!d2?2bNR9@bXowy3nKvGO0^yiH700k8How@N#**S6J+3JU?l2Ebef<29eTq zxJ(jkj+Vr>H8(6o;?fo`8kgKK^XbDitH1-W9Pj?LFGS>%rAPi zl>dYsfmm?NeP3mj*C}t`k93a8&TEf9_?G!}$VNZx|I)gHttv0g^Sr=Pob`E%i8>ux zk_prsXX%MB0y}`S8l9><&G3`x?yOaGYnY%n0v|GW{Zw5Zb)J&Is8s!bAYG z=v>7XS+3DM!_G7UZS}?If7=^8Wv`^q=)YB7u0g~*O;Sxc67{C&uZu{?!h|0at~3<~ z5eqZAeb+CD9vL13BlU`hfNQWa?&z*SIbuX&9O4WmV2LeEyljd7^7r_YCHrJmeBr0x zCPlCC7Y@UY3`J5rWz4Qi0GJqRT)X9H#9(7~p3~;946}v;z2EU_uA@kvoV%HT>b?oM z(;5^f;mX_t9h=OF;!*~u{_7(*a(13wVa9+%;w;83I_eRXBbeBJ!_g&o0tPdtRIEPc zE562Y5mkd;h>F8+A!pf0A>u3k^*7dt&2iZaU$bSWB%i^5*^8eSBvI8>Fze1BN>$zH zgdAw;u~9VD!hwnGv9VLiVK`;*!G1&CL+}GHLgy@2#+4YkCHfgnZ%ghoLFN@|*W zp>g{wTl^K@?BTCZ&m|MPW1xgi#BfP=y96n{m{DoA$8U*h@gg!u4Paj999=r&bXn%Z zUSra~KqOer!FT-)6l+T!{qPg$_h>tcyT(&dAD4S??vIrMk^UceRnOopJAn+UqaZ2` z!=zkvSEhwh(-8`kiv4y(7bi3q^u=4pk-)i%8$_i07#*GQHD|^&1C_4iciK_9GXpJe zRTn;T{qj6m6wf+nFIX5d2r1yi2z*m#4c1=$nFR~|tc${>g^-U*mS=KJ$^4^5`6d1^k+y#Q21DwV6jkFmR=_P?9EdiCNmWOS)-@N``J#nw=(ZF1-oYN zSC^*h8{e?-@Xw8*)PMH%r8*VJIJcxFi&c__x&#q!zz+uwFKvZJ*N*S@8j1ru6B1!J z_dOywUIsnJ;fq_-?T79P@c0O9qRX##+rFN?S1#dD4*jrD42FeO*hf|#Oaiz_rHGfG zH-R>reW_XpbQhcTTE9`FlN6hR&|4J!e7p7eZ&11O^9@}0*)^RGsMJa`%lv{e&lNxj z2%`hgPmpB~Wll#u{KBRezurN0+fL?#&4>Ce#U|e+Yy)CV1T~da1h5xP<@RchFat)R zPPhk%?SJXd1E!S)(37N@zvP+yqsU;Z2!SRC1vA=DP^0G+SI;buS;s=M@)iLBlWS@#ZL>!EOMPw0yGL~%8{LCU~58QLuFk;>;fK3 zNT86|64T^sW^(?*^^&{KQPfSYDeSiG6^Ywc)J>UDMIMI`)+>E9glL6_Z&lV|y?s<` zA{6M>#ax;LLAegdiUoi++i|UNkzACmFNW#^oIaevCb_+7U%3+?)szF~&t;Zkez5gY zeyv2K`;-l{QgsU(!>F+*Az|Q^H(kxk)Qtc_MJ*yQl^fjr3hNO$qnwUNk^+etvW$42 zRUhA&8F;3= z;OqI7KDd=HJGJl+Ef1q8yq?ovF6A3^8JSSsq*a?vIWqfoLVv4;r1k&xUe9GZ23I&4 z9l8`_){+AC`8cdzqw)zBgkpbcG#iO5l<#Aga3i#_fK20J=ZHN(*LsLh<$ZM z^}M6SY*r?2y#T%ke>A)l(TiFJ*F4H5)_LqD;k`aLe z5XOU7Vb&@(CHAp+bo?Jjz z|M)8K98VvAEP^cwu~Aa)%%nuIL3)}#*}Gs@^N3gf`i$&Kd1cV5(f}fF9T#_6Ag%^@ zRlw9gl?%ugl!H1CBrTN7y5I6&LLSwZ_r6H1yHArh2#x-e__>uKw#lqypv`m(a%Vb+ zgv4pLgrw!ErLg|i4X1Q6bC5e0<&D5Ew*<@=uZH2w58K`E`!BZg6o^FN=YHl9_(v;@ z5uxE`tyme3j4rV#UQ#BNt}Z6+K#S_wP|)DX6!$-4U8dG<1)12*!P6ZZE-?HtU|%HR z>m(2wdNCPyAT8)vnUK6%)|?Yu$nhypiAph~?-GTg6CZ179>XWTHrhERdkq_(8h$aP z$BtMu5omlMs?_(2$b|M6TYn1O)*C4B6EYFVNAzvGv${;l@Oqt&N~3*=qsA z{r@a?dSY*^dgCl!2jPq9t=CY zJT{w!RD$X4Fgy{E40J2P*IzJDr1L={z*i;)K+t|mivn52$<-X=a!naxY>QTYNxV)` zZ{u1Us5BGV#b~P7&drN9;5BEg&0H1~m{HU12~pr@n{C7QNA`alB8Hx}vx=tHip;@HhkS9}&l&rE-iMi46zqR?`$O zSk%{R$yB55rDi!XWb>t~g!|t$sio!R+&ky^B)pj7^VrqU&g`F?PA4NRB?ryYUZz^# zhG-E6Stn0;CH3T-jUGTsWPL4LIHlg@;4RRYdYw8qBx^G8$&c`j+ctW`*K#uYZ2T?j zfBWd`ec;JPewFT%XGhmdG)(9DhBDYN@I)0{Z61v(T`u-qmj1NgH|Rafy_&(puLEBq z_SuM$maIkhzjSBtBmW`5uW2x+afa39hrLZ|8iiCXTbVw~v2BePi1H=prK%A1#k!6TAt<09zkHvJ!t`T z1j)(!aXL}QWP#AAj7obu9g$*^GYgHcpDkTG9RM&k+4v7?M(ACe-rQvvQMzT|#eYaH z_5fR-8cc-Wao=(+2j-WgVE;qc zTnQUecfG}laGlYHoE?z`gd=z|`l4lUxfQ3)?2Cg0Sx`Nb-H6QbxuDy!n@gi?QhEgI z#!P9}diDrru0DkSs+--e{dVfqPsT#x&y@AJ+ihtd6e>PeSH%gdrI>KdEjyrz8)231 z6G8jOEonY`vmqBsSQpw1c?2iy89bBZ8J?IRFd}2~hs;DEdLT04qgBLrLVc-DzpT=r zJt|oc3b*xFC=Is^A-dIr?8X4T2{q^oO+**d!n6dvW?C{J1qzxXpfWNIBOIjljl;$1 zi(GR8ZTNcwSy-n>#D*DaqTx@6MgICks>zxjaC#3Q-suqnn!548w@J5uY?R<~W2S3Q zPTuzjDKHI(uQ%fE=EIV3+SJO29h`NxJRsz*`y|8>K7`{LMP})~H8ye2aJyv`o9wdX zJz2vgG7JuRD?@0M=&-eGr0YEwre6=iJs!E2hJJ;e=A8tF;S;8#Hgn1Hq&u-p2xN5uVi;aszXJbo@Y zH|3V5mt-OPAyhyPD$Ri>h!!2qOy$5G0ao25 z9NQfzO@@rMeS+vjk!h(A#T$1dyGta;W^M4=kpzt24%8)wuq}l+#`N;NMZ<7&o~=`Cr}I$d|L#K z!}fKct$otydv_ljLS1_n1+|!GTgf37pB40Gq|%qjn?6Chw&`@(gFeiti6N)<0- zJ!D!p{U})PO+ib9u~GFOD3SybRHg9*#J?Yq`O?@U9{uq}-aj<;{(WbZt22hKtj(RV zztIErYN70DukJ@JHvlwYr(PLBS!4U&D@lVR(jP***|cfyQK<_nNIRWx@HChV$TT?+ zAai&A5mc4`%$jiUjjs8oY-|VCPx#gL%GYm}UrZ|Ae(Tv0%Ern5!^sZBp^e-dx2B`P zlIS*rvher&VbMgQfil0ALVhBT9lo@XTyoq=6RQO^JMsAGb1%@BH$#(XTB--xVjGiC zB9?R3;o->-i)}jIO52cME*Li7QBOshAkjveH0Yu}*++qIB(Z(b?1%}+fJJsh zUml=-R0-EY1WtQfA66o5t|qUUe0Wc=4+tKXl&$S z_3RZ_?0=(%E%D%9LlW>O_^7%y=+VnV^t55Xh&-fa-nJXn1gYk4n!~xkEXQ!eP$%6x zDZofNX9zM5zetv%U@z!MGeQ(5Rh-(az&Dv9M7 z{4-|p512L+6<#vBloeCL#3vNyO@DQA6i}Y|y?qx0mtZ31oN9DQYF&nw7vdS%s{r+Y zQVkG~e6JE?vLc1SO{90(wqiE1*dy!$kUKF3z>&v}Kp(R7IC7UZc*L|f{nC^9s5fD; zJ5(&_J{cL<$j}Kw$NHmGLr+f|B%V2)T*_dv@qTI%E!7Hnvlyenn6h8fVAROLVwb)n zrWR?Y^rW$oM3olzl5$c-S9%{xsgJ#+U?*Y|Ql8^d)s-2kL0qo*=UF6JT==h~`XvcY zdym9}eV^}$mtD1n{lcs`KmYoW2^zO1kW>^g?*#-Ts%0RWD+yGph>wXB46u5R3)+|w zao$?F%-^fFS?b;L>i_I&IvO-L^%l49jlUZb`x*7gmg6o>(Kz^VuyD?qu9g|g|JBEF z_YiC8jAUpTNfT@19;V&IrJ;E8Cubg^jO%?@7WH;9iMZCJW9!C=_n_JY1bB5UWoyRF zvkRkZhnE&Uk4P)c=R2+u^Cu;MJ2NzPcA{2MF(2Z_s~uPGF)Qz}d80QX>=(!(!ei0g z5|&BPoVjM*sZfPjnTD%=fjm9ciNz`-Nxr`LXEC0%f=q%$N{l2tvQiTa8)Wdrv~W8m zEl`y)VHM~?wvh!R&m6QI!*rAzgnAfv(6Q*m@9|bm$s}RCTFSe>X8klgIAnBM7h?INi)(zv7~QL5?Z@CZO*yP>vcgGUF^_!*Had=Hb*k&2(9^01Iad@4+3?d)IeK zv<%f8(m7%KkY7cJ#ppj_)`!c9~(Ll>K=M;rE~Sl2lZ)~T&iKz&6_#4z_}hu?qa zDq0tkh4~ThL!Uef??9`aH9mH^P8d{IoRE&3b}w|3qWKLs3|w1V7XhZEMFsd@gq>5Y zXhD~5&)K$Z+qP}nwr%Td+qP}nwr$({?mxLnr~9G%rBcteDp^%Ezd6R4@UsH2muEJk zBF^2fcI)p0IJuU%m}8u0TepNhm+F>JCQ~O7RZTg*N)3D_55YfRm5wRug1vzkSNr49 zL1YmXHE>%q9(8=!_&is<1YbpsHN0bN$Ybq-8fEDjJTChLGcB8SfP?eIWHlA$GF{3U zHt_J+(gVbV^1LIWC^QELx+i}_A7x?Yn-viiD3j3hb3+qN2~1|NF$p~PA>SY)VU~E+ zc4+DW;2bM~;y5M;yfVAW$>F%Yi}dodmxb3om@NgtqJ(1WVp(bxnHP2|R5WEnscqeJ z%ksTPFh0mTqfx04b6Y{ru(<+@%i76MPg#_?d{4pk?b>b5TSGS0uL6bjQzjZhdm@uL z8teSln?Psb1t>up@&f=Ri@*vAn*6=M7!ha#$(r=@m0=_MeUX;n7X(rPbmuN*V&!i=85Oifaxp?=JrVswq>R3y)oO)Qea5V_0>pt;K8s=k0c>@d03m+ zk5gCPuj@(_3-`=}r|$Qk4QnuKFO;X9V<(%=7`Dow>=g3z6ymRNqg*a@qK?$p(ALqE z#$BZt3^v5!cWKxjYy|Q=6AGkWLcLI26aE1hRz+kDRU*Z?GSJG1 z^avXU$`Yx)S&UK<2x&dt=oF0(McVjD69X5L{eZUzIUU@3s|C3r57U$PZRQYD98 zXo*5=(k?y?(w0K}?I1a&Agf6ypj zIKtoGn;?(q(zY3#(jc)Em^wzYYsBcEF9WmFulvfp{mZdq-sAT-8O$<1lM7QxHzv{Q z)b1XR@R~cHrfJ1ot1T{u9At~Xj7lH0t_W_rKrIlUwW(AWOv^tDzpbZjImDVH&(s(+ z6nrD!#(=7$(%A4+!_V2L)b(J)2Apr`ca`+dO`GuV)v0)>ghj7Xzv@_?5 zJvE`&o_kbWOUwBHBQ&yf4JuL6=guc}GVEiO-I70p+FNxihQ7$HO=v$FBUV5_^i$_e z%8-}^xQnK$q4zj459Y!9WX*@DScmNSL<~@-jaT+)NXA@DIPC-|s3?`t5q|uHz*uJ_ zx~|`1^~o~1p*N$dxq05}sCf5Zaty?Ec&e>B-62@1M`Cz~B-o_J6f6CD`dsAJs@_?K zbQ|!qYCS3{c>1Njm7YvfMLwJl$n_Ju2bnzdlmvJdY7F)uJag?}+?t+xyqZKih$h(9 zu63^5{m{~>2&*)24}3l9NNJi&Z-$yu+kda|x^3+)_|K^sH*BGhTundxO@{2}v0Gac z&m5>8^$tI_HTOaUx1wyU9tUD9`b7W;s$ zW+pjVuwKfyeVd?myuDd}T={9I$MyrTWkA;#B&x2+MxY$&2fo%feXMbqS8Jw3dvRY@ zfcLxGbK{`kbC5W^1T;x-q15+LIj3k_B?;mMLDf<%fJ#|#Mv6i(Lq(D9bziLJ+Xuc zQ#N6>DGMP2_)1$uEj=@0gaTFnmqoZM1mElP>iWlMa&eG~a$jVK4>2O(^U(;vhLV4^0I?7a%t?3Jjt&et zPZJzKl>(q30Rh^E1TnK#WT(fLB9OFs;iGNC{RT$^d~KBy=+fO*wFcAo?&KKaKyGK6 zks+HURHienA;^x{YHG-%gxQt}-gWtq7Knbyz_!v?s14F}sUcp>yqyf%0kpXX5-PZK z0cF?n=`~9I1H5`KJKl9PLi~0r?o#6(@}+F`qMBu-x_kd);~j%AseSGpN@-ssAbT(L z^jl(zJiqS>QOR1Zutc@i!o>br?ZqYkqCdXCeie4!Cy>zNAOD>uPnqK$P|DtS4fEaf zP8^kHky>C{dV+rIKQv0`jCh%*=EzlyLZ-T1U3+|isFJ<~GoW*-RajUqKNK8$6j)t3 zuCM!8V~>3(ra4rfv7XziPDvHi?M8)1P!R)yBcD_RKBMy}z`Pk`*eCcjSQKeAAxtcd z3#)JK?5k|%FCXh@1%8lzxymrA}#%dT_Oim zMm>>R58z>;eIq5;uc|Icq0BZ+rh@aZ#AB%fO5H^hrn%d%KU+s;3?HuTUx~O{EBky7 zG~Gi(Q3nwkJ!=1X_`rh<|HAV8>k5CLnF0KE=XKYu+U>SOnT$%h@kwD`9ctiS8-y!L z2)Y_vKUWdcFkYT|zIX0(W!==5%J$C(OV6{WlsG@XA$⁣woc*4yhy4Z`n>5`3XUp z`L13Kt^)dfi)jDuld#g9MDKyBm%8C^3vrvPxwN7!*8_B|GgTo|NGBo%%&}|(`DV?E z)U&il0vlYzgN^VmK{|G3+NZaetZYI1gG}=7aT~Ic4)>m18mH3pkXI?kmiJNG1Z%dS za~_4)J8*)KCV(NsC}vnF+^PzE$yT3o*QHlVIEr_QxvN6rnDHaN|5VhEfd1~{vU2`&QDkjU_V!nBl?E}iy61C|omb*@x13Ht70B;s!7OK{ryYQkr z3bE+FI2PRBBDxz3PIMXQ98YvP7%38F3LH~4yQR&eDonQsl<3UTC>)VqRPh&URVUf3 z@Bc_I?Rnp);~cVZdju*Il=j^up5wtF=K|UYX;_L(tM2FOU5wf#Rb1yRB*o{GI8hl1 z!#t_myZxIfL8AMPo?twnJ_(Cy6UichVJ~}%Yc#D*cc$sK5o>wL32W4eM|{T?qtw)# zg2JGH*s^rTHsTOM^a;1IB1zJQ472d_^;=sOg{v|xSq0`2Bgb`^I^A0pok00rE+J~A z9kqe;&S&j-TJ&#;lJOmxgg^PTBkvWBv0Ucpf+D>P@6ctzI_Vjf}WC9A=1PjH@@G3U67U*EB#5rKiIBy$q>nZhJR zJ;_4k1!eqk3U{%HEgiZV>k59B`~3YiZ>coh@8tF-}&- zxSEu$?vbs`&d#`Lyhcy6M4L1nY~KGqJruX~uev}739u^ik6~XdU8Nkt;04bM3F|3H zn2Wa3`j&!&flKV@8=mv=t`KikotF4)`yr_wt>ph1_PC^s{9(_Pq0kcV{3vG+(mR&C zHd?^J0QQ2v%hf81Br~3PvV19&C-}hSl#ZACl-HiAht?vQZ=@M|98Yf|x?lf*D_*-= zD8^TclAE{3;C8n8Xd4)~Imvd>M6b~pbJalC8Z+|P#_II^0sl%UpT=l{n<3g8C`+>E z{{{qIOSJIe5Bd+@VxPTyopfHZ{EHx47aMK{OYN-~qB469whU)m3F1_e-I?j`FEc66 zF&t8+gZ>)7ilTBa$a|A##iP$vH$|0hM-PRVSK#bN-1DE_z%U#(B^N(o?hl>lAh6f+ z6l<;JiRxJQP_9r_g|t^nkgkiGm%5eP!s?GzVVDmy%y+cAb~P+0yWiG=ygExY(2IGP za(~nYxx&T};gr{>0THm1KvK2%`)p?(kI`y{ARVScoN3yEYFh+at#Cs$vm%GM;@h}< zPY9{>vs$XWTlkiejHUHuHY3?S25fJ%h9H@oT%A3ji(N+(!n~;{^~P;GfMjvJD+u2F zYX%a9bwP8Y+;ee-3A?hi#G>tz7sitf7>8BJ5Tz$rPbF^hd(rp9e-7yc?V`HIUEo!F zu;{S&;+5Eo@Z@aghk$7p;iJECE~nxw-zQO|7$wj4Xqa>K9hL|o^T0yh8sG7TNWQ5^}J%3;itvcbCMg+vLha!c}8Kop$Yp3>i7@zSncwDydf`E)P!lKp^%RS1njm( zo3fk5k6xlg(KA^zCj9QA-J5XNjlJZ_CGgpz+FX!HU>_n1Ik-vZl)1hd zyvH*2#KU#@A;&e-jqIr|zID%a(Pum-iYZ86ZX=I=4|S60(Cj%KiSm|42EOLGOiC6> z^S|B~N9Y4#)HqLYXtotsPEMPX1@YL^`GL#0z{=ULOa%$gcGXR*Ssr|)0bv8i%MnzN z(U4e9L#yzes2kAPaYRP*nRoF_RxYA(GuH^rj<{6yvqDc@;gb?IrhE7dN`xC%@!kM*kDA0J%EBw3h9gu0N0 z-^&v`m(9L%C=zv1i`5d&oJu|s1Fr&_MJu)s5av=4{zO51MH0bzMIKnhc4=3$n)!?X z(*%f0hN>f(kw~7K!jbjYimsAe)4&;VKAD4v&sf=}qz5IdZ1YL4aJQ1zWAfbL=uE4^ zflO*iZ05!(O5q7AZ&a$XSm4_8HQWi#9QW!iYgdUt7iLqlrt;APAy#;I@;HiKOCv}& zbc(HgbY}G``-3W~%0pg_7`@O9+=`Qg25i&r2QtpvZo~RWq6CvO(+-P2?fL+irfiL5Iv` z)50v;hYRBU1fYGEjgvt{(!iV*U6rj(j!TWZQdj=|e|}xlFCqP+jomyfQ`(p^R7-{1=Zl8Z@Rv_%zh9P5 zC2ladjb^4Xs?KWKiCo%s99as)tQ|!?yDjO*KQM)C=Mie$UqDR?^%7waPbJ?|(jGj* zFWWu9&Q;p3tG{`wDk8}4w4U7`RWpwtRq`u^X9NsUET|Q9H`9V|5J({&*<0|~lMb8j zMP|Gcso`+tn%mGc6zJn0g4g%kW{KtRi+QtTvRgq9k}KZZZz{70o5UQ`Hqjq3-hNl% zpwWz!IBX(yqFJz2yCLz|`H7^fMhxx(C$f+!K?66wLe0Up8%TqIiCO`)bP4)Mo0#(+ zpxx0o^-;wdqTbH0!zA4YXQ3^0IOPwJ{qPL-^&=i@V2={kZ~*$YFnR7y8nr^~Txg|-aWH;6U`_Xzc< zL7w$QizCVG0I{>SQ;B}T`v0^gcZN$emb;mg$98!#mCbKPcJ=!a z_kmO#W5*{O$2{pc2{YRh;iTP)*HSXfb-;7mRW;S|GD$-^xH8~rH zlWvj;j03_~C4w^3teep<716GT=WaIb2O2T+=qU%Rc`4+`J%`8|OC~1!Ohjz}jitdO zXYhDW(x_#7ky#A-#5fZSE;^2uGfFiiWiuf&6dSHRjDf}Jzj5qq!81>DDUJzZJz9XXO-#=t1rAVlx=e`o4z0u4E6dOavRN5d1) ziG5%1uD8g+T&!^{Ju>$echfE|A=6DqSjk$|RI7?R&g!Rty1}Qn(sJ%1B}aV8Ep5sF4TDIqRc+#KyOXM za$S9HQma%(0GF5FO-GgF>6RimkFzqL<}P_IG?sJlD=UAbshrGgxuBH0OrsXj%H))k z?A-K9izE@@E@T^GqldN-lD$fPsZP2Z_QPTE#!>vlzE+BT%rVlM@seEKClFkfe67#; zIrKa~5L}@movrw!&8egUKk9byyxM{#>t7l_l-hRbYjgI= z?e(*2*Lmyu{vXuyHp`;GW`I9`Xo3F^|Fo#!f00a`6Vw$E`Qb)94Qx=Lz_!!MO)Xv9c#>hsE4*$2U-K5P$Mnh@<3oRHN=iJ7J4insR$g%b z&;i~w#0E-$(DXY%sDxGA4FUX7wYOYgUw#?1Kp`G{*hcKBd=6 z0XWilQGF}80VPYY0N%QT^fXIV5vvXoPRHR$pIk+eA!=rd$xxfchvSYC82$qTuVVy? z<0?(cL6=e$81H24Cu;oS_O;7Jy&bDFh_}-f1OcL_;(Jn0jWag}CDLwsh9+^T^}6%L1F*#6-%@F3@x1}}Gux1v-vb-T$4eei|Je0>L8i7T8%_x@8 zL;|dnz>(A!(r9LtV{f(jl#$6_gAqxqjRPJo4{kq-9ZBwVW0FKrn}TF*!($G>e*rlMMg2{ zAF+zo^=@fY@3Bpr;=MR0;o4%|82ethS$1FWSQ+f=2>G5KG#USzA3mCq-Pz9Bl8Y-8 zS#R#&f>>6)$V&To@_2$V%6Rv9;do*W(w!&dWJzP3J-}lQYn$tZaR1Od} z0yI9z3xED0{HX~$;k)u<=GJOAKpTqf`A%r}p@3=35cna^b>4FJ;+GL-QauHGiJE`u zu6x=0-fp>O7CeiY%U*@-1#f8CWIRh=#WTJzv`D%Z)BTzIl-@g?#{tKdSuLe2lIfA# zFi+6c%lIKEy#jZbwFZ^Ibq1Zlcl&(#z~T&wL}v!PpEmp<0QJ9#VGwaz@f}sUp8p|C zu)s5Y4*lKu!a#riQ2wv*JS%rQv;PKLbXJg-MU+S4j+u4}Vem?#ZK_fM&XVba@0%gu zCr%e9ivNSK2ECBtV#k2J!Ot`d%`2!?J1>ozE{30uw)LJ7>7lDxN4`4LWxCDzlI7`b9fxhB8pbpS(te!RkHQ%L>&FDlM-2C9w2a|l|9Fu?{ z=<6Tqdj6E$bK1SD6O5m4T56kVbL_sMjEh|jk>o2X|82E^q9^9ts?XaZJ@X8CT<`>a z9n?075vb8fIW+voLQMl=qFPrEZIO}saoGIV8d1#vl{gNZz?qGy)T+J)F;%o=483Bf zErUFKu48g)PF$>O zKB_Mgv;l>EU|#>;6ynGxi_%D|&KfC|?B1r8FB7lx!nMT{e-YYwxslD@V^$SZJu+)- z8;>7-b(fSYZyL_a3f7uqnmQYU;k4Lr48R^jwDj|Wk?=VFeugd58x$UtdRvzrdajPo z6bTwITns{n zemDptmal%zF_r;afSHG%!%so@AcZ3tzlBZ)cOndj0c{#@%cyehP{5VMvNMoMxnbv* z5{y-5(!;NfMrw}V^ACw%{q&MRhK>T`S|Dy)FhU8Ib>0m9iIkynE>n)jlTAFpX^^N( zq@bvin#1Y`I}ArMfo+9I#+Uyj!FzCDo7pVCH00V%7I1=^jlzz0596JO>G=onzw0Z+ z?Uhkxf4vn58h`!}{crnO+*;rC|GO)uJl*w_RNG!%uQxeelQY)hJxN*9@WJK45`fIi zNv$==@c&|rn-a?z~OEEql6O&zI!<^eHN;dyTuS%WYl!055IQmeV1+lsuMO^<0%}HB7186|Tir9{T$}K^L|HyJw!TyW z=m6V19w=$)@Y{k^+K>)>k+Y_)dU7b=;@?GHv?4v{C`}`%jrosM%p<+HDmRVPH>Z8=1cPXUo*)OImn_Fxbn&(;MnI!)V-{aOK zt83`@)v96|=edvPnT0ms;PLeeXq$gGi^DSowta1*^ezn5@`mIV$08bzjeoZbXj`=8 z)~DRtuQrDWLloRA_FV>kgjc8u7V#C#UlcqN{*}x>2L6>z-+gif$22E*oz(f69G7!y z8g87_Ojh>%vJ&=Tm0xJlO4i9>GXUqFX5Vp7dVR zGWFjtgkMg4%dTU0|kDyHpI8q z@t5=-hU(`04C8z_sbhf~v^^9pk;!&*(d}Zn`FXi`_)1f=+16@jZFMrYw5-S0j%oa;JAMZ#hqj!#Qk5n#Re7_TCa% z=JJfQx8;p1u(`)0gOP?u|Jyd}3pOyz7ZC>q?^_pSDDClE%A;1ZaytiJVXs`LF4R9p z$k5ZG^aO0%t-<5j`bXaMC)6Z@zZTJ*EzyRksV<6=q$=I$&lREt$(@~I@edqDv3Kap zLy2bC9?zYaR9|pig-3SPv5dzI#G=2jflEaJz?b$}U!K&@7C;t_CfUiEDvrz(jLgPJ zw72X2P)|0CrV}1_<6q%RzTzJ{UDUv#CCX!x{IZ^w9HxjAlDSuY1@H%{(J|EWyvxF5 zp6Vj|dVlY=ENu|gXh5jTPxmN6X@R~u{QM~6+Rk=k|FB0uLC}qlc~|f!WsiHigB!$Mb5CckMGdAd=%+oOVFhsRNCAd{V!0*BPCM&A!N?u5+KNqHo!xM zOVKKV>P3|-iIho6Ar49P!*bOm^)>i|;zc;@Ra%m}9Q(8i%elK-x?XRXHe{@`ILj0l zP)8!=RiU`MYvfTO_5SJ7`D;=u`|*Ey{c|wJMT`^vqL#fezP9CAsn{^2@_Sm#H=|7& zkYKP2wxv-sBvy^HXs_9jaVBPk+%p`Z1e#Ay;jv7d*>bI?;h>$L-7x8EK*5^zwT9SdLnv-ow#N=(3VKF7@bmooYW3-6zpMRW%ft>2;h~o)P@s~rBhdLjH zL?A1&Pg+8(#w-3^FJ&on5^cs}IQ67dmDgfdUnTk=oqCc`1d;{{ArN}XW+w-dQ zQqKP+DxO3yM@@yV(1ppUdSJr5Jl^y1rwl+JH9Ca!BBJqk4eNoc4CGqiOv66oPRDA} zzoZ^ZozvtEnu@(Mb+GI-?YjD)yp6@xy8{B7n4JhLxQ^)^Njg%1!;i}w>}?ZF@qMUd zYPMH#F5Fyt-f;QM97C7ZP@E*L9%oUvz&t%dyioGeJd_mrOYKC=U5B_!;jv1t%pA~% z{dLNRJ`N{qy5LoF2Z!3f`~uA3gy++w z&abDx|7uat|7%!dkVGXNJZ`Si7J0ZDjeVZ>F;d!rn`;mD%4DDq3hiI>?*|-?%I(+^ zm=4DmVgF)6^&~{#&cDJHGgS~A1g!Fa)a_t;k<)s6wo>4)*{&~YO9Ocu%Jo-@5kvqk z&S$(ZqfL(zT8HGh!MCMmS@EN}et&0aw6fW0?P@Es=j4!BrG2FUHX7@MsX9pRu*X16 z9e4M3@L|=n7Zm<*`8mo z2Q*)7V7C85efZ{rZH1bKQTLSX36}&4-*|a+$zslzvWVp_2;h#`#u)u?T<~eG05B_` z^Kx>ApigncF~5&`p9L7E@#SG-$)^?8kPGAp>+c+^%y4ZM27I!=1xu)-Jl|eHPbo#iACxH4)o|LS@ufI z9W+JZbKZ9+azGpM;dRSo0o}a=HYDUK%y*OnKIFf_PqIhR`80g^zUh{@lt_&W0oh7S z+a*6c5W3k+QR`zYkbH|;#-){wg_P|y0eku2%IbW`9y%y$PgXnp{6u2Wy}a}=7s-l~ z$>G76LJ!Y&vZ~pYCiReZPDR13D8YUyt8Oe$6Cx#z=j$|Lj&yHgc%^?yHrk{7NY(YZ zjNwMm+B%=Ur?!p<8YmcF9i9u`pr(6wqw8G}&T zEs9x^NJy7fmT0!g-R4yGbf=>Y1~-zu>t@LnEqTg{Tj@UTc7Z-lgW8JLRv3lIf)NR% z0eUu!Kv=;@%FV=9CWmdyN;V5u!hvi&$rk1_+BtVZ#5Wut$0;GRagv~o9*>VRHe8!G zyQ}O?Kdz{!;&nwmKwwKeAg$EW0!eSyF z`WwdDW%ACV@im$&UF_x~HBpV$j*~vUHi}g_TYPNrPf*=0XYl3`V@tB9TBcJn5dioy z(-z1#Ww(L$qh^@-bRO8@*LZ`7-N3>f__IN>u{J5G9_GZ$BF*13D zgjXrUN8dHdwzt1!=8WW8^o(0fVb7ujls@LBkf}Q*njl4n*e~*kj6-xKv9nC_Ksq<= zQJi7fXYwW9E}=)?umkQ+RTOPp%$@%&BJRW59=_m>9TI`PV3Svt>)5aDI=JH~FyX$s z(sV8$t7H0BX)xQ0;V;{9QW1WfcB)sQOn-0c_l&;okM`wtyqTYHsrW8LS8>{y_gobx zH%FlrQ{fe!i5y8WrqJGwo}Ql&#~lS=HnXs7MaJAumB^m<{&wvwDNjm6K=U=8`|!vm zBwOOFXDM}YGSkHtC!L#khB6gMnpoQzKuPap#_UP7!e#@4=m!7kujyO)Tl)#(m5{O% zf7Id*p7?rv)m(+#DTUFr^@CV&na?Dx-4f=@E2eYh_x6$twuqZfY&*tar)889*v z6{{{tAN%cP^iU4K4bdxxQaFu%g=wE`Z>nGZAP|rPYKTU7Yp@CLZG^ipofp#!;3pv1^lA7oY8fSmSM0kw8J zYN|=oy!iBEMp@6{zQT0MJwH>DzW!{pE(yU!ZZY+FX=UgN4TY9PaWd#oJtQY*C?(Z- zLk}4?XWRbAR2xKdTFg+g{C^%h;jqm7x=sk=7v9OV8>Vk?>XY~C7I<{v_7kk&|G<<6 zMYWn!<>K8xTq?l_7V8Sl zf{iTcZG(+1!EJ?&EX&TY{Vk`;;nfUjhO=!NBrdM#Dm)VYO%VfM5g*S7JS#ZXwY6?& z8PJ?v)1{nA{Kp06rdyVS7p-4+48-IvfSA(yIQ0T=T`orn5811*~6DXb7J z`oQ&*RX8_W6!=wP1@culjjL!=CfH~SmL}xOiT3q6Fpm}FECx9sv>TTdT(`|j)<+U} zD0hwA9~Po4O{EL4KdbQw9QN}HeAbZAca!Mn`KVLVBM^_7vhTn5s4)_QeF154g0IL+ zw{cl#hP1zeFH!}Jx1T9RldrUC;g_{k*RWC%a=Ihfl)|bF7IIL=X0$xJOr!=mkHI0c zE{7Iv>+_}y-&Cec-(aOgoU|@-jhL!(j}%?A(&1HdiSeA$7ux>KJS%- zUb9keZjDJGbZ}i298=;>&2gI`OE9QnVGB6M3MEXJ?kZR|hnE#Ekrb!RHPeUPnHo&)|Yda+m<7HDW(@S>sq3%ew<|v>(j}al!==7C08-#LrrLFYC=BC zk3pcmHyO=Z#LGSS9nF3cV0@1`$&NJL_-*$153#-1zVCsp^jD}fTcvIK;9d-%`k9+0 zgv0|(e_yyn7u{u$wv!0FUI{3uWsp4oyq)(sBwT=K*;CUjy;E@^Xrh(~%l2c&{ekkT zW0kRNVm3R~sAm6*RNf}2O^P30u|2&qbI){r+Zgm9OZa_sC*8%LtH#gxAgZTk* z(ZAdfh-hy#fEKxTpgq&yM`c`NN47CU-eC=5JfuDjkzBRE)tHO|>nz=WEwFy*uwt^J z9{%NNhi{rZ{$ZY-9FsXi>B}|o%|TbZt6`TAOXs05r5q`GLCeC&M!MuhN7NHa&R zN-c^jfh2}*KfS$$(fpwrzS@}H#jXrdu8nr0syrd?g=q!mAlg&)X9?c2@v;35Gm^;z zm-jz|x{-nxsArFGgQ(>vH3WLZ&hC_p@&C(1wF1{NEnpF}q&HO*84#oOVX98GcZ5wU zOp(Xc0}q)8(FH*m9t5S58Psp#^vpUMw~`a+-H{U@!(|`UK1vGZ9?h}jIg(cqQOGR) zP(GWa7-xBUkR^57Tg1fz4hnQb^zAqs_3DA)q)Do?Wqx70r}svds8Ezrddv_0_Rny3 zGACd4!k)ORnAHWMI==)B(q#ijm0g6!3k5gW`_#h2Ez2`Q=aHqSZ>*l_o;f^O@YAdO zHU)9}OU_6(udw~~Y41|yjCXOA1(|}-)l@^&C1{*eZ47v|GTzhi@3~V5PHE1kKkDu| zYVIKyy2g8^99HH9eW%NjT07#=ft(gQxVLyxz2x{h5X-x}hgiY+A6eKoN^+WcN^+|E z)N`;?8ZJKOGs#pQMaC^hl92NAFPZ+5(IlwWcz#ueLy3^AOj*7ocN|xSNk^W)QKtTO zer1F!gsn&m2#o@)F<9E-I66p*P0+4LJp^b{AKD_h zo$wt=b3YBXUY5HW#@^z;8hd^Wh(8>F<7R&7y>RDlm^C2uGI2|A!<6+H!*EA#m|OS_ zKm+g?S4N=CYCdh4SY;2GN|CIigB?~VM*x(YZv$Q@#EdT25+Xy#wi6q+ecKSveFG(> zf@I3Xc}mDZ4(nP2@$uaGW=sUTN|z@~{ii|x_{1J7w8+fkzH$BT*0`heoE{4N|kaFGv&FQIP+ zh;~))jXnGojm#nXIv`7}mP>4l?9cypmNKcj0Vl~@{khJx3h@Kl$(54(&iEn91%HSg zPUoag>SiG6-T>4h9*1c8iKE{bFWvn+U1?F3GR5?JEv{1}joN1SxfUH{b!Vcg0Om(< z@LMI|fL8{yKg2cGbT&cK3mS5(k_?)s<2aN4tvXj1YD%AL%pb_alc)$3z1%ju+=+-W zeRl4_LmNU>+mmi-D5xeBKHr1B--F(()3B4{3>aFI)&HYfqVW*PBna<77b<#_NAh4) zk|FmeVASIly|PdTW7;2D9(omPLlzNIDIczyxrfCKTWW zz%&}TK&^beyb1VC-Wu4UD`V$Dj8>l%^+m&%MZvO~ZEpsOv)zYt^&n3vC4#c_SPt$8@7CMlxEz4xIG#1p z=4JRBZUW?$Y>hFjR$`Iu*S)+ERWuADK!A5}hDrS79AY6V%j{+gE9+UTCB# zjm0S<7E4^Wa;=N`0~QQxS&SmGDofxWcfcnK2!RI3X>h2!@|av*8SShPcPVYE-7t2M zA%z%TMkv|rZjQmRx(H*i`jTPZH`le=`9zl&Pzkh2w%M#goG|3+^RW32(N(EQ4Mjbf zVcH8%+%Jh%#d~ZFEi9gsvzcP5EI!6-ttT623<(GrRvT?b4$1aaSWQH>b<7#-m|&Xx zV!`B&F&T+dj#@=l#2KkW=0en zA%<3L(fKca$U`#ulznHC_*+8S<=Z$kBbZB48|@}i;*zenDZgFa9)C;0>{NG#wS$MM zXW0~7>VRJE@dwQ$E<_iCnwn?dHKLoE&B8dm#S#^wrs47@7K>rij+uAiHmSIIwudoW z^(9pT`%!Z(!ZOqvQwB(`e;EUrTaKwW?32`A_Ef_i zo7Tx<+~!BtTaCx-Ng8Z=$Jveje;2s$>&`BD0NcDJR0V9-rmf$M9j)3dpl~QQFDgv8 zE?nAbF8hpQNIk(MuKzfE&eZWrjw3q8I_L_3B_52%Lkssc$c0EZVTSHdb7P}B##DvE#z8pL$6VmbdZ+>w(mv%(=m@=)v0pu<_Eb^hl zWcU*SNZ09(+To7ZA#hl}uc8pP{5cyT$L3me)+&9leo1mDOfE-&<})V+wgvyujbeWE z_EEbRbJ0$hnVBP0zgz-=6d~sWo`M9UCNQtKEGg-(&faX)tqXQu9LnZNZc_08Y4n}5 z*-Dq|uEc9afuO-b>}1tB2s0cMi2vLVSv+J8e>}PNo1FB%{Osb0B@gUN)}N^-x2coe zZfm5!EBiO{KC=*AK%C;R!W4nqU55O}W~j=fyWbsO`V5q^+LdW%;gRWA&>gE#Ag-Bk>oVF zw2RDtZBy$gC(?>|VtB;SpMN-fQC!D+;!}fIt80FTD#2Sh1>x7@LKS}g+Kzz72%H&j zjXgF{9_F)^c#wABP4-)J@t$<1S#!PYJYc}ZSiTT3tn zlPM)>Y9(_}``=oI;A8i@rb}46J%_`g8=$uozThd?kZKMR?cr>^PcKZa_>=JzSkvd) zX_|%zOJ<>pJj*W?x?*6-BvhF1pGse&$gFU$4>B{IZwa}G~lxZ_I;5TKW+P^ zi$3j8_E2f9;sCtT3@v6Lr}n2M9HUg>PMkA9^KU0MdDe-1cC1=`6!xwQk!8*DF>KwqkRbUQd3-ba@ zM;HtyeUQWz!4}JD<()-pDf13ZVOrcVnFwzE=pChU9Mr)CPARNLANpEtEp~H2A~a-9 zaNuu^xlrg5Y{~N~EEhyns{mnVj+zZP+)Ye0B^MR89a4~zs^bm}%wL^B!d;n`wQ{Ec z5TolfZ1=ug-3oNN9|i(~&7!a%@Pye8JVK1X1Uw4|#f_T0k&kc&5Lz(^yEsxn3Oy;6 zL4w|R(O{hJSm)YPP6(lv@St|#Si&7^dx)~y-c6|ejWTGYDfQ*w4Px|zEMnO7!5S>m zC7%rPC-^vYy=}W#hp{6NitIf4+V&-74+|O1rO-e*Bl@#g!<8{@zvpzIDa66YE>;2k zXHQsp`rPS*kqJG5(}WmZ$p#HSa6w82X|7o@H)@y?ghNh7Ns1at&j;lMP2ReN z`w>v1p+Du?x%u7rzweal%m(%KfTeyb#Rx#_r?AGQMXMr`l28W1Iaa%Z&yY;~^Z?uD?g`Lm^YT7oA2Cv$dJc44a+DYzAT};}$@3@qKLDh5nc0U_36{v2k#R%rSJv(A;i%b@o;sPF`XHEc7 z#%A_X`jyFyiZ+nb|1=Wkw8mjZYh5&UK)ceGeC-#$x}BXOvYnEGaJ2LlS+Zh99$3(1 zyt%6P(`y29yS8(7FpfTSLDwKn$?Uqc`6V=3u<0V=eBsNbUb&GCE#Uxj^zkf15MtEf z9QTs}ON$Nrb`Ilq!%KF@!({P4Fmi$iOkJtIC?2Q+=?sOs$!8K0L<-P^Hu9hZiN7q9 z<5;RkJm@cE{-lA)EU*PiH3hm#4O`dn%~UMBFxIm8RrZA7U+9wA@vRx`&pd&sozRwSV$WMA>FFw2=eg( zl5J8Y25nnCkem&@*?ieWzNZ3zf|ab2lbbzi3wB4aVUdXeutDWOhQTj!)PkxZR!iOG zL4Y}a#mQdo3kkTx)J2;elOITGjWFzC12&hxxi~IOYW1)(cylS`yRe4CF+__^zpXG{ zzWvTdX8KVlNR7OM8J_6}=5_&+(`Fsj7%u5dyg|yN1`Ia1tv8ITF%5M|$^6U+^i*t9 zA?>Ql&knbrzhaCtoOTf-FXu6{IRwWeC?Wt#N5f%%3qJIhH$4AVN5KED*9_p%bUiL| z^?^Qe*{fOzO@I&5b_i02!wxCk9-JP3IJMXs-{9{@d9&GL=DD~!WSV>s7azj#ne0Pf45B{$Ha((I>@6u$Jftid(G3vYk-oN( ziV`It02Clsl9dH}`$eGKg-E|b1~jxkw8N&v?>-5LFqECCL52Y%1buu$@RI_LI7BoO z1n5!}k@=xu0cc2S%ECfQdb0+?(-pyy^Iq_?GJh!I$N0m>EQ*XBzOJfw?@rw>D5i8w z(2pH9?{vh_!iNv;4#!BC2heQLs9W-}rz@GyVQG+wy}%)gPE-XOJdn!XPvjsGCt z!EHq6I)Hm@o!!#T&QUb}MakphzsDe*)ZX5E2Onld#RZn->-vspzOcR*9FjX183ead zm4kWJI&3Y~9)N$3QD}32Jn^dDx$&>vQ5w~D0DV33eo69 zIW`PAl3=v=N41}x1HX>OW-X2M+wRDYrRjG}UMg!(xX-7yW_!5DYyvFKeM5v2qTzab zYriOa3zMPcq04hW&w(zQ+Tk4n193IRu+0q$9z~Gp-BK_q@&(Lp+f|Ft9=P~A!bw*M zF#Ez$M14V@m)RRLV);Z)b~>aVT{YdfneQ(h{+4geJ*k-+Uu8MG*~lE`^sZ%oUZe#f zC>lra^cRjaM5N(DTsz!JZKOHcQ8$JY$;%a^Ie+EA-z)pxQV9SNHQfoL;2x1PbgshI zIq@bP{ocgfwiffz6_=Q5nzKum+AP7dCA%m(X-wMDQclvJk6dG6bmwo(tcf4TIsU%T zPVfVZU5d3jyC>cF1{R}|ev}n*U7hzy9+9cXiTH=kEas6UcpB@y&sx@afai@2n%o!X zZ3&VKh@{w?O7QD{1M@z9Xf}6wox58q=%x1mhWtN00ztZtx*%A9fP5l>fGGc4kHG)! z-97SCB^pm6tuoV%MzT#u6aZ434OK#*5l^Eq z8Yvp6^e;Ch)I*ONXCHNGVl zoQXzo2$}0Vy=gQK@M-Ja_bf9u2f`#OKNXL|zL8i&gA zj&epi3NkWTeX>=lF(}cMW03o-A}Ntxx|6L$LdDfHU-K^@Sr6<|g)l^fs&NqSpJwk?hfO)Cb>r^h=T1n9!8_$A09xU_=qm~0}{ZJ?46cqD6LKD zk&(zNom*O*nmDJ@7@J5z^$G#_O#Vntq>}y$PPCK$3P@ynS-|J;R;}%uqaOo3Dsh^*nI|$6i&4oH2pD+g}FGJ|8^~-qa zSv{ICBa_$HsQX}8ga!RsK8_$So!7C@NOJkFc(WP!L&o2QB8u2J`8dKH?96QZJmr3u z@3V+D7pF=bKF8XB3l)c`MIh&UbK{;T`-kH)86y{l;LM}s3U~spM$T5(FVn+JgV?xv zI6m&~cfQp}S6EnFti0~!Wp!?A^&sbZWOeqx^TYg|H#eDBmpSIE1U=n$Z)$Ttp|Ey# z-5~2Ca}LfguW|XEfB9jmpK-fB3{RgeWNeC;5#Wt6#i8Qxc6S%~S!h5C*{oaRk%S|U z%L^;M77P@``apU?VW<(w^oKsX!Qo+@ukW%j%7Sy2tVRXa>fwWe@_mLOJp8g8`yCgl z04D2T1)iDSDY)@$qmT;^wA5$QA`FCOmTpsDf53q zWn}@@)|2_nC)cR0kQW0fkPJk^Tc7F<=QvOV9mtMgl-HCARzsZ*+5Q!vhD;IIm9(Fj z26-wyb+j;%!457R_5Sv@%6`_i8!FOvv5plx8QEg|xV<73&NcG+9MkL5Y;>n7w!3hM zIE$B_K|t7(wm?C~7)OwcGX@Y;L9m`g22UT(z~S?>2Pcsw=)pv01uqNN zmQA1GAcOcC68(@c@vNs&h@NwnmAlHu%fsO*5R_qJ=&rgKx5xxb_;a+7ar?KZNF^*b zR-m|ln}cC{Qq$)-ZpZpOKbmx`G*<3fOl_zD1`WLqeZ|Mb=p$TJeb&fZtXLgSqBJ(a zN>`(Md+|0X>>5{qIf%2?tD>nL!s*viYmuu%7SScI8`CxS4yvk}l~~&=Ruu`ij0mT^ zZ17Jsg74a-vN8TqPE1RfG0yY&(6Yty>WH$|1OZkPdcKh=Hgbns;sl3S#2qKZ z#X%{pX@gKqgWZT*R2DA@^Vq%fGR$WeSJyi?;cu0gX40|PULS8y@rRv%2QAIEa`^dF z>p*eEvui!gMqHV%_}r-e78*)9%*~XX|9zkU2#X4ZlHP zuXTNr>-0O-Tl^vFK>~&l<>h1G<>Cl4c#%;>rK4ap$~$-}u9mlW^}D%2a$k|aj6xY7(-;uIT-ZV#;nxQ#-l z~-f(qOX3JeM?EFrQT6Vy4@G4HF9r@VLH0KeEWc*iG>QF#O=A}ZY(x-Y^K z2JGGrsw96>nkp_DyC**{plaUaHAcN0xRERZUY}KTzM_h%m1zMf{zRLG3-@ERRZM zY$TPnC>$v0UsYShsS;mHl0yW)tLu_QH^_7TZcyr-T`!AG$hFZLpw})+j=lZ+*LcYY zx<6whebT5i0z$ys$n3+{QNOfIf{@?w@@1$vIc*cnhf2`sGmm$Hk85TZp1ZfcYjZX0PxJ&u?!)NgH1aCUx`rsA?kU#TWmF3&#rB9LfWA zU+5lccj5@TgmEI@wF#4-$BsXtJ-*|+{n7_P+$C~Gt`($SBmf`H(|HN6aT6a)cVE%n zc`46McfI4+T&(Y}pQv~OVP;%?elSz$;qf97JDv#Mqlw?NCwWar48OkoH1GPNUU!?0 z^2R>IWWKEES*Z}ar;RKT|_ z<$(F}O8OT=uuUlK?>!$M%6QC&O*Sz#w5*xZ13!j3uhS z2p90;zIe&Fz&H>|MjTJ*OF-H{L)!!TyT|XpBWVL#mFkGWll+J{`t;pf?jASNLsL;2 z-1^w=rUxa5DIsZybvE6r`y)5nacdCXEtp(~eZ$L;(rBvzl>-KrH+=BD3}SBsFeI;j zGbEq(#3=Cr#A1_!JR^|!WQk{@)L#jd>IkF(Ho@J}Bfjq%LQ9Iv`JZ?;h}^RSsrjx5 zcR`&OcrQVnz6gy$p0iw%i09y^f=-4j{aW*PxyWg7F6fqSK~)u|Z;oUwhIf^`GF%LvuDNW<9XrDn68GR1AW7SAH`> z9?yyx_J;;lMsrPqMscF1i{MA^UG^KYwC^dBED$DZBL%^_f_>Wky>H;i0iETzky~8f z4Z3EMfc{hGe+UC}P9mcNlgn`GPeC^3Mky}@J`xi54f&;|{uP)#)EA`NQ`dcYUe={u zcz0>KUaol0tQ52eKjooLr{$lWcCtHCJg$J!jO>a)0A^m=c>-28P8&)7?*$*~=zFT2fZFG#N9;a## zTU8)%m;3}O6n+^abd>dk7cqmgjBWEKFx37z4YMY3UK?pxzRSY0^#q~($vU3~ruUSl zd(YggDdqaGYbZtZ^}!|c%2&Rc;=1sKybO2}xHmPRF@t2@9og3ENMK*A=>+lBAM{18 z8|LPm{EsCnur0}!coFINyWCvHF?QFPxLzLoeYePo9`iNY>kYWnNQR`CLh*rgG0Ko* zy5?=914)Cn`Xu?Z?;;G_CGb11mA%=Ty#6m&J{I-41l2k0`PevYc?u_Pax({#L)^@= z)1-z_GU9a?c(t#Obgfr7&Slsq*f(}>R4eb~;~)+Ry}%v7S;c0i4?H{7g^3 zVfU!{&D_kbxjE+-^Np>~)#zxRReNA4E@Upnui|5DeH^H~k~UvyIAG81NrfBFs^FQ( zE)SuOje<}WwnZ(^8(!7}tVcz!j3zVpfg;*sD%u#ZSI&BHXW1USpz|`Z{*FCWj75iZ zzel4FHZ1u|uXhq>Y3lAxR+1@JZ)AnvmH*CMpjp~JwKx+3!(AQeSPT8UMQDo%#Zwc` zAk*Jz*IX*n^`qm6hcXny>Ir@~vf{}6l~0v7o&X?aSm)kVn9w)E2$4O273x+iG(E5^ zDCFBfizS>LVjdo{gfLP#q&KoM$?NHcv+Mx^mJM0?&IWv)?z|H_E~6HlK0dQ!_*2MM zycq60ke2MFS-_^_nB}hBj4zK zu-O9J%Bl_IOBJYt`zN!Vl$ASShG1%!aP83SlZpnBb)h4w!VRF%_r1Yv(N&S3&T&HvF*z4mFK2o6XSE^|Bxh12?TMnAs1hkoA1@;>DCno0NT=!T zMpDp&)`q_h;q=?vb+OyX*(~UmgN1$BLWKWG*2F{FixLXNE3?%Gh(4^~8e)Ayt&I|r z0ge=A?le?Jr%YeI3!@?BOue1%w$hnf*+eQ7N8bJ7n=G z_DrjT-Nxh&9&2My(kA&W7k*M?zr-d#PN87H!wUcVp^o|Hpxjb-f1LO(gW$$#5){UWUiZG8+VGkt8` zv-}NVrhO+1BnFO@%&{ab2NzEg&NaXrUmy9tDQXz>FekEaiB($fuR&5~SZAxG*3_q} zHo{f2H*?)Do!m|(b+rZW5J{&N8Vw;8 z*3ORGH`{DcnR++qS#ySg9L;N&hyD>sJXCXV!#dVocIkUtzD)bx?l>WcOxIi@n7eAW z+I`pz|ggL39++7S@EC8%P}wecA+&O~e#G|~VMVNnrKNIK@>@5&7$ z9UjLrk0U)}yu&x1&4G(1BB+CdkL^q-JVIfLook5s-lj@-vbwEvS2Ra2UKOxcYr|4bVjYaPg*qFi-9Ci zdp$NM3>GloXcxiW4GCV}_X7Aleb7Q0^xS2aO5Dn}l{Gdki@*bIH}!lG#g4>Qa>a;Bw6 zy)u5JtY?y8fnTI5Ps$QF>T1S{;zK^rOP+dNpShgPN9f@p^@*HgPCr#FNxNt40N*rZS1twT-JM(O)tWnKP$7ba%88{XTLnc~AlCy@c*-#}_kOial*CwlO zNMSiZrBOFS2l{NPBZB*g_G~JBV$iuUL_$k0;xcnD* zix|cy{XTh12*;dcnoQE$d+NAUNpvl7rn?Xrl&%BJ*!NflM?(}f}^f8?H#(#EkhdD(AAnySm_^htV5 zYM#RrHCNqayedjp(x76YlpJC;SMlsmD{3C6j>%PH>Mrk&$ypO_jSbJhNqr&evI3_H zdgg7_4ekmXAJdepmh#Ix13A;WyH$0yjn-?S5NwDE$vfwPXc-f7t{0-mndwH90tcI7 z=%gT&U+3kxW-MDaC8d>9}(E^CG_paA0ccOEp4$moTa*nQb zRUgCE!0uR$)7;7GR0vq3!jegJoNcf3^dFzX)1uQGfV#IcsJ)3%;y8FiM@lR+C<~k8 zv=U-9s6bGpyh!Ay5s@8m(l40Fmk^|J>K+m0pSJCxOgMs4T-}!m!LAQsVxVQ83N(Q! z^5*MuDz_w!pKXkO0LOnEw@gL0KR5hM zD}*>xLyN73DuFo+jJ?Fo1=f3{oS|VMpnrU#V&%MA*0x;VGG*>bu8!F_erCO^{{Ech3MhyRo{7ZuUMeo2bEG*@%RrMUFYkW z*cYU9qCmM^0!tQ@I7Pt9H;fash{;=&I_m?WGzAiqs(P3ed4Cl0ok#%jwn&*qJD%oj zw<+773OtS?(r0fk?3b@>k*-QMknb4VO}Gk^@5uFDnNedy7_0AAi1_&uTI$F<8dqj_ zkWpEyq?L2m*x+<*2`Xjfl4tx>${1zi!jZZq2Ms(X(#Z@DVhHic08^4Us1vCzK`=bG z@(~QKW&6`Fm5+bzTew0l5(Np>3+ptgPt3h%HJtcckrI-X1|m}SpySh~xU&Z@lU%U$ zC+P35xN4+%w)Jt?;MIGqu_9MWdVcFHvoHib-#*?AyTgw4&Mcdzq*3dx zMbWk_lNoCoEf!rtwx%Odtv=H;A5pE_oTHAIwtAeSAW^LttqI2rf6mu2d<%<&L#Zyk ztAX(e)PFbAlxf8(4Zy@mt2P}%o&*is=Lk7Q<|*w@?dd~Vbzq> zM}-a13jb*E&9R5XoJ&4*!J;d*#;6(P?1{P$6TMPbv@=FKw>6;z7i=Y*ZG_)dr|dST z7|28u@~scOSRjt2C)MECR>;Lyi@fyhScf$g{d_O0)GPS_sxnm#bzyR55Slo9DX0b$8;Q$WBfXef~9r z5Zyn`2``2AW3|N!OQRZLL+g3Zs`?dazzVL_JoV7&YYY3?k=RKuT&U75$nxQ3T{|57 zid@RHNIi9+hbDTMS`%yF3KDJ1%5P1&P04!hKwA?RJTpdtf%A;K71Dl-eBoe@bNW?A zGu8%OYP+BP)r?4;^7fL>3iK$Q(P(yz`~!U2epf{Y9`rU14wqjJ@97H@cNOhX$4&Iq z|4I-3otF~O{v%*C3y~xu720 z5%BL3m`2E6`AW4t#=*7`0J$Z^Z(p~8RtseJnMFeW?3E7Jj$LhvS^cqbGOg-rCyCN>d`WS3Obyl zeHukg9=pKOf46LcsjK;NUbQCBJP;qEUzG{WhQsYaegV3O8z8-bs`JLt?WMN61E})J zhRi0hRcoxBxVX8mYOv*L7U9DqXO}^42>Eg2T&PteDj^hBZVkIGp*~HKFt&!};y*-- z-_qZKB2*@vacs~VtZ;4iHRv7TZ2aV$+a%9xp4MwDFuQ&0<>W4%$Cz?mEO5Mg!)=KA zxUbfCUC{TV0r`n{Xaa+K;Xy)5H|HVyHNP9=|KYI;02V>kF^wka6)5PM(Quc+qTFpn zN};u*3o4>%5j4YZYZbZ~#d)2mzQ<_t8y03|8zb|u19-6yzirH8ZL$rR3khsay{+$P z_Qt{2Z`%;1?#@7L5U^uST@U<`bE@{mux^=YIN4Cq+~OK@-4hYm;h=1ha7-LAP-dDNX@~)HI_$NpnN~1;OcFIZ7RoWvq(i`N6vN-^HgnkTa zMk2aXHM)z;2v+J+plS6ucW9~3Jm+HwD3i5?rnbmX!Ws<5QvzG_t`&UB9j&K)Iz^RX zUvun}PIb}umyoyD70-mn17kW)#m{zy|{7y4!YB+)T#sPUS< zwZ{D2aRnHovh^Lna56`x{@O(ee(f`sVeA9DUUxu$LsuYD)O7=|GzfdF69GjGDUMJa zf1~=X+%HonhY)f@G%nt{fCIK+5z6(W<%)(%>P`uhc?_2F!Zqahyyas$OQsWFpQsh^ z#LOUcO05_O})yGj$-zp-HgB{0FQ9kGKvX8qm2r(L34<&Z%84^gty*| zv5i2EFARnnZ$yI9s}bzeMJJ9gJSL7mfV$MB0t+-HmH{i~b1 z+o@Z~>4WySYR$0K3jPW5y#7Xe`6TfBSJr>3a1Y53Zpk2Mtl}YR=*#hmxAd)cHf`Li zkp8>fY%a@ZV@1>}m=eQEph#J)tYOk1R(y%uk88+WUCK*IO`7a23CbH3d48mdjI6NY zeY>t++A8QBJwj$f;cdHGnpS4TH||yQP=STeTCHeS!wQSK)A4Z`+WxUfg2J`0ax5Cc zuQsC!n+4|+m_vP27S){sWo{Qg{tL+XzQ{Hf8C*|4~B@xbq25e)~Gcp;GPt=(ZL!M zfv1ChH7tUFpADI$q%l!Dfe{udk)~%qQO6tRPJj$1i3TkGJag@lxQ5qRabShJ`^!lu@F>Kz4^3Q}THT7G-3^q+ovhIv znG2hBLO$@T;tlD^Ite>Rr7iN@W^>7kMuovJNw^gvn@4*wkOg|xk#{iy%&STG?a*)K zRYd83HG1oIW+Brzs_zqoulpJz&px?f?Au@tU%oY(+S!8q83%30tb5*nv~>EXITHJ0 zxeYcS8GW`I!rdM|Hl}J29&3C~*X*TgKt+Xhs5q#aHke`yb-uITbAhT8?+!fENnOns zrUu}#*2`$&SCAi*BEP_;-0CxkEjHa#S>Z?Zdnrmaa{ueEHEs!49(y-odTW9zZv?V_ z68#1CXiIk|tQW-r`bfKTt_I2ms8f?~(E@$zz|9a+*!q5K@bozX#%2sV5g{nI z62iQUDH{Lr{S=OT<*?K;J-HHC7%BP~s3dfV!qn?nSsu2abh(2vTc=iAwwIb_&nEYC z=P_DC6BOKtHG4y>yBwZ-PI3~#H%Ya*Ub0)d2mf)W!{nz_bncs}`rj&U$nObiDd=*B zNJ~fstsSn<*__#G&@mE86 zdQUGXeTo6RaYKt*HQ3T^i>iq+0@mfDL;2MiZp~+`Dzt?Vgw-K#Ed|=$3D-vYR+iN3 z!j4A&PmMX(WNW;FL(9lXmpw^Se60skV9Zje>j#c;?wsnRwZFyOIk((7>q{|>@|YI~ zE}iA_8r|}k4n}lAqR}k~%08>?;mm9F!Wfa-ZBPWs^PG@~`>^6VEr>c~@M^cAT4PwP zoNLoAvDiEfpUi7)q56J^7a9Go|3pPv(jE&7I-{u{GCnJdlt?5V~O?wW9BjckT34CxF%IE*gg5S5+ky(vh4!7bAX zs?x~H0Z?)%ouWl)tl@98$>3aK>r`u*CR1CFPs|3Q1SeeG$>6F7seAYE za%l%oUTpkGd_9rrRT}v^jkFC-EQ>0@7P3G=5+0P6tE)t4&>|-(GD79iJ`oGfQE8B+ zPQeHH1O=D@PDSy2jxYSG5)|c_D|(hYIv`vao%N#<4@DbXsU2S14j?6zn&(v67vlU^ zR*oFGrW;)pn7OjXp}ljsUzecg@JfTyy@j94#ElG_F%zzx23fJ$7eAp3WGC>07bgnW z$tKJ8f!nFpe@tB9Ct*$r1CXtf1e%M~HeC^n;>pCbJwq=@mRQ_geMQ$u-81}e%X*2A z!yJh*Q64+uD6B`Usrfe-eQ&BS4)>B6=4Ps0SUy(H8>%A4oWHoM7cz`!H??EmYc)~I zaQAX(a^Yi#QLF;HHtLClc}?5)^bG#ZfR`@}{!IO#86{0WnU=T^Qh#iZr^xZ9XYBf= z(dg5ErU~X_*vGch9^xHg4J?zCI)36Fu$%qOV#h>NhP7g*{;v<&V(#P$Zghhx9S`97 z6vGusyJ%(dJERIZ-6GkiDlvgXKM#_l&9eP1KmWHUhW3EBc&1p%tRUVuxxQY>@U@x!qzR;z%LYxfa86$o7#3q+g*Uu= ze1vys#9KN16`1(MSb6#t&B)4u={h!IBbSIA+7UGIO9)0vfPV1fm39~BWR3&$6`Xgv zFT%Dl^pzu{BugyaZyCj7H%Ox^cx9r?BjrNAh>_r!AzI=URz>_3wnpiTj>CE0p~}kU z5E8#l8U**XNa4G5>DSzqJ zxy_QJmL$<>)AGETe-y>W&I*_)nz3jr2b_vf@RtCJqSQ$1s;u++cGqb}Ro_J^z5RR@ zWef%|%_oJ4oC-XN~3JaaNbA3-A*{ea0+r3|+I_66+Ag95jqi z_1oq-8`bg8yH!#il270 z$U9~-)0?v8{*?t{?KyO%n;D7(Xvf{G6-Z^KY4N`y<)vTOV^x&-UAGiP3)qMY=R<4f z3I64^MurPl0Se<4C6Pna6YEz{lqCPdqu;dpqA3;0@^^8R&NxV6p6|i~wVEQX!q-Hf zi*g)O!$C%H-%v?mK6wj~irNBN77SDK8rgl*M@Cw2hiFel(}vk^2nR3f6i1d1AE{_5 z9=B-Yo?oUk1@Pr5Gg$b(sKJg#=inY+E|8GI36cLq$Q$(G2+YpoHlz$gMHV5>G&Iq+ z1^3>SVPqx_(Up9+rW~bdjnUa6=<1R3WVq~T|GUS?tiUFZaDwe<(qEnM9MjCgraU*} zp^PWH=g~y8E)z_gZfnY^4g!&~HOsezq{%Y|G-+0O#;Q%{4jLK9uI!1Faqa~=MrskA z_HZGd37Wy{2f6pa52s=D%GVfwo(N%hv(AP8!mRhb5K1*;vBuCwXhh28Pg5o9WRo|M zR-bNAU#3Gh)|7`$Z6LY8%Cr+gh17DDKrTxchWDa8Q$%8a64n2H9jv9?9DJL`0yofi zS0ARdsQ6oSp7obi33)S?Q8$#N&X4=6Xo$I;2leWCNU@z9cFl@x=~=W^&(l8Kxj6rS^L9GO7f-YS&5DilufZ z16sXtzfDvQW+?>P65jQ0pFuO*$>@(-5oEf9E8MEe%Xngjf9Kxx;vG`MeQ^y=TwWPdZ>qK^Vj(ei)_gyIE za->x-wKw=IOs`G&%v-JtS8DEdp*5#%uasVK%lagld*t`U4C?SlFK{(``$X*?4w%&_ zYPNoSSvpU(Ds;(lKhU9WrVq6#fMUE$}$`)8@(O}P&x!8*W%;P0Cu>&KoenST~0M(NFZwM}((2))4a-q}j zi$gnAbbKp8j>y?>4r$E)v{KIzdEa#$J!STdo(<4C3>=1SHI}IA1Su8;a_ibosIBUq zV{}HSt?Va+ZQ(y>_A%B*w+=9VE~V+-*Z$e!M9!)QI}t>D$(Uf4XRrbb;&$%$qYxmz$Y`ZStD98_X(nO!nGY_Z^Ung z-W>k(N^a9vx4a%kcaD)&KS)*jYh2%^dzvj48+6?_!y1PLqG7^|qCN){)sTH0!eM$z z=+)=vzPP8$_^n`k<|n#`d%1N8=S|#&84KaH!eDkow<4c~I7=*Sm-?RnB@#^u4y;3Q z(f&suf(wSM;L_w5-0a!MDmU;iS5MEBCc{xW!HUqc(ZKqSc*{uy12!fS3S5Ix;XsZc zx3P#8CA@(BkE|r~|Hw-FLQCHrJLEQW*63OFEhj&nUF#Y)4nrN|tomD!Kf$&wHiMA^ zF~-S=Zd_+M)p^&Mtp|Lq&{JVu<>PG{)~`=i$|JVjf6iOR2Py!NWvd5!-Ry1isHQp_ z@=F8zrNDEU2(bgM(varn(*qST-Rc-lzn8BE5~fY*1vji9AG4JIO<(}9*Q` z4|GK(#eJ3Spl^O(ATMk{36LqKT)iAYh2e%=2(?PiXb{BmGhEYkb>Y5Eom65Nv2JdLi*9F$6BdXj^ z4wvnM<57l@GLYIxP?migo{J4vM*r5A+Xz7J+Lb3P9gug$Ca@8IVO zdsVS1gsztwIeiRwp>axgB}DFaYcG3VnJ~hf@y?r1dTz`gIxPIfnmXcbSlS1KPm7wi zV0A)+_|BaWCyf?{>CK7pZA7dhWWdj7bEe5hYGWc+$RM`IE`%9DQo%1z0Bw#O6Pxr?9AcUsn|#OHMwQ%!3ip5of`_XB5798X;&0NXVhifc;PADo?+HHFW%bJk0)(}(?)Ug$saYs>=k z;^Au_Xjk@<_nwEp4^Hb~3_dP4$kZZV&T7YdQ}a^=?qeyP9O0NgsI_UUkvo`%8esY( zrq~Gg*RIMO_@b`Z4;}lmv2n1NeYhFfz`cZ8d***x=Il9-4iXRv%Qy{7lMBD&x;q!>IP~$ zjL%1so0d06;0i6`h2I6O6`_1*RosXdGU+}8weu2Vc~}T1A)6!%d9*;E6WC-WU6lfm zF`XAO_2!~0dJJCTss`VC;oEI)zlZm6vG5$;ARr}Jcjx!Qr`qg)i@Lo%8CSj{z#UN2 zBZoro6A^ETL$#p};s-MhBL6#raw85DA?Xf5+L4BGLmea*hC&^rl;E63jwK3IA&EvF zH_oEkKM12sRG>osC4W2epK^4@S~XHV8+|D+y&! z5gZ0(n2JPj0!6_!$b%%rVVX%A?S?YSYsf2%RR(pahj+M*cgPn^9HI|D< zYp2u8PYg{pFc1*^|0tG5*51&>)RErU#?Z+rMv+ejR0zo@Nyc%)BSNaVzPY}=cw1(<-b~BEoWZ8z$dR`@ z_Ffq_rMR>_xtySSzJ%6JPB~&j*~*2y1`k!f^-0xCyME2&EpG>jt{GaQPsSLbViJi5 z3f~!OVYiEeT=<&j&+CjZ03*!eu142m3lAvR-@wPr$Sztvzf3`B^Rok=@P=!f(_84u z3f%r7`t2~S^1i9EL23X0lc9W}3V`MEL%X~mVEiA^P|?xe)$;$PBG04!8b1UCgfWDx zD}=f$1iLsy?!z+5+}7}eYT9t3s5pcRyn^z@OD zPrIMUQf01)coppH?%eVAEUACKb#0B4x#iPG^&RiML)BLyPZGx z5(}rhoqxvO^Z$!ly;<9Iwrbz-*0O)$N8@zzGKn|@a4_+glvc$AK(Gr7I~_~VKOrO` zAR_>L80^0w^`E(|G@p+S`&0fy2L=d;`oBf$zs~CvbsY^<4YV(Lv%vrpQM8U>;ROtU zY=3!=Vn;Ehem+FK@Q94P2y^HHW0E{X_k$7P8_nB%Sntguq?U~0g|=;;bIn%}|C52U zOO4fYE}T@qGuw$_UXst#*VE4K#yeS`!1pUAkp2E9)K0$%7$nn*=m99ksaCO;Y*V@+ z6O99LAh|czfY`Luv_$?s=~EZl00_N|?*3twx3}EEA(XbvSf1z&6+O$aIZB?ye8dDL z9Bt!x{di-_MArpM2c>5{nW5Acl44V+ITT)Js_T~S;`C9C@PaIcy~jkhHCk%5EE89Y z&C&Fd2|GzW$MsQlbVA-#d#NRZrk1^wpe%IrPZ1B+nyo~Ndas4?t?7>0$ef;E+wAus zN^7*~p&UZg+;L{v$AQ)uQK`nTmrb0?f0!Cevsxo_Ewk*D@&P_Wnrw+QxhV>bnTr;g zO^&VmHSMMSFR_OU-xVf2Uy_k~IO24hcgi5r-QOq<%=;$LoU#!a$;Fuzhye@kRPo1T zWtW4I9H$+n4ZMp66i!_yyaz^qD5r6wJcI?1`;Xyy(#UA1W>+Er4l0FdodbPC-NVes znyl*3&VGQg4fMLiBeb*?eNqy9Nu``~c%yjiMyb5nZPZEjH@|OBwNobGwL} zs%7%lL#?%I)xUAem`~G{mR@KoA}A7Uc;>g{o@{u7hVVYL2efZmXPmM=ov5yMhWbH4 zfy0-rqGn>w!IJ|g(c1M}%+>RV3SOPZYp&ARUp-Mq>HucAp z8;{8HwfO#ijM&{F3ca{2p)3~FkNp;`3S3jD3M?B~C7{%WF*}dl`v-@)RiT*6alFmZcX4S`6qF3Z^S&ebl#z95?Dur zR2)5f{@EmiDuy9na{z?iw*QY$;Dpi*9z?7ZmPIbMJB(D;wlx%DRnW7!_8SDDp@JB0 zmX^ro@Af~{#28;{TNGPZ3mZ`wB&6caEsB*QSO~pklvdfS-Lb|fr&4)^dX?FiSk0q; z^HfY^w>wl3s0ZLVlqf?e4=jveLY-#(>G4=T@_q=EN9Fb}X$s^$OhOqws-?mg|7w^~ z^@itm9&?7H_dolG$koy}=ulbi!D?iu`KdK=)pg#CsF4d0U2O|(f1_UU3tgb4e`hqS zu!WV2L0HgLiy@Rusav3snN2my0F6@aTrrAFLbLSRl`326>AR=!H zLRzE5H3$TUNF!4V$9P!?D2a2+b4D+>;7jQtei(R7*uDvgE$4lAw9D=) ztK=`>{cfwyZ+=_?+3Pw;^wwxC%WRQ2fN;F=x&U&(&-Vn@3OYOJ8ep<1b+lJDp{k44 zLNanIq`w)5O$a5A9?!w>Mq}GNE2Oo=AT75%WALvCi;1$dLEQd{+AEFa`ctoRpAiPv z>f+?@b*%31|1NEYl{J4dzocyA>tp)Als0J# zC!mS#{~MsjGQ#xnqY58e0h{KGFcDj78y5wk)G0K56XiA#*$iC6T^nw`;Y&t+$Dxk& zr`(!gm|lMWt{-04Coe!51%={tsJ(>CUe2BvRhved`~^N0mv@=_!g9K5>q(zIJc7FB zt+N>RQ}H4neeQ(c3jAX4YupKO9%Wtba3Zw$G0+V+1mTb()y0R`o^R(R{^MTrVVxWm zi3p$>%J$z^?f+6NT9N7dtS`kP{!%R3|8!Mp3tJNbAkfjm&>3jr^p9GZDE-6RRKuK| zE?X*3sV=F7R5>R*E1)1mWH?hKfdT~^Mpo}Y0gCs_<`RJX^o*i?GBgluy^^+~&qCNf zJCJK&xM%m{*WXl93P^M3H4$H4ntub>6?FLqrJ z$V8#|ey~tjD!Q{;hVngRA-8p%yGp<>;kCQm1>89kh;lK-}4!+;h0HSEmtoM3pw;?SBPJm4Q5$L^{7(YVjUH9DU*d&9Pn*cf5sY6 z@O4}8HCJWmY}cZzv1Ld2cG<@gHXp1fL&CZ^t?E{Bpk@eE&TVFwEB@xEM2TJsIaIc^ zop++KTW?9ZqQem<9N(??_ZfgAVbIr)>xYIHVz>A)3fmvX97RA-H(Q^n(z2&GmIZhT0S}cZW)MsxOAQD?beNbJt)D0W&G#0!`{wW?c!JNyv6MFP zy_*)NQD&Yd+O2OR)cR-*Dl?rGm3ckH5`NUi+>~lVbU>QQ=EPLdhIlR85vH&=i6Cyk zgdpO`D(J9lj~tU8G37CWz<@@&v$*`;^D&E$s5DMV7ebV@&?Q<_^c*`oYKse%6yvgO z#B75FT*k?@=fR(yV$w9&J(#7&m|Vs`JCO@{{r%kr16ucm$Tkm2Iqqpd^4@(`@V&w% z=sTKGh{1qiIxz#QW0*aKXfEv34-D(~q~??`o!=W=1Ezb0r&M~EK5FGW#Sclc**qv{ zC5c4jnaFLt7M`MDI#F5>jazW+x^{x30)lUc&i1O4_55#oOc z$>j~KO@Kg?|6bk@tt?~r6_WEzGe1U=@9&@rf-uMYwX7TVlMtMr8Q7%;lMaLd^Y>dE zz>4aPn7{poE~r55yvm1IoavHCR}Y0u21U{={!XxY3yK(Ff9=f-jjV{%$^Nv#cD%** z)XDbox?jomEriiVA0x&JlLfI~9+``A!aiVED+DRz8W5mhN{;p03{$EAavwk%0v))M z6f%!HZO&e~o8_l#CA;KRgA5h64iYX|xOI~WvdT3N*R6R^^{^&5Pt8gIIaqN`w^6B* zF=&M`6$5Slo6U9AhBvRTp1=OgbIU93yx}2KS6G6@sm3LB4M!1)R3b%~n^8Bj1{?(h z(sA87qnj`<$7;eIq&&T+io5lKPTSS&JSVFpSMzY}kg4*%0zp{c%e+4=S5R_VyBG!)T!F&$k(rWLApIl~4))?A+9;jZWJ=?h@(y{B2=+m&}#G zE;1U3%w5a~GNuU#%z6L@CLL1}7Tqr7_t#I|$fr-yof%)t>Q-lZs7?tsd+@oZO)=ZL zt0}cezNxL$7Ln1qX@}`293OU+Vi-I&+OfnE5nf}*rPtGZE%F)GwFgPWmM6llH6%0)f@|0cdWFX+*{3SYw~HA~ zfcXT|x9>6taw1J3OU)tMfd>%vdz~wJzF0$DhLW9PW6I+8U}A-Y9uzPU4%K=>&xO-J zJ-iaCi3xnjvT0YxS8P`076wUAEGBb^=eek}T~EDpaoO^$`H?J|VLn0r-5WWl-UN{% zeET-^V&E5Ie(S-9etUzeg8-ex?xd_6cL*PJ# zN+|v)cTdT+@u%TPm_V9yhO}5NkxgWk5XvBXwjNZ{88m~{ZS@%KystSbOZ4d3uc;^!}z$dx4hVj`MqPus84a+MzUeDl0 z3;)^X8GG6%^zVB$OgH(%z1nZOhb{P?jo(u{Pw6K(fDgZ30;XqT2;YevIm|c8{rB_* z&+yTbooTE$aDumDNY+|IoE>L`H#DzA@QxIiAoz0uhqk4>^~8htnWW)a(DxGox7ObGpd|fA>r{hA2B8*sya3i^eq>SJ0KZE= zqp~=D1^&WJRlzXmaM~>6JyXEc!6A30zC7i_J(Q6FTA&G_0*l6Mu)E42I)WF0WcL+& zX)eqM;yYX*ceVU2I+_|0#lEN`i2>2hw*c;VPK%hCHz>RTLdUtp%uCYJv&uIuy_7QK2FGKGZ!` zLSefpBZr`OQJ2n+Sd$B{QtJZIr5`q7Xjv!p9x0RIzN^5wUs<}YF+0a^;z-cVh9^UA92}av8$l_ESgw5 zUSH5L)>0)+gJ(=c$l#4MI*9|Q2QW3G?4#Wh;Ln7lZZ}rfy^F#(@+$b{4Z~L!?73Yi zg5@oyC94@tOtS)=7e;>Ev{tQ%+ZSvW$tv5o9T6j zXV2538y~3X{F0WN38-~&Uy2OcTR}*5M-L;=4Qi{hU&Bp) z_-AWMf%LG4TUMX*Z*iugbXn~1r8FeDQcX{+vlYK+H z>@*2ipS@*RKZC+ox#h*XHgoEN3GdVkGe#ylsskk3&{WHX1Xxv`o8`S=Tb-6r2*vx`m>~8xD#=L4PfZq0Bl6 zCA;}{*?U*SqU|Slnl_}R;T=I#kkyw6Wo3J|%bC;1iN_0@JA2F0MdB4>dIQo$FmZD+ z56Cfw{L9TPXc2gNHWgC%9{BnMcNG@0EfsF?`_(Qtf?RZhMWiseY9Grh?rSH4Au2k3 z|6nn9kK{>YrO(VnQOO)5)5%%p0!~nUp*Nra(3~hp{P>ZPnL-uuy3&fpMM#8f5d6S? z3vD!kJ0@K$7M;d{DwQdk(n^{C@$o@cy=cmrT@Wdi@1tNxQvi~IimgwQ+oYwxcRDuU)mhMRr3Qdep>&_q(>lT%ghRl)wm(`iSlmD<|ly=2%o z>>7`XjW!dhJ8j7dhj1O@&@T#zT&7pCi=29k$!OR^d%ijgQvp=DNC%1Kqq_t_eB{a~ zCSRs+ps(T|A2Hd8 zxIh+cxi|tf_46Y4G1d9&Aft)1m=Ln6knp!d^ZT+JjLZl`>z2or7;4$^$T}pM>;g=A zrd!C#1qxCrMgD9Ca&~M)Rywaf(h9{Ev>HuEOT&?Rjp6nD7MQq6;jx@_)b~HLCw|$5 z?5ckL;+91khVy}iuozc_kjoH=c>c5#Cc>1-;V;0Q|f&#$_B zQ$|Xdtl}woq1l2}BWjV+1jpet?}I1Pb+ShUR28SR?=hEVFxDkr+<@O5D@l1t$|aEZ zH5jW)P|oh4KESFBf6Nl_%d!8V(pPT>nCwdJjQJlV6me6mpKZu-gH~ig(rTwdKAGIi z?2zKdI>mv$6W#K=5%ikKv%*^S7Wc_j+$arj{Q&!Dq`k=`nG)KMjOjV538+BMJ;F`O zg;XglC+!rfNXDgXN+8W0Zi8{dxs#t=KJos8Rc2ih$gS)`!CbqQq2_GKZILzA&tz5F zF0?D*M?zbvk|rafj!T0PV6IttkRRf2cXg;5$O*gnLnD<~U6f#IIZsn_w;SSNj>%y$ zx2|UGyU9KLxADIfO~G0rrx`w>Mn89Gk}xKU`#2o_Fm!I4)`Kd)fSSX?o|~&o4ixet z_?v_J;{osZb8~5sSBQeoaj@SL^d6}>pg567(16&y^7`Rs{E*Q@f!6P6!h$ZITlBfV zblp2WR2(BEAt<=yKt|yoq7O%REkNddk}q7(!_@=z_Gm+vw$4!MxEKDST_@)Ht>a?j zOp}=uhuC}F0`0^Ddmao(tS#@*?E+HxmxL4;rr- zpH&5}dzaMT)&q|TVJNUP<{|@*=X3U*B!YD%G1*BmIIJ0GXHsd#}O@IvQg!q0{v8Gn_jKOW(5w~A0TA$di z5o_T0_4`aotj3iV+tFV1*SRUH8Q8|>6OU~AskZR3)4On@ZLw3b&cq-1IMzpm6k*Dj@I~R>Fc*}^&ZCOeS+z*(HMEev~gtk z;N{l$;FWqDMX!GwjOeP+uOLaycJwJY?HW_}m**hj>KMku&0aX7~(A4gZ@e#WBjCl$~L;r1nw22cJh-Q84>N@uQr#fMH zIkvr4M=00k_nIcp)_E?_nr`MlW>TZcw=Ls-4knOl4)*{1P=Qo)vi~}$DUOI<6CPr4 zt>|;xK9mR0;t049<3XqdICkJij93&vw)`Yfrgmbq?)}E#25BxpYRqAB@@@A~=w3pp zxGVd}zgPFtS)4Di?Vf5>A2`K>n&LA-GlS6>tG77;&q8Ufn|%uk|Qm)oG~IkQL>dXB1aAmSHD?xiALwFKw^$)AgA1=6N8&P$e#IcnQ{eW) zlZHaf9aBR+P}7|At{IgDgW|c3=v`AW?$qiSx;k_I*~{lNrLNI&I-XW@Xcl}=kD}f) zoQgG=l6y)t9xDtz_=R(j2@Y%0?lLYMby&F5$2xLME7oTA&C94|#?lMU)gUW<9yJ`4U@ zqx0%~J+62Bf^A#bmR!ZS-E;+@>NjVpbrPq54d@tZ1f0p`cA8 zere8_(_%ni&6>K31mNVJTo7kM^?0mJitgm*1?QqG)Dta-*k1I>;5oqJ2mos!Klxrt z@bA}5OL_=<{HpoUKz;j0_CH(`XyR!5Pd%YZSw{&+1=9z9^?+pDa2+*6lq&aoVlZ+O zm5ODRj5wSk4o+UP=Dv%QIQpEmX+vJjd~Q!L^VS02PBg})G!BzX3C0%tek}5KgT@{| zwA=2~h3Axe_tc$FcK64}JE@YMjLpu86I|CXAuM(n=Lk8Qi2PX;JV1>AI z4BQru2ta;?0{t==sFvZ*B(2#d!zpUJ;Ueu5l@M#2HCJ*YgOzF_<=o`;25^uOTV>q3 zq``2VX;9EE_)Vyg2wl+%AUQ;rxb9zIm6pjCJrr@9N5&1H#;pV_Cf1D3E^AR5En6RB zQsA`()!%+%0FWt- z;|w*&S7SX$w)gAIFobgf%n-KKI~xjir8bIDEyed~;ht{Lik179)8_5<5}>23o*_u# zj4;i_aUTs9b#@n`HP={ZhGor27H?<)a0o^3B&O`ra3=CyODw+%`xF-S4TSp1EWz6J zzxeJ~Vm(;(Xf~+mOdrGmb^Qj~+x31pPN*t23+ystK7I zRupA60bAxH%vq~whzm&`*#dujVll3qTT&-oUv z@OIBnkB)=!!*v?@;NnRZ16+?N`TLDTjIhnk!N2C6!OO*^=pls|=G%QZa_!Y45p!<{ z|86;?Ipp^szCba!FY7__A1#NS#eYG}&I&SiUl4Ov^yLa1JY0S;zYx^nxC62@HYy=F zp>SJK0hQtp~aJn2d}1%c2$AnV3oit|UYg?3@als+zGj71$2cWkdzp zUEer`<*Vg+Qh36j%>?{~n}!p-3h};@4xTfwYFWR`JO`|5HCu(YV4)VSS3P1Kz}ZoR zVN927Kx3vvzjnNFwBTOF{JPB6VT9sZBtKYz{kwCDp38l}6_aOy)yRl2H0nHBHpj1d z@*ueFA_kBWeIUpUx;#<$FE&B~$0L{5r||q3K?WVp0qcX&4O$xc+WW=>^$RlRG6X;! z%5x=aV@BZx@l#cK>!dZtRGC7%L^uE0&RN;sU)6~nQU*<_+3aDjYPF`2=JviZoF-Q~ zh|Aa4W7*S`#plY@;&GcKZ2$5-LF&SQxN0yB41pd zh~iQpnNKjqv`!(s0$yX$r|{R_2HqOs7<+C;E|HTcj~N;7T!#kdHye({4e_5tY!tBw zakLdq=J7r;!G?6bib1hRIYOVPTs!ptE=MIiXPm-cz-sCj>O}OPQUjxF;JRCB!-uJ!Y?w$7lc7(ui*tZ3;AG zu4%0l`w3B$5cE=MDGdF+*P7J0twfOddzvZz{(2QvTDU|q<4u!m1&6Gc?RNqWDfQJ3 zP?HPUrsF`(NU~yR;-SBFx3|n@9ASygMlE#6BdC#s7eqEJN_zRxg2gFqUS?a?8!V}o z=uJb4Fu@>FNYtw5mKGU~%&Y6*^;L39lbVS_EKXgPK9)yTB}${)>J^&fRpA%3@P9H^ zwa+ikr;W?4+EnP9wAah+Po|AyY0lipl}QAuo)$MHw=}C)qf3(Qe3r&ic{|JZU|^_Z ziAsp3gh3dgov5k{%J#6$pPp|`(3Y)#t&UH=y*e#4`E+jFx9e!tX%i*x1?rW#VJxSo z9q-`alR$JU#}8u}K0Tk7c~6@x-;u_;y*i1ex=IsC)@rw0G39Vu_J7jmvT0c*J=C=c zXbf02_t0e0DYD?ejpwi9jdo6qk1IQS=H@9W#ec#XB|$iR!wZw6sm3D`@pbn`LcxQj@5ilXAm2E4CtF~zH}*~nUJ3(=MW5OvgbN`_CirrXjpL? zl1zD!=NtfX?riBP+mO=aoY#FkM4c5ZaC^|G`GgnDgtdHh%KFc_lOhw_*n&aTP%U!3w$>reDIvxn=!a*}fFcAlElW_c|rl5Z(lJZfiTvgNXNjgjWT5kZ`>3 z8%*NvDSf|i8z{bbbkNAj<#J-sWL*I;ksR5}Ad7Y{NZKDMvn8-%(uAdvViUy5vVs=G z=L)BHf5XOf+oF*dj#=4rONB0$OUT8}gxdu)GtRfJw}Bl3bFXdQH?B>7%u@XNJ22nl z{RNpGP%Sff1I-wO8H6wI+G{PtA_z}A3D3DZpe0s=_rQT$j3Di0*509pipP}bvc+o7 zvy4SLS+edq;uP7&)uczTR=dU(R;LMMYUzqsJp6Gq;aPh%hO0GFuqH%y`7|eg`U)|; zNPaY*qCs`ClX4wrU1Q0|d2`P4Iz!Jy9HLD13 ztr&OMUAY?#)jbtw@`~8h-QVgVQt}DZl;9P_9^x11MY`15gGBWA&ILntRd~;bcR$x4*RaBxF9s{Z z7$}FNeRWK@lSO;&6CXkd7Jwx?;Y1>?5NwRGyJw~ z&0)?lz?ngZafcXIYkYoUnsFTD_&j>Cu@1AZ^TIFbG$oP95JW~HpfG@5{i^(epHBoQ zd|XI*xSiDsy%NA^bP(abDUC6yNa$^Q7Ln}wikynsh}FQkua*)8=3Rc2(ok)b3ImW4 zHPlFmeLOwsqYN2J?l+^CJsI%}vDApfi1!g4CTlz~7s<3214a=I&a|uQcE>ajy=5U9 znHz=Gyfyl3bg5N>gEus9qqpljh_eHvLy7Wu=7xM0U^rBaF342-~ zesl24`xADDc?X-H0Wry%tYH#sCbXgin-TNc=(?Z_6rTL(l_1+_RZvtyO`I$lMz5hG zs86>adSb4>)ioh0pWu{@rG-w=A9zb9vTjh{{lzWO`tSMMB&hI#v)_MW6r%{7!U~+~ zDsa+?H4RTA#f+cl(9mzKE_Q`sD{#~CnG3Ri+B-*z)`*0~Y!ihm&lIF$BZgItw#a`G z``pM`%1TOb4LdnXl9d;sCQ#|zz3$g^>2V7oG(-MnqtGJCmLk;?j6YV=Q+P#%V(3>=fMOb*{f5+wY{;>xd&mj!GQ?QEL`UX?a(#;YMpKirBxEjO;MgS04?-+0z8K3qN7P3W}HQxux2)F?sC^%2u zc=6pFVM>MNl6?fp@XopU|L}zzO+{Ref4TqPu>S*Jh@y!R(7@Kr+T^Y6{|xY_@^z>$gGTwZiXdc8cj;I zn#ITj!=|dT-1ltgM|Qg8AaDB#7; zh1b?-@=LMm%!lFZ*c?qpTC445?VJXF09ZBx;PUST%+lPG7^-U zaS6Y^mn-3gsKgKDTb*AA7h~1)!ZMqbIqzQiz;W=YHia>{WEfdn**wztUob0Lhw*EM zF%ChqF?R;I1NjW_<8)*l?C{VRH9Q_?b3n}8y?~f<24r!V#%|o~ufaHJbOH>wD)gb7 zg2c%caQ~qQpY>$}>HUHW^>=TpHs@zyeTz&sWdQ6E0_GZ zQrDlmOgAG_G=w6*g2B1OJX}c6m{oIy33!~aj=!(l7G^Hre8c2rd6fQX*A%zvGMvUY zw0ZkO(;=u~nG$j)gltUu$17XgAJ%1-n9xZ$DCXu6KPAO{zCh80|);4SnWivz@7&i}B^P?3a2dHRNWfc+H14o=JUu0$4 z0rpX~&LpaTJa&q&mi~DcjDRC$F zI}}pp#buIdQepB7BqPO~Q3hr0x2VI;z&80M%cUkP1vifQU40T6w-=FHTT5TC{WIxh;yG2rP_Z_+M`9u_^^r!C6)9^l~rw|@HWMGUybm8@*ij!Sy_e{${Qw){5?i zV1-3ho2JG;OX#MHo=q;jV>!Qm@;QE7X62Q5mB}f0i;kWxQ{Y>5S++k!4-{6QYRuE- zlx-1C-$8HRg=r_rsiccg-ii4jyj6^6jICLoo3|?+%bLL$Mx56okb8&zfurUlIeVh3 zYV2?>5uljNA?+SW{?SEle!`eRZ>KM3e9iednzn=#e(_{nEU8*)EHg%%w2b6^!l!nf zS_6~wtaiAr_W4DxN4Rx%=gmCs`l7tc_wTXD!gCmN=+{upDdx9tr2jD=GBLBT`>%qW z`d5R`1K#IH=F=Zt{LeYXmw;j!4q@qeL1hMZBvhJqRm`XY_!V@ayS1Sb2^mGi?7IL; zw+qM2f{wIAyjK503$nyh_{|OP?M$w(qh0pxT~{}OjJCM-;m*DLiO=Wh?_8;#+dZZq zsiD6=np=Z^72s%x7Ju!xNZz#>&<2t_7$_fK0)9s7I)81HehRTXA`j1 zztaVHWS)J+S`12@4bJPz3zh|K9E!;2m*PEUg*U&!g8h^JT z)*Z1238EiR4-kDOD&5_Ou)#l^RDjE65Faq2Ife=}fY5*-p14U1k;7Jx7{+>q=KYHr z`OIR+x1S$L3E0$ZFU2S$G31p44Qe97DI6NN)E`Gy;bu0ltBlp0{72LB4jslk#W6BXJucJwl(A0Vo(+^iXFz~{M`dbgB~Yw4G=;hVMAjW(AY zhpMl}gxg=2MIhKjng_oeQ9nJ{6g!6lxzh53=xaY^C0mMg5M<(QcGqZ--_iEk?Ck7Z z`L2e42qR{fYDmgvx_AMbZ812|_jqZl3U6WrEnE~_+1H0H+66BhuFk58JwKZxoCyc^ z!rF+%7gFlCq2oE*+S&@3hbvQj_yDp+icFFgm?f+jDY$HI&}xV=O}xfbkmiDvHhdQY1vw%h&qqixNX0!-3ohby=;7WoD?;vXr%1 zB}65VoIJ`EVg z+&%|kk%!%J`WF*kg5nA!TZ`mRRLlBkZy`2Hpk-im+(;l0G18m_Z73!K&O3@223ROb z>PRarOC-e^vsafG%1jsnzv+;GALVk-TyWOc5G=52k|q0CG_YNER-gIJ!iU&1PhcXYAYMB# zvra35Y&WqrSryDSzCdFlekHOM)d|ale*Vg2ji<96)KzLDn2XZt5Av}tf%)~xTJ3Ku z8P6REe~skRW)BMVSk0n#-JVp#oV4j3V8k69vfV%?*pZB(IwwccSu+_k?=~Y?MYAGg z8gEJPdRIt9{7{T#v7*GB(Czv|@eN|Za&ab|mY|1x(^Rt)J1$Pxz2@${dl=& zicX4cHxDtGF@B|e^)uJsMowSOxDDH2W(m=2YrB?Xl@B6(sRQ~iYFhS(@Ed)S`BXE3 zw1>Z$EO6PKy}QcSthNdRqozL_f@V-6hx^O(TQ})BZ9JpiXtz;|(YfA0TqHgw9HezW z?d7OAk{HKFj zhiwoMBhJ#Lt5VkQgH88{O3q?5WBx8r8g*N|e@-tXtNOSM(|Ol{{(0R_xUVS9=pDP$ zG_Mte8}>8`P`HN=XNhvriG=c^Xz%Me8v+|$XxkFf9kdb<%du^kDC2|s$!P;+Q;xhw zq8>Wnp6d%rKn*i@UG=|6lj-fn8|lUQmoWf%O=#?k$hUpSvuCVx7M?#^C=NWkKc`CQ zPpa-SL;eJbF;=aai_8b?s5Djzp;diz<&WaWg|ODf-ky37mWKj0hi{C@qlyc(!j#;B zLY?M$%`4|1nh@l&Rh2X(-OE1R3CBD8EM{JzVO364<*T5^~^Nvi4|&WZ5JovJokoyS$v2VjD=EL5kstqhcKMK!W=}xU>qGQnNaJA~YDlkw6nxn*S0p zdtgrReU?NlUu25l`#te#UJa36Y9j)3_GXyfN;vApB|Hf@K4F4hSNJ-EkW@n3>>8(SGzn8=a z?%t-7msTLtTs0#W0%o4utX+vXuEw$_<4k}6eabFe0L&-)#c2gJrX&_ zDo)WcjmK2UG45D{P%|yQcI+}*e_U+711dEu)w2!JcJqUaKuty%l*hJHix~yMu#4m4@kS@A=d&GQ^z}%X2Ctx!9RWStRhymm2X-0updwty#YOS9TQamf zz(b@<5d@gD66bVp(HziG8uu+bIH+*@6#hlfuoqfG>5ZsdKB-k@opVdK$ig9^wX@Iq zSWk7AQ=nU1&cvk(FL&Kj&cLNA)86Hhqxg@Y!NG3W$Xd~OO!-pC4bl*Xbk18qE}ovz&7b38rde7yd`=;NJ}S+-gj zLbm`}WR&gK{en%lEYgFnFoUk@J!3b~iN(aULa7-R6JH%Dz%w>UqP?&l_YKJ2FMd1~ zvS9uW@1M9vrHrL;C&nN6Yvdm>zlAQJOTtrO&4H2P=wic+(AP2@xT{*bo(kSgp7&&& zQab7>>cA?^WN9wUbe&Y6lKtolrKFMShh>&MPu3;49|MllFkrMxg${mp%w_X?t-dzk z72VW+w9k2?mLXOH7GX!rEldU0O1*gZO{q^Ro%C8!)$$tr)qfiSBL36RRW78+>=fh> zBtv?hq!^Ta+D&wkyBKRQP@Mk)Y9fi)-FvBa7dYL3+VB(dPoA`5=6$RckQvMyNTuoA z#{#K&JYqD?IQb67ICN@Ms2jqTHv>!KxWX0kfhCzr`+f0&R}cqdk{&~4OwLdV$O6_Z_ztoqGpLI3Gc&cm^kV5|A}PBwg1^)Jje@g_D&oD1SzJy|CGV8_J@2DLjjJ z{~yyx|C(kM5Hb$-{F0vXFO2bvPxk-60Yllu4Jd8kZs!anQ3ASKn@9jnZ2qYcMJtX= z_RAyk%rB8umB&8&s8wZHN};zaX`fWc+vh{k8F`k+r-dB6Q0(zhio?SA_z{p!*+kX| zGR94$r}t0rkUzceAHwypMpyt0H2bBo8KuwDR2=An0Y_+>Gj(MNq{+t4G?lFdgrx?7 z42Yv42X}nr5KsHYAO6!?@gksdw14qey}zLyht*yB_iq+8)Y5 zhxdCVL!Q?A&6)z6pdjc+Vl3!Xu;O8%zr6PgYuLP8S# zjo=FOp`@Z=KLzu@sq7p&TU?-Ss0_i&Y>|9l>mG7WdBAIVK}a6?bB({(fwZ4G8Q`(f zZyWp$X;7eJ1j_+$-OMj%gH6fTafw$_+58Z(q5w{MjHcW?R63pRbR0xK^rezrMnPh_Td zUbrWAPJlt>1qI0r^osJc_Wr;m2?N(m^pBDtAxVQK(rkjYZE{&`mdA)_J~h;8L@YHt zHO+thz2d#irqw|dcl~zNaXlFngt-2>eZBv?Bg^xu?J4KIOQPHU<+M;3n9s;Vl`#;B zq)8wyfes?tz=#1O@~3!#kyk&V7OsL4abTAR znNdVFp%_z8PWz5Ct)7&{Pim>6K!=OEXg;JMB@Fu0ADW+;vClq{K|b=x7*%N|%MxNL zQc)+5iHZzjiVbwWV4(*NzdRD61oDh{!#WJY6wI`M752_F)1O139Z^2M2OOz-Qsh!_ z;V9olAqc7BH;Lt){Djz@Au7GlY}g($b1gq|Hh$k4tiU-@9FTGdy|5WfnI4>Re5M5# zsEv-42r5jexNj1N$p1ILC~|IO5i$|;3_-EhX2TYi$xeXvC{=8aUy7__4%$XfK;#sh!~EB*j*MhMmav zx_k#g6QsOn2bP;y5^osZ6`&J^$5Y`#=$7uDCu}ymH_Y*tf*+q5 z^g4WQo(68?PB*LnTf|eo8(##1{v4#h)qa^bMPHBQD}m^xbH0@CnPRLeXoMiQkhT6YxB@pA5miM$JB1y7w-$mxZl371c&PUcv|=-P zFWf-|)LaJ8+^9b*rQaSb?!6!e8z^`q_b?%1KxdC<-a4_K1(QB7%UD45qULhbxzXe3 z4GzYUY%34o~440 zf5VG_W=t18Q}cg>?15X{K#%$DRdz#u_|N)g`1=Z2yx~NDrun0|{e}u6CzL3i;f^*G zkLWQh(+}s0Yo*5Ghs#jMW-6j1Cki})%{OJz#S&!>AcFbO>`gQIo}sFYE=ggk5mf*t zLP??FKg+~2u22ajwVqU0rjA?&_^O6W8Kc3F(^TF@fifA4VGG2kd~9x>mVDg12&gK_ z$+g>QxKbrbn1Nv}KRn*UX0*1q)EK#VSh+68NPD=Ls(oI>{9cHes;)|3rN-PrhPaU1 z9`G}%tdZMH%h=35J5Af1+g@Ir?==!BpI8FQ`6^AXZB^&^42?BMM6A!7-G{V$d@bsr zZI6s+I~4_swrXE{COq) z&0gE=Xz;D@g^;%Q=W^A!mX$|5Q$>`1q!Kt%-Ktr$WMACgZfU2UM|cK4A$O}REIJug zbX0czZ9iwSn8DWV^3gfZAU&_vN0rv!t>4O#_N^%O6{_$w%Xl~`A_~!H@o5SW=unT_ z0Ls8tfCmI*R<}7?z|Pm@RhCxz#^;qgn{ChjxsTS-nopJ9?t!fP)xtA5-gIBuY^#;H zR>#{^%ryxyorIEhP?ddJwXS-eJ<2Ifv9i|TJv{Ys-=58bWooA+Ydmiq5na+6GRd^A ztt4w$IXz)Jrl}-*B(=c2^yqSZb#=D8*yk+swhi2f8D-npUhc@Gw78bKL}qaZw5!o| z1eG!(q3fqpw%ba&-mNy}TFENSuYON?aCv&vajbUumGSY}4Y;04Qd&96NYKggVdeB< zTexgz;sMzG!F%pk^|I}tMGIY`)ppNUT?_`&YJD5{Yz1%zP7%cbA_@1 zq(dHxUP(Hxn>EZdAG?#`h<4v^|J`Uv_xmsg_?d839g}k@Ig@>bpQ) zX`|%3M+{7!ZWHtTw)3WK)2@uykBufyZ=?21k)mn~4-_1w=E4c|6ElZD@cNpYZl^7w zlM}SIr#@A4on+>n;XhMst0fx7w#IhmhqV!Lmhw^$$X?H;%9Qr>{64m-%0H^N@K1h~ z$Qb1*v--s+AIpp>#NM?gBv*HUSqW=X&?DI#ey?F3$j;^blV+CezvanG} z7k_1KRFD!mi`5Sub<=jsc47Uky);r;?_k=e2{u{2<>@`rzozM~rX01hkP2opRFcnP zYtYq5{&Uo%AWfcDY9eMy)w(irgvnvqCp8w59jQ|n1GK=sUT}H9Olra1D6MfEG3;q# z7U@EBy)jeBs#IV#-tm?uwm9l9Z8Is=d3F=oO*;LuKCtG|u#l{=mHcxwOQMyD$t>g) zQ5=z~S)8;~x**-Ve9B#sCxu5*@N)Ndh?6No-Ry2~t!zDrLnNK)1(d)2d_2n02Q=L~ z26D+pHIsZaY6lxvGbG=Bxaa!R=z_@2uEt~N>T$?KR9kzvgT2F+7|HV?%OgL1BiK|_ z*Ze37YBI_wO+j^A5v797z^e|-wzo!1c zM7mMie}5%l-yGek)av!jYj?rb#sGXefoiDRLFTqPo`fz{rbOFhlbr-Ag(Tbf*NuZZd$P!G zT7-fA9fLe6D-`OYdq(W;Dc(fmf#!Xh=k*hr2brrj%?Db&jPn2otuCA$OsQ@ZvwK#L z9tym=gY|U$SJ)k|(haLopF5otZ+Twh(a=Mpl#OXJn(gR99(=qZ7+%xS=NK6FL5!d)e2Ms|An5@c)Jxs&CGeR~*iLw^ z_wu5>ph_(lXEGYK8^2b+>$u~P@|E-5PT-5vK0@e7{SD|~?gal32(R8UpAYvpg*PUr~?`0V-!B>4_2d_!6u^s*P?3v_&G z^bXqmaAHc$EJP1Qbri`kf=N-mS=8%|!aBH5`Jh@c3;f6=(d!*nD|qSn2qpO*%_8TG zy8ME|{~nI|9zja+M)2Yd-$Rx?ZSo#T5=s#WsT=f5V;fR3jC(c!p%%gn$%8cV%JQZZ zok5g~BuYUrT!TguNn70v%L^+Y=QqeF2YFSacXUwxQ9@+Yx6sQ`goP`NlE!1qk~D=A zRUJ3GAI-!VdC*J7E-NY z=SVfqRFksIRC?KgnpXanA(Yf?xqq|6&H1f< zW%X3!XUPgaP*2t90NaH+>|QE)>SK(3$LCcHtL66^9xZ)oco(s1hJu zQpU8ab6^A&X{0x;_4(yuSJ&s(Jz2LBPWSk;-Ne=MaV+SiYE4i+XXHGZa_N7~U6pdb z+ou|#4ot5#z2-W0bZdYxW+$$otq`9J1hdO?T-#=w2X_3CMyFK|DBl0Mwr$n$0D+xK zJSokuyFFZ+x_L6gvk{y|oyc8YTt7;v`Um<$Z<8CIZ6E%q5v+NiPyN@CA6@m>W!2Jl z`zG$^V?14Qs=uLdqQu*y(Q0|G=wja&g{3BqgXae=wz5k^?e-o|YIAutc0vW=QtHJC zPa~{cF?3i(VRWeGy_Mm-WTesEa(ebvV+&hoUqYj$w0Tff`{@n%Dbk>v}W9iBrw>UfY4c`ma!J*Vd%| z{834wGD(tOS5Rz4+`vw^gt!kwq=uEw>5x3>^x8;QbuXd+?2D1swyFRPgvy1%nA~Sz z3b}vN;MV0n<@{BiPGCbTxs^%pwM`9Ub(C!Bm=Oufl=5z!6AHeorj>4UHgpg#a;&{! zwwrg(%0sC+ZmGGcX-EAI@(%66x!mvs8)vOo8p|vo4XzmO6f1^>d}TIuSOHUOMb2cM z)SMI3mK!PAub+C$^|3Fk+;(CBRH ze2ysC$OtK+x->me4(}sf2~~jpfrcb}G&CpCEo!E^HiWLc=KK$TS@rpINc?iScm)q~ z0oBCm@l+0jN#vAaWTEj&qMA*shb6p1)ru6<22RBAbnlgqJ5rNYtQZv}G zWsCk(bT#4ymG_uhx>6yw*02RTCH*;_}A?cT0U2h09o;1zck8*Cz?OQI`sLL=%U zdS3^5TtBo1*|XB*Egk8GcEJR%4RI#4Qafc<_|-zpk5HP!$~#7($?8n@`eJkOtc#cS zY4pLfKSI<6|CR|%%gyD{N@Xy#s}J{T9{gHBII|umlIa?SBD{SzY}u$rENxT`#g`rQc!2p``ceLuht{N z1?T{g53G&&vj9_?m4YohOD&DLWR1D4^edy?hb^y|L#m@r9r5@?F;> z#;{i0hb-FOS7YupK_3_1nx}s%n%^2?k+eLe^A@!P5V`15K3kqf)ioNauV|cv;*NW{ zF)-QU$=xvIwEo-odE^Rk48o)pVz$8*fdLYBQau#!sMcFijvhSg0x+I;R#J>5LL62o zAG=&!2$U~i5Sw_XvTn4g9ONs-VFZ)AhTsEjr)*`461{m|!(=o4gS=Q@WtuV{o~E`8 zXZWWq>v-gTR52a7VL?Fmi$aoN5Dd?mW9O47S(eF$n_8vl?-@sBCTs8?kj0`~m}r}8p^wgAx6#ja?G(#j ztk zIkl?8&`mC8dLcw;x}D>bswnoyw5;OQ7H!SJx>A73p^d2z7<6nM4%53wX~UH`vQRCqTYvF)b0(i=OvAixk~g6c zldaQ>i|ezTE|AcIZs}WD6|soVa+>P1L2V7lJ)=w&-oti<>}ciDZc;WWO$XAbb2xw~ ze3^_0d9zWdVM=Y7 zbn_nNqjqU7&0D)JU+vN~%pT^Wcl%0NPNuV0qkFJtx8i^fmF8lj44=|ly9i%vg@;Ok zPLV{|c^fV~&c@Y*m7Bx+Tjvw2t=sWhQ&i3Fo9pa=3Ei`IWWbAJW=@rzdxY>O!Jn#{ z=>%?Ru;u8H&-W2%Q;HzblnLr!g+z^rr4Aj?(9yk&WH<{J?1FwF<)mW6rnXk$=M>X& zXcbX;T|HHYw2Qa5x89NgqD0B%Un?^!iTlah*Tu-WW z1X6nIeFGBF^EJsnWDbW#v$=kS)$-pD45k=U3&v%{zE5VESjUZ*^{;AWZk8s9_+MHwYCDQ0W8drUvH9g}kY+0p|6~B-?9jZle$DdHB#% zpwy~%iE&mNs+H9z={g2ut!ZJbLPibpL=mwQL0wZ_qk0`LbX?;|XXT0$kH|wp`OtPc*~Mp^TvS zfT;U50F({R6lx{;4Qb~_`hO00DKXF*)-*dVVw6)-HnO8Q2)InqZhfW5P_!@EqFNeE z)Yw*nS%;ximhoxa3Oe1;@>XL=pq`P#{paa^O-z6z$%d`&H|$>I*KEXq*i<&sU|{4q zt`9=8iBBYRS8l~{Bk@R6U5pGk$4ZMDH}I3v@OpL3K;rQ5bJY)a8prDlCS4tGip46- z(b^Yqvql;exb{_jGAE}RB6|_`(^@~Bd;OW+P{Ic*n`*hZ`CVb+NOY+$vA$jaEN7FVO4_HIibr&_bqIWhpdWAROaZ)&bqO9-+4thQyD>~y8rI%Zze2$)oB!L$ zmuF2Tu!&Mb@wy+~h8j;sl>JqWLn96eAjcnZVQn7H-D*4{!F1XfImC}vsP`{cb z8bx-9!Lwb7L0s*susJ4J^PX38SDBoq(Y1RL+wj;SY#jSgAIgheYXw(pB?I8@U-=Z_ znck$J^ZfgfDQ)wr)&QcXJ55lsaK`%cnvlri$0!DWh>3J4IbqEq3uSy7jFxh>nOXdJ zZzQc??Hpk^Kw%i)nWV5LJ+nyXkg+CC-5L)AATQ1dJ;@lREIotA!$iYUupXN`daYN7 z#a<6Lb=uJ5Lko`Q$7*++$%VB_9NyWI(OjV1y*Tr2Mum1J;~$j6IxL)56}xSf z%vaUDPEl9S$UNJm&K&eD$5t=ebP>)uw$m^=$8N6dQQ(w|1_4>Kkd7fp2kxO~2e8dd z8YXI&#Z>iwb&BiF%Fq4yp$_l&yz-eCvWm}rplGZ!;CV!(q#X(=Tw!J@BPfMpQw$<9 zrR%jIeoDBkVi$!iMgb`$E>4h`%AtxdUBn)NWDbtnt;JJT5W9q01QPS(HZr%wp;{_@_bKQ&}b(SdUWr2RB+iDFWn-5+?=UW=Nc6r z^N>}DIWS6$fBXrEmkqHlm2Rm|mRb9xE;NrhR``=Ky*W5nVt$5u2hP|Wt2@ejSzKlx zfZUjPGS9{p(+0Yqb~w;@%&Y+Xk{-*4l}~}oG85c9-$INl5ma`NHf)`srj@18wrMq_ z0%To&szIpF){S@}#e56N%ZyvQp>bBp{lad_Ek{UwEmBij z=<|#2dUKW6ZnKUq9c)K%bU}tj4OVhFc}qvmv93w}%F5G%n6}%(4JXW|#Q`>NAP^H( zn{Q~FQmqT`v%oy?>jX&Sy`ZvXTSHklfdZ3wmt5P?FFe*W4i#ioCu)8WAVKIkShPQB z*GuwF7=LjGs*7(QRshcfkq5LkMc@e*)CyEhb{&1XEIF?Y>2+fy3` zc)_Sk7=n2D6`l$xUNoZ#=IyhjU0-)Io~}ulCRE6rqaw25&$(&`ghSKM&FR5TMK1?Q zi6Cn(_x#xES?shRfN)LdM)YHwXJ8#IFoCgUi3_o@E^Lk6uHiSibA6H^+`5Gl|>)$d}%vTFb+TvyAFUf{*A+y>5mK&t*c%$X=P1SKq&2FQTvID?$ z8vXJgC-5YbovW+7Y|}Af+P>OUqOhmE=KVCl-s63yS1(W)o$Juf0_DHWbAyXIBIpL^ zYKRTS$#SZMYDOS}OXcZzUaZiH)j(;=jlJiepybFtIf&+7^F{NDj~%KS)M1fc^D;~s z=e-V|sndshAqT3pUxu!F9+h2a>%G4J3l4N&Jn3l`gTt`4JBT?4d&Cl$A;Ci$wy+NN zVOY2YyEjUBR&R|>jm4Q5Y-5C%ydto(ehmpP2H|yTbM-^xMMjSGI?vM?b~i58IS5KC zMA3qqdv`+bznU56)&Y*7dE} zfeo%4Q6?Mw`^uNKv&d#8&9_B{yGY~6pXfnA6(^QWl^H+fWoFfm=|$SPut_SD4bxS_ zsW4h$i|{>3EO1lft?pPM)sQjkAU2PkeTzx4lFxrn!mai!i7(^c$52@hk%q#5zD_-> zgu?n;&klO_&!xl0`9GA}qi?n3<;KPvu*2gGa5aEF)FYvef_(yz;hIE5L!XQAYgWa4 zlYbvnszZk=v;wF~3>?-o;%6u+GK_-%lf3AZ0TiR1=$?qClJ>u}>i6P)b^#avH-fX` zhDK=w+N0CbQ%$ALc(ntXDI6nzAl`JgaJCLtsR^SFm5FIa6JbJG5K&^xNXF8Yd>>_` zNKZ!`9-*4eaTfdbz9Ug7y_!SyH%BO=;I^crAKH9cu@JupJE}|n>k?s>9nF7Af*B3H z3%oe&oi~$TcRJrT_q%r&_$7gy5gbQrcGhxLT4ePPn$+7~J>mjc=kGk12nIGiT&GB< ziq;p|3kVs~C|(&7G^aq8KEwwX-^HlCGP7oV+<4)IN;fGV3clS^`LCki5m|&qzm2gP ze#u=vGAq~XbgI?3M%*C+DS=UR$mw28+eoIY1XF(%!zl%VQM0VE?C6?Ub>f0;DseK? zKV<(nva7;F)E6e12MZ*}g}bx^EmJ#E^+Lw!>};!MQuTI~PsZH_rh9c?u#<0iE7-TH z9NAyqR}efingQ{mV$7;si-6H#)m#qI;4{clK^LtUhbU>l2(aZWKMk)ua_OhMQFDA$ zhmO4-u)#z)+*WQJ}|OpLJTWuoQ7iuZ~i~t)inFsvtad+ z@~PvNGjH3r&HO(4;|+>Czt(YHeTI#j11Ff3KBd$>MEq2K~*RPbWK^zlxHtnC)w zr}99r6{p1RFx*NO1tg~!Luernc-t#qaT}~lzpkMNZ`a~)bw5aqI+VWsnZ|KWZ;;!8 zweS~6zL3^nNeCl~H9f9AL_xoLA4SE!e=B%_R6DcsC~E!?^0j`YlFV&t5JDO~l85gE z+;{9f(f6fyxMEJ;3?!=p`_E@~t*RF*43Z8b|7>f>G3Nf|x)fX_d)T6GUm23hRUsH`FW0BGK;slj=Y)?@qg;<&crPwx#x=<2r2>>Z926g@y=y;A-=Y?17JCvII0rAl4s1SxOC3%Ce z{)G-NL5puJ%50D_Z;jJ0xQI~Z5!Djrk!es}*uURCd{TVs)xZbAj`7l%E%EI9uF-$E zeBHx*7h23?`#g`}U|2#=#bDZ*qobECS&)^VZ|d%5WaUX>dH3jESpFb`0qxQ|vO?6^VxBy9C>Ck{0Sn{#0!$L-K?c%7U`nKj5pB%vyK?G(X2_U-C`oPce6 zWBg{{4;OeN!8;Mi=Jps2gpodQB0tVK?o6Dah!bIXFIy>=laxo3<7Vv?8upN4p=8y@ zNAmSC;1TnyL|wR#s6_oqyGLN!8w+Gu34|Pq3UW1{VKxHmFRR087zoP|Y&lhxUY>{S zn`g+xB02-DawY`p#C}R7U>G33+rS!^szdfXO)5~2|1MyY>dDYUq`F;C(5WKaMJ5DR zEMmkTa^JFQA{&o`fgl@CYia@E*nPM7zVAl9a4v^ zdab==ESzM;!}9#wb(cW>2{t59)fB5^1@=Oty&cE_S?x$I92PTV+JbY4wzimIO%{7- zR@#OXnztkGt=m`dvPWO@FN{4WgIF@;gk~(PK_6W+5h1(eK8dhnNdMEUmwnKr=vL%k z6j{%ERRQ+B@atdq2WtFOyaoK9e0IbtymM@Uh4^TB501?ix7!QnvO>^{(C(23LwV#C z`u-4(>F~{n9iJIJpKM*?Y?_Cfg71Tug_Zk!vUmklm3|*dnAEAVLkF_8yzfX0W+V_S z;7dv%v5Y7%MrBAIh=`snkC?x)BqZh-j>O7Gi(a4) z*{2kToD(T0EI?9`-oyI&5~JS6@}Vk3RQ;Lux(*Az@7X46vC( z?3|a6=!369HWUbP7vwJIAQ;V(ADr%Idh#G;JBg*M8GKZ?CZ?Us)tcZICKK3YeJ0^a zx<3ppGv{9fx+-P6e?;RdRsLl+C>U2hZ>wqyQIL$1M6G;%?S2ZbZbckuJ5Fmoj{}yc zEwE&%9#^t`7>fO2Dei#=c6H=z>SwAL)dd!O z=X=Cf;Xs|(yZ(e7ajg;0U^Ml0IERBI+@YS@LG>D2YNhg%Va;N-SM)|v*X z6Kiq^kY|KmlIpC!M@AG?t&tkWsCBq=;7s;!9$5k@TgBemCw~_?T-sx@5|iLVP?!-O z)vvHz4l+YefEu)ie{bhWG0gY~m!23BP!<+{_>q_%Aae)~a^Od0<&F5U7ofcWqjVpP za*qa{uo#QHMhPhFI;<$*wFunnPk0?_Y}<>ke9#TwBy(htgx6^vAbIWyo5~oUj4cwz zg}Da=8yw}8ja3);4`phY>}m}(@9d^mwVUCxz_yi8=8$P#dhD7Y&!O2pZ%{yV+GTk+ zV>9K&o#LAA#~q7JyJFRxd;6csaZ6?*Rg{RX!OmEsjkpY4yJ6KFZz8AxR}@}uPNcS{ z%y#$UaPy;_wg2+qDS3W;53%ic%1xj`mb@Ljbr1&%HWCEoSawesN?}EHkmis&-vr`K z{oSa2IWQ9n0ybU+SBHTmA|sE@9|g&h2$Z$9;@UNAh&wi3*Ht-qva5>_u7q{^~&vMKQ5 z@AQ5S9C=b|8^oplz0|<-Dv{xyC?GDz;empI!cD@dEbY^zZ`LH9TmoOr%Ccp%y8D~p zBibh)AcIVhJ9W(1g1%*G(q(gYuc@r2Yk^! z{pnZswWE3AF8V~$EkHRU+C#akgQJZ7fg^r8S^x(Jf&nF$5%QMg)}-qOMpJOf6YB}7fb}=4>$H$u|t)kJ_mE6MHxJa{2KLrD2RtC4= z`Yk-*-lagrXQ}ogZp`;+!7vUeI5Hn87ioLmX{!+96qQ<+_6&6xsa1dtU2aec4XJ~M zln-@qJZGSk!elyHyqcCVAtX~LI6{?Uaj1wb`1@>k2Xv82G=xr2%8{xp0LB%#(a@8& zOG_jLv0fZOS4PfoRdZtZ#PBfQcbkl(oTiUB4jaxKDS9RNG4hRt-_7TPUIkVP3$35w zrLWy#Fl09@dhk~0mMD6kWi3k^6dMLmR%Q@avvF?p&wDZ|-Xa z44QqqFI8ngXn-2!7&W_hc!zjmzxpW|Y%5$m8LbN?ml6!R+ve8DPii0$XNTzN%oHZV z^IEAP_a(2g9!ooI?K6;;qhllczQ&EB=NI=t8xlZqTQ07F+St=@WgUQdJ0@3(ofVnDLnDveuy^K@NrcjH!p)<6 zAU0(oHs#qP6xK+t4&x=w`+7-~vYnH* z9GAv6e78Q`{c$~by?J=Z%{Gnr;*$e8@aPYPh%JvLSLV#MT{_k}o}q1|uxDUmZXL6D zY&sO|MNy(D+F?0uq|!`AuMBLyOt{Lj^%uzac_I_tun<|Ejuml4V5QGBM)h350?L&P z!B=&?i0_ z+g3cMs?-*{UU#i@b;S5%u@(+QVn6+1EhE;5d&{?9WvRtgS10Mr484BDkbIKc&8g=7DM~!7d9KZa(0OqFXKJ9?ys~;73F9u zfjw@$zb@?A*vR*x<>iPq-?O>joQ%n+TLJCRzV(I!Ap;xivS*Q4%hbDO{=m#VyW~`} zsWj<9%zj;-3Xs9_45O@sFf9yJRG6D+$!YB2rP5;&QcbQ?2z^too*X>vi$5vuIkwe@0c zZN)2Lsj;u9>nw-x>K4#V%|n3|#24xD3N70d4z&cco;ttZ zK4g8!&yx^blOs?@{!A0T)ufFH4)VA~UT*&CU~BIj0`kdMaj;oLJ4DTv&JT|qg155z zkW6fk%7&SH{8dAlra#j2+F>zsj4;jt-|p1rJZP$WLq@xpnf|-LJ9M8^X07IB1r~fd zdO;E8%d>2lP33(mc(FMD@LkNw9cpE_x*rc`k;!(B*IBFiytzB!hObd5fAnKxVw&R3 zBga>Eewy@iBR`kz#4Oj>^udWNXL#R3IEeg)unVZ*osREAUHHszE~2EP$J4~k?6!}{ zaCEqIaL%#bTAmsZ4jnN*HuT39IwBeTej0gO?PJ|eyVRUN0Vk=&4a|2LSUT)#91|=$U`OfkwFu#Z#g; z{K2a`pQANMIG|S)!6o^R4$Sl=(>>MhWrwMm$4z9+w-DX6c5raJt@e0;^LG{N4fQ4T%DLzEVNmXjx|jFyY9FGK8jT8H33|-15SFm& zLZnRWinKB5j-Z{jv%A}s$&=Ih(r`GdUKJ8_+vDgw!J*xeFb3%0UG?u1YTb zop&SmqFz@TLHz-=z~SD7{D9lGyczV`$N8Pqo_`nE(jJ-*>;I6aGHKKrUp;Eo{etJ( zskmC`xYTdTJf%DB_JBEeSX{S{v&%+4Mo0*^W^b+i9p{!xcsVGTAhY*0B{{6!$fT}t#$FRN-#=}_~ z=I|CjmyRv))6b1YyG0-w8rIyfFv3|{KQXEQ?Y7#MDfX;i7%c}nx9U>4yF6-`G?1AKdAUFKi?CnQbQ*f7KIu?QO!2p3Pp;(M|iwHu)` zLGY*17p`=KS4%U@zm-11A!&01ELtPPIhBIbb#;SZWNAAy54f4Xg+gJ| zSBwm6!@*C$2lpMfeVea2)>^|K+BTjObnm=`-BspD>7+Gh)~X6sgf+BO)T^{A%nfYR z3>srp5R7nY38y#}L;_qIB8bnGQ7D#VQKpouAkn{jMH8c3Vwva13BqRCr4U#XeOF}s zg6+){`k~q6{`KTPc4B`-Ea_N8JP>MXn4FXwOnD4uk}=Im6FY|W(v6T}eh9u3`SS_3 zh{M>d;mv*>kf;YGVv0cu(~-{z=7|Kd`?rCy{?-aa_D=#+ts|hbWC1Zgg)#K!6Kzos z?!AtiEH|im;+n(DsM;0=Kgu+?C0jei9n^jshR)>UP6}{; z7*jpG=hA^rokpAN>Vp|vn`ivImwg7KNCnG>K`_Y!*~W*FFcDv(*TXD83TtT%l@-^Q zf+y|GN@~)u3@KiYq7ahCLL7^W1H)~O%iuJ`p5>dhBub9!@rvk4Eq;Na?GmT zQ$wP*tOy_fs7KU*Pfmz4H_&)IrB+o*#TrKV#CK0!?JkMFd>WRQC;yWA= zDEkhlz9LpTDuNZze|B)&Pe%bhwTGDk-6k8VEYQBm4u8RyxLB@HVi-SfntHy07=_v~)u7J|aQPR2P`z1= zddTIP-2^+R=r5mGm4*;jc{F&C6_taenOQungr3w>57mP`x)k-yC*)f}oxW?eI_0jQkFrcSy zVbDmlAi5`=+=tL<9krPzF~hpI=l`=p{wHM5(`7ug{s%Ky@_*xkJ z=)KK!FQVzavyBkW+ zc}oCi*1r;&FPgynXWt0Ak(tn*724m)APW#oFUl}12JmPbu}XEnWUS1sVk_{~0<&2yMVghObaT%sQxJwBN8HKh0 zTm)QZ$HD*j>a$Oik|)$bzLQX9Exv<6xkM zOye4MrVF8Tw`NZvELRr?<*T6 z%^p!YxXTK{5<5^hu-d=n0Nkj~8^$wSC-V+aH8JZiiehxIXKjHU&J--yL?vv6`?%e6 zw=7rZFJNvi-jVN!Ey|;-PGs>Im|T9g*7%8SWcfZe7p2FS-cye3SIqTIK&AU!+&jBo zkctVu&(6?)Y5lH;)d=(NqeDe$TG_9S!e)f0VcLpKsI^l&5`UT(a4eimO?1 z$jR&Yc6%UssH7rM`qtFn(g>z*j$%P1{fYq6rqb1_YgYXI0}!I7rEUwF zoPF==yy!ZE(GM5h>Q=K>)6%D0E*`GPuxO{YCdeX1QpxYLfmC}P{>BO^8BJ?JcKbGL zu{ks$0NVuWU)m7ni#%42X658;z5@y6!^xsSv{3I8mXhT$p zJSRnIq9kYePbg;g!dxZ)=5KQ_8DnhrWcnPUBzG_mNXegl2MXUn$%;4dAjObjLu-d` z!;a3C8tRrx6K2{LDBN)fvJ`laGc4Je6|hql&OAtTpi!`Ip>fO}RDZR=x%e0JfC(tp z?+O(kko@|WB1;L;K-3bBVCP>^VEilu3f|+VGg=52+osTy%jCqLWaia_L8c(GQ5i^q zN-9EKaCGR5QjAi6elFi5N~r*n+4s7r5~z^r096IcS4xOeLg}%5%QOnZA4W(wBQ#hm zGuwPBd1aqaoHHVvS|9i)=P%b9(A3!p%d~}3Dvr5KiIQWcUI37{lD*HJ(MZ0YTF^Q? z$0*rWHKFxhlm?dWX}l#rjx4&AIfJ0ZCm=VT@xVFgh*s$K7*^h)y8sj{BD^NMZg1nn(|Ox^qg}7Z>YZ4)K0%KrqgHk#M)WO zf6T=R=NaHY`1iR^=BkbJ#V{(*#$^W1p7C!^9v2$3TP-*YCg2*Aj<53#(?t@k&Z8g=!x5s zd-1{hfqX9FnbLX^>nj3S0zsjSjMGUW5A~QD)gLeEr=SAnG=D{I2i4^eD{lhRsZg~hLECTDUblRbHRf{t@_lk56F$(Js#b3K>6z1eyA zHEPghv(<8z$)8+52Q2_kCov=!MU;B|H7B;HhZ$uH!p)=+8D+1A;Nem$_r@mp56++e z4RiS)2YiV10o>vb$-wDnQ2F1nFn$o}Kj{hv)^vgv#uh5h7S{hYqhzaSDb35nZwc8c zYJosO=nDJ>E>r02OL8E!5P}>`k${4J@|PIR4}yeD%nJA*!+RLT!29_9MGoMh=+9SA zyCsn5`Fy&&w3U53e~!)lb%TjI2Oru>;hq}sOp!)LUsqvMjxt1Tn@V+Pqe6EE0-?Ja za2-5xw-R#LVh$bnJKAh&BOVLVU%5}dLZwR@-{#~{MMbM34fEI&;=wVpL3v)ReSU-VvQ`9KFdDT{K(Z_;WVw> z$BG+p_FMnxC@}{r^JLEh|3c{M>}qT3FP7Hsp}1m2pP6xsHIMc+6(P&xnz-e`E_K$x zs?C7-@My_~+_&IqV2Vo8vrME!yhbhJ3m6a=Wj=~c)DGc);f-lb#}N&9f-_8HlSr9`~MDr{ZD5&kc+Ym|8PFU ze<(5d{~I>M|JxZt|J4#M30iUs{K$ZBqKlR!&)*`ekHde=BU35eqX$EER*GQ42T8c= z;EMV$N=ne?S*;$lF!1nsUw(hc56`d8d+eL|esee-O}yq#Z2R?m|61wif&$A)zoF&^ zKtah^Pt7)%>=gT}0WE#?mG1ldHNDr<;d}cZxh9Dog8O3tc#zXBO-d8!$4o%U$^s_q zsZBT$%-IKOu;lZ0{z~{8EW1?-N}vN)M;{Om(Zi(^tOZq|$SG6pmRa z9Mwyg%Kp$Ea|bQMSg9wJRBx-9Z`OvfI-7TYw$8#i{9RLGc_iBPpiUKH(29#E=INh2 zfwH0Cp7-mpO*{ze91W&)A2{IduYv9zP;<^U6B?gMvVwH6ON{K~j}$n4uW<{6`11{s$sE19oz5}oP7_~(LhT@5 z!2f?yoNn>im;D0)KKV(Z`9HoLg$%5%4GoN}{_9bQR<@DDRzdc?x}s+4m_YsKnlI84 zR_%gbwl1g_+->dOqWlMiyd`hWKif8xmT5gbA7z{Gh3p~m7D@sM3CflrBkg5$P;bZW zJ2-sFi5A#dDq5Oxn&Wxv{wd4J%=i1aRPWcr&N#9zz$T;|8Pe3Sxy&+7JPbB@Oc* zGq>Vxd<2OI`+y!Zpo5WC>`7#pCbkvjofgRE)1{co-naW%gLOcYqgRgty-+@qQr&(N$yJy<#cXQ2Gewhm+cKo zkQd%J$SF@@+{WI-<`c<|>U7@9RkmT*N1 zwdVQbxwp5BWkE~8E+XuvH3+b9&m;xz) z1uF~sKMYTFS^6wv_Msct5Eo>72{AG+={=seIB5SPvK6IyMw zO$gu;RraC{K+{&4NJBOM)~f)DrrWB`sE)2$;Hy-uZ}$_6&PLkecqo&`{EOC*y6qa~+YIxd*w_d6s3l31u;58iuBV6_P5h`kW~6v^j4H8;Gnylw z6Lv5vo)d9kL4k#DzGMcuU7~^tNlu(-YNkfp4R$MBqMx`mQiMT?@%zy_K_$T|%m(XG zMAQ#o#I)DQ3C5wZ@z2mj1);%FMHv&9U$ADdscAALiKV1+{1~Mkkxflr_a7e&rgp$j zj!AUrxkc)S0QO&~2iSNU&9$ev7BNelA{O!L=qAx}6Nz=IW>2Qp80d1+MVl`O9kPw)yV*?ruh9%*2Z-ne0qq#W3lZsIarr2p?QvgH2T-+Q?0%NDnV0?W39}Jy z*@id^%$Qs&n^#zHKhgV3mWyle2w*C60`Ca5(4L6!Lo+{Kwdj_VJDN@AS)o_PIk zzkHdj!`P^pDMMto10RpnC5QA6dJp^R(-gZr%5nL<^AQ^-Df|*0Wc0-OeTt>ozMv?V zL{Bh>5I!%7lO>h+(-$w0?D_AVKlRncfg^jTaY11Y>}jY_;degChy14Zv19y z3zzBQzjA8-)+Zvi&DESpjEs9EnKw)vi$_+h1pFx_1dfvj ze?#vOVc_jm21;ODq#*XyS9=i}hz1498G8kJ_Ht9HIs$3;wc|lCqTaLw(h=wX1`$cZ z@4VlmVkjEuiwGp)OESI>ANDB^*|CHuLtI4X{zYZW5IFaC3TJWKy?DP**%a}UY0MNZ zj3ZNy;kmE@U@}EtF9yIuW;ESrm(i#^f!-9SM3G?__Dq{Z8%;A-YF&%fE)PnC6|KAa!Kux!jrch68HG9R~#I#;J&#i)GEr8cy1`slBUh8d2HFI~(HoZ{Yf&M%x zzsxB2$3joK=I;S-L`NP{V~PuR%HLgS^vCha(?{N7yvH=*9igcGeUtytFI#C{N%ag1 zXKoAG8B9s&g3=$Oa*bNav`Px$KaHmrV1CW zuwH*LsWcT~!H4Ld89mumvt8opwSIwMQD}|OMGkUxP-vdQ@?kr_zl?tp&RjOcA_w7l z23hj8KvFqAN$xbjTi8*8aFd6sV1J50 zh{{j*8t8_e^M}AIT3E6RKKd_5_f6m)h0w&gDr@@k!#TuKg9}EfB1F*Y$a+wd=YW<= zN?DR;tO?_@DAP*>+aSIO;0pbBU9}YxNb&`WCDj?kS;Ik05d)%kgNe14=;A2XVEd8D z?Id^rUsJ-SHhH_8EhDp7rD;B*0z-kxX5H)=)8e3iLqY=wXo-Uu@+5WW=rK&<5)U3= zSNBy$Ng81f+#cGVnXx$U#Og#Bv)2q@*`bpX=Gf1|-B%q1%4~t&Qyg@Jo`(rTM_*|G z3;}b|oY&JY1>+2EUW{f45)ZBOYl*??w5`S*E1fGVcEqzWy{R@xW7u|$76aNjdHFDD z@w}eQO!jk+&dB8W*_Llei|z2x9BOhmsR>c<9#ZfL$y}2-QM^!6FQN4|BXbO;(VT}n zjxk=MMj*Scd3SX`j>wqH@(9Pap?=%kaIl8w1~kN(g^?$du}DPGUGYobSh<~fq@7a& zy3psiXbCNUiAn$sWJUrFJCS)tautkJoXkDE9Nt`1^W+8S5oh;EjEN+|9M_}=#u(g*w{M~#sehSH#*82Zgg&s1GO^+`6z zDeI5)OiXUWe8NZ2;V22~R%#rfi`|KOPn4@O-U{l=eu-`C~eUKL?^iGKuUh#CF^d??CD_3^`d z&lSpODW$7c;E}}&uDONm*6bFjfRm&4Z!S4WG?hz8EWS1&BliUGb>orPgaO>DHBvTK zQrX^2@a{fuUO~6Pxs3M>)bw*~00xE|{h+y=rnMwC8r@*h41#CeVLy}K6vq)SNuNex z3|Z5``RqGvm80dkQFGtX~bOz>dUQUw8Z`O^K@rA z=p?VDp(E8tkIb{e**4MYtaLOKKiplxt9)<2 zJ60$!j~(I}t#iz1$eC9DN%KqOx(9DN8gY(*khdB1)_IDfg)R8)=H(G~8>9^dc}LYC z*C5Z_sBe%|fTjSDNza1j!OucsrZ*_DUnhT^YS`&9?UOLQ5*R!RcWK_ws2Z(*kRVk# z1rE#U4zhvZw-7xP(H)xd3B>38FP9al5R~+$v)7et ze1Xdg=tft;O^_8_b;rvQ(`1UBG)$tg3Boo0rLVQXz{ zhhKI-2Biv0=`+xWg31t3zu!;F*~{_vnep_Z%#XXLTgYzAB&n3XctViFaBQ02*CpMC zpp?K;z*FE@mA;s1`pZc}HW|>`D96|ADmES#>-3D2q4_SZyiJ{H@@-l~NN;}gr4{4! zmq-p^G=c{4o`C6gvVANn9{&cMClrHKHpb`4ejWE>6gbj^bcLKf<_ORwXyES_lV!KR zX_17Ith#wNdobF958_$ka${!O7ek-q=$Ao~Pw5hfWUQM2+2T6=E79s9{|>F5&5CUn z|BW!hL@UeGLP`>L*YeChA{2DN3j)mEHZZac&$Qc)@%_;GJn23Uql4|aFGd@BzpYEB zR?onNA*XQmlVEMeO~fjhbGD{V=8sCHJv_y2JRw<Ks6T^r(+gL# zaQn2i@m~;Qrx`u3>_PF+WP{h(9EYhxgrxF2I<$3U7AR%KpC z>_p3b6n5O<$#|t-|3e1`Yg+2Me2bd>I|e84@7#rd=|BNzCtD>WgKx53YXei`e~dqt zcd&Ia|NSpNqC)ALd6yrVXKsFQQQ=HWtE>XDQ2~5c5g~sA1Oy5I2mtt znzeb_Bk~-OL{mwXRh35OLrtrXg!bMg>&rIx6rRu;3#eIE;56v0K1W=5)D9Sqwg7R^ zS8nJv=<(c$H%j@}KRqR|(j3yTMJ|IemBBO?{+^0ldMq}onXB{!?4&iLu_94M&>2S9 zNTj3KDCG?2-qAV0G79%0K*U7P50{D5Ybi3+< z&@WUo2#s{WQ@owcag`?3pvhk-lf|TU4KyGdFEHW7!>Q^Xpm zJnEOG$<=F5Z5L9W1|2Mg1L~bZ~jW(Ks ziMvv_5b!4UJ&nIVhlHT#{Ky4jHH?s6nVmP?;mc^lEz8+x0^JH<=~ zzI|Tz&BKOTroiq?gD){O@Q(4C*GA(T2uGW##s`{HS)-{7n031T*#u!+i_lOuz0j1h zH35(h*}7{VEJQ64AM~-oXesN_ribTt6ru+a*4NoYmVG6CChH#Hh<$6>jOF;p(aHT?g^CGc zPK#607ZYqsCKUP}=dqD|>XzZ9E<#U8pTUu8 zuH0CA$?an>@P~;8r2Qzx-im~JSOhHQ$$pRdA6kd5*L{HQOn&a51mIM;9!1CmE65~A zKJwi{C*`1bWT{7bu7O%_Xa}c5gT;N;O8Ve>h-goBbCv(8Cx(C~7m~gU7YcFyzo&)& zzp?uN&=ddsg(}p+)U}pSKC6h`;@p-6|J1~Ug7u@uLE~zCyU8ZY#3sc0WXskUruV?T zf`qeu%NjCk$g_!!=5ef-A?c0w@y$_@M)HEYPMqegPm)hItNEV4TaLJS0{QV1ad=Zt^6?t}z1QwI;Hs3x8{V^MMe%An!M|BEB@X#|t;2#@&V(vL-(=;lQmn zK$E5aqrl~|mmdg5l2L1fF?g;XW)EQx0hj7mM?aU`uDWf&MIl@LNQ8*n&=<}CQFL*= z$AFq`e8Yj7ZHmSzb+xe#Tmc5m%D3T3Nd&-1wxg|B)(7KqBnY$*ELXz>LiyomUHJ}le*Xi8%b zt$k8&)hx5Cc6Z6@fz4{WY5k51wX10t4m*fX@;N@R=-2+CoqFVAaDuvYq{MR_zxhTE z>dCRHpoL@CY#u9l+AMD5IUMMGabOoXwRBWdv8F9CKP%>Hy6csdrR7S8%LFFS40Vhi z9>fqcN%@7mU;w2EnkUFPkh5>K)&@rCJruQKVfu+Y3k~`uf_f7T;zfySn^2EmiY5-$ zY-xajOk_}^Jj=^UUl9>N)72(vk>UNzaHIZV;+SQ{WkSo z%UcEX%rDlF;%X`?#!V2b#FSc}8pwZUj&8!|jy#-ORIV>j?_l_iN{a(V;VtJ+=bSqV zM|ETnvnEudiRc^w^tQZEoB{iSmyrQzl4?4K~7TZjX) z#};fPKX4rqdZy=z{XW&eUX9-zDb2;aaN$>q9335#AaFda-&u?gWP9~OkCogF9e>kf zhd@}3Sr9Q*QnJ*$OMtC`fnf8L4#F6wlNM-&3p-2ZYA3%X0vXm#iu*&4J%k?@k5{A^GBa>%@W}s&W!&WzoZfA^>W%BUS?fp7Wydew+uS8O3@TIKYm10Ce z_B8ej+=Z!ycM}z*^Z0GhYn_^LW{^3cu&UCWgbl$2B}J-Danx>jiK4XlJ!Cc#=CFD= z@h@@t3cd!}cGlc|k_W3v{9hc`VM4j#+To%KWT<{Y4R&F_2!zdAgw13G?Y>=3R5ozx z^eHCmHoH*(ysNz67(g6Z2{2+9OTLqjIa?Yhrid7Byn|Q(%%Mh+nEonzFxzv2s^h9fHzhoM)9+qZ|=eLm9G)+KYSlC>CH&ReZWM zhNV%CiVH}l5VrPwBY&t}DS7~ISW#0qKPRj{JXkaZCavU_))uAC48Hr-zQTmFwoW1c zaJy`-xsEtbT#b*OE}U6h6_PM~?_ZKi(;t$6tI)}YxBydO{?h)$TH<#6n8T=uopU8H z%3?w0;Sl5x#G1BN2I}cy{$xSL8RW0&N|)&FB~1lAhVr|`kZ}~nQB)&L_@_WxY|F7^ z2{(k`o23I3S!lM*p-05Q%L|DR1&at|?e$^L=*og(daf+_U*KIP!U`0^V4& z^CnICh~Yto6FfWO$`xwt5d(E6vn`YQmPQ)~kvN&m z4S59>6^>LZa7cIt93Sdi5`2-43Xs1%38rOekbv*oGA*z_rhr-r?@g;dsYT$uN8BIg zHO5fAQVNf1ZqACRZx!{GMah6rSjzgU-xVKL}~*t(%VjXNEXNyWBl*}YAxyFEc` zU7-(!hHr3^_X6u@&dNfFj@T!Vo73`tk+y2AD9%^UOL(JxwhdJiyW^1+4N0{1c)lWd zhPxZ3KHYDSKPJ16l?ILV+3z=Q{6-a<k<8}Hy z$ExlRr+#q-35lvBR!(_AVc#$X&uFnE(hNr$=J~sDHJr))Deq|%(Un>2k|lJ5MPJ$2 zwT0eJf-Ttq%Fyyv*zI+VayAqaZ`H(c-MhLw_RG+$w{9|`&t!Gl{k5FFK=KNA)L$B( zs{lLVByUJq8x-kA$oIKgS=m3WKA+cIrVw8+L91;i?e?zT)uA{O4MYKV_{=tI!W}-Enexw&e zWi7zAOB1BY<9smf*ie&$$T{>^YV{HWH>;Y zXzCeP_(T-(5{lZm55>El3~e7-E23##ff_?(1%0xlmG4IFFpm>T;3$J+@GTEXef1!- zyOwr~Jt^8qB$C-rGvP_+*wAqjn^$WLMj+jzTpiD@u5++d1R+o5WN2TO`b_R~@X=Tv`d zXF87YmVI1ZMr<3&kz&m7vECIXFPrSF2IWAz)9+vOM% zvu=?Bdnk^x?3qm*5?=d>ac+8z#Jq7Zq1nkvTSP0j##`lH9C0vuS=ochL>hO-dYpPu zzx$HW@^21?sdmRY&AnFG$o{!CiTJ7Z{l;W>3`!5@pIZ~^F_~XvjPqemB;Arwm@b{N zUC+&o9;S?YNtj}})lT`~EN@}gufvNf_S#-|gSB~s`>qPwi1AfqaNI3PyMz+O8+(U>wR#OYG{z-J)acpl;xf zsW%8G9Y7R0C)P*4oR+V3+v$)JChi1Qxaks}$l5nKVI*srevN|Jr2`G*ZUHmL`g3E{ zt=R`Nydqlz{p2y(+%AvK%N_6qt(X-3LQlI4oIqH%13}|zB2Jr}H8@hO1jDY>J=Hs~ zlD*fGDml0vvL6|&^3<>8 zOab4NwC^hUj1Re_;ix|l9LM5@SCK$8vkM;M>);ZnQ>$^5Sbr@=;XIZ1h~C=%T8ce? z4B%+pQoK#a9w`c9a3E}vaHsG1b2swks4lEyn?p;dBh|*f4NJ8tF7lba-D~4ttqQP`|A{>RN42{XDbvUzu#y+<8=)!WHvc>o^@(HX4y%^V?boznDdGkx6*N z_^rpn3U

yu!n5d-zU$ediG|r&^d+xXs9a*O^}OJaKnAUUEE5^7@{n*8Nc3%@!!{ z+5DlJmn96vj zuZ*}~+2iHtUx@E`2lC{YUZCSW(ceu2n$fbM;9t1quAp{(xV!B-yZ6JX*+%!lxzW@p5!@Ij@5FAQJ!Bfob-JCKu-k9rbuH4dD59$I-57R zVkQqS+&<+AHk0dWKTbBixJMYvB-~^?$aU1q_^mbDmHZlc<7-g@J(+~SF0^wU*_)ni*^bb1S67VMRWh&ZES#i$ZJgpw%O zc@ig0ncM{dk1M|xC22by?118&xs`tB3&20z%a3+ir~h`UKG5gv>Z!?$lUSBv=8~qZ zw2{V5F z{$?`wt1?XX8=^Pawpqc^$?N(wp*j{5d-$ss~lg<*I>5gn3>2^i$W|Hn4`+<1~GOflAzqM$-4V}ee zmxR(eM4M$KwRJQDcP&K9$9Qd|%QNIN>1gQVpN;93mG^<+W3!DW?Gvhp@yv4P)7RTI z00rtZgxNKOSKJBR4nEe6XFuQFR;@LZ4gHSc%P<|O1!HBn?Q7i8#qh%QX|PSwUasGh zacum^R&-bI9s98;K*g&M^DxkKv0?j!N}H#)+JI9p8~2m-$%-p}|14CB4XfN=q#-Ta zitjJwQo`HP#cgXzeH&D8UA?+M-{bhTJY_aEz@iDP_yLWjL)J3^ULm|+*D}$gOCL@_ z`YwaTJtKiQ!>6#-R+7|M%>6=}Hr^4_ij1vYISqM_mMB+p2_|*w_eF+^K8&qWuyRSN z=D2B)V+V<8f`4ID6bzO+`98RD1+$P*T|c#B3I&(murVxE$Ycbm=~bQMBRm%5`g%)^ z=SS;)ly!p4>CH=Wcx2vI@(7IPey$HK(9>|{T=zZ$_N5fgu}jq1CBa(^u&ld!g^epND)(QZj?p>;O)lu3ePJ*wkI^)Wq0UkCsUtF@HDT z;Z%H-uMzd~Y;ySX0v8vcnQMcesra*!svK`p;RV^nlrccftR!yM0(O(u>7YEVc|p_T*|`8SoLqxGE2L zaG(5h7||z8ev52+JmXHmY-pDQ%AD}v{fpU7!) zg0`0Ay0TLPrJB>lz-eNhP;UO_*BH>f_ ziRguLRDjw0nhm?=5Xw0Bk0InVk5F@5{8T@mTo}~O5Ll`XJUoT1NL}+aF%VXl`+b;> z0VEuQZFc#R80pP6&~Wl@YL4p2r4jsgUakYB6ubnb?46Wkyh<{{3&N4~9R2Br3xJ>i z!f9^6AH=);5PymJxMXsIXohlfzf3Z)z^X>eeq~X>sW?ooN1A+SYB5M|YdpRV(;4h>%`QzezM#rNJ+&xGqZ_nw0fb6_94ehmGU#QV%z*&HSiR!MsUY4! zbE(Mt*!%&5j1F%Z^>WOK%1Kpyr&n^RESl|2`ezB|layW^@^9x~#?F6*V;D6}UQ%s1Wt77tRXKt>OvIJL@Qrf{C{;~VL-CJRsmV-IQtPuxRl@6l6cBU5>%Ok3@`-x0DTNN59Djd&SDv=(88$=C4@3x1P zAzSIT(etZG;D_M+COloTObP;>MZIYo&uYXourh+5vv`D?u(s#yib= zNVFiz+Nu-v^TZ{jj6Nd}X~pT0`3L&g{G_Tj0{do``{bm|8(|#O#z~g1!^LTY- zRHzhtgu+nejz&6(o5P|QpTXO^N^Hl0MHdX_h@O%6N)?1 zIr)Y=+Z@eF?$LwQ4tIUgJWInYx-%G`<(?Nb`eckDxO1bn*aHsGcN#SPd*-fkAD$-XlV-bA~a{*hfH z;ixg<&oxIcPC`7QQWy9Qf1S#XNMvSP)A)0x#^%P&1~Z4g*VDc6Dn4F7+=%Z4GerP$ zl*H@JVOo1y*862QSjkhnC%yEE`wm1yO+W7XsYUzRFM{yHbIpcFg=fn%yj&V}k0?al zwuDAI=%wL&4iP3#x_$g=Zwn~@_6Y=!#0)VWG1yHe>v#^|CAwHHZAm>x3U5c0lJOHL zCf1d)8@f(%%dR%$3CDfz?khnjE#l`$fJmo;cJ_8MTJ(`Y;BkpZ6rf$h?PWsp`FKIn zjs|HCp1P1XLY;`oO*;5!P1A`Hf$hPCY#()Fp5y0Orl964Iwq!yvtrM@O6#(wP9(## zj5niLWbKMYvWx65#GR%BQO-$06hrJX=E!HB>UA^NW{5RFWs9_Hm0RD2H!QCOdQqEY zQB&R{C0<*ZAYoR7D=>+#uM{b*De70ihN8uIT4^dkl^C~T@TRbvWyEIn-4MGGWYCZhKAZ2Nc4 zzFL&G|H)OZd|D9Z$hvKlt=m}2v@3F$jNyV&IjYSKGA+F@cQB#%q&)r@ac^<35&)R> z@TVC#)1Pv-?zQysbM-hMH~SQeb>3!pd~3zrYhN+LnO@KVR3}j5$jIWod5IKdCT5zs zafrG{s+5jL|9PaC>$;S5d)N_zLo3w_Fm%FFAAdQ6>*80S&1DbK(Lz_gzsS+>+>-mK zDo}!HTjY}ak9$Ad$ET>($f1eG*O;6QJ`NvgB~|y({u7b>qZWW5WCXt&=En~xjQ=Iq zAZ>2wVBqi{wUU`?5N^s#&Y#gH?yjP61Uy9PKr5)a{Fv5%0;NGCXacLj>h&Rrq0YC9 zX5#PlnTe*_fnwbxuuaWR2Q@NS$(^zH8czZBe=-DEWDQ;YST~-$^=h4^nwVi59VXM+ zH2Irtj^e*DNtWZK#`p7fs#WEM!O!GP91*?i{8tZr5A8+@vI|?3g_c5F^%>=_p2~A+ zZxzLRYp*T&dtt9F#d~A#75RH*?-j*+XD=T4du}ft#d}R}Hu-x=Z}td=Z{;-_=BMIL zRqwOqdt^|Tikm`r7u4R>4IJhtLpS6!_gCP%C8HNlgza`Zus2nNZ!e$ab25G7wjRs7 z7JB4!BlK704j=QoDY}-(#aG-*m+`YK`d7e}g&R@Ch2RU1Ho~zC#%A1s79_PflmO^# zaiAO#o@1<@0=~4sY;Qh!J~_2xiKqv%sme@cfe>e1YnHe}i_u8sksLFGd;x!Z6%v(> za)DfNrCKi)^F-dNrjQQU2wO$Di0y#ou&UyJf^g~*weKyhZ1m>o5PReDc-!UK2<+8u z_dLluy*Q3WpA8&$)Zn>GqI>InHpIxnZc2+ zF3MxtMB!WR0r7L&nps3ebyG)&yV$_;oz|(ckSnT@?_#w}E6@xh>0O z%+&_onYQZV-^If-&g{b!k4{uxQlcYqTFN^ljeW&P2Ad`%5iXP@A2(SG8d`I6Pj8?! z(V)F_IsfQrS64dXWHGNYvtWSN_PcldcZCmCe8U`t-~nXO!XU??#!H$Tx9%7$>^!o` zM4A($iGOtK5zdL-#N_4ra7f~cf@g|QaJ+DycZM?N#S!=R_e6|Lb4T?E7iVLDY-B6* zZrLpvhLD*XnPD<`=(RVvX%SBX15;6@5slt>B-J?{s_;rP$Bw;g`d<0+V4|%03FgA2 z@N9<{P7%Yvq?9Sv!6wV;%*l_=UeT3_H7#e4KOVnqHa161fOtlnDD&{;fHr9)VlbKGDl@@GpNTq8=nKX(^&g7^*GHVMv$`rdrQ)RL&4&zJ{XEcCD&#@`wju;w+wk^9VN^Ao$u* z$MaI`LZ-zf&fCUP&C20#!<3vz#*1d7Q??tX{6xYlV9v88ZyQ*>s$Kse3%(tn#RIXZ!L| z0oT5BO$@QjbsCYf$4+si9I;NEe~&b#8JP0sJdZm9L#T+1Ib`0($8$GjX%Q)?6k_ht zc`Enr+;oScA=P3mHHGBx$ns?6#P=yz%pAj0Yz+x>_mH0RW#tSt?|oHWCT#N>VsCV@L9tIC)8=jqZ6dHml~82(Jh``wvYWDcIC7gJp)S_DfAkT7=1F z_aYys-S$Fn1_{LjxfbHC7Ak09q!lMDRLOLrmsJ}%GfQnY zPcN^|t_)QrGK{rxA2DgKn3KyAHa6S<>O8r7U^fxy*FZRYfu_&%6X_hptY^I?} z0z=6ImTZ;UCG2GTmH%8C)v@Y6A z<%8t|IK57@lC{;5M!MF2Xd_dxBhx^oyWY(APkrm@-%)8iD93L}O_ zB`A&_8?M^6lx)N}?C1gHQ4W{d(7wi!34%)v66D5o0)>VBuD)C ztZGKsBl~F&W>eBSwFZBGhjpfA7<;xU_7$Ktj5gUWeL&d2#-{dYO~GxD#$+$RLBsZ` zv4!fnLV`-5xP;^O(K--CR(<6>${5ft)7cbXcyXQxwZl6S$&pg8mXD~5YT^{>P zbF+S{wOE}*zuZ8yP0QRcie9DFXiGsFJq)U?M4W|eS*eCryD5#vPsNz{E%bR7$bjmL3KPB;yA~BNcZ&PK>93w7ov|_NbsG>7;4-7qX z$4wdh>WM#LI{BO z5gCAJ($O=ZOLL_bE{~VZZ@EmgKQ2rfd~QuUzz%0`AvwHYyqKT0DxqX7kCEDSHM!BP zO3TD-B=r;pf4I87U2j4)-~pR`sENbW!EQM$CRr_q*9%vuPBUK%de-S(#i1uDmLkz+ zvNF)bw6}YIuFf6?b>yFr>>%OJ6w(CzXv|X-iF+jJ;{w`EX!Goh~s#E z6oc`_^`kgeAR6vA2Z6b;nwQ7LHe^5VDS$r1Z9lcTFZW8{b&O#}YW8oy)Fr2&p)oed z)`NE$MPfBeKnbjPd>(r#hlFEA@60Xk$F!}BW3#>APDj^0>p=)wI;hdzE-jEu5NHDq zNF=Ygp5I0bFA_xqzP`~mT{l#20ehL6*je08mghNUV!elX&GQcMYwhH=8QXjGGknewe;BoA3o$ihI$6xx_H2QkDn zqWbwo*4g%z?bW8;`Jlo!m1_`mvFy*&4EIodgPyhjBGMiK#f{dEB;szDWhA$Uh5s~k zV{yKC9go~@Au5jBp7phOJL16d+Io9C@^D~r8(S)7qRkhSujHw0xjeRO+ECg1^icDF z`!{as@6pM{H51si$R~dI3mw2drmu^FlsBqcAiap*#HuBvA^>gLs(!zwBI@hTY+|o6 zg>Sbju(^vxT_H+MMOvrdWyeR&wI6RsI0UBAf`m@rSxESWvVq%0ddfGCAUYFaGNUb0 z=e*1l@Zi||#0>nIYF^)8A0#y$D9ByBZ+9@;IySm(uG~ynb7j2AvyJqG=>{xkU(GW? zvaL~X5AjUR)i~jJg)ezD6AJIS^zr5&l)Z56h&kz?0b=^XtrneFy>HkR-f;e!TnRoR z1)W(G!`0bih+^_W3gz>O>&Y>EXTEpHy&B2m?sR@+w_&+{bV#-)mf7CfRrcxJUqqaL zrYF>%ZoRR@5viYSYXbY$b~wGFW9oD;gLAip!NUlK*{>|yUh82m(*2;)fneYBy1RKH zSZ@v)3%bXji_eUn7C!Ty@jZB)seIdGJ|~e!Dcr=B*6mm~uo54z6K-qR5Y!$7b*G&o z=@y`-&z3=#Du(0#J$R#NKl;r7N;l4KcM59?VMskH@Zj>;ojXR1Zp$}?uGJP3Qx5Ed zNe7$nLy5eCE83467rU4W?tp<^UM+jb!1dzd&14rXRpDS z9&wg-A5}_Eu`jIHqua3T!Fns_|AIE@7Pr%zshve*YKi1sx7gl3kzXvdeKaD& zB&KPXC=!7yTlB%o)bKZB$QhyjxHPnAz+YaV9%^;KV=&oS!?@IyP;_GDF!jpQVbXq!$XPq*VqZ`42lq)St!cy za%Vsd4@eRf*reJWAu0WRA>Jw#Sx_enf3VW-x7#KeM*gH?oFV0{s-%y-kZT znIBKwuK5_;%T%cRC6_UXGoXGi5q#7k)3+`$@F0g;zty$h0^nEEmVoYPyms-7n$z-$ zwyZ-hAs?AA9A^vPb9BS|;$b^T?9sqx*__&836)5XvFJ?$^HUe_Y?Zl+C|z_jB$;~l}ClO0@ozP_)}+nlJ5!#morjww@w;dhkU z^0~#3XdJW@?c+7w_J4b>0WRbi>WKtv_TDWSn~}vxVJQZ+i%!{XvfDvxldE;=&5>)+ zM6U1C$*vgnitd>LdQq7E_#y3?#9|${WwQ@1r#6-o8;~Fp_Sz&D!i~Z>tGR~h=tCeX z(g_Bs`FMXxDZ^OM2G*jkfx8;0GB%=| zyar%4x=Tn32cz4#S1lmrU<+n@&A>alD~^tf%$&^di|Kp=nJp9zEPf)BofO6(U@n4He*}tV(R(ZV$T$UD*EA@+-6Ev6 zI{&D+1(jMH1ak7O1IuI_#D!e+jBo)&>i%Uc;Z{U^(kw|tLm?|1FGlwXn}GQ5Z~V0~T+4wr;9 zs9y<27$$TRRXu&c*>Y87kI$U}mp<(o$S1*GGcwB#mWzqY_HZISdTPn_ho@Ov^^9Fj z1PYc68Y(6Ua*aivu~B3X21u}}*hjokkO%#N>$;kD@MlSA0qhe7WrsLfkR{XpsVcj= z>2R5u*FZIfu>Em(O&nY3b-d@El(leq%k58KdD|MQ24*nm#mC8*SC1KC`esH5Lf#!B zlG@Q`z`cO}^Sl2eO12hm`DF1O9rOBLbpI|&rlh7U;$m#$ z^p8}svCY5g#@RKvi8pSw%r7-T_V@^ZOa&{=3Q_ySIoRghA)HX!+ z?#F-)3;nJ4sDaa2z;kl``t<(# zfu5rv0JH$o0OH(RaI|ig?rW< z{82ySSyAp{+2FQy&A7giAGph`WshR446dfpsoGv|a;EVTof83-!DYR0w>D-qMVo<2 za*@&iHCW0N1xx6TieLXtJH?ztAxIbn%a*SPZVnkPytj6Vc8oOi`d=cS1K%IVv!u`- z?VGoT3+{iHsf@9s<9A1ZBdwi-t=)HHp}DbR1)7c=Hph9U9s&+sg(TW>2f;T5O8vnz zavO4{(<~*Ik&7bZh<4n{iSELePh)(lvAUC-2;q$y_L>pb=EjEJYf~MStWMpdhla>a zp&J!1Ha7P8j>mHwJ70@np>2km0vA;R&EIg8XZjwap+Rt7)WF~$KEDox$0>1y_cmtB z(hd}~ivf1ddFPsI1;r{LRf!PAg6{n7!l8+?qBPJUi%Hur$YBct&0?aHka_7pb{loj zbwZ?c{)jj^vvctC=oExoG>Zy#Gnt8%2;FMtsLLP zzS$EvvQI+NQV>Cf#JCBq)Rn|-ay%tgSe(wWE)B1*U@d}C3nD3=BHQKk&hpO6^Np)f zF4Fk;`H?ElXL)xsOW>z8uQ26R{Z~;_T3J;Cgz?V{hFM65MEemR759;`LVJJ{MLK6G<ugoPaRzlByA>pLyzt6P1rYHo5hU-;i;NUT>Pdb*CxMJuS{T5nOGwp@ zvO7hq4OUezEGjSZ#1x-;7MkFrDTmDW-DX`@X}gA21rqbaCv>RyzK$n2F#M?2d1m=EXKAGxa+;Y_l53jxd)rUl(`uEV zKMYN}4NZBUW=*BQWysmD4GZXdpP=P%VgIfcIz|K;dWnSq=CdaG!#hZtbL3JGK`YlY zv_8fp0uK?6J`qGSLN%^CPixBF0@8g|x~GYp{*#@+z!~tfK)rmx)@{WWpvcX>qp#Qn zVN0gP$t2xCl)aIeG1MYz+BSVRy8|LT5>SUaDI#E66Y6Hef(uEb3BRGhXh7IV+u?FU zMd08?Yi#0?KcQL8O=+s25Er550uivOhBD=aMgHCMHj!E?ERD$Fz|BKBjw-g}HG#IJ z*X)clX^lz-@v^O{6xLw*cSergkh7fb5Pq162nd`HQb6uRh(}Kzwt{aHFub)^c8SiYN^q()8XuzVl6J*_Kl0n`8ORAdv)OX%L1UT7 z30Pj!c0nBg>UiK`JKrpKg4nzJ-`Z%0+4NldM1C3MQ|wI1Ck!1!$reCCSF9i~O^iHm z){N_09wJXKFB5s?7`>HAMW=|@D)WWJSt1de`s0P;T`JuDyk_Aa>B5+#`f3Ed_y=i? z2H}|gpy7oV04&)c+!MLX!~Pha7{rV-G;dx!$d@a2X4*pgV#ajC2gPnOliD37_3P2$ z_9I*L8VF>z91VymP7;;;R~#&|3bLEe4)YD?eLn2i660sfbb z(KB%PFJ>lFSw;?v0lwo&t$d!#V5w~dq(4^@qyk=^B%u^haeqTPAl9hLijLsp0R}gK z%--W%@7rnfnj~#q!>sXe(5RbO)V8iur7{(s2+qRvP@0>CI(|cd-Z;b!qxnJ+QYR#&e zg?UVqVOo#nnfBG8^A6;3vz!e#zOyzu^r`()tr5j{u%9%||+EZWW zn?c;}m$ELH#fRXWf$%Ka&(Gr$Ura=HB*A$_8^v&gIN9lO7wZd=bW(lH8wI_I1c)uL)VH z;NQQs=SM@Oong_-zLK>Nq_ZN2Q7L}lxPUp~4X}d_2Q{1=+sSux#=|2GXK6vjO4b=Y zCIJfKwVcOsZ4p1>9g2vd8}d{r^MHe@rNHGo;G! z+jQ){zyH~U^iBRUAq8dIN%|6+v0qaZa$;qQBT^IPiei&f)RbetsAxy&shX52q@|Qb zC@3jwW|w3neo6e9k(8PorJ;~s7^jz5I)Z|O4}+JarJ9_Ws*j>%`N6;p6gN~vFgpP7 z3(H@arstfQ?ytD>pEECc4ohr&o4x<{;N|~(W`n<@D=Yb4>$M*+ombQ_=bI!U)?(ne zbs!qSxps2Rn948|`zcb2<9Pxzm*>^lG*D{L_e6;M#4pbyF)LVWeiJtXY8-}9&O*-o z!-iwgaqr0!z=q_9kLIk@PLPj4aElFibg|L(@PpOu!&}=6V|ooPCC&(y@5N(hrj?Cc zzDmL+$+8+Xm8%~ohYqR@%ispJ6x6r|DkIhm%#tHFduJN)D0X`b{jUjs?TtgZ)3VOLA{ii?liW;VMC zl+2+wcN0n62#VAB>Exh^0v?Di7(J{9Y$3Cnw-qO+zsE=v(uVTDA9A)JwXjXn#Pdu| zEr#yss=sA=(<0e&HLkg7`RMyOGDsaPHPQEb(i$|UId-_%39N*Ga18S$YbH*TQj|x;n#6Hh|P#}=rx)@nH7qD`j;}$&WLj1?F9|=!Nv!rTNO->h*+!FxTaV` zos@~KF9w4W26y*UFQ^K(ta4b=$nVD1+^4~IP?gQulc9|yo-;Ha;|C^QC9)n7iCJ%Z zq0cXVuU(uMfv0|iSXv$+fv=iqB&yShEc!ltGeDgLQmGDHFlWOY98sbU|kfzj|EM&FSBb#$9rio9BG#bAp+9Wz{aC zP%6N(UM{tUPq#@r=T#9%h9Zoxr+a|YIlJG7KhKdb6(!ETB!;>#X2$%CgQD|0P4V@w z4u*do_aX-ipZ#~<#QU8${qOf>^w)hwsjOKOw<3B?sUW1Qh%#Y=ylwkJSgK{EE~sWX ztuc-01Nh0SxG-Ikp=wvu`hPrS5VdQt5ku&HuR}yTa}WP8VQ5TODLx|$T_`_Ja6`4Q z@SiKksJQb%&k|!Mw4~eW)F?%5L{@Kh-!s$K`nZ%CY#u(~8y(kCN~5dLjAb?R=1YFM zV@{{4FH+X7RE}PRHM$iwXS>a}rj{5lPjnJM>$PU(=?4aVqB5D3h1m_th}m0_X{?Pl z#A0ssZkF9BB6X8mwP9{lv+Qp7EWj6-Y(NC&&?UytPg)evAg<2aDkH`<~!luG}AFqEK5=Is#dPF;(K+rK6c&#BK4yb|-a z-P(J5TKFDkd-SH9O~pR&>^Yd3?<}P!iGVe088A94jKc#8NNR<^B)}Eddp_^|*bZ&VY zvXF1tC-x?pA5y;UNuJx9WXH?*SnP_6 zSpn0|z?R#+WzNX7PDpy6Grl>@I8bjCsK$3BpF2QVlO?uZa-4Uto8qb&BH^5T zhcP)sw5vQ?U}B(VJmMEs6K4f>75~o=&7|EC54YcX2A$rQL0iCr2VP3B2lMLL=4}h} zLXu?bX8BxTS=^X@_tGE7PjiOrKK!_hm)=4xBYtV*Gc-)L$+AqWg*FiK6NoX1hT=`Hz^$~y;kSC08sNU8UAwiLVJs3 zvYb{G!a*3}(jmMl9$P+)^koYNC{i_7>@r1@=)hX$x4SN}x*PRSCg7K`cd`I(`@(TR zi`(+u*fJ#9pMmY(NU+K}8Cs1cU8AW5-d)@N?S8j!k}C|9PrJF=eRW~H$5qfk6uxZ8 z8Y=S17pyDUFFjOxOFwJUJbJwZ-_qnS3ii7u#-s;Q{h_hDZUwst4bj!~yz+K`t{)sK zIS|D@*O2qnCpW!cV$gq&=ZK>796gO@@|JY2oa?EA+W_PNHY3VJS!HvOCU-v5b|#W( zR*!1707r8s9DC4CI;N-_Tpz{3Lemb7F?EJNA`YgH%$1N>?2Z@9sn zpy`OK!j&*V2dz`epRY6MGGCRhp8^0PfhWp?IgQlHmiv?{`n(AN=h2X(tJay>{9Y9j z2jn)fd%fr|%neLp=t!;WW8u!)zFp5i204o(4Fz*>{H`a{RU z=-nP;k@I9yGq@?v8a}=!TcEaFtu%lw9%7*ZVS%K6#C&M@uF7xjk{>mj|8Dva?N{NZ zlBf3`yx5S`hK=N;cJVbc{qP{7;U?Ju_4d3DoOCXv10Nc%{nJ1AC9&PVcXu6heC^aA zMyq}cB!=G}z3%fDoBNLkvl{fD;QEd>NWlMv2Q#w#%Y#)aYT3;HK=3?L`MKNqlzJ#X z0NkbkmBpX=0vj zRl4#$gxbYqex$Y3J?4IRzWMg3&n#cCoK@RYbn!Cbls*&ju$10zv)=vLg*`QPYFj4G zK7M_c5AB2YYI_Rw>G?{K!kK|`nKW589$6}q*+p+h+ z1wK`ZsVGj;nlDpsUczR`dQjE8&3esew2?FpO%L$$=qWlwd48j3mtNs(s`OIVHl_uH z?0>s3nno7$a=&F1b;MGrb3w9pBfSQ88Owam112;b^(7{RffYhaN62YT{_0-_O)|XW zfV42sv0yN$2f$|oFVxZ`EqOYz0s-6gKG9x(I`J1rXAEU>T#hkq$LE+?H<{b>Q%8|G zId2MJ<9al#TZo|#RRBaB1{|p$4K3H7TXu(h*!X*=^SceEame;(YbtGv4@YxqUGg4j zS90YYs}18TUBI&ZPL0ynfyNaIYyMXlsymKRR&7nTNZbsRfh=`%9*et((-Tl{v0ck!gDdD5M`0%_nMypuO%D{<%D z3=g9IpLWxr zLEo|WyUOA-LsRL;G7M2ir9#oh`ZFgzg0VDCp7{a4I9rLI7-?*<16)$zEcLRtq<3_8 zpXq5;qsG}ySX?wIX@o&mYH43Y^@95X#3}ZaiN53K4S5Fpas<${V%z`<-IuS$dGD!z zZ!yh5AqU{7HbY@hO+rzxPFFx)FD^fQd;>7CdDVM5@=D0t?-cHeb`85Mu5R;@$^XP;?yjh`~J%PnhEd@9W~P<%j`M9ZM;B4 zfC0c?{P5xzsP6f8#}~La$(l%Jqr4lZyw4u@m1a2IQ>`~e40Q3%WqT|fHKMlGQkc;d zWTpMW{OB;u7v2{hJBlB|y{Eab!$Wc5w#GR1H78{ngGNaKi z=vs97CcWWq=tcOklHDB57-cwTevm$XGtADy1{-^u2J-k(BKVG;wgFr+j>qN=84KHhM8!lId+|y<$(Mw#yTmw<%$Mm&J2S;8L`A_(M! zo7;HjjxkYi3#vy$vSx2AC1B+E7bHr3 zQv&}VZK`_e)$fO#2rn1zn*^Xgrqa=YzpUM2>1)M0aNkgx*)bu(5Y2l=H0KjA<8T)@ zTC~Aq+KYsmTOBw16bFSyA| zt+|AZ21gquv1GOE=fYvNa4igUkRnnPCoU{GJNr-%Ky2dGBap4eP82@fIz01Hp>uM! z8dVduKg4!G756`;cH1^nMqn20`W4M1i1tKbTnb7sVPb&TIhT8hZ!6zS zz#F*N_0RrL*q98IBZ$?qwJjb_u`tFq?2a!nsut(Ky+_HS>6_Kq)n3EQ=6;ci?^#Wn zp4-lYVm$(R|5t2lzrvgS>zj7X{N88vKVOIO-)vf@vYgH84+QV6>d=O#BvQXXDQ-uz zC1)ir$m25c-?5M?`U?Yb28(q%-P`rwnMU*FVEAtP-OF27``sJC?*)+?quhWOGSymp zlQNE>4ffVZ{c2*1h zrc08`9_K%I7g%0DxVEfqLsD68-&%rWhNujT;Pm(RM22{!|6HPmng=pO$@r6}JLxX+ zezGl^IFvFohzdFC3pp|d8%&!s5q+`}*OB?j;34<3%iZBVy1xiaoEcG~MtYi?#_ZVJ z_pnl<0|FWi4EeL^l}k*UrkG~L^F7WkIOH@FEW z@<0D^4X_3)a(^!-u-&Zzh$JWxiiT^VI^@05xv3{`eiuDvM(Rg3234e5CGf|58XT$+ zM1-O%@HRc3iP)Y6c!$G8kj_M*YJ*hW_H|!#;wWmD*+?otcz%40*Z|+HGA9y#YvEq`??sR4lB(VUY0E~SMujUB2pOF9<=gV zL}NVak26GM#y(;^|Vw2XQ6Z)>B6qY+dh%Dqv1yT(J{Vywv zo|kM&R+r8G?rpnLfGiOb8Z()O((_iWUd>)64xoVMw($DQss-d~XvXMinDT3chM@5e z3ib9)sZ|Uzse@e+Ev`6!J5H2#T0W*+H{L_)JUu>NXVp}`5BB3&rZYo|04vWvo3;GP zWf6h=uZ|m4R(QC}c~NeSlK5G7(#1eFhiE=~RYOqv+@V)#4X3azzi#x*zA9`^Ruqlv z3jl*%SiqCeHOXu0@@2Y-*lE8XxC=K@ccwMh)49{`BqXBmmy%iIppGcg(nXOTrmd?P zhD%EclgBkEfepzu!opz1R$k2=`_ybdF1RQjj$2#TSPrdMB3$54mh(>W&WUF*ZPp!i zpI%S1x~q>IAxSC7#3ao+lV3q{u6|YcbtMYo|3QVs@-K6lDg*3A0*!nBueV9UNY`WW z4V8xf1}crs{(?#=IVqd}w=AN9vD|r*dz=dl?>AQssusy9DW}UXK(5&rga|7xef04< z8^3ly6ebfBBO{{|A-}eHXN@0GL?3q!kHR~u8?nfeRMQjE@&cpa=w zwmaRkG+U*b9z}OE;{BA4{)9?^55<}zGs!^}Z?^PjGofF&=aTTRX<%{a@eh27g$6l% zJ?R~OFK%Ryb&dx^ZoD;f?k2&SZ&`~1vRo-cry3DGw%N;b+#fUy^%AHLHIrlB=leb& z4m_q+`ZjIlU0>6KzY|%5Gv2B>3{W;YtJtyJ0N9J#$vMkzs1JT{KCkpih95jRiS_*2 z!ubX2pri+3bJ{GMFK~QKCb}3OC!N9~F~cwG!dTRxJNzvOKDa-zq$Ugfo?d-gb(fu# zO))^&Jr?WvPOT^Joqxj&?4KgG)t;!S<3QM zhGUrJCDxnx;_5%W9K2A#@gUyD}^`wGuOqkrGY%K+ahoaVm zJY1(&3JC>*(9&*jv8-v&L_%Bc_vH~1(^wZir=7I#$+T`0eD|z1VcAW*-k`0>HA7jC z+{ltu1GO3droav`Hs9k4T z+NK?l!6&GB7bM)G-J6e(FynjeIY}isE$h)BLf0KU3bL7GV2l&m^Q=P!`2(kjpUy1X zzGuI2LG`HIL|G%AX?!#Im))cf3oNt6k;^Y9%xpRb?brhsev zmwk-%#_Bz{`k7G)LR-krLDxE++$z1rA7L9n5^AXABV zcl=G@gPehU7Bp7Z7xm##lXHGU{&N=9J2^)=4&fROIc$3p)929@cZV9jh`gjq3}Z7@ zLp_*)>*KHjJV@C>!ZnFj-e#L2h#gRlF;$7YvR)(2J6|a+IQFWIP=z*1ZFkNfohqva zUICdEyO-1UV2DX99ljf?bYA=0v9OtfOmIK!U$$ulnJi{}fz|H5rDRlM&$m;^OToOt zZ*mjy34r&WeH1$-(p<_+gmB2mKvlSzDrgV31dLyL?|XIpZLIe8jVP&jeUbl^KTisC zQR+}1HQ^|`nR!WA@dmgiGcDL*mBFz$bFsqWv2Y%=8t%fH$2TTv+&hgA&O7xc<8&Y9 zIoPj0>8X&Y@L*Vn!v_{xHX1cc`vupv{0z{dc~1XT4JnqoTj)M9;wZF^V0RomIXr}} zf|{kFW6_*FZFkktIoJk@qz-AU<-Sg79?DW&X}(|PSkp0&GY@NPAf*>N1X0KWgEsmu zTu(@_`?hapc{{pD^)TK_xcmdr{S*A`b{1UP_sRxtB1tuE*xtOJUajvA0V zSyv4_G;SGmav6!Z0ijXk>8O3qvscH@3g)kX?*8}JiB?3N#)>@1z6;G%c3c>d<&5|X z!_~wWR=w^K;xN6Dpwnm*3ivRtYa;~Ysa#=qxKT;M?9I_c;J)tv1dSXhIQGJCQitRl z^Z(~q%=B-(H;NvI>idBp{Nz38`^H+tt3IfdOb0^Uf=CiJ2VNj0E}!c|pNT@#(%SlK zGSv{a^+a?{u@Rg@kh%>q;iSWAe>=*F+%{C!V+(&z6)KEVurPg7&(IRJI(+!UsWT%R zc$_`mUb1zi*O3G>O1{s2hH^fT4CIDqqf3aq2mv4FXWKRW_ofHVy`Nh(EKF0tI>E!G z#H3&K_EzgOr9a^CeS^l67XPa}7*-)yTKD~f1^-R$-}LW%z)D`vX6^^V$7FTrz>^cA zpsCO&0Bw+5TW%4GlwexM{z!+mXWZ0@nD>XJPO!RFwq>_Tcl*<%O+ANG0Mnn&sWC!} zM%=pa3YVgD9*)lx27c!&FBgvV#5NG_04S+pTal{NPD*c^D{H?6C16(EDse5L^d>fH zr+S(8)+W>(^{-wu3>6Dg*ZH^mf=9DajvJLIQt%xh zec!vM7mBt%w6=>MHAC*S00fGgu}ahq2)OgySz>enMh}=oNj@V_zc)j-3w;Fb%VIej zF=!_srlQv!tFMJ9v_c)DJ{&|tmMb5It%ky(^W)OmD{podzE0=Ps!ZbVS*PV?pZyia z!TVI?UlvM9byw+*XUg!AZe{+wi&ZIa0lFn5&>L~%E~nJtPJ~V4gc)${vbqdwiY+wE zJdfEfW~J$z`Se)<;%|d?0zTSVa`o#~&EaS>F~W~xtOcke!D(mP>h${0l@KW(95+Jj zCk5*GE`h08MUz3q@6G}&_Fhr=km+$nV*(<3j=;C%eaqsQ;Fkzb?C zFm?s{PqUYp66D+bHhaZ?WA4|FdfqHU0AvBW-6Y+RTMDnah&(q9curII= zn}a{GlX6=vWI`N7Avr6qGk3PTY%3y$rhkX9rNJ2A69-E)vkS}Q&{JBf1am*E*!jvF zWp-|wJ<`|;LYBLnc|$}S#FcjyhjpLm5PrtK_@bl)OU&UcTsIL~D%RLa|j{TE1ltWCbMe!msSe^ZPw z`y%9vxi*m$YAIm~%c|h8~L?h3L2* zCz9Pfvr@kqJGOJ~MlF=|%;Vn6KzmmA)q-PBS8AZVV||=SX=&CP0VW|MQwVm4E*`fT zPn}91-Pk`qok%SZ>xjF_Qw@_!EWrCZ)y|&^4^`|u?`hF$WY@Ms_>&K^Sxr?QHRaQvjr3y1)}Du_iKOOP zO5wCE02rzBWQ1L#TWfyZ*kawu@0o0i1)AR(RdeIL1F!(ZA?jw$tr zKbk^IPjFMj!rhWvPu* zn(2(40zCr}TBfqJ@`aK)nl$nNv*F!S*x<7HjKTUY%vw^GRlS4!#2u%{6N-Jmu-Kjg zR(In)FP>qSPKW;0MulcrI@RAzl8FL|@J`XxrrsmexUrnnuAm_MRyguDVW1X)LKO8} z8#C`mGvWS4XBNpSgCZd9%2V-IyYIqpFwxF-J{85YL~20Y)r z+J)N?J%>(d#{ZFIY+12<#AhtJ2H`eHuC_d7JJvzGTgmf5!QV<$t&YJM(lTycPEb+h zn6Imz$pa@A3c!&N^%=#Ic zOlbCvSC#+9Yni$Hg;`b#QZnB&Hl1(OX|5qapp?N1QTPlcxuFaU3~LtJKq^vA}~${XS9sFoqLKxa*DA}r6MoRa;3Ar?22e_OL5W0t^) zDJ8&h4pp_8oQ&^&pswFrVZ+_Q?17Wv!yJ$dAvnZ5B|tL_4MSku5oQgCN(3h5K&)8A zzO)D&BE>c5NDK(z*AwgaIs}HcC$gf2Quk@}Ue9CKz=0?i=J3Dv9ha!)`RS;SWF!|ALAi#1N3oZR&AuLKE<4 z204q?7%t*9Ud^}p&x=DJ`O5wJ?ygNj_!lvj`QLn3;&(UFIxB+Ll?ueMh51<>2`Rd_ z7!H^C?-su$aan>Me-u$|R&_H;vwFLLcaKp_v%=0kuehasO#3R6;BGLLhufL75I5|S zVO_%VVS?)NMXW}1i?w$=W`N!ft7P<}BMB;sDFXUGb$<1$ycu7NAD(<~&}rlJAxdU3IALvM5wAbY*RTMwFg6+fDGiRwj(ORA~7n+>XkwZ5K zGsL6DU8Yw>no}|b*th?Mw%%QDSe%kFX)<^EWD$*s9ZcBy3wwW@_9=L_1 zZS+4qQq21}@TL?oY7+Jr%L;C$wb}2gQDjJQPx^Y+)uEL+LleaVWe^gH>~Wk!ZnUt+ zdB`HC!oE7PX)hRA;gUoNM?x=cX_PP@tIhG|4RV7WK`N72%Btw$92gpY%)fIz!e3B| zdu+%R4;-+?q@b+21yL@Vu_Y@X(k`n3Zz4>}`>pAZ1bR+oB#R_H^q+4wxSkC$Uqxkh zB4^zkQ{uR6A;MbgZMbDJp}xY(Cn3EbTe;Mf%mCA5iJ^;5mWuJxF$4&m)cV)ZpE(Ja zn>U<#9dt0i!9+QvfET1vcuMrJZmY9iJd7<$#^MbIg~H_&J(Oj}=+fLcpZ8j8MOpRd z3Dr=G5*V4?+DVOf5y-C`xrTUK7oOHKaR!4@Tq?Gc^wll$JyK_6s zErQ#Ltd#aPFa2T*cO;a3GAWu~XQ-ib6**q4iKYp{*O$H4cXg_{dw)axin;CODw^hI zI^FJLx<|gSX1Cvv*aZ4PvN1V1`53sSbm!QfrBogS^fsYL%kBGt+2Mtl0nV2Pe8#-d z>U?}iFLcc&}7YCZ;_I`tAcHqu|a?;RF^(7u83xy5~=RrQU5C<>#VmMxG0L= zJ+lwF^E5-qT&k-+I}}Bh_xddz@$`~X@*TG^njE&J)T+~0qAJnZ-QNrmR*QRs%^~e{ zuC9JZUA=o$x0^B*8S!LaYKCk_al!SxX>=<5YV2b_{T01sB2^C*^@>pWiaUF>E7>j% z@zcrqVmn?dW24tn^972krmjGo>;5W~{mOH_4a#)7ndpX-OmE3JXZ@6ZH1=hqK$zxQ zJjoOn+JNd?nH<}F?g4qDMcWr_s34i8)2hnG*el6l2m9BM+Hx`mi$&6bIl|ASOf+P7 zBn6w5uB$+bMg8mad>Q+5$HGtN>iHRu*dYtXkV73c+3`!fu0@^+>o|Aua1ob+=u6n` z`j{69V{yX2C7b_g4XT&t4@v)Sb|C*Z>tF$C}K?!MVhWxdSh+CO37 zSUDGRrozWc!-3}uwGL-az#-3DHYP)GkdPf99dCcmCz_eI&aY+)w0YP)ny=&B zLzC*k7ui}Fy=2WkDE`P)>gKrGu}-fRQK=Q zwDwfeNchxQ<~c55kXK*qF`XyarTW#A7~F}dVM*07_$@Om)W0AOs4B|hq^ASWcE0)` zR=JcK?@Z+f81R73Pr*7odJcY_UUM<0U6|d?X(@5>$CxKv(^bkG*emU}p^;_S$oHNV zZ%m$}U4BeDqkIm83E(rjG71e2%jAMwSZNV*;syIg{eVEmh!!6XMz+oj7`y8Eqe_C~ za$oel4LTJdnE*&|QZ!?=fX+gc@YLViK%KYyNfz@*{ScpQFwY%h6l*Uu39F`vXz)jj zuYSc%0^%Ic#W{TXh)zQ?bZ-|&L6D=ohiyF=_wVTBbX0$&ff!gDl+V;eBQLi0#;*3r zOvN8p=%MI11o}|z`{(#huxHVI+nh0v9qT=Hm$PAUfSC-yTgr;cuHsZO95?muT0H3P z%KI7~Dc)k9kGN?)faoBciteiC0Fy?0QyMDO{K|?KMeM$5i9+$j(FU4I$YODv)*)7WTkVC z)E<d?rgLMkOm?kpM6`5-Iw^;Npq=s!XRAQIaxT=c-&olixLpEF!bC zcm_JID=0Y5bH5-EpjS#LF6yU@t@cp|gckeqcv?^q*7IXwe(^T3@;rV)4Vxrt4vL(? zOy$m+IPhu3;5u>#T}jIrtMP)Yet7BYHE$P!m0+Bi<8 zzM?_NXbQ!&nX=!*v`rq!b$o8H?l?T*#ai!2>XKN|F*nHeh9DLcMERX|^$h9mC)_D2 zGT|*5`MtU|CdX@se;~G$F68|@OsL^qwzKHA@Og&bBHrC0{BeUj4oHCBzM2;0G@q|~ z3808l(_J;MNi$X08Qx~oN5{C6&w{}X0X7QQyEiMOEbll9Gm0S5*-!~-t+0Z!(1o|O zQ$BOB#|b~n3f0P4*pRvfdHF%5lQgvO99pyelA8*63tF+~RnNQiXc80!xustvUL=z? z)NW$YdsyB%p*mA-Egr-y=rlXmDFQGh@ebLEWLNV(U(nfi&<7fnz5g-j_^#5r)`{(%}bk`kj5&oK(oApl%!eXH5(ljoM&bt=aK3oSs zJq}AIn{nQq9r%8z9q9iW3_0bM&}90?Vy%DEcxL%GGxCkaHfyZkrEDDm-D)8u6V-si zZK2%)wWT3S8uCG3z@gE$WfQrA9(?Edysx+Dg7cBo8FKlKnBn#q7yO6sG3ciH22D3P z=RClTu%MYudCc$LvrgMk{R1x#H+OZxuu~B9aP<)KAvzf!%C5=7ubkgZIjmm4A~ST; zcs%1>O#NnuG46p9J8r*%>u1^#16NJ@mCP!Ve{Wx$t)g^Z?6Vw~-H}k$JIy z0FT9{-~gE2|M`aY;kOb`SNe{QqhHM-ibDsmd(m==lwmGCxNw`yeyvp1l$_;= zu>~)In$3jr)%kSMh6ySx74dBk%tUvd0b|2DK=Ncy@1!^M{@;`V(V%1h?3G#oCUWUEH8x2)bjb$wzA$E2YZ31TuY6rE!4=357Ji*_?6 zoLk&iH1H;Vfnq5~jIVP=OX6WA$W$l~r8gpC*LeP9^{GA9 zw0zorHTE!-t0zO2v}{I3GGd<@K6kJ2o;qFn>2yl&vC2_J}2$20G%T__#*QS=1D z0p|{vFPOBnw0jy`1=J|DpRonY0Qt&3B6pd3C@AE6C92GKZ-OOMFR2Pb3wv4owR4olCmw9(_K_>W zi-W6Tdsxn{WxT3$BV1XjED*R8(SA`+nuqe`C-yof!W2vI3O(IOu=ihx zNn7LMrV5}^2nL@Gk2J>On~sYaff+|w^_zL*QZU`r?bv=F@^-lOOD z_encm&u7uE&F(jT_cl)!{aVNU;dAoallS*`n5l?wdJIp0E@AZtc>~d}gP-lBY)yMF zZSwJHQ%L=^Y}i>YBaW0blu_Ck%JVQeUi{zr;c(E064AJC^q+)d7+A`SHFQ9&#WX}^Ge zEjcgJ|D@Q_B|qVX>l+oObn8tlhwOQp3bA+OOMMXS3`y@jX7a*%V-yJc$#W znn_M?m-%Dw!+7F-xy2h9vT<_W%H{oa`O!YQbaKwOK1=VTc!Yk0uBTUUGUB778GIGO z5DqQVcEG?VvU*O2#u)?A8U{?SSUo?qG%E#q3}GF;tBnALbOa4k?Od zqS_c8KeV2z!U+?yq)!(F3!8MR%x`0~kqRg)hh=$< zD<+E%)cKN=C`PCNdx3^|)XkQr_Pq-t`?P6@w{9}^$P`Z18%B$HXMmQZ1d&ys+CPV) zo7=%y66Mf7IBo2FSTm|mfP&}yd@QWKCXa$`N`0EVepF7VFOxbw^C@w&6vFs2`ou|;EJu&o;Y32@I~v~%(PTwClngg`O;+RaqjvcI^$&ZZC30Qw@wsYE>{N3uWSL*hf#1Q&JW>oYcA zdvk}-T?L1JzufcD)~eOyHIz~D%wcW z6~+so`Ma%1;7$K4_o7FycLSf3^imG~LM|C<%)r*pp5HeFZ4H???*CPMI4#}TQ!T=B_w0^c{l zzScQ&pY^pPDcD0GZ+m?ud?cr;3W9~nV&=LuS1 z)Gx@Z{p#Tl+Sdm*zV7qdjl`d60P}aE_+{#u3z$IuwpI`%q`!3f>uV?HU}zQkqj=rX zJlCMDL4yhCmnK+va!?DG)8R_DDk@*~i%G{koj<}awR}LgG#e>kg4KSDU7y%=woDX- zw#}>AwEVGEfk9GJdn(E~l)#xwU_o#%jXUQ;dIOl}h6nOm90dtQU9nYl5i^dVBr@ET z=xnUl$cI)B?rfz^p`YmgDCOO(+`WM6-LG3jmZFY!iN-%mODOrCg@i9bG}OV*W2Qt` z4l@|>>Q3S5nC9NV`S{V)bOM*=@Jn3qFsopj>z;$;yr@-M@Aam+u3GTG{mjfZWGlxU z0bf&mwxVFXtU{{9Sps7MeMU%B@wa)G)c^q5EKeih^(cYj;K}`uasx;WjT;y`xKfR4R551dhZpm*ty(g()cX96oyA(fcOlu!$vhL3Og%YF>CS z0DWtsu^)i0=}dln#fv;72$0WUl|)*Cb-hJg|97~sqW_y&`#YU7{WmF<)!&0<|My0D zzBe_9F))M14T%uUl0Z(d0@N$~*I>cV7`c z>fa(l2;&@jyk00TRFCJ#={shf_Pbjs-#TnwitwQvk)eX6&*&hK=ufQ!#e7*@os1<< zN86&yjJdton4L3MZOc2x-#4;gaq2Yf)R11qr#|EE)n}poqcn#V&o=o&8b?{bpyy^M zwUx(BM!lHNJxevv@f!y9dvRFE3_$%|4Q}%QKBv}c`1W(ss>i_3EV;=2Js~bjry(~t z^O8F=?|0gG+EUHHXMji@#|nt(Q>3Q6E;h_yZxXlN%z)C4*n}f&&_=RAVvNGN@p^SP zrR{Cu910l*H9h|uumP^h$a1tL&OSvT=D-NOD+o3^OO(Tgi`BIEeDe`gje`XF?2K-) z7lwAimb@XrOV}6ftz?Cyu7=yX(#&zR&DKqQxRA0@!XBMQyCW^h zF^QurstGOhGx0q{4&wHFTfibK6dH)yY6jq|sF@&|TLP4a==Vw$NikHPMpYR3|z0k-peT06A<^)-2FT4HbK9Wi@k(W*eQwnTa^r4t)C1{;jBVStlO5Z(ZQHhO+qUgw$H|VJ z9oz1lzx(1lr>d*^tLmy}U9GD%pLfl7&N0UrbB7l|k%SqKuTICW6xdR6Cyc_ANUwk@ z%yT)kJvUmZggX$>WXdsJZe%oRq~ZBQ_CWcBV9tP;vnXQb6v>tPP?w-(AFidj>vbBH zigoC^&M&;BasNFFAE*O?Rl2?AFUw`R*by;fshAFAv1oMtI35lTw?eE=dVc~B%8p)N zvrf?Vx8lY)>~|Ok4_2(NXyQAsCV*$#j5^}%H|J%5fmhCOVztrPUp$+RgxG9#0#EF} zoBm3L>jn}0&TkR&g2!1R_R88*0+{V;h=O2a+ti0;$z~TyKs%L^TBKOG(ruBHbT~Fo z&tv`Ptl|nYbIM_8J{Hv4+7N~TtjdN3%HD;ZaFP4U_F=%V;`I7ApmS{m^xohWlCC~7 z>*oCBgQ={>30H+L>pi|Qen)!Lo49kgbaJO?Z02xJyAuOLFM-r&xDhDDkNnY>#Qp5- zoIs1ze}<*`)7~XY$5R1&NfqS5_lK0EaP>+mAku@w*4D~a>9o7|BD_b14>D2Ek`#=M zUm3*3@-OSG_1@_iM)DeRrJ!}|&FmR|7M9$YD-+LB%6}9G#&P0>uZ{NOPw-s4o$fK4 z(QXsnsb*1dBI+pIQ%c!Lt&UXLu!24?hTZ0Ec3dEC6^wXKn6StHjp-&elOEmaql2sDu9!wsJ?>`#hVIn;Mx9sxBXQB6AaLuR6R1yK77@tr&FbDwN;F&Hhcp zSm9Hs-%&MJ`gcgQ5$v z6Y4=R6NRtp!L=6@UT3ybO1TZ!d4OQ*x;3WNs*BCQ+M{`Z<2v=zxKKFe>iW>wVx!?Hb(CIRzd})#6r2a4g{EbORk|934u}X*@G5>RSt_N1>qD8<@a9 zQ}$786Gx~t>=M6YsuFe&n-T?(e~I1MZ;vwT$Wftv=JlE5Knubp&P_V!BFM4>a>XTM zBCS}i4I~wi%^Dg8EQkkn41W<+KeX)Z`0=3#Kq3DL7!A(h%#aJVS`?p2oev0M9sa!$ zw5-k%Xp!J`!GV@W8!5FrKeTWhM)NWy0=#nSXeRh#s4+xg$UWKW?#xiDgzuhK_B%o- zd)uy2hka$uR+}rr;w@ZO2{LR5`zP`llL>@*UQXG>` zKBfe~sMz&POS70Yc_Z`0_Tn+PaYheYMZ9)w)zQ^pSwox&D{@(B)jxN| z_-{MuU?IqlvS!;J&ls8WhCQm5~(d%XDzFmUsExLjg4$v96U1xLJPw> zc2fp&7tR;qPYruNLPCER+?+QU8pZnl!fcoruL~t`Lb*^}DcpV=hmDMsQK@k3>zPh& zwXWn^f`yO9)Z9WUY3Mum{hl@3dcC>Sq>7pTHmpUNl+6FpB@W|e`vj_tV-+0X zX0kP)z(UIF+jb;gKypHQgfX=;RJVv)z+)uAI3<=_G^wW^>m(UkuO1Ck5M(P0TCh3l z^?l!3E7aPzu(XyKnpH9{S(j0yMFn%ZhglCOpiC~mj_5F+h$i$np z1HyKQ^sty)U}Wt6B3Y6~oV4DK?A#|<^89t2iu4Aw;R7$Gei3#(pRCAN&@e~RF~%^1 zgH7MB&WeywJ_sWv62hCl4(<0h2p(0742u*yGJ;xenKH8t(@<|*~H zE>3PBUGx;S3lRC_jA&C|UWy_irl2VCuF-zz&RsVOFr$DEs!TwkM1?@$jT@qgc^GCsql(P=v%Nv6K0mWnBoBQ`1o&t8+ zNZ{AT?tb%3>%+$yj#T~sb|iP5CL(1&oHo#Zqg%26uNjJp%ztz%KL&&}b5>Mb;|1l= zc*Yv4+8d2~Do7|41k+ct&BRU2xW_vQAyO1Ts4pzoUd*#v#C0USLOo-W9W8 zUM9Alc>x`n4zjpImDXgC=VFnHiVdo0QKLNK>!HwY|)NCFv`$)3IPGDjgQ7P zMP@jX2-lwyn|461Bka~_Jbu1lVU`s#{HQ>WlddVRkRsPRjgc&cGOvQSa1flLH~$4n z>MSFJ5Q5`groXj>y+zD0JU1Jz==BGWU) zngk~vWLa2LKg^jCv}LK48uCmYGt7Rwpb%=_}Q2R{m35KEl77kZP0Wf zQ823TuY=q`qZO>|8| zi~;;i!zy++W5fl5e4hTp`zLaM`#0+#^bEZw2AX!o-pAMv-POjC_A%$ zFrRLnp{saiV7ls#ocx2ZK7M|IG#xoy+_4iU^X3o8lOA5uG$1}@-f9INVw*~;=DsLx zX_TYbu@BI@iN~`15jZ;weYr5;W?oG2*snNNzD3&o$#jt|1Gp~VcT%SBR0-H75nd^i zmN($6DC!SmcYCM@gDLuM?MSwHvB~gT?6P-7zvT-6DNXmXY(a$x!!~wvSFW&G3#?z# z)*nwRc!Lg$Ub6g{^5#KWA{g+Z8XlH2{Y(lUtVqiKceYFKVM3($Gdex`8J+%rwiU5= z))Q3xM|b~^>8Ue4wmiqgn>-CDBS-8asKrITD-#TbzWmCFspy?@qGKZ3>yDe4Vq`Xe z2O{P6sjv6*mO@`7BwL)WU8{m}+#uC6#mY(H9~JERFgYTCaGU7gw*vwtR1C!G*jpeN z1v@w&jW1>$Z|H&A=2=h7$xU{bdxYNo54?Tx_umV)g_~H>(@}S)7j_IWI6EyZl641A zKFP;)fzRgHCIfpuvdj||j66ZX>e5K@~jjE&@Ez5UYb_2)hiO*?bjggr zqScMW{gIoLE1puI_V9dUI67-MXYhdB#Xw8-X>P#(6v&Xpc8^v&iu?l2$W;!;IqwD} zA4{1HB?kjGfjGAj_^ZfegE`xH8oMm?YX*DymR8slwXX8gqh;iso>SN`wYC|(d-iIk z84zqRzw&6+&+d^fp^ED~VLxp-x%@ICc-kSribS>LT14k?&_!F5*okfb>sZgehUvBa zrdIU!{wAX5liPFskG6c5a5<@3D5_Y|Jaxz_*jf*MQpxjRODoW~E;2ORZXTU%t-cER$)rC=EKOS zA%AXE$?N3#M-cA|&ZyS6)P*9Tc$O?iool#9`iL@=(Cnzk-K@-^ZG;ZT4nE#4Gm9YI zZsOx>b9BKfk%I;%{bjqo=Xq*)6TXAi4D@*WSuI;Zsx1O}0S~wiRYowOYy}Wp1Cr2+ zz~zKKllnNP@VbKxgYLaFo_J3+QnV^(gdlrSq8OjX#Qq8uWuXozyAG{4HiqRL$MNn4 z5au0A<2qBt4y_!XixW*`r79tU!{-5NU%if;d?#o4acpgJ>_~2qb|aYA>^dsE6G&kL zLUoj33!CrBpWUDRw6GcHn~@L6 zB3DSgJ1thIF3cqxI^*=llpj3!m~Ysy&pb1Thp;b%B6k3I<9ZW~z`fa#WiYh2IA^4j zr}2h6hC6{0zM_4gJ)*GmN4lw4rA4#5{nW&Ki%Q2;S13fVK$#xRnKo|hmJHlj)=9)9 zcZ%*0G_Djvy%rpc3?GEzByxybFzaV1xgp6j@+`3#QSW+v@`cq47)m0q?RlnAt(``r zF_bFkU3q9&g{G;dPG7g3C5*dD@U)G9b$fpQ-BcO;@_c*x0U}-gjcCa6znZEzIjbMh z(8%>WO6XQrY$GnGGsQ#!%t}hh1_n&ze&n!FL-cpEah`94(kk84qSu1lQw`M!>~^z` z^6z}l=AZ?`B6gX`{h=Z#;mWA_nyA8oP?&l1~=&&8p1a}8&<|s^I<_%rMr42XWU=NOXJ5L7L+u- zOydi9@MN*|Ofj(&d(@9iu5$O!C=+eW`@aQp1XEMAX}Bk}FBpTPUXU;5p=Yl(jt@r? zLLvjNrY%g9X$X_jGB5HzW3wSfmFDzt?+o4~dXq}@{yU2A*H5HIKdlbszbW?||H!wa9iC&t%nSMn-Xdnzn%LABOUsc8P1=r47=7*!_@t^G1GK#%0Z0SP zaH4H77_#Dz@}{ryi*^kEh^8-S*3v^!I^UQQIqJFl&d(8xvmTw>k4FmoC`D4N!@r{T z689{Hnw#sTEtnlabBOZ5Gk3bM0BSB9A7XLz)3tpKwDh6KQArl)cnxvvV+Fj-Cdch~ z#_myoh~23j*sn6*h+!k05x^;5R$@6Qro|*Z%e}!pTBG9pZL@>CN3~J0+_}rLrt0LLQ*W@2r948P&IoJ2UhxrYUa~M-8eb4@BNf z2g~9nO5%kmPCL1U0%cSIla(asqzG_@emNbN+L&@Sb3q2w19YxyMwky@JPz@}a8o;3 zN}x!u-&mM<399QY`;lnhiaP5e(g=g@4R_lIz4o}I7)UpET!gPZ(frvHg~F`H!!bqo z-LtXW4yHt*%g?4-wbN|u+I8Am<;1iKzJz>`8Q5z7_@KO?H6!=mk8C6OQfuzdAAjxN zOd*`C{!ypM$jCtlGT`()Q-W=MI+d5NP6!2p12^#pu3i)rb?l@o-E63>7c6>p_gSUJ z=VV)I&K?eP8jz_b-O5f3B%Q}qgR7tS@B|m2AaU~7q+BA#TMeo>baCHBAFoX#8CaU@ zcI@#A-?FJPTbt5{w0=nAF8?hOb?-IpKp=LnIL+GJ>yy{aO&nsXt^ltaK0)~+BFEq; zcy;LjmiSnC{vWvtbl0|i#7{q#@Nd+9PXELrTvYzSX$wi}w&n-pWs%KvvA`X2RkWti z&o0h#K^s{VmyFeU@Viz}&VCyM04BPA7x(4f_W4qY=~&~`Oc5~AQVH6s%(k&lZVAZw z7V;2Q37yl{(>9JTz%;NezvVD&>wGc!GHLk0?&%Hj{<|)B$I#%*0!wlq;f97YTePaNsOwlua1&!)v$7Bl$Q@@){PyY>@b4^mjAV zaaO&JF{|$Y(Sk_iLG*5Q!XYfWVX2)l`8?a0|CR7_t{-I5Oxa4nS^$j`Q`{`mUK-b^ z_zX&}Ae|~E=kOdOqd^@&0txp|4UgzCNsq`k652sx^+S>^MvXFqv*;wYvWOWAJq`-W zJLL3vBZ80z{#&1lp8znA8>}JF*h!FccX{=T^nM=Ak#@dqjk8)p^$?rQOSHh*lvE8o z97zoA_Wt4-rF&mO+uZXBtX|pVQ$xJvk{qm*jXD-yBIdJ3V1X zGrs3xg8;S8BFrc&8x8wAxbAVDh!ifZ0Y-d~3(&3!K>i*0_>Wd_eb0yt?$@fpM3&rP zQJx}bg_1aows)cImMoZ26jmsaWa?BE5D<8^{Z1d^MF#MJrcc+0Z@BM1j%UyXtmRg1 zLoCTi$eXLna1OXV;G#qP9e4{?j3{~Y+*@9(x7h}#&~vPHKWgXo*&f-YADH+Gy(r<6 zj6~+)abM~oym+HF*aiO?OeX>)>R~h9=DMhukI&x{=Ory%ZuTK3CI01HN&pen8pDQi z+=oADe>Qjgps16%N0b8(8{;dP>l2L&Bv&d9|Ga_0#w`h3_5||I^Zm~-b%jf(G3+OW zo&Bpc_D_S5|6fMGveQzszZ?FKOTRR5!f1s)6R5PNzc@04MKB4FNs!Eq_;1SorMzhU zEHVM>$q9Pd5DFG>DJ%j^lVEvULw^gz|M^)Y4Fs?MDaqph4c6`SPXe5d!j$X~148#b zYC`)WUOsp{yow}cTx&Tz0uqvAB5Av|_0)DVtX|xu4C(O1`P$?3%nSEa%w=|E9|d)1 zbwcNqWYI)LA?d+~%wNDpdLl0#43tKIZhxedM|%Tp9uBQOTi)&dDDaHQ5Jc$J6317H z)SZgS5<0*UcfIylBqrcy!&Wc5Frjwgta=9noKM`&x)uk57yIeH)EE(jHZgM=0wI6- zL?%t^EnOxySh!)ZNxL~+YQx^mdp|Fs3t&Pz>NzAQARvITFex1b2r_jwVyazd?xJPpN{22L{Ewh%H z2}D7t5nHiZ9h-Y^rsA5rGN@^?&WljV{`!5?AZFwssJ2TfiUm1I4Rq{9H_S^YBiS~= z=VANUdc_Z(dJ_>YQ#Ts}8LxaI!tDSatEA|^Olf&IfF2R>S`ya>B-=*oy7%l}G$lGj z|KG3JNI?!|!k>bJtOw|aV}z?J zoRbXF;5U0C>>qsfacJ=H`*iozwiAQfKl^ZW@ReGSnr@qP)sLr~8WztTmsG@g1!n-B zpGrn%wJbIXX%^hl|6SwC`{g&hWg)`!88Hm!lC%7f$#HBEN5!QnI(Jo(q`ZKAqD&wB0cAD0VHiGFB~u&^wjra+y- zq+XvQ=K_xlR?0Qqz*;3v6VA#Q=SM87R3u_j53Na7)wJr2A~8J=i>laBcQZ?How|*I zr79TL?d80hF)og2M_H`AZ|sr|LAlPH;ZRa?qEo1%z#l=5O9PrqUoivR*!e0Xven;) z9~@a_$Ut=Ps#(s?E&K^l{f9uNHS|< zbPst-zvlOt-a-bpJvDC;pReT8Rj&D`1}1 z>1DYbPLBKy@=va!@?_@AEI(_be0|_fv5Xu9@Z%=YN+Wb8wPkt7=-|GtqJe-?1n?H; z4~1R!4W+h*nM*dP_QlkZ^(2yo8%F2CzZe_~Vl1CKMamLGE(NgW{m*aG{wOc-W5`jb z#=j;6dRVq1!|`h`$0P%&4up$52CQodqG8?@QPWa(SH#ILf`Ea?d z+WVWRIF$zz$PnM5Xr7DLbD4{CXq!>{LidSrJ_|NCn?|msj*c@0NA2pyRICJ7==Qd= zt5fv)F@aUW_G2_5p&9teKsnP>&EX2QA;z8SN%U@xkrB1d!NeYIC)qebQaT+Y$|Tu_ zll++IQ&k}kNbJQG5j>&u+l9NlI9Oy^>Lh=QlTQ77yRhA*$xSy4S}?NcUfd01!w^!n zofZ|aKl+d(~DXMj84UT z>}d3s)%IWaP0VlClIPm)j;GK8QGeS+r_=Ch1o^?AqTX<~1^()wMS-K(%xbVF+xE$rgW%Vl8xiCmN9b~VMTurr9vu}ZPfP(Rr#FG@O$$;a^-Ocw8Q3aMM^k!MY;U5xvEkBahwzV&-e%7q#&&yjdU_m=Ovy(c7eSz8#IkVtBl|n>$6G9+9leLz?uJ^%;QL!vK^*RW$C{vHJ zMi#DO+MJChdiwE9Kn{gAT3tIcTV^PEiaAoDedJvF@bQs$TlPM|m#pVU$EncP)$_!; ztfE|JtMW)D-TLQCF|DP*n#;61VR0uVoX#4!a) zJrJu#tPCM8eAe!W?D;{T!TeKnIy4_UJDjI9*^pReRMbU)D0<2Qh3U`s+9yhbNUqTs>@2wc$hy>DL8A`7R1D0xD=}z-QLFK}hsNGv2ArDv zZ(|^_O|L8TKzGeh<5HB0ta+PKO2}K#vM6nYH3b{vUU%;rbBQ>A!le9<;Ccdw+@I{H z>$Nob5*dTFuMMK<;Pcm^nw4E%J^-ExbUya)9ggs;{yga~RrUH*tjLlPdw|CoQ&=#( z3VTdIe61!X1-$Ksj?V&sABrCSEuFIz2~40dlO4dQ8l=HNu!V9V0RTaEyN^9nFPd1sbP~b@rI3l9<;Hd<}=&O2)fgbvWS@nwAih9)V(lE9dWOT=+zd=G=lHu zAovI^&0Ha{`NV8Z@JtiKbkaMaXVTMV4~~&#uY^%G>_PHy3W<>b(H$i@!87|SC|*B5 ziRU(qPQ=R8nY;%i@GT>uo&c7CqmS=7$uI&Jg1ez zcANg=>RrZiD%XG%L&yw)nzOszb`-6t#%{ben7vH2}u$`n) z9{eYgA(UutMCgb-bDk`zIQnD6b5;d!k;tiFZic0a5te9O@Otd-xJNyg*q(Fy0xno~dMY*64^6VcVZYy#@qdIlrC zax;T~OAMkXEkk<+*aqUDN(jQ6clAIVT!p(?0oO|KX0Kw^?MPpV-iph+*(}=WPjiT7 zUs6YtlOk9C%$#wF!Tb6E%&PQ7>8E6AGwzxqDH0!(x)_rDkQ^NVrYO(19S`^)~9L6^x$)K z@Da^L) zzYR~x&U_mae}4+M#WaaN*0yZnC&B4AI@^A}y{JooJ|dp1S95V#UhcoNt+k!M>dobB zK4mvWe(AP;>7_Ese)IYbvyLF!1qsL7P+`Ne7A7XXFDrt)gO~p?2L=0@G8R~2uWW() ztuj1F*yaPLIoA+PptgI_6Nb`2XXjNg89jX*RocR@MQu0BjCaA@q4~%Bm}ugY-HdaS zrFyz}3I)|SLg2=2T?;y9BfwZC6jI;f6~^~(Qw&7P>%CGO zbXc>C)eqm~WeRjkD8I}NE)!|eJlB>H`bDaWL!i&v%qj`^N`M3W zT&Aj#L@!j3wj^aeAjj?I(6~0ePIG|O&+#!Q!KEu*4_mD4x+8}1ituS)WEjR3xxrldt-yXPis9#Atj0#y(Ml@K0LPcxREgR5Rq9I#tU6v#2-U?B)L>C7u&y_|?d?p^R}6f-6+w<9I2p>v4vo{9&+z3R%$33U?@fxFHG zbs-yim?8^ka8VMYvJsI}Dmr+*dSy}rf4B51{gm^vX9uhDlD!w3}!c!;G!UiKneG`PLt!#A-tR*51yUQRJUxS@IM_O=}Spu%zIkCEW6rfon z6n2@IzPL_{Xj>@`OkcNG_N?5zygBxA*QTp2&~_88eQiV)f#}_Gdr$c%`o?p%T-ajt zbth8v@sP?$p`;z>-iV{f!fA|HC{f2W(IYg=Rz_LmXZ7F_$xw&85kWCx+K50=KV9IE zaq*F=hFBM=j9Y7wn#s^~dTAq70WS|c5dp6XyP#B^@vff7{JDd-l(c*UYt$3lRFoi5 z`ipR_6zh(SJvfdJ z%$}RxD`i;I8)uPpLL-jSLpgl&)c8f~AfZxZ)kD}w+Qt$7T;As|``ncW_r+K8)Uxbc-@ZFJ!PLo(Nx=l0QcYOc!p6w= zq?BZ1Kk(n9S|y552P6C|e@x7B*pK5?x3%w*r=9Gd2C|}r)!akFo_qSNxS%n*YI}4T zX2yHv&t-yK70x!nt4uHp<8qH^H@kq_{Lqtt7EOtQ{lS`p%)8Fw#5`tq7LT8B5-6r+g5do5d>7 zf3Lc<5Qd`tmx2TcGjZ%C@3ca68UR>-J@NGt#X_{NFui}j|AvQjNV$@R=VpeBJ~ea{ z(~$zI#Nm|b0vCk=(ZQg5DSe%vRVxF{v#?G!L8EdqkK{^3uDTMYGSc#CM&Qfu*jEXg zwNU6-kD#_&RS`0zypiYo-IclB%D$>r&?c>zm1Ym6XxQ|W>f}sp`B{rk??*!}aqa_r z#t8}iQt@n6x{@xWdr%6QDtX91{j*1Mo=WOTmZo*o!cZ0K-Qlhps_K1SP!3%N4SmmY z4WML3o*TnRVA)hg3>Ce4;SgrmYBAGAUW{&^qny~raIVG5#qSUvtMh)gM?W~KBs{^2 zhFTO=$7KV))U7xO2bzYdyVoDem`d9)trT#nuv7_^(jfpRr2}R?m=>>Uz2Ibgt(WWJ z#OuR7{C&=XRrlAdBA$;NUQs>zd=xTtSvBxv(P4Wl5zqL+;o;#3Ap1koM7L$w&yLg- z3*5~BpXl0cVZH<;65cS#>)E~a$IerFX=KIct(( zD=)FE@_F+xJZrF?z}zsSZ6#Q*81`F?H_^OQ^5}l>uDnE8kv0EF8MsH&tgnQu8R%YQ z;g}hqT%5b3*z>x0hThU7F40hkuJ$L3CM+Gki=56=5xO>XKnwH%+la$QrBn=!x~0Xl zsFd0$z=KMS-$x{7~%SbDYm{SPYe@QR)~gEwl37xk=v3xRe$vhSnbjUOe};0_Z7uYW5* z!y7yj*6;#>hCi}zr0$IqC8vKYL4&7uQg(Jyc4~&=iO2T0X*GS8(wTby81)Twn4aoE zDqK(X#C5oi!Wn#D4fTz6xQ_Y(JA6m^gl^c5@?j%mFVtH*s86na zK9o1PVL!@;o$zmk6Xfu36>pF^G1NSGhmid$!D>}+*6@}{IRWj8moo%4N?z24-jzS{ z=UOro>;5itvF8c?Cg}=XnbE=&1z@C)1=?(S;wI2T6%`#1P#8*r6P#%dG3>75 zMw$)-Gc^)*<^mWpRVYjWQ7{TuaWRF364FP51>aAuPBot2;}@*O!kQBTzs&a-mk^fV zC>BdTb^KzmvcYH4`WWcF+)`G`QJ&ieh0|Ta>%0A&6xh3}dmt%^0 zzFu1mKLYA8C$g|A3bt$RW|%s`CApd0S&5Q{fU< z?F&a!&}w7B;11y#uwu%>q6fte9cjxjeJg=wTQd`JD&|C-$aP>(ewHI9GmjMO{rV?K zu(TGEq_Qhtv9r?*{>^ z0G4ki$MuJ6{-l%|2aa%2U`J}48-!ZYrWC(|y=3*c8HU$Ul8R7}12csjzYNAs%%08< zR~ZSTry7twA=uHA$&EdjJQ*xw89e029l8zpfZh$rAq7$J7jjU4lMWagDwAYEuhs^M zCelKcNM;F z>gU{!lU?a>aVYZ3FSd51-rF@U+Bid{XCRPP(iGFN6`B5diuI#|{xamrM2tx9%7^y2 zN+1K9;%YyM>Q^N(&q@b=y5etI88{8aPP`&+$()#c1|ZHKcq_QJvqDna;w;q!v}au4 zQ#gS|$)iIHBJGn_&?FHT{>t?h0h<3Jxrj3nT0zHPA}VrKw(hr@vJ~Xo+Z>!q5Sv$1 zZcbSi?;k)OT4_mHrmW=d7$DgYvTqwTZ4S>&2jaNx?r0Hx?Oh>+OBX8IcLk3k&dZgILucx!=JBOT$&r?m-{%4f6Ze8D zCSQ0G=H$zrk?mKL{aFbZ&#O`FN|q%zi`KmWd7~vhWH4M(vF1FGJCoCYMl0RSzx!69 zZ8?ZHD>Hl3NpnDHt;FL=p=54RKOp3IoQjCy@^l7(Ev|Pik(9 zlh~!Q>xa568@!H-r-e}-A={y4>+aG?JPsZgYtUs7uh~4xnQd}&2ukV!LQ}m!3g*p&1TQBVivvz`ip9Ot;$p>`5`)x}X=4V^CZ3wJ z-A@LgO3o5Lc%#8#*C2n>*F7|qTYH^uIo(t+HQn_+nGY*feD$6$M_d9lGi@(MBVmZW zFQ%|@Dc*%aQ6h!y3ehNH^rass^wjkcAKgStXvhvdws8~Rsp89e zNW3<3gP~k;e3{=es26Jw#$L%t?bt9qIx5NV5 z?W%S7$L^0wLLa3c6j{9G4;>7WWZfRUCCD3rWR81wZb(9Hrjf5Ir01k(0S28uu;_A9tnJ`V{FZ$x9xG#Fesz}n$`q6%gDy&uk$L1;q+?E@%s$JY z!MPsT%H5KI5jXdQmvXi5z!S8On=siMPs=`;aw(KEUVRM(pZs1(2RyD%%ckKX|L`{^ zCw`t&%K(23uc_yQJbt2hG~02^slOL>v>$22ZAb3B0ITogC|?!en^80ya}IUB@k>AU zCP_UXV4i(t(W_0m^U&2W;=b>0%pSkbmb;(iZvX3H@_9S$#4C|{E}VJnok_nDKwU6p z=g%@rEfsXm+cQ>fK4t%o!9q1%;xbG$&LQAoHOe_B+sPbW~cnZL( zyrY??dTs?$jF`fk!YlL08jv14K%Y5kh!h2bHr9|nN)$G-%u>6+{gqinuI!nZt9a`2 z;F+?`Z>M}J^I)2?Eoi55YV+WlvMq3}d}^VbUFv4wz>fCZUjVCfGZd1)@~8Su)4Qg! z?)RVCh5Hk<6x$N5T_$QimWQXiYwHwO`uqdEL#nONls2OV;F?vTpdAl$`BPbx#y?miz`j$TqQ<~dbORM{iT2#^aFsnNBX2bdjXSa)!9+-#rqVIJ@^6~Jp;B~b za8l_Ge7mPBn$brVltT30bk$DrmL#uHeO=v@PIZ$HibC>u1j-+vP{Pxo{jSROy0y!l z@uyOf$oR-)O5Qc@SFuA6b^#)bj{*H|35RC!nUt~kkxyBNW@pg)+<)KPZ}dtAnYVUq zPD|7;^&n)r(wR=?kt$<}R@E|5^Fk!DZ*7Dm<{VVQ%dAgI4>N~JzAN-u{i5FCg!rY= z43wFEQ?Ti-awc4924$)p=K_Njtg!c&Ck2}iZW(>Yl1RTE-1b8XtAERfZc4|6);1k8 z%Y^<{mAwF(hLc;(yzbfwbktu&bFU%@wGfyRjF1P8(Kp5h$oc$R`giZ5bl zCdiNUVdnn2#21HMC8RvC+!S}Xm+0X-Fkb3g?wi2jWB`?FexNsMk{>GldbGEH0Q%JS>p;Ia zBl_@Out~l|ckL2ieEN52ZrcF-s)l#KK71qc0KZ%#`hZ`!NPW>>fRX$VUb;xWYhGhwwFCGr9qt4D*g*P*ySesU zd4J|U-+Rw}yaAr|d?_&N`5tfL^}W!*=ew=G&huP(p5;D2I?jE3gP!$#Eivo)klgPB z`@)X+Mtiv?`SRNJi+{ln_?FoB1N%ak^Zh>4kn_8*F6;AJS>)qBf6UAMeA3JP_})nR z{&v6n4krKpb~(;Na{Hom87ty-c<6ggL;78B&`?QeFdUNa4+bO0@0)eTUC*d({_>jwq` z1_}Za0vZBx3Ge}W$HXH;={w2C0ck~J0)-A$w)0D$Yq|-VbM=fD#ejp1hGGF71<{o_ zfI$YL4Uh;J0ulqkL*k&isqZI#poH0LN@QIEJN$hfMpzBD`2ujuBdi+jJd;!j@UFAv zQrZ^zZCU3DGi@0mnc3eQ(acc%a4NroV7a`2!hw%GGnVG4t5k3g=?s^SCJ;-9THHEF zkTm$(P?AlJ$~g;htVVm}Z4xpQ2Ysw0^{cLG?zP!IS7-9I5-R67_&%c~y9$-F59^qZ zBMyPy|M!qmCY?SSAV7s z@NfoP0@bN^!eN_LPSq*xIRR|qU1P5oGx}%otk6|&>PzBoFWg67aasDe6=27A(BSv4 z*zZT|zZ01wz)M0?kRKLC>M#s6K-yw;W`MbXgLVQ@5sXy=ye(ng{nSSMgj(>{r zM}4uQf3_+AP8wBIl>C~d!vaWTB1&|G?nx1GXCsJX0Qf=a-B6o&aee#XENSE z!BE%p4lny+wr9Hez~>}7;F**qUpqY(y#2d2-N`2XUeY9AaUW#=-gWrP2Km&2)t#r4 z(?-eOm0){o!kpZv;>@i?fbcY~p=JoGt|LEtPd&gTZbz;~q%owz5aHZ{etfPf$wb+Jk zG+)BFnR8*jwdlZjHQ!_E=Ie~#5M3IErkSTPZPw_F-Z<8z?cv$b4^cP!z&u-lV}Lai z48^0FmRtG2yte>`hO&*{L+F{NEsy zP8^RcumDU*9^?cWVV_t&a3*DWJW+8tRHk$D{6#h9udZoPF5*I*C@MuLxc&l!rYYt2 zn`1Mc)3qJGV|svR_1O6#nj!9?KnAL_8a4q-v3&HwCCAmy=DT2zD(I)ugwH&y)n5-> z$flR-cZ}$2;Z5_G-(BF;4oG;AJy{!SgGWj_Hy~>12zY0sRBS01Jk)*=lG?kYr;-*% zt6mc&iT28D$(Fd2E^Z6=S!|3R4U71iD(f086K>W(wGN2iKw#mNCu5YM>BEOUKa0lf3e|6nlw2e)(QO@;jWiJIe2^mzV#^qid?Eeu_p|DV{U$n(hk zFj9T~u(ZPhY7QQcgTc7#>sQ26l@9g?swW^zAFgmvxJK>j5rQEA^ZDb4w1Wkx-`Zba zZ7nYH_<8sC0CNnh24YY{dY=4WoPA?-=UdWlM;+U?ZFOwhw(X>2+a0rG+qP}n={TLF zgZu9@Gw+-;cinksZq}--e8`vIQ?=`$<rJf~hJZNe?3GT7N8P+bpFrL%_ugJ{S$*?!7dgkAZT_V9 z&nunZX1Ub1)JF>d^$;L}|242eb~g5QwkEdDf40mxJ~=@~ltDR>1HOR#mbOwUa5Y22 zRA&b#Z^|p3?BKbs4po#c)Wq;W{2q7{>)Lev(Dxq}p3|q7$IHLK{j4%s6?!FLi&yNN+oh^Zb_VrN{sb?!aOXPlNU6>^d}UBi!0Ts%A%0=kp{=FF*Z0lLUwY^O>J9& zt6)%Pv8-`}S{$Zhq&jrB`Dy#nLV@z9zw;EvN zhd?5q3i#*Q^+C|!Dse1Z?QkK7IU zwfQs4*|)2Uj~6J@Al@jbALb6Gu!jgi&Bs33@UaLlHNdY0nSvd*NUh5GI2A{%n}IIw zqYd3`(ziuXLSQf%3I~v^p8`~%wGu^xOhEKRyu!Rr5l8J`;SjGCp55n6JYA9 zr?)I{n-tEa3o7mef4d^w6W1V2-Il)&pZ2 z(7_m{%8pAVl|?EJ_b`m7U^^cCilEZE*z0 zim@V$^w;wF$J4%_g=h9;+g3H@Tt#j+hnfOPnzZs(T`GWXpYD%^A5bTa{kUM!8hxOKXAw19b_ua_f$jK$)kH^ldPMv+Hj>*cMg~D1l+Il8M{T@W1UZPR^0E)X0OH5 zWsXPo>h8xiJM}xm80KS_mdnGL%X4HIfquk%y+5JThcKDe(z%z;zrX{Y+`TjkZHDi}^BQ@2$4ed@b(MP3pXj!<|*$(1+z+mLTFPYxgl=&PeG&=8a+Lcfc zuw&kPW||62v57tUu_$5~*>!1jFwt-9OEWCz^9;Xa`|&q{x_b&2<4w3hcxKZ~maV^q z8l~>*0qbM0fLV4JIT`4*kXS=x^qCk)wGe5eHU;DEre6oz>$DKQQf#2uLN*6;_v!4C z?`F#vg6}=VeQ%`tT;kmc{(xO}1^G|!|C?5+p`-i90SDtVK=-2mwZr+F-bD?Job4Pv z{-k~x*OzD0-m$956+n?Z?O3R&Bmvbm_y#xNFU|4|) zLqbnae{gtr{sz(mD{BQ~uWg{b8^jQkRWll8l%(jK9mFpeqSm+08^(bDYyZKgn=E<-20gDD-mkQx$uMkDWwSRoJ8mc&tTdP5kfEJC#)g>kvb^!eTS?zW_%f=fVE zL0rqO2%c=s(pTmrQ|}AXqg_S>_=tmaDvJ%RI_BGjdW#x;!^4KAMUZkt);zOKgD-NU zUO0BQ=SrH~v7*C@73H#GY8>$ov+?U7=8NE1AWsN^lMt!{G1K_Yk^T{%1BC;E1UBvv zFAU|{MgE_+Aonqs&tQK=_SZ&0_aXpQGeCCPObs z6$YEyb$l+xwJZ8^V~Xj})fd`1Z+bbM`O~_qW*?XJlU?pm<6vencdM-lob6p}5q0xp zr@}LJ?EQ~v^EXq{q)gM;{!ijU{U$DBDHjM?)M1ZYb1CGTHT)LiMLsAbrn1FdqP+?a zA9zTj^ii;CL))Y$K)|r&YO1`vV{ir8pq=-s7KdAqnk%bsR3|Usd_U&sE+IXY(j>+- z@?vOw{ke@YKN51%faDsr9JXLu>&r_qbfMxr28=Z-HC| z!uN#k5MML40PzCl1Z3=>wZQU%xbY+YxZ2L~N*0!BX!)5nz#dV}_g8~ibhP300T>K6 zfU*Ajgy3I7B$I?z*?xY65VO`kL;^yR4K;8UItp_Hr2K%8&$FUdUN~qITx(hOU(isI zz~Fm?2#2P22Ks{erulx3Z1d&leX<7zzhi)GXI&}p?Fv}MQ%t!|Mk%oZyX31cM;2V3 zOc)^kr7Fsmyr)Sg`g84+A{O_~dF&b?al+u#9EEa?P(`ZU|M06L84iLcC4x~tIHd~C zrKOy$|2K6zl?MuJbtIP+3jFFE%wOz5m!8s$M2Q-*dJ~0;`dY&`Et~dFNjyfR18PcG z@7tCSd!S#EFAXLTh3X+j%WD@X3+l!wnl!XY*+se0QlLEQG4}If2h*61V6&)GzkF#^ zVsJ?tR1c+HQMFHR)%8=be8B_x5CpP|ojPNgQ3%?~m?;R^3c%y}jDGJv9$kfmODOk} zo8uDa7`a3Cb!TX>uF&|9^^AGyq&6 z|Gia1VI06j2o+OS?B`{(7_3;)TP)zmJH0~r1ljok1OnG6Y#9=%#;eH_Pp{O7;S^6m zK1nOstnMnu3@V%R%FE8(oqXNED?{lZ*kI6ia7Yx!1_r&oL=lWybv4#`!Xpj&<299# z?ZdOV)CKq@(6qr_Y=Xg}aDwy**;i7PDpFu$8xd8fk>2xmBiK$<-uuCu{Hl;uY+F4n z-(|(5gmSrh;|;trYhJ@1`kqeX)-7Ggzr!V_LnW^gld!W*LzD)V&_cRKdk=V^GpCX& zf9y_*pv45EqL)*&kMxpWyb8n)u4q&p>v$0D@Mku7ZUojmVAl>I5`HMvJ@Y`~@#CI% zU}rR^d>(8{LnPaYvA=~bW|&lu^6g9Afte1w`XWY3k_OZ|hM?>ru=h3WfLQToSZs8w zgNctaxVoG;AHki5AN)U$-EZwA$3EXd0<;qc5P|>RPXhvJMF0l^6lDEBxp@0;0?d?g zGD^DO%-BcHH0b=LkXdU(>}WLEvSj*niF$>&h{k6h;_bW@2Jl zy(*IV5YfLH=c2!h4(pnPo17l0PbM{Fj-KzPo(Cmw@PRnN9`T^!{>zU|)JfjPw{3BEj~iksXpHt1Npm0;SDkDv=A2|-8^N@;wI zai~72uyHG72fEbhl>3E4=Y}G~3<-@pIn9J92czhPILj3?_^) z*!;x*l^{P1{_8#n;+JEepI!$?~Swra6E5PPzT)JkI{b%9ri4zbghoSe$fG zLgaLx0WA^YQy$@y;ZqEi-4!Xf-Cuk~?D2UNKJFD^G&b%PW3*n}C*XjSgiqv_nS@X5 z7Mf(5_?a{TJ?<51G*a9r`hZ`t|Q_gNBlkbU|ak>@PL)%L*$k#;e*fvWHf_c5&|ir0CW_Xq)>b;E{V95UoqU6 zFoB9BGLAyLEPjEYR75ef08auWL6k&B97jAozJb6(WKVc5#F#O`l>}FuE1pJRF4&kd z!9+Yp$ni@8avY6V9k2v@LbA9jAt&OfC5csBW>5k6s3r+Hu}0_t3P~{$C+z5{_!9A| z-vJB>cD%J<9p-3SGO1RXWYah6ata5PqJ98Y0hL^iI~x3M~2Y)n~0L>1BCB>+ks4$gxC;muHP$y5>yq?uugFwf80LFcljlV zs%)WOhw{8LG<5MJ2)mHT9o)j&x%rns3(H0-wDoBkS^E-j90cU``fn*MDa^(7gB`Ug zQI<}sY{m_9rXhunLBeMXN#Md4JQ* zp+~~G@gU;v^l4t^%MBfma~I`#7w@@TvU%?Ckt>*H#W}@=41HKR-_X||xLU$pTFt8$ z)fSMW^JUn15DV!raORA)^Y4G^+=-iZ2TAo49|5n2J_pJ-p}Yw(IQkXk#!TX`oAa{* zQLf6oc$m9pv&U)*{o(Uh+qX#}12uQh6cxQ3a+J9T&UO4S7>^L(-p!u}(4ij&P?DwBM|9?1UN zdwy}zfHyjq@T7Aq@p&x&hn@>}d$%|!9^GTE7ZJFD#%7<+*S?=d@Wt-NM5RctwT;cTBUs zBC4Mq2x=Tx!8NU-N4TvKlN@6oHrq?1O`1L&~yCq`Hmeex;P76SMS7^(R54ed~{4ql_>gE^=yW$aHAcTym@p z9DLTfGq{2)DLm#9KVmB6`!A&02qbZO6)aK9sD$9nK-*XNVS1JuY}mpPX=|qRaD2aJ z%Nj$vy)vWlCz=jl_PfHdiSlLEgi{Q+im8CYQc^Q`5G$k_t2>SWq!^*Zzt%|QswGmx+fLdq#q?|vsQWdu_Cx(yfl(-0(h)QwP@0k{o9Q?SE3}|=dXFyV75fr z;wOko+&1YHoQdHiX3P#HEY4#3K)vT&q*#%aA?L2^#(jmVLgYqi5DT0)=0tch zE3Dl`q?%l6;NPmzLCnF(OV}JZdL_S;HZK;y)F(s${jg@@1!2}%&Op(!&5+JQ- z%uE)yq(dBMG~GL**vFO@9tJfD)Us2|jxkld5o#F(R|!44HHjvBJFuofog`6uF`Y0; z&zm1@MM*CoH%nV&nnhvZw8R>JRZTMnj+)pp&-8Vh8cz$F{3`tw#}IQGXKA}n!LOQj z>50YqTHKQ^SYR%tF8$^6gNjwb`xm8gu}c5VF#+53yDT2bMMw z?{_j0R9ofHc`!|;%$TB|=cDqMEa}4O^GV7*rNx4*>fzY;0xe@i?xKP5KRRuRjh3~{ zX{PXts@0`c&hTs!DLH8g6h~q7CcRYFD}!A$X@`o3^C2Inyjrhl!Ccm8L(KH;ijdD{ z36j~u1K|f0G2gdDXNkl!oWNzKNttd|2MlQF7{<#8ols^FX)FcI8;*{s?$~W>3|$Y) z>f8o8u`pxzvv5;J5Qb_#zs@lc&BQ=ZWklqVqncXf!P6PREJh1z8FpM{ET(Bou zgHJ_kD|s*r$4AtH+2Vj6m@hOu(+uKE*VdfU$QrQoaAuJPd#ya&ym|Pf6vORN*+iAW zn$L{fDcKRyMF~#zp3erR7An^?G~h!4xVb3|vf2PQ5eF*3lwQ@ z9n2PvAYtFaF&1Ks7!wnwN|%#o;yg*q#?2NnEak@r?nCkwvQN>&-mSZJNR(pXD$t^xsuA;rRr;vHa38yLR5NeYQJ-b_Cr%f$ zuV4197Ifsqym4DAi>6~EZFaUK-ozV~!JfI6Qn?r4D^$e}bhY%oWrmT@o|#ANI4>Os zO@YL}_}xCq^!bGk;Mi7*N>vFdhukvQXvPD#i^!Xy_x9I|dL+`D*JCh!)}jq53|Atu zYR2G52~nxg-B}@L*ngtSJw)Lz`d<1be_2>#X_p)pWKguQr6`|Ii`_}FkP;o9_`Lwl zuh9eNQgcL?Byc+xZonZTn#u)_lTR#HU2Zs$d58o@a^UCTW&t4~JY1R8ETzJELmz{l zDfdw#I`GbEl{a3kkGgChw}|OcQM3_fWp$w7+(s{i71#W3#2GHj=50kIRguxWdXiFI z)q0Aw%;Uf{irld095^$NJf~12ip)Jj5S~(S=4ydYxsIJ#+zU`z>;QM}i*hTb=zgSCTB}aKV&->3a^(4#AXpR45$@HA-hzo$oVv zZK`?aahZ~GN++Ie0(sa9HJBb+HSMqx^sbu_i?Kt^#Fz!#C|p5uKw&^U(B6=h#WxmkDsXc`P@M=0bouDi z_efvcP_|E)7TR&z)d*UV+ST&%%5P4w4s;?tmuJsWJwdSA5RL4SCGA9?wBj}-?UYU~ zpPa!HxZ^j1x3;4XC_2UReiB~!kiZkX2A-&(nMV5f`zYka=585@uKWi$qrr(MX@ynTMQwzD}AoKc|GwrmjiB+mUE{{{u1UJm|MUSkFyTH zeAUO@0}hozASRBgUpzHgBCivSy8laY7RAR}j^z8k=k)9)vo^HAAq#V>#UX`;B$IIr zLR}iiD3D{Eyd%U1mmHkXhoI7j%}n>mLN~^X9s2{l|2wzzn^fM1tkMV5N%#0dH_poy z+Jz3`idU@um1OUh7uZ`l=-bb<7d4@ew}4Kopc{MyfX;8>p`Ml@UzVX?@R9EA5bpVC zPPQpde1&JaC1yTK2)Z@me5HHZMZ&g+W!|*%d^1WvAWyb!&wPhgzIjK9bbtaU*R5KWsHwhhWGKlr(vg|Zis1n3Se&}j)xh=SC!tfPzatOpOSqN zJyN;aN2`uEE|2s^d&hu)#IMkrE>8rMq$V?TojFNg#L)noJjN0fAGKY{OIJtmvkGJJ zK-N)(3E|{jZqxd~(_z**cx(93@86js-Jy1}8poIMvkqa1%wb$7~B#k(q7c_W+Ig9+RVH>P=( zj66jt<_UhDiU7OQj4NC^>4TO^$<%i)vQTRlG0phWU}IS@`_c#IARI-nWtb8B=m7_g}_wg>@7(baYVe zcLKT;wG6!_N@h)>_=2F4{n+WvKpcV=t5M3@BLBK?rw^Nc%@`HvQlbeZixXqolh&| z&C21J&`qlcD;8-lRB0QWpmFci)pZ+!W!r)@P6C!{D-<~Qy2nwV~(4%Y)>QY=sV)pX^X`*h!BWgliLUQqb)6~#?V#e+ua8q@} zRuuroZf}h~bOx)Rg{`zC1|`>lZ76SvR-smH`qrr0KxeI!bSQ0Fl8#+=6oONLoCtVM zqSE$6+t!>^XAmX}qViic1q{pWVi^rRA*0^&LNW5;vn8ueKfheS(hGuCBaxKq`j-LE z6U`7$CY|zSC=(iJI%dNJQmZI+42|o`1*oUNL`)75+ns`ZSb#S-`Yao&Dg~K@3x_LO zNxZpLu~R9dzjeTsgt@wkoqz%&Lxujhzl>qhI^u>LnXz zKZ%k0`{GtC+FYpEMBScTC5iY(PM%!tp>Eh}fHg0wf;EeErp|ZHMLx4-csy9gc*dYC znjk-D`=CaPSLb)KDNOHkQgX@3o>6m=U+!BONH?Kvur2p-fJcw=^RNk!yVH;zhl}{m z)~4n=nC^ZxSAp&?h`8)T6$Bnl0P2sZ{TYM0@m1f1zPT=^I5vpJ+nS!qTl#KfdW7hz z5%ZY@JS*8BZVj~451*0p6tVvM2{GAZ*3?Mm81XP5>DOr@nk zSPJJ$mZM83i1ZUQI#;9o9>UY|kmlw9o$a1r_NQ$!89$(m(la7YMj>zRhV*?)bLki( z>k#N+Q@mmN-I#8nvnLHU-0q}pzirA+An-`tu#}tNce+zSc+_ZL&e*Z`LIue6s4-NW z2Oh-*#EcTPTlEe#tjO7XBL`Hz;B9iAV7ImV7t0?HzY2A)sMgM{+$;D9q91HCboIuU zAHWOGJ>_O~^#EO#Pxt{%Jt?@lr9W7$S6=I!K=|VMUeRO^>LYpM?GCm}c$u!)%bX&7 z5;LB#UP_!{v$XE5Hzs1+V@G4!!zTBCpuxR3V4ire3D^}jGtS8ITV>iOADK7TBh$5I z<6BRfhz(8`J%I9cHpROxovF;=HA1yz{d$u~+x}(!;tdY|mIUKGL3Wabb3e~CA?P>8Z~ zrF?U>pT|Q6eVH^lvo$TiM-39a4(jcfqnVSKt$DmY@d1CU`H<&fx%eisT&D4GHG6+| z+bu>8Mc(MaJIYh>{sQSsGzQ}hsd+mNpzxz)B*9|T46z)ZyGDOX0eyn_ zYcwB~);@F7(vuO+?Ksm&0qw{z=Yg7WTzwNkPOco&NVTBVn?+;2x5YPY`KKYq=`Xw@ z-9tw=lrJt@yfW7x;CHZZV)n40zQEwJtOa!>lRjW#BOiZi3;A8`Pd!Dc|J{#d1gJu@ z{qMEKKPrrR_6E-8dd3FM271nat1|}6bpjfYFut(E+6wu7yX7Wg{1h3lUQU zfptE#v49wnSH_~pUjy=+=!@KQF|^Fygb7+&I=;JUPMlsw&v#@Eht<$JAnYTEF}}nB zFN-8o3JGaotMGZ4WU(jDw@NlvM!=F4K1Lc>b;@@1=$GO*jx~-iT)xc};ADL`nXKjZ zjOJL4$~lu|??%Z8Ey4HD#VJc~-&!Po+t>{v4P*2bsv`(hqYhJJsSeHBekjoazQxUA z2gL?i`fGvq8y;)^D*!xu0EmqL6^Q@9Prm*4?PR9Rg|;CKNvfJ82^7H~Y6GH7+04wW zW~ft8Qr6iHrIz#Hk;wJM!FwqQPHgL5i`8p-6jtOsTQg+^&Q#6VYRgO z82b>zj7Je-pT0yF zy}kRqKo@uqw^FSE8>s7F?D3=msXZC6%}gEe$|Kpoh0@rBUPxHvZ}yk0qNRqchO+hD zUmN3xF##1pWhGEvY2zZ>4k?K`Ia!z~=%6JRi4X=Rvjx*mVM_OYOHwsQD~PQJ48 zZB8>Ln3RIZ2BXQ8R6xVW{Buncmu4$V?51qcv?|uQC+6G$*{o@YSFT&m8b=b=kR#XI4l82lCw#U(uN{lb-FDTCn+Qe zs;N2G#@!PYGnHo7sCs+31ap#o;|4*-M5?wI&c<8`mL#vzt20QcYE>k#Vp~<}JHoZy zma4XNpBkKeof1zm57utwh3qPt;FCt-2)C|G!(*xEWNpd7i)i6ZXwyY!TM1`2-cBk7 z0-F@rm?SFBA|07U>cNobsT%gy&tLH=1XasRTTIT-gpow{R;?db_v!~vUBX{t!@Zzm zbl0ZNS!K3sq6j*ZDPZCzM5mUxYqCO$ApUx1h*HN zn7c}mDX8Ie?Mz*VD<0OMHWKnKc817>HO0%}bd{`9f?dz~3ZX zerPkmX+aA!(?I(yhY*do>=u~YfET24)yfd`S)}M z-F2oikIpi+C?@Ll3$FJ31||KR{MDvL8!jLGP&tm|Q>QkXdF~fZ@oolQ;(65c63<>5sVK!Drq+gL; zZzzvBPmeirq1hG|6wJn;%ZD{@&{ zlFPG|nzf6Ebf{&{(EP3>y69Dz@qgwa1u^&I8OKebe8L1Kqlz~2fu&^VM^P84(lojcg7Q=fno$0WQKumnbu z=^;$qp%oA3J`IGShbXwihx77aBGHTx+5$z7qx#g|TdWzUW}1PbfugSDHA=^>qC=$O zUBIs)a7pR&qyiLcdU);D1NLgeUV&U0V6GUOLOs3j&0a(Nu5DpkrAT*TNG|`Jlxu;` zG7s7CGbtBC7U$l}9_S^>)8f;sCA70E9afl2?YFRyq?)<;f-dYT%tAN%)hp3mCq#g9(PEf2<;^QFsDF^5lv?S z%&X|l(T=FY*Ntc5_a_Mp>9ff`*H`o_gErx{7Al=6bM+U~;C5)^IHPL&9WkzJ80lL2 z>te)yPG@o4`6*xp)&1)^k|AVSf5L7r{G5GD#CDIeg~0(_w;q!9G|gOb!D$0q;nxrT z@D~OA_L%DS`DcyL(~3+}F61}xzjEY{+!mqVkAM7be5U)~9J{dd-=-l19L@gb;Jsr3 z0R}%pklB8!Qk+R-HjE#Zz36tPJV~U`R4loM~%){()VdAyy52Y;!4HTA#Lv%s2H_ zq17P^y3OfM-`PUDir(&s?Jl6-w-`|M&;2-#J;#^7m2nuj6s*GWJCJua776vNG5+49 zPRMix+vTa87eY=S2lkD;@5s)p#uE4pwbBf98|dr5P-FItnr$qA8tVYm_;0U)&1~&# zOz6dAja3HyiRlDVcaq`FTI;31o`qk7L& zkY-5?E}D>Hs--ZMrY5M?kJT&FP{$XDFEpELp^DXK%1lf@PcNM@LyXXq}E`pR>9#ra{+B5a&%BtB_>5n2oA* zJU&aPJNk@k$@mNOd(RS7B(@a|PI%i9%(y+CBn6MKedlNMQUG)})}v;$EA*)2%dbe#5Z)*JhcQ#zWE(@ZW}@&|BAc>CdaSEAvCO8TNOEB(r%0Qnr;>GdQc`$m_Ut;V zu~EBotu4`5w;ZIzUW#dVHNITZfVFLao?j?a^+~BEL#ogop33E(6(4!@(4se2nZLox zjHvB2EQwE0i4t!wM17d&-n4!%T3CH^OJ%x5M%5mI0655!p*Q&@1?%Tyy zJf&bFlgh}AQu1z+^t`iLkvH2b&t~GOSR4Jk+!6q>PSev35qy>Jm| zQq8zX!F6L(wTR5* zTKJLc_#+UtLcvVKofijsCE#U=KAOkQN?^7IOUN>NE0KK_4QeBm&Z0`2Wv{^a0}1o< z*NO>Q+c}J_6~YewPLu*_XH953UYifICuAUpN6sT?bzym#b0<9^Jbh$X>)4nP5edXS-M!FT^job@Y_ zD?6l5K(EHBgnET%1sP^x)VFgv|t3|irJ?YTDe^0|(mWsW0#5^VY`W@$7R~L$&188F$ zB=?#LocfWPanG|f{v%!=2Deby=nUEUP5qgae!oqBz+o?nnZLIMcu02V#9@Q=EC#aZ zdNyhrcA2~{8IfTZdrzHw8qyI*NtPG>3!GX&7W+oFMz8xN10|N*T5u%uG$uX>*#4KN zuQVof`q2VaW20YaSiK!^26&Vv zMk;y|ai|&O6D5yOcSiQI-2jjezM$ETVl>?yb^M9%Uy0HAN3M}FfD~T<^5x$V$tG!29^&lRj3>X1Jf8mD*aCz3ksz;`Z=zp`y%I=z~=OWH@`Ktc8%YJwc9kEi6ab=GXK^Lc0fPMV5P6lEt zg$obgzRg0dvBBohXEvJ!c@qwX-2ub~1mV8$A4X!9NMssj`O!#I8k(WSDc+?63J6bg z`?W0Td2EMn8#k@l9D(PQt!69la_5FKdKefI*=`>VFJ zL$gvq0GbL1$iGeXfA13$ldyF*ar`~eDPe2+_m(X0xIa>Tkt-55*(gX41On9jeFbX) z>{)q&`2zm@Xf!h$PIW63ErP>p1hB9G_?_qg;Mq3IOhw|tWoaoQb`E|9aMbi%<*uy zAu=Ps4y7+~j9K#W`_aO;13lK@_Y4>9u;`JDG3<6Rl)2NW@c8X3D{hsJ+q2R&IMAI; zJp`(zHHD{dOvwl@1o~o2SAq3E(9NSV5(?os!qcVf`#oJh5nabNS>dYWE_}wlz5Bs% zrVxCV-NBzNpM}=s+YSCezSEh16LsVU-1VpDM}AkJwt3^_4*+)04?vXu-`f9gME|cr zB$e3O-`%CcW{}}Rj{JgBB4Xy+Geqeq;w-1;#QRQkaOA&?=?R{wsYsxB{bB{J11MMe z>`WWEo4S9`zG?3n2jRt_l0hl;tqjF+@@U3{^EU@kaqGHLy3*Z5t{mm4G*0VG#4u<- z>uY{c-8!$cSTFkqZXFitWX!arGeE?@NxFio@w#`B-A|dh1-*Gx=mbNB25Cq~yEZ-O zxN`B;KlM1As7Rpn>Ol2BUx3fi~$yzfw6KtriG4f(m zRiW9b))LLRYPaQT&9mu{>kF-+KhwQd8#|>X5@zk!7pTXmd*zx&n&vv9osPWg07JSS z_P;s=XM}qC-|f-?0HOVlw4vZ);^^`Bv?lI%-4rEw+6H?r2nB0QPXhrtX%NI}u+MKy zkr2VkI3QdxKxy!^^44!{{LX6}aG|W-uUn7bPwV-30iTAWPaPD>F#zQWq^bT;dt{U~ zQ&(R~G!urJ#eT6W9|E~Itpk}$88y=BK+><~3& zy}zO44v|a!wieL<`3(Z`YoD!#iJ(LO0)+uy#Ym${^0(lTi)apBv#5D)l}U)r@Q#%H zI4v6C0OdAl~ulBLN{UApD$6*493s_TchLzsnV{1DwcXyo= zbf%>2-5n6@^LK^cXt>1bzZyw5b{qSAM-nF$RE3NZRT z#^DxGsCAK%_LJ;+@_6|Sq=z)dQ21nMMW8zPvn$vm=p*E;ARo0*E@Ivfm7zsgIj8C* zQWml!0Yo;axBLwrbPyYtmc5hJXWDh4l-f-kX&F|znqV9MK+w<=a&ZEP;wiucmA(Rn z#uuiXvy%wxzbjh9r(#5L_0a%NW8pnYGp;fYn`Cnz6-!F7cfDOafu+R8_zW!<2 z__uHMsmVSU0f6`fkpG>njID$n4a|h>0Ho#q_rkPy95-N&9wj*Tmt;oKj3QK$n!!G! zF~D6!1dYuBh5M5-g@@jSFozQPQMW1sJufh*r972I;q)b4W+k*xc4YhflOuph?azW4$wrvj zL4D?m8|;dM1!taEAuJ@ocDsy8-};vB{coah=IJFhyC5FK^Qnz2=rC`|i``&?v!P0K ze!M80ahn^?_xT43IpbLO7;>mdoIqivn(v;aIha@rBi)U_PL6cKiK;=udqqr`3B`xJ zhPNZe3b|b~pzvyB`NCstFg3hq@6paHj_}5uVn*IKu~G2SD578_C$W`LW<@ndS*>n( z4tWlVjy=8_4&ZP)>#b=ivxY^ffAIqM8vL4E+i1x?Pp0op@H6Dq@Kg6c9ma3thL^}C z`ptKo|Dkb(vR&YBkh%Q43X>|GT?WYoZ| z-uFs1R^p*l9HTmmmDx!&O+!rbuH~jC#bCFfTk?VN^!cA){Wf587aZ<;09Z}{u>Nh; z=#OyA*y<0k1Z@BAUZdjxcmak4yE7)aM%&&=Zs?QMAs8o7ngrw#!=r=su?KMb6Bwvh z2Sxq|uPad$YKI6;rzxI^Oy(>6YP%lZ&9Jz|FtkfD7p@T2{35`438WURpRQ|;Jr4JPXW>pE2AT8jI+)fFz* zjiad}69Xl^mbC8lH3!Dp;?aWw&-0mW==P5~sCD0$Df*ztYA>;EFqXSGTpPSIH&Aoz zsB9KKnL*oRwc4VHr5SGx4Jieb{&(hE@kXqQ*C_5g!c=riIZ(aNy#G=&>excvVFO?X z4u}>069@1Ieu6H}&i}|LqUBow;S~zb@tScx4q*^>A0bJMA@o`pk)OOdMV$HfIM(=N z zM|3Jiyk9;uBCvAyj6458ANWIL!(o%H_Jh8e|3Pm1A)5ct?exeQ)OAbW3)2?mx+iTX z&H)Mq$yq^qre8Xs9__foZtF-24ZT9QscR4vVDI6SRN7Eis9lo60uy+hf?tSvJv56? z9TPw^*$Z^z-WhkW|7ygGm7s`X03{Fsl)(HS(AghK5OTBwEGM@8d;K#yj^{V6g=~Yx z1m4LbqTWEuODqg%8%WUz8`ab@)YcG=&>Dkmq_2zg_?pnLpuK(g1p`nS&nBcv; znKCuw{UciVe$>+k(15jByx^+nplC8751K#yFw8|lufmsWtyh2WbfjV!4c4{{NJUK; z&cY01Eb5T*itY1TS2z{zQohYGzf&(+ESbR4O!z)$O9qCbI4pdLKO77j-ZHhN1-z(hLR8Z zJ>G4labsAGbSc`@>)$PiLhwfh5)~aN9B97_cvS9(`IXNft7ZhI1N>Q`&sWUzL#o1M zkhOw5$y9Qgfpw57giCsme+m|EaO{`jLvLr)VoVK!tVa+f3Sc2#2nq{^AmWz|6D4UP zM#XoHH!mPRQ{2;{dzTt}N|xZ^Y#~@4b0GwY`m#{Iz^Tr#)n4j@x5H`HKn`=k>GXzJ z5N(i1>69uWQv2?OoUU|23#$M&$}8^-EQ?~vOB zF!vrrDAkNv^wOOqX;ZYSJC@$lqqk)g8>_bT?>={rl?{>JYzOT66$y5 z-$tD^|7#i@H*?kc0XPa_0O%S1(<32jXXNtt^%K$ZJW>G9p6S-=lz6vbz9-gz08Lob z+Q+6&K~-CeU>$g{R6}whY5UOQONxdF?d!)UnaM3-Kx|ukx7A_bcI55T>HCReNH+=s zn^Du89l=n^K*~U?Mj0ccRggd}fMb~r`;6R%CtOKDpC%G}%<-;blP3cxqpNl(2f}jm zyOx0a5fg36PqFz3xta*p;4Dzw3o`K^3hyh*T96MMofj*(V`a7Dq2grX+l2RuBLy~_F@z#qh z=0AB~Q&rCamDT`aJPdva)X$$umMAQxElFL<23V;6nG;4shC%N3ZjU~+PPO`cxO;=x zM|FXLWT3UJlzk7dLFOiN#VkwvKkU6_RGdM!E*vDm-AQow0158y?hxGF-GaNjySs$o zG#1=~1Z~`?w`BPk6Ekh?X~Jp_xtRsUEAu}3H~{(4YKbT_U1h9 zb7aLsRl}k%RTZ2E4NJyUORC8XCb}cU2F(hnDOFP82|j+J(ciYv@BeZYVVPwR4>@#2=L2bYz=QIWNg{ z4pHMv)Zc}rcV&J8@uB$*;UUAlHnCm5Tz^poK~B~63s8zzhyZ_~@P+l)vaixnSH*Q! zg@smldi3A*97{o*vvam5J@x`Ljbb>2`L}Kl)))k{T;A_|#CoiMfw|8QMf-e(j}Z1) zVE*-_Wt{zfDgY4x^8d|AEAQ}+*j=pBr~)q#;*#Q zyi2F}BR$rMQ>f4S*ol*I)Y3J?X0GEUbt0wluydV0oTna`Z@Utz+d9HL8Y8IQf5yxH z<=hw;)F(V|^e0J?1^cK68V}NQ2Dkh;23hf>#4I< zu(2>$Kfqi9Z{)qJ(#J#Nu}#+?_DFklr!t3R4u$^9k)9U_cs$oRKUTqS3RKTJUX zYg7{V(qmjf*?6G0ns4^On?JQ*2@ULqJL$~|kK!trW2(>|x1T z^^BkNb*>w2QK9=oT;-QX3#b0T-w#abx`B?MRB*dynykXd{>w>q`uU6|wjy_U;cw)$&d1Al# zCR_1pzn%?g2!2s8^uEQj76&BPu)IU3q;S-bH8UhKHWVY9*^|?lwNPt%sWT?Q_L=<$ zB1`avo4&Ez7>`uHp!(W;2)wku@*bDR2NfMHnp|Ix%+q|vWNBoSv7aXMdTU0 z8#nWPf~q`wV08Wx+D7ZHk!D8Jz!1l--%3DHWGE<8xsc8_!@7DOk_3yK?6=AMlG@wu z@q2jTdvQQ2I_k^(dK|*D!GokM`>9cg=tk-fr0c%(O_xFc8E5>0GbOImRDXP%i3a%| z-lrAAk|ux9J8xmHEIH<%U(#WpJm&S4>tX+yVC;`N84Ap`@P%MJ2+{5T&H*ba`j_kZ zf3sD-BtjzoAF;#Y3uop;tlHCf_sDV5kZU4Bp#c4XgM{so@7G{?n}~FXqY)y%!fh6! zmmkrUn2sF03jTO?%BjDISA6q5@F)F5zcNAGxMP38>`w_X2@CpbRc>qq829l4{f1ch zS>KHF*ckLEfHUBjIG5q#55~T14)=%^)+)9|6sX>H-Ua0=2bgftA_go&Ckx1azU&6rK;3-2bBL(T`v|^ zuE^HVgI~5{E}`{qL5zM2&Gayjy1!%TlG~Fms-$ipO*@^5w>F2$k2v7K2T54xeM#V+Tm}!b1FUY4Zk)PXgIqJz$?NG zG;%1n-WDV6K@B=ygnHGI6boj0gqzmXGAT!8KV%$ar0UhU!8RZT(T9k-91csC#Up=7dVXvF|hAAv6}WJDGE2b*Du@?$-cs@0Guh zIW1XsjO>hv6a5h1b==;Z2M~xl)djuC#9n87bt71#B$3^VcQcMEpLb2>`n>iv!t6Lj zu8G@Zz6UW(?HjILk}K||2x?{2`f^;Ql1}1@q;cgn`+!D`zA0ErSof_A@S~}2IvR5O zsv{)<;@Q^3A@Oa)#paqQ3?8wyBfgH&u8Z{BXss*H(ssm_7--g=p1i9m%az0}z2k^F zI>kX1P$s0xtbmcgk>bw!a#zdM}on$*&Wo3Tz%4t zv^p|jC1VA>;ltjhZVG?A*UR{n|h9@Bo)zkc_* z93NQZh36Hajo*YU*v<$!KEAz-+S;J1NR*q^w$6=eLMD}?@}(oAhsBt?Y( z9$B1J=>KEwyge4Gg(s)CkBLNbV@GNm9Lx`CApaUtOK_-#luTzODeud$BeL_y+KFjv z?Qq3)IiKbp^$_gc5PXwk1Pl-V7SH5LSYf1Fz|bICQN}|#^BEAWQp;4x!G4#eYo_55 z)sQx%$kT2%osjKsYvM80xRXq$Tc4Crd-Jk}{rk;(OT^Q6zeHL3c%b{0lO$A{jZ&>L z`FRL*%T=pmV%Ahp`}aH@y@V~9=7WNAHlk+cCQ(Eiq@$_x*aF@+@>jrJEbbVGR~nm*=R=o%4( zcSC%$VK8l_vwQtk*}Ux2#d0+EWz*Zs?U~2ZWf&U)i2NV}i$w~V)_`khFOi#-Utqg%H!P*U zBysrvHM&wL7RHDM0k91+{`)kgsf~t}i<{BktV1S9xC=sP8U`#*`E~@y1dL&X*Wzm+ z$9*?AxcJ#QiNYhf1&0^n z*ISn<#1i5@+?tP3WO~M3XFHtsQLehEN%qT+Dcc|}sTdEZNsez`B8NIt+okSEA`b66 zGQECZ>FFwnwym+HZ(nDNG17h3XQ9(z)M<$xvPO(`1sTfhkRa4%uUcunRE~KDn&hC@ zF=R6+AgS_doDVRldVecrRy)T#=GK*q)jbOOn0Mk)i}+>#_;aSqu~TR_h^i01kZOet zF*$_DNb?t#M1PMX=?6r~e$g#0E(~G>t4_b~ilQ9LdqR)rNY|0;_y1yhZygAXIzT`b zhMZ#mPVD5b2A?p5Ao*{5J>gGC7dZa7egDM8LSB zgzai)e&LS7n(r)D9h2c5FG@2x=Qi1pg}>gA3!WZa8Bxl8u0Gds#|EgMkMbXtxLsSbMM1cRj@F46(rq*sQu6F+`1tcE_VUrX_ z7VkyKrXma3g?CLbN7M-k5h^N?jSh=#&6@nkyK+g5*;XV{wE5-(;ufRoX8*vuoMt(igy@9cGeU>6>`hjr79*EiW+hPh;OlKMA6^*=SfeZc!Z`6kwb{T^LWk7 zaLupCC#Gs{DiMcyMSQBxA#_+~&c5smbgzD(izjf>%wzNJgCv9OhphyD30pqs=y1bf zXU$(aMtVlQDdSo@$eo)$$p%Cc^apS(1vDw;k+neQHhSo7oK}nZ-uIw?VcVobwkZ7r zrS-q1eO+xt9Gp$f{;nYYUTmG5Ash2G1eA_AOIpw##8DIoT`KccEIMb`n3=U_MBwY7 z?w|Wef1yjHF4>LBZeP#+*=7JF$$v97i-b`?R1|!MOL$0sN_&b(Sefbl6&Y68KH2A? z!vhJhGU9bXY>D`2yOvVsz<$EQ<>>4^zMGnjFWfAsQ0*N8{~pl?cWwJ!X{!(U(vDFvZ@~}S=1uqzmV_YQKS29;gEp$2S)Wy-Oo@raVreRkVYu~ zVx@-K^N#mGU_6ElNI%K{J@wXKJZx7RQ8Qax6*GGXlxEKVM3>7H$BzhMXA1>_+g^%2 zp?O;oj2Sp|;W>gr&oI}WV#8>ql|ULAL7{LUC1br08D1BFkH9X$X}my$6*uhJh?$rf zay7>66sE7ZA=bkJPRGL`8`F7A7r%5AlB;E&1#A^lmHj1XQNwIR=mbjF#Be_MgH721 zy0uerp`G4^F%~u@xS4`c?SjKKH}v6+v8}FgGu$K5*g9EqjGS;A-q*_TKr+|wan6w2 z3rmOf*Dzt)XqDL(1$^Ft=OZUb|IlE6oMD|&AMtDm6!nnt-y80(HWF5zW~P5Vd6;7P z5g}G)$U&xo2_k<`XaPrk1I~M4!0?oRGNV(n8O3Ok{(k!!&tBRb1>4*r1e;Lp1h$E8seiyWdbuE-n@{f1? zV*oy@epCDVWrhDfyyXfp0G-^dT&(^P%=Lp*rTxj*y>4;g#ii^<7B!pkodfD|c_!sj^%=Q>OsnBoBx&wzNEmPT%dMGcO8TLH8lta*I+7 zC(1E%(!|bmla7VenXf#0^!?H3ynFlMX=IYW4#Z711fX|B8%zu-g?|=2Q_KLcshd73BAa_VCXQ znpjrCp#gY*0#?N033Cd+}J@KqsL z=-fFS@9y^8)nksH40!|)^ad5?vMGRa^q@V)lk;PvA-GmUWheEj!MC%E}| zz&7{+xsr}h0CCuK^cYfn@sDAK9{{jH(y*%VWf&8rju@WszOYb1=)wp9#vpFk-|)ET zdJ>Lr0WR=)l8!h4BzS!>M-;$&^ezlQHhLEU079LC1CWK^2-`G+b&v$1!d{4h$ly0Z zH`n1eAln(k7e7c7-IpsUUI>&3FMtS;q|g#;2r(o8AO^7oslzT|tO+RzH-v4f!=Av~ zlm8NGh%`h3_+Z#lRFMA?ZiwE*4O)diC;cA1NgVVDFYpfVi0;c6L>Lq=swCMEZb$-v z3W5ia1yR8MfG33Ch~IWrDSXC4wapnh{5*I3(i|2cZKfVB<0Pgh7|^FPK5S zF`MqM$wa9%z6jIan9I2N9r20VaeEcn?@22lK!N zzyX#hz@6|tPynDP=nlh|BFI4mgof_R9^@bls)g@C0klzoQDK3Ro1N&soIw}Dpl0=OyFsK;b9~lr&0Y-oojNB|ofBF=ZFAQpi_eTXxQ-I&X_C{=` zqd&0)83==_;r&qn^%UT6_!o?zfSApEQgAu^3qepo+@=93I34~4Cnz9x6G#efgMT3k z3W(q2M1P_V0*ZiE;r-zN0Yacocz-m&1qB!e_7wvVAOgzAc*28y#Q_9}f`AxLXt1wX zfL9SvFUAvDke~?Y2mBjf_BTBQZ-B^w4*Sy>zUTlOM#pgUHxGJ3`ACN@kiW?o)C?aO zP#63Ld;tBbx0#RhA)R4a-n{&0@;`w54_OSMeZA;lJm^=EO#}ERq^|*FAQsq{|5=k;lBms)Jp}#Q zFV6RG{!vi3!uPX~KF9@Fgs;9^Of+47{)c*=KM*eR&@MvnazQ3jp%*S9LSM98`r}>t zJFw2Vw|cOMxr{<+iO9J`9AgSDPtjlF+3>mWpbz?~Gy2*aLVE*UjNk&G4*Hp2qH-I; zfvOf@dVdn00hZ0XL@a^f3oM!^&!-nE3or`!d0YXk33}Ab1z~|6Bd0*wmyb(1TWy)f|xO<)f7X_z3)rO!Fl7s`YHp?d?y3Cj4M=q(PK@Wmb1*4_5qV-D!?@)1S>91MOYqj@rV zdWm4~iUpFm^^*)-#Pe<`Zr;)4g33XUKNZ02;Ad@`C+?@0F02=5|A1M7o@k(fbHBmx zMLpM6{r25b4ruA}5iJ))vUNwoyCn#?5OfQJ{IrDvc>3~@B?n}%eP_VE)jNFA>(n0* z4GcI4c_r@c-3sZA1OlD=fx{OUTmx!7FOauk_79jQu;09c$QBLsNRkT@+`1Fw-3kC) z1i1CTh5=vCLtbHeUwd+t#Cx1F z4B|acp$73DrIdkq&r-}mCp=VWy*;Xkp!YnbnEn>Ib07QW$|QJWJUK z2BNvi&`y_X=3tIF9Z^7@4oAh1r=8U-U13*RTJ9X8|F{CeUbE{u!3*NkhNBG5Qyq7y z=4I!3iv7070G-{o$2h?Y;Zx5Nfy6zF9q)iaG_Z>O$ql^j6sC+O(>VoN+PO<(k9kn) zI71GA@xS1rO6i>AFLx#BCD}>Y2nWVPe&3)p-bKc(yd_-lE_V*fpQ0Srm!625sz9}J zD`g`d2&aG$sK(rA^CyAir&7ws8l0@ATuoHH`lrju>v z5M*`fS8LQcFxD;EGoLd8D{9Z}8MEh8hjPVfV{!=ym$XuGK28NTURj@=$#(McX1{N( zJ#%hF9BvM}M9p1HrrWX7ab6@y0t!$J8A`L z!mpA(LCKnv+txIc3>e7XJi{$0_z6O1``sa9aBAn+FD{N(^D;uOzBi*q`@+Y*OTTtD zcJrRzM{H2Cp*j@@cYw8MhNfo%v+UvcB$1QgVU_CHP5XhD@;Q@?fGJOnlhX_HV_i(5 zuke^~3@+Pg+sSGCR0FWES&$X$eV(vISeaW=#E%dWAai{>UbR}%l-ULux5xoG*Lbw< zAPzO?duw;QhIxC36A`sOi!)-*+2ohq59w=qz_mJiCgyaT)unX(X<7dCwKc6;J>Ys7 zz8+>iAcA)_`(%3omBwxzhsxB;CmFAo8{1jhkhQ|j)*h&*hgbT!P#>tTH%H~c$(4%C z>{k2$lTry+Lzt}9-AeLV*lNUbk=MztyIaw?mx5w0=J*Rbvs9&f^^zT@*Vh-OSgFv5 zwmrOadxvyRcWd6%?55q?6zzLcDrcY4E~cc|WP9d~%C(v`6K4v<#;(Tf)GAx}%FC1y zw%kPfh`A{%-qhTi-Q4U>WC`o&)z$2_#3Bc5ga*vd#l-5(OJWc8EhMd=E{Z6eXkV{v zDzJjtO&w#HNs~&2Tn0Z2xq6TKMulBn&HOhHjL@SK-WYd_=mkgWsj-o5jn;`Mxr{>1 zaZUz98GA$Lp`TIqY^6Un8-Gkz$&q>+?kQli1!Xy(ya9UZj{SJNK`j-by=qcfHx=oI zZ+`)JuwBDB-Izr@MKm-%T&;q@&7%}f0nA>$yu<+lUcPX|0c2jjG{ga{UQg(JFDh4H zX1*8aE3iG^i_8_6nD52<3S8*<^!Xm_9<@+EPOHl3uO^^O=`Sas{J~#OKpER#L_itY zUqwI})?Y?o_Ax+5VD=#_tdtGm%b}`L?(O!=w8nZOXH1gq)EKzqU zogkpo_e&sRAx2jF%JxGC?fUfyQr8Gm5opnZ&u?lGOJRKRV-Zumsx|?ZgMog%ZDbK! zY0~*ThxU%jvSFfI*r$yz0NLKJpMCdM&c?>3P+~RK+hr&3X;A6vA@qWW7GWtNTB|j- zQo|=giN5@2%cDLlkvNluC`QUC&+FAAb1qwH8Osdr?3QBpkh3gsQH?>Jnt=Xds5| zX35WUy5N57Qz3!WPwKZgIfgpJLKT6mh#-qBwkZCtSEq+zwIgz(d8ATVT1}_jC{Y?w z+s;CNy-HdB{@ZmXJ*cgGTFs+}!mn)QckU7ULjHwJe&stqj|VxoL!UW|Dsf=8fGYn( z&vmBt++Fmw>2x$)hzgKWB~)3X=y_N=m$1emQM$P_5Uj~CI6N>6h0%L6G4oa$d-KKY zNEQ~`zqG}M1PK1k4musFT(P(;aOIKm$`Yz8`t`!+6%1vZ1FQg)4E5hZTT9; zHF}2H#ka~ZY8|j;p=6gLSG;ZR@M|UcGM~UHq;*kO5(_!)>e2pO>BVp?q;bET+E6Ats}<{r$1= zq7hZ>s!rWKyBDMB1QUt*_a0bP$sIPF`Yk3|yo$wbVj(b~Z0(Q&g7@GxF#;UizrBAco%99Q}abteIJA_VM}m ziZVKew?WePu?P;Ga@sKaAvNNh^wmvb%0eo@k(d1KC&Neng=d@J!3ZXA$+sgd* zAEM$w4yICS;Q^{yCDUI=z3%THYiwra=%-S^eYulN-I7mq+c1E+&VhVta`-$(-h490 z;I_GX6r|obye+#+gf>>+H%BbEa~>R2C#co(oPY-vqbF1zesvICfP*H7)=TrTswSZmIV`)iyJ9T_F`pl5jxV=liQ@v6nZE^Gr z24zSCTPCz`eAiM2UHr;eMi{V9c#?YZ9=}#Qolwj$@z;j%7b)63l!wDzugng)idzJ$ zNH1zjqQFqzLzQ~=Qt`!|cEE;bao6{Lne;;jGcSf{+)eF_0*FQ?!eCQUq#r9H_Yk(Z~;LWXNS33cXTRSEp@ zCu!uj>pCSyjI-tAh?iqwr`Ph-%L)%Z6U%kyphMMzZ&5mLcyK@^Yz5G!mE<%>44U-O zC)rEO9%>%nI4_#E2j;A7FmhcJl|=%@i7k0Yr6*8bI<=Q}Z?|uwJinGOGcos$jk3=( ztp8f>ku}TsOq`k>y*i&_N}#?dDyag*(U#otALr!I=IvmDo7U=VR_?1~F6wppoGw2) zX56JPfSjVsiCfLqR`GOM(a=FDLD0|GIF^;|mgb**BJ243^ZCOzhYc#$xmwydmU5Sc zU7Ji^Amft>ed58d?=qOnZp5GVr)*Qrh>Xp;J3MnAXtT*}C?n1K-l?b1rP9CGxP+Ew zZeE$#W|jT4@4GgLjsg?8KIFDfs>YyH86{?t8Yvn>+g(%rqXe(r1wee>m=zYZeI&R4 z^HePjKn}ZXTpBU}kh-D;8K+@s4XQoYPM+nS4Mj1wy$i^_d8n$n(848Y8_F5q>qJ(w zXW6KYa!qJi&z;Pcv(YXoCUyAOW~B@HPK7#nA94Lwp|%Kml&hIWAc=mb3*{ne$Oxlb zc%M?4vU-n5SqJ&$YF~p2z<}i8mzxF4>+^oLRtB5(m-^;XX5qNT{tepzbPKvJ!-TTI zAw}|g(Z!=Kbvj_0m4vOYOKQC0cOG@*R|Hcf?p2}vDq4=5MhJyt7;nwX+Mok9!dqD; zhaP}DQclE=eYax@la$t`#?E$K94<(MPI^m5k2y*lS7$nR4m0rywjnJZ6V=gz{_Zd4 zb_A`Xs!vYsdhJ6!DlNG6B!t0=9AjQ5saI8w`#;#R&w;2|cJ1F>`Ja7mI%pG4FNicG ze$uq$L7Quxe$(nUlSlpCbKdjPx}^Uony8Er-l|#i73xGW=@5Q#znb*5k`yUC>4!>b z&Dq8pW;^xzeC}1++`I2Q3|61isH$k)sf?*)usXQGq^dlLyzE#VIA78t^cFt?oIG`C zqR9=lScf7R2Y;}c!|zI9V+IR*#uU%lZ#%HCvLM}aCsP4U%_^E&^>GvQm>k{|&%QW4 zzmIl=8lRA4s-ORe%4wHWDd{C$sL5eEb!CyaM1dHp1};zVkgkjQFpXPx9EM3Cu7j`r#eQ|$sykUl2?^b|IQ{6B5OERRxzVZw%1g4 zlAy(v2S>eu0gLQcC-pb}a_Yl!GoILNB~-_GQ-_qOKd*9Y*i}Tczb@L{?Ms~T@L>9K zxh>KM4rj&xe5dhZy{E9vR4zy9Wmg#kaLH8C-q@zU*H33+Tnw{a{76GJkB4X)Lcnyq z)W*Q@X;2*}^Ffcd#+2ip7{Y0&5z zZ_#u|r*N^U+L8w+Juki@{dnC68p8~!jzBS^g{?44_rv>r`O6enW}*$%x^?N-lw#9U z#dG~AOvZ<&LOv$F(TWJZSajksWrRsVx&8rmMy7uB?WrMbY||R7OIIdzV2!s4EwtxAG+DAa>~fCK zFOlIWO~^3$);XyvE2Uzz17pGX{2HT@xIlPpypZ8C^1c~G|;!&f3c)-=U-cEap;z1%WZ5{g<~gGxl6 zMHp={XC)?Q`XWQkkEy^+Ro3t1?_9aaCTa93EnL|aeQQ?neFu5G492u1fB5T^M?pbf zuINUurizEvvCZkHP5hA7Vrk66{m^eR z@7vQiwkmV>@UV}0lYD69ce(Lpv|W4HapNQ$;+Tz-&Ak)2$mFf@7zjCUNE+tB>8e9* zrrGfC!|9!vtLf<_`E2tz>F&D{yS?(_)a`~%ltbXX{!C?F*8{ZStGUzKBig9G5JdkC zkL6R|4LOQWKE{5><&V+f$Mq9Fm6)s`mQ3?3}3e25y3BO ze)%h!n0;plE|yuSFnm`-sgre-E3AtS`uT3&ddB*d57#ejJt95yy2;! zw-}cqzg$pTMX;o8XR9QZvnj_yF+QUe-ld}zx0qNUG5@&++%44G`ap(d)PT=_KCcVc z`t07UeKD9Qm7vo$d8%hR;ht4EP5kXn(IgGwTGyt9(j*0Ig^>Yv?@76>M-%I6`U59w z0x{~hQ z5MFl*AuxSMThVCJrQht0vq1bA)tj-N*-53`-*Lv0sIi6L^m9aNxo5s(;d?XkYIILi z$@q@g;26N1@&y#b=Nh$^V>SB0g9yvGV0pK!?qnRDlp59|r<@hhQVWF2Wb5u>Y05)- zU!)F*jyYw zy~lf!X|}2jQ{!CkwvA$r`dMRor{GcL)iG-k6B0&kN^EGG7rI^o9Zqi)^8@%B+mI@k zU<9d(gSQ9TPYog5>4&*zpNKDVo3`&yF|wejM=xJaO*B~JK~Ev6bfnJRK7%@G-5d;W zX@qoN)g2)hp`rI1%&Tkh2Ik)}k50&X<%W*-LIg$ycy$3veI25>mjuGJZ+4H=4 zZ5*y?lFZib-7N{28+rKiZ*dWv01}7hG_Ig);;eEE1wGrdW(hR6TTGfYYgeE0kAn2T zMd*sf%)uB5X|d2_jcj_R=I+U?PJ4&)ZuJZ2dh6;&pa}vzHn-MVPtSE+TXA?XIsv3l ztXP0Nj&HoeM@oJ3W!Qf6@)fhQL9ds)ebK?~({LDNfg~^6r47euoGi03-gvK}y~P2E zwsqt0V~!^p-K!Ps*n;fW{jE25OC*a1)=kL1l`AJrIRSep)RBHHEJ!y~sMyo;0iZR| z?*kcwenbua4Tn!!GRusGzg?K=1}y5;=~Fq?Hfx7H^!+o9z0ht7gFf+@`W4BXG{}1{ z3sBm>6}N~koH>>eA?}Qu6d@ROBJaNYB!Wa~S-gZs%&4kLLq`$QKj+IzCdb~7_#51K zAGl>9kve85l9`Y)w3r;jgW;7bb68ix;<4&9F$b96G!raTl;&B$|6N-zk=C}cyU}{7 z(Ox<>kA6}>$)9k&y4$KgYNZPFWdhk2w#oc z%oATH`doa#G_hySuh3jjB5kWYklnzdyF5hgKGHk&8L+I4b!}D?oqXSP#FU0&N9h&p z<&_wk$Mlkn&|PHu17A6FD>>woXvxh3)*{h>i0&ktg>C+~Pva+N#d0q$R?XjI){RnW zb-S6@M+Fo&ws0tAZ_a6BV~0h&toto|M%FBJ4kjPM=0ASK);2jqd0@=t@!fS z{Hl?5zf{E&ZOe`bmX;lTc>TWo()UIYdYob-khTqO$%~8$s=H&ALRrts+*ATKP0*@Z z<=R^(?z`Wjmp|ObvXMxX4dwi*82p0YUK|PU4459`C5TMqB@B@9f_o{yHapVRHDA~N zgll7y;yAr|uLl-rxQ%p!Vthd3zDB0uS;8E}NuAEw43!JrZ zgR!vLEe+uL=&t(E@XL2TC|ek#meh^L*0#)by+lQv38U#P~E(Qm~TXAn=<+=3{ z=OJBZ#*C#&EcE@XPDveV^!9V9PeM;ZVY3*GAeqw|^)l~yIg8<|(jl{z%1$e94Mtpk z&S}f?g}_Dnmz}q+6Q;2X=QYiF1rc9hamn*&HGSqCot)eOLwuZ-Mbja1@ zDBZK*M0ZzY16Bw7wxzq(1^W&8P++=0Rv2bv?6Pd-GaBGyv#yJ&YbmJMP{%uoYhsXi zj^s2_HZr4;vI~c?LE{gwTd`LeK5rJd|FTpW-HSbBQdVJt?x|Xi1zUD)dfnH!(9Dh^ z#4>c9t*m%=`dq7o03Nu!>HOz?BCF)9H++vO?l!3wQhh?VufEwR77Npp%qfcBA%|`} z5uIxk$3skGObc$eS_^BD5Kzz4cRbdXfdoPr3~S^W;Yp0lvBQn14(&=-SKuG@pF*2+1T^H@3VSj(x*NyL}gjuZx>o6t{`am zDkhc1X(L7rQkPN1b#pIF)>q!wF1zob3(RuS+YoZUsr|k&rR3ucEqiGc(TD=K@j-<)-nKaELg%LSo z3a?U1y}3MD71GWli82wb5tP%hE{AbVZ;Kv#Mc2D@(wE2kSJivDpN96>1Ek%;`Ke8AOsJ2N$iLlJ z(IOpDC<^OWF(G`wbw0RCF&mz`-8vvl@Jg`|x_X{}b29yN<-A1oVy{_3^>ZVv`ygov zxv%6;sxvP}oXvIj#(j3ttxU>}o0eT>?bgbq4Bdrb2&G}Mm;07Nf9;r&Tt(X6RGAaf zq^6zfG%*TCKJ@LCwrU6A9T83KJh!@M{=1VymfP{SkfmO}P+wVwAgXXxdwM9PF3%a- z(^=?feud9?!Y-;c;tf$YQM44SSR6&}W_hAsnlY=48-%5lH@#@vXve z0hE&a^a00oOJjazSF>WMTN1AM3xSxWh~Mvr?Jp}(Z1#oRt-;h9V@A0;4|o*cc=1Ph ze9m4l-LR*rv4^AcbCd$Y(AEVQ;29q>;_E(tB%;))zAUvoOU6PD`_#~9o)PAI5BixZ zj7_cH3>G0Y&S}7a`SQv8!&&|<_&Up=FMAH>UhSnM)f!&bu}PUmZOi<*CDcuL2X`gN zUfun87G*CpYNB8^_r9k{b7IN7M4=O9nW8%!)iJ4l-!kES9acTNQQgGI>!zULy+1G?&6`HD1S=f2e%en9smZi)R?Ax@sXP{b zFi7Aa4uL50GDq{ri0$CJ*-E?1vI^X-!BQ;_yt_l*T4>b{bBekJx-ne(;}N4`f>Qhn zMCD0Z<9Hdj2KWsL*M6KG6q>W(;X*yzA0slxEjHBVgD3)!D`33ERg$Gk4Y5j+(6 zx`6xnToc#yti_T0t)Zv&cT|a*{dSdJZin8z)i;tvh8BY@jNYqHZ#hsFCoO9&qTn%- z!vLrlSrT9yo=A7kZmaLIx|H4uIRjbSQrKj4>N_$sSqnfAsxQ z`qgdGSM$xJ`+9L2g`%m3rsUqK;nCz4-At?61PA%rk#S~a#e-VlO$H671if-;A*Ahd zezJndpr>6^yHJ2`f5swy{4pB&gyhHy+6A2YVT%WtSm08Dz1A0ViF&&-%Go*QQOTa! zLL}h5lG&O_w#M7hqXUQBF|zFK1`!mj*D&eoKJLTW(|hfExSo^m#3W1JqcQK>Ia&QQ zQ4gTs90Xzz&>Lb!Tm?q0nAVX#X|D`BOZ)WDzMXNz+@w=PNR~Vo*|!oiThv#OA3qT5 zl!h6>V^fUekWHn~*{sj?*JMR6tq38IRYbh=6rFRgGqXDC>JtA)P2v@hL?M zf}Bh&WwC&53#+1lRcRdEEv(gift(E4*Gb>P=aAS4_p)UH8E?@a&I#hPETB=I=elrDhC27C|QGcKJntDm-eg|D@u!;vNi1# z!3G-JwOKXEPgfK?PzzDq8|!c%7)$fTrYu9N(nFENws6iP2KXa^KGQvU*=zQSIM2wg< zSZTJVDAH(B=o`O~>7>SkC|->Ymg2C$X}665yu9WJR)t4ULY4o7NO%I!)Qhy0~-$r|jfOOd|rda2)%-)v9go>=jnZ%~aFnstn!j+vzCj94rT zAxo^}P!R`J_|)P^lQ#|U5xk2{>sX6SO{;6D`5JUOjA6=7p3}u2HsZ!!GfKX5whw1dkW zYyH;CC*MD4GFAADT)|6oqmo#SMAB(t>LgyBmg&Tz!OLe~wyPiDEG7w)3;ji|+RY>ynganbhU0YSxs!92c|PiBwu{ zT+43TyCt?pX(FSk9vvqnS{s!xmWniK=LX+QEOro%8qAU|&oTt(o(Ukm#dZJx!cx+# z=Dv1BiIrIN$kT?8m1lXQS>c#}@qI2lqVtpllJGTpP1$-=$)-~xqp0z{_#--DToq|P zIYKd{c#Eivne53_MR6g|%fiFwCoB?fqL)mx9z-2DWHWh;DN^CEuWS*Qpo57LtAOQI zqNwqmsbU-}M`p&<=4PjJ=nJNT?lR8zmEf7hPcy`^kRr2Gf-0goZW)3iLX&itwCCxp z0JGfg9`NFKf93uPJfSJO#FVBBN@_Dsp@jdc+{8BB$B3re~32wDzvoWhJyjd;BNuR6`hN zgiY9p=SlRCpBON#{LL}?Xg{-x;jIIBsMnY2vhVnn1jRKY7o=rMob&3#jAWs!ksj11 zvcN`H6{stELPt$a*~zXU-tIPz8ZA+SZ46T->AK}HCRCM!dVDM}xgsX+$1Uv4heYBK ztyrv3x|RJPg*~RK9X6Uxf!!HOCRNje2eYZF(x);Cr!C=AOj@a-D$ExRCL38(kd*!I zJh@5LO5AI7Xjo{%M$9Bte7Yw+s3|)+lSI7+`4I*sW0QG^GWVzKVwD zf*`GMv{M)5ciZjMO$-aUJmVfjPw1po)??!br~#K`17|Q@sUzDxWl3EQ<9Su3hfTnA zfNNq}0QV^6eR2{9YYv_pNH6&cmL?eRZ?+%N;T~4%j3#{AYVcz_rgP|Af%fLAF0@dv z&@U?r!dW}gt2~zEk)N|Boc&_)Y3YpM{3$xPx!#THw5krZMY!2hcA;5SnIwiKm z+c~QKd_zc{Op*_XqgeT^5qL*TP8M^NXWx9Uvc}g8kD2Vu2o@(%FYKOJW;|-JCo?aR zjmdjDHn} zX&RsDwx5Wv(uw1$XmrxSipMdNw_A=qm_e(I^0TcSa>nF{omBz@&ru5qT)?iYwn zt2QVeF|7!jvXieG^E{bJD(0W^&1laLCbppN!H$pXST55(*@K&y_IvwDjH;iI)(w>~ zp^C`$3F8*plqyIiy4GmPF|d2W8!_0ESuHWlaqilEQLiU#>`cllqa1JTb5KsUe5_@& zO0hGk+oz~LW%1J@pP0#{Q!H+s&n8bw&D0s@ktLIZSyE_-@!_#9mAs&cDZce+udZ(Q zdBXrHi50c!S84`EQ?p_U>P-WhqEq)=?;ugaU(6+7L=368rLB4Wm;NMJ%WA^q-g z8%-4yj_{aCnli*{Cno|G3~GMrB_g@0mq>7LoG&78Gk4;IFSiulxP{J>TP`Zw$0<{| zje#G&e~Gq8-zRqgXzv%u^4T)=1i&1@5x&VGwR&|x#cWhLCW)CEEovI?O{6Gxl?gvb zF3#gChSlj=^7yDH!0PN9pGK2P3SW5{%lmicR2q%>U~#q_H;QGBiGDeZ^Uk`+F&mu} zl!N``(7v5Kql?Rl?&%zQ_T)Wf`V#FopO~D^ktnIEdlBws=+xm9bj6zcBJLGcS$Idx z-ETB;Tf=Fx@xQ;!q-rSZz3RlwI+qCBcIy0?P&?UE#gk=$v|7r|%^VgIY>uU`XDWh1VanXxK%JFrj z)0l1@>D9BxRBEg-RgK@9HEK{oRjAQou+ca`RpAw_@A;OQrvF|tXq{(oHur73KZ2d? zWTjLrm&vzAEa&sG`KHn{X(2b7=oD8Mf>TJ7j5B$RQB{TdOD_+QWa)hYucV_vs*y~+ z7UQ;KCMi=NSgW&-yUuU|$7u)26=!RJRUovPNVq`}GZV>fy%9&*ND5Ns99Nlc;}+Va zCWtFHvS6GNRZ%{HnYAftnU*3u7lz{=_{!AF1IUCnL&>73polr6tIF%&rgP}Cy23%U zcY2g2Jk~~Qb;KfN#P}fEZ}Uz1u^HnG)7MoMC&bHV86RqEzEt{UU4d1tE{2v_PV>tT zMJJ|M#E5PVK5DFmj9ShJNy5h z3`IS%3N?L8%ol=)SqjlVDBy9$^kSFfT2b-3xw%JIi}TeKmhrXm0AFj9u^Q@^eKr{*`?T9D_3YEwBV4`Pl`hJ&*D7M| zPL{3;#(WV}_IZK?7>K5wA8|LMMjPXT{8}Ww!fMo@ObMaKJoV&NeUnydkP+q^wHWUK zzkU+F=e>#;_Rp8&)mL0CnOrnHVI$6{qI{5%U#vNuLmyQY#CW_$$k>c?Y^wBeQov9o zPWTq*JeZ^)D<-b8q)Tl@dgSmttKtDhIow>%OjhXx2Okh)I*0CO751BvV8@~!+F6(N z$R+E(#X+92XqAdZwCGa%N-HnYUf(S}?x~Q_#$BpneFMc&Dwy=ZP@rnTo@1F8cHX?9 zNG+tYYzU=X9)B^{PdGHpp%&EW`dx9kX&zHi?TWj-ltr8G8D!@|+ObSl#N1NfYO#AY z=dJvrPUbNRxmy4CXKr8Xw@^7NjA%YTSVUg#8)0#a?x7sJ6{C(aIo|g4`j;*5M%*toRDlV_ z#6x*`3%|s4Oy&r9Uc^d7Q5XUN)ibYEn zQ~UK6PNxwE46cbSoJC^#XA1J9mAaq_KdLu+;?U5R7+#<6FTsuQ3oYf{am-7p<6JVz z@43DLii!Q>b{oSWI2k6n30+iw`6-y%C)Gbk$AzH3P*fEvdafA zXy4z}0p;ulMe$J=iC7I<t+N{$MLS&EYwj_4y3*bFsVtYr3t z6D1C3^9$s3Bb+Y2?i?Ahn8--!CdA>8oaq0efYj+Ky_if z0+vPXq|{N% zalgo#0zBbG;|B+UyW`?uMzQ+CE-|XAc28SkoJ%DL5$u_!1t96R4`7pjoR-8^S-&fpEEpj&(D|6!GcJmVbl7XU2q~X8p()1V z^i;XcW!%D^(sabZSd&)jSSqYs3UTkA*vhQ)l=z52t$K0`awa+{S4llOwhJf3CmSpB zS%F0@S*+t1@VI9m9W?ks(zN*}ajAtvpU>5lPkSmiC}PfKRT*YRSZ9>_f3tn~zSRJ4 zV!V7-kX55KLE=FqR1VLgs4O%o;9W5lQ=e!LljX5!`TNLdUz}-;=^$V+dgA4a&1Ow> z>TxepbEQIyuc}zu>cZD%H zw0~Uh%3A*WPndf1I2u>*cd1H8ox@CTv8vwz|Je zv(Jm24lY++JUCdT&0{lp3_VhmuZ?B{+jG2p)`+4;y@gBKv!@lvdb4rV;?t9EE=N~F z--{L~8cGW1il#&tMNn<3@iL@e1Ct8ohD3_Dt7dG^}cvdxSp=4)nh zii^&g)YOdPk`m`N(N2>!uyAO^*sgF4iv;vY<`f^DCE)6Dr{0o#F~!4ygeyC5Jgb<_ zaArxG{kXt>mN#cA^0g##Rmf}~KKD-O(j9d`P2MLS7IH1h>S)>sPM5?%2{fy>X0xV> z(-&rjVjRBM`popsM+4HH*4)y^NHw26-vBhH*(OX zg?zMCny-z*72@d->(eOYd@zHeZat}j_7Bd=SSmx@r!r*umXZ}0mMoQPsRnU=HAq-j z?P_w3#%Z(Hpl#644<@F30*{y&`j^XK3%OHKRW+FP~+q#rbL}YgDLV z;+c*7!B;*vaLMoZYqp&JSh za}*?9;8eS%_UDvY59082esN&QQfk!JP=$ruz*LszCHUKU7R9(gG3Ct_qyeAq?!zxi z#CYID)FefjrTscenN6QVMc~MAOoAe&s%rW)K_OF9jgRYC-^|ba_6vBtXsUBcv{qac zO$)tbSCA*;JbR6A91IJk{KSY{HQi)=w^KTh&dX;_Cu-c9{Q4<7S%`P*dumLixIw-| z;>Tjj2A^_|hAuB=$sE{@!5`Kg5lcP7#3MtlfaEb*Mqp?iq};a2;keH67Kx=YN><;awvUDxU2 z$qa!+6arlzDOa&7^hs$8+er508p#POQP|87$1OBz6PFdzGJDmgM&vfL#BmFauQTC? zii)9BF>=ILb?`eCuM8r@qepDIQP5T>509Az$I`ig4QlSka}d&tE(n``3oLR$oLbF)( zNBWC-R&(jd@vuLubAfLBiIMZJ7hkhPmV6x}c2 z1={=a6iuE@QC97I)~qd}%CKmum_2KItHKvE#J4hrY=XQ^$Av}djmPrPuo0c5_M40e z2a`OfiJ!D0oRYDh9G@`Thd-hiNK1+GT*?aYS!QWQIHkE`3-i5v){Lfx4Lx*Q z=X4I;(-i2WQLM%gHO$JH%~PUOG9IlHFIztR`ponQvm7_WbHdTfBM&ac?p~-vER2&?nvUKr09d;uY#}6nw z-^Z}QY0zx!8Sk+XrKt8M8!-{hylcWn92VbB z>x$ApClWFSoW&5sxQ)gI;XCYQHq~WGK`NH)Hi%F6KoHG#4sTXd)Hel`irEvUy8MnT zO^;w)5Wx)ZQmV-3m9mdY`XuY_#!S*^MhvU@&WmFw#a#>^ zbj1ujTj+DT0yQl42T^jSP9MIX8tAp#&CFgEFQ4TKwK#@Lq>dl*qmF4=?3!4iYZDU& z8u7k9kD}%p3{+(XDnb+GE|o%y?NR^_vU$CDQ;O1QACk7jpfqvRLucNk`%A ztnsW`S{1V-R~LIM4b0>*G!@)uZ>k+Bb-0yS8u0u(X7a`y{-{FPR4N6W-ujlyMS8 zuf7+HmN}2r)Z(`f8#LZPx4Y=ACX1uQqqw)Hp8#)fJEDgn zkE@ul5$AQ$BkS9W1Pl}Q2p0<2V$rF0Fdad?$k{%8!$91}fX=tZ05^D_)Vs9YmQKE0 zvByM}v&KXfV3@R0Nj6~?2wt{DX|LVG^^sC$p}&Y>Chl*>B4RBl&FHX~etACuYe6YA zVF)bPV$r3YrO8|RN*G4szD6t(`9)S;OE#NW*#w;X7OcbtH{vs)I&k|u_Z$xqSST6T46!QBJR6`cH$#(>eu1E;TS?buxw)JLo02?c zN3106D`6xAwY{AK!PY-M%k^!_qSg2ESw@e$*rlSi8BLX+w!8$&{V!X2e2#WYobX zGLgQo_Wt&syesGa(i)#=AGfdrO@|MjsT;*|c$1o9OesXMTuaeXV=E~Fudml=9mUE< zX&WQ7so=`E`u?%#Q|B?QZ=)4Nwzm<(suJg^5Mw?^MP`ijbPEKY%QKPb&R!Wr$Qm0O zU&1IwFS5rbBar(+g(k8Bd9gS+?KH6~XtodEY9Q`oK;hi?)%r%5^%Sm5ThERU2&;{$_utI+CGCzkqH+rgce)Lo+Oj%dN+2#X? zU&)K8de6w^0c2F9J8d-{X9j!y0^XZ)6cN?vWG^~Uda<~@;3Ph6T$!FU06B%m zF8tPl4?d~4aJR&My;G;%V1z(#c}jWd=CqHnf-Ieco3fK#Q@nH9jH9H@G#PWI3Sx8E zt*g7e3-~z(8m)&>w}+W-yMdH{*=``ymM-)c;oeENe$Z&E$B@!-&cZv%V@{i@X)2rK zIYm4>*D37H_ThV01L*=VE9bd9CG+^TenP%5#bBMHl^&5R{CGP{RpVJGPU7XwlX6J3 zUB)%^Wf7RkV@zo(^p_mE)2X_!IJbDxN)5F%h?}&MLYNAQnBqPAH9Au~Sqw61r7lQe zzTkp7F-OTwjcG5jP-50J!jEt|Y}SwDKJt9_R92k^|5?=DJjkYRzRrT*9BebPV6$EH zeKv_%(@4Sh8EjsE#TA*<#Xf4Fz)ji7zAoOaKgyhcZD)yvHOV9+-z%6c<|7n@MJGq~hk;*sraG-R0I9NJ893-7oAX8FBn@+vdF{C!Ct%C^v7UZiKbf34oD zi?L5Pms7GSQB-NMVpzzX%TqpVW3RAqH46B-2IMgcc?Kju*ML0PDbIl9UoQH7k;e=D zMO>SibU4VykSQ;B7ikVvSjg3tX;o9Mvc|oft1KIp_em+un9LeO-OekC}NtbagnOZG$XD%%tpj@0Wz0%VoZ=gc;qqFuED8T^T6UYjkpy6Yiu?PWmhLSnjLTi6uo;U2QHdU=0ho zFItr4G?2K>Oq_oErhyKylE)6eFsRw*@**I%_HwB!g9zCYBI&7F6GG+YD2oFCikNdb zsvVZj)I)ts|D@|ZSfX!9?-nSqH(QuyNhQo<+tkzR60Pbg+qJ02bQkSR9wV@*;95R9 z?A$Nl@mx_ZEug*kvR4`uF>R$GmO|Fjpm3-3>er+j1ShT3ab5UGtQp9X^eTN&?>;c9 zG$-m74^4|8>9IUKsvYf%GmO`g0R@9^d>kk1AIGV#CfuYYeBjLFGAtiTjOjeOrf||Y z;1RWxb^E-4oAwjBZ&Rq(iRb0B^iQYkwtafa&{ZBCU=guRQ2j&YLQZti10d}sRK*==0uarKaT0YD>$M=#o zIJuH@rlIVKva@~ou3>;53@gf|jS(6uyba;!u?9uVS*t4cXg7LE`;^bNzd1R+y@+|) zu1aT4^~8#+h5jPNW_X?yn&BeP6`J9PkP6?XpO9G^d3kN2`8rMdU_}DYTTN(3?TRs@ zzEW2QF&5M6S{nYjCnjS1Yxq{n%Gt(tmRZ7;djSxIO0t~DCgkuN&dB>REXm`1dC$%$ zy;=5xp>7-{%wI<9o96opo6(`OGn0FNjp$_AW^%^)$r;;{v(ZxulbVDY!?&o&;_KzJ zUP#q2E-s_eYs>LUI$%9cwz7$Xp05t555JWa%s8Im=jsihlJ zHQKBu$cMgBDv}m=ZRH-HMlBVK=9}V;6pd_?t$PKwj0TPKiwaYgsb@R5{1AplPPSVl zT#FID6K^Jup_ddn&?Jsx2n)F*wWC_EaT4KVY`PQU>@^&JRKZgFEJLI+zral$I}}%f zl#1Ci7Il6NTn^p#zPR{(D*L${^+t<1q%m_I`R%)d2!%POcnStZ%qyy@x))eYn#%*o zgn5Qq$u)g>%-lG{1H?g<5l*~rf6_`_FoY>#F?uG?%bV{j5m=4Lg&PID%lV4g%gT#I zu2{|Bg0WL{ZiELqfQ(`&S^v`MwjV!9XIfN4`7$qRhPqL&+la8wSeJZZP{Dc4B;`U} zYn))-yPo>11p@l##RGhvRc10Vgk??AMjW`y{3DyH}O_M)JOIb*7# zUz-4yK@SWCK2}USZv6tD_~>pF@mrZ{N9lnfYv~prUBI)biB`+N)ealoSk7P; z@ofe$2h}`1Y01?o`D|;XJ9SL1+9J(4Rwd=2H7m!6D1AkGpOPL?jXiHwZz&RU{RDiw z3B{kcvWzU^Ji5Ju4$l-f^V~BtOP35&Q%Fg9{F=A|BPe3dXsY||lTN*x6n~RGsVOMO zOX@^V6QvQxsFN)OIw!CwvA8z&BY7Nr4sSwJR9w<5ZO#FQ^iC)0 zqBvs05aq;iO%#hJ$4t_TsaV=mr}2bZvR0P?TP$0K0R*qwOr}EtiQMx8irLIj{2Os1 zD@Qf@NzK`eiSB)R%U%J0zQ8~u-5*mVP_mooWjyaZ?t)Dj_jf5#>??7(&9F4$H1cox4qalWs_)qErTHUqJ@_4&cX3e1UPyZHb|zPXH8P7&a8EQ7d| zJtH-t%lW2~wHpilMdtI(sHc|{3HWvs>1khfR!(-)$?0*g-tFaOw<jIB(qHC%xrYEt^t7ROuS!qQavI6~yd>E*K@L z{Fo_11u+|-!Wm)(G3#65G@+dG8gpnRC)ZwHRf&Q!61Tg;Gsim5qJp7IKHtZvSTOV4 zKvFrl68FcItVq6@>=;*(gl)f;UxNd$yK&?nJbz;wKnMj zFn*OG78yQGyPce)-pB5&?d4Qk_?$wi58I+nu)?i}g(`>yR_!WgDtYQ{FPQZbuve|e~1r1a3Z zA1U2GryO6U6Q!u-+9REqStCpX6Z@=^MqTdW(LB9813f5`oYC#jsoin*f;*M&stPnX zE)Tn-lTPo6>2;gHd`6Xz>@b)*YNz*1^b?p=O`u41+fPIa>BO;&DBt*GP{vguabWqF zNy=Pj$S0Hcqgd2yzkuiJiU}IbQvy*fYAit>CJ3Lm|0xGKW5=D= z2?l)+%r{E9ktDqEi>3Nzl4xw>eV9&4ZUnT@E${29cO9AU%dpK_(M(rey{+%SWbeUZ zBL7OV9WTdR=77=UY*!1-_@>Nu2lJaW_Ow-949A<~k|B;m>blVfPN}&I3%N#3@3lKk zR$QBAO;0(-8^!J^v4l~7W0spOA-G3TK=X7ST~(k<<2ilGPR?NNaRZYxqXy*#RjASG zoKl zfRtXWP%36;nhJuFCt|PLOdey}rgD_c9ffRr1K-SLc>`y778OzS-CRxv7jP9d^QI%Y z<;*YOU9~Ej+vlDiOw6~M6CYNhJ&pLdp7zaIPR212?Q+&JNr-b(|{c+d($~|-}Ww1^SykQaXYlFfmi9Su3&WsGYlhYN)qW&QxjNMW1Oh*Fp9N% zM~fwlcehXXXmKXii$@1phutoqn-F(Nw)p%)wy@jyQ@R0uw9llKdd2)|I%5Fh^w1vck25MoWV1b z$MAHObcKeS%gK}sN+@v-mgsaBa_3ED;RPf;(|!Werit|AL6e*jXFhMTOx;eN%!tck zHen+MswizVl|7j_soz%Kl&W}Af803XZkClgqY59MHkxk z!*C<=`}ii;@ZJjsbu4TnQIs_edZH*SBT;&dUNbTiMUXG+;_(A~Ks|nd!EiBm(O<-f zEJo!8Pmip^nl;qWz|%F~hso~JeD+KhJyIN+&Y{1(ug_G$6OP@lv<}<6^uE+rX_Kyi zTgI6-*-$21U#*pH{b-qwz=~JB+)pW+YNBL5O~G8_ zHD=Hgig>i%-2YodK^|0`5=@Bq%|ao**pfLiK-y-?J}JF(K&9nwJB}Nzqo;hAtTKb; zqflB!-{UEh;nnDAlR;L@r&6lY9Ia4#_kc>bsS&86r=Do`2ZH6O+1#e-jHsK!LEm@U z6a|v@)m+8G%t3KiiVs+}Z#Cl6dUL?296YF;)Q_S8t8(TDzJan+$IPcwQfqa;$R$dZ zq-OWS8B(eAqLTrUmgLuIPJF%5daAGCTHB|sr}>)o_7Ofvs7#BA$7C-AodLvrJv%v2 z#MM!HaSCX)TBUom3WR*sLUJIKSTdf^3bJb5C}XpLH>E0e8?ju;VWq06AttDZ{=PvH zHZ8?ZC5!}{vcJ|C?kHB@MPrzfW!h^ww<+LYR< zJ9%htKMXK4O7Sf?F0ry(=fRaF^2y z<3hFhRL9K~Tv;pFvQ}_qO-+o8obV9JOBd4H+6$AWI8AEWo2_^7)7@;PHcEf9?S1!# z)yk9(O7<(%FbLbA8hPq-K-Ow-y()90EtvJ_t-NG0exWg4!BR4@P)m!;ORb|(s*|Y! zFO}S%Qorgftg4sMcS|`{$Me)q2V{AuTw+=oDymW471gQkiYlwSqU!3ds7`gKWJ+5{ z6;;(A5EYXeA70s3<-wuPz!jy4ZNy0^_EU9+tZCLxV zF679eDDF6Mxq0yORk-I zyKIKZcf||1#QfuLP5Ak>(kB&G!ZE&bll0=!q}DqAW<2d^LDJ2wBqgtso8+r?(KOd9 zDVcT18Gm%t@kk)CL#|ufvYgTfDwYNUa^_s`uKqfpd zT|zqWx~`TVSkFHa1^n^Rw}&SwA}0$PW-N$)-{T|%QuZb`HpD7E!Y%H(Q2 z>2teCI40q=@KO|}k(JIbtIei5Ya*UpzGT61N=-?tjOzkx!nokLwtQS_x|=0*0hd@e z^;W406e^*PI#s1t0wCK8X~d6TqM%)bg?v^s6-v7Oq2n((O6xdCq<~8-tmM|TR^FLf zP|3|pKzcbF&u7va9)F|L-A%aWY@S-j164lG*i-V4zbVbIk&@Kx_}lRD=RAA|vAlRz zdGW&8bl_Oi>SnA&k~Ai_f-$)jX-sZKI+I(G#^hF{Gr1M%Os+kll3V0QxQ^#xU3V0I z`3hUZPrlSU!KsOqd~pmYCY~l*yuvmZ)=HmL`E8c2D!q-=d)#f7G0#nN`MB(|w`Mi( zFTZXPsWi3LaW_#lTA#47PARRKHc#G^CDXv|SYjRZYMuHjpH@WETrY$kJ$Fi(-25T8 znUE@DrYtKiUtoQFy*8T~kvCgs4$0b;?`nrDy=cpdA7HY+ zZB)s%pe>`d)zffZFjKK)OAH-AQ&Lqyz{>JQ^razCqH7C zJ}qRsabdaKX&5~2iais}I)leuX=1Onis{f$il!w zRSsVl@H;`V-Gu*u|MQ%}>qX%P|5w8Qt^cqnyg^$g3U3TF7l9iKZ-PS3DPCH90#1}q z7%At+a8KbzQFyceh$!4txLE{x_w7<9p)s|a)pbQgg>fyN@xH_%T61|Yvd7#$KADgwhX7#SEP0%HPWMPPhjf(T3s zoFM{J0#ij`T41^elmupqz-+u@4hHiA^F^QxxCIsk%0*ytV2KDU!(c_=Oc6Lcuu=q8 zVZPNEtifO{1{a{%>jD>vz$F;H41+5$xC;4RgTJl|tQUa|7;FyQA_7|j+eF~jzzz}E z8Ms{pb_I5ez+Hi+BCt1bw+P&eQ1|1#`w;mF44wh41fIj-1q@!o;1vvB!{7}J-ooG= z4EAI2J_a9R@G%CTBFX^_zQEus3=U#&2!q2Ie2>A87#u-O{(`sthQS{g{DtNC2ZN)5 z|AgkjAOoUW4Ggj|@M(UbnfNOLC|Xp@6Iw0&Re(Vu26Zv0k3mD=p*7Z;2(6h0zglXo zgjR$JCm=vu4BBDP9;#OBsGTCTPFiQ7bp=_p?ilpYdJ3(#)<oB+; z#l1nhQD_^nfSa_q^nUFDp*@JH4`afk z&}_AR&^ELuF!~fGJcGe=7<~bC`VtoZ6^y0!f4F1O89}ND*;Hc34 z(~b$@1*t@)s3AnQ$PvOX0s{Wig$Rm}5MjU*VUdeL6r)G+SDp|xpN-o-Bv>pCxkJmbiQ-tUQolbPYXg3T_!=NYT z>kSnuPRD3JXtrVi{zBs+hJc#HFfm++k(h5ZTBfmLoDdU`?<5Q+V=xtiX&B7FU?v8$ zMJWVOZ(;>PorU7A1SN`9qD+X@(Eh|4ln?EOxB!E7(CWm+;u0Y)6PF8drMOB6G#TPL z0fKK3HwbYPN_sP1+l;{$=nG;S{<;-xqPR`$6ygqy?iRpz4@!SG2KS2lgm?hCJ&36f zV?sPGo)7|Cj(A2qE5!5S1tDG%FAMRicuj~mpoxmNF!dcQ(SGrs5FdySh4>h6 zLo*@{Al?@ke1*Y5@r@AQVo|?C9zWoP{3FCs436m@p=aos zLeJ8(h3?b+Lf4>a=mzl6L%J#Sh@LC-JV4QF>G?vhtrrTtF8-aU`)}w;jh#59zyShP<=pGdSB3* z-XFx(2jZ{6`VgTH(}xRvB(zC=G-er#3FGw%LZ5`cCSx!agK2o%4826?vyfY zNi25&Bkro6v8?;5L1y(C^T93H?r#We*nN zZUnejhjKiC+ISFIKCC|?^vCplLVp6w@f2ox27f(=d|$v~y`;Y^^jG!Qg#L#9rqJKk z-x2zL%2j!t$tYO--FT8f7E{x`p+2ripBaJ zgFmq<{>I>64E}>V3=dcfBg4oPMwXE+3?DRpBVcI42!j3$6K*qbbTOg^L~8+aqX5cl z6heuNx)@*sF>rJ-8bhItrkJI<(LxxlVAM5=j5fkJ5wo0Rv=c^qXi`Q;F!jc%cyDK; zi!izw-G$KulwDrejco!7RMD6jSFyYd7X2N*M-=KnF$z3WTl1ScX|vASjMK#!8fVm2s{x z&NJ2sV=buIxB!E77+j3Or5Id}!Iff>Fs{bnS`5}B{08F&VcdjxXiJREcx?*`w9VKq zj2#%=iCVh@neWDgyYSausD0xejNXR?o!C@jeA~;e6 zM}zke92*=bf)fyBQt%8BoD!TWg43X>1ZNUV;NsvC z5nP6;*nEO#VcAw9U-VvstMS*G;Q1nmmL_;1)J*Uq=y}0Qf|rWm<%oioCU`ZBOu=gr zc|9s(g8I|AwyTi9Uq%SL8Z#6YpLCiH58%7 z_^T=YYL54|gfS}A8h^EcDhr(md_yPUuaogw2V{Oq=u{Ev9O@!M-SFCJFdT<^f-@27 z4X#V*bj;ijg8`v|A~YDY48`bhymutnztCv>H5L=bkKT>6qKbHuT4iuOTZrw z%?iyHp*f+sA~YXU%b-Ps7NJxX7%aie%kbV6Sgf<~*Gjy$67&~Zg~4i!u0aXbBIpI7 z3q|On(8VHjDe}D>L9Yxz@vp|zYcW`lcWemVfWb`|Y{cMZ3^s|-W-yGQEf{RW;8qN7 z!{Bxdc7Z_--HF`xAUqCop?gu%`!O0r+Lhs%{e!=b;;&=y%k;u8GZTMh;jbJF{1|8$=%yjekZB4t zf zsh!Nu!t9Fh-SJlsv!^h7W0un~OFz6e0P_uk;+sQ2vF0#yxG+bWql7sI%4UwkU;+k{ zP}0d5Oa=X!)9~613}#|58?VhV=L&Paxj>i;p#huaW`!_uxHFf5u{2jeRhVbNJix@( zWUd1JnXB=(HK1p6E$Gy|0I#jXUl-%AOYzs``0GmZDq&t@UMtM?cx?l8Y!mI3xe;`4 zZbAue!F*dWbvs_$f!B6oa0f;=M0}Q8ix$PsV&5#u@WXFt6s@<~zdN4^t!aeXz=T2B%}t z50n!g5FRMPgE2s(6~<+oFwVTfqmeIq+~M&sPKPHV${83;!Js%iO@wD)FcX8>7|g+7 z9tI0ASO|VyxEzDUSdOK5+j0!fMC7wEI48VHgjb^&Xs^O+k=q5~3q|-M@MOZ5KuZi? zhQSpmJx=Sx*MM~lUk5YS@b&oX21L0D6K)P~65(6$-mT$n7~CqtJMi9};oC)c7XsXg zckF>VarkZw?hQk+uyKXa6%Rigenf;HgXwViaTvhEPlDkNKaHY2i#mNi{DKI-6n


QaMA~Gs6T13W1#)$~}VUbBF&}5VV?OJ3S1~V|2 ziPvVM;B%n8MCM_z0KAOI!pI^KsX&D-LHTgJjNo_~L1!$2&RAp>{z7Lgg2pX^O)qi* zC_Az)a*>E!61h}FaHx!2iNCJKU)Mroh^)t78zNAcn^1zABT$4}@YhyM*ba>*vLkYv zh}@2f*oD{bMC3hKz`G;&h{%0lHzE&!8IGVEAHj8j$fGF1KFs$7{(1@%a9JtxT;zEX zc@eyu$jf-`Rm6K8gEz6zZ%5t{k^PwPe&hoY`3Ra%+Y>fFxwt1D*dj-Wj-+6x18@^ky*ul^Ve#9%OVwcMfj zYd8iYp%!vSW4^HnFdoY`F?W*4os7SxYKJg5EOMvi0?QJVeilklikatPQPDTc#pTM} zMVMLv`paDcbHChWsFD?!`78`pf_8IPVV2c+?;4D*#a|a-unvQZQI<==4b8nA@3<19 zS0l=`7+sIzZpghs%zy3{EYUW+b}L@H4WqZ?uU%LyY<0PN5dISs^0V9n zA{W<;a=*e~2a(&M+;2thcbM-741U7YpHb3Zk@@e?+H?QJYkwokzbH74Z&42bL^H%C zB3c7~Wdl^yhd}_cL~(qJ8gN@Qgh3dCT!4?}fy~ib_^SYeLQq(=E~eIpNmsO?zE4DP zkc&3O)aIDb61lYoixh2x(G&5GlQ1|LgAN#+fB_G3?^ZKHYYk2#h8X-%)no0bE5drB8m?!qVq7i z0Dmn6)kVueYthAcZ7K3t4w{Ufi2)9G(Q`20xfq;>(etBgMHJgXbX^o~y9BS{eR(e)y_0TXV-!k|MH-2^oqy#;@5#cSI!x&woq7)6IFx*LYO=v^4yi@`n7 zdqwnq7ttqCiBCtL5z*%`cp>_th`tK0ee`uqcoXbZ^lj*f z(RU&6Ka%~7(7Qvb`8TBccljF&?GId-{uyiGebm*5(T_y*6R=3p&!W&uu<1p=LY4=i z6Gsn4zZKE%@ER^xMt{PDpOMk8fit`wf8X8%r;O~?V^ANj$J+y{oJ8DYb zQ3Klv|Cj!+AmKz&xTEkkd|JJ1anZukoWh-=@b$rYIb?qGV5@Z zFe07aV^ohpQ^xifF{J;Hz9U8SHUH;uXIVuXYXf%Hpwb0PmU}&!J^GJ>>m$qN6_=Od zsoEo#EEqZxXun}aqsNKtr$H0h&tmW#2G2uKh*m#vW8rR5h|{9%7vP$|0H#X*g6!9Y zzc&7=jlb&Rue#Zk4H^HE(zcEN|eJHDFA!Mp3 zl1C{Ee=|(UrIhlLc_qaaCGoW8cxwyH5V0_erY|YS17?aA7SAdX{+5^|C`mpy75-Lu zPiqW{Ff};6Y{AUZSs+&&v<+T1@MWAL-1|1IthgLRJpr%k)0Zr^Upx^nn$}BeMYtED zNbZFY{?||1dx`umigukN{F>zL7Qun8z3QonK69%0z=z>9447y>^9fQ-P*d@62 zuV_ITZeEHPs0SwZlq}_yBKhqK;qQf)3apPp6vaP?M};kdW-9!>rR>%TpmDT5h<3Wv zy*wMPsB}R*sjsXk>uMR)0A22fmjif!aFI+C{{DzDK!W2DO7Z3*`~xvzkQCK=51F(x zSR&6YS&`c2hDfF1Wk-V>DwP43mXGTzcaFdbm z6j>su@I)!$wy9EVyqG{LmPmMMb}<@<(s?tAtWTOl;xt*Cm}pfTUYnl4u$nqtnjy1U zUpTNJguldojRc#Cg3XfUOnf^AI@SWHfZ3Q-D(eg1mn@oHG7m~WM-~G2(iFu{tQ7va z7BXI>!zG1(9$u>@KmArDH_BQE*cDlyw-EmM7Bap~1lQn(1+o+s%SsndpH23vOg4_i zfS2qyvcoKtbs!U{U&+ghP~>t9DkK;l1C?lLi}CsrDZVs)Nrf+!t=Kv`sz@I28NX*4 zrsU8OU&6l}qc!k@6vDqk{>E>!2>+Q@Ln&T>MWHW62>)4lHEU)$j4;A~HgaDn^Cb^h zD4I0`EY~@*Ft)4Zv!)db|0*eJ($JTd%oP4}E#nSci<~g4B{*$6DViov&Jq6eBue}< zPeoWq!oNo9Rc?%eYQ^`kig3%M@Sl$fA}SyM z70<)-%v+X~&uT+&O|$`5y-obBi8i3ZHu8P^zPM?-bb3Wj;a#F|5AIUii};TrOcVC@ zB^4#T5JC9&Vdlnc=A|X&71q(Q&JA)sm%soC&AIUI!h4|5ZScpvqVT@L`$gdcg)y(E z9{cJC&JYUH=IG`1WK8Upx-D-7bfEdr&}Wq-&TE624a7v?L=g)_@UtE{L1{Rh2=Ip)>SrHUsckvcQ7M zt{|a&&2givwR0H8_+mUKHrjkpvQX9$cb(<&&FRrgrtfuheVE1r>vr2tuVBHy6N zo-dsYYwv4+sfilaX4J-MS;!`Jf@~HzLy-qQB&t5QSC9T9hE5sNXT-?UcSs{JzCyBk2{z+~&t;?2HbNy$PIB34V1rcJ?&L zelq(hk0;|iPd?64JszOX5`B*7^F&`D`eOD=9&h%`@H->>6^Or<{dyw)2E^aA#ovPX z+m84os|8emD*A_nt@&6q1981-Ijwcz<0WT&eBURrynYQ#A5YMv3vmu_7Do>8j zk>78N2OzFF(uG4F-NE0m&%NEavc!5Kn+7AAO zsr8XlC$;`^>ZaCTPCbV{>Zi8PoCY?24I$ph7H zW{Y=+_-VFy4~X})#d|@#w=LcW;-}l_Ej|?D!))>4 z5FcTSkA(OrTYNOc$JpXyAwJF)9}n>fw)jMdPqM|&fcRuvdE}Rvl|w)0I^wGx_n&9Ge+|UXx5d{& z{C^JlE^y#o=)ha&(Dy~D<3-NJHoQw9eyKyhm)YW%L;MO`{7Q&lWs6@8@oQ}HYaxD} zExsP&*W2P7Abx`_ej~(hvc)$-{AOEx6T~;$;P43@VYqgx;pT>Ippo` zxc@ZA{XHD{ZT=zlZ(lD*dF}op*jrzp)bZ4Jx`SU|N4%c{ufK!;07v-;I^-SXh!1w) z4{_W-)R8{Skv`m!KEjbc(vd#O!GE+ZKlo?9F^>FW9r?#O>TA3$9qhetf+PP#hdh%U z_-EMS;6M5%JMvF)c~IGkv`Xv zKF=Zle24rC9O-3_^o0(*MUMRCj`nEtr)7Uw>}bDB9Qs*mi^F`^x6DyL%N^xg;gIJ{ zhdgIF@}KRf30$ALgSz`+Oi9;+t&g;6M6o^8+8u4}4o3<=g5g zpKZQ?>sP+*j`Uj{<=^4Z-)#>4?sV|G-I4ze2i`8n{kt9a-|3L&E=T@74*b22`|oz} zyT?%c4=RZK)zJ8AF<6TGl*zc(S_Z$@ z-cKF*KXc$8aNKX3&-uQ9_?M3R9rHQg*N*!g^Eux)j{Jul>EAli4?FO_bLjVbhkk!> zM|9rbCSFQUD+Js*(s ztD_En|2g>CpBMb>$@hCw^ZUK2`TdUP2Y&nW186V)8mapB+vl4wUit0wO&G8I_U8*Q zUip2g_2c(D_yrvDX%6{>BVBjI4M+Z<13%;_kLf5+*n#Jmzxr+SS3k^O{kHk5zb3?M zIquJQ@W=V92WV}gw)v|c=C8PUR9>&=;9K9JSKD(4e?y2j@;8?EH6hxRXfvYCiMAlx zl4vWUt%(*9ZA0_~q9+nCY( zBf602BBJF)D~K*8x`gObqRWUbC%S^@nMBVbdN$FOM9(3*is-pSR}(#t=o+Hu6J1O6 ze?%`JdLhwuL@y$GG0{tiUP|;bqL&lBg6Ne*uOfOi(QAlaOY}OT>xo`ZbOX^Fh~7x_ zCZZdO-b{28(al6}A-aX=R-)U8ZYO#x(H%r@Bf695?L_Y&x{K&;qIVL#i|8Jrdx_pn z^d6%361|V;{X`!i8YB83(TB*+JxufwYrg{SE0XjvqWg$G?y##*IPBw-j&bNIM}E5> z3VyfW=7*x+<$u;OPTBoXm>2nNekiVs`t5!wjN5+uIzNotew!bP^C-W4-5=&re*3zA zImBO0-KXJy%_0Bmj`BMEQoqA5^*j7h|Jx3{cO3V>>&S2SOL1I$&!OM<9sE9U;D6}A z|HzU5V+X%a9Q;0Y+;4x*;MMATym&?$gkQiF)8mDI9#2-w2Rz=r9{ewWZ#;|& zI%Q{e$cg5_?=7C({A~Oc&1rYl=<{>DJC1$(uqS7He)a<%-=J1|0N(5W8p7tD%wwJo zo}3*2LHNIhH^=`?j{guy_qV6kF^?almkE$l9_&UUP5=?JfVORUzsKLD_9Iz?TV~d7 zoq4|}P`k#|M|?vvQNG$+JmI2fjn>f`Cto@0{2Cy_4@F7FH9Z-}JO!RC00*XrfUjhl z>+$1GKH^i$lL`NTcR?t$ymrX9d}`s;T~c9w{}28jDOYPxhP)Pyd9)3lP|PD@9({u+ zr^XIXjm%vMj#-|J$Z1}0zW*ox5xAD`|C#77M1Ljv8`0m1{z3FlqJI(no9I78|0Q~q z=zm0ySv24w>Lr>%G?QozqFF?nZqB>E7Xpm@#s7W+TG(t3&Xq0Fk z(V9eS5zQxBK(sc|LZWqu)+JgmP+!spfrgSc3N)6qNua5u%>vCOZ9%jpU2jGCS`#fI z+J^F-K=eeSZ7KaEinpWq$wb=+I-qS1bOdU(oI##!+0`1~?J@561jp8`pI0O133bSp zaWk5|t5c5kGe2h^w6Yve$div4p4yUH4J+H@9iJ081vs1<=p;FG25NB_19wD#wy|-@ z%g*!V1@c6m5%YvQhAakoq1~Or#4Bv^D)2OdI~#eLNSfd^JExpXBiU5U#mj{z18dR-+Yig|K78pO%i z-HD2pAF?>r@-%?^8bE0p*f|wjoVo?NOHQW&P3jIg6{XQ#)PmpPF>IXnre&4m$v{>C z;MD_c2eycwM0*8#OWG$ONBh9(KrI>i0vuYpdT|+Ip1d9wv--O`)gZPttO5#9rU(A= zBux}>lvTjKfqoLaKTr#N2sFL`(55K`jCpEy@KFQw?ds$w9)62Q7@D33{_04Y;4#tS zF#wxB@E8cx;?WiEs(}@pEI_TU7G&YBA=ZzgeJ~!y#dnH5h;%ZT(@DNdoeT*KmEgmG zTHxK`E;RXdli&sMdKQDNU#+2*QDDu63AKxuJ^&p~pd&cY+KQkf1EVD9XrS@h%Q>$0 zqELH<9m!_o5|(FpvfxA zc2rS=4pf?Gja{9x2tTVjRa6p~De-3kjgL`Tjxj19#;ArJ2{tclx7rvroBDGp*Pk1? z?9X!ud@cvx*d_S9zaP-NKUEg!aC6!c~I3Y zlT4D;U@W>DZTFld`16hlx*wslV zetLBb$13U>=W;!xwacEdn!wNFz>8dhuOaaBIq)_v!PgS_|2XgyT!LSK6Au8tFi@Ys zyO1MLNZ>KgiQO&4y1Ns80J#AXJK{(Sab4gdiFh&4^qse@%g%cVDdSR387H|^#$^P4 zIS1a(CHNHtekBKfvPb&J}&c8jhd;jiU{@8DAS>jLW~`1L@O14@ejBL|d@ z>5XM&(t{lvOLlAnak!xh4yU;0aAV*mIX${Du#xD^a$$oO0Q@x3_s16kHWA$%xJ62~ z1!&Uu%eG6_x+vy3mGPE%-*0PRn`E#ZD8rzWYX-Llc1Q-d0gX?x{ZQl1Np?O=vO9Mq zXFt!syA%1B`C4@***gQbOD=Z+wYZFc!Ojn~MT%pCOL{R+mt;NCG%?>t^;j7jsK+y? z9(M(HOFnl3O*)q;>XOc7*CdCewyM=q?!u)T;IId1va4cOYGCWC-IDmpzOI$KFJ{VQbK%+WPh5<@(ix$0EcIRrd(MLH1FC ze1RZeD3LRM-H@H=-BGM^E$R7uDJLafao&QU@vB*Z!l z;#?)fx`bGdL7btMxTqs7G~{LP zwkdTLM3c17GYCyd8m`%z-a)3Etk? zQ=)aKa!<)pw|h$P)0>jVsX)`W^<^&GdMB;3MDGHWd*Hg;*Kwlx;oukFU)Z)N2s)oW(r%XP2_^L#dL%=|JNZ|Xk`K1`rmY2FHI-P6(nd>GV}Npd zR?c&~X9d6DD>;k@8gEenXN$55lkbmt)-c;0`4%-nnLF>E}QREtyrQ@0~&7tnrr|? z#}@_r;r}{PKg`p1JNGtJRuW(}&0;m3hPWBr5O;ygA+ChLXL8^dx&)u4&6eP$K;yoU zwX3g5vSHyilXc9dKHe9aqs^5p<^fICa+af(FQR8+b#|$lk@WUd(g7Vo$t++RBwxw3Q96+sai`uUB*RdV|Y)y~f(2u3gJ* zQNJ;vlWJ{I*RCVb^&IF;ilEoiRA&P>)!C>t)ww~tQ9|AXG`Rrbvk$t34R9Op&CHyS zA9Od;0>sVC0>mbz1&B@BW{G$U&@=;WVa&7HLH(*4>4l1mI|azkJ0+ag6V&rY{*i)as0Lwbm7NVh9Cq=%_$AK|L@4wqH?sP)E) z_89xd$u8GVdiGiS&$P$c{b#$~?myF>Amu#CDd$d?%6W>a^J%U+?^3MJX9)CJ4s?$q z=yRlo=Q%a(b*Y9I=n2P*+!Kzwl?LgTXnpEsc75uexE7Sxr(Pj_yvph0UYGiK&Dyc4 zz0U5~ywByz${U3KCX0T*OZ2y>KfcZO#|K>Z$9Je=-(@Q{=DK3{Q^mf=RqTTMO*>p>~wXrWvWdN`>c=W9p&yG`0B+NYAmXF%igAH1nYY+UFAW3+;1HcXtO1L|#Nsf|;tAI*z9YNyJ!^NKbg82ssB8VmcCDve zcdehaBNG2-py^lSo=&v;>a7F(LXGlQu2DYYvQhq~{Vu`(0Gh5&<(rPrx~|jTv_FZ( zpM0}Bbl9dBe-D$Cv-KW5_~yfF>U#r_2`$8^k=XBpEp0@zFiFIVum}^Xv@9SPX>M za*ss@=N`YR<{pboQA2`e0j+#PR?C>@HI)rnB3tB0NFUI+oL*0cO-_J(Jt5~H>sKpY zPQM69Pz`9Z0_~e;r~=<$);id6RtTX>76#BX%VVwO^4ObhhjI}VA<4o7nr_XejALEkVj?6oU}|_WFui>|WpZ73(%%6iC$CK+|j-v0nSfJqt0< z2W~fxh(cmir+SP&bj_$PF{)QRMjyFmR9`fZj2Z&1e2uqdy_n}?cWb<&5q0IpY*+q7 zvAUaBj}}Bz?$N@hDytTvndQWa=ByL@nOi4Tw6NSw(UNmF54dwTMJpOuTXO^J=ZXVs z5lP&JllTjl5}#nLJBbsyb*C?tYOpOS;UrE8Unwe~9m#nzC+F8na<(VEcHs1S(4}5G z3c2M{oGMPS^=9X6_?rP1W{bO&W_qHNbzp$#T*ZL_htj*b{A=K%3n`~7r<`wHDyJKP zcjv$ly97Utzu9-5#P3x7*_fmE9iVbW%cJP6JVz|T~ z0W>+WaLiqBV)3injsIdKweV3~3;#{Ag^$Kl8Ub`n;>6c3G?7a_%>TW!1y!&sF^^0C zFkg%nazCjUFXH=2hmt1Z^PiUWituS>%<~7M3x57HfqapPtS|DXYhPp%;h(|c|K%Eg zGT~2Q@&9&>Kb7!{S^R%o<4+^}=`8-guJLCOehG_z)HVK0!k@+B|K}QiwkVbObAVRS z368n$1hcJe17c3q+XlQIx7!B9TvQd%dDh9lVm?k!fi4hbk}edBBrO*ek}eiYBwcE~ zy(pGhCk~6{){YRdg3SDxjG6a(U7Go`Xxei&GwtzaC^@4mttSKG9PY`0H`Cpd0kI0T z2{VmAmcd2P(wY3ReoX2f~_hz{ieGNJO=QEDKH(N>9bYh+0POS6hxNI92 z5cq{0xX&f{I%}`ExQN>;?)AG|Be+;xBGE4eTDhtVV%~s~A9|U%TtZ#}v~q1Ujd?Yf z+PG3&CBd%-npR$+BrhE#C%A*;yt+$yuM^g+?Rv33O-E^2FXlB|c9ad`28n(n(Ddj* zm*_Xq9AzUjNAZS~bbT}FdK0H>)1|IA6ZkD0c-STQ7VCXVv6XwD(i?HNr(0|z(YJG= z=eiXAR=?b3Aa*3aWzmhkWf8B(m^X1MyBKKwYDwOGz~+jBcR!N6``g4$iFrHF_^c$$ z@wlT-v_{OE$Lw9?XC-%#PIhrRsp(QDyM^W9+$HX0tZFTnR&@`}3HNeyLT|q7IpN)e ze-Dda;2Qs4ai7G$A86W@V{h^>_Uf%1KS0BJj2qSqm4@{Psf|3uwUIh5+sMNt?<1VN zb(Q3OR6Hgj_W`vahtQdkiEdNBR*hQVR@X~bp7XrO%C`K1n#WV+JuaS*Je~v^=V2`r zCV9vMx4rd~Je*5~Rq&u4oPyiI>1~i`AJtnict$)cL7xK}w^NSyd|Ji44OQOr5zmVk zB<71i)A#d6F8ldQ;$?~c3ecn#Oi{IE1siuDD;Uk%<(%*FR`6Bvnq=`h&~y(4>%_cG z()aZ$9tyr;y>uboO#jk_x2eiY7ve2y9B*@tqnTpkc!%cI?{f2MZ*$l4>iyz9iT^%O z3x5Q?Rn;YP#A;p3uzq1hY+Rj2xzCdhotL&Rtz->Bl`&-Ceg5qECPlB6}`z?~(9N%>C+RjB! zZn>xyx1+@EKLu{5y5@Eafj#-USN9~AQhlkLwxlNWz~}3f-sh{#-~*S578gB3&y-wh z0JXS`f|(nRUacHl@MAhLZ|5YZ6$Ji4;`q*V=~xE++^22O-rzjCXab-XQ2m)XOMW- z_q0G|cv?xC;8{vM4Qp)HgH?{r-CdU}q??jQ7-(|&-sfm@=*z^srzN>Kx2sjr<|4Y> z)2B!E+(eu6%ZCK5DzOo<_`*AKNG(8{0F$US6O(Y@V1r_t+DDeCd1=;OK+^{r7%Z@`UWr@I@)^o9i9hy(BI z61=fB3DKKylaPLDlMubB<@D;!7^kosz{ zHTN31cc8louwF#sx8cPf|?F+>Zh}-ykiwb?rW`0>;2fZ>2c|YBz|pL?{Ce0^a1?bXT0k%Zy*(55LV9cjHjppW8S zAMl=`h&q~D;TX0RPF6%6OO-f|t;8v=D{;IoKgz04)Z-sz9ZG{p%C`>Dy_=e3;v7f# zZyoBBs1Rqcg(!Aih{=RMg~gxd8h=WH1vbv-n=( zn!zj@)@L)r`b?!^y;Pqg5$6Jp59_J#&w=kTE76;(_5PebkHnnMh&fwH%mupKoun_+ zo}dR?C4u3t#m%HTjEA;p-xyt6{%UsUOucRuwimS5aF01Tn{Tc~= zEzsl=rt^#5`Oq^~xc#EHew}Vj(y!OoCwOF~@F=KVC?Bn#$!svC|uFj_LHC z)NhtNHvvsEwP-D;7Asv(EjH`&>#+J3J^pprk@W3? z*C|%zWBNXc z`Z!SLLB~a|Ct^>~RP{-As(P{Osp?bI7N6$YqRTVc^=C-6}v=6<>B`g}>3JM;8c^p|Z6QXWhogDYG=n0Qr}`||YH_4vNL{MSQNxJulN&-C_T$S6EnDIAHRj#H z?AT|Q6^txH?vOKbjQ9?@;hvn-9dhv*a?E=hb5I*Qnq-q%q6scc?6<8>ZFCjWE~A zcd2x8gT7~LFyFKF?p7HZji_OjyQY!nNk5dQKV_ZoK(#E7#>i(qn&dI-)$(YJ0&C60 zs9n{X$sV_BCPtw()ivsHQ{BC4Q(dDjf!5nuhScAe!u#U{~|8d@{9q1~^D+T4&^%Z!#r3tK~TtYxC5ejw>wsI6rhtpZkC zZXJlX7*GB^QfawBf#=V(0W_8bl4L~4a?xmNg)%T{=j(N2P&3^YxD zx2oY`*ZsY{wTsp0!0%#xBw5dDPNdP%`mCIB3inw#@1u$h^i;Aaoj8l~n4(4LY;=*J zU4b$?4)(d$MK`0nWN;eL`0mH7)HYuq&G+N!dlkR?(dc0{c&pxyb0v zFBd)OS}T35?L5ZmRd45cD#=7`JCD)VkYCs}`WyXhrAU2~3rg{{+c&w40n{P~axL;1 z#TGfp+AL`dW;RPEPExPlW=Ug+wLi@m%I;5lPO(D{qv8+eivPS~@kdb88_74l7hE^J zQPd$vSJ5F~bloAxP!Y!RMR>_|5yn|-bH;dfZSLjt`25S0X0ItuW{b(2o5p%`uPgSV>4Z9iMSVjNwS-V-vZ!w=qRt{a zGMlv{Zz-adQe&9IHioxdH-@>^D`3XFDqjJ!orkXW3YamUY~cdV7A8(Zucj?5BhZB$ z=zc}eMb^5MQO>MOy{ED+WmJ$qxtRAS-%nqGRoFY)t7mX~^*&Hq30Ou=ZaLfJK6KsW zR?rY|CO-sx4^I9GzN1{&WmVSO?_v5loU z9>h}1S1V9U#Jry-HRJqVJpb~dah|b8aylPqd{NEXwisVjYntE`^M1x`SmYPg)>_Y5 zjQ?@ZSq`{+&SG3Z;1_b>pSuKKN8lH6;9s}|znH);;lRIi34SSoU&eud6R- z|Jo(^m3X!$fL{ePjlpRi^B#0Yr=jk8Zm9cKaj4s1 z$m9Bq8;#^~eW~lc1+`<|^oOAH>%BLTjo--I`0rfXcsjh#U=Q#6-laj_ObWk+Q}_>x z3g1Fr=T_G1{87cs?FoyX{s@GhlKNIV0>RipD!GkQ$q_}B?4(uY+qqTc zpOsc;?jUz)7vm27qT~+krh2=Rt+!tl>+LR*a}OiuZ%T6RwRRa9ck{c9es{fqd=Dw& zURDu*C@SJULcO0w{ZkQ@b|@O`4#mF|o9lxl>O-uke=Camuwgy1c+`kLu}Jf5;h$vZ zReZMa7-?f4r;UFVwedJv;3qf>eAJ}{e$seKf-$lZ;`yT&^lgDlq!_8anmK;wPmy>vFC5N$?AwriX50kQb7Di%4eS$sre;K%95 zzzm<#82AYxe##*Fl@LE8!~+asKnd}4Li~b3)RYjvq=#HzF%P*igpwQeH7%hWWR_4e zbfqPfZwT@b2WcoleoLx6%&0P`q{{DT5%+s;5jP{GIK=;8$j{FhKN<1Q&p7vI7t{t= z)7}1T;|LA!KQqI7Mp$upr_UlA+-H$9(jPI-5AVMj^0UasA4dGM$Rp^ZhN+voj@EM5{$CU^8e@OTLa=On`)csNGd|Bf^?tIydnr_dR zHI7+bG3epCVl9=f81x1+BxEMg_(N-JcS*`C%6Z^_Y71~>zXbP8V`z~{C05A2$sL+m^-c|qrM_)#QGFYFqiujPDTTltt?71=dm(3RFt_UwcT25 z+ij$Xnop<&ENWv#)Y{gl9V}! zi`rTdwa6OkgKfB>zDRAT57L=}LGDb!j5bQcLt7diPU40KmnVP++mUKdW>tHlqH5a{ zY6ljzts-his<2bI3Oh-$!f2;uklSgQ(M}Pxv-QY5*oAxKo^f*eZpJ@B3U;-QnGAO0 zkD1J9pX8u+%w(`Twd2#ccHBX!9rvJi+>>j^9bLBLUZnBftj14qt?@q8qEBaA^r?z1 zx^GbK+6?v&a=SJ&I=S7o85}^>IFPHx&PvrdC@4=h2@VOyPc|7wAG1jAJEud(GrBOJ zvS9a}2ZsiSNiM^I#ur)xaz@$O!9^bZl+m>#!RG~bJLi<_p%cLo!I6^FD4@yhxhadF zv^_VY8*}6gKhqhlt(Hv208MsSzkO_%9kx4j+*4(Xxxit(#bInve!w<3KA8M~ZBFXJ zIfV`IxnV4WIhLE>WgMJ9#&RNOEE8vOSJPNd3Z5ZBCj(9A_uyMU89iP5JyU{HC5vL9 z@x}C<)E5opc{#C+Ug>qiFQx~lSv&87)2rHfm(e@DRjz`^46<@1td(PqIaQqkYFd%S=a56Za3g8@qBcp+K% zMVy5ns03LakjF3uD+0IJ)R3~uhL1a3AG{r099$w9ECrhG5osNq=?r$=p7A_y?O~T(`H2thelg7jtjf zXN-0CmVNM&;H47%GN9>puz>jET-%Y$$@X2r*}m~g^+|^t2D!rxGbXsK&#SG^EeEgR zKDV4P(WSbtrNQMoZg82Tq>c5o$aOul$aRL&BG(3Cnf@EZl5`!raV$geMDE~?WH)bO z?dB9k)Qz-Sb~C?PHq~|a+Z5a^8QcPt8Bdr)xT|2WCAd{G*akGcUZyGPWjh(?TUp~g zT@iJMCg0Tv-liQ%hYFozh9c@tLcN_uEm1_hgWB6Jw!O_%MBPmt_)e|^&r<5ZchQr< zJ=~MQ*-B3a_fpyKX3JixSoV8pW^*q)vzg<1W^*6m-_PRDb&dZ(FedRI1Zv@rruB;W znd2?%$pf+Tv!gkAzF5XQX2F6za~xPbM64c4u(DqIu(86~EFQ<17l>udXWskZSv^dn z;UnB=xWMIT_$Yxt#(|f)1m8#Ck8|J)U4lPB;7@Yki(G=!X^uheG{=l`m*8}oV~{<~ zF{8qDH+YWB?ena;U94yvULe#LS=1$ps4tNrdzm$4OBGRHA=Fn{)MbjOuTj0d&eq#< z*Y)-W;lIh^uW*h37U93m;-Bdn{~c-}@3JlAEJdyFr@DNPt;@3&QQs$7KVW5DsfhX^ zx#}M=uKGDjuKLGhh(6&A(JGgQ=u=X{XRH#=RaC+OT7&(($~D;4$)={b21}<{2ANYV zGtN^|^jFl(zGj=*8bw_kq?y(?>`Y7X-0R>WLj9ISU8{(Cm}V5;u``PQDWZN)s6Vi% z7bv3sNZsNmu3KE_vRfRX9dSRiJL1;4+^a-;K!fZa(2HD({u}k^-`O60v7)yBpb5;M z+yv$lr3uVmvQOBy! zb*!6RcB}@Wh7!CH(8>q(0vOacxf|3&jY;$-oamcfir$n&Z^nv#i%Zd)lf`MlSez|N z7N@24b>vVh=Ih8ATUAEiP-_yhh!Jv|l8|kzXVak*xM$Pb6D_z}&!$5sT7E~UE$4UK zs#NlmNX~YgoI4cdJekzlo>Av*O6u%D9!y8pgW2iYgE=L1s>JUEG`TZO2i&OlT&Wo+14~gJF$%2%-%Nk)bmhJZL;Li3uxt52tD>YU0R{unqQ*#0h(+- zh;Dy7>n^t+N(h}!-Ka0ujrJ&ZqkaV1p957q$2v41G*E&L0-A1Lj(h{*?qp|D+m{m> zObu`d(*W;LYJfvY6T>)7+^eXG;bc=rur}qsq#6`$%1G;2l)Yt8q|LIZi@Uq~0E4>> z?hb=H4A8i{`{3?_ySuv$?%ue&yW8bEd+#`B?LTYXj(Dr9qa!+^pUlY2s>;kdoupi|x8D!MV0PU6g>$m)@P%drfjitkUr2_sgDe?mx`k~cZ5 zN2Ud?VL0p*ImoY(Gi0_HD&4TDjl{I6-8ebCn5t~w2_*039Nndu$x(!)3esldHU2K0 z;ZIS{*UVi;>%=f?vsQC9#EmIdN9jf+XQQG}&k(*9zQ!!J*-QV%&pAtz9Cxu~(W#Vs zs2ri$)A)x;w#_oPgiT(Q0EWXF%=Iq45`NFjy;l5E8Q9bsML@2jd= z&g-hlarYXNTF3)^r6ROu@?MlYR6csB_jK|1R%$?O3`O<$;3)(4w>(S4>W1W8k;9s? zX~yr;o#8*Hj1~3OM0MrbxMCItXseuSgLU$6xfiMJubkL*^BV!@pXx!4zVzRl@*2BI zoO1VSkj&D8n3z(k+Z!IRgBX&JQb3}3T9>0^BJQxW4Ky6Fxh#teK-j(}S zz3Wi-pl%2JpicpQjQTYWJF4aw;Cb&}-8IR0>&u<*r_rp8@l>sjHTG|}(QtK~ruBK8 zlq}X0I(MH&w~+$q-#2i=8Q;}>P_P*$yHfV@TDaOtozU%DOGm$LJ$@4FaG<3`!7@zd zZ&Vey5%84-U7jMjyb^ekoTV>>=a9fHlytzL0x}EXsa;xRDo_^v9gPBN1)J~4y=93{XoL>#hpIy z$P*benfhr2RW6VDpY90!gKiaqgU4x{rWD-1E+xFaql9D9i-cob{dR)Bi^A3(?=_vR zoLQYMpV=PpNT@EwS;5HQIOt`A{`=n9+z`o~5ZthC)%Yh=3#}LjxzifMIS)DtffgbU zUX0JJ7iwzUizIoXJlxL?Zuz<7&+iVLSyg}6pGd!i z)P9XHahPR%=ol^`6OuJoA@zQ%<)x&*O!K0^pgyAy(@8y}4`z{z{HOsP{0*sUu(_qO zPc()}BDhwc60wuOL#)mn(=4qqZBWh-SD^S(M73g}LtKWq+EIqMo0{Ic!=Q5!)g!^P zdRP4`nXLkQMZ{M9S@_lVcpc3r-{^?2j`ETPxaa!KvM#%A*Hg#!O{A8mOCg8YF2|Q9 zz(>-On7G!G*!TNGPYmomI+p5n&+&9IfS%u|azG%w1RCbv5$j%GY@SgPT)z&Ft1R>d z(;Z9SF>An=fiEJE;)b(gcnCv`Uo`V{^)8ckE`PoiDPj91npr1R?;EWMzM04w)XF%* zK;9hG3QDs-2kV@%H6bbXsp;gvC?GnZ>hHkIx4ZKGPeijcAql}(Z%pIfmMh-cWS$*i)`#-&8D`#p6aAVAWSC(W z#-{Ta{?=xLhsFmpFPwb_8vuQH-Ow!egJ(*6+AFrdNN46`<^iit zJ8Hjx$!l=_gQjzBYgxcDG@IJ-j<55jgNBpwqKO|Sx+A4T(@z#=f?01sx*+8&#$-*_ zk#Fu7+Rkbs6E7+T3$E0x8_!(o$w~gtX-}KXx@l5gmaEYsgP39 zlht6r`>kG#iJV1_ddCbgg7nl`bT;ij_6l}jjiF5((a)Oh%gbEpZ20#j(JiWbm~DM_-^~F@kL)|@!dAI zk;V+kL=BQP;V@R+QgxcZkPpBT!h)R2qFv7WHA{6!oggq6Oj(ASdw6~lwi#2Kuvlu9%hrEW?lG7PTz5{wRC~h%yqtcwy_Dgur3@k9jPvi zT}!^uXk!;Cnvqt;Qn+vQwOB6`h2z4$>PbO9j;YR1-K`k{Zvyq&N+ykcpA(qC1jAd| z3n_aO*Ay)QnLVkydjFzT8-0wihD@cZ2bjeI`X0)5NUjgR+_o6{aaCX+%B{|Wfuw@i z{KEYG)NY#(V_{YIpofEL))Zgs*jXj@k=E&T{IkHtwG#0+Ax4rYyPg-9}_@;Lj43mnMMSDB~|{x z#96|O!STYl04_UcTu4LeZESG+U93%RaA%P95;=xyU6R%yN0o7;iUsbf)L3(!7SX=e z7?0#wv%Qv!`$kJ^LXYiX&(POSU?5w4#KO47V%oA?G%zzVpIv@rrY!=9@?d8Pvx{zB z|9N5hx=A564_(qZ(DL9CJofNF z;xEweuiyeJ551eV*4vV*{I$yIZJ~5!vN=#m*?Jvr_RY9IkI+TZbz-R+$&5_ro+vz0 zNL7%zGW9D;eHt#TJ06+L=el-J{xRifb*RW2xaO6$=aUQ9Dx)*@PKKM5qLI(S8dhzl zqSwSPYS8u9CzQ-F*^>BNHI5v&<4cz;DzWL&Y$b_X6eb_g*_}|hr zPwGm$&Aoa0ydqv*#19BuhhWld(=R3OT>Xn?+(^wbxNAD#qAnMsrJycBqgaQf`R)s9 zgyR&CamYDw+hzQ`^Zvmp#vcjzm`4HF4wG+ z3~jv8YrKNi`;J|ebPkorjG?_Lynzmj2wh~GSC&9dCvUn|_t^PQ#Nv+uh6Ufyp6>mg zlokj9+2lu_jT;8%%9l%Xq&sja4de^x{y?61yGnWp?o{c zpnwL&6qQ1wz(oONjdz^n?n6yRkcLtO6+%Iv1_P63f$vy8XwdlFl&wwk{l_Me)~F}^#|(CR^>~Ui?P?yo86y@z<7RTrb06!LCdEWj)A6PIC=XnS z;2hmsnK;#G$M={; zq$>}KU8L@Vmly^*NwdoKR|~)2BpHxxvQ-YNt66WI6)z#KIT>YBOYkdZ-G||(@hb`u zV${&qK|`8YES+o_9B)bu)^td|1!p92DhT%D222eP-Ypq|cL-j?m~P9x9UboIOi{eY!U&Wx z){mjXk9`nKj{=nO8VraBe&>!yie3=^a4!e@(=|JZ`}_aNv*7 zSBW9-?#-T_Vwa%#g08nQh0llbEL;SdU+0hOh*7`>lYNpPItJl? zY2`|wT>Yq&x3w#uQ>$DsXAiWT82BhjAb8~yCGB9}Sw^W|}jV)0xK z=8KGP-L_(B?qf>L2exx8dMsuyq3{L44z?#Eb_|(-w@23dKU!OLSo)e#v29q3-k1Nh zT-)gd1KJe%4G~mAFxPx0=-Uf$*e8E+$Cu>CJRV22M~`9`baaajV`r4tN5tG zgoyr>3ULbjU5mJ_;dILKt5cn$|Cdj;OwTMD7ku$Nny}a-ZUobgiZJpfm}z%lFlrWUGCuotyp}k#+Xe2)!A

KTAN!qQiX+{K@i+f^fx@jb#oogE?^)H1s(CuteH?1{bCFQDrF^*B)W?)wPrZaDN;~6& zqw$Tw-SpT-k>-w~0(O7ze*SO={(G~(I#KRF|!8{1UJayBHyyA137~h-%mgQR? zm_3Yda9>CFcqWA2GxQ>Sdb_aR_0aFRtgYm0@YCT3@zWCu?o%p&0%jK8qx?}N`tEFr z36$OoY3#)*2_G841gn^|3B=gDtOcwXs8bH8*LiB9Z)rsn+$Jk;1?<*GJrxtge>w}y zwJj_x>*cK9Q`i+tODiWh8t?UIMBn{`k0osW_yF;WBG_aFnERYmE&njgnrLfED6->C+KiSUaNnn(=qB3j^{s3GuZx7SkPVbz0@Sz`Bi=P1*-fW(WugPR&v8o z;-;tw*lbR5SwrqVW=G+h-+%4OVBcZ=hwDQK>N$aZm{2b8O$;-n`#D@B{^KMKGsJhE z5A31)jSuW&vri`UCtSOX;vNyY*vE7@*<(^^?@-!{0-v%-qE^;6s*5}DtO?0SxKTr8 z!se7jkj$b%0v#zXxYcd=Q^9QSt_lJyjxsPlo>D~g`zM}^6lO$aH;J=VkSKEE$VEDm z3tH>g+V8#UzqJq{@l9l?CWG*4YaKn|`s9#q1|>9YBRL)hGfwz0_Y60bHN(zVlpnI- z9Of#<`^Ked8-W$$4iEL5Xp3N|ql(i7j-&gfqXA(Pk#&V^9qbaSh*T#l|Gm^Y?OFLeURXsV=fm z_d3(pTh;5}Nh{UR4M52-Ate`%V2|2$ZJE7*5u!jn_|+s8z_)D_!Hg>2(NBqQ_HP`1 zkL+Ym?P zfV`ilugyvfZSL3N{zU#>FDcz`dtE{CK5*YPa9c1=`5o0ZtkL0c3FCFf5Q5hABhz%~_4EYW@=Ss;p@t_b<>qGjAdKC{o*^m}MZQ!^hx-%!x?{JX+P$)3G=Pl(q2RA~y@H@tbL zW2C#@TjQ%$wUi%06RVK{xA!b*C4n&as#uK`lqZ$||3yuB-omgKs9Yoy#WC}|E-5UW zzgjsRlqcE+r$5ye;nGU_Bp0_UsxZGy!#i`$c}%5rT8Yyg-ctUeT72?qTI>7ki7|>- zPd7qc8O~4+GHA?hj|3B0*zaE-Ntd9os;RqhUP*2gk;1U~e0V<4*s>92L0Q6%(44^z z5w$utit9|xI1yTQ_l%^MT#lWCCe!Zc+_o6?dcPU3}Z=8A{@r5!md=c?yqW_z{J zDgf3*B-%&VYRG&GaJCA|DYXy##n(^%0Tn};(IXpmTWW`P+s>lwM9ElajpY$N4Uisz zm0c=Xsp+L`LQG#+;{sZ#HCGFg%%0P##mjG{!H-~#W;N98qAFwI(|naqHAv%r-X`l~)B zS1OH56MTTA))iwFr9$N|R)w`Ifz)OPp)sw@-6|ptUWV}o8 zc?8+YXLf>1;5m-+P|U``g)<{VKrz}kZ0y?8tg<5o_681mSsDJuG@xTtsH+0fJ&L5- zF}AaT#FunfpL=*iE0&YmWdmSnN?f_*9-&mZs#CV0E;GWjm(y*J^b(#M_#waLYq#}| z(EC|{`Wk^>`=K@Xlz#UfGPhX6$oVF-<;s2KAH4Ml^TrR&Ka8+;&8;Y8cZeDjyqVrz z)|~vq$GN)NsBlkD|FrRE=wkcgBI+nx^q%Z(TdsK?EH5VXM=+_I#lpyXm;q0#cA!Yn z%&OCm+5&Ha;`j96m15GX27)IPkt-*fWxH9&kzkW@8>+x?>gWlv#M~dK1E8~W+jj&- zrN!m(WK`to7>Jy5;Tf`kkns${4Pc!JR>1hzf6j+Yjgn2zWIb>H^7p7b*h0i#p)SNH zUp>U;Na+s_?$)JfIpFIc`VUUn;Xe4M9t}Y~ht98SpuEk_Jf2~@K_UeH;&kU&JRM^g z;`?Y3e-&dh!BYQ)$1$hNq1G(HvCLH*#G;X(J7KobiUMY+#h@Rd6CB~lO;hbYkeI>7 z1oIFb>|EE>l3E@SB0EaQH_FrkiA=4|XHQshixFN^$KB@Mialy!u(jEO+kD`n z6@S`fsO#EQv{LzfqhWhT&QRn6V=%p`0n~QLrU%=i>?LxJNRh{ljrW91Za%u;w7#M$ zf?Y8?qBEdm%2OqmBd?KPO1o*x^Wht*uVmN!EqxR(s=8Fas$Rd65o#tu<4X4yMa9F! z(VavbGz*sVlM$}t6L~(P>2zM#nk2e3N`Au05h^yNg=2F}|15 z(T2I~a@p#yg$v3eJ}I;ZTLmm|QCTtwc$E4JB?+jvFJn3A-ITd#*3~gNiCr||^85J) zY8%<)niFrgyHrn)OXN=qcDmEo#5QgbalgZ+J~;v*KZiL39xn{BNg0Q2qln5k#Qla| zOW}i|^qHvmKuwN)lWH27YuY+3hZyY$2FaSb?Aj)NK*hab-8~u*7{cDDC*7nmxUv&UdEzkZ z3|W4vC^w(fAg4EAlOV7lTIe{08 zTig+4UV0c*s10db(h*1AYS;zISNkA_y!NmQ(ii3#h0u#{Z81kadC;Lm6ds{R26+;p zMBiS%&lrgckiT%xFoqIQc?29u<>{mGiQTcyREK7fdL$n$icX>O5f@ByHXJ`Euikmt zv>ZQAJ`?pax~cdt=Jz?n5~Z-G9}esP=F?4iApJ@g+LZ476m0t(KK5Dr`4~~~s?c#k zh2Y#R`aEaM;QOOANkG@$M|`-P$ouL0I+++aFvXjI4!MOg21Euot5h%RJh?rZL#g zaN?FC!@1Nxj`)zQ7GuX*nO1M}!)8*CCB5Fp18l0tU!Mb_8J2+SiZ*yFS}Rp2MK{%l zLpOCT(gOR)#L(4WJ?fe@;X_m7y&8Ad^xK;i-G{T$+gaJ<`N7u7TK5rM)kXduBR9A2 z^TM>g!vK=V)f$oNR_~N@;3hBc($T%RCXE9iu{@rW*;v5O`L<}~-Z9@}$WA4qWMGYcl>Q*hdQiFL54JMm zS2~0a`0QpQR@%@oOpK%WQ{ObqR}#8ce0sRAh1>udgl2r;n+$v%hEVbW$tN&?P9)Zt zd)T*uB%5s5fncv&D|Y|;x00R$2-ilXfbcSl12w~PlSejiA!9g3)uk&3ee_0}T(#Yw zK?>-NKXQS;6!X8#Cz=I4);+4+{K{{w5V3p9JYZp{QcM{2UzSMfd)N+8Xa(M`w|Q%E&=PKRzu#(~w#|@2O?xCp4J6vA z081nmHTXJ2p{9M3-3F3X)IC-L03QAD1H`g_plms$lrFAr6>a{s#r-+Ig!#;^tQN(r zWsz7WHT}tWff!k!n${iI@p91I9B@A22Wy*-sW_2sV3xn15W6TtJac`Gp!dT)KjZ>r72>PncJ9z9tNW}#4+>2W4AbK`tz;s(Eau?bNU-26=H$F%E`~I$`B_(9A^9y5cyl57 z0)zB^WNm|8gY+{)%O9>~>C2*s+z^bxl6P z%BNz0h=B_Fta(vg;Sw3md9<{Wv0<<=O?WeoH5c{imP)`-W#X5yQ%YHGFJB%z6Fa^F zBZG)z2P8LEXtK52w4cH_^F@>`JFiB$a*~{g$I#6G`fP=^S;+Zt-1&aM5)$H!b(^Q; ziJ8PPW7v|ajWA|j2?4q=3|#-qQHQx71#M4TNVwY4Kt3PAV{AU;;tCybad*@g^8}c{ zLcyGferjo7TMz9T%7=|mudA-DJ9O!ctH7uW-&oYR7*fgopzsRPQUR9zymWpf-SLT` zMA!>l^z@X57R8)9?OX?^*dk%{vmBm)=h`C@2bJJ0gHWNol)p+O+sILa-`y}L`jIH1 z%d2-pB;yTQD{yND=90)rGcpbvdJ-nTBxo--3~LmRvxeVyu=hKA4#%fA(g3lNnYRkB zwvCF6-4K&qB&&Eabb+t*2X>3T_=ygQMbP5~42yhRBL@VpHVMFGnbKeL=AKeHAF)Pc*0@P`Iok%h*39p}n8HRp57M*`ujM>8`_ zw;cG=Hft)9KSkqS*h-K13uQC!$(9S$X*g=?U5*Ri3*j#wrHb6k3g$Zga86`1$dup? zlO!9bMfJTkqmJ?T<%~etXPw%tGTv}q=-{iM(VEv>($CU{73zW*4&mj4 zGUfL9z%h5O9e6ZdV)cRS$k#!vEt{u(rNa=4#Ots87+AP|KR zA8Y+mMt>gsZ}%hCA8erxj4Qs`9{m~p5&dQTK^m|;+7Af>)rTjWe)YSji+=T+CnBk* zEVywRBjX5cM$;JVPKW-8jVesW7g1XURXwgeTfsY8T2^UGPg(8m1qk06frw#hBQXBU zMNnpvh^v&0i-@M=CpzB_92}Iuoq=-DYpo79K9Y@4>_DN8V?M7A9IzdFKyUy~@VFg{ zE%_}{sB4@%a{ueF7s!qXK-}*MCN+*koRk%>9|tfPfDhDocbAosh}*P~%}b=vuJUBoZ~!dc1x- z05Cu!WZVPgde{o27By9lgo7L%q!u!@lB9{Wp0FPY;1aMDG;V|98T*XWPc?i7S&N+d zPJ%?@h|*6AKnmmv7*|BeCUZpSHy!pu+JOf|2Xgt3OQGnKIl}du1G>QMp;GHfy1q`m zLT`t?z<0C(UEtSzEZBl?e&aDD3}mHr(RmauVp zl;*fr@EuNo5A-!Oi*LXad}_DQTi|#%$px7sa{oICxi?9EtUE*jF)n<8pm;!}I3?!r z{BXZtq!=X<01q%5AR8j<5sMpJD+I=ZB#E6GNy18+fD6C^)CG(O%eusJ$GT$xpr&@2Khkb+pEMJQkQw_t#3Vx$_9M3CR&3=hS*gY6{@CxU3Ar7DrgkSjw0 zGyz#)nnC}hpl}h#@Q>knNKNF_7ZQAuITQdn z058xgV0?}wooo&P;0Vz2vkH@?jjfC`2d|@NITz#)l=X}yj6H_A`TBA2XI_>}!LI{D z$XMN2cho(ZVJFCC#MBECPm($Gy|Ljtux0#Iev((RTexAL;X7ze2FQN~=&zv*9Bnto3z=e&_!(3(QosS?pMgRW)0a0QYH%|Wlp8D6 zo@9ue6?9J&K_`W>`Z75XI6?Em6CZW^onZfc_jX`#aB%&8 z;p^+=GCXH}h|A-9)u^_^vD^;@#YgXNUcKGrc>6Nv)p+;SjQT1ITSWW5KV`rn;6OlN zU_koGC8bg9hmYPtKtK{fK|q+l-fbOB8I6ss%OiI7re=~nsr8Z>Zi7Gl>}_vXtU)dQ!+N-Ze39|m>i!907iANP0?n+&6^W8VZ$~jr zD(aZ7$!TvPl*1!je^aVFin230QCCQIw)Y~o4E@!L{$<5XfMlML;S#oYm;uN9?od-x zA@-O07FREI)ALNLq90GMQiv9#*#AA- zrQHf_2eDCoo)$#^Us?92{LtF_VyOGYlJP%gDP`mIKP35N`UO#fvs>F+T5$v>OMZQG z$x<(<;e)7^E+ko!p2xxJ6OZRZep5Hh)p!DbQ`}j5_D^aVm~3Z7X1%>weI28%nKBXW zQR-)*_4cU1g9>IRZ5b<Yrijy__YC{^l|qK?E<5ukP=7Dim0}PwSmLeD7yd)sM&^rbk0CGto1>F26x^ z{R#bvNHY#cxxsW%(}W?N=I;1U}Lk#2802XsS{ zH2MC}4DM zSBH+zF}|v9^x}%pmP6`zXqKiGuK%1pTTw{Y2=!~ALWmMr;ePDDbl;Je zSliYb!jc~KMah*3>0^)v+$I?-43{91x1pTe4F zQvR#fyz)?pg{o`h`7+2~T;Au8r5yb7o3Vw^vo=H>otbQN-M6t19?cj$!!`zB?Wg;) zOXl4H%Yqmv4`4O^Rv1?JTW!>06fYSaIM)(agDw#ACm*3R{((abk&GM*IlGq#wVFk( z-huTy*dV-v!muy<7W%)6q(-;l6!A+WfnN{Hee4N zIu{PHzXeIyLXlA7;a5U~X{eQAW6j7D{`74F@_C@ae1oV3F}y&CgO*rh0>H4d)>H4z zfS1J8wjfBgnB;;ygET)-Ei~Y3iSlGLM)x{1dB8GJ@PZOr@hw?Z;Z2|lD`ZcqU>dz_ z;4a|bbI!hDds{c~kg2#nPHOTuDT|dnQl%cE(;0p17Y~rTL!$_Vf#y&M+VM{p4%?Z8 zwayrg8W%mO8YpEeC46BLv0f@?PiR&G$VSWz$jWi;K6E4HvkE`POgvFB4iWqc;Y5Do z0lNZ&Y26tP=i=G<-{(M^vVeSDqDkw`3#23D_Klc99w-26US1mOq$ zSGYh59;v4s0IavVFWPqc8gmAIHW}|e`7$P z(f{~s!0^WrLtk*a5(SbP61hi&lv12z#%^HZ;>_&+eCY6uu!oex=(k4Lj9`5PH*B2FAn49;cJ1)eoTNCMHC?C-yk;{-XHLQ-r!%g_=+bVV>R} z9BA{X@JKiIWeDK;e@k{|XiqzEc`{4qH%pbzXWv8>Zo>r9xrB^Lh#N+a%03pR5juXo zZbH603cX$?j}aRRKh*Sri|7&hJ%wtZw#-*@lQas4ti7cyv6l)XKI)TB{prR__8qEp zfDyGV=^%MR+JgUAj0z{2^oN_;IJUAV^9^S(D zK+4xi8gNGN&9h1xZua?yhy({j2F9t79pUwcAe{N<3U8pR^Vu|}R>Iv5)=W)E7fUI; z&W0QNaxvF(^lA*vN>?VoD_eUAS+&+8v?gJd4gDm?gf|X?J2vCX?HiBG?XYR@-Dzva z*@)ban2y0{lFR#+)R2M@R)4@Ja-;P~t<;7Yjpak6I?qe6Zyza6WUPXdwj$b<6KqfS z=4Rn7_0?WQ!UquYL9ZUyH8Ydc7@W(tHMK?@cj(#%j;fS1*u9&{6MXVQ)JsDTX|VPR$=Jy0D`d0!A1k8pW!?q9tcW24Gt>=< zAdToy{+FWXsKoa*zkPcFc=4}^rE@yh19o(M2gZF&* z;OAxYZkV>o36B2TB&<$?90|IsxJ7?9KM>Hkv_$P9>+lMw!>9C4*K(ySAtKU+vp8Iy zhdFtdQn~>Jv!XtOSQ^mtbEfW<;)rS3?$Nbz>atrYnjD{Fnu%*)$Y+YGt4-xH{Hr{n z=|+6=JBdhdY8d|ElaHwb#pzS8^+i=C4xC`RP~wz`nm8O6lCk!yBV)fF=^qVJ5#l#a z&BjorGvaRHEX9r=O={=z%MkPUG>j!52wV977i8a8%!^1E*$@5&G4kvAw=Pmc$i`jF z#rS`MH1D5sGG7e6=O}H^^P7Ox9;m3IooBg=O4K{Kdb}_@l@RsG12x~52Y!Agxz*fl zAzw*^CEV>U@tvi+-b6lJSd%Z+WFE-_Ai*<|DAKw_FaU!`mj zqg8lw)xN6CKbfz|A?-(`(X#wToo!u^p zoS?Ph$C&%Xpk4pV?OV$b9o>AXo5L4PwtuU3;m#m zX^7tx=PO86-)70TZ1%46`5 zi)-k$KX6$YG{6sC-|KWyu)F2Nt=uI7r58|VpuPGyyEf2&*jVK^ZZ+eO2#$-V~3ySMo!KS#{YQ!|FaHr z{BQrS_+PFCJU|L&``t$XF2~;g2n8)K015(4m<>3wR~+)&xO$D>0Lx3wp22b-oJcon=~lA8E_0%LJcrx#-uL|> zghqKEVl#51(oo)$cw}7%YFWUG%>0P&$v&Cw%~`Sm9S5)VFcW;gcUgThDz4nP)_s#o zoDH-bA?%5y^9@QU^~E4zmq2_IDMFuJNqVW8@N$lwNTDo0oVQ$Wmdfk}>z$2_F4G+$ zRwlS7fb6jR&tPQ80u8W6ZvX=d*QGJcJFb2FcXneXV-lwLb$`rxrLQ}s3dMLS0{t>y z4RF|F?4V1+=85nTy~^;jz=vG8ZRe&tUw%^Rda+~66P(58x2i`0TBi0_|PdOXFZSUq^a{VNo)w$sg&CdzYSo?0u*UW!kbepa(oxO4Q78+1}#oqS-Esy5<5%n7ua+9oN z3zi>YD>T66Oldl%502rwqxIl0>SYUc{V4KIk(d_+X+LGV3P-WKxH|ps@lnJq^<9X_ z8xxjo{blk}7-F=Al=MOVXSQrG=IxG9VU30&vlTD_^~Y6|eedn3->iV*puUy-U~>g2x!Z zxc$cQxwLk@n`6CzVpa`(F!l5Aig8L^-|US6IcuF2uv{Xtw&+;i%u!>B--vhF1*so2 zQX1d_T0PU!<;F169@-;m*~-iy51VC~O%xm}@zl05JKv+^XLGBBcM}@QNJk{YyYPRh zpINdnQnQT2kW)a$ui6-D?m!XP*yCYN+&Rjy$%?sjmcwGYk5z^qk?T4*{W*MeQyTioS@Fkj z$Kqxx#{?~j0XaLC7^y3sNLBZ$nIM`7mhZDiEMHOrdPcj3M@WrUG7AuwZbx-AK%4)U zAy@thwy~KZKT|Yw+67-*`=%auUhJ(|kvXg}p#(PkfgwUc7$F=RlEeKG`SUVXw+px8 z0ezPJBiVdj0rg2-T7gGxxh_A{ioSrSq=-o}v__6i92rgYfm1Z~*Wk3e-?KXC!9uW1 ziN)5E54BsayNLG8K6^zcVo-$kUt`I{@CDA9p6o`3(EwrdL8}4JdMu370k4gKzZ4ll zGaM@;r}}GN{5wTi>A%Pzg?eJPoqBwOO!nw zMcL5=)cv(jS)!753zp)dAe|f$oXdiXp^MkGNacVo3Bw9tl4cN#C(8c(f#)sSir8go zmN7UeRS}L=Z1~fTy%7byY6->ir%cvq4bo`@3|?twd~tg}YA=${hHuPywnR3OQ-cQb z>zSz%V(XrM&G&6P+h{5X{?H5hYtbN2OJ35}^c~pc^DrtMOdH0i_*Lxg@D;E7ErI{L z6#8fT-*?a*a|Zcq_k{T0Y)$=_zmSj;|KBd7QUlgaWzm^m!8UzA>W;!KG^Ah&1;1bh z0qi@`sv&GV3X@R!431bV4wN}v6yG>CD>=DN%C8WPiJkenImk4>aP!2bh5;lriY4he z4yjXy<9H71%p$p!7P*uLsq~_XQQr$sa>P1Bhi(wZi`K(6|^_QUJZsHq7Q#RR~M z85Q4dVe<@EpeZiz(AMsc_ly{}7xzq`UJ0TXF=E1EbOax%Vuh=4g%hV)i9#poVpt;I z)UaY)-pfMPJK9L^@LH)s{)sJh+_yC3t;N)9^UTScug#hlIU?UN=pF7%y{j2%k2tFA z>lybn!wt5GuAU_^sBGgaZm5($*J#9#_E}wL`4(w2$G7|zIi@&o9JgItkk76-Zw!H( zMpt58izyu-`VTur-BVl0I30g2K1c%vZf1z*vYDRX0tJj=eFq~24#Bf_kM)@zRH1xF zW7tH0eG#ETre`LLzOmgMcmJsDjGtMKy8Pp3~&&WYet+#EvHg^9K? za{nPtmJF8$a3vCk?M>V1<<)^-`%8q>rOw>_QgEP)`x^j0;#EZzM=uMFjyxox{9GA^ z%Ae=Nl!`yHw^KSP>chnfg9(LR`n0WaL!!cTyX^LZ6K#re*4?#TARR}l|JE7>#Js#o zqWRqN`1m-qsj)wAUH5fmlhE?=#JHj{zuo$<>qspO%sSPHfmq?tbx1iM7I&MCRuS=X zt*gAyRHC8MRbSgx-AdhcXpK_ONi$DcZ+>nD?R*8hZ-ley227Jh!OqK%7SV5>+6IAD4;r@GG76r% zUCvw9vPB8c6KEri&h6inR#*a=GEJ%R1v+Sb>_qe0R?KuID0${%aU{Pxd}uCJi5=YwtdDK|p%kUg?{9QD6zgN*~>E+G(&f)2B*XjRBAc4Q@Ow^j+0h zG8F)G*D4}6zr3htre`KY#OBEJ-TURpeC$Nli86!Zcz(RoJD@Ep81DqG)};nkhTOuM zMu}QloIZ55rYZ7kQHRlqm!00x#G^G29SA+J*u@XNPD9~id4xt$hL|T|wxf+vu-7L$Ng?45v)02~z?g{cDOQV*n9QBPh6|sQF zNY7t;cy{KV+ivGQw?{mMj85{eS+Y$OMHbzDnR4K|a1mDpcsklLqI0|ROPlCJt`$qR zhMlhZJ9{(Fs3MWSX{hpn3OvKjhT_KQa;P!E0M!4-**k_;{$<<3RY@vN#kOtRwr%c; z?WAH`72CEewr$(4IJx`Zea^Y3`@HXcPWOl3e)i`v*PLr&jJbG)Vz5y1Z36I2YSIPq zo%_gbJ=Uu(Dm$*ONZKqJiQF&P5yHXWdEWyBw+d_}8%%yLHpal^u=J#YzZ1VBYrssM zUqoQuTYFU2P^SVyUC@F1Q{U?abt@jbrd`Y_1%=it+B%cq zUL?7Q6pz_XLX;S@tHIw<-WvozXP#ehu)E_%xq3^;pQEwu&f+tzvAE zV~3@i4pHE z%K#}+7_2hLdf{B8k)vWOVV zfZW3>Le%JDOT8px&7v#vWh>^t~NyaPt;<{V7 zsh@u6bJcBBX{OlsZ>sT$ziIB@6%dw+e%|bV=F2|axSPf>1TaD3)Vd9aAy_ZN zK|4>M@IJ)#8aqc!x3}`oh9J2YAmVh~CSQarWAKcuFPfnF2$n~-B-BGq))9VJuyFmd zLBW%VIe~7fJ-0i)sM`SLzu0vkYlY?m3~*t!t{e%btV7qrEr76wy&>L+`#o7`+9uO! zaHdijHu19?_d4XvOQbihl7uICJO#!-EqkDsEn~Qz8;pn1@NIiGuII>m4j$}cVK06u z(|F^LXcJoj&z=#7@P@yuEq>$Qxy`(NIRq8G6C6hty$fL+$|u<;8?f*Xi3Vo~TG`Mb zHf#t$mw+BsR_PV^o-7lGq*5f|Em(57lQO?i7viM!Ol%Ou)M7s{R^U9EZmE`0)Na|Z z0}LVMN_&W;Dvn_dKGHi~=n_}FWYP)s`duPfAJgA%v-YY{N56oV?1Mj(W{MS1H(&)m zDlR55LoPfS8)yEBFRtffk^RjV{1LluVw0US8arzuMqWZwz?Qm3CZSFy88_uJYCaGo zghJMbTIauSsZy{0lFSQ9SRS%I2_-<9iq`&BDC?t<$$D>17a*9cBOsedbJ%F;X<768f4$&zMj}gij2C`hJ zRn&$BKwJJwpLWs7dkA2oflC|vM8_|ZTWfNOI z>}uAgWE*mERp0}`mCbSqPDVn3tWu+~n0f?qz43Q0&h*+wM>~a|tALPDJ(m+M==GdN zyV5)PuoI_c$IR%S;u!2DK&l)+p)RpkDz9G+)dNMw(6Q>qv{uYCRgcExWLnXJIrlj0 zgPn31GB?+5CxHYp0Adn6*af(+5r^_g`W~UYrM4`?MkW^GDa}zeJYXRU^r0?biIMMY zyT`GrUv?qe4zzd`uC_*pSN6es$1(FT4Z$hyX;zkk41V%NNpzK!p~L5sl?idQ2+}kR zG!@m$L9Qo;P>@sfaTwABQkgWoeO}x`|1-9$(s8v?_(sZ%oL6 zSJ@BMMQ+lk^drNPA{R%!7a~0299)&-GL~&JUV&O)qOSPRDLvQ>Yj9}Y@l^&i{wN+6 z%N07RUB?Fub)UrAUdDtndiG3sqO5XK1G?d|L6wul7+kl{K_d2OA({esZXs!>xq}?k zraf6l>Q%(cD4=aP3@227Xu5^KqpYYS7M^qlaUTz;3uG>%RH$)sw`MJJbLv;4nKtQE z5(th3Jfx*hQRv|*I-#evpd%_MG{*0r&5yiL!Z>rQP0M=VpP%)#%r00PC!M{=T5i%A zZkiPu5G*LkD^X8v`$#*(u4C97W;Ci`w7|IIh%rUTsO{I}plfPmX1sePm1gHsq#Ra3 z{n(jZ=M#K%1VqbrYilK}a0@yUc8*x3=OhuzC#*0Dx(tKmoBNN)mPX*pA;ws!7G1$s zLZ@mhQ7pQrk2e}+R*&egH_J6Q>@`fz#whbb(>PpJOb z{S3rXrR-VrIE@6KA2I!ua}5Pys>M*FG9$0-$2$sbIU8^BaK)m0g7_ATWZKCJ$_LYP z%^#wmb}6C{A&jTzU~n6Y?cn4tg)N$mTa^%J$w6^u7{L}zEQG9KbbxieeHhjY{dC*m zPu%VL{(K`1zA=8;GwVGZsz&g9x}GcUr|Zui9nNt@12hwINBpC@OyoiYIhnZXP^uez zA{Sjao#MDH!&_rTg8=;>DNklQJbQ30a&ZrCgnt4YGpET%s7I~Jq_F9v>ZWCKDSx`R zkFy#A#lPN4DdUY5r!1nq$-Nhf!%7W?J3M}ruSfO^J)&m?InEwI+Ql^%nbQS%spV(1L z@mr)7!>k^7SVbE^0IAX*;vz#8R77NtA{{&;Y2Hn?X_wZ2s1f^aiL7YGB3dYgOWc>g zmm5NGBGrJf7M}b$h7}PK;B9O?%M_@F?!S=Y*B46(zt! zk7IO*28$*BI*Ir@lHY=ux41M2t``0fj#E61dP5fy6x}mU%luIBxbN4YQlfu@fS{j$ zi&7|c5XTbKEN-%czZ5QZQ||n0K&1SfTF5+}z55zb)q6To(OHf}s{)+hZ_&hN=ystd zDNcnUW0xSKq_nId80G~k>YOxHwfAwKb2FIRKcj$0oFzsr+n3Ha{O3<9^pgC{Ddf^YITg% z);(mgiY_6%ZPUFbm$dYzS8c#iOTu_qb-rjtlBi~esI;E_*?`;(&DIIZ!Kn78HjV#V z%(VE%?F%Z3`emIQ&2Y$A_q3gJun8xc(zWu<+`dL9@{}o|W>1Fsp3ItkMso1}BMW2r zk~}UpKh^Qf-hESSZz!@UVbV=&4stFd^Q9DA>&YQW($vL_J_7CP)NH*dZ5tIIw?h)O zdFkr=yqD0jU;1Y83>R05Czkpgu0iAO z#P>*c7#-B7;G1PeQNE%)74JA!*mfwzV^)o!?<{fNj$CT74gq@*d>umRo22KLJ+#oD z7S_?umd0O=-rL-LE4GXWjk9xQi}YzucSSEcC%gNFmK)eiu$SNNSiaI6GE(&`DvVk|4%c5Kb9AC9KadD9`K|6f6WN~ z-XM@t5EU1+v-=NfMO2)u6euG`@RrkRDwkYiG(W1xm$mRnqPE&l-%1rN1hKYh)e3va z%+&7ym3y|`-Y9H#XgZ#s#n<3^0`mp)nFh#i+k5VPDfI5I9`4_2!rieo_gRaqH0)?) zu)81I2Sb7|Vd}xml7@6_kW8e=xewR`J(^tyAs3}4vLJaDq1uMS?=9L zKo40hJymAKGIS*Lp~dpyyw^KS{Im6uF$!-P1t(J)=B#J-L$iX%doZlvhI(+I!K6R$ zIXEgVmfVL=c@Y&QYZ!!lxFwDJ9}wfMsak#~Puc#O@(&#-6Xi2-&}b+*W@5xB^fP@3 z)I2u*dCZA2qqoEg>7w208Ri~8-R_u zilNbeQURhA#(=C%go=?t-KZIX-$aTgU^5X&vA-lpAPh+#83BXA8>tLyg4gLCZqRPm zp#8Q=<^isAAyss3B|ct0UA|v9go}f4&&@5!yQkEH17Jg-Pf)p&{d(PPPXiWM=-o8q zs#`|g2 zKIX46qO<(TWyxb$7V^c_$dW?Jg74I>sc}6&4{)d1J&&M;H6ydDoO}*)s3Yaqy8 z!&BJS+WNmJ08z0$z*CJPdUe*o>{6k2L|ud$M$o3HqoG~uLZRXmgA>yBx?l92!>YUxunn2l|_SIhZHZAE=vFrcqr4uIG&d z138PBQ*pA4{rc5pe|)M&3k?=|If}hyEKR6F(-9-K-|=#C6GkV{=9;JV2v=-nEVHzg zSE08(4IyCKf=azR{88-9duL%~Zc5s;xP~o$zABazgX!|@3{!=wx4(CVcpZC8A$K8h zKk0Wwa%w7;s(ei?o?-g?0@$a|RuSD^&~4vut?zx@w&bBJHD68PZs7hAzhcROU2q_F zX26f@e}|ulsj)3^ct|28p(-Y(uOcM#_o-pjUt|geAR%S;u&*Qxf1%!OS|=+<{AT*c z0@1M`?{RKN|I#lceNy+utph4?=L=|U^NkqUubEb>}r+; z_KijcsCpz}l#wge=z2PRv7(CVNWdCr%`8l`s6aLlR$qhY#=rJ#uKqStw{<8M8YhIE z3C8XXILi}WPFHDF(A4Bvl$xIsLs>168dE}nXHqw^vv=|;VRBJV4@fBY@pW6_C4TaC zaw}^r^C+ya92m)~&5%-*%8a;ks$de9_~-)T_OgoLH*j=RVmWxPi}@MX@{Dlku7+J> zmLPWvE|vGx=NXjLA1Jm=&x~&v#Ax7xV2pY8PaWCipv0p)Agba(bpM}2+5a1G{|jA3 zRY5tG{|2x8m=q9R-UPncew97rTu=103Do|~P%TlKUC?qE-NZGHz{EA}iO$6<2SWc) z{B7~DxhfqlyF>Tj)Vue)n{Ni#)P$>ySifQvhJ->{`k8Vc?kg&@ErcO2tD$O*&tCQ5 zVUdrcr`Kt%PaYu~qb9V4I+=Z9CE2Q=2U;odaj747RRO%}YS^mLPCaFw?p_Z4t#;*L zjE6`ph)6CUT}_*vZJZoe51t|Bw+mZDoQ=ul(KC~^#0A2A5H4MZ!YPZYgBhv}G3zwgKkd+qbzCE^YbUPt`4Ev}Je`P(CiV zRDo80ltNBtIv8VGYTh{lf9~oY%Ee^pCO3XKS(~ki3j!hJTfWs$x4Q5{)2cPGxagBV zLYt+Z2Fwp_l|$^PpM+8@&{GL6FhA3c3WF8u7T%U=da z5D@$K@N>0wumoN`{~f|Y#lJ&17D68C4;i|MCgn($jFq3qh=E*bMMLK~v~GH{OxzyP zpZ#Abicrh4pfTIJ+}`SKo}aDV!TjX4N{$;!3n>b5_6xGZ88QKLXeI|qy6%aCtK(?e z2NRLcd8t+6dNN5ycF>PeD#?IT3QyIGOpIoRxAt2!5RsfNrofJd)x_2?A4GJd;;kHU zQU=g!`dyluA!X;OMA1CE?0OaAV$v$>!S2^wPf`(v$b9=iI0Lk34 zG(hM4kT>uv|4GD<-ib*!S<5m6d@fqyfUf09EYdlvh+_kVTOJ{XIjtM|kqcX8x|?a0 zhjm)$755))a}aCwQyFL*L7;8;{x#c}=mTRhz{S)=-^s}8?~yoD@$a;@wdw*undE!f z7PNnwG?*yZfN=RjvOnH@L`V&wnUqyl3aR{q23{252I48df1}Xtx`1xnliAbl&g^jS z?g6)(ZV`1HprAM@+|%*%+t&z?WE2`|4N`@d`&!f{Ou^G8xmbK9{&OdzWP}2U$0~o; zj}?xts)dYlsRV&)h_4Z4d{M?|$mi^RC6@KcS8zsVL-1i2-=HEuPS^CZT~aq%czC%D z^Mo62XdNYTo(>I{oE4@ zNQQ5T45D_|5PjAm`RpdvzeAz0|3uyB1i=twiV3q9#nV>}Clft-!E;XSMWu#hB z!g;a6B!A)Thdx0DxJm>V2yHSN1ED4N+Nrb zlm+nUx6vv|UzC-GFF{?8Es(t`dW`!(zPOU*bKafgxiFFu!T$PQSofZp%5H1Ena<9A zx1XZt`{&yi>}z(^070w4*l=M%H^u@(z2i8R3lG_^G3rT{BuCQaxi^k;f6;ZV>wVuv$O9P~$-fZ3k3%hwd1n z$^V$XCJctRB=HH{$>(`C9_JlhAHwA8xt0u;XY@+jVaCGiGsnb(kM|>NdgMgopSu2* zbt?)dZ;*kCOyeUtkU;gD2mwPPC7iU}1crvJ$v~UZv7gRF)?DFg^x#~`e2@z5BCmUK zYWdu{0r#YG~?EZCz6hV$uHZ6n9)eJx&Tn=AIhl^lwRXuRx zA2I@1Zd>=kb(e`o(-~<~nUzSXzd*U#nDhM!Uh(|(v&e}~ca>;ccdkKS;DjUuM6i|I zG0>=bx1oC$LhPK-A$8nb+@eo()yI%U6KT1%DA`-3d3G{7fRb})oJ{;%TUE%y$43^~ zs#Qs`32Dfq263mC4b!t+`BPbV^$d0CE~N+hx8#YdB_0+o4huCkWrZ6!R8Pun;vlbh zYwlw2PByu-h)#EAuFFAQko4*k(dpW6xFF5C){|$;(YTGgT?F3F>wa{&?HHFv%M&#a zp3Y9p8q3wj&|C92EH1}SXfCHMnS|eN->oxp6XtGkz0`*uuf1{2HC>~3AYkWUO?mS7 zcpRCZa-+@d1&17Y{_ybMfH?9SJu&u0!1C{m@S8qi_Dx~>^ju57g~tmFRcpG1?XW*p zMsMvzMsL+WLA*&0WIMkF1$EtG=?}_Lsk8Qrn`LBDquvq^t&byXD0#P_^)X#+TC}{_ z4Y3|Eqau2ofWfk#MeoD(od91AvuM^8-PJSe?fZjW;OUYzHN!UUWD^U0m8`Yo+v@k? zCo~Y1Izmoc2EA5YbU>rQuyES;jf`0CD`R0{A5Q~CCiSdjaRNY3YLrTIUZjNKDL%>6 zSuaFL*xBc&*EOT(OS3XBHiueWL<8g;?jkkub5Bp6ECeP_oiw^Lmg^5@WBB;I=oiLH zCctQ;=S%5Y5V(b7-|TAEq_>R^ToE&70$zv3!DJeh*@K;nv$Op~qy&5l$`3#Bs)&bj z+fiLvlrRIbYDHnP1mfnvvI7fuGzfD`nMXQ@s!te>sl1#B&PqWw-afPZGM$Sax?a zLf^jNhA)K0uk|rnU;<%$aJMi8!_`8M03*$BmkIrKbe!uE5C%=2-JDturQ;23Kwe zhHCnlh8RcGfvE_oqcA2liK8)Q6w4HQR1%bakY>`gIOtr* z0$@GjV$0)}X^k1;rlt2urd(u{uJZ`k>Uv)0oBqT3m^;)H)H8vs9S2NS+*}eT2k3 zC8_0WNd;voQx9;gpIj$Cl_`-@Y=7SEtlMP{0@;c!fEa|RaNoWt6RP~ zZci-Ep;A`p=v1~SIi}XK^|hrhK3>l!dF@E_{;rEju7PzVCK=|mI6-{+uu5Z-Com}# z>YEz_Z}$C>a2q_ecZv}2r?_amN=e>ros?T&^{_OkH^8;+nUUgNI0eETvRM(&Hc}nS ziX@g(qe`M<_z<&Ug#Bh0z@mdT*3~XeKFjC#bA9sd^`{3#y@kV{+6q6h=8^PhvMXBc zS{$FeOp#~&3|FL!zZUwC<>`~Pr5;m~{$$KcAmJCgKNHC&coC=hWdFw$Ht3gwBpWb= zH3Fuv;{RGs^ux~7T;KRF74qLQA{!S|2cS;*?^#ijqAXC0gy3CI5CIt;9-77bB_+*m z{ujm@o*wqBH=hu2?@Dri3q`?G02Hpxo&WC?@>1pEhLE7-UMk3fQ&-Xwf>4&hMUelylB}s64s2NcK++0MD%GNQm`Q_tr ziB$WrX-*oaotUTS_kftd#oD-V%1M|r&FNOiwZJ69Lhw|TR!mhj&J<4Dp;mH|Vl8S< z9VW#aUGmqX!S6~*DWV4|rD=yNM8SkdU+ zV7NLk6l*Bwiz7ImP9(Do$lN}xt~;Ul@K52`vcA#JB8iu0C~wSt28;2(VET&Q!Qf3Y zWyxvqHj!zcacjX-*qc|*bwdxyb6D1I^`G+oqx-FD;=)e=ZCnKUzwRslXQ$~uY;0|3 zYXdAy^Z_?8y0X6Jfvivf04RXrR2e0J<=@D@VyI;z{D1|DaQ78|-ge(kZ zk$s=EsgVtMQqPFx_?uLvmK)&T|Fx#!^*qvSb7kcwCVT5QXiJP4Ds#)hPE$xbbmKH! z@MQdiGY(~m?l<32Q{tC`n)$g@OxTAwD94mK@3(f{*XtQOr%lNQXJ{Bj4$kzE75o9k zO=HCkNu4J?<&sB=%Rje(l&-lSXEZEOqFfRD3B6MLVofFLEmBZHgAG?AfeU2l%_4}X z?;Qu!9TvFJcVz?aIQm${>Xebl1NPSrMs+$NbeP<4vvF-RrTuwMHe68d%O(?@OCj=l zP0p_;d|hKzjfqVh)Wmc*c3XuTKYK~=%ur17c|xt_N?9cl;AK+$I|Iz8Tq&Fo3$c+i z#SbE+5Q9d!vu57*mIt4kddCkJv4d97j^^!f*?Fuetv-b_KVsWFJa)x-N*DM&&b%G1 z{QH+}uFK|%V_$u*^>Ha#KKbAu6?XX1H1=2{I8nAbrjfG{=^)E{MGa+JNOaI%dfV*2 zMa7pJWusO>O+ih;ECr$474EC*?CIX@YBJa+r!R=!J35RQrp#wz$e3!ie(MjJAu>)0Ojz18eL-t-T+A% z#?-?oY#B3}^#%rNgLcE5wX@MuY2t7F9Rl*YtvepnN?PQL8Q5jP#Q74xr$J7DZ<^2; z(5hy0RfQdYk;bNCl3TU_W~0znVZE{!4J!L?VPvtUfWi$63a;evzI`@zVSUccHY+MW z@5an+Ui(4RDT|?$L0#1262yjaGYsA(+7l|aSpB00bT_kL|EOqQVvB_uIc;}qlH41A zwKWdf(#D`QkatVk1t%X0x%83?BrQ)qNEMZb5k*CBS8?>d5>3UbtHy@1V_D@Jko-J> zmm~}`)1903PHvRqs=PRUh)g4v4XDVC+xFxrr)>I>Mda39>K}drH(Sap7(D=Nal83J zMx16v(H=2OQfyZS^F6w9VVO(D)|hWF@K!W+b^Ru>BZeqoW{)~K$n1f^10uOsj;}AD zO>z1O*mb*OIhyVbbwy9Xk6Blbbk0`|cdyn(ob>|ER~s*=cPNd)cK|F*@ph)1uPnK7 zA2w_gco)Nl^bS7)LA>p0*aH2aWsEW_FEA&k2qDMDKSc>ju%nZ~8VGffZ{el#ty^Ht zRoy341Mcp@?q}NL3!M?ZT`y%{3uA;n8|2~3Jt_18K8)muG47B#Fz=Kx$kOsejiLRR zN#ODWNWwL}LZXfMUcZ)F7Z@9Ev2kq~9C1e|Icy`~9us6y$K|WibPjyj${pwygPZQ~ zz!cJ&B!`;JRmV~nH^+HK+0W8yxwL&Cd7H+!DWNw{p`ehriaf&$Wsdh5HG3JBYy+qb{&Sv#T5PY z)hA)E#nr-FAhR|)bwul^X=(E)BgyOi;{@iXq}6B(vM@WD>AJNL`X%GKxf?-#%5!cc z-(e|P~*5{6owgw8RjexBe}8OU>?|j{YAz#4{h~p8yF=qVVf=; zN+301#)O$UX+y>XYrDdAAbl>ObVOQtKu{e(X&GC|;o?2dG6k3RNV&#KAtB6U%4rwk zC{RdK?Z^5SqXYN%p@w~{F?5LEhtF;FBn^usyt2OK*ohL1YIE9HaM`3`a@ff`{Dh@( zGfOIVtCPwak-fUW{_<49jgHV`6>+$h-u`*~-mM<*7VS~V7gA%*UfJ{p1vex#aW$*v zlJE=wN0vgTw?1nL-qNh>0>z1FE}iu_{bZb+UQ0k1x~yqSY_hEW)J5g#B(?V%iqj$z zk}rLw0a$*|%{Wy{px(EXnPP*VJ5*JaEz#J}Od3ixk*b)M(2FzG+e98HR@*`Tf;MoB zu*^C;D=8J6%8%fU@irP*`jM9DWLxHs*<`COC&?A$qq_=zb8-IK*cTnxFq*Xz~+{I$;!DJtJ?%b zL~>;OnOKX6Od=J*Us&13s3J&)MHWO3(5?Prhegmv;vAlX zIclanfe2)qSlnWFT3ZlVx<%?`Rp&YO2N<+hGug1}2>9HBmhnuDj1A0AIC*iLlhdg@ zVk{_H{37>Y=+Pj#P(t70Y>(sYJsEJ0+H7Yu=nL_RUE*DqL^}j{WIOz`{%P0sl7NHGVl0$;dR@-tb)AUZsu*htfX!;fN)-}YkB#@fNP|Xe2U>8g9 zzx6)AyU8<{{25LijL$tV6|r-pYia)bpcJ!6lQKzWH64Q2Xn*@Ib@zKQ!^Y z;%x+zK%0tIs z-3`UEoJj$9Aek3AW{goPL$CRDS{T;FGc#5?8H*rze^L&h$xX-tc!?fDlJC(>%15s; zSb)GUb`iW9J=F76Ce%po6Fo=C*GL^0?|)ac!ZQ99GK_27v@+f_G#sPIAw{o~v*3S& ztcr{tVHT`Yj^zifMfi$yP%lxP|MK5f0{o?e(F%b~{7bj>5Y(EW+Qt0rIt2}ZAz2@4Wa7_VFGPO>c!^dvk~5a#Us`A*BPH9$G20TlD_-c z+=g2B2?ac54^Y|$Tv*KSAFH$E!lXJ->4(%#N1SWS_T=jl$K9P7lLaYEIPZ?E!82FX z1~4#AiaA0`bSw%%%Gshsw1u1*hr889|w|XvxkMmuXr>A9?rzD7VV5YTb^ zNhIZysO+IVyeM|&7O4%+rW(k*D#eOqE+u>a0XMjoCq{8EvnL@&)mXxzo}y~u z&w&Wou;0nyw<%djyPS6hp=3b2te?t?G?MtXY;>7-8kid(iYFRsl{Je@#$ttJuILu+ z02WDmdZR-sRQjXVeC9hn8Y;O%ib#GImm%+UewpZ6E-geYH~lO_wCV)K>l9mt#Pg9U z8~b?Y(w(X-8Bru}Af`e@Z3#Dk%0W27#hvNlL51Ogi6#!Hgm=N@G^H`ocZ6&TOuEg8 zmkgXR_O|g-F1P>fGUQ&h{k18UVIl_H@|4w0&j=~KE_25wg4bEtbLd_4aE zg*o*{+OvO9WH?(ch|^rGXRnqvb+XIcNsQ!%XmOs&YJlo-f#uA_SQF7yf6DQ$BD#5^ zLchd;;8aS!#C&&u{JL|_$ioCl6ElV=H4UhKiuou*yiAQ;{R4DZM@}D9yP0-BLAZiZm z>)k<=AZ8#iazCSF>`%xB2ZuzCN$xx>o9kIZKwAkCtWp<;r9$ud@~&fovDqGZz?PIgk(nX z{aPr(^6hQ;7c|ulKS|&NCpi_F{S+;A3Sk;%Xe$;k1fct8W&nm%8RSaJLNBTnc5rtv zxzr(={wNwaRF>NzT!QUO)aj^NSM8KLFI9lnxCJmi$!||`qPkO>TP;R-Wu}3p!D4=L zIO^6&Qr_lsY1J@2DrMN?u5)&>>>ME8Q4&W6rpj&TdnQ@bZE&Bxs?#=bnJCW5>H|!r060-nzhF*4WD~wgB{HtW5>+~%4LH+)Wxn4c z$Bf@;Enj^$3n*Y97_`#fUqH9$M>|!`i87D;ARYC7Wv$q1bmQVsEGOg&XKmr)Vv5xZ zx*ntPBvyU>q1yLmT>B`POcDGoFsm1uPo!P(wwLYlpa^b9PY7u(%qrxeE%E}{?7iO@ z5jsK7@QoKfrZq0Wb)TWZEBeDR0)J-D7E-e&qr17tgG1i5lqL2{ac*98$D%(uh+sV+YAMgw^rn;4yP zRL}~C?E%V-(RB!C;oEs8?FGTNsCxC?tAcu5oK1`RhQy}kDfh!Fr%ltTWN&vt5V|X` zrm^#bwK4e5E|84m6Ed~y%Y8xc?#ifZ)RI8Qx-Qxd*ERT3&D_=VC#CjH_V8ew`q!3` z-Uv|rNY%D_jrGTo))D?uf9RIr&f^en-IsW5%doZoR_~DSPM2o|1kbo~aHEY@bFcZTWfmHp?V=EqmgVADTmOuR)VY8*aE2Uv4Q&r`KLQ?S1Xk7$iJ%@$M5 zQ=ynmJqN6&Ir~Uoa(7~m5&N>Ije}$6rr>;g&YnnlO`lla7q4CK#rybY^x?8|k7l%< zhKwP92g<|GlI5r3y#=ztt?Ex7ny%)#&oEh=t_BS32347!8o4lgI{98Pp@K*7qI)El z+Sa&yoTS-?&a$ ztu<;|z3uWrBSfkDWerSqLv%{3lKG-|rA@;o_x7KCm!7F8r7YB-l;=qWmZG3J4(YLxXHrqM2dg2oIRt}x>B z3OHiEU#9icI0c=&_S`23e?B12c6L%80uUY<5B4~BCak9jMMB#HqkOGhLZQ})JMfyA zeuorM$nW|Dz#9Ij@yr|ep-AV1=55nt^60d=*%lEvxaH_3%_HfI*G%R6b#U6d1;D$% z>nr{w(tX#rNL3wX|J(li51pLb3!DXJH>>CxP7mLb4G2RpBJTo)lHokP$4?P7oyC`x7R? zV{s)sB{0jLdQtk1(uE#|`EKMi7pFHt!_h!|$gy#(LAIUdla9TaET%?KN=anId1lk7 zmDA+upO2TraOT#%m?hF9elw+7G;c#%&)yyU7rPyt*VYBXnkS6vhk7-&cAX90O3s9+ zqEGy2W~-5-FBfE7CGzN!eHZ$Fu;K$N$Z@2a$Saa2@8RxO8{$HpVMY?TG4_uCsZ!AQ z3{1HKRtXotj*j5JF|-2wHLG$o)i?RCY*a-X*Z@WODB5soGo?hKP^i^IKo~8A1QGh` z=KuvJ0*XZpKQa4V!>ZZYH6m<^aMS*GhK;lHs1a>e3vZezHTA$Uc(dK$&m(TERUuFbhU?v3cJ~>y_N~aVMLSA4^I?0){+HShp#Xovf=7*u%ZPr+8|95I7OC65XW5`(CW@#>#&Carm?t1qBEPB2UI zQv7CeCdp3n1f%36a3^{jlAA`4xm)rUKIA`|?eMpcVd7Tojn5QvkZv3A5JuxYr_boZb}P2uLYdKlhD_suFG? z<|Vwsj(x5bp%!!d`N-Tat<-{Y6eVR(3EyW4@#AkM+0Al*-pS&qF z)`yP{7_<6--@j9LTN^r9nmXwJMb-V!fx_QN<-iSnYih=pKjvN*HhC@R5j)BV9s;0e z{kH|-4bx%mE$e98%NMf$lT?l)ga`(YXSO-HH!(T>`sVTar6xoU4wa>xBLBc}qOa(u z`2kHE&i%Lk2YF{I1Pc$RIKs06`?%jzn%#L~c~&&CLXs>V4kntv;^a206H9ekOW0cw zEI#t0gN11o+IB7nybF`KQ7CM$bf8o`5(2#{a(4Q}H-xpf4UA@EcV%R0gIo@U{$iWf zD;zKtUVFJYxX3WFr&Zo0&H|N>(Xh5usYn*@qA%W+Blk=I>)KllzF~$0E;xvM#sRsU z*U#Ez(@(1NBcdUpxUaU+(j{mb>3g?0&pea<0oA6kPYZ&j2B#b9p*9E|OL__(bo(IK z1LWr@YPHdS{%ImlU%#n`0}O6Az`h&*zY*M=?HmoQ?f&mI)-&_4IkqMi;rgo=iV+*Dy8w@ZAz&&s z6*n7qD%$t$?H0G2EeoSXRS*si^vqOcX1*rZP`S?<&Xq;Ow8BWbPxoSTZn3H965GvS zo3KxYB5({BlZsJ=Cz%`y^tD^T#e~4vC&LAUZ1v*LS7B?M-MTr+KqHEV7>vl@3c4q= z5oW(4>{4gx@+ZQR_oW=kR*IS@f*bofOu>6UB<3z_ z_{xjoNU6u_<(+HX49g*b#$cRwO1Q0~MxTq@IVVA;)5qrlr*46EiwI} z*#YxOgrw$FOqSom32`xa_Fh9?EO(vK=Oe~d@3-_tw?5%Y`=m48rK(bVpXWtJx0~rF zotGU4Xw2GSs>{$=E)N(Z)6)Fmz z-eaK6+=s7g5Z%QiB5WnItGkX~L~yMw{PWMVFIUUa@hW#HQA&wrUPd*decJiiIWm^u z_0#Z;F8%Wxh*JkLl{hy>lhW=Pt9-i$i0BqGynZ`enh^WUyP1dPC~1wO>?q7Ku{VwJ zJ35mIoY-m7m{Oc6{JZy>Jrg>%LHFVP4^i?Z9V;X}**)_-Nro>h**oAnG^SPYonp@F zVXh3H|3r^X(tX|92ktB@1n!~v7xNdp|9^gLr6#~jdjK)J*p;6bx2Z!i(Ex;WKNgqU zc^DKYo&(*0fRU7B{uj~MY<<2+vW}!&#&i*aOgdg-6Cjrj01~X$APe73M5!(Zua7j-o6d#eD`4#&_3<-NdRlE{k_{@HO8M4^^O69gSB)LiM)Nq*~;vKGmH!m0> zuY405Ju#(lqe;alPBRqte8=(#qry|sM|>@sC+36^Bre$v^}{DDJYk+klqN>;&K@+V zsKhq%B}-vP67@IH&&7oCqubgJS^?U4P448-@5?J67Oitu^2HbDiz+lJ^^PD`ZKMeo zmQ8!WogCuI03GcOh?axKp&094S(dI}lE-O0jXWwESYe8LvzThtc zpDw+i`cWx%8`(-RpWPTYe&{&kMYfuPC}1Hz7snjdmiL^6l6EOAQtS*_^Sc}?ikU27 zP{XUJrR+SLR#tuuVlE^f6tiJ&E~trmiF9t{P3&ubQu!Z90_y<)2!>bO%vYyiXnCj_ zgRP$%A$hy0A79t~a4yAN7_!i}Uy>Fylr={^l&nZNS(eW|6 zYI2~@^M~F-Az+A|=pSivc+#ne>U*F~b5M0MxH7P{ov$UP7TE7){dLXPBKs5Wit0pW z8Fme<OA^A2nV>kjw)I}a+o!VM8Bz2Xg%*HYjW;sdnTRvv@BTm^7w+rW^JVZPs{QV0Fw=!5Mh_XeVh!5;)mY-H%vvk3_ z(;t=meqCOTaB1b2%J2l^YHyC*u=n|Zy&7Oz=XqUzji(;Meb=T3dD-5X2{07?xOcJ^ zj6LH_Y<4W6Idw^wlP_v!H_so6Op}uBjW2VwcL8`Lf7zhoVz&4&$B{R z$#*dKPDU)zuZOuMLPX$aB3?AVdY)U^*gAY6AA+doNgPw`Xe~>Z4HC)0hWgPLBsxbS ze-dN*m@qLZl%baB5}U8MFlq*=p}%pXHsUWplo~N)r;V(Lw*m?Oe<=H=;LMsZ%s3O< z_QbaBWMbR4Ik9cqcD~rQoiDbNi6+_kclTmzuXd}t>U8zxIo+q;r{4$LJ@$E>Cm)un zd^w5yvDX7^r!He=yxB7e-RE724dITQ2RL1v7hIU2h<-f{w&VW1JeT@fdOv5Ahdp|u zHA{T530-!eZSIce!xYIOzwbyvo`An+ZTNJ(VxigU?+cFD^Hwx3Uc>(&`LbS#Tv+=~ z|Ip+Vn+>FT0E@`A5TXf+DG4gkhM+I%h44m+k!rXWFaEJxKJaEA&O0e$JrMjopCL8m zVhA|A4WE5@wO1uI@{=FpdX*4Pg(Z%}YXlNOstX1c;o;^7_gm>hDk{D#bMd#9JJo@B z7@tFKY*ESy)fQl{UpcG&`1l3-{K?y-A~Nu=I~)}ws#hI{zyan~J!UlU^L4yGw3s-$ ze8^iA1vAu&Pd8*FM^iaOmP^SXMAj*c4^ftWg@mD*Z6PT$rZ_!b*oQ1xM;oI|+Qd;y z(}G6X0?2PRap)LYs@yugn88z%n&Ga=!vfaZK)Tx+C;f+TkChHCbkP%>nVvx;11%lh z2!@J*UWDJutmCgNcOeF_X(hJa5>7davKFyH@v;o zUBJU|>_O>%!!gi@V&D6}!8AoHutw`o-r}TbgcEBlTaBeds}o$LCR!NQge$&8I%?;CPXQ8sLyHOwEn7_blt>Rc3GWtO&=*)H59 zGP~@U5IQ?pr69}i^czfMk$QY_xgd>87rc~AU5|?k5k-A%*(y}R5JogFdwH)@S&iS;$QBCxXH+HG{D>->sHuOar>yh%-hY-_g5O)SA3yxGgm{p zjGN{GC|r&xY0bZQFQ*z;WOl2uw$%Jwu?$`817~JEI#iPAW&}synd1TSgMp zO0OD}tcj%&(IItz!pa4c=EI5m2a!&5hgl3n$z+h@hnArdDi&tmk^DGz`5hbq%O4`F z=hBI6fgFL~phUvpI15vOB_cvj{z|#8Z6qx9O7tqz&@gCQIC!OmM zR>4M+xGZq|ikFJgXSD;Qn)NUF8TLD*;?Ju?u4@;k&C`Px2TTPYy&MjhJOso!|G1e5 zsSL=$;vx(~%7M$chIp?`YK6RFfhb2twJEwKpcaJ|XgK|q1!!E@WF+h~=x}FOq#de# zu#w<^9WszZ$T6A^W~yWs3e$BTbBR01(MK2-Jw!d08lo0fMZz$!4rHg}Cq8cQ6ak7{ zrUkt(JoBtrj0)GNm7EWhI%}yFJeU{<$%bA*@}BY_RAESzo*ZEjL>x0cwkNz_Ar!1* z*KiAoYa9odj#3RrsUB~*du@##t-zAWNOR%7Gupj{< zVo#7;w3+>UjJBnqHi&3icsP|I9TFz0vrwxyG2}*7%cHx7h+xsjZJfbroZfMB5>x#f z9Xge-Tt$Bv+&Q}<_d3V}3q{H!+KM_%v@dOIsOj3DKq}k&eY7?*R!x8Jw{~Q(+ z`9Mk{KyS{-8R^3T?t)c4N;Z$D4o!QhvkpW1v9k_!dukbCqH?-HaA+43oz5%4M-O&> zT9_EzlJ=|FT*0A_tu5; z_Mzlys61nn_t`V{g=HL%r1uBCg4VcT761wQxtM?Cmz>l#+Y)uGZa`9 zsl*^;%rmK)MJDKYCu-*5f^Mk@-*?hvS^g=fqUFp{%Q)wH$MB>8$$n+oekUzgtlL+Q zY0dFsi5_}=Jo@$*j(;yay_&8o(x|qg4$W*eWcG<6XvptOJ*($sVH|A{!6_n`Mj8mL zY51tO@h?Z%R0@$!%2Y6V`a_ydJUnLdxZaE$v|}Y5pI-aD zzLcEW(KUgi?bB@cyTvLTK(?xSELcUJm4`JO-v2=5TqQeQF+sL=ZR^v&!p6L(d?B(X zyNr?;1aSpZUyBJ^jqsZ)=l}R5mhY!wvjwZw?||@m>8y7`5~Y2`9cn0eIi+xh-qVfPtI-*fquF^$<2@d@ zgl?A9nP?6Wc<1)zNS2@D{=Bqaaq>br)dM*ATEv6Um^s=?3;nPvd^rBY1* zc=Xr*=u+YPCbK&eI%uH%+6$^_SM%)aE{*EVr?pb8Dh(dNdiroc{;*Z&G*4ysKS>D^}ZdQv~ zAdsxDJDHh7L#L8|_RoYqKCwmIq1 z4;?Rhoh$HOi0XCSXHqOsx*$Jh9X+rTz=8m=1ZsXJ17QR@i4o*TX)F%X3}Q}ZhbY86 z-x-k`xXu^&vp*7q3N#1)7Z?+fA!q~^Y1@x|^uQ62QV>~UIUHkI&`Hp=x76dw$_TYr z&B`XQeYQZEz*N}e)xJWewp^vFUVk&oiXthEs?U^k@F433-B4}CwbT{=wrOR>*m&bs zKob-YXPhwfh}Ew~f*Y;?ao<)?tD5%oZ1VIoPh)cmHth1HOmIGOUN_=IEQowX z15V>J2p)*GYw5}yF5RLF=6(CYY6Y&LDSJ+YFP93M=!7I z)svJ3Si~l*{g^~ux*MWm#SV2e8AXuQjFowMS=}HI2f%PCyV7fsuvM3)k{boXa zC&^8zQ@`~=@7w|%&L%nk4)Z~?`@&IcfayZs*`Wh)_j!ftg|E6m_P}2SHrK;r2+lGE z&T8iv?Ud@o<3B+*|AL;?flnxCJ~0ftf0XE(Aqffg>j(Oczmi1!0eJ%dHHwvbnTQA7 z0ZK0TAib4}2WtP&h?Sa+^1&Pt0CuOM`8o$;5BkczX#b3s{x<>v1V6CX0UR6R6|$cZ z6cKD2F^Lzs3$1l0^tT76@t?rG`hp%G;Rl$>;LDP6MP$#B1^hV_?I(?3o5 zmwl2RSd_ou?_m3@K>~j78U$AaKzb7FBba_7?8^tXgY2%R_I*$@LPOm4_wGTyLPNY# z_4|N)gS~S02Y`IR-3got2wjN=#)BY&;zMzPF^LIxoi!WaCit%5B8^#pMRVGKm- zM@o=D_yrEgx}myOxolp6FEI@Y2StV$2OF1QU?-Mm{~ZU0jgSB*LDI(~F(XP>Q~f4I z2*-qI$kz`M7zvUgy)pr!0#XJhSNvvq2C0{OlWtDUt?vdEY7J4)UyVtc1Jgv>$0Ivp zS~V-ImLa#&$=17qhMvaFCI(eK3my@4Bo>%#Sm?*p>Hxxz1L9I0)v2Zj64bpo1ldBLKmcT@f~Ej(KZg5mEL>Wyoh3IL$i7HGLeoDiSOwaREz z|6SB>g&n6M`n~j5xfy`F%ns5DV&nP@Po7^{Z2?;UC>wjRVc3Ov#to8B2CgcIguJxk z9U05h-s~k>Q`y;QVq&4;;^9%#Rc@nV5=HENWLq9N*<1d!*n?ZON$sw0EbMOY ziWb$}5U1*YIL$gv006V*0ji)nH=}VU&kUVztxCq{@?vW%-oh>_ zH5Pi=bsS2{9z&1_mz!DA_XzwmfK9og8k=k3F5_%oz^skko}oo;dE1s&?u;H@t!w0) zdJe#Rqe=5RNhb15Bd52lrD|2=sno2M9m67BR<5cbYm#TPQp{%#2f;2oYob3x!&qNf z8Qz!qywQ4QGGnL+s`B4uL6x3#Yypj)o{~?MsF@U9N7GY{rq=VM8hc|@m8QIw6^7EL z)r#7&T5HZCW20QjYD%3hhK{9N!{BpF0w1YU=5$&SfEV^akpHvZVPI3^Vpd zcC~UfwR~xo5^$YfKucV8f-6)`3g}d+rrrjC1(#EQcuIXWm$Jys%&c5k8n;nYHeIJR5eZ@WTppT^nqKgF&W3xTcaH;RGof-x0wNxB-U+R3LuS!krNDkJ@Q@+Hgl}n9ABvM@ss~7g^f+V`vK1-^HuNn!*x72=^%uje4&vmo*OqXnP=LoTh6Ubk z!Y0DmSG_#x47DewHRzcRp*Y8qHeZTzYJ(ntcWY2*ta)Hl*5;yiR*SW)hKt+MW|~#` zg7PmlrIFZXoaDyTZot#DCOgq#OOsi|FsladkT1BC-o=3oJfyZ#=y8FsTaY0;&}~?} zfNj{ySUW4lkY0)dPKOfJqEaUZ0#d#agH|>@ohsuOta*so;6(YMSSp~*T+M4`Q7owR2g9qShlt&I z(&R&yrkk1>!7wd!`AWtv6IEjRflQ^AmYzIqXKLc{WI^Vfwl8 zGzHbk<|9@9lQj)7SFHp_$>T3&OpI!J!D{uRScTcc=$z6cI*8dCzOf`zIb~ADX7kkM z7PZ>S8i;S!Nh5k;iy7$>v&E*MR;onfKpJl9h}Q6W@yM2^s*4M?IzZ4=1q0ntUCWh9 z^jwB0gS?tnE-ia*Ii(5)lC0X5y)0y>VVy{tfpK+?NeO9&fwi&rV_M#c`vX&U*F<~T z+Tb&qG{NPX2PZG0hmYu7SzTD0rX-e!?%(}n$FN0eNS=JO<{Uynw;AUrON6i*5rtF> zVH+QtInqe#C5s#q=GGW|!z`k#E&xwNE>90w<;l~e%fwLb%IbWtk-EGh)?4#qVxAuZ zY&qb5s!YS()h=UYdwq3tV~4NW$7QZ&)qNAOg|lVGdS|_%&uKN-#FI&ciC#}%=J#~QTftKf@@2pyA)n_7stH+KUXeabsnDVFp5%F4p;UuK!rn= ziCrz8v|-&_y+`NpczE^$#B2tW8|)-tum5 z>O*Ju4&Z@XudwuId0}mBF*C)|$ZZ8d!R>Zkoc#4XOy<@T2CaEb!EZYyD)&H{U=!2T z)!B_gGp)*rNMYKAR>qaoSi>og>X1?Gt}Px8*(<#K(09pcQm-za0K1vZR#_U$!1D_z zy|U|SP9q{40XWb#)bCad8(JFAaY^R_9}`JQML3QUg41(YZqcw-jd(ZCc6QE}gpG?y z&Rl;qFZ*Dmyse1ZCJM`%Y7^GdQbkEwPn(*zAUIP5GLMagw#GWDEV0OaGNBSmk71UedNMGmB^qh#DZOoE?#96C*%i*Zow|HPv$H?V&(FG=X;KRd+`78E zu~9BIS#?I&UY?zwub<^+0Be_ut;k=5aQIAt~CZO$DoZCI?b`7nhh(H2d_rdbLs zB~qoPtnExn{?jhg+vdJEcQqFF)V30YyU+@dZFr7B=>?{=_Lw7ym>gG8Q6Y*hj>G}9^C-kb4~FpOskDecaz z*JYrj+|N}_iDP-;6qL)Z1-Pg|w_2=jH{x)-#g^vBURkU-Og6iJjqIGZx<@rz*pR-) ztb%#^<@|v}(5p==PXT8=?Y8vlhuE6mkd}vIbFf;M8bUj}nzN38eJ=0voZ}wAv8vUC z3&l$IzEGgWrtCrY@JUJaX7gJ0K3LjG>Yj%R(Il52Q?GesV6&>7Rp5CK*E-1HIn&;t z?|qRVuVnEN#)`D>l`wv>L7{(*>GtX^u-oi#qHUS)u`+39%AOltqrCZT+I!A1nHs_k zrXNzuJQY8`qOLbn9(1YG1wa#!)b46k(9{gP930%2&D~n|7N!H@l@DQSTZL1V-Nz_! z*|gZN!)Vzd@W;}x4xvnq-L`$Ceq2glen}C;n^1oQ?0fa^m9mdE#EdJan`H|dG2FDs z=}d?S17v(Qx64kR5wbV8XDJdr?8x8Q1l$3<9B%9!TVHScN*Wd%*fpv78l3#-3NQK~vCHO#s@e0w5*sqcI?FTtwM2zAzFJ;y0j} zr$^f(JgBx;X8ci+y+RM_Mmuj;amo9@4*Coy#F$c-kRfAZBrnv90Arw9CM^eAr(Yrl zc9Fh(4cckYOIP5Jvw?+H}xe*Gx;1%l8Y zV-=wdS;wZygR27F*ILjuW&6@nTL2o)cx?5MnE5Yu4+z5D?swQjiy5X+#?LP9+V@&x~oBj}rv zm!i;L3c9BPZy%u^(H-Ppx2m_WJwSU&&;a1@gc75F5?7(0P@We8-|u$oJq6DT6&R zYyN@Jp7-#;PeQ3Y=xVHl7fIxAu`es3zi1bpN`BM@y)niCQ17r(`jD@1{Wc)~3yMBF zfIiC)?+)g?a2N+bdqVH)1qOh8qV4ks1{Agtjjus22P8j5g;0Ki`TZgK@nmpxBQe*D zbbtgR0P*?*7a+e&lz;@XEhEGq5;FuMX^0%GD)$BX^dQSbP{bCiR7Xid8T^?0a1Hu~ z7#Iw~iJDtxSv9PHEey@GMhuo!RtY*I3RrZVk(GUv>fmZ80(*Evo`+WYyPpb$3?fyo zZi6#E4#5Nx1fm790m7|GkNJ15EeUi6qFM>0xNuVPND}-gVsnSy9R)@GBKg0nOV^bKs>>|BDG5b_?fz_H>?GMZJIjffN6chPI)45l!JR(#!N8Dz7gSr_=N|OqRp3CFO9Pz zS}wKXfYO6$H4z|H0(@mnT&QMAz#jtUhXuj;ehQ)|5D8uR2O5BW{@V+Ir$>9xM}p(& z1K09ia*YsF`=6GcpW2?+hZ9=1wj<}CgMsP=7+}*W8Bd*o>LoLSRsMx_Gl-ThEeC<0 zpk4@@x=&j$|LP4(JM>1h3E!jm`6E$~+hQ-G!EHsb`w0+=&CkF#kj7Au=z_35A$HtA z8Urx_6Cqyle;J^8aMx^KOLS3{`rbk}p{@>70aV{i1*z7uU=Q&Hr4wVW)Eeyb9QEP6 zsYgiGW~gvP$?pF^Re=`n{A6K<+2%vwCubJMao157_(OJu<>>uu$1&Et6pUrLb<0rJ z>%^?RkUzLzua*wVyuy;DZy)57TT2T;aWz_Rc;YXsXMjt#lb{`W_0^rnti&|1!tK38 zvC9fqYoTPzk__)`h>$Jb7KZS~J-fSY^Z}6GG3IT|N#3|Yw&U)XgVR+bYkVkR4kymR zXJ8DuM5{Z;X`=(+;SddY3=cRvZ{)lwkMShSZNVyvifuc2LExu9s3Z`4R&MM`)Z7^7 zJg>wne)D~llU^p-VfL3IZj5=6$3weByH5ApR%@zLOe3tVM9Irq{PJ;;h0Y|TO3!G+ zdg;o3OrUh#JW`)nTj5m)9KuYS316-JLNuw{EekIrkRnY--=f5H}Ronh?b4&`rr7 z(nmT0f2uSzE72}llRBhFzTo z)RoW9j)W9HCYc{))gGlUy*}U94KImBif?nm*HHWTk1$Dpo-J>k<%q}o=HRy?P!>O@ zEffrI;1pdNUtU>i*ou;>i7j2!_qPXU|}1xQO~~`LATN?S~g<5V9o;FwHsPIY?cP zBugZ1BnoPE5x`<=xo@2YR0uw_(pe?PM&=9JlpLE81UMP0aBhbe-}G>?>S@@@@3r~a(&%>5ASR3j!yk?Xvv#@XA78Q6pQ1?* zVOIO3%*wO5NJ_F9U%C8{OXG4oJRFgnx7P~Pjk5Rb9IQV(KO=!_B0#9Qh%iocOflNb z?A9ElT0pV6B6(Y)KZS-p=3M`~Du=$c-XU#_kw0UykIQExFUeC6T?Xcxe4PBlVBWbt zgz5yGlyX0&4XRhHbps~To~WVH3vL5uJQR&Mf{WKDY_0)*?Ks&f4_!VXqFfYFTH8gE z7XNe zs7|@SIx%HZW>W%$g?=s?qDk=mJz{Cu!tgz>z#tKna77h2zT%Eoyx@w?x}u%OGW@<@ zlF!y8vuzV%6z{AbxYe6+S3zIS{bNG;Tw>h#**=TXrYik@tTfJu?cOYYYIPTMjS(f? zra_p_4z$3U&HSl7OTn&o11aI7N&s=cZ8|rh*J>`p57y4W{Q(Z0XmXnUXAWk^F8?IyHWfh{~ToN#jVtH z-iu94jH}xUjSeMngHiRPf)34ib*$t~o7wW{_h{NHJG(G$f?CZ*LSNLUO=^QgraDBW zqg^PjF|xP3_NbSb^S#TWyW-Kx0|vdo_Xb+r8A~r-d9z*;O0L0YT=JpwO;{ZX=4a~r zF-j>u7b(7Gxbmn*G!wRXm~<#Sa@Gx`Yu?Y;&DWuq##FQ*`;2lob{Cca{?)Q3;$s@n zg4pD4jWBXa94w>mO}gjK=dgplvwJ9?P{2J`;2!Cx43<9za8F^N-W|9{@|l(Lc?-C^ zMfv)h^4Xj6NkILen=SGUj^$4PjD%c|9nS(JA}!hC;bdd zdG|jWbRhqv#PVka3cCNZS@ZY&M!~tTM5NN=@k?g1gHK)D!J>U5B)ZT z{mZC)7LX|tz!duRg8mto^59XH(nnj$?@|7A%Fpnr5wdqV7$E=ZNBw!LX!Q+F{r67+ zTtLKKxXO_ix%ii)%Ih~}=od5A7Z*^mkA*)=K%~L z{`A870s{uP0s}}t#j(Cnfs9>uf@oh+Z~>`zeI0jeKfbHs0&?#-bzk>>1T2O2iN9_! zeBOri9S-hMd=fEy=7;p14t|Tj_UXL_{P?bi3&_7i(tQmN2{;-=Lh)Y@B_i?t`Fkqn zcrpnt9Y#a9L#E?okU!*-VOzFiF7yLN(FF)PxTNO^1sr67QrLd|7}hy05B(<(+ruA#;vEltDOM zPZA)j9@mWE109w?_8m7vcg9^O!zaYxY3JQfAQj*)`G+94*XIO9-RD~fqazRw#^7jB zk;JzVj*-GQ5{{9~*XrRlVJdknyqnI;cl^JJuXU5(6vo7%xPBs^{?Ss>ShQ{?AK_8z z#G!DV3?Y)UTa!%0%cZ2%L zM&)5ndg5Cbwr{R{eoXo7Qsn_p`C+Z}&PHxQQ6SGO5SFdqdexC0-|A_X(#7m+ZKcu&*uy^N=2o zvqaj?{DwKob!eyHjyY#KZ6ZfxYEAkLS2;39Zf5Ya$>q+7wghLXbewp}Hi~f)3nofB zCg5jOY#hF69$+iO{{`ex9rGvnv?S!G`)|gHE%D!Y8MAD#jQrz^5dsY?%3S@6aHXJW ziECpQDdKO5f9aa@=}Y>@85xw|{xlo0*YeI3ojvYU%V#?CgpF?k^2 zNX)1nZe0WNqxQ^8hh>OTj`o7u zIQn6v0=2FhvIMp63TwVSJ~bPTd}g$(6@wA6r~CQKfqeFXl#tL|(v`66w>+gd3g#2D zUMy>FQ6i|i)ZomO3C=h*u3>YM;3!Yl*6kOI!k6?O7Ka5TCW`2 zvDHEf_^{0j;z_xo0?|@RHnsBAhP-hiI=Xo=`=&i=wAkGhDtef*T#w>}?C%Pp<((@r z;w&Lj4z0Gf^y4kljTDo0(+z9XzD{&*BhGBoC0M>K%2O`J!riMDuKsSEMXsjo)J3|@ z{DY``OCXliHBIO#MC*0C4{b224*CR<;Ug&|I9(MTJoLuAzBzixXX%FCo__w6Sj zUA2vWyiKKU`dy3C*^Ks4uZmx$&kOQR*vy9?1)&KIElbhj0)<1yqMCwwD_uND_vXig z9x8Z{ZP_A~8fF4<`v>&;gnP+w+;lq`TuSKZi+Ea~?8L4jq6Pi5!-Te~1NJ{^03(b= z?!oA3cr)f{J8>H^eoWtJbCtBE04Wl{EG52yRIgWr)Q_GDr_X*$N+oyjjycrDiiWzh z(g;SLf=X==lTKDY(|E#V_!j-skY+ix*@|x1tIr~-Q0_<*JEJ%H*pV(+6o&%o z$L{M>z_J!HSU<13#U*u5~hBB1Cls}8v-FXi8%oH-FnDZqn z5~AgNvkd#&<5_SK1$48k;Nfm22gIN7`o0)MQR5Fq9XIIVzp*Z19;|YF!Lowz*Q>C* zgM`>p!kln%RepwXjyf0VP%=Ft^Zo`<@bm8oGM^$pCWH%UPt8o1O&R16V0_6@$ z)ioNemer;zs?$_7asjBxo{;9N7~ofI)iU+M+VyhFhh|6TiYvzss9g@+{00MjY!TQ# z*|4=@@%hLr=sG(mJXXHr2{e)N`_p)=?PZf2C9sPeik#TG*m$ zi_mj1u~gXEYkN!cLq&Iemkfv=jcpNHd8ZoF2;3KD`wnVh%>w z)x&r*(J*xIZ;tWbH?UO;ISMiBMShv~jnMctk*jg_lX`^(r5vaSdB*3X#tX z>h;Q4l9XBt6%Om2#W1J{4vwW$eePdWH%lD?F_Y_vC5lsl|Wi7WuTGig9Mx}GS zp-bfllbi96FPB)m5nJ5mH>_RhrNJxliq5WIen)E>!fqPUY8pZ-%XcLHBiW!iKX+m5 z^e-A4^FH)OCVp-FV$w;o>H(YM05jf?Y8{L+s21XtmZlq%@~O1#KAG{J1g|hDPl71| zN=($e?Fx9;6shEz780=p(hX{&#*yXT+wrxCm*d5XeqVHK-rc=z$I11^!#Rhq#yjZV z%a~jbX=s1gKF5SyahVF*dvTFo&^F4WVqid6YYD91^o>cTWjr9h{;W#G&Y&ObAC&}U zGf$&Csk=-mG?JuDiEk^^=O-60tUXpeNuCBRd zxvX-DY{KL+ro_^R8a%7+7GjsORPGhk56{;8rI$wdXyxj-=IGjr@S^N%(nmQY@rqw0 zu%Y6G_|eMsh;Np0ykxU!IxzY)fc|CqS{LYS6K{E>P}Ul{A(hzi)5nAzcQyJ1O+w2b zjb-xAm!o8zgfcWmd&{!({x?qAJ&e-#WFA!BeThnn@h1MBd)4a}RULV?)+)G@?DdM(p1HLJotspUg5=cQo^$<;vNkHn z1w7f6v(KvK*X1{k6$)5Mxk9RV5mliQ7B;DN4oBCpKCGBSJ6s+o{_gsOFyuy13K2+YEVv3NoubbRx>2pEG1PPuWu3>{NjdWws49^s z)C&!$Go0NE2{@b7eRz``-RAm#y9ut;qj1%qprTH+7cPJD>_+)#7cJGEX;AZQMd?c| zB-fn171}~J{)#gWyNuRupRsU+n!=W5iY;@FLp_b_hGU6)p`;Z(dp zknMR>*3C1U!ka8aeimo7Yy4@|mTH=jS*An;pSO!T!^A2__IinBb?~gr#+u=SBJM{m z;zD@{nS5_gDe<0G0o_PB?26ma`(8mLtiqNX7S}G26;?ov+u$Zbr_iM7X?v`+=t?Wy z-YO^Esh+fUgDCT$EiF);2VWf|Cx`Y-o)9-&V3jfOzMsNRzd*Ny_T-m*!TfA^UOT+~ z1huw;D!GJ}lMBUxGoW!I0+TsFL{z4pM01lFUj`X```qTJu4RZXhFoGTrS<&0FDZ|x z9K+?zF61*DTmB~Y=UqChVDcii#ZJLONjsS~*25nc=2j+~esSA%n+>e&rYxJ`zp3M* ze0;Wjx0K5nzRNiR!Uz&HMw6rf>;pzdAMiT;n`H?;)7sBz;-^Y*Yt?X(CG>|<#EC88 z76z%_-8wtTB4oTC3QLdr)s$-WcyC}4>38?MCF`29e4^2^Pdj94Vf4kR#8{{V^~t)o zi>{qzinSyg??EFOfZ%=;WoUv`%-o@ulR*((IcWx#Ic{v1zEjMEu3ABX7x4pAUR=5k zhQ(Y~-gGbx@w*<6;TGlzjdQmq_E{qBj8bRH-CRYc%=&a7O9jqbH?tCwofqjOH|f;v zxHQehb8uVb!Bs=wnY$@rHvwIckFqiB6YS>MmLshzN#p!M0Vvw_MWVDT{>|F!Qx-w) z6#rNke*|`o3^)Ja$B5^_had4f8aiX#q%n?2!zfKRMZJ6Hfv~Y4Y$83J(?JXt)*{PV z3Qoxasma~v{JMQkt;Fu1!MFJpj=yHRAX^Ib5ClXi6~t3nbsnKsItcj37b;4 z#YHjQ=Y%@+zkx(0VrnYSHx!CfvL@qWe^kU*@L!na_13GCWbc&g=Z11qPaF@nkGJR? zGcviFlAFRSM*QmzqA7s1*9Th_|I&*u1M0`M#r&-CQ>~uC8L6pQN>ipAIK=#z<@}bB zKJeH0^4|wUtr&l0chOkgF39&0ZpFsvE&@yk&6H-A88Yt(rX8%RMkC zI~(EnZKX3t`~j=*2BpPVs@s&WXPrB4ca4@E8k%@V{86PA%2@>8G!^k;6KTW&*L&;Q zX!1eQn>9^oQDSxpNUXz##osT;rLIh?s`DTq(>bj!MrxXx(CRKir83og^R6cQMZA?FO)P$bqFzf#dVOP9+zB4KOZ0p{3#5%Akj}bmzx?Ljzkq%Pl5A{Z zuZs{(#UWp(fx08iHun-*+~u|dZPSxDRhnGfmvOoKdu74=<}UtXFf|Z-a$S*6z6rE# zqQ7XV8AW>`X!`yI*%H22AxjejK&!h3>P0U_{1Ep~I1BNb_J}x6FhhqphA|1N=xkyl zgYBD`OUz6z(Uexkr3O;InHPpY55t@i8VBr%U`VEW{9&z^*qUjk{u(AcDc|4lo4zb8 zIhLAKxkUI|^sYxY&KzsfMITg&DXfAjoJ|3lF1@71l|B9|S%k1Y?(vX_SE60twzX=&5IQq6nVmvxRJ8FV!dP*o~bBS zqUb@XBj?9#IYXDt8R(KMrqn_O=d42U%AoAb9MY``+OH}n!4H(mXvvATfE0_6L3tfa z?84AQWS|de8Aty+S(!!Pb?gyB10ER=)IHhTB@eh`?A^|ImfCY)82N4D9+y?#{6x|O zU(fIjA062`ZZNrCks~^t5|a~o==(<0^rTDlJWT&Emn+FnBe?$#I%BK7zWi>rf1<=S zZck}9akh!)WbU>kQo^x9zg?EKiP?Hi>I}}`Qux(aiE&+wvsj$ejHmu#1v?2>Et$^2 z@7440%QM@1;iaNnV+h8)2&2p#`(vJS_>SshLy`M5ALZ10m*j?8MsQd{mMsCns)!M8 zj>taskB+=jl_kkEiOe+0xf+DMQg(;h-y9Lg?$nc8HrPLZWLMDP`>B~)dln;Mz1oFI z`R3=$e@W!w&@oIX7j2lk|Gb>_Ps5pHX-;MJZoa=N;gq_wBDIeRd;;mRgNB=B`-l2! zkC+h}K#$ESs%V)=NpY_%`bzh)%Gx`If8eabq*ao=$UfvBE~{h6D2h%Fa6OWC^W**$ z@|1Lv_9lr(&&)Bjhx>HISm2#-(6B@TLOW{+ZV9z++j+kr|6#+2Qn}#&yUd8QAO|U% zhEvccE&}C1=ngwl=<0mM3cXQ3EjOF(j@lz&y~Ud52@TwZ7FS4E1-#C)_vi!ZG$b zxbq6^;=^K{8UxxZNjRq+O+GZ%0GsJNu~j}5&cV|V!e6m<2ZxTOUkYDUG4Tc95ZBdW z%;+ctO-7ZB_Y>3(rg+CAe7BKtjuupt3Xfuqs8eIkdy}qyy@1o1lr_G(6Fu_+9XZZCR`lD}o#k%!26Dmj zq*Rz8k**pP!|%~?+E`X|^!t#$U{~`UuCW2ZNMtIf9}v$RS7>tl%wc>^&pi53=JN&CCNh2yeMt_bBqior;LQ#2fM(vQN_r2rPvOWD}T>;#=V;99sRss>tbm1{2 zs>K7D>pe#qJlEz$tU?=F#$j^3Dv@yqC~o?$Z1M8~6u3>squrKy7>_g2aX@A}eYSRx z7*<&Mo}aitm(cS|4MLbVYL=ef4s!WO%%2M)6g*4tNl2k?Kgm+mQJLV1LdkK?mMIl* zh*?;x<71)VTCF(CWvKv}|GMq}p8AD>#+a*Ew2v1>hZjXA#czddBXkX@l&j)YVq@Wp z82{>arcwqTZXW5#hlzn<_$ospBkvcQjY78O0!dPm^b zRL*u5F>}oob|%s?mQ-CX()h1TA;_m&xM)#BW$)A)^y2$j9jbW3FWhf?@#3YzZ-;V>EkM4M! zb+IQ$q5P9e5t|~>-Hu?Hodu<1bV(#`oFWQ^VYRVEHwxidjp-xU*ruHp8YyFcxEQ+z zNrMmMpyqDt5IvP7q(AF^pk0lAmz?i%ihPQMVj4Tf%->>V~WnWxELcW z-&#P5wouJTXJsbG**cmJX__{s9FIr~)0C#jL+}gzoNFJVU(n$iCJlbVnzqfqqe(N5{zrn9_8&};G ze2vYxMQi*G=xTkp*xKCu?rZ(dx3io1zFzeoJ#=5N;JyT2-Eyr;Uu6q8&CHb9E!w?s zNax92BciJ5FLF%meq0$94s%$x^xSXQIC$larHz?**GQ86xu=g0=lk;uu>R1H{hAiC z=vZw@w`jGOWON(MWyCRCH5g&$)Pj}`XB$-04VI!n%f>Ixg*AT<(U`vy=8Ha2ajHWg z8VS`=9*=4+n2iZjx+F08ld@cJ2|mGR*+$A7r1R(w;#Fz(cAP{83lXJLK$P>@`$yi> zJtFc4qpaZcV&U5T?odY=+&2Z87SO)jG(z4aQ_>7hQrI@xCB7^niJB!NQN)NFZiy zl7X}VVJYWygRA@)aL#vzD=A7FyGR%Z?-KCXHcqv-x19TtEpYl(w$|Bqj`fF{GsM+D zf061&x9XX`dt-#XE;=m|Ubh21ofVQ~Qs*dAHDy9kclPR#Mknu=7LNIJF@r_k=weuB z4$RO;U@M{r?PGy&R|7_{)>_PpFvmt)3GbEexmFVwQt0Qbd1MqPKH<)Qe;~Jw;8ZIE ztpiFje%{;BLVbw88%lyYD4#OCrkyzShC`8d4n4p5i^+JN^YqnNq8$O_mEi?Fi5h`n ze3)t$e_o)U04RWpzH!|sT^Hf03*Y1nI|no$%(J6_DIno*+Mx`J;`u2CHTczpNa@c2 zq_KqRUt0bFjge4LNq3)zr(JKNQ{|neivAay^Muh$x1N`TN{obt@s8AltrcD~&ms`D zdC>(*sW2)NCYj;uwinNhCS!!gO(lNgG4~5kk=ks(#BzPhO~B? zsdIJ3`jmIO)j;M7OsqDj}JgLt82Biw0wWh3m9&axRd4?>`?5{;{u}T2#`j-YbCS$Hp~Q-eAE1w{zROr%%NCV z8Vi2(G?cJt91cBY3-?(F_|Z;y>3$!c`ML2n@8h}tB0d;&#axw% zJDv~9ixF#lkujb6d0%?vjSy!2H`PV!(5neT+q5J)cK>=e)$?Nz?(_)ieZ+&6QTk3) zSF4xM4gj=`>BM^t#v1A}6Qs{N&8J)!^F13$DrC8HdT zk!5W?p}izpnIr7CJXCMHy++$(;$0!^lR5WvbbZKQe0R!|=l?~jhPsqF#B}VNH_6H` z4|j)sQ6#`orZFVT6u{CNd^G~tT9>u4k$K{pDr`wlUX;7mu2Nt6b7u{QQPcXbli@_p zlT0S4W)2of^j6)$baT;uOn3PMf#;G2Nf$45Z{hf-3CZIRJjtc6ipns$cvyKJi27sw zK6otZ$aztMzc%H?22?pvJx_1np}RZO6JtYeMw)w=YC{r~#y(e#uQDC~EY;ukQ}2Xd zXsGWP+10HhCATrZ2lYR`|wCCBI%!I8uG_(dNo)@cw+Z&FPPh?hi)glHZei3QdS* z&ieUdDY?U<=t9`oKI-?)WvUWV;Ir<>3m)pvE#-DTd9E|amBJxSxswpSeXW!ZyOudo z#LYmIr0v|UKYHGK4+ea5US;l~zM1+65Eh9KGsz_j|0ASl`mQrs8j&roQLm;1sM-0R z(G2eXI9=-Rs7fmefIGe1^1O<@(~Xyt6;vC?4vHk~R=M8uh9j{UP>F91^xZphbBZ zp|%C}sIJF_`@>~Klf5VBrWeL?i`QO>-(?d)|!JM;pZhscZU- z)OR^K9CO&FhsCs5VW`le=I#_)DbY#spveHYeqKHciPtJ&Wi19vCZVei-o{^GMw(V0 z6Pw2>Az2dk*|@WIO}9)^r(xf%6?y*8*v{@=&w8}nX+m8&M4H1aBO7EDWM@C zhK#b@YG6r-;D+@@5jW;4$>drv!}HnQ?2+y&@}$>G!DdZSh+koK?8?Ag-=Zrb_+RNz z`h7HXrAICMmEjd4ix}z%97m^r#II-3t2vcV=Gzd z6zR0d>WJ=G&&v*mfF82~k3&1u7%hVhLJ6P@Ko%0;Z^i3OGRlqP+u^i$Ps+8`h3Dc! zJkheAV76x3i@Ywya~twKR*GS)|4VfjF}~nSY_33Xp2Ghjv_c-vIr$|*?meWW#*)eo zA>5u(Lie-~bZ{o0XDg$Vj*F^w2|wR}_u~{-|9+T%sj+Y%ff)7$J}h%AatZu>xd?IK z&1oqhsuFqkykcbZm1TM@CjLP=E89P+Qp{&A7OZL@L@5y zLOTrc!o;6wH##g%iq<;fluF`>b#tNco$uIRWB9=}dlsD_IDAEQ?&|KoBhH_nEL_J6 zl1HoX!h9`naJTYvrA;M^k=MPNp!AQ^=1R11sD+O@du#qpZvW3&& z5SB0bf?~n|V}S0U^K;1Em5kI1?mO)n&gT_4*Q=Ohhu;N^dxgs5z3(ga45gf}xW5`t zVBsj@V9r+TS=9XIw-4}keZ-BVAU#_YR8mV-l%Df$oq5js_Z*XKqR3Hj)UjRKRkjHY zF%482I8zQk^~2wfb+yADDniazmP4;Ek~N7Rv!=l`&8iiBmlNOvC~i{@7_~6JH7Tm(mBYjO2znd9QPWxlBBRABS=jI);{xeQ_V|JW?$>2P^QtT6Xg(EB8kAU3z zdQUM3dCqg7ZdGCou{yFuH4{+9#oe}+HNBpdFU~$C-!YZ$o;fE}F_qqxUNA)_X(ws* zzTj;uRf;4_XO+*br|8Ywh8Qu5H8L?U#>`>l8%Pxvg~58bhZ|3oQ_D%}mOwT8a79>g zW`NUtc2RxE?orTDBYWRc3dykqrC+f;`o`Fs+2nhEWlFz^Ai&6kH9DJ7=}gC*nsi{k zlF5ZZymnReJ(!(bG0>17SUUhq-Z;?y-Ob2bGO`v zWCVh$(Du}pKgbP*m{VQBvbP4|2LDs|;pdcsRFp%$ASYQvdQ-~Zx>zpmB8{otrQKXE zgSgtYn!uwi^VTXa05^Uz6+g+*HFUjX0=-w{sWW-+Ep`-cjwgxuMF+;0b{LX*Im{Kw>^e6JOFYeC}~`$}uUU|_NtZqDtrCEj8?ZnKKP zC7D}_)NaCrs2?#>=}EP!gW~X^9n{q^%h8G(fAbR>stwmHb9E^z)!sbwLdHyiFg7M{`<$_R0w|HqFs_LO9kEJPoQ_*5p=t-ShPw` zY&ERg3D0HI5iJran9~F24p6N2_rmoezD$j=k66VR90hh&us<~XbzJ8J@UFEy9qk0! z;XHilR+}wG?|)5nk6Y*gwr0HHEX${u)gn$Ky|}H1JQJBV5gauwLu)~^%#_TQ;K@vA z_@}|>@}$V;U2U3V8-RxW=z^#2-3(Jz#RqE!#8cbMOkLwg{q&X%lOIP@;Ye*+FLV8a z(MDtbGqZS<{-2}F`))EbK#P?D?+zr|@Pg$^kS}1XYWF153D*>|P^Jrx-fT z<-*I8c_JQ#Nzr*j>louJwAy%+Y$zXNG+3gM^@xc181wi2e%`{`x|Y)&(5DFsi3B4ta!oWLF&?MIFG>+UX1)6u1*6{78%(DO9pamq&e}H{{+zd` z+Lk$2Q>S{nk!TR+DeVfcxq%g}_0#tkG1ZK~emFe1Fm-YIjXmPtWgRFu3m@D&S$7SUqUJhRgLpJG(?-Y^oxR~pb-;v7kMBP!E z4!>r&vFcflj#}@c|H7tVWwX>YH{D(rgbF{H5St8|S<%9VfK&CtByu}do0!4Pon~L8 zTJYUeKyawmX}FV{^bWbYy3Zt_na_Jk0+*t+@0M-g=V~Z{xP<)8nSnH`19a8rPZ9d zsgb9B6R~+-0v4OtUGd}ShVVQijF04n91_?DuA)=aTWeX37Y7!6%$1-RwtV>)Y`~(; zM-y2ZH)%)7w1^kCF*9L)K~J8R1x%o>w>InL!cYE0{mZJn4409o;WWJAZXnbGnSW-n zAgw}z(>-!}Z7OA*vEbRaRKf+0LFOfaFIszSAd6$N9MouvzN)nG@- zJA179CJgm_Z*t@EqU}7NNm=4K>%F0^eT*gpQuCm@7tp*UGeQtL_*;YlK8gTxr5z>m zTQ!K9BQKkmBd5iGaj8mQihl7(eDZygC_`eSX(u73q17-3>0mf1}*+GBe>A1GDp zdos0}S?lub!Gb}G}W;OA(c3Nvz`m&_zgC0))vrx=-xR0;%2-t;$|atjmH z`wk^4BDC@`Rx2A=l` zGHgZ&Hq8jt4NYjs*hnEo-8f9cPuc{7VQE7WZzbP~dIH_#Rk2|t{Ww5X?#KP!4}kR8 z+rFRW`H_}J$_0}Is zPJgK256(-B-7`>b%k=CO_!B-7Dex&qM(yr%va;^aL4ZH~bU!;#&IQmAkgY2ukoisn zM*WEm@ETZbHs`QN%6p_w+6D57?dpCohPa4Xl-#4Lryap&v*#X+2I8xeHZ{+cibIV5bR%2I7o3 zmlA4^Hdr{uR=X*?Ln&M4f>ziv35ghv30Y5ccOfSJb*+}R25(ukzr5cTwl)(m!)ogqu3V};fX zM35`E5{c^7jB2@7YesJ^Tfgi4AYk7^)RLi6iB2Bo$S`=Za3q}rbC2K{Yr`ucGB?uu zOf}ukBz^~?{|VSbFZgnTSdD>==yx00qb6E5xd`!kuK;_V9B?t741KY{A;^YEv(}n9 zrk4FV5kP45DLwyVLQ0SVUjt9AF)};Yt=hhaBf_~zc|@H>$n{rjeidA3&X6tI(aeIU zAl_*>@(@FSks9$A*k9}k+L8bdG4_9N?8tQlR`bV`;prmQr*F-u;|6uzw4{ZHXO*j1 zK9WLQMVC!30VX*R-gg8o^4D*B)fPlEE_( z*Sx!ToNxh3q;vJcKc(5&%T>KI`?15ACZ_^rzKnb=NOsUEm?N)e$b2P&Ya*_h$$S-p z12A{a$b7|u1JHLe$$aI49U`x<$b6-OrKE|1+AYSB+!-Gt3J_#fJ!vH2 zxr%h=kcKjdxqi-BY5a{qP9&LS36kIHqkD$qHG$ThB#$RN7bl`!>5DjLj_zC#hYjJ^ z5}HYG3p$|T9?_<1ZsQ+?P>y~8P57=Z2?iwV;-1J)&h&*p0G&j!sh_y|tC4k2f9|pY z1UCm*S1UJ&pNBX27Yd_r=xK z`7Cf1RuyDvmxf|=7+#Vo{G{s=pOPKykOk0;Fjx#;;^M(IhX4pSYW$!dTNCxyhzr>a zh=2U#LSbekAM;$vTDN(yRtXq)^b|~V7 zHzd(TUVCOXe2X|Olk&@uiOcWJesXQ_`LTDMTM?wI-Ro3Re@5?1kdDN?78lu1Xb?W^ zgDXQJB0UV1JLna`<00@y-((tDw=4r`{Eaa)V%LY|B)J4kzX_JLIK@y*;#$6F0$Qyv z=GzD-iOjDmP$u%4^oj5lk>4FU1mV0wEg=)pN|5BVq&FgRyW%#NviqBqIzi9OPE8j- zT7g_kjx_>}=vS07lv)f(*F(EM^xb@q-GEIMn^&ICe^+?lJO6~^ixAgmA>eNH?k%h0 z3L{q+W(jmE6l!Zy^X%Ywa+a$n#P*3zLmg{W@N1_QX1OpPs7-Cm@Li3Fs*BYHFn`+% zfks`qJAWv8bl_fwhEzUzNo5C9acAX0olv<=EoAPXi(c4o^av-cd-gsD)1aSDkrN|D zc9C}cIkAD=M4U<-aeKtqBaD9!$c;CB{yGNnbSYIk{!FlcN`XLmmYbI`uJI%0s)>dF zbZ%XF6}r#u?|iD#a4ho|ovwr9Ch#c47W)*=Y;T4A8?=T%;IIr$Y5qXat;Zq$R{rxh z@l*!iC4Tpm;7&(e$wCHRcWgW=9F5CPsf~2@`pDr8iUHp=Qn9WXd$|w$zYTozP;2iN zEihGIIgX%!j|0*c)6x#gGZX6Tn#W^Lx5pXA`L&MicO%}Z3Kl>D|_PoFN$&eXd19M$Vnv&ZSVJ4=9#l&6{! z!NNv4le{kqT^VGN^1^VRNoxk{XQ|IYd&2YnK@I}`laTE(eAZlB3u!_Ed%OEfs|foY zDKAs<+ZLM2*veMdy9S1U-;@0qL^Om&C8v`PeaGzqE;7!rOo7({&9fNkw+)Ce40_*mvZe+`PA*$u z+NdXM#-sQA5g%nVwMR&B!>^9pxHm_Mu^%uBKWz9*Fq?XAIy9BJ8rtM498cfKsNS z5`ErzcZlsA>Jg6Z;k(89+T(uqA$MRLFAM=7*mR`FU#(uK@vzQlb!A2);ZJxp7-0nCk4ayJs*ur%J5xOsG#k}PyMJ)@k5EGITyxjF^2@>) z06}3ESri)vEZ(_0+OxMKnyy-cU4dN@(TH>p{q%A(-RvC&aQ!0cN}@Jk+hNWK9N2rk z*|igpXP~s?jrT!EF7k*1gx$?`E7I>@(UA#9zEdr;bp!oefnghR2?|y(F(TAuFpDZV zh%jBqHpdJ%-(j);MJ&AM9p`c<5k=+1bdp63#X8s(5xZ(I*tn)yxp`R?g+UNkJ`GWc zRl4TukVPKTWc9aG*Z1JWd~NWKFLH&!D%qtt8){NMLk;Wh;%^bX|8I_Rmb{`!NHqbU@emX@g&<2m}wr>@`6cR?i3Q3eO55X8$a1nnO){AD;wa1qWpZQf(Pq3{>9cE_F5FIkh$`u|;WR2h%f0cwPy{3A~zfT37 zFzyo{7mT{s5;k#HNf zg`)>cM1`LKCv3~(+2cgtLU))}g- zXWjzvMk4)i;$$3=1*81}IN&e`c`#fQ!!`GOuRgSku?ddkZ^PM9sx-4G z(lk4Hk~9-}YD>HZ*cdi*6aqVWiZq*jR0LdJh!LCZ8<*8U0GOuVm z>$LA|A|m@&)#l#r6JH&rs1`Ooct6ZoQI6=cCh zP(lo*YH&^bhvf#t8sn;3Stv;DF~ID9Au5_#rk%v6V-2ylIbk~PfOh^#Rqil3V6GPf-f$43LLUuF{{@JiNetKO2hDNfdQkGG#o@FwbsPH_B0_rYKU*0XZNuNcVIbmYa41j_oB96Wrddi`qG5Qw z>DZkZueVe+;V%>v*a4fHB}hQwb=Iw$G<0paJFIOyI#%>h)2^QQkAUe$oc;m6U%s+A zcLElE)4VfYDK7T6`{B&O48fN3TWIsL0_Z_xyXoSN`ad_0d2_eH5)xs*0sWkf)+GR# z5_C2EoQ$~AC3f=QH8+u)EDI_TK@o6z4m1wb$@!4cgRm(oX|zewG&z>oymXUkYAqBn zFVeb@K_)US@!FMZ2H0?T0Y(O0JdAaL378c0U-GqqdCA*ofpTLj<0!T21UqbjT<(n- zD+drZFl(@UFx4;-A305f)uvq)FnluA^@!C z&rCZ9DoSd^iDBsBGM;A=ste!E4@LW!nxp}J$wPs2lhMv?1}vhrw}?Cv1xXGTq|K2SNzErnr{lF z2dZz3uV2o#|G-^H+{(e~HzdM%^p0{yKLO|N&u7mtOYCcJk08L+Oo?cGMYC zV-HjT>1(sV0SGA=A~Mgkhi*}XH&^1~BcU1cP%hxLZ}R$oUd8>LGq46-z}k}tJ{($b zYr9$4-ZLjpEr6ZE|JW-5`d}oKD}AwB;u<6M+R7Y0FqideeK3dgF`ER>FFr`_wgX}Y z%{O-Y4MLdYnY1sK=jLeiGFB`n*VAA2)*|%ih~OD_;4Tsc>GE#HinR654Er18(u*02 z?f0QUuJ~;U`(<_rX6LSesrfCGH^#mz7=4PM*tn`K^!Gp8u@KCt@8R1s5CNo~@w%^? zfvK>clGh%A*ln&ewH3K2Z-RZqFrV_*$bkW9?;+a~)7J(NUt9uTuvU0!kR;SCh6fzu z*_uD^gG-JSQ3$|Mc1Dly7@M5Ged(K(o9m104L&Z*C6kkK8q6J@Uim}c z^4qj8dGn-?!8>s-&PY}G+tooz_H61vmx4u+v3hBK% zztsY}f#kXs2by};`}Sk`kWnyw@gp5^6MWOJ>mOi!zQs$+xFrLnqRIRTKBd2F%Z_Li@`zprQ!{$9r|EW!Gai? z@0IBiAl}H`VCNV;-fnw#_;6m=XfpuE0o+(zpKI!-2KUFWAwrBDitaVDdR?$Lc353o zaq#=nV6kVgJF#@oYmrBfPwCcY z+2C(&uCTCyBBKM-LN9IoxaFMG4!%ik(RZ`^XmUB4%H*bAU0De}9|S164>a8+AT}H= z%qT9A`|Ow~y6-hTG-Tr89Zo4si#_gsyeQ(uFD}e3B#FCUgTE*qPG_)NJa)eB+nUbg zCe7HVvHM&SxNe4>k3I3MDl9L}SY-ibe{PK`E_v*~_-%@RL z&FQXj9Q#7Xn0A}`___0|X(RS`OABL-X88GQ1zf6LfWl7wSq=R!_ouXeGmovGt>>3c zA*vkCTZ0%YrCBqHNTgEFDPp7cx$ZDyw=IOCNVkLx3O<>?`i>~pkXGvC?E@w6ol{_s zt!CiDNdXNS#zcmUnrMLVDw0FU=0-?E*555|Cr#Nn2r?6B)Ota#Q$uoi2^ndQuU5c? zttQI#E+a##@}GSS>s)Ck3LZb0f0)n|*zFDfL1Q5npB= zEBwI5cGg?a0-StXW_6TISXzL3pae5ho*dk9M#`sX$fqbIHliNX0rxh_C*w%Xsh{{}^{|&EErI0ZsB0Jy#?Q@2R>>@}HzuI82NdgH(iWo?kbROCb z#Ao{mYGC2es4lE&F!O^?T3S$k5b~?a_d~Lm%+GBry*J@~VXWX|zBm?xEn&0c!}L#A zCG(kBu5onMw7bAPXL5!3PTp}G=gW>MV4A{H)c4{OyXa^wE@p0UGI`G6hhk7-mGF#+Ee8^`TSNkX)5-TM0 z`9;l-P|P0l(yO>u?tWI*yrS|_p;N6T;JLU9vSvIafEp;5`;egpx=T&M^!Ep z#5G)Tbh?57VAA`yNP`4O-6fb#Fn)Y+OFtx$4Be3#t}e$IuFkeLLMSsn+TlB$alPy> zHtXGL{c42)OyLVr#KKU5^5q}tvMw7NCbp7l%@Y0Img!AaJ((}j+_ zJ^v$r_)6~$-P|h!TtU2QulVvlEd6wFKaZ9NV+Uc^wAJ2ROvK$!Pj|02bGPP&*W+{a zd%`Luw}1I6{w>nf1^2)S5)06l00^?4P9`~A6ssBSy+#yUzn-1EB>T;q9f3$W31R1v zT_ltd&JnRSX=j*OH-HM%pc0Vth8i-61R5r|=ai{i?J3v=a-o{raDZxjw&LdOl^g|A zTby2F$ZrNAxn=iGR6kjvE7%G5fd(1{q6P**{DmQ+cxL;32`E?CoG~|;BVu(6S}9yJ zDS3qGQ^oTwsaN%~>8T%B0Qa6<5c6JHP_WA?lj8A(6`}njfkGLEnsjndVtVx;6T>QPUXSnAei?MdMR8pOLe zr`JBi8?zxG5N=KRg1N*Zt8{*0gj^}N7nFXqV@gKB6iBgua)g>SqoSyvDe`k98`&vF z7~pSzihZ1@;0M>I7H9>cgLuY?+WGHeZNYk#Y#0mBMhh5o;$*oQctz-%UJz2kx-4x~Bn)X~ z0@fEVFIb$eqZbIF$t>LTi09ixX=dISTJRkj=R*pgOooJmn{}S`8m8x@sP3Y)vjTzdI|h>sg=Dw=p+E z>ztR)(!bJuu&KUb|E0f6YKVj^d331%uB>{{J4LTG1AFR#I5|OFeobWt)itd5?y6U_ zHHOn`bJ++ZB-Uh3)jYA4zpPF*owWHM+-7fH7fU{=KBh|iqy4MON?UvN)V9uK>`79n zMp0}jR>b2dkD<0+-CiF`fMT`jd8v7^#kwGk4s$B+`aYJf_{~wMr5nH5&u3{LA_*s7 zxvf8DiccdTK&o$}PgCY|-I!>q`opbytPEL#ssr^tKJ>Wlj@QAkJH6M&MMIh&md&^j zrX2*uG=N{jIlYisu8KIaW=PZD%Ft16 zgYWPIYT-e|MyR7>d5nv)>$}W)AfZNgAFE>EVAC$u5jUb|@5+Q~$eYQ0V@6+iO}CJe+2~$ zwYwOC0I;p4!cTsL6xom6BLfF(o2BWE7`Jgq1Nj+kT$?S*I#v5Rm-xHp#jzM~X~0XA zA949dFp2&jH>~ZeO zMpd19&c=q;m_DqxI;tP;aWk1W=kY}3F=yU-cAc9)r4v>CgsD9+fb`tjrc`ev;Uy!d= z5_?}n_W{_+TL`b;j~VkE7z?(LUI``ck!G(L3br6$Hzn?Uer#!o_h8?@MI1i`eddzA zg69cqi3h;kUr5~l71>f24}iFriad_}^XWPF{t5nyE^)vwaz$4l4Es7w_Ntoafj#H( zr@JGp8~Q$n>{WmEDSLjaRO#f3y)+3qTjjH-H0nwF!sRwAK>sw6=L*^E6_Wa+AklwJ z=85kFT(Nyd=869Vl$y|e9+g_~xae=zy$JP3eIilH3Bk3yxDQMmaR^my37$&DYu;>m zmE7NuQXOKfS8&nK5qc+efDKIUzuu&rGG3BS8R9(VlNuqCXA{#zNzVCRF)|(oQExU& zXqih?Kz#6x$y#6g@F7TT9AOmnyW7ogks4Y{t?%{eF))5gKQ)NYMg%~DZ-ck_PEa|sgYoz>H^$P39 zZCxf(I4>=7pHRO(eM|42avH8ZHpkwZI)wuEY}qdbv-_|qgAAyGx&gTFai+CBmOYD1vCMh-rFc@NUFFBddOvalc2&iaJgO5 z{8asT`B1~t`~b9Cbkbe{tW)I+)?y3+?P82Za!&FBSM`el_=*^aj4lWvu$~3X&;?A{ z+lt4qQtM*hAu;G+$aqn-5P8yYO7b`Hbi(R&>T$aUrY5Bo_aY=?q&}oI%9N&$N2HE@ z#r>2)I(r~3*<^rfBDbHA#ApNJ@;Tqc-9vGDv7g(#)3_AIj zT@NdEh4qML;^=QfuyW3{9H< zG7|iC=xK&04N*)cxv}UcNSGc*KJClqa(8~VD-21b<_<(w-Rvxp<}DvJG`!0a5s5G_ ztC;xAe0XHXVl1g39k9IUGQ4wv5mFF}m-ypL<#hi%&>h5m?z+@>-EZBHFVLMPlfw=j z5G$=kW>l6WE;mL{asO%qOPe|=2mk=R(qYj${H(07iBoM5fs+&V?sR2yYZ z2)B9g;3FB&Pw=HAF=J<=viE-erGw&kxlsvbBDne|QQ5nGadobfrY;Ou*blV& z{-nbnyVCz~frFr|Zlzo)Cyl0IW3Fo#-~}0>Z@>TL9|)0HlR8M7^JKt6?`v|l+fnX{ z_=zuoj`3;m*Gc&@5;dB*jj_QfFhGXWHi#v~k7hd}#UXUN!I)!@a?$eaWF}%J82}^; z#(&$2nWx(Uvj|L5>k-1~!0Q=#L&qaZXH!aRMYH=^-Dth={&jgS*m^C{EhtZ5<1r9W z!_%u9kr~ijyo;9I3?qjd5lT@7mjPqTLO04nSr%)j8-mKZiB&mwHhMAHy(qxlO~of| znW9rw?6l+y#%L0?*q}<}MjI*9W9UjuApfuBW%H)<WtJyt0L||;8*g%WJeA4KxtDXO|3f!n z5PmJH1a%JLZi$PLkiUR|@s>>n^Nl!R;18>O69>&wKPcZGuy2fm3DraLTYYjHl1y zty02AT-R-bpDcsc&9Mz(T`3iQy&fK&yK=rmViSp2!-y?-Oq$Yf(cMsdQVGiDF&whF zrW)@M8yCA-WH8j(2cEh~)Ke)8LNPL5Ffxco3@=xPh4TGyeCL+c5!XpEqFXmf&4cWr z1UD#mvIQf{%So7*yL>eJqD#}Zvc%o;&3+#!g@aBJX{HJ>U5vX;(ZHopXh45#usHyIY4yZ0xrP#YiB#3#BD>JMZ>ck@$1M@%Cnrm@+l<&62B>~@{>^#vX{e{dT(S>3#Okcsl zp~UpwPiO=(eZCWSXyy3MP1qav(sB6g9fi;?gjmlxa33S`jFm#h#AO9{&sZ*9#*_MB zJM#-P{bMz<%HOQNqjvEyffq;-mmdTr?CQyr>mt%$zOxe6MH z;q|KZmhfwmgQwZX0`M7(sdKHx>|!lggn_atSxFT|v|Fa${^t{vTx3f;yveXUamBNR zl;j23Pma_%8_HnMW?ugQkm0RbD%6zoxUuU`-x>311gw0@O^#!;BGnQGpB zX=7~LH?(i5+>zs#AN^f`#U(Nv*qcpxRsz+E`8qj>N~O~hDj&+TU^DW`fXC+ked z+L=;;RTCA@LR6G-3Q*zo4{>uq%a6zkgU@Y<3w&};J&X&uohKU5kAfw_#I%miUDkI* zI@isg36$16#U{%+Yez8jYru9CYd~f})Rp=$TN#cL-}Hk)MleX>{VB&Uuze8gWKe+A zExwWYPmUVg@d(Tf5|U5f)7cmdNjUq;qBzF=%ZjaWA9`-uzK6KI_eX|oE55BnY%rgj zgKp{a*3wBsRI_3$yIpoDXMPpCq29uh)k{?iDBkr!97F+LqM@1vS_Gba|C|byxhq(r z^gS6B~h+ z)H%f9R3*%?stzeR4y}r)DaKtkO=K7*4kuKVtWkn{CzZYrCB*HP%*hKd5ovDgb2&_w-vxKI$W?jdEEt1s(ZhRz7?F zZZAS)-hT(}gJs2b(OrG6(A8;TBT|*YXR8_pJiTb1=%EZ4-Q(inp6EEz5H8UD++ji? z?5ZjW6)6=sr1n8ixHg9u>loc>6sX}$rc5IZ2^qO`doBO2l1*am_`>VklMW??IzNOw zlpQ_HH+C`?%C`zoh`700SapYwY`+j-B63QF{6(5}xHJD?SxV^ZgI3a+kYGO!_aqb^ zfW8VsvwWMc=H78=h~}3-_l3#QaaJ;D(y11tBEu&Blg>W#BV!o!uF_1Q5UIMk9M{nr zN-I%OzBR*Rj3lBMfsdrn^@qQ=3ax@z@>biC!8=H4F8WJc;WBnRl{_EyY$|uQo}b+2 z_*+l&d*-^tc(9jIe-Zx!)=1+6{|mV>Ni5=FU6sm|5ZU6nz7+>huhRBoL?Yc=+i|ht zR%jC+NXcOhsaKhFadj7)w0wOPMv`5_{I6(njhyQEc5QoHu%MAonU~ByPWGXOpQHE4 zYsmwy!Lilp7;Zy+@huXs6+w(u9pqrZy0Vw(M&ILu?hH?J(fbT*$@qkZ zOj13zQGeD*5CvT;tMW<633A|FR-C8FF$b&(3i(&B*RXiB>2qys9XhWFy`z@x*#@;e zA5Am9(_Y?ljXAshZM403XA>`3N4OMHSh+iIRiCl2$`OV>`js;M!${}4(3Eyr{kY$5 z54xL=IL_!)c>BVK#AO@OGRU*sT5gs`Y#ugH_i{T8wpM@tkWVQ<@%Rc5FJ6u8hHzxNsGH*?yvXfkUbUaEfO|C`f*psZM zbQ?8XLnRh4ODo?#EL)cr_iRgf)w z`79wQaDqgJTF$2Y7_B7A7T*S43H(UaiVm+!fIc^jY_5*mLRGm&nzE2!K1OHf!p=Pj z8BBTQFq3rkTd8GpZ<^n=AI=@YKlFL#i-fF`kin}(vNXeiLwd!cf`=4Eeb%}v7NWjox{!?b!2!A+4EJRiS&2##}Z7I z`l=(R@2FpO;!Si$FeyH-2TL7ZZRH?f3UX(VFwcVe)4(9sTV`MDogeU}UEX1{P0k+z z?6N4kR9`I9i6M3lJ{cYqEs7)c#4RggHi`~epr{PLK0{mL2$ShASE3ApwW-F4YRAJ7 z*W!ehqgD{7a_gLxz5x@>KQVw2BhTC6G0FeTZlc#2Z*f?VyW%0nkcWqr8m zy*r(w570Gmm0OEL&&9&DU{dEiJ#{WnJXXZCa#)=BLN`8NU9USxG9fEl+#l2{IQ@m0 zy|%c>Yk8A6+$a8-(bA+46GR})V1E(Z<@F|V6O$a47uxAz zIY)fI*0U7Q*HP^%toQnsUf#7{_L$?_6tG{h8D#A{fA+nFOL^!;gnWgH*rYyBNsRi{AP2P_85TftX-AA!e2cX}>T%5Cy+u3O zAdBOiGn0};fbB2#MqMHZyRVF@QSns6K6M&|)ImHxbGr3n9YiODkxGEo!oMignc$&a zVNLwk<0Y=u1e50rhI}Pr6@4nQ&y--7u9HfpgIA&(Kp%A|%8BbbWR2iHIt!HhXF|qJ!~& zFpuRV!(3vHQ!+o%FUI_Xue;&Q{T+4ck>=iRhr`()eEQ29+ZNQL&k5yN zvA6B_2qXThI($w2pwLgy?#1W4sp9#)NUUTcW8$)VQ7?3YI9cSmIyG<86j@}|cv)mZ z>0(W~Dgqg*;(I~XRgAZRM!7#@@XG0bg_OqyCUM*;NfgKx3hEa}Ww4!}L9soV%6C(F zCDfmZAh`dB;)& zorO=(Q^iG+bVG?wC)5jB(?Me(eYBE~B3&aaz!|eiy6kYO1<;p;v>l;6G;HZ}@?m#4lh1vM9{JXj#{mO?Xw^Uj|#g9n@yWeE4-4&A~SW|^bq-2Y4pFvp3(7WNSa z!oWS7G<3m7c;Tj3yBN7&_oBn=A_(G4uB^ z6^1#!r1$s~Gg~A(+iT!(k5KtXucmw} zK%{K9_U-b3w}_UiuBpQETMX#BvPtp%BLI7S+towjA-+raGlztep8BNRE8D;=fw=Bd!glkuk|t=T_JB- zU)CO}T{9C8f=n%{K1>bG8XiL20Q#dePX;~w9UOGGi%qYU&EHu9HrERWzfWE4%C!CE zZ!v5K*=<<`6<_IS2JVmL!-J?yQ~9#{&*sNi=)BS%($te-Y8U9Ug&iq`0u0<+1w?kz zN!Yl#*UIvTJH}wV`v)>IOJRv$0rpNBKQqUa&v4~(0@T6}@IR*Ln`;H2U${dGEISX# z90aB+TzXyhZFi3%yR|+k!LPOk=wC0G70TvPogyVZVJ{u5SH|zEO7ULNl9(sV4!cL;pOk>5B+?n0>L3S-SsC z9$A<_@Axy8VSS_ZZZYR+!~=7jtG~2<=)YE+?vZ8%vWre;DOH(6Cirq9qX8CK>)=xK z2~BM&5J;aJ;h;20iKHH0Kj!@Bz-<-{PPxdAk2qId-RB-=ASoW=@7xT9crGpya=f>fc(lkv4#CxHxVx!%CJFuLPQmtM z;n*NVtP4mae`U}(gG0*6%GX1m+S@tMk76Rzc^!KlZkuY}p;2gz2p-dNMO!<w>c8BLy5vI_eWXxj zgu`-l`Hr!Xj8BL&cww%mOS%_Lmv~{@0#euk{yMkr9BXmmW_81zb{_Mm(B?rQ~L zlJ^KER!0bc|3+}k!nGJccKTKK68a}EFn72JefN8!2DOXWJZ@&cTRKOzl22_Owo^VS z5_3B6=pX(q8{7+VcJpm^O=fCF4Yc$;n5!Eig2hHc8*^%C6Dy5~N8I5a)&+ykG`j4A zGqv)d!f{zxV{ceER1Zpk0M`zVry& z&l|;!`Z~&gHS2RodMqIvqwR``V5h=E(~mN(V%+1*D)dhG1GP74jCAm5J=Ry59BKAH zj&zHJN7=V2`78Zn((^QMS>X~Jo~xt08UR+5YYjj4@at22i+yzJ#h=y8U=vF+cRyg( z8|yef?ijv>*5x&0{IerpLUU?Tw_V|*dp|mxi zc1^uye1B#r%qB~% zkf%Zwbmz*a@GD@eHAJ_Uzx#XbMRWlA;iJ@0-@VdgC@%I1J)@xt_O-sFiG^XUO7ib~Np z6)u#+(ki8Q*A_Nx_fA-<38p;dzw`@X%2{F)^5*@$`zlHx3iwG72;S)7<_zr(d3Sx~8=i?%8J)jdlJgMJ5b;`T% zy7D-#4jTcc1pBI50=WEf)OB3HShl~bb}(-oh7?xGSBQ%r%4(H*Xds^@I8(%k=|mfy zm0?JdbszP!x_!b$^0T{r!a@2mzZE`uo^w4-`&GCCu@KsBEQ*}!3LSTituGflC-|G5sfYxs!{ zR|~&eic*UxU*VBd#zU-QXraYa*ee_+8hU7A5cV0%>C}G}_BALlbb44q%Yn;oE+EQe zueQhmXNjw>KDRv%jQpr9XCHM7t;0jsg1tUAIsg*-oHLBsv#D4&>HaFoOG~KYP@8i` zLRwxJ2Qt;Di&)`kn2HFMt&)q@w8Q(iz|u=ZW~&5Gby_HkR*oYKj_WuCd&z{f>@0jsh_`I` znkY|6^K4{di_W%jQjWJe$2Ul?2J~$ulcY~GYrJl?TI8XK#Ubj;Tn72K5*LqG;}P;@ zPJ@3WW|jKekTF)o)EOS8T$=AdrCFNqLyfxUYHwtm*=xm{b!svqS8w*1ZL9>GYr+JWYdZnb)t>>*wq^m6ZLkC=(?kQ1ZK?z))A|an&)g%?WLZV;*Ru=vb!=vLtLxUCbYQs$GH`j7JOlj4e+^QQ+FA%%w1zRG0q!} zBve>0GsDIAyU>#lJN>(`+qJLW>?;7;<#v`%}bZdz}s`vcq38kP@;& z_&YS~=*=M=~q}#afj4IuKD>z}PkDzCf>0c659~T*|D=wbiX;GhQX))bxUjMSWxPbuxIQ~0Em#BriiLsJ}r-_n@nT?6<{~;!&DC)=|3!v~c?YN$I zj!3RR`TF}e?i5B5@LG8DS5;UPNnp9}TbPHLl;qfy1|j^!)Ah!iGmO3ge<@t#xTfU8 zUyyP+@ttgYoov5+SABQxft!xPjyRBx3@=#fJ?!p>Kr*t4cLHF z!E}T*L_ZUSM(b@D*W;1t48%XTu|<3B*%4DhiIkzihZ17rUeKp%l23RUY@a4TV6~8G zKXzhcpQWk>zT%;47nFFI^-X}=^e!PE>hiLfjo*%n6>dfDg*%LK3Navck`(sFcM=#E zzmhY55gC6zPnL!OmGWqdZU}(}gjF`EGMFKWSpNMBcf**hd%wng<$w{=QWT4R#-4!k z{Y_d^(;9|g1#{`warBthVkYN@B$vSe^5g&E;(qPtLR^!)jw(40XJKMvC$En#mUszF zGHbqkWT{8K7_PtL>`H8{N}eL`zFNm73u`w)%KPGe8JU z4o;>oKp*(*aRJOY+9)=T8%F>pFAc~9QZJp6?-{sl@lP<*PhbP)I?4=MHRyFbdf&Py zWQ)4C+BeusI@}_eA&jEY6hbNRIY)a+U}8Xo9z4yFm8njD`v~efiL@lS#puGU>sIkk z6G$#L`MjNaBc#CB|6m1*qF0#Sg8~3JLIVK&vfKU}R-mY(fsKi{iGi_+&m%;m7WX0Lw zm8W3nc{`LUmKkNpa@mx*TL0jMQSgvu!QwI24BkoIaP&Po%-!yuE&2JXgZa~@D;m%l zL(Ep)k#^VN!`uN>@!JJ~pp7S#v=KliQhEd6C6b8IOWP;t2k#>_@Fwajhp=mtPNjpW zk?+WX^dWpn-f<1!f%qYD6TM~an+5Jc)PQADWbbZ6^ltFo#@C#vdjm1f94v!>Dko+~NWs zq|huvPr#(CQqi5%Sts;8;Jq8Ed)x1*2f47ns+%PBH7?}kTXeb(^t7b94$UVmTTrXD zJg$LXZy0zL?!N(*+1p(jIjs5$$>@62GJ80`cb1<-TM~j^wzJ>m3K5s+!feW{IinEH;CE<_F=|3^1H6?IWYF<)iMd?$ZR)vgZq6 z5tG!jr2BV+g^fGL@1ta|=7YzY&r8kA(%9{Ji#n!-vZNFD0fV!z|L~ceea&YyecKM4 z(|L(>O$vONyfM!>#qyl*ch{srpf~WOon#LUoMfL9!leR_IF(}0Q5@rR5|iCOX*Zo- z&j#vb-c0XQE9Ve8TF*7volG^cA1FKO$HeEfpBz>3WY9?elR&|lD_Y_w<1J@5_gce& zhua4?p!0PF`5KA<{%-z|i#@sFb7*|q4zMHtbKt8zQUK@&NLb;s2mCr8N94zOcMJ1H z`HrP1)xXA(quw;S5ds63x3KJnFRmj~@A)af&xxL>8SpAGBb?}yNV}IGf_tx)w_*er z!eAp1eZEu-EATLyUrTT9HXn1clni@~9ZCc^)i1HoYv?;ins5WN)@B;gY|L+U-^F@J zxZ3Ap*1q~uTJAPj{(?`baOq`~-c503j=qW=Y*XdZ8}Vw}K1Axh2q$tRa-ns(yGXOL z!())bGBP8EP+@kvvq`t|#h=!Ba+(_e!O2T@M?c;}@fh&|f$~pG9z2A10Lfj-xbU5Z znvsFAsOnHP*mcMfQwxa}g_My(s|o6`SEtitqCmi|+lHbgMcqk@G6^{f!N3tuDZL&7 zNr_s(x6oBAhJ0?h8WA-;nw*hjt6{p>OP^i>eXO*z1#Wl#E%tZ}fu(r>juc~I`;^el z?-}Z)PJ_^JI#OJ2SwT0gsVK14OiaYb&6Tm5>q}+miO|h+t3Vx46VQX~dMHg9mf1XR zNC)&~mLNKiO!&IM?u0k&VfkhkZAWP%z%MbTuig89YI;l-Y(5dfQ&+~{t9b$c{8=!! zFL1NBVs)8%A8E-=z1IkvGwyf(bu~bgV%WXx&4`#YAJVuHn2sX9@*~{@_47fvg?VWP zkkVqtJdB-dIk8eNp(Sr%C41NarepZ|1T0=_ebtgC~ z7-by_n}-{~d_l`g0fRsR06;(h3=@epyuQ2_v0{yP^U?qX_cV_>UnU}*h+ zsO2qb7VgR`7-M@`857gn9ZVi(iAfV62pWQaWC$U(@%=MH`03H{2@vcj;-wP@i1&>L zS1U!vTC&=uw5S{eDr`(3%T;p6eBPB`OY*kU=xTPRib`)kb0LKxRyNE2PTcz4a@=yf zPIox@_DkvUxPkys8uUPV$j_-;2STU1L&DmsPkP|CEkCLd8DSiHiy|9w7=ap*6lE+7 z5~<#+R6$l5!0NRytacjY0P7!Nuxac2vu$o|A?fU4VbSLs>Ku*=yLnU>vGrQutMb;9 zXyD$;jUs(7>^`{bHFK%f=NP%^AeiVK)n)d>lCem4Y6#+8`E$H*7GBZ1mek9%3 zx>E~~-@|V}Pwvsj6U&g5WIO;sJyJSEgeBU2!fVD@iuAsDG?jM<9 z(s!L@v6eU}=rCj0ob55ETg#FfF7&>0Z(qccRK?w1R~H`KHg+Lfq;{&SIoGps>=sf* zMfMF9zZ?AuvOA4(J9J-9<8ApIsPnnSdaTKQ%)^dl#`V}s-Nf}c?VZ(hyVBZXJf=9J zpe~^lEUeb~)V;-w6!qL+GgK7myt0uM;iOSV*(Mrta@(d~g?h80%)eUW@9yBqz>$`3 zHu<|o@3n!F*n<@<2Zr#sA&#@;d-aIn~HnDUFu@7)EK zK42SSkG?`3GBG2y{!%N-;d;!<^)-+6^EG26CM`-}k$QM?mK8f$a+Z}Oy@a3r)k&q6 zHdEk?B0+@l?HFb{rv<@y0>^JUiXPIxO7dv$W8TTjx?$_!D>_187qyzq=hi_kW+vAm z0Uvm;clJ6Dnvr&s0}68n_XDqO{f_rQ-#)z;CMhaGEu(zSR@U^WRJ@faX_8g3%$1j9 z1op~~JHWcmWwLE!+2nECw*4`G?RMw_F4|F{_shPi4qM!T9=#Ubf%Rr}X(KA>6UPvG znh30&1-%IgkIOnOI)$`X;lOFN8q~pn#Kg>0#|TcC+S$66_6gHS+qzK>{aeoiw^h|$ zA{Ls@-%(BnzCk)HL09Skd9Em$+Nm_ zl%+_j;zzIGv56)tpF0bxE=~Uk6$&JT&DSi zUxs79uCc6TS~LV#ng0MgK#9Y`>{2iNcZXi>ohX%hNRp07xJg8ixr~}f7`{GP&xBM|UQk=?N(4@ojsv2eSSvSL9?U0XG{e8&@$JJj?Rz*CWX4+w0l43XRLa}7>N;{{VkBD$1E9(|%$78}fL_|kx_JvrKSKkvI? zp3oQ1QOtZA>k{lKW++zW4*ySJ0ODdYM{9&1K&;?G8bUtlA$j%-DtRwsFF>7wBTAsy z0E+|2AOfdBwzdkEB2|;IYOuFr#%CVYGtC~Lw}9U(PKd=*bR9rROX>}@nEptFRN0)0 z+f~1534ApX%Ieq(aFl3Vl?Je^Eu&5LLwngw!ouRou$ktjc5G^^boTb*Nhvh^w_$10 z{tE)P_pwPj4Bn988wMcltRX(pI0Qo@1_#W@`0+t~RD7C5cst}f_czGVXT&&cQoNR| z5S;^K=J3+Onj^rpQq3wyr)F-@ZLdX3H1+9^Tgxs!Hm^s=P_y>%!gFVAP1@-rqqObg z{qUc?503``qNuH-2&2_0Bd0q?U3$Hfhkg3EDJN_#dN7X>Tn&5iKindJxRw5B(?FjP zDA8xJqpBg486>mo;Hvp6MKIkd8WNl!MAZkXM{|Xfv?sZP-H_%|XemT4g`21gQIGiy zH{D4bNGlIRX-iZOr$sc~$qjF?=a?IjIKb7C&5nRL;Oa_LC&uK^VRZpGML;u1b7MV* zpXvv@)1Dw#HTbIsF@?9Ni&qa39uQKKt{Oo)Fw7Ft4r~mAHAhG}z}<6aOIZ(l44bHn zaN}7;$f%2H$3}%y)#tV&p&@5EKsJD4g;&{AG@xchR5gUOgQ+1_#I*lWLtgufc>uaD z+KsS=L^lX`FtRRTJ%-K@sWbe%E_2n7Ye+U4!LZGLH3(?PMjFAxE*^bodH}X9*bUN# zz(Vt>pk3oi96Z7tWT*4p^w%Q>u_S$|B z_S%7N?4>>5*t5I;AsF}gy$~<4z#Ag&!5aha$r~i^;JuP}@}a1Av=JHahyyb3j000% zBmQ>aoa1)noWpkLoYQtJo&9zIo#S={ox|0j8oO_3o#Pj_-r*Z)@2mq_@4&s3chaG) zca#xZ?+iomt^Ib;t>bpot;2TMtfgn_R5H7upyBw`A>o_9n|flFpEtF7F2E z(3siY0aYLNtAokVVWSQq*1ygeE3td&-p_XH(r|0jNNYwBR)PeXmT}XN@lhU8*0f0L zMiEve5mr>fEF40txN%Z3V(bDansIm)70@3odlVH@e`*68Fk|%vc`yT>jx25CSleP!YslBnTGLFVPhsO zW1}eRlW^;kNb8db>q=qP7Q!sfgqh#tJ>#YgIi?p##-Pj>w>{K;=!kRF1O_;?c z%<5*jakbcp@w;?TZ0bapChN1=`ev>l%mCk&lyWHe4F?EJYleNxrqx<)? z&eRz!O;)?#Mv2K|Yw8S_ChM)mMt7abV?gRmm?kTp#fEpa$zw$7OqnKY-uG?e#PSb# zm>=+e2Dulh2B|H-^CVfnfiCBN7vw6q7}z>ncp5lc*!|y;Zcd7h60#clSl*0jlLW*O zWII28c%ixYGPya32ckzqiQ!6OK~e#ohU_W=7LZJ8t382DSk$hkhFN#HOK6s^`Bp_v z>VfoDlx$xhTaPtgU~DGusRTde`7#CCPm@<}o72C(tGmx8JXXMba9OW{Kb$4l5B^|b zJqV$+IE1>M=gC|O@6N)!I4ghC;TTZ!pX9?1p_pSe)MiSuc{-T$X0kjCbR^Q7IHUl_ zro(||QqIn_4gQ9-Tj6KTZ3;n||IJ9KwE)d7g0|S?L(SDDH#STiH$jV1t$r)OnL4ql zif40bxa!VmFibi$Dz%Y4LQA`%=+e)4{BG=~=&@2ZQM7Bn?9=RZ_4rDmk}}<$txb1z zC74V3hC)9_vsJUem{xzO{W<`(>JWd0aGQ}Pm*+CcI_QZyAsic*0oANhmC5QLsUr6| z%go4_{~HYCsgTZLWvs!P6`%Hq(v;C{L5FJIig{D`as${Yv%$Za{vrTyQF3(;7^oL! zixkOLV~j#sXo*=q*L^+z~`<^_J@ zUGh_cDzD+RTy3D!5|!EzYV15rM@lacnET=2=_rBpp;FvP0_&_Mml3YRY*#(L;4*PZN<)*bpR?vq}eRmv$qa_uFRL)za? z$X=Gy4wmNu&Nn|DU@~vP%2ZXMg&Dehj?RiQ)^4+>a?ItGa&8g%yy#Gv>%~kV4Z0mT z1^J@<(okdRt|>Ar0@wgmLM;{?O_w`LD4h!mY^CJPRI&Y%Y=q_%xjJkRG!1~evVX|R z9RKFnsl${x+}kkMV0p@Ic0+pvX6C0TLUSEU*>4v!WzD{fE8eG@a9Ik9QLGD#IwZMhi_?_TF>3zVp(@Vo{w)UZ!z5h(Y)(Xi0_(EGa zoukowU*?%whPon`!j>_S_^@QNXMZ4pag2>{6mVS08gP<*cY)HDXhKs)$SSkiPWj3U-Kz6e|nH9L% z4)iImVVA`m{7oS?yB7tx=xR;86J}HV`3{5gMtTm|E$xXACeGE6`-EVIJ1Vi zum~}mB+4M<2||~Mdwf222Vn8LHVHdSLT(>*(Kegtdf-(;?m(-!><+{t+e!BDvbe`P zl=sF1EKTC`o@f!USseUcWs#tkC+ap4?vVPNLz@_9fccz!Ew(`18~CHI!9kK0&{S#8 zIx|BidIQ3gC_P4>HE?5@2`gx$nLZ2g|J?y$x-@OAnJz=ugg7lqk2PQo@>?MtwHE!~ z=l_2${QpN~-Lxq6$6|2rKb-;h4So>SFYt2nTiE|QXP{`}E4Zua!$BIhLS^sok7_e4eb5c%n~t^fi601okA zi}5KL8QA{cphb*|l+u(0yX&(&Lmn>9hF3=nQ8?P8?__ z21%O@B1m%T#!2a}Gg2w_=8VSiP#M!q)+KPh{BQ7Yik#9XZ35`Tc;?mVg&xkm_Zv6d z#Gkj2i9b z!DuFJqjuVY8ugmxN>a8Qk)1`V?Z2rs0=l}nNeI|1(nA<&`K4)OUUpLQrIl6MgDX$o zE0iW44c5!nxh-wk)dq_B6=_YPB6RmxU(?MuXHKRzLlazo)~JXGyPJk{A~a_zeOWBn zqg1x+qbog@$ZM7Y&UHEK52^dTU8Dp9xtUHb1~q#}qhd?a9=oNX9i`3)_sFnPP;F_d zXCe;EE+`a8F*zP%Z8^+BON9eNby;0y19Pi{G|yrY1n#G_JZ&WOD4Z&@gV)dbCv#>E z&B=0Js$3|~IR;iu`ebXrIXhxn6&PL2h6+iC**Z+BZRj*t5=U&d){ou`Z95bBidr#W zhL@#yJvqN0mPm(kS*W)VyXX(t$)wHD?fzcVlhR{|Lg^p@O*t7f7vvaFMq5lH>_jQW z|2R`bYiP&Y-}`|b9gbvRHHl`-sn$f^1UumB7!Zdn1WSYi?*$8)_f9;k*h-A|0RC&W z#VCyMu++{?qf~aGtQuKsADJOU(~y-6-(6vQw$@;i+W2a{zJoGwo?2{+TG?z3miGJy zQeW?ww#mfkHcHgCL)lhc86x$xIC4gkdR9odSec@#Y~6^{97Vv7hA{o~4Rg-0Z&(4J z`;&bN59x>hhcgKlw(LE)EKe4g&IbQV6=D zy6oF8Dx|1G7Y!Y{tWOntbBy9vgR+gYHBs@*W0C~D7>YBR;^RDqTEZ_qB#4`YEbXcz zw-7H;PgZOGM{Rje(Cw(b(_SnB9PQy1c(JI+4zJ6iTc{Q5fvH)15s??%=qz;`&&Q|& z8nD4F;r1iKTA)PPzvNK(ELdxk7PIBw(Cq?(44;TNuAG8mM{>Ea?75r{hU@W|2oQW~ zY?>rN!6fj)nWvx%*x9*HhVr*ZVI}G$)5jbA6ZsE+;=fS@j@_XUB_l+41L{D~>j6cd zz}?pZNOIcWl-=^kpgaL3KI)4`(YZH=6oa~bhcq|+z>d_~cwsOf+^1~9M3s-A48o!l z%y9`87-YyLwMaIyD=+x$TWnq-~n;3j!@M=6eK4 zYAIp*lSJeX0p9y-706fNnaI^_vAdn?!Z z?ymd04*R=Rz~Eo~xODV{NPJ9%65(I)|LG!j>C4T)fB*oJf3bP`|IS7HCnT@*e<1m; z9h1vcR{{BIJaT-ghz)ckJ?7E;n1JSi;u!cl=4AsN^R^jRsI#DZx;uF9$6vbq$S}B{ zKc8eblWhuR1Vd8JbkAwF*Uk2X@8`c`Dt}ZEkM{NxLzuP4<{kO1YRGKDxUBYL{n>%; zAW$$iR4zhP2`PGXttyz8Aohr3AuuL&&slw*n_hf!H0zqm`Rg%-!DD>X%zDu;pM+7a zI_LHYd2{jI&8kP;B)hf<<;Y;`@Nyy~jnng7af+26Dictv$`1@j8R7(Q4nf>fC~ymE zk*2NqihZ0=FM*mY(E{_9LtVY#Z(xkk2o#IKErad92@qB*WZDlg0avg`ax32@GMItj z@St&~jsrs|-tZ)EaJF9R!U+bBX}6E{GfCvtxUIr+qdUO`5Ab!*W-DbHL(AW{NLX84 zg(4MQKRZ-1dVAX^azu)H~)HQj^FWiru`HmNFU^v6(}&ba}T)T$e_1AJf^l zNm%y{KX)C0j-;JJNFPb^>Z;wy>#Ghqr2S7;M}Liz>IYVFv_XCU2Uc$hN6mr!8*-fe zmj4oHQTks&$A4=0#_vcD(W8g%HMhEG?Pz~@gmNZ88Hq)LUy!B*2o4~}1x`uk%ZOzb zxXa|aGvDNweNy|Aol;>Lo_;G{Rk&qno3HLBM~a+q-dfqumEYQ>ZHl#Z|}W zP*N@|Gckkm`xZgE^hO^NAwz`TK8B|6`KM7qcCN=#E-&rxSl)QjsQ{jKt0zPOG6}557ncWOhH?}wkA|_O8*QneUL}UY@X&3-(aA` zC({oC7+>Y<)&NXP0G@Z~`JBE6UF?OH0ZqBf;yjGBAXPoYb^lHAb$r;EH**lroe+|D z;F4V}nBF{l{-unyK5}*;DU12Qo51ou`-}zU=t5&a-hX};Yxid0pTEW&7#skA_5b&0 zAuS?o;A|jaYwzN$#U%XlL^l2}@P87^^i8|#vRG2N z9b9fF(-}^;->>g@VE~QwM(zG?M)L{RMJry`yJksC_B3Fq8oQtVM0l2Oi>0A`j&mP-k|@-eTGgyRJo;`p zvzmYwMIUKPwl#`LQXtEnOzN)m(9k9Fv>1J~_II)RE#&DOIQ^ImfX-SRkE=yGBr zy(Xn8lq+E?lNYm*wRS1nSx6;)4oTL23Wu_g!aeW`(x_ngc_6#U^Gj~2H(BjOIqHwz z;6Obrym(daD1U5PZott!GRw|es8B1jdvo}q4y{X(&=A*YouOQN;PldWLcmfi;Dl6m%Oyktp~cAX_{O-(Y!ZWDUb zl5u=Ah#!?r0}D%A!6!BZ5iGg? z*$J}7^pbZ){6W6h1Z*xM6S%iB21n#5NKFidgiQjPHxG)9nn!=8^nfy4MuB-mS=1A6 z+!9L`QbdyoL?6H!fN+*L{~=u@XcKsdE|fRXqbim)!)5LG!jTBCf`@?_Wp5 zxX_cE(^%(sQGDVF18pRlxH>_EU3jphuf*g;XSQd!#1f-or#5##l@&*beHe_6M&6o9 zBZtT9QD1%x?$g3?9wK~Yvba>*oHI`-aVhPybIe^9WY<3-ouVVW9{SFEfKCIPf1$<~ zfCm(p^W?lIJH*#;*oY!TWzH+J6`h?y)wQL*A*q`og;g9J^AWBzIt#~p4CA&rmFk`T zEE~&o@`?fO$sVZD|Uck^>LEpPSEvx+0{(EJEv4l4z|# z_E2|AQxHtn2Mspk%JJ%>Rok=|?S)VdU@&Xr{Xnyz>WP!7?5ccnsp z0RPjR;DUt?n*4g1qF*n=_TQQlITz>uVNZPG=VS-@5k~t$1T6q*9`yEg_vK^QSa9W~ z#l_iwe~Fg$gIMMh8^ay%a->m^;d%YxOJvt1`4_BRT}@xu^m?baK7XA#u(Gmnf(nAb z?A(Jd`8fIHx(P&=(rmnyUKU%aL6@l6hEPCNstI;EJXkjl##S40|((9sO z_%wIc64fPIjOx^nd{&!})wIo8#yB}*c~mQ5|7Z@pl;@D$h;Y?$Jeo@J4%ONrBsn=5 zK2a(^kOZGO%x5hdef7V2TsdkZ#>3ES`zfdC0)x2e0F&k{Gg3A+9Y$l;3^Q@RHm#fz zmCXpyZ0|gFv!S>>+WVF~H{~A0G7f?VR|IG^~15 zEzgbgKT14J%4@*S4}2GVr}vQm*>f6Wt-#m6t0ghf0RU+Ji=NX|6S6ZlG5X*3zeF9{ zTj?0}XYYuVO@fZOAXp7aLD$AkPhH(*QI%p@!vb;>s`SlVoYn@Plm)hVKeW&Vp6zsf z8+#VArrkQ0XTFqP(;mHv(J@rCJ`q_lT>p9-dTvKv@7`A`?^ zl>uET-UYEY1nm$8j*)#>t8aAQ7Yv@=ffkO*J$^uF`jay#48uDp#V=`~P5r|LY|r5@ zBKHO1MHdXN(fJ{*H~ik|F+9qb{6L&GJ3OE1iJZ1OkQ}A^WJt;z1zoP{9SoiC$>7{Q zG-#jl9Tnt7sJf?qOs*y}*5V;jF)QRO?7gwElbLlF?EuMHtFSU&qCC<@qU3pm^HeKy z1h#jh&R#kxV`B8hD4-c}ZI}|3IgP*qvO!(ytVKjXlBMdUp>>)LqfQ z0OxgtdDv@UEmqs;g_x^v3LS)5kiGtO#g1N{jEkBjqI>lpe_b~E^x7zrMNdk^*fmJ- zi_NaJn$qv7^#$RdRA)2JlCZi&dB4e^8mggld#!tz3i%|hMPz5{jO^I`V4>n+Qe&h) z+0LD8s5iG!B48Jr?%;SO*Dvmj*egm%e{u}{y4KsGmxgqQhY)!rRTgEUV~=yHYOgeR z7k4E;FpVPAJ2e7Z3LR;VN5(sOD#bt;M1)(3w)aqX5!OV`pT*ZT?q7yjINZpPgKt=0 zmTSpi(7MRZryU&fqH$Ue7^|DflD-LrZI}s(-8oDf?6W^kzKktN#wb(IcoOz9#~&*q zLKj7ibEGqAE-B}i5nm#oX$}lY0=q`G>C_-jP?9qCY>IZK)d(m4*`SeuOEBzEZ4UAX zF9kZoml|61jGR%35$z-|RT~0Wpr#cHjXbXx6h>9Zf?dPVn#KzXkDyBH4C>;HKFq{cVivUTWI%t;^;;B`&$JW@G-1&O*k9@8liFCh9K9xf-x-dzx z88k)aNb3ykgYC8<_E7r9m~CKQf5PUL6y|ArRlU*VdBx71!_XSlhYnbR%`v^<1nn`Y z&*4wwx-dN(wDLw{sdV>u^W_T-L{aJv;;DQ=(p9}N1!dV61@@J{QT;1DxP1!?_9&~R zz*I=G2wJUS*5**FD}N*VSF*oHsXGD(X+oBJqrFygz=yr1c8@v17Sa}adGJ@*PUliy z_*QN#5i+%}c0W9l&0bmXy+UX_1ubEpmJ-@_y;yA6X#p)t%;HIk-DM3`v<4f^E{4X0 z8>(@ktySP~r2aJ{E`&xB4p}HRQoI7|tlR)-rEx%+&ZHGb7%pL6I-&>mcTo^n&p z*-c|ht6rFm!|Pd)jDu=(b8~vt2@d5Qnly?0p^=Vz9+$`u$Q@Pb!AGE`kZyMAOe_fc%dRCNh*a0;AWGW1 zA%aV0+vGq>x_fo_Qrf#BLWuN+_1+U%&-7qQ#ycd!q8SpKl>t^dponx;M*2}kKQjwx z2?iy-w5*?560}r6(*94J$sq2>0!&yijiHDfr$Lr0!XhC0PwUCZqEh@{{O+MmW1H1c zeU2-3u~ydgYFJkr>{a<7L1~XT=YllIEnVwHh^^yXYLshTc6WuWkfe@UYDwk{2orMX z7WZt~4W!b`zEP?(5gz6VnCcFx^dNxL+uh|6m8PiA~6-DYthg&I(L|M$erHm5XR6H_hMu&T3{>Pw^l7|*w3o{Bsww%X zw;#PvwmM9g+Bjjd;3;LxjvC!{;CM;AqJOCZ0djO!Q3X|mJlE24x@4-;ZrD831gdqn z1FsgT6Ly#`eqeU5G4rKGYeA+pJUVT7(BW|9_?jL0l+b}SrP>kcotG*G4?kl1z-mW6 z_Bei9c1-MV19qwH-tNUzS&i23BU&3aXRcO-tDkKJq4JW7GNjEHiv6wi05~oPDjzpY zXo>KsV(K7+RKq2!LTQvt#Cf`%-4SUPZH0~M(1MLJ8M%fIUcro@0E$9aUbVkBDOj;r zt+e#kpe_b+aE3;mE@{l*vq?6nku`-25o#W;&Zbjs4JUp8g6G6+xVR z-MrXkdY)}cyCr80?ZyMndmg-UY&mV&J-Io5c}i1dX6Kc^XE^JcJ@fKx5G6xjAZBP=!(3OM#IQCTAJ1p?Oqsv>oJX5EvM zJ{+s|sf{&6xHI-$m2vQUTQrJ z6;!)L=2S;!J8JI4fXx!(4gx%U&+wYUX)g68MMUMrLB1(oWrn8QjOXF~y=KI9>dy@U zTiYpi%y#nB%*zr-<#ui7fKbPlEc$O)LriU%T6@u1R;nbj2kju#DhaNMT-|Kos%NkErF!iqO+8pKvbMg zS}MZ}HYcT~Dk^-C89)z|sW|9r1@0BomnaWPV-?ifP;+?OWZ>%Mww`l8JJ@y{BK3mz z#}=1tI^Q-Uuci9umNxkWuj+ilB(L~vCNICY}>YN z+qP}nwr$%scGkcK@Ttz2~J*KV;@xKE%kiGGfJ?Ur1TCRje39`{_nULgw(s zmTs5tdBqjI8)R?xPl4I%hBp|HTJnS+ADUBe9-vq`_YaRA#f~{I=8>AaZDORMaNevStBVRY!uqX-H}l45WHd=SPvulDesSv3K1~g8 z0HpW3cs8(v9qN{WrZPUxu#>q>q+jgq^X6m#sVmWY&#v{o*va+>qd<<%7QDj@+orf} zTSr&K6E2*91*$ht6D0Lnum}i`#vD3gZ;siiDolr<3LAR-MStu>HISgIiy2*U%PEK; z>~l+h%*IVmN}fO~7#lYtVbRFs=z%<1lDnj1&&NL9?#k~!Dn95dG0)lGdf|UHj;a5v zxz|vWln0#m+l3nu+5tCBl^o z@$0A!O|8!-slQJ`f1mZtJ^Y`t*CiSY0dF3sI!<%FZg@^}oo;S-j(Vi*vVrCz|6;Yn zXaI3s88oU9(lyM&p@5Kz1dYKv>FL)FZPg2}`$r%|d~f0IMSx{s8|H*%a1U&YWq2oa zg^|Rfs|Sy^Kaxa@!cdPOLDIv7@RC0X7%ufOa`zj_eMES#%Wc=uzf&gFy<3C;CY7ag zPaj=MkF}WHRR`GZW%*(cx2eBi)7{yyzT!mOVyX{jDc|#)+gwp=Z(Xqf5{(^ z1a!{a?8tOMx`g9KtlhU`0b%I{>(#IYZ-S%O?hbQHEzxXq2%v&N+Z zZUktTW~1Dh{!q_822K@NS`&Z0OT!vd64cvSg!7NippncAjb+ft^!lt`SX zmev|t)DL5forZ?yr0xS9r)n2mB}1NjOWEpq&RLvyS{Zd_%c6#gPE0CK1OnQpit2I(3olW@fq( zljxS#bPAOm^v(#Glo`tDlfd{5P4_E$l4oI|+y3q>*0nhcH1K;Uo{Ju3+NIZ`YXNe( zm**W#Ak_tuZX-YS{7cWD8>sQ=`8^(1PbfVogm^J`qcg69#?i z+lf@IPtnO$j>7xgg!hqsffI@2f*tBOizihyJ1R$8&1`llZwM%?C0_)JGBCwZIvs#^ zQ!*Bf9#s)AYvLVj_eR~8hKPI!<{gp>&&4aAVEU!-r(K(4Wl%vhZaCQXp%Wd3{X;qC zZ1dyASUjXFOt0{uGGEAiMI32b5;m7i_n5KM)M5$}FUmBpn7+0LXm2GEhwKIiP3^No ztXp;mYFpndvW+=3fRlB&Zb)@T6GN_Z2JQEnh*JifBy(>Oov+*5^#Tu6UM@yJEE-^7sY4snof59CN)4*&x0q|w=*FQrPJ z3N9!a8ld1Z-=slv4*1c1NRm2okJAV zL8W4ayyPWeN4bQ;5^Ski4h^$AvJ@IuDHzJ{d{ieGthJm1q;N24xsnn6GAG^g*`3FR zmpDRlx4p6h9K9ZlgliYsbQZS-FwiqQm{qkeLkdnq*-j*lBWdjh0{B4;x1Cfwmd^Oo zhv8DJE?ZPq!uquJr^|AM-6F80>c*Vggq}s}5P#WT{-L1F1vr3hN_C)@<+}>Ye}l;T z>E7CQthlyUh;ahF3*8jzVm13OMPvoC`H$Nm+`I(3~ z`@VrkFdbgu{{l~lSSN``a)JDPvm|Df+JYjO7tsOqnC;OT=WwONo@G#-BwH%*AwKBT`p=hqOa)*SfAVbRhAVsxN z>t6cc=yAYML?N)Ww3aE@wO}reReFKmSP-nt{dSMkePb&4WtNaOC_;c757*%BGDpgr znILLy<2&@8(d-{vC892t`=rNceo(cCFpX^)4+=Lz=*S&RI9HjrwzP6-w-pa9z zG5W!v-qC#}zex7env^w=Fv&N-e!lrrdWm&8NPSBC_$<{SmP;*+RM@mxP^}}pXzlY2 z*BdHATuK&w>Wtc%wN|_^|3;eHQXwhW)G`854N=kR3Od;;HmLbGE7x2-$6)IOxmGHM za-y7+w2V)mPw;|5dseK(kSjb>3OxQA=Yad0q5Sc&7?NP^La4ds_*RfI37o;JR#qPn zAT1|2&a;hff6s*qM^dn5nFOgl1I)(7CJPlRtU0ND-&`*m+vr8qlqId_+#Ii~G|}pI z^h6s{0BXq%ksCN)0g;0?(Ef-!2lQ@mtm>tWBJQ!Aq-j1$U0_!dj%Y0cQqp+|$AcyN zR6k_ApB0aSrA4A}8e)5;BBngUdSKRxqhOXCVvtpxJ792YO_UU&o`s7Zz``oRKaoB~ zcn-EvzbUc2#mdG7@IHQ)^?Jj1Bft5Zc5&l$oLjsfuQZ0!WOu+_lo8^Xx(V~n^_CHE@(G1xo9-xjPqavbNS#@=K3^sUvnj}! zjNPa#_3CS~1*y9A(b2^wY+W)Hp-Z$m!lh}r18Rl2z_E`IHmgWb#tv!Ynbd*l10kjLP6As=6inDn|TcqXeVxw8}ROHHU z_4B2v`xMKrQnSVwQBr#cA~%+Ee2Pf93t82s9)-j!_{NHn2-)u}CteAO5EBs>(d@%> z@1~E~=A`na?Kg{2z3vNXwfQ7YT`X7Ng0+~n`Nf>%tQzuFEL3)cs%gKBjEJI2^0ES1 zH7()eRBa3*n&cWvKBsC=~S39i=fA|t*SRbh)X>8Idyy)`jrMD zC-3`1lnU92DI&~`|4~5`{(S)75*JlA+|;CUsJ{cUB~j5EeEIf|Q%lA|7HREm3vjHR zB_0`izCD~}sJIluEqYQc1nM|8?5^hnJPDWd)pJ(;9XdccoRA z(~F+6*QY1ran@D!)?vMHBdr0@?CxUH_I`c-3L9;Ed{~?K`dk_k!3W}uJnPBEnf1(P zG8u~n{>=FJT1(E>L{@Z7@adnmmG9Qe&f4o%TA9rntu~iSRzZjxj#r-V_IQ1_$FQ58 z(;cT7p4TtFUiah2Q-J)e?TW!3d+m@@p1r`h%R8()2plyL6yAy;EZzKlCFhqc&TRkP z!u>Xtw`N$-Y~mrb^DCz2Zw(aPg#uLB^EqIqW}nQ~*||FoxEr5b_Z*T!C(7>X0gu4H z`GZtSG&bW4)#s_rr&cjpV>daDLO-)bRu7Yp+Ik?!P#?`N64^}_rD{7Ux0n7^>Dzqty& zzykP)@Axd<=rDbS2mKcB_=rFMUDzY&l4;OZ+!^8Agiba;f)Z#G;5KmrjVPZO!mlB0 zGd3F>)n0#lfqsOwMq3Xjkpu0HSd;37n?Z6No>NE(ep*5&?_baN`vRN#jrCs`GnuSlbp(VoH^fKB}ty@c%MEDbzcpxwlg`(DLd#Lz$4~jR!>aM*GsE%W4Gs zHUGTZZwQDv#;t85(+y!%cSpJsqUA}Kjy&khBcdjbK?GcHY=^atE+&n?)zb*RQ}~2I zu3&om?q=t-yxq7H+8m>|+sT@o0)i-l>e=pa9^+KE zM{aMm!No*w-yEJ?X}ML-2;T44pH~hIjy8o{whE_}kG!^n)?t-xz%AxMHVcC_#o)phYR=!%TR6~m!+g;#vD)c+`F2t! zTvezzCH;(>KRART3Q#~}R!a#3NA8tpD&fPeK3=sl0%^bunZfI(OluD?F(+@brZnEa zFq@1Q60U4eE-%c7$$yZvxeqCzT|TEVarb;bWq}<1OCz=AR)w@EbIs}5l;+EzOBvz- zl!o+|1EWLQ7`OFxP0{uH+b6t1s{#f=9|sMV_T(Nn8mxx&0p8L9@I;B*@fJXo2aBA4vl%Kg;$kxz z^fyqb1oeFoV;vw;d=9$A1Z`5gq<;nCfMHwg%RqmZsQ5(}nvfVKosZ>M24{ZlF=v18$VnSWw+4hL;g8OaJysUcwW(S-=xn=wB0NnEc zAbooZs^xQAMSZu?=e+-R67vfLjg^a`ajl9JK^d!0K-&Tm-Q9g4?cDj zbSi8!%~7rfqJATeQYu)N5*=hlbIU8eeWbR$ z_gedP`z~FI5R$LB+O{o(JDGG)?WJU%W?P)K& z-B)TP%PtBE8s)8PDR#awX?~P;$7}{YSX9BBH|&yL40U_Mo|3}_r#Ht5IxIMg@ANE9QwW$#$x zCU;vzyoT0v#r}wpVjy1RtirRwpcTrUIAISUR(J`=j`u=F>*GSuoY|&w_|}_Vc%Gk~ zXDqFSBne?o>k?FI{0l3Viry3}5iZwicIys0H(+2jf{a=C1TUjX79XYkZm}gz9@*cm zl9h#eHb^uCp4nV9jsLVnWuL{ywOq7S+{=}q>jRK2NsW#lTB(aSMQpCsssj33J$#X- z$wq~52}(Yg)&(uUt086g!oO+f@LGKgM^cri)|MkZ@|eu!HD~B`otDUn!UkKOy*5VFToVTKzpv?UExyoRiyMYzmL63A;4}8DI#kZp$0oc#% zzfd2P?R^W3K$DyHa7ii`^I@IwD_kMD!M#lR_3UPMc2dpwVR&FP^RJ=b;>$^snU7&< z^Ms83#H7YXpt3_Y6eI*?T_|tfNJg}2Vc3e98-(EiNln4cp>_i+CK&z%uC|@$oUie3&D!~TX%R1YR0D-tcK$dH?Yb{Sl5@RRKJAa zbq-A})DU$CvZcq{JRz#>fXq(7z6VpLZ-S6}1D`MBd`MxQBE4=xnRtF;yJgMehn>1O zwY}X!|B3$-8^BA$biddDw8IfD-)_5AoJXXc{5WRGIz3G zIV8;INi9e=!cMeId0%au&MyWRE{?sj3xp?bsbR`Bodw|fuy61*g_%MuQas_&OyB0n z_|14-9U3UBBr}bkwvif*9DHaODc9UH9h7kL3YzYb$DyrZl?9 zf)aVEv`Z2Y&W*~#SuSL9+(|PIv(Ze3k!Nf0_)y=0?Uh&thr#|K)SQ`kanz^aa1QOn zvrQwY2>X)tTOh@&1icQUAVgb}+_1nQ=u@oJWwlu?lXa~buIKx!8EmvDA>R|yHU^lX zYfIJm@E@F@TzAS`mNkQdImOC#xaHqI1T=t8r2-M+isa?uOn0mauz`@IL8O>>}? z_M2fU?tG>bSPrPH0lqaxf27Hq%p_JBaN@3gt-IXgvMWHWH zrffIW`!Zv5!1M^Y*zp0mY5!574U8Von-t?W5t&HC5}g&?J*!--zh}0-Pzam=xdToq zAVfDS$a`UfTQA)fbS$et$Rv@x*q+e=fCiMVJCo`lT3rA0yWX267wRY-=(T^Jl z8tb_V)2kpLWG=Sq-%V~gVUu4hpYr=ARB1~D8#)puvp&Ph5@zY%4C^xwok3ypun6eI zAQ@J3cNXuau%W)6##P*cO@1~4Ejm(%Pm#NQUN3+88%~iE7(_8Iu`he8LAn1VQ2l8tYR8_mi~DD z)ldiRZzHDpR^T-E25S0~=(QeKaEth6HqUTbCj_&%&F_*I8ub<-kE1WPSDu6?%D^Sh z4(%EYr0K7Dh=J~~Qh8OaqM|sIh-&LqLY_#e+8)p1MKY=AA6s|xlouGsq0oXvSKKY) za#oA6<^kMk&2#Gsk_nmxbYqq?wIl^@wr~4^+)lNC%?VS)WG^=i%^NRl_V~jkUxv+= zOUriwh91J3%|}sFc6T{!z(&GZ>0mgtuu=rN&6CoeT#tB(ocX%Gf1Dx>vfi%+OI_YykalhQ2M!Y7vLD0Esmpaqq-3I)BvK;;*tX zk75L^c0p9K>MG%fWCDdrVNI*1as@cca@(|tK^?Q;955G&&h))A0^+ufX z-zx8<(5dAi40pCv@V2If;Yce6FjU_2{f|fasH9g9Y9d=_(W$CV7z^MEZI+nEEndXI z(HP4FH`S{zTk}3RniF&{m>Jq^u3>F2vuDIAmM)~tJeoZ!7wQ!35Z?Wyau&tPnXv=D z|7e;aP&gWjLHzk+iuhlt5an&ngv?!Rt^S{OVv5?i8`3JuzZlU;@#sNy{Lxlx!BkKU zM)ZRviw2oHLL(H93L1MDiF|i*im2EX?pJVJ7FyDgF$*9^{KCV?|0z0x9`9F=iARa_S+`ve^zk3 zcLdRD#NK0{*}+iVddc0 zg(cy{5acYyoQCu6pSX*S{wk?an-#sa>TMzQwNtiSZq9bQLVs^^18frxbO{-f&@ZiT zn-X^12`4xO17s5DhZXXLyEZcjH;{Yky7DM!Cjt>lEEa>;i)F}+Sfx!L=&|tIYwB#| z3QJgUkD(P?w~RDBh4D!8DN(!n^69|(fjyCJbxssJertCIFN#{VO@ITe1ux?gi&#~5~ z-Fjr!+I>w0?xb9bWhA4DNE|h^ZsK)PB`!2ss-qW*hZo_hXmO6+4a^;EDVSEgawTj$ zhHo}c`T-*`Gfwh2QZQAVA_cy+LJK$IF%~|^G%1>^n70B1ten{_I*!+0Tqai| zlZ>JF(f%&4>4=%T$BExS5Ey?$D2YOR7+ni8VEBlPvMsXa$&L6#dDN5dxxREq0HVj) zhooXb8lin)f*2RXC(g{?2iXaU`sZMET^|_#(1u0x?Zq+v486Fla6?`skNFtC zWm4y@Vn0q>Wjv@|faaxvO%DAdJ!F@igZ_LI-IT5i`#brDo;O!>;-x#n+$-UvWxvqm z_BVA2#JYQTzewR1u_FD#YV|GV*eOa~H_;}*$Rxlf*xtS^;}+A<6(2o0Gp+^%BG)E9 z)egU%WRY~FN=8b)3ut+Zoj1aDI+RJC+q!1vxXBx&rMWlPQWay5qk5Jhdu{J44CL0d zBW~xD&@1l0{8ZFY(1I;vUb=dX>YKH1G0=v(PZRumvk7jaI}}UX#}RpB9pH+(L*Lia zII;6Wz$NRh0>35gE(5~jE^QA@1v}{_s{eM?|Ail`Qq@101W-sa^s_WmJHI~6o!i)_ zIeR_Cj$U`BXt^W}5~f&*u$!l3=QZmzS++x0D9#2Vo5kgZpLKw8B-ZJT)anf((iE9; z0>ugUAS1w_sFFJ;f%l7T245RdQ8+>h?t3INH;fRrA+i-`MFXQ1(~?pu)}RwhwK%m> zumxw49}4lBg&{N!ltYT4R=`ZdBU3brR~kIr^>M47%x1JCsaUY=0!0ofK(jc&bv3h_C`bmYzKuN>~#^u%hhcXPPTy2C3Zk9dg5! zv0LTMLyeZHlYncjZxC$$=q|V{?p&EaFl+T&S6JGEW-l|Tqa6u>Wj~L0aA0bqDPY>$ zfGb|UeUHYn$#8`{O8tZ6j796N4m655*%Be5odeq9hewgY@yiB@dh$Cb{Wenf3AzA~ULW;cm~bWu>6WbKTQ6<~o-HIUzvA>7 zT_<6;E!^V%Q20CpQnhRgrZarz1}t0S>o+02GJX?4a%IrA9k30hl2*@jLGE_nOH`W^ zy6Fu#l_3)hKUol(f0oA6~#<9l;oT~MmE&rFl43?ej^0aAug!-h*;K8%nU|K)mg94ZE-U9kWFA#@5 zR|L%SdqjYL<^c3N;k7@W84c~^41R06i5X15GvIzlR^-SBk}TQ>vkv3)(tUDmccg2C zH|RBHduoEy-Hw2KE`)ANF#I#HFFY-e#8Pq~_Wl7dqBlIPdLg@T!CsBmOs3&mu|U(U z&TTo*+MbdI;8ep_)?}31E$rj`nk7+}%jLuE5&#p@_SREl^+*`%LB^cok z%*aR(YaHhQ8{-VvC1RWMD(y@kUPVnUA4i-F&?4dRE1z!@FT(xm>2tD``vUW`h*6f9 z&_1OW#3)Vm>z)?&8%!rJG32pH6^6mK#lKQ5cX?&Q2!XPN40LCT>j;6ig|znIWzw=V z4u=!sbR-g4jRaXPC(ZOE{Fxw;Y;vd0j78aO$D|^rve}a-q_RTgzQ9{V$>9-6K(qRs zo79O3+=d9-Gz{+HHPF+BMR0m!L#3ax*yn6Eil1MQ(5P1_$i(94V*ayZL7;ED-GNHy zIvzYOZOH}(+t`0PdVYX6)o6YVf@8l1L8kwrPxL<-nd1Kj(r!`K`d=LOpU_t8E}eov zLU#d?kcCJpa@{{DG$K_X2$(UXywLO%P?N3ewBddOLt;Mn31A-nz>xDpZ>9%!j29W( zHZdRkRtLmRe7hXG98NRd-#`0E{!(KVXMMxq8}oJg{33mu+KGcEC}9lcdy$d!wUq6p zg2OH#LXrd!z?k8xb=>&q0U(tg&{d0fw0x78YY|{-p z0SUaF16T)SX{yzwpnX`($Ec^g4uV%Aw~%9rF8t*(dfEH2#GohbZ5g3QE?;|I8HDIn z(#b|!y6r2Q(VyJya(uD#?WbsD?hIpRGXr-fd|ZD~@h(c7RVQyP7K=sL8- z5JQ4((85*da}%%!swcQ{J9`n>$R7+C zjoO@`uQW`uad7AmuP&p{(3t^!FC zxy;4Ah4;>B1G&IS)?hK)_E;(b@tV$zLJ>F3zXE+TF<cIk!t>B4>xe#;EcXSHT3>nXAG76Oby+SA&HZXOVEs-KKev-SD|yhasC_ zuYXUwH^z{;)fss$m+}zp*?s*&SFlRW_O@+cgsm;&(%_&h5^1#C7O=*d{)@Bdh!wVz zuc8UwBcz-wpT3cpRf}*giiCWPWq2fZy5Cyv9nY(McaB%6p$OrXxxxC?ccBu2t4DYu zf$afB(jKd?UsTB#99pV?`2HWREcf9qSp8oj%=Pc;`!D!xzx9x?iIJVL#qaair~AKK zbW@adl(0;YeRqtLeuqkj)TgjU_!owju&P>7+aOB)A%O$|kg}+1B;`Vf$eJ>3NQi<) z`v!WRsp7;j>)tPA0?Bb5!Q0EbBP&niw7Jio#sqg3ou0~d+Ih^4JN52{VcgpNuX9q^r-4QrtY|y1k4Gc*<__5 zdu=g-4A1sA%y1!7b9_*puTG+*n^W1DNudgf5m_Y9Z0&nrkL<~1hB{VkF(tKX%P}5z zl-^{V7+)xDuq8#SeqJx4O(wTSo-Fm5Cbv__?He%kwao;SQ?1T4QjaZ5J0-34OP!=s z3WEsuI}SM=(rQB5VUAiB%Zx{2tJv_K-;1nX3$u#-XJxgW!%81o8KQmGc_2DDbh!l z0AUycq5bmuESnN6Q74q0&)im|4>kZHLPr(;Lr(*hj>%y}KhE0wx?%q^YK&6gxygc=!cs89!xH;4ROzl~U8^zQJj^H^SU3 z2)7?|bF_{4b^3I6FkKhjp_VOTxj0RAlOmT^nQ~H*RH=9LC~vSbNZZj(WJ%#oLwpsf zwr6|ZI$9P?ifujq(Iue0qa5cIKKt58Z~}z9S2!b%OK?U&l<3p=76bROhU3+NxfWO` z;Q@Xc1+QHQ=?;T$fc+*A`~>H-Z^|i=D{QYrDhu$q|BxUG56f)L8MOh~F<-aLwF&Em z3+b&*3Ya~VnPnG=i4V`aH~U(5f8zZxJX*l1yEVRf8-@e>JM4O{I>$#0A9_ncOz>0q z5GPt>^HBIZim;SdjGjd02e(po-t#5=v*RO7K12LDv{&*fv{`iHdJx(-T*;=uLab-b z;b3cC_u=)r==E1)JAZZ)abX@IVOU0m%+LouPBFK-I!3RyY=hV|uZzvBs3@JZ@9*LFy^;>hT0e8Hvvz zA7XRVZg=t@%rP9o?w&$%hOzdPWjU@)y8``H>@ zeFQ$MrS7TbnMKIWLMLCB>8Y-!=_fQ`mZ@@5%(3JM5q)1>-(MXc0v!~52wMrXr4rKQ zVWD?Lboo|S<5cF@x#CI@O_ZnoV5U1clXiBIHE1lGNk#LblNH&Zd(KWXWdr5n$HKr- z&f{d?;r-t@d(mG&fNO2j(v@{)lfBd`w+s6i> zBd~Is8!Qf{hS|VzA%G&hfbT1JQUr(wo03cmB5XVgs&MMD`{G!A3X6-bR2rHWJ4G%x zIp`V-%0%aKk!(p(G}EXUlpuU)m`ZMKTxs%Pjvpkrwd<2r=}HM+Dr)KIA_OKaa;b*B z2T9J2Y6h}$LKwnPospguU>W4$|8PXj<&8^164{X2-k4bq7%l$I+KXRBx_=R*odNbc z&^s|^d9@^^jh*S9X)|KBG>PvGTF{RO4Z|-X5rms=>rAQF#Co8uhx&Q>55^HYrReMR zZ>B5$8{q!}iSfUGyD|pOM&|$X@yd0{{zCAwW3Ja=!60Tyk&%)6rwQf>Gw0g*<>&Ec z3~4!O$J_#M3&Z2d-~RAIHpIUDQO(R*HdkwU{C<1~*#i_KGL|P%w0^ZA8zmm)H8@*q zyN}>!DNtX7kFisKlDf3%CNys9oau9}~DQ{CmI?uZCBUhz`< z*VY$NJW2e(kMCDlXE;bq&Vaa+XhDe;ao7IwM`e)(botlp`FY}BPu8O+!!SY{srP>_ zenE_i%?-Fee>`#iD@fG;7Qp`}bUEozze5vRMpwr@W{B!t2lzuX_}$Ct2|~k!VTc9) z5MTyoqC=NY#X}Rrld@yrlT#A5tZ1TG)u=}yRw!82kVDkqx3rejv|V3+TH0+|T~CT_ zYJJ`XOli~L@Umzav>7U3;$%43Tl{lD1Hu%LuHlT^u`?W+7I(WICJE?&2W=N?)rdXkbxT zU)Rw98ys-vZ15RQojRe(D3#{SSYwtW;+o${k)F+nA*kMNwAEpR;j`jmit21DX2g8w zMC8HA7}}tPc6hl6y?OeWFxFB#J;^mTnQfIeF{4b9E<@Bw$;m>zOiDp3S)r4yy_D=@ zS22ZtU1^%;H=;zj4lC8w*^Pp?B#JN~3?tw$xhy_d#iOlN8@f zG4RhQhW#Smj`=*`lHu^egI2>L8VSsw+= z=(QPcEQGabjHuVWE^BfiD~+K0Mkkw%ESVSl>_Nc+Bk@hCu%$?)6<6U_(zz^=yKC1e ziU+>cp>6y-dT6n5d}g^u(ZWBc@8g1R0ZqYEFbj&DDut`Oya>^hGW2qM9ey;>3W4G{ zT<8TxPB!spLLbIxQB8zms>Va#dn<@8DI{SSlt zdiCO1RFHE73LeS_4^L|GDsz|kRH|S@vfP|;y0-gY8=haH81WmmT!|PCEEm7C}t{iE%D1-insU6Tow(*Q|JISYm z0#-Mwcl(cGqz<2U7Pk}EV_7M)v%S7UM`;V@OFi}r?4yH~nH!ojg7|Y>SqhA@EFad^ zztM4|`zVZHU>${soMAI0F_j8b`GQNQ$ICwDv$IvJ`MOzZd~nYo9?v22)mY(8=KwMqv_BI0b>KCUXW0 zU}NDJK*l4=8J?k60FH(tyls1Y;yZASM5o5I+3I^w<%L%+UY12y+SjR-(2wPdm1(SP zm;^-~8`N-FogCr;1ZY%YI@*wEu~QTiBx-OT`vi1EurhT~l#??~Tb7|yC#qD&ENYv_ z23l>8o%|mso$CCY8G9NHLdK{dbyWz3aL86f2Pogc(e4WSCz<9I`K+oFTUgr>^w1uI zFXJ9nVQd~rvy`i+;zqRRG;H07uQ$|eJ%x*1NC3fupJQku6uL-0&@s%N*p>+|xGIuQ z4x~q0w!+v$Hcr*g@G+74;YD5y{tAjXI5BW@<_rwCZe<30ZcDiW2L+5=Tw9gQraKHv z@f|ocaImk|i7&K?{>E9SoW1bd6Y`Zq|9CkMg?7Z6octqsw_<2<&hYGE=#Kfl*{`0{ z%cjgru?{h-cdy|V($|uT-A$Q!({cnTFT(oi4HgW}!<*n*5z<&uy2X%~MG?SEW^9h;R@^Anu|t%d#qV_#qWP>~9~EToi1GN;l6B+trP_UBqU_{i$(_;+ zcknVl+ZDQF)`NbG7|#@k;E^^|Wb9J$7Kq(>N-D(0jVN!QWM~a93R~s%gm@e=$>f)L zWBowoQ*i=giF4n^kMXw6m5(O6P-`nbmEfy-k8o1l{DXRo0S86~>&RIEN+=9bZPdg` z+;~;S7Ks>?-Q>pQyqH=q3C;^<(MtPZ;ZNrB0w7q@nqLJmlNw$xoYC6vxMSu=X(b~?diz4@1i^$`fM^gcj|p3;OFj=n zVn5XQ9Fb^iHXCbfc}Hnvv-=ecRtv+-L=o7SbmA}J!yVVSt=a-3Q7f z7^Q}|kq#r)xcF_)bXmI%8$tu$+nrS7q^=*hZJ%KrYk`y+6)uLscpk&_!ba+kYyr-} zbUX(nr6lw;(Ty5jYic&JW80zd+1uIPBN|&{a~mQeDXjOs2}xmu4RQ1w>8e&k1a408 z#laPa3vjN{^M(45FK8}!BHuq!$E9J{t|}&BHI}273@A={2ozYw=&eD@hb$c#dj;fM zqW4D!aLrYTvXnpPBQIg5o)6RMH@RF50+e_fTdLO#e;n3;iLF25wTwmu`6P$0VYw5<*Hfrhbz4Oai}}P% zGCaqhzJnC$*HL`YbwMI*M+i@CT@{uzp$-!Tl@}0A{WA7Et-vnTc)C_G$fs60-X5}J zvzdW$61q7w5UKX|CmouIgORUr9_#6FpY@HuzOpa0lEvu>-`mC*!=dT6sxR6Iz%`i+0A(i5co0Lff^p_l)?HwJPZEZrXkUNFtdGpRn{n-E#$ zCsuh=AySGB80nMYIf}?$Sn1=6xkY50KeDH;XBd&bK+=Z{bLWvafQDPpG-^*q`k;9r zHS*5j>`6>>k|KUUrjGEOQu8%^*0%l!T(?|(8r(pfYV$_?C~g9?s!w2h*s+4MD$aQF z(`iDd6dxS)d8a~F$T%RD9$-)0BDVlg(qn=mUH+W4L9~ib*!AgTLTTg|Kr^Q;b0v|w zps5ZB&ARhBeIVWtPl6(Rz?kl=&K-qh0iv=(YUCeK_6VN^XjGjb?ZI^ZtQ4NO<|pV+ ziXw41!op}lD(LnqVo{=GpNi3XQcWx zd2lvL&e-Osivj0mybCu2h~# zYg0w}Y80I?;!Z@(V)L*5(0LHsg=Y@>RAu>_$lnlW#}ISZkuLyhk12Cj$lr)-{Vj7= zC^$g1hnM-QRGjO99k_0{M7JfsYMe!Dx_rG)4A$%(={0o)0C)h7A zB=km1hsIrU)-@ocO@Pktw2yCxh^_i0wx8Cnd}lHx@y4u2z+QNUHc+HZcPJ8Aw~y$XQwH7}@CQ83DQhn)(wF876%DF4Mg@U)RwmF->4|9H z%{_LnD?RwmYm4a3O~enyHxDQf4<9R-?+6NnPta*Uu0#6{30Uq;X@~p`PNWV>Z!dgL zq`LghtXuOguRWOW)FV(2|IdKl154mP#i!akl2(wvg0YZ#CnbS;Mv4QwcNl@Yh7uuv z51#{f4-tC@4uR_kXM^kRCHpVY(|@_Tt$T6msQY>DS$ls`dPu&Q;!wK-zEFQc)WClt z{lWi@qy+vY!U?u>Y}=z@M(dqM0=dN(3n6ol+0&7he8bHnbH|gS-B z(WRPUX>xgKm}NTN;Na9P^qoz%I%8t*6PP`El=cmR@<=v&@GAB1e+$}S0^wGg(;pVe zr8vugl%YLo$gP-TiedD?j(e3Z;$AdQBI536ig}T?eh|*R%2;Efh$^$6VxpK;Hg@2c zeUTQ)tynwY(7w)yvGjhdzUrw#)X>NPE5zPX6BGS`Jfo|%_?`0;gQh~kY#os*gdqDKM2KI2J-`9o=Art7MoPrs87NlwRi_k3Co_XuBv2EM7Q?YGV#kMQ9ZQI(ht(~M|`{sP#ZMU6sp8j{Y^{^gyn{BSq<{Z6`Uza;c zN+pt1k~sPE$}qBv*H@CKfGx>AKJg8y&%$y%uamdWz2GO;A2?X+|0H!9KrRCU89Q_ zhnt9VQuz%p2}5zFf@4NFK56T($S!hic4xpPJ|6NL#-0iPvrjOW$P$$Jg`C^{4g$jP zkEBI0yDxF$|K;;)!29T!VhH@a>}vDeC~k@6`LhaVv$IB8z)ONq+9wF2YVjnV%n^pz71J9T5R%T=X&1#bG&YJoM)G zn0fofoBQIC)PP87@tFiMD;hnd=)7GzO(jmTlRWJE3jysDe(`{NeKvF6!7+wfAfvL3Ch1 z7Wfykj(=*0bx1B_TWb#S<=|JF`WBY&bz_=8-G3<&ZG5Lmh`*@)VedXO9FtwW z)h+q!S7V7zTo_#W-_b(nXAkZkqHG)m$rbN!2X?V{BkT{4;>3nWFhcEPR2fq~skv(o z2mQ_WSrU=ouh0r@)>0G>An><6#okUmWSHmYki>P^akrOPvI%i#9*-NspHiIjxLCQf zyOkB4&B=M@60oGnMx4S^HkN#3n6ng2=C4&{oMe1RXXi2qP7Ks09SfPScSY3!*7~fL zq_~8*a!7~T`MhQG3j+wAoY}GP+?KHKjE%Wf#|_smcC^`Yg`v$u&Cz5iM@t6|e(2Q_ zXk+oSyiOfd>BtnrX~#I@h0IzqHC~hrrnDKOhY9H}8pHyBu)i1D$`{ur?bVL!YEttj zQ99urlg*F$&?Erv#=kFgg36qZA`BRs{^I9%G1c&){l1+fohz;OLdNcB&7Q7s7i(4z z<7ztptpml>Ti#ikHH%^TqLy9U7RQFr#vHN6l$9$X=6IyxijIwG-MD_4tdeeaQpuh- zdGr`{x&|N?hmnX)Gf^&Gm*1y+r1sJz-Q)i{I*7s?iEtEQC^nZ#F3Y-%=jHfEUy(Ko zmEB{3ID@0~PA8KtjTae>YZvFxGBkJC*eo>JuVj{yjI})iifx>ElnJYdw7zN<ESBaW3YRxV)KdBYBFP4hVE+Ciq8~arRN3kPyveW6W`4S<3lXU5bN|Gj+_> zgforBstnC8C2iM3m==GU`K(qImZ-bH`6oY#1+|4;It=ypJuX&fiqenS12dB$Q=Fz1 zyF_}pl}I_B^kx@cQx4n+>uY(g@(H)@J5P3dS`8+i{riPh-fnwooAdN9&QZea(-Ubl-<>^E$H%Wo}6%w zl_z4{pEmoTxIcvhJz(|V+5_kZp5#pTqp^PKv&R^Bmf7$^giQnxs%)glMMd@xwvxz3 zo%Ids*km?44i6i%caINi;U3FY_Gx4fgVqcKi4t~`f%2cA-#!9WnH1^vuW6oP zcUqh3{5@qK#Lf*o-s6bt^RufkqbzN9071u!+XGDofBl0$VOR--Bw6^H1!P7{$)a zAlI@IyEuc!L5{OeNpb#uI1JVm;n>RxX62bHh#1{bn%yQ;HhTmGzrblrZo~Bp(#zF9 zhJ#LX0L{Rh>6cxC9%dMci+|}L3}zz!oD)P)1};xn`0I(NlzU3BpPtmEa>At7$)WNr zjS^MX2gs2Jmo~WzN>l}WV}N6HGzKn@O_W&G)n!St=7JO?cN_5JC86YOCp^(5U5^iv z^|@EKsrMIc1tn)dFCH;4K^qs`*?Wu-fi;_7q{6MgQH-4~;cH}8%E)UMw)Gwwb5lFK zjOYYrvEpvY^QM%Q3`qsBLB*39fdioed6=Evj2t_*Zwjp&oUbqxReobF{$SPz&&@14WVXi{Q*GXi5dL=0kJX;xr5b4QejAF)6 zr}%_x;)RN6h?mpbqS$3vTR7&MuazX-Y)Nn?*~?vAg=h|{Q!0=}gg}G|FOg_U@$$zM z$oS7llJP-^w20kI1T`G`6i_W@6f^_s5Od}OwhdbFdXpdI!ur2@0dOwg6^nv9qn-*c z^X3u6y76G0B#9Ok5ltD?p7BDoQ8Se5fF*)7%CtMqlKjvN_}-Xsoer1PJ^6B zbyuQ^e|EHY_782U3MiQ8pQ?I!+uCOFD=g~9&nVX#Wv5iQpoznKgn3l11*Hr%!0YA~ zpBx5Z2u~WrGx7@`2dHXxgv)%8HBJ;Wwf)v_Ny;}#fM1Yo8d;)kXe&I*pKT{oov5U* z(q{F>I~kcH6Bf|=fqb^Ey z$~w9MQ@f_%!L<~RRAd#M}R6PjmWD5I(5AEQUzEPS2o@&B<>{V&ACznp_#`5rF+*95>8GiCL~hzqKC z*I~1|L*x}_aK6F`ien>1iK>hY-O#p8i65gp%k`|v1S9YPA*42`$3`B~*wkZnedfvn zjRIKbN681R!{`a~Ve0qrj==hF4RCtPQ7p^6#p$1$;6P`P&vWJJncSD8rQxGlJ+4`C zkSAS2!3!TccBFi_0}z+mW>aZlcdbXR5oXOTN!hJZzW1erOP;m|*FlnnT4zbKB9`rRSs>mAw0+r`{(P`(&UZ~v9dA!@#5 zeEhXcT3^c~@{g`TlK+-7|37{SSyMZ67YivnGyDHlP?KZ7oD+fwBcB%%b19129-vTA z20Kvd1}DO#h0H%5WX^&r=#j?p32TOe1_A#vPPi*ku+y#zoZ0I%_x$Q^2BCLk6(ZD% zu#Dg$s-sQ*MXV?fTz=sbCkb-Q!#}P+$|cnhkLrOfns1QkH<+#D8o)|K4-Gmiu0pS= z^mMD-$j}hyZON}qqtWhniYK06-OqR+L@a0LzY zpdlH4TrX7hJ~YJ6e)M_U^7$NDegzS~j)*YktPt*p37v)M5Igp zhpRJd7;8)AjB-qM#Nd*3XeI5%nuny+;{gl0Y6oe45(Httq->o8aOu%~rIu+tY>#_a zs6o+Pddl0j8!ey7XZ;`0}A|7$tPbmHKzwR{uw1rg@lNme2ceBO8miv7g)S_&pbt_!`hBbCv?L!Cu@w%!o#^(&(VstG5A6!cZZV8l5eWG zBBPXVtV)V{j__x1LWm01(iPYX>z%Vl%<{}z$0GB=$Fh!v2h@yJTE8RWJf0cnq!VX& zVL&RIZeL~yZwU5X#siOAvyt~kzA0a5-1U&vb1#50| ztEdFHb|fMj3Sxrq$g2UTNU6vhJ~hwpS>nV6z0ZP+l@V6;G+-R#Y{8k%6A<3z!9?~e ztZXc9ZsFk7Qe-@h**-ZflPEnR4{MK4(^6grTjT?QPvWhl6{Qu*=!PF^bdFt{Zos$! z14U)k?!p1H?OYH(-Xa8<1F|1(do92qB7h3E*k-h|L`Xyy*XamtkX_77axaAH>%TJ% zb^t;UR^vPZK0T-8V1iB4k5F1=S;DMGRL|W!VPiGvSf4eGG) zAhoz}dg?;*E;^88hMK_+Z*R!zLaF=LKf@^< zxCYm%kcWjYr2=i<6R^(F6~o6HlS@!O(EhV1jkp@|@_ZGf71V!fg!}j6?)-nNQVO(( z&LYOhOv7;Ef%&Ucz0m|opkQs$An^o>Im$N*(VameB&fugPDuvwa9U_B|G1QCmsF(O zL=0VRcR&g$shyPcNnw%qC0}mfg_yMD4A(EMaLaB#&ilu%?aR#y=hzv1FRxhs_Z<<@ z#<*%GOR+G7cFkJm(bmC2wY^268>N*Cakp+=6NZi5_ z4gPh?ff`23L?Ly44W~$XzlH|7OB@%W83#wkL$jMUJrX;sgAiqEjhz{IdH!TvC#cPZ z;r^ZW&Gi;!E=c^kh1@?jc7+G%%ywL29=3L|8RzLq?D@W%iQwn+#xG9ZZ4;8t-HQW| z;WYO=&zseuqiL3kcXx$+c80Ys=V3x>I-LBxR8#E2 z>vf4Vv<>4*s=Bsw$;>;_-rqJEEgPtC^``Vz$3Ko{m`%OP6Xjbbt|Mon+GUBVYCQP~#dCeG9)rI{ z6Ypr`tL+G=%GG*k2_T3D8nsF*XC?bzO1m|Zt)~)`n6>Fe(d1w z*7mtE|1z`xfOn4UIDA_c{kqnML4rJj0vVgY6o*w({@N4-@l*ff$5@}UCBe3o10Qli z&u@s>MCdxTdhf%qwnG<0=&@IrRf#bUhq$+W69Qre_c4$ULjxzxB#CK(`B6br6GaKA zucU498&KNV%pp2#?EM=NSn+$q`a!nPvIoI7QU1n5Y-cmLdi#{CT0>HBiP5e04fqUt^Jq|TFqF_(+_)V)7`I_z68lIBH)cPRX2dd5Qcm&4bnB#+5y>H9^9p`Gre+#sxm*NUu zR!GpmdVL8Amrux4Qmd*liEUilG;2;hBpKCIRjVe7s)PzRDSEuMUTU?{MyZ*)gvOlG zoZ6gfMrm3-yMkANOTx3eUT(EomNId?ZQOGl$yD0Y->KWl-zm>2-l?u$#WU}Q`0$%) zs#BR$x>K7|@Qu--^dZ@}+W6wQ-677Q_F>Vu>v-c0^Wot*!J*e7lxdAq!42FYxoM75 z!;Q3Q&kgnA;yCCb+c@3$F=bALPxj68c$Kt7n_Rk*S#q^ghm)db6?4f(-bUFZO=uG2 z;aAw{4Y#QhS3WJ3J57zoR_;bYr&627GuR<~G6&VqLZ3QDSq6W7xGn@Qu0~`p$UAAE z8lWO?S+FKpAG(X69oq~1P8=xbrg&!t{0>wHvIFS=I(aQZ_Muy#U1;t2?cnWb?eOim zYsj16(ZZJj7r|SIUC=mzTku`TUIKTZKz?8#AT$?QK;VS(gZZL+ zp>V_D2l7MlLHOc%VZA`yNdZ*=C3!P~Il%<}{LsDxUg$4icVa*pKx$r%;LqT0C|~>+ zs5?2J4xlCPQgA!C8`_uf1@uk?C;^Dg%Mla^_JexIdx5-@0crqh@HJ1A5ic3FVJ@iz@LCbfFp1lpaOseS^^6rwr%9%LDhr^M&X}- z2$%?iQo;o1T8#!+DbldwCv2C93|`9L zfKTx0cl-D}KQlkWjfzO(G&kDVLO~J*ROpf5(+O6smWqN8C%-x#52{z-*bH{FiqcwgMs;{cD)b7!m}8=^tGYq)ZH5{v9!( zvaXCPfx=e+PeT_;BJzV2Wh7=_v+_qIkvracdnI5PjA{-F^Xdl_(`fyWbQ6x`iON$z z=K&cN7`?z1=trrlSD~6#u`WhEGK+7!x4gUkS9Ig?J=t$H-ubw>+R{Knsu+~+xs%$Y z#>#zAb#z^|k>%4T@n>J$5%(wk5#%Q&DeF^@(p?ICSYG9wx*M;4Mzz{`t`6l1n+~@o z^+t~6O)zk0Eb@BW8k#nW7LPN{AgV-^0TXTw(#uX`OlSX=dQmJhi7h&|8eaX~u`z6ShN=5KZR9)hSMR9fWwBS3yS6#13r8=#aS895P66Az{L(-aoyQ$uvSE+L{@tl zO0i1wO?t(`=X!8jUMAVDD!kf?GP?)gh1gWRHa6+k*+R8PP}YiWSj$}j5v>eJ!3>?; zW8s)@K{|97Y?W3uG()KAZI`$DU7IINX=8gjp>rSbN~zY<-m33$Oc6UCPOf5zeS$^H z>k?VURQ1WUUs$ErfO`R4g$4dGc_NKGwv{%0@Q^^kS0TM^&Wmv{0aVkiZ^}qVjubm| zn0nq={acet5B-npABti?{Jn$4gXBL(OB_GwbxnOV3KhUGFe+nPqDcvF%hp%L=_w3(ZD0}2;wfjI$$pYsO z&{?LO2WRp=!}}y|(NPqXYf(0h%}&AgMP4ymF<0yXRd5JmpD;G8vi?=S85gz~N~R4m zjohyB&`JQXn^?MpnMSG#LRrfVv^dwv1_MMO2#FT zsn>N7i9g~0^T3;=#KnvY1_F}v^_l&{Pt4TLoo-56b&gWRZ5g(VIwyQhqmI<5ocOj-oHq# zLw_AzZsg)Vc=gwDM)Mi`OcUNbfBjm2W!t%VGs$Vc)%o**3a7tF(fmz7C8TF4=`$O$ zdzJox*BML<>k^mj#$IbQ!U(L)3$kvGcyXteCJO^zE)67>3c*L|rB^d-EL zg*7yLJomDlfIP4_-m)WD;_T% zO;rAyXWS?iaY^_=_#~=l*s!1O?@+#-V3tD9px*>!PAQ`lVLl0?C1E~)M$JpN&CBk& zID81Y1rH)cyG0L}0^XRldV~)uM7zoRm&Kk!V7AeBb%=d*eqVaz5_Hq_KZgCv8AT3z zQw$;uzcNPkBkoradrF6ijl6P3%|_XkLVg4DlRD5Ke$x)x4!hEUJ8&m{(+?6*yp1J( z3xCStyTW{;Y5E}V-R48_FM)Uph4$LXDSS%>rw8E#p*qRl>%~1k$5~s$hzV_Z5QFmC z>%wzTLXzg5FRLsrY(ypFD%b;|+|Bi2UBkF!<3@IKN0!(#LE(z?CYjCNdNPU(y24jd zl`!J8(Z3Wco$l-I%d5Js(;PITJkBk4t`%TM$0mqDLb5rJ9rT9ou)#M$^4Xm2_As$1 zs|pA#2ZH>0>CcKUG9yH4;yTBMi$!z~c_rrDMylVROJ4gc!q?smLt#EY>>$WXKol7m zs1|(H93$>nzuT>8pVof;lJvJWlDn1jiUHQ!zySojLN_1TJ@~aIUkML-h!$1+Cvsv4 zhr7d$S^p81d_>a#Nqn9I@jRq7rb1v&RO?Nsh6Ia4mmwE!5?_GXqWS@XTu z5;awxM{@E;DkNPMDOjbN-iRlqdZRQZdX@2Y3NDW02R5KZt)VG$vy2F2E2-16>}C67 zgD&c};}?s6?ix#yOjL|on9JCQloNpx_{zz!lax zj`@8r-C5%VMO0;l}288rRZvtuw}BN%hENCa{jP_ggnwKt83P||6N|_!{=dq zYi>a(ZK5-C8 zxtOc$)mM)=4ukf!?XWKMKT4le3Xn-5spe<{cKeWSWfi7jc@7IA#2SV z#TUIGb;68yc2ErXR7%a*Ri9iiavvyggv};@_4E)Az{ZL*K#uRKl?(6{eL!mq9|irI z5fL@?oPg7fALQcH-^&!$6@?(ZqW5SS3=hgvD#R`av!gjqBc`p#gHgZnO`)sh$diNY zA3LFvwW4y<$t~4xulY6x%O;}IK`1kSv{0?;Wlvp8|HIL4TBJ~OkCZbg*ziXd@yr0r zdfjikZ1KlkJ}W4+s6(3KQdM3Eb^<=+!lvI%$0xKi@L|=Mo|y?FRm)Y)zk8G88kC7m zvBE}dlPa4*zKg};K3WMfD3YdnZM>Kv0OD=nC9en~Y7!dN&tb7fqS{lvH-?9sUB2NJ zqw>eD!;PDD;3C0hYV3`3X$5%i{D5|yxUimND2^HMe%?E7cr2??2te+IzBb#ee{&Sl zq@}QX=Pnw7H~Su2pybWx@@&us#5*0738toJ%d{dF8Bt-CmYX;nRzqQ=lvaOt~DE{=pXPVeld1i9(898+H%zTl)X&D&i-DqXeZHk{*kEA<=GHq9yBc>DI zOhw>K-S9mLN-qUXXiZkKBH}ya?{guqL?ML3Gn*Tf@d0+Om>aVU-hi7@IX~=iWAMCIX}p89m5PV8cO??v3*K~6%&j@e0I-ohg2Dvf}SOaIahA02c+X2n5!z0 ze=Rd)KT2$wpL=G>6MxYru2dKQk*d)vALQO1;9N4!x-|9?*ymvK6FjcC>cF-WI?ynp ziC7`NK_s#t!{ZdD@Sh1}QO$tXD5XahwlF0aj3Z3Gh}%wwRv5h;7jYbAI1qvhzEMmF zokKHtyZSJwwR}S^f_WA!HAEW6nf8*2@dqOELmcMCX-o$C;853+ozVR(?K`vOLi9U0 z9Hoh$7_ErY_)#N%A)@ar{A<4l&Jx=JGZ!xc!lMbKW;UzmI}-D8(WvDN2A7$ZK~00r z;p7oQRsh_5R4OUUa&4;&qx587gd!c3mHcHiq>>N$xNWc^DcX>EekbgfF36C5{tnyJ zBn_4{)gSAwtu@ZRC%Lp1jG!`BL%W|)#T&oBUbgg6ipPW6jIps+f0r>EZYiM@uLZR^ zW2eD^f4z8`3#H+p9aWne_&~`itOV-pga(NrNBAQ7>OfIwOAwhr()T!;p5_0b>;}bR?KX{H;ume zz|##Yp*%;A^!dge$Ncp8hTm{e)mgB3;+`GEw$g)?zAg3sF3~sDa>Lpi4xW8=8+x3j z&KJFwQ4j`iL8DYYtnS#LnCX+cR<9kuat)fq8r&<8%}0e6yB`H<{)C zQpW`|hUjTQTFP5FF;K)sLy4+8C3ZLPU^wBOS@jFomgJ}_*z@?h1FNU$Dfmh!^k>6H zQeuYljVagjP3A^IVvXhwk%d?5`b}-PXK6{1Pp_apVy->S%i_sCljTh^SDPznE*c&v z{(09Ge~24=49Qe9;*FYTt5>7{sSH6iwr$+H?5g?bdGTM4gZq%oP(&}0EsM1ZgjX5a zSMmV@_CqAAH1CghhoE35Se;hoW_*YZrA~dgDPUxw+`?`tTVXun^?aO&VzGS8h#;l5JNgVq?GTkE2}gm6@82rEx+R!)VVpguAFk&f{En zKX(nGSxDZWU|Usq9}t`|Oqqdf7B6uaqljrVa8>K`bxn5bvA-F5qQ-#YwiY5X%jkCfeirEMpx{4bxUxWzX0P`&fFA_nBT zU7y-DQof>~h7f?Av=^LqJr8}n*%Gse;REI8!4$M7Q=TB}tOVMxBo8wk+O358vB|c{ z^yhBp)k^_>Ur@&1=F9|f7atnI zHCtGBlZ_!h>_;X_!>~S$9q|ZEti~~2qAEw0j4{kMbst-6?1>}DiBYR`M3Si{Y#RKZ zn~h69xiO)zJBt~y^Y6C`dAc{orQ_ETWGP4{sv5h-0gUVkcv_j+X_8SC*f{%K6&AQD zeAXr$Z5d9FDt{G+$5#drBICJt?ve~Oeki1=xuo==%@lD4+EhC^^XZSx;)hS8H3ZqF zovDcPThf|~jXtG}-MWQ5ML3x_i(LdoNQmm;2YyQ?hA(I*Si}edRP3-$Q(6pA!6Kt0 zkW}V^pTkY(>-CF3#xnK;n$0?y8)fC^OMdxNVu_^@>6%N!(dX+3;Ta>myDm(Q+poc9 zc-Tu~X)yYae9NW;T;FkKX`_q$g~&31DQx4@I`_c$iD?R^W*C3Yw8!tlh|DxOMHhCv zGoezzheLmYV{n)t{Q^>t0}f15*(w{^O<}w)qNBv{qr-}s8W&>Fit?wZZhtFLVv(_= zGBB0Kk7@Vb-QaMX3%wK4^KnPoyxJLzu*$ELXB zvQSGs5|CaG((7&muCPDad?C8woRRD8arGbN7q7Z`-1M)hBs?yW1w7n|Vhcl_6!(oRP;cT|q*EMDPlc0L*VZK9w{szty4n zNq0m(Lq`<;vu8n{=Nmx#CDJ(|{Zo-n)=SvN-q`xzDOW}6O3t_%7$4P*bJc~(zRUHb z>-7n&MLU$XNjpZ$dtX!_-3r%AgAU6@kZJKXPHCGl$&6tn#kC~t>|4o@;t=M2=(hy) z68z$M><|t=s5h`JEOLYCHtp4V!;qNksBY((ucSV{W1i!SmzmF(fz&S>!7XEmBUup4 zmXSwB9^Nod>-bmT*RS3RLT1`JFS$_sVOLenvYT<(`BSI?<^j2_((7S5b^C_){@QUx zbBn7BoEpDDz{2deum$Y~w}~umOI17gFn0(qn&`!*;(4n@ocp0@w$MryFcJ4t{0^0@ zHHWOF&IYUH)20_@E_BEMx|Wlb3NYh_=p(14vdV=eZTV)`<*B99>iNobn$@-PuD>P> z%c-~WMM^~lp6gAR3vKXVvl;Go8=6gLkq`G6p~^gu7+_N_gn3wf9Zg5@TY8mE7+N(ac@NC&LR6mYz4hHkLpdor6E`_3s_%R_8VYpT}Q+$LA<~gf|Q5Ac+N%qi7s|teZtW={TNF zTuf1Q4833J6|p~ZpV`f4+pb&Frxz^ZG^jfAa>#fd@Q5Fx;Za0v!uo57$FSpns{5QclI!$SO`(9I@~zlpENxfzEV+zc24KSWjB-fXggs!F&`xK-xe9HO?$Q zypcn{3JE{HW<#^YW4T6*0=63O>20vp#UP@MIQ9?nJ-je=o9}UDqK|&q51U<wPE{nZ?n%#qP)h|?A5hr~?2pY$ zBkhPKfH7RdqnS(2xSg0w2`m@=O1zt;e>a`$SuBc;|2U_A=RK*xi+IeUe+N6+*LxA0 zqiDaoOpxagKIA$a%cQuK#<`<0_#@vbUV)x<9shUA2P&Sc3;gGB5BR5X5tW1&bBL^yWjCJ=b;bo4z){3uvnh?&}<>imgV6;=%jM@cbfzY-< z;PE9Y}dLj;~(i$CM&`cxSMBIABQVf(e zH@t5q;M0vVp8ldwM3a*$`S(-@ZpE@tRqgXq+>LTJI_ut^7FT3d)hqmdvz~QY`zy!m zb+23xB%MIDp5VmB(^tvW0$bzBZ!5>Fu;A4Sgw14@I$nHH$yoJ*pX+K5oO5db5 z*WTCnFLav%+vwdqP$~w&<$mvM-SY{j2Z{GQ0b5(73%{pF@*m~LYB-*+A?G1Jh#~Cup(n)_H@FRP-^XmQstt1G`L;RBh|C2{pPmb}5 z+*Mgmb=%xzU!(8RtxFXNtH?<)Azx+=L8=^K93_u_ASfgXCB!6&k=7fQ zWR95CG>VZrF^G>@Y=tHkP5PNPk4ypWVN9t%ty6GC;nOL_RF@uw+$5NhN+$^XJ3lFt z#V*=6k?$X(0o4~jr4V%bHD1uc&x78R7~jF};O7)da(0hxur(7yhRc|Pu!#}Oe$Sf# z4S}XRPDMk25fo$KG8bP6gh+zn?=dVBEF(!KgjcG9Lf&C)C75km)OqNQ%DRrTA=mm9n*RuDUom4wYQ|*Q;!qtXVxU8 zQ=z0TQdC8P5j%vFI)3$z$eBwJynzk)*qvEO$(5j-esE8~m z#h^E>QY@mGc8p9*lijhQanTWv@CEq}mEd&_ldw`mWF%(t=``mcYRNe<*oX$4JaLJZjO&bPcm6+PBpeYWM|8EE957fa`IbYNnJ{ zBEif`jZuMIdNa-n)pQ=M_1fh-dtB0o3Rt)ex&Hn>8)yjLI1n{CJcgp*Xk^QjHB&f< zo0`pOK@3#7w?z(lJRzp)2&u~0H5WsTdPgfEMd@ea$(E2EUQ>;l@z@w}=k;~QoCj7I zC0$c)$S>or>#mcBh^Z&{-;~_cdM(*{tTvcfCu%FSCDK+kwG5HkC^dWKAO|#6uP{Q; zGY_xW?5J9!Q6X`rsWgR!{rluXn1~GyfaPl#uwnRXV|({e0jAkzSf-!@BbHn(Rg?yu z{TN7U2vVg~jCJJ((SbvTS%J}NpzjQ!bW>+=r#=*yyS|W!q@+Fty+SI2v3L&&i0mhJ zx=B0KE4}zg?vRBsTdsXh=`FYZLj(4x=UH~Kq8zc5(mvvHc$ReKTz=g`k*jJgwv9siXQ$3KT-tPh+5;fdXyp^Y!`DSu3|;mn=@Z2irw-S4r^_WskHq~# z)hsH@k&M|f>TPTlVa|W)oBn|a1#Y-BOOp7_8}r<^NOf^)tg(#C1HBIxbW@yIM#^{^ zp{=yGtb3VajDN8L!-;01PRz6dU*~iL@p+!NMTE|oa1hPrv*&A&rZx9!wf3A-cvK^W zyg)jDRNsLTEaF)C+lg5Cd`WgwYZh%y{WQx1B8v)g0de0g78Asi3wxh zh3SF_3vTn$?~DdJvAV_RX%R3|AOr5WRW}RjQMVl)Y0TUZ4oX>W`7!Tgg{Ieas?zxklb7@aF>6BI6*%wWQD>&B=m1I*ir{ve2Jewo8gWo~C>b4FY!emLSGkoCfR{AISf1dkM z%fpa(zwXWXUnQ&CeM`s`V%t5!i?6{lz<4|oz=&i{J?1!@mCO};;8+rzfY$EyA?G^B7E)*mXT4) z!-f>z7th_t*|8nZ=hrjHpm_ZW0ml5Jk{*3m{;|Ea0fm?(SQvw8y~<#CmgkYmy*Qidy4|TmaVCWD42u`v)1^8PQxakP|8Q8^Je%U>=-J^gw zJ(HEH!^Bl#`g8?Ti5WS|W2dq*-4`3`f!;ZUkJ)mi7MB6;DvhJI$Z4VM2VUER$pi8P zNFs9to|}CV6=Nwnu4F0GktWzaH zR9@Yxp?#CcH6)Uu9ww0lu9)&#y~!Zmv|zB7W+A67p8;4GVV7hicYz{uuxiIPU%GCm z7Q|R3JdzY46UK<=(OhR+npjKJ3Js$1Iw%^^g`#u-7eSKT4Z*0Mbr z)LoPq=0>>~Kx^<^zGdeFqt3I9zfhS9_^RCe?az7X0gD@KYS^+8L~BO+5O!PdL>cm9 z7o(GJu26wBnpqW7Dpa=xF4%GH(JP61RjO0bW8|s&M$I8!xD=ZfC~JusW{W0ae8;SF zptAk*XvUuH&(ka}N-z}1XG=ldz0O|2I_fO#w=rh)1x4E8F3U{-RkukYHlD~A_-@R< z<>_zCm?HA1%_=J-rq)r*q8Cuvhtxdu5B&OU$1ah18mglQ_b&#WhawdOt1cAnq?>r2 z6lB3Sh%x*xe0Q0~jsoRvTbcOkw~Bo+bFwS||G-wOC1`CRCIRRcmF|>%6WoHy;u(bv-|727c=Z>GpM~;#-xt zKFuC64mv|za;57QVk=qDt)#!LQE?>yP5#NNHH+#vhLL)LnG`sO|2GEe@Te^C=8ot* zWm0|V6K34e9f0ra0gXSK+acF+NPdws+ku;R9`yHD6g}i6OYuiU|3V zv%@Q#hfUk>hxY$jigg`MB`v=~UoOc0Q5>zRl(@L4rIV?#i@lTQ|C*$mG!cDNM;vpy zyb0YE6iO0zq`2jwv219wNKJbEhQUxtiLO?KOCRquRbU*QU1iTWL3f8W-{qAl9QptFt=P_y+HI`J&0j7R|9;8wy)2~gkN&e=6Ogr; zp7lS!boZH`&l|L;wy53+?U5hUlAtfvPz=l-;eFd!cP)2x;05+)ckbN2Be_3(mvb*? zClEfeJ-cUiFrYqB3JC!fyk60L0?bc&Il$KOZy!=6gxIPle{W}aUoZndl0W21esNGr zfA)?3y4m=}^kko<&tXB=l{YUIxHS98c>OJQ3nsuPy$=J*IgTz@O%R6Q+2g=G7btw+ zV{IqTY=Phc1qvk;==TGGAX(7dkeSjH#Yi#4Ry56g;#sr?=)ypPfuLRsOb)3PB`Z+l zEjL3ktc^&O2-MUc_%Tdd;Zmm^$DD~R1K(c7l7VX}jT~c2UNR@AB5@;^9`3`<5sza% z9#tYfC+8c!+2O5>CbD|HsR`ibOw0bWMoyWOemZ$@YkU$Do+oQ19LM=X5_h6FWR6P+ zw>p74K}Igd465YMH`*qZ8=)xfr0mI9BUfR?v4vfcO4aQ;DN3`^^UEoOYnO0Q?s+tl zsAPiigOc|7wylLH(wsi`I1Oa85ejET`ov)N__0|VexB^EFe zPLO+&${Io(`Xf*s%FpPuJ2M2s`E<)2A=5}l3Ix+%k4TM+6`nP?-q?q?HqWDLFZnru zJm=5ob|MZ>=EK0m5ro=Z)5Xc9G~vlTj{4mmBmg5b(wt94b~qm;5$$#u8p9gzXt1A- zQ45CJ+$rOc+oS*ZriDQlHt{{cNcO_7p~H|I(cF;SV_lOOn!zxi=>IVG4pE{7TbJ%h z+qP}nwr$%yY1_8#JZamuZQH86uj)14ZM^!&?!<`3Zp7FlcEntBebfA?6R=}mDX=we zKOiozjywEBHC^|bGJeuz9trB63b;D%GSV))lv+(O_T)WXS3~>XDx7=8_|{=}&ZDyH zrM*L<3hmz?Lq}!#WaeIVOTy%VXc#k5*pp+GN{85OwMSag?Ni-@fFf_CbncnOex+i~ zwY|@HWm!$1M5(}rq7TM{gyyy4rjD5L#%%lj?yoJJFPo1K&ewg1)DYsv=VmfQ={ppP zE;mxo_q2UF+H+~wD}#W3NT&SKCc5}b%PZpo%F*@g(@T3&PS-1|o7LpijrP!N=bY^> z;GKKmQRw#kmv|Z=Ws9Pu{ccN?)r0_O7mx4UVyUv6Ob1;8@Uft`c}@dAX}? zf2+LSnfIowCh}<($Li%!aby!htS^Vh=E2?DhA>{eM7WGSYbv_*{yw}PzF0j<2w+zm zB3n;kbWeTO&HJj4EfrR#dC5VSflR5boLP%DF+?8)HN`C$o_o-Y_f?gXRvWDXlzQ3> z;db@83Ts1`Ct>2&AbA~FiiMc6HoVPa4G=$7{65=)tnJ+@U6sS}Wl9jr4NBR`PEvd&O!WG-y-`^7uzdAI*HRz#qSwbtk4I+~eiaWUF zB(fZ`k@_gzOc9?IOBS%K^HblMyu{5$WIpABOTRrX;4_fti78H*8DKPBGu^{+Dg6HKAt^TCXK0ppJY7l?)~*uVCSWJLqd5?kB;n5v~{ZYY^BB zVD97Y4RFrGu2Sy9}c+hhu)o&K3{-WI*(gDz;7S8?}=XDp+Mh1uV6f{p?*xOS$}cMf~2;?EjH~ z>qd0%3;bbK(fYc@w&@!I*et@Av&@+OP=%|KB@t3f$LH@ zRK>yFM;ttQBep;m{t=&4{cN@@e`+W_%lDAlGPFv0R;Z}XRShm?RgWm90Gf$2E$2r6 zQ@IbF1ZdOLc-z4zi-ogI(|p|mq}n@wKi+Fm(YtOTqVBQd%#VDm3nb^y9O8u6oU}TN zQU0XlS1H~jodKUZikez37#6AQ9lscPc%YOQVDC~B0J927au=e`P7j|=@D=4rkBr97 z$kZj=&b=yOTEFBTSbVPE_;gxIeB#-kxUR4}@9=o7v=1fq>=9A)u%1XW`FaVuSU(yK3E~_LXTn|_UsAZyTnU%Esy1npF zT3fh_B3MkKtfF+1r^a781DQQt&~$5u+_;CxYf9G-G3~F@_YdQdJUk025^lc}^ymH$ zisYPNp@BC(Hu^waluDHyyO_ZbY~E4BwTafag$F_qq|}x>!R#cJH#giOkEHN`2EBTI zRzXGBv0a!*qed%$8xR*H^SBcVm7Ky~YTP+YLndQ}w<~E?IY|tvMy17fYL#m~Ih83+ z@TEGaX4Me!!z#x%M*_m6py)J7nzuFHItHN?Js?dK^kvdqO0z;p z^F?SDD+NG>0d9JiTn_p^<~k2x$3j<6H(>%-Gz{jfm?|N|);ZmG43q$UiXqm;1f(h| zuer)ga6EeYJXomZ8nWM1$f#9gB%09^jr@ZxfgN9=uPpg^cO5=>Lh1nr>gRI@&4gb^ zVtFI0MfI358YvN{ArdZ{1BY;5tP3_<^K&93fhFpk=#3WHftZzpq-HF3n!iLr3&sMS zqU2PGqwaggGsZdt)LhD%0JO1KCU_o{8Wu1msHU4hubM0vWF2>`E~FU`BPnAt5FJxDLy-}+^C`|_%_WDeS%IMS zyF#6zq?U_@Q6V3x2mj#Q#z9ijd4ze9_);50B25;xY}$uY!}h(}K4>R}@G9{|mRVj_ z=WZ$RRmONIDzOESZ+{&(G!kI5pU1?I2}HxNuTDKYW|H6>JI$`Yg|8n-{5_Lnq1IMm zc{?@_#v;j~3fYV#CkyWp%TB$}g933eJ}&*DVUZtsG4AXJMNr)F*TvaI@M`TA#fxhb zjV4%m-G+itdu@?y1pnC%51CiljAadZ_6ZHmd>!mp-^bGm$i0u^3w%xh`$6>(M$ubr zFlAb|G1o$yXsfW%3V_oI&D{WB51iQTP{oQJp()0lYrn=Gg4*{dG_`UNW+BT7rG`lt zQsdZ{sHx5oDF#tE5tx89OfEJjqkyjw35n4<2RXp5PJzaDG%f4quk}Q&BA=}U@hIk} z^9*A{JnJ8vlduCeE4(Mi{j0r92%@Q$$3m_OiL6l+drhm(bj1Xf`a)AQ z@bR|wHi*)Pi<==dbymdWQ*8gYb8d!o7WF=hYwutsDhm}7BbgwV4!PzL<3cEzH+LqC z!96=_2t#fb?QCXq{m=mNp#uTO;V7Vfrn?2n#4Rn#=oxjcYIskKK$hVj@TZ>^ z(>>f_isQR|&bS%zg;&)1XaUWM);yQ4EY8v}uF*TUfRRET#B2Lac zxYa)UKjkZ|{RSPv%$fB_&Y=y*F~{@bXV5Hcq5JW(&^A`bS!d%LK1o|4xfLpa6%hf| zMaRIrev;ZD_D<#R*iHI0L=?rDC0GyLg^E^00R8saT26^9DPa6`U zu9w~0{IaQCLe}Y#(fjY#>&!vqbC1_;xy)zDh%GWy_Uu1cL>xpjN`$i&IFjNZL8%9I zco3iMoR?D!5tEaS>r@O28O_w2>I_M0$y2H5>nZcpQ<$O*)z3~*K--lDG^!|b1~@*M z7Gq!ve7Fonn31iep&#~2h_tUI_BIZyRJ=2&b7F$3!J92ov1@74sd_ABd1MrFDAB;_ zuDBH=pm#!Ltw@lZDJAKPfz5u@NI-ZSta#}oG1>`Qk)EvN*=J{5o3-fXIZ^}#@sbDv- z4R0BL?WS7pF_zyyZsi@q1hm_93Vv3uCm$PEhN<#vdPSoJ=pQQY86VLt2t2;`RR@_d z_l1Ib2hR*E2Gq~!Ct?`2Nwo`X+7y|yPFWoi?RJJCoTza7HT=()R^abW?xv$Y@4=LwwfNwSK{ zhz;B$B#EB}3y?MeTs|`+u|Ell$Z0?-X8%Yor3l7ju*IvFh1=%OODU7swnXb~BWmYp zW8nP1m8)zO9VH|+bRXD`%*3E#okeA|vL;pHF;YylME3bPl|mlLw#d&##+Yus;c}9N;Kw z0pj{`bVA4j$Py8BK7Cw!n8Zwu+nkjD-N3 z!{}tOPUX;iTluY0`ADxd8YUB60{B5hzcF++9p;Fmsw_sylTlaHShRgh_RFp*h^G0* zDix*$?dPOb|ExGo3X|$mqDh$IaqyV5h3M2&H#;xVh8;N>QNduHfHpoq}J^wmyv0QKh1MQ9WbJe6^7v2w2DkE{a4Sf|+{G%qJ<_%z?GO?AQ z>u{|s9_@&W-56seS&$GjC3n;fSzz>-o!A{q>n)16`FRieA4y=5`ky@8J3%&py?`@%N>>A`FGHf=z!s$_E=nfSU?n^df?#3 z?Z&!5Jkp_-2^Xp;1SntQ9{Br)B~F0U6*DLnh-s5JelVISk(a#cI1O;hyKLps+ij@a z=uZ~au_hzV$T9ptJ6og5g4Zy73g#$X%gQ@&tnA}YH>u1_3zsJF-faM`MJ@ICT-nh| zWzARuCO!6uI^AlrOMp4MCoz57Yf_W5rZ3#sL>Q*6i432^rOG$p-?+g7wj3Zb9(Y{p z8vG3^TdCs2t0n7yBHh!My7;J=!SHykl*3>pM8c_cpWYc*>yS* z*8$dr-`)`qFwaonmtfC}S07HA)?hddUMPlwjHIN#p~Tkpg#|srZqkyJbE)8iUSlwi zj+QmDUohEGCBi%w4pd~;e@Rs~jnqBtM>cK*Ut9xb&}-4=u^#2M!Gp!4=QzoEOIyJO z??8<71buPv{sM%5DfvT_e|!6?zCz6pTD=|@)bhLS$%R*xUE$|JNJJMtQSI?KTf+2e z0@V!z6>>=DJz02#Z1o$&<+G0 zlez_w*DgW-BS=vHKq~bmpD2`p9oY#$@S|T564SDjCciopB?@ozI3uKL8GiM(PL+ia z;>2CDN-K!ufVxb^#XhkY4xKZ)Yr{km{A6)Mz=?vx;)=qdI%!F5=s3x`Ns#+_xIp_u5B~P2 zv5$g}xzB=9w{RaCWkETscpn?(q`Z}XNQ9DB;m+evq}^QY#3Pl|5Hw4zd_2=0WxSR0 zWClH&pANlUe~Zj(48)EKKE2EL64FUcZmaWs65#shrx)tl64L@xDKOLnELsVjZf-_S zU+s7!APm{ED%<$mk|S-8V`EoRbIQ5;DCMmYQA4xCl0TsFK~h9V;Xj6xG`h zo3PN9x}I_`8(**~as-x%w(*|l(SC7pakL?a)Q%uj@xMiA&s@2P?`gN~Wz-c&_WHK= zI$jk@yWQHSrYY;QkI|_5dCPKc|H_hfr_~4G3k&23FL8VpzTbz3XG_L&DrIgQ>W$6p z9*XL9ZpKqbcf)w#iH z1o{cNCh}#u+K;8VwI3R$?+xPTk@yz-;c12{H} z02nrO9|Jm6UnMbEezk zCbPVGx@gF3$Uw+M$VkW^F>6kc5CdW92h=isLvNa`2WY^vZ8Lc35&METW`+55aU2Fi zQE6Z3?6Z^E+du0!7?ST4X*ViA@j2d*A+7^wZ7N@geCT8SNX^^iDWtEidB1-_elz%q zoslC7@tK4>0~hL;Lcv%A=o33^xldr{Ckbu`BkE}O&p%h5kli$!vr6(vGkvD71ZnH2 z=B<|QmG4?pTK4FZIP}RlVU(#d?!}T)2Gx0F@-E>FWd?KtVgb10Q%ahHXP=TdN+WGB z-?0S$K!!))yUJqB`{@^ip`5vQnE`&B%!iTG#2(YOU-lU~ho-CPO5pSlSSpN${Wzm| zs>Nn&qs;*=1`Z&0h|K%pFu$QXXCY(rhDK!Z4di%3^gkhTUR9qoq&@!(qc+Z4rnsWX zQK|N!+?>7MZKXZKQ*%8@nXgsoy2%}IGkd`xh@>+bc!k;;bwV12*p1ii zS$vh&h~xYYx7O)iIoIwkzP|i{&cY1goo1wG&f^PQaoEMY)YDR-_vL%__dVW| z6$^e-Ah^m<$cSyS<^?z)4N4g>VoPzzOKAn-oHJcz()P!SP+Eg5I|$_)Ll^EF?YkW3 z9az&$2d%>uGx8lhV+b>RO`@*XdLx;q%{yc}fJN^AeltOxG4Rn}~Iq)#gr< zOC65pzo01)%n0yAwut7W_#svBVFIaA0#{#{+=h5M0Q z<)!;e*g-LXQf}kAGSpq!1hC@V1vW+k6D7{GCPXP0d0!eBYpsiO#s3k)%NC9sd;PYcuqGy=xtKrb>uHqDwEgD0A71t%MbqCY5@mw`bt-`@rI zM*y6{)+VLr#~&6*>BaZg!JdRaC7JE-Ljm*#*5nvPU>DYB15<-A25)q*kM30fbbvL5 z=oG~7>%;bU0F;0|UNc>7yWT6}*MUVcKgko%%k9;LO#zpNUnS!>b^!b!8QB!t{R2h< zcm>SK-YTL`=O2jODWH$%FFTg11l-QvDwCJVAK?%F850HO%C-ggl|$glVd2kP>|i78 z0sICO&mONU@GVX(P$U<}haiE-K|E^q+s-)#e$NpPBn%664-yYc_!aP(J}N*66z~}= zP8Kg9@GV&sIU0wrhaiE3fqHl&?4iC2dSyysJu@HUjY4NV^ShqGSAPL1-3>hb{UzwE z$9RZ1-3>BL^c5jDFhnU8S9O2a7E-q0=ZciMGAm^sey1(qmL@v?hLTm*7kM$|s%a)!Y2BfV}t!JMIM&I+k( zFWWWlqReTTfHr+vA{0lUiTKK3zuemhMgrr?!y>A^#?F0gJq6X$^-+x)8I3TMX*S1l zutQdg#STM?yskkq5=*y)%-c+S9Q1|#LI4Scq_X2l6471^+Y3A9HTy$ZqTC!Tm(EL^ zyGG!%RI_PusgsrkSIXb$($)J4R}+ea%uM@Dcu}gyo2OxH(Z^Rpn(0<( zudc)k7zu+(J0Ed4h0>FPgmiiZUi_f*^wL+L?)p*t4uy%%x9M5JKiZjk3b~=SIB*sd z#_4~w9yvCi)7buaZEbo;hyIx#E&{miZ9DLXBeo@MBwI(CCKBxvL*uR?-pAv~S5Y6E z6W`Olbe)eksf}ufKYqmBf`0-M-XTH2As*Sp0I}6#dsAzKAHIY8&#>kH8EVPi zescQVQ>6hQ}^6QP`_@A976%A~S z?QH&YvlTEhGI9Dp;JV+?X}b;M-_Ysz0~hU=$Z4bEG4eoxb7<}Mp*j~BtqxW7M&x)! ziwh7396EQYtWk&jg3=TQSf$c)iu-frwup0_j_QuDO*-f(B2JGVG)JK zLzT5)i1eT#lL1<~vUquaY#GYX2FRr)p@%*n2C(!9$Q!0C`73aM*(8p15H6|6a(tcU ztY|!~T*l9xuzY4E1qFp?g%W%mR82|9ZjOm+!NV6aCRfJ)#8*oW<( zee-BK9v<*K6`I!4;4b=1cHq{T%gkJcdn4KV=)DF$HX8;?u4s9L`V|;&acLd2S?L2c zfYj{AdM)7H0?!^?=qlEvtk?i^TT|{0>%&iu!gvETFZ37%EPVG=|i2{)fcL%iFAoh>=F zhdYD`A(yUSo*(GQ{3fLMBiv)?5_W(U9-6_uafln|4o(n{kShiWD(G)|{4qsoo->+j z3(fh6tFaL((OL_!gxOoJoo$Z)wIBWkDJ_$4dKQ0fl$QbRv~IsL+TUjjSz zHjh?Bi!ajp2H3gGT(Tet9hRqaiB2IhYq>Rv*_^y~BM0{usCRTU5ndyKPR2pU!6u3z zPCy)TnO+P#h^CR&SzCuo}q)6tCS?iW!##^03V1wpb+F%G#pP8m4*;Qdr z6+_J-X$uwEk95IlxnjwYwJ+kodRQ|Rpgr_qmVJA7R^7vojQm@K4^WjkSO1Lu$@jl3u%Tlo zxvjsxD;x6vQR|zUg{`2B{C^8r)fy1)$}2A4H=Q;qZ1ahOK%i4~=vc&4b&zs421eAK zW1?Z|6%5T6=5fR3b+e;|ec*;b(e)eawu%`gG^-5%1i5VlG41Q~bdy+Z4p=1;nWYjr zGZ#u^7TPTqSYb#77o4s=nH~ly*UvwWYff@JuQ`4=UN%c-WV$(F^E6fk8V@vpdT@i_ z@WAl{;6Te?VQ68|U#IY)y~zDYUTJ7~Cx`5u?zpr+Y0T!?+SJ2Qb#4qmL@%vUJgyHxHw4qD!sAvEYu75Ix=YPasuYP!2;J=p!)I~3Y( zO#Eb@=*U{97dvP#p}dkN?*p6k??;?7WjoH5^tEhMcpJbymo@)pca#mg)ZqSCK$k_^sB%%?1kEiXEP@uz+?S(UvneQm7M8PC%}glGbgRoV$XM` zK%p}eA+Cps=3~V`@ARB}6Rd`G|4&PRuaHT8(jHNM3-eAfex1}S5`#~kwX49kNBDYZ zmd34}bot<@eIE^G~YWby1+B?@*P;8ZBQcI%_QS&G?(illk(i zN~{~E+m;~PF>9j=zU-OOu_!>9RQ7x+vYFLocRY=JzRYtM<9Uhm;SVhkXw+QILkwpR z(y~9{lPk0Cd=_6coatHPCM_-3)GSxLJbh!UfwC=)90ugH z!8y16OPN)wE0COBO8C_>E)VLdeTmHm!F zuGvuO(ysiwB|k{MpnK4vZX5i(7c9#r*#_Pz(cKx@dtAsNqa1jI^V2ztz3-EMsALR- zx%&o5H>v1f=KK80yRG)o1Rg24&G%Dp^Z2*gB01Sy(qD16S?|2P76y0rZe%|H;E;a% zNBSP_hiZQ!q4DkS=eE6~(QfbU>$RJei4HBU<-ZAGe+~K{>AR?S~R}Tn1@_eR-ht@3(%H8dd`h+Zy zy6!3<-TVvcxZ_1KbH6-E(~sdm(m921J-)E;od_bjle3BT*ew?5`oFsQFc@i^ODmSJnU-K8MW1k`cNN9 z+cO_GeleO*apv7J=hchKBIl#r##ch2!-*r`Nvat9+v<_G9blll&yF%Ja_84Id@!JQa+LTtg$kfq7rNkTcGENwv z;VKHsCpGzA8P2%k^|?>RU~Xev)q=D2CR*)eJ?&S3LW)&ZC4U`0n1ou49UEg9B_#<2 zr?UyWHYDIi-zG1SSIl#8VM{i(0I6!+5e(ERheg8lT#0|{XkV@dASTmSN$VA9(J-fe zy`txDx47p!S7&aD68Y$#Vk;=gtW$G1Vn?RuQ^BG%dCh0U!V6ZnHmXU9^1A*W*wc7J zlBv(8r;ojV_Mgtdt4f3?Rf#gHV+FW0Yh`feVl`n(chzDL+j@Z%<@Ib}mYkJ1FTFsc zRpuV;najnDwj_ik-jj`cmu-xcH_fq{w=17X16^m=J%K}-*f5k6N^I8q6ec5)lGnB_ zl`Y*Kz#jntKV%fhyKOR;w-tiY6GAK|S^4_FG-y!vKd0gy0qOxA5tg2G1jQ4c)6ow}=5PR{6o*yP zH3|*N0TzwtmAMJrO$PJV&jCeXO5^(Hj-L$_1cb&)!CO1__2!k8?mS(>fpF`nuD$uS z4k`oOaDY+$`0^{fc8dJn-bWEQLS%niOg=D%Gu1{Wn?sZZRM^1=9Iv7R8^1)9N7Y6q zPm1E;SKyW4uOD^YxBsDwa3wFu;eX008f$vuuCOUh_O0@Z z+rT9?7H`ks8KYlml0Ip~<}BsOg5KD}%p#=>?Y5Qs7jc0hebb2T@jG2ruxbgtVPNB) zt5bZlomwReUsR#74x|IY8>D0N=En5}$g6E?HE*2WaJF~Z7z*dCLY(47OX3=ywkV&h zFO%Y;a5NXZV7l+e5TGAmBb3KFTD$sA*Kd0IF;PYj2|jNCc!?*V!2Xf_g_%r;N0+Zx zC2EYQ0xS;H6;P-l9>7c^cBsFGkrHV z2NttSv!n1-hC|J8Dhrw}R5lMc=2JBTYx%WRtRm(mmYr6sKs0KQ4q2@S>H_;Ib{J#< zX5F)(x~gU4^!S-cJb9Os6_RF?=L6uToz~!FH+t}QO?6FWvbn}l=CYPB<_4Lxr$*J# z7pjWzTt5eI5#!cs2zu@^7bn9{E=!>6EVmc1FMAEug*c7~!=|ShT zDlc^I=6xHW{ZP3Aug+5MrDX)kD6J`cVhy5F(vsLfCAGFZ$RydWJxE2V9q^@Ef_zH# z0{k*)-MT3jAG-llHz*%9k5X)d;tcKnep7m_fXJ68sbO_cu@>dC7Alu})OA(>v@CNd zk7_&8Sgt!bH)1>qN6y{$h~~O_^L%?8io#iKWmzdJPgKUn8CYeptTH;9Un!PS>~97! z?bJ%@apCsc2)!T#^3)2sT?gor&Twa6qQytbK)atgOdL+>>g|-SmrR|ix#s=;( z6Hk?yiaJ)a!2TGp?sSxpd**HU=DXWW1St=%>$dm1QBtA zh`GTH)Vbk$s*e-Tg9Rb_8jxiWaR-UH!zA3{A0ScL;&)+aYUVKyhUXQi4`G`&>$k~9 zW}WFW+kkO(k?a`ED=W9EYwoO@MjMt4XH>(3ST;yv-A7N{M=z+rp2(`oq;MYOXP)F| zD&@fr*-*OGvT7-b-wpe{!>lA7veMF_E9~^gOi4^Dn^EVY!}7uR9@Yeczhw<9$cR|Q zkFzBZo@hSN`6vbqX+A|TRk79KrA?%lW(=W9-2&;ykpgO?bs?~Sh+`%YOlU?Lw%l{E zu$vtK#hx~yhO)5=Q+^6p5n7Hrl%Tk`|0a)cp?T~N696xr-ev%jgs}jM>C3l~nm)$Q zNaBT;0ndbSUb|>USCvbYs!&AGaG@Uk-p8sKBJd*h2JWY7ex$2q4Tl)e4yA?ivklu> zVnYj;FA)~U6_BkitO~i^i=qGO*hGPi9W5NX#>S)M3`k@ZyyNVl`TtX?3HXbeEeK3T zUy8^XWpmlM#N^RNSTS58xKw{Xg%V6LC z;on?Y&}q{F$mwE7eh$rYO_jpx%!OEe1euMCNo2~erxZzi{vj6l;F!){DRJro*16uC zGWf<%KIVQRP;}eSq%2fC&)skgz2+n`lg_fi z8SMPCG0_db=dwR(7y}cC8eh+ips9rpL%NefZ_eMk%c-??Ocg1~ z>nz?6hxd^47+v)tbEW1yCTKV|&qIQ^X!RJ~Hg7224-w*SlVX!xg4|`Lbv`VzClztZ zzFwYs^%#nN;sSk}vrmgmdTm`H{YrU(q|uKtYi43yVNMGFRK2JGtE7`LH;x1E0xC^7 z`y(uqJB{MhQ24 z*#S;`l2MjWQpVz`_fN%wdE9UmUUZGQ=5AaRDv8&VTkG8v33KQ`d#z9RO*4g$mL?ycT4p$H zH>k6<){i$9A5IyVKtc5E8VV&Zo|#&bqeoAiYc9t?%P1}AZ?3%s-t4z_R#NOtct<;q#?xI;4G@?$jp9b%Yd!o~HWt7{FmjuTspNk+fK;~MU? z*hB)1TB}KfJK%eYkwx3NPCr`~j-^KILl>0G*N&N+#>;dqFr|Cz=pj%^^`e=rSz0T_ zV=5$>4p~*)SSCIQTBZm&u!4uTP`?(DTAd_sXj{xGllKNW5LeHk(uSOn6IODmG>M(3 zcOA-twzCb{P{YUuJAivZy7Ow6&6IdnT2@z-ho9)rPer7}vkuL=z$=l`$%pz)4yY-d?vxU_?tm3p8d_Y-xfBuUntdY>5=KLE+{P^pkVEMnR-oH|q zgpk~SOJUh+AD&t+7(ZD#RRMfZzcPcp_8E~=t^l&}X z`wXbD4HsVQ$ zfQ_@_deqB|^I}p$#e=KU?uLf!^)ymiPxYN$4)pQJ@>VyIYg#JYGGI)N+({h#SZcwn zM^Fbi<@MI41!?Ex*EB9sXoyH>lj`+E%~T9I`<3llhJaDhcNx;GE;8M9R0@dpW1euP zj>XCZsiUsOulge;m2cM6Z1o+?%9KTj9qn4p>%ct%s>I6a`^UnpiBSkCe-BnE^LR7m zh%v^R%7qzK*u{t$P;;EffSHqKI5o)=W`ah$i5S!b>NlUgGz1`hv$}+LvMY5t#+e5Mn0_3hKWR{HlCEcd!@n&J=bTWSwD(Ure;__H}I_L2_5 zSRXWLX?z0NVZ4%2eamo9Y5lO!RgWygxhsiHXvn(1EZ6PSe3v2o#(W^EPEw(QQ2+Gm z1cqBzC`)qGw3+JCgSw`2vrkqi!CcX$C^8>OAw!0L8NG#n0cr|mR$zfPUx`O2w;SCs zp&Y2kznCTzH~#T28ySgf- zMPewFELE}=nrJtLCA?`|whs{e2A$Ib;z$)VjtSvGhny~;M-16IR-5S(} z=MlxLBxbXmK>Y@;!`4Nd#=4}SG$D#2RZmR8VJXp=5mUjO^q1U>h685_FeQbz+~sYs zj8@cYsLTMvpMu(|$+>5yqN!11QN6u5M$`p2TGR>-Bj}8#rxwzZLU;$8RxCRLCg9`5 z*}tVez*XMGIle?esK{T&h75mRPBIfMDq~86Wov-xNWc!CUD61UT%j<_?;TRQ#Z6KM zFPK==gvoeJ*y>RoBs?aANnH%B)~MQUu<5#*qFtrPHQGZlvL)DTccWhsE;2jR!AG5N z4Lyacn5Ybjidmkddrw`P+cc9dz;&@*Eer0BxnI~Wn_g@H6Xg-N8&>$$NE*uFWn65N z%8hBv413~a4aK^ycjz&3Q`KpM^gX(P&;Fv01vi))mx}2IVl+`5nSM$l4WQvKqKCe} zACXDtb-aLpGsmPHbH$OPW0O(N>E?lGyQyCVxlq}K4l)~xP zam6V0WZkMy8Zv2U{vz_zjMn z@mcDzK2$+15G|s`g z2@bEh{-Fl*h)})00(zLQnFKe^7dnI$*~IvR>LNe>PY`o9o=|*uyhiguZnUe!Lir!UzVeIi=!>+_!p=cy(=a%JTT z!ta?w|17w;GwM4L9VhU0R!8nwLO=4?u9?LF%JxN3*w(^_V`$&W`kJ;9YxHh33@hl2 z{>o@y!qBGoSMTj=&dnW10GvzZbYDJRwAQ-6SCrOrd?jzPCExGHiHrJ9?@kTcKCQV(+?Vu5K@oHoY`+zbb$j(+ z82S(7B`hOxPsWg&c?PCPT5Qad|E(0j1fxCz?-$^hdYG{AL7bqL$rhfSR}YLVv&awz zVwqB5EkQk`!o@cA5>Voj$2b#MsEV-+G*vQTmf_%){W&Neo%ghde^I9H#vI0=E)i^` z6ggf#se_L=XqB~TRLXCDgod40w=y@gEA~a&imF?m3a#xa75A_zQo{Ef^`bE~2hA$g zbnjYEK5FEOGo3w_m4xf~x*zu2h`6z&3U2Av_Guk;XI1geVVfk}oS}-x5bk_SM^fdA zj5#Q(IdTu^Z@^th8-hY{(Sb05g=;G|RMx*DZf#d3Et@0_Vi$w0RCFw*iYThy7+)`h z*l0Xd1@%B%C&s)PET2Sj9E_5Mgg`#K~Hw)lzm~-hu)xSTql@9 zAYKcmQV#!HVr{vhgK8x1fh&25qJ~h^w7>37b)@8xu1we!Aa>uV5_$U^siECphlwC6 z2BM0?XMlP3UTsS$sp9-X`KCkD1Q$16;IOfAsb*~V3QQX&0hX+g{28a0 zmL#UEN=|cMnBb~s%11$yZB|;Baqf4lHiVB+nyf_&5S8(s!WcL~GnJ62a1|#z_QjoqJ(Rr^_V4dq;e(=& zwd9ySt9b2{--JZ7@qSTj*6Y^ha27Med4m^hsfv#zzmEtsJy}UXSjwcbvXrFgk`Qmf zJ*d(h0!prw2@a`zmAuBV^0JdfSQlJu-NeDq|Cb~J3zdM!_X|We_-)Fd{r@}0+t_(n zSX&#=iMX2>x%|Iz!^uiEatrdvKD1aDs*tD1gJ{AI`e0NhbkL~?Nb@Z96Db(1WW#WU zg;P~$>%}zspE5oQNcb7AK%WZ3O3iSjmaOZCS(#3`4_Qt(f3Iisc6|Pf7LFRh!XX-` z{4d7dGAfRsTN@?81|K~5z~B{>VYj^doTHRG$(tAITSI>EDzpN#B@GiRsE}ri08B`*oTZi;e$Wv&Cl4{f_ z$f%-W&DQO}`tWn^Xln+N&suN!uNqXTlEuN(;Bs%#`nh`~1bNVD0FB--l#&z0MqAaMu8yu zav6J5LWVd9)ECrH?fmYBXya{LTodwG+Cxawd55SvksIX#;rWX_nr@=g0y&xj_}ymR z?Z2*XHHAKcy-}w$t7%~U(H((uec-jOPMI-1(WaiGD)1B1|K1gqrpFrU-u$R^0B~@u z|0i8hMh2q#hG_HO)Yl2>2LC}9GmTHPO-xjo)Ok;(1d1vvEuUS4g7OdvCJVUgxp%}8 zn|gxXrUvc*oG-9$=vt&#S7wj>!bFe$!nbAN*jKs1M|QrvA`6cad(tg@o7vgvGA8Wt zdUtzX2}kRwDoLS66nYjAX=G`9uS6;mF=ekOiGxc@(SMFYyLE@B>f{~55N$<-T0_d8 zlKjtX+bLknCHez?BL{cfiBvEYgnkdPr%5$#C!BXLGO?_#|<7j$yqFGo{d8&!$ zXvm;rt}2+Y%C@`M3USGCMUOVCUAv6eI#s*AoO^D189CFOKXbtmUH7r!rQK{PWVh)- zp6AO;F8^?Kn~(&baD%)S>UAZ9%2mWZHe<;CRq4AL6X98ySr1s%Ij7`;C{sf{ETZ+C*Or1&x&V!-GLr zi6ORGNYMAA$>Ub@8PzH%D_-68lW$Z|;uH?yJRU`76q}mu zGUmI`Aw@*;GO{`^t?3ZZZ5jD<^~!lJyqBlRNphvgtWL1EPrTRUjqdZk%y^|3JLnJo zLG&Sas`^lrQJ}rpztWn1t7H$`_a*g2KMj1>s?yB&F}sy?95mMuKQs2P?gdHqvs3K3 z@}x7W-yO-Zt*TX?>BJ8S&g8cV^T#TVujRzN&~vdE6NHgxs{$AxR$ojc4qJPOKto}Y z95H{$#l4+~b8n^9!?1AiH?_=3vhAR{9g^z@&`K|7O&6fi;ya;uR0j!bO`#p8=o7}5 zV1N;vuc96J0(X`dd(<}IHDtc+LGYtJ)z}>N8ni87j}~W2Me-YJJ`VZMAzePv#}3(K zs;jB}b=kCYB&w;n2lV`0k-*a8bAsk1dj(lM+PY|8ZiXlk3HewJYYrSml_O8X(JEt*dms&&0qtK6%g}`rHDBWeT?SU#Z=6M>3TXQ7-G1qj2h{~}^*)6@ zA-t>=_GNyb=riiP$-6@s90H?12U?EHKOz6`AAm3Ve9<>=wC+)FAA$eRBc=bDixmDd zCux{DyI48c|0f*BEN;|(N)#{nZ;yLAaiP14ud1x>Z#$J(d0hgv5+F^4Qr?TS8(y}3 zj+KD*N{hQ$E9ji|C|1k08%fJw>kLi^Z!`K3fv!HE$mid3v&YHE)BWQiuRpiYOtq<~ z#0s2*NnnT>*_RI^_BMa*PEW4%9a>nBX3gn&yFrScv98U2lz2Ld6FXwdOYI4(Xbd&% zPG2Jsk!&DG<Cw3V{jqce2(Dbsb>cGu0*5#U9gR27S4QcK;mjKn3%Y%lE3fjl2` z=F#9oHbV$K2j!6RSNAxIM$eR#Dt4>)vM__DzqMBX{XMhv==`x2S@`!Fmq%#lR`lN>u*K~<e+Q>P}<`RELQ0vvdX6i;})<`bvpuR^4erihF$qog*cY;G? zgFp~+%Gdn-$&a(YvXnZ|pBF|9=sHC&m#?JMY}S7J23F`B%Qi!4&Rrs`TjGp9vQ+9s zoJT09dI^n8&Rb!B3#&6#+MXD%=g_Ra4!Hk-wuLd>A<=IA8`}HxWxEhRgy-e|1(A!B za6iAy1_vis4+qEme=?J3DM@@$_-|meugIPx{nelR{Q+@G+0;sb-zr1G^yztV5Ew*l zX{FVdxQ4`Xdd&&&Z&$ycwx=l?3x*T*hi_f+^;T!}w7)68bG8O`2U26^oqT`E@pEVx z>Esj?Y(ysgDdp?A-WW&S9EnUwu&iCSr0=L{)MZ$$rsU{RoyT++uDtn5r1Zc5`wKi_ zc;D36wB8Yzm3xQQ_CTY*62k^PV}c?BeWT7mFbAOTk24_b6i6<4Ck&$izJ#68K}En% zh#QpvKM7BSjZ?s@=p7xb6G;B~ju&PEhye=)oKZkush+SlWT1t>Oi53q4Q=Q@V5XGk zyN%D#B%r>eC*p=WbPlL57^tZ$~7!R~AP812i1)^fh13m+K0W<(}XgDAq zNDG!1qyL;7!G>WZ5QvzKZiEDrM___6Ll*!pz+I{liR73nI3r3J6|@&12TY`5me7i> zdT&GnV}#BC9zpZKSDM|5{gX)IK(~x9$*s#iLq0GnBta_VN`XXIx1Vy`LHS!BTg8>Tl_dO zm^9FlUYrDG1Yn2qiNB`|2Z8f|4V1Q0mQsAAM#$pG;&kGK;=JNmFl<0Ozz6UQfCmU- zyHg%X&IdVC!;}G1VA3E*W|$M8ADr&*NClGt(1Ftf9T{O(fI@J3fFmtT8(;xW4|4nn z^8)+>>-jrU!af5C!Fqv?3@|f55?C+5@eMi%pb6Fsa%6$I0p`HV{*GXn0)Q919O%dd zvja4OmjfK>U|#`2fMj?K5wSnh7H_Euc|#xih>9V?OXRCu#!2FfazF0aImiaxsgw^v zu6n8;JOd?mvJ?6K*pZ;^KCSYeH@y>1^kXA>3ERn%4-qlBCyd{y)Or~;^+Xu%OhbSD zbCE^c-C5;5ZF7?Xk?V{#82JfmQKwc&We8m3bSDXj8dH1$LpwonN9SLMBG>iKS% zP$49#%6q}o6LDBmA!M$~d&SffewbGwq_N6-$glZ& z2*Y0#LjG1gEt}rGA7)husjPZhG`&L_c2Ed8t$JEDy?ZxIp&*GOn>%JYZK7?+L0rX^ z8i`xQ%tfT2MUWENtCg4>Y0gZS8W~@eu4kE^6E7PSg~5^?lGyMtG^(DRI9@=I!Bu~9 zHb182B~@ml>s8m;*lx3=lflJ4zcMpNad@x>Jp2%=(5K>LYx7mEsko-O*EGE(o>rZA zHk8NEN5Q0u+nPghN+v#`Wj3UxZ^$~{N#Kk=;X0~(Ldk8b4=H{kC%(XRu8}7t(hU4< zj@KTL63HoPZBCkiWKfp$yAz*Cie7 z9>*!Og2STaVa1BVgtA)#J3mnSWwpb&4+O_V|F+;q)Ns zd#f?oPR5Ei7YB3gu9&OFg2CrFin)9k|k)-&`hqV zq1wyE$4tV^uH_y6dgR1bTLGT6HOwu$z|qmCWlpFFKB7R0Gj>M3fd^qOIJLAp!7Zx3 zEeKx#N|LIho5M1iJ@l~NG87U59JNGk;FgpaC4qG*6`&H?q><>Rao`SSUb^Pf+8n8= zHL|Oz&n!2wr=HjlY3w_jMxr=a2h!UD5Oq1oQ7!bgZP84Q<;%)T$}mGxbr0Ug&7u+~ z_h=t2!9beMfdoeRzPXX1TNBY$kjs>+nF5-m&HGu5jf1PbVC%18S^P@cd5(*`DU4ES zS(>@H-P{TCgq0~V+|7y6*IH$>-4g`s?ep&|ZLF+x2usTPv?aSmVN1J_tGiW~=8^4e z$!b4zMNKogWrK`|sI8&+oA`iL&zh@Rkde+vK@iAfy0%{9P_i__Ed%Q!pgF1;mD*+bW(*{Mn{MU=t`MDweY^I;5Z zf}Y7}{*jZW?qI}gfSXHgkZ==bBziT3F&d3m)EmKwm+?@ICufxb-Tx~rLq%I%3Zg2J z_6MJ(>fG?#==v3#>CSys5XZi(rPiO(LB&tvkK(J*BhBP zvLs*VuO0iLUuhc3HYPMEDzH%Yg-hl5zfcOfPwHB55eRz7nBWVzYNZ8Ph&Rd2PIDur zh?E8}Eyvo$#-HjR@hp}!@pTtX{M%*XISQe2Jv-|H@j`4uFm5h{YqD`>i7));x3~BS z8cZa1LLBYP(sz7nmb4@|63*s*mnDbB znS5YIV8ZVlj4{>ZM6E5IPd>JK$R>`I!tMknv|Am^cJqC0KSs0VFMWyH72L|}jmV$u zk|X8&p6{o%!A|L zLiC4s3u4)Xq{C@f5NGj7@QhnTV0QT|@vYi&(scN$Xz?~pCd8Cvbv&E~ffbrhIiZU{L%5U+Q{g32_6RJnm;T8{=bdFC#T^XGN6(a!sE zKX;uYt>7us2~;`5kKj4L=5>Z}B^D8UPrCGlxc^lDCT^{doMl--{J_*5S(@Om`*ZP( zyqDXj*MYUWweE9wJw^h{`X3&Fy+50Wt~plwW7!IShQWYQw(F40^8P9I-wi&_!nAR)vJW=fGM#_TJX(S5;Mj#-H|Ba2z}$4})2&bArf&AAFC)s;e*&J+LKm8_zoJGd z#CA(!ey-WXhmBk1x@(7lB?L{#;H!O`xd$xtrdF1wmsS%jPs81=0_trsf0lQh&6b>L znC}KY=90C3iIoA}W@9PT{IQ_URk-8*^%AX=tLmXCLnjnjD6LsO5SOFt9_AjRV zcyka_9421=Afl&uRm)dlj3(?>Hyh4ZAhw#2_@fDH9?r!*z8Hgo`I{MkxeJ;teZDnP zL*Rpbj)796^ZUtF!bax5!6%65| zsQNA0`ew|-R^y|ugl5w!WbT{yTKjd5=st;&NZn{DR`e64So@T#oeZvIv?Q#vO z7HT=h%_!TcWR4^*TGt*pxhY_-two~gjH1F{v1PCP+-b2)lug(MT z)%&@cSXr2y9j?Dx7%cVLmlsxA3(ISAa|PcqSU^VAvQ=wIw-S>7-m$p<>eGHwu52(7 z?F07}%(h$!<9VB@TQQBIL&0AhUPZB7wDG@r4-FI)i{Bo@KM$UKbwr>i%O9I3fEI7? zN8+2y_r(%)>!zT5rkC_QVKANZRG)6h7Fq2={-AmGy_`Xn`&~)NU6`Y(%`Rh@=Db!$ zN0IWP^`yE{K4W@C1ikuCS9P^wl?vFW|7Jq zr?SAt47942FzmP+hf$Zk%btlo_1yn0Ze&ELXoQJ*c=HlU5^JM#y8Mu2!U3VPnnl?d@M1 zF21;cY$Go6TE~_S&nI(lBOP&f)5Lfh)LC;y2enm8MTZ%A8|o>Yk;Z~5oXuxS=4ASh zqTgZ)r7SMYzE!GJhRp-9`L!y;Wobu8+|-nvPPE#8MMIsUjf6=0oRCT9WMd@G?_gc& z9X6&G0He9X2aW~FR26Sf)3JHM1hJWL4!K13Ovn-hkc(qtM6 zlt~X^#)q*nQ!y$l8uw)22O1es?OQ1fLE^QrMiz!!5$UE(fG~Dt9+d2suw%luk2GH`kIRR@dKw?l-5cYi;$t>{d8`IjPPEqn6Q7-lXcwPvmZy} zD$-J$eM}?MQ=7w0b;rtvN{q_7%J9fB3%k{|kEN;QB>3JE)yVf2Hi`i27B+BzLmZBm zaqtJDWp?ox6*Fl`1%}ah`8RJW+A6w2+@C6(;H_;#_vDd*rf*uquDwx4?2-jjJ_Ni5 zeDPm~ITS004h#ye`rl~2v8QTxF;d$x6+pE{XO|hBL4_W+%lMD9&UwS zus}2SNqb@A}`ga(^*}>quN7904Z1qCf7vfw13!of8GSU~-rx4sXs&s&B?vvHA zX1kmtGE%mdzh*Z}Q7kf4CL-xqrtK#L3x5EQJj7oYCSMosf8jm+JDp`Vo0TgvS0+NS zA^!Z?{F&xKZuDXJS`(xf>{zeu7xki3`|{1FQQI%~g|_yEpsfW%eI=mWV`F9oAoBTb zhy0239BtaU(|4fE53Z7YT*Pwr6^Vpg91+W;Q{;D`$o{MC>?{5u`NZo=*(=@RIekRT z0fmGo=>|&ei^#((l~DK@9&8Hm7bNuKj1+bWUo#DWS0hOS(h%;i?A;1CD7Jfzm zTLQeXlKnU%hdlx)K;B_z*sy*;7RWo|j0pA*U;y$CKf{O30oFj?KhDTtw*V2)6F><> zxd*%gAOesOz%T@;I0CLXwm7>uN+6LD6^t4>2v7yeVi!=!OC(1diqT6X2e9FGMX;e6 z@xVx-&45ebE|^(VE367xO<^zU-iQqC(t?(*TBW~Cy01uQKumf;MKn}$13SO5y z19@V51AL)y;)pOEs1Npy#5~0Y926d#5xWE!2$%s-LW!X|P&a@P6af1M6@Y$&B0(jg zw9p`6Dt69)ULuf(%2s?nvI^0N3DyM&1(8OKhd6!^N2iRA6_utM05Cx9087}5*qBoD z0pkJV;p4&MQR6|52;!{b6yhQ1a>T7wVxaFQ_P(FF`EcW=w3e4%bC3I}Q1}GpZrMIY+k@xu z(5c@viF1eWsxSSz|`)7A)4&-m45?~d6*Wm~7-p!(__gUnZY%N(eP zF7Mhal357QsQ*}CE)XQGyh|gkr4v^C!h90lt)mBfKq_s^^k*_{oIxmYWHA&gDJgCL zO<3mgUHDVdU%T0_L(hj!P4CII&^qVd*=&UFUG?L*19Jwn&;zwkd* z^=Y2XpNn1fx4G+1VjvaF!3Jlk*3tMfK8ftL$6ZJDtG+;(GYe1Ey*rBi|0cQi+mSbB z?XKSrDiUC^Hy&LMFoa!~Y8$ljVq@{CDpLBwlLO&bO&L=n9*<8xjNB5FThxFP&<%63 z-17w25wg5+WORo%6q>v`T|R|E9wJ`<-MlHPJ7ls*ztF0>rwMKp zYTYjRu$!l?oI3s)l91Q`Yl7qcOE5wHkM*E_nr^g&X%kLW%<-5%vgY_<$de;{7Q8J^ z>)(x98XuI1`e~f| zH=ggSM#lJhx)sVQ*C@Kx-4~1*D*JccB?Ns{VAI zEZ%pQ*8=hDoXAIL7R;Cxp;|ZHr1l%%2M%TL4gHo#m5Pr{=Bj+WndT0i__sbauI*k@ zZ}Bw#MJ(MsYqtI5hZ8umqv!<>JZw}xaak~$>$m+_C>7!+*8?K|Go3t)9h&d*Xv{t@ z(Rlfsc=0_x+J7!Fa(H|W;~j-`fnm`S^2(pjACd2FF;7B&@n(kIo9c?Y3wl*VKc?I3 zH2Q-Fov$;zS>oRQ-1G?}Siz81BbGq7g|;1e!s{N0dgQFT9j!g{)usJopct7@inz=* z8#%;ujMYoR-xlm>vd|b&WQKQ6B9*EvBH^@&Ivr};0T~J8B>iFNhhsGF* z4-megurOB`yPGw5*kj4von~(Q*iXU2_KRM_IqOS#20YpGv2gXO@80x6hpyt{x1G#r zE%8~IJ~a>H-(TnyKI|RrMbo&cC|lf$W{q@Wd^DZAL*%I3aac;A{%*3=FzE9P-_G$$ z((uhTlJ;Bc`J?_S{@OVb^t_gB&h8!1elib|8(<9p1 zHv8_&R5x+c@w~AJn;m1rRnNnNyW1_T;QjZ{JJkU%$HMI^8N$!SS~;Dq9$1*#aol-W z(%+UIZ-3EkullCjJA99G`Z#tlJ3|P)xxs7EuCR}Bb9FfE*8frSjcir3qm@os@l>a( z5m-HyUZxm+azb{@p}?-m*$xUGm8(rvJtlMZs%h6Wzp1-6dtezpXl$*8FBm5|>}J|S zRJ9k0yBr-j2rCV+iDsF_RueuBC(9j;>;0j?MwRB%x;z2CHP-7xYQb6BS*3Pa7{Gty z;8+_ABGQnyKb3WhS4=Zp*ff&BS;^SRI3h!>o{Km_jDMtj8?_z{9zzW`6`29Px(0rg zq=%$52=juR`=UVG)U26u!Dv%!&LBs{1%b02PDi(zm{?1^-GsF#6 zv@@lZIYYH2$FLqieh{yVT;{835OGL9Y`s7f0MwlPG7~L~4yY zKeLKBVX}mO*iX}!8Jr+2JbJ<7xlJ$I^Sh?Fg=~$$_UqhgC~MgajX;hPfhLa|x~RRQr%WE?%eg|EU*}JeLtsh2%{dh#*|r~1~NU*vH;WpMj2czmvkd6OW#{PHcIOS z21%Dc(z%a^s@yFaB)8Kv2l}|w1Q-PGql)~dqY9Dfy;CQ&mb(o zg<`A+%7uOGE#TrOh=OK7D#$w$M1gkU6zDB9KnJ)82W3$Y&;@x%fU;0Igx;vn@Uuj; zy=W6Gl)ZQo{waBVO!%kl^(lc+$%`_9P}z$;AxX)LAt6cGi$6hA$%`gIQ`w6NoQk~!fnKq%1xQicwPdEdtZ=^V&PR4=CfTtEv=evuvz$bzM2eb=8P$$#CX~0tl z$Pex!H1G*~;O$}2f&3T;vI3r(L4NNpk^-Ob2Mo|IctL(l18V_K?VwkUi zlp6Bw_hLN$ws_kpAVSm{lx-@pk4Vjs*iq0IylolK7lLgDP|`sm;{{i2AzBUMHiuZ| zy9;@-hCm^?Sc@Pbsn~ykLSJJ41+|IB5(c(eCbwzDCi%CyCb!AQCIz*L$7%+)$;N61 zwZ(vzMD@V~JD{b|K}<3Cz#|+}-T`v4o4_MnQ;~spVij;+<)Blv3*W#tkpX1DMF2<> z)fs<4OsoRw4XcbBG>3X25ZfI{`p&BzG#8-9I)DMOJ;uax-2_VMV5EWHwO=R8(CTzD4T zT#8Aw=rXiwcDLnJL-B%GB`14SdHQLeOW^vp!Lw24DW#Ui;j|w%oUwrH` zScJ9&vKab%AZ7gD+GiNM)p z*bp+C^!~t2p%{DlIg@A1CGUm4&xPw(x&^pOKCni}L@8wgm1~wtuDae-I(3sqj(N8$ zA{@f=Gk!EN@y|HTB!>p0sf?m~JT$R1h zG<|Dc@Cu&P=$kLxVi9N!ujmueWu{7-#)3UGKghKHw8^5^{#}VkHAZFau(SAOs&?x4 z3p#!CjbudKPEOVkYAE#&*<^+I-9NGXlR_Gd)W|MzoYxlYakbea$%drn;tMZ&|CYNp zFjaUrNS~YKAH9^Ej~~T2dWHCT%YCF%t64SqzgJ}%99;rkWgmT;{{E#)AS^0Q=l_^M zc))7R$Qlw?S{9^%E16J}KaB2pcBA;4KAlZVS=yI=+t=R&W>1~<#J6gbMM3v*Hyk04+udH~KL~JYezBhPb&Jm6bQp`} z@?iBC<9;Jqx;gy4y@&;J7#X(x=F7j&Gg#KNMSdYgD~(Dcj1-RYOFzbgT zTqpF+rv&L~Pt>Bqd%qLmdQ40s&lGf+vKIRCGQ5VTUgUo-ek#m+valXXI?liNE`8tr zCY|PAJX%t`=-E+JeE9e22{FILpd_y)VN2HS_AoTL&p>RDTasRoRpsI}woaCY|4ZQS z`1P!Wgou|Yy;pdyErZe#&+8`hC;u0*$vea7atz)sAI$C4-8JFF@@EpA(zTNE1iN&b zYheEGwc`BW+*`6@SgLtR@7rlN*)TN5bA8w$K1j1i>1j!FH zIAx@9H&xk&^$(rA5S>gZF%NmN-bcjpGV)AGy*ll z*VUD+waGeA!+6(12MZC&? zd0Lk2m@ciIkx2d2=d!T4KDQQY%|L2b|ut<~bN@bcChWkH)Y~ z7Pp9NV)|9uh(?#nV0=>bLL-({$_S6?@}fDz-d4zYMGHO#ahYcf{i&K-Rtelk)naoJrm{U?n=%L&nQ z6lL$64ptp*C2v{0tP=GDaQv{CYt&v{dXn;DzjfyfEpA zi=VERAO_mF&1Gr=$;sk?JgBs+^T##}kqWZ77B1$t6zP%s<(cxXxNryuJB3UPQk)Eg zgJCU|G&{hQwPOUBU%HMPJAn* z06(ZgrClN!cpE>(9m@=kGi#24Jl$`jV+i{4|78A~)H*h8IEYH6{gc%rBfl#y0aC-U zCj4THkyu6*r+yy|`DfXbRYn^(W9=dNRJ5*#ak3aHeKVMJ@ONtcBgxPCixuC$bL$n{ zkQ&-G^ES~SnjMHrrbCuqQ9zF(_094LtW%CC?&IY8N93P;7d{Qn)kt#bkj;@>t2r8C z@%hix72b(@C0#hYKbtPKol}tHgdm%PR)dp7%q<`148xP8+wP?+#zi;5I21ogAtyR1 z8Nm}qnk9TAR2vLVnRWgNsH53d#3B=S_{1+yOyj?Zm~qcg?ids3*1fsj<)CcWh4@?r zG**;yYpIHH;iF=#-M+{0y$cAxAWq20iZN%IC>w3(X)+j3wc*yPWt})mbFtGRs7(#Y zJLD=G^@Y@CrE+`QRm>TVzCddI#_EkM2=&rP-Y&g^xQP0ul5mfcN^>EXIDBzkR3J{* zkl{*bmVk?q`S70C1`xoQQ#_PqYd>MA5NjEDFuP9Sv|7@YYMoQ8VHvTIb!Im43x9Jh zZ*`YuC2_NB!>=bPh8Dt={Y2^5ic~lQRETQfzWa1VMqE}*ld2OO(upM;S%7YNqUJ>= zKrt$iqn)&t+RCMcOPlKW;=@@)IY|Y<9rV$aD;#U(hW`1Mqn#xp@d|-|!<3rdDZDR6 zO8u5otm#2ao0etu+X|E#vTX4#udC4iJJ&LRIJ|)Ub#PZ7W0nhBLG>km)Q7l?Kg<$y z$YJy^x7L(A*XX^KZ*~B!ESGXU`lyv}aX>+^rOtF*R02)fBR6%LMvj$wBhu|?ERqcm za1^N?-HwGB-o`hdQo?~cHR@hs{T;@vEmf+*{q1Na_fgs?q?Z4-7&qpN^yP1)R2E1r zji2bNWYAOt&Zq{rP1BTbX;*4c`0o1C<1Qt}<1R3VA8MInI%>^6>F9R0)wfnWpL+wQ zk#?oWCMy9Zm_8Guf@LXk$Tj#OJ78;Y4o`5{Yhu6kgp+ho=hpf6gthYlDc29!WCg(U zED0hps7UYyazd8)pcEcBCt18Fe$1ME^DNZ#%HNP4(0UpN0977eFrt z2~St%<;8XLn+3YbP|5CsVaE@I7!VHZ!RGM0b71&ONdWz4NO3jrSpj~6hidVy8U$(t z_`)u{t1wyAA*s5Iu!hn?kp09Ox7M^G@KQto!XbCG~GEnK&yG;(! zn^SMwHyJ1I?NpzR{jLZ1WKH;qN;qxSp2BX?J*9)r9MuvR-2i7Ymmeekp~_*K zU;X1cDg}oh3_NLM!)MJI&L~6Ne(4}w;5NH$#$E-60EAMDqBXG4(1Lq^5SIyr6h#lF zB=3Q_n1+v7t)%7qab~Jf@%{1!bxKDhMu!=(xbKUi!6jRGC4(yadp7DwHrEkJ1Opsi zRmP~w3PdC`=MXpVjz@fhQcdo%85D?{_*lKlSWS~grIl7GZumiuH}=5o%;8&2;YEtX zBr_z$MT)c~@t0<39U2bSzXgJe6n{GJioO7?Szxujy2Q@w#SpiWRP_|xsin7@qKXt% zhmZDz&}t9JFf=k0)e1{1@k0bxD_3U>XNVwfl4Cs(11;@^%7TtUO7;vHHH+Ow)VTwn zx3ZtrEQ&va{vI}IP*`e})`lP0<}6Bw7YmuF@A?K8?@J_1=iPMIO1oqJ%*c&(YUMCR zo9Y^4d6e$0@izU3v(S8yXcl+UPs}VBGccywI8vf@6OJ@IMptv`qMJAvY`IWK%-rff z@KI@?#xEsV5qg(y#!?Ntf39tRGBc)bjUD(nvM*VKo>hMa_t1>Qs}FgL95T&6Dx4mw zbwL)f`lUb~*KwAJqrGp~t45V3z*F-h{AjbfVj+om_6UcnmHIYSdyf;n!`#41t5z z9&U-#z3?aU{Y1Q&CYmK(%)edxDp97hZV28o<*Apc0U8TYDrsS*_~Ivub7w5}3=EvQ z8oJDQPwY2 zxdYn?h}K3EoJ>@xlmD1zoMN3}p7AM<^BW7qhNz4lyjW8`u?nf%Ut3Z1|9<}0VJ_M| zXb6g9pGvucz)ED5!l5PB+OIPdo+NkI(fQT6MuqZ326s)qhnK0x?C9UlY;@h6PN2+R zj{LIe_cuB$5-sAaDOL<5WyxYaoPCnsXxMAwJxhP|pEy_Gr*9bT$%jpc@#OYn$F}p2 z3{wQO*rNstoi1i@-sn*i+sf1i9;db&aqi1e`3-j9r`=iT8su5hc=DK3v=60 zARR%^PnFb&{pRcFGH4xJIRc1| z=Dr7+)cS_>s=OIGpA6JBN7oq1Lm3$nd^f1(7@FC^G3Q{|xx};&?wn2G{W7J{X284u zX~p!TypcEqVF8B`F<}8`7c7{Ac4QqUPpj3;_5-E1nJpB>rI{@T<+8cT9|f(sDh%aE za}^XtuDL1_Ww^O25QV9^Dgvdnxe5!{R!^%N>ils&Wqmy`9_8c5rj+%Kz$BDUAMa9t z>*9#Gv(82av`78H&nb>@gqWt{G=wF|z-@7ETyec3BXJhO**8$`U;yr{OLjEMwVtJ+ zIG8Xs*%3&XngZMrN5j==t_naQX|4)KNo%eOM$v6{WW&YWfMv^DZo!P?!<;C`pa_I< z^0Cda#xR~6zc6Z<*Z)VjO*i;$A$(JsQTT@7VkGqcLn8k_;WqyjCGo}B`i)2azo~Ch zQPgnfi2FO`aA=|D;ULD3>WkKtVO=sDV$}qBA3^W&^eY z5R)(AS3#GjN=S0RlJjq=FP{OapK_Tz-7%l&z@i*=-tY;6TUyG!MG-%lB#M;~b z*m(j`)3G|py~>#5gT|z_n97wuX)r%XBo@S*neyj|ftg72ev%&lK4%Xz!Bz-M0Eigl z=MYT<0zlCcbySN$u^#K5^Dtlz8MY(@NEU_*RED5qOSw|bf@Y}{A+D5);^~omh)#@~ zTs`|?oInezG4b>$K4hnln?gPJVQ4@?s*mD&kxd9r44WK1J7G*fO{xlUy{IN6Czeft zp6f6Y;34o3o0rO7d^xfS(TQo3rw6I$I7|@eL3JX&95o--gzUt+`KjkQ4EYRZ2a~%4 z=@*3F=-V38VQpP69)VU&e`E*+#^@gBA+tJEA`x2U{e4FluM%Yoj@7^}{Q-TNFpPxL z7W=b0QO$K?fR8#pBm2IaAc4h!QCFz5xXoO~O?X8^tT90fR^22*YH)z%hQxfeQ;ho| zK=5NZYnm!|W3jdU_0t35hiMftg|DikZC-9J`x90>FG`jPo%f!MY0L9k_~8vL??Bso$aEetd|v!*DR71suC7<%)()73Zm{G#%H_CgvB zAFK4oRE3&$3|(}`SFVd9$%d)R(w}5?CN`js+jO0ai+ybc8**>o&+sPdw03EVf7t8v zdMc@DkUBSaHHDrck__1I@hLi0Mk+_v7te@;98AvaBJ)=6c#zim)|Wnbll+$ky#qs} zCe2}Mcd6-~=OoYMj>oVNX~Zx@q89kw=>JnwkOJEG{U;5wrI?>vTITav@p&XVUn zkA6qgo2Wn>^?&6t{K5ts+3$I1*ZQ`XlD$dd9ZYC3M*pJ;SsU(eJU_Sd_9mfN>-!(k zAnwid))Jn}uxslqPNW;eIJ_0Uy_9e7Ij1b2hB$0uDQ}iMtnsr>L`b5g(c`)bw(fQV zvF*etfw1kwFo8nDp2?41Tc^I*Bn%t2%C0t~M)*tOk^hMQC9skVkG9$Gx^Y&fEhTxa zj2xQl^Io>3%#F6$!eL#0OP=d)Bj3Gt3}vqj^%5RAG}6bqtjTQ~IW*H}z5JFkH{7Og zkFa~qQJLh`J~Hgioib;tAMd5G`yYWM^1sBSolm>h?3FQIJ|l-F`V^NB$#eZt!)>2- zS;%#AmX2!ogy2M$xG$11le#Sg^x!NG9Vzu7pxaJGRoBTcLUq5oom;Xk z3lwsGuttIG-;GLq|0I?P5z3Fx*tUAF`qHQkVELRmvEia61+rO_I7pm1+}5s<1OMIM z_F7D96V;y{l-o8V8}sLMYx?k!l1)s2OuUPU*h`(R^5>k}1(H|%pXpbNki4Ibt`|IB zQlWm`Q3BopcR1(xn>IbXVGh`9lpRt}{&(2txW>Gj7Co$C2H2gHH%dTJAOsZ_SVd(U zrUnhE0#Ck$API;|p-VhrvXlM{_z*AvF{PBGs=>|$=3<)w`NOKioWjn-P{M+MxY+T) z=CB~_P!Q&4c}aOO(vSg(ZE$+n`yMq2r7H~lm7Ia1>55-cBOb$p6rxzO$ zCyn+DZ!3+z9bgjhqNpq(2vxh{r@Cj<@>gN*M_QX8zXh&{voCDHQE)6j+OL@ zpz75ZpNa9DbBQ2K5_}b9KT2|E%%)el|YID+v z5xwxouEbk0lFwg9e@0ipXH`bP+Y(Ta;)mmsncd*IXf2PP0myxWLYs` z+E_~{E#}9YSGN4KqlknVW9Eytyp33=)o_FPl6~mZUV|;uuB2FlTWU3$Y+R!)vQ1De zvh5OOeKuj8{CBK8Gu`ihCgAszbv1RQU{(4b^m)0Rra!^oh)NzCZpyZr3tJZZ#Ck<1 z*e<)6Z;VB)C#UE%anfC%+YiW2+X^;fNsbL>q}EX`$!N0Aqvoc^{GmULzCx0-w=*_z z=Z##&G$Cf&3nu-?^T8&-ENx$VFi_VJUKovJZ=p--y{58a$h7d)Re`l*J<^<|V>!~? z1c}fyLXq1GLIaq)$(J{8GQ+{&Eqn?Eyl3c&Rh4Zr^+&z7`&*L}oBn&A2MWW2|3%qb z2gSiB-NHc=EI7g4-QC^Yeel6GXdu|&?(QzZ-QC@SOM-iFz4N@gd-v{sRbM^tA5%q5 zQB?7(({{T1^!zR@fh}8{=1-matbjs`HicU4cP8qy|B#?u!ZsB4u z;cJUDln5B^rE2o1)aU9Y-150Ca#^HGxh;BGq(-?brHVTk)aNQC)F{;F8YQW;%IhVH zD%8sBKNMLMay^8z`Y4obRZFPRs0r0eQW=%`3l#yMFpw+MT42H_k1+`S^Yya&=#*_$ zN|<57r50#>AO|(w!cF}1rLs&w!m|~MENZ!jY9-XD)J!(lL$s?noS8>*L-Vo@8amXxAW8)}x6 zVp1D|30c`(4~;C7TH&j7UWqK>t2SPVPT?yM?;x80u7X>55#*rWF~&G_c=IAnG6Kd$ zsT%tyjEi!$N|gU$moca}wMw{kb037WcCo>nvRk+*OnA%!O*(XV%pwi=Ms!Z8+5=3u z68L90jBc$GD=g}_T*X;ZiDj9rr>l%x#C=Fr(dm7%VL!aYv|c z(r-Gxc&>##td^^q*o`>GJ(sPg_ih1xa~#-~GuYpj;vD-nZxDb_MeMJ#>AnmK-EqZR zvhx>(NB46#AAwH=?5{HE=gGxFa`Q%oM?dCnxPWa^$-6|qWmHKO)C$Wa(shf<6w-@j z)fCcKi-76uZqoCvz;d~Hz}$`doNEjFWNa~6A+VTzGQOCs2-w6PsR!7Q@WBma{V? z7S9)!$)#h-%pc7y#1`8FkK>9Bf!4BWIqVF{#cYK~@<3fu1xuOveu}cJ3BvU0MlqA8 zMrO99k5`0;`iB$x^H4U+f#(zaag3EvlWnM#$qX}#NCvKx$=Y(GrGrAV6Eivf393!s z&G|CdXAnwk{b#v{RB4}(|8vW}>z| zf%ZUGCMi`3WoHLRb7xo2f8_pA$^uIL%qZWM*}0J*o_Y^PVCk_;sYpGC;wW z!it0vzs-b{1L0bA{CZX{!PCHOY|NXBYl&8;acn*!VRrZRPJJ@5fi`1gXHq>zK;+F~ zV$q73Ux|m*^*yrVjicAQWW{`q6fBy@Tr2VgIkd{83$(UP?$9g>C)KMJd^fNkb7C6|gaEANu)DG)UW&o8>VTfz?dY zk&mfmA$Y_s{{bx7UW8^Cef?+Wr}QsPTmku;)MQSoQF`!R3(>lQxGwYMdsSQ4F*2iR zIrggG(k5g7%(E%}46^5q9U(Q2PdPOL?&m#cSbyF{Bvm{H)3I z6JKwa82?7>c%(IFie|Hz3{zBSRpU@w>|_!nW@tJa0g!V4<`=}H{kw{2)0iN#?i+(W z@s*F8pYzRV7SN+@R+c!MKM!_0)+)vMs!1n}|8`uC?dR!7VeBs%OFqLtHZjH}(>gpC zKL$|*n>wipH7Oy^|7zoncr)_%Iyv^Vw5)F4Y*+vwq!7`xl09yhPD&ZP^!)=Ts8^;#~XR3O6v8li^T%mCe#M}8w{(; zB@vyjPy%_iMatCkYZ0?#<_pUBa)oeD@We8P#jwIQ;r!!;k?i`f5ObhiLYBikNbIdVe z>C=igQgbQ1!e&K0)oleo6;gR*JuTalgpZl;>Rr?-jon;swIjsO+c=oT4|J*SsnA&f&cKOyJE z6vu?3oPFkPnmfdthVjP=+9pChHZ3d4Lg_H{6Dw6zASHeH^odNZP<}x6T&{O4!CX~O z#j0A{o>7%%%9V%DCVSlD?L-eZ8)X$o$ho*h@uyct*h+pxxHp_40VIbz&$YvR$bQ3) z{=td=>NafKE}z4HKE)o&i6lSR5G%ok$o}`9Lc-3(+)T~Y8E9|$k0%I@Hv$WZVGreq zt+~;`q>;WCL&9sf$!LHz2Pn;E3lX5v5oaCq2n2qH8#GrUk#9aDbzJkM3g_2QU(F#1 zB|c?W(@z+9%r;-}OndrWtd$%rUpNSTpf4x|U~{p{n^FdnOjyr2ss-tpI4A~rqGE}+ zRP~{(=4+?YUem%OmZhV&2wEDO>tb~FL}ar{5AA*q)VFRa)aT&5%Yqud&dRm6lVV^h zt%mco;&Z;+wBwyjQ5-yAnaL$Bpck(iD{x!3yJC)X5?7LnQsn3_tzDTmQ#N3-c#g&lnm)!n&yI2TZ_@aA;Emef=ZEvdFtLdfr%s@f3qpw8 z=o_JWfv#}hzl*EJS_Spexc8ET~u80GeI+2tm z*AW_q1dFq?OHFBRkR3 zS@ui}FYRK+P-(0LoBvo*1{P7(tMfz#!g<~81)q+{-Ks6^=uxnI;d}SL(fAjudElmJ zO#((^3K)&7e-Di^B94w~R_3<0|BMN$c7MeL-=+?H{k*Cb+R`LVIha$=4y$q+7Lo>; znC}z{ncm5(@OQ14P>~tWyv~@-H9SM<-5C+1T2rCoY79e8Ga1 zwUOlK12VSbv5s1jSbY8~QS(lEeFAXz6sLo+38=$eksn9osF^DpkG-0=1dp<_3GN$B zVT)O8yJntsJLhMWlys}QiV5y-&w0ekZ=?OT%SWr?%AczNQPF?Jk!v6OM*;h27_kNm zZP0rrNu~pqS0U~lvm=*&?`zKpmk}3&I(a!Ch~q1^=;Jna1=55kIgb~d{;&n4jkZ#5 zXb9S8H04Wb6xx3OvuJe@h-fPHi-I4x$xQm-a^3qm0g$fW$X|8N{iFv-M?4nw@Gkc5 zyG2|x8DUWSzOuSk)K3)>Xsv@9fSl;;sjZnECZa==#LbjM>%f?!Io4|=izcn5Y;b+` zwBlsDe2@Jr?((ERkB;9#Rv`i42LPpH4e4Y^pFO5n1b)OHxYl)<@(G#{cKH47U^P?M z*LTB?*5=T@FtzBy!ph&a1j%dtZeg3c{wKO&NmX4Ha5`5>3Cz}t{fw)miHp|)o8D2T zC7EiQju5Z+ONK-oFBGd@%h(8o9)#Dz)7>3KE%D*Rs1~h@+ET2xN>`8{rUITsWgNU{v&ru2L~JRRF^8zr=>%-DV=g z!GP-qD=7Z|p8x!JB#;qtbG4GOw{UQ_1N;>V{)xMPi75s)u8l>gC4RwF=|M^hj%tQu z(cw&tw=-B`*-JTH zekKmCVkVymvC{iQ7CHETxlGT&6V#S-n+VO~o?w+mEDQaGnie^*b)r3T)N9 zmIJcV5lMXYw=^1MzcUVF_t{C6RTAPmPk#_AY^hI}cd;fZz-YCJtt%aT$uk_YTBU2u z+*6DrY1wGVcyj^ut{8U6=2WT`!0*bM23nl-F2Z-sV8U$VI#}rRG?@Z=8-+#EVYRPvI>@Ol7R0p(t+UZS#hvo1SB`TdS zzXyhp5+bSMM0wK&HdwLv5cKOJYM`Dt=?_cdXFNy_B!}uEu0cvJCEKh;9UtEnL^+Gm zA)v5BEHBw0*_pRCwG~vQaDBy1*R)?~YKA6X_B9Tnw_#~dsCujhx?{m0rr@vrNjqCk zb2I+b;>4&Ir-YChGg($rdl(PQc>4wcbu?z1B|kb_rms(S5e+<N`L zhmS#4^WC_aCwR{r_Sw`* z+bqX$r^Z|$D!9`fnj;68C!VL|Unf-8oe0-F2YlhDM#@<|_Z^pnxC(HoHi zUU}!xf_>_c3JuWfF6)X*xqh`5I64=>IUIIdj&>I2e$Iwz$%A!GB~{E9zZ~mQDAi-^ zHXH?18Ml`-bY})8N36Sk(z5E%j*WE`QhOESYE5F3=c}DS`C-duasiW*%8i=hw!y*Agq;}v zeJ!PFb%Wkc{itg3wOGR>nZlU5N<(p2P?VZl@y@KUl|0>=7E?A{<;El3v-B$`#_zD+ zOqZylIi2b}8^6*oT3mmEnj)i*u99D1n6j$0>rxLGtZlS# zKU+rvpQ07!xST(j`uGC5Kry3q+vuzJsJQ(lmY3W+X>1;lfej@HGAEY5Wb$)-h1uC| zvHEN-rG#w)3DS2Fjpi8!n zOj@MWncd^KoyF;XtPNVlqG%jy8P8jGL{sbhret zF1}M$3&})x4n{HBQ>vMK0J|1;-0d*a7v9*|5;yE`Cw<)_?}GkI|L>KMzp^n(qvBT_ zaB%!f((!NB1~Os*dsA~;fQjwD7!g%~!<;gPfCKYtQZ&#FT_TG*>xgvQ2+Fq)eu-CDcfI21hauiud>8B9bKd7awzm8rJpu&a zXw1adlZkf~G0tUI20AeV>gil3;aixt{l901GQgoVQSgf!3j32R0&kxB?JM42T6h*R z5algbxQ~(Cs+`VHTMM2w$bZhajNL99qWfF*J7hj3S9 z0EusWJ-eh4k|xB57fN74WOnRp)`D=XgLVZ^F~<8Yj5Tx*z?DU)|;}sJ06dt2nL7pd{UcP20)V$4~~$7<@OFE)?N zb{_%4=aE%4Ho*@3YVHarWc^-mMj!J5vf{R>#SSpNu9fkSybai>6(;B%I-u-$#0l`rB%{Yd5Mot7viw8mAX^jRdD{9^nr z{=cWy@jzp^o7F=EkTqfHcpz*Q*@KUOaDjl&n4TZ zCmDWM{h4i`uU^Ui8fUP>>43)==xnE1WQqqmNsFFj{+7U7*sm_wT-T6ebA;ntQ)!9i zV#8mbzNp3e@#oTg-P5-V-5ENudJ!%|>e3cH^Ok zIq^knHTQrNLI!8~vg1#63XVlJ!|iTsn;uCg?ps%p^SH^Oob_+Hs*jADZ_>|O*X(>{ zHa4@iOFqLz5GNJ5cJl=_SnRczTm{xn(|XHI4&)y&hoA$55D$B$naNyndl}YYH*VJP zq*ABY{TgCor6fbKP-J3q0=`LJGDq&(bNLdXj(vcgcbkV2;{!**g3pjvl9Fgqkoo<1X#({DSdniG8U5 zvHQO&2?D0(4nHu0MZpLb_?xAx7?_b)`fBd%3^X%WGk5+9H2s&?RI^b+7r_t^lWV5` z4QSL>uOyCXQq`hfAsq;6P)G%-*0-}imD#t88PuP~Z@>`!RZl?vvz|x^AW?z(1F)D% zYdzR(y`TCYWp-*4gCXkTz0^3=;eN;TB$kjzlKd~APmIzdvLiAh!&lOvNC_Mm{^VTd zNP7osuXdIB=JZ9*AFRw%u|H>5J5<$fEh>u3J9Ou*ts_D?4;Gt>D-cJ|nup@@RzUM= z4L$Xlo4ozfV2$=3$ADx25j=>6)xP{}gTP>8@kW~rN5 zUSn(+>i{SHFnl(fc>S$@L~^)IytOXovq?7@|Fub)=4GBIIsY_#%JiR0CXX zcdee}88#G-;LE;C&oOq8N;Qc+tl?;yXxfAG&_m2q%FdnHq|zrnU&7xRmFcNavQ1aU zx}In2?8KaW3{edo^9tcEMvhgP$C8qx-d54F9a=y4ueBU?A6-~3r zR#@k!#XnIm|Ak9o9=*#i`G~jwC8^r5tCSs2y+CuOmJVc>AfLSazHpw?RSMy(1+V63 z?SG7Y81lT%Qy8O#MZz!UxpuBw!Zlc#K#l(CU#;B24 zvO6M5vYu-wU29lsNXq5%SQS`UbW1s9H1A`cD0lR3y@5n_{$&~TfkOwCFJ}zC4 zKhu61b$7o*#}0|XsZ0bp$pr-I7_BEeesn1?%SHzDHm?&G6!>${eGS4(ZJF~KyAypWBZ_3uUtJ@3jSxwFP_&2oQLTse*YX*!fW@wplqLyY^@;^Ws0cX^UM_~Ot;KF7p3aoHi(ITAB7v$zB& z2o~7IuU_T3+H2Ly8v;rk^h(@VT#NU2QvK|$xwh zvrDnp8@w6&Up(>4E?oH2&aXu&%g7N3@(NYGG_%G2g(dRX$5V8H7c2qavP56UJ3j`%4WzBV=64HtwDd-qi!Ttrpb zBr^&!>xaJ!+53^Bp~|(Le_B36E*h(Gv?z}{_$|4SQ?tx6zRr##>Qv>$zb7-f;h|#l znRm<4dO>=43yI1fZ1Dme1~i@_Rfc+?cE8I6~UK&N2KMC7hEWP zpNrb_-)BHsv`<6T8KFkP#-O|fDL=4D=JzgdG2G3A4`>H zmC4z{I71b^@C@_>u7#YMJ4S!$A%g;4vwQ^&agpC(mpM*Is}#;W(cMOYH)QdG|HU2ywpuJY*SVbnw z6zViLl7R{xYNX50M&V+d^dg!kI!PzRRQOK^_GHq|J173i8m^s1OGpuVsi3At4PZLz z`macpTv~(FGCP9E7lG|?bsaW5w}hz*JlE=K91Qpt3zA9|dN~z$4|;vNPoegA8(n@z zJsxds9a6|{GevZf_G^MXUzp>3wGE07@`2AoJs0;+1pD_NtHdB1xcH{h zen_Xcr?Y+|U05-VXe7`Rht*My(?WG)7xxLUKrH@*&9@NISqI%2o3ruMz ztI^;2QUGdIo97tU^zB_!L$(7OE*V5M)=A;~$r)F^VA_F?KLXMOSj9_A;#-F!$Rk@W%vR$0-0t;<-tsnYDs%urPEW9q7Z(^q!_=?O z8T$LuudldVuZvmo*lV5$v25%I`&mKPFw}0^I-<8a!M^7``>U#k@W(x@$yU$fG8+18 z)uIj3Z$GZ8i(j!h9!RT0F6nphNcg3mxkk?MyJb9Rf*jDgF#8-(effeM(7Vw4+~K;> z`+$ApBpsq296?(Y^ud?j>?6>d^$q*d@uJW^7UEjThQc18S;x4e7(ijSq7QCJs*X%l z7IB?(z}2Ff{EMrke*e?fKv)kdY5`TK)9xJ2E}02F490Y*Qs{_KR1Qm8WcKb@Oeobs z4yh-6oP%YK32BFoN6tE0b?m5&{c#G{3v9CLum%Qa%864=3cNVUOyb87)(^yM4xJ=U-mR-MI^4!QnW z5#S;pO9EiI*Q5=y)*%K65kzpNQTa2#5{b@UiFzOFR6k6;AGTCec=^wLts++LK+h5B znCTYU3b|u<^ohx9kcL&wU-7uRY0)K+>658qgmW!NKmy)}5zA?z zdyEQnN@M0VsAVE0NR`Hzg`Vs@+seYx4?+*DJ3fd;Bt9IVWjLdVos%FglNRBNDC{no z2TjDS;uwj3krONyPjVCmO>6Hzh^OfJR0L<=?>L_oDr`q8a z`rwwDY#vSFpkl>X+sX<#20m58^?)K3XSS$cs_DrJw)!X;&H82#)gjRvhQ%u4#9y~J zYE)U-^Uen1B)z}h`>d}06!3d}ea`tH&t9iJq<}+YmPbsKAc}R;Lb3O?quiz88_aF3(7behlg^j*?7b%7o#FozQA_VE+hEH^ zaK+}Vfp=23;;VyYx8>Se=U2-dlICZm_h=i&dr4hB_@Pa!KtR{IH(;Q%bbJG<*YPZg z6Y(TCO^Zza6^68)=Wb}$0OZ=GayX}YPr%x#j0_zUl=XlYo1d_C8yH6kd+vb-&+sBR zH)Kc-8PmRM`d(6d;yj6Pb`~|1*di4LnY1$o6w(t?4Oi*;sG?8B@NRYDv;Rv0lJIxJ z*znOrGnwE+(1Ja>?2Ojf$=1 z#c~Q)FfD``f-KjzEh8eB;HcF5P5E)jwZs~ep`bDulwYVj)T2&^^@WxzVlZzXdcfZQjN`--164<&<)T^$n43VA?qDfsX1I{*^;cEYn;LeYQ;gMLcB)MyIgF z>#9plNhndrgo7;W+?DfX;)~ZePE5IAcs5IMdMZ7AlcG+nSA(%MUK3`%vx7;zxrd@J#UF zVRi)RQ0;hPQyoz>WSCM!vLgM)CF+ZX>U4MTdB3t_+D0z$S;^OT5bcvR4y1}|C zzJi^-YKAyQgr@WNMT@mldy(e1~RVEPS=?ziX#wEHN$9dec5%ZQJoGHf5W*chd#6Cb?R_);z9CTP(F?%R5}k*QME8 z#7nx6RdK1E94$^JZJ&ahhUWaRQ5LN^tBZSI*POn^h|jXV)a`UT{neU4M&vT}L*{RB z3j5Au;EptXvhEoN*pV&&@=VTuIl3n3$@0D$gu2^{)7u(;2tEtF=VJqXpb9 z9JM)knhC(0GOSE(ZaJ_6e*22~!xXEx9U7Jy$11c#1}t1#I0xi zau^`7J}3`1LXALmX7{WbZ2!qe> z{GFjF88HV38{q%ttp0IMaBOHbiMJ*+zcdumK5(_SjY|Y$oK(U%C{AK8H~cE!s?A(N3#2PQ@#s2#;Mfpk#fi)s*H?d|7*1vwzShDP zPrxg~ga zxs62`I!M9_LBWqz6J(C11YNmJ1M|79aQ16T9EVsN@q>I1M{G_Y-u<1H@yGnU!BHFU`b70!|jM`(#q^ipn^-=i32-bCc zJRK)RK}~AhgwGT`dwQ$t(h9O+``B*l2TTz~79(}p#|{z(36G=(}qMC{< zPC+79It=WelvdH((v7ctf*QQDtl!G{Mc7LKglY~6?@Gcm@Y&xhqF@*2PdH5jdryO< zI#*Tb^cIHF9ahTi*VsvA?8q`9RQo9J1>%aS+(-nF3>V$`7RvJs>difS=>o19b1c>E zHvS|xOv*KaJ`Z8MmZ&@hp+qdt+0*#*!3EzTbq32sJKdFjkYd&FX zHv2H_V>+$FzyL(R6-sz7g+ozz`QS^vcg7hNMma-y(dB;OdEena{q29IK8#^v%py%F zqjhk^T5vu$n@s=&qk|ot`1H-tgGMQ=(h_1*YN!D;(E*Vd=OfCwvNPBqciG!ci=vME zmbOLqChXK+=2kriT(8Eb0ctJT_M$WADj>GrD-PnaKz>c z^X#Hp9W4&`F5-2ql4x47fY`yD4H`Avxq76X$K(^{38SE3 zTK-`khbjjvYmsk_9Y!+^ce?&Rsv6+S%gcN;7RBY%`VkytsVT2Ms0S8B{Hi7*AZ#<- zPADfpRjz_F;kLWV2Jk=sumz-!nzG|tm}K@= z1x;Y0c6?p?whtZb8}YQIrtJM|s;Ka;BH?g>RE%S`_T|z7z!2eQxnOtxoG~;N>`H7h zF}H_Z^pWGc{0cSuxH00Maq6L02#d7KF+VQVXo4j8(q?jwAk1^nrD;oenJ-v88p=%S z`p6$${F(||pI_|8l*j{V`MC#YBs76wyu_3$R&@L3RNPgcSF9r>dJnev`H)Avy=%gf z+=QuHSP@yGi{zGHz$ZM8P5+nxy8FL6XAp$<83|w{NB^&p{Qv1V%o}6)8V)U0Cr6;8 zsEa3#bEerf*G9-AB}?$7u|?A33QgGCSu{48FYZ zeJ_`GgCF)n_O=a;U=d%I34XgKj>cJ!e2t97s^ntLwe+T0dwT~Ka?Y(o?>4g&MJ4N} zZ{tT*ERm(vdyj2<&92_dW~l;M!oZYw$}kQ3;cKw$0F_M{7Th8Swe4x#QAPFh5^Mhi zqJEvGw~m~OlIdiEpx~tdzw>ocwZI6rOtCI$F^d>Ur=OU3x}M;~o9S_$ZQ8eK zX9CdRA9WHFEHjCt{lvOVmFrd?NtH-ZNha!|NP4=u>=exUf|1_wWo~uxbtoiLl8O_r zGW`X%pU%}8qj*0*4_0Ea71_6{sXGTdw;D57W|jo+pHD@v-3zSZq}y!V7`21;vG+fM zMt>a@eX>IR)MAhQZT?SmXc-HAmd^1bTcZk;K;dh*8!$`N z5-U%^_Q^iyQ+tj19evEYF7vOktH|;y@6GfkPsV^!=6%$cB;g?KTqIIET=Z1vevBhA zY?9||7H|Ci!ujes&tQ8rjK3WBG{JXpca24Vu+jgW*JA)upO>W}M%s(0-{lY6teChE zQcZmjTrF4Wm+W8Syr_JjtZ;$=gjxRmPnh{Gp@bU@eh|~$PvIV-p*|rGq%6+gXT>5Z zf)3+&2-5*J4@mz`PyVWOf9WxnI)O1>0{7tq-QP@4#Q#aDXl$wBX<)qB*NuYA{3(Jj z>B_!KBnaceYgaaCOV3iGiR2~eHX6j+ecG^{GXvt_Z@qlP&+$>kv3%MM%%TAf@$w4t z>~RYo`t3Cww`K$fU6O5tT&%lX@NDr|UhuSczd!Fyf1p%$Ci`B7#eLb9u^oWtV5~bJ z!N}y4x^r0^I&R-!JQk%h(TduSdCoe$!|>Um-3imlH+}o^a|jHo3>PoU@4j|>dXq4R zu3`7hgPI9I@JFyX8*d;3*HF){k5e?(xDU$|AYgkRKd?S=L-UV2wyvpXrx|sdlff*f z2HzqH8+X&9N!P%(!HV)Zor=ON+pKv(>LT0xFE5q~%P1UJZ)WklAZ@`aR#zD0wQJLZ zmrexGu)$x!Jvo?wAZSxrLVRt zrP=FQ+Klil0+vg0qrx7@OdtP!F>eqFi_YXx;I#RPFah`j55HHO z6k}^mp(5QHr3ghnRUkoEE4&_dc`pyUzf2c`g;zxt){idQl{0~ZTZ4Y8jMrMzNtUeN zj^CpDRAU2^zZ@uEVG|sn8%fhKoS9y38R=GUoNq^CSDi#3(3I|?%A(*rhBZxWZ?UZ& zDxt(C9*qkU(_4B~g?uwUeK19w)Bb41oMqgRWHso)mF7GWzc5Me%_4u`+S$;wqj5tV zI@5K|hEw_oC#7J6SM`1A61}G8^l7vaVU4VZ9FfcK1BF-e;=Ad&K2!r!1ianijUhHO$LwZ(fNxV<+#ysjYmdJgC6}ERh{dZ@r!oy zaLrGJ)NOsE%2av3r{Cw4iUDvRGo#_QmV-tCqGBF?=u{HQR<~-Ej$V)Isuemf^LNQ4|U&IKZvY=w1BpN zve!IyL)*svtKLc0N4+S6AH6;Npg$RQeR@?xny&zxZ}s&jo>3cLEi*)ZdQ5a5=#CV0 zEh$O{mUj0kS0Wd1@2J%E_Y3qoUfH>b`PKLFB^193B_v;2I9t1+AjPjCqW|9C{p@xN z5Z+9DCIuqCc2bebE{Vl`MNIhZkRE{%)^QvXqS@+ft!_)T{$4$p^VNe31I8yP24_ED z?+(T%ItfzKNv`h{_C= zCt{oi>^MO?$8;n@9vQl-vj1WEk@m}Cw)Jy$QQOx^ErNbGYx^SWr({KIjbS%B|I*T= zYDMd0M^z}T+YQ|qEn52?14V0p;71T(LH#^cWAQV1I;@Lf!BFc5tiV=b^H_~fpV-<& z9pBm)IEqyE4r&x3)=Y+Oy#QeGpTZU_Xfo7co$3t4<*k#W*_7KVER9jcIiwSQM4ZC9 zWq#_c{4YXYEw!CrBuvb3ex@4{5pkUDt@5ii z7n;YK=X^j?GnMWZX59EW+=uN9kM~R+#gf&h_nb;U>G#ZcJtlvygwd^PVPtF{8P7^) z>H3DrpkS`=Iaw5$Be*?Xmt{!76BDMeRCqvyn@fx{`rl1k>zVYnB(TLXf?4suyG8=G zIEk4Rv$o-@ap!usV&QPBd?3h?j(%Qv0}YqUhE9d;3)&UXoe?`hx%m$Z6GgWN5B=DXOvY&?POZ9*IhRNqMQ+zcQY2{}wZq8l91@ zfYE;xPK1-Z`%Yl+bDJ48VxtgTP^1b7QEi5U;n2DGO z#H~4er#Fyt7c6dj4DCMvSF8p4^u|j`>G*E6%N|UGYB@CUEEY5sikurA#zjL|@J$d^ zO_7-{Oe{#r6JAi%dEz;nbsW6GQ7vu>N_~cry%};54@pfi`Nb13d#TlJN`i&-5>^po zTASiw6FR7^j?-JjhMk48V^i`lSW2jn94?%7R@W`{TYlCE?Z2m!Nh~DR9Kc3S12%H* zzgrOe)5z5U&X(r?H?kVItcha?;88X0u9i9NqUo-pSVa)f+J1Y82ogO`)f}=|AA=^g2oJ;AGPtRimvpEsIVwN+zdtp3FigE}t0N#8$!AI)8E-9OiwDx=Qq9U@59 z-l8wA^2a-~Ot@~OHm7LC+F0%aV!Z)5?Ml%7w`{CdMawc%*C+e$&q!yDLS3BKk|c{l zSq_*_>>NX%88UR-GxxJE`|hYe1ylKiPc-Xl`lrjfi`n~a?X(LBI?`yyeWr7sbO;;X z6L1PBtNeLxmZaS^qXdAvV8$4JQ&fqy^(#zPYexC2s1i&PmZ$F@9`}@`EUK1i(jwdFV~eSsF9$+bV(32T zJD1;$aJB*s@@@3cVToY?Iu72S)(ZwFW zd2bQz&cUIVm5FN%ZFa;&Tpr051zoNUU-Ivq3|~U-;0-%Qv$A-G9CpeZ)rHPN4QM52 z2MPq@%ZGdKGKq*}?%Rx!mj#;vPpS zs2qE06ssGT2sM!{U6&Jaae($5BK=0C1)5lsN?K1{k-#D_S#!1 zeyh9BZu?KKPj|Ps`^AR$ySMxPYzYzfF!rDSL)lqH#kp%ySoNRaH*iT);`kq+V^Fh7apqdIQu`x9HWn~Ur7k-kCS-z))jDHvs@49 z&KPbwoRA7WuKTp-KmXb^j?{u6y2Lj>T#q;|Evnpp)>oq3u`4`BJ0e=QF^%)d2h{NhF_$>k-l3??LRAI>3KByBB9;H zC!AflI<0Hw%wSBH^Jo8SwxF#`+qWn8Om)M(Z()v?@BBYqs>0&=k_br0p}+QTIqJ6CqN(B9@TuD{Txfn zxu@eSb698l$rn@F`~au)cKo$YGk>%-P&hSXp;5_DtGsBH)JOh&>C2t93>DFs!FP(A z6bA;x^D_O-F?fj~fmej&BWajSF+%u;;imI5*rl?rn_+3%+q*82FDJ_TFja#@pFrXo#F=S5QsIkyWYj4wjQ}*e* z3Os@ugi9kDL^%o@giD_gAXa0k5vUcS5Gpp*-*gwlPDfWHB>|s-=J_MoAoABIr1Opg8$v0Q}Hm%sVJKGw(hUr6`8rt ztd7gvE7qqPlSc>lvMIfiy=<8mt3T}f4o3Z$7mzjrfS{B8=7%)FoRd=>o0FjM zX36v^s(NY8p2`k#!{tr0rOW5>5z>=dkK^B@@@IFYv>GRz0YX(qdLY6Ss318>aXk&G z*rgt1>$x{~9YY&75|T8BjOyB%s0`9j3x*+|GEclJmT^(M3+bITZ@jMV?|pUauf?W`YCr=@k{Z)Q|`@>L1yv08H&*K z^|8Z*-hpu7W!5PB*>4yAZxAb$X6}B*#)&ho4=jAg-cKy zj()jhZHW{Pom}e$PPp!`>$-41R*v4dCkjG>)8qE;{WejXNAo@6ET$YUbxeeIe|>LX zX+z2jZBZ=(K+4Q zw`Q?EzKUsXB?>EhvgbymJasPq7T$RJH9A$&hKS_(ZIn$VjAUkwHn3F|x1xN+E%_@A z4buMdFyX})ZRF!gF+ev)@0U}tQF{_QQ~43k1VqXBn5{@=D@J@P#sRlgF%ZLk-PO`C z-p~rVaj0GG$WBZHR0C}<#HN%#r8m~~)UM|~$*$7Ae!qUSVE;yxS4jS_p$V@G+N%6F zqF?oVnZc~o^&x)Ia0BG~0v(CoqTazB5TsNh(5P66!6n0H zyL{JMyU^EEe3C*aq%^&7LSp``-elJk{WFw-{GdO>oV%~XidS^k?9Le-Fr>615F(%a zG5wQ3+XRD>NkO_^XNW^7pZ+G$i8fle!mm-8_(57bRX1<(*9%cj^xYM?jw2-E68a75~{Bd z1lLbS6^Q6hcKtj+M)3x?ve~!lw~7vee)(_>H-LumP6r~sjvPotf5!(M42wd%QC%yC zh$_Bt?#^Jo6N6Ou3*mx7KzIYj6mJ$++EGC`FWmdaa6w@p-hmy8H-{^)s35!-;r+xA zGTJw2knH{roT%gr+%6gBI|gWUI0*8M{<<(ENa+P{7yN9%0j&%NLA^0w?}P*?zo6}k zV!mU6W`^G(-x#m^Lf(~L$aaG;-|;{@!|zamIUvq`L1q4vEB;}-UAO3&uobP&_zn^y z>2IVWh2Bu#8prX_okDrBjNzTgd*Qd`{Mr2xuFI}0hFL>dX?r1iQ$P$L+kLyNe3xs< z_`>t#mal{~@8_*sSQNGpr`@`hM>YCS9HCs&EXf62UOl`SlOm(+!Iv?1h z*jd?h*t7xLbO{{`>WTs~fWMo$Yjev0qxLB0Gj`S|`Tp{qfaIxE-6s*AzRm|GyKlK` zx#yRF+Z>1IODiP399vTJDL;MO0synsT5$$V_u_I4&x)L}&;ie>yj-AMf-PepPZPh( z8}f3wM)oh=zjrE!t3)mT+Q3VK|L0S2aNk7H$-~X!-|I0=2W=uv^gu_bADC)6=c}tqE$dVUmzDBY8A!HGqbQ&reU^4Ux@MF56cz%U|e;iTcz(pSnn zc7?JYH48}TT0(PYt7MLT$0s@TK_4g&t)rVZ6O4%j$qlR^_Nsy#3m8MgXQ8@wY4WXQ zip6p&We#E z2nKHryU({Q@Ux8y*1kwLu9U+{ULwud^NX$KB9|8e+&l{_KG9*^hXcA0_-y|6 z79pf;F}Zh;WkgCOgSq(aXEgH=o6}^S=dTHHQEsu@T*D|J)5{>Y=(Rvuj3_VbSSTOW zSDx^1_@0{)HODp0F0t7=jn9|E-ZGmIY)bNrctpmr)T2_3Qm( z#M1~Rm27uUGjF_0b}GMVCAFkpVQs3d%Wvi@58-BRjGY_Ljdr?1x)-9n;Rl`;1GL_R zVG)L~zvDaG&S$ipc5uq92FnXJy=#bpRZ6s(a}wQU$GP-|M+S$B8glYgf86CJDsF}9 zPHoos1w)OH{XiM6uK5ZHx1Di0Q#kG(_4!WKiUl^+zH$?#3w27>RbElw1r-g=T@Po@ zB^b?-Y}HR-xR>@F3Z|?Ov$?iLHM&#BkDE_x^^&-`6ib&ZmCh@D<~sSW9s4$_mG7-y zX-IdE-1(vgWF2TWE_hgA9XiawG2${`ba}-swc9fF;IG&~8JBUR4@vSbni~#te^ZV< z>)QO(@d*_6p;rPl^r2V5n<2OX{Q!1}f0!2`2)lnF+zQe=>?{&oafC_YIY zBoh+VCL4s=Y;t8ta*&4Comq2`Wfp8H0MFCX;8J1b(@XXa8+5JrEmS^YaV@EjAj;;vsF+}fqjxhbkQgO(S(&79!mM6DwGL976FAgH}*Yq6>*-xl>(Ce$!C*RY?fBze!!junPJj>p2Z8r7U3 z8JeBUc5PUU1bJu!wK;7L4A0VSLao~$liH8)njHxyWzupy7aGmywA>x4nb37cAaI_C z*^e^p$8-3ItdjfZCHx)6F<03X4AYq9=TM0yhrT5oM@|9?inj|UQU}-MyS%xqCe5_e zO9D(MA@RP#s+U>tpdG(tII}6X=enudKzdcM03GS08R#=A?Fg0hzTdBRtPikRsAf1b zIBDp2Ri7Etp5@mtB-rydLOR6>VOacF6OF!Q#H2I+UDgtR9g{$7M$lB*g3a#Pq5QFX z1?_2$6B0>3^2p3TN*wJGrFDfwA$(HH-nfmT{W)MmxOr>3zXCwyY285(X&zakzC3zi zRWt(>m9`g+dQsg)T_pqd-N8X+IwTg3zR~?cgCa!TlPA=$#`3<=+WDy9X_K;8Vs}sT z_0-YJ55=)!;;J>F|LU7IfKY72im!fp$=74$He5~!h8a!(1=c@8#IVok)Wg%;GWT3F z%T~LRlf-zJV5Z@ofiLI8m&LQjVWpb8_d7ew=(R$t(S0`XQZw68dY7no=h{;MGNfYV z$D)9!AW=ebQa>}bze{OODU(#ZC83f+;@*E_R&A@~=J8ET}^<)yls6H%ke8-4Ku z)%M~&c%&}#r;1l}3u=}PBVP%ZUEp4Qu~E$-K0fL0QCq0r8BguVGvveq(m@Mx&Sc*iIk&3ir{HF3UlrH?P>Dp8~V?(UN)9U*P|g zU~&-)tiQlqQ25BOso2&Zy?C{yKI@CeA`mYNB7rSBE0sm$P*^^20+ zA8nP6;4-xYxun4O5&OXjvjj?v)sY7pb7WVUH}SZ4w6CxKF&&*iF+l$c_AfR5AN)&T zfrHIU;Js2C-~Vs#f8!@U$v8F)4#trMl-x>uyyBGw0hK4f^k)-YVAj@pyqtrq{^FJL z9q*N-VzBi-+LbhcvY-ki%`DnUf!n1|fuHZRThQy<8`dY>GJMRAnZ3`*!dbg)1S1;A zLgfLeapZAnPL@w}4sD4Kmi&`0=*3xyIPsstJNB#Ueph#? ztLM^COQ)oph<0sqB(915(8wQ!cVv%hTfp!TSab21d)r^2g?hVy` z`fW7&Id91TIv=CYZ^UM4I$)Bo*Oo~N4xz)AU5>>P*AK<8EZ?)7fvFu8T%%7dO!T~{7hbU(7y#3CpFQmvS zLjDY-rB>tqJt;kkf(T+j>)-hVg3!;9?`9oga|gFRcbJg)2xR1U2+3D(3KXuNPT_XH zhf_*CLiKNbfnLTl8GSS5mr(jzIAb!JIO_#VG$Q;|$ihSPG32x%N^;v@JSY_H_0x-{ z=`Olc17=!TB~@)m;* zQUe1^CVKV@k_^(AM3O}o6N`5B{k+`0Ql|byP)6AMVb)PB=?)mvd zyY^Y`c~O4;ib)iTAJS2`9r?$r$V>sp(Y|T69391F>mOds3?hLW4@hk@f~Z3}`$gHM zp5bNXoF5}GA$aw!eQsr=oiu%h8maU8W#z2zho}xsWOVwM;Bl#$_Hx4cP&w$fjPx7n z7c#6Ew~>rbJ9@X=I!lCp_nz^gf?c$BQx`t@^^AMqY2@T3!Le@`>&>QT01)XJ(S&cN zd1^Uyeep=6{4#C+?Q_FN1m`Hl#G^o4nSdP8b8^@T%sp0Wv{ zbk(E0q8s-N|F-F9%OiKnl2&B?m9I^%0-4R`;tOFE{55kN?vyn=`1QY2%<12J(A7T^JjVg$MX1Haje+b*9xHAT}OE=}Ne%&BpwPwbjns@&GZAjQ@j_Kmu zOcjv#`N|0jFbO?gvW1Mg$7U0l^FWydPew4P9&QD6MsB_%eTh%R6yOO@q?GWR7g|$4 zjK)HRxj$A4fb~n<1@I|8t_q6+RbwE_kM}7H(Q}@|=O#Ar{Z?oZ`C{FmOrgvIjNhQZ z?67cNbkn(Zd=$~AsOt{=rF*2ps&_-vnQP1n=Hcn?dGBh}XgNY zo}g@NPOqnVxkaq6+ayof5xEo`VFu_zx>0wy4PgPTX7RZ2)apbo216b>+)`x+v#QYt z5Y;Y=U#F8!1qBIG`Jh%NJCwgE%seQJ9&1^)zX!}Td6#>Fw%JJ9SK1FM4*NObH~`goW-_%MQ9 zGR!^XtgwPItV(WZ@9j`C%p9vkTcwRE$y?K{pI99~7qXJiP_f=?biasKIz05Vz#%t* z0WLyml41Q^CqwBpda+R8R{wOLtEczCI2qWv;P!fXm*{ryJ(4nF=q1@uAdlF>RzH7f zo*bxpko71jvn{keg2VPr`VD3hqt_RCX2JU%zgGbiX66k6xH+v)?)l7#5N`cp?=BtB zWM)?snT&!zhSfAqr@zd7e-J|K6e^Axjs49%ElWU7Ln>NceUF@?k0QrNB>0LeKdRsh zd+85gKg|zAk$o$vmx$nZqQPhcicw`s=8pf#1${|)T=!P4=YdMk63(k=iaF`ePkB}Qvey{Jth-=i9 z>QlMT6dw%Bvs7>Xj-mv9_Lz~uGFwD{$r92qYry<@Js*N4Aid}=Kj2V0_UmpF8(T%= z3R@mC1E(}13d&HyKj?52s%br(szdL>q&-COpoz4leToxpnIUobOux7TfP zhn1UY_Zb&Z2$*E{xqH5OIg@$&-jCF6W#2T5I};6c#dRWb?PWfYJ$FVw;+I}ye{<)i zt>dZlQcJBVx%X99cuMHW7h)zOTF-;K!{Kwp!kG2F&=dvqp;nMI5aE8u7se(7p~FVk z@Zz~gLL2|Mq15+QuVJN9g1PbXXZZn}OttFi3cE?Yi_4)dxm%tBK=IuV!8Z0TJ#Ntx zWzVC^3+U-R`VbZgQ^oX3qUxovW6WEu&} zj+Z_s5s;-2!-}#fXYKF(h>z^WPOwgkAo|IbE0vf}n#wKV=O8t#Xe4+U2`NM!{}&A5 z1eMuI66EgTKJ|WpIq@Sq3568`LENG?mFZ>gXC+k0KogLI>mg)GCjPlG@C;9y^LhMsW#+PNjY@2Yn7-|GimQ!!h(?N9 z)wGEe2Qn%wuvFlwVKGub-~jya(VW2~YGtreVC&WG;gh_{>o;dW^@5!u@QXP@h7 zWY=RhQf!FI%Lue2?ec#@`{O*9q~r{mAVwlUK=hd% z5NTj&Q7=~#nl4GDFifjPa*=AXK!$`gg`pTbdvZO(SVbP>JO2q~s@CwybJYFgT#-~V zeXd|Coq@CnB2Hbynpgzot4N*zZH+~*vfX+y{2j%n@#7U}0KUs2-HZ(}LoB9e9 z#*UcjsqH8%wsYnDVI-}F-~+C#C>Zgdy005f42wmulF4+*B{L4~CTrDt;^FlhFB2(Z z#qTO$K(>RpC3QO_0y5Qv-|djMDpWDB1DFbr6Mbn zD{~5k2~9Y|`y}iU$?y{NM)>FM`5eLkFF&$!_~ZQ{9g-iLK;DrK?03fJAG|`m5eI4~ zmmrIZ;o>Kdj}jP(E3xHC7+HBk`K0b)-{1}h>*<+c@GZ7-r4#57=Nm7jy2)AGi--PEyT zNn_W>_O_aeW8hHV!BW(y-;(G0`LLfnqTIb>Ua63hm$&;zoXK8itsRe<>GzeR{7%Pj zhx6|9~<@%Xg{!JxBZ*8m| z##i;s6`h0(zux>wP4H--ow>Jf>Qx5!8rN~|^yi&B23ZMseQMbiR2rq+Xf*6Gi96R1 z3>DnkHqulZdzH{iyl36mg6v2%di zoHuLPdk#Cv;f^7*g;wg2I*H+nzTP_ueM0lYegGV_lT8~C=9e(tqxXpeLnl;TX^u&q zO}<(D7-_>KguLwByUWV?Ge>6Zte0Z`sa|}s%a&Qa9l|kODAK4i@y?BG`C6NzTxMs& zDF@>&>OsK;+f>WutZO)i9^ntM8cRgEet%NF$tLl{Yr+wnqKa+}qc714G|c+muoei& z%%!EFa`1zM3uUAoT?a&6HO4n~ZD}lCfz^b@3YAZ=pmg zR}6KLDpY|E!3~&wB4FT_6Y>?+_2!4n=pEu+yWv|XAxVF?;0ksHyBwFMcAE+XA%(}r zkxp6_Qu6Mf2fO)Z?}}+ZGm$5mG9pRMysz1%j*qRRtXw(&h{wGxuhdkU?CHDoZKJod4c8X*u ztNcD_#meJTQ_jCh_J4{ZyFb^X(jS~HzljSriWGa`|GV_L>N4wL>vEOQP-$PNn}(5TPggV?LoWbx=t-jyQYlWnD?rozRl?J6qX<~Hp9%v zivae3F&gCwXB}1(OP5D@>Sj`Lf_=S#0JyKLg}G?&-DpF`2fg1LAaN^kD=GPFF~TvI zWBLFx?C{McJy}xeJMpo22eTPOEJp(6_b+XN%uA)#g7sBh4Z=&VLd^&8`df~Dt@iqr z>Dxg=zdE!wl3n1^xV|dvZ89Pc@os=x(!r<)tjg>sr)T z<;EmuV{~QZ=l>{E4=i~U>tLl!q`t|XJ8@ex=cy=-K$m;cWvGcNP1Z``7hb@2lVT}5 zM_!Po|LoVfexj-L~TAs`} zdaC>5)VoU7Xo9q2xO+r76^nU*`EmpsivQRtS>ck`k6)=41o06|WGPabJ!HZBkth+X z;OCGPeE#pD6Z9{f?74?jTf^|r=U90;n^4d2;69{`$K9P3yNpr)PYrt$ypSqGDt_I}+KaU3Y9il3Ns%&>~M9A6eV`!8Y(r}x84EPyJ`|bx^cZ>QA1-{LGb)Ie; z7mBD|krY`RF$iDOwa`dT3j z#@-((^snAXgDA?)UF8lpPv&@$s}x6bspSq2gtC=}^DlG!H;<$Xk{V-jIqLVi;+Xwu z_S^_IK%x8Jbd?CW7tO4~ni6UGmsHtQV2(&4#lR7?i$!G7{U-%a=64ITw;k(w7jP8t zk(9>YQAybjXHag%?W}&zab6gN%gt|jDo7vXRMF??vZtHQG1D)Zb~bcqu5i`V17cFJ z`mT$h<-fD^mf{B(o>(ZZq{Ym!G6Hoyu^k0~rufobHfILb2DF3?T0RkWIAz951|yYe zH8E>m;rl-W+6GQ3DWzZyAes_hy4@H0MM)sPSy0Y$DwoRde zn@iR{D3E?SuG^}5iA_ryE{C7+8c9T6xpjJQP*6}CE9I@%-79JM^`MZeWuh(!<8q=C z(eO^2w_fh-n7(+DZ;hSgi{!wL4|I`jl*+Z@Toxh)5@iIC2b`L27Ka!WN6VP4;xNmnppKO*jYeAy;=5jp-*PX6T{*T+@nV?qDn`Wez! ztE_~da3E*TUa=R@%eYfkDrtkTwCNFe!1pRZx&1wVCEBj@&q-QBR?TRV5ys^k9{W8k zdqiSVAqEV_!49fnz9K2*E2r98oiSUXCh2=ZL|z^UD|={jAIX4Uiig%J70dq~S{^Ge zci$?N1F^-Dt$pT={L?#JfcF*a-!Co4L+;JLgr@+mSO01I|1aUG*qGVSB z_(3#guQrd5kP~?>qVy!-F>h&Yt360GRMSFHqMN7>E^<07#I}YHKqMMa zawyrgJuPZL0lW*ae^7Pzf^IStkcGwqa2{+{#T51v`@$3G&^UHLUQS>Zi6(vwsB?S$ zIO*L)P^?YTC%V1iplp)(o*!LmG0CNs2gS2nHCrkRD(rS3XCg*ob=^ConmKmP8WsBJ z?8#yA@*r7}KXC~^Nj6R?8YOQEL!-ue)tO|elCuS0{IOg5%eSXdJv1_A0|+MXAtbc; z`7^x+SVAuN&FIvS$vJ{@1En2HS9CyERK(5a7iDyyLV}tVE&QL`e!nR!$Qv&x&(RrD z<@t}_U%OmV=-c3Hy}M4`^c~ce9yP10>!p}AwX4bqV%Dr;j%}+!zv>X}S4`kY0dZw_ zy2gztXSy-69z$R07K9S3GX?_G*-`R2iI-P|BJ8r(uSVGV8q`OEG z8`3&IQ+aqv4*~Mb=|gU7jCl)Ids{Ed2WU<$T|!|dy+9vjMi8)(x>P4u5p{Fw1%G2X zz$ziHT(K=(Z88r^@}XE=ndIY;z*HpRAu^E{GF}n8_VjBWk|7GA$zM_C6<$%?L_iu# z7%^N?mlh(vaNZnMff&`VC)BUCD_-o>-|ivbL%*r*5{>a9y)fR`hO8tx;edD#H(<)g zybzfv8DX?;%SSASyA$L{JH*m&q4)_j?Z*o7BqKTdA+}#fL8`54>X}yuCs$HFov)AoeCsB z0yKjs0rBE4aYAKm%biW^QYayqQo8+?&42iVh zTe8H>SSpE2$l=)Z6v@UIAaSk52tk3-^~sI=mEiQ+ORS{SRxm{>7FW4dQ+E4TtU6wtUG-!I zx@m`t%Lfk*1?cZ|IYtK$80$ys*_XW9(leDg)laq{1$f>0m|iBfxO(@zgvT@hpQ_UG zDW#X<`WdL}D7~vre1#*n?S9==2$(!P;hHi|_cKg{B^}3D&t@%*uLv3qYeBf=-f)Sp zl0b1IW}aYTVfbN2B5ohaTJ>jTL?ekLi6HhE$#8;crq^;K-UzlMTWNQX=Xx<0Tz$G{ z(A7N2XtQqupu*TI=xntux&EQgqvpi3O9`9xDuvwblxuUl1SNnW_l>$V&?G1-c!n7VhZS^kBLl-S*mG#&8G!AFCwi7vtB72I=hxfZz= zkQ}4Sww!S_>{GJ%VY(>X-b%ffUz2WE#0pRCpeW2fhJA>YAWOLnw~BIt1>s&xLljW=lI#i& zdqMdpf&zod&_#vt-VfZau^^N%XYwZ=>G~6+)gj)(`ABw=U3mKWkr#rx+7?B~B_-hB zo;i4*aetwJ5&;mgVQ&OCXmehsO2h(U))(+Yi3#jC=lweU^ZnjfCFW%-9!kW*Vp&91 zP9)+#1!4SSjzZK&qZ*EahO(UF>_o-0u!9E1%wLu+qhcCo3-NcbXtzyo@LkwxKy(k+ z@r51{>-a#82Udun*Q(cli`e}OH1gP$2>&P!7QC)6V_^?Y*99lhDFDC$ zk=FlvC#wfJ&6}VHZnM)ZI!-^?IiPa>ewLjM9l77RGpN%o)}z zT~Wv0mPz&#|D^Xx>Jv{i90iqq%-CMhhX_EMDvv;qWvlu|!`&Ss)B-`qLtKGT4UgWE zXXlb_TH&Qlt8I71d3wQ|4d+-q?{Q-lU%8-4+-&>S0LxI^ap!02F2IU=0-t!XdABDh z4j^DS<1TAcAn7>AmgqBDvpS#;^zN#+sx%Sf+!FD^H$DBC#dM!9%fa7;>9|W{0UN|Y zT{$N29UdCYjIiOYK>Ug!?e0slhiDFTm?5Lf>i|M%hdvgWc9TCEE~sw%kU5O9Lgn&nE2|MEVsC|wBM%pb#Xz?9LI}^ zyQ131qvBI29$N9`7KTdN753ulRID?R^ia`BB!r@S{}2rtny|bV^IOQC@6(Qpq#-VX zP8(Ap+(6}w2k_oazQ@|;Susb2SIC)bp(q)_rbnc}G_O|Xz5S!Zr(*-pbvDX7f4x+B z2a&H(i07&sazUK72?WsHz6WB=6@xmT?|0eX?Nh288+Vbez8iV)VWe>Kxz$TL^x- ziT=YY?EgEo#_Yy?|3jPj@3-7&Tk%*`O`FOWa+H=4)XFA{i6$93{59(FRaqJyg58l? z>NR#%i^Nh~py(dhP7*SxkQ+iE@`Vh>fS+Xw`IM@g)6e6fpvSA>BH8HWFW3YFNUUEN zW92F&I5f%vJJmvbI1*$X-ImUD5sUtXWsWCA4c7u92Oa?jL-Qx3EhI9;J(B`3-Lqul z+)J+)x_puR1T>&m(#$(=*@+T{Z({^5hy3xak4Y22bC zaYEP}jrep9wCZFSDCs#-7EK}eUqX?fUN$4O<7_sdu0D&p zq4sBMOu6=EO3AsvoIz^l6ge4#<^n$5lG>IkEVe#7@(}m^djD)5=3EEoVuw*ZJ76e;ERpiR29t{Nf+t;?wbW-llQLoo6Ulj3@r((NKS|6uCFNDCf$oMo$RwD1&;ym zU9mMyFbR0ko#xqgXZ<|M9rYl@$z{iC4EI;Qy0GkW@(J;bU$)E)OWHN^j+N-cVJI zlwJ`|?f4&w8EHExSSzg9-Z2q5z3-_ftt@p!MwG2GyX5Uqk^FuY>hdGSE&nHg;;Zt7 zXD;{-u?BY`x&Nsg-QAtV|CSt}t_lu;L4V5-(@$Vr3=J)&KwQP0>CR|V`HZC|<68CumFo>FD1^25%U^&&h&MWqdUR?+@kfAV-`XEbOE1g5chC(=KQXC% zlqp4=ASRW<(|&!pU)CAztvftt?@^#A0^~J1CV+|>*+gp)iVh%z{MiDZi5cwYy0LBT zXjEI##?P8&>oTf3AF=glRxvO#HfCP(2q-hEa%3NtV+E0GJ6v!r#jLdD9aD&x&bk^T z3L3?4z=iI8-??)?Ru11yInvq20&au!ilwS&IifiYnhhp4FdNXq&v9UQB{zg`i+l&= zfdSKq!vSig`z{9-*_YZrL%1AL>{8^Y?pigVKx?X=|2Po7Hza5RfRC@Rgvv8!+3ge6(DJSqLm|&muR3E$yh07geL~?4dI0GlZa2UkDZ@Bj{V)C=(TIzQ=Rd1jp#San5v$h|0xg;L z7B2nJqiGl(P6jR&i`GQ?3NL^puCOq%q`^%jE$*I}GD%44a+v-}#FKcaBU=9?VdL;; z!{%RoL-p_-N@9{f!M#7PZfAZb%7!%HdlRNAqw|lLfcu7m-anDaJm$A)2tLoyJj(`G zTTuPyY1e;Vpri$#_FV7<^iNG#vdVwQ52{`M7f3U{^&HU@#{3Nu`)lqZDt<`B2sBc( z1AZ0`5ek`QQR2U1yV%|o168p;=78Wp(gaR$(KIQaM8Em%1e|Q$`cA*zKR+vfK+;KL zi|VGhkVSV9)hCs~AC*WRPnBF(LiDEFO%A5VSiq#3l<`K^{1gFjHe1@hXjyJIwy*P; z&%nz8Q>KG=#Ltt@)%ll2@{`I zv3@El6fqmC(Hbc-kucKQJ{)aWU;I|7Cpc3_QX|&6x{1Ux%ip>`hW{P@^AM?q6ppg_ zq!`wf1~4HfjtJj$wz#9lt;;9LS(snRLsY?sxGUt1{OJ%bq$X1K!Jm}ukDo-HakNvu zbmV9>Sp@RJ3gBa7xua^=)YOBXM11M?;GQp9b)$oA(pT&E)I>*$?9TAUqCcYRGzDqsi1#@RB5(8}-t8=69`F zf9LtFe&?{@qSBmY<1iL#N_9qU&+?+i1v;8P@($bgSOPqpKXTqZxgdFEwI<{3ekcxS zmAa6YR$UJ1@F1nd@6lkhZJue$kEtrDOPtebVy~aj;Z`41m>4GoAh(|`|M>$QgHDI4 zo!*OeSWP>YXjk&bbr1Qq(9asfVzcv;a!H>yV~`|3#`hT<8g~p1jVno0A}<8&=6(22 z=LF01)!_#C+`a!FzOu58&K~aI@Nx@)!+)1L>gTTWpYVkl+qo1m8ugr7jjF}Cf%@`| z-bE@7_+L>!9vs23$bRC2rIB5_R=gv4lFnC^vhx2xHhzty;!(@#Y1Oo|wsmPgec|=+ zoXEQKp|p|S6cwTkIquG(K6Ei0 zMA=MpMuKV3r_JX%y>nI_#30^E{5YYiY4`Q_{1)L0%uvKDTcvF*Nx?!>K8~W1Y} zN0rs#rY#R6Ev6pD!X83h>fUy1n%HWRGm%$RbKde@TgJq=p4`kh0cC#D20b&gRmKMW z(Ae>vrz;r6A4s*>4j0z@TL8Ja`gS+4jHh#+xqFa)qo$28NW$}+&~eGR@WEqgK7IvR z+SHHaNK>P9IJABPco1i+S5CpZ)uww`{sTRx>d56t+WHW2PP`ikI%zv>0|lXpOVxD; z1GOnh%pW9xZY7KzyI}?@eZ53BuYQuv*H&YWb(gV_8Rc z3s*~knZ>{5lYnQU;HpFz>tiOD5XAmF zhCr0`<6#L&_7J2c%w%-d;}G1B#K}e+S$;!1F1jaD`dTD4W-XccuubIv!A)ldgPj%p?=x5uloiC= z3Zp;yl<|GVbN3LzP#da-y_1~Ol{}K>Dtu1;06|=He^Qv5d`ucLzumugKo)zuzu?A$AnFG;wg>}mdhM6 zfv)MJm&SVQj4k%~512BrNn(2arXLbN@lP9%0anMW*HpZ?46U+y7d?PKEiUNm>qdxj z^zngJI$U$LcF7$r_$T!Agbv?J1(Zw6WT;4FTjqu4?8`S_I3Y>luAr5F(2naflgdk# z-kEZckEG6Jr75wv(x)HSd6&#pkIq-gTUK3M{?5)YnKrc$jgOE|CgW8; zh{v(OSDj!p7k;1F;yGGwHu78Mn8{4*q^+m)kYz~`F|OM=h-x-W)$75UH|Aq~NQ}52 z+7m)z-awdpYG;X@YZ}w%LE+M#=F~_NIA^?9nN1=7onXA4Qrg~rO46c7%3<;y+B=l! zjpm(MUS3v`f#=S1_t-;xA<2ixPYuB?5Ll3oNLk^Lsah}%!_397g05W)R@ftWN;9Bm(2<#&;2k+B=@|J$!&^#Er*y0KNBLKiZ zLhv85^#A%J{%imHKeY`f8k}7;;Z;A|n?VX}9z?K0hc~(yZBy*D*?q9h8Aj!-q;=W< zQjr4w_8)PfqnZF=-g2;Z!+((Fec|%23}swAe#$Fy5sN6O5KltIt1&J(6xai9dS|Vj zYWx8gLKJcj@*30u+7OQ5;dPO^`4V*-Lp)}w6uA&^sLz?uXLbl)UGAV!qaY?p9357U0ZTOFA3J}uA=iH zkR+v;a+N*NHgl+kpjWkm%zO2#+OL|1zRI&(u{@i5MLkj1O>dLUYxuz#xvQo3DK2^W zdcB7B7DM?N97?7mx3iREr?9sjW{izo++GH-Ff+bzoV7?3*jZ(0tpj3-*uXu!RAC%* z)p61*h6xhe_HDuRiq;TSE{QgMuMo)io) zC2EX`D%U(bWTWpbGx)5^X0c>Z`IfV-xd#x^lcORM8c~2=+a@|G&)>}H90{+%ox+R! zGH~O-68P0CJ;+W=4+wpC_(g&>i$vq60oW?T#Cz*oM7xVAdaJ_W>MW(QkGpmh(pS&P z)IK+sXAzkVtr)=kt!>C0%QUmsx8xbH3OoG@Emn@jbu~#_rscxy!D&ly(3CrM@eO)= z62#!uz370V$B9JlVB#lok=lnZ6bfUX#&Z%|*s2vTp~xoQe!PlCAYH3wvtaUTJ%x82 zukZvCG5I4wI}&ieV{r?D2VX%)Gx=+SK9fmz9Zhv(0e1AZ_(&agUU4WVP$FGsbA<8n z26_wsvk~YIs8isxgwcSYt_PCwf7;gge?hHc?&N0f;_C2!n@BGHE^mg_Z=!kck>@J1 z1r2J55SJ#V1W!roQVNe8KzfAyJ~xNCnj7|~Ot+8WZ&u!ojV9!O(cAX0?(OBg=~?tw zy#3El+5o@_%L=EMST)TKr2PQ(DsnZ2k#1K7^;a=5C*3{*a)?vbat=D3ro~csq6Qm* z24IU#a0OaLQ7P5B*Fm#Nh5NB$)4E}mJT}y_A$vFUf-R%V&M;}pUWzmlbuoK| zW$a!T%k-q*Y(lq&XI-R+RB(?JB(#pO$!7B!aYNPL(`$g0xO63OQ3(q*qLs;qMOq@H zmq5i?`7S6nqe2-AfST%L$~TqPi8q8=f_2r>hzNzg6{GU~+`+_i#qa}Aa5*}L1-P#} zfc8rP&1;spWiyKN@0O7Z$gaVU26U1<^Aqv~c|;}RaQObm%h?~WrIJlk$^*gn8wj?4 zn!zWdD(V2F6&^qqZft7vZxa5m?hr=2lVS+hPI)C1f@%bd2T;@aegHZuA6Mg)dH18S z#!7^>UHZMkzuH6ot_gS5jR>Q3OkZ;W`Zgz89(WGkU+>PK{86n*D@|ykph(5&Lsmsm zqGQ5MDH6)q_6*WVZqoeg{UiKs@lSxO^3b8}nop_5l{IB!wDuy*&X7 zswZhPyoaYmZuJwD*X>26-U1aaLor|4r#DY(EBL-??{@yab;q{AScGXlzpS)Jk_*-v zccy<&AW5sVY$ZouBzDO^^Mao}O1^_n3~Pky9>x~&yIQFcfCEI^Ox}{m0 zKG?k+x>&{ol+v;1TlvvZBzYM)(hoexPzF%zDF2 zp>S25CX^%+ouXc`DFTCQ$Tz!eTU`G`d;dVx!Q6Bg69`fB|A*y-oP(*c?SCknR95Y0 zKO+mIvp2fXS$!eV8!h;rKjb{Kq>w8ir9J~o79!;o>YrRKzcyraO3O)nMdbrB*(FLB z^Y}>+7Mm-UdM8KQ%JTknY&kVEHTzfR+b5;|AavH|IC3ZTz6Nw~J;gX;N6|icm=l`N z=ENSmwOB{ZKFHqM7Mr_}%#Qawd+#sxI4RxL_kFmy7EKK{=Xm=YQ_L%(Wt*l$=~a(> zmCbAE)EeAw?Zb$ojlaExhdLa))|)s*w+*iwpOcx`2_|8PSm`l{k|FbT-cztuT=pgq zu1s9B8$<-X*0&|U`EIS?==E7cYSXh(zq{rx?fIfF$y^Gm;WO125xOUGy+aMUwala* z1qdMEZ;l$FJz(#-SQ!|lyoa{EM9JdVG43e#uWad6B_K78vQ{nPITx^2j@&7CkS1Fl zl~&G|a&U}%bcUKhQrMzcnw8xf7gUaL=rfI$mM%N@F5uQ0lxZLikm=8k;EFoMqFXFi z;&#^+Svb7^KA+p$!@*dl?)Yhe`EjA6wl~uPQtY5{s7NEyYN?pvKPGW|vcHU_%ZL<6 zK)XSTk=MoU1ByV6EJ<=qlL_1GkB_Z!h#w@z?FU(8Rrdv4s&ZOBE5&K1XW^Lz_#PwM zL!p+3VYXVA&Ol%94XIN=CWW#4mf6ydq(6g~Rk{#qg@o(%+K4J0KkO-IFm9Hahd zG30nHgdM>+SVCqwbk+jrh7=};^Sx4wsDM=X?K72m`n3M`mX4QpJ=%-kZ2hHH`=#vW zQ?L$?x|OSIR)-W->#n#q@Vm=NyIyNov}=Nx@Oejtv$uq7@RE(W7`U|zKYCOtw$15( zbY(fV2Rhem-_eleMTvT_=!GA*cWFrS}4cf!{62US%ZyYo%iDtkaNlriD|%oK)_Qd@lb zun--`5Kv7^YuamAnF}!W&YgDsO*B$l=w~^$vgWop*3&{yV!W0=${~*B0?k%y^Mx}= zwi#X`FCv&(#Kl_iIhZE5Tat+xH=PgS1^kJ5Y)8jW2>3(`AvaK;gZ5}bg^lRWd|iXo z9@Qe~m=sE(OkEh6^*Oey;5Xtc*dBt6-REUdRP&$F87IU`V9!o)O)p)Zpd*(1Yy%0J zhCi)$I~`JdO1>*N<$zTrGyLyc|5jj%u^EW)x)aj{Dyi|w!Vl{DW!+o zH}+~haD5odVEc<#5(hbBlQrWG5LbR@iA*K7AUJ57S z=v@qPtezfcLIg`KPcfvQ+(RzjgZD1gj6I!hnthmPUaYudL+${T(yZor;*Ohfg*ft5 zrIZ>&{OsSkd!}dtf5=y4|BmF7j!hd+XDah)nrPx6F~&cjFpGsFX3Fs=HE%5O2cX(Z zYjx!UuqT*@f|p|jwyxAKHP3ba8mKkO12wPbIinXE) zek?n2D^%91J(Xqp$s!pDap}bHGuy(HiQlrRn7wOOQA(?;>l|{cCeZGHjydbpp4v62 z!iQ{unx1_f&xR4^*Mt2?!Jy8^aM^8S^$OURmT8r{BoEQ>(2p{_eM4as_^l;c5#!N@*KMeb-h@yD~(aAA@vB9QN99 z*1y%nR7~Zj7jbhg4k;R?EbDOxsRz0olNJcYhJ@V5qD}l4$aT_~l*lDg4Qk)__JCF3 z8QshQKi=M0R_N54&GVkXitv--f__?8COrY_~oMLUAt#edNKxo1khwckzN` z#=b;+p4${X6bMxQfPx3l{wB=LdH4otMTg1Lr-am7(_OrImnZq&x#||a56y3LnyfqA zi~YX`t;(5AOcvl@@MqBarzgImiHo`OS99n8e)~}UoBRefxSAiLLnu@70^dFw(+}Z) zz$8$7H6AM;&E4*$uOCZWiEG>%{ja{qFYIa6FWVX3lL7~xUfC|sfB6h(wJ>7(`J+!j zJ=Ubg0{!{z6;wQ&;-Y@w+dDOn=X9A$`}+fZ<-|_ZtY%FM;|f*U7yMRs_Yv}t7R+M~ z=>mBT#tWWrkM_r&%QbE^V*`GK-S3q(U+)_$bNSs3Cr z0}E2tXe^jkb9J|p5yqT2&@1CRq|Qdo?4cGhxdP+p%XH|&(>&sW`Xkx?-^6OMD^=z% zeL?Ukv1N$F^6r?TurkBeG-m4SXPzcU28Z_$SR3h_g(r zL!+|)o8X!r=;)mTF}May$oi+^VI^mC3lV2ypj7E!Cvu{?ohq&-x^E>@HK{~Unzc#V zQW|JoA~H-)BFyRTnI! z(xWdHmggOlF4->UJR#nU|LXtMn7ZEPU7)aiWR6tqyX*>4yi)2ZOkqQZJN;5Dz)f6G) z;_iLLInlbE>t62g*m0d?dQ7X;Tpy!rF{TB_9?*|!Ozf}|Ubl$xstxZ#!Wq!ZpuxNe ze2nd`o90}Z01J-YvM-iVU6`XTzhZS~EiAdvZI{m7QkH;GuI#isrw*Pq@8~0RJG%ZP zAe~nJ^s05h;Hlw86isKXf8Yynimq)^tb51{ zXL{xCGiAzpbOJEZY%WN520n#jdsl;2d;~MG8=~1|7ulc-n=-2j3*PNQVq%kGQR*E| zsM{~w&0dO%XO4VJ@+48yZRJNm{#RMv`xM3%C_BaUk3 z+S;pthDu|%tTv6bVqo#O#*9r_v5EpGi3!c>ssF0Riih)#?hP5@oS%)Of9g>-czNq7 z%aw^4hKpl-r}4}nLO-|-i_on(n*sAsMm@7NOWiFqN!Yw;k;e4Ac9l@s{Y`-Y*%J$G zvFAi5bUwm_h;UD~Lsq%|nieMvQd30ny$E~`ItOktx2fnfCCP(8rhUHAN*86(#B|D3 zL(|S%zr}1XhZgf_xkJGr7Qo|lV#N`X?vb5Ur&~|hL%+QW<`LD|55ljw31zl8NQ7#D z+vN_uNzg@vf{3_HK_o)}zA&<)E1YwEJHCqGc!TSXc+wGyKCm?4L8}Zk` zP{(&f3BMy6vd}C|uji59i0$9ZPwfN?nw}zNH}y0<(Fh}^d^qs2`L1Z(w@zfyq_U(s z_7)POvZb*2E*}bL%J1{Jh7OR& z@>Bzgevu%j5Wa)4MT6BjI93EbfX>Fj9XvaZ?Cv}<`u>)d}%-d}qh=LwfA;4-J>UmL@S(m)is zAz*hxx0%nnx+9V*YoRF*+voFT5Y|Z<;2uqSXf#Fjr-^|u$OFX}c+vf|d0Vc9bVV*3 zyS#xe_~0;vGCNzlDj`m;JC0e87K6I9)q#QdM&r4aeAfJNu6sV+8{1!b+N7>;6STcQ z2_IvtzfIS6h)zryx_#5+H#r<%aA838+KSP(hg?Sv?mSfxkc5$BV6<+m&TcW;-1C(@ zRdrohnl>%cTfafgRpVS#aY9%erbWfXfTJ#@&Y?F}?nfm=HoWCBJS{H97EjAT1rS7{ z9SQd}9Cl3U8D35`37pwE?s;WZJjK>vTrXNwTY8_Pxce@aW#TO#j$k5@0WZ2jrnny~ zg622rDcPkHZ$t+T3pbk&b3LQtpXVBZldsFS3~(U-{O@YPN{V`#^BsCQB6^==s>+< z!jgmP;Td;fi)YwQh`pqXBJ=n}lh{>mLEfIi(5e+fk$Mr<ds)Y|21^~OJ}uvq=AYmzhvh;InJY)?efB3^y}mG5yA)s0tXCRcDn{1 zXLtHuY(P~m9~dE5!k$<(n~;?h6C0BlBWo-X9H@=s$shldZat1#g2lY68_aeG41@HNA=TG6fku;-mkm=+Yn z!GQ7dH?uzlJ*CU-XO4h!`UWmg;pL}r#w$tc9(7(Ld+QSxK!zO|kjLUkTtsQQHFipo z&&m2d!~>m-3I?ZQ+d&F}eDj;6P9mC6PP@7{)0BJI`#rdvNhxH#}JT_3nE| z<oqzJemv_n&ZDEMr+4)+Z9;4v8lM`kP0onT`HxZ{GK4 zqpf3nLuuVpSBQ<^W15gzM9fTwtPd<$)n0gZaf(9KT&G3!h4rJ2k8Bzm^6Y ztcOjdLf4;%Id3*QuoGeD+#`T+QLaFFSynYUXsmW~4kaw$iV1zkhuKru-Rzs{4>^ zO?)>-(oM7o!t`+T4S+c{VFDp&06)*m>T)Zmobr$T%!bZ@4Vn*E=iTX>rrLEj`lx!e zc5E|JxWL3Bt^B(^zjQ6B9)D6cokyJ!vpkOUVE)pmGa@I`KTH8TZzNr=<^`3hSX{b6 z2BM_ON!+ASZ%5QfeMsD8WgZ<R}BO{Btc$*o&J29ccI%A^x<3#Q&(Dl`?bY3S|0=z6`W+H=)VQKt_?(H$NgvneAb*8=wW``K_d0=IrF6@Cr0}S}ldk$oc=e*{ z^W*UzeNz}PFqcZsmM@JG!qjy-@GJ{GiSiC^yVV;e@I%|nAfRFI6gsvjB}{NtYP_BJ z11q#bmMnB~0EOtg0~~-`OzP1C^_9+I048&jnM>rArf+Ws#XiE30>2WUghzxFzmmY1 z((^5SlO2gfT*}=~7-*r^+cB-h#*ufWIL!h}do%tQ91rU^Hqc^rVBy>rMUu^7;nHxXLtb zGQ8~7tcBLEB+|%*!&!}2@~Hy}MK%sq&59ZC;4ndoY`T5*OsqSWh&yfX(r&j8qga!$ zQIOy(!Q~-!b$fUgxd)u@D%0(bK|y%M#p(?v&a$`}#W?)r6_3GfhB%b{_~n7myiH~9 zmbsg<`W~loP0rg-ts74ku%%h6b#o^6x|(kRl^o_N5HjUhv2L6(k~?{?rfc~CYJ_f> zsqk*wUMmk>_<+YYu|$KX1rh=iYVWC2oOh30Tya=BWq7iY>bc{&-?+we+}0$3d*gNm&(?gnd+X;f@;pj$C1cON ztckpGR9rJht}4nZG8UT}QHX4?n-6p_E!2&%mRg+(cDwoO_HO*iq>+Z57yNmBiRQ(o z@Ky6U#36JIUcD5$yXh0*7&r2(Zd@F1I({jb1NojlJ@}Isr3V4QE&>GmKh>1|3+(^RzTzGJsDB40 z^`ZJ%zz2xLmBoa)%)YKKeM&uCDlJK}*Ora%us*g;N}FM{>+a+7r8G2VzXE&Ai8zRA zS&PD|_8Y(OJm+~hx0=lUCFBS88D=Bj2v&yw^ipX*hZUlYMykgE_I{fJ2?|_8Z9GBu zY~M+CF~5;ykwP^IuduKQ#w=lSp@pJ3X%VfS+OeGwb8PM#Qv^K|EB(V5WsD&2^j)EK zV_KRv@nQ)_>dTz3G+T&5ojzHR)P z3r|aO9!3_ecNI>?#B%vgR7gV85JgkcKqkHu2WBHZirV~iUS z-$y`I7Ih#N0v@;gzyrqQ_JOuBRARkIfUC!8nZFGI4K5~h2*b(yHig;46uadSOW=`2 zm+@>!u-E|3l%N~nw-U|H$L-#_B;DM&|9EY^Bz*Wt7%-lGIzwDdq9OC$UcCOgz+`#i z;?%w3b;I6M$RV*W+N7YZ&PLnZ>zWp+YQ_<|3dR=LS^9P1;zbzUqYk8XnHsR1n{=lD#=R9U9CJWMa4O2C zM3%V+qGy(|&M)Rh-`H)6J#%2#)TX%y#O4EbXLS3>CD>jFd=-h1i3hEqOJ%FxO<}QEl)Kv8yP=(P2RH@ta z8^26zycT2F(M`=10O@3k8gXmB7AWA$v%jo+pv{7QEuZ?X!?n2liUgXUztD&W2$38+ z$bs>BCY$@jTFb*&bhhCyu%Vov#DVFo_B1gyf?4)-OI?{1fxEKx5wgv6+gSj499>v+ zTe{|G0DxR{@ABvD8P~~rkz_-sfvFL1?@FD5p+iHo&fQnds`#qK=S=%_60gS<51TKI z+wcuhm-Loe7Fw09QB3fXx9~3sYyjFM(=bW+ZZWcDGF3PEKrqE-qa|55! zn?09tKPvW7lrhS5R7f%zt^-ncgWg`lSbS$Y0p&8|V{E4rz3V&#k$Q?s*C@2&@jo;xOZX`LCacpP z+hliTXwf~4(g+{kq#5wwSJ|q(HcAOOO5g-os>_1y*vwG9c7?njCnKbAhEvJ&+oK!> z;uM?5Ly{C%pPMTag>cFmw_nV^O(et#^c|^+9bV8Ab85hQvXm3U2GgOjM|^rBXHQ#% z4x8cq%$cyl4fO)JGHRA6DttMCCXxC|fuYYWO6(TAHDUh@`h$onhqZEd;9^RiUZHx) z60nzSNG$%li;J2mciW{HODijG_df=OKTf_4#n_rLAoL!A!OM*Y?GdrBb07O)x&$Y+EP2G1fFXc8uTxn}C;8Wv7TZoO0 zRJJov4(lkZyuz*!1+P9KK0wIZd?$x>b;DdyhI>L>Kbxi? zp|S+z=EJpM_2fvnh8{|VXynJ`qws5MgaLa-3JCHRV+a`ptn*qcQ>@qiAl7|)j_bzyx**o!d zF94@5jb3v!F;T3cHc76$fVQMhqP9J2skpFobbiFzy2ILw3N>g5Kw=zXd_>#Q5Wl-C zG3!?R%rRRK7c16Cw+|IO{rKzt`gkZ-saP$oaJQbeWsuqZNQLR4UnJ}N=Ux)SbTxfz zV{`jrYw|~>=mWQ(m5LX1+UgfCO3iVd^{e}Hb-J@jYMtO++mRMJ^)$=@_EZ*;g^R%y zOHkZMV{B#T6=;6l2eM!rVCTdY_4bLNPtauw(%>P{!!}G0*SAN^?Nvd3X^)YRBACsq z?KXg=j|V0QZhUG6=6i~Fd#UJ)~yaby7 zB=janBHXef7eBs3>e;-?HsJ#K04|5Xd(E z=?tx^DhJe=xw`#*C`YR4ssckA-Z>%3@o~_o$m^}r4V+oXOX-MP|ksp#?t+jg*D;`z<{)dQ}KV zANBg}Al$Jjc@c`5mOED4eyyz3>%xy&r06(t+6^9YcSty7YPB^S-7a9uWI8;$*n{(* z;zdQgRsZT!`XXUW>PgMz!Hf()855I9mSIx+6hbGnc*Ug2U`0HT(9JAe<{!dqETj z36+K)(rp>xp6y`7YA2Aa<;5=f;{H*Cn7ut33Hy5Z7sr=JPy__S+>)OK+l-wiDYqZ0 zGNr!;9lM1ZL+jp!A#fRub$Y)o;jYklezN`?z0*ljT)QrYJ?uVnDN3^w12v#<8$<-Y z1|5G&N|77{=;lV*T<7Bw%Xm~Cay(tV|P6NoHT_czkOuTU}CS` zu5u&C)lm=?SQ(5_9v9tUXWzDwThMmXUa6@+Irp^YBVNTYhpA`aV$)N~y2h&>AGE$s zoiw*AW+;_LD117FwmD0i6n(D8`^FD~mxc9%d^mW;qS@D^R1zt@OkXDip#_886PcOX zyse|j7AKiOqg#Qn!hHeKnA)T+PTbE~i%(O4UU&ZkU8aoF#YFv_ zL_Un)#WiZ&z!Tvf^=R;tq<_86Z~)p9qxI@aDdCHF2V9ZB3W;Q@`Sl#;U*BrVC4{$d zaX&Pxc}3oc>QQ6N%Vn&f8CpDzT1o6JX#aQ@fXO+<>&7R7=)#lQs3nv8j;HhZi5Y~ybn9h-36^f(Xd6Ad{Ce&wyk2Q2U~+5=w7eb5HVy+Rri@ zw|4TR&Ca-|iMitv@VSlo;I}JbD1Gz{c=v+2-aWD{Y)ISOG?;B|*B~GSu~37mOJPWV zlr&+5pI-A_D}d_9RAA#+4q^^u6F4TpdOtZd!>RLT*P2n%#N8A$JZ&$$v2uI}czP$H zi3u)9AtVP20>BY)@se=u3}HOz?q+C|3E-q`y)d!hL9}Sx3UGh|aGR%)$V-Q;F0q8M zyXl3uFhlPLn6jiVRgMyXD>5~-N$xtOVekYdE_@7@of^ILu+~0xkdB+BV!e1yeMGLRZR^%6WJOs}C<)co;al1mne3OT>0j1ccoJ zZ>XB}vK@Dc6nn7>`R@XR{(I`YvHle(4TPm65SIT`;{GpKO4>S@7~3kESOfFF|BX#Q z#o5o;gW2}gNg&~>uNBaN_Ib?k{qG163y7-%RqMR>8?N_=Q3jn|Egz-xN2vl$2cE#>nFFnWm2Q}iuDcYfZyow( z$}^i9Mm33)p55`eqr0j(UC(}`(?7JzKH_j@ITGX-wf4Uk)}m1;U+D#;KKthVkflSo z%3o1H{@_eFi982g^EZb$Wj{FqWeCzcd>RyqjuSD1iN1R{Dx!{R$=U-}pymqKU=;~Q zN57(>pu--j5*S7=Rt{ZL%cBFc^-A^IRnlcq0As|#7tTGPIH*)c3D`3h%SVg{BjViIyrUtS00$bAD95*pBjpJ7PlA~nt zf2_BEbiuN_NQt@qJ(5o~TNhY&tQ=+G;E}@@kD|*m zd1H66rPANkR}D+?TKCCfo0Gl=Lurdv#i~yht(?R#eT!#r1za<>O7W6k!F&nu*cryh zLA?v6w5-Pi_L~hp1hQ$$#?~(cCuFx;gVrx7wW(p5t=I>oK|@8dI5Us4r^Q9DG@|)A zWbpclCHki8e{BIjGls*G0O6Sl6lMI=JdS@sRMp(n&DqM;^S``Bk!p6Tci2M0j-qjT%3XPZfM4)8*BP1)V!O9U!($x}JQqNN(S)Jj$2--h{OxpRdJ#r&UE>tUFs3JFANW<0rBtzTD&gkI z@7dSC*(zhHt^hleK|cXMk%MulgSX+95V{SZ=a3TuFDU|2Ys>L03cj4q!xrige^vvv zQD0Yc6IVS6Hc-3gnCC7ZQuk~sPA%18b`vE-W)LdeoCU~dDu_I+Rn9I`GFRMYbV?Fb z37?@!Y@Pc-q2BCxR^NTp$ZibLC-QK?9R=$#4C!=-XhMN=j&R@zDRBM%mZuSmpw0pZ z6n?0b;}&X^Cz}@xO|GROhYtOAJ_{&0wQ7)w9koZ%Vk~}ED%W@jPgyLtu~t_;aZc5J zgrVS6Z^vMQ{`i)w85Na*}lmwIMSVxu4Ptz(ft&u!OLu3XLSgVPj-Mi7n_(iR{L{nPoFJ zroB}w@JDO*OmJ#reWX?SxUI#FWxcqHCpx@7yE1!Wj-yzG*mb_Z{U zd8-;C82SN5E`}m0Y=qw$qvmh#{)mTdPIeY@x@E+%xCXNt#R6LyygO@wD?&?5HhEl? zE#}8prLQa;@5nejb6MPoF8zDNWHMCKFA;T`nBnrMzZz-rL0cDHYT^so7mi31^%I4j zCOpLY3*X)wbvjY>nOVxW>Gk_MGXqyM_B~o7Klr$Hlb-zjwZ-U6xx*hTSootK&sf_1 z58Rn2a16v>&#?_eUpKIQiLRcpJAZiWg1#HU4;q0^F3mJ=h)SdTbR8%dx!hsIU@>R> zAPQujsq!c4_+-JA*JKdra6|EUk=fIC6T2;`i33^5Ece;jtXLEpwAtYuw!5W^M(!On zrzO>r*&Wu{7hWhRtffSfA87E(3{y?<5)38YX$9}(rHS=Jeh!Mc-i;feiO>szHYyRKfcpISq3)^?XC}IBG#BGGJ zRlW(vR?Da>rI!W_mCu-s4mFafdI)8@Qh!Au^4BnvbuG~U^+(O|052!SzPz&6{;=w~ zar0Lz#zHlNDX6S5{b-DnB)&2ZOJA=RimMx7EDS}3ihM2cr?gaXY+Md8gQ|ywNRn~6 z4*T)APQm+^s$y`A;;TXmm3hp`wHpc*|R#5aJ(hsSYxL&J-zyg2;wyHX?famemK*q++ z7WxPW-MGW|Du1JrK!7}1s|7sn$iZV7Xunl|W?xWZ{(-l*ryLQ8zksnn*Yjd_!LE8)$ME|(vdYDOAAT7der;6;1IYqzgsL7BcjMAaXoi%LlC z5lY&7{9S^Zm)})ylWd7^t;*>ma(3a2^J9n^=M784sezRTbJZ@{dt>?cIZ(N%d%PS! zV-RU%^~g|W2AfX~S_zf8^yp$e}nUBP(czm3gqQh*$7)xaGEREQS^n7I$ z|D|gspL6h2IIl?)#G7v~^djgWa((kTrr*^g*BbMMNhq^l&AGuqum`nX*hL zjQ?WZyBv!)H&M66CS*_rkS|$r6{=3#a~#hWOrSLqeBE6>&l)oL zb+%;y@o?q8J*GtVd|8V^0oO#-qI>|9Ad&F}4cuo)r1P`-Z9 zt%9Ixd`zPF3qmU(83ABVxKINUEL&788e)D!dV;z`Dv3wUzaa?n5@?Izgt0jBL{Oe( zvY}aC-uV%nkv{n*wq0?O@$-?Opbq*K;(wU_pDez~THj$o;CC%vVzgn4^{}+F1 zP&NJ&rSwf!HkuHkCR_s?1)?4Fw~QRV{w^)1<(gtN+Tb;w^_-Xso)5$8*C(rUS>|WQ5u?ScQB(<>}<1hO=^_j|- zQr)HyH)L8$D1*7-R8#2rI0Fyi`^I@1JU+fk_Soh?iM8BWIzn&x97CR33C6>ZwnVR9 z<`IM&U|H6hckg8Lxnoma`b^XM1ZJ<+unX9*zLnE~8gpNx-D1)tcCHSG-L%^E%ASV% zclPgo7ENDFt-Z50r^QEW6hU*KF7oZ$R2~`KZ<{utR-Ky9AtgL*J})VA$k%MPJois{ zhs!*-Q}p&}G~{L4uteITRi}%K$?C$a^_+nZIFSWV(9>y269>&t9m2N=r+7QfH;?l5 zSWdkdl)72UJ_ek%z{7!2bTR*$w%v!z?MOv=@?ordXJ4GKt;B#$b;}yx_o$m%p|=I# zm}8Vv#E51YoBu>tP*mVIgj5@iis}EliY4s$^jST)zZ1U7AOX?(wm!+0m*Sioo{1yx zS;#epV#5a0N99F#CC6j-n{+Xc+|d*H@pS9q5(ojFF;3%$89fg_bYldFsElq(-UdP5 zJD~_|0zrjJN|(G6l7W}Hn3a4fIiGYEIVrpU(#$E(5*2`m1N{7cq6j4qudB}zUOYOv zleD%yuI2NsDN<@rhaYiBRZOq`9(1AOuaJzv%K|2Gso#%qCejqw1XqjFcldc&Qm%Pj z4*!G6Kj%@IFM_cK5R))KO!EFir}iHB4|7jWnru?E_! zMYu9m8$s|eIR}EtC;=#%B7-8aUtYoZUd%WODC~-UWDonznE^-8KkuGRpK~oJPdPjv z?%HQvHu)az52_r5KH2^x-=W3K^&l4MGhr4$zk&{ z=}8NPCxnZ^u{bubm{%^V=oqk%utHSrIq3#Bf8N1sP>E|ZTUpE-J0m3x$Grs zlsNiLj$nY({f`AZi7`GjLjk+x%*06`d%T?i_as#>s!+6-+N_ox_NixoVL(H$`L@wN z7eGavyvXPlw(Gm;1m<8l1eN$A8I`jH39Ob&ZP#k+vW^Wk?{#cBKp3lJp^uio+xa=8 zR3c~hF;#k)B;HAtxKfU4 zC!4AeRuj7Q3xpCG2{%Z?NpBEgN*d~ry{EoVBacTx3ZBHvfB3csXUP&gXdCf7>i!^9 zNZ5&!Ae~#B`dj$rF%N0BL?6$XA-Tdk39P6II?(+pCP&QLwPT1J+5R40l7jt=cw}z- z49|9uLXZXW>pvXYe@@VJ?MGB%pymt({?jMce~MN&M@I)|*MGlbYFKHaX=3{Z%dr|` zgOKUuD%t4cf}$c@GY~@r0#&A{W_EC**dpv>TVp>>9v05a)*C9;ZLn`{>i2<1DNyAZ4 zY<|LGW5kA~__LsBhr)61^eYF;L1Sa(2wOg&+44}5VmV2$Elrq>vVbIo`h3ldmTts1 z#f`E=JqFiRV0}fObqi-u^tj3oExKf{JH%niGT%IC_sB9fG#a%6VS@D-{*f$>zKdbe zQwD0V8$*+hB9Mda6c4sINm3+z9^s16cy_X~xxr1%HB&K0($?U6WWLco^b?q{jHeQS8jcAkPES_ZU&>PsbCPC7lQv&LPN|L<$jWL`OsTpgqPBc0 zu(rZeVpXbaFwl)bP0IFIEttzlabBwDRY^_7cIwh(o#Lc#!=-~E%Mr>)%toS}w83m% zau=K<=d^^}yxyD}0EvoQlW;iAX`U;Kqo8zdK;>szI$y3sF1H{t7mvSC)knYDHDvNr z3~L);k_MINHsEoD%|n}#YWnWHLS94_tL&OzS2ppb75;@||muI!xt=Eh30ZPzZEfjaS1vZu{M zQ3NiH9Qbws?nAvzHiRlTppE!6DlX~OdXE!qFLl_(gV?6At0u+~HJE3eo=d~R(eD}B z8^z~*{}r&h%AGLJgH;f*y9dB%%Av1xvQr~!>OW_w!eDE_! zw$vkfwlsJ2B*kql_L(Kh6R4px^)H(m)Xtv8u@hey@5^d{esRoHSSY~Zl0mmK5B|iW zL_(ZqD-dvT6$Bs>4s%bBPqUYKgqa1foM{v1!0(w;&=yx}B>E4B!K_yo^EOqLLi5q#~Y86HmAXl(pDT7_JP@bku&)VM%R7Ox`k7E!G(~+~b zGWxk)DIv*O7cx>Q&1IVm5oZ*CorVRuO8S`+Y>2aO)eeEGM~1YUeU!nyAoV)=nQ`Ja z!K!4J{geiQAXqR57Zc%yfNuOGk&>kYrQ#W89=gaPTj~N`-AOTHejuFf*N5LZO@Vf% zHNAapk-vDu{=MFC*=<8Hp6=l|;SoQeCD%8VW%&D==!bZ9?6R3Pk_d; zkB$p*4(z;GNltw9m(zo*>$;&7g)@p%`PoGtSCYj#Y^65P&sy*v;BG(I9p!o@G}9Ps zpAM%^x_iYpE4s<`UZ*%5=vGe|xHFwj_?^Sy0wJU?En6Aap8pSJ@8F$>y0v>ZX_Cfj zY}-y6G`4NqPNT-QZQHh;G-&X|w$1P9K6{;a?LE#I-+KRtdCz;|x_-0x5xJKmKoOeB zhCuT>@dG{OW-Wqr_&j4cu%jkfZQ{b4G;4|`KpZfo~A|`!=tRzU7X_f z>bH?TFr>lPT4PR`u4|VlQ!LmD{t9P>V&~Hy1r1WUC{LDiM&<&;L6MUQ30Ckhd*$_+ zG`vs}b}EOU<%u0dM`keCa5~gq{iI+O_Z2k|9oGRJ{+kh>GVRyeedu#i1a@@A$MbX- zy|qK}el1E$*dYYQO`4{Oes|S?*5tm9L&5IP-y|e3rS8}G!YjqCz9HUP%Nh)Git%rJ zZs3IR?f$gxjOt4bIWuaVeJ(xP8AhZ#!y3HJt2+woGL?7hz~$F!r#nmr^0|tOO0+a; z{`J4QHEa0yX<}e?WDnfa{?q?P3Ft&~H2J^K?7wIW;hC0DxrKWgbhISk+hMbtZGS_suVHrf?HFuBOxO zEdbmU6Gy$fHn73pmd{yZ@Xq9QsLxH`mdQrgx!EC0vF}T7qL64lf)~m@HU-vP&Mrat z^KM&K56ZS8ISG{UIqSpSD6bDHW1`j@j`*YDag+MNY4&4=L48l3zNTIsd;y8~N658m zC%n-<9y&JkN%5{9J#a$i8IC+xyHt{iFDI!#f<(Mfj6UpAVGgUfYsyze^!cYUQaP$0)H2Q2Bp@(_365R!*RK{a0LMl;T4p@H8XOk z>P8F=^c6m$E{bBX7(SsQ3UuH66dpjHrSHaH740O3Z|*yzfKT^-7UY6i!ZfA8kgEa~ z zhl-sQXDAJIMb-&-2EK#|UZWd!%iAv6zt>#)tEw5nn56;st!GIsjkd^#)#+ugH#k!4 zr$HXsxqKa{l)n$V+7_~c%RodMv%a^RUY?1-{=Xm}xRT@0Dx=W7m$8^!nkiC3U{dsA($Ctg6y}o*$EGw@ZI-H* zn_5Y`ZMtmL(2^b3+KrkrMdr}#5fZ$Pq}TyhRHkYi2yrXi)fLx?i5AMS31z7;)s%QDq%P z<)FPAZvGA1#x(22T)2{SI_C_6H0zWL46=Um;4^88?>)}_X6aFLsl#af+o`24&CE|Y z(sjEk6|l`T8X+}J3#pF*`={qdFr6|70CkzJbzZaBgO4BJmEU(K5*pmw09JExaOe~6 z{Z~{s8dT`7mSm>lf&d*|m&tAMzKgxwxQON9(V3R{G`XNUZhj;B^5oKW{ zoPt;guWx_xU}U~QyL`G}(CS^t@(Q}l@?ya{{uV&*9`e3S4C{FeQk@Bb!wt^ifi?3J zltM2`;uzE!)Sdht)6XuT=L@8vBR(So+uJrwkYL`$H@|5CVqHe}fZ7-er-7>~w;6GF ze=|g{ANT2CWm!mXf4gw`rwPp({a7#n#ylJ_=KtwtUfIsdR^RBq5Yw8nU*!?s;R{de z62g^HP=)slG}RVo*K|M`%{7Ps36c~nSop`32Mxbz+NE9P|0PoV_w8d-O=NKfUoQ?v zlc@>M`i+U#_nRGbAJ}Uwva9cdf^by(eLJy1!){Cx`%m%;2dFd@YDzT)+oce2P;lY} zO2Aozeo6_N&G63`bA6TCV0PHUR*^y#SgzcGTpJw zoD(-KIfKz?jJ#0xL=x13DtxX3!dRg?yngFd{SaJTyD0wQdJI@@)tD%K)KrWjJD_vn zZY8MTv5FqW~$jk@o#-)BzKX&X>SL zZN$99Nh2YhWN@2flOh5qy6Yd>BUWSpelTum(&=lBB+ zcYci|nJ(V&Mao>4)id|bp5m!y*>KuMfQ!%76?g6`E z7Uw9Z)P2!OkPlNE7ltW$KBG#WhhVYuP(jJlh;Os5N=HBLSAPjL*h+f#i`|~NQrNRz ze_c6wT%t5E#fUCfB=+($$U(^Yy9?@1H%de%61)nm44!};=|2@Ws^|k}xi){T>y=Mb zfJXIqvK&~jd{VMIWP0m(6gg;Oiv|tF0IeXuA|JB`TD%k_^33bQ1a*zNdH> z+J1|==TBZGO)nF$W-2R%uS+kd7oIOJQ@s7aP3RT556vwkSuMr33>dN6wgIOfeJzne zBDSu*q4a=uRX050@X-m;^(tLR%kZ5S5<-hNMv-)CJ8J7=P`E-$0FD4>031>}QpC1A z*hU^v9+rS~PohGVhGoLV0-g;TmIJ>J0ZdDqy!FVPmhhDIJXW2SnUW&EG z6K2KbQH7Xis34zz%+=tUa5xv?mxESiJ0~&~ufLV2<#Jkr`du01$xzu+4RpVLcZQzO zZ!{G(x*L2bgQ6l3Fq=w(BmZ{b$Ml81U9Xqh<`}pe>ZyULN-vUWRKor3u3;j|y06ed z!8vX)J(4ps$n#_R4$H;0sgtgW^`NGVhdfxWLlRL{vrX5QAge?a2!nxAewD~y0`&zf zL2cQiv@hj@o#{-asy=g5v zq0zo~(r&bxWXUtr%hs@V)!egJW6uZRi>A%1zVJP7dsI>sG2jM*G9k2>`D0~qHY4{Z zolMLg`hl;uL8glw?i*ODTbrOUmlpmek`#}ti_w_EF;Yy8NYKAI&a9}Lc#Oph$`QT- zl21*=6%I5V!_o|Be@jd<8^?N*Bw5hmuoUjk`VBCJ`7DPg$no%UG0_ee9TnZ=l#_*7 zQ5@5QRt@su7`Sde_ELrwt~7b<2!k|FOE=z7bgLJYmVA@unRd!Vh|#(wOEIM@pUN$L zOkJaOje0TPlg_oeuu7zQr?u4}6@hv*8V}Xp(9gWdvzcGN3|bRgckh9*x56kRHC~-{ z-4H$4-)OF%{m20A935z-opY?>DjLMM5w^Bu<5TzED>o(HcQye~I~)n@2!viic@>Zy zd0-Go8%mTT_3;M!Oq+Q@85(VH8_Iq!VC4~y%l?W?JpJkDeRm!kvby&t=(gD7%MN55 zIzvi?HWxp^55G;RFJ9H22FT3#$S<;p*P#Z<9N|jQO9v(-2212J&WK0I-G{;*vPk{~ zq_zHsFj5d3p?gMFJrAj8n`0|ddTcRk8E($du8yNL;$KO!2cL5)yC$#`?sp@ zPpS}4zd28Qcf@a=t3e` z;mKM**aAVw2B%p43Z#Nd3h4+ChG1HKx#Dyo;YP`YZjoP^x0t+kInytkQGx*l!^-d~ zU|krx|EZ=EET=|jHb zap$qgEHu%aNj7BJ06vVOQgT{V+6wx(Q?o>#X5AWlm_XM3SSeX{3Tq6NVO}f?f)(z` zvt#)Bj?S_%{`qglA}&EvSTsswL05vWrNDtlt4wSe$gFZC%1h)19t1iFnEZcp4Rx*Z0hgjwc@PW^CeR=)6QF*h^Lw>*YdmGYAqnX5y4#5mzmb0(gvC^Xy`u z+L%dk%`)}J8VfjyfyLE_TJ{Ms_S~&lb~e2oQ@x{$NI`1uk76a)pVdk{4I>nk#sm+e zPrsb*J+s8MAt`r(?(Py{;xewl*WA3ZDQDfCVc22{7D7vmW-<35k9^Dh)o_zKU zao|&=4|Bc31EZ1qz%rry`60#oCHDE2j?!C*Sl{5e%R69EbU6_lVw5uZrLO%8KG0j!Z)wopq$A<#gwk+@Gt%MPT=Wa65H zfIfRFD+@~LVpVQA+m$I2sc&;%4h41zlt zug;Q4#zGdnAC)-I?sK%1=B;yRupyGFxZ}pkZXEM~5kDV^XjS_kS zr!L$Z=a0$&uLHyso@S9O{Df2nson~VB*J5fk!%0m50%?fAK$zvch-(LmaVtVf6=yy;W<8qk5Yropp%QOJvdrwsZLkJ{Uu46XJ5`B&$?Oc47wqW#2xJB3Vkk*fMbs86B=;;A4$;oD}MF zi%Kk-(QU`#bhmj zs#l_?Y*#|ER$ShbrReT!4_R}>3~}zAnM2javv!_W($(!3RxK{$`R33I`Id0(Gy{dR z3mhGvSm9L(Fu#1cCz=WC$2Qw3wSkS>xkU3WXp{80xYtnsVu|A>^V{RX6o9*N zzVX(0*5=Z*L+KB7SNxs8LtmrzL(|z^ryW3exykf-Yr(GD<|f>8N1U#HR%^@@4J|Dk z)1x|TeAmH+Zu;Vk)nOd2ZQ>2mX?rH{^Mrj8_ABVdCiCRbwnI=gAVu*YGh%KN`>rj1 zx_UPY+08SR65|3MzKsXpmE*UAXYTSTXBN8EIh$`Vy2=t|xCRnH;qIi$ti7c_HlYsp z9udX>n^EwHB~$luq-aXVfyE9*(eFG9+b5XRYm565l;zb1L6f!WTV<2zmqI}MkP3=6 z;6?@YPNA~bcMBfahxz~*q^B{ur2CayM7nw1e+b|_IR+JPLTC-Q7-ord^cGh@b-I6*fQQl4eA!rF?NG{!VbzNSUL#;&EoXjwTFocMc&Ck^?r+v#mBL_W z&Bl51Szxf+ha`IIp*YkF@RejhHZ!M52|0U*`n8Mkz#h>pHN@99fFwmWjGPZ5d)LZG z{)q6OL!+Nf03A+X1HBF`G5#sN?SC?o$e-(hrpSa29VI6y*M zl5%Dy~1L3!~-` zh?`3FaTX!pXlG)8?L8MB<%qf3_pEJNdx_{RCc*Wa_O=|McHbHQ&!vE>Kb(-a%!ghe zC#2oCmU!i)%M}sw=iJFDIP7z)eB)ODinpEzUH_@@v7BK1piJ+WD>U_ThVfoqtCk1h zxMQZly|Z_znwFZn78T9Sr7Aiq3JZ;Xs{T7s>tyGe_H^>?qnqkTT8u6CxE<|PmOl60 zM}+$t*;bnI6A`34?YcqAx90h9(>?ddp_v5h;tAYfeF|f%z@sS|!G4+S^!TLFge+NR zeDjbhTtw@DNvgNZ+S5w{HP$k83l`L(P6Ssxj2@vDm|bZCUuwOU6+u?mI9W;I2WJ7~ zkQtC5W@b^nY((z}1G1M_l-dX@?syCiAri-sUPD-~)03j9S-6jKMaD>MuQi$=_oZD^ z3d7@adbzK^M%W`dq|8Jyctl=zp&tLHbon#Tv4J+lJp{%hHqhJjzmG>nV<%_3zXqC? zwm`E5PZ2<&jw#2mqUH)OAD7x(6)!MSG|eQv*rha%cT*A4QYGw<)uMW10R1~C7&4y^ zeqf7va0uA!zF1TAbZght-PN@2hW955epJT56LFewF(Q0tb|EneoWi|rc|TzgYETJK z8!~&zfDi!Dc@7Ow@}wlqmS&=DZDXMvuZoFbngU>xT9>Cr)jq)}vniQW>EG?Jz>>}+ z)b`NttRoqA@Q6ct_G)e(vy-}7C1_Z!vF7w(>%XNCt6FG?S}tj1du)o$*B~}^%|nS zn2@Be!a7({a*|e+7S^<;)Es<7j=@B%JT5mGnWhqJxlhKPmi8XtMg?#35U!sxR#dg1 zT%SK48B`{^1=>HLv2hlRfH733*~$hNwpeH(h2^qz+39;0Y>PQmpK(w!G1`c0s+%>k zzd}i~NoDTey@GPC&l4?K3J>1Mzce?O|uF5$st#53O#&H zVSS}jaReP&MSpMq{9Got-sY43cbT(g{WbV(5AAc8GxlBo z({7Q7F{(JZa~H2~kU@cK25Q_43p+u13r7+wTg;-hIKliQ*Ok9`-q?|}ZO(hNk0vp4 z4@BW-&UE+)(nR+(!Y@iVR1hn!S8^q!L+wM?Z&9A4g~%K}Pcm_Yva~vKxeKNYU}ogq z@wo+E9${pWW`u4r-)2T`arWIXdPZG7VRU_eG=SA5^5hJvCg68uV!jJ+h$G|n)R9E* z^6~mUk5NU~K0R&dIg+g=f&UaSFl!nyIr($&@glL9}t1f|3Z}WKk58GHLw3Bm>Hf>IP0Mb1EpfCWc7wb zHcYxrwF7h$sm_?s<}N43Qwc8S;TV5}Vqj?hy12Jc3Fm zcKF~(Og6!_Gy;8L-4$Ui{$?1A8$3`9FwA0h(Gp*CPENmRIuc#$QMV@7jF~dV@F8GX(mv?frLnvard+8)yFUh}yk-+KPH@pNw{a3ys%b5J zlM%JBZqOoEL%TRfTV0j@C4(^GObzz}s^6^vr97T;38L4aaGZ8!;La;ZuR$p+R)=Jf zbiQP{7_Lt;Q zYBMze=Ni`XV6+m~sl92ZRVkP4VQkgI61&X!5&@R^*H6IdqArQS6a&tA+Z%iWhRSq; z`(2$JrQ*f0&K%8Q(I|n?!feiqSM|m@!H=%DYL-d4Qz_gu`N?`*tcFo4TURaUa&M6O zVrDd{HRtB}Oh?r0fLftwv7%;n5!50OLR&QE7SNz_FDGdJYjb$>OkyDsA8!H_U2cdM z*bl>&s3(0-JkYaGMLX9^NT6555%hF-h_5V0bels^%mG&*%q#|Uw*(O>;Vqm-Q?W$E z*rDTFg!RFpAoNn~Ev&n=4~a(*x&%S%o`VH6;9bPm8d+RJP6+5}$%bW`0+Sp{+j@B_7q8P;ta2rvzCuvYzKpVD0 z!qk)@L56N-8AUsY5s)DP-Ecc(dkgbCi@wq*pwg}aB+MvtiPnbJZPU4N9-OA~Fl&nD z|Kj_2e`5Dx$qKdy!6xd0G(^VIy=5qCAvRDPKpO_&T!gexZ{zWKQ*Kjt{iMw!+veq~ z)0a><*d1b^y+e#ZoIrFDX6fe$|DxY;DK|ijqyiZY9+7C&sX>iic9NKwR!8(*4lG0Q zF0D>kO=VBoSh|V|wXukTnzgeGDkKT7j+{oHu|0K$r`VX?78+x+*?Q5isd==*OR;C- z2&{C%J__&L>3WGrr)-3}aUrgwXmPC0yB*){?zlj`vILyzGbq_-6X9y}K;_DuYR3aG zRgXS(1*I&wWh{qVLFv&-!kkZd!9b=Q(GnMdJ>Mf(QTd{q5RxXm+p6LA{e`BR1Wt%S zSIRd=84UOqfkHOsbF)>f_GHztK_XLF^CDpvof%oq9FH>|3+SvL8}?Fw8^Mx1S@dKq z6;VQE0^j^L(YrCL>apV_%-&*xMZ5fRR#5CS$ooraNEt9XZugWS@zF_m$82V8?%YR{ z?@i7m>Frs_fU7%)TiQ}*ug0w*%hJ^=ZLo_S1F-4KP9GzHIhm9~=Rq3OLus|vl)owf7#B3%kaRV%m0Dp~4io#!bE z+!i)Etyb;`Dt#qK40Ll5-96;EMQ-i0=FrI_RGXi9u_2|m z`aflnd6N2=+QF+*R)tq1y;JxY2ABfTqnCG|y7)Qpdhox>fYk_bVD?b<9etWY%tF`% z?_2&fg`@+!DGRv)yGa^wBz5EWdwLXEnC}qpZg9qFUmv8%OA+ z+>f>baS5(o``4+x%(;R>ZMV)8#m-SKhjYOgC&=NQAv-)F&GKB*864i%u2CUGV{+dH zARxQ|V;3QK+iSW6G7)cKy`jHeer|Ri8hz+;w13TX4|NRYz|YLUxW2lz#JRqjK=R%S zFPY&A<5hOs(^bZQyST*v&p%zNY@Ny-_@^@hlixo*#Qq=Xu%N(S8` zZK99;)hKPkx=*{wMtywdQ6EjkMA|yJadVvfXAJOL|3CWgD-;o?e5is^;Axo)o`cqZ zDM|<;hlz&l*+V#y_$NFJ_y>3fM8t6M2{F)P5bbn(3=qYUi10#UYDn}_^*iEJ=T#aD zY<`E)>KN%|=xeBGc(&gvO|d_T=EtcqFAyXvbYGQI_P zj;g@Lz@22Z3fJ^t^LMh*y1TVH<_XTj*r!(ry=O?4Hn%jlwj~#L;7lE@IwezOZZ97# zl;TPpGf^mQY&w72PxBEatRNn)af@Aj)8}Y-i8@zterR0VSJC`A&4BshTDxv1>@d*` z;90Ym8PYUS<7l40anDsL9AsI;tlceip1!}^P-r-QZ2b`$7-spv9mIMYwH1@< z@p&B1geE0zr{ZQTFi1L(QS!nlqXgsofFal#QA-3v>{XbkT!aC%s`$?!awuKwo<%58 zh)WbNc8E*q$3vL{5>CN12L0$~U!hybQy3Y1F_h+)d(30<%_q`6!Qe|$ct)Je%xnDq zkEa24G2q1LWvcgQUbv^Q-s1v0p;SWoOzOou9aSe9!^k^_}b8fHPI9B7EWf{UC#<-NWT!<3s25 zGeK}v2pF`yRS073XnI~qbxq@UhtQqHj@J6`(&9brpy$vOqV}=@0bwCj2^8m|wX~Zc z{RW*C*lR%s+%sj`jbp%bUD0Xos%I1rE*dK?lE0wlEmYYxU_+k~_NPr&7}xYQ9>|GZ zTLux!BebX4wSP`K!CMDO+6I~4kP~+pPZ-NYpr@tfu5XZ2U`sR?o;IPbLH;Vk>>pHY zDpN6MPuQR5crejZWaQE~A#4uBoXW0qo;OiGu~SSS^d3{HT5Rnas9~AYm5v*?*D&kp z zd2kjImRz9b6b9L)hB`M;JLuMpNeaMn>T6ouIqdDe`+PsZnt3a`qbnL^Sfnj`w)A_- zh&y8`%QY!v=<4TmmRo-9xZMs7=izN-h3$yHRxN$;DF|z-6kl@Pt^pnCcu~eA0tC;G z`eF1^7zUCC{%f=+kX@kx|0Mpb`Z#O%Jtj7k_a6v$^L~Qu+{h97z3fV>%QHLE7 zXVS~t4qUD+-M%-Y2v9iXUctpic(KGjx8Rs)`I%eD(z?=o! zN7T_wQDql9ySBs{zU(1|_e@6!J;Rr;$J3>~Vj*lbQN9UIfX!_* zh1}rkd&O5Ars%wXO5MS@QLKC-ClW>U7G%jiuS9AHMt0oqb+f8^zZZKWz z7El;~5iP~}o)$=%{cXQ^?x^AhieTP|_1+=PC~95v^}ptc4mp##L%`3<064$l`G+F3 zzrAcJb4Mp*o4@v!k@41lj3L9*-~sSUU%A#i)6&!XaGJOyC%2FxR9$cz>dy$e`rx5^ z_1YUU$-JrW1|!J4Kk+48IWhd!pMVhe`1#>D{q&HP_5Meb^T|k$92RMl{E|rO`=jx-%<;C`E=H@bMZYg>aPS5J$WT8>i*|=BMQPrVG71! z62esMnAGg3%4CS;U5mUHp1RC!9ouR=`R&qrbw*fC`Is`=C)tN!Ngt|YnVlsl^7DBT z_Sy-`e0V;HDY#-xw9KhVN@OalJ_y=hK$Pji#;B>r`^EGUh-SkI>b8GCixPP1=y2I3 zVuCV%4x}`@+>fC;l$g2i06R7=byCYGg7CE2B;qfy5R7|xTT+=X7IpUzH7?!ya_)dj zM%Ca%p?L9ITYaTCPnC0nc>-I{FfTC}-6{OjPh%5nyXupqHD)-_^2Mt@|H|EK0rL6<8PYo$O|c?{GpTE_r7;q4 z{;#vkGR34;O{;<(ki9M_(3i-LEV&E6uie>}SIo>)@?l2v;b?<7OQkf#4z8_fMfKje zqH{8E@Y9nFzovwLd|R{8{i>SOTdIOH*S9Zf)Oo@-X>h&_y&hKBWdZj8{krLW+S@p& zNTA#EW*-x9(C>z3lVN^Uy}7gQ_4y~w<_A=7bL|YJ-G)99S_)K_y<3kz^xvgFe+HaH zbkp2xKpQ75@ca9x$5#Iz4}|IebhDWX)|NnGr1!Z-decdfO7Wd@`bhz*fXR8 zak=>SJotsS??g?e9z>e-8cEIE>i%+7lB9@kAec75vqq+YRitwmyP=d`XYy7P_o?)Mq3bvA32pD!BV?rv<(0k+7b8xjjnQ8C{ z!YhMdhF1o-nU%cF7?qjd;SR6YNRKy!Q=U_*S>{AK`$c}>2;~J5);|-@DVs>@V-PCi zV%;mR{c=`ZM0jpkBvw0ql~%6ZIZy2Y`m$x2#Rpzfgu!k0Q4Qf`Tye;hLZqkp7Q;vw zRQ7~_@1-WqP?aR8z`&@1^$MF$^T)myD!v;(iT8FSs2UegQcEhR;?dyydJ0{Ysr5C+ zdt#$|4{KyIsCr*v7Mir)!X+L^??Eomm*A>y{tbxtCvadi@UwpdGwVFC4fv;0M^$qh zBU@KheJ4Y+|C>`4H5KRNfKo?r4R>EGTem^#BmpVK?{2jH6v9(MOr)i}sW{XUwj&JAk-OxbbC?Hv}J2C4`+$m;>dmZ$SIcZO=Kxc3Ei>4 zs!z!m$kYxLm~Lc&--`?37!=$hL`qqo- z4Add@=Vau!Jq!^+omjT8eh$!}{DKGll+$0`r=JhWGu5Q7!7{}XF!Q|_`0g=J zdHRI2;fq~f$r)yDqIV0sWJC7&a(`N3f4*=5l{K^?za)Wx!W!8x?A`FaX4{rG0yQmJ zpqM2Xu`9)oSsI$bCaF7W#uzEe@)S~#80;dtk;8hAh_yB9~czpMKkw1z=wGNa5!oEF7t;LqMmB z7o_r(Uw9rg9aOwOJ)CWPx_+edRo_*rAc^T?ZQcFa66NQIeZ5l@ru@`NmRzk=A=#R{ zZAocc+Csa>P8DYFpcob}EY7daFFa@hs`$v}3tWcw=wVc&PMQ>VEqAnQA7P+Eh-|JV zEi2tp`c#uDGtZAjWGp=2kVXA;6Py_V=;g3dNItrVEr*7 z6_Aj3aifcw(4fv*7MBFQbx#o9gv-r-rpn~D;---F?7O@l-sv-8U=Ahi=|kjtJXH0S zR5C#Y`9>{AYA{$TzGPbmnROo3inKQJ1-9+r!6}^PA)qik`}3O1=W*B8{O(Jg<*5F< z=kHB4x=UH<1D|KM1_x_Yj)oH?%cOARQs*0kPHVtI8%imzKJwx?{5gYO zb2;1k%wAjw5LySl(w~8yH41fFLU>@`F`SOtxUs6MYPGSdN?YKm#<@Cg`grB;qw|SL zSNqVO`8kDK!hj*Sev2o{#sxFxg*yKlbe4y9{}~X!D^imf2PNQk6=bB9-nTfwbOt(RY29- z`xsgDWyaTBkvJJ)GL|kA)2*w66RzA8MgjZ!l%4lp&7}qLMC44&E5C@43|PnHOe?jEA;vm} zy9)UEFGyEJXvEuGA|pIC1^2nHQHq$-k&lkucSsu_hwae=-e#RbmawNR*7St9oEqP8 zphuNNzA-karC_Dvoe^`wwns<}@M7<=r~VoWI;HlCKuArtCpE~5%@vC>Z3qon9^Q>f z<_U4VR?G@3pCMi+5>A>v&7L7U=>C8yZcsYKEy6ROv$boBPVLqIy9(-$q!HN*5~2k7 zi-iLx`Tx`#rN+v@DG0<0|9gm^WUY!NiZXlwhALD7#)_~~W*)C3O);J?XN}c{Tpy;V z{{1T;E)aJl&6u#gywq!<`WFz7>=l2TF))`khcJV%a6je8k#|s%RX=gD{;}#|dN9@V zuOa@Yfz3$tqMSpJWf*UYTG3?~)TOJQsxMi!TQ~Abr;O7Gr&l`^y!We@@z`ZHZd)LO zFgtQL0=P;DatJJ|GbESdbi~PJn#;3ueHzLd#NSCkAYLvQJM$4#^g0-whBIT z-GPEMryLOzGH4{#4|Z)~f!MlG6Yo7I5rP_)l-Lm=q{S4HE4Wl@EE$bcC6swd(vzFG z9#fi72Tq=@K$*BU7ZUaR_*^cNy+W6?!3Xy9rQjc@WFP7+MGmp=67m)cbhfjPLpsob3!rKKB*yEn$TYrl94Mpn`>Lk99tzeJxHdt2%B#x zR4KORx>*Aw}4aON+J6A)7svoI#{oySIVGzvYS6bpJVr%TZ$ru{#-VF@Doe1lT zC`((YPp#V^w)`|NTeGgM32axbwQMUn?YuIuM!~U|CbtU=RhEtWBp{CIgiTv@V zUZtrD=A;iov!emBL*2ODx-N@$w#cNmv942Nk2tK&K%H9QT;e4nbPL`)Uuw%fEl{%p z+ujN0!*<7*XY$+#l3>z~+xL}nIG*Dj_8{|;<{D@CX2g}_9p?!q>Vjxv=aKh1`STST z#5OJbfF@SRgC4=UV3gp9u04(j8b=g?C}$Mm@Rwt8SfH!B#58pW#lbzNtC~}XUuW+T zdsh_aXZWj|9uHX%LCHLw?l76+a8l?K7aRp zd|harmrqrN2n;Mhz)|-@Kx=paF*;;3!3H|KK5o>^+E-o<2wBxbUr?)u0NjX?cjycK zTa1owUvh>UG|*v&p`bZieGZ13ZG=nkx2kTvS9OL#59S-v#7=I6C{jQz7OW)__s~~N+5UimK9o@L5J|Px+Fw?S_i489VqhXA9|-2Fmm&lRHgS$$&_c zOhOGWTTd1xfL)t~pj*=1nsEwk>mRy$1MEu%vlKebFmg2p+1Es)@S-?N1vl^P@Xx9p z`?!lcFO4Sn#c7USIZH^ZCxd%^r95It5+s_?X!gncnCle;k?7D$RuVHPMm2*B%n3rh zpf-$F?K-M^31<@rWgp@tNrEkgOUGXx%8da*$B%C(4)h#d#v0o&{#*{4m3+-D2bxv= zi1@{b<~Tb~(vzntKuSNTvQbhJ7v+Q_YS%SW?Me68MQ{(UESrX!wPB8bGnf4APl~Ro2)Yy zQ1Yt~{h2ySy(E=K*1z1MiR1%HKF^gdNY-3HV&(IUe|`lia)0!s;u!kRmw(H7ADB7R9k-`~8m7LPqr}csO-x9#{IxSsNU|9YD zhUGuSh^om6{=Wf@f0>rk9cfABD8MzK|Cp9*9nJGZ#N?5E<+TW8@{|63A%4W=`{jRJ zh%Zho36N{AGj(xsnVQJ@_l5X@HrL-hyFcSK~dimF6(z%{q7+ zWN@~$?6Q7&@p@KXiAP^(ieM$%aYn-K&#Db;2ZilKk@_2uWfA_k@EmRM>Thf%?X>4j zNg5;tq$e7{7FQj0FSbk$2aOYCVwT{ZbZxm888TO7lcQd>s3b_gGK6nyZ$DVwq)xbo8pl z<@U4rK+zzNt>jroIIV;fGiG5cc%K?9mSnrIRu4A@-7Iea$MUkGOPDv>1jr~j*QkO~ zij$S$9^xspAL=lw7S9zQ`KunKE+Ut}BXPhIxlll|F4Cw-{|4cy@lLQ94q zXR9x25q18@(B`KPuw(-m|IIa1yBB`P1csjc|AJ-?3_n{NoBx5=0^2lz65_iIu4Y}n zmfT#7f*F>W;;>Sp(I`7IbT5=r8izj62XWbtGahD-6ep?x-JgC5_ST=>CzbAtiRTjx zs?ubRj6oJF>T!Ra2{gFyJw@~Ve8cqtY4;(u3TaXG2{52ygQ3{cMtYc29#wqgKUPu9 z-4o|V%z>eRVGv3IpU7AEj>?MUj06XA!8mbo!Et6|S^ais*KB#l#1OtUH3;CNIwm(e z&;ih|m%?q7Lb;NTtC6Ok9XAPpFSHKE#e*va^OtKlqd$|jaBw-c3~pk+0vL9;oITHJ zr5dDJOo_L=NQXzSbR2jfuMcP@JdOqqAUD;}Zh3K|T>1te)Cz)UWub~`|Ej?KBz*2e2)^S~%)%T35!#b9xP^~O2yvQSXWUr&xz?I7~X?1@pP34;{VJ_~vICv4yqH3KY_+cwa~f zx5qAA5MyAalOEuJnavAtSo8OLB5-+HE3$Eq{6;yUN4iI1PVJVd6O>+{+YfqVn^A8( zYnxeLJtvdo4M}1fFDeq`{k^K;8TpiRHdr6kwde|f7%OQ{`m_RLBtg$m-{!*X5q2u8 zc#5^2WpJT~_Cq6C!r>g%g|nR|Lm(wbxWoUB(mmjG$@P(WKymkHK#SW2CYr3g*g~Y~ z2;KVgG5W-q!an=NPdfSzDgWxxSpyrZ@oFo5E8Xm+>0#+L(pH}dkosj^quEi+(Z4g0 z{!9>$7wP1cU_N~s!2kaN1ON}X*qB<;n_9Wii%O~ed(y1_#Z7Yom6yGVMl-hASTi(p z33C7n%7lzXoKSYQo>mkF4gh7XjATTdCssvcI+IA(_OSl_3$D1P84Q(muEz|&E+S{roU zyP5dlEceo*{Gx0ude)N?b=k>Jtp4*>IVQxcg7dJ7^`U8#^H#Yg)T{^c^^s{v7ESE+ zxO6VOfFq;!j8jmEs;e2`%AH|uhY*5hBxgaFh9-eI@LGwiJH@((k{RO6XiqXSGWE)d z$Zd(_3JZdk1U^;KBm@i&HccE6{{b@q8`B99UP!SkhEgPcl`x+y&SW(ru0ULbK|v(} zEHo2#bu|~WCZCPjjCk2?m4&#e0sLUH7(*hpX@*ofQqHQcrqHsGMQnr%}S{=(;>;4lsi?57-p{1wOj-> zn|L4}7A8zo@!L>>If*zUNhj%!2niDNwh4(j!pekRXBCVHD49FEorqCgpsy->F6#<} z=E)5;@=<_HR#YT)!QkOa#|GS09wBgKQZ;}K_RaI+Z^ zjKdUIiYVw)!2ND$B))3phVfBB)7K#N;wL^T{gVYHQc}QxqCCnedk2K6^ka?*C)#9h@ucx_0l5jUC&zZQHhOJ007$Z6}>{% ztxk5($(K`4eO2#$pQ`sc|G=)=YpyltHO9E+?}DG|WWzziW#iu{R1cRKO=9=cA=5Wi zPcYhiq^!Azq)s58R3p+!<6OtKBMbSc-ZaL9KR_mkAdWOq$Fr7~LE$|*UL+gtOi-Ij z7L}ckN~KRGZ|clqI#4a=YmiEAG5h(5!?J&1L1JxBTm@<7uPxI9mdqv#v>E?l3kPY= zUl+ecv=W{uP*fFSSqQ_b+b@vYX>X;ScD^zdd z5P1A{;2%IZq=yh^Hjw(b?=Q8qMw*(dISKY19`a5bq8 z22GsSXnJFz$|B)n5Pq-Rn(I-K>uI?yQ8XbxopRSkE}>}*+d$Ih*TB$)Zs~l$R&7*E zfj%uwSQKgB@1y4YawjM&kIAURN&c0irom}g<4~R7g3ro~94A|>Nc^+<&vyMM3^q4& z5Nk7yKovEqUNgaJ1I}JgU3TFw1M*lW_~(gU*(O}a8pLH!Fp)VrSUqCk#mW zH`g$|7g$IH(v1o%w%3>yi@A$QH3wtn6eJ9bn6t1=gtdLTj(#}PuZxO;`Seh5N0)l- zZg~8$;0=iLs~Yli*;JPCcnFrqDOtAn+LX8>FuS`|dQZcCO==dqcrg2f2<}z;h-}$+ zhMIH?W+QatF3G`iCfJ6>wkO9UFRETC^p=V)eJLp^n~pj4=ZB+ZcICNk=DrdXq9gO0 zR!>p_oPCM-`drEcQ$_V^8_1NgUTH@;^u?vA`^^D}Ed{Th|8T6^BM=JG9V_Q&l zF!ajpI-=2GYk2#=HJkK!3`;Y*ms7^>d$eebg?z=w#dGBp{mmV(3Wca$gRvsF)04Wb zg`9{JY0VR~biJ|GbV!f*;4I@Uj+{(BN}22y*xy6zbtcmlvM7JA4T{uj^=~YUA1<~2 z8rn~EYx6wIgs=^H%h2?`G=`b{VK>_;qqcB5Tar9!x@G+rgu!YsrM+%~5-`IYnO(%}!8^$57~v1ZSp9ZRIFq(vt;#J%h%l>p-n0zO5o z8j*I?X>o|8G2jN5X79thY-O_MsVBqzm|g4Q_(+S+7r`r@><7gk69abDTx@Uy+E9` z2Vx=7D|Ofa_WczqU|XbN#qJ7eKuGXT zBv2kUfOS6y&5w0o7wsKm*l}t4!G7JlSD$xAx9#-~k-lK3>gb{y?cHJ663q|e&bQSs zup#}6>2d$YLEq=Na{u;MCm>Mh_YIQG`w!8Bl{}n28k|0X(z^?WI77l^;0GYik#Gs< z(a~=BZ1r--OfM)JfCMsWczOZvfHXbf93=u?NP)=!brN!f(K>v>WDV#5jpm2=@wNba2&pjA)f<;28jc z^U`vl;S@2lFzxFg>Pp!;Ao{VJv&v&E{|jcvMxw#Xf6t&V=I1R=u!NIQ-`f3;0Ha1w z`ShLkpDdvr0R9GHL;Kl?WB=1q#W)l|hXAcHUqBJeD!fGhH?Nz0kbu~q(8vSKk3(}BwEaqsB^6bmQ%mAp z=HL~2Vo*Fplo#HPjQfdt;070+ua>JZSjM@+F*v3tjbq9|>HIF&KjpuE-D}&TDON%6 z+ZTg#d*e43U3yT41i^mBs_xt2?(bDS1Z9vFf(ZCsuA16?z$L1Il=t@?BSuiUUnK1U=Fyd-vxeU85URfPKlP7MA1#PqG%W^+z=R8DRK)$cx9_Q{?;WoyW&!P-lV{Jx{A z_r~2qz4je>Y*=DGNBS*V#oC_AetBP-5M=SW(EWh@mh_U zZ^_`gh(xQwHIcXz_Vvt~D7}-Txl;L36a2WSGS0>hLW{WXjtMdh@a}a3NrH3tfP(V% z4&m@$j*`uSChK*9_|sI#tq#O>FbZ)t?%L#b3;l&TJF9xDdK(UQUGnac@?mn7({GOr zs>>uS#m-N#PqF(;jd!}&sp9>9L-TMnaA3XvgC6^TDyK<$wT{g%H=EVh@(}%Rt~OUs zHwG^!w-{)rr{^QsDmtk@6LR}U$tGqdM&?FFlSW2HN+A0P2=J@lNHvt9cY~O(^%K=s z(-^%x?Kz=BKxb;%3KPNAWZ+^1Jh4jLf`WdF%kNmHP+S329cKKY(O`pQ*qIK|eZ$Ey z%qfo3zGn`UK|s8SlaIjHqmx&Odq;7@PegHZ8$_8$!NbcTYtl5cHL}Z@7=iDBBTqrH zK~^q}>~Qk2GLrY@cJ>{d8_v`X7ii@~tX1V_oQhZ}nuankW_ ztWDI|tXHGgImRKG)cuJ{c$ESVwy(RXuKPoHr^pn2vbm;DtTbNn^oG~V?RwqyrHQ{P z&Wx4Mm-(7+9(xVKi~CmJZbn6MB!aO<-axs+59qx6!Xv=Af>MNW_)y5Ml?4e7Zm0!E z-x3Q>H6LzSb*GGjk{=fWRGyXVWdK!Tv)u*F<^JGflm+anD~^_n)Hz(%kP zSq&-)$URUx0b}dzw^3wz`GzDoekdx(z~VihP~mlLEhum~Cb_yK`PXYSA>k{u6_wEP z9wuE7x71DmyKt{U>&cp;!1!Jpp+HrsgHv<7>zTUp>XN6tpVP>a?$r$MAvbs-56aGx z#k%aF%Va_(eY$HxZ=fwVI7{(|w(Yu1Jj+&#gzY?Y%$nYhR6M*@qkCf7tL(6ep0)_l z2njt_e)n0r`0NAknB;|9Zv?~HTXBR8Xp*n+I=U8a&4I_jycIwWyaPiUF+`HI=Xt6r zI!n6iSK0-4cj4~#^xj@kQO9(MTyjWORUj{pz?z^K5@VZlSfAUnj3PC(tfx9Y{_|b{ zzTv1Q+Gf*)&a-meQ&zpS(HW4&f?aRhXbW9RP2Y<&X4f9qWe`m0R zIz*2+MK-7YicF@9k`8}oz|;DSwp5%ok5*1bUWQ1r7>3usT`Ueis_{Q4wwrRn?0xJ2;z+9_8oDG!Erz}x7OOU0{wgpCO8q!#LNR!&C>kC4|SS8lF<)#XQsXG2UhAVhiz83bSK*RWk&qv$6l3w?Y%$ZFJ$mbLPSw6h*^c0?$XN;Dr~y<0)5U9}~3L z_94^POI~GotY%FE(XlfL&-bcFZd;JT(z;Ubb+o&6-82>@w_b)>lQo;&#M$ zHPT;tgox->wKc!iHa9mUhZ6d zp-xeKPhaxS8`3x1I_0q_Vl(CjLu;bKoc+3JspBnz_>hH6tTI_ZVQe{>{w-(=exy)v2X+i@I%a85jZFv1^Bm9EEr%+J)_3ZrinPh*H5M0ZpwI)j~ zjg}xy4TgzO)3ojbEJyF0`|R?Z$+j5qtb?VIj1G!Z($cI+J&dI$XapIu9?&`GoM;@& zpiAB=&uwz0a#)S3R*Un&=qmNFyZCEg;P%Um19M;(h-?plv)iUyVU`3+l5kTTLE7&7 z;B`qwblM6;bK+@X0e#!9WY{1k@tgq8h_WbNp{HTU{FN_x=l5*d+kB|m8*6KBwpm89 zN=J{GlJckNRL2t%zXv5>b(BSR4uf2Ef{Uz(KFte`R&j72Fn->+ev)ARaEv;`(EMsk zhT+_4z059TSA@y1EL*esf!TCqX?BWZ0QzgD6`rhkq+@Z1_M+wDyA}wL$zWB@QgZ6zvc5#p(D<`C^s5RWTD_UMN=L=gim3>89ScUyxWp}N~-oR zwV7d|K=;izCb?Qtqy3|qIC+Cq!SU$9SxG#Nl`sO_+989nFY#*F0I( z$^sSX62C$|j^$le%NzdJ)E5Z>5&B!_(2;b{a46sZM;wEE(4j(8AlR|D}AXn&jO zcIe=1jV^qG5u!rZ9f%GgPmO*AchyS><{qb`J zP>h$824so0+L%nGqxpSdtsfq=DzE)5Q1bJ~Ma79Q9+lyA$K26mvs3W(hOb4@()57l zgGrf#TiVL0zn?GRa8dI_K56#?W^L^_lcS zs8?U*yHnZ^Et0X_);uF{@_sGwcDHeIOnCEo*6>YXuLCg5gdIK@#>B9%3Yae^$&?U? z#qL2VYLhjN-DQc<)Ydb0Uh1zv&@+9s14nTs_6u)X5$yyFT6&+cgFO&$#_qZT`3`!K z2SEvyccZ4(O`yf|Q$?J-^h`L@*NB_BgPVh36FLUX?8D?Ww7;O~rplO!C-wT>E~dt> zCIL1+3POhvCMkb(7Fuk#-Mf9pV3bmYgMdfxHE{Hob^#wG4HV$PjG8v+H?=}Ud0h;J z^2@S_2;?gU=$&c3YMvRc3QaoJYDo3%ocV>5 zAf(df_g{T(%+$F1?7iqs;ab7EG5FHL)vMBSUyHQZWt^whXUwy*zPtx|h8axAr)jj? zPi^uQR8YF=?+*bZLnC<6T8H&(Wj2R!!Wi$!{nq+5$$EdqG#uEX7>-pAb87jejC3&3 zFm>KkX<9%S@DYh)Rvu#7&7^x_Taxw0aYnKVl&g4-rJs-R%13cFF(f`xnl8yqVe8hV z_OPHgv@JHumdV&_@6!kPK7L1~yy(EZn29H+Vc5Rw@tNxPBq9+V6B7;0r>U;A~`54jfckvd`DZSWFLEU-Bha4S`}RewbsZ$tI1=CbDqtJwvBko38ZTeZa}5xmd}tg zf3tD9CBs(p&3YG&Is@lL*Wn0wi0^880m9BAe}bM?75CIJ0sJZrp?C!=z^uuQS zOGIIcf+o#`YU{kHZLma^KWNg@CyIA$ykOxUKQuzyBA#tw%6O3dpQs7QSS!ut?I8Ai zTt7hm0)h{UP)jVFz(!X|Lu;WY8qvwIo5&_A(HH8xo&Y?pDHF37&wxcM8OVK&!9|Xw z#$-ylI@+!vg_@K$hUFhxyu<6ntesH~HQnr$W`is#!WWI_8WFG>3Fm&_BhQufYjdh4 z88K^9qp7r&s*;y#eAH#K$bafIzN)E>EE+Gj1ur!uzuqKTb+gMm;A84vPVX&9He1ZX zSA|BysmkILqbjqYYzQTs%CsS;hHu{maVTIO3!O)nR|zaBGT?0s5neqfAem1Q3(8+z znt=sO7GFdHf0COnG-%ldrh`#XaYaJ?q_ zt>qWM1^ZPe2R3bO-;8IqAFq*YKoSH-e6hH&tc^q#q=0y3NG>@O2g7C`C(Ap}-)Nk| zX>qx-@QQz(k7j*Dto2f21$3foVc~y@bO0+9KU#od8{<33WYu7f34o3S{_op&w;`!J z#Wq$k)qC{S5<2KHD3avnf+~$BG{f()RZJIptoxR^Z=r7`h5O{Rcb2g|#_38yA?!k$mC--2?@XEst~|H$(f6xfEpn?;FHZB=O*JmLHLr*QQ=h zsEmj-f8*p!p`t)9Wa?{He?&Jpl05i6&S%#LQxCzH>3~O90V$d{VrOTaH^Dy;{@Dhq zV-P_v_?psk;ry2=?SBref1Xt%S}x`N?Tuh+9Yz)I@iZ?CHSK+2!=crKb6rbh`p5lTuir6mu|g_;ZbjciVUiS zhvvPEg8stO+b!l)Zfj8^J@zrq>>N7w1H{7)~(4HL#V&HdFEI+01+&%&0Fd9In zq>ac;g+oNlWM2JS0I(AEI=EZk30~lMaAHi_BLVJs&j3>@k@Vh~auez~JK)ghe*ZIiO>QNih50QZ4-kZdbXbm%pPXx=*WgugZb~qqyd?nH^Z=4hF zmP+0$cG5q^sZV_05%mQz=?29+cloeK>AfpRfU)3b z)oX?6R^8GQT<(kT$U%l)Z_|fz_C0u#A<7GS5)?R)9ZXk)KUWQ_v5_NP9WUcbe3U&~ zj*7aCwPYNA`Kf{x)gCswyhTR$sc@YmDXD@IwPdZmlBN1rRaEKxKtD6uITD0vkppDD z5l@&CwR=Wxt}Jn>l;aC$;S&*xB+JN%@*Z6!7{?x;Yng+#<>iC6Y9 zXNwUfCY-aIoAcOFPsCB)lDTmvYpjrJ2zgI4uZVhDl#~VUrcR>e-09yjccydGyB>ob zbfYV7F?R@eBz~e_ZL2XKPeeCBCjuA~+T|q@x*W^x87ONhF$?bVMKKASgMN7tsS_Q_ z8Pb>t*bBmskffk`sr;5cwj`NK-KK9iIG)UnkPD_-#n*F>i;uC@!z@wMkWt~{f3l>i zWGKifHx{MMWvpWWS6)vv*DxMz3=N}orr0k;A^wcv6GvL5^1KrkUNZUpd4_+35p0DR z<4yC4)d{u)i4?~UlS+f@uyk9#^#OJ}K;ID+319G45v$-C^y@d?4#8Y>QRG_@sx|ylDDwAb=s?R#$RSqu0~&rQ^)8?Hsj0H(LX|(*^+EkvvwHw`>%@){z~=d?za zQx`LnqY^9+sblYt!+U#gO`u@&52D-ioBqO4`TKSc0wu-u9fEPC2?q%*P{@vk&amNs zcbOT@eA*Ny+{SFmCrq0mw4R*QVardO&9CP0@}>)alIpaF!T#@ zy`$z2e+=&705KyvhIqO3U=(3)9T63W0=f9WD7ki*V(lXj^4`cIo(Jl3)0lgRF4Ezp z%_3Q8TbwN{K`jxF4=2TCx1a~P9-wP;Ss5Rkk1Jr%#b~tAElJ{cbvhM{P%JB&Y^FhL z!&|;?dsx-$y1NI)U|Mv@C^cCs6D{^X9d%JEgX?vGIUOI)Ukqt@&iH z2ua{*qgqa_8fn34|42G+OEv*Z;^>62_1Jqv7(XYA$g!T!a)Pt89H`SGhkG3#7l`HQ zU7$bFVy)DFB`|I@iBdH%({hA+^oi;a7;+s!G*xSyUzF}Src4_iCi_9ob_kcmXy#nv zhh|u(ZqDRvWoD7<0o21d1**^Q$;Fnd-r4z3uvQPN{t~`gf_7&w`$3n#Z4I^^&L5T9BQjy5>xdD%P@~<8z+j1pd|@PLfPr$9$-Zv^G*BdckLWNqMOBBF0yT zuIopUrabzGKqP9!@w-mZ<0U=~!xQ_LNbPKgpzO{168QY}=R| zCqJv%zz(p*wX5-NNzc9~3qyvuII-c%BE`m9w0VY#cbu6_>pUw<@cUM|_?pbTq>vostLLz!&_4ulmSv%!wB?R|eS^w)s zU8hoZGB}!%>8{pKhmO$_Eq1xB=J#KYlls+w}vaVQK%`qQ5B ztMiVZ(k7d$w2JO0$3=w%eKev#^mfRCjY7${U&Ar#zWZ%aGv6LdatFToTMi)}l2Qa9 zzhq#{Np$5cMU=xY2;oFGy^diyZTzelo`8mnLy>k zF}a@L_s*ySp@8{e%ETQ_l!U+JuVzsJzlJLk6*Gn%e~5jaqxg>wTPA7BUQVUnLdjeb z((NIot@7$Me-x8!N0|EUi1<*;RJVOLecP3P7ST@m1G2(z)P?)l_j}jHbUDii>b2d_ zS5V876aOpAvhe~*IFRcx;=#aa%e=N1)WEd%X*O_A3F0`YA#~-Oh6COJS=pu7onUh; zsAEItc%Vhxs(DiMg(BjV3wElG9z#}02v;l@gm#^R1lbwcc{;=sk-%;us9`G$EU;VG zg|~qhv_sYb<|X?0KxysTI+^c5Y3bVDA+6_uNXB5o9)NG29C5VU>LEB)oBrO);0_Ne zx$8HP9+LeeQ`P*3tCos!W9J~%xZ$x1*r=PU{0wNQA7Xh>f5SBeC6X-QqN6B0r zuR1jw?8jce64EmYeUB!1*2eWf z@ajJKehMFmV}C{ntCJASv#v1<)P!G5lXqM=pz_J-(8ro{R z_PUy{YigIE7p$4!6lzMaceDdS&!j%se^+ocTo&@e?>q;);oZ3fCcL!Q@WZdSoPHK{ z69_t-zT2aRwhTzT`T%vvv_}gt9w+h{Y{P8zoVDtxz4B)!SN&_2@~q z6zq34)=4*P3mrDrG;g))QPz{fA81L3mgsxZ>cAd=1IsPLuvFSXi{cP(4Fi$&SKikRCJhThBo1zP(-t1E&nNH-aHZt-3qA_t~6Z$#}Dd8gnT^prP z!uwH3+`H3RNSaoJA501Z+{m|c{+1y9kmmVB1Vx{q|5W;Yd7g(gU(V;d|3<<2kJ1Gk5u?;xALPcSTi43vg&&lW)g;l@I}VQx_hVnJ?J{gDdBV{3uFW7o(}uX`dk9 z;vmb~$_sxTXm}5QUBm8uj8Q_rHnSoQ6x@X6U8yV@1nP&oeBA&JV3R65ph9cmBQljArX6$+}2d4O%NpJ@^ zq5@Gy;49jG)eH0mO+dYhqtbA`1XUad=*pGc@B>3kXo_S{@KnDR8tcj*9=b;K%nNR| z<+Y1>>6G#fS2+qp84g4BIUm)2L{lukH$l{<+H`9aMp)rqL{lG{_c!8n^p98FS`RnU z{%Cirw`-m!k6U+E6$#m~%c1tEk70V<_F+%4pY`V?HN)o)Za~tZMa2Z^W->FyFv4$+ z;wiJgp90Jhz?)c8NH2RPA?zW)~XlcsbKLcgy=i+Y~){m zzk7dB)TD|rOY4*=^|A~1iw8kl!@Ur6!9^V!hy5!#d&pGHL-uwiX?a0AVDhub* ztbg}wYAr3AGEr`_b`7^(yFx6Re21K&D}*;jI{uC*%kUJ^dD_1Hv{^pLtJn*9x^`If z6`En)$^I#rnCPH>g&(wT?I9FcmFdN z@lnRGam}9sp@pVb(}MAq>(Boc2Xu-5@&48!_$_xB35Ns=(Fkc9VHZz=Z9NZ3Xsj}v zi~-yUKuuwaW4(Yw%_7GD;e>r4w{5yZ+mmB-9{@+eE@!MWqP{#?*;&5N6Y=-^1Ompk z*?p13F`;u~LY7(9VI~%69r*%P=5m|M00T^9>vuPDQ;ROGW(c+n{3f|(0hcZd^aG1t zm3q!eFVlf|O!^W7niJeRX;w$vC6f_66eZOZ&3Xm11~_m@+a<_}E9?@Nl}8m~K2Q)% ze&%GeAY|yS;3hcI3AmKC3pxbo(1N7ow(p;ET{`U%5Iu9NvENyAXfs>U=uCxo72I?^ zj;if-)Jsr0WRG*P3oLjZ_jyYtJe3>_5)%*=)+7QvHY*izJ7_3`3-{Bbd!mD3Wu!6W zbLi|`eX6FhB^H=z>N?cQqo@Dg@KM?v-nvxHSNY84Pw7~sDNS<7NEEdv(&M-XvChd+ zje4`|DD3QX&ZW7Nehq!qqOjIQ8A}06&VAi`E%SlI)uG1;tR>FV2yycLXfzrjhQBe_AD_2mS}> z7B8P@kZ5-Z?QWNMzRl9E=P11K);XHPWF>|cBMWF=?1xB~5E9GpKm^~a{ho2T*j5>v z*Li7{9$|gy^+l>3&0Qz9cGZl=xmj15cS6)(^p2@@*+OuhRGhHxZT8qpqxK6phC8`i8# zbgl31UoWpQ2a`5?<9o1U!tj{(M&Y5CzH$3N8JsW!D?8SzU1e{06^HEorO(Oy&&*tg zsukAObRofg&MHVnO0>du=x!;1@$L!yZmNu<4-*M zCHY%DCD#aCK}E+&>L$(B^ifF7^qxMRJG%Xpz10%;=MtsV!#>bjp7A)?$2NY-rJgfp z-S3A6O($_C(%a**N>H2>R={QdABs7y)C#UbD$KONTR0xITs3Ez5nY-w1r=?I59X!81?};K8IP!WRtYPZ7v1>R-4GCv zDYlS`d9LY)stOpzjYd!)_ch%PHK#o)7fjPC)t3j^Ix0(G4u2Ah^xre2J(&T`;MNGXxeqQm)zq|aCcOn!wXG9#@* zTL?pJn>nB#VlFB|*v-*A2;#cB=vq>-S{~GOm^2bga|_eeIUI7D9rAU_9=~6c^QT## z2u;%L>U20@6@0u-LlX{s29_pO#BWY&NKxxGwD6Xyz3qF8eP5?4fl4EiGy)ije15gkS}YeC}InQ zl+F9YBSB-$z(Ie(^o#4Vtwd2kbv`AdziOV@G5@z(_#in9AY)RkehRpB&uQxyL1)f)yk!{c}FYGE3^jd z$H|ZEV!A`3>`RQ~$&^b-DX$682O!ZxiMHtr%7t@n>o0Ib5OjnC1NZPr-0OY5?xr)PLcY|ZD0KTqN6lZ z7{X%E*+7P$Ir%Bt@=Bi%{c`$XSc)#R#JawT^(Ak8K0 zg8uab!U#FBlJv;cU;wQk#}^xrsgIs-8E^lFu0OEUFKU^U1 z5+=0BFt$WgdmM15vLFej&2Ut6xHb-lO2)8CeuL^pqP7_`s48BD3w31{i8e{(iO*WKKB*H)XCIK)!eF1HPFPx6K)B19bd^qd><5;irHSR9$yNsLwD7C z+8?f1{FO5oL=6JZnAJW}sd_7B9N_vXp}3M4vpnPF25#`Nf!Ov5b1P3Jl*rrYZNTN@ zx+}9e@9-ze$ksRSy=RPpr*?a{%dAq8Sz=KEl-GYjfrA2rmJh!6G|MkX>i^16`On^_ zV(egP{!jH7rZ{CYD1?>EI5%3bSRi>?r4!2kXmwYt1ddcZ`-UR8FVdW37UN%O;Dh3G z3IdPk8AJCU;Y2^$W%>@?f&)1 z%2ecKzs%MCIj!=z3$Jy5P}PRp)m(1107VegR8qQpr`GZrY}pjV+NX|pm|l1L!VrMK z?}9S9i_s`jOcE5lO_A4VV&$v7a6SG#!I1jiDw4gbRz0fNV64x)8y~euLU;M4 z5@ZYd#xLYwh~Bf#wc~u%9{@XOS(h0Paq@BvUc{w^znn)^ikPFOaeu629P|rr3r86B zjfKWSgUIP@wU`^D*~9Yye-@>xaWZm?_(0cMJ84HFI4ge<-kOnhmG&t!R9;Ta&6rB* z_EL5oh{Nj+HGiPgdc`By6r!=GfnMLd{*<9r39l9I_9FzrDx+*nekv9e>KZ#4 zC*c$_x2D*~Gc1ypLlD_WB|<#%_?@8SSXk8|Mdj%~Xx%JLHA>9v_b+eppb@jO^Vfns zeJvQre?PxfOBRxQv@#mwGu-F&lAAO5mRBF61sLM0l^@iVbdhop4_yo>;ln!(h~Hauncs-<-K zzpbD0-p9&=J6h5?YVs}*uc@BrJ$umjrnByJCelOA@#wWIb_u5yaw=(Ehw$B`B(~)@ z+|z((DC#f(wfeal{9sAYVe*G$SLrgRgkTb}gHQcoJ1&zQqU@V!>n6mqb*2fM9t{@T z@R``d!t+Ev5Wa{9(JM2lMr5KL?Tu6DCPY@_x@)9t+ioTw34w6H z{S1%qEO`ooPojZJf^F-r$wcvLPURUBt}|P{+cRAef`p&1yx&oQ!b3G!v4xlw>^;HH zgIpzh{<%ktCnQ*djS`De;5-7eP6Qx{EFZ~GfXm@c53?25j*!aV@F}wu&r}PSFG3Pu!1%+a`XB$MyyJ#Gm zZ8dz05S216(-WhL5^?uqm=l-#h+Xy+cJtzs!AF(8G17>TLOjc`+@sY;9=Rm zrp3&T$z7D_N77eBxxU2)$&O7QM2(z81@Po0rfoE3yy8>?n_iDkIF<~* zXk%{A!sm6o%3j2>eBkpm;zWz;I@$Qku2gUWq6h^$>4~tB^F{wouHgi|*m@-?!2jl* z&%V#_7eo3$#FZ+nx=tI@4x+UTOOcl63j!dQfrxe>GC|8_I)Y-(oVJ<$TxaM|+8MBy zZ2A^%?wgyP2MLxTGJio(l!l9Kd~=`|%0qoy5k0L>@QrQ7_pA>N8T2JypcuTbV^l$Y zdo`hTdSW@>=2E!$^aYimWPu1&4CHO0PpC9)r&S$%d2YF^mKcbf1(jm4rvc@^?Nou6%U$_KOMTR zs3Z_#u2y0-%bqgUoZR>;`QvZh_(==|5p?IsfM}2XN z_^8G}`%}xk`Y`9z^ccP8L*z2CDlgTql2w5!nj{+z z`ZLa#W}5;8N1;wf%o=Hyz{)rD7^k1Nu6xL>gh^4*{I*}T(*Cy+XhMEA0twNK3$gWN zn^-;ZcICKI3FJ@ypZtR7F475%XLIB~RTJoyMfMkDFHW^b2l5U0DML8! zA6#&CV&?_-7y0-1{c}HDi9x)G`D*+9fc-CdzW)(!U;Cl7o4NfzrCXi4uIE?J|Nk`p z$WkWiuB3ITjk;D#JBUuzsMUOe=9R!Vl)x7)vf-y@-6$v)axit&rn8o1V8Am12C4P|3oLmckd7?uBSZ)r5QBhU8xKbiW8)f=-jJ+a zoxhc}(} z1sv)4L)Aad1ac?MMo04z!+PdR!n)#GSTe##hf}gu=E5A%De7nI)F+EdO$gj9#^)@3 zqFFt{OsX1{>lNvj%dt!{uHHr3D$H~wW|Fm?qG`4v?CXLtd^_rQn8z{$wSLmz(N$`R zMMRSmr-x|?Lk;EERRKJ`6i42G-{;HsMfK9|lA;qv^aTL>s(PlUtDy6c>He#X2S1`` z#NJ0KwFyXy zT936#E?W2~GmH1=-pUGDF*l$2s?6nD-AMXosCJ*L3B&<@k*r)1xMQ*sYND zO{0<7sP9+oknM7e#P?lg5^*(FnJwb4;74Y4iPX1NPm9Fs?n%T_#F7!>9N143UlRnI zoYzm{B1OBRaqq3#)kO>O7?iwV0g#Gf-KF0W=9%xmh7m;Hj^l5=6n{Y%LVeh~dZt_) z+bpcyj;H;2BO#af%Ni*2W4A5HpFmxyIy?q<$nVNTK}Q?kNH59JP)}@E|1ATUg8h>R z@X>q46(9V4cx~58(Yg>=9~QuFz;ve_Re)$91QkawH81r|`neD_V7>}H>C#z~zZAsg zM88qtO!EkluS40_C#nACHK+&2>b>3;VY_Jh$z967xBdxohvz+8E}Aa&8#PD~IBKsi zv`sV?8vQUj^;ifu!wFkjgpChTMHuf&VLkEWQZPo<8GwMnYA&1Lk8@k*f(bkA@quyk zejialkfc}YnAbZ4j>A$BL4OQ;Wo>9CQLsBqrN4@$2#V2P!J<3J!L17`9k4!_vU2iQ zQ2i~KPo8XF=*aTKSS#XWWqERJS7x7{pS?9ZC0UEh?0lFRLg}AAk@`r2x837}3OnC- zBvH5BFBgkzjWgVkx{=0sP(Q@JOZw9hw~clVis~I16KIb;?A!Dm=xB;mGyi{#y<>1D zQMfHU6HG9%ZQHhO+qRvFZQI7gm|)^eZ2NsVbmsm=$S?+X9{|i zu9#J^)|W!u7KKbs7=G7<#)NRP_0yGU23cMrfzlb=7#A6k9Gh`smQ32@P6_@!O2UJ% zJu_CorRYJIG1-BJJZeOue=UByWL_vIfePdeauJ7wMziR-y!iKimRncW;}yL31zQm?PSxu!{|y*cJT?~LNezIP6+$%Sqa zDAupN*y?D6P{d7}*!e#{?z%Z2>3v`MocS;8Ce8obV^RB$n#liM1UanvpTNzJ0&;J9 z?~5l7HA;6tWgPtmI(b=<2UXm~a;qx}uOzVCgud<|j=Zi*XG6MZR-_X)l@=1Eejbb= zQ3R!SF?HSrbKHOD(SHY0XP&9KtEpDYBNs8Ss@Ku-gY&8<%lr1Qhtub742l4(L812E z_@~3I39A9gUYS70HH$-c?i?-F*7R{&tib{KoehO&!#O*hFMY`hvo}BIPTxKf=at!= zgoLBXorOdMHooy4hJ>T}9fyRY_(Z8U|_KJ z)KpGjnpI$Lm=Z)yQDCuVnaMY>-z+CEy01)8V0i;cVQ6+kPGM+qgHB;+e#8DnmVFHm zvx7zzTMg6^L)Yrj<}@K@M_G|jwV`4+4mSyB;i(l@DPpjd>JoCc(TW@Wt)4u`HHFke zhCPo^ITtHNfX6D&pJk0f8(yS1*%A&%UNqgZSZa+o*)mQ#Xk%7NYU$BgB*xCKsgb=X z@2<2W$pThjUCGKLD+$kUQCX!Xp=5IE^|~oPVKJ7jHX&}!nj#T&VDNs zBm~qhnQM{l+p3aWlPjnalW3zVc^@~@YbAN5cbU>r{cOlCB(9erFvOcfEio-$ceH{D zdIFR9!@F_hW+Sc2o-D&>)-&bBz5Ay%-p>z1E)l=wOz@s*m~|lMi^!`pG<{zKB^RVS zUE%ne=1h)Opx}v`;$MYUmyW#X!hANbYZN-)PZ&(C)QM~@Z?!IDsc)f#;K)uBXoGl+vWNw zz4Uv$D800#vBcQAjq-&7+e|}~NwaMN*gJ!d=60j5W^Z4DAB`b%30U{cFU$cXz>24= z*a9FX%Zv7d?3>KUZ|{E&4wq&(xDJ;lce-y1BLotEE!W&*Kl-eG%@Jbj&m1fAIWU$8 zHbZeTz5IOqKo3l5Ovniw6*PN8O&H7U`yMpTx-#G1WjONp+R^slGYNSR$dbPcqJi4| z%SFB?ja_ka4%1)yD=G#WFhEaA%*W!UOa+u6mM)f(knu9#Lmg&8fYnAJZ=yn492PJ>Bj>{x{2UQUBqd#W^O3u(?%cAG4JU<}_*dJ8nvebqHs5&gwilHf| z5d6Dw{JGWSbnP_Qx^ko3tn2kwxpQ0ZIm^mhqLMw~MqXzl^qQVwzC7*r@Scu8J?%Go z(k|SfYj?A71WEar`OJ=^8ZeC6VWA2eoC`^9S7?&$!1G54WDSO0YW)5+I>NV~QgqqF zH&>VHapL;c-H!HT`B*Q9$|a$d5B$SmG;LI+Swpe?uBQ_=%D&_hKVX3nfvR>j^h{8A znRPmruhQQHVhv3n)tF1u-W7s+wV*Eil^LC`7T16h=VIu{_}2yffdZ6<#-T=Dzg4E0CM7BC~p8r+8LvS#$$<~`GC%kBlv?0jfH zM@jrR?;hZ^7?!VFv_cIaSSQ=?(OoGJ)=_O;%A9*`YU58g<10;KwQj2QyhUOTaUE2a z?8h>Wl4xZF7DAj~5RKi@REF|e_hT~)NsDzzX)K0EI+q%8avCA~SY9esAU`W%Tb~dj z9=W@B_IUAoC-%DVdq?+Hxcz<)Lg5AU3_fzd{fNmBRw{baD~2Va*pv7U(hYeOg~(Z; zg8oy!r!a_`D`AX-(pb#aO}Wb(d$S9J)>T1Vq09oqeOz8t7HU1QiHp-@W_H(uz8 zqi(IH+Ds9rV=}RDjoL*N$8)KdG$JEwZ=L97kSXy_=C}AaT1iRH)Ex)DpZdlK7Jfks z*mpx$J2~(|M>jU`hwjeOSQ|sbvWWAiriF1GJpX13u{P`~0i>NHSR3W{4YxiG^oAt} zJM`}xHhpba4O=j)2rRcqyb8LcOj0edmkU}EU@*uq%7w=;cNodJ(Z0S5vqE}Ej4=5_ z;t|Sf%PeuN+K>#;Sw?V!(@(Tg`vC8$1fgR0T6<@&TWL0eaJ8XgBKGX~DJoE(I@xn& z(Nv~<;Q&ilg#H5o57?vg#nU|uKzGS#cIrkE>t(Y_I}7R^;xJ5D!0S^qiE%+LWNjbH88#=t5vUcQ)iJ_|QO=0_ayX0PyMpNY}x2hG^Ib`jpSdY`^K{Ec8pqJ1M+A(u4% zbQt-0SfG}hwSlNAD3Fg`_FN(>#j)lWgphQKmdEH~$f!9e?gCv`+pHP##l?M?Xhv2{ zz}fuELa3;4-OjJ~_N~uvKg#y@{d08D>ESSA-;~MMfvCrF(F$e?FA!3Q8nx=k8nF)pdGG|h;Bhn%^ z66{!>7t^8hQ14R33WV^U!Q2naP+OCUmg&bue6AGYjFdI@t1@YOn5?rrx;wdizxu40 z`Ib8z97h*l(SS&)^7Mn!9tYhHOrl7ej7 zIWYgPTR%li^pJSakoe5wBzcfmk(Xp&^$&V^eOfn7h)Yb1;D(E1in3U_1@1)$Cz-H?W#a(s1b2nJOL8wnOU1 zI4k7;il<`)jl)-ICjsFD^5MF0g23=!Y{IkpE}~Hzu&!3nxtk+l)Uaczwt8VDSQuPQ zRo)wPyqrg90sZ)4hR&LM1c#6c_LRuT*=~dFE6{j5QRQF}?a;k_jThwTk@Rw~aN>CS z`AT}M;NJCJ0WtJq`bLD@Z5@DCDrAi3xJjEwcZ$WBmYm|UlT+M7(lCGq^_WDOBGpq; zlu+`-uV!VVb{KuZ7x@VFrYr9NT_Z2kW&g}acLf_zq`{43ZF0!amRsdM&$_MWBxQ>? zr4#!#;b|hp{)!B%4Mel-lvJTsX6KcFTOPy7;bc~#l^RF=B&PN>T@AX;-?G8svR_uY zjEiTj>xW?j)~s7P0R<=bVPiP#9)soJ>ztY*Q{Ig!EX1JfAVd^ z;X`6BZgd65qVd0pwcB7+57>(IH9}8ZB+k{B1^?;&OTUToYeKs+)zsUs3t7v}lRZ*n zSQyQp$9d@eHHaT*fj`#I)Ed8NB(;Cu&(ssoc8kU(<^=&DClHm!OMg=NkIX#%1j-_r zy-P_G0ar?yfP_EYhuQ8ib1dA0!fgC72XKD_Gf-&cGZfwT=CYW02<@E#1p9w(zL+BB z^hc;K$OqE@gGcGVB5Yrq@Bb2F)4cFS`y!>;I=8d%cyMTv&LqCd6uF=vrc3+tJ-Cjn zg_s&T85(Tatcleu!_H%6EeOfbrd2&!r*4_^x=x3^w-5u4Sf%!TAL^4p@Xq^Ol+V(_ zGba(u+C87ra+?2f(>3tOo#XS5>zVwwuIu!83O|xy1H7nRDuF6gPr1QNg&1e}g88IA zX9`NKqXlHPU9uEaIR(~dI7hw`Kr;=6n~q|21BsiFj3kZ+1i~~^mc;~iFKNwQZ9Mk= zQ@pl7UP9q7%cpdblo@8{tW-8HoM}+it(|Lp7^d1}bK{Kd(^o}>%@fL%;6yQ_t(U@} zCYxWH<_v2tCdm_y0_-9N{mPgx7%w9F=1FPFw0R6UrFQmh@>=&%hc|7;9m{lW3LYbx zOGvtJU+W=)Rz5os(pKR4Y7#0cr36AOAsP}XSYwe$@4mM8RYw}{Q7Bbv_Aj_a1OaXq zKc`&tR=0~@JApHP1|%6bLQJJ_*f!B03t?*j?ip;)=4P<9A#h@5e60%h3!Py(4t*H} zM0mFy-^a`pipmA<2)(E{Zw;Tr26eplau)aloMZywqvX9&vS^S|j(g-$jzC^85Np_7r-~s%j_1o^ zQ(H!o7xqrsG2~6L`NrB?%h2O>^w4A0e|Q*A$R4t9nPcV@nwf79U69$A5^NqOdb>qX zK(kOmbg%vI&Q*)mLo?T|G8IFN*VWO6GH=oTK5=8eLAX&n0P^qeeq%)r4c);tCc*&E zr=B6gYsshtk}^Q`P{Flj)ZbnAC|kLDAbRzFm21A{Kh-SXFza9pI`WV45lm3=5Ih}> zT_@$DwIhYMnH+#39rD!Axo!91nHXKlp^#RR&$10rEwt*gHIO^BgVjghP2518H+Fa~ zz^hAhSj(0-?(2VhMc*rgL=*fic*y=uA0Yo0LL+LlAt9q&cp>0Vq~z+82m>EG{klTu zEeaeW4Pl8q6Q*@MvZ9uN5fpa_9(P&?Q9S+LpSn#?#=0oq`hk*n&6)5gtQhFC-c1oX=S1M;k`2tnLsqTpQl32)NWuJkQT0RP3`fjA5r|*-t<;N zsSuJF_SIfYJ=M8sA*;`D=O;U}NP-}4#Av~`_<98diQE0PQsQA<)N5>;ko7MA_$34S zA0~VAt>9lHA@LD}!`b!Nz=nhw%=cgMP7tRxTguV{e6xGT8P%kapNMRe66?=| zv8y{_OLx+l5cKCYRU;=r>I->Q?N8U*2iZ8k5abf@@dKbr9nu0-A!)k!&--2<`#gzH zXVPJ_pJ*07!u$H5m;*a0r{asUI5gU z*nso$B?5qLL&h?A&GKs@l45~OvEtJ@`_T8MBMLFar?voa_7x;P!qJbWW!=iM0>i$E zo)~EO2RC}*6U0Xk_V5g)9F-{Lthvc+mKL>8!=zLPF*x`6SqfEUQAXD$OXL@lVu?kC zVK4)nd4(lNKRP=x)|9FjBXvv;vN}LZwPrz2>^kOYE9b1!d?dEJL=W=csF4+zls=+4 zg-CQh$!IJUXQAPgXp-uLt0`41gl^@BBt~5-vze5G?AiEK7yKLs(l&pX5t2#BhZXAoh;!FKY60($8 zHzf%S$w*#Tt2W7Qm0x1y>ysEvC%_m$-$l{Omy0u&z?RgrC!S`(C|1@j^O~%^K@Q&w zXg975Yz;NqHS@G3R@fEX{;r!=pBshZ#U_UIl;f20jpr7)T31haQK18Y5_T-=qd&5$ zEKMcoavN2hdx?y2hqfgUZ1hwjPsM>6f{AfInfy-_l4a&P=o4w$?|bAS_V+&I56jP< zoMctauoFU6%ZrBG8KB9{e?+Zq&ZDg(2c!{Eft1&+ub8V~XheI1th~wJ)98LzVwqyU{xCUk*Z0=9l;{}G;fCPH3r?2_njUUdv+d?sl6V}Z zjMLb#YU}*75tmLo8|P8~;vq6`)Ut+ls1{+hdFi{Z9gd5#Z*M4DQJm-!F=x|4Z!Gz% zBhHW#M!l6N)+1*Q8`jW2W{wP7p4OE&%hvQ#`4GC#{M>jmNewE-Am!s!*G+thO)2z}^%cDvSgTofI}viobi{GrDdIQbS41udZSJ!wto%JEV?A0v&`P zH}^&eS3e7F*ZBxz*v`VcJ(9$mNz~SUZ=dP*xxl5CAJcA?y*%ezpCB<(PYb=GSBDk? z^h}&LrP&m2wV=n0TehVUo)gQw^_8}qzAB?yO7P?SJI}$kQIIMTdPZq+JUQCN2xweE z(x?xw*C=_U5_y58Afgq(rzoHyaQ@{+RnzW8^zq*y`XIVv_{uK;%Ip^a<$si1^fWiI zwRU5aQ5Cf_c6Ajqx3G4wcC&VL_}{VJu%!P~YYE@^emZ}lqN4;cgb4BcXAQ|QDynJ@ z4TZg^UzQcW+}tS7-a>)+={Gbc7UO?tbOP2c3gx?v7$3N=vRGNKxNlz{ju^lFZp1DL zwq(u+ileJ)#a*)uWDB{~=W%wTBiRROp>QwYCqICxr%7K4jyq;!K0?}npL}U8C8104 zNwb_vSqv6?d64ZO){zLrvo`o(OjN72@kRo!a842Iws%xR*O$>14@V9L;P+?GJ;9!bGUq|It&h4srp;Es5Kw2E6?cm zIH~j}P;2@u?=lTYg~y?A&?(1i{d!VyS*1bN;J?Fc==Rt*8!;W!2MCl5=&}9f-`+Sr zqr1mh0PYzYNqR}#5Zz%$&jgbTQwufyg99ul$TjFT2oanRk^w>hL65FeZ8Dm>ADe*q zQ&WpQD_DE2GpIuZBo92< z|4XD**4j-{P4<6JdYrw-w>3<6$KQ703+*%A&$QwbMb@N=!7OR z({4-qmz=cEwb%RG1r0V#r4X-7N$Qu-ch?LRnIIAB|LBsTE=&D7DXoj_%{S7HX{KPXq+Wa0@1okZ{LRNs2oH=qG;_U9pJ+TXs=3} zQ2+)s&yv*y00G)#@e^|NS62eyJDMpKffxuWIzpK*7@&-nP~?R-oS@7X1fZnK7XzuH zDHc6pM^`An;SIA=<%@&xq74hZ2!?-B1w;YF(f*2nP@*%G-*AR$sSLzHxM;tNyzqw| zmAiug@@RiWK^W0HC0`>)sMH+-NTAvl2T`J36+N*=KPtaL4nt9WL<2<80!2Z{(SJ*x zu!aSd{DJ`mR0854Mzn&WC%j=ys*gB;EZV0q$Pn#Q6oelATeuPyHhdsRL_8^k1XG+K z+7AsUTAB)03GbJPBs2Cf8k(xIoG3GXSdht&Xc07Vs%Yg&G3DT5G;!=`7c_J#i=s*~ z<*;HLaro#psza;T_S!HzjU$^=zME<~A4$vRP7XlM{sRk=lGl%0t*o*|%3*88ZWLEtA*o+j zypLp%w-?_mtg?^1$X*Xxt*XLBPMBY07;=!m5Z)}U%0X_oI@K7m&-P&hc*yhr0BOnd zBh1w?U+#diPleBy)EG?)%3qLP+1zAcf!nKO)$cBe`ms0|+Ut zYGIha8iL4itwa*+BUiR!re{c=*0DP~l_PqQU0*$}ioHH2y*?#nysTt&Y4vb>b$;pM^wK)$_zy1+uaI?+p0&MYMpsu^SI$Nr<4V}s!w5p9 zQS>Et6v9ie^4}HId*BoaaD3)nRe4(S3n}m7OD3#0ao}D}r(?|Drh~qWu%s2!G1hClX~m z`~{ORCV_$Oe^_uAH^wSm$Xnk7ohUPpzNm^6oxZsr!aYa$T~d|u%yrFa4H3dpBx53k zN$j@$^6JBk?{Y(aV2E(0|IT770TOyF~rsoqUapVPf*H?|mh}l-n7P;qRT# zRHsOc5GDPTjUFNSdlE5_9ajbM;)Z^PgOh@P5TQ+$1Sb^|`Y-}uI`|D3hx)e&5#T#t z-?h*mLQJ*`X9Ss`bANhxd3ZwPeYOhuZx;MU5mgVV#^Ve4>v5t0@3#c#5IsGRALA_GfgKj851mFOvh_w*_T zVL+@G_ao>F-anB4eH&G7Z0BSn1bY$G@ zL#(0Hi@(tkMz)dV6O_MgOVwNNYzQ48D z?BSh471~|1(fgyPfIU7RnKYQF?Kv2nORZctbxJ|a?(zHqF%FKL$^BI^)M!~w$kKvD zIhpa4UWIup}UB@5m5Jh~;92>^2!ntXbjX!2bS4c(>3nmM0=aIz2?#{lW7${Jl& zID_%64_hzK*oio;?_cU-bXIvaEeTIub3uYS>$~3DaxJ?>raiNv737$FClg7ydRfw{ z7#*W`jK%my%b(I8AT?`yd3&Q}baD&Xw%dQ@i|8Xu)Y77Z zz9#ZR5XQ=a3-E|b#(r&Ze^N9>aXbXczTKHRQ{-X8)H&-jV@3CMe~oi`S*1jGh=5i* z1ixsXDos@m1CEbqQC zy}1rhK*~pJJ@dSIq)M@UTGNI?kb-|^n~gJhQHEZ-*iWo~5N$ofOoJ&I2N5U&1-~H1 zvQ?ZsIXAz$g&Ta3W{IUi@6UIyM{-J@l{QWX{bDSQbq_h!(ojz;;}FUDJ%$@=hSDU@7$GVbf0aaMRiHhNg5(|3cYMr3db7e%Y;3BOgcOVv)q z4!=EKN}^a*2I|Spi>qfOyf3*H8(1CzG_4*cHn*cix01UoINPKIIg*3D#>FqJ@)oO3 z4@DGq%woAQtQuNudqpd!PHji5a>_r_wEn#sy#h8M)ei$u7WB~|-Wp8d0N~arhCv-q zA%6sN!d+3OBJFFG_TY_v{_urGrZvjvN8Vav8QD&{oj#xBhI61o1Mo>o&%Sq|OVlH!WJL++KV zBrq&nPNq1Hi4Py(o${t^EHT$PNH;r_En(3<@H;Cf1qXlQM+-D3!3evhsMa!<#)cO+ zRzc-7gVKAzwB29?jC-ro)M#@!$0 z@Dt`RrTHr6-tY5ccX~dqVcbP;RQ1{^u_%JV=*YWV80#tAj97!1>AXUNBx$bwHDuT> z5I$)FN$u_4n3k2kb~Y9u^-uDoy0dP8BNrMYV+(^qTe6;Jawko{4AY!Xk-Kwrn$d`_ zbUn|k9fkLE+?0uGHmmIWDlCP$OKAkVzeFZKav2Rax?;^Z2yL8I`MR@0PD9(CL%v7f z_4FIm=u+jR7=ZAXLw_EK>%P&O`Xjv%5u>aqDAZo|{)wjXt5)T^YbB+eC`iddP0K_a zxiK@Y&u8^0F5D?CpdpT9DJop4Uw%iRgZG-3;As-4l_mM9B90q%zFL~>3~_Lf-69HS zh*!8+#a5oR&#*jaPx^tnS>s1Ra<7W^Yig;Au{`TJ%xwa}!Hv$@2%C6@G%YI|I^$0> z_htBJE5Sdf``wTA%`(G9vuas(~6p8*(B0Js} zk?kbn{N}I4Mb}h1_GFC7IWr}vovB`8Ba167+HB(oFPlu9AV{UMN@7+8+e}9d2+;Uf zQ-0hq&@FJ(Nn(MgzPU+(f%uGKdC@_VgTHg ztK1g}cSIx3#I!+9Cu=#?a@NR=$_RNc^v_*Zn2KU|@ZvBg$}O$*x>!nSZa#U`ksw_Y zStn0Vv$y|xYJrM+<}#f%Znho|t33BPs;j$mdGy^JCIQSJUkRA_tPH`+_N2 zRCxIF#Why>FjXfHhox{jd_-{49XPXe&0UzbH7}(U15JO{`J!p#=*p(F?KMCSy;Vd0 z398-7ZS^f2nJ~qr?ncgxFK*l{tvMxU{vz66^L$L>Yd@m!#LdMP2)+B|d?|Z~yn87O zagr&-(*3rz{}~2_@!I|U(`l%kiv>sjJmTJn>llU(Ljt1$REt#bxwX~6YlTAC;MW!L z?#IDW3may`>yLm0fBh0VSx>k8fUI-C&1tg=k&ZhH^=u`0v;b+;&1! zad~M0Z^7-}?H*2_UzMNMkRKOGMhkJHwC1RRBhNmJ)`M-MzS;DPh9Q1MoALH^a`eTW zc#R})HzvR-hjf=+Q~UwUf%HZ@>=IPU!Hy+!{VCw zxk;XW^ms<&gu4f1mx`rkV~x89e7b%Nz{k!Pyi3`V6F8%LeX8M+GwopMd2C16rE2Nf z{La&bd4DOI29>xgyy%N{LO5JAs{9Po?SpckFQQl=!I|IRbkuO}{SZ8DFjSMVI>F-` z)>GZQvUNJ)4p>@2*YE@<7XuV8#ug%$29!pprKwqD+kg8svr1Q`eHCWXm&2U)E;F7>T#$ zF|{Oi*9c*6Y|d8TiKuK|>kjiaT8KIxYIoRlzN3L$CB&4Moo*gXoTTaH3eF!3(no^sZDMYXUS~kIouK1$ zq83JI@NunL$SQrJ@g^VHTOv_-6EXg9(Wa4f*Z^DYH&eBv4cE&ScRX;)k4+`Hf z13nq|9yPCD&z>C1smfx>-#q#p+4npV-lpZB@}U0SraWh+D+$5`V3uAJCJy|rXer1o z79?K_17*OJx}*OJ!~ z<;rnFFs9$r8}JUjg*k)9L|R5=Akz~&CgV$VV%`%Dg+gT@<_o=68W@DmKw%)&Q}E(4 z2H%4nNDGxjswd$~c49Xs-NPB67_bwQhZtBbpfeWsiGHyA@`^q>ek19!akg|Le)=A*lkfUQsuS^tt%9oJjbhjd z-K--sHKKw!l54hcQhg)6OVp5`P1Z z>bx!lO~hCIk&63~wo{j8G__kIAkVPpH?gE%+94l;&2w};TCh!#%A~T^gsPUJzy{d9 zZj@h6tX{J;f~~hg09+x_l?rU?*spsEZ+B+}XM-dS9&5R*mRg}iOw4oJ*|B(rb2M7@ zV-z_l^(c?cf$2s!)X_1A;=V)qK90~7-*1rKP%YHoaxj=(q=r;BIC-3WtUJ#fPH|5YBxXReYQSDZV)$t-S|F3EB8z;0zGniC1XD&~v} z{%b%2cdb&sBZ1kxX=s2gECG3!QNE=?Uh5Y=PJ075(}I5LO;@JLD}%?pKEz08OjX+z zm`BE@2sBfE3)DA;2B~%H;1>;Z8E|t)>we&CXO>6@B;<#>Gujubb+pl_{M&iokMs=~ za9CbRp=LZg5s%Z+)RP)W>!n@<=RH3>3KCXxRaJHoAQ25NRQQxLlgNIT**>dX^58>W z%GGzYecWF<;JK#BjMdnxuo(v`zYi~M3;Q*)8R6vAWrTVlDCUyIz;(ldP}ih4`^J6^ z^!bP%Y-^6f#ZrscKpfTfR9z}+L<_Y;AD$G8J;(>Yqj}I+3c@Qj5W>`5EAr3+8>u-| z#T_v|VlRdAGcQsFCMbHGnoJh&-8F$&i@JE`+@Ve@#;oT9y@FZI{lFVW9s9QHPTvHVO+ zW~Oa-Lhs7feL^VYrry8)Hq2v`-|a<{Kk@K+gSdm=g3Pv54rO>ekulPz@{SM}RIiXvRIh|K6niAcK1WMG7~3vBfo#zMblBoh=Iqh=Lq_8CC*qOx(0wUU4lu`N49DK|^2-Vs8#B01et(Coiup4Q=kP9a+%?Gh zu!R;Q4db^>=NAyxGky{XPIb(?$NlTK)ZwV7qj=}Ax2epa%6crOwXf<<(xeiNQzlIs zo~cAU^4Hw_b|w7_If-?Ku08w$|3FdkhHoS>o~EtbD{k;%aCm_H)sjyQOjNEPkG{e8 zf%s5PG3r+Q_z*w5!pZpv5ZVt+OX6H3`%L`z zNkQt^CjEgdFBX^~FGjDeAV!!n`kCn6y<9$8fb=YAzJF$y94Hg@$fymdo1p;-kOm57 zJfAh4Tw#iRIAVL($lowVy$Q11z;ld!%I<%rB(;A%H`+gL9|2M+@ppLcv^1F^TY1aq zJkN5%YZd7N24>>2S6g^9kC|y1X;)*gr8}2(yqoxO}c0+^x%*-)X6V)vt5l}0Cvu2`umPxv?Opr0vhLOPJGTzzL zxsi9U$)6x~G$EBRliU(tz5C5lOvD^^n?AuzU+a^!B^wU{( zT9DW$C51PU>B~}gE2Iw$YL{L66uXU4DgKLYQuhOS+9d{n5I5cd36v+?fn9F5sADd- z3&PaP)Uu70Q)nB-z~@ponglRbTyG)l{1_tx$o82{&7Nu6!p<^T(n?nQ(|fx z4{qG}Y9$>>PVnuRF5F&-1;oa_iRJ8%&Zqk2n#l-I*HEv?UlK67KAPoR`b2hXN(4m9 zyD(1W`!}uKF49>l5+*%NNGVPvy9xPGyTgtDP0e?hw|Ff>`vgSbuEJP-5oQBr=e(W4 z0ek>#XHw8oB@>H2bqI#J4CH8LQpuRqFN1kFX~X&4tY?Z5Z)wz0YF@IurkQbY}9i)2^!66aMiwDK$K zi%lESxqjqum4bt2YYr0s7)OGCAyfr8u*WUg5Soy#+bbj&8&fx^!=Kga8dp2}gP3y12IO z&yTzkoEb+6u^*kgcf5W9GeJb75wQ{60w#RpAZDO*ydX#Z?h`6dI$n@HAMnHqw2l|# z%-?&e1>ubvV$Hbv^*@o1V#XOd`;B>J0e{D7a`oE0g#&Fz&hP?lyZgVgk)C)vECDkh zdmtj-4)e!9kUmfmZ-@0`666nL#1mxt7zc?09q|NNKBhsYKteo0=8rLuD$o#55H--H z%Ty#>2wh;^t-Fm@+62*uzk)QUeuX>3;Uho%M-^zJf$L@V z%VZbvOTrMCV`^XuX5FA43ELwM^o#sZx!dfOFB;84EcaToi92R=S#(t`8%W*!!} zTezQaG5=u9D5h|y>aZNn{sH6TyJ~fZYx@9yBX&6+-6k6aCSK-Cx7!gyg4j6ub`0A~ z$rHER4Rr517B~tnE_vJDWaoz5p;Ku;JxKxZ7oy+e?w0K7<|-5y7}3h)HNP8VB_4@P zhw~BM3`CN)octhmiYjJ7i~g1C{KJ?^lnM^{N1E_!WC3l0h}I4IY30Rh?F#}k5N$5l zCH9`A-xLTD?Gj4n9C|Y^Qaq?4IG+&?_VJUrfjrap{qYXoDAVd~633-l zNaP-CmgMyUz2s^TMyw*?9C^}M!{f0eqM4?@n)U;o3Pph!XXe0C#ylD`FfT9*Ven`M zC%j2i6!fb^ige-nT=2hfbif>ZIdSU{ zdbXzbTR5wenc%@TL3TKgQ!qR0IC)XEI-7JM<#K>XV5G&;)vWP}tY2X5lLh9><>y5< zhRP7{lUkTW#VRJ5PBJ{o33=ZsUkLBOck@g1C)&)QRsFe+wq0k=J}$W}?`Jjwd%o`q z>v2TN$xa?)jbLWtnY!eTE&Nb0TJB?gDi+>gJYfRSr5uqR!U5ugQlz9y;oXe0Ik64$ zlK3UN9=eBz!C*UCFFmMQXqI1d8alF6$ZEUF>~(RJ)U%Z&Cy?U`Y1VpPdNW-6>&2=!)ALJ}X$UGlDWp*)8xH~~y&a}f_IwkZlQsQU zYJe03QX%I}l}5igu(7E08NugCQM6=raJP!RlUeG{S<8@{Mrf%rr&Zw2P*jh|r#pzb z6bPEOUXkV(+)K60O$n1DeO)0l$asqH`yqI&#b7Aqn}oPU?=n}H3j2tbL6AV8+Q+4? zRe3m)s}}F)`02F+-i)$CJ}c$5m_?Z*4+;V;O#hr#>Zi7dDBpq=mfrp=>4cH%S_UcN zb=32Vo!3bY@Cuf#dy}gKpXmib3dUczyfA7h=l8=`epQaZI;zg zZwSa`1sL^v!+3zT>*1xsyR;wJ8sdG5>hrH#-}VnjIQSVhioe1PQq^N(jt&8P_x!W# z7Og~kJ(1lJSulq#|CWpT0-53o%8w$&B9@b<17x6N$oUZfLkpq|_f{wZ266t1PYqpN z`=SQ+5kmUtf&?0b4w9-ey$A`R-R^qK0MxNRsW!n%)Iboc`KC}67GER_(s+j#Nn=&} z=lgTm7?HgenruF1sr>IzGOq3wiG)**=scgSub7NK?Ye%-00X|a?DfV@H;Lq=w7n2T z7Cf&4^p2)<)GEs~`f~WZc~V?fBKi1Myk{)RypQPk)%mjU3YE#N=%=53ad`90E{lf9SJoZ2;k_c&R;sl@9(7BqcIC+hm{_Gm zXou6y#opSmMY*7nitJk`%S;tsrD~BweUnd_tr=%S`&w+ z%<0@bz61D8vV;UV<_U-U(}A(-Plg=q^apR?9-GEL%jnAwnecC`$bUy|Pt<#dMVRHi zr4SJZq}|64qz8QQRa=7Mb9L> z?&xpg+G1?$AOat7Wm))g>B__Tbe*T4Y`lv~Cq`*nqU6ivCI}L6v}Rcv4G8|F3&}~- zHB3)pIyk>YyO*cWird`Mn_yNYST8+fTg~EG)EJFOD^6bXNs}ya+3lLi3UqX)Irzv; z(~^x(T3~75{U55Y=XWP6m35C|mon}3BD{lhaaU%&JA%aZ@6nb=b%j*b- zCzT;7UTjp3t(=P&zZ;2klS~z38>w_psAM-o4HTV$w3GD<&Hwg(y2@58c~{e5H)hvd zw}eD-)gyBmD!HS$nS;~HwYL=a15oVU#$)Ph|CZLI;(I7?$DZp_;(C+it$OT4Y@3zq zm@G}%nwh)p>e>1OAxG?gd`j0pk@LkV{DtyM`UEN*A4!~O4`^7`@d%Bg$WMMQq^o$_ zbOwuMo<@uU{{Aj0_;V6KQ+na zvG?%iSS0Dvtf8j2@#SML6PLGF#=TiwT(TJw?4g)~N=MYN5kN4H5}?P2XQB|YlB1uS zyUBk-1MT7S{`&lH;50Z5!mOJw+b-aXRf+b0`0YBn*bBS48Jk+!n>)Du*RNMiUlY|5 zDPY_0JiNhZe<4{M94uVkDta*hx=4MI1EZQ=t?VE;Oh09nJST-tOHtABg>ilRK0L?Z zm>VqLCqAR+`0Aw9&EgRrrVwg9W^$7Eij{R{l6UQ|;pa1KADRXLtIrL0of(xfNs}2@ z5~85tNEKsc)jzL}8-r7AGZu&;VEV`wGXtdBZHs0ENHtIfJ!4^L1&#qc;5R=*+g}Tf&H{)E$uctGcGs--$A5EHfGF%##R zAo3&vX{Ko|tveEEaa_LmJiy@z0i;16C6o1z>G%`%)>uf(@;7ip0)LQ|nHfS8vEj2CC2yj!vz)9D8!e{fev;r3-3><8 zipBmfzTPpo(x~kc?v8Dp*tR-O$F|+EZQJVDwr$(Cb7I>`CzF}?tD1SH=6nC_b86pp z&X2qH-nFi^ueFwhDyWRAs7dG>Av1MCI3!aq*me>ViTp?Vv2MQinw#uo4oMB9(e|bFCMF8tEr<9)N$eHBhCHui2%IG@ZRd?IvF^(HmyR1jRw)MS;=C`e3uD!jiu3H~`R{J)Qz|jI|JOr_+ znzC1?TnA3RFJ2lFb@f%j^{(_AwdDuwf6Aj!WoJ)WzmE+C)c?LT{60GVcWIcU`s#_Z zgwAUyg)M;(IWml(c)(%L7$<7493c~lDio$vt4V=S7hdO&qJce;FdF|?R3jIy{r9uC zOiL@Q3~Zy!y0E!~)G0=bQ{H=bOV5L^pHG+THjYNJ@WtD^*VUxw@pjwwW}EB7rf-f9 zL@)eH3qi|%P(%WsRUarEB6DX9kSJt@g=g{xY5)god-R5!e>+Od+YQcE-;JiGD;jtS zcYTA}nNYJmbYp{l4NQ7|n+txv9e)S#^;!}fG$Fo1@l6u(_unXlC{gnb2gTlRQ(*Cp zEy^)vny4RO<(Ry{4Vb2VaE6Qk_+}DWyt)})Zbf*2BMEL1Kq)vqlh_)@Ocnd-SRDI+ ztnA&28joAH=Hsr`-sX!T5r*!ophlG^^J z+l&kM$slUOsU!{7v})obQAWd)n9=IDt>_b^vPY7t7tPxGK-CFJ(qqi1%BF;Tv>?NV zqzR?WwM^qf478U7VW#3iRLzSFg;yv#<4H0}fThKEKJ->hb!Sp_)pYfKCM`P5gVBh_ z_Vlr=!?Os(EI z7?r1!9x;Df>eSy7UwS3#=~z3_L{j(Bz{e_ldvZbJ$GLdN7ld|<5zXqHfI7AVc2tf1 zjodx%#h;6uFw06%80~Kw)K!;qw6hA;2x7xAq%mZavg~*nwJ;{^GTS>Uv$D7yluI$z zjp9RPx_6qKz+xhQ4K5w)5LBfEY*FNu8;cN7I9En#=FKrX%mLN`fe`4y(;wJyUUEav zY#)A@3@o??sRkTT@2QL>K;0;FH@d3=gGvcpcrp8+1Q~NI)17~CBel8u(EN)xBs?X+ zk(cn0oIO+Q?2#Gt2W_Ie?VzODzxrQ#b|yVWFX%o~zkxmjj03T5@B@4yCOfTqJW>@&%p2G^%NG@ zh;62L-(GEplIGAeh|@K6DoD(4OCDJV%DI4Vl2JXGTPQpGB_YWv7hU!nab#s3$Emw8 z0hp}f8Kb#Fd&L&{svTDX51$5@`}WWSfB$z4yTb3rISTSRH=EB`wHfLn_|d=Ul8uyP z)-60NZ7O2v?%?Ps5=g%ZYmzMIp6esZ%D2DXbW=EKXd;Qw6epc89($@Dm)<2lSz(cE z)*4^8x0Yyc;1xqHK(>phK0I3N&Kj@G^Ab_CmQU-0iFo*C{~ZV7wLKc&`49MZ>4Es# zpI}J2Nw|CW6k+$R_q^3{>hU{2HT2>BH+UM*?-*H)hOg6Hgx`G0XZ|e3ZKHML)NqM6 zkDurwxGrISo%r;(QE7gg?6R?uh52f0ew*a7Ykq#S;ZlFYA`u#1zn^q#D~P-u{MnlWdFn>D>GHFBP6ufPCw8GT4!O( zqp>3j9@8Ga$jlZznejJy>jMg078-3r_mc%6FJi~t39${NdIAbkd{?GKTuoD@IP}D5 z%XD_Sf>+_q7Z*P`P0GyJ8R!gSP!aI{dXP)O9TTFe zE^H0KsHkq`9xA8Ub;wm&$kJBUMc(@MD`e!nx~qT9SIADq{|rNBb2K| zjjm{AR7$kShD)OTKf6+M@z>BNi_|RHIM#xMW!t2^DP#XtFG;xyU1DJt5jEJNT`F`1tE0?{bNk zRenk6pDfwqAwG8LS)PB>(`=n2ICFidOTv3@Y44u_{Cc&({7{f4Ue=S7Z7l!@dP+z; zq}mEEB{A!=cR`hBZFT64xHlp=-XUZ~;x!BJ}W}B2rrDC6m!kBM6gl7Sv>et%wM%t(07KD;4e905TyuI>n0I zrRApPtBz%Fwcg)6$6YB#sS*hvj1#Uc*W347j?-Pwy!MAcmLJKm@d5CoMu)nU4OrTZ^CYJUEef3H@P~sLX7s*jPTb`qTipc1dtT7^z zDKGwYEX-4ynr5`qt8Kk7mM+&@0!B4i+>?c9NPqGv3$8=LOCF3FAizLR! z+?5Gh@UA;pRwuMWH zS}8D2S?beWQQ^$pl)xv7k@X~J5y)1vSq^ma$bt}(DP*9Y_Qa6?IGzTu?c%Y zfo`&H8o45!uq1CtJdrat&?qQtYN}`sTTVfE7Bl2c8;&khXWkI@C%k?bhGd>gwv$Ut z?zTCTS#$}1VQvfMS_wsEEn$}5XlCi;mhO;vVrw>CIAGO$!*yRwwhkROv{viQKeft@ z^zL%eoLpW$y?p8ntC@9YLOre^&PKXr`>DuoY_3`dpGD2S$Bv$XwGQCIVrQ|^Umd@3 z<|*5w;*qFRxP|vp8fs~M-+ziSHAd7;jT9ndt->qub`is!c(BnFGf|~Kg@|nL^^O6zKvrj<%Id;Pk;>+>@iK(nT$O3Z2tB1DkNlCGnSxQY0 z?4b_P7&O*>hZ(@bO8fJqZp04Y)fmUl?uxzxSJRkb0 za2pn<3ZAlyv76eUCj!xBNU&+A@tNrOc$K%3q6FkSmZkfPa8l^)xWyn&+ z9k?FvZ9aQOtc7-k!sr@b+Pp@QZ&pz7iAP48Y802G(ax9;O(M}bZ?9*yT9?r2sgiPt z4>S5i75fUdrv5wakEw`hLT67`uZUzedsYlMA^M{Car-mMXJD z9D53bG5;vP=%Hb9U9&fc!-L=b;-L*?tU7*W+uKnBPo2mlX~Y?-Ez;?tdU4=Nwkuww zJZZ{aEprkz3b{!e{km^EV$c%GG^Q{8l%1JXiG(UBbDDcVxXZRZ>X0>wp!fj#d1;Ym zHUQI;&B{gg&Q(hVjVk|(rY$D3E#cZp=&+Y`_xUE zpy6T{v-PYG1_XKA@x9Vm*NA2>KfRI8Dq1E~Ld_;^{6DNFylZ12h4x?bqVYR@>!-NL zdPf$vmi`8&_bf-3z+>l!34^)D1Tp60v>oaQjV68-qXvENS-1EQWF}&?&+56xRrb%J z*|?avq4ppAaSIQbJ3PeZ?Y}Rv)Xzq`izkzLSx=>Ga;8=9$uQ}lwK&w={`nh`vH(Az zS-%(GA7zjJfB!_|n>#qz3i1YRIJanwYrn;$(LsD6Q?^I8*PG;A zGnQA97w}u$?H&EysT2#?Qy;)w62`t~T}+sY$CZpt7<<94}c ztta$E_IQ=)%PU;DvX2iImT-_6^U+?cjtkkyXvQ@J{Mh zEOX~2jNQEv*4slI<*}#psY3zP_7W&T`iT)?@>zU;FVd!1V@;js z+Ls(4uK#Y|EYyBgangjIc_H^3D~F%#V~J9POUUR-n|ys4?4{dCS7&jNGhH+uUX$I@ z^2@$wHTc|0Op+Z|+kl9<&Wc>RD?2-Gtl>Fvml^V*65E&vlLTdt3boRo0Q0~n*+Mgq z{+c0#J3rD-6}7R&lB+5Ww#1OLdn0O5XFex{Q1{-Vm8oOJX9{Q}IG;{>ua-)schVM~ zzyzlT;M|>U-&3w6EtH<|1dC)5*&_Uu!3`=@vJSaw@(HdcTAcUA`?rFa2lS)G#dH4+ zH6|!-579N9dj|K$O0a>15f8sp|5xvMgI$vK*C7zRD@Yb-&?Wrr$jBB)qLsdriATL4 z#pa5z0y*)>T%nkkk||75q!2=IsebJE=X}FZD9UyvE{+!pF;&0 zehuBD7?e6;2K&DUheEA=Wh!Qe$W`+XdBZMjQ`(s_JEarHR62z$v8{sj^p<>TkaJzq zkE)1!COVvm4DkFBgxqrXS)#QGbUFPpmz%n(1xc=m`qdi0=-~A~zm2y}eY%3^I(v05 z{p2`24%e7$fma5$?xR{6_jn|h_)kMB(~6bd%vI7#?)pha%j(NInp6^LrflPlit+V? zdgD`1aLLT|zS`sK#8mBGF%Ev4bzVqhuhWZCl{@*JyJQ5URE{GlRm>@DO9x2IfEjb$ zX^&}l*F?m?k`C^ZCFNwbS^e*u3)X<>zEPyDdSP%J*QySF!3<)aZGs@ z5dIHhMdCN``%o-mT_R!`=RaVOdAvKdO%cweGR>?}IKh#2=Oj=!(ZAMMxhyahJfN@i zIm8wF&$`NO%a+8bLxITj`sCJREFS(aybsBorBUR7Klkj@8~mEr^EqM7szKJJXwxhFj;M00>ddl1(mVW?E#@}CS?m0s z6eGzoW(z^qH6zIn7tC_2=yDouoc{1geISL_1?Z**A0giG|0g`l7oQz=`(}j%|9e(Q z%)#8~f3reeYUXa=x466{v8*KSQP%!b27z0J?4l5xf9a_P!5D`zmiAVMS!FeulY{Aq zrK(vC;DbnIQk+Q47m{selbW6UF(TQ`7rdoXoOYhctlOVmQy#W|*;dX`j>*07O?w}2 zzh80RZ)b4dk$idH=>J&R)5F%S0m2i(?fVmYxseQHU~Ny@r32~UY>nM;1~vfOeRd&0 z+5v9Be_=1i&J>_G+%eWR0rAU^SiVs^z92;4fPfbmfpKeZcO)EvAsqoC5Cub!a-VtN zn-4167r;Y2eMyXwJ9kqbF?I4%9XWRL(jCAQT=j!=@`}4m!Rns8LC4cux@FV#6c{1l z>aO0$;p#5i*W$uIjIup{iHW&Bd&!QuK7a9#Avk-9iy=6Ffo6DD!sB1KaRi@&TmYv@ zU~brGNK4z)<}#&{&xc8#K7vgvc+FHnhsGb5)@m34;D?$lq@`(0kjo@JnAvC^(A!fp za8Yv_-kZq!1C3Ul4yF4tv3W8jF(U`ACY94kWvJD4!{wC89Trn34wxN_+PW%b1p*5P z1am>?yg8ESWRwi-Mt0>{8C@l$dXf#cx(-|9zobYsTMFjr*VPxXt7@sW3wPCDF z2z7>8)R|L{8YvGzM-434pYy-0^WHF=%NyiVo5PSBr7t18xSiOx*GOEIGtM4xx}u7 zv&PC4^M1EXr_r9;u{o1-D{yk!@oo`V(q#G0v_o^f`Sa}HHBpRL>Q6E&wG2(Y+B;Hb z6w?bmS5RPQWgh&IkIE;W+cDgDQ}(gxHYyrn7AqTH&sOhdTu6~MX1O17(!2e;BzCgu zpR47bF<*37&D89wEfBhr!4`WUrMe?e#L~6UC=KemUj&iAYP|~%#}vXL6hJh_*l$NN zKsgK-#d4v~)mNMH?c|JS%rR{j0F({sz`7dlf{3|2ak*H&kpit5u3`OH@3mch)e7Op zl4brt=vlde1g#mM@(>4&1;#>r293af0DKi*n$I4ra9(w9*JfuQ*(2nw>@aydhK1Vy zIlWBpud|N&&z;ONk_Ee6bk;{ly2qHdx$d|oGOwLF`IBGe;kWQ7NXfZkztDUR-?)N4 z?Es-@e{)f8X5(}w z{I<%`o=&Q6>&#ve6~OHAI=9eenCIfpkU)PIy(6`V3}BPX5y(ea_#z=qK0`E`GKkYH zEpL(KC{rvL5s8%>-t*?p+_Y_hdrO9>Ki!1;g?&aL95^(utiY#2^DBJ)IqfL*{{D2c zF#w*o3AhfpBcC69%BG+?L6GCqQ77XIwJhC0TrkZK)nzz?_%J^RA+(ZSZ*7D&PneGp z_bg~!Bn!ezafyA96Yo%aQQHe1MBf}F(3M8%SsSt?`Xt8mWxDmI_o1<0NZ>yVFVXf& zcUvENM$NIm)z|}I^8fx|$DBdQalFO7?Tnm3^$m~248zpfB-VAf4ID&ld(rPFG4Sp6 zR$eun-=@E`HQY+o2!dC%wzvv;`bvE zqfEwv8J+s^}^ua6-!GC6hoOPXC{O&`;vq!Tg&yMgZGVebPm+8-9nZD0&~kJdvT>1 z0d$m&bd{C@V7}X13aU-WEtn2^PKes+G5p&rD=osNky2( zI2}1?IoEc+~gP#h1A^876+qA zOxPbVguz*|>S&ALIy9|s+L~c!=|%^12IBY20G)%*2ax-(>=^V--FSiJp|!x`iB+sC zntcu0!YIq}RpI)*!Nq$Q%<2>DxOUnyOUbi!x^{+>335u4qUy{=XQ@^idZ!_cl_vYV z|N7FfFQ}${60vfoXsu&^S`6xj0Ny`CM(8To{;T%d9juF|RTjd$-3ME(!!g@dZKOBb5CHshK<%FtbLDur2kj;G_;FF|<4rRMVU7sRR)=ojRFLKlv!WGgU~A3r>({#$10 ze?pi4V3s&x-7;2Gj?Yp)&Tl8KX--!b?%v~8%alH=WasH6gbdGEwJz8k=dBfKmsDx^ zO(taK6>|rA$KRQ-5LnH(7!kP$H&7@nS7v`Y=Kh|*LQ`+C>7`6WS5*g?A3+{(zJJxz za)+bdU}kw6LlH zj}VLcq5{%W+)uGN3S0^QlP)fY6e|`6w4J^C1V&eAw6!oYD z$}7IN5+=uCz;@ue^ahUV-CXdK-hYSW<{fN$wr!>r*l@%7D>K;9CN=OLj2`Kw6K1Ae zpPTBAZ+!z{h7$G zq4;Qn@y+X(g4-lX80ZjdNEjeNg;W0p%9rWIXl?N1WyGAun<{Y_Vt0)MI%DVH#mHh{ zz}mK+F%5@s_%}iOsqlIvSc?8lbmXoCL7e%1v3gG1#wk@`X!gYI2sjs~&Ah>#j5`_y z$4u!-$(ua^hxSLZ*;JIdZ393#Mfc2HdyOws1mH;TK%ARrVdUarPm_8!!56*QKFXfE z?TdN%&CbFeca_SKe$J6D0blHhiq{cm<3yYwWqUrHU^xmgl3*y5<{uvO9AhHtuz}^~ z={V*O`AjzQ7B5zBJ_*}+_jz$JID2)_ixg4blUy3N`C{gxH9>@~Ym!TRH*G{ewtICn z5L)9|-r=bO-Y~XbkkA&>G0Z|`&@2OX3y^kr_`7L1+lYK0ym7USbf}_Cb(aL=Th^bV z@@ibq2+-u&X*{BDF(sq;kb{bQ22!dF}+!;ZE>S7ZZ-?+jonFb zwX{n(#rHcmrZ}Jdl9xg~H&K?!*wOt66Te)OUH;`2`dHGbzdg^iw9# zcH#CHG+b|wW$wf zWp)~YuDj!0_*BG8yFcCJ1FsXJz(FFxlA&k~y@1BiOeRWc`F#o;AwQQ=vp88*1Jaq2 zH~}w_1KpO)2{J_^Y)1w*5Y6dgtZrB_`af&51-Z=@0#{_sC~6c%MB$`M6yuBO5m}bibxx^bwkYFr zCRRHDp@>z(p2*7FDF@P!?E_s~oEE6VK^j2_V#PH&8qyxbZI0crjoH}s_OZGA0^&T` zyR5UQHKl^{W5?-LnT?(p@?f1&QMiM`xw7%|Xu6F_6yz61AnFqq6FX5Wcn339xWWco zgFd?46#j5Y;vMXzW412g4}w=0=!iYx;)u&hU+5&&n@hS~qz)tunAEr+%8xK(s`gyq z%>040u`Z_Z39P0Q;dP0QF+|SjtR-?q6*I8y0{fux*wT_`JHS<<&(FO8w$ z!wmGEXT;YwEOrWVymFI3q>x-Kf8k ziGXZ(T{V1htgh}sJfiRDsATY*X`PD}IR9O_TeNogPq}v3XUMM;{ z7zM($XS!^;W0bevlMj?B*itTVn(ZppUsA_U#QAgCJb^4&NDSHalN zIPN){%-kGGCq%=crUa!H+n;Jqc(spB7*Tz@v#@Tm8zpzs@C6HOMu)AolT4vcUpP&32Z=`f(p;8~4nUy^|h%{QpfC_W>jcQX|rnX8H+A{Ab17CO$o#LZgajrh~wMJ0_-B+FD} zogM-eogp_5F||I3a(dHfC&sH;iiJNkONkYtcDT(T&{0*P%rx!>(4210Jv7eJIrL~V zcMsO7$(?*mPt&4WCU&ft)lh*hNLM>A!psT4s!>HH7phL7=2AoQ{mJDd8u!?YHHCRjNT%iW{po&Ll}nLHR?t6AbWrAaqx!YXnK?N=Z4g z?jQ<@)R>7(`EeSxVS^NdBjg+MWnD>#;t+H)^Eg^Trf1LYRXI{EN?uomB=uodT^Dh% zCCg2lYT`6<1yKcOYtcl>`w1|E1`-O{&2xNIU9j1T?BhrRn27o>#HIzP2-OF>bW>4D z9LrU~fPHp?Fm6rizZC|vTV)4lRe>PYm~MnbI)5xQ-S^W~b%horrIaMYhprWiXq&}N z`GYuPsPe<+hlpb%(eC27nO-@usgAW5Z!I_ddDq7#b> zF?aMget?x99uqmMw)UoM>QFpkB7@^)VFB7RmT!1X+BEyk0M2qI480 zY9(DUEd%B_1D_>9l9r(Fo*LNae}j?83d&9#O&$9GuT8W2txqQjb4iXI&(+ zi7yl@ojBsQl$Xq!3Es?_yWzH-w!fmy!e)aY^9suOGxCBvy`n*#mzsQ1AwN&jjig^95EOyp9Sqw@^N}QSC*Elv9mD5uTGP( zUZSBgekm<8U!DE{fk0aCaH2`EjvepVvSe=}oUSUySRTkP3%-5rWn0KY#z&T+lh!0| zWz!dS=0}__$r2M$f`!c}cg}*WDh^TVd^rpW9iCcTJkUh7j9VK_V$MKvs}Z|L0ih)N zn?l$w6*XDaTZsxz96AvbY!o&LeA%JmdhQ9*K1tt)vjkW<^b( zp1#-~u#zf6XPa?~gfEjv2@?#Nfen)qtlL zEj=ekP81q|zN5%Vm8{u^B%W$%JQb8)31N_(jgGnkFV_s8F0G} zARHQG-uTb2EfxNnGUQ^RPkTl>Ph)^L+y z4E6q}R_RAY$9#1C2VGI_j;HaH%1!MkpT(2yI9^-|5gO7oKj`*B-v{P^SG9&7+${&< zp_Ub&P!&cb&&e~4#qWoVphuRNH>%b~<8YrLfT{9i%rZWR)q0M&{>6b&4+B`kE+W49w_vvR~@zA?vUeF zq->JAFhRk&KN59P_A~MAR*YXQDGtl;)(&YrOA~A%;aWu+V3}KJTN1ZUuU{jNKi0)~4(IPr@6xPE&o&{a+cxkfnB+M0@&?=%oPgAlV~lW;ratKqgu( zWB?6FOd73croVWQiZ5aM6om?JHoF734xLqo1#``zqG)F>y-9Qw=QJ%2tA9I%f!uoz;93MtktvnD89@D zpdm-B53~Znv2v2-g^AGcXCw;#1QemTxiKvfxgkK#sPd0WZbft2X?~pSi90~9j{MJk zSCT!n0Jf01qJHy&d{8XCO14_6W))oJfj9X+CuEo_h<6ijNz<0qoN`M2MnifTbffA7 zgO$~p{e5cLra~6$h?sPf%gIKOsM$1&d6j&wJsytJn7E@&851;XJR?b3PVxKwlR4^PM79GsAcOMb}I<%k0*x+il(r577*z zV-4wQj2jg)gdz)pX~9& zS=si${~ty1iD)2b@Bbwxk6K2CVf~IPKF9j;gZ#hU7yl=}x~2Z^t7Q!M>Yh5x%*<>w zU-)I&oa|z#wcw;x)T_Y4wKi@PY!lpURjDMsTxcztzDR0}6SfkdhQ^Sa1p{T)m20zZ z64f_|Lk&Z%dmE9$-%GHyshV?~xxg;P&CW;QBfoy+zWwZdHS^xR<2Y|R!)FiDd-xI> zVyPg{c-5C-kCX6E8iA2>^uUGOmC+qCfjg};VXPyyGh@s%wNs7cj*lD_Upt~Bb+Zci z@5L_w(Sz`oh@9XkDb}nNl3?kEX|n>j{{ogE$NZWY@^04yNmseqKmH6{uzCW3Ct!2( z41V%g03Ba=Xf}rqY`_uC1__K-5Sd;B0z%m;#r&JD59dgz@r^%4(Df9ViuNX`yYu%d zI-l6kW6NIJBWz1wiX#T7V@h8zq`IpNG>4)hh$&o^hxTD&sN4j5H8?nu(U5dNw&&bz zIFiBUr&%w)xU9#kCF|;1TfLT*;a8jX8pteG>3(|6lf1kDrP;?6E za67`AjERZHjQ1|vFt^ug<<7vdkwPd|GJG}C^6|~H_N8XZq&@gnZla%#&ZNRT77uPEEuBhc&G>?qD?RzdrH1Lm@H-J^Uw_&9T(r0YD>3=ynmEsAV z-hPOidf5dj>Urz8&@CDUtGWz|jped$&UZ2RXm!%z&A_!hqB}I=9Xv}|sB`%+a?3_r zb*H+(HC_}3lJB-uHXA#ZZBYHmY_2`ezksCA&wT+J&89fOy)F`Az!j4k>nZDUaXoue&& z?#7*PefQvblHcj%MZk~LTF6tFWTIv3^2K&5s6B$Xb^svvydeSSU%o}lex31-Zp(aUGJS<@w&h z^~-eu;nqn@&UWSZOpK2%lFa?{yl@c0(*uf5f}-|GYR{P05cd%S&(>{>`@OE#f||#K zdG-2VK_N`T2msKQRbLq>5`wRQ5ZF9moHz`tIMy-`kB2YP-BteS!0H@KqHH4Kk3FoK?cEmNXmXAf1RDpT=rD-VEuTl)()OXMGYkAA9m7DcEu-rdm%c z(NlD>^!b3)LxnQWje`Njl$brBOF>(TL6#2+LIWFmSR+Bt_TDA~bvM0p5g|(TcX2ziTZxThB?qw*$$`jS7 zc9Gwf{fzhxk#do`Q}m~BX^gwp2Rr&;q{kA*Ke&iovwYjRaL{$gyi&2YCW8DyR{z!I zHP3)EE41ps(|GZc5iK-NlI?)+ma!VDt!N$pF^I>dq4Dn%Tqlt=b_IgM!lq)Hd{Eiq zue^w|HPa#osHzeBQyWRalGp_{6cW|3K{0|9wwegMp|b(BQGGYu5K)>lBJE4Y&Uu=o z&^cN9u1f`8l=-5HFg67OvV#oKseN^+F?ToI-8$2LDTSN_d?6CU(7Y8n1Hdn;Gs7&C zfeBix6XG*RtR!Mh5g1F#%x9$XaCZd%K+A+1sY9wp;flh$!r}JMHV^~RN9`-7f?<6y zBR@qn8%d^F{Hnu6iaKJH@+RgaA_;eF>IOfcdFtR4Gh;ZP2HjGs6JG>aT_)Wz~1vi>>&u(MmBgF)Kn@G{0)dgYOsYJ%ni-w|hWp)!nD$I7c zbR9*B0utW55#l>&mzI~Cg2+|%>u@#09OyljCSRZ7eyLoZiXNO(sc9t#A#JF|%BqT_ zj(PKmLvX(T3f+9d(FU0;N(V}fBWs1Yiuj4pO=+iWKw$5gQrZ-`$v%>un+_+c_xTa1 zmE3!+atxYh^ufx0e4qVuigp8}y18Z2^4uwPhosf6Sk8*rMC;YNBx{<*7|;%wm`wy=1mJ%^P>LX%DtTOZZ;{ab!dDLS&2FGp08B)I9L5 zWAYwpoxlqMkgTHv-~^5(PD*w)N7Sol0-G6~vAXPG)VQW|1(24jmH(2BrSe6hm+Dl~ zTD2LwVV_g>Lo}7C@&+~uf>F&&(8exi31lEOl>kd91i{;Mgw9Ir#;7WJ;6u$2*p>Jm zTv6zgTfkJmG8{mC*F5O*3f_8RW7y{JVP@xsNkazbZ!ks09#zD|U^98)roD(H=i+b< z8mfoqPt=XaKVxU>PM3s6>^QkXQhtvN51p)fA#kxz7bxG;h>YBDo?y|7{jb?hzZf$6 znb4Re40hFNUVJ^86UvtyMhq@ht6WTNkALW2TrrOwu?C=Br_JcM?dcO?Xmi`v3@vx- zhr$=XRv`11wVA-xrjlqGt&Fc0Na^XC+`Ej0c4)Tc%a5p)JPV4x$AyW-{N!}q;v*WR z4YRV>@kQ#1#p?S1IhKzva?v)GGqbs-t&>Yl>6EKd`zDa7MYMWIyqZW=XUlaD#d}Uu zTsIzUD{?|tbBdXtXZ`^a)&!bjKu}MzaKLdny=r{Q8qI_shqu(Q`TBi<{O83!46(UQ z!?(zN^gBh9;{Sm=ZS9P0OsyEiq=octT=X4Dnf^=Q9<8$Fu%Y~2lI^L-Ijob*7;2iC zX>Kmd;?e*af*XVP3j2m_Bo{trK}6#a>kUNi#LKg8aJ&3zLNcc9R?^T|;T9lINbCkO zy|!E~Z^(tX7ErTv48_qp)Bj+V zQ9#o$Jc< zmEi7EI&}Kq&r>5u?Xx5Tsz2Y6HsFJ=v8R2Ck{g7UZ0wZnLJa|~oO~m^;54|;b>np* z&ftw7;N!T6@~Ss~_ocjsZkwW%i{3=rA(lDW^&J)oIl7aogSkSZ>0%&oW2{>T?GVT3 z8{j%m+WQ$eM3KV$^xY=R0PTjY$=#i8WYYPzdmMM;E=VwvCRkr`J^=a$${^s=Eibj( zhKz*(ZvbUak#BCOO49d?=Yt-gW*>x4#wNXO}` za$Y>uJOe%$SL5!g@-v+t7MvRMn$U=5_gv+MN$w6wNEnUSx4FoIz!&BfhMLeuUK=ZZ z@OFebhe6YrJeSuo3HxVG38Ln7`{i;c`&PR!%MN$AA2(@-SmSZ71C7TJZ*Q1$v{KlOa?~V}nm^vit2+`-$ALyz1$Y%%)2c;{9c&cLMYKs( zfMO~T4}6mo|JKd-jMxOUg#r)PlM|NY27xJho;&X|J$-&@7J=G?Ms+6a{0?lrJmqn| zzv$dJu*6W_w$Fs7ei(<3vONwNnve@+F7T)i5!s*v=YIwU?gVm2nqrM`Z51CtG^p;3 zg)=feAil#7-?9Zi&D8Dc0`(2W4gMPVe!KO59lHU1|CW+w8s(qw5Z)&4G>wnW%C$j( zwtafh$9Xy2dEz$ZIDl}iBN&Ag>8_|9*Edu@?f8G2Vi4kP$k*DmtdhJQIlka zS(3sWyeSt!VoM`ChFtw=)$cEma7FRu*!otyO|jvBR3J4qU{=i;O?Ez;^}&>cfxF-Q1>7hco*^dEr`iCn(&S^&>4m( zh4#vh@P-@`%j^{ev_Yl-9|byTPCXW*$tbb?+;bz(QZu~=3Ho_96+^yIHyQOqji-s2 zt2p5_JjySoPnv$aI;JFEL&kc=9jiytVS{6iB8T#lqMJ8_1jH@?e_8@ScUP z<@J=bF^eX1jZ4T%6jRA8f2-OUO;!~N$VFFLs^lE8yRnx;PduJ#Op)^cVC<9!HXhZDNoIXu89fJki(|Y z`*?sYr*U!7a8n#1OF&p_j-&>cNfkCf`{~j0MuMY6Lzp5aG>j;id?OC@Lrg=yFnMO} zc?W=@bHAFs1Lt-6qel;gQFISL_=X!pAH~tM_78}mw0l%c{gE{jA6!PjYk9|8M>Qj+ z+@RCRH?XcBvR=}oEu07Lx7z62hMSMKfN1= zxrsMQ?|+y?=;MwV8wju;oN-^EL-d^Mb$8a7!ZcrCag%Sfe&k25*=ziF7w`=C;;G-k z;FiX<$^$2GdbfF|pP+FQZ`9;AN62<2n7?Xu)6XE~d-+f4(Z2igKeFh&9w&U7%yF#b z$^s*ZT!-3u%Y-LIhYHReGO_XjyD7#QK)A|BSM#odr&AS*WU8HihO@DvNbFVq&7Y{S z+K$&6yF{d0k*3H}l;S8i{R<{kPYiqVU^0C&;j##+2%_%g@oMv5F-)mFqx572bE)gB zT}-aHn#B1B$NEmfi7dsHT1d!O>9wrk$Wlf{xf2V|McXhtcyB)1ZBG3WRu>g}#fr&Q)DborECvuYsfe-2AQo_XAh~tOlV;ZEQbh)Bpnx(?6hW6IWe&CPdbpu~!KnIF! zUQ7duvETcb3#+M6~majg5V?ME}#`tE`fR?mn^)Hi_`*HMYGw;!&p=He9DA34XA8YGbmRs z#dqdMnXX^#KYGibYZmDt z$hNCy&03MK5O+-Fl~n-9-RYT2euH=6ieaN5Nxod4&9>%h-!=8xm=wj(4G$sQG8qQZ zmNKE2CG2cTd6FX_w;z1bzq0b+MnUHj9y8KggTfbIlf_zI$DHQCpD)bFn!c9{BK|?J z^Y|=q@AFwe-<$@m!e-L;NQS&{L_mN;BlW;jW`9-+wvDnVlEVf^#Qp1! zxi#VYfv4YZu0ITi%MH|}&SV+?MKat!VJL__)U54cMM4 z`^>%7cJnxI*#c)+Cc&eqPEWY`%#iU*Z9y)fZdY`z@BpdHZ3V0sHE@ghJn~cLjN0m! zvI`mr4$+nH7pI2-x}(N0qTsONz9UP7NAA9D{b%+_%1e{OqIuDmqmFf(dqR5zpD%52VO9J2TALO5;uYchk-10S ziBK)`ZySrY5H*_Oz`Gu`fv}1_Ov|Z;_4El4n z$Eh#M$J&8wBiwB@;pTC*b~4x?^buAQJ8GE_U0p8MwTrqah+jKXOevS3(v<36T#o zBr?#F#3IQ+QZWHSyEjaJSEr__P*MWFkNr=)$K4A7@gMVPS6P?6Yu?Sj@5e`W0O9eK z0_Y^P(hG!s69jYyT1H~WsLL&#IjQZ0bmQ)+6W$08^hYG<>L{dFO?(NO1~QUVO?;{9 zdcH)D=}xMK(RYzU%1h>m(`tGWy+$g#+~)Ykc(nSZ5hc%vR5tkXsh&*r5>wHP1ye6dD{OT~+5GE0ROJ*Mz8YL?bjJv^pBxtz0JhbH2E-G=DHA!j ze$1AI7>GDC%P?HbN^b;186+`SszJTq6;g2xKk*Iw z8Dbl@kyYdkbmo;|U(G~TG~(_y#we&9KGt)zQ;NW&9#PZ;Y>v~bW|!ewB@%5>+d4KM z#`}QxopQ;g1^gT?U$mUprS8)$tKc0d0_5zb^5ohQo_2C&Z!J8F5&AquXY09FLGgv4#N1x!g3Tx-sg06#u!}NU3A=nTKh6g8u{yh8a|9M`=mbk28{GHXC ze?e2K|EBC6oGk5JEbYwc75?K!#M02l-u!wkM_2=LbdJd#o%9$40fPXTD3g{AMFNo)F2ab>==lCWi>9RYSuVH}Swk7a z=?RSp*s@;(f>P-WI^*e)br!H=m~EI}FB=RMCIw8Dg$RQdVC(u+_jj&3mK;!57_)H= z`=JaPKIze>>w2g;!*3ew+$kA)uy1Jh}p5s@_5t_%&^Ay%H+mV;xSTI2fNP;8G+ z0&BnVP;~~x`U5dMR_vF%b?YI5xK3F1dkmvvKJ3tp_QZ&pE3e=NztKV%BN#9o57bdp z83Gy1@US9D-rZ7C)?m#k<9mgqU2I5rlBd$)Rg)xyu%d#}+3H=L;6pxItmrd{c9;RI zSg>c?;19&);9QfhV8eQgA)?^LlIxuSR9su=H1N$&=rng4Q3u*SrL96%QI^K-6p|%E zrVAaMD6&K?mJYEfn8fSFTFJ*rwAL2TPur<2;G8NhG;lgw{V;{zN?*~j%$Eoo_PWj9 z!M&B^TaAn%`dswb(4;hi=_k3(>s1GaVsd0zU0*K}CifRC5KBiIiP~&eE?s`=wD}Wm z=Q-z>zm}fx$>iK!iCVWOsG9<9t3%}4NSOxHmpQ(rYsRKa#tE6WZewX7#iK!2PjMVGX+77XybD>2o=hl3r#L8%&?ZTl>`K_B z02^64G^fv*jGZS8n$v&=x@_Be~uk=1E1BbdF z*5dnpyO%yZQ+&I3!0!YnV|s#sBOW)wz=_`3K>G%;;2XrijXyBG(+Ai+*}*ZQ_k*Bf zA1rZ9m`47#-629rfWjXTL*bn~;~n$j4~)rqtJ@mVB0&?fByQp;+rm}MNaCO2aLsdm z2EGqPh;X1?M~``-@>OzBkC7;1g$saO;#vR2# zz?L~%iqe#eE@E^1h9qXU2JV&258oarL;2-3;4{1t^407!`!wX1wm#LsO z+K~-L-%n7!VSOj=LVQaN z=r?WJb4>eBt>4~J2EPY}b!kM2cu`zO-9P^z{uJ-$9Z-y@ z)8MTZNzzKHm-Rp10UgJf*bvOujR+r<2l@}Nr+*Qd?&WGQVP4Kj><+VAc!}&KPuG7> z(vaf8J)3&^69d1wcS9Op{9(%<1CF?Z<3(6`Kj-`mibc>1RY#z5G8u3%A}F+Fcm1AF zE;|kf2Ma7c)2E@DK~&t3c%gi=cPpX^+be65hUy&&e83UpKs>`OX4$CQ_doNnETYe$ zQ#$nWw~5M)8T(m`S>)-QZo9`F)ib>U2}*+MjWTvZq!86m31;X~$dDd8D+Xt-g*%zB z(gZ#n9}Q6ypG)zwPu#-q(ksINc zpJHipAQ>(EwS6BCd#UM(xLJ?P-3>mIn9cuNoj2o~l_On`R)$a#Keyx)eTzaNmFA|8 zb>|Mc`mWyUT7A6O9WC9ORZC}Z2KL5okZHoGs-H4qbQ&;V;ej1Opf5pCX@88=W0;_j znW7(|1P-d0eVw4mCtT&8L9ua)nmb=73f>}-uZ}(MHJISQG|ifM5|MZ{F)Quf7)rhD z7mrUzSel~2#bFSB)nKCEDVcQCxgqN?!Db`%X%ZdtOYN29S^eo_jr1|#q~*re%4D2! zOU-di(m;Y57Rg?O9C?-<4T5$ASgz&k10Pcz1G>&=x0ais;fJf-@X zyaDjlbNNuGyM}V9zPZua!PV%Q%;N`uC^+_58|}S*ifp5gg>^z%LtWQn5s)JK1rsz# z3Q3;_$+HymDRk^{0xh`4w}6-zAB4iPlr z@Nmj>?21Dz7+O_}Llt;NuP$`hnt+x=)1&W`hP_Wt2Huj#{~k8UCDh84bc`mCh`r~7 z@766WDs%F5uDP}bNPS&ANjr0Uvvo+U^OM{`;y9J3*K<4D?s1EIa``Ej zH?uvHmp_6R@rR63i;d$UxLNJPACIkVcJ|tU-|yW%b}ch~E(zU*+oyh8w(=yd4T#kqLaa!xH))Ak+tZ?f3{UhsF~Fsx zH1QJP`IBq&3Q0ic1RB0!`5rrh@!}Sn6zfaQVg}3E&U70lRkfi<3?nLfH`WASUqX)r zKh$9PLKaZHrgBt8h?dRoaDT1|4VOm!!%QOWOEB56>MCk6D{-Bqh3zimyFAJ@Yn`UL zUIKMgd0E;jz1Hv)!WCV1=*X-aO<)sRG#gVT+C-X{@+a^Zt2tq)nIoMsMuykZ9Q6VJ zfv6ZuN=jNFbA}CEP+>87 zv|6PlDX2r9x0fOv{U(%@mnG4Ne)K zR*4hv+#F8|iEdvKi$!HQl*8uK8xJS9{o4f^J&OS-}Oc0&TwTehau3jk0_dLdmjBhlA%mv?j3rgji0T`XzZCiQGk!|1E18&Bc3Zx&kV|>cW zi{}l6rBzn09L%H~Q+bUcBfK(-*Sh(ulAC^<_U?{*aq0L+@7JxH;>Ud6Hc6~VF@qWU z>PbRPxH=T8)@9X}&Q1=>t9qkK(UGTg1wt#vz8&jU)WRNv@X>0K#G>*yLg zXew^F0(fSmZPNS8Kv=JGL|HYcn$j@8#-kuVIWFG1ATq(l)G11h9`q9KE%4NW;v0{K zHzlK}F6)$FZ0a21T~W>qq~2bV+UqDfqY`GFSGu7r!=PG(@iLAwA<&X;D9bLIoY=ax zd}-&!VS7?(bW|4Y1AgL`cGWt&h9BZvp+qE7+*X;lRV~hMD@iOWm#V3swybqKpJ7cs z(UN|2d3AAr)}60Z^Q78YTB#)Vb=rf)ieV`>Na_-+X`D`ADI!`TbF_NRbfGQzV_2@1qvj;xS}A9&tzJvstKh1e&lLku z;yz2kTWy9IL%F`KbU=7YJX5L7YeRfn#e-^>!gHyf6GqSpw^E5R3^&*hp;H_Q+-U_u z$5#Ia(fp8ZK~{AG#Y`u?jhYdU6Cf>-LPSEDLBrJy8fM^-GuKk`Mqp!Gqe3lis2z2o zc->_wT->=MzU1~cNy1@+9gvfZ|2CsDF=;X{EGVdsqV5MPCfChD_%PIZYtyk$i)uK@-CC2{iO2q%$}HmzN*VIUHHjQ z{Pp&`K)*rN{vA^INM=9JBF-8;nfX*EN%4go(c&zR(q1X%z?WR2@&Px=2TF(jQ~D>F zPA*BFuNqo5b!j8JI@u&Iz%40-mLKDHJBUg#aW_qb>Gwp(f-JJgCmxEQ2SA^i8?1`7 zIoc1M!t@yoD^>i(LTHq#K(3pt5NZzTomxq_64Yk+ZAx;&r0F>Nm>*8ke(0ag4yH!I zJ%jTn(!HNwtS0S}B~xW3`~bbTAJYOFo$lkOA}`nr(~l}<05RQb5|fCN2b`ao`T}UP zB4{+~;*E2{E+ZFNwTY{3x~_t6s@O`wp24?Lwk2**?*rJZ(i2aQp~kbk?67w7;E3*N zRk7XHU+i=wIu<0c;7E2LZS1@|d2LqR$>nzrmB|r%$o`p{iN?Gt{+l{3S!I=TkQ$r> zTjbDXMatf^s?Ta(S?XG*U{$=w(UO;C)ny6z?a!{xj+4N~hPOds+a0Vo7ON%n_Jz|C zwhI^6vMA>DlKjP%2%eqeuB9?TBtqFfRHpQ>zCmD-yGxLM2|{N3w|G81fz`|;rj%wY z0v$LP;9V>1|Mk=yCYF$jM>2l!Km!2KqxM0~!`ZEhIMp@|)`dHEpz0O+F*{b*H>NDt7A90d z>IfUK2A~1eP+2!MxvdI1ts(0i%9Y>ROrfS%%Zj$a@1)Lu+nCq=weq zC{GEAXpO-bg%E*=Mh-^`PZ)FrVpn1}l+yRnnd!X2o$#vEsywOIfzGo z1t7~Uv5yci#KIuCjtC?UX#(LB3dUz`I(F8br?7E)RleYu;=2onZ>pa^K#%l>H1Ww~ zs84vO>eP>1JSjQYE4IF7sh(GS!ybN2IoKQ5lbo*Bj`_pBM*k1#H|$L#DfFN`eGS<-J5I= zI*Sb{4$Kt+cxhxn3KhfD?oz6h{O7OwQ7A#xaHr8wI9U*7W}=Bf)Q8Z(w*eKd_^t^T zT~+=e z=Kkn0gVoLDbUB@**!P}Hgr=&hs$S_3dZzMW(9Iy?UKf%S_KbijRsJR6%FNbKy4V2b zWNeAy(X#P6_Zb-(SNn2m>Zm>HXcR z+5)xhvKcC#An|m$V9BdY2NH~tmIa%&ja>!);D6tsDPIV_i~LwmanGg1h+w?TtS>aJ z3WMnQupdb=*8|sv{t9agjfpy^7e9vR3qx0(hB=BnnUg}3MW^VOGS>~wPkn(tXA7iU z`qngPkne@32E_`@801QV(aRg4>#fVmpD9wdMZMwdSlb7>Tci7jO)*n;W1DO{+;>Xq z8*vP(g>~kn2JIup6Zy$b5mlFiMh@OmgA=JPRI9hIqRsO|?b||{p^Jes9996blEY*U zKaBhmi8HrR??H&LY1zK%5%H*)EYm{wD`DJS^#TH>&u38uHy-tDf9+yQiU7AOrRUfU zjpySi$x$GcfCeRP6C4;atp-TO@^LSgCT~Q4B2(v4Ldrny!g}#15JLm~es2Z^XNjOF?qSIu6L{3YvCk+ksc%6+pS6<{&=c@PNHBf3!Pf9k?e? zM0E__9NZNF9IH_F1%2#Y>+M?`1t&c=)O9N$}v$mK%94VjT!rBOTMk zv0xV`VW=J~4vN?EMol>A_W2#8?y&gO?>v2a_n|rLG;#GQ-b68gKaKn%_R}6j1G_wW z_U&#Ey92<`9!Y`xVEYie5xefBJrdaPzW(0c!8>(r0QE}Fe#-l0{N{Z7aduZfP=om6 zc2_=7gX)iFf%HKc$Iz3vmhDfao6U9|TLT+s4NWxD6L~Omv6%aW>{CN(i{3c0_KDrC zT%nX23}}ZhPHutNi<=+g`ov7GLG{MEL+y$h@CMlJ5_{0^5okbCquQVWc_hAlyj_Cs zNie-b!YH^S8)hA7rsz|@xi*6aiR=m)g!c*FT|#d6_XOG>^9_VeK!5QN?2|+W*ClNe z;3M9#4hs3G^s_@i7xg{blGsNJ1jj++l~_b1baMn?TM`TaHpN6c)G0F44PeuvNhUGZ z5v7f(@cw*8(90U1wEZ^3Yd5Io#1|JHr0orCY>Bw3E?EnZBJIEOV@Xl0nFd*w zySNlls>(_>0xfB0R`h}`X&7*gAh;$I&68?(-2ZNX9C@w_^E10*1lIm}v}|Fv=x*(7 zTprzoco9odsGh~Ke=jUiB=EH5$(9hxc z4&AZw+yiCjG%5^HH<2O;uuCT{CaQ_PgA|?L8vxxJ)#+W5N!HcY*3u3u;Xy^8gOT@W zB-@s8iPQy`?gAiBs324UWYH(w$-WuiLsmk}s?{|CNKzR?k_>KD)V6}y4==}Ot9_k! zH>>|TYiUmU10GolmAP*qMpvs%L^-^pT-qyQ)Ej*v^ z%x|tNu3e0dVYstgexWTq8#Hz@s%Qaam24sZn5Vt9A@CZYsO4t6j0v%Y->1*7YtkuG> z(M6R3>%c+Ani~O4!99)?>s}8XyQxDV)PI^JUqE9KYBYYNkTZ@sGvPQ*P}LT{`twsS z;QYhK5=RZVB+2u3n6~ajfnM;%G(MKwfX>L%Ahf7c} zRxI&ao&K;1h+e=77y)$0g@NX_Bxf#Y2P=2p(5Bfn$>`W7%o6mLMFYY|=kE6=+?qUA z*>MRX33^zUSA-S9^Z~AOXy9HbG2dk3>U6TO&A+25q21@p7A}!ZQne8Y?8`Z~xH2?PISwL?bnOI3 zPt^#Mq3L?VQ!2}|f38Jz%4FwIIwZw1=FW=#EO1_4XY2s~6GYled}uN$x$+!(5`!-z zW4O$(x}m8)Ode+Cl?^U6oluf>pgM>mBYrrR^K8;x<=a$HqIR8Z#+zYB6Q3oVuw;EI zq^ij=Q^GY-v2XqLJAQ##de-1Cl9u}zSI+G^X&nh=Eo+^ev(fEf$;3@{H20Yyogqn@ zZ)WS2Lb2YtYRiDy5wXQ9EwkX@v?AH9J}d4fGQB4`YPPzd=_o;VM7doCJDj+}Fg2yc zpIaCMA_fI}R+fkzbsHZN5~p(*TN3Jc1qtdvi9hWvnxU%!Qf`6g0IijoVHe&Iw=@ic zKh`S`p@R3Mi?w7XT|kqLG3)!QaB-UwGRsq0@xkWy0_X9;4F<;Lz4qeGZi98U#j?NC z+>q3pWSFuMdY{8ocwVhEhW$*WoX&?-`K=-1y#4QVe}>7R-EKij>7NJV%h46)=-Z%k zw0wnlKPAjm#cKUZ6hU*q0-PG>Lr6wy?>59+b^YoAesX8ml9J$FX_GIP zULVD~e!}*7i`qV`(wy=`%#B}cJk(pab|&~pdU&)(tAjK26MyE z9sY)`Z-JOmeq03gPn))s!rX-wZ~&M3=5I3PL}ldss8*h2mG8&(0f|dv0mX z2YMZujamHa4H@MMb*urn( zP1PL-yJFQCQF#L`>GUf(xu}x9KR+T1x@ynQc z2kq3NH(kjTXc@PyCHKulEeq@ldm%{X)({L1NvubJ{OBj<#6h1}@Zxm1*DGHs%}(|X zs3tEk;j0X0qVKSa3~^(~0ZN1F(=`!i^#ku)TahS2anzW0Ed8 zhO}-CJ_jDff5}Bm2=etl`3lmb4^fgvK!==Mpdl46f)HMWA;4#uf{fm4IEs&;CzRv*hXh6SsA zsS(D#w_)SvGO>E7L9e~C5Cbz+6N_hc6}t;#qd{gPJCb5V-=1@E4{ngE?5b5+v+@G- z#EN8PVw?AQlIpKofUam$q@03j4@714!<7a{7tD*JeYUV2LA<$A2 zsq;gv)wvsMu3ZRG@DQ}`PY4H`JR{ca58ge<{Y-HfXaXVu$zIoy#> z6uKOc&^n!^QvPV76oFxkm(&?8GrW5T)P@3|5mWyenTZX&{S(cM^^?ZO2R?T0|3u;F z&BWk?@@cn30s?n-|MojSpJv6K$G_p&bvJT#O}H*t2Vr7(s+{_Q>h&PlpDl6S`tK*t zYL`8#$(`cz0vQTFSvzQ%|Dfv$B@^U*{~Wf6L$84;5B(rLvbYEL0%aKy073}*M*HUsY42rBp} zm23yiVrQ>{u(xgXQh1@$d0;E^!m#VJp0+F@Yc)r@VF(S58A(J`UfaPlzbCmO zq#Y$Y$gyjtQON2-zZB$+2_d5}-$znV8C4lYg)!5fpe!Ga@)j~B`+Q}x0Ukv$Tg`DM z#)DarkcDOt%>k%_A@vlR1l;tZM>?hDgW;xJybeaNZMgPQ0c>MSle5jw1}e~~Y>+WP zt4?6~N4l;=Coc-+KxTn>wWjCx7h;I{rh^tS9}={ zGI5=dV@{7fNe=vlugD$Q7Ud~hq1{+hUzi?yB+-~-OfkZqE>^4omiO||mjZ^ZHc*{8 zmAE!-32cilLJ}ZWW>2>(?_JT~bT#~DPYcj5R-M+q6+mC}H#=c{#&2EF{OHukH&V_EdUaFXx^u44TzmQMWj(>j*l#V)`;SOL=o#lg~Jr$}3c?=U_N{1CwY z1h>tR0+hV4>IXGC!CeE9O(*TH&Qz|l4{`eglxQmZ-AHPgzA+R|$8f%Qxo5)Erb^ja z(vh+7hO0xT{X<$OLsh*`7(O2LHo6;~MRy5fkaZP=G`ArCDDUQ}s-sg*RS&6JFaK(# zJ9SbXJz-=WC!CiGnq|=$AMr3Pu#EFGZTrCRaak~}+*YvNazf#I>%@_d!Kkn6TZF{v zdw}NmjbBi!Tz=3zunVje1*%@?%oWwmj=p4t;?0<4=+K6e$u*^F(<-GMR~Eo5xLdm@ zjY(v;$sW;9k{wojj)+a}Fxm=LBm}t2WGU+Ds?>z0&Q;RYwuYptC;nqyDe-Qdt)bg% za@BjT=GCT7xtQeDZptdCr()yAPRxAt*v_RFSRmmk>@ht?{R7Px2)bA-WBO`R7n)6d zs4`}QqHg=f;B1&+P-<9R%@@E4m?dxIMIae=uz-BFg_SxL6K9E1J&IRzycjd z&X^p2Li!&FD5w7A`#_*W|F7A~f9$R!dmx4Dez~#-Apd_zgQiaZmol58Z2cdWE4*!> zn}dN0yYMYM$x8jI0)UMF zb$3{Cvp&kzX^wb7$5dP5W6F}V1^fpa&bhXD7iu~W${Ypq)8L`U9BE7H#TBIF6jMa_ zau(JPihkWjk3JoFa>|%lYe}6WJk$!XljNH!AG-5RS(Jium6)|E^GTHdVm;_|5Kq4P z5RUP(R!Z)yfcn(Il`rWovF-Q*`n#M5izPcqJ!OtqoPKigW+S-dq#|KN(7BFEXbg4s z2P)G}i7hZuvkKStI3J$`Qwq#BE?7wpl9ZCjFi$h+jIcr?wwi5Z4khtQIheHIkI?Y9%IH^ z(#iDMiHh?0)|}%%J^x20Zxo=h4_1e4m#EiWTvVJ!caCv>-1?Z(`nb~S)wX$f+;yjq=Dk{|Z{uX9nxu!vsk+ z^fPhtlN>?IPB5Z|b@rni&)2N(Fmy@%jtfjr=RnHi5nHgXE+KN@Fm+?@oxYDloR@`q z_g}PaLtfcoaKB}r_p59D?=-CrPWI+bhPJ;{+5dU{Kblr$UHM;{IPVf#sVy^l3)IQ$I;)vjHFEk@0@yqvb&QUYInVK9v{{7fmr`N||r>S5(}A4j0}vXzX9 z4YBHKTuzc=rrC+cHGzpwf~D2UsXW6ACsto%#7HDvaq8(;bW^=BuCp}))eQZ&V5Ga( znv&?ZfGS7;9J(BF204W~*?J*P-Y8m4vpp<10@+N$ z5G43vi|Nw5_SJ_?!7S25n5E8vC26{EXj=d}N6aa*Hf>SBNd4F4^lRvVxr z8XOdz@K;U}(dIU@;G4wtCnkqEBa>%k)yd|-r^iM(uS85@BolaZjk$2O{Y$@nUFLM{brsIZW;08)cS@zEs>Y$6p|P#^OS;>xTFr)suJ%>!()?VZ7z_h9SSG)$tXy$fz*Y2hj|- zl!|{us&7WWI*Qvg9Q3+h2(Qi114n?k#VjPZ&WPM%HeyOnVF_hQPOC8R2UP$wm3Oik zj~Fu^`5g#rp31tiOLE;pb{Xo6D>qBi7t2F5=+lqMplfPpZpkJ)%8M*_bHf*0do1s% z!Jg4xX~U`N3vNnJej_^Mk~GU>7t0$j_y^xdKv2)LpTxfS@~d&kSFqYoa{rvg_nhUo zAJ-_o%8N9We@UbM)`tDMJ?ab8!4Gc0Uey)6O0U#@9EcwA9XXIc*&RAi9r7DB?!A5R z$1TXWq4|%=PvO^h4FmoCH$T(|eTx13FnfhtZ2wQWXRZ}GirWI*$42>};K4N9tsw0o zjx-&jK=0KNT8Q?w1Nnj|vojB}1tcV%0;<^a%jEc5s%L>RU0tA8uVSTCqbpRXCHOMNW zhWx%Q2phr~!MvnFQAJm_Ru+T}Sv@FfntjPkE8@D`J{ORE+vBCD!U1Ueu`g9z`6n3GrYONLE21AD03$#!)0>zJ&#!3mh)TYkr{aMyD@Q zhzl}bsKW7E-0Nt+kks~1Vlr6;j1IhU*8Sr^=wn15ME!H_2A1)P*AxJ1ej&bZcC+E}rf zaW2_e>Y!{OYtOHA;-;9ja5;8{TN`|BKRRcw$5a1^REDI_6s%ldZe~^_5jMJ*8{oR$ zN7EH2b&~Kv*e-S(3OgtHb;kCyG5$3%(8ag z1_)pTZ&+~IF6!mRjDTfG&$1SzyXTk*k-R!b%A%cYc!6p_#az!6nov0H9dIx(Fpm~A z{P#(7tn%iK0n*s@!{5BfBlfs!^6Lt$Vc-V(F5F{C6yDC{A86xsR4~vh;adkaTj7s<7fj%0rJ4$6+|n zsn_jx!Gt;`7}LCpign3+TpFYA3wk(t%(IY|rG3H_xh=i=*>{z)}Gw_y`q>gjv{@)~L=CNugxNJBUBuQJM#256ND z09F$tcMxV8BDvI3IcQ9J1fld%+x?cJoG}XsSmMF1gU->dDZBM2T&wJn`@^f{=hr7N z)(^o?{OIkWyY=S~2;U%o<-NB?#wh`{zKTD&8jsXv=gSHO*`c)JOL6Q>x_pYRm_-x% z;=`QvC-f)bQ|$nmiLJJRyhG*|b{02FYif(t^uQI?57ES5<4=m92Wh2X!qU!(apsGo za=8+FPShr<7=K960Irk8brO)zsZVMwelc^U3v6!S%guyvQm zyndTnr6clx;iK||^a+j0qw-5!=B<;~pH-hQIZukp=%TarU^M{9&@ud>USn7rN-_Mi zdGqT?ttgmWY;}Bi@Yh8k!bkFryy-a>pOoLMG5oSw$1Q6`-CS8)t8J_9h0VpB5mY2K zkrtnwqiXdjSpK1Ts&7qLKVUy(Cs0`avBTfA-?lOQbA3VIu0B{r_vqtnxu^72HtbtP z4LqrFW#lRI>0+(r1jJwC{2_hC_qPwX8OxEx?k^+8-sAr)KR<>G@Z-HC1DEI@@vT0^ zO-%St_!r*f|IM*{JAXsR{3QA-9rYddt-je?OxXXU#`?iUy!ZYF59zDC=YLpZ`6mB1 zkNFAdi(R-}ToncE)S_q@{h5rj__Y7L#rgsNDI5I}?fT5>_kRn5G1KGQAj*j0*L~c* zxy#QTk()$w9#SCLM262tx(}^_;%u;hz*sj9Ii5BY=ZjKwB zJI6coHvucpJC)RhPFFrcFZWF7F9*pSCaIM~id0Kfj3R;d*G;Pav}?Ye$LPH1`F7yq zu}mDqH`vGJ&&~f(F673;l(9;r+F)_RwO%0U`a7?ITD7WyZ$-5wVSpL{5!)yW0t8ZP z&dq{mEGWAu;VD$DZQP~7{_yb-5lD7i;3n};DS(W#L`c?2QZ!Q~`h>J&;hhwIg$yyB z#*@_&3bP+XS0c*MmjX+UOjb}z88X>ok;NpRC1@W86GLG4w{ zSQk>3iu5~0l6fKBCzC}wvqsXnxC4utL|$ zC$=+jve|k+?0(qVztGiHefr$Ea4MHkG)d`Jin%N45>u@ge%|%SDD<^41eu6tee1bm z@pGG`%ro99Rt&QioK5*(Gf6nOrX17@)^ge;;Ih-P!H3z=07ewp3CflEOlO+BhQ zllH0}`2V!8g&w;PXTE9xM#TL=dR5Z3&xq|VT%VtuE zwQ1=9tlKMK5IQiNIp4Uh10y&b8^}3ck#S931y^lRB2EHZsY*wj#8tTQQiKYXnnFt%|&YTnJ$Y<4`@+R7Z4lo1o48y}d5M9^RU5 zQ3QTJyw`?pqs;oAl9JHVMq4d(#PBD0_M_WYtLIfL+_l)3ai@)c019psJH74p9wS_2tIfIf4K&-D-B$E7Xwq{~9mYC%(v|96$&_450yzAR z79a0m&g2q`%(Ybo{D=VV-?^6Vtf`Fhfl(^9x^=9As;0DzGd6H95ygrc><=u1@~kO$Xb9dJ{j#tX3=Q3FF;R3Rn#lKhRg;=Y z>w5yfz_mljHNAs*Lif;?f^PK}RUX?EfW{-b0mzt_D8})VoMi(5o z!aUa|o~}lABIu-vh1jg;mMxPs5LMPhb;R6H; zx~ALZmyixZVKCX|@Fj#JiSY2G&3kyUA<~CPT|7Y*-jLSc(@xAYT{S!mBseoZxkL42 zMqg=}Bw~yCQY^Lri@}K&12h`m9u7$aVPZ^Q)~Thc=VUQ1E_$!da%rfE?jaO-Bz!(+ zmtAZN=!hfp-<*XFiVB@6DvOF9Vme7V4VnNAQtbXNBT_W+~TY!7y^MdA7~lV9U_3{mgqihw#l7Sx{7J3Y(CEFgap(fJ-tNz%e+371Y! zY9ei*m-0|~WoruPYtPRFc@s-@H;L~&6QCqo{>?pu30uZCDgsR$xwi&SV(DWAlWAnUp#ziGnDNL-rJQL6XMSW%HVwVYi8hcN2H6=K zj~^>?=ivdAI6ZM!$bYAjN`@Dod_x*s&>uA>7e@S;%2C46L6b~$&`9H6#~%V6;GxZ= zAX#A3rMP&eH3{$MW&~6E@Vl`h!0@#AQT6i(qGoH4=ZF|&({$nhg2cNk%ne5PHdRh% zfh3d12CB;F&N*#)7c=hhU*gT|xkPTl#h)Eu5phN|{7cxgvsv~eZ$|PBk6%_bJ&>cL**X(1&ii{E?BHUN7 zu@$^BW>V7gvmnHP6K^ogo!P8ty>?rhR|>(1gu8u?G6JQLxj79McCydA5Gm7il8$(-q?-e55lHTF3z;P zOgu9G0gEt(TxFI4T@*;qQAwev9MgC<2MOx6;M8t z&+Zu}sKmL@?-XCeYmlRLK2*dBCSm_9=wCG(U_*OHP=4QSw04DM!sXUUR{t z0ZS*1F#%6firO?YLE20p=So&uFt*|ObB%D2GRlU8z!Ky0XqHreY$#=f{A$Gfmh{Vh zd(!HYfrGbHY9v+?xGt^pRbJ%=Gmb~2*L~g{>|J)zfrk>Xf!8L9I0kh@9ipMFa!qt2 zRJYr(#f!sU^C2W#!^9082Jacs*QF=l&^yYl8>leCEyfQm1uORJlD+jaT28Ua)p6sS;xZdZ%*l!O7(w#{FG|=L19~SgF;=^>(uz6HN0yGXluC;R@IFmO=Mfw zl@(R0JRCi1B!~hj?-~CrV39}%#1FD7qP0pIMJJMeg1IvHOUI}`9u4GIL7KkN5z|`K z@UIr4(b`w>!fL2>o{Jlf-&=h!qN&Pfm0n^6medR}mMpYPU9M7>Sgl3BtGBA-$=v9| z-HWA@uL(3)fG*k86|a*1SHn)J%D|8h$4Q(if0!H5q<>mQ8sq@~hSDr!%W zSms`?@}ji|=W0&syqFanQa=_mvE&7pGXe7e1{Z*>=7xTo4+Z>8Z?D*N;#$Jx#0T4yA(Zot$Z~@6(x3HtO?k9K+$&MUPD9bqX2&@QCx{n~KGt3fZ@^t9E2&^XPxz}O}2McX$# z&_w>=qfq`wQn&A)6oSsKL^txnZT_|*=;><~OvL(=+VH~)0ifT&-K)#VLAQ*WI%IH2 zJyfSUR>RQ8QBZAGOf>3C^EF9!zB$ZAQ#NLq(}fml6CRtA!W+ z+5#BzU5jzcz}2O{*o)v~vZGg6HBH<%5kbz+V(s6Ri0ENax)y)7zmhg+D`E0xb&lSL z_kf{JadY9~D=URIq>kYRz{S69>0^8W)&a3~DXl1^a7W3DH7{STCLDw_vmt)%5;L-1 zWJ$b0RB^3KkrtU!LntxtfFebMMM!afVu)-OM3G$mEO&uwhD3MTyi$h8mpK!%kvSt> z>c0ybBXbt{<%1f+*RpLxMvg{0J4`Dqayt#S9sq;L$V$z8VH^bP2z}ge!Mq)1B_h>( zCQDBEE-8)Y7^^|R9DP6znp_6&6R)K0MDd49z~AVG@e|RRr&d;(@C1@#LK{jC$w3?S zQbUK}YwiohAj(1aC2czbv^}{NYwRPao9YG=HzyX?W#xB_Hs3e&J66wD594qM*w`z&_$66e0qZ*yu2whM=WN9%$T`Bqkk}DJpT<#z2{G#33 z{8!BqC-@EjN>m)kgIx@nefx5M2~b;&nkEHG3)2;!J+d@RePl~+;B$T}p5rz_q2$wag+PF8-3f;0)h>3J1*xTz6sRz3bJ7>x$`m?T9ABy zxLw55ahfI!zp_o65xW(IbkuPA*foF|@3lO`RR3arVQE@^%z}=?YMZq~eXPvuQdI(B zJ0?TikIY=eLa0JCX&q}Q*^n^Q7j3u>WlO)<=GZ(y-FEI~h62HJf-pB<_D zU(OPCp+!mM+XfK;UH`<`xF)|6O-yJ9e+7?Iylln5Km%GFmKT&c<0=u8&G#c z2xhXJnSVv+j5zP$uv$YD^vCJt$ef@- zJ&=NHgCM_IcMMR75vvmZ7B`yX`Ws=-(sN)}RP@V|>d1{f-2{ z8S)c+JH9h=Hgprppij;=zlUy{Y*4J|B{$foHq6|JpmHmRpU{_XSX7voZGisHF)Q`S ztd#EpDv2?z{FC~i7oDpu#DFCdF81Ma~o;CJxuAtP%NC#fEw5iDEL zFqW}DjvvHB4Gl;a6XS)mS+p<>_9lZ2iei)o?>-pO6HUYV#7SdlK19(H_huf=%*hhf zGu%(n++Rs>A0zw|ngqYbJTX`W?&QF(<|P)l65#E5Sw~~nt?d6~Y_$XV4Iy_!dtxP> zQXqs$2!u^AX3#sG{SuQp$Go6|a)*5o?VP6|SZ1Jw)bgts+Xoo?J&|>1M|!XO^TEi! zIM+;@6dgP8dAPhw#|B54oJuu#w3FvW9k79`@+uzyc(z`BPvv2ILJ6jIUfSDl_U545 z9)4Z&fKs$|itCo?R;W03&WI^3>g~EvxzCoF~K9#x2YaD zd2hW+! z&|GUl6&?`LU#L4M(u^su(eG0Ag_Ne7%{p<4 z7}a!6ve=@1=y{dzF7z)d?bmZ8gv)T3D&sJ4#x%rn*uUeAPyUrgyPdR&Vjgi?JWS2Z5{QcKlT~-GT76=4qOCPI^;UH z7o&zu4>Hgiy^2-mFzo?oj)T%{&(eK_X}u90%oq&I8&7Di@FnHD(-VNETdR({j12n9 zVuM-R&jXgt<@^}p{71FYY}Ak!%%ag6(5(4!NoZ5$UtHUe;)I=CU5VA#(h^4p;ybT1 zXg&@{F%XNmnv)S<*4^NKyKs^!K8J;QWhB|5ymcO|M4O{r0+v_=zE)xNl%}I};@m+E z&GL!Xs%__L1Ogx`-7qWF_REU+9R~qX1^ei%3o(*5_@N+)CL|-4jDW${(|q(z@FhPF zsGJW#u|Vcb7NN*F8|@9_!%q(7D45vEt&q`x-Q|?xoUGdpC2}?EdPDiV<@9=EDI2|* zOMtbdU}36dgv1NRPc(=Z%${Y)&5)zBk#O}-^Cg%kRvT}s;Vg(OIjoZZ9Y0EOJxQ&f z>@Yr238W}6;cwV2>tg`1oz%B$oSW~^1W#br?c_- zTC06$szxO2$(nxp``isvY^;7+xHaufVrog=@6E*4+Fi#6tOBH~wl>#-(yC6xM1wHOQ5C?2Xk~^;UOKZQmDWojr$% zy}QI8y^fRE4EHvY+b8ov7}ypCSTj#a*G^g_Nq5@b$QV#<;j3A~o)fsltz!!rA2s+I*wC-RQ0N9i;FLxTx`` zJ+AA6bfpz2*fA7j#$#!$dSdDI(qX=?%LQdrP_R|nxk`xn#7Ycaajq9k+IMa;F!D5QB6^N$gXoxv{7>2oH;{7L*DN4yRMl@MBP?n9o%l*9qraO}PhbyhgOxK>Sha z-dJc#mF$1LQ!2gvz9zr#!B|TCNm@wkG5i+m&?gM+BM`Pr)bk(O5TbnU__D9|z`)~Q z6>z4{lcZTtg=0zV)KC)L4aBTp>fl1fJ7*7tzq6|JBJiPPWH=x9oY=(d0S3R~3}JC^ z;)B3-$5L2(++aA{ z`>3MT?-8MCd3mdtsxA-t*~mM|0rJfiw$}%?!rJ?pyX`Hds=)_?aMUaA%N`~CI9%CoP_{br}f`}sN+)$^-_B9FXq z@(8h8m7(@w0da&pZ7oK=OYSxzmNX#i4;gp&A;zk=Rf$JNxQ@=;224Ht_7xBhkQ>O> zo)JM8kel}ifm2cVz~sO)I#3RM!>SC67+4N{0HtIXu(4qWVm{=ye)GQNc8GdjxR8K0 zs3jRt5>W7-b5r}T`#4SP0PWXy9@HM0d(4J60c>ULqWe?&Wa)1Od!BBs_ zL^mN!6((UlbVusk+poB15ql>*A3}+fp@bI_OFrN&=z!#3pOjcRKlKuwMOav%SkF=M zkrZ~#sc4gnKnj5`2bO%X*t7ZX&;Wrmo7MBiAZHi|++8zoQZfL%R3~ zgRT*W+fTZcFZ})+p$EwNcM|hY=o*2dC(N{$swf8i4Y z;~OF18~&EK-#4Zk^)gmxNRlQ))a=Psb+(+l>{x90(qu)wFlX-q=kiug6gl-k z3zxeCh|ig2B~|2-HCX!lFyiYZ@h51ZD3}^oh!*=xRY@(A{wXMhs>~3bk%ja=m~WP! zDgGyW`HKa?wv(z+ps`pW=~SzcEjfz9%VTO2J@TK>C|ESp_n^$P>07Gp<@IJ`gD3v% zg7)SnhwYi|_1Z$C5$sKgn&RI#Q{tRRQf40FPZ1_u8(paT7Tb37?SFEcw9+RN9>lp? zbe2L_#VrWKUEanSk4aDCfdi)8xshxw^jiZ9bqa0|{5|tKn#~S2$4HNsb#{6YY;ES6 zZ_|kbc8T=)e7t>J8Ru~&V05+Hi(4z5wut)m9mHgdXz z0$3Lcu$da08=c;bv@00~d8Q4Sx{Xl>KwWnHm6p^P9ty?*WEy|e^{#N?Wz6AlTeFyII(=s{gk6$pn*+`^ zNkALyCKP2nC(ufxTIkfEot-qw7!E*BFL5SJlOksZRB3arRilo0ls8u(DqscsFC^Z7 z75)s zkoxYwkZ~o+_vdX6u&cWWI;mI_vq&;587`=+{tcozFeJ4iOkQSLu#$|hJdc$!(#L|E z@GwzuuSq)wfp_+;qnyQ2=rv}ZEqI=2tYz#XI&#?jo5%-WZonuz!Kyd)Xv|RXRT8`$ z@Y0N8J2lE!Z*^y|J)$g(c81@~t6diQoN@y2)})Uu@LWj z@@?Nb*9uBQYj8dhKXr=y=LB9cWV72`PuS&V5?#0)WoIXuNtDXRCl7h$X3F|3odc_v z4}}|qlZ^luKp4PFEcpTpd8{3p^iJa?nY!d@Huuh+q2zU7@eqHFEIbO38n5OIt2euU z;@F4iwMiN_jmR!p{T~vKCUUjEt5`dto@^PL#^ z==pN`W#e)QE-ZpPTW8g<;|tYz$>QGyO_`VdFsm$xZ&aV@bt@&0tz>QwNQ87T!pRKD zI;WDMmw%*njH<9$PimFz3owIU`px4ft4r&OBpMq&|*6z>ao zsY|JI8DF)zhcDDbr8BM}#|Rw5ZP@?uK4ARw;fQ;gz9&fYi6-Br-Yt!^uCD-2hkt7` z*?gXGx4h4}*UR*2JG{b68DnMA=#@x*zH>GvXdCuH>eJY%iv-_7X*uQZ_bxD4bYOk` z)rmMBu6OU7=_ATt8Oa!T#>7<}Sp}B$_I?D(`YP0F_e$icn;c*CWb}$G#}9mz9RU8* zXS6)5+#s7Epz9*Tj#_P1J%32WHK=c1cwO052D$HBfu+3uwZ?y#pK0q0DxyGLq{lI@zFAvpC~>i{nH+`JrLWQP-yWsI z-`vz`SI%UQ2XPfPKXe~%M^Fo$3&nN>(-`=H`cm$_k!5Mk9EV!luNvxgL!+WQbUfA?uSVp)Jc&wFaE0GTi>z=x+XY4GIcfTe~A53FF zI&(xO{M5vUGB-Wp6zOogwKmbo614kGhT+%XZ??&E zRrwCVZ-S`_|9a8}b6WQ#hvYu+Vc|yAbvm%Vva%Nw(uLxF@+fhT!3RC?Y+5*hjkW-S;YDM@8OC~ zAJh%24p?tIxD#t;V`bYq3tj6VG!tQ%r67NF*s^k?i-aj z1FRN-j@4K=dW7ZLpn;N5FQu;cTd)WCN74WCm#$0BtK`5|KF%XU!-?FVsds}Qp?YFw zbjO)>?T(c}Idc#hW!cFdj}}q81IyFZ9>MsOBTn>H*v#+iISd2VWxUwq18o?dTiMa{ z7udmKs18WgWX(|SFwZ>7<$-7B_clRismuO&%rtmlGD&+nKO7k{_Gjk%&8+`-UB8Y=c zP^#`_i}A#vSnzA@%@J|v6-JgWXHGTenAji?MW-Ytf{0i)9$`ipibyGg5&_htb0yN% zBC7d6jxP5LE15Ge3-p1kETd7-I{<6+65&4QZpSf>EZnhoVpiBf#tZ)p3e4V>yX{(I zOG_|l@T4`DO>DJpgZvXf>?O!me9=mN`iZyKX`|x;&7pqg&z>P1;{KBgpNyO^_A;>o zz#_?bNgs$`+vn|5{!Q z;6j2xtW|yV*?0o8t<#E^$UKkTWfAB|sb*h&Zo$gbXx&(|p$UE%b+oG3N?bAlzV_ux zWSPS2i9Pmy7zxlR@N$?&u?nzVH%z5SK}GdZ0~UYEQ>6dbZrYvZb#R8UU9h(bie6rJ z7h?T!?&$krxH`u#vW|Lvlh4HNGPryZS2m@wVm+C4t|mggy*~1GNE2D+dDQY(UT1Sd zwSrNsTJ~gkVm-M7o{aofq_TZontGzNQ`CFK;PNWacjZe{`cr$43rWsF#{N30aXzec zui4`BI@ANReB7OgGc9sG78OAr{rs~*6jAxIa0>`mVlXLs0G4IC)a#818U22ja6W#y zu3CVpSAeZofTdS}qqi^Lpz@|J=BDn9Kj9!#}KEt4lOu2+9i1|GRV;a7$J7KBNU?77r-lvG z%g9r$0!N{>-7p&9Pce(MtR;A5*7i$+4(Qf{+PR8wOPtgE()90)>vr(9`4{CLkqs`R zR`$WT)x4)U#goDWE1$fE-Q4DWe_&%sP7yq9qo2dN;k{^$xTJr6_X9x*H)x^wvZ_c+ zbG0HmJW|nuCWK`pv0}2SfT;MqK>s@VKhwkk#@9A)$EiP2B?_www&~f$kM!eN`3IBC z-E?!C+jX2@kIW@?Y-LtlS;t4Qei3c7hlC|Gg5GAq#`ANEA#%cfY7|yt<>?%rYIB&> z$9ilpfEt6IL4#w$ve7Ugg!b+!hdQXY&S3EEgQelN68CI%_~f#wS&257DQO_-$&Z30 z#WT(t`Ugl*Ugyk}%f|IlOS>YL!>y5xpObDJNL#~z^z{w~_kPu+`4^|C3BRItzZYYu zRG+X4m9l~HKv5o8nq+kzCUs>lFJ`t(Sz`mxw!oU39&b%&z@Dub#j#&A?kbs1)wSzv zD9Y&MCE{2*SGP9fqOM8g{uK=r#iWestc@u{Ted)a!=-(Ug7D=(t?iV1O_nFa`C5C% z$&4FA^bg-Xx99?F_l&#CF}o~9HB;tt-yu~Os%u=-_>h-@htD|+hfBfb%YU6`Jf~01 zO|kfhL}(&+*CjQz5z{+SxSFl)_Vi1{FLJvwr|&sLYku1AIT6`fz3hz7C&mW`+Y)%8 z&kH@S+w?bkeWX5%Fm6ZNyvq%fn;wFj-sxSUe)M=sxIhSKrxKT)D8?|wAa}Fw2%_)Q z>@N04O`rjvRE$P(s+I^VZloH-fd?vd*i6<^zb&u7|MtopzylZCPu0ayHbE29G`Qt3h+! zuptPkG0rQ3($$b5c3jAGJ`&YX+)NgQn8jFd3l8P=T#Q;QlWoX< zv5N7z14w?=tmXFPUAY4xW91qeGfFg>P2SNrtV0En)l3PK&WV$7rV{jxpnop)vrQ3G zQd6B*DWYPIY+{dOrqs56YK8{49zE>Wo*M`sqK!>fdc^OYkc@U!&pNZShVl-$IKl4dpOD+Vh*68W*v$%4#rOd=8xt z3eQStA+6jixz;q8ZLw?%;bEBzwU3WK0n0N>oaIx{$VFr;A>c=d;3Z_cfrk!p#XpDb z?!&)wd$e5+RR~@En}Z2Ii^3!D?Xpk(Rm|`ytsAD)Su&6A94c|TB9+C^pyATDVZ39{ z*r&|YXJ>uPB9~frx^ioB2;DKf)NSF^cMLKHcdP` zbR8O;H@9#t>ylV%-m9nX8NGA{<3pN?<@yZeM8Xpd-4*6Xpw71V_!TqLfGhr^W zkIs7(>jlOyt=C_q+ zSg=m>pA8$_=wLvVygQ!5rm2pa*7M^KYg4T=mxWY(QdutF*rxjq(a7zqLHO0BSmPGV zxLaH|y%K}80qNmvbWMV`d5CJAa{{_}@#qv8dF&MdKaT!p>o6eyMD?*fEr;>K7<1!> zlUF#UQfuIauH`FT`*71V^%qLfxxX?z3PZl9b(lPKEoQg@bbY@o>`Z*qx+S79@sc|_#x)iDf)(qcx~+zw z)S4&Q+5F78u^ZtUt?*zk4i(!B5@p;yuY38eV1fTM1A$vRd&V zhE)l@Wx+JVo|rD|ae`*ud{Q<4Wl%BC{U<(loc93Rim=+L`W~;Dlz5S|EoM> z^8kbO3Le=$UL&tRt_Y+EM z2?YnuyAG@L4{wg0k;@sBW$HEFs7^tyajA@4>s;l{cM-F8M*6Pai*u174;RIcw+cz1 zV`jlN&63(bG0M!-sb*tU+QcCKp_h}XXmus;Z~BhqY7ZPmrb1Pjo~u)yZL>!FA`1;5 zL~q-)#jS!qg6B5U$Qg`({gnNt4Oin`C7Hg10&k!7EZ8$`D_-UrK8;Iwr&~u~+Ks_t zkM^01o>$j+nfpry<-2zY>C+7ZKmK_h8WQOR@LtG5?Lwsmrgasj`h;}Ef;17gV&Ph_ z;UY1M)m~r8m#bxjn6`fLFnmvx%wSxx7A^dXXeFXWJ*eEm3Eq>82J$8f`MgSS*{Q*r zx(fo=UkUwZ&68uvuGyE7=mCQ}K}V}*&dK!GR84op+5;nOPaMTFGxy-r7omeP`EKr^ zYOWlwWR`=bE0a&!0E^vrYUs)lUwMV;t8;V~apLZ$^M~9xBT#Yt(_77Q%h+j3Y6k6^ zCOj6;7Q2E%B*jGyhfBjwzO(eGxo#S!{KSZ>8sp>^8r$bst0H4SLSc~>oGyPqN%*DT z`0w+q@Gzf@3-*46RAnHJ(yhZ1!OQdc=-=LWhhJ;Ena_Lq^6m(`4q0O=(gyftbwvc( zDopabP+4Ygwyo4?`o5TybJK-rt9IU@TK5-t&RYH~Wf5>mO{dhWZe3a zqevdOxZLtSPs7e5tTL_nxr|vGZS2uvw2N&{!fa0mk2w|7Fk~^|H~++|zS^ffB+tw@ za+Th$nVtY^-_=pQY6p)&1QZ5yCcCgh&C+B3vxN+-lwr$H<@4d`LIrFiRc*a#7CW$$ zIeu=`FBZFDl#nrvH|VtQ&vNgftb7(|EWTWf7prxJL}5Qrb-fB9cUFybgeJesbjo4c zvZm6ATLY(7Y~gAqR(=892Fn<~`i-TQu8Hrep{g2}=v1;m&Ev znaP~$y?JkzBFgdJ{no}~U_6OI-FDygcK3eXKJxATUTM<@35fMrfkhn3#v};BGubu0 z)PbY-g!0K+^-~H=Zez116~XK~Bm&{x~@)+hc{gV;Se zGBCMZ#eRo}=o{Z_OFwJFPTqr*y%*7cCsObaFB8~5i}!fz(tqK5@df|thqd{O=y@+e z(0E`5QRiWKWd?!b9XoL06C_}6@+BH5Kf`8^nFfr=?YQDR0dYZvfRh?;tugW zuqS~1f$8$W5&V7EAODvk<}-j}=XNmbRs}-C!z-ns4eKKY2n5u$K1c`~6cUsIxhOht zuz$LLsQ+Tb87)SQv7A&)^C*15FMLdm*`@f-uUmYZD{_SFRjhiQTXXPDG+PPBu&3fg zam$@VN%5{2$Ej?2Y`uyMz-kP3B+i+t1|jO>uiqIe5WeV zxChkLL~S(;$7l8^m);<`5W$kXRZe<9G3{~I`PfH;E>CXU(2a#cT2Zq+@M8Z^jx3`T zrm{edrC1L-kj%W$<1>EfA79h$n=m7Bv56`*6cx}bN}DJO)eKMP$g~}m7<17;&uz9U zXzFa5?`{(h7p6*twL**P$&9h_Ox2Dclqh`=?Se)eNaa<1u%WTr*zD8mSxPr6EAiM& zv<&Tm#!p_4y2|no8aUo4t>1kPhZp3h$=B)yZt(nwB=Hix9oPZ6JrRgEstx9a5>Mkc z2ZSHd0O=MCcq^ohmYB*R%k>N-*dwcdQ|mK%YH50nc9L^^j|xIzhzhvuZ#0PaUQB=m z)E@qAbwJZ?bAr3ImVCBQ=c|{WAe}kXMnOcJG#=XH+=fV z2h#S}EfCc9IES-*FQLE7F}y#=6#Y6#1?p-QE; zm6m}@F6nwQUA1aTRCmwYNp=soTW62&g+K6_8S1Z4Aa@)r+yk4$T)rWu2UHOV#oq6r z(*7Zze5qEVFcpx4bR^E{J$?wOR`O5)8#XVoW7ujOGv_Go^8qNxZag8<%da|Lq%ri5 zgnmR7&c7lr_5BhauOu%@fn#?{frLZxupeN-W0cZA)U^J(pSog!$UKx6tB;tVJoFbX zUZUHF7x0kIp*Q3n8u6WT9J!wx(99v?X|MRj7B^Gerh+1&O@PIqdNRXEVc{O#PjM?1 z*yMp?B!;N4DfsWRHcD@)jmx$mwcsh!F1Ye$ zf%T3xg%D<@E6qwEDNGu$*ytJrqvJ6Mr4Uyd?{s!jDN%A>YQsTM#g`&;>{C&K3r_0B zI||=lXsKBmgQP}X^Pm-)u`wgI(;At12~3@vY)(s_R`Y}BjnW&c(AE;ms$5P&-s$sp zbl4y@NV&)W7_(N7#q=yf^-SW;Sx2wVbELF(WGq6p zIwSSuO8LNu$XEh?)w64hRX6B43gr9yi+y#4EzBWr6Np>vPjpoa-0QQDsny>JcZt4!HS5 zltOay#vsM(d6xZz#9|H+O0yk>l^-92M6L#-8BMiWohCy5K(BPj6YP-jIPuPqh8nem za;fE}pX}9vg@O}L3bq1;>yDmQVL&8n=;Hg=YP5v61QkdSbEm!qWpS_Mk0r z`38AO0{`%Il>=at!LioEaI00^Vw5gRCYE zpt=LKaImt^E!_M2uhwRptD`9=VSa&K*IU?o1ri$?dIG3Og(x{T`z6x8EMep4S(9Zc zOGP9Sq47qPq83aIzPuncGfDu;Vn$uq_Md9tla zOa;Z@y0yUX?Le;X3eQ>M?-VkL7{9=1yd$?RD_#^Xi(AZmi0dCnkFd(}7^c@|y$pq0 z;h*Zw(gQ^FXg@R7-&g{&<{`{Z(gY!DWJ0NtEZIzps0+%4^#68pI;*wcIL~ZosaVO` zM$*Rz)XIqg)|0vCDC?o1&~Ey7HNCAhpI+R5CoI2=a{b#~%DQGMb=gN^#obzja?b#4 zY==Ymk@YjK`IHN0zW%pt#mXglKFd5iUvPiPQJ7Q~nQ{=8I}efH=#p~HcECP8sa$=B zc)}#gef}ZZZla!VZ@91a|GC}K`+RKhn)XTCB`qFFmS4-mhZyH}X!K+_uX# z!EEgnHY8bQs)nD_(o6=CC_GEO{FW=*sN@Z#t>^-(JsEf_x?#^Eg+h>O3~Mt0=fE!E z^@rXv;q4R5cD|hvdVNrSv4Bnmh7lHkq)443x%WDKpCRvn&(5Fo0Cg}3^CHO9X;MT6 zSj*2wYB^fXJOm!EaFz2KRF)@(QPfhESO=o*f>0@76pKp1e82AiHPk)Yc{*Zb=RWw% zP1Yxc(=JT~?Q=r@)C0&bbIG_9Y1ovQvYk?EEwXATsgQAsU14;Cpg&eQ%}rc>Cogn< zM=vCUm*Cp^zm_Dt#8%a8R;IM2 zgsSz__^8dOcUEv&rn@U;Q$OC>!?uU>9v3)GqWC#iU84xFX?Emys+u9-$h0xhU0wsxPg~d0^zW zkhyK5tZJi7dRqPwjwXvK3o5OlwV3ARQcDT>#`76ZWs%~)U1(2pk#Wt1XglW?8=v9Y zn3MF(2*&N)PBXkp_kH7PXM*H#^#<{K25-+0%Gz}S-oOUK0x)WpQEQ!Mm$fIc{G0R~ zi5nAAEC7~3&*SZi(Q_5gB+fcoL+U6hplTf7F6SoB;{7kezOhFWC``6( z+qP{_+qP|6)3$B0fqvjZram>~$$iaQAg(7Z;N)@5yl~FI z2lzuFIKEVxPo%NMy37i}as@ogt~~HrhRs-OzZ+`H%26)___rFu4crG?zItM?u#I|+ z+M}%`fI`sA*-_&Mucev?l2>!}MGHmd5a>NLr#{)gt5dD-g9aQw9@FrRWhBC{$xQde zLnQ~3^7PJU(XM<$>lWIY8_Vepti$YyPSq+)U0?hy&t<+~VRd&9WbTD|{no`#2b&gv zgYBXk7sGviN}{W@bACu_V#*1>^DO(O|Jx5?lr6TVN6(FrZ&tGHq9NfL1p#kv0GOh; zi|Fsaqo&Gw7$rOLx!W~pEl5kysEHXF8Reyu2c40Op&@ZqPY23a=a)rIhOiZJ)z*Yp zoFKrsN8IY~Z3T+h)Ure2y%^WtFHM`5CoBtv^)+(489LWG@y<^F?1QjtMBhNH;BTnb zb>>4L((b4#JUu^#w7W3%3dLPyhHl}iAwGIXq{Ex)>-_%a`zD|!o|E3KP*24R;Z`Nd ziWg=FR?7^9q{51^ud!9h)jnO*=Yb9X&eHaf@#yIjfDP~bzC!HFN6i)*8^9CIK4ODJ zwfMtvj+Y0CRefj|<(N0+he8s$@z^byHy89SiJ$km5t%f-EWI?1Ul75oONY&DY1IKk zQ>1JuSak@~j^`lU2XcxRQm69g^f!pjZjc`WBt3E>NT1^&di#RXD@BZhJ-x}O&OJ?a znVv7IrOXJY;dS<(UYJ-IU+~{Bs15;5zU1ehG<+6oqPd+Yuc5o-QPG1xI_sDxhDlW# zPBJPxbLjFaN@s8yhUkLkvqNjPZlBizqZh_?A_-yCKY9V47$`e&{p1;COr3Q+nW1P7 zZ}?i0sQS6Y38AEz#2A)0Il-Alj}>drNFQ#VyXq{`J3(8gHM*y~?v0^cAeZf*ru$TC z#j{fdu8U5qdSO{^T3j45uluuu-abbq2hpV7Tc#3#bgP`b&$>q{JG*g(!y-_2YeWFqZL|nGYGdA zuehC*TNyLGx_GWVD2}6Iy{QdspMq0ZIX;M`vrcC5eA_D|1-QYaehmIMQ)amjvmk^6 z0&2nm0wVpt)RF)9hVw5YXEWFTBFeFv(4M#>=>8@(%}L$8#X=p$rGM`S0i(g>2a zrwL9Gk+~UUiXQYe$0Lm;HO7PeEw_4zR`G4AN%kShG|`Z%X>oQp}#)&w^Jm zD-b^iclETnVS^`g_mdvZzhAf4*&BR+H(BR^`$w*{8J~v6`0DWlZp55YyvHQ@%aUj}zD2*$Iwh>TKlNHTFxgv08A`3l-P3zg}eQ$EF=Lg#ZEn_^X zCU8Oz@IM6T!$w)+>`tEEPF+Z$oQvJZ5B%A&I##qrH zzoMzzK)I-=4l3x!{z zd*XKM1(J1Z0S6DOI08T@hSAyyeX283$87`u_48&<`@-mqPok?2(4e~$$Z0fKhprUh z1I*mov(GDdySmU?p)?HrTe0>o&L7*+XCeR%qxDs|(9=vQFaV2F+JqqDOnRktp)u9#t*&BY{cVhI({SRE2j`bf<|ArT`{BbyDL&>Bi7?nv5*TzK2!!7MG< z)p0L9yIWOF+O3lM8L;25TUCbG4l3)Z)p|x-P;+XwU4uN1`0c1S2^%XgTLV1IdUJV~ zV7CPzoc;A6w?Q7Xj-1BpQf1op=e9Av{Ce-XC6G&gW)Zxa;-V0xlfv zv;AA$csB;+L`zcoa&s)`VBt*mXd>_F;RNJW?-)x)^xf9gRdsELff5mtN^I+>>YP9h z`Oj@Fa_cg-iY!o1=mGR-dWrVjycFv@C`Pp2@E8GfjT4p6WDqiEJ(HP7ok=u$r~S&- zlh=`_-Hozbb8Eqs5s@fK*|i920VUIoQaE<|Q7&4B%{nz(dOn~h(XwD|!dpSLnt z+RFt?>B2+YpZll!wdsYP>jLOoTuQYiy@-J@?3V+Zh=Ml>Ej@)jtM>{pu91f#y3g}6 zI#i~6NEoJS&JnK=Auv{G;yx%nYlo(Dire!aWlJmJWI76O702q2-hx$XS4*_B+PpFA>7MSI>k1WO#N@vZt&w7E@e=Jg4@(2!(Y!}^@c-S zN8tEYj(DhqtOsF=d}hJs#ziH&DOWge@%Ab z!bCK5M@zn-PfW#!+|`vVy89kOUE(E`9)x`V%m+YV4E7`ek~uD6svO zIXa+6oGNtc&(e@J4TT>*k0R#qt(-soZa}%3--IA8zE_h@F)_>yp&2?{;cABq&E(`$ z0x8nxypVKJepem$T6nvO&pRc@+|btvoC4>=`av$}f&QpVN+g7)&QKbz+(29E9#*@a z#14V$>W?3bVYhp>*c1X5sim;nOSTLmbvl*}AONh6nPQ8IX(fSpN!}$EJvQ59o}x~k za;061$zd&cb)1+Enf*HF?$uFm-H8+qx$9pz;X@Kt|Gx9ZL(SAcHxr}-wsvlKg8b~I zKY8aIGQnH!G+ML1_W6{cmx%#W@3Sn9p=_Cj*NO)df}re4t`IzACr-H#fU~FQcijlDfjVIxl1XrFhqt+Jhs>- z)vHMDo(#{9^zu$UVooxj(3U%LxTSh*c;RwyTYSJeaVW&aiix~(jPd~|&X7`b1atP9 zLET|ASBJ-1LYmY8>QGQR3Ly7g&lMwie$9_lv82P8Eti#)*B8~n2U99yTK`=Bm#*%p9*ex!eau5>;Q>X$r4pj-6GR~mAS zd%ZqM&vrr2#yA+Z4!kr83{CkiTD6QLKYkk@-1w|>!XI6N02Cp*5@l;O^{TQsC&!|J zmx+)OEYJGvY3&-+xb*cni8lf!XM9r4b+6kC5(+~T~|ej)4Njvrk*ekt14LUnTLpp=*+NN3Oi8kcP@ zi0ucpV%$uy){@(xD|XgRGc#V8aj!WBWN|XQ*gKlS%DNvE0*r!xLg0_a>R4t8XJu9A zDCPcTa+uyr5Z|eopvf`{dp$KLMI(v9Q0Lu_1K8f_={Lax6w?qL| zdaB_Hm$~;dS8px*VLLP-K`?N1fK3^7mot@Xu*tcV=D7MDJ|78@TI5hm1QENbp@&aB zF}x2&gJ2STRx&NaNmTiKPfWNml8YbXPi@Pi0Q{QV{=XiyUoH|q^%1^~(nor%By)d$ z-5KqdChFwBr;=B|Py*E~84Vm~;7pu@a%C$ObcIlA9!+3wLadg#kL4WSO243Yp z_$=-gu>a(TkPdoAYZfQQR90)%DWmJ5Gj|uO4;m3YdJ&D8r{G{pf=d^M^pu;|HIa7D zN;j=(y$SvwelhY-i910u%p-vX0`mUXyi5K6;uj9K4$h)hMz#(X|HCqT(T>so{=Q=N zkxh~tx5XMv1a)(fL^CVK6_pf{1QSk1A#Ry6^;oCdxGLer1!*m99oby6Zh_pSF}Cy8 zpdO=P( z0zw>IpE{Z$u$^;mYme*>ozThbo(cAAUx}5ucSlUz(tJfg+>P{Tc(bRtHM;L1*p7RZ zY99*jXjd8ht3|IL$x1jVdeeDAsG)q*K5!l5G2plFZmO@qvEQ zJLzv=azmC}*YpN|(mV05^m@8ah;QT+> z(1NLVtiU=ntF-l&!HH4q=ZT`e^UK-O`>rZGCLSBTjru}ftwq?TN@ivFq@(UG>V+KaMYJF2>?*H zrl3~bU~lDu3f3C24^Hb~J#7b(^tyyW;IO1B@_kGce<`79yo8JMc1khx<)lmr zWz-CCs4kzG0a3o4O!<*y=0G9CA$hy>)s*?9hO+9$(%6U0GPDd{c2^6JbU1uJ7XMI# zDNlsCsrh+Y(x8f%y+3Uz{*;HWZ^1Ew2jJMh2lLIoz<;OSabo`-6Eyuo{hM@8@_jHz?$<{U8FQDiVU4LbLjAOR82Jg$ z)Q%-C(nGrFVlo&g;%II%7+hj>4x{wYU~Whk`IDS!sX=mBPTsAM@EQURq4DO=V+br7$8!jQ)L5*kr=rZ+c{71Lt`TcntN8MyG4!*nZoiKK6+@Dk z(DFo49;#)3Q={ck0cLb*qa{lE_`yFP5k~rb)D-rz^GT3w)(CoIdr~r@lb`l547Ni* zE;zUjqC)0`|0(heDgM4~?gaT$7}qq|Te)wGUr0+#mD=eA^+*p6sw1%8-6SU%lfk)t0n6xA-&f0gVx|HJ*+v2 zg@aa+Q(>;A87=M-H4*KZ3`|-@PM5O9b+U+EHR>bFd{3$!zewb&9Dx-`8r#FkT#|pt zu#Zo=F=%E70v{M>h0f?{p`%c+=^}Q$H2ozf=D3Q4N6)-o4n3VOz<8xl2WMI>7(erl zZFqgncF1`-iU6!Z$5%j(A|6uqTZj;g$^HB^Jsf@VVrTHYqFu%WyB!4IHKV#glW!-L zm)Z1UUpI;maSMhgD;YnxVH=D+&trkUnGOFMZbS+(AY{d4GjUFUkYj3ye|`#6nsblx z5oVjxb=iBfnstrC9Sce~%eE|4UUjb!p4@U-W~>PdAihH8Ay4y327_33c1)F87JLcq z`qcbT_narvVq4B@;u7V<_MCGh-kb3jJAK0VmW4eh@HxN0$WdOMwNEfd-$-L=?%=l- zATH4y;;>L#lf4$!wY65Qvn7_LxgIlC@v$Fb^z_I)t8c1mszn%)O)=xefx9rkn)`ZE zII%Uf{Nzu7kCTI7<60%hkG+$#_%GeA)%}`*$I~~~>Rd9P+!4Eirvo()N7@;*=zHJQ zKzgmnoU71=Z<}46Xh?Pr&1xU3DrMHfn1)HBU?(dTgp!5Bodi}Nx8Zy(3=bB!hj(ID zJWCMpy`e?75B7kX!cyaOYsJ4OW3KZfES?zE6d4n*tmU3kv=LcK%x3vShC}c7T86J4 zwhB6Lhwz-$(Pm0Mudi{&d~LBO5*B%D?K9mhAaviXD!qlu4DouG%5Jnk0RW@F23Ji1 zsyg>sbN>M04F5U1-uwuYVq6#1d!pAGLnvZSnlx(i)qRFHbBP;xi8!7h@Z*k`UY~%Z zs&uf_ObGnO!h8NFi1Cg#u%2p4=RbkxOotQm=diw#D)nDYWEf%t%S8SgKpfH>G6L?enH6towS1YL~%me z)`3rV(UAcfR}j@^TMT2cZ@~dokFC^xhH7@Z937DMBQj7}eWLjxg@Y?&`|K0B9h@^DAD zU@D!F6F7CiOR*(7F+oS`_gzCf#gY28>%;115P=6PM`8;8X`C?Do zs&A&+nxZ${te$RL($IDW&6EL(G>&+BQUeHIKX=G@YmFT=9qNZ3Z=fVUFn67LP3m1n z`$ljXTj6|qO&%k4h6WtB;0vA@9&4w+*8?agmrdRA?_qQXJS2P@&^M}97y#ee?2h^) z1!Xz?hlXY{##Cps$uBeA%<}7apI^s6EHv@j@>*_{$E_3`c;;cSwNx;eIbve_Nfz)^ zV&ipGuSWMJ2&xt6y^0WZ(C=!0BMX%4s1yvRklSqsU`j=l5Dq9->Qw1qXHYNr04nfs zJzYL1?rtbA@5%KV+C!R7csi{#RV)UTd||k}um9c;uevRTJW7F@{1{nsJE2DPubuQg zzhgxB<7=5v(iy9!D+Hz#)0MjcAY?#E7g1{PQtKDVcPUv$ztvS!e$b`$m=;)wUU$@u zilb@|$T^&}*0#(6wQIh3X~+}yGW4g_aumrN4Rh2pRVGGaHx=AppyGm!oPl7pUm}AH z;`^nZTNaV8EWYF^yG-t<1Tbb#cl%@UO}&9INx#dk24+I9YO8lf{*sSxgzpC=3$$(K z3r&e{N$bqluAgql$9+tON-UU2K`L&pdkzxRP|n!!N{HvrNx`K4Et4Oy-|J06+oBd4 zU$9icZx!d6t5dd1TkhL_I?`34@YW)jk%*}5Z@^}Hk0J{(vOQbYg_#|F51lI`}6 zstSiC;ye6CRr|cl4r+bxZ0Y4$Mg?@iogQ zkL#1R#oM;cc5L47{3*D>C3qt+g$rooY9!c~=BFD~Ijv6a=3ndcO`BG@w?^o#&_F!M z6J%t`%m;9;6~ZhdRKgpc)2Y2-&k7&^9#GT=jY!Y?DkGwyrpMxjtg&wTP@`Vp$ z$Nez=*n?nj7T>r~?FJn^O7;dEssEE#j(S5F+K4;wC(@Z;@yB-}8S=%`>k8T-M|#G0 zLXYgp48)uf%eKQ;za?+Pob$)r`>uhT{oyBq<#qDI`3-eK2G@2{vctDI=by8w^aBol zNg@PjLk51f1x&O7e<6MF#~d*w-4^U2AAKzF1Z*oIx@;yfY~}=^p|8J0y&d*G|E69d$S}g)g!-pXe1WseEC-Jmbb<{2OnA73&@L z$!FE-voxGPJaG=2H2>EP^^t$*(c0NxT$Q`+3U7jcx7&`E^p7TBBz_k}GZ4Q)KhkHH zkn+3BT6*C{^g(6qv6_D3aaIUa?}IG*E%YEk_FJS8#_=b{-+{}{!F#<6>%ErwfpNSi zblV`YJ9gz8{`+s-w&1iEJKN6^-Er{~LelTTwZ4Yge}i8{@gK|yA4^Zd4n(0JR4V_7 z1EP;y(Ff!U`2_}8aqfXi`Sk?UaU*e~a%2!1X|BlG!DD3meWyr1xW!zOy`eD_!TW}y zesUPS!D!n=8MI;-=p^N*Ctx9Lp;!?X9GADqa_|c3Nu_F`S`;^vM1V-2lVt@&k`mfj zu^WgsB?uK7AS0%eb-oT_1P86WKn6&P9;b{XP#b+|;otsdg&J@Rjk)*m1_+K?PPD(9 zyzqxB_tQVKKS9!C;+yaprwOH5UWRtjZri1ALzx^dG`$;F;7m5TKYsi8`J?Tb}p)}cHZGk{v;@m z5pLRzWmhdn2}f~@dY{XfmPpMKpG(_Wn1lUUM+p*?Ykee`&`RDtNiI>SOFbJi*bBbv zfw*fO6EgDq;$I%(B058nZ!w_@wRx5~Ou0K;X8abLgO(Ai3_-+M^BSyZPOg*X zsqVH0*jHB2t-;P_+#kqnl!)9TzOzwK>ywsLjb^VLfaOve)4| zt=MB#A|@VUJ9l3Y4ulRcm4frY@pJbLr$vG@6x5rM9;Ku-g<@8Ov)8hr>SA?~?XUFl zH_&fN_LHq~a5?3%gO*k`>n(IjNCiyXQr68|RT2~$gSJsbib*$r(N%j44-SZ~qd^i$ zSVqL@A6OxpTG3*Nz`8p`x+;r5^z@WDy`vd#57G~J0G7;T09pc}qD#?XVQ`V9&q}|5 zan#Eu2t^|}1lOS}rqWHEdDYl!OU~O8mbFN_$u}2p+r;f)N7D;L2FYdlwu+Q~F&zRm zOMy6t4u-WaII?yXG@-&qgSRojP?{#=rfjua9j3&}3%gRo{p!ZJjVdWi!`+gOknlj! zNdjWmeCS}>#3nzGt;Cl8uw_owlalWNGc;ntkY^L)4H~70pJV2;IpxC5(oAvMjkSUoJx@X94V=h^O)bsHA(D;*NK`hV z4XCo7?kVzJnbHknEY2aRuf517(L=sa7ZGa-Ve>v~Ua%7uF@}UjlZz@cYT5KQ-uLX5 zdq_NRasJNlf2dR#;~1pIBN>b0Qc)M}vcxIZ6y1(+j#y2DYQx4D7cMT1nV^7fq;C(^ zjNK-G@Cm?Mk+HIhexaC_GP{-Z;KP%l5|Rf>N;rzR-rvcz#3;rHYf4TdW#zB>muO^H zXvt|?Q61u5Od*u^QtdUx)LP=iwFO**#eBr0mv0*aj3};5VtGrPEf*WgKm51@jbnQr zn$kNNkDNBpUb->u3}6cs9n);5?Ifj3&0O}fFBYMRP$tc{>+=vqXRbi*a?>f75ETeus*RQ#W zsqk(i#Ju5v*=SgwB30-p#3Qn4LdlrgH1?x(q>HO5!e?2jk{S-$ zae~s3p=&3_iyoplaa3#MTyvGMkl}6r<(w7hAjd#GRSGphSe~2lE4klg`JM8;+_g8? z~(R1rN4;T#;cRgkvB zzn?%I(9G4Kgqb}&(%CMmTNTaWpFXR@d}xlmEY@}Q$M(vx01mz=Isxj^81}>A*kE~t z<0pZEWL%G;^|DCC^}HziuO6n7>p3w@#FUsUrjxLV53_stdS$fUStIUHEmHn?gS^YC zSfZzbg<~6o0JT7AG;O5l0S%c(^m}K!xGo_2@{jvkUY;Jx)>;VTZ7h~P$jci|ZrxC4 z9VX}5!IID+>laFSyAozTI_EKwNEs0D;ALk!w#^$}eMjhCS@hf^;l%-LfH#0Bz(8cj zPSUFdQ8Ign?hV8x@uT^;Yp-}3{r`zQu| z^t$r{55tb7rfKV>$1@z|%BO3xGC#d9eazoP;KV;rVSiE0zgTC++wLP6c99$|4j2#U za|sKuNB*G-c(5Oe2;aXz&;~wZubg5NEWh~w-LY35B3i9#!r5`-W&W> z%*LC9r8BSj7|y*4eY6bDRa&EEhog<5DjsUKj=etUApp^%wA(HD@Y2TxV*$B)6=*#?L#`6h14^Ekip` z#FRywHrcZE)}o~NEy=a55ZKdPM5a%cT3G&Y!%SGt)Y+IMq}ZT$s!#ltzZr-Sk}8M2 z8@4%_ zH(I}Uhv`})o?Y~aDI5#L9IYO0O4HdbBGh8F>6+UYwGFr26SkdMFs~Kx%6l~`oA79R zjaTkoTf1Uq3TWlyNJvy@wn(~Qu zd}96RdJRJM*aF_U3bc)39qnqz{5N`buS8Ubq$R!;ZKTcBE_1cQW-7K5UA3J3{HyNE zjw+qk)vk1*UV1I7WrgRrM3>^)s!+k+!s>-K>~XW3=8SjsGpMp@$~=q?yfJ^>Bd20F zl;{o-io9L%tRM(ac5}2__6!9V0B=@^Dy^L`x&sUsWUHpdDm${grQo09Ltl>F!?d|V za9sUiLMkrWE(b4}MrtM6hzyONOBpw8L2jY%El@^ik2q%R=)Kevwc~Wo)*0NO>DY!` z4ps8WGIO1tD3Z04Q7qm0a2PHYf-rPMj13%{i+`VXHu)0A^)KZ*x2TXEx7_e}b1@Cw zA{n+%&TH7;&D18owMD{XVkniza$rP%x4qIx1vFvuBHa(ehRu89P&{QEo8^k%ZlYek z+!-24g$UC{iOig<7htl0fcJgudW0)6epm=G}9>*QM!S(ZQAM3yh-tRjv?%ZLS2 zD$kTWQAmhT7h`1Ku?3F|X3qC8EG+mijvQHs=q@f&iIh>I$b}|F)W0rXu<2dx2@yxi zW+^LX@sw)vnMS4;QszmZ>O66pPflCTvQPOMb7CDsVtS z%O<+KW=7v6ppHPgX7`598@;mNu!?Au#C5Az+FxLCvkjgmj znE#Gbb>R>2zbK{9$lm7aQ8^SF?4&}gR*&!jUepLd0Rq(CXn(0P7&3rF5i_Y`QN0os zP9mHOrlc=!X}@P|225_^;92H+*^6duQpM`0ds7QC|8+) zD2*%Y-k=m|ZEVrdGngA?WVnOF2e|!!UE87I>jgtPGWO^gnTefs5mERFY7|!{5caJi z<=|Gga1WBrs28(yoLMr2Z+qC$t!Win<^^$?F6>m%BCdhh7uPV4@iQ$g1bFK53F`u$ zkzVv)9qyFZrBbgK+d!3>M=}q~NJtRnrRL*p3~pIL_aP)c(t4BGNR_sZ@uIpm+q9+o zwbwV$VT;g|zYGlLA%YE%Hf`ZYJ13lx;2#do$@9Urm^))0%I7LBe8#%{|fvbBT0Ta*rPmr#Nqw*O< zVi}rT-Y~H78{Is@bz4SqJCUhadxCNfa=d`jqsUFegve>9aQURtB-6KLuhN_x0^iEu z9ID=EgQ9fc<*!rV4AVpgM48ks(oz^y$wBH#Q9^r}w%~hmBp8IZK1OU-IXLP_X6%pg z*vo22i|dgZWn)Y;PP?0N4#a^hb+=aQYfHgFAJbdeIq3|#>q~3QhAu{`@Hbjhqcv(& zl4?ufd+iXhR+A~6SW1lE%}BDd9>5=YaFbQUAtHYLo?MsNOHN)MF+ z1;{39V=h46pKTc0-&^hNqDM&r5Ty&%77_6d>qWt9c2}9rgtd;Q+QDXPw9xfdgjyyU z)wo|%)U07JGZ2-86UU-AWI05ru~Lb!UCY^W$43y&fP_i3*(PR{ct^0N3|8uiIm)@T zFEE9}4|r;ZCXB(`xMS4V>!wE}fF=`O$%(GHgYgbU7x6|y;tB|8f80a38&6izN@PR? znpKw49!RbgD^Z`@QVJykAs9hy&}klhIdUWm&l%n%Z`vy;4$_HyF2)OS>R z$?4b}7L`WpY~oo%I_+jGwB;-@f5A!I?t zX3;9^r+kJKm*ebU+N8%zNXD{8Fl%jex7J0h+w3+DMoXu(Q!cYNMV(VOWlf_GP3LQ} zL5?MPWf}cXpn`A2g{)c|)Mpfx5e`S^npdZeQ)N6P<@O_ooh+kA3r$NGll1i+;9fOP zMz&)95}!Cs>m@V>hY90&C$YdPx4DJmXe?TWE^BeeIS z5~K<7gmk?46gq{Zy^vh1=C~Kg2tIB&RT6T-Aysmf8%-RxJF<~2@FA3{`s8fO7of$< z@LLtJ^O&T;p_1$Qx|L<{h)mKp`cxQxl=h z$nA~t5lazPk$?(&iRIiFc1|!d^-M)_&zfn?mJ+L%&24Yd_nhb!5s^Pj>Cckz6OOO!+L@C9J>JS6_(W^d$Ip&8<&9 zkab$|5E<|W^75GS3S_6ASqvppVU8DtFyCol0(}Q54%ua9)A5(7-NVl zeLFFH0GX9k8H&;=#a_Z@!qW-7#ma+xYMXc7aWzK9bs4h%?qYp9#KTH3Pn9YD z##sfNo~;xnemKXzLNScu(<<=6yCNYtTH`U6BRJ zAhqNLTca3s<}ewjZs4(Ep!Xh0KhBC<@#meAUd%_CqgUl;JC*0`70!x_t(CUm@FULY zw((nawZ2%1v%|4t3O~}ALwtx*y|;8u*?GdzS*T@oGHk&~Tj09#{K$Z=d@Eob!6s5C zuwj$wjX7P!DUGWfkH-oSrr5BpGhQC1{_Pe87L9)H{s)kjK+$SO81gb(s*^Z=xMcUVT>B;x)wp*jp@p*?0 zWR9wo_vC8679d8Kqth*g##DI%mxp#AkQGW#2MqEt^Xd~Uz`CMOidrq4r-TB&p-Fgx z3FKZSP>$Fz5IHRi^*R4kNTSUfL$3~eoyMaAj{T4CG~pe_k?0}JPh?{4P(Ek??5z)4 zz{d0hmmg(3wfF6EYSS1RJc*n4DeQ+-W7UR9_haNyE%WYcEg#c~5Klp*?4ia30IdL8 z6ak2aNOtrZoN*b2;+JCICXOS*5*aukfXB|brK5O7io+kF_xM_>Fst z^a}fv#&h@*eejyWEIH{aM!i02P0SN=A;py4Wzz0*JyhIi66AWJ+xwuRA6`nGcS@jk z`lc+itW{b3ja?^6j<$(%!?lmX9H<<{G|6_9Uvv2k*u6|C&z{Mm<)wmlViv>_hnp2P zwMj?iE#7RC8pF(;vM-amJAof?Hk-fYy?{=%KVZ$K00FCRPF(yOc(UUgUm4R{Gt zfYc;y#K8W+R_XRtddvM+$!&mgI7 zqJl!>$PdUQa*6Yn|H;F-pDboK9345HSuz46T=U;HUrSJH9d zLC)Ei)dzt15qmP=Bb~)zmc!Sfq_0`*(QDV2NA7-3+ZK~(6ukU%5qr%<(NhbeD^2-_ z!oO*A=`1IX8eiN+dhKx zs)YlIzl+W_$s^ZIglt9yS6{1+nxUmtL*sXzDxiifT?aQY3B1bR2eZOc1yD>M92>g& zSzKHcRxc91zhk6oPD9q7ueFGiX|&8e85(`QB0aoZ`p`l&oNY`#8OE8qT0VUM4RksR zi%5wj+Tj>4Lz23yP!R43FYrF^+Sw7Z<)6}LpMo~S%|2#u0bPw^+1z-e(_20+oQUka zH$+*d zO4g0vVYBE+>;XArQ1wdYTM;6)XBgExNr~7D?CH5(22^nxMd5ilYp9-vby30Ic*5Ez z`H6t!)+aSs|2{UQ4&ov?1=p8VmfpJhc%( zt&^HmtPDh%>gr_^uo-bln8x54mXI&?UZ;sxtu$FUoMMK_0x5M=$Dbik74YCA&UDd9 zT>SFTwOql&ZjJZc<&W581G$I}h5QYnl}S-43dNbSxPw6qG0_I1WEb$umM#=V3@C9H#1!|bOq~Nu!SdyF6C~M4;!;r;ba%dqFG*lR`3=N?CVtBsXITZJ~vY zKYWug6Fq(1t$)|6GK*FdmEoSm&2|2IDn&7t2UPcmyv41GiOUg0Sj*wz`XiWP>IiqZ zFL|5OBPt$W77cB#Jn)NP$-?+_2^ly%PO_`rfz%4o)x(VnxGyua_Ol+&#d-=B-Rcf zbl~wO^?qYTUrgG+tCh#{ap)0o&Iy)$Rg3#A&o#s&eo9IW1ogYOk7(k?{`oZ7pn7kP z;?wnaV1_^8hT6lK-%&Q9nE|=*^H#nggL7UB29&k7jMomcfMHKV%9 z4V@dG*pzz-&p7*@gPlquPm^#=76MHYUqZ20z>W#W?^r=gb{Bxx9&vH%>wO*IWT8q+ zgKaP0^p!wasMqMZGnfmU<0Y#(7=al|*PHmyJmex`h?M&t9?7xvhnQ`l_eC>V*WNbv zHI`3+JgNb}BgN|2Um}U0OHKBbH?F>y?DNAs=x?lIu1l8Evz+Qm$I-;VxrUeA@zJ|6z(VLBtI%{xD7nRJ?1Wglf4FCQ&L-&RghOK zmxJLyC@mss$#i|Ix)aD#CAq~^8kM6`zYI*Vo=AN%R4%9P{?)2KygTxJz6&8JKWC%j zwX7ys3rs@Db|+(G?-mysbg^n0{u_cc&+Fp#J`0Ti@$&R1xsZLP1p^{fvilp##52QW zuT?96M)=`diaOI=Aw0{ysw(oVPk=*kDsl$$$Ypa(lesA!p2+|jNwe=BHKj4?#sFH~ z!2FIP2|DkqvS&lF%OwTcQ%yX0$~mR4onJ@da~T@%6#)H5n}1g3b<(j6m*ALLeBMa{ zel*GGx+CyOt8c$)`NKu}5h7d}@gnPl)CX{R{}^q8{dnzhoJMfd&na3qo_ap>n^_Yp znaO4HVe&oR0@n#AfJSf>)k;A+3_aTfsO>e-XD{EM4(s@OLOiAff4&h$h8)$=g~0sD zJzNk6WuC~C>kncqd-;x-i7zPTNVIysve+zzJ3d_qgkFb%sb&$045suu&`VEMY1AOR z+JH90fbVrX>z%Q8M74Oe;Yb9@VdfVw`nw75u08qQMFmTnyoas$`tsaQEov6Qm6xSB zRkfwc4-3e1pOo(}!;_$L?)UhLgR#}CcqEDbnLc!w85ulBd!gL-XtSeBY>_WapFdv! zyL^qv3>Bc+?RaEw|5ZQ5X_-cYtlou=v2~Y_qq+-xf5`4A1Jty>z#qRKVhW8ZGGW~( zo$8rj#0L>_`AU}Q)$#+oxsrFN;!J>d0{SEBwD1^a$UxS8Y1?4KmQI((j9Y^>Em-wjuq5s2@zs~JLS6&JbNNeo(^upYg znz`#Oxo3Qujn+R}vZ!+W__m$q;^hw5JN6Kelt8`^KTiM5i2u}Up47O&uNbneD+A^0 zVn+>%G2M(DO+LGB6SZ-9?Sire0n9* z8TDESx21mejuNbi7>zV_jWetR>;3_L>FTK#GS^oi{qu{f`jDv^bIf6qY~%J^_p~~! zx2hPFD$X!+LYSPDDuchYx5yx(NL1kVG(Dr~vr>t)fjzX|K> zDfBk5^%=cK-C?G__6Mq%D*s10e4K_>XP$ynx{lbHj&qmvO;BM`XK29C(T4D7yAPHQ(g2FJ?u&T)Wd^$ zs0IK^Mlm%;N6~EC)4TQ{RL~-AGm5?7XEDB>19kCFIhr=r!4I_hhlxuaGVPgeVrR@6 zwQ;sOg97#rY46Yj%wWGm{pZ%!d4c|k4rE&6myAhyKKrP)`Fk?{rimH7@io$ioDW!y zSgQwr$S}3=T^wm%F{O#AW@jEpehvF-#fVLPVQQ@YF0HkgMXbm=ePG( z&sw|U{fF;LkTZlleg@y}WQE|vF^BooMswUA(*^UotM&-{x>>sVRPxwa_Ceq9rZY>i zApEurpCi4db99-Hnh}Y1!`d>R3vr0ZJc{O8vBg2iM+qJwKrBr1d0p3Yh5 zZa>Fpi8)0`a+ztir=p?%QaDVBrL3@k*F1cVYZNY+p0P@JtnA{~r3#3MD^z+@ybEY) z!Fx3=Tz8NQdafJ`0;f+-W3OTf>Dk86sKIpa<&@eb2Z9$4FGW2<-3%Uwa(7crdTx++ zB78fNLrT;bl0);l<&@CD#7BsLy(PpR2DkjHP@@s>2)~BAGnZReDhqf5(YL{|B^`1j zIM5-b*%BL_oU)w60@Wf%AawwOUHklp?jZSKPdK%MU{V5mCj#@uenBTP5f!$ur3aQk zc&%t)(nPnhtekuFj^&?u;r3%nMZU8J#1v*m{sEsLp$JwlPd6-wlPXw8E|F_`1^G}A zX1dW%a_vUQIlv8Mt^=;|PX+wUv{8`Mr#(0R(6o}r4D^G7E=t?uHKZu^q zr0IrS$>{hRjB1)E4-rkh9fbnN`bB>^hSu0Z=L1)eS~v`5IRaU$Yir;wXd)QM6WK>a zx-aH^y@EKPr6CTsTGL?&D45II`yd2n8)V0Djw=!SE8(%bBYz(mpY`8CtZ^*Ck5f7gLlGO6o_d?sYCwbrcH4#76{}aC-Ds8*v&-Odg;#~rF;m)8b zvCp^KP)VHr1?%c~6+|z)H~eLJSU`(GCd9Kmu-4o(Mpd5X)1;F=tvUoUB!{2yr%BaM z4r0Hi&D1CZrq6Z8&6<8Pp(etDElwLece|fMB#1`uc=FV=g_;VAT~oZIMP}|qpFG3K zKR@rtzasx_la4@4rZGUgKl-}W1gXb0+xP`!!4&Q_BVCXFK4Ri`>r>P_aEHHkBN(8FkuT)<%=Flu_w?~$6LDeatTDr0f5fV>|DW=1?0Bm;{+ zn~+rK*;4yPZo0I88Gx*lPT|8AQ(S^-@nV&zn%Uulb%H!jfvqvewb4K5arv1wC z=?+nhs}6Y&AwpY1JVu-L%j%PPPOwzk(GsF;HXB?9wjdk4fHuu+gAt)-%6y&DW7J&X zgplh9QPVke_6RWz^VZ7lEbCtZ@I{Vj13uk3G$idUXK`Jfq+fK))`DabK&RPPN}PUdu?^>agfzhwez*}r8-0z3j4*Eb%HTf+0$rZX*)1ZfnK$=HvHwI z{a1iP!E2q-Sv*R2o_I8SY+uzP&ogOsU-jQUOe(MINo!t{lUe8BN4j@~r99ta#lDB4 z8UJF1sG%raGW~CTs}+!=b7Kd*P6MnCg1>lmil z77e)cB?^4OQQbJh^a#g{H-ENQ?tyN6|J!`;u76$(&dFD%))lIp7d|p`hv^+ugah&N z+pI~X)byed(PfP4Itfj?5xC55EkG`*TNG0Ie^xXlk6>eJ8080?z-iFA2i{%*l?&Yl z=$f8aTCWOlU25X+g9;$Zrv_uyBXoH;(Fm?x#avvD?f$`@V9CZ(RMb@V&1M3te3v;Q zI;5<5R0`$9UCz4l8z^ZST%o$SMw~>`u^~%QJ}y%hU&W|sgiecNSpW3^2V#u?>%zY@ zMgq|4@xs3$2(X92?B;~cgf&_(u`#U+`>`li#euofi7Eg7hx+_k*ai>u%Z zs#WN7dZ}RxpF`0465vB@rQLCbSA!Q_>fu7ik4$$<6;@N`-QvriDku z!;&$(8_SQrr42jaZA5k0ouyX_ZYV5|m&3fLh?LnDx8b!kJVLvVSH>w~fU8@CD8Jn9 z_sebkEvQ-Zcy=ZySX~`a?Nwc6?%wc_=Yhlq-@z9*cUx5jt&u>9SSd&HW|n*B7a7wUsIL9d=>-vZI3F zxY6@N+n~9QXi9U-W)zoAf276r2$E;G{tjPTVD+Q5#B;FMzA5FU&@+(=YvI-e7c9RK zCbuo3sLL^Jev;#19aklZWO$}OhWfuH@6ZoPK9Og?|G~5W<47vY3XCnH0RXJE0RW2s zQwM45VDD_{V)|c1d%@e&KxF~_snvOFJR_}x^iK~w{4Z#bUPckaz~vWi`F*9`2b1>T0DLuU#<_pj0tIxk$y7l=t zx3*&0b8^Z*+GvpE!G3NHEkO9H=g|O{z9SsEty8TzaRPjA+l8^XKn)6F_4_Oo3=+3g zcd5lnW!$5a3s80nbz%$?1P8D>)N)xdSfcLQb4!_f99EG%46!_+ z)dNqpfN95UaD^)gH*uHgd#+h9q|S<5NE3I8TEMnoK%~o((L8N$fq9+^ltOHkxil>D zuZeUcsvc%ZBQ7Z#fZA!5^LqovU#(NEgFPj-9<&a2gQRkh$pM+8lPWe}z4w>x0j(M$ z*xcv($tx{;*uyzW-R*o#ot&GwfhzGZ3$T!(>$aIADVZ2tgC`^bFJZn&o9bXhtbL;`p~=%Jo}n00{`()g?&Py-pjydhlm#ZIjGmzH$Z z17(+hxLW|YZWw=WxX3OwciqtRGoiHy$_-!w=+>Y$!|#iK<dXn5UPJ0sM(wG$Yp32Tv|be7@Yrv)=HR5HW8GZG^T+0N$|AcCu)u zx2qXlq?Z$_d%thDG@r!BzYX&4g&+6QDMjs%ZztIIFNG@FsXW}O`xSy~H zp_CuwGh7cGfC2O!{oPk6Va#Mh8taWC%16_^a~IRIi1}k{CaTLTF}SNHlssbJnS%?=Uo_!9XkdH^obl+>G*WRe9)v=G zZ=pu8k_JGNoNpBJ`M|=3USb0)ks2`gHxJZ+7H1H2j1#B~7|gc=h3A}v@uYsuicli! z5e3$qf5jousfR}me}3&e-?r<`o2n=Xb@&)}v~q2Ft5xK;($P!K!`+gvUZ|6-tTb?C z*Xi@w)?Q@sQ3)9*ySX*13}vkT&>it`b+Rt|ZT+{EEL%#AL6y%8c2>DIS{n7&C9m5D zlNZDBxumOmlj9RF>O;U}HX;G>X=+n+ZP5hXhR#e!MSYq%gHG4qTDzA~XLSyG?S|J1 zbT|lfhEB#NGPMniGmBC?NmmMPEi7ez64@dn{L(ukTfU#a)&7#ir;|vk$??j^H3<^& zG~RNGHoCi#qn198bHiVAG!AC4a}+cMf@zskT*s6G2?Z?Auq1sMt z+#m`StH|-*q`v^?#S+wGYRhfSPGl=P(mm>PZ;~hmnc|U}c*!N=IlIm0iaa<%(ytKs za5GkoMhdh*|CrJFuk?IJ;`EoF{~EykfjM->tw!E7u$GWoUuCz^pI>Bq664xPo}REk z!-I-XRn(C%U`*sdN$B1oR;@Ud)|S~nZgm z{seehY)fX)wN)75R*#K%*D*ibAP%qVAeX4SAgm=GTj*h#f7SzNPk>nxcefUFsQZ1# zrZ+JJ7`^X%R7GSD#&_6~sbdQjY8gM)>To=#YHQgW2G>^2KD*f&qBvaLhLm77ohg=1 zz#C|YSWY?)jBS(SDygOYK6G3XFBAW4FM3Awt}Rv`o3YItpj{W$mr_{Lo(@tJ;QpNLUeAd$_5~VhJPQA?Tpn8yPRm7Tp6; zXd7kDm-miij7=glL7Fa0@%!Mek8qNZ-6jUJRm$q~Ys-1EW!MLs`-L0<2sgIhvCZ`n zS>%t=%XCIb;%kS`kLxJs?LaBJWs|z{WhxGGZkvj}x+Ix2-SYeLMy({gpA?(PRSxw+Zt1yLnfY^Ut6++64& zk|qRd?#Yv;QAleP=_=68707VH@QfV_r6^+yc~yAT(-G-=9VI_^M8hvj*XbP%t#Plg zV^f;Dl2vEL=00Y-VQI&RK2x4WIEq9d(Q3oowLL#et*DtoH%_u?K8zaFe)TQJU=FI$ z@T729!@#xg+1_X{hsJrA4`wg;HXj|hi?!PJV0k$?|0Rj+*@o2%;@i}<)SCReKeILv z%H1JMpHx#jZr^mESJljp+qS%p=YF1LQ`*iqV^E6Wun}Qr!((cs@zwHI$a3~wX0WFHkOdy-v+4*Mf32Ewa*nqZea|$ z_nsnhf95m|0*@>;>Q|8&BC2t2S#MZ`4k!!)hy=*YvFgO%$K#5}SX?x%htr&riOloO zVtAEPoDsO>Cpko$C2^emIW2~RWx{kwtJwC9TKkc8cZqegO^3NP?T72dVsmGP+|A%y zRqnBNsdF{SR}pT|r`vV#<_};+s`k-Go%g9l*oPNHwsDS?{ITtEz5(8|5Z;v9(L2E6 z@EwzPCDGE3D_>E&xOX3NO5_;4BUMjxKSIbms}@7gc@9cvh>eNSJ|s5FzW9pCu-W{8 zV)L&i=rYbc5n{@JKIGaylE%Ti>@_C&9vE87dGrBr_*$`vI!YyOTJ;59v_e zEF5b`SfS{)9=-xu2XkQP@kw|S09^lky-GHRE#sZ>)vptTQ0)kQ91ewl@fq(*IiPql zd}Lj0*EfTIVFZ7K?g0Tx92kf2QvP;%X(7rHoRG43K-xz_a13pkL#8Tu)ZMicDjd2) zM3|xIvJUg=?+j^)HAh40N#*@c!v?o|$J(aI-Hr2}jg#Mbu(U(p3RSv5e}k`usQ*Hf zx@MW@qa|SKcc#U^0dLsTbW~8dFR0k`K9Iw{JraC|dyy9TgpC{yJ43iZME(T4U8en! zXpw9+?0K*j;g1x17w4z2*bRr|S*0#HZ!w2a_*z7^XIeVAN|kx>D%m02zY{s1#j`gy zq#e+{j{SKoe>)ZO9_ig1$1vKBb4ylpW54}5W^gMndiAR$&c_6pt`7g=&Uo?b`jZM+ z4Du*gCJ=h92N7I7auVW=5BnJM9!j*e4><$r^hveFyuWsfFY*rC=ZoO<36bQj-eo@g zbkL3PLinPe`p5&~ap7mMop5+|1%E@3vj4RY74oj&+rP6Kem-jCcAO#fJQTtU4;z#E z@2fxVL&jfRCY?MLGaarv!E%um`4eu!Htgu;R48zAY_=mx@b2bt=6!bpmHJo2fwv;k z8}n$HrDwXK#f!~q1Lz`Kr2-t57Rp(j8ZE(0qhScU4jh4eMfvhC@a#DDfapy2A3RQt zdBKf}A!?usqapG%dkN3w9x&ActPL}%WjeUVVJSp;>R1-&s~RG>P%)qQ+p4S`AtB1S zKae6eLOcSeG;DdnZjku*8(>yTL)1pj+*+sYiLa#S0w(Rln-OmKYdT^lzdOC==h!^4 z+AMzLFP4U1w7Qj288#LRIzI>voBDRci1<-HLLzknAj)O2|1}}q-;Wo_4#3Qz2S~-# zGl91*)?cak283a3aE5RlcqodqqiovDI_I9+X(tyJ}mWdNRS{eyZBp-*llc<}7 zNE8Wd%`OjSn;UC)$Sil%1gKYk&PEhgp%@*ae&-M4X>9q&&M8$%j&0RJS6GU*EO{JycbI{Vc`A9iCx zk&tX7k;m&BAvog%%;+-*H>10TehPJoi zky?Ky7Yx6Ud6r7s9jTLF)NE;?bHdeDeE(<=#_A!Oeibqq9%8uc}vT4m5AZ=t_7K1(?aY) z=szdJMyySP3DIsA$5j>TtJU(=W99PXd4KC`qEf8lu6eC0!K9LGjvn@lfgz|=w64e` zRffB|Gjf}>{Asn%57h#NzBsj!X0=tfBy=W>bJ9V%&@T9(_JmGuh#rV# zXeNcj!-HdN=8fDOHgYnzzS3tdvL{ZFl-+Fduo8Vlr?sHaY{bPfN#&WFS<_g^g@uNZ zR8+^ws&M)o&XpIQOO^CM5mw_Yf}QjQoz7JsV|I8yn2Fb1UDu^|GVDn$5l9+idwVa% z(~U2h&V`%JqnYhEB&)o}!OHkn#KCzVCPYut6d$4E@BuV8W^N+mH=tQ)9Makq(Iw|9 zQ9)YHeAG~9&uRM}J3jPdckW?htkI*V2l0%c6lReBiN&ARE}#9ww~=8ai#3(W zXt9|f`PjcYj$M)OlE_C}Y@4BZ7+r6&vl2~nHmT2UZ@Ty53a4p4VQOzJZDZ4*He<25 zLTPnY%eR6N&D>5_vzh;z_6CMA_M>ZIGrB>y1I!!SxXEQeNx{>aG^6ZNJk*s0%B>gi zKo>o95NT$fz!@w;S&l;YHf0jrb~>+w&h0)+=YV@ z9y0>--rh{BbGsER<=v)fWJ zcs=%k+;QE!U~reTIryp4Uu3vE?WlAPaZWsSJm6x%wH){#MvYkpZ_eS>K?K^#iY)O* zOoO(Faviqrn*pr_*RZx@xP0Mkn!>^2W}*Ln^smWbxoYKWs;GT=Pp6~aBgHQfjNa8E z#b(wIKk_HG*2XOP-vk?Gr&(9TkU+Vf|GH%|fy7>R9*v*UpKUSn{rC$}j;mg++Y0@} zy~1RhrR~TNUrfKQHX!?E&RR;ta)b*u?`CHNwn|_>#6@ng`iuPUMB=#2-QcwGrn@Gl z=t$CeR)HKrQd(54qHDPYcBm(doPI~i1J7w9>x4(ANKF53$neltgx>wP5BQizC;R!*4xQ(z3cUepUxKG z#zFc|ZXK%2E!oOA19LUv5Q1Y4({(zyw2~ve^u+2lMm3)FrDZMgBoDob~c~$9X-3H9ukAa*}n1gD`-AJ z``X~>rZv>xRPuJHL@(dYwl3%L3lqeDVqC`f-b5VzP?!=>8Hcu;i;3u>JL?A7J-U_6 zEhv2PnH53nGIwzP6$`i3I-O;;d3GCr(IfrseLATIjA*V5+Jls zOO8(cl4q40kaPL%LN#Tas+wnnVXk2)SDALic{5_bAh;rH?OidJ8T=7Kf5bxjmVr74 zk9kP>$CBv7W4Tjee~{Hi#}4{_+-6`n<5z7<+&1W z%k^;9sXLO91m?}GQ0R)~;Z5~qrCcu6R6%KZ zWZ>)`60>rlRh&w1rF<=IDLjMl8ZFK_btp;SwQUG`yQy(X9{(99_2u;q?}|J~O!7)D zcm}iYqHNc!It|RsT<>NRcUJ;+BpO!D#{5n>4Ffc^6r|WcFg6O-Vb^7&>N-Yu4Vs3z zV*a`kWk(6A`rmAl0kzs_3HzhENQ{}6b*I{A+9mH8 z>j=qhXHO@I$3M&YT{{>pGfn9tq%REd0zc>`;;aRJ)yHD0Z>i-dW78?135aSjcuJsq zxsBtgRVHk#up&R6i~nZgCm;|;svPqC?C-`sgz)Z^Zu zA=oN&B7Q9mV6w>`8x>E7A2kZR4zIE$9~9w1v@|{fo#UWguujpyg^uD-3L& zljM~yF0@>xzdc7YDwe)~`05mG+%$V&Fbqf@;c^PWn6EJ0yN0(qZ0RYe%Tm%{L*QW> zy!JMk<)>E5$Di%nXN!qLGwPSvpZp5){;3nShk1ET<`^u->`vs}m|-Ra9~)q1pB^CN zn0Ta%Nte~sFrXGZI@YEB?i|2v`uW<1C@B_&me*tez!1Yex zK!lZayaW@;ScJsYvKT~}N?U$P17C;G=+i#6OC0NssX1dJc3`Ef=yTLRE1W4U{~~}f zE?B4Q+oib;`V-P7fj+7}M`+lN3{JuAj{;CS`MG5_vMTKnbtlx;g9;@yX~j%@MP44L z=uM?1TNiOnQKqPHb~2|CJeTl96AQen9Y<43ZZeTQ>&7sn3UeryrdT-vLDa?M#LHsk zw05-c#6+j=Q7weOsV$N3hIEYXgtGnC{hra35&yJcniJCpl&Yd7OG9o<$^-=F&?cq} zv^dQC^;AG)4p*F-wKk#s3|fzm>IR8_1ayiL4c4nj9PgfjcISBMk#rd{J>VS1O-{*< zkDM0H1hx<3Z$#reghi{S7v0BC#+G)(kCNAxO=PLCBb5J|V?93a)hml4*gpyxDk)e* zP3ri{hpK(q&{3iDxS>7BT3q@PWtBdZyu~9I!Q2|x@4-EVZmp>_ODnslbF38GaD7K? zZ(TOIb+KS@DV0nhNm(gWU|jG`w_=!)qD!K=HES#Z?#wTJY%_2CqnxfJ&DB&)7)FU%WP@eBY%ocA zueT+a=XhkAd2#WeGcgp$SJzN+b*xE1gyw06OUH4tCn;|*v!=oW{RzHI39K4X<8TDN z(v4Pgy4J*PtyQy@WZ;q<_eUvVjLX~os6?^W;h9=^J40diiC zu@BQp9Sse(t*YJd;Rjs_gw#|A~N~*S@ubnHR)+J_CQRxD|ax0PI8G;v5>!IUA zaAQ}EJ%epiCVnm3{*(glp@jhHU@1g!XF^hS{vz~CA0+O&%JiH!gjBqWg1y6wjb2XKJtU!%l}w}* z{$U<|Y&sdZdLX;AVKH*Q_fRj^5)6)KvV*I$8_hR;dicYc%rGQ8d z*t55+VF}|J8udP38(8G0@C{i7Mm+FH(WeqmE~QGV9KAy8?2u4?r>v1%pcA6pDpJre zqzmgdFgdHogG);PG=Id<>=?Zfav6<%*2R?WEqv#_(*?aJ1*8k~dI8>tZl9s^?|DS7 zo7nUO?Hl*9H(L*zPh!kvq5V7-)Nw||nR&Q1{r1l8%I(baa2;NjDO{P-9H8s{eXka6tqH>06VnqC2nd*eujt;6 z)ygFZqr(uDHr8hEvnm#Iis?Duq83eP?Vt>Z=y9yhFcgsYa~Zd+M?F%u8w36aaohmy|CmRzGp0szjdDZcn|I=MaBqJMts|RdM^9oeN$T2 zaF|CT#6oW3cIfr{cdIty zkA2&4#a&OG4*sasqB3?z@R{L#b#%U3t-OWeGM@YtSV3qhSIZ&oYs)mre*e$eG?PNb z@K*avlQ}~2&QJKI?U`Wmi)G^Oi@>p3Y+8z+RcN|F(nU#C1Fjyg3rpFIHMS&?<_h&md-+JKYf2+{mTraAbJ@5xDABFQK2Z7-hp%ZqNpRHA#4}RxeHv9RwD2 z&q^PK@VYg$4Tq~?_t{m2)u8Okwug3(Te(30jmrzAB@D6dsNvmm4_J^8_TC4tD>o?n zkSLLyZ|;cYi|*ip2Q_WYrtScB*fqQf3*Pq9iBvdcj)W&0 zF-*jbrWBfU^ zevw2n`;^25_B|D1G*&bB9g7GtOd__oGrpbfl8+pbo0+{GO;gu^1*-MZ4r5#=?7%Vf zh=YZvHx>b7s+iW)+=Oi?9!$oxM5+*%;Dqgoe0&SHumdi|2`PLYo;&)`$J~uN43h0K z=m~N&{JZHn2$E5}y|15eEdOXyzTi+BkKG=938p-ON;1pP+ChzpT(M1iU>il?RoFvp z&GXBSd-K(ft54K_qx5Q-^8kr`_>toSrKuCUbZXeSOZ0p97XAZJb%4~kwu~S%7rSj+ za57|#6)ut&tHXJ~U*xHC2j=Bl8;_2=X-Z}WH@;ygeraVET zGK9j--b+z3i1;DBtysO=7RQ=)g5H}RtLi7`B^BTXgV+mXiR&e?&kZ20=ur*rN`S7^ zPqaPXgf;|0z4ym+#5lHWF%66?g(N%~@xZ710QJlRwrcj@k<3Oj;_d{@JpwnqWLcQg z<8BLl_syQcFTQZHKi{0m2!Fzi5aw~~;(h0UDPI2hUJo)04IiPW7_V9c*si+z5w<#0JPBU}tny-26vr}sX8pnYQodoa_GYTaK57jO{$wCS zg@10H^UVwP!>)L3zsu@j{ILUabu(jj$M=rhxg`zRZF0PI9_i?h@NH&oocq%VGWTmk z;%jWg^dO#BSlNNna~tJT_+FkqpuC zx8%^duP-oN$CvVs5QHOpgeif3OcHUl9M6;#Mt@T~|I|M+C&#G$qjtjY;0J_9Jzf*) zx6NJD&cgOPBBQ@V>R)+C$kbxT7tPT05e&Axu;e1&7R~dQ2SV9|P?H{+Ru@i*-|=i1 zGF)fAXROZ?Ombk4X-N29c~e~ZgW8h#92=Dzm@5nx3a5Hu^r0L?)?xax1%<)R_R|L* zUr7a@n22ry6?1354!*`391yl+^29$Dao85xY$+#VZmB~Vm6XyknEwglGVWz`wyx$IX~&shL|z7Rbr{Mz#f_qenRaOWphy1q z`U+{@)%A%xU)x!zVvY|jSf+=*Q50M)V+|S4q2!pFm1ZfE(locRwe%%Nn1M`2>Q~w% z5?(rgVZ%`6#GERO=+DlaD-okponO~?j)fp|N`yD?lAYDRU=n z7QYmj{-*ABeXa>_DhP?AF;SH<37`#!86mUbJ@mD#42SLDT~C8A^oycjTcE(B=Zl1G zfqH;{i((f%pTYy;GO{R`h{w99#<>@9_{SyAc4P7Og5yfek3{;)odh_IW}m$|C!k;c z`PcGZ%Oi%`H*)e?14303-L0V6pWx|}#71q`VpHwl?XZz4BICX4>f*k-M*?7>#)xgk zKm%nogJd+pW&d*p#gIihmk3^&fBj;C$YqZYp@D4|E-R*e?nYV6xp+foO7kH^)IAwk z=gB@g=OJLCrhurF%zxPS8-X`T6FY$?T(eVjQ;}NLcY?zv6Y_JOUgHH*{CCH48{qlK zGZ>||VxkKBJFqK?(mZ20xB<&qP^G44Xb}e`dqy(S(U5Prf@&w!K8KQx%LTRs7m4Bx zIf(>h#Wzem*DgRfa{13vCPs0Yv60f{K&&q~c4U@K{iMnYhvA@-I&qljD=5qj=qL6R z-z1B@_qgQPxa`M3mv%u0CqQ(!OW8D*wP_&f)J|?v&`Ii_-(mf(n^shztaN0!#RCMN zB;O!PQ-6kH#cCW13TfX>9n3?9%7=++_0|0?D09n|h`4u;tUgTZt7SneG%1q%=`KW) z|4Y~h{lBX*?ZnB31gt%qBk<6nj<<<+GH0l4!;2mr3Iz-_dLZhv`b|P1JwgS&qzr<< z&%f6BmQ0r2C1nfF`W+EU?$dpVX8rqG`m7bjVr(qt5fMGlrqlu+KByqXVR>Z$^5TI8 zi{%WAIg%Nsut)4*&gdbZGIn`<*I|Q@yB==H43#$iOiY*Z{&z@&FlWla=y3zmW7P%n zs@49v1EK^}7TR}YOzTM@{#dvG2#(p4op*!YhSh%#hFbIYPA)@JbJk!x!hhl%04$8F zfa#(EY#zAhd0#C$8*V|m=|wZwiU<<{z6o6QLN_+DjP6oD_4=Us?sD0rbi32}u^4#| zdKN6rbnJ7U$#GfiY%9H+$T~wK47i#WsnV992=&4Nnul@C+-)G6z-bh!_`RGF+Dt^W=Z87wHjRmeUqwHZw7Ix$4JjWNoAgz zancLb(1ss0T9hWUhZ4kHaGb=e5=YDKg4JC3Q#FeYutK6UYj&6_+GJbgQOz50wpgyNq9pMUHvNRg`h@ z5dB8@AJ8L2Y=}@ZFwALwY~d%4Aez)qeDCAvT)e@l#|E=IG_>-|BY#zUGI33@;}mCb z^mhhzc2=ZNi`XQmT>WJt+Th+3F>T(SOV_3pI2IC4UQ^%pg7$K*7EKuJSVlVoNMR={t`$Ks1!nJCc9)`BgBRAdnPwNql=fd(TnYmGW-j4m*F$Q*Mp1?DW&1}KP z<(?lVgYf=cx+4QMyEXHgcJ$OF)clHTZAXVjDLs&%tlfecZ;_|r(Dm&&MwfaLztG95 zc&3ie`gph}To) zJU<49rH)cM(peSlR{o554G}3*Ci&$zO%7O911H1Mow0d7ygKq?_+P|73ZFW)GB3|| z#UMF}-auAxU$Qmg#*leG73qU`N}9)XE>jU$H;43Ac>jC9fiK8HRpkn`qbH|^p?2#F zA33+#*L@PNxWbd#l}8LG>v}GkVReylXm7zD#>K8CsYq8mouG|%`_57v=%1ens5`Mg z^?g5e4q%?|=$uvU?DmoHl%^W#z0HKavoA+nG7==izjIxR!f0R>#C3o_uJ)%Y9LsPT z*Vnrbk;c(Y-(T}kBlO3W0}9J0QqxaqA#$9?kw&i#F`?R9bHe#h`NHIP{yHVR*iIG{ zDjebUapG<5K3~Xp@&EPKvjpM_IKf~37~7URxY+YXKvwaj*_>Z_Ur=1q1FKNu`WD!k zNQJvgoO@6e_Td$!=TA1X_n*X5dg0+Fy_4hr9TwC~;Z#rjGy9i{W?$VY4l^#<{NgkI zFl&LL;-G2Fq)1_KEuDQoQnzk zBEBt|8TQR92bK93ZM`R$vj?wO!d|$Ax2{p2$%VIWafhPJ6=P{9`U|vGf|06qB8QP}Kbu$ac#VX3@Om#Qw2fW=(do z(C%3YoZU`>mps-B?}hfHLPRkNA~;Pnbu{{3;a%zUX9lNwCcE?dPC*_x112F#z)C23 zZ`42@c!3bUduXTJQ)|F!;UJ8chLnyYk_N18P!ny?nnbUZmz9YU?`HSb*o|{tkZI_O zBtSWU9^7JgbHUdQx8Q~S3a7t9tOVNtzvp+qt>7^+F#6f%n=)>}`sRdXeXj~PC$=r% z_?tm>ARV}R&;hojI-9lt6ySxR9Y?Z01?l1%2r|Sn$aFX!4~P*!9rPUZSXkbbWFBlf zlKT-v2Vy#QGM0oDP=lM}1$aD7n!kHJnOn+woH^ppsy)LFjUQ@>J z+~GuR*%Q0aeX}}efGQ{thMqUTCXxeKPc(q=+6(Eo0Q5DH_hx@5;bT2!0qf?uF+;A1 zUL5`{3Q)V{Dc|=o;mNq?lhcSF(AeTnJ2FBh9`P` zN4dkF^W@9L4mFdkFL)dYKmuHSA^%3+Ci>|IlgB`^W9ZEvFeP#Gf*us}lQn3XvBwwy z)9-`6?Fh^V$OCv`uF*I@aD?UGI-kabuHk!@K)OI2k@4^8O2Ukvy)kmOAfFLAfwK?3 zn8RCOrAId}K7bP4E2P#Z318dnr=jO(m}|e&r_b>>XYWtYobLTi{qO(7i#fgfPsr_N z00VMv6Uk2n{d-S8iBfQ1xNA@lDYBnR-%&~m|273)JN<6E_LI`JyucLwAg%pCp35W5 z?>;uWJ-;S98A$NUmfw&rKZ^DH8uA-Q6`!evUjWr09f;H8onN=sDIjOe;ScsfpCYrq zS{BV5fKRw<^53_PDsiH_eirS`ADerd?mbkjjZdV)#mL;UBK`dijBNi{_@GY`4R8wL zWG$@bz+s;c9tZ|afQn**qJhW&e3^cL4x@nsL)i23 zCXjnbflB>Jd8tl8thzTV)dDUQrVI7+y&p2 zV*{L~c}@HV-T^Rv(i6Ji0x; z0Jju~x5TA1(F=-u4*KNK_W#WUxbJQa&q#T-WSkatVo@2LAbnnqJT!w(LSJ9sUDEse z$S#+rO*Pm7pEnl&0t;mKwgXFpCdX#BvSVomYna0`bjvMMEVu;pj2m2vHepe_kqNg0 z+5i@27HnPsMwV0`!B$wVS0>~|pbB0NnB=moG33Qegq$VY6JVZXO|Re*jKB=?&dRQ9 z=xQWy)YHi%Qb&a7P7C&I;U~HVrrZImd|(vjFaSe=fzbpMZf=#Fu*g3t--e!#Pkb zNH9-e@SxuQ;zb-w5`A}(Souf*i2A^n+*n&?LeSBao{tm|g2O`+->L6)QFi3G1l=7? z7_U*9$TPtprw=?+D&BZ?!0=wO=R9*x%{T?!9o5)7&TclO$Do(iM`(3*hV*a0EA~$g zd=KVW<2LcO#{T1MeVu`~Kl4k@2{0I;n)Yv5>t>Xn}gaIf+Gu+)cMUXJU-hYr0EbD|w@)aDXd-Mu4-0!*wgPVa)Cw zkR#`=o-Mhaq={kG(XgO6pQen-wsG+M(?Y_}rXfgSc(eTE-)vAoq1%Ad~7YW67h znm^3qYHROzv~J<0Uco?+g}cSW=x$$;&%%wI)85hCUd=&(8%9rh|G4pSqeoq31^(OI zufX2CBI_CgmI!xaCVK-XgO~ciOiDj>&`R08;E$Ca7q_cF$S%+CUblhcR2mo%+9wGf z<4!)~x(>x$WT#0%dwX^3ifXpD*f7k>PUboa=4P7NIj&~YT!F>8UzDF!!=IZQU^K*F z@h(vhYt>Or`J#RWoqRa4LrwXpeI!z@O2)di{vXaMZ7qo`%`|m(#neEC9dld5FemsqUe$|Mps}R+=$Q+h$e|Q zxQOaU=Lrf-URhadmxead@#*IfHHw4Zn%v}@`9%2Wyhwm3E3waQo~kq>oSyB@l=6Ce zg$QS9yvJY;iEq~5PqNJ_Oj5e5Pz%^INr0#-+jJUi;saY*5bl5GT5pfKbLOhv+XSU9 z%7UD_^tX^cKc(~_DVRC2IgSp$VJ0F|R-S@P_irn4yi=u(&QN7ld1YlM8(Vg6NuChu zzfD+KoJA((Q|7ZiYaeZ@ZB{qqF-CRNLvn3qS?#i3xy0wOn0E)--(18vOinoyD>oX& zh@M?w{wXG%#XGyYLKl6%;4#+4m&(0({IoFnY3dSAkfj&h`}=T_PK8ZdqOMEAbv){H zVQ*x-lMrLCq?@me2?vwQIXt+Ks($R8c7pZ)0a`$%zsCHM*OrmrN8iiPB5`mv21grd zNYW%Hs1&-H9C6{^PZ6znl2#V|o`n?Ju`r%VOk=#-QMmr@NdK<2V#1|yTD?Umww&(X z4w{ct_js#Uua>B!0Gt8tt(N)8d_u!H(LO<#sF1qJp<1l%ZC5HNn{baDDJfZ!+~&kn z2~8cN=8_Khnh?)Mi4%P(A^9qlk`{N<;+bh^`HjlT8A~B4GeGA@A{uY+k8O{3EZe^p777VsOe$ulWubCOIr`8EOSkuekO(h} zb_u%`;pG@qWNi|5AB|5Uf>uberUKO?%_%TTid>C(jV(KK`<}TYiQtM@ypJq75$O8luRKs;r8}2X^;I$5WLIR6!!2NL(20>PzOY9yNa|p2(a>nb$=3C-Y`e4JvO6 zn%jg}B8;1 zO*^B}u5lAe!*<(6m`~$rmo!9?J%_R<*D4^^+Pk}b=Xi*vAxe#i?EMk4;ZnP4_UIHc zaAnK%M}?xv9J(bkKHGSlAd@mim?AV{;1tRD^wCf#Bh#c@m6%O@Rf!n_IWZ+RUCv5U zQZhs*H7P?FR4EzVn!S4xwX&Gr#dH(cK^Aq=)^jHbAL7yW@vIZZh;_{9 z7OHYlz%i4Yz8)BzWXsNIR~F-aoXIPq?Y;eSP97i985kEcz3?f~3RtdwHvaz#!bbM2ROMskq{-7syeMP|}fhC&=WdCcB&{ec$jaE#gPuBY^2O$D?i z@gXa%souo&mUaD!`2vsBuPX&;zDr_ig(kX-nA+7ti#D*m`poM_a~5QHU{~Ar-2(%? zJ#Bro3W~Q0wX4=kt2JiQ@0MAsxjCQakrd?qXjhcRhJ)@$60KpoV*`q)a4WU-2%n(D z;+)D{iB&w7(Ji~T?^TYDe1Q*?w#W|3#m%+cT+jI;v@9jT&&AD4x%Y0ke}KD>dRLXr zz3|~6c863^MQ*q)*5mLuAsITTi4rij~#s zEW*dl_o<4g3|K1@Pgm`Z!gVvRdi5C;`M|D?giNA3;H)e29fY=?=-#$f%g^K?gVI?D zakOIH&RTMUOQVApW{?NanVyB45ri%g>(`A@xvL@9ufABkh1!oQOwt^SCmmWo@zFF8 z?G|Bo{N<~)?b4ohY6s}B@%Tcwh@wTtq#Y8Ay93z=VNkkZ<`}vpb$W-dLy6vnG2we! zj&gFH$mYmEJE=b_#ow)Z&+(aZWEE4tC1th z5+SGGoNq^uJKqi&`i{dJ@3=vw2D(J}0eZw@xlxWFhgUQyl0>7bG=U5ITYcx_do2d8 z+@(-ilE$TCf5nO|!nVwnwB7IGRPm=j8b+Nxf;Z_wf{!BAE*i_fBdr?I#BCYbisD%; zr5b90Gnz^pS1nsAXigeQVpV^nBPQxNS*U4Jb_-Gl~&f>SPdwRJI6tm%u0gV9ib8Hr`Y9@KCH>4*UPcJ#!@oM^n$y5 zvFy)O`)kDZm^70)b&Z@SQKnK2+h;P zX{4DK&oJ}iSNo&uuKMAlX;(&ad5!Kk$9sz%pu?mB{oRr;|L2Pb&TY@u}-VPIlPfXytjokiP{sw?$#E^=P7j?GAd23`T(`f*h>Qc zY4%u}w=rcvoO8z2UDj-ZiF7+Io?^#=-!1FqcEgyhC8raqYscIsbr82W<3;M()E2d1 zILIy*9`P+uY+{kkz5fG)l#`!;Z~vdtO2$7mpdL?FmRj3vEHAa1uN;_OXATQ zf4QUN3#qK0co*4K&1z!P*zz+u7sfRs&&k3~<`5C&h&Pd;$Yn)2Kd?~F;^`ZcNbZ1> zSMGMdJ1&|?R-MLgjJHSnqDhKTVY+a>l;&tBN*XY3mW@$Tk3A{r8-&^|a6XewZ0_#s z5VmZw3?wC|S+H%(XvdWBORHY$N6g(qJ!1JHhbt+E&RCDF&(y1J-LLyJrd8T{l^&uf zzRcEbJ=fNI^j@1U=PPIpVDm4Hs6g# z+JhqYqOg5@q0RU6t8M-s{r#MuWAkf3=vscj=GOt?dZ50Szt86H=Qr5;LQuPr5)7Z= zw~gy`qV@rOvCR)6+XwlFYm;jepA47a-ww)b!K*kj)QMNM8rBnW9g% z`5oxB&+t2GRy*Sfmn?mfp+z9OciH@I6nPJd{47HE^3U1&G_(ZPq_us$f^w}KAe?{R z=J)aYZ9d4qVC&O?mQSQ>Y3|=%E9u+%46yk{gRmX|_&@%jZL}L5Hh)N`I(-S<^ksDa z!~7AOKT3VYzk)6~g5rnxu&uZ17u)(J`X-IdI-?Qb?6}(eF`lsbS5fxk07v-~Hh+>o zW%IA`r)~ZWzm*KaajB|Cu~N7B*FokRVE3Eg?_2mh2JkHZwyg&d_Z@sb$G>ay@A2nt z{(Xqi55UI{^=8h0Wb+^MpV<7TK>Zo2_j6GD1wy|>=vM&0=D)G|Z;|r_NJIxnzQ})P z^WP)x4*-7z_!EE0=6^=$FUa;+fWM(d|4zz#+=5UiLiYlE0N}83hppd= zgf4(xdNt?&w)ubf8#e#1{#jeUO7A~oOQ)@_ZT=>I%jW;%Z`=Ghe>=+owq;VU>(}T9ZT)>D=p-r=xy5bEvV6AXr{1@65VEQBtXz_R zD-WOf1ay-`#VQ~*soy|#)V=sD#ODX-(<(ymze)ABCQ!&K1_+?fKSL9S{%h)0V>NZM zafz|Xwt|SeNhdw`A9Tw{DP&C~DX~hZxvY>i$+k*Ka`Y!iDOzQuBdv04vaR2#lZN}4 zezUDl(o1c95!kJ;Dybo;hxL2(D{TE<^^7NRnX(3R%{EVeGN^#Z+6V`XD4Tx@HJwUibewzbS! zZd)rLMvV}tl>n#vXjps&De z!+~&-wb8a(_-kWX7AZ?H+iJBgwyjGn61_{2b+fg_*8fH{t*tZ#>Uny;trzP7TdyJ8 z$hr*jcR3{S3e5O#18=?6X6whn#mo5IM%k=vJ#4Gf+G$%cn(wSD(eS$fx)9e5W_k!16O6!kWnh``ETboTZL1FgbQK8p zgV#7h10)yLZUQs~((t^?xZAe&SbJ@2AC%T})_&W%+Io*|U1MEqTL(x7Ti03F(l%fAW08S1<^3Q0s2tpw6L0kW{e&~$a zROJwhQ)DA0bCdO9+qxMo@Da$^Efg}gK^d(9FCVo&rm^xd7eLUR*Jd$bGJK*Cn2+1m zC#+A})~%#_^>g()QhU~Iw)H8PTF+Ux+t#O1^+N!MA=G#16}I&mjE*}2?t=2X5NzEI zN&2P!E8BR$AbaH@<4d+N%P6*u8i4IUxrg+t1tZD27gRn+1J(LGsN6>trk=gWw(f^6 z9VDG^3>fk8%212&jf3AW*wz=p>I2sQ*+zths&O7Hdo4_hoXnLultUM8tw%7Qf7y6=j3yQR_xf*4oAfi0xObBepeUk@RmQWnr`$Ew=R-ltse& zs%*$MbkR5OBLBo90 z`WD5JBv>$`tY^vGvA#{fxD%AGG-9^#Rdn1nsL6LQ7M?@xzH5EYww{MFyBOrZZ!Dm8 zF&1cSVrH8x+a2o?gZu~94{7aJ`JUwQ``UJx;|-Oz^&{)Y8uKiq;r0`QmTi@^inD%d z{meFc4C3tP*2Nk#!j))S5N&#pYH@+^RPvnxe0 zwef-8zG{lnSmCU+xOs>(X3bhignx?F4mZ}NIL(iDSroZ?>X!Dw)F?=kGAzEXvvqzP9Oq(w)JQ9z+bRTqAuD4YJWu+ z{S8w5ceLTlhp!;jhV@@t&%wxh6NSHp_1I;^oqj$9;(ylLn3tM3Cl&ENqzQZJfBK(n>$vp}EYqt1 z$n-g3owTh}n2r3E@ubVl*C0QHQ;K>`^|HqFh1XIO``j4Yf~BVqz-G05%=T#zybqzK z%V08XLBenJ-`e^M`ir*DL!UmKnDH6VTozTx=OvBpGbx*oBV>W!<;Z)RaXSH@561Cl zY@Z)WI%V7_7$9q+QC0BH|i>@=1i6 zk($uoYx_#bTrh;4KUY7?HjWxkWHG^9b-SWJvR7_6m5!m3Nuakj0jwr_c|3d8BMKbh zR|T~#SyWk?s~Jek##rvU$00piSBy2OxQ89av3*5!&UmnFK~k32cv(5(CHBRKP2+|t;#d}-6h^)&x%!7gsct~TLFYLkb? zHRg@tP{|@bIlzfp#5z zp{aIsczk#Njz~N1ZAmJ9kpU<`)dp=ldi&d4l#+B5Y`I1E(E_MLoJ5a>i-C0n( zBQ%BW6U=QD%oRu+q{Vx8_oIYBdbW;eoSI|5IP|NmaE+}W_cqq}c34Q2ii-mp4IXc2 ze^eZD-rltv0uYhc?*f^ew)J;zk6^Qt+F7=Az9S!Mwd{(>ju>{0bLEFyAwNW_vmHC_ zIKUu3Kp)(?QkI{RAn6pR#0R{_;7TWXGhm^cEC80@vA@m4LbI*C3Dq?VGh zah2EE)+^2si8Bg2)Om`O^9jyT5o1;BnzLy+mys+_I<(8kyFU}G~(|4TRDykD8;$D(Vu|c%(nI+J@P(hV&d!!^| zV>=%0fhTE?yfPs+jfzrG+yf-odTgOK?ND23{yp1!_c>eqnMB(oJZaHklsrgH?}mbUq|}X4av>ACUM|?#G$;A+mQ0g46gWebo7hkVnrhlYfI{;`CZ%xM*WU{lU7OrW z!%drxq@5^qrl^Lv;@Z~V+nZE~K}Y8TS)ujeup_S8G@_kVqiXJq z#yWQnsPa^f?KC^|D0R~>4noWw&uN0Rt6NhtIB<_`NwTq?9lf$gT$V~Qa_o+iaCV5M zg$5S)xN?Cb&kTx*%^29byaCRr$I7EDu~Jss+_qdJm_ZQo?ocaVy}*}U}jN(y=) zGeya7UUH;0i=(l6H|BP+`;Q?dObA6-*~_UShzXcd_RX7@mb%Y1twp(eV)5P7&7vN{ zgjO<-Y$<8`WSE#*wPH=%hNerKmu_ggw24flkhEm>b;Y(jmZ57fTh-zUwK#gCOeQhI zXR!vQUZ93`k|1__V*@+el+8sAyFyM9Nd(u9awlZRGE=u>+!7DwC3!yo+Pj-?no7#>Q0}*I&GjdSK?bkLVYO2{!HIPnIKG8A~IyP35R1tk}?W zF&UN^uj-~@^x{4_^OB6_x{)h-_fl!)nW~x;04=Jb9ZKb0@h*?4zUwosvqoh=s2#Ds zadZwDflZFK8$F-I78#Z1D#M(t`e|e1MHe@LQWZzis6X*6pMasaB?~L?enb5sG))RG?ZCu#?VpS-0JDEoA0}4d5 zO1q}i`7o9Cx6DUk|OVv<-XMeKA>{i~Rrv5pK% zGNvT00SM6YFcK4M10Uu_b#!-BJbycN8U!NvU6jM`5+J z;oMWm;&Mtyq$}l~hWK6)i*)sNj{VMo#926U{&g17j{Vg%ww=+ksa4c)#C^)-Rl6Bu z9Jfo$<`))&df#N)*r(KH@@Q{Fy-g(BZD z@`)8KaRDo0uls26oc8*xB3VED+_P%M=D$2_Yg@z+KTOUeyqV z_QnUsDpeRzjh8g8YZdD@*q`GwKjrTGLis|%+;?$|QRXmrMfdN41(#CoKAZ&<{*P`N zlx-co&azS*L63F24i9Oa;j&GiT#O1ccZ+%~Mh?C)PI;c;s$}vF8fqRQnC5rhIXc;K zU9ohCkK=k}AVb4;NBa7p#WEdPLTx4WZ|3^b$(ENAFOR(MNV)vr-;Nb>Yj10BU%DR& zOIzh<$%&)koT)+~xq8Y$qSZdxzRJZjx`$VtH0PiqR%5%ym@QX`)%cn0E61`N%RBAZ zo+a+BhDd)r+O|FAX!D}1_Zh}*)uvm<%a$%*<5dK6SZh7mq zEs}KhX5(kL&YHY4i+6Z7^vAj*{rlb9w&mGdyN^r@;ajUcbFVw6fTViM^sBGj zxU`ujAfqd~gQ}(vKogaXGGx^2N+s#7wQnGKoi!tAe@fDLr#N}INKIJswIt^$8$ac3 z-=!W!OmVLCMWegmSc>6lq`6P{-6*hM1#o#pl5oAqSzc}%vE3#n%=kcmS5%nqTbD_e zeBImO9Nh%pXh{~|=+HAc8kam z%Er)g>7316HR5T+fmolC_B>@kxlNSi+RT{euT+ou;>qj6Mz{1@q`YczAzpQZktVJn zWO)uGYZ;36ym6w7X;9_yz>3#e)XXV1FvRpNYfF~UsRhyS&k!3E-lTdKJy_Pp}8k! zSR!gav?F~xC)PvDp%}Ju)UL^y9b~EFS?s#npr5>JotyS%QHEzEeUGFlCCT+tmyWnC zpXx13&7!Vf%Prof9$Lt?$X(bm{JC-<%U5~&uu7HkYjpHtzcVM2I!H#o#hdv)2Fb@j zB-S-@)wEMxyUvW?qvG)nhkC|1(;X}x1B_7{seN=|jP$psSQWxvcZ^Em62eYMADjc} z{L4^Rwu2m;h7e$R%~`(l9@lA zEkB8mK%I~UHBeJ@=p1I3*fN4ulK?rl?-)u0-j9aS{cRkE=scEXpivu(iLbrxCUsp#<}y7WC# zOTtZO?p3F0aukPh=Qb~eXCWMCepe**$m~Ml!JXuuP!NYWx?`l^dVAWWe-iISsOzYO z@RZ5dsGHWUY7>o8kg5aWWX7m>$Vu&+Nl`p7hIu4<)$Ryfh*_Ve+#*j~xMwHFdg>^7 z@-QS_4{MuRTH9J0*ODoTot)OjOIy?TV&WNlG1JHSWJu;2os=m)mzD*X9lF#DD5;Od z^hcdNWRoU}U9p~#PAcc@Or{iOi%-Q*b^psPf{A$fBI`B!)XwVa-78L)5ZBJtdG#1? zQ?tC)UYNc=K(ayT?K>m=G%*2PqcslS72PlIVdqdN`BLkMU7Cz*>Z(4{1qvm;n~XX^ zr!vE-<~|SP%tg{vCPHe0UHh&tvyXYdygjCi_)F+DH5$>81nkd)8ONm?vDM4>98r`R_3iZdl=lnNI(sJmH$M$Cx(_J6tow%&zw6+XhOVdi^SVbr=@tYU_sJcqdO6IPDz)UJ!YBuwWs?}8bf=0j$@PHNY4YNFl#ku-CiL3pHFpKE-&PQ4P zA+0oMpd>433^HDsUJoex=$ut^D4TAv@xPbnqeNU(CEIJv0e zz#$ecFrVO?^0d;IsICB8ygCP`JuuDn?hXWC)iYgX@jh`W|&QH z$OB)YykRz@fw&IkA7L{AssUyJgcGbLXa}r=0pG!Z|6m}8n4OJ^)e<>FGzERkd4$?x zF13ZIzhBn>ELru&jH(mw^PL8e^-r*~gSM>fISF=dCE=hu0{%cwg4GSO3dt%mC0Kn% zGKxPh+22!GZqah%1j{vwPO^<$pKsK?&GKeVInEfw;2m}$jTHTC{Tw9<53_uhN58j_ z7%iyh;UFJo4fVdD56$h>9}Jj>7}p1xVbtZgEuKrr3j;<+S;zU0vPFm3c|o3F=R*_{ zY;n+jlwFWuOGqvfY^kW&va*Ba2Lr~zfR|ctIj~j?vT|f<4El7h7UaegR#|z#2zXJa zmBxc^eF~U)l1(%WYl(J(1+)el$GA_XxQDjz0N0V*di^|=6ky_CXJJ0#dR1wHUFe`J z&>Iv=p&H>{s!r2GDtTd+WTN0CFLIMh)tJvm?j;oowmM8>ufah)Utg>ccMux>CCL?p zxTZP0u6Fi)thn|FGdHh0#&T+h+1f*=UwvdYb#n9UBWzv05j03f)^7?D+(3CQIz%cb zXy}Mbc60&LPcqK+3%I@{aDq+72w&=|jhUo2r1DZKBPT5N#l|EBooR(4(=wH5Gi3s! z%Luilu#z1>!3Y;!eBdwzsjm;SnG|6TvrCp$fo$j~+qC&Hc4>o`RIE3_HittrLboIf z@sd*a6LGw)Fjl*+7%N%R8}*ecxkM$~)G}LzL|%5_L4~zy99RtZ3Kdqh!YT|){d@WS zjFVzB^aoS;_v9}j9DTLZw)#V)REz0%AJM*IN#Rp=1vF$5iddL67032q&xxJhmu3RF^o+pJ;+D6be5K`2$* zTSvM(kcTST4r-A?o#0k+L{gE%0S1DTSgjJPvn8IV63j!Dl<=Ow?PXoF*N*-v86Qk-DI3%kJ9G}7B^~XpssNT{8?(imxR#<*BmDO zfWWnfMvQwCMyD9~YxQQ8dojxq`f8f!RIUps7yjALY3Pb2==x<< z6_hzpm0<5Z#LPjKL!tLA8$A6=Rg#PkQ-$9!@;MRPkaYrEkU4>A`bGLig?JMwJlM%Q zp-gL@C01$d7tu(5KiH-*R0MXGk_oh0^nGq6=KKV^!FVuS33I|A+#4HAF|U0fWFBD$ z>v@n1gD$~7NLEKpki#1I5R9ao(q?}nH|GQ^p$?sHaJ>bST&sSu;%g`MhfTk0Fh>@ydV^^pmAQTH(V1eu(8s>?PL|c2c%-Xce+V zJH;9_1EpS~Z&K?2*JO1}C$r0$~H>k|hUfp2%s)19Cn<0mbHp@S!9Fv@?hF=%iXUZn z-I!o^gJ7^I!S2}v_}NgtKBulIR6uY-z*kos@Ye;va9t4G)=exu%&rB0B>*8}sw5Oj zuzOML=jtaRvJ9X+9Gny?3zeh7L@`vN&*_k4Lm>o-wv3|!WK00xC}~@$OrMirpQjQj z0i|vWmCKlkwUC^WP{|Q?UwtSTLPHEC*!>AMSWiaX7ZU7?_2r>FeQtt1P+t)&4ORrp zK@vm~?EmVkf>kN&2IBj{U?@<0Fc3Hx2p$Yf6vjNo7CW)D5(!pe!7(*BH6`OzYWs(R zq2N?nKY*7n(E{&o79f7{@qwZ*_k1M59v-B{01X)+f}-d%Rm{wEo!qEl>{Auuj`o7Em?zT?oy`sMl+ z?%G_gysdC)nK8_saMtE+qhUPhU~JPP3S)*^-xrb=dg>6Fv=2+``D+iS>Uh$vxEHSS zoTp}nsWQ(b`g!^g%@f1y8Cv2SPqNm{dbs6bodRi8CKK$?ql(H_n(4sXLRsL~*VY_k zVg3BFny2n%6KcxlhwsO1;k&`34W9ld#oi-KpQf3?{O>_@%WT$ zY3s_;-V8JQo1{f2J4>ydF4pJezmUoLum)NiuDKs83#i?qZwVz$C1UY&Y;pNvVaa(8 zv-)!5ZYNw3ZZJYdawUSWiKyB4pMF`=c~+?J*)^hGIYjH*&4Z`EoVqafpI|w(UOK_@ zxgH~^(aho@xyd=s>>PfaVRpRI<#@j8hH_!Z6zIZ^DGFmck@nAbhS_szi>We#yWIS< zRJ#;Yj_)3(Nfymh_}1wIs(svS69{&>*=kg_pjfng?|_<~Xx)8%X2t-)Zg(~>R;G=m z)@P2%(WCb&jCQIKR>bRMgFTN13rpF+1>_fz`& zSy;mi{c{Tbg8qI1UW%UsQ%*8G`4NvibF* zG6Lt*7^1o3$prgNQtpV~e7_iS4#`DifPv(U(Mg8If*-_wJCf2>dcX2(HIqE%(C>uu zIx}F@abdY!RqnZmg#!k`X-C-$G||4;U{z=#GE;sRqCWq9or#}6)bT;Kpd4ii+&R#* z7V-uv0$iZ~F=Wx?NupR(5imu}pF|8l%3j(GhudT9&krL;K!G72Cc>9dQclQEH6Q~?)Yysm z1Fe}*|5U!*W~*lQNwz@KXy}xZ9;LCLGs}O9Ep^mte2JF7RQn$U4^McgPC8dvsk4NY zs@Pf=uAsU6FGtxc^!1Mhqas8+|5LzM9f#^}eUIzCk&4qN*6+jYHAlDZbx~$1fiI^1 ze*IZEI1=n%L+o{YrYXjJ3hvYQyZTB?@eB;Je<#>~9O75ISYIGH!@tVCPhp`Ng7I|~ zwZt~f=5N$tPXBM6S2#;NaF)PN#wqg6I#Wa%sgZBhVGT>R@Bf4gtw;{Mw*zKJ!04zm zj4e7USJUX%ForeHrnFI(EpavD7@H_GKdm9~c|`N-dO|bPYFS0_2K`1w?&wNvJvOtmtv)2q=Z3M3JmtLgJG*A_Hx?J!3i=Wv7K{`eoz)@ zY#>I8u#r<8ybv|c|`)RS@r_qus_i-in!ke5jZY=1cH|y*` zPQdO!_>GRb+<^BGn;Gzx26FFKOS=PgKEi-cx*U4T?kMe8G{`0!b>8&oKyJq(Q2Foq zMqQ3UWQo#JLAEYe#Mk8!)q7ZOSs?EoXW5t&$T`ePse^OXZi<$R=E@_98rJf4;)hDO zFDu`9=>uNGzEziB4(fR{uJheO?0|h(too%Z9SUwCQKrZ>WcyeF;}F@Ukl(rjLr8F4 zAu&<_j`9!)6a)$rTA{-w;&Poh{0?hHfgD_CT+95yWFCAiUg#2Yg z?7yf|LC9Y|#NG_~1I7@0OV~RW?48fjn1|$OzQbgViqHf}NGq0I`>Ko$3^LW%ucFwG zgsCQZs-3PUYfJ=Bo)(r~z;M!xWZFBYl@-`hXrc|>PVgg;(1Nr?LMc-Pip9tXEXhEb z2$a-z4df1MC3;;Rs=Nd}yD&Vgg&Oih`Nvo}wsr!?Sb(A?p%Ha#SwdHo27Jg|;2+jV zGz!`E*AoS5qaZ3)7%s?>@ft&Xp6jX5>RHWcb`cEBC32}@XwcS-tg3$&G$k5L)qtMtz21y zP_Q?-SRWwsf^2oxHmq|7dQeB9Tbz!RT z#M%rhOcd{AE~Ctt6bqbrG!0YJZyB|xDpGptqb^pj+E7}Xy{EEuh>^{5@@DOpLaa*R z7JQFm5K+R%+_qe*+H!W5E}_wmHkD=zE5UDeGw9Hq zHGpVYo?kPpRl1&3Wo9g;BDjwlfXpgyy-^u7gvAlA8Pbd-Z6DK%sp`gZxF~OR4e$fh z?%3atD7#}xMMA4uCOl!rBUK4)3Qd<@S*ni9+sAxM<36v$;Ga@^*6`sXPDh5Ka9ur}jaCblhxwVBUm+v6))05S>pVkk#4j<_f%Et)40{a^y%gPmH9d`GJ8`Zjej*^F}8nNfA-VY54Qe}UdRpeA+Puv*k7@Ip$#k1Ntes7k9ezfr z=lf~0k#x>>&vK1Ir#vfxFX&$!FC+Pqk%Fvr9?<`9yo{WcmB54gL*pe-mX*Ml^e;2V z6)g!&9fg2bRK&BD}hJ#uP|rVQxXV|LckPkaM`L>R$@o=A?EmXC9(2R zh-J?|tUuklyvQ;ydSAaW+Xir@&9<)43v)gHVzfO@X@>dNj_z@Al<#s>ke%8$-7!86j?T(X<6G`99|w)H>@<$KBYm-=Q9UXR zuTv=_JFREk@jecIre>${ZFk6DsA$X|m4@l?!`;@lvQzzzJND02R4YcMItq#B+~L1k zkvM1c%DJ2sBw4G>PWQX69vDZXP0vo_d#)-N2Mu2~8Vpa!D;f*c{u51JTHRrpt^SxW zyk9Hu;e!Ih+Jdk)q}8VpvPvoZeOGskgPFP6EA#_beT;)fMRpoL)PLkw$P_eYWZD#3 z1N|+ezeV(SKK(5o)-FgI>Pi*;Q2+562`!<&rS!Lq{+83Qdt# zW|bLtD2cy)t$2p!>CaLF}iJnD%Wl?+WR6K(90PI<7TX zT7yqGf^E6pS=C?#%)?r%uil@~E-coXBK0a|4SfggTR+2E6WUrFn!^gv9Au3m`#dMR zI6#YCuZaL9As<3Mv0iJg;{kr?^y7o9uvlA{&^8R7{!&BU;OQ^Qyc==4#%nwX%7>X5 zKq#TLJb-EDaVGumI9C*=zb|L}-GslRTI=S=w2K?^LU}>|quM1mCbUhVyd&DBg&G_L z)1=oLF`I7z+;R)?bcp2}b=<(-n@KQvz9tNmtvF$$;jGpmJMXA=8PUJop??L@Zv)b{ z30mYH77{cjfamQZXd&26fPHrG)I$OOAdQsk*p2Li+C{2^8d$+;R?7U!`^EL20nDfW zGbWBPPdxiVuK(P~_02iKmT>(Sp5v@^n%{U^Y(D=||CJJvIm&So6bJYWdFb9BG%6EX z$Af97)U*?{>c*)gcjtetT8+&Mt5dvkM?G*%NQCDuwZkuWcypzPAWDl=Eooc(m{DX+ zApVvR<=^PPbxkL^sw}6Gx8oQq4%ggAZT+Oyxfz=zJ8&jJ>rAuIO9;N8zsMZ-lB`w1 z>>AQ8J3~e-P77->XZ8L&_oUtX2@{gh7OtQXaHZqEIK-Az)q8_pgysjmI2?m>W18;} z3*mRyAzI}J`LNb?m^zChx`Fg~y?5~R^Ke&Gh*NSo;+R`Po9Ig(kt zryPFo?g{0J(U)Q*{lRqyT%wdzgF0$#9npFU3hohu7>6C;_-Lm3_KNkDHc1Tc8hu#n zg9&lPuy$3lKmANEI>9D#{Q~T${=s!C+^yPUfLfwoj42$~t8mYUtFqSB9?{|umxMOp zxOA2U%?cWQ=x9qH(smmUX83p}D(_By$O^mXpK*WTef8$x=_ga9EyOHe!6{Zq?2#l3 zukd9{G)C=32!GW7q?p~H8suEnAfCg*=U~z2bUmT%SzGfkJn?HuhkClB0Q6-n9_#!bE?{Wq8N zdzE}fDBr#%$#Wl{WmP2K`_Y;Oq9XJ`iu&q@WpBUdVd{wUmmSrvA!Tx{Iti~G!0u%@ z@AkuDi$kD^qgo{J*QK7U(NC~ygP&wGXf^$JV&`R-oy$n#)V+ZWc2c>xKBIIbfrDY~ zz49ZYM4>V&pfX-@OIoigK3`~l>L~4f4~s$}W$J|gItn+Ys3N^K!8X?>>E!|b~Wt=4hBz2;&RDGU?6uRH0l zyXi}n2CKo$p|;N)A2f|2<(-C1uBu6}f0EF=nG}D@S`5$Kzx02zY^M~CXg4_W_#an0 z)VQY<Hb z05esC7+%9vC~fML-vWJ6ICi7b*FWMr~eaPSNbIDZZHr(A!OX3oi43yQHigH zj3fue%s9n-n!MkXe*$OxchLAZxM7XbAcoT*KEt0CWmvn-VL8V|NxgwdbK<8QBDRq` zN;$VXD0%K!&8BuV{NtV~eYO($fHRim7Iv1wJONh@i~^%j<@`HE;uy)W5V5=T#d@7_ zRQoik`a=y~jLUj6bztTNO)>c#9z+nsQ+#1IUmP?a)$T~JCFM9x^OMfC(-=pEE zfn^M74>n&y8`5(e5hIwIAP>}Y-1GZ%%1th&m}e@9=P3f4sudFX9v0Y-+%l{k zk$erxOGl`FxWF18SBJ!grjg1S8hwb?Cm|lD!8aVVlGDdrR!DXVH-cmz!Qec_&M^!v z?>IJnS7BvE^U8c{*4wPwQ@_OUlPnk$UHREY(jiPIM>@PAuOl5{4iQda*9&&NVAm_y z^`^6%Hlv+j%ZMe&t5M>Xwm}Wjda=VwLPz5N7`9l&N5K1pC8RUprQ#DB_FTT&z{7@y z+CRQ2Wfej*vUP&lPyr!_y4FDDlI^eMYOv?D+m8ur^!8!x=pmd+qagL+6AdO2CcFR1 z2Ha*{b3}Uz3jmDWugRJ|O~yTOb5whV8szIR{i#8|K^%RPmi5}V=)$@c>!N0S~GPZ3UyU-|i z+juecAU4e3JgPlU&GUUFFF&B>`61-tNB<|Y{FX^yxgqQrCT<^Xhc~fFx=tQb1L!b& zXcWDKF;$#7rZx?-YiUuc{rE75^dHrJLRo*Rvi^*+{v54CYK$`c;t(r_3fp~%ogLy> z(G9XCGLX=IS#Jd`bY)4u^Z^ z9Bxd`sq^?xvU%K~5m-5+S)A(8r23{Qc(RO_M|$3drZ%eFwwy{MFNbKoEhj@bhXIT5AyJJ}z zGSzM=s`jT2!1FtXwDXT@FA?28H&|kF^I>xP9P^RT4>9V8aEO}cFI$GRzc%>BD3S_m z@P~Xj%y$yt6u@Z$8ZEs377qD_wZDf8d?C_ZFXQ8tkf{;Plm;a;(zZ^qPTWZ`rf_4b zI7r<>4K~eHAAh6GN}GY_f2OqCs{pTo|HA-x{J*zb$O3^o zhqTwzn`=C^E?_!p-NTLPves1J8E$>^Xb53)_xPP@eWsgsH4QDO(#xC$TC3C`|3dx4 z_pzxVFHWU+h0X}#H`j4=SY(lC{|+1CUS|&OZPAn)2xnsaCxuTNtMBt*U>=7P)u?v) zSg!WXYaKqOXC|0kzzIj553q-RczAJ*Q^h{Flz<4L~rnRPZ<{?uh*E6?&% zPvGC#br;WG(I(Zh3PMPeZGc0~39UoY2ibSh<~35%6ESPfcF843=t1ri&ir(rGqHr_ z+@aTD;des$u7AdF=LLyvgaI(y+g1tOwgry-)q~pZQHhO+qP}nwr$(CZQuT4 z{)n0ASxnEW_E~39l_&E&mhXU*+ZXL?CCU(Yp6V{6Kh=!d@)z0(wa9A4Pusl@?&71| zpMd3faSZ^Of?dtCadeDIX#HEco=C3hRTXnkU{Cf4sBDIC+L)6IXV}HptiIF^bIWw9 z>~QSR+hez`Hqkg0tfQyZtf5IY!`&srogj2I$HnQm!)Xyz4M=c+so2WVlP_xhdCh{B zi`DSPR18wp*5|A06BA@gfE*M!F3Ab>IU_H6?{W<>owYfpUg;!d%0Fn&F*blowm9dU zuwZ7Sj${eL@t@wMmrfD!xhym!8F7v>-fta_!$u459IDu=)c^<{I86JRSlB7M)W9@J;Q-3)Uh7Rr#AWAu(E zyZylLOKAIRp%RWM)rBxLUDL zaCbLTM8bO$CJ@INnQjv!*GjJpTaNNVFfBHdg+t2bd)89fSK7HA`=14 zRTPvy7bgb?jFVk*8~9JUlxa}0C=qiJJxJNi0?C&5ag3QpIv1eR<@#68RQ@4BjTYrf zWLZ>*M~#kgm&8vj8@c0hUpfcMoQMLYG|sCROJ?M2Ctvb5XOWtY0ni@Q(K;uw>-14M5eF3>5u@ID--@H;l)7=cC7Rxw${x=#X; z(gX^!WKn<@G3leKX#4Gd63B5N2>u5|q+E1pSy9qe;uwa7{NXyzi2fPK3gt#Q90g?= zX_|&+yT{a|Z@Li5oy<(Iy&;fOnHO*U_MisCDk4+wAAeG`FI9zZ64h&rsJ>frkC)I! zw_~v9F@Qj2q|x?!vLR4LYoER6K52U*2PwvoiY}qN!IlgXP>TeuE@um$z(N7p)|KyR|s>)k)r*k zS4jBOqi%tz_Aw$&87KQ3c{M|>@s9hy064E~q{C5BG?wzvYHpEQI4{fP3&25#*`m2o z8%Yke`kX`8tcURKf2x&mRwWi=2u}Pv<_;!<$f{!+2h?tAGVN;X68~|R%NoY#0e21N zti!@o(-tKU2b{@E9uKPL9hyuHIBwZOMF>3mvu3eto6#2LMr?N+#Q(Xt-8N&UpM)1GchYV~3_AJ8B3qV-+Ba_=9d(`)=#$Il9t3f{bP zh>-sh8mF_}hJ^OGL5YR(94sa!qIcfp2W9sI%w07OW^|`S z-0Y+B9-K}=BOr_9A9Sj1hrg&Gs%_Tx2R!SB{ekwn9Pz*po*g3S#Q|Ug*oFo8aMzKp zxqdN#i~VR=L&$Ty7@zA#df3<3&_Gc+VmxwGs}M&EQ&B*{f9e{jIR9_^bnrdA6L}}+ zaDb4)vR^zf{HnH#azzbA{7D6Z4n=qcHiOtJ%047naOl63he41N0>m_t+YI!oVKGfo2da(HZjxmzHM0JYA{pmtd)|mPSpOJWiik)GX8| zsRLwCopZ!)wJ0{z2+FELkqEN~e5!OEuVJSFpXp4Qr}aZ)pwmO1T9oxoX37p3YvJu6 z-ZLuJ%E2pf$tlZ+>pd1@HT-=IL+Jm1uAOsv3lzr+At_Sr z$E7K$XhfxMfGQu7h-++eE{UGdGdI0rQ-2|Rw7xamP@DZ{BU^GdeOQmwP{#@>%=01i zM{1~(R5%ML2aFC#9PL3niiaJ-FLt`*s5a??L~P@!;voL)1t4}i>baKm;_+xRzNspLKqTB&`j1x- zbDDKlNeF<~@fU{?xNsV!xu$2GzlV=P)uED9h;p;Yzp~m-xXEz`{umFyGuH;uoO7MV zB&AK;fk^CTuW(pf2I`eVPoj>v`Ru?N^kB0SI-+Tnr#zRz+h0m^5;`QjktbpHdJCEM zJ>AZ+`=Ovu<{A4IB;~Wx9<-Z#E^Uj}c&$PJ$eAJL&h&`Tp^r=O8A?%VGRBgH9;EK)=CdeA)=b zx3OitrWafzmhHc8ImsUmrWqu5O@~U=+Dzt~mBp}&!@*rs?UOAoFfK-l*y>oX(Cn}F zeF$poJd>+E#2y>#OHh@-?dUbiFGU`8@x1z_8t=dgh3*%=5fd zT0vV8Mm{CjZo`Ny)}34$l`~-@*}{7iNKN%^ubpC>(7m?JQgV8V>)ZV(xJ6V{{`3si z07=zU_U3q;ZhM~AGjet|Fc@<%Toe5z%-9qRb7;1#8zJ&W>PU)FgazhpWE6VgqG(d? zgp4I5g8lOg4-#Tbh3KIvBSUixgu~wjZE8vgS2x{+Y+AT0VIIXE&KK=Dw#?YctNw96t{6njHpf+mourh1 zx*9%Lr1&Ai@g6jmZD!k^*EuLZw?s12n^^v>5jW8=ZAwVmjJ>V}_yC@)>XRJ{oy zWeAq;GsL&YbKuuf3~xDVFCi7G^@D@G`N&-$eh>~ zmY&^^+c->+Izu^rb=Es?oXM;iOmH@Jo{_g4hgqiLcMbpU;2Ve)*zV55M|Dmi_L((@ zW056mY%mCLf8d()K9YvdW|1ZRGmwd-l+)st3NaJFww;8_YC+368@*o(p(zv;tR z(Id&Li_5b-J3Pxn7{k{8(z*@OGLnqr0;956&K;tlEZ?1G<0@9aS@-m;{7GnA zS<6LR-Ku|eTtSK`4K_d3k*mN3k8N2630_nyfXX%O7R=ziq(d#j@jy^*Vjig--^Io} z41PRLMl^2u7D@~IX-Nm0$;w{7nV1)82xdo|#z~osDO-TQ7Is*yua}$fSwz!825-3~ zIPW$d*gF>tmvsZ=OEmV659(cZz$<-hw#P}w-Hq_O?DIa)=}F>#FVeOn?~cZewcqdY z*0w_}asVOd2f4zsG-o~H0MF$5wVsN5t#aNfTSfsu9ha#{mwj-bCF>b#Xy=thTqM71 znmnXPwRrN}kEMCLmii-j$Tqb#Nk|~N2T)SHI#U=zt^$K{oPIIaU$_W9sen0`3pOOy zRpFdm!F-#u^Hw81@00{_feDeLqmQ6FyBjTV(!VF$+Bsq0M?1zTVR6sQ)l*`pIz$;=OaRfUG{X|2D*YNzZv7e{YRo z7a_Z2vFR=EO%BNBA#Ihp1n~+M(sZr5UYF$O`0`?k2#75=mSlSV0Bhg+eNB~Z{QNB9 z-qi(}24oD@xQwngLNg1_goy;eUPCqcS1-DrtzTI?}FtLcd#b+)B5 zo3x-gKs&dQF-d4!-MFGa(0ko0l}k^&S}Ab;n>6gBLF?Tu+bx^)!+A zZi7*XHVb^1z8GkYfyHs21iXk@R z)^5@ewfb&v}++BUiczPi~tkaU{UdlCK!WsMDG`@Y|7LB@wRalaE_8wpY3(-eRim z95S88xboO|S=;Bro=D{=n4>)UU!P{HDvhbuVHq<=qNdCV&$0nAug8iq?=CXkCE8PV z2F&<^Hx)~Gm>3~!79V_uGbO@Ze1NZR^R??I1k%dbvJKe-82jasK#)Tt ztNA$@YL#q};iT9~YE^_pzs*~@tAJzG4YD)g750mlsTQvFRGlT2snB+XT(9`d zd6FHZ^+wllHAfGLFGmHS@)@ej%N2Z~!;C}tgmzl|^ooA9rn3=dq)E_NIb6@L`A(JU z)?-6=SZatC)~Kitz;bM8DTpz5LH8v0>00OAHFH61_RV=cs)RR>l4C0gH%38d< z|D94iupR=wT6)Idov^a>O1?Q$VdT&bFVxkwm0Gl^WivsiIz5P!WKg zv3IP@NV*@Kgz5+xT{&9z^V{2uGTT zb=o!z+@YURWA5gmdm1(xLa43xPbQX}Xn3F*z1e71^I{!u;C5Ym5hp)Bh8S&UA`uap z8E8Y_i|vsg`wC*~xDACwk(zx_7T7=QCC|YljnA1oy$@M<}ea zkZDhY=wu=aDw&Q(G+o$`*Dtc`S6^XoncIsL-oI=063X3acY4louFEHsSB`DErWUtIu9 zXU&>cX3m^j_MMx)_}SEAa<9g&Io7UBJQp*bCB8ubzql_JU-E3~+>fsfK}sfXk1i=Z zhchXilU0wr4GeXZTIc4wG_&YaT~!ZN)78CV$?Xbn^~@YuE{SifLR^72;W=DoV03Y3dg9 zXn*+ps$VMnrp>F4gqP)s5Akuuz&~FS-@f5{N=Hz$n#D>qA4R^VNZJ>@dVVE+V|Qck zKR~{G19y0FeTn@k)EtRVh>hZvfAqfM9jJEBRW+X?7L@K&paC!Eey5YOj;~dXvaI*-(;BQO%6PxBiQY^ z8-LtV!DJy5m~f4$a2%{v{HC_c?s zc!ofER}{pyOAAJN1eI41Hw3>d?NFw*A54x8tqpUP{>zt; zSVt|c+rmy5i6)79+6K>}NHsJgzN5Saovp`xU?BZeC18(WJ7?;U%sH;K{WWugag5}p zGB;Dw`?;E@=UIDpcE9N?K06AQQ-A7e6RK|yspXwvr~b}T=2BDZ+Yc6IFGI}3iaesq zCQB}9VJABpmQZS~CreML34HuZs#3pX9K#8}&S~sS)T@XHe3Xl_3aY3VR-v1%)TJ_7$TQVdDx=Q- z{9)I-u~U&@Zqcdc@*ox@){|qd8H=rr{eqKGZdr@%S}#0UdG<=}1U{J;p{zZr=G0u) zaxBAr#C?jL-j&0Y!H+P?CrwjLn4*mSz$iIBnmiJOAD?;>aHA5qTgo-{1`z&`aM{z} zKIu4Sq*NUyP$DJ!xR0~!LXdJds1VHuVGasb4#68)or5IsN6m;>A`l48E1<=FS}E9; z>qF(3(dJ8}KUsKYC!n?>&9)@D7ggs6rL`iOYKAL>_ltL19aGS6!u#koyDBj{ZT9fe zR~7w$Y|<{!*;2V2o^v6RnCce}(EYn_m2ND`L9Q*+Ky++~wbWdcQ`<3+?6h`(E~8ox z`*uMY=rAHv)-Db$ysn+%p!yDNb<{3+Z31UA0b@XscL|hMgX zy;(5}dX>O&_zA1&&pgu94_?Pq+k4g^+uXDcTw73V)ie)Wb5LyY8U}5`SZR3XM*eo? zw#Mmh%P6kUJb%(Waioaoe&x(?S2MzjW-P^vn;wV`abgu|e-CL_YUp|-<);JWG5$Dl zgG@F9z}yXxQ2RL1Xg)=HM}st<82NMlneS$*An;tCZ8xk{H>%c@L)^eeq-qL>Q4uuT6CNz8TEv7rQ*E^h#Y7 zpOO^dLJBE_)kLfn(cPN^ByJLb@{ELDlt{CaUrrn5>$eSc@Y6Q(yZA7}HqpsC8f}bL z(T>E#ikt-t%4%)Tr>==Mlu4GvMiPuL`0K&qowiv6QEia}0)~)Dx*F+vt#B#CaRTYB zckp0m>U^GYsp=`jsh_vtgEA8gc0u08&TS}AdIm}ujIyLBl;{p=RFOT_f)Sv`32lFD zuGyK`YiIGz&W)W7o92%3=k390<3ZePi@!385*v*OZ)K1e2`e&*+D9QfWd4%3Tl4lx zahKNplu3~x9z;ik2s4ZX^b6FFk=p=zDmox`qxe)GfWywoIJnc6u`f4awF-yoxGkJ% zuaNQRJJEZi($J#wRt@PUdH3pcD-c+W+Gb@jd?~53&pAABvz@iC??gsyo-YP|$xmN& z_N+w>B>cN!&OFsluv*~sD^0Y3jdX=BSqc#=An7Bfd`vI@xO6Vzj1Q57Yho-$iwjL= zs#1V>>1Mi2IOce_VSE5IIwD29y?`1stQs?_>QZL@gZqE5NFzkzl1}QYS%`oD0Oddc z0A&B4SR^}ZK?6r(hW`O0g(&IBAqgP!2wA1=*p}pv$QA3SEUKb?^CN~cr1CP$v-pmz zmE<%7T}dx#Xg+MP>3ySRrN8qBroZpQ-HTz|RbzPol5&up?e@5x?s7Ht{e6Ff?DMMH zw6|a4hgq@K8jTK^Y|}Gam7AN-&UY7Ng-V9nYP8!FSQ{h|AiW0D(lAWhR0ymc-S9uN zygidYixw`O6ue$ag2??0V1m>^Q4Eejmpo=HMA>01x~v>PZ{*T({j-DafW|nLn~fnl zoZhaQx4Uco{V_f%Dq%CZ%-Fq83BG2U7_A~a6rqAB0<)s#_9~q zlrkcG>7N%8q6)<=5XH$`5VXh`u#ubqd+A@xv<$LbwPt&>kc!jl7;u=YE-9Y3# zPM)Us8Wn(sD*JD&ArBMoLvsn5RE3q>=@QuD{=he{bTkvXH4#g23H6jP*}8&fhHY6K zS1SgNkh@jWa{Q=z-E+w-{WGsc%uNAK*eh*7@iByMTH{Ev_gK^Wn0(P{I^cj?o!5>w z6!!w%08#0O`pAKD1YN9>PU1>z?+3 zk4Svb*U*o!S`~JnQcPTIUHZ5KTrQehkaSS;u$fR9i37JFU7SmhAVF5RWmID$sD0Mr zkowwXtN}+8gYpHjWARx_XCTlbCUDzNiZ4=Ze-wydHVcYgtpHvvsq&a^qC9Wz)5_bE^bD?)%4aHk$(s4(zyl#`pKJ z_t)X^@x2xnajyB00xwhM2dfa^Yoe$JpX0}Hv8MeO8nx&6P^bN!CDC{ExhL}pSL=sI znDv{a^*1u&m-Wm?P+9tC;{JC=6g4iUK$z>5HIR22!KB?iG@?N}`(chp_pszaD=HWm z+#g&{9};IpB;FK~7X{!Zqogi?k8@0jJZR4@>}%k(HV2}rLq*u#%t%}*%uHM}c<~Sv*9*<) z+t!xlaovzM)xH5)8D+}-P;~T?l-GvCP3=AwlQy%xD{-yna3e)~d)la~qa~Cqv%?Dt z?e3s3WNekO+l63Za{X(>Q)!!W-1hc%7B#bzGjhaQA)aq7YbV5WHOH;>7-|d6n5)Z? zXXHBYFn1Tk%Q53-WA`wy)VsF|V+s{2@))Y)rsEcNW2`kpt(mHus1M8R6(#Z`F#iJ0 zdPDd8d?Vp%7`FqMOE5!l%+*aOUx|$xcjRFUWTrjQ=|<swQrN>KVO#4xywp*=RMm~qj(Y7_3{SXK$+gy)M{6g{^z=F7zo!HrI3l0w!SF5W@ zfxUhc1#5aCjPdn$!p-)^YF8T+R7~e-M?U|#uJ#VeO*NDp))X`^W#Rs_&5dyf!g1Bn zf;}1h<<(5<2`=!v)n#T@+jI|e`^$&6+nu2~FL>BhHLbJtPUZ}K8Su>LE>xRtzk)h* zc`rWFkR=gpPoPgca8D7%-r5e;0~iT4kGe6tQ=NEXjZKGb%+urpdI_6$DmLOFP?f70 zMioX{D7l)r>biO#!4`zZ_N=r_pd(t5&PDd3rcWw&8V27jEgBcn+6?b}XAFfi$Qp=3 zmDA3MwZLLaM;BWkLEjl8W6c{Nj=4Tb>6t45c8z3Sr>yN?ze=#vR6(Oyp~P3-2y z>woF9S1+Dc4RW`MJfZSRn=4=-wwIGGx6B~Yff;wYd;dJs4_TGy6+doaHVT(p#0jR( zhR6?WbZ{?N7uSL;`dxmx;F;9A>3T9XWlw6jZhjZ?(b27Zh zfCp$}F<;hpAVSj(liG4`K)>RnE_Ojok6_^nciF0L8@JvU1+q^uJOgC#!=l)JF{GmI~hZ)+ZwCes(NY-V$8L*m}4&O zeZIR8gFm`g1z{og7P6**yzSxgW{uk}o}Sxf@`~j)mZf|GOLb!1SZ_*sSbO4j?UumK zgIImSdCAW!8@4uCZJI|+J=N!zr{00EVjpGm@oyV1{%u+<1vR7b`fzirIwPCX9MZUL z9pXCF$=sdz^ySlS7Ut--iGclVqiS9F%h)+oCE>P;gyOP_tfFoWyJqd?|3=}no`Prn z2LCRja=E+3Mtz;9=TjUZZKHB|SfD}fw@sn$5ZXoII`qlR$@fv%?-crjzHS({trM*{ zj-JTSIGqR6;OHGSprnD|<<_*4SdoNoPLlczA$9 zk3COa(p_sSh-jID>i$E0G<>Ly+LUZDeh|S?f_R*~wq3_LnH^<$R)i^rfN(Qr#PxyK z(Q!=clC-UwL_yd)Wz@|-n?r^4{;7usV4IMsm*)w&W}P->+Kg6B0bMmt@vH8xGjbaX zq%9u`Y{BUsZmT-__X;?}2##VN)uRjE(GVZx^?IkU56H!g-0|&-puTc_#mEfqiVTV4#r4GF#ZhkgfZA-^1J{qrre`dEh>F7n z``z^R=Hh(7eMad2{w-fy9t7&jWW5`}Z|7S=#Yi=Z0Cq!8OBc}_Tf=Xlh;gw>IJUmh zCdCx9lOiSE-u(3iPm#ioNq!D6o%k=^IrvwbbZcRxdU}9L#pTS)Y3sEUkQnEqj3dc?E>uU<6C9$^k=UorY4qokMTjmX4ka{a~tqDv6P| ziW;R|klIeo<2g4~o2{u}DTaW~rgR$~QTYITYzJNe7)URTh|$br%2?U96mbyah{Bv= zfV^{Rtg9If8$|gUL^;3igJ-6Tv$ZLy{aLlc@WCBbcPRY%#W-G%`DXl<_PEP}^A{z) zN$n7=0JZE6TXF)^A+8#4`Nhj6hFogZzQ1v|%nDey@i)GjY{@qo>XW{Ya9(Ww&s^gV z;h9f$^^g5C4}C>1oRyG2Igb=kUJ6qG^lL)-n*))og7D(WC&?Qn$e7rNF5WL5P=Z0+ zbdgOQ6>Y%X1%$`h-;|UsFQ0z%3zQ<;JNJ5K0tq=HldQrqz7o_tGZW3|G{~}-S%1R! z*`sndn_GhAs&KVp-c)bdcE=i6lkKa>^y<`qrE^2rMr0uzv``?~lzDs8{C&}P438-^ zZc$|-{ows<4y&;0^=qVm+%7K#jxN)@9pB0i z@#8KI|uUG@VG2J83)RDm|yw;*Qw`?QJXcZ!JNMuJM)2mxJ zO9dI@pBbQj{yrX}d5r!bR>i*RGpR!?G{LMA%;@aAi&~FNipr@*rv9Q5LA98B)y9Jd z{I+n;3yqYy;m*W4*0uzFitT^ja&z-W5kI`iVj*rx!2hx!9a0DToeLzwWH=EyWnM8JkN8k|WW-oP zwE_tGK5Gnr*LX(|k^G0>Q%6~Po!VpBpn;IISgf#O*DeR96Mso?+LntBtVNjhC~Gg? zv1#nHZlp*!bfrATh071QMPwwI+7!|GM+@UR;lLggs(PUG>gFPtyiChg3;$U*-vfxK zdm}oaVtGtnJ$}+Xn2U^%8&s0^848F`FB_A|`aXB83Cd+$D{<>MDUz8lEFw>a3TP$= zzOzOzZUIvo3t;jSnGfUFJplB#>vVCz;z(o{U&T6=vF6%C-zE=bL_>0vhdSZZ&V@_A z?2k*0T77VCSH{G3qT{oh|6<^*!Q&3X8##hGQN7Tp+aWaqcf>`jgu7{lg-ALj7LT*9UNALwyF&L9UsXh)}Vhf z+c;rux=t*G#k``C@0_2t(w(|Nr?cXft`Hwbhz&LtLF29hLxUS)vEh5#OrU3{CNP3t zfF7Z5k%6%9?px`zgt7{mdEi}3Z$6VGN=xU#4PzI&4Un4T7P&P=7%g1@OChq&G$?OD zFk-L!7g+-aeg)-^V#(g)%fy>?nte##7_|0z)A$*w&y5c9l7&wV56Z%$TeWX9ZRW%y z0mDKan^SL}0>drn+#(V!&6|%|_lg6zase_kUPzWDhGE7n5=T2sRZRC6PnlvmIpUzb zs7F-VhNF&IgT=Z<(AfwzmK76~RAFWmHP<-ndI=esR4eMNH!xakHFRu^6Cx%t#l+@d z6qXdB7B~aO-U!>r8WPg3}bqZv+x8KFmY+F$JzyT3$n6HZ$I_*MFfrE{9t>u!FZ-s*nydJ< zv0^e!MT;tGLhr%vF!LuAH=}PqNXH4FBn6d&S%{B8hy4Tfl||lQr8~+e_=VPZ5bnF; zDc-~uwn4Xz9_HOW}t4#$x zY~aVv`f=EeV%5q72pbb>HcT2zt)g&`1%mfGq|Pw3oZ;yFFH!NqMl)z5={ft9!*vq= zn4upQH=f`ao`nEg6SIyzkXGu8Q}|>QzGnLv^9%rbfw*>q-cMu$?{q@T5q25uvnbvT zI;O`6{iXpR%tW3PSE+ptXy8E$4`xQ`GH9#%krs)h&n1--p+>oZSk92(CzHx9T;=Bx z(o1_e&En4hyPz@{6{$Ch&6*UHJf41&fSUsKHpQY(`HEjLRE04&GPFIh^E&JwJ-q5p zcbuIozbIK1=zg#0Qg>+1C$mBaV8b3`MIbhpcTX|;3_o&4dU-;4?U0ID2~08*#-)JF zwP-2zn{6VlGL9b-I8;2_2 z4EoAk@sPy00Xpc6&MZ^6X)GXSEXYbMJk~Y6+M>P@l9j}MaIC<;DjiH&36^yek(LpP zvDGVg3Fc+kBixA4&k2c6D3d6&)7LZe7DuRDRAbAd5b5 zvq(^DsOXV{?87ykiTJ2ZP!HY?`8dEgkb*SrX{Q3Fm;obgN9%{%CLmnaIYT-JR)(JQ zlox782xOicDAC+##YltA%GW?@g-MM&HzbMpV7Y3I2O?0kpog{8=w?Yfn)Z7Pz}Fm+ zB_bL3hGU0|ff1yfH<@Qr(oRAzeijc3UQTJkLGgW5!t22M6g}(G3-zH1qXUfH0&a+qtTWc7I=;!R2!`ZAcmTN*Q%WNP!8$fiuw73l1U}pF~-;w4Gb{=S)bO&h#?6z z8~Ec=#5gIe?cU80`nSso+3-j{wI8y4uh-|gq@NXgaRzfe;QHe6qr3NXRpcewHhCW{ zL$T7hKydRXUFsE279ZZ%0A)%Vg>xkYq6X5Hpo<|!QKIIQ%9BK`>!rST3zBU)Ei0*U ztkAj!f^mzZm|1Y@Gj;b-Ivlz1AMsnuMPNbRs;4gdK~+EtVZ+Z%d^WH)7sve&s?CS4LoIaC}h-J*6()&va4{m+}zmQ3mc9;XD=`#bAr;keAdkk95O%tzMK86^aK}SN3SS zTj3(rGG(QwjT)u*;}I#?W9rXGs4UB8j`yRnDYPOyc%QX@u1EgF17NZfz^UCQ3;aNb zi^1=INNJUz`edWc^5IF3Can5_eeZCnfvq)ZMv7R4Ua+|`wgS|Q2}RquvQFFJ`n-^J zKvVAQS@3GY;?-Q3kptz$D=S}P3H8YilvoVrr+yIHakkFbA#4S2ihym34dqY-m!j%{ z7;vKp_1trSS158HOp7JYa&5H+vi7uK>HCzCTGgW_G$YxbW*8B`d?o5R=J73a6a)3m zuk_u8w^5*a z68k{Rv%dyTZwxS1MUw~GpRTBFzX`PR?x(XNH&-&8gKjHk$o z^&vqwplC!*ntFhPiwJPadsRMTs1!c7TV$JCkWSJa{>$1YtpX-nP=JL`k@Z%ZaWF4; z+;Kua8U>HGJRTHDKh$V>rmlg%n`fJyHxqkr`9j2hka7-nr8@hrNsOvqpi9*S9Oa{1 zXnXO7(k8!4AG3RMGg{!WLZDj?DvbCP7AhLhJ=G;v7)bwzv`uRi%GUEG#xCFR;lNlb z>zmPAG~>mmZG-+IoAWm9te0*Knz|S_^5_vkl!~?PSECw{al#9rQL-Gpr@_wA6d$EI z#L1Op$ETY0l|I}-=UpOO@g6X(2xpv{m5b>B8o)E;kg}^Wej8&H5%kPH=7^;&o*m2- zSYO6S5dhp=CgeXP6YCko>D;2cuBAD;DloId&OasPZYC;}oDmUte$x$5DaTjqP>4e3 zrM9$(NBvRyO1kau@gA6#&0!oP52sNAhV~ymX172_YAzuU=(vL5qyJIo&86$m!9IEq z&&o%NTWEUEkkcP6TT7kgj9O+?%t!ekP3*66af$r;25Yw{>~j;5u1jjtMPndz6drLT zzBfL?nzZ=4wtEu}=LkNdCE}IjRuzNNU2Em>wb>IsSvk>TA8|Hx5MNq-0EpifAH@Lg z0N668-l#Dv{Jv-p-iC*~(um;hBQA>L7D~TEt0FlgO4}*@bd>P>d~7Z(r(tpiT?*f` zCT)h(&rs|Of5m~nUn3USHQhEsr4mTesK1#fzcnSc)=KVvH=P^CVh(>k@rHwR^bA@) zYO)Z0Z^*ewm#hmwO|wSyJqHy7g^!ftdPO4|U{-wezobr*Jnf0J+T{(&eu%a*py|-3 zID8lzy+JtgGqYt+#MYofGmFOkvnBxmJbS-&!kIR9X?C$Gb=bKjU6ZZ* zKO9T1u+{7W2_oaE{GlzD!1j50qDAcHx<2%RR{ zSaqXQgW;WUgtL5}D`JxmBdT|Fw==T`?z${8z7i9jB2?3&)&2!J5p1EoqP~?fyd}Kh z-80`!4Mi`?=GC_0FaCLsPoRO$;x`}Y$bI1Ft7YSlb!7AKkC!K9e%{RT3!?0ahqV80 z>qn41K=s4jMLKgg=w-Jpnj$#9NAn&{^QDG*lsNag&NGW}61`7yZ!NvAwof0A#^*3z zj7A1#4DRX<`$1`xGvbFEU!4Pv<3`lu{_aE{G6+W8`HU|D7*6HeqQ?gJt(HfAmuY!a z?a+E^y_hUKvC~t3`l#l<9(_(mPPu)mV>qAS5-95FiUA?~;uU}uKiI@YF z)-2CxDRopGI_fXhx4dqnnlF6C0(5675z{xov`Oj+YI&JVG5gcjDrxvz2ZN#Nol2|4 zJNDPhKgYiSZ@~)lS~SO&&3)NOECaI^&$Xf~hg8-)Ud&pJESV1x(;F+hUk~*ISl|z! zw;T!krZnhtsPM#B#;@r4#-Tvg)ru#;EGRpG7*FX%T8=B>Q#a(4FID)B0vsSpSO5#QZMcmtY5P}z8^lmL zJP(q90hFj@Dh6Y0s@GVS)b9mcQKs)%GEU+q*E6*6Tjgc!87am>@^7VII1RbQ-p>3^ zd=)m4g|aKI@MDP)<4pp?PtE#G-Y+O4p9?=cQjnDL^TC_cM%=(p0+5^G^Tr)@1Kw0A98<_t zof_$f-__%Ak>nT<*)wWq|uRMj}^7Xc<`dq_YTFQPwAu%%lB2uyQL{PIE7n=KxDN|n|N4^fQ$7~l!U<{7-_W0dSLZK^r=Vp zX@zahsNwtY(sh%iPcLT5u9*@bkw3nF2;SE_S9bNI%P73*n_kS94ZGO#mn+^YJ~VjK z>g$44n0^!?llOGXhI)Ne&k;SBgI_{7St8717UGFAzXX-jKy)gQGHj953msBo{&T{p z%?Kbv!(rEU;%4>y2DQ~lYntyG7#ST`%+`K=B>=t%R5KKV0OrMgoi--lAXY zJU!rpB5$tPyH`QH#=NqtMuJAuPZ~!vcpc-&E{H3J{;2+-qZt6kORoZa;0|0>i z&o3tbKX}Cw&L%ehw^^JpB|FHE9QqevXhWfB=dJ?}1Tk2MnTE**1BDn3h4hvpyGh^@ zN>9X{7Lhg_0=F-OAxA}tDzENKEXg!f;U5>YYTD%91C#n*FAKZNoWdWAXB zVf2fM7A8d#R~uf$xQ9+GV^NTlZM&GVwc+jnAPm>K+RNVY3Y=xKwK8FqBZw_NQErHO zG1Cgv4|}E%Kgt+!n>YF+Jkyd^iV1ns9`YCLqiR^*D|0yEz+lo$i3DRZM_Wo|f-6@6 zqkuS*ISTTeZor!lO^Aqc!F4~eO;R=Jwqxw(Rd=BCj+}=`j~J*0q^V0k`xZ+54(3F! zI2eYL2D4EPJTh7JA@KiI{(q}f-fl1e0HL@50Hpsv%Kty6Y74rDe$vZJ&*^(&o!%xu zOgw3bA%~vrPt>dlZe(9`{^^%Oe%w+W% z?fN#4n;tBiTi$JQz~`>p@p-}_o>kUc*Y1ng-OsMqg;O2x12aJN=ntT`ZZNpFfInTN zzCWGDzk?}gStj-2YZeK0`yKw!myT?Nh&^@I6FVJ=n z{$RxS002H@1KMwKe>|qDyzu?tyvNzO_xCeZb?83*d%&hYlDnLfrKV`NRY@;8$a|^; zc;A_BJ;Um~`bD#hEjx_w*a%%i?~~^@!ta0o5s&yF?#=%F5)SGe*b!C-_Ceh@f?FNn zsRI5P2l_$W+l_w54S-kQalX?9si9{Z-SdH-VZ?I-Sy}p{p=;~kQ!e$2a^lnTgMvUq zSFc4CqhW$DN9+qVrTFvz_h3m6gQIaKYG&*uHzbfG+^i3Oi?jfx)3C5OJE0cMpgF{* zJ}3!FOQV0(PK)ebN4 zC`#y9&2b$!cETPAA1;d6V2*BJAjZ6^M+7!lP|&W8geS2a9US=MNT|D1UARn?9`?<^ zkvJZQ4a4?f6l@I%V#3ETssk<}#Nd&K9W^nKQ=o6?&)Ne%axvq|vCPMBT{0j~%*@VR zPO#Bj;;W6EREL*(6v2$fkQ@(|RXCJ@uVb=6obze)TM!_Rue(G7hWIP~UqDI=v7V7y zR-4nDteun`*F^r-$OV1F5FTzS3^KOpruCJjwWZ~aVuE{a{s zx(LwYfI4&H+$E1i6_(ifqA`XaH7d>n?B|zB@Z$wdk$%0|T2ciP40sfknRAWX25jOI zHVonw30gqye%gD`fGvk~EVu{}4uKvF8VVhOb6EznQ+)8J%H$@YKdqrZ2Zt1u9L^vXZhD6*BNs33i zvDh88qGYVd_dzRkgLri2i?I+!#^~Mc63?EcHlJ0dxLhZLCN;4#Um~O z9^*R~|JgUs{ZnhMs+z0TniFv7GSI0!fOIdlD#a9}#~?rK$k=Q` zfDz~i>g8|%gu>l)EZrTpr}O|(5kI(syisaot?3+14{7^9CQnh}%uGKBewsc0^K?fL z5fx4$i~hrxfkG~vxRKkVxZLsb>>0Yo-6j8^EQnC06ca&mz29ti& zIZX6Z*ZN}7=I~U~yMYI_L^1p${O&;tKqLK;qAu&xD8`QkH>!PLT+mdd_U2om2Y8ST z2tN_g)_e>_qCO3>B~-vWvKK=b)>*VCk%U52rp1jWQ19jz*(vc56O)+G3AD-~Q9opSv8v92!A3TYe` zNHv+O9K;+qLnsiy$U`r|Jk^|H943V zXqnPfnJJw3UO}huxs=uCdpVUMWA#K&5<7RdekE{H3yY=|J5%f07W^rqJtH~1hMIi( zQ#>5a_eWH@TtgmaspHlVMY;Zkkw?fr@i6;{bORq9L~wIpWvn9$kTguk($8l370~4W{e|(fS2=-in<}nH-_8p* zfNQm=z9N#8OCh<^%kp2$M0G`(&b`(T@|gRL4Y7MQlJ4_+(?oF>8(O3`(0`;a10uPM z2-X}b2s+vDMwPr@`)`GGU`CLz0$g!2oQe4}f9xKG@#q(Z_8k;fn%wTA|8u}dbzF+c z1I9vq^%;T^3^f%2w2#9Q*5s|%dCOnwjEj2A8%Jg0Pa+}kU$ITkKQ^mxDu|MaV#S6m zrkU!z&s`(;VIlhDI>a4+$lk-(0r8~0`HBp4FR+4TxJQia9a6WLq3(PSb`OJ)x><&TNSZ z)#o+~3AB|Js|n_-gq@s6#wi#ze{ZoCs4Jz$DFk3gS$3tCbZnYdPFpiCdV|5UU+crE zQ$YdPN`9427u#1?vBVGl*1uD6)T-^blI=gc)Gu17ilHcr{KKTFLK!o-uAi>s2xp?G zW8tJt1;McLaYV+TuVjHA?$#||Io@LL{vl^l*CX#5HEA2H!iuF?2BnR*5F55Wpc~wI zo@QuQuwjq1B3-JTt>TK#P4>F_YFYPCVwl`v=DZ5^>iigwzK=V={=VbMa|+<#uFe$H zf_ujC(@215!&C}*gJ?7OddmPohPnO)f?NfIu)x@KMwLG{F)Nb~&_W=TF=3dilW_s0)JwJ7C!;wk7p+OMLY||l zFVR0%m)%GQk~q{voAH4RqX)9GJY~MVE_V{hy(P$c5nwd7{n=~8Y0Re1bu@phWM~a$ z&ZzZ3;O40eC9|D^wbgiVZs7OReod;2#hj9AJL8;Gd3*r8HqF1NEvXgFriRduk1U0u zyIK%*K|o^u(hpDqO(Jhfm!^D_Tc6_Pip)(p;VLTqMbH_WvRN}3<(al;9Rijucz~5N z-=?HqY!EZJkgqerMJIas~nV*giI(>Hfco$Y~!$(Sk z-nx~>LjduwYj4|b>|nYep)0k5|oGd8={(4 zd_u~ieiF|enABFx?@&X!`uuQhUWpRa7Xiqu8nt3-=qWHh>m|I4J+j>5<3TTO*bDC) zobWQ%0i3D+l6Iuhe^a%}QTsKpOPDXcC^$FTa%aq9G%t2f5OeM~=(@?pE2ZN*g%iNn z{l5l0QlBhVFYz|1Pc@r5jiwjmNzbma>wYl_x>bmwn;0#1f0}7wSFD_l<-@nQF~f72 z@ZIV8&SSKU(>(iJ_&2@!C1B&cT%#!8U9q$9Hv+UFI71y(bfIl+FAzXNaKNLVHp( z@Eo+#CPf$~-k1HMzt>L=d7Yuuz2^b*`qqi%xVsdQGmHFI?OW_ff%<0@LDUI|h+L7S zL1x8QrRCn*t2))hPYH&tj^tfcPmSAJCHq1BO1cR(@~I_L^YqXc36hQ&FZRMS z14@JcRA8&&lnBf18;;CY3UVUzc?NcgKrSPSm-H#e`Tx{zo8|XJlC2HIg!3fa#d{Eg zQ6i5gf>@lt=KfFS9EWz%!u`#hDgJ+$v#61)nW~kY+5a$Sr3ra3CUm~g{y?J?C6s)4 zV@48sr~}jkD6Is;O{^!kQf%okqyj|W^hoi80p#nVnAUQcVkrj)hZ(<|yqVf3WRP@D zn*RRMaIh9$)w+wqv%(|ebfYPR68k3XIOsK2Sh1AxNQ{EKY)AS7Q7rszN7Bo`nyxT@ zYa zRN7a4e~5X1w^%2PKnCwp!kKY+bqDqLuNzm>D$BM=i#2DxBABU1*wA2Sp()Jk&aS||5J2PA70b;f#D-(l7* z(>g2EML!#TV5*cX7)MoG)NvdFe{k6*xJkZ)@U(yx(OqOqbXS=ATnuL5OQ35!9X5izzTi3c>rRaU(}lk2o)m~x|M z{^sFe7b$aviqMC!86Q)+RsX6B(Huv0TB^v9Kc4zql zYN%m(z>@W+kyUHyiK{k6N%#zR*0&vw-IzV(>}%wLf375rUkzB96kD8<$AXq(IBXgb&B zYU1CF0Z7a2#0Grduq+q#n*@ML9qP?L1nwWij@?wS{XSZk}d~eDUndrNBwN?lDOc)w#Rp zs5wt)p{xR?HeR%qoZf^#n`_o62diX5g870hG5V*f9%qKTlIYkljW4bZ{jw5i%8r1q z_qMZ=L@=+^}aYB^_-Ky-8bJpvI z%^a2#b5_@XUQg0+lxhK{Y81){ql9A`iY4O0_zv_wU=yv%M>DuJ_n6PfnXyax zhz**C85pM*t#b#i`zRfQ&WQ6k`VRG<;mrd*au@y%Z2`ppGVfXbJMUFDA=j06irND;A|c0{m5%v?$5X zM6RU?X7KVe*VZy0t^@@9L03iDBCY67B@fh5xek-t0?l%VqUP08&1vdOhwR9g+0b2! z6&IC+wFdDKJ=iu)7`G*<*VWRY17WZz2GYI}2l{^B zDB^ME4`bA~;-7{vU@gPl-0%B@i7wf|gsDbPgwdY;5e`eSZgLrl;4OO;u?Mane1ZzN zQj2!b+pWOkt!<(iP1so0A=v&hCJNR9*HUHO-*OIteDTEt`D^X zpAmx0aN6MDn2LgXQy!!RudNq(i+eK4;M)JQ>D+Fs?ST9}YX5K~@p8c}L{*NPuJ{>h zKDurvBwl^-!d6S`!EJOR6?PNEU0ouBoPx8`N3QfF|&N*Fcc z^Vk2DzQ1&4>bE@RYt%ZoEF^mo7^^feX zK5xl9B7YoNqM4rMuF_2p_MV|*_lI%nxr&hQ=E^~v))gz|HPqY zsxa%H?`=lve|!G0{x=SjwC(*;rEN7g$MEB%{YbP)2bkpjRl9B1Nv> zEo+g}U)mpTv4536Ns~+B{jl&y6ONJF4KTGH%gcRWxtqS6e9AZI0rQNki=w&ox_0c1 zg7!SwPlSeML$)bz_sscL!*s*Urq5$cx!`1v#YD)4=Ee_Ymfnv{EXZ7U#6gyr&#I%X z!W$}cPCwzOkGjwatKRkQ@*3TBy;o%3#Diy(J=|_rO)HKHb|Hz^J=a0;JC7U_#G{-S z3pVmn5Q}85$9X8m|5p$Vh$~w?+SNSN9B7FSD2l`w^iC9(pv^uDDT>7pO9j?{&IKG` z=c%Nw;Md(m1zypTFUXU`B!=}1pd6FPhTwz}#wnPJhF4>ja7DzXqE9N)3cs3Ul6^_t zl%a+#QKcTbs_>njRd{=tSF0nG5Ot0Zyx;k!0N=<|SJR6&=OI)_CnwfnvO<01&Ohzm zeeiSV9yt5`?|!&tDI(t^Pd+s+&+XFBc7~X(KUg)3`?nl4ujlt^=hr(%o*T7=Znzw= zp^!H`JpPp4*`0JOYF+36%P#N*&8zkWhK-vMv_rC=L+kb;0c}sp#5dF`W2yID;;0?Igu%2W8WjKaj_q^xcfEXHk zh9KjOy(MMmFt^=$pdSWd05p*n)6Kw1HL@8mAj5NEiz1iH;*}@g<@Igmi4dgYB#Y5$ zB`DN~LfvVn4;;lml+h~i$Y9O>Ql#F^a&A248a8ka<_%Nk|JA6HhLa6F>k5B2R&)pg zof2S`w<%ApycyA*WT!m*b{Mpa+J`&X>%&6~PSL9yHVDUaFV&Ef6LUm+c+_YTMW++J zb7wj>st#pB9BDsl1J+eHVH?S|3Us}BF_;e*X}3OBd4+S2*1-@_GY-%>mL{2r6hws;J?FJ42Z(&$*gl!rjw@DGNd^XGDAz^?Y?aQ){t zubn4NK9$?J#6`W{O)=BdEqdfr3>Mh0S6kx?=sqIOWa%|Bnjb+gwYw*_BK=u_jSiSx z&lv2AMUB$cY1Ngn0ekE@R%)x(t(Jo41j@No?m>kK2O76hft^bBFsKB2lK*xHu=*1S z6w;5ij}@$GaCE%X?~HTqE_JaLu1Q}qcujYH*C9To9VU89Ze@k8e(Cu=MidoA(i%0a zN6MTjDJ$-zPkkTUY?LAqL%E}hsqQ_@Hit{Lb@MsQ={!gyX*=n5%ZGyr78wX%8=_6O68^ zt0)9TSMmba|N|64wtUG-!fzl zrbtP=D8`<7ox;S{oi+-+&vLUbB#;M;Nl&T&n~fPx*QVahJE^fHyco(Iuc%hgxh3Sc zH0!EYI_ES|rj)T}B9`b>TBt*;)Af=;a!VPjrRZcW!uQ-Z^{v`Nw;YTTDb}V|-h2Bq z^S005^6A}eWqQmnY{(-gwsP&%H{bFF#^LXzeWmf?y6WHSbu^nuZA@o-b#~Y$J`$W#z3u?78Yfq}s)varzMS`s-Ce>p#}VhnAed?1V=tn=_Qs*Ny?J ziJ@mTFwhi_jhVq~)-Y-sImVoqsoC#hEmx_G{5^~> zHj~UY(Ht!J?d%F&b!j?QW37eO_96dU*E!W^PrNf#p@ zv-H?k?IGc@s&%!%__r%D&Dxt_+H;gSfv|2HXLlQ=wVeeK>?ReW4q+Mn#NWCFQ6tjI z8{X_XrI$*L<(&zA>snkc#pNaCl5z78=k4?`1J~mY* z>=y*l`OtXSoHi*!6!ob?P|FWM=&z&|+3f;fwUtT5*$!a zN75-}Fx=2eb8jx-JbUek&%ulCG(SjI%8QPW@?E;35JtXhGuvf<@mb97B)Ht;Ut*%K zT?Y$ZNl`AAfz^}d>ViHH!g)@uGiBpSGfUU$!QPkR+`N9 zI9Zcy(Q$*Y`Fp^>M?aCEa%`E@q4VL@vEp>nq&qL;fCR(N87-@hPf#m;~>$nG&2bi&T8>` z?xDzG=_68Ims?mbQks8{uZ2$KSZ6=lgOyTPLR9Mf4g34G9#j0vXqY)A)U*AA;4?_4 z$n$u{;r_oB030=7D+U_~h>qL;y{qE?Q~>@9RMmdpqw2#;Uz6R9J{}w#V-$6QtB9aN zFxe1_OqL`tA>m5G2}D2Y!V`;+8omRr`Xch*~* z88+5i{d!er-)9`mFSoYHAbPI8)*NPXk26^;rgONCCqu6OUGaa=nVjR@EP-U(cMc+3*g770al8*DE8hVY$rQIm`Iv~|W$Lc6PF$L&=+Be2* z01o0n;ITbOfS7$-AWzS0Uurr5s4q##yCwR{or; zeqiqN0O^6h=5u}J??awF3_p1xH8l`={3F>7ca3HWj9Q!l@khFbC-OwBFd%uL?Y*&K z&9v{j{Mf(a(3hYy-yY@;RPP0S2J~Hl_&Z<&K{O_fJ7PZwE*fU4Nm(W?SWOwAEj%4@u?}4AY zhaN$6$WVJj@9I?z`~%UnOQ!1CK9t0k%mjgI2+}n5PPhfr1mRzNO@sqE1R(@Gx-LMC zLXL`v@dwyqLZpEe!+{@hy7=}XWTGYHMnp+T{sfhUN_+zK!&-w8bn447P6LtsHVx~; z23h!SdqP044~jYRCdiaNVGDngqr0MeE`-&}AYY>I*~{K-*$KgWIS+R2_jKc%OKH zcuX&>AxnoI(lr6&EnIOHor_FBE|C&T_EdG+8mgfP4Vp+gr(k9N+mxLp;IAQ118i|k z(>_A#ceW3et1>Gv{+YdqKq1F=$WBr2(iX=&l$@xxt+chynC-SiV7} z=utMv&VXnS;&qZ)58m!d(4x)H3dOM_kHjEp_JX7-!e6JzN;jkvzM(9gEk&f;dJD@r zvLnekne|6}IXBwVycfCnd>dS7KKoN!DO=lX8*+$MkD?L|$XVKBo-!5^G0HD|H(PuY zJl-8ox{I4AHqmH7(G@AyK3QlXThvoFR`$^zLs?s3`6_*D&uSX%SGIW@8_Aa0Iz=*- zToZN|=k^YlR;vp=&5f~PH0o1~yPIZHd^bNcmlpMhxINq)+1=DEo?6;((pp3{nE5%{ zD=jW-L~V7tD7ieW9268RpquY$CUbt$q}Wa2Zyd4TyHawuC{=G|MYIlE^B6~BOuDTf zp}@4dad_A%G?5+GoXUO|Cej1W8#>zY@nfc%<)!2-9H0?RTJPOG{_uFK|3c_m-QL{V zoEPcRc<^G%n(h$w(Vgqd>}8wFJr{z#aX~Av-C|!K(&e`1-r(+MHlc04vvZKIIk7z@ zSGH|WVB4_rcfx7w+k0scZK$mFLLPq~JIcM$EYdv*kX@-5ej1x(9u@sn*;v_HY2?#Y z9ov|ce5=9UH!ya@cKMp;G*2*b-?b%Qi8_|Y89Akyo5Q|HE#a=6X!%bleY57$)5fNY zhrmWs_a|Yp(e%L27LP}Yg9(rM{o-(X?H1qRqzUzE8|$`m|BH@NGnr+8gSJv{i~Sfk zPElzi>Dt2PTw_mrb^1ugc~iAcPt{{X$N6`x$JHJxHOw^-aZU8>^g8T0r>n+obF|cY zI@XuuVkgq8$V-m>AYaIFbKk3cr-Kk}(iTULUo>a7n)kd}#i{MpnJk6({!(+}%7T5>X$%U` z8P_{Y$9<8_quOSB9fQ(E%Q{a`#sg1AK_~NG{`CBz7*DB#Z@ZzWzykR`n5sHg?#LzWLv-ss88^?oj4D%794AI%h@`VAwVQTMNL#DshQgx;w|FSw?DRZ<OO6>>{_5nY!DHtjB2LxwlrKcK5Ui)QecT3(#%H>Jfe`p2coq z_eH2$DpAe{V78VZH97bq#x4^(JJ+6gXM#1MMWW)?J>yw6CB*O|Q)fGC3kjJo^!Ssy zTD%dnlNZsxpQTh4_dqcJb&Iz|<6XGERFrccN@gG<#b@`g)P<7*S!volJ#MpWD9F=8 z!ewLAj*sW4Cg#F%dgCVK7e%#H&H;~TwrA6gbHxjso$ioF<&B2vf^BAPl8hJzhFZmo zkC}D+rbts5|G0kfc|8dEKfGjwzpsbW1!T4;CHjBT^uY8q-<*(41<`pfmBx)72$aqD{gMCDkr0 zd9yI~#uCByQXsW?77%s+jaGkeQtcwvkR*I5)27rzlIrb6=Qtgx__rX`qrYpSJ?`fP zBaxDt9-9h|6v&9p^oY0LG3NpO({W4P7di)9 z=|fM8?g{Q>2DzrT623DX@)8np|1KPt+^TKl6=yB$=6}BeL(vbhBarn}SUgP<S#;`((Y#1@UO^}3P?BtR2zL3_YAKt=;`05JEvplaX^VX!2r|x#WYrSa2 zaZ;|U> zCfCB0+iQet^Xy^!$@l4(4a@}&9wFc9*nYzFnRLw@1;4wrFri(TqwF#i-SEBBZ@g!| zxMgiL%)e3Eah~Byu0nkxaFXvjfdO*V)a% zw`h<4wY=bx%)bLbi>5X0?&9x!xZvYa7W*M8uKav!?LOmngb6+>#7p&*H8@%mKS`lf ztk;+`>to?#hl_FOJIa68*qng!AM(pb%3<1NODqg1tfs3VKXB5#44Q-{O+`&X{x&Zt|FOh zqnbCpdc6K_mDdo9^T}h3=alx9*=vrAp-wI>Es`>)j`?>~J%XzxBRUrd)iefMwY^cE zK`F_ACvj$5G<=jlLIZ@_W$e=_=aiUi9d0k2$ABO5O5@zYQ2SPL`)%hGsA+izyauit zqZAx{?SHkr0H1+&PQ+iG0F2-e@^?gMdb@hn#Z}|Xz1CPK5cYuh0L?f^17dY$wDs$K zlHfI8m|Y?m&Vcffp*_m}`E5}h1b4_rv1%fP&>4|^RB6>U@BMp#k_5fC)+MewTn;gTW8diAQqNh(TWJEF7LmmwOk z+O>Bn%Xbk)4U{BX6J|js>#~6Cf4}Bs5;|hs+ce|XD@N^q;M&bmv9B37wryfu2|dCE zPy_%#ra*K~4SxV25YP;>>$fnN>bU8(6(p=_0QFNC8)EyeI1F{esm9@ebw*1bLFi}p z^doBS{*m6B-gx5oTf@#4 zww+1fuQ58t>BiVQ;(Eft=hmnV+XFBso<97Rw&ZCrCu-25&GNcoy?uL~&0x082=Z7Q zvzGb+A}K1KRAU2Npo0TRW&pNTbbPdr-GQ=6HQ*A>$F4rU)J7NTrFZ9~d6;qtyv0-T zoU%C|L)@&pveEP9GOaD z`qx1g%p`ckMxjBmEmOc8v=hH?+%(PE8>?3AU~3K_F4Ce-JHK_f5vpQ_;nyIh&{2AX zfjI553A7rPjH7Dr;7)UFh*OE3YjTC5D~?`EBCEG;kshx^z$*^E_yY9`)x-}-tC@`v z^B*r}C*`#fn6@`0B{5r?FM97hhk!ihYk7GCZ|7^JRw0Mj4c;s_p8XgFlZDTv7!^6g zi}4Z})w&9G{m7;vBe@xZ59XPaFlQL?9v9f9#qCXiiNrj3a-IuPd-mzCp~?Bg#3__? z!4vi(*U5;^gXEj7`$0=7zu`LPE%M`p0*2rv8MFur$l`F_%FO~itv#%lJWj4u(WM+F zUj*j(#>*r!w-@e+bAH4QbfO?JMPJ(i1?M7Ij+S|YDdHN|YJ;AZf);{v4zpp{`Gq3w$>N+Y18tP1y<{09}-KVVV$rFwDw74>E5Fj zpz&GUt@={xo+=W)nEQ3*9wgOb*fIwQ7rLhqjgJlI=UX*zW97APP`@I{L?pB<*;N`} zK`Zk_RmynHugi&CRxhyVB%p}6J^ndK+i)zASm$IP#T4>mIM`ja9X}O$_Z<`{64O(LXPnll`B@>8COf?DzBjYXM81yNq5s+&~LU_D44z?mO@HMdY zJEN&))v&7^tS&9B)mmSb>DenJ(|ay)?0yf{{*{1THRn)zYN>YKCUtlTccD*^Jlo*m zwNgy{+jNphBw2@#M@JTyO48D6v;CwT!9N%; zeJ#OcsMT6&s&SffJ{+4CY^bn*Ih%;^SbK^dIVzEEuvzC$zV(LWu)#FR%}R>2BlF}m zT$*f z$~@F?E+EU|-8Bs!0z__0%39l}{_2P)4r&z8pZQ_5y z&*h6;=9|X)7EW)UN${84j~yOkwMuU3^^cOM5kjnp!N1`<+f)8zua?lAVoo!6YwB2s zo9>3Y7at@jtk$CqIV~}Hp}wT;u>*Ksc+R`i+LSAK##p2FX?onzYXl~_N(btfosVOi zxJb7ACLkTH=LJu1Z3X8z&6&avy)k(cGwC9Cx#C?6{msx`R%G^a=?PEZVrp61+>L*0 ztf_ZsdXoD%jTI@juzXa!>05R$!F-iH7WYNi^yvPl-&*@APkx26M&RP@LZtELHV^+ zBPXf6s%}a0&9Z9bHUZb1(PXvM-Wpxuk&~rQrHZbT!fxRuxmzdsRS{9S_Nc}5q9tzn z(lZvlP{{zY59-SLQa&lwt05d5hBRSeht%M`5B&!9swJ>2aXzbl+EbHP{={g6s%xU> zCJ}Geym7%s)x$%RW4aWu~#Kiw#~qRJ*v<#J!)}(+Ow5;-=mpq z#ckQnPHI1LNm4%6`9c7fLQtvYjy9N(v!mb~zVV;x`v5$tpuL==pat!A^`DJ?EUJoy zPQo6to-%E(N`3AhmMyp3ZKS371F{p&>#2QQ?gQ%8Z+_z-$un;AruHslblb~|8w=k0 zDi$`C#ocIYjWliqi%+h}<-6)AHD1okyTE{%M1doc$j z%iy`kaV*Rr#C}v>gZy5axtkYTudlh!vZWZ9)v9 z8`m&)4_~Hv7~xC#1qeFGw1`M$TG-!dd6vmxa`BadRqL7TErR`GPX?}x*~iR3e^haL z$H-aX8Iph4(v)va6e^L}YwuyNoSXFwVEfs2P3BQIvb<%KIhsdJotd~S7Mq*3&`;U( z;Z|Fe%|ew>V_aC6rO@~UJ_k5NrItwvq`#nvK3cybqJ-x3OeWkt*I0Z=(Zq~RhNn|_ z+v8@EAGf$V_eU1k{LVN2`KI}KD8wFKo~g_Elyx@NUjcdE0oE_OfMIH6yx)}nl#V`A%XvIfG_N{=%M z_>%>BcP%v&znV7z@s-$#x*$a-WJj=9vH9nHj)>_n#|R%QPqQ4UDVpx6?COoNPK$a{ zTH%Jjg9Jx_XqZqe zhT2P1RnhqXNkT*D4<&OZ<~^Yn2pHPNQe~PlW_|a$*tv3|?3jerKUKT?(piDho{X># znsQ6H756_{t!cV5uA-H#BTKbVADYUuIw=pgddLqG3HQMs0c`xd)_;vWyv!SdJTyb@ zX5k3FU*JDjwvBA)K7S7blX&(3>+Adz-2E84GsM7gV5$0Sxv74d2>BJ&|ct)+rk;K-n<0!`{J@K^c zbtOfmsZe6s4OJyLP~*=lb)YR@gAJ7qL!&dbI=F6!nT&v$5>>h>VxKlT{m3@pnrVIh z3g2!wY!95{-2id(&vrh6PL#1*RCVQTXqR?S7qLv#Oq#SzED&32^=obyPGK}#9JOH{ ztC+J*kB``0hI`~+y3r{bGlR(vzKUTIpVA}TSO#dPjQSq~bsmZ-!di-oM7FA%Mwk|tw=@>_Xe z$)U(TX>E^ZJ0Y`!v~8Jq!(#vE(7= z&5}{u*4L$qP$2r-y1&)@F9Nqb@N`~B4x0KTH}YXC$$08hsHHi-S+Qmq$U%Kv9^ORa zY@9IM&}70y{F)kl!pwIw>{>VcmKjUR4i4JzQKbf)ffjayaTxr=Gul#VOJWo81M$Yj zBZ7ymT0=}~EtA^4MA+4zJ(mh}U8tqVfVq;p^3#|6_l9a?$T)d%A*7##mQ6>yDu|bo z_GwE|72h!T{E$~B6-_1@*{i{HA09VE*tWF%}*Bn_NGS*E8SZHxp&{Yvx2NAx5yKCW_}`{cTeA5&^W?3? ztA9>hY&s5?(K}dtWagn)iOi_^0mrh`5^`=)SZltE{Z30y%dL(y2jBs@eBswg<2L z%J5#>p}_OAzkC~pzekWJpP#zLgHTI_RmfueP4(tO^IylUp=PRoPy=#R4a||sqX_jQ zk-0NzpBxB0=y*dIfAT!=0JtK8{DYM4l>i1U?*lr66Lp*N;t5;=hBu^su@0Irwd2-^ z#;X{~6dTGaNJ4{@T9hkSCJ#&2h1sgft%m%R>GfgmD!EBIWY=j{hdc_xPhc39H_pe7y>VMCu&5`_i}3M;%+Oc5`+&5Ej98fc9M!71k>jJHtRvH zOTe}=nWHAEX4hX7a}}x4M)I)VO?>hgpFFn2h^D(TC4 zJ)!bS&ge1G0Kd!=wbRQO6n9MEm!2}kiY`;{?GwC>bAq2jzO86^!pKjIjbE1Npw^C^ zm~B9PXXpB^u08aQaLGJk-a~$U1-ROkm994axENgvkA7-Ma%6N2_$xj-3_B0{7CLA& z1Bww50g#jcVK(!A=foNnfpU3>m}i2N4t|7&)l-Bh#y(dTEiH1ho*m3wsT3v#ftq`r=d!2}6W0kOyzwuFz5jS!iCPgD zgntfBAkviCeyc1euf*&1I-Q>pbHB*xi8-?kVKCQDo6o0dmHMbx33zQ~+jg;DD*YMJudFUvZ7-6Knf-Nz0M^wf-GieBLeOW28kktiQE; z>!Os|=)qLBZ{#C?)r9^w28FM(`Da?jh9hyw>pr%&vlH-5h;iK9ik3Mnzl;3s``m0_ znb-x2+++Xha{j7*p)RANW*Q5)!P2^oD4qb-37S|h2lC6HNA2&Uo#PjdvQOFR$5G4N zxQYoXGb1Z>M+Y=GQc6~BPD8h*oVUN_3X1~J=jbR56P2{>H^92H7eY&EQlx|4?aO#8 z-NdarUN-2i02qAzN1Eb$IR{mbcFZhXpWc5LJNHUN;J8cf6di!0Qw$izcO3D`wHf~{34BojpRM7uFxNXN5A5U^5{}Zs zlcygSy-o=V1fATx>dk?@9!^sdKQqcFBR<}))ZeE3nQ0HTe&M;QX~cHyl#WpxP^Dzc zv_bQAFKS59$f6Nzj2++)YDg>X$cB+l#FR;^K zQ-)HmlHH=gYZ8BoOT;%uRoVR1Ax_FS1_laY>1G%M$*(>fa8>tq%=YHb2Y{mkV)6yt zpl~-F0yuYIvDi;wOA7VC@kf^O?pew5Yl8;HLv(J*Qgh8YeLJzri~*H-&vjJYw5`xR z2j)%2%-g4$uzGQU8NAh*VHC>>nDZsMIOezbmKLg)9ccw8Nj$@zy49K6Q7*24A8(-8 z`;Q4PKSP7O&{hP6dMh1UVyYXw}mvyc-tg*T1qLz%w*l%2oXT{$1U>&AIGMdch35c49#>UmaTvO`?-_nMG*lA zb3QF|QAu6Q6<15r?F)93@1cCICylI>)rfu^rz#f9%mHO3DR7V1jd_ZtwMDs7<>@BI zIUFk;_gwRL<{9bj{imZ{}6VTL3KsVnm)L@ySu}|HMqN5aCZq1B;2M(Qt2IkcFPiKfS3(-u@gWkg(f#n;C#4y@f=!GvUF%D#==dh*I& z!k)ySPEZ~*jIits(j{ST3DebgTcFjLUfl6!%PRRVu3taJq)51ajUCp6|Je|;v;bxm z9`1_KBu2H`S;o*zc?JeH#vCpxTz{qbXSAa1{+Z(6`(!dpnh(X%-!Uk13AbR*llwc; zZ17tb8&21|2>(arb6e(XYLG04cYm{0#r*ju%6V8-r4;P$%{F(+Pu3<+mwiF>b`__R*7 zJHq{p)mF7CdUI2z-`mEY^)tQDExpS3s)FrA|kiPVi$__6p-_MG? zh&pWA5gEy)el)u$h1Sau+(LB(60d_&R)oD=a;b>waq6_}MKz~yKB)Xw&qXZri)QM` z&iLQPxC%Lx;|!eh&2ZQ($^CQL%DW1V;jJ6TbzlOwmV_jxI;xMqvb6DT)s0>njpu@R-DiJ}RG`Oq`nOe2eQ(BxT2}a4Vm29T`u`O_XHj}JwXR? z#mA3E*_c3Zwx~I#X6I=Jx0`V}GO9(&mY(GsPg5R;C~O>@`hf7b=d$3X7K%>}ViDaL z;qzis^c-HA2Qu1XZHg1R2pi#uLusiP;B5=qp(sMRVS&BLYk{#!-V{EfecQ6<`}+;S z8StL0_h**KP))7;&ij-2PJvV==(6K-z2T-OpF5(i~l0pTx zwmUeKhw9ea7eS|IL0X*BxIAxmk25axT$BBFj3tvM`>I!(fkz&y^fy|*Cf`v~ERE!i zr!TBp^Rx|QD5leYa&O{!F6leNV$ozYT{~yMs5vF~(0lR*rheKpUhq-k3#1_Vv=uRC zQUCt9pbK$lFMXI|N0Ip$hRleZqt+aY_=!D*R9h$-c`_eZz;<>@$Ni#Q^qF`SSq4Gb zX;~{V-|A2!BD#YfjXlR=1%>JY?!5043fk+&a`RZG#?i89;ZxP)?StY132GHO|73A! zh{o7gQ`lb`m`vD;v{OamD3xjlB6_RG33XBtdOo*P$lo{-@a*yDE&64f&mGV781^{0 zPJgESxjk~a6A{XtD>{pP2&8Ht$*y;Elr`%l#^5`laBx#!`LXMtk5xy`p{@y6}aC-U@lkK-i_TocY~)oUk=tg>x$t@b(8 zZt$#Ts-P8z<>IqKeIN0^^D^QOZy~L+GZ14n=SN4?Jbh}fi27kkH(9M?twF@r8CCAf z7H~- zh-i*%%0E}eO-TSXf6QLu-&XpXSgSIQ3rMunea()P?C)z`6MWNW{DGC2)P#?8oaM08 z5%EDTh&1qHWccwdt6g~2%3@l1`cb9mCsljXM69&(?2Nw$^<%1H=_gC+NMidKV*A7E zr4RRK*z=gVLCv3dR~ZK5wlRe-zR7XrYdm*^7|W@{y-yDvJ)cMFCy6~^@Q4Sl zjj$u`b#ZEKE9i2N1<`?&q8`TJ^@fSK!8$ssTgV*o^~{a#h>3V(1gT1%eYT80sFK?y zOO|K~fw=1KE)zJd7)eo5J7g(Xq)Vv@*OaZgO4uHu*&?tEwxzXd1V4>Jmg-P~6*K~& zl79DRt2Ny^WrnJ@h^X(L9U6W}Sn=rBZ5l6>?u}kq?UYh|BujQW=Df%)>iAH{^aRH? z@g79Q#{AIMF`waCz;rEO83LQcupuWoClUBfC*uZ$ZleKmg$&f&hp>R*`dDS5fge)A zBHrT$Ox%ArDM$M$T8kyDs6j&FOGK_TF;A0P+ncdfjQ5FjVUKCWziDQ~69^u;ehY}P zlR?ee_WMjA9hoQ2w|izB68a_{;WfQ%l5$WH)MsTzf9l7=7^s;)DB{R2h6MZ4d}z2E%3-`>35 z`num47Q_@QRe3BZ0xuJ)4k}!ONx#D4-*q+M?h&J9l=&)AIA)Ft%t`gD$%DihECOe6 zZmogc6QY7YtxzW`nW-?yh;RRvCeD)gZLX|B2?c42v{NPZ!x#;DNd9id;F;bS^7Nob zZ$g?Gs@0-WBITWKK`Bq6Q}dhp=vo1LzNg>rot@hASK&N#SBV|rY7kfzJZ8&%^Ti@M z5Fuw83)x~GcDaSn;Zb53e>#m9$(X=o!5&s^u`mYgyWN6%;YHfrCWJ4=?0B8p;^zV} zy(rnR^O>^YvUB)aM5M++tErCqx<>K%PccI*GOB;%#wUu?i@i>t&lxw}o#26F^WtnRL{1%wC@FLu;uxChUau6DY)u zGnRg(($HO;Lx42S93|JJ_^=^Bi{5N;+WUvu)P`x;tu4UyPi+ja78;T}Z7-!lHzOR! z7yp93?FjfZa zTNeZQ1{+#Jtu8axcI#r}!6bb`sPclth~(DCf~a2}$=i3vdxia@9&F)$TV!N24sF|o zsouM)v8p8d{%)(DX`kt?zhYtC)|S#O+r{?(R(M6fezNT&onWDFC1c;DJAV;mnU*vH zqSo)p2tyhtWeKS1zYv=cn?xtTCJK)=mP;LUuj0|E-C!gY1%ijZ+y3_66EQ9(b0T8+ zn(X&S+BN2zokml_QsL;2f1ad%338p?5XSUH=c-pRk;NjQb|47!z;hrzIJYlI3w~yR z-N{PZT%K;Rxayr5u}8#{9AK{MK1X&klS?+{;8l8QDKm0ONE zv=w^sQOFaE^!TGy)pDIf^j1!2bV`LkHxo%ge_GX9m^^thuuJRyzBaK#xg(O2r1xU{ zaLa9T&wU!-ym?Gu=5TWFfElxNE(uyxJNk2~b+)zn#a1DL^Tl5pseE$q+mcM#QF$BxU#t%Dg! zZV?wRR6b+309wb+(4zSbOXJa{Z)QJ4Y`My~OV4ZLpW-!VS9xIP#1-9`?r!WAZ#RsmG_&NL(UQ@_{O+QWMl3I0HLfeb-92`;NIB_?0DJE&+%M>!7khRG? zark$NJw0NXV?_T;w0mhH`E!jE`kt0ixOnSH@CQ2R=h(5{{lj|rQyxhWOy${ytG!=A2Eo~I zZ1}A(_MN{_e#tRi_umlpzw(vhSeNr&U$e6cN{%^3emAlk|CKd-&)0t+Mw6i(#%Nr) zW!CtU+SzO22{4$!_?L397ydvo^pE4lH$G$8D{u$RR=G4k{oosSo+TyGrc2l-zxCO3 z)i%27ibFOmnpFAGEusyaKnG*Xg@yFF5u;W+?!`t&wpaF2x&nT};2I2-k zV3j)>u2wA8%Syv{1=t>Iyrw3~Qswuf?YCA#%9=bNoJ-Z!tVyL@TuEhFR&{)n>LtYS z4wH^NKSP27EE%A`xG4SZS5+BJu~Q${1O~g~!5Fz6(BBW%c%c@fg!0&}trxE0fYx?D z!Cu)l;_qN_!AED~Ug~dzFw3Fxc9$Cc+N*M*0gsOmZQ!fa2#Ia&lsd{rUN3F$`|Y&4 zrN-ri)s0zL$+T|WpS$yF_ZAeVyDmf~(U-YkQY;J9DQS@qsK{I}cT~grQXG zoWl2)s`dIcqx}SSThF0t`~A+8<;iidV?j2q%A!ndvG)vcF;HGkwgF=|NTwjg8 zIk4r-RmoE4OKv@>x{gElPF7@Bo%)rKzaz}=8(}to4d9%&62h9}wnWWy$KupVRaU+5 z6@x4+5U3;KmYvO=irK*E!tg_R$`qNye{_C5>0d%MEtFxK3Qx;QlY8-37 z;!G08lu2w#hWx;y@PMQCnIFyNFa0TuT`iIy3ZThflvZI`W$^QxyEmuLQx47VK0abX zq|bysCsvyXPHPDDC^2fE<5kH zK#+H7ZmRR(HI`(6RbytQE+M;@+ zcgHj{&#tLYhr(EgtaeTB%M+H9ttnx?Ds3#K#8#_CiOdzTigN*Ph1LP7NB#H~1U`Zn z+X}@=XP+;Mg)<5zSH)u&S`>L3wg!4jBKcT~U_51rM@Y?{nY`OFzekDN9%a%Hj1C&YPu`kc+);B=sY((kQ#M`H;Y z-dM`NeATdDo~qv84Us+f$S*Hx6*H5s=5>VXUJ$FLQwmI?wORXO$I=RAQey`J08u7<+#D9p2%USaCi9&ZFz2txRRpeU5L2>i-aD^%9h8l_QW6ZTs=AHUf&S?-(&Kf?A~&YlQoIQP5a z2=f8@9Rpm4K07%JOg|km1%D&95C*875rUI`3E+~!Z6V6pt}MbDl5Jcg6p7$A5W)Q< z+!#L+s)j?}P7k&2tXb)@rC`*tZp^r}rwC1{@gVv=SF;4#+Nzx@tGvrJXJ?PDC37kt zMV4Q%trn2Gz5_MyZ(}s5+Y6vHV8xEW&xDaYqJFv-@Nhp2((^?G4#)2zO|a%>nvh~gAWSBNWigSi3<)O5@hHKTyDjxHViO$aw1(}ztW^du7V z$QLIWeXs4Fy8<4$>gc)JLwJCibo~ac_?tAMIoQgPD8fS=d*0B3Ldd5kgwu+)W}?;* zDgo58pSEES>5~Qp*|(z{xJJY+1Tq)|2apOu_ou3prX+^_V|;d+c0dd@)Lk@PR@^(o zOo+Q=lf?VHUwE90+1C?YIE=Mc1#uqb5LUOs2ujOlm~W;qCoOCYzcGsJl^=Q`z9P60=wUCe1eXPmTSa(swVX8* zt+RHC%pM6qY~qOX((Mw&4UD-YGMd*QK(8_yNx|Rgq>+ zA5y`HzvR9>lmjR_W&coOKtqv9S(28!Q~^xF$aiPlpN&tJtwTCl?dU(96`JMZCXG?r z896x~|XAe#1 z8g56NzeH3UR}u({oj$38<3(wFM)>UIRnBE1PIMWT3n>J8t}#YiBJjH>tj6sWR@?aQ z$@5qypH#FVrXfwE=j!ayeh@{e0eLBYzNu*@TY^)~z`_U|$yLNkboUI*QyW7kk#$L` zb~qdq95%U*J!V>?kG#D2(1MEtIZwlbn5l&8SC=7_v)zvBO?8YMc^>Ahq-CXNp+OA{^l8 zN@ssJ8ix^8UXu|tMY{Xa!bbYZq>0)j|1`98)`keZpcF7_0#b{4r;_&F$ia7!uE4Ew z9^nhnw2R~8mkZ6&WtQQsH_$Kek3Z`$_i*;jlSie%h;WXu!ta%<>wG|2?&-SS@M51s zGe|UK^6Wb%*I@S@9pmRK8r9u+y8d3d2;86Q6;wzb>qQq3_ni{;6$JKOXBeZR8wkWM zAckwH4Yt;N71z!g)TiH^IltxgZ+4sQRMm>yvb<&*EM9boG9{n;CII9pK^L zHC9mOiZSF;D6N8;3@qGX7{wP3`0z+X;(a5nf4)d{k*rC80n^3>^AW9sk>a!bB@!?l za&h!RD7c=2c_f8BNQIsRT%!=&U?rQRPOs*~=@74Y_AP8bWgN5!0AA)5m>(e`)?4_|I||pkL*Nm9{`kbH~@h5f2ybK&7E9r|92^+ZJ@QR zPAJ-8pGzNJUMy42nlTI1x#(|Vj8(#eWrN}Ak4Rw(LAB0dydApcw+eWQK*&zE4!G8^ z{lg>nYV+=r6MAV=!T%JrC%7lN*VXm@a(clJ;J+~(A%oHni$m#TDJ6P(zU-GCh7r4$ zT^Qf{5cVjKsL0(RMR8Q9D41)MKy|tUy)I*Xh}~oE{19#`;t~t2FQ0r^9F|B15;x;v zbW|lO)woHM3Ven&A_ozj=c2V81zQF(5s#~pQbM+S#?DMLuMwvSLuxLMb5EvS!?8z> zkdSLH4|jW${=|>%ls5e$tIm1D)@>*d`mZ?2Y9`RWn|lVccR|FghnsWI+ziK$-MJsm z!|sHzv#}?}Gy|MAtSZDj#yatlIoCbOEP0BB|C&^EM|jQ`Skq(ZQY!sOlwhmzl^v>i z&|(&*_ELZ;UDR;&vrp2ASnq`E(v`?jj&gU#Qbz|Wp3AAoDQAaLiO^_d$cBp*lp*-9 zsJN|1C84YZvA)G-xR%K5N62lJ`*dHW<5;pFETHz(ZlWgC9(^;rjddO|9@i5EtBY!M zZB)VrQq7|fP#@*PR?uz5T#9oYCu0VW^~PG=XZi}V>XffqqL=oG0g5a-!9NBgSu>fZ z4BKzZng?ljs%(PP&g33L#DA1O(UbH|OUWgo6ARnp(a;X)VTsnmf1z~xDxYw6izS+N zx|;Jq2K#W&C;9a&ig}+kN=bukJEe}mCoh~Yg;ETWRsCpw1iTkX_wXgmxu!Qt1rlSf zJ?)icsRG6dCa(z9SV+p@n=r6QSY<_T;fa`{<{5_Th3Xf2;ze}De07g>hPvxmDrefV z@j;UEXmvUk$>GH1)iv0%&SerEf5{ZyQ7jztHqsIrAN-~(rkbNmj~r4Z=Go_*FYOhm zjp>EquqcFoPq2X@7sS1p3#rsF~P8uvP1#^NU#6^w*N7B zJghCfTs{1x|9ceaAq9}?5d9mtaX;OR`5_%Ya81fW-80ZPyf0}h#;!`o&{QLKA z*QfVs=j|1u<+B5`Pu-+C7z=|bfg2Ad%42*ri#(9E)gEwi2cYv_g?%n~;0}m7 z7)GTq@4>h`FYSFL93FpZP=gJ<4t$~Y6d%3*iRdmqTx(=-#b>I*y*;-j?CZ@itbO+L zmSUI8oRKRa}a?glPEV=xmo4Qtr_K95R$C$O#mvn$_&WJA|_ z+g-%AY{;3}t`}O5oKGYybsw2%eUIVWmn&?m6#iV}OOF5dK>Z-(-_m#*9&s(*Vy*JwU`W*wv*A=N5E)w&NnoHY7~zVxKM@<G{7Q2V#1G6WU7&F!{x1OlBX?K_iM{4)-5a3m+X%fYSNCe z?lqjN#j_}vINk;%U09^!)*7^tCnD2U1;*Wxl~!#D`Bg;mYS!VMBC?7HV*ZIS1~)nv zD3fP;#8il>;$;$Xa}W1jb-v6PMjEZ^5s^(~bsr&yZ}(1b)~Xrtt!%xawuATmU`#ox9nN~n) zV=AmTaOKfi@%5F)@7%a$b>gsqLNeoL6oai`vyY{$_5 zBH=N?B`4pLNVo%OMWe^w7+^S7$Y;2w)S$~bRs|$}Z1eWMay_|Ya7-Tx;#1W+PON{SM z8x6~a;=jzwA$n&r#G3{oo~Jkh5kD}f6+JiF0+3%8eztyDYSh`_^J+PZcaJ#iYK$+{ z`leX35gGTHa=;`ZbLyLT2*5r) z*BGD0NbtA6XSQDL-6cjaHsbjFiG8U)f5lR?JzcP^ead^VtL&2YSucZ$XV~$9$Un)N zksrx6VU1`^&mcM;L+~#cB@SGR(k1o!c<*KRo}b&{b?;sTj1Aopv9O7>al=4_dR)V{ZXsO89ce9WgF1&iYdKWdp@3oP!;By}8c zswYjcxg_HMx%To9Y8lQEmiEr{1}-l=-GLwGT1cOi5!iEi=lEpn-p7pJs;+xY4ZkRg z3Cbxmp3(eio}P69t}n^%3@R}WuH3%$4g~!oCzyQI(!5J&DyUP}?KKN~vLZG2?1guD zLO@7&5&nkLUp~%K`5N9mOPt_nbj0w_EkU?}YlzLdvrMCGj|f}fl&I}Rwyvbolj6Az zDM&cv-@X5yVNmdb57MIlj#&<*Upatnl*jvawP5!WNI6#|Rhu61wC`TFEMmOBhtg^xqfIEDB@xx6RDWOzYZg zeD~YXih6ar)>^Pd__8SMjwE;&Mfso)hW)M*`;3cS|ECYb{aep9C$nOe6O!ywv9=qs z&gyE%{-MH(GUaM3H!#}j7$;Sor7$iHEI=j{S1-+0+s=jy#ug)h@ zw2l!iZaj`+rhIjr0F$%^r8KuIbJj{dGM+<+fH8Dzk<0^;xoF$q$s3(sOUvZe`yq84 z#5A`c!Alvi0up4Y{}O^N+!YA^)9hF3gC0;Mgd}&qY zLXIlfl!RibzqmPkw)a<-P8|J_)6ns;Qc1O$;?m9J)D(m3po;QNN`q#t0PT9UPApgl z5m`>ejZ(bBTiv(w3cG`C5{ER>vuqBeWiD;+SEI1KLGdO4&-j?kcfO?My#sIx29l zvzX7oAGngDSgy@$KtkMI9R90DQPRh9yg>UL@r!1I7S}m5Ur*$(E^54<)r*8rL!wpr zvVEb(V3olIATd(dWcT?7Feh}-(oQ!?9HgXRHh84Z9e=0aO8o_Ob#NRUK+J+`TyK_z z3#ZVC6>Seh2)lL9p@gQFo%*Gxf)ipZDPxI~&Aq%GVemJH`&C|`v-0%M| z`6E8;GCqtZMTcwEV?_eQqm$t#K5Qll`S*ulElNP|>SLNt6)Kex&jxk0?AtldDr3GV z(e!qQv`@Q&Jg0fW*>8VeZjtv%@yLj9y>ZoJv;g7yDnp?jWnubC9yU@GN-luXONo2$O2v)=w_;?0KA z&}X$ClN`?Y#TZ;~vcBu@dSR%(dzYPknxWl4@cZrWjh4rWvhebJo~KPgSK_}f^Y3v@ zL|2Sb14|Y`@65T7RX${9qCey|bRJ6~r9%r+F5XkS(8H}JkE*ahkg`i$?PzJ0fJ&-v^r>y(mF3_65U5T+cWlfmrE&NE-uCrtrR_HhUM1)OU^$Q@ITi zfGc!w1h^xl59XS^_cv58#%~0O80;e!I58v;`cliR5Gugs>@(yYd!Hq^5vm{R8DC+5 z=l-|G>$m+ED5=}ezTd*17=jC-KKuvZu2~?id?e++>51W6&_?B*O<|zIBhd~2*`kyh;mjQAp@Otm=8pfeQg8Dg=0k#l!XXm zXBFBcamAdw#K9jsnF)SLag5`NYvL28I6C0>VEfX~g}&h%-1p&AdWpt~CHVBIjUnWR zRj^2KKrmB<2Ed0|Qw64-0}=#ShO*KBn%Fd8PHNV2@sO)*N#8ZbIxMijht7@cyk<@s zmn7wMq<+M?Ebt-90~opEzeh3g#bc+L#y|uaZ5r%JqqnU=T=Of80kvEd2geZRV4WOd zH3K*>WaVT?6xpnJ^h-=oJ$x0Ez7=7tb30D{OQM zqc7QGBi|GdyGi#O0VGg-P{vT#{J|_x4bYv$|G|MmHSok;1olKnCH{h^v|~AnBSAfq zHOjWzhFvhndKQL=qaN89P4c@8{^`Tp=fmtG+eZh0<&IQ`&%zN^gZNt8d%ZpXrtD~vzFM$g0xIphuT4X_ajfzZV#LWzCaRSOA(zZQfL06s$pSQCEU znnD`m2i?@yOH1?gx1>estL&9xUMn3T1plKxlnX8d1i?SUIoZ%6Q&kw){XxFuH|wb` zr&l2ys8+9?p*0g5)^Gevvg-s}`$ljJ`*6oY9mFrhAt9sLj;Jk2 zE-m$cBuq;Gn6;brN`)`(%2JY1!UQMkv83byFyW&Btp>p9NT@Oxt3`6Vc9>}i14k$b z*6ahQYp?|QAG(St*8US=K#oxVCi_s5oXo6~@(hnezwC72a4%_|V~5aF+77Du zH<=Atv)o`4s7qInUwJ-IYCu8!uy)6_lw~om_MXOSykt-hCRi}XEaAi(uugJ<_GRhd zBp$&36C)$~$1B#fm6+mkNap~|ADDfcwrAWJ%FB;KAo&O1a_|# z1Qkq{|A+_?PYyx=JXgLjLM#D^F#m}TKvv}ou7`+o*8GOTualN*i78;A+%^j1xeO|) ze&vg6a3+)~RB)bsKRiHiIRGXdfWS*-o8vl+g6{AEy8Q?b00kfb@fr&<4k!c!AYT(f zP63|*0Z7+)kafTwAOPi>4B%V&LJ>>>_=|E)15pEr!~B5;qwMPi?ZmO}ax((hS?Rj$>A} zq(H|K6u0t&PUkQbH&nAS-fagjv{_lTBy7i@idUpE+GwR{HEUtK})4`heve3q!aH-qqHMZ2U46 zl%)zd`{f)c$u}5bfEhi6H5dlV9TO}W9CjcIyHN~?97I`M4n4ES8x$td=K_qV)#93C zGS_z@3S*mFLr$P(5E!3+wl1*N4%OV#DbyipsZ}E#XnMpqmBC?6CMrWFv536;Gn1hvcg zlQ=ExG3%z4z3Z#F45>9pMzYSlZCHu*P2Z7>_UUEKBIl5@Y`!;-zS-X1<8`zm;|3=S$>B8jg7Ls(~W6Ppb=IV3-uS;En4QtqxyHpPS1-+ zFRB9Ni6N#ZTT~bRi17vT{ly-pe|d3x_L>gJn>S!RYpo5m=EU@^y&>%1*I*Lm3)xQo zY+Z!yfv_QQfQ#(8w0?~=z`tM2+;izFqB({`|E2v*Sj!a zv|WF+3$ZO9q`hZ0u+s(9v=5T=1T2`!2m@LK7ND`diC)o#-3Bwz4rye&c0~EJ3H`s9 z7f7^F|5?^o0zR}tTg;m}XHi>GS1ezsmOnDVnWz_ggc+GokQz3y14`ZIS2A-~O1!3X z^b5A-uOwH(6%P&eeF*Hg{pNvYZ{(QqFAKM#W^q6^#GbYBA5CW1V9HCwLaL+Fq9-^o z$6}wlOAt%8~Ar{pxW2U2a2>e7|1uMAlPRlfDIJN|9L(nkQ6tCGuje9T4UkK z*1(FGzAasq2cdAs3gni=dMU|g*nO?wM1UB)?*{=o_$fdcPiWx{fD%~d%mnH5(tvTr z2FC;B1}|W~5J~X%GJ^-8Swr<;+L6q-dVAsZ0Ib35fG(F2D0EzPO zoy2CqV6#FQGm%_!mSoL=>78Z9_Z+4W=DNUkIEJ z;SLr>b`k)O2U|n?lj`Gx8G_>hkF?-q$X~b_j7ui7(qK`vIQ|17v+s}sxD1#}8#6zM z3seTiCA1kz@D6+(VB;600=frg189~T@(bls)$9%G0+x?#!vtaj*n)QYW@Z9G2dtqv z(Sz$DHc%H(Uns#nA!M+ZVrG%hAE&vbStXzro{v-?4UB3Q8(a_7PN>fYrh_CPd`UAW zY2TdSx7*UrGFn^0MkU~1m8Bkx8cHEKUr%d5Yj4Gi+%($6tw>{HC1%1eB@I+Dd~{NkJB;uj{S$ zijW*o!iGB*pA}IXUnh;3weQdKW?S2Gj)M_#g-%BVi4k!U@lC7}fo`Ubj%G?q^EsRh z@2DKSB0;9^8th3Xd#NfrdYkm7gV84bI-j||R*a$|w9=H~NyZecjfocH68TuTr>OS* z=jPM&yZIO(--211c>oV}ZJ3G*UR%AqA?uPxY&A;gMc-5-GD6s@3e}YTa+-^9b9*`Y z!8VNz^=xzTX$_4et&Zc>Pz_zkdGm33Yn*lkxb>WlH>BqKonovbv~@aNxe^{Kz@8Nq)3xZ){NICbKSaAhxe8iAoGX-NDH;T#8{!Pi)bS<=;r41H~lfjx$p9JC8?MUe?Tj>kHmo56Fp$8YaworuU- z((VcAR9A4*;Uq=Ju1A8w_~u@fjmXzd^;yx@)(xWgofcCB2Ov&SG9x(R0Z-7+1-kP7&j+gPO<@3PePp z=+(%Q2o=KKrmGOx5|Em!XW0gAh{4@w#%{S%zmoTpi`(oF*}}5cEm4`8C_mAP;$yOK z)C+Db^#g_%2_3M0g2oX>%i|UTm{Lemm5C^D*VzzF2}O!U36me@qfnyDJ&(G`k&|}y zkz0~;sGq2dOt*}EEha%BD4FSLneh0mbK8oP_GA?C5%N+s=T%~NdT^-H#BmsJ74tHLUu-W)z zc~<{k{tQu^?@BCFHMKaNl^@QttMMuiz>6PZWCW6;6sNHoEu*_M++~PrtZN4_2M^xN zZI$taMrFRd54hcfxvny zK_D zsdO-}CP+=r`gkjRh>|wct-WRe zeW90}A^R!Mai|CK4PjXO^3*sLBIhYBTq`iN7dQWav5Yda@`r#Z3Yxj zK`b78;KDAIha2T?bbxEb-jo4yr+1<|KOZNdFlKlmmY1n9wlZOH=Njr!7;H|f-3%YT zYs+pqIH(6~BoB^(Ru`hA&19;?Cfe2>$^<){?a(~l@yR(iBpgYiM-4=Rjo_vXiH{yr zf#k`QLC=2b-UE`VU#ZEM{`vvK`CHV2u*A4mw8bm`Re9Ze1h$u9W{S7(J@+y;)C#8fXi#knR-}@BDNQ>Bj@F5tfCmwDY&2Rb-?mF|Um9 zVrZ4T{~mrXORTeZ!V;N+W+w2OfGWk6NqO1b1H2llI9+4HL$L#uqoV*WZ&z8VtpeU+0NwXC%zDmsvSH_~CUn)*v*5Sqv!Hk!z5}FNYt)fR4u_%|LEb_GO1`S1- zBf`~{v`2)Oa=vC*{odak6UvIBM`OK;0wSoHD)(v3WP^g_X;fN&&sHHq?V3g zQ);*DmX>l(myy;9ou@g`f%pEX?@&_T78WS@?|*@uwH_QU?01H#WsIiFnxRh7=oP^c zX`Txdve{Gq_Ukxrx{Zxr;%CsU3s_WgR@M~m=sEcAd!&7{SL#vUWwG}0YB`;kdi_#Q zu_B8XVyf2kkFtqXkWGWx;_#;-ya&RB%*m>gwf0M%mLaw^n~wW&OS%ztXIvcP;R<)4 zIg6?+_%_anv<8eNNeZrN@mM&Dm-Io(Hb`0f9L}tFXT^aErQY)hF}*2&b)%n^;MrNK z)uUBGad)6K*#`bzPgvUlGGSMg3hK70xxD0#JtZ&;DGbDwfw-y zm<@{-uVG$)4)uFqPK%0?q!DVwJL+Z4T31EJQ(Lq+>_T8m`4*6}cBL3t$US1NknmW$ z{XjQ_tJ*WbMV_@X!Yhv*jk8p&{@3RbMJi~SoAwF!RCxCJ7Foj|I73()Ba}RprZ;5x zv)XIT!LT@lZqCRo_?v>S-fCk=R-NXKU$iWziK&9I#o1CvM0)t(6Abg|ktt<(Yu+(* z<7|s~wGCT|aJ7;Bp~a7~$&lAAPfnirU!ynewuqCnD*kpX-a$>0BN&uOP8as(owQG2 zW6kQblg#EGD6|&=<;OV?c|Br72Gqv%Pb;O5D3_#t=TS@g{b|vr=#?>X-mDScFVBV< z9!T<3`|vQEc^1=B=;G<(xAdh4aq$^^B@g4uN!Ixza77VRXsj3Xi8VB7u`%;hixwWC zv39Bd7XV^Foxg=0)K4?}hLlb2Pt?iwsMib0FVMC+*c=Ra0#N3p{oXVuRm3$A?WEC2#G&v>yJ70IcjsryCf7| zQ$#DzFkTe_6(N$5x$$vAf9xpQH6q!;hMj8Ye>cn~!6X-u$X*-XAY%t-_8tkbpLu7B z;|D9%+W^r6)d?qIlLp&16VJkuW#$@rB$mh>qlaQCJQ;~g&3x^mEoFmnd~bnY>D7+s z6w<3quDTFai|rND^_7uLoaPn^F8JwHm5H8WDm$o&atNk zg*^Nmz5_`qOX3|fAvXH%YyPqONVAr+_V7yiL`AX79fmgb7@Z( z)EDeI^*!zSwCF(B)JE2D_BGf7lZg^$NDV)A9gOM8b62cMZ-rn&*M!>ZR+nc{1Q}i@2Bc;Y zm|Lu{z57uxqYG+oaa7|^jpl9%uH!ievo&}bp&-i5$?H2`b1liTXAGvy3z_$So}ia# zxCi3ss{aesC>x9=iGmDM#m$e})|jw+{{Pbsi0L_ds0>-nE9)XPp(S-(#xI^+Y}msT z*7Sd+dj{>G_)4fWE87e^ovH(Dk2RFVxE9jhHaB*!EfsUENNC}>qek*YOxHKU!KPqs z1cq{nc}51~0K+=V(4QoF;b3!UZO}7Zr3i1#sw4x-qDS>IAZAV)Ap-ibBsn5R8k1y6 z9`i^Gx3?6*k|PBgJ|4@t&P6evHOy;gdR&;WmqiTqejI_ew{ZFqL;nITZf}7xN{D(z zt*w<@woKgUqatatNVAv}KCal=k+?jW{R!N#2u2b!4U6>E5Vc8V+>&hy;!~H=lQr^;{jMjbBt&t{f)Dw4iguQ6wq_TlbfXR{CwZVn4BXTQ3x~QDi zdktcsjV-o0L##+~6LCmFhW;#CO{Fggd-o5Lxi_}>$9d7~#qPs{OX6>y5`&2586;6^ zBx>1__ml?VE8?OOnwk-5Y;Gm}Txj52K@A($2RPi2)>gylq*jm?(-LVSeSLm#gRqj6 z(adUZNetK9TkE-T)5hIaZ%4G&f%R6b1n;$D*GLsMrlB6XAp*EoSkH~LfmV4KPgE7fVo!sDEagk?3hfqI=(}0ZL-BP`z#%9UId9tg%qrOY)Wsk3|xax1}?x8Zo-&JunrYWwL z1T)&$A@mMcGd0WACaW#3mJcvZZRsahFH0be*5FoAS35=}+H0xvv~}n_i#CSv)YYNp zpkZgac9uPY<`M6o#?Um{cxalWO$8ks1x2Q|hJ&jj)Lr&S*Y<0>TzizZi&%KF0^d{FHgHp!ojfS1;+T-l; zuJ#Y@WSSlcx?yM?7Q1WjWl!*;sMxZq()D%0Rx#V4Sk#tnHa#EOy z%6gliYsvt7FwwJNboU3(5_%|%Zfi5}ZUZB>7zZnMh} zF%RkH+vToZ0jv_BN^LRh1+IORz0kF*5mI9>a_z+;=@QpoYA@b9T0c#8a{~JlsSUfu)ywP<)@7?}pMb>{cI}9c zbAfmQ#Da6f!4ujW!@)@I+?x5h)2Hrt;M9FxyA3sK*PbSQJU)F%u#K2yuwE3sR$h;d zY%V(N_m~SOq5n>{Pa!F7aMoV4p*46^doa8KUHb>uK2`g(!X^%#r>tdF$hA+y;GSmF zOgIBYp9$}oE8cC~6$u?khJ0k#0xVqgn_Edq8Furg#j2mN|30j(T; zl}de86I|c6P}=QX`#iLN9+IAqq!%D*1(FVJB5|dcxP~7A z#1dbmebCR^PiTErwQFAte)%Vrrt@EHT8x+4mniIbZhFZbPZm zAsWAMVPRx-Ey)B0IBCc|G`A(x63jg*SFEYr@p;&73fe?knO)E;%w65susRrNi=*t? zm!a6pwP#)R8?@}NU@s?A2dbySyAjOs5AdD_?xj9iUnA>R$a=kfgKOVtJFb0`-Qn7QxBubVH-pG6_N}gc8z$N9_8qRh z8B6m{fKGtBh$q`ykgf~UsgJA@Q!8qlNEG6i8!*AU``LgJO^ja3t*+f;-|cFRsM|e| z68gvhi5Zb;o2%UgzSIrod@rhZAHe<8NOhZfFX?oB<#yM8fLf&Ohnf8#u<6(`BD7`L z4^d|APw4ti^)ADH*tNH#-H%X+eiT?}{!nLwLOkII0$45oboFBN`VOpzBgG2Z>1t1E z|Dr{X!Z_=H)P781`wd09m^7n>@z~YtA0%V^A_?IZ)DO!ozR$7GGc3Pd)M}y{CxXU*ZvGO{oMY-wZFvf^%bVU*8txDd`m$8 zBQXQo4HvljS%lf&X|K_`quOh4YHy&ozIXM@^}h}_LAi1b$uFd^JW4fHrUFx#>eX>7 z)p2UVhkvg9ANvO}%LdMocpBrXH>n*8D;O$EuWt#84O!k&bsK4mD% zrE#Adxaud!c)UitTa!i-awBvR#l~l^_J743p`QQI{>im}=3EOLk|d?vyj?g{b8cvST^MC+28ui3D4qZ?6sRS)y1gvilvm zXmnPuaT`3}s)6#wBKmBJ{T;KoT85VD+P~Vrx%O`Da}rr%GgDA3bhYoa?`dJgA_`r7 zAAORm4abBmUU6)}F(PGdxMAgr+G7qsdeVx03l2Iaf69uZBgY)x7hl0WdBQ`o7-gog~L+*eR}~k~HsVsE{sb)1p1tb#%vY9n-N~Jx3p7IJWCJRDFGKeWL5QPO_{0 z7i(C_bDb1yWsd}U9r0Ew?8=YaetlW!(NM1hGb<&9?I2qV6Zc%S_o#8~q z$)x?n$+Bk;$=&PMEN+an*V0}v-x=XLBf+{2+M9F-J4kY%vXlg(s)h557M2#dj^7#O zI-@~5+sQ$eF#uy-Jwf^z&-$z z049@$raeZ~^)!8$>*Rs@fI1jAe7a7)dXKA(p-$Gu0^|aW0~imm7r+F76o52 znE+V;BLGGM_z5^uoC0Fz&Qw?PX(|r-+SU}*^WCF>--L6uJG9NNrXeylE)rL`T}?-D zMqIG>eUYmfh)R!(lAjv6&NS^H*UBTk!O8=c4=f*80k8t?AYwCnbi(R7`#SqkUpmuC zO>S`OqA=0)8y3R~eH=S9SUI#h{xlSSS9}ECx-hndj90qVW zwmb(dlDh(~OvHQ<*EzzW$uyh9W~bOb#dS)ssm*svU1tvR&&51C(kXMDd6?Ssk+>Y< z&Oy+oKhr*U^{M(aSI^g{xK0IfR1!w(d$Q|PA;$s&+Gq^jC=A_EsNO=S+I4CG7CDPu zX9=*S0LuWD1FRq_>VxV-gBVG0^onV4w5$G2z0y^$pc(8O;~eYi$LY1MK8q@-!`z|6 z(xDds91L&>z@acKUQSY-6F@DY^EXR` zA%IqZ69B>l^kQHUfHr`3fVBYY0M-L+0QepHeFv};0e%l~62QseCTBt4IUUB7vw=aC zRnGx-Ca`mX{Snv>U^{_53hXgp&jWiB;3;@t1oi^3e*=37*ek$Z2KE}TSAqQ%-~)Ky z2lf%LPXWFJ_yyoi^%T#nm-z9JtL?2#RG7B133dVJ6bOMveQkIRzpHHlYX}GNHKq-G z1F;JJ$s#-}`7;6zP-sOhILF};bsK71B-+5w|sDgmDZZ(X4 z&L1%3r(!yu23|h{v*&bp&%iP`6I1z*P|Rl{;w(g*4e$Q&o&)c>@E!o~dGMYO?<{yP zfEP-=^Jk3RkLro8wt-Y}=R!nWL|*4&V1EI)#6HDvE_I#Dz+o@9hr13s-r0!7vI*b{ z=WniarE?WcC7o8~)g-bz*I-vD*2`UeE&=rg?Hbp)7SyhDt~cy-aw&mx1I=;gM(iRN zgRkFY=etga^LN+zhjX**++x4rI=4Evxz6n<`VIDfw5Wfo_qv*`QOB%< zjYGu_>N-83b2nsMC()wq+~e9!&NkP%*SU|j-gwh>ReM{^cJ18nJm5MHYUjDmLumZN z#1fqC0FOANwf+Tl+CiB59reF{D!e-Z9;GnnF^9IyCs4%;X`J*H{RG!}()pL`JcUku z8sHh{S@apHO0D_{L#Z^q4-E&j{!5)lZ`aofjkSN8(L4XH13R*|NxQ<;1KRxxn>1)W z;=XBcwTnE3f8g|q=hm*i0zLG+O@hq}=z|ya4X)aa>G=}o$iFegUv^$`omaKzX$x>( zbDh`0(BA+De-qxf;C&nE-odJR7vMdB_c4t>pzd@&bUxYxI|M$JZxjbruJf_;iR*mo zeC9fzJ0H2u7pM%BU-esz?b%@M8!)H8!bJEQrGJAqeCvGYI^R40ah)GvoBkaPo{u{J zb)6rbpIql>%=mMV_i^^i?`%Ux$T)}0N@ z!pqt#(7w-bT?SgO5-nGOM5pZKx=F4N9IRm_d5?e~{e#eb2Nfi|Pr>!(q-p~L%$seow%2K+(3af{v5Oj=O_P&}x4g1@|wEKi@k zFKzmMao77xt;F*Je*a5cN%iK%&^O+rcRGf+CJ}d$Ll3T}?Vb8eZ;Xgj$YCw)olngQC9|PTU&#;DW5jCW`3ZoI#66ySyNP2 ziO&P^oy>AsT3v`_7*f;2xB*{_D<$G1D_p*72nfy-5bT@NmXBbGGWAJiqR{k)K!`Ua zKs!|61o8DF5h~+^?8ZomxcrAFEdueiHgSDGd>y7WMwafkpLi)Oz(3QWb;55Kh?OcY zBgjkX*@#)c-+l?P>+8K^aee6=K_M++toY4`WbZ8->CaENwGtcA{%5MYRK8MGvur`B z=u7GV2fZA9(8`En)I(+BR)&{tNm(U-NnxDV0C{7giKb3_D@Gv@ZN;#-s~934lOgeX zhBp~>e3l(r1;I4fEQV{}AujPIysII`Tp`Ix*#Kcyd;^{m>jPp@tNBIB_3};?wMt(t zKMNlfSF6UBwYYkT&q%`jX^?&Ux$Qad;~@NlELD7#GGxxgCLpKj9V-yCPPl0%zDb~= z64znm$AW5o2-ju}c{`QHcW!yv?6|kdXv*_>5{M8F2!-1)hm)g$;?ivjRuX1DzAxu5 z0wo^r@+&a&R`699ow#w+Id0;b>q}yzPeht)kEa>2HhRI0uUXKHg^4*f4^yBm+!PcS zZf1+|^zplYnNfbZ0B;FMen}ZTCQ-0Q99^C=AKcFwNbJxy)It^G*B3|jLv+RL$=J5E zNh^x^V6}y+<<~D6{Zx@!!XH0q@><^9*wUvrnj7oFp;%aUe;*UYYOdmwZW>mqbf1r| zChyU9wR|#m`$_%q_5MDGS}bor*4JbI!1Sac#XAHvNx(C}MFMlmmX?;lfWj*hI01*! zlA}}psYBhxj~-;pMXWaj@Pr-zEEeDPfHTmtu8szXS)1I*AJ`-QE)omR6ROE-YOr z-**|;E2}vFP9&e*Z<=Uy&Hx20C|f#v(VV_Th*R^yix6psETX1r?%ZcR-{|;yp}s+Rd6`xf zpS_vU&-z+IZH=oohy*bD2E;2}5RF>+{w3x!-{<*vl8qD-SXEm`!uSSn-;N3i|;pu99GsJC2xXoit; zYZBs_0^ImUSrG1#b;WE>#$CCgEf`sV7Y19XiboFpB*hS4bVW-X-XK0y4YUS>)V2_3 zi4aW_N2M*)+C)>RNe)Q-f{rcZ^w{F!dH9EfDRWEaC&-j3u~b5d81Sg2#(Ut>rP*9Z z8;rbnEJcULmOy>To7npewT&cP$`Ha-GMHGvdm|S#hT0+$iHK)E;?xRSLbHahN1q!U zqBgpi+~S+`K&$uGj5m`_-r<~y&Dw){_ALZu^f3eU5;x_A@ukIaFXcs_VOTEACH?0w zSx~l!6n-d_@@v{)NoXBSig|;~)&KD)HEDcHsDsVg>z841LEh>%CY8zcH&V*3{8o0N zj+MrRO@2K&uoCYKa1lkWW3HK6;EXGTmY3N(wVd9hM#}eiy|quwpw0{88&PsBNi>1< zM|iO#dnrA*#2kofnJakt8GO~?m@d#pJrii<`d|7|`5p7ZP$>GGYw}VqE-moZas~|_ z9y!4N)oXpBTZzZ-=X^S2gxr5f=-LSUtQA)#RCX)fPK5JH=qn0A< zX~Lkoj86F2e06QO8V{ki;QgWH*gD!*MhVr0np?S@X%N+?ujTGwe(obpbu$JotxtYw z--7~zM^yN+QV8n2M#>(9@%~I@lly%)iti5m{QxfB!G5u4U#^P&9r!Mm{9;yLEuSMz zj}{a+0{whm)c@DCkT@Xu%4Z+(S6p6HT`iu@5TA89ct)?x8`3No6$X4hqPB^bA}+7A z(~NEj$fd#=Pd$GDfY?T3oyT~F$H}{xYOBOUs9Z!@%P&L-!A#mXhxZDv7Y_kU8M?vZ zc7@oZG?@+0+XrfC-33HyFe)j{jz(0xAQvXq6qTDf1CPq4e9;dR57?AR2F3Rod{ZOX z089%4%Zg+435?v|E21UP9DQG4Y_B*OLwKk;*akgx-#z+-I<`?Q9+>F5P- zO^w7lmqxK=cxH)X^7%nm_`HV)ea-lia7-`4t>UEvuK;{D826@tff>MenB)IH$b)?CkcIKBSCc|>ihL0S>uhyoEj4{oNlD2H{y!#+M&8LKG94w550S{HL=qH> z06w$B6zdzoHxe$zjp8>x0*w%J1GLMMDiaKNFJQSM()04gC6$W!2Uzf^5>>K<8#qK3 z(g;bq$q*iotTg0i_g5sJqkI$3Ow1I0?miG9iXb*IuDR z+hDv>m-EkaM}WnOXQ4TF4b)UsmDiLl=ywW04VD&y?7p!vvu&bfAlms8`{qG>Y*u)U z7~Z>5i_1!v^b-twNTQ?@m3E}?8I^njs~RvOd%%d~DqY-%!pa6YN8*B-_#>G4d)(># zjlDz~xHi&*k$P>okLHb7dJA#OdTXp4IB9yu+sL_3Qn zT}LRowX3)!uEHpUm)f~mKblr<-6`CqnHnux1mHkQhSF>?uxNPIG2<2ntk63$t8IOx&t%E?3rx0*6Zv-}c*AdeY^+42WLo^wHt62|-%pGdg(O@P;z|4da!2oksr;E6G@y!fT8M%C(Y|53=e=wWAde_M{Zk zGFw_hJK~($HTb$dX8$Q$K2JOY>lt?z(b^pzO8;#rzv zw!qf*wglUt5Hq-SN1R4R0-S|Gz!1j2=+;h^kG_U6j|&dIaC-}X(V6qW#3v-;tu5l{ zKWgoFaFh>oW(b;r-n^#1F)ZHCNVu%g|IvB&q~gJj=6t+?2M&I)=2;l}dr47~l3;$U zHt|>a2D*CE-XgATsKR!rd)F@1MlP-4O_AQO0^@NM3^tY*X@8CKccU8DSIfmisrW{w zi(lPAzc1%mG%16-OPAD!TcZ{zmmg;E|FF}J>3i>izp&8kJ*%{L+_MWFli&gaUlcJb zb!uF?byT1*RSL_WwpRSImUly>qO@{Rpsc2}Lb5AtUzj@0LM&@vDfY8eOo*Z;@!mzG zCL|3}p5)E5;x{KoBwFp$K*5++|Po!k#g8R+@4 z^hiGsjl~O^&7pei*N_SEDA}ASE0bwKpl={giOW>PsT<|pFOjlW;K-P=0R~*#Mh&Hf zjz<#G6QhJo2=-Z4!5;uw5TZoG+CoyWi@i~k8fVTB_d9UGhId&`FA+Ls8W&Ug*gY_Z z$6bURN)=8_j0(gbhVUbUu=oX{Jv%YzO>J0QOU2I(B+fA*)n)SCocFD`@#I;T=Pn>2 zYfee@05+=^H;kG^dPmgO@lW0RMo@p^nvKvV%f#KO#1!(#p0wh!6=n1I8qV&OgbxhF zzg6umb9M$HA?H3u^uCEIY-P0DlOl{` znx$GJ6}J!RyMr97n#^N=4Zz;#$I6zFOuNr4)8_^cTZ5m+>NMh z+-+Caa(A7&p1T`l`roN1a`*Q#-AU3tSv`fje~|8}GT&*^JzctINcT+X{!zMT$$V!^ z_Z;b-E8X+d^SOHgcTK!Tz}*YEJ5s%fTt&T@{}$G{|?^{RtE|KlLRJxbRdS5Qx zzsh=Sl>SY!y;sQazo}Pp_bT2`Bh{-ZUeZ^uA^){J{YYFw=I-?}{|)Mm+`UQd;NgFd ztLORT|3_RssbuwLneP_$R_@-W-p<`SWWR5g?w!)@RPW;M7MZ?F*0)=_Th$)!-mTul z-EHc<+`Uh|pSusp@*Y$l;_k!hcJ4kR`{AE5{SG+}J0;ymrT;PMe_VZnyHCpSe@VJe z$@r&b{hpEG&&u%Ur2D+g_k#4lDE%)<|G%aGW$AxK`d^j)*QEb->3>6(^Ck};slFB0 z&%Y=C+j0JrDcw8jyFC4SvR~hqbUu)DKa~EDB)yO0#_>-S{)vqLRK|Z67f%qc`0)5I zr2D0$`<3*6E&bm}|F^Op-*I=O`hDDd`w#hli1YuK()}pY|0MhQXG!N5Nq3j@|0>h{ zrtXG|$EvuSB-RDKf#j|xU0u3{bWP3TuB|z!m*&Rmp(Rszik8ab)1*60y6Mu*knV8l zW=c0px+A1JQo4TWj*{+Z>1IneN4jIAJ65{6(j6z=@zUK(x)Y?kw>FU}+CIGglUN$w z;)=E>kNo*@{wb8cK%2_*O%v-}+c$9>@Nfo?pDx`QqTlhQ7k3Yk?o8>kaPFFTUWdCy(w)u6+r&#b+%1uAsf?c^-MP{|Qo3c*ohRM-(k+*6 zg>);WTP5i(knT~^T`1jZnXX2EQv?!dCFGii^ZQJ;*;KDDx;;U{Fs-&m8p5^y>=Io;*OrTjTy`G zwlcGeS%llmwlb%Sxr8PYn$pEm2~8t(SQksDJQSIcmzH%O8{Wk-^U@}Ev8=qbeY)6) zJhh9B%p0b%``D;1Haag&2mNg6=HzW***;$tIXS|~Rt@eGPPUe&bFXx>b(4Gbv~2D* zWVDf%!~LcpWu}d&N*~k1##T((p^VAGei_uPo?hdu>!Kawcq4lD4tEsRa`Y z)opB_Wd&Q=q(V(8)cvYo>t>VvYI?!_U&Q&Rh?lg9lKxWnaqStIlVIF^vUGccCbZ$eFqzX zN=@%zM^c4!ztPS1?_h_>u>C2_po$w06gtQ-EZ%?VKO6HnelK^WZ~a{nVCm@cp&xpK|5Ir z&E-Ox2?uwvgAg*B#`};v`D#HcRhE=Ij8@tC+69tWJ?)nuR!ON3-AT(L?TGu>VO{J{ z@*j?{`>$bpaX0OV<0z&AxNnM&2lcQc@}`KihvqG#49nHyy4d0Q)d?l0ynMMaI@5IR$5uGlci;mC^_foSroo$ z_h%rBd}9}Y3b2A|w5k}5DvmWNg{fnIVP(Iv6hpgE9YKw|D9Wf-$nMOe?C!Gihru)E z5`N?+Vl(8HSv_oC7dtY4J5Aw={Owa7xRb`+AU$RR<(k0u;jSnrmHBqDv4(bW2~Cin z@muTKUxuCp^Aje)e1!Br3DnQEOC+&_Xh1ZSk1Cc^XH|5u@^R>_4DwWNWbRmss2bP9 z7Ff4M>G+s3o*I8?bYu&O4C=HuB|eHW!8x;sEnJc}Ou3Le)kLhOiyf6Wwu>#gGoDTw z(YZ{!T++E1G90F}(L5>N_ka`#Lrpo}B6VH<8|OhL8Bm7a}}U zgzsc}2eWv@=Q-Keu$*jzX73U{cNSyrWP1~bQ_YE=vKh=w{goJslKL~#b`ir!)5cK! z{;F-1^{bJCjP0RT{`+ zI1PdgJGf9;eiemU8hW0nkK}Y;Zovh&1P1)Y- zBu}VGKXzHg!!)G>8)>snD-cuKT~_%pIdeDCPMo-us3VUoprvFNss-Eq>H`#P<#)5% zDLhP@vfZ!I0#liv9l#DG`UkQ@xT~=;R!(j?TfkjW zSy0nsuAs89OdZ%Q6t!7+ZEGQ+Bab>QScR=~qc)wv>?asSA$Z zi5iU*op*c>TXUmV!-SqqtW>H?0@-!i^^)Gn)IO}ixqL)uZFRCH+Ty2@lQVq>%N@Om zIcdX{^yVJc!o#SfyqlPrmes>TeBC`G20@*IfdQA(sPhl!omb3VQFbQNf5jxZSQ(v0 z>rHk#)IIG6Q5OYFl{!e7LJX)CbNCi60MZmA(U1OIK6VTeAj|GR*IBd7PIiKpHqzI_ z!q>8N8VbrA>0xcN%ns%XynU9LWnz6%2I{D_;CI+@)@@|_W|_0>EXXx$DV|tKT9)0z z)*n5~#!m5xjM<2s9V|7=jCl!5AJ@q?&~o{mSJ4e|^&Bnh33iZ*#9P>jUF`QU-$`BU zWcYl-drA-cLl-+WF61;GaylvW`X;SI=DC9g3rqKpRQfqP ziC6g`4xDrUVc9t(5dzHym!>|<5FVsCBZ(%+JJ~tc2;O#1wt5_Q&hYKd$*vdN>YN;< zI(rk~KmKJBMm^ZlUU%Nm{mFr3pFAuV!k~yV-eXXxIc|6&=AWlXLN5mZlZ_ z%1lH1yYe$lHw6zdM>jqnADOY(MYpl@Y16!*JTEPi+We>dE$q)5nL#lZ#;EeeH^$Iz zrhdD{Q$M0)CQ#yw@@T;khd!RB-o<%D=r0s-NhiB>C(*i#U7FC9dr?>3>XD(fXAmnO z#lMqXrlsX#?q0`|d)Vchm_a<{udyk89W#2^#-*gJEc00llpeNe6SFlX{R&!*#DX?5 zn+p9~7rU~DU3F--eho|3eU$I&*&WQ$($YD~_xIn^Qn&`4d9O)Qw>~aYquFz9C%bMN zyPi7ZhV&b|*-hJ6$1=sQ_prZjW&a?5H@mr;-9m}dZzZ>f-8PeIbNfs)F54ZHEW4X+ zPQR0oOe2b!gn8LJH-b~%br;*x$+ln}{-C6H(S+_U)V8s$%P|=;{d>ea$BAzU?{FdL`!v6J_F7}k)qN;VVr$x{+ z6!dH-dkzHeV$XN7=Mn#04|{=#`!({@7`(`v{t{}C7p>#JMIB%6WH0kNz9dt4b$kV$ znbG90@_sbaXnkpE2G9ChCwq-&eMP4BvQokWI@#+8Iwnfs4Uy^1PWC3x^tw!OFwfM% zW^}T*;Lnfdcw3h9PA7YZXL(B|@ydA@o@%d?G{4rv-s4?F?S2VeL;>$7bkGN#>;uH2 zgOv0S`6Qr@{>V?={(u48|lAcVsx?HkbQPjzjrAtZ;QfW9nXDMkD`bS8A_6l9Ey)} zC_bKp#9u{?<*>N7N6|RXsOK{zotLWUJ&G}lCRJ+GYtGUYxx6zJ5=m~Nwh3my8-nfV zO26srQS4b3@GL7!C!e!aczP6fsaLUNQLz+MEQME$icwPIo5FTwC}}+kxc{zBB^}Yj zm5d%Gb4iSWMp({>wBatvY{Zle^%r)svy06#wA&_5{F${)-AzJ)Dl}?EWvZDpp8gry z?WA7srfn`3rNu`nqi0R~g)I^PcCmaSmpOI6y>>B2{h1v;iP-ZU+GZ)3+)nH|gWTCl zKKh_yN{^B?OXJ;`nWc%I8<9qRHa72 z<)R5JXPOjbv!Ze=O*^eC zDs4=b;?-~}sF>Fhy@S=x}(`3E+U&E?4C^hggAkSno1?$@WP*^ z^(fPNlzp=dnoutH(cgY{g0@Z~5ngbXc~9UiVQgk%2aM(IuPoTXQVWDkZxrNj-^}~r zErqZ`H6Ic^*AFJJY0&qn9&o3z8Qc{G4r8k9j~wccVs7cu?un{=zmrPzF%Z3Ic~2LL zzA~drnNF2K2-Rj3+-x2Pgp_7pk@t8ssr1T>3ZPuqrVx@Gr*I^{nsjbudaPF+re;8w z_y3GW z1sv3^6k4|l0tfG)FmlFrD~CV{BH5Hu9102$ExVP&;)MQ@%-%(t%q})wp;?)#D5I&} zDH*0s@%L%>%f@9%Ba+R>U@R%0n|Wi9=7Ff`UE$M2M;av-|6p9a+AH4FN~0Zkuv8?c zZBq_kwpBTzQ1z>0dXyr+N-|oPGTX0iQHt+UO5lIEODP`XS9_FF-V}bgqdg=E&5#S0 z_~@L9DPy<^ZwIpq#&j!lZ;NZX!<1jyaLyPm#HmoUCPh+HnLq@1Eud^`urfP-ZA@;r za^!8DO39eq9;NKeUgSna$yG_SO8B)nAUV_wAsn9LZ_&^Xi51Z=~U1&+Ox8t6RCs2^MYJoogi#2 zHPjRJqmQ1d9=D>3#DHsIQc;4Vr$O6v1=^#m;s}9;(bGnsN#(Cz;6*hq^|0ePB^-mn z9~*L&B~8yK{5kD;DTj5^G#ExM9|>w_IsH{wvq?QJ)bjGDbSrCUe09_yJxeDcTxs%a zzG)c1%8uPD$9Y1>4F}mb%`e( z=8eN*FmYa&<2N5tP9X8pZ$7A;0B+E&gf}wXZ;=ei<{Uj6nV(Z);?(8L+0FJ7;*>8! z|42l7L5NSI#JN-~lv`+DpnrTD8?&3ln}JfO5W)1H+34Lg*#;%En-)&r()Xs-|DyJi zoJglh&Rn}qi4ZGos~|qwE({bYQ^s{FYtQUa)=k;W$8$`lvfi)Jl-c0*+sG-vWGbLN z%I~IZo~yZ<^UtcwO-hWpo84@f8GP>Oni$-uYXUQx46` z?pFSQfc$RdRCrD!o?gk3f(LxJ(yAQB{9FzYC1kOr-Q@Lg-gL9VkNJLP1{L(G_L?kc zA1VkdC|#W1(SqcMcHPS9u?0wc$HJ{M&m2k{p}{$uKQBwe#4!AZuooM!7k4XX^k*wp zwAZyaB!S_QE1ATRsL%fBv5+^TDW=JjoW0@~RGyVTMd?<~TDna+ds)73>J;D9t;#tb z(|b#MTV~#m>Id?BBeQlNCH5gl8vRG)@4Gt-0|nGHD-)qZb)-QY5>SKrq@kV(>{{7-Vg zmfK|PhyBIA>&1T5U+l+T?8p7Ze(A-2qJ1i3FOt)FZH|ytPhdh;#Uz@;bCaw(We3a6 z-@&q_ia&)^`wZoBg84$p-%JU#DVsBtzi#f8G(VbjicCt8T0T#TKNgd7jO<*^NB^jL zpJ|_q>ghMqWJ{&GllFYvEES8KHselZqhF_0zR9m+mDAn?Qt2yDcnY%3|R(G&dr)rgjKW9q1eKd(Kpk2yN-`07ANy9q`xE) zri2+k@KheA-YcZ|WsKiDMV$AS)JcD@_6j?f{?chr(9?#?J;6_T%HPh{2$?#mw zJ;1I3mYnG(UCs83&y$gtmu2x>*LEq_b+9zyzgqY+lPBWV$*SH)v*Nu*cUku(dzw?^sR)|=j~iS*2lBn|YG-8}ZT1bWJ+9m+=tzCB9s zj^6ZcPoSstbSPb%-W@#ljzoH!I+V*1yqVJj73D0RZgV1)aEB5?@SV|eJA0RVXJU>c zI+R0sxwCj|XJWbIJCv~qzAIL4Do=M;0u}ai2m3FAw?xyik9fK*iRqr}U{52sE0*p7 zo~|n~-OU}W1Hs+Vc5Ln4j_$-9>pNIG+QC9Rc56aA*zyjx1i?K~dUyAx*ON$ZQU}|c z(;LfU?@pv=cStsn5@jk$4J>Cj8_z`y;(#jO%6Ah#5dn%9urCgPPE}wQ;xY)AOxAPJ zB`(m=PRE@b0{-2^uKLC*BGwa)dPOQc()I0TIRjJP&GH7$J4DX?{(qEnpZ`VHef}4j z|2H{(0}nZESzl;hN`+$-ZT>LJJ+n=@hXmGbh5A^z*$5l<7UkZ}eB+sEWm@-|g|^?$ zGBT~`uH(1kw;i{DHdDV-(5>9Jk&W;>9KEVrxxbM12gC0`!KV4YQ4;pM8Oj4Z?F5Nl zHBps{x%n`Ve;w_o8Prdtn6cN#di}C}^MGBp{r^>$Z6`705f~?W zlz(y)G)3)zaiLq;xslECr~0~;M+?*ZX&K66vabY+zDlFMGJ5x`pxV37N}1_Et@@vK z+Hb6ad%iKWZ<8oJeh^BEZu`Q7Ozs5PYZdB!tfP_y+sC_=Cn~2X1-|JxdFWA|Tsp-Q zIRB&lAafs0-A@V!DSej<$1a=yFh9Ic-p2ei0x#ah zE+v27_RZya+w&jbHuO>4;yap+;cgu5Ohdc4R2aU0!A&wwJ4GrAN#e5xQiOlieq%{e z9~8RJXwuAHD$h@w+O7Qi(0pIEN)zzq4CNJ4PqNmi6yj_&rsl1vG0}v^6>NU(7b_DBSdWP~^OaXPEDvyWqKAQ^AeNlpYOM-q7 zgt1AQm|piRO1H{7sGg;UZK4dAks+xiy|DwjukuE>@}^({Z$Lj=%JnlKTa>qspuv87 zcA_mr6L*6(ZVU1IHP}LQti~|RD6~LTCC(o5RGbp$XZk4fS2kQ#e`WiSVr=Nz5%l*H zucsazq=dfodF?QwO)r8KirguP=k}f~A*b6t$~!&EySHvr-lN&_etACke~`aL`7lAH zA4%lQD7i%Cv0M4bE7AJjXw?kAuMv05AuiZ&Q4Y9OMmy2ym`Pv@v$~a!y%esV+z){Z zJo+h~4O7ubu6*vpMRnW- z?@_+wsTEvN7uKD1q}5GLOS^{cYfwhqxS$Bi)1!O^Q>owbP4!zU{b}^4Pu;2mKD>nQPkFlnp=w(5Icvb0Qh3kbW~ZWAbsXHv&hDyIFy}^L-nn z17XqP^5rg8Waz0gbsDiWeVFKyq!Y#AT+$@I9?NN1zw1$!SM(^~bJLcxVO)>$AMo9) zl$J~y(I3j?CkciBr?o2qkfONS)m44WvB&Jp&g}Ni4$E>gGrPb7i!6tLa>%lvtgtMo zK?N@~#%SUZMnVLQlQ=`qk0Z)zww6U%h7v($q9IYs>2677W7Lzn{}Sfd2F$N&(~6b$M;S4gI`_ zU0&1<4tFLArJySi%Iwr$(CtySB$|Fvz~wr#B1_Nv?6tEM39NWHM}~+NdT}7zaPVjrJNX1C1EETbJW}9I8ml)rp@|cbB5W)>_dX95`A~Bd(0)Xq=9Z0 zD(ukOt}4$saF}L#sHI<+LhiF`^#DdkZU`L1Z!slz?Ijqxc4NVl<#ZM+)Jde`8*TZW ziT4X~a4+{iX0yyiGxErRB;|!G4O~z@$iOOee6eI8rv}Qf+0drPTCg$5HWWdDnd27w z(Vf5STWO)5?w2m3yJ1G3_uO$~^#|_6IlkaV-(i}uFb21D^#czo?RCIMY3=_G+DAeA z+T84!G9Y?mh}}&ixC*|21iJz0!R^H$h!J#Q4M0KAhcH0xfg+3$@L&%}LCA+Nz!{PU zLqf>=*Fg;&5LAWQv(=~&WJMyILXZR53UiWZkAONzb2!$tmOCf@QSGZdb z0uVgJ+)*K)3KUm8y~o$-3kPIVFgf2>%;e@R0UvWk#aeshls2a0FQ%(aUXYV4iH(!g`WR^9?$!j~GYItT1thWCu@xs(M|geXs+QL9YussWkUZ-NzsqT_-qE=I}K4Q@~K$M-uI#{}!u zsUfGNr+kA>@eco72J09K?eIF99UMBFrs;xx6Jj=sxg+Z$%2!C|q8mTE*F^m=fW!- zh#8+`;#OndU62rX83a1ot7VsJl}kPyVCY9z;`V@;S4CZ5 zMSi?=5OEjXK%l%rIu52K7SR1#ZCQHd>fb!O7%HBLX*e{Gq*ME0upf{M1<@4JBYAUD z`Dviubp3t~Z{jZ$-44ivw}!^H78T!Ux$lChEIL?dfto#XH;=EC5fD}5L`p>H>Xa`4 z0Gr+q^YY`C)7~c5XU2IuQ%uO8?=haM&ga8=hJ&fc~4zQ}MPJ)`R%qijw~byqw`w1r&W znX{mo;s=?dloW~jRsfut*8{XFxbM55;-!4G0&2$}f}^X7C0MtMG2JI;inLP>%sE?Y zc9K>6MyU5#KWm1*VAKZ^HHuq&Ri%7ol4<;stA6&1@3ON`jy@~Y9_CdWyDBt&D%GOW zmybP$c4|;RE4a>$D=OYAE1oOQ*FP&b-U=(ryi}Lo)$VZo$kv^UzKEG9gP=r!c0>w#m9#T*^on!8ANM%rd9@!?(prefuim-=rg5ElEl5%8AZSp zGq?jJqO`j&y9DRW;Iq&3AHa^1O~S))nEHOdYoc}qV^2F+o-?S^N{r4sAtcWVE@X-? z6xJJu+M+2jYCoH?+Yb{ft}$(A-PRAxk~tM{@k1`Nrfr;o8c&OQDXt9Ddy;+P zd@Gr2S)b_ZrH4^GjGq$44U)LQwn8?N$eFRDG`vIF@>^4Jx)WJAGSGu3?u{37wH*A>NdGz{7?%G{37w zYZ5}cquKGpQ=bKC;;#1fU=OFaAD@2m4*T=&9;bI4A8`1x z`gvEDWqtfI&9FXor~Pqagzr?tG-~%Wdcf(M$)7WJxA}QjfzvyPUpsTh{ds2+yK@9s zJ$~8xa;F--xf=~TeChsj*Nxpd0l-h+y?@@l;oRJhiXXjz`FYF4`VHdG9=`nkau<%} zKLxNKzX*SEOjWSI9UM4$C-QG+9l*aIC_8#L^82dv-j^P|5XM1s#G)=ryFnWJ4xq&h9k>~!=;e5ayYC|r|{*CD-IkGj@mWh!Ie*PK| zy^|bGPjCP|)COM0%O@6T)Ohmx?Ei(=+${C!qj}do)|9Gr^V@Q#&&Zb`>>EA)PIM#9 zlbZ?|-sB_Sn?+A(BsKhwZiyam?1eb}U~=q*X$!O|xxs|s0C0rb6Bu2q5IecphHDCR zP7%tV*sNDEp8t*A)d=uX2Vma zYp<|rA7>Lr8pHiDJBK|c+>~|gCdpq`hKc??_Sj%(2iuZ~Q_^1~Dnj!YPiTAr5<0m?sqSgNUBeOo< zDf=m}v2PY=G*Y{1$yM~%+=mZ(4zus#v2U$IyWjWh8I%1e2RBL}Z!0L&F$0pj-sAy; z)7+w8@CKrncA{VM3yNSh_FRZ$HEzf+C7!Kg-`MQ~Lr=Ao#Suz}RPy8#5f|McJ?-1n z_s{W_S#5l2O7Ud^H5y!q#5I28w@*(uMBfPQK<}BW^fVtOtx+xZGETMkME(vV{4p&$ z53+Ob6T)qK9b?Z2so-WkAqHPbasK^ACb#03ZQ4H@Y%LFA@3VpMKP-zz$on>j9TBmZ+ zJDd2sZCk`$jh>36DSORB~9m|ojV;6G{=5EubE&2V!c2bB!T;is#O7+Nv1cMN8m z-){6%vnV2(ik*1;#1Z#t;W`1@H829yksH4@{UfW!qv~t~kV9>debu<TAv>JHRwQi%r0j_H}In)02<7Y0T0eJl8c|c>9#V*S#$sUu}PP6=NAZf9L-) zGyeTj%SToFwW$AgNpp1jrtt;U70T5MqZnermRPrmB5bXX*4oX zs7Y&gfH#tpQN3RwQWx?X&E-TbnJ-f^*oVw(9hY8+RMsI6$FxOgsia&=tS}dF5@ZC$ zd6qx$gmmGNiU-ad1G#!mdNk2koUnS-Y^sqfxQ4%_Mq*)vd=0~v^(mdqL%nLOKAP$V zRsDv7Q3_c11s(Z)w<~rU%ET=2n~C{`4q#w-8LFD1>6^A}h2=b?sfGF|niNUoNc2$#ZaYwMJutWD6>a|DRda)mYV*30?JvTzee#n1bj2Rkfd$7_ zGN0M9-Z#aQ0n0MvXKQ{9!T#r)O~hrZ&CH4{BQV}BlfkR39LYvrjw2#sM`=-WX44iZ#$h@ z!W?oyfkXk)4wx%@R*fiidl980oE&Ys3dEoMai|O)!$;Cj7P1(hDZ_=(AtjEv$HxJw zc%e?018Py+P-7}l-cVucVSAWL>R|~O;$$P1&;WOci~U*<9TN>*6L)DLfVPA3PSLex z9mUA`G@%Pu6R9m2TYdgq_JU~qpL!&&Qm+_(uv@dvbxM0l*tEMu7S)_XtK7XFV-y_N zEgDqD`4&+kBSRoo$g%?93bK+~vKY_CVmWQ?V1O;FYe!7%((cHE zY_*8|lqE8ywE|v|y}}JSsNT|UiM`4Vt0DYgXS=fh-V{1=5qmG)=$ z889iy3p6?eo(9oL6)+>2P=5etvsVBjh^}LVSDVvJPi0j(xxj0b)zISUq!%sB?Q4|P zbbv12uu@+UiEaEHI`XbW`C|}f)=~xa)kdc@RaK2H1o6c#H8hi`tT2&X=IPH))i3L@ z-R*e6P(jLywbUB5E(saCJmhE^=O!;#fgEhnLpWe(fIe(wY+K~TN6GG z>P{%M7-kI*C7**Ue~DAw%V@Dh2J48>#+I0v0YBM$^EVY z8G80_3h&R=F}ypP_J-|mDHG6<`6bw&qnB*1H9G=`8KmVw$q-m68WM)sG}Rk_r946- zD-2ovLXFGDc;>rFxtAV~W}J&eG(~CZwC5l9iOQ_Nrb$fa#1sUZ`l|g6u$CWY|Gd?N z2Chay8F^|=K1AiEn>h=%ayHP;>{5A=t&E5@k}C_ zPPitUFDc3;aL6y}O%Cnd9rx$~jV0f6&*}lOFcIt!S(in&*bo{+$&dga@4@mDHNArZ zp4{W)KwAus>jPYA=}nQorXPBuwMj&`2>;?nSd+YGfV>pj^+@oN+yw>s7xyR*%r7WS zN&%up5JN{FCzg80j0@RCvZ^Q30szrQaz~_g=Z35!xkUnYi_en+rZ*nB)rs^-s!uyH zp*c1*+mtk*(1eyE%8-ILx(dRZ+$mI%V+D;onem4?F%IiH(Va@g6`GP~b2SVT2$U*p zS|c3t{?POX<(Cyw(!6a&9E)dA0oDfB$VIbo!nz@|nmu3`^+K_NpL=Q;TtNr%9mF6s zCN{B7ijGOBXJldnGrldYo_b)kj}9~z$yuFrppJFTEC;~ouJ!9Oi-DuaLA4v+rwDiQPr#iG=cF$_ zun;Yl1c%jr&E6M0c-da1KYr}NT>%S01UhI*(Swzh^^;EWSi*NmAvTiY{ zGl7v5k=OxaJH0nf#=2Rb>^OX&2nQy8`wn^#2Qv-DfA7rgQ;9+Rg_Km^{f*ZW&a7ZE z3~&CJ$HrzM7+^6tsS(S6f>78(G)tedq~-i1VB-M`l}V9_J%7)tv)Y|-blv3&@0zUV z;YyXE64uyi;Cu9fJe-T?YJ^5;4&l&!N*tKgm_P0URKXxzK6)4fmnj755Y*$o zf>Btqa_AXUX7DCGA*{?Q6@y?mzo4l!3X(`NI{45f)crof#)OP<`MhD33380)2=KuJ zRTl=>?b8VC$SUEN$AMBLJ0^C_v~`0gCXUfrko9YD(B);2HE0#f*pznrgIF3{NmCPw zXdtV}Tz8VtCqd^GLZ|Yo&5J4?x_uD5&C(JsI?Q2pzfSDFecGPySSX&^ASLi2V%ke6 z*by7IPA{A6baHO|qh5$Iop->#cDa5!($wn`9U8eK`!=OfK+ZJbt8w68ZEEfTo zwpE@sJQ=%4^Qhd%CP2Harg5Q}&aqu-jSu(4G`9V6-RSQRFx)W4d4)my%~XPV7Mg)j z*dP|3;08)?wKH&q?tyw3YicBy(`Ea9rXZ#Sm!eSbNKQshjf-Qw$+4DUjN6W{`{j*W z14FJ7v1yZIf;JF`Mx|fGqW-|3pJ&`=bi4xh0?eUK;A&0kYHhVT5VVncC6pYf`tH2;-D)1&vy^0Q-@3x+DAoPcYL&Jpp-E}`010Khyx(yV3Lf=n}72Q@}f3k*RfO(8hjvzXDSGlfAkme4J*7VdWKuW%M9KOQyQjN zv7|DiKb_H6J52SeSCA@AP8f#Jtf^bP=`*7OHr15NMyH1K&p-!ls9SJ0B3GE_U`qSr z;zBI5YvgbE?r}Zc-mpMc-~=zF*NBcWyQ>h@s>?Y3(YJD)P|d^Yv}gzqNN3U41YE8r zpsAZQt=Q2r{?(QiA>=6hCB@$;7`D?`M*XeyJV9^l88^kqSF=2R?OmdMZ`Q;TN`%~2 zfC|62LYgu(qR%*Z(gb-8ghdjHsR0fa9~#)w6V*JC>1aK&-oPgLlv-8o_Ak8Ac>l*| zyMGDOl&A%7h(8c=EqNyKyMw4B*>-y-fZdjft9vfC1iQ38&m+Zy%_*8qQrOos)+cZ3 zBmD%%QLV~priOVIw+`D4``w_D%V*o5beQXiZt|ncqlZhk5bUvmCGtD5d9xWCdSB;c z@C1Dd`mYydt2>Q2uPh<#EFgubVO@BhBZrSAZ4UIq)Kdf_Hlcu{wlFj17oGt^pTg3% z@Mff_1E16=QpxB`2p{qG3!;i}=^+>^i^?-Auo?1Eu zavw)kH*4ExouOvhh;x5dg*G#z10rL}?m9i{P%2_aTHd(7 z4)8ZRdw9u7^QPxevGOBW_nF6IPhf1t!~!JliCe16APmbd-cxGaxPOkOWmN<{k(#=D zVzqyviuow7;``J1n2V?4^IOKYD4E1t1Uc(Hq;nF#ko@P^>mg=@Z$@&xI_{REPcyQs zu!|WtctyhI2oqdesnh$dpO#y48lH41&&&in!F5N<8lQJHiA}N3X`JmT`q}crs~a+! z|F8(mQ!-t-gPkgrjLKox8yGjh7DvNyj>wv0NmvO|LxMEzg_ zOdU5FGDt4}%~Brtmh5ZKXq3;`c+*qEZ%7TO!&w)CB*AbL;t! zz#{IG4Xg?e)S?ef+a9I_wlxbL$97!=&_!;>4Kxg0VeYmU5?qU5f5lk=@vnhdME6{9 zI=G#<}MO~e>raYHnBw`LZhB2Y%YQ>fO7{{`^tiq;31=!QHB z`U#@*61xI(UdmGc@`pa)4!~u=2cA=C>ifZ4P?Ix*;@L5oB#*#@ud+x+%ApAG)i#O< zst=<`;&-MdCh+nqn8RV;#id+aTyV){+|Lbf>^&wa9l#mC6Bk?>_h<>{abm+hPX@0& zxSKY+g{GP)a!&oJ_|t`YdFK+a%fOx8aKEUNVG+0jjx%aVo6GIBODq=|RG$5QMGY4Q zyb05kSk<}nW0|G1dLA-RH%<+|gcKH}t|N#z$ogO;<3!gQqL?3m*-+2rfnS;@GrTj_ zveYW+8Z==+!#`_}MS#`}d(;Dg#=hq@ti&qlO_jQ(YY^xU8wplWzNekYU(pl zKPW3nNm{pI0gKQ`^7Zw!q53XKXFEntW_ib+ebLC<7(GysSp9pXG!5{6{&nmvm;d%_ z6wb3y7<7CZTVP@|KMn9UDqpBMi9An@(|+e)G4w>LV!iTASQ43>l9Ie{#b6t~&@6R8 zV0C5cV!>mV`~Is%7?VAhauYJ;!-ahcrCeO2(21A$=xGefD>XLpB(6G%B!Qd)YSTd55tLQJln`huPK2R zWWK}7lPN(579kd~nn-$uW|j^WoxP;F-Udw!Xo)Jh@hs}=r(L;&sZkp}6dMENcyY^95uHd{7&yERj-E~NCcO`Xo{0L4P$&-#-(jcGfATBB+T z?oouiK=ogzx<`BGR8;p`$LX#mH!tgWR}<-HDuSI*u)+E! zaG63Uko)4h;xi;QPBGlz`FR(*y-1Tw&C^n18Ei~#oyhDB$0jBVVr+bv>0R+W!9v|( zi&RY!YWb?ytmF?yUR(=FAugu5$usi`L#uq+h{)iyM*9S!7RRtPeKuW6r?L}ONJ+~i z6I4mDG7E2|H33WPzTW5Ka_8egMP|pcCBy+%s_@f?{rm#`!Pe4Z6Z;lPY4J5rG4MXT zb18ziPR|auN&u(kCBXSN3p)5>jM4FQ4e!xpzE&F&2WMkywSW6b1m&sQY>k+HdU{)I z2{3HZw_hni&L$AO0f8IPx@d7DQ6|JDMzV$u6=~%S& z16szV&Sqnafm$V&(W?mwX~B;?PDJ_Jn5yBL=+T3Sz<*{@i@n+vSC*Yr^m;PlSXY*( z=64`}@ubiJ=tJvCIugbYsAZY$TzI#-YES^&;Fd(=N70S7_r$tc|F*0t)%nP(yr;I>&OIP` zvsT(?SOLZM_VA_9^Xq0m$m`QyEx8=K!DN5~c}>`wPCNMngztAMIk+bIme>Ue8iO7C zjX^9YPrEN3WeK^{zW6O(c?iKe36>RGJwv?b0oto-Bfv9Q;EoEiy>P95%z~#K96pb$x_y#+N*gn^Iio?}x$#?rnQAN;?i#CZ9RD ziATZmB`t(n9~HQ7=;pOm@M&ScyF^rEKdv+*BIXBZi-ZJkXX~dHyP=R+HAz=g{M9)gfj;j( z)eTG{S>25Jw3jEgf4{n5S|eO;6W(BZinI)qKQ?VHNy5KpqM~+$Kj3>_@QI_`BB8l| zObJVrM*H&8Tu*6enhV?I)Q7a_j41c!=!~fM=;-w*_v%8vza#_*?K&lfB>%Ao5A7g2 zE_5FV$_@A-Jjw?LLV2zR2|>Nt1%8DnXb9eTC`1Hqlb`+$mO_2BgQrrS3j?_lFVhhO z=bG&=6NCgk1Hpt)r5t z(G!FPVI^V_6g+^y0w^Q~k|8Vw&xwG_l2{-KC8DsT=a~hQQCmO>iW4qX<&}dp6R?O1 zE<$9)&M87=2+qj_mr$Kr1ey^p738ghObc3M1jZ7cY6O>1ThIwUAY|m{y@JpRSd;|D z5?c@oiBp#)=ZQj0D=AWY=aGWo0_Jc)vjygqf@2jGxCP0HoT~y;5SwuF+o8@0 zEI@@GP?jX;q=JvBEl>r?37Z52$&sC*ghnY(oj|I^Eb;^8NKaXUwNRb81g#LXit?I4 zvI7({17}E1t4OalhX7UCQ`;jmK63n>PMy9pY+J8M<+;-Ie6sx7GZA0l|1FfauAXc1 z0s#Vwg#`M+I|BjPIhxa(8e9LdcQU1Sa&xA4`(t8m>g+@>>S}81{2!fsj4HG`wkXOM zTEZn6%|v50+J=G^b|A)Pg^!@BM0NR+k#-r1{-l{C+JNnpEkS+tlZ}shH6V9BM;Tar z<_`R&kY}5l8-Y3quD*)f+(=TI$kW@y+Z%r~2@{S5nhyD>NeWu^WGCLUrbqu<-)Rw$3Rr8^H3{ z=)@Q{m%+DUj~O=C>P2WU6{}~zeiN(5?24VLNA)fnnAOT_I8ae_j{P{rk#dq|O>|bu zL)zDJeCC*DN4og;_#e=C0|`NI={wbcBG}jEf?|Pj1&x(^*iHfC7CTxBh)ubb3JQyf zbSO{u;)&#FqiweCrFO-M!Z?New*i)*+)@*d$(f__(7Lk}++6Yobc=#9ne}H~alxf2 zY|_EUY9!Fu1*DVe^Ri4*N$7gURTgjUQ=XmMYUogQ`S$Qs3iZY#6V5_a!ct@F84VUC z(82Lyx_8peaYfDy^CL8x(dV*{KU&&WTlmGA!vwOJ6CG@%3N?(Z-UstK=;p^XiLDY( zTMg;06jXfON1=L#uo1?|O}Ygfie6Q3af=-Whd5pZ6PBo>`#5JYy{l_Gk%GP9e-LaH9%6CDr_zvRzJy)$E!ZImeMF-}8=kM^g&HqIOZedH~P;Q3M zw6^-X#%t00X4h`i#df}^jymyt>VAm%AgtceC|!Qa*&v*F2~`l{7y{lCx}e>IxQO*I z&9q4?M}|d-ONN6g$t>(cD0eBssM%di1Z|c&RW~CiP?~J4y)yh~2st8NMhrHqVst(|7ae;0f6qV!Imh%b!0nOO0@osQ$@eJj4_?P9_pDYp@WDCI)%P~D~B`02gT z?}`SD7W)b9OGcEE+k>Mmy&8AH$m@EIOdkr=8w z;sNkZl0zzq-Uw;{GH~J0DORFSRF|#=5!g@ywL(I^Da#gka7Inp5{`(7#lNb252{xn z!ncq!^!(fo;48eq-ZL=~bV6~9ajb&M@MToH1KjdcK?#bDmAQqncHr(B@7Dak^vT)S zNtG7e6jn5D;tIyGNJxSXs@1Dl6TgudH%k`b1yehBAtUT-W?7O_I zkFJ-{FTQvH@Gih8JP6OwfjlVB)Q3SI@l-Bc+r)=LA3XC9UnpPy`l$E6vuoKZoS6j_ z2*?}?2#DhU$}SZ{BWu(D1X!}_jvcZ%!q-3B*^@^4IF>Y{%R<6m=Nur50?H7QFY#hF z6(~zwz^HhU5w-ohYw&RH~vvpU@lPD$PpY0Toi` zGuL=6!oW@Oj*Z;h57VuU%m-oL-d^y1gwbJhp}x>yaA^{d9&tQ%VE*HSHIB3--Gt!?UB%p}H92N3_by>bG}2AytwURbd99KZDZXu?oK_hqF?gmd`)` z&aVfeyToTxkaCu*2f7bvpd&p>=;5x5ArTH-VY9iytr^B)H&L}HvFWK!lkzby5v~DE32pssKZuB%;C5Q8<|1>5eMZ-}&_H41>EXR2Z8jj`J~ipyjzmqfqn2 zn@|a>2{&7zlGqPmGJ2q^bTxg}p+C)y-B0RGM8l_w_I3)EOY_PRm4@0u>MZ~$hsQ!3 z9L0!pW2Z>oqJsWGHimp)-MdhOZ5(?^ew^iq(ajpgg)5`~%TM~Cz%HFaU2_4A=YwL|({bMk^* z?Opmb7F)`dR;<=W)__n?sa$ULm#RB0aFwn*>^2{5E%3PAm|PXOtOj0_B79sdNf*gQ z{EB{o=zs@aG|`sHNFSki#+hDP5s+VO@oUCN5e?QHz zeg3p?Yu&PG-E?lxF)SkNHwA{Vw=UIr?^3tUXV3e)ZHU!Hc z50^S|3YM=xmO6!A1fz|F*PYVg+Eqr8Qmt<=eU=rc7V#|;t+B9`WItakXDEO2XC$hX@Ov$=%0BsXBlHR zUoH=qd{=FIjtrY}_s*hMs60^6{4;p1S#z{00)3O(EY@$C(7Af%%^eJ)L640;=keQa z$vtsoTBgQ+;!>K4mcNcR<8N}yoj*xE-Pa@ae&Er ztVSKad5Z1{VtWDS3cf63eXgYR$nSpprp$8tvUdJ>hQjiH21IBP3`aH4;)CT+GOW#w z3%0RO!^V59z#hn*SWoc@~&AH|HB!E%CLR0x+g z&_izE9Yh2g2$zgO-B+Kwf)X%>xXpp8SVLEzl#RUJxo3cD7<@M3f-bFIzwgLOx(hjw zecnBT219ci99h;se+NuJkj)58ZHJ#6qZ-v9^R+7r7)nqZC;236eO>0rTGg+ZkHV%* zs1UImAg1$&B!h!xdKRQJD8o0Pa^Oag1Q*$w8(Un4eM`D>1QzsuA)!6yXjHj1Dp9F` zEo2H9S7KgYOH!gRva2nAyws{KMAI3snOoP6HZaVNncAU0Q5Q)|WZ^?7n&F~Q$(xNlVix4p*qDOB-K*Hog#{sK)MQN705xPPDR39?#-}Mp)lXs|HY51M_8d%1cp0(1e20x>rf=Psb-f$1+sTeIU64vP zopc19xjVH5Sw>%UXEp|i;WnCAiAskd8@N;9Kx>ZfAn#6PpnW{nAYpU5>B2tDG^L4(^p_^&>wkLBLIy0D+2xx44b4}i=-o1<{_zn2zD;pylW z!RSx0*86NQyFz5J);rfAw_Ca3=6<*NLK$dtVw3ctUERmB{;TMyo0NIH?aDh|u$w}2 zm~Ht%Fnojz{6ly~+6?7)?hV*41~-hql+M78P13PooF;YS;IP*zF?ev;-V!CZGA`8;uddHhm&2HN(PLaV=aw?Ob%-f3YhJ+r%jvAfYz#Tb6e z+tC-_pqY7(pq!FY7+?Qbj7J1s>)W6?Y@g&D*3-6VIin!{nOue!!jWrYm=yej(e$oH zvjy5|qcC}_56Z8m{@i?GEWedq^3$Y|T;>-DzwJEqKWXLvv}P(DSF(@eULe|D0?RI< zK8pI~BS-*~kJ1J?bm6wWH2-`v#9*<0?G}2=9Mocb!1yiinuA*D9P-TcaY@!t3wUJ#2q5oY!!%55!qTkHY34Wc!I9ey6yv zPT7gcI_yq?1-c`BPYs~GOh8e(&6Y7~d>kKPlkaqr~ z`OOTv(;za{UqFqeG6gNZ)J6-mM{P(RQK2F80u~xspwv*bmSc9mw+sqWSFzl#?HCUV z_zJhgF}U5}{E$*GxSilcOpAmE1p|hS7ij9t_n|1y5OO1khkyY!z9U?AHbHcyK)1M` ztt%p-41&ubUU0S+fzP@ks5seoLZ1mz8nVvFpVN&2?OwN}oN}B;ivtzc->o~pN&4l4 zY({~Ik@Phz!w-NedGg`}XVcXE<3y5jDZjSGFQdL4vr>nBCJcj&+6!O8cyFlshgG|t&6DephezHlXWEuC+**FxZwQ7ebDeY zm}j~2vEH!g=SCI1q>XSYKfDnX5WbFt9j#S?#2Y1NY%$i%*C?-LR1#|CR&i#uV-jLa z=lK=juM_x%`N;ID2T!g|Q;(pYu@!Bs*$s2)wub}QhFOuBabZQnI0H<)OPaKGkWJ|- zJ65mkc&?jd`gAr`r}f7%x8A5>`=SQ?#oJWzD5;n!h%06$rIZx;9UxO{Wd3+56h@WJ zzTBc0lsgU!K$>GUczV0HaD9v@s`+x)fhyz1J7rrohO&xZ%kzX3WW9^EN0~K@iV+H7 z`IGOBr4P{@v}cQI;%Q?Q^_J*G{xc&F%CxoM5*gpGZbXx=`e*#{9paDEMz%WuPoy#6 zcS;hw{%CjN2D3}O_7oLb@>(L-!H(VXh>XD&ZfkCz5~XUtMDJ9S0T+>3?Y-+9izc9h zw@s`4u?kn`XTYxedJ|Dc&@csU@%JMEV*Bmj@8CoDTF|HAN)aFY#$G_>jgpba`Y&Is zMkUAHT#1Ke)oA2g^>CTo{MikN7`6e&9gL%;ZaEJ=o9Ha`@#b3FEXPgnVbUjK4qYC3 zzLVuWav#{R6=SqBPBIEdPAWuJDdEtw0`uwVt3rBB^-$reD{6mSCu5d} z(JjUmt^2s7#Vyt`XlFCT#0jglx}@kACK=z6eAZX`8P~)=t_j7DC?{$`iq98~)^ws) zEk7*jvhz%tR=D&FH^ek+9Ne?unpp*@z41;cmdC1;ewmjNsE3+JAG=%?>{yatD(Rj8 zIcZc6)^0EV&Ppz9novK(DGa4)q;o`RJ7;v~GOonl^W>a(>N;>vl>bfMw&dtiP#8Bj zARSkt<5PR^56bX>GlO16rK-j&$8k39eGiB4VVizw1+uMXLm6gOMMEd_U(j?U%R}Jk z)$M(z52~_M`m638`EFdT*i(g ziuSC**r#N)QN7}pWhA7t?l4?~ck>?61LuodlTo^#U-3DusM80|13jk1X$p7O8Yj&X zk;u#L>aB!*JLZTc2p{csSt}-IXNL&!Xc~udW&>YrFX3ZND5qAmZhTTlOl<LoyYqI+e;N|w+Ef-VlC*XByyI7{eot?<3ie-Y0`V#6z`?QiQHPQv zFEH}R>Ni4eS#}Nxjhn&I|5bzZa7@?~TC)D&rYs!_xqHA=o4EaX)P)3R;5I2AV0^a3 z%0u7|6CzolaBMU0Ke!w|EMLU8INz7pnSv*6210!*d;{6wHsx*ys#aG^74?b?v5#wGM59K_J*usGq?>ZNcU)HL#i$p_(pzP;tE(#i>I8Z0v`NsukNj~m z&LM|y--{I|vxqW|*-T^?($=rD6^ZM1L-*}utZDFD{t;(lGGzfD)iBw_=pFM(Wjw%e zI;|Y0{xN-}nnDe~`Az-ikh~NeBQ|JEF`HayzWvT;FoGprKIao6rE;6pxt%W-Gt)4c z>=XjC-M2r(7m@c%N?lBQx+#!$>ce%B*ZRjYknzCznwGDRoLqLwHejln z<>Mk-1=dTtWP`-EhxmnRs2=?3SrV#oDq0})CNydh?PQPwnCCOibh%04VA9U7@5g^R zje^ClZ1`J>X{x8jG)%|ad`Ou_CKxwDjT(@KHj~=;Q=;2Gsld>of@F--$qars{fvZU z7Kkfs-*qHIxS^yHfVfr6^I)3(Aqs{PX}go*vu#e-A+6FaF-5CoO|{lNbL-+p^6+ zNI|!mJJt5sXR4hWmJxHNaIGW_xgKBZT55PLVBDy@{3w}ajUOk6e%5xs3V_hj(rf~T0^&n_7k z`lprg;i{GB9SB7lV&8h}I>l6PDYvAdx(8n)y~<$@(QFvZ7yJY-EwSoWGurg~t!AjT z(W!gc2V|wd>QYD!lI{5In44Fkl`xmwEqIDtY@8nPfrj=`EhiLNBIQxt)w3GurnNwD z>H0?JW?VHB+4;6!k%_lG`Ni4GvHRume+WB=U{RuONgvy`ZJYPlwr$(CZQHhO;~v|# z(YO0`ME5`F*Ml0=q$2jDVy|48UqY-PYTy*q``ZGBfjmK-W#o&Oys}upaRgY!lzvn& z!NRVHTmTy_jPgWK$?brQkSvT`iyFmrNpp+xy=rbJ9__Szh)C`UG%noD{3X>ZncTuN z3jPSVq!7|-LDDnk-EO=_O4)o`iFwcU27lpwpO12%>L22Qc_^wDv>eY#`=#3+#`;F< z6UxfL`hKx9{{-_*dojFri$8Ma_yard$BrzA*Hw;Mp1GHkKfuV9ZEM$?B&cNv?og9o zkms74wxL$1iFr%0QWfeM75QZ>DNIL3=0HLwJjqK9qnxPS z3<-0!#@eWlHLl1LB)aKW1C;7RL&~~2lg>*RnX^^Q>2DP9v%{M3s|9%lo@AVvoO1`~ z)}>Z^25|6Bn6I3nCydT8LSql}<4cDaA3X45UiN%+`dfca+QwtZ_i%D^%MT=`y&2^) zF)U9EAoF$$4<_);2TH2ekb zF0fP^W3KB{Q+Q$=F1J8x%+8#3*ZN7WyE_vK=>Jjrp?^i-wWD#peSzVf+|#GH1H0LW z%Un7cqiu6V-76nEvM??l^w~w%_skyZGmfJen?u6dvqC|Hc)RV<;3ZBH4+v+3=npM1 zHYVlq^SWZnzG=?az30>1_ou8IoG`f3)cOV&^G*jI8$?Iy8iAxd+Wo_zGcJfqKd|7@ zna@dSu7tC40@EGR8ZGFI&Kz}n27}ZN{(5}CmLf;`CZX3V-fKb3=E^I^l_8eah?8_- zJ=xQ2BrReq`kaUMniosSm0@YNJ_jEpl*&5-24cw$FHH!;6qrhXm<9!xZ!gdm(D{h4 zhJ&c{1TevVurcZ4@E`PBcZ!MxT@1Ivy-$teMU}8Wza%n$CduIh`zqRB>$pUDHNWlShUuxU={G4Nh+mGg#%@;s*9` zA`+5X#+e7LCE(wz4rOh6at}4gQ-$bPp{}($Q*?Wnl$+U(ob|Q9hXWN#vkQgnndpJ=aVK;}~3+M|33?5wVDAL?6jfXLm)>sFyIQu1jq*8|7F$-oZa<;;OJ)Op@Rw z0=AueTh&)+seL+$mFK|SmXtN>qp8!Xeb`R#htBCacP83dNLp5^fb*$B7+2`ZHb;<5 z*HmJSe}>;;91NOx3)E4 zk;yIw>PUuYY)GOv8iQeXvJHisc~?JYK<$$;l|DZ&6}ICUB&&g)?{h_T$yJVhEsOJ^ zl|FsMbEG-XPMd@ULZ2yq8v%+gOFOHuXWB@Lb^K~%{F`A_ERkqpAv=sz9P9Vgpt?%r z#7Yxmjg}0}Vc2JMW*8!E={#OfL$v9@6L;+*NQ6R_4_hNK8%`*snSx;TY%{#8)^#vF zzbY?8gK}$WE=68+Uke(#-HGh8I&u>=N*vL((TK;6~JIY^N3Au@y6RNIE1~A25UE%b^c-L4*exGq_s~9Yis%> z+YM{*(0*cVvvlXWRWeZ1z5h9k=*r{_?hv?%(TQxgbVs*UGXRKoZQCw1N7*(c69cPWM{- zJPOT`J=ELl7*B>>b13~jKI<29Z}ooOJu2)D+2(%LTV`n8AvRXGbnbB9 zJXQCg^>3t|`09}3u#^8Akdnx`@)Kh61pxen_DN%t$kOF_#Ytt8OfdW* z@=7KhB~uD9i%IDcRiz7*J}~fo(%{MlZ6g$E6Qex@x3OXkB$?;oKg%b}(IN|Ir7)O9 zNWvLU_K-F+i=`unWZH+L*g8|`5d<|Lc#9OYP!tuFFq zBPrw5R0#?F6&U9}+OuqI4wi1NQ-g(~G$msw${Qh*whiTh$-%_9&5H>)xBIi@Rm2M( zrFgQ~V&R$N*ESSbWkx~?;nn|2*=zQgver!ht!RL+0aMBH77B@3(T2635=Xl|(}|Kh zNFDN~XOFvWrH3s!P}nV7I688&&>JQAFE1If*obj1;Qo>@Wn3Y(6!(%gtWfnYm2cqA zHYp>g)$*F`a3Gqkn8>k=sLB?%FP=>BEg(G9*VG{>qHz9ceJ&FY4{zj-$y{!zDpGJj zq#8TV97i9zcz=6dt=?abit*r1t%l1^jZK_w#vCeZC4 z{SqOEy8Qx07*16m?JOL#V>G$134dWM+-4g;$6;M%Sl?DAVx-8^NngOtizF5rb$(B0 z>)(>x-)t=v<@=Z}#L8wQRnH~Jr>Vcj`FCFl-e5z4k)^T*b2e2^)EHdp;6X^7?3(t9 zbaR^Lo`Km8r>~uYi^}na>o=sDD~6%W_1)lOdaIKTXax^7&AORfZ2LIUKq5SDwl}Hc z)dt1OIY+dd28)G9NrKF`u>Mp*K^aR|)PRh5`Eu@96*OH6g94yIPw$uJrdE-2?| zp>yF_awmycU$O0vy9#6Fys444vLaWHWw6QM_JTTz6~5k$Bh#{lsNqe30yW=bxqxf6 zmBtd9xsFI^69KuFXgWnsy=TQLv9AbTYAhz*p4<68QYeYZ$Yq>AhZf*t^@FP>{K^_4 zZe%-(#CNrBj^C2ggUh~*QJI3qAM3&#u0VD>fuCBW?bY=z39i+%^0zmciQ&{(ybjg$ zEUv<@Lk+r6y@4X`YH=2czq%wkk?6zcnB9**L!Prz?RW( ziw|;yH@)_jHijl<9HdyS3L!=;r9H7qIu5NB#t|`@7e~$gVruC*=95qcoy}PzH6>dO z-90(HK&_~RMyg9{*L`t&hE!5Khod%N;d-_I(+kAL9< z59;RpX@Orphbg7Mi6wMon!lRd6zE)9L@dKT4f05GM8gPupuaHfuK?P1rL{ErUl^!LI%l;jyD=yxjSJtF$?4Y zJ`j=$${wo+9~p~_UkLIeHQ z61mZsVlTX^|LUd>Q#EN~rg!TOl5*D5ga2C<8NdEB7p>2f9cuVvdg)KPmJ{2AmY{j;z zRS2rpFKE<&^;p&`Jj;6bHw~OPwc6HyF0aDEq&_8qP@VrjEgK*Lt zt#=>um8sQN<)3?hhE#t02_cWsb(3Be62HpZ2kQ@z#K@0prbvpuH+rlApyOs=R>E)JMX^ky+8d+BeA zV&Q4lej%hdP3#bI!%YpDu@9WpAJGHvExPpdD0FkN=rtU2 zhCFyE*)cQyA6R=B#wvDG=o9F0m=^g;&+a4neH?$0>A-5oz_#v%^Es$nCoIK9wQL7P z)MKp%YqNlpaEDrA#cP5EG_!A2sM(-9h|0Sq;CX*!?ML|jKgBI&duB|p9{?gZE=hkg zISh8GrJm;Y&l$x{Z&FG6{LM%tI!G2kB)~Gm^e1dH`u!{vT9htMJr%~3Kl4q7bU8dg z)w(_i)iwD6rlQsF>ZAVku%oTpP%>dA&+SpjE4!s_F6XS6!knb#8^M@V$D;R?+!LA9|I>_Fx9@+8VFP*_{}!YtGKRTINk?k@eMXh!Mztn8!&cJKdOI*8s0V^Z!%bmTV=f4TTrh6&hUcH+&CsQ*%M3*dc^d0u^ceo(Jv1&wfE~j zv6JC*7LOG|^&_%^=%@DO6_Wx)RxyWyybxmwwLZ0&@B(;jO0itvJjq4z3E^J_7w>BR zb8j!{vDXYaR?%|X5}2d@M9BtJ;1$Bku!-mq zTc=xaBR>~{>6fqT;0C$Ig1PGY9)@&HR7f=)yz0kF6T4>jy-=d-<^J)JY5tM3GdZ43^{d8djIUWW2($I+pF2x|_0k!Z&{S^_&{xKDQr1asfwEL*EpqqV_H;Z(k(jb6aK8+vigeGXeD6KCWTi0@)0v=^ zM$--c)+PGkxWoULlt^^#xewrRjGaFxVw~>4lM8ZruD_vGz=`^CgWHQsi7zhtBE?ZXincLG5j1^GEUm z9yDe%zaWJ=CWX3@=_IJA_=$j}@SE6Pj$l|!b=kI9-b{_{{_y=$^v{RrJ^S1?AeG4a z6-^*hNl+#!cT}nxD~Os2^h#SL9adnKlb=Yv++A+)7M>4G-guCuG4P>LlXlQE%HFOn z}kAs$*>qHXfOeVq}K4QZ`F?7K?eNHwPy+UkGXv5G)IvwUBN)xplp| z-g>opM)#-xzd);jkeTBjz^n+4)=MAu;^FZ#R z@4JxG`X&v)le`he@f+S5f$^jhGru8&@tfV1!Td~v!o4}2{6gn`lLqw}4(fI7+3neRytu*q`)|WT`7ypKcO5ZcBpOgqT z%uTsVRm}CkNWO2(HbbSEo0?kbt~Rb09i&o3{`*j!t~zC)2g%k{ zIxAR*UCl_71g|KD4!K2YVGJ?SiBY0i$mCyWd|KJ)$(hBnQ9}&e(BPQ?2+Z8*D?T$_ zLfF}G5E$aXnP?Fmerb}ic6d-h^mV><;6?|xi#rs|b*VGTcDu)}yvvV)VIE&wnDVSJ z>q1g`w@A251-m2NR^?tGzl}THh<}NF{@K+9h8N@Na!t%CU~#^I6)Ea$^WQDr^Dv^p zlocgL+QPDlt%!Y2c+jY_mK8p>YT+z(lo#Spn#1}B`zH3@OqJ06M1_UUc8%qdLXQ;> z-rwcnmvE$*IKpH$mJ=q+f&AKFvXA5nLKBioMVN>+iIf0qiADTzEN$IpqAV{gET@l_ z;jPx`mJp5VV@xd}BYnn=M9p}S^qV6gaI`Nm?IS@#zLHS{)%$v35yY!5^~kyO zKU$_xHW8G;p}^B8L{~yxz@`FHT9jPV^HdsmG(lcr#_o8slkK=&l;5d(vZ1d-jF_=Q zj2FmE-T-_QLrO5YrccZ~p9tRSp>h3UCf)#aXnesaWkpP}`|Z()JmW}Y#vIUP#?gx^4oDMqD^oTI5e-8r=8x%o3p@-IMmS}{ z-qNEWmP{8MGJqt9gxRSMV7*j_2CL5yu9I(cU~z|CCf~|t$*(vupbF)nWx&~7^`q%VKbR&Bv#WNPAzDaWedC_$TCYf7ueM3*5 z3?|TOzp$BHB_t@kvf#Rc4?`+nXoDMC74a3o9z!{thDHWyY^W-+@}?o9EAzwoz9b*3 zmGmi1KuIQ^Bq%B@k_KI-j$V{If!>Gu<1`Y22QZ5GiyU!YSQX#ZGaVs_j7>Nwv7jyH z!b?HHn1`PI(SWt_XnfUYP%SeJG)nSu;APJXUKR1IPfsw|{NBp%g|r4roBGof4seAe z$}7n-BPHh32>Os4Yky{(6mznRLnWI+_iOQt(sE3BPLdWo6_&w7XBtPh)w1vwqUZOZ zu*TQ@8>xnuhdve;orRZHBoYfrkR}u4y(7O4w6w#(Md2z5eWVD$C(A1}7D{zd+Z;ju ztk~fC%X?6Br6i98%0vq`M@wFab?7CGaLGi~j5k^v6rO^nxC<3KIg%8^j=YABzoT+` z&e@wCGW00nTo^4$Dr>Fg(Gc6CQ(HkCv2|^iV>r*l`fXf!+kMS@O(McogcPv74oM~>SC5@N zM@``98AZH3CA)ONWGV=f;GeULTi24yKw6B{d`~~QBJ$GxiH_Z8}=rp+HaS#Xh~Q1+9(n# zp*CQLzT95G-N0J!F7O&BbHoG&p@fefB3sLXe~y+{Q;EUzo=Tgjnz=Q4FgFum8U}vQ zlWNGt?N4jU#Dut!D*M7Z%DUkf_8(HHJk7T^=&wvLsKStzzSDA}LaIPoDet4! z*1Lq}x0mk9DyqDwLZ(1JK18i!1BHG<%RB_yMat~f7X%ulSSn|4wb7LYv9d~zkZ?%o zp(IesDu|VWZ>Ko~g7EOl_`&X;<6n`)j;QMOSKN;wPdMB10Dm>lUn!%{;ET7Fno7iZ;Pr0}OKEGxHC z1lWB~{SrNK2^Nd>wkTvxInl%66@s8a)g;atX#jh23 zc#nFd3%Jg90J`O{xW)hge7l9!;p_90!3vhre_huF9uq;RQFuV*rVpI*e@ByLQw8nq zj*>rX(g`NggW4*!--Cx$YM%imsxI`(xg)&o1*p0bGi#V#mt8v0Fzf*!wV<$(OVl^I zu&9$2T4&Qs@sM<>(zkM0(-e+VcfqPng|wisOUbUGOWIW-Yw1j^ZZi zn0nuWBr7RMR8`El?9hmJ?+BG>vX^g9+$BrvNFS5ZgOOj+ls_?a1Wa=CG|_rgq7}(4 zq<&#*`>Uh6N7)#MFSo=9+C1TgiSyZWBPDNRN#cbwh9i!wi;^9diLwk8SDJ4dZ>P0O zg@erK(X@1!9^x5SbOBoaz#-njmf&HdyKv%AY>+cY-Pmc-{;#(zHRDv4X-?G1Q_YN4 zuc4Q%%Fc_`ih3on@w#P27}=t;G}G-LM(M|Lr(;K6vD_3Fl6m-yn@1WglXWg zUKyp4Dl#=P@YufBmX37>gQ52Cye-Zb2=21^4vS#heajl}3$Re&~4-#%tu;xyie;TvNMSFM+zkp#UdzS6It->CAn0D{Jq!8tHi{LtR|iFPWk3;1#~&n+O`qU)a!|X=qx(!m z+h%VsBnoZKjVoO*D9xx_UeK($p%V=#WWVdo$N7_92fWtyUHejlOA!i*Km3PF_IaP8 zSgn|EpRnFjhIEDu>P_0HWax|-)oAErtqI0_TvoTWpzeHiYz<1Z_B@vEfATG8N@a&onf11*txM`g&z}+Y zz`?VW!QLQwk_1aiN~e&4_YmG+0Of;!w=NZt-sw&?Fe~NvtB;` zT3Ht;vGp4E*3+Cno$zhutm0qyFr&cDS&XebZP$tTZ29e$Ox2D1zr=S z_O)kwi`q>I9FyzU^tNLx(ts6YncBT~S1FEaZS6jp;6za>&(wm;|OZoIx3;nTqlcjqX$@qKg=Cl+g(+RRW zg(7l?rR*Pg@LIgz(~8o4i(1|={Dt+OUZbRg5Pc*F008HIajE|>--J`Z_nFWpjuaw!l<-6=w&UNTQ<0J*P zWirWbG!`@@3WLxizs zIE0<)g%(CPtMGCgqbv90_Q2XEW_M89t+BP{U{3X`PPaRI-ZLJU+xp-p~nXxsOdy#-z6S2?KBZ4Ci zL;{u+ogo3D~@XWmp+)yEEq$U-=c;Vldd+^Jz69Ujml+I_BMtA}V) zpfqy{HdWcfz6?$@c5x={P|WKJL-oqcLI(D1YhTVvngT;isoA9{(cYqJWgu_JV_fN0 z9Yj^t6!GH7$T_NQ%eeu>K}??UsSfzE<8K&zbVbNI#-EwJlV_uNm`xm%`^nLAd(F|G zjO6>#(MWjt%j50{@Z%VQx}!qw>Z8OQ)Q3s(Cf<<^a)-=Jd{a|%_YaR8&&*%iqx9+r4y&(HqrM}1f8S#K-xQNrwpC?xD2pkMDu98R9IYqq)@OG2 zUzt7xz5Mi*e#Rw#hS&1muln}tn0`j5iQl@S{pyGL@9D9SsaboGt;si8TvQD+&DQLz z4~bTF_ZlNXk2zetU6p5IVb{i>FR{6LmaA&VhwWSQ)l-@6%QL@PO`BpjK$8sm*;sjZ zeG$_p6)stxBvy(;{O6Tdu~?ndFtFQ>eLnL!Z$p%SB*NU^SD4 zn!bsO%p|@xbP{GEms$vnu}69$b01F_Tnu`S;Xv? zfY@$4XsymN0-3GYXcy}JQ6{*>O^20L-2TK@#z-k2%NP}&!N$JZ>`eYMSGP5&hr?sC zJ4y~P&S7TqR>9QKxhPImUSH;f73sQzK+UcakuKSTtH`!AmZ~0ok{V%SPE9b($!*e< zcV6P6=CRbq#$hKF3lwUcg2_!Pu$fQZx9pD){h57U(x$Um{E0v;=xTz*eJRRN)Q!WC{|4cQuQBa(#A&UzFuDvhcWw zl^$RiK*(V+MCzV=yArVN0bGMV~BNqs5giX4)nbL?#rh z z{)N!GfZP$Z^B4Opuh!uXe|r&x`rPaf{0q`m$%^P3POiRFqw5gnn=DuMbixY<76B5x z{7Sa8EuA0_u-_vN&|~!H{3&CjVL;r|_UXb$L!NMk>fyrIOc~(}*AXxrkN7Vj5$`4< zM=+q=I%=+toMC|2KDz0M7{Zm*hdXb4w#yng)hq@*m(U_}E+Y;D1p zLkF<%;G0AZ;wgjq2@u7^Jmj z?c~8mM4R>+KE)sWZbC?A1Rt75o*r0US%*&Tl`TY?1FdQ+YMiZVW0ggy&nnj3$OjD< zNVT>#5fKZSNVyxTe{rg^hL*h5w&5e(G~cdNxh~G1A9AvH-zV*tHsIbNsd&ZPdxZuU zehWYgg9$(Sa#Q;~2IqT}G{0}IlJXA2Ha)oRju5^@HB*uy`EeQsc4!8&mg>uB-7#ef$3J?F> zIP9SpiOG4!pz(&7fqeBMZQKaP11iVFB@QhsQOa0QA&t z7q=xEzAK-m@Yt1J`nNv zE&|K;jAXI+X2#)snEKaKdl zA?ti6hvqPDPrW~&cYYGXPTfDLM?YJjeS!OquOGedxOKmC)%?br0ku4#zY+}m90&6D z>ASzFmA+!s{7(1rqrc>Izoq>By7J}NAE0G5zr*I}9`1-9-{|qXg@Kv$gaZvi{WE|z z5QYWBRR$w^962x)5$Zt)a1%dM;vpMIlMf+-zgCSN%C&J5hk#dfBGR$&$_ZV?h#5@? z&@dsv5!sqW4S`pn?xRLiF5c7&HDSq<)z#rt;7LL&vjT;3WL&x;CL4rct0HtDu&GZH zqALafNfGO6<6y*y9asyNS=3(Q5uOtWCJvxrr~(RVhug{2MVw5S|FptkpTh?qx#JKv z1wuAui(><$}aCYnQ)&_?g`iM9==^gVZfEhYE%fk*= zy^4t^WTLpZN#n{YNC|Imd1F4vlExEMg`gPjlOA8N2HnO|Iz~=}Mv^j(n}8)Sd=~Q| zMGhlSp%dg-xx==E=E$^>p-i=)W;&!zEts2kd&y&}UW}WU@=q8W>#E5UqE|T>ia$xF z&#YH*Mx1}9@6<$>io|EdZX6qwXhCNklWYS85F;eU?qNj>hhk*_W(@G+sD=_7u-2=s zto>DcNuZX|&&q~?NrWtPjGm83@Gw!89K1?LQaoxeDePbunT?~4NKOoeEO~cmuLsr8 zI;juina>{vW@OEjCpJK9A$2h=4HhNN$0ZEg!)0{ssy>8vQ1Y`I@%~hPfjC2uC%7C2 zCpId*l1ZrBuSPi8nb#A?6?s5NqJG#TE28`k>;!H5^e zEqraj@Olgz`CkiLty3uMFSBT9)d# zqFg~{3Mch^Tvnonw0U)Ly4g)B>EDbhsf}Ff&+>FhcK;~X`T%VpB{D@6Lc@#SVi|g< z9Q_10n$lh2mDg9+&{Wk?l(V-wIe!ChYxvWsa8J6An^PFI9KH6E^g(A0+{C2#PIJN6MWDC%Ml7z_${4}nu@214kg5h zODaxRm}-OC5u&KJ2^RqG^%+{mqlCV_=J2)R-Xx~oZsVF&;9p!=S>)5I&}SGwKMsA8 zrjrN3b_<@=8iHQI#uhoe41+ak(iTwNwgJ&u#PpjCV-IuU>W^7=3(_)vjvOsC3sxIQ ziNgfi7fxHuuit_ewWHW!MG+3ZShRHIxyihbxX8*z#Z9W29K;*ZZa9OHA$Yvn)u+6W z)m>)`+?B)4?72=f2)D7#&*0;jpwRYQ%DZY4B7HO|+%&L#84{ht`F?9i!wL(3JVXn* z|46#~Q#%+?k*dSycu(8A?O%Ck_@rmmeiM+pO=N50lI|)E6+jjKeaK3auUo>hnfd35 zJ?7Mzl~e!_L9Y}AcB({7OXB;H%ph)i*<+K;2P}h+h%X)1HqJp$UJ5%^_((ji)9_RT z`mGq1zLmErb;F4%d7_k~SH>!8rVf?<0KXIOJ;3a@7>|EoF6(#IEgC-nuLf>Zg+l ze55(^d%|Ihtpf!A27W6pJV>;#w`r3IKARmMmk#8BT;IF`Z3V)n?S8==&j`E%+T;wH z;MlTc;FP9mH17=vo-D~b1v45o-$2 zlnXVQVdn)Dfr}$|0*k~CS|L%GSRy$t?v?&?&Z#T+XPwhf!*{l^7?x=gBvTKP+$B^U z$h9{sa(qBSJ*n^J2&LM#aP1C5&G7eS)OnoMp-r>i#E$u zQr;qxh99z5gQXXWQ6A7=Df@j}1HZC@FTJAX)M-G*?e&7a z(f0A8VulMQALfjbf;X7Y|8`k;4jtFH*kxu}r)Ifk*%Ad@NVDH>JPr7uX&|lukxh{{ zdP+Frl|q!Yvm)NMAymQsgaohYZ!DIC`4k07i$s+p@U=72D&aRwysrb6eOOvQ^1>2j zh2B_L9NtL`^34`jQc`frJV$b>k`}B0#fRHqB~xrDB_8{K6QMxgophrUa0f-b52?bj zV9gXu79yPQDbMWs9iTvnl;w}F$@uH@!?JD4o=D@KhN74OT2D1vF-El_+fV9ypjQm> zUSo*$++s#@20%l4g;}m#qX4Y2Heu}Ko^=pB+SJg)n#HsHq7zK}cLd5}lt}G1&x#MRe(}63|P|~EH zE=uFyfEDo;v$K=;#HQ%@^I6J-wL;UF1qiUdl>!+c!KKN2ZIW8Vj9I;MmF}NtR5YcQ ziq$Nyi@mNr-*qJ}Ib(g9P56pgYV>wKC?d^h@c}=8aEuc*U(|7bFFqcd(ZPLd6tS&FR*JU{9q1;9KAkX!>sYWyg_MQ&VOmj@|5vTJn1VRQ_`3+E`Feu)@r^uA!maJK%PN5&C1Q*Zwoy2rH?Oz-p_ zwe%ZRP}rJOThl7}dV!wNU45_^iMI{`NB(JaHEq3fP{ySd3)1jyN zR!ohjt9;ukL@@HOmEF~K3(N+=hWSY2v$g|i_CH2e^0wuTae(h;du-?=?B=sz4`Yql zCl>d$Hr1wBVjyQGR;qvmy9A`>%MHt1s*9XbA$=MAQyG$~5xT^sG2e!6=45MT2H=w9 zIwiAIr8OxbdTAr&ry-zT^N%H0&x$k_(5bMTnJTtZRYZ2d=BR0y@B_CEZ^NOaPrlTx zL{c)7rcI7Dch15ItsGm&h7JXV>RzgaN0M1Rzvk{X*}kP)%M!CUXH9O#InwooBaRks zP2NNc6=OVZXi;4eC1+Xb+z#UHy2Yj`8(%y#4Ye%{R~ue-S|71 zfB#jle^nL>bi^5rhY`MPBom?YqL90V?q%|2%E%BUOqb8riM*m?iOUbkE2%PNjia43 z+=;F!C(+FXTB2d-E+HNRk1MOucu!*Inaa+GnqYC2?MQX1h+{24@(U4ksA>g5gKa9_ z;JYgKyxw{v@abN}T*}slcUbYkGwq^#)k|KQgYYU}5d5kR#7tB>{_K@+ka$WBpjs+l zDEvzI9KLk>=v2NDdXO}kw3DkHzO|W>w_DO&znRaTfdh8)MF(wX2<7RXW!Cq%%3pYX z^7kZpKVn}JBY6!{BYJmgsD496K(I>3WsR)~Qy1r=o!z5wiKe=yn;ojmHYXq#bJ1P2 zR#k7@KLrPQ3>qG4a~=DUM{r`~sJ}y$sB+Bip#x1Y zxkh(9K@O4Lgvts;-j#Pz{#1b=ckS`Yh=onT(z%FGMh2yo&T56p|C%q<9FW5X@!--z znsk&mQ!YW!=0s_S%_Q8QgH*w;ol?==!nAZvQ7=~ssB~GJP}KiO@P>tIbJ51RoL`wS zljmW}K4C}Tf^L(Gt=dkpF)0TkXIlNC5+Ws@ zGU1bD9hq5jEmahpD~YM*F8f1i5-mN7Q`|`)(xtV5n`+Uoi0#fXjRFA%1oBO5R|KD?xu{(*f>f1xKrz0dcCkBEx}A|)ATuS z=)iqGk40%%n|(z4fJ#+(N+Cqjytyaq7w2~w&K_yRWwOwn%cO%_(KyN035YITn67%X zNmj!wO<_cr16gb_e@MEM+J>xhNvQ%V3D2e$O;gXiY}?CM$-CdV8@p|${XAtk_k3T8 zCMYc_!^)JL8`DdZi<-fYj5kNWcywu$mdL!MmVH_`AZ6(}qbRh-AxK>IF1Rp`PQ!W8 zgxO_-X;nazJZ^Oc3&u((m6f^p9-8*#MA{&c)fLh@ zZ>Y;EW=W%xVb{UqZ4jjy7 z%!>(1}`;7)V!WI?!A(tNsxv}SuBoxk2Kex=|Cww&n>JC_d!__<((*Zv~jEqO+B%xQ5+NvxCc)P{VUc0 z-JPa{obY`#nK(KZLy7H;uF{6Xf&GMi`9bm;Dz(w(uoua}{%azXScF<~pV|+uF_`b3 zg+=p9Y?6B)31pmz;3P-h6^U>+h~kuZQWl{x08uY!E+a1AFj3u67PMfq!3ZrX@P9D& zPC=SQ-PUH>wry3~wr$(Cop0K#v~AnAZQEJttiMk5>F#rGzV4eH`))_96?lc41r-VHlB_JPyh;!edyaLRgfwX&HN=7lrdhEBBVukx|JE8DbXHhwks5+j(E`8i7c8KoVbr< z9|<-lJd;GRA{P89p-9SV1@@FBQlwdb!r5%DmHh2Rw`}U}LD0f5_=jMq(9Vml6rBDU zX;PxWG#Z0Dt^^Xf9B#f~fcMgvfV2Xs%5whLgi|It+=;z{lbXU$mO>F98crw)aj;HS zb0;XDH;#jp?BK<}4++H?g$|ls8fyNKlS!WPL$@$A=ZF*0KV5#oiMq=HsZ00u{Z;W} zb^gYjvet0WBTnCyl>Vo|(Irr5UZ9j!#!XugwlQxu(kjUToJ)f>yg9HgQCGJy9ofAO zFL9zNs`Or1)IFz)b9N<|!6Cekdio72tw!Bu8M)gP>amwm>{*3Zhafr>^xFi677bd( zH%VFSkCNy`@MYGNI5KH*K}YsK#|-1#nksfhOz0hk!g9HG7`p@5ucPcpRfrEQ?6a!- zn)SN){p~Cc49OomvBPeVg*;Pdtzu`i6P-|`v4+AX@tOn)$KA`yOq#fg7i}%Mm2b{G zI9;wNwc@F%EK+SzTHH}^FV>FrOJ4m0tYwu*XL_L-xgYs%**_O=^R`Lxwk4lr!Qm`b zu@0Xr7QIM20Il4&p+@@(Z1U7E^>F{GMLwgQsbLnrdx{J9?(y*!h^BcSnxnSo(O`Oq zf-+g=R=7Ih3au)o8J0^0eR4UY6s{=E{h>W$ePaDms#{)95ynj@a$FZarK2*4(vzf% znv^tUXN~xptUUB8`>$WhaQ0NNt{%6%TbD6LW9SY_t%v$~kWAmi?UO)AZA>WcXh(AH z1cSg@BCe=N#UtFa3RFDl^o|K2wx1fz=4_Bg{3E-W@eB{DO0{6X1RFpR$rw2 z503q~-I500AMMfDku>EKC&}X}6HSA2Y7X?QpALc{5O2Qbk#6^Z_^sQKWp{x*0N+T> zRCf)Io!uGCxe1c14Y9_^pvkeEY8<8fQFF!|zWo9IGmMk*rF^3yG5GAT(Xp8p`2Uo* z|LZ{dMk#oA0RjZ%_K)vO@}CZ*{|}raEO|y{Nf2u^&u+1lp4yNa1}#CkpIxo!5Hpxc zQGhu_KZ;~qIEy-Q(LD*s+X(9ime=cO5ZI!alYsX!CLrag%Sw9;gFMGRX=m^0x@*on z;OFB5zMrg_kz^dqi7k{s7giZ6ec7wYu-ADS#jet9J$2FDCfMS%CK8$(37H$3|3js6AJgnI7jyKf z)ID(bgLob>g6j8x3M2U?Ptg6u2p7g{JwdRjf4#13H7E%L(=jlzhTva-J63v6fqv&x zL4tCK!Fx?{in9Y?6TVaS%s0Ik5XpC0U>yTO@+jT@hlH7|XXfi2m2Q)O* z(gI#@DnM8CjfAOlsqAdSHj$;x3pL)|e>k*pu*HWmVWK#z?hcmWIv|h?Z;&u{7H{#^ zak%Y@c;VdM_ix}l`m5Pru${xiEAq&2q_CpXZTy5J7g?y6u=D$Uj{kjgd&}qVoI?Qu zI{nAt{Z9c7vPQ;cw*S}NEr%1@H)Z94W#ee;NM?P<6f_G=Qjp9xkjl~+HHP@WaY!ql zNb06>!&eG}z01i%B%-x&#ahQFLIbu%t%1#M1cPd$+L%wLyJd5z($21i@7j+L<5=LS z*Tf_FLhafkg4nfsGCkVp2$67Y9$ zzYgwabpLMp1(9buG=TXn9qh0B{Hp-nLErQ}L&#sIH79}`!+U~|WH2PG{2W1KP*jB( z-bTjyMdlPN3zvL+d@{5g3c>mZNq0l0G)6ZV1-(5*>V%1C+xOB z*=SgN&G@s2XJ2#u;i8dg58!wP)A{~F9DGN5{b8HEvHXcpu^L0~yWt|r4Y-*Yok0)= z^ZCIgndx*OsH+xc&B^@OlNOdKW&-+j$1u>t_9UzHO)E6RdEx^a$YQJ>NDUS{c$es- zTDIS86<1hoxoUm_sO*gEwNI45xkVLyl+2J1NTw^`!MM+IKI*TIOiRt7eKtVv9l*G<6@{sXhN}5+* zg(~0ut>%N1>h_m2GJsxeisGP@SzEbZ35BTwF7C(Ecg$Hm>CdBR(o~8mC-Q~5_oleI zO9b>cx74+uEOx{g$$<*p-^yjz&x0M_f`YmN_lQ@+;l@fUh>vidzTl54p zxm4W31cq~Ty$$o6aEV_9z7X=g5DA!wtkEetZN`R3J3CBDuc$B8>JmZxsFlgz<8wxk zyyBI*>kVQ~v6yu&kY5z2>r&uRvGRYQW%Pv_iZ@LaV|iTc!h5fxICEILszP1gPzLK{ zL?!PMX7?t(T8j@xHpp!AxeF@Ouc@w*Vj0Hxl1SQ8`u1qck~Y`!RfGUN8x>c6|F}rYKM-HdHQ=gZd;{ z@lMhON$X;NE1#}VChXgh_LvaQuo&n!K#UZboOw2lfts2b-e3;%_M{H{Q=uLw$x*5& z`r!1^hx3j&wm=MF;)C@z{Sg~n?MVEjC%*o#qp<|AJw?aQFIx*4UyV^*uh00W**wF0 zDNd>*xi@NTfmn@+H|$0%|1=4Xp=jpY0UgtBbgPjY&+wSMqlGi<>woPy{=juf_wv|% z5e7*Q468@>-4cV}%)3*{eK7`!5B$6KXt(JPRc~zA`r#a=U)+n&K>S2U91)1OhG`BY zHJqX0W?vYnaraJRmf)pctZ~@TgeeE$$}n+>H~T=_3iNSs27x?go`E&*NJ)gRgZ$Xc zj0{5|F+Ny6W?$@g?@;_SLql&FG4V&JDE!n%9tqV>F*$#+3J#o317Pyf4xrQil7-9- zoSFFopywKEa)`rx;5tqzR0!nfm5<_=NQ6cu)Z2rqBXNT<_|^zMy|pNAnWFJcHmr zDJ~#>C5pefe1XT_(?b+*P2Iz!?ywjXF#n+Jr9EiaP8u`~?>TyYr48`B@jwUsIwJhX zJO6?HOLt^&U&q`%3E>-y_{f0(=o=CWy(p7LszFhZ4uQrb5LUuWtN;{QT#Z<(MCDm`WIlVdvu1`K1{KU6q5B^&8MRu8rBlj zls*mKthr6Skydz0cYTTmxe{GPUg1thW$#GSdGt6>*>m5 zamQ*N2M|9Lp-xTL4t*4=d~t%O#R7EDDqaLdKAWc5SKJ_RSe}pphiof=t<2c9;Ohl3 zq&fw!P9w=G)>3WrpDHIOm#c5eLOI#Ut!gtL%P&|HXHN~YylR?u={-Ay7Qfi&^*2j( zhEIGQ(o`!-ZR$4ZRy#;;SLODCthG2iMgfQ2&O0if?-5V)>6u-UQ{>QaicHj73Ld}6 ztkvpb>JqlwzF)?=CT9otNG!aSp&MdFk*DZ8?@!8AVJrplbU46}e~>g7dhn)3Av!*` zI=NuQcsOVb3ai;maCdHYf)t)OT7Ff$5lvuY!@ZRE98)l##bJT7w_)I07(HNLtF=l} zp{Sq;zt}H$Un;34-Efqbk%n3kHOtt{fskBQT$NyyP$uD#apW+#q*c((lvU_Yxl$9X zR;3>{OPr_RV5>UAlW1eYI36&U?X%!WkSFdlA7>%eeHW0)neaKem7EV}PcPx`l}t9R zLU4o9)iY-$h~}$R{N(ShKOWYypVF8!JZLJXcRk5T{X&x)naO1CMz%q@jTiL^4p}Z& z`lR^UT-c~Ek58c1_pFpPaA$C?6%hTfS)X2_YYs2u3>+aZ+H!-Br~NuUZ`zIRds+}#;@(;E$` z7NN@$Bv=@C1Fv?v&^x`t zJws+{NaPQwQVHQxkhGy&GkjvM3c>3m#2g>Iv26ID z{U=CvUD-Uva*Dam>{oZiqZe+?$tqk5$GRq>Mn}t{9pDE}78l&7qa_03G#p>f9U{)T zYVOv>v|q#iWiz61syn7a9-=#T=j?d`yL`W3Bb#_7qpsZ28>cdJnkK}mK!Kf}EOYLf zg)r#iG?yu(HqPSSbCfCtFYLFDT&YvJJ;|54@!=7VultfPxB3ZtL*P71CDcqOQHAQzcMw3Y8Bbve?v|vg;trS2cmZ1$hkn?;x`H^ z|B$5|KP~R99r&{uJWy2rN_oS2Q0b?Cr0X?@!)Q?ZB{>vk`UYUKEY>1T2g%w?0z`X? z7yRYti~cok{(#oEmjWndD#tFVe$HB9lEC@ZCr?KhRuwTN>N~^5D#vPWIDxhPvRP!r z;`o5ojMPZ35RJp8_-g?`zd?LLvDT%Yla$lB&92`3&zi^$QDRMZLLe7Qm^M6&V<-80 zUfipS$x_qFSBZM%k&Glwc4^i?1|lsIj_N_>luiJIcCkK5Jl#CH3|89uY*Tls+bp`O z3g101+7Z%qte@2xR>O3|fC~Y*ugnBay#f>DzAgku12fgo{et-~XZ0$J3CyR$ObnaO zbN6bZJN!mcw5^e;Ity%uubCO^8wK7nyL0ltC7M$62vit0RK!t})M0U4rv3V=wIKrm zDyyw=hWsOBS zt9Vx`-LTxaJ72*14{s^ZrmNG6{`&qu?f{hUJ{H(pGGIpT691A#ppmefziF+}a=_29 zM;jRZ2+y=R6zj7>8M499xTMJdcsRa@AK&9M|pr+yC=K zHTK*9HWh~NHJ7y=wFjPsG)~w%+ICT9Hc(PTRQthRDInxD<$xQ;#cg9otS&Tudkb{cadUcTbz{zc z&Q!Dt#yUnf8*BGeAgyq2y#Q-}B74*89rFP}!2E-gdz{LGqBQu!Uy2(cZ;cG`VbKZ$ zh9I(j>8zCTx-FuWk8#CHU_seTv9RP2(Tqh>GDxExD!GyH4}Pw+3O)(oWtp178HN4>%hw7! zX&cSwyT%W$U}K(F>-JSxmy@G}`kD=QJ#TV@?Wu0EQ|(p0V!(m0P!0Gz;9V_G%zOss zgElQ_0$4FI(#K4=tbuU&<%OKJ@DY@q|0=!nuXC&0JbKh_bLz8_YdcQ;6IHsUuR3fb zp;w$<8M4NW&;WspDk4GL>xKuBbzS&SEb{@A_YFmcgZCxH&gs>#c0fL*Mi7S9fWTHE z{dpeSpaQonray*7c7B8xQXEI{XS1=TFazgEomW2fj8k`@;SWf6IsTb<6#s0s{oiS) z^;C!K-&R-?90(>cXs%wh$4}bl-KS~S*>et7NPfNPzP{*)rAr*I(&?q0GFk;Fwj&-v zF5l!sksiql8+%xdF<@{~aMdQ^wdgCrsULKe<(mix<73EJgS5zq zD>Pow++ve9*-piMqyxA`5<)N*Yce3xv z()m|6&mkmLGFwFx1DeGVt=~a&bok(iVc zU&3*3KbXezKf%}!7zl{$KM2RNMqUnX|97Hk!hrOUAQIrO+@koB_7yBtu;GyzA_1ws zvsFPp(R0E-YsW+y8*U?RG=DOtLNMe%R3-`~IfqjA&=m}^?uFm_e8 zEJiflPY`-+xlF2#zlrJz3gBG7tyNY`Kk4qmpf}YfM*c8{#QMQhd(81q9-dY=4rzbj z!5fF?#-|*|p^_tgWw61Xo#|~bs^@cdElB-r*hfMusj@gXX8@?r&ObqNeJkiQ!rNB( zGST}-W-f;Dw$$PORT2M-U&EaAV{Q3w#IpWL?*DPrtn6*bnEu;NuB;;iB82ucy4BiR zVYM;CDl-V%phplbI2_*Z4eg!hI$vR@;a*yuhi)l^_#No4>bf0D09O&jl0D@yoBcHN z{^sig=@^<6iM8m2HJ%}9L@mG87)hrNSB;}-5pj{LTH*)roqFnOwd8563$20JBp8?1 zurzBc_VJM&N4=2tn^dHm1E|+xk-*oHV!=J?-}5XYlqGw1(WfM>Ehc_U)GE+Puo8w= zh0sitZn^W4@7#9G^VOLc4hbVe=YYEMOI+hf%Yp`Fhv%JNgifEJ_u{4UH7Zh#P(O#LGhXRVqqj? z7{Wk7X2{lXlfg|O9bk%zNBD<`NG{?RnCT2_E9T-rk-UNY@0kiD=W}HN1p@kp`2R6e z%>OO;s$D5B38MX^krc2A(nmo>1?CH`jiBO9N|PD%@L%P$K^yF)6#T$HenPx z&Zam+4aJE%A_26xYs!#n<18$er-`??O37RuJHtr{ZpE#b@CMOl%*WT^w43j9i{#Ku z=368bkY1(3ra9_r!GxlBtE}?;{so1usddqVO?uq^T%2imez}Fc`8Tj&_0LmqL1gZx z*u^P}^=f*qaeMPd22UIm$h&f zU^N$>d+wb+YnR}1w>FQ0$>Ev2gC>Btuc5G!4dxEE`{7{~UX};EU*#4&F9m13J9eW_q8sjB zNKl>g_XIL|6HiGfW`H6daAV8mMHwg7oA>=fNz;gVhV zYYGk`)?z2_xyQ%Oyxra(J9^uI3eg-jlI9M1mXtD>obAC<#nX~PR}h;x^H3jf4P5*^ za?HvBlwVA~+x}XEI`KF7^)naR#^xGtG~LFsK?+x(>IFo-prs{LM#YTSK}D4Z`|@@Fn)w^@*K z7+3e!YBW`LAeM6aXgQ+7ic4G$#`##pbs*9M#(fLUoSP@q#=t{lsz6dnudT>$t-OnQ z=1`2rdT0nzt5>^TvW0iE4Z*c3pDR(QBheQUe)>kw$mCF+FZWUbpQv1_r4JsZ;Z$7l zkoY)NbSO^74*-%E4HTM`0^MAyKm%q^vib=HJRhmyG4h9s!|{ol7}}Bj5nol(TdW8% za4HW@vkpy0F69^|Bi(yZ3L|NgBg@^al7jz`tS$*C05)~SDCX@ZAnXJB-#=ZC5!!Yq zCJ;~ot^Dr(n;50C(>0+anTx#uuMY}92J3pd?{MViS8(OH7SBTKnWH6sE7=XX7C0vO3-)5 z^Gad0#&AX0XaCCE_b=zruh(UhFpvL-2uPP~rOJ_TO6@@ZR9%x`>mc)oJ4KGbsAUd}p>63C*gf=XcXTcywMZ#E=l~Uv&hE8Bm4tEKS&R|+)eNL)U zTedd3>KmH66lWEhx*Q7|4V_`ja%V!68BA5t2MJA@N<%P=0CG=Kt1l$Ie_77Tk|Str zwKEZTRfVPM#>h1ah^7dtO@$>%R958SiYiosTc{o1jD5Z_DLTDsv$9jgAbZ99ID3^X ztc}XLI{GSBj>kgoRz#CK$hJJsjpDkQ>Rs`nGYz+DYxGSi{ccVDYk7T`-a&t1a{Hc0 z4G_8yItCdT@#VRyEAk)vX#5sYRK8GJ*<}N%9ewnQJJRY;Gz=vpM57%ONuo=^?Lp zcV!(SFkRwdRaUpwCb!zw7pksGO2Qx?KnpKIseTdg$b?jtBiBRCU!==DaVh@z51&e@ zeldR8=5-qDl&bAP<@(e1`KcVr4^qO<)S2zM6#O-fS?d<%J}l^Y_ zxaIAsX?{Tz_D$=dqCMBqtnNHU$W0#e>x}3I%InDJx5ftUhE9~RzG3RSVv3~8AC=(y zF`&^)(P)3z(0fO2)z{b1-BW-aB1NAyRSbv-){fwgL;V>Chys>UgT8|{dj*TTTKSM+ zB|34j);~Z(7J#$$8mV!vYF9%14u0D8aRm~}Z{ol|dbUHko^o;gZJTzcj)1kYb_m#c2S@&Ah8+oGWsgarb=>V=x=QPC1I>>cl6qA zb;56Ly4zZr*Y9upIBeCY4Y$+aK~v&jr=%)?@*yF(k&aO%`F$^Vau#qjtLg%@t)c*~ z5H27Eiat?OatEHm5-UvQxp5MEbrMK3tosmyr`8H!!97`^-&<0fRG~z->;W7XfYQ-p}kB<(8&B9x=40$w`g<)a3T%;L8=xDu1QM~ z9IV8tFrN^{^Q59i)`_~k(f6BOgOFROAC>$=(_3ogs!c$;LM+ z$|gUMnjKwlAM734aAF_W_`Zj-K&*%4@`=#iGb_SkYZ}8A{^pw{07#ev$swDJM>pLy zNud1!oh2}fclz^>y5B_DK*^`W* zxc|sT)AZzq%eB|6eqeyp8~#6FvWV1A<~LyI7qF0jH0rbzt-&xzt{KLx|Cs(H34LSP zqM7LD9r_>0S$*@mY{bsECXEe3t{Bp?YlUmJe|N~}?C;-bn$ADvV;ctR1``-RqZ@l0 zS1_ND7Jp60=V|Y0LMFAe?zNG>$88f=0+JJchJL0t{+7Q$XpIe4TW)T0`6P?J!1=(Q z$c#X1f3Ry^O1VeTq1`;P@(htDV1)}HTZx?|MG-~HB$*y0#dwlcnI0vPY+%1NHYK0y zEv;d~M`iSrhZj3fP&*;i6?6<5ST0%da}@C=1Wb$^I5ZdfwlW1`oH$aa$K1bcO!+r) zl2|rm$#^OAr^6I;MHM?))w0IRJdq=Yb142=1Xo3q))%8WTm-QqPRB@$oX+85aH-Iw zIao9vRD$_?;0PmM6kI|c)tn+@rp%R2Ge1q1r#f)}XAUlFj3HKesNAADRHA($%s@|? zvpJ4zd#w7)7OnlUJ6nHQ8Q+67q4 zVa#Qwq4KhHEM*Ui*m22ln8|4yPEfD%&k@ssv#Flm{R=rzl4t5)f zP_M`ppplk7jPqr8=N9vsCvdYwxhJ=+1bo{&AY z4q|2Jw=uUpD8Daa&LJbvSuia)&kD>S8s$|LEfk32b|k611x?dQD*hEBWEqhz(9#J| z!IpX#=^E`8KYXxAgBCANXSw!RTuzCQ|3Hii-yIj5Ux^bGvJKGWu_SgoUl#0 zLi12yi?W@RD(*8xDc`5U*eCCcgB!)M?y8I4B0mV5>$bEcX5>I@c$8fcqi)4B7)$V% zx>Riw90irw#h8p|Lw*OthISC0L7_P#52QW8jaFsT9rs4T(MIG;b6^?hOKn`3Nz@Oc zJm7iQOe~ffGXytUHnFn!@3w@)L4K#7L;nH?p8B*Mj~XMXAjd&mrtNhuZ-g1@@+m`| zqu`sOMHo=UXnfPmN=zOnCnC%C!D=XxqQN0T7W`Q~_l{vDvY;^G=~>{QL7PVYZ6pf} z%^iLp{GDw~hh+9N9y=DL~}R#L;XUH7y`Gu2wd(oU(A9c(WPd{&FV#!w54 zpF1QFXIN>uksa7omkE# zW8AM0!95lC$5ak>cB6#B|FTYVW#Lz5QL^phOXYoD2EK!OH*`W1xihO=SmM}`qLa%~ z&0kr8&3GTknPq0X0{_sAYkH_C5Me-3P$_ewF5rKEdskBe zc}qYGqR$bZ+Gtt1Zy_tfvlM476*x`>LGjfkz%*{}TgYzM`gs>;eT6158qYk3?Z^%K zBo4_ffKnrParQP*X3rme2N9i+AO02{?vC+fhZH4f`{g+AN;rAO2#@lc`xa9$D6s&& zuzB`nkaC87SwJ$rQVBGKpj`@5!Mwn$ag$8-c7Hnb)TDH8W(zL$ z8{Wz^wF5iqKd^c254|u<^9}HK0w*aW(0yx|g4oU0Uw2)|2<|w_qH?lrP7n z6k~GIa?cXeBif z-?`(2gnHd?sLGZJZ}QPi6j`GL zQR@X=??S>%>MhZt{L&za9MGi8^?9R$8@KD?3cVworGz^rj4Bjx!WyY3sm3lVSbv*I zk|k=L)8es^MLw2G2G6A%aKWgpSFqCo?_=IKcbs!x#<%x?AEXn6WUh$oZJpD1qIR%y zpBl;)EiXvkfV6$i8#$^QH*rE^E~=+uM7KpWkkJkKT_OyL$l| zR1>@pkSbmXnJvc-J$mbujJd<9D;|`Qq{C>Ss3T}?jQ=(;ZJ&XBN24>6B;T_ry z*2eoL|C&55S~T@%VET(te2^0^)QfrL{e@Hd`@ZQuyF;;vxEf!yoD#Z9nc15k{!oOL zavK(D!YQ(6BH$k(QB8;Az7^)D|-V+Vg#O}8f~N9Eow zR_H6{KjncbeM&)lhEHwxg^?tPHo`)sJwF7=je7$!qpUgz?8vgatp8kR)_0)wPSFci z6_h-5)YVmOlm1y_Q6C}=Th_N=V+rtKpNJcE^Op2+G{&k0O3{RVGAe+AcI_s_4Qyf- z3{+bb?FHg3iTt~3En)ib>36B*2wem@t!nB_da^xe^vs#Nc|6ttu^hfbQTpPaUjoUN)B#b=A=gO{F-8tX#p}b`#22efOe0`IlMTZ-ne_-agQt z$fIY58G^^HFgQh+bJ?!3gB|~sXa2q=R|$dNbIS0bqVQ~tbj#NA+eSH~4c(e9)?f1D zE7|_(qu=r~Hh!7WcpPJgb1Tk# zT%4*06AR8)mpcS)6w9t7MvkOtqvVgwXyb|umDj(CG1JBxvef6K6%~YgeNob=@5fXW z2iL9E0S&F$%`Pyf2{P$D;HxZH)-~lKT4~6NREG-GV*NIpze8U5gpqTS(TPoYeF<{< zgEk}u(CbuTCuc8|5^tjUDo?Ac&IoeNeJ;SR2kbyz7!W6-~N63aGlDS((tHpB!PK1mxXf_putEa>-{)V6R^N;Daian<66^Aj)nVN0I{ zzM#>GSpRIt#2t;w>}4fH9|cGyx1d^;HuVY7#Ujxw6_@OxefcQeGi}oRxD6GY!frpq zq7K_<%6Rjj7pv6cwSCf-kO#;(JTluK_J0^PNF8R=5ccQ1X7T)cA}-E`jLs64y+!Vk zd@&UEmG*lF{xpMJP1U<&A6eMvbC;-hjrq4~Q@cRN^-JTRG6*|W1v>{bd&OUXkTT|# zI)w9@@TNG$vewxzLCFhOfdu^0VuT#iSCGic!Lpkl*DBb5tE^Z!wJyH#qsY3GU`IsG zTFZd&yP1ZNQz0BTDgN=K35-Z^R?i-wGM2ZY8BIbfR}tANb1eMe0-&V|M`=b5VaZ>1 z%nebeIYyCZ8$)xLOh8>X6LnhylHK^_$orhNk&s_GoysRem@49QsctmHjZapS?gz+x zBeElkQ2E%3jk!)uM%RnW@5q#L0q8JaA=}UqPn$s{eSU^0Sm-&?l;y$n26bUqQD6`(2FE?|#<2IG2Q_ zd73tFDj26(KWx$Ycc-t@`$K9o=7O3ws^1}BC`;?6#}Ad=XEg7`{&7jP;zz^bIPw7Z z?|Jh>Js%a1WLM<1&^Yvf>dU(Cg6%tcJHYb+bviz3Q+4UkfN#=ZtuipRxCY0Af@>-h zJ9C;bgHV$8mNJeN^xzL9kp8t3wZ}f#TdH-!=dVWhR(OgNu3)l^WyH~;$LiD5Fo0yg zk4B_%Ab7)Ao+-D>0@4^8*g0IPGS+&adBg|@)1~Z71?0MNNw&QV7_s=`Vht_`6n9T5 zvx#cAJDIP>8_bg78Bvm}@R8jPm$veE3U829LEb-gm*tK8gCKBs&%^U&9lj^j*cRK* zA_~GAL^jN98u=R8cHFUPG-NuGz3HXdp)t#%%JQ(gPF2#IRbZwQ+9jVy)Wb6TdH&lvuhah96!UH)PRc zOe8Tia3nYIOh1;;leWYTB40$`5?NkJ?a}WpC&W{Ssg*Ty)M%LjWjhGwkm4HTZlEwF zL!XR#^CN`?RWupg1LfLlQX%6;UT~TSYTy*z8>ACY%IUS^oTv%=WmSeKlzKP#F(U9uj{+=P>GeBP59RI z1JzMuAFCM_Xn^~Cr~xD5g*C8nvU$l4G65o?zTLOCd$2@s$%Qf<4zTbr);xKUnRdF+ z>mE`J|n?CzKC z#oH0hInjWxx0Kwix|wG%6n4GbP>7|U?Kx3F4~0FS1|9w^rqH%xN~vb-$Xc=T>Yu~; zm`i6+ZdN8a9@k5$qzc9?2XH}qe1v-KF`%gb-sPJPHLGZbeM0MeLplL7x#=+2v9+RO zZi<1ajJ~`)sU)$9ml(BC!EHI_TdqxgZHCvHN60PhaeS(i?bpLE4`RaFP>wx&)?k6W zLQeczP?Da*@N7=1Td!Kcs+M}ovYF|MIQVkJPyVq{0hdMKO>Q$Ba}$JSdHR~Mu6e|_JXW< zu1ZnMt^r-Uc&L?)5|0#^5RU}cAR6q&KNLr3%axu#&UH-e6T;l`%FC@6A7+0S@Ght$CW6;51b@Q*&!FX< zj5nb14#={A`JsIg%~vB?UogWyzaqdcux$@!%_k5)%srFdHsS*Q^P>7gpavcaqVXGJ z|5(EhakDruKk&U&j33Otk!w50J$T?R5MSu5-k6QL{1-Jn-is2R`U?b?B^66Qco&1Q3WcH< zcnLNoroe(|2k(oXLo`6&$-!zHaBs-1N5hZL{@Lrm2;qJ+Vc{p~^bgiP<^GI9&!NTP zS%x3PKIZx#nd>)n?K$IXuE4W~qcxyDq1U#Axx4Pe*F(hfALM-npn%cVzWf&rI)7k^ zZ+McgUu|z)I$t7~e{V`m1m82>2;1H$Pye1^{Jp~X>mH5C^nnuRkG+pBn>)Z{;dEKW z`GVI*_K<-FgwhY356lUy3&b6mjJ|H*jL3VX)N%kmv+M{}n%$RG~=MLX3tF&6A%H+`T3I z%>#k@khmV0-|??iWFXBkfR#BLu8qv2Oq--Zl@l3OOb0@RM_V2KTC=axM5~Kvtx%FK zNt@*S5CE;?E&%~CRAD;x_%?gs$(#h!<}tHHB&jot1>+nF@kU}Wbcrk6B}~7dXj@;x zks2`!vDHxC&M|WS918dp-YPWGOM|VXt5?O>9H4vw6dnAik`d_OOO*~bI10~yMPh|{ zgqg(5hmKrIJN#8_50G%M{)JV><~m~{kNq9#NQK_I5xD^0nH|g%9$w`02^i!V!sqgJ zVKrP^_Am7$L%)sPM!SuwiN1!GNDgMOKEK@LbpVkMiJZSzEEd>kWSHf(UprA1HzUf1 zLImjuDywR9BYEMfO_d^)AiIV?kc5> zK*xo;W4hDAwwQFjTkxu9`$o^Fv)URdDN&geb;(UkDX*pCz9-zv2LFOF$M z!f%_SG^`8v2a!qq0ND007e$`2Sr2-5W4^{+&DeZPk_J53%J+v)cyiiM4PKcS14Xi`h}-I3OiE8HD(v0zZOE<+QBMnBOr5+jTdMC|^H$i5 zo+)-zg*dL1-#5HwvKWdVqmleY^|K)!z-e<~?^2wx(t7eo`?dGdZu_&vR=?}z7^M_8 z0g4;*YU{L}$=Ip2wp95GnxLH&SicjemmUjEYY8wY!wp9I^CtrQTX3Bxki=(4)bW6}Nnqae_M_IRU;w|#0aEY>x?HHm%DSZvw z8TG9h+c3I2HzbsJPjC+FF%~X#wi}ZGiioQ1lG$|JF5}8V@NaPdwzD7rzC#ZI$5~Pw z&I?p=hZxcgo9$R0o5d8*5UXsD3KbwLeYy(eaRHaZHY`Tun~nt7j&*q*?}7M^%4Iv2 zdo&m6mD)9iirmmS$%6O7wd~UC9Z7H)S?Bi(C*GsD8(!bqKpMNMCf+07gZAo`Nxx%1 z_R)Me)_d#$_~!Zc5tr`Qcb!1Oc_Au(opjWDMdl^hS< z-=RhPQEC)`yM_3tQH+SBiDF0}kwupK{c#x1zp93OoQ})j-?1fIqTB?h1GNcKJzy$- z%XY2ki*P!g6bTlI@Q*f6&fyqSF!Di|@iF}Sx^OA{JJ)R#H`5f7zLM$q*ol;0F7q*} z;knhjmxzDRJNc>c8RkhkoD-9_0mQ#>d23Rf{-L_XXn-$@iNZwP#p4ygSKEImVJ}qQ z4;CvR>rAxClIhsv*aQBZ@7+)2NAPDpK_IEE9#_Vxe*+!5Y44YI>SoY=lh8|P>{?u0 zqGXClIdK48z@U}*&ddgxo488U2bMj`AdNy^lYJzQFm{h6&$1#iH z;x8b7>HxEGixU^YHxv>6UjcRzQzPgA%j7=bw;G{3M#N#?Qe8T6TEV{%p2&e64?OQa zA}Nb|To&Flmi_HaZc6N90^M1+yz%8Tf3%*Srm6FnhAu zr>po~G0p~p1qm0Dx=)uRymexk$P8tbFaz_i<*?0CH;P{$MyZ2;u-9I~JwMmpYBf(x z#5;=&{BaXR($!Y6(#=Jt+CnyadtFMPGSK5N!Q&Awz1X*4&1i6XCyc`T6?y&^-q4J- zj>S@LhMt3MHaq+pVw0;NV#S#tTegRQ?$4NFbbZPkFz@(CE-eXL4@u!JW@KPvC841D zO{`2%+uEk(RyYTosx#_nHiKFPlFKHFHhdVz2uPKrY;MjJ`~|6cv3h!|YJ?D;5i5x= zA(oW7 zA;gR;^@M2JQBiyG=bc&5sF@m|%GAn?K%sN?9#cG$B(!=;*O2_KVI`UY9S@f+lb>YF z{j)hw&?lH$^zhX{mlFS3f(82&eF^2|V|>tokLX#4jOi6IF>fodQ^7yt9W^P^_6A-R za73d?FU_wbs!rpWYMO~7XFOf+Yo><2ywg1`FvV&mUt^QCeZjPegxTVXa9BuRzm;7Nsa zc#mzg>hk`Rjoo%iUFtQLUF>LvtyBoHB?moUg%%#7KJGP)khBq%`kn=u$a;UJO7x=P-KSPn^jIwH8NtuGEM0%lHX3uPY6z8 z-5AP(dbU|&Yu7Fs&ESI0wzNF$m3n>_-AEA&dw(B~davpUdW~w7n&&zYVO^|PwG`(( zbOOmtr&Ipoi*Qx_e~w)zd^oj)|Fq9dFJeM8iZS&>aFMm^Z*Ri!666a?z4Ei@%hQ`+?yx5x7yPNasu zDrXS7ZlwEgv=BeZ(Dec47iVhmA%Gg)0VHWdbqP;<_1BR^1y-&b*%cH%QuRE%Ya;|W zVvEjoBFZgiTS-rD(*Dg~_qj4{T(fay#lMT-W^9z-N;50HIn`T&pL0Os40Wa;IHZ_W z2P9(X{Ee1P1U{^2H&J_;j^tm&x&9fcQ7it`Ewqx!-J+16y@Y+`-A>AY3OhwK3e5CUJLT@Vq@Wtmu2@VY}VL??Ongd#8?E+J;df zIT0G?ENq5WL0VJA8V>_Rqke*8IAW{`AooW1A+UOjN5RC^EPzlWVjEUMbD^UDhbV7@ zm0wk%-iW&jv$5pk&zpxSp;bCHEMtMo6Q($)$cHvB9|5KR>Mtc=%$e&aH=9tP@1VWF zfb|`fXSz0EJUjmg=Re&ewG+s%~!6;QBAUfz50_vBEcY(PWwg95LlX*LK& zMR$2)O^0Pn86(8&c-{q}qKuu7P4i6VNC?a~T~;9v-lu$48Ghrp(OLadP^W}Xl8(up zeT(BFQx&E2u3HW@HrCq&Q`QJ>reDtI)?=`Rd-0-4YW_M?F)~~=x7;$XLT>Jg9GdMx zw$;jCR`y&wDdlTNGxUyZKA>&ZX|%YIwF%Q}VCyxI-{61|INo1afs_Uuvcv9V4E z=?C~MRgWFZNhHR-e0+~uSgIl4&QK|?V{L8d|8b-ojh zVHU5{Oo6CuD0qimxV~>bv8WW~7BXL>X*H_kltL8Jp>1IVu!Ppfr2oVC+D@{C{wLI? zv_bp@`PI(mo+Wy5up2y=Pwgq-S#?8FZmh_bwQNDpuGxT8R8d`3hgH1VYUy{4sp}aa zRedD*@ZQ=~I(jY@>P2U^fuLZ@HkxXQQTa;o_>SO)OkExAbJh!ZCZX z{w;UZV%#yN@UK_vqeU1z1ebJ=`I%Afcex$)%Rk9~O%|2GT95XkhtO^aHEC&gBNppo zo3WquwNox6e<&4t!Bcpu@J)v~eEwRZX1Ju9+J^jYBNYa3oxnM`VQ&eDm^ev#XcR>fwF z4|K(_KJzI9s+a04e*zvWDLou0q84ocOLmO68bLY4ChZx3vg< z!h6YCq64Wiz&WQr3I>IpE@t>iEQ?EgEVo_abU56?=C#KB{dr51eLQ%zby;66N!wc4 zc3RQ;7(ylI7hg+Pt9W#I-p`8wE_!fdORb)K9j)rgtEZTzX*^34VdvLe3Jds*mi)3; zosY`3Tc4yYzjEQr$8&LJRP}^sYC7aA^p^DTM`kLF{=@nOF4zI5xDHH5=3Hw6qDj<{ zF`4M8r>^3amMIwq3R&4%|CqYKt`f^HP?brQC;mzyjHw#;nD@9M--w5^ zS6uD8)HQAgo@ij;?q4?MX0;IFAX8Qu&B zfo#=oU`^3v(ICzZFx=g>izhDm&P^RZj2Bc1m_IQD%1~uPIRrIz$0xu1sY7RIW zYOVoljF+3NK<0Rtj6RN8gEcnqO}|#{p|dPQ%=}M_gTU>&*cgZ~A4oq7|&)as9++&9wYT zPi?$GgiTcx7MEFY^aurNt`Y2;{PU)h3G+%qHq6sc%+fwKezg4UyYTntn)lN$oifl} zve3^r&W-k763C1KG&9srImMdf&$LRqn0Mm!a}dYTB937Jb~o0aJhMrg@YSH@SdaLl zN!YJrm9lKHfmtU^8}ol=2QYf(Tv*lb^-G-^#4xW;e0=OA#TMA6C|^4scnQ+WDr&h) zGfN4~P@~@5DebAEUCqV6gv|Lmn#7=|<}cKG)YM+rgr4UhE83E<{I&;+CWvKH-u_My zFDkL=WtVwCo_LGR^2I*6X-I0And%5{aAnnYlvrj_?X#d7_?vYP8=<{U;=qnGFeORY z?{IJ>QT5)5k#X8*Z_Zyhws>*DFCr?252ibURRK}~8al91QeUO10P`;)dN*(>#GfhZj$e;MH5V*B%6dU#WNA@!WEr%?A~rOpoKXC?vn(lbzG*=2|c+ zpG4Ix2K6yCpKOdPG45|s(j*sTEOYb3Gf-!RbQP7&nXx{luPN-w;^UPP2d$pAoMB3( zz7nUdl_P!)8C}zU(AZ5Gpbx&OnS|t+Ool)9kgvWG_Z$+)!e;DW02w7*$KY|zyawSq zVsTBeE)R*O9!L6O1<}xkx;Wc1i;>{-;HySiu0*M-`E;a2ZvAcG;#1F!TR?uxHQj~m zD9hFq5c^YqnE$JVViFvIdvUUpWt(56g(Y3_&zaS^UbEI-zxKJ$5$SfzIoS4A*@tT# z`pz)BU$nk}SUWM9BVGx=%T5hJNj37CV!@K(QQIFN`@YHBMq!`+TPEL8TApEVnjTCR z;coZB#jB?R>^ZK>_(r%LrvM23!!6b4PHCqZy!@DzO3^Eg-7JoFt76x3;z8d8mtJRr zU%7_Mv`$XGZ@EYmUW-1 z)lmdIQ>Hq-(BDu~H%aE>U~`D4G!X62?$mgz3BkX%pOf~aGx_db0Nr~1bL`@Ah`H%a zmjaEoXprWUBiMuC+QTgNcA7mBv1+5Y@-#3znJU#``R&bcN_rZM_40`63pI|o5Q5kY z#a{&WfGMsHH`5GRi!HJg`7X13t$>M6Wex#`fICi`WlE~foRp*CGU zeR{`e+bgf#A3Eb(W6pm)#vi-AOyG=b18x3bE-4;R!ALS|!$k2IQoz(WENZn{vYPGc8zMYd&}WdtASr9+HzU=Hcz+|@;l99;EO8>R1eP@qC0JmN3NY{(z@j3 z+1+Q-i~FlJe{*19|EzeARi*4yS@m2Db%x?6P}ec*Cyf-ddRnT(@2dO-nx3M$z7n~- z!6{aNS|K}c~s4+t@`5?+Wa-qw&tagm0a&CC5}-Xq`)UeFpFQ6 zOgq98!aYHa8k#&FPUlZ_ThV_GIu+i3uDp{MP&|&zIbGP&Xc>cVWyM_!&&ptVAfwDj z(87(!hNz?&EAO4q%|Csb(@(B0Cz(1*zJB4_g8mkjwI)Ju`GCgOj?F)G+Ota?sf(YV zxQ{~-6z(fl?XHi!Iph9-V6@Zu;NgyS4w!LXvQp2a9%)W`SkrkpiL;~>wCJ^Teq@&F z8LQ%D$n1V#W%FrKxTg{^J^^;9JQl(p?NjtSpi-XZS5%vd+fS}i4N}W1Lnq;ZGPyDp zI0V;sxcrwdo+!VbSU1v?6E3UCbgEinRQH^-n#QXG?|89PZ3!d0S&(PSn#jtBz*>D< zDut=g>6=+z_5Q8%>o99ew=_>_iD#nd0^4>^!0WfB+8!{OuvV?DDPW557jab&8%jddHZ(pu?Ak;!3biJ@KkF86JD5_()>*za{=~ zx>Gn+s6m`qJFTb&0?Vp3SY5lM-4v$O8l&&m1LF)0ny?Eaoqe9@Eh}u)0dIUH1^ZLBXmqG% zacbu)HYmjzGJQLu17AEfPePe|Jgobgdt__;BbzD4?h8+Tk(Jrn&wJi#OaE8w);!+x ziSut<)5{Z2r=gs{#>vD~+j*xvZ+oR&=(?l4r%g(ZGXLfYzvL+A&&f*jTLJcCu;8m- z3fRK=#s+fycCzoT))%C~Ox%FZy8OfEUn^ptdGv>P`Syn{JGXen3^5C+a`^Y(e*s>2 zh@kotDAy$FwU4xPbAZw=jRqNv^d2E$q^sitm}qzmCiQ*5r-zvE@Li|eT>ACsXQIHTGC$P_J{&k1bolO>tQT@oK+}x5XN@m>5n`*Y*^yvj#A(}Ju#pEZ9PoA2u z?ugN)Mch|mFW?`V53I6$K#sN#n|E?xNpF_vCo|eUd;yZhuXEpDIrC%4}(VuIq*mvCB;Df7N43Fft;Xst2!+r?yYLM0qUNioHMi5QZHC0fr?+Tkke!LA0qj5eEb!ywYtx@X;La;vwH1`IyCDzH zsGq38{{&)F^H&~i@3I&)s2^%YUu#@}S2aWtc25-Ab4%=-tv}-)DqKA_zUugjv@iE2 zd&AvPIewR&{NwLO{InBLeif!)%l@LjAus|TH5Y^{ITtTpirgnKzK+)<(!Ot-yLz-! zjb2__8B&=IUgfA}v$8O_+}IL^VKhO%s!_=)eyN583WN+GdsRPf@Tk!dd-DI^Al`rI zZc$0*Yv&)TJN{>o|95n^rL&8vovG7*!FW7>?N|9BARvq(+}t44+#uM+AZ}uYyHj#h zR-2Z(QN$p~;PXF=T)vi$a;Aa~RK5-#P`)0fQl6HQjC>nltfv{pAi#B`e3+x9l^&I% zF|oj!P$p6SI-@);{QXL9Qc9K%U}s`xWC{Qxfh7TD0fmi#{y+G^|6KN#`=VL~IuOuL z0sgrA4>oYgy~Itae?5-f9&+_K@$qn@h$c zu*-3}?S9*Nn&bWaG00Mm&kqLFT;j$o$-+_ay&JAKf>qzf$gRK7Q2u$e|Hsn~COo!SsG1oWKZ)~ckzB1!+ zL7GN*W;6PBrTKv9@mVwxOeZAsi7vAz%CsXB_Ra3MC0(vHnY%^myhWLKvAFqo+7Lcn zm-J}=bxJU|g=qN~0mL1p@{h&vz&6aLX*iBV2i}9Q5zpm0*y%xfp)sZ{0^zw=C zXh6a(7meLZIwxpqj6+CZzu1d7nk^kd)XphScbQ%2EkVOgxEg}RpTIszR#uoeP} z10`x1Vnst{Va10cJtOqlN+@BC=0$kXGnIS70=o&t!t3y{dDI;UY%6iG(^-ed<*@O? ztrPET1`y~vJU!h>2MzYIBzTD-g1gg12s=8t=9aN7NXTYzB)9g{v1s{|n{y%;*WL`Z zQ8p!MJ15j*ch2rEwTxsn5ZkemFRNE6uQVaR0~IX%7$fvXwuQ=DOIx0E8pTY;kF->T z^DD6_i`u~P77X%74zbIprCTIcGi_!7huO;pIZxdFPgq5iVM*88JcUpCiJoW=3UUTV(@p zBnBfIBZIGSr+7IVDW&9o85vYnmDL0&L&QpYEU`$z8I6LZa1V?ZB-R=!QE)Hgh?NDC zf$4&tra^MIs@dWwQI81g;IE-n#O`rfYopvdwBX(6{Tv}4NmL&mvS3YIkZ`0CmCItD zy8kdM?Vh4PoLhHKiV*}rMdcXdww4MPd({2tREnW;VQ@E+H;}%m&c;iwi9(pmk;qz1AKodc2p3 zf&62{+D~VZBfOaF(}ixrIP0>14wu`$cZUJt$}29I=Z;JpWLlLUf1=pQ%y&L@-&P9@ zkin=Bo;jwCHN-s}ocj9LaS+7+AC@=!dgUE&cR@qC>NAW_Sj-2Dh300}AhL&8sQMlU zrx6r>@TjT>>`p-s1F{F!>6$(Ct!8RZ6zrAz6KC{Y^^aw-a&X8&!PMW>UwKWNTG?`S^9paOkNShByS&N2!N2NC|1NN1$Mp4` zzq@WQ{4Nouem~+Xo9awYm`nZgnVv3%9edng#gw1{QS}+Q1TzN5Dtn)V`UA1?JA?E+ z#CM{=XY3%JN%2pSsOdk`f6D7j8-K9^4_!uP*P&B$O&BV!PE-Lav!T22STv27cwK zszphsR0>!Cr}mKp+aY^tB?(yk(E?{BqP9x?DdMp?;#Aclep9ief{1k~q^3*GQnln7 z31|`0s+7VQsHUbN+#}too{La|4J%lZDqcnn5A6Wz3mS@Q5ldY`Sph6?i)z(N;gm?L zut&9cQ30x=gC3FawYp>;su#q3QbDe!1&fO9+laK&Dr{(JluOuEVWfysQ=6DUICbSp zMhiod#J<}jx31cM;LC;m7|xhj?Zd!pF_2sbh*BK*t^x4^=;F%0H>QMe3MTUeh1=Bw zBhR8F(x|JS|Kmidm^|Mt37u&zUb%L}Vj_DOWvW(V#l3$O{94>1i>F1@HdoJ!^(=GC zfM{}6BE>-R9KpUop3il&LYp-?;N!*Ew$7N2#kWS}e();BZxMs$lU-C;CFe-K0k0jP zkU=kX(g63KiNb6GP*cua1{q~4D7VqdEc0B$GjAj8@b~NnIZ`8TOAFpx{RVWm%Wnrp z^uO+rZG)V1Wj7WHFAQr7oQZQy0eO4xHt_CNONXWMJ7|{|H^h&3?8B5>Gh53FHZHoxc9LlN$vc3*)?8#R*qq;pO~d(&^J84vgS_ znNFDh-ZP}P=$Y#?o3BFOjXFNV%}gwn>Udd`vBxUA2v@26bUYXyC9wX3JDHGQjUu`> z*D^3FN;eT1mQZFKP}H1)85BX&Lu81FH;B|sXdXSk*7eTi%2{Q74<;FYLPozt&SBjZ z_u|70=#TCvbPpDkvKraTkZL1@WUjHbR&blE_M3EThG(uc`n;m^JamPp=~4&HZicZ2@{L;m z>WB+6wKYT8p`<6gSroSZ1z8`Xm7V64L8rr_u+MY;VcAdrvgicev3id{%c*?~Bo|>|I*)+qT*bbP z;bGR zY>PW7tN8=I>b#w@Z|L+HbkkK&)FzNqE!v<4b}@{D<5TS}gk_Xt47EKK;D8OWI75jS zF_LU|7OByN)p#dWq+ExX^y7n}Be>tli&w;mwvMhmsXH4M3_NJG-HcT^ zzqk<+J=(E~t;c#QwDzsb#))JoaTN3bZag0&MY^AFCz$YuC1&d3q3`7`EF5Y5fQM`H zO-XW|>%VKaeF=}9dwOfYxr?kL8ujiv*dVv@Xi0#&!ZP10>gc_*f5YfKivz{6;@^r`{LEs)@EI)ksk;x-R;C^8y zUq0$o`5>~LHsr6zA}>gv*eh#qEcmZIaDs;(HYx+jgq#&n!rQ~wke8VL>fgM-_@jH+ zN~stLfTG*Y1Ss{CQED`=%)#5l6{bn5+Vm7s_(k%6Sr{Wl&Z!7BzXTluRY0Vr_P#*X zIQcLug}y+cND;bN9!1uWex$LqB*G;E1h= z$jX9$T&H1Im8|>FBK)VHZJKP>hS7WKSh8Y^sCm15cUnnRkQ-VJ+;29ncmeYh_?xnV z4T>TwWNBm-7q}WDm2sa?Mn~Nlh@7psKslKa3r#fMx3g?)Xg_~%HfV-c(0oh5MGBG# z=nlg}80k!Ga)E%3;j!*AluGWh^}`Uxs(d4+tm37t{|^C72fO`dbLrI!v9!j6o!T2pa$fx=aUr5?;oRr+?*bsK)hGvQPN z=e30E34KKZK_iF8)F>I$cBl_o1iyGIZkQfqx*PdW}Vap3;#ezhrYBW<^@Wzr&;v?J!C< zJvwyWv`CyqPwKaWy#77cfHUGHc~A-(XH>yb8_T@_vC*}obR+fz%Y}wZJGtQn(IF>@ zH{|hdO(F)g#tSgWb^@_Nd)<_D7R%!m!^qNg7;b=eFP za)d}byvo0(Bh^bB*}+DRK1Xdl(08XRYkPE^96T2ExWxB+5q#4%^hS0SyWou*>9Y3) z)!09B*IIl>p}t-ox!`KLjg>D3iWw`9^BK@Mj=TTr3Z0WyYG~Yv`2%9-jDRIIX6I7g zHcFJir$SUf6O$n!=zZ23C9@lt_mLM!c+c7P+o5=ZD1do=|M?gaXlAm*Hy4 zluN%JzjF+T)Iso{3RET-6b~AT&d5~p*B134-M=dH!ihGSJmM9S(%d=rOe`#3zBx5k~MlzUuT&b)QKlX?;<(35LEEL z!c+c=UQc>B$nde&Um<9WRBL47Zh$Kz2tj&TVhSHWRBFs8HDHO0B7;eu>5RZ`T6c1G zNE6O`$*MIl8F0taTdM6TgMUu~KsXWD@OA#_j}(0QHg!>Y$!<`@514~YlJRbmH>9*U z6U=h@`=@ANA3JjrDRhGXQpLDH%-Yf+B+o8>sjFzq4_3y+)WpYR1l36>3*<)DxfX$N zl*k{n7@gB(U4)(I13*TCNarhL+r{J7OUJmb5m*v^;z0%x22M7r_(TQD}ua* z_|<;D*#^nHa1?w6V3j4uHI6Wzh-em$_%#*5V#(=ff;##fF#i%un84fe8{FWzsTXB@ zik7rWh$bOE{y~l0P#aMz7V_Th1C$GOZX}emjZyUK0;%?j><1hvt+1iIQ=PF~^t?RF zNeiiNH1IRDy*a>at5#+4>uTGrQx`Rb_A0f7W7HKdv`$$+pXS$DI4XonA6KbLlX` z#*Y%(4dd{9W1dXDK*V;E>M&&wpvWvM&Da^8EM5pD%=8H)!GY2P@K?vJ4QRL03T zE9>Ub%pRVFGT6IRz6cdcc84n6rFM0r`n1v$G}_hC?2YeRpBa$VfegNSow;Qs;jf?> z{2|t3?|Qfm?Y@#Cv-hNKM6YM*gi%8s;!wJyqZZ|3oUmmtdt6wInkS(0(L!(LierJH zU7Mh!%}=^g8QB8ZVbr0S*zwRP-X8g}IMr%Is@Wv)Js46x73l8O1Q$*`Py^z ziD3a>AQ`@W@lU^2IHDqjV#!&H{AMLC$0s&+IM7W_pXb96oVxtGK@SMS@^6micRV%p ztKP^A*@QMW|8X5JRE(bqh-Oa87kB*f{^;^&_}qhXJ$QAA6RZCuBIvYS3fIDKqk3AM zW9iG-W*P*knYRbeZP!_&%AfP|~Y+JT8c9W5{I3B6I(jHhq?tiJ_qBZ!aSdS2&$sj>NHEla1T;y`gLO7 zRAMy3OFzq2w|ihMWeJ0Sg@YT4ic9&A!NUM|`U}BmeAe1zDIC^(xEiafn3$v=q%f}X zYe~_lgU~c%XM0bE0*8C?n;)y3kl^F$|Ba3H&LD$~{VBt@{|w6irX2sDy0+v`QrG`d zhla+;!VEH^hMX?vs~}$?M|B_`AP48I=`6{?4n{rLSxz2`{d~P#G>FsyQETR5zqCoi*0dLC%wXXd}NiZ6m9NzVPDa zAR7{yx#2~PSS6RPNs z36<&p=ays*ovo!!J)J#m|EqEzn>nuMipgW>;v_>ohr`Pf)MKu&D`eS7DB&8PqATGaXvak(t;GL!AelALamVFI#F zSQD1{TZEC4k~{=x04zZS$pVjjJRydZ#nEiAD9xza*4DN^DN)#7Ki7_^sIoS2<&nB_ESyA zlB@vAq|R2?6y1d==t$aS5%Mh>kSq{QU;_+P^}Ov00Gs1DdUDyVupPxlyNoCjzUWp$S~j< zXq$dT2Kov(0!Rn60ki<40Fe*|3G@uI1Kq^d0sxExBuVQ86y#>)hS6`AzD809+JUdN zT@dZwQ7L$}4B**bqI?d#5yNQT0l%{g$Of{5$bR7jcpDzR0AHfVbQ8$%197!)ROCTz zBZBxq4f4mV*aEeKZUJ}42e=7z_@It-{L#^+or$S3BDN>e1=Y-!g>k1 zBJB4)+lgNxD&PgWBc#M197f9UTcG17!xwu&-oq2SRNSM|e}fyr4^E1|Jc!74Gdkx1 zm;=Xw;DTa8OdkHkmgR%K^7caqPyqD;*$G?lMXp%geHF=)tQmEAz(~KLXz90cF^+)r zfppg!U0DNB0Oy5`=(|VTN0|5TWy{^ux%AdX-*%kKHO^A?M!&o;zWN3b6nqFfc&*U+ zk@~@r3jn`T_0KZi=S0gz}3P(E&%*p?z8Ci zFicp*i}yW@?I{zXD8M?kg(LCO!(T|BpP-$d!VmT>l;)PwG`jz=Z>{5;%ZFJ1`jgc9 zhgf%e-=wIG4Hr*?TH^Asy`cNX!=}LBm2zVl2c2}~PQt!|WGfqcJo+iLYp61>nxu_~ z@7Ki@+)LPDp$Y9w$~3JHA#xmt`@IS?`lW6BQ+v33GxbiIwGfYRYfqttyR9}eexzHo z$0%8%1!N~+N^y>dm)6w1T-9XbNLQuacw6=cFAhHyh)y9`#5+PmqBZ=^{PNimyw}D6EEk$ zKZ+RTU|j9uZbJo;Iq&5%y~JBs#@UUZAt*Zi#l^H!N`yRY-7tA&VykBdtUSNuY2)EV z{%B0)Ty!2Dxr_>b@g>fPTtn!Q@yOd}e2q+t$ST^{q#L!0Cgg@^sY5NiZCB-VQA4&f z-Q&EA+KXYI5?Ts&9^B@UpR zJq^2odwbk!-e0hhE6LNg6-!=IO~#NeT++m_m{KC!T}3{*HDVk55$)_?OH`s?-Yr3= zvQXmP!q~*ZZ^hYhG>(KbYWt%dQT*l6#Fa550DG`M94EnAY@FA*17BDtA%kJhEM^pA z-0@2@+d@8L^K=+zfc%t8o+nsH6GohGP&-e;_U)-13VuQm%UhRs5FIu%n==wJB})?r z7PeU#D^hV2UhstfqiWO}riE)LQzkt}^g+4WRuC6WMX84zBeZgc5+jRsZWl1aGNE!s zauPSx*$m=)t=-|_%)G@xBixXpjDE>42Ly)mB=BS(>_-tQC{TRLsfDf71YQ!$X@?%0 zy3kg+oq*kA7y&pS4%RCbzzR38FPGq};ysh7z5_$^EaNR=LFLa1{r0iy}rG*`!F&eChKK;IY( zV?5fE;0PryF-&J7EH(Z2MX3`q#bkS2Vgq%#>OalZcIwXJ!gh|8${AqgIqR@)nH8KS zU~F1Q3owm=0KZ^7WDZPXdCV{yqn`8jrz9e&23?wU#bKAZdZYA&YE94em=$c#P9p6& zD}dB$lV}K{$K^1Ua9z6knNu*)CMnNis#7r$hv6%A=X!$nk(w}P|EH}{6cIUSO3=eK zoCsEcL3Xqg=9|@B?Qa^&_VXX06JgzQjQ-Uett9-|O?WB7Gb{s9%z4-|>?)0q%OMyF+~U zOv0*gJxI+p#2bR}4dA=)SFP_8$|#cX4&|_Si zulA0~Shl0iF3GO-*o$GjUE=NPf9THY&jKO=uw{N)dzPc?EfkDsvaoK@><{FCuZQ9= z4N`j6qujNCc;H(#&vYWl#x~FgJK>$+mC?o;`Tm&~p+P~UH%(WyQ)B2Zghz$cbuhAPGD>leXHfMV(!Xj&v(f(8r@HgSeCPMC{uc5|1u8DFtkBM%C`jANInW&0zj33N77ppQjfx#mwV;gYb z@Ro5`L9PJ{WXKN4$G7ekp(9K>Qz^D;kXN0uDNK!8NZyF^>t%%)Qd}7v*RokpQfwkY zQRUf=L`gTEUAtDTQT0!Ettd08G_FFG6?wHX(1mM@8Igz?xhsv$+w&H(XGA%+Zmn#x z<=#h>V^e;cVOWmwvc@6uHd$9zwT8-Oa6h7~+@ z{_A~H8WDwR#3A7AVV`N*1PRHDdww&(5vxOA4d#}W=6n!mHUh_h!|I%^rYM8<7Zb`o zld2ya*Lot3M`N%X?D4td-8iy@EftY2~!LF{5KbpYT{xozib5}LryEXj=M6p^|>aqqcS?f z=xkm%Xh5a;KRV@8IGGH-)Z&%e#pr+LakZlX%926e|V^_Y$^#PfA`H?Etr=h%~O zt(9ZumjnS3xY0Q7kFCAZB5v+*4M>Phdd}M9<{AiD(7l_7jr<}8p?ciZxiNWSnRPU|4ly8R?=OEGmQk-CgBIRgqA@m?bw-*a8ivo%( zvL=DWm$xtQ3^PtkN%t0S-x^Y=Cz2^2X{GY98C7sJa4ZUpm0^`5%u#rS4~Rh7duZM+ zL<(b*$q+4$3^(7|Z`q05xblD*k>I_kn{Hl$g3>8M^o{FYwUP;adwd)^wZK~4yohgQ zV}6{5c6z|l`Y^4tA{~A*TMIjlqg7*_HCfz{x;g1g`qEU5r8`$Ix}~tOv62kX-#GM6 zTJ-lmcJQGt=Umf~Sy?SI(m?bNm_3tqRL?`{3(FRUBr%+ruy6OQvvZBbXGEuvIma!Z zQ`SVKZ`tA{HNLPLnp$FO$X->MTlMZrZ=vUP6)vUip#tux=W{ z7p#|FkH2M)e#1^*(@tL*#_|Wp%18SX$`Gu?-0j&5+`s;wOq0WZBFSHYQOGExlJB6S zV<(7k$?P)Eqi94>-xkO<<}N&apvBMu`sM4?h>6t;<$S;C`$m~$y80H3h|lEVq9yVQ zElpO>O>^~-g%S?_Qh%)<=!s-0iNDAj-^(fU?oZ~1?zKPteJh7=PAER&m-O^-+HdH)nlb zn+nqy04~QJfVVHZFzP_=mAk_TZ6nWQp1ZsH!9hS)KiYYV>Dfw=Q=500))*R1^TJUL`I2q{BWj_Mat zRi%`ms!l`6@`$POYNfJWw6E{wv&7BIw^Hy$X>ZldwmodkZ;P12axPF>Hz70eF zH364I0Fo914-m4;LpDi#eT|LS7m6_YEHzUg@|kH<;Le~ZS}WBsklrJG0}($@N>&NL z2n(C(o*0tP^kkIi8EUFhIlqLFIHgsHRYVpF@6? z)=lq(^lf9Ds|2lvUG@&D;t_<+zWKWAsP(-DZMEf-qt;@;&L|{H>v>!Xr$%So&?RTQ zreh8%3VuqSkH@+$MtKU(9bzY$V-zYo#0+9Tc`%fQmGdjhN)`q{w)Lx^gP^%4lL8Lb zG>*(V>0CMZ=uYoD;~!^h%tCyl=y!;XZMLiVI3U(WRQ zP)Vp|?1+sp_rmRR5Y)>s%>h}?~F;qpAxKTP^nkwTiK%e+jw*oX+tiL5_e8?`N; zw@OfX@}jATA6Fx8Y1{@@_%q1cw*hRjpp;t*%27_v5F?I47&r17<$bxHX0UReq3w@1 z_Ig>pc?-8|IkQbf<9zH}Az0ipJA6+UJFBODt}Lxn)fIr1RC2R^bHJ2ccD(Qzv|atY z@G`pqP5X$pMsmN;+kqMLSaDrjyjPZWp!;%G=L}IqMSe3c6#!N$#XB;pl-Bjldq#gY!MZcH`Mm{v>}Qy7gk&4? zB5--{wq5IOwT})o)m6Q}l#fIB9C%sX&Kb|P+j-_Hf(kpwE6OO@DSWmTBq@@R_2uGM zZuPq}kNCwol-;7)n6v0vSo^})dKtdJ55CkWXau2T8+F;$vK-XI@)#@;r~l|=#?ttzaob&V6Ik%ZVlK>b7BC2vXMc19?Hf4bW zPS~qPQSF>_OLSF|aHM}dAJGaYr`#OOd9nZE(+Pcn-kz#**hn|UGL_uyt5qQ&1D)fa z37=F~ovORBd0ThyQ^&gJ*zg_!wy24&psvz^8FE6HPVt0WYO9*2)qk5+27fkap_-{~ zQcE%2;+;d8HcPAy{nFUu7(wes6lr-+&Gd7Sla}4cuiOp4v?z0Slo`sS*8fO7hQ}7) zXD>IH1!NV{;1zdmt>Oio!)2w}%E+QM)oNQbl&5qYqRlOfKU347=;h0;Ph)WN|1SRe zmw2-*(|~K~RS(ep6dcYETWJx@bG>=3mc=CG&+BkO&?E-l!+SKotkf2LxDk$3&w_P& zY8No`K7G6@|8)NL@tl*+RvBq}OvXv;UuB1aIXFdZWCbuY!Z>C3ilqs`oRq^o%w>st zPif!Mi&Tj>3`SJQIX|fp`HP_3#zmIuBGK)e>cjNji(fMc3emEH<`&%M7O>S8m70X^ zXGr^YsO|EJ+;fH?HV^QMKL_=VxY>HtZpiRGAN&2@i1bbeiK#PX+oK_xZ8pyo&0n)SMFlBxs!>Sl9BTr80a<1qT zg1U14TE>0GC9|W}JNM(>@R-UySIx78Cgmh7dfhn{0ho_T1QEd*ZrEtf*~ei{r8RHbCCSSb?B_14z!Z!oIC<08O4%+*_9yO?YMU_M+)|bO+ywvy!lO8hy5}^j z8bN?u9(}GWL7<=7b%U5?Q|F|~CkQ1a{ty&3>KVpHYWfM|83-QTR)X3+)a$Om+{vyb zyk9z`o`AgUb3(R1|C(SsCi@>K_!Aab9Z7u2Up57MUo>K8GK?gCsx&VI6K44f0TWPh znQgDR)55IBF6jJomajwbf}EwzN>SGdix;f6Pf5x`4r2Sm!z*eGp$kPBPgUaukXbQl zwwpk?yySfE9{rXu{ZRRvx0n-G(0bsGniW^eF!Kp~>P7atm9nf$ej zdO-FY@Jd9nPU7lseXwlp+hW5rbs;=YE_#4te%Hhj99jiHNu0hUQYp@PX%L`+SC`nI zFnBEq1YiJMnU6>rmsA2YDyQ*0If{t{`M>mL7TkgFHwKe5AbTm*Pnv(dTCY{YdJWX$ zJb~;xR*u*Hdc|I&Li8G_!rA}zs#`r2^U-dzn^}fU&YJCwfmhmiy{#H;Ft+f%u47q) zi!|K-p*MLEY$ZDQnU@N3AWseG`*|r+142?k1Vgh}Fweu(j(FtFyR~Q(REv-*tT+l@ zi;p*|COJ!P;dSm>zqihCTgUB~@5ryv+)xQg%pPmzL`UBxThmF?T-;O%*;+?(LlNPt zM{u?0Rn60N6(_vl@x;40mZhbAV)u5)aDAI-DfY?)0_e_2Oygg8(7SJ(DZiqoH*nL0sMW11`hnm1YTk-;pZ)!muydX-cT9Nv#Hn;+vujJ4bMu71ImZhIc zIDZ3QvgQ(HJD$IY-kMvKm9|TIqdq%L8G5cITblzaEt-v6i@oMIakcJCrn#nz&r~G? zm(RSmhwIFmi@0ar?rE(9R>|KAGO&p{7VP~t+>XIhDC+nI@#Xxx1BUd-mY|ebhqA_! zbZJf6Is6Tb@>cA4UMu>`@=&4n0iqLaY{SQ92kNcv^Xqr0Z`Fev0N|j86I_;lY5;w1M&j0OiA{I6 zy^Tcu@%$M<7TOy}KWWhwf#kR=$ihkRAnaTHASf3ZfZxyX5VDgBKw^OMMr=^d1{M&S z0Ui(=Jm_oypzQr7|57`a1Mx7;PV)9eh& z9(3Hib^p>h^yK^CkKqJB2IcU5kQx+26gu6voP&Lb3d%yMlCO@irr*KD{*FA-&w3lX)?-d4gt46GL@H*zDHNGm8lB&-&(x~-ceQ}f- zSaQRLw?785n+5TX+J=o~B^PnWZwRdt5%0Cr=eX!B+U>bncj@~{Uv4D{iM!T?MO;V3 zVicKTxO_pR!tAJ(FLD1I0hI2r5o6AfTTb%`s1|d5{-8y4*)2c1H1Jy?ZT^1Mm^VaM zS24;)TEv8j81o3`U3l?iwd=9ZtT)Rf)vP6(S4*rpzsI6+vgYn!7uhvUrp0LRg?)6^ z-Sq4KU3J{d_~ylEQNVeYAIG#G_>evb=$E0!iioor%YFSr+;Qy!zQZ}DuE}W`lZp*l zmNo-k{RO>4vp0XvN88@kNmS%plC4goy@swEcV5?Q@dllrgmkijW4rFt>Nr7`CCWXX zE3d)ZYPT2J0zGSs=5VuA6|q(|p&(aT0V^4=AeU5L%%g@grvyKfeUh5pvhq&i*|pf^ zp+576HqnNOdyHK!kDL`+rd^2&qwnwj#ihl2Xq{s2bB?n~)bzY>X;+!H<8YO#@wL1; zV|IYF)s$EnUqyGzpKp5J>LOd&R$~8r!5L>ym(3NZ`SpuBa(`F9BD*2m>P*Cjd)u4= zaXrSiTHbp5I;Wf3h>59rFHPXSj!sUP+|%znhs-*f+Y(&=P=*_dj|y3!FUfoRWR|@i zM3dN4q4siID7}Zw*~txTPf{toLRTXvCVkPy@I`nw#MY^e$xEJLTZ|Jf zl@6)J6bHgkh-lA0lQGF{RiyNfN0}@V*fv_{I=6lWz(8u#v3B z2a(IwUS{i#I(zOB25B0N*>yFhQV%77Z0P3M%w!w{Z{IcZeV4vezh3c+1d-X-3UcR>@ z{FBg}Y6)zZ$NyHJ9nUB;6`rpXIPevR!0zy&$$jJgK`((_u1Q8~c_N&Tx0XZ5Jmlbr zePH^d8|CER^TVr8;b~9?puwH53;lrpATM~EI&1HEuS|h=&HJz1IP%SmnaY(MMV?Fr zOBNi-SDCipGDDtrfMl*r>-U9G_2(|=mDjyE*>dW;xY*^=Cy7Ya%+=ebxLY|jBjTt$ zVeeH)RH9=1P)U2!K{-1 zZx#?o2WvOS|Mr;1sq84ADx>LFhzIXY$4bl85;ZM`80#p?bA!lf z_kdUu1c3S-gZ?Q)OkPa1GWoOS$TIG}O)7y>oKoBNVm7bydCPk7xu9qWRBJ>ZPL}Sb z7fk}BI$>S~EW5K7qMN>%QUewh&)KALKRQvu$Ye=ioQ3sMnl*=Qn+j5k(JmVI619 zEo!^rZyk3o{W znH>>|o+Y~txtuwje0v|?er;P_`iVNPbJB38dI!ub(G?#@QP1u6F{FD%DV`@mY?lq_ z`}@>LQ*VVplz4R(ra0`NtQ2cLzV_EymlVlGJ6x^^;-IcTPIjhPWYCgimE>_U`4TH5 zJq^@k3UYa4F{8s%G#w`Vcfj;H|K{Z-|1>N0cLi40|`0!`j^m4cH2 zRIVkv6#A;tZ>ZzK4ml$X*Xt+#{U##zyoEkVshiUQ;>HA}#*}~2LfoE_ha?XUgxxQ| zI*ltWhD5lFe`Ncud4{nIcD< zKqrZ6@MQVgfZRDU1V^(Ne3ceFwjy{I%^9zaFq1&d=kjdgsBQJM;mzg01Zw#((%7X- zb}Moz-r83bzrp|ii!}aeI}@CrMVccl5D@kMGWe8S94%eUU0p?t|2qKv)vr8J)zSXR zQ(C9@yFr^@N$4gmLy!w4je&^hhNfyPuCE8%!VK*?Y zZBeVSWn~ggM&+%#Ig;{>WVrJxK+>}{P}?moQF`|xd=L?2q1>^%&1t*&D@$9d<-$mQ zE%%w(-DfmbwQVq|Dzhi8v!vs+mET^brpr~equ;Smu!NUw*+|yfD+O1~NL8Z*M=`TA z?>iPqqn=pDwJB4P7*|Kv5h(vLJ4%HU`P-h1b+5_-It@jbQI%5dfSG!_-cBGws^}kf zc|~(<;^dU0F3(->g-LIcOn*!zA{Px$48|<2YU3-cbjcj57wwvD!+Wf0u&a6~*v zmWv2LEP(QcRRHA|?GI9Lrh=CX0MSN zqbIecQ0q=a9WV7Il& z_8^gC{7Vm}s2w}x60EX^KmwHGW+L^O@DW?8EGsmRoou-jSBkuyXI^|BY{ z4Q~!;M#YhR{>g{e7WG7XLvPLX|z-7OnqwWr=)o;tmlE^0=7N5nTADApe#5g+c}hGTz{se#CZo*p_BO#NlE}i|GX9E#=jQ7=oq}{sfl5WX4E&!0^PemV z_a!3+@gIZh_s>o9zZ4i1cLxV!6FYM`N7Mf@t?iX&6o!S+_%o{L6M`$t-sJQef0-a=E(o5Y37viu!*Lbeq@ zzBv{T+o^kMq3z~c6L82PR0Zgc>-e|X`Bsa!*@FL1yJ<^9qrzn@)Y?5Y!-wcX)SF8n zxh%`cN+yc}wCH6g7tK~(O2p(P&(?K{d-f~0zy_2z6)2@|$ja2Hty zeIdFc>Q|*v&?i3V0va*#+;Mlmy4sexeP+}0JRdklB`6GYY3l|c^&rLn`56Dfi?rG5 zn*$>Q0Tq+{@4+Cd#;#WXCm6&?bt8#&{o#3_-%NWwBgvi#Oe&U3PKp!-7sXF0kvjx_ zbWy{vG=V;mTrwFrqP>WoS~{g%GMQu1jo4nb0&QE!pMAc3!=dHQ!Lui)uiNwWcK_9( zV`eGqbpU~oiy`Ww@6~d@*V|QaZ=-LVbIh2r5tQv{N$DFtsg|; z&E4QD9FF)c2p;KI#=z8JhZ%@q>R|^uVE7(zOb;E9VXfEu#t$Aq_$^fAA2>Nf_;8zm z_02Ly&G5gkl^{@f=9X@m*1Mq~Q3o8H|-(!D)h6I`c6yd(N zfX@qH4F1A~1eyaJ{VD=84h7M)UghWtA^`}cTTK{xd8;ntGxGZ4C- zcF}~uTeN&j9-;$^h)Jk;7zTm^`VPho>iaUW2oxWM2YoX-IhY|8IszupW5#WOa9eMX z=8L&H_8AGpVL5MUrZD-+Z?-ME<2|cXC%n$@L!)zS2Sl!d8EW~iG5j_dR!Rnrxzb!| zBGLx}7Mm`)Q5ZM`oGlc@iFwW`D%2B@(P0PF@xpXz0YD02z<8E~VHh)qOK%bLoc6-R zeW2oa^nhH@XcRMUX01O>(U9)c2~riY?UV>t-5fQdPuZ7eU4Nw308o3yAO}zI?0X}I zcmmpey)leOeFUBb!0#t4zS|WxjX|5P(U_EIddu zFK?$X#b1V0`F$F3WK5wrQ(`lBxA-rdImQxw@dRX zKo(q2BRmf4W9|+{Q^u!+hIo?Dd{idQ-p}K+E_H#L!7W(=rM1Xi&sl@~NPf^;`7Oti zM~PnZP+N2nSLef{eAu<1_7%nP5l)0$Qu&5;J4YCUPcVDIdbKIEK6z#$;<4;!YIzZ@ z5Y6Yp0?nz!rktvzCDn;+EWA}ur?0Yr9p=v<@gNNqkWVl*3hCNp*8QvDJYgcX=y7GT z57`E5sleEMY?%!Yn}$e}v=du8LncY!O@s`Gx}GtGyIs2yXK0;Di|;ZU&CYDar#cGEW;>ZF;lKi}tv0e6;Qba1 zQxImT@&N1=QqSY>y7BFRP8g~=ncVTz`@0C3R5>v@^N0dnw@H4?dW9|;Rm%rNNqP8>)R9?QS6TU;XQ*_OeJuNTIMehZaxx62M|6&x+_zS0BIH4VXd zXEZdM8sBgs=BJw6<1H0|Dl_^cRW)>yz>X|(lhCD&k!B$gR-g$V(q z`WqNy!qcs`hMAYc`nzde)erBfrKQRXM6sG{6pp~$DSF5kde9Xv-!cH!(GrWeIQrhS znAgGflK*tWr<>EfF`Gg9EdZTxWQ8UGNU-ua;;onUmLVa@^7!P<7=7Uwl}12%9*od5 z;R8yb?0BX3q~t9Y#xPixCLk6lwh`7Z7uyd6nr}wK7okAY_~#5_+$dLlkLv)+7(GyC zO!3M8+R^ihfq#SY2$m*)#rVmW9)9GwLr}!>D?GNJLsMS?R!!#F$#a2E9G6#d5zR1EGS}*jLN%2$>(DJ6A^{Mmz1Kb#hL96 zR}=5x_{V}ZWLNJCMDL}^URKZL{dDlxl~P_Y3FLHANCa$lC7mF5mP{1x(ytJwK$^RmhQptXne zs%fSHZK}n-C>Cjoyb7aTHlvRcSl*KvUCvbElTsJtMPeJ2{1Z%{mIcy!f8F@ZOtKPh zektLj+5|L$9ReQd)=q{ccgkO~i{qWgn@#+^9L3~${tqL874!{N3CVLX=_PJeLDm_y z8N55&s7bqp?acz07LMx{W(#}D*xvbOZ)x$nDl%=;0dK*;d8v%-*EtKuZo7ChYAGGD ze5GV=1@WxhBqZwyzk~cqvZ$b4-Q>slac_e;{ned)U=(jBBI4xol80Yhb=6)^D&6jm zSNj+{9o~wXZW{_NQ^&JgR^-p9CVgXMYM*4yh01+CM$tR9C+lY#PM&l(}-1 zcO|T`9rPM|P^CMRnK7(qm{E=~Eb9|L(PyU%po~A5?M6n)kcg?w^0_FX1wa1m34-7A zr?tCVY%tF z;1`?g#42bTzo>oiwk?f-9MbU$T8FxL!<2E#UoW869e@hRUSW+p1phz*!v)+!qynZt zpa-2RJ88U#^+Ge~vMpjPyuIM;Xb-?NwQxWu$hdqp;zL?VZ=Y&$N)iMZ5jDro7m!Fm?R0g^mpeh40uC7lLZm8*&}0>PYUZfpGt30ll6*&N<*1gfr^o6oByBLrOIv zxuatW)%zmjwZlL2)m>ak3|Aum=wpAx@j!S+NDqrZ_X81DS>xp`bK8v*7r5*!b8|97 zdg7}P#PMcF^c@*;Qx5Nz@UmU;XbkD2Pk=4UTu{?Vi$&G1Mwog{pkJW4LOpzj`AH9- zJ{1#!8RZ}~6NH8Gf$Y(OTVbXb@+k4SrJQ@46@}v188#V9YmKP&`&XH`{NCe^peySF z{XniIiuy~HDajAV>S{%Nsp_`ZtOiwW4g@~IjHtr+40hV5B1U7E! z33BdLJQMahI(jCCSSc6C^1HmwT_0B|mwL5fctnyH&RpdOkDu!cO+nIYH7O3`pRdX2 z?yw`w=k^P69u)>3{|bjq@P^1FM&85Fh^=F(SxrP2q5ySIR~M32Zr)sD+PEF8$+b~P ziP_3WIfP)FO|)4#Ufi7ftm}b2~xcYf>1~PdRzEpe3j4g}9ei z`HCz<^^zVh>%G7pCN=;b4K~J(S1-_>oIq*C+Xw2|dax^nxla+QP?5F>ExMnHJM8DS z_AMoj1e*EnYrx!QA49IA$_0gc08FN*eRv zc|dr-c&GZ^i3h9?^s^b6fqO*1k-eyxVGxyxP@b`Fi-azDC><0kf4c(2GQDo$-zSU? zz-}E8LPdj+y)ws6q!FdH<^{1Bnk?u4LO zTcZ6zGMUY*s4nR$d1|aRf54!&x|oP8MzF80P`XKSeOu*}k_DqnnpVz;kZK%los`Ql z{9ePaqK{%<6*pk}NGyMr>Pq;2OPb2w%Vt6PZLDR-ioZ9^3G0`pK@C{Ld9v++O2cWf z8?eHO6+h7;${$hoy6L0K25H>;HIWKQbD$SP32yY7BqeG9pup9NAW)uX*T1 zkH|Kx`T5hxCyelmuA{BH>CSCnnJ>_pHFYsu+wfd2a>Y4Dz4rYpU zXs@qFiatbq>I-Q3eP>o$(0k4eni9^+BI)y28I+R3K(MvdmIKP#rE1(t5yUazL#pXl zd-)6}>EQ~*2rHAgxFJN(n*OoTF`51gfTjRYfKlhISJhd)A{W-?(3I;Gt?HD`~m`#nhI4f zSpnXZl}verrv2V$zV+#EXTLo5p<+2WNNV!I?0_ti&&>6gsRI%jlM@`7`;@p6Ze)Bp zpM;$gt@rKMnxdFk{72!M{Gx%5KUJ|6Oh-k!oP}JMxLg;v%kHj0B_AKw17HyO90S=R z<_$(zgJ=(}isc72A?DR42|~`w*ksKh&rfzN>xO_QViBp1sQSHVHs>lOmJ#->3ygu} zEDn6W24eBcS)DeW@W=uZHmD?wt$!y=4(~*WQN6+B~RK{?3t& z*1{$;viNe}sj?i2m;EU|Z>OEZ{FOFbYml!vG+PCg8rE5_vwlPY+MoyeHHDV8w_U z{xbhmZI<}jg8F*mkkr_Fx+Ox(hV8G#gK&40fchd<{v%P@4+L|nfe^<(Kg|@-n(6JW zVIra8`-cBc|K{;iw*?NXN8stgR*lpnoZl|j9(x>7Jv-!kK0OaL=>!aLvJ}Jan!Wc5 zK>z0e_E)zgj9)7s3^+|89y)zsL| z{J$qC8Zo}OlF0w^+}dlOrZwz1htiU{?31TmbTL6fN2Q(YBQ#`oyW3DvA(1ezX!Pj{ zLVuf4f^RQ_9AR5o2Zah51_7iB5t>MJs2oAg{+=g1OiavPlj*kOVZJ}@&i=l2^sunB zc#Zql|H1Q1|1BZ>@7u3%P${NYVM4d>d;oD(5jHgj97V!gRB6@>ljVmq%8Fbw-Ta%8 zcTr(jSZ+9kGo0-Ng1L4y1*HpPJOt_7BNyqE&Z-*m2qMvvkc z1r{+2Oq8!rHiNweABJC_|7x!>HZX&iVxfn1y^ z3tQ!;aHBp6G)fV!)S|)}`7&|1+N4*R+&7KZyy50(XuEAV6PfIE!P;efgH}P#l8Oom z*YD7O#nO%1%*Le@5_l`Ll}MZRGlS>J?g}rlPR>gtW^q^*v9)!F;-%;~)_SIOx(hE@ zEtY>1&Sa&t9*$_DMVbvQ;-3WU$*3DowWd#S? zZ#;^&X6|-=u9~|Y_ZLvR1BGjCQe$jam~0Ye76vDzdt@(82=M-B5^8G(TP$@F2{G(~!qr2RU+=J~8-h+St z5`^K4g#`bx(ZGI%jYVjE(Z-n0@(-RmzV!?W0u+M+o5BK5B7^5_-}&b&RtIa!SXy&M z7tOhGm>2_$=WRw;PEu|7o zbx0w1@;rGZ3VW}pTh@6>8F}nD7577F0W83{#y{M zqnO1p=pjkG#ef?K8ZN}%KeQu(TmG(l5uf^F+&&_YvUbt%;d~1tgx$X=?)#Zqj4fUz zOEwC_evtN7do?JGP>)Tnn`n={zjk`o${n6@K(fHW7>a1+Oz(bc;l{PO*EFnpY{9hY z>xva1HW5WRfNs`=u+@J@)U``>`(yWH?!8 ze7wD#!OrGh#8Vf$$k+E8ZPy;6MA=ahItQ~*UxWxpT|X$V8uB!Wr5zAq(zf>Zg-%?% z#7#>qp?ivuDC)O?=O-_9INq&p$wpILhW?m+@8@AYiUVcjUCLTWdCaPQ3pj!IB0k=0 z^cjAL?#!n5OLGlxd;M>G_PvPpwYXI`LIcf?j&k1qdWS!D>=hJFqic6A?$nB)2tFikmUE5ndx72a2Zn?Up*^MRJh5Nprtt(sVI28Y$9FX3?z3xY$>TO(*B29zczjz7eGGYM^`RS0Cm|C9P;v0`e-Dz1ulANx zIFYMP*#G!5eDJT~SvTIX?5`mu2mTp$PYQ?b&Fb&_-n&-6<&F@ltcdOFS(Dww;z1N1 z27(PRdAZZztk$&Yu?g}-&Z{mge-Zqjdix*s=)@?sKjKHQ!25}Nq5NO#t&5|b-TyHs zv~k0Fshu^kZcS{p_;Sz@L(}CT1@jG`hbe_TMj}Z$lQK%ffp6E#z9iCZ-G=YM1M5&% zMz*K7x37ydMYkV=(kv&|j!WK@^y}KL>FDn4{{#iyzqYke<-ISMdZx%vdJg!4T7GZL zez86+2(kt~K1wDCf&;|>I2E8q?sJ_ezw&(p6RTb&3BLJ50uv7jyZ%x_e{Vsve8D1q zrwt7-zYMUx1TwuTQhs56aW4E#K;4fJWS%bYJ_4oGgAO!y${&h=6EJZK0s=WA=4k?= zN72bsivQ6Q6e^KOipWI4d9Ws9?@qZfO=)*sWwkCtX(akkD3>fQlZs#{S)G`uOo(K; z#-L#vwZdPULa$`71FPj+Q?QZNKk3f%xa7;h85gKJSfyoaPwy<4 zJ42Bzo`&%t93Wh=XHy!dp@HA>;DB?@g@K}iLW1&w;)H=gPxmfTjusbotFw9~hIJ@g zS-pAJALRI7F{HezP}?8=aM=40zhW>)Bs7_2Fk2aM#E|#q#)CH))spkIsFfeF5HMJN zNkC{mwikB<`_g9hMonovSbt|l)NHxU!3oUcG&motV;M2fb1VjoVra5z%^2l{-sCy; zc|_uxbrNv0bcPR6P_~=kzSf&B&Kt3!`UhjS0|IotX%ohTW4k)$Ov(sK{E~+%GOsGI z!MwfjLy%(n^DcNgH-7R|hxT!0Dx1)TZ`&TkEc>>RreV7GS_Y0!@R#Tn&SdSOu z!-2O?sFp?MD+0{-n^i|x)x(SpE29JJM#^i+hmk1bfFa=>iB-0!JB`(~w$q`+l;R-M zsXy%?*VY^x7gtwjPlf}RQz3+n)Y+)rNr@jfwI!L!#8p9uZZF}j?zPe~V@36*ZguLa z7RCQ|FWTp@kl&~-k-fWgkiR<%YNKH)5c3RQ3*DYlYYpP9rBID}GZ0la?bV)?o&?J= zou#8oCDljIh8g8Su}M%Wpa8|$?_SK8QZ!5uAPR{Lih#fs&9lLXqAPD@&C+ELG^rTg z$(A9N;)}f{O|m>YTFioa_7LYue#SjX(u%I6yt)k99G@g$&n`tR&1$dj&%c8Tljxw{ z$AqPPf;Dg66(b2X$rGtu){_b0?aB+K2_ui@hEjzqZ3n}TZ;D`Cu#@27F!m+GMy}A> z&bmf$xR)(EZZdKdk*5e5hDNeer>B3?-5d{HToPBQ8rmzeB3zPcEu~ku7z@=REgv#t z6G14HPeiZQX$NnI#>}1d3f8eix5yyIKpWWn)k@P2AMTNOqoh_0B^K;4cCt4&OnGHQ zD!rmz=}2=p>Cy?7XG~RsWsoPJV3tMH>JBK!ER(+4M4$HdN}8l5gdWNJDei` zJkono2h$71Nc_e)?qtq}a}Q<%*-}KL&2)*fh$E$nizn`G?lP0L)n%Z)U4zc~zhFX4fTF6bC3ng_YqzE)F z@Cx)d0yruF-Rr&)K=nLld;-lY#y5c!e-ITef3$+kPiN@<1QE>-qCg$M?PoAVcnk~E zAGx47Dm%+oe<=Ib9NchN1Jgf#DEU?$DyRW)|B4EB1k5U8_f8$s`{@o9EP;H%>?;HC z+ea3>|9M*5a)BmL0kD9-z-1q0{e*^I0h%!1*^EwSZ&kI_V$QWjxMwq4rWp{~*lo>1 zMTuxKRT3N?QEm>gb~Yr)l;Q3r-dW{^n7{&xyuJ?_pv%T#>DG+S2LnJJVJ}Fue}7%2FsT$aFosD z3t?K+t+$%}Lcyylw}j_w!$YK%m-ThlSlVS16ljjU>_!>a#wJQ7czUG%H8$GlZV^m{g8hf)pxIa#wcfl5V;p=;GcC5)O20T< zJZtn=x{aI=CK}b}jU{g!ivJ+?+s>O|tMVDUx24Fu9is2Ss?@Z>0N&ENcc+p?-*&qm zp`Fvw2Hvi)Sdqo|g@#`tjb*qz?Ph}wQg;JFgVPbFy}fRCwR}&UW3ux3{Mk)B<}%ol zz;aIKir+W4l3FP}8l)hk_l7ZTZZ+(%LD0Tx4oB3n&` zm?D7$)wqJ`7FT5!PRykfrB0(TYi;@<^2=q_)+>aUP?glBk=)s-R^R{z2#Jbxv;1B0 zT20oQ>809F6Vgn~ID`?@*eHrP4Ake<2scuDpYYU{WACH+#otQ)sd_a5`jMzXWf3QN zQrxBK@~`{DN!@4$bdJe_za!mX3@JqGFa+rm%P3nVLv6YbeAJc=w(OduV)Y}Q7c3WI z?6YP|uamUxM$xl}8P!=kv67tj#cZ=n#_D*V@J4bq8Fq z{D{OGho9txL9yw=17dLB2e{K)nXnUFNN<@BRPN0ef#)1i=U2Xn0|ENzD{3oH_t21| zLtX+R8S!fARSGf%=C81T<-*+MG2g}1{`^1Yqd!=>9YLV5$Bp=5vTJ}Q7gYWncaK+< zH&w%R3j7)&M@F9#QF>NG8X8TpV{{QKf4EyKj(%uWEo%`*3lzie(xTJUXSJ7a^3}f# zd@MGgam`HdWs(Y4xVhMf_SI1@mtIyYa;+^VMc(pkK6_VIq+Ram3@4sW2T1$;g9qfGf1;stZ?YM;YQ~=f(`13 zBIJzfh=X(=491~$tcqp7O;4t{cT%66u?R%xxvev8C*MPZ!iXC~ax4VQ=x$Kw+y2z8 zgpw5GbHb^Uoqv&|EO?c%0aVz%0pBG`S7{K1ru z<_sp%fjehub+7ArCFv7zdKH*$7O61}V_9-VrAr_hNKJ3$jrxE#eu)NCl6ysex3rP{ zq?m!oEo4i4ELC8Ju(;?1-`kqP7-%A05XO2_Wmw`*vqESL$+}Sypj;`&nrmBV&$l-{ zAm@hB_HuB;%l2}}?kOkMQV!WzH`>a|KG8{UFE{haBG_Ad=aX-7c~)?FTF!UfKk~dc z>hG@7(KZo+OKL~mbf1woFQG!k!QIRZ!>^-UQE^>$>hF4*J=aYXC9A7qb%t$tqc`My z&DUsMr|aTKWit$w50Sq-AitWM5H6jR@z`@3b|!T=r=ng+?2Fx4kFGzSFuC5wrEBAC zk@}mTrrajf>#6;$M`U5y8)(dQj?@{B)T4hV{5*dzeRC0*b494UmC&9fD2h&jXM%b> zGMx&pHHp>_J{^wfy&>z3$49XARFO1pp~6Ryscb+p-GGQAXojap5aZ4_e3M_a}9OCcSU>T$j^c3#>iCC`}#J2Auz00|4)bkiRjeBPSxgs_{lk z6Jc~_qFWWGIAdVLd)JX0LIF`@WyI17s@)d}n?g7Y%adXfy~5Il5O_u}Mq04W_#6XB zj&IW22-w%wvVTHT{dN=pc96eGCB`N%0xxMFv=8dlf4vc1DV>BQfL>$i6~{&rWq!zxf(6Vjdx|z zq2xu8bTHwL08p5PA*XG7sR5UVigbs5%m%(5yiX;9`c#;Q-8sl1UER|ux^~JKA1jbP zfnuIGolonS)Jw6vZVR@o1p6E6(>%i}YPzDL7H`6`JZ|W3;mG)YYYr+vz0nuvcU@H){7a*Y!|kPx0Qf1#k*6yHr|_iYs+U#FxbP@e)%`L7Ec96 zX+@);qzi+iKf>)rh(@q15(iWM?C;tZkNi=S`Y~tbE9pUjeeRw?tJkTBl-ZLW=Io`f z(D7GcDzyAlC{}{7%kle{=-&x>LV18UzZ1v1~no zqn!Bn_$TKxq#H-_Ug9QVCz89u?+EiAtSZI(cU&No0fZvOV7;cT-m_>QzZG#TrX|{)^(pL9&tG)*{G587CKnvZ8u!uaQr&g9!=mP(!PSp+?k;~)etgak zz8`UuoT6vil$a>)%-(wjF!VW2AX-ni16p5klWv`O4E@RpXQ22lLVVxL@sI$ma(E4X zWnBlnpFjKY9pK*>-wJ8}>Q|E+=acBHuse;pgHbAxI)+Nv-K*%2-32JpL%oZtsmSwCg^8`O?G z*nE=mVfSkPx0X}(qc?ny00gu{@c+&OtC;+!<^1Ob{+|O%4i~H;+Nu+4$M)uC_wsp& zE6XUf33kjdF>#&AI0jZQ5(G3T0*YY>YW)^{hiGZ%g&c906$CFl{uWH6F zIH1;pMw~9ho{2p~RZlqmc_<(GK*1%UFu{er%ezsUX??RhoZwaLuKv!R{>oj`0>ODh z$WQ9oo?+Ns^E-=}50_kD$MfH9uYxJ&wJe^o{g-e%h7Wl(cRx5y z^*nZWtUJFGEYkjwD+2~pAtG#Kbh<@D%tmiG{(rl8VV5%B zvBK*0JHy{JmVwe3(O}`LLcc&X){+@HLd*UojT1|os8oZi@N$W;V27(Y^p(w!P48!c zK!#|BVOS*^jw;gZMSI*1Tz$M!gQ%@%qQ&w6B^2fOfnH&5#eMrs;nNwm*dEeRXKl+6 z=4Y(h*GjfH=xe5XzJ%eRT8ls$8+Fc5ort+*Tewe-49I~xj+A3~`$eJ0q--aFV06r% zp=*$YElb|$)krOKWIHi7J*3YlJ~6DoM5ykJ3*ez zvn;`pg>$Zu4$7JuQiFfg>cndC zO#TR-BnCAy?L(Y~ok70LJ~kA-ic~|NMCwkaM$9bgrM*T`)s+%r4p_CEoTC#7Ck~~9 zmXYPP@Sc^2t$#? z^vhEyTydZjX+=MSZl^craEK)i0J%>0vQLcfy+%h{Bh}R^J38pu3%Kp`xLAWdtq~u4 zz3=xi3IHtKL*eic45tS<;mh-tm=KP*$}vf?aLu&&K~UqhRnUZf`<{RXqxq&nFQm^i zMju;Xzinp(9<%yqV=j~unL9S}D-Uzl20G+0fW}M$=%GwF5x>7 zlt3T0ldd_{%I+O}?cv zZIkKyZ%nBm>q<98+c-Le#WXnuUNL(^mM5}17vNNP$Vl(~>;81^VH)V5zBd)fVi}`y zi$-bD(V?S}Sv}&5w#ppi1z&2AIYqA$k#HhOxv)MTX8tG$hnwayRk{&nZMTV(8<7z; zy^kuihu+GZpopPu^XfFgqp=0eAa1SJzFRP-bhVd?GjnEk$)k#dGD=2V?G98Rh|tLU0ycZsog?87r$N3zc#WYmxv#x1uY7O>{aC}cu57JEPVQr9qPuUU_K4MQkOsKjkPk2QY{#NDg=?T;dS7maZ?C<>>yqh_%ks(s|0`bh3$t{26S8v8LBKC#(GwxM)j+`h29#sf!7}$ zJkK}8rOp=mGrQT_7^bOt{Zq1~FzTz*%-Bz7)D7tEL4vZ>OYnDd*}fQxG-#}Mq1g#D zrBvs?KuWT+^eTTI8<^h^^wC-qw`v_qs&v-NMfB`|1FZOg>6}G zPO1$>Xi?8MH>>%3!YxQ5k`MNwQtspm2ts!3 zPiRcYk!NFTYJLMB*<>W?bWgmg{8GHkCym!Z;W&!6v!`tVe&^?wr-4N}n9yroJ)OUP5 zg86Ju_bm&vfn-;LrqFIMrhL5ctN58g#hthXs7Uq^a(xXD+Qs!P`*U1$i0=eB0mlh4 zA*h8R4asdrAQxBXv8Z*>hmifY&ASpa)O zn3efzHs1~Bu+4MoM*KqcIZ&~}qQCFtxC(N8@62UQJN90xU0&%uuJLfWYR6Y1f8|Z{ znkL+ToKYTDRQ^br9O;x={7Qany?d9`ThElzAE6`Nad6p=W$b%M*hEN0Afq2(`^5Nc zk=M1$n#9z*%m_INR^aR2gISt9`Ezf-f1HI!$r#5a&@;~cSQ2r#pQS5V9mAC?qyO7f zrKl(p#0---M(Ot#R+lAV%hHPe>EM}F2C^q(duMJz-{ZXJ(2g4~zlUu`nEJy&thYKF zhLOr4*VZ~6@)n(lW4GIOlm#4`$jgQe@=Qbipb+4bup zUvQ;vY&|7Ta2=hHu!l$`A77k}F^ElVUn7HUFH8R}sLjD1r#2lg;L0LcnaT;FO4v%H zYA9aGeG8-IM&i2!CFB|SY8g#0@b|$9t|j#RLo_^S+N3#dm>2m$h68WKO2LU}+QDR0 zkMOUB5HjQ#7W;va+@<_|2_$Yz1CZP_PHb8}b`H703RaqH2)Ul2v zTEnN^AbtcSNp}hyolhH7`qU0DGZVw;z&Nu5+*z~Myt`^#UKW9PS`5xd|B!}!QcI;e zrioFyEn#u7I2Dl2iW2|Bz4+MIa(Ea zVd_>`Y@XpiP!Z@S7m^2PQl}y?q7(Y#81{yMR!jCCoNKSZ zs(X16|7ZbtYUj$p6(K2t(6XF19a?LjeVB>ueWA7>=f7Y@)$6IyV==R35>$cWO_mJ zXk-R!Q`=iXp>B@)M&!$3C+I7%3{J7z%$8z&cxseHXPS@EH5x6O$n`T1bwr4!!;poR z?9{OsHS_)K4KIMHsMre{9D}gkM($iP#luNFE=0uG0rOH#4Xx%(ekLU1G{3aM*b!73 ze;;4FM*g%~WER(MYZAsJk02 z^Qw~lNqf#+U->ix3i24b?7?SpPtR5J4*0PtrA}EI2U*WEhc1+ z8JUnuzk9LQ9dVTXS>XRoo3~w}6O;sjfJ#FDKbAvFGh5sLVa(mWUOu{NZ!g?0xx60c z&Pj5q(ya0euE8ZqMfr5pu8rPSR#y4mv?YtC8zB@K@zNcCk|z*IvZ#t8uE2`aQYrB1 zvq;3jR2{*S#ka)4=ktESc;(&xi_~UFFAn?b3;u1lFvxw(w&0x|BJ_RVro=WW)%0(T zO$(};dS&iYv^^|+?ZG?x;m{Ypun-=J{ci$eFOBL0y{~o}=?U@Ig_@~>=!ILr|6Rd= z58wpeX^3r==LlwI`@-+z1?p{$6*Mdn%v6MjgG-;<8lUo?K(m-KjcJ(rmGfyOY- zKVlI7MmzGEW!Htv_HF-3Ds=f?YS)Enx~%z#a`**Q{0)8wto^ge@r`PVszK->tc zUC}2HZ~&1ILI7wyY`jI*+iMsQ4AU>8IXaf;FLSJ2lw>$jJ*&Bx^g-w+bL{FdI><7i z^@u-ORsgun!l`-96)f#yZj3!JQ^lrfC*c*U*|{p1Itdp(Wu<`@tym|U_-gQUpkLv+ zl*H`JhPk#~ffS`c*-`BZq7?*&D1GNZ7C>?k=^#wY9Hrpvp(x`guA857@p?rptU%@< zQvZukn7DOw7~AwUK}#1M9pY>cri{46RE__ zc>EebRYMdSz`>P)t?ZT!80&6a*|Q|x1Pytj6~Ko45D~z(Lc0>KHXVgg z?f4BSM*;Iqygk~f6{0|n#aXiY%xf(2Kvl>-@RoIKQ}1OXvPgz=ENE533x)vu0Qw&6 z$W|pj#1$d`09f`U{xLO(?pqA0V)Kh4_HP6RKM3n>>(BJb)Ew^D7nP6`^4ld<{{K z570mflq>d34fpLDU$a`vJop0C6SrX@lqM*2p%nlMG`oI+7~BQZs9vs>2lE>C=Fs- zV|o=xUs64B$pLr=$a9fnSERHx*hip+c^8b6(y+(m69>H)G1H&vCxXRG@f*!l4b0Nc zU*egu$?|Zuy#R^p&wMJpGb1`&f9%56%&44|VgCZEQ0joWt>j;}F!vaPI(ECIcu4*bzq*$%GNr zTnLJ~KLmUa7DgwaG#aR3?;M0r4@r>`b?=-65lB}rpfVaHKqP>Kjaq^R?pF}G@j3{e zesa)$kAjd`JU(E=Oe%#`EpRpPNNQ2Ona8f(a zTJ)NoxZ{Mmd&aP&sOzol4YCp1wwC=_@#SImex zVtsK|A=r+*xSK3s?fDw`KltllLjRtd<(5@6nFaUwiKoqFM*VZi3YUcsv4xjjq?nC{ ztmFdy)G~Ob_C^+p4tFDm8$|~%7i++}KTbK>&Ul7Y6GwH4 z6K2^Fj@NTMDqZl`w3oW2CTjH`V`G-u1g z_kr7ZwVs=fPA122XQEpp>a|`@J75SWcz)9+eX^yo>Ay>3Tjeecy@Ilz|DTAh*ROu60 zr}4EOtx5H^Q(}`X<-X}kqRZ(NKPEQ{Z%;Y>HND;-D{G+xv0BXw^uQx zAGwZ8J$Cq!rm~nTSVbs*yws~6&rmNUmdNMuWINW#$xvM2I4DuFPA3*iE>e%S+_%7Z za<@{CqMl1UO$w6k=D>Oqv*p9nEqOJZmDWAYWE;#t?+$OtMiP?YSXxCR}0pj!Pb=i(9Z7{usfJcatyo<=W?{0mT_Ij1Dw@&6FBmJ>*j&g>fh_exod%=Y zIh=T?rRH+dcKpavB#V5EX=HC)XH|g5KUD7p*3Kr}ia#W!hdJ8P+(dnz5>Cgw*>clj z=Ilkj4t_pgA2y5>jgwyp5FvvpEJ|HCQRAe4f1pm|r-Pd1eGk~`O{5b=-rsKk2W7A< zygvibWP{@{g=?Gi$g=c4@tWwkwZ3w6_}>_wtlHJ1%a%mNBDH0s z|2_{(eXdF=6X^iea#_$@MGd6z$Qf%RK}o8qh5_h1)l7nv#Yxs1YYt%zcQGMJ(m07?&B=y=5t-8Pa%ruQ1g`t z_^?d*KvdcGXkLV~wP_YY@^%O;&~$4tQbd2lBtjCI^*q{7^}63KOoy&zzMP_4=ZdP@ z5o65*Wy-f7ggUB@qLqnAd~~C^ZIvo-dpkpJt|usY1ult>w!>lkiZ?G0Pwsqe8)P9# z+qn8>Pl4sq-DGO`{@H$fSrXq%){^vX)14m-E5ICSydRFl3k-reWP&N;L&`jT>IhOO zzQWoP1(SCLz{;eHRtBl1z_@N(o#1Z z3L}0xqC@^sOl>#85a&QzfS^IoBiKT&zA95$uCoH3BUbbFctORM*EoG`eQJ|Nb?$Xg zA8A)zL@JrdR%A?*fT}71p&f*SfGYh)#iJ1^rcKWIlz6y#0LzxnAeAH$k8vAm-5H6dSS%v#bgJJUB+qbO`Y*g+*!bLZax~$u zF>-q8^WM@)uS*x8+uTtGC@Qkv|79WK6MytH#~x2TL841ijXy&Zmv6^A!c++NC%bKB zuVqr5UQ!KQsGO7mWQ~}?edr(F!g<7Vie%nGPE(#>dLtg?n#Vegn1q`|PK&0+b1H6| zK-PHtae4KJim-u;ZbX*UP7nzp?Fq4}DR#12na@_kQ+t?CK9~fw%}|lgc!*6q@M`rl z5SADXKLOf=x6ZKIC3cj46)6s4aB?5e9+*q|q!5yT|8{g)#V995R_5%rX)H1AFao@0 zwhbe{8;e%rH^#9+V!KjcP(|@d?wi6O1|=APc(0uuD)&!%KeZehwh_7TMv- z#w_C9)85-icG2j&3|#{CBKNut;YxK=?>G-_9#9*o0xb9S(fpxxJyegifi5v#894)`Uz6k(4E^REm>tR;Pj z^iOODlqj|7@rfI=BC8H?w06je@LE)`()du2_u66PTfwyqyiXX0(k*#Cx;4t zFcr8&u3A$E0JAv|Iu%QNY=<1AndB?DT^W3E(_W1bN$Rr6!) zEIbonYu*@GyhAdR0+S;wfXQ)mvI(CiDDu zy4ElNy#&`X1?fp=kgfn)V#!FnYG3p&-!=j9rg4S&U<6{6rf~wF6o?NDS`$6U8a`fY zjLNYJ87O6hZqyS(>~{=12Q{lp>eA9VP*nne9i|Y_GQzc@8Gs6;0EYFi*(=~4NQo{# z>DPc@{}qT9VP8hy5yoD{2F{HpyO^9heL@&}e}zj`5|x=PyE?(`#)-@^flbz#CZ)-N z3XlRg7d)M=Em6Cf&J%!roM*d!rjCMl1r20`gU+aed9c1jYZ3qOF`2yw$PrmcV9ny% ziQdTFf+T~iJWSkYq~09qT!*!O5kK7sW4@PtGxJc>WmsplvvL@EIPu2>`D?m$#Ae zG2CkMjSgr|jM&r;h1UzhR$mkvG2?1xZ{xKAUWr`g;yrI0Q6urkp2CRv(HiAJw0Pp4 z`wiOnd+Ki!^L&djpgAzTVcC+Mdqiz7F>Zdl@t`dkw_08jdG;S_G_M@4pW!@NUojuG zZr-qshCx*iH_;35em4uajKz&3(P%b5)67v{sPB+!(%N3Z7~OzDXoy7^-T)+<=$&ci zwf5|3Vg~YWLEOR9xkF~^!qqgOTDa0eeMPU`k*^d3+^{os0a>1K(%Vg1`*!9U8l@(B zfxtCjZ7_S*mpG&EiEh<)zU%chjWr%sxNws#_O4Y~LZAlq_U=`+#wiA%Ry#rDTQYQu z7sA$0AtwCPJvUg0 zxN%%7Dt#l1D=&SVJ-3>(ScQ0(kYqa57Sv{Z(AGyptDr+Q@JS7yCZ>6VCH_zt-+aiTrv4(UBN51h95axbQTOjvc|0qVXCE-`cdP+zyKkPa?npy$trQXlHBnt`o=m+6d6Edw>a^ zP~L8E*uNoEV)qw=m{-WE{Wvnt*wa`{yi3KEJp4<>f5wEBUA#-Z1)dnpSa{PS0;MF4 z9ci=qSiY80Dz-J(y(!IBAj1_ULX8E4&4)s*Lo7?;K3kTpxbr@8UMcR|PENLeWMHG3 zqq9?`T|Vw))@>J7HI4f|w*F>?;k;fC+s4jHduIzfE}`0D*hogDF!cz}>@1(D{4=`; z;W`iBMYqS?ie@2O(z{WHadNdPR#5hLa&nScqYq*dM&5HxrxqUyy<^Ug{eH+GyzMO@ z$%|s$e`Qikp{ktv+L@=`x*r)cbD2Jgszt>McYUdTW@hbqL~PW(3&#{ITb3hRafsin zA4z<{8(G!C!P6sPRFr3hsraww5Z`4)5w2F-sDu2to3>6$!mZ6ps=w#ltIkufB{v`a zMt=B1Y)*ZPCn)yH#HfMCR5|Mv6Ss^0@78D&&7gIR&61o0Pl%k}&nlHB4G|uaJ3yAZ z5*a^cVe0~Y%`FzgSTk4qUtFO2b(5V#-DN+ku5M}lg9A9slCAoOObiRT&42KDSY6in zX(*D7wz|{kc*`Y?UelQaxh*XS@|S&;DH2IHEfI5<-GNxE*oysQ1#Q>G%$hnEB;;~h z+Y`2lgH3*?7LTi>Dtf-m&S@BfaT7fLwlq&f*Yf0^!qwSLX6duJ2EA?>h5=U_sQ$Y1 za!jy&xpm?5gy(7dJ9=HZjd38goXBd=Kk5Jq4Lb);wNI?GC(aX~PVv29;i@1s+L6>lHLb zlqcKLf|?Tu4OeB8^85B21pcmuS)`QOPhA0>5=BG);o z2Rjl9FSGl-&{kJzZvB6FlFk)BWZ}q&3O}npWC+6;o?iYw41Q;k{mBj-qQZ)P6a(qe zVmkR$i!Y+t=31mjdc28eQ$14x!FMX5SmUfN-;8MHIShX2$_8NJd#-epwHh|W;r2eX zxhhiuWuMQoDjuus74J1JVT@dTPtjUJgj-%@WZ4ct4m8G25dxcU(~>CwN|@bfzMyL? zLK#S0LV;mzIqn|juEag5$@YGJ2s^e(ohb$n| zP#UOYKMV00dqN=ETkV4QB0Knb0=Nm9$y7MwsU{Uw=7Km9XkTPYH^bDZK`gDVD*n-;s^V=F3*2A?6M#077rMc(K%lHaBdVv(kN z#Oe7L952AFQ*o%W1BrNUA<*CUC48%NxE2eCO3U#f zRC5d}a+3!iQjes6>E>uHC)!%NPA8OHN!pCh@)YgrjmL7o_wyC%yC-LID*O)oZ8P;W zbeuh;UXkf`H9BW9YJJq?b&ugGf3t*`5n=5}YOEkIqBirKeBphif{c*7wlbF?HOJ%w z@X-Zb&>io}tZ2Qw5LUA}>eP8J_I~=qw~T3e!VZpRp4#pJ5D!K(aeF@&jRYCzTQ1bs zCOc~gGnEr~tt<<1kD#%tq~hQS{|=9pM7Wi-Awj41`%G8$)5A=1g{u#_LF_fGgukew%dnPQ3}EXZM$W;m z|HYm7*fGGi2&d}Ix(KD2HGX^U!;5oZN6%(bfWOtFw=jI4+xp;nnPQ&lq>Sf!QN}>c zk9GjV@*1kc5BJ4F-$k)HPhwWK!DPzAHrnKbHBokDPl+J?AsT~U#Y!%lnAwcH(E*h| ze$`K|By?>T@Qh_+8`CNhX(uJMSs^Tw3^cUd0p~=PHOy<`O8@8IXX=}E#xZ=z^csg~ z^$+pb#KhwBnT~+uN3l00*vqoo0vss8MQpso?}PV)sfwr;j@{!D?A`Cg<6tf)Pa0vQFTG;sO$vOwdMA;A7 zS;vqUUl9!833N$$4RvgB*@-n@dY6N)AtTCil4zLtrka0;U#hRip{b}^CJ{7UQO#>N z`INWOX{-#d6>i!$EGHAM?eAAdFm!AR-~H(-ysIbn^%OX!6x(7uDrVCGV1TASPi5Mj zrJ_%zmSZACCoF`_A)H2i90mr#X}jEZqo&DZZyt?NX&_hM4YUmNaCY8TtET`ylc-Oa`{@`sqntE(*`ezH{?D zg+m>$IF4{4XU3a5MyUu6yZ2;he)AwB<|+@8iNk|i!m>G+o2w?45|KLDnwx#TX}Ox4m3LLHdp+YZW1osb zRwHN#z0p1yYHea0i9@!p!I9_w9aCW+5~#@2qL7g9GT96_8)0>A51D);TwM67eR!Sa z$Wymq!8!V`b&R?yi{1$bdq6>xQ)eB#ef+BKtG`Cj%J_hid1!?fvvO*0mFNZ1F+<>G zMl%}jd1!Dn$P8W#UP>w5$A8esBC%o z>LN8uckS$L)zE@c+-BL!rD^IEuc5s(LTL(Y{g6KoBvnZd*HT?{E5*;(?;epK zCrv}zs99m(Uy|?q=yoM|-#x{%{*LwE#HQ;;ev%QCX~1z@RqDh;Hh)Z>^tO%UmX-Wc zO1d>XJ)G&Obs%4iYip(prvEI^naO&cG`Y}+gKZr=b1|((Fzpi4pRBLjyM8YAg4_LK zfFN`_9%(^XrS92tZ1pzx=7>tU_h%g;i6NH36r7bb4Eppd>m@wD75@54CrsQ`{QIKv zh5ySyY<-+@)j-r={tG>fyjS3>`DwMb7x)YQ!U3>9=1`MB+57f8J6=gBPq{FD!>Le8 zK~3(>QKBcrH+Nw$MmeZS5@Y1yt`xoC(`w{}`4IVy5_-J@g!95y4 zozSrVqPr@-WuS7AN+p`we1wFMf}NRmS|Trj&M<~~?5=jI@v+Jz46VByJ#BhF)Mj8qP#mp?96cU$wVZKp zNmYD<+5D+yKv{m;n`JF;X==*t+Az$o3r4*4$*KV*DprPd=I$y-wx~4t#Jv%5WYH?87JhRHnC9 z;Wbm96J{~p?=pF?ro31^mF~ElIxZX-RHGa$c=U3``gb^uJQkk?N+XtBSyeptil;rD zbaF@o)a}U;eo0p=!TyT(H7*xzZkgP!EcnsZPtzr0rS7YQNw^)}J3v*-s>A`3xUE^m0`r)Wsvm~dti@ttgSV6TWIdcm#PQoSQ%^fS}_n4fyLIbo%i znE3EAwYqE*yAe)c$GH;PQ)3cEc)V*!x->V8MDPX$qI1^E9Le<}f)#N|@Ao6!<{#F9 znUK0|)x6S%q=YBV<dSK=h@OCPC}((MQggQY?tEKz^OWeA8S=OJcH{5{F+Af z(Z}PB37o#-QPY=3_;C$y>SarE^{ltX`6#r*7)5IX((a6~BrYptI=Mm&EYTF;uJ;Gh zfoQy)Lk!TGOD^XXXuK9`jJUGVysdfkw7NU9>VNA#QKDhgOR&_yW#$~!^9_q>pR)L_TvKeR}h3rfU?qNLA8trl^;BT%HbZjCv7 zIf_^J!h_f+I21|~{^_Ha2|MI~2;7ID?5e8l_{KPP#g?hxc|{hdq6cz-4gvb7IgXjz z@fnAjQL*Z$?+9^Bf4j97?ZKWWd0#G38codS!ZPWU>LfFj0rxcv_S!VLp+vc%TMW74 z`b~s7YBuvu^Wmg^$j#>lv5*=GJ2IiJHi0ro#NWXR@R0NTBxVg8&H}80MwACakAg;= zLBUumx}mYVWnesl1{JLNe^f0j4Fa<~oPtrrlMT^Nn*KJo3^iGLMY+YpEXhhqWTABe z!6aNrjM_iR8mT!32(zHmCf>@`lQ|DbbsSmR{_}K5V!CM@6+v z6pVgRx6u1CM7wrgCnm&D_#MKA_Bq^`qnh=8U5SY7ovK{Hc2_wIc1E!9VpI-xwoweb zPe>%c7Vv0rV~;mp&zCVpY8WhxG87lcvOcSuFB}K9i47l9yN70>4HTv5V$0W8zOOB- zI=f*=SdQ2%G+~*~5UUR&HW)Is)(m{7Pt#4(^2Y)!DwyR7XDuR`g8x=m%hfY3ZyNEU zbpHTi{kd(z^a`I`SUUxM%Ob7ajaqmscv6UnD_W5vjr33wf{o+rS8y zYv=NtV+4mCwcm%-jbJw+J(!ALH4NKRuoF8(AX-e-j|?aQ6?zOE&(3~a4lRr}U>~L9 zzPW{&-}NAZgp}>$s&Y1FvDR=VBPyf2I!AyYi_r_uD@3C<(mci9EJ zOKVNmZDbi|^2KZ(@#86m@1#_88@nn8FpMEF)0*hnS~E?fb5u<>i>g^pO%-khOBrpj zsM^WdM0P$6t=h4^`=*haSBHVVd9UR9s@Xo4n&$^i$7<&q)Ju$oYQ@g(ua|RxJ_o(a zSRDZi-;%P7t68K5_4#Qucqx?QmT`mn&6KS{*(N3eyJl!j6;_3Fat$84SaI3KrU8}VLF<4ygm;fqBNNRjs#0Puzf{i z1K;}uGo!>zv-2@hH(!*xgfP zu-A4C<6p~ULIg5|j?j$=!WbHU15tK|c>2%CjR*&`GB~F=lYb9RtT(-Uns73?#m;U% zq{9Mm6M5QCue4uT;=k18-RS%O-U&C{MIO%2OW>P7HB=Z9J zh58$}`QOG72ozxod(S6y>`hQb^56T~>f##m9T<*gn>wCJ%^G_eVkgzQ!b1Lv>I6gy%l%x&l7FPcoi zeJo=<`Pw4BrHgAb*}6-g8%tMJvrw(4pg-1L2v$Ce=W%-#;tVbTh=cBgAK&}g$!&&_ zM?vyCQ^g&Ph3$K+)X$(AEu-D)Ph!BD2C6l2C!QGUV75c$WB38=fm>{J%~e}E6oZUc zf3c`LjN0fhrFX<*uG)v$AvGoU5ldJ3v=|vsL5-YnqnAU5$CIif+ydx9l|pGEc19&B zKS+8&Z8bxnuKi9(CsDpLPboisLw-j!e5`pk5Sx0RYtp6wYWTj))g*6c zx@yOAY|~VDpsrNwlMx5Z4ua{+C`#Y~@9en8m71Oh<@TiA&1@`!7c#WFV_|yks1LJw zO!TNgj+fn&B~!rGO%NWs1b~x}{Ql|t2$=G~K2_HKn#k6F2BXcQlM)M||I6D>VVeWT zA}lFQ9~hzNPB!*HQV_&bFx@aohqG54A>~jT1}R42KCNu z`4{s*VFg%~=y6iunU?eGQs9|}Ie1(EXxuPz&(0X8A#gT(Zd5->8h3nE58*nE{wvNB0>g=Zy_Lc-Y=^tX}I`4lx zXobAJL$X)VA3IU}=C?|U+)ms^nZ&pMw0_V`C|Nz<^9vugoX5HL_6quHyu>Q3H|-B^ z_cUJEY3b>9qmORRu?aSBZOG*uAa!lcU+}}1yv9~YuU1eFCKXKODvrEg$;)f)DrLVf zh5DKDsQ%RP&-sM#aB{(qEBS>Qw>Ggy$c6E<#XDlG$gq3^j)7L1u|NFHJmp90Uwz-H z*L42a;D0lBX$E}{#e`pE-FgBN_GyT_vpeJ{@0}%c{z#$UNiAOYWZm9^Ovqqkk*Tkw z{ZJW7y&gH23PZs2J*%TT8s+{^^@yq$yS`VdDesDem8YRUl*(7VNiO6Nj|-XA@>_bE zPjBQsDE?f#F5cNIMgf(!26hOOsXc z239Fw+0`%;W-htOu-fomx23o-b7s{ap;;-3E^RVv`r}Wt`h&$~@wT zar^L!Xi@spswA~ZHNX*7A4~9^2ghp6h*;_Uw^Dv}d2=kS?6WfYrSixoQAg>(?Ttx1 za(C>MPUf>^2l_PRn!?hfh~eYUZ2w1YbSXhH!5E#IM(SywzXqena38-{ei74%-;iM% z(=+l{mgEn{T<_CgMDLf#7aW&7Rn*1fqD?z~aiT(HIdwA~b0z!0(vA zK^UR`y1dH5jL-c&>tc~lFKzq;ekPt={=ACb`XbCZikOQmMwJ;J*nB@zhYA7q>NhGut89DnU0tE1AGUhg#^p zhJJ9LJ>G*x$U4qYPk3xX+Ez&!KUFy>YQQ@5O9+MxO&lb_Fe)#?G%D`@@O4f>qQG3X zZriqP+qP}ncAvI=+O}=mwr$(G=f9eHnyOS%mFHCL>}0KPMS)B~^4i?^>50CYe;s8> zz-{MJnv`RSE%^7B(Ft0L|TUy*bS zg0GXV{S@!KydoL@5%Y+8y82v_KHwCa})%I8mQ8wf%9^_LhH#ezU z#4(d4aSnT2aA3UNV>)!I#+V||ljGjYw>akWd#HMfDqpf3_U1m@#l13Z++ibCSG;Kr zEBr_fX4$F3#cFTNi9U_@FSSx4NUKT9rxNNAs$u1~*=$&I%A7oX5Lhb#(q_vPd?#>) zZwDoI4dCw#!DlWNpTQ^y&usvfKk&<#&^HP>56zOo=L%o+DsEJ6IqTn~nOK10K@CT# z#ZMO7hB$$#dw;lBgb4UllmfwnIu7F-=n9^uW@?fm1#7)IO$vvTU-kKdDHt4oAd|0O zP~`QgplV=_EGaLDjt}H}A&D$0JfuiWSHAB+ry_FPhyo}u8dF7ieVjGPdbL8sAQ0W? z?o?;0G4@^{W%_3c&5xSJ?P%L-u>7jz5#O)_shWHBvQ!wz*3dw^K4t!RpTbk4Q@;Aq zt(v3FerBP*JZ&s=**qyeI>@-ae;1)=E&2)GqiT_1 z!IAFdeFVT0eoO!M3Gi7Jx7x4JYXf}KtYkWr@J0iL{k=EOqK5YWOpQ^fPiXDCs&FS&DIX0hENl2x7Q5x)4!BmPf znHJ3`aI8iTZ4?-N8d>oYw;OY)0N!#hxtZB#AJntz9g3ge?L+zd_p`)G^3sFZ==~k0N3vb!9UD0lq7aK~V z?1>C!U|j3ew6tC0;v|CD0;g9M=)n3Bu@yni5guI8wE24-9r^r?oS>nn%RKb8vfVZQ827(3agN)NbhS&|fTct?J}1*dLlY2tEk7r-hT`CIqc`gycN!`YLsp}A{jCD; zSn!&zEtKM&2Qw;2MAI|UKNbnrPBMsYp0Aeatu^DK+UfbZoB(@zST${?xQ`)c{r=iK zPW7pd>R%>>V%OpJ(S2;U&o|(O0g~cSxXWAUlaX_JZQY)-nkbg6g9sq)r}YSJh6Z1r zY0|=geK|#&#~(NsFO9O`r+HpTuKao|MC|23wedG&I_6YnAC?os^G`2`s*)$A( z-tE{Fwe|oOTrtKYIF=ArsH(~657Lk=7!$#Q7PgO5Q8^VnCT9^uDGyi^Y>2~-G0_8~R7U`rv)a~7J<>eISs)lLkAd&%ULCG=3 zJf$#f$n_G(Pzs4kHVIKtH&vXoaGE1*EYZsNxQngHG4F&_nG6xKjj&xzVM0V7JS0u> z2h-}2+B#M@QE6wbN#H7a`3;Lt{d;qO3U*mclr>yLefOKPb~s#rO?CsEs8xQetYIW>n;|Cz-hAIEOZ2N- z)nrC*4;gY3@7Rl8fl=~jD#TO8dH#Db;Mw5VGvcRU=FAvr0YfzLnvJ;f!gg(3zt+)iACyG7WMJD{c z3b9ECxwnV<0*NW(^g=|#?!lmRIQe@<8)}v@4{*VGXabaZn@se=d9#L|AHa5BqT1FW)i(o6PHcGscd zL6!82T;&FlB>61|NWGY%5TKwbzg^?%(c)Mj>?kPQ*{2MmjrqL)$t zisT(>*8!VNz+^}j*sD`*)m1!{~a|Yw8YSN3g0ltQoI9U+`+d$J+!t`IQa2hxin&~_9cq_V;9|D zTGnCF$@rkKw)DSFPkcvW6?LRmx+tps=56Jxh;%h!eRj&Orpp}%U=?kMrY;%m&yTex z2H#5Pm(M^JwXNaIA=OT=A7=TcT+l}+0p%$7TZxPYLR1v@bacC>E^TgH;XAbGk*f0) z?%v^9g@~G!b$z8&VuBdmoH6sYFAYA)A6hJ*-8)8Rc7#g{-d*Syh#!Xa3bAM1GtZCO zI#OM$)}6s}0P^pR$DOFX$sQu`jzkngnGvJXkaaC_w->}oJ!tYO)fG*s#jVfvz#6J~ zDRd93+XUg>(VtIyh~3RP?FdZ$_P3|I61DxdYNg8^`|DOLxDU8bw=;fzt9f@Wc3Zdk zwIZ|mPhuUZ^lg`Ctax*_HFNaonJoE~m#T>y5K8YidN{7YCaax@^_QRTygu~}2)Enw z#HzaA3NH&r3M)}f*`H|7RHhHZ7SDWs{;&x)vi!Hx3w#ffF3B4sL9q^L`2OyywU*3` z00_UDOl}k$1to05_#P%Ak^B^cso%|J8Z_mEJ@2wgabpfX7Ue!KP*I)hcZi0SBURAa zD(gO;G{f%F*^lt&o{wo`A>Gd5$4N_-c@gf|-cLjH=cko9XU;`Pd|kP>n!>3E+MjQ& zl8Ptrv%y}3dja)gVpR~3j%#M_#3*a0OlaCtci{}=ThnIZSK*>j_m#s4MZ zpC(rfltQ=cfH@mWqdTR{z8pd+Z^o(bmdm@M_6!XKNR)gvT;F2iDV6qrWCy%IgPwAo zn*IOjr67j};2W6tdB0y04ju8>@a9!zH^zC<$(vK*y6v^b#0xH1>VF(BudEuoQbc6@ zJ-Z>Iv>I$4tOto$oQ6#U)!y)GNolS!X^Lh@m29+60I_o$5?ng*k0S;7PLN9K^*8~e z4KvPmNU+iTl3VX2R%WrF#3kT#V@B>SKfq)Q>q}}g@{w1?r#g9*GUF* z6>qmd!AX9Ff{~11$&pjv1plx-Tv4$imRBrT)<>yWUIO4aEY#;Gi(m!tH%GloGGlo3 zq!@d2^90|A6$X;r2f+OHI~VOvWJ_jA=`51@Nt}N0u!OiOF#e$+gCGgKGu=rYh&o*PefZ6tLah zBgBXH)m=DE{VJiI4%x1sirq9Y;btb>9#kW6z_ry|P~K|s<~<)#^V*umZcM}#17-M; zB=Wb!v+}1amMV1^PbEGtd5C>>$$@htH?fNfIa zCQ4NZz6&WeX#jjBf?4=ikvi_Fb7vj>qVYAM**V+&=(AfQ(F=DH(BH4$dBH$+Kgc6` zxA@^Jm56y_4CZKn`lk(@-bo;gVW)<)rop?|R2I^?f^#|x5BEvAqC7{bBDuH8Tmw~x zT#d6w=UntnGLhHNU6kuIvrfD83)#)Qsi8p&vNoBMusg_*X7%2GBJvnM_2I}q70Ma7 z{f%)8`B(qdMQkN!=I-TxFJl{vm^(Q$95cMU=fjrpi?bQM3p)5bGz`Kwg>|{V7o2X4 z+0V7)Q3Ht2Xt`HW;iqdHHiT6^^Hgjk!}{5J`3I=Fil~U;eTmHx(M~~4CF?aVhvOBVu$weSB8Vs%Pzgu@h=_J)M1Fb z-oVcez`gkyU6|EZhxR636M>w-HZ2eP&Ze+@qS5yVPtT%~N9m?Wj{*?#|^SbY18ELPbxEOXz%UEpfoA}cVC*%UI@ zx&%30QV88hT@eupPhiQm35 z4P?l&SEcu&E*nmNe_gBA)I9mtT2{Xb2Q@i72e0z|bkd|BlpZmQ-yKB4(BHsEo4aOe zM{M+YZ^R+IdYD9ALGZh6%6qi{qj|-rVvHm$mCTm#B9&i zyTJI5?`}qvyEnxB5@MYh58ue;Jcb{DUGvz_kVGoxJaz#f+Ro5qEk@uK(0$2^+`av1zIecbjc}|5`Hx30xWRV|aW9E4qUX=@Ke^q-vt8FD<2kH) zXo`HkSF*${P87opjw^Bjm~j4PR-*mT4(}FUKlX|q%ZL2M=)nq^@AGu&{`WPt5O{Ay zZ5A7C_Vp3)tIM@Hz7XilyUCJxa{(@XiXJx@cay{JSA?!tg?F-nB0TOmj;;XyojF##RJ)y8Ej!(-$f>hI+x)-?U6dwrqQb=yyexSISLrxI&YXLg) z#m`=e0Cj@`7e3jOe>|#*r1$TCi?sev2oPB(jq?jM000OU007DV6iQ%YX=3X1e=PaU z7|XIN^`YF|vlpVw)7Pwsx`8-MjFwe!aW$*AAy&uVj`B=$}2b$2p(5kGa2Jr{BM+ z=rg>|U;t_ja^O$>owx`AVU|zfFmVf?jF@=te{gq%$GjoC$G3G}gi%dN>09o&%0}Zw& z08D^HL9Re-KxjZPB)Mq^8{sSne!$)!AbErf<0kGH!D+}JoPxd_v3SL$`2@NB68j#Z z5?t$`iiz8+cKRmH~11$7O0QHsjCht&z^hq~XA$+p?*dct9`dT4; zGGRVqf%SrKH(<>BtP1?&f_-wu_zCWWG5wDIT<&2K{c7~j%R_wq?VH89bAAJj@sr$B z2l11Q@)JhxB|Y{tPnKc-%>nre5c4OwBWF6DW#N+CqX+q+8r(H`;|BKDkS?Fc0A88N zqsUVESjy0g0{%la8XzV`p8i+kZ0K{Eq?0z80s?+iYsYK zc?rdZDSD2m8_Cmw4dZT3Lz5CkzPB#)l8UmH1p6s>YBJoCgO@S1BeWdOres~})x^D0 z@;0VeL5#G>|Cw3h6C$zq2ELdQmXwvXRIj{u2#uts{#5$z-^o{@LWGASW7UO^jKNsz z!o`Kfg~=L1h%CG3qvfa?33VusRvtO>$v{y_B9#erl=8T?1S=jQAOTy}rKF7*4~qze z5<`mki5VaPQ=_+3`n&WoM#i#>xZ)(C3Bk+$cY&+0)#CczcD`E+W|ii?rS+9X=3Yr( z2{&3q*vmkh2ur?s><*p<=X)1V{1QCaunpU5Fkoo=Tp5ADRe5+YAIJUVHVhoNf5V)gfl>6oLj;Oi{7rHNl6McdC^&ZoiaBm5uP{Fnks%3qQsjD#+6nX2LN5&vzeJ2 zI=J6J!>3mn&+TS=1!(grGDCVt4@@$SFYFmd4^TH|4UWYPyZaLj5g9YInJSrsDKKx& zM&|TJq7sJ_gGY`-l9Cby7%>g$k!1}4S{R$_BL@(?gQ~QYlscuEEE~QsUJAH^x=dky zgJ-qbq9_TmgLJ+&FM`eB?KlBOKmeH+ToaAtbzK9|!e@dYeM z8e_ki6l4el4Z%Glpi~5nak}Jn)2wPE*(}`|HRm?;ogqzT>wOzo?dd-DPSWxp*Zpl_ zFDn6$eW;d{MruuJ^Gk@PdbRZGQybvTv2|6Eb&)+QpzErjMr#M49l+b8E3IgzEm>`Q zGi{DdsTP|c4D;25;?=?GMiK>c8mtnd%tWmA8fJ?dmfv!cCxyqO}@i`_*NOj&Q{TKT&}5qrFyf(SUxWYJN6=o(G@?a z1C!K_K|{;zel#>11>H47cTHwcFbkrjE>hI`D$fpG=ILex&iEWnE!oP>rQu4k%Z*yC)rc}fO~dB_7Z zB7ZgJ69`xNgs~SiuFueMP5Ip=YnC!jvmG73A||j3x&7|~SkU|yq|~P^!?t1{7Ym1k zD)+VuO?_BN$)*8<{Mk6ROzRem<+9AAt6{D1*54^^o$sr3>bAKjQDh zfz_4LL`j`RaiyKro8(c7gJ95o?ZL;@Uo%#3#_h0H5-A%xY5WJa@G`SBbkg`k)Nf^V zSF2cj(J9*=AP*ey5HIc?$pd$V(0uM9N%-3CQiAKCFt(u)$q*_*sDJj^lWOa!U~O<{ z$U*3X1l!h+EZIC$-bVU1JkTP29x&GAf_%*nImR-5|3U7JC0tVGE`v3)L@YqnU_@OI zS&uygMi2$gLmulXHToiZStv%QZuGoO?_)8)G`Wdp@3QJ(|a5A47zR33xSq3QSgi`29$x>(Zg@OSuy;)t1I_A2fR6x>*kWi1q*#PO7yz#XSqbSANsod!vRdzzJQjE%)cca;{a%doS zM6U4oV_CVy3SXl=s5C!8j4dd}Tfh(GB2?ZmtEV0`qUPH9-1MIU@U`waKYH#7`Vjg3 z)pkc&`5?X5J9ei<0`A>y)_}SAuJK~Ld()L3>if5e3Osz<0RdjNEacSU5Yo72j|*Iz z=sYsqryl@SH*v=$8T#Gla0g|o8fwrF=#AMj>4f-g^XTyKFQ+iB@G(uOFjoYWS5xL-dV)TJr?R5-4Td;S;23{LX7rI_+8@7p&xh_AkU6 z+}p@LlT>R`U1Z{Jgz9QAsIzCQpbl+#CG!xnna%AOlvc2(psRL7U`9-rt!1$&tHzi$ z)0ey4L95w&?bsoHP_yQ{eK2B@KR=b!Z+fAUKEqq>lLj8xurr9w3acthRlz>$-$Xlk z)n|+*HtBXLoxOSM1nAS~0i$q16K z76w<%@a$L@_hT1J%WBENkD$>hzn2qEPj(Z}+w>%N|MD97K~fro_-{(8bM;#~EO7xZ6o7eqhoxL4Y{f2J3ChHaO@@ zFPS8*Ru-`AzhJuH>gznHEu)U!tw)%cA=h>hXWVc^tXZLUB-acQ>^M)+;pII+VBevX zP^(aUgsFXQK-`c|;USi7j%dVlfzET`uxHmE_kTVcTX?KD9_sFv5Em7hF`Kp-&gQEG zbuojeY;q{UiuX%?y9qHgj&W#MX#hX;*lEx6dHP;W`DQt~A|scdFTTSBUvNS{bj~9e z7qWOv@K`M&PL5IoRNRt8aJ5;i>LjKDmEpfu+d~#>G4HeV?YgO{ePbwnMgQ5F#@AW? zfoeq(=Y%ySM;RKHj`7A;ojc~d8Vt{n&Ra(y?AQ{GHkcp$rDPYu!}bN0AJG+^!FzIv zrz8!)3ow$Q zB-6@O*6zBIcintmWf*If+^9`*gK_ z&-efUd}06qr2kXj;9zNI_x~FQEl3}gO&G{NaMtG4rar7E6b>5x{YbIBDLz4W9ukdEr>;7AT0_ z_ubc>DHA4vjazk_+0M!K(;V-acJuZm`mCR|MG=?umSt({Ve0My+83|3XR1Hyz+GSl z_K@Lx&ZKiJpXLAVf~%+UMlEup65mi8bmvX`zzjn zJ7G712w(KNUAw~Aj9rL>kHg>&TzEfusdTKXxIpcr^B?hae9^atf5;sY$>%>H(D3gW zeiWO?=D5J(0pBQI-qXS)@d48WUO7^DLGK)Od9G1^Y54+V`63eJ^Peqi-=g2D=yL9v z`pp@?IBi}%b$tML>5V?5F@Bgj^aU>~Yf!aq*J%;;f#10L*a3PV?`Z$n1N(qi_CU+^ zoFeXq@vTJS8ytjjeE;Ts65HGxH~9rd)vp4MH=TASsl}KKL$QPu_PxOj&$6HkDi_n?%us-kB2X@~hfIs*EKfq6U z6nd9St4C*8e-s91z=~v(oZ+!FKCWSXp1z1H>g|#N;O=SE?C{xi8;TQy3qo7Q)S)L zKN&GVRnvS5P&KjvYM<2~x*muJEVWi0;5syd@Ca9cRUv%slWs^iSPcH1gK7hFNKHLygX3*sVnE`Hj$eU<=#ViPZ5_@lxM*=!3?TT}VCD0g?0Jt4 zb~Y^3qlg$LEv^pk>8KS*D$Gcj%5&>NZ;*Cy<4*5|eG>VOwsq9FyU2iqHH+U9hYa#GOtioCq5U5u+gQ zl~JX03!V%Vlr$-l$ibowGGjX6Y$d++zPr#n4f{6#V8|7Q{+u%j7Xn=AA!nA;+~gA! z6HzoD+X%AK@l_KU(LU}vVlIZQq#fWA9NPZ&xnfSX9sa%#X~+ewKbpLKiJ0=@-bSrr zLHDxt+w+JynTtp;cA4C!gZU;NHsUq}%Po}s9U3D&x~afdUmWr0OauKC;COGB z5eXnk*_%ws_d?1lWj0aG1o09~>b~_7UgB~MdVy)a-oZ4&gc6IY2&vlt;_Qbb~PObJY01_lH!`z z^nc`GNW@T8%_q4=7Dx|Mjd=!ON(u=Vh1)!o<`tBsjPvjE7W7%gz>t9t%N2}xY-cn* z>OH73WVA_K;uTnyBKOa7<}{cxq}Hh`7`Rd}u<;FZRE?u=QNY{>V%DRXSGkcO7gg`y zrAG$pvz|Fd*T(Mww>TfEWFI&O$}MsqC~1t;gpACa<^rY|!lK#$tp)d|YYeSYfUl<> z{Z-3}TGl4Sb93~Ea48R%BeCh?dAoKi?oFUJ1*WxS(4eMNQ-f?!%E!?%CtBB3dWK6% zv}~*rhYK?8<0&@@IJKENsh;ZmcqYO?mXGNheb;NYOi!(KlI21VDJ{C$F?TR!O9qHV zv+eT{t}L2iM7p?>{idv!7MPyV`V(X^j|iqm8nW00X_$sWj9LgUf_D*$2du-P6z#&L znkoP)L}LiUhHzT;gm%0>ykOgfY0SudS`1b?|Fx&}*J17njKVuKVX&M;ZrDzLTN51} z8wrt(3JFI6>=Gn}OH4N651q)p%Y|OJN5R2SDH%uOJCE#ytI!37ZxE?D1?X9hs)@=b zH(}nPJUa<*0fyBB*Yw`0gnP7lM&dt@vO|5UyP{~Hwg%+j?PGVC@D3HGF4&K@A6!Fy zc%wvm{?)YyJYgCH+dw)F_pAQ$+hd%4pUm{0wRn4=S^E(*`viN`C6^i3ZQf9y%&ysZ z`%%K(0M*yRPW)!L69s%y+>QH>8S?`ed) z#JkpGujAo_@DsmGN1vRD6Yv-hK@ahH(6jiAgps4L?}Qd#g9F+kE-xh|hfGI?@4fuZT{z`sQskv-N=5?p$ znBk;096X~Wxe2pa2VzZp;EXq2}V|nOUP_ z7N|`zG;wYWhq^_q7==?*>St1i!Z1>}VQ2|hvo?9sVA%3`XaOYJZU_m(SVsZSu{KRM zO~TqT3Usy%&D8*GO0QR2;Z||m{)Y%i-CPNCZDkvhUwxEW0(4%28{V_Z5~&0xEc7;*V~%L(u7)DWR_;M$k2pc zz~I2LD%jdIXLN0`28Q{~pbX0UY8O$y{{9wC zDQezMFs1GY*l%FUzgWtzSzHaabTUKiBf^l6Ww@x-4ugz#G;0MOlm~25J1Zd{*|{hPhV#FE$k%f>Ef< zYs{dZatzqRsCYM>2uVXUDG_C~Ol~IBr7#D4QkFgAOi!pTM3_tOf~3qG zNO*?C4rJ0@eTDC3KT$DP_UzP7BwP?`nRNbT3V^5!&JcH0;tDAL`JRfF;&5GAG$Hh476X>fD=>%lsto#mG{#*PO%ILhZ~o_k<WkXc(fKHr&y*Z%F`sUNDECmubq^|R^#M=mvr)$O?3`b;c-I7h0+vqSVOgNrC=FD zGeYmuaTF0KHONW(R1cFlq9yPDtQALeu*^%)#GvOR=rITp%;iFm6HB>Q#I6v(+)SIh zHlhVh(ui}7k>N1@;{gB8=}^r3Gn)tGd;F!&Lt?e(cY1@axrue4GFg?<$+1^n)(sF> zHQ}MHT<*1T&AwGY1>5~x7L-xXVD;yjc6DYM|4XEH-aEX(PYvo|;LcHULiA^P!+ldcQ#12Q z_TjH(+H)7KXVgyiczf%D>5$)xLWhcc8P&(ISmW!OPr<-ssMlKEQO9&wP4>PD%k4Qt zNX4=(9TG98N?eFh`$wk+4zA=?WRB@OhWhQPc8yVmseJpGU5DJP1^<9Heu1`b!dXkV zGo2Ey`Q(dgU1rBP%Nk+uRht%jA_hd9h^7eldT*I~#T*7sqLMDp>&=E~4^Vs9+ts)W zQGtwGrxYTTtlqipKFfh}O(FwvtgA^WDQ9VN#1b0T-J6EUwmUwVLqULJZ1nxrVG48( zWB<9tX78Ppp&nz{IoO2L3rkw`fp1ddcoQiwJcw~Kx&C;*q&m_%qP9BbToASgy!CVe zk>;M}x)H2UvMkN(8Uk_f6c0l>)N|uSsAjt5jLQ90=oDc%Igi^T-RH7{SU>KVXU7B0 zaDs>tMw}-d35&)yk89(-N55MSjoe>$sOMYA;pA3D&bagHNX!!|=4q$Oy9Ldy^70{F zPN<%|ew(A2!2sFNyU?=uyPY(q+&ayz4$o=NJQoU<9K0?flTy1FHGW2pGN&exj;M|5 zo(Dfq`3iIs__PCpIOP(h@UHcwfgf(?)yWgPg~AG`LN%=;thyb*Qn1phMV4HsR8+`1 zPztJVm<_{zuJDewtNk~Vm-LL%HS>v{`K-n0QQ~r)i$_^psu;Q(;2m=i$BBWBNaE#+nM);=NUCS z3a1mQt2OIezTaYSY^gGtBU9z_-`0uy5;E_58$3a{s=chiADqAf0bV-=M8CfJ>*2|6 z8O8PKg8kt9Vo_D(!A#kkm^iDmC^46p%OG%~NJTYa8`jyv=?k0k*% zH?>MTl(jH-0P9y|rev*^jXgaG%n{gLDV*HI20)+@G8F^~^8??MLmPoZRaFej4`qOj zE>tp|Yq>BZp?}Hm15J|~(2cp_5&1K#+5EY2W9qFP9KG6+d*fNqBf7HO2bc+n%Ar=G zP$GYs4+rCq1#wE`tEpb%1C_vgKddiUi-U{%B!%KMf2#OD>7Orva&KYt_Q?<^&OVzMHjv973u$YJQ${C>!h=7^P2N5b1td!4BFvv=Hp41 z>o@&rf!g9Cib$hEpZOUugA(+luaa}jL5xvgu12-{Du-W&jJr!jAbo?zG(^}}C;Qrl z+&Nut9r#ewI#gY0)mpk6jboQwu0>^jYYenphU6bi-?Ao-xogF11g?g~L>iR zt8k8NrAjOu<9T`lS};yDXNZ{Q%m!3UfKWz3Efh9HGtZ+d_9Y6@c0ZUcnioj|`87T{ zHKA;N!(s9xnbamRRexfx{b<@HxDNOLLZ#&yM2VwIL&u0dhQL{#BK{Is==ILvd4W$- z+R_C$4aQRI!gGA>0Kfq(UdAJgdQ$2XF=?Yh>$uv%tkS(?{bIcVTiv~+e!+}aJi%oT z`yQI=^_m zF!N6-b!qfqDsOJlvZ7~Ok26!P-niFF=WE92 zv4ToOT znwM-siM8)KR*HT59$;I^roUkTbn@$Cg}7728b29?w}+y z)(Jhx77E;e6zm@^*dM@!yiA$V){$}W6lA6%b0eD%U8~amU{OwC6-mCDTzIu9^-YSY zsU$Flea-IejsH}mXq(~nI997m*fy%Obm*R>`mxjVzS*y}We-5tBDR`+v@W-$ZvzfC z)l(>!*_|hs+MTU*ww>6WLN7!wlQJI`7f+feXZ+w^1`XZ}kSqe61E9nsc5$TI54Xq# zVXj_AoozR}@ty4;lIO3{U_@mjnb=TlD4+UbGG#{d=eh8r&a#-Oy1 zTCYZ?35@ImW4cLEX0YDTZmLEnfgMY!bqIS50h+yv%M_5$;};PtEl*-*k)F8l7-iaN z$|wC028UndlHeTS8`RfRw_m^$G|7TL)uXV*Bh{oHt=<7~{VTwjWh_mYI)mqxw=YU# zKo+^FkbX2hYKXGJST~${NXgo>xH%#zbp)@q%pE1&tbb#1a>&Wri`Ks8(S5%PW_#=A zjuS)HI}Hn%58Mgl#4Mt51=6ppS#AWA4<&8bGsw+?N#1$Cx@HRRTuFwrS5)k#ZoOidF82CqkmUSdM`!y z@1|bp_dq_+3TAuGX13@!Ih^+RTm{X$HjN>u^GB7Iq3BIz1`|umQorB&sg=l+TiMz5 z+UddB_bHe7`Zs(+K)*H^T%tH@=*@J!lC+If%#n$*6NGF?<;4S?nL<_0%ggd_#C2y$ zx9#%O|KeA^)UO*GgXlW)# z+S^-)W}K@z zQ%2Z%$V) zRdLC@l|%aUJoVM{pG4xPj8FVLO^d6_#n;{_pOdrfttO3ApR5}* zuG&MKKIKWx^~-k~djSFeL-1uv2}vkX{ePB8)k^a-yjWjOj4p`0ANahX-!n$5P1WwEp4Oz!Wx z9Q@F_0wmsCPk)5p+S9zG!!~dI)DR)YY;};FEZ0;N^DXh@VY^v{U!Qp;?Puq>nOyuk zOA8vdH;*1aczCRDT6<@2UoLM88_2{CZSgtAS`OSB<=|b~7(%yq(&ZiecTJ#PpYK=4 zmltteI-p#1^(mOUDc>bq73~=)B(i=^fK)spJ>1MlMiS zwS|yr8kqDRi0N?1&+>t@pz`h=asb>L)Uv}h!ONP|PQ^m;_6^D&*LQr~xi4E+%C&X` zmg#awPqXvHxuw2HXERiHlAbg|cbwzx!Z=xZL=GiHVSDqJxo&UJevnlEBm*s`jCs>S znt;P?0RJfYpD+3m3*XYG?d+-x^x^F3qScK(kSGt&zoIO2%Pk6-ga;%wg_X9fv57!e zU7nvF<9mR4BKpV8fs>yppml4KO^i3AiuIe8O?oi_QDqVrl;j?veC*OZgTu~sAkhq5OS#$jL}v~TM=d0^ zrs$g0*s@{Pqc6`&49h2kUz=ab`~*2m!hICyp;=F$n1N5Cmm@3PJ1iE?n{y!ZnN*U; z2l{|o1Np(iT`~Klxu6`x?5Qvb%U1#>O%Cs;21}RC6OOTTn8x!`C6twfJ9W~RI>1^X z8ONLM+NfTi>dNwt6XQwOkcYu>qfi^{*0MXH2_+;$TNoyYhsbHe*)%JQgy+rwWoWPV z1xK3Ug*JVPnAba3ZdZ}ny^XHU@TR2P12in1B}xzjkzKj=-k^K6rP8$aE@fCh_+5)e z?KZG}%ttNwdiPM_(MZAYrmeWjCAd3gJ5;Pqi7yFb zf={ci!2)DnstAQup{f>*6(R9*?u3Q&9<>;5MxCUZVsu*h&X+j$P8_c?8@qH`ta z9N6Q!)_^wzYi(_#a8-u@tN}9* zt`#@`+GfL-*Q8qW53>82B*i*oe8o;%Q|W>Zc&gKQ$M}1uS*yg#?d6nJKsCzcCgT<}4Mu(+aELMU;EsC>6xCsqXfhPKVlGo@H2&if zLmT>Oi$82D^8FMNq%gJR7vS=>z@?z4^P^ezA7`)nH)fzk^y!Ivuqj zpiU2y@f|zfcux~_TZa*Sib`fc%PPClH~a%$Z7h2{uQ+iTCMGsRenztNcW(33fO|V0Yyj+20w+Mz7=U6nJYKJE`3# z<&c}i=~+WTPK){R%(+HE=$TRB`HGyy2$e8(!#nF_SRq!W-W#jISJlNQ+?##`Hqn+o z!H2igZ`l&oTVYq@AHRH?Q&(A!xe%{<+1s zTBx$WNZrM~@$752v;zBln~&G)Or2`Q_g%AJ_=L6GzIe4&&h9FE3Pu`NQ(rw^U2Xu>h++&zuVmozsOcz?L$v^}#3JCxIqb z_AJVFF^j}B~yrm{v5bl*YC!729t zz>N_<2jyYVIDdBf3=n*Eez)XjI9mY*2A|%77FO-M70~=O!oOJZZgP`eV1-u%_oKOT zX#IGn%CRt)KGyPnapW5RE8lFzr}wk~l5O{V9-B#a2E$GcQf&@YU6I&5w37C5&D6Z4 zeM)->pwv&O8Xox`7jK}C$+`7w1 z`1U3q9BTdnx$grIxDWUtPM4%9S8Q5(&ZWB*dy4Q1>C`1ctx%OkfSI{ zFnn_j7tpouwvT><=5FFVZbeEZR*hB@2XWbI|H1!Xx`Dxex`E2T`G9+H0Dzi*;n`IG zlWxG%&e+1q-pMx?Y+phSzbH5Abq z<5|46y9X{BqB_wLu2W?<*P3Ll`4^-zS2l{8V3R1tAS{*BA(^=re+!u3^ZA>Yzl)h| z!HQ=&U+I91hzd`6@ozn6KY4CPw|lpLA8i4=8Hm!cpFW8{5DbNK)ON9qrYcPzW@}Yj0z4%v)vw$Z?NL`utSMJqxf=U&cm z1c{_w=y9q?t7%TvI5f#V_F~)A%kAh3 zCD08Y{%3u>%c@>4aaVpUyX!z zs*rv`O8qz|9*lK%5bKoceI4qCs~eWHcv)^#0iK(rmOBgfAupdGCkkvxGP`1yG%QZI zW8l@O)8WqTJnpT?ljY;WLT5joZWC*Wzanfu(Tz5`AJmO*9w)B5tz?iQTdnjp>-%7F z$+#-b^NAdK>An0vq$3Ain6`vmEw}O@Bp1@$vIMy|hn&V&fEH3a%ZH2_{*wH^(1N~n zeN?RyAvOET6gam{A4P1lLXDp+ zq(Ez58=&@2(i7o|q0C-t#o6&HZRFRBet2*w+Y#))Y!4-jwx)Nrm9~ZEKv*}0?gEXH zhb4>C!rXOlaT2Eu1|I8;;S;4%Lgud4&vH@k4vY(o@+3ugTj^0DX#dxEcI<%gIlfW^`vuMcHheU*&x{0rgAi{&U? zRmOngbiE0XEYs80Kzw`9nO3)`z`s>i!BGN}Zbf%WN!cv;t)aMG9LM8S`0j|S&)W%6 z%w$NYBZzb%>4KNI$GXH4!N<9U-1{sXDemyB1j!%s$8y;7!}%V{MN9HrvW8XE_Mj;j zy~JxY40EGqsfb72%4odn4BDPllr&ZE` zOjTTZ;w5vVqjPg>5CGCQF_KFr&`0OTIfsd?G!S9M0Rgmz7&9b|7cUkM7S^=XTwh;b zUe-Wu3U7^6wgMzzLKU{Y&|6Ze`dDtS*}B>Xl9_3{=`v!(^8UH`h|#_Fp5cC>?)i5; zAj;Emf&h?dw4l@q_MP7k_gVqub1=Qr_6q*z$5H++4~p_LBEpC27O(neVo0=e*ASFP zJ`bJeXc`fhXIzu#A3yLTqI4l5Y8*5c4Sz?329k@h4M%HHD}I;^s&=Wv#MDJ91+_x0 zS`egKe}#nhqJzU;F>-CB<`Go1LLQjtgE%hfsgwN5SdbcvNMD`ShZ1%Os#I}&hw|p% z=!iQlrGE^55eI3rkWey*)5Nr-{}_aaFf+%g-`cQt4Sv-sJ22Nv4h&fle`#e3KMv5* z`a?F;`-Y^pec~mm655=gTF%|>s6l&j`_`85P3-^(<#vL?(u-WH!!7M-TvqNb-ME~)!*vG{Tvi^0R>QC|(dFsO z`$;NCB;{Q8MUKmt!mKxA?wX-nR|f~|dFrz`Wa#~fT{v5nXR7GzFN|mvJVnAs=CzDk zPYPVAg?@QZp$skLyEG}z`7um*c+NslGNM|}Am1YU>DhrmnIc>6 z30Z2{qF7Li+{WVnf!IDSdbG=4JWQVvH$l;jZZ>heaG<_dZkt;YP^H$KGY;6zz+Er{ zfpt-mHB}h19*=BJ<<)FPn9jg0e$NB^skcOSxJ0mE}6r&w`DPxSlL!qd_c{6^LwzIl<=5vos64d3CsW zm33wHvMG6bH6}#uAVNK0(x7*)aW8IZXPz2OrlT-u$6j^vM7C6kI&CSV1}5_Ld}y+z zv>QsTE@8rG7-=o|>S`|`Ya32-51vx;H0cQxxr|~?(vFWIJ6{eUZsICL#J@}vZS&XT z?$fQGm5$Jl36+!&$PqF`yCd0nU{RKA814K_jB;7uOsd?x*xD|OC2JlU?ghU4ptoR1 z!hXRZzm{vL8kaTH@(k!A*3w^~Ho+p=TBE;@sqEH-J84-J0$IPQa%q=zwWtr)j!1uz zkC3K9)#BCoGd%dufhtQUNIj-EY)|zbn_Wln&Y&$pB;MWM2~Flt*3P`WqRu@|J7@+@ z>XMDWkEKOY`w%@9d&aNaust!nv%4CtG6NH5R;|d+mal@Jalxv)F)W=(KKXmfu1&!! zyKt~I!Wu5z-%0~mw-%tbi*{t&g?qF*c1)Z3eK@ni7U=4bY0?k-cSb^DS;qe&f?Ia0 zFt>-5+HWnO@<)O-y8Ev%F9SMQJ~6iQ_Od$fTKB4LU4w!v@gYjywzonX^ee{9d;eY-HGD7*~u)MOtX7~44 zzQP~hsMBVfQnllHm+$hje8zIp^+sTR|Nhnwd{91Xg7Nl7Fuet?f#@3CC6LA3{c%;fyE_w8_;)E3vhX*z3KPR z>a6KQzAY4)iDzTZz}n_ZrT6F0%8rC~4$)4SxEGOPrI8XP$8ojoSC9Liim{(n06lid z7RuE`lB|ImF`V#E;S2Xt#zu>JU@w4ZA}gqiV&F!V&}=J)h)e3E zMr9Bg5+K-|9d>P9f~em=z4u-uX9Gr{iW~wv^wX=|7PY_Z%usvhr^+b=~CwnFuX9L}LKQ z=<(wC4<&vHAKc>9#c>TZE0eC6x}yE|Fu80V^UyZ#Hl;u*{vh=s`DN zIP3`_P=6T9$TlYyA5z;$m(Z>LdsY*#tY)ec z?GTj(?9dpffIauzxP?>3R}_Udolvs1`5mQQkYd?wvsk@h#^~@pbTYI$Ggt^ZV>avL zaPXnZLBCPFWdICrKg%fMBfD&DB%?I}711$gWyVZJxNDNDBG$%8OYnT13S&yFpK6(- z8SvUfu^cDaoO_~t3^v)QxTjriqCr?=cD<*@o{%m*yFUHY^dLYv$=A~Q@VF!NHce9p zXpBltr&B13^Em?iRNgq)fi!yFug6GKMHze}p5{Q;kSAyRbe_cHsS(cyB+NEwokRi3JEL|cV{b+?69NrV z(MDt#5=6_O1i)5xuEfMplF7X}YP#`r{V2=5@Li5HwafuSQ_?jg$dRXi~woZ}5B&4?plbFkr%5h#o4CnaJ?wL!Nl>ckSq(eN&N$;yTl4 z2@R^sp5hEWF|gl`s=JT|$_EH1usXt_D`~s3kPuFS?5m?^kpr%&ibR`p{Ipa&Bd}8M zVnzdpJ%f3wg=SS){drQ1DB>j)i&$X>19f)1MbLww&5P|Fi zG~)xr0i58}2J^4}Cd|Sj=JsCa2rGD8NZ9d32h`^{WrxS;VPq(>!38L{@mE|@_4$`Z zwRYmL$RB>ZdD@i19fEIUL45_Ue2o77(7ds!>1Qc0R!w@HcKJj_wrI%ofYp9D0D1*@ zUFSo**t7q_#{uwtLUSF+o;vkPegKqwa_hlsv8+6XG{}7F=vSlwf;0fK?Tzik(?Fcx zF96HzwbtUgBU~bmkY*cpw7h|E#wQ}a)MINvQ+k+VLBSJ*TazR>^$?X?_*2^Q<_`Bj ze!S(}dpTUV;#|i5+yJaOr7#?`QeIVKqk``uqSMIVIKQTem-Z1ehsn@FVS|GR`A1Y#TlBxZS|6#hAK{&sIJ<8&^e7$nLG=`&8@;0fcyhi zD?hH-f$Wf0fS)f-ILAsO@ewvwW{b3mP0g;@caOblplVYeo8lNY&vsA4A_l6-!D=Lf zHpIl&WR0l2GWe1Qula+{d1?f9?mBV>snq`bhD+RQ}xwYR~16iJo_yy-6a`=M6GUX_w-TqC&XD{QU1V$Stem^e^Vp#*kAzeYW>f!(jlu zzBC*5POhslp7tI{PCXv!GG|-5(oOj-dqD#gclBu|Wz52XR!?^rd$v@t5N3A2W$|K# zXUOI<#gk3Up}oiA>AQOwba?5(W9zcw3I1XwfuC!kO=|jy8_eo^E{+z|CI5Q@%4$R* z*dOVufDY+p(^LtZgB!4)crS!iepUlsfQ8ml*uydo^(b7>XT=Wyqp7PDKk8=FPvV`k zqG?IEUdQE`F!fY^ha5Kmnfz_>YN{HNhJ>-pRXY#0DMm|M z9oGI+9PZno&a&}Ggqq4C5tryX`{S)iAsI&MrD1)|+SUCaEQ?yG1M@K-3+a^7O*vjAuDk`M}J0%MzN(x61+R=r`LW9$hgXa$B5xL42xfT}?&@W7624O2;%Ld?d2MZZ#_DR< zaYb3PT*L*3HB?{>ZV1zaEgT(rtE$vO5+aKN_(xQv9MzLlUO=re0zrZ0*m|R!K*o<$ zMLR}kimjcWifRH8*M0%T{MZD-Oov>8q47cg(kUyPF?N+XnC!6F+k2a~g0;QylvJ*a zSW;5!Nir)^f)BGmIxP@xiVKCBwzgH15>!x|mE6VPPBes!W2Dgk9CE~PF+o>gVe^R6 z?V)0F;U^5K zTjAt_i$4MNXBm+?uK%OLU>0S-MApL?=>a}zSn-{&Kfnn=HCo-JfhJISrPIG@@KZ&< zton(c*#j)$6;IEhdk1Xi4Q-p*@aufsK2Ef-$JxgZAw89q`(6k(zl$DYbt{B?8H)WU z9cVG%-r^10<9#m;4&;aj6t_2M6?`ZcLCOJM7;qL`-|kN&#cl3yg-Haf@dY_r?hGO# z-x4F>H4aFma_2#3hHm={VW6#t|wTfa}uQwE!>$Y^c&3s%~XCXpRmM$O4 zDmNQzQ(qkEo6#eWIBLvz$45(#X>5OQJDJcjJ7)EHf>H3Xv!#$}h;3Z6t62&tEe(NNwOhNS1FH=k~mL zw{P`vedp+mjQX$K89r_jJ2*s|P!avIteRf%ilc!F!ZQUbH`SyDpG9_q2nKVnrA@kF zikyYPhtZUdv(LO_%7d74c@@^a`I3TbJ7Up;*d?T{_{Qvu{EAikRNi$`OIfNyZH_hN z=-2diC*vPmEX<~ot`VT$zB~j33_leRL?(|ZUE*6N5QG*F+1_?LEzM4<10BzXfG98D=A*@ZeR8oBxgJFodX?b@7NA%eEG3vFkjDg!A8kF7`n5*@aN z0tc3ngZt8s(auziKSd2yJS7;oT7$D0TwTaACT}fIe3f~lL?7YL1HUc+YO-NO+9Q2g z8nmooi0j;2C&U|($S^|`w$?X(flUqdJJ|<==T}fTi=GJamSXhLHm9Q*hX!)*5Sdvb z5@fZad`n@uPk4mf3f-d1lr!hRKfOo6r`i4`(or;QEUXDz=FwTcUVo$z86v88xFqi- z1jM{+l;VQu<{kvsySApYZD*)PV4V@2quD}vAAZC?)m^8FyVE}-5sUmFpZmj}IS7>r zyi~E`ywt7f5*JMfgwGUc*1Rl1$^W^;V*H}0jiAQ!s zTAS+X1#9Gh)=Zr~_Wz^Eq5RvD950iQ!Y#g?6*V>GfRw{RGK+m~nvq1fyb(|Kv)Vtc zJfm%APImS8&ZjuowzoN=6Q%p)rf9pO7j|i-pdrfX5^D@?jw2`asuaq7Inb)x9EFNF z&Bh*>e|0*6Ponp$R=fuV=@@MDkAKwPuCAz8>W<+R`+YI7>j+@K#32k(-HJZUQk}|q zM>yV3M8%WeIYxv|n-v^T_oV98d{)3ds&xg{URq)YbnAS>QlbZWCjQu*_}MDN1=-Fe z%#>jJ+CE@^wRR^0AGu&=e&1cz?py&m`EQA;_Zfq_QdvgB9*HHcy$Y^JZ( z>V&XO5(N}0tD8A_vVHopYIjfS(76?A)SuQh;f7_e*;EfJs&r`+n)f91h$3b{ojplE zqTAe~by~-38?}!au@2xdt`!*tFNBu5XrRt1l@TMWQ&!%50TQ#2s@ueRlAX9oh7pJC zVN_n@VVE$H+;|U1^u1HcaU@J0K<&AIS!o`TIn^1j-yBEQO*32X^iqdiw)=a&j({d> z4d0z;yUuKW7N;HUsM>e!fnytFlv6q@)Qm$laEw^SdNGP>tVK35imnqRe1+%XQntPL zi4x9B2EvLlrTt<=3|TdsfJt8p?m1jqtaOSZs(}f-0pyr~?}nu)`}Nx_fJ`lS$MU_S zra(06t@QwzS_TIn$HNb1p;h^100KZEqk3Pg$^ZUHL1D+||D&uS@&cozV0jZqo|rwV z*+08#?yl}``S57h5(3l^sL`j!>&^KfD19B4s~eC=uRaH2g3_hAx~`cqaOnR5{J-_z z|G>D-BRHPxFaQ8QzkXByO*j4vYE&p7N>KV#{f8gj#P`%SIB^IAsd>hul*POmyfl_!dI3vWC3Ca+LUD1^i&x=` z*N$|?B5{`6&kyJ!g40P4ukH4xtx5NZj?Xg(Kr7OX^MJJ7JSced=tT5V~@^5jLst zC#jE;HfAAo9j8xJFx}&=H59i_-fv(5w(X%)E%Qv`;wG=wpxp<-MYr0dpSS@xOz+?@ z-lG~^7ldncbwIoZ%e*F=7~WrD5jsBS!8v_17@z)Z#IAmApYeS)uTLw#3O^X{3CEs) zh_Bo=-@TvTqgXx$n%|QZJ-5TUZ%?+9-#1eC@X61z!(S17J*cj0u$rrzCwsh7x4NXR z!hWh-vmhsodp@6BIgmAX4^GW5uzy4OC<*2Pr4;G2a^S#Z*i3TwJqO+gN~Pwpv>g$) zpr-8yqU@pppup5&ju}TyigAl)^y%rg2)x##+dR2qTprR~01u8k>;cgzTjlx)13BXK zQZUz$B7=!2f!-ktp1?hnunwZ5JjNo2p7?1hhY=HFaas1=LGU02Mb{hEGTLLNCpX&J*wHJ) zKnl27szkJYW5-5VG?bW@2`b5RKqYmDWI#j%a7l^AO`Z6%N1>j|^SYP)s^y4N#qbML zB&{kCVV+n(4TYw45c66wnHQbM0tXW(8f**-p3NWwx{^>;kPpN4Glqq0b?`!i!_MyS z!^NSV1_SDJXc9}!68!+L8oCGg$;YtG(VjDJp)n}jMrvBfh>ouZHa`@8+fRkN5 z&m1sK#syoYNmxp4fk2p0Z?}_QC@u_m`++-e!PwHEwF1bi5$W0af#xJ6gO`IilKD4w zI9--`wB=!6u6c_91k?Q6kz$y9tx%ujWWWKEV6U|rr5?JCO0Co`p%(QnZL8)kl2&-E zM;>Fz`}+TL(YY-K4|wv@fei>rkP`*v2F!6I@k>pCmOZJ5(K-TTCz~R~Ar%b{NK* zhTbKq_TWAZunRke{{4s@bLXWdpkTcmU(MCYk3DW6IKsGb{-Qp zkatd{1%q;RSOnHj)<7P+JbqnPKlZLlz4#&g0~d%ArinXJK~mpUN`|YpAYkY)`?0-Vm(TjolT&t5yEHI1LmMDmfU;1VF)a0Z`i%x3q9C#M&cEayfa=L@2VN`Zlr?_{!t+f3eUPXVexa6PdBL^COm>EOY|( zKru_pG3p225bB!-r}c!OWlueEl=_!kGbge)E?tZFCrmC&qzs#M1<{a6DoG6Dc5kHu z1PjFL-N9;@*R_N)|BmUEf6(Sxcrl#J`szO!F8E_?nrrrgO~x(w(v(`QPg-nVZ|Iw$ z)K>2L=CuU-BR0w7<%gf=oJYsl6Rum!8L!h^yh+F)Lh1}R)Wq%|Z^m@uXg9rA#c z-on}fvhEd?!PX4Q%F<%BxjElys7a*GCCRE(LD#CtxWyqlg*i-=w}a_)z^&cqn>WEv zL-2tkur=hRC*!8*y#`{)z#^fp!M(J~0%`1|`Zra4XjlP1q7eg2#atzaSwduqz_LG6 zPt%uHUGI{LE&xe>`?!r8u*>q7w&Z7~b(LLKr_h6`bl~9V0vm*6?XIXAI$vb~Wl3uI zm$J1dX`22mI6x$pJ>`S*;w#JhT7q`U=EzaD*u}GauO@p*NydLa7 zr|8u~FFbHBFV8MKY8q@|#h6<7o6Hx5FhNg@$z3tWj1l_pg?i?R0Bz$R`2ubcI%{mx*>o>RG{AZ6?wsALM-{NM$3OTpn(CgIvzi=0 z=G)`Mg=Bqqja);E;MC_x`A0twqZqQ<@q|vjICisy+Cb)wK&cnf{tpnXow)nuTr?Ql z;`BK55x9?I1~{eoN|+P3*@VTy;-im$;~it*ei-SUR<6{?i_)Q47x8wh6nNg&X7?bMbemPh->_EM!89|?p8TmIqoTl(X zfs)ZkUrWn#gJAr=@vxGq-uLxFtsWHvZ6$V4P*O>*8I9k*Qy|5dc)+h|kvK0P)SiST z4$>KV40VN5y+VoJT<1`riRFBJE%6Q_EnE#Zu%yeMlta$@tP=gTu|zM>T~C~HR97qm z;B4@iK;cSGuNXCrW6kE)LYfl2H}Q#0m;R1o0`Ug%ZP*uS!TNq@a4(Ib%;UnIOXhvK zc5=w04lP-Ar4R)rP_(3t;__N^TrpvDcw64-oYDnd? z0=cben-;bcjf{U+Q;?8*MI!-Ue#n5SKiEVA^Z@4F%+QXp@v1Byal17$h70xKj^|{64)>;I~8}Fmcrd}3VneC!^ zo$u7tQ!d3gL7<%pH&XrW#9Epu@~QG)y`ABm2TyC~}L`LMdfS^2P-+~ThOl%<#(fte)vkK$f)!uE4RT2quGZ9IhwO*g} zo}+|tnu8hHe!(y?Aqr)8`uAGMYcm|vPu7MBMoLM6((#CNGQFN*mBch`l9gIfXH@uK zTqsnxd#VIPi zr2d(WO)?z7(vCJ8oyWvhbV?AD9oc+H8t05_c|mDJv*$E+XbM^z!zd2?>u#eYL24NX zx1~i<}nW9s!kt2UN9G>Cq9zm3N)` z^n>cPC$$Y9ki>kVS%%=;_A4$xIPeoMf)$u<^Pr)E+7(*D$kAp5D(gWBXn9K_3k-&0 zbyjkY>?cS5f3pW}s9b@+I7Z_ml-iVQS7#C3{8J$W)nzZw^==$#Wj@5Ctzq9%0Rl;; zw_0<%)fr+-8|FzUwb+b1Y$p^~QLktjeeZ}uKU5{q0c!v=5LG4g{r@xL9sF&Mw5&@k z^<)45j4A;Di2ggr#me}9TcfHjNWH|NH(s8HY>&trN!n`PW*9;ND?hcvex z7ncOs!ml6s)Dms8#pb@r=D*Y+gLt?fPsz>=pD@f>TD`3O1 zUc=`=a-Qy&L?y2kc1THFA?4)cTcxy;JL|7v8K)K1w?fc@D^3+}Ico&$fE8yYP>*rd ztXExy{;9#-1~+!`X_3(m+vZHX5$Bj&y`YppetFxVTc$1MY>;acY0s^5DNqMKW<%U1 z_8xo5?*JU{O-w-yEcQ&;hB(GU{J}5|cxclfEhU5`vUMT<0!C~rIAUw#mA_e7q0ft z5@_jLSdPQ*fTo|z0t>m)f^9 z@Lk8aevm!7VvOYgpX)C69~1wNlOSZBH#z25|*i zAUHRg4Y2>oaLGZxmg3d`$J*{QcTd zEJM%Cn#4>HRToVPMzA|uA=)D5Y-p|1z(gwi<}6YO%Las=0IwLY?tPbYr$#g{yBPFVo01DfX+SwUZEt(h#E@mo--I+DpniH0>wy5O9qMw{|% zu-aRQ)}jjs7XJc!_6XU|&%9W2#$NPGamrtcAbP3*=PbDcO#V)QYkQaiTo~1jfUGm2 zdDK~(+4s1@SeV=&9sCm!z6SU)Zvan#G3oLc$hL|uT(WXx+Z|tu?a(CO3yaM>@ zv6EO&Tqj*V2P8=Lnr9MXz1DTrSl>MhU6SPTF1~mn4?rhfL7?gizYB{Up#)okH&&T@ zmsrm~zjMSBPukmen3uT;&mL`aYbEWnt-h@4Nf+^|h0QKlfFcpNU0Bk1_a@^wSeO^~JTUGmPWQlLOl6_4(C> zEUonOqg}CKE{dsQbM^VfO?tvFjek2qVp{wPE487fqk)PeHhp2eyNhg1YU=o($+%nT zKqIuH8YSj5xS7YtM?x1cx?jWCK{_cw>=8t!G|q|8_x0A5_L%Y(MSpLiyjQyic>Gt_ z75BgNEtSP0yI;tbem(O{t+k+om!;Ox)*{#TrT+WbD{z~J*^$h61-=oUIP3F1`&Gi{BJD+21iO0`#7w4>zSA6pM##XnFq033HjK;`3 zRYtPTQe&mH`9!&;N=;s~275);V`l`0F(jn1;W^rJ=Ha>bfM@L*UjbiPeti5jTg9NdGd-42{I$)u`?VigmzBCI351@*pkX**Kh9_i ziX=@rtK8}63V4N-@AwNmK|w-YDf=vD6VE?VIR{K;TWFY*&QxC4E5R@7Av>~-`&R)D z_8f#axya>_c;n-Uk}bD=&CLc?HpjIKq_d(h;Mx4f7ytcuyJ_nqfg9vV>AcpMS*7EB z#zw9;yuaGQh43WeM6liR}`uJKyxT87o+4t^b@UrXN9LG)ug+wYQ%$uv@A zNR4{zDragI2%roaOH_0JLaKPsWd6*t!GKH-^Yn%jUzNwu)0LRnf)|fY*am?7>q>hf z9@xN}*V%1inT9Eh6C57B11!q!3ukX+|hitjX*C_e&QrUYO?7; z7gKGt-RkznQbMLI7%s*?N9->B%!5NZ)$dR zr^nearj_){)u%V4b+Z@ub@y?~Y21L+s!&k}<3=tkGW{}Zp_nZ+>fH`@Zw`WNaSZH{ zXJOIBbBUb(Ls7QsLDQia_Jo@5!r+^kfAfG7a?#~iwg3eN`3$51(h;=m_g2lh5hs|Qr7+~HYk!8=rfFZ&*ycjkIOZ6z|S^5%^QvYzC@+g_1NlS zrEv{za%NqZo?T0mdd;3q&?80NF}47m1ObuAWhw$RX`PREeL2xXP6}ejtz1 zm%FsU)U)-e7mp9vG|w;BO!l1$#Cm*wbjFRhy z7pqcZrEty|U7A4q#z+GLZG(mIuyFxYNGfwmU#|(h?>k6wP!YB=w_@!yIP>&tS7IE= zEfiPiX!l@hg%LluJ|Bt>Sk)$($SE=iGS-2j9a9WfbbIt0Gz%ZExzYcyFmpR#;;#GD z%~-dUx@rkL&kPGwy|LL5EdFvMj>o59D8loaqajIRFSWMbWA9 zsH`n@5+D1dAAOJh?#{*)B}mN6D-pTLy?J!XHt_v)6L;AZ|fg^_TIy1rz3hum!R>^=}qJENID7HQou}n zOJ-C}Go?UAwV_C%bV_FAr)W-`p`xO`a)BzL!Q>cxkE%*G;A<-F#y2suyCRYN=z0d1 zoO@H3{3vi3oc}J|v^J-+^l46UdCrX_S-I+w&`utIc_v9C5<( zGLo+qzb($ZQGiE5;~HY22~>RxW5dTOjvn3YsL^R>JNoAb+~bNe<+7U|4e(jKb+Tvc zx9274#mnk9=84=Ff7z%s?rAzU6o4dVXU#l)^M(>RMXGXHI$@ha#!W>dNvMVPF=2@H z2wd>c{Kcy3?krbrJmNW4XNr$y$vm(RGApKP?^5d|+G+?nBr=LRp~pHX=IDm%$)Z71QEZcSN>()AjaSPj;+SWp z!e#F?$1jrD)ZRV4{EBq_=Fosrg~tt7yN8myg=~fD7w1y|4W-4BiPZfLB^oV@h2GeD zb;X^=RgN?ydnDWj=T*-Fn_0v(OFyy2v}4*;dL3Yc*ZB@BYzK9$1Y)Mh zZTRh}0NRt&Z~u|&hzb`ew++whhKz08I%5S}h_+k_jk|+~XT=}JCwB|p)IVmqw-o8t z{2t&=He>BR8+7szd(bb!qLYiOnN_F;PTQ{X8J&lxkeB3@3Y!PSzn+`c zD2GbriR^O6bixBff60F(41YO$(R>GfN0e;SSu>`{8E}6g{~gtUcfsWES{Za4rFELG zg41V)-4l6YycJ0M7^*=loO8ym`ERYlYAouk2#78sk|2UljO9rQhO` zZNQH1Qh4=``uIoe0OA4J6DWV{WG`~+6WNd}!=;6fRQS9Ee1_+9gts;lSrvVz=Xs?s z)qf(NZ54>Wp^ew)c`FKOzTCe4B|D_t%SgJx%jm;c-SF_;xqU$=d&k$7`oy(Xe3N`l zB!8pwnNSc&&e_S)(QB6|jbjCM8J_)s4}aBvD@*$Ll?u86lpToV<1PO z%Xi7^Q}{4a(ir9oSe?63TF_zHPm~gVqA!ha8R_VpvhUtcvGgVPcSIAAst+5jOBjYb z1Qm7q3QG&yr{4ohzVVO@JMOljn9=18rt))|O)?`D%Nt@esohc&BlwTYq6my&9}rHo zuBKXIJj81G?-wPKe}v9Q{k02%xMM5GcHV4-nbnO3#Pl71<2wS%hdF>U%rmC$j~G(R zfM0>L_R7jit{XHP^pzYVC7cQs$rq+>h$QWgvO%AqF$~h0b|Gh|Zj3ZBEHc&-j-Xf3 zUtb!=oG_EmcYBPgl@Xqb&XsNKLTQe$j zM_<9HAyRHRe@c^VSI41ez$~XOm|7i9`}-3Gh@=loF+x7GlE`GtBVMHn>+i!U4p0Cr zUJ}KH>N&NwKk}? zuCb*dt~Aw(OPGr0XeX}q=HV&C!-JfR$@Mm1J?AuA(!mLOho7Nx^t|c==3=HaMhuCx z%9dF9ft2i6MpxM%JMgKIrDd=ek(xRz?-F17{4@AUv%Nc@ww5pipiSoHEAtRk3lCd4 zG`e-34Vx`j48s%wjj4R7wO6jnx&4VzOGw)o-5q$z{p)$rwt_4JjNu#I&m=|wl~g}*mI z=*hfsfmtnaoqv{3x47~#_hvy79J)9R+Hrmxn5=$S@)HzV#fAmYPWMYq%r&F#PGoy- z?SzLmZ-*<;&5dwtyQu4EYP#6DIhl;rv?w#%i9*G4w+hFLbL}mh+D=$EA9y-fG|Zu) zn`MzUbI|RKuC6a}i}JngfOcmNEH%R0cztDS?W~U3ssws4Lg#c;qkb48=pVShGS*pR zC>U3D+(PYM);duKnfC1#Tm9B@PWqdtTSzqgt?u05jyzDf_#Ft*2J2n4zYlQ#7MRc1 zeXR5S{Ll6`NyJUxN*m4!i7ri7!N@2BV6_KRR7a={cs8OVJM3>P~iHx1`kv$SM6OrQQd#+sTZJhZS*{Lp*4f_o-fb8mHtv+wkI zN;Kfy={eyh*;;Q>%Jy7kY5cYH@&E@RCokDuDw$INo|Y(%YNkfK=iXUP%T24qCbkHMuLD2f3e|; z%<{TCyP1Ag+Ta;6rhO(N(HdaRtxPgRX>GcuCz2s&$$u_PVE<5MNXHw+;^WW)6Cgm0 z5VDNdzH&_dK%8fR%0SYZImmLmPbo2Cv=pA;_G-Ktgd&Yu!f$H8 zQ4hNy4D)oG&N>Ua-63Q*dplKj)eObjPnv!@eeV83Vsbog3AL!}&wjodq-Gp-szww( zR3_30nw1z-8@~=(>*zrCz(g@@-9SUJ-kM((HP0b}dT8lAz28r7RxsHlAW{bq6K>HB zeT*$2V%1q#hf0pEvcnUDd>Y)@-r4caEwG&dv^PvPpCuGC_WqY$Zo&p;x>%J+A0{7x zzc@Li5ZhQ9eb`<|6l2GI2sUISqD#~G&&lP(n{2ttjTt^+WP^JJWGL_xNegWHXygEQ z{_Sw64O^6+SWqV?X2|i>uCWO|HcG%XrBP&%bYQ;S;|!)K5SNiY88g1XukmViBLlUl zl^~|fuI^vD!%Y36dxP39DuLVY_=)`%!+i zgB3QkIXf%zAHyqWLEdEOtHFp4LF=ct{{eeIgueo}9IH1Xgk#&P=Jl;PQLowc+3~TK zl!C6(S(S&Q?d_HMwr+9bA3tA{c3eo0V489UkKW>d z0!K_JdUU7SQeNbP2suIM!$4nOPiyy{R-w=puNrAm&zJYGt=X+ExnQi{s8Ix|i>SL) z(lPRjvzShQl6Lc^Je9ht?y{>NiOT#*E>oX=*p)IRF0HdBkY!-H|8~D;x_&dVSb~m zjf*a1F*zy)$tzdE3`lieZf)i;XDkY^i8k@EL|!uQ-`kT+we1wXyeWB(c1Fjd>HGWk zrG!+HCdjlr?K>biNA+;+lerhBn5-M2`BMHE?lCl6{X&!SfiD|HF{vzy5g|uHe9jr? zv{xk$xO47G9=Nf+$2m?i&Q2P|QpJX2!V9)=JQPzd=%En6gkdN(ZD=BcXd{W!mW_@v zC97~W9Yr*2BgG~Xa=lQb!tEoYz(zZAMg;W`8{0vM%nn3yJg<|_6Hw_inm_)%lUH6Y z9ub??*E^at<%&pbh&4HzB?L?!C0`r~QX1PuK;U>np**`=aJ<~GIfPSPaJ?&q5iD75 zPSE2-Q@-=4PQ?(S3ygunejTW|Yr>9Zs~9Be8#G_ri`Xvhq5gd~M-zoWlzH_ag3 z&LY$r-YDTAOJx;|&LkN~J!4srUD+0->G|%&cs+fH=958+D{+wAb&gWhp*`Nb);JR zR&Vb}iH|=<4KuoC%S_x}kQO3hE%Rurj^&wAHvObTyL#7@^F)Fy^D z#3?tOfXnl(Fza(R;ot5p_paPQlz*)5_6{7VX=p0K|~h|5X4HIDjZ<>g^hH0I2!Hw&x5NhhjOBO&mY&D z2_+8Y#N*1ZE3!ukb>+TCJnB*!agSi*T4!Ikt4L<)#`3P9Lw>&Fd_<8`=OCjLd%C`L z^QtCEa$<8Gk)PWhE)C~GCePbcMimn#on-56*`Mmd&%KQPcy|`LBY4O!&9@ZCd=w%1 zZ996%7~bBupR>z_Yy7_;8u%t0En?F7+y(BIUNjDiDl@n^UZEY`ICdUY&xWubr@`f_ zXrp52bCmnMmro*IKxlT}{mqtKWWtFoZkUKOX1P1oH?1PBLpEz$^O_};+0X>$Ox=W6 z=lb*U^W=@)9m&yO>5UOKe_?D-$_p0msIy?xJLIA67>}emp9DaB%=w@aGQyDG9AsMn z+0N`N$B(+ZJ4dY^)lBf$Xx4_1Vu8B1|tX&)91N+i^dk06UbImm?kD54I0d?Yt<~xY$@7mr@nqgZ9Nk3QjZWyPxzaUCK*(=^WL8}JyHH&|E-g5}%01B?eNTVijosjzJ$>z+(&Fd< z!;O+)% zP3}#~`&if=P~zJ5yBXp;0g-XA5flaYEK{8Cphh6&a=XKRyxm#qJOy(rzcmc1jyA?o|ILdOywzA<*8JjmQ|ik zm20S6J7WAag^8MqG*gZM;)eNsM7=9W(|YjZ-4smEDp>-s0N z$1N&kK70}7j60W^_<2TFIUyXTLH!eoRCde!3B?7Jn7T@T(yZ_?Zvd5XA|Yjga)s=# z9H9OYfq$Ae{QkpC_dg!hqh^{F)d#g_0u3QM6b+_XaVaZFvq-`Ysk4V!w4nf%Wp=?y zR#<(MmD>f!n4M-5s?#i1S4e3^T{sdx#0rnH%0tXwtte?WF;YmYj)cV=N?nm!S4?xp z53@pFT@mG~>WT;1WXe>>huNghu*nT2cF9RrWS1OcQ|iQHYF%-pNVy*asC6X{Di5(3 zrk{40l?d>L*z|;5FwADum1xfd8%iT3hu9vwRQ0co6dqy=u)0W*y3H;nm}XW>0FSc7 zATy4#8frJkpJuh<`Tj$!9+@|~{+P-V#YKGb6L#!cO zwD2ffkY-n;*}^)Fx-6=UXsX{&xy5O=WMhI_HzvYUYKPg<1V?_DEu-Pp$40ho+DJn# z7q7I;aVcP`|15i*sWfr^3Y~UMS+1;bK%PwN#K|k5F;~#ZON42JE1$|eYFxQeX?8j^ zIE-Z~b!e{nBJ(9o>TrUs++1^%S1MOII2KWj z6TVtdaT(659w$`WI#07Twyw^8Os&&VZD5&4S&MBDC9I9;x^1YlcW$fG<7swvy%{zA zIQ!~jQM2A3^@DUSj#A5Y^}%QmB|8xfVhG#d!)$#-bf?~E(^~HzJoh!QjB6w=H{8o& za=MM9riRC3M2xFmknyK_Rp}ofZ)9CdoWwo=~rG#c+Z91@~#vUZ*NMmn$ z00=h-f0Apiu06?2!rSHy-U3W5(oeGjB0n+U8fUZZ0kZ&wk_$zI`9r#ZK_?)xfLZHN@?rc#lHQR>RwJ~ZFLz``2^+I%5HcmY;h;5=T$r20{u(j}F>fp33jAiP! zB~(>Zi}u58dxLIkwtkFth%u1s6g@LSH^hv>z-d-zD(fpsN@%UE$~AJW>P>VMOR44_ z-$KB1h#$j|OtWO27SW#38g#YJh~VhOkFZ(v*by=2LYSFqoiF0^9bv_`9x)@nZCdL= zAEEcp2MtQUL5%4LE3x&;d54)X$ozt`UZ&dn`Fc!HZvcaFbK5ZMYVg^<=WJh^?UV=i zFwv4uWCGDA1h}Qvg(4xbr`$-h?%UGr`uc)sL8fWcA1z3;UD)qPXl_H;4xeP-w8JFP z>q$tLL`w7{Y;x3y7DY;Ii=;~rIv}skrqb?%B?R41bu%?!65Bb%#2ac&BX~ zWBZUyQIYN+W(N{ct&*CW+91Dwo$jln&ovY)yR2tQYN#8^}8P#;kL^UnVUN6!& zh*xhEKxw3y-n>x|5?WIIn`%z7f|@AN_r99SA#p%&IZP6VS`$9sJV-TS0@F9hED0^h zOECM0p>3l!nFIR*n|zMcV3TCCscaAec_Rt1GpvmBS6L~Y<3jx`n?(|OiFTG*^dQDe zwoE0JCK4Cb_-kzHX|iD!MIy!2`ZX4Ll@Y_%&k?KB+))`j&BEHWa}elmoyJrs1f)5z z>`F~kQmvV{R?V)}Wi7AJXBHF%uH+V-x=pT-;0KB0kiN}Z5}S{)Tf@FvhS;q*BGtp} zEwt4KhuCdYx-GNGC=vGGn())>w+*q|sp9sm*Tm1eRwrtotEF=_#1`SuWxbV${Ei{E z7>Ldp)fcWJouw*~W^W&2cT&ZjIj`?bvv+JtaCMly6ChyLG`kBADLRq$=`?~#=U&qn z%G!54u-L`W>ZSG@G$C1m*R#0B2xsaJ(k zQomm#M0`NfSJ0)e4DC#2g)=4+DG*VVPc&yN=4aS6t}Q0&2y>)y9x0$5+3a#gGE{|< z!oR7yVm1CymdGZWZK`or^FdHIoiCgWJO$nRK`;s9ulinVK{^#^}6Twd&IK-@)s9!V)q2hk%MK5BpIGNAgPjKUD zZhMwY8^1WMwqF%>X?7%GQ`a3RRft5}23n49(Ma;eD>~5v)in?Rw-zlHn^{Bq_24jj z2yvjes@#2!BAC#L#1%`-?`h8`cy&_se2h4fSGN>VC{Np~yKaPSwLCgpts zX>qeIJ}iiQHf58>CO}Tu0_}h7BP=9}=C(l=HbvIh zsrRJWqYEpaWmW^EKdnMj#VdhEVwS-gN6r!Rr+u@tm^&u8pTXQ*R3l8*U4!BXh!UaV z;~1zyBytBfi5=MF?m&=sAcKM+^M&+^RlK4+5~MY5Rjzf&|8=AufkjOt_4+Z7Eo_fy zhuC!>hdYVL9~YUU>=TrK!pT2LOz8<}ITkuq7a2~~AvQ@=fv+87PYKqOW=CbKPtrsa zWUC^{_>O_`kx`pwL-j_~01koz2ZO9k&JY%)9f%q*AtvT@9`-sPulGfLIXsM7MWTi? zWz?6MTQ0H~iwvPb8a(Cch+h0Ds-BFNCoHOYCLZ?>Q82j8X`+Sa#c@d~qX>-ObAk&r$KiXrCgKV49 z?z}5?-Z|^~!jn#m?PIk#>9*+bOpcXI6D=-H9A{rdD@V``voCFwLQ_g6)Zgh;BEJav z9Jpstw$sDK^^PB*MHnCVc*dgk6jglr3TKwa>Tp;k)4!B`1zv*cL~NKv1vmI=_M4`S z6^8`Vdm%HUpQ&L|nHG>rf_Yt|@@+IPe70Sf7gJ?R?wrYDun36p&4?KHuu5SZ5PT%< zi=tX0Vt9ZEOW+4NotrjRj)JFvk=Q>ntiP*XSUd=*-S>7$>p>;OxdAy$qBSd5;$bHske~PEYEXP$kMqrk4W%ko1j0q~1@*h)PpQ+i+#Fkk1gzTIZH*!Ho98I9p7*!PkX`qc9L`y)Kl=2Gcg5 zvrU+3Is6o6S^#ERP_45fmJr_j3D-;uMMGI;nlBo1%`|InL!n)GlHX((!b}URb;Xfl zVW#QPaHLomUp?r6ykn*nOEZlclHu9nGzY13##GT9@=#{Fb@1sr>f zI+?Jh1JdTgpif~e=xOS<%y_<$!#Q1MxsA1~#$hA7HJNH|BR#v3ImVC|yRD7v)?)$S zwKlU``^RW~30lLR48xu*jD{S0G6Z|lB71U;Fx`B@p7fDDDV6&{_DgY?q%t-v_Dm;B z(l$<#YGq*0U@T6lgSxgTYDWC(Hb16!i&}0`&n+6cMN<|r?!!{yTbOX%BL*iWjx<(R z=?l`bh$4j27a)SRrEV_h*f#7SR6d;R6=d7G_OG6RTVx-SDZ)b{Y~1mgqQC%&_7SG& zovaFmWU1~u%}U9<+<0N5^AbNeN9`%LZq}?U%d^b0JT+LJ3fYZsXMVSeixEs^Cu8SG zU)8;O#;AAPH6(W^-3~8$#E~CwldNKsWEH_YtYQ;bMSeD#Q7%)t+|gAlg^IaZ_!YNg z>y{W+Fp*7C+5ScbV1JYAOW!)ECs6a;#h@klF6KA z-#o--+oq)J>8@ZRHiWt~`_>_5Xmy4xlnn~+c&wsQwe{od+jVBdgic4QOvu*Z>eN-O zHDW%N#o%eH7Q(v^=k04uSc3#$kFwhVu*mV0>$IfrJnrZMy-MFmT|;wqjXlZ@&b#ZR zVb7K%YHDY}J$IaaH_NA1n~**=aMxq(dnZ}MHf#AX`@U_~h#JZYLHf-Re#-)0BmI{B zp3+j}fB`6f;A$)>Wx#PU1PKz{wtm%-QXiFg{2`nHw+*u&EtcgUHwq`baJg68x=mW= zPd1`tBJss-6TZ5i!1?^sdjARb;^yP*XAQxq-zGKo=fbv=R=i4F#tt51zo@fBt(H^! z%ev4B_N&dbXS3nu@3eiCF(?zUBBA5#*A2Lp_zhX6N7-+Uu%FcB-#x^N>;fXXm*~y! z$)tJ7F39*eXW=aE5(me*vvK~X!d}Lf2}6Gw$<{{N=o<+!`#e>#mhcGgaTP6a75!bE zg%}{DrgoV9p+=o8Hl><)COK>Aj&xtv%{dl%(e@AR??(HW$*Ee}{N1Q$Li%p@v zE(N__d4tpB4b%Rud_y|9jTf8X_g(+`rbhu(FmEpo?pT zEpfxzT45~o(No_hT2&g()7*yk2UUIW+}CR}Kw3;W!)DNFAjXBe=Qk=hJCHm>aNr*Q ztuC;1$kk^8Pf9%fX>lB)Ueu@y$)b8$_>gg-Bh9x%tT}4nMn>olhuFnxT_6&W=RkNC zA_3SZQ6G&`XzP^+RUQenfM*4SCTZZN#uz*|Ok-bT>luirFtZTm`#Bb7hIF%G{w*`$NeuY+(F49kIp|pry12S% zCKihIEUqEu`Hvbf&cjUSPx93@;E}~}HPP9k-Px=vEe*P@SG(8ay0{*f?aH#qqL3-4 zSs9VV88#C_iRQg6FYEc|lg>GLt4HPK&TGqIJ^xCxf4hYKHm_B&)5;L1$vthik2%Wk zvqrhYYc)h@fz9w9XKRTv|8v;6Fpb~KmYiU(5XX9Tm3oD`uu>8qymC7#iT3W3j`TyU zBoWnWhS_TsBn3~U*`rs83yg19Ja%55S0x%p zrTV+PW>1p*f&{xW#|R?=bNM9U=-P#nTbE^X>i{78eWx%asm->^S}Dr~CDwX0St}^A zUgHghM1F;kHnthnT$(gx$c7=Bmf1#TRV7RhonZ-Zt5QE@GY+F42XA zM&geexJ38aegYz9`_)c0KW(Oj{ z1oaEf7TW^<-|uV^+|(|!7nd;O9BEcQ84LrV%Ef)#rJD8%D?EeH1Z-0b5i^=>B>Eom z&Q_&!GhdXm@-)}oV|dtWdN&~nQ6W3WMv^#VxOx_)I=M*^-(h)wQS63#go$?qC1x~g z3tZrR+2Faa2yB3#Jf>twq!LG9vQ!;}@+j^AYiJv-SB2tmkEa4X@9gBe#|CoX|F4~t zfMYvzc9J>-NLwZ|IN}#Z@RQKEyv0;uF=Ou8E8up@hn0`yXOG-)h4;N98zRkzEUuaNAdEmA*58C=MZaI1ZO+$!C z#H)|(xKWjyy(7c~r1-n<=$e|{D7t*4;Y`<#(*AO2U7Vsq30uDq2 zsFN0Opx%%LT#oom;=jF#VP1TgbzNZG74EnTv&J3i4_GW3scBx4^R8*5GpAUD?|xYQ zyp&exAu`5{(`>4#JO~Z_EZZRL>{pp_ilyMWc*w;mF&kYdZZl5s2;ns9;IxczT8`1} zIwMgXF^2gBfzOz0hd-=5;*4;u6ByM9QCm@`ReoGHxg5>J(SWhX&VkERM6?oKdGley z76A;eAeATHd{{0ZV{H3C2UB<)>4z70h1!epez@l~W0X;0 zuw=zY3Sg=wFpqk`te}A~)WrnMWCwNA(8obvDHIKVnoq&qP`rAWPu(OSoR%4wl+5xX zYW;EIuJWYJB%Y3#?BBr$P{OCnxM4oyB>O%=JQJIIl6}=#cD>=R_r*bqGf(gY5kU={ zf$^ifmWW_h)UX4`_-uNsJ%!wydZFt`=W_yY?I2m|s_-{#I>cH%C$w-ghQ!6P)|uuP zZIl%V5iUHzFQ$p+HWUdrXrbROO7nSk(J_7rl1sPMS(u-?RmqpYi@OW+~64lDE2insF z7%i9e(c%`n(G?XsS2#L`-}XmM*ZS~F>w_rK4h-`pZphRyZ*-4Z&<=*HhWJvO zlq=`R1##psa9I{amqD%-l6=0zAqzLW7|Qk{1Q(5{RuFppDw{;hIIg6fWjx_X+gqgz zhR))w<9wOp%KZ~e*m}p3l7QCvq~Mt(y)Tf$yy*}l#l(j|57({oQ$KVm(w?H4(~O>P zl7pk3DiGKrNA{u7H5qD;F@FKPEh3@@-;c!LONDPdjJSfQJlsmz!yRWF29n5QL_+DNBK%oIWwzLT)-j%^v$lAibH|Lb9|9?!6!^R6A59rxfR8TIxy3>MG!vLKY>Hs@uUuY25K*g0&F zlpg$3LykxeJ16UD_n34ia^n>4Ow&O~WJS~P^NCqlTtn>l(-%->HO6ZWTYS14eg;Th#P zI`CXTw$h6YS>c;rl@q4 zS2$~!EvLPiMSyM+r1l~{9Kqq<;dO~0aRLI|E_}1oWlN^ZjzsJ@- z8%hbjWr(*8@$1CrFBxbIrj?c!!$0&DFL>ssh-ahci99aRLJ>ph`PPE5xeair;p>N~ z`1)ZvoNI^Klq$s4+oC1kCN$?wgW{HWQI$Ui-4U%ext0;8!8=*q!}ITvC2_?FEl$jfj0fSJ+P#$df8E{zYG#PUBTCn`>; zG?A3R?PZ7;G z2O`x+5oshSsWcMIBGX7Pmrf(WI#al$TE^H^Uk)MHuuzByd9H9pQKm}ca#PN-70`RR zb&9pV!c-@&>i;};@_Q$i;W^j4taUDk-6NTu{H~|4Rx-O&OWzbGO$crqNS8Rk1gt^m25?{-7D5`01VnjDR4O?=_Q5yyS*F#lm3E;#t=Mz|hIw~Gfm&CHuY8N_f?<9=z70CYI;hH0ubmw! z+}3h}@7he_st0dmpWVZ}*R|}5>>`A6Z;I1= z&oI9s5+Ga2F4D;!BqZBK!)&P?9_Ia&UKz0hy@qMT>#Q-Vuk`%u+Ul7CxpgXHi2tA{wmYOjS4&t;nFjRO8Ppz z>ok>bm`anW+<2PRQ(6!{$BMGt_)Phx@-2rX7s_aGHaDK;H>UZ%djAQ&fAevE0H!bO z$(!upG5-2^wfORyzhP4%PCyUyH~L*K-M5wRcy6Y!@CeO!$0~HkB4kGkF;>wrezVw4 z3>WoBgV9i$lPQW!_MqQYzUK@&kw6a#e?Yw2FMCn9VSY=7~d6j6+fiI*r=VXldq{p z=AMC4wJs`;MXg#_7Aea<4Q1jql#0_(Ru{!-D8p&6i%x3qBaFTIFhfXwOa#A^6%Z0u zfVMvpJuJR7KnVL`tveAdynTq@ng|oQz6JhYOtOd&%WT6g6CYTYN!R$S3_6 zm3!?Y<;J$`E{8~k-+YX}1s^H#x1bIoiG_hT2_hA*bi5HnYRMOeS$VXulpoA$W1@}g zd@IkO(iZz}M@wzO^;@GhZnMyoOG7Ky2fs?+@K1U_Mfa(5=|SX8g}+vUeF9vp*| z+<}Bln>x`0?cR}ymg0st7L7^$J}SN;=J?z=5uO2Vt9(;J;dy2%BZQ;XV*ZKt#4j=< z(J)a=e7P7l2tXtV6UPTa^>O~rhKcShlkADY1e-9-?;7Usa!N5#diNlU*%OJcMThx& z#7mifFTHZ};Dn&568=6Kt=ujrNZ&7}?i8SO5{5I|@&MZ{An9_qBVKLCu^l4NOMo5+ z=y8Bf$)&_NiDfoa(R5YE_y;IqFCpnb(mBjOn6t|v+RM1y%R#mvGiEM2oH64xW*lS2 zF=iZN#^snUXOG))y9k^;9*KgpQxD?ob{x1P)_tDw3E;S0C7C^KtR0u%=JUG~nDRrk zZFnxl1`~;c*l!L0`DUC(XIsPpxK+EwslruFfovAOC9Bz8;sjmo(f}Ciq5A45)zt#nw+U!PdWT3aeOsE}i!S$pJOdVRth>TBq|){4<6$W(OQx1PY5oeLU7=Ct3Ju$~qh%yCsO8-!SyZSuBpZ*ixu_x0 z^ihGv3fn%$KSlydaC>@nD}nJS>C4WGj2v*@eS8zKT;&Eao^pL6oDc`N-CayshGUfr zj#W+^s|*~g{J-H?6RPbAf@7806C92ua)M(amIQ)j0+LulDmXFllW1w&odDeYZy{JH`Bzzavt)jUccmKuM}3 zEhQ#0+ge62nCpb{#~(k8A0PpiJ$BO&_jpZ1mGi48_V8Ysly-(IP6Lu_46>TGQqUU1k);Wn3f^X9Cnq%Xo#|C$)qFVKzV8$pU>*fu%!sJJIf*( z=W&F47o297n#z)9UOJ(;fHb-5P34E$gwxRI9)FEZ$5nX*U$&N#S@n2{s3EiW8)Y@8 zSW3AJ@v#x_cTI4|ut)?5EWs$qIO?EqrNLD;uEub=@v4$j?Akg~AT<^m)rqlFtUY{& z&86~>Ccetb&#*UPlymG#R+49mAA0e-U#Gs8on{x)Y}6@kDnF+GUuEH{<@EH`oMU(A zosKE^cS9X*ugSTON=O1d!JndocN8w;_z?eO0-obzYsh$W?SM72GO-kXro>Mpl;RsS z=8K+PdWf`PxS94PvVRK6z`dj!thGSBq9S&L*rE6muh8`dPaR}%aa1iNQ1BV+;tIrY zX^E&NW5cwl;rBb=M8*$Eg{)#O9r3BcE%$`@ZaCXTWEQbAEWml$Qhu>?AOFnrhP~eL z7H*W@!pWYu(9U=ZeY5C<4vE2kBRz!EJP%=o>mhW!gVb_ZwEQI;gU2&q6yu8D&&z~K z>*Et7AV2rQwlY67<3@(%{z#3uh2;3BJUu^RP=v9zj`ZW5_bWIN@YtYN;{y*n&C?h9 zu*+g<5j#s7niDkm3-4pNA;*vSKFnWn4~xR92KFPuD*UmE`{bt+n+|0`rpB=E&abVc z`O{a7>npmL{2;BtjB1GY2izjw|2G%>aE>-V#tLWw7jb-JQW+IXa6g@UM*0V37;KhH z>xzm)Su3f~9&&IxCZOVDg^D9=UQNW9U1zEh^FdZE9Ch-Ox=12oVgWu{fG=wSFI;c| zHuHr}6R)Br#J#WUiky!Cf9ajJ*EwI;Epxm;0&)rTnBUp)PhIFcJ0DXD&ag|whjzd6 z2yd_R!C~77q61|i+;vf{aJUsUdd|JkzO}{GGE7FVmDm{JWveFjAlF|5hV^UZHx391 z=VRDNqUJb1A&dbLO{CVO`7=3p1Er$hZ=HVU=)~Yw_y@NBMR9rY>@oI*2Jt&9)wVIr zpBrL-#xD-oI%ULb{%P@}O7d-knH_`vR#7OG1%Cr0{tGbZY1*gRADovFOkXez%y#d;7P@CW6O4l<%nXmcRvr|p2~ zKPfMJQ3s|tr%=737&m0Wsf|}>kGZ8<|zfBbLoi!=|pGClw&x&YsPqH8$Wd1yKP(%D^_Qzi88Hnb; z@rV=#fA{EngA+1*SvI{*=BsN?3OcW@A!1yes2yT|6BmWG!~DDOOdwyQ4)gEj@bA;C zf=J*s7DkljKfHS~+wq`=-0Nau&F^{2+0K7@Ke{r>+0H*mF$4B3j=b|>oD@~kQoo`C zUVEDV`-zz3@WcEEX3W@T_Lw{K{+Poi@3$O?<8Hs;-3xk%>z5^f-TCzZ}~ zbCQS+F|uJMiQaldr`}Qd%Zm|RJI+EIMacIb1n{I{d_Vg;{ZL5=!5V&kmaQbM>;3S_ z>%@UeY0E5`E%#CDe|czK;h=T06MpUd&`K=LMdT%ztPW(Q`H!-d1GV@n4eF%O6qy1I zP5HM+X1kr+=naXi59AZhm0?_78G3i4{~w|>bjDAyYiR2Kc>}KLr$3!SP-Oz zzt|Wj+u>&}8NcQ&10VfBK&}_mPD}YY?F6{a#||@b)%l$HkYFD{u%%6%#zQP2{M7kr;w~l~2rJVL|x#h=3b0h-{NMrYlPZ2eG@tX`lQKpy1`aNPrlmL+{ zZIg1=QxS5Yixsh3?qh{98j!esOz0)CA#O?_LJpLN9#&2ubUYlS+##+Ms}_#bXi@qBSFa2!j9id>UHO{Ak(af|OotDjN&H7>f#~g(WLg<{zoR0T8QH?j``SA(? zIE=tGF5tdO?LoF!b3XE^JVNGSO+=qN%r1xit=z{Z;8$r1Ndm)Q;JV@hyOas)Hgso}LwnBt!iwoMZU6RZc{LDkG$FCl!%M zg(SymUIW|f$OcV(wYD7A#NTOS2F9x zl?W;RC)t&5B)HG^RXdU3am0%Iqkb|t`D|Lol!R@{(i|lJM)DsduONAKn7@|5)#P!0 zDiaoGGPQKlDb~W+{{v7<0|W{H00;;G#3M*bkfgZq9uWWlHY5N5DF6TfZ*pfZWn*k< zaC2oZb9ZzvcWGp2Wpr~dRAFLlWkYXlb!~1gV{Bn_bB!4Zc$C#~CVTw5n}3rfkU&U) zghP`J5Jj6s7PagcshP2cMc>p~8H-rE1y8m+Te+=GX z@U9EqW1h#D`}^EG&VY9O5JsqnzyN6_<`F&Gcxd;)73g5#ipw9!<(}QJL&dg^iINJktyu8j_K8Jh7 zOkcsEk`FkS!8`@$d*E!G!c+^~)H{X2sm!p7c~x`I&*Hx0CXyNj7xDoXG5umU^=i3y znu4c$U?a1+6_>cFM;zWpyUj;x<=z=?JQF`cfOYjY*6~^EdF5FQKFTCZxwnkLat14y z)k@y@UEc6)>Y3_X#b7lL*YL8nJZ$jbI$UqVyj8&L&#`^@!6PHwJD0gMGI0|VpQqpk z5A^amd$F0pMh2UhYBTS8K7%a`S{St2>?DAK7x3Uh7I!Q6E@Fn;cu|`Nx8uc(yTpSZ z!vOF3ah}jT*p5NAa)@Vj+MPwe0y`LVGU#GpFu0UKH-j*P9tIHwqYCyS(=~QdTy8{+ z#7cyDRVO%XWA%jy_O*0gg!v~V7Su&rD6}>jNyMVz4MsenNAws%&G@x?KdCETJBD-_ zv+%Ygv|x9$)+_iTOe2;pdRPx8jA$gobkT%N#}Ny*o?w?A>~2ZyWS;h_#%fZ%*TnU; z2T8|q1X~qTs==79CG@RAJd>8!hxC}<$sB2OZ%B{R!FO6TD#6uZqchSYyny%+_k2P& zNBebC48oDU+=SaKeWkLh`h?!XjJN1<<8qTp0S#NTEj*2Qc}nOppmhjQ9aT+Yel|iePxeIgo@m4f1~k%fKj{f! z3ghXFdO!<@qd~$^fFq4{MWfy8giUCdUNR~jEK7$mvXd51Uny&Bsi!zb8};ksylJ|L z&*2hkJj)6SUPkUnnDxYrw*@iBl(WaO|N6^gu6hfHRDy|xDEan_jFm5g!W!y6-zH=>IdN{l+7dKNd z%lPpz##b<*!nff&D%^*CDh$FN(vyn)kW%5hFr?xD9732ncH5S%tt#$-A%q1JdhE_# zJ!IK}+3!^Ga=b#tEAbO5?t%wZyb7;Y;T3pUg{R<<3QzM^pyDUtYbp#A2Cug)b4n8}KtKJi}Bs!d)ufgr8OMa|Gb4y!=r}DR{GrpC{4q3yi)O z?osg;{Gy7tGT4n@qChnE@J57OF`|WQ$%AXp37Ot%#$pnIH@l5DyB+UPaS)zYaSspg zByHhc-1{;C5a4dOOToKU+{*{pms3+YH)G~sQSlzUSH-U~_!{1)!V_>5!9O{JME7d* zwvm&mxF5f+;y3tA-{j-$<=*{dP}s-41MFY3Co-KG#Z-I%zop{0kpwu1B>H!8=!7au zbZ^lw>oekpd5U37s(2X57T;6xLAJmVd`QLbiw9(Pe`%w z7CYZ#cvOYk+2nV!5YMs!9>*tCd=h`E;8QC844+o<8GKg7pW`o7cn*K5!VB=Cioe3= z5av#JR&j~hIe9gi@dXAivbet{7WfjI_GJdY!B(KUDlDyUM?q$-jB) z|M1rTW&Qk*!8;7z#rIS^#!F}iJ`q)8LhnJCH7OH>yzOfl ztYOeJY4$gr$E+1NJ(&ks8|~v5o-Y!tl_ec|EEc6Wy?8vgv~nrZhpKxu1Nx~&qm&|$NoNNp&WgIixSt^`Ix$8uxAbrX5#1yCM09v78PBvU4@GmpLSBu^*L<=hd7MWeJ18)&do4qWlB=s>d`>El|E?hhHf zl+|SVkkFQvPDBW&RW+Sp)9l10ybkFlbGhA!}BCG_ROJmy!-MfBWROlJ72E<;oKhk*R_jMLIfq0o|QoU)X%Sur@{U zPJ-Bav>noXG%BKFwUXn}>R7W=A|g6EJEfz$DUe&CY<>>qjfrI9-4&0<5>{4Qa8i+s7Kz&>bJ_d}7p+TEEz40o zjHe<=`Xc3VMFV$Zr&K&o+^IgyN|!?9w%w zP1iWNBpzQ0Ejy`a5Bm<;Og>zV9qoM`^7@OX5fndBf+T0!_BbMgiw0&(`H5XhW z>xs`#LJ+PmNU%g#`sFRow|$KeX)G6-3+s_i5lQ)`ig)7VpBl`kgrfs&LyL(S)~pf7HmHPKVK>lwmVWeL1^&BT zws**up4+0O*NteuEZe(fyH~dRWcwAkN3{3KmY%u3)J=r_nsH(LaFc{+jX> z;9^+3(9=n1p=4&%@0@vG{=as*hgkJ^GYeCgwn0l&UEh{ghGj& zDUoH_$TCJ|4rPbD1=FZarrDQDF}&hbNhqh^EXz!XAtlMqm1L*0dWm#4X=m3UDAXL_ zVT7GiaAnb-woh!G*tTukPCD$^Nykoy9ox3e6WeCTb~;YS{CaA>s(;PR%Gw~EuMHEwt{ap|XRB@#hT4R?=({|kcC_Huy zA3>td?16ko0c*LL^I&H?+9H=Id(u?&3}oGh=*L1Eay~hNP?`_1Kp4H7Omda(OP1#} zx)QmF1Jot`yr5r{u^0?=nxRIwAI(zezJUT*V{v1l*unpWMIc`oQbhOY$kSD%85Rs|6)pQ8i^>&=8$BQJvw)0?3CkLdGd z^SI%NorL34i;?un>*2HE-&bR;w0Efi#A1SETtJ(U^34p|0Wl&Zrw}tY8G$}2*^Cib zydLtne$|RU^s>SXdG3&ZWC$PR2}@!p#2vA$4~j}+NxymbT4%cyGf5maXq+&(X~|8e z@W~R+_^~7rhQmxcShT$#Q&g|Y?OQ|~hjty*s|1dw&Fx0q%gUWnAlvAafOu16zr6&w zMpG|xY`KmaEIHh7gc|pw@npp|*i@J*(XQEZ5_>4-BxSX@$+7Y)4H|Y-49^%_<-?|h zxDCUTt{@b%A)eE|qOqM@y~In)Y3-JU`^MzSeLvtgU+qXO9OAuj58Ks$gw3OeQ^e?- zkq?W)+}x`mUEQrRutV2qq0;=jd?A~ceQ&N9xvSPs7*#2zruj`H!|EWU>uGNvisM!1 zHJ=$R)!sF}F4n%?t#X09KEbu6Vr>>Day|tH$TI*_ta zugC15aMb9rkhIMRwKOe)MrwcYd4R%Bv0rksZgTL0JnlN|h;~&fcMi0!-`-6Ig^e<> zd7lU`o0rjJHanKJPQ#{L|HQAOD8?b>@9;rXh;Q|1`hP;#JT)LJ}W~OZKcuzO(bbCByBp zm^0bJXdU#=gVp2GooA(sP%OKeT|DznQ84*bwNAxyCxUttb?_sa2sS88o9$Lr^IVhn zPf%K??w`&&){4*CU|xiK#Byfj5~Fvar)P7&WX*8Y?R1s&+BQ^~fU?0qDZerpWYKfY zxVIsakQ?K^VX2)wh>NC{I=3j>wRb4ZlQa0gy`VFHW}QI^BBL4UJR5C)#O=yo=5T z%YlqwLrT-#V_;V2Z3;rguH4ebFcqbS^}Q1{)W_R2I^>r1l86)^@oJoS3wW;(vXC!| z1s3p=(v#PKA^q`A*Q<^JiobgB%R;R&Cl$XZkqSY^r>526<;aY+#13Ui&%?~g;-9n{ zW^IsZ0Q+}}wguU5>WMSm>xr@a06)1ixwop+P9z1 zM@U^6yEy0C!bm)A4)4Lfoz50(o0PpbcT7f&fa4+88O)rf&ImVg{vTe0%vli{S zwtI5&*mZ;WXNM-(^a7l0pMDc+=ULWY$7+m4T6|Mw=HK>vTN&66tNJk|)%1fq$M$%K zYIa6*I@?eGb>Cu~#WQHh&Ysz6+fWRA)oaDP* zzQ&p;i(i>D$C-+Dn)(jhU%F1#UJN590C&%cIh2ve%^PQlA4I!68f$TZK9FWFeG|{Z z|1)G6=FGn#zy<*3cmM$E|0`tqk0k9<*UMLL_%<&k_x`?^dOR(erqM}3Y@ePBvR+<{ z#3_3anK#@8HEW-yai1oEl6;((rldq9&@eugh&4GBmBqdQ$%sD`l7>-{MqA#RR**&- zMNYne(Vn!QG-s{V+q=*vd^*AEX%4@=7)cK*LZ>cm3tG=OaTyNx|994hAl zWTxji{)~wrDIb{_ApLWY%c4hdF>b!4u59I z6P48wE~?`(=Y8XHyw)f-6BJ8Jm5tTY^PVHEV}N)(G23$1u9buiM&=yeeBFvo4QER{ z!DEPI?B-eymYCHG$e7iURNCmOR(A~RZK2yX(_Wss`@%_MIuaI(r)v^31(!1=)@~Ai zDqhgM`DbU2>)V?HepzHQH+i|Y#T$qqVgoajw-CZPP<3RA6UN9g7(s(-$Au$6GWm$! zaXWI}n;M<|d215#ZxKo0}2tC9f=>JWP}o8&D$24^VX(%sBjDlTY3?8^y0`7)iL|X_okHR zY{qdB$BjD??(3t>7Iu{zjedK2Z!$FKzoU8;Ci<71>kVrGZ>}Szf~re%XGcZUXB`GO zQA7JK=8sT(wu^1ihy=~=Vu|U^KbY#QQq~krR)!lqXC!-{g+dXsFcN-Wh`fHD+qkmWk^EI{}nr)KhGnGw^@ zWtS2opAaStn??GW>;!q{D)D}bt4YqxI%Rli?Oz_DWN^_MI4d|)Q(phPmK+hBHy{=p zwc}TEZJJ3+>?bHhFtcTSAA~%o*k*W88tiOC2~sjTS*G9I5bb9&cy|WjS@2o5;yf}D z`<%>aGV+BX+9|v>G!xyd&G$sd6j;tR4p!h*V=PXYr+mc8xE1$VX(z7E0qKxDSu+Rf zWq8PfJt0?w!m1e@1UW&G38CR3hC$}YiNqu|Vrtrimt#CSEg(DBl-ohuBrnyLI%hHi z$M`Fx{}XQOlXrOeqVAFxt@iAQ*TgGxIt2#|Sdk5*?qgccPXrI0NKy2|O_>At3cdz` zdcf)$YTk+gS0>a{H7E~E=#1BeQ_&{=X1`s^I9j143tRz2nz@*2FY@chfFIo^3QGA! z)D4hpEluN@Z?m>hZ#B*Q5i`XPvvX}_l_=$*QBTtyKcTK^0~aG!LwfR+lkN?=YcijL zxSoKNLZga1g%pNZt!%;OpR)SP+m3MsW<8xJ6E^p&P|`j zF0$y}u--ubXktPcTV;*c_5WnrB==PpfX?q0p0zO6%ZAH!1AkYMP!?AKgf{Vn8f&ON zGUh0Y&SASO5L0F->a#f)XeojdR~GTyYW8eC3g$beaY>Cmb&bS*@e{d5!Ww%@BIB`h z4${^U)N66!0wn|7Ms@HWeL^XM)48g9oP3i_61k?rT6;7E32Vw4j{Nxo)*)`6mCqk{@Hpp_W6);CVxc>Uv=cvk)AhDW3K<5xTWbIr* z)k>?(d6WgO(T1-H_C&&pS(vCONfl>vr{)68#M}<`&{#A}iaX3G9Pqgj+NS2siCLP> z_hcZlUtL;Ofc(+4=se1^Z)23dpc&4NZSt;oJSEi9DJ@TP+3yJ(oB=wTa&i{iUnosE zhP;`;f`*b$+<;u#zl^xSELgRWgIwW0DF*lEy9_NLbR;36gK2 zc8YM{-XbbZDI90K>}TAAJz_nYo}O03OfS?EyY)nLX$(SRe@3bz(NouB$KJ>UN?gIa zxebF3n0^<{W^-;%+dCY4$Xbqq9!cDqe~iR5yUgXRqba#cgLlr|oBpX+HOroVHhIfe5{Cz*^$kU{2+M3rfzR$=DG1ezHY$~RlI z_q#H3*V^c2A)2C{Bi>V(GWLs5Ea7Yff^25?7|!slgMKZU8H8;N77*(H5)D+5=cOyl zsGq0cIL4yv`-3__uk={PhlmiUD44bAm(-YElGTY-J9a?Y8eJOa9zBH?t&PLfC?&>- zJR+{jir*kWseb5bC+%Zp$vvVz;mC{r$IXH&$-UJ5RYZ(u&XBLI0v39@rpw~SQWcuj zeaZ#Uu*a`065og)iXwALOw>5LXJyr;RO!PaeAXHW8a;(F+DsFHdn zw>OyS?vOHRTY;V=vh%(pt9U0tBS7Oy4n#8Gdy^8`=cR5$p(M+pS?4Jc=1%b)-`Sf6 zVAwFt#DzhpsoHO=@He9(y*7!(12=s^ni0EfUXkCL+{(&2cRWV`rT!ngi>u>4s)yEi z1BV94y(zp70`=~p@D5ALxQ5LJOQo9u6BThMtDwzHd=vqKnD81^`^hC{PbrAp);pW%Nbuk1ui%IT^;+V5#tpXCgs)<%)#nk9 zMZ%sP3x|8{ z?x5i~1`FSggpz-Eyn>CC*ugOmgKmMz6Jov7BnP7LbXCxO`w5Q$x|%v{u**U;wFKZV z9i;V00cyWd9w&aPBqx$OR`=#zzYrGxGU9_1svo;Cz(r;WrMxn$R~=?{ryZs)+99Kh z3t~VCY`O~&T0@NfS=#5U%L1c9k?0rUy6S}HV2ReLTcserTg$dW*Loim3=K_s-A6jM ziRu-9U=Gyz0NKe(sW5c+3=l?D4Yr z$}xJvmM47c*W!g$N6u%UAM#`#0|_lL{CN3jdMjvCVjDY4sK&QUC~x9WZGj5Oc3}{j zWH4CJ=_nSG4=w*?gc~}pfEVhU?14Ms$9INOr9t{2kh~r;%XU`3RZhW9TTwir-vnb; zfkN@O`!&;@1xdUD5oNlpgosFcCzat<=(6|s(_mTg^@93ht9f#d{MV2BnBm^|SE1z0 z_a_@!^`Rv+HNDk}2)h|nTua*`bFpnvp6;26lAPm)%tv7G?0y6CzFyQ}a~G z1*AgC9vbjN)l(t-XC3OtE2~5+R<@H(qdacoqASx`G+)%H^D5WqAM$ZQERAOcA(fx7 zbX+WXZm@JHV!vrhvKC;{BefxYQHZb;mBqssb@Z*~{}@JlcxeW+&=Y#FxbocI`uPpm zhw}#AAuDcYPIQWP5bxOfkSd?^HSJbcc^BwpTf$o8{m9j}TZ2(Yq-NkcxSi=)9(q;k zG0xDwAZD%HJ>^}uD)9o?(60fq<%?%<&}wY<;8bw=kw1-%E^tE40*Fr&Ux((io7sr! zh`xjpBM+2MdvxemOLuR)g4~c-hRl{AQ5dA`J#QYu93>Q(fU0Q(CReE0{vr}zMYPC^ zCFRU0LW*l?CtVeIzhsNgDRfP8 zSbf*1f3FnWed297hRT(d7~}Fk-kbzqb0I&LQy8kNd2LLyN3)5QngiL z3k^aIztv^pxU9)5eU%bQL*<`D)*xYb&Uf~%GB*Hq@rDreI3T2PN3wv4|_4?KE$ry^2bom zgC1Z--!z2DdJOO64hmKy`!wPiSmTdo`uf3N8rv!1xVZ;L0lVqx-3&KPq z{1}o~r3;60x#q1;u4)qhe>- zlHf_BzpiHoTud4yO&Tg2u=t-~xo7{y<;bA5^n`T5nPIl~bmkZIhC?k#f-OAyMy3pM z#WMi?ub=z<-LA><0{-yGE#^xXd7o)iXJI#1 zNnHS8_V9rbtYHCIJ?V(Mpj@38|9JZ%i!a}cF;rHGQ0#}X8$_v4U*D_G9VzHKiM0IR zwuGFOqu~iFBvA1chLo7xQak3RLr9UR$Yhn;ce;Mad2(p`V5jR4dM$#592>^9?Sy=i zyN*`aO2pOjaGm+vXKt}TEO1a9i)6Bb+U;#E@J+eBCwlw}ct^q&m z(Aj5!>`{vudqlZ$ztz<6`2)snJfQEW_Rj0}xV$@msikWx^o&yIvK-LCtA^Z=uCvbr;O`Cgm4tT zmOU!wnXF!i^Z;+3kgPy=U@#cl*pMV^nXw`|UOZ{bYLTQAHyN+kkr1)K?kqY=gY8#S67MiAjSwk+fLr; zP?xn`Miv*!6w0TLV*_tpODZG$J}KRem0;7hSP>e=?_*<8H7XvLFw=t-`J8e(=Mrum zs^6q}n2FyP(@tf&)-;XcRe4L7<%*b1Woa(-RehUjXq*E9^ypFh9FBA>CN1hx!xix* z4!P85(qXHVef@I(zM_4D@Z?7RH+G6H?)V+~Co|5*Fm|$(b73XT&lgGp$eQ6P2(5vG0IAU5y&9eu4zCBU)R5dY>%t!wH2aZnS7 zK6Dp-b#Ll)PSO>wRRZ_#{Z{?QZSz1CNu3O(Mo^NFRC%7L1=k)Fi-N4smga( z0*&lD=y%!GFPmFdC1n!zarB9pvugjkT9-@w9O9N|S@EzC$pE`6h)6$Uq11>Fws?3u zYGc0iF~Yv+8Al*kSxcKO$wAhQC8-k7l+SO`z&7CEH=h^_r=g+2uyC{nY4Lol4JVuL zM8K@=+_PM;s7QNsk5_d31YByNalI*5&W}81C%8|te|uP*5?6ZbzH%l}rfPO9#9xb>~G=Yo_$qY6b0B810xf?Qdsm^&VtEyKikRfsj1;FIHxt2O_feU+8|Fw1%9jo5&!ia(8#KV7R>{yT^ zK&vkwZbOS-x|8XsKu^eAsxuN}{X;SSd4x4iNpt!! zaR3Vyr&Lqc_yy(2u9ki6{6N;>EO5iZrlAIF3S;`zWMu%qm|qR;%HC7jtcEq((NjB1 zAG%9F?H_WPx~N5%@pAavKvI!tn-H~o{nel>0!P8*Da51dv~Sohv;xWxJHk0{r?lCn zaiX?(X4KakR+j^G`MZ>{0}__y!y@ZTVuP5=Ixwk4lj_iB+EI%|2;cy)jAQmuFx zV8bx7Z5i9tU!TPOvq^-Fsp(;2efY>b&G}|rxpL*?#iHCTyDFr6=7iEWokFw%xVQ?& zUG3J(5vL4CP>$R=QVB z=B;_`asv*emvkEP+m-fv2pXkF>Bm0jjqh$;;e`FZ|2!vIjfQ_S>}8+twpln$!V9xE zDS+QiiBy>q{yI+ZK)s778!8>zKNt(PZ^vGaS8aHtQ<5sbfRZ|zzk0|Up=2~aD*89W zcGZi(*B?EXHKNwgm#@t-ofY{fWeTtd$sb$cFY6n{Q^w37wI6O`1woFs{==!zSRF$x zU2e?Z=Q`Ny;v-P-nrtr?L73RZ3rts(?VQb`B7wB_a>94 z-j`jUo1XYnURSHvf|j^91vxT9sY2DB%tg^}oXhv$v&fG`l%&43$l&{hWDD{}TN7F< zoa2n#2V5+P;pnP{nwlkPM(15Vr&FCNW*iDWi?A??QoXKqv1Q3p6^Lq0@df=7y=BOr zM*3@^3k@T)$?|KIw&@k+KLPvTzZPR`Q!2z91OSi=0{~F|uK~M@t(dX%|8FpA?5N{w zVthKdCn=^Xit5&LpwTpQ+9?Dt5x3B4;=d>0mE%E1G2124uQgIlIn)pOd_@WwyIn+~ zu#j7i;(ArHf1>of?C$LHEW5aKQyk!!SzfiDXE79zJ$2q9l<6S2gqjU;Lh&btPq}Md&k`($#WvnP1lhUGXiP+!X{Ex|RG+jAnGK5Z*@v zBX-Wmm+VT3{!t}ccf{oZUT!)@DC(3fRjMRmeN6H7BLvMDjFva0epT3@go&`sk?hnH z_L4O#1KrJZE+w{EK&N^`X*vao&chbTd{NAwrl~HC(Jr<{7an65zBOm1cGvt z?FKE14GT)?i@&k4!_Ek<3EJVn6e7=Py^qFl>82tO-s+kiq|orKzc5f!pDW(vQt3f4HyRzjgcd?6Sa6HsYNz0{*%)+AbjaPQ%+j{~$Apo3>MJO)btA}jGg=)NCMs^^Hi=S}oMy@Q9@ZD+ z&rG`}ZJJ@n+GeU5PcMQ4jkGF00r{PF6Kgjo?F3IWT!?a~mx=a!e-q9??Q9nKkpJ zHIWb__N)I(RLLULVUWY65vMbv+fg`5d%zD})!)AhJJehC(I-K(aWnkF@Ec#ponTnR zn{UiVt}$16zVkC|k|WROMvzaYcMx`m8{RG-<AzNB{sA4gjF~ zKSsiTj@*7}Y-nvrVQd{BG@8GtIZy@~!IE-1M{O+l$Sj1+4KXrdbjBB_X{@U2iS|eU zWj)XQilY4Gwj??Q`H*n=ZA8kO@15ob*v#d zc&!Yf&CDPgKuC1+g~jjV*ulO##M_zXQ#>ZixlHADd)&^K4%Lz~jqlx+_RTgHYvW$9 zbT~=TK!2qyLVYhg0Z!-C`SRn)T6?ZaNVwl#reVel9xhP2)Y%|VkGF?k`!`>WHn+S- z(2^Zb?Ytv5qHP$=cnth_qZOCJ6ZD|nvA)5b3;z9GiN!A^AoGf+S7%$3CJ52#*+`0E zYWiO=0mG8j47xcq5)csenZwiq|x7+RY{f2b{p z5y@PG!|}bcAkrL(GaV0$wQjEgwyCYs7%z=efvjea?-QKMd5i5Ds?ZicirB09Vu6+3=LTF5phNTX-Y%So!tX<;g zh`4w#EMXYtggV}LOfko(AuDJOPa-DF}OlQ}XMG zDTOl>e_C*BAj+=-adOoK@O_jl3~ z_61jElUfhf17H%xlpc5zD(Pmz3ABUUt|B;ei0!`Lnm?;-B}z{KkatMskup?0VMl=7 zVjOYr`}#C^CKwbC=M!r;cY#KUL`6tBat@DdrV zwlV~qE29^V)H&OCl#s`JCz}qTWo?6Ha6w?TY(IQ#8C{s?8>(s4K$1h?VFb8o-`9U; z$$wpvP@~Hga}xjnNPYnTWdGYNG52)+@4ZEfAB?ZY=0`kj z^*HBiYO_;Pe*CT5GRviGHiO+#DNE=5C{4&IlGQ!M?MJ}B;GS?dkM-(S{f}Y53iu|> z4PU^@TQv9Xn^XX|R}A;T8&*K8cTDSxPT=h$AkYJ_8F#@y?UKjmF>}ZfIF2~NGTb@$ z308OOV1}F#O{eP2W7IY3i7L1gbs=6Z7<4fRhyw_~-{6e+5mY>Zb?unZvp58@QW^GZ z{DA-v577{d{TT#EC-{L^!~uLUc4Y&~0I|k%ainepmm>+80BGuWsRd7ZHDc*uYLVO| z8NrxFW$zeuFwnp=23sEVGh&}5#K=kXtZ6heKp^;Wmh3??fEmmecb6V8TQI5()&qUx zR(Bf%*f8=%&-n2|`(W?-g{-zCy5<3KVL9>?{~^>U%mP)jZu>rG@`o4&`XeEUUixa( z>KVQGC;ZgU(4|?hpI|rgWtS$0KcQ}H19kxK%N0}7R~|U0Pf7_fgO_ zzC5r`@Eh%bFQ{kyz8$bnG?q{6cY34ujc1e*-+tS!Q_DEKGO!uQ)vXZ<8pUBthzmCo zslZ!cA1^r7?PL+vQ`1TYv>KHfcwb?ILR{Wf%|w^g#ox?NfR9M5G4y*fUyPQQ)o{d# z0>j`GH8cxCJe@@4m@O8J)hRGJ89W&b5=;!R2q=Y_s@G8p&&0$j2Oek@m_YX_1lR=N z1?T{pz>f1r^*M6GF3bUcYg}H5lbJ=LU?*%7Y7o*9roGLu!St^<>tO3=D=`huEb~g- zRQ}L$#E{sMb*87nlL_WGGDBDatROvk`+fryPeG6sB|`&rLDB{v*0FnKlgP-QyXVZ(pW7qu!k2f`hAupPQPviQ)fP%5 zZow-Aa5t!t;+38l6@D_8qu*fS`NG|{f|hDee0PmrEDw~BRMKLS?J$x#jKpxRJszQf#j2XGthbCW$#E%mI;o=>*|x-fU` z0Y6aEuUW2PJgL{a5h`Hfy}A2HcYScmdtfSH61I>G5blGrxriY= z31GWW`2YR{AAvaK42?tyNG5jm#-$0W<`Ry^lAb?@GeXp{|B10i4VDWY@NW^?k-&(+ z2z(b(1h{(u$P?K~0w|lRcr6RTIl}L%1OEHN022yx&l~OfE&}ho;mc+8bN(IJh`M&H zt@20^|3x=J(l}j5{)QCr<8|uC%hZpzsq5#dYwzi6sAmJ{6u-QlV-%Jv6u;v*-{UyH zV`blCWz|>qycd_e7lgbQdBk`6fbT_nKUi*lupV3!kc}g~2wD(|Q9{M6CVW8j z5rdKVO$(DhUHd=XBTVB1wksL1DD)SxCkbE)W;npL zkAWc71dnXAYqr?FPND!<|G*FVl?>Bj2p|q1CJE^AI=-Yhw8|NHjP#wksGZ;HG#y-# zg7JYi$-d>xetCNW4Z%p8z)10+lh4;eVzxS^3Zp7UH(x~|#oAr%$SeN!_IVt;aBB$8 zesO;yUgZE1mI@Kcfsh|nqH>?DRz#}J^Lu?zVj6`Pl} zZBiJO%&eLJ zEt_iY?JeWxO8mA2+~LPizKF-_wtC( zs|T^&wQ}?jwCP~BWGNW|1U?0pIY>WfT+S*$(I!}pd@0u>q`FidqX)-?WLa>5m{Or3G)oe-x>1lx9sm;SPJnwT)c{!cIEGFMyAmxx_}?rhG^GRU+aqu3{` zNZjvZD?;-8V5mf1t(zO=>00q2R^=K5LU7%YX<96VHjy9uB-@CaJC8+~KjZ3@j=;b3 zF%ciUb>JJwDsjA--dPhnxGXNhJB>gZ^%ku)HVZB4t}-F#z{^3&?=n4!g>rrWOX1c! z9}#h#HWb*rwk0gmU5wgr+*%{bA4MUvp}S9HGJp~H@5SBccnx|{*N*q&GFLAYhlLml zgX7&lP3Xa(WpS2w<&mo9!qMaIXhrbEZvSI3~I7) z;(q!<)UClv7T&UWBMuq+T+EpEsp~Y#C5NehoY%-Wh&OXgH9-#GbQA2R(>*9lf33x?#EX1 zknsRzX1KM$D-3$W9$HKWwv2rBQMr!^3_F}!&_coU+$msQnJSd0N`&;)gKm<-WlZF| zKy5X$*g#RGRg3uiXR>}n=$Hy=h%aqKa$SS#z3!T$-4EaI40; ztVIsz#Vcwfqh2RdIKws&WKy>xCgGt1#dXZi@H$h+e5|$LxxNe|e#`ummVv`UqPGt# z8Il?7hQBb1h+vgcD1iel9nCZu<^?x}i;;Wjrhwvs5*_z4X0j;!3IF)BH))UKl;LPL zM~ZC=A|+fgLt(%U{l>_gF`j!YT6$O4$Q<=N8R3VQh@R(9j%;}8(|5|gT*6Kd>ONjd zhw@6)OhM7aig1ynP>v-m92VnD zw1>0GJUi{ZaG`Q$o6UZ%C%4)s)$;Onb7KEV|7n}$A1G}4*AlE}OxW@-GwFl*C!*!l z@%N+eO)s<0C#P+e%odBFX%J26NFn3{J9FD=tRjd8^B0H)T~se*h(k|7nu}q=UNvys zB74?E>ty5Df8QS@SyFx%i3>`%#@Hxa|7H<|H=r}M@83LZ$2yATqROhQZxQt}#sv6@ zrfM6-XNC>F6eS62Bk-#e*`c35pfin_7LK|w;qc0^s+TPCY-g;Ms)RFw^PjS~(39W+E8tSP+BC2>M2;Yafie zd0h5_Th}&%RRtBZLAAWP&zBkNPh4edz<9YQVt z*@Od<1UDzQ;*KnMjQ9ZY1l%H<#OkRyiERGCtZN*u8n!od>SVk^^;i!z_jImTFYRg= zwiTJ|tT*pv+dsrNe6EhYP^Btkyn<$W3iq+U)y?-I4)r4JI|tha-2D4Ph~VDXXO!vI zjL$Z7R4uq#Jp*}Wd3G{xlU}tB^AC60-2#rixRoYJomR1~1J&8i_aBAQ|A^{!VY_<6 z$`JQgc;c){4;6tt6$Gv@ubYP7hSTj2(b>aM!cl-O2RyzqzN=W$!|0&r@aJ;ITny#4 zeVmbJoKt`O=nbhrRV)0BIZyEC98_67-%Z%H5svy<>hdgA<7A&(pA?qmheRa@t zGl_wrkEmKT_2Gf_&~x;2HKPy=(M#8J5MwUd?D~FG-@ZKH`-of**=d^}WOm>=h?b3}8ga2;z0LP&BAe2EJSpBdtN^!onWkm^3n zw>^~MIJ;ZZTl9@FcDt-ESiLv(xkJnS6Rt1HuV2&tN+R!K?+aMHh3vNaNOA%(aQ=#F z{oiaDI#>4zbLJQn74_bslipR{m$7=q|2#>tRs_obraOHR26+|_u{2Pb! z`W5}bygunZSFrpCo!;;XxeweAkoDdPTfPDuU#P9#na#c!KMydOSEUgD8Hw`#VX&O{ z!6=lH>|JGJ{xQEJiH4$+_Ny5U!6Q$sen#nRa_PbmPUgpe6oAmFB!Bd<0!FPG7@02< zk05T)6Sev#niw}GdLb2Btl3Z05ScgV>S6Q9s#ZaAEFKk_7J8xqs1bg)coU_1#v63h z4)t^ZI18+QyB7y0(Z#;a_BJafbWimDO<)dZHV#aEEds74?a|0+{ zCXk_p9+Fm(eK)~c6<8)96~F-X4Z5CS7ZX4a&;sXxXa=Nd0%`y&fNJPAtSD=Uy%iF- zoZk-k8?4v5wmJ3}B9()>*DYMD%Tl&U_7`l0pq31)QU3vd8$G@18qmC?xczunnvbj+v2OvYjC^?a^=+8oUuqA|_I z=v9Oxe&~y@X;Gi)+hU6>{b{Ums2T;C3|%feTzqFPU$K1H7-69pB|4`H5Cn0Glr5~w zQw6bF4cQ;S1DTfdFQ1|Jhkcg=C6=Qc z*DF{%fM<;TmUz=0@!z_mIpx^-`U)Q&A(s5?*vu$sS+p;EXp)s}xpI;6GUZXBNv$6} zwJuu|Ej3kq8@hbUZlLDY+D(}vIT)O-Ia#h|XDbhs(WlBKBMc{pJChA}u0gl*sd+v$ ze2I6{yu0}gGnP6I+eX{<tG#gQ{;jbvy|r_ml;622R}u(6X#4@x1ckw_@wZNKK#W2m*) zu@zmfJi5+bY!Au)st?7JV^+aJdwG1AmAFg!c)JdnRuM6m63XzJOg&Kj6{)VN!RKyq zt0u>5qn-6t%(c$YfMMzkJDO3LtzKKnJH7rxd`isAQ=;yg-|OL6hyJS_nr9pfZroUc zq=l~~5xi`PRK5W}v2R>6rqnVuP>s&8U&qTr^VpfbRO^b`rP`&=6%vm33}SZd7r{F6 z@VK6Exu>GnYoVBl)+-}Vr6T-etO|hrjCoiTLIv%^!ZnYjjbo>L(@j=k#k>byFy=A&ttY zZ-V=fy0psVjW|JQu|meFTVf8hA>=EHQKRbRZR1)8SJMBUxv!N-icZj9i&W_>%9n-- zGwsY->>9Zhl$L&l!Kxu3iX|FyDhOr|pyJOuBEl|S{b7$$lrfac*9^tE~GIbSb#k`G!T4+K7!cRMH zb0&yDK^qx_pEvO0t2}YEsM=*+hybOAc&H|d0v9pumo;^oMiBJ zFgRnbq|?z(T|wSblL&8rhlp80UjT-;fICv;OV-anzshFP2OeR&NWM_gHR5a28RM*~vHFmGtzr4%dVr@fQ{sb=v9V7DSN0bjNNQ5O_(* z>yWS;&&Mc^N5}&!xl~sxQS9zYg`$+;FQ=k#t?6w`!{mjql|yTxxWyo?g3`8f0o0Qm zOxz|q;Zu)g-ocaodA4(&t831@E2U~kW20;u#^*LYFQFi9EvBA^j70_IynWDY*4r+L4jUG;=sE&y0cl zaoO!UDy!e$8}FV`q*~>@s7P*4+n$=VlvPQ>%BJwRiXqRm#Xm)6`;}iEl0V;?YkM8B&-@)8KCpQQ?5l$_unv8bg!>4n%&o20jO)zb5o#DG|aeE;X;iKfDVWTWx z8N6$7pekdOc8U6^IeFOb;9bmI;Y*rhLQcOprg~WNAytL5ilG!r>2|cW@BqZbkjH_U z{7Xzi7pcXkBbe2^KDp0VD`2ZElh3u zz3rsiYh)fX2VK+|sg#21^Z8;cy*MOch_lQ_a&kh59h$6o#ZBk=$YE<4DC!n%(?GNO z0(&yjhxkRI?|FJ*n19*yA>s}!l#smI8`TAM#xU~)i!Mjnuq=c8@K%tvL7j5ez@pUY+vV8xA z`I;g!N*55pi8;WQiAPL$MOCn}ftfPu@|zCx_k^}$zMibAe5;tI!s4iG%$)S-((St& zHXlCu;BBzyt90?%rIzs#yx9%Utnu^52W*?k<>@h3M?&;A zFE$cLwk)DLoQv|M()pL)0@E&Ps4A10p;Wnx_cErq@>l(039RyBcC~k^M(Ww(RV<3M z=B8=P0QhXGtH?xEo@Qlh6(hv$&}se8_Z}%eI$eW#t6 z-V9?utF0`B0`JLx=HNuSAGGU%b9Vmfb=zVkE^8RGj$-t9w4KUKGJduH{L4}zvy;m_ zj}Y1%^0ts8ETHxjc2_Gg=rs+bpJPjFmT>d`TM=adf1Vg2?xUD)Y2_3Lizn9Br_z z+=kBIjl-P5<_q1kJ?YM+qs|T>k~uuV7Wn++#f+TD%l#~a8AElAy`}uW%5gD^(xPMr zHl)&TC}zCm0=}dEj$y~ZXwr$(SEqnT(#Z1I(CL(tk zk&E19#`)g!yiRu>7JsyPl)s{&8FI$7Hwy^}8*{$REu?O~-5D}P*d!~E( zIKL_U#>aa^U+qhddVXJZPxMf3`0799O#2J!mwbiNML~KcA{_8V;5}1*lmOpZ_`YXe z84jOQfZi;RpXlFgeLD)*$P`$9svg>f&CFvj&}JeLJ_nOgsRuG59|w~WSO=3occT*( zq6vfljvKj+*oLnDYe1g<=Zw~WayC%=j&L@Rj%aps4#NN{B`BuIXshYiXRCFZST@~` znbd@@vJ-l&Kj{06%rcERm=`AePmX#{qNWV^J9n2)yMsON)cI@Uoq`30JnQ5)>k2yy zADYRJ^fV2SACfS$iURi0D+?>hEYgF7C6?YNA}Iw#RYhs30Qz={JNqfqihs{3MyP=b z>ofvddN!bz3{Bix*Ifstk%c*)(vjnlpxwMQuRp&Fgr_~w8&37jpemiT=6tjcS+xpv z4+|89HI;tRmNgp%sJ;Oztum)_H{rfNL(>?HWNXt#!^tX!p?W_g$i}RO zHTg%A*I>vc*kt)TL|J;svUyk*_6DqvX2dKX#AXAeAj? zaR)AW1x+48S~6e|8Z+<={wh#qi)s)vHzp&+oFih0oh8us&193UC8v(ro-&)P$@C87 z4851KnF<&3P&Zp%jZ4ch84}yJOtudiFMc*tOG2;AJr_pMpjLi@o#Y@DT=)$C)+7W> zq!GuCxd_`*!A3_&;3kqoZ^OAcd4xxQ(? z^|wxGP;im(SS+S$N_~@?XQj105~^19yaTFqaH-SvIQr6+O&GCfN)C5kb{;`wUDOyS zHlu!!h^liMD?Ep#8qllsSdDXj?|!?CD27e)5t6(Lk}0Xgq2~DC`Rktt^m9SJAo>d- z-u_A!KQy^aj{4z2UuS)x)r2y|wEOVU-mbiV*6X%#Dp7B(U_iEZ&a~_jrTgA!k1sde zPi76!w@Np$cA0a1q0sa$Fqw#>LIMYuY-R^Y{V+S0ub=4p7=obTo|>V;v-I_(%W1X5 zWz%<01rOgutvq1NVkBE8G}|u&*bF?O+8FaRtu{6{+hy8T7lk)RHB>DYy9L-rE)tk) zkE~%N@=N32@-=nB_?}fR^3ld-q=dQp2BM>>#?}X)BB4E=lX{&K8V0vhS@6lZ0Fkq< zF?=pJTgp7V0Z8)N8?x#C;$zdHX+5p&4pE*F6|l{$nh4-I0Q2fm*3U2Ir*X22&1AjW z32KS@Y6$sRX9>%EiCLDuc0gt5>GmNTu8$C*q5JPuYa?hOec#!Z3R1dkLsLUXnp5x0 zHNy$G$Xxq747oD0NVeCd(hl68i?m=0{_+Wr+be&8wE~v{=uh)*+gAlcyxGCV`s^{j z8^fy{wHa%$j<9cvMA-Rb1^jxCcWLlnxjD6jBKNf%m(uloatUNhg2sZ!ym^0gGJ5z= zcgUG!>$~y<6X4q~9y%@_N|r4vwheh~0w1rR7QD8zgRS)0%Y8SL;9fvrL)J7Mb_-K- zvP^!&2+)IZ<&f%`EdQ&eOI`$8^mcuaQ+=kK3i!nw3acf}s(#RQVC*zZUL2jg1T5Ix zMcPbrxv{~TxWs*Tm41=#v5&FOWhODiy+W)FFIn#T=PiKry)MJ4>8W{Ykdv4A8o-pO z;u&?F1>d2yT-mYXb_&@a71%gN7j~%Ei3c8;1g{4}#4b{+#Vrm`xqFAV&zQomoiN@4 z%YRoY)Y~tC;Zj^@^{W|Qa`m4@VjdO-O-Np#K|(*)vAT14+$7-I2lF8aC@KK~@)0?4 z48EF!()G~}kweuF5y6J%+&CoLc!}0NoUjEl2ugHAlJ$kFrCH!U26&gXm^5*)5K~W} z)|^uj6l<)0=}Fnf^pljdTYType(A1R1@s)I_`|Gl;wbeOlApT_Jys!&ZtULa`LEqY z|1emxwJ#ub zAt7`)&u>3-qWVueL@!2_gf0lsl*_1m^u*ippq74*)Ykw4Wq*zgEz=EU!UH4q4XL}vN{&$#QE9mg1`A{dxH(?34e}1_)OqqKu*6U zx;Q_Dgk5F=uC}5kIvVAwuWa%f2*rx^Awz%S!Mj}jN_@R{mzHiqSKb`M)e(v*17^?% zMe@fmlN#Q9{Qzzf~bT zUUO-ZP)Hw7I`A~}2;clUw!wR3)n)Blj|;DtXYPADan8&67v4@%^g+iLgTo}(o#Ye9 z-07C={5<=Br=*)j20?}b|7DDC?4%La*;5gl%u-Yxq|$|gN~*z)CCPTL58^fG43X>i zuQt`ju4|Fpjd6=qPj$l;D=WaA+wb{tcu5!8DeKKzsp48nb5c{M{5W&iCjflq@gD0+L zxpNM=Z;vrlqFvIpB>#}EdK-+sC5C#}`uoh0Xq#z6g?Unf9xd;&sa zL#@?!HO}R;{4~j@FB$0sll4ZTXe=L<}K1$vdP7dNYW}& zY$oyHuUH$hklhK*Vi=yKMc;OaXSqEPeDK>+w(o57U)r(tXF>_$d|JWrL(rx}0ZsL+ zr)#QfzsJV2hqMbdpNq=J2~@X^0jkysbip+*d9{xww(TGdzRP42 zloLfU_wx;t1D>*XmS$bOpq}I+AwDVH6(LCmCZXi(m5h)+TvTcE&DI-*-_%s__rbCA|ns_UAvF28%JYS(N#<{89_$@x(Ey(8~h|R zRh5DcszSBNgu-slHY8J$snf(`i!EL%%G@Wq&m!L%VIbwU+g&)xHv16Oz!!^GtmG)u z@Lt8Z8X81>W5}mvl5%})Y7Eum+&7`lv_KMivD$~qLqQ9xnEPVWB8KI`g3?^1@MKlu zlbg)GP6bqTf&h|>vUrEE;aSr!;eJ>p)y3{07WA1$FlTZpO^(?jmlqi3B>e4!TX}TC z;j{?aTyk({qs&>XNIMQeRbzg|tg6>K8JsY{t~*%}SDx>jjXx$Ei7loNn)ZaAGL~@< z)y|WxS$Fv%Y%LI*C(6D|8C!fH$7>rD?Zmh465T;Bo?&oU<1?-)9K3P<+=w4Xzax?3 zOf;}5T`$i-o~$G7zbpxvm3cM8c}Xo-Ka+s*p#z{KqcOnu@1Cwi^p0s@AX{VUu$VRX z#_5Drox@aIB$~oL#l&Jh(M&JUDx%egwSJYJM*)i?2tdaeSxfoRogW1}DU3HUBx&Fu z5UIzy8{;RDEs{RvP{@nz9yp7vUaTG;x9ak$U+wYQnP*z2h}Wrz4?=D&tqzG>j0pfj z$c!AfoEPRvHe2H3bFqnqA>l=-#API`GbD)JQ)tp&A^Vk=A>)z^BGIfR54=zehFJqH zt$WXCf|Y3~0%X?;P@MdbQRFV-M!CGF$h3pj%&}M=FNLO<{P^C11vKrZD!6Rzl7al& zY-dmq=mGiyl>sr5Pth*?l*5J4QJUAR>gP=5^CPo-J)SBRLs4HsMr&(ah(|5ii&WE& zmKr6YImwO@gNjZ5JK0V~$2j*cGfI55n0(UR2XMY=JTB;|&{(5+&_3$;S2tWi={GS+ zveG>)>!9w9i`;UxH-t-+EJlmx=jP$44$K0X$m}f1T+;0bCyLyX)0$*sn$p4$5dq!L z2ef>$^OzV9!5kvrzf{imA)ou`*P3jibT60{`9YNs^Jy*NuHb0}mXGZ6S|iQ0P92_^ z;rOx2CU+Uo%{L?{?a`~+rqMZOE=2iKy18uE=8s6_R&x~>(}>@c8O+Z$B+`QAARx1? zFvbtI`G?z`@WW&6I##(imDhJ=wN(P;t7&9anpQ};9X2Q5W&0=JTWxh*uUH^|rL z*p{66`2~FLOSUYjwlBG*!~0L1E1m7D^AG!&9UV+6?|Y+IYqlUcDkz4xnkRfZX$Dub zNN#nSo%KgZ)k!lf5Lam$WSXLf8PBmsMyH%vX3vt`Z@a|M%>G-MiJF>#x^A6I;o}Bp z|KsYQp}A~w@@?gbt`#ygNn;eO&ttVEts01@>%U+kDc7!?w)u*j4`Ax^lO9ro*&}Kw zmao_B_fMV@{4hV@$5-Z@@&1m`&KWZsBDxF@jH2dAaEbR64>i7gX{vH4IvG;cXuKTwjj+7EI=}9WK z7BLUrJ!M(Sg~U6M?A^JTc;8%Xk4*b4*wUhNbBKNe?^QbCbIEY7VoMF*3tW~}WACiI8YCz^eGy;ZV_4^I~-leRLPWHQ>aTO^|(>dUCs!o)T z_#zx|dBgfFkltx%KgIMUZxrn>BuZfBJg|LmxkH%zTh)-$(wR}MFV$o+q8D{>YltQ% z4Nxf}Q(w_F?37wp<8b%Ro+UT=Md9q3@CiwS2VmeI)5}fMw*dx?4Ra66Ei9TF$jSO? zoYP@H13n{$oKD{wRB!zwisfrlh9&Tb9OcprfM%Rm{0`ZqrbUwE!UU#FFE{?9UGj8K zC5-x$J`C}X44uO;e(BH~+~T5P;;_HKs&^d-PmX2#%FvMqG|u8uTIQLCmmEqVAg6%> zdzTJ*%U|_zA~Q9+E#oq?!YT}xWzbrj(Rlr59F5Qfo*>|e-jtlyz+m5*@jH^?7{_CL z^XxQkukkjxTPKz=(FASyul)Ux-bn+Z(-JKsC0tK+29zsIVv5x);ki)CSd!X3HzKDpJyAid}laBEqepdB! zmF+rt50{Z-TOEu#r#h5oI@^VDu$Tdm^XyjasHP{YkQxN7dQkWVp{*qknka7OtH|rs zs@HB!3vTGMEp#hxTqE&t$S>8Qntr2^q+|CINS@ZLdtJ3Mg3qi8C&BmGrnGszwjkIp zFdKV5*fq6d)Z-i%v9k(ESvE%KK5z924|?ybZboZxO8@<=Lu+b|Qme^)0BT+PZVT zBHGD?NuFOUBI-V|_W{s)r+5>;z=C7$^M7By0XjY2q4hwMbe*G8wgHExh8LWOG)&zx zu&TJAPh^OS3zkYy_y-GRmZ^md3<8r<37w!~k4$>hVSMGetS?cE%r<7pGWDi<2k8qo zKZrZlTh&JXdC-4(L#49C2iKMA?4PZs{-_h*U`N$hBwHlw^WWFrIZBKS3^N6(;}ne* z2R6FopMl6YTy=G~jKxZ?))Mv^_O?8;f+4jFzuDFwX7Ry~U+lASu1oS))DeL_erzcS zCCu2-!Jw1B;sMuirQ*^fRj8Hjm0Ac*op2`{-jU(JB}es^Ij6#*f_^WAsYvn*$b3`X z_-Fbmn!SLUynrT!kIImxV$z*KCqHTY>`TzCAE^#;TH{Q8=A?x_9%EJINQ|v(>jOfj z?TFO#mTFxIH~5rj{*0(og9D{uC6cj7xpX0XVeLI=k@mc@g;(vPX}i~*x9Rfexh0-{DfgH@({MS{GEDls@PvUU6;}3NE_Efo zjx6rl4P~ZgTn(ykyc*-8n9;%TCmjCiM`HhYm}qtqFCr0k7JVTn7vz)RidL#x^@IG5 z5$npl?_$hq6myM8MNfo0)+mbTwhNtb2kZ3QW=dt^Grn6olCT`ngDSrNw>9n?o;xZ)} zB2zSm^TB>p*9Jz=Dt?^=eq@Erv!meMW}fqLc%U+@n^@bj=bc+&Re{Jbues{ja>EKD zDc{0e{O7+m4K@c^8fGP!#CYKVdX3VA;CKfj4J5jtZ8{jt;GZ|f{y&+AK**J{(L<#R zkLkQZ@S!mQlXQRIbW*AZ_Sd9lmm(y)*((eYu0{q#mFkj@mkWya=9OD1Gt0%H@%teX zSJ!^jgEYmU#T4#GHbpSR)9@d{pGd;gD35msCiRmObf2wo~_u8|Nps9SKk-;Q$mbh6C&7>vQ{CdH(#PP>?4IYGjQMGt^r8DN-{)jT$zO+ zH21DSOX&w%kbKyfZrxA5m!K@^_vsmROlMa?>HJELjk7!G45{H4ae`N%$wZhoZNAzh zaza0iA6plC8YMnxO-lLVCa%C=oazPXRx!%b=bjMjKEOV8R$rS5Go_N(iAgs;L+3*d zBWku5UBA~j*)ONb$x)@iv*f7SBfZ?h`DiQyAP&x1u#XSi*`E~dp=)H|t@6%uCGS+$ zJaebfm3QajYBw@dP%(WgVbxK2YNc>SP;KP~1kD@#YG-dKqOqBI%Ej7=E~he`8gQZQ~-f1;*v^BeY4P}H_wbcsri zyFF`4yj(D}2ezv%*pj36#%ai}M(C}QPLA!IeYbikmBZN>7S7he*8r`71-{|zGR*!gIS~2e^xZy_S&KZnl{vPU14zYcC9_x4i|0rSCOi zB3@=$mfmY|B$z#~Xg9A}wy7a8%-zRd+?9@&t}Bt>t{Xf@S>S{?#%!85bRHrCjk_H3 zz7{_;yZKgNfAZsv`56krP{n`tgP3yZV_iBx(yu%#g0r56sTlcWzCI2(8kf8~bPA1p zRIsESa1yFAFuq#MJ*-1_GZ_DP)x$Y}c)n_75t~Y6x)n*g;koH>JV?6kxI_1f8hyPR zO)(}`jl(U#-C8+FsV#T7X5!X`v0K`xC-EJC#sBFhz-1u!XQQp8%9YhL%a2A+JLQvz zi8sZ~G6i|bSwsOsE_N|{_kE0sr_w)6IpYwLxl|N_@hik=%f4)T%X2;@LkO1pC2D~A zgXq;po<@2O|aK~yXW?&?(vQClzPRV&yREIq^;=7s_(N-2g(6E zMsd435_eeoZe;0Cn-Xb{o{`~B#ttuB*huO$lMJW}>a zncq{E_dQ472OnN2OWe8A;lp2!uP@ln^C>-5VeQ>r)B?;bGR*@#N*@k?j;3n$jHHXC zrD81QS+E&SZt0ZM+3RIi1o?Ywg(6uhdqkQxvnq<}|C(Glo8)^tM-Sa%)>%_*=J`$q zf0=nEi3Z$$F@H^y`R8)&dwm2{p1IGc^w5x;JS$isqs#QN>-}CJ3bG$ROO%G6tKIO# zoECoT|5r>MSM2v~^nl~rK854Eds0NGc5CVlt=C9X~i6w#v>q&X|MFREwFT!fDC7spwaC7EXQ&}AsyOy;gWa4ep6Ldn= z4)BT&NRLR2a*IatL`4T$kdieaAhMj#)Y1zsg;}crxCQSOaLSXwwlGm5S^KBhysp!w zUUiR&e)S|N1z&c)urcj3PLt5_C3wC6VMo7S2pE4*|FV=C30?V7WxmSZ7D3zVM*Hcv7j#tYtUu_o;V`q|^)>ytd?p9@@#M@%;!q$Fw;_OSkM%>-e4Br6Ja6 zKE5@Y$Qh4&n0N4&S6S`5rtAE{T8`jOZhTqlf@NeWe!b6OqP*$O3HmXhne|@JbMnFm zFtHyQ=Ki+7A*)`)oCTKjoP%n8lBI`fWLg5=_M*D>;NE-j*1vs!ZCuaxL?t3tN?yN{ zijJqMyc65=lQ4&<_(XlmNd=L;ll$q!oA8y*NvBbTnSD+t9(rDNrk-rPT*o0Y3T?Z@rucg0b|pH-<8y|rlcM*))(|M z%oY2k(8z5^i@@=6@wJBtsaLSh`Y&X&ovGI?2tuA`AJOwH@Vp}dX?9ru_+ZOT?I`WX z*7v4A-E~i#{{LM0PQ$g)L)dWVSEZwRXZ!j58|Wi4ypi`NDFhcRPNLXKW?#5nQFX?MF`m#ic-#z#N z-p={VtE$-AR<1pS22GU^QgueM{ELZ0wPdce1=sylk@5jIsU?0$5sug^6`ZDmzzoBG zKKw1RmMGAWr7Yi(9~ndS-~)^x@pOewdI7Yam=--=6;bN21N}r)Qj(0$K%Mdkw!eYQ zaNtnL4mR@O;~=yUyY(80V)*yw6!l4QqNkLe5pJgsO01|)NgC8%(L|QrDq9}>a+X|U z>*8yctswsH5!|R$GvZnF;8(o53}7z_(LQLa`kgM%8!$L6CNg5 zjfPhrs7VCP=duZd*V0hyWqM~mi*$06tX)dw`#2|Y%klIIY3OgI{RhPNk${}p*w|cC8cf+Ml90K)!ye`!A|2uKIj|6`)q+uQt)iISxW?W3}^BtUs@wmo?Wo)DM&8wE4~iz1we zW)X}kXbR+q6bVLUG$2i!nR&911(m&`DQS6q$f~F**R?OWd{zujGBLfqG0&<(?O)kj zd(Be5>IMz?>(@y)2Me_1A9?@7?v70FYt65dP3IADfsUHcFu9YTdhO|77e&@*s2slM zeY=0;z5zEvSqNj#EDUEZSSB*_jK`eP=AnV9o6NJFmnN8{8Yy|&UIdn%^Cmo}N+gJ0 zAsNn{yiuVOF4e4_{fs*Uq=#`8ZG!2-npo6bsD)gL8gINR0|i86m}9jX7*d_7xb)6N z%y{%uSeC;uW0Lc@lw?JE1T&PdTVQECqG}UC`e~JNF0GP<6^d5%`lsW;)%_DmKIIP- z&|4-9Im609hc1@WamO-QCFAbtr{CabS>%PP^<`REvMCK_5;a;9X5;6Wrmo4;4PA{v z)`z=+1P{ljyerZZZDOmF_qiPUU>Zc$_ofqT%<8mL!j>3g{lbt_Bq-I9MP&f9a8_y2 z9@F|O>3O=8Hm@}6TOe1bu-6GUmGsm3hpDD6wNiD$F)#8PtsZ|)56ys>W|v_4AGnM{ zXqbh#)&7in|;`=PBE33R?n)af__#Cma6*ae?Q&7WdAz+Liz=b zO<|gvsF(4uE;8@Rti!Ou?%_#lKmSUS)pK2J-sOU>PZz}yzs=EkXtMmORJ>`R&1)wH zxHs-=7O|bznQ)ok*XZ<2z>W&iMm;VZd^;?B+*4>slaa9s8zK=_o=VJuW0iDwMKUOtC=d258e~}o zFgX4(>1TTuoLqpj#Cqb#OjPX+9O)o{nd{Xcy3Bf}#wb|H8yP|cY$H2ZIthhOUeq;w zb7~*k<5)CuoP}uNIfV#}2JpYj%cH{~7|vB| z>XVp-jy-al{@Lm>S{Q*%<*r{83l14S zMD#$Ws{}>Xdb6mzW>j~qvZ{ZFvA|E*H0LR@J6bptvfsD-p>oD|NS#M(rwgJgZgtoW zaq!~zcJ&AZ1rwZbIhp+`wPzxYKcDH~U4a|k|7*gDA&W;iH_N^JqiSiN?dDvA*J538 zc^pFyEZH?qLlF`Z-u94Rh}AuJT%7Z+brr4!ahhnb*8CVm<~C!4faVf!DA@O)gQ*Ep zoj@me4!G)-vE!g=bDF^_#!9)uTFu14-aI@dlCRj7Gp5<>kk`6RT2Vo7+!haR)A0k- zsM&$l#7?7K%Y>)d1O*-3Rh%r^^iD(JWFqcItPG_L>No}gg@0fIEkZ!N6equmniOgweuJrRscnQ}uV1Z^5M)US&ls zn1F$K4=fL8jgm1D0UKE=9)!VSjWLnmtxbKKf3kv2f7*j7K5RJbxi|nKdviN1N`zYG zu3F=Vv0P}kKsgCL^*-C+A^bxb%xU<_t)wsDzkcZ(-afy)Lwx_qW`7A?w7X5JcYT;s zF6k54uK+O)tSKf4^p}yx@rk}&?-yWtDPeY}|DN2fav_b2`Am|e+CU3auZ{sr;I?OZ z;qKnv&AEq%*r7Vv8RWR=+{$%7;|sLU)5r0tsBMq7=Qw5ZTFkX|$?D^I!Gv&CN)Zw$ zsDj}8?a%&#AR+fWrx$pGhJT%4MtQ@0>gc6n&iIDjg8Kl%XE94^FzCKO58*r50pA_U z1LqY+z<&DNsC~i*;m)jaQ=k{v@v4_~!{Wj4y}tX74f7d7$oL}U6>B+Jr(<*byZ*rN ztE#UDpjR{clE^CGtK#x=%i@;*tE(@TQN=qZ+nfeF;*3{PVc)eBnEEp8?=ncSG8rRc zBm}M)_}%Nz`r_EVxl8#6-!CEeImhHbkC48Pa`b@h7dqBpNIWW57ZMG z@JCI+JjDRZ?(@eaU;s)(^w4E{gJ+zYTIzwL>&gqPys8H31Ny8V+WTsx?;&zU%RK?x z%%|jrFU6T(7(LO0Rs%m<&=2h;;N>`($*k7nv?p8pZf*tQbVhU*6A^C5a|bP#umX2X zU&0vJUvfHka<3dXdS(c*J{Z1cb6d{v0|f%)Ed;vkXCd?NMn3F`0{Gcq1U~eGGJ6kx z7uSu@T|z+Dbt{xY5AJb%4d!N?ngt3JWecSuP#r=*cYkrvr{Xm%PZ6k>8co)KF8D2nqL))0OESdeOQ~5AobM8nC)Lrm!;=$jCmv;K`4S zCblxU7@=>(&aS!i`M;#GT;uY&qZx?H2*4mdp?VehaFmYvOG#(s8V8p-Aa+&jGIUcJ zejqHDjr=WljyX{Up#oDTADvZ@P$iPU-xE2$+J&Y(76p$WUeZUF3(6RL_hNn&=CnK| z8o#~7CB;Q8H+m9m=p8nwbSOkG!FD-=bl%Yo1A!JZ^gEM=5wzXDP+8=O6hts z5kCh=YB8of<^~Y78sMl;YU}BDxImXK@TKsUgVk0GT31lVW{&q|BmXSEWG-Kqqr5vV z25Gc_+vm!xrTtrOED%*WvT7h;()JfRLtbbt7p;hf(=q$;H()6V^cQ`v=7=lTGQzm8 zON6&IZ}TrWSzOXO5N1=}X0_rx_E~-lFM56wkwgJij5@}7EAj2wXGMzZ#Tyj2J=WE# z4S_9|BzUJZxZ*n)TKh?a;6q3_9BK81CXvDe#2S)>?^_KeeYAt~NJvPnmc=})sC*4y zdsTgbI$oN&>s~4`g!$X5#}rI=RCmQ>ft|Y0#{EvZb`#p8cXhWo*iY%_IBQ7wmXOL2 z_VDT}qA8|@9ii(4JK!d~nM{wMw&1#9x*$T<_3W7(#rSkKc;jfpGCyxl{Vb_d^f-^l zzfvG6iD&MRqNl|4sRZM2xf zSNQ621>h*c4u@R7vKL2AV_->E)QWWyaV(Ojv=AU>KKM(73iJKK06(8jBa~YD02%9_ zg<>ljvPYY4ZmOw9+#bq(*#tWZ&K7T@Y-BrY}xfXDDe( znPf|DZu)*)IlzO^Q!=_>6h*Q^j8m-4fr&>n3YdYURxj~Jq)NKGZ5i@{G=3Ej?f4cj zlC^Q)l;?6?-QSV&d`Yw?XW&QQ7}cCZph)Vn@}cq3=pJNJrCp@yvv)JiV=o#%`$<8e z+U8T8U8SNn7kC4{t5&4W>C;}2Uy?!bu5((Bj(zHy!G^b^Jl7Bn2VY2w|Mk`$5mim8 z>?WY=%l(<|Y-YG0CnC!+6SNF^`(8Gp#xoNGRbt?r0w28t*M2ESFCd8c&kqIVBJja{ zouH-hO`lm4EfLzHR{b@b(;YkQBC`bR8rGBb!LOdjUo?K@>^_U#rE==5?m(G!=A2T2 z?ZR=PaGeHM?mX%k79n!UJfVp=H}KRpft>iuXGX=cKS=2&e?e%W z*pig3FrERbPtM;GGpmWkSD2j(VV)5rU*X>9yybx>;~QIn2XeziNd-+nGXX8%DW8Xb>R~uH6e#Q{J0!^dAOwAXl!m~ z#vOh(JKNYG^5o9nH&{}e|E)6lRv9TSt=%#Km}AR$L5f~F zV;KF$;uoLM-o&D$R^Dn!Nv^!iFH+Sqg{1G0w(QY86P1c~e3=;98G6AD?4k8d?+)#5 zOi4CY_f2Y8giK|ZAi2zSZnqpF;O5B6iSWUB_^PL?f-{^dah+`=R?s-CDCD{UZ91dn zDV4e6`A)E9#aY=yrqJPK{y>ejiF9$BeK0Lcb)YI{`)vOE;SmG~|jV(2p(vG*r zYbjEKqmv~P>N}Wxl%ll#vcoerd>EP;;C0EI)R$8AhH}41Zk0-9aGR_pmA89nJ9>iO zlrDjlnKWYhKGz#p-;uNG?rR1Xu}GPe7jdv9t{$Yrq$|o< z!q%S86`p`c!yYE!!zz;Nrnc?lP`@)Ol_;w=h8Yen8*;094&lHmq$y{QPnt3@VEep* z>=7pHP`P=Dyr!I?Z!Aw-_VQQNRE_j6qX={*o-C-K0I5W(6hr*R^cO|rJ0O|d!c8yL z7m8eqbg47LqG~b9#fpG^B>*G8?cZ~LqHg^BHFFnF%R|iuNngNUR#&vT;q>?sHNXu~ z=0rGs<%BM`AcYX9qlO?aQr&KI&V&8(iqoPxc4K`lTSoBK{fIN|e7k+9>9zj0+5HcS zuB2)~7u8?cN!3w`=$KZ))57YQZ0qYuuj){*4kShaC^u z^t-$-iZV_Siz+)9C8p;ayk+*%JZC&KDo>Hn%y{tQ+#*~?i$6W)kUtv`gDSmDD^dU;J`FdZ-bj0A0BE8w^L+#0B1%RHz9PJ{;APv+GB60e~#Q@+K za`(JpxO_c+(Cr9d)cDVZve(Gg#@|{gJnJ(otusW7oo|sm6EBX3$lbRTzu`ap{I*rH z*q546Qyl~x@hNd8^3dQN0g*~IE*ZoXXP*r(xh3=#?Or87*C44NEvAOn#UJYE#Ur!K ztt*`xBmKgrlUzTy++e^&;N|oj39W!M=D|xD6b5hcPRGul*#|ju5?NnWzfNC4!Z{aZ zza@Dd;DV9Ra=p*WlU~(NzOh!@j|tZ6Gn!rIm&!-Y@^W<$m)YCi$st#^)OTH(1^5!u{%YkQTiq~MpoHx9ed;YW5gI?(N#hmxgbdG#a)bx(o zPV5Hvf@cgw4tvoXTZ?OM=kRU)JJwjoYOLRgsLeSBH)E7oC@y>nZJ7l64Dm_Bi$dS%Q zCA=X7!h44staM~nP1ccUl4I&!@veQxyCCh%+xeq?$=z>G48Gv)g;QV9F7rQ+fYn1~ zyE{t;UGf;O)>k+Xx%_9k^qKuf=iWC@R(*#^1~1DJ(V>*h{RzGGXxEM^SAC(I3cEWt zO3m*G#y3~i9l?89#F|fxbYlxK#wXj|&lsVK-ah614vTJevhEvn0VDyiJOR+f*tvJ; z)K6swK1#okV=kEwyeS5vPW}96TGk$jTl>!amVI}RPd8h_WLU{7A?g#KlT9me<{=A@JgRPQFW@@W^2!LtxfS=ZVeUKW(K$uM4dB74t=8N1?6S&ezOFu1lzTZ zO3&2pdWH_IzVSYs|L6geogGks$Fd%@M!m?^foV0hNG{+alw~nv05QbOcSlagXt8;m z=g^Gzcrg;x1^vU`qO(_Y6J8(+RPr02h&Ytu)rSbF>hE7#t=XdI$wjCH%#5*FEblj7 z{4wv^BQi|`I`z8epY>m$pV}Ir$K(F!gs#cZJUUED?ScXIhZt=!$lsuC8LwDVIb`$L z3ejvuYuloCZ#|L|8roB#Y5N8`kf6lEpy=|VAU=y6JV`j(5N%>kb^Sx=P_f^uOc;}R z9UP2VnHJN+5uj-HpMC2jSjk7(u;iEub3Jhr%DPSGwmobZ@pY58b2OU;FwP>)<|mpd z0C!IgR>4H>_RR5;$QB<&FMA{4PEbJ+gWo*KVSmwY| zH&~G?5~RJ>z;@9Eudl9RpRTW_@q%XY9*2TyvUfP@HTR-{1RfYMUvFoy4I7ptc^Y~LSGs|`Ql2~%Vheag7u%VfVxe}7 zSM1k9Z)q0MvWA&8IEc-R-@%lwY_{Olg3NV6-=dhQCfyQ5T1Tdw@Jr!4Sh<5h`~7cU zN_OryEJZb5$=7&UAuL?4Vh^gZC~q?YuX$dn9Z3U4`nBv31btEnP{(+NDMN{IFUEv- zj1oa^lMUjIvj5JMF2VGI)Ek}5ep(3gR@Yp&*3|d?96engycn9;h__85NU-K5q_ifTHib}bvJV&Nti`nFgbGVWrQ%hm%bI!wO=&-cf zd7{=8&Gw`F0j%M<_#N<0Z^|-6hs-FNcF7d{o0F?EwO(DvBBIR*L3_djroF|M%~WKP z$y7zfc{O9+8y_!9%%%zajj#9;@L3!+b|KrQ&YI5ri;FFl$Jc|=R2U(ZnHo}-`!_=x z5Ms^@Mwo$op?ETv<6mcjQxC7fcpxvWy~sVxs}w+Q@KQFlOEWPYh~Zg5tsiXK^5ezL zGn86C`j`zUryMlsTI@qV6S1{_RP2MF+?9KJ7mk*)u9ii3f%Wlkk0URbLm_a`F$O*MQKQg|i)cXjgHL}1KWMMQ#coMe`fRU@SV z2Mngby3$?LclnLR#d9T^7OGb_ZO3CrW7C0(VUJ7?F#(IQLW?80*Xn^iq|6h`5n)Sc zk*!CL$}!_3vc$w2l!wsD{Lt+Gv_aGHk|Ro)*Qk#|Y60-*H`c`Ke`Qh^-`qc>wW89>NC z@5FlkV2J_{z?{*X_M;h@SG?L!ov(g02#a@eJ%4h4+wq-U!JecZ7Ewq}JZNmwE#mL@?ypDW`o4LZKK#z7SW#7A%w}>G%+rgfl@}ZR55bG z`BI}8EI(G3G!x_GzHK`W=fPB4m4WlMo8Ty%n8*_pRvF_MrWIBz&2_OXrdZNP55i4; zw8#pn5!^~|Q3gVaf_gi_GJBN3ga^BYD{lgE0R=^S;$CZblCVnUSX!If7apZsV+ z)|sMr6#C)@Ha#R|GR{xW2WX62L$kO!F9gqVkH%BV=+u(pvJ&XCDB$;UOF!mP_sdhQ z8-2E$Tf3d5w1(;KOLaGlM=828r^@_Cw7O<24)=%Zvl($+Xjpi#L zlIOqwem740(wUEVa8T!|e*?)?`B9Urvd)LaGt1H_ZY&|*CWmOAP=++VuMpMtQ$khp z)=Rx-n&YAv^f;3=$VpQbg$7b3DToFFEwP#!O-bE1MO3}5oSjqCygSJ2eJufOO05Xg zJZcZ?o=RW<9WCKYA+rrr>XwOgPe#LbC5={x4uRHaehzGNgkPoRqD9Z~FTVvb*vXvE z!6qY`=+!u@W?AF90O0bVlq(*Hy?od@*M3(ZXb)}LqeiK!&-AoDCTQ%liDx3ZLne8b zAPF-v)?kF&*Ba$*XSb8tYNSH}|6hciQ*>t0wympT+qP||V%xTD+pe&JKQ{i@sMxk` zR&1QyyW4%(_vJjU_rBVkWAxd-4o9H^Al~seXXy)XeKjE|DCZg%R%5%l7GfwYVSd%! z%%|~z$h~5*hy$-T#__eWr7KY*U43i|l!V+v zjP}ZWqq%wn%`;rO)sS0X3+w)V#=51>AWNhc+Jwz@qHbcTb3%gdg0@$gGVZG*P+Lrb zqhVxX${1V72T`oV@hH@QpY_cq`>0N_|FD9w7Vv4)`0Z3(TP0c5+t;w{XQFPLPji+k zgDt%)J;d;aJDmvA)lJ%eh!C5RElJoPO81p69pSBs)cR|rR%bZ-N2jy}W{t7^5}IRK z&xq~OL{_976*EFEEs9w;RsM?`^v*3d9MFyCg${`6|2Gpa55 zyzR`bUMWL?lqeMx;CWB3x}g1sH7j^#H8=O)gx(zdz+6|Fu!-|8D}0GXHyDwZm9nWe zlq<>yWQhK**8Tl_5`<)44;sMT9rt*8p$>O^L2B~AXrQWNgR2E#=s~W;N$jPgYwYI- ze1Cn8rRk<(@O&A#ij5v*#~A@dZmRv3R#4UY-eMw@y}pzG`c4Kj^p-+)Zc{fcWfHSS ziF69h@5%#(x@2_l2{?3LCnF3Rh_oX#TdKt9tJ$_;v_G}#EX66cHh*r9!MRDXcqlAf zwXR}#A%)9LQGx}^c*R<+`$~!M#jsSQ0=PDV(BusgUK;PzYSiJGzjVhyrgK(;va?jq zQm!_{8r#nkDCb4WsjA%ZWL6*VQwGHPw#j znGj&ei=6#tiX49QmM_&}^M3f$Tt`|0^wd=705|Lq^z8Wnjauvxu(g-@KB>Or)_bYV ztBSj_1f^uJZDwRqAz(ULxNN9#7`F5diQY`T6v+I2Dn_+NhZ;iI`38KOtd)8GADrZk z+yDe_)!w-N=o{>WzLI1Xe4F06d(&#-Tqqak$!FCg&Jp_rmugY6a#=2r3D4OeGlMEl z9OaoaA^`iv*+9VciP)M=iCgR}*~9R)UY#RYc}Rl%n!MJwXd~=)`J9s9 zF|p+xw?S|2`ea95xQpb{g^nMF>F^PJR!eBJTeVQ2UW?cG+Jg>f|o);LFnt#-PAxjqy6kV2NYgf11f4FR;z^6`k3MvZ(g4DXX(Y4cf^Pz`7n zR8C446hs8^g+KI=|5BD&Bwm-5Y6!-JmFJKbq!7|#D`i|e=}bAmj_iCznnU1Pi%a2A zV%uU7ldk+Gmq-I7BsRHZl+D0an>yMqGr9lV(ZL%6K#s7Mj@|sc1@o(>YYTB64&}v*2j!<)q2Jq`h5y@s)33mOesu!-+_zuW%XugMs%?!x>BUPH1Bv zDY*>Ur3!q{<-l-(i~#D2OT74RfsRD0o|o1&)(e1>u*6J=S^AJvwHFjU#|Po=C%`bo zBE>vbkvu^iBlfl+GfS{4@|7&nxN?B-N3i6l+dkvqJs=?f@va78dWT9a?sx9;+pyss zuej9_Qy0UD5skSV46}65qgNqY$0GJUFLtMQsI_P3kjpf2I}AQSmP^VJK?y=9GZ*U& zyL40k!o>O#I#G7bZ-jZQJWIVLO!Tl$PhImuF> z(c-i?4VJ(sEUm{EuYFSr;Y~QQbpEY7(KqNJ80P^T>Y(!kI1v`c^pvq&HSUnh0M~7W zRzujae5;Vm@{-=}T&Qrdh~p0}a2N$d+JOQI?*=l^PUgwG#W)?$W~4(696OT`6}l(^Z2<}40rH9|H_vbO`LT# zCf(AUfV@g9cbP2K9e_tly8*wn&ciJS<`jzIas$l{pST93Zk4#lqyFB|I=iRMG3Rt= z;Jh~EK9lvanU#-?QxEqzf1eDXfsfQxj$pvxJ(A*Z?+a7e9RN%HCz1nSr&)>lYqd-yhQUtCZ4`Q{ivtB)a1Zz6ABaN;T-V&SoCjdu`aG6TZmr zp9|;P`(X-&gDS9?eOEjuJN}g&OUJBm38hKi64dR$G|;1*zWl#-YB?gVcBq7<+vc&$ z{}4Qh4+H;b^;xqBGa~_Q+e@Ty*(rEQ_{PVOVwfU993&c!c$!XLjaYUXo@n$QZ}y~Z z>^hAD(bp%BWgIUY_7aPZ69LYQ6wBDrzsKue(BC^!rhPLr2d&xx**$lvFNDq$=GWu4 z6yvbmz8be9PD;!pyjwpgN>wE*z`M$oq1*YJK0OF3Em(5Fr=_M@Mo zDecs_fUPAQoBLG%V@d1Gce-iB#nIxFCpdHV;1g7*KREm1e>Bru=(y#{YCy`vhR;0&-j(?(Lve_$X3_ORZ5f~`~+#fB#53fW2P@dQ@oxB#0 zILT3JA6z5C41x#MS^)x!Kj+>p^DI;J7tikAZbc+cIWS|qQ|!C9VHO7qT_JP6w{%AC zU|4%~%g?J&=8fvlu+F#v<+-G9+3(lfcyM3Z-KUI)K}Oo~+4`JmWU#$h-gM8)%N#Fq z=Ke_b`|zYGx8DkjlQ@;rY9GI~cGx=dEBf{9Q~u7j*g81Iy78b%Q#(hmPf>MDvjk?~ zR`R99_zb2 zsXo=XOV)huUJYHEU?hQQa(fR<-g` zf7EQY2&?pNITx71&+zW-JZ5P8*F`JyZ+&PgFOUUsbbeu^WOIVl#wCTw#HAuOj+smR zM4vB&)HQe5z3SY#)*t>H9IoMN3vc2TZvsB^jo0pKyL;2vQA3fH*Tq@c+~U*6+-1@w zy7_MXuvtg=wV)m*;qYlIx3IQh2W{$|`qwTfhwPv1>!{^g*YsnOUhXZ(3o_|qzt6&Z zqB(H`r8EL#R{SlDg@>NVIa74Y%tR|^w^|ygCj-05vJ}*-GIJ7`u%{5~+73gcaqalo z2;X8Ecy=voIRQR%=lC1|>#AvLPF1GuTn**Nqv;EMwaiaNerZC6AFkStQ&NttKpf0JT&F{)=6xMHWU2d2|nV@|FV9Y4ipN~B;@hbho# zI*(_e!(bqWF=;}(FXyN*o$!CA{1ktwBl^0#_@)CGkblv%iZ_kd(z?hjS8^V3TbQ-F`%`atoWZUt)fVR1?>bW~tdH3Ow$@PpK^2`+SKgZBvSJPlt zb0oDf>Ri;_b!mTrdplOJSH|fNsUpm+XZ?;7`{7Ia;a?!~fW&7aYnveYuVIWYwux+4 z`CYp2E6W%NHq^BzvM(-cZsPg==JSABn(tQSC80g;%ra-2vNHdj9O1da#p-)By%>M4 zji-h{S7uw~d_aNUdGaJAk(NVWb*9Idq*41<9bld~%_WCGK4LS#AvbQXw^*aL$0y}R zSGOObS#i;`G0`_2+y|-TvUqxqrAtI5#L3AQD&HS@8#R<}ozWjqJRK73)v=P_qFvBZ z(MZd3>4`10j1@+EcqRXhW9W!WY=;ew=g)g~7uc_f;P!MSYeMiLrd- zD~B$*U^Fsq^VfLUzGHBlU6MZrQ#)=>ck^F5QCR_pi~+vO49r!;fSb2kLUR~GzQYj95&{}1{Zy3h|bS1y<*6|e|~$!!6eBk6QfP+8JvbekZ5=VcD;H4^r#Z zD6p#sk=B1eF3o?VBzp7LEYt_ti3fycs=qb#frVGQ>J&@JX=X2I7dJWfLXPjeD0z@d zx2OdsE^e;c2D|)>i){b&s@DxxuJ863_pJcS?8o4k(JkB5?iSB64y3K z;_Ia&gYbYYtiay~`fu75yFzBKx*&hbHL~Q%m%9bOYgoTlutL9s92||O-K37OBDpfG zN(OL|p%j*q8ay_xX&d&Wxej`{Z`mWi*Dqyn8hMecm;V+q6Iric>G(4I`R4ZFo(r+IztNXED1kVjzp){P-av@W1CwRIMEU z(~oz_C4i@?(RdU-2-gmr@i1|s)E*LVzf?J$PCY$|Of{Ls zjIdSEoG@iEX=CxPh;ldKeb|wrp!%gGb>^X74w>121+RuRdUMV}1D zew@Tltt$l0f3z7%9qBLcovp*5?opu5s}dg8r$+K6dywpq zNN)@zfPRfs@~Hg`X-F2bNdq|0jzaO47wA0F>|)t$#HLWOC@TskkWhF z9X~dA#HOv92A@?wHXp$t#NXV$zqJWTu8p$e>JpG{C7^v{dqBYm7FI-fHpW31zz9nd z6%@~nKoS(6pn?=oxTFmMJzzm5rfFJne$o*UD~0mzfWMSb7X_yZ^UU(f=d54?<2}qM z^54EI#9~%*d2fkj03SzEJV!C2X(qw|Nyw~hd$|o2PC|`wo_PTza-Hv;+wRA1i69rwRopHtqQwTU~(z$#XA~-UnMqS7==0LZIBrz&(d`U1egg2ir z6?f+8QAZsD1OapH;94JdlJr%ASOGQQOs1r^jviZ(EnSvL@sWg^pC4r%)9zB{O2V|! z-!WW*b(B~)=-BRjW9zRGZ?$f&Y!U0OsJR7~08UfB(`sUr9q{t97l@0!5~9<=;W}R% z4b~B|E}X>y#%aGtiq3_yG1u-SQbyZq0UDRyn&;Plv_x>BTGOwmK8#W~p$!3h&Oo$e z_#vON`bu_F@`Nb%_T*-UFenx8*6Ym|Q%Z|^wcSF_H-~>!xEt+AHisT3@y1$bYV1On z9YrCDFkW(nx|!fTXh%U>Q-H7cF*@+e){!lY%+BqBmwCj9O%!o)M+(jmF7{KE4G?7y zLrH?c8F|kg$6r!6{oPMKaRK{KI!`Qvp6|(m2Mu48)Xz52qp{4qC~s*Y<%#(RB!>}2 z&O&)qM14E17QLw{$_7?zopgwU@~z($rm?F1mD&PdHcTSi_~W$1Zd8*byk4ijU&+Xl;leKYuOjaxMsk->zrM!BE7d92GYJ=oZyoAQGizGBli#c4Z@RZ|*tF$^R0#jz-#|WvBcP2&OYh&2zoxZ}{nPM| z=$_%dd)OxH`H7YTN$8i4Y9W4cew&8dhTCi_Esipec3d{K|7u}E{QmeQ2z{6;Qw3Vm zAjfm&Lu7G?KiWxv@6_!pkx9V$=6>`T3Wg>_@Iiht@60c}!Uc8rZZk(uk6>#nde{ya z3K|P?tSDIas$7GE=KSD$QP?dS@;9%|Z779*gcW`!_wmLt=H|CcKq!!hf24i1;W>0* zdyMV%ANRMY-EI)Xx=4`B_=x&gH4HEcU|-pe{MX$t1&7O8XR*4OLLW0QyJ}V&P;0^d zs1WiI^RaF?orw3TP2O+XRWe;3I zkI1)-o(~*W>Wzj~1D-^$E5Z#Z^MxxHq~JuCr!fBjt-q!QB_O{ROG#3eU->RyqEVEN z#sq@K_e0CwV2|0F#pge#w#|6C3Gw)gJUQnZIb1zDc&GIH`J{H8%WaVVg~wd4tDA-X;MWK80oR1$sv+7LC|9H zph{7wq(!tU=vY(4i$@jH<}(=7)v?CN$BM(9v5_6r)p5oSEm6MD7(1as>dKVG@Op7A z8!C;t3!dV|K-K}#op<@f)n%GsoYlO!k}A#=cQz9<(EdAvVq3NpIkHe9pAAKxluCF{ zf*BZmDuZV_?a2BJum-W!3hM0edbzeCL!LXVYgXkRcW}Fj9tIH35+0FLb$6^Wd9y$H z7wX<{W{8Pf<2hzCj`|EO`5ELotd!oBTR!!I5orXFf zx}7l#qN+vH?rla4?(TF!dq%*PYIGg03=VeAl#I&W$ZME%aWmE-GyXjMm&8L-mV)N) z0E1ks6V2j=5_8X8tfneqTVkp2l#VhcAhEK*>~AkriC&dsK@`$dILU0#wIyFaEcV1< zJh7I1B4`*`7*d*ulfHx}EX3(Fj1hZsBm!k!vuug*E*}gd#`XOoYaW7la{sI>%=pEj z8@|dki^Y0ntS$7s^Sa}6UKM;W zNtxzAxL3YCZoJWDo|y3|a@A8ERF=}gC5YK#<5E(?&Ev_ZW;d76WGtlpHpWI`R6erz zN%YJjVZ6k8G9-#?ZC9^%yX?vgO5|l%f%5i>-vc?>ac9Mx$Zn!#nIQKDri`c(Q%F68 zaNI=-PU10uKt)8BSj69*Zm$)o&QydLLy{VqpXls5{;uf7UB;VSHHScW9xv^$|0hi< zY5uQcxq6{$SyOJ}EB~#B>@41vh+v(XGq|Rv>Td7-$0>ZBb!=T%q2ziFN3*21);TYN z8ai!`)SFWf^wj&ZoS7;3^^V|d9i^PMv_koCNC{8V?-c$u?EFHK^Vn}XB=u1qe)bhi z1hb^~z2vmS+QYtxoe4P~aHoPvDIVQhu|0%Als9HJ(N=O*G%9m0>?n9Vn1|hpV#cBD zVx-x=BeWG%^@)7ArON#L^iBsy(yDalhW&62_Edh?SjA6@DqO( zRo5Skwyd`i8rL7Qs^MuJ2=}EO$vUsE%z4WSg<0jKK4;XBYPrWG(MhnRhwbrQcKANL zClpajoW^=q@J@rhReh)lZlG&ya*=5s3sxYb2NH1rg8qzISE}#=7-)_9y?=vLC0}Ci zhq@Z*14z9HV$De5(dn&@r=9~u9|^`G4xuy&zdc!ZwbO1o>Metz;$~9m$Vl2$W{|Y1 zxS%dt?4w+hh5g6oPlVelcOjhibzO7tMwQfWvI=Y@mcwUWmv$cIE7)&zKXx@?_@f13 z05v4Lo^t2V1&PsWuB}{y%3i{ayY6cPq$N^aC)FDq>#&s1IGId`S5ECb3D<09l5=I( zeo9sbx(T4W{<6gpGk=rb#3gRa@l`}{6Gpiw*rv~*S;V&_n4jVhG#TcY*~g@{i8FYP zCq$!caFWFo`QTNL4kaQ_4=kf6gs;TC!~kpbj^ceAv&Zrs6$YkJ;q9^B-t#Mr#mg~@FM_+5W`y2 zDM&g0d8}0$?YI2|Idug%i4t9|9u{2HV>E2Wg{k+uTy@|k*O>^@c@`gzOc67)!W6bH z3r3gg&FTwF#?3b%-=sb7=(rj))$Z(Hg|7Z;_!(Ju(PeuFZKBm z`|hvqF9?}%^Y8fZ$?3%pB!Vc1Bb7&f2k^S9g%b@U26^|rasm#^72)3p)|P(uicpZsvg*?{}L z?B|XR(mNjyO6oXx!T^5Sfou%-o>*}~X1~G^BBN$Dr|9Q7_Y;o-(RBDT% z%=nY(-?;E;dyf)RgQ7Zq-Vj)dh0)U5zT&J4TqnKtgKu{xxIs@1LD;IcFUV2j%Ex2j zj)VJ*g?&SKm8!OhJo6EWw0?vJE3J0}Q!ZomZF{FSBfO~Y?-)hRGL5dl27h@LQ-S?N5n0u>GfUl z7XrH{yBY$0Dx`KAp>>LgoW2|!9c7T7?Ss1Fza+lbV%PBT+Yj@znh6K*HvC@n7k}6X zjYBPiIBC}9gEyP<05km`=aQtr7k}J`Wf$n?lb?6DR#ieBf$WxyYd8z#ONP$KQ}t1Y z^H`xAqeK_UaKTTsO#mUs2aI&Ee}ziv@ttZbrP^V7+F@xeFml>q{0`_-=l~cv{4-_)`>YuN`{KvJ6N-hP?6;Q%gZ9>`6IVXvqU<&`}X=; z%LzXTp|aY&P%{j#tdWQa06S7gcuIBz4|zT5g^;&W1`S;BxlNh~7U)0E>^_tc1VA6u z2$-aB7_-o$3&k%1BH11Wxxgpbh3RhCgHKqqfW!+%3U~~g?5A^6LU#GFqos|W@9wNb z#(n0D`-u)Y-?7^lk_g7zM-mOj+dC3U#@lO>Cx+Y0P7{S&>+#}J4FCslFEmV{k{hV# z@+drcgCJO|Kr=9x9eJ{m1Iy1-(UW{=&|U7S{E)KS+1kLeUi5s^c=Q5!yt z#cXhrq)%_GE;_>86d*fxUimeB*Sd~1j_H-15Ocs_F6%_zBllZ;fUf_%-#BsC zj?j;~UmM)oNA{QOh7s^<%^W{Fp=&nSAN`0x)KJ9Zr zGOqC_Qq&=Bbk0W`bJEQBCDRcv?cVUNz(jF*@3G^<8{>?U$Po+&L4><>PXI=yq1G1e zeW|}+y5Hj3$Q#<~zJoH4`YU`Ec8I%`Ko^G($O7-5-^+;o32H0ohj3gfOU2bQRiZ)d9$Y`*+$Aw_y9p9_nZ30qFXAlvX_I!ytBMoW?MPcX{gHiTigF2(16$WpjI7SOB zme{SJvn^_lO4>}poVTsb4co+dS6|%m0OEY_=h-@Pk1bw6-<5KzLGe5V0>^* zy5_cQq=~gd^ULn}E_$}Iq1FSA?cH{Y^%!j3s!S1+H2E6yxpHF&_C7CsNf^;m(s(%} zDfxRQBp=Jp%lG_&uya4P2<2WSbX`xm3-gK>X0u*3%3n=3E=Vo z!CaZ@5g*b`<+?`nbPp$W6{JhJp2m~T5GCFf8(<3w=h^lE)Y~BAiEEn z*_)dH79j4T&4~mw<@u(>(gxjUEfz+BgrIK~VR_PEO+So)+%>C4O4OwInf9vrp2)UK zCzIDqTyn|nSOA)1uk5b7E`(TSSmp_~4%^9g;Hn)mkxQ+bcXlERdN_rXNees2IRaVr2Pci)s}fbiS89UQ)Qk7J==pm9_6KBENk>VD^#`13>ZQ*D;()`&Gpw0 z_EIATo#CncXFwP$S_Kw(#b_Z6d`plL--HO(LUuUK++sO))3#ap(+ePuGq_Lb9Yw4? zN(Zd87t-#9)7&+F_1WvE{CHDpnFp`G3}{J2IVzK=!YcE!$lR}~9<3nD4t(i+yzW%> zYkSrQbKXzx_#U^2)#bJh>&25atd>XMf-lY>w#AP^TxMmLIRXcFFTDt(8ijB2%*Ho| z33$WtS{B!0W~6DibUA5XbLF;)2yeV~Zr=$6LHQ^EK1SfxL^g6{9_RXESREhneh;ZFRoQYa;q5grn0`j@000Kt9q@(GTNn#Kq0O6wPHDrxj z<*VBG@tQm%)n@lKhLZEM^DjyUDAI0^vF-aH_FH$}UcD zmlziafPbnuPMTe*1jcJu<^*B1SsqfiwnxD(Iq&eKuyJ40i2`u;j8T()+7fF99b`AGy2Qx3j^w zom$Z)^p}lQ-)6z>?{Fn{A0`Q2JM-H@Rn-p{JITAALgcq4Sv5%2bM+Ak`yGl_mE^oY zZKgrYD}QlAIuWR;rTz&m;4@LnraSpr%vCU23shd`; zlSJ6Z%PjSiI=izp^r%*0PADg8{zt0E*_&KLVl3mNSnE9&jR4}L~^&(05zdX2C} zldqnGw-iNy#^b{Br|3vTTU7426gYt%6?-~C?^F>9|DNUo&>dy_`7Z^W{KMi1#RFcM zbb}{mYpRE@>en3&L-GsKJX%3#Wm#2Mq{De)(R&g*BIV-mFb{>E-S>D5?c|i<4P!61 zJdK_04d_;%ImY_f)LP}XX9TUU5(;_>Imxtt7|Pk|$(yLxIsJ;$Wl}~iunOaz-QG3n z8nYN%5(ac$R~e@}I4k_-r^N&5873ERR4y``@Am(?+>X%s{46s+QRX!Y9~ zC4iB>x4P=Tm?ph`Xj0Y*Js*~-ggs}rcmgLajQrdPgcT3#gVzxaOwDt3B3(L8z@!d0VaoI~m) z^ozKjUh+T?Iw&3P{`=WtK>sxW#@9K{$o_E6a5TJjhMco~!LAxkkl!(4)H$1~R)gAm zkR1H3wBZAnd8^|y@cfLmma&nR>Tq1Nfb$Ccl>BTo%Trs(2_ml;8dDH5Qwu}1Ft3C0 zI`cjyK?nDsmg?fVFv))s{O_QnMnV4iPmmu!nm~X2ApP;Y4qVCYq)Rm%~@ zpl;2LX9=u5*W9CR2g+dT4{pI z=F|0HILh_7{Y3xCsmdj0SI7$x*>cX_2a@ueKF2Q8TI(GeYl7apOvE%jm@@TuIZtRasrb8VF%Dj=7@vkH= z@gy(VBuw#}f&;i3a@}1|txs^Rzm+}FesJ~2CZ-qCu~m9wgCncOzOkKp%EnagQgQkI zAVug3hNe4pKbgDVVJ;V$VZ@nxsoz*xpk4P}D;l7uuq;%vWtfZrGHwDvMgWB-lH25R z^)QAydB@$ts>s~`R#4Ls_FtNB1rdP#_(A=@DM-b^&Dq54|LMq1VO|l*yhBaJ=12~;A^U~8Lp zndU#(3g5JB zyPV_eJUGby{U{=hV*ZD6%jN%HC8kVFp*KSTi@2LSI-wA zX;sb*_bZ!s*!f`eC%HGfS#)53Q8xK$SHgQNN3?w@p@tzU^}}=p<8>u~(c_RkOElmy z#U(2iqz=6!}%%t*X!;-%7n|D-0 zS7t>JS9{P&Z)ha=q~wwqMNraDi7zM?^HBR2aus0~-;7k(ZbWvU{D_zorn&y&i}V#r zZ>(waLH)B`XZPSoe%a{tYuNu5Tz3=}*>CJ0KU_Kf7vtR7$i&9X`9ESC)cFsf@o6&4 ztD%!Dxkr|Tj2Jp56dEZGhYcz?FdZum1q>RPEM+b!9xltn`g2yHm>#HSdueQ!4^)qq ztDK2WLK0Kzh-~t)v(dQJ*I8fdlJ52Wa`~4f1X1*xG5R5%dN` zGAvBF%tP$D&I|3bgsz7LMFd$zg4n}iqup54=%B@*K&W8J9=Zv9o>?f4^|L_y6p?jN ziK03d0uQKkO_Y2C?hdU5vu!48DlbzAJ%l^fjX6Xd0v3zT)Db#F z2Ad7rI58{p7ih>h79Ga`ialdX^6PgrwHy}kY$qR^QEJBL0UmN_Ung8;I#g|1J4RlO zGCAP{8&av22@Kb~ENSXH2hr@0S#oyBCf@3~^wwWCkA$~h8{onnV29(lbwpL^UJBDk z4>^@}VGVJI(9zU(IM~X<(j6u`2(~O`Eju!T3}Ko-KhVbvfkG@qdQvtvZ)Q0$pXnzW z+VRGC;q;|}r9Y-Vi7@BIN`Y4Gjkg3vJrF&c4ITt0+({s2cTlfm)$SkJ3xX*mBc{4@Q=EH2YXB8tlP~lQ``CqJ9d*P|-$L+x5Us0O0cm@z} zjR2NZ$?u3j$W}Jo)5Ab}Afr;?MSO(H>ERN{Z15tlNmt^4ypXuD1RFNuKa>Mm%6`3d z={PXkVfl)&1^3_76_rR3@A@Kj3UatL>sjREk&dC@YM6Q%yo_M zHFZWCJ9O1rypAAAZxgO9#7Z)h7fs;!QjPX#1B0WhRuyNOK8dCLCAVe1*OmB1J0EhC zFbFE6CbGl%Ic2O-Z(JWQq&Bl&C7~=|)cw z001qlu=Yr3%;15=S-rNyeY{@noCu0P-u;Q^Zz`vYwPCS|6J^=Fb@EH(-&KZUNjCZ| z0-4d9>3UZGqA8V;B7tdA9Ua~Udh2U*9ee3>Hd)8QdfKsqi zF`GznA;FM$c3Mv%p1Z|7J|%heSNF||76CljC-CIoKLr;$yp)~@eiJ>t%kGT|mYI&L zMj0cvT=9a2DLfgtQ1QE+PcS&nnOK9HhIx~W->F3yk9Af?3b6nJvRwr5fN)xWy{eOV z7V}cQ$kUumsJC@c*4F*} ztX5^i-7$}2e`ggbxF1-sgnv;+9pO)9^yq5N#^inVNj<^;@Jn>$v7SRbiJ;&oy$bpV z(3_;-mmZe&j-9j{%Q&&i;z+*-M=zhghYj(~fuvLUqDjUj zjbt6AR~SH*Qt_4Z-9+0)+D21lKaFm(D|yCXeY1nuMxM^t>)7)tdH%~~1SR2>qtuIfL3?ULpSb#YY{W zEnoQt7UM%HxPpeVFLgoAJDRz>=RSbiAAD)mQ$o|H8>5?f&|$SlAElf1RIKj`!Zzlw z#5Vh>!>|0~R*&?AqaDO8kkd>+71%+3kVt-7E{W zn+n>rj6K4=-0M@dQ@gAxp5pu0>v%^$ys;Vw8A_#M-C4pIiRXzglp2_d>c8cvEGP2L z(iz)>-gXu8?jB1yf{0&}dae!B`lyt0Hx3-}Dz>4egXiH`%QGvy#a2lY~?^trMoO`>Xq3s zP4t?Xaf%OuU$9K+;eMxRDh$gsaKO2R5*xJ=b$D1*jwzUik{a23chp zb?75xw3e4Q1#T+_+66FDwb}3mUu`shb_pFg!+jjp2TZvu#r`oNDb+&*B57e z?ZXVrqQ<#B{rU@G4X#F?!uqpG*_t;+mTWG6ViSxUA#D-Rgm7#lhQ+6ceic_s{oHD) zq?zOyPJl^BR93U*_z>n8!B?fK(8`D-?6!)19#;^2mbNiI7e#Axq%0TiT}b5m79hla ztg4u!)5}Gl(EX_&&TDd-gS0&jwz%9ZYddLCqE^0`#OIu73A1sfT;JbPz!@Vu2Q zn0#i$+)=FDky0k@MYPWxt7A>t1j|snsZG@#>i#H6zmyGH$zsO1H@9qI2YL=~Cl_dd z{q{vl!!@(OM^pYe65U;YQX0Au=~$Q>L~F_Q%TEc2`w6BWT<)DKW&+5vyQ$t@#&F z!)pAwZG`bIiTF>Cm{>pcBZ&rxoOHX0C|IPCZ`!-t0zX5;8%Z_Y;0 z(2NMXEFU9fV(*s8p>ID)QB?e|gj8Yebld&{y)JQ1NY-5(Yt8s4$4CKsxwM%nw9-%5 zv@JK$Y}5i0t%y(jk@o(72UItI=m7F8bp~qWqGa8eo80M0m}sUV_gJPwWNMv7){;yx z?dw|&uX>b{q>1OfywR+oM!93iz&AYn3i7#x+WUJ`K+j}HndzoR=?KDc;=P!2=V$V8i2M=wngWr7Nd%`6uLFNVxtnR_ynbuI{ny&+W9x{Od4x!lwR z79+vCm7jW&dKE`+wqeaOK`ua0BjM1q36@i z8|Vu0FYH&CorZMoT-$+vgAKuM=>25zNf?d%^36hXA-$wUh)MjBLC96OWXDErur;3* zO*{zEov#%;O3rZ|wO3%+{Rj4Hz|toB$O*rmhu4TQq7fYgWnvXZ7=*;9+M+M^9+djH zU+Eknf9+>D!4i%3#yL-QmZs#s8=mW`um-Y)K*17}MvvMH+05K#&NDbgRYLSRIl1dO zP{2?8v;LaQwha`px`6PwONkRkz}&0{i5;3d{|;(~lz7S}>hS!{D=|`&sqE9a&$39b z6Wa_WD6gc?EE>el2M`pUzaQR^;{oLbg@mo+%Y(f5IgF&H>k~`E3F*2X9JtxMLnRl$ zl#7m;`Oi#?+p6xi-&Z|$uh?~5A4=QFZS@EHwXGC7#YdsmWsk&m>jv&%Bd&e#2H;9sAK=;I}tg%iu8 z30W9ZvaqF&+M}^LkBO+0S7P%BN3hGHccia%+C;UaH;n8Crl^8{r<1DfZ4C3xy_E;8 z8Rn@kHIqIvPgOnG&b$jZE&en<&QETxUy`Jx^i>xL-a)aQi~UZ#EA@SlMR$;r#?Z;F2qb=pqs?O9ilCb@hml!|0#}tCph| zdFpGg#N4xzBTtGm1pkbpjIerSX?`m;`rJU6^%7yT&}urL@+%_rkFWKZIRnOdUXw#)}nqDems>PH`yi4i9%P?(XhV+#l}lQ1s#M?#}ftmt5}8U9!pk z*krTG?o4*(oB2K{0@#`FegfM#r3eSx5D#scLPvtqv`)CCXHtqs;S(W*Nw)PP{+hM> zsxPhtQGF8pc%@O;cz(qp|H4RQ4di(W1lOLLVrIECMi+u2%4|R7q?1s1T%d**Ar%t=|A)G+tKRlgJ`VCa73E zr+Tq$D~PO^Jf;P+y+1C%=MX^IIdHYqwJ6H z@0>%E*R)-By-3d++F!;ywa3fqy(hsz$CetS`GbDQidM}GHFd_lK6Z3?OAEj1tfoh3 zu2nP7clwk#J*`v~YfLh&8>&H@^_EQUi&Dl_6^R zF{&zfWC2*KA8yMz-eWs0M7)v!BlEM&Xn*B9l&+nNfhIvif&xxR$^rhBt{O?Is>!9} z!xz90;&QqEQuh9m-UG`CGc8dvd*KfF3$G^pIbeZalpEygS>Sb43@hRHS(iMvjbl9G zo|!RuROF3oW5f+X?AwHyE{*8A$V`6R1C9*fFoZ!sNe>L}G6G&EJY;FDzbg*4X&!^A z3FE&pd0Y?X<@1PR+s{*LoA^hw19WsMJb&rzzG5Q`nG+~X5^WO>q`oSjc*;EqfSC9l zdJ+>5NOoTeJ8(uC#Jz$_d+!zCLVfJ~vMz=p`W=fJrw}P!%!BEcId9!}bZJ!NC$h(7 z(ZMZ7jD8-M;g6m$T`!Y4utgAFl<$z>m0v|d7@b}3TqNoi?~@izjldB11OnuK@2o~s z+BmB4Q(rp7A&RR}OE^1VF^+E9q>f30oFW6tB3#DhUdcTt+zW>%AYgfrjG?-4mfcd7f>K8EY*vm3*Jxy zLjZ)%OI}p;?yX~JmC9ly9LR7*bW_OlPBv-9&*TUHqsbc@MmR7KyJIwRZAvhZbA{8n z=^1D~j=7T1qLLech;xQ30$I=n*)>lO^0J*Nk4wZtd*bHN_5)tz zP$!Jm_tFboKK?*kB5fESL zK4Wh)Rn?Gd+m3e~eM>JVK}n{avJwo}nW)ncx`d5S%)kD61cQ3!wiu9K!R^p&ZpBU& zN{JZ1xIIC5&)T6n!50m;9VG&oyONY6n7B~7Z63ysLmNxMHR|+?QhtBxDkKI^%3mB; zsh7R>eVy41D3#(~sztP*Va%w97`QS9Y|X?3Gx2D}wUB=cX?%E$9o%Dic1{0PRYNM6 z8h96C9oukY+7)M6fA-AyQocn}X)~@NPuF;;068(7`$OIK7=y{L;eU zE5g8EI8L|M*>={n(of@f6l|9sZ7s}ROjg(-h0zFca)GhOTA)GRx< z&t>hA!y&+DLB%p_tNo^|NeN@ch~0nuIQGc00JK*xab#5`;rmeVQ4|oj3R?!?mwtXU zMCJhaB>7Mp-H_(LLoP4~DIUx`B{rI3%cdscXQ4dXL5zRZxo_$^A@rhbtnNE9uw&kA zARv5D(iOU-Z#K-{o2XUk@yXdgkQrCsW|409E!k(udJi&pnV@e;tv5tV-4IT?*qQV` z_{^bnHOi*r?YqbxF%1rbuaCKIFCAAILue@gW#HmWvY(0k;)v0GAgd_rjf>jz}51S<#f z*R`ii#{T$e&^w1M5hbl?YOq0{RzfnW9o4Nn1R>KIX(r#$_9?qOGDN=c3MkYZD3SUn zd#*L4gmI1H2<~#W{MGXgS02CnWT~&&%vR<$G>~es^n}LIQu`7vum&~>~i8!exl(ImVyktEek`yS1$9u)s$Q-1;p# zwDO^^xPjs;`?$V75%RBP$x>$a45Tqcfz`YUR}NwuyuY!mx31nV;L5iN%(Y=MOB@jg zCz3H!rz}4o0xd!kt-YB)K#Z~=N0^tUSke=u1gYuLmm3q1=YcZz8`DamtEIZ>DOMTk zFM)OYZ+ZQRpz;2{WAFn@dwbrXtY`2e|JjCeq_VM#$|V67rzmZz#k3Z!Y%a`F8-c0To^4ch;N_2mej1$xh$P2IghQ197|}#7!+G* zM0T@UoJMzYDrB>ZFm9GwKG$1`7|lQ;z`U)(q)NmUI#mOCw9bxjA?stIz$4W?f`gNh zp$aeJqW^LpKlA*~x4r86f~VvBMsl~}>}vZZ+SuvN%&D6lId5dq)8iJaaPQe2>WEDE zz_^cseM*ZLaCi<{EOP+y$v8J>s=AQ4&(Wru>agi4pp}AMHO^LeyFKvXla0$<(|s1|7rw=YvoPfoRH}B zS^b3u<_7ZUq@L?I*6&GW%x0JdVV-J8M821}j#J<67Bg;{m)uE(`On8&SxoZ;D0+q*wyJg+}7X1%fM zVmjS+acHO0%yGio)?f>S-{+v3b?>v*yrc+VD;L-}Vz6km10KSkPy{%1Yb8P4Bm&gA zuqLM(+O*StV2=}4uKsnQFhP*Jh-v-V8+2YPGt!*JAY?;C+F?Xcl30$eEKRgu<%NP8 zUQ+y5oCgnwa9D;&x)EGwspMqzz5Q1$s6nK#m{yMFhfk|r;^K_(k{Hm1vr4j-i%>@j zVjg18e!OgtTOq!cw`TVzp~MfYI%*7;gp3sK*0HAX z1S^)SPn5fhsmEx;1q>~(os%gN8^qWLH(d7m*2;^n$@X7#wW>Xf`~`FiFMokqoUW49Nfx_cz3m3MNH2XlhjHdLcAQI2 zC8>K4Y38dg*VfKSC^2gFXAZUxsa5UHAzv&nF?xDwi!n$}lTO@#`Gi5u@7f{leGk7j zTJD||A3Ti%FT+sVjYaQOJo}KFVPo*R;K)g?v8q$Y#C9r2Js^z!%%HA@CVIj>(DY0E zj>TZJz268hM~rPk+`OA=z06n)JOF@PAa5=J)t0dpcq4kK@hgtjcE8 zt-LQa5t>U<7JIf29&{j=4P{G*Z^$JJX@jSdX>3g-21g@!agGC%HGbS-l*1_)K-KPv zQS*05Ht&PO2{8G6H%=2DirOkS=gBh`5sP4@dCxvcjf=&6hw>@*<*azpMtcOhf;RX0 zm7L>^^JJzq+1)D)j1hB3nWD8t^nm!VgRPO+g@fs^xH@VC7@sqV<0M{25zC||GCwJw z)(${b3Jk|PZqc<#a*OG~Lhs_pyR<+RXh8bS3--+G`HghE7lOp`rTC41Gnef!8Y03w z<54K|_WW;$@R<`y+x#62Y@gu&;ott}5dMdMJ4O@1Q1t*)u*`Fv{3EM$7DIHRk^e6S zeIOPCsfzYbcZu3mb+IzOGKFQUG@FQLISW2NZpuocotUKfl8XX{fva7)V&u5%o@him@)JEi^Y9#M3q0p!65#P49Cn^zsV&BL_c zK$Jm)$yo?{R^_En+P*FNI$7wqjtuiJ!&}az-?yth>F+v$Uz+*2n+e-JSgWr!(3?T? zh_^6FEzcWolg~n_Pth4aKuXdo>|~ic6!eAIf5`9)HszQQ`Po>b2IDAmGsaRacd&@AdJ5i2n6AIq4M&X)P1MLmikFPNP3v=tK}x6wTF9 zVTh&cr2jG_xipShBSS`cPX6xg2>@&DM?h%Ix zPC|6-C+Z;&RZCz;DWpgbCmykG2NQ~A5?MJV56xSWPdh=v9_-P9@u;uquDBlKlk6~^ zmE!BWfNgjB$nuzM8oEOV0VPn0oi(zfnAi@f5&rBd^+VvIT;cahnl$lob|>w-BPcDo zg+@DpQQ;|lF&e}G&cpZ(H+q@@ZZyJ_zn!UZNY14Zi`_Y!!vZ0T)k~ABkK*puX2LNt zgwuiw#i_Xy8(sIA%GBA2ksX ztii$(*asTtzlNt1=Z~261Dzp+75Rzg`-po6phX1VQ7aQqDrqKmo6_!lf5D~0`#IFv zwf`HQGf-u^l9I5aXfD@$D5|2Lus~cI4&rtR32GjPYjA9$TYO0><0x9EbmU;uGB2Bn zw5+`Lc2G@=beU(d&5^T@i-3i4wcNIje3;w%lblQXPE|fE_-;%q0&cp2&)IiKip6h< z(d#HS6A65I4+7j#TDnzfo74rq^}8LmCS(|DsRBm6Zr5%-kqdnH~#1JqwE?C`IY}iheN4TmoT*PY|Xnpc+1#bFlV;kZg1l@=< zSly5`c#rJV9bRr?VmKOb-&rOHj!Gw7$m%}H0D$LG@Dyw#g1In!YrFgfY;%%R6V!qs zK#eqe!8C($#0)~mAxD^NMmwX*0*bs z<(c@c!43d%Joy|=6;h|3LWXAf;Y&GrL&_2}6$1^0b@7aawKSO$wRyK|78T;X#{QU` zL-m>*YnHJUV5apEXk9vV!3%Z~3}LztjD96Km;m=8ICD|f<~xknLdAK^m=iKX6MOX& zr#rj-y@$XW7gyy$HodvU0o=lw;!vB;Sn@3kn4Y5+-Iq&d0>G;&>U@swsUZ?}f)Xx$ z-KV|bSKk~}c`8cws-#lSptfZ{zNq!sBn{af9eu$4E1JN1TzpkPYi%B%m_&v@!B00b zZcUy=%kc)LEDzLFUY!}z#})BvO}uZ?oPj3C6<9M~SDfyuKO09nN?szXFWp*3x~#~U zJeLw)Bmpj2aWCKXcBr=)yBf<&Bk~^|g~!*Gh$rd&n`=ND4bw%OVKFX_kZ|s%&X&SR zRL@>=e+kSR@5X(G=c)is!1j}{TFDELz@JC<$TOFmdS)wOSGDDd09qb*&I*NQn69~L z0hd-T%!mm+&m;}?QOY)^eT_Bpv2y9Z2~;=c2bQbno0*7neH7hltgDw#AqSj z9_B;%`30n>JmSx#`z1)vNq&b0?fSpB1SJKS*?h}Q!U{5(CoO}t?wT^Kl}pUZJCc+i zb(AftFvYx}V|;SvXe{EHXl7OHnmH(ZOOCt_gAmorMPv7bOB(gP?JccwK0O;#YS)xi z=6=fNCjuWYGqlWSTO8_3EL(CJD(c!yA1?3_bG1BN?vKexwl8n(vN(c_@nb2;nc@b1zW%eiFg!X;y5mk9$HUjQa#2bM+mUwiTMA6}BF^sO0?K&VHPy>_)N<#RXWO;JDPPVaKsn3>c#*5y1nWe_Ta3v~vbk zV3+mXMmL-hD-#;R$=e6$aQMS~Hxwc9M}u(yRe3txJHR3VxEGbg*}&*_FodAY(_&0h zDQ&<$rRO=-{;HtHl+=u)6YB;Qo0;Lss$qg(j|%_ zN})z8HRAo?W%X;tznoh+XJ^gIm>R881kgYA`$B7)n`&N$eS@!4GuMpm{m)sTI^tc# zL&+o}v2T*SeazD;od>ZI?3Fe!_q*DrgB8kb3uR*GG3Qq@HK);92*9&4u&K3lEwui2@0ubx2OgL?918npZ0cJlAt5d( z)~A8W2)N;~qlG8jI_C7-*BC+79S^VQb#^%G-&F}IV#|YO$4v#2{+(&QZpV=3htS=& z2;E$e&roQ=)Q|}tXHA{DCLu*oolF^i=oFIE5~fLZGh8v-HFJ7$dcwAmINXmp$m{?P z;$bG)Yh}1tnQIpKs(IpTf%q+G$PXbgEA_H#+WJpwA6H5jBjW z(T0`Gm)9f;aN$>Fs7t`sr+N&4WX*hjfX6Me9)Pj0sFwGAkCpA~WpMMq~dwjK=sQ^q9KP zsn+~2L+KHTG{Ex5R9jkf8+yvfXl;s=jJ-~Xn_gT+E@Pz{qDpE;yUl#K(LEyBv8@@F zHPt8b{{()6|Ad#kB)1o0l)%6&>;At?klBBikym%Y872?C2npWldw0ojj@aEE8lnrs zWKSmonNdi#!i8H>WdekFRYfLUaKOBn1WJqmMxa+Df~T00~975;cl0kmyTB%B~O_ z+Pv)j0|ia5L?0Q+Yi~|IEEC0(z@a)=pz8wxsB1y5-l*qdn>uq&M2X8J9{7Q0zOUa! zfRa6^5m`!S7qE7ZCoNDQm@e#y4_LeJO1GSdPe|nUQs+p6m*Uq7?7y*%uOzR3K)I8& z;zM73KfRj261mlfdMdd1p!N_lzXujQfW z`=kyWnLQSR6^u5->61e7!RQ-Ndf5vXh~0)eAEo-IE|{oD;(<3N7A_Ekt{byWyH6;& z2^)wJNCqp3>VwwT_{RyO{%H2(4lD#a{v9JRPxAZ=#Rq2W-~WmSDo^Sb$C#-wh>7@7 z0?I)2+6>A-#CWMdu_&6OG0p`!2WO#Fwl+!1#82U=e(^L8!j+jrw0Xvue^u`RO4Z&f#O(w}fs z`XbjBP`^d`43w|A*x&e!zh*OF$Tk0bp^yD0dFBfIIu7=nYz>Ckey>bl0f4(fF;7^z zEi9hFZVI0`-upm6+h@vsS`?tK!rP&IGO}|+d=H52%~ubNP(ffJx(M7UGs6ephei!X zS^1M4MocsYk%x-ZAcUaF1rLegjnQW!SVTSgX@pUv0LL&Ko-c~w|E-Q)7B!hhwk3*OiSb2SIA2^Y)YQlVkaOe_EBkx{y*Q~JZdjSV5yzfd6RMKp+4!eco} zQ1t91r8IUsYed&tP)6}>8Nni|!38*l<6^4qK5h1cnDKMO6Qlnbg}6x!qe~kh^hUT| zj5?n~lfdX&geg;?AXEp6ZV;FYNZQy9@)89`r2|zrs+5x#6l7@MyF#V$sgCGKI1FRU zStI2DdF@7&|7=U$h1w^txw{rLJT=S^ejyd5!@OI4{K5%!CERx*vWK*%aDS_?--Yn7 zNyxrwvVOpJiFCUb^F(^&8@>@Vw29!+5w{ZKD0FKla{e*;29_BljxPrL;~Ezk=EWDy zL=b2D2^X0JhS!!r_!gBMne>I)IL?TbNc7ssovVNJTOX$&8oQvi4`Sa4Du1lHqM?Cc z01tn}HjUh!6;zo2KL)}pTo-qoMwkpv6~8^{SsV`Gyci4R9m`&iX^&iCiT+uH(Kig^ zykSIoe#lEV0n#pr~-)J_{d~QK4_#@7XsL>|d;<*O=Ku8V-oWOT!xA z222IpjFayG*zVYEd}`==&hC)$e+%wP*Lw7q-Z(pNetX~i_QC}Bq(nZ&R^>m%_D1~n zsHlH~?B5sc-zV+g3EFykjekdMYf!&)w7-1!`e5+-AT;igI!DgWVk)|igZ&KN#>9BX z?K4ois!cyNs1NNaytk(Yo8$Zp*v=b$!w&pS_KbY1FL8|*xGbVOkJ)Q zn5#FDFKl6(%R4T^BlH#MaDQYxUfbXk=Fl#eY^eQhXypF8&?LX3Hm%XnUBlksx3NfJ zSr}WRH*r1N7$|va;_bD=oPyV@-JJrxG0_{w{Nm*DTo#esqWVmcT)TXUVA`j_x#IUy z`56<+!ygHhpa~2M)tM^!KPK+Ke;2xg1vzoP$WdY)Z-O>cG&geyMmc2NuY?PGTDmOM zD|MnWF>Dl@=5w1%@v&^qE)|jJS8@oB*V^iOe35OpD5q`e4-kMYPi_>vR>gyxdQBg9 z^_~F=70qt>zQhH)QQoT^md>{l*^jN2d9eX_@_8PzdXRkP3@7U%QCDXpezvw(D?6N7 zZmZ^i)TT+PN?Y6@=-0=4%GU#k1+Cf(KRWq(P0B-e7xKih%jyk*4ahfGemB>vjdpMR ziYob8#(%cUKDW`FlSq=WKD)tFm-A|;w*oX=yq(afDh@?>^-6-g=z=_3^LC?hiKo+k z4J?Vm(rq8D4$)Qkl~$F}!$POJSLJY1!4Z{OrRDlEJ_&*53$Js?)as%eAmXgmX?@Th zO`l}m(ds(2I>{eZ!JygX!TI6v3GJDD-ULWQVP?}M#x?dJ zODQF#v}VXR=1%3cNdAr>>XH7(fXBrTUqbfsRkLUNVOHu?^W2%qG69S!rEhEkAH8gx z37?058=5;>-0qvG;NtGJ&qd*EH&?~u$PG^PY;d;hceM-g#A6l+fH#ZWpo7>~!f5*nycr zCsrg;wa*}WU+5tPkzq^K0W@a1UDwe0&>3*=Bc`Q#!!1d7 zZZTSSa)8-Z0XBzDjH}>8Kt^XweDR;8usIR77aiU(tRuO~e15f*q!o5=a)|_kfAqLb zc=>-@kka@cBKD<};y7i6qf1)n_tXGDa#}s;HigYk@khJ3B9r&zvVr5<@ZXM9>jN3a~_ukbfdSjIWXP zqS>ilj+y9GE_hZW&P3I`B2>axO`IMR|CZH&{p5PCAPbwFX_Dm>|3*4DE&RPS`TB*7ZFbw?}LVh5NSYqO|-d@}}F>SW$z%E!jo?7;64N0MZ*ARmCY zl9lflwJvR15j3qTH0^PHTX2JD**Hmz#c@p>}U&=2WrrUfcpmUpakjO4ki< zLRtLV?k_?{pY-$Aqp<3Pf-SX;Q%q$h#GD75y$a9#7(4xp7h>j~AME|?)om<7GJ?Cs zc0+)9c6kk0nMbXamG3Ni=#OEfD@}mr>PG%(rV*|@ZEZ@QyuYp!yssVz_j6MvwYZ*T zrk(?Q{AA~irb8gZ;SIUq&j98>`vg&h6~ZSii?Ly+W~ zbqTpRH`w?5W_RzxJk!2uIdRNN@^OQGHpXu&N@+QwYRHwFZ-3EgC6=ZIDC&lQB{dCS~D zR_RY*Yt!j(z+94A`;euL ztQFAm+D3hoH<)HK7y;MJl7nW$)3{jGelP(YD_SkSBh9PHmfY3YDs z@f0fyW+4)-RotmWw5xg(S}q3c{c-}0x`tOPxMj-X1zKh*V&`BbRoXos(<@S~LQ1+o(<0Z_yn*m8Tk6^}&|EejDoqF1#!BDHhWzXDcJ^Xdc3p1d@=n zj#iUC{aS)uD5cxC(8yui*KK<)EJPGwN1zED_pykdhKzOA;K9q^+of z%S3~sf&Mfkb&2NhAg;e5-RhhB>L+W&k3119gTNY-F6!*E%VqLQU+Fr}-lU zf%Kq-q^~*6h38W5pokmrYS(6*hP%pNo5d8&c8;EgLSwY1H|>2N=VY{MgFYXjQdh2j zqF=nBsK*=If4nTE_}hMtc}p5y;e)+j6SxYu?t`C^>JuvQ`CO` zebdQ-xF#DK3{Ojk4tG~Hj>HKYt1E2IEL9bp+KO~Xhqx8`2>X;W?c!>^n(CJ3679-r zy$XhwW*FPZ>JNoAV*sGp6{ud30@Bu`Yno!cXat_59{{xq1OQnn<|(!ctR_IM>WjuH zynT7jGL+O@JH?YfBkEcm0ioK~3e8iEO+ssj>`?Pl+H2WH!@lM@wo6&xfjslsW?2?l zM=RRNl*QEx>MxTZpl6M`an_MhLFfqle%}BZ4Db! z%hbamj85fWO3)$Zq2$_nan}eZmujvjg|UW}x6K{HDwc6I`X%&1z3!&6TnD1t#Iea)s3VXcj@@q)7!I7Jx#02b;-dd%@=~KymFn4NxdWJ154|6})_jNpz2iHVJ7+kr z%8D-s;JS!BiF&t#a4cb|pTUSfM4Fz76OlvTaf}u5n z!RADi(ad29@8M7zajS3uKx|@fqUmp*3Lw9WKw_El=66tbPZ(x6j;a zn6d}3+1&AASaL!3bOWZFA8=PruI5gg@<^}V(if5IR){i%8m5TQlf#{_)QnB1}NjqEY8_zcim28fG&S$$3{Aw6apRzrbfqG;B77t^1Yia*hfnue2@8UbV9(o zOoDk=k61)eOMR5Q7l4rrcytg=Z=~>r)X!K$O-y9mQNUi>=5d>e`tcbTtEgb7hyXg9 z%0zXuUs_2v7ZhNpTytkFsFR}0EP(Bkx88+V_sNf< zvpu6FnA2&Zv$@zBq^{y5Z#jl~q@t-?t)H5&Kbr?j)rt6~O{iWrBfGN1N#Bal=Qax4 zZnvV|;@WI`QRig?pbFF{e?vKkxLrnrZmj9kdNP^zTfb>~a8+@4S%6%!TWeoh4TABO z2?%sxhfE@${Y_WBp=+aOp<|ykFQ5?M+GV?8xM8Wo1&+HF+iefDtFQi8jV54~8m8PE z9|hV;D01nvfZ8Qomb}7TJ8U6#w+*Sz9f(`+|v zyOx2Q78@oyPsYq&U}O5rZd8WZ`PHH1K>cFbTDu;guR1bnuNL6jYlnA-wp)^*6lsKO zo~zK<#Nu*^y)iy-egZqXT-Vksn8j=MS8mLKBR zB4;FPUMsx*aOe(##)RbCb7a5=YQnn5_{X~HG@Uq-=XhJ+bj?cWnhM4KhAp_@XIS-j zvu6 z>Jlux3fLl zmfLJQRtua;f5u}$=S1jH2e6d=0zEa*#Y--21pI{AnjTcJ8o5a*?2^Oz-4A+jze*IC zv%{w0C!Q)dSUGqF_Mw1)jf4$imojb$XUXhGHoVn4Hq#C2p5L^S1tM%QiuBvnimfMw zaf++cPT}rKSj&KsG;w)}O|MOMspgfFU7Kkbv_|<|Jw3iecCsNP6naLBOOFAIPWiP&rL*9k6B`wBrn{-_CHU$i`E_3T`41S(?R9)@m1Q}H%F@R)n3pVX<|K4z^Jvzw!FRY>Dg~pi;b#F(n&UkiGRL>x*{i`dso2=SKHY)3& zidwb|FUC+4!)9Y`uF{U9NqEuJk#o5t3+M(dZh$w2Qs^l=6eQHE?%Hs+NdK96l68A} zr21145XGw7X0l_&y0Ns_2HDK#-UJs|P0T8EAw>p&0-}17$jPDUtDTc7odE8flEHs+ zaI`}&{L1sgp0sNNC&Ah6It$g+f}<6&oMpX^HXpq9j3_<-29M6E-RSa7)I*{P60i3=E#ptD$)$Wgz7OUh=fZoa zq>cEG84qr7`p<)BvXj%ROAH+{yONFI4#gy=#}pQ97BjpiCC{TOn-Pih`|O~v1zhf< z*|y7{9ZywfSDP8##IXnpj*`;|?ARPufDZSI?5U(x_Y2wHbcU70;zZA%!A06@tF8cT z4aR~ly@qR*jK+VtmR|r%#ZJUDfLc{XAB6 zZp|p2AxO_FG>`;4a&=H_4UXq+2PY$`n{vJd_e6zh9x}^OT#sH=7ms4zo1bP`TiIEI zD2K1$JUN1vyrqSV_NiUTu6t01qcdKhSjPbL{L4cMwkO!^)XxX7rQgODjGiS9)HG6j zQx`40Kde|YbM#ya)b{*C@t$pV7dSb;!C12;L3CLkBCf~9!2*AwOwZ%~{ho1y;+WcO z@Pajw1-yLh9$}7D5w3_KtybroC}~%8gWX4R2zc8bT+AS);-`U7>~ib5zL)-C?h|9; zL$N{7Sry!o+{p$rY9cx_uo*eNM)EA#;%68gAzFtwoOzPyCr-Nb`)DHnD)8cR>N-|0 zPwZJpO2nS?QksMda>#i*ts<#3#$+~5(^T@?0r~>UR16|_s%U6)+F)*teoBkx{xA8? zkw9d5Id%yHKD}>wL*uS0Z6>5GhiX53W#8gpv6g_Ht{3g}1{ag>a8SK3s;xrGNN^6<|47Z2@|+DAD{FV4D55Q8E3=Bx$L1SBdUyl4P46>5U0 zQ%3ADl2BVrT@@^;d_x+i$cFCE%Njdqt(9*(@{H?j&h=G{;DVRFFcubh0n19SpJMdm zR3(gHgyDZ*B>+qCpy8vTD(#Xk?6{0d-B!N&hYYNAmU{uXReFiNTycBs{h)lbVeOkp zVgm|o^=j>IOTidig5#e$N$NS9V`5&m+4UXHjFRM?3;RyTzb`#&f4-%}c9=eS8ML-P z+y>b7`*%KWK>7msR=v^wRWIEiE|d+|Z&uXv8%G0CZ(lpJG1gMQ`s$st5p%P!;9k=N zmrLjSRZ7QUP#pTU&*#gPWd(`f9ZQDto7Bc0O{(>4Jgen91;-;)K03V7#()?7hP)i# zHlz{c;C8~(Fz6M>*XErTuKdaVwqn)LI6R(!v+kAJ#G2ZrM%|{;(c>h(T!-v|v1WkB z2aX_Y@}D7wb+Oma5IRd4Z#7>Cz`K@Ima65JbRq)6jxdWRf2b+~9#N9u#5A;<{7_HC z@xwK0SM%>_GN5fJUjS#CQr$Phc`OR5;;1cif@Zv4G-nUVs@&@pvd+`*mizK~9r6xL zgAw}{-bDRo=S?Pz=;n$tB8~6zRG!1cXsd3`6mD)*N&eNwHw?y_m3i8>|Utr9G7 za3J9`ri)Z$2ckM`kQW;wx>QFdLU0)?ti;iMFcTkupWQj^23FD7j4yREaC};0*X$TYmJ1fXB0qOV{{7u0K?5*Nv)+nj zc5ilZ8S=z3y5te)Wc{-=B2)&BG>diWQ_rfg$3uZQVmw(lj&yhqll$SP#^huRD|$;1 zNUusGJ4|KP@}i7DPG+y$wEfkJw#dCY>OQ|>z7@pCC!vU2I?u+IcW@0hvk3C?4BPRl zdnG=Fi_~fwT%1p(*Iou1zPvXBplKm-SA(@WY&A&h2UI<>6r@s}*KcML6u}!MzELpY z5Q^?ymkp)-%UXmpwb=(PRckkxz7%E$Y7H-jHEC-^cEmkiehShM|6N#n-RJ_>{L-Mj zkS}Y;{P#Sx{ZW<$jVDd{7Ov~$V6g%JAYPReX#P~Div5&aG&RK-#)Nn zWLN!Eq6QzVB+yTN4L46}n%uODRSKNQIZ)UWIE9J0zH2iFOF5aoH-iTu17lqoSzC?$ z&{8b9K6k9f!tjwzW`MVw5k3Yo$|~?7e!8vMp2-3`l5gT`7sm)fY0~euyvfNuX+{uo zkz%6_?77o+Ll_Eq+B^^;x4?W)mTm9-wQ+DgTeQbP5k)lKy{n$)t+Jl=B2 zk+SJajU6|ayDT#$f0ZYC*<&j5Y%ZWli3)7oDdM15wF7BX0~&yKtGrN5u=+D~qttpA zJ}^|0EjB&)8-KwnEqUDQs#%#-47-`X!;#)zo~-6fD-pJK_dPTZB*kx1?(}UcB3UO9 zNc!wk^FJit9@1GDfAR=SDdR70H8Ei1{mmW z8X^$rgSGqN-^{bH5YAk8xSLs3ky9iM0)lMEXH1q>aAEE8jHrgV3p^ol6dJ>_Z1%f#+VjP>6f5I<{C`q;Zl~+7SU&0Tz+wsE#qo&AF@;YUoR!v8PB}h zZO`hKDZ4r)@@QM>>I~f+r+ivjq}{5EPxKEtrLQ?P-Oa0rH!%R$U7gzQ?-ieN4>={> z7L`{#6(2S?4D-_^Ov}?^scwQL_-udWle>w@d`U{O*#0_G`zlMm8q3a1NgIfsc;cSC zB-`P#Z3eRJF%YnA7XF)lux%z*-CPzut5m1-nz@)>1$Y5 zs?{>pp&WmeYbUhgp0KKv#| zbCH^E10@bm)2Jw9XAlK73JXuAzHDDZj=5CB$T(}cv~1ht9g1&jn|)i`HV5xoa2$C2 zwp1aaZZg{)4sOzJVp&u*$#S2uIxrb`wdYlYDpLLx5>%y$Xg0eY@Bu?4_~QM`$v=mp zyPILAvCtgdaf8J*fonXw)g+d=hkzsyb)0N?Zxae~GTkTJYQ5aVkDo+MH9IC_zNLbn zpxY=MdK|&kZ9e<95FXyARN*V8Vcdm@ch9qF@cSxegMi4NI85b}2@ZCL#V)Z)M1It` zR}~u-=h6Ql?3}tQfr2!eq~mmK+qP}nw%xI9yOVTm+rF`FtK;0*nVwm*)~tD%c{%4N zoKsa_?G1x(3c((sH3frjv0PMLYLP1M&`h1Pkcl~tYX%Xqwj_*EK5x}%DSkUHHL>Ijk2@8a4okWv+Qe^wTK6qeLadJet!zkUxO;%M zU^Y{c#L5e>KxDl@;?1a>f{ksw2W0Hsl88yHzYf{COW2CGxZ8?XWRFahf@>uO3qR$H z%}kYQ#Fxsi%QN;Pv{*JXyEb4Y8 zgO$>@RH140ikLxVvi=*ha*oLzI^VGEDog&d)zFXD?>?Z)?gB!0nVE}jLuaoleUs!e z9nTV4e0jgM#@0jZ4@w*&>9S2=Bs7x831tZZA~U^-gyu_%s@+6$HW*?e$~cPGN}S_8 zqv^|#A4f;k4H|;H^{FRVkgCG^ugGkTnv<8C*?`||Ogh`T3Ml;3Gs{#&Jj@x37V1P{ zEOYub@|%U}0%1?ESXqjw+TvK)h>PV_c{38iwJo-~>LC{5FSCV^8OuFVWPx*zkIJnU zrrXG^?&5qont3J{2m8LhBae#(Zhx!=!y5An@jjXO9#TJViBB-ejBS7NYW;w9>DaLf z`g#QmK3O!VO+c<`f1uhFg7Or1RkCnEgYTCYt0@;e0he4ajXVZNY?{`^p+6!p(c}>T zTd$(pESr6>2j1--oMcn2DZB!*Hhu@s8-avM2k_z8H5PFgedm<(PqeZfOWr5B94ISg zmc%ENX?0y@=oHPYQ24wY$twSrkObhF>Gp?*YwfH>#W&83d`Nu}bpej)A%fe`luE=* zjf2!qnEH(OkU+bEW#8Z`7=rMZON+ik#fCqT%u;``nF}qVuVQ~U zZ?Hi_WVr|Hii25YyEB3gp{XSg%iQV*Mx&j&zeo=u%CS4{nrM)uh^%}0|?>z+zo8^@g z_L@C~TsRs_q-0XIQg!k|g3OxOlwd-P&Zd-qAPhDZLxWr+_F#hfS-R=wpnDjeeHHHsPw< zdTfL1lloP zf@EW;TNYX@X?e#y${LWwpRWoy)00No zxJkr&Yz*@@uJlZ)rdlhgl=?_eadgW%M2}}Yn475m(Mr3*Kc%B~y}8hV`LoG8&Bu=? z13aXBq9@2DB)TWsdW)(Qw_!-*RM=e-5Y45FGifcNjy9gD;#EtXcAE2_rr!U$YN^R1 zviBKzE_g$g23$lHTN0N_S& zEE*DF%dOKjpQTgSuGE%s)$5bPi7f@}SeC0Ob7v_Rke~gts4tA$_Z!g`T zE5v5#1ReIo2~iltRtYH5Sc`@nOg}_SbP|#LmrIhrUhzzYUa>E_#8(<#pr?sgWBc`v z^nacOMW$@5JE}X)HFbm+Xw$-8i z`Tf2r>>xVOk(Fsw66m0s#-QS(CKW*soy) zC0i6)of?}(#f6^$5UG}vssmiaCZEBtYze8daE=s{=LO(4=iqhO(kHg0JoU!>^XLz? zk04|YDeMsJ;a@Lp)sd@xN{T`(gG7ZEgHfSt1<0*vJ{illqV2x{f6rg2e2m1(Nf0g2 z1-T{!JjHV^sGpEZ1C=G$hhC&G!mL81`CtAf{&cOK;s+@wP1-{yJ1a*l)F8GjhstCb zCgwo1!RAK7gN*uml~^w-oJ$a)+6q~r@|j9`fhz`oAP&)ZFot0z#0`{{6t-vqiDi`d6GVwrUcYTfCT)ljtsjuJWoq# zR3h)cV~#vX2bACO@BnzD!yU1VJtD34`6QscBfxiu-E2?=z=SB(ekAYFDiPU%-X#_H z@rdqY{gxv%esuN=m8YDFUR6H;`IRg2VI?d77WzW3?rKX&M*>>A3ijQJQ>uV~~%6 zklgmaTorl8WYU~D0XYz_xOVKy7@%~k)IubU)R!`=J}Bkb{~nS%@hJ(>1gwNcq6aC& z!1Gizv8mM7ULHTEa<@SA0$emD&G&Haf{a)sZY+o+wTnR|a(b0!dod-!ECWdE831R8 zeY!YNSg(AS0qCPV8D$6+J#oul?F1ydFf)Jj%%PBfpQv5kt8I^U8%mdA9$l9&7-iybnNy#rduqjj{rrRe-J^@$|8 zBtgt`BR-DN-nWQo<4rp9%KA);E~||(Vs(`0W+%=+#p|9K**2i0Z^aX{7{#Xw8C9RqmOi58xedvq(w~k>u;616&%zCHE$+*ARx5V23k~Ku z&BSHxiYpBt;FVc_r}?pA6mDUS@V3e99$YzAjG=Vlie{W?nWC@d#Tr_5G+6Z6@$@iL zVPAE4n^K`9H;(@z&JK+X+d;7J8k1e&gd`3Kh5qQfV15pka+1n)vo0%DJvCKeqd*{H zrG$50)INf5J*rN|2x5Xyk37OQH(#_wljj|!w;q)oZFfVp29C-zjUcQ%?c%8pjr9@1W!4gm@+&J)M;7zUPHNYbr1es9dU6QZ`yaaJ zVBA5aN$>=9{^1{FO+^RoF?iVEU6XJu{M}ZV*Z_)EP2uHOoS{rEhR{LUUGhE=g*1~< z$|&mpz+sr0GVL+2E&^TGLn>3t&{O`D(Flne2o{SZTzm&vr(8{!*6kdmvii4a!VVTEbWW56HiL9Hwu7RD5QuS?VNo~1}X zWGCy{)uPnk$Dmr02wb`}CZaAyWx*(56!W_jvyrrpG~^(?WJ>dPX9AMpQbQeiEOZ2t zoHd1lq62t?c?z!JfL}jLoK&UuPq3(cA>c$eX_!BiR(8yAL%Tm|s{YN>o)j>YP#RU1 zeK)RRbIA;By>`D()HlBWI){+N`>`=WA6E($3$Wo9D4T|Y0YnSkz zAU{n?5XQ=gBgeEyni81$k1oZzAscRpX%1R>oWSO$ghs7qAxT>Y-z@EvP-vCY^|hzI zFQeLg-K^}eY8bHVc|9^*IjFm*EitTpc#`B^wz$l5=0rX?ik+?d02Hy z{iHjpN$EmGle*++hI9OINSC)NZGGB9fK9Kq*iRR5x-j=_{Y=FV%)i<8C8KI6;u+@QDS+XH?`AL zUt3rW?DrG>Y?#KScxcCgW`H++0~NL~FzN;2Ev%m-|xKy?UA|uwHYrdgIdrZ7uQz;?}rCdG}X_g0) zT(iN!HVZPhO+o)CkhTU9da@ob`oGE(Zf^6R%Fk~l-15TZYlV)N?tHy>7~y1#(i zLb4W&^QFfyINwu4>=NE7<;$T@ZpNdvNp#GX6cIkzpjYUm3$bI$hEtx0^`y^FN_Ojz zHft(D?^ezDoKZa^R?z-~*)_aMdKn!e6X?|%R?pTXbAg4Op3sOSm6g24v;Fa0DwhQ+ znQt2_Oh~K_4;sAGOpGc@E7`W2b`~#1J4C5D9tmeQCk@s=h+fP=4=8F#F^Ffty6Wz-o2x#sPZGeoW~O3W6XS2bO; zR}kC{t6O;gouF~9kOpPkxXWbe=70>>ZlL4(Yc39^lw6`12C=B17K`Ux{ZMKExa{U! zw?iT{pt?TggisjV7l+pNBQeyP3Il&4Grey&o+T9#{~pcVR3*6NiIzI52*Hdv`}47g zN-}u;Q;h3q~&g(23 z!2^R1R+b7;2tQGBClodZQXAEUbbkmJTxP+Y@u zHbv<3<`FDk+>9|p?qE5FC-v$BByg48{Hw1yZ{lP%t9ClnoxYhX!!wVu6urWeaSHm? z@X7j_q>`e<VE~ZpbXf(w>Aw`BGnX`w%nI?ERwq>S z0wDht->QMeC+xV*hLnH<)->^~Hj*s_8s z4B@35-QdZxKTSY3lkkIN_+w;Z9e&DET$p@DN)(}Q;pAYLYEsYw)q~12>fF1<2<(Vd zUe!N;h-Ve`s&h;eO*B=0I1)JPnnYsEdWjXlLKqEe!{HyJGkkg)_z<^=P0;Gce^x|= zY8V_ZWGH?sQgR;$2#kN<6WJopvtqe0jHc=pNWRt3vB&cKvA`=s4+aC`@U(2XC}34; z!dtz-Y*|7JpR#UPqATmm$%YRMb5BqvVIhZf8oEYm?($4Xr|?Ws(gp>lBU*NAO(P2o zqc5D@gP+R{%a^zX)`%X^?Si^fYvmi6j%7U&gf8wLg6s9P&%pen%QaSELKO)g{nJ3L znT>;gXyEvY#d}A!tROVhE013RKet;2RC zH2Tr}(-q(G*djUCy$k&MMyffrP)fsRyL@D5(y7T>H;TIBpPCJ>tbbgc$vp_fw9Cl@ zWPnY>{(lZ_PDh(D)e$uhSQ2~bfm+eC4s4YI3{`rOHV>N9`CyGHUJo^~Si>J%xZzsV z6ns;IQhAcr4S~(D*HCzJ#}Jtpn0zq?te`zgWEFR>lhQ~me>CEqXwUbdp%fYUhjCfo ziF??n^rmlJS4~&+q;nc!G-VF=Pl6vbbpDcpNqr*tE5bST+xvo$MkY97cS)+>+NsC2)=G7oW|#Y@-I z(H3tQ&i7XG#HkYaW{|Wq{A(n)lZ6bYfyc$LScvgq{h?>HHdMuWB2HIaiU5+=Q=#3* zI3k5R3p`^v7N!}1?QxAX#DL zMiz!C^Cl;}*DS!Z(7(A94Nwfo6%B)y3BeZ1?vKo`V~tRfY#)4N3bE-xm$mV-G>ydZ zvtDN^S3%ZpoY{@ZnA*6PzAUg2F<5>Trr5mmWx3pVs?`7S z|KW+tBb{73>W$^Ep^yJ42O-FWxr_S8BP4Vm1XQLfFvVPZ6h8EA#d>1}e5*Ah-uC_K z8!{^WRHX36lRu{fS~8Q!4gyFOoRe1MtIpwjaPro*@vN2-k*50P*0FsPiu{hu>|cty z6GKC}_Y$_;tU2K)pL$eP^3kTItm&U6>t!2xUc1dyYBd}#L@`T7qAnjH&X!*P#-;nS z>g2g++{CZ<^08*JqnFV0mwD%%7=3c7b@p#P1^Hk_Gfr65B0}%O54A?|Cuc`ho25Ty zm%*}mU}QV*!ubeIiX4#6>ks3e?a&MX86>t+Z+Joie}Cng;D`dHkRDa+xT(#YY(g2G zZefy3C0@>@tP*omIutLq>5C0s&~Zo5YSt24evbz6r3qPLl-4viT6VXq2Je+RRg&k23X@4$(X z06m~0t#P%e;Gpb|PjP-KCa}b`WY?Y)?C*dkWHSl8U1oQIKN-a@6wg+wzD-@4Vx2?F zq#rVclLEv2!KPSizg{xX6BD5+EcB#7bHeF7(8+47ot*RJRKKI18h^i~$f)#a|JI0# zV8`3n`!)X7E)GI6Kh-4 z)L`sXKk+`b4z58)5;cQ;5IQ2k!KV1x>-wi20}G~C7WEE4)z({ims_D7K^pcMvqtpP ztAoV}e6@Ry-(1MH{ij(Y{l( zGkl0aK!tZt`a+^ThrDN5^}dMqb|QqX%`MMlY~~0kN7_Ma6>@6yJ4Qr*O{gLN#GR9< z3$uEpStfUIyN-VfdrTM4mF7m>GM-M_$Lb!vMWx=9@l&Xp{rw?3I`s~zfm3A5r_$^> zMn-7LG5n(A*M)<}AL+Kf$J$k$H1mt@<^^{_o)~i-n0+$mJ{r8^9z`nmKf@kUv7>_R zrAEC<10PKc^=_k0{NMz5=BGni7q1n!0<((8Xat^>sKKN9sKSiM$yQs#A5(JO#CiliYHyDSuv+4SNrcr;eAQ2vB4BRqqdS;%Xa*D%;{-D)2o5$^!z&fyO{bad(VZTIsZPHe>+Moz;?9k*S+f7EmuHxHf zR{z+@#{V!Rf;Aj?FoSVJk59PVr=byw9bf+4bWma9EyA(5YZjlFoxT6|hN5g zi`szsC842-++tw40B3h%eQpO9C(`?;v6@buP*vWZIGWb^^ynHLB5oBqMw@3kEi@*45~l>3BP$ZBb>69AReX%SQ@WbQb+>et>whSNl58EGz-X9b5ub?!fPyaa5id(p`^LctGT7WA-s~AD zE8s9j(-H}L@zs+os46n4Fb-521fpXa9eew9kcd2)usks^Ge4gYEkRDmG<(7y$pcjHbMK=mgKQ&ZuZ*v&n-iK@zuGg3{8StOrNwo0nEgUqRy zJ_k`j-VmphlSxrcJn&GC-Q}Oy-Tb);Z9sMvb>vYi@+@`9FLlu-{jH;sf^@W#yK$~L+H#q`L9(cTH~3Lmhyq_6Nem}E`2Z6egvLC7-lbuYy2>B3u6ITF*H{eW<){6c|9 z!p-?c1up@@W`Wd9L!g3Ho7;(b+MULi}FaJhyf*SR%Sn*&rEHk z2%;_FHsWyq7FrMl*Sn&A3oSYDz*T0L^;*Sin29x{_&ADMEUe2QYn4dF!hYP2RVu$5 z$RrpjN%aA8C$SDOAG{X_#vq;)^-pisN{3})J|B$oB_)ARc-~$r!;Di;wssWx!>y29 zVVx>~?HuU@`lr=+YSS!c)CUtV|C?{3v};>!i3`2xqXAwaOVv5kp1?R;JQg#~NkrlI zD!l0B#0ypx-HzS8>~GqfXwyp+W;Nq!s&6FuJb-?*AJ?^-*!1r})o{3HmNg1$Dv^4! z;f!dEa!$O2^u6C5Bs#?86Xl*hlRzpif?5cOWrw7u+ogv}sbwNv;*q=~IqxKG;EA-! z9-Md^v7DHK@j{k-!3p%Y)IFI;>i7&9@nc5Wi=^s@DUCX@3L&8d`V3ha6N)@=RJQwP0f4o*4>X0)uArJE-Rw=>I61A2{ zYO0l$D?7ZVUuHls*b_}KKlD@SONw?`j-(}aiY50HgUoYQ4}Rq=)+4rP9`nP09_wYY z=cWo%)Zi5f;)O(yOCm{cFr6h{BRMwG54l*anN+;^)u<_s-><69KVRAKaeFln0$~84 zmxvMHzCY%a@neJnBjU2vFs{4q%v)~dv6oL^q}tI^@03*G8Bb=hiawm~Q>m0mWj!Ve z1uSK1JL2z>j5WZ-P%C;Ze8%l8*St zdB0>7dyc8Us#VY9|H;u|g7x4V4Kn;QIv+NjbF8T69V=g#m@&{rAerk$FGY@!GPr1@ zBuftQPG*tokU}$}2}-T`0*32#6Dh_kFT-dfWo%X>znZHK_uGX!+&!2}tZ`H+tmLrg zGZ&Owsdb)q=Q8hb%Jk+kRm$|{u>fRoL5Q$snJ(by&eTO}fnZ{?CJNjzGwr#mV+~BJX;^|?t8Ol+h(x2+#?DbblBi}Wv?r{=G%)$V= zZ!X&h>uen_X)k4gab3;#X3Ds`!^E!MFP>}!#vte&ob<%<$48SS)|AlNg-%W_l`fE( zLFDHM#Z<4g#6Mb!(UHkBp&bw>sjQWBIWd(nyGAjCw`JqFtKkitT0UCP@jQ7CIJCS2 z4q}v)>ngA5-c!(uIMz`~4RG;Iu2xJoX!mD0pWo~lwFYvA0o&3~UpYg}@FRHS9`q`l zI?gb8lCP(cHogQNgAK_3bk|78zRu)lhP7mUjl@@PNJcuBT(#f^r269z&2lR5Ve;mR zTYk001~k};T?_`8XPBBAcxbaCrP8j6^;j#2tXm0kd9q7rvp+I42Ja`4Rx+UNu9az# zloVG=;bmYdU0MYO&%?M6rGZ4O^a&AJb&0t~!ZWg%x2~a4$luYmzfW}2vdnwfn&jlO zwR4)YhIfUq-XP}R>x!+#m%Fr=ySR&XFx7jaNk3%Iw9y9Yi2El)9nLWgjiX%oD;D~Q z54|JIzb)e0M~*son?m`;x@i3?WULFkL~d3jUo-M%*r-?WPYk01pzAzUKB{Pm({Zd# z&ley?b|H=G_IU=e6nVo_Rl>;O9bUtU36t}QOcp>QkzQx?a_Xp#1P5$jmiB)fh6G@K ziJO>L&Rf;i`}Q5JTm1w^8erKU*`zsHNXV_3KA;6q6BO}CSVjlH3m!J|F9ML5jgqQ( z#uy%a4_OQsNAv|d{8%UgNQJrjVe9qc17X3||7zmRxsYl>wiFVyd~@lerc^511PM8mka!L!)4$LO0>y3nHj+D5M3 zcWyOR5ktGbVb?XVZem<)`r=5UN{SL<{{CWWihf1>{VDm17)A6o1}u9?hZ4L_j|1yTsm2u9`KxkDSdGa4@fpM=@WV}7w$H+5yfxH+mo2FWb$-2m)*=`Kc7DS7*r=2I=Iqq$C zt-3SzE;pz{%M8Z^I!{3;A|lLmcs64w4V& zv1oLU9GYd#WV|E zLF+BN&ICErDFp25Bi9lBOR>r;`9=4E@q!$*x4pQoT=hF^NSA%`z=Z^>dA9wCZxrqn z*p-cn#QRktS)yqlRmma#dRm!hOceIXcpy7w9ef>s*Jq*P+VfZCM`7Hrs1+iHzv?@! zS8A)47u&qJZnhMcU(D9e&(|(rf_gK*WBUde$_gmGE;^+3rwm}O-#Et`%gVIfOU%|s zkD5{^*2Pr58QOuff(rsze zE;+Yg6T=10reg_T^=-+pznQ6@sXUshTFT}OV$<8cFeT|M9DZ$E0Zai!RX|p=mn<+( z0F;Rl;CsAq4}$DS?jUC-AbOPFqGFZccsFZKDgPR zy30AbPvd!TIPK0xtv*z!q&twfH}Q3oL^~%4s|vyKY$AkC852nTOkta;U&^OW)rsdX zpQu5#gQwWV50MQ9vI$+F;>o~ig#Cv62qX0_YT1>vd=XmNqvcSGGZlds zqY%Q243N|4lT1+|_#P#8bo?0}-&6q@fIFKjYNogBs4H9Fj#jnD+;oXWM-sRJW&({B zew$+p9Q>vyWCrq*e8hQfL)WFETMTmRbZeoPK+2e-L4-l_=9ASa`}6UiQL)O>yzx%~ z&>8kA2J!`_QRy2kG?m93yfe|&lp@5^Xu3rMwFZfiDG^^KKe#UQ6_gmYyVU$I<$FOcDVVPHw5`IQu}+ z;I4eKzNjuni^}~2Q?t%UqI`(6oRUcDT@# zvlOs|Ru|5m+!SGCnA#(Adf#7e5joliTKNQBh2*TtRYL*=x}`%ex-*k8$vr1Rs1btU zNBS=1Xfco?hWvR(S#d}FZUz)JZIUkgGpg7)eOJMR>0ezN|4DXkP3cT4(3*HwtoGw* zG(%|nB!<>fIJtPx@FIgkpyiU|5?MUGE=>|=uGF}H0*OIT71#&<7G=P|M=O; z_j0JhldD_Uz8L9@Ta$ttsthNmVB!_RjGZn~yZ3gu;KxBBMY#hk-E$sms2rgs!AZ*_H=eDSWi5Y5fPPfUHrnFR59>kV~)8s^Krz zsd~D#;@_~jGQLq2XUp~ae)r%Ur$Fk>ectKzy5GTbwAh(dCFJ{{0?CQG@!8{e34wAS zBHUhjPl4JA7G%9<{P|VczuW%V1@?U#i2F4j60n|_zCnZ-81Zd!OK9+Hc>S3$u=#Vx zd7x|R9?LZ#!DkeEE}pQT>JbTxs0P4SD2a=BMbM6d|1*bA&2#WrnA008W0ot;#{d%I zCoKAdS3XWz2Mijs1y2YzA6;oreW(vBcIw?73uF_YRy_KYZ)>nCpVH{$#niEDhvw8n z+RqapsG>2G;Q#~y7iJmrvIys+g1Hk8Yqg2N^45J*yJJ^WvBZG5Z{V<8A3(lG=Q_7` z51U|Z%+6t`#Niw{qHWIMK1rv;qZD^`unDHVc4v@Bz?Q#`FJd4f(+v9=2aC?KK3Kcv zC~kKjezxRK#f3rq>Tf7srZEi!V6{S5(_c*!$`A&~?Vmp(Gg$u`BU#K%5)~S$4G4sO)UOmo%&w9Y z)W-)-hcbJ&Fl-N&<9|kpv6;Z<`sv6ekRhGeMUHgZJQ}V2jk#z64v1alBcCsz0ykPS zYdMKLy^DzXx6okDcq1KAi3fG~8YG-Voq+k%f63Dkt_?(br>`_fQ~y$I7zOYlU)jf6 zhcvh24{Q>yuHy1OB$ACOiWTj16xGS^3P(_HICXI3FNe2CY1-S8ScrB01(`XP07zAf zah2GdH*u&N#u^#Q{#$qrAkh)lD-IK}ElLuj!`U$cpn8iX5HHE;RsLI!JZL$T!p#h8 zV=sX4(P{4Gs0l&m4bf^nH|Eb|DopyhIGnE9lY&bU&h{$;T2=^bz?! zS03#=YCeAmzjx%YZLI@_z>O%?5p}^x;0iUFn;Lt}D>w8o-#YHRPZ2UH)|}Mvk79~Y z>)0~Zb5MG`ct{w@fjG1k{J5uTREh%qx3SV+^>pYXV{$rte!0~RCAkM!_H6lzPNi8C z0uhqsKsr(?PY5qk;}z5p34VfDY(F^%Sh=TP^LnVer#ZFT=!pi`Lrp6LE^!}_=N(VB zUrgVf?PEv=0=D(TgVr-4+UFJI-NlCH)MDyGmS!jBx#Gnf(s1Bi-4i9Akj!Yr15ULMiewO|B(VVK2*vV7 zv004IXn#_N%rdNj;^>GZJD@!}p#>b?fTY?7fKcJ^>q-4oeB1&H~= zXWk?|OT)m-OIb(3+B>#Kj`5rU01Fr{^~)NdeW4lLqBh@IsywiLu|*FUGWozT)C9u& zs)wxxI`s{4qJQ*62lNY8zEJwg4hUAgV5Wc6y>Ox%L~x>g@V2}lqJNLq-Z7p$IPSa& z0>Hl~-(Cqr+OWGrZYn)vEq4$1ojr*oQrD?X?b)_=Wr@p8SPDx+cX)@{?uEuSd(#JS zAl07eGWig)YE4&Qa&o3VS0&!LgHK~FZAzpf2x7#kWf-%@=xYq4Sr0T~rMF&FzgdO9 zmMTl76z z6zBAe`sbj22n!?UqX%UY#3>`2k@s2DS}J4(^W=ikQnGsi_vLblZJ zVBwQN)#jw$fQVnVA$E;rUyK{Ppr77ul)+Dn!#Ar@>RC7k*(y4Rx3SZ@u}r%S^|O(~vU z#=MoXTxb5$OEp(>0oFs8b~O3%@os9yCX61if*O+$ySHZLXif)Z0lMOxBB@?%C5hx5 z2%)WE;{0sr^aFBy0XZheu!unO`Ou;eJJFaZ?4@dCNsdgYwgHFy2)fU|C9oeJ5q3AJ z^gT`Dva>kz{>rV-WA1f!&*M+BPIXo{DfB(gFn?gB5*9we`(%n+wH2a4V zPwTKjv->Dup2)ZmqQkoPY+Z!OlkUrwu@5n(W(vP}_=qYpq@UnErJYvrATkW4gD;@l z+m`Vl6=QZY`lw)D{qrKSLrj36Kx)FCB|oHI=SJUdio9SvU%ihCkvnQAh?j3zSW9_b z2yvUFRg0#Q{JlV~c6@}vf@Xs|aAQz=t#+s_e?n^jdpzsyDndv8#>Y2Q<`p4@eJbU^ zDJ6tL(XseL_BG>gX0YMX;J`lBF>}m?+HS>=Z>H3_g{%i!d69Ju8_Nz4MBxIlndGrM zJ2P%l)`&*Yok8p+ip9&xVk`saN;0)M$5!*wp~PgfQ`tsVtsXI+h~_3+Xfzvy+j^;z zq$VkLCg9G^ObkTgeko^wM{^ z6@hG6vk|`CJ)=qIVH&ev(!*k6{hdN=J7-8N_3a5MV3wawI2w$vVBgGTXOjd z{6?*1=D&pAp3#-w`Cod+VPm8iUqd@~TNey06@}G%iU0!`-A~YJj=Vp=%w@7v4!FSC zIg5>yqevlQCR2^I?m%kh!3~KLAnr(6JW*^e^!WU|bF;Hq&-p?PbS~GSj+eMyBN@oq zhp&}jXK38Fp%C6>0vdb}sh&RfsN56!v%XAbq3N~}TTR}PxKA#LonB$B7Ea{{*RP=# zlbb_q(KLcog9z7yW-RW`T0q_tl2f5nz9u<`#I z)n9UW*px8ZN9M|iZ?MTSb--S?XD9TK&rQj0ag$-@RX8;0tQej_wkn3s2z48VC0>Oq zW&yjPenV!tuzF3Dl+oB5jr5JnQ!qU2GF$76F9dj9x<~B!K0)Wv!c3#}Ge||mpl|J0 z7_aSB>6@UcwNR)}u3Spz39m~a z0+$=;+Z5E@|8cNuIa*$@MVYR6nsgg}9Uf2` zT^|^?TS3cAildPNt$mrrS8J%-&0Zh9+5;Z``u|%NwLgNrs+um@q*Zk~Lz+^ot7yhB zJ7L^peS>5tJaNJwgXTch8#N{-hydez$Ko2(kG^tEi2Asfo9X{VrgchL+6e8ZPcr?% zNlFQMZMk@9J3gQJ>1(pPUncl$PKs1)$o**%H>cr6igZU>VnAELmtoMY1zE46Q=NU_ z{&s^IG$}#LGg{n`we)GtV_cVPJtasnzc5%^WF$CNGb@@^ulsP*D(E#WTDk!!RmgW$JJ&K2pn1fvRAaQCB{;2`3XKWPeDv+++)W63LKaF!bXL z<^fJUQ+a+Sl3NlH7s`!R|~pJs6IX~0`mxX;(}1@&co~V zx<&TjLOF&{d)N!jD1Cq%wI*#}HpZ+55VQFc2q&j2u8V$(6I$1vyPG%DZc=I5ej#kCz&2&G8S>Uxgw_)`i!8f4oZcmt*JK{Vt}e#M{2$ zjK$+%YsR{?*9q17p}RWG1v60V$j23k9qCe~iD#jn;}2ODRy7GOsd;zgta7stD+*|R zbz#gW)2t!o%S??{SZ>)qp5rWcZCF2e|6G9-?TlKS3!CEgs*cQ_QV2}tug@2W*69N@ zJ=yk)a4~%~hF>SlKH8Oc!PD;oFX%6EV0~^VcaIMiaAw>(Hp_IU+l6bH6~*`Zh}5gJYWUd2)pH+ZF^k5|GJVF zzs+W!F%<2(_8+>xc4Mx+8bob?+`S~{l1?BrZ=NN866|rQttSNepqYUVOuPvG?Mk*G&lbrRp6Dbm#^CLqu}#Qw%5JIjv2+|&!4E!#_|-z!p+hU!bG5Q ziR^S>%VwEBjg68AvS4B~s#aQR^lR+P)uUH_4QQZ==+rDT(dtf# zhQq%F&%OyXY3&ESvBXqvP#QWRsnoA-97b?zAZN3tMweAv5hI;uGtZhx=gp6;nX7LP z?bI!|Dhbo%Su3$auW8;GSgCJe^TKCewXKe`C+duA9-+WllWUo@`W5q|cdT{cFGpq1 zU=?CVu-z^G1O9{!f%v)MW*lLbc1n`E50TRhh#^MMN|g zx=(p~G9-$ctO~ujcwNYq`McFTT+}X{b;TzQ?_Qw!C5?@q{BW1Ld)yD5HCP)xS=#PV z6HGd^rVBQ__CL|MRu4VRD{*RFQPJ7fdl)nh-G)Q>4h6l4NQ7;wS0QS? zkrIM(!;i?HL%I3DL7E&Vq4|i^a3bKrfo!JtIT2Hq^XT)qCi9exc!v@)wqW3aNA+8S&ug zi1Hibja#fFyfTx(-;`EC!fAfu=7uf7z*&i+1SKF9x*@3rVbR9wcyYWEuswVjb?F&i z=Qz8#(9!yG^OEc_x9~vPwd20xF?Id7WKEeg#jdlEAaEz4%GkMJqF45mr_dPqbD2PM z{do*hQwiut<1aLfXdOm4f96=Skzieurvy|rIkr^$$ccZ4!}%m%_7{)%U|;qZm3ZgM zx^c9A@L;?B4*;P+Ucao$ey_4WFtF_jB_cg322(DLC%Wn*;aGG}GE$%1m#W_v?d*!A zlJy%Bk;noD%8oeERScu+N`&_8jE0l-%aH&H?cqo)wpFS+EStL?p2{Z8$#P-5uxNJlqO0z zyt@UZxgU>G)l!Xe(?s9u-dK#m@^SD{JvIr0MPslz`Xac1OK{cJyFDBWC6f#+RdlMN zcBEN~2kT12dwV)bXTw)Y8&*?17Edft*&j(?En^ZgaMJQkv@uy5Zn;%uf0DZ{k0&DL zt{Qc;bcebkW34QRkcID|H_)UFk^N|cUYfHjv^P{A3-xr>x1|!%o-W+q8jeLm3H(+W zO0OQv-OcMI!jlKgk7AT)zY&zx<3+2 zVr0Q^Ll!*FB^=q3b7CXowW^RFp+Mzms|grD7$l{R#<+wFSH4cLy+VS9J)@pc4aCq6 zD%hkqMd!=yFr~skrvWtaChSiM6KOFHNS)-J1voF@9(Fk+NmHtaV`G6SB-7QQ?ucPQ zWmN?Yh=j0K;`r18C(n1XG`Ye4j4`H2R@Ag1yJXFjn>VMOF{bBmQj}0<&5ki4%m5h_ zt2+@v^G`;ac1B~J$RutjdoDrCibXPQ(;Kuzq&vPhA{@q*%=L@{h@I#E)tfM(Bq6kn zO^F7d%r^LBj=?8QgD((La_yK_nm*cwX)0GT&)$<+_MS}J`>5O-_HOJs9;z8tqlE1Y znV!sX3W_V63@I2^;zOQ>87oQq&jf{qhG@>VLIXPd4t}r0kKh+N{04qY&1ZG^27FUz ze__w#0DD1aFS3_(_E)Cq>}B>ho&6mL*emQ+2BrCjY1_O(Xa8VzI{PQQz@U6IyVTih z>|Z+k8_<|%<`YJHQjtVYC|2K<+Fie*bGNV);`5I>`!_tJv)9=hI{Obht+O-iO_dGl z>@0grXXoGh&fPlqu+t1Y`NC6qvCd0)DKf>&bnfLo9iD`zbUpz$;$l5NpF~AKhi}2R zbvOy9bY6}pfLHls9sbFu=)8hY)p;eKrn3MJ@acR8vHcev{sV8Qe5TH4@!2}B;?-#T zW5*5RC8+ZnUaRvt#C7#pa?MQTb-Z5Zb4eoSQ8AB>aXu9nkXYVi- zU&UAJ@I0;pZ^1cI44(dJBw-4?tixZiU@E+!^EEVWEfwqddY$RK4V^Z)H=0QGhGIdT z6*1J+29o59}1PB-nS6XTS-dW$e>^6@6!3@WJ{CaHJy=) z_!UIbPP=u`5kj=ub{^JwCt&(hF{)hGAZJrXGJOMEbsph6bl$~x>O4xdT_lR#Jf`z* z-h=#>HKPVrhplx zSr-6wX*8K4cX+qXuc6{v{vMrQ$FJA<4g9@2`~VdVKO}A6NX1Q5+{|y$`K=_S+weH7 znxQi0@b~Hb{rm&O=HZrGR*IVU(zr;#Fj)3~a2Gz<0zj zY3RhbIyl0KNGK)w8P7eURZJ^ck=iZ0679+8Rgw4(N=w?iL$O#qOd#G`jZ8SZ@wyW; zW z#)&Q449;vOH)?mJo5DGR+Tq742)GMcTg5G*t8;gIm^#{b;6}z_C?a)6lY3$zOlvDg z6jQT7vS2ioiM=Qd!jtqw_A%HsN;GYb?2U#a3&weX@mx}j3#<&bMH!r56{72`r8~4= z+#tl|PBn-r&m29=Y!Y+}3i#V_Dl|HwJ9&>ZlxJK|DU)u>=1+^7UWyhyvJld~yui>_ z3oX?i-@Xg+zP-065kY&f2vf1){U=7rpjx$UsiALMm(=sfSkV;k*&9h<13Bv69!_qE zZw{kSCKkv(wk@sR)j#B`^;OqzF>airy&%=d!k zP%1ReIa>=rX<9MOYg;y^qLiJN}qTU;g>=TLz zOH;O@FbU~Y^cX|#6AGwpkMJ#|6{sjQXB@O0(Km*G#UM~%k4P-mj`u5+)OiGrV}d}| z!8RGU#5Ti}gkQy?XovFE%DT_^vi;eHL~Kf5Fdk6WN6<$TWcIUi*)xuBa3~HKPexPG zc#q-E+t8nd-U?14`n}O)bbCxzG>O|ww5)k zQP}C4jl@pwJ|R8Fc&W?Wx!v+onK=u+SK#RO-W{2~DFtJb5t+p0Q78{Lnv{E{M0{U+ zBz<)gM0lLpBTFl3Dz;O&0C{QaR^d;MXjcz9a+@%R8MK0+OGq9KIU|%|Y>ae@pqnLa zoL{yxo`_zBsZWS@*%L~HQWR&(vaC%YOFCw5Te_`90DywhJs)+xM+k)&XcJ)4o_?8_ zMP@!8@Iu1zJxEN*wL0RiGqQ(l(vv&VyQi>EMFkw&NPZ;pnYi(I_H{&)6r|%x(w8@P zt8l!Vp0OGDhcg%L>BK;s6oI3~2poDAZsLGvM|A&^Fw&sSMq|+wnslk5(!AaRuAan| z!iEFo!@jV@EIW)EyGhXT==83r(gA!J^&B5);Yx?U<_V#*{5(Je)hKD0H+M zX70_4F2>V{v~WCoYl45V03AX&80{zO#6|5Nqrj>K$1n{GRoXCDE$;y#T!LLV>O&$bGF6GZ8WrbX@lqi*B zV68a@w)|y(E{UhfWqmosxr+lmno0W0kzU1bW3a}H6l$>MI9R>5J}9aQPW4&`z}^p& zgM*;mGzgC4V8i8{L0l5Y20`C^RBZoCEX#nO!$a9r_>~I3Rv2R>3OhZ`$*rd17dyc@ z=`OH)Y=clV=}3CI1!aKLzk_Gfr#7e^!Be2BNF01Ho7gUhe?MMg1^xhkl*H3fi9}o! z902!0(1M)xOkl9-;yww1k2o-H{%<8)(mhGcjEW;WOayR*DDeuxPC2o(b`JHzxOuo2TN!*m5Z z3sY728}a^kct!I5faLww5qP(iRJ~2+l^o`2zF@Vw!Roa>YM*cO6YE|piaaO=Y94{w zEzFNFH0K1=4HG-hoM0-k{%RUN5WIwO?lZQp58e;TQLN8Bh*&PV&MTRB?Llaj>p7Q3t$={qzEyzq-6jWh{^z5gg*<-rT?Aw=6Z>@lhSMqz#@UW6A^a>6lG(tS4=j2 zb1qIMz5<0O2L|BcVY6R{H`1}#nC%j?k$CYSEO}6#=szYIyA5s8iQg@SHxYVSxiiB%T}KD5A-dM2yKvbxK4lO#^npw4rgRWg zH)wPQu`v}Jy&8QsDIIvE`3{4-&3#b$5L{;SXqG|PavzlW9k}#XV{jYpwbk#)sAvjk z-m?%?6kEwzSgELR`c2qWycj`Z8B^hm(B7L;dlO|;*M(XJ;9WV&lT^F`{n3ldKRcT8 z7FQs1%}Xl_>ZNi3E^oBT+7*qe&#K+g2M(V)mKL=N)M8Fw``t3gH8?AL+8}f^=#|+p z<@D-<5Hc0-LYhjyYna|$rrupb?=Go#Q4xI|4Mn7BHx=TD%5m5p^jRzX9yS1B>Os`l z?sxe;mO4}|(G?Rv>na|CPJc1hBK~4aT|2q)xiZBON=%47ppBIh)(3U*emNt~mT`raoRXvIgk(*&u zv>d^)9hn>kVbfcL^|R^4&ci-R!DpI_Wh`c~G)J_Xjzw6kn|wsVAjL(tO)yNo^Jw1m@AcQ#$(}7otIu@y!@Tvu5-;l5a;2=9J5H;4*{(%W0XwT{Pb& z=Z~`B!a(_Fa!4tI9DqGJh*RVl0zWaW$h2)Gi3fuxAel3L0GPen3ZUJR(GiP%jGYZVvJR%LFq9l{Esg?ec?3|5;ONo(nm6WQLid3x> zQ?)X`Tcm1bghf89PxTkGG$Jk&VPwA7(%|!ZPhtY*9e}Hk!Mm}EX`a`27)s=HpNVav zNuvcjvU0x63A`T4&D<8R$2gyn^O4Pqq|cp6`n&=V&6yj$Ui#z%;(90*X@fI;A()<( z&ux_X+;wyj{}l$*LK&UPCZOL;I1MiD3Kk3GoXGsrvY*5IZxMb^kjd6i@}i=t)(pTk zeOR|+oiDU3%CivjRiVbbsnKmK)nND95eNL#=|3e%E6=5m>Inblvrs(pk4q#J}a-M_b zpo-U3Aq+gWdk8K@Frh#Fk|z$Ldy_AF1AS1GLD}nrffH~;&I_j->w)x@{sI|eDu`aV zk|Brn7UnLlkiNJG z<)<7qaaJZF_Mlb9i~+c*&v^Gk-?>@h$spX)Xd|z&SBQ%*u5)W2gt1}q+v(}iZ@1L( zd!WRizm1yn&`8;Q&99w=Qon`6bF&W~*F zKX+%EpNZ0QO@1DNj}F4eXjV-|Uem#G7A7fd2+EY=vxt{yymJ<#Du=*_mvSqav&;E%Z3TkeK4Goks zr(?0ori#rfrA}@}jgUdK&}Ox`S#9=a^u|&vcgw>0MF!4KCY%=!U?V@&;gXJ8$>aPS za8+y_eruXhqGkjtojGIzDU2AkJ*5I&R12@XO+O1@Fm8nrZV>!MzO?cRaa9~|9 za2)Q<98;xVF1fv5Hq z)hB-q`VnD`yZC&?uhhzi=*B>8n_r7C+Cjkav>w5lCGTl!$*@=lDC zBYCH$8Gcs4JDud6n($5$AJg9H6gN1tdPSofv#+!>YQj66K6g&X+wsnNC@mAt=~RcH z2o>cuy;MOTRSJ3N>~TZ%w6P%CHYP;R9zR4!|IZ-W`u`A}Z2(niO#F~EfK3G;8bQ~9 zXoRC(n4M(nT_L?s&Bp3lOPzt%62@~)VzvR*^_#YHDo$Wz^-ONd0%@1F%A)i zPtG!!&zS~;?TM)E&nt(pF0Kv(@P$#0M{6V#k@!4=ew}v8P~Ri)MZ6dt*?QdQ#a$j~ zP`xTYgc%jJzSN-lZC*^gzig(~g)Pt~?lCW=?ZmW%jM~Lxkc)~UpA&Osyq3x9UHl+? z5V?DxR$hACM&)RO$LsdHEOqySj#riR zHIIu5_6%fMCP#vWm^w8O)4bJM|WmJXjH zP&R9?u+gYL1zYfB8;6NSypCmLqoW7t%SWZiwl*C~OpjZ!r*QXA`V54vbpXw_b zfUo4)*hitbY@%?2voI60)4vUYmz@SXy2Lry2`*u1c-d0u9wskenD3v14I_gWM|Xh5MZZ2h1EpBbVRc~sorv)((FwGgg`USR3TB?b z9x=DCXLIF*JLTUIB!$bF+l%)X)W@Tv`WLIk3&YpukLn^huxwR2{A4wcUlOfAB1-mxh2%H^rMP;|Nzj7jb8xnh=8KvAF-;WGEImUh z*8c!dO9KQ7000OG0K_9mO7Z5Tk1+`V0H7BD04o3h0B>?fC8FLQTvFL!BV zXJvGAFI0JOWn@fgb97~HWpXZKY+-YAl~)OPTvZkR?@ZF$dGng4Tj)aCr0Zm9r#q!B zrDf7h5?h*dp%rvGc}=FB%uHuV8&DC8K*bIB7BzK^;zlvC4o?wWDQ$5>tqNLPKtx3? ziUOkNy!&R}l(oX{LXM zs{x76N_c@?E)pM!FM&};uMy9*M$;K1Zlna7S`(?Rpb?2hlW8NEK9C7U;~66r566P-+dI}4lUE3g zSQ(8+GphvV1zv?-!#076P@>ZisO~Z{YcKR*ZlK{+`QYyk$2((&1q)0sb>u*_lSWAg zErvDtsP;@avacfjT-a+;J&ws~?j+mbfAO-8|KTNFKnQ^uE(#qf!*I;jm0>Bn!Xvx_vs`#< z4<;$hjy<=zD4uPxLR9Kv1bZrZ{sFgW88IW0i6-KXc{eu-RC|Px!Y44>h1?QNCll$S zAh?CfCfYVN;TLFdqo%@LEk=f%aLl@nU*Ou7bumg8Rl=)_(W26QldVfcvgzSBnMJcL zSBo*a7%RrO&vCtx&SX>hW8&tr&B_n1i*aJSyUh(oEIIrZW0vja1X1H|t<8vMhquNo z+u9{!qAn)!&T>06R~vo#d%>OIOm?!EqKiw#6!-ntmgtP`=^OqSD4y+St(fW_ah^}n zCUM_+4$*OVnyAx7JzmhobgpKUySmyE+2XU{wyP|f?f6VFOBb`ntTIk9yCX%nspHff z)(UXdz^ZPi-coH#^cHQl#$kur-fb8$N1=&FX=W|A@P-ocOezr@{tmE-_RQysdF~sc^56O6PBvIm6H#&sT{n-4D?=Bb7fy?k7@_ z9E+>P8eLo^LKo4# z%*oJLl&3ceRHgY*u!gc%Z#ZLg((;x*RZ5-@)^o+LjM58$)n>Y?<(~*j$r>&cqr65+ z6`hGFxg&Pc<0nHYKa_~Y!bz)ZmaDzO+dw^zX)(gi-1AU0 zl88t5L>Zc(X4zuw31?#&mFO97bPv-7j{$3_D%*%k=%gKR)h6THSrPi8A)6&T{M@XGOJrM&IU?kxrN2w)rHjwk_#wp>+*g zN?MPVM>~vznQ|MhDBG&V=qcpN@+e?@g@4NAjjlb&r&4unpj24s;Jir7XMMLvC@;l$BGQt#CTWHd zm9Cgqc9$2YH5HpAXC@miHz6m>S$dwX2wit2TxS>gTU=nD)_f4 z055&tfp<{-PLgWip?^SYJORwW#b?`_t@R4{&ZKiUPNEcnscagl6+7l!{LpZhG+m$a|Jm*#G9dxjArGk9% zET%Td(|!OICMvIOI)M>6NDl@o>J>cFgpXk@2S3AFp4MioHRqsv(9}GLQA4O2@IKA> zgBX1TlNDpMiE7pulfziD;C)EJ<4lZactQ>}3?DCX;}V9CmcSDkKBVADCMGjHC5KBH zjw!g-#8if-THHbD0q&E0K*MAG!pE2wghfs z_^gR$mgm|Ln@14~s))_!hc%z+DBf$cxvpfo26?XX1K>U!TJp7@lAv%<^tK1`&!ur;33wggpazu0*ek z^zgWeZkD6A-d@t%r}Sb&=;1a#gv0;_^ZVE)P9kDb5%=3cPT4_D^J3vp$>zxLfcE~+ zn&@TtKn@2P-fE(c+PLATYnCCJ+Gr4^T@31PTBE2nYbfBS=be zrNzxwF8}}@Z~y=#0001Qa%V4PV{B<~b7e1ccXTgzX=G<*baO9PX=G<*bS`6TVRLi6 zy$O6&)fGQ}-+OoF%}id%5HbTHiD45%77!OuP$VP)2C|ujRUL+82qPgAXC^Ezh>BKi z-M4~uU#JQe5n?K?RdK0yQETmDZELNq+FH9=73Fun_uV)1W)cSc|3AMU^__Rmx%ZxX z?z!ild+vDyedX)NpJI$1&p+2Szpjn+u=}-99;RxA0HfUmiU5j%mY{T$u9bS22dzBX z7?8&Ti~|_2YZE-os~rtwqW0Org9kMyvDZ`pP5>3h0F(ht0w{ND62r&C@s~sWga$98|5aj9ON@F_L(T11rP*S0T2RM=_b&DQYSzdU==_Yz-oX9z}Wz6 z0J;IzLLEKOT@+|9#yAJ!$54s`B+%q!fIduE(AMeNdIvNAj6uf@02=|$1vn4je1Ho8 zHtE`h9(Imt6Pp1p0>6tjHpX1cO8~ZL%xj{T0$c|0Er820?iB!60{yn8zy^rHcihCZ z7^SNKt_FGyz_kF^fp$H5+7nTd(fpl70WkO>7VU9NY#(S(06Yos6u{E} z&j37&#y^78&uNSlr!f_P1GpH&?4Adg7tmxsdb|kQO8_qeeFfmh;PNV(yoSI1c z&j5Z7@H)U70B-{P0^luxUjn=h@GF2{1N;Wyw*c<|{0`t(as@yI&`2uIQF7?q=~2j|xe34sxDK+5Q0_*_1CRq? z0OSG;1IPpL0t^Sp2N(fR0N?}g1B?W~bUEBA9}Q3h0Jq9Z0FDAE1%OZG@TnX=m5&FQ z006JbCj!8Oa(GY<56a5{DgY`0ssJVfz?X9PQVw6r;Y<1P04D&Ph^bBkfG_1I>HK7m zavGMUi_dT?r}3EprvN<_U>3k>0J8zA73N$W>gntbD@<9SFh@MGQIrFs97<4G?~KHI zyF(ilHnu(*>zWeo=#KQp!&Bnx6I0elI=jM&_>@|+$xMZL!t264iMG&6u$gek4t2>^ z)Q5M55+Q}<*Pk6)7n;%?>gk%&wDRn5M}q2HwSm^=rq;Szh2_>ndq}39#G+7lUsxkA zq>%VE)s2g)TY;%G`og-Ub+y6T>b7c7IMvUuZmg}Z1LiWhRnMufs|D_*ksIn7+fg1v zbxm_xYua1Py4f`UhNeX(vYC|7Qr8+-CQ#B2^DL`tX$me5)V9qhib|9mL786{m^Z(T zNStBlKy7VZBVjIdncLJ-H?O6sy%9{jpv%u@|a=^Tr`E@l5gRSjz76%$@n-)Gg2iMWE*hL2avB(w3^CDb&`@t!}SxGnGn$Sh%TDlDV2%E~;*; zBi?h`+uE8MgUz%kt--oQLh{jriNQeQqPmt=vG{aq704EH@>a*bcaI)Kj;lnU*wI(^Owy-AvOY zQ*Ui*3AQvXM*HD3I<;tOwMmpu21yG~i&U27AF05k z!T^(GKiU7hmg+gdWcQIY9PJpvcNFmrG&Z-3riC<@Kx1oNOIuP4qlqbPb^Bt2TtwRj ziOBv>oyu|{B(v^cHvO7J*0yT zprbhl)Ol?SgEg%*sPmLaPb5L6sFh7xM7z8u+KI@m?2w_`V)33CKx-oy>g=qK#1rA3 za7OA=yJK~@W6hjMB2NCaEMWebM7C#iguAJQAe7(z*_rOc65j0ajVvBu5;|zj#!ui@A)z; z=Vhj*O{&?Bu_hSmNJQ3&3k@$5Ynv~qooDyO6KiQ@fh^qzF3mCPf&%`1WDLF5-z%7 zqa39(v@sYbSvnHnR+32yc1HVXW18bxU8pXR~NWgOMJq!H5(w+Ly4X!$9qdg;rX9#VD&I@QY%UNJnZVWfBpSVe-f!Dwa`=&Z*KBiC2sD z6o(9rokxVeM6@;%GYyJljl^YWf;|w4ngHEmBhc`uBtIU0{(FgY*F2h5L{DyxBDxfZn{lz`pktc`ZkwwEVlp6LW^ z$MX0rL7WgsoO*7WSE9~bDOV~K(UWeGmn}oPJV~g*ri>wu%9Ru=z_IXTfFyA%ni&=g z!z-mC!6`!sm^`Cf7qKrLWa0xV|_)PsKe3sXU9hDCzt(MEJGta{N z$!g+Ocb(Ik?Y#-e>aoi)+!sJKxaF4pT@ zBbo}nl}Fr~w61fylI7B%0mKEZW+FYyZNXeF;lpyM7pYA4sXttwZ?cx;G>CaUJRxc2 z|H251gq7GaBp}b?mC+5sp745v(1B$EcoVM}Dk@YhVo~9asB*yq zab?Z7*Z1|9EocHl3|As*ZV_@SiD<9oXx-xYH?@|ZprAdDSgtKbp=K-|>ZTD)(a}QZ zPAP;8w-ExYGNoRGz&w4To#Azn4upl~aU_I=QkIL!Ob|B_TG^oGL`A1w%M(jI!JC?z z)cMKsQes=a%;r;%;Hm~oiI_48>l7hmWGzKkVr5swqHD3B6fn^;Wiq3qgl3xkf^Vpm zYL%hR1~^TfWhe`kdP8Ycnhf<#qAAVF>Ez81{}s&ObJeMaS`9t{wboFNRi`TK=tFgE z+ej8`9UUYCpJ%AYgTYDa$%eXDebi7-0jmM^QJv2*_+CE6l&7I8x2iYryJ@^MB`0buAx2#hA&WKrA28aO)VelOWhO_cCr+9G6_4` zT8zmQ7$+zUpDZ#R@xGO&;TXJ`BTbfKH76RSlfMT#-LJ4ohvgTEn<2HK?j`|w3n$se z94?<=s4Z%%!P|Jd&KDVKo7!&h#p)7+FX2lKzKkzd7&G`8{7jvnW$++hVek+?)8H#n z>OiRzr7%jXQ0hWyHA)eb&gLr=R(7O5)8K1pHF!5)YpB0ce{Jv{b(z7V0KMux20sTN zMr*+1XqVu9hI+4hpTXAwtVg>I02|dc20vFlXz=p@&PR_6_$EVrP<_bY7h+*I^NS4i zVRe_mFXopR>f=h3<`+wv7OBvJCx44p|xLG}PCjgCFqQO*0>QbC4*LJHW=9888`q z7fj_5%yl=X4YP;uHF!UN%-{nsvme4}9;cG}zWRZ|_wgqT{v?0O;7{{s4E`+o{)j(k z@aOpp2H($LH26z|s?VVB%h2^J06&H`yo%Cm80aVbrw0ES|GB|m=WiJNP1xk$)V~}2 z7w|&=R6jM;|H72tg8lpw&gL^3Nd2GsxxwGo90vat0rd+))i2cp2LCm{Z_xO+1T@Cq zG5GJ$_+9>cgZ}~GkC5~|4E`sS-Us+I|G>~x{uhJ)mH25~rI6NBZe7pHDQ+oW9EH0$c}hJ-}T6w*%aX$$U%-Q_oS)q7e3oEV52y#bB3z zGt_BN%-?BOsApnV1OYk$!T=F~wE#T;QGggg93TPE2XG$11ppfXHUVq~xCr19fGq%* z0bB!c1Hg>{w*cG*a0kHs0N(~Uo1B>14X^>=N`R{YZUXo&z*c~50N(+)3LvDeG}K;z za{+E8pc!OI+A#i!!T+J<9ac|ifr6oV`KJc|CrtQX0RQIyF|-l9murhnQ5-T$~fdw zT!;I%g{NoMlRXXdvTEI3$5Z%sxKAE3vnC@q`DubABn^kUpr@OP_;w8#}3g1nZaPBr-RnT%-UuW zhU!Fy(wo*PlTdu9kz{0UD-R{N{8-_TZzoztX(9Es;k7Uy_oQO;wUdOIrs>QPqyM0NP4p5ER!Dwhoiyb zK<&J`wqQebb2C1|7Y`DorzZ7{RJs3982i5kt-jq{p<&4DF#^{wU(pxKRzc6ZJlIz!59Lfzfg7oC9I zc(i};F@%(}yvF<{DYKSxs}+U9`|a^OuIPcyJ|+g)bNhx)F4mH`_o! z4rSNx5Z^S!C4Cvpd=S^Zo(#S|+*13ff3!0kOhoauWgYIh;zDjBAj?fD?wj4AmErDS z+Spbvd!oTip$6rzCEa9RmWP}$#I1CQZ^F*L9@Ep~TX!qPcRjcTT1;}x#P$&<14$oq z57Wr}t>qD96?ua0mL%YMDa)9{`+JVW0@4NHM14qRh1&$lX?a-Mdwq#jQ>T&2V%Pv>>Y!-WordN#$@>#lqqA=z1f$)3q>zSS+-$Bic)+UHK4) zTwn-Nz)US3nRAq$(TUuO_}sD5JQ)|6ZkLm{I)1Q&GCwgT9hB)X<8gcrUKi=a7g=PU zj!J!vvfL9{X0nm#3xu@p^-zDqO6Y%5_Oxv3qsbijfy{xsqoGbS2V?#K*i93gvtYUS zEHuhC3w-p5Q4itUlHb+rqDFew!4Zii^bg*rX1~IkruwFqV0{3;qe=b(3O^oG*fB?# z+Z7qLWVF_F_=S@RWWM_FDR;p~eDlcs>;_dtNbAyRiH~-%j?@>( z;HoZ>sW7db{CFvLXG17X4%2D* zVo&Zv19}_H^fZ3LH?|A=)CH<-k{gUMiE3j<@wq z@&{pMl=@m;B2OGx%jN$jEt5x0>PV4?4ta8-SqXO%z!p!;t*@TfnjR}BKe86s+O?+I z`fcR)Mq7m#9Y2GBlecA_3o_EQ>+#7;Hn)*iCqJ?VM80;Um6OH_td6dYcF{c}DiZYK zmk`NxwWG|;RtxgFB-x*t9k#ARMX0bXNiY_Trt`7uHB_CYeVcZfk0#(Vh4n&_nS<)nR@Sm6j5iDVI0U8mwt* zY^x45;%C>#9ieMCujrP8w=|L?Gva0%JyWg8`39-kH%f(nG1N9qoVVVlt@8Iav#w^XMMw@{x_b9J&F@Q=7^aeljMn$@ok_BKkDFPnab{GjGfOE z9Hhd-F9X7m^@URVEMR=DDUrl(#hM!IN8XMkC_*xs@LwMAN_Y!`N z4UZ8Xx8VuG`%*aj5#j4>_;ZA>x8ctdzQKmSK=?)*zLD^AZTLFE&$EqpKH(SG@J)nY zXls8F;hSx^kMN6Z?Jp+$5?g%%;af8FduaxKS;qQ)D`R~xx8YY1er3jbf7^zChw!Ux z_|=48W5cf{{JISLxZZ}}K=_R|{3gP0w&AxB{#_fsmGEsg{8qxhXTxtJ{B|3D2jO>S z*yCL`{BFYUvEla;eqRQDzYX6``1fu21B5?l!yh7ihYjCJ_`^2*2ZZmk;g1l$+lD_% z_#PX+m+*cY{uto{HvET#KW@YK5&nb?f0FR0Z1~fJKa;T^pS9tz_vdUl?E3{9zMt?H zZTL%szih)_A^gWS{8hqVv*AA>{HHejXN3RUhQChu8#eq+!hd1I-y-~%HvDbEe`Uje zP55tY_-_e+$Akj{@Q(@q zn+^Xv;h)&>e-Qqu4gV+M|FYr#Cj37({J(^MX2btS_~$nK3&Ov&;Rgu+Dr5hDZNm?; zQ7TK}DxE0=Qn*Gqx8V-LoioyiA>x z^uNavUT(uH39qo>53xP;s#L3(qE05taPfCD8FQ3B#~n6OrjPdNo3=51bkXLSql-3eWd;${i#Gu+PNBtwmZZ=ULVcX@ToWgLL~z&$4iKC+ zf|Ce4lCm32KfsEVVZ(-byvGW_kF{i{sM<;1 zr8#~qB0DUULrx({^?DkYx{ShLdsxu`E4~l7A2zp#m0+1f9lvduTmEt6=nMR1Hr9)RCNw@)(aD)&Q0R5gInd$kZ23Ovdxjj>$Qri8 z)NP04xL6a(k)wO`jJ4Fa$WBo2d3?saF^7CDK6o8tDQ&fNtZ_>HInc%M7yCwFed-Xn$U>mIeDT*z$$2tWEVV7yzHVLH%y&usKHXQY?%CnpoZz!}%RX9PwchaR zirVEbl3a(mtQ&>7Ua6%c&(2H!I#NG6Ip|F5`xSk~DtZJB3|yG^UJ<^JzP4re;1AV=MlB=ahXOFvMH_$Mkze@S* z!Uv|e_Z+i2^3yY9&EYSFGfyX$oi+XtlUX-m=jaFbrErYe&7E=I*iq#H=MR0$)eA3s&pH`iR?iB~&JNudxixTrdByn4>LFuuSNC^xt*Cp- z4jJOs65=yZb5|y3R=?fz-0EC>v(|YJYzPS1kZo&s=v}en=}9sryVXPPsfS!}<1ew@ z8huPXAYQWZ&ch`0t+3(*!^}VvueL(tGA9TaSSuvdT#9T*(Ils-)u}#Z#{C$ zF)U>3Ise@CVS!uq+>wA5w(ktco^-C*?s+3w;gZUr#8Ne zdicY8)y#pV8=u~>4GWif8-0)0KiJ{PqaocY99>5)%J%+#@~Kim)sbj}+N-;iLk*(e zGyL~__^99gaa!%UDu=o8vbTXvt_eg5smdeglM7;`z|<9d3#9hZ2x zYRjTc1NXmBKJ4Tky7J)?^L`=q*2S0Yc%E{*E8iD6ZfQDfl|8BFA%o3hr>~N!4v~^1=nnoo-uMwUi$W9B`!c-9w$zdCRhgF7VYHmuYj>XV7W+ zy#7DJcc@<*Vvu^eQmnU8;#S$5lVXQgP3f64Mp|xI`PG!zN0&F~o3BZ`J7@gQy>l81 zlb>B%bK#zU<>WoHmL~FtzP9nrC{nL^@Abz1W*`4vg_+@Vt~0_i-fZb#-qoE`(9&1+ zgk7IU%2Si8=M5hsfAYYI)8TD9=ha!}Uc8*#(!aEIi>39453H9)d7KB6T$Yu}C`@i( zsnpC*-;+H#J514})>qNw>V_4z6BIcnSCyY0|51MS%P#Yh<|hRj0}9gXrMk>djFLS& z;pY;uodfr=bmz}3=Bms8@*eRyDev2Tlko%k8>vQZ=S*DMe7Ae+%-n-x&i4FKF8=mI z_c5pYeYsKS{9+By(CUFxoTs8>{aWu=`*h-7*R7Xd=X$C4IvH)Z@l7YyB|h1e?y0D2 zyk8f+ukEQz@=k}a1JTlL6U2`9$#@;zU&=9K!Mf*%t}W!+9%s7WtkUK4>h5G*u}id? zY&PJw>CQ&YbFs}f!DlVhUkr_ZcR_bUl&Zqgc-kb;&nvF}Hz}UDN=pK7Cg)`1JR0`)&-D}7rSx$ZP; zUGQqLKC)ItQq^uZCQiz(T;$xZo|SAkVTop5M($$GA1*aRLz^c}vcB6=t`JpTqhg^I z-(%hy8#8CMX#I&2DSBqr>q}ykqchv)$Su8e-YIY-r)s?sOM?5}XY^rg?Ujf?q8RacKZacEp&b*lgRBl|yxE~^}W z)o^XTP5t%OSogr*!9y1m?AyIG;hT8y;(}3ib==yw3dP@sG==Ren%Sf9ed_5WHm`gJ zyx4st;YdH(oSkE~-q(tCYuxzQzP7AJA>QQdzkPcJ?Xg38*4M4<;`*Xg zonD8!wyp48nYa1M(S~v3`2Gp|^_HBk%gQTXXr=JsR_o!tb*j-l-R7q%o|}C>WuWsH zy&vT+bxt*}M!#o0vC#XF!CSdA_iV3sl?UIe@e|Tc=l);B(n+4WB5Pkr$)KXQsb-oo|SOBAUSW0r=Ew&IO!Xgj=BXcvl*U! z?z(Do#^b@GO=s2Rt(p?dIez#{Wk%A6`1q$iRvmLZ%=i|(y6V`bO4}3qKDsg~(HD1C zTKWgMrUv)S`(c>SI{28jO;d8A@5XHRl@sr_p5I&=RPwU%meIVOvXvZoMJb@xB{!qb z+03HaH<$A-c{aJbF7@~>UOc0zXWg(GS=+7iz0OY)n|ftymm_|aw$cYSKQpM+ooPL( z!SYE|N~ny~;iuEQyMEWrU3>7BZ+UfgfM<>N`fC^Zvx8nd`#3P&JFmGlZRD?uHY=uG z@m1A6qhcK~bKAgOY2Fh~$1B9S`@|ONR4Oa{>_5nhH6dnwXjtx`hjyX4u z@3$f$B(eXLt;NrE((3~b%bz@{GWiXMfB&Iy*J(_}Z>F)~H;S z%XyWk`S{+|=H$akvaNQ7`5#Q?1iNR=jI)Sd5f(KwuGg7^^(yjRvJCE+NXNTHdb9@f zChM)3mv_tRkRNZ8TVCywCNZ|jWCxi6_Z+n(%a<8SQlmHtof$x!Z3vz<1Jz$bB|dYD{?WMXe%zdgYI% zef8GgY8QXjaoqoUL_fP!d3M6E+=2b?tgcd;tLrTz`RRns>isrKyNi3hv^sv2Bd;7A zHAm)#Qh~L<&sw+nb^EW@Y%gt0JpS|OW36*7{aYG>UBZ`bcq#GNFVk$GYx)`U(lG_; z{)v6$(^XmywWPXV8|%HSmqtH_&pFZ)zWRP%Y7$jb?zvle>S)QxwI9z9mvx=J_RDfh z$H0sYqt-mJSbcEM$$9hKi?%m3y?*oRVsm`Uu@k#eyeC($O1ksNvfjLl@9BuyRU5@- z@0?|r*edqE;KF;A#A{jRPJJ$ZeWxY+%Uw%fa;fe0rFS(JO>rzA_{Ho?zkOl7FO)s_ zp|+;VuwiKAD`O|SxH8lIPeQ-C#FdSZJ~}Hr*z`rSbVFQP&e1D#M@r|7Jsgg*2}-9|HIbj`}au$R}@v) z7C$w=-ZW@XqU+|#zt*T8N}m7Zlvdc9Gtpa(kDb1&EwxDLndhXDeTTe2IWonx@-{NljJ~>*htYO2|sa)U0P5h#w zi&fRlqZ)r*ir$_2M&U`+&6c$`k+1_+_Wt??fJo=yHBFMo_na3{e_9kBg%L$Lz}e6YJ4v7ZXMqo_hja1ttJn*J~6+F9lz~S5BX*D+Q9ebq1$JQ ze!eM*|FyVMKWg>iqy%;|d!Z-uT2_~=Yb!77c#Ny``#AZ5%BAg%iXXZu z#Vq&|x+VUKy4jda?LngkEXf+0BQ7DQtvmd(+4ki%TU}l({5!cOy3ysa^+BJTlv+O#pPLr-R@@Lsr%i5pyZ z*V!gvec4@&0No2ayOlXrJ_x>6GF^7Z)nWVIY_=M*VCk+v<+fd2zCQcnZV=z5Gp%Hg z>zw5>8>(tP%jSFwOO(FGPFrfbJ>mPO+haboEl+y>>xEhH%^2T%>x1Sbt%(tu8 zwdPGf9^c?^YF148^{fOwcP$Yk-FLdm-IBKLpJLY-{cKzQqKX|aCY!7}>RR}#>PZjW zR~2tu_`&;J$la}FQ(czN&s2#~QoS@JsyViCOz*3A*Bnt-yb_qbxnb5X1NT?`A}0+= z`*6$nE_Z7~!@c0i)2d1iRE?Nd_u_i=tcxD|eV3ZeTff_1+fT9j$D+ugZNGG@J~ZBs z{(3ZJ*{^;d4Z60u=9hH)xo6oPneTDSJfy>C5Be_7SY-mqV~VwT0KBb?DY^LwT*kCrG}ez<5V_n?+yWbwYyoQn>=({7#p z;%-t>GpS-CuZffJ{etz+jSkIH(=IKmDX)lDt7^CxyWsWGKKtRLxMjDJyx`g+)7n>~ z_bfWqI5Ki$rf*BHdY{6udEGCx^;$ePZolr@T}K|go0Rh5%Ka_{CzBp-P2O1V`c(g$ z>h8={H`QUhuq|mFouHU-o zeBSG#PXb?K)#s<>ySC1E*wtv4`e3{IlV6hQCvz)?Imj-zx_LXgmFGTd_kM|CaW^-+ zS#-DYp1NLkc}hvpey{hAgZ;idHR@S?CgDZc+4a-%KW<1mcBpo{O$jG*;ajD2-@q^L z(^c;(IAm3*$|Pi8>3b;X@T_eM6*eB1zA-mW{_vGGF3cUXwsBlHB@gt-wRNrAwCzk& z(^%UF*G-a}D?MM<7Zt6p<>l(S+b!_f(x~G$fB4Ez?^L(N=I28=cdTE_Ea!Rms z*0 zZOSxb%^`CSj1`+1lImrkz4=R8%9Qw`Z|?pXfa&X7Ev?>wBha?RJ=B8e6LL*=ge5A{!^`p=}LU78h1Kh(~y=%6>I_ zN7YnqRz}gV(og^YMq~k;%fC5h{->2)8H`eK2BSNi%ZIc0;}!;IJ3H7t8Q&|qRhN4krCc66v%SVo(vtf0T(|7n^T+Y^BTWwcT9SMsX2NBikw5L5Y^%b)HRlCi z$v!=8Xt}a>N}1WntPx!r;_ofc9kqY>$mYudg9c`ErJS_xjT84`8|!Y_a-y4+?CkL> z*AjnP$ElpXw}$CvHDfAs*E@CJgZd)ua!NbKW^Jar5|rM&o|e1kG?tw(NFvm*b(dipQ1-`Ja<#4eNV-m7ex# zg+qG#-5Juz`Lv7X;BM1+4I0z9LmYQyKR8kxV^mn} zK2KWV-P*yMHx9^|C0&1U#!lz^ef`uud)fy?-P$$g#r>SA62>yKUR>I^X2PKJ+D$vo zUfvh9%0G2m#vAc%kw?bQ883JF`a{#?6JNYH%ujajQ#|;CCF8=hiAMWEx*v% zb8>R}Rohi<6KlE$WxcsIWuN`bvcs(tpG&6?joQmL^erl#YwN-Ldb8LuUHkhmUn?Jr zrYXT?x$~9wyZlse+UJ=5@XpX7y?%A~*i~az&3Ty+_WjCb-qx$eUPa9h9M|a%__D9f zYQk>0#UT$kQS$Z+g1W!?{2Wd(`@Ng@ZFhF1TtxIwe&gp?`Im-`U;W)v>%HdJwEDJi zU!A(+m5WM;edrPTsxoEUrc>s(zt-qD>0bEW^@H}S@9YD6T+ghLJo4P(;jsVSAVHB@ zq~Uz}DMYAW7Y0M(Po#zi@z+TPef?G=ouzKkRA~LOW{an{=K|J(1w{)MEU@qLvPTcu z$BYp!4wBDfv_8z+JnM0iW_VcOWQo|WC5LtPZtmthQ)bZdnCzQntW^*~?_~HDqRvK!>MI}z-?`do2c7wm`(zUOcG=m|@VBCPeg#Y;OPm0m) z&;Rems5eCa*T&S@P{Y#JOtbA4 z8$(-5GgBw$aW-cE372GSGMO-QaPMNpLUjg1H=Due*YW4@Z$=FAW&qn0QjP|b@2}wt zkItkCdgFq*f{fYykL4IZdRhjb$if-r8vikXL%}F>^W1$8zOD50zz&m*K@^ z^SD%$KD(`6e(nX7@q(H-&HGF74G5=V$P7PriUW-ApgXQfBGZY;5$F3CicpBn5{*fbtk+T=>e#8F$!icUNe;>$=WCsK=sr0zCdK&k{yRc}K0SzB^xYl@q&_|dh-+tUz^=2?;UlGLY$RoyRYYAXb zFTAX_!1YoTgK@KWSAll543p?EPxxPL2;+vqKyUK>pHcp%5(Wb$lg~RP+TZK@ulEZ7 zh3wxyM}MKP>-S%Dyb1XKe~D7w-~Sl?4|JG6KlGOd0)L6#A^iWp7SwcT#~-(A8c6Wh zOfN51a4_4G9l+*CP_q8ZgGiGsB9;hk{_FLK@PvTi$>W9vvv`;u*90sAmstFtMS7>; zwnP^33JKby{3eDvnWxxUP^l?tmunIJ4zU?*L+G?r|h+z3dIg&`r>*C zMw}96uSZm{KZ7Y{Ge==Kg}oFu>%gIb2xAJWI2>cTQ8BqeERHYUHw0W9ZkITexb#}bGWLG^}rXxH8u`lV7B@OL#psT!W!oC#wf%9(8UhFn zv*VfVA^{|b2T6&|@uh4KKJqjXVz`iM@j*k-$T($eny`^O-sk@>=SwmW<=G|KNl@f6 z4#0yZGabK-7$#7>{CJQD#sxAtY#&xI|Ns9OrD#^yi?&8km7O*htc89OsNm%4{QDjO zLCk@&yx_60=20mI_Yrg`0RLvhgj^DC31CclJno-spafaJPS~%X0<8DIQpQ+MFB7xE zy;wnfG;i|?U zl-zkoB=k>N6u=tk$O>SRYwo~unX(e*rD-D=jB%I{^Ah?(h#R0?sy*jBFdLu|`WSoQ z6Z&jJFQmPpH!}!I0AfMb!_{<4LC9OMlpZE%;nU6vGDTGnv6wG54JO5aHw95sF)>Nc zIx7ahrc5xNZtC{i{uv~k0!b4wNf)X*A&JUnIi+!ns{wr-L`=s-h`s292tJ?3_6*^( zDE+hU{t5HD;2Rs5SmA~*rXpJMZ$^dIsV9h^BqW04E%))OILC`B` z8s4nv*Yt%Mb2)q-H-OTBb_3%3x`GrhSOuDbHLJ(lPD`RPUvA3OuFxVB6w@#{=j%Ex zhuU}(Kblr{M_z(v8DgRazNatB)SJy`dIqqZS>b$23nujQX{-WqJs{)bfe~H*Z{jG- zDAQ+n^&@E0CJ<&&F@1y@O-4zM^fnGA%P+E?`UTMS3vRt*c^Y# zV5t32ukjq@m_sn&9XCe)A7oKP!nb{cBCbQLbU_*(c6kc_Ac;B*j%J3QxCz2kK-i?e zL4c}|XZ`34qn5GUC}O$M90==X215sv(bAv33_H(-EH9E#_S>?B`6vj2VC5LkbsKr~ zB{-w3Kp6xI{k^pYft(2FGR=d8mlVUomFAD1!>#IMR20m&7 ziTEsHTx{p0Q3x?9>3+fotkxX@!4PXweq1Mogv0H<-Yjp*t{WTg&Q}LfCy!c=UQ=qzdys?DA1-ImTKaNam;oWwsqek(gf$yM zMlje9@3-^Uby5yRP&D;3_;Mav179f;=(k22Iw^(PY{-?=n)t)DP9DU7&vO>^z1}1#2$d+I!;TZ22LQ; zadqJH?Zn8wTSXx!5NnwvKdkW$Xz32g0&n)aWD!VW;L6PDi}L~46M%TL<#$j6waJ4FcR3C^_(E57{jON!;wg*Ww4~fsUVryd9oO4b&lP zcIcVn^%GH$3q@pTx(Fn(j%8cBC3ymH1^{u_yw9WtP9)QDXtQ`*F*G|KY>C@3{(uN1 zv5v+odvj4G^9l6CfqM^911Av!ms}WpC_$3Jkb=E;C9Gfb4^sk(ap%TP{)xI^F6cJ_ z!)AvTrYbbH~4_uz7LMuw78H`nf1iN4xg#@GD zo6N*EX}l$2F$Fw|x*;6j`TNU=>Ghag=|4!y3Ht0I(w^w(zzpaFC!+gTr@d^ z)4q&LthBkp!{8(U-i8NvWK|_O+Bkq4%yMOOyt!f2W`4OMt2GYnu@+)w%wN4xoLqfI zjyK_kaRRtZZ?e8Ax$pk#(?Ddxpn+32KBqw?>5&0zq%Mzyc!t$V0qVL}G^ix~uy3!Y zgUvwKK=2=~Ytw5QR2^csMH(z@L9<#}A!I^t=BCE(?jKaKC@lG_A zn0fbu&#Uzi0sBB(O^iAJyJ+U0Zb23Z=O+9Nd;)NLa2pPv+(I8t5)Yp~_WyMhz;X}| zc(3#LNgGaV6t>QLop{ju6c`0xHFSf$EWxY3I|i?gZt=$`9$Fr{WQLjGT6D1*1S|mk zaRK^WJ12m|I6A3~MyCL-493CpMo_m-2_QC*SOUv<9q7LU420*>5J}N^D$6|mI&2AA z3zC67L!3NQnkJdVB(Wva93!DWD#4x}PUg$eB$MQj;YGQY)?kSxK*!0t@-)dLc|o3awW@;W_hwkEnS-NNx4Jt`Q{Lpw~fOcRXLcHO*e~~H;DoHdfTFFjBm1z~| zi&OWFr9mZ$1mll`8JB>nE~IOk1`R4n6ddMkU+D!0kX0Z@;3LX@P0>{12;k(Hd94La zg+~-yGto>DBT9~3q;Unng+~+>3;J-9c(Cp|c0xATS9nCRo=F={Y?NsuTWe6EwFQC# z?`|fxu{9Hd*B%&*R+Q_O!SprkIkmQP~sfZ$CHzjR)~mCpuhW zE366JGH)wb;RtAqk4n|^=ukxj$A`&#%$@;$Huw<77kYFCPhyk6<(^t5kceR+RiK*r z%IpN5*fe7chNi9o-FJg&@M>n24;{FN)_AhyS^FScQwB`3PpD9u>qi$&Vv@~Pszd!j z>lL8&f5$=5{Nb2CO*FANYewHPN>J zOE4C`gQ>l&(;`G%nHYEUo4LEQ`c+Y4XG8X+a7~gA{~s&quEKS6Rds zJgTg7wLeH24vxY%Ld4f}S`s8DVkupiCb#y2?H+-j@C{7fhJTRap+^?jn{PfH@e>3k zfRk`R{+s?y5Q&Ev&bN3!ph}$!vkZKvG(Y~I1Q8qV(xRXns$jH1;37Qn_Sr@*#*z~h z!gu2HSj<2&LnS=;zJw3@`@!I^ip>jF?4SiE*4HI_+wt3gGXfvr!<)fgaxAO^w=YFf zZh6MF=wAN{*csqj+!V7?MWZQJIm_!kt#*LUXyY3nClb>|gQ*wZEYeG!i@_9kyu9GC z*vJfWw$1Oi*T{S~+VN7l2?PZff&wqvu4L1IlJwCt@2=aV0Cl_2+{-$L29;zRQ9+F< zeh$2*2wuZ=)jUpvO49qpS?0-$KvyXtT{TbAppwk!7B@t@p$f-Bi2C|84JyfuZpCZY zp2=W=2SCOxkaLa(m1IUYy2mt&V?cc(MBQ+n29;!UDEFI*4f0lv5Ou{x8q~?eNkPkU zLFO_bY6;nOMLrFxE;03VhD~k}=&C9dSu?NFpz0A*Wg9YAHv&-&#>3HAUUV-IO(hA} ziLY;~9|l#?@P~&0d=s8@ov|YeOd#p7EAn|=&j1yj`NpYnw`ovGI_%qJvxCGTv4nvE zaBA!w8dMSsOg?BS{T*~YC*-W+do-vd9rhvXjS=cvyM!ztTS|jU(qXHTWUp-_q}D#9 zK_%(1-yCGOoPZ9H4+R5m*BfOts3aZMN9p^$Y}kI;0!w^&xL&RlO(ij_uX(u(IxJNv z)CZ1Ni-wX|waZjykNsfPE&#?0j<0V;LrFK>pPhEeN&vIQfmv|_RM*kK5+?!433EP8 z0bS)Fu5jr2kD{R@u4+1GIU22)t`vgmH;RUmq*nH$yA>!_(cpq>+U<*IC`o4BXnpdi zF#y?+25{($Cecum#9DRZ$0%+ngAp>Mi@;$E_IHs`VmIx5T%;cZdd>#_6!U2WBLA9@f}E3rrk#v9Is!{{i~e zfWG*G*pIEEu+(c+ii@I-qXjW^z7codjO{eZq*ivdN&l7rSL|&OjlT0#1cRe!9@*I$FK|aNGo|#FLOj7*J z^Y6iK0dgdiDmdBp08KK$MJC81!q;oGFTj6T~aj{QQFl*Rdpou0n$Mu^E3g{T3@H+kT%c8(e zEM6!ZE}FJC5vvu@yyhZU1z$N|P)V08A_)7e+mZYdMEHSma1k@BIxB+2 z_Dht8+;xOtEQ4Ug55FvZ(Mb`+X7Vkbp1Km+AbbWv`L!qkO@nC+yA)j!UfbR4}!LMsTbQy&U9i21_iK8IU#{8 z9uwY1B*r!RR$widu5?zEj~zP)FW{2RDj`Bpoz$HK}l*>(Qfs9EyjY zkrX-Inau}(lE1oodCTBs@u2T@c<@qkvWy5SwYu9TEtymTP)kr5&mp~K$-yq{j?1ck zPdF$i1;>`2u}uRJ&)~sd=+y2(Uj%hC@@|y;gq!0S`VfxzR*tG1xeQm9=btmfBTYl$ zeIuAkno%C{T@bFSyA#gfT!41q&Q0${M+|YSk2m$59tO&nK`+Jg%8JpHR4X>0tmjMb zfASt3D*OxsGCuz|P!r80S*@JA-=n+^pqT*06`e9pB$OnHj2oEP8y!D#0?*-0zX>9l zB%`|4!CpIbfZ0=s={Qj&lcca#i@J4UHZZ3PF>g&4$s{YOi&G5FL&qB-iJyCzGXZV?pfy^2P~MbGn@!SJ-@JXh^A)f;LhPjNwAmzm zc7uHXY6FM`bTAj!``S+0Y?8j)Q?*ro5$K%L%I52sIkGFKgz9v&(P&LCv> z(q$7z2}^fRKq_c_4=jSO@+?0nicD2Xq)vRI55_Tum@SO`bBm)P2X3R|v%EW2 z0m+7)2cHjT&IGu*5WMjSeK<+@MgHi!)e7KNLhv;?^x-7&w@I&j0D27sUCf1>MJbm) zoFo9xx9Lxnf$8f3Il*c+<^*jxag^vx)IRqLQWA10UMQF4k)n;*-fSn9Zy>xuMiD6k zh8T_74(P}5;DxgI1v+Tzl5JI=-C5Vb*lZ{!Ms(y<_%|cwJq0}^Ab`auw#29Ib4QH= zpgaI!I{){V5%cLXxw;|25tbZp^d24AHEV6oA05$F$}eE!qn}3sDc9JDiLTZq>nH2F z=;a2=G8mf&3XUnSZ-`fJSH`EyuE!&{$JDvD^`evcqA_taLW7wG*Ow5W&| zlBrL~xyIZ;PcHe3=?fXxy6*sB63ow4{yGZzKCDtClvqz2-#DisfJB$!;YjD_qL3u{ zrdqP<1S(H*AOYjZ6E8&}NwUoG(Qy@H0Qp)-OZOU4NRr(0%3!iSS_L{FguL-a6q002 zvz&b)3+9u0qH$4Wf`F zMd_3a>n?@DeqNoKU`{gsBmyZMmFTKm)7pjY=jfEc=XkT*dddndk^C8KjjfrUEW-C| z5VCH`Cq;Da#eN}VY;RFWf@3P}*?2mAiKS&OBNZM0y7Z8Dj3nhl0>Y_@$&ZjW8H;9LD7T`mLNSNm;KE7 zaX}iw0G(0Bjj+Lt4wfKABkY{m`;z&9Ef&H)v!H_|NYe<*zbF=52ewTXvTcnO9W24| z7KDwg?Nj^(GXJmLf~8JBTN+s6zWZs`C!6Fxc+iL$whz< z061Qu$8DtpC$G>Eecr*8DK~(w40L?j9G^^sPN{kAq}J5S}>yPe-S)JEM-M^ zpq>vAgtyUwJE9B6cw}(vk;zk}U;=u$r_k7Wk5Y3>J}Zzou#XRl_#zLHu@fGAVvzEf z9LeOd$flgg2rmuxPDUr6P}t!^Qd2oKlsc62@AWxn2#RV!$l>93w}PDe`%YVQJ1g-P z8`8LHodSCrkS7R{=RT!LCK>M*>fd$W3xNy^mjbqUSxJvf>^L*N*79!<2a|^h?j_xN zManf{`7lEQ_~@En!keX$Dg3IrFFe2$sEow3k@Oc*FnXOFU6p6U^=A7-Q1t5*kC;tp zHKH6Id@K5N6HPjGjCjPlCkKN1=(P-7{nW3d0<75_mJ!p--PQsvwZxLEFQwnh6N09D4BDYGW4-QBItx?v)hk@K5qQTTk z%RH&cL2nsv0LAhCp74{DZNm+PFENlVT_K&LcMS{A1byGbgRhD$Y7@z&R(X%Zt>b4w zWl69zj%NNMMcXss`v|}9i=~K>HEvV(Z2*l+;lU%2)ujj7Z1Mll)QVqv$yh!Q6i2Ve z;B%81UCHU(ppYQMC0fZsmM-qCn*0{1DC}_RD=`{WlH501e4}0rP%DLW?cJRQm88_? z-ZK^(8 z^xt1b%*bA(O!x$z(03h>i%Q?=C8EI(ZII(5b)UZEFlMly8J9=+@*#q*Gx*vq1ZHAq zVfw7{S3k6bw@pDLlvvMeqfF(|O4e7<6Q6|G4;P6fpFKY*NHw_x{weAqm>46JMPjMS z!F;{ISR>FEEe_+|HAa;j>;>242e7g)D{AQHF2kYHHh5JtgGRRR23M$kUj!cd(i`W=){>x;?Ly(k$8}aVG z#fBQ{jZS!x4M?t2=H*#Hbf8)YFDaz#X<|A=D+uS>I@98k^zeKKl`yn>p$qYbbI&iJ#U<(6Ra)nY3xGKobjG=CFIrrZ zUOjEfZZR9s*Fea=p=?@QlKz|;w`)N(#KUo+c#sL8#U)uXYvE6eMi+cM2YvDIy%$J_ zOX9Yyh6r_Zi*GKN7{@*fqJt&z+8pyBdwsyR2w@HQbg(2&yZQE*BD$YNM+kd4j1HE> zXXnr8Twek{L+^{=MreqngC%j-I>=dN-PinedC)`X9qCClkx?f-o0jX0@?etOxa3x!rK zWHbC;ryJYo&>fx4TzOCx@rdynZuk3nKp3BfdJexz*mfTsJQ_Ht_~V#XGfZP)GeDz@ zz(CiUMT0M_G#Wh@E#h2M0!N~aA-tkkr6ET)t!tl^w-0obYc8AGn#y(pHxN3Y9#)2b z8ApR{!s7-xGkLzSDCgq?A2T5vR0F#YlU@ck9|5k#FB^KT^$%i*?WCxs{VEge)C(M} zjB} zw}6+JA+3{VU`>p zE^#B)U0k&EE##Xg3k6;9$-EO%h~X~^6?QSeLJ`meye1hHK?-mGxQ=lMFPMCB_g;^* zaa+JNXq?BnoG4mcl0kmb*Ms@Xfmv9xl98UxJaevB zFL+gWgy8H-e<3wn}lX+BRBTlIVSN z_x5mA7z|`#Fu>=Sd$!Zz5?jH&?+{0Ha2dTjj#K6KQd6CIEW#@QQ0}~$`q%+o0q{zg znnr_4Vu5WVJ`Eh%8-^%YP8)@dR9!MeQ;FLjd$rrWGSD;)au{Bhu}_L-c0>YMl0UVm z*aU4+6+w@{Ymkkn>C=fdFMqL4#TGOV2R_c0Jx7~OV(G*;Yjshq8A7b#HNn0-x@=;t z-E)d&g#$GT)>rY~G4P6LGV#7Bs`iG6b@RxEm{13cK#BeLml0E5Le7P{)YuQLm5|pO z2p(!=Zc_`$aRAr&i$<%i5AM={lWkBT^aMk-Zbtx%_BC)XO57KXhHpPp)L)UymF4;Z zR~kHt&*!?7lCz;(QxDH_;@{q*kLdIVG>&yIqk|=hxxNX^*GSt`A#Huj>0n6)=R+YO z>T95jC<@J$uRNuLB^jF&K0RMB9gLt1Xxs=}s_0-zhGyM20m^oOMYm$$*#0l*U`a;i zxvS;Qe*>(X&=frTB^@luz^s$OGePg;qt7Yf+P?^ZwULI+FuVJ$S^PmrlPI}S{6P$kgZJ%Wi!N3||U|eBsIdV3e6-Kq`m$tcL+d^shw4czXpG_~3P+}X%KH{1jl7Qpu za3Tc{gqA@fkz{)o$N*Y3V@{*SdqcX#!5@@`!QLEiO`{0-0yvMyjBw^LIl(?;YfvM9 z4E9?LM&N-FMqpm^9z_f`4qyj)a+y4DViS~}8?^EkxC-49gjXU;S|Wh(K2~@HSy4kgmcqa^Ao2MQ%!75h*6G%ySKIg0=-|N zjG-E4kfJEc3{|BEvnPTPO+hskjHhZu%A@W^2Wr;b$^rGBLrNKiAyZ9BkUXZZ6E}qC zMLH*WGAVW!+K*QhVkVi3WRm35_z0QS{xBnX0ueb9({qreC?>IU_D^-3`UFVm&Un24 z1kVvgq&)ih>0b5iQ2<2~bUfUX=ZXUVUZo^fSk0@?wovfCzfjpGK2Ic<`053eM;yhv zNF4?{e1iv{fm~T23QHYZn_SB_vA_s!LUC}0Nk;Bit|ja9=?|;=830o_wwPWbnIy60 zX{q-Gy_S0mx)t6Bs=Y-rNs8gUCl`ge!w$PXjC**zX0k;wiQWIGX!XWs@X-rsJsz*d z%R~_=E9U3y8(;?= zumc{iQtL!vspHi|eREV#h=DwqHREwGXgx6*eH4XyFx`2!#`^^rcc)Nn1ttT>k+cTt}1h)I9Q| zWP`q&ypIEqjRB8A;`;l`h%vrE#$>VRIm{@1(VP2FtENL~iL?6_P_gOw%WNyJ6lL%W zpgUd|O}I_Wp5;i<$7z=3hPKl2ap5sJ745mgIX%*G0jazCQIu{mfF%Ho=kJgA zN!9%WrmzAxbVpe#U|++7=Wp>+QCM6_nrOk{xJvMKR%R;;F6dEQIF0_ zYew<*3LdOzs&ql-vf0>+$^SV|1LE35gxI!54C_IV+eS!lUYP zW97YkHiD`*;lVqG)=N@#|6r*Vp8e(k^A=FMfpzgwQ|c8d8GX5m!npDNb1PATK-%I) z=wD6ABv%y$?)-qcyKVq-l29OgBFDnI8D(^RSkGxI1BZQv(GnllT5Cvk1t>-Hm!v9Q zM0Z!0!-IF*{ee^OdzzqRJ{ek%Q(MTXHY`pEW%Khxn~$J(hZEyo6lU!94 zwG;2|Rz`m7F66h_AIY(zUXI&jx_A3#prXWxCvpEzBB<2r9@LyzUItLKPJvs&q>%y~ z63n8Evw^$C=FEXO8w%YFPvV}Rsj(Cu8<*mL;Q;9R86G@|clk;UCAR^Ju4j$?o};=H zcAw1>_g-1aPDZKqf zIEwK2G|G>^1++zd1P_mhR%$4wk&Dk}HhJYPApJQQ_+X3Twc>9lwm20!A-#g#)0ZW^*}|A;&u{u7wT|pu?7U z9(vV9G@8PB205>0XM&=rZ{z($M_e?R+8*C7&S5kF_ok2oRl1Y2{a69i^TTs<4)sF^ znjS$Q<2i1w1T~hz4)y`@trtMo@c_nyc#ae`l-vfWJ3rCn_R9oC4+!-*4{1`aGdIM` zk3!k7WqxPZgR)oP!Iib_K@Fu=HsAU}=mjvq6HplE4v{10@|dA49_37S@7>H3DAA*w zhIb5QIQ2);lOYecbIDakeXgKj%XKvDqpJe&a;=RD%_1L05%tbe<{m_q1S(#w_wGZX zFl^3~uOI8S-8JL{hrdkXreCUkl{ zmJS@E#F4jr;c(xts{m;V0~KCjq#mI{21Eq;Q8wLj(X4)VA&mOW5$v3cj!}Tyw~mNg z-!-o~ue&x}X}nf2JH?iZA`%+_<-CBh;lba*FX*@%UI~@R_7)nX{w9%jJrUKK%Y=)9 zh&8@u=l_-zQgzMZ$^x~933_sM+s$<(tYiF*2e-34Y4{H{gFsI+DWu!2C~Q{ zAlo?H-K!u2u{Kv)uv;AJDN27byC817?!)`&ePRX#ohGJvTVHxyis8od(9y=707Yw$ z_&aR@aH2!7>cAcPTWRW1N5iz_%PvrU1SpSJHLe3F_~>;WLq6mR&k#O~BwcK_d8CM5 z$Lj_84NpVU6)Dj6Ot{3C&mxX-l-Wa)Kdw-LVeR^G!6aNWl#)F5T4$kTU+>xMc$1fu$K%0um zh5KcQiTkG2O)&x2CxefaJ8k zyte)}D0mSDNW6$UK8*?p(@{7uPd1L1eqXJ<3erIB0KuW>nh_<{7v{LciY{HGM!Qc>Ga0>EuG!c=5}mDbfgAA)^@L0{Z)J2nebp(jE2WCm!WTheFo*#X4RMPtk+ zUI)inKr{^h>$diOl|TU{sg$;}TQ8?TCwL3F1XuA)5*3XZ5a0>xfW#^mW({^w0jL2$ z@g<}kJE)*=H4T%`_GAaJ`4PnAaTPPV)qq74fsT7$Y7Zru19N54x4YM^>i5_TOd11Z zoT`&XK{c_rL~bHG689{3P8QnyUoJ$=Pp3g8ITW{BGf6oUs6|3ln@k#1l4EhJ=J?MK z1*$Ftl_3^Xy8CHR;d7*1CodRX|5kg{^^!rXV(Tvuhu+43Pa6IGWyJKz>Wnxjto^to zr<-qdDSo&Wy7N9bEIjJ31k_I!91<-ImLxvhc=uKtT6_;4Jo8AGQUL7(n0z0?jEfxfWS#%Fh2ZSP@ZbgW z@`n^qCs*h8LYcatEjlVO`#yjh;lT@9y+<_QzpvpF_E-Vv9hJ3j5xVtWABs<0Z+>MeyMK)+(BOPhSdt+2R{BD?$I|keqP-vF9}Tu{K^Srq)hg5^c}p!-M(LB9PvntWCO1;2*BI?f(^ z>j2M0cshO=F;D90@;M&Hh7=;kZg}2;=7};=f=!72Kvx8niw~YjjX_`iLKi*ZF8I_) zm(Qor-*>2;F*^Q^G9=DV`9hZ;Lc!;iPv2t=;hzEn8qQb$N|WzH6`~_2jLSzaTzCuV zf3uk`pG~2E&wx`#==0v_UP)a4*dKKHR4Hh$w481x#N|k61I|D5izeTXO8+&79kOzP zZ6uU}vKf7Z)=K_X@-VJ+iRn`(!E6VJ<4i)S`gBPl)fTOqb80(Esp- zhh2xj5Z?k-H|}5M5j6Q6D*ew6A9NO#DCpA#IDeBWT|QNOKC;`Hd>GhTLM5WXSepDG zD*YSJu}smILB)mgzy3J7e5&}=j9@8yK>XK0{Np9cgbB3y9!8E7E@tXQwtR)~2@y)s z)stw7;8D5YN5fYYFK~gXkcrfE>GG*kM6N{Xsg84ncnDQap~(;Sr?8Jq-m?X3z{T-G zL(FY`x_qh-dZa%ucNN&bPN)lK7|`WYl@KG34R1!i-7M6_qGr(LQ>D1IM!L@EHokNr z{tP3UeAs0mpT%649iX8HUBFIg2sma;mrrG%ZIQ#Lpgpcb3W6zUiUm!62vz)i?&EqS z9rS+y6IVPwlWb`6BdP3T(K1ks3;Y4V$5X^=N7{T3XIByzpmi7R#X5_|^nrDMIKqH$ zG|qIT5W)2Ff=T?Jbx%!SzQ3lSvz72V0b1W!^?t9#3+Mn-A&?B<-;Q5K%r$oBB=G$W z+mF>*`Y?%(PoU<6Pv0ul1`>E6Y+8pT_%|b_tAD43b?oR7x9iclM^`#-`!K>p%?;?R zsP-GqIq=>WNz;%WYt>hIe*!tK&@@v_-rK;=%0m~9pmQB)YlK3ip>Gba07NyxgEw(1 zT$CWV-THS5K@c2#e+xLD1k2f!^44q12cd7|psQ){P+gBMjOhhmzlk7`iF_DUar*$e#bE=;#0|PSxU({ySbt8LQaJF( z)8&`Y%_hE(M5beHe97;uy!IDrDP%q51?r*8qZPq$EKLI6ou<->!^8oZ=avdmFp{yiQLZ80NR8%Jbn_Jth?EgQkp4}+sAWO*^! zJg%lO`d{HBjJVrs`yNM0(F^Q_@5+R45y@?*d%*skpNBV`_VFOt>p(KL4xO0x6J!K| z3@!M#dc1u#GyvTKi*JmyWQf9x9JP*|KeOKizhi#!>fv-_PXfWO0o zd(!2gXfV2Lp#8@1aDuLh^zLc7R~dOy5Ab6#lhhp&ji)%r<`*(p{|f+v0gU(0{@FCa zh8&Vz&-2d|o}>3XB_Kj@o46jK$u@%@$GL&3)4}= z^e=KRN73>H9^A20Ptc^p8?wS|WO?;_XWpaRD`kZod-)_ywhfm&E82%PZSDp5L-62^ zwLMJ}4<{&4kx!z3QsRj`^a}0|cyPxaJwsD~Q~MX8h|QB`Ho?6wpijbsmjV;d(Zsv5 zIo{kbCq9!;5e1t1zQGo8sK|XOGz{QD#RQDR?npnlBm*2Pa7mItCuAPD3ZR z(TIVMp&KsK;F1^F$Qr38E7f}f_%uBD7<&JTXt)bIID|=Z(Cx*W>4jXN8iEh;|*HTOf)KCA#;*kYGOg7^E;AS>;~IwdJiq4-}${ z-=IMc2;h>{Tu66SS>H@VUnpdA`bmS06|w?nua-!YEU8@cbw|cn3OoOSBL(b~uaR$QQCptJ5jxj!GR*iW_f>20OAA zg|Okvc%-Fn)`GmkHqaTF9mhW?5{>`!xf8hGha{!z{JPWUAmCrXgX4!6i^jXMz2R#r z6jo2F>k)muiorN?UrgYgn1LlC@ODnbQQm9O&9^8H6NL<-d{mx%NeiuT+QSiQF6x~W z1?jcqDRWT8I1li6-s0rY#lsYXSohb*l2XuBE_@-~_Z*`OZ+}IB7;k^`zKkWH6(D)x zY4LL|9X$ED>YRBe-t_=-EIjx+`N|V?$iGi-qqi)GHGecx-x7@$bA$}@>Lh)>V+g@v zcGUUp>cm!}7r-ZhVQ{Caou-B7`VuE~1pnx>cnDe&M8gK&h3=f819v1J>rj5_F=YKU zG}m1Z5AM`{=S1LV!xV#L4ui!1J`rk4Q)aH%D_DksFQ*IG=~F(1AU3+bQ4_r)hAyvWl4P2t z*CQ0*#y7^CVS*i2>4r$SxuuyX{#XC{ak^loSMcC#4Mt@m`7`ZJ&0&p)q_eIG+-Uy+ z4891^CYS>X9@9g^H`V;0mS%gw+5qg6z}O0N)%MkI;_S2AYE~5rs!bpfFUu4vXi0?K zAM&znzh%Kov_;_q&lIdZv!2kRo3OmNP&|#CX)(*p%){Bpnz$8@;}YL|20ohGKL@4rb9{N~X65_>LXQ^7i0+l1w@VojG*= z7@WX(54+r^m_gUR?366O5FZ~8m><9_J_5wHhYfMC#BJkQqBSN@Xr4JHa`(GViCh@O z^7ZgSFYUJrq{9S0^Zb}j1O|BoGJ{Fq(>i-^Xxg#vFp|P4M7))ke*CLI9vr=Z2Lt{R(%hAGrR@g!T@t@9$;oS+cV&h&^{k=y`| ztcS2Q#Ilf6_kWSuem9MWHhaCepFRMEtv@=AMU`c9-R^vM1L4L)$Q;yRC8E? zMe$Mc)1Xet3}%L+<1ADoa^SO9%e3rZnJ7=NN}DmbGqQ*W#FC}c_oJ$F1+)ZTJip{m zi*CyCw(}ubXhkDYtmiEsBbc(72MP*k3!o)HR7pNp`(s5tXg?K#O%DsU_*9w#V3ErS z#?n9cwGkW2Y(SRJC+M#{8NnIY!*qILcyP6a1x2svlXtdpi!{7f0p$>WXl}qkS`sXv z3<_j&C@TKCt<0?_p%FVl3|^DBo}eYd;dj~5zDG?VXX=OHv17p}AK=l&d?KAkOO7L4 zkIUro+c&L4$ri2G{)l?)03(G2)R1`Wt~*al3cN<1#$~ zEMM{o(n2|PJ$Wz?x>FGMcg$6KbYvpR&UbtM;rHkgwOnWfel+3DHCiHEm^?O0P$IiW z*(YBK^q~te3BgUQw(BA#S(~B@I4sfF>4c_6B(L}n?Y{B~x}y}G>ctn#ZEn+)VHd&| zA;j&*DY1I!BoX*(#^@KBmvA(Cw8G{MNmUR0VLc{={8SuX~|R#|XmdUAZ=jc!;{K|x9!Nk;RA zOpyb8wvdP%$xexYMQ;*!=&G50tA}o20V44I?%vX!5@EySdlB3PgsKjsfmu%~U>z`W zh+yV+l!6nQ{MLKj-4#g71o4)HCOTj!tggh{3G#xnVXed}I zP6L?uKJ$75BHTcPG5p){%ZP~?Lr28_7aHxcWXRszX62dP8d;?O5!y`X@?H$2`?q$9!-&T3L*sYD08 z3FwPndq4z^x6`AAXK}nK@K^OeZafV`L4&Ir^38uii2h~}?dnt?$DuM&_%DE!(&am}i!sfx29J~iW z+`|Iz^{$@?*7+e+Z5wcC@kLbtk=!reK0^gCpugbZ_yYJ0C+SNYzZZW{18mR@`fQf> zpY%jgiH-dn)&g7fk7U3d3ThzR6HvI7E` znx^3_FSuQh*h2QD#iveyBj!L*X?6@l@NY)U+%y_&OG^_ch*%Q1-F)4RUtx&b00MB^ zxbLF@xB1-wxJo#f^q5MYq~^y@VQOe~Qm~}YJU~OlY=qnUA?@$)y80Jz(aQn&Wa>%{4L+O0=4+Zeno^&fEK_bUm~)+cuOKEqAki-MT-z(sfsnW z5{>nvMO!LV^c2n4R;rfri7nLj{qBo8=KW{pO`3CZILGn(&D?wE&Ye4V?q7i-)#RU< zs!K_X8(>MbJ_e+=o_wZ@b*>F~6O-A5$*|E#F4Yu}5{rx$+IsRzmC3mN zutbe(1%}07V2x|l4JC#k++#;jcYOW~oGeBEo(=p1a2c$BtyiXqZ>fkU>8hW*`e+(Q z2KIjMDyHDB2`g{kR-^INne-Lai`t10gU6V)k6kbkav`iLy53h~@YAKn>67`rsL8Aj zj@%TQ1crVZQ7!#TmBFk}z_uNtOGSwUrv#H|ns+VOI~6p(py|uFljz5aG$@cBjGG1{ zn*T=Vjr>mF)`jw&VJ4+$#(|1mD7_pSshDq&ANAIXr>8J_|3qD~Awdw42h^TB@4wK@ z4ct?)5mk>WicDj5si|nl6i3f3U+m>Y6Iu3acuY%7WCJMJm{wO^5uRZA$0D+>{L{6z zbOU%g#?R&rPS;c%{FPJaHg5;2ifjYdW~SCS`OpMc(aXkEt_S-C_0k`-V& z2qrdT+Psb`lOZWlu%Qp_-2I>h==4P)cF%IEUm0|%upC>I=X?T^xf+r)4J$(;TqC6U z(kRb$<3W&zc()U?9QQU+Bk|`eAaQ%QA~!NAM7HcknBH6wcyNe6KF}x0TNf=YTnGwJ zSS`DGTyCjMA*e(n*|dreIBx)c2jH`p2PfK84!@!dm@la8;^vRufb@GH&&vvGL3?Eu z@kimve|)w6t$1?l^AOy!=GwBmGKKX!Py%TYNz#L-TNXfCFJTX4ec+B>$|T~<@^{YB zb+%$PY}2fUPx~mtV=2Imi>p-{)GS~12S{Fm(XzYjlD^6$LxUtz^8e4Kb5slS9&Czr z<)?j=8AO2ssiC&HiO+aUp$X)~?yukYSCRl#+Rq-y^DQ^G)3>#%U@irk^|vrkkqUWc zdQ-{AkIuQE-w6CI8vM)uQp7jJNrFS~&0k*s6{5PQ5!J=F)$pzHEhWq&63xQ!jkMCO zTXF-+{7-~ou}4?8a5XAA9P`DLTH5mhVFyD@tkGrb?}Y!9L#DgOpJBziE)Z#W^)?VObdgmiG0sZ&YiHP7ipa5m%>itUT_kdx-9QY5Cv zC8qJ}e`pTwwKurXVN;JSg|(`8K2{~b`I(dNBlKx)@^an-x-ZaK=NPu2GU!qnnO#2o ztOH1XhMd?SqQgRE5_-ChR^6zUuwMp|+#YvedJsqg zLBj4EM^{#cBrw?!t4kK8-HFM^bGB0}uq=lYVZ)$tpH+q_0+%Xy?;}q75m~h}(?c^b ztq2gZo7CkzMKZlU$y)ydp?TQkK&2VdE5M-7v7!1Phyw3)rH_l1P=x0jWrz7fo794;(ooB@QQU& zw#;TwZf{fMv81ILjAp@_NvZYZq&wy@9t)GjkK3(=j}M-vn&Nq}JZao7H=y%+;O9e5 zY^~PWLN)xM;X!(Fw^@&*r z)kv&$k-X>51RwON%cDtvKMr`-sc-s94Su*j*4JprKv2S$(!k6oYx_V#Taj5}4K?#v zWf}a&8qtA+&ogz``O&XK8zqPH>4L?8{S#0%=5M`JuyLW_eC-b-cZtU zA|~SnBKF{(f9@ZU@GmKdX!Tv^qM6YDcvv$VDQ5kwOoVIV1bY3$SB!DxiB(51msseZ zO`b)e#7^ZDf(7$8h4{NNG@I6*C*P#+n^5M4J&jH-{l`?@`O|u3q}z(KnARDn$i@yX zDRNo+`V6N!N0BRNu){Q!K2P?SX6^iPMUez?1ns<1pH}bP8ZXm#!{@-J*)3)2bv2sE zp50!ji#@)qTEDE;QhziAK*rT59F=S9p37UDV9=#nay2hcYUIrCl^Cc2L{pu+l zJfOQKWc#zOT>`w5k2T2gH+%XvTmfbFZfdK{<*0`fy1ZUi!DKfs_pIZ-hzEtBKK4@>3BG72Cq{;C|F2C+I1Ky_?DrOHde{`{n|M$k= ztFtqUtf^UNFTxZPV3MqlJKWtai?ujOK8r%*mAg7%)8z+?f3JsKeCsFwkZjkQewb<%UjJYW+2lzb@E5uEAhm z{&QA&Eh_#=4Zo}_K^~b41H`8Y66kgrlJ>reLR&2 za!a?!VPA=AADY$-zAo9e@U!hm`y>eW1|^yUd7b+SJgZB?cReure5j%=qu$@rwcyFR zOoPQtt!0wM{=eq$PsewM++9(A-j0DBlqrR*XaU7Hmsf|mExCm;PQhYgt3VD-6N94t z4SMY8;?V1bcLF5=)PK9^iZ?*R>e~w7@%vI>u>f+`Ha$KPOQwGVTLG9ql0wP^uzFz8 zkqH>5uV$R}7YjiHQGq5NXYU(}lB*&LnApK~oZqjILdwS3I;XccMe?oSmD(`cZ@gUy z8g7up6aPM2*U20H?HbBYJQ&2TJ7tKnQ989wOx*! z-LUR_@JTIT8tP)rC?4n9+-w{}r1kJJ$Eh?PH2%aCqXfnYDmR&xcs$Px_08i}ScH1tPHiDN_D{R{}O%YlZ6x zKU?L0|Hv+=R;H93i#-zJl#W+<3CMo4OX*RICYnGu!`f-l^ejZ@BM!@od3qUcd1(t< z#4p|yBUV^FAF+Gcj086X`a2LFum)N(NQP+C3r@F}>cr%>Mszv!zTSbXpDhOH5K?UR98TmNVwY2v2HvpMx(?CUhfKG4HOXheP$0hY+vyA|)7w7}KD zVH#49Y}Bx#y}t~(z*4o@9Kk*QrN_fG(SR&33sF)f-e_{M%vkD+xj1X+8%mhQ9Xw$pbGM8qvp-J zwiu{%t;xF3z~Kt0Jw&6;b-a{LSBDJ_0~HIsGQ0%@+?hE8{kiS@b+ z&V}{(qGFeDSQb@hvEfO{E*Z2bEm@bzTd)M`wfNR+wWh#hH*vJ_ z*p}UPLA4?v$mGpMUrfUKp9?X#Gama$ucpGCMNoN6fW=9(dU!)a2?+LCGoAj19Y8^Y z9nw^Em(s9_NCA|zvK8ke9|@{8nTH&YcJ2h)y>afGS8=>++u3%gd&2C2(tgE9y)voG zhBIiz@IKPitN@(j#i&%WCbB0Q>nFc`5L2%PT1rC*ENZ)S2`c&t%1{%I7N3 z8LR!NQ^cs&1g3WKr2Vf>7VZv*=HF_F!y{u(CuWNQQBuc;yWh^|e^;=AEHJ3n4BY62 z5;(4IXn6Nm_A5j~a)(P;lI(nEW{dHHb+NoBQv|sGVAbJwFtB&A)Y<5M@kSXSWni>Z zwi@MvC*$ajfXaDU#1oq&i0D4T^BROWt!-a(4{REbu^q998Tob)1p&!EU4sjSjS)DY z3XN6EpWEb!+Bwr1(c`CC(SxyE?)ll`UHV3bXX40^v=R{1{kjQS`53=!z-YN5$J3|s z&as4fA>)T(**MOe#K|cz1^8tbbX7-Jq2nbyQK(F) zq5Z$@O|1_DU_ET5$B!zgAv#kM-uRd@IlhhHKRoWeqNxj-QXpbr1v{jQ{XuI5LFU=p zuWZI|FwHj*F|dNz-b{vRGU4^7uVI1~l}38(eD(A`5CYZtvxl2=?h;hAkmgn~(Lnot zn;hE|12y1a%Z4?>J;abN+{W-nYB?3-NuS1gf?qbe-`!aZiyru?bcv1PS3%pMok^qg zjtFd4!&+H`4e%7>M(9kO7DF_B*VRdTyH-a79qeta@0{_L1P|rdf^+i4m{0f5!~mN? z>uiX0Z-5N&*}V;4n(Lm9`nn9skrJF0S!+5JX%AFfj`y=^&ipVWaMpfcJNE|(5>)SM2PWGfoJc+hxQv_Yw(A6ND$K!apTU3k7x~T zoPH{+3>wdeJXyMwV<%2B~~Wu-<*-67o0?1A%Or~Szu30_5}jSqPw@O+FL+)>T`w$si&DTm}wn}(TQ+^DV} zEZwi4?Jyr+mtzh|~(vuW>T;i35H)^HC`3hql^|vF__KOg(4!CA|6H zO;;#x5-tw8`sY7u zvH}Vun0W8|j<`0G)MWhp>l4Ee&bLUj-GmPu6a!a$3`@JzX%jluestLknEaV)_+0t( zfl~f^_|4`HRLeDv>AFGx=f%P>*x&P?HCc|gEB+6s{;QeW(0z!*9AIP)2qT{VtjT)m jPICvm?6S6SGEYF^ST*ZF)>m0yWlyl9Lm&LJgTwy;KhwN_ literal 0 HcmV?d00001 diff --git a/150_gfui/src/gplx/gfui/DirInt.java b/150_gfui/src/gplx/gfui/DirInt.java new file mode 100644 index 000000000..a07943a92 --- /dev/null +++ b/150_gfui/src/gplx/gfui/DirInt.java @@ -0,0 +1,33 @@ +/* +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.gfui; import gplx.*; +public class DirInt { + public int Val() {return val;} int val; + public DirInt Rev() {return this == Fwd ? Bwd : Fwd;} + public int CompareToRng(int v, int lo, int hi) { + if (v < lo) return -1 * val; + else if (v > hi) return 1 * val; + else return 0; + } + public int GetValByDir(int ifBwd, int ifFwd) { + return this == Bwd ? ifBwd : ifFwd; + } + public boolean BoundFail(int i, int bound) {return this == Bwd ? i < bound : i > bound;} + DirInt(int v) {this.val = v;} + public static final DirInt + Fwd = new DirInt(1) + , Bwd = new DirInt(-1); +} diff --git a/150_gfui/src/gplx/gfui/GfuiAlign.java b/150_gfui/src/gplx/gfui/GfuiAlign.java new file mode 100644 index 000000000..54bde5e31 --- /dev/null +++ b/150_gfui/src/gplx/gfui/GfuiAlign.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.gfui; import gplx.*; +public class GfuiAlign { + public int Val() {return val;} int val; + public String Name() {return name;} private String name; + public GfuiAlign(int val, String name) {this.val = val; this.name = name;} +} diff --git a/150_gfui/src/gplx/gfui/GfuiAlign_.java b/150_gfui/src/gplx/gfui/GfuiAlign_.java new file mode 100644 index 000000000..6de8860c5 --- /dev/null +++ b/150_gfui/src/gplx/gfui/GfuiAlign_.java @@ -0,0 +1,61 @@ +/* +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.gfui; import gplx.*; +import gplx.core.interfaces.*; +public class GfuiAlign_ implements ParseAble { + public static GfuiAlign as_(Object obj) {return obj instanceof GfuiAlign ? (GfuiAlign)obj : null;} + public static GfuiAlign cast(Object obj) {try {return (GfuiAlign)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, GfuiAlign.class, obj);}} + public static final GfuiAlign + Null = new_(0, "nil") + , Lo = new_(1, "lo") + , Mid = new_(2, "mid") + , Hi = new_(3, "hi"); + public static final GfuiAlign + Top = Lo + , Bot = Hi + , Left = Lo + , Right = Hi; + static GfuiAlign new_(int v, String s) {return new GfuiAlign(v, s);} + public static final GfuiAlign_ Parser = new GfuiAlign_(); + public Object ParseAsObj(String raw) {return parse(raw);} + public static GfuiAlign val_(int v) { + if (v == Lo.Val()) return Lo; + else if (v == Mid.Val()) return Mid; + else if (v == Hi.Val()) return Hi; + else return Null; + } + public static GfuiAlign parse(String raw) { + if (String_.Eq(raw, "bot")) return Bot; + else if (String_.Eq(raw, "mid")) return Mid; + else if (String_.Eq(raw, "top")) return Top; + return Null; + } + public static PointAdp CalcInsideOf(GfuiAlign h, GfuiAlign v, SizeAdp inner, SizeAdp outer, PointAdp adjust) { + int x = CalcInsideOfAxis(h.Val(), inner.Width(), outer.Width()); + int y = CalcInsideOfAxis(v.Val(), inner.Height(), outer.Height()); + return PointAdp_.new_(x + adjust.X(), y + adjust.Y()); + } + public static int CalcInsideOfAxis(int posEnm, int innerSize, int outerSize) { + int rv = 0; + if (posEnm == GfuiAlign_.Null.Val()) rv = Int_.Min_value; + else if (posEnm == GfuiAlign_.Lo.Val()) rv = 0; + else if (posEnm == GfuiAlign_.Mid.Val()) rv = (outerSize - innerSize) / 2; + else if (posEnm == GfuiAlign_.Hi.Val()) rv = outerSize - innerSize; + else throw Err_.new_unhandled(posEnm); + if (rv < 0) rv = 0; + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/GfuiAxisType.java b/150_gfui/src/gplx/gfui/GfuiAxisType.java new file mode 100644 index 000000000..153ea5087 --- /dev/null +++ b/150_gfui/src/gplx/gfui/GfuiAxisType.java @@ -0,0 +1,23 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.gfui; import gplx.*; +public class GfuiAxisType { + public int Val() {return val;} int val; + public GfuiAxisType CrossAxis() {return val == GfuiAxisType.X.val ? GfuiAxisType.Y : GfuiAxisType.X;} + GfuiAxisType(int v) {this.val = v;} + public static final GfuiAxisType X = new GfuiAxisType(1); + public static final GfuiAxisType Y = new GfuiAxisType(2); +} diff --git a/150_gfui/src/gplx/gfui/GfuiBorderEdge.java b/150_gfui/src/gplx/gfui/GfuiBorderEdge.java new file mode 100644 index 000000000..691c26ed7 --- /dev/null +++ b/150_gfui/src/gplx/gfui/GfuiBorderEdge.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.gfui; import gplx.*; +import gplx.core.bits.*; +public class GfuiBorderEdge { + public int Val() {return val;} int val; + public boolean Has(GfuiBorderEdge comp) {return Bitmask_.Has_int(val, comp.val);} + public GfuiBorderEdge Add(GfuiBorderEdge comp) { + return new GfuiBorderEdge(comp.val + val); + } + @gplx.Internal protected GfuiBorderEdge(int v) {this.val = v;} + public static final GfuiBorderEdge Left = new GfuiBorderEdge(1); + public static final GfuiBorderEdge Right = new GfuiBorderEdge(2); + public static final GfuiBorderEdge Top = new GfuiBorderEdge(4); + public static final GfuiBorderEdge Bot = new GfuiBorderEdge(8); + public static final GfuiBorderEdge All = new GfuiBorderEdge(15); +} +class GfuiBorderEdge_ { + public static String To_str(GfuiBorderEdge edge) { + int val = edge.Val(); + if (val == GfuiBorderEdge.Left.Val()) return Left_raw; + else if (val == GfuiBorderEdge.Right.Val()) return Right_raw; + else if (val == GfuiBorderEdge.Top.Val()) return Top_raw; + else if (val == GfuiBorderEdge.Bot.Val()) return Bot_raw; + else if (val == GfuiBorderEdge.All.Val()) return All_raw; + else throw Err_.new_unhandled(edge); + } + public static GfuiBorderEdge parse(String raw) { + if (String_.Eq(raw, Left_raw)) return GfuiBorderEdge.Left; + else if (String_.Eq(raw, Right_raw)) return GfuiBorderEdge.Right; + else if (String_.Eq(raw, Top_raw)) return GfuiBorderEdge.Top; + else if (String_.Eq(raw, Bot_raw)) return GfuiBorderEdge.Bot; + else if (String_.Eq(raw, All_raw)) return GfuiBorderEdge.All; + else throw Err_.new_unhandled(raw); + } + public static final String + All_raw = "all" + , Top_raw = "top" + , Left_raw = "left" + , Right_raw = "right" + , Bot_raw = "bottom" + ; +} diff --git a/150_gfui/src/gplx/gfui/PointAdp.java b/150_gfui/src/gplx/gfui/PointAdp.java new file mode 100644 index 000000000..c96c821cd --- /dev/null +++ b/150_gfui/src/gplx/gfui/PointAdp.java @@ -0,0 +1,33 @@ +/* +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.gfui; import gplx.*; +public class PointAdp implements To_str_able { + public int X() {return x;} final int x; + public int Y() {return y;} final int y; + public PointAdp Op_add(PointAdp val) {return new PointAdp(x + val.x, y + val.y);} + public PointAdp Op_add(int xv, int yv) {return new PointAdp(x + xv, y + yv);} + public PointAdp Op_add(int i) {return new PointAdp(x + i, y + i);} + public PointAdp Op_subtract(PointAdp val) {return new PointAdp(x - val.x, y - val.y);} + public boolean Eq(Object compObj) { + PointAdp comp = PointAdp_.as_(compObj); if (comp == null) return false; + return x == comp.x && y == comp.y; + } + public String To_str() {return String_.Concat_any(x, ",", y);} + @Override public String toString() {return To_str();} + @Override public boolean equals(Object obj) {return Eq(obj);} + @Override public int hashCode() {return super.hashCode();} + public PointAdp(int x, int y) {this.x = x; this.y = y;} +} diff --git a/150_gfui/src/gplx/gfui/PointAdp_.java b/150_gfui/src/gplx/gfui/PointAdp_.java new file mode 100644 index 000000000..fd1b8a787 --- /dev/null +++ b/150_gfui/src/gplx/gfui/PointAdp_.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.gfui; import gplx.*; +public class PointAdp_ { + public static final PointAdp Null = new PointAdp(Int_.Min_value, Int_.Min_value); + public static final PointAdp Zero = new PointAdp(0, 0); + public static PointAdp as_(Object obj) {return obj instanceof PointAdp ? (PointAdp)obj : null;} + public static PointAdp cast(Object obj) {try {return (PointAdp)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, PointAdp.class, obj);}} + public static PointAdp new_(int x, int y) {return new PointAdp(x, y);} + public static PointAdp coerce_(Object o) {PointAdp rv = PointAdp_.as_(o); return (rv == null) ? parse((String)o) : rv;} + public static PointAdp parse(String raw) { + try { + String[] ary = String_.Split(raw, ","); + return new PointAdp(Int_.Parse(ary[0]), Int_.Parse(ary[1])); + } catch (Exception exc) {throw Err_.new_parse_exc(exc, PointAdp.class, raw);} + } +} diff --git a/150_gfui/src/gplx/gfui/RectAdp.java b/150_gfui/src/gplx/gfui/RectAdp.java new file mode 100644 index 000000000..1b956b956 --- /dev/null +++ b/150_gfui/src/gplx/gfui/RectAdp.java @@ -0,0 +1,42 @@ +/* +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.gfui; import gplx.*; +public class RectAdp { + public SizeAdp Size() {return size;} SizeAdp size = SizeAdp_.Zero; + public PointAdp Pos() {return pos;} PointAdp pos = PointAdp_.Zero; + public int Width() {return size.Width();} public int Height() {return size.Height();} + public int X() {return pos.X();} public int Y() {return pos.Y();} + public PointAdp CornerTL() {return pos;} + public PointAdp CornerTR() {return PointAdp_.new_(pos.X() + size.Width(), pos.Y());} + public PointAdp CornerBL() {return PointAdp_.new_(pos.X(), pos.Y() + size.Height());} + public PointAdp CornerBR() {return PointAdp_.new_(pos.X() + size.Width(), pos.Y() + size.Height());} + @gplx.Internal protected boolean ContainsPoint(PointAdp point) { + return point.X() >= pos.X() && point.X() <= pos.X() + size.Width() + && point.Y() >= pos.Y() && point.Y() <= pos.Y() + size.Height(); + } + public RectAdp Op_add(RectAdp v) { + return new RectAdp(pos.Op_add(v.Pos()), size.Op_add(v.Size())); + } + @Override public String toString() {return String_.Concat_any(pos, ";", size);} + @Override public boolean equals(Object obj) { + RectAdp comp = (RectAdp)obj; + return size.Eq(comp.size) && pos.Eq(comp.pos); + } + @Override public int hashCode() {return super.hashCode();} + public String Xto_str() {return String_.Concat_any(pos, ",", size);} + + public RectAdp(PointAdp pos, SizeAdp size) {this.pos = pos; this.size = size;} +} diff --git a/150_gfui/src/gplx/gfui/RectAdpF.java b/150_gfui/src/gplx/gfui/RectAdpF.java new file mode 100644 index 000000000..1391910cf --- /dev/null +++ b/150_gfui/src/gplx/gfui/RectAdpF.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.gfui; import gplx.*; +public class RectAdpF { //_20101206 // supports Graphics.MeasureString + public float X() {return x;} float x; public float Y() {return y;} float y; + public float Width() {return width;} float width; public float Height() {return height;} float height; + public SizeAdpF Size() {if (size == null) size = SizeAdpF_.new_(width, height); return size;} SizeAdpF size; + public boolean Eq(RectAdpF comp) { + return comp.x == x && comp.y == y && comp.width == width && comp.height == height; + } + + public static final RectAdpF Null = new_(Int_.Min_value, Int_.Min_value, Int_.Min_value, Int_.Min_value); + public static RectAdpF new_(float x, float y, float width, float height) { + RectAdpF rv = new RectAdpF(); + rv.x = x; rv.y = y; rv.width = width; rv.height = height; + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/RectAdp_.java b/150_gfui/src/gplx/gfui/RectAdp_.java new file mode 100644 index 000000000..d8abe69a5 --- /dev/null +++ b/150_gfui/src/gplx/gfui/RectAdp_.java @@ -0,0 +1,32 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.gfui; import gplx.*; +public class RectAdp_ { + public static final RectAdp Zero = new RectAdp(PointAdp_.Zero, SizeAdp_.Zero); + public static RectAdp new_(int x, int y, int width, int height) {return new RectAdp(PointAdp_.new_(x, y), SizeAdp_.new_(width, height));} + public static RectAdp corners_(PointAdp upperLeft, PointAdp bottomRight) {return new RectAdp(upperLeft, SizeAdp_.corners_(upperLeft, bottomRight));} + public static RectAdp vector_(PointAdp pos, SizeAdp size) {return new RectAdp(pos, size);} + public static RectAdp size_(int w, int h) {return new_(0, 0, w, h);} + public static RectAdp size_(SizeAdp size) {return new RectAdp(PointAdp_.Zero, size);} + public static RectAdp parse_ws_(String raw) {return parse(String_.Replace(raw, " ", ""));} + public static RectAdp parse(String raw) { + try { + String[] ary = String_.Split(raw, ","); + return RectAdp_.new_(Int_.Parse(ary[0]), Int_.Parse(ary[1]), Int_.Parse(ary[2]), Int_.Parse(ary[3])); + } catch(Exception exc) {throw Err_.new_parse_exc(exc, RectAdp.class, raw);} + } + public static String Xto_str(RectAdp rect) {return String_.Format("{0},{1},{2},{3}", rect.X(), rect.Y(), rect.Width(), rect.Height());} +} diff --git a/150_gfui/src/gplx/gfui/SizeAdp.java b/150_gfui/src/gplx/gfui/SizeAdp.java new file mode 100644 index 000000000..73aa5f1a5 --- /dev/null +++ b/150_gfui/src/gplx/gfui/SizeAdp.java @@ -0,0 +1,34 @@ +/* +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.gfui; import gplx.*; +public class SizeAdp { + public int Width() {return width;} int width; + public int Height() {return height;} int height; + public int AxisLength(GfuiAxisType axis) {return axis == GfuiAxisType.X ? width : height;} + public SizeAdp Op_add(int w, int h) {return SizeAdp_.new_(width + w, height + h);} + public SizeAdp Op_add(SizeAdp s) {return SizeAdp_.new_(width + s.width, height + s.height);} + public SizeAdp Op_subtract(int val) {return SizeAdp_.new_(width - val, height - val);} + public SizeAdp Op_subtract(int w, int h) {return SizeAdp_.new_(width - w, height - h);} + public String To_str() {return String_.Concat_any(width, ",", height);} + public boolean Eq(Object o) { + SizeAdp comp = (SizeAdp)o; if (comp == null) return false; + return width == comp.width && height == comp.height; + } + @Override public String toString() {return To_str();} + @Override public boolean equals(Object obj) {return Eq(obj);} + @Override public int hashCode() {return super.hashCode();} + public SizeAdp(int width, int height) {this.width = width; this.height = height;} +} diff --git a/150_gfui/src/gplx/gfui/SizeAdpF.java b/150_gfui/src/gplx/gfui/SizeAdpF.java new file mode 100644 index 000000000..418efebfb --- /dev/null +++ b/150_gfui/src/gplx/gfui/SizeAdpF.java @@ -0,0 +1,24 @@ +/* +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.gfui; import gplx.*; +import gplx.core.interfaces.*; +public class SizeAdpF implements ParseAble { + public float Width() {return width;} float width; + public float Height() {return height;} float height; + public Object ParseAsObj(String raw) {return SizeAdp_.parse(raw);} + @Override public String toString() {return String_.Concat_any(width, ":", height);} + @gplx.Internal protected SizeAdpF(float width, float height) {this.width = width; this.height = height;} +} diff --git a/150_gfui/src/gplx/gfui/SizeAdpF_.java b/150_gfui/src/gplx/gfui/SizeAdpF_.java new file mode 100644 index 000000000..78cc513dc --- /dev/null +++ b/150_gfui/src/gplx/gfui/SizeAdpF_.java @@ -0,0 +1,32 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.gfui; import gplx.*; +public class SizeAdpF_ { + public static final SizeAdpF Null = new_(Int_.Min_value, Int_.Min_value); + public static final SizeAdpF Zero = new_(0, 0); + public static final SizeAdpF Parser = new SizeAdpF(0, 0); + public static SizeAdpF as_(Object obj) {return obj instanceof SizeAdpF ? (SizeAdpF)obj : null;} + public static SizeAdpF new_(float width, float height) {return new SizeAdpF(width, height);} + public static SizeAdpF coerce_(Object obj) {SizeAdpF rv = as_(obj); return rv == null ? parse((String)obj) : rv;} + public static SizeAdpF parse(String s) { + try { + String[] ary = String_.Split(s, ","); if (ary.length != 2) throw Err_.new_wo_type("SizeAdf should only have 2 numbers separated by 1 comma"); + float val1 = Float_.parse(ary[0]); + float val2 = Float_.parse(ary[1]); + return new_(val1, val2); + } catch (Exception e) {throw Err_.new_parse_exc(e, SizeAdpF.class, s);} + } +} diff --git a/150_gfui/src/gplx/gfui/SizeAdp_.java b/150_gfui/src/gplx/gfui/SizeAdp_.java new file mode 100644 index 000000000..7d530d3c0 --- /dev/null +++ b/150_gfui/src/gplx/gfui/SizeAdp_.java @@ -0,0 +1,40 @@ +/* +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.gfui; import gplx.*; +public class SizeAdp_ { + public static final SizeAdp Null = new SizeAdp(Int_.Min_value, Int_.Min_value); + public static final SizeAdp Zero = new SizeAdp(0, 0); + public static final SizeAdp[] Ary_empty = new SizeAdp[0]; + public static SizeAdp cast(Object obj) {try {return (SizeAdp)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, SizeAdp.class, obj);}} + public static SizeAdp new_(int width, int height) {return new SizeAdp(width, height);} + public static SizeAdp parse(String raw) {return parse_or(raw, SizeAdp_.Null);} + public static SizeAdp parse_or(String raw, SizeAdp or) { + String[] ary = String_.Split(raw, ","); if (ary.length != 2) return or; + int w = Int_.Parse_or(ary[0], Int_.Min_value); if (w == Int_.Min_value) return or; + int h = Int_.Parse_or(ary[1], Int_.Min_value); if (h == Int_.Min_value) return or; + return new SizeAdp(w, h); + } + public static SizeAdp corners_(PointAdp topLeft, PointAdp bottomRight) { + int width = bottomRight.X() - topLeft.X(); + int height = bottomRight.Y() - topLeft.Y(); + return new_(width, height); + } + public static PointAdp center_(SizeAdp outer, SizeAdp inner) { + int x = (outer.Width() - inner.Width()) / 2; if (x < 0) x = 0; + int y = (outer.Height() - inner.Height()) / 2; if (y < 0) y = 0; + return PointAdp_.new_(x, y); + } +} diff --git a/150_gfui/src/gplx/gfui/controls/GfuiBorderMgr.java b/150_gfui/src/gplx/gfui/controls/GfuiBorderMgr.java index a27517de8..653025a72 100644 --- a/150_gfui/src/gplx/gfui/controls/GfuiBorderMgr.java +++ b/150_gfui/src/gplx/gfui/controls/GfuiBorderMgr.java @@ -13,3 +13,56 @@ 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.gfui.controls; import gplx.*; import gplx.gfui.*; +import gplx.gfui.draws.*; import gplx.gfui.gfxs.*; +import gplx.core.strings.*; +public class GfuiBorderMgr { + public PenAdp All() {return all;} public GfuiBorderMgr All_(PenAdp v) {SyncPens(true); all = v; return this;} PenAdp all; + public PenAdp Left() {return left;} public GfuiBorderMgr Left_(PenAdp v) {SyncPens(false); left = v; return this;} PenAdp left; + public PenAdp Right() {return right;} public GfuiBorderMgr Right_(PenAdp v) {SyncPens(false); right = v; return this;} PenAdp right; + public PenAdp Top() {return top;} public GfuiBorderMgr Top_(PenAdp v) {SyncPens(false); top = v; return this;} PenAdp top; + public PenAdp Bot() {return bot;} public GfuiBorderMgr Bot_(PenAdp v) {SyncPens(false); bot = v; return this;} PenAdp bot; + public void Bounds_sync(RectAdp v) {bounds = v;} RectAdp bounds = RectAdp_.Zero; + public void DrawData(GfxAdp gfx) { + if (all != null) + gfx.DrawRect(all, bounds); + else { + if (left != null) gfx.DrawLine(left, bounds.CornerBL(), bounds.CornerTL()); + if (right != null) gfx.DrawLine(right, bounds.CornerTR(), bounds.CornerBR()); + if (top != null) gfx.DrawLine(top, bounds.CornerTL(), bounds.CornerTR()); + if (bot != null) gfx.DrawLine(bot, bounds.CornerBR(), bounds.CornerBL()); + } + } + public GfuiBorderMgr None_() {Edge_set(GfuiBorderEdge.All, null); return this;} + public void Edge_setObj(Object o) { + if (o == null) + this.None_(); + else { + Object[] ary = (Object[])o; + this.Edge_set(GfuiBorderEdge.All, PenAdp_.new_((ColorAdp)ary[1], Float_.cast(ary[0]))); + } + } + public void Edge_set(GfuiBorderEdge edge, PenAdp pen) { + int val = edge.Val(); + if (val == GfuiBorderEdge.All.Val()) {all = pen; return;} + else if (val == GfuiBorderEdge.Left.Val()) {left = pen; return;} + else if (val == GfuiBorderEdge.Right.Val()) {right = pen; return;} + else if (val == GfuiBorderEdge.Top.Val()) {top = pen; return;} + else if (val == GfuiBorderEdge.Bot.Val()) {bot = pen; return;} + else throw Err_.new_unhandled(edge); + } + void SyncPens(boolean isAll) { + if (isAll) { + left = null; right = null; top = null; bot = null; + } + else { + if (all != null) { + left = all.Clone(); right = all.Clone(); top = all.Clone(); bot = all.Clone(); + } + all = null; + } + } + public String To_str() {return String_bldr_.new_().Add_kv_obj("all", all).Add_kv_obj("left", left).Add_kv_obj("right", right).Add_kv_obj("top", top).Add_kv_obj("bot", bot).To_str();} + @Override public String toString() {return To_str();} + public static GfuiBorderMgr new_() {return new GfuiBorderMgr();} GfuiBorderMgr() {} +} diff --git a/150_gfui/src/gplx/gfui/controls/GfuiBorderMgr_tst.java b/150_gfui/src/gplx/gfui/controls/GfuiBorderMgr_tst.java index a27517de8..98e55c75a 100644 --- a/150_gfui/src/gplx/gfui/controls/GfuiBorderMgr_tst.java +++ b/150_gfui/src/gplx/gfui/controls/GfuiBorderMgr_tst.java @@ -13,3 +13,39 @@ 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.gfui.controls; import gplx.*; import gplx.gfui.*; +import org.junit.*; import gplx.gfui.draws.*; import gplx.gfui.imgs.*; +public class GfuiBorderMgr_tst { + @Before public void setup() { + borderMgr = GfuiBorderMgr.new_(); + } + @Test public void NullToEdge() { // all null -> one edge + tst_Eq(borderMgr, null, null, null, null, null); + + borderMgr.Top_(red); + tst_Eq(borderMgr, null, red, null, null, null); + } + @Test public void EdgeToAll() { // one edge -> all edge + borderMgr.Top_(red); + tst_Eq(borderMgr, null, red, null, null, null); + + borderMgr.All_(black); + tst_Eq(borderMgr, black, null, null, null, null); + } + @Test public void AllToEdge() { // all edge -> one new; three old + borderMgr.All_(red); + tst_Eq(borderMgr, red, null, null, null, null); + + borderMgr.Top_(black); + tst_Eq(borderMgr, null, black, red, red, red); + } + void tst_Eq(GfuiBorderMgr borderMgr, PenAdp all, PenAdp top, PenAdp left, PenAdp right, PenAdp bottom) { + Tfds.Eq(borderMgr.All(), all); + Tfds.Eq(borderMgr.Top(), top); + Tfds.Eq(borderMgr.Left(), left); + Tfds.Eq(borderMgr.Right(), right); + Tfds.Eq(borderMgr.Bot(), bottom); + } + GfuiBorderMgr borderMgr; + PenAdp black = PenAdp_.black_(), red = PenAdp_.new_(ColorAdp_.Red, 1); +} diff --git a/150_gfui/src/gplx/gfui/controls/customs/DataBndr_whenEvt_execCmd.java b/150_gfui/src/gplx/gfui/controls/customs/DataBndr_whenEvt_execCmd.java index a27517de8..42d9e9aac 100644 --- a/150_gfui/src/gplx/gfui/controls/customs/DataBndr_whenEvt_execCmd.java +++ b/150_gfui/src/gplx/gfui/controls/customs/DataBndr_whenEvt_execCmd.java @@ -13,3 +13,38 @@ 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.gfui.controls.customs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.core.interfaces.*; import gplx.gfui.controls.elems.*; +public class DataBndr_whenEvt_execCmd implements InjectAble, Gfo_invk, Gfo_evt_itm { + public Gfo_evt_mgr Evt_mgr() {if (evt_mgr == null) evt_mgr = new Gfo_evt_mgr(this); return evt_mgr;} Gfo_evt_mgr evt_mgr; + public DataBndr_whenEvt_execCmd WhenArg_(String v) {whenArg = v; return this;} private String whenArg = "v"; + public DataBndr_whenEvt_execCmd WhenEvt_(Gfo_evt_itm whenSrc, String whenEvt) { + this.whenEvt = whenEvt; + Gfo_evt_mgr_.Sub_same(whenSrc, whenEvt, this); + return this; + } + public DataBndr_whenEvt_execCmd GetCmd_(Gfo_invk getInvk, String getCmd) { + this.getInvk = getInvk; + this.getCmd = getCmd; + return this; + } Gfo_invk getInvk; String getCmd; + public void Inject(Object owner) { + setInvk = (Gfo_invk)owner; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, whenEvt)) { + Object evtVal = m.CastObjOr(whenArg, ""); + Object getVal = getInvk.Invk(GfsCtx.Instance, 0, getCmd, GfoMsg_.new_cast_(getCmd).Add("v", evtVal)); + GfoMsg setMsg = GfoMsg_.new_cast_(setCmd).Add("v", Object_.Xto_str_strict_or_empty(getVal)); + setInvk.Invk(GfsCtx.Instance, 0, setCmd, setMsg); + return Gfo_invk_.Rv_handled; + } + else return Gfo_invk_.Rv_unhandled; + } + String whenEvt, setCmd; Gfo_invk setInvk; + public static DataBndr_whenEvt_execCmd text_() { + DataBndr_whenEvt_execCmd rv = new DataBndr_whenEvt_execCmd(); + rv.setCmd = GfuiElemKeys.Text_set; + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/controls/customs/GfuiBnd_box_status.java b/150_gfui/src/gplx/gfui/controls/customs/GfuiBnd_box_status.java index a27517de8..51985c05d 100644 --- a/150_gfui/src/gplx/gfui/controls/customs/GfuiBnd_box_status.java +++ b/150_gfui/src/gplx/gfui/controls/customs/GfuiBnd_box_status.java @@ -13,3 +13,42 @@ 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.gfui.controls.customs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; import gplx.gfui.kits.core.*; import gplx.gfui.envs.*; import gplx.gfui.controls.windows.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; +public class GfuiBnd_box_status implements Gfo_invk, UsrMsgWkr { + public GfuiElem Box() {return box;} GfuiElem box; + public void ExecUsrMsg(int type, UsrMsg umsg) { + box.Invoke(Gfo_invk_cmd.New_by_val(this, WriteText_cmd, umsg.To_str())); + } + public void WriteText(String text) { + GfuiElem lastFocus = GfuiFocusMgr.Instance.FocusedElem(); // HACK:WINFORMS:.Visible=true will automatically transfer focus to textBox; force Focus back to original + box.Text_(text); + GfuiWin ownerWin = box.OwnerWin(); + if (ownerWin != null && !ownerWin.Visible()) { + ownerWin.Visible_set(true); + ownerWin.Zorder_front(); + } + timer.Enabled_off(); timer.Enabled_on(); // reset time + GfuiEnv_.DoEvents(); // WORKAROUND:WIN32: needed, else text will not refresh (ex: splash screen); will cause other timers to fire! (ex: mediaPlaylistMgr) + if (lastFocus != null) lastFocus.Focus(); + } + public void HideWin() { + if (box.OwnerWin() != null) + box.OwnerWin().Visible_off_(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, WriteText_cmd)) WriteText(m.ReadStr("v")); + else if (ctx.Match(k, TimerTick_evt)) HideWin(); + return this; + } static final String TimerTick_evt = "TimerTick", WriteText_cmd = "WriteText"; + TimerAdp timer; + public static GfuiBnd_box_status new_(String key) { + GfuiBnd_box_status rv = new GfuiBnd_box_status(); + GfuiTextBox txtBox = GfuiTextBox_.new_(); + txtBox.Key_of_GfuiElem_(key); + txtBox.BackColor_(ColorAdp_.Black).ForeColor_(ColorAdp_.Green).TextAlignH_right_(); + rv.box = txtBox; + rv.timer = TimerAdp.new_(rv, TimerTick_evt, 2000, false); + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/controls/customs/GfuiCheckListBox.java b/150_gfui/src/gplx/gfui/controls/customs/GfuiCheckListBox.java index a27517de8..f11b7ebac 100644 --- a/150_gfui/src/gplx/gfui/controls/customs/GfuiCheckListBox.java +++ b/150_gfui/src/gplx/gfui/controls/customs/GfuiCheckListBox.java @@ -13,3 +13,28 @@ 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.gfui.controls.customs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.core.lists.*; +import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; +public class GfuiCheckListBox extends GfuiElemBase { + public void Items_reverse() {checkListBox.Items_reverse();} + public void Items_count() {checkListBox.Items_count();} + public void Items_setAt(int i, boolean v) {checkListBox.Items_setCheckedAt(i, v);} + public void Items_setAll(boolean v) {checkListBox.Items_setAll(v);} + public void Items_clear() {checkListBox.Items_clear();} + public void Items_add(Object item, boolean v) {checkListBox.Items_add(item, v);} + public List_adp Items_getAll() {return checkListBox.Items_getAll();} + public List_adp Items_getChecked() {return checkListBox.Items_getChecked();} + + GxwCheckListBox checkListBox; + @Override public GxwElem UnderElem_make(Keyval_hash ctorArgs) {return new GxwCheckListBox_lang();} + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + super.ctor_GfuiBox_base(ctorArgs); + this.checkListBox = (GxwCheckListBox)UnderElem(); + } + public static GfuiCheckListBox new_() { + GfuiCheckListBox rv = new GfuiCheckListBox(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_true_()); + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/controls/customs/GfuiCheckListPanel.java b/150_gfui/src/gplx/gfui/controls/customs/GfuiCheckListPanel.java index a27517de8..973ac498a 100644 --- a/150_gfui/src/gplx/gfui/controls/customs/GfuiCheckListPanel.java +++ b/150_gfui/src/gplx/gfui/controls/customs/GfuiCheckListPanel.java @@ -13,3 +13,47 @@ 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.gfui.controls.customs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.layouts.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; +public class GfuiCheckListPanel extends GfuiElemBase { + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + super.ctor_GfuiBox_base(ctorArgs); + InitToggleWidget(); + InitReverseWidget(); + InitListBoxWidget(); + + this.Lyt_activate(); + this.Lyt().Bands_add(GftBand.new_().Cell_abs_(60)); + this.Lyt().Bands_add(GftBand.new_().Cell_abs_(70)); + this.Lyt().Bands_add(GftBand.fillAll_()); + } + public void Items_clear() {listBox.Items_clear();} + public void Items_add(Object item, boolean checkBoxState) {listBox.Items_add(item, checkBoxState);} + public List_adp Items_getAll() {return listBox.Items_getAll();} + public List_adp Items_getChecked() {return listBox.Items_getChecked();} + public void SetAllCheckStates(boolean v) { + listBox.Items_setAll(v); + } + void InitToggleWidget() { + toggle = GfuiChkBox_.invk_("toggle", this, "&toggle", this, ToggleChecks_cmd); toggle.Size_(60, 20); toggle.AlignH_set(GfuiAlign_.Left); + } + void InitReverseWidget() { + GfuiBtn_.invk_("reverse", this, this, ReverseChks_cmd).Size_(70, 20).Text_("&reverse"); + } + void InitListBoxWidget() { + listBox.Owner_(this, "listBox"); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, ToggleChecks_cmd)) SetAllCheckStates(toggle.Val()); + else if (ctx.Match(k, ReverseChks_cmd)) listBox.Items_reverse(); + else return super.Invk(ctx, ikey, k, m); + return this; + } public static final String ToggleChecks_cmd = "ToggleChecks", ReverseChks_cmd = "ReverseChks"; + GfuiChkBox toggle; + GfuiCheckListBox listBox = GfuiCheckListBox.new_(); + public static GfuiCheckListPanel new_() { + GfuiCheckListPanel rv = new GfuiCheckListPanel(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_false_()); + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/controls/customs/GfuiFormPanel.java b/150_gfui/src/gplx/gfui/controls/customs/GfuiFormPanel.java index a27517de8..b8cdda096 100644 --- a/150_gfui/src/gplx/gfui/controls/customs/GfuiFormPanel.java +++ b/150_gfui/src/gplx/gfui/controls/customs/GfuiFormPanel.java @@ -13,3 +13,25 @@ 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.gfui.controls.customs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.layouts.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.windows.*; import gplx.gfui.controls.elems.*; +public class GfuiFormPanel extends GfuiElemBase { + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + super.ctor_GfuiBox_base(ctorArgs); + this.Width_(60); // default to 60; do not force callers to always set width + GfuiWin ownerForm = (GfuiWin)ctorArgs.Get_val_or(GfuiElem_.InitKey_ownerWin, null); + + GfoFactory_gfui.Btn_MoveBox(this, ownerForm); + GfoFactory_gfui.Btn_MinWin2(this); + GfoFactory_gfui.Btn_QuitWin3(this); + + this.Lyt_activate(); + this.Lyt().Bands_add(GftBand.new_().Cells_num_(3)); + } + public static GfuiFormPanel new_(GfuiWin form) { + GfuiFormPanel rv = new GfuiFormPanel(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_false_().Add(GfuiElem_.InitKey_ownerWin, form)); + rv.Owner_(form, "formPanel"); + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/controls/customs/GfuiIoDialogUtl.java b/150_gfui/src/gplx/gfui/controls/customs/GfuiIoDialogUtl.java index a27517de8..35b1b22a9 100644 --- a/150_gfui/src/gplx/gfui/controls/customs/GfuiIoDialogUtl.java +++ b/150_gfui/src/gplx/gfui/controls/customs/GfuiIoDialogUtl.java @@ -13,3 +13,39 @@ 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.gfui.controls.customs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import java.io.File; +import java.awt.FileDialog; + +import javax.swing.JFrame; +import gplx.gfui.controls.elems.*; import gplx.gfui.controls.windows.*; +public class GfuiIoDialogUtl { + public static Io_url SelectDir() {return SelectDir(Io_url_.Empty);} + public static Io_url SelectDir(Io_url startingDir) { + FileDialog openFileDialog = NewOpenFileDialog(startingDir); +// openFileDialog.FileName = @"press enter to select this folder"; + openFileDialog.setVisible(true); + String selectedDir = openFileDialog.getDirectory(); + if (selectedDir == null) return Io_url_.Empty; // nothing selected + Io_url selected = Io_url_.new_any_(selectedDir); + Io_url selectedFil = selected.GenSubFil(openFileDialog.getFile()); + return selectedFil.OwnerDir(); + } + public static Io_url SelectFile() {return SelectFile(Io_url_.Empty);} + public static Io_url SelectFile(Io_url startingDir) { + FileDialog openFileDialog = NewOpenFileDialog(startingDir); + openFileDialog.setVisible(true); + String selectedDir = openFileDialog.getDirectory(); + if (selectedDir == null) return Io_url_.Empty; // nothing selected + Io_url selected = Io_url_.new_any_(selectedDir); + Io_url selectedFil = selected.GenSubFil(openFileDialog.getFile()); + return selectedFil; + } + static FileDialog NewOpenFileDialog(Io_url startingDir) { + //WORKAROUND (Windows.Forms): InitialDirectory only works for new instances; http://groups.google.com/groups?hl=en&lr=&threadm=ubk6dEUYDHA.536%40TK2MSFTNGP10.phx.gbl&rnum=30&prev=/groups%3Fq%3Ddotnet%2BInitialDirectory%26hl%3Den%26btnG%3DGoogle%2BSearch + FileDialog openFileDialog = new FileDialog(new JFrame()); + if (!startingDir.EqNull()) + openFileDialog.setDirectory(startingDir.Xto_api()); + return openFileDialog; + } + } diff --git a/150_gfui/src/gplx/gfui/controls/customs/GfuiIoUrlSelectBox.java b/150_gfui/src/gplx/gfui/controls/customs/GfuiIoUrlSelectBox.java index a27517de8..8db62316a 100644 --- a/150_gfui/src/gplx/gfui/controls/customs/GfuiIoUrlSelectBox.java +++ b/150_gfui/src/gplx/gfui/controls/customs/GfuiIoUrlSelectBox.java @@ -13,3 +13,57 @@ 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.gfui.controls.customs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.layouts.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; +public abstract class GfuiIoUrlSelectBox extends GfuiElemBase { + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + super.ctor_GfuiBox_base(ctorArgs); + label = GfuiLbl_.sub_("label", this); + pathBox.Owner_(this, "pathBox"); + GfuiBtn_.invk_("selectPath", this, this, SelectAction_cmd).Text_("..."); + + this.Lyt_activate(); + this.Lyt().Bands_add(GftBand.new_().Cell_abs_(60).Cell_pct_(100).Cell_abs_(30)); + } + public static final String PathSelected_evt = "PathSelected_evt"; + public GfuiLbl Label() {return label;} GfuiLbl label; + public Io_url Url() {return Io_url_.new_any_(pathBox.TextMgr().Val());} + public Io_url StartingFolder() {return startingFolder;} + public void StartingFolder_set(Io_url v) {this.startingFolder = v;} Io_url startingFolder = Io_url_.Empty; + @Override public void Focus() {pathBox.Focus();} + + void SelectAction() { + Io_url selectedPath = SelectAction_hook(this.startingFolder); + if (selectedPath.EqNull()) return; + pathBox.Text_(selectedPath.Raw()); + Gfo_evt_mgr_.Pub(this, PathSelected_evt); + } + protected abstract Io_url SelectAction_hook(Io_url startingFolder); + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, SelectAction_cmd)) SelectAction(); + else return super.Invk(ctx, ikey, k, m); + return this; + } public static final String SelectAction_cmd = "SelectAction"; + + GfuiComboBox pathBox = GfuiComboBox.new_(); +} +class IoFileSelector extends GfuiIoUrlSelectBox { @Override protected Io_url SelectAction_hook(Io_url startingFolder) { + return GfuiIoDialogUtl.SelectFile(startingFolder); + } + public static IoFileSelector new_() { + IoFileSelector rv = new IoFileSelector(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_false_()); + return rv; + } IoFileSelector() {} +} +class IoFolderSelector extends GfuiIoUrlSelectBox { @Override protected Io_url SelectAction_hook(Io_url startingFolder) { + Io_url url = GfuiIoDialogUtl.SelectDir(startingFolder); + this.StartingFolder_set(url); + return url; + } + public static IoFolderSelector new_() { + IoFolderSelector rv = new IoFolderSelector(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_false_()); + return rv; + } IoFolderSelector() {} +} diff --git a/150_gfui/src/gplx/gfui/controls/customs/GfuiIoUrlSelectBox_.java b/150_gfui/src/gplx/gfui/controls/customs/GfuiIoUrlSelectBox_.java index a27517de8..61ff05402 100644 --- a/150_gfui/src/gplx/gfui/controls/customs/GfuiIoUrlSelectBox_.java +++ b/150_gfui/src/gplx/gfui/controls/customs/GfuiIoUrlSelectBox_.java @@ -13,3 +13,8 @@ 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.gfui.controls.customs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public class GfuiIoUrlSelectBox_ { + public static GfuiIoUrlSelectBox dir_() {return IoFolderSelector.new_();} + public static GfuiIoUrlSelectBox fil_() {return IoFileSelector.new_();} +} diff --git a/150_gfui/src/gplx/gfui/controls/customs/GfuiMoveElemBnd.java b/150_gfui/src/gplx/gfui/controls/customs/GfuiMoveElemBnd.java index a27517de8..cb86432ac 100644 --- a/150_gfui/src/gplx/gfui/controls/customs/GfuiMoveElemBnd.java +++ b/150_gfui/src/gplx/gfui/controls/customs/GfuiMoveElemBnd.java @@ -13,3 +13,68 @@ 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.gfui.controls.customs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.ipts.*; import gplx.gfui.controls.elems.*; +import gplx.core.interfaces.*; +public class GfuiMoveElemBnd implements IptBnd, Gfo_invk, InjectAble { + public String Key() {return "gplx.gfui.moveWidget";} + public List_adp Ipts() {return args;} List_adp args = List_adp_.New(); + public IptEventType EventTypes() {return IptEventType_.add_(IptEventType_.KeyDown, IptEventType_.MouseDown, IptEventType_.MouseMove, IptEventType_.MouseUp);} + public void Exec(IptEventData iptData) { + int val = iptData.EventType().Val(); + if (val == IptEventType_.KeyDown.Val()) ExecKeyDown(iptData); + else if (val == IptEventType_.MouseDown.Val()) ExecMouseDown(iptData); + else if (val == IptEventType_.MouseUp.Val()) ExecMouseUp(iptData); + else if (val == IptEventType_.MouseMove.Val()) ExecMouseMove(iptData); + } + public GfuiElem TargetElem() {return targetElem;} public void TargetElem_set(GfuiElem v) {this.targetElem = v;} GfuiElem targetElem; + public static final String target_idk = "target"; + @gplx.Virtual public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, target_idk)) return targetElem; + else if (ctx.Match(k, "key")) return key; + else return Gfo_invk_.Rv_unhandled; + } + + public String Key_of_GfuiElem() {return keyIdf;} public void Key_of_GfuiElem_(String val) {keyIdf = val;} private String keyIdf = "moveElemBtnBnd"; + public void Inject(Object owner) { + GfuiElem ownerElem = (GfuiElem)owner; + ownerElem.IptBnds().Add(this); + ownerElem.IptBnds().EventsToFwd_set(IptEventType_.None); // NOTE: suppress bubbling else ctrl+right will cause mediaPlayer to jump forward + } + + void ExecMouseDown(IptEventData msg) { + moving = true; + anchor = msg.MousePos(); + } + void ExecMouseMove(IptEventData msg) { + if (!moving) return; + targetElem.Pos_(msg.MousePos().Op_subtract(anchor).Op_add(targetElem.Pos())); + } + void ExecMouseUp(IptEventData msg) { + moving = false; + } + void ExecKeyDown(IptEventData msg) { + PointAdp current = targetElem.Pos(); + PointAdp offset = PointAdp_.cast(hash.Get_by(msg.EventArg())); + targetElem.Pos_(current.Op_add(offset)); + } + @gplx.Internal protected void Key_set(String key) {this.key = key;} private String key; + public Object Srl(GfoMsg owner) {return IptBnd_.Srl(owner, this);} + + boolean moving = false; + PointAdp anchor = PointAdp_.Zero; Hash_adp hash = Hash_adp_.New(); + public static GfuiMoveElemBnd new_() {return new GfuiMoveElemBnd();} + GfuiMoveElemBnd() { + args.Add_many(IptMouseBtn_.Left, IptMouseMove.AnyDirection); + IptBndArgsBldr.AddWithData(args, hash, IptKey_.Ctrl.Add(IptKey_.Up), PointAdp_.new_(0, -10)); + IptBndArgsBldr.AddWithData(args, hash, IptKey_.Ctrl.Add(IptKey_.Down), PointAdp_.new_(0, 10)); + IptBndArgsBldr.AddWithData(args, hash, IptKey_.Ctrl.Add(IptKey_.Left), PointAdp_.new_(-10, 0)); + IptBndArgsBldr.AddWithData(args, hash, IptKey_.Ctrl.Add(IptKey_.Right), PointAdp_.new_(10, 0)); + } +} +class IptBndArgsBldr { + public static void AddWithData(List_adp list, Hash_adp hash, IptArg arg, Object data) { + list.Add(arg); + hash.Add(arg, data); + } +} diff --git a/150_gfui/src/gplx/gfui/controls/customs/GfuiMoveElemBtn.java b/150_gfui/src/gplx/gfui/controls/customs/GfuiMoveElemBtn.java index a27517de8..0c73d9cbb 100644 --- a/150_gfui/src/gplx/gfui/controls/customs/GfuiMoveElemBtn.java +++ b/150_gfui/src/gplx/gfui/controls/customs/GfuiMoveElemBtn.java @@ -13,3 +13,74 @@ 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.gfui.controls.customs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.ipts.*; import gplx.gfui.layouts.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; import gplx.gfui.controls.windows.*; +public class GfuiMoveElemBtn extends GfuiBtn { @Override public GxwElem UnderElem_make(Keyval_hash ctorArgs) {return GxwElemFactory_.Instance.lbl_();} + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + super.ctor_GfuiBox_base(ctorArgs); + this.Text_("*"); + this.TipText_("move/resize"); + + this.IptBnds().EventsToFwd_set(IptEventType_.None); + this.IptBnds().Add(moveBinding); + this.IptBnds().Add(GfuiResizeFormBnd.new_()); + } + public void TargetElem_set(GfuiElem v) {moveBinding.TargetElem_set(v);} + + final GfuiMoveElemBnd moveBinding = GfuiMoveElemBnd.new_(); + public static GfuiMoveElemBtn new_() { + GfuiMoveElemBtn rv = new GfuiMoveElemBtn(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_true_()); + return rv; + } +} +class GfuiResizeFormBnd implements IptBnd { + public String Key() {return "gplx.gfui.resizeForm";} + public List_adp Ipts() {return args;} List_adp args = List_adp_.New(); + public IptEventType EventTypes() {return IptEventType_.KeyDown.Add(IptEventType_.MouseDown).Add(IptEventType_.MouseUp).Add(IptEventType_.MouseMove);} + public void Exec(IptEventData iptData) { + int val = iptData.EventType().Val(); + if (val == IptEventType_.KeyDown.Val()) ExecKeyDown(iptData); + else if (val == IptEventType_.MouseDown.Val()) ExecMouseDown(iptData); + else if (val == IptEventType_.MouseUp.Val()) ExecMouseUp(iptData); + else if (val == IptEventType_.MouseMove.Val()) ExecMouseMove(iptData); + } + void ExecMouseDown(IptEventData iptData) { + active = true; + lastPos = iptData.MousePos(); + } + void ExecMouseMove(IptEventData iptData) { + if (!active) return; + + PointAdp delta = iptData.MousePos().Op_subtract(lastPos); + if (delta.Eq(PointAdp_.Zero)) return; + ResizeForm(iptData.Sender(), XtoSize(delta)); + } + void ExecMouseUp(IptEventData iptData) { + active = false; + } + void ExecKeyDown(IptEventData iptData) { + SizeAdp deltaSize = (SizeAdp)hash.Get_by(iptData.EventArg()); + ResizeForm(iptData.Sender(), deltaSize); + } + void ResizeForm(GfuiElem elem, SizeAdp deltaSize) { + GfuiWin form = elem.OwnerWin(); + SizeAdp newSize = Op_add(form.Size(), deltaSize); + form.Size_(newSize); + GftGrid.LytExecRecur(form); + form.Redraw(); // must redraw, else artifacts will remain; ex: grid may leave random lines + } + static SizeAdp XtoSize(PointAdp point) {return SizeAdp_.new_(point.X(), point.Y());} + static SizeAdp Op_add(SizeAdp lhs, SizeAdp rhs) {return SizeAdp_.new_(lhs.Width() + rhs.Width(), lhs.Height() + rhs.Height());} + public Object Srl(GfoMsg owner) {return IptBnd_.Srl(owner, this);} + + boolean active = false; PointAdp lastPos = PointAdp_.Zero; Hash_adp hash = Hash_adp_.New(); + public static GfuiResizeFormBnd new_() {return new GfuiResizeFormBnd();} + GfuiResizeFormBnd() { + args.Add_many(IptMouseBtn_.Right, IptMouseMove.AnyDirection); + IptBndArgsBldr.AddWithData(args, hash, IptKey_.Ctrl.Add(IptKey_.Shift).Add(IptKey_.Up), SizeAdp_.new_(0, -10)); + IptBndArgsBldr.AddWithData(args, hash, IptKey_.Ctrl.Add(IptKey_.Shift).Add(IptKey_.Down), SizeAdp_.new_(0, 10)); + IptBndArgsBldr.AddWithData(args, hash, IptKey_.Ctrl.Add(IptKey_.Shift).Add(IptKey_.Left), SizeAdp_.new_(-10, 0)); + IptBndArgsBldr.AddWithData(args, hash, IptKey_.Ctrl.Add(IptKey_.Shift).Add(IptKey_.Right), SizeAdp_.new_(10, 0)); + } +} diff --git a/150_gfui/src/gplx/gfui/controls/customs/GfuiMoveElemBtn_tst.java b/150_gfui/src/gplx/gfui/controls/customs/GfuiMoveElemBtn_tst.java index a27517de8..06fed80fa 100644 --- a/150_gfui/src/gplx/gfui/controls/customs/GfuiMoveElemBtn_tst.java +++ b/150_gfui/src/gplx/gfui/controls/customs/GfuiMoveElemBtn_tst.java @@ -13,3 +13,21 @@ 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.gfui.controls.customs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import org.junit.*; import gplx.gfui.ipts.*; import gplx.gfui.controls.windows.*; import gplx.gfui.controls.standards.*; import gplx.gfui.controls.customs.*; +public class GfuiMoveElemBtn_tst { + @Before public void setup() { + form = GfuiWin_.app_("form"); form.Size_(100, 100); + moveBtn = GfuiBtn_.new_("moveBtn"); + GfuiMoveElemBnd bnd = GfuiMoveElemBnd.new_(); bnd.TargetElem_set(form); + moveBtn.IptBnds().Add(bnd); + } + @Test public void Basic() { + Tfds.Eq(form.X(), 0); + IptEventMgr.ExecKeyDown(moveBtn, IptEvtDataKey.test_(MoveRightArg())); + Tfds.Eq(form.X(), 10); + } + + IptKey MoveRightArg() {return IptKey_.Ctrl.Add(IptKey_.Right);} + GfuiWin form; GfuiBtn moveBtn; +} diff --git a/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBar.java b/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBar.java index a27517de8..e184687b8 100644 --- a/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBar.java +++ b/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBar.java @@ -13,3 +13,35 @@ 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.gfui.controls.customs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.layouts.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.elems.*; +public class GfuiStatusBar extends GfuiElemBase { + public GfuiStatusBox Box() {return statusBox;} GfuiStatusBox statusBox; + public GfuiMoveElemBtn MoveButton() {return moveBtn;} GfuiMoveElemBtn moveBtn; + void StatusBar_Focus() { + statusBox.Focus_able_(true); + statusBox.Visible_set(true); + statusBox.Focus(); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, StatusBarFocus_cmd)) StatusBar_Focus(); + else return super.Invk(ctx, ikey, k, m); + return this; + } public static final String StatusBarFocus_cmd = "StatusBarFocus"; + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + super.ctor_GfuiBox_base(ctorArgs); + moveBtn = GfuiMoveElemBtn.new_(); + statusBox = GfuiStatusBox_.new_("statusBox"); + statusBox.Owner_(this).Border_off_().Visible_on_(); + moveBtn.Owner_(this, "moveBox"); + GfoFactory_gfui.Btn_MinWin2(this); + GfoFactory_gfui.Btn_QuitWin3(this); + this.Lyt_activate(); + this.Lyt().Bands_add(GftBand.new_().Cell_pct_(100).Cell_abs_(20).Cell_abs_(20).Cell_abs_(20)); + } + public static GfuiStatusBar new_() { + GfuiStatusBar rv = new GfuiStatusBar(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_false_()); + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBarBnd.java b/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBarBnd.java index a27517de8..8300ce49f 100644 --- a/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBarBnd.java +++ b/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBarBnd.java @@ -13,3 +13,16 @@ 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.gfui.controls.customs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.core.interfaces.*; +import gplx.gfui.ipts.*; import gplx.gfui.controls.windows.*; +public class GfuiStatusBarBnd implements InjectAble { + public GfuiStatusBar Bar() {return statusBar;} GfuiStatusBar statusBar = GfuiStatusBar.new_(); + public void Inject(Object owner) { + GfuiWin form = GfuiWin_.as_(owner); if (form == null) throw Err_.new_type_mismatch(GfuiWin.class, owner); + statusBar.Owner_(form, "statusBar"); + IptBnd_.cmd_to_(IptCfg_.Null, form, statusBar, GfuiStatusBar.StatusBarFocus_cmd, IptKey_.add_(IptKey_.Ctrl, IptKey_.Alt, IptKey_.T)); + statusBar.MoveButton().TargetElem_set(form); + } + public static GfuiStatusBarBnd new_() {return new GfuiStatusBarBnd();} GfuiStatusBarBnd() {} +} diff --git a/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBox.java b/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBox.java index a27517de8..15263fd3c 100644 --- a/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBox.java +++ b/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBox.java @@ -13,3 +13,80 @@ 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.gfui.controls.customs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.core.envs.*; import gplx.gfui.kits.core.*; import gplx.gfui.envs.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.standards.*; +public class GfuiStatusBox extends GfuiTextBox implements UsrMsgWkr { public GfuiStatusBox Active_(boolean v) {active = v; return this;} private boolean active = true; + public GfuiStatusBox VisibilityDuration_(int v) {timer.Interval_(v); visibilityDuration = v; return this;} int visibilityDuration; + @Override public void Opened_cbk() { + super.Opened_cbk(); + UsrDlg_.Instance.Reg(UsrMsgWkr_.Type_Note, this); + } + public void ExecUsrMsg(int type, UsrMsg umsg) { + if ( !active + || !this.OwnerElem().Visible() // WORKAROUND.WINFORMS: else application hangs on Invoke + || !this.Opened_done() + ) return; + this.CreateControlIfNeeded(); // WORKAROUND.WINFORMS: else will sometimes throw: Cannot call Invoke or InvokeAsync on a control until the window handle has been created + this.VisibilityDuration_(umsg.VisibilityDuration()); + String text = String_.Replace(umsg.To_str(), Op_sys.Cur().Nl_str(), " "); // replace NewLine with " " since TextBox cannot show NewLine + Invoke(Gfo_invk_cmd.New_by_val(this, Invk_WriteText, text)); + } + public void WriteText(String text) { + if (!this.Visible()) { + this.Visible_set(true); + this.Zorder_front(); + } + this.Text_(text); + timer.Enabled_off().Enabled_on(); // Enabled_off().Enabled_on() resets timer; timer can be at second 1 of 3, and this will reset back to timer 0 + GfuiEnv_.DoEvents(); // WORKAROUND.WINFORMS: needed, else text will not refresh (ex: splash screen); NOTE!!: will cause other timers to fire! (ex: mediaPlaylistMgr) + } + @Override public void Focus() { + this.Focus_able_(true); + super.Focus(); + this.Focus_able_(false); + } + @Override public boolean DisposeCbk() { + super.DisposeCbk(); + timer.Rls(); + UsrDlg_.Instance.RegOff(UsrMsgWkr_.Type_Note, this); + if (timerCmd != null) timerCmd.Rls(); + return true; + } + void HideWindow() { + timer.Enabled_off(); + Text_(""); + this.Visible_set(false); + this.Zorder_back(); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_HideWindow)) HideWindow(); + else if (ctx.Match(k, Invk_Text_empty)) {timer.Enabled_off(); Text_(GfuiTextBox_.NewLine + Text());} + else if (ctx.Match(k, Invk_WriteText)) WriteText(m.ReadStr("v")); + else return super.Invk(ctx, ikey, k, m); + return this; + } static final String Invk_HideWindow = "HideWindow", Invk_WriteText = "WriteText", Invk_Text_empty = "Text_empty"; + TimerAdp timer; + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + super.ctor_GfuiBox_base(ctorArgs); + this.Border_on_(false); + this.Focus_able_(false); + this.Visible_set(false); + timer = TimerAdp.new_(this, Invk_HideWindow, 2000, false); + } + @Override public void ctor_kit_GfuiElemBase(Gfui_kit kit, String key, GxwElem underElem, Keyval_hash ctorArgs) { + super.ctor_kit_GfuiElemBase(kit, key, underElem, ctorArgs); + this.Border_on_(false); + this.Focus_able_(false); + this.Visible_set(true); + timerCmd = kit.New_cmd_sync(this); + timer = TimerAdp.new_(timerCmd, GfuiInvkCmd_.Invk_sync, 2000, false); + } GfuiInvkCmd timerCmd; + public void ctor_kit_GfuiElemBase_drd(Gfui_kit kit, String key, GxwElem underElem, Keyval_hash ctorArgs) { + super.ctor_kit_GfuiElemBase(kit, key, underElem, ctorArgs); + this.Border_on_(false); + this.Focus_able_(false); + this.Visible_set(true); + timerCmd = kit.New_cmd_sync(this); +// timer = TimerAdp.new_(timerCmd, GfuiInvkCmd_.Invk_sync, 2000, false); + } +} diff --git a/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBoxBnd.java b/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBoxBnd.java index a27517de8..f6d7df8f7 100644 --- a/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBoxBnd.java +++ b/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBoxBnd.java @@ -13,3 +13,26 @@ 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.gfui.controls.customs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; import gplx.gfui.ipts.*; import gplx.gfui.layouts.*; import gplx.gfui.controls.windows.*; +public class GfuiStatusBoxBnd implements Gfo_invk { + public GfuiStatusBox Box() {return statusBox;} GfuiStatusBox statusBox = GfuiStatusBox_.new_("statusBox"); + void ShowTime() { + statusBox.ExecUsrMsg(UsrMsgWkr_.Type_Note, UsrMsg.new_(Datetime_now.Get().XtoStr_gplx_long())); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_ShowTime)) ShowTime(); + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String Invk_ShowTime = "ShowTime"; + public static GfuiStatusBoxBnd gft_(GfuiWin owner) { + GfuiStatusBoxBnd rv = new GfuiStatusBoxBnd(); + rv.ctor_GfuiStatusBoxBnd(owner); + return rv; + } + void ctor_GfuiStatusBoxBnd(GfuiWin win) { + IptBnd_.cmd_to_(IptCfg_.Null, win, this, GfuiStatusBoxBnd.Invk_ShowTime, IptKey_.add_(IptKey_.Ctrl, IptKey_.Shift, IptKey_.T)); + statusBox.Owner_(win).Visible_off_().BackColor_(ColorAdp_.Black).TextAlignH_right_().ForeColor_(ColorAdp_.Green); + win.Lyt().SubLyts().Add(GftGrid.new_().Bands_dir_(DirInt.Bwd).Bands_add(GftBand.new_().Cells_num_(1).Len1_abs_(13))); + } +} diff --git a/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBox_.java b/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBox_.java index a27517de8..6fed2807c 100644 --- a/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBox_.java +++ b/150_gfui/src/gplx/gfui/controls/customs/GfuiStatusBox_.java @@ -13,3 +13,18 @@ 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.gfui.controls.customs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.kits.core.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; +public class GfuiStatusBox_ { + public static GfuiStatusBox new_(String key) { + GfuiStatusBox rv = new GfuiStatusBox(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_false_()); + rv.Key_of_GfuiElem_(key); + return rv; + } + public static GfuiStatusBox kit_(Gfui_kit kit, String key, GxwElem underElem) { + GfuiStatusBox rv = new GfuiStatusBox(); + rv.ctor_kit_GfuiElemBase(kit, key, underElem, GfuiElem_.init_focusAble_false_()); + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/controls/elems/GfuiElem.java b/150_gfui/src/gplx/gfui/controls/elems/GfuiElem.java index a27517de8..c7a890cea 100644 --- a/150_gfui/src/gplx/gfui/controls/elems/GfuiElem.java +++ b/150_gfui/src/gplx/gfui/controls/elems/GfuiElem.java @@ -13,3 +13,66 @@ 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.gfui.controls.elems; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; import gplx.gfui.gfxs.*; import gplx.gfui.ipts.*; import gplx.gfui.layouts.*; import gplx.gfui.imgs.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.*; import gplx.gfui.controls.windows.*; +import gplx.gfui.layouts.swts.*; +import gplx.core.interfaces.*; +public interface GfuiElem extends Gfo_invk, GxwCbkHost, IptBndsOwner, GftItem, Gfo_evt_itm { + //% Layout + int X(); GfuiElem X_(int val); + int Y(); GfuiElem Y_(int val); + int X_max(); + int Y_max(); + GfuiElem Pos_(PointAdp val); GfuiElem Pos_(int x, int y); + int Width(); GfuiElem Width_(int val); + int Height(); GfuiElem Height_(int val); + GfuiElem Size_(SizeAdp val); GfuiElem Size_(int w, int h); + RectAdp Rect(); void Rect_set(RectAdp rect); + void Zorder_front(); void Zorder_back(); + PointAdp Pos(); + SizeAdp Size(); + Swt_layout_mgr Layout_mgr(); void Layout_mgr_(Swt_layout_mgr v); + Swt_layout_data Layout_data(); void Layout_data_(Swt_layout_data v); + + //% Visual + boolean Visible(); void Visible_set(boolean v); GfuiElem Visible_on_(); GfuiElem Visible_off_(); + ColorAdp BackColor(); GfuiElem BackColor_(ColorAdp v); + GfuiBorderMgr Border(); GfuiElem Border_on_(); GfuiElem Border_off_(); + void Redraw(); + + //% Text + GfxStringData TextMgr(); + String Text(); GfuiElem Text_(String v); GfuiElem Text_any_(Object v); + GfuiElem ForeColor_(ColorAdp v); + void TextAlignH_(GfuiAlign v); + GfuiElem TextAlignH_left_(); GfuiElem TextAlignH_right_(); GfuiElem TextAlignH_center_(); + String TipText(); GfuiElem TipText_(String v); + + //% Focus + boolean Focus_has(); + boolean Focus_able(); GfuiElem Focus_able_(boolean v); + int Focus_idx(); GfuiElem Focus_idx_(int val); + String Focus_default(); GfuiElem Focus_default_(String v); + void Focus(); + + //% Action + void Click(); + + //% Elem Tree Hierarchy (Owners And Subs) + //String Key_of_GfuiElem(); + GfuiElem Key_of_GfuiElem_(String val); + GfuiElem OwnerElem(); GfuiElem OwnerElem_(GfuiElem val); GfuiElem Owner_(GfuiElem owner); GfuiElem Owner_(GfuiElem owner, String key); + GfuiElemList SubElems(); + GfuiElem Inject_(InjectAble sub); + + //% Form + GfuiWin OwnerWin(); GfuiElem OwnerWin_(GfuiWin val); + void Opened_cbk(); boolean Opened_done(); + void Dispose(); + + //% Infrastructure + GxwElem UnderElem(); + GxwElem UnderElem_make(Keyval_hash ctorArgs); + void ctor_GfuiBox_base(Keyval_hash ctorArgs); + void Invoke(Gfo_invk_cmd cmd); +} diff --git a/150_gfui/src/gplx/gfui/controls/elems/GfuiElemBase.java b/150_gfui/src/gplx/gfui/controls/elems/GfuiElemBase.java index a27517de8..582612374 100644 --- a/150_gfui/src/gplx/gfui/controls/elems/GfuiElemBase.java +++ b/150_gfui/src/gplx/gfui/controls/elems/GfuiElemBase.java @@ -13,3 +13,286 @@ 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.gfui.controls.elems; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; import gplx.gfui.gfxs.*; import gplx.gfui.ipts.*; import gplx.gfui.layouts.*; import gplx.gfui.imgs.*; import gplx.gfui.kits.core.*; +import gplx.gfui.layouts.swts.*; +import gplx.gfui.controls.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.standards.*; import gplx.gfui.controls.windows.*; +import gplx.core.strings.*; import gplx.core.interfaces.*; +public class GfuiElemBase implements GfuiElem { + //% Layout + public Gfo_evt_mgr Evt_mgr() {if (evt_mgr == null) evt_mgr = new Gfo_evt_mgr(this); return evt_mgr;} Gfo_evt_mgr evt_mgr; + public Gfo_invk_cmd_mgr InvkMgr() {return invkMgr;} Gfo_invk_cmd_mgr invkMgr = Gfo_invk_cmd_mgr.new_(); + public int X() {return underMgr.X();} public GfuiElem X_(int val) {underMgr.X_set(val); return this;} + public int Y() {return underMgr.Y();} public GfuiElem Y_(int val) {underMgr.Y_set(val); return this;} + public int X_max() {return underMgr.X() + underMgr.Width();} public int Y_max() {return underMgr.Y() + underMgr.Height();} + public PointAdp Pos() {return underMgr.Pos();} public GfuiElem Pos_(PointAdp val) {underMgr.Pos_set(val); return this;} public GfuiElem Pos_(int x, int y) {return Pos_(PointAdp_.new_(x, y));} + public int Width() {return underMgr.Width();} public GfuiElem Width_(int val) {underMgr.Width_set(val); return this;} + public int Height() {return underMgr.Height();} + public GfuiElem Height_(int val) {underMgr.Height_set(val); return this;} + public SizeAdp Size() {return underMgr.Size();} public GfuiElem Size_(int w, int h) {return Size_(SizeAdp_.new_(w, h));} + public GfuiElem Size_(SizeAdp val) { + underMgr.Size_set(val); + return this; + } + public RectAdp Rect() {return RectAdp_.vector_(this.Pos(), this.Size());} + public void Rect_set(RectAdp rect) { + underMgr.Rect_set(rect); + textMgr.OwnerSize_sync(rect.Size()); + } + public int Gft_w() {return underElem.Core().Width();} public GftItem Gft_w_(int v) {underElem.Core().Width_set(v); return this;} + public int Gft_h() {return underElem.Core().Height();} public GftItem Gft_h_(int v) {underElem.Core().Height_set(v); return this;} + public int Gft_x() {return underElem.Core().X();} public GftItem Gft_x_(int v) {underElem.Core().X_set(v); return this;} + public int Gft_y() {return underElem.Core().Y();} public GftItem Gft_y_(int v) {underElem.Core().Y_set(v); return this;} + public GftItem Gft_rect_(RectAdp rect) {underElem.Core().Rect_set(rect); return this;} + public GftGrid Lyt() {return lyt;} GftGrid lyt = null; + public GfuiElemBase Lyt_activate() {lyt = GftGrid.new_(); return this;} + public void Lyt_exec() { + GftItem[] ary = new GftItem[subElems.Count()]; + for (int i = 0; i < ary.length; i++) + ary[i] = (GfuiElemBase)subElems.Get_at(i); + SizeChanged_ignore = true; + lyt.Exec(this, ary); + SizeChanged_ignore = false; + } + public void Zorder_front() {underMgr.Zorder_front();} public void Zorder_back() {underMgr.Zorder_back();} + @gplx.Virtual public void Zorder_front_and_focus() { + this.Zorder_front(); + this.Visible_set(true); + this.Focus(); + } + public Swt_layout_mgr Layout_mgr() {return underElem.Core().Layout_mgr();} + public void Layout_mgr_(Swt_layout_mgr v) {underElem.Core().Layout_mgr_(v);} + public Swt_layout_data Layout_data() {return underElem.Core().Layout_data();} + public void Layout_data_(Swt_layout_data v) {underElem.Core().Layout_data_(v);} + + //% Visual + @gplx.Virtual public boolean Visible() {return underMgr.Visible();} @gplx.Virtual public void Visible_set(boolean v) {underMgr.Visible_set(v);} + public GfuiElem Visible_on_() {this.Visible_set(true); return this;} public GfuiElem Visible_off_() {this.Visible_set(false); return this;} + @gplx.Virtual public ColorAdp BackColor() {return backColor;} ColorAdp backColor = ColorAdp_.White; + @gplx.Virtual public GfuiElem BackColor_(ColorAdp v) {backColor = v; underMgr.BackColor_set(backColor); return this;} + public GfuiBorderMgr Border() {return border;} GfuiBorderMgr border = GfuiBorderMgr.new_(); + public GfuiElem Border_on_() {border.All_(PenAdp_.new_(ColorAdp_.Black, 1)); return this;} + public GfuiElem Border_off_() {border.All_(null); return this;} + public GfxStringData TextMgr() {return textMgr;} GfxStringData textMgr; + public String Text() {return textMgr.Val();} + public GfuiElem Text_any_(Object obj) {return Text_(Object_.Xto_str_strict_or_null_mark(obj));} + @gplx.Virtual public GfuiElem Text_(String v) { + this.TextMgr().Text_set(v); + Click_key_set_(v); + return this; + } + @gplx.Virtual public GfuiElem ForeColor_(ColorAdp v) {textMgr.Color_(v); return this;} + public void TextAlignH_(GfuiAlign v) {textMgr.AlignH_(v);} + public GfuiElem TextAlignH_left_() {textMgr.AlignH_(GfuiAlign_.Left); return this;} + public GfuiElem TextAlignH_right_() {textMgr.AlignH_(GfuiAlign_.Right); return this;} + public GfuiElem TextAlignH_center_() {textMgr.AlignH_(GfuiAlign_.Mid); return this;} + public String TipText() {return underElem.Core().TipText();} public GfuiElem TipText_(String v) {underElem.Core().TipText_set(v); return this;} + @gplx.Virtual public void Redraw() {underMgr.Invalidate();} + public boolean CustomDraw() {return customDraw;} public void CustomDraw_set(boolean v) {customDraw = v;} private boolean customDraw; + + + //% Focus + public boolean Focus_has() {return underElem.Core().Focus_has();} + public boolean Focus_able() {return underElem.Core().Focus_able();} public GfuiElem Focus_able_(boolean v) {underElem.Core().Focus_able_(v); return this;} + public String Focus_default() {return defaultFocusKey;} public GfuiElem Focus_default_(String v) {defaultFocusKey = v; return this;} private String defaultFocusKey; + public int Focus_idx() {return focusKey_order_manual;} int focusKey_order_manual = GfuiFocusOrderer.NullVal; + public GfuiElem Focus_idx_(int val) { + underElem.Core().Focus_index_set(val); + focusKey_order_manual = val; + return this; + } + @gplx.Virtual public void Focus() { + if (subElems.Count() == 0) // if no subs, focus self + underElem.Core().Focus(); + else if (defaultFocusKey != null) { // if default is specified, focus it + GfuiElem focusTarget = subElems.Get_by(defaultFocusKey); if (focusTarget == null) throw Err_.new_wo_type("could not find defaultTarget for focus", "ownerKey", this.Key_of_GfuiElem(), "defaultTarget", defaultFocusKey); + focusTarget.Focus(); + } + else { // else, activate first visible elem; NOTE: some elems are visible, but not Focus_able (ex: ImgGalleryBox) + for (int i = 0; i < subElems.Count(); i++) { + GfuiElem sub = subElems.Get_at(i); + if (sub.Visible() && !String_.Eq(sub.Key_of_GfuiElem(), "statusBox")) { + sub.Focus(); + return; + } + } + underElem.Core().Focus(); // no visible subElems found; focus self + } + } + + //% Inputs + public IptBndMgr IptBnds() {return iptBnds;} IptBndMgr iptBnds = IptBndMgr.new_(); + + //% ActionKey + @gplx.Virtual public void Click() {} + @gplx.Virtual public boolean Click_able() {return false;} + public IptKey Click_key() {return clickKey;} + @gplx.Internal @gplx.Virtual protected void Click_key_set_(String v) {clickKey = GfuiWinKeyCmdMgr.ExtractKeyFromText(v);} IptKey clickKey = IptKey_.None; + + //% Owner + public String Key_of_GfuiElem() {return keyIdf;} public GfuiElem Key_of_GfuiElem_(String val) {keyIdf = val; return this;} private String keyIdf; + public GfuiElemList SubElems() {return subElems;} GfuiElemList subElems; + public GfuiElem OwnerElem() {return ownerElem;} public GfuiElem OwnerElem_(GfuiElem owner) {ownerElem = owner; return this;} GfuiElem ownerElem = null; + public GfuiElem Owner_(GfuiElem owner, String key) {this.Key_of_GfuiElem_(key); return Owner_(owner);} + public GfuiElem Owner_(GfuiElem owner) { + owner.SubElems().Add(this); + return this; + } + + //% Form + @gplx.Virtual public GfuiWin OwnerWin() {return ownerForm;} public GfuiElem OwnerWin_(GfuiWin val) {ownerForm = val; return this;} GfuiWin ownerForm = null; + @gplx.Virtual public boolean Opened_done() {return ownerForm == null ? false : ownerForm.Opened_done();} + @gplx.Virtual public void Opened_cbk() { + for (int i = 0; i < subElems.Count(); i++) { + GfuiElem elem = subElems.Get_at(i); + elem.Opened_cbk(); + } + } + public void Dispose() { + Gfo_evt_mgr_.Rls_sub(this); + underMgr.Dispose(); + } + + //% Cbks + @gplx.Virtual public boolean KeyDownCbk(IptEvtDataKey data) {IptEventMgr.ExecKeyDown(this, data); return true;} + @gplx.Virtual public boolean KeyUpCbk(IptEvtDataKey data) {IptEventMgr.ExecKeyUp(this, data); return true;} + @gplx.Virtual public boolean KeyHeldCbk(IptEvtDataKeyHeld data) {IptEventMgr.ExecKeyPress(this, data); return true;} + @gplx.Virtual public boolean MouseDownCbk(IptEvtDataMouse data) {IptEventMgr.ExecMouseDown(this, data); return true;} + @gplx.Virtual public boolean MouseUpCbk(IptEvtDataMouse data) {IptEventMgr.ExecMouseUp(this, data); return true;} + @gplx.Virtual public boolean MouseMoveCbk(IptEvtDataMouse data) {IptEventMgr.ExecMouseMove(this, data); return true;} + @gplx.Virtual public boolean MouseWheelCbk(IptEvtDataMouse data) {IptEventMgr.ExecMouseWheel(this, data); return true;} + @gplx.Virtual public boolean PaintCbk(PaintArgs args) {border.DrawData(args.Graphics()); return true;} + @gplx.Virtual public boolean PaintBackgroundCbk(PaintArgs args) {return true;} + @gplx.Virtual public boolean DisposeCbk() {return true;} + @gplx.Virtual public boolean VisibleChangedCbk() {return true;} + @gplx.Virtual public boolean FocusGotCbk() { + GfuiFocusMgr.Instance.FocusedElem_set(this); + return true; + } + @gplx.Virtual public boolean FocusLostCbk() {return true;} + @gplx.Virtual public boolean SizeChangedCbk() { + this.TextMgr().OwnerSize_sync(this.Size()); + this.Border().Bounds_sync(RectAdp_.size_(this.Size().Op_subtract(1))); + if (SizeChanged_ignore + || !this.Opened_done() + ) return true; + if (lyt != null) { + GftGrid.LytExecRecur(this); + return true; + } + return true; + } + + //% InjectAble + public GfuiElem Inject_(InjectAble sub) {sub.Inject(this); return this;} + + @gplx.Virtual public GxwElem UnderElem() {return underElem;} GxwElem underElem; + @gplx.Virtual public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, GfuiElemKeys.Redraw_cmd)) Redraw(); + else if (ctx.Match(k, GfuiElemKeys.Key_set)) { + String v = m.ReadStr("v"); + return ctx.Deny() ? (Object)this : Key_of_GfuiElem_(v); + } + else if (ctx.Match(k, GfuiElemKeys.Text_set)) { + String v = m.ReadStr("v"); + return ctx.Deny() ? (Object)this : Text_(v); + } + else if (ctx.Match(k, GfuiElemKeys.TipText_)) { + String v = m.ReadStr("v"); + return ctx.Deny() ? (Object)this : TipText_(v); + } + else if (ctx.Match(k, GfuiElemKeys.width_)) { + int v = m.ReadInt("v"); + return ctx.Deny() ? (Object)this : Width_(v); + } + else if (ctx.Match(k, GfuiElemKeys.height_)) { + int v = m.ReadInt("v"); + return ctx.Deny() ? (Object)this : Height_(v); + } + else if (ctx.Match(k, GfuiElemKeys.x_)) { + int v = m.ReadInt("v"); + return ctx.Deny() ? (Object)this : X_(v); + } + else if (ctx.Match(k, GfuiElemKeys.y_)) { + int v = m.ReadInt("v"); + return ctx.Deny() ? (Object)this : Y_(v); + } + else if (ctx.Match(k, GfuiElemKeys.TipText)) return TipText(); + else if (ctx.Match(k, GfuiElemKeys.font_style_set)) { + FontStyleAdp v = (FontStyleAdp)m.ReadObj("v", FontStyleAdp_.Parser); + return ctx.Deny() ? (Object)this : textMgr.Font().Style_(v); + } + else if (ctx.Match(k, GfuiElemKeys.fore_color_set)) { + ColorAdp v = (ColorAdp)m.ReadObj("v", ColorAdp_.Parser); + if (ctx.Deny()) return this; + textMgr.Color_(v); + } + else if (ctx.Match(k, GfuiElemKeys.back_color_set)) { + ColorAdp v = (ColorAdp)m.ReadObj("v", ColorAdp_.Parser); + if (ctx.Deny()) return this; + BackColor_(v); + } + else if (ctx.Match(k, GfuiElemKeys.font_get)) return textMgr.Font(); + else if (ctx.Match(k, GfuiElemKeys.IptRcvd_evt)) return IptEventType.HandleEvt(this, ctx, m); + + else if (ctx.Match(k, GfuiElemKeys.OwnerBox_prp)) return ownerElem; + else if (ctx.Match(k, GfuiElemKeys.Focus_cmd)) Focus(); + else if (ctx.Match(k, GfuiElemKeys.ActionExec_cmd)) Click(); + else if (ctx.Match(k, GfuiElemKeys.Zorder_front_cmd)) Zorder_front(); + else if (ctx.Match(k, Invk_OwnerWin_cmd)) return OwnerWin(); + else { + if (ctx.Help_browseMode()) { + String_bldr sb = String_bldr_.new_(); + for (int i = 0; i < this.SubElems().Count(); i++) { + GfuiElem subE = (GfuiElem)this.SubElems().Get_at(i); + sb.Add_str_w_crlf(subE.Key_of_GfuiElem()); + } + return sb.To_str(); + } + else { + Object rv = this.InvkMgr().Invk(ctx, ikey, k, m, this); + if (rv != Gfo_invk_.Rv_unhandled) return rv; + + Object findObj = injected.Get_by(k); + if (findObj == null) findObj = this.subElems.Get_by(k); + if (findObj == null) return Gfo_invk_.Rv_unhandled; + return findObj; // necessary for gplx.images + } + } + return this; + } public static final String Invk_OwnerWin_cmd = "ownerWin"; + + public void Invoke(Gfo_invk_cmd cmd) { + cmd.Exec(); + } + public Gfui_kit Kit() {return kit;} private Gfui_kit kit = Gfui_kit_.Mem(); + + @gplx.Virtual public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + this.kit = Swing_kit.Instance; // NOTE: assume that callers want Swing; SWT / Mem should be calling ctor_kit_GfuiElemBase + underElem = UnderElem_make(ctorArgs); + underElem.Host_set(this); + underMgr = underElem.Core(); + subElems = GfuiElemList.new_(this); + textMgr = GfxStringData.new_(this, underElem); + this.Focus_able_(Bool_.Cast(ctorArgs.Get_val_or(GfuiElem_.InitKey_focusAble, true))); + underMgr.Size_set(SizeAdp_.new_(20, 20)); // NOTE: CS inits to 20,20; JAVA inits to 0,0 + } + @gplx.Virtual public void ctor_kit_GfuiElemBase(Gfui_kit kit, String key, GxwElem underElem, Keyval_hash ctorArgs) { + this.kit = kit; + this.keyIdf = key; + this.underElem = underElem; + underElem.Host_set(this); + underMgr = underElem.Core(); + subElems = GfuiElemList.new_(this); + textMgr = GfxStringData.new_(this, underElem); + this.Focus_able_(Bool_.Cast(ctorArgs.Get_val_or(GfuiElem_.InitKey_focusAble, true))); +// underMgr.Size_set(SizeAdp_.new_(20, 20)); // NOTE: CS inits to 20,20; JAVA inits to 0,0 + } + @gplx.Virtual public GxwElem UnderElem_make(Keyval_hash ctorArgs) {return GxwElemFactory_.Instance.control_();} + public Object SubItms_getObj(String key) {return injected.Get_by(key);} + public GfuiElemBase SubItms_add(String key, Object v) {injected.Add(key, v); return this;} + public Ordered_hash XtnAtrs() {return xtnAtrs;} Ordered_hash xtnAtrs = Ordered_hash_.New(); + Hash_adp injected = Hash_adp_.New(); + GxwCore_base underMgr; + @gplx.Internal protected static boolean SizeChanged_ignore = false; +} diff --git a/150_gfui/src/gplx/gfui/controls/elems/GfuiElemBase_.java b/150_gfui/src/gplx/gfui/controls/elems/GfuiElemBase_.java index a27517de8..d8808f740 100644 --- a/150_gfui/src/gplx/gfui/controls/elems/GfuiElemBase_.java +++ b/150_gfui/src/gplx/gfui/controls/elems/GfuiElemBase_.java @@ -13,3 +13,8 @@ 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.gfui.controls.elems; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public class GfuiElemBase_ { + public static GfuiElemBase as_(Object obj) {return obj instanceof GfuiElemBase ? (GfuiElemBase)obj : null;} + public static GfuiElemBase cast(Object obj) {try {return (GfuiElemBase)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, GfuiElemBase.class, obj);}} +} diff --git a/150_gfui/src/gplx/gfui/controls/elems/GfuiElemKeys.java b/150_gfui/src/gplx/gfui/controls/elems/GfuiElemKeys.java index a27517de8..2f4011abd 100644 --- a/150_gfui/src/gplx/gfui/controls/elems/GfuiElemKeys.java +++ b/150_gfui/src/gplx/gfui/controls/elems/GfuiElemKeys.java @@ -13,3 +13,32 @@ 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.gfui.controls.elems; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public class GfuiElemKeys { + public static final String + ActionExec_cmd = "actionExec" + , Focus_cmd = "focus" + , Redraw_cmd = "redraw" + , Zorder_front_cmd = "zorder_front" + , Text_set = "text_set" + , IptRcvd_evt = "IptRcvd_evt" + , Evt_menu_detected = "menu_detected" + ; + @gplx.Internal protected static final String + Key_set = "Key_" + , TipText = "TipText" + , TipText_ = "TipText_" + , width_ = "width_" + , height_ = "height_" + , x_get = "x" + , x_ = "x_" + , y_ = "y_" + , font_style_set = "font_style_" + , fore_color_set = "fore_color_" + , back_color_set = "back_color_" + + , font_get = "font" + , OwnerBox_prp = "ownerBox" + , Border_prp = "border" + ; +} diff --git a/150_gfui/src/gplx/gfui/controls/elems/GfuiElemList.java b/150_gfui/src/gplx/gfui/controls/elems/GfuiElemList.java index a27517de8..e5709068e 100644 --- a/150_gfui/src/gplx/gfui/controls/elems/GfuiElemList.java +++ b/150_gfui/src/gplx/gfui/controls/elems/GfuiElemList.java @@ -13,3 +13,39 @@ 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.gfui.controls.elems; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public class GfuiElemList { + public int Count() {return hash.Count();} + public GfuiElem Get_at(int idx) {return (GfuiElem)hash.Get_at(idx);} + public GfuiElem Get_by(String key) {return (GfuiElem)hash.Get_by(key);} + public void Add(GfuiElem box) {Add_exec(box);} + public void DelOrFail(GfuiElem box) {Del_exec(box);} + public void Del_at(int idx) {Del_exec(Get_at(idx));} + public int IndexOfA(GfuiElem box) {return hash.Idx_of(box);} + public void Move_to(int src, int trg) {hash.Move_to(src, trg);} + public void Clear() { + for (int i = 0; i < this.Count(); i++) + Del_exec(this.Get_at(i)); + hash.Clear(); + } + void Add_exec(GfuiElem box) { + String key = box.Key_of_GfuiElem(); if (String_.Eq(key, String_.Empty)) throw Err_.new_wo_type("box does not have key", "type", box.getClass(), "owner", owner.Key_of_GfuiElem(), "ownerSubs", owner.SubElems().Count()); + hash.Add(key, box); + owner.UnderElem().Core().Controls_add(box.UnderElem()); + box.OwnerElem_(owner).OwnerWin_(owner.OwnerWin()); // needed b/c box may be added after form is loaded + Gfo_evt_mgr_.Sub_same(box, GfuiElemKeys.IptRcvd_evt, owner); // bubble iptEvts to owner + } + void Del_exec(GfuiElem box) { + String key = box.Key_of_GfuiElem(); if (!hash.Has(key)) throw Err_.new_missing_key(key); + hash.Del(key); + owner.UnderElem().Core().Controls_del(box.UnderElem()); + owner.IptBnds().Cfgs_delAll(); + box.Dispose(); + } + GfuiElem owner; Ordered_hash hash = Ordered_hash_.New(); + public static GfuiElemList new_(GfuiElem owner) { + GfuiElemList rv = new GfuiElemList(); + rv.owner = owner; + return rv; + } GfuiElemList() {} +} diff --git a/150_gfui/src/gplx/gfui/controls/elems/GfuiElem_.java b/150_gfui/src/gplx/gfui/controls/elems/GfuiElem_.java index a27517de8..66431eed8 100644 --- a/150_gfui/src/gplx/gfui/controls/elems/GfuiElem_.java +++ b/150_gfui/src/gplx/gfui/controls/elems/GfuiElem_.java @@ -13,3 +13,30 @@ 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.gfui.controls.elems; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public class GfuiElem_ { + public static final String + InitKey_focusAble = "focusAble" + , InitKey_ownerWin = "ownerForm"; + public static GfuiElem as_(Object obj) {return obj instanceof GfuiElem ? (GfuiElem)obj : null;} + public static GfuiElem cast(Object obj) {try {return (GfuiElem)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, GfuiElem.class, obj);}} + public static GfuiElemBase sub_(String key, GfuiElem owner) { + GfuiElemBase rv = new_(); + rv.Owner_(owner, key); + return rv; + } + public static GfuiElemBase new_() { + GfuiElemBase rv = new GfuiElemBase(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_true_()); + return rv; + } + public static Keyval_hash init_focusAble_true_() {return new Keyval_hash().Add(GfuiElem_.InitKey_focusAble, true);} + public static Keyval_hash init_focusAble_false_() {return new Keyval_hash().Add(GfuiElem_.InitKey_focusAble, false);} + public static void Y_adj(int adj, GfuiElem... ary) { + int len = ary.length; + for (int i = 0; i < len; i++) { + GfuiElem itm = ary[i]; + itm.Y_(itm.Y() + adj); + } + } +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwBoxListener.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwBoxListener.java index a27517de8..f6dded627 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwBoxListener.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwBoxListener.java @@ -13,3 +13,17 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +public class GxwBoxListener implements ComponentListener, FocusListener { + @Override public void componentShown(ComponentEvent e) {host.Host().VisibleChangedCbk();} + @Override public void componentHidden(ComponentEvent e) {host.Host().VisibleChangedCbk();} + @Override public void componentMoved(ComponentEvent e) {} + @Override public void componentResized(ComponentEvent e) {host.Host().SizeChangedCbk();} + @Override public void focusGained(FocusEvent e) {host.Host().FocusGotCbk();} + @Override public void focusLost(FocusEvent e) {host.Host().FocusLostCbk();} + public GxwBoxListener(GxwElem host) {this.host = host;} GxwElem host; +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwCbkHost.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwCbkHost.java index a27517de8..6adba3f50 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwCbkHost.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwCbkHost.java @@ -13,3 +13,22 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.ipts.*; import gplx.gfui.gfxs.*; +public interface GxwCbkHost { + boolean KeyDownCbk(IptEvtDataKey data); + boolean KeyHeldCbk(IptEvtDataKeyHeld data); + boolean KeyUpCbk(IptEvtDataKey data); + boolean MouseDownCbk(IptEvtDataMouse data); + boolean MouseUpCbk(IptEvtDataMouse data); + boolean MouseMoveCbk(IptEvtDataMouse data); + boolean MouseWheelCbk(IptEvtDataMouse data); + boolean PaintCbk(PaintArgs paint); + boolean PaintBackgroundCbk(PaintArgs paint); + boolean DisposeCbk(); + boolean SizeChangedCbk(); + boolean FocusGotCbk(); + boolean FocusLostCbk(); + boolean VisibleChangedCbk(); + //boolean WndProcCbk(WindowMessage windowMessage); + } diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwCbkHost_.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwCbkHost_.java index a27517de8..ab8cbfe6f 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwCbkHost_.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwCbkHost_.java @@ -13,3 +13,73 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import java.awt.KeyboardFocusManager; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; +import gplx.gfui.ipts.*; import gplx.gfui.gfxs.*; +public class GxwCbkHost_ { + public static final GxwCbkHost Null = new GfuiHost_cls_null(); + public static final boolean ExecKeyEvent(GxwCbkHost host, KeyEvent e) { + boolean rv = true; int id = e.getID(), val = e.getKeyCode(); + boolean isAltF4 = false; + if (e.isAltDown() && val == IptKey_.F4.Val() ) { + isAltF4 = true; + } + if (id == KeyEvent.KEY_TYPED) { + IptEvtDataKeyHeld keyHeldData = IptEvtDataKeyHeld.char_(e.getKeyChar()); + rv = host.KeyHeldCbk(keyHeldData); + if (keyHeldData.Handled()) rv = false; + } + else { + if (e.isShiftDown()) val |= IptKey_.Shift.Val(); + if (e.isControlDown()) val |= IptKey_.Ctrl.Val(); + if (e.isAltDown()) val |= IptKey_.Alt.Val(); + IptEvtDataKey keyData = IptEvtDataKey.int_(val); +// Tfds.Write(e.getKeyChar(), e.getKeyCode(), val, id); + if (id == KeyEvent.KEY_PRESSED) rv = host.KeyDownCbk(keyData); + else if (id == KeyEvent.KEY_RELEASED) rv = host.KeyUpCbk(keyData); + if (keyData.Handled()) rv = false; // was false + } + if (isAltF4) { + e.consume(); + } + if (e.getKeyCode() == KeyEvent.VK_ALT) e.consume(); // force consume of alt-keys; occurs when alt-f4ing out of video app (though not audio app) + return rv; + } + public static final boolean ExecMouseEvent(GxwCbkHost host, MouseEvent e) { + int button = e.getButton(), val = 0; + if (button == MouseEvent.BUTTON1) + val = IptMouseBtn_.Left.Val(); + if (button == MouseEvent.BUTTON2) val |= IptMouseBtn_.Middle.Val(); + if (button == MouseEvent.BUTTON3) val |= IptMouseBtn_.Right.Val(); + IptEvtDataMouse data = IptEvtDataMouse.new_(IptMouseBtn_.api_(val), IptMouseWheel_.None, e.getX(), e.getY()); + boolean rv = true; + int id = e.getID(); + if (id == MouseEvent.MOUSE_PRESSED) rv = host.MouseDownCbk(data); + else if (id == MouseEvent.MOUSE_RELEASED) rv = host.MouseUpCbk(data); + return rv; + } + public static final boolean ExecMouseWheel(GxwCbkHost host, MouseWheelEvent e) { + IptMouseWheel wheel = e.getWheelRotation() < 0 ? IptMouseWheel_.Up : IptMouseWheel_.Down; + return host.MouseWheelCbk(IptEvtDataMouse.new_(IptMouseBtn_.None, wheel, e.getX(), e.getY())); + } + } +class GfuiHost_cls_null implements GxwCbkHost { // NOTE: return true by default so that super.proc() is always called; ex: SizeChangedCbk should return true so that super.OnResize will be called + public boolean KeyDownCbk(IptEvtDataKey data) {return true;} + public boolean KeyHeldCbk(IptEvtDataKeyHeld data) {return true;} + public boolean KeyUpCbk(IptEvtDataKey data) {return true;} + public boolean MouseDownCbk(IptEvtDataMouse data) {return true;} + public boolean MouseUpCbk(IptEvtDataMouse data) {return true;} + public boolean MouseMoveCbk(IptEvtDataMouse data) {return true;} + public boolean MouseWheelCbk(IptEvtDataMouse data) {return true;} + public boolean PaintCbk(PaintArgs paint) {return true;} + public boolean PaintBackgroundCbk(PaintArgs paint) {return true;} + public boolean DisposeCbk() {return true;} + public boolean SizeChangedCbk() {return true;} + public boolean FocusGotCbk() {return true;} + public boolean FocusLostCbk() {return true;} + public boolean VisibleChangedCbk() {return true;} + // public boolean WndProcCbk(WindowMessage windowMessage) {return true;} + } diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwCheckListBox.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwCheckListBox.java index a27517de8..ec43c3444 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwCheckListBox.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwCheckListBox.java @@ -13,3 +13,17 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public interface GxwCheckListBox extends GxwElem { + int Items_count(); + List_adp Items_getAll(); + List_adp Items_getChecked(); + + void Items_add(Object obj, boolean v); + void Items_reverse(); + boolean Items_getCheckedAt(int i); + void Items_setCheckedAt(int i, boolean v); + void Items_setAll(boolean v); + + void Items_clear(); +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwCheckListBox_lang.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwCheckListBox_lang.java index a27517de8..15049b981 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwCheckListBox_lang.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwCheckListBox_lang.java @@ -13,3 +13,187 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import java.awt.AWTEvent; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.event.FocusEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseWheelEvent; +import java.util.Vector; +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import gplx.gfui.ipts.*; import gplx.gfui.gfxs.*; import gplx.gfui.controls.windows.*; +public class GxwCheckListBox_lang extends JScrollPane implements GxwCheckListBox, GxwElem { + Vector internalItems; + GxwListBox_lang listBox; + public GxwCheckListBox_lang() { + initComponents(); + } + private void initComponents() { + listBox = GxwListBox_lang.new_(); + this.setViewportView(listBox); + + internalItems = new Vector(); + this.listBox.setListData(internalItems); + + CheckListCellRenderer renderer = new CheckListCellRenderer(); + listBox.setCellRenderer(renderer); + listBox.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + CheckListListener lst = new CheckListListener(this); listBox.addMouseListener(lst); listBox.addKeyListener(lst); + ctrlMgr = new GxwCore_host(GxwCore_lang.new_(this), listBox.ctrlMgr); + +// ctrlMgr = GxwCore_lang.new_(this); + this.enableEvents(AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK); + GxwBoxListener_checkListBox lnr = new GxwBoxListener_checkListBox(this, this); +// this.setFocusTraversalKeysEnabled(false); + this.addComponentListener(lnr); + this.addFocusListener(lnr); + } + public GxwCbkHost Host() {return host;} + public void Host_set(GxwCbkHost host) { + this.host = host; + listBox.Host_set(host); + } private GxwCbkHost host = GxwCbkHost_.Null; +// @Override public boolean requestFocusInWindow() { +// return listBox.requestFocusInWindow(); +//// return super.requestFocusInWindow(); +// } + public List_adp Items_getAll() {return Items_get(Mode_All);} + public int Items_count() {return internalItems.size();} + public boolean Items_getCheckedAt(int i) {return internalItems.get(i).selected;} + public void Items_setCheckedAt(int i, boolean v) {internalItems.get(i).selected = v;} + public List_adp Items_getChecked() {return Items_get(Mode_Selected);} + static final int Mode_All = 1, Mode_Selected = 2; + List_adp Items_get(int mode) { + List_adp list = List_adp_.New(); + for (CheckListItem data: internalItems) { + boolean add = (mode == Mode_All) || (mode == Mode_Selected) && data.Selected(); + if (add) + list.Add(data.Data()); + } + return list; + } + public void Items_add(Object data, boolean selected) { + internalItems.add(new CheckListItem(data, selected)); + this.listBox.setListData(internalItems); + this.listBox.updateUI(); + } + public void Items_setAll(boolean v) { + for (CheckListItem data: internalItems) { + data.Selected_set(v); + } + this.listBox.updateUI(); + } + public void Items_reverse() { + for (CheckListItem data: internalItems) { + data.Selected_set(!data.Selected()); + } + this.listBox.updateUI(); + } + public void Items_clear() { + internalItems.clear(); + this.listBox.updateUI(); + } + @Override public void setBounds(Rectangle r) {super.setBounds(r); this.validate();} + @Override public void setSize(int w, int h) {super.setSize(w, h); this.validate();} + @Override public void setSize(Dimension d) {super.setSize(d); this.validate();} + + public GxwCore_base Core() {return ctrlMgr;} GxwCore_base ctrlMgr; + public String TextVal() {return "GxwCheckListBox_lang";} public void TextVal_set(String v) {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + return this; + } + public void SendKeyDown(IptKey key) {} + public void SendMouseMove(int x, int y) {} + public void SendMouseDown(IptMouseBtn btn) {} + public void SendMouseWheel(IptMouseWheel direction) {} + @Override public void processKeyEvent(KeyEvent e) { + if (GxwCbkHost_.ExecKeyEvent(host, e)) + super.processKeyEvent(e); + } + @Override public void processMouseEvent(MouseEvent e) {if (GxwCbkHost_.ExecMouseEvent(host, e)) super.processMouseEvent(e);} + @Override public void processMouseWheelEvent(MouseWheelEvent e) {if (GxwCbkHost_.ExecMouseWheel(host, e)) super.processMouseWheelEvent(e);} + @Override public void processMouseMotionEvent(MouseEvent e) {if (host.MouseMoveCbk(IptEvtDataMouse.new_(IptMouseBtn_.None, IptMouseWheel_.None, e.getX(), e.getY()))) super.processMouseMotionEvent(e);} + @Override public void paint(Graphics g) { + if (host.PaintCbk(PaintArgs.new_(GfxAdpBase.new_((Graphics2D)g), RectAdp_.Zero))) // ClipRect not used by any clients; implement when necessary + super.paint(g); // Reevaluate if necessary: super.paint might need to (a) always happen and (b) go before PaintCbk (had issues with drawing text on images) + } + public void EnableDoubleBuffering() { // eliminates flickering during OnPaint + } + public void CreateControlIfNeeded() { + } +} +class GxwBoxListener_checkListBox extends GxwBoxListener { + @Override public void focusGained(FocusEvent e) { + host.Host().FocusGotCbk(); + clb.listBox.requestFocusInWindow(); + if (clb.listBox.getSelectedIndex() < 0) + clb.listBox.setSelectedIndex(0); + } + @Override public void focusLost(FocusEvent e) {host.Host().FocusLostCbk();} + public GxwBoxListener_checkListBox(GxwElem host, GxwCheckListBox_lang clb) {super(host); this.clb = clb;} + GxwCheckListBox_lang clb; +} +class CheckListListener implements MouseListener, KeyListener { + GxwCheckListBox_lang m_parent; JList m_list; + public CheckListListener(GxwCheckListBox_lang parent) { + m_parent = parent; + m_list = parent.listBox; + } + public void mouseClicked(MouseEvent e) { + if (e.getX() < 20) doCheck(); + } + public void mousePressed(MouseEvent e) {} + public void mouseReleased(MouseEvent e) {} + public void mouseEntered(MouseEvent e) {} + public void mouseExited(MouseEvent e) {} + public void keyPressed(KeyEvent e) { + if (e.getKeyChar() == ' ') doCheck(); + } + public void keyTyped(KeyEvent e) { + } + public void keyReleased(KeyEvent e) { + } + protected void doCheck() { + int index = m_list.getSelectedIndex(); + if (index < 0) return; + CheckListItem data = (CheckListItem)m_list.getModel().getElementAt(index); + data.Selected_toggle(); + m_list.repaint(); + } +} +class CheckListCellRenderer extends JCheckBox implements ListCellRenderer{ + public CheckListCellRenderer() { + setOpaque(true); + setBorder(GxwBorderFactory.Empty); + } + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + if (value != null) setText(value.toString()); + setBackground(isSelected ? list.getSelectionBackground() : list.getBackground()); + setForeground(isSelected ? list.getSelectionForeground() : list.getForeground()); + + CheckListItem data = (CheckListItem)value; + setSelected(data.Selected()); + + setFont(list.getFont()); + setBorder((cellHasFocus) ? UIManager.getBorder("List.focusCellHighlightBorder") : GxwBorderFactory.Empty); + return this; + } +} +class CheckListItem { + public CheckListItem(Object data, Boolean selected) {this.data = data; this.selected = selected;} + public Object Data() {return data;} Object data; + public boolean Selected() {return selected;} public void Selected_set(boolean selected) {this.selected = selected;} protected boolean selected; + public void Selected_toggle() {selected = !selected;} + public String toString() {return Object_.Xto_str_strict_or_null_mark(data);} +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwComboBox.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwComboBox.java index a27517de8..2ea4e7249 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwComboBox.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwComboBox.java @@ -13,3 +13,25 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; +public interface GxwComboBox extends GxwElem { + ColorAdp Border_color(); + void Border_color_(ColorAdp v); + int SelBgn(); void SelBgn_set(int v); + int SelLen(); void SelLen_set(int v); + void Sel_(int bgn, int end); + Object SelectedItm(); void SelectedItm_set(Object v); + String[] DataSource_as_str_ary(); + void DataSource_set(Object... ary); + String Text_fallback(); void Text_fallback_(String v); + int List_sel_idx(); void List_sel_idx_(int v); + boolean List_visible(); void List_visible_(boolean v); + void Items__update(String[] ary); + void Items__size_to_fit(int count); + void Items__visible_rows_(int v); + void Items__jump_len_(int v); + void Items__backcolor_(ColorAdp v); + void Items__forecolor_(ColorAdp v); + void Margins_set(int left, int top, int right, int bot); +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwComboBox_lang.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwComboBox_lang.java index a27517de8..d18ae7109 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwComboBox_lang.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwComboBox_lang.java @@ -13,3 +13,111 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; +import gplx.gfui.draws.*; +import gplx.gfui.ipts.*; import gplx.gfui.gfxs.*; +public class GxwComboBox_lang extends JComboBox implements GxwComboBox, GxwElem, ActionListener { + public String[] DataSource_as_str_ary() {return String_.Ary_empty;} + public void DataSource_set(Object... ary) { + for (Object o : ary) + this.insertItemAt(o, this.getItemCount()); + /* + Get current value + Object obj = cb.getSelectedItem(); + + Set a new valu + cb.setSelectedItem("item2"); + obj = cb.getSelectedItem(); + */ + } + public ColorAdp Border_color() {return border_color;} public void Border_color_(ColorAdp v) {border_color = v;} private ColorAdp border_color; + public void Items__backcolor_(ColorAdp v) {} + public void Items__forecolor_(ColorAdp v) {} + public int SelBgn() {return -1;} public void SelBgn_set(int v) {} + public int SelLen() {return 0;} public void SelLen_set(int v) {} + public void Sel_(int bgn, int end) {} + public Object SelectedItm() {return this.getEditor().getItem();} public void SelectedItm_set(Object v) { + this.getEditor().setItem(v); + } + @Override public String Text_fallback() {return "";} @Override public void Text_fallback_(String v) {} + @Override public int List_sel_idx() {return -1;} @Override public void List_sel_idx_(int v) {} + public boolean List_visible() {return false;} public void List_visible_(boolean v) {} + public void Items__update(String[] ary) {} + public void Items__size_to_fit(int count) {} + public void Items__visible_rows_(int v) {} + public void Items__jump_len_(int v) {} + public void Margins_set(int left, int top, int right, int bot) {} + void ctor_() { + ctrlMgr = GxwCore_lang.new_(this); + this.enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK); + GxwBoxListener lnr = new GxwBoxListener(this); +// this.setFocusTraversalKeysEnabled(false); +// this.addKeyListener(this); + this.addComponentListener(lnr); + this.addFocusListener(lnr); + this.setEditable(true); +// this.addActionListener(this); + this.setFocusable(true); + this.setBorder(BorderFactory.createLineBorder(Color.BLACK)); + Component jc = this.getEditor().getEditorComponent(); // WORKAROUND:SWING:JComboBox does not reroute events from editor; will not handle canceling key + jc.addKeyListener(new ComboBox_keylistener(this)); + } + public GxwCore_base Core() {return ctrlMgr;} GxwCore_base ctrlMgr; + public GxwCbkHost Host() {return host;} public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host = GxwCbkHost_.Null; + // need to validate, else dropDownArrow will not get redrawn in correct pos + @Override public void setBounds(Rectangle r) {super.setBounds(r); this.validate();} + @Override public void setSize(int w, int h) {super.setSize(w, h); this.validate();} + @Override public void setSize(Dimension d) {super.setSize(d); this.validate();} + public String TextVal() {return Object_.Xto_str_strict_or_empty(this.SelectedItm());} public void TextVal_set(String v) {this.SelectedItm_set(v);} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + return this; + } + public void SendKeyDown(IptKey key) {} + public void SendMouseMove(int x, int y) {} + public void SendMouseDown(IptMouseBtn btn) {} + public void SendMouseWheel(IptMouseWheel direction) {} +// @Override public void keyPressed(KeyEvent arg0) {this.processKeyEvent(arg0);} +// @Override public void keyReleased(KeyEvent arg0) {this.processKeyEvent(arg0);} +// @Override public void keyTyped(KeyEvent arg0) {this.processKeyEvent(arg0);} + @Override public void processKeyEvent(KeyEvent e) { + if (GxwCbkHost_.ExecKeyEvent(host, e)) + super.processKeyEvent(e); + } +// @Override public void actionPerformed(ActionEvent e) { +// } + @Override public void processMouseEvent(MouseEvent e) {if (GxwCbkHost_.ExecMouseEvent(host, e)) super.processMouseEvent(e);} + @Override public void processMouseWheelEvent(MouseWheelEvent e) {if (GxwCbkHost_.ExecMouseWheel(host, e)) super.processMouseWheelEvent(e);} + @Override public void processMouseMotionEvent(MouseEvent e) {if (host.MouseMoveCbk(IptEvtDataMouse.new_(IptMouseBtn_.None, IptMouseWheel_.None, e.getX(), e.getY()))) super.processMouseMotionEvent(e);} + @Override public void paint(Graphics g) { + if (host.PaintCbk(PaintArgs.new_(GfxAdpBase.new_((Graphics2D)g), RectAdp_.Zero))) // ClipRect not used by any clients; implement when necessary + super.paint(g); // Reevaluate if necessary: super.paint might need to (a) always happen and (b) go before PaintCbk (had issues with drawing text on images) + } + public void EnableDoubleBuffering() { // eliminates flickering during OnPaint + } + public void CreateControlIfNeeded() { + } + public static GxwComboBox_lang new_() { + GxwComboBox_lang rv = new GxwComboBox_lang(); + rv.ctor_(); + return rv; + } GxwComboBox_lang() {} +} +class ComboBox_keylistener implements KeyListener { + public ComboBox_keylistener(GxwComboBox_lang host) {this.host = host;} GxwComboBox_lang host; + @Override public void keyPressed(KeyEvent arg0) { +// if (arg0.getKeyChar() == 'f') +// System.out.println('h'); + host.processKeyEvent(arg0); + } + @Override public void keyReleased(KeyEvent arg0) { + //host.processKeyEvent(arg0); + } + @Override public void keyTyped(KeyEvent arg0) { + //host.processKeyEvent(arg0); + } +} + diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwCore_base.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwCore_base.java index a27517de8..bd79f84b1 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwCore_base.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwCore_base.java @@ -13,3 +13,35 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; +import gplx.gfui.layouts.swts.*; +public abstract class GxwCore_base { + public abstract void Controls_add(GxwElem sub); + public abstract void Controls_del(GxwElem sub); + public abstract int Width(); public abstract void Width_set(int v); + public abstract int Height(); public abstract void Height_set(int v); + public abstract int X(); public abstract void X_set(int v); + public abstract int Y(); public abstract void Y_set(int v); + public abstract SizeAdp Size(); public abstract void Size_set(SizeAdp v); + public abstract PointAdp Pos(); public abstract void Pos_set(PointAdp v); + public abstract RectAdp Rect(); public abstract void Rect_set(RectAdp v); + public abstract boolean Visible(); public abstract void Visible_set(boolean v); + public abstract ColorAdp BackColor(); public abstract void BackColor_set(ColorAdp v); + public abstract ColorAdp ForeColor(); public abstract void ForeColor_set(ColorAdp v); + public abstract FontAdp TextFont(); public abstract void TextFont_set(FontAdp v); + public abstract String TipText(); public abstract void TipText_set(String v); + public abstract Swt_layout_mgr Layout_mgr(); + public abstract void Layout_mgr_(Swt_layout_mgr v); + public abstract Swt_layout_data Layout_data(); + public abstract void Layout_data_(Swt_layout_data v); + + public abstract int Focus_index(); public abstract void Focus_index_set(int v); + public abstract boolean Focus_able(); public abstract void Focus_able_(boolean v); + public abstract boolean Focus_has(); + public abstract void Focus(); + public abstract void Select_exec(); + public abstract void Zorder_front(); public abstract void Zorder_back(); + public abstract void Invalidate(); + public abstract void Dispose(); +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwCore_lang.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwCore_lang.java index a27517de8..93be94fb6 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwCore_lang.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwCore_lang.java @@ -13,3 +13,152 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import javax.swing.JComponent; +import javax.swing.JWindow; +import javax.swing.RootPaneContainer; +import javax.swing.SwingUtilities; +import javax.swing.ToolTipManager; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Font; +import java.awt.Graphics2D; +import gplx.gfui.draws.*; import gplx.gfui.controls.gxws.*; +import gplx.gfui.layouts.swts.*; +public class GxwCore_lang extends GxwCore_base { + @Override public int Width() {return control.getWidth();} @Override public void Width_set(int v) {control.setSize(v, control.getHeight());} + @Override public int Height() {return control.getHeight();} @Override public void Height_set(int v) {control.setSize(control.getWidth(), v);} + @Override public int X() {return control.getX();} @Override public void X_set(int v) {control.setLocation(v, control.getY());} + @Override public int Y() {return control.getY();} @Override public void Y_set(int v) {control.setLocation(control.getX(), v);} + @Override public SizeAdp Size() {return GxwCore_lang.XtoSizeAdp(control.getSize());} @Override public void Size_set(SizeAdp v) {control.setSize(GxwCore_lang.Xto_size(v));} + @Override public PointAdp Pos() {return GxwCore_lang.XtoPointAdp(control.getLocation());} @Override public void Pos_set(PointAdp v) {control.setLocation(GxwCore_lang.XtoPoint(v));} + @Override public RectAdp Rect() {return GxwCore_lang.XtoRectAdp(control.getBounds());} @Override public void Rect_set(RectAdp v) { + control.setBounds(GxwCore_lang.XtoRect(v)); +// control.setLocation(v.Pos().XtoPoint()); +// control.setSize(v.Size().XtoDimension()); + } + @Override public boolean Visible() { + Container owner = control.getParent(); + // WORKAROUND:JAVA: .NET automatically propagates .Visible down ElementTree; .Visible needs to propagate else sub.Visible can be true though owner.Visible will be false + while (owner != null) { + if (!owner.isVisible()) + return false; // handles case wherein sub is visible but owner is not -> sub should be marked not visible + owner = owner.getParent(); + } + return control.isVisible(); + } + @Override public void Visible_set(boolean v) { + control.setVisible(v); + } + @Override public ColorAdp BackColor() {return XtoColorAdp(control.getBackground());} + @Override public void BackColor_set(ColorAdp v) { + if (control instanceof JComponent) { + ((JComponent)control).setBackground(ColorAdpCache.Instance.GetNativeColor(v)); + } + else if (control instanceof RootPaneContainer) { + RootPaneContainer container = (RootPaneContainer)control; + container.getContentPane().setBackground(ColorAdpCache.Instance.GetNativeColor(v)); + } + } + @Override public ColorAdp ForeColor() {return XtoColorAdp(control.getForeground());} @Override public void ForeColor_set(ColorAdp v) {control.setForeground(ColorAdpCache.Instance.GetNativeColor(v));} + @Override public FontAdp TextFont() { + if (prvFont != null) return prvFont; + Font f = control.getFont(); + FontAdp curFont = FontAdp.new_(f.getFontName(), FontAdpCache.XtoOsDpi(f.getSize2D()), FontStyleAdp_.lang_(f.getStyle())); + curFont.OwnerGxwCore_(this); + prvFont = curFont; + return prvFont; + } FontAdp prvFont; + @Override public void TextFont_set(FontAdp v) { + control.setFont(v.UnderFont()); + v.OwnerGxwCore_(this); + prvFont = v; + } + @Override public String TipText() {return tipText;} @Override public void TipText_set(String v) {tipText = v;} String tipText; + @Override public Swt_layout_mgr Layout_mgr() {return null;} + @Override public void Layout_mgr_(Swt_layout_mgr v) {} + @Override public Swt_layout_data Layout_data() {return null;} + @Override public void Layout_data_(Swt_layout_data v) {} + + @Override public void Controls_add(GxwElem sub) { + try { + JComponent component = (JComponent)sub; + Container container = (Container)control; + container.add(component); + }catch (Exception e) {} + } + @Override public void Controls_del(GxwElem sub) { + JComponent component = (JComponent)sub; + // clear out painted area, or residue will remain (see opal when navigating to empty grid) + Graphics2D g2 = (Graphics2D) component.getGraphics(); + g2.setColor(component.getBackground()); + g2.fillRect(0, 0, component.getWidth(), component.getHeight()); + g2.dispose(); + Container container = (Container)control; + container.remove(component); + } + @Override public boolean Focus_has() {return control.hasFocus();} +// @Override public boolean Focus_able() {return control.isFocusable();} +// @Override public void Focus_able_(boolean v) {control.setFocusable(v);} + @Override public boolean Focus_able() {return focus_able;} boolean focus_able; + @Override public void Focus_able_(boolean v) {focus_able = v;} +// @Override public void Focus_able_force_(boolean v){control.setFocusable(v);} + @Override public int Focus_index() {return focusIndex;} @Override public void Focus_index_set(int v) {focusIndex = v;} int focusIndex; + @Override public void Focus() { + if (Focus_able()) { // NOTE: .Focus() only works if TabStop = true; otherwise, must call .Select() +// FocusableThread t = new FocusableThread(control); +// SwingUtilities.invokeLater(t); +// control.requestFocus(); + if (!control.requestFocusInWindow()) { // will fail when switching to imgApp with gallery open from opalMain + // control.requestFocus(); + } + } + } + class FocusableThread implements Runnable { + public void run() { +// comp.requestFocusInWindow(); + comp.requestFocus(); + comp.repaint(); + } + public FocusableThread(Component comp) {this.comp = comp;} Component comp; + } + @Override public void Select_exec() { + control.requestFocus(); + } + @Override public void Zorder_front() { + Container owner = control.getParent(); + if (owner == null)return; + owner.setComponentZOrder(control, 0); // NOTE: last component gets drawn last, so will be first visually + } + @Override public void Zorder_back() { + Container owner = control.getParent(); + owner.setComponentZOrder(control, owner.getComponentCount() - 1); + } + @Override public void Invalidate() {control.repaint(); + + } + @Override public void Dispose() {} // JAVA: no Dispose for component? only for form + protected Component control; + public static GxwCore_lang new_(Component control) { + GxwCore_lang rv = new GxwCore_lang(); + rv.control = control; + return rv; + } GxwCore_lang() {} + public static ColorAdp XtoColorAdp(java.awt.Color v) {return ColorAdp.new_((int)v.getAlpha(), (int)v.getRed(), (int)v.getGreen(), (int)v.getBlue());} + public static java.awt.Dimension Xto_size(SizeAdp v) {return new java.awt.Dimension(v.Width(), v.Height());} + public static SizeAdp XtoSizeAdp(java.awt.Dimension val) { + return new SizeAdp(val.width, val.height); + } + public static java.awt.Point XtoPoint(PointAdp v) {return new java.awt.Point(v.X(), v.Y());} + public static PointAdp XtoPointAdp(java.awt.Point v) { + return new PointAdp(v.x, v.y); + } + public static RectAdp XtoRectAdp(java.awt.Rectangle rect) { + return new RectAdp(GxwCore_lang.XtoPointAdp(rect.getLocation()) + , GxwCore_lang.XtoSizeAdp(rect.getSize())); + } + public static java.awt.Rectangle XtoRect(RectAdp rect) {return new java.awt.Rectangle(rect.X(), rect.Y(), rect.Width(), rect.Height());} + public static java.awt.Rectangle XtoRect(int x, int y, int w, int h) {return new java.awt.Rectangle(x, y, w, h);} +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwCore_mock.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwCore_mock.java index a27517de8..8f1f165ab 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwCore_mock.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwCore_mock.java @@ -13,3 +13,37 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; import gplx.gfui.ipts.*; +import gplx.gfui.layouts.swts.*; +public class GxwCore_mock extends GxwCore_base { + @Override public int Width() {return size.Width();} @Override public void Width_set(int v) {size = SizeAdp_.new_(v, size.Height());} + @Override public int Height() {return size.Height();} @Override public void Height_set(int v) {size = SizeAdp_.new_(size.Width(), v);} + @Override public int X() {return pos.X();} @Override public void X_set(int v) {pos = PointAdp_.new_(v, pos.Y());} + @Override public int Y() {return pos.Y();} @Override public void Y_set(int v) {pos = PointAdp_.new_(pos.X(), v);} + @Override public SizeAdp Size() {return size;} @Override public void Size_set(SizeAdp v) {size = v;} SizeAdp size = SizeAdp_.Zero; + @Override public PointAdp Pos() {return pos;} @Override public void Pos_set(PointAdp v) {pos = v;} PointAdp pos = PointAdp_.Zero; + @Override public RectAdp Rect() {return RectAdp_.vector_(pos, size);} @Override public void Rect_set(RectAdp v) {size = v.Size(); pos = v.Pos();} + @Override public boolean Visible() {return visible;} @Override public void Visible_set(boolean v) {visible = v;} private boolean visible = true; // HACK: default all controls to visible (should be set to visible only when form is loaded); will cause FocusKeyMgrTst.SubWidget test to fail + @Override public ColorAdp BackColor() {return backColor;} @Override public void BackColor_set(ColorAdp v) {backColor = v;} ColorAdp backColor = ColorAdp_.Null; + @Override public ColorAdp ForeColor() {return textColor;} @Override public void ForeColor_set(ColorAdp v) {textColor = v;} ColorAdp textColor = ColorAdp_.Null; + @Override public FontAdp TextFont() {return font;} @Override public void TextFont_set(FontAdp v) {font = v;} FontAdp font; + @Override public String TipText() {return tipText;} @Override public void TipText_set(String v) {tipText = v;} private String tipText; + @Override public Swt_layout_mgr Layout_mgr() {return null;} + @Override public void Layout_mgr_(Swt_layout_mgr v) {} + @Override public Swt_layout_data Layout_data() {return null;} + @Override public void Layout_data_(Swt_layout_data v) {} + + @Override public void Controls_add(GxwElem sub) {list.Add(sub);} + @Override public void Controls_del(GxwElem sub) {list.Del(sub);} + @Override public int Focus_index() {return focusKeyIndex;} @Override public void Focus_index_set(int v) {focusKeyIndex = v;} int focusKeyIndex; + @Override public boolean Focus_able() {return canFocus;} @Override public void Focus_able_(boolean v) {canFocus = v;} private boolean canFocus = true; + @Override public boolean Focus_has() {return false;} + @Override public void Focus() {} + @Override public void Select_exec() {} + @Override public void Zorder_front() {} @Override public void Zorder_back() {} + @Override public void Invalidate() {} @Override public void Dispose() {} + + public void SendKey(IptKey key) {} + List_adp list = List_adp_.New(); +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwElem.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwElem.java index a27517de8..90818ffa9 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwElem.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwElem.java @@ -13,3 +13,12 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public interface GxwElem extends Gfo_invk { + GxwCore_base Core(); + GxwCbkHost Host(); void Host_set(GxwCbkHost host); + String TextVal(); void TextVal_set(String v); + + + void EnableDoubleBuffering(); +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwElemFactory_.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwElemFactory_.java index a27517de8..4968946db 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwElemFactory_.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwElemFactory_.java @@ -13,3 +13,9 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public class GxwElemFactory_ { + public static GxwElemFactory_base Instance = new GxwElemFactory_cls_mock(); + public static void winForms_() {Instance = new GxwElemFactory_cls_lang();} + public static void swt_(org.eclipse.swt.widgets.Display display) {Instance = new GxwElemFactory_swt(display);} + } diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwElemFactory_base.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwElemFactory_base.java index a27517de8..9bb187b76 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwElemFactory_base.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwElemFactory_base.java @@ -13,3 +13,18 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public abstract class GxwElemFactory_base { + public abstract GxwElem control_(); + public abstract GxwWin win_app_(); + public abstract GxwWin win_tool_(Keyval_hash ctorArgs); + public abstract GxwWin win_toaster_(Keyval_hash ctorArgs); + public abstract GxwElem lbl_(); + public abstract GxwTextFld text_fld_(); + public abstract GxwTextFld text_memo_(); + public abstract GxwTextHtml text_html_(); + public abstract GxwCheckListBox checkListBox_(Keyval_hash ctorArgs); + public abstract GxwComboBox comboBox_(); + public abstract GxwListBox listBox_(); + // @gplx.Internal protected GxwElem spacer_() {return MockControl.new_();} + } diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwElemFactory_cls_lang.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwElemFactory_cls_lang.java index a27517de8..7191b1123 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwElemFactory_cls_lang.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwElemFactory_cls_lang.java @@ -13,3 +13,29 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.controls.elems.*; import gplx.gfui.controls.windows.*; +public class GxwElemFactory_cls_lang extends GxwElemFactory_base { + @Override public GxwElem control_() {return new GxwElem_lang();} + @Override public GxwWin win_tool_(Keyval_hash ctorArgs) { + GfuiWin ownerForm = (GfuiWin)ctorArgs.Get_val_or(GfuiElem_.InitKey_ownerWin, null); + GxwWin ownerElem = ownerForm == null ? null : (GxwWin)ownerForm.UnderElem(); + return GxwWin_jdialog.new_(ownerElem); +// return GxwWin_lang.new_(); + } + @Override public GxwWin win_toaster_(Keyval_hash ctorArgs) { + GfsCtx ctx = GfsCtx.new_(); ctx.Match("", ""); + GfuiWin ownerForm = (GfuiWin)ctorArgs.Get_val_or(GfuiElem_.InitKey_ownerWin, null); + GxwWin ownerElem = ownerForm == null ? null : (GxwWin)ownerForm.UnderElem(); + return GxwWin_jwindow.new_(ownerElem); +// return GxwWin_lang.new_(); + } + @Override public GxwWin win_app_() {return GxwWin_lang.new_();} + @Override public GxwElem lbl_() {return new GxwElem_lang();} + @Override public GxwTextFld text_fld_() {return GxwTextBox_lang_.fld_();} + @Override public GxwTextFld text_memo_() {return GxwTextBox_lang_.memo_();} + @Override public GxwTextHtml text_html_() {return new GxwTextHtml_lang().ctor();} + @Override public GxwCheckListBox checkListBox_(Keyval_hash ctorArgs) {return new GxwCheckListBox_lang();} + @Override public GxwComboBox comboBox_() {return GxwComboBox_lang.new_();} + @Override public GxwListBox listBox_() {return GxwListBox_lang.new_();} +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwElemFactory_cls_mock.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwElemFactory_cls_mock.java index a27517de8..b09904187 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwElemFactory_cls_mock.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwElemFactory_cls_mock.java @@ -13,3 +13,17 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public class GxwElemFactory_cls_mock extends GxwElemFactory_base { + @Override public GxwElem control_() {return GxwElem_mock_base.new_();} + @Override public GxwWin win_app_() {return MockForm.Instance;} + @Override public GxwWin win_tool_(Keyval_hash ctorArgs) {return MockForm.Instance;} + @Override public GxwWin win_toaster_(Keyval_hash ctorArgs) {return MockForm.Instance;} + @Override public GxwElem lbl_() {return GxwElem_mock_base.new_();} + @Override public GxwTextFld text_fld_() {return new MockTextBox();} + @Override public GxwTextFld text_memo_() {return new MockTextBoxMulti();} + @Override public GxwTextHtml text_html_() {return new MockTextBoxMulti();} + @Override public GxwCheckListBox checkListBox_(Keyval_hash ctorArgs) {throw Err_.new_unimplemented();} + @Override public GxwComboBox comboBox_() {return new MockComboBox();} + @Override public GxwListBox listBox_() {return new MockListBox();} +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwElem_lang.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwElem_lang.java index a27517de8..0ffe816db 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwElem_lang.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwElem_lang.java @@ -13,3 +13,54 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import java.awt.AWTEvent; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; +import javax.swing.JComponent; +import gplx.gfui.ipts.*; import gplx.gfui.gfxs.*; +public class GxwElem_lang extends JComponent implements GxwElem { + public static final String AlignH_cmd = "AlignH"; + public GxwCbkHost Host() {return host;} public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host = GxwCbkHost_.Null; + public String TextVal() {return text;} public void TextVal_set(String v) {text = v;} String text; // JAVA: JComponent does not support Text (.NET Control does) + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return this;} // noop: intended for AlignH + @Override public void processKeyEvent(KeyEvent e) {if (GxwCbkHost_.ExecKeyEvent(host, e)) super.processKeyEvent(e);} + @Override public void processMouseEvent(MouseEvent e) {if (GxwCbkHost_.ExecMouseEvent(host, e)) super.processMouseEvent(e);} + @Override public void processMouseWheelEvent(MouseWheelEvent e) {if (GxwCbkHost_.ExecMouseWheel(host, e)) super.processMouseWheelEvent(e);} + @Override public void processMouseMotionEvent(MouseEvent e) { + if (host.MouseMoveCbk(IptEvtDataMouse.new_(IptMouseBtn_.None, IptMouseWheel_.None, e.getX(), e.getY()))) super.processMouseMotionEvent(e);} + @Override public void paint(Graphics g) { + // always paint background color; must happen before any controlPaint; extract to separate method if override needed + Graphics2D g2 = (Graphics2D)g; + g2.setBackground(this.getBackground()); + RectAdp clipRect = GxwCore_lang.XtoRectAdp(g2.getClipBounds()); + g2.clearRect(clipRect.X(), clipRect.Y(), clipRect.Width(), clipRect.Height()); + if (host.PaintCbk(PaintArgs.new_(GfxAdpBase.new_(g2), clipRect))) // ClipRect not used by any clients; implement when necessary + super.paint(g); // Reevaluate if necessary: super.paint might need to (a) always happen and (b) go before PaintCbk (had issues with drawing text on images) + } + // todo: call Dispose when ownerForm is disposed +// @Override protected void Dispose(boolean disposing) {if (host.DisposeCbk()) super.Dispose(disposing);} + public void ctor_GxwElem() { + ctrlMgr = GxwCore_lang.new_(this); + GxwBoxListener lnr = new GxwBoxListener(this); + //this.setFocusTraversalKeysEnabled(false); + this.addComponentListener(lnr); + this.addFocusListener(lnr); + this.enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK); + } + public GxwCore_base Core() {return ctrlMgr;} GxwCore_base ctrlMgr; + public void SendKeyDown(IptKey key) {} + public void SendMouseMove(int x, int y) {} + public void SendMouseDown(IptMouseBtn btn) { + } + public void EnableDoubleBuffering() {} // eliminates flickering during OnPaint + JComponent comp = null; + public GxwElem_lang() {this.ctor_GxwElem();} +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwElem_mock_base.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwElem_mock_base.java index a27517de8..900d86fb6 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwElem_mock_base.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwElem_mock_base.java @@ -13,3 +13,78 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.ipts.*; +import gplx.gfui.draws.*; +public class GxwElem_mock_base implements GxwElem { + public GxwCore_base Core() {return ctrlMgr;} final GxwCore_mock ctrlMgr = new GxwCore_mock(); + public GxwCbkHost Host() {return host;} public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host = GxwCbkHost_.Null; + public String TextVal() {return text;} public void TextVal_set(String v) {text = v;} private String text = ""; + public void SendKeyDown(IptKey key) {} + public void EnableDoubleBuffering() {} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + return this; + } + + List_adp list = List_adp_.New(); + public static GxwElem_mock_base new_() {return new GxwElem_mock_base();} protected GxwElem_mock_base() {} +} +class MockTextBox extends GxwElem_mock_base implements GxwTextFld { + public boolean Border_on() {return borderOn;} public void Border_on_(boolean v) {borderOn = v;} private boolean borderOn = true; + public ColorAdp Border_color() {return border_color;} public void Border_color_(ColorAdp v) {border_color = v;} private ColorAdp border_color; + public boolean OverrideTabKey() {return false;} public void OverrideTabKey_(boolean v) {} + public int SelBgn() {return selectionStart;} public void SelBgn_set(int v) {selectionStart = v;} int selectionStart; + public int SelLen() {return selectionLength;} public void SelLen_set(int v) {selectionLength = v;} int selectionLength; + public void AlignH_(GfuiAlign val) {} + public void CreateControlIfNeeded() {} + public void Margins_set(int left, int top, int right, int bot) {} +} +class MockTextBoxMulti extends MockTextBox implements GxwTextMemo, GxwTextHtml { public Keyval[] Html_sel_atrs() {return Keyval_.Ary_empty;} + public void Html_enabled(boolean v) {} + public String Html_doc_html() {return "";} + public void Html_css_set(String s) {} + public int LinesPerScreen() {return linesPerScreen;} int linesPerScreen = 1; + public int LinesTotal() {return linesTotal;} int linesTotal = 1; + public int ScreenCount() {return screenCount;} int screenCount = 1; + public int LineLength(int lineIndex) {return -1;} + public int CharIndexOf(int lineIndex) {return -1;} + public int CharIndexOfFirst() {return -1;} + public int LineIndexOfFirst() {return -1;} + public int LineIndexOf(int charIndex) {return -1;} + public PointAdp PosOf(int charIndex) {return PointAdp_.Null;} + public void ScrollLineUp() {} + public void ScrollLineDown() {} + public void ExtendLineUp() {} + public void ExtendLineDown() {} + public void ScrollScreenUp() {} + public void ScrollScreenDown() {} + public void SelectionStart_toFirstChar() {} + public void ScrollTillSelectionStartIsFirstLine() {} + public void ScrollTillCaretIsVisible() {} +} +class MockComboBox extends GxwElem_mock_base implements GxwComboBox { + public ColorAdp Border_color() {return border_color;} public void Border_color_(ColorAdp v) {border_color = v;} private ColorAdp border_color; + public int SelBgn() {return -1;} public void SelBgn_set(int v) {} + public int SelLen() {return 0;} public void SelLen_set(int v) {} + public void Sel_(int bgn, int end) {} + public String[] DataSource_as_str_ary() {return String_.Ary_empty;} + public void DataSource_set(Object... ary) {} + public String Text_fallback() {return "";} public void Text_fallback_(String v) {} + public int List_sel_idx() {return -1;} public void List_sel_idx_(int v) {} + public boolean List_visible() {return false;} public void List_visible_(boolean v) {} + public void Items__update(String[] ary) {} + public void Items__size_to_fit(int count) {} + public void Items__visible_rows_(int v) {} + public void Items__jump_len_(int v) {} + public void Items__backcolor_(ColorAdp v) {} + public void Items__forecolor_(ColorAdp v) {} + public void Margins_set(int left, int top, int right, int bot) {} + public Object SelectedItm() {return selectedItm;} public void SelectedItm_set(Object v) {this.selectedItm = v;} Object selectedItm; +} +class MockListBox extends GxwElem_mock_base implements GxwListBox { + public void Items_Add(Object item) {} + public void Items_Clear() {} + public Object Items_SelObj() {return null;} + public int Items_Count() {return -1;} + public int Items_SelIdx() {return -1;} public void Items_SelIdx_set(int v) {} +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwListBox.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwListBox.java index a27517de8..94dc334b3 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwListBox.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwListBox.java @@ -13,3 +13,11 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public interface GxwListBox extends GxwElem { + void Items_Add(Object item); + void Items_Clear(); + int Items_Count(); + int Items_SelIdx(); void Items_SelIdx_set(int v); + Object Items_SelObj(); +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwListBox_lang.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwListBox_lang.java index a27517de8..7ded60729 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwListBox_lang.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwListBox_lang.java @@ -13,3 +13,69 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import java.awt.*; +import java.awt.event.*; +import java.util.Vector; + +import javax.swing.*; +import gplx.gfui.ipts.*; import gplx.gfui.gfxs.*; +class GxwListBox_lang extends JList implements GxwListBox { + void ctor_() { + ctrlMgr = GxwCore_lang.new_(this); + this.enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK); + GxwBoxListener lnr = new GxwBoxListener(this); +// this.setFocusTraversalKeysEnabled(false); + this.addComponentListener(lnr); + this.addFocusListener(lnr); + this.setFocusable(true); + this.setBorder(BorderFactory.createLineBorder(Color.BLACK)); + } + Vector internalItems = new Vector(); + public void Items_Add(Object item) { + internalItems.add((String)item); + this.setListData(internalItems); + this.updateUI(); + } + public void Items_Clear() {internalItems.clear();} + public int Items_Count() {return internalItems.size();} + public int Items_SelIdx() {return this.getSelectedIndex();} public void Items_SelIdx_set(int v) {this.setSelectedIndex(v);} + public Object Items_SelObj() {return this.getSelectedValue();} + + public Object SelectedItm() {return this.getSelectedValue();} public void SelectedItm_set(Object v) {this.setSelectedValue(v, true);} + public GxwCore_base Core() {return ctrlMgr;} GxwCore_base ctrlMgr; + public GxwCbkHost Host() {return host;} public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host = GxwCbkHost_.Null; + // need to validate, else dropDownArrow will not get redrawn in correct pos + @Override public void setBounds(Rectangle r) {super.setBounds(r); this.validate();} + @Override public void setSize(int w, int h) {super.setSize(w, h); this.validate();} + @Override public void setSize(Dimension d) {super.setSize(d); this.validate();} + public String TextVal() {return Object_.Xto_str_strict_or_empty(this.SelectedItm());} public void TextVal_set(String v) {this.SelectedItm_set(v);} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + return this; + } + public void SendKeyDown(IptKey key) {} + public void SendMouseMove(int x, int y) {} + public void SendMouseDown(IptMouseBtn btn) {} + public void SendMouseWheel(IptMouseWheel direction) {} + @Override public void processKeyEvent(KeyEvent e) { + if (GxwCbkHost_.ExecKeyEvent(host, e)){ + super.processKeyEvent(e); + } + } + @Override public void processMouseEvent(MouseEvent e) {if (GxwCbkHost_.ExecMouseEvent(host, e)) super.processMouseEvent(e);} + @Override public void processMouseWheelEvent(MouseWheelEvent e) {if (GxwCbkHost_.ExecMouseWheel(host, e)) super.processMouseWheelEvent(e);} + @Override public void processMouseMotionEvent(MouseEvent e) {if (host.MouseMoveCbk(IptEvtDataMouse.new_(IptMouseBtn_.None, IptMouseWheel_.None, e.getX(), e.getY()))) super.processMouseMotionEvent(e);} + @Override public void paint(Graphics g) { + if (host.PaintCbk(PaintArgs.new_(GfxAdpBase.new_((Graphics2D)g), RectAdp_.Zero))) // ClipRect not used by any clients; implement when necessary + super.paint(g); // Reevaluate if necessary: super.paint might need to (a) always happen and (b) go before PaintCbk (had issues with drawing text on images) + } + public void EnableDoubleBuffering() { // eliminates flickering during OnPaint + } + public void CreateControlIfNeeded() { + } + public static GxwListBox_lang new_() { + GxwListBox_lang rv = new GxwListBox_lang(); + rv.ctor_(); + return rv; + } GxwListBox_lang() {} +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwTextBox_lang.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwTextBox_lang.java index a27517de8..f6fad53b5 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwTextBox_lang.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwTextBox_lang.java @@ -13,3 +13,206 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import java.awt.AWTEvent; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.ActionEvent; +import java.awt.event.FocusEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; +import javax.swing.AbstractAction; +import javax.swing.BorderFactory; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.border.BevelBorder; +import javax.swing.border.Border; +import javax.swing.text.JTextComponent; +import gplx.gfui.draws.*; +import gplx.gfui.ipts.*; import gplx.gfui.gfxs.*; import gplx.gfui.controls.elems.*; +public class GxwTextBox_lang extends JTextArea implements GxwTextFld { + public Object UnderElem() {return this;} + public GxwCore_base Core() {return ctrlMgr;} GxwCore_base ctrlMgr; + public GxwCbkHost Host() {return host;} public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host = GxwCbkHost_.Null; + public int SelBgn() {return this.getSelectionStart();} public void SelBgn_set(int v) {this.setSelectionStart(v); this.setCaretPosition(v);} + public int SelLen() {return this.getSelectionEnd() - this.getSelectionStart();} public void SelLen_set(int v) {this.setSelectionEnd(this.SelBgn() + v);} + public String TextVal() {return this.getText();} public void TextVal_set(String v) {this.setText(v);} + public void AlignH_(GfuiAlign val) {} // TODO + //@#if !plat_wce + public int BorderWidth() {return borderOn ? 2 : 0;} + public boolean Border_on() {return borderOn;} boolean borderOn = true; + public void Border_on_(boolean v) { + borderOn = v; + Border border = v ? BorderFactory.createLineBorder(Color.BLACK, 1) : null; + this.setBorder(border); + } + public ColorAdp Border_color() {return border_color;} public void Border_color_(ColorAdp v) {border_color = v;} private ColorAdp border_color; + public void Margins_set(int left, int top, int right, int bot) {} + public boolean OverrideTabKey() {return overrideTabKey;} boolean overrideTabKey; + public void OverrideTabKey_(boolean val) { + overrideTabKey = val; + if (val) + GxwTextBox_overrideKeyCmd.new_((GfuiElem)host, this, "TAB", "\t", false); + else + GxwTextBox_overrideKeyCmd.focus_((GfuiElem)host, this, "TAB"); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + return this; + } + public void SendKeyDown(IptKey key) {} + public void SendMouseMove(int x, int y) {} + public void SendMouseDown(IptMouseBtn btn) {} + public void SendMouseWheel(IptMouseWheel direction) {} + @Override public void processKeyEvent(KeyEvent e) { + if (overrideTabKey && e.getKeyCode() == KeyEvent.VK_TAB) { + super.processKeyEvent(e); return;} + if (GxwCbkHost_.ExecKeyEvent(host, e)) + super.processKeyEvent(e); + } + @Override public void processMouseEvent(MouseEvent e) {if (GxwCbkHost_.ExecMouseEvent(host, e)) super.processMouseEvent(e);} + @Override public void processMouseWheelEvent(MouseWheelEvent e) {if (GxwCbkHost_.ExecMouseWheel(host, e)) super.processMouseWheelEvent(e);} + @Override public void processMouseMotionEvent(MouseEvent e) {if (host.MouseMoveCbk(IptEvtDataMouse.new_(IptMouseBtn_.None, IptMouseWheel_.None, e.getX(), e.getY()))) super.processMouseMotionEvent(e);} + @Override public void paint(Graphics g) { + if (host.PaintCbk(PaintArgs.new_(GfxAdpBase.new_((Graphics2D)g), RectAdp_.Zero))) // ClipRect not used by any clients; implement when necessary + super.paint(g); // Reevaluate if necessary: super.paint might need to (a) always happen and (b) go before PaintCbk (had issues with drawing text on images) + } + public GxwTextBox_lang(){ctor_();} + void ctor_() { + ctrlMgr = GxwCore_lang.new_(this); + this.setTabSize(4); + } + public void EnableDoubleBuffering() {} // eliminates flickering during OnPaint + public void CreateControlIfNeeded() {} + @gplx.Virtual public void ctor_MsTextBox_() { + ctor_(); + } +} +class GxwTextFld_cls_lang extends JTextField implements GxwTextFld { + public Object UnderElem() {return this;} + public void AlignH_(GfuiAlign val) { + int align = JTextField.LEFT; + if (val.Val() == GfuiAlign_.Mid.Val()) align = JTextField.CENTER; + else if (val.Val() == GfuiAlign_.Right.Val()) align = JTextField.RIGHT; + this.setHorizontalAlignment(align); + } + public int SelBgn() {return this.getSelectionStart();} + public void SelBgn_set(int v) { +// if (v >= this.getSelectionStart() && v < this.getSelectionEnd()) + try { + this.setSelectionStart(v); + } catch (Exception e) { + Err_.Noop(e); + } // NOTE: sometimes fails when skipping ahead in dvd player; v = 0, and start/end = 0 + } + public int SelLen() {return this.getSelectionEnd() - this.getSelectionStart();} public void SelLen_set(int v) {this.setSelectionEnd(this.SelBgn() + v);} + @gplx.Virtual public void ctor_MsTextBox_() { + ctor_(); + } + //@#if !plat_wce + public int BorderWidth() {return borderOn ? 2 : 0;} + public boolean Border_on() {return borderOn;} +// @Override public void setBorder(Border border) { +// if (borderOn) super.setBorder(border); +// else { +// super.setBorder(null); +// } +// // NO! +// } + public void Border_on_(boolean v) { + borderOn = v; + Border border = v ? BorderFactory.createLineBorder(Color.BLACK) : null; + this.setBorder(border); + } boolean borderOn = true; + public ColorAdp Border_color() {return border_color;} public void Border_color_(ColorAdp v) {border_color = v;} private ColorAdp border_color; + public void Margins_set(int left, int top, int right, int bot) {} + public boolean OverrideTabKey() {return overrideTabKey;} + public void OverrideTabKey_(boolean val) { + overrideTabKey = val; + GxwTextBox_overrideKeyCmd.new_((GfuiElem)host, this, "TAB", "\t", false); + } boolean overrideTabKey; + void ctor_() { + ctrlMgr = GxwCore_lang.new_(this); + this.enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK); + this.Border_on_(true); + GxwBoxListener lnr = new GxwBoxListener(this); +// this.setFocusTraversalKeysEnabled(false); + this.addComponentListener(lnr); + this.addFocusListener(lnr); + } + public GxwCore_base Core() {return ctrlMgr;} GxwCore_base ctrlMgr; + public GxwCbkHost Host() {return host;} public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host = GxwCbkHost_.Null; + public String TextVal() {return this.getText();} public void TextVal_set(String v) { + this.setText(v); + this.SelBgn_set(0); this.SelLen_set(0); // otherwise will set cursor to end; want to see text start + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, GxwElem_lang.AlignH_cmd)) AlignH_(GfuiAlign_.cast(m.CastObj("v"))); + return this; + } + public void SendKeyDown(IptKey key) {} + public void SendMouseMove(int x, int y) {} + public void SendMouseDown(IptMouseBtn btn) {} + public void SendMouseWheel(IptMouseWheel direction) {} + @Override public void processKeyEvent(KeyEvent e) { + if (overrideTabKey && e.getKeyCode() == KeyEvent.VK_TAB) { + super.processKeyEvent(e); return;} + if (GxwCbkHost_.ExecKeyEvent(host, e)) + super.processKeyEvent(e); + } + @Override public void processMouseEvent(MouseEvent e) {if (GxwCbkHost_.ExecMouseEvent(host, e)) super.processMouseEvent(e);} + @Override public void processMouseWheelEvent(MouseWheelEvent e) { + if (GxwCbkHost_.ExecMouseWheel(host, e)) super.processMouseWheelEvent(e);} + @Override public void processMouseMotionEvent(MouseEvent e) {if (host.MouseMoveCbk(IptEvtDataMouse.new_(IptMouseBtn_.None, IptMouseWheel_.None, e.getX(), e.getY()))) super.processMouseMotionEvent(e);} + @Override public void paint(Graphics g) { + if (host.PaintCbk(PaintArgs.new_(GfxAdpBase.new_((Graphics2D)g), RectAdp_.Zero))) // ClipRect not used by any clients; implement when necessary + super.paint(g); // Reevaluate if necessary: super.paint might need to (a) always happen and (b) go before PaintCbk (had issues with drawing text on images) + } + public void EnableDoubleBuffering() { // eliminates flickering during OnPaint + } + public void CreateControlIfNeeded() { + } +} +class GxwTextBox_overrideKeyCmd extends AbstractAction { + public void actionPerformed(ActionEvent e) { + if (focus) { + int z = owner.OwnerWin().FocusMgr().SubElems().Idx_of(owner); + owner.OwnerWin().FocusMgr().Focus(focusDir, z); + return; + } + if (overrideText == null) return; + textBox.replaceSelection(overrideText); + } + public GxwTextBox_overrideKeyCmd(JTextComponent textBox, String overrideText) {this.textBox = textBox; this.overrideText = overrideText;} + public static GxwTextBox_overrideKeyCmd focus_(GfuiElem owner, JTextComponent textBox, String keyName) {return GxwTextBox_overrideKeyCmd.new_(owner, textBox, keyName, null, true);} + public static GxwTextBox_overrideKeyCmd focusPrv_(GfuiElem owner, JTextComponent textBox, String keyName) { + GxwTextBox_overrideKeyCmd rv = GxwTextBox_overrideKeyCmd.new_(owner, textBox, keyName, null, true); + rv.focusDir = false; + return rv; + } + public static GxwTextBox_overrideKeyCmd noop_(GfuiElem owner, JTextComponent textBox, String keyName) {return GxwTextBox_overrideKeyCmd.new_(owner, textBox, keyName, null, false);} + public static GxwTextBox_overrideKeyCmd new_(GfuiElem owner, JTextComponent textBox, String keyName, String overrideText, boolean focus) { + KeyStroke tabKey = KeyStroke.getKeyStroke(keyName); + GxwTextBox_overrideKeyCmd action = new GxwTextBox_overrideKeyCmd(textBox, overrideText); + action.focus = focus; + action.owner = owner; + textBox.getInputMap().remove(tabKey); + textBox.getInputMap().put(tabKey, action); + return action; + } + JTextComponent textBox; String overrideText; GfuiElem owner; boolean focus; boolean focusDir = true; +} +class GxwTextBox_lang_ { + public static GxwTextFld fld_() { + GxwTextFld_cls_lang rv = new GxwTextFld_cls_lang(); + rv.ctor_MsTextBox_(); + return rv; + } + public static GxwTextMemo_lang memo_() { + GxwTextMemo_lang rv = new GxwTextMemo_lang(); + rv.ctor_MsTextBoxMultiline_(); + return rv; + } + } diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwTextFld.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwTextFld.java index a27517de8..58c2ae5c9 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwTextFld.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwTextFld.java @@ -13,3 +13,14 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; +public interface GxwTextFld extends GxwElem { + boolean Border_on(); void Border_on_(boolean v); + ColorAdp Border_color(); void Border_color_(ColorAdp v); + int SelBgn(); void SelBgn_set(int v); + int SelLen(); void SelLen_set(int v); + void CreateControlIfNeeded(); + boolean OverrideTabKey(); void OverrideTabKey_(boolean v); + void Margins_set(int left, int top, int right, int bot); +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwTextHtml.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwTextHtml.java index a27517de8..93b32f9d4 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwTextHtml.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwTextHtml.java @@ -13,3 +13,10 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public interface GxwTextHtml extends GxwTextMemo { + Keyval[] Html_sel_atrs(); + void Html_enabled(boolean v); + String Html_doc_html(); + void Html_css_set(String s); +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwTextHtml_lang.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwTextHtml_lang.java index a27517de8..66f811137 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwTextHtml_lang.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwTextHtml_lang.java @@ -13,3 +13,243 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.core.strings.*; +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; +import java.io.StringWriter; +import java.util.Enumeration; + +import javax.swing.BorderFactory; +import javax.swing.JEditorPane; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.ScrollPaneConstants; +import javax.swing.border.Border; +import javax.swing.plaf.ComponentUI; +import javax.swing.text.AbstractDocument; +import javax.swing.text.AttributeSet; +import javax.swing.text.BoxView; +import javax.swing.text.ComponentView; +import javax.swing.text.DefaultEditorKit; +import javax.swing.text.DefaultStyledDocument; +import javax.swing.text.Document; +import javax.swing.text.EditorKit; +import javax.swing.text.Element; +import javax.swing.text.IconView; +import javax.swing.text.LabelView; +import javax.swing.text.MutableAttributeSet; +import javax.swing.text.ParagraphView; +import javax.swing.text.PlainDocument; +import javax.swing.text.Segment; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledDocument; +import javax.swing.text.StyledEditorKit; +import javax.swing.text.View; +import javax.swing.text.ViewFactory; +import javax.swing.text.html.HTML; +import javax.swing.text.html.HTMLDocument; +import javax.swing.text.html.HTMLEditorKit; +import javax.swing.text.html.HTMLEditorKit.HTMLFactory; +import javax.swing.text.html.InlineView; +import javax.swing.text.html.StyleSheet; +import gplx.gfui.draws.*; +import gplx.gfui.ipts.*; import gplx.gfui.gfxs.*; +public class GxwTextHtml_lang extends JScrollPane implements GxwTextHtml { + @Override public GxwCore_base Core() {return core;} GxwCore_host core; + public GxwCbkHost Host() {return host;} public void Host_set(GxwCbkHost host) {this.host = host; editor.Host_set(host);} GxwCbkHost host; + @Override public int SelBgn() {return editor.SelBgn();} @Override public void SelBgn_set(int v) {editor.SelBgn_set(v);} + @Override public int SelLen() {return editor.SelLen();} @Override public void SelLen_set(int v) {editor.SelLen_set(v);} + @Override public String TextVal() {return editor.TextVal();} @Override public void TextVal_set(String v) {editor.TextVal_set(v);} + @Override public boolean Border_on() {return false;} //boolean borderOn = true; + @Override public void Border_on_(boolean v) { +// borderOn = v; +// Border border = v ? BorderFactory.createLineBorder(Color.BLACK) : null; +// this.setBorder(border); + } + public ColorAdp Border_color() {return border_color;} public void Border_color_(ColorAdp v) {border_color = v;} private ColorAdp border_color; + @Override public boolean OverrideTabKey() {return false;} + @Override public void OverrideTabKey_(boolean v) {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return null;} + public void Html_enabled(boolean v) { +// String newContentType = v ? "text/html" : "text/plain"; +// if (!String_.Eq(editor.getContentType(), newContentType)) { +// editor = new GxwTextHtml_editor().ctor(); +// core.Inner_set(editor.core); +// this.setViewportView(editor); +// } + editor.Html_enabled(v); + } + public GxwTextHtml_editor Editor() {return editor;} GxwTextHtml_editor editor; + public void ScrollTillCaretIsVisible() {throw Err_.new_unimplemented();} + public GxwTextHtml_lang ctor() { + editor = new GxwTextHtml_editor().ctor(); + core = new GxwCore_host(GxwCore_lang.new_(this), editor.core); + this.setViewportView(editor); +// this.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); +// this.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + this.setBorder(null); + return this; + } + public Keyval[] Html_sel_atrs() {return editor.Html_sel_atrs();} + public String Html_doc_html() {return editor.Html_doc_html();} + public void Html_css_set(String s) {editor.Html_css_set(s);} + @Override public void Margins_set(int left, int top, int right, int bot) {} + @Override public void EnableDoubleBuffering() {} + @Override public void CreateControlIfNeeded() {} + @Override public int LinesPerScreen() {return 0;} + @Override public int LinesTotal() {return 0;} + @Override public int ScreenCount() {return 0;} + @Override public int CharIndexOf(int lineIndex) {return 0;} + @Override public int CharIndexOfFirst() {return 0;} + @Override public int LineIndexOfFirst() {return 0;} + @Override public int LineIndexOf(int charIndex) {return 0;} + @Override public PointAdp PosOf(int charIndex) {return null;} + @Override public void ScrollLineUp() {} + @Override public void ScrollLineDown() {} + @Override public void ScrollScreenUp() {} + @Override public void ScrollScreenDown() {} + @Override public void SelectionStart_toFirstChar() {} + @Override public void ScrollTillSelectionStartIsFirstLine() {} +} + +class GxwTextHtml_editor extends JEditorPane implements GxwTextHtml { + public GxwTextHtml_editor ctor() { + styledKit = new StyledEditorKit(); + htmlKit = new HTMLEditorKit(); + this.setEditorKit(htmlKit); +// this.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE); this.setFont(new Font("Courier New", 6, Font.PLAIN)); // force jeditorpane to take font + core = GxwCore_lang.new_(this); + this.setCaret(new javax.swing.text.DefaultCaret() {public void setSelectionVisible(boolean vis) {super.setSelectionVisible(true);}});// else highlighted selection will not be visible when text box loses focus + return this; + } +// public HTMLEditorKit HtmlKit() {return htmlKit;} HTMLEditorKit htmlKit; + @Override public GxwCore_base Core() {return core;} GxwCore_base core; + public GxwCbkHost Host() {return host;} public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host; + @Override public int SelBgn() {return this.getSelectionStart();} @Override public void SelBgn_set(int v) {this.setSelectionStart(v); this.setCaretPosition(v);} + @Override public int SelLen() {return this.getSelectionEnd() - this.getSelectionStart();} @Override public void SelLen_set(int v) {this.setSelectionEnd(this.getSelectionStart() + v);} + @Override public String TextVal() {return this.getText();} + @Override public void TextVal_set(String v) {this.setText(v);} + @Override public boolean Border_on() {return borderOn;} boolean borderOn = true; + @Override public void Border_on_(boolean v) { + borderOn = v; + Border border = v ? BorderFactory.createLineBorder(Color.BLACK) : null; + this.setBorder(border); + } + public ColorAdp Border_color() {return border_color;} public void Border_color_(ColorAdp v) {border_color = v;} private ColorAdp border_color; + @Override public boolean OverrideTabKey() {return false;} + @Override public void OverrideTabKey_(boolean v) {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return null;} + StyledEditorKit styledKit; HTMLEditorKit htmlKit; + + public void Html_enabled(boolean v) { +// String contentType = v ? "text/html" : "text/rtf"; +// this.setEditorKit(v ? new StyledEditorKit() : new DefaultEditorKit()); + this.setEditorKit(v ? htmlKit : styledKit); + } + public void ScrollTillCaretIsVisible() {throw Err_.new_unimplemented();} + public void Html_css_set(String s) { + StyleSheet styleSheet = htmlKit.getStyleSheet(); + styleSheet.addRule(s); + } + public String Html_print() { + String_bldr sb = String_bldr_.new_(); + sb.Add("selBgn=").Add(Int_.To_str(Html_sel_bgn())).Add_char_crlf(); + sb.Add("selEnd=").Add(Int_.To_str(Html_sel_end())).Add_char_crlf(); + sb.Add("selTxt=").Add(Html_sel_text()).Add_char_crlf(); + Keyval[] atrs = Html_sel_atrs(); + for (int i = 0; i < atrs.length; i++) { + Keyval atr = atrs[i]; + sb.Add(atr.Key() + "=").Add(atr.Val_to_str_or_null()).Add_char_crlf(); + } + return sb.To_str(); + } + Element Html_sel_elm() { + HTMLDocument doc = (HTMLDocument)this.getDocument(); + int pos = this.getCaretPosition(); + return doc.getCharacterElement(pos); + } + public int Html_sel_bgn() {return Html_sel_elm().getStartOffset();} + public int Html_sel_end() {return Html_sel_elm().getEndOffset();} +// public String Html_doc_html() { +// HTMLEditorKit kit = (HTMLEditorKit)this.getEditorKit(); +// HTMLDocument doc = (HTMLDocument)this.getDocument(); +// StringWriter sw = new StringWriter(); +// try {kit.write(sw, doc, 0, doc.getLength());} +// catch (Exception exc) {throw Err_.err_(exc, "Html_doc_html");} +// return sw.toString(); +// } + public String Html_doc_html() { + Document doc = this.getDocument(); + try {return this.getDocument().getText(0, doc.getLength());} + catch (Exception e) {throw Err_.new_exc(e, "ui", "Html_doc_html");} + } + public String Html_sel_text() { + Element elm = Html_sel_elm(); + int sel_bgn = elm.getStartOffset(); + int sel_end = elm.getEndOffset(); + try {return this.getDocument().getText(sel_bgn, sel_end - sel_bgn);} + catch (Exception e) {throw Err_.new_exc(e, "ui", "Html_sel_text");} + } + static void Html_sel_atrs(AttributeSet atrs, List_adp list, String ownerKey, String dlm) { + if (atrs == null) return; + Enumeration keys = atrs.getAttributeNames(); + while (true) { + if (!keys.hasMoreElements()) break; + Object atr_key = keys.nextElement(); if (atr_key == null) break; + Object atr_val = atrs.getAttribute(atr_key); + String atr_key_str = atr_key.toString(); + String itm_key = ownerKey == null ? atr_key_str : ownerKey + dlm + atr_key_str; + if (atr_val instanceof javax.swing.text.AttributeSet) + Html_sel_atrs((AttributeSet)atr_val, list, itm_key, dlm); + else + list.Add(Keyval_.new_(itm_key, atr_val)); + } + } + public Keyval[] Html_sel_atrs() { + if (String_.Eq(this.getContentType(), "text/plain")) return Keyval_.Ary_empty; + Element elm = Html_sel_elm(); if (elm == null) return Keyval_.Ary_empty; + List_adp sel_atrs_list = List_adp_.New(); + Html_sel_atrs(elm.getAttributes(), sel_atrs_list, null, "."); + return (Keyval[])sel_atrs_list.To_ary(Keyval.class); + } + + @Override public void processKeyEvent(KeyEvent e) { +// if (overrideTabKey && e.getKeyCode() == KeyEvent.VK_TAB) { +// super.processKeyEvent(e); return;} + if (GxwCbkHost_.ExecKeyEvent(host, e)) + super.processKeyEvent(e); + } + @Override public void processMouseEvent(MouseEvent e) {if (GxwCbkHost_.ExecMouseEvent(host, e)) super.processMouseEvent(e);} + @Override public void processMouseWheelEvent(MouseWheelEvent e) {if (GxwCbkHost_.ExecMouseWheel(host, e)) super.processMouseWheelEvent(e);} + @Override public void processMouseMotionEvent(MouseEvent e) {if (host.MouseMoveCbk(IptEvtDataMouse.new_(IptMouseBtn_.None, IptMouseWheel_.None, e.getX(), e.getY()))) super.processMouseMotionEvent(e);} + @Override public void paint(Graphics g) { + if (host.PaintCbk(PaintArgs.new_(GfxAdpBase.new_((Graphics2D)g), RectAdp_.Zero))) // ClipRect not used by any clients; implement when necessary + super.paint(g); // Reevaluate if necessary: super.paint might need to (a) always happen and (b) go before PaintCbk (had issues with drawing text on images) + } + + @Override public void Margins_set(int left, int top, int right, int bot) {} + @Override public void EnableDoubleBuffering() {} + @Override public void CreateControlIfNeeded() {} + @Override public int LinesPerScreen() {return 0;} + @Override public int LinesTotal() {return 0;} + @Override public int ScreenCount() {return 0;} + @Override public int CharIndexOf(int lineIndex) {return 0;} + @Override public int CharIndexOfFirst() {return 0;} + @Override public int LineIndexOfFirst() {return 0;} + @Override public int LineIndexOf(int charIndex) {return 0;} + @Override public PointAdp PosOf(int charIndex) {return null;} + @Override public void ScrollLineUp() {} + @Override public void ScrollLineDown() {} + @Override public void ScrollScreenUp() {} + @Override public void ScrollScreenDown() {} + @Override public void SelectionStart_toFirstChar() {} + @Override public void ScrollTillSelectionStartIsFirstLine() {} +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwTextMemo.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwTextMemo.java index a27517de8..cac488150 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwTextMemo.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwTextMemo.java @@ -13,3 +13,21 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public interface GxwTextMemo extends GxwTextFld { + int LinesPerScreen(); + int LinesTotal(); + int ScreenCount(); + int CharIndexOf(int lineIndex); + int CharIndexOfFirst(); + int LineIndexOfFirst(); + int LineIndexOf(int charIndex); + PointAdp PosOf(int charIndex); + void ScrollLineUp(); + void ScrollLineDown(); + void ScrollScreenUp(); + void ScrollScreenDown(); + void SelectionStart_toFirstChar(); + void ScrollTillSelectionStartIsFirstLine(); + void ScrollTillCaretIsVisible(); +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwTextMemo_lang.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwTextMemo_lang.java index a27517de8..15aaa8c57 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwTextMemo_lang.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwTextMemo_lang.java @@ -13,3 +13,338 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import java.awt.AWTKeyStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Insets; +import java.awt.KeyboardFocusManager; +import java.awt.event.ActionEvent; +import java.awt.event.FocusEvent; +import java.util.HashSet; +import java.util.Set; +import javax.swing.AbstractAction; +import javax.swing.BorderFactory; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.KeyStroke; +import javax.swing.ScrollPaneConstants; +import javax.swing.event.UndoableEditEvent; +import javax.swing.event.UndoableEditListener; +import javax.swing.text.Document; +import javax.swing.undo.CannotRedoException; +import javax.swing.undo.CannotUndoException; +import javax.swing.undo.UndoManager; + +import gplx.gfui.GfuiAlign; +import gplx.gfui.GfuiAlign_; +import gplx.gfui.PointAdp; +import gplx.gfui.PointAdp_; +import gplx.gfui.RectAdp; +import gplx.gfui.SizeAdp; +import gplx.gfui.controls.elems.GfuiElem; +import gplx.gfui.draws.*; +import gplx.gfui.ipts.*; import gplx.gfui.controls.windows.*; +import gplx.gfui.layouts.swts.*; +public class GxwTextMemo_lang extends JScrollPane implements GxwTextMemo { + public JTextArea Inner() {return txt_box;} GxwTextBox_lang txt_box; + public GxwCore_base Core() {return core;} GxwCore_base core; + public GxwCbkHost Host() {return host;} public void Host_set(GxwCbkHost host) {this.host = host; txt_box.Host_set(host);} GxwCbkHost host; + @Override public void setBackground(Color c) { + if (txt_box == null) return; // WORKAROUND.OSX: OSX LookAndFeel calls setBackground during ctor of Mem_html; DATE:2015-05-11 + if (c.getRGB() == Color.BLACK.getRGB()) txt_box.setCaretColor(Color.WHITE); + else if (c.getRGB() == Color.WHITE.getRGB()) txt_box.setCaretColor(Color.BLACK); + super.setBackground(c); + } + public void ScrollTillCaretIsVisible() {throw Err_.new_unimplemented();} + public void Margins_set(int left, int top, int right, int bot) { + if (left == 0 && right == 0) { + txt_box.setBorder(BorderFactory.createLineBorder(Color.BLACK)); + txt_box.setMargin(new Insets(0, 0, 0,0)); + } + else { +// txt_box.setBorder(BasicBorders.getTextFieldBorder()); +// txt_box.setMargin(new Insets(0, l, 0, r)); + txt_box.setFont(new Font("Courier New", FontStyleAdp_.Plain.Val(), 12)); + txt_box.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.BLACK), BorderFactory.createEmptyBorder(top, left, bot, right))); + } + } + public void ctor_MsTextBoxMultiline_() { + txt_box = new GxwTextBox_lang(); + txt_box.ctor_MsTextBox_(); + core = new GxwCore_host(GxwCore_lang.new_(this), txt_box.ctrlMgr); + this.setViewportView(txt_box); + txt_box.setLineWrap(true); + txt_box.setWrapStyleWord(true); // else text will wrap in middle of words + this.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); + this.setBorder(null); + txt_box.setBorder(BorderFactory.createLineBorder(Color.BLACK)); + txt_box.setCaretColor(Color.BLACK); + txt_box.getCaret().setBlinkRate(0); + txt_box.setMargin(new Insets(0, 200, 200,0)); + OverrideKeyBindings(); + InitUndoMgr(); + txt_box.setCaret(new javax.swing.text.DefaultCaret() {public void setSelectionVisible(boolean vis) {super.setSelectionVisible(true);}});// else highlighted selection will not be visible when text box loses focus + // this.setLayout(null); + +// Object fontDefinition = new UIDefaults.ProxyLazyValue("javax.swing.plaf.FontUIResource", null, new Object[] { "dialog", new Integer(Font.PLAIN), new Integer(12) }); +// java.util.Enumeration keys = UIManager.getDefaults().keys(); +// while (keys.hasMoreElements()) { +// Object key = keys.nextElement(); +// Object value = UIManager.get(key); +// if (value instanceof javax.swing.plaf.FontUIResource) { +// UIManager.put(key, fontDefinition); +// } +// } + } @gplx.Internal protected GxwTextMemo_lang() {} + void InitUndoMgr() { + final UndoManager undo = new UndoManager(); + Document doc = txt_box.getDocument(); + + // Listen for undo and redo events + doc.addUndoableEditListener(new UndoableEditListener() { + public void undoableEditHappened(UndoableEditEvent evt) { + undo.addEdit(evt.getEdit()); + } + }); + + // Create an undo action and add it to the text component + txt_box.getActionMap().put("Undo", + new AbstractAction("Undo") { + public void actionPerformed(ActionEvent evt) { + try { + if (undo.canUndo()) { + undo.undo(); + } + } catch (CannotUndoException e) { + } + } + }); + + // Bind the undo action to ctl-Z + txt_box.getInputMap().put(KeyStroke.getKeyStroke("control Z"), "Undo"); + + // Create a redo action and add it to the text component + txt_box.getActionMap().put("Redo", + new AbstractAction("Redo") { + public void actionPerformed(ActionEvent evt) { + try { + if (undo.canRedo()) { + undo.redo(); + } + } catch (CannotRedoException e) { + } + } + }); + + // Bind the redo action to ctl-Y + txt_box.getInputMap().put(KeyStroke.getKeyStroke("control Y"), "Redo"); + } + void OverrideKeyBindings() { + Set forTraSet = new HashSet (); + txt_box.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forTraSet); + txt_box.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, forTraSet); + + GxwTextBox_overrideKeyCmd.noop_((GfuiElem)host, txt_box, "control H"); // else ctrl+h deletes current char +// GxwTextBox_overrideKeyCmd.new_(txt_box, "ENTER", Env_.NewLine); // else enter will always use \n on window; jtextBox allows separation of \r from \n + } + public void AlignH_(GfuiAlign val) { + // can't work with jtextArea +// if (val.Val() == GfuiAlign_.Mid.Val()) +// txt_box.setAlignmentY(JTextArea.CENTER_ALIGNMENT); + } + public int LinesPerScreen() {return LinesPerScreen(this);} + public int LinesTotal() { + return 40; +// return this.getStyledDocument(). .getLineCount(); + } + public int ScreenCount() {return Int_.DivAndRoundUp(this.LinesTotal(), this.LinesPerScreen());} + public int LineLength(int lineIndex) {return LineLength(this, lineIndex);} + public int CharIndexOfFirst() { + int firstLineIndex = LineIndexOfFirst(); + return CharIndexOf(firstLineIndex); + } + public int CharIndexOf(int lineIndex) { + return lineIndex; +// try {return this.getLineStartOffset(lineIndex);} +// catch (BadLocationException e) {return -1;} + } + public int LineIndexOfFirst() { +// this.getl + + return 0; //todo +// return TextBoxNpi.LineIndexFirst(ControlNpi.Hwnd(this)); +} + public int LineIndexOf(int charIndex) { + return charIndex; +// try {return this.getLineOfOffset(charIndex);} +// catch (BadLocationException e) {return -1;} + } + public PointAdp PosOf(int charIndex) { +// Document d = this.getDocument(); +// Element e = d.getDefaultRootElement(); +// e. + return PointAdp_.Zero; //todo +// PointAdp point = TextBoxNpi.PosOf(ControlNpi.Hwnd(this), charIndex); +// return (point.Eq(TextBoxNpi.InvalidPoint)) +// ? TextBoxNpi.InvalidPoint +// : PointAdp_.XtoPointAdp(this.PointToScreen(point.XtoPoint())); + } + public void SelectionStart_toFirstChar() { +// scrollPane.s +// JScrollPane scrollingResult = new JScrollPane(this); +// scrollingResult. + //todo +// this.SelectionStart = this.CharIndexOfFirst(); + } + public void ScrollLineUp() { +// ScrollWindow(TextBoxScrollTypes.LineUp); + } + public void ScrollLineDown() { +// ScrollWindow(TextBoxScrollTypes.LineDown); + } + // public void ExtendLineUp() {int l = this.SelectionStart; ScrollWindow(TextBoxScrollTypes.LineUp); int m = this.SelectionStart; this.SelectionStart = l; this.SelectionLength = m - l;} + // public void ExtendLineDown() {int l = this.SelectionStart; ScrollWindow(TextBoxScrollTypes.LineDown); int m = this.SelectionStart; this.SelectionStart = l; this.SelectionLength = m - l;} + public void ScrollScreenUp() { +// ScrollWindow(TextBoxScrollTypes.PageUp); + } + public void ScrollScreenDown() { +// ScrollWindow(TextBoxScrollTypes.PageDown); + } + public void ScrollTillSelectionStartIsFirstLine() { +// this.Visible = false; +// this.ScrollToCaret(); +// int currentLine = TextBoxNpi.LineIndex(ControlNpi.Hwnd(this), this.SelectionStart); +// int lineCount = this.LinesPerScreen(); +// int previousFirstLine = -1; +// do { +// int firstLine = TextBoxNpi.LineIndexFirst(ControlNpi.Hwnd(this)); +// if (firstLine == previousFirstLine) break; // NOTE: avoid infinite loop +// +// if (firstLine - currentLine > lineCount) +// this.ScrollScreenUp(); +// else if (currentLine - firstLine > lineCount) +// this.ScrollScreenDown(); +// else if (currentLine < firstLine) +// this.ScrollLineUp(); +// else if (currentLine > firstLine) +// this.ScrollLineDown(); +// else +// break; +// previousFirstLine = firstLine; +// } while (true); +// this.Visible = true; +// GfuiEnv_.DoEvents(); // WORKAROUND (WCE): needed to repaint screen +// this.Parent.Focus(); + } +// void ScrollWindow(TextBoxScrollTypes scrollType) {TextBoxNpi.ScrollWindow(ControlNpi.Hwnd(this), scrollType);} + public static int LinesPerScreen(GxwTextMemo_lang textBox) { + return 50; +// int lineIndexLine0 = textBox.LineIndexOfFirst(); +// int charIndexLine0 = textBox.CharIndexOf(lineIndexLine0); +// PointAdp posLine0 = textBox.PosOf(charIndexLine0); +// int charIndexLine1 = textBox.CharIndexOf(lineIndexLine0 + 1); +// PointAdp posLine1 = textBox.PosOf(charIndexLine1); +// +// int availHeight = textBox.Height - (2 * textBox.BorderWidth()); +// int lineHeight = (posLine1.Eq(TextBoxNpi.InvalidPoint)) // TextBox is sized for one line, or textBox.Text = "" +// ? availHeight +// : posLine1.Y() - posLine0.Y(); +// int rv = availHeight / lineHeight; +// return rv == 0 ? 1 : rv; // always return at least 1 line + } + public static int LineLength(GxwTextMemo_lang textBox, int lineIndex) { + return -1; +// int lineLength = TextBoxNpi.LineLength(ControlNpi.Hwnd(textBox), lineIndex); +// +// // WORKAROUND (TextBox): TextBoxNpi.LineLength ignores String_.NewLine; manually check for NewLine +// int newLineLength = String_.NewLine.length; +// String text = textBox.Text; +// int charIndexFirst = textBox.CharIndexOf(lineIndex); +// int charIndexLastAccordingToApi = charIndexFirst + lineLength; +// if (charIndexLastAccordingToApi + newLineLength > text.length) return lineLength; // last line of text; no ignored line possible +// String charactersPastEndOfLine = text.Substring(charIndexLastAccordingToApi, newLineLength); +// return charactersPastEndOfLine == String_.NewLine ? lineLength + newLineLength : lineLength; + } + public boolean Border_on() {return txt_box.Border_on();} public void Border_on_(boolean v) {txt_box.Border_on_(v);} + public ColorAdp Border_color() {return border_color;} public void Border_color_(ColorAdp v) {border_color = v;} private ColorAdp border_color; + public void CreateControlIfNeeded() {txt_box.CreateControlIfNeeded();} + public boolean OverrideTabKey() {return txt_box.OverrideTabKey();} + public void OverrideTabKey_(boolean v) { + txt_box.OverrideTabKey_(v); + if (v) { + Set forTraSet = new HashSet (); + txt_box.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forTraSet); + txt_box.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, forTraSet); + } + else { + Set forTraSet = new HashSet (); + txt_box.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forTraSet); + txt_box.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, forTraSet); + GxwTextBox_overrideKeyCmd.focus_((GfuiElem)host, txt_box, "TAB"); // else ctrl+h deletes current char + GxwTextBox_overrideKeyCmd.focusPrv_((GfuiElem)host, txt_box, "shift TAB"); // else ctrl+h deletes current char +// Set forTraSet = new HashSet (); +// forTraSet.add(AWTKeyStroke.getAWTKeyStroke("TAB")); +// Set bwdTraSet = new HashSet (); +// bwdTraSet.add(AWTKeyStroke.getAWTKeyStroke("control TAB")); +// txt_box.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forTraSet); +// txt_box.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, bwdTraSet); +// txt_box.OverrideTabKey_(false); + } + } + public int SelBgn() {return txt_box.SelBgn();} public void SelBgn_set(int v) {txt_box.SelBgn_set(v);} + public int SelLen() {return txt_box.SelLen();} public void SelLen_set(int v) {txt_box.SelLen_set(v);} + public void EnableDoubleBuffering() {txt_box.EnableDoubleBuffering();} + + public void SendKeyDown(IptKey key) {txt_box.SendKeyDown(key);} + public String TextVal() {return txt_box.TextVal();} + public void TextVal_set(String v) { + txt_box.TextVal_set(v); + txt_box.setSelectionStart(0); txt_box.setSelectionEnd(0); // else selects whole text and scrolls to end of selection + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, GxwElem_lang.AlignH_cmd)) AlignH_(GfuiAlign_.cast(m.CastObj("v"))); + return txt_box.Invk(ctx, ikey, k, m); + } +} +class GxwCore_host extends GxwCore_base { + @Override public void Controls_add(GxwElem sub){outer.Controls_add(sub);} + @Override public void Controls_del(GxwElem sub){outer.Controls_del(sub);} + @Override public int Width(){return outer.Width();} @Override public void Width_set(int v){outer.Width_set(v); inner.Width_set(v);} + @Override public int Height(){return outer.Height();} @Override public void Height_set(int v){outer.Height_set(v); inner.Height_set(v);} + @Override public int X(){return outer.X();} @Override public void X_set(int v){outer.X_set(v);} // NOTE: noop inner.x + @Override public int Y(){return outer.Y();} @Override public void Y_set(int v){outer.Y_set(v);} // NOTE: noop inner.y + @Override public SizeAdp Size(){return outer.Size();} @Override public void Size_set(SizeAdp v){outer.Size_set(v); inner.Size_set(v);} + @Override public PointAdp Pos(){return outer.Pos();} @Override public void Pos_set(PointAdp v){outer.Pos_set(v);} // NOTE: noop inner.pos + @Override public RectAdp Rect(){return outer.Rect();} @Override public void Rect_set(RectAdp v){outer.Rect_set(v); inner.Size_set(v.Size());} // NOTE: noop inner.pos + @Override public boolean Visible(){return outer.Visible();} @Override public void Visible_set(boolean v){outer.Visible_set(v); inner.Visible_set(v);} + @Override public ColorAdp BackColor(){return outer.BackColor();} @Override public void BackColor_set(ColorAdp v){outer.BackColor_set(v); inner.BackColor_set(v);} + @Override public ColorAdp ForeColor(){return outer.ForeColor();} @Override public void ForeColor_set(ColorAdp v){outer.ForeColor_set(v); inner.ForeColor_set(v);} + @Override public FontAdp TextFont(){return outer.TextFont();} @Override public void TextFont_set(FontAdp v){outer.TextFont_set(v); inner.TextFont_set(v);} + @Override public String TipText() {return tipText;} @Override public void TipText_set(String v) {tipText = v;} String tipText; + @Override public Swt_layout_mgr Layout_mgr() {return null;} + @Override public void Layout_mgr_(Swt_layout_mgr v) {} + @Override public Swt_layout_data Layout_data() {return null;} + @Override public void Layout_data_(Swt_layout_data v) {} + + public Object Reapply() { + TextFont_set(outer.TextFont()); return this;} // HACK: + + @Override public int Focus_index(){return inner.Focus_index();} @Override public void Focus_index_set(int v){outer.Focus_index_set(v); inner.Focus_index_set(v);} + @Override public boolean Focus_able(){return inner.Focus_able();} @Override public void Focus_able_(boolean v){outer.Focus_able_(v); inner.Focus_able_(v);} +// @Override public void Focus_able_force_(boolean v){outer.Focus_able_force_(v); inner.Focus_able_force_(v);} + @Override public boolean Focus_has(){return inner.Focus_has();} + @Override public void Focus(){ + inner.Focus(); + } + @Override public void Select_exec(){ + inner.Select_exec(); + } + @Override public void Zorder_front(){outer.Zorder_front();} @Override public void Zorder_back(){outer.Zorder_back();} + @Override public void Invalidate(){outer.Invalidate(); + inner.Invalidate();} + @Override public void Dispose(){outer.Dispose();} + GxwCore_base outer; GxwCore_base inner; + public void Inner_set(GxwCore_base v) {inner = v;} + public GxwCore_host(GxwCore_base outer, GxwCore_base inner) {this.outer = outer; this.inner = inner;} +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwWin.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwWin.java index a27517de8..89ee470f1 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwWin.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwWin.java @@ -13,3 +13,19 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.imgs.*; +public interface GxwWin extends GxwElem { + IconAdp IconWin(); void IconWin_set(IconAdp v); + + void ShowWin(); + void HideWin(); + boolean Maximized(); void Maximized_(boolean v); + boolean Minimized(); void Minimized_(boolean v); + void CloseWin(); + boolean Pin(); void Pin_set(boolean val); + + void OpenedCmd_set(Gfo_invk_cmd v); + void TaskbarVisible_set(boolean val); + void TaskbarParkingWindowFix(GxwElem form); +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/GxwWin_lang.java b/150_gfui/src/gplx/gfui/controls/gxws/GxwWin_lang.java index a27517de8..6c1420e7b 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/GxwWin_lang.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/GxwWin_lang.java @@ -13,3 +13,256 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import javax.swing.JDesktopPane; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JWindow; + +import org.eclipse.swt.widgets.Display; + +import java.awt.AWTEvent; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.Window; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import gplx.gfui.draws.*; import gplx.gfui.ipts.*; import gplx.gfui.gfxs.*; import gplx.gfui.imgs.*; import gplx.gfui.kits.swts.*; +public class GxwWin_lang extends JFrame implements GxwWin, WindowListener { + public void ShowWin() {this.setVisible(true);} + public void ShowWinModal() {} + public void HideWin() {this.setVisible(false);} + public boolean Minimized() {return this.getState() == Frame.ICONIFIED;} public void Minimized_(boolean v) {this.setState(v ? Frame.ICONIFIED : Frame.NORMAL);} + public boolean Maximized() {return this.getState() == Frame.MAXIMIZED_BOTH;} public void Maximized_(boolean v) {this.setState(v ? Frame.MAXIMIZED_BOTH : Frame.NORMAL);} + public void CloseWin() {this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); this.dispose();} + public boolean Pin() {return pin;} + public void Pin_set(boolean val) { + this.setAlwaysOnTop(val); pin = val;} boolean pin = false; + public IconAdp IconWin() {return icon;} IconAdp icon; + public void IconWin_set(IconAdp i) { + if (i == null) return; + icon = i; + this.setIconImage(i.XtoImage()); + } + public void OpenedCmd_set(Gfo_invk_cmd v) {whenLoadedCmd = v;} Gfo_invk_cmd whenLoadedCmd = Gfo_invk_cmd.Noop; + public GxwCore_base Core() {return ctrlMgr;} GxwCore_base ctrlMgr; + public GxwCbkHost Host() {return host;} public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host = GxwCbkHost_.Null; + public String TextVal() {return this.getTitle();} public void TextVal_set(String v) {this.setTitle(v);} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return this;} + public void SendKeyDown(IptKey key) {} + public void SendMouseMove(int x, int y) {} + public void SendMouseDown(IptMouseBtn btn) {} + public void windowActivated(WindowEvent e) {} + public void windowClosed(WindowEvent e) {} + public void windowClosing(WindowEvent e) {host.DisposeCbk();} + public void windowDeactivated(WindowEvent e) {} + public void windowDeiconified(WindowEvent e) {host.SizeChangedCbk();} + public void windowIconified(WindowEvent e) {host.SizeChangedCbk();} + public void windowOpened(WindowEvent e) {whenLoadedCmd.Exec();} + @Override public void processKeyEvent(KeyEvent e) {if (GxwCbkHost_.ExecKeyEvent(host, e)) super.processKeyEvent(e);} + @Override public void processMouseEvent(MouseEvent e) {if (GxwCbkHost_.ExecMouseEvent(host, e)) super.processMouseEvent(e);} + @Override public void processMouseWheelEvent(MouseWheelEvent e) {if (GxwCbkHost_.ExecMouseWheel(host, e)) super.processMouseWheelEvent(e);} + @Override public void processMouseMotionEvent(MouseEvent e) {if (host.MouseMoveCbk(IptEvtDataMouse.new_(IptMouseBtn_.None, IptMouseWheel_.None, e.getX(), e.getY()))) super.processMouseMotionEvent(e);} + @Override public void paint(Graphics g) { + if (host.PaintCbk(PaintArgs.new_(GfxAdpBase.new_((Graphics2D)g), RectAdp_.Zero))) // ClipRect not used by any clients; implement when necessary + super.paint(g); + } + public void EnableDoubleBuffering() {} + public void TaskbarVisible_set(boolean val) {} public void TaskbarParkingWindowFix(GxwElem form) {} + public static GxwWin_lang new_() { + GxwWin_lang rv = new GxwWin_lang(); + rv.ctor_GxwForm(); + return rv; + } GxwWin_lang() {} + void ctor_GxwForm() { + ctrlMgr = GxwCore_form.new_(this); + this.setLayout(null); // use gfui layout + this.ctrlMgr.BackColor_set(ColorAdp_.White); // default form backColor to white + this.setUndecorated(true); // remove icon, titleBar, minimize, maximize, close, border + this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // JAVA: cannot cancel alt+f4; set Close to noop, and manually control closing by calling this.CloseForm + enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK); + this.addWindowListener(this); + GxwBoxListener lnr = new GxwBoxListener(this); + this.addComponentListener(lnr); + this.addFocusListener(lnr); + } +} +class GxwWin_jdialog extends JDialog implements GxwWin, WindowListener { + public void ShowWin() {this.setVisible(true);} + public void HideWin() {this.setVisible(false);} + public void CloseWin() {this.dispose();}//this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); this.dispose(); + public boolean Maximized() {return false;} public void Maximized_(boolean v) {} + public boolean Minimized() {return false;} public void Minimized_(boolean v) {} //this.setState(Frame.ICONIFIED); + public boolean Pin() {return pin;} public void Pin_set(boolean val) { + this.setAlwaysOnTop(val); pin = val;} boolean pin = false; + public IconAdp IconWin() {return icon;} IconAdp icon; + public void IconWin_set(IconAdp i) { + if (i == null) return; + icon = i; + this.setIconImage(i.XtoImage()); + } + public void OpenedCmd_set(Gfo_invk_cmd v) {whenLoadedCmd = v;} Gfo_invk_cmd whenLoadedCmd = Gfo_invk_cmd.Noop; + public GxwCore_base Core() {return ctrlMgr;} GxwCore_base ctrlMgr; + public GxwCbkHost Host() {return host;} public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host = GxwCbkHost_.Null; + public String TextVal() {return "";} public void TextVal_set(String v) {} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return this;} + public void SendKeyDown(IptKey key) {} + public void SendMouseMove(int x, int y) {} + public void SendMouseDown(IptMouseBtn btn) {} + public void windowActivated(WindowEvent e) {} + public void windowClosed(WindowEvent e) {} + public void windowClosing(WindowEvent e) {host.DisposeCbk();} + public void windowDeactivated(WindowEvent e) {} + public void windowDeiconified(WindowEvent e) {host.SizeChangedCbk();} + public void windowIconified(WindowEvent e) {host.SizeChangedCbk();} + public void windowOpened(WindowEvent e) {whenLoadedCmd.Exec();} + @Override public void processKeyEvent(KeyEvent e) {if (GxwCbkHost_.ExecKeyEvent(host, e)) super.processKeyEvent(e);} + @Override public void processMouseEvent(MouseEvent e) {if (GxwCbkHost_.ExecMouseEvent(host, e)) super.processMouseEvent(e);} + @Override public void processMouseWheelEvent(MouseWheelEvent e) {if (GxwCbkHost_.ExecMouseWheel(host, e)) super.processMouseWheelEvent(e);} + @Override public void processMouseMotionEvent(MouseEvent e) {if (host.MouseMoveCbk(IptEvtDataMouse.new_(IptMouseBtn_.None, IptMouseWheel_.None, e.getX(), e.getY()))) super.processMouseMotionEvent(e);} + @Override public void paint(Graphics g) { + if (host.PaintCbk(PaintArgs.new_(GfxAdpBase.new_((Graphics2D)g), RectAdp_.Zero))) // ClipRect not used by any clients; implement when necessary + super.paint(g); + } + public void EnableDoubleBuffering() {} + public void TaskbarVisible_set(boolean val) {} public void TaskbarParkingWindowFix(GxwElem form) {} + public static GxwWin new_(GxwWin owner) { + Window ownerWindow = owner instanceof Window ? (Window)owner : null; + GxwWin_jdialog rv = new GxwWin_jdialog(ownerWindow); + rv.ctor_GxwForm(); + return rv; + } GxwWin_jdialog(Window owner) {super(owner);} + void ctor_GxwForm() { + ctrlMgr = GxwCore_form.new_(this); + this.setLayout(null); // use gfui layout + this.ctrlMgr.BackColor_set(ColorAdp_.White); // default form backColor to white + this.setUndecorated(true); // remove icon, titleBar, minimize, maximize, close, border + this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // JAVA: cannot cancel alt+f4; set CloseOp to noop, and manually control closing by calling this.CloseForm + enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK); + this.addWindowListener(this); + GxwBoxListener lnr = new GxwBoxListener(this); + this.addComponentListener(lnr); + this.addFocusListener(lnr); + } +} +class GxwWin_jwindow extends JWindow implements GxwWin, WindowListener { + public void ShowWin() {this.setVisible(true);} + public void ShowWinModal() {} + public void HideWin() {this.setVisible(false);} + public void CloseWin() {} //this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); this.dispose(); + public boolean Maximized() {return false;} public void Maximized_(boolean v) {} + public boolean Minimized() {return false;} public void Minimized_(boolean v) {} + public boolean Pin() {return pin;} public void Pin_set(boolean val) {this.setAlwaysOnTop(val); pin = val;} boolean pin = false; + public IconAdp IconWin() {return icon;} IconAdp icon; + public void IconWin_set(IconAdp i) { + if (i == null) return; + icon = i; + this.setIconImage(i.XtoImage()); + } + public void OpenedCmd_set(Gfo_invk_cmd v) {whenLoadedCmd = v;} Gfo_invk_cmd whenLoadedCmd = Gfo_invk_cmd.Noop; + public GxwCore_base Core() {return ctrlMgr;} GxwCore_base ctrlMgr; + public GxwCbkHost Host() {return host;} public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host = GxwCbkHost_.Null; + public String TextVal() {return "";} public void TextVal_set(String v) {}// this.setTitle(v); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return this;} + public void SendKeyDown(IptKey key) {} + public void SendMouseMove(int x, int y) {} + public void SendMouseDown(IptMouseBtn btn) {} + public void windowActivated(WindowEvent e) {} + public void windowClosed(WindowEvent e) {} + public void windowClosing(WindowEvent e) {host.DisposeCbk();} + public void windowDeactivated(WindowEvent e) {} + public void windowDeiconified(WindowEvent e) {host.SizeChangedCbk();} + public void windowIconified(WindowEvent e) {host.SizeChangedCbk();} + public void windowOpened(WindowEvent e) {whenLoadedCmd.Exec();} + @Override public void processKeyEvent(KeyEvent e) {if (GxwCbkHost_.ExecKeyEvent(host, e)) super.processKeyEvent(e);} + @Override public void processMouseEvent(MouseEvent e) {if (GxwCbkHost_.ExecMouseEvent(host, e)) super.processMouseEvent(e);} + @Override public void processMouseWheelEvent(MouseWheelEvent e) {if (GxwCbkHost_.ExecMouseWheel(host, e)) super.processMouseWheelEvent(e);} + @Override public void processMouseMotionEvent(MouseEvent e) {if (host.MouseMoveCbk(IptEvtDataMouse.new_(IptMouseBtn_.None, IptMouseWheel_.None, e.getX(), e.getY()))) super.processMouseMotionEvent(e);} + @Override public void paint(Graphics g) { + if (host.PaintCbk(PaintArgs.new_(GfxAdpBase.new_((Graphics2D)g), RectAdp_.Zero))) // ClipRect not used by any clients; implement when necessary + super.paint(g); + } + public void EnableDoubleBuffering() {} + public void TaskbarVisible_set(boolean val) {} public void TaskbarParkingWindowFix(GxwElem form) {} + public static GxwWin new_(GxwWin owner) { +// Window ownerWindow = owner instanceof Window ? (Window)owner : null; +// GxwWin_jwindow rv = new GxwWin_jwindow((Window)owner); + GxwWin_jwindow rv = new GxwWin_jwindow(); + rv.ctor_GxwForm(); +// rv.setUndecorated(true); + return rv; + } GxwWin_jwindow(Window owner) {super(owner);} + GxwWin_jwindow() {} + public static GxwWin owner_(GxwWin owner) { + GxwWin_jwindow rv = new GxwWin_jwindow((Window)owner); + rv.setFocusable(true); + rv.setFocusableWindowState(true); + rv.ctor_GxwForm(); + return rv; + } + public void ctor_GxwForm() { + ctrlMgr = GxwCore_form.new_(this); + this.setLayout(null); // use gfui layout + this.ctrlMgr.BackColor_set(ColorAdp_.White); // default form backColor to white +// this.setUndecorated(true); // remove icon, titleBar, minimize, maximize, close, border +// this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // JAVA: cannot cancel alt+f4; set CloseOp to noop, and manually control closing by calling this.CloseForm + enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK); + this.addWindowListener(this); + GxwBoxListener lnr = new GxwBoxListener(this); + this.addComponentListener(lnr); + this.addFocusListener(lnr); + } +} +class GxwCore_form extends GxwCore_lang { + @Override public void Zorder_front() { + Window frame = (Window)this.control; + frame.toFront(); + } + @Override public void Zorder_back() { + Window frame = (Window)this.control; + frame.toBack(); + } + public static GxwCore_form new_(Component control) { + GxwCore_form rv = new GxwCore_form(); + rv.control = control; + return rv; + } GxwCore_form() {} +} + +class GxwElemFactory_swt extends GxwElemFactory_base { + public GxwElemFactory_swt(org.eclipse.swt.widgets.Display display) { + this.display = display; + } Display display; + public GxwElem control_() {return null;} + public GxwWin win_app_() { + return new Swt_win(display); + } + public GxwWin win_tool_(Keyval_hash ctorArgs) { + return null; + } + public GxwWin win_toaster_(Keyval_hash ctorArgs) { + return null; + } + public GxwElem lbl_() {return null;} + public GxwTextFld text_fld_() {return null;} + public GxwTextFld text_memo_() {return null;} + public GxwTextHtml text_html_() {return null;} + public GxwCheckListBox checkListBox_(Keyval_hash ctorArgs) {throw Err_.new_unimplemented();} + public GxwComboBox comboBox_() {return null;} + public GxwListBox listBox_() {return null;} +} +//#} \ No newline at end of file diff --git a/150_gfui/src/gplx/gfui/controls/gxws/Gxw_grp.java b/150_gfui/src/gplx/gfui/controls/gxws/Gxw_grp.java index a27517de8..8e94a2690 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/Gxw_grp.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/Gxw_grp.java @@ -13,3 +13,7 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; import gplx.gfui.controls.standards.*; +public interface Gxw_grp extends GxwElem { +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/Gxw_html.java b/150_gfui/src/gplx/gfui/controls/gxws/Gxw_html.java index a27517de8..c0e62367e 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/Gxw_html.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/Gxw_html.java @@ -13,3 +13,18 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public interface Gxw_html extends GxwElem { + void Html_doc_html_load_by_mem(String html); + void Html_doc_html_load_by_url(Io_url path, String html); + byte Html_doc_html_load_tid(); void Html_doc_html_load_tid_(byte v); + void Html_js_enabled_(boolean v); + String Html_js_eval_proc_as_str (String name, Object... args); + boolean Html_js_eval_proc_as_bool (String name, Object... args); + String Html_js_eval_script (String script); + Object Html_js_eval_script_as_obj (String script); + void Html_js_cbks_add (String js_func_name, Gfo_invk invk); + String Html_js_send_json (String name, String data); + void Html_invk_src_(Gfo_evt_itm v); + void Html_dispose(); +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/Gxw_html_load_tid_.java b/150_gfui/src/gplx/gfui/controls/gxws/Gxw_html_load_tid_.java index a27517de8..a20406e33 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/Gxw_html_load_tid_.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/Gxw_html_load_tid_.java @@ -13,3 +13,12 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public class Gxw_html_load_tid_ { + public static final byte Tid_mem = 0, Tid_url = 1; + public static byte Xto_tid(String s) { + if (String_.Eq(s, "mem")) return Tid_mem; + else if (String_.Eq(s, "url")) return Tid_url; + else throw Err_.new_unimplemented(); + } +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/Gxw_tab_itm.java b/150_gfui/src/gplx/gfui/controls/gxws/Gxw_tab_itm.java index a27517de8..d48dad8f3 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/Gxw_tab_itm.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/Gxw_tab_itm.java @@ -13,3 +13,11 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; +public interface Gxw_tab_itm extends GxwElem { + void Subs_add(GfuiElem sub); + Gfui_tab_itm_data Tab_data(); + String Tab_name(); void Tab_name_(String v); + String Tab_tip_text(); void Tab_tip_text_(String v); +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/Gxw_tab_mgr.java b/150_gfui/src/gplx/gfui/controls/gxws/Gxw_tab_mgr.java index a27517de8..b386ad232 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/Gxw_tab_mgr.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/Gxw_tab_mgr.java @@ -13,3 +13,21 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; import gplx.gfui.controls.standards.*; +public interface Gxw_tab_mgr extends GxwElem { + ColorAdp Btns_selected_background(); void Btns_selected_background_(ColorAdp v); + ColorAdp Btns_selected_foreground(); void Btns_selected_foreground_(ColorAdp v); + ColorAdp Btns_unselected_background(); void Btns_unselected_background_(ColorAdp v); + ColorAdp Btns_unselected_foreground(); void Btns_unselected_foreground_(ColorAdp v); + + int Btns_height(); void Btns_height_(int v); + boolean Btns_place_on_top(); void Btns_place_on_top_(boolean v); + boolean Btns_curved(); void Btns_curved_(boolean v); + boolean Btns_close_visible(); void Btns_close_visible_(boolean v); + boolean Btns_unselected_close_visible(); void Btns_unselected_close_visible_(boolean v); + Gxw_tab_itm Tabs_add(Gfui_tab_itm_data tab_data); + void Tabs_select_by_idx(int i); + void Tabs_close_by_idx(int i); + void Tabs_switch(int src, int trg); +} diff --git a/150_gfui/src/gplx/gfui/controls/gxws/MockForm.java b/150_gfui/src/gplx/gfui/controls/gxws/MockForm.java index a27517de8..b7596b7f3 100644 --- a/150_gfui/src/gplx/gfui/controls/gxws/MockForm.java +++ b/150_gfui/src/gplx/gfui/controls/gxws/MockForm.java @@ -13,3 +13,18 @@ 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.gfui.controls.gxws; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.imgs.*; +public class MockForm extends GxwElem_mock_base implements GxwWin { + public IconAdp IconWin() {return null;} public void IconWin_set(IconAdp v) {} + public void ShowWin() {} + public void CloseWin() {} + public void HideWin() {} + public boolean Maximized() {return false;} public void Maximized_(boolean v) {} + public boolean Minimized() {return false;} public void Minimized_(boolean v) {} + public boolean Pin() {return pin;} public void Pin_set(boolean val) {pin = val;} private boolean pin; + public void OpenedCmd_set(Gfo_invk_cmd v) {} + public void TaskbarVisible_set(boolean val) {} + public void TaskbarParkingWindowFix(GxwElem form) {} + public static final MockForm Instance = new MockForm(); MockForm() {} +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/GfuiBtn.java b/150_gfui/src/gplx/gfui/controls/standards/GfuiBtn.java index a27517de8..dd89eb192 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/GfuiBtn.java +++ b/150_gfui/src/gplx/gfui/controls/standards/GfuiBtn.java @@ -13,3 +13,60 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.langs.gfs.*; +import gplx.gfui.draws.*; import gplx.gfui.gfxs.*; import gplx.gfui.imgs.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.windows.*; +public class GfuiBtn extends GfuiElemBase { + @Override public boolean Click_able() {return true;} + @Override public void Click() {GfuiBtn.DoThis(this, clickMsg, clickInvkCmd);} + public GfuiBtn Click_msg_(GfoMsg v) {clickMsg = v; return this;} GfoMsg clickMsg; + public GfuiBtn Click_invk(Gfo_invk_cmd v) {clickInvkCmd = v; return this;} Gfo_invk_cmd clickInvkCmd; + @Override public boolean PaintCbk(PaintArgs args) { + super.PaintCbk(args); + if (this.Focus_has()) focusBorder.DrawData(args.Graphics()); + this.TextMgr().DrawData(args.Graphics()); + return true; + } GfuiBorderMgr focusBorder; + @Override public boolean SizeChangedCbk() {super.SizeChangedCbk(); GfuiBtn_.FocusBorderRect_set(focusBorder, this); this.Redraw(); return true;} + @Override public boolean FocusGotCbk() {super.FocusGotCbk(); this.Redraw(); return true;} // Redraw for focusBorder + @Override public boolean FocusLostCbk() {super.FocusLostCbk(); this.Redraw(); return true;} // Redraw for focusBorder + public ImageAdp Btn_img() { + Object o = Gfo_invk_.Invk_by_key(UnderElem(), Invk_btn_img); + return o == UnderElem() ? null : (ImageAdp)o; // NOTE: lgc guard + } public GfuiBtn Btn_img_(ImageAdp v) {Gfo_invk_.Invk_by_val(UnderElem(), Invk_btn_img_, v); return this;} + @Override public GxwElem UnderElem_make(Keyval_hash ctorArgs) {return GxwElemFactory_.Instance.control_();} + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + focusBorder = GfuiBorderMgr.new_().All_(PenAdp_.new_(ColorAdp_.Gray, 1)); + super.ctor_GfuiBox_base(ctorArgs); + this.TextMgr().AlignH_(GfuiAlign_.Mid); + this.Border().All_(PenAdp_.black_()); this.Border().Bounds_sync(RectAdp_.size_(this.Size().Op_subtract(1))); + GfuiBtn_.FocusBorderRect_set(focusBorder, this); + Inject_(GfuiBtnClickBnd.Instance); + Inject_(GfuiFocusXferBnd.Instance); + this.CustomDraw_set(true); + } + @Override public void ctor_kit_GfuiElemBase(Gfui_kit kit, String key, GxwElem underElem, Keyval_hash ctorArgs) { + this.kit = kit; + super.ctor_kit_GfuiElemBase(kit, key, underElem, ctorArgs); + focusBorder = GfuiBorderMgr.new_().All_(PenAdp_.new_(ColorAdp_.Gray, 1)); + Inject_(GfuiBtnClickBnd.Instance); + Inject_(GfuiFocusXferBnd.Instance); + } Gfui_kit kit; + @gplx.Internal protected static void DoThis(GfuiElem click, GfoMsg clickMsg, Gfo_invk_cmd clickInvkCmd) { + try { + if (clickInvkCmd != null) { + GfsCtx ctx = GfsCtx.new_().MsgSrc_(click); + clickInvkCmd.Exec_by_ctx(ctx, GfoMsg_.Null); + } + else if (clickMsg != null && clickMsg != GfoMsg_.Null) { + GfsCtx ctx = GfsCtx.new_().MsgSrc_(click); + if (String_.Eq(clickMsg.Key(), ".")) + GfsCore.Instance.ExecOne_to(ctx, click, clickMsg.Subs_getAt(0)); + else + GfsCore.Instance.ExecOne(ctx, clickMsg); + } + } catch (Exception e) {GfuiEnv_.ShowMsg(Err_.Message_gplx_full(e));} + } + public static final String Invk_btn_img = "btn_img", Invk_btn_img_ = "btn_img_"; + public static final String CFG_border_on_ = "border_on_"; +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/GfuiBtnClickBnd.java b/150_gfui/src/gplx/gfui/controls/standards/GfuiBtnClickBnd.java index a27517de8..5ceef749b 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/GfuiBtnClickBnd.java +++ b/150_gfui/src/gplx/gfui/controls/standards/GfuiBtnClickBnd.java @@ -13,3 +13,28 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.ipts.*; import gplx.gfui.controls.elems.*; +import gplx.core.interfaces.*; +class GfuiBtnClickBnd implements InjectAble, Gfo_invk { + public void Inject(Object owner) { + GfuiElem elem = GfuiElem_.cast(owner); + IptBnd_.cmd_(IptCfg_.Null, elem, GfuiElemKeys.ActionExec_cmd, IptKey_.Enter, IptKey_.Space); + IptBnd_.cmd_(IptCfg_.Null, elem, GfuiElemKeys.Focus_cmd, IptMouseBtn_.Left); + IptBnd_.ipt_to_(IptCfg_.Null, elem, this, ExecMouseUp_cmd, IptEventType_.MouseUp, IptMouseBtn_.Left); + } + void ExecMouseUp(IptEventData iptData) { + GfuiElem elem = GfuiElem_.cast(iptData.Sender()); + int x = iptData.MousePos().X(), y = iptData.MousePos().Y(); + SizeAdp buttonSize = elem.Size(); + if ( x >= 0 && x <= buttonSize.Width() + && y >= 0 && y <= buttonSize.Height()) + elem.Click(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, ExecMouseUp_cmd)) ExecMouseUp(IptEventData.ctx_(ctx, m)); + else return Gfo_invk_.Rv_unhandled; + return this; + } static final String ExecMouseUp_cmd = "ExecMouseUp"; + public static final GfuiBtnClickBnd Instance = new GfuiBtnClickBnd(); GfuiBtnClickBnd() {} +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/GfuiBtn_.java b/150_gfui/src/gplx/gfui/controls/standards/GfuiBtn_.java index a27517de8..07286019c 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/GfuiBtn_.java +++ b/150_gfui/src/gplx/gfui/controls/standards/GfuiBtn_.java @@ -13,3 +13,34 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.imgs.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; +public class GfuiBtn_ { + public static GfuiBtn as_(Object obj) {return obj instanceof GfuiBtn ? (GfuiBtn)obj : null;} + public static GfuiBtn cast(Object obj) {try {return (GfuiBtn)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, GfuiBtn.class, obj);}} + + public static GfuiBtn msg_(String key, GfuiElem owner, GfoMsg msg) { + GfuiBtn rv = new_(key); rv.Owner_(owner); + rv.Click_msg_(msg); + return rv; + } + public static GfuiBtn invk_(String key, GfuiElem owner, Gfo_invk invk, String m) { + GfuiBtn rv = new_(key); rv.Owner_(owner); + rv.Click_invk(Gfo_invk_cmd.New_by_key(invk, m)); + return rv; + } + public static GfuiBtn kit_(Gfui_kit kit, String key, GxwElem elm, Keyval_hash ctorArgs) { + GfuiBtn rv = new GfuiBtn(); + rv.ctor_kit_GfuiElemBase(kit, key, elm, ctorArgs); + return rv; + } + public static GfuiBtn new_(String key) { + GfuiBtn rv = new GfuiBtn(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_true_()); + rv.Key_of_GfuiElem_(key); + return rv; + } + @gplx.Internal protected static void FocusBorderRect_set(GfuiBorderMgr borderMgr, GfuiElem elem) { + borderMgr.Bounds_sync(RectAdp_.new_(3, 3, elem.Width() - 6, elem.Height() - 6)); + } +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/GfuiChkBox.java b/150_gfui/src/gplx/gfui/controls/standards/GfuiChkBox.java index a27517de8..0f6a6d133 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/GfuiChkBox.java +++ b/150_gfui/src/gplx/gfui/controls/standards/GfuiChkBox.java @@ -13,3 +13,48 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; import gplx.gfui.gfxs.*; import gplx.gfui.imgs.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.windows.*; +public class GfuiChkBox extends GfuiElemBase { + public boolean Val() {return val;} private boolean val; + public void Val_set(boolean val) { + this.val = val; + Gfo_evt_mgr_.Pub_val(this, "Check_end", val); + GfuiBtn.DoThis(this, clickMsg, clickInvkCmd); + } + public void Val_sync(boolean val) { + this.val = val;//(boolean)v; // NOTE: do not resend message + this.Redraw(); + } + @gplx.Internal protected void Click_msg_(GfoMsg v) {clickMsg = v;} GfoMsg clickMsg; + @gplx.Internal protected GfuiChkBox Click_invk(Gfo_invk_cmd v) {clickInvkCmd = v; return this;} Gfo_invk_cmd clickInvkCmd; + public GfuiBorderMgr FocusBorder() {return focusBorder;} GfuiBorderMgr focusBorder = GfuiBorderMgr.new_(); + @Override public boolean Click_able() {return true;} + @Override public void Click() { + Val_set(!val); + this.Redraw(); + } + public GfuiAlign AlignH() {return alignH;} public void AlignH_set(GfuiAlign v) {alignH = v;} GfuiAlign alignH = GfuiAlign_.Mid; + public GfuiAlign AlignV() {return alignV;} public void AlignV_set(GfuiAlign v) {alignV = v;} GfuiAlign alignV = GfuiAlign_.Mid; + public PointAdp ChkAlignCustom() {return chkAlignCustom;} public void ChkAlignCustom_set(PointAdp val) {chkAlignCustom = val;} PointAdp chkAlignCustom = PointAdp_.Zero; + @gplx.Internal protected PenAdp ChkPen() {return chkPen;} PenAdp chkPen = PenAdp_.new_(ColorAdp_.Black); + @Override public boolean PaintCbk(PaintArgs args) { + super.PaintCbk(args); + GfuiChkBox_.DrawCheckBox(this, args.Graphics()); + return true; + } + @Override public boolean SizeChangedCbk() { + boolean rv = super.SizeChangedCbk(); + this.Redraw(); + return rv; + } + @Override public boolean FocusGotCbk() {super.FocusGotCbk(); this.Redraw(); return true;} // Redraw for focusBorder + @Override public boolean FocusLostCbk() {super.FocusLostCbk(); this.Redraw(); return true;} // Redraw for focusBorder + @Override public GxwElem UnderElem_make(Keyval_hash ctorArgs) {return GxwElemFactory_.Instance.lbl_();} + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + super.ctor_GfuiBox_base(ctorArgs); + focusBorder.All_(PenAdp_.new_(ColorAdp_.Gray, 1)); + Inject_(GfuiFocusXferBnd.Instance).Inject_(GfuiBtnClickBnd.Instance); + this.CustomDraw_set(true); + } +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/GfuiChkBox_.java b/150_gfui/src/gplx/gfui/controls/standards/GfuiChkBox_.java index a27517de8..5602559ca 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/GfuiChkBox_.java +++ b/150_gfui/src/gplx/gfui/controls/standards/GfuiChkBox_.java @@ -13,3 +13,60 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.gfxs.*; import gplx.gfui.controls.elems.*; +public class GfuiChkBox_ { + public static GfuiChkBox as_(Object obj) {return obj instanceof GfuiChkBox ? (GfuiChkBox)obj : null;} + public static GfuiChkBox cast(Object obj) {try {return (GfuiChkBox)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, GfuiChkBox.class, obj);}} + @gplx.Internal protected static GfuiChkBox new_() { + GfuiChkBox rv = new GfuiChkBox(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_true_()); + return rv; + } + + public static GfuiChkBox msg_(String key, GfuiElem owner, String text, GfoMsg msg) { + GfuiChkBox rv = new_(); rv.Owner_(owner, key).Text_any_(text); + rv.Click_msg_(msg); + return rv; + } + public static GfuiChkBox invk_(String key, GfuiElem owner, String text, Gfo_invk invk, String m) { + GfuiChkBox rv = new_(); rv.Owner_(owner, key).Text_any_(text); + rv.Click_invk(Gfo_invk_cmd.New_by_key(invk, m)); + return rv; + } + public static GfuiChkBox noop_(String key, GfuiElem owner, String text) { + GfuiChkBox rv = new_(); rv.Owner_(owner, key); rv.Text_any_(text); + return rv; + } + public static RectAdp CboxRect(GfuiChkBox box) { + SizeAdp size = SizeAdp_.new_(12, 12); + PointAdp pos = GfuiAlign_.CalcInsideOf(box.AlignH(), box.AlignV(), size, box.Size(), box.ChkAlignCustom()); + return RectAdp_.vector_(pos.Op_add(0, 0), size); + } + public static void DrawCheckBox(GfuiChkBox box, GfxAdp gfx) { + RectAdp cboxRect = CboxRect(box); + box.TextMgr().AlignH_(box.AlignH()).AlignV_(box.AlignV()); + RectAdpF textRect = box.TextMgr().TextRect_calc(gfx); + + int w = cboxRect.Width() + 4 + (int)textRect.Width(); + int h = (int)textRect.Height(); // assume textRect.Height is >= cboxRect.Height + SizeAdp size = SizeAdp_.new_(w, h); + PointAdp pos = GfuiAlign_.CalcInsideOf(box.AlignH(), box.AlignV(), size, box.Size(), PointAdp_.Zero); + cboxRect = RectAdp_.vector_(pos.Op_add(0, 1), cboxRect.Size()); + textRect = RectAdpF.new_(pos.X() + cboxRect.Width() + 4, textRect.Y(), textRect.Width(), textRect.Height()); + box.TextMgr().TextRect_set(textRect); + box.TextMgr().DrawData(gfx); + + gfx.DrawRect(box.ChkPen(), cboxRect.X(), cboxRect.Y(), cboxRect.Width(), cboxRect.Height()); + if (box.Val()) { + gfx.DrawLine(box.ChkPen(), cboxRect.CornerTL().Op_add(3), cboxRect.CornerBR().Op_add(-3)); + gfx.DrawLine(box.ChkPen(), cboxRect.CornerBL().Op_add(3, -3), cboxRect.CornerTR().Op_add(-3, 3)); + } + if (box.Focus_has()) { + // -1 so border does not start right on top of text; RoundUp to give a little more space to edges + // +2 so that focusRect does not overlap mnemonic (in C#) + box.FocusBorder().Bounds_sync(RectAdp_.new_((int)textRect.X() - 1, (int)cboxRect.Y(), Float_.RoundUp(textRect.Width()), Float_.RoundUp(cboxRect.Height()))); + box.FocusBorder().DrawData(gfx); + } + } +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/GfuiComboBox.java b/150_gfui/src/gplx/gfui/controls/standards/GfuiComboBox.java index a27517de8..342f59d1f 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/GfuiComboBox.java +++ b/150_gfui/src/gplx/gfui/controls/standards/GfuiComboBox.java @@ -13,3 +13,59 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; +import gplx.gfui.kits.core.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; +public class GfuiComboBox extends GfuiElemBase { + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + super.ctor_GfuiBox_base(ctorArgs); + this.combo = (GxwComboBox)this.UnderElem(); + } + @Override public GxwElem UnderElem_make(Keyval_hash ctorArgs) {return GxwElemFactory_.Instance.comboBox_();} private GxwComboBox combo; + public ColorAdp Border_color() {return combo.Border_color();} public void Border_color_(ColorAdp v) {combo.Border_color_(v);} + public int SelBgn() {return combo.SelBgn();} public void SelBgn_set(int v) {combo.SelBgn_set(v); Gfo_evt_mgr_.Pub(this, Evt__selection_start_changed);} + public int SelLen() {return combo.SelLen();} public void SelLen_set(int v) {combo.SelLen_set(v);} + public void Sel_(int bgn, int len) {combo.Sel_(bgn, len);} + public Object SelectedItm() {return combo.SelectedItm();} public void SelectedItm_set(Object v) {combo.SelectedItm_set(v);} + @Override public GfuiElem BackColor_(ColorAdp v) { + super.BackColor_(v); + combo.Items__backcolor_(v); + return this; + } + @Override public GfuiElem ForeColor_(ColorAdp v) { + super.ForeColor_(v); + combo.Items__forecolor_(v); + return this; + } + public String[] DataSource_as_str_ary() {return combo.DataSource_as_str_ary();} + public void DataSource_set(Object... ary) {combo.DataSource_set(ary);} + public String Text_fallback() {return combo.Text_fallback();} + public void Text_fallback_(String v) {combo.Text_fallback_(v);} + public int List_sel_idx() {return combo.List_sel_idx();} + public void List_sel_idx_(int v) {combo.List_sel_idx_(v);} + public boolean List_visible() {return combo.List_visible();} + public void List_visible_(boolean v) {combo.List_visible_(v);} + public void Items__update(String[] ary) {combo.Items__update(ary);} + public void Items__size_to_fit(int len) {combo.Items__size_to_fit(len);} + public void Items__visible_rows_(int v) {combo.Items__visible_rows_(v);} + public void Items__jump_len_(int v) {combo.Items__jump_len_(v);} + public void Items__backcolor_(ColorAdp v) {combo.Items__backcolor_(v);} + public void Items__forecolor_(ColorAdp v) {combo.Items__forecolor_(v);} + public void Margins_set(int left, int top, int right, int bot) {combo.Margins_set(left, top, right, bot);} + public static GfuiComboBox new_() { + GfuiComboBox rv = new GfuiComboBox(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_true_()); + return rv; + } + public static GfuiComboBox kit_(Gfui_kit kit, String key, GxwElem elm, Keyval_hash ctorArgs) { + GfuiComboBox rv = new GfuiComboBox(); + rv.ctor_kit_GfuiElemBase(kit, key, elm, ctorArgs); + rv.combo = (GxwComboBox)elm; + return rv; + } + public static final String + Evt__selected_changed = "Selected_changed" + , Evt__selected_accepted = "Selected_accepted" + , Evt__selection_start_changed = "SelectionStartChanged" + ; +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/GfuiLbl.java b/150_gfui/src/gplx/gfui/controls/standards/GfuiLbl.java index a27517de8..17eabc323 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/GfuiLbl.java +++ b/150_gfui/src/gplx/gfui/controls/standards/GfuiLbl.java @@ -13,3 +13,26 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.gfxs.*; import gplx.gfui.imgs.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; +public class GfuiLbl extends GfuiElemBase { // standard label does not support tooltips + @Override public void Click() { + int focusOrder = this.OwnerElem().SubElems().IndexOfA(this); + GfuiElem focusNext = this.OwnerElem().SubElems().Get_at(focusOrder + 1); // FIXME: incorporate into new FocusOrder + focusNext.Focus(); + } + @Override public boolean PaintCbk(PaintArgs args) { + super.PaintCbk(args); + this.TextMgr().DrawData(args.Graphics()); + return true; + } + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + super.ctor_GfuiBox_base(ctorArgs); + this.CustomDraw_set(true); + } + @Override public void ctor_kit_GfuiElemBase(Gfui_kit kit, String key, GxwElem underElem, Keyval_hash ctorArgs) { + super.ctor_kit_GfuiElemBase(kit, key, underElem, ctorArgs); + this.CustomDraw_set(true); + } + @Override public GxwElem UnderElem_make(Keyval_hash ctorArgs) {return GxwElemFactory_.Instance.lbl_();} +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/GfuiLbl_.java b/150_gfui/src/gplx/gfui/controls/standards/GfuiLbl_.java index a27517de8..58fcf6f4f 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/GfuiLbl_.java +++ b/150_gfui/src/gplx/gfui/controls/standards/GfuiLbl_.java @@ -13,3 +13,38 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; +public class GfuiLbl_ { + public static GfuiLbl sub_(String key, GfuiElem owner) { + GfuiLbl rv = new_(); + rv.Owner_(owner, key); // must be after ctor, else "Error creating window handle" + rv.TextMgr().AlignH_(GfuiAlign_.Mid); + return rv; + } + public static GfuiLbl kit_(Gfui_kit kit, String key, GxwElem elm, Keyval_hash ctorArgs) { + GfuiLbl rv = new GfuiLbl(); + rv.ctor_kit_GfuiElemBase(kit, key, elm, ctorArgs); + return rv; + } + public static GfuiLbl prefix_(String key, GfuiElem owner, String text) { + GfuiLbl rv = sub_(key, owner); + rv.Text_(text); + rv.TextMgr().AlignH_(GfuiAlign_.Left); + return rv; + } + public static GfuiLbl menu_(String key, GfuiElem owner, String text, String tipText) { + GfuiLbl rv = sub_(key, owner); + rv.Text_(text); + rv.TextMgr().AlignH_(GfuiAlign_.Mid); + rv.TipText_(tipText); + rv.Border().All_(PenAdp_.black_()); + return rv; + } + public static final String Text_Null = null; + @gplx.Internal protected static GfuiLbl new_() { + GfuiLbl rv = new GfuiLbl(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_false_()); + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/GfuiListBox.java b/150_gfui/src/gplx/gfui/controls/standards/GfuiListBox.java index a27517de8..dee4fca43 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/GfuiListBox.java +++ b/150_gfui/src/gplx/gfui/controls/standards/GfuiListBox.java @@ -13,3 +13,30 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.core.lists.*; /*EnumerAble*/ +import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; +public class GfuiListBox extends GfuiElemBase { + @Override public GxwElem UnderElem_make(Keyval_hash ctorArgs) {return GxwElemFactory_.Instance.listBox_();} + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + super.ctor_GfuiBox_base(ctorArgs); + this.listBox = (GxwListBox)UnderElem(); + } + public void Items_Add(Object o) {listBox.Items_Add(o);} + public void Items_Clear() {listBox.Items_Clear();} + public Object Items_SelObj() {return listBox.Items_SelObj();} + public void BindData(EnumerAble enumerable) { + for (Object item : enumerable) + this.listBox.Items_Add(item.toString()); + } + public int Items_Count() {return listBox.Items_Count();} + public int Items_SelIdx() {return listBox.Items_SelIdx();} public void Items_SelIdx_set(int v) {listBox.Items_SelIdx_set(v);} + + GxwListBox listBox; + public static GfuiListBox new_() { + GfuiListBox rv = new GfuiListBox(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_true_()); + return rv; + } +} + diff --git a/150_gfui/src/gplx/gfui/controls/standards/GfuiTextBox.java b/150_gfui/src/gplx/gfui/controls/standards/GfuiTextBox.java index a27517de8..e3e11b6b9 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/GfuiTextBox.java +++ b/150_gfui/src/gplx/gfui/controls/standards/GfuiTextBox.java @@ -13,3 +13,56 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; +import gplx.gfui.kits.core.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; +public class GfuiTextBox extends GfuiElemBase { + public static final String SelectionStartChanged_evt = "SelectionStartChanged"; + + public boolean Border_on() {return textBox.Border_on();} public void Border_on_(boolean v) {BorderOn_set(v);} + public ColorAdp Border_color() {return textBox.Border_color();} public void Border_color_(ColorAdp v) {textBox.Border_color_(v);} + public int SelBgn() {return textBox.SelBgn();} public void SelBgn_set(int v) {textBox.SelBgn_set(v); Gfo_evt_mgr_.Pub(this, SelectionStartChanged_evt);} + public int SelLen() {return textBox.SelLen();} public void SelLen_set(int v) {textBox.SelLen_set(v);} + public String SelText() { + return String_.MidByLen(this.TextMgr().Val(), this.SelBgn(), this.SelLen()); + } + public void Focus_select_all() { + this.Focus(); + this.SelBgn_set(0); + int len = String_.Len(this.Text()); + this.SelLen_set(len); + } + public boolean OverrideTabKey() {return textBox.OverrideTabKey();} public void OverrideTabKey_(boolean val) {textBox.OverrideTabKey_(val);} + public void Margins_set(int left, int top, int right, int bot) {textBox.Margins_set(left, top, right, bot);} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_Margins_)) { + int u = m.ReadInt("u"); + int l = m.ReadInt("l"); + int d = m.ReadInt("d"); + int r = m.ReadInt("r"); + if (ctx.Deny()) return this; + Margins_set(l, u, r, d); + return this; + } + else return super.Invk (ctx, ikey, k, m); + } static final String Invk_Margins_ = "Margins_"; + void BorderOn_set(boolean val) { + textBox.Border_on_(val); + if (val == false) + this.Height_(13); // WORKAROUND (WinForms): if border is off, height automatically becomes 13 and immutable for singleLine fields; affects statusBox in opal.imgs which will show with small gap over bottom of screen + } + @gplx.Internal @Override protected void Click_key_set_(String v) {}// TextBox's shouldn't have clickKeys; among other things, .Text is used to set ClickKey, which for textBox may be very large + + @gplx.Internal protected void SetTextBox(GxwTextFld textBox) {this.textBox = textBox;} + public void CreateControlIfNeeded() {textBox.CreateControlIfNeeded();} + @Override public GxwElem UnderElem_make(Keyval_hash ctorArgs) {return GxwElemFactory_.Instance.text_fld_();} + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + super.ctor_GfuiBox_base(ctorArgs); + textBox = (GxwTextFld)this.UnderElem(); + } GxwTextFld textBox; + @Override public void ctor_kit_GfuiElemBase(Gfui_kit kit, String key, GxwElem underElem, Keyval_hash ctorArgs) { + super.ctor_kit_GfuiElemBase(kit, key, underElem, ctorArgs); + textBox = (GxwTextFld)underElem; + } + public static final String CFG_border_on_ = "border_on_"; +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/GfuiTextBox_.java b/150_gfui/src/gplx/gfui/controls/standards/GfuiTextBox_.java index a27517de8..c34189728 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/GfuiTextBox_.java +++ b/150_gfui/src/gplx/gfui/controls/standards/GfuiTextBox_.java @@ -13,3 +13,33 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.kits.core.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; +public class GfuiTextBox_ { + public static GfuiTextBox as_(Object obj) {return obj instanceof GfuiTextBox ? (GfuiTextBox)obj : null;} + public static GfuiTextBox cast(Object obj) {try {return (GfuiTextBox)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, GfuiTextBox.class, obj);}} + public static final String NewLine = "\n"; + public static final String Ctor_Memo = "TextBox_Memo"; + + public static GfuiTextBox new_() { + GfuiTextBox rv = new GfuiTextBox(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_true_()); + return rv; + } + public static GfuiTextBox fld_(String key, GfuiElem owner) { + GfuiTextBox rv = new_(); + rv.Owner_(owner, key); + return rv; + } + public static GfuiTextMemo multi_(String key, GfuiElem owner) { + GfuiTextMemo rv = new GfuiTextMemo(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_true_()); + rv.Key_of_GfuiElem_(key).Owner_(owner); + return rv; + } + public static GfuiTextBox kit_(Gfui_kit kit, String key, GxwTextFld wk_textBox, Keyval_hash ctorArgs) { + GfuiTextBox rv = new GfuiTextBox(); + rv.ctor_kit_GfuiElemBase(kit, key, wk_textBox, ctorArgs); + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/GfuiTextMemo.java b/150_gfui/src/gplx/gfui/controls/standards/GfuiTextMemo.java index a27517de8..5683d5176 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/GfuiTextMemo.java +++ b/150_gfui/src/gplx/gfui/controls/standards/GfuiTextMemo.java @@ -13,3 +13,28 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.controls.gxws.*; +public class GfuiTextMemo extends GfuiTextBox { public int LinesPerScreen() {return textBox.LinesPerScreen();} + public int LinesTotal() {return textBox.LinesTotal();} + public int ScreenCount() {return Int_.DivAndRoundUp(this.LinesTotal(), this.LinesPerScreen());} + public int CharIndexOfFirst() {return textBox.CharIndexOfFirst();} + public int CharIndexOf(int lineIndex) {return textBox.CharIndexOf(lineIndex);} + public int CharIndexAtLine(int lineIndex) {return textBox.CharIndexOf(lineIndex);} + public int LineIndexOfFirst() {return textBox.LineIndexOfFirst();} + public int LineIndexOf(int charIndex) {return textBox.LineIndexOf(charIndex);} + public PointAdp PosOf(int charIndex) {return textBox.PosOf(charIndex);} + public void ScrollLineUp() {textBox.ScrollLineUp();} + public void ScrollLineDown() {textBox.ScrollLineDown();} + public void ScrollScreenUp() {textBox.ScrollScreenUp();} + public void ScrollScreenDown() {textBox.ScrollScreenDown();} + public void SelectionStart_toFirstChar() {textBox.SelectionStart_toFirstChar(); Gfo_evt_mgr_.Pub(this, SelectionStartChanged_evt);} + public void ScrollTillSelectionStartIsFirstLine() {textBox.ScrollTillSelectionStartIsFirstLine();} + + @Override public GxwElem UnderElem_make(Keyval_hash ctorArgs) {return GxwElemFactory_.Instance.text_memo_();} + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + super.ctor_GfuiBox_base(ctorArgs); + textBox = (GxwTextMemo)UnderElem(); + this.SetTextBox(textBox); + } GxwTextMemo textBox; +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/Gfui_grp.java b/150_gfui/src/gplx/gfui/controls/standards/Gfui_grp.java index a27517de8..932d19635 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/Gfui_grp.java +++ b/150_gfui/src/gplx/gfui/controls/standards/Gfui_grp.java @@ -13,3 +13,12 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; +public class Gfui_grp extends GfuiElemBase { + public static Gfui_grp kit_(Gfui_kit kit, String key, GxwElem under, Keyval_hash ctor_args) { + Gfui_grp rv = new Gfui_grp(); + rv.ctor_kit_GfuiElemBase(kit, key, under, ctor_args); + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/Gfui_html.java b/150_gfui/src/gplx/gfui/controls/standards/Gfui_html.java index a27517de8..84f93c8c3 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/Gfui_html.java +++ b/150_gfui/src/gplx/gfui/controls/standards/Gfui_html.java @@ -13,3 +13,58 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.kits.core.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; +public class Gfui_html extends GfuiElemBase { + public void Under_html_(Gxw_html v) {under = v;} private Gxw_html under; + public void Html_doc_html_load_by_mem(String html) {under.Html_doc_html_load_by_mem(html);} + public void Html_doc_html_load_by_url(Io_url path, String html) {under.Html_doc_html_load_by_url(path, html);} + public byte Html_doc_html_load_tid() {return under.Html_doc_html_load_tid();} + public void Html_doc_html_load_tid_(byte v) {under.Html_doc_html_load_tid_(v);} + public void Html_js_enabled_(boolean v) {under.Html_js_enabled_(v);} + @gplx.Virtual public String Html_js_eval_proc_as_str(String name, Object... args) {return under.Html_js_eval_proc_as_str(name, args);} + @gplx.Virtual public boolean Html_js_eval_proc_as_bool(String name, Object... args) {return under.Html_js_eval_proc_as_bool(name, args);} + public String Html_js_eval_script(String script) {return under.Html_js_eval_script(script);} + public Object Html_js_eval_script_as_obj(String script) {return under.Html_js_eval_script_as_obj(script);} + public String Html_js_send_json(String name, String data) {return under.Html_js_send_json(name, data);} + public void Html_js_cbks_add(String js_func_name, Gfo_invk invk) {under.Html_js_cbks_add(js_func_name, invk);} + public void Html_invk_src_(Gfo_evt_itm v) {under.Html_invk_src_(v);} + public void Html_dispose() {under.Html_dispose();} + @Override public GfuiElem Text_(String v) { + this.Html_doc_html_load_by_mem(v); + return this; + } + public static Gfui_html kit_(Gfui_kit kit, String key, Gxw_html under, Keyval_hash ctorArgs) { + Gfui_html rv = new Gfui_html(); + rv.ctor_kit_GfuiElemBase(kit, key, (GxwElem)under, ctorArgs); + rv.under = under; + return rv; + } + public static Gfui_html mem_(String key, Gxw_html under) { + Gfui_html rv = new Gfui_html(); + rv.Key_of_GfuiElem_(key); + rv.under = under; + return rv; + } + public static Object Js_args_exec(Gfo_invk invk, Object[] args) { + GfoMsg m = Js_args_to_msg(args); + return Gfo_invk_.Invk_by_msg(invk, m.Key(), m); + } + public static GfoMsg Js_args_to_msg(Object[] args) { + String proc = (String)args[0]; + GfoMsg rv = GfoMsg_.new_parse_(proc); + for (int i = 1; i < args.length; i++) + rv.Add(Int_.To_str(i), args[i]); // NOTE: args[i] can be either String or String[] + return rv; + } + + public static final String Atr_href = "href", Atr_title = "title", Atr_value = "value", Atr_innerHTML = "innerHTML", Atr_src = "src"; + public static final String Elem_id_body = "body"; + public static final String + Evt_location_changed = "location_changed" + , Evt_location_changing = "location_changing" + , Evt_link_hover = "link_hover" + , Evt_win_resized = "win_resized" + , Evt_zoom_changed = "zoom_changed" + ; +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/Gfui_tab_itm.java b/150_gfui/src/gplx/gfui/controls/standards/Gfui_tab_itm.java index a27517de8..ed67cee74 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/Gfui_tab_itm.java +++ b/150_gfui/src/gplx/gfui/controls/standards/Gfui_tab_itm.java @@ -13,3 +13,17 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.kits.core.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; +public class Gfui_tab_itm extends GfuiElemBase { + private Gxw_tab_itm under; + public String Tab_name() {return under.Tab_name();} public void Tab_name_(String v) {under.Tab_name_(v);} + public String Tab_tip_text() {return under.Tab_tip_text();} public void Tab_tip_text_(String v) {under.Tab_tip_text_(v);} + public void Subs_add(GfuiElem elem) {under.Subs_add(elem);} + public static Gfui_tab_itm kit_(Gfui_kit kit, String key, Gxw_tab_itm under, Keyval_hash ctor_args) { + Gfui_tab_itm rv = new Gfui_tab_itm(); + // rv.ctor_kit_GfuiElemBase(kit, key, (GxwElem)under, ctor_args); // causes swt_tab_itm to break, since it's not a real Swt Control + rv.under = under; + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/Gfui_tab_itm_data.java b/150_gfui/src/gplx/gfui/controls/standards/Gfui_tab_itm_data.java index a27517de8..ced76391d 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/Gfui_tab_itm_data.java +++ b/150_gfui/src/gplx/gfui/controls/standards/Gfui_tab_itm_data.java @@ -13,3 +13,24 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public class Gfui_tab_itm_data { + public Gfui_tab_itm_data(String key, int idx) {this.key = key; this.idx = idx;} + public String Key() {return key;} private String key; + public int Idx() {return idx;} public Gfui_tab_itm_data Idx_(int v) {idx = v; return this;} private int idx; + public String Name() {return name;} public void Name_(String v) {name = v;} private String name; + public String Tip_text() {return tip_text;} public Gfui_tab_itm_data Tip_text_(String v) {tip_text = v; return this;} private String tip_text; + public boolean Pinned() {return pinned;} public Gfui_tab_itm_data Pinned_(boolean v) {pinned = v; return this;} private boolean pinned; + public void Switch(Gfui_tab_itm_data comp) { // NOTE: key is invariant; idx should not be switched, as tab_idx remains the same; only contents have been switched + String tmp_str = name; + this.name = comp.name; comp.name = tmp_str; + tmp_str = tip_text; + this.tip_text = comp.tip_text; comp.tip_text = tmp_str; + } + public static int Get_idx_after_closing(int cur, int len) { + if (len < 1) return Idx_null; // 1 or 0 tabs; return -1 + else if (cur == len - 1) return len - 2; // last tab; select_bwd + else return cur + 1; // else, select_fwd + } + public static final int Idx_null = -1; +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/Gfui_tab_itm_data_tst.java b/150_gfui/src/gplx/gfui/controls/standards/Gfui_tab_itm_data_tst.java index a27517de8..39cd37461 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/Gfui_tab_itm_data_tst.java +++ b/150_gfui/src/gplx/gfui/controls/standards/Gfui_tab_itm_data_tst.java @@ -13,3 +13,18 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import org.junit.*; +public class Gfui_tab_itm_data_tst { + @Before public void init() {} private Gfui_tab_itm_data_fxt fxt = new Gfui_tab_itm_data_fxt(); + @Test public void Get_idx_after_closing() { + fxt.Test_Get_idx_after_closing(0, 1, -1); + fxt.Test_Get_idx_after_closing(4, 5, 3); + fxt.Test_Get_idx_after_closing(3, 5, 4); + } +} +class Gfui_tab_itm_data_fxt { + public void Test_Get_idx_after_closing(int cur, int len, int expd) { + Tfds.Eq(expd, Gfui_tab_itm_data.Get_idx_after_closing(cur, len)); + } +} diff --git a/150_gfui/src/gplx/gfui/controls/standards/Gfui_tab_mgr.java b/150_gfui/src/gplx/gfui/controls/standards/Gfui_tab_mgr.java index a27517de8..5587a2fca 100644 --- a/150_gfui/src/gplx/gfui/controls/standards/Gfui_tab_mgr.java +++ b/150_gfui/src/gplx/gfui/controls/standards/Gfui_tab_mgr.java @@ -13,3 +13,35 @@ 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.gfui.controls.standards; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; +public class Gfui_tab_mgr extends GfuiElemBase { + public void Under_tab_mgr_(Gxw_tab_mgr v) {under = v;} private Gxw_tab_mgr under; + public ColorAdp Btns_selected_background() {return under.Btns_selected_background();} public void Btns_selected_background_(ColorAdp v) {under.Btns_selected_background_(v);} + public ColorAdp Btns_selected_foreground() {return under.Btns_selected_foreground();} public void Btns_selected_foreground_(ColorAdp v) {under.Btns_selected_foreground_(v);} + public ColorAdp Btns_unselected_background() {return under.Btns_unselected_background();} public void Btns_unselected_background_(ColorAdp v) {under.Btns_unselected_background_(v);} + public ColorAdp Btns_unselected_foreground() {return under.Btns_unselected_foreground();} public void Btns_unselected_foreground_(ColorAdp v) {under.Btns_unselected_foreground_(v);} + public Gfui_tab_itm Tabs_add(Gfui_tab_itm_data tab_data) { + Gxw_tab_itm tab_itm = under.Tabs_add(tab_data); + return Gfui_tab_itm.kit_(this.Kit(), tab_data.Key(), tab_itm, new Keyval_hash()); + } + public int Btns_height() {return under.Btns_height();} public void Btns_height_(int v) {under.Btns_height_(v);} + public boolean Btns_place_on_top() {return under.Btns_place_on_top();} public void Btns_place_on_top_(boolean v) {under.Btns_place_on_top_(v);} + public boolean Btns_curved() {return under.Btns_curved();} public void Btns_curved_(boolean v) {under.Btns_curved_(v);} + public boolean Btns_close_visible_() {return under.Btns_close_visible();} public void Btns_close_visible_(boolean v) {under.Btns_close_visible_(v);} + public boolean Btns_unselected_close_visible() {return under.Btns_unselected_close_visible();} public void Btns_unselected_close_visible_(boolean v) {under.Btns_unselected_close_visible_(v);} + public void Tabs_select_by_idx(int idx) {under.Tabs_select_by_idx(idx);} + public void Tabs_close_by_idx(int idx) {under.Tabs_close_by_idx(idx);} + public void Tabs_switch(int src, int trg) {under.Tabs_switch(src, trg);} + public static Gfui_tab_mgr kit_(Gfui_kit kit, String key, Gxw_tab_mgr under, Keyval_hash ctor_args) { + Gfui_tab_mgr rv = new Gfui_tab_mgr(); + rv.ctor_kit_GfuiElemBase(kit, key, (GxwElem)under, ctor_args); + rv.under = under; + return rv; + } + public static final String + Evt_tab_selected = "tab_selected" + , Evt_tab_closed = "tab_closed" + , Evt_tab_switched = "tab_switched" + ; +} diff --git a/150_gfui/src/gplx/gfui/controls/tabs/TabBnd.java b/150_gfui/src/gplx/gfui/controls/tabs/TabBnd.java index a27517de8..46b365185 100644 --- a/150_gfui/src/gplx/gfui/controls/tabs/TabBnd.java +++ b/150_gfui/src/gplx/gfui/controls/tabs/TabBnd.java @@ -13,3 +13,77 @@ 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.gfui.controls.tabs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.core.interfaces.*; +import gplx.gfui.ipts.*; import gplx.gfui.controls.standards.*; +class TabBoxEvt_nameChange { + public static String Key = "TabBoxEvt_nameChange"; + public static void Send(TabBoxMgr mgr, TabPnlItm itm) { + Gfo_evt_mgr_.Pub_val(mgr, Key, itm); + } + public static void Rcvd(TabBox tabBox, GfsCtx ctx, GfoMsg m) { + TabPnlItm itm = (TabPnlItm)m.CastObj("v"); + GfuiBtn btn = GfuiBtn_.as_(tabBox.BtnBox().SubElems().Get_by(itm.Key())); + if (btn != null) // HACK: check needed b/c Gfds will raise UpdateCaption event before Creating tab + btn.Text_(itm.Name()).TipText_(itm.Name()); + } +} +class TabBoxEvt_tabSelectByBtn { + public static String Key = "TabBoxEvt_tabSelectByBtn"; + public static void Rcvd(Object sender, TabBox tabBox) { + GfuiBtn btn = (GfuiBtn)sender; + String key = btn.Key_of_GfuiElem(); + TabBoxMgr mgr = tabBox.Mgr(); + mgr.Select(mgr.Get_by(key)); + } +} +class TabBnd_selectTab implements InjectAble, Gfo_invk { + public void Inject(Object obj) { + tabBox = TabBox_.cast(obj); + IptBnd_.cmd_to_(IptCfg_.Null, tabBox, this, SelectNext_cmd, IptKey_.add_(IptKey_.Ctrl, IptKey_.Tab), IptKey_.add_(IptKey_.Ctrl, IptKey_.PageDown)); + IptBnd_.cmd_to_(IptCfg_.Null, tabBox, this, SelectPrev_cmd, IptKey_.add_(IptKey_.Ctrl, IptKey_.Tab, IptKey_.Shift), IptKey_.add_(IptKey_.Ctrl, IptKey_.PageUp)); + } + void Select(GfoMsg msg, int delta) { + TabPnlItm curTab = tabBox.Mgr().CurTab(); + int newIdx = TabBox_.Cycle(delta > 0, curTab.Idx(), tabBox.Mgr().Count()); + tabBox.Tabs_Select(newIdx); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, SelectNext_cmd)) Select(m, 1); + else if (ctx.Match(k, SelectPrev_cmd)) Select(m, -1); + else return Gfo_invk_.Rv_unhandled; + return this; + } static final String SelectNext_cmd = "SelectNext", SelectPrev_cmd = "SelectPrev"; + TabBox tabBox; + public static TabBnd_selectTab new_() {return new TabBnd_selectTab();} TabBnd_selectTab() {} +} +class TabBnd_reorderTab implements InjectAble, Gfo_invk { + public void Inject(Object owner) { + GfuiBtn btn = GfuiBtn_.cast(owner); + IptBnd_.cmd_to_(IptCfg_.Null, btn, this, MovePrev_cmd, IptKey_.add_(IptKey_.Ctrl, IptKey_.Left)); + IptBnd_.cmd_to_(IptCfg_.Null, btn, this, MoveNext_cmd, IptKey_.add_(IptKey_.Ctrl, IptKey_.Right)); + } + @gplx.Internal protected void MoveTab(GfuiBtn curBtn, int delta) { + TabPnlItm curItm = tabBox.Mgr().Get_by(curBtn.Key_of_GfuiElem()); + int curIdx = curItm.Idx(); + int newIdx = TabBox_.Cycle(delta > 0, curIdx, tabBox.Mgr().Count()); + + tabBox.Mgr().Move_to(curIdx, newIdx); + tabBox.Mgr().Reorder(0); // reorder all; exchanging curIdx for newIdx does not work when going from last to first (17 -> 0, but 0 -> 1) + tabBox.PnlBox().SubElems().Move_to(curIdx, newIdx); + TabBtnAreaMgr.Move_to(tabBox, curIdx, newIdx); + TabBoxEvt_orderChanged.Publish(tabBox, curIdx, newIdx); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, MoveNext_cmd)) MoveTab(GfuiBtn_.cast(ctx.MsgSrc()), 1); + else if (ctx.Match(k, MovePrev_cmd)) MoveTab(GfuiBtn_.cast(ctx.MsgSrc()), -1); + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String MoveNext_cmd = "MoveNext", MovePrev_cmd = "MovePrev"; + public static TabBnd_reorderTab new_(TabBox tabBox) { + TabBnd_reorderTab rv = new TabBnd_reorderTab(); + rv.tabBox = tabBox; + return rv; + } TabBnd_reorderTab() {} + TabBox tabBox; +} diff --git a/150_gfui/src/gplx/gfui/controls/tabs/TabBox.java b/150_gfui/src/gplx/gfui/controls/tabs/TabBox.java index a27517de8..67b7bbeeb 100644 --- a/150_gfui/src/gplx/gfui/controls/tabs/TabBox.java +++ b/150_gfui/src/gplx/gfui/controls/tabs/TabBox.java @@ -13,3 +13,141 @@ 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.gfui.controls.tabs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; import gplx.gfui.layouts.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; +public class TabBox extends GfuiElemBase { + public int Tabs_Count() {return mgr.Count();} + public TabPnlItm Tabs_SelectedItm() {return mgr.CurTab();} + public GfuiElem Tabs_FetchAt(int i) {return pnlBox.SubElems().Get_by(mgr.Get_at(i).Key());} + public GfuiElem Tabs_SelectedPnl() {return pnlBox.SubElems().Get_by(mgr.CurTab().Key());} + public void Tabs_Select(int idx) {mgr.Select(idx);} + public GfuiElem Tabs_Add(String key, String name) { + TabPnlItm newTab = mgr.Add(key, name); + TabBtnAreaMgr.Add(this, newTab); + GfuiElem pnl = TabPnlAreaMgr.Add(this, newTab); + mgr.Select(newTab); // always select added tab + return pnl; + } + public void Tabs_DelAt(int idx) { + TabBtnAreaMgr.Del(this, mgr.Get_at(idx)); + TabPnlAreaMgr.Del(this, mgr.Get_at(idx)); + mgr.Del_at(idx); + } + @gplx.Internal protected TabBoxMgr Mgr() {return mgr;} TabBoxMgr mgr = TabBoxMgr.new_(); + public GfuiElem BtnBox() {return btnBox;} GfuiElem btnBox; + @gplx.Internal protected GfuiElem PnlBox() {return pnlBox;} GfuiElem pnlBox; + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, TabBoxEvt_tabSelectByBtn.Key)) TabBoxEvt_tabSelectByBtn.Rcvd(ctx.MsgSrc(), this); + else if (ctx.Match(k, TabBoxEvt_tabSelect.Key)) TabBoxEvt_tabSelect.Select(this, ctx, m); + else if (ctx.Match(k, TabBoxEvt_nameChange.Key)) TabBoxEvt_nameChange.Rcvd(this, ctx, m); + else return super.Invk(GfsCtx.Instance, 0, k, m); + return this; + } + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + super.ctor_GfuiBox_base(ctorArgs); + btnBox = TabBtnAreaMgr.new_("btnBox", this); + pnlBox = TabPnlAreaMgr.new_("pnlBox", this); + this.Inject_(TabBnd_selectTab.new_()); + Gfo_evt_mgr_.Sub_same(mgr, TabBoxEvt_tabSelect.Key, this); + Gfo_evt_mgr_.Sub_same(mgr, TabBoxEvt_nameChange.Key, this); + + this.Lyt_activate(); + this.Lyt().Bands_add(GftBand.fillWidth_()); + this.Lyt().Bands_add(GftBand.fillAll_()); + } +} +class TabBtnAreaMgr { + public static GfuiElemBase new_(String key, GfuiElem owner) { + GfuiElemBase rv = new GfuiElemBase(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_false_()); + rv.Owner_(owner, key); + return rv; + } + public static GfuiBtn Add(TabBox tabBox, TabPnlItm itm) { + GfuiElem btnBox = tabBox.BtnBox(); + GfuiBtn btn = GfuiBtn_.new_(itm.Key()); + btn.TipText_(itm.Name()).Text_(itm.Name()).Width_(80); + btn.TextMgr().Font_(btn.TextMgr().Font().Size_(8)); + btn.Click_invk(Gfo_invk_cmd.New_by_key(tabBox, TabBoxEvt_tabSelectByBtn.Key)); + btn.Inject_(TabBnd_reorderTab.new_(tabBox)); + if (btnBox.SubElems().Count() > 0) { // place button after last + GfuiElem lastBtn = btnBox.SubElems().Get_at(btnBox.SubElems().Count() - 1); + btn.X_(lastBtn.X() + lastBtn.Width()); + } + btnBox.SubElems().Add(btn); + return btn; + } + public static void Del(TabBox tabBox, TabPnlItm itm) { + GfuiElem btnBox = tabBox.BtnBox(); + int idx = itm.Idx(); + GfuiBtn btn = (GfuiBtn)btnBox.SubElems().Get_at(idx); + btnBox.SubElems().Del_at(idx); + for (int i = idx; i < btnBox.SubElems().Count(); i++) { + GfuiBtn cur = (GfuiBtn)btnBox.SubElems().Get_at(i); + cur.X_(cur.X() - btn.Width()); + } + } + public static void Select(TabBox tabBox, TabPnlItm curTabItm, TabPnlItm newTabItm) { + if (curTabItm != null) { + GfuiBtn curBtn = (GfuiBtn)tabBox.BtnBox().SubElems().Get_at(curTabItm.Idx()); + Select(curBtn, false); + } + GfuiBtn newBtn = (GfuiBtn)tabBox.BtnBox().SubElems().Get_at(newTabItm.Idx()); + Select(newBtn, true); + } + public static void Move_to(TabBox tabBox, int curIdx, int newIdx) { + GfuiElemList btns = tabBox.BtnBox().SubElems(); + btns.Move_to(curIdx, newIdx); + int curX = 0; + for (int i = 0; i < btns.Count(); i++) { + GfuiBtn cur = (GfuiBtn)btns.Get_at(i); + cur.X_(curX); + curX += cur.Width(); + } + } + static void Select(GfuiBtn btn, boolean enabled) { + btn.TextMgr().Color_(enabled ? ColorAdp_.Black : ColorAdp_.Gray); + btn.TextMgr().Font().Style_(enabled ? FontStyleAdp_.Bold : FontStyleAdp_.Plain); + if (enabled) + btn.Border().All_(PenAdp_.black_()); + else + btn.Border().None_(); + } +} +class TabPnlAreaMgr { + public static GfuiElemBase new_(String key, GfuiElem owner) { + GfuiElemBase rv = new GfuiElemBase(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_false_()); + rv.Owner_(owner, key); + rv.Lyt_activate(); + return rv; + } + public static GfuiElemBase Add(TabBox tabBox, TabPnlItm itm) { + GfuiElem pnlBox = tabBox.PnlBox(); + GfuiElemBase pnl = pnl_(tabBox, pnlBox, itm.Key()); + return pnl; + } + public static void Del(TabBox tabBox, TabPnlItm itm) { + tabBox.PnlBox().SubElems().Del_at(itm.Idx()); + ((GfuiElemBase)tabBox.PnlBox()).Lyt().SubLyts().Del_at(itm.Idx()); + } + public static void Select(TabBox tabBox, TabPnlItm curTabItm, TabPnlItm newTabItm) { + if (curTabItm != null) { + GfuiElem curTab = tabBox.PnlBox().SubElems().Get_at(curTabItm.Idx()); + curTab.Visible_set(false); + } + GfuiElem newTab = tabBox.PnlBox().SubElems().Get_at(newTabItm.Idx()); + newTab.Visible_set(true); + newTab.Zorder_front(); + newTab.Focus(); + } + @gplx.Internal protected static GfuiElemBase pnl_(TabBox tabBox, GfuiElem pnlBox, String key) { + GfuiElemBase rv = new GfuiElemBase(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_false_()); + rv.Owner_(pnlBox, key); + rv.Lyt_activate(); + rv.Lyt().Bands_add(GftBand.fillAll_()); + ((GfuiElemBase)pnlBox).Lyt().SubLyts().Add(GftGrid.new_().Bands_add(GftBand.fillAll_())); + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/controls/tabs/TabBoxEvt_orderChanged.java b/150_gfui/src/gplx/gfui/controls/tabs/TabBoxEvt_orderChanged.java index a27517de8..52ce6f18b 100644 --- a/150_gfui/src/gplx/gfui/controls/tabs/TabBoxEvt_orderChanged.java +++ b/150_gfui/src/gplx/gfui/controls/tabs/TabBoxEvt_orderChanged.java @@ -13,3 +13,19 @@ 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.gfui.controls.tabs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public class TabBoxEvt_orderChanged { + public int CurIdx() {return curIdx;} public TabBoxEvt_orderChanged CurIdx_(int v) {curIdx = v; return this;} int curIdx; + public int NewIdx() {return newIdx;} public TabBoxEvt_orderChanged NewIdx_(int v) {newIdx = v; return this;} int newIdx; + + public static final String OrderChanged_evt = "OrderChanged_evt"; + public static void Publish(TabBox tabBox, int curIdx, int newIdx) { + Gfo_evt_mgr_.Pub_vals(tabBox, OrderChanged_evt, Keyval_.new_("curIdx", curIdx), Keyval_.new_("newIdx", newIdx)); + } + public static TabBoxEvt_orderChanged Handle(GfsCtx ctx, GfoMsg m) { + TabBoxEvt_orderChanged rv = new TabBoxEvt_orderChanged(); + rv.curIdx = m.ReadInt("curIdx"); + rv.newIdx = m.ReadInt("newIdx"); + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/controls/tabs/TabBoxEvt_tabSelect.java b/150_gfui/src/gplx/gfui/controls/tabs/TabBoxEvt_tabSelect.java index a27517de8..805abd305 100644 --- a/150_gfui/src/gplx/gfui/controls/tabs/TabBoxEvt_tabSelect.java +++ b/150_gfui/src/gplx/gfui/controls/tabs/TabBoxEvt_tabSelect.java @@ -13,3 +13,23 @@ 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.gfui.controls.tabs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public class TabBoxEvt_tabSelect { + public static String Key = "TabBoxEvt_tabSelect"; + public static final String SelectedChanged_evt = "SelectedChanged_evt"; + public static void Send(TabBoxMgr tabBoxMgr, TabPnlItm oldTab, TabPnlItm newTab) { + Gfo_evt_mgr_.Pub_val(tabBoxMgr, Key, new TabPnlItm[] {oldTab, newTab}); + } + @gplx.Internal protected static void Select(TabBox tabBox, GfsCtx ctx, GfoMsg m) { + TabPnlItm[] ary = (TabPnlItm[])m.CastObj("v"); + Select(tabBox, ary[0], ary[1]); + } + @gplx.Internal protected static void Select(TabBox tabBox, TabPnlItm curTabItm, TabPnlItm newTabItm) { + TabPnlAreaMgr.Select(tabBox, curTabItm, newTabItm); + TabBtnAreaMgr.Select(tabBox, curTabItm, newTabItm); + Gfo_evt_mgr_.Pub_val(tabBox, SelectedChanged_evt, newTabItm.Idx()); + } + public static int Handle(GfsCtx ctx, GfoMsg m) { + return m.ReadInt("v"); + } +} diff --git a/150_gfui/src/gplx/gfui/controls/tabs/TabBoxMgr.java b/150_gfui/src/gplx/gfui/controls/tabs/TabBoxMgr.java index a27517de8..c07c5cffe 100644 --- a/150_gfui/src/gplx/gfui/controls/tabs/TabBoxMgr.java +++ b/150_gfui/src/gplx/gfui/controls/tabs/TabBoxMgr.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.gfui.controls.tabs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public class TabBoxMgr implements Gfo_evt_mgr_owner { + public Gfo_evt_mgr Evt_mgr() {if (evt_mgr == null) evt_mgr = new Gfo_evt_mgr(this); return evt_mgr;} Gfo_evt_mgr evt_mgr; + public int Count() {return itms.Count();} + public TabPnlItm Get_by(String k) {return (TabPnlItm)itms.Get_by(k);} + public TabPnlItm Get_at(int i) {return (TabPnlItm)itms.Get_at(i);} + public TabPnlItm CurTab() {return curTab;} TabPnlItm curTab; + public TabPnlItm Add(String key, String name) { + TabPnlItm itm = TabPnlItm.new_(this, key).Name_(name).Idx_(itms.Count()); + itms.Add(itm.Key(), itm); + return itm; + } + public void Del_at(int i) { + boolean isCur = i == curTab.Idx(), isLast = i == itms.Count() - 1; + TabPnlItm itm = this.Get_at(i); + itms.Del(itm.Key()); + this.Reorder(i); + if (isCur) { + curTab = null; // must null out curTab since it has been deleted from itms; TODO_OLD: should raise Deleted event to delete btn,pnl,view; wait for rewrite of view + if (isLast) i--; // last was dropped; select prev; otherwise re-select idx (effectively, whatever tab drops into slot) + if (i >=0) + this.Select(i); + } + } + public void Select(int i) {Select((TabPnlItm)itms.Get_at(i));} + @gplx.Internal protected void Move_to(int src, int trg) {itms.Move_to(src, trg);} + @gplx.Internal protected void Reorder(int bgn) { + for (int i = bgn; i < itms.Count(); i++) { + TabPnlItm itm = (TabPnlItm)itms.Get_at(i); + itm.Idx_(i); + } + } + @gplx.Internal protected void Select(TabPnlItm newTab) { + TabPnlItm oldTab = curTab; + curTab = newTab; + TabBoxEvt_tabSelect.Send(this, oldTab, newTab); + } + Ordered_hash itms = Ordered_hash_.New(); + @gplx.Internal protected static TabBoxMgr new_() {return new TabBoxMgr();} TabBoxMgr() {} +} diff --git a/150_gfui/src/gplx/gfui/controls/tabs/TabBox_.java b/150_gfui/src/gplx/gfui/controls/tabs/TabBox_.java index a27517de8..a1c9a83d3 100644 --- a/150_gfui/src/gplx/gfui/controls/tabs/TabBox_.java +++ b/150_gfui/src/gplx/gfui/controls/tabs/TabBox_.java @@ -13,3 +13,20 @@ 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.gfui.controls.tabs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.controls.elems.*; +public class TabBox_ { + public static TabBox as_(Object obj) {return obj instanceof TabBox ? (TabBox)obj : null;} + public static TabBox cast(Object obj) {try {return (TabBox)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, TabBox.class, obj);}} + public static TabBox new_() { + TabBox rv = new TabBox(); + rv.ctor_GfuiBox_base(GfuiElem_.init_focusAble_false_()); + return rv; + } + public static int Cycle(boolean fwd, int val, int max) { + if (fwd) + return val == max - 1 ? 0 : val + 1; + else + return val == 0 ? max - 1 : val - 1; + } +} diff --git a/150_gfui/src/gplx/gfui/controls/tabs/TabBox_tst.java b/150_gfui/src/gplx/gfui/controls/tabs/TabBox_tst.java index a27517de8..916265bd7 100644 --- a/150_gfui/src/gplx/gfui/controls/tabs/TabBox_tst.java +++ b/150_gfui/src/gplx/gfui/controls/tabs/TabBox_tst.java @@ -13,3 +13,117 @@ 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.gfui.controls.tabs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import org.junit.*; import gplx.gfui.controls.tabs.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; +public class TabBox_tst { +// @Before public void setup() { +// fx = TabBoxFxt.new_(); +// } TabBoxFxt fx; + @Test public void Add() { +// fx.Make(1).tst_Selected("0").FetchBtnAt(0).tst_X(0); +// fx.Make(3).tst_Selected("2").FetchBtnAt(2).tst_X(160); + } +// @Test public void Del_at() { +// fx.Make(2).Del_at(1).tst_Btns("0"); +// fx.Make(2).Del_at(0).tst_Btns("1"); +// fx.Make(3).Del_at(0).tst_Btns("1", "2"); +// fx.Make(3).Del_at(1).tst_Btns("0", "2"); +// fx.Make(3).Del_at(2).tst_Btns("0", "1"); + +// fx.Make(3).Select(1).Del_at(1).tst_Selected("2"); // 1 deleted; 2 shifted down into slot +// fx.Make(3).Select(1).Del_at(0).tst_Selected("1"); // 0 deleted; 1 still remains active (but will have idx of 0 +// fx.Make(3).Select(2).Del_at(2).tst_Selected("1"); // 2 deleted; 1 selected +// } +// @Test public void Selected_byAdd() { +// fx.Make(2).Select(0).tst_Selected("0").Select(1).tst_Selected("1"); +// } +// @Test public void Selected_byBtn() { +// fx.Make(2).tst_Selected("1"); +// +// GfuiBtn btn = fx.TabBox().SubBtnArea().Get_at(0); +// btn.Click(); +// fx.tst_Selected("0"); +// } +// @Test public void ReorderTab() { +// fx.Make(3).Reorder(0, -1).tst_Raised(false); +// fx.Make(3).Reorder(2, 1).tst_Raised(false); +// fx.Make(3).Reorder(0, 1).tst_Btns("1", "0", "2").tst_Raised(true).tst_FocusOrder(); +// fx.Make(3).Reorder(0, 2).tst_Btns("1", "2", "0").tst_Raised(true).tst_FocusOrder(); +// fx.Make(3).Reorder(2, -1).tst_Btns("0", "2", "1").tst_Raised(true).tst_FocusOrder(); +// fx.Make(3).Reorder(0, 1).Reorder(1, 2).tst_Btns("0", "2", "1").tst_Raised(true);//.tst_FocusOrder(); // FIXME: broken after FocusOrder set for entire form (instead of per container) +// } +} +class GfuiElemFxt { + public GfuiElem UnderElem() {return underElem;} GfuiElem underElem; + @gplx.Internal protected GfuiElemFxt tst_X(int expd) {Tfds.Eq(expd, underElem.X()); return this;} + public static GfuiElemFxt new_(GfuiElem elem) { + GfuiElemFxt rv = new GfuiElemFxt(); + rv.underElem = elem; + return rv; + } GfuiElemFxt() {} +} +class TabBoxFxt implements Gfo_invk { + @gplx.Internal protected TabBox TabBox() {return tabBox;} + @gplx.Internal protected TabBoxFxt Make(int count) { + for (int i = 0; i < tabBox.Tabs_Count(); i++) + tabBox.Tabs_DelAt(0); + for (int i = 0; i < count; i++) + tabBox.Tabs_Add(Int_.To_str(i), Int_.To_str(i)); + return this; + } + @gplx.Internal protected TabBoxFxt Del_at(int index) {tabBox.Tabs_DelAt(index); return this;} +// @gplx.Internal protected TabBoxFxt Select(int index) {tabBox.Tabs_Select(index); return this;} + @gplx.Internal protected GfuiElemFxt FetchBtnAt(int index) { + GfuiBtn btn = (GfuiBtn)tabBox.BtnBox().SubElems().Get_at(index); + GfuiElemFxt fx_elem = GfuiElemFxt.new_(btn); + return fx_elem; + } +// @gplx.Internal protected TabBoxFxt tst_BtnX(int idx, int expdX) { +// Tfds.Eq(expdX, tabBox.SubBtnArea().Get_at(idx).X()); +// return this; +// } + @gplx.Internal protected TabBoxFxt tst_Selected(String expd) { + TabPnlItm curTab = tabBox.Tabs_SelectedItm(); + GfuiBtn btn = (GfuiBtn)tabBox.BtnBox().SubElems().Get_at(curTab.Idx()); + Tfds.Eq(expd, btn.Text()); + return this; + } + @gplx.Internal protected TabBoxFxt tst_Btns(String... expd) { + String[] actl = new String[tabBox.Tabs_Count() ]; + for (int i = 0; i < tabBox.Tabs_Count() ; i++) { + GfuiBtn button = (GfuiBtn)tabBox.BtnBox().SubElems().Get_at(i); + actl[i] = button.TextMgr().Val(); + } + Tfds.Eq_ary(expd, actl); + return this; + } +// @gplx.Internal protected TabBoxFxt tst_Raised(boolean expd) {Tfds.Eq(expd, received != null); return this;} +// @gplx.Internal protected TabBoxFxt Reorder(int i, int delta) { +// tabBox.Width_(240); // needed for lytMgr +// TabBnd_reorderTab reorderBnd = TabBnd_reorderTab.Instance; +// received = null; +// TabPnl pnl = tabBox.Tabs_FetchAt(i); +// reorderBnd.MoveTab(pnl.SubTabBtn(), delta); +// return this; +// } +// @gplx.Internal protected TabBoxFxt tst_FocusOrder() { +// for (int i = 0; i < tabBox.SubBtnArea().SubZones().Get_at(0).Count(); i++) { +// GfuiElem subBtn = (GfuiElem)tabBox.SubBtnArea().SubZones().Get_at(0).Get_at(i); +// Tfds.Eq(i, subBtn.UnderElem().Core().Focus_index()); +// } +// return this; +// } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, OrderChangedReceived_cmd)) OrderChangedReceived(m); + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String OrderChangedReceived_cmd = "OrderChangedReceived"; + TabBox tabBox; + public static TabBoxFxt new_() { + TabBoxFxt rv = new TabBoxFxt(); + rv.tabBox = TabBox_.new_(); + return rv; + } TabBoxFxt() {} + void OrderChangedReceived(GfoMsg msg) { + } //int[] received = null; +} diff --git a/150_gfui/src/gplx/gfui/controls/tabs/TabPnlItm.java b/150_gfui/src/gplx/gfui/controls/tabs/TabPnlItm.java index a27517de8..00392fb51 100644 --- a/150_gfui/src/gplx/gfui/controls/tabs/TabPnlItm.java +++ b/150_gfui/src/gplx/gfui/controls/tabs/TabPnlItm.java @@ -13,3 +13,24 @@ 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.gfui.controls.tabs; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +public class TabPnlItm { + public String Key() {return key;} private String key; + public String Name() {return name;} + public TabPnlItm Name_(String val) { + name = val; + TabBoxEvt_nameChange.Send(mgr, this); + return this; + } String name; + public int Idx() {return idx;} + @gplx.Internal protected TabPnlItm Idx_(int val) { + idx = val; + return this; + } int idx; + TabBoxMgr mgr; + public static TabPnlItm new_(TabBoxMgr mgr, String key) { + TabPnlItm rv = new TabPnlItm(); + rv.mgr = mgr; rv.key = key; + return rv; + } TabPnlItm() {} +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfoConsoleWin.java b/150_gfui/src/gplx/gfui/controls/windows/GfoConsoleWin.java index a27517de8..3e946fedf 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfoConsoleWin.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfoConsoleWin.java @@ -13,3 +13,241 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.draws.*; import gplx.gfui.ipts.*; import gplx.gfui.layouts.*; import gplx.gfui.envs.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; import gplx.gfui.controls.customs.*; +import gplx.core.envs.*; +import gplx.gfml.*; import gplx.langs.gfs.*; +public class GfoConsoleWin implements Gfo_invk, UsrMsgWkr { + GfuiWin win; GfoConsoleWinCmds cmds; GfuiTextBox statusBox, resultBox; GfuiTextBoxLogger logger; + public boolean Enabled() {return enabled;} public GfoConsoleWin Enabled_(boolean v) {enabled = v; return this;} private boolean enabled = true; + public GfuiWin Win() {return win;} + void Init() { + win = GfuiWin_.tool_("gfoConsoleWin"); + win.Size_(700, 600); + PointAdp point = PointAdp_.new_(ScreenAdp_.Primary.Width() - win.Width(), ScreenAdp_.Primary.Height() - win.Height()); + win.Pos_(point).Focus_default_("consoleBox"); win.QuitMode_(GfuiQuitMode.Suspend); + GfuiTextBox_.fld_("consoleFilBox", win); + GfuiTextBox consoleBox = GfuiTextBox_.multi_("consoleBox", win); + consoleBox.TextMgr().Font_(FontAdp.new_("Courier New", 8, FontStyleAdp_.Plain)); + consoleBox.OverrideTabKey_(false); + resultBox = GfuiTextBox_.multi_("resultBox", win); + resultBox .OverrideTabKey_(false); + resultBox.TextMgr().Font_(FontAdp.new_("Courier New", 8, FontStyleAdp_.Plain)); + statusBox = GfuiTextBox_.multi_("statusBox", win); + statusBox.OverrideTabKey_(false); + statusBox.TextMgr().Font_(FontAdp.new_("Courier New", 8, FontStyleAdp_.Plain)); + win.Inject_(GfuiStatusBarBnd.new_()); + cmds = new GfoConsoleWinCmds(this); + cmds.Owner_set(win); cmds.Init(); + IptBnd_.cmd_to_(IptCfg_.Null, win, cmds, GfoConsoleWinCmds.Invk_Hide, IptKey_.Escape); + IptBnd_.cmd_to_(IptCfg_.Null, consoleBox, cmds, GfoConsoleWinCmds.Invk_Exec, IptKey_.add_(IptKey_.Ctrl, IptKey_.E)); + IptBnd_.cmd_to_(IptCfg_.Null, consoleBox, cmds, GfoConsoleWinCmds.Invk_Save, IptKey_.add_(IptKey_.Ctrl, IptKey_.S)); + IptBnd_.cmd_to_(IptCfg_.Null, consoleBox, cmds, GfoConsoleWinCmds.Invk_Load, IptKey_.add_(IptKey_.Ctrl, IptKey_.L)); + IptBnd_.cmd_to_(IptCfg_.Null, consoleBox, cmds, GfoConsoleWinCmds.Invk_Help, IptKey_.add_(IptKey_.Ctrl, IptKey_.D)); + IptBnd_.cmd_to_(IptCfg_.Null, consoleBox, cmds, GfoConsoleWinCmds.Invk_Clear, IptKey_.add_(IptKey_.Ctrl, IptKey_.Alt, IptKey_.C)); + logger = new GfuiTextBoxLogger(this).Init(statusBox); +// gplx.core.ios.GfioApp.InitGfs(); + UsrDlg_.Instance.Reg(UsrMsgWkr_.Type_Note, this); + + win.Lyt_activate(); + win.Lyt().Bands_add(GftBand.fillWidth_()); + win.Lyt().Bands_add(GftBand.fillWidth_().Len1_pct_(50)); + win.Lyt().Bands_add(GftBand.fillWidth_().Len1_abs_(20)); + win.Lyt().Bands_add(GftBand.fillWidth_().Len1_pct_(50)); + win.Lyt().Bands_add(GftBand.fillWidth_()); + } + void Show() { + if (win == null) this.Init(); + win.Pin_(false); + win.Visible_set(true); + win.Zorder_front_and_focus(); + } + public void ExecUsrMsg(int type, UsrMsg umsg) { + if (win == null) this.Init(); + String s = umsg.To_str(); + if (type == UsrMsgWkr_.Type_Warn) { + if (!win.Pin()) win.Pin_(); + s = "!!warn!! " + umsg.To_str(); + if (logger == null) return; + logger.Write(s); + } + else { + statusBox.Text_(statusBox.Text() + s); + statusBox.SelBgn_set(String_.Len(statusBox.Text()) - 1); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_Show)) Show(); + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String Invk_Show = "Show" + ; + public static final GfoConsoleWin Instance = new GfoConsoleWin(); GfoConsoleWin() {} +} +class GfoConsoleWinCmds implements Gfo_invk { + GfuiWin win; GfuiTextBox consoleFilBox, consoleBox, statusBox, resultBox; + public void Owner_set(GfuiElem elem) {win = (GfuiWin)elem;} + public void Init() { + consoleFilBox = (GfuiTextBox)win.SubElems().Get_by("consoleFilBox"); + consoleBox = (GfuiTextBox)win.SubElems().Get_by("consoleBox"); + resultBox = (GfuiTextBox)win.SubElems().Get_by("resultBox"); + statusBox = (GfuiTextBox)win.SubElems().Get_by("statusBox"); + GfsCore.Instance.AddObj(this, "gfoConsoleWin"); + GfsCore.Instance.ExecRegy("gplx.gfui.GfoConsoleWin.ini"); + } + public void Results_add(String s) { + if (!String_.Has_at_end(s, GfuiTextBox_.NewLine)) + s += GfuiTextBox_.NewLine; + statusBox.Text_(statusBox.Text() + s); + statusBox.SelBgn_set(String_.Len(statusBox.Text()) - 1); + } + void Hide() {win.Hide();} + void Exec(GfoMsg msg) { + String cmdText = consoleBox.SelLen() == 0 ? consoleBox.Text() : consoleBox.SelText(); + String cmd = FixNewLines(cmdText); + GfoMsg runMsg = GfoMsg_.Null; + try {runMsg = GfsCore.Instance.MsgParser().ParseToMsg(cmd);} catch (Exception e) {statusBox.Text_("invalid gfml " + Err_.Message_gplx_full(e)); return;} + GfsCtx ctx = GfsCtx.new_(); + Object rv = GfsCore.Instance.ExecMany(ctx, runMsg); + resultBox.Text_(Object_.Xto_str_strict_or_empty(rv)); + } + void Help() { + statusBox.Text_(""); + if (consoleBox.SelLen() == 0) return; + String cmdText = "help:'" + consoleBox.SelText() + "';"; + String cmd = FixNewLines(cmdText); + GfoMsg runMsg = GfoMsg_.Null; + try {runMsg = GfmlDataNde.XtoMsgNoRoot(cmd);} catch (Exception e) {statusBox.Text_("invalid gfml " + Err_.Message_gplx_full(e)); return;} + GfsCtx ctx = GfsCtx.new_(); + try { + Object rv = GfsCore.Instance.ExecOne(ctx, runMsg); + if (rv != Gfo_invk_.Rv_handled && rv != Gfo_invk_.Rv_unhandled) { + UsrDlg_.Instance.Note(Object_.Xto_str_strict_or_empty(rv)); + } +// Results_add(FixNewLines(ctx.Results_XtoStr())); + } catch (Exception e) {statusBox.Text_("help failed " + Err_.Message_gplx_full(e)); return;} + } + void Save() { + String consoleFilStr = consoleFilBox.Text(); + Io_url url = Io_url_.Empty; + if (String_.Len_eq_0(consoleFilStr)) { + url = GfuiIoDialogUtl.SelectFile(); + consoleFilBox.Text_(url.Raw()); + return; + } + else + url = Io_url_.new_any_(consoleFilStr); + Io_mgr.Instance.SaveFilStr(url, consoleBox.Text()); + } + void Load() { + String consoleFilStr = consoleFilBox.Text(); + Io_url dir = Io_url_.Empty; + if (String_.Len_eq_0(consoleFilStr)) + dir = Io_url_.Empty; + else { + dir = Io_url_.new_any_(consoleFilStr); + dir = dir.OwnerDir(); + } + Io_url url = GfuiIoDialogUtl.SelectFile(dir); if (url == Io_url_.Empty) return; + consoleBox.Text_(Io_mgr.Instance.LoadFilStr(url)); + } + String FixNewLines(String cmd) { + cmd = String_.Replace(cmd, "\n", "\r\n"); + cmd = String_.Replace(cmd, "\r\r", "\r"); + return cmd; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_Exec)) Exec(m); + else if (ctx.Match(k, Invk_Hide)) Hide(); + else if (ctx.Match(k, Invk_Save)) Save(); + else if (ctx.Match(k, Invk_Load)) Load(); + else if (ctx.Match(k, Invk_Help)) Help(); + else if (ctx.Match(k, Invk_Clear)) statusBox.Text_(""); + else if (ctx.Match(k, "consoleBox")) return consoleBox; + else if (ctx.Match(k, Invk_X_)) { + int v = m.ReadInt("v"); + if (ctx.Deny()) return this; + win.X_(v); + } + else if (ctx.Match(k, Invk_Y_)) { + int v = m.ReadInt("v"); + if (ctx.Deny()) return this; + win.Y_(v); + } + else if (ctx.Match(k, Invk_Width_)) { + int v = m.ReadInt("v"); + if (ctx.Deny()) return this; + win.Width_(v); + } + else if (ctx.Match(k, Invk_Height_)) { + int v = m.ReadInt("v"); + if (ctx.Deny()) return this; + win.Height_(v); + } + else if (ctx.Match(k, Invk_Enabled_)) { + boolean v = m.ReadBool("v"); + if (ctx.Deny()) return this; + owner.Enabled_(v); + } + else if (ctx.Match(k, Invk_SaveUrl_)) { + Io_url v = m.ReadIoUrl("v"); + if (ctx.Deny()) return this; + consoleFilBox.Text_(v.Xto_api()); + consoleBox.Text_(Io_mgr.Instance.LoadFilStr(v)); + } + else return win.Invk(ctx, ikey, k, m); + return this; + } public static final String Invk_Exec = "Exec", Invk_Hide = "Hide", Invk_Save = "Save", Invk_Load = "Load", Invk_Help = "Help", Invk_Clear = "Clear" + , Invk_X_ = "X_", Invk_Y_ = "Y_", Invk_Width_ = "Width_", Invk_Height_ = "Height_", Invk_Enabled_ = "Enabled_", Invk_SaveUrl_ = "SaveUrl_" + ; + GfoConsoleWin owner; + public GfoConsoleWinCmds(GfoConsoleWin owner) {this.owner = owner;} +} +class GfuiTextBoxLogger implements Gfo_invk { + public GfuiTextBoxLogger Init(GfuiTextBox tbox) { + this.tbox = tbox; + win = tbox.OwnerWin(); + timer = TimerAdp.new_(this, Tmr_cmd, 500, false); + return this; + } + public void Write(String s) { + if (!owner.Enabled()) return; + if (!win.Visible()) win.Visible_set(true); + textToShow += tbox.Text() + String_.as_(s)+ String_.CrLf; + long curTick = System_.Ticks(); +// if (curTick - lastTick > 500) { + WriteText(textToShow); + textToShow = ""; + timer.Enabled_off(); +// } +// else +// timer.Enabled_on(); + lastTick = curTick; + } + void WriteText(String text) { + if (!win.Visible()) return; // StatusForm not visible; return; otherwise .CreateControl will be called + tbox.CreateControlIfNeeded(); // otherwise will occasionally throw: Cannot call Invoke or InvokeAsync on a control until the window handle has been created + tbox.Invoke(Gfo_invk_cmd.New_by_val(this, Invk_WriteText_cmd, text)); + } + void Invk_WriteText(String text) { + tbox.Text_(text); + tbox.SelBgn_set(String_.Len(text) - 1); + if (!tbox.Focus_has()) tbox.Focus(); + } + void WhenTick() { + WriteText(textToShow); + textToShow = ""; + timer.Enabled_off(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Tmr_cmd)) WhenTick(); + else if (ctx.Match(k, Invk_WriteText_cmd)) Invk_WriteText(m.ReadStr("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + String Invk_WriteText_cmd = "Invk_WriteText", Tmr_cmd = "Tmr"; + GfoConsoleWin owner; + public GfuiTextBoxLogger(GfoConsoleWin owner) {this.owner = owner;} + GfuiTextBox tbox; GfuiWin win; + TimerAdp timer; long lastTick; String textToShow = ""; +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfuiBnd_win_host.java b/150_gfui/src/gplx/gfui/controls/windows/GfuiBnd_win_host.java index a27517de8..fac35b0a3 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfuiBnd_win_host.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfuiBnd_win_host.java @@ -13,3 +13,26 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.ipts.*; import gplx.gfui.controls.elems.*; +public class GfuiBnd_win_host { + public GfuiWin HosterWin() {return hosterWin;} GfuiWin hosterWin; + public GfuiElem HosteeBox() {return hosteeBox;} GfuiElem hosteeBox; + public GfuiWin OwnerWin() {return ownerWin;} GfuiWin ownerWin; + public static GfuiBnd_win_host new_(GfuiElem elemToHost, GfuiWin ownerForm) { + GfuiBnd_win_host rv = new GfuiBnd_win_host(); + rv.ctor_(elemToHost, ownerForm); + return rv; + } + void ctor_(GfuiElem elemToHost, GfuiWin ownerForm) { + this.hosteeBox = elemToHost; + this.ownerWin = ownerForm; + this.hosterWin = GfuiWin_.toaster_("hosterWin_" + elemToHost.Key_of_GfuiElem(), ownerForm); + + hosterWin.IptBnds().EventsToFwd_set(IptEventType_.None); + hosterWin.Size_(hosteeBox.Size()); + hosteeBox.Owner_(hosterWin); + hosterWin.TaskbarVisible_(false); + hosterWin.TaskbarParkingWindowFix(ownerForm); // else ContextMenu shows up as WindowsFormsParkingWindow + } +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfuiCmdForm.java b/150_gfui/src/gplx/gfui/controls/windows/GfuiCmdForm.java index a27517de8..18acb538d 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfuiCmdForm.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfuiCmdForm.java @@ -13,3 +13,40 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.core.interfaces.*; +import gplx.gfui.ipts.*; import gplx.gfui.controls.elems.*; +public class GfuiCmdForm implements Gfo_invk, InjectAble { + public void Inject(Object ownerObj) { + GfuiElem owner = (GfuiElem)ownerObj; + GfuiCmdForm.host_(key, cmdElem, owner); + } + public static GfuiCmdForm new_(String key, GfuiElem cmdElem) { + GfuiCmdForm rv = new GfuiCmdForm(); + rv.key = key; + rv.cmdElem = cmdElem; + return rv; + } String key; GfuiElem cmdElem; + public static GfuiWin host_(String key, GfuiElem cmdElem, GfuiElem hostElem) { + GfuiWin rv = GfuiWin_.sub_(key, hostElem.OwnerWin()); rv.Size_(cmdElem.Size()); + cmdElem.Owner_(rv); + GfuiCmdForm cmd = new GfuiCmdForm(); cmd.cmdForm = rv; + + IptBnd_.cmd_to_(IptCfg_.Null, rv, cmd, HideMe_cmd, IptKey_.Escape); + IptBnd_.cmd_to_(IptCfg_.Null, hostElem, cmd, DoStuff, IptKey_.add_(IptKey_.Ctrl, IptKey_.Space), IptMouseBtn_.Right); + return rv; + } + + GfuiWin cmdForm; + static final String DoStuff = "doStuff", HideMe_cmd = "HideMe"; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, DoStuff)) ActivateMe((GfuiElem)ctx.MsgSrc()); + else if (ctx.Match(k, HideMe_cmd)) ((GfuiWin)ctx.MsgSrc()).Hide(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + void ActivateMe(GfuiElem elem) { + cmdForm.Pos_(elem.Pos().Op_add(elem.OwnerWin().Pos())); + cmdForm.Show(); + } +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfuiFocusMgr.java b/150_gfui/src/gplx/gfui/controls/windows/GfuiFocusMgr.java index a27517de8..cdb6bf282 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfuiFocusMgr.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfuiFocusMgr.java @@ -13,3 +13,17 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.controls.elems.*; +public class GfuiFocusMgr implements Gfo_evt_mgr_owner { + public static final String FocusChanged_evt = "focusChanged_evt"; + public Gfo_evt_mgr Evt_mgr() {if (evt_mgr == null) evt_mgr = new Gfo_evt_mgr(this); return evt_mgr;} Gfo_evt_mgr evt_mgr; + public GfuiElem FocusedElem() {return focusedElem;} GfuiElem focusedElem; + public GfuiElem FocusedElemPrev() {return focusedElemPrev;} GfuiElem focusedElemPrev; + public void FocusedElem_set(GfuiElem focused) { + focusedElemPrev = focusedElem; + this.focusedElem = focused; + Gfo_evt_mgr_.Pub_val(this, FocusChanged_evt, focused); + } + public static final GfuiFocusMgr Instance = new GfuiFocusMgr(); GfuiFocusMgr() {} +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfuiFocusOrderer.java b/150_gfui/src/gplx/gfui/controls/windows/GfuiFocusOrderer.java index a27517de8..af8de8673 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfuiFocusOrderer.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfuiFocusOrderer.java @@ -13,3 +13,45 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.core.lists.*; /*ComparerAble*/ +import gplx.gfui.controls.elems.*; +public class GfuiFocusOrderer { + public static void OrderByX(GfuiElem owner) {Order(owner, xcomparer, 0);} + public static void OrderByY(GfuiElem owner) {Order(owner, ycomparer, 0);} + static int Order(GfuiElem owner, ComparerAble comparer, int order) { + List_adp list = List_adp_.New(); + for (int i = 0; i < owner.SubElems().Count(); i++) { + GfuiElem sub = (GfuiElem)owner.SubElems().Get_at(i); + if (sub.Focus_idx() != NullVal) continue; + list.Add(sub); + } + list.Sort_by(comparer); + + for (Object subObj : list) { + GfuiElem sub = (GfuiElem)subObj; + sub.Focus_idx_(order++); + if (owner.SubElems().Count() > 0) // subs available; recurse; + order = Order(sub, comparer, order); + } + return order; + } + static GfuiFocusOrderer_cls_x xcomparer = new GfuiFocusOrderer_cls_x(); static GfuiFocusOrderer_cls_y ycomparer = new GfuiFocusOrderer_cls_y(); + public static final int NullVal = -1; +} +class GfuiFocusOrderer_cls_x implements ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + GfuiElem lhs = (GfuiElem)lhsObj, rhs = (GfuiElem)rhsObj; + if (lhs.Y() < rhs.Y()) return CompareAble_.Less; + else if (lhs.Y() > rhs.Y()) return CompareAble_.More; + else return Int_.Compare(lhs.X(), rhs.X()); + } +} +class GfuiFocusOrderer_cls_y implements ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + GfuiElem lhs = (GfuiElem)lhsObj, rhs = (GfuiElem)rhsObj; + if (lhs.X() < rhs.X()) return CompareAble_.Less; + else if (lhs.X() > rhs.X()) return CompareAble_.More; + else return Int_.Compare(lhs.Y(), rhs.Y()); + } +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfuiFocusOrderer_tst.java b/150_gfui/src/gplx/gfui/controls/windows/GfuiFocusOrderer_tst.java index a27517de8..30e3e27de 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfuiFocusOrderer_tst.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfuiFocusOrderer_tst.java @@ -13,3 +13,73 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import org.junit.*; import gplx.gfui.controls.elems.*; +public class GfuiFocusOrderer_tst { + @Before public void setup() { + owner = GfuiElem_.new_(); + list = List_adp_.New(); // list of all controls + } + @Test public void Horizontal() { + ini_Subs(owner, list, xy_(40, 0), xy_(20, 0), xy_(0, 0)); + tst_FocusIndxs(owner, list, 0, 1, 2); + + GfuiFocusOrderer.OrderByX(owner); + tst_FocusIndxs(owner, list, 2, 1, 0); + } + @Test public void Vertical() { + ini_Subs(owner, list, xy_(0, 40), xy_(0, 20), xy_(0, 0)); + tst_FocusIndxs(owner, list, 0, 1, 2); + + GfuiFocusOrderer.OrderByY(owner); + tst_FocusIndxs(owner, list, 2, 1, 0); + } + @Test public void Grid() { + ini_Subs(owner, list, xy_(20, 20), xy_(0, 20), xy_(20, 0), xy_(0, 0)); + tst_FocusIndxs(owner, list, 0, 1, 2, 3); + + GfuiFocusOrderer.OrderByX(owner); + tst_FocusIndxs(owner, list, 3, 2, 1, 0); + } + @Test public void Deep() { + ini_Subs(owner, list, xy_(20, 0), xy_(0, 0)); + GfuiElem sub0 = sub_(owner, 0), sub1 = sub_(owner, 1); + ini_Subs(sub0, list, xy_(20, 0), xy_(0, 0)); + ini_Subs(sub1, list, xy_(20, 0), xy_(0, 0)); + tst_FocusIndxs(owner, list, 0, 1, 0, 1, 0, 1); // 2 owner controls (0, 1); each has two subs (0, 1) + + GfuiFocusOrderer.OrderByX(owner); + tst_FocusIndxs(owner, list, 3, 0, 5, 4, 2, 1); + } + @Test public void Manusl() { + ini_Subs(owner, list, xy_(0, 0), xy_(20, 0)); + tst_FocusIndxs(owner, list, 0, 1); + + GfuiElem sub1 = owner.SubElems().Get_at(0); + GfuiElem sub2 = owner.SubElems().Get_at(1); + sub1.Focus_idx_(1); + sub2.Focus_idx_(0); + + GfuiFocusOrderer.OrderByX(owner); + tst_FocusIndxs(owner, list, 1, 0); + } + PointAdp xy_(int x, int y) {return PointAdp_.new_(x, y);} + GfuiElem sub_(GfuiElem owner, int i) {return owner.SubElems().Get_at(i);} + void ini_Subs(GfuiElem owner, List_adp list, PointAdp... points) { + for (int i = 0; i < points.length; i++) { + GfuiElem sub = GfuiElem_.sub_(Int_.To_str(i), owner); + sub.Pos_(points[i]); + sub.UnderElem().Core().Focus_index_set(i); + list.Add(sub); + } + } + void tst_FocusIndxs(GfuiElem owner, List_adp list, int... expd) { + int[] actl = new int[list.Count()]; + for (int i = 0; i < actl.length; i++) { + GfuiElem sub = (GfuiElem)list.Get_at(i); + actl[i] = sub.UnderElem().Core().Focus_index(); + } + Tfds.Eq_ary(expd, actl); + } + GfuiElem owner; List_adp list; +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfuiFocusXferBnd.java b/150_gfui/src/gplx/gfui/controls/windows/GfuiFocusXferBnd.java index a27517de8..b7a0bb630 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfuiFocusXferBnd.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfuiFocusXferBnd.java @@ -13,3 +13,40 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.core.interfaces.*; +import gplx.gfui.ipts.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.tabs.*; +public class GfuiFocusXferBnd implements InjectAble, Gfo_invk { + public void Inject(Object owner) { + GfuiElem elem = GfuiElem_.cast(owner); + IptBnd_.cmd_to_(IptCfg_.Null, elem, this, Invk_FocusNext, IptKey_.Down); + IptBnd_.cmd_to_(IptCfg_.Null, elem, this, Invk_FocusPrev, IptKey_.Up); + } + @gplx.Internal protected void Focus(GfuiElem cur, boolean fwd) { + List_adp allElemsInOwnerWin = List_adp_.New(); AddSubs(cur.OwnerWin(), allElemsInOwnerWin); + int curIdx = allElemsInOwnerWin.Idx_of(cur); + GfuiElem target = cur; + while (true) { // find next visible elem + int cycle = TabBox_.Cycle(fwd, curIdx, allElemsInOwnerWin.Count()); + target = GfuiElem_.cast(allElemsInOwnerWin.Get_at(cycle)); + if (target.Visible()) break; + if (cycle == curIdx) break; // either (a) one elem in allElemsInOwnerWin or (b) n elems, and cycled back to start; break, else infinite loop + curIdx = cycle; + } + target.Focus(); + } + void AddSubs(GfuiElem owner, List_adp allElemsInOwnerWin) { + for (int i = 0; i < owner.SubElems().Count(); i++) { + GfuiElemBase sub = (GfuiElemBase)owner.SubElems().Get_at(i); + if (sub.Click_able()) allElemsInOwnerWin.Add(sub); + AddSubs(sub, allElemsInOwnerWin); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_FocusNext)) Focus(GfuiElem_.cast(ctx.MsgSrc()), true); + else if (ctx.Match(k, Invk_FocusPrev)) Focus(GfuiElem_.cast(ctx.MsgSrc()), false); + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String Invk_FocusNext = "FocusNext", Invk_FocusPrev = "FocusPrev"; + public static final GfuiFocusXferBnd Instance = new GfuiFocusXferBnd(); GfuiFocusXferBnd() {} +} \ No newline at end of file diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfuiForm_menu.java b/150_gfui/src/gplx/gfui/controls/windows/GfuiForm_menu.java index a27517de8..e098e6b38 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfuiForm_menu.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfuiForm_menu.java @@ -13,3 +13,62 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.ipts.*; import gplx.gfui.controls.elems.*; +public class GfuiForm_menu implements Gfo_invk { + public GfuiWin Form() {return form;} GfuiWin form; + void Visible_toggle(GfoMsg msg) { + GfuiElem ownerForm = owner.OwnerWin(); + sub.OwnerWin().Zorder_front(); + form.Visible_set(!form.Visible()); + if (form.Visible()) { + PointAdp pos = ownerForm.Pos(); // NOTE: add ownerForm to handle different screens (ex: outerElem.OwnerWin.X = 1600 with dual screens)) + pos = pos.Op_add(owner.Rect().Pos()); + RectAdp rect = RectAdp_.vector_(pos, owner.Rect().Size()); + form.Pos_(GfuiMenuFormUtl.CalcShowPos(rect, form.Size())); + form.Focus(); // force refocus; last choice is lost, but otherwise focus remains on owner box + } + } + void Visible_hide(GfoMsg msg) { + form.Visible_set(false); + } + GfuiElem sub; GfuiElem owner; + void ctor_GfuiForm_menu(GfuiElem owner_, GfuiElem sub_, SizeAdp size) { + owner = owner_; sub = sub_; + sub.Size_(size); + form = GfuiWin_.sub_("test", owner.OwnerWin()); + + form.IptBnds().EventsToFwd_set(IptEventType_.None); + form.TaskbarVisible_(false); + form.Size_(sub.Size()); + sub.Owner_(form); +// form.CmdsA().Del(GfuiWin.Invk_Minimize); +// form.CmdsA().Del(GfuiStatusBoxBnd.Invk_ShowTime); + IptBnd_.cmd_to_(IptCfg_.Null, form, this, Visible_hide_cmd, IptKey_.Escape); + IptBnd_.cmd_to_(IptCfg_.Null, owner, this, Visible_toggle_cmd, IptKey_.add_(IptKey_.Ctrl, IptKey_.Space), IptMouseBtn_.Right); + + form.TaskbarParkingWindowFix(owner.OwnerWin()); // else ContextMenu shows up as WindowsFormsParkingWindow + form.QuitMode_(GfuiQuitMode.Suspend); + } + public static final String Msg_menu_Visible_toggle = "menu.visible_toggle"; + + + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Visible_hide_cmd)) Visible_hide(m); + else if (ctx.Match(k, Visible_toggle_cmd)) Visible_toggle(m); + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String Visible_hide_cmd = "Visible_hide", Visible_toggle_cmd = "Visible_toggle"; + public static GfuiWin new_(GfuiElem owner, GfuiElem sub, SizeAdp size) { + GfuiForm_menu rv = new GfuiForm_menu(); + rv.ctor_GfuiForm_menu(owner, sub, size); + return rv.Form(); + } + public static GfuiWin container3_(GfuiWin mainForm) { + GfuiWin form = GfuiWin_.sub_("test", mainForm); + form.TaskbarVisible_(false); + form.TaskbarParkingWindowFix(mainForm); // else ContextMenu shows up as WindowsFormsParkingWindow + form.QuitMode_(GfuiQuitMode.Suspend); + return form; + } +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfuiMenuBar.java b/150_gfui/src/gplx/gfui/controls/windows/GfuiMenuBar.java index a27517de8..912988990 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfuiMenuBar.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfuiMenuBar.java @@ -13,3 +13,242 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import java.awt.Color; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.AbstractButton; +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JSeparator; +import javax.swing.border.EmptyBorder; +import gplx.gfui.draws.*; import gplx.gfui.ipts.*; import gplx.gfui.layouts.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.gxws.*; +import gplx.langs.gfs.*; +public class GfuiMenuBar implements Gfo_invk { + public Object Under() {return winMenu;} + public boolean Visible() {return visible;} private boolean visible; + public void Visible_set(boolean v) { + this.visible = v; + MenuBar_visible_set(v); + boolean menuBandExists = win.Lyt().Bands_has(menuBand.Key()); + if ( visible && !menuBandExists) + win.Lyt().Bands_add(menuBand); + else if (!visible && menuBandExists) + win.Lyt().Bands_del(menuBand.Key()); + GftGrid.LytExecRecur(win); + } GftBand menuBand = GftBand.fillWidth_().Key_("GfuiMenuBar"); + public void Load_cfg(String cfgKey) { + try { + root.ForeColor_(ColorAdp_.Black).BackColor_(ColorAdp_.White); + root.ExecProps(); + curOwnerItm = root; + GfsCore.Instance.AddObj(this, "GfuiMenuBar_"); + GfsCore.Instance.ExecRegy("gplx.gfui.GfuiMenuBar.ini"); + } + catch (Exception e) {GfuiEnv_.ShowMsg(Err_.Message_gplx_full(e));} + } + String separatorText, mnemonicPrefix; int separatorIdx = 0; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_visible_toggle)) this.Visible_set(!visible); + else if (ctx.Match(k, Invk_SeparatorText_)) separatorText = GfoMsgUtl.SetStr(ctx, m, separatorText); + else if (ctx.Match(k, Invk_MnemonicPrefix_)) mnemonicPrefix = GfoMsgUtl.SetStr(ctx, m, mnemonicPrefix); + else if (ctx.Match(k, Invk_Visible_)) { + boolean v = m.ReadBoolOr("v", true); + if (ctx.Deny()) return this; + Visible_set(v); + } + else if (ctx.Match(k, Invk_RegTop)) { + String s = m.ReadStrOr("v", null); + if (ctx.Deny()) return this; + GfuiMenuBarItm itm = GfuiMenuBarItm.sub_(root); + itm.Type_(GfuiMenuBarItmType.Top); + itm.Text_(s); + itm.Ipt_(Read_prop_ipt(IptKey_.None, itm)); + itms.Add(s, itm); + itm.ExecProps(); + curOwnerItm = itm; + } + else if (ctx.Match(k, Invk_RegSpr)) { + String text = m.ReadStr("text"); + if (ctx.Deny()) return this; + GfuiMenuBarItm itm = GfuiMenuBarItm.sub_(curOwnerItm); + itm.Type_(GfuiMenuBarItmType.Spr); + itm.Text_(text); + itm.Key_(curOwnerItm.Key() + "." + text + Int_.To_str(separatorIdx++)); + itm.ExecProps(); + } + else if (ctx.Match(k, Invk_RegCmd)) { + String text = m.ReadStr("text"); + String cmd = m.ReadStrOr("cmd", null); + String tipText = m.ReadStrOr("tipText", null); + if (ctx.Deny()) return this; + GfuiMenuBarItm itm = GfuiMenuBarItm.sub_(curOwnerItm); + itm.Type_(GfuiMenuBarItmType.Cmd); + itm.Text_(text); + itm.Cmd_(cmd); + itm.TipText_(tipText); + itm.Ipt_(Read_prop_ipt(IptKey_.None, itm)); + itm.Key_(curOwnerItm.Key() + "." + text); + itm.ExecProps(); + } + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String Invk_visible_toggle = "MenuBar_toggle" + , Invk_Visible_ = "Visible_", Invk_SeparatorText_ = "SeparatorText_", Invk_MnemonicPrefix_ = "MnemonicPrefix_" + , Invk_RegTop = "RegTop", Invk_RegCmd = "RegCmd", Invk_RegSpr = "RegSpr" + ; + GfuiMenuBarItm curOwnerItm = null; + IptKey Read_prop_ipt(IptKey ipt, GfuiMenuBarItm itm) { + if (mnemonicPrefix == null) return ipt; + String text = itm.Text(); + int pos = String_.FindFwd(text, mnemonicPrefix); + if (pos == String_.Find_none || pos == String_.Len(text) - 1) return ipt; // mnemonic not found + String keyChar = String_.MidByLen(text, pos + 1, 1); + if (!Char_.IsLetterEnglish(String_.CharAt(keyChar, 0))) return ipt; // keyChar is not a character; EX: 'A & B' (keyChar = space) + String keyCharRaw = "key." + String_.Lower(keyChar); + ipt = IptKey_.parse(keyCharRaw); + text = String_.MidByLen(text, 0, pos) + String_.Mid(text, pos + 1); // remove mnemPrefix; ex: &File -> File && key.f + itm.Text_(text); + return ipt; + } + void MenuBar_visible_set(boolean v) {winMenu.setVisible(v);} + void InitMenuBar(GxwWin win) { + ownerForm = (JFrame)win; + ownerForm.setJMenuBar(winMenu); + winMenu.setBorder(GxwBorderFactory.Empty); + } + JMenuBar winMenu = new JMenuBar(); + JFrame ownerForm; + GfuiMenuBarItm root; + void Init(GfuiWin win) { + InitMenuBar((GxwWin)win.UnderElem()); + root = GfuiMenuBarItm.root_(winMenu); + itms.Add(root.Key(), root); + this.win = win; + IptBnd_.cmd_to_(GfuiEnv_.IptBndMgr_win, win, this, Invk_visible_toggle, IptKey_.add_(IptKey_.Ctrl, IptKey_.Shift, IptKey_.F12)); + win.SubItms_add(SubItms_key, this); + } + Hash_adp itms = Hash_adp_.New(); GfuiWin win; + public static final String SubItms_key = "menuBar"; + public static GfuiMenuBar new_(GfuiWin win) { + GfuiMenuBar rv = new GfuiMenuBar(); + rv.Init(win); + return rv; + } GfuiMenuBar() {} +} +class GfuiMenuBarItm { + public String Key() {return key;} public GfuiMenuBarItm Key_(String val) {key = val; return this;} private String key = ""; + public String Text() {return text;} public GfuiMenuBarItm Text_(String val) {text = val; return this;} private String text = ""; + public String Cmd() {return cmd;} public GfuiMenuBarItm Cmd_(String val) {cmd = val; return this;} private String cmd = ""; + public GfuiMenuBarItmType Type() {return type;} public GfuiMenuBarItm Type_(GfuiMenuBarItmType val) {type = val; return this;} GfuiMenuBarItmType type; + public IptKey Ipt() {return ipt;} public GfuiMenuBarItm Ipt_(IptKey val) {ipt = val; return this;} IptKey ipt; + public String TipText() {return tipText;} public GfuiMenuBarItm TipText_(String val) {tipText = val; return this;} private String tipText = ""; + public ColorAdp ForeColor() {return foreColor;} public GfuiMenuBarItm ForeColor_(ColorAdp val) {foreColor = val; return this;} ColorAdp foreColor; + public ColorAdp BackColor() {return backColor;} public GfuiMenuBarItm BackColor_(ColorAdp val) {backColor = val; return this;} ColorAdp backColor; + public String FontFamily() {return fontFamily;} public GfuiMenuBarItm FontFamily_(String val) {fontFamily = val; return this;} private String fontFamily = ""; + public float FontSize() {return fontSize;} public GfuiMenuBarItm FontSize_(float val) {fontSize = val; return this;} float fontSize = -1; + public FontStyleAdp FontStyle() {return fontStyle;} public GfuiMenuBarItm FontStyle_(FontStyleAdp val) {fontStyle = val; return this;} FontStyleAdp fontStyle; + public GfuiMenuBarItm OwnerItm() {return ownerItm;} public GfuiMenuBarItm OwnerItm_(GfuiMenuBarItm val) {ownerItm = val; return this;} GfuiMenuBarItm ownerItm; + public Object Under() {return under;} + void ExecProps() { + if (type == GfuiMenuBarItmType.Root) Exec_root(); + else if (type == GfuiMenuBarItmType.Cmd) Exec_cmd(); + else if (type == GfuiMenuBarItmType.Spr) Exec_spr(); + else Exec_mnu(); + } + void Exec_root() { + SetProps((JMenuBar)under); + } + void Exec_mnu() { + JMenu itm = new JMenu(text); + if (ownerItm.type == GfuiMenuBarItmType.Root) + ((JMenuBar)ownerItm.Under()).add(itm); + else { + ((JMenu)ownerItm.Under()).add(itm); + } + SetMnemonic(itm); + SetProps(itm); + under = itm; + } + void Exec_cmd() { + JMenuItem itm = new JMenuItem(text); + JMenu ownerMenu = ((JMenu)ownerItm.Under()); + ownerMenu.add(itm); + itmCmd = GfuiMenuBarItmCmd.new_(this); + itm.addActionListener(itmCmd); + SetMnemonic(itm); + SetProps(itm); + under = itm; + } + void Exec_spr() { + JMenu ownerMenu = ((JMenu)ownerItm.Under()); + JSeparator itm = new JSeparator(); + ownerMenu.add(itm); + SetProps(itm); + under = itm; + } + void SetMnemonic(AbstractButton itm) { + char mnem = GetMnemonic(text, ipt); + if (mnem != '\0') itm.setMnemonic(mnem); + } + void SetProps(JComponent itm) { + if (backColor != null) itm.setBackground(ColorAdpCache.Instance.GetNativeColor(backColor)); + if (foreColor != null) itm.setForeground(ColorAdpCache.Instance.GetNativeColor(foreColor)); + itm.setFont(MakeFont(itm.getFont())); + if (String_.Len(tipText) > 0) itm.setToolTipText(tipText); + } + Font MakeFont(Font cur) { + if (fontFamily == null && fontStyle == null && fontSize == -1) return cur; + String family = fontFamily == null ? cur.getFamily() : fontFamily; + int style = fontStyle == null ? cur.getStyle() : fontStyle.Val(); + int size = fontSize == -1 ? cur.getSize() : (int)fontSize; + return new Font(family, style, (int)size); + } + GfuiMenuBarItm Under_(Object o) {under = o; return this;} + char GetMnemonic(String text, IptKey ipt) { + if (ipt.Val() == IptKey_.None.Val()) return '\0'; + String iptStr = ipt.XtoUiStr(); if (String_.Len(iptStr) > 1) return '\0'; + return String_.CharAt(iptStr, 0); + } + GfuiMenuBarItmCmd itmCmd; + Object under; + public static GfoMsg CmdMsg(GfuiMenuBarItm itm) { + if (itm.cmd == null) throw Err_.new_null().Args_add("key", itm.key, "text", itm.text); + return gplx.gfml.GfmlDataNde.XtoMsgNoRoot(itm.cmd); + } + public static GfuiMenuBarItm new_() {return new GfuiMenuBarItm();} + public static GfuiMenuBarItm sub_(GfuiMenuBarItm owner) { + GfuiMenuBarItm rv = new_(); + rv.ownerItm = owner; + rv.foreColor = owner.foreColor; + rv.backColor = owner.backColor; + rv.fontFamily = owner.fontFamily; + rv.fontSize = owner.fontSize; + rv.fontStyle = owner.fontStyle; + return rv; + } + public static GfuiMenuBarItm root_(Object underMenuBar) { + GfuiMenuBarItm rv = new GfuiMenuBarItm().Type_(GfuiMenuBarItmType.Root).Key_("root").Under_(underMenuBar); + return rv; + } GfuiMenuBarItm() {} +} +class GfuiMenuBarItmType { + public int Val() {return val;} int val; + public String Name() {return name;} private String name; + GfuiMenuBarItmType(int v, String n) {val = v; name = n; regy.Add(n, this);} + public static GfuiMenuBarItmType parse(String raw) { + try {return (GfuiMenuBarItmType)regy.Get_by(raw);} + catch (Exception e) {Err_.Noop(e); throw Err_.new_parse("GfuiMenuBarItmType", raw);} + } + static Hash_adp regy = Hash_adp_.New(); + public static final GfuiMenuBarItmType Root = new GfuiMenuBarItmType(1, "root"); + public static final GfuiMenuBarItmType Top = new GfuiMenuBarItmType(2, "top"); + public static final GfuiMenuBarItmType Mnu = new GfuiMenuBarItmType(3, "mnu"); + public static final GfuiMenuBarItmType Cmd = new GfuiMenuBarItmType(4, "cmd"); + public static final GfuiMenuBarItmType Spr = new GfuiMenuBarItmType(5, "spr"); +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfuiMenuFormUtl.java b/150_gfui/src/gplx/gfui/controls/windows/GfuiMenuFormUtl.java index a27517de8..0d84c81c7 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfuiMenuFormUtl.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfuiMenuFormUtl.java @@ -13,3 +13,26 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.envs.*; +public class GfuiMenuFormUtl { + public static PointAdp CalcShowPos(RectAdp ownerRect, SizeAdp elemSize) { +//#if plat_wce +// int x = ownerRect.X() + ownerRect.Width() - elemSize.Width(); +// int y = ownerRect.Y() + ownerRect.Height() - elemSize.Height(); +//#else + int x = CursorAdp.Pos().X(); + int y = CursorAdp.Pos().Y(); + // check if entire elem fits inside owner at x,y; if not, align to ownerEdge + int ownerMinX = ownerRect.X(); + int ownerMinY = ownerRect.Y(); + int ownerMaxX = ownerRect.X() + ownerRect.Width(); + int ownerMaxY = ownerRect.Y() + ownerRect.Height(); + if (x < ownerMinX) x = ownerMinX; + if (y < ownerMinY) y = ownerMinY; + if (x + elemSize.Width() > ownerMaxX) x = ownerMaxX - elemSize.Width(); + if (y + elemSize.Height() > ownerMaxY) y = ownerMaxY - elemSize.Height(); +//#endif + return PointAdp_.new_(x, y); + } +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfuiQuitMode.java b/150_gfui/src/gplx/gfui/controls/windows/GfuiQuitMode.java index a27517de8..9ed6e4965 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfuiQuitMode.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfuiQuitMode.java @@ -13,3 +13,23 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.kits.core.*; +public class GfuiQuitMode { + public int Val() {return val;} int val; + GfuiQuitMode(int val) {this.val = val;} + public static final GfuiQuitMode ExitApp = new GfuiQuitMode(1); + public static final GfuiQuitMode Destroy = new GfuiQuitMode(2); + public static final GfuiQuitMode Suspend = new GfuiQuitMode(3); + public static final String + Destroy_cmd = "destroy" + , Suspend_cmd = "suspend" + , SuspendApp_cmd = "suspendApp" // TODO_OLD: merge with suspend; needs Msg Addressing (*.suspend vs app.suspend) + ; + public static void Exec(Gfo_invk invk, GfuiQuitMode stopMode) { + int val = stopMode.Val(); + if (val == GfuiQuitMode.Destroy.Val()) Gfo_invk_.Invk_by_key(invk, GfuiQuitMode.Destroy_cmd); + else if (val == GfuiQuitMode.Suspend.Val()) Gfo_invk_.Invk_by_key(invk, GfuiQuitMode.Suspend_cmd); + else if (val == GfuiQuitMode.ExitApp.Val()) GfuiEnv_.Exit(); + } +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfuiTipTextMgr.java b/150_gfui/src/gplx/gfui/controls/windows/GfuiTipTextMgr.java index a27517de8..1b9a4ed6e 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfuiTipTextMgr.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfuiTipTextMgr.java @@ -13,3 +13,20 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import javax.swing.JComponent; +import javax.swing.ToolTipManager; +import gplx.gfui.controls.elems.*; +class GfuiTipTextMgr implements GfuiWinOpenAble { + public void Open_exec(GfuiWin form, GfuiElemBase owner, GfuiElemBase sub) { + if (!(sub.UnderElem() instanceof JComponent)) return; + if (String_.Eq(sub.TipText(), "")) return; // don't register components without tooltips; will leave blue dots (blue tool tip windows with 1x1 size) + JComponent jcomp = (JComponent)sub.UnderElem(); + ToolTipManager.sharedInstance().registerComponent(jcomp); + ToolTipManager.sharedInstance().setInitialDelay(0); + ToolTipManager.sharedInstance().setDismissDelay(1000); + ToolTipManager.sharedInstance().setReshowDelay(0); + jcomp.setToolTipText(sub.TipText()); + } + public static final GfuiTipTextMgr Instance = new GfuiTipTextMgr(); +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfuiWin.java b/150_gfui/src/gplx/gfui/controls/windows/GfuiWin.java index a27517de8..fc1ef51d0 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfuiWin.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfuiWin.java @@ -13,3 +13,111 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import java.awt.Window; +import gplx.gfui.ipts.*; import gplx.gfui.layouts.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.customs.*; +import gplx.core.envs.*; import gplx.gfui.imgs.*; +public class GfuiWin extends GfuiElemBase { + private GxwWin win; private List_adp loadList = List_adp_.New(); + public void Show() {win.ShowWin();} + public void Hide() {win.HideWin();} + public void Close() {win.CloseWin();} + public IconAdp Icon() {return win.IconWin();} public GfuiWin Icon_(IconAdp icon) {win.IconWin_set(icon); return this;} + public boolean Pin() {return win.Pin();} public GfuiWin Pin_(boolean v) {win.Pin_set(v); return this;} + public GfuiWin Pin_() {return Pin_(true);} public void Pin_toggle() {Pin_(!Pin());} + @gplx.Virtual public void Quit() {GfuiQuitMode.Exec(this, quitMode);} + public boolean Maximized() {return win.Maximized();} public void Maximized_(boolean v) {win.Maximized_(v);} + public boolean Minimized() {return win.Minimized();} public void Minimized_(boolean v) {win.Minimized_(v);} + public GfuiQuitMode QuitMode() {return quitMode;} public GfuiWin QuitMode_(GfuiQuitMode val) {quitMode = val; return this;} private GfuiQuitMode quitMode = GfuiQuitMode.ExitApp; // easier to debug + @Override public boolean Opened_done() {return opened;} private boolean opened; + @Override public GfuiWin OwnerWin() {return this;} // TODO_OLD: null + @gplx.Internal protected GfuiWinKeyCmdMgr KeyCmdMgr() {return keyCmdMgr;} private GfuiWinKeyCmdMgr keyCmdMgr = GfuiWinKeyCmdMgr.new_(); + public GfuiWinFocusMgr FocusMgr() {return focusMgr;} private GfuiWinFocusMgr focusMgr; + @gplx.New public GfuiWin Size_(SizeAdp size) { + super.Size_(size); + if (!opened && (size.Width() < 112 || size.Height() < 27)) // WORKAROUND/WINFORMS: Form.Size must be > 112,27 if Form is not Visible + smallOpenSize = size; + return this; + } private SizeAdp smallOpenSize = SizeAdp_.Null; + @Override public void ctor_kit_GfuiElemBase(Gfui_kit kit, String key, GxwElem underElem, Keyval_hash ctorArgs) { + super.ctor_kit_GfuiElemBase(kit, key, underElem, ctorArgs); + win = (GxwWin)underElem; + win.OpenedCmd_set(Gfo_invk_cmd.New_by_key(this, Evt_Opened)); + Gfo_evt_mgr_.Sub(this, GfuiElemKeys.IptRcvd_evt, keyCmdMgr, GfuiWinKeyCmdMgr.CheckForHotKey_cmd); + IptBnd_.cmd_(IptCfg_.Null, this, StopAppByAltF4_evt, IptKey_.Alt.Add(IptKey_.F4)); +// IptBnd_.cmd_to_(IptCfg_.Null, this, GfoConsoleWin.Instance, GfoConsoleWin.Invk_Show, IptKey_.Ctrl.Add(IptKey_.Alt).Add(IptKey_.E)); + IptBnd_.cmd_(IptCfg_.Null, this, Invk_ShowFocusOwner, IptKey_.add_(IptKey_.Ctrl, IptKey_.Alt, IptKey_.F12)); + loadList.Add(keyCmdMgr); loadList.Add(GfuiTipTextMgr.Instance); + focusMgr = GfuiWinFocusMgr.new_(this); + } + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + super.ctor_GfuiBox_base(ctorArgs); + win = (GxwWin)this.UnderElem(); + win.OpenedCmd_set(Gfo_invk_cmd.New_by_key(this, Evt_Opened)); + Gfo_evt_mgr_.Sub(this, GfuiElemKeys.IptRcvd_evt, keyCmdMgr, GfuiWinKeyCmdMgr.CheckForHotKey_cmd); + IptBnd_.cmd_(IptCfg_.Null, this, StopAppByAltF4_evt, IptKey_.Alt.Add(IptKey_.F4)); + IptBnd_.cmd_to_(IptCfg_.Null, this, GfoConsoleWin.Instance, GfoConsoleWin.Invk_Show, IptKey_.Ctrl.Add(IptKey_.Alt).Add(IptKey_.E)); + IptBnd_.cmd_(IptCfg_.Null, this, Invk_ShowFocusOwner, IptKey_.add_(IptKey_.Ctrl, IptKey_.Alt, IptKey_.F12)); + loadList.Add(keyCmdMgr); loadList.Add(GfuiTipTextMgr.Instance); + focusMgr = GfuiWinFocusMgr.new_(this); + } + @Override public GxwElem UnderElem_make(Keyval_hash ctorArgs) { + String type = (String)ctorArgs.Get_val_or(GfuiWin_.InitKey_winType, GfuiWin_.InitKey_winType_app); + if (String_.Eq(type, GfuiWin_.InitKey_winType_tool)) return GxwElemFactory_.Instance.win_tool_(ctorArgs); + else if (String_.Eq(type, GfuiWin_.InitKey_winType_toaster)) return GxwElemFactory_.Instance.win_toaster_(ctorArgs); + else return GxwElemFactory_.Instance.win_app_(); + } + @Override public void Opened_cbk() { + if (!smallOpenSize.Eq(SizeAdp_.Null)) super.Size_(smallOpenSize); // NOTE: call before opened = true, else Layout will happen again + opened = true; + GftGrid.LytExecRecur(this); + GfuiWinUtl.Open_exec(this, loadList, this); + GfuiFocusOrderer.OrderByX(this); + focusMgr.Init(this); + if (this.Kit().Tid() == Gfui_kit_.Swing_tid) + ((Window)win).setFocusTraversalPolicy(new FocusTraversalPolicy_cls_base(focusMgr)); + this.Focus(); + super.Opened_cbk(); + Gfo_evt_mgr_.Pub(this, Evt_Opened); + } + @Override public boolean VisibleChangedCbk() { + boolean rv = super.VisibleChangedCbk(); + Gfo_evt_mgr_.Pub_val(this, Evt_VisibleChanged, this.Visible()); + return rv; + } + @Override public boolean DisposeCbk() { + GfuiWinUtl.SubElems_dispose(this); + return super.DisposeCbk(); + } + public GfuiWin TaskbarVisible_(boolean val) {win.TaskbarVisible_set(val); return this;} + public void TaskbarParkingWindowFix(GfuiWin owner) { + if (Env_.Mode_testing()) return; // FIXME: owner.UnderElem will throw exception in MediaPlaylistMgr_tst; see note there + if (owner == null) return; + win.TaskbarParkingWindowFix(owner.UnderElem()); + } + void StopAppByAltF4(IptEventData ipt) { + ipt.Handled_on(); // WORKAROUND/WINFORMS: must set Handled to true, or else WinForms.Form.Close() will be called +// GfoFwd_.Send_event(this, GfuiWin.Invk_Quit); // NOTE: no longer relying on Invk_Quit; // NOTE: must call send in order to execute other commands added to Cmds() (ex: DVD AppForm) + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_Quit)) {Quit(); Gfo_evt_mgr_.Pub(this, Evt_Quited);} + else if (ctx.Match(k, Invk_Zorder_front)) Zorder_front(); + else if (ctx.Match(k, Invk_Minimize)) {win.Minimized_(true); return this;} + else if (ctx.Match(k, Invk_Pin_toggle)) Pin_toggle(); + else if (ctx.Match(k, Invk_Show)) Show(); + else if (ctx.Match(k, Evt_Opened)) Opened_cbk(); + else if (ctx.Match(k, StopAppByAltF4_evt)) StopAppByAltF4(IptEventData.ctx_(ctx, m)); + else if (ctx.Match(k, Invk_ShowFocusOwner)) GfuiEnv_.ShowMsg(GfuiFocusMgr.Instance.FocusedElem().Key_of_GfuiElem()); + else if (ctx.Match(k, GfuiStatusBoxBnd.Invk_ShowTime)) {UsrDlg_.Instance.Note(UsrMsg.new_(Datetime_now.Get().toString())); return this;} + else if (ctx.MatchIn(k, Invk_Close, GfuiQuitMode.Destroy_cmd)) Close(); + else if (ctx.MatchIn(k, Invk_Hide, GfuiQuitMode.Suspend_cmd)) Hide(); + else { + Object rv = this.InvkMgr().Invk(ctx, ikey, k, m, this); + return (rv == Gfo_invk_cmd_mgr.Unhandled) ? super.Invk(ctx, ikey, k, m) : rv; + } + return this; + } public static final String Invk_Show = "Show", Invk_Hide = "Hide", Invk_Close = "Close", Invk_Quit = "Quit", Invk_Minimize = "Minimize" + , Invk_Pin_toggle = "Pin_toggle", Invk_Zorder_front = "Zorder_front", Invk_ShowFocusOwner = "ShowFocusOwner" + , Evt_VisibleChanged = "VisibleChanged", Evt_Opened = "Opened_evt", Evt_Quited = "Quited_evt" + , StopAppByAltF4_evt = "StopAppByAltF4_evt"; +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfuiWinFocusMgr.java b/150_gfui/src/gplx/gfui/controls/windows/GfuiWinFocusMgr.java index a27517de8..9f32a13e6 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfuiWinFocusMgr.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfuiWinFocusMgr.java @@ -13,3 +13,123 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import java.awt.Component; +import java.awt.Container; +import java.awt.FocusTraversalPolicy; +import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; +public class GfuiWinFocusMgr { + public List_adp SubElems() {return subElems;} List_adp subElems = List_adp_.New(); + public void InitForm() {this.Init(win);} + public void Init(GfuiWin win) { + subElems.Clear(); + InitRecursive(win, 0); + } + int InitRecursive(GfuiElem owner, int focusIdx) { + for (int i = 0; i < owner.SubElems().Count(); i++) { + GfuiElem sub = (GfuiElem)owner.SubElems().Get_at(i); + if (sub.Focus_able()) { + sub.Focus_idx_(focusIdx++); + subElems.Add(sub); + } + focusIdx = InitRecursive(sub, focusIdx); + } + return focusIdx; + } + public GfuiElem Focus(boolean fwd, int cur) { + int nxt = fwd + ? cur == subElems.Idx_last() ? 0 : ++cur + : cur == 0 ? subElems.Idx_last() : --cur; + GfuiElem elm = (GfuiElem)subElems.Get_at(nxt); + elm.Focus(); + return elm; + } + GfuiWin win; + public static GfuiWinFocusMgr new_(GfuiWin win) { + GfuiWinFocusMgr rv = new GfuiWinFocusMgr(); + rv.win = win; + return rv; + } GfuiWinFocusMgr() {} +} +class FocusTraversalPolicy_cls_base extends FocusTraversalPolicy { + List_adp elems; GfuiWinFocusMgr formFocusMgr; + public FocusTraversalPolicy_cls_base(GfuiWinFocusMgr formFocusMgr) { + this.elems = formFocusMgr.subElems; + this.formFocusMgr = formFocusMgr; + } + boolean elems_empty() {return elems.Count() == 0;} + public Component getComponentAfter(Container focusCycleRoot, Component c) { + formFocusMgr.InitForm(); + if (elems_empty()) return c; + GxwElem gxw = GxwElemOf(c); + int orig = gxw.Core().Focus_index(); + int idx = orig; + while (true) { + idx++; + if (idx == elems.Count()) + idx = 0; + GfuiElem elem = null; + try {elem = (GfuiElem)elems.Get_at(idx);} + catch (Exception e) { + System.out.println(idx); + Err_.Noop(e); + } + if (elem == null) return c; // FIXME: why is elem null?; REP: add new tab through history and then close out + if (elem.Focus_able() && elem.Visible()) { + if (elem.UnderElem() instanceof GxwTextMemo_lang) { + GxwTextMemo_lang xx = (GxwTextMemo_lang)elem.UnderElem(); + return xx.Inner(); + } + else + return (Component)elem.UnderElem(); + } + if (idx == orig) + return c; + } + } + public Component getComponentBefore(Container focusCycleRoot, Component c) { + formFocusMgr.InitForm(); + if (elems_empty()) return c; + GxwElem gxw = GxwElemOf(c); + int orig = gxw.Core().Focus_index(); + int idx = orig; + while (true) { + idx--; + if (idx == -1) + idx = elems.Count() - List_adp_.Base1; + GfuiElem elem = null; + try { + elem = (GfuiElem)elems.Get_at(idx); +// System.out.println(elem.Key_of_GfuiElem() + " " + elem.Focus_able() + " " + elem.Visible()); + if (elem.getClass().getName().equals("gplx.gfds.gbu.GbuGrid") && elem.Key_of_GfuiElem().equals("grid0")) { + System.out.println(elem.Key_of_GfuiElem() + " " + elem.Focus_able() + " " + elem.Visible()); + System.out.println(elem); + } + } + catch (Exception e) { + System.out.println(idx); + Err_.Noop(e); + } + if (elem == null) return c; // FIXME: why is elem null?; REP: add new tab through history and then close out + if (elem.Focus_able() && elem.Visible()) { + if (elem.UnderElem() instanceof GxwTextMemo_lang) { + GxwTextMemo_lang xx = (GxwTextMemo_lang)elem.UnderElem(); + return xx.Inner(); + } + else + return (Component)elem.UnderElem(); + } + if (idx == orig) + return c; + } + } + public Component getDefaultComponent(Container focusCycleRoot) {return getFirstComponent(focusCycleRoot);} + public Component getFirstComponent(Container focusCycleRoot) {return elems_empty() ? focusCycleRoot : (Component)FetchAt(0).UnderElem();} + public Component getLastComponent(Container focusCycleRoot) {return elems_empty() ? focusCycleRoot : (Component)FetchAt(elems.Count() - 1).UnderElem();} + GfuiElem FetchAt(int idx) {return (GfuiElem)elems.Get_at(idx);} + GxwElem GxwElemOf(Component c) { + if (GxwElem.class.isAssignableFrom(c.getClass())) return (GxwElem)c; + return (GxwElem)c.getParent(); // HACK: occurs for JComboBox when editable is true; focus is on MetalComboBox, with parent of JComboBox + } +} +//#} \ No newline at end of file diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfuiWinKeyCmdMgr.java b/150_gfui/src/gplx/gfui/controls/windows/GfuiWinKeyCmdMgr.java index a27517de8..5fecdc673 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfuiWinKeyCmdMgr.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfuiWinKeyCmdMgr.java @@ -13,3 +13,48 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.ipts.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; +import gplx.core.lists.*; import gplx.core.bits.*; +public class GfuiWinKeyCmdMgr implements GfuiWinOpenAble, Gfo_invk, Gfo_evt_itm { + private Hash_adp_list listHash = Hash_adp_list.new_(); + public Gfo_evt_mgr Evt_mgr() {if (evt_mgr == null) evt_mgr = new Gfo_evt_mgr(this); return evt_mgr;} private Gfo_evt_mgr evt_mgr; + public void Open_exec(GfuiWin form, GfuiElemBase owner, GfuiElemBase sub) { + int keyVal = sub.Click_key().Val(); if (sub.Click_key().Eq(IptKey_.None)) return; + listHash.AddInList(keyVal, sub); + listHash.AddInList(keyVal ^ IptKey_.Alt.Val(), sub); + } + public boolean CheckForHotKey(IptEventData iptData) { + if (iptData.EventType() != IptEventType_.KeyDown) return false; // NOTE: MouseClick sometimes will send key + int keyVal = iptData.Key().Val(); + GfuiElem sender = GfuiElem_.as_(iptData.Sender()); + if (GfuiTextBox_.as_(sender) != null // is sender textBox? + && !Bitmask_.Has_int(keyVal, IptKey_.Alt.Val()) // does key not have alt + ) return false; // ignore keys from textbox if they do not have alt + List_adp elemList = (List_adp)listHash.Get_by(keyVal); if (elemList == null) return false; + for (int i = 0; i < elemList.Count(); i++) { + GfuiElem elem = (GfuiElem)elemList.Get_at(i); + if (elem.Visible()) + elem.Click(); + } + return true; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + ctx.Match(k, k); + CheckForHotKey(IptEventData.ctx_(ctx, m)); + //boolean handled = CheckForHotKey(IptEventData.cast(msg.Val())); msg.Fwd_set(!handled); // TOMBSTONE: somehow cause alt-F4 to continue processing and dispose form + return this; + } @gplx.Internal protected static final String CheckForHotKey_cmd = "CheckForHotKey_cmd"; + + public static GfuiWinKeyCmdMgr new_() {return new GfuiWinKeyCmdMgr();} GfuiWinKeyCmdMgr() {} + public static int ExtractPosFromText(String raw) { + int pos = String_.FindFwd(raw, "&"); + if (pos == String_.Find_none || pos == String_.Len(raw) - 1) return String_.Find_none; // & notFound or last char; return; + char nextChar = String_.CharAt(raw, pos + 1); + return Char_.IsLetterEnglish(nextChar) ? pos : String_.Find_none; // NOTE: .IsLetter checks against space; EX: "me & you" shouldn't bring back space + } + public static IptKey ExtractKeyFromText(String raw) { + int pos = ExtractPosFromText(raw); if (pos == String_.Find_none) return IptKey_.None; + return IptKey_.parse("key." + String_.Lower(Char_.To_str(String_.CharAt(raw, pos + 1)))); // pos=& pos; + 1 to get next letter + } +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfuiWin_.java b/150_gfui/src/gplx/gfui/controls/windows/GfuiWin_.java index a27517de8..045264fa9 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfuiWin_.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfuiWin_.java @@ -13,3 +13,59 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.gfui.kits.core.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; +public class GfuiWin_ { + public static final String + InitKey_winType = "winType" + , InitKey_winType_toaster = "toaster" + , InitKey_winType_app = "app" + , InitKey_winType_tool = "tool" + ; + public static GfuiWin as_(Object obj) {return obj instanceof GfuiWin ? (GfuiWin)obj : null;} + public static GfuiWin cast(Object obj) {try {return (GfuiWin)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, GfuiWin.class, obj);}} + public static GfuiWin app_(String key) {return bld_(key, InitKey_winType_app, new Keyval_hash());} + public static GfuiWin tool_(String key) {return bld_(key, InitKey_winType_tool, new Keyval_hash()).TaskbarVisible_(false);} + public static GfuiWin sub_(String key, GfuiWin ownerWin) { + Keyval_hash ctorArgs = new Keyval_hash(); + if (ownerWin != null) ctorArgs.Add(GfuiElem_.InitKey_ownerWin, ownerWin); // WORKAROUND/JAVA: null ownerWin will fail when adding to hash + return bld_(key, InitKey_winType_tool, ctorArgs); + } + public static GfuiWin toaster_(String key, GfuiWin ownerWin) {return bld_(key, InitKey_winType_toaster, new Keyval_hash().Add(GfuiElem_.InitKey_ownerWin, ownerWin));} + static GfuiWin bld_(String key, String winType, Keyval_hash ctorArgs) { + GfuiWin rv = new GfuiWin(); + rv.Key_of_GfuiElem_(key); + ctorArgs.Add(InitKey_winType, winType) + .Add(GfuiElem_.InitKey_focusAble, false); + rv.ctor_GfuiBox_base(ctorArgs); + return rv; + } + public static GfuiWin kit_(Gfui_kit kit, String key, GxwWin under, Keyval_hash ctorArgs) { + GfuiWin rv = new GfuiWin(); + rv.ctor_kit_GfuiElemBase(kit, key, under, ctorArgs); + return rv; + } +} +class GfuiWinUtl { + @gplx.Internal protected static void Open_exec(GfuiWin win, List_adp loadList, GfuiElemBase owner) { + for (int i = 0; i < owner.SubElems().Count(); i++) { + GfuiElemBase sub = (GfuiElemBase)owner.SubElems().Get_at(i); + sub.OwnerWin_(win); + for (Object itmObj : loadList) { + GfuiWinOpenAble itm = (GfuiWinOpenAble)itmObj; + itm.Open_exec(win, owner, sub); + } + Open_exec(win, loadList, sub); + } + } + @gplx.Internal protected static void SubElems_dispose(GfuiElem owner) { + for (int i = 0; i < owner.SubElems().Count(); i++) { + GfuiElem sub = (GfuiElem)owner.SubElems().Get_at(i); + sub.Dispose(); + SubElems_dispose(sub); + } + } +} +interface GfuiWinOpenAble { + void Open_exec(GfuiWin win, GfuiElemBase owner, GfuiElemBase sub); +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GfuiWin_toaster.java b/150_gfui/src/gplx/gfui/controls/windows/GfuiWin_toaster.java index a27517de8..bb03e306b 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GfuiWin_toaster.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GfuiWin_toaster.java @@ -13,3 +13,168 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import gplx.core.envs.*; +import gplx.gfui.draws.*; import gplx.gfui.envs.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; import gplx.gfui.controls.windows.*; +public class GfuiWin_toaster extends GfuiWin { public void ShowPopup(GfuiWin owner, String text, int interval) { + this.TaskbarParkingWindowFix(owner); + ShowPopup(text, interval); + } + void ShowPopup(String text, int interval) { + if (Env_.Mode_testing()) return; + messageLabel.Text_(text); + messageLabel.SelBgn_set(0); + InitVariables(500, interval * 1000, 500); + BeginPoppingUp(); + } + void InitVariables(int growingArg, int fullyGrownArg, int timeForHidingArg) { + popupState = PopupState.FullyShrunk; + fullyGrownTimerInterval = fullyGrownArg; + int timerEvents = 0; + if (growingArg > 10) { + timerEvents = Math_.Min((growingArg / 10), fullyGrown.Height()); + growingTimerInterval = growingArg / timerEvents; + growingIncrement = fullyGrown.Height() / timerEvents; + } + else { + growingTimerInterval = 10; + growingIncrement = fullyGrown.Height(); + } + + if( timeForHidingArg > 10) { + timerEvents = Math_.Min((timeForHidingArg / 10), fullyGrown.Height()); + shrinkingTimerInterval = timeForHidingArg / timerEvents; + shrinkingIncrement = fullyGrown.Height() / timerEvents; + } + else { + shrinkingTimerInterval = 10; + shrinkingIncrement = fullyGrown.Height(); + } + } + void BeginPoppingUp() { + RectAdp screenRect = ScreenAdp_.Primary.Rect();//WorkingArea + int screenX_max = screenRect.X() + screenRect.Width(); + int val = popupState.Val(); + if (val == PopupState.FullyShrunk.Val()) { + this.Size_(SizeAdp_.new_(this.Width(), 0)); + this.Pos_(screenX_max / 2 - this.Width()/2, PopupAnchorTop); //screenRect.Bottom - 1 + // gplx.gfui.npis.FormNpi.BringToFrontDoNotFocus(gplx.gfui.npis.ControlNpi.Hwnd(this.UnderElem())); + if (!this.Visible()) { +// GfuiElem last = GfuiFocusMgr.Instance.FocusedElem(); + this.Visible_on_(); +// GfuiFocusMgr.Instance.FocusedElem_set(last); + } + timer.Interval_(growingTimerInterval); + popupState = PopupState.Growing; + } + else if (val == PopupState.Growing.Val()) { + this.Redraw(); + } + else if (val == PopupState.FullyGrown.Val()) { + timer.Interval_(fullyGrownTimerInterval); + this.Redraw(); + } + else if (val == PopupState.Shrinking.Val()) { + this.Size_(SizeAdp_.new_(this.Width(), 0)); + this.Pos_(screenX_max / 2 - this.Width()/2, PopupAnchorTop); //screenRect.Bottom - 1 + timer.Interval_(fullyGrownTimerInterval); + popupState = PopupState.FullyGrown; + } + timer.Enabled_on(); + } +// public override boolean FocusGotCbk() { +// GfuiElem last = GfuiFocusMgr.Instance.FocusedElemPrev(); +// GfuiFocusMgr.Instance.FocusedElem_set(last); +// last.Focus(); +// return false; +// } + + void WhenTick() { + int fullHeight = fullyGrown.Height(); + int val = popupState.Val(); + if (val == PopupState.Growing.Val()) { + if (this.Height() < fullHeight) + ChangeBounds(true, growingIncrement); + else { + this.Height_(fullHeight); + timer.Interval_(fullyGrownTimerInterval); + popupState = PopupState.FullyGrown; + } + } + else if (val == PopupState.FullyGrown.Val()) { + timer.Interval_(shrinkingTimerInterval); + // if ((bKeepVisibleOnMouseOver && !bIsMouseOverPopup ) || (!bKeepVisibleOnMouseOver)) { + popupState = PopupState.Shrinking; + } + else if (val == PopupState.Shrinking.Val()) { + // if (bReShowOnMouseOver && bIsMouseOverPopup) {popupState = PopupState.Growing; break;} + if (this.Height() > 2) // NOTE.Val()) { does not shrink less than 2 //this.Top > screenRect.Bottom + ChangeBounds(false, shrinkingIncrement); + else { +// this.Pos_(-500, -500); // WORKAROUND:JAVA: cannot do this.Hide() b/c it will focus ownerForm; EX: typing in textApp when musicApp moves forward + this.Visible_off_(); + popupState = PopupState.FullyShrunk; + timer.Enabled_off(); + } + } + } + static final int PopupAnchorTop = -1; // HACK: wxp1 showed obvious flickering with top edge + void ChangeBounds(boolean isGrowing, int increment) { + increment = isGrowing ? increment : -increment; + this.Pos_(this.X(), PopupAnchorTop); //this.Top - increment + this.Size_(SizeAdp_.new_(this.Width(), this.Height() + increment)); + } + @Override public GxwElem UnderElem_make(Keyval_hash ctorArgs) {return GxwElemFactory_.Instance.win_toaster_(ctorArgs);} + + @Override public void ctor_GfuiBox_base(Keyval_hash ctorArgs) { + super.ctor_GfuiBox_base(ctorArgs); + this.fullyGrown = SizeAdp_.new_(600, 96); + this.Pos_(-100, -100); this.Size_(fullyGrown); super.Show(); super.Hide();// was 20,20; set to fullyGrown b/c of java + messageLabel = GfuiTextBox_.multi_("messageLabel", this); + messageLabel.Size_(fullyGrown.Width(), fullyGrown.Height()).ForeColor_(ColorAdp_.Green); + messageLabel.TextMgr().Font_(FontAdp.new_("Arial", 8, FontStyleAdp_.Bold)); + messageLabel.Border_on_(true); + messageLabel.Focus_able_(false); +// this.Focus_able_(false); +// this.UnderElem().Core().Focus_able_force_(false); + timer = TimerAdp.new_(this, Tmr_cmd, 3000, false); + + GxwWin formRef = (GxwWin)this.UnderElem(); + if (formRef != null) { // FIXME: nullCheck, needed for MediaPlaylistMgr_tst + formRef.Pin_set(true); + formRef.TaskbarVisible_set(false); + } + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Tmr_cmd)) WhenTick(); + else super.Invk(ctx, ikey, k, m); + return this; + } public static final String Tmr_cmd = "Tmr"; + GfuiTextMemo messageLabel; + TimerAdp timer; + SizeAdp fullyGrown = SizeAdp_.Zero; + int growingIncrement, shrinkingIncrement; + int growingTimerInterval, shrinkingTimerInterval, fullyGrownTimerInterval; + PopupState popupState = PopupState.FullyShrunk; + public static GfuiWin_toaster new_(GfuiWin owner) { + GfuiWin_toaster rv = new GfuiWin_toaster(); +// rv.Icon_(IconAdp.cfg_("popup")); + rv.ctor_GfuiBox_base + (new Keyval_hash() + .Add(GfuiElem_.InitKey_focusAble, false) + .Add(GfuiElem_.InitKey_ownerWin, owner) + .Add(GfuiWin_.InitKey_winType, GfuiWin_.InitKey_winType_toaster) + ); + return rv; + } +} +class PopupState { + public int Val() {return val;} int val; + public PopupState(int v) {this.val = v;} + public static final PopupState + FullyShrunk = new PopupState(1) + , Growing = new PopupState(2) + , FullyGrown = new PopupState(3) + , Shrinking = new PopupState(4) + ; +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GxwBorderFactory.java b/150_gfui/src/gplx/gfui/controls/windows/GxwBorderFactory.java index a27517de8..299a3f2d8 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GxwBorderFactory.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GxwBorderFactory.java @@ -13,3 +13,28 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.border.EmptyBorder; +import gplx.*; import gplx.gfui.*; +import gplx.gfui.kits.core.GfuiEnv_; +import gplx.langs.gfs.GfsCore; +public class GxwBorderFactory { + public static final javax.swing.border.Border Empty = new EmptyBorder(0, 0, 1, 0); +} +class GfuiMenuBarItmCmd implements ActionListener { + public void actionPerformed(ActionEvent ev) { + try { + GfsCore.Instance.ExecOne(GfsCtx.Instance, GfuiMenuBarItm.CmdMsg(itm)); + } + catch (Exception e) { + GfuiEnv_.ShowMsg(Err_.Message_gplx_full(e)); + } + } + public static GfuiMenuBarItmCmd new_(GfuiMenuBarItm itm) { + GfuiMenuBarItmCmd cmd = new GfuiMenuBarItmCmd(); + cmd.itm = itm; + return cmd; + } GfuiMenuBarItm itm; +} diff --git a/150_gfui/src/gplx/gfui/controls/windows/GxwWinNpi.java b/150_gfui/src/gplx/gfui/controls/windows/GxwWinNpi.java index a27517de8..fe31b517d 100644 --- a/150_gfui/src/gplx/gfui/controls/windows/GxwWinNpi.java +++ b/150_gfui/src/gplx/gfui/controls/windows/GxwWinNpi.java @@ -13,3 +13,6 @@ 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.gfui.controls.windows; import gplx.*; import gplx.gfui.*; import gplx.gfui.controls.*; +class GxwWinNpi { +} diff --git a/150_gfui/src/gplx/gfui/draws/ColorAdp.java b/150_gfui/src/gplx/gfui/draws/ColorAdp.java index a27517de8..fc00f7f5e 100644 --- a/150_gfui/src/gplx/gfui/draws/ColorAdp.java +++ b/150_gfui/src/gplx/gfui/draws/ColorAdp.java @@ -13,3 +13,32 @@ 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.gfui.draws; import gplx.*; import gplx.gfui.*; +import gplx.core.strings.*; import gplx.core.encoders.*; +public class ColorAdp { + public int Value() {return val;} int val; + public int Alpha() {return (255 & val >> 24);} + public int Red () {return (255 & val >> 16);} + public int Green() {return (255 & val >> 8);} + public int Blue() {return (255 & val);} + public String XtoHexStr() { + String_bldr sb = String_bldr_.new_(); + sb.Add("#"); + sb.Add(Hex_utl_.To_str(Alpha(), 2)); + sb.Add(Hex_utl_.To_str(Red(), 2)); + sb.Add(Hex_utl_.To_str(Green(), 2)); + sb.Add(Hex_utl_.To_str(Blue(), 2)); + return sb.To_str(); + } + public boolean Eq(Object obj) { + ColorAdp comp = ColorAdp_.as_(obj); if (comp == null) return false; + return Object_.Eq(val, comp.val); + } + @Override public String toString() {return XtoHexStr();} + public Object CloneNew() {return this;} // NOTE: 'return this' works b/c ColorAdp is read-only class; needed for comparisons; ex: ColorAdp_.Null == ColorAdp_.Null.CloneNew(); alternative would fail: return ColorAdp.new_(this.Alpha(), this.Red(), this.Green(), this.Blue());} + public static ColorAdp new_(int alpha, int red, int green, int blue) { + ColorAdp rv = new ColorAdp(); + rv.val = (int)((alpha << 24) | (red << 16) | (green << 8) | (blue)); + return rv; + } ColorAdp() {} +} diff --git a/150_gfui/src/gplx/gfui/draws/ColorAdpCache.java b/150_gfui/src/gplx/gfui/draws/ColorAdpCache.java index a27517de8..c7d7a597d 100644 --- a/150_gfui/src/gplx/gfui/draws/ColorAdpCache.java +++ b/150_gfui/src/gplx/gfui/draws/ColorAdpCache.java @@ -13,3 +13,14 @@ 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.gfui.draws; import gplx.*; import gplx.gfui.*; +public class ColorAdpCache { + public java.awt.Color GetNativeColor(ColorAdp color) { + Object rv = hash.Get_by(color.Value()); if (rv != null) return (java.awt.Color)rv; + rv = new java.awt.Color(color.Red(), color.Green(), color.Blue(), color.Alpha()); + hash.Add(color.Value(), rv); + return (java.awt.Color)rv; + } + Hash_adp hash = Hash_adp_.New(); + public static final ColorAdpCache Instance = new ColorAdpCache(); ColorAdpCache() {} +} diff --git a/150_gfui/src/gplx/gfui/draws/ColorAdp_.java b/150_gfui/src/gplx/gfui/draws/ColorAdp_.java index a27517de8..b02ee0a0b 100644 --- a/150_gfui/src/gplx/gfui/draws/ColorAdp_.java +++ b/150_gfui/src/gplx/gfui/draws/ColorAdp_.java @@ -13,3 +13,104 @@ 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.gfui.draws; import gplx.*; import gplx.gfui.*; +import gplx.core.encoders.*; import gplx.core.interfaces.*; +public class ColorAdp_ implements ParseAble { + public static ColorAdp as_(Object obj) {return obj instanceof ColorAdp ? (ColorAdp)obj : null;} + public static ColorAdp cast(Object obj) {try {return (ColorAdp)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, ColorAdp.class, obj);}} + public static ColorAdp new_(int a, int r, int g, int b) {return ColorAdp.new_((int)a, (int)r, (int)g, (int)b);} + public static final ColorAdp_ Parser = new ColorAdp_(); + public Object ParseAsObj(String raw) {return ColorAdp_.parse(raw);} + public static ColorAdp parseOr_(String raw, ColorAdp or) { + ColorAdp rv = parse_internal_(raw); if (rv == null) return or; + return rv; + } + public static ColorAdp parse(String raw) { + ColorAdp rv = parse_internal_(raw); if (rv == null) throw Err_.new_parse_type(ColorAdp.class, raw); + return rv; + } + static ColorAdp parse_internal_(String raw) { + if (raw == null) return null; + char firstChar = String_.CharAt(raw, 0); + if (firstChar == '#') return parse_hex_(raw); + else if (Char_.IsNumber(firstChar)) return parse_int_(raw); + String rawLower = String_.Lower(raw); + if (String_.Eq(rawLower, "black")) return Black; + else if (String_.Eq(rawLower, "white")) return White; + else if (String_.Eq(rawLower, "gray")) return Gray; + else if (String_.Eq(rawLower, "red")) return Red; + else if (String_.Eq(rawLower, "lime")) return Lime; + else if (String_.Eq(rawLower, "blue")) return Blue; + else if (String_.Eq(rawLower, "yellow")) return Yellow; + else if (String_.Eq(rawLower, "fuchsia")) return Fuchsia; + else if (String_.Eq(rawLower, "maroon")) return Maroon; + else if (String_.Eq(rawLower, "green")) return Green; + else if (String_.Eq(rawLower, "navy")) return Navy; + else if (String_.Eq(rawLower, "olive")) return Olive; + else if (String_.Eq(rawLower, "purple")) return Purple; + else if (String_.Eq(rawLower, "teal")) return Teal; + else if (String_.Eq(rawLower, "brown")) return Brown; + else if (String_.Eq(rawLower, "lightgray")) return LightGray; + else + return null; + } + public static ColorAdp parse_hex_(String raw) { + try { + int[] ary = new int[4]; // make ARGB ary + int idx = 0; + int rawLen = String_.Len(raw); + for (int i = 1; i < rawLen; i += 2) { // fill ARGB ary by parsing raw 2 at a time; EX: #FFFFFFFF -> 255,255,255,255; NOTE: start at 1 to ignore leading # + String hexStr = String_.MidByLen(raw, i, 2); + ary[idx++] = Hex_utl_.Parse(hexStr); + } + return ColorAdp.new_(ary[0], ary[1], ary[2], ary[3]); + } catch (Exception exc) {throw Err_.new_parse_exc(exc, ColorAdp.class, raw);} + } + @gplx.Internal protected static ColorAdp parse_int_(String v) { + String[] ary = String_.Split(v, ","); + switch (ary.length) { + case 1: return new_int_(Int_.Parse(ary[0])); + case 3: + case 4: return parse_int_ary_(ary); + default: throw Err_.new_wo_type("invalid array", "len", ary.length); + } + } + static ColorAdp parse_int_ary_(String[] ary) { + int a; + int idx = 0; + if (ary.length == 3) {idx = 0; a = 255;} + else {idx = 1; a = Int_.Parse(ary[0]);} + int r = Int_.Parse(ary[idx++]); + int g = Int_.Parse(ary[idx++]); + int b = Int_.Parse(ary[idx++]); + return ColorAdp_.new_(a, r, g, b); + } + public static ColorAdp new_int_(int val) { + int a = ((val >> 24) & 255); + int r = ((val >> 16) & 255); + int g = ((val >> 8) & 255); + int b = ((val) & 255); + return ColorAdp.new_(a, r, g, b); + } + public static ColorAdp read_(Object o) {String s = String_.as_(o); return s != null ? ColorAdp_.parse(s) : ColorAdp_.cast(o);} + public static final ColorAdp + Null = new_( 0, 0, 0, 0) + , Black = new_(255, 0, 0, 0) + , White = new_(255, 255, 255, 255) + , Gray = new_(255, 128, 128, 128) + , Red = new_(255, 255, 0, 0) + , Lime = new_(255, 0, 255, 0) + , Blue = new_(255, 0, 0, 255) + , Yellow = new_(255, 255, 255, 0) + , Fuchsia = new_(255, 255, 0, 255) + , Aqua = new_(255, 0, 255, 255) + , Maroon = new_(255, 128, 0, 0) + , Green = new_(255, 0, 128, 0) + , Navy = new_(255, 0, 0, 128) + , Olive = new_(255, 128, 128, 0) + , Purple = new_(255, 128, 0, 128) + , Teal = new_(255, 0, 128, 128) + , Brown = new_(255, 165, 42, 42) + , LightGray = new_(255, 211, 211, 211) + ; +} diff --git a/150_gfui/src/gplx/gfui/draws/ColorAdp__tst.java b/150_gfui/src/gplx/gfui/draws/ColorAdp__tst.java index a27517de8..f28df31bf 100644 --- a/150_gfui/src/gplx/gfui/draws/ColorAdp__tst.java +++ b/150_gfui/src/gplx/gfui/draws/ColorAdp__tst.java @@ -13,3 +13,44 @@ 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.gfui.draws; import gplx.*; import gplx.gfui.*; +import org.junit.*; +public class ColorAdp__tst { + @Test public void parse_hex_() { + tst_parse_hex_("#00000000", 0, 0, 0, 0); + tst_parse_hex_("#000102FF", 0, 1, 2, 255); + tst_parse_hex_("#FF000102", 255, 0, 1, 2); + } + @Test public void parse_int_() { + tst_parse_int_(0, 0, 0, 0, 0); + tst_parse_int_(255, 0, 0, 0, 255); + tst_parse_int_(65535, 0, 0, 255, 255); + tst_parse_int_(16777215, 0, 255, 255, 255); + tst_parse_int_(Int_.Max_value, 127, 255, 255, 255); + tst_parse_int_(-1, 255, 255, 255, 255); + } + @Test public void parse() { + tst_parse_("0,0,0,0", 0, 0, 0, 0); // parse all ints + tst_parse_("0,0,0", 255, 0, 0, 0); // a=255, parse rest + tst_parse_("255", 0, 0, 0, 255); // parse as single int + } + void tst_parse_hex_(String raw, int a, int r, int g, int b) { + ColorAdp color = ColorAdp_.parse_hex_(raw); + tst_ColorAdp(color, a, r, g, b); + Tfds.Eq(color.XtoHexStr(), raw); + } + void tst_parse_int_(int val, int a, int r, int g, int b) { + ColorAdp color = ColorAdp_.new_int_(val); + tst_ColorAdp(color, a, r, g, b); + Tfds.Eq(color.Value(), val); + } + void tst_parse_(String s, int alpha, int red, int green, int blue) {tst_ColorAdp(ColorAdp_.parse(s), alpha, red, green, blue);} + void tst_ColorAdp(ColorAdp color, int alpha, int red, int green, int blue) { + TfdsTstr_fxt tstr = TfdsTstr_fxt.new_(); + tstr.Eq_str(color.Alpha(), alpha, "alpha"); + tstr.Eq_str(color.Red(), red, "red"); + tstr.Eq_str(color.Green(), green, "green"); + tstr.Eq_str(color.Blue(), blue, "blue"); + tstr.tst_Equal("color"); + } +} diff --git a/150_gfui/src/gplx/gfui/draws/FontAdp.java b/150_gfui/src/gplx/gfui/draws/FontAdp.java index a27517de8..b4337fc0d 100644 --- a/150_gfui/src/gplx/gfui/draws/FontAdp.java +++ b/150_gfui/src/gplx/gfui/draws/FontAdp.java @@ -13,3 +13,51 @@ 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.gfui.draws; import gplx.*; import gplx.gfui.*; +import java.awt.Font; +import java.awt.Toolkit; +import gplx.core.strings.*; import gplx.core.envs.*; import gplx.gfui.controls.gxws.*; +public class FontAdp implements Gfo_invk { + public String Name() {return name;} public FontAdp Name_(String val) {name = val; InitUnder(); return this;} private String name; + public float Size() {return size;} public FontAdp Size_(float val) {size = val; InitUnder(); return this;} float size; + public FontStyleAdp Style() {return style;} public FontAdp Style_(FontStyleAdp val) {style = val; InitUnder(); return this;} FontStyleAdp style; + public Font UnderFont() {if (font == null) InitUnder(); return font;} Font font = null; + void InitUnder() { + if (Env_.Mode_testing()) return; // WORKAROUND/.NET: NUnit will randomlyly throw exceptions + font = FontAdpCache.Instance.GetNativeFont(this); + if (ownerGxwCore != null) ownerGxwCore.TextFont_set(this); + } + public FontAdp OwnerGxwCore_(GxwCore_base v) {ownerGxwCore = v; return this;} GxwCore_base ownerGxwCore; + public boolean Eq(Object obj) { + FontAdp comp = FontAdp.as_(obj); if (comp == null) return false; + return name == comp.name && size == comp.size && style == comp.style; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, "Name")) {return ctx.Deny() ? (Object)this : Name();} + else if (ctx.Match(k, "Size")) {return ctx.Deny() ? (Object)this : Size();} + else if (ctx.Match(k, "Style")) {return ctx.Deny() ? (Object)this : Style();} + else if (ctx.Match(k, Invk_name_)) { + String v = m.ReadStr("v"); + return ctx.Deny() ? (Object)this : Name_(v); + } + else if (ctx.Match(k, Invk_size_)) { + float v = m.ReadFloat("v"); + return ctx.Deny() ? (Object)this : Size_(v); + } + else if (ctx.Match(k, Invk_style_)) { + Object v = m.CastObj("v"); + return ctx.Deny() ? (Object)this : Style_(FontStyleAdp_.read_(v)); + } + else return Gfo_invk_.Rv_unhandled; + } static final String Invk_name_ = "name_", Invk_size_ = "size_", Invk_style_ = "style_"; + @Override public String toString() {return String_bldr_.new_().Add_kv("name", name).Add_kv_obj("size", size).Add_kv_obj("style", style).To_str();} + + public static final FontAdp NullPtr = null; + public static FontAdp as_(Object obj) {return obj instanceof FontAdp ? (FontAdp)obj : null;} + public static FontAdp cast(Object obj) {try {return (FontAdp)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, FontAdp.class, obj);}} + public static FontAdp new_(String name, float size, FontStyleAdp style) { + FontAdp rv = new FontAdp(); + rv.name = name; rv.size = size; rv.style = style; + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/draws/FontAdpCache.java b/150_gfui/src/gplx/gfui/draws/FontAdpCache.java index a27517de8..ca9a10385 100644 --- a/150_gfui/src/gplx/gfui/draws/FontAdpCache.java +++ b/150_gfui/src/gplx/gfui/draws/FontAdpCache.java @@ -13,3 +13,22 @@ 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.gfui.draws; import gplx.*; import gplx.gfui.*; +import java.awt.Font; +import java.awt.Toolkit; +public class FontAdpCache { + public Font GetNativeFont(FontAdp fontAdp) { + String key = fontAdp.toString(); + Font rv = (Font)hash.Get_by(key); if (rv != null) return rv; + if (screenResolutionInDpi == -1) ScreenResolution_set(); + int fontSize = XtoJavaDpi(fontAdp.Size()); + rv = new Font(fontAdp.Name(), fontAdp.Style().Val(), fontSize); + hash.Add(key, rv); + return rv; + } Hash_adp hash = Hash_adp_.New(); + public static void ScreenResolution_set() {screenResolutionInDpi = Toolkit.getDefaultToolkit().getScreenResolution();} // usually either 96 or 120 + public static int XtoOsDpi(float v) {return Math.round((v * 72) / screenResolutionInDpi);} // WORKAROUND/JAVA: Java needs 72 dpi screen resolution; wnt uses 96 or 120 dpi + public static int XtoJavaDpi(float v) {return Math.round((v * screenResolutionInDpi) / 72);} + static int screenResolutionInDpi = -1; + public static final FontAdpCache Instance = new FontAdpCache(); FontAdpCache() {} +} diff --git a/150_gfui/src/gplx/gfui/draws/FontStyleAdp.java b/150_gfui/src/gplx/gfui/draws/FontStyleAdp.java index a27517de8..d813376a1 100644 --- a/150_gfui/src/gplx/gfui/draws/FontStyleAdp.java +++ b/150_gfui/src/gplx/gfui/draws/FontStyleAdp.java @@ -13,3 +13,9 @@ 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.gfui.draws; import gplx.*; import gplx.gfui.*; +public class FontStyleAdp { + public int Val() {return val;} int val; + @Override public String toString() {return FontStyleAdp_.XtoStr_(this);} + public FontStyleAdp(int v) {val = v;} +} diff --git a/150_gfui/src/gplx/gfui/draws/FontStyleAdp_.java b/150_gfui/src/gplx/gfui/draws/FontStyleAdp_.java index a27517de8..012cf9721 100644 --- a/150_gfui/src/gplx/gfui/draws/FontStyleAdp_.java +++ b/150_gfui/src/gplx/gfui/draws/FontStyleAdp_.java @@ -13,3 +13,54 @@ 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.gfui.draws; import gplx.*; import gplx.gfui.*; +import gplx.core.primitives.*; import gplx.core.interfaces.*; +public class FontStyleAdp_ implements ParseAble { + public static final FontStyleAdp + Plain = new FontStyleAdp(0) + , Bold = new FontStyleAdp(1) + , Italic = new FontStyleAdp(2) + , BoldItalic = new FontStyleAdp(3) + ; + public static final FontStyleAdp_ Parser = new FontStyleAdp_(); + public Object ParseAsObj(String raw) {return FontStyleAdp_.parse(raw);} + + public static FontStyleAdp cast(Object obj) {try {return (FontStyleAdp)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, FontStyleAdp.class, obj);}} + public static FontStyleAdp parseOr_(String raw, FontStyleAdp or) { + FontStyleAdp rv = parse_internal_(raw); if (rv == null) return or; + return rv; + } + public static FontStyleAdp parts_(Bool_obj_val bold, Bool_obj_val italic) { + int v = bold == Bool_obj_val.True ? 1 : 0; + v += italic == Bool_obj_val.True ? 2 : 0; + return lang_(v); + } + public static FontStyleAdp parse(String raw) { + FontStyleAdp rv = parse_internal_(raw); if (rv == null) throw Err_.new_unhandled(raw); + return rv; + } + public static FontStyleAdp read_(Object o) {String s = String_.as_(o); return s != null ? FontStyleAdp_.parse(s) : FontStyleAdp_.cast(o);} + static FontStyleAdp parse_internal_(String raw) { + if (String_.Eq(raw, "plain")) return FontStyleAdp_.Plain; + else if (String_.Eq(raw, "bold")) return FontStyleAdp_.Bold; + else if (String_.Eq(raw, "italic")) return FontStyleAdp_.Italic; + else if (String_.Eq(raw, "bold+italic"))return FontStyleAdp_.BoldItalic; + else if (String_.Eq(raw, "italic+bold"))return FontStyleAdp_.BoldItalic; + else return null; + } + public static FontStyleAdp lang_(int v) { + if (v == Plain.Val()) return Plain; + else if (v == Bold.Val()) return Bold; + else if (v == Italic.Val()) return Italic; + else if (v == BoldItalic.Val()) return BoldItalic; + else throw Err_.new_unhandled(v); + } + public static String XtoStr_(FontStyleAdp fontStyle) { + int val = fontStyle.Val(); + if (val == FontStyleAdp_.Plain.Val()) return "plain"; + else if (val == FontStyleAdp_.Bold.Val()) return "bold"; + else if (val == FontStyleAdp_.Italic.Val()) return "italic"; + else if (val == FontStyleAdp_.BoldItalic.Val()) return "bold+italic"; + else throw Err_.new_unhandled(val); + } +} diff --git a/150_gfui/src/gplx/gfui/draws/PenAdp.java b/150_gfui/src/gplx/gfui/draws/PenAdp.java index a27517de8..529f00f5c 100644 --- a/150_gfui/src/gplx/gfui/draws/PenAdp.java +++ b/150_gfui/src/gplx/gfui/draws/PenAdp.java @@ -13,3 +13,39 @@ 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.gfui.draws; import gplx.*; import gplx.gfui.*; +import java.awt.BasicStroke; +import java.awt.Stroke; +import gplx.core.strings.*; +public class PenAdp implements Gfo_invk { + public float Width() {return width;} public void Width_set(float v) {width = v; InitUnder();} float width; + public ColorAdp Color() {return color;} public void Color_set(ColorAdp v) {color = v; InitUnder();} ColorAdp color; + public BasicStroke UnderStroke() {if (underStroke == null) InitUnder(); return underStroke;} BasicStroke underStroke; + void InitUnder() {underStroke = PenAdpCache.Instance.Fetch(width);} + public PenAdp Clone() {return PenAdp_.new_(color, width);} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_Width_)) Width_set(m.ReadFloat(Invk_Width_)); + else if (ctx.Match(k, Invk_Color_)) Color_set((ColorAdp)m.ReadObj(Invk_Color_, ColorAdp_.Parser)); + else return Gfo_invk_.Rv_unhandled; + return this; + } static final String Invk_Width_ = "Width_", Invk_Color_ = "Color_"; + @Override public String toString() {return String_bldr_.new_().Add_kv_obj("width", width).Add_kv("color", color.XtoHexStr()).To_str();} + @Override public int hashCode() {return color.Value() ^ (int)width;} + @Override public boolean equals(Object obj) { // cannot use Eq b/c of difficulty in comparing null instances + PenAdp comp = PenAdp_.as_(obj); if (comp == null) return false; + return color.Eq(comp.color) && width == comp.width; + } + @gplx.Internal protected PenAdp(ColorAdp color, float width) {this.color = color; this.width = width;} +} +class PenAdpCache { + public BasicStroke Fetch(float width) { + Object rv = hash.Get_by(width); + if (rv == null) { + rv = new BasicStroke(width); + hash.Add(width, rv); + } + return (BasicStroke)rv; + } + Hash_adp hash = Hash_adp_.New(); + public static final PenAdpCache Instance = new PenAdpCache(); PenAdpCache() {} +} diff --git a/150_gfui/src/gplx/gfui/draws/PenAdp_.java b/150_gfui/src/gplx/gfui/draws/PenAdp_.java index a27517de8..75ba91d62 100644 --- a/150_gfui/src/gplx/gfui/draws/PenAdp_.java +++ b/150_gfui/src/gplx/gfui/draws/PenAdp_.java @@ -13,3 +13,11 @@ 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.gfui.draws; import gplx.*; import gplx.gfui.*; +public class PenAdp_ { + public static PenAdp as_(Object obj) {return obj instanceof PenAdp ? (PenAdp)obj : null;} + public static PenAdp cast(Object obj) {try {return (PenAdp)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, PenAdp.class, obj);}} + public static PenAdp black_() {return new_(ColorAdp_.Black, 1);} + public static PenAdp new_(ColorAdp color) {return new_(color, 1);} + public static PenAdp new_(ColorAdp color, float width) {return new PenAdp(color, width);} +} diff --git a/150_gfui/src/gplx/gfui/draws/SolidBrushAdp.java b/150_gfui/src/gplx/gfui/draws/SolidBrushAdp.java index a27517de8..d2958c6ae 100644 --- a/150_gfui/src/gplx/gfui/draws/SolidBrushAdp.java +++ b/150_gfui/src/gplx/gfui/draws/SolidBrushAdp.java @@ -13,3 +13,17 @@ 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.gfui.draws; import gplx.*; import gplx.gfui.*; +public class SolidBrushAdp { + public ColorAdp Color() {return color;} ColorAdp color; + @Override public String toString() {return color.XtoHexStr();} + public boolean Eq(Object obj) { + SolidBrushAdp comp = SolidBrushAdp_.as_(obj); if (comp == null) return false; + return color.Eq(comp.color); + } + @gplx.Internal protected static SolidBrushAdp new_(ColorAdp color) { + SolidBrushAdp rv = new SolidBrushAdp(); + rv.color = color; + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/draws/SolidBrushAdp_.java b/150_gfui/src/gplx/gfui/draws/SolidBrushAdp_.java index a27517de8..837af6d61 100644 --- a/150_gfui/src/gplx/gfui/draws/SolidBrushAdp_.java +++ b/150_gfui/src/gplx/gfui/draws/SolidBrushAdp_.java @@ -13,3 +13,24 @@ 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.gfui.draws; import gplx.*; import gplx.gfui.*; +public class SolidBrushAdp_ { + public static SolidBrushAdp as_(Object obj) {return obj instanceof SolidBrushAdp ? (SolidBrushAdp)obj : null;} + public static SolidBrushAdp cast(Object obj) {try {return (SolidBrushAdp)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, SolidBrushAdp.class, obj);}} + public static final SolidBrushAdp Black = new_(ColorAdp_.Black); + public static final SolidBrushAdp White = new_(ColorAdp_.White); + public static final SolidBrushAdp Null = new_(ColorAdp_.Null); + public static SolidBrushAdp new_(ColorAdp color) {return SolidBrushAdpCache.Instance.Get_by(color);} +} +class SolidBrushAdpCache { + public SolidBrushAdp Get_by(ColorAdp color) { + SolidBrushAdp rv = (SolidBrushAdp)hash.Get_by(color.Value()); + if (rv == null) { + rv = SolidBrushAdp.new_(color); + hash.Add(color.Value(), rv); + } + return rv; + } + Hash_adp hash = Hash_adp_.New(); + public static final SolidBrushAdpCache Instance = new SolidBrushAdpCache(); SolidBrushAdpCache() {} +} diff --git a/150_gfui/src/gplx/gfui/envs/ClipboardAdp_.java b/150_gfui/src/gplx/gfui/envs/ClipboardAdp_.java index a27517de8..d0212fb65 100644 --- a/150_gfui/src/gplx/gfui/envs/ClipboardAdp_.java +++ b/150_gfui/src/gplx/gfui/envs/ClipboardAdp_.java @@ -13,3 +13,45 @@ 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.gfui.envs; import gplx.*; import gplx.gfui.*; +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import gplx.core.strings.*; import gplx.core.envs.*; +public class ClipboardAdp_ { + public static void SetText(String text) { + StringSelection data = new StringSelection(text); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(data, data); + } + public static boolean IsText() { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + return clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor); + } + public static String GetText() { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + String rv = ""; + try {rv = clipboard.getData(DataFlavor.stringFlavor).toString();} + catch (Exception e) {throw Err_.new_exc(e, "ui", "clipboard get_data failed");} + if (Op_sys.Cur().Tid_is_wnt()) { // WORKAROUND:JAVA: On Windows, Clipboard will have \r\n, but Java automatically converts to \n + String_bldr remake = String_bldr_.new_(); + for (int i = 0; i < String_.Len(rv); i++) { + char c = String_.CharAt(rv, i); + if (c == '\n' && i > 0) { + char prev = String_.CharAt(rv, i - 1); + if (prev != '\r') { + remake.Add(String_.CrLf); + continue; + } + } + remake.Add(c); + } + rv = remake.To_str(); +// rv = String_.Replace(rv, "\n", Env_.NewLine); + } + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/envs/ClipboardAdp__tst.java b/150_gfui/src/gplx/gfui/envs/ClipboardAdp__tst.java index a27517de8..d4d745976 100644 --- a/150_gfui/src/gplx/gfui/envs/ClipboardAdp__tst.java +++ b/150_gfui/src/gplx/gfui/envs/ClipboardAdp__tst.java @@ -13,3 +13,12 @@ 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.gfui.envs; import gplx.*; import gplx.gfui.*; +import org.junit.*; import gplx.gfui.envs.*; +public class ClipboardAdp__tst { + @Test public void Basic() { + ClipboardAdp_.SetText("test"); + Tfds.Eq(true, ClipboardAdp_.IsText()); + Tfds.Eq("test", ClipboardAdp_.GetText()); + } +} diff --git a/150_gfui/src/gplx/gfui/envs/CursorAdp.java b/150_gfui/src/gplx/gfui/envs/CursorAdp.java index a27517de8..bf76631b7 100644 --- a/150_gfui/src/gplx/gfui/envs/CursorAdp.java +++ b/150_gfui/src/gplx/gfui/envs/CursorAdp.java @@ -13,3 +13,25 @@ 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.gfui.envs; import gplx.*; import gplx.gfui.*; +import gplx.gfui.controls.gxws.*; +public class CursorAdp { + public static PointAdp Pos() { + if (testing) return testing_pos; + return GxwCore_lang.XtoPointAdp(java.awt.MouseInfo.getPointerInfo().getLocation()); + } + public static void Pos_set(PointAdp p) { + if (testing) // while in testing mode, never set Cursor.Position + testing_pos = p; + else { + java.awt.Robot robot = null; + try {robot = new java.awt.Robot();} + catch (java.awt.AWTException e) {throw Err_.new_exc(e, "ui", "cursor pos set failed");} + robot.mouseMove(p.X(), p.Y()); + } + } + @gplx.Internal protected static void Pos_set_for_tests(PointAdp point) { + testing = point != PointAdp_.Null; + testing_pos = point; + } static PointAdp testing_pos = PointAdp_.Null; static boolean testing = false; +} diff --git a/150_gfui/src/gplx/gfui/envs/ScreenAdp.java b/150_gfui/src/gplx/gfui/envs/ScreenAdp.java index a27517de8..c0f3ead4b 100644 --- a/150_gfui/src/gplx/gfui/envs/ScreenAdp.java +++ b/150_gfui/src/gplx/gfui/envs/ScreenAdp.java @@ -13,3 +13,18 @@ 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.gfui.envs; import gplx.*; import gplx.gfui.*; +public class ScreenAdp { + public int Index() {return index;} int index; + public RectAdp Rect() {return bounds;} RectAdp bounds = RectAdp_.Zero; + public SizeAdp Size() {return bounds.Size();} + public int Width() {return bounds.Width();} + public int Height() {return bounds.Height();} + public PointAdp Pos() {return bounds.Pos();} + public int X() {return bounds.X();} + public int Y() {return bounds.Y();} + + @gplx.Internal protected ScreenAdp(int index, RectAdp bounds) { + this.index = index; this.bounds = bounds; + } +} diff --git a/150_gfui/src/gplx/gfui/envs/ScreenAdp_.java b/150_gfui/src/gplx/gfui/envs/ScreenAdp_.java index a27517de8..cf5ad91d9 100644 --- a/150_gfui/src/gplx/gfui/envs/ScreenAdp_.java +++ b/150_gfui/src/gplx/gfui/envs/ScreenAdp_.java @@ -13,3 +13,45 @@ 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.gfui.envs; import gplx.*; import gplx.gfui.*; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.Toolkit; +import gplx.gfui.controls.gxws.*; +public class ScreenAdp_ { + public static final ScreenAdp Primary = screen_(0); + public static ScreenAdp as_(Object obj) {return obj instanceof ScreenAdp ? (ScreenAdp)obj : null;} + public static ScreenAdp cast(Object obj) {try {return (ScreenAdp)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, ScreenAdp.class, obj);}} + public static ScreenAdp parse(String raw) { // ex: {screen{1} + try { + raw = String_.Replace(raw, "{screen{", ""); + raw = String_.Replace(raw, "}", ""); + return ScreenAdp_.screen_(Int_.Parse(raw)); + } catch(Exception exc) {throw Err_.new_parse_exc(exc, ScreenAdp.class, raw);} + } + public static ScreenAdp from_point_(PointAdp pos) {// NOTE: not using FromPoint b/c of plat_wce + if (ScreenAdp_.Count() == 1) return Primary; + ScreenAdp screen0 = screen_(0), screen1 = screen_(1); + return pos.X() < screen1.X() ? screen0 : screen1; + } + public static ScreenAdp opposite_(int idx) { + if (ScreenAdp_.Count() == 1) return Primary; + int opposite = idx == 0 ? 1 : 0; // will ignore all screens with index > 1 + return screen_(opposite); + } + public static int Count() { + return GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices().length; +// return 1;//Screen.AllScreens.Length; + } + public static ScreenAdp screen_(int index) { + if (index >= ScreenAdp_.Count()) throw Err_.new_missing_idx(index, ScreenAdp_.Count()); + GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice[] devs = env.getScreenDevices(); + GraphicsConfiguration conf = devs[index].getDefaultConfiguration(); + ScreenAdp sd = new ScreenAdp(index, GxwCore_lang.XtoRectAdp(conf.getBounds())); + return sd; + } +//#@endif + static ScreenAdp new_(int index, RectAdp rect) {return new ScreenAdp(index, rect);} +} diff --git a/150_gfui/src/gplx/gfui/envs/ScreenAdp_tst.java b/150_gfui/src/gplx/gfui/envs/ScreenAdp_tst.java index a27517de8..dea1799d5 100644 --- a/150_gfui/src/gplx/gfui/envs/ScreenAdp_tst.java +++ b/150_gfui/src/gplx/gfui/envs/ScreenAdp_tst.java @@ -13,3 +13,15 @@ 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.gfui.envs; import gplx.*; import gplx.gfui.*; +import org.junit.*; import gplx.gfui.envs.*; +public class ScreenAdp_tst { + @Test public void parse() { + ScreenAdp actl = ScreenAdp_.parse("{screen{0}"); + Tfds.Eq(0, actl.Index()); + } + @Test public void opposite_() { + ScreenAdp actl = ScreenAdp_.from_point_(PointAdp_.new_(2000, 2000)); + Tfds.Eq(0, actl.Index()); + } +} diff --git a/150_gfui/src/gplx/gfui/envs/TimerAdp.java b/150_gfui/src/gplx/gfui/envs/TimerAdp.java index a27517de8..6304bd15b 100644 --- a/150_gfui/src/gplx/gfui/envs/TimerAdp.java +++ b/150_gfui/src/gplx/gfui/envs/TimerAdp.java @@ -13,3 +13,40 @@ 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.gfui.envs; import gplx.*; import gplx.gfui.*; +import javax.swing.Timer; +import java.awt.event.ActionListener; +import gplx.core.envs.*; +public class TimerAdp implements Rls_able { + public TimerAdp Interval_(int interval) { + underTimer.setInitialDelay(interval); + underTimer.setDelay(interval); + return this; + } + public TimerAdp Enabled_on() {return Enabled_(true);} public TimerAdp Enabled_off() {return Enabled_(false);} + public TimerAdp Enabled_(boolean val) { + if (!Env_.Mode_testing()) { + if (val) underTimer.start(); + else underTimer.stop(); + } + return this; + } + public void Rls() {underTimer.stop();} + + Timer underTimer; + public static TimerAdp new_(Gfo_invk invk, String msgKey, int interval, boolean enabled) { + TimerAdp rv = new TimerAdp(); + rv.underTimer = new Timer(interval, new TimerActionListener(invk, msgKey)); + rv.Interval_(interval).Enabled_(enabled); + return rv; + } + } +class TimerActionListener implements ActionListener { + public void actionPerformed(java.awt.event.ActionEvent arg0) { + Gfo_invk_.Invk_by_key(invk, key); + } + Gfo_invk invk; String key; + public TimerActionListener(Gfo_invk invk, String key) { + this.invk = invk; this.key = key; + } +} diff --git a/150_gfui/src/gplx/gfui/gfxs/GfxAdp.java b/150_gfui/src/gplx/gfui/gfxs/GfxAdp.java index a27517de8..2160ada38 100644 --- a/150_gfui/src/gplx/gfui/gfxs/GfxAdp.java +++ b/150_gfui/src/gplx/gfui/gfxs/GfxAdp.java @@ -13,3 +13,16 @@ 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.gfui.gfxs; import gplx.*; import gplx.gfui.*; +import gplx.gfui.draws.*; import gplx.gfui.imgs.*; +public interface GfxAdp extends Rls_able { + void DrawLine(PenAdp pen, PointAdp src, PointAdp trg); + void DrawRect(PenAdp pen, int x, int y, int width, int height); + void DrawRect(PenAdp pen, PointAdp location, SizeAdp size); + void DrawRect(PenAdp pen, RectAdp rect); + void FillRect(SolidBrushAdp brush, int x, int y, int width, int height); + void DrawImage(ImageAdp image, PointAdp location); + void DrawImage(ImageAdp img, int trg_x, int trg_y, int trg_w, int trg_h, int src_x, int src_y, int src_w, int src_h); + void DrawStringXtn(String s, FontAdp font, SolidBrushAdp brush, float x, float y, float width, float height, GfxStringData sd); + float[] MeasureStringXtn(String s, FontAdp font, GfxStringData sd); +} diff --git a/150_gfui/src/gplx/gfui/gfxs/GfxAdpBase.java b/150_gfui/src/gplx/gfui/gfxs/GfxAdpBase.java index a27517de8..0e9966e39 100644 --- a/150_gfui/src/gplx/gfui/gfxs/GfxAdpBase.java +++ b/150_gfui/src/gplx/gfui/gfxs/GfxAdpBase.java @@ -13,3 +13,94 @@ 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.gfui.gfxs; import gplx.*; import gplx.gfui.*; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.geom.Rectangle2D; + +import javax.swing.JComponent; +import gplx.gfui.draws.*; import gplx.gfui.imgs.*; import gplx.gfui.controls.gxws.*; +public class GfxAdpBase implements GfxAdp { + public void DrawLine(PenAdp pen, PointAdp src, PointAdp trg) { + gfx.setColor(ColorAdpCache.Instance.GetNativeColor(pen.Color())); + gfx.setStroke(pen.UnderStroke()); + gfx.drawLine(src.X(), src.Y(), trg.X(), trg.Y()); + } + public void DrawRect(PenAdp pen, PointAdp pos, SizeAdp size) {this.DrawRect(pen, pos.X(), pos.Y(), size.Width(), size.Height());} + public void DrawRect(PenAdp pen, RectAdp rect) {this.DrawRect(pen, rect.X(), rect.Y(), rect.Width(), rect.Height());} + public void DrawRect(PenAdp pen, int x, int y, int width, int height) { + gfx.setPaint(ColorAdpCache.Instance.GetNativeColor(pen.Color())); + gfx.setStroke(pen.UnderStroke()); + gfx.drawRect(x, y, width, height); + } + public void FillRect(SolidBrushAdp brush, int x, int y, int width, int height) { + gfx.setPaint(ColorAdpCache.Instance.GetNativeColor(brush.Color())); + gfx.fillRect(x, y, width, height); + } + public void DrawStringXtn(String s, FontAdp font, SolidBrushAdp brush, float x, float y, float width, float height, GfxStringData sd) { + gfx.setPaint(ColorAdpCache.Instance.GetNativeColor(brush.Color())); + // height = y - ascent + descent -> rect.y - rect.height [assume ascent] + 2 [assume descent] + gfx.setClip((int)x, (int)y - (int)height + 2, (int)width, (int)height); + if (sd == null || sd.mnemonicString == null) { + gfx.setFont(font.UnderFont()); + gfx.drawString(s, x, y - 2); + } + else { + gfx.drawString(sd.mnemonicString.getIterator(), x, y - 2); + } + gfx.setClip(null); + } + public float[] MeasureStringXtn(String s, FontAdp font, GfxStringData sd) { + FontMetrics fontMetrics = gfx.getFontMetrics(font.UnderFont()); + Rectangle2D stringMetrics = fontMetrics.getStringBounds(s, gfx); + float width = (float)stringMetrics.getWidth(); + int height = fontMetrics.getHeight(); + int descent = fontMetrics.getDescent(); + return new float[] {width, height, descent}; + } + public static float[] GetStringBounds(String s, FontAdp font, Object o) { + JComponent jcomponent = (JComponent)o; + Graphics2D gfx = (Graphics2D)jcomponent.getGraphics(); + FontMetrics fontMetrics = gfx.getFontMetrics(font.UnderFont()); + Rectangle2D stringMetrics = fontMetrics.getStringBounds(s, gfx); + float width = (float)stringMetrics.getWidth(); + int height = fontMetrics.getHeight(); + int descent = fontMetrics.getDescent(); + return new float[] {width, height, descent}; + } + + public void DrawImage(ImageAdp image, PointAdp pt) { + if (image == ImageAdp_.Null) return; + gfx.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); + gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + gfx.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); + gfx.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE); + gfx.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + gfx.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + gfx.drawImage((java.awt.Image)image.Under(), pt.X(), pt.Y(), null); +// gfx.drawImage(image.UnderImage(),pt.X(),pt.Y(), +// pt.X()+image.Width(),pt.Y()+image.Height(), +// pt.X(),pt.Y(), +// pt.X()+image.Width(),pt.Y()+image.Height(), +// null); + } + public void DrawImage(ImageAdp img, int trg_x, int trg_y, int trg_w, int trg_h, int src_x, int src_y, int src_w, int src_h) { + if (img == ImageAdp_.Null) return; + gfx.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); + gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + gfx.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); + gfx.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE); + gfx.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + gfx.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + gfx.drawImage((java.awt.Image)img.Under(), trg_x, trg_y, trg_x + trg_w, trg_y + trg_h, src_x, src_y, src_x + src_w, src_y + src_h, null); + } + public void Rls() {gfx.dispose();} + public Object Under() {return gfx;} + Graphics2D gfx; + public static GfxAdpBase new_(Graphics2D gfx) { + GfxAdpBase rv = new GfxAdpBase(); + rv.gfx = gfx; + return rv; + } GfxAdpBase() {} +} diff --git a/150_gfui/src/gplx/gfui/gfxs/GfxAdpMok.java b/150_gfui/src/gplx/gfui/gfxs/GfxAdpMok.java index a27517de8..29533800c 100644 --- a/150_gfui/src/gplx/gfui/gfxs/GfxAdpMok.java +++ b/150_gfui/src/gplx/gfui/gfxs/GfxAdpMok.java @@ -13,3 +13,36 @@ 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.gfui.gfxs; import gplx.*; import gplx.gfui.*; +import gplx.gfui.draws.*; import gplx.gfui.imgs.*; +public class GfxAdpMok implements GfxAdp { + public GfxItmList SubItms() {return subItms;} GfxItmList subItms = new GfxItmList(); + public void DrawStringXtn(String s, FontAdp font, SolidBrushAdp brush, float x, float y, float width, float height, GfxStringData sd) { + float[] sizeAry = MeasureStringXtn(s, font, null); + SizeAdp size = SizeAdp_.new_((int)sizeAry[0], (int)sizeAry[1]); + GfxStringItm str = GfxStringItm.new_(PointAdp_.new_((int)x, (int)y), size, s, font, brush); + subItms.Add(str); + } + public void DrawRect(PenAdp pen, PointAdp location, SizeAdp size) {this.DrawRect(pen, location.X(), location.Y(), size.Width(), size.Height());} + public void DrawRect(PenAdp pen, RectAdp rect) {this.DrawRect(pen, rect.X(), rect.Y(), rect.Width(), rect.Height());} + public void DrawRect(PenAdp pen, int x, int y, int width, int height) { + GfxRectItm rect = GfxRectItm.new_(PointAdp_.new_(x, y), SizeAdp_.new_(width, height), pen.Width(), pen.Color()); + subItms.Add(rect); + } + public void DrawLine(PenAdp pen, PointAdp src, PointAdp trg) { + GfxLineItm line = GfxLineItm.new_(src, trg, pen.Width(), pen.Color()); + subItms.Add(line); + } + public void DrawImage(ImageAdp image, PointAdp location) { + // gfx.DrawImage(image, width, height); + } + public void DrawImage(ImageAdp img, int trg_x, int trg_y, int trg_w, int trg_h, int src_x, int src_y, int src_w, int src_h) { + // gfx.DrawImage(image, dst, src, GraphicsUnit.Pixel); + } + public void FillRect(SolidBrushAdp brush, int x, int y, int width, int height) { + // gfx.FillRect(brush, x, y, width, height); + } + public float[] MeasureStringXtn(String s, FontAdp font, GfxStringData str) {return new float[] {13 * String_.Len(s), 17};} + public void Rls() {} + public static GfxAdpMok new_() {return new GfxAdpMok();} GfxAdpMok() {} +} diff --git a/150_gfui/src/gplx/gfui/gfxs/GfxAdp_.java b/150_gfui/src/gplx/gfui/gfxs/GfxAdp_.java index a27517de8..208166261 100644 --- a/150_gfui/src/gplx/gfui/gfxs/GfxAdp_.java +++ b/150_gfui/src/gplx/gfui/gfxs/GfxAdp_.java @@ -13,3 +13,13 @@ 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.gfui.gfxs; import gplx.*; import gplx.gfui.*; +import java.awt.Graphics2D; +import gplx.gfui.imgs.*; +public class GfxAdp_ { + @gplx.Internal protected static GfxAdp new_(Graphics2D graphics) {return GfxAdpBase.new_(graphics);} + public static GfxAdp image_(ImageAdp image) { + Graphics2D graphics = (Graphics2D)((java.awt.Image)image.Under()).getGraphics(); + return GfxAdpBase.new_(graphics); + } +} diff --git a/150_gfui/src/gplx/gfui/gfxs/GfxItm.java b/150_gfui/src/gplx/gfui/gfxs/GfxItm.java index a27517de8..5c9134b33 100644 --- a/150_gfui/src/gplx/gfui/gfxs/GfxItm.java +++ b/150_gfui/src/gplx/gfui/gfxs/GfxItm.java @@ -13,3 +13,5 @@ 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.gfui.gfxs; import gplx.*; import gplx.gfui.*; +public interface GfxItm {} diff --git a/150_gfui/src/gplx/gfui/gfxs/GfxItmList.java b/150_gfui/src/gplx/gfui/gfxs/GfxItmList.java index a27517de8..e294c1fb5 100644 --- a/150_gfui/src/gplx/gfui/gfxs/GfxItmList.java +++ b/150_gfui/src/gplx/gfui/gfxs/GfxItmList.java @@ -13,3 +13,16 @@ 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.gfui.gfxs; import gplx.*; import gplx.gfui.*; +public class GfxItmList extends List_adp_base { + @gplx.New public GfxItm Get_at(int i) {return (GfxItm)Get_at_base(i);} + public void Add(GfxItm gfxItm) {Add_base(gfxItm);} +} +class GfxItmListFxt { + public void tst_SubItm_count(GfxAdpMok gfx, int expd) {Tfds.Eq(expd, gfx.SubItms().Count());} + public void tst_SubItm(GfxAdpMok gfx, int i, GfxItm expd) { + GfxItm actl = gfx.SubItms().Get_at(i); + Tfds.Eq(expd, actl); + } + public static GfxItmListFxt new_() {return new GfxItmListFxt();} GfxItmListFxt() {} +} diff --git a/150_gfui/src/gplx/gfui/gfxs/GfxItm_base.java b/150_gfui/src/gplx/gfui/gfxs/GfxItm_base.java index a27517de8..0178709ad 100644 --- a/150_gfui/src/gplx/gfui/gfxs/GfxItm_base.java +++ b/150_gfui/src/gplx/gfui/gfxs/GfxItm_base.java @@ -13,3 +13,20 @@ 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.gfui.gfxs; import gplx.*; import gplx.gfui.*; +import gplx.core.strings.*; +public abstract class GfxItm_base implements GfxItm { + public PointAdp Pos() {return pos;} PointAdp pos = PointAdp_.Zero; + public SizeAdp Size() {return size;} SizeAdp size = SizeAdp_.Zero; + @Override public String toString() {return String_bldr_.new_().Add_kv_obj("pos", pos).Add_kv_obj("size", size).To_str();} + @Override public int hashCode() {return this.toString().hashCode();} + @Override public boolean equals(Object obj) { + GfxItm_base comp = GfxItm_base.as_(obj); if (comp == null) return false; + return Object_.Eq(pos, comp.pos) && Object_.Eq(size, comp.size); + } + @gplx.Virtual public void ctor_GfxItmBase(PointAdp posVal, SizeAdp sizeVal) { + pos = posVal; size = sizeVal; + } + public static GfxItm_base as_(Object obj) {return obj instanceof GfxItm_base ? (GfxItm_base)obj : null;} + public static GfxItm_base cast(Object obj) {try {return (GfxItm_base)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, GfxItm_base.class, obj);}} +} diff --git a/150_gfui/src/gplx/gfui/gfxs/GfxLineItm.java b/150_gfui/src/gplx/gfui/gfxs/GfxLineItm.java index a27517de8..72f2b7715 100644 --- a/150_gfui/src/gplx/gfui/gfxs/GfxLineItm.java +++ b/150_gfui/src/gplx/gfui/gfxs/GfxLineItm.java @@ -13,3 +13,27 @@ 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.gfui.gfxs; import gplx.*; import gplx.gfui.*; +import gplx.core.strings.*; +import gplx.gfui.draws.*; +public class GfxLineItm implements GfxItm { + public PointAdp Src() {return src;} PointAdp src = PointAdp_.Zero; + public PointAdp Trg() {return trg;} PointAdp trg = PointAdp_.Zero; + public float Width() {return width;} float width; + public ColorAdp Color() {return color;} ColorAdp color; + + @Override public String toString() {return String_bldr_.new_().Add_kv_obj("src", src).Add_kv_obj("trg", trg).Add_kv_obj("width", width).Add_kv_obj("color", color.XtoHexStr()).To_str();} + @Override public int hashCode() {return this.toString().hashCode();} + @Override public boolean equals(Object obj) { + GfxLineItm comp = GfxLineItm.as_(obj); if (comp == null) return false; + return src.Eq(comp.src) && trg.Eq(comp.trg) && width == comp.width && color.Eq(comp.color); + } + public static GfxLineItm new_(PointAdp src, PointAdp trg, float width, ColorAdp color) { + GfxLineItm rv = new GfxLineItm(); + rv.src = src; rv.trg = trg; + rv.width = width; rv.color = color; + return rv; + } GfxLineItm() {} + public static GfxLineItm as_(Object obj) {return obj instanceof GfxLineItm ? (GfxLineItm)obj : null;} + public static GfxLineItm cast(Object obj) {try {return (GfxLineItm)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, GfxLineItm.class, obj);}} +} diff --git a/150_gfui/src/gplx/gfui/gfxs/GfxRectItm.java b/150_gfui/src/gplx/gfui/gfxs/GfxRectItm.java index a27517de8..e7fd71ea2 100644 --- a/150_gfui/src/gplx/gfui/gfxs/GfxRectItm.java +++ b/150_gfui/src/gplx/gfui/gfxs/GfxRectItm.java @@ -13,3 +13,24 @@ 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.gfui.gfxs; import gplx.*; import gplx.gfui.*; +import gplx.core.strings.*; +import gplx.gfui.draws.*; +public class GfxRectItm extends GfxItm_base { + public float Width() {return width;} float width; + public ColorAdp Color() {return color;} ColorAdp color; + + @Override public String toString() {return String_.Concat(super.toString(), String_bldr_.new_().Add_kv_obj("width", width).Add_kv("color", color.XtoHexStr()).To_str());} + @Override public int hashCode() {return this.toString().hashCode();} + @Override public boolean equals(Object obj) { + GfxRectItm comp = GfxRectItm.as_(obj); if (comp == null) return false; + return super.equals(comp) && width == comp.width && color.Eq(comp.color); + } + public static GfxRectItm new_(PointAdp pos, SizeAdp size, float width, ColorAdp color) { + GfxRectItm rv = new GfxRectItm(); + rv.ctor_GfxItmBase(pos, size); + rv.width = width; rv.color = color; + return rv; + } GfxRectItm() {} + @gplx.New public static GfxRectItm as_(Object obj) {return obj instanceof GfxRectItm ? (GfxRectItm)obj : null;} +} diff --git a/150_gfui/src/gplx/gfui/gfxs/GfxStringData.java b/150_gfui/src/gplx/gfui/gfxs/GfxStringData.java index a27517de8..fe86cc734 100644 --- a/150_gfui/src/gplx/gfui/gfxs/GfxStringData.java +++ b/150_gfui/src/gplx/gfui/gfxs/GfxStringData.java @@ -13,3 +13,103 @@ 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.gfui.gfxs; import gplx.*; import gplx.gfui.*; +import java.awt.font.TextAttribute; +import java.text.AttributedString; +import gplx.core.envs.*; +import gplx.gfui.draws.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.windows.*; +public class GfxStringData { + public String Val() { + if (ownerElem == null) return val; + if (ownerElem.TextVal() == null) return ""; + return ownerElem.TextVal(); + } String val = ""; + public GfuiAlign AlignH() {return alignH;} GfuiAlign alignH; + public GfxStringData AlignH_(GfuiAlign val) { + alignH = val; + if (ownerElem != null) Gfo_invk_.Invk_by_val(ownerElem, GxwElem_lang.AlignH_cmd, alignH); // needed for TextBox, since its Paint is not overriden + TextRect_setNull(); + return this; + } + public GfuiAlign AlignV() {return alignV;} public GfxStringData AlignV_(GfuiAlign val) {alignV = val; return this;} GfuiAlign alignV = GfuiAlign_.Mid; + public ColorAdp Color() {return brush.Color();} + public SolidBrushAdp UnderBrush() {return brush;} SolidBrushAdp brush; + public AttributedString MnemonicString() {return mnemonicString;} AttributedString mnemonicString; + String drawn = ""; + public void MnemonicString_sync() { + int pos = GfuiWinKeyCmdMgr.ExtractPosFromText(this.Val()); if (pos == String_.Find_none) return; + drawn = String_.MidByLen(this.Val(), 0, pos) + String_.Mid(this.Val(), pos + 1); // rebuild string without & + mnemonicString = new AttributedString(drawn); + mnemonicString.addAttribute(TextAttribute.FONT, font.UnderFont()); + mnemonicString.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, pos, pos + 1); + } + + public GfxStringData Color_(ColorAdp val) { + brush = SolidBrushAdp_.new_(val); + if (ownerElem != null) ownerElem.Core().ForeColor_set(val); + TextRect_setNull(); + return this; + } + public FontAdp Font() {return font;} FontAdp font; + public GfxStringData Font_(FontAdp val) { + font = val; + if (!Env_.Mode_testing() && ownerElem != null) ownerElem.Core().TextFont_set(font); + TextRect_setNull(); + MnemonicString_sync(); + return this; + } + public RectAdpF TextRect() {return textRect;} public void TextRect_set(RectAdpF val) {textRect = val;} public void TextRect_setNull() {textRect = RectAdpF.Null;} RectAdpF textRect = RectAdpF.Null; + public RectAdpF TextRect_setX(int x) { + textRect = RectAdpF.new_(x, textRect.Y(), textRect.Width(), textRect.Height()); + return textRect; + } + @gplx.Internal protected SizeAdp OwnerSize() {return ownerSize;} + public void OwnerSize_sync(SizeAdp val) { + ownerSize = val; TextRect_setNull(); + ownerElem.Core().Invalidate(); // NOTE: force redraw; this may be redundant in WINFORMS but needed in SWING especially when windowOpened causes resize; SWING seems to execute windowOpened -> resize -> paint -> componentResized + } SizeAdp ownerSize = SizeAdp_.new_(20, 20); + @gplx.Internal protected GxwElem UnderElem() {return owner.UnderElem();} + public void DrawData(GfxAdp gfx) { + if (textRect.Eq(RectAdpF.Null)) {textRect = TextRect_calc(gfx);} + gfx.DrawStringXtn(this.Val(), font, brush, textRect.X(), textRect.Y(), textRect.Width(), textRect.Height(), this); + } + public void Text_set(String v) { + if (this.Val() == v) return; + if (ownerElem != null) { + ownerElem.TextVal_set(v); + if (owner.CustomDraw()) ownerElem.Core().Invalidate(); + } + else + this.val = v; + TextRect_setNull(); + MnemonicString_sync(); + } + public RectAdpF TextRect_calc(GfxAdp gfx) { + + float[] sizeAry = gfx.MeasureStringXtn(drawn == "" ? this.Val() : drawn, font, this); + float width = sizeAry[0], height = sizeAry[1], descent = sizeAry[2]; +// if (String_.Eq("opal.gfds 0.0.1", this.Val())) { +// Tfds.Write(this.Val(), alignH.Val(), (int)width, ownerSize.Width()); +// } + float x = GfuiAlign_.CalcInsideOfAxis(alignH.Val(), (int)width, ownerSize.Width()); + float y = 0; int alignVVal = alignV.Val(); float ownerHeight = ownerSize.Height(); + if (alignVVal == GfuiAlign_.Null.Val()) y = Int_.Min_value; + else if (alignVVal == GfuiAlign_.Lo.Val()) y = height - descent; + else if (alignVVal == GfuiAlign_.Mid.Val()) y = (ownerHeight - (ownerHeight - height) / 2);// - descent; // COMMENT: subtracting descent is theoretically correct, but practically results in text shifted up + else if (alignVVal == GfuiAlign_.Hi.Val()) y = ownerHeight - descent; + if (width > ownerElem.Core().Width()) width = ownerElem.Core().Width(); // clip to elem size or else text overflows; EX: tab buttons + if (x < 0) x = 0; if (y < 0) y = 0; // occurs when text is larger than elem; do not allow negative values + return RectAdpF.new_(x, y, width, height); + } GfuiElemBase owner; GxwElem ownerElem; + public static GfxStringData new_(GfuiElemBase owner, GxwElem ownerElem) { + GfxStringData rv = new GfxStringData(); + rv.brush = SolidBrushAdp_.Black; + rv.alignH = GfuiAlign_.Left; + rv.owner = owner; + rv.ownerElem = ownerElem; + // WORKAROUND:.NET: setting font on textBox causes odd selection behavior for MediaTimeBox + rv.Font_(FontAdp.new_("Arial", 8, FontStyleAdp_.Plain)); // needed for TextBox, since its Paint is not overriden, and .Font property must be set + return rv; + } GfxStringData() {} + public static final GfxStringData Null = null; +} diff --git a/150_gfui/src/gplx/gfui/gfxs/GfxStringItm.java b/150_gfui/src/gplx/gfui/gfxs/GfxStringItm.java index a27517de8..af7ef3c61 100644 --- a/150_gfui/src/gplx/gfui/gfxs/GfxStringItm.java +++ b/150_gfui/src/gplx/gfui/gfxs/GfxStringItm.java @@ -13,3 +13,25 @@ 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.gfui.gfxs; import gplx.*; import gplx.gfui.*; +import gplx.gfui.draws.*; +public class GfxStringItm extends GfxItm_base { + public String Text() {return text;} private String text; + public FontAdp Font() {return font;} FontAdp font; + public SolidBrushAdp Brush() {return brush;} SolidBrushAdp brush; + @Override public int hashCode() {return this.toString().hashCode();} + @Override public boolean equals(Object obj) { + GfxStringItm comp = GfxStringItm.as_(obj); if (comp == null) return false; + return super.equals(obj) && String_.Eq(text, comp.text) && font.Eq(comp.font) && brush.Eq(comp.brush); + } + public static GfxStringItm new_(PointAdp pos, SizeAdp size, String text, FontAdp font, SolidBrushAdp brush) { + GfxStringItm rv = new GfxStringItm(); + rv.ctor_GfxItmBase(pos, size); + rv.text = text; rv.font = font; rv.brush = brush; + return rv; + } GfxStringItm() {} + public static GfxStringItm test_(String text, FontAdp font, SolidBrushAdp brush) { + return GfxStringItm.new_(PointAdp_.Null, SizeAdp_.Null, text, font, brush); + } + @gplx.New public static GfxStringItm as_(Object obj) {return obj instanceof GfxStringItm ? (GfxStringItm)obj : null;} +} diff --git a/150_gfui/src/gplx/gfui/gfxs/PaintArgs.java b/150_gfui/src/gplx/gfui/gfxs/PaintArgs.java index a27517de8..63c981cd1 100644 --- a/150_gfui/src/gplx/gfui/gfxs/PaintArgs.java +++ b/150_gfui/src/gplx/gfui/gfxs/PaintArgs.java @@ -13,3 +13,15 @@ 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.gfui.gfxs; import gplx.*; import gplx.gfui.*; +public class PaintArgs { + public GfxAdp Graphics() {return graphics;} GfxAdp graphics; + public RectAdp ClipRect() {return clipRect;} RectAdp clipRect; + + public static PaintArgs cast(Object obj) {try {return (PaintArgs)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, PaintArgs.class, obj);}} + public static PaintArgs new_(GfxAdp graphics, RectAdp clipRect) { + PaintArgs rv = new PaintArgs(); + rv.graphics = graphics; rv.clipRect = clipRect; + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/imgs/IconAdp.java b/150_gfui/src/gplx/gfui/imgs/IconAdp.java index a27517de8..d8245b1c0 100644 --- a/150_gfui/src/gplx/gfui/imgs/IconAdp.java +++ b/150_gfui/src/gplx/gfui/imgs/IconAdp.java @@ -13,3 +13,33 @@ 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.gfui.imgs; import gplx.*; import gplx.gfui.*; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import java.awt.Image; +import java.net.MalformedURLException; +import java.net.URL; +import gplx.core.gfo_regys.*; +public class IconAdp { + public Icon UnderIcon() {return icon;} private final Icon icon; + public Image XtoImage() {return ((ImageIcon)icon).getImage();} + public Io_url Url() {return url;} private Io_url url = Io_url_.Empty; + IconAdp(Icon icon) {this.icon = icon;} + public static IconAdp new_(Icon icon) {return new IconAdp(icon);} + public static IconAdp file_or_blank(Io_url url) {return file_(url);} + public static IconAdp file_(Io_url url) { + Icon icon = new ImageIcon(url.Xto_api()); + IconAdp rv = new IconAdp(icon); + rv.url = url; + return rv; + } + public static void regy_loadDir_(Io_url imgDir) {GfoRegy.Instance.RegDir(imgDir, "*.png", true, "_", ".");} + public static void regy_loadDir_shallow(Io_url imgDir) {GfoRegy.Instance.RegDir(imgDir, "*.png", false, "_", ".");} + public static IconAdp regy_(String key) { + GfoRegyItm itm = GfoRegy.Instance.FetchOrNull(key); + if (itm == null) {UsrDlg_.Instance.Warn("missing icon; key={0}", key); return null;} + if (itm.ValType() != GfoRegyItm.ValType_Url) throw Err_.new_wo_type("regyItm should be of type url", "key", key); + return IconAdp.file_(itm.Url()); + } + public static IconAdp as_(Object obj) {return obj instanceof IconAdp ? (IconAdp)obj : null;} +} diff --git a/150_gfui/src/gplx/gfui/imgs/ImageAdp.java b/150_gfui/src/gplx/gfui/imgs/ImageAdp.java index a27517de8..1d82ca7c4 100644 --- a/150_gfui/src/gplx/gfui/imgs/ImageAdp.java +++ b/150_gfui/src/gplx/gfui/imgs/ImageAdp.java @@ -13,3 +13,37 @@ 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.gfui.imgs; import gplx.*; import gplx.gfui.*; +import java.awt.Image; +import gplx.gfui.kits.core.*; +public interface ImageAdp extends Rls_able { + Gfui_kit Kit(); + SizeAdp Size(); + int Width(); + int Height(); + Io_url Url(); ImageAdp Url_(Io_url v); + Object Under(); + boolean Disposed(); + void SaveAsBmp(Io_url url); + void SaveAsPng(Io_url url); + ImageAdp Resize(int width, int height); + ImageAdp Extract_image(RectAdp src_rect, SizeAdp trg_size); + ImageAdp Extract_image(int src_x, int src_y, int src_w, int src_h, int trg_w, int trg_h); +} +class ImageAdp_txt implements ImageAdp { + public Gfui_kit Kit() {return Swing_kit.Instance;} + public SizeAdp Size() {return size;} SizeAdp size; + public int Width() {return size.Width();} + public int Height() {return size.Height();} + public Io_url Url() {return url;} public ImageAdp Url_(Io_url v) {url = v; return this;} Io_url url; + public Object Under() {return null;} + public boolean Disposed() {return disposed;} private boolean disposed = false; + public void Rls() {disposed = true;} + public void SaveAsBmp(Io_url url) {SaveAs(url, ".bmp");} + public void SaveAsPng(Io_url url) {SaveAs(url, ".png");} + void SaveAs(Io_url url, String ext) {Io_mgr.Instance.SaveFilStr(url.GenNewExt(ext), size.To_str());} + public ImageAdp Extract_image(RectAdp src_rect, SizeAdp trg_size) {return Extract_image(src_rect.X(), src_rect.Y(), src_rect.Width(), src_rect.Height(), trg_size.Width(), trg_size.Height());} + public ImageAdp Extract_image(int src_x, int src_y, int src_w, int src_h, int trg_w, int trg_h) {return ImageAdp_.txt_mem_(Io_url_.Empty, SizeAdp_.new_(trg_w, trg_h));} + public ImageAdp Resize(int width, int height) {return ImageAdp_.txt_mem_(Io_url_.Empty, SizeAdp_.new_(width, height));} + public ImageAdp_txt(Io_url url, SizeAdp size) {this.url = url; this.size = size;} +} diff --git a/150_gfui/src/gplx/gfui/imgs/ImageAdp_.java b/150_gfui/src/gplx/gfui/imgs/ImageAdp_.java index a27517de8..3001eaf17 100644 --- a/150_gfui/src/gplx/gfui/imgs/ImageAdp_.java +++ b/150_gfui/src/gplx/gfui/imgs/ImageAdp_.java @@ -13,3 +13,112 @@ 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.gfui.imgs; import gplx.*; import gplx.gfui.*; +import gplx.core.primitives.*; +import gplx.core.ios.*; /*IoStream*/ import gplx.core.ios.streams.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.Toolkit; +import java.awt.image.BufferedImage; +import javax.imageio.ImageIO; +public class ImageAdp_ { + public static ImageAdp as_(Object obj) {return obj instanceof ImageAdp ? (ImageAdp)obj : null;} + public static ImageAdp cast(Object obj) {try {return (ImageAdp)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, ImageAdp.class, obj);}} + public static final ImageAdp Null = new_(10, 10); + public static ImageAdp new_(int width, int height) { + // BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); // JAVA: must be TYPE_INT_RGB or else ImageIO.write("bmp") will fail + BufferedImage img = getCompatibleImage(width, height); + return new ImageAdp_base(img); + } + public static ImageAdp txt_mem_(Io_url url, SizeAdp size) {return new ImageAdp_txt(url, size);} + public static ImageAdp txt_fil_(Io_url url) { + String raw = Io_mgr.Instance.LoadFilStr(url); + SizeAdp size = null; + if (String_.Eq(raw, "")) size = SizeAdp_.Zero; + else if (String_.Eq(url.Ext(), ".svg")) size = SizeOf_svg(url); + else size = SizeAdp_.parse(raw); + return new ImageAdp_txt(url, size); + } + public static SizeAdp SizeOf_svg(Io_url url) {return Gfui_svg_util.QuerySize(url);} + public static ImageAdp file_(Io_url url) { + if (url.EqNull()) throw Err_.new_wo_type("cannot load image from null url"); + if (String_.Eq(url.Info().Key(), IoUrlInfo_.Mem.Key())) return txt_fil_(url); + if (!Io_mgr.Instance.ExistsFil(url)) return Null; + + BufferedImage img = null; + try { + File f = new File(url.Xto_api()); + img = ImageIO.read(f); + } + catch (IOException e) {throw Err_.new_exc(e, "ui", "image load failed", "url", url.Xto_api());} +// FileInputStream istream = new FileInputStream(new File(url.Xto_api())); +// JPEGImageDecoder dec = JPEGCodec.createJPEGDecoder(istream); +// BufferedImage im = dec.decodeAsBufferedImage(); +// try {img = ImageIO.read(new File());} +// catch (IOException e) {} + return new ImageAdp_base(img).Url_(url); + } + private static BufferedImage getCompatibleImage(int w, int h) + { + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice gd = ge.getDefaultScreenDevice(); + GraphicsConfiguration gc = gd.getDefaultConfiguration(); + BufferedImage image = gc.createCompatibleImage(w, h); +// System.out.println("compatible image type = " + image.getType()); + return image; + } + public static int MemorySize(ImageAdp image) { + int areaInPixels = image.Height() * image.Width(); + BufferedImage bufferedImage = (BufferedImage)image.Under(); + int bitsPerPixel = BitsPerPixelCalc(bufferedImage.getType(), image.Url()); +// int bitsPerPixel = 4; + return areaInPixels * (bitsPerPixel / 8); // 8 bits per byte + } + static int BitsPerPixelCalc(int imageType, Io_url url) { // REF:http://java.sun.com/j2se/1.4.2/docs/api/java/awt/image/BufferedImage.html + if (imageType == BufferedImage.TYPE_3BYTE_BGR) return 24; + else if (imageType == BufferedImage.TYPE_4BYTE_ABGR) return 32; + else if (imageType == BufferedImage.TYPE_4BYTE_ABGR_PRE) return 32; + else if (imageType == BufferedImage.TYPE_BYTE_BINARY) return 8; //? + else if (imageType == BufferedImage.TYPE_BYTE_GRAY) return 8; //? + else if (imageType == BufferedImage.TYPE_BYTE_INDEXED) return 8; //? + else if (imageType == BufferedImage.TYPE_CUSTOM) return 8; //? + else if (imageType == BufferedImage.TYPE_INT_ARGB) return 32; //? + else if (imageType == BufferedImage.TYPE_INT_ARGB_PRE) return 32; //? + else if (imageType == BufferedImage.TYPE_INT_BGR) return 32; //? + else if (imageType == BufferedImage.TYPE_INT_RGB) return 32; //? + else if (imageType == BufferedImage.TYPE_USHORT_555_RGB) return 16; //? + else if (imageType == BufferedImage.TYPE_USHORT_565_RGB) return 16; //? + else if (imageType == BufferedImage.TYPE_USHORT_GRAY) return 16; //? + else {UsrDlg_.Instance.Warn("unknown bits per pixel", "imageType", imageType, "url", url.Xto_api()); return 8;} + } + } +class Gfui_svg_util { + public static SizeAdp QuerySize(Io_url url) { + try { + // NOTE: not using XmlDoc b/c invalid doctypes can cause xml to hang; type = arg.getClass(); + if ( type == IptKey.class + || type == IptKeyChain.class) return IptEventType_.KeyDown; + else if (type == IptMouseBtn.class) return IptEventType_.MouseUp; // changed from MouseDown; confirmed against Firefox, Eclipse; DATE:2014-05-16 + else if (type == IptMouseWheel.class) return IptEventType_.MouseWheel; + else if (type == IptMouseMove.class) return IptEventType_.MouseMove; + else throw Err_.new_unhandled(type); + } + @gplx.Internal protected static boolean EventType_match(IptArg arg, IptEventType match) { + Class type = arg.getClass(); + if ( type == IptKey.class + || type == IptKeyChain.class) return match == IptEventType_.KeyDown || match == IptEventType_.KeyUp || match == IptEventType_.KeyDown; + else if (type == IptMouseBtn.class) return match == IptEventType_.MouseDown || match == IptEventType_.MouseUp || match == IptEventType_.MousePress; + else if (type == IptMouseWheel.class) return match == IptEventType_.MouseWheel; + else if (type == IptMouseMove.class) return match == IptEventType_.MouseMove; + else throw Err_.new_unhandled(type); + } +} +class IptMacro { + public void Reg(String prefix, String alias, IptArg arg) { + if (regy == null) Init(); + Ordered_hash list = (Ordered_hash)regy.Get_by(prefix); + if (list == null) { + list = Ordered_hash_.New(); + regy.Add(prefix, list); + } + list.Add_if_dupe_use_nth(alias, arg); + } + void Init() { + regy = Ordered_hash_.New(); + Reg("mod", "c", IptKey_.add_(IptKey_.Ctrl)); + Reg("mod", "a", IptKey_.add_(IptKey_.Alt)); + Reg("mod", "s", IptKey_.add_(IptKey_.Shift)); + Reg("mod", "ca", IptKey_.add_(IptKey_.Ctrl, IptKey_.Alt)); + Reg("mod", "cs", IptKey_.add_(IptKey_.Ctrl, IptKey_.Shift)); + Reg("mod", "as", IptKey_.add_(IptKey_.Alt, IptKey_.Shift)); + Reg("mod", "cas", IptKey_.add_(IptKey_.Ctrl, IptKey_.Alt, IptKey_.Shift)); + } + public IptArg parse(String raw) { + if (regy == null) Init(); + String[] plusAry = String_.Split(raw, "+"); + String[] dotAry = String_.Split(plusAry[0], "."); + String bgn = dotAry[0], end = dotAry[1]; + Ordered_hash list = (Ordered_hash)regy.Get_by(bgn); + if (list == null) throw parse_err(raw, "list not found").Args_add("list", bgn); + IptKey rv = (IptKey)list.Get_by(end); + if (rv == null) throw parse_err(raw, "arg not found").Args_add("arg", end); + for (int i = 1; i < plusAry.length; i++) { + rv = rv.Add((IptKey)IptKey_.parse(plusAry[i])); + } + return rv; + } + Ordered_hash regy; + static Err parse_err(String raw, String loc) {return Err_.new_("gfui", "could not parse IptArg", "raw", raw, "loc", loc).Trace_ignore_add_1_();} + public static final IptMacro Instance = new IptMacro(); IptMacro() {} +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptArg_parser_tst.java b/150_gfui/src/gplx/gfui/ipts/IptArg_parser_tst.java index a27517de8..d9d3f8a02 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptArg_parser_tst.java +++ b/150_gfui/src/gplx/gfui/ipts/IptArg_parser_tst.java @@ -13,3 +13,49 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import org.junit.*; import gplx.gfui.ipts.*; +public class IptArg_parser_tst { + @Test public void KeyBasic() { + tst_parse_Key_("key.a", IptKey_.A); + tst_parse_Key_("key.d0", IptKey_.D0); + tst_parse_Key_("key.semicolon", IptKey_.Semicolon); + tst_parse_Key_("key.equal", IptKey_.Equal); + tst_parse_Key_("key.pageUp", IptKey_.PageUp); + tst_parse_Key_("key.ctrl", IptKey_.Ctrl); + tst_parse_Key_("key.none", IptKey_.None); + } void tst_parse_Key_(String raw, IptKey expd) {Tfds.Eq(expd.Val(), IptKey_.parse(raw).Val());} + @Test public void KbdCmdModifiers() { + tst_parse_Key_("key.ctrl+key.enter", IptKey_.Ctrl.Add(IptKey_.Enter)); + tst_parse_Key_("key.alt+key.escape", IptKey_.Alt.Add(IptKey_.Escape)); + tst_parse_Key_("key.shift+key.f1", IptKey_.Shift.Add(IptKey_.F1)); + tst_parse_Key_("key.shift+key.ctrl", IptKey_.Ctrl.Add(IptKey_.Shift)); + tst_parse_Key_("key.ctrl+key.alt+key.slash", IptKey_.Ctrl.Add(IptKey_.Alt).Add(IptKey_.Slash)); + } + @Test public void KeyWhitespace() { + tst_parse_Key_("key.ctrl + key.alt + key.slash", IptKey_.Ctrl.Add(IptKey_.Alt).Add(IptKey_.Slash)); + } + @Test public void MouseBtn() { + tst_parse_MouseBtn_("mouse.left", IptMouseBtn_.Left); + tst_parse_MouseBtn_("mouse.right", IptMouseBtn_.Right); + tst_parse_MouseBtn_("mouse.middle", IptMouseBtn_.Middle); + tst_parse_MouseBtn_("mouse.x1", IptMouseBtn_.X1); + tst_parse_MouseBtn_("mouse.x2", IptMouseBtn_.X2); + } void tst_parse_MouseBtn_(String raw, IptMouseBtn expd) {Tfds.Eq(expd, IptMouseBtn_.parse(raw));} + @Test public void MouseWheel() { + tst_parse_MouseWheel_("wheel.up", IptMouseWheel_.Up); + tst_parse_MouseWheel_("wheel.down", IptMouseWheel_.Down); + } void tst_parse_MouseWheel_(String raw, IptMouseWheel expd) {Tfds.Eq(expd, IptMouseWheel_.parse(raw));} + @Test public void Mod() { + tst_parse_("mod.c", IptKey_.Ctrl); + tst_parse_("mod.cs", IptKey_.add_(IptKey_.Ctrl, IptKey_.Shift)); + tst_parse_("mod.cas", IptKey_.add_(IptKey_.Ctrl, IptKey_.Alt, IptKey_.Shift)); + tst_parse_("mod.c+key.c", IptKey_.add_(IptKey_.Ctrl, IptKey_.C)); + } + @Test public void All() { + tst_parse_("key.c", IptKey_.C); + tst_parse_("mouse.left", IptMouseBtn_.Left); + tst_parse_("wheel.up", IptMouseWheel_.Up); + tst_parse_("mod.c", IptKey_.Ctrl); + } void tst_parse_(String raw, IptArg expd) {Tfds.Eq(expd, IptArg_.parse(raw));} +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptBnd.java b/150_gfui/src/gplx/gfui/ipts/IptBnd.java index a27517de8..d8fe0e251 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptBnd.java +++ b/150_gfui/src/gplx/gfui/ipts/IptBnd.java @@ -13,3 +13,11 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import gplx.core.interfaces.*; +public interface IptBnd extends SrlAble { + String Key(); + List_adp Ipts(); + IptEventType EventTypes(); + void Exec(IptEventData iptData); +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptBndMgr.java b/150_gfui/src/gplx/gfui/ipts/IptBndMgr.java index a27517de8..187a7329b 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptBndMgr.java +++ b/150_gfui/src/gplx/gfui/ipts/IptBndMgr.java @@ -13,3 +13,280 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import gplx.core.interfaces.*; +public class IptBndMgr implements SrlAble { + public IptEventType EventsToFwd() {return eventsToFwd;} + public void EventsToFwd_set(IptEventType v) {eventsToFwd = v;} IptEventType eventsToFwd = IptEventType_.KeyDown; + public void EventsToFwd_add(IptEventType v) {eventsToFwd = eventsToFwd.Add(v);} + public boolean Has(IptEventType type) {return IptEventType_.Has(curTypes, type);} + public void Clear() {hash.Clear(); curTypes = IptEventType_.None; ClearLists(); chainMgr.Clear();} + public void Add(IptBnd bnd) { + for (IptBndHash list : regy) + if (IptEventType_.Has(bnd.EventTypes(), list.EventType())) + list.Add(bnd); + for (int i = 0; i < bnd.Ipts().Count(); i++) { + IptArg arg = (IptArg)bnd.Ipts().Get_at(i); + chainMgr.Add(arg); + } + } + public List_adp Cfgs() {return cfgs;} List_adp cfgs = List_adp_.New(); + public void Cfgs_delAll() { + List_adp del = List_adp_.New(); + for (int i = 0; i < cfgs.Count(); i++) { + IptCfgPtr ptr = (IptCfgPtr)cfgs.Get_at(i); + IptCfg cfg = IptCfgRegy.Instance.GetOrNew(ptr.CfgKey()); + cfg.Owners_del(ptr.CfgKey()); + for (IptBndHash list : regy) { + for (int j = 0; j < list.Count(); j++) { + IptBndListItm itmList = list.Get_at(j); + for (int k = 0; k < itmList.Count(); k++) { + IptBnd bnd = itmList.Get_at(k); + if (String_.Eq(ptr.BndKey(), bnd.Key())) { + list.Del(bnd); + } + } + } + } + del.Add(cfg); + } + for (int i = 0; i < del.Count(); i++) { + IptCfg cfg = (IptCfg)del.Get_at(i); + cfgs.Del(cfg); + } + } + public void Change(String key, IptArg[] ary) { + IptBnd old = null; + for (IptBndHash list : regy) { + for (int j = 0; j < list.Count(); j++) { + IptBndListItm itmList = list.Get_at(j); + for (int i = 0; i < itmList.Count(); i++) { + IptBnd bnd = itmList.Get_at(i); + if (String_.Eq(key, bnd.Key())) { + old = bnd; + break; + } + } + } + } + if (old == null) return; + this.Del(old); + old.Ipts().Clear(); + if (ary == IptArg_.Ary_empty) return; // "unbind"; exit after deleting; DATE:2014-05-13 + old.Ipts().Add_many((Object[])ary); + this.Add(old); + } + public void Del_by_key(String key) {Del_by(true, key);} + public void Del_by_ipt(IptArg ipt) { + if (IptArg_.Is_null_or_none(ipt)) return; + Del_by(false, ipt.Key()); + } + private void Del_by(boolean del_by_key, String del_key) { + int regy_len = regy.length; + List_adp deleted = List_adp_.New(); + for (int i = 0; i < regy_len; i++) { + IptBndHash list = regy[i]; + int list_len = list.Count(); + for (int j = 0; j < list_len; j++) { + IptBndListItm bnds = list.Get_at(j); + int bnds_len = bnds.Count(); + for (int k = 0; k < bnds_len; k++) { + IptBnd itm_bnd = bnds.Get_at(k); + if (del_by_key) { + if (String_.Eq(del_key, itm_bnd.Key())) { + deleted.Add(itm_bnd); + } + } + else { + if (itm_bnd.Ipts().Count() != 1) continue; // only delete if bnd has 1 ipt; should only be called by xowa which does 1 bnd per ipt + IptArg itm_ipt = (IptArg)itm_bnd.Ipts().Get_at(0); + if (String_.Eq(del_key, itm_ipt.Key())) + deleted.Add(itm_bnd); + } + } + } + } + int deleted_len = deleted.Count(); + for (int i = 0; i < deleted_len; i++) { + IptBnd bnd = (IptBnd)deleted.Get_at(i); + this.Del(bnd); + bnd.Ipts().Clear(); + } + } + public void Del(IptBnd bnd) { + for (IptBndHash list : regy) { + if (IptEventType_.Has(bnd.EventTypes(), list.EventType())) { + list.Del(bnd); + } + } + for (int i = 0; i < bnd.Ipts().Count(); i++) { + IptArg arg = (IptArg)bnd.Ipts().Get_at(i); + chainMgr.Del(arg); + } + } + public boolean Process(IptEventData evData) { + IptBndHash list = regy[AryIdx(evData.EventType())]; + String key = evData.EventArg().Key(); + if (!String_.Eq(chainMgr.ActiveKey(), "")) key = chainMgr.ActiveKey() + key; + IptBndListItm itm = list.Get_by(key); + String chainP = ""; + if (evData.EventType() == IptEventType_.KeyDown) { + chainP = chainMgr.Process(evData.EventArg()); + if (!String_.Eq(chainP, "") && itm == null) + UsrDlg_.Instance.Note("cancelled... {0}", chainP); + } + if (itm == null) { + return false; + } + return itm.Exec(evData); + } + public Object Srl(GfoMsg owner) { + GfoMsg m = GfoMsg_.srl_(owner, "mgr"); + for (int i = 0; i < hash.Count(); i++) + ((IptBnd)hash.Get_at(i)).Srl(m); + return this; + } + IptArgChainMgr chainMgr = new IptArgChainMgr(); + Ordered_hash hash = Ordered_hash_.New(); IptEventType curTypes = IptEventType_.None; + public static IptBndMgr new_() {return new IptBndMgr();} + IptBndHash[] regy = new IptBndHash[8]; + IptBndMgr() {ClearLists();} + void ClearLists(){ + MakeList(IptEventType_.KeyDown); MakeList(IptEventType_.KeyUp); MakeList(IptEventType_.KeyPress); + MakeList(IptEventType_.MouseMove); MakeList(IptEventType_.MouseDown); MakeList(IptEventType_.MouseUp); MakeList(IptEventType_.MouseWheel); MakeList(IptEventType_.MousePress); + } void MakeList(IptEventType eventType) {regy[AryIdx(eventType)] = new IptBndHash(eventType);} + static int AryIdx(IptEventType eventType) { + int v = eventType.Val(); + if (v == IptEventType_.KeyDown.Val()) return 0; + else if (v == IptEventType_.KeyUp.Val()) return 1; + else if (v == IptEventType_.KeyPress.Val()) return 2; + else if (v == IptEventType_.MouseDown.Val()) return 3; + else if (v == IptEventType_.MouseUp.Val()) return 4; + else if (v == IptEventType_.MouseMove.Val()) return 5; + else if (v == IptEventType_.MouseWheel.Val()) return 6; + else if (v == IptEventType_.MousePress.Val()) return 7; + else throw Err_.new_unhandled(v); + } +} +class IptBndHash implements SrlAble { + private IptBndListItm wildcard_list; + public IptEventType EventType() {return eventType;} IptEventType eventType; + public int Count() {return hash.Count();} + public IptBndListItm Get_by(String key) {return wildcard_list == null ? (IptBndListItm)hash.Get_by(key) : wildcard_list;} + public IptBndListItm Get_at(int i) {return (IptBndListItm)hash.Get_at(i);} + public void Add(IptBnd bnd) { + for (int i = 0; i < bnd.Ipts().Count(); i++) { + IptArg arg = (IptArg)bnd.Ipts().Get_at(i); + if (!IptArg_.EventType_match(arg, eventType)) continue; // bnd may have multiple ipts of different evTypes; only add bnd if evType matches + if (String_.Eq(arg.Key(), IptArg_.Wildcard_key)) { + if (wildcard_list == null) wildcard_list = new IptBndListItm(IptArg_.Wildcard_key); + wildcard_list.Add(bnd); + } + else { + IptBndListItm itm = (IptBndListItm)hash.Get_by(arg.Key()); + if (itm == null) { + itm = new IptBndListItm(arg.Key()); + hash.Add(arg.Key(), itm); + } + itm.Add(bnd); + } + } + } + public void Del(IptBnd bnd) { + for (int i = 0; i < bnd.Ipts().Count(); i++) { + IptArg arg = (IptArg)bnd.Ipts().Get_at(i); + if (!IptArg_.EventType_match(arg, eventType)) continue; // bnd may have multiple ipts of different evTypes; only add bnd if evType matches + hash.Del(arg.Key()); + } + } + public Object Srl(GfoMsg owner) { + GfoMsg m = GfoMsg_.srl_(owner, "list").Add("eventType", eventType.Name()); + for (int i = 0; i < hash.Count(); i++) + ((IptBndListItm)hash.Get_at(i)).Srl(m); + return this; + } + Ordered_hash hash = Ordered_hash_.New(); + public IptBndHash(IptEventType eventType) {this.eventType = eventType;} +} +class IptBndListItm implements SrlAble { + public String IptKey() {return iptKey;} private String iptKey; + public int Count() {return list.Count();} + public IptBnd Get_at(int i) {return (IptBnd)list.Get_at(i);} + public void Add(IptBnd bnd) {list.Add_at(0, bnd);} + public boolean Exec(IptEventData evData) { + for (int i = 0; i < list.Count(); i++) { + IptBnd bnd = (IptBnd)list.Get_at(i); + try {bnd.Exec(evData);} + catch (Exception exc) { + UsrDlg_.Instance.Stop(UsrMsg.new_("Error while processing event").Add("bnd", SrlAble_.To_str(bnd)).Add("exc", Err_.Message_lang(exc))); + return false; + } + if (evData.CancelIteration) break; + } + return true; + } + public Object Srl(GfoMsg owner) { + GfoMsg m = GfoMsg_.srl_(owner, "itm").Add("iptKey", iptKey); + for (int i = 0; i < list.Count(); i++) + ((IptBnd)list.Get_at(i)).Srl(m); + return this; + } + List_adp list = List_adp_.New(); + public IptBndListItm(String iptKey) {this.iptKey = iptKey;} +} +class IptArgChainMgr { + public void Clear() {regy.Clear();} + public String Process(IptArg arg) { +// if (String_.Eq(arg.Key(), "key_7")) return ""; + Hash_adp hash = (Hash_adp)active.Get_by(arg.Key()); + if (hash == null) { + active = regy; + String r = activeKey; + activeKey = ""; + return r; + } + active = hash; + activeKey = activeKey + arg.Key() + ","; + UsrDlg_.Instance.Note("{0} pressed...", activeKey); + return ""; + } + public String ActiveKey() {return activeKey;} + String activeKey = ""; + public IptArgChainMgr() {active = regy;} + Hash_adp active; + public void Add(IptArg arg) { + if (arg.getClass() != IptKeyChain.class) return; + IptKeyChain chain = (IptKeyChain)arg; + Add_recur(regy, chain.Chained(), 0); + } + public void Del(IptArg arg) { + if (arg.getClass() != IptKeyChain.class) return; + IptKeyChain chain = (IptKeyChain)arg; + Del_recur(regy, chain.Chained(), 0); + } + void Add_recur(Hash_adp cur, IptArg[] ary, int i) { + if (i == ary.length - 1) return; // -1 b/c last should not be registered; ex: key.a,key.b should register key.a only + IptArg ipt = ary[i]; + Hash_adp next = (Hash_adp)cur.Get_by(ipt.Key()); + if (next == null) { + next = Hash_adp_.New(); + cur.Add(ipt.Key(), next); + } + Add_recur(next, ary, i + 1); + }// a,b,c + void Del_recur(Hash_adp cur, IptArg[] ary, int i) { + IptArg ipt = ary[i]; + if (i == ary.length - 1) { + cur.Del(ipt.Key()); + return; // -1 b/c last should not be registered; ex: key.a,key.b should register key.a only + } + Hash_adp next = (Hash_adp)cur.Get_by(ipt.Key()); + if (next == null) { + return; + } + Del_recur(next, ary, i + 1); + if (cur.Count() == 1) + cur.Clear(); + } + Hash_adp regy = Hash_adp_.New(); +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptBndMgr_tst.java b/150_gfui/src/gplx/gfui/ipts/IptBndMgr_tst.java index a27517de8..710397632 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptBndMgr_tst.java +++ b/150_gfui/src/gplx/gfui/ipts/IptBndMgr_tst.java @@ -13,3 +13,58 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import org.junit.*; import gplx.core.strings.*; +public class IptBndMgr_tst { + @Before public void setup() { + fx = new IptBndMgr_fx(); + } IptBndMgr_fx fx; + @Test public void Add() { + fx.ini_Clear().run_Add("key.a").tst_Exec_same("key.a").tst_Exec_none("key.b"); + fx.ini_Clear().run_Add("key.ctrl+key.a").tst_Exec_same("key.ctrl+key.a").tst_Exec_none("key.ctrl").tst_Exec_none("key.a"); + fx.ini_Clear().run_Add("key.a|key.b").tst_Exec_same("key.a").tst_Exec_same("key.b").tst_Exec_none("key.c"); + fx.ini_Clear().run_Add("mouse.left").tst_Exec_same("mouse.left"); + fx.ini_Clear().run_Add("key.a,key.b") + .tst_Exec_none("key.a").tst_Exec_same("key.b") + .tst_Exec_none("key.a").tst_Exec_none("key.c").tst_Exec_none("key.a").tst_Exec_same("key.b") + .tst_Exec_none("key.a").tst_Exec_none("key.a").tst_Exec_none("key.b"); + } + class IptBndMgr_fx { + public IptBndMgr Under() {return under;} IptBndMgr under = IptBndMgr.new_(); + public IptBndMgr_fx ini_Clear() {under.Clear(); return this;} + public IptBndMgr_fx run_Add(String raw) { + IptArg[] args = IptArg_.parse_ary_(raw); + List_adp list = List_adp_.New(); + for (IptArg arg : args) + list.Add(arg); + + IptBnd_mok bnd = new IptBnd_mok(output).Key_(raw).Ipts_(list).EventTypes_(IptEventType_.default_(args)); + under.Add(bnd); + return this; + } + public IptBndMgr_fx tst_Exec_none(String key) {return tst_Exec(key, "");} + public IptBndMgr_fx tst_Exec_same(String key) {return tst_Exec(key, key);} + public IptBndMgr_fx tst_Exec(String key, String expd) { + output.Clear(); + IptArg[] args = IptArg_.parse_ary_(key); + for (IptArg arg : args) { + IptEventData evData = IptEventData.new_(null, IptArg_.EventType_default(arg), arg, null, null); + under.Process(evData); + } + Tfds.Eq(expd, output.To_str()); + return this; + } + String_bldr output = String_bldr_.new_(); + public IptBndMgr_fx() {} + } + class IptBnd_mok implements IptBnd { + public String Key() {return key;} public IptBnd_mok Key_(String v) {key = v; return this;} private String key; + public List_adp Ipts() {return args;} public IptBnd_mok Ipts_(List_adp v) {args = v; return this;} List_adp args; + public IptEventType EventTypes() {return eventTypes;} public IptBnd_mok EventTypes_(IptEventType v) {eventTypes = v; return this;} IptEventType eventTypes; + public Object Srl(GfoMsg owner) {return this;} + public void Exec(IptEventData iptData) { + output.Add(iptData.EventArg().Key()); + } + public IptBnd_mok(String_bldr v) {output = v;} String_bldr output; + } +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptBnd_.java b/150_gfui/src/gplx/gfui/ipts/IptBnd_.java index a27517de8..d7b2998c9 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptBnd_.java +++ b/150_gfui/src/gplx/gfui/ipts/IptBnd_.java @@ -13,3 +13,48 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import gplx.core.strings.*; +public class IptBnd_ { + public static void msg_(IptCfg cfg, IptBndsOwner box, String bndKey, GfoMsg m, IptArg... ipt) {bld_(cfg, box, (Gfo_invk)box, bndKey, m, ipt);} + public static void msg_to_(IptCfg cfg, IptBndsOwner box, Gfo_invk invk, String bndKey, GfoMsg m, IptArg... ipt) { + bld_(cfg, box, invk, bndKey, m, ipt); + } + public static void cmd_(IptCfg cfg, IptBndsOwner box, String key, IptArg... ipt) {bld_(cfg, box, (Gfo_invk)box, key, GfoMsg_.new_cast_(key), ipt);} + public static void cmd_to_(IptCfg cfg, IptBndsOwner box, Gfo_invk invk, String key, IptArg... ipt) {bld_(cfg, box, invk, key, GfoMsg_.new_cast_(key), ipt);} + public static void ipt_to_(IptCfg cfg, IptBndsOwner box, Gfo_invk invk, String key, IptEventType eventType, IptArg... ipt) {bld_(cfg, box, invk, key, GfoMsg_.new_cast_(key), eventType, ipt);} + + static void bld_(IptCfg cfg, IptBndsOwner box, Gfo_invk invk, String bndKey, GfoMsg m, IptArg... ipt) {bld_(cfg, box, invk, bndKey, m, IptEventType_.default_(ipt), ipt);} + static void bld_(IptCfg cfg, IptBndsOwner box, Gfo_invk invk, String bnd_key, GfoMsg m, IptEventType ev_type, IptArg... ipt) { + IptCfgItm itm = cfg.GetOrDefaultArgs(bnd_key, m, ipt); + IptBnd bnd = IptBnd_invk.new_(box, invk, itm, ev_type); + cfg.Owners_add(bnd_key, box); + box.IptBnds().Add(bnd); + } + public static Object Srl(GfoMsg owner, IptBnd bnd) {GfoMsg_.srl_(owner, "bnd").Add("key", bnd.Key()).Add("ipt", AryXtoStr(bnd.Ipts())); return bnd;} + static String AryXtoStr(List_adp ary) { + String_bldr sb = String_bldr_.new_(); + for (int i = 0; i < ary.Count(); i++) + sb.Add_spr_unless_first(((IptArg)ary.Get_at(i)).Key(), "|", i); + return sb.To_str(); + } +} +class IptBnd_invk implements IptBnd { + public String Key() {return key;} private String key; + public List_adp Ipts() {return ipts;} List_adp ipts; + public IptEventType EventTypes() {return eventTypes;} IptEventType eventTypes; + public void Exec(IptEventData iptData) { + GfoMsg newMsg = m.CloneNew(); + newMsg.Add("iptData", iptData); + GfsCtx ctx = GfsCtx.new_().MsgSrc_(owner); + invk.Invk(ctx, 0, m.Key(), newMsg); + iptData.Handled_on(); // NOTE: treat invk as SingleDispatch + } IptBndsOwner owner; Gfo_invk invk; IptCfgItm itm; GfoMsg m; + public Object Srl(GfoMsg owner) {return IptBnd_.Srl(owner, this);} + public static IptBnd_invk new_(IptBndsOwner owner, Gfo_invk invk, IptCfgItm itm, IptEventType evType) { + IptBnd_invk rv = new IptBnd_invk(); + rv.owner = owner; rv.invk = invk; rv.itm = itm; + rv.key = itm.Key(); rv.ipts = itm.Ipt(); rv.m = itm.Msg(); rv.eventTypes = evType; + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptBnd_chkBox.java b/150_gfui/src/gplx/gfui/ipts/IptBnd_chkBox.java index a27517de8..6860aa1c4 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptBnd_chkBox.java +++ b/150_gfui/src/gplx/gfui/ipts/IptBnd_chkBox.java @@ -13,3 +13,29 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import gplx.core.interfaces.*; import gplx.gfui.controls.standards.*; +public class IptBnd_chkBox implements InjectAble, Gfo_evt_itm { + public Gfo_evt_mgr Evt_mgr() {if (evt_mgr == null) evt_mgr = new Gfo_evt_mgr(this); return evt_mgr;} Gfo_evt_mgr evt_mgr; + public void Inject(Object owner) { + chkBox = GfuiChkBox_.cast(owner); + Gfo_evt_mgr_.Sub(chkBox, "Check_end", this, setCmd); + Gfo_evt_mgr_.Sub_same(fwd, setEvt, this); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, setCmd)) + Gfo_invk_.Invk_by_val(invkAble, setCmd, chkBox.Val()); + else if (ctx.Match(k, setEvt)) { + boolean v = m.ReadBool(msgArg); + chkBox.Val_sync(v); + } + else return Gfo_invk_.Rv_unhandled; + return Gfo_invk_.Rv_handled; + } + Gfo_evt_itm fwd; Gfo_invk invkAble; String setCmd, setEvt, msgArg; GfuiChkBox chkBox; + public static IptBnd_chkBox new_(Gfo_evt_itm src, String setCmd, String setEvt, String msgArg) { + IptBnd_chkBox rv = new IptBnd_chkBox(); + rv.fwd = src; rv.invkAble = (Gfo_invk)src; rv.setCmd = setCmd; rv.setEvt = setEvt; rv.msgArg = msgArg; + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptBnd_txt_cmd.java b/150_gfui/src/gplx/gfui/ipts/IptBnd_txt_cmd.java index a27517de8..3101e85ce 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptBnd_txt_cmd.java +++ b/150_gfui/src/gplx/gfui/ipts/IptBnd_txt_cmd.java @@ -13,3 +13,31 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import gplx.core.type_xtns.*; import gplx.core.interfaces.*; +import gplx.gfui.controls.standards.*; +public class IptBnd_txt_cmd implements InjectAble, Gfo_invk, Gfo_evt_itm { + public Gfo_evt_mgr Evt_mgr() {if (evt_mgr == null) evt_mgr = new Gfo_evt_mgr(this); return evt_mgr;} Gfo_evt_mgr evt_mgr; + public void Inject(Object owner) { + txtBox = GfuiTextBox_.cast(owner); + txtBox.TextAlignH_center_(); + IptBnd_.cmd_to_(IptCfg_.Null, txtBox, this, TxtBox_exec, IptKey_.Enter); + Gfo_evt_mgr_.Sub_same(fwd, setEvt, this); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, TxtBox_exec)) Gfo_invk_.Invk_by_val(src, setCmd, cls.ParseOrNull(txtBox.Text())); + else if (ctx.Match(k, setEvt)) { + int v = m.ReadInt("v"); + txtBox.Text_(cls.XtoUi(v, ClassXtnPool.Format_null)); + } + else return Gfo_invk_.Rv_unhandled; + return Gfo_invk_.Rv_handled; + } static final String TxtBox_exec = "TxtBox_exec"; + GfuiTextBox txtBox; Gfo_invk src; Gfo_evt_itm fwd; String setCmd, setEvt; ClassXtn cls; + public static IptBnd_txt_cmd new_(Gfo_evt_itm fwd, String setCmd, String setEvt, ClassXtn cls) { + IptBnd_txt_cmd rv = new IptBnd_txt_cmd(); + rv.fwd = fwd; rv.src = (Gfo_invk)fwd; + rv.setCmd = setCmd; rv.setEvt = setEvt; rv.cls = cls; + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptBnd_txt_range.java b/150_gfui/src/gplx/gfui/ipts/IptBnd_txt_range.java index a27517de8..e57f12572 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptBnd_txt_range.java +++ b/150_gfui/src/gplx/gfui/ipts/IptBnd_txt_range.java @@ -13,3 +13,83 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import gplx.core.interfaces.*; +import gplx.gfui.controls.standards.*; +public class IptBnd_txt_range implements InjectAble, Gfo_invk, Gfo_evt_itm { + public Gfo_evt_mgr Evt_mgr() {if (evt_mgr == null) evt_mgr = new Gfo_evt_mgr(this); return evt_mgr;} Gfo_evt_mgr evt_mgr; + public IptBnd_txt_range InitSrc_(Gfo_evt_itm initSrc) {this.initSrc = initSrc; return this;} + public IptBnd_txt_range InitEvt_(String initEvt) {this.initEvt = initEvt; return this;} String initEvt; + public IptBnd_txt_range PropSrc_(String getListCmd, String getCmd, String setCmd, String setEvt) { + this.getListCmd = getListCmd; this.getCmd = getCmd; this.setCmd = setCmd; this.setEvt = setEvt; + return this; + } String getListCmd, getCmd, setCmd, setEvt; + public IptBnd_txt_range PropList_(Keyval[] list) { + this.list = list; + return this; + } Keyval[] list = null; + public void Inject(Object owner) { + txtBox = GfuiTextBox_.cast(owner); + txtBox.TextAlignH_center_(); + IptBnd_.cmd_to_(IptCfg_.Null, txtBox, this, Invk_dec, IptKey_.Down, IptMouseWheel_.Down); + IptBnd_.cmd_to_(IptCfg_.Null, txtBox, this, Invk_inc, IptKey_.Up, IptMouseWheel_.Up); + IptBnd_.cmd_to_(IptCfg_.Null, txtBox, this, Invk_upd, IptKey_.Enter, IptMouseBtn_.Middle); + Gfo_evt_mgr_.Sub_same(initSrc, initEvt, this); + Gfo_evt_mgr_.Sub_same(propSrc, setEvt, this); + } GfuiTextBox txtBox; int previewIdx; Gfo_evt_itm propSrc, initSrc; Gfo_invk propInvk; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.MatchPriv(k, Invk_dec)) PreviewCmd(-1); + else if (ctx.MatchPriv(k, Invk_inc)) PreviewCmd( 1); + else if (ctx.MatchPriv(k, Invk_upd)) return UpdateCmd(); + else if (ctx.MatchPriv(k, setEvt)) WhenEvtCmd(m.CastObj("v")); + else if (ctx.MatchPriv(k, initEvt)) ReadyEvtCmd(); + else return Gfo_invk_.Rv_unhandled; + return Gfo_invk_.Rv_handled; + } static final String Invk_dec = "txtBox_dec", Invk_inc = "txtBox_inc", Invk_upd = "txtBox_exec"; + void PreviewCmd(int delta) { + int newVal = previewIdx + delta; + if (!Int_.RangeCheck(newVal, list.length)) return; + WhenEvtCmd(list[newVal].Key_as_obj()); + } + Object UpdateCmd() { + int idx = Int_.Min_value; + String find = txtBox.Text(); + for (int i = 0; i < list.length; i++) { // try to find .Text in list.Vals + if (String_.Eq(find, (String)list[i].Val())) idx = i; + } + if (idx == Int_.Min_value) { // try to find .Text in list.Keys + int key = Int_.Parse_or(txtBox.Text(), Int_.Min_value); if (key == Int_.Min_value) return Gfo_invk_.Rv_unhandled; + idx = GetByKey(key); if (idx == Int_.Min_value) return Gfo_invk_.Rv_unhandled; + } + ExecCmd(setCmd, idx); + return Gfo_invk_.Rv_handled; + } + void ReadyEvtCmd() { + if (getListCmd != null) + list = (Keyval[])Gfo_invk_.Invk_by_key(propInvk, getListCmd); + Object curId = Gfo_invk_.Invk_by_key(propInvk, getCmd); + WhenEvtCmd(curId); + } + void WhenEvtCmd(Object id) { + int idx = GetByKey(id); if (idx == Int_.Min_value) return; + previewIdx = idx; + txtBox.Text_(list[idx].Val_to_str_or_empty()); + } + void ExecCmd(String c, int idx) { + if (!Int_.RangeCheck(idx, list.length)) return; + Gfo_invk_.Invk_by_val(propInvk, setCmd, list[idx].Key_as_obj()); + } + int GetByKey(Object find) { + if (list == null) ReadyEvtCmd(); + for (int i = 0; i < list.length; i++) { + if (Object_.Eq(find, list[i].Key_as_obj())) return i; + } + return Int_.Min_value; + } + public static IptBnd_txt_range new_(Gfo_evt_itm propSrc) { + IptBnd_txt_range rv = new IptBnd_txt_range(); + rv.propSrc = propSrc; rv.propInvk = (Gfo_invk)propSrc; + rv.initSrc = propSrc; + return rv; + } IptBnd_txt_range() {} +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptBnd_upDownRange.java b/150_gfui/src/gplx/gfui/ipts/IptBnd_upDownRange.java index a27517de8..6067dd579 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptBnd_upDownRange.java +++ b/150_gfui/src/gplx/gfui/ipts/IptBnd_upDownRange.java @@ -13,3 +13,46 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import gplx.core.type_xtns.*; import gplx.core.interfaces.*; +import gplx.gfui.controls.standards.*; +public class IptBnd_upDownRange implements InjectAble, Gfo_invk, Gfo_evt_itm { + public Gfo_evt_mgr Evt_mgr() {if (evt_mgr == null) evt_mgr = new Gfo_evt_mgr(this); return evt_mgr;} Gfo_evt_mgr evt_mgr; + public void Inject(Object owner) { + txtBox = GfuiTextBox_.cast(owner); + txtBox.TextAlignH_center_(); + if (bndCfg == null) bndCfg = IptCfg_.new_("gplx.gfui.IptBnd_upDownRange"); + IptBnd_.cmd_to_(bndCfg, txtBox, this, Invk_TxtBox_dec, IptKey_.Down, IptMouseWheel_.Down); + IptBnd_.cmd_to_(bndCfg, txtBox, this, Invk_TxtBox_inc, IptKey_.Up, IptMouseWheel_.Up); + IptBnd_.cmd_to_(bndCfg, txtBox, this, Invk_TxtBox_exec, IptKey_.Enter, IptMouseBtn_.Middle); + Gfo_evt_mgr_.Sub_same(fwd, evt, this); + } static IptCfg bndCfg; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_TxtBox_dec)) ExecCmd(cmd, curVal - 1); + else if (ctx.Match(k, Invk_TxtBox_inc)) ExecCmd(cmd, curVal + 1); + else if (ctx.Match(k, Invk_TxtBox_exec)) { + Object valObj = IntClassXtn.Instance.ParseOrNull(txtBox.Text()); if (valObj == null) throw Err_.new_wo_type("invalid int", "text", txtBox.Text()); + ExecCmd(doIt, Int_.Cast(valObj)); + } + else if (ctx.Match(k, evt)) WhenEvt(ctx, m); + else return Gfo_invk_.Rv_unhandled; + return Gfo_invk_.Rv_handled; + } static final String Invk_TxtBox_dec = "txtBox_dec", Invk_TxtBox_inc = "txtBox_inc", Invk_TxtBox_exec = "txtBox_exec"; + public int Adj() {return adj;} public IptBnd_upDownRange Adj_(int v) {adj = v; return this;} int adj; + void WhenEvt(GfsCtx ctx, GfoMsg m) { + curVal = m.ReadInt(arg) + adj; + txtBox.Text_(Int_.To_str(curVal)); + } + void ExecCmd(String c, int val) { + Gfo_invk_.Invk_by_val(src, c, val - adj); + } + int curVal; + GfuiTextBox txtBox; Gfo_invk src; Gfo_evt_itm fwd; String cmd, evt, doIt, arg; + public static IptBnd_upDownRange new_(Gfo_evt_itm fwd, String cmd, String evt, String arg) {return exec_(fwd, cmd, evt, cmd, arg);} + public static IptBnd_upDownRange exec_(Gfo_evt_itm fwd, String cmd, String evt, String doIt, String arg) { + IptBnd_upDownRange rv = new IptBnd_upDownRange(); + rv.fwd = fwd; rv.src = (Gfo_invk)fwd; + rv.cmd = cmd; rv.evt = evt; rv.doIt = doIt; rv.arg = arg; + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptBndsOwner.java b/150_gfui/src/gplx/gfui/ipts/IptBndsOwner.java index a27517de8..17059ce38 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptBndsOwner.java +++ b/150_gfui/src/gplx/gfui/ipts/IptBndsOwner.java @@ -13,3 +13,7 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +public interface IptBndsOwner extends Gfo_evt_itm { + IptBndMgr IptBnds(); +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptCfg.java b/150_gfui/src/gplx/gfui/ipts/IptCfg.java index a27517de8..04ea6129f 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptCfg.java +++ b/150_gfui/src/gplx/gfui/ipts/IptCfg.java @@ -13,3 +13,79 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +public interface IptCfg extends Gfo_invk { + String CfgKey(); + Object NewByKey(Object o); + IptCfgItm GetOrDefaultArgs(String key, GfoMsg m, IptArg[] argAry); + void Owners_add(String key, IptBndsOwner owner); + void Owners_del(String key); +} +class IptCfg_base implements IptCfg { + public String CfgKey() {return cfgKey;} private String cfgKey; + public IptCfgItm GetOrDefaultArgs(String bndKey, GfoMsg defaultMsg, IptArg[] defaultArgs) { + IptCfgItm rv = (IptCfgItm)hash.Get_by(bndKey); + if (rv == null) { // no cfg + rv = IptCfgItm.new_().Key_(bndKey).Ipt_(List_adp_.New_by_many((Object[])defaultArgs)).Msg_(defaultMsg); + hash.Add(bndKey, rv); + } + else { // cfg exists + if (rv.Msg() == null) rv.Msg_(defaultMsg); // no msg defined; use default + } + return rv; + } + public IptCfgItm Set(String bndKey, GfoMsg m, IptArg[] argAry) { + IptCfgItm rv = GetOrDefaultArgs(bndKey, m, argAry); + rv.Msg_(m); // always overwrite msg + if (Dif(rv.Ipt(), argAry)) { + rv.Ipt_(List_adp_.New_by_many((Object[])argAry)); + this.Change(bndKey, argAry); + } + return rv; + } + boolean Dif(List_adp lhs, IptArg[] rhs) { + if (lhs.Count() != rhs.length) return true; + for (int i = 0; i < rhs.length; i++) { + IptArg lhsArg = (IptArg)lhs.Get_at(i); + IptArg rhsArg = rhs[i]; + if (!lhsArg.Eq(rhsArg)) return true; + } + return false; + } + void Change(String bndKey, IptArg[] ary) { + List_adp list = (List_adp)owners.Get_by(bndKey); + if (list == null) return; + for (int i = 0; i < list.Count(); i++) { + IptBndsOwner owner = (IptBndsOwner)list.Get_at(i); + owner.IptBnds().Change(bndKey, ary); + } + } + public void Owners_del(String bndKey) {owners.Del(bndKey);} + public void Owners_add(String bndKey, IptBndsOwner owner) { + List_adp list = (List_adp)owners.Get_by(bndKey); + if (list == null) { + list = List_adp_.New(); + owners.Add(bndKey, list); + } + list.Add(owner); + owner.IptBnds().Cfgs().Add(new IptCfgPtr(cfgKey, bndKey)); + } Ordered_hash owners = Ordered_hash_.New(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.MatchIn(k, Invk_Add, Invk_set)) { + String bndKey = m.ReadStr("bndKey"); + String iptStr = m.ReadStr("ipt"); + String cmd = m.ReadStrOr("cmd", ""); + if (ctx.Deny()) return this; + Set(bndKey, gplx.gfml.GfmlDataNde.XtoMsgNoRoot(cmd), IptArg_.parse_ary_(iptStr)); + } + return this; + } public static final String Invk_Add = "Add", Invk_set = "set"; + public IptCfg_base(String cfgKey) {this.cfgKey = cfgKey;} + Ordered_hash hash = Ordered_hash_.New(); + public Object NewByKey(Object o) {return new IptCfg_base((String)o);} @gplx.Internal protected static final IptCfg HashProto = new IptCfg_base(); @gplx.Internal protected IptCfg_base() {} +} +class IptCfgPtr { + public String CfgKey() {return cfgKey;} private String cfgKey; + public String BndKey() {return bndKey;} private String bndKey; + public IptCfgPtr(String cfgKey, String bndKey) {this.cfgKey = cfgKey; this.bndKey = bndKey;} +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptCfgItm.java b/150_gfui/src/gplx/gfui/ipts/IptCfgItm.java index a27517de8..537930c0a 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptCfgItm.java +++ b/150_gfui/src/gplx/gfui/ipts/IptCfgItm.java @@ -13,3 +13,10 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +public class IptCfgItm { + public String Key() {return key;} public IptCfgItm Key_(String v) {key = v; return this;} private String key; + public List_adp Ipt() {return ipt;} public IptCfgItm Ipt_(List_adp v) {ipt = v; return this;} List_adp ipt; + public GfoMsg Msg() {return msg;} public IptCfgItm Msg_(GfoMsg v) {msg = v; return this;} GfoMsg msg; + public static IptCfgItm new_() {return new IptCfgItm();} IptCfgItm() {} +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptCfgRegy.java b/150_gfui/src/gplx/gfui/ipts/IptCfgRegy.java index a27517de8..e63c87cbd 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptCfgRegy.java +++ b/150_gfui/src/gplx/gfui/ipts/IptCfgRegy.java @@ -13,3 +13,26 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +public class IptCfgRegy implements Gfo_invk { + public void Clear() {hash.Clear();} + public IptCfg GetOrNew(String k) { + IptCfg rv = (IptCfg)hash.Get_by(k); + if (rv == null) { + rv = (IptCfg)IptCfg_base.HashProto.NewByKey(k); + hash.Add(k, rv); + } + return rv; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.MatchIn(k, Invk_Get, Invk_get)) { + String key = m.ReadStr("key"); + if (ctx.Deny()) return this; + return GetOrNew(key); + } + return this; + } public static final String Invk_Get = "Get", Invk_get = "get"; + Ordered_hash hash = Ordered_hash_.New(); + public static final IptCfgRegy Instance = new IptCfgRegy(); + public IptCfgRegy() {} +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptCfg_.java b/150_gfui/src/gplx/gfui/ipts/IptCfg_.java index a27517de8..a9b73ad7b 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptCfg_.java +++ b/150_gfui/src/gplx/gfui/ipts/IptCfg_.java @@ -13,3 +13,17 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +public class IptCfg_ { + public static final IptCfg Null = IptCfg_null.Instance; + public static IptCfg new_(String key) {return IptCfgRegy.Instance.GetOrNew(key);} +} +class IptCfg_null implements IptCfg { + public String CfgKey() {return "<>";} + public IptCfgItm GetOrDefaultArgs(String bndKey, GfoMsg m, IptArg[] argAry) {return IptCfgItm.new_().Key_(bndKey).Ipt_(List_adp_.New_by_many((Object[])argAry)).Msg_(m);} + public void Owners_add(String key, IptBndsOwner owner) {} + public void Owners_del(String key) {} + public Object NewByKey(Object o) {return this;} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return Gfo_invk_.Rv_unhandled;} + public static final IptCfg_null Instance = new IptCfg_null(); IptCfg_null() {} +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptCfg_tst.java b/150_gfui/src/gplx/gfui/ipts/IptCfg_tst.java index a27517de8..6a4329fd4 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptCfg_tst.java +++ b/150_gfui/src/gplx/gfui/ipts/IptCfg_tst.java @@ -13,3 +13,70 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import org.junit.*; +public class IptCfg_tst { + @Before public void setup() { + IptCfgRegy.Instance.Clear(); + box = new IptBndsOwner_mok(); + cfg = new IptCfg_mok(); + key = IptBndsOwner_mok.Invk_Reg; + } IptBndsOwner_mok box; IptCfg_mok cfg; String key; + @Test public void Basic() { + cfg.run_GetOrDflt(box, key, IptKey_.A); + box.tst_SendKey(IptKey_.A, 1); + } + @Test public void Del() { + cfg.run_GetOrDflt(box, key, IptKey_.A); + box.IptBnds().Cfgs_delAll(); + box.tst_SendKey(IptKey_.A, 0); + } + @Test public void Change() { + cfg.run_GetOrDflt(box, key, IptKey_.A); + cfg.run_Set(key, IptKey_.B); + box.tst_SendKey(IptKey_.B, 1); + + cfg.run_Set(key, IptKey_.C); + box.tst_SendKey(IptKey_.C, 1); + box.tst_SendKey(IptKey_.B, 0); + } + @Test public void SetBeforeInit() { + cfg.run_Set(key, IptKey_.B); + cfg.run_GetOrDflt(box, key, IptKey_.A); + box.tst_SendKey(IptKey_.B, 1); + box.tst_SendKey(IptKey_.A, 0); + } + @Test public void SetBeforeInit_msg() { + cfg.run_Set_msg(key, 2, IptKey_.B); + cfg.run_GetOrDflt(box, key, IptKey_.A); // iptBnd exists; ignore Key_.A (and also msg=1) + box.tst_SendKey(IptKey_.B, 2); + box.tst_SendKey(IptKey_.A, 0); + } + @Test public void Chained() { + cfg.run_GetOrDflt(box, key, IptKeyChain.parse("key.ctrl+key.a,key.b")); + cfg.run_Set(key, IptKey_.A); + box.tst_SendKey(IptKey_.A, 1); + } + class IptCfg_mok { + public IptCfg Cfg() {return cfg;} + public void run_GetOrDflt(IptBndsOwner box, String key, IptArg... ary) {IptBnd_.msg_(cfg, box, key, make_(key, 1), ary);} + public void run_Set(String key, IptArg... ary) {cfg.Set(key, make_(key, 1), ary);} + public void run_Set_msg(String key, int i, IptArg... ary) {cfg.Set(key, make_(key, i), ary);} + GfoMsg make_(String key, int i) {return GfoMsg_.new_cast_(key).Add("val", i);} + public IptCfg_mok() {cfg = (IptCfg_base)IptCfg_.new_("cfg");} IptCfg_base cfg; + } + class IptBndsOwner_mok implements IptBndsOwner { + public IptBndMgr IptBnds() {return iptBnds;} IptBndMgr iptBnds = IptBndMgr.new_(); + public Gfo_evt_mgr Evt_mgr() {if (evt_mgr == null) evt_mgr = new Gfo_evt_mgr(this); return evt_mgr;} Gfo_evt_mgr evt_mgr; + public void tst_SendKey(IptKey key, int expd) { + iptBnds.Process(IptEventData.new_(null, IptEventType_.KeyDown, key, IptEvtDataKey.new_(key), IptEvtDataMouse.Null)); + Tfds.Eq(expd, actl); + actl = 0; + } int actl; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_Reg)) {actl = m.ReadIntOr("val", 0);} + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String Invk_Reg = "Reg"; + } +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptEventData.java b/150_gfui/src/gplx/gfui/ipts/IptEventData.java index a27517de8..d605e2c11 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptEventData.java +++ b/150_gfui/src/gplx/gfui/ipts/IptEventData.java @@ -13,3 +13,36 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import gplx.gfui.controls.elems.*; +public class IptEventData { + public GfuiElem Sender() {return sender;} GfuiElem sender; + public IptArg EventArg() {return eventArg;} IptArg eventArg; + public IptEventType EventType() {return eventType;} IptEventType eventType; + + public IptKey Key() {return keyData.Key();} IptEvtDataKey keyData; IptEvtDataKeyHeld keyPressData; + public IptMouseBtn MouseBtn() {return mouseData.Button();} IptEvtDataMouse mouseData; + public IptMouseWheel MouseWheel() {return mouseData.Wheel();} + public PointAdp MousePos() {return mouseData.Pos();} + public boolean Handled() {return handled;} private boolean handled; + public void Handled_on() {Handled_set(true);} + public void Handled_off() {Handled_set(false);} + public boolean CancelIteration; + void Handled_set(boolean val) { + keyData.Handled_set(val); + keyPressData.Handled_set(val); + handled = val; + } + + public static IptEventData as_(Object obj) {return obj instanceof IptEventData ? (IptEventData)obj : null;} + public static IptEventData cast(Object obj) {try {return (IptEventData)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, IptEventData.class, obj);}} + @gplx.Internal protected static IptEventData new_(GfuiElem sender, IptEventType eventType, IptArg eventArg, IptEvtDataKey keyData, IptEvtDataMouse mouseData) {return new_(sender, eventType, eventArg, keyData, IptEvtDataKeyHeld.Null, mouseData);} + @gplx.Internal protected static IptEventData new_(GfuiElem sender, IptEventType eventType, IptArg eventArg, IptEvtDataKey keyData, IptEvtDataKeyHeld keyPressData, IptEvtDataMouse mouseData) { + IptEventData rv = new IptEventData(); + rv.sender = sender; + rv.eventType = eventType; rv.eventArg = eventArg; + rv.keyData = keyData; rv.keyPressData = keyPressData; rv.mouseData = mouseData; + return rv; + } IptEventData() {} + public static IptEventData ctx_(GfsCtx ctx, GfoMsg m) {return IptEventData.cast(m.CastObj("iptData"));} +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptEventMgr.java b/150_gfui/src/gplx/gfui/ipts/IptEventMgr.java index a27517de8..1c70a2ecb 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptEventMgr.java +++ b/150_gfui/src/gplx/gfui/ipts/IptEventMgr.java @@ -13,3 +13,85 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import gplx.gfui.envs.*; import gplx.gfui.controls.elems.*; +public class IptEventMgr implements Gfo_invk { + public static void ExecKeyDown(GfuiElem sender, IptEvtDataKey keyState) { + keyHandled = false; keyStateCur = keyState; // cache for simultaneous ipt events (ex: key.ctrl + mouse.left) + IptEventData iptData = IptEventData.new_(sender, IptEventType_.KeyDown, keyState.Key(), keyState, mouseStateCur); +// if (keyState.Key().Eq(IptKey_.add_(IptKey_.F1))) { +// Tfds.Write(keyState.Key(), keyState.Key().Val()); +// } + sender.IptBnds().Process(iptData); + SendData(iptData); + keyHandled = keyState.Handled(); // WORKAROUND (WinForms): cache keyHandled b/c KeyDown.Handled=true does not make KeyPress.Handled=true; + } + public static void ExecKeyPress(GfuiElem sender, IptEvtDataKeyHeld keyPressState) { +// Tfds.Write(keyPressState.KeyChar()); + if (keyHandled) {keyPressState.Handled_set(true); return;} + IptEventData iptData = IptEventData.new_(sender, IptEventType_.KeyPress, IptKeyStrMgr.Instance.FetchByKeyPress((int)(byte)keyPressState.KeyChar()), keyStateCur, keyPressState, mouseStateCur); + sender.IptBnds().Process(iptData); + SendData(iptData); + } + public static void ExecKeyUp(GfuiElem sender, IptEvtDataKey keyState) { + keyStateCur = IptEvtDataKey.Null; // keyStateCur no longer needed; set to Null + if (keyHandled) {keyState.Handled_set(true); return;} + IptEventData iptData = IptEventData.new_(sender, IptEventType_.KeyUp, keyState.Key(), keyState, mouseStateCur); + sender.IptBnds().Process(iptData); + SendData(iptData); + } + public static void ExecMouseDown(GfuiElem sender, IptEvtDataMouse mouseState) { + mouseStateCur = mouseState; // cache for simultaneous ipt events (ex: key.ctrl + mouse.left) + if (sender.IptBnds().Has(IptEventType_.MousePress)) { + if (mousePressTimer == null) mousePressTimer = TimerAdp.new_(EventSink2, Tmr_cmd, 100, false); + senderCur = sender; mousePressTimer.Enabled_on(); + } + IptEventData iptData = IptEventData.new_(sender, IptEventType_.MouseDown, mouseState.Button(), keyStateCur, mouseState); + sender.IptBnds().Process(iptData); + SendData(iptData); + } + public static void ExecMouseMove(GfuiElem sender, IptEvtDataMouse mouseState) { + IptEventData iptData = IptEventData.new_(sender, IptEventType_.MouseMove, IptMouseMove.AnyDirection, keyStateCur, mouseState); + sender.IptBnds().Process(iptData); + // SendData(iptData); // TOMBSTONE: do not send mouseMove events for PERF and DESIGN reasons + } + public static void ExecMouseUp(GfuiElem sender, IptEvtDataMouse mouseState) { + mouseStateCur = IptEvtDataMouse.Null; // mouseStateCur no longer needed; set to Null + if (mousePressTimer != null) + mousePressTimer.Enabled_off(); + IptEventData iptData = IptEventData.new_(sender, IptEventType_.MouseUp, mouseState.Button(), keyStateCur, mouseState); + sender.IptBnds().Process(iptData); + SendData(iptData); + } + public static void ExecMouseWheel(GfuiElem sender, IptEvtDataMouse mouseState) { + IptEventData iptData = IptEventData.new_(sender, IptEventType_.MouseWheel, mouseState.Wheel(), keyStateCur, mouseState); + sender.IptBnds().Process(iptData); + SendData(iptData); + } + public static void ExecMousePress(GfuiElem sender, IptEvtDataMouse mouseState) { + IptEventData iptData = IptEventData.new_(sender, IptEventType_.MousePress, mouseState.Button(), keyStateCur, mouseState); + sender.IptBnds().Process(iptData); + SendData(iptData); + } + static void MousePressTick() { + IptEventMgr.ExecMousePress(senderCur, mouseStateCur); + } + static void SendData(IptEventData iptData) { + if (StopFwd(iptData)) return; + GfsCtx ctx = GfsCtx.new_(); + GfoMsg m = GfoMsg_.new_cast_(GfuiElemKeys.IptRcvd_evt).Add("iptData", iptData); + Gfo_evt_mgr_.Pub_msg(iptData.Sender(), ctx, GfuiElemKeys.IptRcvd_evt, m); + } + public static boolean StopFwd(IptEventData iptData) { // check if (a) control bubbles event; (b) iptData.Handled + return !IptEventType_.Has(iptData.Sender().IptBnds().EventsToFwd(), iptData.EventType()) + || iptData.Handled(); + } + static boolean keyHandled = false; static IptEvtDataKey keyStateCur = IptEvtDataKey.Null; static IptEvtDataMouse mouseStateCur = IptEvtDataMouse.Null; + static TimerAdp mousePressTimer; static GfuiElem senderCur; + public static final IptEventMgr EventSink2 = new IptEventMgr(); IptEventMgr() {} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Tmr_cmd)) MousePressTick(); + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String Tmr_cmd = "Tmr"; +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptEventType.java b/150_gfui/src/gplx/gfui/ipts/IptEventType.java index a27517de8..786c57dbf 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptEventType.java +++ b/150_gfui/src/gplx/gfui/ipts/IptEventType.java @@ -13,3 +13,22 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import gplx.gfui.controls.elems.*; +public class IptEventType { + public int Val() {return val;} int val; + public String Name() {return name;} private String name; + public IptEventType Add(IptEventType comp) {return IptEventType_.add_(this, comp);} + @Override public String toString() {return name;} + @gplx.Internal protected IptEventType(int v, String s) {this.val = v; this.name = s;} + public static Object HandleEvt(IptBndsOwner owner, GfsCtx ctx, GfoMsg m) { + IptEventData iptData = IptEventData.ctx_(ctx, m); + boolean processed = owner.IptBnds().Process(iptData); + if (processed || IptEventMgr.StopFwd(iptData)) { // NOTE: IptMsgs are single-dispatch; + } + else { + Gfo_evt_mgr_.Pub_msg(owner, ctx, GfuiElemKeys.IptRcvd_evt, m); + } + return Gfo_invk_.Rv_handled; + } +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptEventType_.java b/150_gfui/src/gplx/gfui/ipts/IptEventType_.java index a27517de8..70bd13cc8 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptEventType_.java +++ b/150_gfui/src/gplx/gfui/ipts/IptEventType_.java @@ -13,3 +13,44 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import gplx.core.bits.*; import gplx.core.primitives.*; +public class IptEventType_ { + static EnmMgr enmMgr = EnmMgr.new_().BitRngEnd_(128); + public static final IptEventType + None = new_( 0, "none") + , KeyDown = new_( 1, "keyDown") + , KeyUp = new_( 2, "keyUp") + , KeyPress = new_( 4, "keyPress") + , MouseDown = new_( 8, "mouseDown") + , MouseUp = new_( 16, "mouseUp") + , MouseMove = new_( 32, "mouseMove") + , MouseWheel = new_( 64, "mouseWheel") + , MousePress = new_( 128, "mousePress"); + public static IptEventType add_(IptEventType... ary) { + if (ary.length == 0) return IptEventType_.None; + int newVal = ary[0].Val(); + for (int i = 1; i < ary.length; i++) + newVal = Bitmask_.Flip_int(true, newVal, ary[i].Val()); + return getOrNew_(newVal); + } + static IptEventType getOrNew_(int v) { + IptEventType rv = (IptEventType)enmMgr.Get(v); + return (rv == null) ? new_(v, enmMgr.GetStr(v)) : rv; + } + static IptEventType new_(int val, String name) { + IptEventType rv = new IptEventType(val, name); + enmMgr.RegObj(val, name, rv); + return rv; + } + public static boolean Has(IptEventType val, IptEventType find) { + if (find == IptEventType_.None && val != IptEventType_.None) return false; // check .None manually b/c 0 is identity when BitShifting + return Bitmask_.Has_int(val.Val(), find.Val()); + } + public static IptEventType default_(IptArg[] args) { + IptEventType rv = IptEventType_.None; + for (IptArg arg : args) + rv = rv.Add(IptArg_.EventType_default(arg)); + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptEventType_tst.java b/150_gfui/src/gplx/gfui/ipts/IptEventType_tst.java index a27517de8..089f2b325 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptEventType_tst.java +++ b/150_gfui/src/gplx/gfui/ipts/IptEventType_tst.java @@ -13,3 +13,20 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import org.junit.*; import gplx.gfui.ipts.*; +public class IptEventType_tst { + @Test public void Has() { + tst_Has(IptEventType_.KeyDown, IptEventType_.KeyDown, true); + tst_Has(IptEventType_.KeyUp, IptEventType_.KeyDown, false); + tst_Has(IptEventType_.None, IptEventType_.KeyDown, false); + tst_Has(IptEventType_.KeyDown, IptEventType_.None, false); + tst_Has(IptEventType_.KeyDown.Add(IptEventType_.KeyUp), IptEventType_.KeyDown, true); + tst_Has(IptEventType_.MouseDown.Add(IptEventType_.MouseUp), IptEventType_.KeyDown, false); + tst_Has(IptEventType_.KeyDown.Add(IptEventType_.KeyUp), IptEventType_.None, false); + } void tst_Has(IptEventType val, IptEventType find, boolean expd) {Tfds.Eq(expd, IptEventType_.Has(val, find));} + @Test public void add_() { + tst_add(IptEventType_.KeyDown, IptEventType_.KeyDown, IptEventType_.KeyDown.Val()); + tst_add(IptEventType_.KeyDown, IptEventType_.KeyUp, IptEventType_.KeyDown.Val() + IptEventType_.KeyUp.Val()); + } void tst_add(IptEventType lhs, IptEventType rhs, int expd) {Tfds.Eq(expd, IptEventType_.add_(lhs, rhs).Val());} +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptEvtDataKey.java b/150_gfui/src/gplx/gfui/ipts/IptEvtDataKey.java index a27517de8..104fd2e58 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptEvtDataKey.java +++ b/150_gfui/src/gplx/gfui/ipts/IptEvtDataKey.java @@ -13,3 +13,22 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +public class IptEvtDataKey { + public IptKey Key() {return key;} IptKey key; + public boolean Handled() {return handled;} public void Handled_set(boolean v) {handled = v;} private boolean handled; + + public static IptEvtDataKey as_(Object obj) {return obj instanceof IptEvtDataKey ? (IptEvtDataKey)obj : null;} + public static IptEvtDataKey cast(Object obj) {try {return (IptEvtDataKey)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, IptEvtDataKey.class, obj);}} + public static final IptEvtDataKey Null = new_(IptKey_.None); + public static IptEvtDataKey test_(IptKey keyArg) {return new_(keyArg);} + public static IptEvtDataKey int_(int val) { + IptKey keyArg = IptKey_.api_(val); + return new_(keyArg); + } + public static IptEvtDataKey new_(IptKey key) { + IptEvtDataKey rv = new IptEvtDataKey(); + rv.key = key; + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptEvtDataKeyHeld.java b/150_gfui/src/gplx/gfui/ipts/IptEvtDataKeyHeld.java index a27517de8..9c8121e22 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptEvtDataKeyHeld.java +++ b/150_gfui/src/gplx/gfui/ipts/IptEvtDataKeyHeld.java @@ -13,3 +13,17 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +public class IptEvtDataKeyHeld { + public char KeyChar() {return c;} char c; + public boolean Handled() {return handled;} public void Handled_set(boolean v) {handled = v;} private boolean handled; + + public static IptEvtDataKeyHeld as_(Object obj) {return obj instanceof IptEvtDataKeyHeld ? (IptEvtDataKeyHeld)obj : null;} + public static IptEvtDataKeyHeld cast(Object obj) {try {return (IptEvtDataKeyHeld)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, IptEvtDataKeyHeld.class, obj);}} + public static final IptEvtDataKeyHeld Null = char_((char)0); + public static IptEvtDataKeyHeld char_(char c) { + IptEvtDataKeyHeld rv = new IptEvtDataKeyHeld(); + rv.c = c; + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptEvtDataMouse.java b/150_gfui/src/gplx/gfui/ipts/IptEvtDataMouse.java index a27517de8..0263dd7d8 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptEvtDataMouse.java +++ b/150_gfui/src/gplx/gfui/ipts/IptEvtDataMouse.java @@ -13,3 +13,20 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +public class IptEvtDataMouse { + public IptMouseBtn Button() {return button;} IptMouseBtn button; + public IptMouseWheel Wheel() {return wheel;} IptMouseWheel wheel; + public PointAdp Pos() {return location;} PointAdp location; + + public static IptEvtDataMouse as_(Object obj) {return obj instanceof IptEvtDataMouse ? (IptEvtDataMouse)obj : null;} + public static IptEvtDataMouse cast(Object obj) {try {return (IptEvtDataMouse)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, IptEvtDataMouse.class, obj);}} + public static final IptEvtDataMouse Null = IptEvtDataMouse.new_(IptMouseBtn_.None, IptMouseWheel_.None, 0, 0); + public static IptEvtDataMouse new_(IptMouseBtn button, IptMouseWheel wheel, int x, int y) { + IptEvtDataMouse rv = new IptEvtDataMouse(); + rv.button = button; + rv.wheel = wheel; + rv.location = PointAdp_.new_(x, y); + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptKey.java b/150_gfui/src/gplx/gfui/ipts/IptKey.java index a27517de8..7077ba378 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptKey.java +++ b/150_gfui/src/gplx/gfui/ipts/IptKey.java @@ -13,3 +13,16 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import gplx.core.bits.*; +public class IptKey implements IptArg { + @gplx.Internal protected IptKey(int val, String key) {this.val = val; this.key = key;} + public String Key() {return key;} private final String key; + public int Val() {return val;} private final int val; + public boolean Eq(IptArg comp) {return String_.Eq(key, comp.Key());} + public String XtoUiStr() {return IptKeyStrMgr.Instance.To_str(this);} + public IptKey Add(IptKey comp) {return IptKey_.add_(this, comp);} + public boolean Mod_shift() {return Bitmask_.Has_int(val, IptKey_.Shift.Val());} + public boolean Mod_ctrl() {return Bitmask_.Has_int(val, IptKey_.Ctrl.Val());} + public boolean Mod_alt() {return Bitmask_.Has_int(val, IptKey_.Alt.Val());} +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptKeyStrMgr.java b/150_gfui/src/gplx/gfui/ipts/IptKeyStrMgr.java index a27517de8..bbd03f73c 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptKeyStrMgr.java +++ b/150_gfui/src/gplx/gfui/ipts/IptKeyStrMgr.java @@ -13,3 +13,61 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +class IptKeyStrMgr { + public IptKey FetchByKeyPress(int charVal) { + if (literals == null) Init(); + IptKey rv = charKeys[charVal]; + return (rv == null) ? IptKey_.None : rv; + } + public String To_str(IptKey key) { + if (literals == null) Init(); + Object rv = literals.Get_by(key.Val()); + return rv == null ? String_.Empty : (String)rv; + } + public void XtoIptKeyAry(List_adp list) { + if (literals == null) Init(); + for (int i = 0; i < keys.Count(); i++) + list.Add((IptKey)keys.Get_at(i)); + } + void Init() {// default to US style keyboard + literals = Hash_adp_.New(); + charKeys = new IptKey[256]; + RegLtr(IptKey_.A, 'a'); RegLtr(IptKey_.B, 'b'); RegLtr(IptKey_.C, 'c'); RegLtr(IptKey_.D, 'd'); RegLtr(IptKey_.E, 'e'); + RegLtr(IptKey_.F, 'f'); RegLtr(IptKey_.G, 'g'); RegLtr(IptKey_.H, 'h'); RegLtr(IptKey_.I, 'i'); RegLtr(IptKey_.J, 'j'); + RegLtr(IptKey_.K, 'k'); RegLtr(IptKey_.L, 'l'); RegLtr(IptKey_.M, 'm'); RegLtr(IptKey_.N, 'n'); RegLtr(IptKey_.O, 'o'); + RegLtr(IptKey_.P, 'p'); RegLtr(IptKey_.Q, 'q'); RegLtr(IptKey_.R, 'r'); RegLtr(IptKey_.S, 's'); RegLtr(IptKey_.T, 't'); + RegLtr(IptKey_.U, 'u'); RegLtr(IptKey_.V, 'v'); RegLtr(IptKey_.W, 'w'); RegLtr(IptKey_.X, 'x'); RegLtr(IptKey_.Y, 'y'); RegLtr(IptKey_.Z, 'z'); + RegSym(IptKey_.D0, '0', ')'); RegSym(IptKey_.D1, '1', '!'); RegSym(IptKey_.D2, '2', '@'); RegSym(IptKey_.D3, '3', '#'); RegSym(IptKey_.D4, '4', '$'); + RegSym(IptKey_.D5, '5', '%'); RegSym(IptKey_.D6, '6', '^'); RegSym(IptKey_.D7, '7', '&'); RegSym(IptKey_.D8, '8', '*'); RegSym(IptKey_.D9, '9', '('); + RegSym(IptKey_.Equal, '=', '+'); RegSym(IptKey_.Minus, '-', '_'); RegSym(IptKey_.Backslash, '\\', '|'); RegSym(IptKey_.Semicolon, ';', ':'); + RegSym(IptKey_.Quote, '\'', '"'); RegSym(IptKey_.Comma, ',', '<'); RegSym(IptKey_.Period, '.', '>'); RegSym(IptKey_.Slash, '?', '/'); + RegSym(IptKey_.OpenBracket, '[', '{'); RegSym(IptKey_.CloseBracket, ']', '}'); RegSym(IptKey_.Tick, '`', '~'); + Reg(IptKey_.Space, ' '); + Reg(IptKey_.Escape, "", 27); // escape should be "" or else prints on grid + Reg(IptKey_.Enter, "\n", 10); + charKeys[13] = IptKey_.Enter; // WORKAROUND.WINFORMS: Enter generates keypress of 13 while Shift+Enter generates keypress of 10; JAVA always sends 10 + Reg(IptKey_.CapsLock, "CapsLock", 20); + } + void RegLtr(IptKey lowerKey, char lowerChr) { + IptKey upperKey = IptKey_.add_(lowerKey, IptKey_.Shift); + char upperChr = (char)((int)lowerChr - 32); + Reg(lowerKey, lowerChr); // 'a' 97 Key_.A + Reg(upperKey, upperChr); // 'A' 65 Key_.A+Key_.Shift + } + void RegSym(IptKey lowerKey, char lowerChr, char upperChr) { + IptKey upperKey = IptKey_.add_(lowerKey, IptKey_.Shift); + Reg(lowerKey, lowerChr); + Reg(upperKey, upperChr); + } + void Reg(IptKey k, char c) {Reg(k, Char_.To_str(c), (int)c);} + void Reg(IptKey k, String s, int charVal) { + int v = k.Val(); + literals.Add(v, s); + keys.Add(v, k); + charKeys[charVal] = k; + } + IptKey[] charKeys; + Hash_adp literals; Ordered_hash keys = Ordered_hash_.New(); + public static final IptKeyStrMgr Instance = new IptKeyStrMgr(); IptKeyStrMgr() {} +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptKeyStrMgr_tst.java b/150_gfui/src/gplx/gfui/ipts/IptKeyStrMgr_tst.java index a27517de8..f7628302b 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptKeyStrMgr_tst.java +++ b/150_gfui/src/gplx/gfui/ipts/IptKeyStrMgr_tst.java @@ -13,3 +13,45 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import org.junit.*; +public class IptKeyStrMgr_tst { + @Test public void KeyBasic() { + tst_XtoUiStr(IptKey_.A, "a"); + tst_XtoUiStr(IptKey_.Z, "z"); + tst_XtoUiStr(IptKey_.Shift.Add(IptKey_.A), "A"); + tst_XtoUiStr(IptKey_.Shift.Add(IptKey_.Z), "Z"); + tst_XtoUiStrShifted(IptKey_.Equal, "=", "+"); + tst_XtoUiStrShifted(IptKey_.D0, "0", ")"); + tst_XtoUiStrShifted(IptKey_.D1, "1", "!"); + tst_XtoUiStrShifted(IptKey_.D2, "2", "@"); + tst_XtoUiStrShifted(IptKey_.D3, "3", "#"); + tst_XtoUiStrShifted(IptKey_.D4, "4", "$"); + tst_XtoUiStrShifted(IptKey_.D5, "5", "%"); + tst_XtoUiStrShifted(IptKey_.D6, "6", "^"); + tst_XtoUiStrShifted(IptKey_.D7, "7", "&"); + tst_XtoUiStrShifted(IptKey_.D8, "8", "*"); + tst_XtoUiStrShifted(IptKey_.D9, "9", "("); + tst_XtoUiStrShifted(IptKey_.Minus, "-", "_"); + tst_XtoUiStrShifted(IptKey_.Backslash, "\\", "|"); + tst_XtoUiStrShifted(IptKey_.Semicolon, ";", ":"); + tst_XtoUiStrShifted(IptKey_.Quote, "'", "\""); + tst_XtoUiStrShifted(IptKey_.Comma, ",", "<"); + tst_XtoUiStrShifted(IptKey_.Period, ".", ">"); + tst_XtoUiStrShifted(IptKey_.Slash, "?", "/"); + tst_XtoUiStrShifted(IptKey_.Tick, "`", "~"); + tst_XtoUiStrShifted(IptKey_.OpenBracket, "[", "{"); + tst_XtoUiStrShifted(IptKey_.CloseBracket, "]", "}"); + } + @Test public void FetchByKeyPress() { + tst_FetchByKeyPress('a', IptKey_.add_(IptKey_.A)); + tst_FetchByKeyPress('A', IptKey_.add_(IptKey_.A, IptKey_.Shift)); + tst_FetchByKeyPress('1', IptKey_.add_(IptKey_.D1)); + tst_FetchByKeyPress('!', IptKey_.add_(IptKey_.D1, IptKey_.Shift)); + } void tst_FetchByKeyPress(char c, IptKey expd) {Tfds.Eq(expd.Key(), IptKeyStrMgr.Instance.FetchByKeyPress((int)c).Key());} + void tst_XtoUiStr(IptKey key, String expd) {Tfds.Eq(expd, key.XtoUiStr());} + void tst_XtoUiStrShifted(IptKey key, String expdNormal, String expdShifted) { + Tfds.Eq(expdNormal, key.XtoUiStr()); + Tfds.Eq(expdShifted, IptKey_.Shift.Add(key).XtoUiStr()); + } +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptKey_.java b/150_gfui/src/gplx/gfui/ipts/IptKey_.java index a27517de8..8f19571e5 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptKey_.java +++ b/150_gfui/src/gplx/gfui/ipts/IptKey_.java @@ -13,3 +13,227 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import java.awt.event.KeyEvent; +import gplx.core.primitives.*; import gplx.core.stores.*; import gplx.core.bits.*; +public class IptKey_ { + private static EnmMgr enm_mgr = EnmMgr.new_().BitRngBgn_(65536).BitRngEnd_(262144).Prefix_("key."); + public static IptKey[] Ary(IptKey... ary) {return ary;} + public static final IptKey[] Ary_empty = new IptKey[0]; + public static IptKey as_(Object obj) {return obj instanceof IptKey ? (IptKey)obj : null;} + public static IptKey cast(Object obj) {try {return (IptKey)obj;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, IptKey.class, obj);}} + public static IptKey add_(IptKey... ary) { + if (ary.length == 0) return IptKey_.None; + int newVal = ary[0].Val(); + for (int i = 1; i < ary.length; i++) + newVal = Bitmask_.Flip_int(true, newVal, ary[i].Val()); + return get_or_new_(newVal); + } + public static IptKey api_(int val) { + IptKey rv = (IptKey)enm_mgr.Get(val); + return (rv == null) ? new_(val, "key_" + Int_.To_str(val)) : rv; + } + public static IptKey parse(String raw) {return get_or_new_(enm_mgr.GetVal(raw));} + public static IptKey rdr_or_(DataRdr rdr, String key, IptKey or) { + String val = rdr.ReadStrOr(key, ""); // NOTE: "" cannot be null, b/c nullRdr returns String.empty + return (String_.Eq(val, "")) ? or : parse(val); + } + public static List_adp printableKeys2_(IptKey[] add, IptKey[] del) { + List_adp list = List_adp_.New(); + for (IptKey key : add) + list.Add(key); + + // add keypad numbers + list.Add_many(IptKey_.Numpad_0, IptKey_.Numpad_1, IptKey_.Numpad_2, IptKey_.Numpad_3, IptKey_.Numpad_4); + list.Add_many(IptKey_.Numpad_5, IptKey_.Numpad_6, IptKey_.Numpad_7, IptKey_.Numpad_8, IptKey_.Numpad_9); + + IptKeyStrMgr.Instance.XtoIptKeyAry(list); + for (IptKey key : del) + list.Del(key); + return list; + } + public static IptKey[] printableKeys_(IptKey[] add, IptKey[] del) { + List_adp list = List_adp_.New(); + + // add keypad numbers + list.Add_many(IptKey_.Numpad_0, IptKey_.Numpad_1, IptKey_.Numpad_2, IptKey_.Numpad_3, IptKey_.Numpad_4); + list.Add_many(IptKey_.Numpad_5, IptKey_.Numpad_6, IptKey_.Numpad_7, IptKey_.Numpad_8, IptKey_.Numpad_9); + + for (IptKey key : add) + list.Add(key); + IptKeyStrMgr.Instance.XtoIptKeyAry(list); + for (IptKey key : del) + list.Del(key); + return (IptKey[])list.To_ary(IptKey.class); + } + private static IptKey get_or_new_(int val) { + IptKey rv = (IptKey)enm_mgr.Get(val); + return (rv == null) ? new_(val, enm_mgr.GetStr(val)) : rv; + } + static IptKey new_(int val, String name) { + IptKey rv = new IptKey(val, String_.Has_at_bgn(name, "key.") ? name : "key." + name); + enm_mgr.RegObj(val, name, rv); + return rv; + } + public static final int KeyCode_Shift = 65536, KeyCode_Ctrl = 131072, KeyCode_Alt = 262144; + public static final IptKey + // NOTE: integer values represent .NET keycodes; NOTE: SWT keycodes are converted to SWING keycodes in Swt_core_lnrs + // none + None = new_( 0, "none") + + // numbers + , D0 = new_(48, "d0"), D1 = new_(49, "d1"), D2 = new_(50, "d2"), D3 = new_(51, "d3"), D4 = new_(52, "d4") + , D5 = new_(53, "d5"), D6 = new_(54, "d6"), D7 = new_(55, "d7"), D8 = new_(56, "d8"), D9 = new_(57, "d9") + + // letters + , A = new_(65, "a"), B = new_(66, "b"), C = new_(67, "c"), D = new_(68, "d"), E = new_(69, "e") + , F = new_(70, "f"), G = new_(71, "g"), H = new_(72, "h"), I = new_(73, "i"), J = new_(74, "j") + , K = new_(75, "k"), L = new_(76, "l"), M = new_(77, "m"), N = new_(78, "n"), O = new_(79, "o") + , P = new_(80, "p"), Q = new_(81, "q"), R = new_(82, "r"), S = new_(83, "s"), T = new_(84, "t") + , U = new_(85, "u"), V = new_(86, "v"), W = new_(87, "w"), X = new_(88, "x"), Y = new_(89, "y"), Z = new_(90, "z") + + // numpad numbers + , Numpad_0 = new_(KeyEvent.VK_NUMPAD0, "numpad_0") + , Numpad_1 = new_(KeyEvent.VK_NUMPAD1, "numpad_1") + , Numpad_2 = new_(KeyEvent.VK_NUMPAD2, "numpad_2") + , Numpad_3 = new_(KeyEvent.VK_NUMPAD3, "numpad_3") + , Numpad_4 = new_(KeyEvent.VK_NUMPAD4, "numpad_4") + , Numpad_5 = new_(KeyEvent.VK_NUMPAD5, "numpad_5") + , Numpad_6 = new_(KeyEvent.VK_NUMPAD6, "numpad_6") + , Numpad_7 = new_(KeyEvent.VK_NUMPAD7, "numpad_7") + , Numpad_8 = new_(KeyEvent.VK_NUMPAD8, "numpad_8") + , Numpad_9 = new_(KeyEvent.VK_NUMPAD9, "numpad_9") + + // numpad keys + , Numpad_multiply = new_(KeyEvent.VK_MULTIPLY, "numpad_multiply") + , Numpad_add = new_(KeyEvent.VK_ADD, "numpad_add") + , Numpad_subtract = new_(KeyEvent.VK_SUBTRACT, "numpad_subtract") + , Numpad_decimal = new_(KeyEvent.VK_DECIMAL, "numpad_decimal") + , Numpad_divide = new_(KeyEvent.VK_DIVIDE, "numpad_divide") + // NOTE: mapping numpad_enter to enter b/c don't want to define two sets of enters for every binding + //, Numpad_enter = new_(KeyEvent.VK_ENTER, "numpad_enter") + // NOTE: using same value as SWT; SWING value is not available + , Numpad_enter = new_(16777296, "numpad_enter") + + // function keys; not supporting f13-f24 b/c only for IBM 3270 keyboards and can't test; also, note that codes differ between .net and swing + , F1 = new_(KeyEvent.VK_F1, "f1") + , F2 = new_(KeyEvent.VK_F2, "f2") + , F3 = new_(KeyEvent.VK_F3, "f3") + , F4 = new_(KeyEvent.VK_F4, "f4") + , F5 = new_(KeyEvent.VK_F5, "f5") + , F6 = new_(KeyEvent.VK_F6, "f6") + , F7 = new_(KeyEvent.VK_F7, "f7") + , F8 = new_(KeyEvent.VK_F8, "f8") + , F9 = new_(KeyEvent.VK_F9, "f9") + , F10 = new_(KeyEvent.VK_F10, "f10") + , F11 = new_(KeyEvent.VK_F11, "f11") + , F12 = new_(KeyEvent.VK_F12, "f12") + + // whitespace + , Tab = new_(KeyEvent.VK_TAB, "tab") + , Enter = new_(KeyEvent.VK_ENTER, "enter") + , Space = new_(KeyEvent.VK_SPACE, "space") + + // delete + , Back = new_(KeyEvent.VK_BACK_SPACE, "back") + , Clear = new_(KeyEvent.VK_CLEAR, "clear") + , Insert = new_(KeyEvent.VK_INSERT, "insert") + , Delete = new_(KeyEvent.VK_DELETE, "delete") + + // meta + , Escape = new_(KeyEvent.VK_ESCAPE, "escape") + , Pause = new_(KeyEvent.VK_PAUSE, "pause") + , PrintScreen = new_(KeyEvent.VK_PRINTSCREEN, "printScreen") + + // navigation: page / home + , PageUp = new_(KeyEvent.VK_PAGE_UP, "pageUp") + , PageDown = new_(KeyEvent.VK_PAGE_DOWN, "pageDown") + , End = new_(KeyEvent.VK_END, "end") + , Home = new_(KeyEvent.VK_HOME, "home") + + // navigation: lr / ud + , Left = new_(KeyEvent.VK_LEFT, "left") + , Up = new_(KeyEvent.VK_UP, "up") + , Right = new_(KeyEvent.VK_RIGHT, "right") + , Down = new_(KeyEvent.VK_DOWN, "down") + + // locks + , CapsLock = new_(KeyEvent.VK_CAPS_LOCK, "capsLock") + , NumLock = new_(KeyEvent.VK_NUM_LOCK, "numLock") + , ScrollLock = new_(KeyEvent.VK_SCROLL_LOCK, "scrollLock") + + // symbols + , Semicolon = new_(KeyEvent.VK_SEMICOLON, "semicolon") + , Equal = new_(KeyEvent.VK_EQUALS, "equal") + , Comma = new_(KeyEvent.VK_COMMA, "comma") + , Minus = new_(KeyEvent.VK_MINUS, "minus") + , Period = new_(KeyEvent.VK_PERIOD, "period") + , Slash = new_(KeyEvent.VK_SLASH, "slash") + , Tick = new_(KeyEvent.VK_BACK_QUOTE, "tick") + , OpenBracket = new_(KeyEvent.VK_OPEN_BRACKET, "openBracket") + , Backslash = new_(KeyEvent.VK_BACK_SLASH, "backslash") + , CloseBracket = new_(KeyEvent.VK_CLOSE_BRACKET, "closeBracket") + , Quote = new_(222, "quote") + + // modifiers + , Shift = new_(KeyCode_Shift, "shift") + , Ctrl = new_(KeyCode_Ctrl, "ctrl") + , Alt = new_(KeyCode_Alt, "alt") + , ShiftKey = new_(16, "shiftKey") , CtrlKey = new_(17, "ctrlKey") , AltKey = new_(18, "altKey") // NOTE: used for .NET NPI + ; + private static Ordered_hash ui_str_hash; + public static Ordered_hash Ui_str_hash() { + if (ui_str_hash == null) { + ui_str_hash = Ordered_hash_.New(); + All_add(ui_str_hash + , IptKey_.Back, IptKey_.Tab, IptKey_.Clear, IptKey_.Enter + , IptKey_.Pause, IptKey_.CapsLock, IptKey_.Escape, IptKey_.Space + , IptKey_.PageUp, IptKey_.PageDown, IptKey_.End, IptKey_.Home + , IptKey_.Left, IptKey_.Up, IptKey_.Right, IptKey_.Down + , IptKey_.PrintScreen, IptKey_.Insert, IptKey_.Delete + , IptKey_.D0, IptKey_.D1, IptKey_.D2, IptKey_.D3, IptKey_.D4 + , IptKey_.D5, IptKey_.D6, IptKey_.D7, IptKey_.D8, IptKey_.D9 + , IptKey_.A, IptKey_.B, IptKey_.C, IptKey_.D, IptKey_.E + , IptKey_.F, IptKey_.G, IptKey_.H, IptKey_.I, IptKey_.J + , IptKey_.K, IptKey_.L, IptKey_.M, IptKey_.N, IptKey_.O + , IptKey_.P, IptKey_.Q, IptKey_.R, IptKey_.S, IptKey_.T + , IptKey_.U, IptKey_.V, IptKey_.W, IptKey_.X, IptKey_.Y + , IptKey_.Z + , IptKey_.F1, IptKey_.F2, IptKey_.F3, IptKey_.F4, IptKey_.F5, IptKey_.F6 + , IptKey_.F7, IptKey_.F8, IptKey_.F9, IptKey_.F10, IptKey_.F11, IptKey_.F12 + , IptKey_.NumLock, IptKey_.ScrollLock + , IptKey_.Semicolon, IptKey_.Equal, IptKey_.Comma, IptKey_.Minus, IptKey_.Period, IptKey_.Slash, IptKey_.Tick + , IptKey_.OpenBracket, IptKey_.Backslash, IptKey_.CloseBracket, IptKey_.Quote + , IptKey_.Numpad_0, IptKey_.Numpad_1, IptKey_.Numpad_2, IptKey_.Numpad_3, IptKey_.Numpad_4 + , IptKey_.Numpad_5, IptKey_.Numpad_6, IptKey_.Numpad_7, IptKey_.Numpad_8, IptKey_.Numpad_9 + , IptKey_.Numpad_multiply, IptKey_.Numpad_add, IptKey_.Numpad_subtract, IptKey_.Numpad_decimal, IptKey_.Numpad_divide + , IptKey_.Numpad_enter + ); + } + return ui_str_hash; + } + private static void All_add(Ordered_hash hash, IptKey... ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) { + IptKey key = ary[i]; + hash.Add_if_dupe_use_nth(Int_obj_ref.New(key.Val()), key); + } + } + public static String To_str(int orig_val) { + String mod_str = "", rv = ""; + int temp_val = orig_val; + boolean mod_c = Bitmask_.Has_int(temp_val, IptKey_.Ctrl.Val()); if (mod_c) {mod_str += "c"; temp_val = Bitmask_.Flip_int(Bool_.N, temp_val, IptKey_.Ctrl.Val());} + boolean mod_a = Bitmask_.Has_int(temp_val, IptKey_.Alt.Val()); if (mod_a) {mod_str += "a"; temp_val = Bitmask_.Flip_int(Bool_.N, temp_val, IptKey_.Alt.Val());} + boolean mod_s = Bitmask_.Has_int(temp_val, IptKey_.Shift.Val()); if (mod_s) {mod_str += "s"; temp_val = Bitmask_.Flip_int(Bool_.N, temp_val, IptKey_.Shift.Val());} + if (String_.Len_gt_0(mod_str)) { + rv = "mod." + mod_str; + // handle modifiers only, like "mod.cs"; else will be "mod.cs+key.#0" + if (temp_val == 0) return rv; + rv += "+"; + } + IptKey key = (IptKey)IptKey_.Ui_str_hash().Get_by(Int_obj_ref.New(temp_val)); + String key_str = key == null ? "key.#" + Int_.To_str(temp_val) : key.Key(); + // Tfds.Write(rv + key_str, orig_val, temp_val, mod_c, mod_a, mod_s); + return rv + key_str; + } +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptKey__tst.java b/150_gfui/src/gplx/gfui/ipts/IptKey__tst.java index a27517de8..c447f9491 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptKey__tst.java +++ b/150_gfui/src/gplx/gfui/ipts/IptKey__tst.java @@ -13,3 +13,25 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +import org.junit.*; +public class IptKey__tst { + private final IptKey__fxt fxt = new IptKey__fxt(); + @Test public void To_str() { + fxt.Test_to_str(196608, "mod.cs"); + } + @Test public void To_str__numeric() { + fxt.Test_to_str(12345, "key.#12345"); + } + @Test public void Parse() { + fxt.Test_parse("key.#10", 10); + } +} +class IptKey__fxt { + public void Test_to_str(int keycode, String expd) { + Tfds.Eq(expd, IptKey_.To_str(keycode)); + } + public void Test_parse(String raw, int keycode) { + Tfds.Eq(keycode, IptKey_.parse(raw).Val()); + } +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptMouseBtn.java b/150_gfui/src/gplx/gfui/ipts/IptMouseBtn.java index a27517de8..7f3b99a06 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptMouseBtn.java +++ b/150_gfui/src/gplx/gfui/ipts/IptMouseBtn.java @@ -13,3 +13,10 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +public class IptMouseBtn implements IptArg { + public IptMouseBtn(int val, String key) {this.val = val; this.key = key;} + public String Key() {return key;} private String key; + public int Val() {return val;} int val; + public boolean Eq(IptArg comp) {return String_.Eq(key, comp.Key());} +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptMouseBtn_.java b/150_gfui/src/gplx/gfui/ipts/IptMouseBtn_.java index a27517de8..63a5e3212 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptMouseBtn_.java +++ b/150_gfui/src/gplx/gfui/ipts/IptMouseBtn_.java @@ -13,3 +13,40 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +public class IptMouseBtn_ { + public static final int + Tid_none = 0x00000000 + , Tid_left = 0x00100000 + , Tid_right = 0x00200000 + , Tid_middle = 0x00400000 + , Tid_x1 = 0x00400000 + , Tid_x2 = 0x01000000 + ; + public static final IptMouseBtn // REF: System.Windows.Forms.MouseButtons + None = new IptMouseBtn(Tid_none , "mouse.none") + , Left = new IptMouseBtn(Tid_left , "mouse.left") + , Right = new IptMouseBtn(Tid_right , "mouse.right") + , Middle = new IptMouseBtn(Tid_middle , "mouse.middle") + , X1 = new IptMouseBtn(Tid_x1 , "mouse.x1") + , X2 = new IptMouseBtn(Tid_x2 , "mouse.x2") + ; + public static IptMouseBtn parse(String raw) { + if (String_.Eq(raw, None.Key())) return None; + else if (String_.Eq(raw, Left.Key())) return Left; + else if (String_.Eq(raw, Right.Key())) return Right; + else if (String_.Eq(raw, Middle.Key())) return Middle; + else if (String_.Eq(raw, X1.Key())) return X1; + else if (String_.Eq(raw, X2.Key())) return X2; + else throw Err_.new_parse_type(IptMouseBtn.class, raw); + } + public static IptMouseBtn api_(int val) { + if (val == None.Val()) return None; + else if (val == Left.Val()) return Left; + else if (val == Right.Val()) return Right; + else if (val == Middle.Val()) return Middle; + else if (val == X1.Val()) return X1; + else if (val == X2.Val()) return X2; + else throw Err_.new_unhandled(val); + } +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptMouseMove.java b/150_gfui/src/gplx/gfui/ipts/IptMouseMove.java index a27517de8..fce76518f 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptMouseMove.java +++ b/150_gfui/src/gplx/gfui/ipts/IptMouseMove.java @@ -13,3 +13,9 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +public class IptMouseMove implements IptArg { + public String Key() {return key;} private String key = "move.any"; + public boolean Eq(IptArg comp) {return String_.Eq(this.Key(), comp.Key());} + public static final IptMouseMove AnyDirection = new IptMouseMove(); IptMouseMove() {} +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptMouseWheel.java b/150_gfui/src/gplx/gfui/ipts/IptMouseWheel.java index a27517de8..2ea6a8349 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptMouseWheel.java +++ b/150_gfui/src/gplx/gfui/ipts/IptMouseWheel.java @@ -13,3 +13,9 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +public class IptMouseWheel implements IptArg { + public String Key() {return key;} private String key; + public boolean Eq(IptArg comp) {return String_.Eq(key, comp.Key());} + public IptMouseWheel(String key) {this.key = key;} +} diff --git a/150_gfui/src/gplx/gfui/ipts/IptMouseWheel_.java b/150_gfui/src/gplx/gfui/ipts/IptMouseWheel_.java index a27517de8..1d4943d26 100644 --- a/150_gfui/src/gplx/gfui/ipts/IptMouseWheel_.java +++ b/150_gfui/src/gplx/gfui/ipts/IptMouseWheel_.java @@ -13,3 +13,22 @@ 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.gfui.ipts; import gplx.*; import gplx.gfui.*; +public class IptMouseWheel_ { + public static final IptMouseWheel + None = new IptMouseWheel("wheel.none") + , Up = new IptMouseWheel("wheel.up") + , Down = new IptMouseWheel("wheel.down"); + public static IptMouseWheel parse(String raw) { + if (String_.Eq(raw, None.Key())) return None; + else if (String_.Eq(raw, Up.Key())) return Up; + else if (String_.Eq(raw, Down.Key())) return Down; + else throw Err_.new_parse_type(IptMouseWheel.class, raw); + } + public static IptMouseWheel api_(Object obj) { + int delta = Int_.Cast(obj); + if (delta > 0) return Up; + else if (delta < 0) return Down; + else return None; + } +} diff --git a/150_gfui/src/gplx/gfui/kits/core/GfoFactory_gfui.java b/150_gfui/src/gplx/gfui/kits/core/GfoFactory_gfui.java index a27517de8..3bde18bb9 100644 --- a/150_gfui/src/gplx/gfui/kits/core/GfoFactory_gfui.java +++ b/150_gfui/src/gplx/gfui/kits/core/GfoFactory_gfui.java @@ -13,3 +13,26 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.controls.windows.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; import gplx.gfui.controls.customs.*; +public class GfoFactory_gfui { + public static void Btn_MinWin(GfuiElem owner, GfoMsg appWinMsg) { + GfuiBtn_.msg_("minWin", owner, GfoMsg_.chain_(appWinMsg, GfuiWin.Invk_Minimize)).Text_("_").TipText_("minmize window").Width_(20); + } + public static void Btn_MinWin2(GfuiElem owner) { + GfuiBtn_.msg_("minWin", owner, GfoMsg_.root_(".", GfuiElemBase.Invk_OwnerWin_cmd, GfuiWin.Invk_Minimize)).Text_("_").TipText_("minmize window").Width_(20); + } + public static void Btn_MoveBox(GfuiElem owner, GfuiElem target) { + GfuiElem rv = GfuiBtn_.new_("moveBox").Owner_(owner).Text_("*").TipText_("move box").Width_(20); + GfuiMoveElemBnd bnd = GfuiMoveElemBnd.new_(); + bnd.TargetElem_set(target); + rv.Inject_(bnd); + } + public static GfuiBtn Btn_QuitWin3(GfuiElem owner) { + return (GfuiBtn)GfuiBtn_.msg_("quitWin", owner, GfoMsg_.root_(".", GfuiElemBase.Invk_OwnerWin_cmd, GfuiWin.Invk_Quit)).Text_("X").TipText_("quit win").Width_(20); + } + public static void Btn_QuitWin2(GfuiElem owner, GfoMsg quitMsg) { + GfuiBtn_.msg_("quitWin", owner, quitMsg).Text_("X").TipText_("quit win").Width_(20); + } + public static final GfoFactory_gfui Instance = new GfoFactory_gfui(); GfoFactory_gfui() {} +} diff --git a/150_gfui/src/gplx/gfui/kits/core/GfsLibIni_gfui.java b/150_gfui/src/gplx/gfui/kits/core/GfsLibIni_gfui.java index a27517de8..1f9e9c65c 100644 --- a/150_gfui/src/gplx/gfui/kits/core/GfsLibIni_gfui.java +++ b/150_gfui/src/gplx/gfui/kits/core/GfsLibIni_gfui.java @@ -13,3 +13,12 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.langs.gfs.*; +import gplx.gfui.ipts.*; +public class GfsLibIni_gfui implements GfsLibIni { + public void Ini(GfsCore core) { + core.AddCmd(IptCfgRegy.Instance, "IptBndMgr_"); + } + public static final GfsLibIni_gfui Instance = new GfsLibIni_gfui(); GfsLibIni_gfui() {} +} diff --git a/150_gfui/src/gplx/gfui/kits/core/GfuiEnv_.java b/150_gfui/src/gplx/gfui/kits/core/GfuiEnv_.java index a27517de8..a3bccd97d 100644 --- a/150_gfui/src/gplx/gfui/kits/core/GfuiEnv_.java +++ b/150_gfui/src/gplx/gfui/kits/core/GfuiEnv_.java @@ -13,3 +13,99 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.draws.*; import gplx.gfui.ipts.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.windows.*; +import gplx.gfml.*; import gplx.langs.gfs.*; import gplx.core.envs.*; +import gplx.core.envs.Env_; +import gplx.core.envs.Op_sys; +import gplx.core.envs.Process_adp; +import gplx.core.threads.*; +import java.awt.AWTKeyStroke; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.KeyboardFocusManager; +import java.awt.event.KeyEvent; +import java.awt.image.BufferedImage; +import java.util.HashSet; +import java.util.Set; +import javax.swing.JOptionPane; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +public class GfuiEnv_ { + static FontAdp system_font; + public static void Exit() {if (!Env_.Mode_testing()) System.exit(0);} + public static void Init(String[] args, String appNameAndExt, Class type) {Init(args, appNameAndExt, type, true);} + public static void Init(String[] args, String appNameAndExt, Class type, boolean swingHack) { + Env_.Init(args, appNameAndExt, type); + if (swingHack) { // TODO_OLD: move to kit dependent functionality; WHEN: swing kit + if (Op_sys.Cur().Tid_is_wnt()) { + try {UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");} + catch (ClassNotFoundException e) {e.printStackTrace();} + catch (InstantiationException e) {e.printStackTrace();} + catch (IllegalAccessException e) {e.printStackTrace();} + catch (UnsupportedLookAndFeelException e) {e.printStackTrace();} + } + Set fwdSet = new HashSet(); fwdSet.add(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)); + Set bwdSet = new HashSet(); bwdSet.add(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, java.awt.event.InputEvent.SHIFT_DOWN_MASK )); + KeyboardFocusManager.getCurrentKeyboardFocusManager().setDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, fwdSet); + KeyboardFocusManager.getCurrentKeyboardFocusManager().setDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, bwdSet); + } + if (!Op_sys.Cur().Tid_is_drd()) + GxwElemFactory_.winForms_(); + + // reg interruptLnr + if (swingHack) { // TODO_OLD: move to kit dependent functionality; WHEN: swing kit + UsrDlg_.Instance.Reg(UsrMsgWkr_.Type_Warn, GfoConsoleWin.Instance); + UsrDlg_.Instance.Reg(UsrMsgWkr_.Type_Stop, GfuiInterruptLnr.new_()); + } + IptBndMgr_win = IptCfg_.new_("gplx.gfui.GfuiWin"); + + // alias default dirs + Io_mgr.Instance.AliasDir_sysEngine("app:\\", Env_.AppUrl().OwnerDir().Raw()); + + GfsCore.Instance.MsgParser_(GfoMsgParser_gfml.Instance); + GfsCore.Instance.AddLib(GfsLibIni_core.Instance); + GfsCore.Instance.AddLib(GfsLibIni_gfui.Instance); + Io_url iniFile = Env_.AppUrl().GenSubFil(".gfs"); + if (Io_mgr.Instance.ExistsFil(iniFile)) + GfsCore.Instance.ExecFile(iniFile); + } + public static void Init_swt(String[] args, Class type) { + Env_.Init_swt(args, type); + if (!Op_sys.Cur().Tid_is_drd()) GxwElemFactory_.winForms_(); + GfsCore.Instance.MsgParser_(GfoMsgParser_gfml.Instance); + } + public static void Gfs_init() {GfsCore.Instance.MsgParser_(GfoMsgParser_gfml.Instance);} + public static IptCfg IptBndMgr_win; + public static void DoEvents() {;} + public static void ShowMsg(String message) {javax.swing.JOptionPane.showMessageDialog(null, message, "", javax.swing.JOptionPane.INFORMATION_MESSAGE, null);} + public static void BringToFront(Process_adp process) {} + public static void DoEvents(int milliseconds) { + Thread_adp_.Sleep(milliseconds); + } + public static void Run(GfuiWin form) {javax.swing.SwingUtilities.invokeLater(new GfuiFormRunner(form));} + public static FontAdp System_font() { + try { + if (system_font == null) { + Font label_font = (Font)UIManager.get("Label.font"); + system_font = FontAdp.new_(label_font.getFamily(), label_font.getSize(), FontStyleAdp_.Plain); + } + return system_font; + } catch (Exception e) {return FontAdp.new_("Arial", 8, FontStyleAdp_.Plain);} + } + public static final String Quit_commit_evt = "quit_commit_evt", Quit_notify_evt = "quit_notify_evt"; + public static final String Err_GfuiException = "gplx.dbs.GfuiException"; // TODO_OLD: used in JAVA. move +} +class GfuiInterruptLnr implements UsrMsgWkr { + public void ExecUsrMsg(int type, UsrMsg umsg) {GfuiEnv_.ShowMsg(umsg.To_str());} + public static GfuiInterruptLnr new_() {return new GfuiInterruptLnr();} GfuiInterruptLnr() {} +} +class GfuiFormRunner implements Runnable { + public GfuiFormRunner(GfuiWin form) {this.form = form;} GfuiWin form; + public void run() { + form.Show(); + } +} +//#} \ No newline at end of file diff --git a/150_gfui/src/gplx/gfui/kits/core/GfuiInvkCmd.java b/150_gfui/src/gplx/gfui/kits/core/GfuiInvkCmd.java index a27517de8..24f5c95f7 100644 --- a/150_gfui/src/gplx/gfui/kits/core/GfuiInvkCmd.java +++ b/150_gfui/src/gplx/gfui/kits/core/GfuiInvkCmd.java @@ -13,3 +13,6 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +public interface GfuiInvkCmd extends Gfo_invk, Rls_able { +} diff --git a/150_gfui/src/gplx/gfui/kits/core/GfuiInvkCmd_.java b/150_gfui/src/gplx/gfui/kits/core/GfuiInvkCmd_.java index a27517de8..6e72dd0a3 100644 --- a/150_gfui/src/gplx/gfui/kits/core/GfuiInvkCmd_.java +++ b/150_gfui/src/gplx/gfui/kits/core/GfuiInvkCmd_.java @@ -13,3 +13,7 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +public class GfuiInvkCmd_ { + public static final String Invk_sync = "Sync"; +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Gfui_clipboard.java b/150_gfui/src/gplx/gfui/kits/core/Gfui_clipboard.java index a27517de8..8acf50851 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Gfui_clipboard.java +++ b/150_gfui/src/gplx/gfui/kits/core/Gfui_clipboard.java @@ -13,3 +13,12 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +public interface Gfui_clipboard extends Gfo_invk, Rls_able { + void Copy(String s); +} +class Gfui_clipboard_null implements Gfui_clipboard { + public void Copy(String s) {} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return this;} + public void Rls() {} +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Gfui_clipboard_.java b/150_gfui/src/gplx/gfui/kits/core/Gfui_clipboard_.java index a27517de8..0b51cfe26 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Gfui_clipboard_.java +++ b/150_gfui/src/gplx/gfui/kits/core/Gfui_clipboard_.java @@ -13,3 +13,8 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +public class Gfui_clipboard_ { + public static final Gfui_clipboard Null = new Gfui_clipboard_null(); + public static final String Invk_copy = "copy", Invk_select_all = "select_all"; +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_dir.java b/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_dir.java index a27517de8..c6ccf162f 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_dir.java +++ b/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_dir.java @@ -13,3 +13,10 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +public interface Gfui_dlg_dir { + Gfui_dlg_dir Init_msg_(String v); + Gfui_dlg_dir Init_text_(String v); + Gfui_dlg_dir Init_dir_(Io_url v); + String Ask(); +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_dir_.java b/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_dir_.java index a27517de8..d6a99b0bd 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_dir_.java +++ b/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_dir_.java @@ -13,3 +13,13 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +public class Gfui_dlg_dir_ { + public static final Gfui_dlg_dir Noop = new Gfui_dlg_dir__noop(); +} +class Gfui_dlg_dir__noop implements Gfui_dlg_dir { + public String Ask() {return "";} + public Gfui_dlg_dir Init_msg_(String v) {return this;} + public Gfui_dlg_dir Init_text_(String v) {return this;} + public Gfui_dlg_dir Init_dir_(Io_url v) {return this;} +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_file.java b/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_file.java index a27517de8..96f06343b 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_file.java +++ b/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_file.java @@ -13,3 +13,11 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +public interface Gfui_dlg_file { + Gfui_dlg_file Init_msg_(String v); + Gfui_dlg_file Init_file_(String v); + Gfui_dlg_file Init_dir_(Io_url v); + Gfui_dlg_file Init_exts_(String... v); + String Ask(); +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_file_.java b/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_file_.java index a27517de8..a99e78d3d 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_file_.java +++ b/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_file_.java @@ -13,3 +13,14 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +public class Gfui_dlg_file_ { + public static final Gfui_dlg_file Noop = new Gfui_dlg_file_noop(); +} +class Gfui_dlg_file_noop implements Gfui_dlg_file { + public Gfui_dlg_file Init_msg_(String v) {return this;} + public Gfui_dlg_file Init_file_(String v) {return this;} + public Gfui_dlg_file Init_dir_(Io_url v) {return this;} + public Gfui_dlg_file Init_exts_(String... v) {return this;} + public String Ask() {return "";} +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_msg.java b/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_msg.java index a27517de8..127152f57 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_msg.java +++ b/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_msg.java @@ -13,3 +13,11 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +public interface Gfui_dlg_msg { + Gfui_dlg_msg Init_msg_(String v); + Gfui_dlg_msg Init_ico_(int v); + Gfui_dlg_msg Init_btns_(int... ary); + int Ask(); + boolean Ask(int expd); +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_msg_.java b/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_msg_.java index a27517de8..5f7f92216 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_msg_.java +++ b/150_gfui/src/gplx/gfui/kits/core/Gfui_dlg_msg_.java @@ -13,3 +13,16 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +public class Gfui_dlg_msg_ { + public static final Gfui_dlg_msg Noop = new Gfui_dlg_msg_noop(); + public static final int Ico_error = 0, Ico_information = 1, Ico_question = 2, Ico_warning = 3, Ico_working = 4; + public static final int Btn_ok = 0, Btn_cancel = 1, Btn_yes = 2, Btn_no = 3, Retry = 4, Btn_abort = 5, Btn_ignore = 6; +} +class Gfui_dlg_msg_noop implements Gfui_dlg_msg { + public Gfui_dlg_msg Init_msg_(String v) {return this;} + public Gfui_dlg_msg Init_ico_(int v) {return this;} + public Gfui_dlg_msg Init_btns_(int... ary) {return this;} + public boolean Ask(int expd) {return false;} + public int Ask() {return Int_.Min_value;} +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Gfui_kit.java b/150_gfui/src/gplx/gfui/kits/core/Gfui_kit.java index a27517de8..d5eff8398 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Gfui_kit.java +++ b/150_gfui/src/gplx/gfui/kits/core/Gfui_kit.java @@ -13,3 +13,39 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.imgs.*; import gplx.gfui.controls.windows.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; +public interface Gfui_kit extends Gfo_invk { + byte Tid(); + String Key(); + void Cfg_set(String type, String key, Object val); + boolean Kit_mode__ready(); + void Kit_init(Gfo_usr_dlg gui_wtr); + void Kit_run(); + void Kit_term(); + void Kit_term_cbk_(Gfo_invk_cmd cmd); + Gfui_clipboard Clipboard(); + void Ask_ok(String grp_key, String msg_key, String fmt, Object... args); + boolean Ask_yes_no(String grp_key, String msg_key, String fmt, Object... args); + boolean Ask_ok_cancel(String grp_key, String msg_key, String fmt, Object... args); + int Ask_yes_no_cancel(String grp_key, String msg_key, String fmt, Object... args); + GfuiInvkCmd New_cmd_sync(Gfo_invk invk); + GfuiInvkCmd New_cmd_async(Gfo_invk invk); + GfuiWin New_win_app(String key, Keyval... args); + GfuiWin New_win_utl(String key, GfuiWin owner, Keyval... args); + Gfui_html New_html(String key, GfuiElem owner, Keyval... args); + Gfui_tab_mgr New_tab_mgr(String key, GfuiElem owner, Keyval... args); + Gfui_grp New_grp(String key, GfuiElem owner, Keyval... args); + GfuiTextBox New_text_box(String key, GfuiElem owner, Keyval... args); + GfuiBtn New_btn(String key, GfuiElem owner, Keyval... args); + GfuiComboBox New_combo(String key, GfuiElem owner, Keyval... args); + GfuiLbl New_lbl(String key, GfuiElem owner, Keyval... args); + Gfui_dlg_file New_dlg_file(byte type, String msg); + Gfui_dlg_msg New_dlg_msg(String msg); + ImageAdp New_img_load(Io_url path); + Object New_color(int a, int r, int g, int b); + Gfui_mnu_grp New_mnu_popup(String key, GfuiElem owner); + Gfui_mnu_grp New_mnu_bar(String key, GfuiWin owner); + void Set_mnu_popup(GfuiElem owner, Gfui_mnu_grp grp); + float Calc_font_height(GfuiElem elem, String s); +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Gfui_kit_.java b/150_gfui/src/gplx/gfui/kits/core/Gfui_kit_.java index a27517de8..e9b4a04f0 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Gfui_kit_.java +++ b/150_gfui/src/gplx/gfui/kits/core/Gfui_kit_.java @@ -13,3 +13,18 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +public class Gfui_kit_ { + public static final byte Mem_tid = 0, Swing_tid = 1, Swt_tid = 2, Android_tid = 3; + public static Gfui_kit Mem() {return mem_kit;} private static final Gfui_kit mem_kit = Mem_kit.Instance; + public static Gfui_kit Swt() {if (swt_kit == null) swt_kit = Swt_kit.Instance; return swt_kit;} private static Gfui_kit swt_kit; // NOTE: late-binding else swing apps will fail (since swt jar is not deployed) + public static Gfui_kit Swing() {if (swing_kit == null) swing_kit = Swing_kit.Instance; return swing_kit;} private static Gfui_kit swing_kit; + public static Gfui_kit Get_by_key(String key) { + if (String_.Eq(key, Mem().Key())) return Mem(); + else if (String_.Eq(key, Swt().Key())) return Swt(); + else if (String_.Eq(key, Swing().Key())) return Swing(); + else throw Err_.new_unhandled(key); + } + public static final String Cfg_HtmlBox = "HtmlBox"; + public static final byte File_dlg_type_open = 0, File_dlg_type_save = 1; +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Gfui_kit_base.java b/150_gfui/src/gplx/gfui/kits/core/Gfui_kit_base.java index a27517de8..99687d193 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Gfui_kit_base.java +++ b/150_gfui/src/gplx/gfui/kits/core/Gfui_kit_base.java @@ -13,3 +13,108 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.imgs.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; import gplx.gfui.controls.customs.*; import gplx.gfui.controls.windows.*; +public abstract class Gfui_kit_base implements Gfui_kit { + private Keyval_hash ctor_args = new Keyval_hash(); + public abstract byte Tid(); + public abstract String Key(); + public abstract GxwElemFactory_base Factory(); + public GfuiWin Main_win() {return main_win;} public Gfui_kit Main_win_(GfuiWin v) {main_win = v; return this;} private GfuiWin main_win; + public Gfui_clipboard Clipboard() {return Gfui_clipboard_.Null;} + public Gfo_invk_cmd Kit_term_cbk() {return kit_term_cbk;} public void Kit_term_cbk_(Gfo_invk_cmd v) {kit_term_cbk = v;} private Gfo_invk_cmd kit_term_cbk; + public void Cfg_set(String type, String key, Object val) {} + public boolean Kit_mode__ready() {return true;} + public void Kit_init(Gfo_usr_dlg gui_wtr) {} + @gplx.Virtual public void Kit_run() {} + @gplx.Virtual public void Kit_term() {kit_term_cbk.Exec();} + @gplx.Virtual public void Ask_ok(String grp_key, String msg_key, String fmt, Object... args) {} + public boolean Ask_yes_no(String grp_key, String msg_key, String fmt, Object... args) {return false;} + public int Ask_yes_no_cancel(String grp_key, String msg_key, String fmt, Object... args) {return Gfui_dlg_msg_.Btn_cancel;} + public boolean Ask_ok_cancel(String grp_key, String msg_key, String fmt, Object... args) {return false;} + public void Btn_img_(GfuiBtn btn, IconAdp v) {} + public GfuiInvkCmd New_cmd_sync(Gfo_invk invk) {return new Gfui_kit_cmd_sync(invk);} + public GfuiInvkCmd New_cmd_async(Gfo_invk invk) {return new Gfui_kit_cmd_async(invk);} + public GfuiWin New_win_app(String key, Keyval... args) { + GfuiWin rv = GfuiWin_.kit_(this, key, this.Factory().win_app_(), ctor_args); + main_win = rv; + return rv; + } + public GfuiWin New_win_utl(String key, GfuiWin owner, Keyval... args) {return GfuiWin_.kit_(this, key, this.Factory().win_tool_(ctor_args), ctor_args);} + @gplx.Virtual public Gfui_html New_html(String key, GfuiElem owner, Keyval... args) { + Gfui_html rv = Gfui_html.kit_(this, key, this.New_html_impl(), ctor_args); + owner.SubElems().Add(rv); + return rv; + } + public Gfui_tab_mgr New_tab_mgr(String key, GfuiElem owner, Keyval... args) { + Gfui_tab_mgr rv = Gfui_tab_mgr.kit_(this, key, this.New_tab_mgr_impl(), ctor_args); + owner.SubElems().Add(rv); + return rv; + } + public Gfui_tab_itm New_tab_itm(String key, Gfui_tab_mgr owner, Keyval... args) { + Gfui_tab_itm rv = Gfui_tab_itm.kit_(this, key, this.New_tab_itm_impl(), ctor_args); + owner.SubElems().Add(rv); + return rv; + } + public Gfui_grp New_grp(String key, GfuiElem owner, Keyval... args) { + Gfui_grp rv = Gfui_grp.kit_(this, key, this.New_grp_impl(), ctor_args); + owner.SubElems().Add(rv); + return rv; + } + public GfuiTextBox New_text_box(String key, GfuiElem owner, Keyval... args) { + GfuiTextBox rv = GfuiTextBox_.kit_(this, key, this.Factory().text_fld_(), ctor_args); + owner.SubElems().Add(rv); + return rv; + } + @gplx.Virtual public GfuiBtn New_btn(String key, GfuiElem owner, Keyval... args) { + GfuiBtn rv = GfuiBtn_.kit_(this, key, New_btn_impl(), ctor_args); + owner.SubElems().Add(rv); + return rv; + } + @gplx.Virtual public GfuiComboBox New_combo(String key, GfuiElem owner, Keyval... args) { + GfuiComboBox rv = GfuiComboBox.kit_(this, key, New_combo_impl(), ctor_args); + owner.SubElems().Add(rv); + return rv; + } + @gplx.Virtual public GfuiLbl New_lbl(String key, GfuiElem owner, Keyval... args) { + GfuiLbl rv = GfuiLbl_.kit_(this, key, New_btn_impl(), ctor_args); + owner.SubElems().Add(rv); + return rv; + } + @gplx.Virtual public GfuiStatusBox New_status_box(String key, GfuiElem owner, Keyval... args) { + GfuiStatusBox rv = GfuiStatusBox_.kit_(this, key, this.Factory().text_memo_()); + owner.SubElems().Add(rv); + return rv; + } + public void Set_mnu_popup(GfuiElem owner, Gfui_mnu_grp grp) {} + protected abstract Gxw_html New_html_impl(); + protected abstract Gxw_tab_mgr New_tab_mgr_impl(); + protected abstract Gxw_tab_itm New_tab_itm_impl(); + protected abstract GxwElem New_grp_impl(); + protected abstract GxwElem New_btn_impl(); + protected abstract GxwElem New_combo_impl(); + @gplx.Virtual public Gfui_dlg_file New_dlg_file(byte type, String msg) {return Gfui_dlg_file_.Noop;} + @gplx.Virtual public Gfui_dlg_msg New_dlg_msg(String msg) {return Gfui_dlg_msg_.Noop;} + @gplx.Virtual public Gfui_mnu_grp New_mnu_popup(String key, GfuiElem owner) {return Gfui_mnu_grp_.Noop;} + @gplx.Virtual public Gfui_mnu_grp New_mnu_bar(String key, GfuiWin owner) {return Gfui_mnu_grp_.Noop;} + public abstract ImageAdp New_img_load(Io_url url); + public Object New_color(int a, int r, int g, int b) {return null;} + public float Calc_font_height(GfuiElem elem, String s) {return 13;} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + return this; + } +} +class Gfui_kit_cmd_sync implements GfuiInvkCmd { + public Gfui_kit_cmd_sync(Gfo_invk target) {this.target = target;} private Gfo_invk target; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + return target.Invk(ctx, ikey, k, m); + } + public void Rls() {target = null;} +} +class Gfui_kit_cmd_async implements GfuiInvkCmd { + public Gfui_kit_cmd_async(Gfo_invk target) {this.target = target;} private Gfo_invk target; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + return target.Invk(ctx, ikey, k, m); + } + public void Rls() {target = null;} +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Gfui_mnu_grp.java b/150_gfui/src/gplx/gfui/kits/core/Gfui_mnu_grp.java index a27517de8..8fd5bc64e 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Gfui_mnu_grp.java +++ b/150_gfui/src/gplx/gfui/kits/core/Gfui_mnu_grp.java @@ -13,3 +13,16 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.imgs.*; +public interface Gfui_mnu_grp extends Gfui_mnu_itm { + String Root_key(); + void Itms_clear(); + boolean Disposed(); + Gfui_mnu_itm Itms_add_btn_cmd (String txt, ImageAdp img, Gfo_invk invk, String invk_cmd); + Gfui_mnu_itm Itms_add_btn_msg (String txt, ImageAdp img, Gfo_invk invk, Gfo_invk_root_wkr root_wkr, GfoMsg msg); + Gfui_mnu_itm Itms_add_chk_msg (String txt, ImageAdp img, Gfo_invk invk, Gfo_invk_root_wkr root_wkr, GfoMsg msg_n, GfoMsg msg_y); + Gfui_mnu_itm Itms_add_rdo_msg (String txt, ImageAdp img, Gfo_invk invk, Gfo_invk_root_wkr root_wkr, GfoMsg msg); + Gfui_mnu_grp Itms_add_grp (String txt, ImageAdp img); + Gfui_mnu_itm Itms_add_separator(); +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Gfui_mnu_grp_.java b/150_gfui/src/gplx/gfui/kits/core/Gfui_mnu_grp_.java index a27517de8..b723887bf 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Gfui_mnu_grp_.java +++ b/150_gfui/src/gplx/gfui/kits/core/Gfui_mnu_grp_.java @@ -13,3 +13,26 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.imgs.*; +public class Gfui_mnu_grp_ { + public static final Gfui_mnu_grp Noop = new Gfui_mnu_grp_noop(); +} +class Gfui_mnu_grp_noop implements Gfui_mnu_grp { + public String Uid() {return "";} + public int Tid() {return Gfui_mnu_itm_.Tid_grp;} + public boolean Enabled() {return true;} public void Enabled_(boolean v) {} + public boolean Disposed() {return false;} + public String Text() {return null;} public void Text_(String v) {} + public ImageAdp Img() {return null;} public void Img_(ImageAdp v) {} + public boolean Selected() {return true;} public void Selected_(boolean v) {} + public String Root_key() {return "null";} + public Object Under() {return null;} + public void Itms_clear() {} + public Gfui_mnu_itm Itms_add_btn_cmd (String txt, ImageAdp img, Gfo_invk invk, String invk_cmd) {return Gfui_mnu_itm_null.Null;} + public Gfui_mnu_itm Itms_add_btn_msg (String txt, ImageAdp img, Gfo_invk invk, Gfo_invk_root_wkr root_wkr, GfoMsg invk_msg) {return Gfui_mnu_itm_null.Null;} + public Gfui_mnu_itm Itms_add_chk_msg (String txt, ImageAdp img, Gfo_invk invk, Gfo_invk_root_wkr root_wkr, GfoMsg msg_n, GfoMsg msg_y) {return Gfui_mnu_itm_null.Null;} + public Gfui_mnu_itm Itms_add_rdo_msg (String txt, ImageAdp img, Gfo_invk invk, Gfo_invk_root_wkr root_wkr, GfoMsg msg) {return Gfui_mnu_itm_null.Null;} + public Gfui_mnu_grp Itms_add_grp(String txt, ImageAdp img) {return Gfui_mnu_grp_.Noop;} + public Gfui_mnu_itm Itms_add_separator() {return Gfui_mnu_itm_null.Null;} +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Gfui_mnu_itm.java b/150_gfui/src/gplx/gfui/kits/core/Gfui_mnu_itm.java index a27517de8..f7f18205e 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Gfui_mnu_itm.java +++ b/150_gfui/src/gplx/gfui/kits/core/Gfui_mnu_itm.java @@ -13,3 +13,24 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.imgs.*; +public interface Gfui_mnu_itm { + int Tid(); + String Uid(); + boolean Enabled(); void Enabled_(boolean v); + String Text(); void Text_(String v); + ImageAdp Img(); void Img_(ImageAdp v); + boolean Selected(); void Selected_(boolean v); + Object Under(); +} +class Gfui_mnu_itm_null implements Gfui_mnu_itm { + public String Uid() {return "";} + public int Tid() {return Gfui_mnu_itm_.Tid_btn;} + public boolean Enabled() {return true;} public void Enabled_(boolean v) {} + public String Text() {return text;} public void Text_(String v) {text = v;} private String text; + public ImageAdp Img() {return img;} public void Img_(ImageAdp v) {img = v;} private ImageAdp img; + public boolean Selected() {return true;} public void Selected_(boolean v) {} + public Object Under() {return null;} + public static final Gfui_mnu_itm_null Null = new Gfui_mnu_itm_null(); Gfui_mnu_itm_null() {} +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Gfui_mnu_itm_.java b/150_gfui/src/gplx/gfui/kits/core/Gfui_mnu_itm_.java index a27517de8..78d747dd4 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Gfui_mnu_itm_.java +++ b/150_gfui/src/gplx/gfui/kits/core/Gfui_mnu_itm_.java @@ -13,3 +13,8 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +public class Gfui_mnu_itm_ { + public static String Gen_uid() {return "mnu_" + Int_.To_str(++uid_next);} private static int uid_next = 0; + public static final int Tid_nil = 0, Tid_grp = 1, Tid_spr = 2, Tid_btn = 3, Tid_chk = 4, Tid_rdo = 5; +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Mem_html.java b/150_gfui/src/gplx/gfui/kits/core/Mem_html.java index a27517de8..ac8b505b3 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Mem_html.java +++ b/150_gfui/src/gplx/gfui/kits/core/Mem_html.java @@ -13,3 +13,57 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.draws.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; +class Mem_html extends GxwTextMemo_lang implements Gxw_html { public void Html_invk_src_(Gfo_evt_itm v) {} + public void Html_doc_html_load_by_mem(String s) { +// this.Core().ForeColor_set(plainText ? ColorAdp_.Black : ColorAdp_.Gray); + s = String_.Replace(s, "\r", ""); + s = String_.Replace(s, "\n", "\r\n"); + this.TextVal_set(s); + this.SelBgn_set(0); + html_doc_html_load_tid = Gxw_html_load_tid_.Tid_mem; + } + public void Html_doc_html_load_by_url(Io_url path, String html) { + html_doc_html_load_tid = Gxw_html_load_tid_.Tid_url; + } + public byte Html_doc_html_load_tid() {return html_doc_html_load_tid;} private byte html_doc_html_load_tid; + public void Html_doc_html_load_tid_(byte v) {html_doc_html_load_tid = v;} + public String Html_js_eval_script(String script) {return "";} + public Object Html_js_eval_script_as_obj(String script) {return "";} + public String Html_js_send_json(String name, String data) {return "";} + String ExtractAtr(String key, String txt, int pos) { + int key_pos = String_.FindBwd(txt, key, pos); if (key_pos == String_.Find_none) return null; + int q0 = String_.FindFwd(txt, "\"", key_pos); if (q0 == String_.Find_none) return null; + int q1 = String_.FindFwd(txt, "\"", q0 + 1); if (q1 == String_.Find_none) return null; + if (!Int_.Between(pos, q0, q1)) return null; // current pos is not between nearest quotes + return String_.Mid(txt, q0 + 1, q1); + } + public void Html_js_enabled_(boolean v) {} + public String Html_js_eval_proc_as_str(String proc, Object... args) {return "not implemented by mem_html";} + public boolean Html_js_eval_proc_as_bool(String proc, Object... args) {return false;} + public void Html_js_cbks_add(String js_func_name, Gfo_invk invk) {} + public void Html_dispose() {} + public Mem_html() { + this.ctor_MsTextBoxMultiline_(); + } +} +class Mem_tab_mgr extends GxwElem_mock_base implements Gxw_tab_mgr { public ColorAdp Btns_selected_background() {return btns_selected_background;} public void Btns_selected_background_(ColorAdp v) {btns_selected_background = v;} private ColorAdp btns_selected_background; + public ColorAdp Btns_selected_foreground() {return btns_selected_foreground;} public void Btns_selected_foreground_(ColorAdp v) {btns_selected_foreground = v;} private ColorAdp btns_selected_foreground; + public ColorAdp Btns_unselected_background() {return btns_unselected_background;} public void Btns_unselected_background_(ColorAdp v) {btns_unselected_background = v;} private ColorAdp btns_unselected_background; + public ColorAdp Btns_unselected_foreground() {return btns_unselected_foreground;} public void Btns_unselected_foreground_(ColorAdp v) {btns_unselected_foreground = v;} private ColorAdp btns_unselected_foreground; + public Gxw_tab_itm Tabs_add(Gfui_tab_itm_data tab_data) {return new Mem_tab_itm();} + public void Tabs_select_by_idx(int i) {} + public void Tabs_close_by_idx(int i) {} + public void Tabs_switch(int src, int trg) {} + public int Btns_height() {return 0;} public void Btns_height_(int v) {} + public boolean Btns_place_on_top() {return false;} public void Btns_place_on_top_(boolean v) {} + public boolean Btns_curved() {return false;} public void Btns_curved_(boolean v) {} + public boolean Btns_close_visible() {return false;} public void Btns_close_visible_(boolean v) {} + public boolean Btns_unselected_close_visible() {return false;} public void Btns_unselected_close_visible_(boolean v) {} +} +class Mem_tab_itm extends GxwElem_mock_base implements Gxw_tab_itm { public void Subs_add(GfuiElem sub) {} + public Gfui_tab_itm_data Tab_data() {return tab_data;} private Gfui_tab_itm_data tab_data = new Gfui_tab_itm_data("null", -1); + public String Tab_name() {return tab_name;} public void Tab_name_(String v) {tab_name = v;} private String tab_name; + public String Tab_tip_text() {return tab_tip_text;} public void Tab_tip_text_(String v) {tab_tip_text = v;} private String tab_tip_text; +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Mem_kit.java b/150_gfui/src/gplx/gfui/kits/core/Mem_kit.java index a27517de8..92dca7459 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Mem_kit.java +++ b/150_gfui/src/gplx/gfui/kits/core/Mem_kit.java @@ -13,3 +13,27 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.imgs.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; +public class Mem_kit extends Gfui_kit_base { + @Override public byte Tid() {return Gfui_kit_.Mem_tid;} + @Override public String Key() {return "mem";} + @Override public GxwElemFactory_base Factory() {return factory;} private GxwElemFactory_cls_mock factory = new GxwElemFactory_cls_mock(); + public void New_html_impl_prototype_(Gxw_html v) {html_impl_prototype = v;} private Gxw_html html_impl_prototype; + @Override public Gfui_html New_html(String key, GfuiElem owner, Keyval... args) { + if (html_impl_prototype == null) + return super.New_html(key, owner, args); + else { + Gfui_html rv = Gfui_html.mem_(key, html_impl_prototype); + return rv; + } + } + @Override protected Gxw_html New_html_impl() {return html_impl_prototype == null ? new Mem_html(): html_impl_prototype;} + @Override protected Gxw_tab_mgr New_tab_mgr_impl() {return new Mem_tab_mgr();} + @Override protected Gxw_tab_itm New_tab_itm_impl() {return new Mem_tab_itm();} + @Override protected GxwElem New_grp_impl() {return factory.control_();} + @Override protected GxwElem New_btn_impl() {return factory.control_();} + @Override protected GxwElem New_combo_impl() {return factory.comboBox_();} + @Override public ImageAdp New_img_load(Io_url url) {return ImageAdp_null.Instance;} + public static final Mem_kit Instance = new Mem_kit(); Mem_kit() {} +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Swing_kit.java b/150_gfui/src/gplx/gfui/kits/core/Swing_kit.java index a27517de8..2715446a9 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Swing_kit.java +++ b/150_gfui/src/gplx/gfui/kits/core/Swing_kit.java @@ -13,3 +13,23 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.imgs.*; import gplx.gfui.controls.gxws.*; +import gplx.core.brys.fmtrs.*; +public class Swing_kit extends Gfui_kit_base { + private Bry_fmtr ask_fmtr = Bry_fmtr.new_(); private Bry_bfr ask_bfr = Bry_bfr_.New(); + @Override public byte Tid() {return Gfui_kit_.Swing_tid;} + @Override public String Key() {return "swing";} + @Override public GxwElemFactory_base Factory() {return factory;} private GxwElemFactory_cls_lang factory = new GxwElemFactory_cls_lang(); + @Override public void Ask_ok(String grp_key, String msg_key, String fmt, Object... args) {GfuiEnv_.ShowMsg(ask_fmtr.Bld_str_many(ask_bfr, fmt, args));} + @Override public void Kit_run() {GfuiEnv_.Run(this.Main_win());} + @Override public void Kit_term() {this.Kit_term_cbk().Exec(); GfuiEnv_.Exit();} + @Override public ImageAdp New_img_load(Io_url url) {return ImageAdp_.file_(url);} + @Override protected Gxw_html New_html_impl() {return new Mem_html();} + @Override protected Gxw_tab_mgr New_tab_mgr_impl() {return new Mem_tab_mgr();} + @Override protected Gxw_tab_itm New_tab_itm_impl() {return new Mem_tab_itm();} + @Override protected GxwElem New_grp_impl() {return factory.control_();} + @Override protected GxwElem New_btn_impl() {return factory.control_();} + @Override protected GxwElem New_combo_impl() {return factory.control_();} + public static final Swing_kit Instance = new Swing_kit(); Swing_kit() {} +} diff --git a/150_gfui/src/gplx/gfui/kits/core/Swt_kit.java b/150_gfui/src/gplx/gfui/kits/core/Swt_kit.java index a27517de8..ef1fac8bd 100644 --- a/150_gfui/src/gplx/gfui/kits/core/Swt_kit.java +++ b/150_gfui/src/gplx/gfui/kits/core/Swt_kit.java @@ -13,3 +13,372 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.core.brys.fmtrs.*; +import gplx.gfui.imgs.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; import gplx.gfui.controls.customs.*; import gplx.gfui.controls.windows.*; +import gplx.gfui.kits.swts.*; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.MessageBox; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.ToolItem; +import org.eclipse.swt.browser.Browser; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Button; + +import gplx.core.threads.*; +import gplx.gfui.controls.customs.GfuiStatusBox; +import gplx.gfui.controls.customs.GfuiStatusBox_; +import gplx.gfui.controls.elems.GfuiElem; +import gplx.gfui.controls.gxws.GxwCore_base; +import gplx.gfui.controls.gxws.GxwTextFld; +import gplx.gfui.controls.standards.*; +import gplx.gfui.controls.windows.GfoConsoleWin; +import gplx.gfui.controls.windows.GfuiWin; +import gplx.gfui.controls.windows.GfuiWin_; +import gplx.gfui.draws.*; +import gplx.gfui.imgs.*; +public class Swt_kit implements Gfui_kit { + private final Keyval_hash ctor_args = new Keyval_hash(); private final Keyval_hash ctor_args_null = new Keyval_hash(); + private final Hash_adp kit_args = Hash_adp_.New(); private Swt_msg_wkr_stop msg_wkr_stop; + private Gfo_usr_dlg gui_wtr; private String xul_runner_path = null; + private final Bry_fmtr ask_fmtr = Bry_fmtr.new_().Fail_when_invalid_escapes_(false); private final Bry_bfr ask_bfr = Bry_bfr_.New(); + private final Object thread_lock = new Object(); + private Cursor hand_cursor; + public byte Tid() {return Gfui_kit_.Swt_tid;} + public String Key() {return "swt";} + public Display Swt_display() {return display;} private Display display; + public Shell Swt_shell() {return shell;} private Shell shell; + public Gfui_clipboard Clipboard() {return clipboard;} private Swt_clipboard clipboard; + public int Kit_mode() {synchronized (thread_lock) {return mode;}} private int mode = Swt_kit_mode.Tid_ctor; + public void Kit_mode_(int v) {synchronized (thread_lock) {mode = v;}} + public boolean Kit_mode__ready() {return Kit_mode() == Swt_kit_mode.Tid_ready;} + public boolean Kit_mode__term() {return Kit_mode() == Swt_kit_mode.Tid_term;} + public boolean Kit_sync_cmd_exists() {synchronized (thread_lock) {return sync_cmd_list.Count() != 0;}} private final List_adp sync_cmd_list = List_adp_.New(); + public void Kit_sync_cmd_add(Swt_gui_cmd cmd) {synchronized (thread_lock) {sync_cmd_list.Add(cmd);}} + public void Kit_sync_cmd_del(Swt_gui_cmd cmd) {synchronized (thread_lock) {sync_cmd_list.Del(cmd);}} + public Gfo_invk_cmd Kit_term_cbk() {return term_cbk;} public void Kit_term_cbk_(Gfo_invk_cmd v) {this.term_cbk = v;} private Gfo_invk_cmd term_cbk = Gfo_invk_cmd.Noop; + public void Kit_init(Gfo_usr_dlg gui_wtr) { + this.gui_wtr = gui_wtr; + this.msg_wkr_stop = new Swt_msg_wkr_stop(this, gui_wtr); + this.display = new Display(); + this.clipboard = new Swt_clipboard(display); + this.hand_cursor = new Cursor(display, SWT.CURSOR_HAND); + UsrDlg_.Instance.Reg(UsrMsgWkr_.Type_Warn, GfoConsoleWin.Instance); + UsrDlg_.Instance.Reg(UsrMsgWkr_.Type_Stop, msg_wkr_stop); + if (xul_runner_path != null) System.setProperty("org.eclipse.swt.browser.XULRunnerPath", xul_runner_path); + this.Kit_mode_(Swt_kit_mode.Tid_ready); + gui_wtr.Log_many("", "", "swt.kit.init.done"); + } + public void Kit_run() { + shell.addListener(SWT.Close, new Swt_shell_close_lnr(this, gui_wtr)); + shell.open(); + Cursor cursor = new Cursor(display, SWT.CURSOR_ARROW); + shell.setCursor(cursor); // set cursor to hand else cursor defaults to Hourglass until mouse is moved; DATE: 2014-01-31 + boolean first = true; + while (!shell.isDisposed()) { + if (first) { + first = false; +// shell.setMinimized(true); + shell.setActive(); + shell.forceFocus(); + } + if (!display.readAndDispatch()) + display.sleep(); + } + gui_wtr.Log_many("", "", "swt.kit.term:bgn"); + cursor.dispose(); gui_wtr.Log_many("", "", "swt.kit.term:cursor"); + } + public void Kit_term() { + clipboard.Rls(); gui_wtr.Log_many("", "", "swt.kit.term:clipboard"); + shell.close(); + } + public void Cfg_set(String type, String key, Object val) { + // XulRunnerPath gets set immediately; do not add to widget_cfg_hash + if (String_.Eq(type, Gfui_kit_.Cfg_HtmlBox) && String_.Eq(key, "XulRunnerPath")) { + this.xul_runner_path = (String)val; + return; + } + // add kv to widget_cfg_hash; new controls will get properties from cfg_hash + Keyval_hash widget_cfg_hash = (Keyval_hash)kit_args.Get_by(type); + if (widget_cfg_hash == null) { + widget_cfg_hash = new Keyval_hash(); + kit_args.Add(type, widget_cfg_hash); + } + widget_cfg_hash.Add_if_dupe_use_nth(key, val); + } + public boolean Ask_yes_no(String grp_key, String msg_key, String fmt, Object... args) { + Swt_dlg_msg dlg = (Swt_dlg_msg)New_dlg_msg(ask_fmtr.Bld_str_many(ask_bfr, fmt, args)).Init_btns_(Gfui_dlg_msg_.Btn_yes, Gfui_dlg_msg_.Btn_no).Init_ico_(Gfui_dlg_msg_.Ico_question); + display.syncExec(dlg); + return dlg.Ask_rslt == Gfui_dlg_msg_.Btn_yes; + } + public boolean Ask_ok_cancel(String grp_key, String msg_key, String fmt, Object... args) { + Swt_dlg_msg dlg = (Swt_dlg_msg)New_dlg_msg(ask_fmtr.Bld_str_many(ask_bfr, fmt, args)).Init_btns_(Gfui_dlg_msg_.Btn_ok, Gfui_dlg_msg_.Btn_cancel).Init_ico_(Gfui_dlg_msg_.Ico_question); + display.syncExec(dlg); + return dlg.Ask_rslt == Gfui_dlg_msg_.Btn_ok; + } + public int Ask_yes_no_cancel(String grp_key, String msg_key, String fmt, Object... args) { + Swt_dlg_msg dlg = (Swt_dlg_msg)New_dlg_msg(ask_fmtr.Bld_str_many(ask_bfr, fmt, args)).Init_btns_(Gfui_dlg_msg_.Btn_yes, Gfui_dlg_msg_.Btn_no, Gfui_dlg_msg_.Btn_cancel).Init_ico_(Gfui_dlg_msg_.Ico_question); + display.syncExec(dlg); + return dlg.Ask_rslt; + } + public void Ask_ok(String grp_key, String msg_key, String fmt, Object... args) { + Swt_dlg_msg dlg = (Swt_dlg_msg)New_dlg_msg(ask_fmtr.Bld_str_many(ask_bfr, fmt, args)).Init_btns_(Gfui_dlg_msg_.Btn_ok).Init_ico_(Gfui_dlg_msg_.Ico_information); + display.syncExec(dlg); + } + public GfuiInvkCmd New_cmd_sync (Gfo_invk invk) {return new Swt_gui_cmd(this, gui_wtr, display, invk, Bool_.N);} + public GfuiInvkCmd New_cmd_async(Gfo_invk invk) {return new Swt_gui_cmd(this, gui_wtr, display, invk, Bool_.Y);} + public GfuiWin New_win_utl(String key, GfuiWin owner, Keyval... args) { + return GfuiWin_.kit_(this, key, new Swt_win(shell), ctor_args_null); + } + public GfuiWin New_win_app(String key, Keyval... args) { + Swt_win win = new Swt_win(display); + this.shell = win.UnderShell(); + shell.setLayout(null); + return GfuiWin_.kit_(this, key, win, ctor_args_null); + } + public GfuiBtn New_btn(String key, GfuiElem owner, Keyval... args) { + ctor_args.Add("cursor", hand_cursor); + GfuiBtn rv = GfuiBtn_.kit_(this, key, new Swt_btn_no_border(Swt_control_.cast_or_fail(owner), ctor_args), ctor_args); + ctor_args.Del("cursor"); + owner.SubElems().Add(rv); + return rv; + } + public GfuiLbl New_lbl(String key, GfuiElem owner, Keyval... args) { + GfuiLbl rv = GfuiLbl_.kit_(this, key, new Swt_lbl(Swt_control_.cast_or_fail(owner), ctor_args), ctor_args); + owner.SubElems().Add(rv); + return rv; + } + public Gfui_html New_html(String key, GfuiElem owner, Keyval... args) { + ctor_args.Clear(); + // check cfg for browser type + Keyval_hash html_cfg_args = (Keyval_hash)kit_args.Get_by(Gfui_kit_.Cfg_HtmlBox); + if (html_cfg_args != null) { + Keyval browser_type = html_cfg_args.Get_kvp_or_null(Cfg_Html_BrowserType); + if (browser_type != null) ctor_args.Add(browser_type); + } + Swt_html swt_html = new Swt_html(this, Swt_control_.cast_or_fail(owner), ctor_args); + Gfui_html gfui_html = Gfui_html.kit_(this, key, swt_html, ctor_args); + swt_html.Under_control().addMenuDetectListener(new Swt_lnr__menu_detect(this, gfui_html)); + gfui_html.Owner_(owner); + swt_html.Delete_elems_(owner, gfui_html); + swt_html.Evt_mgr_(gfui_html.Evt_mgr()); + return gfui_html; + } + public Gfui_tab_mgr New_tab_mgr(String key, GfuiElem owner, Keyval... args) { + ctor_args.Clear(); + Swt_tab_mgr rv_swt = new Swt_tab_mgr(this, Swt_control_.cast_or_fail(owner), ctor_args); + Gfui_tab_mgr rv = Gfui_tab_mgr.kit_(this, key, rv_swt, ctor_args); + rv.Owner_(owner); + rv_swt.Evt_mgr_(rv.Evt_mgr()); + return rv; + } + public GfuiTextBox New_text_box(String key, GfuiElem owner, Keyval... args) { + ctor_args.Clear(); + int args_len = args.length; + for (int i = 0; i < args_len; i++) + ctor_args.Add(args[i]); + boolean border_on = Bool_.Cast(ctor_args.Get_val_or(GfuiTextBox.CFG_border_on_, true)); + GxwTextFld under = new Swt_text_w_border(this, Swt_control_.cast_or_fail(owner), New_color(border_on ? ColorAdp_.LightGray : ColorAdp_.White), ctor_args); + GfuiTextBox rv = GfuiTextBox_.kit_(this, key, under, ctor_args); + rv.Owner_(owner); + ctor_args.Clear(); + return rv; + } + public Gfui_grp New_grp(String key, GfuiElem owner, Keyval... args) { + ctor_args.Clear(); + Swt_grp rv_swt = new Swt_grp(this, Swt_control_.cast_or_fail(owner), ctor_args); + Gfui_grp rv = Gfui_grp.kit_(this, key, rv_swt, ctor_args); + rv.Owner_(owner); + rv_swt.Evt_mgr_(rv.Evt_mgr()); + return rv; + } + public GfuiComboBox New_combo(String key, GfuiElem owner, Keyval... args) { + Swt_combo_ctrl rv_swt = new Swt_combo_ctrl(this, Swt_control_.cast_or_fail(owner), this.New_color(ColorAdp_.LightGray), ctor_args); + GfuiComboBox rv = GfuiComboBox.kit_(this, key, rv_swt, ctor_args); + rv.Owner_(owner); + rv_swt.Evt_mgr_(rv.Evt_mgr()); + return rv; + } + public GfuiStatusBox New_status_box(String key, GfuiElem owner, Keyval... args) { + ctor_args.Clear(); + GfuiStatusBox rv = GfuiStatusBox_.kit_(this, key, new Swt_text(Swt_control_.cast_or_fail(owner), ctor_args)); + rv.Owner_(owner); + return rv; + } + public Gfui_dlg_file New_dlg_file(byte type, String msg) {return New_dlg_file(type, msg, null);} + public Gfui_dlg_file New_dlg_file(byte type, String msg, String exts) { + Gfui_dlg_file rv = new Swt_dlg_file(type, shell); + if (exts != null) rv.Init_exts_(exts); + rv.Init_msg_(msg); + return rv; + } + public Gfui_dlg_dir New_dlg_dir(String msg) {return new Swt_dlg_dir(shell).Init_msg_(msg);} + public Gfui_dlg_msg New_dlg_msg(String msg) {return new Swt_dlg_msg(shell).Init_msg_(msg);} + public ImageAdp New_img_load(Io_url url) { + if (url == Io_url_.Empty) return ImageAdp_.Null; + if (!Io_mgr.Instance.Exists(url)) return ImageAdp_.Null; // must check if exists or fatal error; DATE:2017-06-02 + Image img = new Image(display, url.Raw()); + Rectangle rect = img.getBounds(); + return new Swt_img(this, img, rect.width, rect.height).Url_(url); + } + public Color New_color(ColorAdp v) {return (Color)New_color(v.Alpha(), v.Red(), v.Green(), v.Blue());} + public Object New_color(int a, int r, int g, int b) {return new Color(display, r, g, b);} + public Gfui_mnu_grp New_mnu_popup(String key, GfuiElem owner) {return Swt_popup_grp.new_popup(key, owner);} + public Gfui_mnu_grp New_mnu_bar(String key, GfuiWin owner) {return Swt_popup_grp.new_bar(key, owner);} + public float Calc_font_height(GfuiElem elem, String s) { + if (String_.Len_eq_0(s)) return 8; + try { + String old_text = elem.Text(); + elem.Text_(s); + float rv = ((Swt_text_w_border)(elem.UnderElem())).Under_text().getFont().getFontData()[0].height; + elem.Text_(old_text); // was shell.setText(old_text); DATE:2014-07-25 + return rv; + } + catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "error while calculating font height; err=~{0}", Err_.Message_gplx_full(e)); + return 8; + } + } + public void Set_mnu_popup(GfuiElem owner, Gfui_mnu_grp grp) { + Control control = Swt_control_.cast_or_fail(owner).Under_menu_control(); + Swt_popup_grp popup = (Swt_popup_grp)grp; + control.setMenu(popup.Under_menu()); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (String_.Eq(k, Invk_Cfg_add)) { + String type = m.ReadStrOr("type", ""); + String key = m.ReadStrOr("key", ""); + String val = m.ReadStrOr("val", ""); + if (ctx.Deny()) return this; + if (String_.Eq(type, Gfui_kit_.Cfg_HtmlBox)) { + if (String_.Eq(key, "XulRunnerPath")) + xul_runner_path = val; + else if (String_.Eq(key, Swt_kit.Cfg_Html_BrowserType)) + Cfg_set(type, Swt_kit.Cfg_Html_BrowserType, Cfg_Html_BrowserType_parse(val)); + } + } + else if (String_.Eq(k, Invk_ask_file)) { + String exts = ""; + + // note that Dashboard/Offline does not specify exts + if (m.Args_count() > 1) + exts = m.Args_getAt(1).Val_to_str_or_empty(); + return this.New_dlg_file(Gfui_kit_.File_dlg_type_open, m.Args_getAt(0).Val_to_str_or_empty(), exts).Ask(); + } + else if (String_.Eq(k, "ask_dir")) return this.New_dlg_dir(m.Args_getAt(0).Val_to_str_or_empty()).Ask(); + else if (String_.Eq(k, Invk_shell_close)) shell.close(); + return this; + } + public static final String Invk_Cfg_add = "Cfg_add", Invk_ask_file = "ask_file"; // private or public? + public static final String Invk_shell_close = "shell_close"; // public + public static final Swt_kit Instance = new Swt_kit(); private Swt_kit() {} // singleton b/c of following line "In particular, some platforms which SWT supports will not allow more than one active display" (http://help.eclipse.org/indigo/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/widgets/Display.html) + public static final String Cfg_Html_BrowserType = "BrowserType"; + public static int Cfg_Html_BrowserType_parse(String v) { + if (String_.Eq(v, "mozilla")) return Swt_html.Browser_tid_mozilla; + else if (String_.Eq(v, "webkit")) return Swt_html.Browser_tid_webkit; + else return Swt_html.Browser_tid_none; + } +} +class Swt_shell_close_lnr implements Listener, Gfo_invk { + private final Swt_kit kit; private final Gfo_usr_dlg usr_dlg; + public Swt_shell_close_lnr(Swt_kit kit, Gfo_usr_dlg usr_dlg) {this.kit = kit; this.usr_dlg = usr_dlg;} + @Override public void handleEvent(Event event) { + if (kit.Kit_mode__term()) return; // NOTE: will be term if called again from wait_for_sync_cmd + kit.Kit_mode_(Swt_kit_mode.Tid_term); // NOTE: must mark kit as shutting down, else writing to status_bar will create stack overflow; DATE:2014-05-05 + boolean rslt = Bool_.Cast(kit.Kit_term_cbk().Exec()); // call bgn term + if (!rslt) { + event.doit = false; // cbk canceled term; stop close + kit.Kit_mode_(Swt_kit_mode.Tid_ready); // reset kit back to "running" mode; + return; + } + if (kit.Kit_sync_cmd_exists()) { // sync cmd is running; cannot shut down app else app just hangs; DATE:2015-04-13 + event.doit = false; // cancel shutdown + Thread_adp_.Start_by_key(Invk_wait_for_sync_cmd, this, Invk_wait_for_sync_cmd); // wait for sync_cmd to end in background thread; call shutdown again when it does + } + } + private void Wait_for_sync_cmd() { // THREAD:non-GUI + int loop_count = 0, loop_max = 50, loop_wait = 100; // loop for 100 ms for no more than 5 seconds + while (loop_count < loop_max) { + usr_dlg.Log_many("", "", "swt:waiting for sync cmd; loop=~{0}", loop_count); + if (!kit.Kit_sync_cmd_exists()) { + usr_dlg.Log_many("", "", "swt:sync cmd done; shutting down"); + break; + } + Thread_adp_.Sleep(loop_wait); + loop_count++; + } + if (loop_count == loop_max) + usr_dlg.Log_many("", "", "swt:sync_wait failed", loop_count); + Gfo_invk_.Invk_by_key(kit.New_cmd_sync(kit), Swt_kit.Invk_shell_close); // shutdown again; note that cmd must be called on GUI thread + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_wait_for_sync_cmd)) Wait_for_sync_cmd(); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_wait_for_sync_cmd = "wait_for_sync_cmd"; +} +class Swt_kit_mode { + public static final int + Tid_ctor = 0 + , Tid_ready = 1 + , Tid_term = 2 + ; +} +class Swt_gui_cmd implements GfuiInvkCmd, Runnable { + private final Swt_kit kit; private final Gfo_usr_dlg usr_dlg; private final Display display; private final Gfo_invk target; private final boolean async; + private GfsCtx invk_ctx; private int invk_ikey; private String invk_key; private GfoMsg invk_msg; + private Object rv_obj; + public Swt_gui_cmd(Swt_kit kit, Gfo_usr_dlg usr_dlg, Display display, Gfo_invk target, boolean async) { + this.kit = kit; this.usr_dlg = usr_dlg; this.display = display; this.target = target; this.async = async; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + this.invk_ctx = ctx; this.invk_ikey = ikey; this.invk_key = k; this.invk_msg = m; + if (async) + display.asyncExec(this); + else { + kit.Kit_sync_cmd_add(this); + try {display.syncExec(this);} + finally {kit.Kit_sync_cmd_del(this);} + } + return rv_obj; + } + @Override public void run() { + synchronized (this) {// needed for Special:Search and async; DATE:2015-04-23 + try {rv_obj = target.Invk(invk_ctx, invk_ikey, invk_key, invk_msg);} + catch (Exception e) { + if (kit.Kit_mode__term()) return; // NOTE: if shutting down, don't warn; warn will try to write to status.bar, which will fail b/c SWT is shutting down; failures will try to write to status.bar again, causing StackOverflow exception; DATE:2014-05-04 + usr_dlg.Warn_many("", "", "fatal error while running; key=~{0} err=~{1}", invk_key, Err_.Message_gplx_full(e)); + } + } + } + public void Rls() { + this.invk_ctx = null; this.invk_key = null; this.invk_msg = null; + } +} +class Swt_msg_wkr_stop implements UsrMsgWkr { + private final Swt_kit kit; private final Gfo_usr_dlg gui_wtr; + public Swt_msg_wkr_stop(Swt_kit kit, Gfo_usr_dlg gui_wtr) {this.kit = kit; this.gui_wtr = gui_wtr;} + public void ExecUsrMsg(int type, UsrMsg umsg) { + String msg = umsg.To_str(); + kit.Ask_ok("xowa.gui", "stop", msg); + gui_wtr.Log_many("", "", msg); + } +} diff --git a/150_gfui/src/gplx/gfui/kits/core/TxtFindMgr.java b/150_gfui/src/gplx/gfui/kits/core/TxtFindMgr.java index a27517de8..c62e011a1 100644 --- a/150_gfui/src/gplx/gfui/kits/core/TxtFindMgr.java +++ b/150_gfui/src/gplx/gfui/kits/core/TxtFindMgr.java @@ -13,3 +13,34 @@ 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.gfui.kits.core; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +public class TxtFindMgr { + public String Text() {return text;} + public TxtFindMgr Text_(String v) { + if (!caseSensitive) v = String_.Lower(v); + text = v; + return this; + } String text; + public boolean CaseSensitive() {return caseSensitive;} public TxtFindMgr CaseSensitive_(boolean v) {caseSensitive = v; return this;} private boolean caseSensitive = false; + public int[] FindByUi(String findText, int selBgn, int selLen, boolean keyIsEnter) { + int[] rv = new int[2]; + if (String_.Eq(findText, "")) return rv; // make newSel = 0 b/c all text deleted; else, find will continue from last selBgn; easy way to "reset" + rv[0] = selBgn; rv[1] = selLen; // make newSel = curSel + int adj = keyIsEnter ? 1 : 0; // if enter, search next, else search from cur; else will add to selLen if at match; ex: ab->c at abc will keep same selBgn, but increase selLen to 3 + int findPos = FindNext(findText, selBgn + adj); + if (findPos == String_.Find_none) { // nothing found; set selLen to 0 and return + rv[1] = 0; + return rv; + } + rv[0] = findPos; + rv[1] = String_.Len(findText); + return rv; + } + public int FindNext(String find, int guiPos) { + if (!caseSensitive) find = String_.Lower(find); + int findPos = String_.FindFwd(text, find, guiPos); + if (findPos == String_.Find_none && guiPos != 0) + findPos = String_.FindFwd(text, find, 0); + return findPos; + } +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_app_browser.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_app_browser.java index a27517de8..32a231b39 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_app_browser.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_app_browser.java @@ -13,3 +13,85 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.*; +import gplx.core.envs.System_; + +import org.eclipse.swt.*; +import org.eclipse.swt.browser.*; import org.eclipse.swt.custom.*; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.*; import org.eclipse.swt.layout.*; import org.eclipse.swt.widgets.*; +public class Swt_app_browser { + public static void main(String[] args) { + new Swt_app_browser().start(); + } + + public void start() + { + Display display = new Display(); + Shell shell = new Shell(display); + shell.setLayout(new GridLayout(1, false)); + GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); + gridData.widthHint = SWT.DEFAULT; + gridData.heightHint = SWT.DEFAULT; + shell.setLayoutData(gridData); + shell.setText("Firebug Lite for SWT ;)"); + + final Browser browser = new Browser(shell, SWT.NONE); + GridData gridData2 = new GridData(SWT.FILL, SWT.FILL, true, true); + gridData2.widthHint = SWT.DEFAULT; + gridData2.heightHint = SWT.DEFAULT; + browser.setLayoutData(gridData2); + + Button button = new Button(shell, SWT.PUSH); + button.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false)); + button.setText("Install"); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + browser.setUrl("javascript:(function(F,i,r,e,b,u,g,L,I,T,E){if(F.getElementById(b))return;E=F[i+'NS']&&F.documentElement.namespaceURI;E=E?F[i+'NS'](E,'script'):F[i]('script');E[r]('id',b);E[r]('src',I+g+T);E[r](b,u);(F[e]('head')[0]||F[e]('body')[0]).appendChild(E);E=new%20Image;E[r]('src',I+L);})(document,'createElement','setAttribute','getElementsByTagName','FirebugLite','4','firebug-lite.js','releases/lite/latest/skin/xp/sprite.png','https://getfirebug.com/','#startOpened');"); + } + }); + + browser.setUrl("http://stackoverflow.com/questions/12003602/eclipse-swt-browser-and-firebug-lite"); + + shell.open(); + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + display.dispose(); + } + } +class Swt_app_browser_mgr { + private Shell shell; private Browser browser; + public Swt_app_browser_mgr(Shell shell) {this.shell = shell;} + public void Load() { +// this.Free(); + if (browser == null) { + browser = new Browser(shell, SWT.MOZILLA); + Point size = shell.getSize(); + browser.setBounds(0, 40, size.x, size.y - 40); + } +// browser.setUrl("about:blank"); +// browser.setUrl("file:///C:/temp.html"); + browser.setText("hello"); + } + public void Free() { + if (browser != null) { +// browser.setUrl("about:blank"); + browser.dispose(); + } + System_.Garbage_collect(); + browser = null; + } +} +class Swt_app_browser_cmd_load implements SelectionListener { + private Swt_app_browser_mgr mgr; + public Swt_app_browser_cmd_load(Swt_app_browser_mgr mgr) {this.mgr = mgr;} + public void widgetSelected(SelectionEvent event) {mgr.Load();} + public void widgetDefaultSelected(SelectionEvent event) {} +} +class Swt_app_browser_cmd_free implements SelectionListener { + private Swt_app_browser_mgr mgr; + public Swt_app_browser_cmd_free(Swt_app_browser_mgr mgr) {this.mgr = mgr;} + public void widgetSelected(SelectionEvent event) {mgr.Free();} + public void widgetDefaultSelected(SelectionEvent event) {} +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_app_main.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_app_main.java index a27517de8..461bd391d 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_app_main.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_app_main.java @@ -13,3 +13,415 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.Byte_ascii; +import gplx.String_; + +import org.eclipse.swt.*; +import org.eclipse.swt.browser.*; +import org.eclipse.swt.custom.*; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.layout.*; +import org.eclipse.swt.widgets.*; +public class Swt_app_main { + public static void main(String[] args) { +// Drag_drop(); +// List_fonts(); +// keystrokes(args); + Permission_denied(); +// Combo_default(); +// Combo_composite(); + } + static void Drag_drop() { + final Display display = new Display(); + final Shell shell = new Shell(display); + shell.setLayout(new GridLayout()); + final CTabFolder folder = new CTabFolder(shell, SWT.BORDER); + folder.setLayoutData(new GridData(GridData.FILL_BOTH)); + for (int i = 0; i < 10; i++) { + CTabItem item = new CTabItem(folder, SWT.NONE); + item.setText("item "+i); + Text text = new Text(folder, SWT.BORDER | SWT.MULTI | SWT.VERTICAL); + text.setText("Text control for "+i); + item.setControl(text); + if (i == 9) { + item.setShowClose(false); + item.setText("+"); +// item.setImage(new Image(Display.getDefault(), "J:\\gplx\\xowa\\user\\anonymous\\app\\img\\edit\\format-bold-A.png")); + } + } + ToolBar t = new ToolBar( folder, SWT.FLAT ); + ToolItem i = new ToolItem( t, SWT.PUSH ); + i.setText( "add" ); + folder.setTopRight( t, SWT.RIGHT ); + shell.open(); + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + display.dispose(); + } + static void keystrokes(String[] args) + { + + Display display = new Display (); + + final Shell shell = new Shell (display); + +// display.addFilter(SWT.KeyDown, new Listener() { +// +// public void handleEvent(Event e) { +// if(((e.stateMask & SWT.CTRL) == SWT.CTRL) && (e.keyCode == 'f')) +// { +// System.out.println("From Display I am the Key down !!" + e.keyCode); +// } +// } +// }); + shell.addKeyListener(new KeyListener() { + public void keyReleased(KeyEvent e) { +// if(((e.stateMask & SWT.CTRL) == SWT.CTRL) && (e.keyCode == 'f')) +// { +// shell.setBackground(orig); +// System.out.println("Key up !!"); +// } + System.out.println(e.stateMask + " " + e.keyCode); + } + public void keyPressed(KeyEvent e) { +// System.out.println(e.stateMask + " " + e.keyCode); + } + }); + shell.addMouseListener(new MouseListener() { + @Override + public void mouseUp(MouseEvent arg0) { + // TODO Auto-generated method stub + System.out.println(arg0.button); + } + + @Override + public void mouseDown(MouseEvent arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void mouseDoubleClick(MouseEvent arg0) { + // TODO Auto-generated method stub + + } + }); + + shell.setSize (200, 200); + shell.open (); + while (!shell.isDisposed()) { + if (!display.readAndDispatch ()) display.sleep (); + } + display.dispose (); + + } + static void List_fonts() { + java.awt.GraphicsEnvironment e = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(); + java.awt.Font[] fonts = e.getAllFonts(); // Get the fonts + for (java.awt.Font f : fonts) { + System.out.println(f.getFontName()); + } + } + static void Permission_denied() { + String html + = "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + " click to call permissionDeniedExample -> will throw error and not show sel.rangeCount
\n" + + " click to call permissionDeniedExample inside a setTimeout -> will show sel.rangeCount
\n" + + "\n" + + "\n" + ; + + System.setProperty + ( "org.eclipse.swt.browser.XULRunnerPath" + // ADJUST THIS PATH AS NECESSARY ON YOUR MACHINE + , "C:\\xowa\\bin\\windows\\xulrunner" + ); + Display display = new Display(); + Shell shell = new Shell(display); + shell.setLayout(new FillLayout()); + final Browser browser; + try { + browser = new Browser(shell, SWT.MOZILLA); // changed from none + browser.addLocationListener(new LocationListener() { + @Override + public void changing(LocationEvent arg0) { + if (arg0.location.equals("about:blank")) return; + arg0.doit = false; + } + + @Override + public void changed(LocationEvent arg0) { + String location = arg0.location; + if (location.equals("about:blank")) return; + + // build code + String code = "alert('unknown_link:" + location + "')"; + if (location.contains("direct_call_fails")) + code = "permissionDeniedExample();"; + else if (location.contains("wrapped_call_works")) + code = "setTimeout(function(){permissionDeniedExample();}, 1);"; + + // evaluate code + try { + browser.evaluate(code); + } catch (Exception e) { + System.out.println(e); + } + arg0.doit = false; + } + }); + } catch (SWTError e) { + System.out.println("Could not instantiate Browser: " + e.getMessage()); + display.dispose(); + return; + } + browser.setText(html); + shell.open(); + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + display.dispose(); + } + public static void Combo_dflt() { + Display display = new Display(); + Shell shell = new Shell(display); + shell.setLayout(new FillLayout()); + + String[] ITEMS = { "A", "B", "C", "D" }; + + final Combo combo = new Combo(shell, SWT.DROP_DOWN); + combo.setItems(ITEMS); + combo.select(2); + + combo.addSelectionListener(new SelectionListener() { + public void widgetSelected(SelectionEvent e) { + System.out.println(combo.getText()); + } + + public void widgetDefaultSelected(SelectionEvent e) { + System.out.println(combo.getText()); + } + }); + combo.addKeyListener(new KeyListener() { + @Override + public void keyReleased(KeyEvent arg0) { + // TODO Auto-generated method stub + } + + @Override + public void keyPressed(KeyEvent arg0) { + System.out.println(combo.getText()); + if (arg0.keyCode == Byte_ascii.Ltr_a) { + combo.setItem(0, "a"); + combo.setListVisible(true); + } + else if (arg0.keyCode == Byte_ascii.Ltr_b) { + combo.setItem(0, "b"); + combo.setListVisible(true); + } + // System.out.println(combo.getText()); + } + }); + + shell.open(); + combo.setListVisible(true); + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + display.dispose(); + } + public static void Combo_composite() { + final Display display = new Display(); + final Shell shell = new Shell(display); + GridLayout gridLayout = new GridLayout(); + gridLayout.numColumns = 2; + gridLayout.makeColumnsEqualWidth = true; + shell.setLayout(gridLayout); + final Text text = new Text(shell, SWT.BORDER); + text.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING)); + Text text2 = new Text(shell, SWT.BORDER); + text2.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_END)); + shell.pack(); + shell.open(); + + final Shell combo_shell = new Shell(display, SWT.ON_TOP); + combo_shell.setLayout(new FillLayout()); + final Table combo_table = new Table(combo_shell, SWT.SINGLE); + for (int i = 0; i < 5; i++) { + new TableItem(combo_table, SWT.NONE); + } + + text.addListener(SWT.KeyDown, new Listener() { + @Override public void handleEvent(Event event) { + int index = -1; + switch (event.keyCode) { + case SWT.ARROW_DOWN: + if (event.stateMask == SWT.ALT) { + Rectangle text_bounds = display.map(shell, null, text.getBounds()); + combo_shell.setBounds(text_bounds.x, text_bounds.y + text_bounds.height, text_bounds.width, (text_bounds.height - 1) * combo_table.getItems().length); + combo_shell.setVisible(true); + } else { + index = (combo_table.getSelectionIndex() + 1) % combo_table.getItemCount(); + combo_table.setSelection(index); + event.doit = false; + } + break; + case SWT.ARROW_UP: + if (event.stateMask == SWT.ALT) { + combo_shell.setVisible(false); + } else { + index = combo_table.getSelectionIndex() - 1; + if (index < 0) index = combo_table.getItemCount() - 1; + combo_table.setSelection(index); + event.doit = false; + } + break; + case SWT.CR: + if (combo_shell.isVisible() && combo_table.getSelectionIndex() != -1) { + text.setText(combo_table.getSelection()[0].getText()); + combo_shell.setVisible(false); + } + break; + case SWT.ESC: + combo_shell.setVisible(false); + break; + } + } + }); + + text.addListener(SWT.Modify, new Listener() { + @Override public void handleEvent(Event event) { + String string = text.getText(); + if (string.length() == 0) { + combo_shell.setVisible(false); + } else { + TableItem[] items = combo_table.getItems(); + for (int i = 0; i < items.length; i++) { + items[i].setText(string + '-' + i); + } + + Rectangle text_bounds = display.map(shell, null, text.getBounds()); + combo_shell.setBounds(text_bounds.x, text_bounds.y + text_bounds.height, text_bounds.width, (text_bounds.height - 1) * items.length); + combo_shell.setVisible(true); + } + } + }); + + combo_table.addListener(SWT.DefaultSelection, new Listener() { + @Override public void handleEvent(Event arg0) { + text.setText(combo_table.getSelection()[0].getText()); + combo_shell.setVisible(false); + } + }); + + combo_table.addListener(SWT.KeyDown, new Listener() { + @Override public void handleEvent(Event event) { + if (event.keyCode == SWT.ESC) { + combo_shell.setVisible(false); + } + } + }); + + final Swt_shell_hider shell_hider = new Swt_shell_hider(combo_shell); + Listener focus_out_listener = new Listener() { + @Override public void handleEvent(Event arg0) { + if (display.isDisposed()) return; + Control control = display.getFocusControl(); +// if (control == null || (control != text && control != combo_table)) { +// combo_shell.setVisible(false); +// } + if (control == null || (control == text || control == combo_table)) { + // combo_shell.setVisible(false); + shell_hider.Active = true; + display.asyncExec(shell_hider); + //Thread t = new Thread(shell_hider); t.start(); + //Swt_shell_hider + } + +// boolean combo_is_focus = combo_table.isFocusControl(); +// boolean text_is_focus = text.isFocusControl(); +// if (control == null || (control == text)) { +// combo_shell.setVisible(false); +// } +// if (control == null || (control == combo_table)) { +// combo_shell.setVisible(true); +// } + } + }; + + combo_table.addListener(SWT.FocusOut, focus_out_listener); + text.addListener(SWT.FocusOut, focus_out_listener); + + Listener focus_in_listener = new Listener() { + @Override public void handleEvent(Event arg0) { + if (display.isDisposed()) return; + Control control = display.getFocusControl(); + if (control == combo_table) { + // combo_shell.setVisible(false); + //display.asyncExec(shell_hider); + shell_hider.Active = false; + //Swt_shell_hider + } +// boolean combo_is_focus = combo_table.isFocusControl(); +// boolean text_is_focus = text.isFocusControl(); +// if (control == null || (control == text)) { +// combo_shell.setVisible(false); +// } +// if (control == null || (control == combo_table)) { +// combo_shell.setVisible(true); +// } + } + }; + combo_table.addListener(SWT.FocusIn, focus_in_listener); + + shell.addListener(SWT.Move, new Listener() { + @Override public void handleEvent(Event arg0) { + combo_shell.setVisible(false); + } + }); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) display.sleep(); + } + display.dispose(); + } +} +class Swt_shell_hider implements Runnable { + public boolean Active = true; + + private Shell combo_shell; + public Swt_shell_hider(Shell combo_shell) {this.combo_shell = combo_shell;} + @Override public void run() { +// try { +// Thread.sleep(1000); +// } catch (InterruptedException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } + if (Active) { + combo_shell.setVisible(false); + } + } +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_btn.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_btn.java index a27517de8..312bb517f 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_btn.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_btn.java @@ -13,3 +13,55 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.controls.gxws.GxwCbkHost; +import gplx.gfui.controls.gxws.GxwCore_base; +import gplx.gfui.controls.gxws.GxwElem; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CLabel; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Layout; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; +public class Swt_btn implements GxwElem, Swt_control { + private Button btn; + public Swt_btn(Swt_control owner, Keyval_hash ctorArgs) { + btn = new Button(owner.Under_composite(), SWT.FLAT | SWT.PUSH); + core = new Swt_core__basic(btn); + btn.addKeyListener(new Swt_lnr_key(this)); + btn.addMouseListener(new Swt_lnr_mouse(this)); + } + @Override public Control Under_control() {return btn;} + @Override public Control Under_menu_control() {return btn;} + @Override public String TextVal() {return btn.getText();} @Override public void TextVal_set(String v) {btn.setText(v);} + @Override public GxwCore_base Core() {return core;} GxwCore_base core; + @Override public GxwCbkHost Host() {return host;} @Override public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host; + @Override public Composite Under_composite() {return null;} + @Override public void EnableDoubleBuffering() {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return null;} +} +class Swt_clabel_lnr_focus implements FocusListener { + public Swt_clabel_lnr_focus(Control v) {this.surrogate = v;} Control surrogate; + @Override public void focusGained(org.eclipse.swt.events.FocusEvent e) { + surrogate.forceFocus(); + } + @Override public void focusLost(org.eclipse.swt.events.FocusEvent arg0) {} +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_btn_no_border.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_btn_no_border.java index a27517de8..4e1b25514 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_btn_no_border.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_btn_no_border.java @@ -13,3 +13,82 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.GfoMsg; +import gplx.GfsCtx; +import gplx.Keyval_hash; +import gplx.gfui.SizeAdp; +import gplx.gfui.controls.gxws.GxwCbkHost; +import gplx.gfui.controls.gxws.GxwCore_base; +import gplx.gfui.controls.gxws.GxwElem; +import gplx.gfui.controls.standards.GfuiBtn; +import gplx.gfui.imgs.ImageAdp; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; + +public class Swt_btn_no_border implements GxwElem, Swt_control { + private ImageAdp btn_img; private Composite box_grp; private Label box_btn; + public Swt_btn_no_border(Swt_control owner_control, Keyval_hash ctorArgs) { + Composite owner = owner_control.Under_composite(); + Make_btn_no_border(owner.getDisplay(), owner.getShell(), owner); + this.core = new Swt_core__dual(box_grp, box_btn, 2, 2); + box_btn.addKeyListener(new Swt_lnr_key(this)); + box_btn.addMouseListener(new Swt_lnr_mouse(this)); + box_btn.setCursor((Cursor)ctorArgs.Get_val_or_null("cursor")); + } + @Override public Control Under_control() {return box_grp;} + @Override public Composite Under_composite() {return box_grp;} + @Override public Control Under_menu_control() {return box_grp;} + @Override public String TextVal() {return box_btn.getText();} @Override public void TextVal_set(String v) {box_btn.setText(v);} + @Override public GxwCore_base Core() {return core;} private final Swt_core__base core; + @Override public GxwCbkHost Host() {return host;} @Override public void Host_set(GxwCbkHost host) {this.host = host;} private GxwCbkHost host; + @Override public void EnableDoubleBuffering() {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, GfuiBtn.Invk_btn_img)) return btn_img; + else if (ctx.Match(k, GfuiBtn.Invk_btn_img_)) Btn_img_((ImageAdp)m.CastObj("v")); + return null; + } + private void Btn_img_(ImageAdp v) { + if (box_btn == null || v == null) return; + SizeAdp size = core.Size(); + // HACK: nightmode + // for day mode, resize needed b/c blurred images look better + // for night mode, SWT introduces white pixels + // so resize only if height is different knowing that (a) btns have height of 16px and (b) day=32px; night=16px + // note can't use width, b/c search_exec_btn somehow goes from 16px to 20px + if ( v.Size().Height() != size.Height() + && size.Width() != 0) // WORKAROUND.SWT: width is 0 due to "need to specify height" workaround on 2017-03-28; however, can't set 0 width else SWT will throw error; DATE:2017-07-23 + v = v.Resize(size.Width(), size.Height()); + if ((v.Under() instanceof Image)) { // check needed else will fail when image doesn't exist; DATE:2017-06-03 + box_btn.setImage(Copy_w_transparency((Image)v.Under())); + } + } + private Image Copy_w_transparency(Image src) { + // set transparency + ImageData imageData = src.getImageData(); + imageData.transparentPixel = imageData.getPixel(0, 0); + + // create new image with transparency set + Image trg = new Image(box_grp.getDisplay(), imageData); + src.dispose(); + return trg; + } + private void Make_btn_no_border(Display display, Shell shell, Composite owner) { + box_grp = new Composite(owner, SWT.FLAT); + box_btn = new Label(box_grp, SWT.FLAT); + box_grp.setSize(16, 16); + box_btn.setSize(16, 16); + box_btn.addFocusListener(new Swt_clabel_lnr_focus(box_grp)); + } +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_clipboard.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_clipboard.java index a27517de8..b9b846ae9 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_clipboard.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_clipboard.java @@ -13,3 +13,48 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.ipts.*; +import gplx.gfui.kits.core.Gfui_clipboard; +import gplx.gfui.kits.core.Gfui_clipboard_; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +public class Swt_clipboard implements Gfui_clipboard { + public Swt_clipboard(Display display) { + this.display = display; + clipboard = new Clipboard(display); + } Display display; Clipboard clipboard; + public void Copy(String v) { + if (String_.Len_eq_0(v)) return; + TextTransfer textTransfer = TextTransfer.getInstance(); + clipboard.setContents(new Object[]{v}, new Transfer[]{textTransfer}); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Gfui_clipboard_.Invk_copy)) Send_key(IptKey_.Ctrl, 'C'); + else if (ctx.Match(k, Gfui_clipboard_.Invk_select_all)) Send_key(IptKey_.Ctrl, 'A'); + else return Gfo_invk_.Rv_unhandled; + return this; + } + @Override public void Rls() {clipboard.dispose();} + int Xto_keycode(IptKey modifier) { + switch (modifier.Val()) { + case IptKey_.KeyCode_Ctrl: return SWT.CTRL; + case IptKey_.KeyCode_Alt: return SWT.ALT; + case IptKey_.KeyCode_Shift: return SWT.SHIFT; + default: return SWT.NONE; + } + } + public void Send_key(IptKey mod, char key_press_char) { + Event event = new Event(); + int modifier_key_code = Xto_keycode(mod); + event.keyCode = modifier_key_code; event.type = SWT.KeyDown; display.post(event); + event.keyCode = 0; event.character = key_press_char; display.post(event); + event.type = SWT.KeyUp; display.post(event); + event.keyCode = modifier_key_code; event.character = 0; display.post(event); + } +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_combo.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_combo.java index a27517de8..48182a114 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_combo.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_combo.java @@ -13,3 +13,94 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.Gfo_evt_mgr; +import gplx.Gfo_evt_mgr_owner; +import gplx.Gfo_evt_mgr_; +import gplx.GfoMsg; +import gplx.GfsCtx; +import gplx.Keyval_hash; +import gplx.String_; +import gplx.Tfds; +import gplx.core.threads.Thread_adp; +import gplx.core.threads.Thread_adp_; +import gplx.gfui.controls.gxws.GxwCbkHost; +import gplx.gfui.controls.gxws.GxwComboBox; +import gplx.gfui.controls.gxws.GxwCore_base; +import gplx.gfui.controls.gxws.GxwElem; +import gplx.gfui.controls.standards.GfuiComboBox; +import gplx.gfui.draws.ColorAdp; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +class Swt_combo implements GxwElem, GxwComboBox, Swt_control, Gfo_evt_mgr_owner { + private final Combo combo; + public Swt_combo(Swt_control owner, Keyval_hash ctorArgs) { + combo = new Combo(owner.Under_composite(), SWT.DROP_DOWN); + core = new Swt_core__basic(combo); + combo.addKeyListener(new Swt_lnr_key(this)); + combo.addMouseListener(new Swt_lnr_mouse(this)); + combo.addSelectionListener(new Swt_combo__selection_listener(this)); + } + public ColorAdp Border_color() {return border_color;} public void Border_color_(ColorAdp v) {border_color = v;} private ColorAdp border_color; + @Override public Gfo_evt_mgr Evt_mgr() {return ev_mgr;} private Gfo_evt_mgr ev_mgr; public void Evt_mgr_(Gfo_evt_mgr v) {ev_mgr = v;} + @Override public Control Under_control() {return combo;} + @Override public Control Under_menu_control() {return combo;} + @Override public String TextVal() {return combo.getText();} @Override public void TextVal_set(String v) {combo.setText(v);} + @Override public GxwCore_base Core() {return core;} GxwCore_base core; + @Override public GxwCbkHost Host() {return host;} @Override public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host; + @Override public Composite Under_composite() {return null;} + @Override public void EnableDoubleBuffering() {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return null;} + @Override public Object SelectedItm() {return null;} + @Override public int SelBgn() {return combo.getSelection().x;} @Override public void SelBgn_set(int v) {combo.setSelection(new Point(combo.getSelection().y, v));} + @Override public int SelLen() {return combo.getSelection().y;} @Override public void SelLen_set(int v) {combo.setSelection(new Point(v, combo.getSelection().x));} + @Override public void Sel_(int bgn, int end) {combo.setSelection(new Point(bgn, end));} + @Override public void SelectedItm_set(Object v) {} + @Override public String[] DataSource_as_str_ary() {return String_.Ary_empty;} + @Override public void DataSource_set(Object... ary) {combo.setItems((String[])ary);} + @Override public String Text_fallback() {return "";} @Override public void Text_fallback_(String v) {} + @Override public int List_sel_idx() {return -1;} @Override public void List_sel_idx_(int v) {} + @Override public void Items__update(String[] ary) {} + @Override public void Items__size_to_fit(int count) {} + @Override public void Items__visible_rows_(int v) {} + @Override public void Items__jump_len_(int v) {} + @Override public void Margins_set(int left, int top, int right, int bot) {} + public void Items__backcolor_(ColorAdp v) {} + public void Items__forecolor_(ColorAdp v) {} +// @Override public void DataSource_update(Object... ary) { +// String[] src = (String[])ary; +// int trg_len = combo.getItems().length; +// int src_len = src.length; +// for (int i = 0; i < trg_len; ++i) { +// combo.setItem(i, i < src_len ? src[i] : ""); +// } +// } + @Override public boolean List_visible() {return combo.getListVisible();} + @Override public void List_visible_(boolean v) { + String prv_text = combo.getText(); + combo.setListVisible(v); + String cur_text = combo.getText(); + while (!String_.Eq(cur_text, prv_text)) { // NOTE: setting setListVisible to true may cause text to grab item from dropDown list; try to reset to original value; DATE:2016-03-14 + Thread_adp_.Sleep(1); + combo.setText(prv_text); + cur_text = combo.getText(); + } + int text_len = prv_text == null ? 0 : prv_text.length(); + combo.setSelection(new Point(text_len, text_len)); + } +} +class Swt_combo__selection_listener implements SelectionListener { + private final Swt_combo combo; + public Swt_combo__selection_listener(Swt_combo combo) {this.combo = combo;} + @Override public void widgetSelected(SelectionEvent arg0) { + Gfo_evt_mgr_.Pub(combo, GfuiComboBox.Evt__selected_changed); + } + @Override public void widgetDefaultSelected(SelectionEvent arg0) {} +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_combo_ctrl.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_combo_ctrl.java index a27517de8..73aaff9f3 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_combo_ctrl.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_combo_ctrl.java @@ -13,3 +13,389 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.*; +import gplx.core.envs.Op_sys; +import gplx.core.envs.Op_sys_; +import gplx.core.threads.Thread_adp_; +import gplx.gfui.controls.gxws.GxwComboBox; +import gplx.gfui.controls.gxws.GxwElem; +import gplx.gfui.draws.ColorAdp; +import gplx.gfui.controls.standards.GfuiComboBox; +import gplx.gfui.kits.core.Swt_kit; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.Text; + +public class Swt_combo_ctrl extends Swt_text_w_border implements GxwElem, GxwComboBox, Swt_control, Gfo_evt_mgr_owner { // REF: https://www.eclipse.org/forums/index.php/t/351029/; http://git.eclipse.org/c/platform/eclipse.platform.swt.git/tree/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet320.java + private final Text swt_text; + private final Swt_combo_list list; + public Swt_combo_ctrl(Swt_kit kit, Swt_control owner, Color color, Keyval_hash ctorArgs) { + super(kit, owner, color, new Keyval_hash()); + Display display = owner.Under_control().getDisplay(); + Shell shell = owner.Under_control().getShell(); + this.swt_text = super.Under_text(); + this.list = new Swt_combo_list(display, shell, this); + + swt_text.addListener(SWT.KeyDown, new Swt_combo_text__key_down(list)); + swt_text.addListener(SWT.MouseUp, new Swt_combo_text__mouse_up(list)); + + Table swt_list = list.Under_table_as_swt(); + swt_list.addListener(SWT.DefaultSelection, new Swt_combo_list__default_selection(this, list)); + swt_list.addListener(SWT.KeyDown, new Swt_combo_list__default_selection(this, list)); + swt_list.addListener(SWT.MouseDown, new Swt_combo_list__mouse_down(this, list)); + + // listeners to hide list-box when focus is transfered, shell is moved, text-box is clicked, etc + Swt_combo_ctrl__list_hider_cmd list_hide_cmd = new Swt_combo_ctrl__list_hider_cmd(list); + Swt_combo_ctrl__focus_out focus_out_lnr = new Swt_combo_ctrl__focus_out(display, this, list, list_hide_cmd); + swt_text.addListener(SWT.FocusOut, focus_out_lnr); + swt_list.addListener(SWT.FocusOut, focus_out_lnr); + swt_text.addListener(SWT.FocusIn, new Swt_combo_text__focus_in(this, list)); + swt_list.addListener(SWT.FocusIn, new Swt_combo_ctrl__focus_in(display, list, list_hide_cmd)); + shell.addListener(SWT.Move, new Swt_combo_shell__move(list)); + } + @Override public Gfo_evt_mgr Evt_mgr() {return ev_mgr;} private Gfo_evt_mgr ev_mgr; public void Evt_mgr_(Gfo_evt_mgr v) {ev_mgr = v;} + @Override public Object SelectedItm() {return null;} + @Override public void SelectedItm_set(Object v) {} + @Override public void Sel_(int bgn, int end) {swt_text.setSelection(new Point(bgn, end));} + @Override public String[] DataSource_as_str_ary() {return list.Items_str_ary();} + @Override public void DataSource_set(Object... ary) {list.Items_((String[])ary);} + @Override public String Text_fallback() {return text_fallback;} private String text_fallback = "";// preserve original-text when using cursor keys in list-box + @Override public void Text_fallback_(String v) {text_fallback = v;} + public void Text_fallback_restore() { + if (String_.Len_eq_0(text_fallback)) return; // handle escape pressed after dropdown is visible, but down / up not pressed + this.Text_(text_fallback); + this.text_fallback = ""; + } + @Override public void Items__update(String[] ary) {list.Items_(ary);} + @Override public void Items__size_to_fit(int count) {list.Resize_shell(count);} + @Override public int List_sel_idx() {return list.Sel_idx();} + @Override public void List_sel_idx_(int v) {list.Sel_idx_(v);} + @Override public boolean List_visible() {return list.Visible();} + @Override public void List_visible_(boolean v) {list.Visible_(v);} + + @Override public void Items__visible_rows_(int v) {list.Visible_rows = v;} + @Override public void Items__jump_len_(int v) {list.Jump_len = v;} + public Rectangle Bounds() {return super.Under_control().getBounds();} + public String Text() {return swt_text.getText();} public void Text_(String v) {swt_text.setText(v);} + public void Sel_all() { + String text_text = swt_text.getText(); + this.Sel_(0, String_.Len(text_text)); + } + public void Items__backcolor_(ColorAdp v) {list.Under_table_as_swt().setBackground(kit.New_color(v));} + public void Items__forecolor_(ColorAdp v) {list.Under_table_as_swt().setForeground(kit.New_color(v));} +} +class Swt_combo_list { + private final Display display; private final Shell owner_shell; + private final Swt_combo_ctrl ctrl; + private final Shell shell; + private final Table table; + private int list_len; + public int Jump_len = 5; + public int Visible_rows = 10; + public Swt_combo_list(Display display, Shell owner_shell, Swt_combo_ctrl ctrl) { + this.display = display; this.owner_shell = owner_shell; this.ctrl = ctrl; + this.shell = new Shell(display, SWT.ON_TOP); + shell.setLayout(new FillLayout()); + this.table = new Table(shell, SWT.SINGLE); + } + public Table Under_table_as_swt() {return table;} + public int Items_len() {return table.getItemCount();} + public TableItem[] Items() {return table.getItems();} + public TableItem Sel_itm(int i) {return table.getSelection()[i];} + public void Items_text_(int idx, String v) {table.getItem(idx).setText(v);} + public String[] Items_str_ary() {return items_str_ary;} private String[] items_str_ary = String_.Ary_empty; + public void Items_(String[] new_ary) { + int new_len = new_ary.length; + if (new_len == 0) Visible_(Bool_.N); // if new_ary is empty, then hide list box; else, brief flicker as items are removed + + // remove all cur-items that are no longer needed b/c new_ary is smaller + int cur_len = list_len; + for (int i = new_len; i < cur_len; ++i) { + table.remove(new_len); + } + + // update new_ary; + for (int i = 0; i < new_len; ++i) { + TableItem item = null; + if (i < cur_len) // existing item; reuse it + item = table.getItem(i); + else // no ite; create a new one + item = new TableItem(table, SWT.NONE); + String cur_text = item.getText(); + String new_text = new_ary[i]; + if (!String_.Eq(cur_text, new_text) && new_text != null) { + item.setText(new_text); + } + } + this.list_len = new_len; + + // resize list-shell to # of items + int max_len = this.Visible_rows; + if ( new_len == cur_len // do not resize if same # + || new_len > max_len && cur_len > max_len // do not resize if new_len and cur_len are both off-screen + ) {} + this.items_str_ary = new_ary; +// else +// Resize_shell(list_len); + } + public int Sel_idx() {return table.getSelectionIndex();} + public void Sel_idx_(int v) {table.setSelection(v);} + public void Sel_idx_nudge(boolean fwd) {Sel_idx_adj(fwd, 1);} + public void Sel_idx_jump(boolean fwd) {Sel_idx_adj(fwd, Jump_len);} + private void Sel_idx_adj(boolean fwd, int adj) { + if (!Visible()) Visible_(Bool_.Y); // these are called by cursor keys; always make visible + int cur_idx = table.getSelectionIndex(); + int new_idx = cur_idx; + int idx_n = list_len - 1; + if (fwd) { + if (cur_idx == idx_n) + new_idx = -1; + else if (cur_idx == -1) + new_idx = 0; + else { + new_idx = cur_idx + adj; + if (new_idx >= idx_n) + new_idx = idx_n; + } + } + else { + if (cur_idx == 0) + new_idx = -1; + else if (cur_idx == -1) + new_idx = idx_n; + else { + new_idx = cur_idx - adj; + if (new_idx < 0) + new_idx = 0; + } + } + Sel_idx_by_key(new_idx); + } + public void Sel_idx_by_key(int v) { + table.setSelection(v); + if (v == -1) { // nothing selected; restore orig + ctrl.Text_fallback_restore(); + } else { // something selected; transfer selected item to text + String sel_text = table.getItem(v).getText(); + ctrl.Text_(sel_text); + ctrl.Sel_(sel_text.length(), sel_text.length()); + } + Gfo_evt_mgr_.Pub(ctrl, GfuiComboBox.Evt__selected_changed); + } + public boolean Visible() {return shell.isVisible();} + public void Visible_(boolean v) { + if (v && list_len == 0) return; // never show if 0 items; occurs when users presses alt+down or down when in combo-box + shell.setVisible(v); + } + public void Resize_shell(int len) { + Rectangle text_bounds = display.map(owner_shell, null, ctrl.Bounds()); + int adj = Op_sys.Cur().Tid() == Op_sys.Lnx.Tid() ? 9 : 2; // NOTE: magic-numbers from gnosygnu's Windows-7, OS-X and openSUSE; Linux # is not correct for different number of items, but looks acceptable for 25; + int max_len = this.Visible_rows; + int height = (table.getItemHeight() * (len > max_len ? max_len : len)) + adj; + if (height != shell.getSize().y) // only resize if height is different + shell.setBounds(text_bounds.x, text_bounds.y + text_bounds.height - 1, text_bounds.width, height); + } +} +class Swt_combo_text__key_down implements Listener { // for list-box, highlight item or toggle visiblility based on keys + private final Swt_combo_list list; + public Swt_combo_text__key_down(Swt_combo_list list) { + this.list = list; + } + @Override public void handleEvent(Event event) { + try { + int mask = event.stateMask; + boolean no_modifier = false; + boolean ctrl_modifier = false; + boolean alt_modifier = false; + if ((mask & SWT.ALT) == SWT.ALT) {alt_modifier = true;} + else if ((mask & SWT.CTRL) == SWT.CTRL){ctrl_modifier = true;} + else if ((mask & SWT.SHIFT) == SWT.SHIFT){} + else + no_modifier = true; + switch (event.keyCode) { + case SWT.ARROW_DOWN: + if (event.stateMask == SWT.ALT) { + list.Visible_(true); + } else { + list.Sel_idx_nudge(Bool_.Y); + } + event.doit = false; + break; + case SWT.ARROW_UP: + if (event.stateMask == SWT.ALT) { + list.Visible_(false); + } else { + list.Sel_idx_nudge(Bool_.N); + } + event.doit = false; + break; + case SWT.PAGE_DOWN: + if (no_modifier) { + list.Sel_idx_jump(Bool_.Y); + event.doit = false; + } + else if (ctrl_modifier || alt_modifier) { + list.Visible_(Bool_.N); + event.doit = false; + } + break; + case SWT.PAGE_UP: + if (no_modifier) { + list.Sel_idx_jump(Bool_.N); + event.doit = false; + } + else if (ctrl_modifier || alt_modifier) { + list.Visible_(Bool_.N); + event.doit = false; + } + break; + case SWT.CR: + if (list.Visible() && list.Sel_idx() != -1) { + list.Visible_(false); + } + break; + case SWT.ESC: + if (list.Visible()) { // NOTE: must check if list is visible, else will need to press escape twice to restore url; DATE:2017-02-15 + list.Sel_idx_by_key(-1); + list.Visible_(false); + } + break; + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} +class Swt_combo_text__mouse_up implements Listener { // if list-box is visible, left-click on text-box should hide list-box; note that left-click does not fire focus-in event; EX: focus html-box; click on url-box; focus-in event not fired + private final Swt_combo_list list; + public Swt_combo_text__mouse_up(Swt_combo_list list) { + this.list = list; + } + @Override public void handleEvent(Event event) { + if (event.button == 1) { // left-click + if (list.Visible()) { + list.Visible_(false); + } + } + } +} +class Swt_combo_list__default_selection implements Listener { // transfer list-box's selected to text-box + private final Swt_combo_ctrl ctrl; private final Swt_combo_list list; + public Swt_combo_list__default_selection(Swt_combo_ctrl ctrl, Swt_combo_list list) { + this.ctrl = ctrl; this.list = list; + } + @Override public void handleEvent(Event arg0) { + ctrl.Text_(list.Sel_itm(0).getText()); + list.Visible_(false); + } +} +class Swt_combo_list__key_down implements Listener { // hide list-box if escape pressed + private final Swt_combo_ctrl ctrl; private final Swt_combo_list list; + public Swt_combo_list__key_down(Swt_combo_ctrl ctrl, Swt_combo_list list) { + this.ctrl = ctrl; + this.list = list; + } + @Override public void handleEvent(Event event) { + if (event.keyCode == SWT.ESC) { + ctrl.Text_fallback_restore(); + list.Visible_(false); + } + } +} +class Swt_combo_list__mouse_down implements Listener { // left-click on list-box should transfer item to text-box and publish "accepted" event + private final Swt_combo_ctrl ctrl; + private final Swt_combo_list list; + public Swt_combo_list__mouse_down(Swt_combo_ctrl ctrl, Swt_combo_list list) { + this.ctrl = ctrl; + this.list = list; + } + @Override public void handleEvent(Event event) { + if (event.button == 1) { // left-click + ctrl.Text_(list.Sel_itm(0).getText()); + Gfo_evt_mgr_.Pub(ctrl, GfuiComboBox.Evt__selected_changed); + Gfo_evt_mgr_.Pub(ctrl, GfuiComboBox.Evt__selected_accepted); + } + } +} +class Swt_combo_ctrl__focus_out implements Listener { // run hide-cmd when list-box when text-box / list-box loses focus + private final Display display; + private final Control text_as_swt, list_as_swt; + private final Swt_combo_ctrl__list_hider_cmd list_hide_cmd; + public Swt_combo_ctrl__focus_out(Display display, Swt_combo_ctrl ctrl, Swt_combo_list list, Swt_combo_ctrl__list_hider_cmd list_hide_cmd) { + this.list_hide_cmd = list_hide_cmd; + this.display = display; + this.text_as_swt = ctrl.Under_text(); + this.list_as_swt = list.Under_table_as_swt(); + } + @Override public void handleEvent(Event arg0) { + if (display.isDisposed()) return; + Control control = display.getFocusControl(); + if (control == null || control == text_as_swt || control == list_as_swt) { + list_hide_cmd.Active = true; + display.asyncExec(list_hide_cmd); + } + } +} +class Swt_combo_ctrl__focus_in implements Listener { // cancel hide-cmd if list-box gains focus + private final Display display; + private final Swt_combo_ctrl__list_hider_cmd list_hide_cmd; + private final Control list_as_swt; + + public Swt_combo_ctrl__focus_in(Display display, Swt_combo_list list, Swt_combo_ctrl__list_hider_cmd list_hide_cmd) { + this.display = display; + this.list_as_swt = list.Under_table_as_swt(); + this.list_hide_cmd = list_hide_cmd; + } + @Override public void handleEvent(Event arg0) { + if (display.isDisposed()) return; + Control control = display.getFocusControl(); + if (control == list_as_swt) + list_hide_cmd.Active = false; + } +} +class Swt_combo_text__focus_in implements Listener { // hide list-box when text-box is focused + private final Swt_combo_ctrl ctrl; private final Swt_combo_list list; + public Swt_combo_text__focus_in(Swt_combo_ctrl ctrl, Swt_combo_list list) { + this.ctrl = ctrl; this.list = list; + } + @Override public void handleEvent(Event arg0) { + if (list.Visible()) + list.Visible_(false); + else + ctrl.Sel_all(); + } +} +class Swt_combo_shell__move implements Listener { // hide list-box when shell is moved + private final Swt_combo_list list; + public Swt_combo_shell__move(Swt_combo_list list) {this.list = list;} + @Override public void handleEvent(Event arg0) { + list.Visible_(false); + } +} +class Swt_combo_ctrl__list_hider_cmd implements Runnable { // hide list-box; can be "canceled" + private final Swt_combo_list list; + public boolean Active = true; + public Swt_combo_ctrl__list_hider_cmd(Swt_combo_list list) { + this.list = list; + } + @Override public void run() { + if (Active) + list.Visible_(false); + } +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_control.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_control.java index a27517de8..e56ad5ccc 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_control.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_control.java @@ -13,3 +13,15 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.controls.gxws.GxwElem; + +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +public interface Swt_control extends GxwElem { + Control Under_control(); + Composite Under_composite(); + Control Under_menu_control(); +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_control_.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_control_.java index a27517de8..32dfd17cd 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_control_.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_control_.java @@ -13,3 +13,29 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.PointAdp; +import gplx.gfui.RectAdp; +import gplx.gfui.SizeAdp; +import gplx.gfui.controls.elems.GfuiElem; + +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Control; + +public class Swt_control_ { + public static void X_set(Control c, int v) {Point point = c.getLocation(); c.setLocation(v, point.y);} + public static void Y_set(Control c, int v) {Point point = c.getLocation(); c.setLocation(point.x, v);} + public static void W_set(Control c, int v) {Point point = c.getSize(); c.setSize(v, point.y);} + public static void H_set(Control c, int v) {Point point = c.getSize(); c.setSize(point.x, v);} + public static void Pos_set(Control c, PointAdp v) {c.setLocation(v.X(), v.Y());} + public static void Pos_set(Control c, int x, int y) {c.setLocation(x, y);} + public static void Size_set(Control c, SizeAdp v) {c.setSize(v.Width(), v.Height());} + public static void Size_set(Control c, int w, int h) {c.setSize(w, h);} + public static void Rect_set(Control c, RectAdp v) {c.setBounds(To_rectangle(v));} + public static void Rect_set(Control c, int x, int y, int w, int h) {c.setBounds(To_rectangle(x, y, w, h));} + public static void Rect_add(Control c, RectAdp v, int x, int y, int w, int h) {c.setBounds(To_rectangle(v.X() + x, v.Y() + y, v.Width() + w, v.Height()+ h));} + private static Rectangle To_rectangle(int x, int y, int w, int h) {return new Rectangle(x, y, w, h);} + private static Rectangle To_rectangle(RectAdp v) {return new Rectangle(v.X(), v.Y(), v.Width(), v.Height());} + public static Swt_control cast_or_fail(GfuiElem elem) {return (Swt_control)elem.UnderElem();} +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_core__base.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_core__base.java index a27517de8..063e2a027 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_core__base.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_core__base.java @@ -13,3 +13,135 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.layout.*; +import org.eclipse.swt.widgets.*; + +import gplx.gfui.controls.gxws.GxwCore_base; +import gplx.gfui.controls.gxws.GxwElem; +import gplx.gfui.draws.*; +import gplx.gfui.kits.*; +import gplx.gfui.layouts.swts.*; + +abstract class Swt_core__base extends GxwCore_base { + private boolean focus_able; + private int focus_index; + protected Swt_layout_mgr layout_mgr; + protected Swt_layout_data layout_data; + private Control sizeable, viewable; + private FontAdp text_font; + public Swt_core__base(Control sizeable, Control viewable) { + this.sizeable = sizeable; + this.viewable = viewable; + } + @Override public int X() {return sizeable.getLocation().x;} + @Override public void X_set(int v) {Swt_control_.X_set(sizeable, v);} + @Override public int Y() {return sizeable.getLocation().y;} + @Override public void Y_set(int v) {Swt_control_.Y_set(sizeable, v);} + @Override public int Width() {return sizeable.getSize().x;} + @Override public void Width_set(int v) {Swt_control_.W_set(sizeable, v);} + @Override public int Height() {return sizeable.getSize().y;} + @Override public void Height_set(int v) {Swt_control_.H_set(sizeable, v);} + @Override public SizeAdp Size() {return SizeAdp_.new_(this.Width(), this.Height());} + @Override public void Size_set(SizeAdp v) {Swt_control_.Size_set(sizeable, v);} + @Override public PointAdp Pos() {return PointAdp_.new_(this.X(), this.Y());} + @Override public void Pos_set(PointAdp v) {Swt_control_.Pos_set(sizeable, v);} + @Override public RectAdp Rect() {return RectAdp_.new_(this.X(), this.Y(), this.Width(), this.Height());} + @Override public void Rect_set(RectAdp v) {Swt_control_.Rect_set(sizeable, v);} + @Override public boolean Visible() {return sizeable.isVisible();} + @Override public void Visible_set(boolean v) {sizeable.setVisible(v);} + @Override public Swt_layout_mgr Layout_mgr() {return layout_mgr;} + @Override public Swt_layout_data Layout_data() {return layout_data;} + @Override public void Layout_mgr_(Swt_layout_mgr v) { + Swt_core__base.Layout_mgr_set(sizeable, v); + this.layout_mgr = v; + } + @Override public void Layout_data_(Swt_layout_data v) { + Swt_core__base.Layout_data_set(sizeable, v); + this.layout_data = v; + } + @Override public void Controls_add(GxwElem sub) { + if (!(sizeable instanceof Composite)) throw Err_.new_wo_type("cannot add sub to control"); + Composite owner_as_composite = (Composite)sizeable; + Control sub_as_swt = ((Swt_control)sub).Under_control(); + sub_as_swt.setParent(owner_as_composite); + } + @Override public void Controls_del(GxwElem sub) { + if (!(sizeable instanceof Composite)) throw Err_.new_wo_type("cannot remove sub from control"); + Control sub_as_swt = ((Swt_control)sub).Under_control(); + sub_as_swt.dispose(); // SWT: no way to officially remove sub from control; can only dispose + } + + @Override public ColorAdp BackColor() {return To_color_gfui(viewable.getBackground());} + @Override public void BackColor_set(ColorAdp v) {viewable.setBackground(To_color_swt(viewable, v));} + @Override public ColorAdp ForeColor() {return To_color_gfui(viewable.getForeground());} + @Override public void ForeColor_set(ColorAdp v) {viewable.setForeground(To_color_swt(viewable, v));} + @Override public String TipText() {return viewable.getToolTipText();} + @Override public void TipText_set(String v) {viewable.setToolTipText(v);} + @Override public FontAdp TextFont() { + if (text_font == null) + text_font = Swt_core__base.Control_font_get(viewable.getFont(), this); + return text_font; + } + @Override public void TextFont_set(FontAdp v) { + Swt_core__base.Control_font_set(v, this, viewable); + this.text_font = v; + } + @Override public void Select_exec() {viewable.setFocus();} + @Override public boolean Focus_has() {return viewable.isFocusControl();} + @Override public void Focus() { + if (Focus_able()) + viewable.forceFocus(); + } + + @Override public boolean Focus_able() {return focus_able;} + @Override public void Focus_able_(boolean v) {focus_able = v;} + @Override public int Focus_index() {return focus_index;} + @Override public void Focus_index_set(int v) {focus_index = v;} + @Override public void Zorder_front() {} // TODO.FUTURE: Canvas c; c.moveAbove(arg0); + @Override public void Zorder_back() {} // TODO.FUTURE: Canvas c; c.moveBelow(arg0); + + protected static ColorAdp To_color_gfui(Color v) {return ColorAdp_.new_(0, v.getRed(), v.getGreen(), v.getBlue());} + protected static Color To_color_swt(Control control, ColorAdp v) {return new Color(control.getDisplay(), v.Red(), v.Green(), v.Blue());} + private static FontAdp Control_font_get(Font font, GxwCore_base owner) { + FontData fontData = font.getFontData()[0]; + FontAdp rv = FontAdp.new_(fontData.getName(), fontData.getHeight(), FontStyleAdp_.lang_(fontData.getStyle())); // NOTE: swt style constants match swing + rv.OwnerGxwCore_(owner); + return rv; + } + private static void Control_font_set(FontAdp font, GxwCore_base owner, Control control) { + font.OwnerGxwCore_(owner); + FontData fontData = new FontData(font.Name(), (int)font.Size(), font.Style().Val()); + Font rv = new Font(control.getDisplay(), fontData); + control.setFont(rv); + } + private static void Layout_mgr_set(Control control, Swt_layout_mgr v) { + Swt_layout_mgr__grid gfui_layout = (Swt_layout_mgr__grid)v; + GridLayout swt_layout = new GridLayout(); + swt_layout.numColumns = gfui_layout.Cols(); + if (gfui_layout.Margin_w() > -1) swt_layout.marginWidth = gfui_layout.Margin_w(); + if (gfui_layout.Margin_h() > -1) swt_layout.marginHeight = gfui_layout.Margin_h(); + if (gfui_layout.Spacing_w() > -1) swt_layout.horizontalSpacing = gfui_layout.Spacing_w(); + if (gfui_layout.Spacing_h() > -1) swt_layout.verticalSpacing = gfui_layout.Spacing_h(); + + Composite control_as_composite = (Composite)control; + control_as_composite.setLayout(swt_layout); + } + private static void Layout_data_set(Control control, Swt_layout_data v) { + Swt_layout_data__grid gfui_data = (Swt_layout_data__grid)v; + + GridData swt_data = new GridData(); + if (gfui_data.Grab_excess_w()) swt_data.grabExcessHorizontalSpace = gfui_data.Grab_excess_w(); + if (gfui_data.Grab_excess_h()) swt_data.grabExcessVerticalSpace = gfui_data.Grab_excess_h(); + if (gfui_data.Align_w() != Swt_layout_data__grid.Align__null) swt_data.horizontalAlignment = gfui_data.Align_w(); + if (gfui_data.Align_h() != Swt_layout_data__grid.Align__null) swt_data.verticalAlignment = gfui_data.Align_h(); + if (gfui_data.Hint_w() > -1) swt_data.widthHint = gfui_data.Hint_w(); + if (gfui_data.Hint_h() > -1) swt_data.heightHint = gfui_data.Hint_h(); + if (gfui_data.Min_w() > -1) swt_data.minimumWidth = gfui_data.Min_w(); + if (gfui_data.Min_h() > -1) swt_data.minimumHeight = gfui_data.Min_h(); + + control.setLayoutData(swt_data); + control.getParent().layout(true, true); + } +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_core__basic.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_core__basic.java index a27517de8..2ba6a279b 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_core__basic.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_core__basic.java @@ -13,3 +13,16 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import org.eclipse.swt.widgets.*; +import gplx.gfui.controls.gxws.*; + +class Swt_core__basic extends Swt_core__base { + protected final Control control; + public Swt_core__basic(Control control) { + super(control, control); + this.control = control; + } + @Override public void Invalidate() {control.redraw(); control.update();} + @Override public void Dispose() {control.dispose();} +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_core__dual.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_core__dual.java index a27517de8..61cd7afea 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_core__dual.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_core__dual.java @@ -13,3 +13,37 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import org.eclipse.swt.widgets.*; +import org.eclipse.swt.graphics.Color; +import gplx.gfui.controls.gxws.*; +import gplx.gfui.draws.ColorAdp; + +class Swt_core__dual extends Swt_core__base { + private final Control outer, inner; + private final int inner_adj_w, inner_adj_h; + public Swt_core__dual(Composite outer, Control inner, int inner_adj_w, int inner_adj_h) { + super(outer, inner); + this.outer = outer; this.inner = inner; + this.inner_adj_w = inner_adj_w; this.inner_adj_h = inner_adj_h; + } + @Override public void Width_set(int v) {super.Width_set(v); Swt_control_.W_set(outer, v + inner_adj_w);} + @Override public void Height_set(int v) {super.Height_set(v); Swt_control_.H_set(outer, v + inner_adj_h);} + @Override public void Size_set(SizeAdp v) {super.Size_set(v); Swt_control_.Size_set(inner, v.Width() + inner_adj_w, v.Height() + inner_adj_h);} + @Override public void Rect_set(RectAdp v) {super.Rect_set(v); Swt_control_.Size_set(inner, v.Width() + inner_adj_w, v.Height() + inner_adj_h);} + @Override public ColorAdp BackColor() {return To_color_gfui(inner.getBackground());} + @Override public void BackColor_set(ColorAdp v) { + Color color = To_color_swt(inner, v); + inner.setBackground(color); + outer.setBackground(color); + } + @Override public ColorAdp ForeColor() {return To_color_gfui(inner.getForeground());} + @Override public void ForeColor_set(ColorAdp v) { + Color color = To_color_swt(inner, v); + inner.setForeground(color); + outer.setForeground(color); + } + + @Override public void Invalidate() {outer.update(); inner.update();} + @Override public void Dispose() {outer.dispose(); inner.dispose();} +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_core__frames.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_core__frames.java index a27517de8..9b7ae1d60 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_core__frames.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_core__frames.java @@ -13,3 +13,103 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +import gplx.gfui.controls.gxws.*; +import gplx.gfui.draws.*; +import gplx.gfui.kits.*; +import gplx.gfui.kits.core.Swt_kit; +import gplx.gfui.layouts.swts.*; + +class Swt_core__frames extends Swt_core__base { + private final Composite outer; + private final Control inner; + private final Swt_frame_itm[] frames; + private final int frames_len; + public Swt_core__frames(final Swt_text_w_border text_w_border, final Composite outer, final Swt_frame_itm[] frames) { + super(outer, frames[frames.length - 1].Item()); + this.frames = frames; + this.frames_len = frames.length; + this.outer = outer; + this.inner = frames[frames_len - 1].Item(); + + // listener needed for layout changes to propagate to Gxw size methods + outer.addListener (SWT.Resize, new Listener() { + public void handleEvent (Event e) { + Rectangle outer_rect = outer.getBounds(); + Frames_w_set(outer_rect.width); + Frames_h_set(outer_rect.height); + + // vertically center + int text_size = frames[frames_len - 1].Item().getSize().y; + text_w_border.margins_t = (outer_rect.height - text_size) / 2; + // Tfds.Write(outer_rect.height, frames[frames_len - 2].Item().getSize().y, text_size, text_w_border.margins_t, frames[frames_len - 1].Item().getToolTipText()); + } + }); + } + @Override public void Width_set(int v) {super.Width_set(v); Frames_w_set(v);} + @Override public void Height_set(int v) {super.Height_set(v); Frames_h_set(v);} + @Override public void Size_set(SizeAdp v) {super.Size_set(v); Frames_size_set(v);} + @Override public void Rect_set(RectAdp v) {super.Rect_set(v); Frames_size_set(v.Size());} + @Override public void BackColor_set(ColorAdp v) { + Color color = Swt_core__base.To_color_swt(outer, v); + for (int i = 0; i < frames_len; i++) { + frames[i].Item().setBackground(color); + } + } + @Override public void ForeColor_set(ColorAdp v) { + Color color = Swt_core__base.To_color_swt(outer, v); + for (int i = 0; i < frames_len; i++) + frames[i].Item().setForeground(color); + } + @Override public void Controls_add(GxwElem sub) {throw Err_.new_unimplemented();} + @Override public void Controls_del(GxwElem sub) {} + @Override public void Invalidate() { + inner.redraw(); + inner.update(); + } + @Override public void Dispose() {outer.dispose(); inner.dispose();} + private void Frames_w_set(int v) { + for (int i = 0; i < frames_len; i++) + frames[i].Rect_set(v, this.Height()); + } + private void Frames_h_set(int v) { + for (int i = 0; i < frames_len; i++) + frames[i].Rect_set(this.Width(), v); + } + private void Frames_size_set(SizeAdp v) { + for (int i = 0; i < frames_len; i++) + frames[i].Rect_set(v.Width(), v.Height()); + } +} +interface Swt_frame_itm { + Control Item(); + void Rect_set(int w, int h); +} +class Swt_frame_itm__manual implements Swt_frame_itm { + private final Control control; private final int x, y, w, h; + public Swt_frame_itm__manual(Control control, int x, int y, int w, int h) { + this.control = control; this.x = x; this.y = y; this.w = w; this.h = h; + } + public Control Item() {return control;} + public void Rect_set(int new_w, int new_h) { + Swt_control_.Rect_set(control, x, y, new_w + w, new_h + h); + } +} +class Swt_frame_itm__center_v implements Swt_frame_itm { + private final Control control; private final Swt_text_w_border margin_owner; + public Swt_frame_itm__center_v(Control control, Swt_text_w_border margin_owner) { + this.control = control; + this.margin_owner = margin_owner; + } + public Control Item() {return control;} + public void Rect_set(int new_w, int new_h) { + int margin_t = margin_owner.margins_t; + int margin_b = margin_owner.margins_b; + // Tfds.Write(margin_t, margin_b, new_h - (margin_t + margin_b), control.getToolTipText()); + Swt_control_.Rect_set(control, 0, margin_t, new_w, new_h - (margin_t + margin_b)); + } +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_core_lnrs.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_core_lnrs.java index a27517de8..ea3ff44c9 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_core_lnrs.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_core_lnrs.java @@ -13,3 +13,251 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.Byte_ascii; + +import gplx.Enm_; +import gplx.Gfo_evt_mgr_; +import gplx.Gfo_invk_; +import gplx.GfoMsg_; +import gplx.GfsCtx; +import gplx.String_; +import gplx.Tfds; +import gplx.core.bits.Bitmask_; +import gplx.core.envs.Op_sys; +import gplx.core.envs.Op_sys_; +import gplx.gfui.controls.gxws.GxwElem; +import gplx.gfui.controls.standards.Gfui_html; +import gplx.gfui.controls.windows.GfuiWin; +import gplx.gfui.ipts.*; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.ToolItem; + +class Swt_lnr_show implements Listener { + boolean shown = false; + @Override public void handleEvent(Event ev) { + if (shown) return; + win.Opened(); + } + public Swt_lnr_show(Swt_win win) {this.win = win;} Swt_win win; +} +class Swt_lnr_resize implements Listener { + @Override public void handleEvent(Event ev) { +// win.Host().SizeChangedCbk(); + Gfo_evt_mgr_.Pub((GfuiWin)win.Host(), Gfui_html.Evt_win_resized); + } + public Swt_lnr_resize(Swt_win win) {this.win = win;} Swt_win win; +} +class Swt_lnr_traverse implements Listener { + @Override public void handleEvent(Event e) { + if (e.detail == SWT.TRAVERSE_ESCAPE) + e.doit = false; + } +} +class Swt_lnr_key implements KeyListener { + public Swt_lnr_key(GxwElem elem) {this.elem = elem;} private GxwElem elem; + @Override public void keyPressed(KeyEvent ev) { + IptEvtDataKey ipt_data = To_gfui(ev, Bool_.Y); + + // cancel if handled; note order MUST be "delegate || ipt_data.Handled", not vice-versa + if (!elem.Host().KeyDownCbk(ipt_data) || ipt_data.Handled()) + ev.doit = false; + } + @Override public void keyReleased(KeyEvent ev) { + IptEvtDataKey ipt_data = To_gfui(ev, Bool_.N); + + // cancel if handled; note order MUST be "delegate || ipt_data.Handled", not vice-versa + if (!elem.Host().KeyUpCbk(ipt_data) || ipt_data.Handled()) + ev.doit = false; + } + private IptEvtDataKey To_gfui(KeyEvent ev, boolean is_keydown) { + // convert codes from SWT keycodes to SWING / .NET style; note that SWT uses keycode values similar to ASCII values + int val = ev.keyCode; + switch (val) { + // letters; lowercase keys are transmitted as ascii value, instead of key value; EX: "a": SWT=97; SWING=65 + case Byte_ascii.Ltr_a: case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_e: + case Byte_ascii.Ltr_f: case Byte_ascii.Ltr_g: case Byte_ascii.Ltr_h: case Byte_ascii.Ltr_i: case Byte_ascii.Ltr_j: + case Byte_ascii.Ltr_k: case Byte_ascii.Ltr_l: case Byte_ascii.Ltr_m: case Byte_ascii.Ltr_n: case Byte_ascii.Ltr_o: + case Byte_ascii.Ltr_p: case Byte_ascii.Ltr_q: case Byte_ascii.Ltr_r: case Byte_ascii.Ltr_s: case Byte_ascii.Ltr_t: + case Byte_ascii.Ltr_u: case Byte_ascii.Ltr_v: case Byte_ascii.Ltr_w: case Byte_ascii.Ltr_x: case Byte_ascii.Ltr_y: case Byte_ascii.Ltr_z: + val -= 32; + break; + + // // numpad numbers + // case SWT.KEYPAD_0: val = IptKey_.Numpad_0.Val(); break; + // case SWT.KEYPAD_1: val = IptKey_.Numpad_1.Val(); break; + // case SWT.KEYPAD_2: val = IptKey_.Numpad_2.Val(); break; + // case SWT.KEYPAD_3: val = IptKey_.Numpad_3.Val(); break; + // case SWT.KEYPAD_4: val = IptKey_.Numpad_4.Val(); break; + // case SWT.KEYPAD_5: val = IptKey_.Numpad_5.Val(); break; + // case SWT.KEYPAD_6: val = IptKey_.Numpad_6.Val(); break; + // case SWT.KEYPAD_7: val = IptKey_.Numpad_7.Val(); break; + // case SWT.KEYPAD_8: val = IptKey_.Numpad_8.Val(); break; + // case SWT.KEYPAD_9: val = IptKey_.Numpad_9.Val(); break; + // + // // numpad symbols + // case SWT.KEYPAD_MULTIPLY: val = IptKey_.Numpad_multiply.Val(); break; + // case SWT.KEYPAD_ADD: val = IptKey_.Numpad_add.Val(); break; + // case SWT.KEYPAD_SUBTRACT: val = IptKey_.Numpad_subtract.Val(); break; + // case SWT.KEYPAD_DECIMAL: val = IptKey_.Numpad_decimal.Val(); break; + // case SWT.KEYPAD_DIVIDE: val = IptKey_.Numpad_divide.Val(); break; + // case SWT.KEYPAD_CR: val = IptKey_.Numpad_enter.Val(); break; + + // numpad numbers + case SWT.KEYPAD_0: val = IptKey_.D0.Val(); break; + case SWT.KEYPAD_1: val = IptKey_.D1.Val(); break; + case SWT.KEYPAD_2: val = IptKey_.D2.Val(); break; + case SWT.KEYPAD_3: val = IptKey_.D3.Val(); break; + case SWT.KEYPAD_4: val = IptKey_.D4.Val(); break; + case SWT.KEYPAD_5: val = IptKey_.D5.Val(); break; + case SWT.KEYPAD_6: val = IptKey_.D6.Val(); break; + case SWT.KEYPAD_7: val = IptKey_.D7.Val(); break; + case SWT.KEYPAD_8: val = IptKey_.D8.Val(); break; + case SWT.KEYPAD_9: val = IptKey_.D9.Val(); break; + + // numpad symbols + case SWT.KEYPAD_MULTIPLY: val = IptKey_.Numpad_multiply.Val(); break; + case SWT.KEYPAD_ADD: val = IptKey_.Equal.Val(); break; + case SWT.KEYPAD_SUBTRACT: val = IptKey_.Minus.Val(); break; + case SWT.KEYPAD_DECIMAL: val = IptKey_.Period.Val(); break; + case SWT.KEYPAD_DIVIDE: val = IptKey_.Numpad_divide.Val(); break; + case SWT.KEYPAD_CR: val = IptKey_.Enter.Val(); break; + + // function keys + case SWT.F1: val = IptKey_.F1.Val(); break; + case SWT.F2: val = IptKey_.F2.Val(); break; + case SWT.F3: val = IptKey_.F3.Val(); break; + case SWT.F4: val = IptKey_.F4.Val(); break; + case SWT.F5: val = IptKey_.F5.Val(); break; + case SWT.F6: val = IptKey_.F6.Val(); break; + case SWT.F7: val = IptKey_.F7.Val(); break; + case SWT.F8: val = IptKey_.F8.Val(); break; + case SWT.F9: val = IptKey_.F9.Val(); break; + case SWT.F10: val = IptKey_.F10.Val(); break; + case SWT.F11: val = IptKey_.F11.Val(); break; + case SWT.F12: val = IptKey_.F12.Val(); break; + + // SWT=13; SWING=10; note that Cr maps to "enter key" + case Byte_ascii.Cr: val = IptKey_.Enter.Val(); break; + + case SWT.INSERT: val = IptKey_.Insert.Val(); break; + case 127: val = IptKey_.Delete.Val(); break; + + // meta + case SWT.PAUSE: val = IptKey_.Pause.Val(); break; + case SWT.PRINT_SCREEN: val = IptKey_.PrintScreen.Val(); break; + + // nav keys + case SWT.ARROW_UP: val = IptKey_.Up.Val(); break; + case SWT.ARROW_DOWN: val = IptKey_.Down.Val(); break; + case SWT.ARROW_LEFT: val = IptKey_.Left.Val(); break; + case SWT.ARROW_RIGHT: val = IptKey_.Right.Val(); break; + case SWT.PAGE_UP: val = IptKey_.PageUp.Val(); break; + case SWT.PAGE_DOWN: val = IptKey_.PageDown.Val(); break; + case SWT.HOME: val = IptKey_.Home.Val(); break; + case SWT.END: val = IptKey_.End.Val(); break; + + // locks + case SWT.CAPS_LOCK: val = IptKey_.CapsLock.Val(); break; + case SWT.NUM_LOCK: val = IptKey_.NumLock.Val(); break; + case SWT.SCROLL_LOCK: val = IptKey_.ScrollLock.Val(); break; + + // symbols; ASCII; no SWT const + case 39: val = IptKey_.Quote.Val(); break; + case 44: val = IptKey_.Comma.Val(); break; + case 45: val = IptKey_.Minus.Val(); break; + case 46: val = IptKey_.Period.Val(); break; + case 47: val = IptKey_.Slash.Val(); break; + case 59: val = IptKey_.Semicolon.Val(); break; + case 61: val = IptKey_.Equal.Val(); break; + case 91: val = IptKey_.OpenBracket.Val(); break; + case 93: val = IptKey_.CloseBracket.Val(); break; + case 96: val = IptKey_.Tick.Val(); break; + + // modifiers + case SWT.CTRL: val = IptKey_.Ctrl.Val(); break; + case SWT.ALT: val = IptKey_.Alt.Val(); break; + case SWT.SHIFT: val = IptKey_.Shift.Val(); break; + + // map Mac OS X cmd to Ctrl + case SWT.COMMAND: val = IptKey_.Ctrl.Val(); break; + } + + // handle mod keys + int swt_ctrl = Op_sys.Cur().Tid_is_osx() ? SWT.COMMAND : SWT.CTRL; + val = Handle_modifier(ev, is_keydown, val, swt_ctrl , IptKey_.Ctrl.Val()); + val = Handle_modifier(ev, is_keydown, val, SWT.ALT , IptKey_.Alt.Val()); + val = Handle_modifier(ev, is_keydown, val, SWT.SHIFT , IptKey_.Shift.Val()); + // Tfds.Write(String_.Format("val={0} keydown={1} keyCode={2} stateMask={3} keyLocation={4} character={5}", val, is_keydown, ev.keyCode, ev.stateMask, ev.keyLocation, ev.character)); + return IptEvtDataKey.int_(val); + } + private static int Handle_modifier(KeyEvent ev, boolean is_keydown, int val, int swt, int swing) { + // Conversion table for debugging + // + // ------------------------- + // | code | SWT | SWING | + // |-------|-------|-------| + // | 65536 | ALT | SHIFT | + // |131072 | SHIFT | CTRL | + // |262144 | CTRL | ALT | + // ------------------------- + + // Also, when debugging, note that ev.stateMask is always the value at the start of the event + // + // For example, if ctrl is pressed and nothing is held + // * if is_keydown = y, then ev.stateMask is 0 (none) + // * if is_keydown = n, then ev.stateMask is 262144 (ctrl) + // Note that ev.keyCode is 262144 (ctrl) in both examples + // + // However, if ctrl is pressed and shift is already held: + // * if is_keydown = y, then ev.stateMask is 65536 (shift) + // * if is_keydown = n, then ev.stateMask is 327680 (shift + ctrl) + // Note that ev.keyCode is 262144 (ctrl) in both examples as well. + + // if SWT modifier is present, return val with SWING modifier; else, just return val + return Bitmask_.Has_int(ev.stateMask, swt) ? val | swing : val; + } + public static boolean Has_ctrl(int val) {return Bitmask_.Has_int(val, SWT.CTRL);} +} +class Swt_lnr_mouse implements MouseListener { + public Swt_lnr_mouse(GxwElem elem) {this.elem = elem;} GxwElem elem; + @Override public void mouseDown(MouseEvent ev) {elem.Host().MouseDownCbk(XtoMouseData(ev));} + @Override public void mouseUp(MouseEvent ev) {elem.Host().MouseUpCbk(XtoMouseData(ev));} + @Override public void mouseDoubleClick(MouseEvent ev) {} + IptEvtDataMouse XtoMouseData(MouseEvent ev) { + IptMouseBtn btn = null; + switch (ev.button) { + case 1: btn = IptMouseBtn_.Left; break; + case 2: btn = IptMouseBtn_.Middle; break; + case 3: btn = IptMouseBtn_.Right; break; + case 4: btn = IptMouseBtn_.X1; break; + case 5: btn = IptMouseBtn_.X2; break; + } + return IptEvtDataMouse.new_(btn, IptMouseWheel_.None, ev.x, ev.y); + } +// private static int X_to_swing(int v) { +// switch (v) { +// case gplx.gfui.IptMouseBtn_.Tid_left : return java.awt.event.InputEvent.BUTTON1_MASK; +// case gplx.gfui.IptMouseBtn_.Tid_middle : return java.awt.event.InputEvent.BUTTON2_MASK; +// case gplx.gfui.IptMouseBtn_.Tid_right : return java.awt.event.InputEvent.BUTTON3_MASK; +// default : throw Err_.unhandled(v); +// } +// } +} +class Swt_lnr_toolitem implements Listener { + public Swt_lnr_toolitem(ToolItem itm, GxwElem elem) {this.itm = itm; this.elem = elem;} ToolItem itm; GxwElem elem; + @Override public void handleEvent(Event arg0) { + Rectangle rect = itm.getBounds(); + elem.Host().MouseUpCbk(IptEvtDataMouse.new_(IptMouseBtn_.Left, IptMouseWheel_.None, rect.x, rect.y)); + } +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_dlg_dir.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_dlg_dir.java index a27517de8..937bce877 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_dlg_dir.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_dlg_dir.java @@ -13,3 +13,17 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.kits.core.*; +import org.eclipse.swt.widgets.DirectoryDialog; +import org.eclipse.swt.widgets.Shell; +public class Swt_dlg_dir implements Gfui_dlg_dir { + private final DirectoryDialog under; + public Swt_dlg_dir(Shell shell) { + this.under = new DirectoryDialog(shell); + } + public Gfui_dlg_dir Init_text_(String v) {under.setText(v); return this;} + public Gfui_dlg_dir Init_msg_(String v) {under.setMessage(v); return this;} + public Gfui_dlg_dir Init_dir_(Io_url v) {under.setFilterPath(v.Xto_api()); return this;} + public String Ask() {return under.open();} +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_dlg_file.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_dlg_file.java index a27517de8..72b08750f 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_dlg_file.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_dlg_file.java @@ -13,3 +13,26 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.kits.*; +import gplx.gfui.kits.core.Gfui_dlg_file; +import gplx.gfui.kits.core.Gfui_kit_; + +import org.eclipse.swt.widgets.*; +import org.eclipse.swt.SWT; +public class Swt_dlg_file implements Gfui_dlg_file { + private FileDialog under; + public Swt_dlg_file(byte type, Shell shell) { + int file_dialog_type + = type == Gfui_kit_.File_dlg_type_save + ? SWT.SAVE + : SWT.OPEN + ; + under = new FileDialog(shell, file_dialog_type); + } + public Gfui_dlg_file Init_msg_(String v) {under.setText(v); return this;} + public Gfui_dlg_file Init_file_(String v) {under.setFileName(v); return this;} + public Gfui_dlg_file Init_dir_(Io_url v) {under.setFilterPath(v.Xto_api()); return this;} + public Gfui_dlg_file Init_exts_(String... v) {under.setFilterExtensions(v); return this;} + public String Ask() {return under.open();} +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_dlg_msg.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_dlg_msg.java index a27517de8..b5d790c02 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_dlg_msg.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_dlg_msg.java @@ -13,3 +13,72 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.*; +import gplx.gfui.kits.*; +import gplx.gfui.kits.core.Gfui_dlg_msg; +import gplx.gfui.kits.core.Gfui_dlg_msg_; + +import org.eclipse.swt.widgets.*; +import org.eclipse.swt.SWT; + +public class Swt_dlg_msg implements Gfui_dlg_msg, Runnable { + public Swt_dlg_msg(Shell shell) {this.shell = shell;} Shell shell; + public Gfui_dlg_msg Init_msg_(String v) {msg = v; return this;} String msg; + public Gfui_dlg_msg Init_ico_(int v) {ico = Xto_swt_ico(v); return this;} int ico = -1; + public Gfui_dlg_msg Init_btns_(int... ary) { + int ary_len = ary.length; + btns = -1; + for (int i = 0; i < ary_len; i++) { + int swt_btn = Xto_swt_btn(ary[i]); + if (btns == -1) btns = swt_btn; + else btns |= swt_btn; + } + return this; + } int btns = -1; + public int Ask_rslt; + @Override public void run() { + Ask_rslt = this.Ask(); + } + public boolean Ask(int expd) {return Ask() == expd;} + public int Ask() { + int ctor_ico = ico == -1 ? SWT.ICON_INFORMATION : ico; + int ctor_btn = btns == -1 ? SWT.OK : btns; + MessageBox mb = new MessageBox(shell, ctor_ico | ctor_btn); + if (msg != null) mb.setMessage(msg); + int rv = mb.open(); + return Xto_gfui_btn(rv); + } + int Xto_swt_ico(int v) { + switch (v) { + case Gfui_dlg_msg_.Ico_error: return SWT.ICON_ERROR; + case Gfui_dlg_msg_.Ico_information: return SWT.ICON_INFORMATION; + case Gfui_dlg_msg_.Ico_question: return SWT.ICON_QUESTION; + case Gfui_dlg_msg_.Ico_warning: return SWT.ICON_WARNING; + case Gfui_dlg_msg_.Ico_working: return SWT.ICON_WORKING; + default: throw Err_.new_unhandled(v); + } + } + int Xto_swt_btn(int v) { + switch (v) { + case Gfui_dlg_msg_.Btn_ok: return SWT.OK; + case Gfui_dlg_msg_.Btn_yes: return SWT.YES; + case Gfui_dlg_msg_.Btn_no: return SWT.NO; + case Gfui_dlg_msg_.Btn_ignore: return SWT.IGNORE; + case Gfui_dlg_msg_.Btn_abort: return SWT.ABORT; + case Gfui_dlg_msg_.Btn_cancel: return SWT.CANCEL; + default: throw Err_.new_unhandled(v); + } + } + int Xto_gfui_btn(int v) { + switch (v) { + case SWT.OK: return Gfui_dlg_msg_.Btn_ok; + case SWT.YES: return Gfui_dlg_msg_.Btn_yes; + case SWT.NO: return Gfui_dlg_msg_.Btn_no; + case SWT.IGNORE: return Gfui_dlg_msg_.Btn_ignore; + case SWT.ABORT: return Gfui_dlg_msg_.Btn_abort; + case SWT.CANCEL: return Gfui_dlg_msg_.Btn_cancel; + default: throw Err_.new_unhandled(v); + } + } +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_grp.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_grp.java index a27517de8..449ac7a1b 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_grp.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_grp.java @@ -13,3 +13,48 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.*; +import gplx.core.threads.Thread_adp_; + +import org.eclipse.swt.*; +import org.eclipse.swt.custom.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.layout.*; +import org.eclipse.swt.widgets.*; + +import gplx.gfui.controls.gxws.GxwCbkHost; +import gplx.gfui.controls.gxws.GxwCore_base; +import gplx.gfui.controls.gxws.Gxw_grp; +import gplx.gfui.draws.*; +import gplx.gfui.kits.core.GfuiInvkCmd; +import gplx.gfui.kits.core.Swt_kit; + +public class Swt_grp implements Gxw_grp, Swt_control, FocusListener, Gfo_evt_mgr_owner { +// private GfuiInvkCmd cmd_sync; + private Composite composite; + public Swt_grp(Swt_kit kit, Swt_control owner, Keyval_hash ctorArgs) { + this.kit = kit; + composite = new Composite(owner.Under_composite(), SWT.NONE); + core = new Swt_core__basic(composite); + composite.addKeyListener(new Swt_lnr_key(this)); + composite.addMouseListener(new Swt_lnr_mouse(this)); + } + public Swt_kit Kit() {return kit;} private Swt_kit kit; + @Override public Control Under_control() {return composite;} + @Override public Composite Under_composite() {return composite;} + @Override public Control Under_menu_control() {return composite;} + public Gfo_evt_mgr Evt_mgr() {return ev_mgr;} private Gfo_evt_mgr ev_mgr; + public void Evt_mgr_(Gfo_evt_mgr v) {ev_mgr = v;} + @Override public GxwCore_base Core() {return core;} GxwCore_base core; + @Override public GxwCbkHost Host() {return host;} @Override public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host; + @Override public String TextVal() {return "not implemented";} + @Override public void TextVal_set(String v) {} + @Override public void EnableDoubleBuffering() {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + return Gfo_invk_.Rv_unhandled; + } + @Override public void focusGained(FocusEvent arg0) {} + @Override public void focusLost(FocusEvent arg0) {} +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_html.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_html.java index a27517de8..e56cd013b 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_html.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_html.java @@ -13,3 +13,301 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.core.envs.System_; +import gplx.core.primitives.*; +import gplx.core.threads.Thread_adp_; +import gplx.gfui.controls.elems.GfuiElem; +import gplx.gfui.controls.gxws.GxwCbkHost; +import gplx.gfui.controls.gxws.GxwCore_base; +import gplx.gfui.controls.gxws.GxwElem; +import gplx.gfui.controls.gxws.Gxw_html; +import gplx.gfui.controls.gxws.Gxw_html_load_tid_; +import gplx.gfui.controls.standards.Gfui_html; +import gplx.gfui.controls.standards.Gfui_tab_mgr; +import gplx.gfui.draws.ColorAdp; +import gplx.gfui.draws.ColorAdp_; +import gplx.gfui.ipts.*; +import gplx.gfui.kits.core.Swt_kit; + +import java.security.acl.Owner; +import gplx.*; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.browser.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; +import java.security.acl.Owner; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.browser.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +public class Swt_html implements Gxw_html, Swt_control, FocusListener, Gfo_evt_mgr_owner { + private Swt_html_lnr_location lnr_location; private Swt_html_lnr_status lnr_status; + public Swt_html(Swt_kit kit, Swt_control owner_control, Keyval_hash ctorArgs) { + this.kit = kit; + lnr_location = new Swt_html_lnr_location(this); + lnr_status = new Swt_html_lnr_status(this); + Object browser_tid_obj = ctorArgs.Get_val_or(Swt_kit.Cfg_Html_BrowserType, null); + this.browser_tid = browser_tid_obj == null ? Browser_tid_none : Int_.Cast(browser_tid_obj); + browser = new Browser(owner_control.Under_composite(), browser_tid); + core = new Swt_core_cmds_html(this, browser); + browser.addKeyListener(new Swt_lnr_key(this)); + browser.addMouseListener(new Swt_html_lnr_mouse(this, browser, kit)); + browser.addLocationListener(lnr_location); + browser.addProgressListener(new Swt_html_lnr_progress(this)); + browser.addStatusTextListener(lnr_status); + browser.addFocusListener(this); + browser.addTitleListener(new Swt_html_lnr_title(this)); + browser.addMouseWheelListener(new Swt_html_lnr_wheel(this)); + + // browser.addOpenWindowListener(new Swt_open_window_listener(this)); // handle target='blank' + // browser.addTraverseListener(new Swt_html_lnr_Traverse(this)); + } + public Swt_kit Kit() {return kit;} private Swt_kit kit; + public Gfo_evt_mgr Evt_mgr() {return ev_mgr;} private Gfo_evt_mgr ev_mgr; public void Evt_mgr_(Gfo_evt_mgr v) {ev_mgr = v;} + @Override public Control Under_control() {return browser;} private Browser browser; + @Override public Composite Under_composite() {return null;} + @Override public Control Under_menu_control() {return browser;} + public int Browser_tid() {return browser_tid;} private final int browser_tid; + public String Load_by_url_path() {return load_by_url_path;} private String load_by_url_path; + public void Html_doc_html_load_by_mem(String html) { + this.html_doc_html_load_tid = Gxw_html_load_tid_.Tid_mem; + this.load_by_url_path = null; + browser.setText(html); // DBG: Io_mgr.I.SaveFilStr(Io_url_.new_fil_("C:\\temp.txt"), s) + } + public void Html_doc_html_load_by_url(Io_url path, String html) { + this.html_doc_html_load_tid = Gxw_html_load_tid_.Tid_url; + this.load_by_url_path = path.To_http_file_str(); + Io_mgr.Instance.SaveFilStr(path, html); + browser.setUrl(path.Xto_api()); + } + public byte Html_doc_html_load_tid() {return html_doc_html_load_tid;} private byte html_doc_html_load_tid; + public void Html_doc_html_load_tid_(byte v) {html_doc_html_load_tid = v;} + public void Html_js_enabled_(boolean v) {browser.setJavascriptEnabled(v);} + public void Html_js_cbks_add(String func_name, Gfo_invk invk) {new Swt_html_func(browser, func_name, invk);} + public String Html_js_eval_script(String script) {return Eval_script_as_str(script);} + public Object Html_js_eval_script_as_obj(String script) {return Eval_script(script);} + public boolean Html_js_eval_proc_as_bool(String proc, Object... args) {return Bool_.Cast(Html_js_eval_proc_as_obj(proc, args));} + public String Html_js_eval_proc_as_str(String proc, Object... args) {return Object_.Xto_str_strict_or_null(Html_js_eval_proc_as_obj(proc, args));} + public String Html_js_send_json(String name, String data) { + String script = String_.Format("return {0}('{1}');", name, String_.Replace(data, "\n", "") ); + return (String)Eval_script(script); + } + private Object Html_js_eval_proc_as_obj(String proc, Object... args) { + Bry_bfr bfr = Bry_bfr_.New(); + bfr.Add_str_a7("return ").Add_str_u8(proc).Add_byte(Byte_ascii.Paren_bgn); + int args_len = args.length; + for (int i = 0; i < args_len; ++i) { + Object arg = args[i]; + if (i != 0) bfr.Add_byte(Byte_ascii.Comma); + boolean quote_val = true; + if ( Type_.Eq_by_obj(arg, Bool_.Cls_ref_type) + || Type_.Eq_by_obj(arg, Int_.Cls_ref_type) + || Type_.Eq_by_obj(arg, Long_.Cls_ref_type) + ) { + quote_val = false; + } + if (quote_val) bfr.Add_byte(Byte_ascii.Apos); + if (quote_val) + bfr.Add_str_u8(Escape_quote(Object_.Xto_str_strict_or_null_mark(arg))); + else + bfr.Add_obj_strict(arg); + if (quote_val) bfr.Add_byte(Byte_ascii.Apos); + } + bfr.Add_byte(Byte_ascii.Paren_end).Add_byte(Byte_ascii.Semic); + return Eval_script(bfr.To_str_and_clear()); + } + public static String Escape_quote(String v) { + String rv = v; + rv = String_.Replace(rv, "'", "\\'"); + rv = String_.Replace(rv, "\"", "\\\""); + rv = String_.Replace(rv, "\n", "\\n"); + return rv; + } + public void Html_invk_src_(Gfo_evt_itm invk) {lnr_location.Host_set(invk); lnr_status.Host_set(invk);} + public void Html_dispose() { + browser.dispose(); + delete_owner.SubElems().DelOrFail(delete_cur); // NOTE: must delete cur from owner, else new tab will fail after closing one; DATE:2014-07-09 + System_.Garbage_collect(); + } + private GfuiElem delete_owner, delete_cur; + public void Delete_elems_(GfuiElem delete_owner, GfuiElem delete_cur) {this.delete_owner = delete_owner; this.delete_cur = delete_cur;} // HACK: set owner / cur so delete can work; + @Override public GxwCore_base Core() {return core;} private GxwCore_base core; + @Override public GxwCbkHost Host() {return host;} @Override public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host; + @Override public String TextVal() {return browser.getText();} + @Override public void TextVal_set(String v) {browser.setText(v);} + @Override public void EnableDoubleBuffering() {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return Gfo_invk_.Rv_unhandled;} + private String Eval_script_as_str(String script) {return (String)Eval_script(script);} + public Object Eval_script(String script) { + eval_rslt.Clear(); + try { + eval_rslt.Result_set(browser.evaluate(script)); + return eval_rslt.Result(); + } + catch (Exception e) {eval_rslt.Error_set(e.getMessage()); return eval_rslt.Error();} + } private Swt_html_eval_rslt eval_rslt = new Swt_html_eval_rslt(); + @Override public void focusGained(FocusEvent arg0) {} + @Override public void focusLost(FocusEvent arg0) {} + public static final int + Browser_tid_none = SWT.NONE + , Browser_tid_mozilla = SWT.MOZILLA + , Browser_tid_webkit = SWT.WEBKIT + ; +} +class Swt_core_cmds_html extends Swt_core__basic { + public Swt_core_cmds_html(Swt_html html_box, Control control) {super(control);} + @Override public void Focus() { + if (Focus_able()) + control.forceFocus(); + } + @Override public void Select_exec() { + this.Focus(); + } +} +class Swt_html_lnr_traverse implements TraverseListener { + public Swt_html_lnr_traverse(Swt_html html_box) {} + @Override public void keyTraversed(TraverseEvent arg0) {} +} +class Swt_html_lnr_title implements TitleListener { + private Swt_html html_box; + public Swt_html_lnr_title(Swt_html html_box) {this.html_box = html_box;} + @Override public void changed(TitleEvent ev) { + try {UsrDlg_.Instance.Note(ev.title);} + catch (Exception e) {html_box.Kit().Ask_ok("xowa.swt.html_box", "title.fail", Err_.Message_gplx_full(e));} // NOTE: must catch error or will cause app to lock; currently called inside displaySync + } +} +class Swt_html_func extends BrowserFunction { + private final Gfo_invk invk; + private final Browser browser; + public Swt_html_func(Browser browser, String name, Gfo_invk invk) { + super (browser, name); + this.browser = browser; + this.invk = invk; + } + public Object function (Object[] args) { + try { + return gplx.gfui.controls.standards.Gfui_html.Js_args_exec(invk, args); + } + catch (Exception e) { + String rv = Err_.Message_gplx_full(e); + browser.execute("alert('" + Swt_html.Escape_quote(rv) + "')"); + return rv; + } + } +} +class Swt_html_lnr_status implements StatusTextListener { + public Swt_html_lnr_status(Swt_html html_box) {this.html_box = html_box;} private Swt_html html_box; + public void Host_set(Gfo_evt_itm host) {this.host = host;} Gfo_evt_itm host; + @Override public void changed(StatusTextEvent ev) { + if (html_box.Kit().Kit_mode__term()) return; // shutting down raises status changed events; ignore, else SWT exception thrown; DATE:2014-05-29 + String ev_text = ev.text; + String load_by_url_path = html_box.Load_by_url_path(); + if (load_by_url_path != null) ev_text = String_.Replace(ev_text, load_by_url_path, ""); // remove "C:/xowa/tab_1.html" +// if (String_.Has(ev_text, "Loading [MathJax]")) return; // suppress MathJax messages; // NOTE: disabled for 2.1 (which no longer outputs messages to status); DATE:2013-05-03 + try {if (host != null) Gfo_evt_mgr_.Pub_obj(host, Gfui_html.Evt_link_hover, "v", ev_text);} + catch (Exception e) {html_box.Kit().Ask_ok("xowa.gui.html_box", "status.fail", Err_.Message_gplx_full(e));} // NOTE: must catch error or will cause app to lock; currently called inside displaySync + } +} +class Swt_html_lnr_progress implements ProgressListener { + public Swt_html_lnr_progress(Swt_html html_box) {} + @Override public void changed(ProgressEvent arg0) {} + @Override public void completed(ProgressEvent arg0) { +// UsrDlg_._.Note("done"); + } +} +class Swt_html_lnr_location implements LocationListener { + public Swt_html_lnr_location(Swt_html html_box) {this.html_box = html_box;} private Swt_html html_box; + public void Host_set(Gfo_evt_itm host) {this.host = host;} private Gfo_evt_itm host; + @Override public void changed(LocationEvent arg) {Pub_evt(arg, Gfui_html.Evt_location_changed);} + @Override public void changing(LocationEvent arg) {Pub_evt(arg, Gfui_html.Evt_location_changing);} + private void Pub_evt(LocationEvent arg, String evt) { + String location = arg.location; + if (String_.Eq(location, "about:blank")) return; // location changing event fires once when page is loaded; ignore + if ( html_box.Browser_tid() == Swt_html.Browser_tid_webkit // webkit prefixes "about:blank" to anchors; causes TOC to fail when clicking on links; EX:about:blank#TOC1; DATE:2015-06-09 + && String_.Has_at_bgn(location, "about:blank")) { + location = String_.Mid(location, 11); // 11 = "about:blank".length + } + if ( html_box.Html_doc_html_load_tid() == Gxw_html_load_tid_.Tid_url // navigating to file://page.html will fire location event; ignore if url mode + && String_.Has_at_bgn(location, "file:") + && String_.Has_at_end(location, ".html") + ) + return; + try { + Gfo_evt_mgr_.Pub_obj(host, evt, "v", location); + arg.doit = false; // cancel navigation event, else there will be an error when trying to go to invalid location + } + catch (Exception e) {html_box.Kit().Ask_ok("xowa.gui.html_box", evt, Err_.Message_gplx_full(e));} // NOTE: must catch error or will cause app to lock; currently called inside displaySync + } +} +class Swt_html_lnr_mouse implements MouseListener { + private GxwElem elem; private Browser browser; private Swt_kit kit; + public Swt_html_lnr_mouse(GxwElem elem, Browser browser, Swt_kit kit) {this.elem = elem; this.browser = browser; this.kit = kit;} + @Override public void mouseDown(MouseEvent ev) { + if (Is_at_scrollbar_area()) return; + elem.Host().MouseDownCbk(XtoMouseData(ev)); + } + @Override public void mouseUp(MouseEvent ev) { + if (Is_at_scrollbar_area()) return; + elem.Host().MouseUpCbk(XtoMouseData(ev)); + } + private boolean Is_at_scrollbar_area() { + // WORKAROUND.SWT: SEE:NOTE_1:browser scrollbar and click + Point browser_size = browser.getSize(); + Point click_pos = kit.Swt_display().getCursorLocation(); + return click_pos.x >= browser_size.x - 12; + } + @Override public void mouseDoubleClick(MouseEvent ev) {} + IptEvtDataMouse XtoMouseData(MouseEvent ev) { + IptMouseBtn btn = null; + switch (ev.button) { + case 1: btn = IptMouseBtn_.Left; break; + case 2: btn = IptMouseBtn_.Middle; break; + case 3: btn = IptMouseBtn_.Right; break; + case 4: btn = IptMouseBtn_.X1; break; + case 5: btn = IptMouseBtn_.X2; break; + } + return IptEvtDataMouse.new_(btn, IptMouseWheel_.None, ev.x, ev.y); + } +} +class Swt_html_lnr_wheel implements MouseWheelListener { + private final Swt_html html_box; + public Swt_html_lnr_wheel(Swt_html html_box) { + this.html_box = html_box; + } + @Override public void mouseScrolled(MouseEvent evt) { + if (evt.stateMask == SWT.CTRL) { // ctrl held; + Gfo_evt_mgr_.Pub_obj(html_box, Gfui_html.Evt_zoom_changed, "clicks_is_positive", evt.count > 0); + } + } +} +//class Swt_open_window_listener implements OpenWindowListener { +// private final Swt_html html_box; +// public Swt_open_window_listener(Swt_html html_box) {this.html_box = html_box;} +// @Override public void open(WindowEvent arg0) { +// Tfds.Write(); +// } +//} +/* +NOTE_1:browser scrollbar and click +a click in the scrollbar area will raise a mouse-down/mouse-up event in content-editable mode +. a click should be consumed by the scrollbar and not have any effect elsewhere on the window +. instead, a click event is raised, and counted twice + 1) for the scroll bar this will scroll the area. + 2) for the window. if keyboard-focus is set on a link, then it will activate the link. + +swt does not expose any scrollbar information (visible, width), b/c the scrollbar is controlled by the underlying browser +so, assume: +. scrollbar is always present +. scrollbar has arbitrary width (currently 12) +. and discard if click is in this scrollbar area + +two issues still occur with the workaround +1) even if the scrollbar is not present, any click on the right-hand edge of the screen will be ignored +2) click -> hold -> move mouse over to left -> release; the mouse up should be absorbed, but it is not due to position of release +*/ diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_html_eval_rslt.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_html_eval_rslt.java index a27517de8..c58e76d89 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_html_eval_rslt.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_html_eval_rslt.java @@ -13,3 +13,10 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +public class Swt_html_eval_rslt { + public void Clear() {error = null; result = null;} + public boolean Result_pass() {return error == null;} + public Object Result() {return result;} public void Result_set(Object v) {result = v; error = null;} private Object result; + public String Error () {return error;} public void Error_set(String v) {error = v; result = null;} private String error; +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_img.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_img.java index a27517de8..e35f31a3f 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_img.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_img.java @@ -13,3 +13,39 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.RectAdp; +import gplx.gfui.SizeAdp; +import gplx.gfui.SizeAdp_; +import gplx.gfui.imgs.ImageAdp; +import gplx.gfui.kits.core.Gfui_kit; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; + +public class Swt_img implements ImageAdp { + public Swt_img(Gfui_kit kit, Image under, int w, int h) {this.kit = kit; this.under = under; this.width = w; this.height = h;} + public Gfui_kit Kit() {return kit;} Gfui_kit kit; + public SizeAdp Size() {if (size == null) size = SizeAdp_.new_(width, height); return size;} SizeAdp size; + public int Width() {return width;} int width; + public int Height() {return height;} int height; + public Io_url Url() {return url;} public ImageAdp Url_(Io_url v) {url = v; return this;} Io_url url = Io_url_.Empty; + public Object Under() {return under;} Image under; + public boolean Disposed() {return under.isDisposed();} + public void Rls() {under.dispose();} + public void SaveAsBmp(Io_url url) {throw Err_.new_unimplemented();} + public void SaveAsPng(Io_url url) {throw Err_.new_unimplemented();} + public ImageAdp Resize(int trg_w, int trg_h) {return Extract_image(0, 0, width, height, trg_w, trg_h);} + public ImageAdp Extract_image(RectAdp src_rect, SizeAdp trg_size) {return Extract_image(src_rect.X(), src_rect.Y(), src_rect.Width(), src_rect.Height(), trg_size.Width(), trg_size.Height());} + public ImageAdp Extract_image(int src_x, int src_y, int src_w, int src_h, int trg_w, int trg_h) { + Image trg_img = new Image(Display.getDefault(), trg_w, trg_h); + GC gc = new GC(trg_img); + gc.setAntialias(SWT.ON); + gc.setInterpolation(SWT.HIGH); + gc.drawImage(under, src_x, src_y, src_w, src_h, 0, 0, trg_w, trg_h); + gc.dispose(); + return new Swt_img(kit, trg_img, trg_w, trg_h); + } +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_lbl.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_lbl.java index a27517de8..f9f32a4f5 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_lbl.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_lbl.java @@ -13,3 +13,33 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.controls.gxws.GxwCbkHost; +import gplx.gfui.controls.gxws.GxwCore_base; +import gplx.gfui.controls.gxws.GxwElem; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; + +public class Swt_lbl implements GxwElem, Swt_control { + private Label lbl; + public Swt_lbl(Swt_control owner, Keyval_hash ctorArgs) { + lbl = new Label(owner.Under_composite(), SWT.CENTER); + core = new Swt_core__basic(lbl); + lbl.addKeyListener(new Swt_lnr_key(this)); + lbl.addMouseListener(new Swt_lnr_mouse(this)); + } + @Override public Control Under_control() {return lbl;} + @Override public Control Under_menu_control() {return lbl;} + @Override public String TextVal() {return lbl.getText();} @Override public void TextVal_set(String v) { + lbl.setText(v); + } + @Override public GxwCore_base Core() {return core;} GxwCore_base core; + @Override public GxwCbkHost Host() {return host;} @Override public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host; + @Override public Composite Under_composite() {return null;} + @Override public void EnableDoubleBuffering() {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return null;} +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_lnr__menu_detect.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_lnr__menu_detect.java index a27517de8..1b223c3c3 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_lnr__menu_detect.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_lnr__menu_detect.java @@ -13,3 +13,23 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.Err_; +import gplx.Gfo_evt_mgr_; +import gplx.gfui.controls.elems.GfuiElem; +import gplx.gfui.controls.elems.GfuiElemKeys; +import gplx.gfui.kits.core.Swt_kit; + +import org.eclipse.swt.events.MenuDetectEvent; +import org.eclipse.swt.events.MenuDetectListener; + +public class Swt_lnr__menu_detect implements MenuDetectListener { + private final Swt_kit kit; private final GfuiElem elem; + public Swt_lnr__menu_detect(Swt_kit kit, GfuiElem elem) {this.kit = kit; this.elem = elem;} + @Override public void menuDetected(MenuDetectEvent arg0) { + try {Gfo_evt_mgr_.Pub(elem, GfuiElemKeys.Evt_menu_detected);} + catch (Exception e) { + kit.Ask_ok("", "", "error during right-click; err=~{0}", Err_.Message_gplx_full(e)); + } + } +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_popup_grp.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_popup_grp.java index a27517de8..f62a2e392 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_popup_grp.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_popup_grp.java @@ -13,3 +13,207 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.*; +import gplx.gfui.controls.elems.GfuiElem; +import gplx.gfui.controls.windows.GfuiWin; +import gplx.gfui.imgs.ImageAdp; +import gplx.gfui.imgs.ImageAdp_; +import gplx.gfui.kits.core.Gfui_mnu_grp; +import gplx.gfui.kits.core.Gfui_mnu_itm; +import gplx.gfui.kits.core.Gfui_mnu_itm_; +import gplx.gfui.kits.core.Swt_kit; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.browser.Browser; +import org.eclipse.swt.events.MenuDetectEvent; +import org.eclipse.swt.events.MenuDetectListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Decorations; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swt.widgets.Shell; +public class Swt_popup_grp implements Gfui_mnu_grp { + private Decorations owner_win; private Control owner_box; private boolean menu_is_bar = false; + Swt_popup_grp(String root_key){this.root_key = root_key;} + @Override public int Tid() {return Gfui_mnu_itm_.Tid_grp;} + @Override public String Uid() {return uid;} private String uid = Gfui_mnu_itm_.Gen_uid(); + @Override public boolean Enabled() {return menu.getEnabled();} @Override public void Enabled_(boolean v) {menu.setEnabled(v);} + @Override public boolean Disposed() {return menu.isDisposed();} + @Override public String Text() {return menu_item.getText();} @Override public void Text_(String v) {menu_item.setText(v);} + @Override public ImageAdp Img() {return img;} @Override public void Img_(ImageAdp v) { + img = v; + if (v == ImageAdp_.Null) + menu_item.setImage(null); + else + menu_item.setImage((Image)v.Under()); + } private ImageAdp img; + @Override public boolean Selected() {return menu_item.getSelection();} @Override public void Selected_(boolean v) {menu_item.setSelection(v);} + public String Root_key() {return root_key;} private String root_key; + public Menu Under_menu() {return menu;} private Menu menu; + public MenuItem Under_menu_item() {return menu_item;} private MenuItem menu_item; + public Object Under() {return menu;} + @Override public void Itms_clear() { + menu.dispose(); + if (menu_is_bar) { + menu = new Menu(owner_win, SWT.BAR); + owner_win.setMenuBar(menu); + } + else { + menu = new Menu(owner_box); + owner_box.setMenu(menu); + } + } + @Override public Gfui_mnu_itm Itms_add_btn_cmd(String txt, ImageAdp img, Gfo_invk invk, String cmd) { + Swt_popup_itm itm = new Swt_popup_itm(menu); + itm.Text_(txt); + if (img != null) itm.Img_(img); + itm.Invk_set_cmd(invk, cmd); + return itm; + } + @Override public Gfui_mnu_itm Itms_add_btn_msg(String txt, ImageAdp img, Gfo_invk invk, Gfo_invk_root_wkr root_wkr, GfoMsg invk_msg) { + Swt_popup_itm itm = new Swt_popup_itm(menu); + itm.Text_(txt); + if (img != null) itm.Img_(img); + itm.Invk_set_msg(root_wkr, invk, invk_msg); + return itm; + } + @Override public Gfui_mnu_itm Itms_add_chk_msg(String txt, ImageAdp img, Gfo_invk invk, Gfo_invk_root_wkr root_wkr, GfoMsg msg_n, GfoMsg msg_y) { + Swt_popup_itm itm = new Swt_popup_itm(menu, SWT.CHECK); + itm.Text_(txt); + if (img != null) itm.Img_(img); + itm.Invk_set_chk(root_wkr, invk, msg_n, msg_y); + return itm; + } + @Override public Gfui_mnu_itm Itms_add_rdo_msg(String txt, ImageAdp img, Gfo_invk invk, Gfo_invk_root_wkr root_wkr, GfoMsg msg) { + Swt_popup_itm itm = new Swt_popup_itm(menu, SWT.RADIO); + itm.Text_(txt); + if (img != null) itm.Img_(img); + itm.Invk_set_msg(root_wkr, invk, msg); + return itm; + } + @Override public Gfui_mnu_itm Itms_add_separator() { + new MenuItem(menu, SWT.SEPARATOR); + return null; + } + @Override public Gfui_mnu_grp Itms_add_grp(String txt, ImageAdp img) { + Swt_popup_itm itm = new Swt_popup_itm(menu, SWT.CASCADE); + if (img != null) itm.Img_(img); + itm.Text_(txt); + + Swt_popup_grp grp = new_grp(root_key, owner_win); + grp.menu_item = itm.Under_menu_item(); + itm.Text_(txt); + + itm.Under_menu_item().setMenu(grp.Under_menu()); + return grp; + } + public static Swt_popup_grp new_popup(String key, GfuiElem owner_elem) { + Swt_popup_grp rv = new Swt_popup_grp(key); + Shell owner_win = cast_to_shell(owner_elem.OwnerWin()); + Control owner_box = ((Swt_control)owner_elem.UnderElem()).Under_control(); + rv.owner_win = owner_win; rv.owner_box = owner_box; + rv.menu = new Menu(owner_box); + owner_box.setMenu(rv.menu); + return rv; + } + + private static Swt_popup_grp new_grp(String key, Decorations owner_win) { + Swt_popup_grp rv = new Swt_popup_grp(key); + rv.owner_win = owner_win; + rv.menu = new Menu(owner_win, SWT.DROP_DOWN); + return rv; + } + public static Swt_popup_grp new_bar(String key, GfuiWin win) { + Swt_popup_grp rv = new Swt_popup_grp(key); + Shell owner_win = cast_to_shell(win); + rv.owner_win = owner_win; + rv.menu_is_bar = true; + rv.menu = new Menu(owner_win, SWT.BAR); + owner_win.setMenuBar(rv.menu); + return rv; + } + private static Shell cast_to_shell(GfuiWin win) { + return ((Swt_win)win.UnderElem()).UnderShell(); + } +} +class Swt_popup_itm implements Gfui_mnu_itm { + private Menu menu; + public Swt_popup_itm(Menu menu) { + this.menu = menu; itm = new MenuItem(menu, SWT.NONE); + this.tid = Gfui_mnu_itm_.Tid_btn; + } + public Swt_popup_itm(Menu menu, int swt_type) { + this.menu = menu; itm = new MenuItem(menu, swt_type); + switch (swt_type) { + case SWT.CASCADE: this.tid = Gfui_mnu_itm_.Tid_grp; break; + case SWT.CHECK: this.tid = Gfui_mnu_itm_.Tid_chk; break; + case SWT.RADIO: this.tid = Gfui_mnu_itm_.Tid_rdo; break; + default: throw Err_.new_unhandled(swt_type); + } + } + @Override public int Tid() {return tid;} private int tid; + @Override public String Uid() {return uid;} private String uid = Gfui_mnu_itm_.Gen_uid(); + @Override public boolean Enabled() {return itm.getEnabled();} @Override public void Enabled_(boolean v) {itm.setEnabled(v);} + @Override public String Text() {return itm.getText();} @Override public void Text_(String v) {itm.setText(v);} + @Override public ImageAdp Img() {return img;} @Override public void Img_(ImageAdp v) { + img = v; + if (v != ImageAdp_.Null) + itm.setImage((Image)v.Under()); + } private ImageAdp img; + @Override public boolean Selected() {return itm.getSelection();} + @Override public void Selected_(boolean v) { + selected_changing = true; + itm.setSelection(v); + selected_changing = false; + } + public boolean Selected_changing() {return selected_changing;} private boolean selected_changing; + @Override public Object Under() {return menu;} + public MenuItem Under_menu_item() {return itm;} private MenuItem itm; + public void Invk_set_cmd(Gfo_invk invk, String cmd) { + itm.addListener(SWT.Selection, new Swt_lnr__menu_btn_cmd(invk, cmd)); + } + public void Invk_set_msg(Gfo_invk_root_wkr root_wkr, Gfo_invk invk, GfoMsg msg) { + itm.addListener(SWT.Selection, new Swt_lnr__menu_btn_msg(root_wkr, invk, msg)); + } + public void Invk_set_chk(Gfo_invk_root_wkr root_wkr, Gfo_invk invk, GfoMsg msg_n, GfoMsg msg_y) { + itm.addListener(SWT.Selection, new Swt_lnr__menu_chk_msg(this, root_wkr, invk, msg_n, msg_y)); + } +} +class Swt_lnr__menu_btn_cmd implements Listener { + public Swt_lnr__menu_btn_cmd(Gfo_invk invk, String cmd) {this.invk = invk; this.cmd = cmd;} Gfo_invk invk; String cmd; + public void handleEvent(Event ev) { + try {Gfo_invk_.Invk_by_key(invk, cmd);} + catch (Exception e) {Swt_kit.Instance.Ask_ok("", "", "error while invoking command: cmd=~{0} err=~{1}", cmd, Err_.Message_gplx_full(e));} + } +} +class Swt_lnr__menu_btn_msg implements Listener { + private Gfo_invk_root_wkr root_wkr; private Gfo_invk invk; private GfoMsg msg; + public Swt_lnr__menu_btn_msg(Gfo_invk_root_wkr root_wkr, Gfo_invk invk, GfoMsg msg) {this.root_wkr = root_wkr; this.invk = invk; this.msg = msg;} + public void handleEvent(Event ev) { + try { + msg.Args_reset(); + root_wkr.Run_str_for(invk, msg); + } + catch (Exception e) {Swt_kit.Instance.Ask_ok("", "", "error while invoking command: cmd=~{0} err=~{1}", msg.Key(), Err_.Message_gplx_full(e));} + } +} +class Swt_lnr__menu_chk_msg implements Listener { + private Swt_popup_itm mnu_itm; private Gfo_invk_root_wkr root_wkr; private Gfo_invk invk; private GfoMsg msg_n, msg_y; + public Swt_lnr__menu_chk_msg(Swt_popup_itm mnu_itm, Gfo_invk_root_wkr root_wkr, Gfo_invk invk, GfoMsg msg_n, GfoMsg msg_y) { + this.mnu_itm = mnu_itm; + this.root_wkr = root_wkr; this.invk = invk; this.msg_n = msg_n; this.msg_y = msg_y; + } + public void handleEvent(Event ev) { + if (mnu_itm.Selected_changing()) return; + GfoMsg msg = mnu_itm.Under_menu_item().getSelection() ? msg_y : msg_n; + try { + msg.Args_reset(); + root_wkr.Run_str_for(invk, msg); + } + catch (Exception e) {Swt_kit.Instance.Ask_ok("", "", "error while invoking command: cmd=~{0} err=~{1}", msg.Key(), Err_.Message_gplx_full(e));} + } +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_tab_itm.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_tab_itm.java index a27517de8..b63203060 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_tab_itm.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_tab_itm.java @@ -13,3 +13,48 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.*; +import gplx.gfui.controls.elems.GfuiElem; +import gplx.gfui.controls.gxws.GxwCbkHost; +import gplx.gfui.controls.gxws.GxwCore_base; +import gplx.gfui.controls.gxws.Gxw_tab_itm; +import gplx.gfui.controls.standards.Gfui_tab_itm_data; +import gplx.gfui.kits.core.Swt_kit; + +import org.eclipse.swt.*; +import org.eclipse.swt.custom.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +public class Swt_tab_itm implements Gxw_tab_itm, Swt_control, FocusListener { + public CTabFolder Tab_mgr() {return tab_mgr;} private CTabFolder tab_mgr; + public Swt_tab_mgr Tab_mgr_swt() {return tab_mgr_swt;} private Swt_tab_mgr tab_mgr_swt; + public Gfui_tab_itm_data Tab_data() {return (Gfui_tab_itm_data)tab_itm.getData();} + public Swt_tab_itm(Swt_tab_mgr tab_mgr_swt, Swt_kit kit, CTabFolder tab_mgr, Gfui_tab_itm_data tab_data) { + this.tab_mgr_swt = tab_mgr_swt; this.kit = kit; this.tab_mgr = tab_mgr; + tab_itm = new CTabItem(tab_mgr, SWT.CLOSE); + tab_itm.setData(tab_data); + // core = new Swt_core_cmds(tab_itm); + } + public Swt_kit Kit() {return kit;} private Swt_kit kit; + @Override public Control Under_control() {return null;} + @Override public Composite Under_composite() {return null;} + @Override public Control Under_menu_control() {throw Err_.new_unimplemented();} + @Override public String Tab_name() {return tab_itm.getText();} @Override public void Tab_name_(String v) {tab_itm.setText(v);} + @Override public String Tab_tip_text() {return tab_itm.getToolTipText();} @Override public void Tab_tip_text_(String v) {tab_itm.setToolTipText(v);} + public void Subs_add(GfuiElem sub) { + Swt_control swt_control = Swt_control_.cast_or_fail(sub); + tab_itm.setControl(swt_control.Under_control()); + } + public CTabItem Under_CTabItem() {return tab_itm;} private CTabItem tab_itm; + @Override public GxwCore_base Core() {return core;} GxwCore_base core; + @Override public GxwCbkHost Host() {return host;} @Override public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host; + @Override public String TextVal() {return "not implemented";} + @Override public void TextVal_set(String v) {} + @Override public void EnableDoubleBuffering() {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return Gfo_invk_.Rv_unhandled;} + @Override public void focusGained(FocusEvent arg0) {} + @Override public void focusLost(FocusEvent arg0) {} +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_tab_mgr.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_tab_mgr.java index a27517de8..b1a7c8003 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_tab_mgr.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_tab_mgr.java @@ -13,3 +13,271 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.*; +import gplx.core.threads.Thread_adp_; + +import org.eclipse.swt.*; +import org.eclipse.swt.custom.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.layout.*; +import org.eclipse.swt.widgets.*; + + +import gplx.gfui.controls.gxws.GxwCbkHost; +import gplx.gfui.controls.gxws.GxwCore_base; +import gplx.gfui.controls.gxws.Gxw_tab_itm; +import gplx.gfui.controls.gxws.Gxw_tab_mgr; +import gplx.gfui.controls.standards.Gfui_tab_itm_data; +import gplx.gfui.controls.standards.Gfui_tab_mgr; +import gplx.gfui.draws.*; +import gplx.gfui.kits.core.GfuiInvkCmd; +import gplx.gfui.kits.core.Swt_kit; + +public class Swt_tab_mgr implements Gxw_tab_mgr, Swt_control, FocusListener, Gfo_evt_mgr_owner { + private GfuiInvkCmd cmd_sync; +// private GfuiInvkCmd cmd_async; // NOTE: async needed for some actions like responding to key_down and calling .setSelection; else app hangs; DATE:2014-04-30 + public Swt_tab_mgr(Swt_kit kit, Swt_control owner_control, Keyval_hash ctorArgs) { + this.kit = kit; + tab_folder = new CTabFolder(owner_control.Under_composite(), SWT.BORDER); + tab_folder.setBorderVisible(false); + tab_folder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + tab_folder.setSimple(true); + tab_folder.addListener(SWT.Selection, new Swt_tab_mgr_lnr_selection(this)); + tab_folder.addKeyListener(new Swt_lnr_key(this)); + + new Swt_tab_mgr_lnr_drag_drop(this, tab_folder); + tab_folder.addCTabFolder2Listener(new Swt_tab_mgr_lnr_close(this)); + core = new Swt_core__basic(tab_folder); +// cmd_async = kit.New_cmd_async(this); + cmd_sync = kit.New_cmd_sync(this); + } + public Swt_kit Kit() {return kit;} private Swt_kit kit; + public CTabFolder Under_ctabFolder() {return tab_folder;} + @Override public Control Under_control() {return tab_folder;} private CTabFolder tab_folder; + @Override public Composite Under_composite() {return tab_folder;} + @Override public Control Under_menu_control() {return tab_folder;} + public Gfo_evt_mgr Evt_mgr() {return ev_mgr;} private Gfo_evt_mgr ev_mgr; + public void Evt_mgr_(Gfo_evt_mgr v) {ev_mgr = v;} + + public ColorAdp Btns_selected_foreground() {return btns_selected_foreground;} private ColorAdp btns_selected_foreground; + public void Btns_selected_foreground_(ColorAdp v) {btns_selected_foreground = v; tab_folder.setSelectionForeground(kit.New_color(v));} + public ColorAdp Btns_selected_background() {return btns_selected_background;} private ColorAdp btns_selected_background; + public void Btns_selected_background_(ColorAdp v) {btns_selected_background = v; tab_folder.setSelectionBackground(kit.New_color(v));} + public ColorAdp Btns_unselected_foreground() {return btns_unselected_foreground;} private ColorAdp btns_unselected_foreground; + public void Btns_unselected_foreground_(ColorAdp v) {btns_unselected_foreground = v; tab_folder.setForeground(kit.New_color(v));} + public ColorAdp Btns_unselected_background() {return btns_unselected_background;} private ColorAdp btns_unselected_background; + public void Btns_unselected_background_(ColorAdp v) {btns_unselected_background = v; tab_folder.setBackground(kit.New_color(v));} + public boolean Btns_curved() {return tab_folder.getSimple();} + public void Btns_curved_(boolean v) {tab_folder.setSimple(!v);} + public boolean Btns_place_on_top() {return tab_folder.getTabPosition() == SWT.TOP;} + public void Btns_place_on_top_(boolean v) {tab_folder.setTabPosition(v ? SWT.TOP : SWT.BOTTOM); tab_folder.layout();} + public int Btns_height() {return tab_folder.getTabHeight();} + public void Btns_height_(int v) {tab_folder.setTabHeight(v); tab_folder.layout();} + public boolean Btns_close_visible() {return btns_close_visible;} private boolean btns_close_visible = true; + public void Btns_close_visible_(boolean v) { + this.btns_close_visible = v; + CTabItem[] itms = tab_folder.getItems(); + int len = itms.length; + for (int i = 0; i < len; i++) + itms[i].setShowClose(v); + } + public boolean Btns_unselected_close_visible() {return tab_folder.getUnselectedCloseVisible();} + public void Btns_unselected_close_visible_(boolean v) {tab_folder.setUnselectedCloseVisible(v);} + + @Override public Gxw_tab_itm Tabs_add(Gfui_tab_itm_data tab_data) { + Swt_tab_itm rv = new Swt_tab_itm(this, kit, tab_folder, tab_data); + rv.Under_CTabItem().setData(tab_data); + CTabItem ctab_itm = rv.Under_CTabItem(); + ctab_itm.setShowClose(btns_close_visible); + return rv; + } + @Override public void Tabs_close_by_idx(int i) { + CTabItem itm = tab_folder.getItems()[i]; + Gfui_tab_itm_data tab_data = Get_tab_data(itm); + CTabItem next_tab = Tabs_select_after_closing_itm(tab_data); // NOTE: must calc next_tab before calling Pub_tab_closed; latter will recalc idx + this.Tabs_select_by_itm(next_tab); // NOTE: select tab before closing; DATE:2014-09-10 + Pub_tab_closed(tab_data.Key()); // NOTE: dispose does not call event for .close; must manually raise event; + itm.dispose(); + } + @Override public void Tabs_select_by_idx(int i) { + if (i == Gfui_tab_itm_data.Idx_null) return; // 0 tabs; return; + msg_tabs_select_by_idx_swt.Clear(); + msg_tabs_select_by_idx_swt.Add("v", i); + cmd_sync.Invk(GfsCtx.Instance, 0, Invk_tabs_select_by_idx_swt, msg_tabs_select_by_idx_swt); + } private GfoMsg msg_tabs_select_by_idx_swt = GfoMsg_.new_cast_(Invk_tabs_select_by_idx_swt); + @Override public void Tabs_switch(int src, int trg) {Tabs_switch(tab_folder.getItem(src), tab_folder.getItem(trg));} + public boolean Tabs_switch(CTabItem src_tab_itm, CTabItem trg_tab_itm) { + Control temp_control = src_tab_itm.getControl(); + src_tab_itm.setControl(trg_tab_itm.getControl()); + trg_tab_itm.setControl(temp_control); + + String temp_str = src_tab_itm.getText(); + src_tab_itm.setText(trg_tab_itm.getText()); + trg_tab_itm.setText(temp_str); + + temp_str = src_tab_itm.getToolTipText(); + src_tab_itm.setToolTipText(trg_tab_itm.getToolTipText()); + trg_tab_itm.setToolTipText(temp_str); + + Gfui_tab_itm_data src_tab_data = Get_tab_data(src_tab_itm); + Gfui_tab_itm_data trg_tab_data = Get_tab_data(trg_tab_itm); + int src_tab_idx = src_tab_data.Idx(), trg_tab_idx = trg_tab_data.Idx(); + tab_folder.setSelection(trg_tab_itm); + Gfo_evt_mgr_.Pub_vals(this, Gfui_tab_mgr.Evt_tab_switched, Keyval_.new_("src", src_tab_data.Key()), Keyval_.new_("trg", trg_tab_data.Key())); + return src_tab_idx < trg_tab_idx; + } + public void Tabs_select_by_itm(CTabItem itm) { + if (itm == null) return; // 0 tabs; return; + msg_tabs_select_by_itm_swt.Clear(); + msg_tabs_select_by_itm_swt.Add("v", itm); + cmd_sync.Invk(GfsCtx.Instance, 0, Invk_tabs_select_by_itm_swt, msg_tabs_select_by_itm_swt); + } private GfoMsg msg_tabs_select_by_itm_swt = GfoMsg_.new_cast_(Invk_tabs_select_by_itm_swt); + private void Tabs_select_by_idx_swt(int idx) { + tab_folder.setSelection(idx); + CTabItem itm = tab_folder.getItem(idx); + Pub_tab_selected(Get_tab_key(itm)); // NOTE: setSelection does not call event for SWT.Selection; must manually raise event; + } + private void Tabs_select_by_itm_swt(CTabItem itm) { + tab_folder.setSelection(itm); + Pub_tab_selected(Get_tab_key(itm)); // NOTE: setSelection does not call event for SWT.Selection; must manually raise event; + } + public CTabItem Tabs_select_after_closing_itm(Gfui_tab_itm_data tab_data) { + int next_idx = Gfui_tab_itm_data.Get_idx_after_closing(tab_data.Idx(), tab_folder.getItemCount()); + return next_idx == Gfui_tab_itm_data.Idx_null ? null : tab_folder.getItem(next_idx); + } + public void Pub_tab_selected(String key) { + Gfo_evt_mgr_.Pub_obj(this, Gfui_tab_mgr.Evt_tab_selected, "key", key); + } + public void Pub_tab_closed(String key) { + Gfo_evt_mgr_.Pub_obj(this, Gfui_tab_mgr.Evt_tab_closed, "key", key); + } + @Override public GxwCore_base Core() {return core;} GxwCore_base core; + @Override public GxwCbkHost Host() {return host;} @Override public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host; + @Override public String TextVal() {return "not implemented";} + @Override public void TextVal_set(String v) {} + @Override public void EnableDoubleBuffering() {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (String_.Eq(k, Invk_tabs_select_by_idx_swt)) Tabs_select_by_idx_swt(m.ReadInt("v")); + else if (String_.Eq(k, Invk_tabs_select_by_itm_swt)) Tabs_select_by_itm_swt((CTabItem)m.ReadObj("v", null)); + else return Gfo_invk_.Rv_unhandled; + return this; + } + @Override public void focusGained(FocusEvent arg0) {} + @Override public void focusLost(FocusEvent arg0) {} + private static String + Invk_tabs_select_by_idx_swt = "tabs_select_by_idx" + , Invk_tabs_select_by_itm_swt = "tabs_select_by_itm" + ; + public static Gfui_tab_itm_data Get_tab_data_by_obj(Object data) {return (Gfui_tab_itm_data)data;} + public static Gfui_tab_itm_data Get_tab_data(CTabItem itm) {return (Gfui_tab_itm_data)itm.getData();} + public static String Get_tab_key(CTabItem itm) {return ((Gfui_tab_itm_data)itm.getData()).Key();} +} +class Swt_tab_mgr_lnr_selection implements Listener { + public Swt_tab_mgr_lnr_selection(Swt_tab_mgr tab_folder) {this.tab_folder = tab_folder;} private Swt_tab_mgr tab_folder; + public void handleEvent(Event ev) { + tab_folder.Pub_tab_selected(Swt_tab_mgr.Get_tab_data_by_obj(ev.item.getData()).Key()); + } +} +class Swt_tab_mgr_lnr_close extends CTabFolder2Adapter { // handles close when tab x is clicked + public Swt_tab_mgr_lnr_close(Swt_tab_mgr tab_folder) {this.tab_folder = tab_folder;} private Swt_tab_mgr tab_folder; + @Override public void close(CTabFolderEvent ev) { + Gfui_tab_itm_data tab_data = Swt_tab_mgr.Get_tab_data_by_obj(ev.item.getData()); + tab_folder.Tabs_close_by_idx(tab_data.Idx()); + ev.doit = false; // mark ev handled, since Tabs_close_by_idx closes tab + } +} +class Swt_tab_mgr_lnr_drag_drop implements Listener { + private boolean dragging = false; + private boolean drag_stop = false; + private CTabItem drag_itm; + private final Swt_tab_mgr tab_mgr; private final CTabFolder tab_folder; private final Display display; + private Point prv_mouse; + private Point dead_zone = null; + public Swt_tab_mgr_lnr_drag_drop(Swt_tab_mgr tab_mgr, CTabFolder tab_folder) { + this.tab_mgr = tab_mgr; this.tab_folder = tab_folder; this.display = tab_folder.getDisplay(); + tab_folder.addListener(SWT.DragDetect, this); + tab_folder.addListener(SWT.MouseUp, this); + tab_folder.addListener(SWT.MouseMove, this); + tab_folder.addListener(SWT.MouseExit, this); + tab_folder.addListener(SWT.MouseEnter, this); + } + private void Drag_drop_bgn(CTabItem itm) { + dragging = true; + drag_stop = false; + drag_itm = itm; + dead_zone = null; + } + private void Drag_drop_end() { + tab_folder.setInsertMark(null, false); + dragging = false; + drag_stop = false; + drag_itm = null; + } + public void handleEvent(Event e) { + Point cur_mouse = e.type == SWT.DragDetect + ? tab_folder.toControl(display.getCursorLocation()) //see bug 43251 + : new Point(e.x, e.y) + ; + switch (e.type) { + case SWT.DragDetect: { + CTabItem itm = tab_folder.getItem(cur_mouse); + if (itm == null) return; + this.Drag_drop_bgn(itm); + break; + } + case SWT.MouseEnter: + if (drag_stop) { + dragging = e.button != 0; + drag_stop = false; + } + break; + case SWT.MouseExit: + if (dragging) + Drag_drop_end(); + break; + case SWT.MouseUp: { + if (!dragging) return; + Drag_drop_end(); + break; + } + case SWT.MouseMove: { + if (!dragging) return; + CTabItem curr_itm = tab_folder.getItem(cur_mouse); + if (curr_itm == null) { + tab_folder.setInsertMark(null, false); + return; + } + if (curr_itm == drag_itm) return; // curr_itm is same as drag_itm; ignore + int cur_mouse_x = cur_mouse.x; + int prv_mouse_x = prv_mouse == null ? 0 : prv_mouse.x; + prv_mouse = cur_mouse; // set prv_mouse now b/c of early return below; note that cur_mouse_x and prv_mouse_x are cached above + if ( dead_zone != null // dead_zone exists + && Int_.Between(cur_mouse_x, dead_zone.x, dead_zone.y)) { // mouse is in dead_zone + int drag_idx = Swt_tab_mgr.Get_tab_data(drag_itm).Idx(); + int curr_idx = Swt_tab_mgr.Get_tab_data(curr_itm).Idx(); + if (drag_idx > curr_idx && cur_mouse_x < prv_mouse_x) {} // drag_itm is right of curr_itm, but mouse is moving left (direction reversed); cancel + else if (drag_idx < curr_idx && cur_mouse_x > prv_mouse_x) {} // drag_itm is left of curr_itm, but mouse is moving right (direction reversed); cancel + else + return; // in dead zone, and still moving in original direction; return early + } + boolean fwd = tab_mgr.Tabs_switch(drag_itm, curr_itm); + drag_itm = curr_itm; + Rectangle drag_rect = drag_itm.getBounds(); + dead_zone = Calc_dead_zone(fwd, cur_mouse_x, drag_rect.x, drag_rect.width); + break; + } + } + } + public static Point Calc_dead_zone(boolean fwd, int mouse_x, int drag_l, int drag_w) { + if (fwd) { // drag_itm was moving fwd (moving right) + if (mouse_x < drag_l) return new Point(mouse_x, drag_l); // mouse_x < drag_l; create dead_zone until mouse_x reaches drag_l; occurs when moving drag is small_title and trg_itm is large_title + } + else { // drag_itm was moving bwd (moving left) + int drag_r = drag_l + drag_w; + if (mouse_x > drag_r) return new Point(drag_r, mouse_x); // mouse_x > drag_r; create dead_zone until mouse_x reaches drag_r + } + return null; + } +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_text.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_text.java index a27517de8..96260d1a3 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_text.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_text.java @@ -13,3 +13,53 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.controls.gxws.GxwCbkHost; +import gplx.gfui.controls.gxws.GxwCore_base; +import gplx.gfui.controls.gxws.GxwTextFld; +import gplx.gfui.controls.standards.GfuiTextBox_; +import gplx.gfui.draws.ColorAdp; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +public class Swt_text implements GxwTextFld, Swt_control { + private Text text_box; + public Swt_text(Swt_control owner_control, Keyval_hash ctorArgs) { + int text_box_args = ctorArgs.Has(GfuiTextBox_.Ctor_Memo) + ? SWT.MULTI | SWT.WRAP | SWT.V_SCROLL + : SWT.NONE + ; + text_box = new Text(owner_control.Under_composite(), text_box_args); + core = new Swt_core__basic(text_box); + text_box.addKeyListener(new Swt_lnr_key(this)); + text_box.addMouseListener(new Swt_lnr_mouse(this)); + } + @Override public Control Under_control() {return text_box;} + @Override public Composite Under_composite() {return null;} + @Override public Control Under_menu_control() {return text_box;} + @Override public int SelBgn() {return text_box.getCaretPosition();} @Override public void SelBgn_set(int v) {text_box.setSelection(v);} + @Override public int SelLen() {return text_box.getSelectionCount();} @Override public void SelLen_set(int v) {text_box.setSelection(this.SelBgn(), this.SelBgn() + v);} + @Override public String TextVal() {return text_box.getText();} @Override public void TextVal_set(String v) {text_box.setText(v);} + @Override public GxwCore_base Core() {return core;} GxwCore_base core; + @Override public GxwCbkHost Host() {return host;} @Override public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host; + @Override public void EnableDoubleBuffering() {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return null;} + public void Margins_set(int left, int top, int right, int bot) {} + @Override public boolean Border_on() {return false;} @Override public void Border_on_(boolean v) {} // SWT_TODO:borderWidth doesn't seem mutable + public ColorAdp Border_color() {return border_color;} public void Border_color_(ColorAdp v) {border_color = v;} private ColorAdp border_color; + @Override public void CreateControlIfNeeded() {} + @Override public boolean OverrideTabKey() {return false;} @Override public void OverrideTabKey_(boolean v) {} +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_text_w_border.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_text_w_border.java index a27517de8..e90a31c72 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_text_w_border.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_text_w_border.java @@ -13,3 +13,85 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import gplx.gfui.kits.core.Swt_kit; +import gplx.gfui.controls.gxws.GxwCbkHost; +import gplx.gfui.controls.gxws.GxwCore_base; +import gplx.gfui.controls.gxws.GxwTextFld; +import gplx.gfui.controls.standards.GfuiTextBox_; +import gplx.gfui.draws.ColorAdp; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +public class Swt_text_w_border implements GxwTextFld, Swt_control { + protected final Swt_kit kit; + private Composite text_host; + private Composite text_margin; + private Text text_elem; + public Swt_text_w_border(Swt_kit kit, Swt_control owner_control, Color color, Keyval_hash ctorArgs) { + this.kit = kit; + Composite owner = owner_control.Under_composite(); + int text_elem_style = ctorArgs.Has(GfuiTextBox_.Ctor_Memo) ? SWT.MULTI | SWT.WRAP | SWT.V_SCROLL : SWT.FLAT; + New_box_text_w_border(owner.getDisplay(), owner, text_elem_style, color); + core = new Swt_core__frames(this, text_host, new Swt_frame_itm[] + { new Swt_frame_itm__manual(text_margin, 1, 1, -2, -2) + , new Swt_frame_itm__center_v(text_elem, this) + }); + this.margins_t = this.margins_b = Margin_v_dflt; + text_elem.addKeyListener(new Swt_lnr_key(this)); + text_elem.addMouseListener(new Swt_lnr_mouse(this)); + } + @Override public Control Under_control() {return text_host;} + @Override public Composite Under_composite() {return null;} + @Override public Control Under_menu_control() {return text_elem;} + public Text Under_text() {return text_elem;} + @Override public int SelBgn() {return text_elem.getCaretPosition();} @Override public void SelBgn_set(int v) {text_elem.setSelection(v);} + @Override public int SelLen() {return text_elem.getSelectionCount();} @Override public void SelLen_set(int v) {text_elem.setSelection(this.SelBgn(), this.SelBgn() + v);} + @Override public String TextVal() {return text_elem.getText();} @Override public void TextVal_set(String v) {text_elem.setText(v);} + @Override public GxwCore_base Core() {return core;} Swt_core__frames core; + @Override public GxwCbkHost Host() {return host;} @Override public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host; + @Override public void EnableDoubleBuffering() {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + return null; + } + public int Margins_l() {return margins_l;} public int margins_l; + public int Margins_r() {return margins_r;} public int margins_r; + public int Margins_t() {return margins_t;} public int margins_t; + public int Margins_b() {return margins_b;} public int margins_b; + public void Margins_set(int left, int top, int right, int bot) { + this.margins_l = left; this.margins_t = top; this.margins_r = right; this.margins_b = bot; + } + @Override public boolean Border_on() {return false;} + @Override public void Border_on_(boolean v) {} // SWT_TODO:borderWidth doesn't seem mutable + public ColorAdp Border_color() {return border_color;} private ColorAdp border_color; + public void Border_color_(ColorAdp v) {this.border_color = v; text_host.setBackground(kit.New_color(v));} + @Override public void CreateControlIfNeeded() {} + @Override public boolean OverrideTabKey() {return false;} @Override public void OverrideTabKey_(boolean v) {} + private void New_box_text_w_border(Display display, Composite owner, int style, Color border_color) { + text_host = new Composite(owner, SWT.FLAT); + text_margin = new Composite(text_host, SWT.FLAT); + text_elem = new Text(text_margin, style); + text_elem .addTraverseListener(Swt_lnr_traverse_ignore_ctrl.Instance); // do not allow ctrl+tab to change focus when pressed in text box; allows ctrl+tab to be used by other bindings; DATE:2014-04-30 + text_host.setSize(20, 20); + text_margin.setSize(25, 25); + } + public static final int Margin_v_dflt = 4; +} +class Swt_lnr_traverse_ignore_ctrl implements TraverseListener { + public void keyTraversed(TraverseEvent e) { + if (Swt_lnr_key.Has_ctrl(e.stateMask)) e.doit = false; + } + public static final Swt_lnr_traverse_ignore_ctrl Instance = new Swt_lnr_traverse_ignore_ctrl(); +} diff --git a/150_gfui/src/gplx/gfui/kits/swts/Swt_win.java b/150_gfui/src/gplx/gfui/kits/swts/Swt_win.java index a27517de8..2eaab5b2e 100644 --- a/150_gfui/src/gplx/gfui/kits/swts/Swt_win.java +++ b/150_gfui/src/gplx/gfui/kits/swts/Swt_win.java @@ -13,3 +13,105 @@ 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.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*; +import java.io.FileInputStream; +import java.io.FileNotFoundException; + +import gplx.Bool_; +import gplx.Gfo_invk_cmd; +import gplx.GfoMsg; +import gplx.GfsCtx; +import gplx.Io_url_; +import gplx.gfui.controls.gxws.GxwCbkHost; +import gplx.gfui.controls.gxws.GxwCbkHost_; +import gplx.gfui.controls.gxws.GxwCore_base; +import gplx.gfui.controls.gxws.GxwElem; +import gplx.gfui.controls.gxws.GxwWin; +import gplx.gfui.imgs.IconAdp; +import gplx.gfui.ipts.*; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +public class Swt_win implements GxwWin, Swt_control { + private Swt_lnr_resize resize_lnr; private Swt_lnr_show show_lnr; // use ptr to dispose later + void ctor(boolean window_is_dialog, Shell shell, Display display) { + this.shell = shell; + this.display = display; + this.ctrl_mgr = new Swt_core__basic(shell); + this.show_lnr = new Swt_lnr_show(this); + this.resize_lnr = new Swt_lnr_resize(this); + shell.addListener(SWT.Show, show_lnr); + shell.addListener(SWT.Resize, resize_lnr); + if (window_is_dialog) { + shell.addListener(SWT.Traverse, new Swt_lnr_traverse()); + } + } + public Display UnderDisplay() {return display;} private Display display; + public Shell UnderShell() {return shell;} private Shell shell; + @Override public Control Under_control() {return shell;} + @Override public Composite Under_composite() {return shell;} + @Override public Control Under_menu_control() {return shell;} + public Swt_win(Shell owner) {ctor(Bool_.Y, new Shell(owner, SWT.RESIZE | SWT.DIALOG_TRIM), owner.getDisplay());} + public Swt_win(Display display) {ctor(Bool_.N, new Shell(display), display); } + public void ShowWin() {shell.setVisible(true);} + public void HideWin() {shell.setVisible(false);} + public boolean Maximized() {return shell.getMaximized();} public void Maximized_(boolean v) {shell.setMaximized(v);} + public boolean Minimized() {return shell.getMinimized();} public void Minimized_(boolean v) {shell.setMinimized(v);} + public void CloseWin() {shell.close();} + public boolean Pin() {return pin;} + public void Pin_set(boolean val) { + // shell.setAlwaysOnTop(val); + pin = val; + } boolean pin = false; + public IconAdp IconWin() {return icon;} IconAdp icon; + public void IconWin_set(IconAdp i) { + if (i == null || i.Url() == Io_url_.Empty) return; + icon = i; + Image image = null; + try { + image = new Image(display, new FileInputStream(i.Url().Xto_api())); + } catch (FileNotFoundException e1) {e1.printStackTrace();} + shell.setImage(image); + } + public void OpenedCmd_set(Gfo_invk_cmd v) {when_loaded_cmd = v;} private Gfo_invk_cmd when_loaded_cmd = Gfo_invk_cmd.Noop; + public void Opened() {when_loaded_cmd.Exec();} + public GxwCore_base Core() {return ctrl_mgr;} private GxwCore_base ctrl_mgr; + public GxwCbkHost Host() {return host;} public void Host_set(GxwCbkHost host) {this.host = host;} private GxwCbkHost host = GxwCbkHost_.Null; + public String TextVal() {return shell.getText();} + public void TextVal_set(String v) {shell.setText(v);} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return this;} + public void SendKeyDown(IptKey key) {} + public void SendMouseMove(int x, int y) {} + public void SendMouseDown(IptMouseBtn btn) {} + //public void windowActivated(WindowEvent e) {} + //public void windowClosed(WindowEvent e) {} + //public void windowClosing(WindowEvent e) {host.DisposeCbk();} + //public void windowDeactivated(WindowEvent e) {} + //public void windowDeiconified(WindowEvent e) {host.SizeChangedCbk();} + //public void windowIconified(WindowEvent e) {host.SizeChangedCbk();} + //public void windowOpened(WindowEvent e) {when_loaded_cmd.Invk();} + //@Override public void processKeyEvent(KeyEvent e) {if (GxwCbkHost_.ExecKeyEvent(host, e)) super.processKeyEvent(e);} + //@Override public void processMouseEvent(MouseEvent e) {if (GxwCbkHost_.ExecMouseEvent(host, e)) super.processMouseEvent(e);} + //@Override public void processMouseWheelEvent(MouseWheelEvent e) {if (GxwCbkHost_.ExecMouseWheel(host, e)) super.processMouseWheelEvent(e);} + //@Override public void processMouseMotionEvent(MouseEvent e) {if (host.MouseMoveCbk(IptEvtDataMouse.new_(IptMouseBtn_.None, IptMouseWheel_.None, e.getX(), e.getY()))) super.processMouseMotionEvent(e);} + //@Override public void paint(Graphics g) { + // if (host.PaintCbk(PaintArgs.new_(GfxAdpBase.new_((Graphics2D)g), RectAdp_.Zero))) // ClipRect not used by any clients; implement when necessary + // super.paint(g); + //} + public void EnableDoubleBuffering() {} + public void TaskbarVisible_set(boolean val) {} public void TaskbarParkingWindowFix(GxwElem form) {} + void ctor_GxwForm() { + // this.setLayout(null); // use gfui layout + // this.ctrl_mgr.BackColor_set(ColorAdp_.White); // default form backColor to white + // this.setUndecorated(true); // remove icon, titleBar, minimize, maximize, close, border + // this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // JAVA: cannot cancel alt+f4; set Close to noop, and manually control closing by calling this.CloseForm + // enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK); + // this.addWindowListener(this); + // GxwBoxListener lnr = new GxwBoxListener(this); + // this.addComponentListener(lnr); + // this.addFocusListener(lnr); + } +} diff --git a/150_gfui/src/gplx/gfui/layouts/GftBand.java b/150_gfui/src/gplx/gfui/layouts/GftBand.java index a27517de8..eac1c520d 100644 --- a/150_gfui/src/gplx/gfui/layouts/GftBand.java +++ b/150_gfui/src/gplx/gfui/layouts/GftBand.java @@ -13,3 +13,77 @@ 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.gfui.layouts; import gplx.*; import gplx.gfui.*; +import gplx.gfui.controls.elems.*; +public class GftBand { + public String Key() {return key;} public GftBand Key_(String v) {key = v; return this;} private String key; + public int Idx() {return idx;} public GftBand Idx_(int v) {idx = v; return this;} int idx; + public GftSizeCalc Len1() {return len1;} public GftBand Len1_(GftSizeCalc v) {len1 = v; return this;} GftSizeCalc len1 = new GftSizeCalc_abs(20); + public GftBand Len1_pct_(float val) {return Len1_(new GftSizeCalc_pct(val));} + public GftBand Len1_abs_(int v) {return Len1_(new GftSizeCalc_abs(v));} + public GftCell Cell_dfl() {return cell_dfl;} GftCell cell_dfl = new GftCell(); + public List_adp Cells() {return cells;} List_adp cells = List_adp_.New(); + public GftBand Cells_var_(int count) { + for (int i = 0; i < count; i++) + cells.Add(new GftCell().Len0_(new GftSizeCalc_var(count))); + return this; + } + public GftBand Cell_abs_(int val) {cells.Add(new GftCell().Len0_(new GftSizeCalc_abs(val))); return this;} + public GftBand Cell_pct_(float val) {cells.Add(new GftCell().Len0_(new GftSizeCalc_pct(val))); return this;} + public GftBand Cells_num_(int num) { + cells.Clear(); + for (int i = 0; i < num; i++) + cells.Add(new GftCell().Len0_(new GftSizeCalc_num(num))); + return this; + } + public List_adp Items() {return items;} List_adp items = List_adp_.New(); + public void Items_add(GftItem item) {items.Add(item);} + public void Calc(GftItem owner, int y, int h) { + int x = 0; + y = grid.Bands_dir().GetValByDir(y - h, y); + int availX = owner.Gft_w(); + for (int i = 0; i < cells.Count(); i++) { + GftCell cell = (GftCell)cells.Get_at(i); + if (cell.Len0().Key() == GftSizeCalc_abs.KEY) { + GftSizeCalc_abs calc = (GftSizeCalc_abs)cell.Len0(); + availX -= calc.Val(); + } + else if (cell.Len0().Key() == GftSizeCalc_var.KEY) { + if (i >= items.Count()) continue; + GftItem item = (GftItem)items.Get_at(i); + GfuiElem elem = GfuiElem_.as_(item); + availX -= elem == null ? item.Gft_w() : elem.Width(); + } + } + for (int i = 0; i < items.Count(); i++) { + GftItem item = (GftItem)items.Get_at(i); + GftCell cell = i >= cells.Count() ? cell_dfl : (GftCell)cells.Get_at(i); + int w = cell.Len0().Calc(grid, this, owner, item, availX); + item.Gft_rect_(RectAdp_.new_(x, y, w, h)); +// Tfds.Write(item.Key_of_GfuiElem(), w, h, x, y); + x += w; + } + this.items.Clear(); + } + public GftBand Clone(GftGrid grid, int idx) { + GftBand rv = new GftBand(); + rv.grid = grid; + rv.key = key; rv.idx = idx; rv.cell_dfl = cell_dfl.Clone(); rv.len1 = this.len1.Clone(); + for (int i = 0; i < cells.Count(); i++) { + GftCell cell = (GftCell)cells.Get_at(i); + rv.cells.Add(cell.Clone()); + } + return rv; + } GftGrid grid; + public static GftBand new_() {return new GftBand();} GftBand() {} + public static GftBand fillWidth_() { + GftBand rv = new GftBand(); + rv.Cells_num_(1); + return rv; + } + public static GftBand fillAll_() { + GftBand rv = new GftBand(); + rv.Cells_num_(1).Len1_pct_(100); + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/layouts/GftBand_tst.java b/150_gfui/src/gplx/gfui/layouts/GftBand_tst.java index a27517de8..5206b5642 100644 --- a/150_gfui/src/gplx/gfui/layouts/GftBand_tst.java +++ b/150_gfui/src/gplx/gfui/layouts/GftBand_tst.java @@ -13,3 +13,98 @@ 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.gfui.layouts; import gplx.*; import gplx.gfui.*; +import org.junit.*; +public class GftBand_tst { + @Before public void setup() { + fx.Clear().ini_OwnerSize(200, 400); + } GftGrid_fx fx = new GftGrid_fx(); + @Test public void Bands_1() { + fx .ini_AddItms(2) + .ini_Set(0, GftBand.new_().Cells_num_(2)) + .run() + .tst_Filter(0, 1).tst_X(0, 100).tst_W_all(100).tst_H_all(20).tst_Y_all(0); + } + @Test public void Bands_1_half() { // only add 1 to 2 cell-band + fx .ini_AddItms(1) + .ini_Set(0, GftBand.new_().Cells_num_(2)) + .run() + .tst_Filter(0).tst_X(0).tst_W(100).tst_H(20).tst_Y(0); + } + @Test public void Bands_2() { // put cells 2, 3 on band 1 + fx .ini_AddItms(4) + .ini_Set(0, GftBand.new_().Cells_num_(2)) + .ini_Set(1, GftBand.new_().Cells_num_(2)) + .run() + .tst_Filter(0, 1).tst_X(0, 100).tst_W_all(100).tst_H_all(20).tst_Y_all(0) + .tst_Filter(2, 3).tst_X(0, 100).tst_W_all(100).tst_H_all(20).tst_Y_all(20); // put on 2nd row + } + @Test public void Pct_one() { + fx .ini_AddItms(1) + .ini_Set(0, GftBand.new_().Cell_pct_(50)) + .run() + .tst_Filter(0).tst_X(0).tst_W(100).tst_H_all(20).tst_Y_all(0); + } + @Test public void Pct_many() { + fx .ini_AddItms(3) + .ini_Set(0, GftBand.new_().Cell_pct_(20).Cell_pct_(70).Cell_pct_(10)) + .run() + .tst_Filter(0, 2).tst_W(40, 140, 20).tst_X(0, 40, 180).tst_H_all(20).tst_Y_all(0); + } + @Test public void Mix_pctAtEnd() { + fx .ini_AddItms(2) + .ini_Set(0, GftBand.new_().Cell_abs_(60).Cell_pct_(100)) + .run() + .tst_Filter(0, 1).tst_X(0, 60).tst_W(60, 140).tst_H_all(20).tst_Y_all(0); + } + @Test public void Mix_pctAtBgn() { + fx .ini_AddItms(2) + .ini_Set(0, GftBand.new_().Cell_pct_(100).Cell_abs_(60)) + .run() + .tst_Filter(0, 1).tst_X(0, 140).tst_W(140, 60).tst_H_all(20).tst_Y_all(0); + } + @Test public void Mix_pctAtMid() { + fx .ini_AddItms(3) + .ini_Set(0, GftBand.new_().Cell_abs_(60).Cell_pct_(100).Cell_abs_(40)) + .run() + .tst_Filter(0, 2).tst_X(0, 60, 160).tst_W(60, 100, 40).tst_H_all(20).tst_Y_all(0); + } + @Test public void Height_pct() { + fx .ini_AddItms(1) + .ini_Set(0, GftBand.new_().Cell_pct_(100).Len1_pct_(100)) + .run() + .tst_Filter(0).tst_X(0).tst_W(200).tst_H_all(400).tst_Y_all(0); + } + @Test public void Height_mix() { + fx .ini_AddItms(3) + .ini_Set(0, GftBand.new_().Cells_num_(1).Len1_abs_( 60)) + .ini_Set(1, GftBand.new_().Cells_num_(1).Len1_pct_(100)) + .ini_Set(2, GftBand.new_().Cells_num_(1).Len1_abs_( 20)) + .run() + .tst_Filter(0).tst_H( 60).tst_Y_all( 0).tst_X(0).tst_W(200) + .tst_Filter(1).tst_H(320).tst_Y_all( 60).tst_X(0).tst_W(200) + .tst_Filter(2).tst_H( 20).tst_Y_all(380).tst_X(0).tst_W(200); + } + @Test public void RevDir() { + fx .ini_AddItms(2).ini_BandDir(DirInt.Bwd) + .ini_Set(0, 1, GftBand.new_().Cells_num_(1).Len1_abs_(20)) + .run() + .tst_Filter(0).tst_W(200).tst_H(20).tst_X(0).tst_Y(380) + .tst_Filter(1).tst_W(200).tst_H(20).tst_X(0).tst_Y(360); + } + @Test public void SubLyts() { + fx .ini_AddItms(2).ini_AddLyts(2) + .ini_Lyt(0).ini_Set(0, GftBand.new_().Cells_num_(1).Len1_pct_(100)) + .ini_Lyt(1).ini_Set(0, GftBand.new_().Cells_num_(1).Len1_abs_( 20)).ini_BandDir(DirInt.Bwd) + .run() + .tst_Filter(0).tst_W(200).tst_H(400).tst_X(0).tst_Y( 0) + .tst_Filter(1).tst_W(200).tst_H( 20).tst_X(0).tst_Y(380); + } + @Test public void Var() { + fx .ini_AddItms(2) + .ini_ItmWidth(0, 30).ini_ItmWidth(1, 40) + .ini_Set(0, GftBand.new_().Cells_var_(2)) + .run() + .tst_Filter(0, 1).tst_X(0, 30).tst_W(30, 40).tst_H_all(20).tst_Y_all(0); + } +} diff --git a/150_gfui/src/gplx/gfui/layouts/GftCell.java b/150_gfui/src/gplx/gfui/layouts/GftCell.java index a27517de8..3e8b5e9de 100644 --- a/150_gfui/src/gplx/gfui/layouts/GftCell.java +++ b/150_gfui/src/gplx/gfui/layouts/GftCell.java @@ -13,3 +13,12 @@ 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.gfui.layouts; import gplx.*; import gplx.gfui.*; +public class GftCell { + public GftSizeCalc Len0() {return len0;} public GftCell Len0_(GftSizeCalc c) {len0 = c; return this;} GftSizeCalc len0 = new GftSizeCalc_num(1); + public GftCell Clone() { + GftCell rv = new GftCell(); + rv.len0 = len0.Clone(); + return rv; + } +} diff --git a/150_gfui/src/gplx/gfui/layouts/GftGrid.java b/150_gfui/src/gplx/gfui/layouts/GftGrid.java index a27517de8..ed8290212 100644 --- a/150_gfui/src/gplx/gfui/layouts/GftGrid.java +++ b/150_gfui/src/gplx/gfui/layouts/GftGrid.java @@ -13,3 +13,125 @@ 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.gfui.layouts; import gplx.*; import gplx.gfui.*; +import gplx.gfui.controls.elems.*; +public class GftGrid { + public String Key() {return key;} public GftGrid Key_(String v) {key = v; return this;} private String key; + public List_adp Bands() {return bands;} List_adp bands = List_adp_.New(); + public List_adp SubLyts() {return subLyts;} List_adp subLyts = List_adp_.New(); + public void Clear() {bands.Clear(); subLyts.Clear(); bandDir = DirInt.Fwd;} + public DirInt Bands_dir() {return bandDir;} public GftGrid Bands_dir_(DirInt v) {bandDir = v; return this;} DirInt bandDir = DirInt.Fwd; + public GftGrid SubLyts_get(String key) { + for (int i = 0; i < subLyts.Count(); i++) { + GftGrid grid = (GftGrid)subLyts.Get_at(i); + if (String_.Eq(key, grid.Key())) return grid; + } + return null; + } + public GftBand Bands_get(String key) { + for (int i = 0; i < bands.Count(); i++) { + GftBand band = (GftBand)bands.Get_at(i); + if (String_.Eq(key, band.Key())) return band; + } + return null; + } + public GftGrid Bands_add(GftBand band) { + bands.Add(band.Clone(this, bands.Count())); + return this; + } + public GftGrid Bands_add(int count, GftBand band) { + for (int i = 0; i < count; i++) { + GftBand copy = band.Clone(this, bands.Count() + i); + bands.Add(copy); + } + return this; + } + @gplx.Internal protected void Bands_delAt(int i) {bands.Del_at(i);} + public boolean Bands_has(String key) {return Bands_indexOf(key) != List_adp_.Not_found;} + public void Bands_del(String key) { + int idx = Bands_indexOf(key); + if (idx != List_adp_.Not_found) bands.Del_at(idx); + } + int Bands_indexOf(String key) { + int curIdx = List_adp_.Not_found; + for (int i = 0; i < bands.Count(); i++) { + GftBand band = (GftBand)bands.Get_at(i); + if (String_.Eq(key, band.Key())) { + curIdx = i; + break; + } + } + return curIdx; + } + public GftGrid Bands_set(int idx, GftBand orig) {return Bands_set(idx, idx, orig);} + public GftGrid Bands_set(int bgn, int end, GftBand orig) { + int len = end - bgn + 1; + for (int i = 0; i < len; i++) { + GftBand copy = orig.Clone(this, bgn + i); + bands.Add(copy); + } + return this; + } + public void Exec(GftItem owner, GftItem... ary) { + ExecLyts(owner, ary); + ExecBands(owner, ary); + } + void ExecLyts(GftItem owner, GftItem[] ary) { + int idx = 0; + for (int i = 0; i < subLyts.Count(); i++) { + GftGrid subGrid = (GftGrid)subLyts.Get_at(i); + GftItem[] subAry = new GftItem[subGrid.Bands_cellCount()]; + for (int j = 0; j < subAry.length; j++) { + subAry[j] = ary[idx++]; + } + subGrid.Exec(owner, subAry); + } + } + void ExecBands(GftItem owner, GftItem[] ary) { + if (bands.Count() == 0) return; + int availY = owner.Gft_h(); + GftBand band = null; + int bgn = bandDir.GetValByDir(bands.Idx_last(), 0); + int end = bandDir.GetValByDir(-1, bands.Count()); + for (int i = bgn; i != end; i += bandDir.Val()) { + band = (GftBand)bands.Get_at(i); + if (band.Len1().Key() == GftSizeCalc_abs.KEY) { + GftSizeCalc_abs calc = (GftSizeCalc_abs)band.Len1(); + availY -= calc.Val(); + } + } + int bandIdx = 0; + band = (GftBand)bands.Get_at(bandIdx); + band.Items().Clear(); + int y = bandDir.GetValByDir(owner.Gft_h(), 0); + for (int itmIdx = 0; itmIdx < ary.length; itmIdx++) { + GftItem itm = ary[itmIdx]; + if (band.Items().Count() >= band.Cells().Count()) { + int h = band.Len1().Calc(this, band, owner, itm, availY); + band.Calc(owner, y, h); + y += h * bandDir.Val(); + if (bandIdx + 1 >= bands.Count()) throw Err_.new_wo_type("error retrieving band", "owner", owner.Key_of_GfuiElem(), "item", itm.Key_of_GfuiElem(), "bandIdx", bandIdx + 1, "count", bands.Count()); + band = (GftBand)bands.Get_at(++bandIdx); + band.Items().Clear(); + } + band.Items_add(itm); + } + band.Calc(owner, y, band.Len1().Calc(this, band, owner, null, availY)); + } + int Bands_cellCount() { + int rv = 0; + for (int i = 0; i < bands.Count(); i++) { + GftBand band = (GftBand)bands.Get_at(i); + rv += band.Cells().Count(); + } + return rv; + } + public static GftGrid new_() {return new GftGrid();} GftGrid() {} + public static void LytExecRecur(GfuiElemBase owner) { + if (owner.Lyt() != null) owner.Lyt_exec(); + for (int i = 0; i < owner.SubElems().Count(); i++) { + GfuiElemBase sub = (GfuiElemBase)owner.SubElems().Get_at(i); + LytExecRecur(sub); + } + } +} diff --git a/150_gfui/src/gplx/gfui/layouts/GftGrid_fx.java b/150_gfui/src/gplx/gfui/layouts/GftGrid_fx.java index a27517de8..3b4ca6bac 100644 --- a/150_gfui/src/gplx/gfui/layouts/GftGrid_fx.java +++ b/150_gfui/src/gplx/gfui/layouts/GftGrid_fx.java @@ -13,3 +13,79 @@ 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.gfui.layouts; import gplx.*; import gplx.gfui.*; +class GftGrid_fx { + public GftItem Owner() {return owner;} GftItem owner; + public GftGrid_fx Clear() { + itms.Clear(); + grid.Clear(); + curGrid = grid; + owner = new GftItem_mok();//.Key_("owner"); + return this; + } + public GftGrid_fx ini_AddItms(int num) { + for (int i = 0; i < num; i++) + itms.Add(new GftItem_mok());//.Key_("key" + Int_.To_str(i))); + return this; + } + public GftGrid_fx ini_ItmWidth(int i, int width) { + GftItem itm = (GftItem)itms.Get_at(i); + itm.Gft_w_(width); + return this; + } + public GftGrid_fx ini_AddLyts(int num) { + for (int i = 0; i < num; i++) { + GftGrid newGrid = GftGrid.new_(); + grid.SubLyts().Add(newGrid); + } + return this; + } + public GftGrid_fx ini_Lyt(int num) { + curGrid = (GftGrid)grid.SubLyts().Get_at(num); + return this; + } + public GftGrid_fx ini_BandDir(DirInt dir) {curGrid.Bands_dir_(dir); return this;} + public GftGrid_fx ini_OwnerSize(int w, int h) {owner.Gft_w_(w); owner.Gft_h_(h); return this;} + public GftGrid_fx ini_Set(int idx, GftBand orig) {return ini_Set(idx, idx, orig);} + public GftGrid_fx ini_Set(int bgn, int end, GftBand orig) {curGrid.Bands_set(bgn, end, orig); return this;} + public GftGrid_fx run() { + GftItem[] ary = (GftItem[])itms.To_ary(GftItem.class); + grid.Exec(owner, ary); + return this; + } + public GftGrid_fx tst_Filter(int idx) {return tst_Filter(idx, idx);} + public GftGrid_fx tst_Filter(int bgn, int end) {this.bgn = bgn; this.end = end; return this;} int bgn, end; + public GftGrid_fx tst_W(int... expd) {return tst_ary("w", expd);} + public GftGrid_fx tst_H(int... expd) {return tst_ary("h", expd);} + public GftGrid_fx tst_X(int... expd) {return tst_ary("x", expd);} + public GftGrid_fx tst_Y(int... expd) {return tst_ary("y", expd);} + public GftGrid_fx tst_W_all(int expd) {return tst_all("w", expd);} + public GftGrid_fx tst_H_all(int expd) {return tst_all("h", expd);} + public GftGrid_fx tst_X_all(int expd) {return tst_all("x", expd);} + public GftGrid_fx tst_Y_all(int expd) {return tst_all("y", expd);} + GftGrid_fx tst_all(String name, int expdVal) {return tst_ary(name, rng_(expdVal, end - bgn + 1));} + GftGrid_fx tst_ary(String name, int[] expd) { + int len = end - bgn + 1; + int[] actl = new int[len]; + for (int i = 0; i < len; i++) { + GftItem itm = (GftItem)itms.Get_at(i + bgn); + actl[i] = GetVal(itm, name); + } + Tfds.Eq_ary(expd, actl, name); + return this; + } + int GetVal(GftItem item, String name) { + if (String_.Eq(name, "x")) return item.Gft_x(); + else if (String_.Eq(name, "y")) return item.Gft_y(); + else if (String_.Eq(name, "w")) return item.Gft_w(); + else if (String_.Eq(name, "h")) return item.Gft_h(); + else throw Err_.new_unhandled(name); + } + static int[] rng_(int expdVal, int len) { + int[] rv = new int[len]; + for (int i = 0; i < len; i++) rv[i] = expdVal; + return rv; + } + GftGrid grid = GftGrid.new_(), curGrid; + List_adp itms = List_adp_.New(); +} \ No newline at end of file diff --git a/150_gfui/src/gplx/gfui/layouts/GftItem.java b/150_gfui/src/gplx/gfui/layouts/GftItem.java index a27517de8..ed10c287d 100644 --- a/150_gfui/src/gplx/gfui/layouts/GftItem.java +++ b/150_gfui/src/gplx/gfui/layouts/GftItem.java @@ -13,3 +13,20 @@ 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.gfui.layouts; import gplx.*; import gplx.gfui.*; +public interface GftItem { + String Key_of_GfuiElem(); + int Gft_h(); GftItem Gft_h_(int v); + int Gft_w(); GftItem Gft_w_(int v); + int Gft_x(); GftItem Gft_x_(int v); + int Gft_y(); GftItem Gft_y_(int v); + GftItem Gft_rect_(RectAdp rect); +} +class GftItem_mok implements GftItem { + public String Key_of_GfuiElem() {return "null";} + public int Gft_h() {return gft_h;} public GftItem Gft_h_(int v) {gft_h = v; return this;} int gft_h; + public int Gft_w() {return gft_w;} public GftItem Gft_w_(int v) {gft_w = v; return this;} int gft_w; + public int Gft_x() {return gft_x;} public GftItem Gft_x_(int v) {gft_x = v; return this;} int gft_x; + public int Gft_y() {return gft_y;} public GftItem Gft_y_(int v) {gft_y = v; return this;} int gft_y; + public GftItem Gft_rect_(RectAdp rect) {gft_x = rect.X(); gft_y = rect.Y(); gft_w = rect.Width(); gft_h = rect.Height(); return this;} +} diff --git a/150_gfui/src/gplx/gfui/layouts/GftSizeCalc.java b/150_gfui/src/gplx/gfui/layouts/GftSizeCalc.java index a27517de8..bda5cf0fa 100644 --- a/150_gfui/src/gplx/gfui/layouts/GftSizeCalc.java +++ b/150_gfui/src/gplx/gfui/layouts/GftSizeCalc.java @@ -13,3 +13,47 @@ 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.gfui.layouts; import gplx.*; import gplx.gfui.*; +import gplx.gfui.controls.elems.*; +public interface GftSizeCalc { + int Key(); + int Calc(GftGrid grid, GftBand band, GftItem owner, GftItem item, int ownerWidth); + GftSizeCalc Clone(); +} +class GftSizeCalc_pct implements GftSizeCalc { + public int Key() {return KEY;} public static final int KEY = 1; + public float Val() {return pct;} float pct; + public int Calc(GftGrid grid, GftBand band, GftItem owner, GftItem item, int ownerWidth) { + return Int_.Mult(ownerWidth, pct / 100); + } + public GftSizeCalc Clone() {return new GftSizeCalc_pct(pct);} + public GftSizeCalc_pct(float v) {pct = v;} +} +class GftSizeCalc_abs implements GftSizeCalc { + public int Key() {return KEY;} public static final int KEY = 2; + public int Val() {return abs;} int abs; + public int Calc(GftGrid grid, GftBand band, GftItem owner, GftItem item, int ownerWidth) { + return abs; + } + public GftSizeCalc Clone() {return new GftSizeCalc_abs(abs);} + public GftSizeCalc_abs(int v) {abs = v;} +} +class GftSizeCalc_num implements GftSizeCalc { + public int Key() {return KEY;} public static final int KEY = 3; + public int Val() {return num;} int num; + public int Calc(GftGrid grid, GftBand band, GftItem owner, GftItem item, int ownerWidth) { + return owner.Gft_w() / num; + } + public GftSizeCalc Clone() {return new GftSizeCalc_num(num);} + public GftSizeCalc_num(int num) {this.num = num;} +} +class GftSizeCalc_var implements GftSizeCalc { + public int Key() {return KEY;} public static final int KEY = 4; + public int Val() {return num;} int num; + public int Calc(GftGrid grid, GftBand band, GftItem owner, GftItem item, int ownerWidth) { + GfuiElem elem = GfuiElem_.as_(item); + return elem == null ? item.Gft_w() : elem.Width(); + } + public GftSizeCalc Clone() {return new GftSizeCalc_var(num);} + public GftSizeCalc_var(int num) {this.num = num;} +} \ No newline at end of file diff --git a/150_gfui/src/gplx/gfui/layouts/swts/Swt_layout_data.java b/150_gfui/src/gplx/gfui/layouts/swts/Swt_layout_data.java index a27517de8..68c33df90 100644 --- a/150_gfui/src/gplx/gfui/layouts/swts/Swt_layout_data.java +++ b/150_gfui/src/gplx/gfui/layouts/swts/Swt_layout_data.java @@ -13,3 +13,6 @@ 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.gfui.layouts.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.layouts.*; +public interface Swt_layout_data { +} diff --git a/150_gfui/src/gplx/gfui/layouts/swts/Swt_layout_data__grid.java b/150_gfui/src/gplx/gfui/layouts/swts/Swt_layout_data__grid.java index a27517de8..a3fc47839 100644 --- a/150_gfui/src/gplx/gfui/layouts/swts/Swt_layout_data__grid.java +++ b/150_gfui/src/gplx/gfui/layouts/swts/Swt_layout_data__grid.java @@ -13,3 +13,20 @@ 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.gfui.layouts.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.layouts.*; +public class Swt_layout_data__grid implements Swt_layout_data { + public boolean Grab_excess_w() {return grab_excess_w;} public Swt_layout_data__grid Grab_excess_w_(boolean v) {grab_excess_w = v; return this;} private boolean grab_excess_w; + public boolean Grab_excess_h() {return grab_excess_h;} public Swt_layout_data__grid Grab_excess_h_(boolean v) {grab_excess_h = v; return this;} private boolean grab_excess_h; + public int Align_w() {return align_w;} public Swt_layout_data__grid Align_w_(int v) {align_w = v; return this;} private int align_w = Align__null; + public int Align_h() {return align_h;} public Swt_layout_data__grid Align_h_(int v) {align_h = v; return this;} private int align_h = Align__null; + public int Min_w() {return min_w;} public Swt_layout_data__grid Min_w_(int v) {min_w = v; return this;} private int min_w = -1; + public int Min_h() {return min_h;} public Swt_layout_data__grid Min_h_(int v) {min_h = v; return this;} private int min_h = -1; + public int Hint_w() {return hint_w;} public Swt_layout_data__grid Hint_w_(int v) {hint_w = v; return this;} private int hint_w = -1; + public int Hint_h() {return hint_h;} public Swt_layout_data__grid Hint_h_(int v) {hint_h = v; return this;} private int hint_h = -1; + + public Swt_layout_data__grid Align_w__fill_() {return Align_w_(Align__fill);} + public Swt_layout_data__grid Align_h__fill_() {return Align_h_(Align__fill);} + + // SWT: maps to GridData.BEGINNING, etc + public static final int Align__null = 0, Align__bgn = 1, Align__mid = 2, Align__end = 3, Align__fill = 4; +} diff --git a/150_gfui/src/gplx/gfui/layouts/swts/Swt_layout_mgr.java b/150_gfui/src/gplx/gfui/layouts/swts/Swt_layout_mgr.java index a27517de8..3c037ee72 100644 --- a/150_gfui/src/gplx/gfui/layouts/swts/Swt_layout_mgr.java +++ b/150_gfui/src/gplx/gfui/layouts/swts/Swt_layout_mgr.java @@ -13,3 +13,6 @@ 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.gfui.layouts.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.layouts.*; +public interface Swt_layout_mgr { +} diff --git a/150_gfui/src/gplx/gfui/layouts/swts/Swt_layout_mgr__grid.java b/150_gfui/src/gplx/gfui/layouts/swts/Swt_layout_mgr__grid.java index a27517de8..4cc19407b 100644 --- a/150_gfui/src/gplx/gfui/layouts/swts/Swt_layout_mgr__grid.java +++ b/150_gfui/src/gplx/gfui/layouts/swts/Swt_layout_mgr__grid.java @@ -13,3 +13,12 @@ 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.gfui.layouts.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.layouts.*; +import gplx.gfui.controls.elems.*; +public class Swt_layout_mgr__grid implements Swt_layout_mgr { + public int Cols() {return cols;} public Swt_layout_mgr__grid Cols_(int v) {cols = v; return this;} private int cols = -1; + public int Margin_w() {return margin_w;} public Swt_layout_mgr__grid Margin_w_(int v) {margin_w = v; return this;} private int margin_w = -1; + public int Margin_h() {return margin_h;} public Swt_layout_mgr__grid Margin_h_(int v) {margin_h = v; return this;} private int margin_h = -1; + public int Spacing_w() {return spacing_w;} public Swt_layout_mgr__grid Spacing_w_(int v) {spacing_w = v; return this;} private int spacing_w = -1; + public int Spacing_h() {return spacing_h;} public Swt_layout_mgr__grid Spacing_h_(int v) {spacing_h = v; return this;} private int spacing_h = -1; +} diff --git a/400_xowa/src/gplx/core/brys/Bit_.java b/400_xowa/src/gplx/core/brys/Bit_.java index a27517de8..6699c7d81 100644 --- a/400_xowa/src/gplx/core/brys/Bit_.java +++ b/400_xowa/src/gplx/core/brys/Bit_.java @@ -13,3 +13,41 @@ 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.core.brys; import gplx.*; import gplx.core.*; +public class Bit_ { + public static String ToBitStr(int val) { + boolean[] bits = new boolean[8]; + int idx = 7; + while (val > 0) { + if ((val & 1) == 1) bits[idx] = true; + idx--; + val >>= 1; + } + byte[] rv = new byte[8]; + for (int i = 0; i < 8; i++) + rv[i] = bits[i] ? Byte_ascii.Num_1 : Byte_ascii.Num_0; + return String_.new_a7(rv); + } + public static int Get_flag(int i) {return Int_flag_bldr_.Base2_ary[i];} + public static int Shift_lhs(int val, int shift) {return val << shift;} + public static int Shift_rhs(int val, int shift) {return val >> shift;} + public static int Shift_lhs_to_int(int[] shift_ary, int... val_ary) { + int val_len = val_ary.length; if (val_len > shift_ary.length) throw Err_.new_wo_type("vals must be less than shifts", "vals", val_len, "shifts", shift_ary.length); + int rv = 0; + for (int i = 0; i < val_len; ++i) { + int val = val_ary[i]; + int shift = shift_ary[i]; + rv += val << shift; + } + return rv; + } + public static void Shift_rhs_to_ary(int[] rv, int[] shift_ary, int val) { + int shift_len = shift_ary.length; + for (int i = shift_len - 1; i > - 1; --i) { + int shift = shift_ary[i]; + int itm = val >> shift; + rv[i] = itm; + val -= (itm << shift); + } + } +} diff --git a/400_xowa/src/gplx/core/brys/Bit__tst.java b/400_xowa/src/gplx/core/brys/Bit__tst.java index a27517de8..f5aaf5638 100644 --- a/400_xowa/src/gplx/core/brys/Bit__tst.java +++ b/400_xowa/src/gplx/core/brys/Bit__tst.java @@ -13,3 +13,54 @@ 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.core.brys; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Bit__tst { + @Before public void init() {fxt.Clear();} private Bit__fxt fxt = new Bit__fxt(); + @Test public void XtoBitStr() { + tst_XtoBitStr( 0, "00000000"); + tst_XtoBitStr( 1, "00000001"); + tst_XtoBitStr( 2, "00000010"); + tst_XtoBitStr( 3, "00000011"); + tst_XtoBitStr(255, "11111111"); + } void tst_XtoBitStr(int val, String expd) {Tfds.Eq(expd, Bit_.ToBitStr(val));} + @Test public void Shift_lhs() {// simple: shift 1 bit + fxt.Test_shift_lhs(1, 1, 2); + fxt.Test_shift_lhs(2, 1, 4); + fxt.Test_shift_lhs(3, 1, 6); + fxt.Test_shift_lhs(4, 1, 8); + } + @Test public void Shift_rhs() { + fxt.Test_shift_rhs(2, 1, 1); + fxt.Test_shift_rhs(4, 1, 2); + fxt.Test_shift_rhs(6, 1, 3); + fxt.Test_shift_rhs(8, 1, 4); + } + @Test public void Shift_lhs_to_int() { + int[] shift_ary = Int_ary_.New(0, 3, 5); + fxt.Test_shift_lhs_to_int(shift_ary, Int_ary_.New(0, 0, 0), 0); + fxt.Test_shift_lhs_to_int(shift_ary, Int_ary_.New(7, 0, 0), 7); // 1st 3 bits + fxt.Test_shift_lhs_to_int(shift_ary, Int_ary_.New(0, 3, 0), 24); // 2nd 2 bits + fxt.Test_shift_lhs_to_int(shift_ary, Int_ary_.New(0, 0, 1), 32); // 3rd 1 bit + fxt.Test_shift_lhs_to_int(shift_ary, Int_ary_.New(7, 3, 1), 63); // many bits + } + @Test public void Shift_rhs_to_ary() { + int[] shift_ary = Int_ary_.New(0, 3, 5); + fxt.Test_shift_rhs_to_ary(shift_ary, 0, Int_ary_.New(0, 0, 0)); + fxt.Test_shift_rhs_to_ary(shift_ary, 7, Int_ary_.New(7, 0, 0)); // 1st 3 bits + fxt.Test_shift_rhs_to_ary(shift_ary, 24, Int_ary_.New(0, 3, 0)); // 2nd 2 bits + fxt.Test_shift_rhs_to_ary(shift_ary, 32, Int_ary_.New(0, 0, 1)); // 3rd 1 bit + fxt.Test_shift_rhs_to_ary(shift_ary, 63, Int_ary_.New(7, 3, 1)); // many bits + } +} +class Bit__fxt { + public void Clear() {} + public void Test_shift_lhs(int val, int shift, int expd) {Tfds.Eq(expd, Bit_.Shift_lhs(val, shift));} + public void Test_shift_rhs(int val, int shift, int expd) {Tfds.Eq(expd, Bit_.Shift_rhs(val, shift));} + public void Test_shift_lhs_to_int(int[] shift_ary, int[] val_ary, int expd) {Tfds.Eq(expd, Bit_.Shift_lhs_to_int(shift_ary, val_ary));} + public void Test_shift_rhs_to_ary(int[] shift_ary, int val, int[] expd_ary) { + int[] actl_ary = Int_ary_.New(0, 0, 0); + Bit_.Shift_rhs_to_ary(actl_ary, shift_ary, val); + Tfds.Eq_ary(expd_ary, actl_ary); + } +} diff --git a/400_xowa/src/gplx/core/brys/Bit_heap_rdr.java b/400_xowa/src/gplx/core/brys/Bit_heap_rdr.java index a27517de8..5e29e85bb 100644 --- a/400_xowa/src/gplx/core/brys/Bit_heap_rdr.java +++ b/400_xowa/src/gplx/core/brys/Bit_heap_rdr.java @@ -13,3 +13,78 @@ 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.core.brys; import gplx.*; import gplx.core.*; +import gplx.xowa.htmls.core.hzips.*; +public class Bit_heap_rdr { + private final byte[] hzip_int_bry = new byte[5]; + public int Cur_val() {return cur_val;} private int cur_val; + public int Cur_bits() {return cur_bits;} private int cur_bits; + public int Cur_pos() {return cur_pos;} private int cur_pos; + public byte[] Src() {return src;} private byte[] src; + public int Src_end() {return src_end;} private int src_end; + public void Load(byte[] src, int src_bgn, int src_end) { + this.src = src; this.cur_pos = src_bgn; this.src_end = src_end; + this.cur_val = 0; this.cur_bits = 0; + } + public boolean Get_bool() { + Get_bgn(cur_bits); + int old_val = cur_val; + this.cur_val = cur_val >> 1; + int comp_val = cur_val << 1; + boolean rv = (old_val - comp_val) == 1; + Get_end(1); + return rv; + } + public byte Get_byte(int bits) { + int old_bits = bits; + int new_bits = cur_bits + bits; + boolean again = false; + if (new_bits > 7 && cur_bits > 0) { + old_bits = 8 - cur_bits; + again = true; + new_bits -= 8; + } + int rv = Get_byte_private(old_bits, cur_bits); + if (again) { + Get_end(old_bits); + int new_val = Get_byte_private(new_bits, 0); + rv += new_val << old_bits; + } + Get_end(new_bits); + return (byte)rv; + } + public int Get_int_hzip(int reqd_len) { + int full_len = reqd_len; int bgn_idx = 0; + byte b0 = Get_byte(8); + switch (b0) { + case Xoh_hzip_int.prefix__b256__2: full_len = 2; bgn_idx = 1; break; + case Xoh_hzip_int.prefix__b256__3: full_len = 3; bgn_idx = 1; break; + case Xoh_hzip_int.prefix__b256__4: full_len = 4; bgn_idx = 1; break; + case Xoh_hzip_int.prefix__b256__5: full_len = 5; bgn_idx = 1; break; + } + hzip_int_bry[0] = b0; + for (int i = 1; i < full_len; ++i) + hzip_int_bry[i] = Get_byte(8); + return Xoh_hzip_int.To_int_by_bry(hzip_int_bry, bgn_idx, full_len, Byte_.Zero, 256); + } + private byte Get_byte_private(int bits, int chk_bits) { + Get_bgn(chk_bits); + int old_val = cur_val; + this.cur_val = cur_val >> bits; + int comp_val = cur_val << bits; + byte rv = (byte)(old_val - comp_val); + return rv; + } + private void Get_bgn(int chk_bits) { + if (chk_bits == 0) cur_val = src[cur_pos] & 0xFF; // PATCH.JAVA:need to convert to unsigned byte + } + private void Get_end(int bits) { + int new_bits = cur_bits + bits; + if (new_bits == 8) { + cur_bits = 0; + cur_pos += 1; + } + else + cur_bits = new_bits; + } +} diff --git a/400_xowa/src/gplx/core/brys/Bit_heap_rdr_tst.java b/400_xowa/src/gplx/core/brys/Bit_heap_rdr_tst.java index a27517de8..ec8b2e071 100644 --- a/400_xowa/src/gplx/core/brys/Bit_heap_rdr_tst.java +++ b/400_xowa/src/gplx/core/brys/Bit_heap_rdr_tst.java @@ -13,3 +13,66 @@ 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.core.brys; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Bit_heap_rdr_tst { + private final Bit_heap_rdr_fxt fxt = new Bit_heap_rdr_fxt(); + @Test public void Get_bool() { + fxt.Load(255); + fxt.Test__get_bool(Bool_.Y).Test__cur(127, 1, 0); + fxt.Test__get_bool(Bool_.Y).Test__cur( 63, 2, 0); + fxt.Test__get_bool(Bool_.Y).Test__cur( 31, 3, 0); + fxt.Test__get_bool(Bool_.Y).Test__cur( 15, 4, 0); + fxt.Test__get_bool(Bool_.Y).Test__cur( 7, 5, 0); + fxt.Test__get_bool(Bool_.Y).Test__cur( 3, 6, 0); + fxt.Test__get_bool(Bool_.Y).Test__cur( 1, 7, 0); + fxt.Test__get_bool(Bool_.Y).Test__cur( 0, 0, 1); + fxt.Load(0); + fxt.Test__get_bool(Bool_.N).Test__cur( 0, 1, 0); + fxt.Load(6); + fxt.Test__get_bool(Bool_.N).Test__cur( 3, 1, 0); + fxt.Test__get_bool(Bool_.Y).Test__cur( 1, 2, 0); + fxt.Test__get_bool(Bool_.Y).Test__cur( 0, 3, 0); + } + @Test public void Get_byte() { + fxt.Load(255).Test__get_byte(2, 3).Test__cur(63, 2, 0); + fxt.Load(255, 3); + fxt.Test__get_byte(7, 127); + fxt.Test__get_byte(3, 7); + fxt.Load(10); + fxt.Test__get_bool(false); + fxt.Test__get_byte(3, 5); + } + @Test public void Get_int_hzip() { + fxt.Load(100).Test__get_int_hzip(1, 100).Test__cur(0, 0, 1); + fxt.Load(253, 1, 44).Test__get_int_hzip(1, 300).Test__cur(0, 0, 3); + fxt.Load(0, 100).Test__get_int_hzip(2, 100).Test__cur(0, 0, 2); + fxt.Load(250, 3, 88, 0).Test__get_bool(Bool_.N).Test__get_int_hzip(1, 300).Test__cur(0, 1, 3); + } +} +class Bit_heap_rdr_fxt { + private final Bit_heap_rdr heap = new Bit_heap_rdr(); + public Bit_heap_rdr_fxt Load(int... src) { + byte[] bry = Bry_.New_by_ints(src); + heap.Load(bry, 0, bry.length); + return this; + } + public Bit_heap_rdr_fxt Test__get_bool(boolean expd) { + Tfds.Eq_bool(expd, heap.Get_bool()); + return this; + } + public Bit_heap_rdr_fxt Test__get_byte(int bits, int expd) { + Tfds.Eq_byte((byte)expd, heap.Get_byte(bits)); + return this; + } + public Bit_heap_rdr_fxt Test__get_int_hzip(int reqd, int expd) { + Tfds.Eq_int(expd, heap.Get_int_hzip(reqd)); + return this; + } + public Bit_heap_rdr_fxt Test__cur(int cur_val, int cur_bits, int cur_pos) { + Tfds.Eq_int(cur_val, heap.Cur_val(), "val"); + Tfds.Eq_int(cur_bits, heap.Cur_bits(), "bits"); + Tfds.Eq_int(cur_pos, heap.Cur_pos(), "pos"); + return this; + } +} diff --git a/400_xowa/src/gplx/core/brys/Bit_heap_wtr.java b/400_xowa/src/gplx/core/brys/Bit_heap_wtr.java index a27517de8..a3dba74b7 100644 --- a/400_xowa/src/gplx/core/brys/Bit_heap_wtr.java +++ b/400_xowa/src/gplx/core/brys/Bit_heap_wtr.java @@ -13,3 +13,57 @@ 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.core.brys; import gplx.*; import gplx.core.*; +public class Bit_heap_wtr { + private final gplx.xowa.htmls.core.hzips.Xoh_hzip_int hzip_int = new gplx.xowa.htmls.core.hzips.Xoh_hzip_int().Mode_is_b256_(true); + private final Bry_bfr hzip_int_bfr = Bry_bfr_.Reset(5); + public int Cur() {return cur;} private int cur; + public int Cur_bits() {return cur_bits;} private int cur_bits; + public Bry_bfr Heap() {return heap;} private final Bry_bfr heap = Bry_bfr_.New(); + public void Clear() {heap.Clear(); cur = 0; cur_bits = 0;} + public void Add_bool(boolean v) { + if (v) + cur += Pow_ary[cur_bits]; + if (cur_bits == 7) { + heap.Add_byte((byte)cur); + cur = 0; + cur_bits = 0; + } + else + ++cur_bits; + } + public void Add_byte(byte val_byte) { + int val_int = val_byte & 0xFF; // PATCH.JAVA:need to convert to unsigned byte + if (cur_bits == 0) + heap.Add_byte(val_byte); + else { + heap.Add_byte((byte)(cur + (val_int << cur_bits))); + this.cur = val_int >> cur_bits; + } + } + public void Add_byte(int val_bits, int val) { + int total_bits = cur_bits + val_bits; + int new_val = cur + (val << cur_bits); + if (total_bits < 8) { + this.cur_bits = total_bits; + this.cur = new_val; + } + else { + heap.Add_byte((byte)new_val); + this.cur = val >> (8 - cur_bits); + this.cur_bits = total_bits - 8; + } + } + public void Add_int_hzip(int reqd_len, int val) { + hzip_int.Encode(reqd_len, hzip_int_bfr, val); + int len = hzip_int_bfr.Len(); + byte[] hzip_bry = hzip_int_bfr.Bfr(); + for (int i = 0; i < len; ++i) { + byte b = hzip_bry[i]; + Add_byte(8, b & 0xFF); // PATCH.JAVA:need to convert to unsigned byte + } + hzip_int_bfr.Clear(); + } + public void Save(Bry_bfr bfr) {} + public static final int[] Pow_ary = new int[] {1, 2, 4, 8, 16, 32, 64, 128, 256}; +} diff --git a/400_xowa/src/gplx/core/brys/Bit_heap_wtr_tst.java b/400_xowa/src/gplx/core/brys/Bit_heap_wtr_tst.java index a27517de8..4060ddfb8 100644 --- a/400_xowa/src/gplx/core/brys/Bit_heap_wtr_tst.java +++ b/400_xowa/src/gplx/core/brys/Bit_heap_wtr_tst.java @@ -13,3 +13,72 @@ 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.core.brys; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Bit_heap_wtr_tst { + private final Bit_heap_wtr_fxt fxt = new Bit_heap_wtr_fxt(); + @Test public void Add_bool() { + fxt.Clear().Add_bool(Bool_.Y).Test__vals(1, 1); + fxt.Clear().Add_bool(Bool_.N).Test__vals(1, 0); + fxt.Clear().Add_bool(Bool_.Y, Bool_.Y, Bool_.Y, Bool_.Y).Test__vals(4, 15); + fxt.Clear().Add_bool(Bool_.Y, Bool_.N, Bool_.N, Bool_.Y).Test__vals(4, 9); + fxt.Clear().Add_bool(8, Bool_.Y).Test__vals(0, 0, 255); + } + @Test public void Add_byte() { + fxt.Clear().Add_byte(255).Test__vals(0, 0, 255); + } + @Test public void Add_bool_byte() { + fxt.Clear().Add_bool(Bool_.N).Add_byte(255).Test__vals(1, 127, 254); + fxt.Clear().Add_bool(Bool_.Y).Add_byte(255).Test__vals(1, 127, 255); + fxt.Clear().Add_bool(Bool_.Y, Bool_.Y, Bool_.Y, Bool_.Y).Add_byte(255).Test__vals(4, 15, 255); + } + @Test public void Add_byte_digits() { + fxt.Clear().Add_byte(4, 15).Test__vals(4, 15); + fxt.Clear().Add_byte(7, 127).Add_byte(2, 3).Test__vals(1, 1, 255); + fxt.Clear().Add_byte(3, 7).Add_byte(3, 7).Test__vals(6, 63); + fxt.Clear().Add_byte(6, 63).Add_byte(3, 7).Test__vals(1, 1, 255); + fxt.Clear().Add_byte(3, 6).Add_byte(3, 6).Test__vals(6, 54); + } + @Test public void Add_int_hzip() { + fxt.Clear().Add_int_hzip(1, 100).Test__vals(0, 0, 100); + fxt.Clear().Add_int_hzip(1, 300).Test__vals(0, 0, 253, 1, 44); + fxt.Clear().Add_int_hzip(2, 100).Test__vals(0, 0, 0, 100); + fxt.Clear().Add_bool(Bool_.N).Add_int_hzip(1, 300).Test__vals(1, 0, 250, 3, 88); + } +} +class Bit_heap_wtr_fxt { + private final Bit_heap_wtr heap = new Bit_heap_wtr(); + public Bit_heap_wtr_fxt Clear() {heap.Clear(); return this;} + public Bit_heap_wtr_fxt Add_bool(int len, boolean v) { + boolean[] ary = new boolean[len]; + for (int i = 0; i < len; ++i) + ary[i] = v; + Add_bool(ary); + return this; + } + public Bit_heap_wtr_fxt Add_bool(boolean... v) { + int len = v.length; + for (int i = 0; i < len; ++i) + heap.Add_bool(v[i]); + return this; + } + public Bit_heap_wtr_fxt Add_byte(int... v) { + int len = v.length; + for (int i = 0; i < len; ++i) + heap.Add_byte((byte)v[i]); + return this; + } + public Bit_heap_wtr_fxt Add_byte(int bits, int val) { + heap.Add_byte(bits, (byte)val); + return this; + } + public Bit_heap_wtr_fxt Add_int_hzip(int reqd, int val) { + heap.Add_int_hzip(reqd, val); + return this; + } + public void Test__vals(int expd_cur_bits, int expd_cur, int... expd_ary) { + Tfds.Eq_int (expd_cur_bits , heap.Cur_bits() , "cur_bits"); + Tfds.Eq_int (expd_cur , heap.Cur() , "cur"); + Tfds.Eq_ary (Bry_.New_by_ints(expd_ary) , heap.Heap().To_bry() , "heap"); + } +} diff --git a/400_xowa/src/gplx/core/brys/Bry_bldr.java b/400_xowa/src/gplx/core/brys/Bry_bldr.java index a27517de8..2b60caf98 100644 --- a/400_xowa/src/gplx/core/brys/Bry_bldr.java +++ b/400_xowa/src/gplx/core/brys/Bry_bldr.java @@ -13,3 +13,26 @@ 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.core.brys; import gplx.*; import gplx.core.*; +public class Bry_bldr { + public byte[] Val() {return val;} private byte[] val; + public Bry_bldr New_256() {return New(256);} + public Bry_bldr New(int len) {val = new byte[len]; return this;} + public Bry_bldr Set_rng_ws(byte v) {return Set_many(v, Byte_ascii.Space, Byte_ascii.Tab, Byte_ascii.Nl, Byte_ascii.Cr);} + public Bry_bldr Set_rng_xml_identifier(byte v) {return Set_rng_alpha_lc(v).Set_rng_alpha_uc(v).Set_rng_num(v).Set_many(v, Byte_ascii.Underline, Byte_ascii.Dash);} + public Bry_bldr Set_rng_alpha(byte v) {return Set_rng_alpha_lc(v).Set_rng_alpha_uc(v);} + public Bry_bldr Set_rng_alpha_lc(byte v) {return Set_rng(v, Byte_ascii.Ltr_a, Byte_ascii.Ltr_z);} + public Bry_bldr Set_rng_alpha_uc(byte v) {return Set_rng(v, Byte_ascii.Ltr_A, Byte_ascii.Ltr_Z);} + public Bry_bldr Set_rng_num(byte v) {return Set_rng(v, Byte_ascii.Num_0, Byte_ascii.Num_9);} + public Bry_bldr Set_rng(byte v, int bgn, int end) { + for (int i = bgn; i <= end; i++) + val[i] = v; + return this; + } + public Bry_bldr Set_many(byte v, int... ary) { + int len = ary.length; + for (int i = 0; i < len; i++) + val[ary[i]] = v; + return this; + } +} diff --git a/400_xowa/src/gplx/core/brys/Bry_comparer.java b/400_xowa/src/gplx/core/brys/Bry_comparer.java index a27517de8..55842f0fa 100644 --- a/400_xowa/src/gplx/core/brys/Bry_comparer.java +++ b/400_xowa/src/gplx/core/brys/Bry_comparer.java @@ -13,3 +13,12 @@ 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.core.brys; import gplx.*; import gplx.core.*; +import gplx.core.lists.*; +public class Bry_comparer implements ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + byte[] lhs = (byte[])lhsObj, rhs = (byte[])rhsObj; + return Bry_.Compare(lhs, 0, lhs.length, rhs, 0, rhs.length); + } + public static final Bry_comparer Instance = new Bry_comparer(); Bry_comparer() {} // TS.static +} diff --git a/400_xowa/src/gplx/core/brys/Bry_diff_.java b/400_xowa/src/gplx/core/brys/Bry_diff_.java index a27517de8..cd51fa95e 100644 --- a/400_xowa/src/gplx/core/brys/Bry_diff_.java +++ b/400_xowa/src/gplx/core/brys/Bry_diff_.java @@ -13,3 +13,38 @@ 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.core.brys; import gplx.*; import gplx.core.*; +public class Bry_diff_ { + public static byte[][] Diff_1st_line(byte[] lhs, byte[] rhs) {return Diff_1st(lhs, 0, lhs.length, rhs, 0, rhs.length, Byte_ascii.Nl_bry, Byte_ascii.Angle_bgn_bry, 255);} + public static byte[][] Diff_1st(byte[] lhs, int lhs_bgn, int lhs_end, byte[] rhs, int rhs_bgn, int rhs_end, byte[] stop, byte[] show, int diff_max) { + int lhs_len = lhs_end - lhs_bgn; + int rhs_len = rhs_end - rhs_bgn; + int len = lhs_len < rhs_len ? lhs_len : rhs_len; + int lhs_idx = -1, rhs_idx = -1; + for (int i = 0; i < len; ++i) { + byte lhs_byte = lhs[i + lhs_bgn]; + byte rhs_byte = rhs[i + rhs_bgn]; + if (lhs_byte != rhs_byte) {lhs_idx = rhs_idx = i; break;} // diff; stop iterating + } + if (lhs_idx == -1 && rhs_idx == -1) { + switch (Int_.Compare(lhs_len, rhs_len)) { + case CompareAble_.Same: return null; + case CompareAble_.Less: lhs_idx = rhs_idx = lhs_len; break; + case CompareAble_.More: lhs_idx = rhs_idx = rhs_len; break; + } + } + byte[] lhs_diff = Get_1st(stop, show, lhs, lhs_idx, lhs_len, diff_max); + byte[] rhs_diff = Get_1st(stop, show, rhs, rhs_idx, rhs_len, diff_max); + return new byte[][] {lhs_diff, rhs_diff}; + } + private static byte[] Get_1st(byte[] stop, byte[] show, byte[] src, int bgn, int end, int diff_max) { + if (bgn == end) return Bry__eos; + int prv_show = Bry_find_.Find_bwd(src, show, bgn , 0); if (prv_show == Bry_find_.Not_found) prv_show = 0; + int prv_stop = Bry_find_.Find_bwd(src, stop, bgn , 0); prv_stop = (prv_stop == Bry_find_.Not_found) ? 0 : prv_stop + 1; + int prv = prv_show > prv_stop ? prv_show : prv_stop; + int nxt = Bry_find_.Find_fwd(src, stop, bgn , end); if (nxt == Bry_find_.Not_found) nxt = end; + if (nxt - prv > 255) nxt = prv + diff_max; + return Bry_.Mid(src, prv, nxt); + } + private static final byte[] Bry__eos = Bry_.new_a7("<>"); +} diff --git a/400_xowa/src/gplx/core/brys/Bry_diff_tst.java b/400_xowa/src/gplx/core/brys/Bry_diff_tst.java index a27517de8..7d56ab3e3 100644 --- a/400_xowa/src/gplx/core/brys/Bry_diff_tst.java +++ b/400_xowa/src/gplx/core/brys/Bry_diff_tst.java @@ -13,3 +13,30 @@ 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.core.brys; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Bry_diff_tst { + @Before public void init() {} private final Bry_diff_fxt fxt = new Bry_diff_fxt(); + @Test public void Diff_1st() { + fxt.Test__diff_1st("a|b|c" , "a|b|c" , null , null); + fxt.Test__diff_1st("a|b|c" , "a|b1|c" , "b" , "b1"); + fxt.Test__diff_1st("a|b|" , "a|b|c" , "<>" , "c"); + fxt.Test__diff_1st("a|b|c" , "a|b|" , "c" , "<>"); + } + @Test public void Diff_1st_show() { + fxt.Test__diff_1st("a|bd|e" , "a|be|e" , "d", "e"); + } +} +class Bry_diff_fxt { + public void Test__diff_1st(String lhs, String rhs, String expd_lhs, String expd_rhs) { + byte[] lhs_src = Bry_.new_u8(lhs); + byte[] rhs_src = Bry_.new_u8(rhs); + byte[][] actl = Bry_diff_.Diff_1st(lhs_src, 0, lhs_src.length, rhs_src, 0, rhs_src.length, Byte_ascii.Pipe_bry, Byte_ascii.Angle_bgn_bry, 255); + if (expd_lhs == null && expd_rhs == null) + Tfds.Eq_true(actl == null, "actl not null"); + else { + Tfds.Eq_bry(Bry_.new_u8(expd_lhs), actl[0]); + Tfds.Eq_bry(Bry_.new_u8(expd_rhs), actl[1]); + } + } +} diff --git a/400_xowa/src/gplx/core/brys/Bry_tmp.java b/400_xowa/src/gplx/core/brys/Bry_tmp.java index a27517de8..ec7e88ab7 100644 --- a/400_xowa/src/gplx/core/brys/Bry_tmp.java +++ b/400_xowa/src/gplx/core/brys/Bry_tmp.java @@ -13,3 +13,26 @@ 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.core.brys; import gplx.*; import gplx.core.*; +public class Bry_tmp { + public byte[] src; + public int src_bgn; + public int src_end; + public boolean dirty; + public Bry_tmp Init(byte[] src, int src_bgn, int src_end) { + this.dirty = false; + this.src = src; + this.src_bgn = src_bgn; + this.src_end = src_end; + return this; + } + public void Set_by_bfr(Bry_bfr bfr) { + dirty = true; + src = bfr.To_bry_and_clear(); + src_bgn = 0; + src_end = src.length; + } + public void Add_to_bfr(Bry_bfr bfr) { + bfr.Add_mid(src, src_bgn, src_end); + } +} diff --git a/400_xowa/src/gplx/core/brys/Int_flag_bldr.java b/400_xowa/src/gplx/core/brys/Int_flag_bldr.java index a27517de8..847f1ea34 100644 --- a/400_xowa/src/gplx/core/brys/Int_flag_bldr.java +++ b/400_xowa/src/gplx/core/brys/Int_flag_bldr.java @@ -13,3 +13,24 @@ 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.core.brys; import gplx.*; import gplx.core.*; +public class Int_flag_bldr { + private int[] pow_ary; + public int[] Val_ary() {return val_ary;} private int[] val_ary; + public Int_flag_bldr Pow_ary_bld_(int... ary) { + this.pow_ary = Int_flag_bldr_.Bld_pow_ary(ary); + this.val_ary = new int[pow_ary.length]; + return this; + } + public boolean Set_as_bool(int idx, boolean val) {val_ary[idx] = val ? 1 : 0; return val;} + public byte Set_as_byte(int idx, byte val) {val_ary[idx] = val; return val;} + public int Set_as_int(int idx, int val) {val_ary[idx] = val; return val;} + public Int_flag_bldr Set(int idx, boolean val) {Set_as_bool(idx, val); return this;} + public Int_flag_bldr Set(int idx, byte val) {Set_as_byte(idx, val); return this;} + public Int_flag_bldr Set(int idx, int val) {Set_as_int(idx, val); return this;} + public int Get_as_int(int idx) {return val_ary[idx];} + public byte Get_as_byte(int idx) {return (byte)val_ary[idx];} + public boolean Get_as_bool(int idx) {return val_ary[idx] == 1;} + public int Encode() {return Int_flag_bldr_.To_int(pow_ary, val_ary);} + public void Decode(int v) {Int_flag_bldr_.To_int_ary(val_ary, pow_ary, v);} +} diff --git a/400_xowa/src/gplx/core/brys/Int_flag_bldr_.java b/400_xowa/src/gplx/core/brys/Int_flag_bldr_.java index a27517de8..0656f683f 100644 --- a/400_xowa/src/gplx/core/brys/Int_flag_bldr_.java +++ b/400_xowa/src/gplx/core/brys/Int_flag_bldr_.java @@ -13,3 +13,75 @@ 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.core.brys; import gplx.*; import gplx.core.*; +public class Int_flag_bldr_ { + public static int[] Bld_pow_ary(int... ary) { + int len = ary.length; + int[] rv = new int[len]; + int pow = 0; + for (int i = len - 1; i > -1; --i) { + rv[i] = Int_flag_bldr_.Base2_ary[pow]; + pow += ary[i]; + } + return rv; + } + public static int To_int(int[] pow_ary, int[] val_ary) { + int pow_ary_last = pow_ary.length - 1; + int val = 0; + for (int i = pow_ary_last; i > -1; i--) + val += pow_ary[i] * val_ary[i]; + return val; + } + public static int[] To_int_ary(int[] pow_ary, int v) { + int[] rv = new int[pow_ary.length]; + To_int_ary(rv, pow_ary, v); + return rv; + } + public static void To_int_ary(int[] rv, int[] pow_ary, int v) { + int pow_ary_len = pow_ary.length; + int rv_len = rv.length; + for (int i = 0; i < pow_ary_len; i++) { + if (i >= rv_len) break; + rv[i] = v / pow_ary[i]; + int factor = pow_ary[i] * rv[i]; + v = factor == 0 ? v : (v % factor); // NOTE: if 0, do not do modulus or else div by zero + } + } + public static byte To_byte(byte[] pow_ary, byte... val_ary) { + int pow_ary_last = pow_ary.length - 1; + int val = 0; + for (int i = pow_ary_last; i > -1; --i) + val += pow_ary[i] * val_ary[i]; + return (byte)val; + } + public static void To_bry(byte[] rv, byte[] pow_ary, byte val) { + int pow_ary_len = pow_ary.length; + int rv_len = rv.length; + for (int i = 0; i < pow_ary_len; i++) { + if (i >= rv_len) break; + rv[i] = (byte)(val / pow_ary[i]); + int factor = pow_ary[i] * rv[i]; + val = (byte)(factor == 0 ? val : (val % factor)); // NOTE: if 0, do not do modulus or else div by zero + } + } + public static int To_int_date_short(int[] val_ary) { + val_ary[0] -= 1900; + return Int_flag_bldr_.To_int(Pow_ary_date_short, val_ary); + } + public static void To_date_short_int_ary(int[] rv, int v) { + Int_flag_bldr_.To_int_ary(rv, Pow_ary_date_short, v); + rv[0] += 1900; + } + public static DateAdp To_date_short(int v) { + int[] rv = new int[Pow_ary_date_short.length]; + To_date_short_int_ary(rv, v); + return DateAdp_.seg_(rv); + } + private static final int[] Pow_ary_date_short = new int[] {1048576, 65536, 2048, 64, 1}; // yndhm -> 12,4,5,5,6 + public static final int[] Base2_ary = new int[] + { 1, 2, 4, 8, 16, 32, 64, 128 + , 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 + , 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608 + , 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824, 0 + }; +} diff --git a/400_xowa/src/gplx/core/brys/Int_flag_bldr__tst.java b/400_xowa/src/gplx/core/brys/Int_flag_bldr__tst.java index a27517de8..22040179d 100644 --- a/400_xowa/src/gplx/core/brys/Int_flag_bldr__tst.java +++ b/400_xowa/src/gplx/core/brys/Int_flag_bldr__tst.java @@ -13,3 +13,60 @@ 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.core.brys; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Int_flag_bldr__tst { + private final Int_flag_bldr__fxt fxt = new Int_flag_bldr__fxt(); + @Test public void Bld_pow_ary() { + fxt.Test__bld_pow_ary(fxt.Make__ary(1, 1, 1, 1), fxt.Make__ary(8, 4, 2, 1)); + fxt.Test__bld_pow_ary(fxt.Make__ary(3, 2) , fxt.Make__ary(4, 1)); + } + @Test public void To_int__1_1_1() { + int[] seg_ary = fxt.Make__ary(1, 1, 1); + fxt.Test__to_int(seg_ary , fxt.Make__ary(0, 0, 0), 0); + fxt.Test__to_int(seg_ary , fxt.Make__ary(1, 1, 1), 7); + fxt.Test__to_int(seg_ary , fxt.Make__ary(1, 0, 0), 4); + fxt.Test__to_int(seg_ary , fxt.Make__ary(1, 1, 0), 6); + fxt.Test__to_int(seg_ary , fxt.Make__ary(0, 1, 1), 3); + } + @Test public void To_int__2_3() { + fxt.Test__to_int(fxt.Make__ary(2, 3) , fxt.Make__ary(3, 7) , 31); + fxt.Test__to_int(fxt.Make__ary(1, 2, 3) , fxt.Make__ary(1, 3, 7) , 63); + } + @Test public void To_int__11_4_5_5_6() { + fxt.Test__to_int(fxt.Make__ary(11, 4, 5, 5, 6), fxt.Make__ary(2012, 6, 3, 23, 17), 2110135761); + fxt.Test__to_int(fxt.Make__ary(11, 4, 5, 5, 6), fxt.Make__ary(2012, 6, 3, 23, 18), 2110135762); + } + @Test public void To_int_ary() { + fxt.Test__to_int_ary(fxt.Make__ary(1, 1, 1, 1) , 15, fxt.Make__ary(1, 1, 1, 1)); + fxt.Test__to_int_ary(fxt.Make__ary(3, 2) , 31, fxt.Make__ary(7, 3)); + fxt.Test__to_int_ary(fxt.Make__ary(3, 2, 1) , 63, fxt.Make__ary(7, 3, 1)); + fxt.Test__to_int_ary(fxt.Make__ary(12, 4, 5, 5, 6), 2110135761, fxt.Make__ary(2012, 6, 3, 23, 17)); + fxt.Test__to_int_ary(fxt.Make__ary(12, 4, 5, 5, 6), 2110135762, fxt.Make__ary(2012, 6, 3, 23, 18)); + } + @Test public void To_int_date_short() { + fxt.Test__to_int_date_short("20120604 2359", 117843451); + fxt.Test__to_int_date_short("20120604 2358", 117843450); + fxt.Test__to_int_date_short("20120605 0000", 117843968); + } +} +class Int_flag_bldr__fxt { + public int[] Make__ary(int... v) {return v;} + public void Test__bld_pow_ary(int[] seg_ary, int[] expd) {Tfds.Eq_ary_str(expd, Int_flag_bldr_.Bld_pow_ary(seg_ary));} + public void Test__to_int(int[] seg_ary, int[] val_ary, int expd) { + int[] pow_ary = Int_flag_bldr_.Bld_pow_ary(seg_ary); + Tfds.Eq(expd, Int_flag_bldr_.To_int(pow_ary, val_ary)); + int[] actl_val_ary = Int_flag_bldr_.To_int_ary(pow_ary, expd); + Tfds.Eq_ary(val_ary, actl_val_ary); + } + public void Test__to_int_ary(int[] seg_ary, int val, int[] expd) { + int[] pow_ary = Int_flag_bldr_.Bld_pow_ary(seg_ary); + Tfds.Eq_ary_str(expd, Int_flag_bldr_.To_int_ary(pow_ary, val)); + } + public void Test__to_int_date_short(String date_str, int expd) { + DateAdp date = DateAdp_.parse_fmt(date_str, "yyyyMMdd HHmm"); + int date_int = Int_flag_bldr_.To_int_date_short(date.XtoSegAry()); + Tfds.Eq(expd, date_int); + Tfds.Eq(date_str, Int_flag_bldr_.To_date_short(date_int).XtoStr_fmt("yyyyMMdd HHmm")); + } +} diff --git a/400_xowa/src/gplx/core/brys/Mid_able.java b/400_xowa/src/gplx/core/brys/Mid_able.java index a27517de8..5636219dc 100644 --- a/400_xowa/src/gplx/core/brys/Mid_able.java +++ b/400_xowa/src/gplx/core/brys/Mid_able.java @@ -13,3 +13,9 @@ 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.core.brys; import gplx.*; import gplx.core.*; +public interface Mid_able { + byte[] Mid__src(); + int Mid__bgn(); + int Mid__end(); +} diff --git a/400_xowa/src/gplx/core/brys/evals/Bry_eval_mgr.java b/400_xowa/src/gplx/core/brys/evals/Bry_eval_mgr.java index a27517de8..a26ae3e55 100644 --- a/400_xowa/src/gplx/core/brys/evals/Bry_eval_mgr.java +++ b/400_xowa/src/gplx/core/brys/evals/Bry_eval_mgr.java @@ -13,3 +13,94 @@ 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.core.brys.evals; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +public class Bry_eval_mgr { + private final Hash_adp_bry hash = Hash_adp_bry.cs(); + private final byte sym_escape, sym_key_end, sym_grp_bgn, sym_grp_end; + public Bry_eval_mgr(byte sym_escape, byte sym_key_end, byte sym_grp_bgn, byte sym_grp_end) { + this.sym_escape = sym_escape; + this.sym_key_end = sym_key_end; + this.sym_grp_bgn = sym_grp_bgn; + this.sym_grp_end = sym_grp_end; + } + public Bry_eval_mgr Add_many(Bry_eval_wkr... ary) { + for (Bry_eval_wkr itm : ary) + hash.Add(Bry_.new_u8(itm.Key()), itm); + return this; + } + public byte[] Eval(byte[] src) { + Bry_eval_rslt rv = Eval_txt(0, src, 0, src.length); + return rv == null ? src : rv.Bry(); + } + private Bry_eval_rslt Eval_txt(int depth, byte[] src, int src_bgn, int src_end) { + Bry_bfr cur_bfr = null; + int cur_pos = src_bgn; + while (true) { + if (cur_pos == src_end) break; + byte cur_byte = src[cur_pos]; + // cur_byte is ~ + if (cur_byte == sym_escape) { + // create cur_bfr + if (cur_bfr == null) {cur_bfr = Bry_bfr_.New(); cur_bfr.Add_mid(src, src_bgn, cur_pos);} + + // eval nxt_byte + int nxt_pos = cur_pos + 1; + if (nxt_pos == src_end) throw Err_.new_wo_type("bry_eval:escape at eos", "src", src); + byte nxt_byte = src[nxt_pos]; + if (nxt_byte == sym_grp_bgn) { // ~{key|} -> eval; + Bry_eval_rslt sub = Eval_txt(depth + 1, src, nxt_pos + 1, src_end); // get "}" + cur_bfr.Add(Eval_grp(sub.Bry())); + cur_pos = sub.Pos(); + continue; + } + else if (nxt_byte == sym_escape) { + cur_bfr.Add_byte(nxt_byte); + cur_pos = nxt_pos + 1; + continue; + } + } + else if (depth > 0 && cur_byte == sym_grp_end) { + return cur_bfr == null + ? new Bry_eval_rslt(Bry_.Mid(src, src_bgn, cur_pos), cur_pos + 1) + : new Bry_eval_rslt(cur_bfr.To_bry_and_clear(), cur_pos + 1); + } + if (cur_bfr != null) cur_bfr.Add_byte(cur_byte); + ++cur_pos; + } + return cur_bfr == null ? null : new Bry_eval_rslt(cur_bfr.To_bry_and_clear(), src_end); + } + private byte[] Eval_grp(byte[] src) { + // search for "|" or "}" + boolean args_is_empty = true; + int src_end = src.length; + int key_bgn = 0, key_end = src_end; + for (int i = key_bgn; i < src_end; ++i) { + int key_end_byte = src[i]; + if (key_end_byte == sym_key_end) { + key_end = i; + args_is_empty = false; + break; + } + } + + // get wkr + Bry_eval_wkr wkr = (Bry_eval_wkr)hash.Get_by_mid(src, key_bgn, key_end); + if (wkr == null) throw Err_.new_wo_type("bry_eval:key not found", "src", src); + Bry_bfr bfr = Bry_bfr_.New(); + if (args_is_empty) { + wkr.Resolve(bfr, src, -1, -1); + } + else { + wkr.Resolve(bfr, src, key_end + 1, src_end); + } + return bfr.To_bry_and_clear(); + } + public static Bry_eval_mgr Dflt() {return new Bry_eval_mgr(Byte_ascii.Tilde, Byte_ascii.Pipe, Byte_ascii.Curly_bgn, Byte_ascii.Curly_end);} +} +class Bry_eval_rslt { + public Bry_eval_rslt(byte[] bry, int pos) { + this.bry = bry; this.pos = pos; + } + public byte[] Bry() {return bry;} private final byte[] bry; + public int Pos() {return pos;} private final int pos; +} diff --git a/400_xowa/src/gplx/core/brys/evals/Bry_eval_mgr__tst.java b/400_xowa/src/gplx/core/brys/evals/Bry_eval_mgr__tst.java index a27517de8..30531fb92 100644 --- a/400_xowa/src/gplx/core/brys/evals/Bry_eval_mgr__tst.java +++ b/400_xowa/src/gplx/core/brys/evals/Bry_eval_mgr__tst.java @@ -13,3 +13,38 @@ 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.core.brys.evals; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +import org.junit.*; import gplx.core.tests.*; +public class Bry_eval_mgr__tst { + private final Bry_eval_mgr__fxt fxt = new Bry_eval_mgr__fxt(); + @Test public void Text() {fxt.Test__eval("abc" , "abc");} + @Test public void Args_0() {fxt.Test__eval("abc~{test}xyz" , "abctestxyz");} + @Test public void Args_n() {fxt.Test__eval("abc~{concat|d|e|f}xyz" , "abcdefxyz");} + @Test public void Recur_1() {fxt.Test__eval("abc~{~{test}}xyz" , "abctestxyz");} + @Test public void Recur_2() {fxt.Test__eval("abc~{t~{concat|e|s}t}xyz" , "abctestxyz");} + @Test public void Grp_end() {fxt.Test__eval("a}b" , "a}b");} + @Test public void Escape() {fxt.Test__eval("a~~b" , "a~b");} + // @Test public void Eos() {fxt.Test__eval("a~" , "a~");} +} +class Bry_eval_mgr__fxt { + private final Bry_eval_mgr mgr = Bry_eval_mgr.Dflt().Add_many(new Bry_eval_wkr__test(), new Bry_eval_wkr__concat()); + public Bry_eval_mgr__fxt Test__eval(String raw, String expd) { + Gftest.Eq__bry(Bry_.new_u8(expd), mgr.Eval(Bry_.new_u8(raw))); + return this; + } +} +class Bry_eval_wkr__test implements Bry_eval_wkr { + public String Key() {return "test";} + public void Resolve(Bry_bfr rv, byte[] src, int src_bgn, int src_end) { + rv.Add_str_a7("test"); + } +} +class Bry_eval_wkr__concat implements Bry_eval_wkr { + public String Key() {return "concat";} + public void Resolve(Bry_bfr rv, byte[] src, int src_bgn, int src_end) { + byte[][] ary = Bry_split_.Split(src, src_bgn, src_end, Byte_ascii.Pipe, false); + for (byte[] itm : ary) { + rv.Add(itm); + } + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/core/brys/evals/Bry_eval_wkr.java b/400_xowa/src/gplx/core/brys/evals/Bry_eval_wkr.java index a27517de8..aa4c9d1d4 100644 --- a/400_xowa/src/gplx/core/brys/evals/Bry_eval_wkr.java +++ b/400_xowa/src/gplx/core/brys/evals/Bry_eval_wkr.java @@ -13,3 +13,8 @@ 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.core.brys.evals; import gplx.*; import gplx.core.*; import gplx.core.brys.*; +public interface Bry_eval_wkr { + String Key(); + void Resolve(Bry_bfr rv, byte[] src, int args_bgn, int args_end); +} diff --git a/400_xowa/src/gplx/core/btries/Btrie_u8_mgr_tst.java b/400_xowa/src/gplx/core/btries/Btrie_u8_mgr_tst.java index a27517de8..26973d569 100644 --- a/400_xowa/src/gplx/core/btries/Btrie_u8_mgr_tst.java +++ b/400_xowa/src/gplx/core/btries/Btrie_u8_mgr_tst.java @@ -13,3 +13,82 @@ 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.core.btries; import gplx.*; import gplx.core.*; +import org.junit.*; +import gplx.xowa.langs.cases.*; +public class Btrie_u8_mgr_tst { + @Before public void init() {fxt.Clear();} private Btrie_u8_mgr_fxt fxt = new Btrie_u8_mgr_fxt(); + @Test public void Ascii() { + fxt.Init_add(Bry_.new_a7("a") , "1"); + fxt.Init_add(Bry_.new_a7("abc") , "123"); + fxt.Test_match("a" , "1"); // single.exact + fxt.Test_match("abc" , "123"); // many.exact + fxt.Test_match("ab" , "1"); // single.more + fxt.Test_match("abcde" , "123"); // many.more + fxt.Test_match(" a" , null); // no_match + fxt.Test_match("aBC" , "123"); // upper + } + @Test public void Uft8() { + fxt.Init_add(Bry_.new_u8("aéi") , "1"); + fxt.Test_match("aéi" , "1"); // exact + fxt.Test_match("aÉi" , "1"); // upper.utf8 + fxt.Test_match("AÉI" , "1"); // upper.all + fxt.Test_match("AÉIo" , "1"); // trailing-char + fxt.Test_match("aei" , null); // no_match + } + @Test public void Uft8_match_pos() { + fxt.Init_add(Bry_.new_u8("aéi") , "1"); + fxt.Test_match_pos("aAÉI" , 1, "1"); // match at 1 + fxt.Test_match_pos("aAÉI" , 0, null); // no_match at 0 + } + @Test public void Uft8_asymmetric() { + fxt.Init_add(Bry_.new_u8("İ") , "1"); + fxt.Test_match("İ" , "1"); // exact=y; İ = Bry_.New_by_ints(196,176) + fxt.Test_match("i" , "1"); // lower=y; i = Bry_.New_by_ints(105) + fxt.Test_match("I" , null); // upper=n; I = Bry_.New_by_ints( 73); see Btrie_u8_itm and rv.asymmetric_bry + + fxt.Clear(); + fxt.Init_add(Bry_.new_u8("i") , "1"); + fxt.Test_match("i" , "1"); // exact=y + fxt.Test_match("I" , "1"); // upper=y + fxt.Test_match("İ" , "1"); // utf_8=y; note that "i" matches "İ" b/c hash is case-insensitive and "İ" lower-cases to "i"; DATE:2015-09-07 + } + @Test public void Utf8_asymmetric_multiple() { // PURPOSE: problems in original implementation of Hash_adp_bry and uneven source / target counts; + fxt.Init_add(Bry_.new_u8("İİ") , "1"); + fxt.Test_match("İİ" , "1"); // exact + fxt.Test_match("ii" , "1"); // lower + fxt.Test_match("İi" , "1"); // mixed + fxt.Test_match("iİ" , "1"); // mixed + } + @Test public void Utf8_asymmetric_upper() { // PURPOSE: "İ" and "I" should co-exist; see Btrie_u8_itm and called_by_match + fxt.Init_add(Bry_.new_u8("İ") , "1"); + fxt.Init_add(Bry_.new_u8("I") , "1"); + fxt.Test_match("İ" , "1"); // exact + fxt.Test_match("I" , "1"); // exact + fxt.Test_match("i" , "1"); // lower + } + @Test public void Utf8_asymmetric_symbols() { // PURPOSE: test Hash_adp_bry and multi-byte syms (chars that will never be cased) + fxt.Init_add(Bry_.new_u8("a_b") , "1"); + fxt.Test_match("a_b" , "1"); // exact: len=3 + fxt.Test_match("a†b" , null); // diff : len=3 + fxt.Test_match("a±b" , null); // diff : len=2 + fxt.Test_match("a_b" , null); // diff : len=1 + } +} +class Btrie_u8_mgr_fxt { + private Btrie_u8_mgr trie; + public void Clear() { + trie = Btrie_u8_mgr.new_(Xol_case_mgr_.U8()); + } + public void Init_add(byte[] key, Object val) {trie.Add_obj(key, val);} + public void Test_match_pos(String src_str, int bgn_pos, String expd) { + byte[] src = Bry_.new_u8(src_str); + Object actl = trie.Match_bgn_w_byte(src[bgn_pos], src, bgn_pos, src.length); + Tfds.Eq(expd, actl, src_str); + } + public void Test_match(String src_str, String expd) { + byte[] src = Bry_.new_u8(src_str); + Object actl = trie.Match_bgn_w_byte(src[0], src, 0, src.length); + Tfds.Eq(expd, actl, src_str); + } +} diff --git a/400_xowa/src/gplx/core/caches/Gfo_cache_data.java b/400_xowa/src/gplx/core/caches/Gfo_cache_data.java index a27517de8..d08bb79de 100644 --- a/400_xowa/src/gplx/core/caches/Gfo_cache_data.java +++ b/400_xowa/src/gplx/core/caches/Gfo_cache_data.java @@ -13,3 +13,32 @@ 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.core.caches; import gplx.*; import gplx.core.*; +import gplx.core.envs.*; +class Gfo_cache_data implements gplx.CompareAble, Rls_able { + private int count = 0; + public Gfo_cache_data(byte[] key, Rls_able val, int size) { + this.key = key; this.val = val; + this.size = size; + this.count = 1; + } + public byte[] Key() {return key;} private final byte[] key; + public Rls_able Val() {return val;} private Rls_able val; + public int Size() {return size;} private int size; + + public Object Val_and_update() { + ++count; + return val; + } + public void Val_(Rls_able val, int size) { + this.val = val; + this.size = size; + } + public int compareTo(Object obj) { + Gfo_cache_data comp = (Gfo_cache_data)obj; + return -Long_.Compare(count, comp.count); // "-" to sort most-recent first + } + public void Rls() { + val.Rls(); + } +} diff --git a/400_xowa/src/gplx/core/caches/Gfo_cache_mgr.java b/400_xowa/src/gplx/core/caches/Gfo_cache_mgr.java index a27517de8..f07466cc0 100644 --- a/400_xowa/src/gplx/core/caches/Gfo_cache_mgr.java +++ b/400_xowa/src/gplx/core/caches/Gfo_cache_mgr.java @@ -13,3 +13,78 @@ 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.core.caches; import gplx.*; import gplx.core.*; +public class Gfo_cache_mgr { + private final Ordered_hash hash = Ordered_hash_.New_bry(); + private final List_adp tmp_delete = List_adp_.New(); + public int Cur_size() {return cur_size;} private int cur_size; + public int Max_size() {return max_size;} public Gfo_cache_mgr Max_size_(int v) {max_size = v; return this;} private int max_size; + public int Reduce_by() {return reduce_by;} public Gfo_cache_mgr Reduce_by_(int v) {reduce_by = v; return this;} private int reduce_by; + public void Clear() { + synchronized (tmp_delete) { + hash.Clear(); + cur_size = 0; + } + } + public Object Get_by_key(byte[] key) { + Object o = hash.Get_by(key); + return o == null ? null : ((Gfo_cache_data)o).Val_and_update(); + } + public void Add_replace(byte[] key, Rls_able val, int size) { + Object o = hash.Get_by(key); + if (o == null) + Add(key, val, size); + else { + Gfo_cache_data itm = (Gfo_cache_data)o; + synchronized (itm) { + cur_size -= itm.Size(); + cur_size += size; + itm.Val_(val, size); + } + } + } + public void Add(byte[] key, Rls_able val, int size) { + synchronized (tmp_delete) { + if (hash.Has(key)) return; // THREAD: since Get is not locked, it's possible to Add the same item twice + cur_size += size; + Gfo_cache_data itm = new Gfo_cache_data(key, val, size); + hash.Add(key, itm); + if (cur_size > max_size) this.Reduce(); + } + } + private void Reduce() { + hash.Sort(); + int len = hash.Len(); + int list_size = 0; + for (int i = 0; i < len; ++i) { + Gfo_cache_data itm = (Gfo_cache_data)hash.Get_at(i); + int new_size = list_size + itm.Size(); + if (new_size > reduce_by) + tmp_delete.Add(itm); + else + list_size = new_size; + } + this.cur_size = list_size; + len = tmp_delete.Len(); + for (int i = 0; i < len; ++i) { + Gfo_cache_data itm = (Gfo_cache_data)tmp_delete.Get_at(i); + hash.Del(itm.Key()); + } + tmp_delete.Clear(); + } + public int Test__len() {return hash.Len();} + public Object Test__get_at(int i) { + Gfo_cache_data rv = (Gfo_cache_data)hash.Get_at(i); + return rv.Val(); + } + // NOTE: not called yet + /* + public void Del(byte[] key) { + Object o = hash.Get_by(key); if (o == null) return; + Gfo_cache_data itm = (Gfo_cache_data)o; + cur_size -= itm.Size(); + hash.Del(itm.Key()); + itm.Rls(); + } + */ +} diff --git a/400_xowa/src/gplx/core/caches/Gfo_cache_mgr_base.java b/400_xowa/src/gplx/core/caches/Gfo_cache_mgr_base.java index a27517de8..bce6ab437 100644 --- a/400_xowa/src/gplx/core/caches/Gfo_cache_mgr_base.java +++ b/400_xowa/src/gplx/core/caches/Gfo_cache_mgr_base.java @@ -13,3 +13,34 @@ 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.core.caches; import gplx.*; import gplx.core.*; +public class Gfo_cache_mgr_base { + private final Ordered_hash hash = Ordered_hash_.New_bry(); + public int Compress_max() {return compress_max;} public void Compress_max_(int v) {compress_max = v;} private int compress_max = 16; + public int Compress_to() {return compress_to;} public void Compress_to_(int v) {compress_to = v;} private int compress_to = 8; + protected Object Base_get_or_null(byte[] key) { + Object rv_obj = hash.Get_by(key); + return rv_obj == null ? null : ((Gfo_cache_itm)rv_obj).Val(); + } + protected void Base_add(byte[] key, Object val) { + if (hash.Count() >= compress_max) Compress(); + Gfo_cache_itm itm = new Gfo_cache_itm(key, val); + hash.Add(key, itm); + } + protected void Base_del(byte[] key) { + hash.Del(key); + } + public void Compress() { + hash.Sort_by(Gfo_cache_itm_comparer.Touched_asc); + int del_len = hash.Count() - compress_to; + List_adp del_list = List_adp_.New(); + for (int i = 0; i < del_len; i++) { + Gfo_cache_itm itm = (Gfo_cache_itm)hash.Get_at(i); + del_list.Add(itm); + } + for (int i = 0; i < del_len; i++) { + Gfo_cache_itm itm = (Gfo_cache_itm)del_list.Get_at(i); + hash.Del(itm.Key()); + } + } +} diff --git a/400_xowa/src/gplx/core/caches/Gfo_cache_mgr_bry.java b/400_xowa/src/gplx/core/caches/Gfo_cache_mgr_bry.java index a27517de8..7eb90ff3d 100644 --- a/400_xowa/src/gplx/core/caches/Gfo_cache_mgr_bry.java +++ b/400_xowa/src/gplx/core/caches/Gfo_cache_mgr_bry.java @@ -13,3 +13,39 @@ 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.core.caches; import gplx.*; import gplx.core.*; +import gplx.core.primitives.*; import gplx.core.envs.*; +public class Gfo_cache_mgr_bry extends Gfo_cache_mgr_base { + public Object Get_or_null(byte[] key) {return Base_get_or_null(key);} + public void Add(byte[] key, Object val) {Base_add(key, val);} + public void Del(byte[] key) {Base_del(key);} +} +class Gfo_cache_itm { + public Gfo_cache_itm(Object key, Object val) {this.key = key; this.val = val; this.Touched_update();} + public Object Key() {return key;} private Object key; + public Object Val() {return val;} private Object val; + public long Touched() {return touched;} private long touched; + public Gfo_cache_itm Touched_update() {touched = System_.Ticks(); return this;} +} +class Gfo_cache_itm_comparer implements gplx.core.lists.ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + Gfo_cache_itm lhs = (Gfo_cache_itm)lhsObj; + Gfo_cache_itm rhs = (Gfo_cache_itm)rhsObj; + return Long_.Compare(lhs.Touched(), rhs.Touched()); + } + public static final Gfo_cache_itm_comparer Touched_asc = new Gfo_cache_itm_comparer(); // TS.static +} +class Io_url_exists_mgr { + private gplx.core.caches.Gfo_cache_mgr_bry cache_mgr = new gplx.core.caches.Gfo_cache_mgr_bry(); + public Io_url_exists_mgr() { + cache_mgr.Compress_max_(Int_.Max_value); + } + public boolean Has(Io_url url) { + byte[] url_key = url.RawBry(); + Object rv_obj = cache_mgr.Get_or_null(url_key); + if (rv_obj != null) return ((Bool_obj_ref)rv_obj).Val(); // cached val exists; use it + boolean exists = Io_mgr.Instance.ExistsFil(url); + cache_mgr.Add(url_key, Bool_obj_ref.new_(exists)); + return exists; + } +} diff --git a/400_xowa/src/gplx/core/caches/Gfo_cache_mgr_tst.java b/400_xowa/src/gplx/core/caches/Gfo_cache_mgr_tst.java index a27517de8..a91cd39da 100644 --- a/400_xowa/src/gplx/core/caches/Gfo_cache_mgr_tst.java +++ b/400_xowa/src/gplx/core/caches/Gfo_cache_mgr_tst.java @@ -13,3 +13,59 @@ 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.core.caches; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.tests.*; import gplx.core.envs.*; +public class Gfo_cache_mgr_tst { + @Before public void init() {fxt.Clear();} private final Gfo_cache_mgr_fxt fxt = new Gfo_cache_mgr_fxt(); + @Test public void Basic() { + fxt.Exec__add("a"); + fxt.Test__cur_size(1); + fxt.Test__itms("a"); + } + @Test public void Reduce() { + fxt.Exec__add("a", "b", "c", "d", "e"); + fxt.Test__cur_size(2); + fxt.Test__itms("a", "b"); + } + @Test public void Reduce_after_get() { + fxt.Exec__add("a", "b", "c", "d"); + fxt.Exec__get("a", "c"); + fxt.Exec__add("e"); + fxt.Test__itms("a", "c"); + } +} +class Gfo_cache_mgr_fxt { + private final Gfo_cache_mgr mgr = new Gfo_cache_mgr().Max_size_(4).Reduce_by_(2); + public void Clear() {mgr.Clear();} + public Gfo_cache_mgr_fxt Exec__add(String... ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) { + String itm = ary[i]; + byte[] key = Bry_.new_u8(itm); + mgr.Add(key, new Gfo_cache_itm_mock(itm), key.length); + } + return this; + } + public Gfo_cache_mgr_fxt Exec__get(String... ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) { + String itm = ary[i]; + mgr.Get_by_key(Bry_.new_u8(itm)); + } + return this; + } + public Gfo_cache_mgr_fxt Test__cur_size(int expd) {Gftest.Eq__int(expd, mgr.Cur_size(), "cur_size"); return this;} + public Gfo_cache_mgr_fxt Test__itms(String... expd) { + int len = mgr.Test__len(); + String[] actl = new String[len]; + for (int i = 0; i < len; ++i) + actl[i] = ((Gfo_cache_itm_mock)mgr.Test__get_at(i)).Val(); + Gftest.Eq__ary(expd, actl, "itms"); + return this; + } +} +class Gfo_cache_itm_mock implements Rls_able { + public Gfo_cache_itm_mock(String val) {this.val = val;} + public String Val() {return val;} private String val; + public void Rls() {} +} diff --git a/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_itm.java b/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_itm.java index a27517de8..8a13cf4f9 100644 --- a/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_itm.java +++ b/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_itm.java @@ -13,3 +13,42 @@ 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.core.consoles; import gplx.*; import gplx.core.*; +public class Gfo_cmd_arg_itm { + public Gfo_cmd_arg_itm(int tid, boolean reqd, String key, int val_tid) {this.tid = tid; this.reqd = reqd; this.key = key; this.val_tid = val_tid;} + public int Tid() {return tid;} private final int tid; + public boolean Reqd() {return reqd;} private final boolean reqd; + public String Key() {return key;} private final String key; + public int Val_tid() {return val_tid;} private int val_tid; + public Object Val() {return val;} public Gfo_cmd_arg_itm Val_(Object v) {this.val = v; dirty = true; return this;} private Object val; + public String Note() {return note;} public Gfo_cmd_arg_itm Note_(String v) {note = v; return this;} private String note = ""; + public String Example() {return example;} public Gfo_cmd_arg_itm Example_(String v) {example = v; return this;} private String example = ""; + public Object Dflt() {return dflt;} public Gfo_cmd_arg_itm Dflt_(Object v) {dflt = v; return this;} private Object dflt; + public boolean Dirty() {return dirty;} private boolean dirty; + public void Clear() { + dirty = false; + val = null; + } + public Gfo_cmd_arg_itm Example_url_(String v) { + Example_(v); + this.val_tid = Gfo_cmd_arg_itm_.Val_tid_url; + return this; + } + public String Reqd_str() {return reqd ? "required" : "optional";} + public String Val_tid_str() { + switch (val_tid) { + case Gfo_cmd_arg_itm_.Val_tid_string: return "string"; + case Gfo_cmd_arg_itm_.Val_tid_yn: return "y/n"; + case Gfo_cmd_arg_itm_.Val_tid_url: return "path"; + default: return "unknown"; + } + } + public boolean Val_as_bool() {return Bool_.Cast(val);} + public boolean Val_as_bool_or(boolean or) {return val == null ? or : String_.Eq((String)val, "y");} + public String Val_as_str_or(String or) {return val == null ? or : (String)val;} + public String Val_as_str() {return (String)val;} + public int Val_as_int_or(int or) {return val == null ? or : Int_.Parse_or((String)val, or);} + public Io_url Val_as_url__rel_dir_or(Io_url owner_dir, Io_url or) {return Val_as_url__rel_url_or(Bool_.Y, owner_dir, or);} + public Io_url Val_as_url__rel_fil_or(Io_url owner_dir, Io_url or) {return Val_as_url__rel_url_or(Bool_.N, owner_dir, or);} + public Io_url Val_as_url__rel_url_or(boolean to_dir, Io_url owner_dir, Io_url or) {return Gfo_cmd_arg_itm_.Val_as_url__rel_url_or(Val_as_str(), to_dir, owner_dir, or);} +} diff --git a/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_itm_.java b/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_itm_.java index a27517de8..de5752018 100644 --- a/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_itm_.java +++ b/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_itm_.java @@ -13,3 +13,34 @@ 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.core.consoles; import gplx.*; import gplx.core.*; +import gplx.core.envs.*; +public class Gfo_cmd_arg_itm_ { + public static final int Tid_general = 0, Tid_system = 1; + public static final int Val_tid_string = 0, Val_tid_yn = 1, Val_tid_url = 2, Val_tid_list_string = 3; + public static Gfo_cmd_arg_itm req_(String key) {return new Gfo_cmd_arg_itm(Tid_general, Bool_.Y, key, Val_tid_string);} + public static Gfo_cmd_arg_itm opt_(String key) {return new Gfo_cmd_arg_itm(Tid_general, Bool_.N, key, Val_tid_string);} + public static Gfo_cmd_arg_itm new_(String key, boolean reqd, int val_tid) {return new Gfo_cmd_arg_itm(Tid_general, reqd , key, val_tid);} + public static Gfo_cmd_arg_itm sys_(String key) {return new Gfo_cmd_arg_itm(Tid_system , Bool_.N, key, Val_tid_yn);} + public static Gfo_cmd_arg_itm new_(int tid, String key, boolean reqd, int val_tid) {return new Gfo_cmd_arg_itm(tid , reqd , key, val_tid);} + public static Io_url Val_as_url__rel_url_or(String raw, boolean to_dir, Io_url owner_dir, Io_url or) { + if (raw == null) return or; + byte val_has_dir = Op_sys.Tid_nil; // if raw is to_dir, use it literally (only checking for closing dir_spr); if it's just a name, assume a simple relative path + if (String_.Has(raw, Op_sys.Lnx.Fsys_dir_spr_str())) + val_has_dir = Op_sys.Tid_lnx; + else if (String_.Has(raw, Op_sys.Wnt.Fsys_dir_spr_str())) + val_has_dir = Op_sys.Tid_wnt; + if (val_has_dir != Op_sys.Tid_nil) { + if (to_dir) { // NOTE: need to do extra logic to guarantee trailing "/"; JAVA:7 apparently strips "/to_dir/" to "/to_dir" when passed in as argument; DATE:2013-03-20 + String val_dir_spr = val_has_dir == Op_sys.Tid_lnx ? Op_sys.Lnx.Fsys_dir_spr_str() : Op_sys.Wnt.Fsys_dir_spr_str(); + if (!String_.Has_at_end(raw, val_dir_spr)) + raw += val_dir_spr; + return Io_url_.new_dir_(raw); + } + else + return Io_url_.new_fil_(raw); + } + else + return to_dir ? owner_dir.GenSubDir(raw) : owner_dir.GenSubFil(raw); + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_mgr.java b/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_mgr.java index a27517de8..4c88a7220 100644 --- a/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_mgr.java +++ b/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_mgr.java @@ -13,3 +13,77 @@ 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.core.consoles; import gplx.*; import gplx.core.*; +public class Gfo_cmd_arg_mgr { + private final Ordered_hash hash = Ordered_hash_.New(); + private final List_adp err_list = List_adp_.New(), tmp_vals = List_adp_.New(); + public String[] Orig_ary() {return orig_ary;} private String[] orig_ary; + public void Reset() { + hash.Clear(); + this.Clear(); + } + public void Clear() { + int len = hash.Count(); + for (int i = 0; i < len; ++i) { + Gfo_cmd_arg_itm itm = (Gfo_cmd_arg_itm)hash.Get_at(i); + itm.Clear(); + } + err_list.Clear(); + } + public Gfo_cmd_arg_mgr Reg_many(Gfo_cmd_arg_itm... ary) {for (Gfo_cmd_arg_itm itm : ary) Reg(itm); return this;} + public int Len() {return hash.Count();} + public boolean Has(String k) { + Gfo_cmd_arg_itm arg = (Gfo_cmd_arg_itm)hash.Get_by(k); + return arg != null && arg.Dirty(); + } + public boolean Get_by_as_bool(String k) { + Gfo_cmd_arg_itm arg = (Gfo_cmd_arg_itm)hash.Get_by(k); + return arg != null && arg.Val() != null && arg.Val_as_bool(); + } + public Gfo_cmd_arg_itm Get_at(int i) {return (Gfo_cmd_arg_itm)hash.Get_at(i);} + public Gfo_cmd_arg_itm Get_by(String key) {return (Gfo_cmd_arg_itm)hash.Get_by(key);} + public void Parse(String[] orig_ary) { + this.Clear(); + this.orig_ary = orig_ary; int orig_len = orig_ary.length; + Gfo_cmd_arg_itm cur_itm = null; + int orig_idx = 0; + while (true) { + boolean done = orig_idx == orig_len; + String itm = done ? "" : orig_ary[orig_idx++]; + boolean itm_is_key = String_.Has_at_bgn(itm, Key_prefix); // has "--" -> is key + if ( cur_itm != null // pending itm + && (itm_is_key || done)) { // cur arg is key ("--key2"), or all done + cur_itm.Val_(Gfo_cmd_arg_mgr_.Parse_ary_to_str(this, cur_itm.Val_tid(), tmp_vals.To_str_ary_and_clear())); + cur_itm = null; + } + if (done) break; + if (itm_is_key) { + String key = String_.Mid(itm, prefix_len); + Object o = hash.Get_by(key); if (o == null) {Errs__add(Gfo_cmd_arg_mgr_.Err__key__unknown , key); continue;} + cur_itm = (Gfo_cmd_arg_itm)o; if (cur_itm.Dirty()) {Errs__add(Gfo_cmd_arg_mgr_.Err__key__duplicate , key); continue;} + } + else { + if (cur_itm == null) {Errs__add(Gfo_cmd_arg_mgr_.Err__key__missing, itm); continue;} // should only happen if 1st itm is not "--%" + tmp_vals.Add(itm); + } + } + + // calc .Reqd and .Dflt + int len = hash.Count(); + for (int i = 0; i < len; ++i) { + cur_itm = (Gfo_cmd_arg_itm)hash.Get_at(i); + if (!cur_itm.Dirty()) { // arg not passed + if (cur_itm.Reqd()) // arg required but no value passed; add error + Errs__add(Gfo_cmd_arg_mgr_.Err__val__required, cur_itm.Key()); + else if (cur_itm.Dflt() != null) // arg has default + cur_itm.Val_(cur_itm.Dflt()); + } + } + } + public boolean Errs__exist() {return err_list.Count() > 0;} + public void Errs__add(String key, String val) {err_list.Add(key + ": " + val);} + public String[] Errs__to_str_ary() {return err_list.To_str_ary();} + public Gfo_cmd_arg_itm[] To_ary() {return (Gfo_cmd_arg_itm[])hash.To_ary(Gfo_cmd_arg_itm.class);} + private void Reg(Gfo_cmd_arg_itm defn) {hash.Add(defn.Key(), defn);} + public static final String Key_prefix = "--"; private static final int prefix_len = 2; +} diff --git a/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_mgr_.java b/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_mgr_.java index a27517de8..bb36b8ddd 100644 --- a/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_mgr_.java +++ b/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_mgr_.java @@ -13,3 +13,28 @@ 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.core.consoles; import gplx.*; import gplx.core.*; +class Gfo_cmd_arg_mgr_ { + public static final String + Err__key__unknown = "unknown key" + , Err__key__duplicate = "duplicate key" + , Err__key__missing = "first argument must be prefixed with --" + , Err__val__required = "value is required" + , Err__val__invalid__yn = "value must be either y or n" + ; + public static Object Parse_ary_to_str(Gfo_cmd_arg_mgr mgr, int val_tid, String[] ary) { + String itm = ary.length == 0 ? "" : ary[0]; + switch (val_tid) { + case Gfo_cmd_arg_itm_.Val_tid_string: return itm; + case Gfo_cmd_arg_itm_.Val_tid_url: return itm; // NOTE: do not parse urls as it can either be absolute (C:\dir\fil.txt) or relative (fil.txt). relative cannot be parsed without knowing owner dir + case Gfo_cmd_arg_itm_.Val_tid_yn: + int itm_as_int = Yn.parse_as_int(itm); + if (itm_as_int == Bool_.__int) { + mgr.Errs__add(Gfo_cmd_arg_mgr_.Err__val__invalid__yn, itm); + return null; + } + return itm_as_int == Bool_.Y_int; + default: throw Err_.new_unhandled(val_tid); + } + } +} diff --git a/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_mgr_printer.java b/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_mgr_printer.java index a27517de8..41b11d43e 100644 --- a/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_mgr_printer.java +++ b/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_mgr_printer.java @@ -13,3 +13,81 @@ 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.core.consoles; import gplx.*; import gplx.core.*; +public class Gfo_cmd_arg_mgr_printer { + private final Gfo_cmd_arg_mgr arg_mgr; + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + public Gfo_cmd_arg_mgr_printer(Gfo_cmd_arg_mgr arg_mgr) {this.arg_mgr = arg_mgr;} + public boolean Print(Gfo_usr_dlg usr_dlg, String header, String app_name, String key__print_help, String key__print_header, String key__print_args) { + if (arg_mgr.Get_by_as_bool(key__print_header)) + usr_dlg.Note_gui_none("", "", header); + if (arg_mgr.Errs__exist()) { + usr_dlg.Note_none("", "", Get_args()); + usr_dlg.Note_none("", "", Get_fail()); + usr_dlg.Note_none("", "", Get_help(app_name)); + return false; + } + if (arg_mgr.Has(key__print_help)) { + usr_dlg.Note_none("", "", Get_help(app_name)); + return false; + } + if (arg_mgr.Get_by_as_bool(key__print_args)) + usr_dlg.Note_none("", "", Get_args()); + return true; + } + public String Get_args() { + tmp_bfr.Add_byte_nl(); + tmp_bfr.Add_str_a7_w_nl("arguments:"); + String[] orig_ary = arg_mgr.Orig_ary(); + int len = orig_ary.length; + if (len == 0) { + tmp_bfr.Add_str_a7_w_nl(" **** NONE ****"); + tmp_bfr.Add_str_a7_w_nl(" use --help to show help"); + } + else { + for (int i = 0; i < len; i++) { + String line = String_.Format(" [{0}] = '{1}'", i, orig_ary[i]); + tmp_bfr.Add_str_u8_w_nl(line); + } + } + return tmp_bfr.To_str_and_clear(); + } + public String Get_fail() { + tmp_bfr.Add_str_a7_w_nl("** error: "); + String[] err_ary = arg_mgr.Errs__to_str_ary(); + int len = err_ary.length; + for (int i = 0; i < len; ++i) + tmp_bfr.Add_str_u8_w_nl(" " + err_ary[i]); + tmp_bfr.Add_byte_nl(); + tmp_bfr.Add_str_a7_w_nl(String_.Repeat("-", 80)); + return tmp_bfr.To_str_and_clear(); + } + public String Get_help(String app_name) { + tmp_bfr.Add_str_a7_w_nl("example:"); + tmp_bfr.Add_str_a7(String_.Format(" java -jar {0}.jar", app_name)); + int key_max = 0, tid_max = 0; + int len = arg_mgr.Len(); + for (int i = 0; i < len; i++) { + Gfo_cmd_arg_itm arg = arg_mgr.Get_at(i); if (arg.Tid() != Gfo_cmd_arg_itm_.Tid_general) continue; // skip header, help + tmp_bfr.Add_str_a7(" ").Add_str_a7(Gfo_cmd_arg_mgr.Key_prefix).Add_str_u8(arg.Key()).Add_str_a7(" ").Add_str_u8(arg.Example()); + int key_len = String_.Len(arg.Key()); if (key_len > key_max) key_max = key_len; + int tid_len = String_.Len(String_.Format("[{0}:{1}]", arg.Reqd_str(), arg.Val_tid_str())); if (tid_len > tid_max) tid_max = tid_len; + } + tmp_bfr.Add_byte_nl().Add_byte_nl(); + tmp_bfr.Add_str_a7_w_nl("detail:"); + for (int i = 0; i < len; i++) { + Gfo_cmd_arg_itm arg = (Gfo_cmd_arg_itm)arg_mgr.Get_at(i); + tmp_bfr.Add_str_a7(" ").Add_str_a7(Gfo_cmd_arg_mgr.Key_prefix) + .Add_str_u8(String_.PadEnd(arg.Key(), key_max + 1, " ")) + .Add_str_u8(String_.PadEnd(String_.Format("[{0}:{1}]", arg.Reqd_str(), arg.Val_tid_str()), tid_max, " ")); + if (arg.Dflt() != null) { + String dflt_val = Object_.Xto_str_strict_or_null_mark(arg.Dflt()); + tmp_bfr.Add_str_u8(String_.Format(" default={0}", dflt_val)); + } + tmp_bfr.Add_byte_nl(); + if (arg.Note() != null) + tmp_bfr.Add_str_a7(" ").Add_str_u8(arg.Note()).Add_byte_nl(); + } + return tmp_bfr.To_str_and_clear(); + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_mgr_tst.java b/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_mgr_tst.java index a27517de8..8d1b2b8fe 100644 --- a/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_mgr_tst.java +++ b/400_xowa/src/gplx/core/consoles/Gfo_cmd_arg_mgr_tst.java @@ -13,3 +13,109 @@ 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.core.consoles; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.tests.*; import gplx.core.envs.*; +public class Gfo_cmd_arg_mgr_tst { + @Before public void init() {fxt.Clear();} private final Gfo_cmd_arg_mgr_fxt fxt = new Gfo_cmd_arg_mgr_fxt(); + @Test public void Val__many() { + fxt.Init_args(fxt.Make_arg("a"), fxt.Make_arg("b")); + fxt.Exec_process("--a", "0", "--b", "1"); + fxt.Test_errs_none(); + fxt.Test_actl(fxt.Make_chkr("a", "0"), fxt.Make_chkr("b", "1")); + } + @Test public void Val__yn() { + fxt.Init_args(fxt.Make_arg("a", Gfo_cmd_arg_itm_.Val_tid_yn), fxt.Make_arg("b", Gfo_cmd_arg_itm_.Val_tid_yn)); + fxt.Exec_process("--a", "y", "--b", "n"); + fxt.Test_errs_none(); + fxt.Test_actl(fxt.Make_chkr("a", Bool_.Y), fxt.Make_chkr("b", Bool_.N)); + } + @Test public void Dflt() { + fxt.Init_args(fxt.Make_arg("a", Gfo_cmd_arg_itm_.Val_tid_yn).Dflt_(Bool_.Y)); + fxt.Exec_process("--a", "n").Test_actl(fxt.Make_chkr("a", Bool_.N)); // if val, use it + fxt.Exec_process() .Test_actl(fxt.Make_chkr("a", Bool_.Y)); // if no val, use default + } + @Test public void Err__key__unknown() { + fxt.Init_args(fxt.Make_arg("a")); + fxt.Exec_process("--b"); + fxt.Test_errs(Gfo_cmd_arg_mgr_.Err__key__unknown); + } + @Test public void Err__key__missing() { + fxt.Init_args(fxt.Make_arg("a")); + fxt.Exec_process("a"); + fxt.Test_errs(Gfo_cmd_arg_mgr_.Err__key__missing); + } + @Test public void Err__key__dupe() { + fxt.Init_args(fxt.Make_arg("a")); + fxt.Exec_process("--a", "0", "--a", "0"); + fxt.Test_errs(Gfo_cmd_arg_mgr_.Err__key__duplicate); + } + @Test public void Err__val__reqd() { + fxt.Init_args(fxt.Make_arg("a", Bool_.Y), fxt.Make_arg("b", Bool_.N)); + fxt.Exec_process("--b", "1"); + fxt.Test_errs(Gfo_cmd_arg_mgr_.Err__val__required); + } + @Test public void Err__val__parse__yn() { + fxt.Init_args(fxt.Make_arg("a", Gfo_cmd_arg_itm_.Val_tid_yn)); + fxt.Exec_process("--a", "x"); + fxt.Test_errs(Gfo_cmd_arg_mgr_.Err__val__invalid__yn); + } + @Test public void Val_as_url_rel_dir_or() { // PURPOSE: "/xowa" -> "/xowa/" + String root_dir = Op_sys.Cur().Tid_is_wnt() ? "C:\\" : "/", dir_spr = Op_sys.Cur().Fsys_dir_spr_str(); + fxt.Test_val_as_url_rel_dir_or(root_dir, dir_spr, root_dir + "sub" , root_dir + "sub" + dir_spr); // /sub -> /sub/ + fxt.Test_val_as_url_rel_dir_or(root_dir, dir_spr, root_dir + "sub" + dir_spr , root_dir + "sub" + dir_spr); // /sub/ -> /sub/ + fxt.Test_val_as_url_rel_dir_or(root_dir, dir_spr, "sub" , root_dir + "dir" + dir_spr + "sub" + dir_spr); // sub -> /dir/sub/ + } +} +class Gfo_cmd_arg_mgr_fxt { + private final Tst_mgr tst_mgr = new Tst_mgr(); + public Gfo_usr_dlg Usr_dlg() {return usr_dlg;} Gfo_usr_dlg usr_dlg; + public Gfo_cmd_arg_mgr Mgr() {return mgr;} private final Gfo_cmd_arg_mgr mgr = new Gfo_cmd_arg_mgr(); + public Gfo_cmd_arg_mgr_fxt Clear() { + if (usr_dlg == null) + usr_dlg = Gfo_usr_dlg_.Test(); + mgr.Reset(); + usr_dlg.Gui_wkr().Clear(); + return this; + } + public Gfo_cmd_arg_itm Make_arg(String key) {return Make_arg(key, false);} + public Gfo_cmd_arg_itm Make_arg(String key, int tid) {return Make_arg(key, false, tid);} + public Gfo_cmd_arg_itm Make_arg(String key, boolean reqd) {return Gfo_cmd_arg_itm_.new_(key, reqd, Gfo_cmd_arg_itm_.Val_tid_string);} + public Gfo_cmd_arg_itm Make_arg(String key, boolean reqd, int tid) {return Gfo_cmd_arg_itm_.new_(key, reqd, tid);} + public Gfo_cmd_itm_chkr Make_chkr(String key, Object val) {return new Gfo_cmd_itm_chkr(key, val);} + public Gfo_cmd_arg_mgr_fxt Init_args(Gfo_cmd_arg_itm... v) {mgr.Reg_many(v); return this;} + public Gfo_cmd_arg_mgr_fxt Exec_process(String... v) {mgr.Parse(v); return this;} + public Gfo_cmd_arg_mgr_fxt Test_actl(Gfo_cmd_itm_chkr... expd) { + Gfo_cmd_arg_itm[] actl = mgr.To_ary(); + tst_mgr.Tst_ary("", expd, actl); + return this; + } + public Gfo_cmd_arg_mgr_fxt Test_errs_none() {return Test_errs(String_.Ary_empty);} + public Gfo_cmd_arg_mgr_fxt Test_errs(String... expd) { + String[] actl = mgr.Errs__to_str_ary(); + int len = actl.length; + for (int i = 0; i < len; ++i) { // extract key part; EX: "unknown key: abc" -> unknown key + actl[i] = String_.GetStrBefore(actl[i], ":"); + } + Tfds.Eq_ary_str(expd, actl); + return this; + } + public Gfo_cmd_arg_mgr_fxt Test_write(String... expd) { + Tfds.Eq_ary_str(expd, ((Gfo_usr_dlg__gui_test)usr_dlg.Gui_wkr()).Msgs().To_str_ary_and_clear()); + return this; + } + public void Test_val_as_url_rel_dir_or(String root_dir, String dir_spr, String val, String expd) { + Io_url actl = Make_arg("key").Val_(val).Val_as_url__rel_dir_or(Io_url_.new_dir_(root_dir).GenSubDir("dir"), null); + Tfds.Eq(expd, actl.Raw()); + } +} +class Gfo_cmd_itm_chkr implements Tst_chkr { + public Gfo_cmd_itm_chkr(String key, Object val) {this.key = key; this.val = val;} private String key; Object val; + public Class TypeOf() {return Gfo_cmd_arg_itm.class;} + public int Chk(Tst_mgr mgr, String path, Object actl_obj) { + Gfo_cmd_arg_itm actl = (Gfo_cmd_arg_itm)actl_obj; + int err = 0; + err += mgr.Tst_val(false, path, "key", key, actl.Key()); + err += mgr.Tst_val(false, path, "val", val, actl.Val()); + return err; + } +} diff --git a/400_xowa/src/gplx/core/enums/Gfo_enum_grp.java b/400_xowa/src/gplx/core/enums/Gfo_enum_grp.java index a27517de8..1e0135a8c 100644 --- a/400_xowa/src/gplx/core/enums/Gfo_enum_grp.java +++ b/400_xowa/src/gplx/core/enums/Gfo_enum_grp.java @@ -13,3 +13,27 @@ 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.core.enums; import gplx.*; import gplx.core.*; +class Gfo_enum_grp { +// private Ordered_hash itms = Ordered_hash_.New(); + public Gfo_enum_grp(Guid_adp uid, String key, int id, String name, int sort, String xtn) { + this.uid = uid; this.key = key; this.id = id; this.name = name; this.sort = sort; this.xtn = xtn; + } + public Guid_adp Uid() {return uid;} private Guid_adp uid; + public String Key() {return key;} private String key; + public int Id() {return id;} private int id; + public String Name() {return name;} private String name; + public int Sort() {return sort;} private int sort; + public String Xtn() {return xtn;} private String xtn; +} +class Gfo_enum_itm { + public Gfo_enum_itm(Guid_adp uid, String key, int id, String name, int sort, String xtn) { + this.uid = uid; this.key = key; this.id = id; this.name = name; this.sort = sort; this.xtn = xtn; + } + public Guid_adp Uid() {return uid;} private Guid_adp uid; + public String Key() {return key;} private String key; + public int Id() {return id;} private int id; + public String Name() {return name;} private String name; + public int Sort() {return sort;} private int sort; + public String Xtn() {return xtn;} private String xtn; +} diff --git a/400_xowa/src/gplx/core/flds/Gfo_fld_base.java b/400_xowa/src/gplx/core/flds/Gfo_fld_base.java index a27517de8..280949bb1 100644 --- a/400_xowa/src/gplx/core/flds/Gfo_fld_base.java +++ b/400_xowa/src/gplx/core/flds/Gfo_fld_base.java @@ -13,3 +13,34 @@ 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.core.flds; import gplx.*; import gplx.core.*; +public class Gfo_fld_base { + public byte Row_dlm() {return row_dlm;} public Gfo_fld_base Row_dlm_(byte v) {row_dlm = v; return this;} protected byte row_dlm = Byte_ascii.Nl; + public byte Fld_dlm() {return fld_dlm;} public Gfo_fld_base Fld_dlm_(byte v) {fld_dlm = v; return this;} protected byte fld_dlm = Byte_ascii.Pipe; + public byte Escape_dlm() {return escape_dlm;} public Gfo_fld_base Escape_dlm_(byte v) {escape_dlm = v; return this;} protected byte escape_dlm = Byte_ascii.Tilde; + public byte Quote_dlm() {return quote_dlm;} public Gfo_fld_base Quote_dlm_(byte v) {quote_dlm = v; return this;} protected byte quote_dlm = Byte_ascii.Null; + public Gfo_fld_base Escape_reg(byte b) {return Escape_reg(b, b);} + public byte[] Escape_decode() {return decode_regy;} + public Gfo_fld_base Escape_reg(byte key, byte val) {encode_regy[key] = val; decode_regy[val] = key; return this;} protected byte[] decode_regy = new byte[256]; protected byte[] encode_regy = new byte[256]; + public Gfo_fld_base Escape_clear() { + for (int i = 0; i < 256; i++) + decode_regy[i] = Byte_ascii.Null; + for (int i = 0; i < 256; i++) + encode_regy[i] = Byte_ascii.Null; + return this; + } + Gfo_fld_base Ini_common() { + return Escape_reg(Byte_ascii.Nl, Byte_ascii.Ltr_n).Escape_reg(Byte_ascii.Tab, Byte_ascii.Ltr_t).Escape_reg(Byte_ascii.Cr, Byte_ascii.Ltr_r) + .Escape_reg(Byte_ascii.Backfeed, Byte_ascii.Ltr_b); // .Escape_reg(Byte_ascii.Null, Byte_ascii.Num_0) + } + protected Gfo_fld_base Ctor_xdat_base() { + return Escape_clear().Ini_common() + .Fld_dlm_(Byte_ascii.Pipe).Row_dlm_(Byte_ascii.Nl).Escape_dlm_(Byte_ascii.Tilde).Quote_dlm_(Byte_ascii.Null) + .Escape_reg(Byte_ascii.Pipe, Byte_ascii.Ltr_p).Escape_reg(Byte_ascii.Tilde); + } + protected Gfo_fld_base Ctor_sql_base() { + return Escape_clear().Ini_common() + .Fld_dlm_(Byte_ascii.Comma).Row_dlm_(Byte_ascii.Paren_end).Escape_dlm_(Byte_ascii.Backslash).Quote_dlm_(Byte_ascii.Apos) + .Escape_reg(Byte_ascii.Backslash).Escape_reg(Byte_ascii.Quote).Escape_reg(Byte_ascii.Apos); // , Escape_eof = Bry_.new_a7("\\Z") + } +} diff --git a/400_xowa/src/gplx/core/flds/Gfo_fld_rdr.java b/400_xowa/src/gplx/core/flds/Gfo_fld_rdr.java index a27517de8..bd4983be5 100644 --- a/400_xowa/src/gplx/core/flds/Gfo_fld_rdr.java +++ b/400_xowa/src/gplx/core/flds/Gfo_fld_rdr.java @@ -13,3 +13,112 @@ 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.core.flds; import gplx.*; import gplx.core.*; +import gplx.core.encoders.*; +public class Gfo_fld_rdr extends Gfo_fld_base { + private Bry_bfr bfr = Bry_bfr_.New(); + public byte[] Data() {return data;} public Gfo_fld_rdr Data_(byte[] v) {data = v; data_len = v.length; pos = 0; return this;} private byte[] data; int data_len; + public int Pos() {return pos;} public Gfo_fld_rdr Pos_(int v) {pos = v; return this;} private int pos; + public int Fld_bgn() {return fld_bgn;} public Gfo_fld_rdr Fld_bgn_(int v) {fld_bgn = v; return this;} private int fld_bgn; + public int Fld_end() {return fld_end;} public Gfo_fld_rdr Fld_end_(int v) {fld_end = v; return this;} private int fld_end; + public int Fld_idx() {return fld_idx;} private int fld_idx; + public int Row_idx() {return row_idx;} private int row_idx; + public void Ini(byte[] data, int pos) {this.data = data; this.data_len = data.length; this.pos = pos;} + + public String Read_str_simple() {Move_next_simple(); return String_.new_u8(data, fld_bgn, fld_end);} + public byte[] Read_bry_simple() {Move_next_simple(); return Bry_.Mid(data, fld_bgn, fld_end);} // was Mid_by_len???; 20120915 + public int Read_int_base85_lenN(int len) {fld_bgn = pos; fld_end = pos + len - 1 ; pos = pos + len + 1 ; return Base85_.To_int_by_bry(data, fld_bgn, fld_end);} + public int Read_int_base85_len5() {fld_bgn = pos; fld_end = pos + 4 ; pos = pos + 6 ; return Base85_.To_int_by_bry(data, fld_bgn, fld_end);} + public int Read_int() {Move_next_simple(); return Bry_.To_int_or(data, fld_bgn, fld_end, -1);} + public byte Read_int_as_byte() {Move_next_simple(); return (byte)Bry_.To_int_or(data, fld_bgn, fld_end, -1);} + public byte Read_byte() {Move_next_simple(); return data[fld_bgn];} + public double Read_double() {Move_next_simple(); return Bry_.To_double(data, fld_bgn, fld_end);} + public DateAdp Read_dte() {// NOTE: fmt = yyyyMMdd HHmmss.fff + int y = 0, M = 0, d = 0, H = 0, m = 0, s = 0, f = 0; + if (pos < data_len && data[pos] == row_dlm) {++pos; ++row_idx; fld_idx = 0;} fld_bgn = pos; + y += (data[fld_bgn + 0] - Byte_ascii.Num_0) * 1000; + y += (data[fld_bgn + 1] - Byte_ascii.Num_0) * 100; + y += (data[fld_bgn + 2] - Byte_ascii.Num_0) * 10; + y += (data[fld_bgn + 3] - Byte_ascii.Num_0); + M += (data[fld_bgn + 4] - Byte_ascii.Num_0) * 10; + M += (data[fld_bgn + 5] - Byte_ascii.Num_0); + d += (data[fld_bgn + 6] - Byte_ascii.Num_0) * 10; + d += (data[fld_bgn + 7] - Byte_ascii.Num_0); + H += (data[fld_bgn + 9] - Byte_ascii.Num_0) * 10; + H += (data[fld_bgn + 10] - Byte_ascii.Num_0); + m += (data[fld_bgn + 11] - Byte_ascii.Num_0) * 10; + m += (data[fld_bgn + 12] - Byte_ascii.Num_0); + s += (data[fld_bgn + 13] - Byte_ascii.Num_0) * 10; + s += (data[fld_bgn + 14] - Byte_ascii.Num_0); + f += (data[fld_bgn + 16] - Byte_ascii.Num_0) * 100; + f += (data[fld_bgn + 17] - Byte_ascii.Num_0) * 10; + f += (data[fld_bgn + 18] - Byte_ascii.Num_0); + if (data[fld_bgn + 19] != fld_dlm) throw Err_.new_wo_type("csv date is invalid", "txt", String_.new_u8__by_len(data, fld_bgn, 20)); + fld_end = pos + 20; + pos = fld_end + 1; ++fld_idx; + return DateAdp_.new_(y, M, d, H, m, s, f); + } + public void Move_next_simple() { + if (pos < data_len) { + byte b_cur = data[pos]; + if (b_cur == row_dlm) { + fld_bgn = fld_end = pos; + ++pos; ++row_idx; + fld_idx = 0; + return; + } + } + fld_bgn = pos; + if (fld_bgn == data_len) {fld_end = data_len; return;} + for (int i = fld_bgn; i < data_len; i++) { + byte b = data[i]; + if (b == fld_dlm || b == row_dlm) { + fld_end = i; pos = i + 1; ++fld_idx; // position after dlm + return; + } + } + throw Err_.new_wo_type("fld_dlm failed", "fld_dlm", (char)fld_dlm, "bgn", fld_bgn); + } + public String Read_str_escape() {Move_next_escaped(bfr); return String_.new_u8(bfr.To_bry_and_clear());} + public byte[] Read_bry_escape() {Move_next_escaped(bfr); return bfr.To_bry_and_clear();} + public void Move_1() {++pos;} + public void Move_next_escaped() {Move_next_escaped(bfr); bfr.Clear();} + public int Move_next_simple_fld() { + Move_next_simple(); + return fld_end; + } + public int Move_next_escaped(Bry_bfr trg) { + //if (pos < data_len && data[pos] == row_dlm) {++pos; ++row_idx; fld_idx = 0;} // REMOVE:20120919: this will fail for empty fields at end of line; EX: "a|\n"; intent was probably to auto-advance to new row, but this intent should be explicit + fld_bgn = pos; + boolean quote_on = false; + for (int i = fld_bgn; i < data_len; i++) { + byte b = data[i]; + if ((b == fld_dlm || b == row_dlm) && !quote_on) { + fld_end = i; pos = i + 1; ++fld_idx; // position after dlm + return pos; + } + else if (b == escape_dlm) { + ++i; +// if (i == data_len) throw Err_.new_wo_type("escape char at end of String"); + b = data[i]; + byte escape_val = decode_regy[b]; + if (escape_val == Byte_ascii.Null) {trg.Add_byte(escape_dlm).Add_byte(b);} //throw Err_.new_fmt_("unknown escape key: key={0}", data[i]); + else trg.Add_byte(escape_val); + } + else if (b == Byte_ascii.Null) { + trg.Add(Bry_nil); + } + else if (b == quote_dlm) { + quote_on = !quote_on; + } + else + trg.Add_byte(b); + } + return -1; + } + public Gfo_fld_rdr Ctor_xdat() {return (Gfo_fld_rdr)super.Ctor_xdat_base();} + public Gfo_fld_rdr Ctor_sql() {return (Gfo_fld_rdr)super.Ctor_sql_base();} + public static Gfo_fld_rdr xowa_() {return new Gfo_fld_rdr().Ctor_xdat();} + public static Gfo_fld_rdr sql_() {return new Gfo_fld_rdr().Ctor_sql();} + private static final byte[] Bry_nil = Bry_.new_a7("\\0"); +} diff --git a/400_xowa/src/gplx/core/flds/Gfo_fld_rdr_tst.java b/400_xowa/src/gplx/core/flds/Gfo_fld_rdr_tst.java index a27517de8..5e47e67a7 100644 --- a/400_xowa/src/gplx/core/flds/Gfo_fld_rdr_tst.java +++ b/400_xowa/src/gplx/core/flds/Gfo_fld_rdr_tst.java @@ -13,3 +13,42 @@ 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.core.flds; import gplx.*; import gplx.core.*; +import org.junit.*; +import gplx.core.ios.*; +public class Gfo_fld_rdr_tst { + Gfo_fld_rdr_fxt fxt = new Gfo_fld_rdr_fxt(); + @Test public void Read_int() {fxt.ini_xdat().Raw_("123|") .tst_Read_int(123);} + @Test public void Read_double() {fxt.ini_xdat().Raw_("1.23|") .tst_Read_double(1.23);} + @Test public void Read_str_simple() {fxt.ini_xdat().Raw_("ab|") .tst_Read_str_simple("ab");} + @Test public void Read_str_escape_pipe() {fxt.ini_xdat().Raw_("a~pb|") .tst_Read_str_escape("a|b");} + @Test public void Read_str_escape_tilde() {fxt.ini_xdat().Raw_("a~~b|") .tst_Read_str_escape("a~b");} + @Test public void Read_str_escape_nl() {fxt.ini_xdat().Raw_("a~nb|") .tst_Read_str_escape("a\nb");} + @Test public void Read_str_escape_tab() {fxt.ini_xdat().Raw_("a~tb|") .tst_Read_str_escape("a\tb");} + @Test public void Write_str_escape_pipe() {fxt.ini_xdat().tst_Write_str_escape("a|b", "a~pb|");} + @Test public void Read_str_quoted_comma() {fxt.ini_sql ().Raw_("'a,b',") .tst_Read_str_escape("a,b");} + @Test public void Read_str_quoted_apos() {fxt.ini_sql ().Raw_("'a\\'b',") .tst_Read_str_escape("a'b");} + @Test public void Read_multiple() { + fxt.ini_xdat().Raw_("ab|1|.9|\n") + .tst_Read_str_escape("ab").tst_Read_int(1).tst_Read_double(.9) + ; + } + @Test public void Read_dlm_nl() {fxt.ini_xdat().Raw_("123\n") .tst_Read_int(123);} +} +class Gfo_fld_rdr_fxt { + Gfo_fld_rdr rdr = new Gfo_fld_rdr(); Gfo_fld_wtr wtr = Gfo_fld_wtr.xowa_(); + public Gfo_fld_rdr_fxt Raw_(String v) {rdr.Data_(Bry_.new_u8(v)); return this;} + public Gfo_fld_rdr_fxt ini_xdat() {rdr.Ctor_xdat(); return this;} + public Gfo_fld_rdr_fxt ini_sql() {rdr.Ctor_sql(); return this;} + public Gfo_fld_rdr_fxt tst_Read_int(int expd) {Tfds.Eq(expd, rdr.Read_int()); return this;} + public Gfo_fld_rdr_fxt tst_Read_double(double expd) {Tfds.Eq(expd, rdr.Read_double()); return this;} + public Gfo_fld_rdr_fxt tst_Read_str_simple(String expd) {Tfds.Eq(expd, rdr.Read_str_simple()); return this;} + public Gfo_fld_rdr_fxt tst_Read_str_escape(String expd) {Tfds.Eq(expd, rdr.Read_str_escape()); return this;} + public Gfo_fld_rdr_fxt tst_Write_str_escape(String val, String expd) { + byte[] bry = Bry_.new_u8(val); + wtr.Bfr_(bfr); + wtr.Write_bry_escape_fld(bry); + Tfds.Eq(expd, bfr.To_str()); + return this; + } private Bry_bfr bfr = Bry_bfr_.New(); +} diff --git a/400_xowa/src/gplx/core/flds/Gfo_fld_wtr.java b/400_xowa/src/gplx/core/flds/Gfo_fld_wtr.java index a27517de8..4829fb590 100644 --- a/400_xowa/src/gplx/core/flds/Gfo_fld_wtr.java +++ b/400_xowa/src/gplx/core/flds/Gfo_fld_wtr.java @@ -13,3 +13,45 @@ 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.core.flds; import gplx.*; import gplx.core.*; +import gplx.core.ios.*; import gplx.core.encoders.*; +public class Gfo_fld_wtr extends Gfo_fld_base { + public Bry_bfr Bfr() {return bfr;} public Gfo_fld_wtr Bfr_(Bry_bfr v) {bfr = v; return this;} Bry_bfr bfr; + public Gfo_fld_wtr() {this.bfr = Bry_bfr_.New();} + public Gfo_fld_wtr Write_int_base85_len5_fld(int v) {bfr.Add_base85(v, Base85_.Len_int); bfr.Add_byte(fld_dlm); return this;} + public Gfo_fld_wtr Write_int_base85_lenN_fld(int v, int len) {bfr.Add_base85(v, len); bfr.Add_byte(fld_dlm); return this;} + public Gfo_fld_wtr Write_int_variable_fld(int v) {bfr.Add_int_variable(v); bfr.Add_byte(fld_dlm); return this;} + public Gfo_fld_wtr Write_int_fixed_fld(int v, int len) {bfr.Add_int_fixed(v, len); bfr.Add_byte(fld_dlm); return this;} + public Gfo_fld_wtr Write_double_fld(double v) {bfr.Add_double(v); bfr.Add_byte(fld_dlm); return this;} + public Gfo_fld_wtr Write_byte_fld(byte v) {bfr.Add_byte(v); bfr.Add_byte(fld_dlm); return this;} + public Gfo_fld_wtr Write_bry_escape_fld(byte[] val) {Write_bry_escape(val, 0, val.length); bfr.Add_byte(fld_dlm); return this;} + public Gfo_fld_wtr Write_bry_escape_fld(byte[] val, int bgn, int end) {Write_bry_escape(val, bgn, end); bfr.Add_byte(fld_dlm); return this;} + public Gfo_fld_wtr Write_dlm_row() { bfr.Add_byte(row_dlm); return this;} + public Gfo_fld_wtr Write_dlm_fld() { bfr.Add_byte(fld_dlm); return this;} + public Gfo_fld_wtr Write_int_base85_lenN_row(int v, int len) {bfr.Add_base85(v, len); bfr.Add_byte(row_dlm); return this;} + public Gfo_fld_wtr Write_int_base85_len5_row(int v) {bfr.Add_base85(v, Base85_.Len_int); bfr.Add_byte(row_dlm); return this;} + public Gfo_fld_wtr Write_bry_escape_row(byte[] val) {Write_bry_escape(val, 0, val.length); bfr.Add_byte(row_dlm); return this;} + public Gfo_fld_wtr Write_bry_escape_row(byte[] val, int bgn, int end) {Write_bry_escape(val, bgn, end); bfr.Add_byte(row_dlm); return this;} + public Gfo_fld_wtr Write_double_row(double v) {bfr.Add_double(v); bfr.Add_byte(row_dlm); return this;} + Gfo_fld_wtr Write_bry_escape(byte[] val, int bgn, int end) { + for (int i = bgn; i < end; i++) { + byte b = val[i]; + byte escape_val = encode_regy[b & 0xFF]; // PATCH.JAVA:need to convert to unsigned byte + if (escape_val == Byte_ascii.Null) bfr.Add_byte(b); + else {bfr.Add_byte(escape_dlm); bfr.Add_byte(escape_val);} + } + return this; + } + public Gfo_fld_wtr Rls() {bfr.Rls(); return this;} + + public Io_url_gen Fil_gen() {return fil_gen;} public Gfo_fld_wtr Fil_gen_(Io_url_gen v) {fil_gen = v; return this;} Io_url_gen fil_gen; + public int Bfr_max() {return bfr_max;} public Gfo_fld_wtr Bfr_max_(int v) {bfr_max = v; return this;} private int bfr_max = Io_mgr.Len_mb; + public boolean Flush_needed(int v) {return bfr.Len() + v > bfr_max;} + public void Flush() { + if (Fil_gen().Cur_url() == null) fil_gen.Nxt_url(); + Io_mgr.Instance.AppendFilBfr(fil_gen.Cur_url(), bfr); + } + public void Flush_nxt() {Flush(); fil_gen.Nxt_url();} + public Gfo_fld_wtr Ctor_xdat() {return (Gfo_fld_wtr)super.Ctor_xdat_base();} + public static Gfo_fld_wtr xowa_() {return new Gfo_fld_wtr().Ctor_xdat();} +} diff --git a/400_xowa/src/gplx/core/gfobjs/Gfobj_ary.java b/400_xowa/src/gplx/core/gfobjs/Gfobj_ary.java index a27517de8..4bbd018fb 100644 --- a/400_xowa/src/gplx/core/gfobjs/Gfobj_ary.java +++ b/400_xowa/src/gplx/core/gfobjs/Gfobj_ary.java @@ -13,3 +13,17 @@ 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.core.gfobjs; import gplx.*; import gplx.core.*; +public class Gfobj_ary implements Gfobj_grp { // NOTE: items in array can vary in types; EX:['a', 1, false] + public Gfobj_ary(Object[] ary) {this.ary = ary;} + public byte Grp_tid() {return Gfobj_grp_.Grp_tid__ary;} + public int Len() {return ary.length;} + public Object Get_at(int i) {return ary[i];} + public Object[] Ary_obj() {return ary;} private Object[] ary; + public Gfobj_ary Ary_(Object[] v) {this.ary = v; return this;} + public Gfobj_nde New_nde_at(int i) { + Gfobj_nde rv = Gfobj_nde.New(); + ary[i] = rv; + return rv; + } +} diff --git a/400_xowa/src/gplx/core/gfobjs/Gfobj_fld.java b/400_xowa/src/gplx/core/gfobjs/Gfobj_fld.java index a27517de8..dc8d9a93a 100644 --- a/400_xowa/src/gplx/core/gfobjs/Gfobj_fld.java +++ b/400_xowa/src/gplx/core/gfobjs/Gfobj_fld.java @@ -13,3 +13,77 @@ 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.core.gfobjs; import gplx.*; import gplx.core.*; +public interface Gfobj_fld { + byte Fld_tid(); + String Key(); + Object As_obj(); +} +class Gfobj_fld_ { + public static final byte + Fld_tid__ary = 0 + , Fld_tid__nde = 1 + , Fld_tid__bool = 2 + , Fld_tid__int = 3 + , Fld_tid__long = 4 + , Fld_tid__double = 5 + , Fld_tid__str = 6 + , Fld_tid__bry = 7 + ; +} +class Gfobj_fld_str implements Gfobj_fld { + public Gfobj_fld_str(String key, String val) {this.key = key; this.val = val;} + public String Key() {return key;} private final String key; + public byte Fld_tid() {return Gfobj_fld_.Fld_tid__str;} + public Object As_obj() {return val;} + public String As_str() {return val;} private String val; +} +class Gfobj_fld_bry implements Gfobj_fld { + public Gfobj_fld_bry(String key, byte[] val) {this.key = key; this.val = val;} + public String Key() {return key;} private final String key; + public byte Fld_tid() {return Gfobj_fld_.Fld_tid__bry;} + public Object As_obj() {return val;} + public byte[] As_bry() {return val;} private byte[] val; +} +class Gfobj_fld_bool implements Gfobj_fld { + public Gfobj_fld_bool(String key, boolean val) {this.key = key; this.val = val;} + public String Key() {return key;} private final String key; + public byte Fld_tid() {return Gfobj_fld_.Fld_tid__bool;} + public Object As_obj() {return val;} + public boolean As_bool() {return val;} private boolean val; +} +class Gfobj_fld_int implements Gfobj_fld { + public Gfobj_fld_int(String key, int val) {this.key = key; this.val = val;} + public String Key() {return key;} private final String key; + public byte Fld_tid() {return Gfobj_fld_.Fld_tid__int;} + public Object As_obj() {return val;} + public int As_int() {return val;} private int val; +} +class Gfobj_fld_long implements Gfobj_fld { + public Gfobj_fld_long(String key, long val) {this.key = key; this.val = val;} + public String Key() {return key;} private final String key; + public byte Fld_tid() {return Gfobj_fld_.Fld_tid__long;} + public Object As_obj() {return val;} + public long As_long() {return val;} private long val; +} +class Gfobj_fld_double implements Gfobj_fld { + public Gfobj_fld_double(String key, double val) {this.key = key; this.val = val;} + public String Key() {return key;} private final String key; + public byte Fld_tid() {return Gfobj_fld_.Fld_tid__double;} + public Object As_obj() {return val;} + public double As_double() {return val;} private double val; +} +class Gfobj_fld_nde implements Gfobj_fld { + public Gfobj_fld_nde(String key, Gfobj_nde val) {this.key = key; this.val = val;} + public String Key() {return key;} private final String key; + public byte Fld_tid() {return Gfobj_fld_.Fld_tid__nde;} + public Object As_obj() {return val;} + public Gfobj_nde As_nde() {return val;} private Gfobj_nde val; +} +class Gfobj_fld_ary implements Gfobj_fld { + public Gfobj_fld_ary(String key, Gfobj_ary val) {this.key = key; this.val = val;} + public String Key() {return key;} private final String key; + public byte Fld_tid() {return Gfobj_fld_.Fld_tid__ary;} + public Object As_obj() {return val;} + public Gfobj_ary As_ary() {return val;} private Gfobj_ary val; +} diff --git a/400_xowa/src/gplx/core/gfobjs/Gfobj_grp.java b/400_xowa/src/gplx/core/gfobjs/Gfobj_grp.java index a27517de8..31c25f6ac 100644 --- a/400_xowa/src/gplx/core/gfobjs/Gfobj_grp.java +++ b/400_xowa/src/gplx/core/gfobjs/Gfobj_grp.java @@ -13,3 +13,7 @@ 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.core.gfobjs; import gplx.*; import gplx.core.*; +public interface Gfobj_grp { + byte Grp_tid(); +} diff --git a/400_xowa/src/gplx/core/gfobjs/Gfobj_grp_.java b/400_xowa/src/gplx/core/gfobjs/Gfobj_grp_.java index a27517de8..b648e1aed 100644 --- a/400_xowa/src/gplx/core/gfobjs/Gfobj_grp_.java +++ b/400_xowa/src/gplx/core/gfobjs/Gfobj_grp_.java @@ -13,3 +13,10 @@ 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.core.gfobjs; import gplx.*; import gplx.core.*; +class Gfobj_grp_ { + public static final byte + Grp_tid__ary = 0 + , Grp_tid__nde = 1 + ; +} diff --git a/400_xowa/src/gplx/core/gfobjs/Gfobj_nde.java b/400_xowa/src/gplx/core/gfobjs/Gfobj_nde.java index a27517de8..4a0b053fc 100644 --- a/400_xowa/src/gplx/core/gfobjs/Gfobj_nde.java +++ b/400_xowa/src/gplx/core/gfobjs/Gfobj_nde.java @@ -13,3 +13,47 @@ 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.core.gfobjs; import gplx.*; import gplx.core.*; +public class Gfobj_nde implements Gfobj_grp { + private Ordered_hash subs; + public byte Grp_tid() {return Gfobj_grp_.Grp_tid__nde;} + public int Len() {return subs == null ? 0 : subs.Len();} + public Gfobj_fld Get_at(int i) {return subs == null ? null : (Gfobj_fld)subs.Get_at(i);} + public Gfobj_fld Get_by(String k) {return subs == null ? null : (Gfobj_fld)subs.Get_by(k);} + public Gfobj_ary Get_ary(String k) {return ((Gfobj_fld_ary)Get_by(k)).As_ary();} + public Gfobj_nde Get_nde(int i) {return ((Gfobj_fld_nde)Get_at(i)).As_nde();} + public Gfobj_nde Get_nde(String k) {return ((Gfobj_fld_nde)Get_by(k)).As_nde();} + public long Get_long(String k) { + Gfobj_fld fld = Get_by(k); + switch (fld.Fld_tid()) { + case Gfobj_fld_.Fld_tid__long: return ((Gfobj_fld_long)fld).As_long(); + case Gfobj_fld_.Fld_tid__int : return ((Gfobj_fld_int )fld).As_int(); + default: throw Err_.new_unhandled_default(fld.Fld_tid()); + } + } + public int Get_int(String k) { + Gfobj_fld fld = Get_by(k); + switch (fld.Fld_tid()) { + case Gfobj_fld_.Fld_tid__int : return ((Gfobj_fld_int )fld).As_int(); + default: throw Err_.new_unhandled_default(fld.Fld_tid()); + } + } + public byte Get_byte(String k) {return (byte)Get_int(k);} + public String Get_str(String k) {return ((Gfobj_fld_str)Get_by(k)).As_str();} + public Io_url Get_url(String k) {return Io_url_.new_any_(((Gfobj_fld_str)Get_by(k)).As_str());} + public Gfobj_nde Add_fld(Gfobj_fld fld) {if (subs == null) subs = Ordered_hash_.New(); subs.Add(fld.Key(), fld); return this;} + public Gfobj_nde Add_bool(String key, boolean val) {return Add_fld(new Gfobj_fld_bool(key, val));} + public Gfobj_nde Add_byte(String key, byte val) {return Add_fld(new Gfobj_fld_int(key, val));} + public Gfobj_nde Add_int(String key, int val) {return Add_fld(new Gfobj_fld_int(key, val));} + public Gfobj_nde Add_long(String key, long val) {return Add_fld(new Gfobj_fld_long(key, val));} + public Gfobj_nde Add_str(String key, String val) {return Add_fld(new Gfobj_fld_str(key, val));} + public Gfobj_nde Add_bry(String key, byte[] val) {return Add_fld(new Gfobj_fld_bry(key, val));} + public Gfobj_nde Add_url(String key, Io_url val) {return Add_fld(new Gfobj_fld_str(key, val.Raw()));} + public Gfobj_nde Add_double(String key, double val) {return Add_fld(new Gfobj_fld_double(key, val));} + public Gfobj_nde Add_nde(String key, Gfobj_nde val) {return Add_fld(new Gfobj_fld_nde(key, val));} + public Gfobj_nde Add_ary(String key, Gfobj_ary val) {return Add_fld(new Gfobj_fld_ary(key, val));} + public Gfobj_nde New_nde(String key) {Gfobj_nde rv = new Gfobj_nde(); Add_fld(new Gfobj_fld_nde(key, rv)); return rv;} + public Gfobj_ary New_ary(String key) {Gfobj_ary rv = new Gfobj_ary(null); Add_fld(new Gfobj_fld_ary(key, rv)); return rv;} + public Gfobj_ary New_ary(String key, int subs_len) {return New_ary(key).Ary_(new Object[subs_len]);} + public static Gfobj_nde New() {return new Gfobj_nde();} +} diff --git a/400_xowa/src/gplx/core/gfobjs/Gfobj_rdr__json.java b/400_xowa/src/gplx/core/gfobjs/Gfobj_rdr__json.java index a27517de8..fce5b69dc 100644 --- a/400_xowa/src/gplx/core/gfobjs/Gfobj_rdr__json.java +++ b/400_xowa/src/gplx/core/gfobjs/Gfobj_rdr__json.java @@ -13,3 +13,78 @@ 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.core.gfobjs; import gplx.*; import gplx.core.*; +import gplx.langs.jsons.*; +public class Gfobj_rdr__json { + private final Json_parser parser = new Json_parser(); + public Gfobj_grp Load(Io_url url) { + byte[] src = Io_mgr.Instance.LoadFilBryOrNull(url); if (src == null) return null; + return this.Parse(src); + } + public Gfobj_grp Parse(byte[] src) { + Json_doc jdoc = parser.Parse(src); + if (jdoc.Root_grp().Tid() == Json_itm_.Tid__nde) { + Gfobj_nde rv_nde = Gfobj_nde.New(); + Parse_nde((Json_nde)jdoc.Root_grp(), rv_nde); + return rv_nde; + } + else { + Gfobj_ary rv_ary = new Gfobj_ary(null); + Parse_ary((Json_ary)jdoc.Root_grp(), rv_ary); + return rv_ary; + } + } + private void Parse_nde(Json_nde jnde, Gfobj_nde gnde) { + int len = jnde.Len(); + for (int i = 0; i < len; ++i) { + Json_kv kv = jnde.Get_at_as_kv(i); + String key_str = kv.Key_as_str(); + Json_itm val = kv.Val(); + byte val_tid = val.Tid(); + switch (val_tid) { + case Json_itm_.Tid__str: gnde.Add_str (key_str, ((Json_itm_str)val).Data_as_str()); break; + case Json_itm_.Tid__bool: gnde.Add_bool (key_str, ((Json_itm_bool)val).Data_as_bool()); break; + case Json_itm_.Tid__int: gnde.Add_int (key_str, ((Json_itm_int)val).Data_as_int()); break; + case Json_itm_.Tid__long: gnde.Add_long (key_str, ((Json_itm_long)val).Data_as_long()); break; + case Json_itm_.Tid__decimal: gnde.Add_double (key_str, ((Json_itm_decimal)val).Data_as_decimal().To_double()); break; + case Json_itm_.Tid__null: gnde.Add_str (key_str, null); break; + case Json_itm_.Tid__ary: + Gfobj_ary sub_ary = new Gfobj_ary(null); + gnde.Add_ary(key_str, sub_ary); + Parse_ary(Json_ary.cast(val), sub_ary); + break; + case Json_itm_.Tid__nde: + Gfobj_nde sub_gnde = Gfobj_nde.New(); + gnde.Add_nde(key_str, sub_gnde); + Parse_nde(Json_nde.cast(val), sub_gnde); + break; + default: throw Err_.new_unhandled_default(val_tid); + } + } + } + private void Parse_ary(Json_ary jry, Gfobj_ary gry) { + int len = jry.Len(); + Object[] ary = new Object[len]; + gry.Ary_(ary); + for (int i = 0; i < len; ++i) { + Json_itm jsub = jry.Get_at(i); + switch (jsub.Tid()) { + case Json_itm_.Tid__ary: { + Gfobj_ary sub_ary = new Gfobj_ary(null); + Parse_ary(Json_ary.cast(jsub), sub_ary); + ary[i] = sub_ary; + break; + } + case Json_itm_.Tid__nde: { + Gfobj_nde sub_ary = Gfobj_nde.New(); + Parse_nde(Json_nde.cast(jsub), sub_ary); + ary[i] = sub_ary; + break; + } + default: + ary[i] = jsub.Data(); + break; + } + } + } +} diff --git a/400_xowa/src/gplx/core/gfobjs/Gfobj_rdr__json_tst.java b/400_xowa/src/gplx/core/gfobjs/Gfobj_rdr__json_tst.java index a27517de8..d19e8f40c 100644 --- a/400_xowa/src/gplx/core/gfobjs/Gfobj_rdr__json_tst.java +++ b/400_xowa/src/gplx/core/gfobjs/Gfobj_rdr__json_tst.java @@ -13,3 +13,74 @@ 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.core.gfobjs; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.tests.*; +import gplx.langs.jsons.*; +public class Gfobj_rdr__json_tst { + private final Gfobj_wtr__json_fxt fxt = new Gfobj_wtr__json_fxt(); + @Test public void Type() { + fxt.Test__parse(String_.Concat_lines_nl_skip_last + ( "{ 'k1':true" + , ", 'k2':123" + , ", 'k3':9876543210" + , ", 'k4':1.23" + , ", 'k5':null" + , ", 'k6':'abc'" + , "}" + ) + , fxt.Make__nde + ( fxt.Make__fld_bool ("k1", true) + , fxt.Make__fld_int ("k2", 123) + , fxt.Make__fld_long ("k3", 9876543210L) + , fxt.Make__fld_double ("k4", 1.23) + , fxt.Make__fld_str ("k5", null) + , fxt.Make__fld_str ("k6", "abc") + )); + } + @Test public void Nested() { + fxt.Test__parse(String_.Concat_lines_nl_skip_last + ( "{ 'a1':'1a'" + , ", 'a2':" + , " { 'b1':'1b'" + , " , 'b2':" + , " { 'c1':'1c'" + , " }" + , " }" + , ", 'a3':[1, 2, 3]" + , "}" + ) + , fxt.Make__nde + ( fxt.Make__fld_str ("a1", "1a") + , fxt.Make__fld_nde ("a2" + , fxt.Make__fld_str("b1", "1b") + , fxt.Make__fld_nde("b2" + , fxt.Make__fld_str("c1", "1c")) + ) + , fxt.Make__fld_ary ("a3", 1, 2, 3) + )); + } + @Test public void Array() { + fxt.Test__parse(String_.Concat_lines_nl_skip_last + ( "[" + , " [1, 2, 3]" + , ", ['a', 'b', 'c']" + , ", [true, false]" + , ", [9876543210, 9876543211, 9876543212]" + //, ", [1.23, 1.24, 1.25]" + , ", [{'a':1}, {'b':2}, {'c':3}]" + , "]" + ) + , fxt.Make__ary + ( fxt.Make__ary (1, 2, 3) + , fxt.Make__ary ("a", "b", "c") + , fxt.Make__ary (true, false) + , fxt.Make__ary (9876543210L, 9876543211L, 9876543212L) + // , fxt.Make__ary (1.23, 1.24, 1.25) + , fxt.Make__ary + ( fxt.Make__nde(fxt.Make__fld_int("a", 1)) + , fxt.Make__nde(fxt.Make__fld_int("b", 2)) + , fxt.Make__nde(fxt.Make__fld_int("c", 3)) + ) + )); + } +} diff --git a/400_xowa/src/gplx/core/gfobjs/Gfobj_wtr__json.java b/400_xowa/src/gplx/core/gfobjs/Gfobj_wtr__json.java index a27517de8..c45798497 100644 --- a/400_xowa/src/gplx/core/gfobjs/Gfobj_wtr__json.java +++ b/400_xowa/src/gplx/core/gfobjs/Gfobj_wtr__json.java @@ -13,3 +13,72 @@ 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.core.gfobjs; import gplx.*; import gplx.core.*; +import gplx.langs.jsons.*; +public class Gfobj_wtr__json { + private final Json_wtr wtr = new Json_wtr(); + public Gfobj_wtr__json Opt_ws_(boolean v) {wtr.Opt_ws_(v); return this;} + public Gfobj_wtr__json Opt_backslash_2x_(boolean v) {wtr.Opt_backslash_2x_(v); return this;} + public Bry_bfr Bfr() {return wtr.Bfr();} + public String To_str() {return wtr.To_str_and_clear();} + public void Save(Io_url url) { + Io_mgr.Instance.SaveFilBry(url, wtr.To_bry_and_clear()); + } + public Gfobj_wtr__json Write(Gfobj_grp root) { + switch (root.Grp_tid()) { + case Gfobj_grp_.Grp_tid__nde: + wtr.Doc_nde_bgn(); + Write_nde((Gfobj_nde)root); + wtr.Doc_nde_end(); + break; + case Gfobj_grp_.Grp_tid__ary: + wtr.Doc_ary_bgn(); + Write_ary((Gfobj_ary)root); + wtr.Doc_ary_end(); + break; + default: + throw Err_.new_unhandled_default(root.Grp_tid()); + } + return this; + } + private void Write_nde(Gfobj_nde nde) { + int len = nde.Len(); + for (int i = 0; i < len; ++i) { + Gfobj_fld fld = (Gfobj_fld)nde.Get_at(i); + Write_fld(fld); + } + } + private void Write_fld(Gfobj_fld itm) { + switch (itm.Fld_tid()) { + case Gfobj_fld_.Fld_tid__str: wtr.Kv_str(itm.Key() , ((Gfobj_fld_str)itm).As_str()); break; + case Gfobj_fld_.Fld_tid__bry: wtr.Kv_bry(itm.Key() , ((Gfobj_fld_bry)itm).As_bry()); break; + case Gfobj_fld_.Fld_tid__int: wtr.Kv_int(itm.Key() , ((Gfobj_fld_int)itm).As_int()); break; + case Gfobj_fld_.Fld_tid__long: wtr.Kv_long(itm.Key() , ((Gfobj_fld_long)itm).As_long()); break; + case Gfobj_fld_.Fld_tid__bool: wtr.Kv_bool(itm.Key() , ((Gfobj_fld_bool)itm).As_bool()); break; + case Gfobj_fld_.Fld_tid__double: wtr.Kv_double(itm.Key() , ((Gfobj_fld_double)itm).As_double()); break; + case Gfobj_fld_.Fld_tid__nde: wtr.Nde_bgn(itm.Key()); Write_nde(((Gfobj_fld_nde)itm).As_nde()); wtr.Nde_end();break; + case Gfobj_fld_.Fld_tid__ary: wtr.Ary_bgn(itm.Key()); Write_ary(((Gfobj_fld_ary)itm).As_ary()); wtr.Ary_end();break; + default: throw Err_.new_unhandled_default(itm.Fld_tid()); + } + } + private void Write_ary(Gfobj_ary ary) { + int len = ary.Len(); + Object[] ary_obj = ((Gfobj_ary)ary).Ary_obj(); + for (int i = 0; i < len; ++i) { + Object sub_itm = ary_obj[i]; + Class sub_itm_type = Type_.Type_by_obj(sub_itm); + if (Type_.Eq(sub_itm_type, Gfobj_ary.class)) { + wtr.Ary_bgn_ary(); + Write_ary((Gfobj_ary)sub_itm); + wtr.Ary_end(); + } + else if (Type_.Eq(sub_itm_type, Gfobj_nde.class)) { + wtr.Nde_bgn_ary(); + Write_nde((Gfobj_nde)sub_itm); + wtr.Nde_end(); + } + else + wtr.Ary_itm_obj(sub_itm); + } + } +} diff --git a/400_xowa/src/gplx/core/gfobjs/Gfobj_wtr__json_fxt.java b/400_xowa/src/gplx/core/gfobjs/Gfobj_wtr__json_fxt.java index a27517de8..67244e13d 100644 --- a/400_xowa/src/gplx/core/gfobjs/Gfobj_wtr__json_fxt.java +++ b/400_xowa/src/gplx/core/gfobjs/Gfobj_wtr__json_fxt.java @@ -13,3 +13,40 @@ 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.core.gfobjs; import gplx.*; import gplx.core.*; +import gplx.core.tests.*; import gplx.langs.jsons.*; +public class Gfobj_wtr__json_fxt { + protected final Gfobj_wtr__json mgr = new Gfobj_wtr__json(); + public Gfobj_nde Make__nde(Gfobj_fld... ary) {return Make__nde(Gfobj_nde.New(), ary);} + private Gfobj_nde Make__nde(Gfobj_nde nde, Gfobj_fld[] ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) { + Gfobj_fld fld = (Gfobj_fld)ary[i]; + nde.Add_fld(fld); + } + return nde; + } + public Gfobj_fld Make__fld_bool (String key, boolean val) {return new Gfobj_fld_bool(key, val);} + public Gfobj_fld Make__fld_str (String key, String val) {return new Gfobj_fld_str(key, val);} + public Gfobj_fld Make__fld_int (String key, int val) {return new Gfobj_fld_int(key, val);} + public Gfobj_fld Make__fld_long (String key, long val) {return new Gfobj_fld_long(key, val);} + public Gfobj_fld Make__fld_double (String key, double val) {return new Gfobj_fld_double(key, val);} + public Gfobj_fld Make__fld_nde(String key, Gfobj_fld... ary) { + Gfobj_nde nde = Make__nde(Gfobj_nde.New(), ary); + Gfobj_fld_nde rv = new Gfobj_fld_nde(key, nde); + return rv; + } + public Gfobj_fld Make__fld_ary (String key, Object... ary) {return new Gfobj_fld_ary(key, new Gfobj_ary(ary));} + public Gfobj_ary Make__ary (Object... ary) {return new Gfobj_ary(ary);} + public Gfobj_wtr__json_fxt Test__write(Gfobj_grp root, String... lines) { + String[] expd = Json_doc.Make_str_ary_by_apos(lines); + Gftest.Eq__ary(expd, Bry_.Ary(String_.SplitLines_nl(mgr.Write(root).To_str())), "json_write"); + return this; + } + public Gfobj_wtr__json_fxt Test__parse(String src, Gfobj_grp expd) { + Gfobj_rdr__json rdr = new Gfobj_rdr__json(); + Gfobj_grp actl = rdr.Parse(Bry_.new_u8(Json_doc.Make_str_by_apos(src))); + Gftest.Eq__ary(Bry_.Ary(String_.SplitLines_nl(mgr.Write(expd).To_str())), Bry_.Ary(String_.SplitLines_nl(mgr.Write(actl).To_str())), "json_write"); + return this; + } +} diff --git a/400_xowa/src/gplx/core/gfobjs/Gfobj_wtr__json_tst.java b/400_xowa/src/gplx/core/gfobjs/Gfobj_wtr__json_tst.java index a27517de8..f06873f8a 100644 --- a/400_xowa/src/gplx/core/gfobjs/Gfobj_wtr__json_tst.java +++ b/400_xowa/src/gplx/core/gfobjs/Gfobj_wtr__json_tst.java @@ -13,3 +13,141 @@ 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.core.gfobjs; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.tests.*; +import gplx.langs.jsons.*; +public class Gfobj_wtr__json_tst { + private final Gfobj_wtr__json_fxt fxt = new Gfobj_wtr__json_fxt(); + @Test public void Flds() { + fxt.Test__write + ( fxt.Make__nde + ( fxt.Make__fld_str ("k1", "v1") + , fxt.Make__fld_long ("k2", 2) + , fxt.Make__fld_int ("k3", 3) + ) + , "{ 'k1':'v1'" + , ", 'k2':2" + , ", 'k3':3" + , "}" + , "" + ); + } + @Test public void Sub_ndes() { + fxt.Test__write + ( fxt.Make__nde + ( fxt.Make__fld_str ("k1", "v1") + , fxt.Make__fld_nde ("k2" + , fxt.Make__fld_str ("k2a", "v2a") + , fxt.Make__fld_int ("k2b", 2) + ) + , fxt.Make__fld_int ("k3", 3) + ) + , "{ 'k1':'v1'" + , ", 'k2':" + , " { 'k2a':'v2a'" + , " , 'k2b':2" + , " }" + , ", 'k3':3" + , "}" + , "" + ); + } + @Test public void Ary_str() { + fxt.Test__write + ( fxt.Make__nde + ( fxt.Make__fld_str ("k1", "v1") + , fxt.Make__fld_ary ("k2", "a1", "a2", "a3") + , fxt.Make__fld_int ("k3", 3) + ) + , "{ 'k1':'v1'" + , ", 'k2':" + , " [ 'a1'" + , " , 'a2'" + , " , 'a3'" + , " ]" + , ", 'k3':3" + , "}" + , "" + ); + } + @Test public void Ary_int() { + fxt.Test__write + ( fxt.Make__nde + ( fxt.Make__fld_str ("k1", "v1") + , fxt.Make__fld_ary ("k2", 1, 2, 3) + , fxt.Make__fld_int ("k3", 3) + ) + , "{ 'k1':'v1'" + , ", 'k2':" + , " [ 1" + , " , 2" + , " , 3" + , " ]" + , ", 'k3':3" + , "}" + , "" + ); + } + @Test public void Ary_nde() { + fxt.Test__write + ( fxt.Make__nde + ( fxt.Make__fld_str ("k1", "v1") + , fxt.Make__fld_ary ("k2" + , fxt.Make__nde (fxt.Make__fld_str("k21", "v21")) + , fxt.Make__nde (fxt.Make__fld_str("k22", "v22")) + ) + , fxt.Make__fld_int ("k3", 3) + ) + , "{ 'k1':'v1'" + , ", 'k2':" + , " [" + , " { 'k21':'v21'" + , " }" + , " ," + , " { 'k22':'v22'" + , " }" + , " ]" + , ", 'k3':3" + , "}" + , "" + ); + } + @Test public void Ary_ary() { + fxt.Test__write + ( fxt.Make__nde + ( fxt.Make__fld_str ("k1", "v1") + , fxt.Make__fld_ary ("k2" + , fxt.Make__ary (1, 2, 3) + , fxt.Make__ary (4, 5, 6) + ) + , fxt.Make__fld_int ("k3", 3) + ) + , "{ 'k1':'v1'" + , ", 'k2':" + , " [" + , " [ 1" + , " , 2" + , " , 3" + , " ]" + , " ," + , " [ 4" + , " , 5" + , " , 6" + , " ]" + , " ]" + , ", 'k3':3" + , "}" + , "" + ); + } + @Test public void Root_ary() { + fxt.Test__write + ( fxt.Make__ary(1, 2, 3) + , "[ 1" + , ", 2" + , ", 3" + , "]" + , "" + ); + } +} diff --git a/400_xowa/src/gplx/core/intls/String_surrogate_utl.java b/400_xowa/src/gplx/core/intls/String_surrogate_utl.java index a27517de8..572562b62 100644 --- a/400_xowa/src/gplx/core/intls/String_surrogate_utl.java +++ b/400_xowa/src/gplx/core/intls/String_surrogate_utl.java @@ -13,3 +13,22 @@ 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.core.intls; import gplx.*; import gplx.core.*; +public class String_surrogate_utl { + public int Byte_pos() {return byte_pos;} int byte_pos; + public int Count_surrogates__char_idx(byte[] src, int src_len, int byte_bgn, int char_idx) {return Count_surrogates(src, src_len, byte_bgn, Bool_.Y, char_idx);} + public int Count_surrogates__codepoint_idx1(byte[] src, int src_len, int byte_bgn, int codepoint_idx) {return Count_surrogates(src, src_len, byte_bgn, Bool_.N, codepoint_idx);} + private int Count_surrogates(byte[] src, int src_len, int byte_bgn, boolean stop_idx_is_char, int stop_idx) { + int char_count = 0, codepoint_count = 0; + byte_pos = byte_bgn; + while (true) { + if ( stop_idx == (stop_idx_is_char ? char_count : codepoint_count) // requested # of chars found + || byte_pos >= src_len // eos reached; DATE:2014-09-02 + ) return codepoint_count - char_count; + int char_len_in_bytes = gplx.core.intls.Utf8_.Len_of_char_by_1st_byte(src[byte_pos]); + ++char_count; // char_count always incremented by 1 + codepoint_count += (char_len_in_bytes == 4) ? 2 : 1; // codepoint_count incremented by 2 if surrogate pair; else 1 + byte_pos += char_len_in_bytes; + } + } +} diff --git a/400_xowa/src/gplx/core/intls/String_surrogate_utl_tst.java b/400_xowa/src/gplx/core/intls/String_surrogate_utl_tst.java index a27517de8..ebccb83cd 100644 --- a/400_xowa/src/gplx/core/intls/String_surrogate_utl_tst.java +++ b/400_xowa/src/gplx/core/intls/String_surrogate_utl_tst.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.core.intls; import gplx.*; import gplx.core.*; +import org.junit.*; +public class String_surrogate_utl_tst { + @Before public void init() {fxt.Clear();} private String_surrogate_utl_fxt fxt = new String_surrogate_utl_fxt(); + @Test public void Char_idx() { + String test_str = "aé𡼾bî𡼾"; + fxt.Test_count_surrogates__char_idx (test_str, 0, 1, 0, 1); // a + fxt.Test_count_surrogates__char_idx (test_str, 0, 2, 0, 3); // aé + fxt.Test_count_surrogates__char_idx (test_str, 0, 3, 1, 7); // aé𡼾 + fxt.Test_count_surrogates__char_idx (test_str, 7, 1, 0, 8); // b + fxt.Test_count_surrogates__char_idx (test_str, 7, 2, 0, 10); // bî + fxt.Test_count_surrogates__char_idx (test_str, 7, 3, 1, 14); // bî𡼾 + fxt.Test_count_surrogates__char_idx (test_str, 0, 6, 2, 14); // aé𡼾bî𡼾 + fxt.Test_count_surrogates__char_idx (test_str, 14, 7, 0, 14); // PURPOSE: test out of bounds; DATE:2014-09-02 + } + @Test public void Codepoint_idx() { + String test_str = "aé𡼾bî𡼾"; + fxt.Test_count_surrogates__codepoint_idx (test_str, 0, 1, 0, 1); // a + fxt.Test_count_surrogates__codepoint_idx (test_str, 0, 2, 0, 3); // aé + fxt.Test_count_surrogates__codepoint_idx (test_str, 0, 4, 1, 7); // aé𡼾 + fxt.Test_count_surrogates__codepoint_idx (test_str, 7, 1, 0, 8); // b + fxt.Test_count_surrogates__codepoint_idx (test_str, 7, 2, 0, 10); // bî + fxt.Test_count_surrogates__codepoint_idx (test_str, 7, 4, 1, 14); // bî𡼾 + fxt.Test_count_surrogates__codepoint_idx (test_str, 0, 8, 2, 14); // aé𡼾bî𡼾 + } +} +class String_surrogate_utl_fxt { + private String_surrogate_utl codepoint_utl = new String_surrogate_utl(); + public void Clear() {} + public void Test_count_surrogates__char_idx(String src_str, int bgn_byte, int char_idx, int expd_count, int expd_pos) { + byte[] src_bry = Bry_.new_u8(src_str); int src_len = src_bry.length; + Tfds.Eq(expd_count , codepoint_utl.Count_surrogates__char_idx(src_bry, src_len, bgn_byte, char_idx)); + Tfds.Eq(expd_pos , codepoint_utl.Byte_pos()); + } + public void Test_count_surrogates__codepoint_idx(String src_str, int bgn_byte, int char_idx, int expd_count, int expd_pos) { + byte[] src_bry = Bry_.new_u8(src_str); int src_len = src_bry.length; + Tfds.Eq(expd_count , codepoint_utl.Count_surrogates__codepoint_idx1(src_bry, src_len, bgn_byte, char_idx), "count"); + Tfds.Eq(expd_pos , codepoint_utl.Byte_pos(), "pos"); + } +} diff --git a/400_xowa/src/gplx/core/intls/ucas/Uca_collator.java b/400_xowa/src/gplx/core/intls/ucas/Uca_collator.java index a27517de8..7378790a9 100644 --- a/400_xowa/src/gplx/core/intls/ucas/Uca_collator.java +++ b/400_xowa/src/gplx/core/intls/ucas/Uca_collator.java @@ -13,3 +13,8 @@ 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.core.intls.ucas; import gplx.*; import gplx.core.*; import gplx.core.intls.*; +public interface Uca_collator { + void Init(String locale, boolean numeric_ordering); + byte[] Get_sortkey(String s); +} diff --git a/400_xowa/src/gplx/core/intls/ucas/Uca_collator_.java b/400_xowa/src/gplx/core/intls/ucas/Uca_collator_.java index a27517de8..b2fbf19f8 100644 --- a/400_xowa/src/gplx/core/intls/ucas/Uca_collator_.java +++ b/400_xowa/src/gplx/core/intls/ucas/Uca_collator_.java @@ -13,3 +13,11 @@ 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.core.intls.ucas; import gplx.*; import gplx.core.*; import gplx.core.intls.*; +public class Uca_collator_ { + public static Uca_collator New(String locale, boolean numeric_ordering) { + Uca_collator rv = new Uca_collator__icu__4_8(); + rv.Init(locale, numeric_ordering); + return rv; + } +} diff --git a/400_xowa/src/gplx/core/intls/ucas/Uca_collator__icu__4_8.java b/400_xowa/src/gplx/core/intls/ucas/Uca_collator__icu__4_8.java index a27517de8..d70734bfe 100644 --- a/400_xowa/src/gplx/core/intls/ucas/Uca_collator__icu__4_8.java +++ b/400_xowa/src/gplx/core/intls/ucas/Uca_collator__icu__4_8.java @@ -13,3 +13,35 @@ 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.core.intls.ucas; import gplx.*; import gplx.core.*; import gplx.core.intls.*; +import java.util.Locale; +import com.ibm.icu.text.CollationKey; +import com.ibm.icu.text.Collator; +import com.ibm.icu.text.RuleBasedCollator; +class Uca_collator__icu__4_8 implements Uca_collator { + private Collator collator; + public void Init(String locale, boolean numeric_ordering) { + try { + this.collator = Collator.getInstance(Locale.forLanguageTag(locale)); + if (numeric_ordering) { + RuleBasedCollator rbc = (RuleBasedCollator)collator; + rbc.setNumericCollation(true); + } + } catch (Exception e) {throw Err_.new_wo_type("collator init failed", "err", Err_.Message_lang(e));} + } + public byte[] Get_sortkey(String s) { + CollationKey key = collator.getCollationKey(s); + byte[] src = key.toByteArray(); + int src_len = src.length; + byte[] rv = src; + + // remove last byte if it is 0 (which it often is) + if (src_len > 0 && src[src_len - 1] == 0) { + int rv_len = src_len - 1; + rv = new byte[rv_len]; + for (int i = 0; i < rv_len; ++i) + rv[i] = src[i]; + } + return rv; + } +} diff --git a/400_xowa/src/gplx/core/intls/ucas/Uca_ltr_extractor.java b/400_xowa/src/gplx/core/intls/ucas/Uca_ltr_extractor.java index a27517de8..398c42e8d 100644 --- a/400_xowa/src/gplx/core/intls/ucas/Uca_ltr_extractor.java +++ b/400_xowa/src/gplx/core/intls/ucas/Uca_ltr_extractor.java @@ -13,3 +13,37 @@ 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.core.intls.ucas; import gplx.*; import gplx.core.*; import gplx.core.intls.*; +public class Uca_ltr_extractor { + private final boolean numeric; + private final byte[] numeric_heading; + private final Hash_adp_bry numeric_hash; + public Uca_ltr_extractor(boolean numeric) { + this.numeric = numeric; + if (numeric) { + numeric_heading = Bry_.new_a7("0-9"); + + // create hash of "0", "1", "2", ... + numeric_hash = Hash_adp_bry.cs(); + for (int i = 0; i < 10; ++i) { + byte[] digit_bry = Bry_.new_by_int(Byte_ascii.Num_0 + i); + numeric_hash.Add(digit_bry, digit_bry); + } + } + else { + numeric_heading = null; + numeric_hash = null; + } + } + public byte[] Get_1st_ltr(byte[] bry) { + // NOTE: this is simplified and only does numeric logic; MW code loads up all ICU chars via first-letters-root.ser, adds custom chars, sorts them, and then does a binary search to find it; REF:IcuCollation.php!getFirstLetter + int bry_len = bry.length; + if (bry_len == 0) return Bry_.Empty; + byte[] rv = gplx.core.intls.Utf8_.Get_char_at_pos_as_bry(bry, 0); + if (numeric) { + if (numeric_hash.Has(rv)) + rv = numeric_heading; + } + return rv; + } +} diff --git a/400_xowa/src/gplx/core/ios/BinaryHeap_Io_line_rdr.java b/400_xowa/src/gplx/core/ios/BinaryHeap_Io_line_rdr.java index a27517de8..d238b3694 100644 --- a/400_xowa/src/gplx/core/ios/BinaryHeap_Io_line_rdr.java +++ b/400_xowa/src/gplx/core/ios/BinaryHeap_Io_line_rdr.java @@ -13,3 +13,70 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.lists.*; +class BinaryHeap_Io_line_rdr { + public BinaryHeap_Io_line_rdr(ComparerAble comparer) {this.comparer = comparer;} ComparerAble comparer; + Io_line_rdr[] ary = Ary_empty; int ary_len = 0, ary_max = 0; + public int Len() {return ary_len;} + public void Add(Io_line_rdr itm) { + int new_len = ary_len + 1; + if (new_len > ary_max) { + ary_max = new_len * 2; + ary = (Io_line_rdr[])Array_.Resize(ary, ary_max); + } + ary[ary_len] = itm; + ary_len = new_len; + Add_move_up(ary_len - 1); + } + public Io_line_rdr Pop() { + if (ary_len == 0) return null; + Io_line_rdr rv = ary[0]; + --ary_len; + if (ary_len > 0) { + ary[0] = ary[ary_len]; + Pop_move_down(0); + } + return rv; + } + public void Rls() { + for (int i = 0; i < ary_len; i++) { + Io_line_rdr rdr = ary[i]; + if (rdr != null) rdr.Rls(); + ary[i] = null; + } + ary = null; + ary_len = 0; + } + private void Add_move_up(int pos) { + while (pos > 0) { + int owner = (pos - 1) / 2; + if (Compare(pos, owner) > CompareAble_.Less) break; + Swap(pos, owner); + pos = owner; + } + } + private void Pop_move_down(int pos) { + int idx_last = ary_len - 1; + while (pos < ary_len / 2) { + int sub = 2 * pos + 1; + if (sub < idx_last && Compare(sub, sub + 1) > CompareAble_.Same) + ++sub; + if (Compare(pos, sub) < CompareAble_.More) break; + Swap(pos, sub); + pos = sub; + } + } + int Compare(int lhs_idx, int rhs_idx) { + Io_line_rdr lhs = ary[lhs_idx], rhs = ary[rhs_idx]; + lhs_itm.Set(lhs); rhs_itm.Set(rhs); + return comparer.compare(lhs_itm, rhs_itm); +// return Bry_.Compare(lhs.Bfr(), lhs.Key_pos_bgn(), lhs.Key_pos_end(), rhs.Bfr(), rhs.Key_pos_bgn(), rhs.Key_pos_end()); + } Io_sort_split_itm lhs_itm = new Io_sort_split_itm(), rhs_itm = new Io_sort_split_itm(); + private void Swap(int lhs_idx, int rhs_idx) { + Io_line_rdr tmp = ary[lhs_idx]; + ary[lhs_idx] = ary[rhs_idx]; + ary[rhs_idx] = tmp; + } + private static final Io_line_rdr[] Ary_empty = new Io_line_rdr[0]; +} diff --git a/400_xowa/src/gplx/core/ios/BinaryHeap_Io_line_rdr_tst.java b/400_xowa/src/gplx/core/ios/BinaryHeap_Io_line_rdr_tst.java index a27517de8..a582915f6 100644 --- a/400_xowa/src/gplx/core/ios/BinaryHeap_Io_line_rdr_tst.java +++ b/400_xowa/src/gplx/core/ios/BinaryHeap_Io_line_rdr_tst.java @@ -13,3 +13,36 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class BinaryHeap_Io_line_rdr_tst { + BinaryHeap_Io_line_rdr_fxt fxt = new BinaryHeap_Io_line_rdr_fxt(); + @Test public void Add() { + fxt.Add("c", "a", "b").tst("a", "b", "c"); + fxt.Add("b", "a", "a").tst("a", "a", "b"); + fxt.Add("f", "b", "d", "c", "e", "a").tst("a", "b", "c", "d", "e", "f"); + } +} +class BinaryHeap_Io_line_rdr_fxt { + BinaryHeap_Io_line_rdr heap = new BinaryHeap_Io_line_rdr(Io_sort_split_itm_sorter.Instance); int file_total; + public BinaryHeap_Io_line_rdr_fxt Add(String... ary) { + file_total = ary.length; + for (int i = 0; i < file_total; i++) { + Io_url url = Io_url_.mem_fil_("mem/fil_" + ary[i] + ".txt"); + Io_mgr.Instance.SaveFilStr(url, ary[i]); + Io_line_rdr stream = new Io_line_rdr(Gfo_usr_dlg_.Test(), url); + stream.Read_next(); + heap.Add(stream); + } + return this; + } + public BinaryHeap_Io_line_rdr_fxt tst(String... expd) { + String[] actl = new String[file_total]; + for (int i = 0; i < actl.length; i++) { + Io_line_rdr bfr = heap.Pop(); + actl[i] = String_.new_u8(bfr.Bfr(), 0, bfr.Bfr_len()); + } + Tfds.Eq_ary_str(expd, actl); + return this; + } +} diff --git a/400_xowa/src/gplx/core/ios/Io_buffer_rdr.java b/400_xowa/src/gplx/core/ios/Io_buffer_rdr.java index a27517de8..9485ee319 100644 --- a/400_xowa/src/gplx/core/ios/Io_buffer_rdr.java +++ b/400_xowa/src/gplx/core/ios/Io_buffer_rdr.java @@ -13,3 +13,59 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.ios.streams.*;/*IoStream*/ +public class Io_buffer_rdr implements Rls_able { + private Io_stream_rdr rdr; + Io_buffer_rdr(Io_stream_rdr rdr, Io_url url, int bfr_len) { + this.rdr = rdr; this.url = url; + if (bfr_len <= 0) throw Err_.new_wo_type("bfr_len must be > 0", "bfr_len", bfr_len); + bfr = new byte[bfr_len]; this.bfr_len = bfr_len; + IoItmFil fil = Io_mgr.Instance.QueryFil(url); if (!fil.Exists()) throw Err_.new_wo_type("fil does not exist", "url", url); + fil_len = fil.Size(); + fil_pos = 0; + fil_eof = false; + } + public Io_url Url() {return url;} private Io_url url; + public byte[] Bfr() {return bfr;} private byte[] bfr; + public int Bfr_len() {return bfr_len;} private int bfr_len; + public long Fil_len() {return fil_len;} long fil_len; + public long Fil_pos() {return fil_pos;} long fil_pos; + public boolean Fil_eof() {return fil_eof;} private boolean fil_eof; + public boolean Bfr_load_all() {return Bfr_load(0, bfr_len);} + public boolean Bfr_load_from(int bfr_pos) { + if (bfr_pos < 0 || bfr_pos > bfr_len) throw Err_.new_wo_type("invalid bfr_pos", "bfr_pos", bfr_pos, "bfr_len", bfr_len); + for (int i = bfr_pos; i < bfr_len; i++) // shift end of bfr to bgn; EX: bfr[10] and load_from(8); [8] -> [0]; [9] -> [1]; + bfr[i - bfr_pos] = bfr[i]; + return Bfr_load(bfr_len - bfr_pos, bfr_pos); // fill rest of bfr; EX: [2]... will come from file + } + private boolean Bfr_load(int bgn, int len) { + int read = rdr.Read(bfr, bgn, len); + if (read == gplx.core.ios.streams.Io_stream_rdr_.Read_done) {fil_eof = true; return false;} + fil_pos += read; + bfr_len = bgn + read; + if (read < len) fil_eof = true; + return true; + } + public void Seek(long fil_pos) { + this.fil_pos = fil_pos; + rdr.Skip(fil_pos); + this.Bfr_load_all(); + } + public void Rls() { + bfr = null; + bfr_len = -1; + if (rdr != null) rdr.Rls(); + } + @gplx.Internal protected void Dump_to_file(int bgn, int len, String url_str, String msg) { // DBG: + String text = String_.new_u8__by_len(bfr, bgn, len); + Io_mgr.Instance.AppendFilStr(Io_url_.new_any_(url_str), msg + text + "\n"); + } + public static Io_buffer_rdr new_(Io_stream_rdr rdr, int bfr_len) { + Io_buffer_rdr rv = new Io_buffer_rdr(rdr, rdr.Url(), bfr_len); + rdr.Open(); + rv.Bfr_load(0, bfr_len); + return rv; + } + public static final Io_buffer_rdr Null = new Io_buffer_rdr(); Io_buffer_rdr() {} +} diff --git a/400_xowa/src/gplx/core/ios/Io_buffer_rdr_tst.java b/400_xowa/src/gplx/core/ios/Io_buffer_rdr_tst.java index a27517de8..fe5e8abd9 100644 --- a/400_xowa/src/gplx/core/ios/Io_buffer_rdr_tst.java +++ b/400_xowa/src/gplx/core/ios/Io_buffer_rdr_tst.java @@ -13,3 +13,50 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Io_buffer_rdr_tst { + @Before public void init() { + Io_mgr.Instance.InitEngine_mem(); + fil = Io_url_.mem_fil_("mem/byteStreamRdr.txt"); + ini_Write("0123456789"); + rdr = Io_buffer_rdr.new_(Io_stream_rdr_.New__raw(fil), 4); + } Io_buffer_rdr rdr; Io_url fil; + @After public void teardown() {rdr.Rls();} + @Test public void Bfr_load_all() { + tst_Bfr("0", "1", "2", "3").tst_ReadDone(false); + + rdr.Bfr_load_all(); + tst_Bfr("4", "5", "6", "7").tst_ReadDone(false); + + rdr.Bfr_load_all(); + tst_Bfr("8", "9"); + rdr.Bfr_load_all(); // NOTE: change to zip_rdrs make eof detection difficult; force another load to ensure that file_pos goes past file_len + tst_ReadDone(true); // NOTE: bfr truncated from 4 to 2 + } + @Test public void Bfr_load_from() { + tst_Bfr("0", "1", "2", "3").tst_ReadDone(false); + + rdr.Bfr_load_from(3); // read from pos 3 + tst_Bfr("3", "4", "5", "6").tst_ReadDone(false); + + rdr.Bfr_load_from(1); // read from pos 1 + tst_Bfr("4", "5", "6", "7").tst_ReadDone(false); + + rdr.Bfr_load_from(1); + tst_Bfr("5", "6", "7", "8").tst_ReadDone(false); + + rdr.Bfr_load_from(3); + rdr.Bfr_load_all(); // NOTE: change to zip_rdrs make eof detection difficult; force another load to ensure that file_pos goes past file_len + tst_Bfr("8", "9").tst_ReadDone(true); + } + private void ini_Write(String s) {Io_mgr.Instance.SaveFilStr(fil, s);} + Io_buffer_rdr_tst tst_Bfr(String... expdAry) { + String[] actlAry = new String[rdr.Bfr_len()]; + for (int i = 0; i < actlAry.length; i++) + actlAry[i] = String_.new_u8(rdr.Bfr(), i, i + 1); + Tfds.Eq_ary(expdAry, actlAry); + return this; + } + Io_buffer_rdr_tst tst_ReadDone(boolean expd) {Tfds.Eq(expd, rdr.Fil_eof()); return this;} +} diff --git a/400_xowa/src/gplx/core/ios/Io_fil_chkr.java b/400_xowa/src/gplx/core/ios/Io_fil_chkr.java index a27517de8..ad223ef84 100644 --- a/400_xowa/src/gplx/core/ios/Io_fil_chkr.java +++ b/400_xowa/src/gplx/core/ios/Io_fil_chkr.java @@ -13,3 +13,18 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.tests.*; +public class Io_fil_chkr implements Tst_chkr { + public Io_fil_chkr(Io_url url, String data) {this.expd_url = url; this.expd_data = data;} + public Io_url Expd_url() {return expd_url;} public Io_fil_chkr Expd_url_(Io_url v) {expd_url = v; return this;} Io_url expd_url; + public String Expd_data() {return expd_data;} public Io_fil_chkr Expd_data_(String v) {expd_data = v; return this;} private String expd_data; + public Class TypeOf() {return gplx.core.ios.Io_fil.class;} + public int Chk(Tst_mgr mgr, String path, Object actl) { + gplx.core.ios.Io_fil fil = (gplx.core.ios.Io_fil)actl; + int rv = 0; + rv += mgr.Tst_val(expd_url == null, path, "url", expd_url, fil.Url()); + rv += mgr.Tst_val(expd_data == null, path, "data", expd_data, fil.Data()); + return rv; + } +} diff --git a/400_xowa/src/gplx/core/ios/Io_line_rdr.java b/400_xowa/src/gplx/core/ios/Io_line_rdr.java index a27517de8..f61295e68 100644 --- a/400_xowa/src/gplx/core/ios/Io_line_rdr.java +++ b/400_xowa/src/gplx/core/ios/Io_line_rdr.java @@ -13,3 +13,149 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.ios.streams.*; +public class Io_line_rdr { + public Io_line_rdr (Gfo_usr_dlg usr_dlg, Io_url... urls) {this.usr_dlg = usr_dlg; this.urls = urls; if (urls.length == 0) bfr_state = Bfr_state_end;} Gfo_usr_dlg usr_dlg; + public int Url_idx() {return url_idx;} private int url_idx; + public Io_url[] Urls() {return urls;} Io_url[] urls; + public void Reset_one(Io_url url) { + this.Clear(); + urls[0] = url; + url_idx = 0; + } + public byte Line_dlm() {return line_dlm;} public Io_line_rdr Line_dlm_(byte v) {line_dlm = v; return this;} private byte line_dlm = Byte_ascii.Nl; + public byte[] Bfr() {return bfr;} private byte[] bfr; + public int Bfr_len() {return bfr_len;} private int bfr_len; + public byte Bfr_state() {return bfr_state;} private byte bfr_state = Bfr_state_bgn; static final byte Bfr_state_bgn = 0, Bfr_state_mid = 1, Bfr_state_end = 2; + public int Bfr_last_read() {return bfr_last_read;} private int bfr_last_read; + public void Bfr_last_read_add(int v) {bfr_last_read += v;} + public int Load_len() {return load_len;} public Io_line_rdr Load_len_(int v) {load_len = v; return this;} private int load_len = 4096; + long File_len() {return file_len;} long file_len = 0; + long File_pos() {return file_pos;} long file_pos = 0; + public boolean File_skip_line0() {return file_skip_line0;} public Io_line_rdr File_skip_line0_(boolean v) {file_skip_line0 = v; return this;} private boolean file_skip_line0; + public int Itm_pos_bgn() {return itm_pos_bgn;} private int itm_pos_bgn = 0; + public int Itm_pos_end() {return itm_pos_end;} private int itm_pos_end = 0; + public int Key_pos_bgn() {return key_pos_bgn;} public Io_line_rdr Key_pos_bgn_(int v) {key_pos_bgn = v; return this;} private int key_pos_bgn = -1; + public int Key_pos_end() {return key_pos_end;} public Io_line_rdr Key_pos_end_(int v) {key_pos_end = v; return this;} private int key_pos_end = -1; + public Io_line_rdr_key_gen Key_gen() {return key_gen;} public Io_line_rdr Key_gen_(Io_line_rdr_key_gen v) {key_gen = v; return this;} Io_line_rdr_key_gen key_gen = Io_line_rdr_key_gen_.first_pipe; + public void Truncate(int pos) { + this.Read_next(); + int end = Bry_find_.Find_fwd(bfr, Byte_ascii.Null); if (end == -1) end = bfr.length; + bfr = Bry_.Mid(bfr, pos, end); + bfr_len = bfr.length; + bfr_last_read = 0; + } + public boolean Read_next() { + switch (bfr_state) { + case Bfr_state_bgn: + bfr_state = Bfr_state_mid; // do not place after Load + Open_fil(); + if (!Load()) return false; + break; + case Bfr_state_end: + return false; + } + itm_pos_bgn = bfr_last_read; itm_pos_end = bfr_len; key_pos_bgn = key_pos_end = -1; + while (true) { + for (int i = bfr_last_read; i < bfr_len; i++) { + if (bfr[i] == line_dlm) { + itm_pos_end = i + 1; // +1: include find + bfr_last_read = itm_pos_end; + key_gen.Gen(this); + return true; + } + } + if (Load()) // line_dlm not found; load more + itm_pos_bgn = 0; + else { // nothing loaded; return; + itm_pos_end = bfr_len; + key_gen.Gen(this); // call key_gen b/c there may be a stray line at end; EX: "\nb|c"; call key_gen to get "b" + bfr_state = Bfr_state_end; + return bfr_last_read < bfr_len; // true if stray line at end; otherwise bfr_last_read == bfr_len and return false; + } + } + } + public boolean Match(byte[] ttl) { + switch (bfr_state) { + case Bfr_state_bgn: + if (!Read_next()) return false; + bfr_state = Bfr_state_mid; // NOTE: must set back to mid; possible for 1st read to read entire buffer; EX: 8 MB bfr, but only 1 MB file; + break; + case Bfr_state_end: + return false; + } + while (true) { + int compare = Bry_.Compare(ttl, 0, ttl.length, bfr, key_pos_bgn, key_pos_end); + if (compare == CompareAble_.Same) { // eq; return true and move fwd; EX: "BA" and "BA" + return true; + } + else if (compare < CompareAble_.Same) { // lt; return false; EX: ttl is "BA" but rdr is "BC" + return false; + } + else { // gt; keep reading; EX: ttl is "BC" but rdr is "BA" + if (!this.Read_next()) return false; + } + } + } + boolean Load() { + int old_bfr_len = bfr_len - bfr_last_read; // NOTE: preserve bytes between bfr_last_read and bfr_len; EX: "ab\nc"; preserve "c" for next ary + if (file_done) {++url_idx; if (url_idx == urls.length) {bfr_state = Bfr_state_end; return false;} Open_fil(); file_done = false;} + byte[] load_ary = new byte[old_bfr_len + load_len]; int load_ary_len = load_ary.length; // NOTE: must go after file_done chk; otherwise small wikis will allocate another 32 MB bry for sort_memory_len and cause an OutOfMemory error; DATE:20130112 + int read_len = stream.Read(load_ary, old_bfr_len, load_ary_len - old_bfr_len); + if (read_len == 0) { // nothing read; return; + ++url_idx; + if (url_idx < urls.length) { + stream.Rls(); + Open_fil(); + return true; + } + else { + stream.Rls(); + bfr_state = Bfr_state_end; + return false; + } + } + if (read_len == file_len) { + stream.Rls(); + file_done = true; +// ++url_idx; +// bfr_state = Bfr_state_end; + } + if (old_bfr_len > 0) Array_.Copy_to(bfr, bfr_last_read, load_ary, 0, old_bfr_len); // copy old_bfr over + file_pos += read_len; + bfr = load_ary; + bfr_last_read = 0; + bfr_len = read_len == load_len ? load_ary_len : old_bfr_len + read_len; // stream.Read() may return less bytes than load_ary at EOF; if so, don't shrink bfr; just mark bfr_len less + return true; + } IoStream stream; + private void Open_fil() { + Io_url url = urls[url_idx]; + usr_dlg.Prog_many(GRP_KEY, "load", "loading dump file: ~{0}", url.NameAndExt()); + if (file_skip_line0) { + byte[] stream_bry = Io_mgr.Instance.LoadFilBry(url); + int stream_bry_len = stream_bry.length; + int nl_pos = Bry_find_.Find_fwd(stream_bry, Byte_ascii.Nl, 0, stream_bry_len); + if (nl_pos == Bry_find_.Not_found) + stream_bry = Bry_.Empty; + else + stream_bry = Bry_.Mid(stream_bry, nl_pos + 1, stream_bry_len); + stream = gplx.core.ios.streams.IoStream_.ary_(stream_bry); + } + else { + stream = Io_mgr.Instance.OpenStreamRead(url); + } + file_pos = 0; file_len = stream.Len(); + file_done = false; + } boolean file_done = false; + public void Clear() { + bfr_state = Bfr_state_bgn; + if (stream != null) stream.Rls(); + bfr = null; + } + public void Rls() { + this.Clear(); + bfr = null; + } + static final String GRP_KEY = "xowa.bldr.line_rdr"; +} diff --git a/400_xowa/src/gplx/core/ios/Io_line_rdr_key_gen.java b/400_xowa/src/gplx/core/ios/Io_line_rdr_key_gen.java index a27517de8..57dbbbbf6 100644 --- a/400_xowa/src/gplx/core/ios/Io_line_rdr_key_gen.java +++ b/400_xowa/src/gplx/core/ios/Io_line_rdr_key_gen.java @@ -13,3 +13,7 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public interface Io_line_rdr_key_gen { + void Gen(Io_line_rdr bfr); +} diff --git a/400_xowa/src/gplx/core/ios/Io_line_rdr_key_gen_.java b/400_xowa/src/gplx/core/ios/Io_line_rdr_key_gen_.java index a27517de8..f2c1b1610 100644 --- a/400_xowa/src/gplx/core/ios/Io_line_rdr_key_gen_.java +++ b/400_xowa/src/gplx/core/ios/Io_line_rdr_key_gen_.java @@ -13,3 +13,42 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class Io_line_rdr_key_gen_ { + public static final Io_line_rdr_key_gen first_pipe = new Io_line_rdr_key_gen_first(Byte_ascii.Pipe); + public static final Io_line_rdr_key_gen last_pipe = new Io_line_rdr_key_gen_last(Byte_ascii.Pipe); + public static final Io_line_rdr_key_gen noop = new Io_line_rdr_key_gen_noop(); +} +class Io_line_rdr_key_gen_last implements Io_line_rdr_key_gen { + public Io_line_rdr_key_gen_last(byte fld_dlm) {this.fld_dlm = fld_dlm;} private byte fld_dlm; + public void Gen(Io_line_rdr bfr) { + int bgn = bfr.Itm_pos_bgn(), end = bfr.Itm_pos_end() - 1; // -1: ignore row_dlm + bfr.Key_pos_bgn_(end).Key_pos_end_(end); + byte[] bry = bfr.Bfr(); + for (int i = end; i >= bgn; i--) { + if (bry[i] == fld_dlm) { + bfr.Key_pos_bgn_(i + 1); // +1 to position after fldDlm + return; + } + } + bfr.Key_pos_bgn_(0); // nothing found; position at bgn + } +} +class Io_line_rdr_key_gen_first implements Io_line_rdr_key_gen { + public Io_line_rdr_key_gen_first(byte fld_dlm) {this.fld_dlm = fld_dlm;} private byte fld_dlm; + public void Gen(Io_line_rdr bfr) { + int bgn = bfr.Itm_pos_bgn(), end = bfr.Itm_pos_end(); + bfr.Key_pos_bgn_(bgn).Key_pos_end_(bgn); + byte[] bry = bfr.Bfr(); + for (int i = bgn; i < end; i++) { + if (bry[i] == fld_dlm) { + bfr.Key_pos_end_(i); + return; + } + } + bfr.Key_pos_end_(end); // nothing found; position at end + } +} +class Io_line_rdr_key_gen_noop implements Io_line_rdr_key_gen { + public void Gen(Io_line_rdr bfr) {} +} diff --git a/400_xowa/src/gplx/core/ios/Io_line_rdr_tst.java b/400_xowa/src/gplx/core/ios/Io_line_rdr_tst.java index a27517de8..7f43744c1 100644 --- a/400_xowa/src/gplx/core/ios/Io_line_rdr_tst.java +++ b/400_xowa/src/gplx/core/ios/Io_line_rdr_tst.java @@ -13,3 +13,82 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.envs.*; +public class Io_line_rdr_tst { + Io_line_rdr_fxt fxt; + @Before public void init() { + fxt = new Io_line_rdr_fxt(Io_url_.new_fil_("mem/test.txt")); + fxt.Clear(); + } + @Test public void Basic() { + fxt.File_lines_(3).tst_Read_til_lines(3, "00", "01", "02"); + fxt.tst_Read_til_lines(1); // make sure nothing more is read + } + @Test public void Load_3x() { + fxt.File_lines_(9).Load_len_lines_(3).tst_Read_til_lines(9, "00", "01", "02", "03", "04", "05", "06", "07", "08"); + } + @Test public void Load_irregular() { + fxt.File_lines_(9).Load_len_(4).tst_Read_til_lines(9, "00", "01", "02", "03", "04", "05", "06", "07", "08"); + } + @Test public void Load_multiple_files() { + fxt = new Io_line_rdr_fxt(Io_url_.new_fil_("mem/test0.txt"), Io_url_.new_fil_("mem/test1.txt"), Io_url_.new_fil_("mem/test2.txt")); + fxt.File_lines_(0, 0, 3).File_lines_(1, 3, 5).File_lines_(2, 5, 9).Load_len_(4).tst_Read_til_lines(9, "00", "01", "02", "03", "04", "05", "06", "07", "08"); + } + @Test public void Match() { + fxt.File_lines_pipe_(9).Load_len_(6); + fxt.tst_Match("00", "00"); + fxt.tst_Match("01", "01"); + fxt.tst_Match("03", "03"); + fxt.tst_Match("08", "08"); + fxt.tst_Match("12", ""); + } +} +class Io_line_rdr_fxt { + Io_line_rdr rdr; + List_adp lines = List_adp_.New(); Bry_bfr tmp = Bry_bfr_.New(); + public Io_line_rdr_fxt(Io_url... urls) {rdr = new Io_line_rdr(Gfo_usr_dlg_.Test(), urls);} + public Io_line_rdr_fxt Load_len_lines_(int v) {return Load_len_(v * 3);} // 3: 2=##, 1=\n + public Io_line_rdr_fxt Load_len_(int v) {rdr.Load_len_(v); return this;} + public Io_line_rdr_fxt File_lines_(int count) { + for (int i = 0; i < count; i++) + tmp.Add_int_fixed(i, 2).Add_byte_nl(); + Io_mgr.Instance.SaveFilBry(rdr.Urls()[0], tmp.To_bry_and_clear()); + return this; + } +// public Io_url[] Src_fils() {return src_fils;} public Io_line_rdr_fxt Src_fils_(Io_url[] v) {src_fils = v; return this;} Io_url[] src_fils; + public Io_line_rdr_fxt tst_Match(String match, String expd) { + rdr.Key_gen_(Io_line_rdr_key_gen_.first_pipe); + boolean match_v = rdr.Match(Bry_.new_u8(match)); + String actl = match_v ? String_.new_u8(rdr.Bfr(), rdr.Key_pos_bgn(), rdr.Key_pos_end()) : ""; + Tfds.Eq(expd, actl); + return this; + } + public Io_line_rdr_fxt File_lines_pipe_(int count) { + for (int i = 0; i < count; i++) + tmp.Add_int_fixed(i, 2).Add_byte(Byte_ascii.Pipe).Add_byte_nl(); + Io_mgr.Instance.SaveFilBry(rdr.Urls()[0], tmp.To_bry_and_clear()); + return this; + } + + public Io_line_rdr_fxt File_lines_(int fil_idx, int bgn, int end) { + for (int i = bgn; i < end; i++) + tmp.Add_int_fixed(i, 2).Add_byte_nl(); + Io_mgr.Instance.SaveFilBry(rdr.Urls()[fil_idx], tmp.To_bry_and_clear()); + return this; + } + public Io_line_rdr_fxt Clear() {rdr.Clear(); return this;} + public Io_line_rdr_fxt tst_Read_til_lines(int count, String... expd) { + lines.Clear(); + for (int i = 0; i < expd.length; i++) + expd[i] = expd[i] + Op_sys.Lnx.Nl_str(); + for (int i = 0; i < count; i++) { + if (rdr.Read_next()) + lines.Add(String_.new_u8(rdr.Bfr(), rdr.Itm_pos_bgn(), rdr.Itm_pos_end())); + else + break; + } + Tfds.Eq_ary_str(expd, lines.To_str_ary()); + return this; + } +} diff --git a/400_xowa/src/gplx/core/ios/Io_make_cmd.java b/400_xowa/src/gplx/core/ios/Io_make_cmd.java index a27517de8..63c539f4e 100644 --- a/400_xowa/src/gplx/core/ios/Io_make_cmd.java +++ b/400_xowa/src/gplx/core/ios/Io_make_cmd.java @@ -13,3 +13,7 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public interface Io_make_cmd extends Io_sort_cmd { + Io_sort_cmd Make_dir_(Io_url v); +} diff --git a/400_xowa/src/gplx/core/ios/Io_sort.java b/400_xowa/src/gplx/core/ios/Io_sort.java index a27517de8..8e09372b4 100644 --- a/400_xowa/src/gplx/core/ios/Io_sort.java +++ b/400_xowa/src/gplx/core/ios/Io_sort.java @@ -13,3 +13,85 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.envs.*; +import gplx.core.lists.*; +public class Io_sort { + public Io_sort Memory_max_(int v) {memory_max = v; return this;} private int memory_max = Io_mgr.Len_kb; + public Io_url[] Split(Gfo_usr_dlg usr_dlg, Io_url_gen src_fil_gen, Io_url_gen trg_fil_gen, Io_line_rdr_key_gen key_gen) {return Split(usr_dlg, src_fil_gen, trg_fil_gen, Io_sort_split_itm_sorter.Instance, key_gen);} + public Io_url[] Split(Gfo_usr_dlg usr_dlg, Io_url_gen src_fil_gen, Io_url_gen trg_fil_gen, ComparerAble row_comparer, Io_line_rdr_key_gen key_gen) { + Io_line_rdr rdr = new Io_line_rdr(usr_dlg, src_fil_gen.Prv_urls()).Load_len_(4 * Io_mgr.Len_kb).Key_gen_(key_gen); // NOTE: do not set load_len to memory_max; only want to load in increments + List_adp rv = List_adp_.New(); + Bry_bfr bfr = Bry_bfr_.Reset(Const_bfr_max); int size_cur = 0; + List_adp row_list = List_adp_.New(); + while (true) { + boolean reading = rdr.Read_next(); + int size_row = rdr.Itm_pos_end() - rdr.Itm_pos_bgn(); + int size_new = size_cur + size_row; + if (size_new > memory_max || !reading) { + usr_dlg.Prog_none(GRP_KEY, "sort", "sorting chunk"); + row_list.Sort_by(row_comparer); + Io_url trg_url = trg_fil_gen.Nxt_url(); + usr_dlg.Prog_one(GRP_KEY, "write", "writing chunk: ~{0}", trg_url.Raw()); + Split_flush(trg_url, row_list, memory_max, bfr, rv); + row_list.Resize_bounds(16); // MEM: resize bounds manually; note that each Flush-set may have widely disparately #of rows (EX: 1 row with a million pages vs. 1 million rows with 1 page) + size_new = size_row; System_.Garbage_collect(); + if (!reading) break; + } + row_list.Add(new Io_sort_split_itm(rdr)); + size_cur = size_new; + } + rdr.Rls(); bfr.Rls(); System_.Garbage_collect(); + return (Io_url[])rv.To_ary(Io_url.class); + } + public void Merge(Gfo_usr_dlg usr_dlg, Io_url[] src_ary, ComparerAble comparer, Io_line_rdr_key_gen key_gen, Io_sort_cmd cmd) { + BinaryHeap_Io_line_rdr heap = load_(usr_dlg, src_ary, comparer, key_gen, memory_max); if (heap.Len() == 0) return;//throw Err_.new_wo_type(Array_.To_str(src_ary)); + Io_line_rdr stream = null; + cmd.Sort_bgn(); + while (true) { + if (stream != null) { // stream found on previous iteration; try to put it back on heap + boolean read = stream.Read_next(); + if (read) // stream has line; add to heap + heap.Add(stream); + else { // stream is empty; rls + stream.Rls(); + if (heap.Len() == 0) break; // heap is empty; stop; + } + } + stream = heap.Pop(); // note that .Pop removes stream from heap + cmd.Sort_do(stream); + } + cmd.Sort_end(); + heap.Rls(); + } + private static void Split_flush(Io_url url, List_adp list, int max, Bry_bfr tmp, List_adp url_list) { + int len = list.Count(); + for (int i = 0; i < len; i++) { + Io_sort_split_itm itm = (Io_sort_split_itm)list.Get_at(i); + int add_len = itm.Row_end() - itm.Row_bgn(); + if ((tmp.Len() + add_len) > Const_bfr_max) Io_mgr.Instance.AppendFilBfr(url, tmp); + tmp.Add_mid(itm.Bfr(), itm.Row_bgn(), itm.Row_end()); + itm.Rls(); + } + Io_mgr.Instance.AppendFilBfr(url, tmp); + list.Clear(); + url_list.Add(url); + } + private static BinaryHeap_Io_line_rdr load_(Gfo_usr_dlg usr_dlg, Io_url[] urls, ComparerAble comparer, Io_line_rdr_key_gen key_gen, int memory_max) { + BinaryHeap_Io_line_rdr rv = new BinaryHeap_Io_line_rdr(comparer); + int urls_len = urls.length; + int default_load_len = memory_max / urls_len + 1; + for (int i = 0; i < urls_len; i++) { + Io_url url = urls[i]; + int file_len = (int)Io_mgr.Instance.QueryFil(url).Size(); + int load_len = file_len < default_load_len ? file_len : default_load_len; // PERF.NOTE: 32 MB is default, but if file is 1 MB (or else) only create a bfr for 1 MB; using 32 MB will throw OutOfMemory on -Xmx 64m; DATE:20130112 + Io_line_rdr stream_bfr = new Io_line_rdr(usr_dlg, url).Key_gen_(key_gen).Load_len_(load_len); + boolean read = stream_bfr.Read_next(); + if (read) // guard against empty files + rv.Add(stream_bfr); + } + return rv; + } + static final int Const_bfr_max = Io_mgr.Len_mb; + static final String GRP_KEY = "xowa.bldr.io_sort"; +} diff --git a/400_xowa/src/gplx/core/ios/Io_sort_cmd.java b/400_xowa/src/gplx/core/ios/Io_sort_cmd.java index a27517de8..7b6724887 100644 --- a/400_xowa/src/gplx/core/ios/Io_sort_cmd.java +++ b/400_xowa/src/gplx/core/ios/Io_sort_cmd.java @@ -13,3 +13,9 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public interface Io_sort_cmd { + void Sort_bgn(); + void Sort_do(Io_line_rdr rdr); + void Sort_end(); +} diff --git a/400_xowa/src/gplx/core/ios/Io_sort_filCmd.java b/400_xowa/src/gplx/core/ios/Io_sort_filCmd.java index a27517de8..9a6ea2ee6 100644 --- a/400_xowa/src/gplx/core/ios/Io_sort_filCmd.java +++ b/400_xowa/src/gplx/core/ios/Io_sort_filCmd.java @@ -13,3 +13,15 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public interface Io_sort_filCmd { + void Bfr_add(Io_line_rdr stream); + void Fil_bgn(Io_line_rdr stream); + void Fil_end(); +} +class Io_sort_filCmd_null implements Io_sort_filCmd { + public void Bfr_add(Io_line_rdr stream) {} + public void Fil_bgn(Io_line_rdr stream) {} + public void Fil_end() {} + public static final Io_sort_filCmd_null Instance = new Io_sort_filCmd_null(); Io_sort_filCmd_null() {} // TS.static +} diff --git a/400_xowa/src/gplx/core/ios/Io_sort_fil_basic.java b/400_xowa/src/gplx/core/ios/Io_sort_fil_basic.java index a27517de8..b087cec9a 100644 --- a/400_xowa/src/gplx/core/ios/Io_sort_fil_basic.java +++ b/400_xowa/src/gplx/core/ios/Io_sort_fil_basic.java @@ -13,3 +13,24 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class Io_sort_fil_basic implements Io_sort_cmd { // 123|bgn|end|1 + public Io_sort_fil_basic(Gfo_usr_dlg usr_dlg, Io_url_gen url_gen, int flush_len) {this.usr_dlg = usr_dlg; this.url_gen = url_gen; this.flush_len = flush_len;} Io_url_gen url_gen; Bry_bfr bfr = Bry_bfr_.New(); int flush_len; Gfo_usr_dlg usr_dlg; + public void Sort_bgn() {} + public void Sort_do(Io_line_rdr rdr) { + int bgn = rdr.Itm_pos_bgn(), end = rdr.Itm_pos_end(); + if (bfr.Len() + (end - bgn) > flush_len) Flush(); + bfr.Add_mid(rdr.Bfr(), bgn, end); + } + public void Sort_end() { + Flush(); + bfr.Rls(); + } + private void Flush() { + Io_url url = url_gen.Nxt_url(); + usr_dlg.Prog_one(GRP_KEY, "make", "making: ~{0}", url.NameAndExt()); + Io_mgr.Instance.SaveFilBry(url, bfr.Bfr(), bfr.Len()); + bfr.Clear(); + } + static final String GRP_KEY = "xowa.bldr.io_sort"; +} diff --git a/400_xowa/src/gplx/core/ios/Io_sort_misc_tst.java b/400_xowa/src/gplx/core/ios/Io_sort_misc_tst.java index a27517de8..8ff23424a 100644 --- a/400_xowa/src/gplx/core/ios/Io_sort_misc_tst.java +++ b/400_xowa/src/gplx/core/ios/Io_sort_misc_tst.java @@ -13,3 +13,44 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Io_sort_misc_tst { + @Before public void init() { + } + @Test public void Io_url_gen_dir() { + tst_Io_url_gen_dir("mem/dir/", "{0}.xdat", 4, 3, "0000.xdat", "0001.xdat", "0002.xdat"); + } + private void tst_Io_url_gen_dir(String dir_str, String fmt, int digits, int calls, String... expd) { + Io_url dir = Io_url_.mem_dir_(dir_str); + List_adp actl_list = List_adp_.New(); + Io_url_gen wkr = Io_url_gen_.dir_(dir, fmt, digits); + for (int i = 0; i < calls; i++) + actl_list.Add(wkr.Nxt_url().Raw()); + String[] actl = actl_list.To_str_ary(); + for (int i = 0; i < expd.length; i++) + expd[i] = dir_str + expd[i]; + Tfds.Eq_ary_str(expd, actl); + } + @Test public void Io_line_rdr_comparer_all() { + tst_Io_line_rdr_fld_comparer(-1, "a", "b"); + tst_Io_line_rdr_fld_comparer( 0, "a", "a"); + tst_Io_line_rdr_fld_comparer( 1, "b", "a"); + tst_Io_line_rdr_fld_comparer(-1, "a", "ab"); + tst_Io_line_rdr_fld_comparer( 1, "ab", "a"); + } + private void tst_Io_line_rdr_fld_comparer(int expd, String lhs_str, String rhs_str) { + byte[] lhs = Bry_.new_u8(lhs_str), rhs = Bry_.new_u8(rhs_str); + Tfds.Eq(expd, Bry_.Compare(lhs, 0, lhs.length, rhs, 0, rhs.length)); + } + Io_line_rdr new_Io_line_rdr(String url_str, String text) { + Io_url url = Io_url_.mem_fil_(url_str); + Io_mgr.Instance.SaveFilStr(url, text); + Io_line_rdr rv = new Io_line_rdr(Gfo_usr_dlg_.Test(), url); + rv.Read_next(); + return rv; + } + @Test public void ExternalSort() { + // fxt("c", "a", "b") + } +} diff --git a/400_xowa/src/gplx/core/ios/Io_sort_split_itm.java b/400_xowa/src/gplx/core/ios/Io_sort_split_itm.java index a27517de8..d2aa88236 100644 --- a/400_xowa/src/gplx/core/ios/Io_sort_split_itm.java +++ b/400_xowa/src/gplx/core/ios/Io_sort_split_itm.java @@ -13,3 +13,21 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class Io_sort_split_itm { + public Io_sort_split_itm() {} + public Io_sort_split_itm(Io_line_rdr rdr) {Set(rdr);} + public int Row_bgn() {return row_bgn;} private int row_bgn; + public int Row_end() {return row_end;} private int row_end; + public int Key_bgn() {return key_bgn;} private int key_bgn; + public int Key_end() {return key_end;} private int key_end; + public byte[] Bfr() {return bfr;} private byte[] bfr; + public void Set(Io_line_rdr rdr) { + bfr = rdr.Bfr(); + row_bgn = rdr.Itm_pos_bgn(); + row_end = rdr.Itm_pos_end(); + key_bgn = rdr.Key_pos_bgn(); + key_end = rdr.Key_pos_end(); + } + public void Rls() {bfr = null;} +} diff --git a/400_xowa/src/gplx/core/ios/Io_sort_split_itm_sorter.java b/400_xowa/src/gplx/core/ios/Io_sort_split_itm_sorter.java index a27517de8..9fa787d5f 100644 --- a/400_xowa/src/gplx/core/ios/Io_sort_split_itm_sorter.java +++ b/400_xowa/src/gplx/core/ios/Io_sort_split_itm_sorter.java @@ -13,3 +13,11 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class Io_sort_split_itm_sorter implements gplx.core.lists.ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + Io_sort_split_itm lhs = (Io_sort_split_itm)lhsObj, rhs = (Io_sort_split_itm)rhsObj; + return Bry_.Compare(lhs.Bfr(), lhs.Key_bgn(), lhs.Key_end(), rhs.Bfr(), rhs.Key_bgn(), rhs.Key_end()); + } + public static final Io_sort_split_itm_sorter Instance = new Io_sort_split_itm_sorter(); Io_sort_split_itm_sorter() {} // TS.static +} diff --git a/400_xowa/src/gplx/core/ios/Io_sort_tst.java b/400_xowa/src/gplx/core/ios/Io_sort_tst.java index a27517de8..cb333381a 100644 --- a/400_xowa/src/gplx/core/ios/Io_sort_tst.java +++ b/400_xowa/src/gplx/core/ios/Io_sort_tst.java @@ -13,3 +13,52 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.strings.*; +public class Io_sort_tst { + Io_sort_fxt fxt = new Io_sort_fxt(); + @Test public void ExternalSort() { + fxt.Clear().Memory_max_(12).Src_(fxt.GenRandom(6, 4)).Sorted_(fxt.GenOrdered(6, 4)).tst(); + fxt.Clear().Memory_max_(64).Src_(fxt.GenRandom(50, 4)).Sorted_(fxt.GenOrdered(50, 4)).tst(); + } +} +class Io_sort_fxt { + Io_sort externalSort = new Io_sort().Memory_max_(Io_mgr.Len_kb); + String_bldr sb = String_bldr_.new_(); + public Io_sort_fxt Clear() {Io_mgr.Instance.InitEngine_mem(); return this;} + public Io_sort_fxt Memory_max_(int v) {externalSort.Memory_max_(v); return this;} + public Io_sort_fxt Src_(String v) {src = v; return this;} private String src; + public Io_sort_fxt Sorted_(String v) {sorted = v; return this;} private String sorted; + public void tst() { + Io_url src_url = Io_url_.mem_fil_("mem/src.txt"); + Io_url trg_url = Io_url_.mem_fil_("mem/trg.txt"); + Io_mgr.Instance.DeleteFil(src_url); Io_mgr.Instance.DeleteFil(trg_url); + + Io_mgr.Instance.SaveFilStr(src_url, src); + + Gfo_usr_dlg usr_dlg = Gfo_usr_dlg_.Test(); + Io_url_gen src_fil_gen = Io_url_gen_.fil_(src_url); + Io_url[] tmp_url_ary = externalSort.Split(usr_dlg, src_fil_gen, Io_url_gen_.dir_(src_url.OwnerDir()), Io_line_rdr_key_gen_.first_pipe); + Io_sort_fil_basic cmd = new Io_sort_fil_basic(usr_dlg, Io_url_gen_.fil_(trg_url), Io_mgr.Len_kb); + externalSort.Merge(usr_dlg, tmp_url_ary, Io_sort_split_itm_sorter.Instance, Io_line_rdr_key_gen_.first_pipe, cmd); + + String actl = Io_mgr.Instance.LoadFilStr(trg_url); + Tfds.Eq_ary_str(String_.SplitLines_nl(sorted), String_.SplitLines_nl(actl)); + } + public String GenRandom(int rows, int pad) { + List_adp list = List_adp_.New(); + for (int i = 0; i < rows; i++) + list.Add(Int_.To_str_pad_bgn_zero(i, pad) + "|"); + list.Shuffle(); + for (int i = 0; i < rows; i++) { + String itm = (String)list.Get_at(i); + sb.Add(itm).Add_char_nl(); + } + return sb.To_str_and_clear(); + } + public String GenOrdered(int rows, int pad) { + for (int i = 0; i < rows; i++) + sb.Add(Int_.To_str_pad_bgn_zero(i, pad) + "|" + "\n"); + return sb.To_str_and_clear(); + } +} diff --git a/400_xowa/src/gplx/core/ios/Io_stream_rdr_process.java b/400_xowa/src/gplx/core/ios/Io_stream_rdr_process.java index a27517de8..a9a46e890 100644 --- a/400_xowa/src/gplx/core/ios/Io_stream_rdr_process.java +++ b/400_xowa/src/gplx/core/ios/Io_stream_rdr_process.java @@ -13,3 +13,54 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import java.io.InputStream; +import gplx.core.ios.streams.*; +public class Io_stream_rdr_process implements Io_stream_rdr { + private Process process; + private InputStream stream_read; + private String[] process_args; + Io_stream_rdr_process(Io_url process_exe, Io_url stream_url, String[] process_args) {this.process_exe = process_exe; this.url = stream_url; this.process_args = process_args;} + public byte Tid() {return Io_stream_tid_.Tid__bzip2;} // for now, classify as bzip2; not sure if separate tid is necessary + public boolean Exists() {return this.Len() > 0;} + public Io_url Url() {return url;} public Io_stream_rdr Url_(Io_url v) {url = v; return this;} private Io_url url; + public long Len() {return len;} public Io_stream_rdr Len_(long v) {len = v; return this;} private long len; + public Io_url Process_exe() {return process_exe;} private Io_url process_exe; + public Io_stream_rdr Open() { + ProcessBuilder pb = new ProcessBuilder(process_args); + pb.redirectErrorStream(false); + try {process = pb.start();} + catch (Exception e) {throw Err_.new_exc(e, "core", "process init failed", "args", String_.AryXtoStr(process_args));} + stream_read = process.getInputStream(); + return this; + } + public void Open_mem(byte[] v) {throw Err_.new_unimplemented();} + public Object Under() {throw Err_.new_unimplemented();} + + public int Read(byte[] bry, int bgn, int len) { + try { + int rv = 0; + int cur_pos = bgn; + int cur_len = len; + while (true) { + int read = stream_read.read(bry, cur_pos, cur_len); + if (read <= 0) break; + rv += read; + cur_pos += read; + cur_len -= read; + if (rv >= len) break; + } + return rv; + } catch (Exception e) {throw Err_.new_exc(e, "io", "process read failed", "bgn", bgn, "len", len);} + } + public long Skip(long len) { + try {return stream_read.skip(len);} + catch (Exception e) {throw Err_.new_exc(e, "io", "process skip failed", "len", len);} + } + public void Rls() { + try {stream_read.close();} + catch (Exception e) {throw Err_.new_exc(e, "io", "process rls failed");} + process.destroy(); + } + public static Io_stream_rdr_process new_(Io_url process_exe, Io_url stream_url, String... process_args) {return new Io_stream_rdr_process(process_exe, stream_url, process_args);} +} diff --git a/400_xowa/src/gplx/core/ios/Io_stream_zip_mgr.java b/400_xowa/src/gplx/core/ios/Io_stream_zip_mgr.java index a27517de8..072726058 100644 --- a/400_xowa/src/gplx/core/ios/Io_stream_zip_mgr.java +++ b/400_xowa/src/gplx/core/ios/Io_stream_zip_mgr.java @@ -13,3 +13,42 @@ 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.core.ios; import gplx.*; import gplx.core.*; +import gplx.core.ios.streams.*; +public class Io_stream_zip_mgr { + private Io_stream_wtr wtr__gzip, wtr__zip, wtr__bzip2, wtr__xz; + public byte[] Zip(byte type, byte[] val) { + if (type == Io_stream_tid_.Tid__raw) return val; + Io_stream_wtr wtr = Wtr(type); + wtr.Write(val, 0, val.length); + wtr.Flush(); + return wtr.To_ary_and_clear(); + } + public byte[] Unzip(byte type, byte[] val) { + if (type == Io_stream_tid_.Tid__raw) return val; + Io_stream_rdr rdr = Rdr(type); + rdr.Open_mem(val); + return Io_stream_rdr_.Load_all_as_bry(Bry_bfr_.New(), rdr); + } + private Io_stream_wtr Wtr(byte type) { + Bry_bfr bfr = Bry_bfr_.New(); + switch (type) { + case Io_stream_tid_.Tid__gzip: if (wtr__gzip == null) wtr__gzip = Io_stream_wtr_.New_by_mem(bfr, Io_stream_tid_.Tid__gzip); return wtr__gzip.Open(); + case Io_stream_tid_.Tid__zip: if (wtr__zip == null) wtr__zip = Io_stream_wtr_.New_by_mem(bfr, Io_stream_tid_.Tid__zip); return wtr__zip.Open(); + case Io_stream_tid_.Tid__bzip2: if (wtr__bzip2 == null) wtr__bzip2 = Io_stream_wtr_.New_by_mem(bfr, Io_stream_tid_.Tid__bzip2); return wtr__bzip2.Open(); + case Io_stream_tid_.Tid__xz: if (wtr__xz == null) wtr__xz = Io_stream_wtr_.New_by_mem(bfr, Io_stream_tid_.Tid__xz); return wtr__xz.Open(); + case Io_stream_tid_.Tid__raw: + default: throw Err_.new_unhandled(type); + } + } + private Io_stream_rdr Rdr(byte type) { // TS.MEM: DATE:2016-07-12 + switch (type) { + case Io_stream_tid_.Tid__gzip: return Io_stream_rdr_.New_by_tid(Io_stream_tid_.Tid__gzip); + case Io_stream_tid_.Tid__zip: return Io_stream_rdr_.New_by_tid(Io_stream_tid_.Tid__zip); + case Io_stream_tid_.Tid__bzip2: return Io_stream_rdr_.New_by_tid(Io_stream_tid_.Tid__bzip2); + case Io_stream_tid_.Tid__xz: return Io_stream_rdr_.New_by_tid(Io_stream_tid_.Tid__xz); + case Io_stream_tid_.Tid__raw: + default: throw Err_.new_unhandled(type); + } + } +} diff --git a/400_xowa/src/gplx/core/ios/Io_url_gen.java b/400_xowa/src/gplx/core/ios/Io_url_gen.java index a27517de8..fda5ace2f 100644 --- a/400_xowa/src/gplx/core/ios/Io_url_gen.java +++ b/400_xowa/src/gplx/core/ios/Io_url_gen.java @@ -13,3 +13,10 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public interface Io_url_gen { + Io_url Cur_url(); + Io_url Nxt_url(); + Io_url[] Prv_urls(); + void Del_all(); +} diff --git a/400_xowa/src/gplx/core/ios/Io_url_gen_.java b/400_xowa/src/gplx/core/ios/Io_url_gen_.java index a27517de8..efa1d1d54 100644 --- a/400_xowa/src/gplx/core/ios/Io_url_gen_.java +++ b/400_xowa/src/gplx/core/ios/Io_url_gen_.java @@ -13,3 +13,31 @@ 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.core.ios; import gplx.*; import gplx.core.*; +public class Io_url_gen_ { + public static Io_url_gen dir_(Io_url v) {return new Io_url_gen_dir(v);} + public static Io_url_gen dir_(Io_url v, String fmt, int digits) {return new Io_url_gen_dir(v).Fmt_(fmt).Fmt_digits_(digits);} + public static Io_url_gen fil_(Io_url v) {return new Io_url_gen_fil(v);} +} +class Io_url_gen_dir implements Io_url_gen { + public String Fmt() {return fmt;} public Io_url_gen_dir Fmt_(String v) {fmt = v; return this;} private String fmt = "{0}.csv"; + public int Fmt_digits() {return fmt_digits;} public Io_url_gen_dir Fmt_digits_(int v) {fmt_digits = v; return this;} private int fmt_digits = 10; + public Io_url Cur_url() {return cur_url;} Io_url cur_url; + public Io_url Nxt_url() {cur_url = dir.GenSubFil(String_.Format(fmt, Int_.To_str_pad_bgn_zero(idx++, fmt_digits))); return cur_url;} private int idx = 0; + public Io_url[] Prv_urls() { + Io_url[] rv = new Io_url[idx]; + for (int i = 0; i < idx; i++) { + rv[i] = dir.GenSubFil(String_.Format(fmt, Int_.To_str_pad_bgn_zero(i, fmt_digits))); + } + return rv; + } + public void Del_all() {if (Io_mgr.Instance.ExistsDir(dir)) Io_mgr.Instance.DeleteDirDeep(dir);} + public Io_url_gen_dir(Io_url dir) {this.dir = dir;} Io_url dir; +} +class Io_url_gen_fil implements Io_url_gen { + public Io_url Cur_url() {return cur_url;} Io_url cur_url; + public Io_url Nxt_url() {return cur_url;} + public Io_url[] Prv_urls() {return new Io_url[]{cur_url};} + public void Del_all() {Io_mgr.Instance.DeleteFil_args(cur_url).MissingFails_off().Exec();} + public Io_url_gen_fil(Io_url fil) {this.cur_url = fil;} +} diff --git a/400_xowa/src/gplx/core/lists/Queue_adp.java b/400_xowa/src/gplx/core/lists/Queue_adp.java index a27517de8..94db92ebd 100644 --- a/400_xowa/src/gplx/core/lists/Queue_adp.java +++ b/400_xowa/src/gplx/core/lists/Queue_adp.java @@ -13,3 +13,41 @@ 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.core.lists; import gplx.*; import gplx.core.*; +public class Queue_adp { + private int count; + private Queue_itm head; + private Queue_itm tail; + public int Count() {return count;} + public boolean Is_empty() {return count == 0;} + public void Enqueue(Object data) { + Queue_itm item = new Queue_itm(data); + if (head == null) { + head = item; + } + if (tail != null) { + tail.next = item; + } + tail = item; + count++; + } + public Object Dequeue() { + Object rv = Peek(); + head = head.next; + count--; + return rv; + } + public Object Peek() { + if (head == null) throw Err_.new_wo_type("queue is empty"); + Queue_itm rv = head; + return rv.Data(); + } +} +class Queue_itm { + private final Object data; + public Queue_itm next; + public Queue_itm(Object data) { + this.data = data; + } + public Object Data() {return data;} +} diff --git a/400_xowa/src/gplx/core/lists/Queue_adp_tst.java b/400_xowa/src/gplx/core/lists/Queue_adp_tst.java index a27517de8..7ddcea028 100644 --- a/400_xowa/src/gplx/core/lists/Queue_adp_tst.java +++ b/400_xowa/src/gplx/core/lists/Queue_adp_tst.java @@ -13,3 +13,47 @@ 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.core.lists; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.tests.*; +public class Queue_adp_tst { + private final Queue_adp_fxt fxt = new Queue_adp_fxt(); + @Test public void Empty() { + boolean pass = true; + try { + fxt.Test__dequeue(null, -1); + pass = false; + } catch (Exception e) { + Err_.Noop(e); + return; + } + if (pass) throw Err_.new_wo_type("empty should have failed"); + } + @Test public void Add_1() { + fxt.Exec__enqueue("a"); + fxt.Test__dequeue("a", 0); + } + @Test public void Add_n() { + fxt.Exec__enqueue("a"); + fxt.Exec__enqueue("b"); + fxt.Exec__enqueue("c"); + fxt.Test__dequeue("a", 2); + fxt.Test__dequeue("b", 1); + fxt.Test__dequeue("c", 0); + } + @Test public void Mix() { + fxt.Exec__enqueue("a"); + fxt.Exec__enqueue("b"); + fxt.Test__dequeue("a", 1); + fxt.Exec__enqueue("c"); + fxt.Test__dequeue("b", 1); + fxt.Test__dequeue("c", 0); + } +} +class Queue_adp_fxt { + private final Queue_adp queue = new Queue_adp(); + public void Exec__enqueue(String s) {queue.Enqueue(s);} + public void Test__dequeue(String expd_data, int expd_len) { + Gftest.Eq__str(expd_data, (String)queue.Dequeue()); + Gftest.Eq__int(expd_len , queue.Count()); + } +} diff --git a/400_xowa/src/gplx/core/lists/StatRng.java b/400_xowa/src/gplx/core/lists/StatRng.java index a27517de8..e0fe5b792 100644 --- a/400_xowa/src/gplx/core/lists/StatRng.java +++ b/400_xowa/src/gplx/core/lists/StatRng.java @@ -13,3 +13,128 @@ 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.core.lists; import gplx.*; import gplx.core.*; +class StatRng_fxt { // UNUSED:useful for stat processing + StatRng rng; + public StatRng_fxt ini_(int lo_ary_len, int hi_ary_len, int... slot_hi_ary) { + rng = new StatRng(lo_ary_len, hi_ary_len, slot_hi_ary); + return this; + } + public StatRng_fxt tst_(int... vals) { + for (int i = 0; i < vals.length; i++) { + int val = vals[i]; + rng.Assign(val, val); + } + Tfds.Eq(expd_count, rng.Count, "Count"); + Tfds.Eq(expd_lo, rng.Lo, "Lo"); + Tfds.Eq(expd_hi, rng.Hi, "Hi"); + Tfds.Eq_float(expd_avg, rng.Avg()); + Tfds.Eq_ary(expd_lo_ary, XtoIntAry(rng.Lo_ary), "Lo_ary"); + Tfds.Eq_ary(expd_hi_ary, XtoIntAry(rng.Hi_ary), "Hi_ary"); + Tfds.Eq_ary(expd_slots, XtoIntAry(rng.Slot_ary), "Slots"); + return this; + } + int[] XtoIntAry(StatItm[] ary) { + int[] rv = new int[ary.length]; + for (int i = 0; i < rv.length; i++) + rv[i] = ary[i].Val; + return rv; + } + int[] XtoIntAry(StatRng[] ary) { + int[] rv = new int[ary.length]; + for (int i = 0; i < rv.length; i++) + rv[i] = ary[i].Count; + return rv; + } + public StatRng_fxt Count_(int v) {expd_count = v; return this;} private int expd_count; + public StatRng_fxt Lo_(int v) {expd_lo = v; return this;} private int expd_lo; + public StatRng_fxt Hi_(int v) {expd_hi = v; return this;} private int expd_hi; + public StatRng_fxt Avg_(float v) {expd_avg = v; return this;} float expd_avg; + public StatRng_fxt Lo_ary_(int... v) {expd_lo_ary = v; return this;} private int[] expd_lo_ary; + public StatRng_fxt Hi_ary_(int... v) {expd_hi_ary = v; return this;} private int[] expd_hi_ary; + public StatRng_fxt Slots_(int... v) {expd_slots = v; return this;} private int[] expd_slots; +} +class StatRng { +// public String Key; + public int Lo = Int_.Max_value; + public int Hi = Int_.Min_value; + public long Sum = 0; + public int Count = 0; + public float Avg() {return Sum / Count;} + public final StatItm[] Lo_ary; + public int Lo_ary_bound; + public int Lo_ary_len; + public final StatItm[] Hi_ary; + public int Hi_ary_bound; + public int Hi_ary_len; + public StatRng[] Slot_ary; + public int Slot_ary_len; + public StatRng(int lo_ary_len, int hi_ary_len, int... slot_hi_ary) { + this.Lo_ary_len = lo_ary_len; + this.Lo_ary_bound = Int_.Max_value; + this.Lo_ary = NewBoundAry(lo_ary_len, Int_.Max_value); + this.Hi_ary_len = hi_ary_len; + this.Hi_ary_bound = Int_.Min_value; + this.Hi_ary = NewBoundAry(hi_ary_len, Int_.Min_value); + if (slot_hi_ary != null && slot_hi_ary.length > 0) { + Slot_ary_len = slot_hi_ary.length + 1; // + 1 to hold max value + Slot_ary = new StatRng[Slot_ary_len]; + int slot_lo = Int_.Min_value; + for (int i = 0; i < Slot_ary_len - 1; i++) { + int slot_hi = slot_hi_ary[i]; + Slot_ary[i] = NewSlot(slot_lo, slot_hi); + slot_lo = slot_hi; + } + Slot_ary[Slot_ary_len - 1] = NewSlot(slot_lo, Int_.Max_value); + } + } + public void Assign(Object key, int val) { + if (val < Lo) Lo = val; + if (val > Hi) Hi = val; + Sum += val; + ++Count; + + if (Slot_ary_len > 0) { + for (int i = 0; i < Slot_ary_len; i++) { + StatRng slot = Slot_ary[i]; + if (val >= slot.Lo && val < slot.Hi) + slot.Assign(key, val); + } + } + if (val < Lo_ary_bound) { + Lo_ary_bound = CalcCutoff(Lo_ary, CompareAble_.More, Int_.Min_value, key, val); + } + if (val > Hi_ary_bound) { + Hi_ary_bound = CalcCutoff(Hi_ary, CompareAble_.Less, Int_.Max_value, key, val); + } + } + int CalcCutoff(StatItm[] ary, int comp, int bgn_bound, Object key, int val) { + int new_bound = bgn_bound; + for (int i = 0; i < ary.length; i++) { + StatItm itm = ary[i]; + if (Int_.Compare(itm.Val, val) == comp) { + itm = new StatItm(key, val); + ary[i] = itm; + } + if (Int_.Compare(itm.Val, new_bound) == comp) new_bound = itm.Val; + } + return new_bound; + } + StatRng NewSlot(int lo, int hi) { + StatRng rv = new StatRng(0, 0); + rv.Lo = lo; + rv.Hi = hi; + return rv; + } + StatItm[] NewBoundAry(int len, int dflt) { + StatItm[] rv = new StatItm[len]; + for (int i = 0; i < len; i++) + rv[i] = new StatItm(null, dflt); + return rv; + } +} +class StatItm { + public Object Key; + public int Val; + public StatItm(Object key, int val) {this.Key = key; this.Val = val;} +} diff --git a/400_xowa/src/gplx/core/lists/StatRng_tst.java b/400_xowa/src/gplx/core/lists/StatRng_tst.java index a27517de8..928398b1a 100644 --- a/400_xowa/src/gplx/core/lists/StatRng_tst.java +++ b/400_xowa/src/gplx/core/lists/StatRng_tst.java @@ -13,3 +13,25 @@ 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.core.lists; import gplx.*; import gplx.core.*; +import org.junit.*; +public class StatRng_tst { +// Mwl_parser_fxt fx = new Mwl_parser_fxt(); Pf_func_lang_rsc rsc = Pf_func_lang_rsc.Instance; + StatRng_fxt fx = new StatRng_fxt(); + @Test public void Empty() { + fx.ini_(1, 1, 5); + fx.Count_(7).Lo_(2).Hi_(8).Avg_(5) + .Lo_ary_(2) + .Hi_ary_(8) + .Slots_(3, 4); + fx.tst_(5,7,2,8,3,4,6); + } +//@Test public void Basic() {fx.Test_parse_tmpl_str_test("{{#switch:{{{1}}}|a=1|b=2|3}}", "{{test|a}}", "1");} +//@Test public void Basic() {fx.Test_parse_tmpl_str_test("{{#switch:{{{1}}}|b=2|#default=3|a=1}}", "{{test|a}}", "1");} +//@Test public void Basic() {fx.Test_parse_tmpl_str_test("{{#switch:{{{1}}}|a|b|c=1|d=2}}", "{{test|a}}", "1");} +} +/* +public class Pf_func_switch_tst { +// Mwl_parser_fxt fx = new Mwl_parser_fxt(); Pf_func_lang_rsc rsc = Pf_func_lang_rsc.Instance; + +*/ \ No newline at end of file diff --git a/400_xowa/src/gplx/core/lists/binary_searches/Binary_comparer.java b/400_xowa/src/gplx/core/lists/binary_searches/Binary_comparer.java index a27517de8..0d579a47e 100644 --- a/400_xowa/src/gplx/core/lists/binary_searches/Binary_comparer.java +++ b/400_xowa/src/gplx/core/lists/binary_searches/Binary_comparer.java @@ -13,3 +13,7 @@ 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.core.lists.binary_searches; import gplx.*; import gplx.core.*; import gplx.core.lists.*; +public interface Binary_comparer { + int Compare_val_to_obj(Object val, Object obj); +} diff --git a/400_xowa/src/gplx/core/lists/binary_searches/Binary_search_.java b/400_xowa/src/gplx/core/lists/binary_searches/Binary_search_.java index a27517de8..da377184a 100644 --- a/400_xowa/src/gplx/core/lists/binary_searches/Binary_search_.java +++ b/400_xowa/src/gplx/core/lists/binary_searches/Binary_search_.java @@ -13,3 +13,49 @@ 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.core.lists.binary_searches; import gplx.*; import gplx.core.*; import gplx.core.lists.*; +public class Binary_search_ { + public static int Search(CompareAble[] ary , CompareAble val) {return Search(new Binary_search_grp__ary(ary) , new Binary_search_cmp__comparable(val));} + public static int Search(Object[] ary , Binary_comparer comparer, Object val) {return Search(new Binary_search_grp__ary(ary), new Binary_search_cmp__comparer(comparer, val));} + private static int Search(Binary_search_grp grp, Binary_search_cmp cmp) { + int grp_len = grp.Len(); + if (grp_len == 1) return 0; // if 1 item, return 0; + if (grp_len == 0) return Bry_find_.Not_found; // if 0 item, return -1 + + // init + int interval = grp_len / 2; + int pos = interval - List_adp_.Base1; + int pos_last = grp_len - 1; + int pos_prv = -1; + int loop_count = 0; + while (loop_count++ < 32) { // 32=32-bit integer + Object lo = grp.Get_at(pos); + Object hi = pos + 1 == grp_len ? null : grp.Get_at(pos + 1); + int adj = 0; + int lo_comp = cmp.Compare(lo); + if (lo_comp == CompareAble_.Less) // val is < lo; search slots below + adj = -1; + else { + if (hi == null) return pos; // hi is null when at last slot in ary + int hi_comp = cmp.Compare(hi); + switch (hi_comp) { + case CompareAble_.More: // val is > hi; search slots above + adj = 1; + break; + case CompareAble_.Same: // val is > hi; search slots above + return pos + 1; + case CompareAble_.Less: // val is > lo and < hi; return slot + return pos; + } + } + interval /= 2; + if (interval == 0) interval = 1; // do not allow 0 intervals; pos must always change; + pos += (interval * adj); + if (pos == 0 && pos_prv == 0) break; // NOTE: this will only happen when 1st member is not "" + if (pos < 0) pos = 0; + else if (pos > pos_last) pos = pos_last; + pos_prv = pos; + } + return Bry_find_.Not_found; // should only occur if (a) ary's 0th slot is not ""; or (b) some unknown error + } +} diff --git a/400_xowa/src/gplx/core/lists/binary_searches/Binary_search__tst.java b/400_xowa/src/gplx/core/lists/binary_searches/Binary_search__tst.java index a27517de8..38239e541 100644 --- a/400_xowa/src/gplx/core/lists/binary_searches/Binary_search__tst.java +++ b/400_xowa/src/gplx/core/lists/binary_searches/Binary_search__tst.java @@ -13,3 +13,41 @@ 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.core.lists.binary_searches; import gplx.*; import gplx.core.*; import gplx.core.lists.*; +import org.junit.*; import gplx.core.primitives.*; +public class Binary_search__tst { + private final Binary_search__fxt fxt = new Binary_search__fxt(); + @Test public void Basic() { + fxt.Init__ary("", "e", "j", "o", "t", "y"); + fxt.Test__binary_search("a", 0); + fxt.Test__binary_search("f", 1); + fxt.Test__binary_search("k", 2); + fxt.Test__binary_search("p", 3); + fxt.Test__binary_search("u", 4); + fxt.Test__binary_search("z", 5); + } + @Test public void One() { + fxt.Init__ary(""); + fxt.Test__binary_search("a", 0); + } + @Test public void Catpage() { + String[] ary = new String[25]; + for (int i = 0; i < 25; ++i) + ary[i] = Int_.To_str_pad_bgn_zero(i, 2); + fxt.Init__ary(ary); + fxt.Test__binary_search("10", 10); // was 9 + } +} +class Binary_search__fxt { + private String_obj_val[] ary; + public void Init__ary(String... v) { + int ary_len = v.length; + ary = new String_obj_val[ary_len]; + for (int i = 0; i < ary_len; i++) + ary[i] = String_obj_val.new_(v[i]); + } + public void Test__binary_search(String val, int expd) { + int actl = Binary_search_.Search(ary, String_obj_val.new_(val)); + Tfds.Eq(expd, actl, val); + } +} diff --git a/400_xowa/src/gplx/core/lists/binary_searches/Binary_search_cmp.java b/400_xowa/src/gplx/core/lists/binary_searches/Binary_search_cmp.java index a27517de8..77beb8f9f 100644 --- a/400_xowa/src/gplx/core/lists/binary_searches/Binary_search_cmp.java +++ b/400_xowa/src/gplx/core/lists/binary_searches/Binary_search_cmp.java @@ -13,3 +13,22 @@ 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.core.lists.binary_searches; import gplx.*; import gplx.core.*; import gplx.core.lists.*; +import gplx.core.lists.*; +interface Binary_search_cmp { + int Compare(Object comp); +} +class Binary_search_cmp__comparable implements Binary_search_cmp { + private final CompareAble val; + public Binary_search_cmp__comparable(CompareAble val) {this.val = val;} + public int Compare(Object comp) { + return val.compareTo((CompareAble)comp); + } +} +class Binary_search_cmp__comparer implements Binary_search_cmp { + private final Binary_comparer comparer; private final Object val; + public Binary_search_cmp__comparer(Binary_comparer comparer, Object val) {this.comparer = comparer; this.val = val;} + public int Compare(Object comp) { + return comparer.Compare_val_to_obj(val, comp); + } +} diff --git a/400_xowa/src/gplx/core/lists/binary_searches/Binary_search_grp.java b/400_xowa/src/gplx/core/lists/binary_searches/Binary_search_grp.java index a27517de8..181434ab8 100644 --- a/400_xowa/src/gplx/core/lists/binary_searches/Binary_search_grp.java +++ b/400_xowa/src/gplx/core/lists/binary_searches/Binary_search_grp.java @@ -13,3 +13,20 @@ 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.core.lists.binary_searches; import gplx.*; import gplx.core.*; import gplx.core.lists.*; +interface Binary_search_grp { + int Len(); + Object Get_at(int i); +} +class Binary_search_grp__ary implements Binary_search_grp { + private final Object[] ary; + public Binary_search_grp__ary(Object[] ary) {this.ary = ary;} + public int Len() {return ary.length;} + public Object Get_at(int i) {return ary[i];} +} +class Binary_search_grp__list implements Binary_search_grp { + private final List_adp list; + public Binary_search_grp__list(List_adp list) {this.list = list;} + public int Len() {return list.Len();} + public Object Get_at(int i) {return list.Get_at(i);} +} diff --git a/400_xowa/src/gplx/core/lists/hashs/Hash_adp__int.java b/400_xowa/src/gplx/core/lists/hashs/Hash_adp__int.java index a27517de8..f4d8d1998 100644 --- a/400_xowa/src/gplx/core/lists/hashs/Hash_adp__int.java +++ b/400_xowa/src/gplx/core/lists/hashs/Hash_adp__int.java @@ -13,3 +13,18 @@ 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.core.lists.hashs; import gplx.*; import gplx.core.*; import gplx.core.lists.*; +import gplx.core.primitives.*; +public class Hash_adp__int { + private final Hash_adp hash = Hash_adp_.New(); + private final Int_obj_ref tmp_key = Int_obj_ref.New_neg1(); + public void Clear() {hash.Clear();} + public int Len() {return hash.Count();} + public Object Get_by_or_fail(int key) {synchronized (tmp_key) {return hash.Get_by_or_fail(tmp_key.Val_(key));}} // LOCK:used by Xomp_ns_ord_mgr in xomp; DATE:2016-10-18 + public Object Get_by_or_null(int key) {synchronized (tmp_key) {return hash.Get_by(tmp_key.Val_(key));}} // LOCK:used by Xomp_ns_ord_mgr in xomp; DATE:2016-10-18 + public void Add(int key, Object obj) {hash.Add(Int_obj_ref.New(key), obj);} + public void Add(Int_obj_ref key, Object obj) {hash.Add(key, obj);} + public void Add_if_dupe_use_1st(int key, Object obj) {hash.Add_if_dupe_use_1st(Int_obj_ref.New(key), obj);} + public void Add_if_dupe_use_nth(Int_obj_ref key, Object obj) {hash.Add_if_dupe_use_nth(key, obj);} + public Hash_adp__int Add_as_bry(int key, String val) {hash.Add(Int_obj_ref.New(key), Bry_.new_u8(val)); return this;} +} diff --git a/400_xowa/src/gplx/core/net/Gfo_inet_conn.java b/400_xowa/src/gplx/core/net/Gfo_inet_conn.java index a27517de8..29c822328 100644 --- a/400_xowa/src/gplx/core/net/Gfo_inet_conn.java +++ b/400_xowa/src/gplx/core/net/Gfo_inet_conn.java @@ -13,3 +13,10 @@ 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.core.net; import gplx.*; import gplx.core.*; +public interface Gfo_inet_conn { + int Tid(); + void Clear(); + void Upload_by_bytes(String url, byte[] data); + byte[] Download_as_bytes_or_null(String url); // return null instead of throwing exception +} diff --git a/400_xowa/src/gplx/core/net/Gfo_inet_conn_.java b/400_xowa/src/gplx/core/net/Gfo_inet_conn_.java index a27517de8..71ca651bc 100644 --- a/400_xowa/src/gplx/core/net/Gfo_inet_conn_.java +++ b/400_xowa/src/gplx/core/net/Gfo_inet_conn_.java @@ -13,3 +13,33 @@ 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.core.net; import gplx.*; import gplx.core.*; +public class Gfo_inet_conn_ { + public static final int Tid__http = 1, Tid__mem__hash = 2, Tid__mem__pile = 3; + public static Gfo_inet_conn new_http() {return new Gfo_inet_conn__http();} + public static Gfo_inet_conn new_mem_hash() {return new Gfo_inet_conn__mem__hash();} + public static Gfo_inet_conn new_mem_pile() {return new Gfo_inet_conn__mem__pile();} + public static Gfo_inet_conn new_() { + switch (new_prototype) { + default: + case Tid__http: return new_http(); + case Tid__mem__hash: return new_mem_hash(); + case Tid__mem__pile: return new_mem_pile(); + } + } + public static void new_prototype_(int v) {new_prototype = v;} private static int new_prototype = Tid__http; +} +class Gfo_inet_conn__mem__hash implements Gfo_inet_conn { + private final Hash_adp hash = Hash_adp_.New(); + public int Tid() {return Gfo_inet_conn_.Tid__mem__hash;} + public void Clear() {hash.Clear();} + public void Upload_by_bytes(String url, byte[] data) {hash.Add(url, data);} + public byte[] Download_as_bytes_or_null(String url) {return (byte[])hash.Get_by(url);} +} +class Gfo_inet_conn__mem__pile implements Gfo_inet_conn { + private final List_adp pile = List_adp_.New(); + public int Tid() {return Gfo_inet_conn_.Tid__mem__hash;} + public void Clear() {pile.Clear();} + public void Upload_by_bytes(String url, byte[] data) {pile.Add(data);} + public byte[] Download_as_bytes_or_null(String url) {return (byte[])List_adp_.Pop_last(pile);} +} diff --git a/400_xowa/src/gplx/core/net/Gfo_inet_conn__http.java b/400_xowa/src/gplx/core/net/Gfo_inet_conn__http.java index a27517de8..eb20212ab 100644 --- a/400_xowa/src/gplx/core/net/Gfo_inet_conn__http.java +++ b/400_xowa/src/gplx/core/net/Gfo_inet_conn__http.java @@ -13,3 +13,15 @@ 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.core.net; import gplx.*; import gplx.core.*; +import gplx.core.ios.*; +class Gfo_inet_conn__http implements Gfo_inet_conn { + private final IoEngine_xrg_downloadFil downloader = IoEngine_xrg_downloadFil.new_("", Io_url_.Empty); + public int Tid() {return Gfo_inet_conn_.Tid__http;} + public void Clear() {throw Err_.new_unsupported();} + public void Upload_by_bytes(String url, byte[] data) {throw Err_.new_unsupported();} + public byte[] Download_as_bytes_or_null(String url) { + try {return downloader.Exec_as_bry(url);} + catch (Exception e) {Err_.Noop(e); return null;} + } +} diff --git a/400_xowa/src/gplx/core/net/Gfo_protocol_itm.java b/400_xowa/src/gplx/core/net/Gfo_protocol_itm.java index a27517de8..c23c0036d 100644 --- a/400_xowa/src/gplx/core/net/Gfo_protocol_itm.java +++ b/400_xowa/src/gplx/core/net/Gfo_protocol_itm.java @@ -13,3 +13,125 @@ 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.core.net; import gplx.*; import gplx.core.*; +public class Gfo_protocol_itm { + public Gfo_protocol_itm(byte tid, String text) { + this.tid = tid; + this.text_bry = Bry_.new_u8(text); + this.text_str = text; + int text_len = text_bry.length; + for (int i = 0; i < text_len; i++) { + if (text_bry[i] == Byte_ascii.Colon) { + this.key_wo_colon_bry = Bry_.Mid(text_bry, 0, i); + this.key_w_colon_bry_len = i; + this.key_wo_colon_str = String_.new_u8(key_wo_colon_bry); + this.key_w_colon_bry = Bry_.Mid(text_bry, 0, i + 1); + this.key_w_colon_str = String_.new_u8(key_w_colon_bry); + this.text_ends_w_colon = i == text_len - 1; + break; + } + } + } + public byte Tid() {return tid;} private byte tid; + public byte[] Key_wo_colon_bry() {return key_wo_colon_bry;} private byte[] key_wo_colon_bry; // http + public String Key_wo_colon_str() {return key_wo_colon_str;} private String key_wo_colon_str; + public byte[] Key_w_colon_bry() {return key_w_colon_bry;} private byte[] key_w_colon_bry; // http: + public String Key_w_colon_str() {return key_w_colon_str;} private String key_w_colon_str; + public int Key_w_colon_bry_len() {return key_w_colon_bry_len;} private int key_w_colon_bry_len; + public byte[] Text_bry() {return text_bry;} private byte[] text_bry; // http:// + public String Text_str() {return text_str;} private String text_str; + public boolean Text_ends_w_colon() {return text_ends_w_colon;} private boolean text_ends_w_colon; + public static final byte // REF.MW:DefaultSettings|$wgUrlProtocols; NOTE: "news:" not included because it breaks alias "wikinews:" + Tid_http = 0 + , Tid_https = 1 + , Tid_ftp = 2 + , Tid_ftps = 3 + , Tid_ssh = 4 + , Tid_sftp = 5 + , Tid_irc = 6 + , Tid_ircs = 7 + , Tid_xmpp = 8 + , Tid_sip = 9 + , Tid_sips = 10 + , Tid_gopher = 11 + , Tid_telnet = 12 + , Tid_nntp = 13 + , Tid_worldwind = 14 + , Tid_mailto = 15 + , Tid_tel = 16 + , Tid_sms = 17 + , Tid_svn = 18 + , Tid_git = 19 + , Tid_mms = 20 + , Tid_bitcoin = 21 + , Tid_magnet = 22 + , Tid_urn = 23 + , Tid_geo = 24 + , Tid_null = 25 + , Tid_unknown = 26 + , Tid_xowa = 27 + , Tid_file = 28 + , Tid_relative_1 = 29 // [//a.org] + , Tid_relative_2 = 30 // [[//a.org]] + ; + public static final Ordered_hash Regy = Ordered_hash_.New_bry(); + public static final Gfo_protocol_itm + Itm_http = new_(Tid_http , "http://") + , Itm_https = new_(Tid_https , "https://") + , Itm_ftp = new_(Tid_ftp , "ftp://") + , Itm_ftps = new_(Tid_ftps , "ftps://") + , Itm_ssh = new_(Tid_ssh , "ssh://") + , Itm_sftp = new_(Tid_sftp , "sftp://") + , Itm_irc = new_(Tid_irc , "irc://") + , Itm_ircs = new_(Tid_ircs , "ircs://") + , Itm_xmpp = new_(Tid_xmpp , "xmpp:") + , Itm_sip = new_(Tid_sip , "sip:") + , Itm_sips = new_(Tid_sips , "sips:") + , Itm_gopher = new_(Tid_gopher , "gopher://") + , Itm_telnet = new_(Tid_telnet , "telnet://") + , Itm_nntp = new_(Tid_nntp , "nntp://") + , Itm_worldwind = new_(Tid_worldwind , "worldwind://") + , Itm_mailto = new_(Tid_mailto , "mailto:") + , Itm_tel = new_(Tid_tel , "tel:") + , Itm_sms = new_(Tid_sms , "sms:") + , Itm_svn = new_(Tid_svn , "svn://") + , Itm_git = new_(Tid_git , "git://") + , Itm_mms = new_(Tid_mms , "mms://") + , Itm_bitcoin = new_(Tid_bitcoin , "bicoin:") + , Itm_magnet = new_(Tid_magnet , "magnet:") + , Itm_urn = new_(Tid_urn , "urn:") + , Itm_geo = new_(Tid_geo , "geo:") + ; + public static final String Str_file = "file:", Str_xcmd = "xowa-cmd:"; + public static final byte[] Bry_file = Bry_.new_a7(Str_file), Bry_xcmd = Bry_.new_a7(Str_xcmd); + public static final byte[] Bry_file_with_slashes = Bry_.new_a7("file:///"); + public static final int Len_xcmd = Bry_xcmd.length; + public static final byte[] Bry_relative = Bry_.new_a7("//"); + public static Gfo_protocol_itm Get_or(byte tid, Gfo_protocol_itm or) { + Gfo_protocol_itm[] ary = Ary(); + return tid >= ary.length ? or : ary[tid]; + } + public static Gfo_protocol_itm[] Ary() { + if (protocol_itm_ary == null) { + int len = Regy.Count(); + protocol_itm_ary = new Gfo_protocol_itm[len]; + for (int i = 0; i < len; i++) + protocol_itm_ary[i] = (Gfo_protocol_itm)Regy.Get_at(i); + } + return protocol_itm_ary; + } private static Gfo_protocol_itm[] protocol_itm_ary; + public static String[] Protocol_str_ary() { + if (protocol_str_ary == null) { + int len = Regy.Count(); + protocol_str_ary = new String[len]; + for (int i = 0; i < len; i++) + protocol_str_ary[i] = ((Gfo_protocol_itm)Regy.Get_at(i)).Text_str(); + } + return protocol_str_ary; + } private static String[] protocol_str_ary; + private static Gfo_protocol_itm new_(byte tid, String text) { + Gfo_protocol_itm rv = new Gfo_protocol_itm(tid, text); + Regy.Add(rv.Key_wo_colon_bry(), rv); + return rv; + } +} diff --git a/400_xowa/src/gplx/core/net/Gfo_url.java b/400_xowa/src/gplx/core/net/Gfo_url.java index a27517de8..fbcaf118b 100644 --- a/400_xowa/src/gplx/core/net/Gfo_url.java +++ b/400_xowa/src/gplx/core/net/Gfo_url.java @@ -13,3 +13,26 @@ 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.core.net; import gplx.*; import gplx.core.*; +import gplx.core.net.qargs.*; +public class Gfo_url { + private final int segs__len; + public Gfo_url(byte[] raw, byte protocol_tid, byte[] protocol_bry, byte[][] segs, Gfo_qarg_itm[] qargs, byte[] anch) { + this.raw = raw; + this.protocol_tid = protocol_tid; this.protocol_bry = protocol_bry; + this.segs = segs; this.segs__len = segs.length; + this.qargs = qargs; + this.anch = anch; + } + public byte[] Raw() {return raw;} private final byte[] raw; + public byte Protocol_tid() {return protocol_tid;} private final byte protocol_tid; + public byte[] Protocol_bry() {return protocol_bry;} private final byte[] protocol_bry; + public byte[] Anch() {return anch;} private final byte[] anch; + public Gfo_qarg_itm[] Qargs() {return qargs;} private final Gfo_qarg_itm[] qargs; + public byte[][] Segs() {return segs;} private final byte[][] segs; + public byte[] Segs__get_at(int i) {return i < segs__len ? segs[i] : null;} + public byte[] Segs__get_at_1st() {return segs__len > 0 ? segs[0] : null;} + public byte[] Segs__get_at_nth() {return segs__len > 1 ? segs[segs__len - 1] : null;} + + public static final Gfo_url Empty = new Gfo_url(Bry_.Empty, Gfo_protocol_itm.Tid_unknown, Bry_.Empty, Bry_.Ary_empty, null, null); +} diff --git a/400_xowa/src/gplx/core/net/Gfo_url_parser.java b/400_xowa/src/gplx/core/net/Gfo_url_parser.java index a27517de8..39cc0b1a0 100644 --- a/400_xowa/src/gplx/core/net/Gfo_url_parser.java +++ b/400_xowa/src/gplx/core/net/Gfo_url_parser.java @@ -13,3 +13,172 @@ 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.core.net; import gplx.*; import gplx.core.*; +import gplx.core.btries.*; +import gplx.core.net.qargs.*; +import gplx.langs.htmls.encoders.*; +public class Gfo_url_parser { + private final Btrie_slim_mgr protocols = Btrie_slim_mgr.ci_a7(); // ASCII:url_protocol; EX:"http:", "ftp:", etc + private final Btrie_rv trv = new Btrie_rv(); + private final List_adp segs_list = List_adp_.New(), qargs_list = List_adp_.New(); + private final Bry_bfr tmp_bfr = Bry_bfr_.Reset(500); + public Gfo_url_parser() { + Init_protocols(Gfo_protocol_itm.Ary()); + Init_protocol_itm(Gfo_protocol_itm.Bry_relative, Gfo_protocol_itm.Tid_relative_1); + Init_protocol_itm(Gfo_protocol_itm.Bry_file, Gfo_protocol_itm.Tid_file); + Init_protocol_itm(gplx.xowa.parsers.lnkes.Xop_lnke_wkr.Bry_xowa_protocol, Gfo_protocol_itm.Tid_xowa); + } + public byte[] Relative_url_protocol_bry() {return Gfo_protocol_itm.Itm_https.Key_w_colon_bry();} // NOTE: https b/c any WMF wiki will now default to WMF; DATE:2015-07-26 + private void Init_protocols(Gfo_protocol_itm... itms) { + int len = itms.length; + for (int i = 0; i < len; i++) { + Gfo_protocol_itm itm = itms[i]; + Init_protocol_itm(itm.Key_w_colon_bry(), itm.Tid()); + } + } + public void Init_protocol_itm(byte[] key, byte protocol_tid) {protocols.Add_bry_byte(key, protocol_tid);} + public void Parse_site_fast(Gfo_url_site_data site_data, byte[] src, int src_bgn, int src_end) { + int pos = src_bgn; boolean rel = false; + if (pos + 1 < src_end && src[pos] == Byte_ascii.Slash && src[pos + 1] == Byte_ascii.Slash) { // starts with "//" + pos += 2; + rel = true; + } + if (!rel) { // search for ":"; NOTE: only search if not rel; i.e.: "//" + int colon_pos = Bry_find_.Find_fwd(src, Byte_ascii.Colon, pos, src_end); // no colon found; EX: "//a.org/b"; "a.org/b" + if (colon_pos != Bry_find_.Not_found) // colon found; EX: "http://" or "https://" + pos = colon_pos + Byte_ascii.Len_1; + if (pos < src_end && src[pos] == Byte_ascii.Slash) { // skip slash after colon + pos += 1; + if (pos < src_end && src[pos] == Byte_ascii.Slash) // skip 2nd slash after colon + pos += 1; + } + } + int slash_pos = Bry_find_.Find_fwd(src, Byte_ascii.Slash, pos, src_end); + if (slash_pos == Bry_find_.Not_found) // no terminating slash; EX: http://a.org + slash_pos = src_end; + slash_pos = Bry_.Trim_end_pos(src, slash_pos); + site_data.Atrs_set(rel, pos, slash_pos); + } + public Gfo_url Parse(byte[] src) {return Parse(src, 0, src.length);} + public Gfo_url Parse(byte[] src, int src_bgn, int src_end) { + // protocol + byte protocol_tid = protocols.Match_byte_or(trv, src, src_bgn, src_end, Gfo_protocol_itm.Tid_unknown); + int pos = Bry_find_.Find_fwd_while(src, trv.Pos(), src_end, Byte_ascii.Slash); // set pos after last slash; EX: "https://A" -> position before "A" + byte[] protocol_bry = protocol_tid == Gfo_protocol_itm.Tid_unknown + ? null + : Make_bry(false, src, src_bgn, pos); + + // loop chars and handle "/", "#", "?", and "%" + boolean encoded = false; + int src_zth = src_end - 1; + int anch_bgn = -1, qarg_bgn = -1, seg_bgn = pos; + for (int i = pos; i < src_end; ++i) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Slash: + if (qarg_bgn == -1) { // ignore slash in qargs + segs_list.Add(Make_bry(encoded, src, seg_bgn, i)); + encoded = false; + seg_bgn = i + 1; // +1 to skip "/" + } + break; + case Byte_ascii.Hash: // set qarg to first #; also, ignore rest of String; EX: A#B#C -> B#C + if (i == src_zth) continue; // ignore # at EOS; EX: "A#" + anch_bgn = i; + i = src_end; + break; + case Byte_ascii.Question: // set qarg to last "?"; EX: A?B?C -> C + if (i == src_zth) continue; // ignore ? at EOS; EX: "A?" + qarg_bgn = i; + break; + case Byte_ascii.Percent: + encoded = true; + break; + } + } + + int seg_end = src_end; // set seg_end to src_end; EX: "https://site/A" -> "A"; seg_end may be overriden if "#" or "?" exists + + // set anch + byte[] anch = null; + if (anch_bgn != -1) { + seg_end = anch_bgn; // set seg_end to anch_bgn; EX: "https://site/A#B" -> "A" x> "A#B" + anch = Make_bry(encoded, src, anch_bgn + 1, src_end); // +1 to skip "#" + } + + // set qargs + Gfo_qarg_itm[] qarg_ary = Gfo_qarg_itm.Ary_empty; + if (qarg_bgn != -1) { + int qarg_end = anch_bgn == -1 + ? src_end // # missing; set to src_end; EX: "A?B=C" -> EOS + : anch_bgn; // # exists; set to anch_bgn; EX: "A?B=C#D" -> # + qarg_ary = Make_qarg_ary(src, qarg_bgn, qarg_end); + seg_end = qarg_ary.length == 0 + ? src_end // set seg_end to src_end if pseudo qarg; EX: "https://site/A?B" -> "A?B" x> "A" + : qarg_bgn; // set seg_end to qarg_bgn; EX: "https://site/A?B=C" -> "A" x> "A#B"; NOTE: overrides anch; "A?B=C#D" -> "A" + } + + // extract seg_end; note that there will always be a seg_end; if src ends with slash, then it will be ""; EX: "A/" -> "A", "" + segs_list.Add(Make_bry(encoded, src, seg_bgn, seg_end)); + + // build url and return it + return new Gfo_url(src, protocol_tid, protocol_bry, (byte[][])segs_list.To_ary_and_clear(byte[].class), qarg_ary, anch); + } + private Gfo_qarg_itm[] Make_qarg_ary(byte[] src, int qarg_bgn, int qarg_end) { + // init + int key_bgn = qarg_bgn + 1; // +1 to skip "?" + byte[] key_bry = null; + int val_bgn = -1; + boolean encoded = false; + + // loop qarg for "&", "=", "%" + int qarg_pos = qarg_bgn; + while (true) { + boolean b_is_last = qarg_pos == qarg_end; + byte b = b_is_last ? Byte_ascii.Null : src[qarg_pos]; + boolean make_qarg = false; + switch (b) { + case Byte_ascii.Amp: // "&" always makes qarg + make_qarg = true; + break; + case Byte_ascii.Null: // "EOS" makes qarg as long as "=" seen or at least one qarg; specifically, "A?B" shouldn't make qarg + if ( val_bgn != -1 // "=" seen; EX: "?A=B" + || qargs_list.Count() > 0) // at least one qarg exists; EX: "?A=B&C" + make_qarg = true; + break; + case Byte_ascii.Eq: + key_bry = Make_bry(encoded, src, key_bgn, qarg_pos); + encoded = false; + val_bgn = qarg_pos + 1; + break; + case Byte_ascii.Percent: + encoded = true; + break; + } + + // make qarg + if (make_qarg) { + byte[] val_bry = null; + if (key_bry == null) // key missing; EX: "&A" -> "A,null" + key_bry = Make_bry(encoded, src, key_bgn, qarg_pos); + else // key exists; EX: "&A=B" -> "A,B" + val_bry = Make_bry(encoded, src, val_bgn, qarg_pos); + encoded = false; + qargs_list.Add(new Gfo_qarg_itm(key_bry, val_bry)); + + // reset vars + key_bry = null; + key_bgn = qarg_pos + 1; + val_bgn = -1; + } + if (b_is_last) break; + ++qarg_pos; + } + return (Gfo_qarg_itm[])qargs_list.To_ary_and_clear(Gfo_qarg_itm.class); + } + private byte[] Make_bry(boolean encoded, byte[] src, int bgn, int end) { + return encoded ? Gfo_url_encoder_.Xourl.Decode(tmp_bfr, Bool_.N, src, bgn, end).To_bry_and_clear() : Bry_.Mid(src, bgn, end); + } + + public static final byte[] Bry_double_slash = new byte[] {Byte_ascii.Slash, Byte_ascii.Slash}; +} diff --git a/400_xowa/src/gplx/core/net/Gfo_url_parser_fxt.java b/400_xowa/src/gplx/core/net/Gfo_url_parser_fxt.java index a27517de8..f96bea174 100644 --- a/400_xowa/src/gplx/core/net/Gfo_url_parser_fxt.java +++ b/400_xowa/src/gplx/core/net/Gfo_url_parser_fxt.java @@ -13,3 +13,42 @@ 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.core.net; import gplx.*; import gplx.core.*; +import gplx.core.net.qargs.*; +class Gfo_url_parser_fxt { + private final Gfo_url_parser parser = new Gfo_url_parser(); + private Gfo_url actl; + public Gfo_url_parser_fxt Test__protocol_tid(byte v) {Tfds.Eq_byte(v, actl.Protocol_tid(), "protocol_tid"); return this;} + public Gfo_url_parser_fxt Test__protocol_bry(String v) {Tfds.Eq_str(v, actl.Protocol_bry(), "protocol_bry"); return this;} + public Gfo_url_parser_fxt Test__site(String v) {Tfds.Eq_str(v, actl.Segs__get_at_1st(), "site"); return this;} + public Gfo_url_parser_fxt Test__page(String v) {Tfds.Eq_str(v, actl.Segs__get_at_nth(), "page"); return this;} + public Gfo_url_parser_fxt Test__anch(String v) {Tfds.Eq_str(v, actl.Anch(), "anch"); return this;} + public Gfo_url_parser_fxt Test__segs(String... ary) { + Tfds.Eq_str_lines(String_.Concat_lines_nl(ary), String_.Concat_lines_nl(String_.Ary(actl.Segs())), "segs"); + Tfds.Eq_int(ary.length, actl.Segs().length, "segs_len"); + return this; + } + public Gfo_url_parser_fxt Test__qargs(String... ary) {Tfds.Eq_str_lines(String_.To_str__as_kv_ary(ary), Qargs__To_str(actl.Qargs()), "qargs"); return this;} + public Gfo_url_parser_fxt Exec__parse(String v) { + this.actl = parser.Parse(Bry_.new_u8(v), 0, String_.Len(v)); + return this; + } + public void Test_Parse_site_fast(String raw, String expd) { + byte[] raw_bry = Bry_.new_u8(raw); + parser.Parse_site_fast(site_data, raw_bry, 0, raw_bry.length); + String actl = String_.new_u8(raw_bry, site_data.Site_bgn(), site_data.Site_end()); + Tfds.Eq(expd, actl); + } private final Gfo_url_site_data site_data = new Gfo_url_site_data(); + private static String Qargs__To_str(Gfo_qarg_itm[] ary) { + int len = ary.length; + Bry_bfr bfr = Bry_bfr_.New(); + for (int i = 0; i < len; ++i) { + Gfo_qarg_itm itm = ary[i]; + bfr.Add(itm.Key_bry()).Add_byte_eq(); + if (itm.Val_bry() != null) + bfr.Add(itm.Val_bry()); + bfr.Add_byte_nl(); + } + return bfr.To_str_and_clear(); + } +} diff --git a/400_xowa/src/gplx/core/net/Gfo_url_parser_tst.java b/400_xowa/src/gplx/core/net/Gfo_url_parser_tst.java index a27517de8..7832257b3 100644 --- a/400_xowa/src/gplx/core/net/Gfo_url_parser_tst.java +++ b/400_xowa/src/gplx/core/net/Gfo_url_parser_tst.java @@ -13,3 +13,114 @@ 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.core.net; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Gfo_url_parser_tst { + private final Gfo_url_parser_fxt tstr = new Gfo_url_parser_fxt(); + @Test public void Protocol__relative() { + tstr.Exec__parse("//en.wikipedia.org").Test__protocol_tid(Gfo_protocol_itm.Tid_relative_1).Test__protocol_bry("//").Test__site("en.wikipedia.org"); + } + @Test public void Protocol__none() { + tstr.Exec__parse("en.wikipedia.org/wiki/A").Test__protocol_tid(Gfo_protocol_itm.Tid_unknown).Test__segs("en.wikipedia.org", "wiki", "A"); + } + @Test public void Site__parts__3() { + tstr.Exec__parse("https://en.wikipedia.org").Test__protocol_tid(Gfo_protocol_itm.Tid_https).Test__protocol_bry("https://").Test__segs("en.wikipedia.org"); + } + @Test public void Site__parts__2() { + tstr.Exec__parse("https://wikipedia.org").Test__protocol_tid(Gfo_protocol_itm.Tid_https).Test__segs("wikipedia.org"); + } + @Test public void Site__parts__1() { + tstr.Exec__parse("https://wikipedia").Test__protocol_tid(Gfo_protocol_itm.Tid_https).Test__segs("wikipedia"); + } + @Test public void Site__slash__none() { + tstr.Exec__parse("https:site").Test__protocol_tid(Gfo_protocol_itm.Tid_https).Test__site("site"); + } + @Test public void Site__slash__eos() { + tstr.Exec__parse("https://en.wikipedia.org/").Test__protocol_tid(Gfo_protocol_itm.Tid_https).Test__site("en.wikipedia.org"); + } + @Test public void Paths__1() { + tstr.Exec__parse("https://site/A").Test__segs("site", "A"); + } + @Test public void Paths__2() { + tstr.Exec__parse("https://site/wiki/A").Test__segs("site", "wiki", "A"); + } + @Test public void Paths__n() { + tstr.Exec__parse("https://site/wiki/A/B/C/D").Test__segs("site", "wiki", "A", "B", "C", "D"); + } + @Test public void Qargs__1() { + tstr.Exec__parse("https://site/A?B=C").Test__page("A").Test__qargs("B", "C"); + } + @Test public void Qargs__2() { + tstr.Exec__parse("https://site/A?B=C&D=E").Test__page("A").Test__qargs("B", "C", "D", "E"); + } + @Test public void Qargs__3() { + tstr.Exec__parse("https://site/A?B=C&D=E&F=G").Test__page("A").Test__qargs("B", "C", "D", "E", "F", "G"); + } + @Test public void Qargs__ques__dupe__ques() { + tstr.Exec__parse("https://site/A?B?Y=Z").Test__page("A?B").Test__qargs("Y", "Z"); + } + @Test public void Qargs__ques__dupe__amp() { + tstr.Exec__parse("https://site/A?B=C&D?Y=Z").Test__page("A?B=C&D").Test__qargs("Y", "Z"); + } + @Test public void Qargs__ques__dupe__eq() { + tstr.Exec__parse("https://site/A?B=C?Y=Z").Test__page("A?B=C").Test__qargs("Y", "Z"); + } + @Test public void Qargs__amp__dupe__ques() { + tstr.Exec__parse("https://site/A?B&Y=Z").Test__page("A").Test__qargs("B", null, "Y", "Z"); + } + @Test public void Qargs__amp__dupe__amp() { + tstr.Exec__parse("https://site/A?B=C&D&Y=Z").Test__page("A").Test__qargs("B", "C", "D", null, "Y", "Z"); + } + @Test public void Qargs__missing_val__0() { + tstr.Exec__parse("https://site/A?").Test__page("A?").Test__qargs(); + } + @Test public void Qargs__missing_val__2() { + tstr.Exec__parse("https://site/A?B=C&D&F=G").Test__page("A").Test__qargs("B", "C", "D", null, "F", "G"); + } + @Test public void Qargs__missing_val__n() { + tstr.Exec__parse("https://site/A?B=C&D=E&F").Test__page("A").Test__qargs("B", "C", "D", "E", "F", null); + } + @Test public void Qargs__site_less__missing__0() { + tstr.Exec__parse("A?B").Test__segs("A?B").Test__qargs(); + } + @Test public void Qargs__site_less() { + tstr.Exec__parse("A?B=C&D=E").Test__site("A").Test__qargs("B", "C", "D", "E"); + } + @Test public void Anch__basic() { + tstr.Exec__parse("https://site/A#B").Test__page("A").Test__anch("B"); + } + @Test public void Anch__repeat__2() { + tstr.Exec__parse("https://site/A#B#C").Test__page("A").Test__anch("B#C"); + } + @Test public void Anch__repeat__3() { + tstr.Exec__parse("https://site/A#B#C#D").Test__page("A").Test__anch("B#C#D"); + } + @Test public void Anch__missing() { + tstr.Exec__parse("https://site/A#").Test__page("A#").Test__anch(null); + } + @Test public void Anch__missing__eos() { + tstr.Exec__parse("https://site/A#B#").Test__page("A").Test__anch("B#"); + } + @Test public void Anch__qargs__basic() { + tstr.Exec__parse("https://site/A?B=C&D=E#F").Test__page("A").Test__qargs("B", "C", "D", "E").Test__anch("F"); + } + @Test public void Anch__site_less() { + tstr.Exec__parse("A#B").Test__site("A").Test__anch("B"); + } + @Test public void Encode__page() { + tstr.Exec__parse("http://site/A%27s").Test__site("site").Test__page("A's"); + } + @Test public void Protocol_less__qargs() { + tstr.Exec__parse("Special:Search/Earth?fulltext=yes").Test__segs("Special:Search", "Earth").Test__page("Earth").Test__qargs("fulltext", "yes"); + } + @Test public void Parse_site_fast() { + tstr.Test_Parse_site_fast("http://a.org/B" , "a.org"); + tstr.Test_Parse_site_fast("http://a.org" , "a.org"); + tstr.Test_Parse_site_fast("//a.org/B" , "a.org"); + tstr.Test_Parse_site_fast("//a.org/B:C" , "a.org"); + } + // DELETED: logic isn't right; anch is first # not last; EX: https://en.wikipedia.org/w/index.php?title=Category:2001_albums&pagefrom=Beautiful+#View#mw-pages; DATE:2016-10-10 + // @Test public void Anch__qargs__repeat() { + // tstr.Exec__parse("https://site/A?B=C#&D=E#F").Test__page("A").Test__qargs("B", "C#", "D", "E").Test__anch("F"); + // } +} diff --git a/400_xowa/src/gplx/core/net/Gfo_url_site_data.java b/400_xowa/src/gplx/core/net/Gfo_url_site_data.java index a27517de8..5040cdef5 100644 --- a/400_xowa/src/gplx/core/net/Gfo_url_site_data.java +++ b/400_xowa/src/gplx/core/net/Gfo_url_site_data.java @@ -13,3 +13,10 @@ 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.core.net; import gplx.*; import gplx.core.*; +public class Gfo_url_site_data { + public boolean Rel() {return rel;} private boolean rel; + public int Site_bgn() {return site_bgn;} private int site_bgn; + public int Site_end() {return site_end;} private int site_end; + public void Atrs_set(boolean rel, int bgn, int end) {this.rel = rel; this.site_bgn = bgn; this.site_end = end;} +} diff --git a/400_xowa/src/gplx/core/net/Http_client_rdr.java b/400_xowa/src/gplx/core/net/Http_client_rdr.java index a27517de8..21dcbe2d9 100644 --- a/400_xowa/src/gplx/core/net/Http_client_rdr.java +++ b/400_xowa/src/gplx/core/net/Http_client_rdr.java @@ -13,3 +13,11 @@ 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.core.net; import gplx.*; import gplx.core.*; +public interface Http_client_rdr { + void Stream_(Object o); + String Read_line(); + byte[] Read_line_as_bry(); + int Read_char_ary(char[] ary, int bgn, int len); + void Rls(); +} diff --git a/400_xowa/src/gplx/core/net/Http_client_rdr_.java b/400_xowa/src/gplx/core/net/Http_client_rdr_.java index a27517de8..2d592b2ca 100644 --- a/400_xowa/src/gplx/core/net/Http_client_rdr_.java +++ b/400_xowa/src/gplx/core/net/Http_client_rdr_.java @@ -13,3 +13,22 @@ 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.core.net; import gplx.*; import gplx.core.*; +public class Http_client_rdr_ { + public static Http_client_rdr new_stream() {return new Http_client_rdr__stream();} + public static Http_client_rdr new_mem() {return new Http_client_rdr__mem();} +} +class Http_client_rdr__mem implements Http_client_rdr { + private String[] ary; private int ary_len; private int idx; + public void Stream_(Object o) { + this.ary = (String[])o; + this.ary_len = ary.length; + this.idx = 0; + } + public String Read_line() { + return idx == ary_len ? null : ary[idx++]; + } + public byte[] Read_line_as_bry() {return Bry_.new_u8(Read_line());} + public int Read_char_ary(char[] ary, int bgn, int len) {throw Err_.new_unimplemented();} + public void Rls() {} +} diff --git a/400_xowa/src/gplx/core/net/Http_client_rdr__stream.java b/400_xowa/src/gplx/core/net/Http_client_rdr__stream.java index a27517de8..ddc308945 100644 --- a/400_xowa/src/gplx/core/net/Http_client_rdr__stream.java +++ b/400_xowa/src/gplx/core/net/Http_client_rdr__stream.java @@ -13,3 +13,24 @@ 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.core.net; import gplx.*; import gplx.core.*; +import java.io.*; +class Http_client_rdr__stream implements Http_client_rdr { + private BufferedReader br; + public void Stream_(Object o) { + this.br = new BufferedReader(new InputStreamReader((InputStream)o, java.nio.charset.Charset.forName("UTF-8"))); + } + public String Read_line() { + try {return br.readLine();} + catch (IOException e) {throw Err_.new_exc(e, "net", "Read_line failed");} + } + public int Read_char_ary(char[] ary, int bgn, int len) { + try {return br.read(ary, bgn, len);} + catch (IOException e) {throw Err_.new_exc(e, "net", "Read_line failed");} + } + public byte[] Read_line_as_bry() {return Bry_.new_u8(Read_line());} + public void Rls() { + try {br.close();} + catch (IOException e) {throw Err_.new_exc(e, "net", "Rls failed");} + } +} diff --git a/400_xowa/src/gplx/core/net/Http_client_wtr.java b/400_xowa/src/gplx/core/net/Http_client_wtr.java index a27517de8..cf806fa2f 100644 --- a/400_xowa/src/gplx/core/net/Http_client_wtr.java +++ b/400_xowa/src/gplx/core/net/Http_client_wtr.java @@ -13,3 +13,13 @@ 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.core.net; import gplx.*; import gplx.core.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; +public interface Http_client_wtr { + void Stream_(Object o); + void Write_bry(byte[] bry); + void Write_str(String s); + void Write_mid(byte[] bry, int bgn, int end); + void Write_stream(Io_stream_rdr stream_rdr); + void Rls(); +} diff --git a/400_xowa/src/gplx/core/net/Http_client_wtr_.java b/400_xowa/src/gplx/core/net/Http_client_wtr_.java index a27517de8..efa582337 100644 --- a/400_xowa/src/gplx/core/net/Http_client_wtr_.java +++ b/400_xowa/src/gplx/core/net/Http_client_wtr_.java @@ -13,3 +13,7 @@ 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.core.net; import gplx.*; import gplx.core.*; +public class Http_client_wtr_ { + public static Http_client_wtr new_stream() {return new Http_client_wtr__stream();} +} diff --git a/400_xowa/src/gplx/core/net/Http_client_wtr__stream.java b/400_xowa/src/gplx/core/net/Http_client_wtr__stream.java index a27517de8..b6486652b 100644 --- a/400_xowa/src/gplx/core/net/Http_client_wtr__stream.java +++ b/400_xowa/src/gplx/core/net/Http_client_wtr__stream.java @@ -13,3 +13,39 @@ 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.core.net; import gplx.*; import gplx.core.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; +import java.io.*; +class Http_client_wtr__stream implements Http_client_wtr { + private final byte[] tmp_stream_bry = new byte[1024]; + private DataOutputStream stream; + public void Stream_(Object o) { + this.stream = new DataOutputStream((OutputStream)o); + } + public void Write_bry(byte[] bry) { + try {stream.write(bry);} + catch (IOException e) {throw Err_.new_exc(e, "net", "Write_bry failed");} + } + public void Write_str(String s) { + try {stream.writeBytes(s);} + catch (Exception e) {throw Err_.new_exc(e, "net", "Write_str failed");} + } + public void Write_mid(byte[] bry, int bgn, int end) { + try {stream.write(bry, bgn, end - bgn);} + catch (IOException e) {throw Err_.new_exc(e, "net", "Write_mid failed");} + } + public void Write_stream(Io_stream_rdr stream_rdr) { + synchronized (tmp_stream_bry) { + int read = 0; + while (true) { + read = stream_rdr.Read(tmp_stream_bry, 0, 1024); + if (read == -1) break; + Write_mid(tmp_stream_bry, 0, read); + } + } + } + public void Rls() { + try {stream.close();} + catch (IOException e) {throw Err_.new_exc(e, "net", "Rls failed");} + } +} diff --git a/400_xowa/src/gplx/core/net/Http_post_data_hash.java b/400_xowa/src/gplx/core/net/Http_post_data_hash.java index a27517de8..372dc450d 100644 --- a/400_xowa/src/gplx/core/net/Http_post_data_hash.java +++ b/400_xowa/src/gplx/core/net/Http_post_data_hash.java @@ -13,3 +13,13 @@ 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.core.net; import gplx.*; import gplx.core.*; +public class Http_post_data_hash { + private final Ordered_hash hash = Ordered_hash_.New_bry(); + public int Len() {return hash.Count();} + public Http_post_data_itm Get_at(int i) {return (Http_post_data_itm)hash.Get_at(i);} + public Http_post_data_itm Get_by(byte[] k) {return (Http_post_data_itm)hash.Get_by(k);} + public void Add(byte[] key, byte[] val) { + hash.Add(key, new Http_post_data_itm(key, val)); + } +} diff --git a/400_xowa/src/gplx/core/net/Http_post_data_itm.java b/400_xowa/src/gplx/core/net/Http_post_data_itm.java index a27517de8..43f612982 100644 --- a/400_xowa/src/gplx/core/net/Http_post_data_itm.java +++ b/400_xowa/src/gplx/core/net/Http_post_data_itm.java @@ -13,3 +13,9 @@ 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.core.net; import gplx.*; import gplx.core.*; +public class Http_post_data_itm { + public Http_post_data_itm(byte[] key, byte[] val) {this.key = key; this.val = val;} + 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/core/net/Http_request_itm.java b/400_xowa/src/gplx/core/net/Http_request_itm.java index a27517de8..bb784f686 100644 --- a/400_xowa/src/gplx/core/net/Http_request_itm.java +++ b/400_xowa/src/gplx/core/net/Http_request_itm.java @@ -13,3 +13,68 @@ 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.core.net; import gplx.*; import gplx.core.*; +public class Http_request_itm { + public Http_request_itm(int type, byte[] url, byte[] protocol, byte[] host, byte[] user_agent + , byte[] accept, byte[] accept_language, byte[] accept_encoding, boolean dnt, byte[] x_requested_with, byte[] cookie, byte[] referer + , int content_length, byte[] content_type, byte[] content_type_boundary + , byte[] connection, byte[] pragma, byte[] cache_control, byte[] origin + , Http_post_data_hash post_data_hash + ) { + this.type = type; this.url = url; this.protocol = protocol; this.host = host; this.user_agent = user_agent; + this.accept = accept; this.accept_language = accept_language; this.accept_encoding = accept_encoding; this.dnt = dnt; this.x_requested_with = x_requested_with; this.cookie = cookie; this.referer = referer; + this.content_length = content_length; this.content_type = content_type; this.content_type_boundary = content_type_boundary; + this.connection = connection; this.pragma = pragma; this.cache_control = cache_control; this.origin = origin; + this.post_data_hash = post_data_hash; + } + public int Type() {return type;} private final int type; + public byte[] Url() {return url;} private final byte[] url; + public byte[] Protocol() {return protocol;} private final byte[] protocol; + public byte[] Host() {return host;} private final byte[] host; + public byte[] User_agent() {return user_agent;} private final byte[] user_agent; + public byte[] Accept() {return accept;} private final byte[] accept; + public byte[] Accept_language() {return accept_language;} private final byte[] accept_language; + public byte[] Accept_encoding() {return accept_encoding;} private final byte[] accept_encoding; + public boolean Dnt() {return dnt;} private final boolean dnt; + public byte[] X_requested_with() {return x_requested_with;} private byte[] x_requested_with; + public byte[] Cookie() {return cookie;} private final byte[] cookie; + public byte[] Referer() {return referer;} private final byte[] referer; + public int Content_length() {return content_length;} private final int content_length; + public byte[] Content_type() {return content_type;} private final byte[] content_type; + public byte[] Content_type_boundary() {return content_type_boundary;} private final byte[] content_type_boundary; + public byte[] Connection() {return connection;} private final byte[] connection; + public byte[] Pragma() {return pragma;} private final byte[] pragma; + public byte[] Cache_control() {return cache_control;} private final byte[] cache_control; + public byte[] Origin() {return origin;} private final byte[] origin; + public Http_post_data_hash Post_data_hash() {return post_data_hash;} private final Http_post_data_hash post_data_hash; + public String To_str(Bry_bfr bfr, boolean line) { + bfr .Add_kv_dlm(line, "type" , type == Type_get ? "GET" : "POST") + .Add_kv_dlm(line, "url" , url) + .Add_kv_dlm(line, "protocol" , protocol) + .Add_kv_dlm(line, "host" , host) + .Add_kv_dlm(line, "user_agent" , user_agent) + .Add_kv_dlm(line, "accept" , accept) + .Add_kv_dlm(line, "accept_encoding" , accept_encoding) + .Add_kv_dlm(line, "dnt" , dnt) + .Add_kv_dlm(line, "x_requested_with" , x_requested_with) + .Add_kv_dlm(line, "cookie" , cookie) + .Add_kv_dlm(line, "referer" , referer) + .Add_kv_dlm(line, "content_length" , content_length) + .Add_kv_dlm(line, "content_type" , content_type) + .Add_kv_dlm(line, "content_type_boundary" , content_type_boundary) + .Add_kv_dlm(line, "connection" , connection) + .Add_kv_dlm(line, "pragma" , pragma) + .Add_kv_dlm(line, "cache_control" , cache_control) + ; + if (post_data_hash != null) { + int len = post_data_hash.Len(); + for (int i = 0; i < len; ++i) { + Http_post_data_itm itm = post_data_hash.Get_at(i); + bfr.Add_byte_repeat(Byte_ascii.Space, 2); + bfr.Add_kv_dlm(line, String_.new_u8(itm.Key()), itm.Val()); + } + } + return bfr.To_str_and_clear(); + } + public static final int Type_get = 1, Type_post = 2; +} diff --git a/400_xowa/src/gplx/core/net/Http_request_parser.java b/400_xowa/src/gplx/core/net/Http_request_parser.java index a27517de8..8896f32f7 100644 --- a/400_xowa/src/gplx/core/net/Http_request_parser.java +++ b/400_xowa/src/gplx/core/net/Http_request_parser.java @@ -13,3 +13,162 @@ 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.core.net; import gplx.*; import gplx.core.*; +import gplx.core.primitives.*; import gplx.core.btries.*; +public class Http_request_parser { + private boolean dnt; + private int type, content_length; + private byte[] url, protocol, host, user_agent, accept, accept_language, accept_encoding, x_requested_with, cookie, referer, content_type, content_type_boundary, connection, pragma, cache_control, origin; + private Http_post_data_hash post_data_hash; + private final Bry_bfr tmp_bfr = Bry_bfr_.New_w_size(255); private final Btrie_rv trv = new Btrie_rv(); + private final Http_server_wtr server_wtr; private final boolean log; + public Http_request_parser(Http_server_wtr server_wtr, boolean log) {this.server_wtr = server_wtr; this.log = log;} + public void Clear() { + this.dnt = false; + this.type = this.content_length = 0; + this.url = this.protocol = this.host = this.user_agent = this.accept = this.accept_language = this.accept_encoding = this.x_requested_with = this.cookie + = this.referer = this.content_type = this.content_type_boundary = this.connection = this.pragma = this.cache_control = this.origin = null; + this.post_data_hash = null; + } + public Http_request_itm Parse(Http_client_rdr rdr) { + synchronized (tmp_bfr) { + this.Clear(); + boolean reading_post_data = false; boolean post_nl_seen = false; + while (true) { + String line_str = rdr.Read_line(); if (line_str == null) break; // needed for TEST + if (log) server_wtr.Write_str_w_nl(line_str); + byte[] line = Bry_.new_u8(line_str); + int line_len = line.length; + if (line_len == 0) { + switch (type) { + case Http_request_itm.Type_get: break; + case Http_request_itm.Type_post: + if (reading_post_data || post_nl_seen) throw Err_.new_wo_type("http.request.parser;invalid new line during post", "request", To_str()); + post_nl_seen = true; // only allow one \n per POST + continue; // ignore line and get next + default: throw Err_.new_unimplemented(); + } + break; // only GET will reach this line; GET requests always end with blank line; stop; + } + if (content_type_boundary != null && Bry_.Has_at_bgn(line, content_type_boundary)) { + while (true) { + if (Bry_.Has_at_end(line, Tkn_content_type_boundary_end)) break; // last form_data pair will end with "--"; stop + line = Parse_content_type_boundary(rdr); + } + break; // assume form_data sends POST request + } + Object o = trie.Match_at(trv, line, 0, line_len); + if (o == null) { + server_wtr.Write_str_w_nl(String_.Format("http.request.parser; unknown line; line={0} request={1}", line_str, To_str())); + continue; + } + int val_bgn = Bry_find_.Find_fwd_while_ws(line, trv.Pos(), line_len); // skip ws after key; EX: "Host: " + int tid = ((Int_obj_val)o).Val(); + switch (tid) { + case Tid_get: + case Tid_post: Parse_type(tid, val_bgn, line, line_len); break; + case Tid_host: this.host = Bry_.Mid(line, val_bgn, line_len); break; + case Tid_user_agent: this.user_agent = Bry_.Mid(line, val_bgn, line_len); break; + case Tid_accept: this.accept = Bry_.Mid(line, val_bgn, line_len); break; + case Tid_accept_language: this.accept_language = Bry_.Mid(line, val_bgn, line_len); break; + case Tid_accept_encoding: this.accept_encoding = Bry_.Mid(line, val_bgn, line_len); break; + case Tid_dnt: this.dnt = line[val_bgn] == Byte_ascii.Num_1; break; + case Tid_x_requested_with: this.x_requested_with = Bry_.Mid(line, val_bgn, line_len); break; + case Tid_cookie: this.cookie = Bry_.Mid(line, val_bgn, line_len); break; + case Tid_referer: this.referer = Bry_.Mid(line, val_bgn, line_len); break; + case Tid_content_length: this.content_length = Bry_.To_int_or(line, val_bgn, line_len, -1); break; + case Tid_content_type: Parse_content_type(val_bgn, line, line_len); break; + case Tid_connection: this.connection = Bry_.Mid(line, val_bgn, line_len); break; + case Tid_pragma: this.pragma = Bry_.Mid(line, val_bgn, line_len); break; + case Tid_cache_control: this.cache_control = Bry_.Mid(line, val_bgn, line_len); break; + case Tid_origin: this.origin = Bry_.Mid(line, val_bgn, line_len); break; + case Tid_accept_charset: break; + default: throw Err_.new_unhandled(tid); + } + } + return Make_request_itm(); + } + } + private void Parse_type(int tid, int val_bgn, byte[] line, int line_len) { // EX: "POST /xowa-cmd:exec_as_json HTTP/1.1" + int url_end = Bry_find_.Find_bwd(line, Byte_ascii.Space, line_len); if (url_end == Bry_find_.Not_found) throw Err_.new_wo_type("invalid protocol", "line", line, "request", To_str()); + switch (tid) { + case Tid_get : this.type = Http_request_itm.Type_get; break; + case Tid_post : this.type = Http_request_itm.Type_post; break; + default : throw Err_.new_unimplemented(); + } + this.url = Bry_.Mid(line, val_bgn, url_end); + this.protocol = Bry_.Mid(line, url_end + 1, line_len); + } + private void Parse_content_type(int val_bgn, byte[] line, int line_len) { // EX: Content-Type: multipart/form-data; boundary=---------------------------72432484930026 + // handle wolfram and other clients; DATE:2015-08-03 + int boundary_bgn = Bry_find_.Find_fwd(line, Tkn_boundary, val_bgn, line_len); if (boundary_bgn == Bry_find_.Not_found) return; // PURPOSE: ignore content-type for GET calls like by Mathematica server; DATE:2015-08-04 // throw Err_.new_wo_type("invalid content_type", "line", line, "request", To_str()); + int content_type_end = Bry_find_.Find_bwd(line, Byte_ascii.Semic, boundary_bgn); + this.content_type = Bry_.Mid(line, val_bgn, content_type_end); + this.content_type_boundary = Bry_.Add(Tkn_content_type_boundary_end, Bry_.Mid(line, boundary_bgn += Tkn_boundary.length, line_len)); + } + private Http_request_itm Make_request_itm() { + return new Http_request_itm(type, url, protocol, host, user_agent, accept, accept_language, accept_encoding, dnt, x_requested_with, cookie, referer, content_length, content_type, content_type_boundary, connection, pragma, cache_control, origin, post_data_hash); + } + private byte[] Parse_content_type_boundary(Http_client_rdr rdr) { + if (post_data_hash == null) post_data_hash = new Http_post_data_hash(); + byte[] line = Bry_.new_u8(rdr.Read_line()); // cur line is already known to be content_type_boundary; skip it + byte[] key = Parse_post_data_name(line); + String line_str = rdr.Read_line(); // blank-line + if (String_.Len_gt_0(line_str)) {throw Err_.new_wo_type("http.request.parser; blank_line should follow content_type_boundary", "request", To_str());} + while (true) { + line = Bry_.new_u8(rdr.Read_line()); + if (Bry_.Has_at_bgn(line, content_type_boundary)) break; + tmp_bfr.Add(line); + } + byte[] val = tmp_bfr.To_bry_and_clear(); + post_data_hash.Add(key, val); + return line; + } + private byte[] Parse_post_data_name(byte[] line) { // EX: Content-Disposition: form-data; name="data" + int line_len = line.length; + int pos = Assert_tkn(line, 0, line_len, Tkn_content_disposition); + pos = Assert_tkn(line, pos, line_len, Tkn_form_data); + pos = Assert_tkn(line, pos, line_len, Tkn_name); + int name_end = line_len; + if (line[pos] == Byte_ascii.Quote) { + if (line[name_end - 1] != Byte_ascii.Quote) throw Err_.new_wo_type("http.request.parser; invalid form at end", "line", line, "request", To_str()); + ++pos; + --name_end; + } + return Bry_.Mid(line, pos, name_end); + } + private int Assert_tkn(byte[] src, int src_pos, int src_len, byte[] tkn) { + int tkn_len = tkn.length; + if (!Bry_.Match(src, src_pos, src_pos + tkn_len, tkn)) throw Err_.new_wo_type("http.request.parser; invalid form_data line", "tkn", tkn, "line", src, "request", To_str()); + int rv = src_pos += tkn_len; + return Bry_find_.Find_fwd_while_ws(src, rv, src_len); + } + private String To_str() {return Make_request_itm().To_str(tmp_bfr, Bool_.N);} + private static final int Tid_get = 1, Tid_post = 2, Tid_host = 3, Tid_user_agent = 4, Tid_accept = 5, Tid_accept_language = 6, Tid_accept_encoding = 7, Tid_dnt = 8 + , Tid_x_requested_with = 9, Tid_cookie = 10, Tid_referer = 11, Tid_content_length = 12, Tid_content_type = 13, Tid_connection = 14, Tid_pragma = 15, Tid_cache_control = 16 + , Tid_origin = 17, Tid_accept_charset = 18; + private static final Btrie_slim_mgr trie = Btrie_slim_mgr.ci_a7() + .Add_str_int("GET" , Tid_get) + .Add_str_int("POST" , Tid_post) + .Add_str_int("Host:" , Tid_host) + .Add_str_int("User-Agent:" , Tid_user_agent) + .Add_str_int("Accept:" , Tid_accept) + .Add_str_int("Accept-Language:" , Tid_accept_language) + .Add_str_int("Accept-Encoding:" , Tid_accept_encoding) + .Add_str_int("Accept-Charset:" , Tid_accept_charset) + .Add_str_int("DNT:" , Tid_dnt) + .Add_str_int("X-Requested-With:" , Tid_x_requested_with) + .Add_str_int("Cookie:" , Tid_cookie) + .Add_str_int("Referer:" , Tid_referer) + .Add_str_int("Content-length:" , Tid_content_length) + .Add_str_int("Content-Type:" , Tid_content_type) + .Add_str_int("Connection:" , Tid_connection) + .Add_str_int("Pragma:" , Tid_pragma) + .Add_str_int("Cache-Control:" , Tid_cache_control) + .Add_str_int("Origin:" , Tid_origin) + ; + private static final byte[] Tkn_boundary = Bry_.new_a7("boundary="), Tkn_content_type_boundary_end = Bry_.new_a7("--") + , Tkn_content_disposition = Bry_.new_a7("Content-Disposition:"), Tkn_form_data = Bry_.new_a7("form-data;") + , Tkn_name = Bry_.new_a7("name=") + ; +} diff --git a/400_xowa/src/gplx/core/net/Http_request_parser_tst.java b/400_xowa/src/gplx/core/net/Http_request_parser_tst.java index a27517de8..3b1817720 100644 --- a/400_xowa/src/gplx/core/net/Http_request_parser_tst.java +++ b/400_xowa/src/gplx/core/net/Http_request_parser_tst.java @@ -13,3 +13,81 @@ 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.core.net; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.tests.*; +public class Http_request_parser_tst { + @Before public void init() {fxt.Clear();} private final Http_request_parser_fxt fxt = new Http_request_parser_fxt(); + @Test public void Type_post() { + fxt.Test_type_post("POST /url HTTP/1.1", Http_request_itm.Type_post, "/url", "HTTP/1.1"); + } + @Test public void Type_content_type() { + fxt.Test_content_type("Content-Type: multipart/form-data; boundary=---------------------------72432484930026", "multipart/form-data", "-----------------------------72432484930026"); + } + @Test public void Type_content_type__x_www_form_url_encoded() { // PURPOSE: ignore content-type for GET calls like by Mathematica server; DATE:2015-08-04 + fxt.Test_content_type("Content-Type: application/x-www-form-urlencoded", null, null); + } + @Test public void Type_form_data() { + fxt.Test_form_data(String_.Ary + ( "POST /url HTTP/1.1" + , "Content-Type: multipart/form-data; boundary=---------------------------12345678901234" + , "" + , "-----------------------------12345678901234" + , "Content-Disposition: form-data; name=\"key0\"" + , "" + , "val0" + , "-----------------------------12345678901234" + , "Content-Disposition: form-data; name=\"key1\"" + , "" + , "val1" + , "-----------------------------12345678901234--" + ) + , fxt.Make_post_data_itm("key0", "val0") + , fxt.Make_post_data_itm("key1", "val1") + ); + } + @Test public void Type_accept_charset() { + fxt.Test_ignore("Accept-Charset: ISO-8859-1,utf-8;q=0.7"); + } +} +class Http_request_parser_fxt { + private final Http_request_parser parser; + private final Http_client_rdr client_rdr = Http_client_rdr_.new_mem(); + private final Http_server_wtr__mock server_wtr = new Http_server_wtr__mock(); + public Http_request_parser_fxt() { + this.parser = new Http_request_parser(server_wtr, false); + } + public void Clear() { + parser.Clear(); + server_wtr.Clear(); + } + public Http_post_data_itm Make_post_data_itm(String key, String val) {return new Http_post_data_itm(Bry_.new_u8(key), Bry_.new_u8(val));} + public void Test_type_post(String line, int expd_type, String expd_url, String expd_protocol) { + client_rdr.Stream_(String_.Ary(line)); + Http_request_itm req = parser.Parse(client_rdr); + Tfds.Eq(expd_type , req.Type()); + Tfds.Eq(expd_url , String_.new_u8(req.Url())); + Tfds.Eq(expd_protocol , String_.new_u8(req.Protocol())); + } + public void Test_content_type(String line, String expd_content_type, String expd_content_boundary) { + client_rdr.Stream_(String_.Ary(line)); + Http_request_itm req = parser.Parse(client_rdr); + Tfds.Eq(expd_content_type , String_.new_u8(req.Content_type())); + Tfds.Eq(expd_content_boundary , String_.new_u8(req.Content_type_boundary())); + } + public void Test_form_data(String[] ary, Http_post_data_itm... expd) { + client_rdr.Stream_(ary); + Http_request_itm req = parser.Parse(client_rdr); + Http_post_data_hash hash = req.Post_data_hash(); + int len = hash.Len(); + for (int i = 0; i < len; ++i) { + Http_post_data_itm itm = hash.Get_at(i); + Tfds.Eq_bry(itm.Key(), expd[i].Key()); + Tfds.Eq_bry(itm.Val(), expd[i].Val()); + } + } + public void Test_ignore(String line) { + client_rdr.Stream_(String_.Ary(line)); + parser.Parse(client_rdr); + Gftest.Eq__str(null, server_wtr.Data()); + } +} diff --git a/400_xowa/src/gplx/core/net/Http_server_wtr.java b/400_xowa/src/gplx/core/net/Http_server_wtr.java index a27517de8..c9d9e7247 100644 --- a/400_xowa/src/gplx/core/net/Http_server_wtr.java +++ b/400_xowa/src/gplx/core/net/Http_server_wtr.java @@ -13,3 +13,10 @@ 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.core.net; import gplx.*; import gplx.core.*; +public interface Http_server_wtr { + void Write_str_w_nl(String s); +} +class Http_server_wtr__noop implements Http_server_wtr { + public void Write_str_w_nl(String s) {} +} diff --git a/400_xowa/src/gplx/core/net/Http_server_wtr_.java b/400_xowa/src/gplx/core/net/Http_server_wtr_.java index a27517de8..526d25670 100644 --- a/400_xowa/src/gplx/core/net/Http_server_wtr_.java +++ b/400_xowa/src/gplx/core/net/Http_server_wtr_.java @@ -13,3 +13,8 @@ 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.core.net; import gplx.*; import gplx.core.*; +public class Http_server_wtr_ { + public static Http_server_wtr New__console() {return new Http_server_wtr__console();} + public static final Http_server_wtr Noop = new Http_server_wtr__noop(); +} diff --git a/400_xowa/src/gplx/core/net/Http_server_wtr__console.java b/400_xowa/src/gplx/core/net/Http_server_wtr__console.java index a27517de8..f217544bb 100644 --- a/400_xowa/src/gplx/core/net/Http_server_wtr__console.java +++ b/400_xowa/src/gplx/core/net/Http_server_wtr__console.java @@ -13,3 +13,13 @@ 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.core.net; import gplx.*; import gplx.core.*; +import gplx.core.consoles.*; +class Http_server_wtr__console implements Http_server_wtr { + public void Write_str_w_nl(String s) {Console_adp__sys.Instance.Write_str_w_nl(s);} +} +class Http_server_wtr__mock implements Http_server_wtr { + public void Write_str_w_nl(String s) {data = s;} + public String Data() {return data;} private String data; + public void Clear() {data = null;} +} diff --git a/400_xowa/src/gplx/core/net/Local_host_.java b/400_xowa/src/gplx/core/net/Local_host_.java index a27517de8..70d33eed2 100644 --- a/400_xowa/src/gplx/core/net/Local_host_.java +++ b/400_xowa/src/gplx/core/net/Local_host_.java @@ -13,3 +13,10 @@ 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.core.net; import gplx.*; import gplx.core.*; +public class Local_host_ { + public static String Ip_address() { + try {return java.net.InetAddress.getLocalHost().getHostAddress();} + catch (Exception e) {throw Err_.new_exc(e, "net", "ip_address failed");} + } +} diff --git a/400_xowa/src/gplx/core/net/Server_socket_adp.java b/400_xowa/src/gplx/core/net/Server_socket_adp.java index a27517de8..d3b98474f 100644 --- a/400_xowa/src/gplx/core/net/Server_socket_adp.java +++ b/400_xowa/src/gplx/core/net/Server_socket_adp.java @@ -13,3 +13,9 @@ 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.core.net; import gplx.*; import gplx.core.*; +public interface Server_socket_adp { + Server_socket_adp Ctor(int port); + Socket_adp Accept(); + void Rls(); +} diff --git a/400_xowa/src/gplx/core/net/Server_socket_adp__base.java b/400_xowa/src/gplx/core/net/Server_socket_adp__base.java index a27517de8..91ec41789 100644 --- a/400_xowa/src/gplx/core/net/Server_socket_adp__base.java +++ b/400_xowa/src/gplx/core/net/Server_socket_adp__base.java @@ -13,3 +13,30 @@ 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.core.net; import gplx.*; import gplx.core.*; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +public class Server_socket_adp__base implements Server_socket_adp { + private ServerSocket server_socket; + public Server_socket_adp Ctor(int port) { + try { + this.server_socket = new ServerSocket(); + server_socket.setReuseAddress(true); + server_socket.bind(new InetSocketAddress(port)); + } + catch (IOException e) {throw Err_.new_exc(e, "net", "Get_input_stream failed");} + return this; + } + public Socket_adp Accept() { + Socket client_socket = null; + try {client_socket = server_socket.accept();} + catch (IOException e) {throw Err_.new_exc(e, "net", "Get_input_stream failed");} + return new Socket_adp__base(client_socket); + } + public void Rls() { + try {server_socket.close();} + catch (IOException e) {throw Err_.new_exc(e, "net", "Rls failed");} + } +} diff --git a/400_xowa/src/gplx/core/net/Socket_adp.java b/400_xowa/src/gplx/core/net/Socket_adp.java index a27517de8..8cd78ce47 100644 --- a/400_xowa/src/gplx/core/net/Socket_adp.java +++ b/400_xowa/src/gplx/core/net/Socket_adp.java @@ -13,3 +13,10 @@ 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.core.net; import gplx.*; import gplx.core.*; +public interface Socket_adp { + String Ip_address(); + Object Get_input_stream(); + Object Get_output_stream(); + void Rls(); +} diff --git a/400_xowa/src/gplx/core/net/Socket_adp__base.java b/400_xowa/src/gplx/core/net/Socket_adp__base.java index a27517de8..311931e01 100644 --- a/400_xowa/src/gplx/core/net/Socket_adp__base.java +++ b/400_xowa/src/gplx/core/net/Socket_adp__base.java @@ -13,3 +13,25 @@ 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.core.net; import gplx.*; import gplx.core.*; +import java.io.IOException; +import java.net.*; +public class Socket_adp__base implements Socket_adp { + private final Socket socket; + public Socket_adp__base(Socket socket) {this.socket = socket;} + public String Ip_address() { + return socket.getRemoteSocketAddress().toString(); + } + public Object Get_input_stream() { + try {return socket.getInputStream();} + catch (IOException e) {throw Err_.new_exc(e, "net", "Get_input_stream failed");} + } + public Object Get_output_stream() { + try {return socket.getOutputStream();} + catch (IOException e) {throw Err_.new_exc(e, "net", "Get_output_stream failed");} + } + public void Rls() { + try {socket.close();} + catch (IOException e) {throw Err_.new_exc(e, "net", "Rls failed");} + } +} diff --git a/400_xowa/src/gplx/core/net/downloads/Http_download_wkr.java b/400_xowa/src/gplx/core/net/downloads/Http_download_wkr.java index a27517de8..d48b5a1dd 100644 --- a/400_xowa/src/gplx/core/net/downloads/Http_download_wkr.java +++ b/400_xowa/src/gplx/core/net/downloads/Http_download_wkr.java @@ -13,3 +13,12 @@ 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.core.net.downloads; import gplx.*; import gplx.core.*; import gplx.core.net.*; +public interface Http_download_wkr { + String Fail_msg(); + Io_url Tmp_url(); + Http_download_wkr Make_new(); + long Checkpoint__load_by_trg_fil(Io_url trg_url); + byte Exec(gplx.core.progs.Gfo_prog_ui prog_ui, String src_str, Io_url trg_url, long expd_size); + void Exec_cleanup(); +} diff --git a/400_xowa/src/gplx/core/net/downloads/Http_download_wkr_.java b/400_xowa/src/gplx/core/net/downloads/Http_download_wkr_.java index a27517de8..1b2f81fb8 100644 --- a/400_xowa/src/gplx/core/net/downloads/Http_download_wkr_.java +++ b/400_xowa/src/gplx/core/net/downloads/Http_download_wkr_.java @@ -13,3 +13,7 @@ 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.core.net.downloads; import gplx.*; import gplx.core.*; import gplx.core.net.*; +public class Http_download_wkr_ { + public static Http_download_wkr Proto = new Http_download_wkr__jre(); +} diff --git a/400_xowa/src/gplx/core/net/downloads/Http_download_wkr__base.java b/400_xowa/src/gplx/core/net/downloads/Http_download_wkr__base.java index a27517de8..a48ab6d2b 100644 --- a/400_xowa/src/gplx/core/net/downloads/Http_download_wkr__base.java +++ b/400_xowa/src/gplx/core/net/downloads/Http_download_wkr__base.java @@ -13,3 +13,69 @@ 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.core.net.downloads; import gplx.*; import gplx.core.*; import gplx.core.net.*; +import gplx.core.progs.*; +public abstract class Http_download_wkr__base implements Http_download_wkr { + private long expd_size; + private Io_url tmp_url, checkpoint_url; + private long downloaded; + private long checkpoint_interval = 1024 * 1024, checkpoint_nxt = 0; + protected Io_url Checkpoint_url() {return checkpoint_url;} + protected Io_url Trg_url() {return trg_url;} private Io_url trg_url; + public Io_url Tmp_url() {return tmp_url;} + public String Fail_msg() {return fail_msg;} private String fail_msg; + public abstract Http_download_wkr Make_new(); + public byte Exec(gplx.core.progs.Gfo_prog_ui prog_ui, String src_str, Io_url trg_url, long expd_size_val) { + this.trg_url = trg_url; + this.downloaded = this.Checkpoint__load_by_trg_fil(trg_url); + this.checkpoint_nxt = downloaded + checkpoint_interval; + this.expd_size = expd_size_val; + this.fail_msg = null; + + byte status = Gfo_prog_ui_.Status__fail; + try {status = this.Exec_hook(prog_ui, src_str, tmp_url, downloaded);} + catch (Exception e) { + status = Gfo_prog_ui_.Status__fail; + fail_msg = Err_.Message_lang(e); + } + switch (status) { + case Gfo_prog_ui_.Status__done: { + if (expd_size_val != -1) { + long actl_size = Io_mgr.Instance.QueryFil(tmp_url).Size(); + if (expd_size != actl_size) { + this.fail_msg = String_.Format("bad size: bad={0} good={1}", actl_size, expd_size); + return Gfo_prog_ui_.Status__fail; + } + } + Io_mgr.Instance.MoveFil_args(tmp_url, trg_url, true).Exec(); + this.Exec_cleanup(); + break; + } + case Gfo_prog_ui_.Status__suspended: + case Gfo_prog_ui_.Status__fail: { + break; + } + } + return status; + } + protected abstract byte Exec_hook(gplx.core.progs.Gfo_prog_ui prog_ui, String src_str, Io_url trg_url, long downloaded); + public void Exec_cleanup() { + if (tmp_url != null) Io_mgr.Instance.DeleteFil(tmp_url); + if (checkpoint_url != null) Io_mgr.Instance.DeleteFil(checkpoint_url); + } + public long Checkpoint__load_by_trg_fil(Io_url trg_url) { + this.tmp_url = trg_url.GenNewExt(".tmp"); + this.checkpoint_url = trg_url.GenNewExt(".checkpoint"); + return this.Checkpoint__load(); + } + private long Checkpoint__load() { + byte[] data = Io_mgr.Instance.LoadFilBryOrNull(checkpoint_url); + return data == null ? 0 : Long_.parse_or(String_.new_a7(data), 0); + } + public void Checkpoint__save(long new_val) { + if (new_val < checkpoint_nxt) return; + Io_mgr.Instance.SaveFilStr(checkpoint_url, Long_.To_str(new_val)); + downloaded = new_val; + checkpoint_nxt += checkpoint_interval; + } +} diff --git a/400_xowa/src/gplx/core/net/downloads/Http_download_wkr__jre.java b/400_xowa/src/gplx/core/net/downloads/Http_download_wkr__jre.java index a27517de8..e67c06451 100644 --- a/400_xowa/src/gplx/core/net/downloads/Http_download_wkr__jre.java +++ b/400_xowa/src/gplx/core/net/downloads/Http_download_wkr__jre.java @@ -13,3 +13,97 @@ 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.core.net.downloads; import gplx.*; import gplx.core.*; import gplx.core.net.*; +import java.io.*; +import java.net.*; +import gplx.core.progs.*; +public class Http_download_wkr__jre extends Http_download_wkr__base { + public Http_download_wkr Make_new() {return new Http_download_wkr__jre();} + @Override public byte Exec_hook(Gfo_prog_ui prog_ui, String src_url, Io_url trg_url, long downloaded) { + long prog_data_cur = downloaded; + boolean prog_resumed = prog_data_cur > 0; + + // get trg stream first to handle bad paths / permission errors + Io_mgr.Instance.CreateDirIfAbsent(trg_url.OwnerDir()); + if (prog_resumed) + Io_mgr.Instance.Truncate_fil(trg_url, downloaded); + File trg_fil = new File(trg_url.Xto_api()); + FileOutputStream trg_stream = null; + try {trg_stream = new FileOutputStream(trg_fil.getPath(), prog_resumed);} // pass true for append + catch (FileNotFoundException e) {throw Err_.new_wo_type("write failed; permission error?", "trg", trg_url, "err", e.toString());} + + // open src stream + InputStream src_stream = null; + URL src_url_itm = null; + try {src_url_itm = new URL(src_url);} + catch (MalformedURLException e) { + try {if (trg_stream != null) trg_stream.close();} + catch (IOException e1) {} + throw Err_.new_wo_type("bad url", "src", src_url, "err" + e.toString()); + } + HttpURLConnection src_conn = null; + try { + // open connection + src_conn = (HttpURLConnection)src_url_itm.openConnection(); + if (prog_resumed) + src_conn.addRequestProperty("Range", "bytes=" + Long_.To_str(prog_data_cur) + "-"); + src_conn.setReadTimeout(10000); // explicitly set timeout; NOTE:needed on Mac OS X, else error never thrown; DATE:2016-09-03 + src_conn.connect(); + + // check response code + int response_code = src_conn.getResponseCode(); + if (prog_resumed) { + if (response_code != HttpURLConnection.HTTP_PARTIAL) { + try {if (trg_stream != null) trg_stream.close();} + catch (IOException e1) {} + if (response_code == 416 && prog_data_cur > 0) { // 416=Requested Range not satisfiable; if resuming at position > max_len, assume critical failure; delete files to start over from scratch; DATE:2016-09-24 + Io_mgr.Instance.DeleteFil(this.Trg_url()); + Io_mgr.Instance.DeleteFil(this.Checkpoint_url()); + } + throw Err_.new_wo_type("server returned non-partial response code", "src", src_url, "code", src_conn.getResponseCode(), "msg", src_conn.getResponseMessage()); + } + } + else { + if (response_code != HttpURLConnection.HTTP_OK) { + try {if (trg_stream != null) trg_stream.close();} + catch (IOException e1) {} + throw Err_.new_wo_type("server returned non-OK response code", "src", src_url, "code", src_conn.getResponseCode(), "msg", src_conn.getResponseMessage()); + } + } + src_stream = src_conn.getInputStream(); + } catch (Exception e) { + try {if (trg_stream != null) trg_stream.close();} + catch (IOException e1) {} + throw Err_.new_wo_type(Err__server_connection_failed, "src", src_url, "err", e.toString()); + } + + // do downloading + try { + long prog_data_end = prog_ui.Prog_data_end(); + if (prog_data_end == -1) prog_data_end = src_conn.getContentLength(); // NOTE: may be -1 if server does not report the length + byte data[] = new byte[4096]; + while (true) { + int read = src_stream.read(data); + if (read == -1) break; // no more data + prog_data_cur += read; + trg_stream.write(data, 0, read); + this.Checkpoint__save(prog_data_cur); + if (prog_ui.Prog_notify_and_chk_if_suspended(prog_data_cur, prog_data_end)) return Gfo_prog_ui_.Status__suspended; + } + } catch (Exception e) { + throw Err_.new_wo_type(Err__server_download_failed, "src", src_url, "trg_url", trg_url, "err", e.toString()); + } + finally { + try { + if (trg_stream != null) trg_stream.close(); + if (src_stream != null) src_stream.close(); + } catch (IOException e) {} + if (src_conn != null) src_conn.disconnect(); + } + return Gfo_prog_ui_.Status__done; + } + public static final String + Err__server_connection_failed = "server connection failed" + , Err__server_download_failed = "server download failed" + ; +} diff --git a/400_xowa/src/gplx/core/net/emails/Gfo_email_mgr.java b/400_xowa/src/gplx/core/net/emails/Gfo_email_mgr.java index a27517de8..68a57dad7 100644 --- a/400_xowa/src/gplx/core/net/emails/Gfo_email_mgr.java +++ b/400_xowa/src/gplx/core/net/emails/Gfo_email_mgr.java @@ -13,3 +13,10 @@ 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.core.net.emails; import gplx.*; import gplx.core.*; import gplx.core.net.*; +public interface Gfo_email_mgr { + void Send(String to, String subject, String body); +} +class Gfo_email_mgr__noop implements Gfo_email_mgr { + public void Send(String to, String subject, String body) {} +} diff --git a/400_xowa/src/gplx/core/net/emails/Gfo_email_mgr_.java b/400_xowa/src/gplx/core/net/emails/Gfo_email_mgr_.java index a27517de8..7ce4557d3 100644 --- a/400_xowa/src/gplx/core/net/emails/Gfo_email_mgr_.java +++ b/400_xowa/src/gplx/core/net/emails/Gfo_email_mgr_.java @@ -13,3 +13,8 @@ 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.core.net.emails; import gplx.*; import gplx.core.*; import gplx.core.net.*; +public class Gfo_email_mgr_ { + public static Gfo_email_mgr Instance = new Gfo_email_mgr__noop(); + public static Gfo_email_mgr New_jre() {return new Gfo_email_mgr__jre();} +} diff --git a/400_xowa/src/gplx/core/net/emails/Gfo_email_mgr__apache.java b/400_xowa/src/gplx/core/net/emails/Gfo_email_mgr__apache.java index a27517de8..9a42f3b75 100644 --- a/400_xowa/src/gplx/core/net/emails/Gfo_email_mgr__apache.java +++ b/400_xowa/src/gplx/core/net/emails/Gfo_email_mgr__apache.java @@ -13,3 +13,8 @@ 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.core.net.emails; import gplx.*; import gplx.core.*; import gplx.core.net.*; +class Gfo_email_mgr__apache implements Gfo_email_mgr { +public void Send(String to, String subject, String body) { +} +} diff --git a/400_xowa/src/gplx/core/net/emails/Gfo_email_mgr__jre.java b/400_xowa/src/gplx/core/net/emails/Gfo_email_mgr__jre.java index a27517de8..7791ee6fb 100644 --- a/400_xowa/src/gplx/core/net/emails/Gfo_email_mgr__jre.java +++ b/400_xowa/src/gplx/core/net/emails/Gfo_email_mgr__jre.java @@ -13,3 +13,24 @@ 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.core.net.emails; import gplx.*; import gplx.core.*; import gplx.core.net.*; +import gplx.langs.htmls.encoders.*; +import java.awt.Desktop; +import java.net.URI; +import java.net.URLDecoder; +import java.net.URLEncoder; +class Gfo_email_mgr__jre implements Gfo_email_mgr { + public void Send(String to, String subject, String body) { + try { + Gfo_url_encoder url_encoder = Gfo_url_encoder_.New__fsys_wnt().Make(); + subject = url_encoder.Encode_str(subject); + body = url_encoder.Encode_str(body); + body = String_.Replace(body, "`", "%60"); + String url_str = "mailto:gnosygnu+xowa_xolog@gmail.com?subject=" + subject + "&body=" + body; + URI uri = new URI(url_str); + Desktop.getDesktop().mail(uri); + } catch (Exception e) { + Gfo_log_.Instance.Warn("email failed", "subject", subject, "body", body, "err", Err_.Message_gplx_log(e)); + } + } +} diff --git a/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_enum_itm.java b/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_enum_itm.java index a27517de8..841c6f378 100644 --- a/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_enum_itm.java +++ b/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_enum_itm.java @@ -13,3 +13,15 @@ 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.core.net.qargs; import gplx.*; import gplx.core.*; import gplx.core.net.*; +public class Gfo_qarg_enum_itm { + private final Hash_adp_bry hash = Hash_adp_bry.cs(); + public Gfo_qarg_enum_itm(String key) {this.key = Bry_.new_u8(key);} + public Gfo_qarg_enum_itm(byte[] key) {this.key = key;} + public byte[] Key() {return key;} private final byte[] key; + public Gfo_qarg_enum_itm Add(String key, int val) { + hash.Add_bry_int(Bry_.new_u8(key), val); + return this; + } + public int Get_as_int_or(byte[] val, int or) {return hash.Get_as_int_or(val, or);} +} diff --git a/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_enum_mgr.java b/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_enum_mgr.java index a27517de8..b0c3babfe 100644 --- a/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_enum_mgr.java +++ b/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_enum_mgr.java @@ -13,3 +13,15 @@ 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.core.net.qargs; import gplx.*; import gplx.core.*; import gplx.core.net.*; +public class Gfo_qarg_enum_mgr { + private final Hash_adp_bry hash = Hash_adp_bry.cs(); + public Gfo_qarg_enum_mgr(Gfo_qarg_enum_itm... ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) { + Gfo_qarg_enum_itm itm = ary[i]; + hash.Add_bry_obj(itm.Key(), itm); + } + } + public Gfo_qarg_enum_itm Get(byte[] key) {return (Gfo_qarg_enum_itm)hash.Get_by_bry(key);} +} diff --git a/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_itm.java b/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_itm.java index a27517de8..7c8216f45 100644 --- a/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_itm.java +++ b/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_itm.java @@ -13,3 +13,15 @@ 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.core.net.qargs; import gplx.*; import gplx.core.*; import gplx.core.net.*; +public class Gfo_qarg_itm { + public Gfo_qarg_itm(byte[] key_bry, byte[] val_bry) { + this.key_bry = key_bry; + this.val_bry = val_bry; + } + public byte[] Key_bry() {return key_bry;} private final byte[] key_bry; + public byte[] Val_bry() {return val_bry;} private byte[] val_bry; + public void Val_bry_(byte[] v) {val_bry = v;} + + public static final Gfo_qarg_itm[] Ary_empty = new Gfo_qarg_itm[0]; +} diff --git a/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_mgr.java b/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_mgr.java index a27517de8..e03ecd637 100644 --- a/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_mgr.java +++ b/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_mgr.java @@ -13,3 +13,51 @@ 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.core.net.qargs; import gplx.*; import gplx.core.*; import gplx.core.net.*; +public class Gfo_qarg_mgr { + private final Hash_adp_bry hash = Hash_adp_bry.cs(); + public Gfo_qarg_mgr Init(Gfo_qarg_itm[] args) { + hash.Clear(); + int len = args.length; + for (int i = 0; i < len; ++i) { + Gfo_qarg_itm arg = args[i]; + hash.Add_bry_obj(arg.Key_bry(), arg); + } + return this; + } + public byte[] Read_bry_or_fail(String key) {return Read_bry_or_fail(Bry_.new_u8(key));} + public byte[] Read_bry_or_fail(byte[] key) {byte[] rv = Read_bry_or_null(key); if (rv == null) Fail_when_missing(String_.new_u8(key)); return rv;} + public byte[] Read_bry_or_empty(byte[] key) {return Read_bry_or(key, Bry_.Empty);} + public byte[] Read_bry_or_null(String key) {return Read_bry_or(Bry_.new_u8(key), null);} + public byte[] Read_bry_or_null(byte[] key) {return Read_bry_or(key, null);} + public byte[] Read_bry_or(String key, byte[] or) {return Read_bry_or(Bry_.new_u8(key), or);} + public byte[] Read_bry_or(byte[] key, byte[] or) { + Gfo_qarg_itm arg = (Gfo_qarg_itm)hash.Get_by_bry(key); + return arg == null ? or : arg.Val_bry(); + } + public String Read_str_or_fail(String key) {String rv = Read_str_or_null(Bry_.new_u8(key)); if (rv == null) Fail_when_missing(key); return rv;} + public String Read_str_or_null(String key) {return Read_str_or_null(Bry_.new_u8(key));} + public String Read_str_or_null(byte[] key) {return Read_str_or(key, null);} + public String Read_str_or(String key, String or) {return Read_str_or(Bry_.new_u8(key), or);} + public String Read_str_or(byte[] key, String or) { + Gfo_qarg_itm arg = (Gfo_qarg_itm)hash.Get_by_bry(key); + return arg == null ? or : String_.new_u8(arg.Val_bry()); + } + public int Read_int_or(String key, int or) {return Read_int_or(Bry_.new_u8(key), or);} + public int Read_int_or(byte[] key, int or) { + byte[] val = Read_bry_or(key, null); + return val == null ? or : Int_.Parse_or(String_.new_a7(val), or); + } + public int Read_enm_as_int_or(Gfo_qarg_enum_itm enm, int or) { + Gfo_qarg_itm arg = (Gfo_qarg_itm)hash.Get_by_bry(enm.Key()); + return arg == null ? or : enm.Get_as_int_or(arg.Val_bry(), or); + } + private void Fail_when_missing(String key) {throw Err_.new_("", "url_arg missing", "key", key);} + // if (url_args.Read_enm(Enm_cmd.Itm) == Enm_cmd.Tid__add) {} + +// public int Read_enm_or_neg1(byte[] key) { +// Gfo_qarg_enum_itm enm = enm_mgr.Get(key); if (enm == null) return -1; +// Gfo_qarg_itm arg = (Gfo_qarg_itm)hash.Get_by_bry(key); if (arg == null) return -1; +// return enm.Get_as_int_or(arg.Val_bry(), -1); +// } +} diff --git a/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_mgr_old.java b/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_mgr_old.java index a27517de8..377ba95cf 100644 --- a/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_mgr_old.java +++ b/400_xowa/src/gplx/core/net/qargs/Gfo_qarg_mgr_old.java @@ -13,3 +13,93 @@ 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.core.net.qargs; import gplx.*; import gplx.core.*; import gplx.core.net.*; +import gplx.langs.htmls.encoders.*; +public class Gfo_qarg_mgr_old { + private final List_adp list = List_adp_.New(); + private final Hash_adp hash = Hash_adp_bry.cs(); + public int Len() {return list.Count();} + public boolean Match(byte[] key, byte[] val) { + Gfo_qarg_itm arg = (Gfo_qarg_itm)hash.Get_by(key); + return arg == null ? false : Bry_.Eq(val, arg.Val_bry()); + } + public Gfo_qarg_itm Get_at(int i) {return (Gfo_qarg_itm)list.Get_at(i);} + public Gfo_qarg_itm Get_arg(byte[] key) {return (Gfo_qarg_itm)hash.Get_by(key);} + public int Get_val_int_or(byte[] key, int or) { + byte[] val_bry = Get_val_bry_or(key, null); if (val_bry == null) return or; + return Bry_.To_int_or(val_bry, or); + } + public byte[] Get_val_bry_or(byte[] key, byte[] or) { + Gfo_qarg_itm arg = (Gfo_qarg_itm)hash.Get_by(key); + return arg == null ? or : arg.Val_bry(); + } + public String Get_val_str_or(byte[] key, String or) { + Gfo_qarg_itm arg = (Gfo_qarg_itm)hash.Get_by(key); + return arg == null ? or : String_.new_u8(arg.Val_bry()); + } + public void Set_val_by_int(byte[] key, int val) {Set_val_by_bry(key, Bry_.new_a7(Int_.To_str(val)));} + public void Set_val_by_bry(byte[] key, byte[] val) { + Gfo_qarg_itm arg = (Gfo_qarg_itm)hash.Get_by(key); + if (arg == null) { + arg = new Gfo_qarg_itm(key, Bry_.Empty); + list.Add(arg); + hash.Add(key, arg); + } + arg.Val_bry_(val); + } + public Gfo_qarg_mgr_old Load(Gfo_qarg_itm[] ary) { + hash.Clear(); + list.Clear(); + int len = ary.length; + for (int i = 0; i < len; ++i) { + Gfo_qarg_itm itm = ary[i]; + list.Add(itm); + hash.Add_if_dupe_use_nth(itm.Key_bry(), itm); + } + return this; + } + public Gfo_qarg_itm[] To_ary() {return (Gfo_qarg_itm[])list.To_ary(Gfo_qarg_itm.class);} + public byte[] Concat(Bry_bfr bfr, byte[]... ary) { + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) { + byte[] key = ary[i]; + Gfo_qarg_itm itm = Get_arg(key); if (itm == null) continue; + bfr.Add_byte(Byte_ascii.Amp).Add(itm.Key_bry()).Add_byte(Byte_ascii.Eq).Add(itm.Val_bry()); + } + return bfr.To_bry_and_clear(); + } + public byte[] To_bry() { + int len = list.Count(); if (len == 0) return Bry_.Empty; + Bry_bfr bfr = Bry_bfr_.New(); + To_bry(bfr, gplx.langs.htmls.encoders.Gfo_url_encoder_.Href, false); + return bfr.To_bry_and_clear(); + } + public void To_bry(Bry_bfr bfr, Gfo_url_encoder href_encoder, boolean encode) { + int len = list.Count(); if (len == 0) return; + for (int i = 0; i < len; ++i) { + Gfo_qarg_itm itm = (Gfo_qarg_itm)list.Get_at(i); + bfr.Add_byte(i == 0 ? Byte_ascii.Question : Byte_ascii.Amp); + Write_or_encode(bfr, href_encoder, encode, itm.Key_bry()); + bfr.Add_byte(Byte_ascii.Eq); + Write_or_encode(bfr, href_encoder, encode, itm.Val_bry()); + } + } + public static void Concat_bfr(Bry_bfr bfr, Gfo_url_encoder href_encoder, Gfo_qarg_itm[] ary) {Concat_bfr(bfr, href_encoder, ary, true);} + private static void Concat_bfr(Bry_bfr bfr, Gfo_url_encoder href_encoder, Gfo_qarg_itm[] ary, boolean encode) { + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) { + Gfo_qarg_itm itm = ary[i]; + bfr.Add_byte(i == 0 ? Byte_ascii.Question : Byte_ascii.Amp); + Write_or_encode(bfr, href_encoder, encode, itm.Key_bry()); + bfr.Add_byte(Byte_ascii.Eq); + Write_or_encode(bfr, href_encoder, encode, itm.Val_bry()); + } + } + private static void Write_or_encode(Bry_bfr bfr, Gfo_url_encoder href_encoder, boolean encode, byte[] bry) { + if (bry == null) return; // NOTE: need null check b/c itm.Val_bry can be null + if (encode) + href_encoder.Encode(bfr, bry); + else + bfr.Add(bry); + } +} diff --git a/400_xowa/src/gplx/core/primitives/Bool_ary_bldr.java b/400_xowa/src/gplx/core/primitives/Bool_ary_bldr.java index a27517de8..04a97cd11 100644 --- a/400_xowa/src/gplx/core/primitives/Bool_ary_bldr.java +++ b/400_xowa/src/gplx/core/primitives/Bool_ary_bldr.java @@ -13,3 +13,25 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Bool_ary_bldr { + private final boolean[] ary; + public Bool_ary_bldr(int len) { + this.ary = new boolean[len]; + } + public Bool_ary_bldr Set_many(int... v) { + int len = v.length; + for (int i = 0; i < len; i++) + ary[v[i]] = true; + return this; + } + public Bool_ary_bldr Set_rng(int bgn, int end) { + for (int i = bgn; i <= end; i++) + ary[i] = true; + return this; + } + public boolean[] To_ary() { + return ary; + } + public static Bool_ary_bldr New_u8() {return new Bool_ary_bldr(256);} +} diff --git a/400_xowa/src/gplx/core/primitives/Bry_ary.java b/400_xowa/src/gplx/core/primitives/Bry_ary.java index a27517de8..660cb728d 100644 --- a/400_xowa/src/gplx/core/primitives/Bry_ary.java +++ b/400_xowa/src/gplx/core/primitives/Bry_ary.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.core.primitives; import gplx.*; import gplx.core.*; +public class Bry_ary { + private byte[][] ary; private int len, max; + public Bry_ary(int max) { + this.len = 0; + this.max = max; + this.ary = new byte[max][]; + } + public byte[][] Ary() {return ary;} + public void Clear() { + for (int i = 0; i < len; ++i) + ary[i] = null; + len = 0; + } + public int Len() {return len;} + public void Add(byte[] v) { + if (len == max) { + int new_max = max * 2; + byte[][] new_ary = new byte[new_max][]; + for (int i = 0; i < len; ++i) + new_ary[i] = ary[i]; + this.ary = new_ary; + this.max = new_max; + } + ary[len] = v; + ++len; + } + public byte[] Get_at(int i) {return ary[i];} + public byte[] Get_at_last() {return len == 0 ? null : ary[len - 1];} + public void Set_at_last(byte[] v) {ary[len - 1] = v;} + public void Set_at(int i, byte[] v) {ary[i] = v;} + public byte[][] To_ary(int rel) { + if (len == 0) return Bry_.Ary_empty; + int rv_len = len + rel; + byte[][] rv = new byte[rv_len][]; + for (int i = 0; i < rv_len; ++i) + rv[i] = ary[i]; + return rv; + } +} diff --git a/400_xowa/src/gplx/core/primitives/Bry_cache.java b/400_xowa/src/gplx/core/primitives/Bry_cache.java index a27517de8..b0e32449e 100644 --- a/400_xowa/src/gplx/core/primitives/Bry_cache.java +++ b/400_xowa/src/gplx/core/primitives/Bry_cache.java @@ -13,3 +13,21 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +import gplx.core.brys.*; +public class Bry_cache { + private final Hash_adp hash = Hash_adp_.New(); private final Bry_obj_ref hash_ref = Bry_obj_ref.New_empty(); + public byte[] Get_or_new(String v) {return Get_or_new(Bry_.new_u8(v));} + public byte[] Get_or_new(byte[] v) { + if (v.length == 0) return Bry_.Empty; + Object rv = hash.Get_by(hash_ref.Val_(v)); + if (rv == null) { + Bry_obj_ref bry = Bry_obj_ref.New(v); + hash.Add_as_key_and_val(bry); + return v; + } + else + return ((Bry_obj_ref)rv).Val(); + } + public static final Bry_cache Instance = new Bry_cache(); Bry_cache() {} +} diff --git a/400_xowa/src/gplx/core/primitives/Gfo_number_parser.java b/400_xowa/src/gplx/core/primitives/Gfo_number_parser.java index a27517de8..2ba2fa6dd 100644 --- a/400_xowa/src/gplx/core/primitives/Gfo_number_parser.java +++ b/400_xowa/src/gplx/core/primitives/Gfo_number_parser.java @@ -13,3 +13,173 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Gfo_number_parser { + public int Rv_as_int() {return (int)num_val;} private long num_val = 0; + public long Rv_as_long() {return num_val;} + public Decimal_adp Rv_as_dec() {return dec_val == null ? Decimal_adp_.long_(num_val) : dec_val;} private Decimal_adp dec_val = null; + public boolean Is_int() {return dec_val == null && (num_val >= Int_.Min_value && num_val <= Int_.Max_value);} + public boolean Has_err() {return has_err;} private boolean has_err; + public boolean Has_frac() {return has_frac;} private boolean has_frac; + public boolean Hex_enabled() {return hex_enabled;} public Gfo_number_parser Hex_enabled_(boolean v) {hex_enabled = v; return this;} private boolean hex_enabled; + public Gfo_number_parser Ignore_chars_(byte[] v) {this.ignore_chars = v; return this;} private byte[] ignore_chars; + public Gfo_number_parser Ignore_space_at_end_y_() {this.ignore_space_at_end = true; return this;} private boolean ignore_space_at_end; + public void Clear() { + ignore_chars = null; + } + public Gfo_number_parser Parse(byte[] src) {return Parse(src, 0, src.length);} + public Gfo_number_parser Parse(byte[] ary, int bgn, int end) { + int loop_bgn = end - 1, loop_end = bgn - 1, exp_multiplier = 1, factor = 10; + long multiplier = 1, frc_multiplier = 1; + num_val = 0; dec_val = null; boolean comma_nil = true; + long frc_int = 0; + has_err = false; has_frac = false; boolean has_exp = false, has_neg = false, exp_neg = false, has_plus = false, has_num = false; + boolean input_is_hex = false; + if (hex_enabled) { + if (loop_end + 2 < end) { // ArrayOutOfBounds check + byte b_2 = ary[loop_end + 2]; + switch (b_2) { + case Byte_ascii.Ltr_x: + case Byte_ascii.Ltr_X: // is 2nd char x? + if (ary[loop_end + 1] == Byte_ascii.Num_0) { // is 1st char 0? + factor = 16; + input_is_hex = true; + } + break; + default: + break; + } + } + } + for (int i = loop_bgn; i > loop_end; i--) { + byte cur = ary[i]; + switch (cur) { + case Byte_ascii.Num_0: + case Byte_ascii.Num_1: + case Byte_ascii.Num_2: + case Byte_ascii.Num_3: + case Byte_ascii.Num_4: + case Byte_ascii.Num_5: + case Byte_ascii.Num_6: + case Byte_ascii.Num_7: + case Byte_ascii.Num_8: + case Byte_ascii.Num_9: + num_val += (cur - Byte_ascii.Num_0) * multiplier; + multiplier *= factor; + has_num = true; + break; + case Byte_ascii.Dot: + if (has_frac) return Has_err_y_(); + frc_int = num_val; + num_val = 0; + frc_multiplier = multiplier; + multiplier = 1; + has_frac = true; + break; + case Byte_ascii.Comma: + if (comma_nil) + comma_nil = false; + else + return Has_err_y_(); + break; + case Byte_ascii.Dash: + if (has_neg) return Has_err_y_(); + has_neg = true; + break; + case Byte_ascii.Space: + if (i == bgn) {} // space at bgn + else if (i == end - 1 && ignore_space_at_end) {} // ignore space at end; DATE:2015-04-29 + else + return Has_err_y_(); + break; + case Byte_ascii.Plus: + if (has_plus) return Has_err_y_(); + has_plus = true; + break; + case Byte_ascii.Ltr_e: + case Byte_ascii.Ltr_E: + if (input_is_hex) { + num_val += 14 * multiplier; // NOTE: 14=value of e/E + multiplier *= factor; + has_num = true; + } + else { + if (has_exp) return Has_err_y_(); + exp_neg = has_neg; + exp_multiplier = (int)Math_.Pow(10, num_val); + num_val = 0; + multiplier = 1; + has_exp = true; + has_neg = false; + has_plus = false; // allow +1E+2 + } + break; + case Byte_ascii.Ltr_A: + case Byte_ascii.Ltr_B: + case Byte_ascii.Ltr_C: + case Byte_ascii.Ltr_D: + case Byte_ascii.Ltr_F: + if (input_is_hex) { + num_val += (cur - Byte_ascii.Ltr_A + 10) * multiplier; + multiplier *= factor; + has_num = true; + } + else + return Has_err_y_(); + break; + case Byte_ascii.Ltr_a: + case Byte_ascii.Ltr_b: + case Byte_ascii.Ltr_c: + case Byte_ascii.Ltr_d: + case Byte_ascii.Ltr_f: + if (input_is_hex) { + num_val += (cur - Byte_ascii.Ltr_a + 10) * multiplier; + multiplier *= factor; + has_num = true; + } + else + return Has_err_y_(); + break; + case Byte_ascii.Ltr_x: + case Byte_ascii.Ltr_X: + if (input_is_hex) + return (factor == 16) ? this : Has_err_y_(); // check for '0x' + else + return Has_err_y_(); + default: + if (ignore_chars != null) { + int ignore_chars_len = ignore_chars.length; + boolean ignored = false; + for (int j = 0; j < ignore_chars_len; ++j) { + if (cur == ignore_chars[j]) { + ignored = true; + break; + } + } + if (ignored) continue; + } + return Has_err_y_(); + } + } + if (!has_num) return Has_err_y_(); // handles situations wherein just symbols; EX: "+", ".", "-.", " , " etc. + if (has_frac) { + long full_val = (((num_val * frc_multiplier) + frc_int)); + if (has_neg) full_val *= -1; + if (has_exp) { + if (exp_neg) frc_multiplier *= exp_multiplier; // divide, so apply to frc + else full_val *= exp_multiplier; // multiply, so apply to full_val + } + dec_val = Decimal_adp_.divide_(full_val, frc_multiplier); + } + else { + if (has_neg) num_val *= -1; + if (has_exp) { + num_val = exp_neg + ? num_val / exp_multiplier + : num_val * exp_multiplier; + } + } + return this; + } + private Gfo_number_parser Has_err_y_() {has_err = true; return this;} +} diff --git a/400_xowa/src/gplx/core/primitives/Gfo_number_parser_tst.java b/400_xowa/src/gplx/core/primitives/Gfo_number_parser_tst.java index a27517de8..4a287e464 100644 --- a/400_xowa/src/gplx/core/primitives/Gfo_number_parser_tst.java +++ b/400_xowa/src/gplx/core/primitives/Gfo_number_parser_tst.java @@ -13,3 +13,95 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Gfo_number_parser_tst { + @Before public void init() {fxt.Clear();} private final Gfo_number_parser_fxt fxt = new Gfo_number_parser_fxt(); + @Test public void Integer() { + fxt.Test_int("1", 1); + fxt.Test_int("1234", 1234); + fxt.Test_int("1234567890", 1234567890); + fxt.Test_int("-1234", -1234); + fxt.Test_int("+1", 1); + fxt.Test_int("00001", 1); + } + @Test public void Long() { + fxt.Test_long("9876543210", 9876543210L); + } + @Test public void Decimal() { + fxt.Test_dec("1.23", Decimal_adp_.parse("1.23")); + fxt.Test_dec("1.023", Decimal_adp_.parse("1.023")); + fxt.Test_dec("-1.23", Decimal_adp_.parse("-1.23")); + } + @Test public void Double_long() { + fxt.Test_dec(".42190046219457", Decimal_adp_.parse(".42190046219457")); + } + @Test public void Exponent() { + fxt.Test_int("1E2", 100); + fxt.Test_dec("1.234E2", Decimal_adp_.parse("123.4")); + fxt.Test_dec("1.234E-2", Decimal_adp_.parse(".01234")); + fxt.Test_dec("123.4E-2", Decimal_adp_.parse("1.234")); + fxt.Test_dec("+6.0E-3", Decimal_adp_.parse(".006")); + } + @Test public void Err() { + fxt.Test_err("+", true); + fxt.Test_err("-", true); + fxt.Test_err("a", true); + fxt.Test_err("1-2", false); + fxt.Test_err("1..1", true); + fxt.Test_err("1,,1", true); + fxt.Test_err("1", false); + } + @Test public void Hex() { + fxt.Test_hex("0x1" , 1); + fxt.Test_hex("0xF" , 15); + fxt.Test_hex("0x20" , 32); + fxt.Test_hex("x20" , 0, false); + fxt.Test_hex("d" , 0, false); // PURPOSE: d was being converted to 13; no.w:Hovedbanen; DATE:2014-04-13 + } + @Test public void Ignore() { + fxt.Init_ignore("\n\t"); + fxt.Test_int("1" , 1); + fxt.Test_int("1\n" , 1); + fxt.Test_int("1\t" , 1); + fxt.Test_int("1\n2" , 12); + fxt.Test_err("1\r" , true); + } +} +class Gfo_number_parser_fxt { + private final Gfo_number_parser parser = new Gfo_number_parser(); + public void Clear() {parser.Clear();} + public void Init_ignore(String chars) {parser.Ignore_chars_(Bry_.new_a7(chars));} + public void Test_int(String raw, int expd) { + byte[] raw_bry = Bry_.new_a7(raw); + int actl = parser.Parse(raw_bry, 0, raw_bry.length).Rv_as_int(); + Tfds.Eq(expd, actl, raw); + } + public void Test_long(String raw, long expd) { + byte[] raw_bry = Bry_.new_a7(raw); + Tfds.Eq(expd, parser.Parse(raw_bry, 0, raw_bry.length).Rv_as_long(), raw); + } + public void Test_dec(String raw, Decimal_adp expd) { + byte[] raw_bry = Bry_.new_a7(raw); + Decimal_adp actl = parser.Parse(raw_bry, 0, raw_bry.length).Rv_as_dec(); + Tfds.Eq(expd.To_double(), actl.To_double(), raw); + } + public void Test_err(String raw, boolean expd) { + byte[] raw_bry = Bry_.new_a7(raw); + boolean actl = parser.Parse(raw_bry, 0, raw_bry.length).Has_err(); + Tfds.Eq(expd, actl, raw); + } + public void Test_hex(String raw, int expd_val) {Test_hex(raw, expd_val, true);} + public void Test_hex(String raw, int expd_val, boolean expd_pass) { + parser.Hex_enabled_(true); + byte[] raw_bry = Bry_.new_a7(raw); + int actl = parser.Parse(raw_bry, 0, raw_bry.length).Rv_as_int(); + if (expd_pass) { + Tfds.Eq(expd_val, actl, raw); + Tfds.Eq(true, !parser.Has_err()); + } + else + Tfds.Eq(false, !parser.Has_err()); + parser.Hex_enabled_(false); + } +} diff --git a/400_xowa/src/gplx/core/primitives/Hash_adp__primitive.java b/400_xowa/src/gplx/core/primitives/Hash_adp__primitive.java index a27517de8..3f8a907bb 100644 --- a/400_xowa/src/gplx/core/primitives/Hash_adp__primitive.java +++ b/400_xowa/src/gplx/core/primitives/Hash_adp__primitive.java @@ -13,3 +13,15 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Hash_adp__primitive { + private final Hash_adp hash = Hash_adp_.New(); + public byte Get_by_str_or_max(String key) { + Byte_obj_val rv = (Byte_obj_val)hash.Get_by(key); + return rv == null ? Byte_.Max_value_127 : rv.Val(); + } + public Hash_adp__primitive Add_byte(String key, byte val) { + hash.Add(key, Byte_obj_val.new_(val)); + return this; + } +} diff --git a/400_xowa/src/gplx/core/primitives/Int_2_ref.java b/400_xowa/src/gplx/core/primitives/Int_2_ref.java index a27517de8..4f5708a83 100644 --- a/400_xowa/src/gplx/core/primitives/Int_2_ref.java +++ b/400_xowa/src/gplx/core/primitives/Int_2_ref.java @@ -13,3 +13,44 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Int_2_ref { + public Int_2_ref() {} + public Int_2_ref(int v0, int v1) {Val_all_(v0, v1);} + public int Val_0() {return val_0;} public Int_2_ref Val_0_(int v) {val_0 = v; return this;} private int val_0; + public int Val_1() {return val_1;} public Int_2_ref Val_1_(int v) {val_1 = v; return this;} private int val_1; + public Int_2_ref Val_all_(int v0, int v1) {val_0 = v0; val_1 = v1; return this;} + @Override public int hashCode() { + int hash = 23; + hash = (hash * 31) + val_0; + hash = (hash * 31) + val_1; + return hash; + } + @Override public boolean equals(Object obj) { + if (obj == null) return false; + Int_2_ref comp = (Int_2_ref)obj; + return val_0 == comp.val_0 && val_1 == comp.val_1; + } + public static Int_2_ref parse(String raw) { + try { + String[] itms = String_.Split(raw, ","); + int v0 = Int_.Parse(itms[0]); + int v1 = Int_.Parse(itms[1]); + return new Int_2_ref(v0, v1); + } catch (Exception e) {Err_.Noop(e); throw Err_.new_parse("Int_2_ref", raw);} + } + public static Int_2_ref[] parse_ary_(String raw) { + try { + String[] itms = String_.Split(raw, ";"); + int itms_len = itms.length; + Int_2_ref[] rv = new Int_2_ref[itms_len]; + for (int i = 0; i < itms_len; i++) { + String[] vals = String_.Split(itms[i], ","); + int v0 = Int_.Parse(vals[0]); + int v1 = Int_.Parse(vals[1]); + rv[i] = new Int_2_ref(v0, v1); + } + return rv; + } catch (Exception e) {Err_.Noop(e); throw Err_.new_parse("Int_2_ref[]", raw);} + } +} diff --git a/400_xowa/src/gplx/core/primitives/Int_2_val.java b/400_xowa/src/gplx/core/primitives/Int_2_val.java index a27517de8..1ffcfff32 100644 --- a/400_xowa/src/gplx/core/primitives/Int_2_val.java +++ b/400_xowa/src/gplx/core/primitives/Int_2_val.java @@ -13,3 +13,19 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Int_2_val { + public Int_2_val(int v0, int v1) {val_0 = v0; val_1 = v1;} + public int Val_0() {return val_0;} final int val_0; + public int Val_1() {return val_1;} final int val_1; + public String Xto_str(Bry_bfr bfr) {return Xto_str(bfr, val_0, val_1);} + public static final Int_2_val Null_ptr = null; + public static Int_2_val parse(String raw) { + String[] itms = String_.Split(raw, ','); + if (itms.length != 2) return Null_ptr; + int v0 = Int_.Parse_or(itms[0], Int_.Min_value); if (v0 == Int_.Min_value) return Null_ptr; + int v1 = Int_.Parse_or(itms[1], Int_.Min_value); if (v1 == Int_.Min_value) return Null_ptr; + return new Int_2_val(v0, v1); + } + public static String Xto_str(Bry_bfr bfr, int x, int y) {return bfr.Add_int_variable(x).Add_byte_comma().Add_int_variable(y).To_str_and_clear();} +} diff --git a/400_xowa/src/gplx/core/primitives/Int_ary.java b/400_xowa/src/gplx/core/primitives/Int_ary.java index a27517de8..4088e6032 100644 --- a/400_xowa/src/gplx/core/primitives/Int_ary.java +++ b/400_xowa/src/gplx/core/primitives/Int_ary.java @@ -13,3 +13,61 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Int_ary { + private int[] ary; private int len, max; + public Int_ary(int max) { + this.len = 0; + this.max = max; + this.ary = new int[max]; + } + public int[] Ary() {return ary;} + public void Clear() { + for (int i = 0; i < len; ++i) + ary[i] = 0; + len = 0; + } + public int Len() {return len;} + public int Get_at_or_fail(int i) { + if (i > -1 && i < len) return ary[i]; + else throw Err_.new_("core.int_ary", "index is invalid", "i", i, "len", len); + } + public void Add(int v) { + if (len == max) { + int new_max = max * 2; + int[] new_ary = new int[new_max]; + for (int i = 0; i < len; ++i) + new_ary[i] = ary[i]; + this.ary = new_ary; + this.max = new_max; + } + ary[len] = v; + ++len; + } + public int Pop_or_fail() { + if (len == 0) throw Err_.new_("core.int_ary", "stack is empty"); + return Pop_or(-1); + } + public int Pop_or(int or) { + if (len == 0) return or; + int rv = ary[len - 1]; + --len; + return rv; + } + public int Idx_of(int find) { + for (int i = len - 1; i > -1; --i) { + if (ary[i] == find) return i; + } + return Not_found; + } + public boolean Del_from_end(int find) { + int find_idx = Idx_of(find); if (find_idx == Not_found) return false; + int last_idx = len - 1; + for (int i = find_idx; i < last_idx; ++i) + ary[i] = ary[i + 1]; + ary[last_idx] = 0; + --len; + return true; + } + public static final int Not_found = -1; +} diff --git a/400_xowa/src/gplx/core/primitives/Int_ary_bldr.java b/400_xowa/src/gplx/core/primitives/Int_ary_bldr.java index a27517de8..e16fab4ca 100644 --- a/400_xowa/src/gplx/core/primitives/Int_ary_bldr.java +++ b/400_xowa/src/gplx/core/primitives/Int_ary_bldr.java @@ -13,3 +13,9 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Int_ary_bldr { + public Int_ary_bldr(int len) {ary = new int[len];} + public Int_ary_bldr Set(int idx, int val) {ary[idx] = val; return this;} + public int[] Xto_int_ary() {return ary;} private int[] ary; +} diff --git a/400_xowa/src/gplx/core/primitives/Int_ary_parser.java b/400_xowa/src/gplx/core/primitives/Int_ary_parser.java index a27517de8..be9cc9a4c 100644 --- a/400_xowa/src/gplx/core/primitives/Int_ary_parser.java +++ b/400_xowa/src/gplx/core/primitives/Int_ary_parser.java @@ -13,3 +13,25 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Int_ary_parser extends Obj_ary_parser_base { + private final Gfo_number_parser parser = new Gfo_number_parser(); + private int[] ary; private int ary_idx; + public int[] Parse_ary(String str, byte dlm) {byte[] bry = Bry_.new_u8(str); return Parse_ary(bry, 0, bry.length, dlm);} + public int[] Parse_ary(byte[] bry, int bgn, int end, byte dlm) { + Parse_core(bry, bgn, end, dlm, Byte_ascii.Null); + return ary; + } + @Override protected void Ary_len_(int v) { + if (v == 0) + ary = Int_ary_.Empty; + else { + ary = new int[v]; // NOTE: always create new array; never reuse; + ary_idx = 0; + } + } + @Override protected void Parse_itm(byte[] bry, int bgn, int end) { + parser.Parse(bry, bgn, end); if (parser.Has_err() || parser.Has_frac()) throw Err_.new_wo_type("failed to parse number", "val", String_.new_u8(bry, bgn, end)); + ary[ary_idx++] = parser.Rv_as_int(); + } +} diff --git a/400_xowa/src/gplx/core/primitives/Int_ary_parser_tst.java b/400_xowa/src/gplx/core/primitives/Int_ary_parser_tst.java index a27517de8..e2ba168cc 100644 --- a/400_xowa/src/gplx/core/primitives/Int_ary_parser_tst.java +++ b/400_xowa/src/gplx/core/primitives/Int_ary_parser_tst.java @@ -13,3 +13,16 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +import org.junit.*; import gplx.core.tests.*; +public class Int_ary_parser_tst { + private final Int_ary_parser_fxt fxt = new Int_ary_parser_fxt(); + @Test public void Many() {fxt.Test__Parse_ary("1,2,3,4,5" , 0, 9, Int_ary_.New(1, 2, 3, 4, 5));} + @Test public void One() {fxt.Test__Parse_ary("1" , 0, 1, Int_ary_.New(1));} + @Test public void None() {fxt.Test__Parse_ary("" , 0, 0, Int_ary_.New());} +} +class Int_ary_parser_fxt { + public void Test__Parse_ary(String raw, int bgn, int end, int[] expd) { + Gftest.Eq__ary(expd, new Int_ary_parser().Parse_ary(Bry_.new_a7(raw), bgn, end, Byte_ascii.Comma), "parse_ary failed"); + } +} diff --git a/400_xowa/src/gplx/core/primitives/Int_pool.java b/400_xowa/src/gplx/core/primitives/Int_pool.java index a27517de8..cd2d2877b 100644 --- a/400_xowa/src/gplx/core/primitives/Int_pool.java +++ b/400_xowa/src/gplx/core/primitives/Int_pool.java @@ -13,3 +13,56 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public class Int_pool { + private final List_adp available_list = List_adp_.New(); private int available_len; + // private final Bry_bfr dbg_bfr = Bry_bfr_.New(); + private int uid_max = -1; + public void Clear() { + synchronized (available_list) { + available_list.Clear(); + available_len = 0; + uid_max = -1; + } + } + public int Get_next() { + synchronized (available_list) { + if (available_len == 0) { + // dbg_bfr.Add_str("+:u:").Add_int_variable(uid_max + 1).Add_byte_nl(); + return ++uid_max; + } + else { + Int_obj_val val = (Int_obj_val)List_adp_.Pop_last(available_list); + --available_len; + // dbg_bfr.Add_str("+:a:").Add_int_variable(val.Val()).Add_byte_nl(); + return val.Val(); + } + } + } + public void Del(int v) { + if (v > uid_max) throw Err_.new_("core", "value is greater than range", "value", v, "max", uid_max); + synchronized (available_list) { + if (available_len == 0 && v == uid_max) { + --this.uid_max; + // dbg_bfr.Add_str("-:m:").Add_int_variable(v).Add_byte_nl(); + return; + } + if (available_len == uid_max) { + available_list.Add(new Int_obj_val(v)); + available_list.Sort(); + for (int i = 0; i < available_len; ++i) { + Int_obj_val itm = (Int_obj_val)available_list.Get_at(i); + if (i != itm.Val()) + throw Err_.new_("core", "available_list out of order", "contents", available_list.To_str()); + } + // dbg_bfr.Add_str("-:c:").Add_int_variable(v).Add_byte_nl(); + this.Clear(); + } + else { + // dbg_bfr.Add_str("-:a:").Add_int_variable(v).Add_byte_nl(); + available_list.Add(new Int_obj_val(v)); + ++available_len; + } + } + } +} diff --git a/400_xowa/src/gplx/core/primitives/Int_pool_tst.java b/400_xowa/src/gplx/core/primitives/Int_pool_tst.java index a27517de8..1a7699bee 100644 --- a/400_xowa/src/gplx/core/primitives/Int_pool_tst.java +++ b/400_xowa/src/gplx/core/primitives/Int_pool_tst.java @@ -13,3 +13,61 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +import org.junit.*; +public class Int_pool_tst { + private final Int_pool_tstr tstr = new Int_pool_tstr(); + @Before public void init() {tstr.Clear();} + @Test public void Get__one() { + tstr.Test_get(0); + } + @Test public void Get__many() { + tstr.Test_get(0); + tstr.Test_get(1); + tstr.Test_get(2); + } + @Test public void Del__one() { + tstr.Test_get(0); + tstr.Exec_del(0); + tstr.Test_get(0); + } + @Test public void Del__sequential() { + tstr.Test_get(0); + tstr.Test_get(1); + tstr.Test_get(2); + tstr.Exec_del(2).Test_get(2); + tstr.Exec_del(2); + tstr.Exec_del(1); + tstr.Exec_del(0).Test_get(0); + } + @Test public void Del__out_of_order() { + tstr.Test_get(0); + tstr.Test_get(1); + tstr.Test_get(2); + tstr.Exec_del(0).Test_get(0); + tstr.Exec_del(0); + tstr.Exec_del(1); + tstr.Exec_del(2); + tstr.Test_get(0); + } + @Test public void Del__out_of_order_2() { + tstr.Test_get(0); + tstr.Test_get(1); + tstr.Test_get(2); + tstr.Exec_del(1); + tstr.Exec_del(2); + tstr.Exec_del(0); + } +} +class Int_pool_tstr { + private final Int_pool pool = new Int_pool(); + public void Clear() {pool.Clear();} + public Int_pool_tstr Test_get(int expd) { + Tfds.Eq(expd, pool.Get_next()); + return this; + } + public Int_pool_tstr Exec_del(int val) { + pool.Del(val); + return this; + } +} diff --git a/400_xowa/src/gplx/core/primitives/Obj_ary_parser_base.java b/400_xowa/src/gplx/core/primitives/Obj_ary_parser_base.java index a27517de8..ea2d46a92 100644 --- a/400_xowa/src/gplx/core/primitives/Obj_ary_parser_base.java +++ b/400_xowa/src/gplx/core/primitives/Obj_ary_parser_base.java @@ -13,3 +13,44 @@ 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.core.primitives; import gplx.*; import gplx.core.*; +public abstract class Obj_ary_parser_base { + int pos_len = 4; int[] pos; + protected abstract void Ary_len_(int v); + protected abstract void Parse_itm(byte[] bry, int bgn, int end); + protected void Parse_core(byte[] bry, int bgn, int end, byte dlm_1, byte dlm_2) { + if (pos == null) pos = new int[pos_len]; + if (end - bgn == 0) { + this.Ary_len_(0); + return; + } + int pos_idx = 0; + int dlm_last = -1; // NOTE: -1 b/c dlm_2 can be 0 (Byte_ascii.Null) and need to do dlm_last != dlm_2 check below + for (int i = bgn; i < end; i++) { + byte b = bry[i]; + if (b == dlm_1 || b == dlm_2) { + if (pos_idx == pos_len - 1) { // -1 b/c pos[] will always be count_of_dlm + 1 + pos_len *= 2; + int[] pos_new = new int[pos_len]; + Array_.Copy(pos, pos_new); + pos = pos_new; + } + pos[pos_idx++] = i; + dlm_last = b; + } + } + if (dlm_last != dlm_2) + pos[pos_idx++] = end; + this.Ary_len_(pos_idx); + int parse_bgn = bgn; + for (int i = 0; i < pos_idx; i++) { + int parse_end = pos[i]; + this.Parse_itm(bry, parse_bgn, parse_end); + parse_bgn = parse_end + 1; + } + if (pos_len > 255) { // reset + pos_len = 4; + pos = null; + } + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/core/progs/rates/Gfo_rate_list.java b/400_xowa/src/gplx/core/progs/rates/Gfo_rate_list.java index a27517de8..f5da9df66 100644 --- a/400_xowa/src/gplx/core/progs/rates/Gfo_rate_list.java +++ b/400_xowa/src/gplx/core/progs/rates/Gfo_rate_list.java @@ -13,3 +13,32 @@ 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.core.progs.rates; import gplx.*; import gplx.core.*; import gplx.core.progs.*; +import gplx.core.lists.rings.*; +public class Gfo_rate_list { + private final Ring__long ring; + public Gfo_rate_list(int size) { + this.ring = new Ring__long(size * 2); // *2 to store both data and time + } + public void Clear() {ring.Clear(); cur_rate = cur_delta = 0;} + public double Cur_rate() {return cur_rate;} private double cur_rate = 0; + public double Cur_delta() {return cur_delta;} private double cur_delta; + public double Add(long data, long time) { + ring.Add(data); + ring.Add(time); + double new_rate = Cur_calc(data, time); + cur_delta = cur_rate == 0 ? new_rate : Math_.Abs_double((new_rate - cur_rate) / cur_rate); + cur_rate = new_rate; + return cur_rate; + } + private double Cur_calc(long cur_data, long cur_time) { + int len = ring.Len(); + long data_all = 0; + long time_all = 0; + for (int i = 0; i < len; i += 2) { + data_all += ring.Get_at(i); + time_all += ring.Get_at(i + 1); + } + return data_all / (time_all == 0 ? .001 : time_all); + } +} diff --git a/400_xowa/src/gplx/core/progs/rates/Gfo_rate_list_tst.java b/400_xowa/src/gplx/core/progs/rates/Gfo_rate_list_tst.java index a27517de8..7b24db9e4 100644 --- a/400_xowa/src/gplx/core/progs/rates/Gfo_rate_list_tst.java +++ b/400_xowa/src/gplx/core/progs/rates/Gfo_rate_list_tst.java @@ -13,3 +13,22 @@ 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.core.progs.rates; import gplx.*; import gplx.core.*; import gplx.core.progs.*; +import org.junit.*; import gplx.core.tests.*; +public class Gfo_rate_list_tst { + private final Gfo_rate_list_fxt fxt = new Gfo_rate_list_fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Add__1() {fxt.Add(100, 20).Test(5, 5);} + @Test public void Add__2() {fxt.Add(100, 20).Add(100, 30).Test(4, .20d);} + @Test public void Add__3() {fxt.Add(100, 20).Add(100, 30).Add(100, 50).Test(3, .25d);} + @Test public void Add__4() {fxt.Add(100, 20).Add(100, 30).Add(100, 50).Add(600, 0).Test(9, 2);} +} +class Gfo_rate_list_fxt { + private final Gfo_rate_list list = new Gfo_rate_list(6); + public void Clear() {list.Clear();} + public Gfo_rate_list_fxt Add(long data, long time) {list.Add(data, time); return this;} + public void Test(double expd_rate, double expd_delta) { + Gftest.Eq__double(expd_rate , list.Cur_rate() , "cur_rate"); + Gftest.Eq__double(expd_delta, list.Cur_delta() , "cur_delta"); + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/core/progs/rates/Gfo_rate_mgr.java b/400_xowa/src/gplx/core/progs/rates/Gfo_rate_mgr.java index a27517de8..be143c2cb 100644 --- a/400_xowa/src/gplx/core/progs/rates/Gfo_rate_mgr.java +++ b/400_xowa/src/gplx/core/progs/rates/Gfo_rate_mgr.java @@ -13,3 +13,25 @@ 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.core.progs.rates; import gplx.*; import gplx.core.*; import gplx.core.progs.*; +public class Gfo_rate_mgr { + private final Hash_adp hash = Hash_adp_.New(); + private final int dflt_size; + public Gfo_rate_mgr(int dflt_size) {this.dflt_size = dflt_size;} + public void Clear() {hash.Clear();} + public int Len() {return hash.Count();} + public Gfo_rate_list Get_or_new(String k) { + Gfo_rate_list rv = (Gfo_rate_list)hash.Get_by(k); + if (rv == null) { + rv = new Gfo_rate_list(dflt_size); + rv.Add(1024 * 1024, 1); // add default rate of 1 MB per second + hash.Add(k, rv); + } + return rv; + } + public Gfo_rate_mgr Add_new(String key) { + Gfo_rate_list rv = new Gfo_rate_list(dflt_size); + hash.Add(key, rv); + return this; + } +} diff --git a/400_xowa/src/gplx/core/scripts/Gfo_script_engine.java b/400_xowa/src/gplx/core/scripts/Gfo_script_engine.java index a27517de8..fcbbcceb1 100644 --- a/400_xowa/src/gplx/core/scripts/Gfo_script_engine.java +++ b/400_xowa/src/gplx/core/scripts/Gfo_script_engine.java @@ -13,3 +13,12 @@ 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.core.scripts; import gplx.*; import gplx.core.*; +public interface Gfo_script_engine { + void Load_script(Io_url url); + Object Get_object(String obj_name); + void Put_object(String name, Object obj); + Object Eval_script(String script); + Object Invoke_method(Object obj, String func, Object... args); + Object Invoke_function(String func, Object... args); +} diff --git a/400_xowa/src/gplx/core/scripts/Gfo_script_engine_.java b/400_xowa/src/gplx/core/scripts/Gfo_script_engine_.java index a27517de8..db46ef792 100644 --- a/400_xowa/src/gplx/core/scripts/Gfo_script_engine_.java +++ b/400_xowa/src/gplx/core/scripts/Gfo_script_engine_.java @@ -13,3 +13,17 @@ 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.core.scripts; import gplx.*; import gplx.core.*; +public class Gfo_script_engine_ { + public static Gfo_script_engine New_by_key(String key) { + if (String_.Eq(key, "javascript.java")) return new Gfo_script_engine__javascript(); + else if (String_.Eq(key, "lua.luaj")) return new Gfo_script_engine__luaj(); + else if (String_.Eq(key, "noop")) return new Gfo_script_engine__noop(); + else throw Err_.new_unhandled(key); + } + public static Gfo_script_engine New_by_ext(String ext) { + if (String_.Eq(ext, ".js")) return new Gfo_script_engine__javascript(); + else if (String_.Eq(ext, ".lua")) return new Gfo_script_engine__luaj(); + else throw Err_.new_unhandled(ext); + } +} diff --git a/400_xowa/src/gplx/core/scripts/Gfo_script_engine__javascript.java b/400_xowa/src/gplx/core/scripts/Gfo_script_engine__javascript.java index a27517de8..ad1e33214 100644 --- a/400_xowa/src/gplx/core/scripts/Gfo_script_engine__javascript.java +++ b/400_xowa/src/gplx/core/scripts/Gfo_script_engine__javascript.java @@ -13,3 +13,58 @@ 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.core.scripts; import gplx.*; import gplx.core.*; +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +public class Gfo_script_engine__javascript implements Gfo_script_engine { + private final ScriptEngine engine; + private final Invocable invk; + public Gfo_script_engine__javascript() { + ScriptEngineManager manager = new ScriptEngineManager(); + this.engine = manager.getEngineByName("JavaScript"); + this.invk = (Invocable)engine; + } + public void Load_script(Io_url url) { + try {engine.eval(Io_mgr.Instance.LoadFilStr(url));} + catch (Exception e) { + System.out.println(e.getMessage()); + Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to load_script; url=~{0} err=~{1}", url, Err_.Message_lang(e)); + } + } + public void Put_object(String key, Object val) { + engine.put(key, val); + } + public Object Get_object(String obj_name) { + try {return engine.get(obj_name);} + catch (Exception e) { + System.out.println(e.getMessage()); + Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to get object; obj_name=~{0} err=~{1}", obj_name, Err_.Message_lang(e)); + return null; + } + } + public Object Eval_script(String script) { + try {return engine.eval(script);} + catch (Exception e) { + System.out.println(e.getMessage()); + Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to eval; script=~{0} err=~{1}", script, Err_.Message_lang(e)); + return null; + } + } + public Object Invoke_method(Object obj, String func, Object... args) { + try {return invk.invokeMethod(obj, func, args);} + catch (Exception e) { + System.out.println(e.getMessage()); + Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to invoke method; method=~{0} err=~{1}", func, Err_.Message_lang(e)); + return null; + } + } + public Object Invoke_function(String func, Object... args) { + try {return invk.invokeFunction(func, args);} + catch (Exception e) { + System.out.println(e.getMessage()); + Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to invoke method; method=~{0} err=~{1}", func, Err_.Message_lang(e)); + return null; + } + } +} diff --git a/400_xowa/src/gplx/core/scripts/Gfo_script_engine__luaj.java b/400_xowa/src/gplx/core/scripts/Gfo_script_engine__luaj.java index a27517de8..3bf3a320d 100644 --- a/400_xowa/src/gplx/core/scripts/Gfo_script_engine__luaj.java +++ b/400_xowa/src/gplx/core/scripts/Gfo_script_engine__luaj.java @@ -13,3 +13,85 @@ 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.core.scripts; import gplx.*; import gplx.core.*; +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; + +import javax.script.SimpleBindings; +import javax.script.Bindings; +import javax.script.Compilable; +import javax.script.CompiledScript; + +import org.luaj.vm2.LuaFunction; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.jse.CoerceJavaToLua; +import org.luaj.vm2.lib.jse.CoerceLuaToJava; +public class Gfo_script_engine__luaj implements Gfo_script_engine { + private final ScriptEngine engine; + private final List_adp script_list = List_adp_.New(); + private SimpleBindings bindings; + public Gfo_script_engine__luaj() { + ScriptEngineManager manager = new ScriptEngineManager(); + this.engine = manager.getEngineByName("luaj"); + } + public void Load_script(Io_url url) { + try { + // create itm and add it to the list of all scripts; list is not used now, but may be used in future + String src = Io_mgr.Instance.LoadFilStr(url); + script_list.Add(new Gfo_script_itm__luaj(url, src)); + + // compile the item into the "bindings" object; this allows scripts to share one "sandbox-space", allowing funcs in one script to call funcs in another + CompiledScript compiled = ((Compilable) engine).compile(src); + if (bindings == null) bindings = new SimpleBindings(); + compiled.eval(bindings); + } + catch (Exception e) { + Warn("failed to load_script; url=~{0} err=~{1}", url, Err_.Message_lang(e)); + } + } + public void Put_object(String key, Object val) { + engine.put(key, val); + } + public Object Get_object(String obj_name) { + try {return engine.get(obj_name);} + catch (Exception e) { + Warn("failed to get object; obj_name=~{0} err=~{1}", obj_name, Err_.Message_lang(e)); + return null; + } + } + public Object Eval_script(String script) { + try {return engine.eval(script);} + catch (Exception e) { + Warn("", "", "failed to eval; script=~{0} err=~{1}", script, Err_.Message_lang(e)); + return null; + } + } + public Object Invoke_method(Object obj, String func, Object... args) { + throw Err_.new_unimplemented_w_msg("luaj does not support invocable interface"); // NOTE: cannot support with Get_object, b/c Get_object needs obj_name, and only "obj" exists + } + public Object Invoke_function(String func, Object... args) { + try { + LuaValue arg = CoerceJavaToLua.coerce(args[0]); + LuaFunction lfunc = (LuaFunction)bindings.get(func); + LuaValue rv = lfunc.call(arg); + return CoerceLuaToJava.coerce(rv, Object.class); + } + catch (Exception e) { + Gfo_script_engine__luaj.Warn("", "", "failed to invoke method; method=~{0} err=~{1}", func, Err_.Message_lang(e)); + return null; + } + } + public static void Warn(String fmt, Object... args) { + String msg = Gfo_usr_dlg_.Instance.Warn_many("", "", fmt, args); + System.out.println(msg); + } +} +class Gfo_script_itm__luaj { + public Gfo_script_itm__luaj(Io_url url, String src) { + this.url = url; + this.src = src; + } + public Io_url Url() {return url;} private final Io_url url; + public String Src() {return src;} private final String src; +} diff --git a/400_xowa/src/gplx/core/scripts/Gfo_script_engine__noop.java b/400_xowa/src/gplx/core/scripts/Gfo_script_engine__noop.java index a27517de8..41eeebbcd 100644 --- a/400_xowa/src/gplx/core/scripts/Gfo_script_engine__noop.java +++ b/400_xowa/src/gplx/core/scripts/Gfo_script_engine__noop.java @@ -13,3 +13,12 @@ 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.core.scripts; import gplx.*; import gplx.core.*; +public class Gfo_script_engine__noop implements Gfo_script_engine { + public void Load_script(Io_url url) {} + public Object Get_object(String obj_name) {return null;} + public void Put_object(String name, Object obj) {} + public Object Eval_script(String script) {return null;} + public Object Invoke_method(Object obj, String func, Object... args) {return null;} + public Object Invoke_function(String func, Object... args) {return null;} +} diff --git a/400_xowa/src/gplx/core/security/files/Cksum_itm.java b/400_xowa/src/gplx/core/security/files/Cksum_itm.java index a27517de8..772f665bc 100644 --- a/400_xowa/src/gplx/core/security/files/Cksum_itm.java +++ b/400_xowa/src/gplx/core/security/files/Cksum_itm.java @@ -13,3 +13,17 @@ 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.core.security.files; import gplx.*; import gplx.core.*; import gplx.core.security.*; +public class Cksum_itm implements gplx.core.brys.Bry_bfr_able { + public Cksum_itm(byte[] hash, Io_url file_url, long file_size) { + this.Hash = hash; this.File_url = file_url; this.File_size = file_size; + } + public final byte[] Hash; + public final Io_url File_url; + public final long File_size; + public void To_bfr(Bry_bfr bfr) { + bfr.Add(Hash).Add_byte_pipe(); + bfr.Add_str_u8(File_url.Raw()).Add_byte_pipe(); + bfr.Add_long_variable(File_size); + } +} diff --git a/400_xowa/src/gplx/core/security/files/Cksum_list.java b/400_xowa/src/gplx/core/security/files/Cksum_list.java index a27517de8..a981c239e 100644 --- a/400_xowa/src/gplx/core/security/files/Cksum_list.java +++ b/400_xowa/src/gplx/core/security/files/Cksum_list.java @@ -13,3 +13,70 @@ 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.core.security.files; import gplx.*; import gplx.core.*; import gplx.core.security.*; +public class Cksum_list { + public Cksum_list(byte type, Cksum_itm[] itms, long itms_size) { + this.Type = type; this.Itms = itms; this.Itms_size = itms_size; + } + public final byte Type; + public final Cksum_itm[] Itms; + public long Itms_size; + public Io_url[] Itms_ary() { + int len = Itms.length; + Io_url[] rv = new Io_url[len]; + for (int i = 0; i < len; ++i) + rv[i] = Itms[i].File_url; + return rv; + } + + public static Cksum_list Parse_by_fil(Io_url url) { + byte tid = Get_hash_tid_by_ext(url.Ext()); + return Cksum_list.Parse(tid, url.OwnerDir(), Io_mgr.Instance.LoadFilBry(url)); + } + public static Cksum_list Parse(byte type, Io_url owner_dir, byte[] bry) { + List_adp list = List_adp_.New(); + + byte[][] lines = Bry_split_.Split_lines(bry); + int len = lines.length; + long itms_size = 0; + for (int i = 0; i < len; ++i) { + byte[] line = lines[i]; // EX: "d41d8cd98f00b204e9800998ecf8427e *file.txt" + + // get hash + int space_pos = Bry_find_.Find_fwd(line, Byte_ascii.Space); + if (space_pos == Bry_find_.Not_found) throw Err_.new_("chsum", "checksum line does not have space", "line", line); + byte[] hash = Bry_.Mid(line, 0, space_pos); + + // get file + int file_bgn = space_pos + 1; + if (line[file_bgn] == Byte_ascii.Star) ++file_bgn; // ignore leading *; EX: "*file.txt" -> "file.txt" + byte[] file = Bry_.Mid(line, file_bgn); + Io_url file_url = GenSubFil_nest(owner_dir, file); + long file_size = Io_mgr.Instance.QueryFil(file_url).Size(); + itms_size += file_size; + + // add to list + Cksum_itm itm = new Cksum_itm(hash, file_url, file_size); + list.Add(itm); + } + return new Cksum_list(type, (Cksum_itm[])list.To_ary_and_clear(Cksum_itm.class), itms_size); + } + private static Io_url GenSubFil_nest(Io_url dir, byte[] src) { // split "a/b/c" or "a\b\c" -> [a, b, c] + byte dir_spr = gplx.core.envs.Op_sys.Lnx.Fsys_dir_spr_byte(); + int dir_pos = Bry_find_.Find_fwd(src, dir_spr); + if (dir_pos == Bry_find_.Not_found) { + dir_spr = gplx.core.envs.Op_sys.Wnt.Fsys_dir_spr_byte(); + dir_pos = Bry_find_.Find_fwd(src, dir_spr); + if (dir_pos == Bry_find_.Not_found) + return dir.GenSubFil(String_.new_u8(src)); + } + byte[][] parts = Bry_split_.Split(src, dir_spr); + return dir.GenSubFil_nest(String_.Ary(parts)); + } + private static byte Get_hash_tid_by_ext(String ext) { + if (String_.Eq(ext, ".md5")) return Hash_algo_.Tid__md5; + else if (String_.Eq(ext, ".sha1")) return Hash_algo_.Tid__sha1; + else if (String_.Eq(ext, ".sha256")) return Hash_algo_.Tid__sha2_256; + else throw Err_.new_unhandled_default(ext); + } +} diff --git a/400_xowa/src/gplx/core/security/files/Cksum_list_tst.java b/400_xowa/src/gplx/core/security/files/Cksum_list_tst.java index a27517de8..3977e6f0d 100644 --- a/400_xowa/src/gplx/core/security/files/Cksum_list_tst.java +++ b/400_xowa/src/gplx/core/security/files/Cksum_list_tst.java @@ -13,3 +13,36 @@ 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.core.security.files; import gplx.*; import gplx.core.*; import gplx.core.security.*; +import org.junit.*; import gplx.core.tests.*; +public class Cksum_list_tst { + private final Cksum_list_fxt fxt = new Cksum_list_fxt(); + @Test public void Basic() { + fxt.Init__file("a.txt").Init__file("ab.txt"); + fxt.Test__parse(String_.Concat_lines_nl_skip_last + ( "a5e54d1fd7bb69a228ef0dcd2431367e *a.txt" + , "90f15b7ca11bd4c70d9047cd29a80040 *ab.txt" + ), 11 + , fxt.Make__itm("a5e54d1fd7bb69a228ef0dcd2431367e", "a.txt", 5) + , fxt.Make__itm("90f15b7ca11bd4c70d9047cd29a80040", "ab.txt", 6) + ); + } +} +class Cksum_list_fxt { + private final Io_url dir = Io_url_.mem_dir_("mem/dir/"); + public Cksum_list_fxt() { + Io_mgr.Instance.InitEngine_mem(); + } + public Cksum_list_fxt Init__file(String fil_name) { + Io_url fil_url = dir.GenSubFil(fil_name); + Io_mgr.Instance.SaveFilStr(fil_url, fil_name); + return this; + } + public Cksum_itm Make__itm(String hash, String file_name, long size) {return new Cksum_itm(Bry_.new_u8(hash), dir.GenSubFil(file_name), size);} + public Cksum_list_fxt Test__parse(String raw, long expd_size, Cksum_itm... expd_itms) { + Cksum_list actl_list = Cksum_list.Parse(gplx.core.security.Hash_algo_.Tid__md5, dir, Bry_.new_u8(raw)); + Gftest.Eq__long(expd_size, actl_list.Itms_size); + Gftest.Eq__ary(expd_itms, actl_list.Itms); + return this; + } +} diff --git a/400_xowa/src/gplx/core/tests/Tst_chkr.java b/400_xowa/src/gplx/core/tests/Tst_chkr.java index a27517de8..ad6619b4c 100644 --- a/400_xowa/src/gplx/core/tests/Tst_chkr.java +++ b/400_xowa/src/gplx/core/tests/Tst_chkr.java @@ -13,3 +13,16 @@ 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.core.tests; import gplx.*; import gplx.core.*; +public interface Tst_chkr { + Class TypeOf(); + int Chk(Tst_mgr mgr, String path, Object actl); +} +class Tst_chkr_null implements Tst_chkr { + public Class TypeOf() {return Object.class;} + public int Chk(Tst_mgr mgr, String path, Object actl) { + mgr.Results().Add(Tst_itm.fail_("!=", path, "", "", Type_.Name_by_obj(actl))); +// mgr.Results().Add(Tst_itm.fail_("!=", path, "", "", Object_.Xto_str_strict_or_null(actl))); + return 1; + } +} diff --git a/400_xowa/src/gplx/core/tests/Tst_mgr.java b/400_xowa/src/gplx/core/tests/Tst_mgr.java index a27517de8..836a897a9 100644 --- a/400_xowa/src/gplx/core/tests/Tst_mgr.java +++ b/400_xowa/src/gplx/core/tests/Tst_mgr.java @@ -13,3 +13,120 @@ 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.core.tests; import gplx.*; import gplx.core.*; +import gplx.core.strings.*; +public class Tst_mgr { + public Tst_mgr ThrowError_n_() {throwError = false; return this;} private boolean throwError = true; + public List_adp Results() {return results;} List_adp results = List_adp_.New(); + public Keyval_hash Vars() {return vars;} Keyval_hash vars = new Keyval_hash(); + public Object Vars_get_by_key(String key) {return vars.Get_val_or(key, null);} + public String Vars_get_bry_as_str(String key, int bgn, int end) { + byte[] bry = (byte[])vars.Get_val_or(key, null); if (bry == null) return String_.Empty; + if (bgn < 0 || end > bry.length || end < bgn || end < 0) return "<>"; + return String_.new_u8(Bry_.Mid(bry, bgn, end)); + } + public int Tst_val(boolean skip, String path, String name, Object expd, Object actl) { + Tst_itm itm = Tst_itm.eq_(skip, path, name, expd, actl); + results.Add(itm); + return itm.Pass() ? 0 : 1; + } + public int Tst_val_ary(boolean skip, String path, String name, Object expd, Object actl) { + Tst_itm itm = Tst_itm.eq_(skip, path, name, To_str(expd), To_str(actl)); + results.Add(itm); + return itm.Pass() ? 0 : 1; + } + public void Tst_obj(Tst_chkr expd, Object actl) { + results.Clear(); + int err = Tst_sub_obj(expd, actl, "", 0); + if (throwError && err > 0) throw Err_.new_wo_type(Build()); + } + public void Tst_ary(String ownerPath, Tst_chkr[] expd_ary, Object[] actl_ary) { + results.Clear(); + Tst_ary_inner(ownerPath, expd_ary, actl_ary); + } + private void Tst_ary_inner(String ownerPath, Tst_chkr[] expd_ary, Object[] actl_ary) { + int expd_ary_len = expd_ary.length, actl_ary_len = actl_ary.length; + int max_len = expd_ary_len > actl_ary_len ? expd_ary_len : actl_ary_len; + int err = 0; + for (int i = 0; i < max_len; i++) { + String path = ownerPath + Int_.To_str(i); + Tst_chkr expd_obj = i < expd_ary_len ? expd_ary[i] : Tst_mgr.Null_chkr; + Object actl_obj = i < actl_ary_len ? actl_ary[i] : ""; + String actl_type = i < actl_ary_len ? Type_.Name_by_obj(actl_obj) : ""; + err += Tst_inner(expd_obj, actl_obj, actl_type, path, err); + } + if (throwError && err > 0) { + String s = Build(); + throw Err_.new_wo_type(s); + } + } + public int Tst_sub_obj(Tst_chkr expd, Object actl, String path, int err) { + return Tst_inner(expd, actl, actl == null ? "" : Type_.Name_by_obj(actl), path, err); + } + public int Tst_sub_ary(Tst_chkr[] expd_subs, Object[] actl_subs, String path, int err) { + Tst_ary_inner(path + ".", expd_subs, actl_subs); + return err; + } + int Tst_inner(Tst_chkr expd_obj, Object actl_obj, String actl_type, String path, int err) { + if (actl_obj == null || !Type_.Is_assignable_from(expd_obj.TypeOf(), actl_obj.getClass())) { + results.Add(Tst_itm.fail_("!=", path, "", Type_.Name(expd_obj.TypeOf()), actl_type)); + return 1; +// results.Add(Tst_itm.fail_("!=", path, "", Object_.Xto_str_strict_or_null(expd_obj.ValueOf()), Object_.Xto_str_strict_or_null(actl_obj))); + } + else { + return expd_obj.Chk(this, path, actl_obj); + } + } + String To_str(Object ary) { + if (ary == null) return ""; + int len = Array_.Len(ary); + for (int i = 0; i < len; i++) { + Object itm = Array_.Get_at(ary, i); + ary_sb.Add(Object_.Xto_str_strict_or_null_mark(itm)).Add(","); + } + return ary_sb.To_str_and_clear(); + } String_bldr ary_sb = String_bldr_.new_(); + String Build() { + String_bldr sb = String_bldr_.new_(); + int comp_max = 0, path_max =0, name_max = 0; + int len = results.Count(); + for (int i = 0; i < len; i++) { + Tst_itm itm = (Tst_itm)results.Get_at(i); + comp_max = Max(comp_max, itm.Comp()); + path_max = Max(path_max, itm.Path()); + name_max = Max(name_max, itm.Name()); + } + for (int i = 0; i < len; i++) { + Tst_itm itm = (Tst_itm)results.Get_at(i); + sb.Add_fmt("\n{0} {1} {2} '{3}'", String_.PadEnd(itm.Comp(), comp_max, " "), "#" + String_.PadEnd(itm.Path(), path_max, " "), "@" + String_.PadEnd(itm.Name(), name_max, " ") + ":", itm.Expd()); + if (!itm.Pass()) + sb.Add_fmt("\n{0} {1} {2} '{3}'", String_.PadEnd("", comp_max, " "), " " + String_.PadEnd("", path_max, " "), " " + String_.PadEnd("", name_max, " ") + " ", itm.Actl()); + } + return sb.To_str_and_clear(); + } + int Max(int max, String s) {int len = String_.Len(s); return len > max ? len : max;} + public static final Tst_chkr Null_chkr = new Tst_chkr_null(); +} +class Tst_itm { + public boolean Pass() {return pass;} private boolean pass; + public boolean Skip() {return skip;} private boolean skip; + public String Comp() {return comp;} public Tst_itm Comp_(String v) {comp = v; return this;} private String comp = ""; + public String Path() {return path;} public Tst_itm Path_(String v) {path = v; return this;} private String path = ""; + public String Name() {return name;} public Tst_itm Name_(String v) {name = v; return this;} private String name = ""; + public String Expd() {return expd;} public Tst_itm Expd_(String v) {expd = v; return this;} private String expd = ""; + public String Actl() {return actl;} public Tst_itm Actl_(String v) {actl = v; return this;} private String actl = ""; + public static Tst_itm eq_(boolean skip, String path, String name, Object expd, Object actl) { + boolean pass = skip ? true : Object_.Eq(expd, actl); + String comp = pass ? "==" : "!="; + String expd_str = Object_.Xto_str_strict_or_null_mark(expd); + String actl_str = Object_.Xto_str_strict_or_null_mark(actl); + if (skip) expd_str = actl_str; + return new_(skip, pass, comp, path, name, expd_str, actl_str); + } + public static Tst_itm fail_(String comp, String path, String name, String expd, String actl) {return new_(false, false, comp, path, name, expd, actl);} + public static Tst_itm new_(boolean skip, boolean pass, String comp, String path, String name, String expd, String actl) { + Tst_itm rv = new Tst_itm(); + rv.skip = skip; rv.pass = pass; rv.comp = comp; rv.path = path; rv.name = name;; rv.expd = expd; rv.actl = actl; + return rv; + } +} diff --git a/400_xowa/src/gplx/core/threads/Gfo_async_cmd_itm.java b/400_xowa/src/gplx/core/threads/Gfo_async_cmd_itm.java index a27517de8..0db2b541d 100644 --- a/400_xowa/src/gplx/core/threads/Gfo_async_cmd_itm.java +++ b/400_xowa/src/gplx/core/threads/Gfo_async_cmd_itm.java @@ -13,3 +13,28 @@ 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.core.threads; import gplx.*; import gplx.core.*; +public class Gfo_async_cmd_itm implements Gfo_invk { + private Gfo_invk invk; private String invk_key; private GfoMsg msg = GfoMsg_.new_cast_(""); + public Gfo_async_cmd_itm Init(Gfo_invk invk, String invk_key, Object... args) { + this.invk = invk; this.invk_key = invk_key; + msg.Args_reset(); + msg.Clear(); + int len = args.length; + for (int i = 0; i < len; i += 2) { + String key = (String)args[i]; + Object val = args[i + 1]; + msg.Add(key, val); + } + return this; + } + public void Exec() { + Gfo_invk_.Invk_by_msg(invk, invk_key, msg); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_exec)) Exec(); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_exec = "exec"; + public static final Gfo_async_cmd_itm[] Ary_empty = new Gfo_async_cmd_itm[0]; +} diff --git a/400_xowa/src/gplx/core/threads/Gfo_async_cmd_mkr.java b/400_xowa/src/gplx/core/threads/Gfo_async_cmd_mkr.java index a27517de8..533c38a71 100644 --- a/400_xowa/src/gplx/core/threads/Gfo_async_cmd_mkr.java +++ b/400_xowa/src/gplx/core/threads/Gfo_async_cmd_mkr.java @@ -13,3 +13,20 @@ 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.core.threads; import gplx.*; import gplx.core.*; +class Gfo_async_cmd_mkr { +// private Gfo_async_cmd_itm[] free = Gfo_async_cmd_itm.Ary_empty, used = Gfo_async_cmd_itm.Ary_empty; +// private int free_bgn = 0, free_end = 0, ary_len = 0; +// public void Resize(int v) { +// free = (Gfo_async_cmd_itm[])Array_.Resize(free, v); +// used = (Gfo_async_cmd_itm[])Array_.Resize(used, v); +// ary_len = v; +// } + public Gfo_async_cmd_itm Get(Gfo_invk invk, String invk_key, Object... args) { + Gfo_async_cmd_itm rv = new Gfo_async_cmd_itm(); + rv.Init(invk, invk_key, args); + return rv; + } + public void Rls(Gfo_async_cmd_itm cmd) { + } +} diff --git a/400_xowa/src/gplx/core/threads/Gfo_async_mgr.java b/400_xowa/src/gplx/core/threads/Gfo_async_mgr.java index a27517de8..dca7ca086 100644 --- a/400_xowa/src/gplx/core/threads/Gfo_async_mgr.java +++ b/400_xowa/src/gplx/core/threads/Gfo_async_mgr.java @@ -13,3 +13,44 @@ 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.core.threads; import gplx.*; import gplx.core.*; +import gplx.core.primitives.*; +public class Gfo_async_mgr implements Gfo_invk { + private List_adp queue = List_adp_.New(); + private Bool_obj_ref running = Bool_obj_ref.n_(); + private Gfo_async_cmd_mkr cmd_mkr = new Gfo_async_cmd_mkr(); + public void Queue(Gfo_invk invk, String invk_key, Object... args) { + Gfo_async_cmd_itm cmd = cmd_mkr.Get(invk, invk_key, args); + synchronized (queue) { + queue.Add(cmd); + } + synchronized (running) { + if (running.Val_n()) { + running.Val_y_(); + Thread_adp_.Start_by_key(Invk_run, this, Invk_run); + } + } + } + public void Run() { + Gfo_async_cmd_itm cmd = null; + try { + while (true) { + synchronized (queue) { + if (queue.Count() == 0) break; + cmd = (Gfo_async_cmd_itm)List_adp_.Pop(queue); + cmd.Exec(); + } + } + } + finally { + synchronized (running) { + running.Val_n_(); + } + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_run)) Run(); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_run = "run"; +} diff --git a/400_xowa/src/gplx/core/threads/Gfo_thread_cmd.java b/400_xowa/src/gplx/core/threads/Gfo_thread_cmd.java index a27517de8..793f91eed 100644 --- a/400_xowa/src/gplx/core/threads/Gfo_thread_cmd.java +++ b/400_xowa/src/gplx/core/threads/Gfo_thread_cmd.java @@ -13,3 +13,16 @@ 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.core.threads; import gplx.*; import gplx.core.*; +public interface Gfo_thread_cmd extends Gfo_invk { + void Cmd_ctor(); + String Async_key(); + int Async_sleep_interval(); + boolean Async_prog_enabled(); + void Async_prog_run(int async_sleep_sum); + byte Async_init(); + boolean Async_term(); + void Async_run(); + boolean Async_running(); + Gfo_thread_cmd Async_next_cmd(); void Async_next_cmd_(Gfo_thread_cmd next); +} diff --git a/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_.java b/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_.java index a27517de8..80913dcc2 100644 --- a/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_.java +++ b/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_.java @@ -13,3 +13,8 @@ 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.core.threads; import gplx.*; import gplx.core.*; +public class Gfo_thread_cmd_ { + public static final int Async_sleep_interval_1_second = 1000; + public static final byte Init_ok = 0, Init_cancel_step = 1, Init_cancel_all = 2; +} diff --git a/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_base.java b/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_base.java index a27517de8..534fee79b 100644 --- a/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_base.java +++ b/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_base.java @@ -13,3 +13,28 @@ 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.core.threads; import gplx.*; import gplx.core.*; +import gplx.core.brys.fmtrs.*; +import gplx.gfui.*; import gplx.gfui.kits.core.*; +public class Gfo_thread_cmd_base implements Gfo_thread_cmd { + @gplx.Virtual public String Async_key() {return "undefined";} + public void Cmd_ctor() {} + public Gfo_thread_cmd_base Ctor(Gfo_usr_dlg usr_dlg, Gfui_kit kit) {this.usr_dlg = usr_dlg; this.kit = kit; return this;} protected Gfo_usr_dlg usr_dlg; protected Gfui_kit kit; + public Gfo_invk Owner() {return owner;} public Gfo_thread_cmd_base Owner_(Gfo_invk v) {owner = v; return this;} Gfo_invk owner; + public Bry_fmtr_eval_mgr Url_eval_mgr() {return url_eval_mgr;} public Gfo_thread_cmd_base Url_eval_mgr_(Bry_fmtr_eval_mgr v) {url_eval_mgr = v; return this;} Bry_fmtr_eval_mgr url_eval_mgr; + public Gfo_thread_cmd Async_next_cmd() {return next_cmd;} public void Async_next_cmd_(Gfo_thread_cmd v) {next_cmd = v;} Gfo_thread_cmd next_cmd; + @gplx.Virtual public int Async_sleep_interval() {return Gfo_thread_cmd_.Async_sleep_interval_1_second;} + @gplx.Virtual public boolean Async_prog_enabled() {return false;} + @gplx.Virtual public byte Async_init() {return Gfo_thread_cmd_.Init_ok;} + @gplx.Virtual public boolean Async_term() {return true;} + @gplx.Virtual public void Async_prog_run(int async_sleep_sum) {} + @gplx.Virtual public void Async_bgn() {} + @gplx.Virtual public boolean Async_running() {return false;} + @gplx.Virtual public void Async_run() {} + @gplx.Virtual public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_owner)) return owner; + else if (ctx.Match(k, Invk_async_bgn)) Async_bgn(); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_owner = "owner", Invk_async_bgn = "async_bgn"; +} diff --git a/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_download.java b/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_download.java index a27517de8..aa9ca5e4c 100644 --- a/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_download.java +++ b/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_download.java @@ -13,3 +13,60 @@ 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.core.threads; import gplx.*; import gplx.core.*; +import gplx.core.brys.fmtrs.*; +import gplx.gfui.*; import gplx.gfui.kits.core.*; +public class Gfo_thread_cmd_download implements Gfo_thread_cmd { + public Gfo_thread_cmd Ctor(Gfo_usr_dlg usr_dlg, Gfui_kit kit) {this.usr_dlg = usr_dlg; this.kit = kit; xrg.Prog_dlg_(usr_dlg); return this;} + public Gfo_thread_cmd_download Init(String prog_fmt_hdr, String src, Io_url trg) { + this.src = src; this.trg = trg; + xrg.Prog_fmt_hdr_(prog_fmt_hdr).Init(src, trg); + return this; + } String src; protected Gfui_kit kit; Gfo_usr_dlg usr_dlg; Io_url trg; + public Gfo_invk Owner() {return owner;} public Gfo_thread_cmd_download Owner_(Gfo_invk v) {owner = v; return this;} Gfo_invk owner; + public Bry_fmtr_eval_mgr Url_eval_mgr() {return url_eval_mgr;} public Gfo_thread_cmd_download Url_eval_mgr_(Bry_fmtr_eval_mgr v) {url_eval_mgr = v; return this;} Bry_fmtr_eval_mgr url_eval_mgr; + public void Cmd_ctor() {} + public Gfo_thread_cmd Async_next_cmd() {return next_cmd;} public void Async_next_cmd_(Gfo_thread_cmd v) {next_cmd = v;} Gfo_thread_cmd next_cmd; + @gplx.Virtual public String Async_key() {return KEY;} + public int Async_sleep_interval() {return Gfo_thread_cmd_.Async_sleep_interval_1_second;} + public boolean Async_prog_enabled() {return false;} + @gplx.Virtual public byte Async_init() { + if (Io_mgr.Instance.ExistsFil(trg)) { + int rslt = kit.Ask_yes_no_cancel(GRP_KEY, "target_exists", "Target file already exists: '~{0}'.\nDo you want to delete it?", trg.Raw()); + switch (rslt) { + case Gfui_dlg_msg_.Btn_yes: Io_mgr.Instance.DeleteFil(trg); break; + case Gfui_dlg_msg_.Btn_no: return Gfo_thread_cmd_.Init_cancel_step; + case Gfui_dlg_msg_.Btn_cancel: return Gfo_thread_cmd_.Init_cancel_all; + default: throw Err_.new_unhandled(rslt); + } + } + usr_dlg.Prog_many(GRP_KEY, "download.bgn", "contacting web server: '~{0}'", src); // update progress; some servers (like WMF dump servers) are slow to respond + return Gfo_thread_cmd_.Init_ok; + } + public boolean Async_term() { + usr_dlg.Prog_many(GRP_KEY, "clear", ""); + return download_pass; + } + public void Async_prog_run(int async_sleep_sum) {} + public boolean Async_running() {return xrg.Prog_running();} + public void Async_run() {Thread_adp_.Start_by_key(gplx.xowa.apps.Xoa_thread_.Key_bldr_download, this, Invk_async_bgn);} + private void Download() { + download_pass = true; + if (!xrg.Exec()) { + xrg.Prog_running_(false); + download_pass = false; + kit.Ask_ok(GRP_KEY, "download.fail", "download failed. Please select 'read from file' if you've already downloaded a dump: url=~{0} error=~{1}", src, xrg.Rslt_err_str()); + } + } boolean download_pass = true; + protected gplx.core.ios.IoEngine_xrg_downloadFil xrg = Io_mgr.Instance.DownloadFil_args("", Io_url_.Empty); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_async_bgn)) Download(); + else if (ctx.Match(k, Invk_owner)) return owner; + else if (ctx.Match(k, Invk_src_)) src = m.ReadStr("v"); + else if (ctx.Match(k, Invk_trg_)) trg = Bry_fmtr_eval_mgr_.Eval_url(url_eval_mgr, m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_async_bgn = "async_bgn", Invk_owner = "owner", Invk_src_ = "src_", Invk_trg_ = "trg_"; + static final String GRP_KEY = "gfo.thread.file.download"; + public static final String KEY = "file.download"; +} diff --git a/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_replace.java b/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_replace.java index a27517de8..c5ac8720c 100644 --- a/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_replace.java +++ b/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_replace.java @@ -13,3 +13,48 @@ 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.core.threads; import gplx.*; import gplx.core.*; +import gplx.core.brys.fmtrs.*; +import gplx.gfui.*; import gplx.gfui.kits.core.*; +public class Gfo_thread_cmd_replace implements Gfo_thread_cmd { + public Gfo_thread_cmd Init(Gfo_usr_dlg usr_dlg, Gfui_kit kit, Io_url fil) { + this.usr_dlg = usr_dlg; this.kit = kit; this.fil = fil; + return this; + } Gfui_kit kit; Gfo_usr_dlg usr_dlg; Io_url fil; + public Gfo_invk Owner() {return owner;} public Gfo_thread_cmd_replace Owner_(Gfo_invk v) {owner = v; return this;} Gfo_invk owner; + public Bry_fmtr_eval_mgr Url_eval_mgr() {return url_eval_mgr;} public Gfo_thread_cmd_replace Url_eval_mgr_(Bry_fmtr_eval_mgr v) {url_eval_mgr = v; return this;} Bry_fmtr_eval_mgr url_eval_mgr; + public String Async_key() {return KEY;} + public void Cmd_ctor() {} + public Gfo_thread_cmd Async_next_cmd() {return next_cmd;} public void Async_next_cmd_(Gfo_thread_cmd v) {next_cmd = v;} Gfo_thread_cmd next_cmd; + public int Async_sleep_interval() {return Gfo_thread_cmd_.Async_sleep_interval_1_second;} + public boolean Async_prog_enabled() {return false;} + @gplx.Virtual public byte Async_init() { + if (!Io_mgr.Instance.ExistsFil(fil)) {kit.Ask_ok(GRP_KEY, "file_missing", "File does not exist: '~{0}'", fil.Raw()); return Gfo_thread_cmd_.Init_cancel_step;} + return Gfo_thread_cmd_.Init_ok; + } + public boolean Async_term() {return true;} + public void Async_prog_run(int async_sleep_sum) {} + public boolean Async_running() {return false;} + @gplx.Virtual public void Async_run() {Exec_find_replace();} // NOTE: do not run async; if multiple commands for same file then they will not always work + public void Exec_find_replace() { + String raw = Io_mgr.Instance.LoadFilStr(fil); + int pairs_len = pairs.Count(); + for (int i = 0; i < pairs_len; i++) { + Keyval kv = (Keyval)pairs.Get_at(i); + raw = String_.Replace(raw, kv.Key(), kv.Val_to_str_or_null()); + } + Io_mgr.Instance.SaveFilStr(fil, raw); + usr_dlg.Prog_many(GRP_KEY, "done", "replace completed: ~{0} ~{1}", fil.Raw(), pairs_len); + } + public List_adp pairs = List_adp_.New(); + @gplx.Virtual public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_async_bgn)) Exec_find_replace(); + else if (ctx.Match(k, Invk_owner)) return owner; + else if (ctx.Match(k, Invk_fil_)) fil = Bry_fmtr_eval_mgr_.Eval_url(url_eval_mgr, m.ReadBry("v")); + else if (ctx.Match(k, Invk_add)) pairs.Add(Keyval_.new_(m.ReadStr("find"), m.ReadStr("replace"))); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_async_bgn = "async_bgn", Invk_owner = "owner", Invk_fil_ = "fil_", Invk_add = "add"; + static final String GRP_KEY = "gfo.thread.file.download"; + public static final String KEY = "text.replace"; +} diff --git a/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_unzip.java b/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_unzip.java index a27517de8..89ecaa775 100644 --- a/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_unzip.java +++ b/400_xowa/src/gplx/core/threads/Gfo_thread_cmd_unzip.java @@ -13,3 +13,102 @@ 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.core.threads; import gplx.*; import gplx.core.*; +import gplx.core.brys.fmtrs.*; import gplx.core.envs.*; +import gplx.gfui.*; import gplx.gfui.kits.core.*; import gplx.xowa.bldrs.cmds.utils.*; +public class Gfo_thread_cmd_unzip implements Gfo_thread_cmd { + public Gfo_thread_cmd_unzip Init(Gfo_usr_dlg usr_dlg, Gfui_kit kit, Process_adp bzip2_process, Process_adp zip_process, Process_adp gz_process, Io_url src, Io_url trg) { + this.src = src; this.trg = trg; this.kit = kit; this.usr_dlg = usr_dlg; + unzip_wkr = new Xob_unzip_wkr().Init(bzip2_process, zip_process, gz_process).Process_run_mode_(Process_adp.Run_mode_async); + return this; + } private Io_url src, trg; private Gfui_kit kit; private Gfo_usr_dlg usr_dlg; private Xob_unzip_wkr unzip_wkr; + public Gfo_invk Owner() {return owner;} public Gfo_thread_cmd_unzip Owner_(Gfo_invk v) {owner = v; return this;} Gfo_invk owner; + public void Cmd_ctor() {} + @gplx.Virtual public String Async_key() {return KEY;} + public Gfo_thread_cmd Async_next_cmd() {return next_cmd;} public void Async_next_cmd_(Gfo_thread_cmd v) {next_cmd = v;} Gfo_thread_cmd next_cmd; + public Bry_fmtr_eval_mgr Url_eval_mgr() {return url_eval_mgr;} public Gfo_thread_cmd_unzip Url_eval_mgr_(Bry_fmtr_eval_mgr v) {url_eval_mgr = v; return this;} Bry_fmtr_eval_mgr url_eval_mgr; + public int Async_sleep_interval() {return Gfo_thread_cmd_.Async_sleep_interval_1_second;} + public boolean Async_prog_enabled() {return true;} + public void Async_prog_run(int async_sleep_sum) { + String size_str = " please wait..."; + if (trg.Type_fil()) size_str = gplx.core.ios.Io_size_.To_str(Io_mgr.Instance.QueryFil(trg).Size()); + usr_dlg.Prog_many(GRP_KEY, "unzip", "unzipping: ~{0}", size_str); + } + @gplx.Virtual public byte Async_init() { + if (!Io_mgr.Instance.ExistsFil(src)) { + kit.Ask_ok(GRP_KEY, "source_missing", "Source file does not exist: '~{0}'", src.Raw()); + return Gfo_thread_cmd_.Init_cancel_step; + } + trg_is_dir = trg.Type_dir(); + if (delete_trg_if_exists + && (( trg_is_dir && Io_mgr.Instance.ExistsDir(trg)) + || (!trg_is_dir && Io_mgr.Instance.ExistsFil(trg))) + ) { + int rslt = kit.Ask_yes_no_cancel(GRP_KEY, "target_exists", "Target file already exists: '~{0}'.\nDo you want to delete it?", trg.Raw()); + switch (rslt) { + case Gfui_dlg_msg_.Btn_yes: if (trg_is_dir) Io_mgr.Instance.DeleteDirDeep(trg); else Io_mgr.Instance.DeleteFil(trg); break; + case Gfui_dlg_msg_.Btn_no: return Gfo_thread_cmd_.Init_cancel_step; + case Gfui_dlg_msg_.Btn_cancel: return Gfo_thread_cmd_.Init_cancel_all; + } + } + return Gfo_thread_cmd_.Init_ok; + } + public boolean Async_running() {return unzip_wkr.Process_exit_code() == Process_adp.Exit_init;} + public void Async_run() { + usr_dlg.Prog_many(GRP_KEY, "bgn", "unzipping"); + unzip_wkr.Decompress(src, trg); + } + public boolean Async_term() { + if (rename_dir) { + Io_url[] dirs = Io_mgr.Instance.QueryDir_args(trg.OwnerDir()).DirOnly_().Recur_(false).ExecAsUrlAry(); + int dirs_len = dirs.length; + Io_url zip_dir = Io_url_.Empty; + for (int i = 0; i < dirs_len; i++) { + Io_url dir = dirs[i]; + if (String_.Has_at_bgn(String_.Lower(dir.NameOnly()), String_.Lower(trg.NameOnly()))) { // HACK: check that directory starts with archive name; DATE:2013-12-22 + zip_dir = dir; + break; + } + } + if (zip_dir == Io_url_.Empty) { + kit.Ask_ok(GRP_KEY, "rename.fail", "unable to find directory: trg=~{0}", trg.Raw()); + return false; + } + if (!String_.Eq(String_.Lower(zip_dir.Raw()), String_.Lower(trg.Raw()))) // HACK: inkscape is itself + Io_mgr.Instance.MoveDirDeep(zip_dir, trg); + } + switch (term_cmd_for_src) { + case Term_cmd_for_src_noop: break; + case Term_cmd_for_src_delete: Io_mgr.Instance.DeleteFil(src); break; + case Term_cmd_for_src_move: + if (term_cmd_for_src_url == Io_url_.Empty) throw Err_.new_wo_type("move specified, but no url"); + Io_mgr.Instance.MoveFil_args(src, term_cmd_for_src_url, true).Exec(); + break; + default: throw Err_.new_unhandled(term_cmd_for_src); + } + usr_dlg.Prog_many(GRP_KEY, "done", ""); + return true; + } + public static final byte Term_cmd_for_src_noop = 0, Term_cmd_for_src_delete = 1, Term_cmd_for_src_move = 2; + boolean rename_dir = false, trg_is_dir = false, delete_trg_if_exists = true; + public byte Term_cmd_for_src() {return term_cmd_for_src;} public void Term_cmd_for_src_(byte v) {term_cmd_for_src = v;} private byte term_cmd_for_src = Term_cmd_for_src_delete; + public Io_url Term_cmd_for_src_url() {return term_cmd_for_src_url;} public void Term_cmd_for_src_url_(Io_url v) {this.term_cmd_for_src_url = v;} Io_url term_cmd_for_src_url = Io_url_.Empty; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_owner)) return owner; + else if (ctx.Match(k, Invk_src_)) src = Bry_fmtr_eval_mgr_.Eval_url(url_eval_mgr, m.ReadBry("v")); + else if (ctx.Match(k, Invk_trg_)) trg = Bry_fmtr_eval_mgr_.Eval_url(url_eval_mgr, m.ReadBry("v")); + else if (ctx.Match(k, Invk_rename_dir_)) rename_dir = m.ReadYn("v"); + else if (ctx.Match(k, Invk_delete_trg_if_exists_)) delete_trg_if_exists = m.ReadYn("v"); + else if (ctx.Match(k, Invk_term_cmd_for_src_)) term_cmd_for_src = Term_cmd_for_src_parse_(m.ReadStr("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_owner = "owner", Invk_src_ = "src_", Invk_trg_ = "trg_", Invk_rename_dir_ = "rename_dir_", Invk_delete_trg_if_exists_ = "delete_trg_if_exists_", Invk_term_cmd_for_src_ = "term_cmd_for_src_"; + private static byte Term_cmd_for_src_parse_(String s) { + if (String_.Eq(s, "noop")) return Term_cmd_for_src_noop; + else if (String_.Eq(s, "delete")) return Term_cmd_for_src_delete; + else if (String_.Eq(s, "move")) return Term_cmd_for_src_move; + else throw Err_.new_unhandled(s); + } + static final String GRP_KEY = "xowa.thread.file.unzip"; + public static final String KEY = "file.unzip"; +} diff --git a/400_xowa/src/gplx/core/threads/Gfo_thread_grp.java b/400_xowa/src/gplx/core/threads/Gfo_thread_grp.java index a27517de8..ef47dfc2a 100644 --- a/400_xowa/src/gplx/core/threads/Gfo_thread_grp.java +++ b/400_xowa/src/gplx/core/threads/Gfo_thread_grp.java @@ -13,3 +13,76 @@ 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.core.threads; import gplx.*; import gplx.core.*; +public class Gfo_thread_grp implements Gfo_invk { + private final Object thread_lock = new Object(); + private final List_adp list = List_adp_.New(); + private int active_cur; + public Gfo_thread_grp(String key) {this.key = key;} + public String Key() {return key;} private final String key; + public boolean Autorun() {return autorun;} public Gfo_thread_grp Autorun_(boolean v) {autorun = v; return this;} private boolean autorun = true; + public int Active_max() {return active_max;} public Gfo_thread_grp Active_max_(int v) {active_max = v; return this;} private int active_max = 1; + public void Add(Gfo_thread_itm... ary) { + synchronized (thread_lock) { + list.Add_many((Object[])ary); + } + if (autorun) + this.Run(); + } + public void Run() { + int len = list.Len(); if (len == 0) return; // nothing in list; occurs when last item calls Run + for (int i = 0; i < len; ++i) { + if (active_cur == active_max) break; // already at limit; return + Gfo_thread_itm itm = null; + synchronized (thread_lock) { + itm = (Gfo_thread_itm)List_adp_.Pop_first(list); + ++active_cur; + } + Thread_adp_.Start_by_msg(itm.Thread__name(), this, GfoMsg_.new_cast_(Invk_run_wkr).Add("v", itm)); + } + } + public void Stop_all() { + synchronized (thread_lock) { + int len = list.Len(); + for (int i = 0; i < len; ++i) { + Gfo_thread_itm itm = (Gfo_thread_itm)list.Get_at(i); + itm.Thread__stop(); + } + active_cur = 0; + list.Clear(); + } + } + public void Del(String key) { + synchronized (thread_lock) { + List_adp deleted = List_adp_.New(); + int len = list.Len(); + for (int i = 0; i < len; ++i) { + Gfo_thread_itm itm = (Gfo_thread_itm)list.Get_at(i); + if (itm.Thread__can_delete(key)) + deleted.Add(itm); + } + len = deleted.Len(); + for (int i = 0; i < len; ++i) { + Gfo_thread_itm itm = (Gfo_thread_itm)deleted.Get_at(i); + itm.Thread__stop(); + list.Del(itm); + } + } + } + private void Run_wkr(Gfo_thread_itm itm) { + try {itm.Thread__exec();} + catch (Exception e) {Gfo_usr_dlg_.Instance.Warn_many("", "", "uncaught exception while running thread; name=~{0} err=~{1}", itm.Thread__name(), Err_.Message_gplx_log(e));} + finally { + synchronized (thread_lock) { + --active_cur; + } + this.Run(); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_run_wkr)) Run_wkr((Gfo_thread_itm)m.ReadObj("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_run_wkr = "run_wkr"; +} diff --git a/400_xowa/src/gplx/core/threads/Gfo_thread_itm.java b/400_xowa/src/gplx/core/threads/Gfo_thread_itm.java index a27517de8..840271307 100644 --- a/400_xowa/src/gplx/core/threads/Gfo_thread_itm.java +++ b/400_xowa/src/gplx/core/threads/Gfo_thread_itm.java @@ -13,3 +13,10 @@ 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.core.threads; import gplx.*; import gplx.core.*; +public interface Gfo_thread_itm { + String Thread__name(); + void Thread__exec(); + void Thread__stop(); + boolean Thread__can_delete(String key); +} diff --git a/400_xowa/src/gplx/core/threads/Gfo_thread_mgr.java b/400_xowa/src/gplx/core/threads/Gfo_thread_mgr.java index a27517de8..a774f6eeb 100644 --- a/400_xowa/src/gplx/core/threads/Gfo_thread_mgr.java +++ b/400_xowa/src/gplx/core/threads/Gfo_thread_mgr.java @@ -13,3 +13,23 @@ 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.core.threads; import gplx.*; import gplx.core.*; +public class Gfo_thread_mgr { + private final Ordered_hash hash = Ordered_hash_.New(); + public Gfo_thread_grp Get_by_or_new(String k) { + Gfo_thread_grp rv = (Gfo_thread_grp)hash.Get_by(k); + if (rv == null) { + rv = new Gfo_thread_grp(k); + hash.Add(k, rv); + } + return rv; + } + public void Stop_all() { + int len = hash.Len(); + for (int i = 0; i < len; ++i) { + Gfo_thread_grp grp = (Gfo_thread_grp)hash.Get_at(i); + grp.Stop_all(); + } + hash.Clear(); + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/core/threads/Gfo_thread_pool.java b/400_xowa/src/gplx/core/threads/Gfo_thread_pool.java index a27517de8..898b4bfc5 100644 --- a/400_xowa/src/gplx/core/threads/Gfo_thread_pool.java +++ b/400_xowa/src/gplx/core/threads/Gfo_thread_pool.java @@ -13,3 +13,48 @@ 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.core.threads; import gplx.*; import gplx.core.*; +public class Gfo_thread_pool implements Gfo_invk { + private Object thread_lock = new Object(); + private List_adp queue = List_adp_.New(); + private GfoMsg run_msg = GfoMsg_.new_cast_(Invk_run_wkr); + private boolean running = false; + public Gfo_usr_dlg Usr_dlg() {return usr_dlg;} public Gfo_thread_pool Usr_dlg_(Gfo_usr_dlg v) {usr_dlg = v; return this;} private Gfo_usr_dlg usr_dlg = Gfo_usr_dlg_.Noop; + public void Clear() {synchronized (thread_lock) {queue.Clear(); running = false;}} + public Gfo_thread_pool Add_at_end(Gfo_thread_wkr wkr) { + synchronized (thread_lock) {queue.Add(wkr);} + return this; + } + public void Resume() { + synchronized (thread_lock) { + running = false; + } + this.Run(); + } + public void Run() { + Gfo_thread_wkr wkr = null; + synchronized (thread_lock) { + if (running) return; // already running; discard run request and rely on running-wkr to call Run when done + int len = queue.Count(); if (len == 0) return; // nothing in list; occurs when last item calls Run when done + running = true; + wkr = (Gfo_thread_wkr)List_adp_.Pop_first(queue); + } + Thread_adp_.Start_by_msg(wkr.Thread__name(), this, run_msg.Clear().Add("v", wkr)); + } + private void Run_wkr(Gfo_thread_wkr wkr) { + try {wkr.Thread__exec();} + catch (Exception e) { + usr_dlg.Warn_many("", "", "uncaught exception while running thread; name=~{0} err=~{1}", wkr.Thread__name(), Err_.Message_gplx_full(e)); + } + finally { + if (wkr.Thread__resume()) + this.Resume(); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_run_wkr)) Run_wkr((Gfo_thread_wkr)m.ReadObj("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_run_wkr = "run_wkr"; +} diff --git a/400_xowa/src/gplx/core/threads/Gfo_thread_wkr.java b/400_xowa/src/gplx/core/threads/Gfo_thread_wkr.java index a27517de8..49eb4138c 100644 --- a/400_xowa/src/gplx/core/threads/Gfo_thread_wkr.java +++ b/400_xowa/src/gplx/core/threads/Gfo_thread_wkr.java @@ -13,3 +13,9 @@ 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.core.threads; import gplx.*; import gplx.core.*; +public interface Gfo_thread_wkr { + String Thread__name(); + boolean Thread__resume(); + void Thread__exec(); +} diff --git a/400_xowa/src/gplx/dbs/Db_diff_bldr.java b/400_xowa/src/gplx/dbs/Db_diff_bldr.java new file mode 100644 index 000000000..86d3541a7 --- /dev/null +++ b/400_xowa/src/gplx/dbs/Db_diff_bldr.java @@ -0,0 +1,52 @@ +/* +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.dbs; import gplx.*; +import gplx.dbs.metas.*; import gplx.dbs.sqls.*; import gplx.dbs.sqls.wtrs.*; +class Db_diff_bldr { + private final Bry_bfr bfr = Bry_bfr_.New(); + private final Sql_schema_wtr sql_bldr = new Sql_schema_wtr(); + public Db_diff_bldr() {sql_bldr.Bfr_(bfr);} + public String Compare_db(String src_str, String trg_str) { +// Io_url src_url = Io_url_.new_fil_(src_str); +// Io_url trg_url = Io_url_.new_fil_(trg_str); +// Db_conn src_conn = Db_conn_bldr.Instance.Get_or_new(src_url).Conn(); +// Db_conn trg_conn = Db_conn_bldr.Instance.Get_or_new(trg_url).Conn(); + Dbmeta_tbl_mgr src_tbls = new Dbmeta_tbl_mgr(Dbmeta_reload_cmd_.Noop); + Dbmeta_tbl_mgr trg_tbls = new Dbmeta_tbl_mgr(Dbmeta_reload_cmd_.Noop); + return Compare_tbls(src_tbls, trg_tbls); + } + public String Compare_tbls(Dbmeta_tbl_mgr src_tbls, Dbmeta_tbl_mgr trg_tbls) { + int src_len = src_tbls.Len(); + for (int i = 0; i < src_len; ++i) { + Dbmeta_tbl_itm src_tbl = src_tbls.Get_at(i); + Dbmeta_tbl_itm trg_tbl = trg_tbls.Get_by(src_tbl.Name()); + if (trg_tbl == null) Tbl_delete(src_tbl); + } + int trg_len = trg_tbls.Len(); + for (int i = 0; i < trg_len; ++i) { + Dbmeta_tbl_itm trg_tbl = src_tbls.Get_at(i); + Dbmeta_tbl_itm src_tbl = trg_tbls.Get_by(trg_tbl.Name()); + if (src_tbl == null) Tbl_create(trg_tbl); + } + return bfr.To_str_and_clear(); + } + private void Tbl_delete(Dbmeta_tbl_itm tbl) { + bfr.Add_str_a7("DROP TABLE ").Add_str_u8(tbl.Name()).Add_byte_nl(); + } + private void Tbl_create(Dbmeta_tbl_itm tbl) { +// sql_bldr.Bld_create_tbl(tbl); + } +} diff --git a/400_xowa/src/gplx/dbs/bulks/Db_bulk_exec_.java b/400_xowa/src/gplx/dbs/bulks/Db_bulk_exec_.java index a27517de8..31e58c7b6 100644 --- a/400_xowa/src/gplx/dbs/bulks/Db_bulk_exec_.java +++ b/400_xowa/src/gplx/dbs/bulks/Db_bulk_exec_.java @@ -13,3 +13,74 @@ 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.dbs.bulks; import gplx.*; import gplx.dbs.*; +import gplx.dbs.metas.*; +public class Db_bulk_exec_ { + public static void Insert(Db_bulk_prog prog_wkr, String msg, Dbmeta_fld_itm[] flds, Db_rdr src, Db_stmt trg, Db_conn trg_conn) { + // init + int flds_len = flds.length; + String[] fld_names = Db_bulk_exec_utl_.To_fld_names(flds, flds_len); + int[] fld_types = Db_bulk_exec_utl_.To_fld_types(flds, flds_len); + + // loop all rows + Gfo_log_.Instance.Prog(msg); + trg_conn.Txn_bgn("bulk_insert"); + try { + while (src.Move_next()) { + // fill insert + trg.Clear(); + int row_size = 0; + for (int i = 0; i < flds_len; ++i) { + String fld_name = fld_names[i]; + switch (fld_types[i]) { + case Dbmeta_fld_tid.Tid__bool : trg.Val_bool_as_byte (fld_name, src.Read_bool_by_byte (fld_name)); row_size += 1; break; + case Dbmeta_fld_tid.Tid__byte : trg.Val_byte (fld_name, src.Read_byte (fld_name)); row_size += 1; break; + case Dbmeta_fld_tid.Tid__int : trg.Val_int (fld_name, src.Read_int (fld_name)); row_size += 4; break; + case Dbmeta_fld_tid.Tid__long : trg.Val_long (fld_name, src.Read_long (fld_name)); row_size += 8; break; + case Dbmeta_fld_tid.Tid__float : trg.Val_float (fld_name, src.Read_float (fld_name)); row_size += 4; break; + case Dbmeta_fld_tid.Tid__double : trg.Val_double (fld_name, src.Read_double (fld_name)); row_size += 8; break; + case Dbmeta_fld_tid.Tid__str : String src_str = src.Read_str(fld_name); trg.Val_str(fld_name, src_str); row_size += src_str == null ? 0 : String_.Len(src_str); break; + case Dbmeta_fld_tid.Tid__bry : byte[] src_bry = src.Read_bry(fld_name); trg.Val_bry(fld_name, src_bry); row_size += src_bry == null ? 0 : src_bry.length; break; + default : throw Err_.new_unhandled_default(fld_types[i]); + } + } + + // exec insert + try {trg.Exec_insert();} + catch (Exception e) {throw Db_bulk_exec_utl_.New_err(e, src, flds_len, fld_names, fld_types);} + + // commit and notify if applicable + if (prog_wkr.Prog__insert_and_stop_if_suspended(row_size)) break; + } + } + catch (Exception e) {throw Err_.new_wo_type("dbs.bulk:insert failed", "err", e);} + finally { + trg_conn.Txn_end(); + } + } + public static final String Invk__bulk_insert_err = "bulk.insert.err", Invk__bulk_insert_prog = "bulk.insert.prog"; +} +class Db_bulk_exec_utl_ { + public static String[] To_fld_names(Dbmeta_fld_itm[] flds, int flds_len) { + String[] rv = new String[flds_len]; + for (int i = 0; i < flds_len; ++i) + rv[i] = flds[i].Name(); + return rv; + } + public static int[] To_fld_types(Dbmeta_fld_itm[] flds, int flds_len) { + int[] rv = new int[flds_len]; + for (int i = 0; i < flds_len; ++i) + rv[i] = flds[i].Type().Tid_ansi(); + return rv; + } + public static Err New_err(Exception e, Db_rdr rdr, int flds_len, String[] fld_names, int[] fld_types) { + Object[] args = new Object[(flds_len * 2) + 2]; + for (int i = 0; i < flds_len; i += 2) { + args[i ] = fld_names[i]; + args[i + 1] = rdr.Read_at(i); + } + args[flds_len - 2] = "err"; + args[flds_len - 1] = Err_.Message_gplx_log(e); + return Err_.new_wo_type("dbs.bulk:insert row failed", args); + } +} diff --git a/400_xowa/src/gplx/dbs/bulks/Db_bulk_prog.java b/400_xowa/src/gplx/dbs/bulks/Db_bulk_prog.java index a27517de8..ce034a07f 100644 --- a/400_xowa/src/gplx/dbs/bulks/Db_bulk_prog.java +++ b/400_xowa/src/gplx/dbs/bulks/Db_bulk_prog.java @@ -13,3 +13,7 @@ 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.dbs.bulks; import gplx.*; import gplx.dbs.*; +public interface Db_bulk_prog { + boolean Prog__insert_and_stop_if_suspended(int row_size); +} diff --git a/400_xowa/src/gplx/dbs/bulks/Db_tbl_copy.java b/400_xowa/src/gplx/dbs/bulks/Db_tbl_copy.java index a27517de8..ede1222df 100644 --- a/400_xowa/src/gplx/dbs/bulks/Db_tbl_copy.java +++ b/400_xowa/src/gplx/dbs/bulks/Db_tbl_copy.java @@ -13,3 +13,42 @@ 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.dbs.bulks; import gplx.*; import gplx.dbs.*; +import gplx.dbs.*; import gplx.dbs.metas.*; +public class Db_tbl_copy { + private final Bry_bfr bfr = Bry_bfr_.New(); + private final Db_attach_mgr attach_mgr = new Db_attach_mgr(); + public void Copy_many(Db_conn src_conn, Db_conn trg_conn, String... tbl_names) { + for (String tbl_name : tbl_names) + Copy_one(src_conn, trg_conn, tbl_name, tbl_name); + } + public void Copy_one(Db_conn src_conn, Db_conn trg_conn, String src_tbl, String trg_tbl) { + Dbmeta_tbl_itm tbl = src_conn.Meta_mgr().Get_by(src_tbl); if (tbl == null) throw Err_.new_wo_type("tbl does not exist", "tbl_name", src_tbl); + trg_conn.Meta_tbl_remake(Dbmeta_tbl_itm.New(trg_tbl, tbl.Flds().To_ary(), tbl.Idxs().To_ary())); + + // do copy + attach_mgr.Conn_main_(trg_conn).Conn_links_(new Db_attach_itm("src_db", src_conn)); + attach_mgr.Exec_sql(Bld_sql(tbl, src_tbl, trg_tbl)); + } + public String Bld_sql(Dbmeta_tbl_itm tbl, String src_tbl, String trg_tbl) { + Dbmeta_fld_mgr flds = tbl.Flds(); + int flds_len = flds.Len(); + bfr.Add_str_a7("INSERT INTO ").Add_str_a7(trg_tbl).Add_byte_nl(); + bfr.Add_byte(Byte_ascii.Paren_bgn); + for (int i = 0; i < flds_len; ++i) { + Dbmeta_fld_itm fld = flds.Get_at(i); + if (i != 0) bfr.Add_str_a7(", "); + bfr.Add_str_a7(fld.Name()); + } + bfr.Add_byte(Byte_ascii.Paren_end).Add_byte_nl(); + bfr.Add_str_a7("SELECT").Add_byte_nl().Add_byte_space(); + for (int i = 0; i < flds_len; ++i) { + Dbmeta_fld_itm fld = flds.Get_at(i); + if (i != 0) bfr.Add_str_a7(", "); + bfr.Add_str_a7(fld.Name()); + } + bfr.Add_byte_nl(); + bfr.Add_str_a7("FROM ").Add_str_a7(src_tbl); + return bfr.To_str_and_clear(); + } +} diff --git a/400_xowa/src/gplx/dbs/bulks/Db_tbl_copy_tst.java b/400_xowa/src/gplx/dbs/bulks/Db_tbl_copy_tst.java index a27517de8..71f80030b 100644 --- a/400_xowa/src/gplx/dbs/bulks/Db_tbl_copy_tst.java +++ b/400_xowa/src/gplx/dbs/bulks/Db_tbl_copy_tst.java @@ -13,3 +13,25 @@ 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.dbs.bulks; import gplx.*; import gplx.dbs.*; +import org.junit.*; import gplx.core.tests.*; import gplx.dbs.metas.*; +public class Db_tbl_copy_tst { + private final Db_tbl_copy_fxt fxt = new Db_tbl_copy_fxt(); + @Test public void Basic() { + fxt.Test__bld_sql(fxt.Make_tbl("tbl_1", Dbmeta_fld_itm.new_int("fld_1"), Dbmeta_fld_itm.new_int("fld_2")), + String_.Concat_lines_nl_skip_last + ( "INSERT INTO trg" + , "(fld_1, fld_2)" + , "SELECT" + , " fld_1, fld_2" + , "FROM src" + )); + } +} +class Db_tbl_copy_fxt { + private final Db_tbl_copy mgr = new Db_tbl_copy(); + public Dbmeta_tbl_itm Make_tbl(String name, Dbmeta_fld_itm... flds) {return Dbmeta_tbl_itm.New(name, flds);} + public void Test__bld_sql(Dbmeta_tbl_itm tbl, String expd) { + Gftest.Eq__ary__lines(expd, mgr.Bld_sql(tbl, "src", "trg"), "sql"); + } +} diff --git a/400_xowa/src/gplx/dbs/cfgs/Db_cfg_hash.java b/400_xowa/src/gplx/dbs/cfgs/Db_cfg_hash.java index a27517de8..646983f43 100644 --- a/400_xowa/src/gplx/dbs/cfgs/Db_cfg_hash.java +++ b/400_xowa/src/gplx/dbs/cfgs/Db_cfg_hash.java @@ -13,3 +13,21 @@ 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.dbs.cfgs; import gplx.*; import gplx.dbs.*; +public class Db_cfg_hash { + private final String grp; private final Ordered_hash hash = Ordered_hash_.New(); + public Db_cfg_hash(String grp) {this.grp = grp;} + public int Len() {return hash.Count();} + public Db_cfg_itm Get_at(int i) {return (Db_cfg_itm)hash.Get_at(i);} + public Db_cfg_itm Get_by(String key) { + Db_cfg_itm rv = (Db_cfg_itm)hash.Get_by(key); + return rv == null ? Db_cfg_itm.Empty : rv; + } + public void Set(String key, String val) {hash.Del(key); Add(key, val);} + public void Add(Db_cfg_itm itm) {hash.Add(itm.Key(), itm);} + public void Add(String key, String val) { + if (hash.Has(key)) throw Err_.new_wo_type("itm exists", "grp", grp, "key", key); + Db_cfg_itm itm = new Db_cfg_itm(grp, key, val); + Add(itm); + } +} diff --git a/400_xowa/src/gplx/dbs/cfgs/Db_cfg_itm.java b/400_xowa/src/gplx/dbs/cfgs/Db_cfg_itm.java index a27517de8..1cf7449dc 100644 --- a/400_xowa/src/gplx/dbs/cfgs/Db_cfg_itm.java +++ b/400_xowa/src/gplx/dbs/cfgs/Db_cfg_itm.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.dbs.cfgs; import gplx.*; import gplx.dbs.*; +public class Db_cfg_itm { + public Db_cfg_itm(String grp, String key, String val) {this.grp = grp; this.key = key; this.val = val;} + public String Grp() {return grp;} private final String grp; + public String Key() {return key;} private final String key; + public String Val() {return val;} public Db_cfg_itm Val_(String v) {val = v; return this;} private String val; + public String To_str_or(String or) {return val == null ? or : val;} + public byte[] To_bry_or(byte[] or) {try {return val == null ? or : Bry_.new_u8(val) ;} catch (Exception e) {throw err_parse(e, Bry_.Cls_val_name);}} + public int To_int_or(int or) {try {return val == null ? or : Int_.Parse_or(val, or) ;} catch (Exception e) {throw err_parse(e, Int_.Cls_val_name);}} + public long To_long_or(long or) {try {return val == null ? or : Long_.parse_or(val, or) ;} catch (Exception e) {throw err_parse(e, Long_.Cls_val_name);}} + public byte To_byte_or(byte or) {try {return val == null ? or : Byte_.Parse_or(val, or) ;} catch (Exception e) {throw err_parse(e, Byte_.Cls_val_name);}} + public boolean To_yn_or_n() {return To_yn_or(Bool_.N);} + public boolean To_yn_or(boolean or) {try {return val == null ? or : Yn.parse_by_char_or(val, or);} catch (Exception e) {throw err_parse(e, Bool_.Cls_val_name);}} + public DateAdp To_date_or(DateAdp or) {try {return val == null ? or : DateAdp_.parse_gplx(val) ;} catch (Exception e) {throw err_parse(e, DateAdp_.Cls_ref_name);}} + public Guid_adp To_guid_or(Guid_adp or) {try {return val == null ? or : Guid_adp_.Parse(val) ;} catch (Exception e) {throw err_parse(e, Guid_adp_.Cls_ref_name);}} + public boolean To_bool() {Fail_if_null(); try {return Yn.parse(val) ;} catch (Exception e) {throw err_parse(e, Bool_.Cls_val_name);}} + public byte To_byte() {Fail_if_null(); try {return Byte_.Parse(val) ;} catch (Exception e) {throw err_parse(e, Byte_.Cls_val_name);}} + public int To_int() {Fail_if_null(); try {return Int_.Parse(val) ;} catch (Exception e) {throw err_parse(e, Int_.Cls_val_name);}} + public String To_str() {Fail_if_null(); return val;} + private void Fail_if_null() {if (val == null) throw Err_.new_wo_type("cfg.val is empty", "grp", grp, "key", key); } + private Err err_parse(Exception e, String type) {return Err_.new_wo_type("cfg.val is not parseable", "grp", grp, "key", key, "val", val, "type", type).Trace_ignore_add_1_();} + private static final String Grp_none = ""; + public static Db_cfg_itm new_str (String key, String val) {return new Db_cfg_itm(Grp_none , key, val);} + public static Db_cfg_itm new_str (String grp, String key, String val) {return new Db_cfg_itm(grp , key, val);} + public static Db_cfg_itm new_bry (String key, byte[] val) {return new Db_cfg_itm(Grp_none , key, String_.new_u8(val));} + public static Db_cfg_itm new_bry (String grp, String key, byte[] val) {return new Db_cfg_itm(grp , key, String_.new_u8(val));} + public static Db_cfg_itm new_int (String key, int val) {return new Db_cfg_itm(Grp_none , key, Int_.To_str(val));} + public static Db_cfg_itm new_int (String grp, String key, int val) {return new Db_cfg_itm(grp , key, Int_.To_str(val));} + public static Db_cfg_itm new_long (String key, long val) {return new Db_cfg_itm(Grp_none , key, Long_.To_str(val));} + public static Db_cfg_itm new_long (String grp, String key, long val) {return new Db_cfg_itm(grp , key, Long_.To_str(val));} + public static Db_cfg_itm new_byte (String key, byte val) {return new Db_cfg_itm(Grp_none , key, Byte_.To_str(val));} + public static Db_cfg_itm new_byte (String grp, String key, byte val) {return new Db_cfg_itm(grp , key, Byte_.To_str(val));} + public static Db_cfg_itm new_yn (String key, boolean val) {return new Db_cfg_itm(Grp_none , key, Yn.To_str(val));} + public static Db_cfg_itm new_yn (String grp, String key, boolean val) {return new Db_cfg_itm(grp , key, Yn.To_str(val));} + public static Db_cfg_itm new_DateAdp (String key, DateAdp val) {return new Db_cfg_itm(Grp_none , key, val.XtoStr_fmt_yyyyMMdd_HHmmss());} + public static Db_cfg_itm new_DateAdp (String grp, String key, DateAdp val) {return new Db_cfg_itm(grp , key, val.XtoStr_fmt_yyyyMMdd_HHmmss());} + public static Db_cfg_itm new_guid (String key, Guid_adp val) {return new Db_cfg_itm(Grp_none , key, val.To_str());} + public static Db_cfg_itm new_guid (String grp, String key, Guid_adp val) {return new Db_cfg_itm(grp , key, val.To_str());} + public static final Db_cfg_itm Empty = new Db_cfg_itm("empty", "empty", null); +} diff --git a/400_xowa/src/gplx/dbs/cfgs/Db_cfg_tbl.java b/400_xowa/src/gplx/dbs/cfgs/Db_cfg_tbl.java index a27517de8..9b6eb1f3a 100644 --- a/400_xowa/src/gplx/dbs/cfgs/Db_cfg_tbl.java +++ b/400_xowa/src/gplx/dbs/cfgs/Db_cfg_tbl.java @@ -13,3 +13,149 @@ 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.dbs.cfgs; import gplx.*; import gplx.dbs.*; +import gplx.core.primitives.*; +public class Db_cfg_tbl implements Db_tbl { + private final String tbl_name; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_grp, fld_key, fld_val; + private Db_stmt stmt_insert, stmt_update, stmt_select; + public Db_conn Conn() {return conn;} private final Db_conn conn; + public Db_cfg_tbl(Db_conn conn, String tbl_name) { + this.conn = conn; this.tbl_name = tbl_name; + this.fld_grp = flds.Add_str("cfg_grp", 255); + this.fld_key = flds.Add_str("cfg_key", 255); + this.fld_val = flds.Add_str("cfg_val", 1024); + conn.Rls_reg(this); + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + stmt_update = Db_stmt_.Rls(stmt_update); + stmt_select = Db_stmt_.Rls(stmt_select); + } + public String Tbl_name() {return tbl_name;} + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_unique_by_tbl(tbl_name, "main", fld_grp, fld_key, fld_val)));} + public void Delete_val(String grp, String key) {conn.Stmt_delete(tbl_name, fld_grp, fld_key).Crt_str(fld_grp, grp).Crt_str(fld_key, key).Exec_delete();} + public void Delete_grp(String grp) {conn.Stmt_delete(tbl_name, fld_grp).Crt_str(fld_grp, grp).Exec_delete();} + public void Delete_all() {conn.Stmt_delete(tbl_name, Dbmeta_fld_itm.Str_ary_empty).Exec_delete();} + public void Insert_yn (String grp, String key, boolean val) {Insert_str(grp, key, val ? "y" : "n");} + public void Insert_byte (String grp, String key, byte val) {Insert_str(grp, key, Byte_.To_str(val));} + public void Insert_int (String grp, String key, int val) {Insert_str(grp, key, Int_.To_str(val));} + public void Insert_long (String grp, String key, long val) {Insert_str(grp, key, Long_.To_str(val));} + public void Insert_date (String grp, String key, DateAdp val) {Insert_str(grp, key, val.XtoStr_fmt_yyyyMMdd_HHmmss());} + public void Insert_guid (String grp, String key, Guid_adp val) {Insert_str(grp, key, val.To_str());} + public void Insert_bry (String grp, String key, byte[] val) {Insert_str(grp, key, String_.new_u8(val));} + public void Insert_str (String grp, String key, String val) { + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + try { + stmt_insert.Clear().Val_str(fld_grp, grp).Val_str(fld_key, key).Val_str(fld_val, val).Exec_insert(); + } catch (Exception e) {throw Err_.new_exc(e, "db", "db_cfg.insert failed", "grp", grp, "key", key, "val", val, "db", conn.Conn_info().Db_api());} + } + public void Insert_str (String key, String val) { + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + try { + stmt_insert.Clear().Val_str(fld_grp, "").Val_str(fld_key, key).Val_str(fld_val, val).Exec_insert(); + } catch (Exception e) {throw Err_.new_exc(e, "db", "db_cfg.insert failed", "key", key, "val", val, "db", conn.Conn_info().Db_api());} + } + public void Update_yn (String grp, String key, boolean val) {Update_str(grp, key, val ? "y" : "n");} + public void Update_byte (String grp, String key, byte val) {Update_str(grp, key, Byte_.To_str(val));} + public void Update_int (String grp, String key, int val) {Update_str(grp, key, Int_.To_str(val));} + public void Update_long (String grp, String key, long val) {Update_str(grp, key, Long_.To_str(val));} + public void Update_date (String grp, String key, DateAdp val) {Update_str(grp, key, val.XtoStr_fmt_yyyyMMdd_HHmmss());} + public void Update_guid (String grp, String key, Guid_adp val) {Update_str(grp, key, val.To_str());} + public void Update_bry (String grp, String key, byte[] val) {Update_str(grp, key, String_.new_u8(val));} + public void Update_str (String grp, String key, String val) { + if (stmt_update == null) stmt_update = conn.Stmt_update_exclude(tbl_name, flds, fld_grp, fld_key); + stmt_update.Clear().Val_str(fld_val, val).Crt_str(fld_grp, grp).Crt_str(fld_key, key).Exec_update(); + } + public void Update_str (String key, String val) { + if (stmt_update == null) stmt_update = conn.Stmt_update_exclude(tbl_name, flds, fld_grp, fld_key); + stmt_update.Clear().Val_str(fld_val, val).Crt_str(fld_grp, "").Crt_str(fld_key, key).Exec_update(); + } + public void Upsert_yn (String grp, String key, boolean val) {Upsert_str(grp, key, val ? "y" : "n");} + public void Upsert_int (String grp, String key, int val) {Upsert_str(grp, key, Int_.To_str(val));} + public void Upsert_date (String grp, String key, DateAdp val) {Upsert_str(grp, key, val.XtoStr_fmt_yyyyMMdd_HHmmss());} + public void Upsert_guid (String grp, String key, Guid_adp val) {Upsert_str(grp, key, val.To_str());} + public void Upsert_bry (String grp, String key, byte[] val) {Upsert_str(grp, key, String_.new_u8(val));} + public void Upsert_str (String grp, String key, String val) { + String cur_val = this.Select_str_or(grp, key, null); + if (cur_val == null) this.Insert_str(grp, key, val); + else this.Update_str(grp, key, val); + } + + public void Upsert_int (String key, int val) {Upsert_str(key, Int_.To_str(val));} + public void Upsert_str (String key, String val) { + String cur_val = this.Select_str_or(key, null); + if (cur_val == null) this.Insert_str(key, val); + else this.Update_str(key, val); + } + public boolean Select_yn (String grp, String key) {String val = Select_str(grp, key); return Parse_yn (grp, key, val);} + public byte Select_byte (String grp, String key) {String val = Select_str(grp, key); return Parse_byte (grp, key, val);} + public int Select_int (String grp, String key) {String val = Select_str(grp, key); return Parse_int (grp, key, val);} + public long Select_long (String grp, String key) {String val = Select_str(grp, key); return Parse_long (grp, key, val);} + public byte[] Select_bry (String grp, String key) {String val = Select_str(grp, key); return Parse_bry (grp, key, val);} + public DateAdp Select_date (String grp, String key) {String val = Select_str(grp, key); return Parse_date (grp, key, val);} + public Guid_adp Select_guid (String grp, String key) {String val = Select_str(grp, key); return Parse_guid (grp, key, val);} + public boolean Select_yn_or (String grp, String key, boolean or) {String val = Select_str_or(grp, key, null) ; return val == null ? or : Parse_yn (grp, key, val);} + public byte Select_byte_or (String grp, String key, byte or) {String val = Select_str_or(grp, key, null) ; return val == null ? or : Parse_byte (grp, key, val);} + public int Select_int_or (String grp, String key, int or) {String val = Select_str_or(grp, key, null) ; return val == null ? or : Parse_int (grp, key, val);} + public long Select_long_or (String grp, String key, long or) {String val = Select_str_or(grp, key, null) ; return val == null ? or : Parse_long (grp, key, val);} + public byte[] Select_bry_or (String grp, String key, byte[] or) {String val = Select_str_or(grp, key, null) ; return val == null ? or : Parse_bry (grp, key, val);} + public DateAdp Select_date_or (String grp, String key, DateAdp or) {String val = Select_str_or(grp, key, null) ; return val == null ? or : Parse_date (grp, key, val);} + public Guid_adp Select_guid_or (String grp, String key, Guid_adp or) {String val = Select_str_or(grp, key, null) ; return val == null ? or : Parse_guid (grp, key, val);} + public String Select_str (String grp, String key) { + String rv = Select_str_or(grp, key, null); if (rv == null) throw Err_.new_wo_type("cfg.missing", "grp", grp, "key", key); + return rv; + } + public String Select_str_or (String grp, String key, String or) { + if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, String_.Ary(fld_val), fld_grp, fld_key); + Db_rdr rdr = stmt_select.Clear().Crt_str(fld_grp, grp).Crt_str(fld_key, key).Exec_select__rls_manual(); + try {return rdr.Move_next() ? rdr.Read_str(fld_val) : or;} finally {rdr.Rls();} + } + + public int Select_int_or (String key, int or) {String val = Select_str_or(key, null) ; return val == null ? or : Parse_int ("", key, val);} + public String Select_str_or (String key, String or) { + if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, String_.Ary(fld_val), fld_grp, fld_key); + Db_rdr rdr = stmt_select.Clear().Crt_str(fld_grp, "").Crt_str(fld_key, key).Exec_select__rls_manual(); + try {return rdr.Move_next() ? rdr.Read_str(fld_val) : or;} finally {rdr.Rls();} + } + public Db_cfg_hash Select_as_hash(String grp) { + Db_cfg_hash rv = new Db_cfg_hash(grp); + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_grp).Crt_str(fld_grp, grp).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + rv.Add(rdr.Read_str(fld_key), rdr.Read_str(fld_val)); + } + } + finally {rdr.Rls();} + return rv; + } + public void Select_as_hash_bry(Hash_adp_bry rv, String grp) { + rv.Clear(); + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_grp).Crt_str(fld_grp, grp).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + rv.Add(rdr.Read_bry_by_str(fld_key), rdr.Read_bry_by_str(fld_val)); + } + } + finally {rdr.Rls();} + } + // NOTE: Assert guarantees that a value exists in database and returns it (Select + Insert); (1) String val = Assert('grp', 'key', 'val'); (2) Update('grp', 'key', 'val2'); + public boolean Assert_yn (String grp, String key, boolean or) {String val = Select_str_or(grp, key, null) ; if (val == null) {Insert_yn (grp, key, or); return or;} return Parse_yn (grp, key, val);} + public byte Assert_byte (String grp, String key, byte or) {String val = Select_str_or(grp, key, null) ; if (val == null) {Insert_byte (grp, key, or); return or;} return Parse_byte (grp, key, val);} + public int Assert_int (String grp, String key, int or) {String val = Select_str_or(grp, key, null) ; if (val == null) {Insert_int (grp, key, or); return or;} return Parse_int (grp, key, val);} + public long Assert_long (String grp, String key, long or) {String val = Select_str_or(grp, key, null) ; if (val == null) {Insert_long (grp, key, or); return or;} return Parse_long (grp, key, val);} + public byte[] Assert_bry (String grp, String key, byte[] or) {String val = Select_str_or(grp, key, null) ; if (val == null) {Insert_bry (grp, key, or); return or;} return Parse_bry (grp, key, val);} + public DateAdp Assert_date (String grp, String key, DateAdp or) {String val = Select_str_or(grp, key, null) ; if (val == null) {Insert_date (grp, key, or); return or;} return Parse_date (grp, key, val);} + public Guid_adp Assert_guid (String grp, String key, Guid_adp or) {String val = Select_str_or(grp, key, null) ; if (val == null) {Insert_guid (grp, key, or); return or;} return Parse_guid (grp, key, val);} + public String Assert_str (String grp, String key, String or) {String val = Select_str_or(grp, key, null) ; if (val == null) {Insert_str (grp, key, or); return or;} return val;} + private boolean Parse_yn (String grp, String key, String val) {try {return Yn.parse(val) ;} catch (Exception e) {throw err_parse(e, grp, key, val, Bool_.Cls_val_name);}} + private byte Parse_byte (String grp, String key, String val) {try {return Byte_.Parse(val) ;} catch (Exception e) {throw err_parse(e, grp, key, val, Byte_.Cls_val_name);}} + private int Parse_int (String grp, String key, String val) {try {return Int_.Parse(val) ;} catch (Exception e) {throw err_parse(e, grp, key, val, Int_.Cls_val_name);}} + private long Parse_long (String grp, String key, String val) {try {return Long_.parse(val) ;} catch (Exception e) {throw err_parse(e, grp, key, val, Long_.Cls_val_name);}} + private byte[] Parse_bry (String grp, String key, String val) {try {return Bry_.new_u8(val) ;} catch (Exception e) {throw err_parse(e, grp, key, val, Bry_.Cls_val_name);}} + private DateAdp Parse_date (String grp, String key, String val) {try {return DateAdp_.parse_gplx(val) ;} catch (Exception e) {throw err_parse(e, grp, key, val, DateAdp_.Cls_ref_name);}} + private Guid_adp Parse_guid (String grp, String key, String val) {try {return Guid_adp_.Parse(val) ;} catch (Exception e) {throw err_parse(e, grp, key, val, Guid_adp_.Cls_ref_name);}} + private Err err_parse(Exception e, String grp, String key, String val, String type) {return Err_.new_exc(e, "db", "cfg.val is not parseable", "grp", grp, "key", key, "val", val, "type", type);} + + public static Db_cfg_tbl Get_by_key(Db_tbl_owner owner, String key) {return (Db_cfg_tbl)owner.Tbls__get_by_key(key);} +} diff --git a/400_xowa/src/gplx/dbs/metas/Schema_db_mgr.java b/400_xowa/src/gplx/dbs/metas/Schema_db_mgr.java index a27517de8..3e18ea537 100644 --- a/400_xowa/src/gplx/dbs/metas/Schema_db_mgr.java +++ b/400_xowa/src/gplx/dbs/metas/Schema_db_mgr.java @@ -13,3 +13,14 @@ 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.dbs.metas; import gplx.*; import gplx.dbs.*; +import gplx.dbs.metas.updates.*; +public class Schema_db_mgr { + public Schema_loader_mgr Loader() {return loader;} public void Loader_(Schema_loader_mgr v) {loader = v;} private Schema_loader_mgr loader; + public Schema_update_mgr Updater() {return updater;} private final Schema_update_mgr updater = new Schema_update_mgr(); + public Dbmeta_tbl_mgr Tbl_mgr() {return tbl_mgr;} private final Dbmeta_tbl_mgr tbl_mgr = new Dbmeta_tbl_mgr(Dbmeta_reload_cmd_.Noop); + public void Init(Db_conn conn) { + loader.Load(this, conn); + updater.Update(this, conn); + } +} diff --git a/400_xowa/src/gplx/dbs/metas/Schema_loader_mgr.java b/400_xowa/src/gplx/dbs/metas/Schema_loader_mgr.java index a27517de8..5da84e96d 100644 --- a/400_xowa/src/gplx/dbs/metas/Schema_loader_mgr.java +++ b/400_xowa/src/gplx/dbs/metas/Schema_loader_mgr.java @@ -13,3 +13,7 @@ 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.dbs.metas; import gplx.*; import gplx.dbs.*; +public interface Schema_loader_mgr { + void Load(Schema_db_mgr db_mgr, Db_conn conn); +} diff --git a/400_xowa/src/gplx/dbs/metas/Schema_loader_mgr_.java b/400_xowa/src/gplx/dbs/metas/Schema_loader_mgr_.java index a27517de8..f8969a52b 100644 --- a/400_xowa/src/gplx/dbs/metas/Schema_loader_mgr_.java +++ b/400_xowa/src/gplx/dbs/metas/Schema_loader_mgr_.java @@ -13,3 +13,36 @@ 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.dbs.metas; import gplx.*; import gplx.dbs.*; +import gplx.dbs.qrys.*; +public class Schema_loader_mgr_ { + public static final Schema_loader_mgr Null = new Schema_loader_mgr__null(); + public static final Schema_loader_mgr Sqlite = new Schema_loader_mgr__sqlite(); +} +class Schema_loader_mgr__null implements Schema_loader_mgr { + public void Load(Schema_db_mgr db_mgr, Db_conn conn) {} +} +class Schema_loader_mgr__sqlite implements Schema_loader_mgr { + public void Load(Schema_db_mgr db_mgr, Db_conn conn) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.schema.load.bgn: conn=~{0}", conn.Conn_info().Db_api()); + Dbmeta_tbl_mgr tbl_mgr = db_mgr.Tbl_mgr(); + Db_qry__select_in_tbl qry = Db_qry__select_in_tbl.new_("sqlite_master", String_.Ary_empty, String_.Ary("type", "name", "sql"), Db_qry__select_in_tbl.Order_by_null); + Db_rdr rdr = conn.Stmt_new(qry).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + String type_str = rdr.Read_str("type"); + String name = rdr.Read_str("name"); + int type_int = Dbmeta_itm_tid.Xto_int(type_str); + switch (type_int) { + case Dbmeta_itm_tid.Tid_table: + Dbmeta_tbl_itm tbl_itm = Dbmeta_tbl_itm.New(name); + tbl_mgr.Add(tbl_itm); + break; + case Dbmeta_itm_tid.Tid_index: break; // noop for now + default: throw Err_.new_unhandled(type_str); + } + } + } finally {rdr.Rls();} + Gfo_usr_dlg_.Instance.Log_many("", "", "db.schema.load.end"); + } +} diff --git a/400_xowa/src/gplx/dbs/metas/updates/Schema_update_cmd.java b/400_xowa/src/gplx/dbs/metas/updates/Schema_update_cmd.java index a27517de8..b343cb503 100644 --- a/400_xowa/src/gplx/dbs/metas/updates/Schema_update_cmd.java +++ b/400_xowa/src/gplx/dbs/metas/updates/Schema_update_cmd.java @@ -13,3 +13,9 @@ 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.dbs.metas.updates; import gplx.*; import gplx.dbs.*; import gplx.dbs.metas.*; +public interface Schema_update_cmd { + String Name(); + boolean Exec_is_done(); + void Exec(Schema_db_mgr mgr, Db_conn conn); +} diff --git a/400_xowa/src/gplx/dbs/metas/updates/Schema_update_cmd_.java b/400_xowa/src/gplx/dbs/metas/updates/Schema_update_cmd_.java index a27517de8..be9f17484 100644 --- a/400_xowa/src/gplx/dbs/metas/updates/Schema_update_cmd_.java +++ b/400_xowa/src/gplx/dbs/metas/updates/Schema_update_cmd_.java @@ -13,3 +13,23 @@ 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.dbs.metas.updates; import gplx.*; import gplx.dbs.*; import gplx.dbs.metas.*; +import gplx.dbs.engines.sqlite.*; +public class Schema_update_cmd_ { + public static Schema_update_cmd Make_tbl_create(String tbl_name, String tbl_sql, Db_idx_itm... tbl_idxs) {return new Schema_update_cmd__tbl_create(tbl_name, tbl_sql, tbl_idxs);} +} +class Schema_update_cmd__tbl_create implements Schema_update_cmd { + private final String tbl_sql; private final Db_idx_itm[] tbl_idxs; + public Schema_update_cmd__tbl_create(String tbl_name, String tbl_sql, Db_idx_itm... tbl_idxs) { + this.tbl_name = tbl_name; this.tbl_sql = tbl_sql; this.tbl_idxs = tbl_idxs; + } + public String Name() {return "schema.tbl.create." + tbl_name;} private final String tbl_name; + public boolean Exec_is_done() {return exec_is_done;} private boolean exec_is_done; + public void Exec(Schema_db_mgr db_mgr, Db_conn conn) { + if (db_mgr.Tbl_mgr().Has(tbl_name)) return; + Gfo_usr_dlg_.Instance.Log_many("", "", "schema.tbl.create: tbl=~{0}", tbl_name); + Sqlite_engine_.Tbl_create(conn, tbl_name, tbl_sql); + Sqlite_engine_.Idx_create(conn, tbl_idxs); + exec_is_done = true; + } +} diff --git a/400_xowa/src/gplx/dbs/metas/updates/Schema_update_mgr.java b/400_xowa/src/gplx/dbs/metas/updates/Schema_update_mgr.java index a27517de8..661b5637c 100644 --- a/400_xowa/src/gplx/dbs/metas/updates/Schema_update_mgr.java +++ b/400_xowa/src/gplx/dbs/metas/updates/Schema_update_mgr.java @@ -13,3 +13,18 @@ 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.dbs.metas.updates; import gplx.*; import gplx.dbs.*; import gplx.dbs.metas.*; +public class Schema_update_mgr { + private List_adp cmds = List_adp_.New(); + public void Add(Schema_update_cmd cmd) {cmds.Add(cmd);} + public void Update(Schema_db_mgr schema_mgr, Db_conn conn) { + int cmds_len = cmds.Count(); + for (int i = 0; i < cmds_len; ++i) { + Schema_update_cmd cmd = (Schema_update_cmd)cmds.Get_at(i); + try {cmd.Exec(schema_mgr, conn);} + catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to run update cmd; name=~{0} err=~{1}", cmd.Name(), Err_.Message_gplx_full(e)); + } + } + } +} diff --git a/400_xowa/src/gplx/dbs/metas/updates/Schema_update_mgr_tst.java b/400_xowa/src/gplx/dbs/metas/updates/Schema_update_mgr_tst.java index a27517de8..2b1ce4f4d 100644 --- a/400_xowa/src/gplx/dbs/metas/updates/Schema_update_mgr_tst.java +++ b/400_xowa/src/gplx/dbs/metas/updates/Schema_update_mgr_tst.java @@ -13,3 +13,41 @@ 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.dbs.metas.updates; import gplx.*; import gplx.dbs.*; import gplx.dbs.metas.*; +import org.junit.*; import gplx.dbs.*; +public class Schema_update_mgr_tst { + @Before public void init() {fxt.Clear();} private Schema_update_mgr_fxt fxt = new Schema_update_mgr_fxt(); + @Test public void Create() { + fxt.Test_exec_y(new Schema_update_cmd__mock()); + } + @Test public void Delete() { + fxt.Init_itm(Dbmeta_itm_tid.Tid_table, Schema_update_cmd__mock.Tbl_name); + fxt.Test_exec_n(new Schema_update_cmd__mock()); + } +} +class Schema_update_mgr_fxt { + private Schema_update_mgr update_mgr; private Schema_db_mgr db_mgr; + public void Clear() { + update_mgr = new Schema_update_mgr(); + db_mgr = new Schema_db_mgr(); + } + public void Init_itm(int tid, String name) { + db_mgr.Tbl_mgr().Add(Dbmeta_tbl_itm.New(name)); + } + public void Test_exec_y(Schema_update_cmd cmd) {Test_exec(cmd, Bool_.Y);} + public void Test_exec_n(Schema_update_cmd cmd) {Test_exec(cmd, Bool_.N);} + private void Test_exec(Schema_update_cmd cmd, boolean expd) { + update_mgr.Add(cmd); + update_mgr.Update(db_mgr, Db_conn_.Noop); + Tfds.Eq(expd, cmd.Exec_is_done()); + } +} +class Schema_update_cmd__mock implements Schema_update_cmd { + public String Name() {return "xowa.user.cfg_regy.create";} + public boolean Exec_is_done() {return exec_is_done;} private boolean exec_is_done; + public void Exec(Schema_db_mgr mgr, Db_conn conn) { + if (mgr.Tbl_mgr().Has(Tbl_name)) return; + exec_is_done = true; + } + public static final String Tbl_name = "tbl_mock"; +} diff --git a/400_xowa/src/gplx/dbs/percentiles/Log_tbl_fmtr.java b/400_xowa/src/gplx/dbs/percentiles/Log_tbl_fmtr.java index a27517de8..d188b200a 100644 --- a/400_xowa/src/gplx/dbs/percentiles/Log_tbl_fmtr.java +++ b/400_xowa/src/gplx/dbs/percentiles/Log_tbl_fmtr.java @@ -13,3 +13,60 @@ 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.dbs.percentiles; import gplx.*; import gplx.dbs.*; +class Log_tbl_fmtr { + private final Bry_bfr bfr = Bry_bfr_.New(); + private final List_adp itms = List_adp_.New(); + private Log_fld_itm[] ary; + public Log_tbl_fmtr Add_str(String key, int len) {ary = null; itms.Add(new Log_fld_itm__bry(Type_ids_.Id__bry, key, len)); return this;} + public Log_tbl_fmtr Add_int(String key, int bgn, int end) {ary = null; itms.Add(new Log_fld_itm__int(Type_ids_.Id__int, key, bgn, end)); return this;} + public void Log(Object... vals) { + if (ary == null) + ary = (Log_fld_itm[])itms.To_ary_and_clear(Log_fld_itm.class); + int len = ary.length; + for (int i = 0; i < len; ++i) { + Log_fld_itm itm = ary[i]; + Object val = vals[i]; + if (i != 0) bfr.Add_byte_pipe(); + itm.Fmt(bfr, val); + } + bfr.Add_byte_nl(); + } + public String To_str_and_clear() {return bfr.To_str_and_clear();} +} +interface Log_fld_itm { + void Fmt(Bry_bfr bfr, Object val); +} +abstract class Log_fld_itm__base implements Log_fld_itm { + public Log_fld_itm__base(int tid, String key, int len) { + this.tid = tid; this.key = key; this.len = len; + } + public int Tid() {return tid;} private final int tid; + public String Key() {return key;} private final String key; + public int Len() {return len;} protected int len; + public abstract void Fmt(Bry_bfr bfr, Object val); +} +class Log_fld_itm__bry extends Log_fld_itm__base { + public Log_fld_itm__bry(int tid, String key, int len) {super(tid, key, len);} + @Override public void Fmt(Bry_bfr bfr, Object val) { + byte[] val_bry = Bry_.cast(val); + int val_bry_len = val_bry.length; + int pad_len = this.len - val_bry_len; + bfr.Add(val_bry); + if (pad_len > 0) + bfr.Add_byte_repeat(Byte_ascii.Space, pad_len); + } +} +class Log_fld_itm__int extends Log_fld_itm__base { + public Log_fld_itm__int(int tid, String key, int bgn, int end) {super(tid, key, 0); + this.bgn = bgn; this.end = end; + this.len = Int_.DigitCount(end); + } + public int Bgn() {return bgn;} private final int bgn; + public int End() {return end;} private final int end; + @Override public void Fmt(Bry_bfr bfr, Object val) { + int val_int = Int_.Cast(val); + String val_str = String_.PadBgn(Int_.To_str(val_int), this.Len(), " "); + bfr.Add_str_u8(val_str); + } +} diff --git a/400_xowa/src/gplx/dbs/percentiles/Percentile_rng.java b/400_xowa/src/gplx/dbs/percentiles/Percentile_rng.java index a27517de8..2b8c1a1fc 100644 --- a/400_xowa/src/gplx/dbs/percentiles/Percentile_rng.java +++ b/400_xowa/src/gplx/dbs/percentiles/Percentile_rng.java @@ -13,3 +13,80 @@ 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.dbs.percentiles; import gplx.*; import gplx.dbs.*; +public class Percentile_rng { + private long total_max; private int total_needed; + private int score_max, score_len_max; + private long prv_time; + public int Score_bgn() {return score_bgn;} private int score_bgn; + public int Score_end() {return score_end;} private int score_end; + public int Score_len() {return score_len;} private int score_len; + public int Found_rdr() {return found_rdr;} private int found_rdr; + public int Found_all() {return found_all;} private int found_all; + public int Elapsed() {return elapsed;} private int elapsed; + public Percentile_rng Init(long total_max, int score_max) { + this.total_max = total_max; + this.score_max = score_max; + this.score_len_max = score_max / 20; // limit to 5% + return this; + } + public void Select_init(int total_needed, int prv_score_bgn, int prv_score_len, int score_len_adj) { + this.total_needed = total_needed; + this.found_all = 0; + this.prv_time = gplx.core.envs.System_.Ticks(); + int score_unit = Calc_score_unit(total_needed, total_max, score_max); + if (prv_score_bgn == Score_null) { + score_len = score_unit + (score_unit * score_len_adj); + score_bgn = score_max; + Rng_len_(Bool_.Y); + } + else { + score_len = prv_score_len; + score_bgn = prv_score_bgn; + score_end = score_bgn + score_len; + } + } + public void Update(int found_rdr) { + this.found_rdr = found_rdr; + this.found_all += found_rdr; + + // calc rng_multiplier based on found_rdr and total_needed; EX: 100=total_needed; 10=found_rdr; 40=found_all -> 6=rng_multiplier; (100 - 40 / 10) + int rng_multiplier = 1; + if (found_rdr == 0) { + rng_multiplier = 4; + } else { + int total_remaining = total_needed - found_all; + rng_multiplier = total_remaining == 0 ? 1 : Math_.Ceil_as_int(total_remaining / found_rdr); + } + + // calc new score_len + int new_score_len = score_len * rng_multiplier; + if (new_score_len < 1) new_score_len = score_len; + else if (new_score_len > score_len_max) new_score_len = score_len_max; + score_len = new_score_len; + Rng_len_(Bool_.N); + + // update times + long new_time = gplx.core.envs.System_.Ticks(); + this.elapsed = Int_.Subtract_long(new_time, prv_time); + prv_time = new_time; + } + private void Rng_len_(boolean first) { + score_end = score_bgn + (first ? 1 : 0); // + 1 to include rows with scores at max; EX: > 999,998 AND < 1,000,001 + score_bgn = score_end - score_len; + + // note that score_bgn will be first to go negative; EX: (2,000 : 12,000) -> (-8,000 : 2,000) -> (0 : 2,000) -> search still run for 0 - 2000; DATE:2017-04-24 + if (score_bgn < 0) + score_bgn = 0; + + // note that score_end will go to 0 only at end of lookup; EX: (0 : 2,000) -> (-10,000 : -8,000) -> (0 : 0) -> search will not be run; DATE:2017-04-24 + if (score_end < 0) + score_end = 0; + } + @gplx.Internal protected static int Calc_score_unit(int total_needed, long total_max, int score_max) {// TEST: + int rv = (int)Math_.Ceil(Math_.Div_safe_as_double(total_needed, Math_.Div_safe_as_double(total_max, score_max))); // EX: 100 needed / (16 M / 1 M) -> 7 units to fill 100 + if (rv > score_max) rv = score_max; // never allow score_unit to be > score_max; occurs when total_needed > total_max; EX: 50 needed; 10 available + return rv; + } + public static final int Score_null = -1; +} diff --git a/400_xowa/src/gplx/dbs/percentiles/Percentile_rng_log.java b/400_xowa/src/gplx/dbs/percentiles/Percentile_rng_log.java index a27517de8..c176b596e 100644 --- a/400_xowa/src/gplx/dbs/percentiles/Percentile_rng_log.java +++ b/400_xowa/src/gplx/dbs/percentiles/Percentile_rng_log.java @@ -13,3 +13,29 @@ 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.dbs.percentiles; import gplx.*; import gplx.dbs.*; +public class Percentile_rng_log { + private final Log_tbl_fmtr fmtr = new Log_tbl_fmtr(); + private byte[] search; private int rslts_needed; + private int rdr_idx; + public Percentile_rng_log(int score_max) { + fmtr.Add_str("search" , 50) + .Add_int("rslts_needed" , 1, 999) + .Add_int("rdr_idx" , 0, 100) // warn if more than 100 sql queries + .Add_int("score_bgn" , 0, score_max) + .Add_int("score_end" , 0, score_max) + .Add_int("score_len" , 1, 100000) + .Add_int("rdr_found" , 0, 9999) // warn if more than 9.999 seconds + .Add_int("total_found" , 0, 999) + .Add_int("total_needed" , 1, 999) + ; + } + public void Init(byte[] search, int rslts_needed) { + this.search = search; this.rslts_needed = rslts_needed; + rdr_idx = -1; + } + public void Log(int score_bgn, int score_end, int rdr_found, int total_found, int pass_time) { + fmtr.Log(search, rslts_needed, ++rdr_idx, score_bgn, score_end, score_end - score_bgn, rdr_found, total_found, pass_time); + } + public String To_str_and_clear() {return fmtr.To_str_and_clear();} +} diff --git a/400_xowa/src/gplx/dbs/percentiles/Percentile_rng_tst.java b/400_xowa/src/gplx/dbs/percentiles/Percentile_rng_tst.java index a27517de8..05396d203 100644 --- a/400_xowa/src/gplx/dbs/percentiles/Percentile_rng_tst.java +++ b/400_xowa/src/gplx/dbs/percentiles/Percentile_rng_tst.java @@ -13,3 +13,49 @@ 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.dbs.percentiles; import gplx.*; import gplx.dbs.*; +import org.junit.*; +public class Percentile_rng_tst { + private final Percentile_rng_fxt fxt = new Percentile_rng_fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Found__000() { + fxt.Test__rng(999994, 1000001); + fxt.Exec__update( 0).Test__rng(999966, 999994); + fxt.Exec__update( 0).Test__rng(999854, 999966); + } + @Test public void Found__025() { + fxt.Test__rng(999994, 1000001); + fxt.Exec__update( 25).Test__rng(999973, 999994); + fxt.Exec__update( 25).Test__rng(999931, 999973); + fxt.Exec__update( 25).Test__rng(999889, 999931); + fxt.Exec__update( 25).Test__rng(999847, 999889); + } + @Test public void Calc_score_unit() { + fxt.Test__calc_score_unit(50, 16000000, 1000000, 4); // to fill 50 -> 16 pages per point -> read every 4 points to get 64 pages + fxt.Test__calc_score_unit(50, 1000, 1000000, 50000); // to fill 50 -> 1000 points per page -> read every 50k points to get 50 pages + fxt.Test__calc_score_unit(50, 25, 1000000, 1000000); // range bounds check; to fill 50, always read full amount + } +} +class Percentile_rng_fxt { + private final Percentile_rng rng = new Percentile_rng(); + public void Clear() { + this.Exec__init_for_wiki(16000000, 1000000); + this.Exec__init_for_search(100, 0); + } + public Percentile_rng_fxt Exec__init_for_wiki (int pages_max, int score_max) { + rng.Init(pages_max, score_max); return this; + } + public Percentile_rng_fxt Exec__init_for_search(int request_count, int score_len_adj) { + rng.Select_init(request_count, Percentile_rng.Score_null, Percentile_rng.Score_null, score_len_adj); return this; + } + public Percentile_rng_fxt Exec__update(int rdr_found) { + rng.Update(rdr_found); return this; + } + public void Test__rng(int expd_bgn, int expd_end) { + Tfds.Eq(expd_end, rng.Score_end(), "rng_end"); + Tfds.Eq(expd_bgn, rng.Score_bgn(), "rng_bgn"); + } + public void Test__calc_score_unit(int request_count, long pages_max, int score_max, int expd) { + Tfds.Eq(expd, Percentile_rng.Calc_score_unit(request_count, pages_max, score_max)); + } +} diff --git a/400_xowa/src/gplx/dbs/percentiles/Percentile_select_base.java b/400_xowa/src/gplx/dbs/percentiles/Percentile_select_base.java index a27517de8..707129844 100644 --- a/400_xowa/src/gplx/dbs/percentiles/Percentile_select_base.java +++ b/400_xowa/src/gplx/dbs/percentiles/Percentile_select_base.java @@ -13,3 +13,53 @@ 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.dbs.percentiles; import gplx.*; import gplx.dbs.*; +public abstract class Percentile_select_base { // SELECT * FROM x ORDER BY y LIMIT 10; + protected Cancelable cxl; + protected Percentile_rng rng; + protected Percentile_rng_log rng_log; + protected void Select() { + Db_rdr rdr = null; + try { + int rdr_found = 0; + while (true) { + if (cxl.Canceled()) return; + if (rdr == null) { + rdr = Rdr__init(); // EXPENSIVE + rdr_found = 0; + if (cxl.Canceled()) return; + } + if (!Row__read(rdr)) { // EXPENSIVE + if (cxl.Canceled()) return; + rng_log.Log(rng.Score_bgn(), rng.Score_end(), rng.Found_rdr(), rng.Found_all(), rng.Elapsed()); + rdr = Rdr__term(rdr); + Rng__update(rdr_found); + boolean found_enough = Found_enough(); + boolean none_left = rng.Score_end() == 0; // search is done when score_end == 0; note that this is set in Percentile_rng; DATE:2017-04-24 + Rdr__done(found_enough, none_left); + if (found_enough || none_left) + break; + else + continue; // resume from top; will create new rdrd + } + if (Row__eval()) ++rdr_found; + } + } + catch (Exception exc) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "error during percentile; err=~{0}", Err_.Message_gplx_log(exc)); + } + finally { + rdr = Rdr__term(rdr); + } + } + protected abstract Db_rdr Rdr__init(); + @gplx.Virtual protected void Rdr__done(boolean found_enough, boolean none_left) {} + @gplx.Virtual protected Db_rdr Rdr__term(Db_rdr rdr) { + if (rdr != null) rdr.Rls(); + return null; + } + @gplx.Virtual protected void Rng__update(int rdr_found) {rng.Update(rdr_found);} + @gplx.Virtual protected boolean Row__read(Db_rdr rdr) {return true;} + @gplx.Virtual protected boolean Row__eval() {return true;} // NOTE: return true by default; DEPENDENCY: Srch_word_count_wkr + @gplx.Virtual protected boolean Found_enough() {return false;} +} diff --git a/400_xowa/src/gplx/dbs/updates/Sql_runner.java b/400_xowa/src/gplx/dbs/updates/Sql_runner.java index a27517de8..9f74b754d 100644 --- a/400_xowa/src/gplx/dbs/updates/Sql_runner.java +++ b/400_xowa/src/gplx/dbs/updates/Sql_runner.java @@ -13,3 +13,41 @@ 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.dbs.updates; import gplx.*; import gplx.dbs.*; +import gplx.dbs.stmts.*; +/* +sql = +UPDATE page +SET page_score = page_len +WHERE page_id >= ? +AND page_id < ? +state = -1|100000 +*/ +public class Sql_runner { + private final Db_stmt_arg_list list = new Db_stmt_arg_list(); + public Db_conn Conn() {return conn;} public void Conn_(Db_conn v) {conn = v;} private Db_conn conn; + public boolean Quiet() {return quiet;} public void Quiet_(boolean v) {quiet = v;} private boolean quiet; + public String Sql_fmt() {return sql_fmt;} public void Sql_fmt_(String v) {sql_fmt = v;} private String sql_fmt; +// public Db_stmt_arg[] Sql_args() {return sql_args;} public void Sql_args_(Db_stmt_arg[] v) {sql_args = v;} private Db_stmt_arg[] sql_args; + public String Msg() {return msg;} public void Msg_(String v) {msg = v;} private String msg; + public String Fill_next(String state) { + String[] vals = String_.Split(state, "|"); + int val_lo = Int_.Parse(vals[0]); + int interval = Int_.Parse(vals[1]); + int val_hi = val_lo + interval; + + Db_stmt_arg arg = list.Get_at(0); + arg.Val = val_lo; + arg = list.Get_at(1); + arg.Val = val_hi; + + return String_.Concat_with_str("|", Int_.To_str(val_hi), vals[1]); + } + public void Run() { + Db_stmt stmt = conn.Stmt_sql(sql_fmt); + // foreach (itme) Db_stmt_arg_list list = Db_stmt_arg_list + Gfo_usr_dlg_.Instance.Note_many("", "", msg); + stmt.Exec_update(); + // increment ranges + } +} diff --git a/400_xowa/src/gplx/fsdb/Fsdb_db_file.java b/400_xowa/src/gplx/fsdb/Fsdb_db_file.java new file mode 100644 index 000000000..e60c72e2d --- /dev/null +++ b/400_xowa/src/gplx/fsdb/Fsdb_db_file.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.fsdb; import gplx.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; +public class Fsdb_db_file { + public Fsdb_db_file(Io_url url, Db_conn conn) { + this.url = url; this.conn = conn; + this.tbl__core_cfg = gplx.xowa.wikis.data.Xowd_cfg_tbl_.New(conn); + } + public Io_url Url() {return url;} private final Io_url url; + public Db_conn Conn() {return conn;} private final Db_conn conn; + public Db_cfg_tbl Tbl__cfg() {return tbl__core_cfg;} private final Db_cfg_tbl tbl__core_cfg; +} diff --git a/400_xowa/src/gplx/fsdb/Fsdb_db_mgr.java b/400_xowa/src/gplx/fsdb/Fsdb_db_mgr.java new file mode 100644 index 000000000..6ac7899a0 --- /dev/null +++ b/400_xowa/src/gplx/fsdb/Fsdb_db_mgr.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.fsdb; import gplx.*; +import gplx.dbs.*; import gplx.xowa.files.origs.*; +public interface Fsdb_db_mgr { + boolean File__schema_is_1(); + boolean File__solo_file(); + String File__cfg_tbl_name(); + Xof_orig_tbl[] File__orig_tbl_ary(); + Fsdb_db_file File__mnt_file(); + Fsdb_db_file File__abc_file__at(int mnt_id); + Fsdb_db_file File__atr_file__at(int mnt_id); + Fsdb_db_file File__bin_file__at(int mnt_id, int bin_id, String file_name); + Fsdb_db_file File__bin_file__new(int mnt_id, String file_name); +} diff --git a/400_xowa/src/gplx/fsdb/Fsdb_db_mgr_.java b/400_xowa/src/gplx/fsdb/Fsdb_db_mgr_.java new file mode 100644 index 000000000..efb9a8be8 --- /dev/null +++ b/400_xowa/src/gplx/fsdb/Fsdb_db_mgr_.java @@ -0,0 +1,78 @@ +/* +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.fsdb; import gplx.*; +import gplx.dbs.*; import gplx.xowa.*; import gplx.xowa.wikis.data.*; import gplx.fsdb.meta.*; +public class Fsdb_db_mgr_ { + public static Fsdb_db_mgr new_detect(Xow_wiki wiki, Io_url wiki_dir, Io_url file_dir) { + Gfo_usr_dlg usr_dlg = Xoa_app_.Usr_dlg(); + Io_url url = file_dir.GenSubFil(Fsdb_db_mgr__v1.Mnt_name); // EX: /xowa/file/en.wikipedia.org/wiki.mnt.sqlite3 + if (Db_conn_bldr.Instance.Exists(url)) { // NOTE: check v1 before v2; note that as of v2.5.4, v2 files are automatically created on new import; DATE:2015-06-09 + usr_dlg.Log_many("", "", "fsdb.db_core.v1: url=~{0}", url.Raw()); + usr_dlg.Log_many("", "", "fsdb.db_core.v1 exists: orig=~{0} abc=~{1} atr_a=~{2}, atr_b=~{3}" + , Db_conn_bldr.Instance.Exists(file_dir.GenSubFil(Fsdb_db_mgr__v1.Orig_name)) + , Db_conn_bldr.Instance.Exists(file_dir.GenSubFil_nest(Fsm_mnt_tbl.Mnt_name_main, Fsdb_db_mgr__v1.Abc_name)) + , Db_conn_bldr.Instance.Exists(file_dir.GenSubFil_nest(Fsm_mnt_tbl.Mnt_name_main, Fsdb_db_mgr__v1.Atr_name_v1a)) + , Db_conn_bldr.Instance.Exists(file_dir.GenSubFil_nest(Fsm_mnt_tbl.Mnt_name_main, Fsdb_db_mgr__v1.Atr_name_v1b)) + ); + return new Fsdb_db_mgr__v1(file_dir); + } + + // FOLDER.RENAME: handle renamed folders; EX:"/wiki/en.wikipedia.org-2016-12" DATE:2017-02-01 + String domain_str = wiki.Domain_str(); + try { + String cfg_domain_str = wiki.Data__core_mgr().Db__core().Tbl__cfg().Select_str_or("xowa.bldr.session", "wiki_domain", domain_str); + if (!String_.Eq(domain_str, cfg_domain_str)) { + Gfo_usr_dlg_.Instance.Note_many("", "", "fsdb.db_core.init: fsys.domain doesn't match db.domain; fsys=~{0} db=~{1}", domain_str, cfg_domain_str); + domain_str = cfg_domain_str; + } + } catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "fsdb.db_core.init: failed to get domain from config; err=~{0}", Err_.Message_gplx_log(e)); + } + + Fsdb_db_mgr rv = null; + rv = load_or_null(Xow_db_layout.Itm_few, usr_dlg, wiki_dir, wiki, domain_str); if (rv != null) return rv; + rv = load_or_null(Xow_db_layout.Itm_lot, usr_dlg, wiki_dir, wiki, domain_str); if (rv != null) return rv; + rv = load_or_null(Xow_db_layout.Itm_all, usr_dlg, wiki_dir, wiki, domain_str); if (rv != null) return rv; + usr_dlg.Log_many("", "", "fsdb.db_core.none: wiki_dir=~{0} file_dir=~{1}", wiki_dir.Raw(), file_dir.Raw()); + return null; + } + private static Fsdb_db_mgr load_or_null(Xow_db_layout layout, Gfo_usr_dlg usr_dlg, Io_url wiki_dir, Xow_wiki wiki, String domain_str) { + Io_url main_core_url = wiki_dir.GenSubFil(Fsdb_db_mgr__v2_bldr.Main_core_name(layout, domain_str)); + if (!Db_conn_bldr.Instance.Exists(main_core_url)) return null; + usr_dlg.Log_many("", "", "fsdb.db_core.v2: type=~{0} url=~{1}", layout.Key(), main_core_url.Raw()); + Db_conn main_core_conn = Db_conn_bldr.Instance.Get(main_core_url); + if (wiki.Data__core_mgr().Props().Layout_file().Tid_is_all()) { + return new Fsdb_db_mgr__v2(Fsdb_db_mgr__v2.Cfg__layout_file__get(main_core_conn), wiki_dir, new Fsdb_db_file(main_core_url, main_core_conn), new Fsdb_db_file(main_core_url, main_core_conn)); + } + Io_url user_core_url = wiki_dir.GenSubFil(Fsdb_db_mgr__v2_bldr.Make_user_name(domain_str)); + if (!Db_conn_bldr.Instance.Exists(user_core_url)) { // if user file does not exist, create it; needed b/c offline packages don't include file; DATE:2015-04-19 + try {Fsdb_db_mgr__v2_bldr.Make_core_file_user(wiki, user_core_url, user_core_url.NameAndExt(), main_core_url.NameAndExt());} + catch (Exception e) { // do not fail if read-only permissions + usr_dlg.Warn_many("", "", "failed to create user db: url=~{0} err=~{1}", user_core_url.Raw(), Err_.Message_gplx_log(e)); + user_core_url = null; // null out for conditional below + } + } + Db_conn user_core_conn = null; + if (user_core_url == null) { // null when write permissions do not exist; for example, on Andriod; DATE:2016-04-22 + user_core_url = main_core_url; // default to main_core; note that downloading will still fail, but at least app won't crash; DATE:2016-04-22 + user_core_conn = main_core_conn; + } + else { + user_core_conn = Db_conn_bldr.Instance.Get(user_core_url); + } + return new Fsdb_db_mgr__v2(Fsdb_db_mgr__v2.Cfg__layout_file__get(main_core_conn), wiki_dir, new Fsdb_db_file(main_core_url, main_core_conn), new Fsdb_db_file(user_core_url, user_core_conn)); + } +} diff --git a/400_xowa/src/gplx/fsdb/Fsdb_db_mgr__v1.java b/400_xowa/src/gplx/fsdb/Fsdb_db_mgr__v1.java new file mode 100644 index 000000000..ec4ced302 --- /dev/null +++ b/400_xowa/src/gplx/fsdb/Fsdb_db_mgr__v1.java @@ -0,0 +1,111 @@ +/* +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.fsdb; import gplx.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.fsdb.meta.*; import gplx.fsdb.data.*; import gplx.xowa.files.origs.*; +public class Fsdb_db_mgr__v1 implements Fsdb_db_mgr { + private final Io_url file_dir; + private final Fsdb_db_file orig_file, mnt_file, abc_file__main, abc_file__user, atr_file__main, atr_file__user; + private final Xof_orig_tbl[] orig_tbl_ary; + private String bin_prefix__main = "fsdb.bin.", bin_prefix__user = "fsdb.bin."; + public Fsdb_db_mgr__v1(Io_url file_dir) { + this.file_dir = file_dir; + this.orig_file = get_db(file_dir.GenSubFil(Orig_name)); // EX: /xowa/enwiki/wiki.orig#00.sqlite3 + this.mnt_file = get_db(file_dir.GenSubFil(Mnt_name)); // EX: /xowa/enwiki/wiki.mnt.sqlite3 + this.abc_file__main = get_db(file_dir.GenSubFil_nest(Fsm_mnt_tbl.Mnt_name_main, Abc_name)); // EX: /xowa/enwiki/fsdb.main/fsdb.abc.sqlite3 + this.atr_file__main = get_db(Get_atr_db_url(Bool_.Y, file_dir, Fsm_mnt_tbl.Mnt_name_main)); // EX: /xowa/enwiki/fsdb.main/fsdb.atr.00.sqlite3 + if (Db_conn_bldr.Instance.Get(file_dir.GenSubFil_nest(Fsm_mnt_tbl.Mnt_name_user, Abc_name)) == null) // user doesn't exist; create; DATE:2015-04-20 + Fsdb_db_mgr__v1_bldr.Instance.Make_core_dir(file_dir, Fsm_mnt_mgr.Mnt_idx_user, Fsm_mnt_tbl.Mnt_name_user); + this.abc_file__user = get_db(file_dir.GenSubFil_nest(Fsm_mnt_tbl.Mnt_name_user, Abc_name)); // EX: /xowa/enwiki/fsdb.user/fsdb.abc.sqlite3 + this.atr_file__user = get_db(Get_atr_db_url(Bool_.N, file_dir, Fsm_mnt_tbl.Mnt_name_user)); // EX: /xowa/enwiki/fsdb.user/fsdb.atr.00.sqlite3 + this.orig_tbl_ary = new Xof_orig_tbl[] {new Xof_orig_tbl(orig_file.Conn(), this.File__schema_is_1())}; + } + public boolean File__schema_is_1() {return Bool_.Y;} + public boolean File__solo_file() {return Bool_.N;} + public String File__cfg_tbl_name() {return "fsdb_cfg";} + public Xof_orig_tbl[] File__orig_tbl_ary() {return orig_tbl_ary;} + public Fsdb_db_file File__mnt_file() {return mnt_file;} + public Fsdb_db_file File__abc_file__at(int mnt_id) {return mnt_id == Fsm_mnt_mgr.Mnt_idx_main ? abc_file__main : abc_file__user;} + public Fsdb_db_file File__atr_file__at(int mnt_id) {return mnt_id == Fsm_mnt_mgr.Mnt_idx_main ? atr_file__main : atr_file__user;} + public Fsdb_db_file File__bin_file__at(int mnt_id, int bin_id, String file_name) { + boolean mnt_is_main = mnt_id == Fsm_mnt_mgr.Mnt_idx_main; + String bin_name = (mnt_is_main ? bin_prefix__main : bin_prefix__user) + Int_.To_str_pad_bgn_zero(bin_id, 4) + ".sqlite3"; + String mnt_name = mnt_is_main ? Fsm_mnt_tbl.Mnt_name_main : Fsm_mnt_tbl.Mnt_name_user; + Io_url url = file_dir.GenSubFil_nest(mnt_name, bin_name); // EX: /xowa/enwiki/fsdb.main/fsdb.bin.0000.sqlite3 + Db_conn conn = Db_conn_bldr.Instance.Get(url); + if (conn == null) { // NOTE: handle wikis with missing bin files; EX:sv.w missing bin.0010; DATE:2015-07-04 + gplx.xowa.Xoa_app_.Usr_dlg().Warn_many("", "", "fsdb.v1: missing db; db=~{0}", url.Raw()); + return Fsdb_db_mgr__v1_bldr.Instance.new_db__bin(url); + } + else + return new Fsdb_db_file(url, conn); + } + public Fsdb_db_file File__bin_file__new(int mnt_id, String file_name) { + String mnt_name = mnt_id == Fsm_mnt_mgr.Mnt_idx_main ? Fsm_mnt_tbl.Mnt_name_main : Fsm_mnt_tbl.Mnt_name_user; + Io_url url = file_dir.GenSubFil_nest(mnt_name, file_name); // EX: /xowa/enwiki/fsdb.main/fsdb.bin.0000.sqlite3 + Db_conn conn = Db_conn_bldr.Instance.New(url); + Fsd_bin_tbl bin_tbl = new Fsd_bin_tbl(conn, Bool_.Y); bin_tbl.Create_tbl(); + return new Fsdb_db_file(url, conn); + } + private Io_url Get_atr_db_url(boolean main, Io_url file_dir, String mnt_name) { + Io_url rv = null; + rv = file_dir.GenSubFil_nest(mnt_name, Atr_name_v1a); + if (Io_mgr.Instance.ExistsFil(rv)) { + if (main) + bin_prefix__main = "fsdb.bin#"; + else + bin_prefix__user = "fsdb.bin#"; + return rv; + } + rv = file_dir.GenSubFil_nest(mnt_name, Atr_name_v1b); if (Io_mgr.Instance.ExistsFil(rv)) return rv; + throw Err_.new_wo_type("could not find atr file", "dir", file_dir.Raw(), "mnt", mnt_name); + } + public static final String Orig_name = "wiki.orig#00.sqlite3", Mnt_name = "wiki.mnt.sqlite3", Abc_name = "fsdb.abc.sqlite3" + , Atr_name_v1a = "fsdb.atr#00.sqlite3", Atr_name_v1b = "fsdb.atr.00.sqlite3"; + private static Fsdb_db_file get_db(Io_url file) { + Db_conn conn = Db_conn_bldr.Instance.Get(file); + if (conn == null) conn = Db_conn_.Noop; + return new Fsdb_db_file(file, conn); + } +} +class Fsdb_db_mgr__v1_bldr { + public void Make_core_dir(Io_url file_dir, int mnt_id, String mnt_name) { + boolean schema_is_1 = true; + Io_url mnt_dir = file_dir.GenSubDir(mnt_name); + // make abc_fil + Fsdb_db_file db_abc = new_db(mnt_dir.GenSubFil(Fsdb_db_mgr__v1.Abc_name)); + Db_cfg_tbl cfg_tbl = new Db_cfg_tbl(db_abc.Conn(), "fsdb_cfg"); cfg_tbl.Create_tbl(); + Fsm_mnt_mgr.Patch(cfg_tbl); + Fsm_atr_tbl dba_tbl = new Fsm_atr_tbl(db_abc.Conn(), schema_is_1); dba_tbl.Create_tbl(); + dba_tbl.Insert(mnt_id, mnt_name); + Fsm_bin_tbl dbb_tbl = new Fsm_bin_tbl(db_abc.Conn(), schema_is_1, mnt_id); dbb_tbl.Create_tbl(); + dbb_tbl.Insert(0, "fsdb.bin.0000.sqlite3"); + // make atr_fil + Fsdb_db_file db_atr = new_db(mnt_dir.GenSubFil(Fsdb_db_mgr__v1.Atr_name_v1b)); // create atr database in v1b style; "fsdb.atr.00.sqlite3" not "fsdb.atr#00.sqlite3" + Fsd_dir_tbl dir_tbl = new Fsd_dir_tbl(db_atr.Conn(), schema_is_1); dir_tbl.Create_tbl(); + Fsd_fil_tbl fil_tbl = new Fsd_fil_tbl(db_atr.Conn(), schema_is_1, mnt_id); fil_tbl.Create_tbl(); + Fsd_thm_tbl thm_tbl = new Fsd_thm_tbl(db_atr.Conn(), schema_is_1, mnt_id, Bool_.Y); thm_tbl.Create_tbl(); + // make bin_fil + new_db__bin(mnt_dir.GenSubFil("fsdb.bin.0000.sqlite3")); + } + private Fsdb_db_file new_db(Io_url url) {return new Fsdb_db_file(url, Db_conn_bldr.Instance.New(url));} + public Fsdb_db_file new_db__bin(Io_url url) { + Fsdb_db_file rv = new_db(url); + Fsd_bin_tbl bin_tbl = new Fsd_bin_tbl(rv.Conn(), true); // NOTE: schema_is_1 is always true b/c it is in Fsdb_db_mgr__v1_bldr + bin_tbl.Create_tbl(); + return rv; + } + public static final Fsdb_db_mgr__v1_bldr Instance = new Fsdb_db_mgr__v1_bldr(); Fsdb_db_mgr__v1_bldr() {} +} diff --git a/400_xowa/src/gplx/fsdb/Fsdb_db_mgr__v2.java b/400_xowa/src/gplx/fsdb/Fsdb_db_mgr__v2.java new file mode 100644 index 000000000..9aecfa189 --- /dev/null +++ b/400_xowa/src/gplx/fsdb/Fsdb_db_mgr__v2.java @@ -0,0 +1,64 @@ +/* +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.fsdb; import gplx.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.fsdb.meta.*; import gplx.xowa.files.origs.*; import gplx.xowa.wikis.data.*; +public class Fsdb_db_mgr__v2 implements Fsdb_db_mgr { + private final Xow_db_layout layout; private final Io_url wiki_dir; + private final Fsdb_db_file file_main_core, file_user_core; + private final Xof_orig_tbl[] orig_tbl_ary; + public Fsdb_db_mgr__v2(Xow_db_layout layout, Io_url wiki_dir, Fsdb_db_file file_main_core, Fsdb_db_file file_user_core) { + this.layout = layout; this.wiki_dir = wiki_dir; + this.file_main_core = file_main_core; this.file_user_core = file_user_core; + this.orig_tbl_ary = new Xof_orig_tbl[] + { new Xof_orig_tbl(file_main_core.Conn(), this.File__schema_is_1()) + , new Xof_orig_tbl(file_user_core.Conn(), this.File__schema_is_1()) + }; + } + public boolean File__schema_is_1() {return Bool_.N;} + public boolean File__solo_file() {return layout.Tid_is_all_or_few();} + public String File__cfg_tbl_name() {return gplx.xowa.wikis.data.Xowd_cfg_tbl_.Tbl_name;} + public Xof_orig_tbl[] File__orig_tbl_ary() {return orig_tbl_ary;} + public Fsdb_db_file File__mnt_file() {return file_main_core;} + public Fsdb_db_file File__abc_file__at(int mnt_id) {return mnt_id == Fsm_mnt_mgr.Mnt_idx_main ? file_main_core: file_user_core;} + public Fsdb_db_file File__atr_file__at(int mnt_id) {return mnt_id == Fsm_mnt_mgr.Mnt_idx_main ? file_main_core: file_user_core;} + public Fsdb_db_file File__bin_file__at(int mnt_id, int bin_id, String file_name) { + if (mnt_id == Fsm_mnt_mgr.Mnt_idx_user) return file_user_core; + if (layout.Tid_is_all_or_few()) return file_main_core; + Io_url url = wiki_dir.GenSubFil(file_name); + Db_conn conn = Db_conn_bldr.Instance.Get(url); + if (conn == null) { // bin file deleted or not downloaded; use Noop Db_conn and continue; do not fail; DATE:2015-04-16 + Gfo_usr_dlg_.Instance.Warn_many("", "", "fsdb.bin:file does not exist; url=~{0}", url); + conn = Db_conn_.Noop; + } + return new Fsdb_db_file(url, conn); + } + public Fsdb_db_file File__bin_file__new(int mnt_id, String file_name) { + if (mnt_id == Fsm_mnt_mgr.Mnt_idx_user) return Fsdb_db_mgr__v2_bldr.Make_bin_tbl(file_user_core); + if (layout.Tid_is_all_or_few()) return Fsdb_db_mgr__v2_bldr.Make_bin_tbl(file_main_core); + Io_url url = wiki_dir.GenSubFil(file_name); + Db_conn conn = Db_conn_bldr.Instance.New(url); + gplx.xowa.wikis.data.Xowd_cfg_tbl_.New(conn).Create_tbl(); + return Fsdb_db_mgr__v2_bldr.Make_bin_tbl(new Fsdb_db_file(url, conn)); + } + public static Xow_db_layout Cfg__layout_file__get(Db_conn main_core_conn) { + Db_cfg_tbl cfg_tbl = gplx.xowa.wikis.data.Xowd_cfg_tbl_.New(main_core_conn); + return Xow_db_layout.Get_by_name(cfg_tbl.Select_str_or(gplx.xowa.wikis.data.Xowd_cfg_key_.Grp__bldr_fsdb, Cfg_key__layout_file, Xow_db_layout.Key__few)); + } + public static void Cfg__layout_file__set(Db_cfg_tbl cfg_tbl, Xow_db_layout v) { + cfg_tbl.Insert_str(gplx.xowa.wikis.data.Xowd_cfg_key_.Grp__bldr_fsdb, Cfg_key__layout_file, v.Key()); + } + private static final String Cfg_key__layout_file = "layout_file"; +} diff --git a/400_xowa/src/gplx/fsdb/Fsdb_db_mgr__v2_bldr.java b/400_xowa/src/gplx/fsdb/Fsdb_db_mgr__v2_bldr.java new file mode 100644 index 000000000..0a49e82b5 --- /dev/null +++ b/400_xowa/src/gplx/fsdb/Fsdb_db_mgr__v2_bldr.java @@ -0,0 +1,110 @@ +/* +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.fsdb; import gplx.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.fsdb.meta.*; import gplx.fsdb.data.*; import gplx.xowa.files.origs.*; +import gplx.xowa.*; import gplx.xowa.wikis.data.*; import gplx.xowa.bldrs.infos.*; +public class Fsdb_db_mgr__v2_bldr { + public static Fsdb_db_mgr__v2 Get_or_make(Xow_wiki wiki, boolean delete_if_exists) { // NOTE: must check if file exists else imports with existing v2 dbs will fail; DATE:2015-05-23 + Xow_db_layout layout = wiki.Data__core_mgr().Props().Layout_file(); + String domain_str = wiki.Domain_str(); + Io_url wiki_dir = wiki.Fsys_mgr().Root_dir(); + String main_core_name = Main_core_name(layout, domain_str); + String user_core_name = Make_user_name(domain_str); + Io_url main_core_url = wiki_dir.GenSubFil(main_core_name); + Io_url user_core_url = wiki_dir.GenSubFil(user_core_name); + if (delete_if_exists) { + Db_conn_bldr.Instance.Get_or_noop(main_core_url).Rls_conn(); + Db_conn_bldr.Instance.Get_or_noop(user_core_url).Rls_conn(); + Io_mgr.Instance.DeleteFil(main_core_url); + Io_mgr.Instance.DeleteFil(user_core_url); + } + Fsdb_db_file main_core_file = Io_mgr.Instance.ExistsFil(main_core_url) ? Load_core_file(main_core_url) : Make_core_file_main(wiki, main_core_url, main_core_name, layout); + Fsdb_db_file user_core_file = Io_mgr.Instance.ExistsFil(user_core_url) ? Load_core_file(user_core_url) : Make_core_file_user(wiki, user_core_url, user_core_name, main_core_name); + return new Fsdb_db_mgr__v2(layout, wiki_dir, main_core_file, user_core_file); + } + private static Fsdb_db_file Load_core_file(Io_url url) {return new Fsdb_db_file(url, Db_conn_bldr.Instance.Get(url));} + public static Fsdb_db_file Make_core_file_main(Xow_wiki wiki, Io_url main_core_url, String main_core_name, Xow_db_layout layout) { + Db_conn conn = layout.Tid_is_all() ? Db_conn_bldr.Instance.Get(main_core_url) : Db_conn_bldr.Instance.New(main_core_url); // if all, use existing (assumes same file name); else, create new + conn.Txn_bgn("fsdb__core_file"); + Fsdb_db_file rv = Make_core_file(main_core_url, conn, schema_is_1, Fsm_mnt_mgr.Mnt_idx_main); + if (!layout.Tid_is_all()) // do not make cfg data if all + Make_cfg_data(wiki, main_core_name, rv, Main_core_tid(layout), -1); + Fsdb_db_mgr__v2.Cfg__layout_file__set(rv.Tbl__cfg(), layout); + conn.Txn_end(); + return rv; + } + public static Fsdb_db_file Make_core_file_user(Xow_wiki wiki, Io_url user_core_url, String user_file_name, String main_core_name) { // always create file; do not create mnt_tbl; + Db_conn conn = Db_conn_bldr.Instance.New(user_core_url); + conn.Txn_bgn("fsdb__core_user"); + Fsdb_db_file rv = Make_core_file(user_core_url, conn, schema_is_1, Fsm_mnt_mgr.Mnt_idx_user); + Fsm_bin_tbl dbb_tbl = new Fsm_bin_tbl(conn, schema_is_1, Fsm_mnt_mgr.Mnt_idx_user); dbb_tbl.Insert(0, user_file_name); + Make_bin_tbl(rv); + Make_cfg_data(wiki, main_core_name, rv, Xow_db_file_.Tid__file_user, -1); + conn.Txn_end(); + return rv; + } + private static Fsdb_db_file Make_core_file(Io_url core_url, Db_conn core_conn, boolean schema_is_1, int mnt_id) { + Fsdb_db_file rv = new Fsdb_db_file(core_url, core_conn); + Db_cfg_tbl cfg_tbl = rv.Tbl__cfg(); + cfg_tbl.Create_tbl(); + Fsm_mnt_mgr.Patch(cfg_tbl); + Fsm_mnt_mgr.Patch_core(cfg_tbl); + Xof_orig_tbl orig_tbl = new Xof_orig_tbl(core_conn, schema_is_1); orig_tbl.Create_tbl(); + if (mnt_id == Fsm_mnt_mgr.Mnt_idx_main) { + Fsm_mnt_tbl mnt_tbl = new Fsm_mnt_tbl(core_conn, schema_is_1); mnt_tbl.Create_tbl(); + cfg_tbl.Insert_int("core", "mnt.insert_idx", Fsm_mnt_mgr.Mnt_idx_user); + } + Fsm_atr_tbl dba_tbl = new Fsm_atr_tbl(core_conn, schema_is_1); dba_tbl.Create_tbl(); + dba_tbl.Insert(mnt_id, core_url.NameAndExt()); + Fsm_bin_tbl dbb_tbl = new Fsm_bin_tbl(core_conn, schema_is_1, mnt_id); dbb_tbl.Create_tbl(); + Fsd_dir_tbl dir_tbl = new Fsd_dir_tbl(core_conn, schema_is_1); dir_tbl.Create_tbl(); + Fsd_fil_tbl fil_tbl = new Fsd_fil_tbl(core_conn, schema_is_1, mnt_id); fil_tbl.Create_tbl(); + Fsd_thm_tbl thm_tbl = new Fsd_thm_tbl(core_conn, schema_is_1, mnt_id, Bool_.Y); thm_tbl.Create_tbl(); + return rv; + } + public static Fsdb_db_file Make_bin_tbl(Fsdb_db_file file) { + Fsd_bin_tbl bin_tbl = new Fsd_bin_tbl(file.Conn(), schema_is_1); bin_tbl.Create_tbl(); + return file; + } + public static String Main_core_name(Xow_db_layout layout, String wiki_domain) { + switch (layout.Tid()) { + case Xow_db_layout.Tid__all: return Main_core_name_all(wiki_domain); + case Xow_db_layout.Tid__few: return Main_core_name_few(wiki_domain); + case Xow_db_layout.Tid__lot: return Main_core_name_lot(wiki_domain); + default: throw Err_.new_unimplemented(); + } + } + private static byte Main_core_tid(Xow_db_layout layout) { + switch (layout.Tid()) { + case Xow_db_layout.Tid__all: return Xow_db_file_.Tid__core; + case Xow_db_layout.Tid__few: return Xow_db_file_.Tid__file_solo; + case Xow_db_layout.Tid__lot: return Xow_db_file_.Tid__file_core; + default: throw Err_.new_unimplemented(); + } + } + public static void Make_cfg_data(Xow_wiki wiki, String file_core_name, Fsdb_db_file file, byte file_tid, int part_id) { + Db_cfg_tbl cfg_tbl = file.Tbl__cfg(); + Xow_db_file core_db = wiki.Data__core_mgr().Db__core(); + core_db.Info_session().Save(cfg_tbl); + Xob_info_file info_file = new Xob_info_file(-1, Xow_db_file_.To_key(file_tid), Xob_info_file.Ns_ids_empty, part_id, Guid_adp_.New(), 2, file_core_name, file.Url().NameAndExt()); + info_file.Save(cfg_tbl); + } + private static String Main_core_name_all(String wiki_domain) {return wiki_domain + ".xowa";} // EX: en.wikipedia.org.xowa + private static String Main_core_name_few(String wiki_domain) {return wiki_domain + "-file.xowa";} // EX: en.wikipedia.org-file.xowa + private static String Main_core_name_lot(String wiki_domain) {return wiki_domain + "-file-core.xowa";} // EX: en.wikipedia.org-file-core.xowa + public static String Make_user_name(String wiki_domain) {return wiki_domain + "-file-user.xowa";} // EX: en.wikipedia.org-file-user.xowa + private static final boolean schema_is_1 = false; + } diff --git a/400_xowa/src/gplx/fsdb/data/Fsd_bin_itm.java b/400_xowa/src/gplx/fsdb/data/Fsd_bin_itm.java index a27517de8..963b6ee2b 100644 --- a/400_xowa/src/gplx/fsdb/data/Fsd_bin_itm.java +++ b/400_xowa/src/gplx/fsdb/data/Fsd_bin_itm.java @@ -13,3 +13,20 @@ 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.fsdb.data; import gplx.*; import gplx.fsdb.*; +public class Fsd_bin_itm { + public Fsd_bin_itm(int bin_owner_id, byte bin_owner_tid, int bin_part_id, String bin_data_url, byte[] bin_data) { + this.bin_owner_id = bin_owner_id; + this.bin_owner_tid = bin_owner_tid; + this.bin_part_id = bin_part_id; + this.bin_data_url = bin_data_url; + this.bin_data = bin_data; + } + public int Bin_owner_id() {return bin_owner_id;} private final int bin_owner_id; + public byte Bin_owner_tid() {return bin_owner_tid;} private final byte bin_owner_tid; + public int Bin_part_id() {return bin_part_id;} private final int bin_part_id; + public String Bin_data_url() {return bin_data_url;} private final String bin_data_url; + public byte[] Bin_data() {return bin_data;} private final byte[] bin_data; + + public static final int Db_row_size_fixed = (3 * 4); // bin_owner_id, bin_part_id, bin_owner_tid (assume byte saved as int in SQLITE) +} diff --git a/400_xowa/src/gplx/fsdb/data/Fsd_bin_tbl.java b/400_xowa/src/gplx/fsdb/data/Fsd_bin_tbl.java index a27517de8..ea914d419 100644 --- a/400_xowa/src/gplx/fsdb/data/Fsd_bin_tbl.java +++ b/400_xowa/src/gplx/fsdb/data/Fsd_bin_tbl.java @@ -13,3 +13,108 @@ 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.fsdb.data; import gplx.*; import gplx.fsdb.*; +import gplx.core.primitives.*; import gplx.core.envs.*; +import gplx.dbs.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +import gplx.dbs.engines.sqlite.*; +public class Fsd_bin_tbl implements Rls_able { + public final String fld__owner_id, fld__owner_tid, fld__part_id, fld__data_url, fld__data; + private Db_conn conn; private Db_stmt stmt_insert, stmt_select, stmt_select_itm; private Bry_bfr tmp_bfr; + private final Bool_obj_ref saved_in_parts = Bool_obj_ref.n_(); + public Fsd_bin_tbl(Db_conn conn, boolean schema_is_1) { + this.conn = conn; + fld__owner_id = flds.Add_int_pkey ("bin_owner_id"); + fld__owner_tid = flds.Add_byte ("bin_owner_tid"); + fld__part_id = flds.Add_int ("bin_part_id"); + fld__data_url = flds.Add_str ("bin_data_url", 255); + fld__data = flds.Add_bry ("bin_data"); // mediumblob + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name = "fsdb_bin"; + public Dbmeta_fld_list Flds() {return flds;} private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + stmt_select = Db_stmt_.Rls(stmt_select); + stmt_select_itm = Db_stmt_.Rls(stmt_select_itm); + } + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Insert_bgn() {conn.Txn_bgn("fsdb_bin__insert"); stmt_insert = conn.Stmt_insert(tbl_name, flds);} + public void Insert_commit() {conn.Txn_sav();} + public void Insert_end() {conn.Txn_end(); stmt_insert = Db_stmt_.Rls(stmt_insert);} + public void Insert_rdr(int id, byte tid, long bin_len, Io_stream_rdr bin_rdr) { + if (stmt_insert == null) { + stmt_insert = conn.Stmt_insert(tbl_name, flds); + tmp_bfr = Bry_bfr_.Reset(Io_mgr.Len_kb); + } + byte[] bin_ary = Io_stream_rdr_.Load_all_as_bry(tmp_bfr, bin_rdr); + stmt_insert.Clear() + .Val_int(fld__owner_id, id) + .Val_byte(fld__owner_tid, tid) + .Val_int(fld__part_id, Part_id_null) + .Val_str(fld__data_url, Data_url_null) + .Val_bry(fld__data, bin_ary) + .Exec_insert(); + } + public void Update(Db_stmt stmt, int id, byte[] data) { + stmt.Clear().Val_bry(fld__data, data).Crt_int(fld__owner_id, id).Exec_update(); + } + public Io_stream_rdr Select_as_rdr(int owner_id) { + byte[] rv = Select(owner_id, null); + return rv == null + ? Io_stream_rdr_.Noop + : Io_stream_rdr_.New__mem(rv); + } + public boolean Select_to_url(int owner_id, Io_url url) { + saved_in_parts.Val_n(); + byte[] rv = Select(owner_id, url); + if (rv == null) return false; + if (saved_in_parts.Val_y()) return true; + Io_mgr.Instance.SaveFilBry(url, rv); + return true; + } + private byte[] Select(int owner_id, Io_url url) { + if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, String_.Ary(fld__data), fld__owner_id); + Db_rdr rdr = stmt_select.Clear().Crt_int(fld__owner_id, owner_id).Exec_select__rls_manual(); + try { + if (rdr.Move_next()) { + byte[] rv = null; + try {rv = rdr.Read_bry(fld__data);} + catch (Exception e) { + if ( Op_sys.Cur().Tid_is_drd() // drd error when selecting large blobs (> 4 MB?) + && url != null // called by Select_to_url + && String_.Has(Err_.Message_lang(e), "get field slot from row") // get field slot from row 0 col 0 failed + ) { + rdr.Save_bry_in_parts(url, tbl_name, fld__data, fld__owner_id, owner_id); + saved_in_parts.Val_y_(); + } + } + return rv == null ? Bry_.Empty : rv; // NOTE: bug in v0.10.1 where .ogg would save as null; return Bry_.Empty instead, else java.io.ByteArrayInputStream would fail on null + } + else + return null; + } + finally {rdr.Rls();} + } + public Fsd_bin_itm Select_as_itm(int owner_id) { + if (stmt_select_itm == null) stmt_select_itm = conn.Stmt_select(tbl_name, flds, fld__owner_id); + Db_rdr rdr = stmt_select_itm.Clear().Crt_int(fld__owner_id, owner_id).Exec_select__rls_manual(); + try { + if (rdr.Move_next()) { + return new Fsd_bin_itm + ( rdr.Read_int(fld__owner_id) + , rdr.Read_byte(fld__owner_tid) + , rdr.Read_int(fld__part_id) + , rdr.Read_str(fld__data_url) + , rdr.Read_bry(fld__data) + ); + } + else + return null; + } + finally {rdr.Rls();} + } + public static final byte Owner_tid_fil = 1, Owner_tid_thm = 2; + public static final int Bin_db_id_null = -1, Size_null = -1; + private static final int Part_id_null = -1; + private static final String Data_url_null = ""; +} diff --git a/400_xowa/src/gplx/fsdb/data/Fsd_dir_itm.java b/400_xowa/src/gplx/fsdb/data/Fsd_dir_itm.java index a27517de8..7b47f1041 100644 --- a/400_xowa/src/gplx/fsdb/data/Fsd_dir_itm.java +++ b/400_xowa/src/gplx/fsdb/data/Fsd_dir_itm.java @@ -13,3 +13,17 @@ 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.fsdb.data; import gplx.*; import gplx.fsdb.*; +public class Fsd_dir_itm { + public Fsd_dir_itm(int dir_id, int owner, byte[] name) { + this.dir_id = dir_id; + this.owner = owner; + this.name = name; + } + public int Dir_id() {return dir_id;} private final int dir_id; + public int Owner() {return owner;} private final int owner; + public byte[] Name() {return name;} private final byte[] name; + + public static final int Owner_root = 0; + public static final Fsd_dir_itm Null = null; +} diff --git a/400_xowa/src/gplx/fsdb/data/Fsd_dir_tbl.java b/400_xowa/src/gplx/fsdb/data/Fsd_dir_tbl.java index a27517de8..48b054979 100644 --- a/400_xowa/src/gplx/fsdb/data/Fsd_dir_tbl.java +++ b/400_xowa/src/gplx/fsdb/data/Fsd_dir_tbl.java @@ -13,3 +13,55 @@ 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.fsdb.data; import gplx.*; import gplx.fsdb.*; +import gplx.dbs.*; +public class Fsd_dir_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_id, fld_owner_id, fld_name; + private final Db_conn conn; private Db_stmt stmt_select_by_name; + public Fsd_dir_tbl(Db_conn conn, boolean schema_is_1) { + this.conn = conn; + this.fld_id = flds.Add_int_pkey ("dir_id"); + this.fld_owner_id = flds.Add_int ("dir_owner_id"); + this.fld_name = flds.Add_str ("dir_name", 255); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name = "fsdb_dir"; + public void Create_tbl() { + conn.Meta_tbl_create + ( Dbmeta_tbl_itm.New(tbl_name, flds + , Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "name", fld_name, fld_owner_id, fld_id))); + } + public void Insert(int id, byte[] name, int owner_id) { + Db_stmt stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Clear() + .Val_int(fld_id, id) + .Val_int(fld_owner_id, owner_id) + .Val_bry_as_str(fld_name, name) + .Exec_insert(); + stmt_insert.Rls(); + } + public void Update(int id, byte[] name, int owner_id) { + Db_stmt stmt_update = conn.Stmt_update_exclude(tbl_name, flds, fld_id); + stmt_update.Clear() + .Val_int(fld_owner_id, owner_id) + .Val_bry_as_str(fld_name, name) + .Crt_int(fld_id, id) + .Exec_update(); + stmt_update.Rls(); + } + public Fsd_dir_itm Select_or_null(byte[] name) { + if (stmt_select_by_name == null) stmt_select_by_name = conn.Stmt_select(tbl_name, flds, fld_name); + Db_rdr rdr = stmt_select_by_name.Clear().Crt_bry_as_str(fld_name, name).Exec_select__rls_manual(); + try { + return rdr.Move_next() + ? new Fsd_dir_itm(rdr.Read_int(fld_id), rdr.Read_int(fld_owner_id), name) + : Fsd_dir_itm.Null + ; + } + finally {rdr.Rls();} + } + public void Rls() { + stmt_select_by_name = Db_stmt_.Rls(stmt_select_by_name); + } +} diff --git a/400_xowa/src/gplx/fsdb/data/Fsd_fil_itm.java b/400_xowa/src/gplx/fsdb/data/Fsd_fil_itm.java index a27517de8..f4d2a1aae 100644 --- a/400_xowa/src/gplx/fsdb/data/Fsd_fil_itm.java +++ b/400_xowa/src/gplx/fsdb/data/Fsd_fil_itm.java @@ -13,3 +13,33 @@ 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.fsdb.data; import gplx.*; import gplx.fsdb.*; +public class Fsd_fil_itm { + public Fsd_fil_itm (int mnt_id, int dir_id, int fil_id, int xtn_id, int ext_id, byte[] name, long size, String modified_on, String hash_md5, int bin_db_id) { + this.mnt_id = mnt_id; this.dir_id = dir_id; this.fil_id = fil_id; this.xtn_id = xtn_id; this.ext_id = ext_id; + this.name = name; this.size = size; this.modified_on = modified_on; this.hash_md5 = hash_md5; this.bin_db_id = bin_db_id; + } + public int Mnt_id() {return mnt_id;} private final int mnt_id; + public int Dir_id() {return dir_id;} private final int dir_id; + public int Fil_id() {return fil_id;} private final int fil_id; + public int Xtn_id() {return xtn_id;} private final int xtn_id; + public int Ext_id() {return ext_id;} private final int ext_id; + public byte[] Name() {return name;} private final byte[] name; + public long Size() {return size;} private final long size; + public String Modified_on() {return modified_on;} private final String modified_on; + public String Hash_md5() {return hash_md5;} private final String hash_md5; + public int Bin_db_id() {return bin_db_id;} private final int bin_db_id; + + public int Db_row_size() {return Db_row_size_fixed + name.length;} + private static final int Db_row_size_fixed = + (7 * 4) // 6 int fields + 1 byte field + + 8 // 1 long field + + 32 // hash_md5 + + 14 // modified_on + ; + + public static final Fsd_fil_itm Null = null; + public static byte[] Gen_cache_key(Bry_bfr bfr, int dir_id, byte[] name) { + return bfr.Add_int_variable(dir_id).Add_byte_pipe().Add(name).To_bry_and_clear(); + } +} diff --git a/400_xowa/src/gplx/fsdb/data/Fsd_fil_tbl.java b/400_xowa/src/gplx/fsdb/data/Fsd_fil_tbl.java index a27517de8..a88bb53c9 100644 --- a/400_xowa/src/gplx/fsdb/data/Fsd_fil_tbl.java +++ b/400_xowa/src/gplx/fsdb/data/Fsd_fil_tbl.java @@ -13,3 +13,106 @@ 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.fsdb.data; import gplx.*; import gplx.fsdb.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.dbs.engines.sqlite.*; +public class Fsd_fil_tbl implements Db_tbl { + public final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + public final String fld_id, fld_owner_id, fld_name, fld_xtn_id, fld_ext_id, fld_size, fld_modified, fld_hash, fld_bin_db_id; + private final String idx_owner; + public final Db_conn conn; private Db_stmt stmt_insert, stmt_update, stmt_select_by_name; private int mnt_id; + public Fsd_fil_tbl(Db_conn conn, boolean schema_is_1, int mnt_id) { + this.conn = conn; this.mnt_id = mnt_id; + this.fld_id = flds.Add_int_pkey ("fil_id"); + this.fld_owner_id = flds.Add_int ("fil_owner_id"); + this.fld_xtn_id = flds.Add_int ("fil_xtn_id"); + this.fld_ext_id = flds.Add_int ("fil_ext_id"); + this.fld_bin_db_id = flds.Add_int ("fil_bin_db_id"); // group ints at beginning of table + this.fld_name = flds.Add_str ("fil_name", 255); + this.fld_size = flds.Add_long ("fil_size"); + this.fld_modified = flds.Add_str ("fil_modified", 14); // stored as yyyyMMddHHmmss + this.fld_hash = flds.Add_str ("fil_hash", 40); + this.idx_owner = Dbmeta_idx_itm.Bld_idx_name(tbl_name, "owner"); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name = "fsdb_fil"; + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + stmt_update = Db_stmt_.Rls(stmt_update); + stmt_select_by_name = Db_stmt_.Rls(stmt_select_by_name); + } + public void Create_tbl() { + conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds + , Dbmeta_idx_itm.new_unique_by_name(tbl_name, idx_owner, fld_owner_id, fld_name, fld_id) + )); + } + public void Insert(int id, int owner_id, byte[] name, int xtn_id, int ext_id, long size, int bin_db_id) { + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + Insert(stmt_insert, id, owner_id, name, xtn_id, ext_id, size, bin_db_id, String_.Empty, String_.Empty); + } + public void Insert(Db_stmt stmt, int id, int owner_id, byte[] name, int xtn_id, int ext_id, long size, int bin_db_id, String modified_on, String hash_md5) { + stmt.Clear() + .Val_int(fld_id, id) + .Val_int(fld_owner_id, owner_id) + .Val_int(fld_xtn_id, xtn_id) + .Val_int(fld_ext_id, ext_id) + .Val_int(fld_bin_db_id, bin_db_id) + .Val_bry_as_str(fld_name, name) + .Val_long(fld_size, size) + .Val_str(fld_modified, modified_on) + .Val_str(fld_hash, hash_md5) + .Exec_insert(); + } + public void Update(int id, int owner_id, byte[] name, int xtn_id, int ext_id, long size, int bin_db_id) { + if (stmt_update == null) stmt_update = conn.Stmt_update_exclude(tbl_name, flds, fld_id); + stmt_update.Clear() + .Val_int(fld_owner_id, owner_id) + .Val_int(fld_xtn_id, xtn_id) + .Val_int(fld_ext_id, ext_id) + .Val_int(fld_bin_db_id, bin_db_id) + .Val_bry_as_str(fld_name, name) + .Val_long(fld_size, size) + .Val_str(fld_modified, String_.Empty) + .Val_str(fld_hash, String_.Empty) + .Crt_int(fld_id, id) + .Exec_update(); + } + public Fsd_fil_itm Select_or_null(int dir_id, byte[] fil_name) { + if (stmt_select_by_name == null) { + Db_qry__select_cmd qry = new Db_qry__select_cmd().From_(tbl_name).Cols_(flds.To_str_ary()).Where_(Db_crt_.eq_many_(fld_owner_id, fld_name)).Indexed_by_(idx_owner); + stmt_select_by_name = conn.Stmt_new(qry); + } + Db_rdr rdr = stmt_select_by_name.Clear() + .Crt_int(fld_owner_id, dir_id) + .Crt_bry_as_str(fld_name, fil_name) + .Exec_select__rls_manual(); + try { + return rdr.Move_next() ? New_by_rdr(mnt_id, rdr) : Fsd_fil_itm.Null; + } + finally {rdr.Rls();} + } + public void Select_all(Bry_bfr key_bfr, gplx.core.caches.Gfo_cache_mgr_bry cache) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, Dbmeta_fld_itm.Str_ary_empty).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + Fsd_fil_itm fil = New_by_rdr(mnt_id, rdr); + byte[] cache_key = Fsd_fil_itm.Gen_cache_key(key_bfr, fil.Dir_id(), fil.Name()); + cache.Add(cache_key, fil); + } + } + finally {rdr.Rls();} + } + public Fsd_fil_itm New_by_rdr(int mnt_id, Db_rdr rdr) { + return new Fsd_fil_itm + ( mnt_id + , rdr.Read_int(fld_owner_id) + , rdr.Read_int(fld_id) + , rdr.Read_int(fld_xtn_id) + , rdr.Read_int(fld_ext_id) + , rdr.Read_bry_by_str(fld_name) + , rdr.Read_long(fld_size) + , rdr.Read_str(fld_modified) + , rdr.Read_str(fld_hash) + , rdr.Read_int(fld_bin_db_id) + ); + } +} diff --git a/400_xowa/src/gplx/fsdb/data/Fsd_img_itm.java b/400_xowa/src/gplx/fsdb/data/Fsd_img_itm.java index a27517de8..de21d0df2 100644 --- a/400_xowa/src/gplx/fsdb/data/Fsd_img_itm.java +++ b/400_xowa/src/gplx/fsdb/data/Fsd_img_itm.java @@ -13,3 +13,13 @@ 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.fsdb.data; import gplx.*; import gplx.fsdb.*; +public class Fsd_img_itm { + public Fsd_img_itm(int mnt_id, int dir_id, int fil_id, int bin_db_id) { + this.mnt_id = mnt_id; this.dir_id = dir_id; this.fil_id = fil_id; this.bin_db_id = bin_db_id; + } + public int Mnt_id() {return mnt_id;} private final int mnt_id; + public int Dir_id() {return dir_id;} private final int dir_id; + public int Fil_id() {return fil_id;} private final int fil_id; + public int Bin_db_id() {return bin_db_id;} private final int bin_db_id; +} diff --git a/400_xowa/src/gplx/fsdb/data/Fsd_thm_itm.java b/400_xowa/src/gplx/fsdb/data/Fsd_thm_itm.java index a27517de8..b96cbec96 100644 --- a/400_xowa/src/gplx/fsdb/data/Fsd_thm_itm.java +++ b/400_xowa/src/gplx/fsdb/data/Fsd_thm_itm.java @@ -13,3 +13,54 @@ 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.fsdb.data; import gplx.*; import gplx.fsdb.*; +public class Fsd_thm_itm { + public void Ctor(int mnt_id, int dir_id, int fil_id, int thm_id, int bin_db_id, int w, int h, double time, int page, long size, String modified, String hash) { + this.mnt_id = mnt_id; this.dir_id = dir_id; this.fil_id = fil_id; this.thm_id = thm_id; this.bin_db_id = bin_db_id; + this.w = w; this.h = h; this.time = time; this.page = page; + this.size = size; this.modified = modified; this.hash = hash; + } + public int Mnt_id() {return mnt_id;} private int mnt_id; + public int Dir_id() {return dir_id;} private int dir_id; + public int Fil_id() {return fil_id;} private int fil_id; + public int Thm_id() {return thm_id;} private int thm_id; + public int Bin_db_id() {return bin_db_id;} private int bin_db_id; + public int W() {return w;} private int w; + public int H() {return h;} private int h; + public double Time() {return time;} private double time; + public int Page() {return page;} private int page; + public long Size() {return size;} private long size; + public String Modified() {return modified;} private String modified; + public String Hash() {return hash;} private String hash; + public int Req_w() {return req_w;} private int req_w; + public double Req_time() {return req_time;} private double req_time; + public int Req_page() {return req_page;} private int req_page; + public void Init_by_req(int w, double time, int page) {this.w = w; this.time = time; this.page = page;} + public void Init_by_match(Fsd_thm_itm comp) { + this.req_w = w; this.req_time = time; this.req_page = page; + this.mnt_id = comp.mnt_id; this.dir_id = comp.dir_id; this.fil_id = comp.fil_id; this.thm_id = comp.thm_id; this.bin_db_id = comp.bin_db_id; + this.w = comp.w; this.h = comp.h; this.time = comp.time; this.page = comp.page; + this.size = comp.size; this.modified = comp.modified; this.hash = comp.hash; + } + public int Db_row_size() {return Db_row_size_fixed;} + private static final int Db_row_size_fixed = + (7 * 4) // 7 ints + + (2 * 8) // 1 long; 1 double + + 32 // hash_md5 + + 14 // modified_on + ; + + public static final Fsd_thm_itm Null = null; + public static final Fsd_thm_itm[] Ary_empty = new Fsd_thm_itm[0]; + public static Fsd_thm_itm new_() {return new Fsd_thm_itm();} Fsd_thm_itm() {} +} +class Fsdb_thm_itm_sorter implements gplx.core.lists.ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + Fsd_thm_itm lhs = (Fsd_thm_itm)lhsObj; + Fsd_thm_itm rhs = (Fsd_thm_itm)rhsObj; + int comp = Int_.Compare (lhs.W() , rhs.W()); if (comp != CompareAble_.Same) return -comp; // sort by decreasing width + comp = Double_.Compare (lhs.Time() , rhs.Time()); if (comp != CompareAble_.Same) return comp; // sort by increasing time + return Int_.Compare (lhs.Page() , rhs.Page()); // sort by increasing page + } + public static final Fsdb_thm_itm_sorter Instance = new Fsdb_thm_itm_sorter(); Fsdb_thm_itm_sorter() {} +} diff --git a/400_xowa/src/gplx/fsdb/data/Fsd_thm_tbl.java b/400_xowa/src/gplx/fsdb/data/Fsd_thm_tbl.java index a27517de8..6fd15c97a 100644 --- a/400_xowa/src/gplx/fsdb/data/Fsd_thm_tbl.java +++ b/400_xowa/src/gplx/fsdb/data/Fsd_thm_tbl.java @@ -13,3 +13,145 @@ 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.fsdb.data; import gplx.*; import gplx.fsdb.*; +import gplx.dbs.*; import gplx.fsdb.meta.*; import gplx.xowa.files.*; +public class Fsd_thm_tbl implements Db_tbl { + public final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + public final String fld_id, fld_owner_id, fld_w, fld_h, fld_time, fld_page, fld_bin_db_id, fld_size, fld_modified, fld_hash; + public final Db_conn conn; private Db_stmt stmt_insert, stmt_select_by_fil_exact, stmt_select_by_fil_near; private int mnt_id; private boolean schema_thm_page; + public Fsd_thm_tbl(Db_conn conn, boolean schema_is_1, int mnt_id, boolean schema_thm_page) { + this.conn = conn; this.mnt_id = mnt_id; this.schema_thm_page = schema_thm_page; + this.tbl_name = schema_is_1 ? "fsdb_xtn_thm" : "fsdb_thm"; + this.fld_id = flds.Add_int_pkey ("thm_id"); + this.fld_owner_id = flds.Add_int ("thm_owner_id"); + this.fld_w = flds.Add_int ("thm_w"); + this.fld_h = flds.Add_int ("thm_h"); + if (schema_thm_page) { + this.fld_time = flds.Add_double ("thm_time"); + this.fld_page = flds.Add_int ("thm_page"); + } + else { + this.fld_time = flds.Add_int ("thm_thumbtime"); + this.fld_page = Dbmeta_fld_itm.Key_null; + } + this.fld_bin_db_id = flds.Add_int ("thm_bin_db_id"); + this.fld_size = flds.Add_long ("thm_size"); + this.fld_modified = flds.Add_str ("thm_modified", 14); // stored as yyyyMMddHHmmss + this.fld_hash = flds.Add_str ("thm_hash", 40); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() { + conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds + , Dbmeta_idx_itm.new_unique_by_tbl(tbl_name, "owner", fld_owner_id, fld_id, fld_w, fld_time, fld_page) + )); + } + public void Insert(int id, int thm_owner_id, int width, int height, double thumbtime, int page, int bin_db_id, long size) { + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + this.Insert(stmt_insert, id, thm_owner_id, width, height, thumbtime, page, bin_db_id, size, Modified_null_str, Hash_null); + } + public void Insert(Db_stmt stmt, int id, int thm_owner_id, int width, int height, double thumbtime, int page, int bin_db_id, long size, String modified, String hash_md5) { + stmt.Clear() + .Val_int(fld_id, id) + .Val_int(fld_owner_id, thm_owner_id) + .Val_int(fld_w, width) + .Val_int(fld_h, height); + if (schema_thm_page) { + stmt.Val_double (fld_time, Xof_lnki_time.Db_save_double(thumbtime)); + stmt.Val_int (fld_page, Xof_lnki_page.Db_save_int(page)); + } + else + stmt.Val_int (fld_time, Xof_lnki_time.Db_save_int(thumbtime)); + stmt + .Val_int(fld_bin_db_id, bin_db_id) + .Val_long(fld_size, size) + .Val_str(fld_modified, modified) + .Val_str(fld_hash, hash_md5) + .Exec_insert(); + } + public boolean Select_itm_by_w_exact(int dir_id, int fil_id, Fsd_thm_itm thm) { + if (stmt_select_by_fil_exact == null) stmt_select_by_fil_exact = conn.Stmt_select(tbl_name, flds, String_.Ary_wo_null(fld_owner_id, fld_w, fld_time, fld_page)); + stmt_select_by_fil_exact.Clear().Crt_int(fld_owner_id, fil_id).Crt_int(fld_w, thm.W()); + if (schema_thm_page) { + stmt_select_by_fil_exact.Crt_double (fld_time, Xof_lnki_time.Db_save_double(thm.Time())); + stmt_select_by_fil_exact.Crt_int (fld_page, Xof_lnki_page.Db_save_int(thm.Page())); + } + else { + stmt_select_by_fil_exact.Crt_int (fld_time, Xof_lnki_time.Db_save_int(thm.Time())); + } + Db_rdr rdr = stmt_select_by_fil_exact.Exec_select__rls_manual(); + try { + return rdr.Move_next() + ? Ctor_by_load(thm, rdr, dir_id) + : false; + } + finally {rdr.Rls();} + } + public boolean Select_itm_by_w_near(int dir_id, int fil_id, Fsd_thm_itm thm) { + if (stmt_select_by_fil_near == null) stmt_select_by_fil_near = conn.Stmt_select(tbl_name, flds, fld_owner_id); + List_adp list = List_adp_.New(); + Db_rdr rdr = stmt_select_by_fil_near.Clear().Crt_int(fld_owner_id, fil_id).Exec_select__rls_manual(); + try { + while (rdr.Move_next()) { + Fsd_thm_itm itm = Fsd_thm_itm.new_(); + Ctor_by_load(itm, rdr, dir_id); + list.Add(itm); + } + return Match_nearest(list, thm, schema_thm_page); + } + finally {rdr.Rls();} + } + public boolean Ctor_by_load(Fsd_thm_itm itm, Db_rdr rdr, int dir_id) { + int thm_id = rdr.Read_int(fld_id); + int fil_id = rdr.Read_int(fld_owner_id); + int w = rdr.Read_int(fld_w); + int h = rdr.Read_int(fld_h); + long size = rdr.Read_long(fld_size); + String modified = rdr.Read_str(fld_modified); + String hash = rdr.Read_str(fld_hash); + int bin_db_id = rdr.Read_int(fld_bin_db_id); + double time = 0; + int page = 0; + if (schema_thm_page) { + time = Xof_lnki_time.Db_load_double(rdr, fld_time); + page = Xof_lnki_page.Db_load_int(rdr, fld_page); + } + else { + time = Xof_lnki_time.Db_load_int(rdr, fld_time); + page = Xof_lnki_page.Null; + } + itm.Ctor(mnt_id, dir_id, fil_id, thm_id, bin_db_id, w, h, time, page, size, modified, hash); + return true; + } + public static final DateAdp Modified_null = null; + public static final String Hash_null = "", Modified_null_str = ""; + public static boolean Match_nearest(List_adp list, Fsd_thm_itm thm, boolean schema_thm_page) { + int len = list.Count(); if (len == 0) return Bool_.N; + list.Sort_by(Fsdb_thm_itm_sorter.Instance); + int thm_w = thm.W(), thm_page = thm.Page(); double thm_time = thm.Time(); + Fsd_thm_itm max = null; + for (int i = 0; i < len; ++i) { + Fsd_thm_itm comp = (Fsd_thm_itm)list.Get_at(i); + int comp_w = comp.W(); + int comp_page = schema_thm_page ? comp.Page() : thm_page; + if ( thm_w == comp_w + && thm_time == comp.Time() + && thm_page == comp_page + ) { // exact match + thm.Init_by_match(comp); + return Bool_.Y; + } + if (comp_w > thm_w) max = comp; + else if (max == null) max = comp; + else break; + } + if (max == null) return Bool_.N; + thm.Init_by_match(max); + return Bool_.Y; + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + stmt_select_by_fil_exact = Db_stmt_.Rls(stmt_select_by_fil_exact); + stmt_select_by_fil_near = Db_stmt_.Rls(stmt_select_by_fil_near); + } +} diff --git a/400_xowa/src/gplx/fsdb/data/Fsd_thm_tbl_tst.java b/400_xowa/src/gplx/fsdb/data/Fsd_thm_tbl_tst.java index a27517de8..4a98d1984 100644 --- a/400_xowa/src/gplx/fsdb/data/Fsd_thm_tbl_tst.java +++ b/400_xowa/src/gplx/fsdb/data/Fsd_thm_tbl_tst.java @@ -13,3 +13,44 @@ 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.fsdb.data; import gplx.*; import gplx.fsdb.*; +import org.junit.*; +public class Fsd_thm_tbl_tst { + @Before public void init() {fxt.Clear();} private Fsd_thm_tbl_fxt fxt = new Fsd_thm_tbl_fxt(); + @Test public void Basic() { + fxt.Init_list(fxt.Make(100), fxt.Make(200), fxt.Make(400)); + fxt.Test_match_nearest_itm(fxt.Make(400), fxt.Make(400)); + fxt.Test_match_nearest_itm(fxt.Make(200), fxt.Make(200)); + fxt.Test_match_nearest_itm(fxt.Make(100), fxt.Make(100)); + fxt.Test_match_nearest_itm(fxt.Make(350), fxt.Make(400)); + fxt.Test_match_nearest_itm(fxt.Make(150), fxt.Make(200)); + fxt.Test_match_nearest_itm(fxt.Make(999), fxt.Make(400)); + } + @Test public void Empty() { + fxt.Init_list(); // no items + fxt.Test_match_nearest_itm(fxt.Make(100), Fsd_thm_itm.Null); + } +} +class Fsd_thm_tbl_fxt { + private final List_adp list = List_adp_.New(); + public void Clear() {list.Clear();} + public Fsd_thm_itm Make(int w) { + double time = gplx.xowa.files.Xof_lnki_time.Null; + int page = gplx.xowa.files.Xof_lnki_page.Null; + Fsd_thm_itm rv = Fsd_thm_itm.new_(); + rv.Init_by_req(w, time, page); + return rv; + } + public void Init_list(Fsd_thm_itm... ary) {list.Add_many((Object[])ary);} + public void Test_match_nearest_itm(Fsd_thm_itm req, Fsd_thm_itm expd) { + Fsd_thm_tbl.Match_nearest(list, req, Bool_.Y); + if (expd == Fsd_thm_itm.Null) { + Tfds.Eq(req.Req_w(), 0); + } + else { + Tfds.Eq(expd.W(), req.W()); + Tfds.Eq(expd.Time(), req.Time()); + Tfds.Eq(expd.Page(), req.Page()); + } + } +} diff --git a/400_xowa/src/gplx/fsdb/meta/Fsm_atr_fil.java b/400_xowa/src/gplx/fsdb/meta/Fsm_atr_fil.java index a27517de8..2146f1d14 100644 --- a/400_xowa/src/gplx/fsdb/meta/Fsm_atr_fil.java +++ b/400_xowa/src/gplx/fsdb/meta/Fsm_atr_fil.java @@ -13,3 +13,95 @@ 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.fsdb.meta; import gplx.*; import gplx.fsdb.*; +import gplx.core.primitives.*; import gplx.core.caches.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +import gplx.dbs.*; import gplx.dbs.engines.sqlite.*; import gplx.fsdb.data.*; +public class Fsm_atr_fil { + private final Fsm_mnt_itm mnt_itm; private final int mnt_id; + private Fsd_dir_tbl tbl_dir; private Fsd_fil_tbl tbl_fil; private Fsd_thm_tbl tbl_thm; + private final Gfo_cache_mgr_bry dir_cache = new Gfo_cache_mgr_bry(); private Gfo_cache_mgr_bry fil_cache; private Bry_bfr fil_cache_key_bfr; + public Fsm_atr_fil(Fsm_mnt_itm mnt_itm, int id, String url_rel, Db_conn conn, boolean schema_is_1, boolean schema_thm_page) { + this.mnt_itm = mnt_itm; this.mnt_id = mnt_itm.Id(); + this.id = id; this.url_rel = url_rel; this.conn = conn; + this.tbl_dir = new Fsd_dir_tbl(conn, schema_is_1); + this.tbl_fil = new Fsd_fil_tbl(conn, schema_is_1, mnt_id); + this.tbl_thm = new Fsd_thm_tbl(conn, schema_is_1, mnt_id, schema_thm_page); + } + public int Id() {return id;} private final int id; + public String Url_rel() {return url_rel;} private final String url_rel; + public Db_conn Conn() {return conn;} private final Db_conn conn; + public Fsd_fil_itm Select_fil_or_null(byte[] dir, byte[] fil) { + int dir_id = Get_dir_id_or_neg1(dir); + return dir_id == Int_.Neg1 ? Fsd_fil_itm.Null : tbl_fil.Select_or_null(dir_id, fil); + } + public boolean Select_thm(boolean exact, Fsd_thm_itm rv, int dir_id, int fil_id) { + return exact ? tbl_thm.Select_itm_by_w_exact(dir_id, fil_id, rv) : tbl_thm.Select_itm_by_w_near(dir_id, fil_id, rv); + } + public Fsd_fil_itm Insert_fil(byte[] dir, byte[] fil, int ext_id, int bin_db_id, long bin_len, Io_stream_rdr bin_rdr) { + int dir_id = Get_dir_id_or_make(dir); + int fil_id = Get_fil_id_or_make(Tid_none, dir_id, fil, ext_id, bin_db_id, bin_len); + return new Fsd_fil_itm(mnt_id, dir_id, fil_id, 0, ext_id, fil, bin_len, null, null, bin_db_id); + } + public Fsd_img_itm Insert_img(byte[] dir, byte[] fil, int ext_id, int img_w, int img_h, int bin_db_id, long bin_len, Io_stream_rdr bin_rdr) { + int dir_id = Get_dir_id_or_make(dir); + int fil_id = Get_fil_id_or_make(Tid_img, dir_id, fil, ext_id, bin_db_id, bin_len); + return new Fsd_img_itm(mnt_id, dir_id, fil_id, bin_db_id); + } + public int Insert_thm(Fsd_thm_itm rv, byte[] dir, byte[] fil, int ext_id, int w, int h, double time, int page, int bin_db_id, long bin_len, Io_stream_rdr bin_rdr) { + int dir_id = Get_dir_id_or_make(dir); + int fil_id = Get_fil_id_or_make(Tid_thm, dir_id, fil, ext_id, Fsd_bin_tbl.Bin_db_id_null, Fsd_bin_tbl.Size_null); // NOTE: bin_db_id must be set to NULL + int thm_id = mnt_itm.Next_id(); + tbl_thm.Insert(thm_id, fil_id, w, h, time, page, bin_db_id, bin_len); + rv.Ctor(mnt_id, dir_id, fil_id, thm_id, bin_db_id, w, h, time, page, bin_len, Fsd_thm_tbl.Modified_null_str, Fsd_thm_tbl.Hash_null); + return thm_id; + } + public void Fil_cache_enabled_y_() { + fil_cache = new Gfo_cache_mgr_bry(); + fil_cache_key_bfr = Bry_bfr_.Reset(255); + tbl_fil.Select_all(fil_cache_key_bfr, fil_cache); + } + private int Get_dir_id_or_neg1(byte[] dir_bry) { + Object rv_obj = dir_cache.Get_or_null(dir_bry); + if (rv_obj == null) { // not in mem + Fsd_dir_itm itm = tbl_dir.Select_or_null(dir_bry); // try db + if (itm == Fsd_dir_itm.Null) return -1; // not in db + int dir_id = itm.Dir_id(); + dir_cache.Add(dir_bry, Int_obj_ref.New(dir_id)); // add to mem + return dir_id; + } + else + return ((Int_obj_ref)rv_obj).Val(); + } + private int Get_dir_id_or_make(byte[] dir_bry) { + int rv = Get_dir_id_or_neg1(dir_bry); + if (rv == -1) { + rv = mnt_itm.Next_id(); + tbl_dir.Insert(rv, dir_bry, Fsd_dir_itm.Owner_root); + dir_cache.Add(dir_bry, Int_obj_ref.New(rv)); + } + return rv; + } + private int Get_fil_id_or_make(int xtn_tid, int dir_id, byte[] fil, int ext_id, int bin_db_id, long bin_len) { + if (fil_cache != null) { + byte[] cache_key = Fsd_fil_itm.Gen_cache_key(fil_cache_key_bfr, dir_id, fil); + Object cache_obj = fil_cache.Get_or_null(cache_key); + if (cache_obj != null) return ((Fsd_fil_itm)cache_obj).Fil_id(); + } + Fsd_fil_itm fil_itm = tbl_fil.Select_or_null(dir_id, fil); + int fil_id = -1; + if (fil_itm == Fsd_fil_itm.Null) { // new item + fil_id = mnt_itm.Next_id(); + tbl_fil.Insert(fil_id, dir_id, fil, xtn_tid, ext_id, bin_len, bin_db_id); + } + else { // existing item + fil_id = fil_itm.Fil_id(); + if ( fil_itm.Bin_db_id() == Fsd_bin_tbl.Bin_db_id_null // prv row was previously inserted by thumb + && xtn_tid != Tid_thm // cur row is not thumb + ) { + tbl_fil.Update(fil_id, dir_id, fil, xtn_tid, ext_id, bin_len, bin_db_id); // update props; note that thumb inserts null props, whereas file will insert real props (EX: bin_db_id) + } + } + return fil_id; + } + private static final int Tid_none = 0, Tid_thm = 1, Tid_img = 2; +} diff --git a/400_xowa/src/gplx/fsdb/meta/Fsm_atr_mgr.java b/400_xowa/src/gplx/fsdb/meta/Fsm_atr_mgr.java index a27517de8..c67b8e066 100644 --- a/400_xowa/src/gplx/fsdb/meta/Fsm_atr_mgr.java +++ b/400_xowa/src/gplx/fsdb/meta/Fsm_atr_mgr.java @@ -13,3 +13,20 @@ 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.fsdb.meta; import gplx.*; import gplx.fsdb.*; +import gplx.dbs.*; import gplx.fsdb.data.*; +public class Fsm_atr_mgr { + private Fsdb_db_mgr core_mgr; private Fsm_atr_tbl tbl; private Fsm_atr_fil db__core; private Fsm_mnt_itm mnt_itm; + public Fsm_atr_mgr(Fsdb_db_mgr core_mgr, Db_conn conn, Fsm_mnt_itm mnt_itm) { + this.core_mgr = core_mgr; this.mnt_itm = mnt_itm; + this.tbl = new Fsm_atr_tbl(conn, core_mgr.File__schema_is_1()); + } + public void Ctor_by_load(boolean schema_thm_page) { + this.db__core = tbl.Select_1st_or_fail(mnt_itm, core_mgr, mnt_itm.Id(), schema_thm_page); + } + public Fsm_atr_fil Db__core() {return db__core;} + public Fsd_fil_itm Select_fil_or_null(byte[] dir, byte[] fil) {return db__core.Select_fil_or_null(dir, fil);} + public boolean Select_thm(boolean exact, Fsd_thm_itm rv, int dir_id, int fil_id) {return db__core.Select_thm(exact, rv, dir_id, fil_id);} + public void Txn_bgn() {db__core.Conn().Txn_bgn("fsdb__fsm_atr_mgr");} + public void Txn_end() {db__core.Conn().Txn_end();} +} diff --git a/400_xowa/src/gplx/fsdb/meta/Fsm_atr_tbl.java b/400_xowa/src/gplx/fsdb/meta/Fsm_atr_tbl.java index a27517de8..eed40ca5b 100644 --- a/400_xowa/src/gplx/fsdb/meta/Fsm_atr_tbl.java +++ b/400_xowa/src/gplx/fsdb/meta/Fsm_atr_tbl.java @@ -13,3 +13,46 @@ 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.fsdb.meta; import gplx.*; import gplx.fsdb.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; +public class Fsm_atr_tbl implements Db_tbl { + public final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_uid, fld_url; + private final Db_conn conn; + public Fsm_atr_tbl(Db_conn conn, boolean schema_is_1) { + this.conn = conn; + String fld_prefix = ""; + if (schema_is_1) {tbl_name = "fsdb_db_atr";} + else {tbl_name = "fsdb_dba"; fld_prefix = "dba_";} + this.fld_uid = flds.Add_int_pkey (fld_prefix + "uid"); + this.fld_url = flds.Add_str (fld_prefix + "url", 255); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public Fsm_atr_fil Select_1st_or_fail(Fsm_mnt_itm mnt_itm, Fsdb_db_mgr core_mgr, int mnt_id, boolean schema_thm_page) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, Dbmeta_fld_itm.Str_ary_empty).Exec_select__rls_auto(); + boolean schema_is_1 = core_mgr.File__schema_is_1(); + try { + if (rdr.Move_next()) { + String url_rel = rdr.Read_str(fld_url); + return new Fsm_atr_fil + ( mnt_itm + , rdr.Read_int(fld_uid) + , url_rel + , core_mgr.File__atr_file__at(mnt_id).Conn() + , schema_is_1 + , schema_thm_page + ); + } + } + finally {rdr.Rls();} + throw Err_.new_wo_type("missing atr db", "conn", conn.Conn_info().Db_api()); + } + public void Insert(int id, String url_rel) { + conn.Stmt_insert(tbl_name, flds).Val_int(fld_uid, id).Val_str(fld_url, url_rel).Exec_insert(); + } + public void Rls() {} + + public static final String TBL_NAME = "fsdb_dba"; + public static Fsm_atr_tbl Get_by_key(Db_tbl_owner owner) {return (Fsm_atr_tbl)owner.Tbls__get_by_key(TBL_NAME);} +} diff --git a/400_xowa/src/gplx/fsdb/meta/Fsm_bin_fil.java b/400_xowa/src/gplx/fsdb/meta/Fsm_bin_fil.java index a27517de8..934ebb1e9 100644 --- a/400_xowa/src/gplx/fsdb/meta/Fsm_bin_fil.java +++ b/400_xowa/src/gplx/fsdb/meta/Fsm_bin_fil.java @@ -13,3 +13,27 @@ 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.fsdb.meta; import gplx.*; import gplx.fsdb.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; import gplx.dbs.*; +import gplx.fsdb.data.*; +public class Fsm_bin_fil { + public Fsm_bin_fil(boolean schema_is_1, int id, Io_url url, String url_rel, Db_conn conn, long bin_len) { + this.id = id; this.url = url; this.url_rel = url_rel; this.conn = conn; this.bin_len = bin_len; + this.tbl = new Fsd_bin_tbl(conn, schema_is_1); + } + public int Id() {return id;} private final int id; + public Io_url Url() {return url;} private Io_url url; + public String Url_rel() {return url_rel;} private final String url_rel; + public long Bin_len() {return bin_len;} public void Bin_len_(long v) {bin_len = v;} private long bin_len; + public Db_conn Conn() {return conn;} private final Db_conn conn; + public Fsd_bin_tbl Tbl() {return tbl;} private final Fsd_bin_tbl tbl; + public boolean Select_to_url(int id, Io_url url) {return tbl.Select_to_url(id, url);} + public Io_stream_rdr Select_as_rdr(int id) {return tbl.Select_as_rdr(id);} + public Fsd_bin_itm Select_as_itm(int id) {return tbl.Select_as_itm(id);} + public void Insert(int bin_id, byte owner_tid, long rdr_len, gplx.core.ios.streams.Io_stream_rdr rdr) { + tbl.Insert_rdr(bin_id, owner_tid, rdr_len, rdr); + Bin_len_(bin_len + rdr_len); + } + public static final Fsm_bin_fil[] Ary_empty = new Fsm_bin_fil[0]; + public static final long Bin_len_null = 0; +} diff --git a/400_xowa/src/gplx/fsdb/meta/Fsm_bin_mgr.java b/400_xowa/src/gplx/fsdb/meta/Fsm_bin_mgr.java index a27517de8..acc02751e 100644 --- a/400_xowa/src/gplx/fsdb/meta/Fsm_bin_mgr.java +++ b/400_xowa/src/gplx/fsdb/meta/Fsm_bin_mgr.java @@ -13,3 +13,56 @@ 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.fsdb.meta; import gplx.*; import gplx.fsdb.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; import gplx.dbs.*; +public class Fsm_bin_mgr { + private final Fsdb_db_mgr core_mgr; private final int mnt_id; private final Fsm_bin_tbl tbl; + private final Ordered_hash db_hash = Ordered_hash_.New(); + private Fsm_bin_fil nth_db; + public Fsm_bin_mgr(Fsdb_db_mgr core_mgr, Db_conn conn, int mnt_id) { + this.core_mgr = core_mgr; this.mnt_id = mnt_id; + this.tbl = new Fsm_bin_tbl(conn, core_mgr.File__schema_is_1(), mnt_id); + } + public void Ctor_by_load() { + Fsm_bin_fil[] db_ary = tbl.Select_all(core_mgr); + int len = db_ary.length; + for (int i = 0; i < len; ++i) { + Fsm_bin_fil db_fil = db_ary[i]; + db_hash.Add(db_fil.Id(), db_fil); + } + if (len > 0) this.nth_db = db_ary[len - 1]; + } + public int Dbs__len() {return db_hash.Len();} + public Fsm_bin_fil Dbs__get_nth() {return nth_db;} + public Fsm_bin_fil Dbs__get_at(int i) {return (Fsm_bin_fil)db_hash.Get_at(i);} + public Fsm_bin_fil Dbs__get_by_or_null(int i) {return (Fsm_bin_fil)db_hash.Get_by(i);} + public Fsm_bin_fil Dbs__make(String file_name) {return Dbs__make(db_hash.Len(), file_name);} + public Fsm_bin_fil Dbs__make(int id, String file_name) { + Fsdb_db_file db = core_mgr.File__bin_file__new(mnt_id, file_name); + Fsm_bin_fil rv = new Fsm_bin_fil(core_mgr.File__schema_is_1(), id, db.Url(), db.Url().NameAndExt(), db.Conn(), Fsm_bin_fil.Bin_len_null); + tbl.Insert(id, rv.Url_rel()); + db_hash.Add(id, rv); + this.nth_db = rv; + return rv; + } + public void Insert(int db_id, int bin_id, byte owner_tid, long bin_len, Io_stream_rdr bin_rdr) { + Fsm_bin_fil fil = (Fsm_bin_fil)db_hash.Get_by(db_id); + fil.Insert(bin_id, owner_tid, bin_len, bin_rdr); + fil.Insert(bin_id, owner_tid, bin_len, bin_rdr); + } + public void Txn_bgn() { + int len = db_hash.Len(); + for (int i = 0; i < len; ++i) + this.Dbs__get_at(i).Conn().Txn_bgn("fsdb__meta__bin"); + } + public void Txn_end() { + int len = db_hash.Len(); + for (int i = 0; i < len; ++i) + this.Dbs__get_at(i).Conn().Txn_end(); + } + public void Rls() { + int len = db_hash.Len(); + for (int i = 0; i < len; ++i) + this.Dbs__get_at(i).Conn().Rls_conn(); + } +} diff --git a/400_xowa/src/gplx/fsdb/meta/Fsm_bin_tbl.java b/400_xowa/src/gplx/fsdb/meta/Fsm_bin_tbl.java index a27517de8..649dd7624 100644 --- a/400_xowa/src/gplx/fsdb/meta/Fsm_bin_tbl.java +++ b/400_xowa/src/gplx/fsdb/meta/Fsm_bin_tbl.java @@ -13,3 +13,46 @@ 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.fsdb.meta; import gplx.*; import gplx.fsdb.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; +public class Fsm_bin_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_uid, fld_url, fld_bin_len, fld_bin_max; + private final Db_conn conn; private int mnt_id; + public Fsm_bin_tbl(Db_conn conn, boolean schema_is_1, int mnt_id) { + this.conn = conn; this.mnt_id = mnt_id; + String fld_prefix = ""; + if (schema_is_1) {tbl_name = "fsdb_db_bin";} + else {tbl_name = "fsdb_dbb"; fld_prefix = "dbb_";} + fld_uid = flds.Add_int_pkey (fld_prefix + "uid"); + fld_url = flds.Add_str (fld_prefix + "url", 255); + if (schema_is_1) { + fld_bin_len = flds.Add_long("bin_len"); + fld_bin_max = flds.Add_long("bin_max"); + } + else { + fld_bin_len = Dbmeta_fld_itm.Key_null; + fld_bin_max = Dbmeta_fld_itm.Key_null; + } + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Insert(int id, String url_rel) { + conn.Stmt_insert(tbl_name, flds).Crt_int(fld_uid, id).Val_str(fld_url, url_rel).Val_long(fld_bin_len, 0).Val_long(fld_bin_max, 0).Exec_insert(); + } + public Fsm_bin_fil[] Select_all(Fsdb_db_mgr db_conn_mgr) { + List_adp rv = List_adp_.New(); + Db_rdr rdr = conn.Stmt_select_order(tbl_name, flds, Dbmeta_fld_itm.Str_ary_empty, fld_uid).Clear().Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + int bin_id = rdr.Read_int(fld_uid); + String bin_url = rdr.Read_str(fld_url); + Fsdb_db_file bin_db = db_conn_mgr.File__bin_file__at(mnt_id, bin_id, bin_url); + Fsm_bin_fil itm = new Fsm_bin_fil(db_conn_mgr.File__schema_is_1(), bin_id, bin_db.Url(), bin_url, bin_db.Conn(), Fsm_bin_fil.Bin_len_null); + rv.Add(itm); + } + } finally {rdr.Rls();} + return (Fsm_bin_fil[])rv.To_ary(Fsm_bin_fil.class); + } + public void Rls() {} +} diff --git a/400_xowa/src/gplx/fsdb/meta/Fsm_cfg_mgr.java b/400_xowa/src/gplx/fsdb/meta/Fsm_cfg_mgr.java index a27517de8..f230f60ce 100644 --- a/400_xowa/src/gplx/fsdb/meta/Fsm_cfg_mgr.java +++ b/400_xowa/src/gplx/fsdb/meta/Fsm_cfg_mgr.java @@ -13,3 +13,40 @@ 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.fsdb.meta; import gplx.*; import gplx.fsdb.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.fsdb.meta.*; +public class Fsm_cfg_mgr { + private final Db_cfg_tbl tbl; private final Hash_adp grp_hash = Hash_adp_.New(); + public Fsm_cfg_mgr(Fsdb_db_mgr db_conn_mgr, Db_conn conn) { + this.tbl = new Db_cfg_tbl(conn, db_conn_mgr.File__cfg_tbl_name()); + } + public void Ctor_by_load() { + Db_cfg_hash hash = Grps_get_or_load(Grp_core); + this.next_id = hash.Get_by(Key_next_id).To_int_or(-1); if (next_id == -1) throw Err_.new_wo_type("next_id not found in cfg", "url", tbl.Conn().Conn_info().Db_api()); + this.schema_thm_page = hash.Get_by(Key_schema_thm_page).To_yn_or_n(); + this.patch__next_id = hash.Get_by(Key_patch__next_id).To_yn_or_n(); + this.patch__page_gt_1 = hash.Get_by(Key_patch__page_gt_1).To_yn_or_n(); + } + public Db_cfg_tbl Tbl() {return tbl;} + public int Next_id() {return next_id++;} private int next_id = 1; + public void Next_id_commit() {tbl.Update_int("core", "next_id", next_id);} + public boolean Schema_thm_page() {return schema_thm_page;} private boolean schema_thm_page = true; + public boolean Patch_next_id() {return patch__next_id;} private boolean patch__next_id = true; + public void Patch_next_id_exec(int last_id) { + if (last_id >= next_id) + next_id = last_id + 1; + tbl.Insert_yn(Grp_core, Key_patch__next_id, Bool_.Y); + } + public boolean Patch__page_gt_1() {return patch__page_gt_1;} private boolean patch__page_gt_1 = false; + public void Patch__save(String cfg_key) {tbl.Insert_yn(Fsm_cfg_mgr.Grp_core, cfg_key, Bool_.Y);} + public Db_cfg_hash Grps_get_or_load(String grp_key) { + Db_cfg_hash rv = (Db_cfg_hash)grp_hash.Get_by(grp_key); + if (rv == null) { + rv = tbl.Select_as_hash(grp_key); + grp_hash.Add(grp_key, rv); + } + return rv; + } + public static final String Grp_core = "core"; + public static final String Key_next_id = "next_id", Key_schema_thm_page = "schema.thm.page", Key_patch__next_id = "patch.next_id", Key_patch__page_gt_1 = "patch.page_gt_1"; +} diff --git a/400_xowa/src/gplx/fsdb/meta/Fsm_id_itm.java b/400_xowa/src/gplx/fsdb/meta/Fsm_id_itm.java index a27517de8..542aa044c 100644 --- a/400_xowa/src/gplx/fsdb/meta/Fsm_id_itm.java +++ b/400_xowa/src/gplx/fsdb/meta/Fsm_id_itm.java @@ -13,3 +13,10 @@ 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.fsdb.meta; import gplx.*; import gplx.fsdb.*; +public class Fsm_id_itm { + public Fsm_id_itm(String key, int id, int version) {this.key = key; this.id = id; this.version = version;} + public String Key() {return key;} private final String key; + public int Id() {return id;} private final int id; + public int Version() {return version;} private final int version; +} diff --git a/400_xowa/src/gplx/fsdb/meta/Fsm_mnt_itm.java b/400_xowa/src/gplx/fsdb/meta/Fsm_mnt_itm.java index a27517de8..b899411a6 100644 --- a/400_xowa/src/gplx/fsdb/meta/Fsm_mnt_itm.java +++ b/400_xowa/src/gplx/fsdb/meta/Fsm_mnt_itm.java @@ -13,3 +13,65 @@ 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.fsdb.meta; import gplx.*; import gplx.fsdb.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; import gplx.dbs.*; import gplx.fsdb.data.*; +public class Fsm_mnt_itm { + public Fsm_mnt_itm(int id, String name, String url_rel) {this.id = id; this.name = name; this.url_rel = url_rel;} + public int Id() {return id;} private final int id; + public String Name() {return name;} private final String name; + public String Url_rel() {return url_rel;} private final String url_rel; + public Fsm_atr_mgr Atr_mgr() {return atr_mgr;} private Fsm_atr_mgr atr_mgr; + public Fsm_bin_mgr Bin_mgr() {return bin_mgr;} private Fsm_bin_mgr bin_mgr; + public Fsm_cfg_mgr Cfg_mgr() {return cfg_mgr;} private Fsm_cfg_mgr cfg_mgr; + public Fsdb_db_mgr Db_mgr() {return db_mgr;} private Fsdb_db_mgr db_mgr; + public void Ctor_by_load(Fsdb_db_mgr db_mgr) { + this.db_mgr = db_mgr; + Db_conn conn = db_mgr.File__abc_file__at(id).Conn(); + cfg_mgr = new Fsm_cfg_mgr(db_mgr, conn); + atr_mgr = new Fsm_atr_mgr(db_mgr, conn, this); + bin_mgr = new Fsm_bin_mgr(db_mgr, conn, id); + cfg_mgr.Ctor_by_load(); + atr_mgr.Ctor_by_load(cfg_mgr.Schema_thm_page()); + bin_mgr.Ctor_by_load(); + if (!cfg_mgr.Patch_next_id()) Fsm_mnt_itm_.Patch_next_id(this, name); + } + public int Next_id() {return cfg_mgr.Next_id();} + public Fsd_fil_itm Select_fil_or_null(byte[] dir, byte[] fil) {return atr_mgr.Select_fil_or_null(dir, fil);} + public boolean Select_thm(boolean exact, Fsd_thm_itm rv, byte[] dir, byte[] fil) { + Fsd_fil_itm fil_itm = atr_mgr.Select_fil_or_null(dir, fil); + return fil_itm == Fsd_fil_itm.Null ? Bool_.N : atr_mgr.Select_thm(exact, rv, fil_itm.Dir_id(), fil_itm.Fil_id()); + } + public Fsd_img_itm Insert_img(Fsm_atr_fil atr_fil, Fsm_bin_fil bin_fil, byte[] dir, byte[] fil, int ext_id, int img_w, int img_h, long bin_len, Io_stream_rdr bin_rdr) { + Fsd_img_itm rv = atr_fil.Insert_img(dir, fil, ext_id, img_w, img_h, bin_fil.Id(), bin_len, bin_rdr); + bin_fil.Insert(rv.Fil_id(), Fsd_bin_tbl.Owner_tid_fil, bin_len, bin_rdr); + return rv; + } + public Fsd_fil_itm Insert_fil(Fsm_atr_fil atr_fil, Fsm_bin_fil bin_fil, byte[] dir, byte[] fil, int ext_id, long bin_len, gplx.core.ios.streams.Io_stream_rdr bin_rdr) { + Fsd_fil_itm rv = atr_fil.Insert_fil(dir, fil, ext_id, bin_fil.Id(), bin_len, bin_rdr); + bin_fil.Insert(rv.Fil_id(), Fsd_bin_tbl.Owner_tid_fil, bin_len, bin_rdr); + return rv; + } + public void Insert_thm(Fsd_thm_itm rv, Fsm_atr_fil atr_fil, Fsm_bin_fil bin_fil, byte[] dir, byte[] fil, int ext_id, int w, int h, double time, int page, long bin_len, Io_stream_rdr bin_rdr) { + int thm_id = atr_fil.Insert_thm(rv, dir, fil, ext_id, w, h, time, page, bin_fil.Id(), bin_len, bin_rdr); + bin_fil.Insert(thm_id, Fsd_bin_tbl.Owner_tid_thm, bin_len, bin_rdr); + } + public void Txn_bgn() {atr_mgr.Txn_bgn(); bin_mgr.Txn_bgn();} + public void Txn_end() {atr_mgr.Txn_end(); bin_mgr.Txn_end();} + public void Rls() { + atr_mgr.Db__core().Conn().Rls_conn(); + bin_mgr.Rls(); + } +} +class Fsm_mnt_itm_ { + public static void Patch_next_id(Fsm_mnt_itm abc_mgr, String name) { + if (!String_.Eq(name, "fsdb.user")) return; + Fsm_atr_mgr atr_mgr = abc_mgr.Atr_mgr(); + Fsm_cfg_mgr cfg_mgr = abc_mgr.Cfg_mgr(); + int last_id = -1; + Fsm_atr_fil atr_fil = atr_mgr.Db__core(); + int max_fil_id = atr_fil.Conn().Exec_select_as_int("SELECT Max(fil_id) AS MaxId FROM fsdb_fil;", -1); + int max_thm_id = atr_fil.Conn().Exec_select_as_int("SELECT Max(thm_id) AS MaxId FROM fsdb_xtn_thm;", -1); + last_id = max_fil_id > max_thm_id ? max_fil_id : max_thm_id; + cfg_mgr.Patch_next_id_exec(last_id); + } +} diff --git a/400_xowa/src/gplx/fsdb/meta/Fsm_mnt_mgr.java b/400_xowa/src/gplx/fsdb/meta/Fsm_mnt_mgr.java index a27517de8..db8ddc572 100644 --- a/400_xowa/src/gplx/fsdb/meta/Fsm_mnt_mgr.java +++ b/400_xowa/src/gplx/fsdb/meta/Fsm_mnt_mgr.java @@ -13,3 +13,51 @@ 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.fsdb.meta; import gplx.*; import gplx.fsdb.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*; +public class Fsm_mnt_mgr implements Gfo_invk { + private Db_cfg_tbl cfg_tbl; private Fsm_mnt_tbl mnt_tbl; + private Fsm_mnt_itm[] mnt_ary; private int mnt_ary_len = 0; + public void Ctor_by_load(Fsdb_db_mgr db_core) { + Db_conn conn = db_core.File__mnt_file().Conn(); + this.cfg_tbl = new Db_cfg_tbl (conn, db_core.File__cfg_tbl_name()); + this.mnt_tbl = new Fsm_mnt_tbl (conn, db_core.File__schema_is_1()); + this.mnt_ary = mnt_tbl.Select_all(); + this.mnt_ary_len = mnt_ary.length; + for (int i = 0; i < mnt_ary_len; ++i) { + mnt_ary[i].Ctor_by_load(db_core); + } + this.insert_idx = cfg_tbl.Select_int(Cfg_grp_core, Cfg_key_mnt_insert_idx); + Db_cfg_hash cfg_hash = this.Mnts__get_main().Cfg_mgr().Grps_get_or_load(Xof_fsdb_mgr_cfg.Grp_xowa); + boolean use_thumb_w = cfg_hash.Get_by(Xof_fsdb_mgr_cfg.Key_upright_use_thumb_w).To_yn_or_n(); + boolean fix_default = cfg_hash.Get_by(Xof_fsdb_mgr_cfg.Key_upright_fix_default).To_yn_or_n(); + this.patch_upright_tid = Xof_patch_upright_tid_.Merge(use_thumb_w, fix_default); + } + public int Mnts__len() {return mnt_ary_len;} + public Fsm_mnt_itm Mnts__get_at(int i) {return mnt_ary[i];} + public Fsm_mnt_itm Mnts__get_main_or_null() {return mnt_ary == null ? null : mnt_ary[Mnt_idx_main];} // NOTE: can be null for embeddable parser; DATE:2017-06-06 + public Fsm_mnt_itm Mnts__get_main() {return mnt_ary[Mnt_idx_main];} + public Fsm_mnt_itm Mnts__get_insert() {return mnt_ary[insert_idx];} public void Mnts__get_insert_idx_(int v) {insert_idx = v;} private int insert_idx = Mnt_idx_user; + public Fsm_bin_fil Bins__at(int mnt_id, int bin_db_id) {return mnt_ary[mnt_id].Bin_mgr().Dbs__get_by_or_null(bin_db_id);} + public int Patch_upright() {return patch_upright_tid;} private int patch_upright_tid = Xof_patch_upright_tid_.Tid_all; + public void Rls() { + for (int i = 0; i < mnt_ary_len; ++i) { + Fsm_mnt_itm mnt = mnt_ary[i]; + mnt.Rls(); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return Gfo_invk_.Rv_unhandled;} + public static final int Mnt_idx_main = 0, Mnt_idx_user = 1, Insert_to_bin_null = -1; + public static void Patch(Db_cfg_tbl cfg_tbl) { + cfg_tbl.Upsert_str(Xof_fsdb_mgr_cfg.Grp_xowa, Xof_fsdb_mgr_cfg.Key_gallery_fix_defaults , "y"); + cfg_tbl.Upsert_str(Xof_fsdb_mgr_cfg.Grp_xowa, Xof_fsdb_mgr_cfg.Key_gallery_packed , "y"); + cfg_tbl.Upsert_str(Xof_fsdb_mgr_cfg.Grp_xowa, Xof_fsdb_mgr_cfg.Key_upright_use_thumb_w , "y"); + cfg_tbl.Upsert_str(Xof_fsdb_mgr_cfg.Grp_xowa, Xof_fsdb_mgr_cfg.Key_upright_fix_default , "y"); + } + public static void Patch_core(Db_cfg_tbl cfg_tbl) { // NOTE: thes need to be upserts else upgrading will fail; DATE:2015-05-23 + cfg_tbl.Upsert_int (Fsm_cfg_mgr.Grp_core, Fsm_cfg_mgr.Key_next_id , 1); // start next_id at 1 + cfg_tbl.Upsert_yn (Fsm_cfg_mgr.Grp_core, Fsm_cfg_mgr.Key_schema_thm_page , Bool_.Y); // new dbs automatically have page and time in fsdb_xtn_tm + cfg_tbl.Upsert_yn (Fsm_cfg_mgr.Grp_core, Fsm_cfg_mgr.Key_patch__next_id , Bool_.Y); // new dbs automatically have correct next_id + } + public static final String Cfg_grp_core = "core", Cfg_key_mnt_insert_idx = "mnt.insert_idx"; // SERIALIZED +} diff --git a/400_xowa/src/gplx/fsdb/meta/Fsm_mnt_tbl.java b/400_xowa/src/gplx/fsdb/meta/Fsm_mnt_tbl.java index a27517de8..259bb8770 100644 --- a/400_xowa/src/gplx/fsdb/meta/Fsm_mnt_tbl.java +++ b/400_xowa/src/gplx/fsdb/meta/Fsm_mnt_tbl.java @@ -13,3 +13,46 @@ 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.fsdb.meta; import gplx.*; import gplx.fsdb.*; +import gplx.dbs.*; +public class Fsm_mnt_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_id, fld_name, fld_url; + private final Db_conn conn; + public Fsm_mnt_tbl(Db_conn conn, boolean schema_is_1) { + this.conn = conn; + fld_id = flds.Add_int_pkey ("mnt_id"); + fld_name = flds.Add_str ("mnt_name", 255); + fld_url = flds.Add_str ("mnt_url", 255); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name = "fsdb_mnt"; + public void Create_tbl() { + Dbmeta_tbl_itm meta = Dbmeta_tbl_itm.New(tbl_name, flds); + conn.Meta_tbl_create(meta); + this.Insert(Fsm_mnt_mgr.Mnt_idx_main, Mnt_name_main, Mnt_name_main); + this.Insert(Fsm_mnt_mgr.Mnt_idx_user, Mnt_name_user, Mnt_name_user); + } + public void Rls() {} + public void Insert(int id, String name, String url) { + Db_stmt stmt = conn.Stmt_insert(tbl_name, flds); + stmt.Clear().Val_int(fld_id, id).Val_str(fld_name, name).Val_str(fld_url, url).Exec_insert(); + } + public void Update(int id, String name, String url) { + Db_stmt stmt = conn.Stmt_update_exclude(tbl_name, flds, fld_id); + stmt.Clear().Val_str(fld_name, name).Val_str(fld_url, url).Crt_int(fld_id, id).Exec_update(); + } + public Fsm_mnt_itm[] Select_all() { + List_adp list = List_adp_.New(); + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, Dbmeta_fld_itm.Str_ary_empty).Clear().Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + Fsm_mnt_itm itm = new Fsm_mnt_itm(rdr.Read_int(fld_id), rdr.Read_str(fld_name), rdr.Read_str(fld_url)); + list.Add(itm); + } + } + finally {rdr.Rls();} + return (Fsm_mnt_itm[])list.To_ary_and_clear(Fsm_mnt_itm.class); + } + public static final String Mnt_name_main = "fsdb.main", Mnt_name_user = "fsdb.user"; +} diff --git a/400_xowa/src/gplx/gfui/Gfui_bnd_parser.java b/400_xowa/src/gplx/gfui/Gfui_bnd_parser.java new file mode 100644 index 000000000..4ded20461 --- /dev/null +++ b/400_xowa/src/gplx/gfui/Gfui_bnd_parser.java @@ -0,0 +1,330 @@ +/* +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.gfui; import gplx.*; +import gplx.core.bits.*; +public class Gfui_bnd_parser { + private Bry_bfr tmp_bfr = Bry_bfr_.Reset(32); + private Hash_adp_bry + gfui_regy = Hash_adp_bry.ci_a7() + , norm_regy = Hash_adp_bry.ci_a7() + ; + private static final Gfui_bnd_tkn + Itm_sym_plus = new_sym_(Gfui_bnd_tkn.Tid_sym_plus , new byte[] {Byte_ascii.Plus}) + , Itm_sym_pipe = new_sym_(Gfui_bnd_tkn.Tid_sym_pipe , new byte[] {Byte_ascii.Pipe}) + , Itm_sym_comma = new_sym_(Gfui_bnd_tkn.Tid_sym_comma , new byte[] {Byte_ascii.Comma}) +// , Itm_sym_ws = new_sym_(Gfui_bnd_tkn.Tid_sym_ws , Bry_.Empty) + , Itm_sym_eos = new_sym_(Gfui_bnd_tkn.Tid_sym_eos , Bry_.Empty) + ; + private static final Gfui_bnd_tkn[] Mod_ary = new Gfui_bnd_tkn[] + { null + , new_mod_(Gfui_bnd_tkn.Tid_mod_c , "mod.c" , "Ctrl") + , new_mod_(Gfui_bnd_tkn.Tid_mod_a , "mod.a" , "Alt") + , new_mod_(Gfui_bnd_tkn.Tid_mod_ca , "mod.ca" , "Ctrl + Alt") + , new_mod_(Gfui_bnd_tkn.Tid_mod_s , "mod.s" , "Shift") + , new_mod_(Gfui_bnd_tkn.Tid_mod_cs , "mod.cs" , "Ctrl + Shift") + , new_mod_(Gfui_bnd_tkn.Tid_mod_as , "mod.as" , "Alt + Shift") + , new_mod_(Gfui_bnd_tkn.Tid_mod_cas , "mod.cas" , "Ctrl + Alt + Shift") + }; + private byte[] src; private int src_len; + private List_adp tkns = List_adp_.New(); private int mod_val = Mod_val_null; + public String Xto_norm(String src_str) {return Convert(Bool_.Y, src_str);} + public String Xto_gfui(String src_str) {return Convert(Bool_.N, src_str);} + private String Convert(boolean src_is_gfui, String src_str) { + this.src = Bry_.new_u8(src_str); this.src_len = src.length; + tkns.Clear(); mod_val = Mod_val_null; + int pos = 0; int itm_bgn = -1, itm_end = -1; boolean is_numeric = false; + while (pos <= src_len) { // loop over bytes and break up tkns by symbols + byte b = pos == src_len ? Byte_ascii.Nl: src[pos]; // treat eos as "\n" for purpose of separating tokens + Gfui_bnd_tkn sym_tkn = null; + switch (b) { + case Byte_ascii.Plus: // simultaneous; EX: Ctrl + S + sym_tkn = Itm_sym_plus; + break; + case Byte_ascii.Pipe: // alternate; EX: Ctrl + S | Ctrl + Alt + s + sym_tkn = Itm_sym_pipe; + break; + case Byte_ascii.Comma: // chorded; EX: Ctrl + S, Ctrl + T + sym_tkn = Itm_sym_comma; + break; + case Byte_ascii.Nl: // eos: process anything in bfr + sym_tkn = Itm_sym_eos; + break; + case Byte_ascii.Space: + if (itm_bgn != -1) // if word started, " " ends word; EX: "Ctrl + A"; " +" ends "Ctrl" + itm_end = pos; + ++pos; + continue; + case Byte_ascii.Hash: + if (is_numeric) throw Err_.new_wo_type("multiple numeric symbols in keycode"); + is_numeric = true; + ++pos; + continue; + default: // letter / number; continue + if (itm_bgn == -1) // no word started; start it + itm_bgn = pos; + ++pos; + continue; + } + if (itm_end == -1) // end not set by space; char before symbol is end + itm_end = pos; + Process_sym(src_is_gfui, is_numeric, sym_tkn, itm_bgn, itm_end); + is_numeric = false; + if (sym_tkn.Tid() == Gfui_bnd_tkn.Tid_sym_eos) + break; + else + ++pos; + itm_bgn = itm_end = -1; + } + int tkns_len = tkns.Count(); + for (int i = 0; i < tkns_len; i++) { + Gfui_bnd_tkn tkn = (Gfui_bnd_tkn)tkns.Get_at(i); + tkn.Write(tmp_bfr, !src_is_gfui); + } + return tmp_bfr.To_str_and_clear(); + } + private void Process_sym(boolean src_is_gfui, boolean is_numeric, Gfui_bnd_tkn sym_tkn, int itm_bgn, int itm_end) { + Hash_adp_bry regy = src_is_gfui ? gfui_regy : norm_regy; + Gfui_bnd_tkn tkn = null; + if (is_numeric) { // EX: "key.#10" or "#10" + int tkn_bgn = itm_bgn; + if (src_is_gfui) { // remove "key." in "key.#10" + tkn_bgn = Bry_find_.Move_fwd(src, Byte_ascii.Dot, itm_bgn, itm_end); + if (tkn_bgn == -1) throw Err_.new_wo_type("invalid keycode.dot", "keycode", Bry_.Mid(src, tkn_bgn, itm_end)); + ++tkn_bgn; // skip # + } + int keycode = Bry_.To_int_or(src, tkn_bgn, itm_end, -1); if (keycode == -1) throw Err_.new_wo_type("invalid keycode", "keycode", Bry_.Mid(src, tkn_bgn, itm_end)); + tkn = new Gfui_bnd_tkn(Gfui_bnd_tkn.Tid_key, keycode, Bry_.Empty, Bry_.Empty); + } + else + tkn = (Gfui_bnd_tkn)regy.Get_by_mid(src, itm_bgn, itm_end); + if (tkn == null) return; + int mod_adj = Mod_val_null; + switch (tkn.Tid()) { + case Gfui_bnd_tkn.Tid_mod_c: mod_adj = Gfui_bnd_tkn.Tid_mod_c; break; + case Gfui_bnd_tkn.Tid_mod_a: mod_adj = Gfui_bnd_tkn.Tid_mod_a; break; + case Gfui_bnd_tkn.Tid_mod_s: mod_adj = Gfui_bnd_tkn.Tid_mod_s; break; + case Gfui_bnd_tkn.Tid_mod_cs: mod_adj = Gfui_bnd_tkn.Tid_mod_cs; break; + case Gfui_bnd_tkn.Tid_mod_as: mod_adj = Gfui_bnd_tkn.Tid_mod_as; break; + case Gfui_bnd_tkn.Tid_mod_ca: mod_adj = Gfui_bnd_tkn.Tid_mod_ca; break; + case Gfui_bnd_tkn.Tid_mod_cas: mod_adj = Gfui_bnd_tkn.Tid_mod_cas; break; + case Gfui_bnd_tkn.Tid_key: break; + default: throw Err_.new_unhandled(tkn.Tid()); + } + switch (sym_tkn.Tid()) { + case Gfui_bnd_tkn.Tid_sym_plus: // EX: Ctrl + A + if (mod_adj != Mod_val_null) { // if mod, just update mod_val and exit + mod_val = Bitmask_.Flip_int(true, mod_val, mod_adj); + return; + } + break; + } + if (mod_val != Mod_val_null) { // modifier exists; add tkn + tkns.Add(Mod_ary[mod_val]); + tkns.Add(Itm_sym_plus); + mod_val = Mod_val_null; + } + tkns.Add(tkn); // add word + if (sym_tkn.Tid() != Gfui_bnd_tkn.Tid_sym_eos) + tkns.Add(sym_tkn); + } + private Gfui_bnd_parser Init_en() { + Init_itm_mod(Gfui_bnd_tkn.Tid_mod_c); + Init_itm_mod(Gfui_bnd_tkn.Tid_mod_a); + Init_itm_mod(Gfui_bnd_tkn.Tid_mod_s); + Init_itm_mod(Gfui_bnd_tkn.Tid_mod_ca); + Init_itm_mod(Gfui_bnd_tkn.Tid_mod_cs); + Init_itm_mod(Gfui_bnd_tkn.Tid_mod_as); + Init_itm_mod(Gfui_bnd_tkn.Tid_mod_cas); + Init_itm(Gfui_bnd_tkn.Tid_mod_c, "key.ctrl", "Ctrl"); + Init_itm(Gfui_bnd_tkn.Tid_mod_a, "key.alt", "Atl"); + Init_itm(Gfui_bnd_tkn.Tid_mod_s, "key.shift", "Shift"); + Init_itm("key.a", "A"); + Init_itm("key.b", "B"); + Init_itm("key.c", "C"); + Init_itm("key.d", "D"); + Init_itm("key.e", "E"); + Init_itm("key.f", "F"); + Init_itm("key.g", "G"); + Init_itm("key.h", "H"); + Init_itm("key.i", "I"); + Init_itm("key.j", "J"); + Init_itm("key.k", "K"); + Init_itm("key.l", "L"); + Init_itm("key.m", "M"); + Init_itm("key.n", "N"); + Init_itm("key.o", "O"); + Init_itm("key.p", "P"); + Init_itm("key.q", "Q"); + Init_itm("key.r", "R"); + Init_itm("key.s", "S"); + Init_itm("key.t", "T"); + Init_itm("key.u", "U"); + Init_itm("key.v", "V"); + Init_itm("key.w", "W"); + Init_itm("key.x", "X"); + Init_itm("key.y", "Y"); + Init_itm("key.z", "Z"); + Init_itm("key.d0", "0"); + Init_itm("key.d1", "1"); + Init_itm("key.d2", "2"); + Init_itm("key.d3", "3"); + Init_itm("key.d4", "4"); + Init_itm("key.d5", "5"); + Init_itm("key.d6", "6"); + Init_itm("key.d7", "7"); + Init_itm("key.d8", "8"); + Init_itm("key.d9", "9"); + Init_itm("key.f1", "F1"); + Init_itm("key.f2", "F2"); + Init_itm("key.f3", "F3"); + Init_itm("key.f4", "F4"); + Init_itm("key.f5", "F5"); + Init_itm("key.f6", "F6"); + Init_itm("key.f7", "F7"); + Init_itm("key.f8", "F8"); + Init_itm("key.f9", "F9"); + Init_itm("key.f10", "F10"); + Init_itm("key.f11", "F11"); + Init_itm("key.f12", "F12"); + Init_itm("key.none", "None"); + Init_itm("key.back", "Backspace"); + Init_itm("key.tab", "Tab"); + Init_itm("key.clear", "Clear"); + Init_itm("key.enter", "Enter"); + Init_itm("key.shiftKey", "ShiftKey"); + Init_itm("key.ctrlKey", "CtrlKey"); + Init_itm("key.altKey", "AltKey"); + Init_itm("key.pause", "Pause"); + Init_itm("key.capsLock", "CapsLock"); + Init_itm("key.escape", "Escape"); + Init_itm("key.space", "Space"); + Init_itm("key.pageUp", "PageUp"); + Init_itm("key.pageDown", "PageDown"); + Init_itm("key.end", "End"); + Init_itm("key.home", "Home"); + Init_itm("key.left", "Left"); + Init_itm("key.up", "Up"); + Init_itm("key.right", "Right"); + Init_itm("key.down", "Down"); + Init_itm("key.printScreen", "PrintScreen"); + Init_itm("key.insert", "Insert"); + Init_itm("key.delete", "Delete"); + Init_itm("key.numLock", "NumLock"); + Init_itm("key.scrollLock", "ScrollLock"); + Init_itm("key.semicolon", "Semicolon"); + Init_itm("key.equal", "Equal"); + Init_itm("key.comma", "Comma"); + Init_itm("key.minus", "Minus"); + Init_itm("key.period", "Period"); + Init_itm("key.slash", "Slash"); + Init_itm("key.tick", "Tick"); + Init_itm("key.openBracket", "OpenBracket"); + Init_itm("key.backslash", "Backslash"); + Init_itm("key.closeBracket", "CloseBracket"); + Init_itm("key.quote", "Quote"); + Init_itm("mouse.middle", "Middle Click"); + Init_itm("mouse.left", "Left Click"); + Init_itm("mouse.right", "Right Click"); + Init_itm("key.numpad_0", "Numpad 0"); + Init_itm("key.numpad_1", "Numpad 1"); + Init_itm("key.numpad_2", "Numpad 2"); + Init_itm("key.numpad_3", "Numpad 3"); + Init_itm("key.numpad_4", "Numpad 4"); + Init_itm("key.numpad_5", "Numpad 5"); + Init_itm("key.numpad_6", "Numpad 6"); + Init_itm("key.numpad_7", "Numpad 7"); + Init_itm("key.numpad_8", "Numpad 8"); + Init_itm("key.numpad_9", "Numpad 9"); + Init_itm("key.numpad_multiply", "Numpad Multiply"); + Init_itm("key.numpad_add", "Numpad Add"); + Init_itm("key.numpad_subtract", "Numpad Subtract"); + Init_itm("key.numpad_decimal", "Numpad Decimal"); + Init_itm("key.numpad_divide", "Numpad Divide"); + Init_itm("key.numpad_enter", "Numpad Enter"); + return this; + } + private void Init_itm(String gfui, String norm) {Init_itm(Gfui_bnd_tkn.Tid_key, gfui, norm);} + private void Init_itm_mod(int tid) { + Gfui_bnd_tkn itm = Mod_ary[tid]; + gfui_regy.Add(itm.Bry_gfui(), itm); + norm_regy.Add(itm.Bry_norm(), itm); + } + private void Init_itm(byte tid, String gfui, String norm) { + byte[] gfui_bry = Bry_.new_u8(gfui); + byte[] norm_bry = Bry_.new_u8(norm); + Gfui_bnd_tkn itm = new Gfui_bnd_tkn(tid, Gfui_bnd_tkn.Keycode_null, gfui_bry, norm_bry); + gfui_regy.Add(gfui_bry, itm); + norm_regy.Add_if_dupe_use_1st(norm_bry, itm); + } + private static final int Mod_val_null = 0; + public static Gfui_bnd_parser new_en_() {return new Gfui_bnd_parser().Init_en();} Gfui_bnd_parser() {} + private static Gfui_bnd_tkn new_sym_(byte tid, byte[] bry) {return new Gfui_bnd_tkn(tid, Gfui_bnd_tkn.Keycode_null, bry, bry);} + private static Gfui_bnd_tkn new_mod_(byte tid, String gfui, String norm) {return new Gfui_bnd_tkn(tid, Gfui_bnd_tkn.Keycode_null, Bry_.new_a7(gfui), Bry_.new_a7(norm));} +} +class Gfui_bnd_tkn { + public Gfui_bnd_tkn(byte tid, int keycode, byte[] gfui, byte[] norm) { + this.tid = tid; this.keycode = keycode; ; this.bry_gfui = gfui; this.bry_norm = norm; + } + public byte Tid() {return tid;} private final byte tid; + public int Keycode() {return keycode;} private final int keycode; + public byte[] Bry_gfui() {return bry_gfui;} private final byte[] bry_gfui; + public byte[] Bry_norm() {return bry_norm;} private final byte[] bry_norm; + public void Write(Bry_bfr bfr, boolean src_is_gfui) { + if (keycode != Gfui_bnd_tkn.Keycode_null) { + if (src_is_gfui) + bfr.Add(Bry_key_prefix); + bfr.Add_byte(Byte_ascii.Hash).Add_int_variable(keycode); + return; + } + byte[] bry = src_is_gfui ? bry_gfui : bry_norm; + switch (tid) { + case Tid_mod_c: case Tid_mod_a: case Tid_mod_s: + case Tid_mod_ca: case Tid_mod_cs: case Tid_mod_as: case Tid_mod_cas: + bfr.Add(bry); + break; + case Tid_sym_plus: + if (!src_is_gfui) + bfr.Add_byte_space(); + bfr.Add(bry); + if (!src_is_gfui) + bfr.Add_byte_space(); + break; + case Tid_sym_pipe: + if (!src_is_gfui) + bfr.Add_byte_space(); + bfr.Add(bry); + if (!src_is_gfui) + bfr.Add_byte_space(); + break; + case Tid_sym_comma: + bfr.Add(bry); + if (!src_is_gfui) + bfr.Add_byte_space(); + break; + case Tid_key: + bfr.Add(bry); + break; + } + } + public static final byte + Tid_mod_c = 1 , Tid_mod_a = 2 , Tid_mod_s = 4 + , Tid_mod_ca = 3 , Tid_mod_cs = 5 , Tid_mod_as = 6, Tid_mod_cas = 7 + , Tid_sym_plus = 8 , Tid_sym_pipe = 9 , Tid_sym_comma = 10, Tid_sym_eos = 11 + , Tid_key = 12 + ; + public static final int Keycode_null = 0; + private static final byte[] Bry_key_prefix = Bry_.new_a7("key."); +} diff --git a/400_xowa/src/gplx/gfui/Gfui_bnd_parser_tst.java b/400_xowa/src/gplx/gfui/Gfui_bnd_parser_tst.java new file mode 100644 index 000000000..31cb8d57e --- /dev/null +++ b/400_xowa/src/gplx/gfui/Gfui_bnd_parser_tst.java @@ -0,0 +1,62 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.gfui; import gplx.*; +import org.junit.*; +public class Gfui_bnd_parser_tst { + @Before public void init() {fxt.Clear();} private Gfui_bnd_parser_fxt fxt = new Gfui_bnd_parser_fxt(); + @Test public void Norm_one() { + fxt.Test__to_norm("mod.c" , "Ctrl"); + fxt.Test__to_norm("key.ctrl" , "Ctrl"); + fxt.Test__to_norm("key.a" , "A"); + fxt.Test__to_norm("key.left" , "Left"); + } + @Test public void Norm_add() { + fxt.Test__to_norm("mod.c+key.a" , "Ctrl + A"); + fxt.Test__to_norm("mod.ca+key.a" , "Ctrl + Alt + A"); + fxt.Test__to_norm("mod.cas+key.a" , "Ctrl + Alt + Shift + A"); + } + @Test public void Norm_chord() { + fxt.Test__to_norm("key.a,key.b" , "A, B"); + } + @Test public void Norm_add_and_chord() { + fxt.Test__to_norm("mod.c+key.a,mod.a+key.b" , "Ctrl + A, Alt + B"); + } + @Test public void Gfui_add() { + fxt.Test__to_gfui("Ctrl + A" , "mod.c+key.a"); + fxt.Test__to_gfui("Ctrl + Shift + A" , "mod.cs+key.a"); + fxt.Test__to_gfui("Ctrl + Alt + Shift + A" , "mod.cas+key.a"); + } + @Test public void Keypad_enter() { + fxt.Test__to_norm("key.numpad_enter" , "Numpad Enter"); + fxt.Test__to_norm("mod.c+key.numpad_enter" , "Ctrl + Numpad Enter"); + } + @Test public void None() { + fxt.Test__to_gfui("None" , "key.none"); + fxt.Test__to_norm("key.none" , "None"); + } +} +class Gfui_bnd_parser_fxt { + private Gfui_bnd_parser parser; + public void Clear() { + parser = Gfui_bnd_parser.new_en_(); + } + public void Test__to_norm(String key, String expd) { + Tfds.Eq(expd, parser.Xto_norm(key)); + } + public void Test__to_gfui(String key, String expd) { + Tfds.Eq(expd, parser.Xto_gfui(key)); + } +} diff --git a/400_xowa/src/gplx/langs/dsvs/Dsv_fld_parser.java b/400_xowa/src/gplx/langs/dsvs/Dsv_fld_parser.java index a27517de8..7e5a20b5b 100644 --- a/400_xowa/src/gplx/langs/dsvs/Dsv_fld_parser.java +++ b/400_xowa/src/gplx/langs/dsvs/Dsv_fld_parser.java @@ -13,3 +13,8 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +public interface Dsv_fld_parser { + void Init(byte fld_dlm, byte row_dlm); + int Parse(Dsv_tbl_parser tbl_parser, Dsv_wkr_base mgr, byte[] src, int pos, int src_len, int fld_idx, int fld_bgn); +} diff --git a/400_xowa/src/gplx/langs/dsvs/Dsv_fld_parser_.java b/400_xowa/src/gplx/langs/dsvs/Dsv_fld_parser_.java index a27517de8..0ceb21f36 100644 --- a/400_xowa/src/gplx/langs/dsvs/Dsv_fld_parser_.java +++ b/400_xowa/src/gplx/langs/dsvs/Dsv_fld_parser_.java @@ -13,3 +13,100 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.langs.dsvs; import gplx.*; import gplx.langs.*; +public class Dsv_fld_parser_ { + public static final Dsv_fld_parser Bry_parser = Dsv_fld_parser_bry.Instance; + public static final Dsv_fld_parser Int_parser = Dsv_fld_parser_int.Instance; + public static final Dsv_fld_parser Line_parser__comment_is_pipe = new Dsv_fld_parser_line(Byte_ascii.Pipe); + public static Err err_fld_unhandled(Dsv_fld_parser parser, Dsv_wkr_base wkr, int fld_idx, byte[] src, int bgn, int end) { + throw Err_.new_wo_type("fld unhandled", "parser", Type_.Name_by_obj(parser), "wkr", Type_.Name_by_obj(wkr), "fld_idx", fld_idx, "val", String_.new_u8(src, bgn, end)).Trace_ignore_add_1_(); + } +} +class Dsv_fld_parser_line implements Dsv_fld_parser { + private byte row_dlm = Byte_ascii.Nl; private final byte comment_dlm; + public Dsv_fld_parser_line(byte comment_dlm) {this.comment_dlm = comment_dlm;} + public void Init(byte fld_dlm, byte row_dlm) { + this.row_dlm = row_dlm; + } + public int Parse(Dsv_tbl_parser parser, Dsv_wkr_base wkr, byte[] src, int pos, int src_len, int fld_idx, int fld_bgn) { + while (true) { + boolean pos_is_last = pos == src_len; + byte b = pos_is_last ? row_dlm : src[pos]; + if (b == comment_dlm) { + pos = Bry_find_.Find_fwd_until(src, pos, src_len, row_dlm); + if (pos == Bry_find_.Not_found) + pos = src_len; + } + else if (b == row_dlm) { + boolean pass = wkr.Write_bry(parser, fld_idx, src, fld_bgn, pos); + if (!pass) throw Dsv_fld_parser_.err_fld_unhandled(this, wkr, fld_idx, src, fld_bgn, pos); + wkr.Commit_itm(parser, pos); + int rv = pos + 1; // row_dlm is always 1 byte + parser.Update_by_row(rv); + return rv; + } + else + ++pos; + } + } +} +class Dsv_fld_parser_bry implements Dsv_fld_parser { + private byte fld_dlm = Byte_ascii.Pipe, row_dlm = Byte_ascii.Nl; + public void Init(byte fld_dlm, byte row_dlm) { + this.fld_dlm = fld_dlm; this.row_dlm = row_dlm; + } + public int Parse(Dsv_tbl_parser parser, Dsv_wkr_base wkr, byte[] src, int pos, int src_len, int fld_idx, int fld_bgn) { + while (true) { + boolean pos_is_last = pos == src_len; + byte b = pos_is_last ? row_dlm : src[pos]; + if (b == fld_dlm) { + boolean pass = wkr.Write_bry(parser, fld_idx, src, fld_bgn, pos); + if (!pass) throw Dsv_fld_parser_.err_fld_unhandled(this, wkr, fld_idx, src, fld_bgn, pos); + int rv = pos + 1; // fld_dlm is always 1 byte + parser.Update_by_fld(rv); + return rv; + } + else if (b == row_dlm) { + boolean pass = wkr.Write_bry(parser, fld_idx, src, fld_bgn, pos); + if (!pass) throw Dsv_fld_parser_.err_fld_unhandled(this, wkr, fld_idx, src, fld_bgn, pos); + wkr.Commit_itm(parser, pos); + int rv = pos + 1; // row_dlm is always 1 byte + parser.Update_by_row(rv); + return rv; + } + else + ++pos; + } + } + public static final Dsv_fld_parser_bry Instance = new Dsv_fld_parser_bry(); Dsv_fld_parser_bry() {} +} +class Dsv_fld_parser_int implements Dsv_fld_parser { + private byte fld_dlm = Byte_ascii.Pipe, row_dlm = Byte_ascii.Nl; + public void Init(byte fld_dlm, byte row_dlm) { + this.fld_dlm = fld_dlm; this.row_dlm = row_dlm; + } + public int Parse(Dsv_tbl_parser parser, Dsv_wkr_base wkr, byte[] src, int pos, int src_len, int fld_idx, int fld_bgn) { + while (true) { + boolean pos_is_last = pos == src_len; + byte b = pos_is_last ? row_dlm : src[pos]; + if (b == fld_dlm) { + boolean pass = wkr.Write_int(parser, fld_idx, pos, Bry_.To_int_or(src, fld_bgn, pos, -1)); + if (!pass) throw Dsv_fld_parser_.err_fld_unhandled(this, wkr, fld_idx, src, fld_bgn, pos); + int rv = pos + 1; // fld_dlm is always 1 byte + parser.Update_by_fld(rv); + return rv; + } + else if (b == row_dlm) { + boolean pass = wkr.Write_int(parser, fld_idx, pos, Bry_.To_int_or(src, fld_bgn, pos, -1)); + if (!pass) throw Dsv_fld_parser_.err_fld_unhandled(this, wkr, fld_idx, src, fld_bgn, pos); + wkr.Commit_itm(parser, pos); + int rv = pos + 1; // row_dlm is always 1 byte + parser.Update_by_row(rv); + return rv; + } + else + ++pos; + } + } + public static final Dsv_fld_parser_int Instance = new Dsv_fld_parser_int(); Dsv_fld_parser_int() {} +} diff --git a/400_xowa/src/gplx/langs/dsvs/Dsv_tbl_parser.java b/400_xowa/src/gplx/langs/dsvs/Dsv_tbl_parser.java index a27517de8..1ef8e77fe 100644 --- a/400_xowa/src/gplx/langs/dsvs/Dsv_tbl_parser.java +++ b/400_xowa/src/gplx/langs/dsvs/Dsv_tbl_parser.java @@ -13,3 +13,69 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +public class Dsv_tbl_parser implements Gfo_invk, Rls_able { + private Dsv_wkr_base mgr; + private Dsv_fld_parser[] fld_parsers = new Dsv_fld_parser[2]; private int fld_parsers_len = 2; + public byte[] Src() {return src;} private byte[] src; + public int Fld_bgn() {return fld_bgn;} private int fld_bgn = 0; + public int Fld_idx() {return fld_idx;} private int fld_idx = 0; + public int Row_bgn() {return row_bgn;} private int row_bgn = 0; + public int Row_idx() {return row_idx;} private int row_idx = 0; + public boolean Skip_blank_lines() {return skip_blank_lines;} public Dsv_tbl_parser Skip_blank_lines_(boolean v) {skip_blank_lines = v; return this;} private boolean skip_blank_lines = true; + public byte Fld_dlm() {return fld_dlm;} public Dsv_tbl_parser Fld_dlm_(byte v) {fld_dlm = v; return this;} private byte fld_dlm = Byte_ascii.Pipe; + public byte Row_dlm() {return row_dlm;} public Dsv_tbl_parser Row_dlm_(byte v) {row_dlm = v; return this;} private byte row_dlm = Byte_ascii.Nl; + public void Init(Dsv_wkr_base mgr, Dsv_fld_parser... fld_parsers) { + this.mgr = mgr; + this.fld_parsers = fld_parsers; + this.fld_parsers_len = fld_parsers.length; + for (int i = 0; i < fld_parsers_len; i++) + fld_parsers[i].Init(fld_dlm, row_dlm); + } + public void Clear() { + fld_bgn = fld_idx = row_bgn = row_idx = 0; + } + public Err Err_row_bgn(String fmt, int pos) { + return Err_.new_wo_type(fmt, "line", String_.new_u8(src, row_bgn, pos)).Trace_ignore_add_1_(); + } + public void Update_by_fld(int pos) { + fld_bgn = pos; + ++fld_idx; + } + public void Update_by_row(int pos) { + row_bgn = fld_bgn = pos; + ++row_idx; + fld_idx = 0; + } + public void Parse(byte[] src) { + int src_len = src.length; if (src_len == 0) return; // NOTE: do not process if empty; note that loop below will process once for empty row + this.src = src; + int pos = 0; + while (true) { + if (fld_idx == 0 && skip_blank_lines) { // row committed; skip blank lines + while (pos < src_len) { + if (src[pos] == row_dlm) { + ++pos; + row_bgn = fld_bgn = pos; + } + else + break; + } + } + if (fld_idx == fld_parsers_len) break; + Dsv_fld_parser fld_parser = fld_parsers[fld_idx]; + pos = fld_parser.Parse(this, mgr, src, pos, src_len, fld_idx, fld_bgn); + if ( pos > src_len // pos is now fully past src_len; exit + || pos == src_len && fld_idx == 0 // last pos but fld_idx > 0; do one more iteration which will "commit row; EX: 2 fields and src of "a|"; EOS should close out row + ) break; + } + } + public void Rls() { + src = null; fld_parsers = null; mgr = null; fld_parsers_len = 0; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_load_by_str)) Parse(m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_load_by_str = "load_by_str"; +} diff --git a/400_xowa/src/gplx/langs/dsvs/Dsv_tbl_parser_int_tst.java b/400_xowa/src/gplx/langs/dsvs/Dsv_tbl_parser_int_tst.java index a27517de8..eea30e700 100644 --- a/400_xowa/src/gplx/langs/dsvs/Dsv_tbl_parser_int_tst.java +++ b/400_xowa/src/gplx/langs/dsvs/Dsv_tbl_parser_int_tst.java @@ -13,3 +13,50 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +import org.junit.*; +public class Dsv_tbl_parser_int_tst { + private Dsv_mok_fxt fxt = new Dsv_mok_fxt(); + @Test public void Basic() { + fxt .Test_load(String_.Concat_lines_nl_skip_last + ( "a|1|3" + , "b|2|4" + ) + , fxt.mgr_int_() + , fxt.itm_int_("a", 1, 3) + , fxt.itm_int_("b", 2, 4) + ); + } +} +class Mok_int_itm implements To_str_able { + private String fld_0; + private int fld_1, fld_2; + public Mok_int_itm(String fld_0, int fld_1, int fld_2) {this.fld_0 = fld_0; this.fld_1 = fld_1; this.fld_2 = fld_2;} + public String To_str() {return String_.Concat_with_str("|", fld_0, Int_.To_str(fld_1), Int_.To_str(fld_2));} +} +class Mok_int_mgr extends Mok_mgr_base { + public void Clear() {itms.Clear();} + @Override public To_str_able[] Itms() {return (To_str_able[])itms.To_ary(To_str_able.class);} private List_adp itms = List_adp_.New(); + private String fld_0; + private int fld_1, fld_2; + @Override public Dsv_fld_parser[] Fld_parsers() { + return new Dsv_fld_parser[] {Dsv_fld_parser_bry.Instance, Dsv_fld_parser_int.Instance, Dsv_fld_parser_int.Instance}; + } + @Override public boolean Write_bry(Dsv_tbl_parser parser, int fld_idx, byte[] src, int bgn, int end) { + switch (fld_idx) { + case 0: fld_0 = String_.new_u8(src, bgn, end); return true; + default: return false; + } + } + @Override public boolean Write_int(Dsv_tbl_parser parser, int fld_idx, int pos, int val_int) { + switch (fld_idx) { + case 1: fld_1 = val_int; return true; + case 2: fld_2 = val_int; return true; + default: return false; + } + } + @Override public void Commit_itm(Dsv_tbl_parser parser, int pos) { + Mok_int_itm itm = new Mok_int_itm(fld_0, fld_1, fld_2); + itms.Add(itm); + } +} diff --git a/400_xowa/src/gplx/langs/dsvs/Dsv_tbl_parser_str_tst.java b/400_xowa/src/gplx/langs/dsvs/Dsv_tbl_parser_str_tst.java index a27517de8..2f9b1fd32 100644 --- a/400_xowa/src/gplx/langs/dsvs/Dsv_tbl_parser_str_tst.java +++ b/400_xowa/src/gplx/langs/dsvs/Dsv_tbl_parser_str_tst.java @@ -13,3 +13,95 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +import org.junit.*; +public class Dsv_tbl_parser_str_tst { + private Dsv_mok_fxt fxt = new Dsv_mok_fxt(); + @Test public void Basic() { + fxt .Test_load(String_.Concat_lines_nl_skip_last + ( "a|A" + , "b|B" + ) + , fxt.mgr_str_(2) + , fxt.itm_str_("a", "A") + , fxt.itm_str_("b", "B") + ); + } + @Test public void Blank_lines() { + fxt .Test_load(String_.Concat_lines_nl_skip_last + ( "" + , "a|A" + , "" + , "b|B" + , "" + ) + , fxt.mgr_str_(2) + , fxt.itm_str_("a", "A") + , fxt.itm_str_("b", "B") + ); + } + @Test public void Incomplete_row() { + fxt .Test_load(String_.Concat_lines_nl_skip_last + ( "a" + , "b" + , "" + ) + , fxt.mgr_str_(2) + , fxt.itm_str_("a") + , fxt.itm_str_("b") + ); + } + @Test public void Incomplete_row_2() { // PURPOSE: handle multiple incomplete cells + fxt .Test_load(String_.Concat_lines_nl_skip_last + ( "a|") + , fxt.mgr_str_(3) + , fxt.itm_str_("a", "") + ); + } +} +abstract class Mok_mgr_base extends Dsv_wkr_base { + public abstract To_str_able[] Itms(); +} +class Dsv_mok_fxt { + private Dsv_tbl_parser tbl_parser = new Dsv_tbl_parser(); + public Dsv_mok_fxt Clear() { + tbl_parser.Clear(); + return this; + } + public Mok_mgr_base mgr_int_() {return new Mok_int_mgr();} + public Mok_mgr_base mgr_str_(int len) {return new Mok_str_mgr(len);} + public Mok_str_itm itm_str_(String... flds) {return new Mok_str_itm(flds);} + public Mok_int_itm itm_int_(String fld_0, int fld_1, int fld_2) {return new Mok_int_itm(fld_0, fld_1, fld_2);} + public void Test_load(String src, Mok_mgr_base mgr, To_str_able... expd) { + mgr.Load_by_bry(Bry_.new_u8(src)); + Tfds.Eq_ary_str(expd, mgr.Itms()); + } +} +class Mok_str_itm implements To_str_able { + private String[] flds; + public Mok_str_itm(String[] flds) {this.flds = flds;} + public String To_str() {return String_.Concat_with_str("|", flds);} +} +class Mok_str_mgr extends Mok_mgr_base { + private int flds_len; + public Mok_str_mgr(int flds_len) { + this.flds_len = flds_len; + } + public void Clear() {itms.Clear();} + @Override public To_str_able[] Itms() {return (To_str_able[])itms.To_ary(To_str_able.class);} private List_adp itms = List_adp_.New(); + private List_adp flds = List_adp_.New(); + @Override public boolean Write_bry(Dsv_tbl_parser parser, int fld_idx, byte[] src, int bgn, int end) { + flds.Add(String_.new_u8(src, bgn, end)); + return true; + } + @Override public Dsv_fld_parser[] Fld_parsers() { + Dsv_fld_parser[] rv = new Dsv_fld_parser[flds_len]; + for (int i = 0; i < flds_len; i++) + rv[i] = Dsv_fld_parser_.Bry_parser; + return rv; + } + @Override public void Commit_itm(Dsv_tbl_parser parser, int pos) { + Mok_str_itm itm = new Mok_str_itm((String[])flds.To_ary_and_clear(String.class)); + itms.Add(itm); + } +} diff --git a/400_xowa/src/gplx/langs/dsvs/Dsv_wkr_base.java b/400_xowa/src/gplx/langs/dsvs/Dsv_wkr_base.java index a27517de8..103bf4636 100644 --- a/400_xowa/src/gplx/langs/dsvs/Dsv_wkr_base.java +++ b/400_xowa/src/gplx/langs/dsvs/Dsv_wkr_base.java @@ -13,3 +13,28 @@ 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.langs.dsvs; import gplx.*; import gplx.langs.*; +public abstract class Dsv_wkr_base implements Gfo_invk { + public abstract Dsv_fld_parser[] Fld_parsers(); + public byte[] Src() {return src;} private byte[] src; + public abstract void Commit_itm(Dsv_tbl_parser parser, int pos); + @gplx.Virtual public boolean Write_bry(Dsv_tbl_parser parser, int fld_idx, byte[] src, int bgn, int end) {return false;} + @gplx.Virtual public boolean Write_int(Dsv_tbl_parser parser, int fld_idx, int pos, int val_int) {return false;} + public void Load_by_bry(byte[] src) { + this.src = src; + Dsv_tbl_parser tbl_parser = new Dsv_tbl_parser(); // NOTE: this proc should only be called once, so don't bother caching tbl_parser + tbl_parser.Init(this, this.Fld_parsers()); + Load_by_bry_bgn(); + tbl_parser.Parse(src); + tbl_parser.Rls(); + Load_by_bry_end(); + } + @gplx.Virtual public void Load_by_bry_bgn() {} + @gplx.Virtual public void Load_by_bry_end() {} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_load_by_str)) Load_by_bry(m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk_load_by_str = "load_by_str"; +} diff --git a/400_xowa/src/gplx/langs/gfs/Gfs_lxr.java b/400_xowa/src/gplx/langs/gfs/Gfs_lxr.java index a27517de8..9f0a333aa 100644 --- a/400_xowa/src/gplx/langs/gfs/Gfs_lxr.java +++ b/400_xowa/src/gplx/langs/gfs/Gfs_lxr.java @@ -13,3 +13,200 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +interface Gfs_lxr { + int Lxr_tid(); + int Process(Gfs_parser_ctx ctx, int bgn, int end); +} +class Gfs_lxr_whitespace implements Gfs_lxr { + public int Lxr_tid() {return Gfs_lxr_.Tid_whitespace;} + public int Process(Gfs_parser_ctx ctx, int bgn, int end) { + byte[] src = ctx.Src(); int src_len = ctx.Src_len(); + int rv = Gfs_lxr_.Rv_eos, cur_pos; + for (cur_pos = end; cur_pos < src_len; cur_pos++) { + byte b = src[cur_pos]; + Object o = ctx.Trie().Match_at_w_b0(ctx.Trie_rv(), b, src, cur_pos, src_len); + if (o == null) { + rv = Gfs_lxr_.Rv_null; + ctx.Process_null(cur_pos); + break; + } + else { + Gfs_lxr lxr = (Gfs_lxr)o; + if (lxr.Lxr_tid() == Gfs_lxr_.Tid_whitespace) {} + else { + rv = Gfs_lxr_.Rv_lxr; + ctx.Process_lxr(cur_pos, lxr); + break; + } + } + } + return rv; + } + public static final Gfs_lxr_whitespace Instance = new Gfs_lxr_whitespace(); Gfs_lxr_whitespace() {} +} +class Gfs_lxr_comment_flat implements Gfs_lxr { + public Gfs_lxr_comment_flat(byte[] bgn_bry, byte[] end_bry) { + this.bgn_bry = bgn_bry; this.bgn_bry_len = bgn_bry.length; + this.end_bry = end_bry; this.end_bry_len = end_bry.length; + } byte[] bgn_bry, end_bry; int bgn_bry_len, end_bry_len; + public int Lxr_tid() {return Gfs_lxr_.Tid_comment;} + public int Process(Gfs_parser_ctx ctx, int lxr_bgn, int lxr_end) { + byte[] src = ctx.Src(); int src_len = ctx.Src_len(); + int end_pos = Bry_find_.Find_fwd(src, end_bry, lxr_end, src_len); + // if (end_pos == Bry_find_.Not_found) throw Err_.new_fmt_("comment is not closed: {0}", String_.new_u8(end_bry)); + return (end_pos == Bry_find_.Not_found) + ? src_len // allow eos to terminate flat comment; needed for "tidy-always-adds-nl-in-textarea" fix; NOTE: DATE:2014-06-21 + : end_pos + end_bry_len; // position after end_bry + } +} +class Gfs_lxr_identifier implements Gfs_lxr { + public int Lxr_tid() {return Gfs_lxr_.Tid_identifier;} + public int Process(Gfs_parser_ctx ctx, int bgn, int end) { + byte[] src = ctx.Src(); int src_len = ctx.Src_len(); + int pos, rv = Gfs_lxr_.Rv_eos; + for (pos = end; pos < src_len; pos++) { + byte b = src[pos]; + Object o = ctx.Trie().Match_at_w_b0(ctx.Trie_rv(), b, src, pos, src_len); + if (o == null) { // invalid char; stop; + rv = Gfs_lxr_.Rv_null; + ctx.Process_null(pos); + break; + } + else { + Gfs_lxr lxr = (Gfs_lxr)o; + if (lxr.Lxr_tid() == Gfs_lxr_.Tid_identifier) {} // still an identifier; continue + else { // new lxr (EX: "." in "abc."); (a) hold word of "abc"; mark "." as new lxr; + ctx.Hold_word(bgn, pos); + rv = Gfs_lxr_.Rv_lxr; + ctx.Process_lxr(pos, lxr); + break; + } + } + } + if (rv == Gfs_lxr_.Rv_eos) ctx.Process_eos(); // eos + return rv; + } + public static final Gfs_lxr_identifier Instance = new Gfs_lxr_identifier(); Gfs_lxr_identifier() {} +} +class Gfs_lxr_semic implements Gfs_lxr { + public int Lxr_tid() {return Gfs_lxr_.Tid_semic;} + public int Process(Gfs_parser_ctx ctx, int bgn, int end) { + switch (ctx.Prv_lxr()) { + case Gfs_lxr_.Tid_identifier: ctx.Make_nde(bgn, end); ctx.Cur_nde_from_stack(); break; // a; + case Gfs_lxr_.Tid_quote: + case Gfs_lxr_.Tid_paren_end: ctx.Cur_nde_from_stack(); break; // a(); + case Gfs_lxr_.Tid_semic: break; // a;; ignore; + default: ctx.Err_mgr().Fail_invalid_lxr(ctx, bgn, this.Lxr_tid(), Byte_ascii.Semic); break; + } + return end; + } + public static final Gfs_lxr_semic Instance = new Gfs_lxr_semic(); Gfs_lxr_semic() {} +} +class Gfs_lxr_dot implements Gfs_lxr { + public int Lxr_tid() {return Gfs_lxr_.Tid_dot;} + public int Process(Gfs_parser_ctx ctx, int bgn, int end) { + switch (ctx.Prv_lxr()) { + case Gfs_lxr_.Tid_identifier: ctx.Make_nde(bgn, end); break; // a. + case Gfs_lxr_.Tid_paren_end: break; // a(). + default: ctx.Err_mgr().Fail_invalid_lxr(ctx, bgn, this.Lxr_tid(), Byte_ascii.Dot); break; + } + return end; + } + public static final Gfs_lxr_dot Instance = new Gfs_lxr_dot(); Gfs_lxr_dot() {} +} +class Gfs_lxr_paren_bgn implements Gfs_lxr { + public int Lxr_tid() {return Gfs_lxr_.Tid_paren_bgn;} + public int Process(Gfs_parser_ctx ctx, int bgn, int end) { + switch (ctx.Prv_lxr()) { + case Gfs_lxr_.Tid_identifier: ctx.Make_nde(bgn, end); break; // a(; + default: ctx.Err_mgr().Fail_invalid_lxr(ctx, bgn, this.Lxr_tid(), Byte_ascii.Paren_bgn); break; + } + return end; + } + public static final Gfs_lxr_paren_bgn Instance = new Gfs_lxr_paren_bgn(); Gfs_lxr_paren_bgn() {} +} +class Gfs_lxr_paren_end implements Gfs_lxr { + public int Lxr_tid() {return Gfs_lxr_.Tid_paren_end;} + public int Process(Gfs_parser_ctx ctx, int bgn, int end) { + switch (ctx.Prv_lxr()) { + case Gfs_lxr_.Tid_paren_bgn: + case Gfs_lxr_.Tid_quote: break; // "))", "abc)", "'abc')" + case Gfs_lxr_.Tid_identifier: ctx.Make_atr_by_idf(); break; // 123) + default: ctx.Err_mgr().Fail_invalid_lxr(ctx, bgn, this.Lxr_tid(), Byte_ascii.Paren_end); break; + } + return end; + } + public static final Gfs_lxr_paren_end Instance = new Gfs_lxr_paren_end(); Gfs_lxr_paren_end() {} +} +class Gfs_lxr_quote implements Gfs_lxr { + public Gfs_lxr_quote(byte[] bgn_bry, byte[] end_bry) { + this.bgn_bry_len = bgn_bry.length; + this.end_bry = end_bry; this.end_bry_len = end_bry.length; + } private byte[] end_bry; private int bgn_bry_len, end_bry_len; + public int Lxr_tid() {return Gfs_lxr_.Tid_quote;} + public int Process(Gfs_parser_ctx ctx, int lxr_bgn, int lxr_end) { + byte[] src = ctx.Src(); int src_len = ctx.Src_len(); + int end_pos = Bry_find_.Find_fwd(src, end_bry, lxr_end, src_len); + if (end_pos == Bry_find_.Not_found) throw Err_.new_wo_type("quote is not closed", "end", String_.new_u8(end_bry)); + Bry_bfr bfr = ctx.Tmp_bfr().Clear(); + int prv_pos = lxr_end; + int nxt_pos = end_pos + end_bry_len; + if (Bry_.Match(src, nxt_pos, nxt_pos + end_bry_len, end_bry)) { // end_bry is doubled; EX: end_bry = ' and raw = a'' + while (true) { + bfr.Add_mid(src, prv_pos, end_pos); // add everything up to end_bry + bfr.Add(end_bry); // add end_bry + prv_pos = nxt_pos + end_bry_len; // set prv_pos to after doubled end_bry + end_pos = Bry_find_.Find_fwd(src, end_bry, prv_pos, src_len); + if (end_pos == Bry_find_.Not_found) throw Err_.new_wo_type("quote is not closed", "end", String_.new_u8(end_bry)); + nxt_pos = end_pos + end_bry_len; + if (!Bry_.Match(src, nxt_pos, nxt_pos + end_bry_len, end_bry)) { + bfr.Add_mid(src, prv_pos, end_pos); + break; + } + } + ctx.Make_atr_by_bry(lxr_bgn + bgn_bry_len, end_pos, bfr.To_bry_and_clear()); + } + else + ctx.Make_atr(lxr_bgn + bgn_bry_len, end_pos); + return end_pos + end_bry_len; // position after quote + } +} +class Gfs_lxr_curly_bgn implements Gfs_lxr { + public int Lxr_tid() {return Gfs_lxr_.Tid_curly_bgn;} + public int Process(Gfs_parser_ctx ctx, int bgn, int end) { + switch (ctx.Prv_lxr()) { + case Gfs_lxr_.Tid_identifier: ctx.Make_nde(bgn, end); ctx.Stack_add(); break; // a{; + case Gfs_lxr_.Tid_paren_end: ctx.Stack_add(); break; // a(){; NOTE: node exists but needs to be pushed onto stack + default: ctx.Err_mgr().Fail_invalid_lxr(ctx, bgn, this.Lxr_tid(), Byte_ascii.Curly_bgn); break; + } + return end; + } + public static final Gfs_lxr_curly_bgn Instance = new Gfs_lxr_curly_bgn(); Gfs_lxr_curly_bgn() {} +} +class Gfs_lxr_curly_end implements Gfs_lxr { + public int Lxr_tid() {return Gfs_lxr_.Tid_curly_end;} + public int Process(Gfs_parser_ctx ctx, int bgn, int end) { + ctx.Stack_pop(bgn); + return end; + } + public static final Gfs_lxr_curly_end Instance = new Gfs_lxr_curly_end(); Gfs_lxr_curly_end() {} +} +class Gfs_lxr_equal implements Gfs_lxr { + public int Lxr_tid() {return Gfs_lxr_.Tid_eq;} + public int Process(Gfs_parser_ctx ctx, int bgn, int end) { + ctx.Make_nde(bgn, end).Op_tid_(Gfs_nde.Op_tid_assign); + return end; + } + public static final Gfs_lxr_equal Instance = new Gfs_lxr_equal(); Gfs_lxr_equal() {} +} +class Gfs_lxr_comma implements Gfs_lxr { + public int Lxr_tid() {return Gfs_lxr_.Tid_comma;} + public int Process(Gfs_parser_ctx ctx, int bgn, int end) { + switch (ctx.Prv_lxr()) { + case Gfs_lxr_.Tid_identifier: ctx.Make_atr_by_idf(); break; // 123, + } + return end; + } + public static final Gfs_lxr_comma Instance = new Gfs_lxr_comma(); Gfs_lxr_comma() {} +} diff --git a/400_xowa/src/gplx/langs/gfs/Gfs_lxr_.java b/400_xowa/src/gplx/langs/gfs/Gfs_lxr_.java index a27517de8..186254c02 100644 --- a/400_xowa/src/gplx/langs/gfs/Gfs_lxr_.java +++ b/400_xowa/src/gplx/langs/gfs/Gfs_lxr_.java @@ -13,3 +13,25 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +class Gfs_lxr_ { + public static final int Rv_init = -1, Rv_null = -2, Rv_eos = -3, Rv_lxr = -4; + public static final int Tid_identifier = 1, Tid_dot = 2, Tid_semic = 3, Tid_paren_bgn = 4, Tid_paren_end = 5, Tid_curly_bgn = 6, Tid_curly_end = 7, Tid_quote = 8, Tid_comma = 9, Tid_whitespace = 10, Tid_comment = 11, Tid_eq = 12; + public static String Tid__name(int tid) { + switch (tid) { + case Tid_identifier: return "identifier"; + case Tid_dot: return "dot"; + case Tid_semic: return "semic"; + case Tid_paren_bgn: return "paren_bgn"; + case Tid_paren_end: return "paren_end"; + case Tid_curly_bgn: return "curly_bgn"; + case Tid_curly_end: return "curly_end"; + case Tid_quote: return "quote"; + case Tid_comma: return "comma"; + case Tid_whitespace: return "whitespace"; + case Tid_comment: return "comment"; + case Tid_eq: return "eq"; + default: throw Err_.new_unhandled(tid); + } + } +} diff --git a/400_xowa/src/gplx/langs/gfs/Gfs_msg_bldr.java b/400_xowa/src/gplx/langs/gfs/Gfs_msg_bldr.java index a27517de8..36891e07c 100644 --- a/400_xowa/src/gplx/langs/gfs/Gfs_msg_bldr.java +++ b/400_xowa/src/gplx/langs/gfs/Gfs_msg_bldr.java @@ -13,3 +13,39 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +import gplx.core.gfo_regys.*; +public class Gfs_msg_bldr implements GfoMsgParser { + private final Object thread_lock = new Object(); + private final Gfs_parser parser = new Gfs_parser(); + public GfoMsg ParseToMsg(String s) {return Bld(s);} + public GfoMsg Bld(String src) {return Bld(Bry_.new_u8(src));} + public GfoMsg Bld(byte[] src) { + synchronized (thread_lock) { // LOCK:Gfs_parser called when converting messages in Xow_msg_mgr; DATE:2016-10-18 + Gfs_nde nde = parser.Parse(src); + return Bld_msg(src, nde); + } + } + private GfoMsg Bld_msg(byte[] src, Gfs_nde nde) { + boolean op_is_assign = (nde.Op_tid() == Gfs_nde.Op_tid_assign); + String name = String_.new_u8(nde.Name_bry(src)); + if (op_is_assign) name += Tkn_mutator; + GfoMsg rv = GfoMsg_.new_parse_(name); + int len = nde.Atrs_len(); + for (int i = 0; i < len; i++) { + Gfs_nde atr = nde.Atrs_get_at(i); + rv.Add("", String_.new_u8(atr.Name_bry(src))); + } + len = nde.Subs_len(); + for (int i = 0; i < len; i++) { + Gfs_nde sub = nde.Subs_get_at(i); + if (op_is_assign) // NOTE: for now (a) assignss cannot be nested; EX: "a.b = c;" is okay but "a.b = c.d;" is not + rv.Add("", Bld_msg(src, sub).Key()); + else + rv.Subs_add(Bld_msg(src, sub)); + } + return rv; + } + public static final Gfs_msg_bldr Instance = new Gfs_msg_bldr(); Gfs_msg_bldr() {} // TS.static + public static final String Tkn_mutator = "_"; +} diff --git a/400_xowa/src/gplx/langs/gfs/Gfs_msg_bldr_tst.java b/400_xowa/src/gplx/langs/gfs/Gfs_msg_bldr_tst.java index a27517de8..8196ac652 100644 --- a/400_xowa/src/gplx/langs/gfs/Gfs_msg_bldr_tst.java +++ b/400_xowa/src/gplx/langs/gfs/Gfs_msg_bldr_tst.java @@ -13,3 +13,62 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +import org.junit.*; import gplx.core.strings.*; +public class Gfs_msg_bldr_tst { + @Before public void init() {fxt.Clear();} Gfs_msg_bldr_fxt fxt = new Gfs_msg_bldr_fxt(); + @Test public void Basic() { + fxt.Test_build("a;", fxt.msg_("a")); + } + @Test public void Dot() { + fxt.Test_build("a.b.c;" + , fxt.msg_("a").Subs_ + ( fxt.msg_("b").Subs_ + ( fxt.msg_("c") + ))); + } + @Test public void Args() { + fxt.Test_build("a('b', 'c');", fxt.msg_("a", fxt.kv_("", "b"), fxt.kv_("", "c"))); + } + @Test public void Args_num() { + fxt.Test_build("a(1);", fxt.msg_("a", fxt.kv_("", "1"))); + } + @Test public void Assign() { + fxt.Test_build("a = 'b';", fxt.msg_("a_", fxt.kv_("", "b"))); + } + @Test public void Assign_num() { + fxt.Test_build("a = 1;", fxt.msg_("a_", fxt.kv_("", "1"))); + } +} +class Gfs_msg_bldr_fxt { + public void Clear() {} String_bldr sb = String_bldr_.new_(); Gfs_msg_bldr msg_bldr = Gfs_msg_bldr.Instance; + public Keyval kv_(String key, String val) {return Keyval_.new_(key, val);} + public GfoMsg msg_(String key, Keyval... args) { + GfoMsg rv = GfoMsg_.new_parse_(key); + int len = args.length; + for (int i = 0; i < len; i++) { + Keyval kv = args[i]; + rv.Add(kv.Key(), kv.Val()); + } + return rv; + } + public void Test_build(String raw, GfoMsg... expd) { + GfoMsg root = msg_bldr.Bld(raw); + Tfds.Eq_str_lines(Xto_str(expd), Xto_str(To_ary(root))); + } + GfoMsg[] To_ary(GfoMsg msg) { + int len = msg.Subs_count(); + GfoMsg[] rv = new GfoMsg[len]; + for (int i = 0; i < len; i++) + rv[i] = msg.Subs_getAt(i); + return rv; + } + String Xto_str(GfoMsg[] ary) { + int len = ary.length; + for (int i = 0; i < len; i++) { + if (i != 0) sb.Add_char_crlf(); + sb.Add(ary[i].To_str()); + } + return sb.To_str_and_clear(); + } +} diff --git a/400_xowa/src/gplx/langs/gfs/Gfs_nde.java b/400_xowa/src/gplx/langs/gfs/Gfs_nde.java index a27517de8..0cb260ce5 100644 --- a/400_xowa/src/gplx/langs/gfs/Gfs_nde.java +++ b/400_xowa/src/gplx/langs/gfs/Gfs_nde.java @@ -13,3 +13,71 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +public class Gfs_nde { + public byte[] Name_bry(byte[] src) {return name == null ? Bry_.Mid(src, name_bgn, name_end) : name;} + public byte[] Name() {return name;} public Gfs_nde Name_(byte[] v) {name = v; return this;} private byte[] name; + public int Name_bgn() {return name_bgn;} private int name_bgn = -1; + public int Name_end() {return name_end;} private int name_end = -1; + public Gfs_nde Name_rng_(int name_bgn, int name_end) {this.name_bgn = name_bgn; this.name_end = name_end; return this;} + public byte Op_tid() {return op_tid;} public Gfs_nde Op_tid_(byte v) {op_tid = v; return this;} private byte op_tid; + public void Subs_clear() { + for (int i = 0; i < subs_len; i++) + subs[i] = null; + subs_len = 0; + } + public int Subs_len() {return subs_len;} private int subs_len; + public Gfs_nde Subs_add_many(Gfs_nde... ary) { + int len = ary.length; + for (int i = 0; i < len; i++) + Subs_add(ary[i]); + return this; + } + public Gfs_nde Subs_add(Gfs_nde nde) { + int new_len = subs_len + 1; + if (new_len > subs_max) { // ary too small >>> expand + subs_max = new_len * 2; + Gfs_nde[] new_subs = new Gfs_nde[subs_max]; + Array_.Copy_to(subs, 0, new_subs, 0, subs_len); + subs = new_subs; + } + subs[subs_len] = nde; + subs_len = new_len; + return this; + } Gfs_nde[] subs = Gfs_nde.Ary_empty; int subs_max; int[] subs_pos_ary = Int_ary_.Empty; + public Gfs_nde Subs_get_at(int i) {return subs[i];} + public Gfs_nde[] Subs_to_ary() { + Gfs_nde[] rv = new Gfs_nde[subs_len]; + for (int i = 0; i < subs_len; i++) + rv[i] = subs[i]; + return rv; + } + public int Atrs_len() {return args_len;} private int args_len; + public Gfs_nde Atrs_get_at(int i) {return args[i];} + public Gfs_nde Atrs_add_many(Gfs_nde... ary) { + int len = ary.length; + for (int i = 0; i < len; i++) + Atrs_add(ary[i]); + return this; + } + public Gfs_nde Atrs_add(Gfs_nde nde) { + int new_len = args_len + 1; + if (new_len > args_max) { // ary too small >>> expand + args_max = new_len * 2; + Gfs_nde[] new_args = new Gfs_nde[args_max]; + Array_.Copy_to(args, 0, new_args, 0, args_len); + args = new_args; + } + args[args_len] = nde; + args_len = new_len; + return this; + } Gfs_nde[] args = Gfs_nde.Ary_empty; int args_max; int[] args_pos_ary = Int_ary_.Empty; + public Gfs_nde[] Atrs_to_ary() { + Gfs_nde[] rv = new Gfs_nde[args_len]; + for (int i = 0; i < args_len; i++) + rv[i] = args[i]; + return rv; + } + public static final Gfs_nde[] Ary_empty = new Gfs_nde[0]; + public static final byte Op_tid_null = 0, Op_tid_assign = 1; +} diff --git a/400_xowa/src/gplx/langs/gfs/Gfs_parser.java b/400_xowa/src/gplx/langs/gfs/Gfs_parser.java index a27517de8..a423a135b 100644 --- a/400_xowa/src/gplx/langs/gfs/Gfs_parser.java +++ b/400_xowa/src/gplx/langs/gfs/Gfs_parser.java @@ -13,3 +13,91 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +import gplx.core.btries.*; +public class Gfs_parser { + private final Btrie_fast_mgr trie = Gfs_parser_.trie_(); + private final Gfs_parser_ctx ctx = new Gfs_parser_ctx(); + public Gfs_nde Parse(byte[] src) { + ctx.Root().Subs_clear(); + int src_len = src.length; if (src_len == 0) return ctx.Root(); + ctx.Init(trie, src, src_len); + int pos = 0; + while (pos < src_len) { + byte b = src[pos]; + Object o = trie.Match_at_w_b0(ctx.Trie_rv(), b, src, pos, src_len); + if (o == null) + ctx.Err_mgr().Fail_unknown_char(ctx, pos, b); + else { + Gfs_lxr lxr = (Gfs_lxr)o; + while (lxr != null) { + int rslt = lxr.Process(ctx, pos, ctx.Trie_rv().Pos()); + switch (lxr.Lxr_tid()) { + case Gfs_lxr_.Tid_whitespace: break; + case Gfs_lxr_.Tid_comment: break; + default: ctx.Prv_lxr_(lxr.Lxr_tid()); break; + } + switch (rslt) { + case Gfs_lxr_.Rv_lxr: + pos = ctx.Nxt_pos(); + lxr = ctx.Nxt_lxr(); + break; + case Gfs_lxr_.Rv_eos: + pos = src_len; + lxr = null; + break; + default: + pos = rslt; + lxr = null; + break; + } + } + } + } + switch (ctx.Prv_lxr()) { + case Gfs_lxr_.Tid_curly_end: + case Gfs_lxr_.Tid_semic: break; + default: ctx.Err_mgr().Fail_eos(ctx); break; + } + return ctx.Root(); + } +} +class Gfs_parser_ { + public static Btrie_fast_mgr trie_() { + Btrie_fast_mgr rv = Btrie_fast_mgr.ci_a7(); // NOTE:ci.ascii:gfs;letters/symbols only; + Gfs_lxr_identifier word_lxr = Gfs_lxr_identifier.Instance; + trie_add_rng(rv, word_lxr, Byte_ascii.Ltr_a, Byte_ascii.Ltr_z); + trie_add_rng(rv, word_lxr, Byte_ascii.Ltr_A, Byte_ascii.Ltr_Z); + trie_add_rng(rv, word_lxr, Byte_ascii.Num_0, Byte_ascii.Num_9); + rv.Add(Byte_ascii.Underline, word_lxr); + trie_add_many(rv, Gfs_lxr_whitespace.Instance, Byte_ascii.Space, Byte_ascii.Nl, Byte_ascii.Cr, Byte_ascii.Tab); + trie_add_quote(rv, new byte[] {Byte_ascii.Apos}); + trie_add_quote(rv, new byte[] {Byte_ascii.Quote}); + trie_add_quote(rv, Bry_.new_a7("<:[\"\n"), Bry_.new_a7("\n\"]:>")); + trie_add_quote(rv, Bry_.new_a7("<:['\n"), Bry_.new_a7("\n']:>")); + trie_add_quote(rv, Bry_.new_a7("<:{'"), Bry_.new_a7("'}:>")); + trie_add_comment(rv, new byte[] {Byte_ascii.Slash, Byte_ascii.Slash}, new byte[] {Byte_ascii.Nl}); + trie_add_comment(rv, new byte[] {Byte_ascii.Slash, Byte_ascii.Star}, new byte[] {Byte_ascii.Star, Byte_ascii.Slash}); + rv.Add(Byte_ascii.Semic, Gfs_lxr_semic.Instance); + rv.Add(Byte_ascii.Paren_bgn, Gfs_lxr_paren_bgn.Instance); + rv.Add(Byte_ascii.Paren_end, Gfs_lxr_paren_end.Instance); + rv.Add(Byte_ascii.Curly_bgn, Gfs_lxr_curly_bgn.Instance); + rv.Add(Byte_ascii.Curly_end, Gfs_lxr_curly_end.Instance); + rv.Add(Byte_ascii.Dot, Gfs_lxr_dot.Instance); + rv.Add(Byte_ascii.Comma, Gfs_lxr_comma.Instance); + rv.Add(Byte_ascii.Eq, Gfs_lxr_equal.Instance); + return rv; + } + private static void trie_add_rng(Btrie_fast_mgr trie, Gfs_lxr lxr, byte bgn, byte end) { + for (byte b = bgn; b <= end; b++) + trie.Add(b, lxr); + } + private static void trie_add_many(Btrie_fast_mgr trie, Gfs_lxr lxr, byte... ary) { + int len = ary.length; + for (int i = 0; i < len; i++) + trie.Add(ary[i], lxr); + } + private static void trie_add_quote(Btrie_fast_mgr trie, byte[] bgn) {trie_add_quote(trie, bgn, bgn);} + private static void trie_add_quote(Btrie_fast_mgr trie, byte[] bgn, byte[] end) {trie.Add(bgn, new Gfs_lxr_quote(bgn, end));} + private static void trie_add_comment(Btrie_fast_mgr trie, byte[] bgn, byte[] end) {trie.Add(bgn, new Gfs_lxr_comment_flat(bgn, end));} +} diff --git a/400_xowa/src/gplx/langs/gfs/Gfs_parser_ctx.java b/400_xowa/src/gplx/langs/gfs/Gfs_parser_ctx.java index a27517de8..023443b7c 100644 --- a/400_xowa/src/gplx/langs/gfs/Gfs_parser_ctx.java +++ b/400_xowa/src/gplx/langs/gfs/Gfs_parser_ctx.java @@ -13,3 +13,113 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +import gplx.core.btries.*; +class Gfs_parser_ctx { + public Btrie_fast_mgr Trie() {return trie;} Btrie_fast_mgr trie; + public Btrie_rv Trie_rv() {return trie_rv;} private final Btrie_rv trie_rv = new Btrie_rv(); + public Gfs_nde Root() {return root;} Gfs_nde root = new Gfs_nde(); + public byte[] Src() {return src;} private byte[] src; + public int Src_len() {return src_len;} private int src_len; + public int Prv_lxr() {return prv_lxr;} public Gfs_parser_ctx Prv_lxr_(int v) {prv_lxr = v; return this;} private int prv_lxr; + public Gfs_nde Cur_nde() {return cur_nde;} Gfs_nde cur_nde; + public int Nxt_pos() {return nxt_pos;} private int nxt_pos; + public Gfs_lxr Nxt_lxr() {return nxt_lxr;} Gfs_lxr nxt_lxr; + public Bry_bfr Tmp_bfr() {return tmp_bfr;} private Bry_bfr tmp_bfr = Bry_bfr_.New(); + public void Process_eos() {} + public void Process_lxr(int nxt_pos, Gfs_lxr nxt_lxr) {this.nxt_pos = nxt_pos; this.nxt_lxr = nxt_lxr;} + public void Process_null(int cur_pos) {this.nxt_pos = cur_pos; this.nxt_lxr = null;} + public void Init(Btrie_fast_mgr trie, byte[] src, int src_len) { + this.trie = trie; this.src = src; this.src_len = src_len; + cur_nde = root; + Stack_add(); + } + public void Hold_word(int bgn, int end) { + cur_idf_bgn = bgn; + cur_idf_end = end; + } int cur_idf_bgn = -1, cur_idf_end = -1; + private void Held_word_clear() {cur_idf_bgn = -1; cur_idf_end = -1;} + public Gfs_nde Make_nde(int tkn_bgn, int tkn_end) { // "abc."; "abc("; "abc;"; "abc{" + Gfs_nde nde = new Gfs_nde().Name_rng_(cur_idf_bgn, cur_idf_end); + this.Held_word_clear(); + cur_nde.Subs_add(nde); + cur_nde = nde; + return nde; + } + public void Make_atr_by_idf() {Make_atr(cur_idf_bgn, cur_idf_end); Held_word_clear();} + public void Make_atr_by_bry(int bgn, int end, byte[] bry) {Make_atr(bgn, end).Name_(bry);} + public Gfs_nde Make_atr(int bgn, int end) { + Gfs_nde nde = new Gfs_nde().Name_rng_(bgn, end); + cur_nde.Atrs_add(nde); + return nde; + } + public void Cur_nde_from_stack() {cur_nde = (Gfs_nde)nodes.Get_at_last();} + public void Stack_add() {nodes.Add(cur_nde);} List_adp nodes = List_adp_.New(); + public void Stack_pop(int pos) { + if (nodes.Count() < 2) err_mgr.Fail_nde_stack_empty(this, pos); // NOTE: need at least 2 items; 1 to pop and 1 to set as current + List_adp_.Del_at_last(nodes); + Cur_nde_from_stack(); + } + public Gfs_err_mgr Err_mgr() {return err_mgr;} Gfs_err_mgr err_mgr = new Gfs_err_mgr(); +} +class Gfs_err_mgr { + public void Fail_eos(Gfs_parser_ctx ctx) {Fail(ctx, Fail_msg_eos, ctx.Src_len());} + public void Fail_unknown_char(Gfs_parser_ctx ctx, int pos, byte c) {Fail(ctx, Fail_msg_unknown_char, pos, Keyval_.new_("char", Char_.To_str((char)c)));} + public void Fail_nde_stack_empty(Gfs_parser_ctx ctx, int pos) {Fail(ctx, Fail_msg_nde_stack_empty, pos);} + public void Fail_invalid_lxr(Gfs_parser_ctx ctx, int pos, int lxr_tid, byte c) { + Fail(ctx, Fail_msg_invalid_lxr, pos, Keyval_.new_("char", Char_.To_str((char)c)), Keyval_.new_("cur_lxr", Gfs_lxr_.Tid__name(lxr_tid)), Keyval_.new_("prv_lxr", Gfs_lxr_.Tid__name(ctx.Prv_lxr()))); + } + private void Fail(Gfs_parser_ctx ctx, String msg, int pos, Keyval... args) { + byte[] src = ctx.Src(); int src_len = ctx.Src_len(); + Fail_args_standard(src, src_len, pos); + int len = args.length; + for (int i = 0; i < len; i++) { + Keyval arg = args[i]; + tmp_fail_args.Add(arg.Key(), arg.Val_to_str_or_empty()); + } + throw Err_.new_wo_type(Fail_msg(msg, tmp_fail_args)); + } + private void Fail_args_standard(byte[] src, int src_len, int pos) { + tmp_fail_args.Add("excerpt_bgn", Fail_excerpt_bgn(src, src_len, pos)); + tmp_fail_args.Add("excerpt_end", Fail_excerpt_end(src, src_len, pos)); + tmp_fail_args.Add("pos" , pos); + } + public static final String Fail_msg_invalid_lxr = "invalid character", Fail_msg_unknown_char = "unknown char", Fail_msg_eos = "end of stream", Fail_msg_nde_stack_empty = "node stack empty"; + String Fail_msg(String type, Keyval_list fail_args) { + tmp_fail_bfr.Add_str_u8(type).Add_byte(Byte_ascii.Colon); + int len = fail_args.Count(); + for (int i = 0; i < len; i++) { + tmp_fail_bfr.Add_byte(Byte_ascii.Space); + Keyval kv = fail_args.Get_at(i); + tmp_fail_bfr.Add_str_u8(kv.Key()); + tmp_fail_bfr.Add_byte(Byte_ascii.Eq).Add_byte(Byte_ascii.Apos); + tmp_fail_bfr.Add_str_u8(kv.Val_to_str_or_empty()).Add_byte(Byte_ascii.Apos); + } + return tmp_fail_bfr.To_str_and_clear(); + } + Bry_bfr tmp_fail_bfr = Bry_bfr_.Reset(255); + Keyval_list tmp_fail_args = new Keyval_list(); + private static int excerpt_len = 50; + String Fail_excerpt_bgn(byte[] src, int src_len, int pos) { + int bgn = pos - excerpt_len; if (bgn < 0) bgn = 0; + Fail_excerpt_rng(tmp_fail_bfr, src, bgn, pos); + return tmp_fail_bfr.To_str_and_clear(); + } + String Fail_excerpt_end(byte[] src, int src_len, int pos) { + int end = pos + excerpt_len; if (end > src_len) end = src_len; + Fail_excerpt_rng(tmp_fail_bfr, src, pos, end); + return tmp_fail_bfr.To_str_and_clear(); + } + private static void Fail_excerpt_rng(Bry_bfr bfr, byte[] src, int bgn, int end) { + for (int i = bgn; i < end; i++) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Tab: bfr.Add(Esc_tab); break; + case Byte_ascii.Nl: bfr.Add(Esc_nl); break; + case Byte_ascii.Cr: bfr.Add(Esc_cr); break; + default: bfr.Add_byte(b); break; + } + } + } + private static final byte[] Esc_nl = Bry_.new_a7("\\n"), Esc_cr = Bry_.new_a7("\\r"), Esc_tab = Bry_.new_a7("\\t"); +} diff --git a/400_xowa/src/gplx/langs/gfs/Gfs_parser_tst.java b/400_xowa/src/gplx/langs/gfs/Gfs_parser_tst.java index a27517de8..712a78551 100644 --- a/400_xowa/src/gplx/langs/gfs/Gfs_parser_tst.java +++ b/400_xowa/src/gplx/langs/gfs/Gfs_parser_tst.java @@ -13,3 +13,182 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +import org.junit.*; +public class Gfs_parser_tst { + @Before public void init() {fxt.Clear();} Gfs_parser_fxt fxt = new Gfs_parser_fxt(); + @Test public void Semicolon() { + fxt .Test_parse("a;", fxt.nde_("a")); + fxt .Test_parse("a;b;c;", fxt.nde_("a"), fxt.nde_("b"), fxt.nde_("c")); + fxt .Test_parse("a_0;", fxt.nde_("a_0")); + } + @Test public void Dot() { + fxt .Test_parse("a.b;", fxt.nde_("a").Subs_add(fxt.nde_("b"))); + fxt .Test_parse("a.b;c.d;", fxt.nde_("a").Subs_add(fxt.nde_("b")), fxt.nde_("c").Subs_add(fxt.nde_("d"))); + } + @Test public void Parens() { + fxt .Test_parse("a();b();", fxt.nde_("a"), fxt.nde_("b")); + fxt .Test_parse("a().b();c().d();", fxt.nde_("a").Subs_add(fxt.nde_("b")), fxt.nde_("c").Subs_add(fxt.nde_("d"))); + } + @Test public void Num() { + fxt .Test_parse("a(1,2);", fxt.nde_("a").Atrs_add_many(fxt.val_("1"), fxt.val_("2"))); + } + @Test public void Quote() { + fxt .Test_parse("a('b');", fxt.nde_("a").Atrs_add(fxt.val_("b"))); + } + @Test public void Quote_escaped() { + fxt .Test_parse("a('b''c''d');", fxt.nde_("a").Atrs_add(fxt.val_("b'c'd"))); + } + @Test public void Quote_escaped_2() { + fxt .Test_parse("a('a''''b');", fxt.nde_("a").Atrs_add(fxt.val_("a''b"))); + } + @Test public void Quote_mixed() { + fxt .Test_parse("a('b\"c');", fxt.nde_("a").Atrs_add(fxt.val_("b\"c"))); + } + @Test public void Comma() { + fxt .Test_parse("a('b','c','d');", fxt.nde_("a").Atrs_add_many(fxt.val_("b"), fxt.val_("c"), fxt.val_("d"))); + } + @Test public void Ws() { + fxt .Test_parse(" a ( 'b' , 'c' ) ; ", fxt.nde_("a").Atrs_add_many(fxt.val_("b"), fxt.val_("c"))); + } + @Test public void Comment_slash_slash() { + fxt .Test_parse("//z\na;//y\n", fxt.nde_("a")); + } + @Test public void Comment_slash_star() { + fxt .Test_parse("/*z*/a;/*y*/", fxt.nde_("a")); + } + @Test public void Curly() { + fxt .Test_parse("a{b;}", fxt.nde_("a").Subs_add(fxt.nde_("b"))); + } + @Test public void Curly_nest() { + fxt .Test_parse("a{b{c{d;}}}" + , fxt.nde_("a").Subs_add + ( fxt.nde_("b").Subs_add + ( fxt.nde_("c").Subs_add + ( fxt.nde_("d") + )))); + } + @Test public void Curly_nest_peers() { + fxt .Test_parse(String_.Concat_lines_nl + ( "a{" + , " a0{" + , " a00{" + , " a000;" + , " }" + , " a01;" + , " }" + , " a1;" + , "}" + ) + , fxt.nde_("a").Subs_add_many + ( fxt.nde_("a0").Subs_add_many + ( fxt.nde_("a00").Subs_add + ( fxt.nde_("a000") + ) + , fxt.nde_("a01") + ) + , fxt.nde_("a1") + )); + } + @Test public void Curly_dot() { + fxt .Test_parse("a{a0.a00;a1.a10;}" + , fxt.nde_("a").Subs_add_many + ( fxt.nde_("a0").Subs_add_many(fxt.nde_("a00")) + , fxt.nde_("a1").Subs_add_many(fxt.nde_("a10")) + )); + } + @Test public void Eq() { + fxt .Test_parse("a='b';", fxt.nde_("a").Atrs_add(fxt.val_("b"))); + fxt .Test_parse("a.b.c='d';" + , fxt.nde_("a").Subs_add + ( fxt.nde_("b").Subs_add_many + ( fxt.nde_("c").Atrs_add(fxt.val_("d")) + ))); + fxt .Test_parse("a.b{c='d'; e='f'}" + , fxt.nde_("a").Subs_add + ( fxt.nde_("b").Subs_add_many + ( fxt.nde_("c").Atrs_add(fxt.val_("d")) + , fxt.nde_("e").Atrs_add(fxt.val_("f")) + ))); + } + @Test public void Curly_nest_peers2() { + fxt .Test_parse(String_.Concat_lines_nl + ( "a() {" + , " k1 = 'v1';" + , "}" + ) + , fxt.nde_("a").Subs_add_many + ( fxt.nde_("k1").Atrs_add(fxt.val_("v1")) + ) + ); + } + @Test public void Fail() { + fxt .Test_parse_fail("a(.);", Gfs_err_mgr.Fail_msg_invalid_lxr); // (.) + fxt .Test_parse_fail("a..b;", Gfs_err_mgr.Fail_msg_invalid_lxr); // .. + fxt .Test_parse_fail("a.;", Gfs_err_mgr.Fail_msg_invalid_lxr); // .; + fxt .Test_parse_fail("a", Gfs_err_mgr.Fail_msg_eos); // eos + fxt .Test_parse_fail("a;~;", Gfs_err_mgr.Fail_msg_unknown_char); // ~ + } +} +class Gfs_parser_fxt { + public void Clear() {} + public Gfs_nde nde_(String v) {return new Gfs_nde().Name_(Bry_.new_a7(v));} + public Gfs_nde val_(String v) {return new Gfs_nde().Name_(Bry_.new_a7(v));} + public void Test_parse(String src_str, Gfs_nde... expd) { + byte[] src_bry = Bry_.new_u8(src_str); + Gfs_nde root = parser.Parse(src_bry); + Tfds.Eq_str_lines(To_str(null, expd), To_str(src_bry, root.Subs_to_ary())); + } private Bry_bfr tmp_bfr = Bry_bfr_.New(), path_bfr = Bry_bfr_.New(); Gfs_parser parser = new Gfs_parser(); + public void Test_parse_fail(String src_str, String expd_err) { + byte[] src_bry = Bry_.new_u8(src_str); + try {parser.Parse(src_bry);} + catch (Exception e) { + String actl_err = Err_.Message_gplx_full(e); + actl_err = String_.GetStrBefore(actl_err, ":"); + boolean match = String_.Has(actl_err, expd_err); + if (!match) Tfds.Fail("expecting '" + expd_err + "' got '" + actl_err + "'"); + return; + } + Tfds.Fail("expected to fail with " + expd_err); + } + public String To_str(byte[] src, Gfs_nde[] expd) { + int subs_len = expd.length; + for (int i = 0; i < subs_len; i++) { + path_bfr.Clear().Add_int_variable(i); + To_str(tmp_bfr, path_bfr, src, expd[i]); + } + return tmp_bfr.To_str_and_clear(); + } + public void To_str(Bry_bfr bfr, Bry_bfr path, byte[] src, Gfs_nde nde) { + To_str_atr(bfr, path, src, Atr_name, nde.Name(), nde.Name_bgn(), nde.Name_end()); + int atrs_len = nde.Atrs_len(); + for (int i = 0; i < atrs_len; i++) { + Gfs_nde atr = nde.Atrs_get_at(i); + int path_len_old = path.Len(); + path.Add_byte(Byte_ascii.Dot).Add_byte((byte)(Byte_ascii.Ltr_a + i)); + int path_len_new = path.Len(); + To_str(bfr, path, src, atr); + path.Del_by(path_len_new - path_len_old); + } + int subs_len = nde.Subs_len(); + for (int i = 0; i < subs_len; i++) { + Gfs_nde sub = nde.Subs_get_at(i); + int path_len_old = path.Len(); + path.Add_byte(Byte_ascii.Dot).Add_int_variable(i); + int path_len_new = path.Len(); + To_str(bfr, path, src, sub); + path.Del_by(path_len_new - path_len_old); + } + } + private void To_str_atr(Bry_bfr bfr, Bry_bfr path_bfr, byte[] src, byte[] name, byte[] val, int val_bgn, int val_end) { + if (val == null && val_bgn == -1 && val_end == -1) return; + bfr.Add_bfr_and_preserve(path_bfr).Add_byte(Byte_ascii.Colon); + bfr.Add(name); + if (val == null) + bfr.Add_mid(src, val_bgn, val_end); + else + bfr.Add(val); + bfr.Add_byte_nl(); + } + private static final byte[] Atr_name = Bry_.new_a7("name="); +} diff --git a/400_xowa/src/gplx/langs/gfs/Gfs_wtr.java b/400_xowa/src/gplx/langs/gfs/Gfs_wtr.java index a27517de8..9a0e94763 100644 --- a/400_xowa/src/gplx/langs/gfs/Gfs_wtr.java +++ b/400_xowa/src/gplx/langs/gfs/Gfs_wtr.java @@ -13,3 +13,32 @@ 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.langs.gfs; import gplx.*; import gplx.langs.*; +public class Gfs_wtr { + public byte Quote_char() {return quote_char;} public Gfs_wtr Quote_char_(byte v) {quote_char = v; return this;} private byte quote_char = Byte_ascii.Apos; + public Bry_bfr Bfr() {return bfr;} private Bry_bfr bfr = Bry_bfr_.Reset(255); + public void Add_grp_bgn(byte[] key) { + bfr.Add(key); // key + bfr.Add_byte(Byte_ascii.Curly_bgn); // { + } + public void Add_grp_end(byte[] key) { + bfr.Add_byte(Byte_ascii.Curly_end); // } + } + public void Add_set_eq(byte[] key, byte[] val) { + bfr.Add(key); // key + bfr.Add_byte_eq(); // = + bfr.Add_byte(quote_char); // ' + Write_val(val); + bfr.Add_byte(quote_char); // ' + bfr.Add_byte(Byte_ascii.Semic); // ; + } + private void Write_val(byte[] bry) { + int bry_len = bry.length; + for (int i = 0; i < bry_len; i++) { + byte b = bry[i]; + if (b == quote_char) // byte is quote + bfr.Add_byte(b); // double up + bfr.Add_byte(b); + } + } +} diff --git a/400_xowa/src/gplx/langs/htmls/Gfh_atr_.java b/400_xowa/src/gplx/langs/htmls/Gfh_atr_.java index a27517de8..881549b85 100644 --- a/400_xowa/src/gplx/langs/htmls/Gfh_atr_.java +++ b/400_xowa/src/gplx/langs/htmls/Gfh_atr_.java @@ -13,3 +13,62 @@ 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.langs.htmls; import gplx.*; import gplx.langs.*; +public class Gfh_atr_ { + public static final byte[] + // "coreattrs" + Bry__id = Bry_.new_a7("id") + , Bry__class = Bry_.new_a7("class") + , Bry__style = Bry_.new_a7("style") + , Bry__title = Bry_.new_a7("title") + // "i18n" + , Bry__lang = Bry_.new_a7("lang") + , Bry__dir = Bry_.new_a7("dir") + // + , Bry__href = Bry_.new_a7("href") + , Bry__rel = Bry_.new_a7("rel") + , Bry__target = Bry_.new_a7("target") + // + , Bry__alt = Bry_.new_a7("alt") + , Bry__src = Bry_.new_a7("src") + , Bry__width = Bry_.new_a7("width") + , Bry__height = Bry_.new_a7("height") + // + //, Bry__width = Bry_.new_a7("width") + , Bry__cellpadding = Bry_.new_a7("cellpadding") + , Bry__cellspacing = Bry_.new_a7("cellspacing") + , Bry__summary = Bry_.new_a7("summary") // HTML.ua + //
.borders_and_rules + , Bry__border = Bry_.new_a7("border") + , Bry__frames = Bry_.new_a7("frames") + , Bry__rules = Bry_.new_a7("rules") + // "); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/langs/Xoctg_collation_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/langs/Xoctg_collation_mgr.java index a27517de8..abbcf3a45 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/langs/Xoctg_collation_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/langs/Xoctg_collation_mgr.java @@ -13,3 +13,19 @@ 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.addons.wikis.ctgs.htmls.catpages.langs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +import gplx.core.intls.ucas.*; +public class Xoctg_collation_mgr { + private final Xow_wiki wiki; + private Xoctg_collation_wkr wkr; + public Xoctg_collation_mgr(Xow_wiki wiki) { + this.wiki = wiki; + this.wkr = new Xoctg_collation_wkr__uppercase(wiki); // REF:https://noc.wikimedia.org/conf/InitialiseSettings.php.txt|wgCategoryCollation|default + } + public void Collation_name_(String v) { + this.wkr = Xoctg_collation_wkr_.Make(wiki, v); + } + public byte[] Get_sortkey(byte[] src) { + return wkr.Get_sortkey(src); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/langs/Xoctg_collation_wkr.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/langs/Xoctg_collation_wkr.java index a27517de8..15c53db6d 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/langs/Xoctg_collation_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/langs/Xoctg_collation_wkr.java @@ -13,3 +13,53 @@ 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.addons.wikis.ctgs.htmls.catpages.langs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +public interface Xoctg_collation_wkr { + String Type_name(); + String Wm_name(); + byte[] Get_sortkey(byte[] src); +} +class Xoctg_collation_wkr__uppercase implements Xoctg_collation_wkr { + private final Xow_wiki wiki; + public Xoctg_collation_wkr__uppercase(Xow_wiki wiki) {this.wiki = wiki;} + public String Type_name() {return "uppercase";} + public String Wm_name() {return this.Type_name();} + public byte[] Get_sortkey(byte[] src) { + return wiki.Lang().Case_mgr().Case_build_upper(src); + } +} +class Xoctg_collation_wkr__identity implements Xoctg_collation_wkr { + public String Type_name() {return "identity";} + public String Wm_name() {return this.Type_name();} + public byte[] Get_sortkey(byte[] src) { + return src; + } +} +class Xoctg_collation_wkr__uca implements Xoctg_collation_wkr { + private gplx.core.intls.ucas.Uca_collator collator; + public Xoctg_collation_wkr__uca(String wm_name, String icu_locale) { + // REF:"includes/collation/Collation.php|factory" "includes/collation/IcuCollation.php|__construct" + this.wm_name = wm_name; + // remove anything after "@"; EX: 'svwikisource' => 'uca-sv@collation=standard', // T48058 + int at_pos = String_.FindFwd(icu_locale, "@"); + if (at_pos != String_.Find_none) + icu_locale = String_.Mid(icu_locale, 0, at_pos); + + // handle "default-u-kn" + if (String_.Eq(icu_locale, "default-u-kn")) + this.icu_locale = "en"; + else if (String_.Eq(icu_locale, "root")) + this.icu_locale = "en"; + else + this.icu_locale = icu_locale; + this.numeric_sorting = String_.Has_at_end(icu_locale, "-u-kn"); + } + public String Type_name() {return wm_name;} private final String wm_name; + public String Wm_name() {return this.Type_name();} + public String Icu_locale() {return icu_locale;} private final String icu_locale; + public boolean Numeric_sorting() {return numeric_sorting;} private final boolean numeric_sorting; + public byte[] Get_sortkey(byte[] src) { + if (collator == null) collator = gplx.core.intls.ucas.Uca_collator_.New(icu_locale, numeric_sorting); + return collator.Get_sortkey(String_.new_u8(src)); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/langs/Xoctg_collation_wkr_.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/langs/Xoctg_collation_wkr_.java index a27517de8..91d777456 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/langs/Xoctg_collation_wkr_.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/langs/Xoctg_collation_wkr_.java @@ -13,3 +13,22 @@ 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.addons.wikis.ctgs.htmls.catpages.langs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +class Xoctg_collation_wkr_ { + public static Xoctg_collation_wkr Make(Xow_wiki wiki, String wm_name) { + // REF:"includes/collation/Collation.php|factory" + if (String_.Eq(wm_name, "uppercase")) return new Xoctg_collation_wkr__uppercase(wiki); + else if (String_.Eq(wm_name, "identity")) return new Xoctg_collation_wkr__identity(); + else if (String_.Eq(wm_name, "uca-default")) return new Xoctg_collation_wkr__uca(wm_name, "root"); + else if (String_.Eq(wm_name, "xx-uca-ckb")) return new Xoctg_collation_wkr__uca(wm_name, "fa"); // FUTURE:should create custom collation class to do extra logic + else if (String_.Eq(wm_name, "xx-uca-et")) return new Xoctg_collation_wkr__uca(wm_name, "et"); // FUTURE:should create custom collation class to do extra logic + else { + if (String_.Has_at_bgn(wm_name, "uca-")) + return new Xoctg_collation_wkr__uca(wm_name, String_.Replace(wm_name, "uca-", "")); + else { // unknown collation code; log and exit + Gfo_usr_dlg_.Instance.Warn_many("", "", "unknown collation; collation=~{0}", wm_name); + return new Xoctg_collation_wkr__uppercase(wiki); + } + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/langs/Xoctg_collation_wkr___tst.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/langs/Xoctg_collation_wkr___tst.java index a27517de8..d788cfe78 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/langs/Xoctg_collation_wkr___tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/langs/Xoctg_collation_wkr___tst.java @@ -13,3 +13,27 @@ 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.addons.wikis.ctgs.htmls.catpages.langs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +import org.junit.*; import gplx.core.tests.*; +public class Xoctg_collation_wkr___tst { + private final Xoctg_collation_wkr___fxt fxt = new Xoctg_collation_wkr___fxt(); + @Test public void Uppercase() {fxt.Test__make("uppercase" , "uppercase");} + @Test public void Identity() {fxt.Test__make("identity" , "identity");} + @Test public void Unknown() {fxt.Test__make("unknown" , "uppercase");} + @Test public void Uca__uca_default() {fxt.Test__make__uca("uca-default" , "en", false);} + @Test public void Uca__xx_uca_ckb() {fxt.Test__make__uca("xx-uca-ckb" , "fa", false);} + @Test public void Uca__xx_uca_et() {fxt.Test__make__uca("xx-uca-et" , "et", false);} + @Test public void Uca__uca_default_u_kn() {fxt.Test__make__uca("uca-default-u-kn" , "en", true);} + @Test public void Uca__uca_at_logic() {fxt.Test__make__uca("uca-sv@collation=standard" , "sv", false);} +} +class Xoctg_collation_wkr___fxt { + public void Test__make(String wm_name, String expd_type) { + Xoctg_collation_wkr actl = Xoctg_collation_wkr_.Make(null, wm_name); + Gftest.Eq__str(expd_type, actl.Type_name()); + } + public void Test__make__uca(String wm_name, String expd_locale, boolean expd_numeric_sorting) { + Xoctg_collation_wkr__uca actl = (Xoctg_collation_wkr__uca)Xoctg_collation_wkr_.Make(null, wm_name); + Gftest.Eq__str(expd_locale, actl.Icu_locale()); + Gftest.Eq__bool(expd_numeric_sorting, actl.Numeric_sorting()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/urls/Xoctg_catpage_url.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/urls/Xoctg_catpage_url.java index a27517de8..76ef7bb99 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/urls/Xoctg_catpage_url.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/urls/Xoctg_catpage_url.java @@ -13,3 +13,21 @@ 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.addons.wikis.ctgs.htmls.catpages.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +public class Xoctg_catpage_url { + public Xoctg_catpage_url(byte[][] keys, boolean[] fwds) {this.keys = keys; this.fwds = fwds;} + public byte[][] Grp_keys() {return keys;} private final byte[][] keys; + public boolean[] Grp_fwds() {return fwds;} private final boolean[] fwds; + + public static Xoctg_catpage_url New__blank() { + byte[][] keys = new byte[Xoa_ctg_mgr.Tid___max][]; + boolean[] fwds = new boolean[Xoa_ctg_mgr.Tid___max]; + + // for blank url, all fwds are true; EX: "Category:A" -> keys {"", "", ""}, fwds {true, true, true} + for (int i = 0; i < Xoa_ctg_mgr.Tid___max; ++i) { + fwds[i] = Bool_.Y; + keys[i] = Bry_.Empty; + } + return new Xoctg_catpage_url(keys, fwds); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/urls/Xoctg_catpage_url__tst.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/urls/Xoctg_catpage_url__tst.java index a27517de8..1c5ec4d5d 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/urls/Xoctg_catpage_url__tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/urls/Xoctg_catpage_url__tst.java @@ -13,3 +13,33 @@ 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.addons.wikis.ctgs.htmls.catpages.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +import org.junit.*; import gplx.core.tests.*; import gplx.xowa.apps.urls.*; +public class Xoctg_catpage_url__tst { + @Before public void init() {fxt.Clear();} private Xoctg_catpage_url__fxt fxt = new Xoctg_catpage_url__fxt(); + @Test public void Specific() { + fxt.Exec__parse("A?subcatfrom=B&filefrom=C&pagefrom=D" ).Test__keys("B", "C", "D").Test__fwds(Bool_.Y, Bool_.Y, Bool_.Y); + fxt.Exec__parse("A?subcatuntil=B&fileuntil=C&pageuntil=D" ).Test__keys("B", "C", "D").Test__fwds(Bool_.N, Bool_.N, Bool_.N); + } + @Test public void General() { + fxt.Exec__parse("A?from=B" ).Test__keys("B", "B", "B").Test__fwds(Bool_.Y, Bool_.Y, Bool_.Y); + fxt.Exec__parse("A?until=B" ).Test__keys("B", "B", "B").Test__fwds(Bool_.N, Bool_.N, Bool_.N); + } + @Test public void Url_encoded() { + fxt.Exec__parse("A?from=B+C").Test__keys("B C", "B C", "B C").Test__fwds(Bool_.Y, Bool_.Y, Bool_.Y); + } +} +class Xoctg_catpage_url__fxt { + private Xow_url_parser xo_url_parser; private Xoctg_catpage_url ctg_url; + public void Clear() { + Xoa_app app = Xoa_app_fxt.Make__app__edit(); + this.xo_url_parser = app.User().Wikii().Utl__url_parser(); + } + public Xoctg_catpage_url__fxt Exec__parse(String url_str) { + Xoa_url page_url = xo_url_parser.Parse(Bry_.new_u8(url_str)); + this.ctg_url = Xoctg_catpage_url_parser.Parse(page_url); + return this; + } + public Xoctg_catpage_url__fxt Test__keys(String... expd) {Gftest.Eq__ary(Bry_.Ary(expd), ctg_url.Grp_keys(), "keys"); return this;} + public Xoctg_catpage_url__fxt Test__fwds(boolean... expd) {Gftest.Eq__ary(expd, ctg_url.Grp_fwds(), "fwds"); return this;} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/urls/Xoctg_catpage_url_parser.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/urls/Xoctg_catpage_url_parser.java index a27517de8..112ac389d 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/urls/Xoctg_catpage_url_parser.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/urls/Xoctg_catpage_url_parser.java @@ -13,3 +13,81 @@ 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.addons.wikis.ctgs.htmls.catpages.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +import gplx.core.net.*; import gplx.core.net.qargs.*; +import gplx.langs.htmls.encoders.*; +public class Xoctg_catpage_url_parser { + public static Xoctg_catpage_url Parse(Xoa_url url) { + Gfo_qarg_itm[] args = url.Qargs_ary(); + if (args == null) return Xoctg_catpage_url.New__blank(); + int len = args.length; + if (len == 0) return Xoctg_catpage_url.New__blank(); + + // init caturl structs + byte[][] keys = new byte[Xoa_ctg_mgr.Tid___max][]; + boolean[] fwds = new boolean[Xoa_ctg_mgr.Tid___max]; + for (int i = 0; i < Xoa_ctg_mgr.Tid___max; ++i) { + fwds[i] = Bool_.Y; + keys[i] = Bry_.Empty; + } + Bry_bfr tmp_bfr = Bry_bfr_.New(); + + // loop qargs; EX: "?subcatfrom=B&filefrom=C&pagefrom=D" + for (int i = 0; i < len; ++i) { + Gfo_qarg_itm arg = args[i]; + + // get tid from arg; EX: "pagefrom" -> Tid__page_bgn + byte[] key = arg.Key_bry(); + byte tid = Key_hash.Get_as_byte_or(key, Byte_ascii.Max_7_bit); + if (tid == Byte_ascii.Max_7_bit) { // if invalid, warn and skip + Gfo_usr_dlg_.Instance.Warn_many("", "", "catpage:unknown url arg: raw~={0} key=~{1}", url.To_bry(true, true), key); + continue; + } + + // set val + byte[] val = arg.Val_bry(); + Gfo_url_encoder_.Http_url.Decode(tmp_bfr, Bool_.N, val, 0, val.length); + val = tmp_bfr.To_bry_and_clear(); + + // set struct + switch (tid) { + case Tid__each_bgn: Set_grp(keys, fwds, val, Bool_.Y, Xoa_ctg_mgr.Tid__subc, Xoa_ctg_mgr.Tid__file, Xoa_ctg_mgr.Tid__page); break; // if "from", default all grps to val; DATE:2014-02-05 + case Tid__each_end: Set_grp(keys, fwds, val, Bool_.N, Xoa_ctg_mgr.Tid__subc, Xoa_ctg_mgr.Tid__file, Xoa_ctg_mgr.Tid__page); break; + case Tid__subc_bgn: Set_grp(keys, fwds, val, Bool_.Y, Xoa_ctg_mgr.Tid__subc); break; + case Tid__subc_end: Set_grp(keys, fwds, val, Bool_.N, Xoa_ctg_mgr.Tid__subc); break; + case Tid__file_bgn: Set_grp(keys, fwds, val, Bool_.Y, Xoa_ctg_mgr.Tid__file); break; + case Tid__file_end: Set_grp(keys, fwds, val, Bool_.N, Xoa_ctg_mgr.Tid__file); break; + case Tid__page_bgn: Set_grp(keys, fwds, val, Bool_.Y, Xoa_ctg_mgr.Tid__page); break; + case Tid__page_end: Set_grp(keys, fwds, val, Bool_.N, Xoa_ctg_mgr.Tid__page); break; + } + } + return new Xoctg_catpage_url(keys, fwds); + } + private static void Set_grp(byte[][] keys, boolean[] fwds, byte[] key, boolean fwd, byte... tids) { + int len = tids.length; + for (int i = 0; i < len; ++i) { + byte tid = tids[i]; + keys[tid] = key; + fwds[tid] = fwd; + } + } + + private static final byte + Tid__each_bgn = 0, Tid__each_end = 1 + , Tid__subc_bgn = 2, Tid__subc_end = 3 + , Tid__file_bgn = 4, Tid__file_end = 5 + , Tid__page_bgn = 6, Tid__page_end = 7 + ; + public static final byte[] + Bry__arg_each_bgn = Bry_.new_a7("from") , Bry__arg_each_end = Bry_.new_a7("until") + , Bry__arg_subc_bgn = Bry_.new_a7("subcatfrom") , Bry__arg_subc_end = Bry_.new_a7("subcatuntil") + , Bry__arg_page_bgn = Bry_.new_a7("pagefrom") , Bry__arg_page_end = Bry_.new_a7("pageuntil") + , Bry__arg_file_bgn = Bry_.new_a7("filefrom") , Bry__arg_file_end = Bry_.new_a7("fileuntil") + ; + private static final Hash_adp_bry Key_hash = Hash_adp_bry.ci_a7() + .Add_bry_byte(Bry__arg_each_bgn , Tid__each_bgn) .Add_bry_byte(Bry__arg_each_end , Tid__each_end) + .Add_bry_byte(Bry__arg_subc_bgn , Tid__subc_bgn) .Add_bry_byte(Bry__arg_subc_end , Tid__subc_end) + .Add_bry_byte(Bry__arg_page_bgn , Tid__page_bgn) .Add_bry_byte(Bry__arg_page_end , Tid__page_end) + .Add_bry_byte(Bry__arg_file_bgn , Tid__file_bgn) .Add_bry_byte(Bry__arg_file_end , Tid__file_end) + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/Xoctg_pagebox_hash.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/Xoctg_pagebox_hash.java index a27517de8..f01ab3a54 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/Xoctg_pagebox_hash.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/Xoctg_pagebox_hash.java @@ -13,3 +13,42 @@ 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.addons.wikis.ctgs.htmls.pageboxs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; +import gplx.core.lists.hashs.*; +class Xoctg_pagebox_hash { + private final Ordered_hash hash_by_ttl = Ordered_hash_.New_bry(); + private final Hash_adp__int hash_by_id = new Hash_adp__int(); + public int Len() {return hash_by_ttl.Len();} + public Xoctg_pagebox_itm Get_at(int i) {return (Xoctg_pagebox_itm)hash_by_ttl.Get_at(i);} + public Xoctg_pagebox_itm Get_by_ttl(byte[] full_db) {return (Xoctg_pagebox_itm)hash_by_ttl.Get_by(full_db);} + public Xoctg_pagebox_itm Get_by_id(int page_id) {return (Xoctg_pagebox_itm)hash_by_id.Get_by_or_fail(page_id);} + public Xoctg_pagebox_itm[] To_ary_and_clear() { + hash_by_id.Clear(); + return (Xoctg_pagebox_itm[])hash_by_ttl.To_ary_and_clear(Xoctg_pagebox_itm.class); + } + public Xoctg_pagebox_itm Add_by_ttl(Xoa_ttl ttl) { + Xoctg_pagebox_itm rv = Xoctg_pagebox_itm.New_by_ttl(ttl); + hash_by_ttl.Add(ttl.Full_db(), rv); + return rv; + } + public void Sort_and_fill_ids() { + // sort + hash_by_ttl.Sort_by(Xoctg_pagebox_hash_sorter.Sorter); + + // fill ids_hash + hash_by_id.Clear(); + int len = hash_by_ttl.Len(); + for (int i = 0; i < len; ++i) { + Xoctg_pagebox_itm itm = (Xoctg_pagebox_itm)hash_by_ttl.Get_at(i); + hash_by_id.Add(itm.Id(), itm); + } + } +} +class Xoctg_pagebox_hash_sorter implements gplx.core.lists.ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + Xoctg_pagebox_itm lhs = (Xoctg_pagebox_itm)lhsObj; + Xoctg_pagebox_itm rhs = (Xoctg_pagebox_itm)rhsObj; + return -Int_.Compare(lhs.Count__all(), rhs.Count__all()); + } + public static final Xoctg_pagebox_hash_sorter Sorter = new Xoctg_pagebox_hash_sorter(); Xoctg_pagebox_hash_sorter() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/Xoctg_pagebox_itm.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/Xoctg_pagebox_itm.java index a27517de8..18a88b229 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/Xoctg_pagebox_itm.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/Xoctg_pagebox_itm.java @@ -13,3 +13,36 @@ 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.addons.wikis.ctgs.htmls.pageboxs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; +import gplx.xowa.wikis.pages.wtxts.*; +public class Xoctg_pagebox_itm { + public Xoctg_pagebox_itm(Xoa_ttl ttl) { + this.ttl = ttl; + } + public Xoa_ttl Ttl() {return ttl;} private final Xoa_ttl ttl; + public int Id() {return id;} private int id; + public DateAdp Timestamp() {return timestamp;} private DateAdp timestamp; + public boolean Hidden() {return hidden;} private boolean hidden; + public int Count__subcs() {return count__subcs;} private int count__subcs; + public int Count__pages() {return count__pages;} private int count__pages; + public int Count__files() {return count__files;} private int count__files; + public int Count__all() {return count__all;} private int count__all; + + public void Load_by_db(int id, DateAdp timestamp) { + this.id = id; this.timestamp = timestamp; + } + public void Load_by_cat_core(boolean hidden, int count__subcs, int count__pages, int count__files) { + this.hidden = hidden; + this.count__subcs = count__subcs; this.count__pages = count__pages; this.count__files = count__files; + this.count__all = count__subcs + count__pages + count__files; + } + + public static Xoctg_pagebox_itm New_by_ttl(Xoa_ttl ttl) {return new Xoctg_pagebox_itm(ttl);} + public static Xoctg_pagebox_itm[] New_ary(Xoa_page pg) { + int len = pg.Wtxt().Ctgs__len(); + Xoctg_pagebox_itm[] rv = new Xoctg_pagebox_itm[len]; + for (int i = 0; i < len; ++i) + rv[i] = New_by_ttl(pg.Wtxt().Ctgs__get_at(i)); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/Xoctg_pagebox_loader.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/Xoctg_pagebox_loader.java index a27517de8..025742f06 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/Xoctg_pagebox_loader.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/Xoctg_pagebox_loader.java @@ -13,3 +13,63 @@ 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.addons.wikis.ctgs.htmls.pageboxs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; +import gplx.dbs.*; import gplx.xowa.addons.wikis.ctgs.dbs.*; import gplx.xowa.wikis.data.tbls.*; +class Xoctg_pagebox_loader implements Select_in_cbk { + private final Xoctg_pagebox_hash hash; private final byte[] page_url; + public Xoctg_pagebox_loader(Xoctg_pagebox_hash hash, byte[] page_url) { + this.hash = hash; this.page_url = page_url; + } + public int Hash_max() {return hash.Len();} + public void Write_sql(Bry_bfr bfr, int idx) { + Xoctg_pagebox_itm page = (Xoctg_pagebox_itm)hash.Get_at(idx); + bfr.Add_int_variable(page.Id()); + } + public void Read_data(Db_rdr rdr) { + int cat_id = rdr.Read_int("cat_id"); + Xoctg_pagebox_itm page = (Xoctg_pagebox_itm)hash.Get_by_id(cat_id); + if (page == null) {// unlikely, but possible for itms to exist in cat_links, but not in cat_core; log and return; + Gfo_usr_dlg_.Instance.Warn_many("", "", "cat_id in cat_link but not in cat_core; page=~{0} cat_id=~{1}", page_url, cat_id); + } + page.Load_by_cat_core(rdr.Read_bool_by_byte("cat_hidden"), rdr.Read_int("cat_pages"), rdr.Read_int("cat_subcats"), rdr.Read_int("cat_files")); + } + public void Select_catlinks_by_page(Xow_wiki wiki, Db_conn cat_link_conn, Xoctg_pagebox_hash hash, int page_id) { + // init + Db_attach_mgr attach_mgr = new Db_attach_mgr(cat_link_conn, new Db_attach_itm("page_db", wiki.Data__core_mgr().Db__core().Conn())); + String sql = String_.Concat_lines_nl_skip_last // ANSI.Y + ( "SELECT cl.cl_to_id" + , ", cl.cl_timestamp_unix" + , ", p.page_namespace" + , ", p.page_title" + , "FROM cat_link cl" + , " JOIN page p ON cl.cl_to_id = p.page_id" + , "WHERE cl.cl_from = " + Int_.To_str(page_id) + ); + sql = attach_mgr.Resolve_sql(sql); + + // select + attach_mgr.Attach(); + Db_rdr rdr = Db_rdr_.Empty; + try { + rdr = cat_link_conn.Stmt_sql(sql).Exec_select__rls_auto(); + while (rdr.Move_next()) { + Xoa_ttl ttl = wiki.Ttl_parse(rdr.Read_int("page_namespace"), rdr.Read_bry_by_str("page_title")); + // check if ttl exists already in hash; add it if not; check is not needed now b/c html_dbs will never put itms in hash, but may need in future if merging "wtxt" and "ctgs_dbs" + Xoctg_pagebox_itm itm = (Xoctg_pagebox_itm)hash.Get_by_ttl(ttl.Full_db()); + if (itm == null) + itm = hash.Add_by_ttl(ttl); + itm.Load_by_db(rdr.Read_int("cl_to_id"), DateAdp_.unixtime_utc_ms_(rdr.Read_long("cl_timestamp_unix"))); + } + } + catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "category.pagebox: fatal error while retrieving categories; page=~{0} err=~{1}", page_id, Err_.Message_gplx_log(e)); + } + finally { + rdr.Rls(); + attach_mgr.Detach(); + } + + // hash items by id + hash.Sort_and_fill_ids(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/Xoctg_pagebox_wtr.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/Xoctg_pagebox_wtr.java index a27517de8..c08c2e3e4 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/Xoctg_pagebox_wtr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/Xoctg_pagebox_wtr.java @@ -13,3 +13,60 @@ 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.addons.wikis.ctgs.htmls.pageboxs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; +import gplx.xowa.htmls.core.htmls.*; +import gplx.xowa.addons.wikis.ctgs.htmls.pageboxs.singles.*; import gplx.xowa.addons.wikis.ctgs.htmls.pageboxs.doubles.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.addons.wikis.ctgs.dbs.*; +public class Xoctg_pagebox_wtr implements Gfo_invk { + private final Xoctg_single_box single_box = new Xoctg_single_box(); + private final Xoctg_double_box double_box = new Xoctg_double_box(); + private final Xoctg_pagebox_hash hash = new Xoctg_pagebox_hash(); + private final Xowd_page_itm tmp_page_itm = new Xowd_page_itm(); + public boolean Grouping_enabled() {return grouping_enabled;} private boolean grouping_enabled; + public void Init_by_wiki(Xow_wiki wiki) { + single_box.Init_by_wiki(wiki); + double_box.Init_by_wiki(wiki); + wiki.App().Cfg().Bind_many_wiki(this, wiki, Cfg__grouping_enabled); + } + public void Write_pagebox(Bry_bfr bfr, Xow_wiki wiki, Xoa_page page, Xoctg_pagebox_itm[] pagebox_itms) { + try { + if (grouping_enabled) + double_box.Write_pagebox(bfr, pagebox_itms); + else + single_box.Write_pagebox(bfr, pagebox_itms); + } catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to write pagebox categories; page=~{0} err=~{1}", page.Url_bry_safe(), Err_.Message_gplx_log(e)); + } + } + public Xoctg_pagebox_itm[] Get_catlinks_by_page(Xow_wiki wiki, Xoa_page page) { + // NOTE: db load is necessary b/c page.Wtxt().Ctgs__len() is insufficient + // html_dbs will always be 0 since they do not parse ctgs; + // wtxt_dbs may have page.Wtxt().Ctgs__len() > 0 but these items don't have Id / Hidden + Xoctg_pagebox_loader select_cbk = new Xoctg_pagebox_loader(hash, page.Url_bry_safe()); + + // get cat_db_id from page + tmp_page_itm.Clear(); + boolean exists = wiki.Data__core_mgr().Tbl__page().Select_by_ttl(tmp_page_itm, page.Ttl().Ns(), page.Ttl().Page_db()); + int cat_db_id = tmp_page_itm.Cat_db_id(); + if (exists && cat_db_id != -1) {// note that wtxt_dbs can have 0 ctgs but will have cat_db_id == -1 + Xow_db_file cat_link_db = wiki.Data__core_mgr().Dbs__get_by_id_or_null(cat_db_id); + if (cat_link_db != null && Io_mgr.Instance.ExistsFil(cat_link_db.Url())) { // make sure cat_db_id exists + select_cbk.Select_catlinks_by_page(wiki, cat_link_db.Conn(), hash, tmp_page_itm.Id()); + } + } + + // get hidden + int hash_len = hash.Len(); + if (hash_len > 0) { // note that html_dbs may have 0 ctgs in cat_db; don't bother checking cat_core + Xowd_cat_core_tbl cat_core_tbl = Xodb_cat_db_.Get_cat_core_or_fail(wiki.Data__core_mgr()); + cat_core_tbl.Select_by_cat_id_many(select_cbk); + } + return hash.To_ary_and_clear(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Cfg__grouping_enabled)) this.grouping_enabled = m.ReadYn("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Cfg__grouping_enabled = "xowa.addon.category.pagebox.grouping_enabled"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/doubles/Xoctg_double_box.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/doubles/Xoctg_double_box.java index a27517de8..c1344dbb9 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/doubles/Xoctg_double_box.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/doubles/Xoctg_double_box.java @@ -13,3 +13,38 @@ 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.addons.wikis.ctgs.htmls.pageboxs.doubles; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.pageboxs.*; +import gplx.core.brys.*; import gplx.core.brys.fmts.*; import gplx.core.brys.fmtrs.*; +import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.htmls.core.htmls.*; +import gplx.xowa.users.history.*; +import gplx.xowa.addons.wikis.ctgs.htmls.*; +public class Xoctg_double_box implements Bfr_arg { + public Xoctg_double_grp Grp_normal() {return grp_normal;} private final Xoctg_double_grp grp_normal = new Xoctg_double_grp(); + public Xoctg_double_grp Grp_hidden() {return grp_hidden;} private final Xoctg_double_grp grp_hidden = new Xoctg_double_grp(); + public void Init_by_wiki(Xow_wiki wiki) { + Xou_history_mgr history_mgr = wiki.App().User().History_mgr(); + grp_normal.Init_by_wiki(wiki, history_mgr, Bool_.Y); + grp_hidden.Init_by_wiki(wiki, history_mgr, Bool_.N); + } + public void Write_pagebox(Bry_bfr bfr, Xoctg_pagebox_itm[] ary) { + grp_normal.Itms().Itms__clear(); + grp_hidden.Itms().Itms__clear(); + + int len = ary.length; + for (int i = 0; i < len; ++i) { + Xoctg_pagebox_itm itm = ary[i]; + Xoctg_double_grp list = itm.Hidden() ? grp_hidden : grp_normal; + list.Itms().Itms__add(itm); + } + this.Bfr_arg__add(bfr); + } + public void Bfr_arg__add(Bry_bfr bfr) { + Fmt__all.Bld_many(bfr, grp_normal, grp_hidden); + } + private static final Bry_fmt + Fmt__all = Bry_fmt.Auto_nl_skip_last + ( "
~{grp_normal}~{grp_hidden}" + , "
" + ); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/doubles/Xoctg_double_box__tst.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/doubles/Xoctg_double_box__tst.java index a27517de8..e7489aaa8 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/doubles/Xoctg_double_box__tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/doubles/Xoctg_double_box__tst.java @@ -13,3 +13,71 @@ 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.addons.wikis.ctgs.htmls.pageboxs.doubles; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.pageboxs.*; +import org.junit.*; +import gplx.xowa.wikis.data.tbls.*; +public class Xoctg_double_box__tst { + @Before public void init() {fxt.Clear();} private Xoctg_double_box__fxt fxt = new Xoctg_double_box__fxt(); + @Test public void Basic() { + fxt.Init_ctg_hidden("Category:A", "Category:B", "Category:C"); + fxt.Init_ctg_normal("Category:D", "Category:E", "Category:F"); + fxt.Test_print_hidden(String_.Concat_lines_nl + ( "
" + , "
" + , "Categories:" + , "
    " + , "
  • " + , "D" + , "
  • " + , "
  • " + , "E" + , "
  • " + , "
  • " + , "F" + , "
  • " + , "
" + , "
" + , "" + , "
" + )); + } +} +class Xoctg_double_box__fxt { + private Xop_fxt fxt; private Xoctg_double_box hidden_wtr; + private final List_adp init_ctgs = List_adp_.New(); + public void Clear() { + fxt = new Xop_fxt(); + hidden_wtr = new Xoctg_double_box(); + hidden_wtr.Init_by_wiki(fxt.Wiki()); + init_ctgs.Clear(); + } + public void Init_ctg_normal(String... ary) {Init_ctg(Bool_.N, ary);} + public void Init_ctg_hidden(String... ary) {Init_ctg(Bool_.Y, ary);} + public void Init_ctg(boolean hidden, String[] ary) { + int len = ary.length; + for (int i = 0; i < len; i++) { + Xoa_ttl ttl = fxt.Wiki().Ttl_parse(Bry_.new_u8(ary[i])); + Xoctg_pagebox_itm itm = Xoctg_pagebox_itm.New_by_ttl(ttl); + itm.Load_by_cat_core(hidden, 0, 0, 0); + init_ctgs.Add_many(itm); + } + } + public void Test_print_hidden(String expd) { + Bry_bfr bfr = Bry_bfr_.New(); + Xoctg_pagebox_itm[] ary = (Xoctg_pagebox_itm[])init_ctgs.To_ary_and_clear(Xoctg_pagebox_itm.class); + hidden_wtr.Write_pagebox(bfr, ary); + Tfds.Eq_str_lines(expd, bfr.To_str_and_clear()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/doubles/Xoctg_double_grp.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/doubles/Xoctg_double_grp.java index a27517de8..96f011eb7 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/doubles/Xoctg_double_grp.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/doubles/Xoctg_double_grp.java @@ -13,3 +13,44 @@ 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.addons.wikis.ctgs.htmls.pageboxs.doubles; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.pageboxs.*; +import gplx.core.brys.*; import gplx.core.brys.fmts.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; +import gplx.xowa.htmls.core.htmls.*; +import gplx.xowa.users.history.*; +public class Xoctg_double_grp implements Bfr_arg { + private byte[] lbl_ctg_help, lbl_ctg_text, lbl_hidden; + public boolean Type_is_normal() {return type_is_normal;} private boolean type_is_normal; + public Xoctg_double_itm Itms() {return itms;} private final Xoctg_double_itm itms = new Xoctg_double_itm(); + public void Init_by_wiki(Xow_wiki wiki, Xou_history_mgr history_mgr, boolean type_is_normal) { + this.type_is_normal = type_is_normal; + lbl_ctg_text = wiki.Msg_mgr().Val_by_id(Xol_msg_itm_.Id_ctg_tbl_hdr); + lbl_ctg_help = Xol_msg_mgr_.Get_msg_val(wiki, wiki.Lang(), Key_pagecategorieslink, Bry_.Ary_empty); + lbl_hidden = wiki.Msg_mgr().Val_by_id(Xol_msg_itm_.Id_ctg_tbl_hidden); + itms.Init_by_wiki(wiki, history_mgr); + } + public void Bfr_arg__add(Bry_bfr bfr) { + if (type_is_normal) + Fmt__normal.Bld_many(bfr, lbl_ctg_help, lbl_ctg_text, itms); + else + Fmt__hidden.Bld_many(bfr, lbl_hidden, itms); + } + + private static final byte[] Key_pagecategorieslink = Bry_.new_a7("pagecategorieslink"); + private static final Bry_fmt + Fmt__normal = Bry_fmt.Auto_nl_skip_last + ( "" + , "
" + , "~{ctg_text}:" + , "
    ~{grp_itms}" + , "
" + , "
" + ) + , Fmt__hidden = Bry_fmt.Auto_nl_skip_last + ( "" + , "" + ); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/doubles/Xoctg_double_itm.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/doubles/Xoctg_double_itm.java index a27517de8..d657410b5 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/doubles/Xoctg_double_itm.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/doubles/Xoctg_double_itm.java @@ -13,3 +13,46 @@ 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.addons.wikis.ctgs.htmls.pageboxs.doubles; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.pageboxs.*; +import gplx.core.brys.fmts.*; import gplx.core.brys.fmtrs.*; +import gplx.xowa.htmls.*; import gplx.xowa.htmls.hrefs.*; import gplx.xowa.htmls.core.htmls.*; import gplx.xowa.htmls.core.wkrs.lnkis.htmls.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.users.history.*; +public class Xoctg_double_itm implements gplx.core.brys.Bfr_arg { + private final List_adp itms = List_adp_.New(); + private Xow_wiki wiki; private Xoh_href_wtr href_wtr; private Xou_history_mgr history_mgr; + public void Init_by_wiki(Xow_wiki wiki, Xou_history_mgr history_mgr) { + this.wiki = wiki; + this.href_wtr = wiki.Html__href_wtr(); + this.history_mgr = history_mgr; + } + public void Itms__clear() {itms.Clear();} + public void Itms__add(Xoctg_pagebox_itm page) {itms.Add(page);} + public void Bfr_arg__add(Bry_bfr bfr) { + int len = itms.Count(); + for (int i = 0; i < len; ++i) { + Xoctg_pagebox_itm itm = (Xoctg_pagebox_itm)itms.Get_at(i); + Xoa_ttl ttl = itm.Ttl(); + byte[] lnki_cls = Xoh_lnki_wtr.Lnki_cls_visited(history_mgr, wiki.Domain_bry(), ttl.Page_txt());// NOTE: must be ttl.Page_txt() in order to match Xou_history_mgr.Add + byte[] lnki_href = href_wtr.Build_to_bry(wiki, ttl); + byte[] lnki_text = ttl.Page_txt(); + byte[] lnki_ttl = ttl.Full_txt_w_ttl_case(); + + // build title +// tmp_bfr.Add(ttl.Full_txt_w_ttl_case()); +// tmp_bfr.Add_byte_space().Add_byte(Byte_ascii.Paren_bgn) .Add_byte(Byte_ascii.Ltr_S).Add_byte_colon().Add_int_variable(itm.Count__subcs()); +// tmp_bfr.Add_byte_space().Add_byte(Byte_ascii.Comma) .Add_byte(Byte_ascii.Ltr_P).Add_byte_colon().Add_int_variable(itm.Count__pages()); +// tmp_bfr.Add_byte_space().Add_byte(Byte_ascii.Comma) .Add_byte(Byte_ascii.Ltr_F).Add_byte_colon().Add_int_variable(itm.Count__files()); +// tmp_bfr.Add_byte(Byte_ascii.Paren_end); +// byte[] lnki_ttl = tmp_bfr.To_bry_and_clear(); + + Fmt__itm.Bld_many(bfr, lnki_cls, lnki_href, lnki_ttl, lnki_text); + } + } + private static final Bry_fmt Fmt__itm = Bry_fmt.Auto_nl_skip_last + ( "" + , "
  • " + , "~{lnki_text}" + , "
  • " + ); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/singles/Xoctg_single_box.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/singles/Xoctg_single_box.java index a27517de8..349f27b14 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/singles/Xoctg_single_box.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/singles/Xoctg_single_box.java @@ -13,3 +13,28 @@ 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.addons.wikis.ctgs.htmls.pageboxs.singles; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.pageboxs.*; +import gplx.core.brys.*; import gplx.core.brys.fmts.*; import gplx.core.brys.fmtrs.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; +import gplx.xowa.htmls.core.htmls.*; +public class Xoctg_single_box { + private final Xoctg_single_itm itms_fmtr = new Xoctg_single_itm(); + private byte[] lbl_categories; + public void Init_by_wiki(Xow_wiki wiki) { + this.lbl_categories = wiki.Msg_mgr().Val_by_id(Xol_msg_itm_.Id_ctg_tbl_hdr); + itms_fmtr.Init_by_wiki(wiki); + } + public void Write_pagebox(Bry_bfr bfr, Xoctg_pagebox_itm[] itms) { + itms_fmtr.Init_by_page(itms); + Fmt__grp.Bld_many(bfr, lbl_categories, itms_fmtr); + } + private static final Bry_fmt Fmt__grp = Bry_fmt.Auto_nl_skip_last + ( "
    " + , "
    " + , "~{grp_lbl}:" + , "
      ~{grp_itms}" + , "
    " + , "
    " + , "
    " + ); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/singles/Xoctg_single_box__tst.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/singles/Xoctg_single_box__tst.java index a27517de8..847a7e55a 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/singles/Xoctg_single_box__tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/singles/Xoctg_single_box__tst.java @@ -13,3 +13,48 @@ 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.addons.wikis.ctgs.htmls.pageboxs.singles; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.pageboxs.*; +import org.junit.*; +import gplx.xowa.htmls.core.htmls.*; +public class Xoctg_single_box__tst { + @Before public void init() {fxt.Clear();} private final Xoh_ctg_mgr_fxt fxt = new Xoh_ctg_mgr_fxt(); + @Test public void Basic() { + fxt.Init__ctgs("Category:A", "Category:B").Test_html(String_.Concat_lines_nl + ( "
    " + , "
    " + , "Categories:" + , "
      " + , "
    • " + , "A" + , "
    • " + , "
    • " + , "B" + , "
    • " + , "
    " + , "
    " + , "
    " + )); + } +} +class Xoh_ctg_mgr_fxt { + private Xoctg_single_box ctg_grp_mgr; Xoae_app app; Xowe_wiki wiki; Bry_bfr tmp_bfr = Bry_bfr_.New(); + private Xoae_page page; + public void Clear() { + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + page = wiki.Parser_mgr().Ctx().Page(); + ctg_grp_mgr = new Xoctg_single_box(); + ctg_grp_mgr.Init_by_wiki(wiki); + } + public Xoh_ctg_mgr_fxt Init__ctgs(String... ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) { + page.Wtxt().Ctgs__add(wiki.Ttl_parse(Bry_.new_u8(ary[i]))); + } + return this; + } + public void Test_html(String expd) { + ctg_grp_mgr.Write_pagebox(tmp_bfr, Xoctg_pagebox_itm.New_ary(page)); + Tfds.Eq_str_lines(expd, tmp_bfr.To_str_and_clear()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/singles/Xoctg_single_itm.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/singles/Xoctg_single_itm.java index a27517de8..0406d6610 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/singles/Xoctg_single_itm.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/pageboxs/singles/Xoctg_single_itm.java @@ -13,3 +13,27 @@ 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.addons.wikis.ctgs.htmls.pageboxs.singles; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.pageboxs.*; +import gplx.core.brys.*; import gplx.core.brys.fmts.*; +class Xoctg_single_itm implements Bfr_arg { + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + private Xow_wiki wiki; + private Xoctg_pagebox_itm[] itms; + public void Init_by_wiki(Xow_wiki wiki) {this.wiki = wiki;} + public void Init_by_page(Xoctg_pagebox_itm[] itms) {this.itms = itms;} + public void Bfr_arg__add(Bry_bfr bfr) { + int len = itms.length; + for (int i = 0; i < len; ++i) { + Xoa_ttl ctg_ttl = itms[i].Ttl(); + wiki.Html__href_wtr().Build_to_bfr(tmp_bfr, wiki.App(), wiki.Domain_bry(), ctg_ttl); + byte[] ctg_page_txt = ctg_ttl.Page_txt(); + Fmt__itm.Bld_many(bfr, tmp_bfr.To_bry_and_clear(), ctg_page_txt, ctg_page_txt); + } + } + private static final Bry_fmt Fmt__itm = Bry_fmt.Auto_nl_skip_last + ( "" + , "
  • " + , "~{itm_text}" + , "
  • " + ); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/Xowdir_addon.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/Xowdir_addon.java index a27517de8..2eaa59314 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/Xowdir_addon.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/Xowdir_addon.java @@ -13,3 +13,39 @@ 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.addons.wikis.directorys; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; +import gplx.xowa.addons.wikis.directorys.dbs.*; +import gplx.xowa.addons.wikis.directorys.specials.items.*; import gplx.xowa.addons.wikis.directorys.specials.lists.*; +import gplx.xowa.htmls.bridges.*; +import gplx.dbs.*; +import gplx.xowa.specials.*; +public class Xowdir_addon implements Xoax_addon_itm, Xoax_addon_itm__special, Xoax_addon_itm__json { + public Xow_special_page[] Special_pages() { + return new Xow_special_page[] + { Xowdir_item_special.Prototype + , Xowdir_list_special.Prototype + }; + } + public Bridge_cmd_itm[] Json_cmds() { + return new Bridge_cmd_itm[] + { Xowdir_item_bridge.Prototype + , Xowdir_list_bridge.Prototype + }; + } + + public String Addon__key() {return ADDON__KEY;} private static final String ADDON__KEY = "xowa.user.wiki.regy"; + public static void Init(Xoae_app app) { + // exit if none found + Db_conn conn = app.User().User_db_mgr().Conn(); + if (!conn.Meta_tbl_exists(Xowdir_wiki_tbl.Tbl_name_dflt)) return; + + // register + Xowdir_db_mgr db_mgr = new Xowdir_db_mgr(conn); + Xowdir_wiki_itm[] itms = db_mgr.Tbl__wiki().Select_all(); + int len = itms.length; + for (int i = 0; i < len; ++i) { + Xowdir_wiki_itm itm = itms[i]; + app.User().Wikii().Xwiki_mgr().Add_by_atrs_offline(itm.Domain(), itm.Domain()); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_db_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_db_mgr.java index a27517de8..1951ff6b4 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_db_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_db_mgr.java @@ -13,3 +13,12 @@ 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.addons.wikis.directorys.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; +import gplx.dbs.*; +public class Xowdir_db_mgr { + public Xowdir_db_mgr(Db_conn conn) { + tbl__wiki = new Xowdir_wiki_tbl(conn); + conn.Meta_tbl_assert(tbl__wiki); + } + public Xowdir_wiki_tbl Tbl__wiki() {return tbl__wiki;} private final Xowdir_wiki_tbl tbl__wiki; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_db_utl.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_db_utl.java index a27517de8..c26264285 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_db_utl.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_db_utl.java @@ -13,3 +13,11 @@ 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.addons.wikis.directorys.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; +import gplx.dbs.sys.*; +public class Xowdir_db_utl { + public static int Wiki_id__next(Xoa_app app) { + Db_sys_mgr sys_mgr = new Db_sys_mgr(app.User().User_db_mgr().Conn()); + return sys_mgr.Autonum_next("user.wikis.id"); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_itm.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_itm.java index a27517de8..a49792e1c 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_itm.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_itm.java @@ -13,3 +13,16 @@ 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.addons.wikis.directorys.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; +public class Xowdir_wiki_itm { + public Xowdir_wiki_itm(int id, String domain, Io_url url, Xowdir_wiki_json json) { + this.id = id; + this.domain = domain; + this.url = url; + this.json = json; + } + public int Id() {return id;} private final int id; + public String Domain() {return domain;} private final String domain; + public Io_url Url() {return url;} private final Io_url url; + public Xowdir_wiki_json Json() {return json;} private final Xowdir_wiki_json json; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_json.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_json.java index a27517de8..79e221757 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_json.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_json.java @@ -13,3 +13,34 @@ 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.addons.wikis.directorys.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; +import gplx.langs.jsons.*; +public class Xowdir_wiki_json { + public Xowdir_wiki_json(String name, String main_page) { + this.name = name; + this.main_page = main_page; + } + public String Name() {return name;} private String name; public void Name_(String v) {name = v;} + public String Main_page() {return main_page;} private String main_page; public void Main_page_(String v) {main_page = v;} + public String To_str(Json_wtr wtr) { + wtr.Doc_nde_bgn(); + + wtr.Nde_bgn("core"); + wtr.Kv_str("name", name); + wtr.Kv_str("mainpage", main_page); + wtr.Nde_end(); + + wtr.Doc_nde_end(); + return wtr.To_str_and_clear(); + } + + public static Xowdir_wiki_json New_by_json(Json_parser json_parser, String json) { + Json_doc jdoc = json_parser.Parse(json); + String name = jdoc.Get_val_as_str_or(Bry_.Ary("core", "name"), ""); + String main_page = jdoc.Get_val_as_str_or(Bry_.Ary("core", "mainpage"), ""); + return new Xowdir_wiki_json(name, main_page); + } + public static Xowdir_wiki_json New_empty() { + return new Xowdir_wiki_json("", "Main_Page"); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props.java index a27517de8..dd783ddc6 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props.java @@ -13,3 +13,30 @@ 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.addons.wikis.directorys.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; +import gplx.xowa.wikis.data.*; +public class Xowdir_wiki_props { + public Xowdir_wiki_props() {} + public Xowdir_wiki_props(String domain, String name, String main_page) { + this.domain = domain; + this.name = name; + this.main_page = main_page; + } + public boolean Dirty() {return dirty;} private boolean dirty; + public String Domain() {return domain;} private String domain; + public String Name() {return name;} private String name; + public String Main_page() {return main_page;} private String main_page; + + public void Dirty_y_() { + dirty = true; + } + + public void Set(String key, String val) { + if (String_.Eq(key, Xowd_cfg_key_.Key__wiki__core__domain)) this.domain = val; + else if (String_.Eq(key, Xowd_cfg_key_.Key__wiki__core__name)) this.name = val; + else if (String_.Eq(key, Xowd_cfg_key_.Key__init__main_page)) this.main_page = val; + else throw Err_.new_unhandled_default(key); + } + + public String To_str() {return String_.Concat(domain, "|", name, "|", main_page );} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props_mgr.java index a27517de8..eb50a4e33 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props_mgr.java @@ -13,3 +13,78 @@ 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.addons.wikis.directorys.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; +import gplx.langs.jsons.*; +import gplx.xowa.wikis.data.*; +public interface Xowdir_wiki_props_mgr { + void Wiki_cfg__upsert(String key, String val); + String Wiki_cfg__select_or(String key, String or); + void User_reg__upsert(String domain, String val); + String User_reg__select(String domain); + Xowdir_wiki_props Verify(boolean mode_is_import, String domain, Io_url core_db_url); +} +abstract class Xowdir_wiki_props_mgr__base implements Xowdir_wiki_props_mgr { + private final Gfo_usr_dlg usr_dlg; + public Xowdir_wiki_props_mgr__base(Gfo_usr_dlg usr_dlg) { + this.usr_dlg = usr_dlg; + } + public abstract void Wiki_cfg__upsert(String key, String val); + public abstract String Wiki_cfg__select_or(String key, String or); + public abstract void User_reg__upsert(String domain, String val); + public abstract String User_reg__select(String domain); + public Xowdir_wiki_props Verify(boolean mode_is_import, String domain, Io_url core_db_url) { + Xowdir_wiki_props rv = new Xowdir_wiki_props(); + + Verify_or_fix(rv, mode_is_import, core_db_url, Xowd_cfg_key_.Key__wiki__core__domain); + Verify_or_fix(rv, mode_is_import, core_db_url, Xowd_cfg_key_.Key__init__main_page); + Verify_or_fix(rv, mode_is_import, core_db_url, Xowd_cfg_key_.Key__wiki__core__name); + + return rv; + } + private String Verify_or_fix(Xowdir_wiki_props props, boolean mode_is_import, Io_url core_db_url, String key) { + String val = Wiki_cfg__select_or(key, null); + if (val == null) { + props.Dirty_y_(); + usr_dlg.Log_many("", "", "xowdir: core_db.xowa_cfg does not have val; url=~{0} key=~{1}", core_db_url, key); + val = Fix(props, mode_is_import, core_db_url, key); + Wiki_cfg__upsert(key, val); + } + props.Set(key, val); + return val; + } + private String Fix(Xowdir_wiki_props props, boolean mode_is_import, Io_url core_db_url, String key) { + if (String_.Eq(key, Xowd_cfg_key_.Key__wiki__core__domain)) { + String rv = core_db_url.NameOnly(); + if (String_.Has_at_end(rv, "-core")) + rv = String_.Mid(rv, 0, String_.Len(rv) - 5); + return rv; + } + else if (String_.Eq(key, Xowd_cfg_key_.Key__wiki__core__name)) { + if (mode_is_import) + return props.Domain(); // NOTE: must be called after domain + else { + Xowdir_wiki_json wiki_json = Xowdir_wiki_json.New_by_json(new Json_parser(), User_reg__select(props.Domain())); + return wiki_json.Name(); + } + } + else if (String_.Eq(key, Xowd_cfg_key_.Key__init__main_page)) { + return Xoa_page_.Main_page_str; + } + else throw Err_.new_unhandled_default(key); + } +} +class Xowdir_wiki_props_mgr__mock extends Xowdir_wiki_props_mgr__base { + private final Hash_adp wiki_cfg_hash = Hash_adp_.New(); + private final Hash_adp user_reg_hash = Hash_adp_.New(); + public Xowdir_wiki_props_mgr__mock() {super(Gfo_usr_dlg_.Noop);} + @Override public void Wiki_cfg__upsert(String key, String val) { + if (val != null) + wiki_cfg_hash.Add_if_dupe_use_nth(key, val); + } + @Override public String Wiki_cfg__select_or(String key, String or) { + String rv = (String)wiki_cfg_hash.Get_by(key); + return rv == null ? or : rv; + } + @Override public void User_reg__upsert(String domain, String val) {user_reg_hash.Add_if_dupe_use_nth(domain, val);} + @Override public String User_reg__select(String domain) {return (String)user_reg_hash.Get_by(domain);} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props_mgr_.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props_mgr_.java index a27517de8..28e4c3c51 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props_mgr_.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props_mgr_.java @@ -13,3 +13,7 @@ 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.addons.wikis.directorys.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; +public class Xowdir_wiki_props_mgr_ { + public static Xowdir_wiki_props_mgr New_xowa(Xoa_app app, Io_url core_db_url) {return new Xowdir_wiki_props_mgr__xowa(app, core_db_url);} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props_mgr__tst.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props_mgr__tst.java index a27517de8..d709b4e36 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props_mgr__tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props_mgr__tst.java @@ -13,3 +13,58 @@ 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.addons.wikis.directorys.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; +import org.junit.*; import gplx.core.tests.*; +import gplx.langs.jsons.*; +import gplx.xowa.wikis.data.*; +public class Xowdir_wiki_props_mgr__tst { + private final Xowdir_wiki_props_mgr__fxt fxt = new Xowdir_wiki_props_mgr__fxt(); + + @Test public void Import__wiki__missing_all() { + // handle all imported .xowa wikis pre v4.3 + fxt.Init__wiki_props(null, null, null); + fxt.Test__verify(Bool_.Y, "/dir/test.xowa", Bool_.Y, fxt.Make__json("test", "test", "Main_Page")); + } + @Test public void Import__wiki__missing_domain() { + // handle personal wikis from v4.2 + fxt.Init__wiki_props(null, null, "Main_Page"); + fxt.Test__verify(Bool_.Y, "/dir/test.xowa", Bool_.Y, fxt.Make__json("test", "test", "Main_Page")); + } + @Test public void Import__wiki__wmf_domain() { + // handle wmf wikis with a core-file of "test-core.xowa" + fxt.Init__wiki_props(null, null, "Main_Page"); + fxt.Test__verify(Bool_.Y, "/dir/test-core.xowa", Bool_.Y, fxt.Make__json("test", "test", "Main_Page")); + } + @Test public void Import__wiki__clean() { + // handle clean wiki + fxt.Init__wiki_props("test", "test", "Main_Page"); + fxt.Test__verify(Bool_.Y, "/dir/test.xowa", Bool_.N, fxt.Make__json("test", "test", "Main_Page")); + } + @Test public void Open__wiki__missing_name() { + // handle missing name + fxt.Init__user_json("test", "my test", "Main_Page"); + fxt.Init__wiki_props(null, null, "Main_Page"); + fxt.Test__verify(Bool_.N, "/dir/test.xowa", Bool_.Y, fxt.Make__json("test", "my test", "Main_Page")); + } +} +class Xowdir_wiki_props_mgr__fxt { + private final Xowdir_wiki_props_mgr mgr = new Xowdir_wiki_props_mgr__mock(); + public Xowdir_wiki_props Make__json(String domain, String name, String main_page) { + return new Xowdir_wiki_props(domain, name, main_page); + } + public void Init__user_json(String domain, String name, String main_page) { + Xowdir_wiki_json wiki_json = new Xowdir_wiki_json(name, main_page); + mgr.User_reg__upsert(domain, wiki_json.To_str(new Json_wtr())); + } + public void Init__wiki_props(String domain, String name, String main_page) { + mgr.Wiki_cfg__upsert(Xowd_cfg_key_.Key__wiki__core__domain, domain); + mgr.Wiki_cfg__upsert(Xowd_cfg_key_.Key__wiki__core__name, name); + mgr.Wiki_cfg__upsert(Xowd_cfg_key_.Key__init__main_page, main_page); + } + public void Test__verify(boolean mode_is_import, String url, boolean expd_dirty, Xowdir_wiki_props expd) { + Xowdir_wiki_props actl = mgr.Verify(mode_is_import, "", Io_url_.new_any_(url)); + + Gftest.Eq__ary__lines(expd.To_str(), actl.To_str(), "expd"); + Gftest.Eq__bool(expd_dirty, actl.Dirty(), "dirty"); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props_mgr__xowa.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props_mgr__xowa.java index a27517de8..fb5da59b9 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props_mgr__xowa.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_props_mgr__xowa.java @@ -13,3 +13,45 @@ 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.addons.wikis.directorys.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; +import gplx.langs.jsons.*; +class Xowdir_wiki_props_mgr__xowa extends Xowdir_wiki_props_mgr__base { + private final Xoa_app app; + private final Io_url core_db_url; + private Db_cfg_tbl wiki_cfg_tbl; + private Xowdir_wiki_tbl user_reg_tbl; + public Xowdir_wiki_props_mgr__xowa(Xoa_app app, Io_url core_db_url) {super(Gfo_usr_dlg_.Instance); + this.app = app; + this.core_db_url = core_db_url; + } + private void Wiki_cfg__assert_tbl() { + if (wiki_cfg_tbl == null) { + Db_conn core_db_conn = Db_conn_bldr.Instance.Get_or_noop(core_db_url); + wiki_cfg_tbl = new Db_cfg_tbl(core_db_conn, gplx.xowa.wikis.data.Xowd_cfg_tbl_.Tbl_name); + } + } + @Override public void Wiki_cfg__upsert(String key, String val) { + Wiki_cfg__assert_tbl(); + wiki_cfg_tbl.Upsert_str(gplx.xowa.wikis.data.Xowd_cfg_key_.Grp__wiki_init, key, val); + } + @Override public String Wiki_cfg__select_or(String key, String or) { + Wiki_cfg__assert_tbl(); + return wiki_cfg_tbl.Select_str_or(gplx.xowa.wikis.data.Xowd_cfg_key_.Grp__wiki_init, key, or); + } + private void User_reg__assert_tbl() { + if (user_reg_tbl == null) { + Xowdir_db_mgr user_db_mgr = new Xowdir_db_mgr(app.User().User_db_mgr().Conn()); + user_reg_tbl = user_db_mgr.Tbl__wiki(); + } + } + @Override public void User_reg__upsert(String domain, String val) { + User_reg__assert_tbl(); +// user_db_mgr.Tbl__wiki().Update_by_key_or_null(domain, val); + } + @Override public String User_reg__select(String domain) { + User_reg__assert_tbl(); + Xowdir_wiki_itm user_wiki_row = user_reg_tbl.Select_by_key_or_null(domain); + return user_wiki_row.Json().To_str(new Json_wtr()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_tbl.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_tbl.java index a27517de8..0cd966162 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/dbs/Xowdir_wiki_tbl.java @@ -13,3 +13,60 @@ 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.addons.wikis.directorys.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; +import gplx.dbs.*; import gplx.dbs.utls.*; +import gplx.langs.jsons.*; +public class Xowdir_wiki_tbl implements Db_tbl { // for domain and user-specific data only (name, url); don't replicate wiki-specific info here (like main_page) + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld__wiki_id, fld__wiki_domain, fld__wiki_core_url, fld__wiki_json; + private final Db_conn conn; + private final Json_parser json_parser = new Json_parser(); + public Xowdir_wiki_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = Tbl_name_dflt; + this.fld__wiki_id = flds.Add_int_pkey("wiki_id"); + this.fld__wiki_domain = flds.Add_str("wiki_domain", 255); // EX: "en.wikipedia.org" + this.fld__wiki_core_url = flds.Add_str("wiki_core_url", 255); // EX: "/xowa/wiki/en.wikipedia.org/en.wikipedia.org-core.xowa" + this.fld__wiki_json = flds.Add_text("wiki_json"); // EX: '{category_level="1",search_level="2", ...}' + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public boolean Upsert(int id, String domain, Io_url core_url, String json) { + return Db_tbl__crud_.Upsert(conn, tbl_name, flds, String_.Ary(fld__wiki_id), id, domain, core_url.Xto_api(), json); + } + public Xowdir_wiki_itm[] Select_all() { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds).Exec_select__rls_auto(); + try { + List_adp list = List_adp_.New(); + while (rdr.Move_next()) { + list.Add(Make(rdr)); + } + return (Xowdir_wiki_itm[])list.To_ary_and_clear(Xowdir_wiki_itm.class); + } + finally {rdr.Rls();} + } + public Xowdir_wiki_itm Select_by_key_or_null(String key) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld__wiki_domain).Crt_str(fld__wiki_domain, key).Exec_select__rls_auto(); + try {return rdr.Move_next() ? Make(rdr) : null;} + finally {rdr.Rls();} + } + public Xowdir_wiki_itm Select_by_id_or_null(int id) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld__wiki_id).Crt_int(fld__wiki_id, id).Exec_select__rls_auto(); + try {return rdr.Move_next() ? Make(rdr) : null;} + finally {rdr.Rls();} + } + public void Delete_by_id(int id) { + conn.Stmt_delete(tbl_name, fld__wiki_id).Crt_int(fld__wiki_id, id).Exec_delete(); + } + private Xowdir_wiki_itm Make(Db_rdr rdr) { + return new Xowdir_wiki_itm + ( rdr.Read_int(fld__wiki_id), rdr.Read_str(fld__wiki_domain) + , Io_url_.new_fil_(rdr.Read_str(fld__wiki_core_url)) + , Xowdir_wiki_json.New_by_json(json_parser, rdr.Read_str(fld__wiki_json)) + ); + } + public void Rls() {} + + public static final String Tbl_name_dflt = "user_wiki"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_bridge.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_bridge.java index a27517de8..4e2e130a1 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_bridge.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_bridge.java @@ -13,3 +13,34 @@ 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.addons.wikis.directorys.specials.items; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; +import gplx.langs.jsons.*; +import gplx.xowa.addons.wikis.directorys.dbs.*; +import gplx.xowa.htmls.bridges.*; +public class Xowdir_item_bridge implements Bridge_cmd_itm { + private Xowdir_item_mgr itm_mgr; + public void Init_by_app(Xoa_app app) { + this.itm_mgr = new Xowdir_item_mgr(app); + } + public String Exec(Json_nde data) { + byte proc_id = proc_hash.Get_as_byte_or(data.Get_as_bry_or(Bridge_cmd_mgr.Msg__proc, null), Byte_ascii.Max_7_bit); + Json_nde args = data.Get_kv(Bridge_cmd_mgr.Msg__args).Val_as_nde(); + switch (proc_id) { + case Proc__save: itm_mgr.Save(args); break; + case Proc__delete: itm_mgr.Delete(args); break; + case Proc__reindex_search: itm_mgr.Reindex_search(args); break; + default: throw Err_.new_unhandled_default(proc_id); + } + return ""; + } + + private static final byte Proc__save = 0, Proc__delete = 1, Proc__reindex_search = 2; + private static final Hash_adp_bry proc_hash = Hash_adp_bry.cs() + .Add_str_byte("save" , Proc__save) + .Add_str_byte("delete" , Proc__delete) + .Add_str_byte("reindex_search" , Proc__reindex_search) + ; + + public byte[] Key() {return BRIDGE_KEY;} public static final byte[] BRIDGE_KEY = Bry_.new_a7("wiki.directory.item"); + public static final Xowdir_item_bridge Prototype = new Xowdir_item_bridge(); Xowdir_item_bridge() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_doc.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_doc.java index a27517de8..4978bd5ac 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_doc.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_doc.java @@ -13,3 +13,44 @@ 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.addons.wikis.directorys.specials.items; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; +import gplx.langs.mustaches.*; import gplx.xowa.addons.wikis.directorys.dbs.*; +public class Xowdir_item_doc implements Mustache_doc_itm { + private final boolean mode_is_new; + private final int id; + private final String domain, name, dir, main_page; + public Xowdir_item_doc(int id, String domain, String name, String dir, String main_page) { + this.mode_is_new = id == -1; + this.id = id; + this.domain = domain; + this.name = name; + this.dir = dir; + this.main_page = main_page; + } + public boolean Mustache__write(String key, Mustache_bfr bfr) { + if (String_.Eq(key, "id")) bfr.Add_int(id); + else if (String_.Eq(key, "domain")) bfr.Add_str_u8(domain); + else if (String_.Eq(key, "name")) bfr.Add_str_u8(name); + else if (String_.Eq(key, "dir")) bfr.Add_str_u8(dir); + else if (String_.Eq(key, "mainpage")) bfr.Add_str_u8(main_page); + else return false; + return true; + } + public Mustache_doc_itm[] Mustache__subs(String key) { + if (String_.Eq(key, "mode_is_new")) return Mustache_doc_itm_.Ary__bool(mode_is_new); + return Mustache_doc_itm_.Ary__empty; + } + + public static final Xowdir_item_doc[] Ary_empty = new Xowdir_item_doc[0]; + public static Xowdir_item_doc New(Xowdir_wiki_itm itm) { + return new Xowdir_item_doc(itm.Id(), itm.Domain(), itm.Json().Name(), itm.Url().OwnerDir().Xto_api(), itm.Json().Main_page()); + } + public static Xowdir_item_doc[] New_ary(Xowdir_wiki_itm[] itms_ary) { + int len = itms_ary.length; + Xowdir_item_doc[] rv = new Xowdir_item_doc[itms_ary.length]; + for (int i = 0; i < len; i++) { + rv[i] = New(itms_ary[i]); + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_html.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_html.java index a27517de8..3bd5f4fdc 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_html.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_html.java @@ -13,3 +13,38 @@ 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.addons.wikis.directorys.specials.items; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; +import gplx.xowa.specials.*; import gplx.langs.mustaches.*; import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.tags.*; +import gplx.dbs.*; import gplx.xowa.addons.wikis.directorys.dbs.*; +class Xowdir_item_html extends Xow_special_wtr__base { + private final String domain; + public Xowdir_item_html(String domain) { + this.domain = domain; + } + @Override protected Io_url Get_addon_dir(Xoa_app app) {return Addon_dir(app);} + @Override protected Io_url Get_mustache_fil(Io_url addon_dir) {return addon_dir.GenSubFil_nest("bin", "xowdir_item.mustache.html");} + @Override protected Mustache_doc_itm Bld_mustache_root(Xoa_app app) { + Db_conn conn = app.User().User_db_mgr().Conn(); + Xowdir_db_mgr db_mgr = new Xowdir_db_mgr(conn); + Xowdir_wiki_itm itm = db_mgr.Tbl__wiki().Select_by_key_or_null(domain); + if (itm == null) + itm = new Xowdir_wiki_itm(-1, "", Io_url_.Empty, Xowdir_wiki_json.New_empty()); + return Xowdir_item_doc.New(itm); + } + @Override protected void Bld_tags(Xoa_app app, Io_url addon_dir, Xopage_html_data page_data) { + Xopg_tag_mgr head_tags = page_data.Head_tags(); + Xopg_tag_wtr_.Add__xocss (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xohelp (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xolog (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xoajax (head_tags, app.Fsys_mgr().Http_root(), app); + Xopg_tag_wtr_.Add__jquery (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xonotify (head_tags, app.Fsys_mgr().Http_root()); + Xopg_alertify_.Add_tags (head_tags, app.Fsys_mgr().Http_root()); + + head_tags.Add(Xopg_tag_itm.New_css_file(addon_dir.GenSubFil_nest("bin", "xowdir_item.css"))); + head_tags.Add(Xopg_tag_itm.New_js_file(addon_dir.GenSubFil_nest("bin", "xowdir_item.js"))); + } + public static Io_url Addon_dir(Xoa_app app) { + return app.Fsys_mgr().Http_root().GenSubDir_nest("bin", "any", "xowa", "addon", "wiki", "directory", "item"); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_mgr.java index a27517de8..c0a3a5a08 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_mgr.java @@ -13,3 +13,179 @@ 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.addons.wikis.directorys.specials.items; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; +import gplx.langs.jsons.*; +import gplx.dbs.sys.*; import gplx.xowa.addons.wikis.directorys.dbs.*; import gplx.xowa.addons.wikis.directorys.specials.items.bldrs.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.data.*; +import gplx.xowa.langs.cases.*; +class Xowdir_item_mgr { + private final Xoa_app app; + private final Json_wtr json_wtr = new Json_wtr(); + private gplx.xowa.guis.cbks.Xog_cbk_trg cbk_trg = gplx.xowa.guis.cbks.Xog_cbk_trg.New(Xowdir_item_special.Prototype.Special__meta().Ttl_bry()); + public Xowdir_item_mgr(Xoa_app app) { + this.app = app; + } + public void Save(Json_nde args) {Save(args.Get_as_int("id"), args.Get_as_str("domain"), args.Get_as_str("name"), args.Get_as_str("dir"), args.Get_as_str("mainpage"));} + public void Save(int id, String domain, String name, String dir_str, String mainpage_name) { + boolean itm_is_new = false; + // get next id if none provided + if (id == -1) { + itm_is_new = true; + id = Xowdir_db_utl.Wiki_id__next(app); + } + + Xowdir_db_mgr db_mgr = new Xowdir_db_mgr(app.User().User_db_mgr().Conn()); + Io_url dir_url = Io_url_.new_dir_infer(dir_str); + Xowdir_wiki_itm old_item = db_mgr.Tbl__wiki().Select_by_key_or_null(domain); + + // validate + mainpage_name = String_.Replace(mainpage_name, " ", "_"); + String err_msg = Validate(app, db_mgr, itm_is_new, domain, name, dir_url, mainpage_name); + if (err_msg != null) { + app.Gui__cbk_mgr().Send_json(cbk_trg, "xo.wiki_directory.notify__recv", gplx.core.gfobjs.Gfobj_nde.New().Add_str("msg_text", err_msg)); + return; + } + + // upsert into user_db.wiki_list + Io_url fil_url = dir_url.GenSubFil(domain + ".xowa"); + Xowdir_wiki_json json = Xowdir_wiki_json.New_empty(); + json.Name_(name); + json.Main_page_(mainpage_name); + String wiki_json = json.To_str(json_wtr); + db_mgr.Tbl__wiki().Upsert(id, domain, fil_url, wiki_json); + + if (itm_is_new) { + // create the actual wiki + byte[] mainpage_text = Io_mgr.Instance.LoadFilBryOr(Xowdir_item_html.Addon_dir(app).GenSubFil_nest("res", "page", "Main_Page.txt"), Bry_.Empty); + Xow_db_mkr.Create_wiki(new Xodb_wiki_data(domain, fil_url), name, Bry_.new_u8(mainpage_name), mainpage_text); + + // load it + Xow_wiki_factory.Load_personal((Xoae_app)app, Bry_.new_u8(domain), dir_url); + + // navigate to it + app.Gui__cbk_mgr().Send_redirect(cbk_trg, "/site/" + domain + "/wiki/" + mainpage_name); + } + else { + if (old_item != null) { + // try to move file into new dir + // COMMENTED: need to be able to close all connections else move will fail + //Io_url old_fil = old_item.Url(); + //String old_dir = old_fil.OwnerDir().Raw(); + //if (!String_.Eq(old_dir, dir_str)) { + // Io_url new_fil = dir_url.GenSubFil(old_fil.NameAndExt()); + // Io_mgr.Instance.MoveFil(old_fil, new_fil); + //} + } + + // navigate back to wiki_directory + app.Gui__cbk_mgr().Send_redirect(cbk_trg, "/site/home/wiki/Special:XowaWikiDirectory"); + } + } + public void Delete(Json_nde args) {Delete(args.Get_as_int("id"));} + public void Delete(int id) { + // get item by id + Xowdir_db_mgr db_mgr = new Xowdir_db_mgr(app.User().User_db_mgr().Conn()); + Xowdir_wiki_itm itm = db_mgr.Tbl__wiki().Select_by_id_or_null(id); + if (itm == null) throw Err_.new_wo_type("wiki does not exist", "id", id); + + // delete it + db_mgr.Tbl__wiki().Delete_by_id(id); + + // navigate back to wiki_directory + app.Gui__cbk_mgr().Send_redirect(cbk_trg, "/site/home/wiki/Special:XowaWikiDirectory"); + } + public void Reindex_search(Json_nde args) { + String domain = args.Get_as_str("domain"); + Xowe_wiki wiki = (Xowe_wiki)app.Wiki_mgri().Get_by_or_null(Bry_.new_u8(domain)); + + // update page count; needed else search cannot generate correct ranges when normalizing search_scores + int page_count = wiki.Data__core_mgr().Tbl__page().Select_count_all(); + if (page_count == -1) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "negative page count while reindexing search; domain=~{0}", domain); + } + wiki.Data__core_mgr().Db__core().Tbl__site_stats().Update(page_count, page_count, 0); + wiki.Data__core_mgr().Db__core().Tbl__site_stats().Select(wiki.Stats()); + + // run reindexer + gplx.xowa.addons.wikis.searchs.bldrs.Srch_bldr_mgr_.Setup(wiki); + app.Bldr().Run(); + + // clear cache + gplx.xowa.addons.wikis.searchs.Srch_search_addon.Get(wiki).Clear_rslts_cache();; + + // send notify + app.Gui__cbk_mgr().Send_notify(cbk_trg, "search reindex done"); + } + private String Validate(Xoa_app app, Xowdir_db_mgr db_mgr, boolean itm_is_new, String domain, String name, Io_url dir_url, String mainpage_name) { + // domain + if (itm_is_new) { + if (String_.Len_eq_0(domain)) + return "Domain cannot be empty: " + domain; + if (db_mgr.Tbl__wiki().Select_by_key_or_null(domain) != null) + return "Domain already exists: " + domain; + if (String_.Len(domain) > 63) + return "Domain must be 63 characters or less: " + domain; + if (!Is_valid_domain_name(Bry_.new_u8(domain))) + return "Domain is invalid; can only have letters, numbers, or a dot. If a dash exists, it cannot be at the start or the end: " + domain; + } + + // name + if (String_.Len_eq_0(name)) + return "Name cannot be empty: " + name; + if (String_.Len(name) > 255) + return "Name must: be 255 characters or less: " + name; + + // dir + String dir_str = dir_url.Raw(); + if (String_.Len_eq_0(dir_str)) + return "Folder cannot be empty: " + dir_str; + + // try to create fil + Io_mgr.Instance.CreateDirIfAbsent(dir_url); + Io_url tmp_fil = dir_url.GenSubFil("xowa.temp.txt"); + Io_mgr.Instance.SaveFilStr(tmp_fil, "temp"); + if (!String_.Eq(Io_mgr.Instance.LoadFilStr(tmp_fil), "temp")) + return "Folder could not be created: " + dir_str; + Io_mgr.Instance.DeleteFil(tmp_fil); + + // mainpage_name + if (itm_is_new) { + byte[] mainpage_name_bry = Bry_.new_u8(mainpage_name); + Xoa_ttl ttl = Xoa_ttl.Parse(app.User().Wikii(), mainpage_name_bry); + if (ttl == null) + return "Main Page has invalid characters. Please see the new wiki help page for more info: " + mainpage_name; + + Bry_bfr tmp = Bry_bfr_.New(); + byte[] ucase_1st = app.User().Wikii().Lang().Case_mgr().Case_build_1st_upper(tmp, mainpage_name_bry, 0, mainpage_name_bry.length); + if (!Bry_.Eq(mainpage_name_bry, ucase_1st)) + return "Main Page must start with an uppercase letter."; + } + + // valid returns null + return null; + } + private static boolean Is_valid_domain_name(byte[] src) { + int len = src.length; + if (len > 63) return false; + for (int i = 0; i < len; i++) { + byte b = src[i]; + // alpha-num is valid + if (Byte_ascii.Is_ltr(b) || Byte_ascii.Is_num(b)) + continue; + + // hyphens are only valid at start or end + if (b == Byte_ascii.Dash) { + if (i != 0 || i != len - 1) + continue; + } + + // allow dots; EX: en.wikipedia.org + if (b == Byte_ascii.Dot) + continue; + + // else, invalid + return false; + } + return true; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_special.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_special.java index a27517de8..8617c46d7 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_special.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/Xowdir_item_special.java @@ -13,3 +13,18 @@ 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.addons.wikis.directorys.specials.items; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; +import gplx.xowa.specials.*; import gplx.core.net.qargs.*; +public class Xowdir_item_special implements Xow_special_page { + public void Special__gen(Xow_wiki wiki, Xoa_page page, Xoa_url url, Xoa_ttl ttl) { + Gfo_qarg_mgr url_args = new Gfo_qarg_mgr().Init(url.Qargs_ary()); + + String domain = url_args.Read_str_or("domain", ""); + + new Xowdir_item_html(domain).Bld_page_by_mustache(wiki.App(), page, this); + } + Xowdir_item_special(Xow_special_meta special__meta) {this.special__meta = special__meta;} + public Xow_special_meta Special__meta() {return special__meta;} private final Xow_special_meta special__meta; + public Xow_special_page Special__clone() {return this;} + public static final Xow_special_page Prototype = new Xowdir_item_special(Xow_special_meta.New_xo("XowaWikiItem", "Wiki Details")); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xodb_wiki_data.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xodb_wiki_data.java index a27517de8..f759e1e8b 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xodb_wiki_data.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xodb_wiki_data.java @@ -13,3 +13,14 @@ 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.addons.wikis.directorys.specials.items.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; import gplx.xowa.addons.wikis.directorys.specials.items.*; +import gplx.core.ios.streams.*; +public class Xodb_wiki_data { + public Xodb_wiki_data(String domain, Io_url core_url) { + this.domain = domain; + this.core_url = core_url; + } + public String Domain() {return domain;} private final String domain; + public Io_url Core_url() {return core_url;} private final Io_url core_url; + public byte Text_zip_tid() {return text_zip_tid;} private byte text_zip_tid = Io_stream_tid_.Tid__raw; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xodb_wiki_db.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xodb_wiki_db.java index a27517de8..5bf590f4f 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xodb_wiki_db.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xodb_wiki_db.java @@ -13,3 +13,31 @@ 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.addons.wikis.directorys.specials.items.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; import gplx.xowa.addons.wikis.directorys.specials.items.*; +import gplx.dbs.*; +public class Xodb_wiki_db implements Db_tbl_owner { + private final Ordered_hash tbls = Ordered_hash_.New(); + public Xodb_wiki_db(int tid, Io_url url, Db_conn conn) { + this.tid = tid; + this.url = url; + this.conn = conn; + } + public int Tid() {return tid;} private final int tid; + public Io_url Url() {return url;} private final Io_url url; + public Db_conn Conn() {return conn;} private final Db_conn conn; + public Db_tbl Tbls__get_by_key(String key) {return (Db_tbl)tbls.Get_by(key);} + public void Tbls__add(boolean create, Db_tbl... ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) { + Db_tbl tbl = ary[i]; + tbls.Add(tbl.Tbl_name(), tbl); + if (create) + tbl.Create_tbl(); + } + } + + public static Xodb_wiki_db Make(int tid, Io_url url) { + Db_conn conn = Db_conn_bldr.Instance.Get_or_new(url).Conn(); + return new Xodb_wiki_db(tid, url, conn); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xodb_wiki_db_tid.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xodb_wiki_db_tid.java index a27517de8..b411443d2 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xodb_wiki_db_tid.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xodb_wiki_db_tid.java @@ -13,3 +13,7 @@ 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.addons.wikis.directorys.specials.items.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; import gplx.xowa.addons.wikis.directorys.specials.items.*; +public class Xodb_wiki_db_tid { + public static final int Tid__core = 0; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xodb_wiki_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xodb_wiki_mgr.java index a27517de8..b5fcd52c6 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xodb_wiki_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xodb_wiki_mgr.java @@ -13,3 +13,16 @@ 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.addons.wikis.directorys.specials.items.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; import gplx.xowa.addons.wikis.directorys.specials.items.*; +import gplx.dbs.*; +public class Xodb_wiki_mgr { + public Xodb_wiki_mgr(String domain) { + this.domain = domain; + } + public String Domain() {return domain;} private final String domain; + public Xodb_wiki_db Dbs__get_core() {return dbs__core;} private Xodb_wiki_db dbs__core; + public void Dbs__add(Xodb_wiki_db file) { + if (file.Tid() == Xodb_wiki_db_tid.Tid__core) + dbs__core = file; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xopg_db_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xopg_db_mgr.java index a27517de8..bfb3e11ec 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xopg_db_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xopg_db_mgr.java @@ -13,3 +13,125 @@ 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.addons.wikis.directorys.specials.items.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; import gplx.xowa.addons.wikis.directorys.specials.items.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; +import gplx.xowa.wikis.dbs.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.wikis.data.site_stats.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.addons.wikis.directorys.dbs.*; +import gplx.xowa.addons.wikis.directorys.specials.items.bldrs.*; +import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.dbs.*; +public class Xopg_db_mgr { + public static int Create + ( Xowd_page_tbl page_tbl, Xowd_text_tbl text_tbl, int text_db_id, Xowd_site_ns_tbl ns_tbl, Db_cfg_tbl cfg_tbl + , int ns_id, byte[] ttl_page_db, byte[] text_raw, int cat_db_id) { + // get next page_id + int page_id = cfg_tbl.Select_int_or(Xowd_cfg_key_.Grp__db, Xowd_cfg_key_.Key__wiki__page__id_next, 1); + + // check if page_id is unique; needed for existing WMF .xowa dbs which don't set Xowd_cfg_key_.Key__wiki__page__id_next; DATE:2017-02-19 + if (page_tbl.Select_by_id_or_null(page_id) != null) { + int max_page_id = page_tbl.Conn().Exec_select_max_as_int(page_tbl.Tbl_name(), page_tbl.Fld_page_id(), Xowd_page_tbl.INVALID_PAGE_ID); + if (max_page_id == Xowd_page_tbl.INVALID_PAGE_ID) { + throw Err_.new_wo_type("no max found in page_tbl even though page_id was not unique?: db=~{0} page_id=~{1}", page_tbl.Conn().Conn_info().Db_api(), page_id); + } + page_id = max_page_id + 1; + } + + // update it + cfg_tbl.Upsert_int(Xowd_cfg_key_.Grp__db, Xowd_cfg_key_.Key__wiki__page__id_next, page_id + 1); + + // zip if needed + byte[] text_zip = text_tbl.Zip(text_raw); + + // TODO.XO: should call redirect mgr + boolean redirect = Bool_.N; + + // do insert + page_tbl.Insert_bgn(); + text_tbl.Insert_bgn(); + int ns_count = ns_tbl.Select_ns_count(ns_id) + 1; + try { + page_tbl.Insert_cmd_by_batch(page_id, ns_id, ttl_page_db, redirect, Datetime_now.Get(), text_raw.length, ns_count, text_db_id, -1, cat_db_id); + text_tbl.Insert_cmd_by_batch(page_id, text_zip); + ns_tbl.Update_ns_count(ns_id, ns_count); + } finally { + page_tbl.Insert_end(); + text_tbl.Insert_end(); + } + return page_id; + } + public static void Delete(Xowe_wiki wiki, Xoa_ttl page_ttl) { + // init vars + Xow_db_mgr db_mgr = wiki.Data__core_mgr(); + Xow_db_file core_db = db_mgr.Db__core(); + Xowd_page_itm tmp = new Xowd_page_itm(); + + // get meta + core_db.Tbl__page().Select_by_ttl(tmp, page_ttl); + int page_id = tmp.Id(); + int ns_id = tmp.Ns_id(); + + // adjust site_stats + int page_is_file = ns_id == Xow_ns_.Tid__file ? 1 : 0; + Xowd_site_stats_mgr site_stats_row = new Xowd_site_stats_mgr(wiki); + core_db.Tbl__site_stats().Select(site_stats_row); + core_db.Tbl__site_stats().Update(site_stats_row.Num_articles() - 1, site_stats_row.Num_pages() - 1, site_stats_row.Num_files() - page_is_file); + + // adjust site_ns + int ns_count = core_db.Tbl__ns().Select_ns_count(ns_id); + core_db.Tbl__ns().Update_ns_count(ns_id, ns_count - 1); + + // text_db + Xow_db_file text_db = db_mgr.Dbs__get_by_id_or_null(tmp.Text_db_id()); + if (text_db != null) { + text_db.Tbl__text().Delete(page_id); + } + + // html_db + Xow_db_file html_db = db_mgr.Dbs__get_by_id_or_null(tmp.Html_db_id()); + if (html_db != null) { + html_db.Tbl__html().Delete(page_id); + } + + // cat_core, cat_link + gplx.xowa.addons.wikis.ctgs.edits.Xoctg_edit_mgr.Delete(wiki, ns_id, page_id); + + // search_link + Srch_search_addon.Get(wiki).Delete_links(ns_id, page_id); + + // delete from page + core_db.Tbl__page().Delete(page_id); + } + public static void Update_page_id(Xow_db_mgr db_mgr, int old_id, int new_id) { + // init vars + Xow_db_file core_db = db_mgr.Db__core(); + Xowd_page_itm tmp = new Xowd_page_itm(); + + // get ns_id + core_db.Tbl__page().Select_by_id(tmp, old_id); + int ns_id = tmp.Ns_id(); + + // text_db + Xow_db_file text_db = db_mgr.Dbs__get_by_id_or_null(tmp.Text_db_id()); + if (text_db != null) { + text_db.Tbl__text().Update_page_id(old_id, new_id); + } + + // html_db + Xow_db_file html_db = db_mgr.Dbs__get_by_id_or_null(tmp.Html_db_id()); + if (html_db != null) { + html_db.Tbl__html().Update_page_id(old_id, new_id); + } + + // cat_core, cat_link + gplx.xowa.addons.wikis.ctgs.edits.Xoctg_edit_mgr.Update_page_id(db_mgr, ns_id, old_id, new_id); + + // search_link + gplx.xowa.addons.wikis.searchs.dbs.Srch_db_mgr srch_db_mgr = new gplx.xowa.addons.wikis.searchs.dbs.Srch_db_mgr(db_mgr); + srch_db_mgr.Init(0); // NOTE: num_pages doesn't matter for updating links + srch_db_mgr.Update_links(ns_id, old_id, new_id); + // NOTE: should clear search_results_cache, but for now, update_page_id is only called as a maint proc when wiki is loaded + + // delete from page + core_db.Tbl__page().Update_page_id(old_id, new_id); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xow_db_mkr.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xow_db_mkr.java index a27517de8..435734929 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xow_db_mkr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xow_db_mkr.java @@ -13,3 +13,122 @@ 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.addons.wikis.directorys.specials.items.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; import gplx.xowa.addons.wikis.directorys.specials.items.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.dbs.sys.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.core.ios.streams.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.data.site_stats.*; +import gplx.xowa.langs.cases.*; +import gplx.fsdb.*; import gplx.fsdb.meta.*; import gplx.fsdb.data.*; import gplx.xowa.files.origs.*; +import gplx.xowa.addons.wikis.directorys.dbs.*; +import gplx.xowa.addons.wikis.ctgs.dbs.*; +import gplx.xowa.addons.wikis.searchs.dbs.*; +public class Xow_db_mkr { + public static Xodb_wiki_mgr Create_wiki(Xodb_wiki_data data, String wiki_name, byte[] mainpage_name, byte[] mainpage_text) { + // create db + Xodb_wiki_mgr wiki_mgr = new Xodb_wiki_mgr(data.Domain()); + wiki_mgr.Dbs__add(Xodb_wiki_db.Make(Xodb_wiki_db_tid.Tid__core, data.Core_url())); + + // create tbls: wiki + Xodb_wiki_db core_db = wiki_mgr.Dbs__get_core(); + Db_conn core_conn = core_db.Conn(); + core_db.Tbls__add(Bool_.Y + , Xowd_cfg_tbl_.New(core_conn) + , new Xowd_xowa_db_tbl(core_conn, Bool_.N) + , new Xowd_site_ns_tbl(core_conn, Bool_.N) + , new Xowd_site_stats_tbl(core_conn, Bool_.N) + , new Xowd_page_tbl(core_conn, Bool_.N) + , new Xowd_text_tbl(core_conn, Bool_.N, data.Text_zip_tid()) + ); + + // upgrade tbl: page for categories; NOTE: should change page_tbl to do this automatically + core_conn.Meta_fld_append(Xowd_page_tbl.TBL_NAME, Dbmeta_fld_itm.new_int(Xowd_page_tbl.FLD__page_cat_db_id).Default_(-1)); + + // create tbls: cat; may want to do "if (props.Layout_text().Tid_is_all_or_few())" // create in advance else will fail for v2; import wiki -> wiki loads and tries to load categories; v2 category processes and builds tbl; DATE:2015-03-22 + core_db.Tbls__add(Bool_.Y + , new Xowd_cat_core_tbl(core_conn, Bool_.N) + , new Xodb_cat_link_tbl(core_conn) + ); + + // create tbls; search_word + Srch_word_tbl search_word_tbl = new Srch_word_tbl(core_conn); + search_word_tbl.Create_tbl(); search_word_tbl.Create_idx(); + + // create tbls; search_link + Srch_link_tbl[] search_link_tbls = new Srch_link_tbl[1]; + Srch_db_mgr.Tbl__link__ary__set(search_link_tbls, 0, core_conn); + for (Srch_link_tbl link : search_link_tbls) { + link.Create_tbl(); + link.Create_idx__link_score(); + link.Create_idx__page_id(); + } + + // insert cfg; search version + Db_cfg_tbl cfg_tbl = Db_cfg_tbl.Get_by_key(core_db, Xowd_cfg_tbl_.Tbl_name); + cfg_tbl.Upsert_int(Srch_db_cfg_.Grp__search__cfg, Srch_db_cfg_.Key__version_id, Srch_db_upgrade.Version__link_score); + + // insert data: wiki + Xowd_xowa_db_tbl.Get_by_key(core_db).Upsert(0, Xow_db_file_.Tid__core, core_db.Url().NameAndExt(), "", -1, Guid_adp_.New_str()); + Xowd_site_ns_tbl.Get_by_key(core_db).Insert(Xow_ns_mgr_.default_(Xol_case_mgr_.U8())); + Xowd_site_stats_tbl.Get_by_key(core_db).Update(0, 0, 0); + + // insert data: cfg + Xowd_core_db_props props = new Xowd_core_db_props(2, Xow_db_layout.Itm_all, Xow_db_layout.Itm_all, Xow_db_layout.Itm_all, Io_stream_tid_.Tid__raw, Io_stream_tid_.Tid__raw, Bool_.N, Bool_.N); + props.Cfg_save(cfg_tbl); + + Xowd_cfg_tbl_.Upsert__create(cfg_tbl, data.Domain(), wiki_name, mainpage_name); + + // insert data: page + Xopg_db_mgr.Create + ( Xowd_page_tbl.Get_by_key(core_db) + , Xowd_text_tbl.Get_by_key(core_db), Xow_db_file_.Uid__core + , Xowd_site_ns_tbl.Get_by_key(core_db) + , Db_cfg_tbl.Get_by_key(core_db, Xowd_cfg_tbl_.Tbl_name) + , Xow_ns_.Tid__main, mainpage_name, mainpage_text + , -1); + + // create tbls: fsdb + core_db.Tbls__add(Bool_.Y + , new Fsm_mnt_tbl(core_conn, Bool_.N) + , new Fsm_atr_tbl(core_conn, Bool_.N) + , new Fsm_bin_tbl(core_conn, Bool_.N, Fsm_mnt_mgr.Mnt_idx_main) + , new Fsd_dir_tbl(core_conn, Bool_.N) + , new Fsd_fil_tbl(core_conn, Bool_.N, Fsm_mnt_mgr.Mnt_idx_main) + , new Fsd_thm_tbl(core_conn, Bool_.N, Fsm_mnt_mgr.Mnt_idx_main, Bool_.Y) + , new Xof_orig_tbl(core_conn, Bool_.N) + ); + + // insert data: fsdb + Fsm_mnt_mgr.Patch_core(cfg_tbl); + Fsm_atr_tbl.Get_by_key(core_db).Insert(Fsm_mnt_mgr.Mnt_idx_main, core_db.Url().NameAndExt()); + cfg_tbl.Insert_int("core", "mnt.insert_idx", Fsm_mnt_mgr.Mnt_idx_user); + + return wiki_mgr; + } +} +/* +xowa_cfg +xowa_db +site_ns +site_stats + +page +text + +cat_core +cat_link + +fsdb_mnt +fsdb_dba +fsdb_dbb +fsdb_dir +fsdb_fil +fsdb_thm +orig_reg + +search_link +search_link_reg +search_word + +css_core +css_file +*/ diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xow_wiki_factory.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xow_wiki_factory.java index a27517de8..a6f8174db 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xow_wiki_factory.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xow_wiki_factory.java @@ -13,3 +13,38 @@ 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.addons.wikis.directorys.specials.items.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; import gplx.xowa.addons.wikis.directorys.specials.items.*; +import gplx.xowa.wikis.dbs.*; +public class Xow_wiki_factory { + public static Xowe_wiki Load_personal(Xoae_app app, byte[] domain, Io_url dir_url) { + // upgrade wiki directly at db + Xow_wiki_upgrade_.Upgrade_wiki(app, domain, dir_url); + + // create the wiki + Xowe_wiki rv = new Xowe_wiki + ( app + , gplx.xowa.langs.Xol_lang_itm_.Lang_en_make(app.Lang_mgr()) + , gplx.xowa.wikis.nss.Xow_ns_mgr_.default_(gplx.xowa.langs.cases.Xol_case_mgr_.U8()) + , gplx.xowa.wikis.domains.Xow_domain_itm_.parse(domain) + , dir_url); + + // register it in app.Wikis; note that this must occur before initialization + app.Wiki_mgr().Add(rv); + + // do more initialization + rv.Init_by_wiki__force_and_mark_inited(); + Xodb_save_mgr save_mgr = rv.Db_mgr_as_sql().Save_mgr(); + save_mgr.Create_enabled_(true); + save_mgr.Update_modified_on_enabled_(true); + + // register it for the url-bar; EX: test.me.org/wiki/Main_Page + app.User().Wikii().Xwiki_mgr().Add_by_atrs_offline(String_.new_u8(domain), String_.new_u8(domain)); + + // add an xwiki to xowa.home + rv.Xwiki_mgr().Add_by_atrs("xowa.home", "home"); + + // HACK: remove CC copyright message; should change to option + rv.Msg_mgr().Get_or_make(Bry_.new_a7("wikimedia-copyright")).Atrs_set(Bry_.Empty, false, false); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xow_wiki_upgrade_.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xow_wiki_upgrade_.java index a27517de8..f08f20d4c 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xow_wiki_upgrade_.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/items/bldrs/Xow_wiki_upgrade_.java @@ -13,3 +13,100 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.addons.wikis.directorys.specials.items.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; import gplx.xowa.addons.wikis.directorys.specials.items.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.addons.wikis.ctgs.dbs.*; +import gplx.xowa.addons.wikis.directorys.dbs.*; +public class Xow_wiki_upgrade_ { + // correlates loosely to App_.Version; however, should only change when new cases are added to this class + private static final int + Upgrade_version__v00 = 515 + , Upgrade_version__v01 = 516 + ; + public static final int + Upgrade_version__cur = 516 // must match latest version + ; + public static void Upgrade_wiki(Xoae_app app, byte[] domain, Io_url dir_url) { + // get conn + Io_url core_db_url = gplx.xowa.wikis.data.Xow_db_file__core_.Find_core_fil_or_null(dir_url, String_.new_u8(domain)); + if (core_db_url == null) { + throw Err_.new_wo_type("failed to find core_db for wiki; wiki=~{domain} dir=~{dir_url}", domain, dir_url); + } + Db_conn core_db_conn = Db_conn_bldr.Instance.Get_or_fail(core_db_url); + + // verify json + Xowdir_wiki_props_mgr core_db_props = Xowdir_wiki_props_mgr_.New_xowa(app, core_db_url); + core_db_props.Verify(Bool_.N, String_.new_u8(domain), core_db_url); + + // get cfg + Db_cfg_tbl cfg_tbl = Xowd_cfg_tbl_.Get_or_fail(core_db_conn); + int upgrade_version = cfg_tbl.Select_int_or(Xowd_cfg_key_.Key__wiki__upgrade__version, Upgrade_version__v00); + + // wiki is up-to-date; exit; + if (upgrade_version == Upgrade_version__cur) return; + + // upgrades related to v00 + if (upgrade_version == Upgrade_version__v00) { + Gfo_usr_dlg_.Instance.Log_many("", "", "xo.wiki.upgrade:upgrading; db=~{0} cur=~{1} new=~{2}", core_db_url.Raw(), upgrade_version, Upgrade_version__v01); + + // cat_link: if cat_link.cl_sortkey_prefix doesn't exist, then cat_link is old format; drop it and add the new one + try { + if (!core_db_conn.Meta_fld_exists(Xodb_cat_link_tbl.TBL_NAME, Xodb_cat_link_tbl.FLD__cl_sortkey_prefix)) { + Gfo_usr_dlg_.Instance.Log_many("", "", "xo.personal:cat_link upgrade; fil=~{0}", core_db_url.Raw()); + core_db_conn.Meta_tbl_delete(Xodb_cat_link_tbl.TBL_NAME); + core_db_conn.Meta_tbl_assert(new Xodb_cat_link_tbl(core_db_conn)); + } + } catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "xo.personal:cat_link upgrade failed; err=~{0}", Err_.Message_gplx_log(e)); + } + + // page.cat_db_id: if page.cat_db_id doesn't exist, then add it + try { + if (!core_db_conn.Meta_fld_exists(Xowd_page_tbl.TBL_NAME, Xowd_page_tbl.FLD__page_cat_db_id)) { + Gfo_usr_dlg_.Instance.Log_many("", "", "xo.personal:page.page_cat_db_id upgrade; fil=~{0}", core_db_url.Raw()); + core_db_conn.Meta_fld_append(Xowd_page_tbl.TBL_NAME, Dbmeta_fld_itm.new_int(Xowd_page_tbl.FLD__page_cat_db_id).Default_(-1)); + } + } catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "xo.personal:page.page_cat_db_id upgrade failed; err=~{0}", Err_.Message_gplx_log(e)); + } + + // BGN:check for page_ids < 1 + // select from page_tbl for page_id < 1 + Xow_db_mgr db_mgr = new Xow_db_mgr(dir_url, String_.new_u8(domain)); + db_mgr.Init_by_load(core_db_url); + Xowd_page_tbl page_tbl = db_mgr.Db__core().Tbl__page(); + List_adp page_ids_list = List_adp_.New(); + Db_rdr page_rdr = page_tbl.Conn().Stmt_sql(Db_sql_.Make_by_fmt(String_.Ary("SELECT page_id FROM page WHERE page_id < 1"))).Exec_select__rls_auto(); + try { + while (page_rdr.Move_next()) { + page_ids_list.Add(page_rdr.Read_int("page_id")); + } + } finally {page_rdr.Rls();} + + // update page_id if any found + int page_ids_len = page_ids_list.Len(); + if (page_ids_len > 0) { + int next_id = db_mgr.Db__core().Tbl__cfg().Assert_int(Xowd_cfg_key_.Grp__db, Xowd_cfg_key_.Key__wiki__page__id_next, Xowd_page_tbl.INVALID_PAGE_ID); + // no "next_id" found in xowa_cfg + if (next_id == Xowd_page_tbl.INVALID_PAGE_ID) { + // get max page_id + int max_page_id = db_mgr.Db__core().Conn().Exec_select_max_as_int(Xowd_page_tbl.TBL_NAME, page_tbl.Fld_page_id(), 1); + + // note that max_page_id can be -1 or 0 for v4.2 personal wikis; EX: only one page created and it has an id of -1 + next_id = max_page_id < 1 + ? 1 + : max_page_id + 1; + } + for (int i = 0; i < page_ids_len; i++) { + int old_page_id = (int)page_ids_list.Get_at(i); + int new_page_id = next_id + i; + Xopg_db_mgr.Update_page_id(db_mgr, old_page_id, new_page_id); + } + db_mgr.Db__core().Tbl__cfg().Upsert_int(Xowd_cfg_key_.Grp__db, Xowd_cfg_key_.Key__wiki__page__id_next, next_id + page_ids_len); + } + // END:check for page_ids < 1 + + cfg_tbl.Upsert_int(Xowd_cfg_key_.Key__wiki__upgrade__version, Upgrade_version__v01); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_bridge.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_bridge.java index a27517de8..482ba4b3f 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_bridge.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_bridge.java @@ -13,3 +13,30 @@ 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.addons.wikis.directorys.specials.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; +import gplx.langs.jsons.*; +import gplx.xowa.addons.wikis.directorys.dbs.*; +import gplx.xowa.htmls.bridges.*; +public class Xowdir_list_bridge implements Bridge_cmd_itm { + private Xowdir_list_svc svc; + public void Init_by_app(Xoa_app app) { + this.svc = new Xowdir_list_svc(app); + } + public String Exec(Json_nde data) { + byte proc_id = proc_hash.Get_as_byte_or(data.Get_as_bry_or(Bridge_cmd_mgr.Msg__proc, null), Byte_ascii.Max_7_bit); + Json_nde args = data.Get_kv(Bridge_cmd_mgr.Msg__args).Val_as_nde(); + switch (proc_id) { + case Proc__import_wiki: svc.Import_wiki(args); break; + default: throw Err_.new_unhandled_default(proc_id); + } + return ""; + } + + private static final byte Proc__import_wiki = 0; + private static final Hash_adp_bry proc_hash = Hash_adp_bry.cs() + .Add_str_byte("import_wiki" , Proc__import_wiki) + ; + + public byte[] Key() {return BRIDGE_KEY;} public static final byte[] BRIDGE_KEY = Bry_.new_a7("wiki.directory.list"); + public static final Xowdir_list_bridge Prototype = new Xowdir_list_bridge(); Xowdir_list_bridge() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_doc.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_doc.java index a27517de8..506b8f5c6 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_doc.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_doc.java @@ -13,3 +13,19 @@ 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.addons.wikis.directorys.specials.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; +import gplx.langs.mustaches.*; +import gplx.xowa.addons.wikis.directorys.specials.items.*; +class Xowdir_list_doc implements Mustache_doc_itm { + private final Xowdir_item_doc[] itms_ary; + public Xowdir_list_doc(Xowdir_item_doc[] itms_ary) { + this.itms_ary = itms_ary; + } + public boolean Mustache__write(String key, Mustache_bfr bfr) { + return true; + } + public Mustache_doc_itm[] Mustache__subs(String key) { + if (String_.Eq(key, "itms")) return itms_ary; + return Mustache_doc_itm_.Ary__empty; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_html.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_html.java index a27517de8..fc66e3cc7 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_html.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_html.java @@ -13,3 +13,28 @@ 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.addons.wikis.directorys.specials.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; +import gplx.xowa.specials.*; import gplx.langs.mustaches.*; import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.tags.*; +import gplx.dbs.*; import gplx.xowa.addons.wikis.directorys.dbs.*; import gplx.xowa.addons.wikis.directorys.specials.items.*; +class Xowdir_list_html extends Xow_special_wtr__base { + @Override protected Io_url Get_addon_dir(Xoa_app app) {return app.Fsys_mgr().Http_root().GenSubDir_nest("bin", "any", "xowa", "addon", "wiki", "directory", "list");} + @Override protected Io_url Get_mustache_fil(Io_url addon_dir) {return addon_dir.GenSubFil_nest("bin", "xowdir_list.mustache.html");} + @Override protected Mustache_doc_itm Bld_mustache_root(Xoa_app app) { + Db_conn conn = app.User().User_db_mgr().Conn(); + Xowdir_db_mgr db_mgr = new Xowdir_db_mgr(conn); + Xowdir_wiki_itm[] itms_ary = db_mgr.Tbl__wiki().Select_all(); + return new Xowdir_list_doc(Xowdir_item_doc.New_ary(itms_ary)); + } + @Override protected void Bld_tags(Xoa_app app, Io_url addon_dir, Xopage_html_data page_data) { + Xopg_tag_mgr head_tags = page_data.Head_tags(); + Xopg_tag_wtr_.Add__xocss (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xohelp (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xolog (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xoajax (head_tags, app.Fsys_mgr().Http_root(), app); + Xopg_tag_wtr_.Add__jquery (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xonotify (head_tags, app.Fsys_mgr().Http_root()); + + head_tags.Add(Xopg_tag_itm.New_css_file(addon_dir.GenSubFil_nest("bin", "xowdir_list.css"))); + head_tags.Add(Xopg_tag_itm.New_js_file(addon_dir.GenSubFil_nest("bin", "xowdir_list.js"))); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_special.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_special.java index a27517de8..7d38b303f 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_special.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_special.java @@ -13,3 +13,14 @@ 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.addons.wikis.directorys.specials.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; +import gplx.xowa.specials.*; import gplx.core.net.qargs.*; +public class Xowdir_list_special implements Xow_special_page { + public void Special__gen(Xow_wiki wiki, Xoa_page page, Xoa_url url, Xoa_ttl ttl) { + new Xowdir_list_html().Bld_page_by_mustache(wiki.App(), page, this); + } + Xowdir_list_special(Xow_special_meta special__meta) {this.special__meta = special__meta;} + public Xow_special_meta Special__meta() {return special__meta;} private final Xow_special_meta special__meta; + public Xow_special_page Special__clone() {return this;} + public static final Xow_special_page Prototype = new Xowdir_list_special(Xow_special_meta.New_xo("XowaWikiDirectory", "Wiki List")); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_svc.java b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_svc.java index a27517de8..e8e0b917f 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_svc.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/directorys/specials/lists/Xowdir_list_svc.java @@ -13,3 +13,52 @@ 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.addons.wikis.directorys.specials.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.directorys.*; import gplx.xowa.addons.wikis.directorys.specials.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.dbs.sys.*; +import gplx.langs.jsons.*; +import gplx.xowa.addons.wikis.directorys.dbs.*; +class Xowdir_list_svc { + private final Xoa_app app; + private gplx.xowa.guis.cbks.Xog_cbk_trg cbk_trg = gplx.xowa.guis.cbks.Xog_cbk_trg.New(Xowdir_list_special.Prototype.Special__meta().Ttl_bry()); + public Xowdir_list_svc(Xoa_app app) { + this.app = app; + } + public void Import_wiki(Json_nde args) {Import_wiki(args.Get_as_str("url"));} + public void Import_wiki(String url) { + // verify file is sqlite + Io_url core_db_url = Io_url_.new_fil_(url); + Db_conn core_db_conn = Db_conn_bldr.Instance.Get_or_noop(core_db_url); + if (core_db_conn == Db_conn_.Noop) { + app.Gui__cbk_mgr().Send_notify(cbk_trg, "file is not a .xowa file: file=" + url); + return; + } + + // verify file is a core_db + if (!core_db_conn.Meta_tbl_exists(gplx.xowa.wikis.data.tbls.Xowd_xowa_db_tbl.TBL_NAME)) { + app.Gui__cbk_mgr().Send_notify(cbk_trg, "file is not a .xowa file or missing xowa_db table: file=" + url); + return; + } + + // get wiki_props from core_db.xowa_cfg + Xowdir_wiki_props_mgr wiki_props_mgr = Xowdir_wiki_props_mgr_.New_xowa(app, core_db_url); + Xowdir_wiki_props wiki_props = wiki_props_mgr.Verify(Bool_.Y, core_db_url.NameOnly(), core_db_url); + String domain = wiki_props.Domain(); + + // if same domain exists; return + Xowdir_db_mgr db_mgr = new Xowdir_db_mgr(app.User().User_db_mgr().Conn()); + if (db_mgr.Tbl__wiki().Select_by_key_or_null(domain) != null) { + app.Gui__cbk_mgr().Send_notify(cbk_trg, "wiki with same name already exists; domain=" + domain); + return; + } + + // add it to user_wiki + int id = Xowdir_db_utl.Wiki_id__next(app); + db_mgr.Tbl__wiki().Upsert(id, domain, core_db_url, new Xowdir_wiki_json(wiki_props.Name(), wiki_props.Main_page()).To_str(new Json_wtr())); + + // add it to personal wikis + gplx.xowa.addons.wikis.directorys.specials.items.bldrs.Xow_wiki_factory.Load_personal((Xoae_app)app, Bry_.new_u8(domain), core_db_url.OwnerDir()); + + // navigate to it + app.Gui__cbk_mgr().Send_redirect(cbk_trg, "/wiki/" + String_.new_u8(cbk_trg.Page_ttl())); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/Xosearch_fulltext_addon.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/Xosearch_fulltext_addon.java index a27517de8..915749abd 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/Xosearch_fulltext_addon.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/Xosearch_fulltext_addon.java @@ -13,3 +13,36 @@ 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.addons.wikis.fulltexts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; +import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.specials.*; import gplx.xowa.htmls.bridges.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.caches.*; +public class Xosearch_fulltext_addon implements Xoax_addon_itm, Xoax_addon_itm__special, Xoax_addon_itm__json, Xoax_addon_itm__bldr { + public String Addon__key() {return ADDON__KEY;} private static final String ADDON__KEY = "xowa.wiki.fulltext"; + public Xob_cmd[] Bldr_cmds() { + return new Xob_cmd[] + { gplx.xowa.addons.wikis.fulltexts.indexers.bldrs.Xofulltext_indexer_cmd.Prototype + }; + } + public Xow_special_page[] Special_pages() { + return new Xow_special_page[] + { gplx.xowa.addons.wikis.fulltexts.searchers.specials.Xofulltext_searcher_special.Prototype + , gplx.xowa.addons.wikis.fulltexts.indexers.specials.Xofulltext_indexer_special.Prototype + }; + } + public Bridge_cmd_itm[] Json_cmds() { + return new Bridge_cmd_itm[] + { gplx.xowa.addons.wikis.fulltexts.searchers.svcs.Xofulltext_searcher_bridge.Prototype + , gplx.xowa.addons.wikis.fulltexts.indexers.svcs.Xofulltext_indexer_bridge.Prototype + }; + } + public Xofulltext_cache_mgr Cache_mgr() {return cache_mgr;} private final Xofulltext_cache_mgr cache_mgr = new Xofulltext_cache_mgr(); + public static Xosearch_fulltext_addon Get_by_app(Xoa_app app) { + return (Xosearch_fulltext_addon)app.Addon_mgr().Itms__get_or_null(ADDON__KEY); + } + + public static Io_url Get_index_dir(Xow_wiki wiki) {return Get_index_dir(wiki.Fsys_mgr().Root_dir());} + public static Io_url Get_index_dir(Io_url wiki_dir) { + return wiki_dir.GenSubDir_nest("data", "search", "java7-v1"); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/core/Xofulltext_extractor.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/core/Xofulltext_extractor.java index a27517de8..f91e182e5 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/core/Xofulltext_extractor.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/core/Xofulltext_extractor.java @@ -13,3 +13,61 @@ 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.addons.wikis.fulltexts.core; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; +import gplx.core.btries.*; +import gplx.xowa.parsers.htmls.*; +public class Xofulltext_extractor implements Mwh_doc_wkr { + private final Mwh_doc_parser doc_parser = new Mwh_doc_parser(); + private final Bry_bfr bfr = Bry_bfr_.New(); + private final Btrie_slim_mgr punct_trie = Btrie_slim_mgr.cs(); + private final Btrie_rv trv = new Btrie_rv(); + public Xofulltext_extractor() { + punct_trie.Add_many_str(Xofulltext_punct_.Punct_bgn_ary); + punct_trie.Add_many_str("/", ")", "]", ">", "�"); + } + public Hash_adp_bry Nde_regy() {return nde_regy;} private final Hash_adp_bry nde_regy = Mwh_doc_wkr_.Nde_regy__mw(); + public void On_nde_head_bgn (Mwh_doc_parser mgr, byte[] src, int nde_tid, int key_bgn, int key_end) {} + public void On_nde_head_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end, boolean inline) {} + public void On_nde_tail_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end) {} + public void On_comment_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end) {} + public void On_entity_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end) {} + public void On_atr_each (Mwh_atr_parser mgr, byte[] src, int nde_tid, boolean valid, boolean repeated, boolean key_exists, byte[] key_bry, byte[] val_bry_manual, int[] itm_ary, int itm_idx) {} + public void On_txt_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end) { + // trim flanking ws + itm_bgn = Bry_find_.Find_fwd_while_ws(src, itm_bgn, itm_end); + itm_end = Bry_find_.Find_bwd__skip_ws(src, itm_end, itm_bgn); + + // add ws between entries + if (bfr.Len_gt_0()) { // ignore if 1st entry + // identify punct at start of String + int punct_end = itm_bgn; + while (true) { + // exit if at end + if (punct_end >= itm_end) break; + + // check if punct + Object o = punct_trie.Match_at(trv, src, punct_end, itm_end); + + // b is not punct; exit + if (o == null) { + break; + } + // b is punct; keep going + else { + punct_end++; + } + } + + // only add space if no punct at start; prevents building strings like "a b. c d" -> "a b . c d" + if (itm_bgn == punct_end) + bfr.Add_byte_space(); + } + + // add to bfr + bfr.Add_mid(src, itm_bgn, itm_end); + } + public byte[] Extract(byte[] src) { + doc_parser.Parse(this, src, 0, src.length); + return bfr.To_bry_and_clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/core/Xofulltext_extractor__tst.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/core/Xofulltext_extractor__tst.java index a27517de8..2bca75e26 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/core/Xofulltext_extractor__tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/core/Xofulltext_extractor__tst.java @@ -13,3 +13,33 @@ 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.addons.wikis.fulltexts.core; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; +import org.junit.*; import gplx.core.tests.*; +public class Xofulltext_extractor__tst { + private final Xofulltext_extractor__fxt fxt = new Xofulltext_extractor__fxt(); + @Test public void Basic() { + // simple node + fxt.Test__extract("a b c", "a b c"); + + // node with attributes + fxt.Test__extract("a f g", "a f g"); + + // nested nodes + fxt.Test__extract("a b c d e", "a b c d e"); + + // periods + fxt.Test__extract("a b. c d", "a b. c d"); + + // parens + fxt.Test__extract("(a b)", "(a b)"); + + // parens + fxt.Test__extract("a (b)", "a (b)"); + } +} +class Xofulltext_extractor__fxt { + private final Xofulltext_extractor extractor = new Xofulltext_extractor(); + public void Test__extract(String src, String expd) { + Gftest.Eq__str(expd, extractor.Extract(Bry_.new_u8(src))); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/core/Xofulltext_punct_.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/core/Xofulltext_punct_.java index a27517de8..327188abf 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/core/Xofulltext_punct_.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/core/Xofulltext_punct_.java @@ -13,3 +13,8 @@ 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.addons.wikis.fulltexts.core; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; +public class Xofulltext_punct_ { + public static final String[] Ws_bgn_ary = new String[] {"\t", "\n", "\r", " ", "/", "(", ")", "[", "]", "<", ">"}; + public static final String[] Punct_bgn_ary = new String[] {".", ",", "?", "!", ":", ";", "'", "\"", "-"}; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/bldrs/Xofulltext_indexer_args.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/bldrs/Xofulltext_indexer_args.java index a27517de8..928710ffc 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/bldrs/Xofulltext_indexer_args.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/bldrs/Xofulltext_indexer_args.java @@ -13,3 +13,61 @@ 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.addons.wikis.fulltexts.indexers.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.indexers.*; +import gplx.xowa.wikis.nss.*; +import gplx.gflucene.indexers.*; +public class Xofulltext_indexer_args implements Gfo_invk { + public byte[] wikis; + public String idx_opt = Gflucene_idx_opt.Docs_and_freqs.Key(); + private String ns_ids_str; + public int[] ns_ids_ary; + public void Init_by_wiki(Xowe_wiki wiki) { + // wikis: null + if (wikis == null) + wikis = wiki.Domain_bry(); + + // ns: null or * + // if null, use Main namespace + List_adp temp_ns_list = List_adp_.New(); + if (ns_ids_str == null) + temp_ns_list.Add(Xow_ns_.Tid__main); + // if *, use all namespaces + else if (String_.Eq(ns_ids_str, "*")) { + Xow_ns[] ns_ary = wiki.Ns_mgr().Ords_ary(); + int len = ns_ary.length; + for (int i = 0; i < len; i++) { + Xow_ns ns = ns_ary[i]; + int ns_id = ns.Id(); + if (ns_id < 0) continue; // ignore media, special + temp_ns_list.Add(ns_id); + } + } + // else, parse ns + else { + byte[][] ns_bry_ary = Bry_split_.Split(Bry_.new_u8(ns_ids_str), Byte_ascii.Comma, true); + for (byte[] ns_bry : ns_bry_ary) { + temp_ns_list.Add(Bry_.To_int(ns_bry)); + } + } + ns_ids_ary = (int[])temp_ns_list.To_ary_and_clear(int.class); + + // idx_opt + if (idx_opt == null) { + idx_opt = Gflucene_idx_opt.Docs_and_freqs.Key(); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, "wikis_")) this.wikis = m.ReadBryOr("v", null); + else if (ctx.Match(k, "ns_ids_")) this.ns_ids_str = m.ReadStrOr("v", null); + else if (ctx.Match(k, "idx_opt_")) this.idx_opt = m.ReadStrOr("v", null); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static Xofulltext_indexer_args New_by_json(gplx.langs.jsons.Json_nde args) { + Xofulltext_indexer_args rv = new Xofulltext_indexer_args(); + rv.wikis = args.Get_as_bry("wikis"); + rv.ns_ids_str = args.Get_as_str("ns_ids"); + rv.idx_opt = args.Get_as_str("idx_opt"); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/bldrs/Xofulltext_indexer_cmd.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/bldrs/Xofulltext_indexer_cmd.java index a27517de8..02d107560 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/bldrs/Xofulltext_indexer_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/bldrs/Xofulltext_indexer_cmd.java @@ -13,3 +13,21 @@ 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.addons.wikis.fulltexts.indexers.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.indexers.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xofulltext_indexer_cmd extends Xob_cmd__base { + private final Xofulltext_indexer_args args = new Xofulltext_indexer_args(); + public Xofulltext_indexer_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + wiki.Init_assert(); + new Xofulltext_indexer_mgr().Exec(wiki, null, args); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, "args")) return args; + else return Gfo_invk_.Rv_unhandled; + } + + @Override public String Cmd_key() {return "search.index";} + public static final Xob_cmd Prototype = new Xofulltext_indexer_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xofulltext_indexer_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/bldrs/Xofulltext_indexer_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/bldrs/Xofulltext_indexer_mgr.java index a27517de8..b378fa8b8 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/bldrs/Xofulltext_indexer_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/bldrs/Xofulltext_indexer_mgr.java @@ -13,3 +13,83 @@ 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.addons.wikis.fulltexts.indexers.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.indexers.*; +import gplx.dbs.*; +import gplx.xowa.htmls.*; +import gplx.xowa.wikis.data.*; +import gplx.xowa.htmls.core.dbs.*; +import gplx.xowa.addons.wikis.fulltexts.indexers.svcs.*; +public class Xofulltext_indexer_mgr { + public void Exec(Xowe_wiki wiki, Xofulltext_indexer_ui ui, Xofulltext_indexer_args args) { + // init indexer + Xofulltext_indexer_wkr indexer = new Xofulltext_indexer_wkr(); + indexer.Init(wiki, args.idx_opt); + + // get page tbl + Xow_db_file core_db = wiki.Data__core_mgr().Db__core(); + gplx.xowa.wikis.data.tbls.Xowd_page_tbl page_tbl = core_db.Tbl__page(); + + // init args + args.Init_by_wiki(wiki); + int count = 0; + Xoh_page hpg = new Xoh_page(); + + // get rdr and loop + Db_conn conn = page_tbl.Conn(); + int[] ns_ids = args.ns_ids_ary; + Db_stmt stmt = Db_stmt_.Null; + Db_rdr rdr = Db_rdr_.Empty; + try { + stmt = Db_stmt_.New_sql_lines(conn + , "SELECT page_id, page_score, page_namespace, page_title, page_html_db_id" + , "FROM page" + , "WHERE page_namespace IN (" + Db_sql_.Prep_in_from_ary(ns_ids) + ")" + ); + for (int ns_id : ns_ids) { + stmt.Crt_int("page_namespace", ns_id); + } + rdr = stmt.Exec_select__rls_auto(); + while (rdr.Move_next()) { + // read vars + int page_namespace = rdr.Read_int("page_namespace"); + byte[] page_ttl_bry = rdr.Read_bry_by_str("page_title"); + int page_id = rdr.Read_int("page_id"); + int page_score = rdr.Read_int("page_score"); + int html_db_id = rdr.Read_int("page_html_db_id"); + + // ignore redirects + if (html_db_id == -1) continue; + try { + // load page + Xoa_ttl page_ttl = wiki.Ttl_parse(page_namespace, page_ttl_bry); + if (page_ttl == null) + continue; + Xow_db_file html_db = html_db_id == -1 ? core_db : wiki.Data__core_mgr().Dbs__get_by_id_or_fail(html_db_id); + hpg.Ctor_by_hview(wiki, wiki.Utl__url_parser().Parse(page_ttl.Full_db()), page_ttl, page_id); + if (!html_db.Tbl__html().Select_by_page(hpg)) + continue; + byte[] html_text = wiki.Html__hdump_mgr().Load_mgr().Parse(hpg, hpg.Db().Html().Zip_tid(), hpg.Db().Html().Hzip_tid(), hpg.Db().Html().Html_bry()); + + // run index + indexer.Index(page_id, page_score, page_ttl.Page_txt(), html_text); + + // notify + if ((++count % 10000) == 0) { + Gfo_usr_dlg_.Instance.Prog_many("", "", "indexing page: ~{0}", count); + if (ui != null) + ui.Send_prog(Datetime_now.Get().XtoStr_fmt_yyyy_MM_dd_HH_mm_ss() + ": indexing page: " + count); + } + } catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "err: ~{0}", Err_.Message_gplx_log(e)); + } + } + } + finally { + rdr.Rls(); + stmt.Rls(); + } + + // term indexer + indexer.Term(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/bldrs/Xofulltext_indexer_wkr.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/bldrs/Xofulltext_indexer_wkr.java index a27517de8..9f6be0fbc 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/bldrs/Xofulltext_indexer_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/bldrs/Xofulltext_indexer_wkr.java @@ -13,3 +13,36 @@ 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.addons.wikis.fulltexts.indexers.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.indexers.*; +import gplx.gflucene.core.*; +import gplx.gflucene.indexers.*; +import gplx.xowa.addons.wikis.fulltexts.core.*; +public class Xofulltext_indexer_wkr { + private final Gflucene_indexer_mgr index_wtr = new Gflucene_indexer_mgr(); + private final Xofulltext_extractor extractor = new Xofulltext_extractor(); + public void Init(Xow_wiki wiki, String idx_opt) { + // delete existing dir + Io_url index_dir = Xosearch_fulltext_addon.Get_index_dir(wiki); + Io_mgr.Instance.DeleteDirDeep(index_dir); + + // init index_dir + index_wtr.Init(new Gflucene_index_data + ( Gflucene_analyzer_data.New_data_from_locale(wiki.Lang().Key_str()) + , index_dir.Xto_api()) + , idx_opt + ); + } + public void Index(Xoae_page wpg) { + synchronized (index_wtr) {// NOTE:synchronized needed for mass_parse; don't launch separate indexer per mp_thread b/c (a) lucene may not handle it well; (b) everything needs to be serialized to the same lucene dir, so no real performance benefits; DATE:2017-04-08 + byte[] html = extractor.Extract(wpg.Db().Html().Html_bry()); + Index(wpg.Db().Page().Id(), wpg.Db().Page().Score(), wpg.Ttl().Page_txt(), html); + } + } + public void Index(int page_id, int score, byte[] ttl, byte[] html) { + Gflucene_doc_data data = new Gflucene_doc_data(page_id, score, String_.new_u8(ttl), String_.new_u8(html)); + index_wtr.Exec(data); + } + public void Term() { + index_wtr.Term(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/specials/Xofulltext_indexer_html.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/specials/Xofulltext_indexer_html.java index a27517de8..d2a21d076 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/specials/Xofulltext_indexer_html.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/specials/Xofulltext_indexer_html.java @@ -13,3 +13,50 @@ 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.addons.wikis.fulltexts.indexers.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.indexers.*; +import gplx.xowa.specials.*; import gplx.langs.mustaches.*; import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.tags.*; +import gplx.dbs.*; +class Xofulltext_indexer_html extends Xow_special_wtr__base implements Mustache_doc_itm { + private final String wikis_bry, ns_ids, idx_opt; + public Xofulltext_indexer_html(String wikis_bry, String ns_ids, String idx_opt) { + this.wikis_bry = wikis_bry; + this.ns_ids = ns_ids; + this.idx_opt = idx_opt; + } + public boolean Mustache__write(String key, Mustache_bfr bfr) { + if (String_.Eq(key, "wikis")) bfr.Add_str_u8(wikis_bry); + else if (String_.Eq(key, "ns_ids")) bfr.Add_str_u8(ns_ids); + else if (String_.Eq(key, "idx_opt")) bfr.Add_str_u8(idx_opt); + else return false; + return true; + } + public Mustache_doc_itm[] Mustache__subs(String key) { + return Mustache_doc_itm_.Ary__empty; + } + + @Override protected Io_url Get_addon_dir(Xoa_app app) {return Addon_dir(app);} + @Override protected Io_url Get_mustache_fil(Io_url addon_dir) {return addon_dir.GenSubFil_nest("bin", "xofulltext_indexer.template.html");} + @Override protected Mustache_doc_itm Bld_mustache_root(Xoa_app app) { + return this; + } + @Override protected void Bld_tags(Xoa_app app, Io_url addon_dir, Xopage_html_data page_data) { + Xopg_tag_mgr head_tags = page_data.Head_tags(); + Xopg_tag_wtr_.Add__xoelem (head_tags, app.Fsys_mgr().Http_root()); + + Xopg_tag_wtr_.Add__xocss (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xohelp (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xolog (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xoajax (head_tags, app.Fsys_mgr().Http_root(), app); + Xopg_tag_wtr_.Add__jquery (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xonotify (head_tags, app.Fsys_mgr().Http_root()); + Xopg_alertify_.Add_tags (head_tags, app.Fsys_mgr().Http_root()); + + head_tags.Add(Xopg_tag_itm.New_css_file(addon_dir.GenSubFil_nest("bin", "xofulltext_indexer.css"))); + head_tags.Add(Xopg_tag_itm.New_js_file(addon_dir.GenSubFil_nest("bin", "xofulltext_indexer.js"))); + + page_data.Js_enabled_y_(); + } + public static Io_url Addon_dir(Xoa_app app) { + return app.Fsys_mgr().Http_root().GenSubDir_nest("bin", "any", "xowa", "addon", "wiki", "fulltext", "indexer"); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/specials/Xofulltext_indexer_special.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/specials/Xofulltext_indexer_special.java index a27517de8..e109c8e19 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/specials/Xofulltext_indexer_special.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/specials/Xofulltext_indexer_special.java @@ -13,3 +13,24 @@ 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.addons.wikis.fulltexts.indexers.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.indexers.*; +import gplx.xowa.specials.*; import gplx.core.net.qargs.*; +import gplx.xowa.addons.apps.cfgs.*; +public class Xofulltext_indexer_special implements Xow_special_page { + public void Special__gen(Xow_wiki wiki, Xoa_page page, Xoa_url url, Xoa_ttl ttl) { + // get qry if any + Gfo_qarg_mgr url_args = new Gfo_qarg_mgr().Init(url.Qargs_ary()); + + // get options and create page + // Xocfg_mgr cfg_mgr = wiki.App().Cfg(); + new Xofulltext_indexer_html + ( url_args.Read_str_or("wikis", wiki.Domain_str()) + , url_args.Read_str_or("ns_ids", "0") + , url_args.Read_str_or("idx_opt", gplx.gflucene.indexers.Gflucene_idx_opt.Docs_and_freqs.Key()) + ).Bld_page_by_mustache(wiki.App(), page, this); + } + Xofulltext_indexer_special(Xow_special_meta special__meta) {this.special__meta = special__meta;} + public Xow_special_meta Special__meta() {return special__meta;} private final Xow_special_meta special__meta; + public Xow_special_page Special__clone() {return this;} + public static final Xow_special_page Prototype = new Xofulltext_indexer_special(Xow_special_meta.New_xo("XowaSearchBuilder", "Indexer")); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/svcs/Xofulltext_indexer_bridge.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/svcs/Xofulltext_indexer_bridge.java index a27517de8..85a6cb1a7 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/svcs/Xofulltext_indexer_bridge.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/svcs/Xofulltext_indexer_bridge.java @@ -13,3 +13,29 @@ 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.addons.wikis.fulltexts.indexers.svcs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.indexers.*; +import gplx.langs.jsons.*; +import gplx.xowa.htmls.bridges.*; +public class Xofulltext_indexer_bridge implements Bridge_cmd_itm { + private Xofulltext_indexer_svc svc; + public void Init_by_app(Xoa_app app) { + this.svc = new Xofulltext_indexer_svc(app); + } + public String Exec(Json_nde data) { + byte proc_id = proc_hash.Get_as_byte_or(data.Get_as_bry_or(Bridge_cmd_mgr.Msg__proc, null), Byte_ascii.Max_7_bit); + Json_nde args = data.Get_kv(Bridge_cmd_mgr.Msg__args).Val_as_nde(); + switch (proc_id) { + case Proc__index: svc.Index(args); break; + default: throw Err_.new_unhandled_default(proc_id); + } + return ""; + } + + private static final byte Proc__index = 0; + private static final Hash_adp_bry proc_hash = Hash_adp_bry.cs() + .Add_str_byte("index" , Proc__index) + ; + + public byte[] Key() {return BRIDGE_KEY;} public static final byte[] BRIDGE_KEY = Bry_.new_a7("xowa.wiki.fulltext.indexer"); + public static final Xofulltext_indexer_bridge Prototype = new Xofulltext_indexer_bridge(); Xofulltext_indexer_bridge() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/svcs/Xofulltext_indexer_svc.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/svcs/Xofulltext_indexer_svc.java index a27517de8..fed6b3920 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/svcs/Xofulltext_indexer_svc.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/svcs/Xofulltext_indexer_svc.java @@ -13,3 +13,65 @@ 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.addons.wikis.fulltexts.indexers.svcs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.indexers.*; +import gplx.core.btries.*; +import gplx.langs.jsons.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.guis.cbks.*; +import gplx.xowa.addons.apps.cfgs.*; +import gplx.xowa.addons.wikis.fulltexts.indexers.specials.*; +import gplx.xowa.addons.wikis.fulltexts.indexers.bldrs.*; +class Xofulltext_indexer_svc implements Gfo_invk { + private final Xoa_app app; + private final Xog_cbk_trg cbk_trg = Xog_cbk_trg.New(Xofulltext_indexer_special.Prototype.Special__meta().Ttl_bry()); + public Xofulltext_indexer_svc(Xoa_app app) { + this.app = app; + } + public void Index(Json_nde args) { + // create args + Xofulltext_indexer_args indexer_args = Xofulltext_indexer_args.New_by_json(args); + + // launch thread + gplx.core.threads.Thread_adp_.Start_by_val("index", Cancelable_.Never, this, Invk__index, indexer_args); + } + private void Index(Xofulltext_indexer_args args) { + // loop wikis + byte[][] domain_ary = Bry_split_.Split(args.wikis, Byte_ascii.Pipe); + for (byte[] domain : domain_ary) { + // get wiki + Xow_wiki wiki = app.Wiki_mgri().Get_by_or_make_init_n(domain); + if (!Io_mgr.Instance.ExistsDir(wiki.Fsys_mgr().Root_dir())) { + app.Gui__cbk_mgr().Send_json(cbk_trg, "xo.fulltext_indexer.status__note__recv", gplx.core.gfobjs.Gfobj_nde.New() + .Add_str("note", Datetime_now.Get().XtoStr_fmt_yyyy_MM_dd_HH_mm_ss() + ": wiki does not exist: " + String_.new_u8(domain))); + continue; + } + + // check if dir exists + wiki.Init_by_wiki(); + Io_url search_dir = Xosearch_fulltext_addon.Get_index_dir(wiki); + if (Io_mgr.Instance.ExistsDir(search_dir)) { + app.Gui__cbk_mgr().Send_json(cbk_trg, "xo.fulltext_indexer.status__note__recv", gplx.core.gfobjs.Gfobj_nde.New() + .Add_str("note", Datetime_now.Get().XtoStr_fmt_yyyy_MM_dd_HH_mm_ss() + ": search dir already exists; please delete it manually before reindexing; dir=" + search_dir.Xto_api())); + continue; + } + + // notify bgn + app.Gui__cbk_mgr().Send_json(cbk_trg, "xo.fulltext_indexer.status__note__recv", gplx.core.gfobjs.Gfobj_nde.New() + .Add_str("note", Datetime_now.Get().XtoStr_fmt_yyyy_MM_dd_HH_mm_ss() + ": wiki index started: " + String_.new_u8(domain))); + + // run index + new Xofulltext_indexer_mgr().Exec((Xowe_wiki)wiki, new Xofulltext_indexer_ui(app.Gui__cbk_mgr(), cbk_trg), args); + + // notify end + app.Gui__cbk_mgr().Send_json(cbk_trg, "xo.fulltext_indexer.status__note__recv", gplx.core.gfobjs.Gfobj_nde.New() + .Add_str("note", Datetime_now.Get().XtoStr_fmt_yyyy_MM_dd_HH_mm_ss() + ": wiki index ended: " + String_.new_u8(domain))); + } + } + + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__index)) this.Index((Xofulltext_indexer_args)m.ReadObj("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk__index = "index"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/svcs/Xofulltext_indexer_ui.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/svcs/Xofulltext_indexer_ui.java index a27517de8..cc81ca3d3 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/svcs/Xofulltext_indexer_ui.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/indexers/svcs/Xofulltext_indexer_ui.java @@ -13,3 +13,18 @@ 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.addons.wikis.fulltexts.indexers.svcs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.indexers.*; +import gplx.xowa.guis.cbks.*; +public class Xofulltext_indexer_ui { + private final Xog_cbk_mgr cbk_mgr; + private final Xog_cbk_trg cbk_trg; + public Xofulltext_indexer_ui(Xog_cbk_mgr cbk_mgr, Xog_cbk_trg cbk_trg) { + this.cbk_mgr = cbk_mgr; + this.cbk_trg = cbk_trg; + } + public void Send_prog(String prog) { + cbk_mgr.Send_json(cbk_trg, "xo.fulltext_indexer.status__prog__recv", gplx.core.gfobjs.Gfobj_nde.New() + .Add_str("prog", prog) + ); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/caches/Xofulltext_cache_line.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/caches/Xofulltext_cache_line.java index a27517de8..8fc44861d 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/caches/Xofulltext_cache_line.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/caches/Xofulltext_cache_line.java @@ -13,3 +13,12 @@ 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.addons.wikis.fulltexts.searchers.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; +public class Xofulltext_cache_line { + public Xofulltext_cache_line(int line_seq, byte[] line_html) { + this.line_seq = line_seq; + this.line_html = line_html; + } + public int Line_seq() {return line_seq;} private final int line_seq; + public byte[] Line_html() {return line_html;} private final byte[] line_html; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/caches/Xofulltext_cache_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/caches/Xofulltext_cache_mgr.java index a27517de8..05852b2b1 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/caches/Xofulltext_cache_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/caches/Xofulltext_cache_mgr.java @@ -13,3 +13,76 @@ 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.addons.wikis.fulltexts.searchers.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; +import gplx.core.primitives.*; +public class Xofulltext_cache_mgr { + private final Ordered_hash qry_hash = Ordered_hash_.New(); + private final Hash_adp_bry id_hash = Hash_adp_bry.cs(); + public int Next_qry_id() {return next_qry_id++;} private int next_qry_id; + public void Clear() { + qry_hash.Clear(); + } + public int Ids__get_or_neg1(byte[] qry_key) { + Int_obj_val id = (Int_obj_val)id_hash.Get_by(qry_key); + return id == null ? -1 : id.Val(); + } + public int Ids__next() {return next_qry_id++;} + public void Add(int id, byte[] key) { + id_hash.Add(key, new Int_obj_val(id)); + Xofulltext_cache_qry qry = (Xofulltext_cache_qry)qry_hash.Get_by(id); + if (qry == null) { + qry = new Xofulltext_cache_qry(id, key); + qry_hash.Add(id, qry); + } + } + public void Add_page(int query_id, byte[] wiki_bry, int page_id, byte[] page_ttl) { + // get qry + Xofulltext_cache_qry qry = (Xofulltext_cache_qry)qry_hash.Get_by_or_fail(query_id); + + // get page + Xofulltext_cache_page page = (Xofulltext_cache_page)qry.Pages().Get_by(page_id); + if (page == null) { + page = new Xofulltext_cache_page(page_id, qry.Pages().Count(), page_ttl); + qry.Pages().Add(page_id, page); + } + } + public void Add_line(int query_id, byte[] wiki_bry, int page_id, int line_seq, byte[] line_html) { + // get qry + Xofulltext_cache_qry qry = (Xofulltext_cache_qry)qry_hash.Get_by_or_fail(query_id); + Xofulltext_cache_page page = (Xofulltext_cache_page)qry.Pages().Get_by_or_fail(page_id); + + // add line + Xofulltext_cache_line line = new Xofulltext_cache_line(line_seq, line_html); + page.Lines().Add(line); + } + public Xofulltext_cache_qry Get_or_null(int qry_id) { + return (Xofulltext_cache_qry)qry_hash.Get_by(qry_id); + } + public Xofulltext_cache_page[] Get_pages_rng(int qry_id, int page_seq_bgn, int page_seq_end) { + Xofulltext_cache_qry qry = (Xofulltext_cache_qry)qry_hash.Get_by(qry_id); + if (qry == null) return null; + + List_adp list = List_adp_.New(); + int len = qry.Pages().Len(); + for (int i = page_seq_bgn; i < page_seq_end; i++) { + if (i >= len) break; + Xofulltext_cache_page page = (Xofulltext_cache_page)qry.Pages().Get_at(i); + list.Add(page); + } + return (Xofulltext_cache_page[])list.To_ary_and_clear(Xofulltext_cache_page.class); + } + public Xofulltext_cache_line[] Get_lines_rest(int qry_id, byte[] wiki_bry, int page_id) { + // get page + Xofulltext_cache_qry qry = (Xofulltext_cache_qry)qry_hash.Get_by(qry_id); + Xofulltext_cache_page page = (Xofulltext_cache_page)qry.Pages().Get_by(page_id); + + // loop lines from 1 to n; note "1" b/c results will always show at least 1st line + List_adp list = List_adp_.New(); + int lines_len = page.Lines().Len(); + for (int i = 1; i < lines_len; i++) { + Xofulltext_cache_line line = (Xofulltext_cache_line)page.Lines().Get_at(i); + list.Add(line); + } + return (Xofulltext_cache_line[])list.To_ary_and_clear(Xofulltext_cache_line.class); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/caches/Xofulltext_cache_page.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/caches/Xofulltext_cache_page.java index a27517de8..968ab6734 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/caches/Xofulltext_cache_page.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/caches/Xofulltext_cache_page.java @@ -13,3 +13,15 @@ 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.addons.wikis.fulltexts.searchers.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; +public class Xofulltext_cache_page { + public Xofulltext_cache_page(int page_id, int page_seq, byte[] page_ttl) { + this.page_id = page_id; + this.page_seq = page_seq; + this.page_ttl = page_ttl; + } + public int Page_id() {return page_id;} private final int page_id; + public int Page_seq() {return page_seq;} private final int page_seq; + public byte[] Page_ttl() {return page_ttl;} private final byte[] page_ttl; + public List_adp Lines() {return lines;} private final List_adp lines = List_adp_.New(); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/caches/Xofulltext_cache_qry.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/caches/Xofulltext_cache_qry.java index a27517de8..3638fe7c5 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/caches/Xofulltext_cache_qry.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/caches/Xofulltext_cache_qry.java @@ -13,3 +13,14 @@ 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.addons.wikis.fulltexts.searchers.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; +public class Xofulltext_cache_qry { + public Xofulltext_cache_qry(int id, byte[] text) { + this.id = id; + this.text = text; + } + public int Id() {return id;} private final int id; + public byte[] Text() {return text;} private final byte[] text; + public Ordered_hash Pages() {return pages;} private final Ordered_hash pages = Ordered_hash_.New(); + public boolean done; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/Xofulltext_args_qry.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/Xofulltext_args_qry.java index a27517de8..13c0b0f69 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/Xofulltext_args_qry.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/Xofulltext_args_qry.java @@ -13,3 +13,67 @@ 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.addons.wikis.fulltexts.searchers.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; +import gplx.langs.jsons.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.caches.*; +public class Xofulltext_args_qry { + public int qry_id; + public String page_guid; + public byte[] search_text; + public boolean expand_options; + public Xofulltext_cache_mgr cache_mgr; + public Xofulltext_args_wiki[] wikis_ary; + + public boolean case_match; + public boolean auto_wildcard_bgn; + public boolean auto_wildcard_end; + private boolean canceled; + + public byte[] Qry_key(byte[] wiki, byte[] ns_ids) { + return Bry_.Add_w_dlm(Byte_ascii.Nl, wiki, ns_ids, search_text); // EX: "en.wikipedia.org\n0|4\nearth" + } + public void Cancel() { + synchronized (this) { + canceled = true; + } + } + public boolean Canceled() {return canceled;} + + public static Xofulltext_args_qry New_by_json(Json_nde args) { + Xofulltext_args_qry rv = new Xofulltext_args_qry(); + rv.search_text = args.Get_as_bry("search"); + rv.page_guid = args.Get_as_str("page_guid"); + rv.expand_options = args.Get_as_bool_or("expand_options", false); + + // create wikis + byte[] wikis_bry = args.Get_as_bry("qarg_wikis"); + byte[][] wikis_ary = Bry_split_.Split(wikis_bry, Byte_ascii.Pipe, true); + int wikis_len = wikis_ary.length; + Xofulltext_args_wiki[] wiki_args = new Xofulltext_args_wiki[wikis_len]; + rv.wikis_ary = wiki_args; + for (int i = 0; i < wikis_len; i++) { + wiki_args[i] = new Xofulltext_args_wiki(wikis_ary[i]); + } + Set_prop(wiki_args, wikis_len, args, "ns_ids"); + Set_prop(wiki_args, wikis_len, args, "offsets"); + Set_prop(wiki_args, wikis_len, args, "limits"); + Set_prop(wiki_args, wikis_len, args, "expand_pages"); + Set_prop(wiki_args, wikis_len, args, "expand_snips"); + Set_prop(wiki_args, wikis_len, args, "show_all_snips"); + + rv.case_match = args.Get_as_bool_or("case_match", false); + rv.auto_wildcard_bgn = args.Get_as_bool_or("auto_wildcard_bgn", false); + rv.auto_wildcard_end = args.Get_as_bool_or("auto_wildcard_end", false); + return rv; + } + private static void Set_prop(Xofulltext_args_wiki[] wikis, int wikis_len, Json_nde args, String key) { + // set ns_ids + byte[] json_val = args.Get_as_bry("qarg_" + key); + byte[][] ary = Bry_split_.Split(json_val, Byte_ascii.Pipe, true); + int ary_len = ary.length; + for (int i = 0; i < wikis_len; i++) { + byte[] val = i < ary_len ? ary[i] : ary[ary_len - 1]; + wikis[i].Init_by_json(key, val); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/Xofulltext_args_wiki.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/Xofulltext_args_wiki.java index a27517de8..24bb791ac 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/Xofulltext_args_wiki.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/Xofulltext_args_wiki.java @@ -13,3 +13,34 @@ 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.addons.wikis.fulltexts.searchers.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; +public class Xofulltext_args_wiki { + public byte[] wiki; + public byte[] ns_ids; + public Hash_adp ns_hash = Hash_adp_.New(); + public int bgn; + public int len; + public int end() {return bgn + len;} + public boolean expand_pages; + public boolean expand_snips; + public boolean show_all_snips; + + public Xofulltext_args_wiki(byte[] wiki) { + this.wiki = wiki; + } + public void Init_by_json(String key, byte[] val) { + if (String_.Eq(key, "ns_ids")) { + this.ns_ids = val; + byte[][] ns_ary = Bry_split_.Split(ns_ids, Byte_ascii.Comma, true); + for (byte[] ns_id : ns_ary) { + int ns_int = Bry_.To_int(ns_id); + ns_hash.Add_if_dupe_use_1st(ns_int, ns_int); + } + } + else if (String_.Eq(key, "offsets")) this.bgn = Bry_.To_int(val); + else if (String_.Eq(key, "limits")) this.len = Bry_.To_int(val); + else if (String_.Eq(key, "expand_pages")) this.expand_pages = Bry_.Eq(val, Bool_.Y_bry); + else if (String_.Eq(key, "expand_snips")) this.expand_snips = Bry_.Eq(val, Bool_.Y_bry); + else if (String_.Eq(key, "show_all_snips")) this.show_all_snips = Bry_.Eq(val, Bool_.Y_bry); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/Xofulltext_searcher.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/Xofulltext_searcher.java index a27517de8..31b174888 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/Xofulltext_searcher.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/Xofulltext_searcher.java @@ -13,3 +13,10 @@ 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.addons.wikis.fulltexts.searchers.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.caches.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.uis.*; +public interface Xofulltext_searcher { + boolean Type_is_lucene(); + void Search(Xofulltext_searcher_ui ui, Xow_wiki wiki, Xofulltext_cache_qry qry, Xofulltext_args_qry qry_args, Xofulltext_args_wiki wiki_args); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/Xofulltext_searcher__brute.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/Xofulltext_searcher__brute.java index a27517de8..093a60544 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/Xofulltext_searcher__brute.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/Xofulltext_searcher__brute.java @@ -13,3 +13,82 @@ 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.addons.wikis.fulltexts.searchers.mgrs.brutes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.*; +import gplx.dbs.*; +import gplx.xowa.guis.cbks.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.uis.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.brutes.finders.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.caches.*; +public class Xofulltext_searcher__brute implements Xofulltext_searcher { + private final Xofulltext_finder_mgr finder = new Xofulltext_finder_mgr(); + private final Xofulltext_finder_cbk__eval cbk_eval = new Xofulltext_finder_cbk__eval(); + private final Xofulltext_finder_cbk__highlight cbk_highlight = new Xofulltext_finder_cbk__highlight(); + public boolean Type_is_lucene() {return false;} + public void Search(Xofulltext_searcher_ui ui, Xow_wiki wiki, Xofulltext_cache_qry qry, Xofulltext_args_qry args, Xofulltext_args_wiki wiki_args) { + // get pages from db + Db_conn page_conn = wiki.Data__core_mgr().Tbl__page().Conn(); + Db_rdr page_rdr = page_conn.Stmt_sql("SELECT * FROM page WHERE page_namespace IN (0) ORDER BY page_score DESC").Exec_select__rls_auto(); + + // init finder + finder.Init(args.search_text, args.case_match, args.auto_wildcard_bgn, args.auto_wildcard_end, Byte_ascii.Star, Byte_ascii.Dash); + + // loop + byte[] wiki_domain = wiki.Domain_bry(); + int found = 0; + int searched = 0; + int rng_bgn = wiki_args.bgn; + int rng_end = wiki_args.bgn + wiki_args.len; + try { + while (page_rdr.Move_next()) { + // read data from reader + int page_id = page_rdr.Read_int("page_id"); + int text_db_id = page_rdr.Read_int("page_text_db_id"); + byte[] text_mcase = wiki.Data__core_mgr().Dbs__get_by_id_or_fail(text_db_id).Tbl__text().Select(page_id); + int ns_id = page_rdr.Read_int("page_namespace"); + byte[] ttl_bry = page_rdr.Read_bry_by_str("page_title"); + Xoa_ttl ttl = wiki.Ttl_parse(ns_id, ttl_bry); + + // eval query + cbk_eval.Init(ttl.Full_db()); + finder.Match(text_mcase, 0, text_mcase.length, cbk_eval); + searched++; + + // check if page matches query + if (cbk_eval.found) { + ++found; + + // paging: ignore any results less than rng_bgn; EX: 21-40 are requested; ignore 1-20 + if (found <= rng_bgn) continue; + + // update pages found + ui.Send_wiki_update(wiki_domain, found, searched); + + // do highlight + cbk_highlight.Init(ui, args.qry_id, wiki, page_id, ttl.Full_db(), wiki_args.show_all_snips); + ui.Send_page_add(new Xofulltext_searcher_page + ( args.qry_id + , wiki_domain + , page_id + , ttl.Full_db() + , wiki_args.expand_snips + )); + finder.Match(text_mcase, 0, text_mcase.length, cbk_highlight); + + // paging; enough pages found; stop + if (found > rng_end) break; + } + + // update update pages found every 100 pages + if (searched % 100 == 0) { + ui.Send_wiki_update(wiki_domain, found, searched); + } + } + } + finally { + page_rdr.Rls(); + } + + // update one last time for final searched + ui.Send_wiki_update(wiki_domain, found, searched); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_cbk.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_cbk.java index a27517de8..56110001a 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_cbk.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_cbk.java @@ -13,3 +13,10 @@ 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.addons.wikis.fulltexts.searchers.mgrs.brutes.finders; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.brutes.*; +import gplx.xowa.guis.cbks.*; +public interface Xofulltext_finder_cbk { + byte[] Page_ttl(); + void Process_item_found(byte[] src, int hook_bgn, int hook_end, int word_bgn, int word_end, Xofulltext_word_node term); + void Process_page_done(byte[] src, Xofulltext_word_node tree_root); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_cbk__eval.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_cbk__eval.java index a27517de8..e2375d4c0 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_cbk__eval.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_cbk__eval.java @@ -13,3 +13,18 @@ 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.addons.wikis.fulltexts.searchers.mgrs.brutes.finders; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.brutes.*; +public class Xofulltext_finder_cbk__eval implements Xofulltext_finder_cbk { + public boolean found; + public byte[] Page_ttl() {return page_ttl;} private byte[] page_ttl; + public void Init(byte[] page_ttl) { + this.found = false; + this.page_ttl = page_ttl; + } + public void Process_item_found(byte[] src, int hook_bgn, int hook_end, int word_bgn, int word_end, Xofulltext_word_node term) { + term.found = true; + } + public void Process_page_done(byte[] src, Xofulltext_word_node root) { + this.found = root.Eval(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_cbk__eval__tst.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_cbk__eval__tst.java index a27517de8..0133743a9 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_cbk__eval__tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_cbk__eval__tst.java @@ -13,3 +13,110 @@ 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.addons.wikis.fulltexts.searchers.mgrs.brutes.finders; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.brutes.*; +import org.junit.*; import gplx.core.tests.*; +public class Xofulltext_finder_cbk__eval__tst { + private final Xofulltext_finder_cbk__eval__fxt fxt = new Xofulltext_finder_cbk__eval__fxt(); + @Test public void Exact() { + fxt.Init__search("a"); + // y: basic match + fxt.Test__eval_y("a"); + // n: no match + fxt.Test__eval_n("z"); + // n: wildcard_bgn not enabled + fxt.Test__eval_n("az"); + } + @Test public void Or() { + fxt.Init__search("a, c"); + // y: lone char + fxt.Test__eval_y("a" , "c"); + // y: one char + fxt.Test__eval_y("a b", "b c"); + // y: both chars + fxt.Test__eval_y("a c", "a b c"); + // n: no chars + fxt.Test__eval_n("b"); + } + @Test public void And() { + fxt.Init__search("a + c"); + // y: both chars + fxt.Test__eval_y("a c", "a b c"); + // n: one char only + fxt.Test__eval_n("a", "c", "a b", "b c"); + } + @Test public void And__shorthand() { + fxt.Init__search("a c"); + // y: both chars + fxt.Test__eval_y("a b c"); + // n: one char only + fxt.Test__eval_n("a", "c"); + } + @Test public void Not() { + fxt.Init__search("-a"); + // y: no chars + fxt.Test__eval_y("b"); + // n: char exists + fxt.Test__eval_n("a"); + } + @Test public void Trim_end() { + fxt.Init__search("a"); + // y: single + fxt.Test__eval_y("a!"); + // y: many + fxt.Test__eval_y("a!!!"); + } + @Test public void Trim_bgn() { + fxt.Init__search("a"); + // y: single + fxt.Test__eval_y("!a"); + // y: many + fxt.Test__eval_y("!!!a"); + } + @Test public void Trim_both() { + fxt.Init__search("a"); + // y: single + fxt.Test__eval_y("'a'"); + // y: many + fxt.Test__eval_y("'''a'''"); + } + @Test public void Slash() { + fxt.Init__search("a"); + // y: slash before, after + fxt.Test__eval_y("a/b/c", "b/a/c", "b/c/a"); + } + @Test public void Brack() { + fxt.Init__search("a"); + // y + fxt.Test__eval_y("[[a]]"); + } + // . + // ... + // - + // a'b + // https://site/page + // () + // [] + // <> +} +class Xofulltext_finder_cbk__eval__fxt { + private boolean case_match = false; + private boolean auto_wildcard_bgn = false; + private boolean auto_wildcard_end = false; + private byte wildcard_byte = Byte_ascii.Star; + private byte not_byte = Byte_ascii.Dash; + private final Xofulltext_finder_mgr finder = new Xofulltext_finder_mgr(); + private final Xofulltext_finder_cbk__eval cbk = new Xofulltext_finder_cbk__eval(); + public void Init__search(String query) { + finder.Init(Bry_.new_u8(query), case_match, auto_wildcard_bgn, auto_wildcard_end, wildcard_byte, not_byte); + } + public void Test__eval_y(String... texts) {Test__eval(Bool_.Y, texts);} + public void Test__eval_n(String... texts) {Test__eval(Bool_.N, texts);} + public void Test__eval(boolean expd, String... texts) { + for (String text : texts) { + byte[] text_bry = Bry_.new_u8(text); + cbk.found = false; + finder.Match(text_bry, 0, text_bry.length, cbk); + Gftest.Eq__bool(expd, cbk.found, "query={0} text={1}", finder.Query(), text); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_cbk__highlight.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_cbk__highlight.java index a27517de8..1f9acdb60 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_cbk__highlight.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_cbk__highlight.java @@ -13,3 +13,74 @@ 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.addons.wikis.fulltexts.searchers.mgrs.brutes.finders; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.brutes.*; +import gplx.xowa.guis.cbks.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.caches.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.uis.*; +public class Xofulltext_finder_cbk__highlight implements Xofulltext_finder_cbk { + private Xofulltext_searcher_ui ui; + private Xow_wiki wiki; + private int qry_id; + private int page_id; + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + public int found; + private boolean show_all_matches; + public byte[] Page_ttl() {return page_ttl;} private byte[] page_ttl; + public void Init(Xofulltext_searcher_ui ui, int qry_id, Xow_wiki wiki, int page_id, byte[] page_ttl, boolean show_all_matches) { + this.ui = ui; + this.qry_id = qry_id; + this.wiki = wiki; + this.page_id = page_id; + this.page_ttl= page_ttl; + this.show_all_matches = show_all_matches; + found = 0; + } + public void Process_item_found(byte[] src, int hook_bgn, int hook_end, int word_bgn, int word_end, Xofulltext_word_node term) { + // get snip bounds by finding flanking 50 chars and then expanding to word-bounds + int snip_bgn = hook_bgn - 50; + if (snip_bgn < 0) + snip_bgn = 0; + else { + snip_bgn = Bry_find_.Find_bwd_ws(src, snip_bgn, 0) + 1; + } + int snip_end = hook_end + 50; + if (snip_end >= src.length) + snip_end = src.length; + else { + snip_end = Bry_find_.Find_fwd_until_ws(src, snip_end, src.length); + if (snip_end == Bry_find_.Not_found) { // when snip_end == src.length + snip_end = src.length; + } + } + + // build snip + Add_snip(tmp_bfr, src, snip_bgn, hook_bgn); + tmp_bfr.Add_str_a7(""); + Add_snip(tmp_bfr, src, hook_bgn, hook_end); + tmp_bfr.Add_str_a7(""); + Add_snip(tmp_bfr, src, hook_end, snip_end); + + // send notification + byte[] line_html = tmp_bfr.To_bry_and_clear(); + ui.Send_line_add(true, show_all_matches, qry_id, wiki.Domain_bry(), page_id, found, line_html); + found++; + } + private static final byte[] Angle_bgn_escaped = Bry_.new_a7("<"); + private void Add_snip(Bry_bfr bfr, byte[] src, int bgn, int end) { + for (int i = bgn; i < end; i++) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Angle_bgn: + bfr.Add(Angle_bgn_escaped); + break; + case Byte_ascii.Nl: + bfr.Add(gplx.langs.htmls.Gfh_tag_.Br_inl); + break; + default: + bfr.Add_byte(b); + break; + } + } + } + public void Process_page_done(byte[] src, Xofulltext_word_node tree_root) {} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_mgr.java index a27517de8..76f42d3c9 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_finder_mgr.java @@ -13,3 +13,69 @@ 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.addons.wikis.fulltexts.searchers.mgrs.brutes.finders; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.brutes.*; +import gplx.xowa.guis.cbks.*; +import gplx.core.btries.*; +import gplx.xowa.addons.wikis.searchs.searchers.crts.*; +public class Xofulltext_finder_mgr { + private Btrie_slim_mgr hook_trie; + private Xofulltext_word_node tree_root; + private final Srch_crt_parser parser = new Srch_crt_parser(Srch_crt_scanner_syms.Dflt); + private final Btrie_rv trv = new Btrie_rv(); + private final Xofulltext_word_lang lang = new Xofulltext_word_lang(); + private final Xofulltext_word_bounds word_bounds = new Xofulltext_word_bounds(); + + public byte[] Query() {return query;} private byte[] query; + public void Init(byte[] query, boolean case_match, boolean auto_wildcard_bgn, boolean auto_wildcard_end, byte wildchar_byte, byte not_byte) { + this.query = query; + // create a new hook_trie based on case_match + this.hook_trie = case_match ? Btrie_slim_mgr.cs() : Btrie_slim_mgr.ci_u8(); + + // create a new tree_root for eval + this.tree_root = Xofulltext_word_node_.New_root(parser.Parse_or_invalid(query).Root, hook_trie, auto_wildcard_bgn, auto_wildcard_end, wildchar_byte, not_byte); + } + public void Match(byte[] src, int src_bgn, int src_end, Xofulltext_finder_cbk cbk) { + // init and clear + int cur = 0; + tree_root.Clear(); + + // scan through text one-byte at a time + // NOTE: skipping ahead to word-start instead of going byte-by-byte may seem more performant, but will still need to do substring analysis b/c of wildcards and punctuation; EX: "abc" and " 'abc' "; "*abc" and " xyzabc. " + while (cur <= src_end) { + // check each byte against hook_trie + Object hook_obj = hook_trie.Match_at(trv, src, cur, src_end); + + // current byte matches no hooks; go to next byte + if (hook_obj == null) { + cur++; + continue; + } + + // current byte matches a hook; get hook and hook_end + Xofulltext_word_node hook = (Xofulltext_word_node)hook_obj; + int hook_bgn = cur; + int hook_end = cur + hook.word_hook.length; + + try { + // get word_bounds + lang.Get_word_bounds(word_bounds, trv, src, src_end, hook_bgn, hook_end); + int word_bgn = word_bounds.word_bgn; + int word_end = word_bounds.word_end; + + // check if current word matches criteria-word + if (hook.Match_word(lang, src, hook_bgn, hook_end, word_bgn, word_end)) { + cbk.Process_item_found(src, hook_bgn, hook_end, word_bgn, word_end, hook); + } + + // update position to word_end + cur = word_end; + } catch (Exception e) { + cur = hook_end; + Gfo_usr_dlg_.Instance.Warn_many("", "", "fatal error in match; page=~{0} hook=~{1} src=~{2}", cbk.Page_ttl(), hook.word_orig, Err_.Message_gplx_log(e)); + } + } + + // mark page done + cbk.Process_page_done(src, tree_root); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_word_bounds.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_word_bounds.java index a27517de8..5852ff19c 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_word_bounds.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_word_bounds.java @@ -13,3 +13,12 @@ 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.addons.wikis.fulltexts.searchers.mgrs.brutes.finders; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.brutes.*; +public class Xofulltext_word_bounds { + public int word_bgn; + public int word_end; + public void Init(int word_bgn, int word_end) { + this.word_bgn = word_bgn; + this.word_end = word_end; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_word_lang.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_word_lang.java index a27517de8..830289eb3 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_word_lang.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_word_lang.java @@ -13,3 +13,105 @@ 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.addons.wikis.fulltexts.searchers.mgrs.brutes.finders; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.brutes.*; +import gplx.core.btries.*; +import gplx.core.intls.*; +import gplx.xowa.addons.wikis.fulltexts.core.*; +public class Xofulltext_word_lang { + private final Btrie_slim_mgr ws_bgn = Btrie_slim_mgr.cs().Add_many_str(Xofulltext_punct_.Ws_bgn_ary); + private final Btrie_slim_mgr ws_end; + private final Btrie_slim_mgr punct_bgn = Btrie_slim_mgr.cs().Add_many_str(Xofulltext_punct_.Punct_bgn_ary); + private final Btrie_slim_mgr punct_end; + public Xofulltext_word_lang() { + this.ws_end = ws_bgn; + this.punct_end = punct_bgn; + } + public void Get_word_bounds(Xofulltext_word_bounds word_bounds, Btrie_rv trv, byte[] src, int src_end, int hook_bgn, int hook_end) { + int tmp_pos = -1; + Object tmp_obj = null; + + // find word_bgn + int word_bgn = hook_bgn; + tmp_pos = word_bgn; + while (true) { + // stop if BOS + if (tmp_pos == 0) break; + + // move back one char + tmp_pos = Utf8_.Get_prv_char_pos0(src, tmp_pos); + + // check if char is ws + tmp_obj = ws_bgn.Match_at(trv, src, tmp_pos, hook_end); + + // char is ws -> stop + if (tmp_obj != null) break; + + // char is not ws -> update word_end + word_bgn = tmp_pos; + } + + // find word_end + int word_end = hook_end; + tmp_pos = word_end; + while (true) { + // stop if passed EOS + if (tmp_pos >= src_end) break; + + // check if char is ws + tmp_obj = ws_end.Match_at(trv, src, tmp_pos, src_end); + + // stop if ws + if (tmp_obj != null) break; + + // increment before + tmp_pos++; + + // update word_end + word_end = tmp_pos; + } + + // trim punct at bgn; EX: "'abc" -> "abc" + if (word_bgn < hook_bgn) { + tmp_pos = word_bgn; + while (true) { + // stop if passed hook-end + if (tmp_pos >= hook_bgn) break; + + // check if char is punct + tmp_obj = punct_bgn.Match_at(trv, src, tmp_pos, word_end); + + // stop if not a punct + if (tmp_obj == null) break; + + // increment before + tmp_pos++; + + // update word_end + word_bgn = tmp_pos; + } + } + + // trim punct at end; EX: "abc." -> "abc" + if (word_end > hook_end) { + tmp_pos = word_end; + while (true) { + // scan bwd one char + tmp_pos = Utf8_.Get_prv_char_pos0(src, tmp_pos); + + // stop if passed hook-end + if (tmp_pos < hook_end) break; + + // check if char is punct + tmp_obj = punct_end.Match_at(trv, src, tmp_pos, word_end); + + // stop if not a punct + if (tmp_obj == null) break; + + // update word_end + word_end = tmp_pos; + } + } + + word_bounds.Init(word_bgn, word_end); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_word_node.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_word_node.java index a27517de8..45fbff995 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_word_node.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_word_node.java @@ -13,3 +13,57 @@ 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.addons.wikis.fulltexts.searchers.mgrs.brutes.finders; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.brutes.*; +import gplx.core.btries.*; +import gplx.xowa.addons.wikis.searchs.searchers.crts.*; +public class Xofulltext_word_node { + public int tid; + public Xofulltext_word_node[] subs; + public byte[] word_orig; + public byte[] word_hook; + public boolean wildcard_at_bgn; + public boolean wildcard_at_end; + public boolean found; + + public boolean Match_word(Xofulltext_word_lang ctx, byte[] src, int hook_bgn, int hook_end, int word_bgn, int word_end) { + // if no wildcard at bgn, hook_bgn must match word_bgn + if ( !wildcard_at_bgn + && hook_bgn != word_bgn) + return false; + + // if no wildcard at end, hook_end must match word_end + if ( !wildcard_at_end + && hook_end != word_end) + return false; + + return true; + } + public void Clear() { + found = false; + for (Xofulltext_word_node sub : subs) + sub.Clear(); + } + public boolean Eval() { + switch (tid) { + case Srch_crt_itm.Tid__and: { + for (Xofulltext_word_node sub : subs) + if (!sub.Eval()) + return false; + return true; + } + case Srch_crt_itm.Tid__or: { + for (Xofulltext_word_node sub : subs) + if (sub.Eval()) + return true; + return false; + } + case Srch_crt_itm.Tid__word: + case Srch_crt_itm.Tid__word_quote: + return found; + case Srch_crt_itm.Tid__not: + return !subs[0].Eval(); + case Srch_crt_itm.Tid__invalid: return false; // should not happen + default: throw Err_.new_unhandled_default(tid); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_word_node_.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_word_node_.java index a27517de8..92a887ed9 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_word_node_.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/brutes/finders/Xofulltext_word_node_.java @@ -13,3 +13,65 @@ 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.addons.wikis.fulltexts.searchers.mgrs.brutes.finders; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.brutes.*; +import gplx.core.btries.*; +import gplx.xowa.addons.wikis.searchs.searchers.crts.*; +public class Xofulltext_word_node_ { + public static Xofulltext_word_node New_root(Srch_crt_itm src, Btrie_slim_mgr word_trie, boolean auto_wildcard_bgn, boolean auto_wildcard_end, byte wildchar_byte, byte not_byte) { + Xofulltext_word_node trg = new Xofulltext_word_node(); + trg.tid = src.Tid; + + // set word-related props + switch (trg.tid) { + case Srch_crt_itm.Tid__word: + case Srch_crt_itm.Tid__word_quote: + // get word_orig; EX: "abc*" + byte[] word_orig = src.Raw; + int word_orig_len = word_orig.length; + + // init hook_bgn / hook_end + int hook_bgn = 0; + int hook_end = word_orig_len; + + // handle wildcard at bgn; EX: "*a" + boolean wildcard_at_bgn = auto_wildcard_bgn; + if (word_orig_len > hook_bgn + 1 && word_orig[hook_bgn] == wildchar_byte) { + wildcard_at_bgn = true; + hook_bgn++; + } + + // handle wildcard at end; EX: "a*" + boolean wildcard_at_end = auto_wildcard_end; + if (word_orig_len > hook_bgn + 1 && word_orig[hook_end - 1] == wildchar_byte) { + wildcard_at_end = true; + hook_end--; + } + + // get hook + byte[] word_hook = wildcard_at_bgn || wildcard_at_end ? Bry_.Mid(word_orig, hook_bgn, hook_end) : word_orig; + + // assign to trg + trg.word_orig = word_orig; + trg.word_hook = word_hook; + trg.wildcard_at_bgn = wildcard_at_bgn; + trg.wildcard_at_end = wildcard_at_end; + + // add to trie + if (word_trie.Match_exact(word_hook) == null) { // don't add if exists + word_trie.Add_obj(word_hook, trg); + } + break; + } + + // set subs + Srch_crt_itm[] src_subs = src.Subs; + Xofulltext_word_node[] trg_subs = new Xofulltext_word_node[src_subs.length]; + trg.subs = trg_subs; + int len = src_subs.length; + for (int i = 0; i < len; i++) { + trg.subs[i] = New_root(src_subs[i], word_trie, auto_wildcard_bgn, auto_wildcard_end, wildchar_byte, not_byte); + } + + return trg; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/gflucenes/Xofulltext_highlighter_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/gflucenes/Xofulltext_highlighter_mgr.java index a27517de8..3be6f43d1 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/gflucenes/Xofulltext_highlighter_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/gflucenes/Xofulltext_highlighter_mgr.java @@ -13,3 +13,75 @@ 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.addons.wikis.fulltexts.searchers.mgrs.gflucenes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.*; +import gplx.gflucene.core.*; +import gplx.gflucene.highlighters.*; +import gplx.gflucene.searchers.*; +import gplx.xowa.htmls.*; +import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.uis.*; +import gplx.xowa.addons.wikis.fulltexts.core.*; +class Xofulltext_highlighter_mgr implements Gfo_invk { + private final Xofulltext_searcher_ui ui; + private final Xow_wiki wiki; + private final Xofulltext_args_qry searcher_args; + private final Xofulltext_args_wiki wiki_args; + private final Gflucene_analyzer_data analyzer_data; + private final Gflucene_searcher_qry searcher_data; + private final Gflucene_highlighter_mgr highlighter_mgr = new Gflucene_highlighter_mgr(); + private final Xoh_page hpg = new Xoh_page(); + private final Ordered_hash list; + private final Xofulltext_extractor extractor = new Xofulltext_extractor(); + public Xofulltext_highlighter_mgr(Xofulltext_searcher_ui ui, Xow_wiki wiki, Xofulltext_args_qry searcher_args, Xofulltext_args_wiki wiki_args, Gflucene_analyzer_data analyzer_data, Gflucene_searcher_qry searcher_data, Ordered_hash list) { + this.ui = ui; + this.wiki = wiki; + this.searcher_args = searcher_args; + this.wiki_args = wiki_args; + this.analyzer_data = analyzer_data; + this.searcher_data = searcher_data; + this.list = list; + } + public void Highlight_list() { + // init highlighter + highlighter_mgr.Init(new Gflucene_index_data(analyzer_data, "")); // NOTE: index_dir not needed for highlighter + + // loop items + int len = list.Len(); + for (int i = 0; i < len; i++) { + if (searcher_args.Canceled()) return; + Gflucene_doc_data item = (Gflucene_doc_data)list.Get_at(i); + try { + Highlight_item(item); + } catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "search.highlight: failed to highlight lines in page; page=~{0} err=~{1}", item.page_id, Err_.Message_gplx_log(e)); + } + } + + // term highlighter + highlighter_mgr.Term(); + } + private void Highlight_item(Gflucene_doc_data item) { + // init hpg + Xoa_ttl page_ttl = wiki.Ttl_parse(item.page_full_db); + Xoa_url page_url = wiki.Utl__url_parser().Parse(page_ttl.Full_db()); + hpg.Ctor_by_hview(wiki, page_url, page_ttl, item.page_id); + + // load db.html.html + wiki.Html__hdump_mgr().Load_mgr().Load_by_xowh(hpg, page_ttl, false); // don't load categories for perf reasons + byte[] html = hpg.Db().Html().Html_bry(); + html = extractor.Extract(html); + item.body = String_.new_u8(html); + + // loop pages + int page_id = item.page_id; + Gflucene_highlighter_item[] lines = highlighter_mgr.Exec(searcher_data, item); + for (Gflucene_highlighter_item line : lines) { + ui.Send_line_add(true, wiki_args.show_all_snips, searcher_args.qry_id, wiki.Domain_bry(), page_id, line.num, Bry_.new_u8(line.text)); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__highlight)) this.Highlight_list(); + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String Invk__highlight = "highlight"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/gflucenes/Xofulltext_searcher__lucene.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/gflucenes/Xofulltext_searcher__lucene.java index a27517de8..6dc0cd259 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/gflucenes/Xofulltext_searcher__lucene.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/gflucenes/Xofulltext_searcher__lucene.java @@ -13,3 +13,86 @@ 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.addons.wikis.fulltexts.searchers.mgrs.gflucenes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.*; +import gplx.gflucene.*; +import gplx.gflucene.core.*; +import gplx.gflucene.indexers.*; +import gplx.gflucene.searchers.*; +import gplx.gflucene.highlighters.*; +import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.uis.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.caches.*; +public class Xofulltext_searcher__lucene implements Xofulltext_searcher { + private final Gflucene_searcher_mgr searcher = new Gflucene_searcher_mgr(); + public boolean Type_is_lucene() {return true;} + public void Search(Xofulltext_searcher_ui ui, Xow_wiki wiki, Xofulltext_cache_qry qry, Xofulltext_args_qry args, Xofulltext_args_wiki wiki_args) { + // create lists + Ordered_hash full_list = Ordered_hash_.New(); + Ordered_hash temp_list = Ordered_hash_.New(); + Ordered_hash page_list = qry.Pages(); + + // init searcher with wiki + Gflucene_analyzer_data analyzer_data = Gflucene_analyzer_data.New_data_from_locale(wiki.Lang().Key_str()); + searcher.Init(new Gflucene_index_data + ( analyzer_data + , Xosearch_fulltext_addon.Get_index_dir(wiki).Xto_api())); + + // get page_load vars + Xowd_page_itm tmp_page_row = new Xowd_page_itm(); + Xowd_page_tbl page_tbl = wiki.Data__core_mgr().Db__core().Tbl__page(); + + // exec search + int needed_bgn = wiki_args.bgn; + if (needed_bgn < page_list.Len()) needed_bgn = page_list.Len(); + int needed_end = wiki_args.bgn + wiki_args.len; + int needed_len = needed_end - needed_bgn; + int found = 0; + int threshold = 0; + Gflucene_searcher_qry searcher_data = new Gflucene_searcher_qry(String_.new_u8(args.search_text), 100); + while (found < needed_len) { + if (args.Canceled()) return; + + threshold += wiki_args.len; + searcher_data.match_max = threshold; + searcher.Exec(temp_list, searcher_data); + + int temp_list_len = temp_list.Len(); + for (int i = 0; i < temp_list_len; i++) { + if (args.Canceled()) return; + Gflucene_doc_data doc_data = (Gflucene_doc_data)temp_list.Get_at(i); + if (!page_list.Has(doc_data.page_id)) { + // load page + if (!page_tbl.Select_by_id(tmp_page_row, doc_data.page_id)) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "searcher.lucene: could not find page; page_id=~{0}", doc_data.page_id); + continue; + } + + // make page_ttl + Xoa_ttl page_ttl = wiki.Ttl_parse(tmp_page_row.Ns_id(), tmp_page_row.Ttl_page_db()); + doc_data.ns_id = tmp_page_row.Ns_id(); + doc_data.page_full_db = page_ttl.Full_db(); + + if (!wiki_args.ns_hash.Has(doc_data.ns_id)) continue; + + // call page doc_data + Xofulltext_searcher_page page = new Xofulltext_searcher_page(args.qry_id, wiki.Domain_bry(), doc_data.page_id, doc_data.page_full_db, wiki_args.expand_snips); + ui.Send_page_add(page); + + full_list.Add(doc_data.page_id, doc_data); + found++; + if (found >= needed_len) break; + } + } + temp_list.Clear(); + } + + // term + searcher.Term(); + + ui.Send_wiki_update(wiki.Domain_bry(), page_list.Len(), -1); + + // create highlighter thread and launch it + Xofulltext_highlighter_mgr highlighter_mgr = new Xofulltext_highlighter_mgr(ui, wiki, args, wiki_args, analyzer_data, searcher_data, full_list); + highlighter_mgr.Highlight_list(); // NOTE: do not launch in separate thread, else multiple wikis will have strange race conditions + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/uis/Xofulltext_searcher_line.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/uis/Xofulltext_searcher_line.java index a27517de8..964152590 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/uis/Xofulltext_searcher_line.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/uis/Xofulltext_searcher_line.java @@ -13,3 +13,16 @@ 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.addons.wikis.fulltexts.searchers.mgrs.uis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.*; +public class Xofulltext_searcher_line { + public Xofulltext_searcher_line(byte[] wiki_domain, int page_id, int found_idx, byte[] excerpt) { + this.wiki_domain = wiki_domain; + this.page_id = page_id; + this.found_idx = found_idx; + this.excerpt = excerpt; + } + public byte[] Wiki_domain() {return wiki_domain;} private final byte[] wiki_domain; + public int Page_id() {return page_id;} private final int page_id; + public int Found_idx() {return found_idx;} private final int found_idx; + public byte[] Excerpt() {return excerpt;} private final byte[] excerpt; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/uis/Xofulltext_searcher_page.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/uis/Xofulltext_searcher_page.java index a27517de8..befb485a5 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/uis/Xofulltext_searcher_page.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/uis/Xofulltext_searcher_page.java @@ -13,3 +13,18 @@ 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.addons.wikis.fulltexts.searchers.mgrs.uis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.*; +public class Xofulltext_searcher_page { + public Xofulltext_searcher_page(int query_id, byte[] wiki_domain, int page_id, byte[] page_ttl, boolean expand_snips) { + this.query_id = query_id; + this.wiki_domain = wiki_domain; + this.page_id = page_id; + this.page_ttl = page_ttl; + this.expand_snips = expand_snips; + } + public int Query_id() {return query_id;} private final int query_id; + public byte[] Wiki_domain() {return wiki_domain;} private final byte[] wiki_domain; + public int Page_id() {return page_id;} private final int page_id; + public byte[] Page_ttl() {return page_ttl;} private final byte[] page_ttl; + public boolean Expand_snips() {return expand_snips;} private final boolean expand_snips; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/uis/Xofulltext_searcher_ui.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/uis/Xofulltext_searcher_ui.java index a27517de8..026ea2d83 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/uis/Xofulltext_searcher_ui.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/mgrs/uis/Xofulltext_searcher_ui.java @@ -13,3 +13,65 @@ 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.addons.wikis.fulltexts.searchers.mgrs.uis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.*; +import gplx.xowa.guis.cbks.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.caches.*; +public class Xofulltext_searcher_ui { + private final Xog_cbk_mgr cbk_mgr; + private final Xog_cbk_trg cbk_trg; + private final Xofulltext_cache_mgr cache_mgr; + public Xofulltext_searcher_ui(Xofulltext_cache_mgr cache_mgr, Xog_cbk_mgr cbk_mgr, Xog_cbk_trg cbk_trg) { + this.cache_mgr = cache_mgr; + this.cbk_mgr = cbk_mgr; + this.cbk_trg = cbk_trg; + } + public void Send_wiki_add(boolean type_is_lucene, boolean expand_pages, byte[] wiki, int rng_bgn, int rng_end) { + cbk_mgr.Send_json(cbk_trg, "xo.fulltext_searcher.results__wiki__add__recv", gplx.core.gfobjs.Gfobj_nde.New() + .Add_bry("wiki", wiki) + .Add_bool("type_is_lucene", type_is_lucene) + .Add_bool("expand_pages", expand_pages) + .Add_int("rng_bgn", rng_bgn + List_adp_.Base1) + .Add_int("rng_end", rng_end) + ); + } + public void Send_wiki_update(byte[] wiki, int found, int searched) { + cbk_mgr.Send_json(cbk_trg, "xo.fulltext_searcher.results__wiki__update__recv", gplx.core.gfobjs.Gfobj_nde.New() + .Add_bry("wiki", wiki) + .Add_int("found", found) + .Add_int("searched", searched) + ); + } + public void Send_page_add(Xofulltext_searcher_page page) { + cbk_mgr.Send_json(cbk_trg, "xo.fulltext_searcher.results__page__add__recv", gplx.core.gfobjs.Gfobj_nde.New() + .Add_int("query_id", page.Query_id()) + .Add_bry("wiki", page.Wiki_domain()) + .Add_int("page_id", page.Page_id()) + .Add_bry("page_ttl", page.Page_ttl()) + .Add_bool("expand_snips", page.Expand_snips()) + ); + cache_mgr.Add_page(page.Query_id(), page.Wiki_domain(), page.Page_id(), page.Page_ttl()); + } + public void Send_line_add(boolean add_to_cache, boolean show_all_snips, int qry_id, byte[] wiki_domain, int page_id, int line_sort_order, byte[] line_html) { + if (add_to_cache) + cache_mgr.Add_line(qry_id, wiki_domain, page_id, line_sort_order, line_html); + + line_sort_order += List_adp_.Base1; // NOTE: increment after cache_mgr + if (line_sort_order == 1 || show_all_snips) { + cbk_mgr.Send_json(cbk_trg, "xo.fulltext_searcher.results__line__add__recv", gplx.core.gfobjs.Gfobj_nde.New() + .Add_bry("wiki", wiki_domain) + .Add_int("page_id", page_id) + .Add_int("line", line_sort_order) + .Add_bry("html", line_html) + ); + } + cbk_mgr.Send_json(cbk_trg, "xo.fulltext_searcher.results__page__update__recv", gplx.core.gfobjs.Gfobj_nde.New() + .Add_bry("wiki", wiki_domain) + .Add_int("page_id", page_id) + .Add_int("found", line_sort_order) + .Add_bool("show_all_snips", show_all_snips) + ); + } + public void Send_done() { + cbk_mgr.Send_json(cbk_trg, "xo.fulltext_searcher.results__done__recv", gplx.core.gfobjs.Gfobj_nde.New()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/specials/Xofulltext_searcher_html.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/specials/Xofulltext_searcher_html.java index a27517de8..1d65424d9 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/specials/Xofulltext_searcher_html.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/specials/Xofulltext_searcher_html.java @@ -13,3 +13,105 @@ 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.addons.wikis.fulltexts.searchers.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; +import gplx.core.net.qargs.*; +import gplx.dbs.*; +import gplx.xowa.specials.*; import gplx.langs.mustaches.*; import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.tags.*; +import gplx.xowa.addons.apps.cfgs.*; +import gplx.xowa.parsers.amps.*; +import gplx.xowa.htmls.core.htmls.*; +class Xofulltext_searcher_html extends Xow_special_wtr__base implements Mustache_doc_itm { + private final Hash_adp props = Hash_adp_.New(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + public Xofulltext_searcher_html(Xocfg_mgr cfg_mgr, Gfo_qarg_mgr url_args, Xow_wiki wiki, Guid_adp page_guid, boolean lucene_exists) { + String search_text = url_args.Read_str_or("search", ""); + search_text = String_.Replace(search_text, "_", " "); // xofulltext_searcher.js chains multiple words with "_"; convert back to space + props.Add("qarg_search", Xoh_html_wtr_escaper.Escape_str(Xop_amp_mgr.Instance, tmp_bfr, search_text)); + props.Add("page_guid", page_guid.To_str()); + props.Add("cur_wiki", wiki.Domain_str()); + + // enabled + boolean app_is_drd = gplx.core.envs.Op_sys.Cur().Tid_is_drd(); + props.Add("app_is_drd", app_is_drd); // for options button + props.Add("disabled", app_is_drd && !lucene_exists); + + // options:strings + props_Add_str(cfg_mgr, url_args, "wikis" , wiki.Domain_str(), false); + props_Add_str(cfg_mgr, url_args, "ns_ids", "0"); + props_Add_str(cfg_mgr, url_args, "limits", "25"); + props_Add_str(cfg_mgr, url_args, "offsets", "0"); + props_Add_str(cfg_mgr, url_args, "expand_pages", "y"); + props_Add_str(cfg_mgr, url_args, "expand_snips", "n"); + props_Add_str(cfg_mgr, url_args, "show_all_snips", "y"); + + // options:bools + props_Add_bool(cfg_mgr, url_args, "xowa.addon.fulltext_search.options", "expand_options"); + props_Add_bool(cfg_mgr, url_args, "xowa.addon.fulltext_search.special", "case_match"); + props_Add_bool(cfg_mgr, url_args, "xowa.addon.fulltext_search.special", "auto_wildcard_bgn"); + props_Add_bool(cfg_mgr, url_args, "xowa.addon.fulltext_search.special", "auto_wildcard_end"); + } + private void props_Add_str(Xocfg_mgr cfg_mgr, Gfo_qarg_mgr url_args, String key, String else_val) { + props_Add_str(cfg_mgr, url_args, key, else_val, true); + } + private void props_Add_str(Xocfg_mgr cfg_mgr, Gfo_qarg_mgr url_args, String key, String else_val, boolean use_cfg) { + String dflt_val = else_val; + if (use_cfg) { + String cfg_key = "xowa.addon.fulltext_search.special." + key; + dflt_val = cfg_mgr.Get_str_app_or(cfg_key, else_val); + } + props.Add("dflt_" + key, dflt_val); + props.Add("qarg_" + key, url_args.Read_str_or(key, dflt_val)); + } + private void props_Add_bool(Xocfg_mgr cfg_mgr, Gfo_qarg_mgr url_args, String cfg_grp, String key) { + String cfg_key = cfg_grp + "." + key; + boolean cfg_val = cfg_mgr.Get_bool_app_or(cfg_key, false); + props.Add(key, cfg_val); + } + @Override protected Io_url Get_addon_dir(Xoa_app app) {return Addon_dir(app);} + @Override protected Io_url Get_mustache_fil(Io_url addon_dir) {return addon_dir.GenSubFil_nest("bin", "xofulltext_searcher.main.template.html");} + @Override protected Mustache_doc_itm Bld_mustache_root(Xoa_app app) {return this;} + @Override protected void Bld_tags(Xoa_app app, Io_url addon_dir, Xopage_html_data page_data) { + Xopg_tag_mgr head_tags = page_data.Head_tags(); + Xopg_tag_wtr_.Add__xoelem (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xocss (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xohelp (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xolog (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xoajax (head_tags, app.Fsys_mgr().Http_root(), app); + Xopg_tag_wtr_.Add__xotmpl (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__jquery (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xonotify (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__mustache (head_tags, app.Fsys_mgr().Http_root()); + Xopg_alertify_.Add_tags (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__ooui (head_tags, app.Fsys_mgr().Http_root()); + + head_tags.Add(Xopg_tag_itm.New_css_file(addon_dir.GenSubFil_nest("bin", "xofulltext_searcher.css"))); + head_tags.Add(Xopg_tag_itm.New_js_file(addon_dir.GenSubFil_nest("bin", "xofulltext_searcher.js"))); + head_tags.Add(Xopg_tag_itm.New_htm_frag(addon_dir.GenSubFil_nest("bin", "xofulltext_searcher.wiki.template.html"), "xofts.wiki")); + head_tags.Add(Xopg_tag_itm.New_htm_frag(addon_dir.GenSubFil_nest("bin", "xofulltext_searcher.page.template.html"), "xofts.page")); + head_tags.Add(Xopg_tag_itm.New_htm_frag(addon_dir.GenSubFil_nest("bin", "xofulltext_searcher.snip.template.html"), "xofts.snip")); + + page_data.Js_enabled_y_(); + } + + public boolean Mustache__write(String key, Mustache_bfr bfr) { + String val = (String)props.Get_by(key); + if (val == null) + return false; + else { + bfr.Add_str_u8(val); + return true; + } + } + public Mustache_doc_itm[] Mustache__subs(String key) { + Object val_obj = props.Get_by(key); + if (val_obj == null) { + return Mustache_doc_itm_.Ary__empty; + } + else { + return Mustache_doc_itm_.Ary__bool((boolean)val_obj); + } + } + public static Io_url Addon_dir(Xoa_app app) { + return app.Fsys_mgr().Http_root().GenSubDir_nest("bin", "any", "xowa", "addon", "wiki", "fulltext", "searcher"); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/specials/Xofulltext_searcher_special.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/specials/Xofulltext_searcher_special.java index a27517de8..8cb0067f9 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/specials/Xofulltext_searcher_special.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/specials/Xofulltext_searcher_special.java @@ -13,3 +13,36 @@ 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.addons.wikis.fulltexts.searchers.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; +import gplx.xowa.specials.*; import gplx.core.net.qargs.*; +import gplx.xowa.wikis.domains.*; +import gplx.xowa.addons.apps.cfgs.*; +public class Xofulltext_searcher_special implements Xow_special_page { + public void Special__gen(Xow_wiki wiki, Xoa_page page, Xoa_url url, Xoa_ttl ttl) { + // get url_args, cfg_mgr + Gfo_qarg_mgr url_args = new Gfo_qarg_mgr().Init(url.Qargs_ary()); + Xocfg_mgr cfg_mgr = wiki.App().Cfg(); + + // redirect to Special:Search if index doesn't exist + boolean lucene_exists = Io_mgr.Instance.ExistsDir(gplx.xowa.addons.wikis.fulltexts.Xosearch_fulltext_addon.Get_index_dir(wiki)); + if ( !gplx.core.envs.Op_sys.Cur().Tid_is_drd() // desktop + && !lucene_exists // no lucene index + && !Int_.In(wiki.Domain_tid(), Xow_domain_tid_.Tid__home, Xow_domain_tid_.Tid__other) // not home or personal wiki + && !Bry_.Eq(url_args.Read_bry_or_null("force"), Bool_.Y_bry) // force=y is not present in url + && wiki.App().Cfg().Get_bool_app_or(gplx.xowa.apps.cfgs.Xocfg_win.Cfg__search__fallback_to_title, true) // cfg.fallback is enabled + ) { + Xoa_ttl redirect_ttl = wiki.Ttl_parse(Bry_.new_u8("Special:Search?fulltext=y&search=" + url_args.Read_str_or("search", ""))); + Xoa_url redirect_url = wiki.Utl__url_parser().Parse(redirect_ttl.Full_db()); + page.Redirect_trail().Itms__add__article(redirect_url, redirect_ttl, null); + return; + } + + // create page + Xofulltext_searcher_html html = new Xofulltext_searcher_html(cfg_mgr, url_args, wiki, page.Page_guid(), lucene_exists); + html.Bld_page_by_mustache(wiki.App(), page, this); + } + Xofulltext_searcher_special(Xow_special_meta special__meta) {this.special__meta = special__meta;} + public Xow_special_meta Special__meta() {return special__meta;} private final Xow_special_meta special__meta; + public Xow_special_page Special__clone() {return this;} + public static final Xow_special_page Prototype = new Xofulltext_searcher_special(Xow_special_meta.New_xo("XowaSearch", "Search")); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/svcs/Xofulltext_searcher_bridge.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/svcs/Xofulltext_searcher_bridge.java index a27517de8..dad7930c2 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/svcs/Xofulltext_searcher_bridge.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/svcs/Xofulltext_searcher_bridge.java @@ -13,3 +13,35 @@ 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.addons.wikis.fulltexts.searchers.svcs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; +import gplx.langs.jsons.*; +import gplx.xowa.htmls.bridges.*; +public class Xofulltext_searcher_bridge implements Bridge_cmd_itm { + private Xofulltext_searcher_svc svc; + public void Init_by_app(Xoa_app app) { + this.svc = new Xofulltext_searcher_svc(app); + } + public String Exec(Json_nde data) { + byte proc_id = proc_hash.Get_as_byte_or(data.Get_as_bry_or(Bridge_cmd_mgr.Msg__proc, null), Byte_ascii.Max_7_bit); + Json_nde args = data.Get_kv(Bridge_cmd_mgr.Msg__args).Val_as_nde(); + switch (proc_id) { + case Proc__search_run: svc.Search_run(args); break; + case Proc__search_cxl: svc.Search_cxl(args); break; + case Proc__options_save: svc.Options_save(args); break; + case Proc__snips_show_all: svc.Snips_show_all(args); break; + default: throw Err_.new_unhandled_default(proc_id); + } + return ""; + } + + private static final byte Proc__search_run = 0, Proc__search_cxl = 1, Proc__options_save = 2, Proc__snips_show_all = 3; + private static final Hash_adp_bry proc_hash = Hash_adp_bry.cs() + .Add_str_byte("search_run" , Proc__search_run) + .Add_str_byte("search_cxl" , Proc__search_cxl) + .Add_str_byte("options_save" , Proc__options_save) + .Add_str_byte("snips_show_all" , Proc__snips_show_all) + ; + + public byte[] Key() {return BRIDGE_KEY;} public static final byte[] BRIDGE_KEY = Bry_.new_a7("xowa.wiki.fulltext.searcher"); + public static final Xofulltext_searcher_bridge Prototype = new Xofulltext_searcher_bridge(); Xofulltext_searcher_bridge() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/svcs/Xofulltext_searcher_svc.java b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/svcs/Xofulltext_searcher_svc.java index a27517de8..325be8d79 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/svcs/Xofulltext_searcher_svc.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/fulltexts/searchers/svcs/Xofulltext_searcher_svc.java @@ -13,3 +13,175 @@ 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.addons.wikis.fulltexts.searchers.svcs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.fulltexts.*; import gplx.xowa.addons.wikis.fulltexts.searchers.*; +import gplx.core.btries.*; +import gplx.langs.jsons.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.guis.cbks.*; +import gplx.xowa.addons.apps.cfgs.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.specials.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.caches.*; +import gplx.xowa.addons.wikis.searchs.searchers.crts.*; +import gplx.xowa.addons.wikis.searchs.searchers.crts.visitors.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.uis.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.gflucenes.*; +import gplx.xowa.addons.wikis.fulltexts.searchers.mgrs.brutes.*; +class Xofulltext_searcher_svc implements Gfo_invk { + private final Xoa_app app; + private final Ordered_hash wkr_hash = Ordered_hash_.New(); + public Xofulltext_searcher_svc(Xoa_app app) { + this.app = app; + } + public void Search_cxl(Json_nde args) {this.Search_cxl(args.Get_as_str("page_guid"));} + private void Search_cxl(String page_guid) { + Xofulltext_args_qry prv_args = (Xofulltext_args_qry)wkr_hash.Get_by(page_guid); + if (prv_args != null) { + prv_args.Cancel(); + synchronized (wkr_hash) { + wkr_hash.Del(page_guid); + } + } + } + public void Options_save(Json_nde args) { + Xocfg_mgr cfg_mgr = app.Cfg(); + cfg_mgr.Set_bool_app("xowa.addon.fulltext_search.options.expand_options", args.Get_as_bool_or("expand_options", false)); + } + public void Search_run(Json_nde args) { + // get search_args + Xofulltext_args_qry search_args = Xofulltext_args_qry.New_by_json(args); + search_args.cache_mgr = this.Cache_mgr(); + + // cancel any existing searches + this.Search_cxl(search_args.page_guid); + Compress_cache(wkr_hash); + synchronized (wkr_hash) { + wkr_hash.Add(search_args.page_guid, search_args); + } + + // launch thread + gplx.core.threads.Thread_adp_.Start_by_val("search", Cancelable_.Never, this, Invk__search, search_args); + } + private void Search(Xofulltext_args_qry args) { + // create ui + Xofulltext_cache_mgr cache_mgr = args.cache_mgr; + Xofulltext_searcher_ui ui = new Xofulltext_searcher_ui(cache_mgr, app.Gui__cbk_mgr(), new Xog_cbk_trg(args.page_guid)); + + try { + // loop wikis + for (Xofulltext_args_wiki wiki_args : args.wikis_ary) { + Search_wiki(args, cache_mgr, ui, wiki_args); + } + } catch (Exception exc) { + if (app.Tid_is_edit()) + ((Xoae_app)app).Gui_mgr().Kit().Ask_ok("", "", Err_.Message_gplx_full(exc)); + } + } + private void Search_wiki(Xofulltext_args_qry args, Xofulltext_cache_mgr cache_mgr, Xofulltext_searcher_ui ui, Xofulltext_args_wiki wiki_args) { + try { + // get wiki and notify + byte[] wiki_domain = wiki_args.wiki; + Xow_wiki wiki = app.Wiki_mgri().Get_by_or_make_init_y(wiki_domain); + Xofulltext_searcher searcher = Get_searcher(wiki); + ui.Send_wiki_add(searcher.Type_is_lucene(), wiki_args.expand_pages, wiki_domain, wiki_args.bgn, wiki_args.end()); + + // try to get from cache + byte[] qry_key = args.Qry_key(wiki_domain, wiki_args.ns_ids); + int qry_id = cache_mgr.Ids__get_or_neg1(qry_key); + Xofulltext_cache_qry qry = null; + if (qry_id == -1) { + qry_id = cache_mgr.Ids__next(); + cache_mgr.Add(qry_id, qry_key); + qry = cache_mgr.Get_or_null(qry_id); + } + else { + qry = cache_mgr.Get_or_null(qry_id); + if (qry != null) { + boolean all_shown = Display_cached_qry(args, ui, wiki, qry, qry_id, wiki_args); + if (all_shown || qry.done) { + ui.Send_done(); + return; + } + } + } + args.qry_id = qry_id; + + searcher.Search(ui, wiki, qry, args, wiki_args); + ui.Send_done(); + } + catch (Exception exc) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to search_wiki; err=~{0}", Err_.Message_gplx_log(exc)); + } + } + private boolean Display_cached_qry(Xofulltext_args_qry args, Xofulltext_searcher_ui ui, Xow_wiki wiki, Xofulltext_cache_qry qry, int qry_id, Xofulltext_args_wiki wiki_args) { + int bgn = wiki_args.bgn; + int len = wiki_args.len; + int end = bgn + len; + int max = qry.Pages().Len(); + for (int i = bgn; i < end; i++) { + if (i >= max) return false; // more pages requested than available + Xofulltext_cache_page page = (Xofulltext_cache_page)qry.Pages().Get_at(i); + ui.Send_page_add(new Xofulltext_searcher_page(qry_id, wiki.Domain_bry(), page.Page_id(), page.Page_ttl(), wiki_args.expand_snips)); + + // loop lines + int lines_len = page.Lines().Len(); + for (int j = 0; j < lines_len; j++) { + Xofulltext_cache_line line = (Xofulltext_cache_line)page.Lines().Get_at(j); + ui.Send_line_add(false, wiki_args.show_all_snips, qry_id, wiki.Domain_bry(), page.Page_id(), line.Line_seq(), line.Line_html()); + } + } + return true; + } + + public void Snips_show_all(Json_nde args) { + Snips_show_all(args.Get_as_int("qry_id"), args.Get_as_bry("wiki"), args.Get_as_int("page_id"), args.Get_as_str("page_guid")); + } + private void Snips_show_all(int qry_id, byte[] wiki_bry, int page_id, String page_guid) { + Xofulltext_cache_mgr cache_mgr = this.Cache_mgr(); + Xofulltext_searcher_ui searcher_ui = new Xofulltext_searcher_ui(cache_mgr, app.Gui__cbk_mgr(), new Xog_cbk_trg(page_guid)); + + Xofulltext_cache_line[] lines = cache_mgr.Get_lines_rest(qry_id, wiki_bry, page_id); + for (Xofulltext_cache_line line : lines) { + searcher_ui.Send_line_add(false, true, qry_id, wiki_bry, page_id, line.Line_seq(), line.Line_html()); + } + } + private Xofulltext_searcher Get_searcher(Xow_wiki wiki) { + if (Io_mgr.Instance.ExistsDir(Xosearch_fulltext_addon.Get_index_dir(wiki))) { + return new Xofulltext_searcher__lucene(); + } + else { + return new Xofulltext_searcher__brute(); + } + } + private Xofulltext_cache_mgr Cache_mgr() { + return Xosearch_fulltext_addon.Get_by_app(app).Cache_mgr(); + } + private static void Compress_cache(Ordered_hash wkr_hash) { + int max = 8; // cache no more than 8 tabs worth of queries + int len = wkr_hash.Len(); + if (len > max) { + synchronized (wkr_hash) { + // create list for deleted items; in general, this list will never be more than 1 + List_adp deleted = List_adp_.New(); + + int bgn = len - max; + for (int i = 0; i < bgn; i++) { + Xofulltext_args_qry args = (Xofulltext_args_qry)wkr_hash.Get_at(i); + deleted.Add(args); + } + + len = deleted.Len(); + for (int i = 0; i < len; i++) { + Xofulltext_args_qry args = (Xofulltext_args_qry)deleted.Get_at(i); + wkr_hash.Del(args.page_guid); + } + } + } + } + + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__search)) this.Search((Xofulltext_args_qry)m.ReadObj("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk__search = "search"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/bldrs/Xob_css_cmd.java b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/bldrs/Xob_css_cmd.java index a27517de8..9f471af5d 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/bldrs/Xob_css_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/bldrs/Xob_css_cmd.java @@ -13,3 +13,40 @@ 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.addons.wikis.htmls.css.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.htmls.*; import gplx.xowa.addons.wikis.htmls.css.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.xowa.addons.wikis.htmls.css.mgrs.*; +public class Xob_css_cmd implements Xob_cmd { + private final Xob_bldr bldr; private final Xowe_wiki wiki; private final Gfo_usr_dlg usr_dlg; + private Io_url css_dir; private String css_key; + public Xob_css_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.bldr = bldr; this.wiki = wiki; this.usr_dlg = wiki.Appe().Usr_dlg();} + public String Cmd_key() {return Xob_cmd_keys.Key_text_css;} + public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return null;} + public void Cmd_init(Xob_bldr bldr) { + if (css_dir == null) css_dir = wiki.App().Fsys_mgr().Wiki_css_dir(wiki.Domain_str()); // EX: /xowa/user/anonymous/wiki/en.wikipedia.org + if (css_key == null) css_key = Xowd_css_core_mgr.Key_default; + } + public void Cmd_run() { + usr_dlg.Plog_many("", "", Cmd_key() + ":bgn;"); + bldr.App().Html__css_installer().Install(wiki, null); // download from wmf + usr_dlg.Plog_many("", "", Cmd_key() + ":css_dir; dir=~{0}", css_dir.Raw()); + wiki.Init_db_mgr(); // NOTE: must follow Install b/c Init_assert also calls Install; else will download any css from db + Xow_db_file core_db = wiki.Db_mgr_as_sql().Core_data_mgr().Db__core(); + core_db.Conn().Txn_bgn("bldr__css"); + core_db.Tbl__css_core().Create_tbl(); + core_db.Tbl__css_file().Create_tbl(); + gplx.xowa.addons.wikis.htmls.css.mgrs.Xowd_css_core_mgr.Set(core_db.Tbl__css_core(), core_db.Tbl__css_file(), css_dir, css_key); + core_db.Tbl__cfg().Upsert_yn(Xowd_cfg_key_.Grp__wiki_schema, Xow_db_file_schema_props.Key__tbl_css_core, Bool_.Y); + core_db.Conn().Txn_end(); + usr_dlg.Plog_many("", "", Cmd_key() + ":end;"); + } + public void Cmd_bgn(Xob_bldr bldr) {} + public void Cmd_end() {} + public void Cmd_term() {} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_css_dir_)) css_dir = m.ReadIoUrl("v"); + else if (ctx.Match(k, Invk_css_key_)) css_key = m.ReadStr("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_css_dir_ = "css_dir_", Invk_css_key_ = "css_key_"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Css_db_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Css_db_mgr.java index a27517de8..ef07e354b 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Css_db_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Css_db_mgr.java @@ -13,3 +13,36 @@ 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.addons.wikis.htmls.css.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.htmls.*; import gplx.xowa.addons.wikis.htmls.css.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.*; +public class Css_db_mgr { + private final Xow_wiki wiki; + public Css_db_mgr(Xow_wiki wiki) { + this.wiki = wiki; + } + public Db_conn Conn() {return conn;} private Db_conn conn; + public Xowd_css_core_tbl Tbl__core() {return tbl__core;} private Xowd_css_core_tbl tbl__core; + public Xowd_css_file_tbl Tbl__file() {return tbl__file;} private Xowd_css_file_tbl tbl__file; + public Css_db_mgr Init() { + this.conn = Get_or_new(wiki); + this.tbl__core = new Xowd_css_core_tbl(conn); + this.tbl__file = new Xowd_css_file_tbl(conn); + if (!conn.Meta_tbl_exists(tbl__core.Tbl_name())) { + tbl__core.Create_tbl(); + tbl__file.Create_tbl(); + } + return this; + } + + private static Db_conn Get_or_new(Xow_wiki wiki) { + int layout_text = wiki.Data__core_mgr().Db__core().Db_props().Layout_text().Tid(); + Io_url url = null; + switch (layout_text) { + case Xow_db_layout.Tid__all: url = wiki.Data__core_mgr().Db__core().Url(); break; + case Xow_db_layout.Tid__few: url = wiki.Fsys_mgr().Root_dir().GenSubFil(String_.Format("{0}-data.xowa", wiki.Domain_str())); break; + case Xow_db_layout.Tid__lot: url = wiki.Fsys_mgr().Root_dir().GenSubFil(String_.Format("{0}-xtn.css.xowa", wiki.Domain_str())); break; + default: throw Err_.new_unhandled(layout_text); + } + return Db_conn_bldr.Instance.Get_or_autocreate(true, url); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Xowd_css_core_itm.java b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Xowd_css_core_itm.java index a27517de8..118e5c487 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Xowd_css_core_itm.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Xowd_css_core_itm.java @@ -13,3 +13,12 @@ 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.addons.wikis.htmls.css.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.htmls.*; import gplx.xowa.addons.wikis.htmls.css.*; +public class Xowd_css_core_itm { + public Xowd_css_core_itm(int id, String key, DateAdp updated_on) { + this.id = id; this.key = key; this.updated_on = updated_on; + } + public int Id() {return id;} private final int id; + public String Key() {return key;} private final String key; + public DateAdp Updated_on() {return updated_on;} private final DateAdp updated_on; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Xowd_css_core_tbl.java b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Xowd_css_core_tbl.java index a27517de8..042f4d23c 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Xowd_css_core_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Xowd_css_core_tbl.java @@ -13,3 +13,59 @@ 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.addons.wikis.htmls.css.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.htmls.*; import gplx.xowa.addons.wikis.htmls.css.*; +import gplx.dbs.*; +public class Xowd_css_core_tbl implements Rls_able { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_id, fld_key, fld_updated_on; + public Xowd_css_core_tbl(Db_conn conn) { + this.conn = conn; + this.fld_id = flds.Add_int_pkey_autonum("css_id"); + this.fld_key = flds.Add_str("css_key", 255); + this.fld_updated_on = flds.Add_str("css_updated_on", 20); + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public String Tbl_name() {return tbl_name;} private final String tbl_name = "css_core"; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_unique_by_tbl(tbl_name, "main", fld_key)));} + public void Rls() {} + public int Insert(String key, DateAdp updated_on) { + Db_stmt stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Val_str(fld_key, key).Val_str(fld_updated_on, updated_on.XtoStr_fmt_yyyyMMdd_HHmmss()).Exec_insert(); + return Select_id_by_key(key); + } + public void Update(int id, String key, DateAdp updated_on) { + Db_stmt stmt_update = conn.Stmt_update_exclude(tbl_name, flds, fld_id); + stmt_update.Val_str(fld_key, key).Val_str(fld_updated_on, updated_on.XtoStr_fmt_yyyyMMdd_HHmmss()).Crt_int(fld_id, id).Exec_update(); + } + public Xowd_css_core_itm Select_by_key(String key) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_key).Crt_str(fld_key, key).Exec_select__rls_auto(); + try {return rdr.Move_next() ? new_itm(rdr) : null;} + finally {rdr.Rls();} + } + public int Select_id_by_key(String key) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_key).Crt_str(fld_key, key).Exec_select__rls_auto(); + try {return rdr.Move_next() ? rdr.Read_int(fld_id) : Id_null;} + finally {rdr.Rls();} + } + public Xowd_css_core_itm[] Select_all() { // TEST: + Db_stmt stmt = conn.Stmt_select(tbl_name, flds); + return Select_by_stmt(stmt); + } + private Xowd_css_core_itm[] Select_by_stmt(Db_stmt stmt) { + List_adp rv = List_adp_.New(); + Db_rdr rdr = stmt.Exec_select__rls_auto(); + try { + while (rdr.Move_next()) + rv.Add(new_itm(rdr)); + } finally {rdr.Rls();} + return (Xowd_css_core_itm[])rv.To_ary_and_clear(Xowd_css_core_itm.class); + } + public void Delete_all() { + conn.Stmt_delete(tbl_name).Exec_delete(); + } + private Xowd_css_core_itm new_itm(Db_rdr rdr) { + return new Xowd_css_core_itm(rdr.Read_int(fld_id), rdr.Read_str(fld_key), rdr.Read_date_by_str(fld_updated_on)); + } + public static final int Id_null = -1; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Xowd_css_file_itm.java b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Xowd_css_file_itm.java index a27517de8..ff2519bed 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Xowd_css_file_itm.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Xowd_css_file_itm.java @@ -13,3 +13,10 @@ 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.addons.wikis.htmls.css.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.htmls.*; import gplx.xowa.addons.wikis.htmls.css.*; +public class Xowd_css_file_itm { + public Xowd_css_file_itm(int css_id, String path, byte[] data) {this.css_id = css_id; this.path = path; this.data = data;} + public int Css_id() {return css_id;} private final int css_id; + public String Path() {return path;} private final String path; + public byte[] Data() {return data;} private final byte[] data; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Xowd_css_file_tbl.java b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Xowd_css_file_tbl.java index a27517de8..13fdaffcf 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Xowd_css_file_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/dbs/Xowd_css_file_tbl.java @@ -13,3 +13,49 @@ 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.addons.wikis.htmls.css.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.htmls.*; import gplx.xowa.addons.wikis.htmls.css.*; +import gplx.dbs.*; +public class Xowd_css_file_tbl implements Rls_able { + private final String tbl_name = "css_file"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_css_id, fld_path, fld_data; + private final Db_conn conn; private Db_stmt stmt_insert; + public Xowd_css_file_tbl(Db_conn conn) { + this.conn = conn; + fld_css_id = flds.Add_int("css_id"); + fld_path = flds.Add_str("file_path", 255); + fld_data = flds.Add_bry("file_data"); + conn.Rls_reg(this); + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + } + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Insert(int css_id, String path, byte[] data) { + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Clear().Val_int(fld_css_id, css_id).Val_str(fld_path, path).Val_bry(fld_data, data).Exec_insert(); + } + public void Delete(int css_id) { + conn.Stmt_delete(tbl_name, fld_css_id).Crt_int(fld_css_id, css_id).Exec_delete(); + } + public Xowd_css_file_itm[] Select_by_owner(int css_id) { + Db_stmt stmt = conn.Stmt_select(tbl_name, flds, fld_css_id).Crt_int(fld_css_id, css_id); + return Select_by_stmt(stmt); + } + public Xowd_css_file_itm[] Select_all() { // TEST: + Db_stmt stmt = conn.Stmt_select(tbl_name, flds); + return Select_by_stmt(stmt); + } + private Xowd_css_file_itm[] Select_by_stmt(Db_stmt stmt) { + List_adp rv = List_adp_.New(); + Db_rdr rdr = stmt.Exec_select__rls_auto(); + try { + while (rdr.Move_next()) + rv.Add(new_itm(rdr)); + } finally {rdr.Rls();} + return (Xowd_css_file_itm[])rv.To_ary_and_clear(Xowd_css_file_itm.class); + } + public void Delete_all() { + conn.Stmt_delete(tbl_name).Exec_delete(); + } + private Xowd_css_file_itm new_itm(Db_rdr rdr) {return new Xowd_css_file_itm(rdr.Read_int(fld_css_id), rdr.Read_str(fld_path), rdr.Read_bry(fld_data));} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/mgrs/Xob_css_status.java b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/mgrs/Xob_css_status.java index a27517de8..41fdb0df6 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/mgrs/Xob_css_status.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/mgrs/Xob_css_status.java @@ -13,3 +13,65 @@ 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.addons.wikis.htmls.css.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.htmls.*; import gplx.xowa.addons.wikis.htmls.css.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.xowa.bldrs.css.*; +public class Xob_css_status { + public int Update_tid() {return update_tid;} private int update_tid; + public boolean Fs_exists() {return fs_exists;} private boolean fs_exists; + public Io_url Fs_dir() {return fs_dir;} private Io_url fs_dir; + public boolean Db_exists() {return db_exists;} private boolean db_exists; + public void Fs_exists_(boolean fs_exists, Io_url fs_dir) { + this.fs_exists = fs_exists; + this.fs_dir = fs_dir; + } + public void Db_exists_(boolean db_exists) { + this.db_exists = db_exists; + } + public void Update_tid_none_y_() {this.update_tid = Update_tid_none;} + public void Update_tid_wmf_y_() {this.update_tid = Update_tid_wmf;} + public void Update_tid_db_y_() {this.update_tid = Update_tid_db;} + public static final int Update_tid_none = 0, Update_tid_db = 1, Update_tid_wmf = 2; + public static Xob_css_status Chk(Xow_wiki wiki, Io_url css_dir, String key) { + Xob_css_status rv = new Xob_css_status(); + Chk_fs(rv, wiki); + Chk_db(rv, wiki, css_dir); + return rv; + } + private static void Chk_fs(Xob_css_status rv, Xow_wiki wiki) { + Io_url css_dir = wiki.App().Fsys_mgr().Wiki_css_dir(wiki.Domain_str()); // EX: /xowa/user/anonymous/wiki/en.wikipedia.org/html/ + Io_url css_fil_wiki = css_dir.GenSubFil(Xoa_css_extractor.Css_wiki_name); // EX: /xowa/user/anonymous/wiki/en.wikipedia.org/html/xowa_wiki.css + boolean exists = Io_mgr.Instance.ExistsFil(css_fil_wiki); + rv.Fs_exists_(exists, css_dir); + } + private static void Chk_db(Xob_css_status rv, Xow_wiki wiki, Io_url css_dir) { + Xow_db_mgr core_db_mgr = wiki.Data__core_mgr(); + if ( core_db_mgr == null + || core_db_mgr.Props() != null + || !core_db_mgr.Props().Schema_is_1() + || core_db_mgr.Tbl__cfg().Select_yn_or(Xowd_cfg_key_.Grp__wiki_schema, Xow_db_file_schema_props.Key__tbl_css_core, Bool_.N) + ) { + rv.Db_exists_(false); + if (rv.Fs_exists()) + rv.Update_tid_none_y_(); // v1_db and fs_exists; don't do update; legacy behavior + else + rv.Update_tid_wmf_y_(); // v1_db and fs_missing; update from wmf; legacy behavior + } + if (rv.Fs_exists()) { + DateAdp fs_timestamp = Timestamp_load(css_dir); + DateAdp db_timestamp = Datetime_now.Get(); + if (db_timestamp.compareTo(fs_timestamp) == CompareAble_.More) + rv.Update_tid_db_y_(); // v2_db and later_version; update from db + else + rv.Update_tid_none_y_(); // v2_db and current version; noop + } + } + public static void Timestamp_save(Io_url css_dir, DateAdp time) { + Io_mgr.Instance.SaveFilStr(css_dir.GenSubFil(Timestamp_filename), time.XtoStr_fmt_yyyyMMdd_HHmmss()); + } + public static DateAdp Timestamp_load(Io_url css_dir) { + String rv = Io_mgr.Instance.LoadFilStr(css_dir.GenSubFil(Timestamp_filename)); + return rv == null ? DateAdp_.MinValue : DateAdp_.parse_iso8561_or(rv, DateAdp_.MinValue); + } + private static final String Timestamp_filename = "xowa.css.timestamp.txt"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/mgrs/Xow_css_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/mgrs/Xow_css_mgr.java index a27517de8..37572e5f4 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/mgrs/Xow_css_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/mgrs/Xow_css_mgr.java @@ -13,3 +13,40 @@ 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.addons.wikis.htmls.css.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.htmls.*; import gplx.xowa.addons.wikis.htmls.css.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.wikis.htmls.css.dbs.*; +public class Xow_css_mgr { + private final Xow_wiki wiki; + private boolean db_css_exists; + private Xowd_css_core_tbl css_core_tbl; private Xowd_css_file_tbl css_file_tbl; + public Xow_css_mgr(Xow_wiki wiki) {this.wiki = wiki;} + public void Init_by_wiki() { + Gfo_usr_dlg usr_dlg = Xoa_app_.Usr_dlg(); + Xow_db_mgr core_db_mgr = wiki.Data__core_mgr(); + if (core_db_mgr == null) { + usr_dlg.Log_many("", "", "db.css.exists; css_missing b/c tdb; wiki=~{0}", wiki.Domain_str()); + return; + } + if ( core_db_mgr.Props() == null + || core_db_mgr.Props().Schema_is_1()) { + usr_dlg.Log_many("", "", "db.css.exists; css_missing b/c v1; wiki=~{0}", wiki.Domain_str()); + return; + } + Xow_db_file core_db = core_db_mgr.Db__core(); + this.css_core_tbl = core_db.Tbl__css_core(); + this.css_file_tbl = core_db.Tbl__css_file(); + if ( core_db == null + || !core_db.Conn().Meta_tbl_exists(css_core_tbl.Tbl_name()) + ) { + usr_dlg.Log_many("", "", "db.css.exists; css_missing b/c v2 w/o css; wiki=~{0}", wiki.Domain_str()); + return; + } + this.db_css_exists = true; + } + public void Db_del_all() { + if (!db_css_exists) {Xoa_app_.Usr_dlg().Log_many("", "", "db.css.del_all; del_all skipped; wiki=~{0}", wiki.Domain_str()); return;} + css_core_tbl.Delete_all(); + css_file_tbl.Delete_all(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/mgrs/Xowd_css_core_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/mgrs/Xowd_css_core_mgr.java index a27517de8..38aa51294 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/mgrs/Xowd_css_core_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/mgrs/Xowd_css_core_mgr.java @@ -13,3 +13,56 @@ 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.addons.wikis.htmls.css.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.htmls.*; import gplx.xowa.addons.wikis.htmls.css.*; +import gplx.core.envs.*; +import gplx.dbs.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.wikis.htmls.css.dbs.*; +public class Xowd_css_core_mgr { + public static void Set(Xowd_css_core_tbl core_tbl, Xowd_css_file_tbl file_tbl, Io_url css_dir, String key) { + Db_conn conn = core_tbl.Conn(); + Io_url[] file_list = Io_mgr.Instance.QueryDir_args(css_dir).Recur_().ExecAsUrlAry(); + try { + conn.Txn_bgn("schema__css_core__set"); + int css_id = core_tbl.Select_id_by_key(key); + DateAdp updated_on = Datetime_now.Get().XtoUtc(); + if (css_id == -1) + css_id = core_tbl.Insert(key, updated_on); + else { + core_tbl.Update(css_id, key, updated_on); + file_tbl.Delete(css_id); + } + for (Io_url file : file_list) { + String path = Op_sys.Fsys_path_to_lnx(file.GenRelUrl_orEmpty(css_dir)); + byte[] data = Io_mgr.Instance.LoadFilBry(file); + file_tbl.Insert(css_id, path, data); + } + conn.Txn_end(); + } + catch (Exception e) {conn.Txn_cxl(); throw Err_.new_exc(e, "css", "Xowd_css_core_mgr.Set failed", "key", key, "err", Err_.Message_gplx_log(e));} + } + public static boolean Get(Xowd_css_core_tbl core_tbl, Xowd_css_file_tbl file_tbl, Io_url css_dir, String key) { + String dbg = "enter"; + try { + int css_id = core_tbl.Select_id_by_key(key); + dbg += ";css_id"; + if (css_id == Xowd_css_core_tbl.Id_null) return false; // unknown key; return false (not found) + dbg += ";select_by_owner"; + Xowd_css_file_itm[] file_list = file_tbl.Select_by_owner(css_id); + dbg += ";file_list:" + file_list.length; + // Io_mgr.Instance.DeleteDirDeep(css_dir); // NOTE: do not delete existing files; just overwrite; + int len = file_list.length; + if (len == 0) return false; // no css files in db + for (int i = 0; i < len; ++i) { + Xowd_css_file_itm file = file_list[i]; + dbg += ";file_url:" + file.Path(); + Io_url file_url = Io_url_.new_fil_(css_dir.Gen_sub_path_for_os(file.Path())); + if (file.Data() == null) continue; // NOTE: sqlite will return 0 length fields as NULL; if no data, just ignore, else error below + Io_mgr.Instance.SaveFilBry(file_url, file.Data()); + dbg += ";file_data:" + file.Data().length; + } + return true; + } catch (Exception e) {throw Err_.new_exc(e, "css", "Xowd_css_core_mgr.Get failed", "dbg", dbg, "err", Err_.Message_gplx_log(e));} + } + public static final String Key_default = "xowa.default", Key_mobile = "xowa.mobile"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/mgrs/Xowd_css_core_mgr_tst.java b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/mgrs/Xowd_css_core_mgr_tst.java index a27517de8..606ca28f5 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/mgrs/Xowd_css_core_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/htmls/css/mgrs/Xowd_css_core_mgr_tst.java @@ -13,3 +13,106 @@ 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.addons.wikis.htmls.css.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.htmls.*; import gplx.xowa.addons.wikis.htmls.css.*; +import org.junit.*; import gplx.core.ios.*; import gplx.dbs.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.addons.wikis.htmls.css.dbs.*; +public class Xowd_css_core_mgr_tst { + @Before public void init() {fxt.Clear();} private Xowd_css_core_mgr_fxt fxt = new Xowd_css_core_mgr_fxt(); + @Test public void Basic() { + Xowd_css_core_itm[] skin_ary = fxt.Make_skin_ary + ( fxt.Make_skin_itm(1, "desktop", "20010101_050200") + ); + Xowd_css_file_itm[] file_ary = fxt.Make_file_ary + ( fxt.Make_file_itm(1, "a.css", "a_data") + , fxt.Make_file_itm(1, "b/b.png", "b/b_data") + ); + Io_url src_dir = Io_url_.mem_dir_("mem/src/"); + fxt.Init_fs(src_dir, file_ary); + fxt.Exec_set(src_dir, "desktop"); + fxt.Test_skin_tbl(skin_ary); + fxt.Test_file_tbl(file_ary); + + Io_url trg_dir = Io_url_.mem_dir_("mem/trg/"); + fxt.Exec_get(trg_dir, "desktop"); + fxt.Test_fs(trg_dir, file_ary); + } + @Test public void Update() { // update css files; keep same skin_id; insert new files + Xowd_css_core_itm[] skin_ary = fxt.Make_skin_ary + ( fxt.Make_skin_itm(1, "desktop", "20010101_050500") + ); + Xowd_css_file_itm[] file_ary = fxt.Make_file_ary + ( fxt.Make_file_itm(1, "a.css", "a_data") + , fxt.Make_file_itm(1, "b/b.png", "b/b_data") + ); + Io_url src_dir = Io_url_.mem_dir_("mem/src/"); + fxt.Init_fs(src_dir, file_ary); + fxt.Exec_set(src_dir, "desktop"); + + file_ary = fxt.Make_file_ary + ( fxt.Make_file_itm(1, "a1.css", "a1_data") + , fxt.Make_file_itm(1, "b/b1.png", "b/b1_data") + ); + Io_mgr.Instance.DeleteDirDeep(src_dir); + fxt.Init_fs(src_dir, file_ary); + fxt.Exec_set(src_dir, "desktop"); + fxt.Test_skin_tbl(skin_ary); + fxt.Test_file_tbl(file_ary); + } +} +class Xowd_css_core_mgr_fxt { + private final Bry_bfr bfr = Bry_bfr_.Reset(32); + private Xowd_css_core_tbl core_tbl; private Xowd_css_file_tbl file_tbl; + public void Clear() { + Datetime_now.Manual_y_(); + Io_mgr.Instance.InitEngine_mem(); + Db_conn_bldr.Instance.Reg_default_mem(); + Db_conn conn = Db_conn_bldr.Instance.New(Io_url_.mem_fil_("mem/db/css.sqlite3")); + this.core_tbl = new Xowd_css_core_tbl(conn); + this.file_tbl = new Xowd_css_file_tbl(conn); + core_tbl.Create_tbl(); + file_tbl.Create_tbl(); + } + public Xowd_css_core_itm Make_skin_itm(int id, String key, String updated_on) {return new Xowd_css_core_itm(id, key, DateAdp_.parse_gplx(updated_on));} + public Xowd_css_file_itm Make_file_itm(int skin_id, String path, String data) {return new Xowd_css_file_itm(skin_id, path, Bry_.new_u8(data));} + public Xowd_css_file_itm[] Make_file_ary(Xowd_css_file_itm... ary) {return ary;} + public Xowd_css_core_itm[] Make_skin_ary(Xowd_css_core_itm... ary) {return ary;} + public void Init_fs(Io_url css_dir, Xowd_css_file_itm[] file_ary) { + for (Xowd_css_file_itm itm : file_ary) + Io_mgr.Instance.SaveFilBry(css_dir.GenSubFil(itm.Path()), itm.Data()); + } + public void Exec_set(Io_url css_dir, String key) {Xowd_css_core_mgr.Set(core_tbl, file_tbl, css_dir, key);} + public void Exec_get(Io_url css_dir, String key) {Xowd_css_core_mgr.Get(core_tbl, file_tbl, css_dir, key);} + public void Test_skin_tbl(Xowd_css_core_itm[] expd) { + Xowd_css_core_itm[] actl = core_tbl.Select_all(); + Tfds.Eq_str_lines(To_str(expd), To_str(actl)); + } + public void Test_file_tbl(Xowd_css_file_itm[] expd) { + Xowd_css_file_itm[] actl = file_tbl.Select_all(); + Tfds.Eq_str_lines(To_str(expd), To_str(actl)); + } + public void Test_fs(Io_url css_dir, Xowd_css_file_itm[] expd) { + Io_url[] actl = Io_mgr.Instance.QueryDir_args(css_dir).Recur_().ExecAsUrlAry(); + int len = expd.length; + Tfds.Eq(len, actl.length); + for (int i = 0; i < len; ++i) { + Xowd_css_file_itm expd_itm = expd[i]; + Tfds.Eq_bry(expd_itm.Data(), Io_mgr.Instance.LoadFilBry(actl[i])); + } + } + private String To_str(Xowd_css_file_itm[] ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) { + Xowd_css_file_itm itm = ary[i]; + bfr.Add_int_variable(itm.Css_id()).Add_byte_pipe().Add_str_u8(itm.Path()).Add_byte_pipe().Add(itm.Data()).Add_byte_nl(); + } + return bfr.To_str_and_clear(); + } + private String To_str(Xowd_css_core_itm[] ary) { + Bry_bfr bfr = Bry_bfr_.New(); + int len = ary.length; + for (int i = 0; i < len; ++i) { + Xowd_css_core_itm itm = ary[i]; + bfr.Add_int_variable(itm.Id()).Add_byte_pipe().Add_str_u8(itm.Key()).Add_byte_pipe().Add_str_u8(itm.Updated_on().XtoStr_fmt_yyyyMMdd_HHmmss()).Add_byte_nl(); + } + return bfr.To_str_and_clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_addon.java b/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_addon.java index a27517de8..91e5a3dce 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_addon.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_addon.java @@ -13,3 +13,28 @@ 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.addons.wikis.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; +import gplx.xowa.specials.*; +public class Xow_import_addon implements Xoax_addon_itm, Xoax_addon_itm__special { + private final Ordered_hash cbks = Ordered_hash_.New(); + public Xow_special_page[] Special_pages() { + return new Xow_special_page[] + { Xow_import_special.Prototype + }; + } + public void Dir_selected_cbks__add(Xow_import_dir_cbk cbk) { + if (!cbks.Has(cbk.Key())) + cbks.Add(cbk.Key(), cbk); + } + public Xow_import_dir_cbk Dir_selected_cbks__get_by(String key) {return (Xow_import_dir_cbk)cbks.Get_by(key);} + + public String Addon__key() {return ADDON__KEY;} private static final String ADDON__KEY = "xowa.apps.file_browsers"; + public static Xow_import_addon Addon__get(Xow_wiki wiki) { + Xow_import_addon rv = (Xow_import_addon)wiki.Addon_mgr().Itms__get_or_null(ADDON__KEY); + if (rv == null) { + rv = new Xow_import_addon(); + wiki.Addon_mgr().Itms__add(rv); + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_dir_cbk.java b/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_dir_cbk.java index a27517de8..6077b0a33 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_dir_cbk.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_dir_cbk.java @@ -13,3 +13,8 @@ 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.addons.wikis.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; +public interface Xow_import_dir_cbk { + String Key(); + void Cbk__dir_selected(Xow_wiki wiki, Xoa_page page, String path); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_doc.java b/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_doc.java index a27517de8..cb9cb895d 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_doc.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_doc.java @@ -13,3 +13,69 @@ 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.addons.wikis.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; +import gplx.core.ios.*; +import gplx.langs.mustaches.*; +class Xow_import_doc implements Mustache_doc_itm { + private final boolean is_dir, is_core_xowa; + private final byte[] owner_dir_enc, path, name, date, size, color; + private final byte[] dir_cmd; + private final Xow_import_doc[] subs; + public Xow_import_doc(boolean is_dir, boolean is_core_xowa, int color, byte[] owner_dir, byte[] path, byte[] name, byte[] date, byte[] size, byte[] dir_cmd, Xow_import_doc[] subs) { + this.is_dir = is_dir; this.is_core_xowa = is_core_xowa; + this.color = color % 2 == 0 ? Byte_ascii.Num_0_bry : Byte_ascii.Num_1_bry; + this.owner_dir_enc = gplx.langs.htmls.encoders.Gfo_url_encoder_.Href.Encode(owner_dir); + this.path = path; this.name = name; this.date = date; this.size = size; + this.dir_cmd = dir_cmd; + this.subs = subs; + } + public boolean Mustache__write(String key, Mustache_bfr bfr) { + if (String_.Eq(key, "path")) bfr.Add_bry(path); + else if (String_.Eq(key, "path_enc")) bfr.Add_bry(gplx.langs.htmls.encoders.Gfo_url_encoder_.Href.Encode(path)); + else if (String_.Eq(key, "owner_dir_enc")) bfr.Add_bry(owner_dir_enc); + else if (String_.Eq(key, "name")) bfr.Add_bry(name); + else if (String_.Eq(key, "date")) bfr.Add_bry(date); + else if (String_.Eq(key, "size")) bfr.Add_bry(size); + else if (String_.Eq(key, "color")) bfr.Add_bry(color); + else if (String_.Eq(key, "dir_cmd")) bfr.Add_bry(dir_cmd); + else if (String_.Eq(key, "dir_cmd_arg")) {bfr.Add_str_u8("&dir_cmd="); bfr.Add_bry(dir_cmd);} + else return false; + return true; + } + public Mustache_doc_itm[] Mustache__subs(String key) { + if (String_.Eq(key, "is_dir")) return Mustache_doc_itm_.Ary__bool(is_dir); + else if (String_.Eq(key, "dir_cmd_exists")) return Mustache_doc_itm_.Ary__bool(Bry_.Len_gt_0(dir_cmd)); + else if (String_.Eq(key, "is_core_xowa")) return Mustache_doc_itm_.Ary__bool(is_core_xowa); + else if (String_.Eq(key, "subs")) return subs; + return Mustache_doc_itm_.Ary__empty; + } + public static final Xow_import_doc[] Ary_empty = new Xow_import_doc[0]; + public static Xow_import_doc New(IoItmDir owner_dir, byte[] dir_cmd) { + List_adp sub_list = List_adp_.New(); + New_subs(owner_dir.Url(), sub_list, owner_dir.SubDirs(), dir_cmd); + New_subs(owner_dir.Url(), sub_list, owner_dir.SubFils(), Bry_.Empty); + Xow_import_doc[] subs = (Xow_import_doc[])sub_list.To_ary_and_clear(Xow_import_doc.class); + return new Xow_import_doc(Bool_.Y, Bool_.N, 0, owner_dir.Url().OwnerDir().RawBry(), owner_dir.Url().RawBry(), Bry_.new_u8(owner_dir.Name()), Bry_.Empty, Bry_.Empty, dir_cmd, subs); + } + private static void New_subs(Io_url owner_dir, List_adp list, IoItmList subs, byte[] dir_cmd) { + subs.Sort(); + int len = subs.Len(); + int list_total = list.Len(); + byte[] owner_dir_bry = owner_dir.RawBry(); + for (int i = 0; i < len; ++i) { + IoItm_base src = (IoItm_base)subs.Get_at(i); + Xow_import_doc trg = null; + if (src.Type_dir()) { + byte[] trg_url = src.Url().RawBry(); + trg = new Xow_import_doc(Bool_.Y, Bool_.N, list_total + i, owner_dir_bry, trg_url, Bry_.new_u8(src.Url().NameAndExt_noDirSpr()), Bry_.Empty, Bry_.Empty, dir_cmd, Ary_empty); + } + else { + IoItmFil src_as_fil = (IoItmFil)src; + String size_str = Io_size_.To_str(src_as_fil.Size(), "#,###"); + boolean is_xowa_core = gplx.xowa.wikis.data.Xow_db_file__core_.Is_core_fil_name(owner_dir.NameOnly(), src.Url().NameAndExt()); + trg = new Xow_import_doc(Bool_.N, is_xowa_core, list_total + i, owner_dir_bry, src.Url().RawBry(), Bry_.new_u8(src.Name()), Bry_.new_u8(src_as_fil.ModifiedTime().XtoStr_fmt("yyyy-MM-dd")), Bry_.new_u8(size_str), dir_cmd, Ary_empty); + } + list.Add(trg); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_html.java b/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_html.java index a27517de8..d3f32a94c 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_html.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_html.java @@ -13,3 +13,25 @@ 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.addons.wikis.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; +import gplx.xowa.specials.*; import gplx.langs.mustaches.*; import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.tags.*; +import gplx.core.ios.*; +class Xow_import_html extends Xow_special_wtr__base { + private final Io_url owner_url; private final byte[] mode; + public Xow_import_html(Io_url owner_url, byte[] mode) { + this.owner_url = owner_url; + this.mode = mode; + } + @Override protected Io_url Get_addon_dir(Xoa_app app) {return app.Fsys_mgr().Http_root().GenSubDir_nest("bin", "any", "xowa", "addon", "wiki", "import");} + @Override protected Io_url Get_mustache_fil(Io_url addon_dir) {return addon_dir.GenSubFil_nest("bin", "xow_import.mustache.html");} + @Override protected Mustache_doc_itm Bld_mustache_root(Xoa_app app) { + IoItmDir owner_dir = Io_mgr.Instance.QueryDir_args(owner_url).DirInclude_(true).ExecAsDir(); + return Xow_import_doc.New(owner_dir, mode); + } + @Override protected void Bld_tags(Xoa_app app, Io_url addon_dir, Xopage_html_data page_data) { + Xopg_tag_mgr head_tags = page_data.Head_tags(); + Xopg_tag_wtr_.Add__xocss (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xohelp (head_tags, app.Fsys_mgr().Http_root()); + head_tags.Add(Xopg_tag_itm.New_css_file(addon_dir.GenSubFil_nest("bin", "xow_import.css"))); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_special.java b/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_special.java index a27517de8..bb54bb913 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_special.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/imports/Xow_import_special.java @@ -13,3 +13,47 @@ 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.addons.wikis.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; +import gplx.xowa.specials.*; import gplx.core.net.*; import gplx.core.net.qargs.*; import gplx.xowa.wikis.pages.*; +import gplx.core.ios.*; +public class Xow_import_special implements Xow_special_page { + public void Special__gen(Xow_wiki wiki, Xoa_page page, Xoa_url url, Xoa_ttl ttl) { + Gfo_qarg_mgr url_args = new Gfo_qarg_mgr().Init(url.Qargs_ary()); + + // get path + String owner_str = url_args.Read_str_or_null("path"); + if (owner_str == null) { + Xopage_html_data.err_("url has unknown path").Apply(page); + return; + } + + // check if dir_cmd is available + byte[] dir_cmd = url_args.Read_bry_or_null("dir_cmd"); + + // check selected + int selected = url_args.Read_int_or("selected", -1); + if ( selected == 1 + && dir_cmd != null) { + Xow_import_addon addon = Xow_import_addon.Addon__get(wiki); + Xow_import_dir_cbk import_cbk = addon.Dir_selected_cbks__get_by(String_.new_u8(dir_cmd)); + import_cbk.Cbk__dir_selected(wiki, page, owner_str); + } + + new Xow_import_html(Io_url_.new_dir_(owner_str), dir_cmd).Bld_page_by_mustache(wiki.App(), page, this); + } + + public static byte[] Get_root_url() { + byte tid = gplx.core.envs.Op_sys.Cur().Tid(); + byte[] rv = Bry_.new_a7("/"); + switch (tid) { + case gplx.core.envs.Op_sys.Tid_wnt : rv = Bry_.new_a7("C:\\"); break; + } + rv = gplx.langs.htmls.encoders.Gfo_url_encoder_.Href.Encode(rv); + return rv; + } + + Xow_import_special(Xow_special_meta special__meta) {this.special__meta = special__meta;} + public Xow_special_meta Special__meta() {return special__meta;} private final Xow_special_meta special__meta; + public Xow_special_page Special__clone() {return this;} + public static final Xow_special_page Prototype = new Xow_import_special(Xow_special_meta.New_xo("XowaWikiImport", "Import wiki", "XowaFileBrowser")); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pagebaks/Pagebaks_addon.java b/400_xowa/src/gplx/xowa/addons/wikis/pagebaks/Pagebaks_addon.java index a27517de8..f29587040 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pagebaks/Pagebaks_addon.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pagebaks/Pagebaks_addon.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.addons.wikis.pagebaks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; +import gplx.xowa.specials.*; +import gplx.langs.htmls.encoders.*; +public class Pagebaks_addon implements Xoax_addon_itm { + public static void On_page_saved(Xoae_app app, Xowe_wiki wiki, Xoa_ttl ttl, byte[] text) { + // get vars + if (!app.Cfg().Get_bool_app_or("xowa.wiki.edit.pagebaks.enabled", true)) return; + + try { + // #save file + // get file name; note encoding for wnt even on lnx systems just to be consistent + Gfo_url_encoder encoder = Gfo_url_encoder_.New__fsys_wnt().Make(); + byte[] file_name = encoder.Encode(ttl.Full_db()); + + // save file to backup dir; EX: /xowa/wiki/en.w/user/temp/save_backups/Earth/20170303_080102_123.txt + Io_url bak_dir = wiki.Fsys_mgr().Root_dir().GenSubDir_nest("user", "temp", "page_backups", String_.new_u8(file_name)); + Io_url file_url = bak_dir.GenSubFil_ary(Datetime_now.Get().XtoStr_fmt("yyyyMMdd_HHmmss_fff"), ".txt"); + Io_mgr.Instance.SaveFilBry(file_url, text); + + // #prune dir + // get files for pruning + Io_url[] fils = Io_mgr.Instance.QueryDir_fils(bak_dir); + Array_.Sort(fils); + + // calc files + int num_files = fils.length; + int max_files = app.Cfg().Get_int_app_or("xowa.wiki.edit.pagebaks.max_backups", 16); + int cutoff = num_files - max_files; + + // do pruning + for (int i = 0; i < cutoff; i++) {// EX: 3 files and 2 max; 1st file (index 0) needs to be deleted + Io_mgr.Instance.DeleteFil(fils[i]); + } + } catch (Exception e) { + Gfo_usr_dlg_.Instance.Log_many("", "", "failed to save page backup; wiki=~{0} ttl=~{1} err=~{2}", wiki.Domain_bry(), ttl.Full_db(), Err_.Message_gplx_log(e)); + } + } + + public String Addon__key() {return "xowa.wikis.edits.pagebaks";} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/Rndm_addon.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/Rndm_addon.java index a27517de8..4fea94852 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/Rndm_addon.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/Rndm_addon.java @@ -13,3 +13,36 @@ 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.addons.wikis.pages.randoms; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; +import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.addons.wikis.pages.randoms.bldrs.*; +import gplx.xowa.specials.*; import gplx.xowa.addons.wikis.pages.randoms.specials.*; +import gplx.xowa.addons.wikis.pages.randoms.mgrs.*; +public class Rndm_addon implements Xoax_addon_itm, Xoax_addon_itm__bldr, Xoax_addon_itm__special { + public Rndm_addon() {this.mgr = null;} // prototype + public Rndm_addon(Xow_wiki wiki) {this.mgr = new Rndm_mgr(wiki);} + public Rndm_mgr Mgr() {return mgr;} private final Rndm_mgr mgr; + public Xob_cmd[] Bldr_cmds() { + return new Xob_cmd[] + { Rndm_bldr_cmd.Prototype + }; + } + public Xow_special_page[] Special_pages() { + return new Xow_special_page[] + { Rndm_root_special.Prototype + , Rndm_page_special.Prototype + , new gplx.xowa.specials.deletes .Xodel_page_special() + }; + } + + public static Rndm_addon Get(Xow_wiki wiki) { + Rndm_addon rv = (Rndm_addon)wiki.Addon_mgr().Itms__get_or_null(ADDON_KEY); + if (rv == null) { + rv = new Rndm_addon(wiki); + wiki.Addon_mgr().Itms__add(rv); + } + return rv; + } + + public String Addon__key() {return ADDON_KEY;} private static final String ADDON_KEY = "xowa.builds.randoms"; + +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/bldrs/Rndm_bldr_cmd.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/bldrs/Rndm_bldr_cmd.java index a27517de8..2ed54255a 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/bldrs/Rndm_bldr_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/bldrs/Rndm_bldr_cmd.java @@ -13,3 +13,23 @@ 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.addons.wikis.pages.randoms.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.randoms.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Rndm_bldr_cmd extends Xob_cmd__base { + private int rndm_interval = 1000; + public Rndm_bldr_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + wiki.Init_assert(); + new Rndm_ns_rebuilder().Exec(wiki, rndm_interval); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_rndm_interval_)) rndm_interval = m.ReadInt("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_rndm_interval_ = "rndm_interval_"; + + public static final String BLDR_CMD_KEY = "wiki.random"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Rndm_bldr_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Rndm_bldr_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/bldrs/Rndm_bldr_wkr.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/bldrs/Rndm_bldr_wkr.java index a27517de8..3188247e6 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/bldrs/Rndm_bldr_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/bldrs/Rndm_bldr_wkr.java @@ -13,3 +13,58 @@ 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.addons.wikis.pages.randoms.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.randoms.*; +import gplx.dbs.*; import gplx.xowa.addons.wikis.pages.randoms.dbs.*; +public class Rndm_bldr_wkr { + private final Rndm_qry_tbl qry_tbl; private final Rndm_rng_tbl rng_tbl; private final Rndm_seq_tbl seq_tbl; + private Rndm_qry_itm qry_itm; + private Db_stmt rng_stmt, seq_stmt; + private int rng_seq_bgn, seq_in_rng; + private int qry_idx_max = 0; + public Rndm_bldr_wkr(Db_conn conn, Rndm_qry_tbl qry_tbl, Rndm_rng_tbl rng_tbl, Rndm_seq_tbl seq_tbl) { + this.conn = conn; + this.qry_tbl = qry_tbl; this.rng_tbl = rng_tbl; this.seq_tbl = seq_tbl; + qry_idx_max = qry_tbl.Select_qry_max(); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public int Qry_idx() {return qry_idx;} private int qry_idx; + public int Rng_idx() {return rng_idx;} private int rng_idx; + public int Seq_in_qry() {return seq_in_qry;} private int seq_in_qry; + public void Exec_qry_bgn(Rndm_qry_itm qry_itm) { + this.qry_itm = qry_itm; + + qry_idx = qry_tbl.Select_by_key(qry_itm.Qry_key()); + if (qry_idx == -1) + qry_idx = ++qry_idx_max; + else { + // delete all + qry_tbl.Delete_by_qry_idx(qry_idx); + rng_tbl.Delete_by_qry_idx(qry_idx); + seq_tbl.Delete_by_qry_idx(qry_idx); + } + + rng_idx = seq_in_rng = seq_in_qry = 0; + rng_stmt = rng_tbl.Insert_stmt(); + seq_stmt = seq_tbl.Insert_stmt(); + } + public void Exec_qry_end() { + if (seq_in_qry == 0) return; // no sequences added + qry_tbl.Insert(qry_idx, rng_idx, qry_itm.Qry_key(), qry_itm.Qry_data(), qry_itm.Qry_name()); + } + public void Exec_rng_bgn() { + rng_seq_bgn = seq_in_qry; + ++rng_idx; + seq_in_rng = 0; + } + public Rndm_rng_itm Exec_rng_end_or_null() { + if (seq_in_rng == 0) return null; // no sequences added; return null; + Rndm_rng_itm rv = new Rndm_rng_itm(qry_idx, rng_idx, rng_seq_bgn, seq_in_qry); + rng_tbl.Insert(rng_stmt, qry_idx, rng_idx, rng_seq_bgn, seq_in_qry); + return rv; + } + public void Exec_seq_itm(int page_id) { + seq_tbl.Insert(seq_stmt, qry_idx, rng_idx, seq_in_rng, page_id); + ++seq_in_qry; + ++seq_in_rng; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/bldrs/Rndm_ns_rebuilder.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/bldrs/Rndm_ns_rebuilder.java index a27517de8..ff982e4a0 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/bldrs/Rndm_ns_rebuilder.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/bldrs/Rndm_ns_rebuilder.java @@ -13,3 +13,56 @@ 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.addons.wikis.pages.randoms.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.randoms.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.addons.wikis.pages.randoms.dbs.*; +class Rndm_ns_rebuilder { + public void Exec(Xow_wiki wiki, int rndm_interval) { + // get wkr; page_tbl + Rndm_addon addon = Rndm_addon.Get(wiki); + Rndm_bldr_wkr wkr = addon.Mgr().New_bldr(); + Xowd_page_tbl page_tbl = wiki.Data__core_mgr().Db__core().Tbl__page(); + String fld_page_id = page_tbl.Fld_page_id(); + Db_conn page_conn = page_tbl.Conn(); + page_conn.Meta_idx_assert(page_tbl.Tbl_name(), "rndm_rebuild", page_tbl.Fld_page_ns(), fld_page_id); + + // loop over ns + Xow_ns_mgr ns_mgr = wiki.Ns_mgr(); + int len = ns_mgr.Ids_len(); + wkr.Conn().Txn_bgn("rndm"); + for (int i = 0; i < len; ++i) { + Xow_ns ns = ns_mgr.Ids_get_at(i); + Gfo_log_.Instance.Prog("reading ns", "ns", ns.Id()); + int page_id_cur = -1; + wkr.Exec_qry_bgn(Rndm_qry_itm.New_by_ns(wiki, ns.Id())); + + // read pages in ns where page_id > last_page_id + while (true) { + Gfo_log_.Instance.Prog("reading pages", "page_id", page_id_cur); + String sql = String_.Format("SELECT * FROM page WHERE page_namespace = {0} AND page_id > {1} ORDER BY page_id", ns.Id(), page_id_cur); // ANSI.Y + int rdr_count = 0; + wkr.Exec_rng_bgn(); + Db_rdr rdr = page_conn.Stmt_sql(sql).Exec_select__rls_auto(); + try { + // read pages until rndm_interval + while (rdr.Move_next()) { + int page_id = rdr.Read_int(fld_page_id); + wkr.Exec_seq_itm(page_id); + if (++rdr_count == rndm_interval) { + page_id_cur = page_id; + break; + } + } + } + finally {rdr.Rls();} + wkr.Exec_rng_end_or_null(); + if (rdr_count != rndm_interval) + break; + } + wkr.Exec_qry_end(); + } + wkr.Conn().Txn_end(); + page_conn.Meta_idx_delete("page", "rndm_rebuild"); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_db_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_db_mgr.java index a27517de8..fa9a4ae10 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_db_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_db_mgr.java @@ -13,3 +13,40 @@ 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.addons.wikis.pages.randoms.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.randoms.*; +import gplx.dbs.*; +import gplx.xowa.wikis.data.*; +public class Rndm_db_mgr { + private final Xow_wiki wiki; + public Rndm_db_mgr(Xow_wiki wiki) { + this.wiki = wiki; + } + public Db_conn Conn() {return conn;} private Db_conn conn; + public Rndm_qry_tbl Tbl__qry() {return tbl__qry;} private Rndm_qry_tbl tbl__qry; + public Rndm_rng_tbl Tbl__rng() {return tbl__rng;} private Rndm_rng_tbl tbl__rng; + public Rndm_seq_tbl Tbl__seq() {return tbl__seq;} private Rndm_seq_tbl tbl__seq; + public Rndm_db_mgr Init() { + this.conn = Get_or_new(wiki); + this.tbl__qry = new Rndm_qry_tbl(conn); + this.tbl__rng = new Rndm_rng_tbl(conn); + this.tbl__seq = new Rndm_seq_tbl(conn); + if (!conn.Meta_tbl_exists(tbl__qry.Tbl_name())) { + tbl__qry.Create_tbl(); + tbl__rng.Create_tbl(); + tbl__seq.Create_tbl(); + } + return this; + } + + private static Db_conn Get_or_new(Xow_wiki wiki) { + int layout_text = wiki.Data__core_mgr().Db__core().Db_props().Layout_text().Tid(); + Io_url url = null; + switch (layout_text) { + case Xow_db_layout.Tid__all: url = wiki.Data__core_mgr().Db__core().Url(); break; + case Xow_db_layout.Tid__few: url = wiki.Fsys_mgr().Root_dir().GenSubFil(String_.Format("{0}-data.xowa", wiki.Domain_str())); break; + case Xow_db_layout.Tid__lot: url = wiki.Fsys_mgr().Root_dir().GenSubFil(String_.Format("{0}-xtn.random.core.xowa", wiki.Domain_str())); break; + default: throw Err_.new_unhandled(layout_text); + } + return Db_conn_bldr.Instance.Get_or_autocreate(true, url); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_qry_itm.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_qry_itm.java index a27517de8..c6375c07c 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_qry_itm.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_qry_itm.java @@ -13,3 +13,27 @@ 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.addons.wikis.pages.randoms.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.randoms.*; +public class Rndm_qry_itm { + public Rndm_qry_itm(int qry_idx, int rng_end, String qry_key, String qry_data, String qry_name) { + this.qry_idx = qry_idx; + this.rng_end = rng_end; + this.qry_key = qry_key; + this.qry_data = qry_data; + this.qry_name = qry_name; + } + public int Qry_idx() {return qry_idx;} private final int qry_idx; + public int Rng_end() {return rng_end;} private final int rng_end; + public String Qry_key() {return qry_key;} private final String qry_key; + public String Qry_data() {return qry_data;} private final String qry_data; + public String Qry_name() {return qry_name;} private final String qry_name; + public int Seq_max() {return seq_max;} private int seq_max; public void Seq_max_(int v) {this.seq_max = v;} + + public static Rndm_qry_itm New_by_ns(Xow_wiki wiki, int ns_id) { + String ns_str = Int_.To_str(ns_id); + String qry_key = "xowa.ns." + ns_str; // xowa.ns.0 + String qry_data = "type=ns;ns=" + ns_str; // type=ns;ns=0 + String qry_name = "Namespace " + ns_str + " - All"; + return new Rndm_qry_itm(-1, -1, qry_key, qry_data, qry_name); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_qry_tbl.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_qry_tbl.java index a27517de8..7d40242e4 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_qry_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_qry_tbl.java @@ -13,3 +13,42 @@ 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.addons.wikis.pages.randoms.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.randoms.*; +import gplx.core.ios.*; import gplx.dbs.*; import gplx.dbs.utls.*; +public class Rndm_qry_tbl implements Rls_able { + private final String tbl_name = "rndm_qry"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_qry_idx, fld_rng_end, fld_qry_key, fld_qry_data, fld_qry_name; + private final Db_conn conn; + public Rndm_qry_tbl(Db_conn conn) { + this.conn = conn; + fld_qry_idx = flds.Add_int_pkey("qry_idx"); // EX: 0 + fld_rng_end = flds.Add_int("rng_end"); // EX: 123 + fld_qry_key = flds.Add_str("qry_key", 255); // EX: xowa.ns.0 + fld_qry_data = flds.Add_str("qry_data", 255); // EX: type=ns;ns=0 + fld_qry_name = flds.Add_str("qry_name", 255); // EX: Main Namespace - All + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public int Select_rng_end(int qry_idx) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_qry_idx).Exec_select__rls_auto(); + try {return rdr.Move_next() ? rdr.Read_int(fld_rng_end) : 0;} + finally {rdr.Rls();} + } + public int Select_qry_max() { + Db_rdr rdr = conn.Stmt_sql("SELECT Coalesce(Max(qry_idx), 0) AS qry_idx FROM rndm_qry").Exec_select__rls_auto(); // ANSI.Y + try {return rdr.Move_next() ? rdr.Read_int(fld_qry_idx) : 0;} + finally {rdr.Rls();} + } + public int Select_by_key(String key) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_qry_key).Crt_str(fld_qry_key, key).Exec_select__rls_auto(); + try {return rdr.Move_next() ? rdr.Read_int(fld_qry_idx) : -1;} + finally {rdr.Rls();} + } + public void Insert(int qry_idx, int rng_end, String qry_key, String qry_data, String qry_name) { + conn.Stmt_insert(tbl_name, flds).Val_int(fld_qry_idx, qry_idx).Val_int(fld_rng_end, rng_end) + .Val_str(fld_qry_key, qry_key).Val_str(fld_qry_data, qry_data).Val_str(fld_qry_name, qry_name).Exec_insert(); + } + public void Delete_by_qry_idx(int qry_idx) {conn.Stmt_delete(tbl_name, fld_qry_idx).Crt_int(fld_qry_idx, qry_idx).Exec_delete();} + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_rng_itm.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_rng_itm.java index a27517de8..1f938438b 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_rng_itm.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_rng_itm.java @@ -13,3 +13,16 @@ 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.addons.wikis.pages.randoms.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.randoms.*; +public class Rndm_rng_itm { + public Rndm_rng_itm(int mgr_idx, int rng_idx, int seq_bgn, int seq_end) { + this.mgr_idx = mgr_idx; this.rng_idx = rng_idx; + this.seq_bgn = seq_bgn; this.seq_end = seq_end; + } + public int Mgr_idx() {return mgr_idx;} private final int mgr_idx; + public int Rng_idx() {return rng_idx;} private final int rng_idx; + public int Seq_bgn() {return seq_bgn;} private final int seq_bgn; + public int Seq_end() {return seq_end;} private final int seq_end; + + public static Rndm_rng_itm Noop() {return new Rndm_rng_itm(-1, -1, 0, 0);} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_rng_tbl.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_rng_tbl.java index a27517de8..c8e29d89b 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_rng_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_rng_tbl.java @@ -13,3 +13,41 @@ 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.addons.wikis.pages.randoms.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.randoms.*; +import gplx.core.ios.*; import gplx.dbs.*; import gplx.dbs.utls.*; +public class Rndm_rng_tbl implements Rls_able { + private final String tbl_name = "rndm_rng"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_qry_idx, fld_rng_idx, fld_seq_bgn, fld_seq_end; + private final Db_conn conn; + public Rndm_rng_tbl(Db_conn conn) { + this.conn = conn; + fld_qry_idx = flds.Add_int("qry_idx"); + fld_rng_idx = flds.Add_int("rng_idx"); + fld_seq_bgn = flds.Add_int("seq_bgn"); + fld_seq_end = flds.Add_int("seq_end"); + conn.Rls_reg(this); + } + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Create_idx() {conn.Meta_idx_create(tbl_name, "core", fld_qry_idx, fld_seq_bgn, fld_seq_end);} + public Rndm_rng_itm Select_by_rng_idx_or_noop(int qry_idx, int rng_idx) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_rng_idx).Crt_int(fld_rng_idx, rng_idx).Exec_select__rls_auto(); + try {return Load_or_noop(rdr);} + finally {rdr.Rls();} + } + public Rndm_rng_itm Select_by_rndm_num_or_noop(int qry_idx, int rndm_num) { + Db_rdr rdr = conn.Stmt_sql(String_.Format("SELECT * FROM rndm_rng WHERE qry_idx = {0} AND seq_bgn <= {1} AND seq_end > {2}", qry_idx, rndm_num)).Exec_select__rls_auto(); // ANSI.Y + try {return Load_or_noop(rdr);} + finally {rdr.Rls();} + } + private Rndm_rng_itm Load_or_noop(Db_rdr rdr) { + return (rdr.Move_next()) + ? new Rndm_rng_itm(rdr.Read_int(fld_qry_idx), rdr.Read_int(fld_rng_idx), rdr.Read_int(fld_seq_bgn), rdr.Read_int(fld_seq_end)) + : Rndm_rng_itm.Noop(); + } + public Db_stmt Insert_stmt() {return conn.Stmt_insert(tbl_name, flds);} + public void Insert(Db_stmt stmt, int qry_idx, int rng_idx, int seq_bgn, int seq_end) { + stmt.Clear().Val_int(fld_qry_idx, qry_idx).Val_int(fld_rng_idx, rng_idx).Val_int(fld_seq_bgn, seq_bgn).Val_int(fld_seq_end, seq_end).Exec_insert(); + } + public void Delete_by_qry_idx(int qry_idx) {conn.Stmt_delete(tbl_name, fld_qry_idx).Crt_int(fld_qry_idx, qry_idx).Exec_delete();} + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_seq_tbl.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_seq_tbl.java index a27517de8..505bec6d2 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_seq_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/dbs/Rndm_seq_tbl.java @@ -13,3 +13,38 @@ 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.addons.wikis.pages.randoms.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.randoms.*; +import gplx.core.ios.*; import gplx.dbs.*; import gplx.dbs.utls.*; +public class Rndm_seq_tbl implements Rls_able { // list of page_ids w/ random_idx; EX: 0,123|1,23|2,31|... + private final String fld_qry_idx, fld_rng_idx, fld_seq_idx, fld_page_id; + private final Db_conn conn; + public Rndm_seq_tbl(Db_conn conn) { + this.conn = conn; + fld_qry_idx = flds.Add_int("qry_idx"); + fld_rng_idx = flds.Add_int("rng_idx"); + fld_seq_idx = flds.Add_int("seq_idx"); + fld_page_id = flds.Add_int("page_id"); + } + public Db_conn Conn() {return conn;} + public String Tbl_name() {return tbl_name;} private final String tbl_name = "rndm_seq"; + public Dbmeta_fld_list Flds() {return flds;} private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + public String Fld__qry_idx() {return fld_qry_idx;} + public String Fld__rng_idx() {return fld_rng_idx;} + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Create_idx() {conn.Meta_idx_create(tbl_name, "core", fld_qry_idx, fld_rng_idx, fld_seq_idx);} + public int Select_or_neg_1(int qry_idx, int rng_idx, int seq_idx) { + Db_stmt stmt = conn.Stmt_select(tbl_name, flds, fld_qry_idx, fld_rng_idx, fld_seq_idx); + Db_rdr rdr = stmt.Clear().Crt_int(fld_qry_idx, qry_idx).Crt_int(fld_rng_idx, rng_idx).Val_int(fld_seq_idx, seq_idx).Exec_select__rls_auto(); + try {return rdr.Move_next() ? rdr.Read_int(fld_page_id) : -1;} + catch (Exception e) {Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to get seq_idx; url=~{0} qry_idx=~{1} rng_idx=~{2} seq_idx=~{3} err=~{4}", conn.Conn_info().Db_api(), qry_idx, rng_idx, seq_idx, Err_.Message_gplx_log(e)); return -1;} + finally {rdr.Rls();} + } + public Db_stmt Insert_stmt() {return conn.Stmt_insert(tbl_name, flds);} + public void Insert(Db_stmt stmt, int qry_idx, int rng_idx, int seq_idx, int page_id) { + stmt.Clear().Val_int(fld_qry_idx, qry_idx).Val_int(fld_rng_idx, rng_idx).Val_int(fld_seq_idx, seq_idx).Val_int(fld_page_id, page_id).Exec_insert(); + } + public void Delete_by_qry_idx(int qry_idx) {conn.Stmt_delete(tbl_name, fld_qry_idx).Crt_int(fld_qry_idx, qry_idx).Exec_delete();} + public void Rls() {} + + public static final int Db_row_size_fixed = 4 * 4; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/mgrs/Rndm_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/mgrs/Rndm_mgr.java index a27517de8..d713d573e 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/mgrs/Rndm_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/mgrs/Rndm_mgr.java @@ -13,3 +13,27 @@ 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.addons.wikis.pages.randoms.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.randoms.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.wikis.pages.randoms.dbs.*; import gplx.xowa.addons.wikis.pages.randoms.bldrs.*; +public class Rndm_mgr { + public Rndm_mgr(Xow_wiki wiki) { + this.db_mgr = new Rndm_db_mgr(wiki).Init(); + } + public Rndm_db_mgr Db_mgr() {return db_mgr;} private Rndm_db_mgr db_mgr; + + public int Get_rndm_page(int qry_idx) { + // 0|type:ns,ns_id:123,text:123|123 + int rng_end = db_mgr.Tbl__qry().Select_rng_end(qry_idx); + Rndm_rng_itm rng_end_itm = db_mgr.Tbl__rng().Select_by_rng_idx_or_noop(qry_idx, rng_end); + int rndm_num = RandomAdp_.new_().Next(rng_end_itm.Seq_end()); + Rndm_rng_itm rng_itm = db_mgr.Tbl__rng().Select_by_rndm_num_or_noop(qry_idx, rndm_num); + int seq_idx = rndm_num - rng_itm.Seq_bgn(); + int page_id = db_mgr.Tbl__seq().Select_or_neg_1(qry_idx, rng_itm.Rng_idx(), seq_idx); + Gfo_log_.Instance.Info("get_random_page", "qry_idx", qry_idx, "rng_end", rng_end, "rndm_num", rndm_num, "rng_idx", rng_itm.Rng_idx(), "seq_idx", seq_idx, "page_id", page_id); + return page_id; + } + public Rndm_bldr_wkr New_bldr() {return new Rndm_bldr_wkr(db_mgr.Conn(), db_mgr.Tbl__qry(), db_mgr.Tbl__rng(), db_mgr.Tbl__seq());} + + public static final int Qry_idx__main = 0; // all +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/mgrs/Rndm_ns_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/mgrs/Rndm_ns_mgr.java index a27517de8..4d2490cec 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/mgrs/Rndm_ns_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/mgrs/Rndm_ns_mgr.java @@ -13,3 +13,15 @@ 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.addons.wikis.pages.randoms.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.randoms.*; +import gplx.core.lists.hashs.*; +class Rndm_ns_mgr { +// private final Hash_adp__int hash = new Hash_adp__int(); +// public Rndm_ns_itm Get_by_ns(int ns_id) {return (Rndm_ns_itm)hash.Get_by_or_null(ns_id);} + public void Add() { // rndm; + /* + for (mgr : rndm_mgr) + hash.Add(mgr,); + */ + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/specials/Rndm_page_special.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/specials/Rndm_page_special.java index a27517de8..93ab4f6e4 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/specials/Rndm_page_special.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/specials/Rndm_page_special.java @@ -13,3 +13,21 @@ 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.addons.wikis.pages.randoms.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.randoms.*; +import gplx.xowa.specials.*; import gplx.xowa.wikis.nss.*; +public class Rndm_page_special implements Xow_special_page { + public void Special__gen(Xow_wiki wikii, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl) { + Xowe_wiki wiki = (Xowe_wiki)wikii; Xoae_page page = (Xoae_page)pagei; + Xow_ns ns = wiki.Ns_mgr().Names_get_or_main(ttl.Rest_txt()); + // Rndm_addon.Get(wiki).Mgr().Get_rndm_page_by_ns(ns); + + byte[] random_ttl_bry = wiki.Db_mgr().Load_mgr().Find_random_ttl(ns); + wiki.Data_mgr().Redirect(page, ns.Gen_ttl(random_ttl_bry)); // REDIRECT:as per Special:Random + } + + public static final String SPECIAL_KEY = "Randompage"; // NOTE: needs to match lang.gfs + public static final byte[] Display_ttl = Bry_.new_a7("Random Page"); + public Xow_special_meta Special__meta() {return new Xow_special_meta(Xow_special_meta_.Src__mw, SPECIAL_KEY, "random");} + public static final Xow_special_page Prototype = new Rndm_page_special(); + public Xow_special_page Special__clone() {return this;} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/specials/Rndm_root_special.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/specials/Rndm_root_special.java index a27517de8..f56fe8a7b 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/specials/Rndm_root_special.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/specials/Rndm_root_special.java @@ -13,3 +13,21 @@ 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.addons.wikis.pages.randoms.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.randoms.*; +import gplx.xowa.wikis.nss.*; import gplx.xowa.specials.*; +public class Rndm_root_special implements Xow_special_page { + public void Special__gen(Xow_wiki wikii, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl) { + Xowe_wiki wiki = (Xowe_wiki)wikii; Xoae_page page = (Xoae_page)pagei; + Xow_ns ns = wiki.Ns_mgr().Names_get_or_main(ttl.Rest_txt()); + // Rndm_addon.Get(wiki).Mgr().Regy().Get_rndm_page_by_ns(ns); + byte[] random_ttl_bry = wiki.Db_mgr().Load_mgr().Find_random_ttl(ns); + byte[] root_bry = Xoa_ttl.Parse(wiki, random_ttl_bry).Root_txt(); + wiki.Data_mgr().Redirect(page, ns.Gen_ttl(root_bry)); + } + + public static final String SPECIAL_KEY = "RandomRootPage"; + public static final byte[] Display_ttl = Bry_.new_a7("Random Root Page"); + public Xow_special_meta Special__meta() {return new Xow_special_meta(Xow_special_meta_.Src__mw, SPECIAL_KEY);} + public static final Xow_special_page Prototype = new Rndm_root_special(); + public Xow_special_page Special__clone() {return this;} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/specials/Rndm_root_special_tst.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/specials/Rndm_root_special_tst.java index a27517de8..4757a7c0e 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/specials/Rndm_root_special_tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/randoms/specials/Rndm_root_special_tst.java @@ -13,3 +13,42 @@ 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.addons.wikis.pages.randoms.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.randoms.*; +import org.junit.*; import gplx.xowa.specials.*; +public class Rndm_root_special_tst { + @Before public void init() {fxt.Clear();} private Rndm_root_special_fxt fxt = new Rndm_root_special_fxt(); + @Test public void Ns_main() { + fxt.Init_create_page("A"); + fxt.Init_create_page("A/B/C"); + fxt.Test_open("Special:RandomRootPage/Main", "A"); // NOTE: result will always be "A"; "A" -> "A"; "A/B/C" -> "A" + } + @Test public void Ns_help() { + fxt.Init_create_page("Help:A"); + fxt.Init_create_page("Help:A/B/C"); + fxt.Test_open("Special:RandomRootPage/Help", "Help:A"); + } +} +class Rndm_root_special_fxt { + private Xop_fxt parser_fxt; private Rndm_root_special special_page; private Xowe_wiki wiki; + public void Clear() { + parser_fxt = new Xop_fxt(); + parser_fxt.Reset(); + wiki = parser_fxt.Wiki(); + special_page = new gplx.xowa.addons.wikis.pages.randoms.specials.Rndm_root_special(); + } + public void Init_create_page(String page) {parser_fxt.Init_page_create(page, page);} + public void Test_open(String special_url, String expd) { + Xoae_page page = Test_special_open(wiki, special_page, special_url); + Tfds.Eq(expd, String_.new_a7(page.Url().Page_bry())); + Tfds.Eq(expd, String_.new_a7(page.Db().Text().Text_bry())); + } + public static Xoae_page Test_special_open(Xowe_wiki wiki, Xow_special_page special_page, String special_url) { + Xoae_page page = wiki.Parser_mgr().Ctx().Page(); + Xoa_url url = wiki.Utl__url_parser().Parse(Bry_.new_u8(special_url)); + page.Url_(url); + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, Bry_.new_a7(special_url)); + page.Ttl_(ttl); + special_page.Special__gen(wiki, page, url, ttl); + return page; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/Xosync_addon.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/Xosync_addon.java index a27517de8..eb387a91a 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/Xosync_addon.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/Xosync_addon.java @@ -13,3 +13,25 @@ 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.addons.wikis.pages.syncs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; +import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.specials.*; +import gplx.xowa.addons.wikis.pages.syncs.specials.*; +public class Xosync_addon implements Xoax_addon_itm, Xoax_addon_itm__special { + public Xow_special_page[] Special_pages() { + return new Xow_special_page[] + { Sync_html_special.Prototype + }; + } + + public static Xosync_addon Get(Xow_wiki wiki) { + Xosync_addon rv = (Xosync_addon)wiki.Addon_mgr().Itms__get_or_null(ADDON_KEY); + if (rv == null) { + rv = new Xosync_addon(); + wiki.Addon_mgr().Itms__add(rv); + } + return rv; + } + + public String Addon__key() {return ADDON_KEY;} private static final String ADDON_KEY = "xowa.pages.syncs"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/Xosync_read_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/Xosync_read_mgr.java index a27517de8..81dbf7166 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/Xosync_read_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/Xosync_read_mgr.java @@ -13,3 +13,128 @@ 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.addons.wikis.pages.syncs.core; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; +import gplx.dbs.*; +import gplx.xowa.htmls.*; +import gplx.xowa.addons.wikis.pages.syncs.dbs.*; +import gplx.xowa.apps.apis.xowa.addons.bldrs.*; +import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.wikis.domains.*; +import gplx.xowa.addons.wikis.pages.syncs.wmapis.*; +public class Xosync_read_mgr implements Gfo_invk { + private int auto_interval = 60 * 24; // in minutes + private Db_conn sync_conn; private Xosync_sync_tbl sync_tbl; + private final Xopg_match_mgr auto_page_matcher = new Xopg_match_mgr(); + private final Xosync_update_mgr update_mgr = new Xosync_update_mgr(); + public void Init_by_wiki(Xow_wiki wiki) { + this.Auto_scope_("*:Main_Page"); + wiki.App().Cfg().Bind_many_wiki(this, wiki, Cfg__manual__enabled, Cfg__auto__enabled, Cfg__auto__interval, Cfg__auto__scope); + } + public boolean Auto_enabled() {return auto_enabled;} private boolean auto_enabled = false; + public boolean Manual_enabled() {return manual_enabled;} private boolean manual_enabled; + public Xoa_ttl Auto_update(Xow_wiki wiki, Xoa_page page, Xoa_ttl page_ttl) { + // skip if not enabled + if (!auto_enabled) return null; + + // skip if home or other + if (Int_.In + ( wiki.Domain_itm().Domain_type_id() + , Xow_domain_tid_.Tid__home + , Xow_domain_tid_.Tid__other)) + return null; + + // init some vars + Xoa_ttl rv = null; + int prv_page_id = -1; + Hash_adp pages = Hash_adp_.New(); + + // loop to handle redirects + while (true) { + // exit early... + if (pages.Has(page_ttl.Full_db()) // ... if circular redirect; EX: A -> B -> A + || pages.Count() == 3) { // ... or too many redirects EX: A -> B -> C -> D + return rv; + } + // else, add to list of pages + else { + pages.Add_as_key_and_val(page_ttl.Full_db()); + } + + // skip if special + if (page_ttl.Ns().Id_is_special()) return rv; + + // skip if it doesn't match criteria in Options + if (!auto_page_matcher.Match(wiki, page_ttl.Full_db())) return rv; + + // get page data based on id + Xowd_page_itm tmp_dbpg = new Xowd_page_itm(); + wiki.Data__core_mgr().Db__core().Tbl__page().Select_by_ttl(tmp_dbpg, page_ttl.Ns(), page_ttl.Page_db()); + + // get sync conn + if (sync_conn == null) { + Io_url sync_db_url = wiki.Fsys_mgr().Root_dir().GenSubFil(wiki.Domain_str() + "-sync.xowa"); + Gfo_usr_dlg_.Instance.Log_many("", "", "page_sync: loading database for page_sync_data; url=~{0}", sync_db_url.Raw()); + sync_conn = Db_conn_bldr.Instance.Get_or_autocreate(true, sync_db_url); + sync_tbl = new Xosync_sync_tbl(sync_conn); + sync_conn.Meta_tbl_assert(sync_tbl); + } + + // get sync_date and check if sync needed + DateAdp sync_date = sync_tbl.Select_sync_date_or_min(tmp_dbpg.Id()); + if (Datetime_now.Get().Diff(sync_date).Total_mins().To_int() <= auto_interval) { + Gfo_usr_dlg_.Instance.Log_many("", "", "page_sync: skipping auto-sync for page; wiki=~{0} page=~{1} sync_date=~{2}", wiki.Domain_bry(), page_ttl.Full_db(), sync_date.XtoStr_fmt_yyyy_MM_dd_HH_mm_ss()); + return rv; + } + else { + Gfo_usr_dlg_.Instance.Log_many("", "", "page_sync: running auto-sync for page; wiki=~{0} page=~{1} sync_date=~{2}", wiki.Domain_bry(), page_ttl.Full_db(), sync_date.XtoStr_fmt_yyyy_MM_dd_HH_mm_ss()); + } + + // auto-sync page + Xoa_app app = wiki.App(); + Xoh_page hpg = new Xoh_page(); + update_mgr.Init_by_app(app); + update_mgr.Init_by_page(wiki, hpg); + Xowm_parse_data parse_data = update_mgr.Update(app.Wmf_mgr().Download_wkr(), wiki, page_ttl); + if (parse_data == null) + return rv; + + // insert into sync_db + Gfo_usr_dlg_.Instance.Log_many("", "", "page_sync: updating sync table; page=~{0}", page_ttl.Full_db()); + sync_tbl.Upsert(parse_data.Page_id(), Datetime_now.Get()); + + // redirect occurred; EX: A -> B will have A,B in pages + if (pages.Count() > 1) { + wiki.Data__core_mgr().Tbl__page().Update__redirect(parse_data.Page_id(), prv_page_id); + } + + // NOTE: set rv to last good page; EX: A -> B; A exists but B doesn't; show A, not B; DATE:2017-05-07 + rv = page_ttl; + + // no redirects + if (parse_data.Redirect_to_ttl == null) + return rv; + // redirect occured; + else { + prv_page_id = parse_data.Page_id(); + page_ttl = wiki.Ttl_parse(parse_data.Redirect_to_ttl); + } + } + } + private void Auto_scope_(String v) { + auto_page_matcher.Set(v); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Cfg__manual__enabled)) this.manual_enabled = m.ReadYn("v"); + else if (ctx.Match(k, Cfg__auto__enabled)) this.auto_enabled = m.ReadYn("v"); + else if (ctx.Match(k, Cfg__auto__interval)) this.auto_interval = m.ReadInt("v"); + else if (ctx.Match(k, Cfg__auto__scope)) Auto_scope_(m.ReadStr("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Cfg__manual__enabled = "xowa.bldr.page_sync.manual.enabled" + , Cfg__auto__enabled = "xowa.bldr.page_sync.auto.enabled" + , Cfg__auto__interval = "xowa.bldr.page_sync.auto.interval" + , Cfg__auto__scope = "xowa.bldr.page_sync.auto.scope" + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/Xosync_update_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/Xosync_update_mgr.java index a27517de8..3fd525ce6 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/Xosync_update_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/Xosync_update_mgr.java @@ -13,3 +13,95 @@ 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.addons.wikis.pages.syncs.core; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; +import gplx.xowa.files.downloads.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.htmls.*; import gplx.langs.htmls.docs.*; +import gplx.xowa.htmls.core.wkrs.*; import gplx.xowa.htmls.core.wkrs.txts.*; import gplx.xowa.htmls.core.hzips.*; +import gplx.xowa.htmls.core.dbs.*; +import gplx.xowa.addons.wikis.pages.syncs.wmapis.*; +import gplx.xowa.addons.wikis.pages.syncs.core.parsers.*; +public class Xosync_update_mgr { + private final Xoh_hzip_bfr bfr = new Xoh_hzip_bfr(Io_mgr.Len_kb, Bool_.N, Byte_.Max_value_127); + private final Gfh_doc_parser hdoc_parser_mgr; + private final Xoh_hdoc_ctx hctx = new Xoh_hdoc_ctx(); + private final Xosync_hdoc_wtr hdoc_bldr = new Xosync_hdoc_wtr(); + private final Xosync_hdoc_parser hdoc_parser_wkr; + private final Xowd_html_tbl_mgr html_tbl_mgr = new Xowd_html_tbl_mgr(); + public Xosync_update_mgr() { + hdoc_parser_wkr = new Xosync_hdoc_parser(hdoc_bldr); + hdoc_parser_mgr = new Gfh_doc_parser(new Xoh_txt_parser(hdoc_bldr), hdoc_parser_wkr); + } + public void Init_by_app(Xoa_app app) { + hctx.Init_by_app(app); + } + public void Init_by_page(Xow_wiki wiki, Xoa_page page) { + hctx.Init_by_page(wiki, page); + page.Hdump_mgr().Clear(); + } + public Xowm_parse_data Update(Xof_download_wkr download_wkr, Xow_wiki wiki, Xoa_ttl page_ttl) { + Xoh_page hpg = (Xoh_page)hctx.Page(); + + // call wmf api + Xowm_parse_wmf parse_wkr = new Xowm_parse_wmf(); + Xowm_parse_data data = parse_wkr.Get_parse_or_null(download_wkr, wiki, page_ttl); + if (data == null) return null; + + // parse html to fix images + Gfo_usr_dlg_.Instance.Log_many("", "", "page_sync: parsing page; page=~{0}", page_ttl.Full_db()); + Parse(hpg, wiki, hctx.Page__url(), data.Revn_html()); + + // init some vars + byte[] html_bry = hpg.Db().Html().Html_bry(); + Xow_db_file html_db = html_tbl_mgr.Get_html_db(wiki); + Xow_db_file core_db = wiki.Data__core_mgr().Db__core(); + Xowd_page_tbl page_tbl = core_db.Tbl__page(); + + // create entry in page_tbl if it does not exist; DATE:2017-05-06 + Xowd_page_itm page_itm = new Xowd_page_itm(); + if (!page_tbl.Select_by_id(page_itm, data.Page_id())) { + // update random + int ns_id = page_ttl.Ns().Id(); + int next_random_id = core_db.Tbl__ns().Select_ns_count(ns_id) + 1; + core_db.Tbl__ns().Update_ns_count(ns_id, next_random_id); + + // insert into page_tbl + page_tbl.Insert_bgn(); + try { + page_tbl.Insert_cmd_by_batch(data.Page_id(), ns_id, page_ttl.Page_db(), false, Datetime_now.Get() + , html_bry.length, next_random_id, -1, html_db.Id(), -1); + } finally { + page_tbl.Insert_end(); + } + } + + // save html + Gfo_usr_dlg_.Instance.Log_many("", "", "page_sync: saving html; page=~{0} html_len=~{1}", page_ttl.Full_db(), Bry_.Len(html_bry)); + html_tbl_mgr.Save_html(wiki, html_db, data.Page_id(), data.Revn_id(), html_bry); + + // download files + Gfo_usr_dlg_.Instance.Log_many("", "", "page_sync: downloading files; page=~{0}", page_ttl.Full_db()); + hpg.Ctor_by_hview(wiki, wiki.Utl__url_parser().Parse(page_ttl.Full_db()), page_ttl, data.Page_id()); + gplx.xowa.files.Xof_file_wkr file_thread = new gplx.xowa.files.Xof_file_wkr + ( wiki.File__orig_mgr(), wiki.File__bin_mgr(), wiki.File__mnt_mgr() + , wiki.App().User().User_db_mgr().Cache_mgr(), wiki.File__repo_mgr(), gplx.xowa.guis.cbks.js.Xog_js_wkr_.Noop, hpg, hpg.Hdump_mgr().Imgs() + ); + gplx.core.threads.Gfo_thread_pool thread_pool = new gplx.core.threads.Gfo_thread_pool(); + thread_pool.Add_at_end(file_thread); + thread_pool.Run(); + + return data; + } + public void Parse(Xoh_page hpg, Xow_wiki wiki, byte[] page_url, byte[] src) { + int src_len = src.length; + + // init_by_page for bldr, parser, hdoc + hctx.Init_by_page(wiki, hpg); + hdoc_bldr.Init_by_page(bfr, hpg, hctx, src, 0, src_len); + hdoc_parser_wkr.Init_by_page(hctx, src, 0, src_len); + + // parse + hdoc_parser_mgr.Parse(page_url, src, 0, src_len); + hpg.Db().Html().Html_bry_(bfr.To_bry_and_clear()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/loaders/Xosync_page_loader.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/loaders/Xosync_page_loader.java index a27517de8..1d8d2a0fd 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/loaders/Xosync_page_loader.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/loaders/Xosync_page_loader.java @@ -13,3 +13,97 @@ 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.addons.wikis.pages.syncs.core.loaders; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; import gplx.xowa.addons.wikis.pages.syncs.core.*; +import gplx.core.brys.*; import gplx.core.btries.*; +import gplx.langs.htmls.*; import gplx.xowa.htmls.*; import gplx.langs.htmls.docs.*; import gplx.xowa.htmls.core.wkrs.*; import gplx.xowa.htmls.core.wkrs.imgs.atrs.*; +import gplx.xowa.files.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.imgs.*; +import gplx.xowa.wikis.domains.*; +import gplx.xowa.addons.wikis.pages.syncs.core.parsers.*; +public class Xosync_page_loader { + private final Xoh_hdoc_ctx hctx = new Xoh_hdoc_ctx(); + private final Gfh_tag_rdr tag_rdr = Gfh_tag_rdr.New__html(); + private final Bry_err_wkr err_wkr = new Bry_err_wkr(); + private final Xoh_img_src_data img_src_parser = new Xoh_img_src_data(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + public byte[] Parse(Xow_wiki wiki, Xoh_page hpg, byte[] src) { + // init hctx, tag_rdr, err_wkr + int src_len = src.length; + hctx.Init_by_page(wiki, hpg); + tag_rdr.Init(hpg.Url_bry_safe(), src, 0, src_len); + err_wkr.Init_by_page(String_.new_u8(hpg.Url_bry_safe()), src); + + // loop for all + int pos = 0; + Btrie_rv trv = new Btrie_rv(); + while (true) { + // get next "" + Gfh_tag img_tag = tag_rdr.Tag__find_fwd_head(pos, src_len, Gfh_tag_.Id__img); + + // none found; add and exit + if (img_tag.Name_id() == Gfh_tag_.Id__eos) { + tmp_bfr.Add_mid(src, pos, src_len); // add bytes between img_end and prv_pos + break; + } + + // add bytes between prv_pos and img_bgn + tmp_bfr.Add_mid(src, pos, img_tag.Src_bgn()); + + // do simple replace for @src + Gfh_atr img_src_atr = img_tag.Atrs__get_by_or_fail(Gfh_atr_.Bry__src); + byte[] img_src_val = img_src_atr.Val(); + byte path_tid = Xosync_img_src_parser.Src_xo_trie.Match_byte_or(trv, img_src_val, Xosync_img_src_parser.Path__unknown); + switch (path_tid) { + case Xosync_img_src_parser.Path__file: + Add_img(wiki, hpg, img_tag, img_src_atr, img_src_val, path_tid, Xosync_img_src_parser.Bry__xowa_file, wiki.App().Fsys_mgr().File_dir().To_http_file_bry()); + break; + case Xosync_img_src_parser.Path__math: + Add_img(wiki, hpg, img_tag, img_src_atr, img_src_val, path_tid, Xosync_img_src_parser.Bry__xowa_math, wiki.App().Fsys_mgr().File_dir().GenSubDir_nest("math").To_http_file_bry()); + break; + } + + pos = img_tag.Src_end(); + } + + // overwrite html + src = tmp_bfr.To_bry_and_clear(); + hpg.Db().Html().Html_bry_(src); + return src; + } + private Xof_fsdb_itm Add_img(Xow_wiki wiki, Xoh_page hpg, Gfh_tag img_tag, Gfh_atr img_src_atr, byte[] img_src_val, byte path_tid, byte[] src_find, byte[] src_repl) { + // replace "xowa:/file" with "file:////xowa/file/" + img_src_val = Bry_.Replace(img_src_val, src_find, src_repl); + + // parse src + img_src_parser.Parse(err_wkr, hctx, wiki.Domain_bry(), img_src_atr.Val_bgn(), img_src_atr.Val_end()); + if (img_src_parser.File_ttl_bry() == null) return null; // skip images that don't follow format of "commons.wikimedia.org/thumb/7/70/A.png"; for example, enlarge buttons + + // create img + Xof_fsdb_itm img = hpg.Img_mgr().Make_img(false); + + // use repo_tid to get fsys_root, orig_repo_name + byte repo_tid = img_src_parser.Repo_tid(); + byte[] orig_repo_name = null, fsys_root = null; + switch (repo_tid) { + case Xof_repo_tid_.Tid__remote: fsys_root = hctx.Fsys__file__comm(); orig_repo_name = Xow_domain_itm_.Bry__commons; break; + case Xof_repo_tid_.Tid__local: fsys_root = hctx.Fsys__file__wiki(); orig_repo_name = wiki.Domain_bry(); break; + case Xof_repo_tid_.Tid__math: fsys_root = hctx.Fsys__file__math(); orig_repo_name = Xof_repo_tid_.Bry__math; break; + } + + // set vals + img.Orig_repo_name_(orig_repo_name); + byte[] file_ttl_bry = gplx.langs.htmls.encoders.Gfo_url_encoder_.Http_url.Decode(img_src_parser.File_ttl_bry()); + Xof_ext file_ext = Xosync_img_src_parser.Ext_by_ttl(file_ttl_bry, repo_tid); + img.Init_by_wm_parse(hctx.Wiki__domain_itm().Abrv_xo(), img_src_parser.Repo_is_commons(), img_src_parser.File_is_orig(), file_ttl_bry, file_ext, img_src_parser.File_w(), img_src_parser.File_time(), img_src_parser.File_page()); + + // recalc src based on "file:////xowa/file/" + hctx.File__url_bldr().Init_by_repo(repo_tid, fsys_root, Bool_.N, Byte_ascii.Slash, Bool_.N, Bool_.N, 4); + hctx.File__url_bldr().Init_by_itm(img_src_parser.File_is_orig() ? Xof_img_mode_.Tid__orig : Xof_img_mode_.Tid__thumb, file_ttl_bry, Xof_file_wkr_.Md5(file_ttl_bry), Xof_ext_.new_by_ttl_(file_ttl_bry), img_src_parser.File_w(), img_src_parser.File_time(), img_src_parser.File_page()); + Io_url html_view_url = hctx.File__url_bldr().Xto_url_by_http(); + + // if (path_tid == Xosync_img_src_parser.Path__file) + img.Init_at_gallery_end(img_tag.Atrs__get_as_int_or(Gfh_atr_.Bry__width,0), img_tag.Atrs__get_as_int_or(Gfh_atr_.Bry__height, 0), html_view_url, html_view_url); + + Xosync_hdoc_parser.Write_img_tag(tmp_bfr, img_tag, img_src_val, img.Html_uid()); + return img; + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/loaders/Xosync_page_loader__fxt.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/loaders/Xosync_page_loader__fxt.java index a27517de8..dd686a671 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/loaders/Xosync_page_loader__fxt.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/loaders/Xosync_page_loader__fxt.java @@ -13,3 +13,38 @@ 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.addons.wikis.pages.syncs.core.loaders; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; import gplx.xowa.addons.wikis.pages.syncs.core.*; +import gplx.core.tests.*; +import gplx.langs.htmls.*; import gplx.xowa.htmls.*; +import gplx.xowa.files.*; import gplx.xowa.files.repos.*; +import gplx.xowa.addons.wikis.pages.syncs.core.parsers.*; +public class Xosync_page_loader__fxt { + private final Xosync_page_loader mgr = new Xosync_page_loader(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + private final Xoh_page hpg = new Xoh_page(); + private Xowe_wiki wiki; + public void Clear() { + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + this.wiki = Xoa_app_fxt.Make__wiki__edit(app); + hpg.Clear(); + } + public Xosync_page_loader__fxt Exec__parse(String raw) { + mgr.Parse(wiki, hpg, Bry_.new_u8(raw)); + return this; + } + public Xosync_page_loader__fxt Test__html(String expd) { + Gftest.Eq__ary__lines(expd, hpg.Db().Html().Html_bry(), "converted html"); + return this; + } + public Xof_fsdb_itm Make__fsdb(boolean repo_is_commons, boolean file_is_orig, String file_ttl, String orig_ext_str, int file_w, double file_time, int file_page) { + Xof_fsdb_itm itm = new Xof_fsdb_itm(); + Xof_ext orig_ext = Xof_ext_.new_by_ext_(Bry_.new_u8(orig_ext_str)); + itm.Init_by_wm_parse(wiki.Domain_itm().Abrv_xo(), repo_is_commons, file_is_orig, Bry_.new_u8(file_ttl), orig_ext, file_w, file_time, file_page); + return itm; + } + public Xosync_page_loader__fxt Test__fsdb(Xof_fsdb_itm expd) { + Xof_fsdb_itm actl = (Xof_fsdb_itm)hpg.Img_mgr().Get_at(0); + Gftest.Eq__str(Xosync_hdoc_parser__fxt.To_str(tmp_bfr, expd), Xosync_hdoc_parser__fxt.To_str(tmp_bfr, actl)); + return this; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/loaders/Xosync_page_loader__tst.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/loaders/Xosync_page_loader__tst.java index a27517de8..ec9e111ed 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/loaders/Xosync_page_loader__tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/loaders/Xosync_page_loader__tst.java @@ -13,3 +13,27 @@ 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.addons.wikis.pages.syncs.core.loaders; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; import gplx.xowa.addons.wikis.pages.syncs.core.*; +import org.junit.*; +import gplx.langs.htmls.*; +public class Xosync_page_loader__tst { + @Before public void init() {fxt.Clear();} private final Xosync_page_loader__fxt fxt = new Xosync_page_loader__fxt(); + @Test public void File() { + fxt.Exec__parse(Gfh_utl.Replace_apos("ab")) + .Test__html(Gfh_utl.Replace_apos("ab")) + .Test__fsdb(fxt.Make__fsdb(Bool_.Y, Bool_.N, "Commons-logo.svg", "svg", 12, -1, -1)) + ; + } + @Test public void Math() { + fxt.Exec__parse(Gfh_utl.Replace_apos("ab")) + .Test__html(Gfh_utl.Replace_apos("ab")) + .Test__fsdb(fxt.Make__fsdb(Bool_.Y, Bool_.Y, "596f8baf206a81478afd4194b44138715dc1a05c", "svg", -1, -1, -1)) + ; + } + @Test public void Ogg() { + fxt.Exec__parse(Gfh_utl.Replace_apos("ab")) + .Test__html(Gfh_utl.Replace_apos("ab")) + .Test__fsdb(fxt.Make__fsdb(Bool_.Y, Bool_.N, "A.ogg", "ogv", 320, -1, -1)) + ; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser.java index a27517de8..d991bd04d 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser.java @@ -13,3 +13,92 @@ 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.addons.wikis.pages.syncs.core.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; import gplx.xowa.addons.wikis.pages.syncs.core.*; +import gplx.langs.htmls.*; import gplx.langs.htmls.docs.*; import gplx.xowa.htmls.core.wkrs.*; +public class Xosync_hdoc_parser implements Gfh_doc_wkr { + private final Xosync_hdoc_wtr hdoc_wtr; + private final Gfh_tag_rdr tag_rdr = Gfh_tag_rdr.New__html(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + private final Xosync_img_src_parser img_src_parser = new Xosync_img_src_parser(); + + public Xosync_hdoc_parser(Xosync_hdoc_wtr hdoc_wtr) {this.hdoc_wtr = hdoc_wtr;} + public byte[] Hook() {return Byte_ascii.Angle_bgn_bry;} + public void Init_by_page(Xoh_hdoc_ctx hctx, byte[] src, int src_bgn, int src_end) { + tag_rdr.Init(hctx.Page__url(), src, src_bgn, src_end); + img_src_parser.Init_by_page(hctx); + } + public int Parse(byte[] src, int src_bgn, int src_end, int pos) { + // note that entry point is at "<" + tag_rdr.Pos_(pos); + int nxt_pos = tag_rdr.Pos() + 1; if (nxt_pos == src_end) return src_end; + + // check if head or tail; EX: "" vs "" + byte nxt_byte = src[nxt_pos]; + // skip comment; needed else comment may gobble up rest of text; see test; DATE:2016-09-10 + if (nxt_byte == Byte_ascii.Bang) { // assume comment; EX:" + String err_msg = img_src_parser.Err_msg(); + if (err_msg != null) { + hdoc_wtr.Add_bry(Gfh_tag_.Comm_bgn); + hdoc_wtr.Add_str(img_src_parser.Err_msg()); + hdoc_wtr.Add_bry(Gfh_tag_.Comm_end); + } + + // get img_src; use img_src_parser if no error, else use original value + byte[] img_src_val = err_msg == null ? img_src_parser.To_bry() : src_atr.Val(); + + // write html + Write_img_tag(tmp_bfr, img_tag, img_src_val, -1); + hdoc_wtr.Add_bfr(tmp_bfr); + + return img_tag.Src_end(); + } + public static void Write_img_tag(Bry_bfr bfr, Gfh_tag img_tag, byte[] img_src_val, int uid) { + // rewrite tag with custom img_src_val + int atrs_len = img_tag.Atrs__len(); + bfr.Add(Byte_ascii.Angle_bgn_bry); + bfr.Add(Gfh_tag_.Bry__img); + if (uid != -1) { + Gfh_atr_.Add(bfr, Gfh_atr_.Bry__id, Bry_.new_a7("xoimg_" + Int_.To_str(uid))); + } + for (int i = 0; i < atrs_len; ++i) { + Gfh_atr atr = img_tag.Atrs__get_at(i); + // if atr is src use img_src_val; EX: ' src="//upload.wikimedia.org/..."' -> ' src="xowa:/file/..." + Gfh_atr_.Add(bfr, atr.Key(), Bry_.Eq(atr.Key(), Gfh_atr_.Bry__src) ? img_src_val : atr.Val()); + } + bfr.Add(Byte_ascii.Angle_end_bry); + } + private static final byte[] Bry__span__edit_section = Bry_.new_a7("mw-editsection"); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__err__tst.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__err__tst.java index a27517de8..06a092ff5 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__err__tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__err__tst.java @@ -13,3 +13,33 @@ 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.addons.wikis.pages.syncs.core.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; import gplx.xowa.addons.wikis.pages.syncs.core.*; +import org.junit.*; +import gplx.langs.htmls.*; +public class Xosync_hdoc_parser__err__tst { + @Before public void init() {fxt.Clear();} private final Xosync_hdoc_parser__fxt fxt = new Xosync_hdoc_parser__fxt(); + @Test public void Url_does_not_start_with_upload_wikimedia_org() { + fxt.Exec__parse(Gfh_utl.Replace_apos("")) + .Test__html(Gfh_utl.Replace_apos("")); + } + @Test public void Unknown_repo() { + fxt.Exec__parse(Gfh_utl.Replace_apos("")) + .Test__html(Gfh_utl.Replace_apos("")); + } + @Test public void Bad_md5() { + fxt.Exec__parse(Gfh_utl.Replace_apos("")) + .Test__html(Gfh_utl.Replace_apos("")); + } + @Test public void Missing_px() { + fxt.Exec__parse(Gfh_utl.Replace_apos("")) + .Test__html(Gfh_utl.Replace_apos("")); + } + @Test public void Bad_file_w() { + fxt.Exec__parse(Gfh_utl.Replace_apos("")) + .Test__html(Gfh_utl.Replace_apos("")); + } + @Test public void Comment() { + fxt.Exec__parse(Gfh_utl.Replace_apos("abc")) + .Test__html(Gfh_utl.Replace_apos("abc")); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__file__tst.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__file__tst.java index a27517de8..cce71150e 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__file__tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__file__tst.java @@ -13,3 +13,44 @@ 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.addons.wikis.pages.syncs.core.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; import gplx.xowa.addons.wikis.pages.syncs.core.*; +import org.junit.*; +import gplx.langs.htmls.*; +public class Xosync_hdoc_parser__file__tst { + @Before public void init() {fxt.Clear();} private final Xosync_hdoc_parser__fxt fxt = new Xosync_hdoc_parser__fxt(); + @Test public void Commons__thumb() { + fxt.Exec__parse(Gfh_utl.Replace_apos("")) + .Test__html(Gfh_utl.Replace_apos("")) + .Test__fsdb(fxt.Make__fsdb(Bool_.Y, Bool_.N, "A.png", 320, -1, -1)); + } + @Test public void Url_encoded() { + fxt.Exec__parse(Gfh_utl.Replace_apos("")) + .Test__html(Gfh_utl.Replace_apos("")) + .Test__fsdb(fxt.Make__fsdb(Bool_.Y, Bool_.N, "A,B.png", 320, -1, -1)); + } + @Test public void Local__orig() { + fxt.Exec__parse(Gfh_utl.Replace_apos("")) + .Test__html(Gfh_utl.Replace_apos("")) + .Test__fsdb(fxt.Make__fsdb(Bool_.N, Bool_.Y, "A.png", -1, -1, -1)); + } + @Test public void Svg() { + fxt.Exec__parse(Gfh_utl.Replace_apos("")) + .Test__html(Gfh_utl.Replace_apos("")) + .Test__fsdb(fxt.Make__fsdb(Bool_.Y, Bool_.N, "A.svg", 12, -1, -1)); + } + @Test public void Ogg() { + fxt.Exec__parse(Gfh_utl.Replace_apos("")) + .Test__html(Gfh_utl.Replace_apos("")) + .Test__fsdb(fxt.Make__fsdb(Bool_.Y, Bool_.N, "A.ogg", "ogv", 320, -1, -1)); + } + @Test public void Ogg__time() { + fxt.Exec__parse(Gfh_utl.Replace_apos("")) + .Test__html(Gfh_utl.Replace_apos("")) + .Test__fsdb(fxt.Make__fsdb(Bool_.Y, Bool_.N, "A.ogg", "ogv", 320, 1.2, -1)); + } + @Test public void Pdf__page() { + fxt.Exec__parse(Gfh_utl.Replace_apos("")) + .Test__html(Gfh_utl.Replace_apos("")) + .Test__fsdb(fxt.Make__fsdb(Bool_.Y, Bool_.N, "A.djvu", 320, -1, 1)); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__fxt.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__fxt.java index a27517de8..55293161e 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__fxt.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__fxt.java @@ -13,3 +13,57 @@ 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.addons.wikis.pages.syncs.core.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; import gplx.xowa.addons.wikis.pages.syncs.core.*; +import gplx.core.tests.*; +import gplx.langs.htmls.*; import gplx.xowa.htmls.*; +import gplx.xowa.files.*; import gplx.xowa.files.repos.*; +public class Xosync_hdoc_parser__fxt { + private final Xosync_update_mgr mgr = new Xosync_update_mgr(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + private final Xoh_page hpg = new Xoh_page(); + private Xowe_wiki wiki; + public void Clear() { + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + this.wiki = Xoa_app_fxt.Make__wiki__edit(app); + Xoa_app_fxt.repo2_(app, wiki); + mgr.Init_by_app(app); + mgr.Init_by_page(wiki, hpg); + } + public Xosync_hdoc_parser__fxt Exec__parse(String raw) { + mgr.Parse(hpg, wiki, Bry_.Empty, Bry_.new_u8(raw)); + return this; + } + public Xosync_hdoc_parser__fxt Test__html(String expd) { + Gftest.Eq__ary__lines(expd, hpg.Db().Html().Html_bry(), "converted html"); + return this; + } + public Xof_fsdb_itm Make__fsdb(boolean repo_is_commons, boolean file_is_orig, String file_ttl, int file_w, double file_time, int file_page) { + return Make__fsdb(repo_is_commons, file_is_orig, file_ttl, Xof_ext_.new_by_ttl_(Bry_.new_u8(file_ttl)), file_w, file_time, file_page); + } + public Xof_fsdb_itm Make__fsdb(boolean repo_is_commons, boolean file_is_orig, String file_ttl, String file_ext, int file_w, double file_time, int file_page) { + return Make__fsdb(repo_is_commons, file_is_orig, file_ttl, Xof_ext_.new_by_ext_(Bry_.new_u8(file_ext)), file_w, file_time, file_page); + } + public Xof_fsdb_itm Make__fsdb(boolean repo_is_commons, boolean file_is_orig, String file_ttl, Xof_ext file_ext, int file_w, double file_time, int file_page) { + Xof_fsdb_itm itm = new Xof_fsdb_itm(); + itm.Init_by_wm_parse(wiki.Domain_itm().Abrv_xo(), repo_is_commons, file_is_orig, Bry_.new_u8(file_ttl), file_ext, file_w, file_time, file_page); + return itm; + } + public Xosync_hdoc_parser__fxt Test__fsdb(Xof_fsdb_itm expd) { + Xof_fsdb_itm actl = (Xof_fsdb_itm)hpg.Hdump_mgr().Imgs().Get_at(0); + Gftest.Eq__str(To_str(tmp_bfr, expd), To_str(tmp_bfr, actl)); + return this; + } + public static String To_str(Bry_bfr tmp_bfr, Xof_fsdb_itm itm) { + To_bfr(tmp_bfr, itm); + return tmp_bfr.To_str_and_clear(); + } + private static void To_bfr(Bry_bfr bfr, Xof_fsdb_itm itm) { + bfr.Add_str_a7(itm.Orig_repo_id() == Xof_repo_tid_.Tid__remote ? "remote" : "local").Add_byte_pipe(); + bfr.Add_str_a7(itm.File_is_orig() ? "orig" : "thumb").Add_byte_pipe(); + bfr.Add(itm.Orig_ttl()).Add_byte_pipe(); + bfr.Add(itm.Orig_ext().Ext()).Add_byte_pipe(); + bfr.Add_int_variable(itm.File_w()).Add_byte_pipe(); + bfr.Add_double(itm.Lnki_time()).Add_byte_pipe(); + bfr.Add_int_variable(itm.Lnki_page()).Add_byte_pipe(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__misc__tst.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__misc__tst.java index a27517de8..37e6bc779 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__misc__tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__misc__tst.java @@ -13,3 +13,14 @@ 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.addons.wikis.pages.syncs.core.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; import gplx.xowa.addons.wikis.pages.syncs.core.*; +import org.junit.*; +import gplx.langs.htmls.*; +public class Xosync_hdoc_parser__misc__tst { + @Before public void init() {fxt.Clear();} private final Xosync_hdoc_parser__fxt fxt = new Xosync_hdoc_parser__fxt(); + @Test public void Math() { + fxt.Exec__parse(Gfh_utl.Replace_apos("")) + .Test__html(Gfh_utl.Replace_apos("")) + .Test__fsdb(fxt.Make__fsdb(Bool_.Y, Bool_.Y, "596f8baf206a81478afd4194b44138715dc1a05c.svg", -1, -1, -1)); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__tst.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__tst.java index a27517de8..4b1c97f33 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_parser__tst.java @@ -13,3 +13,27 @@ 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.addons.wikis.pages.syncs.core.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; import gplx.xowa.addons.wikis.pages.syncs.core.*; +import org.junit.*; +import gplx.langs.htmls.*; +public class Xosync_hdoc_parser__tst { + @Before public void init() {fxt.Clear();} private final Xosync_hdoc_parser__fxt fxt = new Xosync_hdoc_parser__fxt(); + @Test public void Remove_edit() { + fxt.Exec__parse(Gfh_utl.Replace_apos_concat_lines + ( "

    Section_1" + , "" + , "[edit" + , "]" + , "" + , "

    " + )).Test__html(Gfh_utl.Replace_apos_concat_lines + ( "

    Section_1" + , "" + , "

    " + )); + } +// @Test public void Smoke() { +// fxt.Exec__parse(Io_mgr.Instance.LoadFilStr("C:\\xowa\\dev\\wm.updater.src.html")); +// Io_mgr.Instance.SaveFilBry("C:\\xowa\\dev\\wm.updater.trg.html", fxt.Hdoc().Converted()); +// } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_wtr.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_wtr.java index a27517de8..98078977b 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_wtr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_hdoc_wtr.java @@ -13,3 +13,29 @@ 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.addons.wikis.pages.syncs.core.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; import gplx.xowa.addons.wikis.pages.syncs.core.*; +import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.wkrs.*; import gplx.xowa.htmls.core.hzips.*; +public class Xosync_hdoc_wtr implements Xoh_hdoc_wkr { + private Xoh_hzip_bfr bfr; + private byte[] src; + + public void Init_by_page(Xoh_hzip_bfr bfr, Xoh_page hpg, Xoh_hdoc_ctx hctx, byte[] src, int src_bgn, int src_end) { + this.On_new_page(bfr, hpg, hctx, src, src_bgn, src_end); + } + public void On_new_page(Xoh_hzip_bfr bfr, Xoh_page hpg, Xoh_hdoc_ctx hctx, byte[] src, int src_bgn, int src_end) { + this.bfr = bfr; + this.src = src; + } + public void On_txt (int rng_bgn, int rng_end) {bfr.Add_mid(src, rng_bgn, rng_end);} + public void Add_bfr (Bry_bfr v) {bfr.Add_bfr_and_clear(v);} + public void Add_str (String v) {bfr.Add_str_u8(v);} + public void Add_bry (byte[] v) {bfr.Add(v);} + + // not used + public void On_escape (gplx.xowa.htmls.core.wkrs.escapes.Xoh_escape_data data) {} + public void On_xnde (gplx.xowa.htmls.core.wkrs.xndes.Xoh_xnde_parser parser) {} + public void On_lnki (gplx.xowa.htmls.core.wkrs.lnkis.Xoh_lnki_data parser) {} + public void On_thm (gplx.xowa.htmls.core.wkrs.thms.Xoh_thm_data parser) {} + public void On_gly (gplx.xowa.htmls.core.wkrs.glys.Xoh_gly_grp_data parser) {} + public boolean Process_parse(Xoh_data_itm data) {return false;} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_img_src_parser.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_img_src_parser.java index a27517de8..3d0377a20 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_img_src_parser.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xosync_img_src_parser.java @@ -13,3 +13,220 @@ 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.addons.wikis.pages.syncs.core.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; import gplx.xowa.addons.wikis.pages.syncs.core.*; +import gplx.core.brys.*; import gplx.core.btries.*; +import gplx.xowa.files.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.imgs.*; +import gplx.langs.htmls.*; import gplx.xowa.htmls.core.wkrs.*; +import gplx.xowa.wikis.domains.*; +public class Xosync_img_src_parser { + private final Bry_rdr rdr = new Bry_rdr().Dflt_dlm_(Byte_ascii.Slash); + private final Xof_url_bldr url_bldr = Xof_url_bldr.new_v2(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + private final byte[] wiki_abrv_commons; + + private Xoh_hdoc_ctx hctx; + private byte path_tid; + private byte[] img_src_bgn_local, img_src_bgn_remote; + private byte[] page_url, repo_local; + private byte[] raw; + + public boolean Repo_is_commons() {return repo_is_commons;} private boolean repo_is_commons; + public byte[] File_ttl_bry() {return file_ttl_bry;} private byte[] file_ttl_bry; + public boolean File_is_orig() {return file_is_orig;} private boolean file_is_orig; + public Xof_ext File_ext() {return file_ext;} private Xof_ext file_ext; + public int File_w() {return file_w;} private int file_w; + public double File_time() {return file_time;} private double file_time; + public int File_page() {return file_page;} private int file_page; + public String Err_msg() {return err_msg;} private String err_msg; + + public Xosync_img_src_parser() { + rdr.Err_wkr().Fail_throws_err_(false); + img_src_bgn_remote = tmp_bfr.Add(Bry__xowa_file).Add(Xow_domain_itm_.Bry__commons).Add_byte_slash().To_bry_and_clear(); + wiki_abrv_commons = Xow_abrv_xo_.To_bry(Xow_domain_itm_.Bry__commons); + } + public void Init_by_page(Xoh_hdoc_ctx hctx) { + this.hctx = hctx; + this.page_url = hctx.Page__url(); + this.path_tid = Path__unknown; + this.repo_local = To_wmf_repo_or_null(tmp_bfr, hctx.Wiki__domain_itm()); + if (repo_local == null) Gfo_usr_dlg_.Instance.Warn_many("", "", "unsupported wmf repo; domain=~{0}", hctx.Wiki__domain_itm().Domain_bry()); + img_src_bgn_local = tmp_bfr.Add(Bry__xowa_file).Add(hctx.Wiki__domain_bry()).Add_byte_slash().To_bry_and_clear(); // EX: "xowa:/file/en.wikipedia.org/" + } + public boolean Parse(byte[] raw) { + // init + this.Clear(); + + // set raw, raw_len; exit if empty + this.raw = raw; + int raw_len = raw.length; + if (raw_len == 0) return Fail("empty img_src"); + rdr.Init_by_src(raw); + + // check "//upload.wikimedia.org/" at bgn + this.path_tid = rdr.Chk_or(path_trie, Path__unknown); + switch (path_tid) { + case Path__file: return Parse_file(raw_len); + case Path__math: return Parse_math(raw_len); + default: return Fail("img src does not start with known sequence"); + } + } + private boolean Parse_file(int raw_len) { + // get repo: either "wikipedia/commons/" or "wiki_type/wiki_lang/"; EX:"wiktionary/fr" + if (rdr.Is(Bry__repo_remote)) + this.repo_is_commons = true; + else { + if (!rdr.Is(repo_local)) return Fail("unknown repo"); + } + + // get file_is_orig; note omitting "else" b/c default is file_is_orig == false + if (!rdr.Is(Bry__thumb)) file_is_orig = true; // no "/thumb"; + + // check md5 + if (!Check_md5()) return Fail("invalid md5"); + + // get file_ttl + int file_ttl_bgn = rdr.Pos(); + int file_ttl_end = rdr.Find_fwd_lr_or(Byte_ascii.Slash, raw_len); + file_ttl_bry = Bry_.Mid(raw, file_ttl_bgn, file_ttl_end); + file_ttl_bry = gplx.langs.htmls.encoders.Gfo_url_encoder_.Http_url.Decode(file_ttl_bry); // NOTE: @src is always url-encoded; file_ttl_bry is un-encoded (for MD5, database lookups, etc.) + this.file_ext = Xof_ext_.new_by_ttl_(file_ttl_bry); + if (file_ext.Id_is_ogg()) file_ext = Xof_ext_.new_by_id_(Xof_ext_.Id_ogv); + + + // if thumb, get file_w, file_time, file_page + if (!file_is_orig) { + // if "page", then file_page exists; EX: // "page1-320px" + if (rdr.Is(Bry__page)) { + int file_page_bgn = rdr.Pos(); + int file_page_end = rdr.Find_fwd_lr(Byte_ascii.Dash); + file_page = Bry_.To_int_or_fail(raw, file_page_bgn, file_page_end); + } + + // get file_w; EX: "320px-" + int file_w_bgn = rdr.Pos(); + int file_w_end = rdr.Find_fwd_lr(Bry__px); + if (file_w_end == -1) return Fail("missing px"); + file_w = Bry_.To_int_or(raw, file_w_bgn, file_w_end, -1); + if (file_w == -1) return Fail("invalid file_w"); + + // get time via "-seek%3D"; EX: "320px-seek%3D67-" + int seek_end = rdr.Find_fwd_rr(Bry__seek); + if (seek_end != Bry_find_.Not_found) { + int file_time_bgn = rdr.Pos(); + int file_time_end = rdr.Find_fwd_lr(Byte_ascii.Dash); + file_time = Bry_.To_double(raw, file_time_bgn, file_time_end); + } + } + + // make image + Add_img(hctx.Wiki__domain_itm().Abrv_xo()); + return true; + } + private boolean Parse_math(int raw_len) { + // set file_ttl_bry to rest of src + ".svg"; EX: "https://wikimedia.org/api/rest_v1/media/math/render/svg/596f8baf206a81478afd4194b44138715dc1a05c" -> "596f8baf206a81478afd4194b44138715dc1a05c.svg" + this.file_ttl_bry = Bry_.Add(Bry_.Mid(raw, rdr.Pos(), raw_len), Byte_ascii.Dot_bry, Xof_ext_.Bry_svg); + this.repo_is_commons = true; + this.file_is_orig = true; + this.file_ext = Xof_ext_.new_by_id_(Xof_ext_.Id_svg); + + Add_img(wiki_abrv_commons); + return true; + } + private void Add_img(byte[] wiki_abrv) { + Xof_fsdb_itm itm = new Xof_fsdb_itm(); + hctx.Page().Hdump_mgr().Imgs().Add(itm); + itm.Init_by_wm_parse(wiki_abrv, repo_is_commons, file_is_orig, file_ttl_bry, file_ext, file_w, file_time, file_page); + } + public byte[] To_bry() { + switch (path_tid) { + case Path__file: To_bfr_file(tmp_bfr); break; + case Path__math: To_bfr_math(tmp_bfr); break; + } + return tmp_bfr.To_bry_and_clear(); + } + private void To_bfr_file(Bry_bfr bfr) { // EX:'xowa:/file/commons.wikimedia.org/thumb/7/0/1/c/A.png/220px.png' + // init repo; either "xowa:/file/commons.wikimedia.org" or "xowa:/file/en.wikipedia.org" + byte repo_tid = repo_is_commons ? Xof_repo_tid_.Tid__remote : Xof_repo_tid_.Tid__local; + byte[] fsys_root = repo_is_commons ? img_src_bgn_remote : img_src_bgn_local; + url_bldr.Init_by_repo(repo_tid, fsys_root, Bool_.N, Byte_ascii.Slash, Bool_.N, Bool_.N, 4); + + // set other props and generate url; + url_bldr.Init_by_itm(file_is_orig ? Xof_img_mode_.Tid__orig : Xof_img_mode_.Tid__thumb, gplx.langs.htmls.encoders.Gfo_url_encoder_.Http_url.Encode(file_ttl_bry), Xof_file_wkr_.Md5(file_ttl_bry), Xof_ext_.new_by_ttl_(file_ttl_bry), file_w, file_time, file_page); + bfr.Add(url_bldr.Xto_bry()); + } + private void To_bfr_math(Bry_bfr bfr) { // EX:'xowa:/math/596f8baf206a81478afd4194b44138715dc1a05c + bfr.Add(Bry__xowa_math).Add(file_ttl_bry); + } + private void Clear() { + this.file_ttl_bry = null; + this.repo_is_commons = false; + this.file_is_orig = false; + this.file_w = -1; + this.file_time = -1; + this.file_page = -1; + this.err_msg = null; + this.raw = null; + } + private boolean Fail(String fmt) { + this.err_msg = "wm.parse:" + fmt; + String msg = String_.Format("", err_msg + "; page={0} raw={1}", page_url, raw); + Gfo_usr_dlg_.Instance.Warn_many("", "", msg); + return false; + } + private boolean Check_md5() { // check if md5; also, skip past md5; EX: "a/a0/" + int pos = rdr.Pos(); + if (!Byte_.Match_all(Byte_ascii.Slash, raw[pos + 1], raw[pos + 4])) return false; // check slashes + byte b_0 = raw[pos + 0], b_2 = raw[pos + 2]; + if (b_0 != b_2) return false; // WM repeats 1st MD5 char; EX: "a" in "a/a0" + if (!gplx.core.encoders.Hex_utl_.Is_hex_many(b_0, b_2, raw[pos + 3])) return false; // check rest is hex + rdr.Move_to(pos + 5); + return true; + } + + private static final byte[] + Bry__repo_remote = Bry_.new_a7("wikipedia/commons/") + , Bry__thumb = Bry_.new_a7("thumb/") + , Bry__px = Bry_.new_a7("px") + , Bry__seek = Bry_.new_a7("-seek%3D") + , Bry__page = Bry_.new_a7("page") + ; + public static final byte Path__unknown = 0, Path__file = 1, Path__math = 2; + private final Btrie_slim_mgr path_trie = Btrie_slim_mgr.cs() + .Add_str_byte("//upload.wikimedia.org/", Path__file) + .Add_str_byte("https://wikimedia.org/api/rest_v1/media/math/render/svg/", Path__math) + ; + + public static final byte[] Bry__xowa_file = Bry_.new_a7("xowa:/file/"), Bry__xowa_math = Bry_.new_a7("xowa:/math/"); + public static Btrie_slim_mgr Src_xo_trie = Btrie_slim_mgr.cs() + .Add_bry_byte(Bry__xowa_file, Path__file) + .Add_bry_byte(Bry__xowa_math, Path__math) + ; + + private static byte[] To_wmf_repo_or_null(Bry_bfr bfr, Xow_domain_itm domain_itm) { + // add type; EX: "fr.wiktionary.org" -> "wiktionary/" + switch (domain_itm.Domain_type_id()) { + case Xow_domain_tid_.Tid__wikipedia: + case Xow_domain_tid_.Tid__wiktionary: + case Xow_domain_tid_.Tid__wikisource: + case Xow_domain_tid_.Tid__wikivoyage: + case Xow_domain_tid_.Tid__wikiquote: + case Xow_domain_tid_.Tid__wikibooks: + case Xow_domain_tid_.Tid__wikiversity: + case Xow_domain_tid_.Tid__wikinews: + bfr.Add(domain_itm.Domain_type().Key_bry()).Add_byte_slash(); + break; + default: + return null; + } + + // add lang; EX: "fr.wiktionary.org" -> "fr/" + bfr.Add(domain_itm.Lang_orig_key()).Add_byte_slash(); + return bfr.To_bry_and_clear(); + } + public static Xof_ext Ext_by_ttl(byte[] file_ttl_bry, byte repo_tid) { + Xof_ext rv = Xof_ext_.new_by_ttl_(file_ttl_bry); + if (rv.Id_is_ogg()) rv = Xof_ext_.new_by_id_(Xof_ext_.Id_ogv); + if (repo_tid == Xof_repo_tid_.Tid__math) rv = Xof_ext_.new_by_id_(Xof_ext_.Id_svg); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xowd_html_tbl_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xowd_html_tbl_mgr.java index a27517de8..511c9c443 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xowd_html_tbl_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/core/parsers/Xowd_html_tbl_mgr.java @@ -13,3 +13,24 @@ 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.addons.wikis.pages.syncs.core.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; import gplx.xowa.addons.wikis.pages.syncs.core.*; +import gplx.core.ios.streams.*; +import gplx.xowa.htmls.core.dbs.*; import gplx.xowa.htmls.core.hzips.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.wikis.pages.*; +public class Xowd_html_tbl_mgr { + public Xow_db_file Get_html_db(Xow_wiki wiki) { + return wiki.Data__core_mgr().Dbs__assert_by_tid(Xow_db_file_.Tid__html_user); + } + public void Save_html(Xow_wiki wiki, Xow_db_file db, int page_id, int revn_id, byte[] src) { + Xowd_html_tbl tbl = new Xowd_html_tbl(db.Conn()); + + // set other html props to null; TODO_FUTURE:need to parse html to get these + byte[] display_ttl = null; + byte[] content_sub = null; + byte[] sidebar_div = null; + db.Conn().Meta_tbl_assert(tbl); + tbl.Upsert(page_id, Xopg_module_mgr.Tid_null, Io_stream_tid_.Tid__raw, Xoh_hzip_dict_.Hzip__plain, display_ttl, content_sub, sidebar_div, src); + + wiki.Data__core_mgr().Db__core().Tbl__page().Update__html_db_id(page_id, db.Id()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/dbs/Xosync_sync_tbl.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/dbs/Xosync_sync_tbl.java index a27517de8..6d0de34b9 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/dbs/Xosync_sync_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/dbs/Xosync_sync_tbl.java @@ -13,3 +13,27 @@ 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.addons.wikis.pages.syncs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; +import gplx.dbs.*; import gplx.dbs.utls.*; +public class Xosync_sync_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_page_id, fld_sync_date; + private final Db_conn conn; + public Xosync_sync_tbl(Db_conn conn) { + this.conn = conn; + this.fld_page_id = flds.Add_int_pkey("page_id"); + this.fld_sync_date = flds.Add_str("sync_date", 32); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name = "sync"; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public DateAdp Select_sync_date_or_min(int page_id) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_page_id).Crt_int(fld_page_id, page_id).Exec_select__rls_auto(); + try {return rdr.Move_next() ? rdr.Read_date_by_str(fld_sync_date) : DateAdp_.MinValue;} + finally {rdr.Rls();} + } + public void Upsert(int page_id, DateAdp sync_date) { + Db_tbl__crud_.Upsert(conn, tbl_name, flds, String_.Ary(fld_page_id), page_id, sync_date.XtoStr_fmt_yyyyMMdd_HHmmss()); + } + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/specials/Sync_html_special.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/specials/Sync_html_special.java index a27517de8..6ea2e5d16 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/specials/Sync_html_special.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/specials/Sync_html_special.java @@ -13,3 +13,33 @@ 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.addons.wikis.pages.syncs.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; +import gplx.core.net.qargs.*; +import gplx.xowa.specials.*; import gplx.xowa.wikis.nss.*; +import gplx.xowa.htmls.*; +import gplx.xowa.addons.wikis.pages.syncs.core.*; +public class Sync_html_special implements Xow_special_page { + public void Special__gen(Xow_wiki wiki, Xoa_page page, Xoa_url url, Xoa_ttl ttl) { + Gfo_qarg_mgr url_args = new Gfo_qarg_mgr().Init(url.Qargs_ary()); + + // get args + byte[] redirect_bry = url_args.Read_bry_or(Bry_.new_a7("page"), null); + Xoa_ttl redirect_ttl = wiki.Ttl_parse(redirect_bry); + // Xoa_url redirect_url = wiki.Utl__url_parser().Parse(redirect_bry); + + // update + Xosync_update_mgr updater = new Xosync_update_mgr(); + updater.Init_by_app(wiki.App()); + Xoh_page hpg = new Xoh_page(); + updater.Init_by_page(wiki, hpg); + updater.Update(wiki.App().Wmf_mgr().Download_wkr(), wiki, redirect_ttl); + ((Xowe_wiki)wiki).Data_mgr().Redirect((Xoae_page)page, redirect_bry); // HACK: should call page.Redirect_trail() below, but need to handle Display_ttl + // page.Redirect_trail().Itms__add__article(redirect_url, redirect_ttl, null); + } + + public static final String SPECIAL_KEY = "XowaSyncHtml"; // NOTE: needs to match lang.gfs + public static final byte[] Display_ttl = Bry_.new_a7("Sync HTML"); + public Xow_special_meta Special__meta() {return new Xow_special_meta(Xow_special_meta_.Src__mw, SPECIAL_KEY);} + public static final Xow_special_page Prototype = new Sync_html_special(); + public Xow_special_page Special__clone() {return this;} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/wmapis/Xowm_parse_data.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/wmapis/Xowm_parse_data.java index a27517de8..4ec9e7c96 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/wmapis/Xowm_parse_data.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/wmapis/Xowm_parse_data.java @@ -13,3 +13,24 @@ 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.addons.wikis.pages.syncs.wmapis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; +public class Xowm_parse_data { + public Xowm_parse_data(byte[] wiki_domain + , int page_id, byte[] page_ttl + , int revn_id, byte[] revn_html) { + this.wiki_domain = wiki_domain; + this.page_id = page_id; + this.page_ttl = page_ttl; + this.revn_id = revn_id; + this.revn_html = revn_html; + } + public byte[] Wiki_domain() {return wiki_domain;} private final byte[] wiki_domain; + + public int Page_id() {return page_id;} private final int page_id; + public byte[] Page_ttl() {return page_ttl;} private final byte[] page_ttl; + + public int Revn_id() {return revn_id;} private final int revn_id; + public byte[] Revn_html() {return revn_html;} private final byte[] revn_html; + + public byte[] Redirect_to_ttl = null; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/wmapis/Xowm_parse_wmf.java b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/wmapis/Xowm_parse_wmf.java index a27517de8..d3792b6b4 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/wmapis/Xowm_parse_wmf.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/pages/syncs/wmapis/Xowm_parse_wmf.java @@ -13,3 +13,71 @@ 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.addons.wikis.pages.syncs.wmapis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.pages.*; import gplx.xowa.addons.wikis.pages.syncs.*; +import gplx.langs.jsons.*; +import gplx.xowa.files.downloads.*; +import gplx.xowa.apps.wms.apis.*; +public class Xowm_parse_wmf { + private final Bry_bfr bfr = Bry_bfr_.New(); + private final Json_parser json_parser = new Json_parser(); + public Xowm_parse_data Get_parse_or_null(Xof_download_wkr download_wkr, Xow_wiki wiki, Xoa_ttl page_ttl) { + if (!gplx.core.ios.IoEngine_system.Web_access_enabled) return null; + byte[] wiki_domain = wiki.Domain_bry(); + byte[] page_full_db = page_ttl.Full_db(); + + // get core json + byte[] core_json = Core_download(bfr, download_wkr, wiki_domain, page_full_db); + Xowm_parse_data rv = Core_parse(json_parser, wiki_domain, page_full_db, core_json); + + // get redirect json + byte[] redirect_json = Redirect_download(bfr, download_wkr, wiki_domain, page_full_db); + Redirect_parse(rv, json_parser, wiki_domain, page_full_db, redirect_json); + + return rv; + } + private static byte[] Core_download(Bry_bfr bfr, Xof_download_wkr download_wkr, byte[] wiki_domain, byte[] page_full_db) { + // build url; EX: "https://en.wikipedia.org/w/api.php?action=parse&format=json&redirects=1&page=Wikipedia:Main%20Page" + Xowm_api_bldr.Bld_bgn(bfr, wiki_domain); + bfr.Add_str_a7("action=parse&format=json&page="); // NOTE:do not add redirects=1; want to get "actual" html, not "redirected" html; DATE:2017-05-06 + bfr.Add(page_full_db); + + // download + return download_wkr.Download_xrg().Exec_as_bry(bfr.To_str_and_clear()); + } + private static Xowm_parse_data Core_parse(Json_parser json_parser, byte[] wiki_domain, byte[] page_full_db, byte[] json) { + Json_doc jdoc = json_parser.Parse(json); + + // get data + Json_nde parse_nde = jdoc.Root_nde().Get_as_nde("parse"); + if (parse_nde == null) return null; // handle pages that don't exist such as s.w:File:AnyFile.png; DATE:2016-11-15 + int page_id = parse_nde.Get_as_int("pageid"); + int revn_id = parse_nde.Get_as_int("revid"); + byte[] page_ttl = Xoa_ttl.Replace_spaces(parse_nde.Get_as_bry("title")); + byte[] revn_html = parse_nde.Get_as_nde("text").Get_as_bry("*"); + + return new Xowm_parse_data(wiki_domain, page_id, page_ttl, revn_id, revn_html); + } + private static byte[] Redirect_download(Bry_bfr bfr, Xof_download_wkr download_wkr, byte[] wiki_domain, byte[] page_full_db) { + // build url; EX: "https://en.wikipedia.org/w/api.php?action=parse&format=json&redirects=1&page=EARTH&prop=" + Xowm_api_bldr.Bld_bgn(bfr, wiki_domain); + bfr.Add_str_a7("action=parse&format=json&redirects=1&prop=&page="); // NOTE:"prop=" will ignore all data except for redirect data + bfr.Add(page_full_db); + + // download + return download_wkr.Download_xrg().Exec_as_bry(bfr.To_str_and_clear()); + } + private static void Redirect_parse(Xowm_parse_data rv, Json_parser json_parser, byte[] wiki_domain, byte[] page_full_db, byte[] json) { + Json_doc jdoc = json_parser.Parse(json); + + // get data; "parse { redirects [{from, to}] }" + Json_nde parse_nde = jdoc.Root_nde().Get_as_nde("parse"); + if (parse_nde == null) return; // handle pages that don't exist such as s.w:File:AnyFile.png; DATE:2016-11-15 + Json_ary redirects_nde = parse_nde.Get_as_ary("redirects"); + if (redirects_nde == null || redirects_nde.Len() == 0) return; + + Json_nde redirect_nde = redirects_nde.Get_as_nde(0); + byte[] redirect_to_bry = redirect_nde.Get_as_bry("to"); + if (redirect_to_bry != null) + rv.Redirect_to_ttl = redirect_to_bry; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/registrys/Wiki_registry_addon.java b/400_xowa/src/gplx/xowa/addons/wikis/registrys/Wiki_registry_addon.java index a27517de8..c23b4191b 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/registrys/Wiki_registry_addon.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/registrys/Wiki_registry_addon.java @@ -13,3 +13,15 @@ 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.addons.wikis.registrys; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; +import gplx.xowa.specials.*; +public class Wiki_registry_addon implements Xoax_addon_itm, Xoax_addon_itm__special { + public Xow_special_page[] Special_pages() { + return new Xow_special_page[] + { gplx.xowa.addons.wikis.registrys.lists.Xow_list_special.Prototype + , gplx.xowa.addons.wikis.registrys.infos.Xow_info_special.Prototype + }; + } + + public String Addon__key() {return "xowa.apps.wikis.registrys";} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/registrys/infos/Xow_info_doc.java b/400_xowa/src/gplx/xowa/addons/wikis/registrys/infos/Xow_info_doc.java index a27517de8..36f5b7d7d 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/registrys/infos/Xow_info_doc.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/registrys/infos/Xow_info_doc.java @@ -13,3 +13,30 @@ 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.addons.wikis.registrys.infos; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.registrys.*; +import gplx.langs.mustaches.*; +class Xow_info_doc implements Mustache_doc_itm { + private final Mustache_doc_itm[] ary; + public Xow_info_doc(Xow_info_doc_wiki itm) {this.ary = new Mustache_doc_itm[] {itm};} + public boolean Mustache__write(String key, Mustache_bfr bfr) {return false;} + public Mustache_doc_itm[] Mustache__subs(String key) { + if (String_.Eq(key, "wiki_info")) return ary; + return Mustache_doc_itm_.Ary__empty; + } +} +class Xow_info_doc_wiki implements Mustache_doc_itm { + private final byte[] domain; + private final String date, size, dir; + public Xow_info_doc_wiki(byte[] domain, String date, String dir, String size) { + this.domain = domain; this.date = date; this.dir = dir; this.size = size; + } + public boolean Mustache__write(String key, Mustache_bfr bfr) { + if (String_.Eq(key, "wiki_domain")) bfr.Add_bry(domain); + else if (String_.Eq(key, "wiki_date")) bfr.Add_str_u8(date); + else if (String_.Eq(key, "wiki_dir")) bfr.Add_str_u8(dir); + else if (String_.Eq(key, "wiki_size")) bfr.Add_str_u8(size); + else return false; + return true; + } + public Mustache_doc_itm[] Mustache__subs(String key) {return Mustache_doc_itm_.Ary__empty;} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/registrys/infos/Xow_info_html.java b/400_xowa/src/gplx/xowa/addons/wikis/registrys/infos/Xow_info_html.java index a27517de8..a4cbce3ce 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/registrys/infos/Xow_info_html.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/registrys/infos/Xow_info_html.java @@ -13,3 +13,50 @@ 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.addons.wikis.registrys.infos; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.registrys.*; +import gplx.xowa.specials.*; import gplx.langs.mustaches.*; import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.tags.*; +import gplx.xowa.users.data.*; +class Xow_info_html extends Xow_special_wtr__base { + private final byte[] wiki_domain; + public Xow_info_html(byte[] wiki_domain) {this.wiki_domain = wiki_domain;} + @Override protected Io_url Get_addon_dir(Xoa_app app) {return app.Fsys_mgr().Http_root().GenSubDir_nest("bin", "any", "xowa", "addon", "wiki", "registry", "info");} + @Override protected Io_url Get_mustache_fil(Io_url addon_dir) {return addon_dir.GenSubFil_nest("bin", "xow_info.mustache.html");} + @Override protected Mustache_doc_itm Bld_mustache_root(Xoa_app app) { + // load itm from db + app.User().User_db_mgr().Init_site_mgr(); // HACK.USER_DB: init site_mgr for desktop + Xoud_site_row site_itm = app.User().User_db_mgr().Site_mgr().Select_by_domain(wiki_domain); + if (site_itm == null) return null; // handle deleted wikis + String wiki_dir = site_itm.Path(); + if (String_.Eq(site_itm.Date(), "")) { + Xow_wiki wiki = app.Wiki_mgri().Get_by_or_make_init_n(wiki_domain); + wiki.Init_by_wiki(); // force init to load Modified_latest + site_itm.Date_(wiki.Props().Modified_latest__yyyy_MM_dd()); + app.User().User_db_mgr().Site_mgr().Update(site_itm); + } + return new Xow_info_doc + ( new Xow_info_doc_wiki(wiki_domain, site_itm.Date(), wiki_dir + , Calc_file_size(Io_url_.new_dir_(wiki_dir))) + ); + } + @Override protected void Bld_tags(Xoa_app app, Io_url addon_dir, Xopage_html_data page_data) { + Xopg_tag_mgr head_tags = page_data.Head_tags(); + Xopg_tag_wtr_.Add__xocss (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xohelp (head_tags, app.Fsys_mgr().Http_root()); + Xopg_alertify_.Add_tags (head_tags, app.Fsys_mgr().Http_root()); + head_tags.Add(Xopg_tag_itm.New_css_file(addon_dir.GenSubFil_nest("bin", "xow_info.css"))); + } + @Override protected void Handle_invalid(Xoa_app app, Xoa_page page, Xow_special_page special) { + new Xopage_html_data(special.Special__meta().Display_ttl(), Bry_.Add(wiki_domain, Bry_.new_a7(" has been deleted"))).Apply(page); + } + + private static String Calc_file_size(Io_url dir) { + Io_url[] urls = Io_mgr.Instance.QueryDir_fils(dir); + int len = urls.length; + long size = 0; + for (int i = 0; i < len; ++i) { + Io_url url = urls[i]; + size += Io_mgr.Instance.QueryFil(url).Size(); + } + return gplx.core.ios.Io_size_.To_str_new(Bry_bfr_.New(), size, 2); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/registrys/infos/Xow_info_special.java b/400_xowa/src/gplx/xowa/addons/wikis/registrys/infos/Xow_info_special.java index a27517de8..cbc9f63c6 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/registrys/infos/Xow_info_special.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/registrys/infos/Xow_info_special.java @@ -13,3 +13,47 @@ 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.addons.wikis.registrys.infos; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.registrys.*; +import gplx.xowa.specials.*; import gplx.core.net.*; import gplx.core.net.qargs.*; import gplx.xowa.wikis.pages.*; +public class Xow_info_special implements Xow_special_page { + public void Special__gen(Xow_wiki wiki, Xoa_page page, Xoa_url url, Xoa_ttl ttl) { + Gfo_qarg_mgr url_args = new Gfo_qarg_mgr().Init(url.Qargs_ary()); + + // if cmd=delete passed; delete "wiki" + byte[] wiki_domain = url_args.Read_bry_or_fail("wiki"); + if (url_args.Read_enm_as_int_or(Enm_cmd.Itm, -1) == Enm_cmd.Tid__delete) { + wiki.App().User().User_db_mgr().Site_mgr().Delete_by_domain(wiki_domain); + Xow_wiki delete_wiki = wiki.App().Wiki_mgri().Get_by_or_make_init_n(wiki_domain); + if (delete_wiki != null) { // guard against revisiting url for deleted wiki + if (delete_wiki.Data__core_mgr() != null) // null check needed in case wiki is not loaded + delete_wiki.Data__core_mgr().Rls(); // release connection if open + Delete_wiki_files(delete_wiki.Fsys_mgr().Root_dir()); + } + page.Redirect_trail().Itms__add__special(wiki, gplx.xowa.addons.wikis.registrys.lists.Xow_list_special.Prototype.Special__meta()); + return; + } + + // show info of wikis + new Xow_info_html(wiki_domain).Bld_page_by_mustache(wiki.App(), page, this); + } + private static void Delete_wiki_files(Io_url root_dir) { + Io_url[] urls = Io_mgr.Instance.QueryDir_fils(root_dir); + for (Io_url url : urls) { + try { + Io_mgr.Instance.DeleteFil(url); + } catch (Exception e) { + Gfo_log_.Instance.Warn("failed to delete wiki file", "wiki", url.Raw(), "err", Err_.Message_gplx_log(e)); + } + } + } + + static class Enm_cmd {//#*nested + public static final int Tid__delete = 0; + public static final Gfo_qarg_enum_itm Itm = new Gfo_qarg_enum_itm("cmd").Add("delete", Tid__delete); + } + + Xow_info_special(Xow_special_meta special__meta) {this.special__meta = special__meta;} + public Xow_special_meta Special__meta() {return special__meta;} private final Xow_special_meta special__meta; + public Xow_special_page Special__clone() {return this;} + public static final Xow_special_page Prototype = new Xow_info_special(Xow_special_meta.New_xo("XowaWikiInfo", "Wiki Info")); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/registrys/lists/Xow_list_doc.java b/400_xowa/src/gplx/xowa/addons/wikis/registrys/lists/Xow_list_doc.java index a27517de8..44f2c748b 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/registrys/lists/Xow_list_doc.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/registrys/lists/Xow_list_doc.java @@ -13,3 +13,32 @@ 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.addons.wikis.registrys.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.registrys.*; +import gplx.langs.mustaches.*; +class Xow_list_doc implements Mustache_doc_itm { + private final byte[] import_root; + private final Xow_list_doc_wiki[] subs; + public Xow_list_doc(byte[] import_root, Xow_list_doc_wiki[] subs) {this.import_root = import_root; this.subs = subs;} + public boolean Mustache__write(String key, Mustache_bfr bfr) { + if (String_.Eq(key, "import_root")) bfr.Add_bry(import_root); + return false; + } + public Mustache_doc_itm[] Mustache__subs(String key) { + if (String_.Eq(key, "subs")) return subs; + return Mustache_doc_itm_.Ary__empty; + } +} +class Xow_list_doc_wiki implements Mustache_doc_itm { + private final byte[] domain; + private final String date; + public Xow_list_doc_wiki(byte[] domain, String date) { + this.domain = domain; this.date = date; + } + public boolean Mustache__write(String key, Mustache_bfr bfr) { + if (String_.Eq(key, "domain")) bfr.Add_bry(domain); + else if (String_.Eq(key, "date")) bfr.Add_str_u8(date); + else return false; + return true; + } + public Mustache_doc_itm[] Mustache__subs(String key) {return Mustache_doc_itm_.Ary__empty;} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/registrys/lists/Xow_list_html.java b/400_xowa/src/gplx/xowa/addons/wikis/registrys/lists/Xow_list_html.java index a27517de8..fe541447e 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/registrys/lists/Xow_list_html.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/registrys/lists/Xow_list_html.java @@ -13,3 +13,30 @@ 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.addons.wikis.registrys.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.registrys.*; +import gplx.xowa.specials.*; import gplx.langs.mustaches.*; import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.tags.*; +import gplx.xowa.wikis.xwikis.*; +import gplx.xowa.users.data.*; +class Xow_list_html extends Xow_special_wtr__base { + @Override protected Io_url Get_addon_dir(Xoa_app app) {return app.Fsys_mgr().Http_root().GenSubDir_nest("bin", "any", "xowa", "addon", "wiki", "registry", "list");} + @Override protected Io_url Get_mustache_fil(Io_url addon_dir) {return addon_dir.GenSubFil_nest("bin", "xow_list.mustache.html");} + @Override protected Mustache_doc_itm Bld_mustache_root(Xoa_app app) { + // make list_mgr based on site_wikis + List_adp list = List_adp_.New(); + app.User().User_db_mgr().Init_site_mgr(); + Xoud_site_row[] site_ary = app.User().User_db_mgr().Site_mgr().Get_all(); + int len = site_ary.length; + for (int i = 0; i < len; ++i) { + Xoud_site_row site_itm = site_ary[i]; + if (String_.Eq(site_itm.Domain(), gplx.xowa.wikis.domains.Xow_domain_itm_.Str__home)) continue; + list.Add(new Xow_list_doc_wiki(Bry_.new_u8(site_itm.Domain()), site_itm.Date())); + } + return new Xow_list_doc(gplx.xowa.addons.wikis.imports.Xow_import_special.Get_root_url(), (Xow_list_doc_wiki[])list.To_ary_and_clear(Xow_list_doc_wiki.class)); + } + @Override protected void Bld_tags(Xoa_app app, Io_url addon_dir, Xopage_html_data page_data) { + Xopg_tag_mgr head_tags = page_data.Head_tags(); + Xopg_tag_wtr_.Add__xocss (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xohelp (head_tags, app.Fsys_mgr().Http_root()); + head_tags.Add(Xopg_tag_itm.New_css_file(addon_dir.GenSubFil_nest("bin", "xow_list.css"))); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/registrys/lists/Xow_list_special.java b/400_xowa/src/gplx/xowa/addons/wikis/registrys/lists/Xow_list_special.java index a27517de8..eed9e14d9 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/registrys/lists/Xow_list_special.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/registrys/lists/Xow_list_special.java @@ -13,3 +13,30 @@ 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.addons.wikis.registrys.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.registrys.*; +import gplx.xowa.specials.*; import gplx.core.net.*; import gplx.core.net.qargs.*; import gplx.xowa.wikis.pages.*; +public class Xow_list_special implements Xow_special_page { + public void Special__gen(Xow_wiki wiki, Xoa_page page, Xoa_url url, Xoa_ttl ttl) { + Gfo_qarg_mgr url_args = new Gfo_qarg_mgr().Init(url.Qargs_ary()); + + // if cmd=add passed; import "file"; occurs when "file" is selected by file_browser + if (url_args.Read_enm_as_int_or(Enm_cmd.Itm, -1) == Enm_cmd.Tid__add) { + byte[] file = url_args.Read_bry_or_fail("file"); + if (wiki.App().Tid_is_edit()) wiki.App().User().User_db_mgr().Init_site_mgr(); // HACK.USER_DB: init site_mgr for desktop + wiki.App().Wiki_mgri().Import_by_url(Io_url_.new_fil_(String_.new_u8(file))); + } + + // show list of wikis + new Xow_list_html().Bld_page_by_mustache(wiki.App(), page, this); + } + + static class Enm_cmd {//#*nested + public static final int Tid__add = 0; + public static final Gfo_qarg_enum_itm Itm = new Gfo_qarg_enum_itm("cmd").Add("add", Tid__add); + } + + Xow_list_special(Xow_special_meta special__meta) {this.special__meta = special__meta;} + public Xow_special_meta Special__meta() {return special__meta;} private final Xow_special_meta special__meta; + public Xow_special_page Special__clone() {return this;} + public static final Xow_special_page Prototype = new Xow_list_special(Xow_special_meta.New_xo("XowaWikiList", "Wikis", "XowaWikis")); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/Srch_search_addon.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/Srch_search_addon.java index a27517de8..3d064e919 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/Srch_search_addon.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/Srch_search_addon.java @@ -13,3 +13,42 @@ 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.addons.wikis.searchs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; +import gplx.xowa.addons.wikis.searchs.dbs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; import gplx.xowa.addons.wikis.searchs.parsers.*; import gplx.xowa.addons.wikis.searchs.searchers.rslts.*; import gplx.xowa.addons.wikis.searchs.searchers.cbks.*; +import gplx.xowa.addons.wikis.searchs.gui.urlbars.*; +import gplx.xowa.langs.cases.*; +public class Srch_search_addon implements Xoax_addon_itm, Srch_search_addon_api { + private final Srch_search_mgr search_mgr; + public Srch_search_addon(Xow_wiki wiki) { + this.wiki_domain = wiki.Domain_bry(); + this.db_mgr = new Srch_db_mgr(wiki.Data__core_mgr()).Init(wiki.Stats().Num_pages()); + this.ttl_parser = new Srch_text_parser().Init_for_ttl(wiki.Case_mgr()); + this.search_mgr = new Srch_search_mgr(this, wiki, ttl_parser); + } + public byte[] Wiki_domain() {return wiki_domain;} private final byte[] wiki_domain; + public Srch_db_mgr Db_mgr() {return db_mgr;} private final Srch_db_mgr db_mgr; + public Srch_text_parser Ttl_parser() {return ttl_parser;} private final Srch_text_parser ttl_parser; + + public void Search(Srch_search_qry qry, Srch_rslt_cbk cbk) {search_mgr.Search(qry, cbk);} + public void Clear_rslts_cache() {search_mgr.Clear_rslts_cache();} + public void Delete_links(int ns_id, int page_id) { + if (!db_mgr.Tbl__word().conn.Meta_tbl_exists(Srch_word_tbl.TABLE_NAME)) return; // NOTE: personal_wikis may not have search_link; exit early else assert will fail; DATE:2017-02-15 + int search_link_db_id = db_mgr.Tbl__link__get_idx(ns_id); + Srch_link_tbl search_link_tbl = db_mgr.Tbl__link__get_at(search_link_db_id); + search_link_tbl.Delete(page_id); + this.Clear_rslts_cache(); + } + + public static final int Score_max = 1000000; + public static final byte Wildcard__star = Byte_ascii.Star; + public static Srch_search_addon Get(Xow_wiki wiki) { + Srch_search_addon rv = (Srch_search_addon)wiki.Addon_mgr().Itms__get_or_null(ADDON_KEY); + if (rv == null) { + rv = new Srch_search_addon(wiki); + wiki.Addon_mgr().Itms__add(rv); + } + return rv; + } + + public String Addon__key() {return ADDON_KEY;} private static final String ADDON_KEY = "xowa.apps.search"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/Srch_search_addon_api.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/Srch_search_addon_api.java index a27517de8..b6a4556ca 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/Srch_search_addon_api.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/Srch_search_addon_api.java @@ -13,3 +13,9 @@ 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.addons.wikis.searchs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; +import gplx.xowa.addons.wikis.searchs.searchers.*; +import gplx.xowa.addons.wikis.searchs.searchers.rslts.*; +public interface Srch_search_addon_api { + void Search(Srch_search_qry qry, Srch_rslt_cbk cbk); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/Xoax_builds_search_addon.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/Xoax_builds_search_addon.java index a27517de8..046318fbc 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/Xoax_builds_search_addon.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/Xoax_builds_search_addon.java @@ -13,3 +13,16 @@ 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.addons.wikis.searchs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; +import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.addons.wikis.searchs.bldrs.cmds.*; +public class Xoax_builds_search_addon implements Xoax_addon_itm, Xoax_addon_itm__bldr { + public Xob_cmd[] Bldr_cmds() { + return new Xob_cmd[] + { Xobldr__link__link_score.Prototype + , Xobldr__page__page_score.Prototype + , Xobldr__word__link_count.Prototype + }; + } + + public String Addon__key() {return "xowa.builds.search";} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/Srch_bldr_cmd.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/Srch_bldr_cmd.java index a27517de8..a3e48e793 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/Srch_bldr_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/Srch_bldr_cmd.java @@ -13,3 +13,20 @@ 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.addons.wikis.searchs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Srch_bldr_cmd extends Xob_cmd__base implements Xob_cmd { + private int commit_interval = 100000, progress_interval = 10000; + public Srch_bldr_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public String Cmd_key() {return Xob_cmd_keys.Key_text_search_cmd;} + @Override public void Cmd_run() { + if (!gplx.core.envs.Env_.Mode_testing()) wiki.Init_assert(); + new Srch_temp_tbl_wkr().Exec_by_cmd(wiki, commit_interval, progress_interval); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_commit_interval_)) commit_interval = m.ReadInt("v"); + else if (ctx.Match(k, Invk_progress_interval_)) progress_interval = m.ReadInt("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_progress_interval_ = "progress_interval_", Invk_commit_interval_ = "commit_interval_"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/Srch_bldr_mgr_.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/Srch_bldr_mgr_.java index a27517de8..eabb0c285 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/Srch_bldr_mgr_.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/Srch_bldr_mgr_.java @@ -13,3 +13,24 @@ 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.addons.wikis.searchs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.xowa.bldrs.*; +import gplx.xowa.addons.wikis.searchs.bldrs.cmds.*; +public class Srch_bldr_mgr_ { + public static void Setup(Xowe_wiki wiki) { + Xoae_app app = wiki.Appe(); + Xob_bldr bldr = app.Bldr(); + + bldr.Cmd_mgr().Add_many(wiki, Xob_cmd_keys.Key_text_search_cmd); + int page_rank_iterations = app.Cfg().Get_int_app_or("xowa.bldr.import.page_rank.iteration_max", 0); + boolean page_rank_enabled = page_rank_iterations > 0; + if (page_rank_enabled) { + bldr.Cmd_mgr().Add(new gplx.xowa.bldrs.cmds.utils.Xob_download_cmd(bldr, wiki).Dump_type_(gplx.xowa.addons.bldrs.wmdumps.pagelinks.bldrs.Pglnk_bldr_cmd.Dump_type_key)); + bldr.Cmd_mgr().Add_many(wiki, gplx.xowa.addons.bldrs.wmdumps.pagelinks.bldrs.Pglnk_bldr_cmd.BLDR_CMD_KEY); + } + bldr.Cmd_mgr().Add(new Xobldr__page__page_score(bldr, wiki).Iteration_max_(page_rank_iterations)); + bldr.Cmd_mgr().Add(new Xobldr__link__link_score(bldr, wiki).Page_rank_enabled_(page_rank_enabled).Delete_plink_db_(Bool_.Y)); + bldr.Cmd_mgr().Add(new Xobldr__word__link_count(bldr, wiki)); + bldr.Cmd_mgr().Add(new gplx.xowa.bldrs.cmds.utils.Xob_delete_cmd(bldr, wiki).Patterns_ary_("*pagelinks.sql", "*pagelinks.sql.gz", "*pagelinks.sqlite3")); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/Srch_bldr_wkr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/Srch_bldr_wkr.java index a27517de8..0b99638f3 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/Srch_bldr_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/Srch_bldr_wkr.java @@ -13,3 +13,23 @@ 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.addons.wikis.searchs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Srch_bldr_wkr implements Xob_page_wkr { + private final Xowe_wiki wiki; + private final Srch_temp_tbl_wkr temp_tbl_wkr = new Srch_temp_tbl_wkr(); + public Srch_bldr_wkr(Xob_bldr bldr, Xowe_wiki wiki) {this.wiki = wiki;} + public String Page_wkr__key() {return Xob_cmd_keys.Key_text_search_wkr;} + public void Page_wkr__bgn() { + temp_tbl_wkr.Init(Bool_.N, wiki); + } + public void Page_wkr__run(gplx.xowa.wikis.data.tbls.Xowd_page_itm page) { + try {temp_tbl_wkr.Exec_by_wkr(page.Id(), page.Ttl_page_db());} + catch (Exception e) {Gfo_usr_dlg_.Instance.Warn_many("", "", "search:error: page=~{0} err=~{1}", page.Ttl_page_db(), Err_.Message_gplx_full(e));} + } + public void Page_wkr__run_cleanup() {} + public void Page_wkr__end() { + temp_tbl_wkr.Term(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return this;} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/Srch_temp_tbl_wkr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/Srch_temp_tbl_wkr.java index a27517de8..8c6086663 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/Srch_temp_tbl_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/Srch_temp_tbl_wkr.java @@ -13,3 +13,205 @@ 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.addons.wikis.searchs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.wikis.searchs.dbs.*; import gplx.xowa.addons.wikis.searchs.parsers.*; +class Srch_temp_tbl_wkr implements Srch_text_parser_wkr { + private Xowe_wiki wiki; private Xow_db_mgr core_data_mgr; private Srch_search_addon search_addon; + private Srch_text_parser title_parser; + private Srch_temp_tbl search_temp_tbl; private int word_id, page_id; // needed for Parse_done + public Srch_temp_tbl_wkr Init(boolean cmd, Xowe_wiki wiki) { + // init + this.wiki = wiki; + this.core_data_mgr = wiki.Db_mgr_as_sql().Core_data_mgr(); + this.search_addon = Srch_search_addon.Get(wiki); + this.title_parser = search_addon.Ttl_parser(); + this.word_id = 0; + // assert search_word / search_link tables + Srch_db_mgr search_db_mgr = search_addon.Db_mgr(); + if (cmd) { // run from maint or from import.offline + if (search_db_mgr.Tbl__word() != null) // called from maint; note that import.offline will always be null (since tables don't exist); DATE:2016-04-04 + search_db_mgr.Delete_all(core_data_mgr); // always delete if launched by cmd + } + search_db_mgr.Create_all(); + // start processing search_temp + this.search_temp_tbl = new Srch_temp_tbl(search_db_mgr.Tbl__word().conn); + search_temp_tbl.Insert_bgn(); + return this; + } + public void Term() { + // end inserts + search_temp_tbl.Insert_end(); + + // init + Srch_db_mgr search_db_mgr = search_addon.Db_mgr().Init(wiki.Stats().Num_pages()); // NOTE: must call .Init for import-offline else Cfg_tbl will be null; note that .Init will bind to newly created search_word / search_link tbl; DATE:2016-04-04 + Db_conn word_conn = search_temp_tbl.conn; + + // update search_word ids if they exist + // Srch_db_mgr.Optimize_unsafe_(word_conn, Bool_.Y); // NOTE: fails in multi-db due to transaction + Update_word_id(word_conn, wiki); + Search_word__insert(word_conn); + // Srch_db_mgr.Optimize_unsafe_(word_conn, Bool_.N); + + // create search_link + Db_conn page_conn = wiki.Data__core_mgr().Tbl__page().Conn(); + if (search_db_mgr.Tbl__link__len() == 1) { + // single_db; just run sql; + Xoa_app_.Usr_dlg().Plog_many("", "", "creating search_link"); + Srch_link_tbl link_tbl = search_db_mgr.Tbl__link__ary()[0]; + new Db_attach_mgr(word_conn, new Db_attach_itm("link_db", link_tbl.conn)) + .Exec_sql(String_.Concat_lines_nl_skip_last + ( "INSERT INTO search_link (word_id, page_id)" + , "SELECT w.word_id" + , ", t.page_id" + , "FROM search_temp t" + , " JOIN search_word w ON t.word_text = w.word_text" + )); + link_tbl.Create_idx__page_id(); + } else { + Search_link__insert(search_db_mgr, word_conn, page_conn); + } + + // remove search_temp + word_conn.Meta_tbl_delete(search_temp_tbl.tbl_name); + } + private static void Search_link__insert(Srch_db_mgr search_db_mgr, Db_conn word_conn, Db_conn page_conn) { + // many_db; split into main links and non-main; ASSUME:link_dbs_is_2 + Db_attach_mgr attach_mgr = new Db_attach_mgr(); + + // dump everything into a temp table in order to index it + page_conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl("page", "page_ns__page_id", "page_namespace", "page_id")); + Srch_db_mgr.Optimize_unsafe_(word_conn, Bool_.Y); + word_conn.Meta_tbl_remake(Dbmeta_tbl_itm.New("search_link_temp", Dbmeta_fld_itm.new_int("word_id"), Dbmeta_fld_itm.new_int("page_id"), Dbmeta_fld_itm.new_int("page_namespace"))); + attach_mgr.Conn_main_(word_conn).Conn_links_(new Db_attach_itm("page_db", page_conn)); + attach_mgr.Exec_sql_w_msg + ( "filling search_link_temp (please wait)", String_.Concat_lines_nl_skip_last + ( "INSERT INTO search_link_temp (word_id, page_id, page_namespace)" + , "SELECT w.word_id" + , ", t.page_id" + , ", p.page_namespace" + , "FROM search_temp t" + , " JOIN search_word w ON t.word_text = w.word_text" + , " JOIN page p ON t.page_id = p.page_id" + )); + word_conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_name("search_link_temp", "main", "page_namespace", "word_id", "page_id")); + Srch_db_mgr.Optimize_unsafe_(word_conn, Bool_.N); + page_conn.Meta_idx_delete("page", "page_ns__page_id"); + + int len = search_db_mgr.Tbl__link__len(); + for (int i = 0; i < len; ++i) { + Xoa_app_.Usr_dlg().Plog_many("", "", "creating search_link_temp: ~{0}", i); + Srch_link_tbl link_tbl = search_db_mgr.Tbl__link__ary()[i]; + Srch_db_mgr.Optimize_unsafe_(link_tbl.conn, Bool_.Y); + attach_mgr.Conn_main_(link_tbl.conn).Conn_links_(new Db_attach_itm("word_db", word_conn)); + attach_mgr.Exec_sql_w_msg + ( Bry_fmt.Make_str("filling search_link: ~{idx} of ~{len}", i, len), String_.Concat_lines_nl_skip_last + ( "INSERT INTO search_link (word_id, page_id)" + , "SELECT t.word_id" + , ", t.page_id" + , "FROM search_link_temp t" + , "WHERE t.page_namespace" + (i == 0 ? " = 0" : " != 0") + )); + link_tbl.Create_idx__page_id(); + Srch_db_mgr.Optimize_unsafe_(link_tbl.conn, Bool_.N); + } + word_conn.Meta_tbl_delete("search_link_temp"); + } + public void Exec_by_wkr(int page_id, byte[] page_ttl) { + this.page_id = page_id; + title_parser.Parse(this, page_ttl); + } + public void Exec_by_cmd(Xowe_wiki wiki, int commit_interval, int progress_interval) { + this.Init(Bool_.Y, wiki); + Xowd_page_tbl page_tbl = core_data_mgr.Tbl__page(); String fld_page_id = page_tbl.Fld_page_id(); String fld_page_ttl = page_tbl.Fld_page_title(); + Db_rdr page_rdr = page_tbl.Select_all__id__ttl(); int page_count = 0; + try { + while (page_rdr.Move_next()) { + this.page_id = page_rdr.Read_int(fld_page_id); + byte[] page_ttl = page_rdr.Read_bry_by_str(fld_page_ttl); + try {title_parser.Parse(this, page_ttl);} + catch (Exception e) {Xoa_app_.Usr_dlg().Warn_many("", "", "error while parsing title; id=~{0} title=~{1} err=~{2}", page_id, page_ttl, Err_.Message_gplx_log(e));} + ++page_count; + if ((page_count % commit_interval) == 0) search_temp_tbl.conn.Txn_sav(); + if ((page_count % progress_interval) == 0) Gfo_usr_dlg_.Instance.Prog_many("", "", "parse progress: count=~{0} last=~{1}", page_count, String_.new_u8(page_ttl)); + } + } finally {page_rdr.Rls();} + this.Term(); + } + public void Parse_done(Srch_word_itm word) { + search_temp_tbl.Insert_cmd_by_batch(++word_id, page_id, word.Word); + } + private void Update_word_id(Db_conn cur_conn, Xow_wiki wiki) { + // see if prv_url exists + Io_url cur_url = gplx.dbs.engines.sqlite.Sqlite_conn_info.To_url(cur_conn); // EX: /xowa/wiki/en.wikipedia.org/en.wikipedia.org-xtn.search.core.xowa + Io_url prv_url = wiki.Fsys_mgr().Root_dir().GenSubFil_nest("prv", cur_url.NameAndExt()); // EX: /xowa/wiki/en.wikipedia.org/prv/en.wikipedia.org-xtn.search.core.xowa + if (!Io_mgr.Instance.Exists(prv_url)) return; + // update ids for old_words + cur_conn.Exec_sql("UPDATE search_temp SET word_id = -1"); + Db_conn prv_conn = Db_conn_bldr.Instance.Get_or_noop(prv_url); + new Db_attach_mgr(cur_conn, new Db_attach_itm("prv_db", prv_conn)) + .Exec_sql_w_msg("updating old word ids", String_.Concat_lines_nl_skip_last + ( "UPDATE search_temp" + , "SET word_id = Coalesce((SELECT prv.word_id FROM search_word prv WHERE prv.word_text = search_temp.word_text), -1)" + )); + // assign incrementing int to new_words + int nxt_word_id = cur_conn.Exec_select_as_int("SELECT Max(word_id) AS word_id FROM search_temp", -1); if (nxt_word_id == -1) throw Err_.new_("dbs", "max_id not found"); + int uids_max = 10000; + int[] uids_ary = new int[uids_max]; int uid_last = -1; + Db_stmt update_stmt = cur_conn.Stmt_update("search_temp", String_.Ary("word_uid"), "word_id"); + while (true) { + // read 10,000 into memory + int uids_len = 0; + Db_rdr rdr = cur_conn.Exec_rdr("SELECT word_uid FROM search_temp WHERE word_id = -1 AND word_uid > " + Int_.To_str(uid_last)); + try { + while (rdr.Move_next()) { + int uid = rdr.Read_int("word_uid"); + uids_ary[uids_len++] = uid; + if (uids_len == uids_max) { + uid_last = uid; + break; + } + } + } + finally {rdr.Rls();} + if (uids_len == 0) break; + // update + for (int i = 0; i < uids_max; ++i) { + int uid = uids_ary[i]; + update_stmt.Clear().Val_int("word_id", ++nxt_word_id).Crt_int("word_uid", uid).Exec_update(); + } + } + } + private static void Search_word__insert(Db_conn word_conn) { + // fill search_word; need to insert into temp first b/c PRIMARY KEY on search_word will dramatically slow down insertion + word_conn.Meta_tbl_create(Dbmeta_tbl_itm.New("search_word_temp" + , Dbmeta_fld_itm.new_int("word_id") + , Dbmeta_fld_itm.new_str("word_text", 255) + , Dbmeta_fld_itm.new_int("link_count") + )); + word_conn.Exec_sql_concat_w_msg + ( "filling search_word_temp (please wait)" + , "INSERT INTO search_word_temp (word_id, word_text, link_count)" + , "SELECT Min(word_id)" + , ", word_text" + , ", Count(word_id)" // NOTE: no need to be Count(DISTINCT page_id); each search_temp row will only insert one word per page + , "FROM search_temp" + , "GROUP BY " + , " word_text" + , ";" + ); + word_conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_name("search_word_temp", "main", "word_id")); + word_conn.Exec_sql_concat_w_msg + ( "filling search_word (please wait)" + , "INSERT INTO search_word (word_id, word_text, link_count)" + , "SELECT word_id" + , ", word_text" + , ", link_count" + , "FROM search_word_temp" + , "ORDER BY " + , " word_id" + , ";" + ); + word_conn.Meta_tbl_delete("search_word_temp"); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/Xobldr__link__link_score.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/Xobldr__link__link_score.java index a27517de8..c14593477 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/Xobldr__link__link_score.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/Xobldr__link__link_score.java @@ -13,3 +13,180 @@ 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.addons.wikis.searchs.bldrs.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.bldrs.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.addons.bldrs.utils_rankings.bldrs.*; import gplx.xowa.addons.wikis.searchs.bldrs.cmds.adjustments.*; +import gplx.xowa.addons.wikis.searchs.dbs.*; +public class Xobldr__link__link_score extends Xob_cmd__base { + private int score_multiplier = 100000000; + private boolean page_rank_enabled = false; + private boolean delete_plink_db = false; + private final Adjustment_cmd score_adjustment_mgr; + public Xobldr__link__link_score(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki); + this.score_adjustment_mgr = new Adjustment_cmd(wiki); + } + public Xobldr__link__link_score Page_rank_enabled_(boolean v) {this.page_rank_enabled = v; return this;} + public Xobldr__link__link_score Delete_plink_db_(boolean v) {this.delete_plink_db = v; return this;} + @Override public void Cmd_run() { + wiki.Init_assert(); + Db_conn plink_conn = Db_conn_bldr.Instance.Get_or_autocreate(false, wiki.Fsys_mgr().Root_dir().GenSubFil(Xob_db_file.Name__page_link)); + String page_rank_tbl = Xobldr__page__page_score.Pagerank__tbl_name; + Gfo_usr_dlg_.Instance.Prog_none("", "", "search.page.score:adding fields to page_rank_temp"); + plink_conn.Meta_fld_assert(page_rank_tbl, "page_len_score" , Dbmeta_fld_tid.Itm__int, 0); + plink_conn.Meta_fld_assert(page_rank_tbl, "page_rank_score" , Dbmeta_fld_tid.Itm__double, 0); + plink_conn.Meta_fld_assert(page_rank_tbl, "page_score" , Dbmeta_fld_tid.Itm__int, 0); + int link_score_max = Srch_search_addon.Score_max; + + // percentize page_len_score to 0 : 100,000,000; NOTE: 100,000,000 so that no individual score should have 2+ page; i.e.: score_range > page_count + new Sqlite_percentile_cmd(bldr, wiki).Init_by_rel_url(Xob_db_file.Name__page_link, "temp_page_len", link_score_max, String_.Concat_lines_nl_skip_last + ( "SELECT p.page_id, p.page_len" + , "FROM page p" + , "ORDER BY p.page_len" // NOTE: ORDER BY is needed b/c INSERT into AUTO INCREMENT table + )).Cmd_run(); + plink_conn.Exec_sql("finalizing page_rank_temp.page_len_score", String_.Concat_lines_nl_skip_last + ( "UPDATE page_rank_temp" + , "SET page_len_score = (SELECT tpl.row_score FROM temp_page_len tpl WHERE tpl.row_key = page_rank_temp.page_id)" + )); + + // calc page_score + if (page_rank_enabled) { + // get min / max + Gfo_usr_dlg_.Instance.Prog_none("", "", "search.page.score:calculating page_rank range"); + double page_rank_min = plink_conn.Exec_select_as_double("SELECT Min(page_rank) FROM " + page_rank_tbl, Double_.MinValue); if (page_rank_min == Double_.MinValue) throw Err_.new_("bldr", "failed to get min; tbl=~{0}", page_rank_tbl); + double page_rank_max = plink_conn.Exec_select_as_double("SELECT Max(page_rank) FROM " + page_rank_tbl, Double_.MinValue); if (page_rank_max == Double_.MinValue) throw Err_.new_("bldr", "failed to get max; tbl=~{0}", page_rank_tbl); + double page_rank_rng = page_rank_max - page_rank_min; + if (page_rank_rng == 0) page_rank_rng = 1; // if 0, set to 1 to prevent divide by 0 below; + String score_multiplier_as_str = Dbmeta_fld_itm.To_double_str_by_int(score_multiplier); + + // normalize page_rank to 0 : 100,000,000 + plink_conn.Exec_sql + ( "normalizing page_rank_temp.page_rank_score" + , Bry_fmt.Make_str + ("UPDATE ~{tbl} SET page_rank_score = ((Coalesce(page_rank, 0)) / ~{page_score_rng}) * ~{score_multiplier}" + , page_rank_tbl, Double_.To_str(page_rank_rng), score_multiplier_as_str) + ); + + // percentize page_rank_score to 0 : 100,000 + new Sqlite_percentile_cmd(bldr, wiki).Init_by_rel_url(Xob_db_file.Name__page_link, "temp_page_score", link_score_max, String_.Concat_lines_nl_skip_last + ( "SELECT prt.page_id, prt.page_rank_score" + , "FROM page_rank_temp prt" + , "ORDER BY prt.page_rank_score" // NOTE: ORDER BY is needed b/c INSERT into AUTO INCREMENT table + )).Cmd_run(); + plink_conn.Exec_sql + ( "finalizing page_rank_temp.page_score" + , String_.Concat_lines_nl_skip_last + ( "UPDATE page_rank_temp" + , "SET page_score = Cast((SELECT tmp.row_score FROM temp_page_score tmp WHERE tmp.row_key = page_rank_temp.page_id) AS int)" + )); + + // adjust pages; NOTE: must happen after percentize b/c adjustment is based on percentized 0 : 100,000, not the raw page_rank (0.5 : 146.5) + score_adjustment_mgr.Exec(); + } + else { + plink_conn.Exec_sql + ( "finalizing page_rank_temp.page_score" + , String_.Format(String_.Concat_lines_nl_skip_last + ( "UPDATE page_rank_temp" + , "SET page_score = Cast(page_len_score AS int)" + ))); + } + plink_conn.Meta_idx_create(Xoa_app_.Usr_dlg(), Dbmeta_idx_itm.new_normal_by_tbl("page_rank_temp", "page_score", "page_id", "page_score")); + + // update page table + Xowd_page_tbl page_tbl = wiki.Data__core_mgr().Tbl__page(); + Db_conn page_conn = page_tbl.Conn(); + if (page_tbl.Fld_page_score() == Dbmeta_fld_itm.Key_null) { + page_conn.Meta_fld_append(page_tbl.Tbl_name(), Dbmeta_fld_itm.new_int(Xowd_page_tbl.Fld__page_score__key).Default_(0)); + page_tbl = wiki.Data__core_mgr().Db__core().Tbl__page__rebind(); + } + new Db_attach_mgr(page_conn, new Db_attach_itm("plink_db", plink_conn)) + .Exec_sql_w_msg("updating page.page_score", String_.Concat_lines_nl_skip_last + ( "UPDATE page" + , "SET page_score = Coalesce((SELECT Cast(pr.page_score AS integer) FROM page_rank_temp pr WHERE pr.page_id = page.page_id), 0)" + )); + + // update link tables + Srch_db_mgr search_db_mgr = Srch_search_addon.Get(wiki).Db_mgr(); + Srch_word_tbl word_tbl = search_db_mgr.Tbl__word(); + if (!page_tbl.Conn().Eq(word_tbl.conn)) page_tbl.Conn().Env_vacuum(); // don't vacuum if single-db + // Srch_db_mgr.Optimize_unsafe_(word_tbl.conn, Bool_.Y); + word_tbl.conn.Meta_tbl_remake(Dbmeta_tbl_itm.New("link_score_mnx", Dbmeta_fld_itm.new_int("word_id"), Dbmeta_fld_itm.new_int("mnx_val"))); + int link_tbls_len = search_db_mgr.Tbl__link__len(); + for (int i = 0; i < link_tbls_len; ++i) { + Srch_link_tbl link_tbl = search_db_mgr.Tbl__link__get_at(i); + // update search_link.link_score + link_tbl.conn.Meta_fld_assert(link_tbl.Tbl_name(), Srch_link_tbl.Fld_link_score, Dbmeta_fld_tid.Itm__int, 0); + new Db_attach_mgr(link_tbl.conn + , new Db_attach_itm("page_db", page_conn) + ).Exec_sql_w_msg + ( Bry_fmt.Make_str("updating search_link.link_score: ~{idx} of ~{total}", i + 1, link_tbls_len) + , String_.Concat_lines_nl_skip_last + ( "UPDATE search_link" + , "SET link_score = Coalesce((SELECT page_score FROM page p WHERE p.page_id = search_link.page_id), 0)" + )); + Calc_min_max(Bool_.Y, word_tbl, link_tbl, i, link_tbls_len); + Calc_min_max(Bool_.N, word_tbl, link_tbl, i, link_tbls_len); + link_tbl.Create_idx__link_score(); + if (!link_tbl.conn.Eq(word_tbl.conn)) link_tbl.conn.Env_vacuum(); // don't vacuum if single-db + word_tbl.Create_idx(); + } + word_tbl.conn.Meta_tbl_delete("link_score_mnx"); + Srch_db_cfg_.Update__bldr__link(search_db_mgr.Tbl__cfg(), search_db_mgr.Cfg(), link_score_max); + // Srch_db_mgr.Optimize_unsafe_(word_tbl.conn, Bool_.N); + + if (delete_plink_db) { + Xob_db_file plink_db = Xob_db_file.New__page_link(wiki); + plink_db.Conn().Rls_conn(); + Io_mgr.Instance.DeleteFil(plink_db.Url()); + } + } + private void Calc_min_max(boolean min, Srch_word_tbl word_tbl, Srch_link_tbl link_tbl, int i, int link_tbls_len) { + word_tbl.conn.Meta_idx_delete("link_score_mnx__main"); + word_tbl.conn.Exec_sql_concat("DELETE FROM link_score_mnx"); + String prc_name = "Max"; + String fld_name = "link_score_max"; + if (min) { + prc_name = "Min"; + fld_name = "link_score_min"; + } + new Db_attach_mgr(word_tbl.conn, new Db_attach_itm("link_db", link_tbl.conn)) + .Exec_sql_w_msg + ( Bry_fmt.Make_str("creating temp.link_score_max: ~{idx} of ~{total}", i + 1, link_tbls_len) + , String_.Concat_lines_nl_skip_last + ( "INSERT INTO link_score_mnx (word_id, mnx_val)" + , "SELECT sw.word_id, " + prc_name + "(sl.link_score)" + , "FROM search_word sw" + , " JOIN search_link sl ON sl.word_id = sw.word_id" + , "GROUP BY sw.word_id" + )) + ; + word_tbl.conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_name("link_score_mnx", "main", "word_id", "mnx_val")); + word_tbl.conn.Exec_sql_concat_w_msg + ( Bry_fmt.Make_str("creating temp.link_score_max: ~{idx} of ~{total}", i + 1, link_tbls_len) + , "UPDATE search_word" + , "SET " + fld_name + " = " + , " Coalesce((" + , " SELECT mnx_val" + , " FROM link_score_mnx lsm" + , " WHERE lsm.word_id = search_word.word_id" + , " AND lsm.mnx_val " + (min ? "<" : ">") + " search_word." + fld_name + , " ), " + fld_name + ")" + ); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__page_rank_enabled_)) page_rank_enabled = m.ReadYn("v"); + else if (ctx.Match(k, Invk__delete_plink_db_)) delete_plink_db = m.ReadYn("v"); + else if (ctx.Match(k, Invk__delete_plink_db_)) delete_plink_db = m.ReadYn("v"); + else if (ctx.Match(k, Invk__score_adjustment_mgr)) return score_adjustment_mgr; + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk__page_rank_enabled_ = "page_rank_enabled_", Invk__delete_plink_db_ = "delete_plink_db_" + , Invk__score_adjustment_mgr = "score_adjustment_mgr" + ; + + public static final String BLDR_CMD_KEY = "search.link__link_score"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__link__link_score(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__link__link_score(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/Xobldr__page__page_score.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/Xobldr__page__page_score.java index a27517de8..b50a2ba1f 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/Xobldr__page__page_score.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/Xobldr__page__page_score.java @@ -13,3 +13,107 @@ 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.addons.wikis.searchs.bldrs.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.bldrs.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.addons.bldrs.wmdumps.pagelinks.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xobldr__page__page_score extends Xob_cmd__base implements Xob_cmd { // create page_rank in page_db; drop and vaccuum later; avoid cross db for now + private double damping_factor = .85; + private double error_margin = .0001d; + private int iteration_max = 1000; + private Db_conn page_conn, plink_conn; + private int page_count = 0; + public Xobldr__page__page_score(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + public Xobldr__page__page_score Iteration_max_(int iteration_max) {this.iteration_max = iteration_max; return this;} + @Override public void Cmd_run() { + Init__tbl(); + Calc__main(); + } + private void Init__tbl() { + wiki.Init_assert(); + this.page_conn = wiki.Data__core_mgr().Db__core().Conn(); + page_count = page_conn.Exec_select_as_int("SELECT Count(page_id) FROM page", -1); + + Xob_db_file page_link_db = Xob_db_file.New__page_link(wiki); + this.plink_conn = page_link_db.Conn(); + if (plink_conn.Meta_tbl_exists(Pagerank__tbl_name)) plink_conn.Meta_tbl_delete(Pagerank__tbl_name); + Pglnk_page_link_tbl plink_tbl = new Pglnk_page_link_tbl(plink_conn); + if (!plink_conn.Meta_tbl_exists(plink_tbl.Tbl_name())) plink_tbl.Create_tbl();// create page_link if it doesn't exist; occurs when page_rank_enabled == false; + plink_conn.Meta_tbl_create(Dbmeta_tbl_itm.New + ( Pagerank__tbl_name + , Dbmeta_fld_itm.new_int (Pagerank__fld_page_id).Primary_y_() + , Dbmeta_fld_itm.new_int (Pagerank__fld_link_count) + , Dbmeta_fld_itm.new_int (Pagerank__fld_has_converged).Default_(0) + , Dbmeta_fld_itm.new_double (Pagerank__fld_page_rank).Default_(1) + , Dbmeta_fld_itm.new_int ("page_namespace").Default_(Int_.Min_value) + , Dbmeta_fld_itm.new_byte ("page_is_redirect").Default_(0) + , Dbmeta_fld_itm.new_int ("page_len").Default_(0) + )); + + new Db_attach_mgr(plink_conn, new Db_attach_itm("page_db", page_conn)) + .Exec_sql_w_msg("generating list of pages", String_.Concat_lines_nl_skip_last + ( "INSERT INTO page_rank_temp (page_id, link_count, page_namespace, page_is_redirect, page_len)" + , "SELECT p.page_id" + , ", Coalesce(Count(pl.trg_id), {0}) AS link_count" + , ", p.page_namespace" + , ", p.page_is_redirect" + , ", p.page_len" + , "FROM page p" // NOTE: JOIN needed to filter out [[User:]] pages which are in pagelinks.sql, but not in pages-articles.xml + , " LEFT JOIN page_link pl ON p.page_id = pl.src_id" + , "GROUP BY p.page_id" + ), page_count); + } + private void Calc__main() { + int iteration_idx = 0; + double damping_factor_inverse = 1 - damping_factor; + while (true) { + if (iteration_idx == iteration_max) break; + int converged_count = plink_conn.Exec_select_as_int("SELECT Count(page_id) FROM page_rank_temp WHERE has_converged = 0;", 0); + if (converged_count == 0) break; + new Db_attach_mgr(plink_conn, new Db_attach_itm("page_db", page_conn)) + .Exec_sql_w_msg(String_.Format("calculating page_rank; iteration={0} unconverged={1}", iteration_idx, converged_count) + , String_.Concat_lines_nl_skip_last + ( "REPLACE INTO page_rank_temp (page_id, page_rank, link_count, has_converged, page_namespace, page_is_redirect, page_len)" + , "SELECT pr.page_id" + , ", {1} + ({0} * Coalesce(w.page_rank, 0)) AS page_rank" + , ", pr.link_count" + , ", CASE WHEN Abs(pr.page_rank - ({1} + ({0} * Coalesce(w.page_rank, 0)))) < {2} THEN 1 ELSE 0 END AS has_converged" + , ", pr.page_namespace" + , ", pr.page_is_redirect" + , ", pr.page_len" + , "FROM page_rank_temp pr" + , " LEFT JOIN" + , " (SELECT lnk.trg_id" + , " , Sum((Cast(pr2.page_rank AS double) / Cast(pr2.page_rank AS double))) * {0} AS page_rank" + , " FROM page_rank_temp pr2" + , " JOIN page_link lnk ON pr2.page_id = lnk.src_id" + , " JOIN page p ON lnk.src_id = p.page_id" + // , " WHERE p.page_namespace = 0" + , " GROUP BY lnk.trg_id" + , " ) AS w ON w.trg_id = pr.page_id" + , "WHERE pr.has_converged = 0" + ), damping_factor, damping_factor_inverse, error_margin); + ++iteration_idx; + } + Gfo_usr_dlg_.Instance.Note_many("", "", "page_rank done; iteration=~{0}", iteration_idx); + } + public static final String + Pagerank__tbl_name = "page_rank_temp" + , Pagerank__fld_page_id = "page_id" + , Pagerank__fld_page_rank = "page_rank" + , Pagerank__fld_link_count = "link_count" + , Pagerank__fld_has_converged = "has_converged" + ; + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__damping_factor_)) damping_factor = m.ReadDouble("v"); + else if (ctx.Match(k, Invk__error_margin_)) error_margin = m.ReadDouble("v"); + else if (ctx.Match(k, Invk__iteration_max_)) iteration_max = m.ReadInt("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk__damping_factor_ = "damping_factor_", Invk__error_margin_ = "error_margin_", Invk__iteration_max_ = "iteration_max_"; + + public static final String BLDR_CMD_KEY = "search.page__page_score"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__page__page_score(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__page__page_score(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/Xobldr__word__link_count.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/Xobldr__word__link_count.java index a27517de8..d5dada4d9 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/Xobldr__word__link_count.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/Xobldr__word__link_count.java @@ -13,3 +13,42 @@ 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.addons.wikis.searchs.bldrs.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.bldrs.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.addons.bldrs.utils_rankings.bldrs.*; +import gplx.xowa.addons.wikis.searchs.dbs.*; +public class Xobldr__word__link_count extends Xob_cmd__base implements Xob_cmd { + private int score_multiplier = Srch_search_addon.Score_max; + public Xobldr__word__link_count(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + wiki.Init_assert(); + + Srch_db_mgr search_db_mgr = Srch_search_addon.Get(wiki).Db_mgr(); + Srch_word_tbl word_tbl = search_db_mgr.Tbl__word(); + Db_conn word_conn = word_tbl.conn; + String percentile_tbl = "search_word__link_count"; + Sqlite_percentile_cmd percentile_cmd = new Sqlite_percentile_cmd(bldr, wiki).Init_by_conn(word_conn, percentile_tbl, score_multiplier, String_.Concat_lines_nl_skip_last + ( "SELECT sw.word_id, sw.link_count" + , "FROM search_word sw" + , "ORDER BY sw.link_count" // NOTE: ORDER BY is needed b/c INSERT into AUTO INCREMENT table + )); + percentile_cmd.Cmd_run(); + + word_conn.Exec_sql("finalizing search_word.link_count_score", String_.Concat_lines_nl_skip_last + ( "UPDATE search_word" + , "SET link_count_score = Cast((SELECT tpl.row_score FROM search_word__link_count tpl WHERE tpl.row_key = search_word.word_id) AS int)" + )); + word_conn.Meta_tbl_delete(percentile_tbl); + word_conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(word_tbl.tbl_name, "link_count_score__word_text", word_tbl.fld_link_count_score, word_tbl.fld_text)); + + int link_count_score_cutoff = word_conn.Exec_select_as_int("SELECT Cast(Min(link_count_score) AS int) AS val FROM search_word WHERE link_count > " + Int_.To_str(Srch_db_cfg_.Link_count_score_cutoff), 0); + Srch_db_cfg_.Update__bldr__word(search_db_mgr.Tbl__cfg(), search_db_mgr.Cfg(), percentile_cmd.count, score_multiplier, link_count_score_cutoff); + word_conn.Env_vacuum(); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return this;} + + public static final String BLDR_CMD_KEY = "search.word__link_count"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__word__link_count(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__word__link_count(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Adjustment_cmd.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Adjustment_cmd.java index a27517de8..59eb0e8e7 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Adjustment_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Adjustment_cmd.java @@ -13,3 +13,151 @@ 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.addons.wikis.searchs.bldrs.cmds.adjustments; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.bldrs.*; import gplx.xowa.addons.wikis.searchs.bldrs.cmds.*; +import gplx.dbs.*; import gplx.xowa.bldrs.*; +import gplx.xowa.wikis.nss.*; +public class Adjustment_cmd implements Gfo_invk { + private final Xow_wiki wiki; + private final Page_matcher_mgr matcher_mgr; + private boolean enabled = true; + private double median_factor = .001d; + public Adjustment_cmd(Xow_wiki wiki) { + this.wiki = wiki; + this.matcher_mgr = new Page_matcher_mgr(wiki); + } + public void Exec() { + // init + if (!enabled) return; + wiki.Init_by_wiki(); + Xob_db_file pl_db = Xob_db_file.New__page_link(wiki); + Db_conn pl_conn = pl_db.Conn(); + + // add fields to page_rank_tbl + String page_rank_tbl = Xobldr__page__page_score.Pagerank__tbl_name; + pl_conn.Meta_fld_assert(page_rank_tbl, "score_old" , Dbmeta_fld_tid.Itm__int, 0); + pl_conn.Meta_fld_assert(page_rank_tbl, "len_avg" , Dbmeta_fld_tid.Itm__int, 0); + + // create penalty summary tbl + pl_conn.Meta_tbl_remake(Dbmeta_tbl_itm.New("penalty_summary" + , Dbmeta_fld_itm.new_int("page_ns") + , Dbmeta_fld_itm.new_int("score_bgn") + , Dbmeta_fld_itm.new_int("score_end") + , Dbmeta_fld_itm.new_int("len_avg") + , Dbmeta_fld_itm.new_int("page_count") + )); + Db_stmt summary_stmt = pl_conn.Stmt_insert("penalty_summary", "page_ns", "score_bgn", "score_end", "len_avg", "page_count"); + Db_stmt detail_stmt = pl_conn.Stmt_update(page_rank_tbl, String_.Ary("page_id"), "page_score", "score_old", "len_avg"); + + // iterate namespaces + pl_conn.Meta_idx_create("page_rank_temp", "adjustment", "page_namespace", "page_score"); + Db_stmt page_select = pl_conn.Stmt_sql("SELECT pr.page_id, pr.page_is_redirect, pr.page_len, pr.page_score FROM page_rank_temp pr WHERE pr.page_namespace = ? ORDER BY pr.page_score DESC");// ANSI.Y + int ns_len = wiki.Ns_mgr().Ords_len(); + for (int i = 0; i < ns_len; ++i) { + Xow_ns ns = wiki.Ns_mgr().Ords_get_at(i); + if (ns.Count() > 0) { + Page_matcher_wkr matcher_wkr = matcher_mgr.Get_by(ns.Id()).Load_all(); + pl_conn.Txn_bgn("pl_penalty"); + Calc_for_ns(wiki, matcher_wkr, page_select, summary_stmt, detail_stmt, ns); + pl_conn.Txn_end(); + } + } + + // cleanup + detail_stmt.Rls(); + summary_stmt.Rls(); + page_select.Rls(); + } + private void Calc_for_ns(Xow_wiki wiki, Page_matcher_wkr wkr, Db_stmt page_select, Db_stmt summary_stmt, Db_stmt detail_stmt, Xow_ns ns) { + // calc sample + int sample_len = (int)(ns.Count() * .0001); // 12,000,000 * .0001 -> 1200 + if (sample_len < 100) sample_len = 100; // if < 100, default to 100; else small sample sizes like 5 will create strange medians + Page_stub[] page_ary = new Page_stub[sample_len]; + + // loop + Gfo_log_.Instance.Prog("loading pages for ns", "ns_id", ns.Id()); + Db_rdr rdr = page_select.Clear().Crt_int("page_namespace", ns.Id()).Exec_select__rls_manual(); // ANSI.Y + int page_count = 0; + int score_end = -1, score_cur = -1; + int range_count = 0; + while (rdr.Move_next()) { + int page_id = rdr.Read_int("page_id"); + boolean page_is_redirect = rdr.Read_bool_by_byte("page_is_redirect"); + int page_len = rdr.Read_int("page_len"); + score_cur = rdr.Read_int("page_score"); + if (score_end == -1) score_end = score_cur; + + page_ary[page_count] = new Page_stub(page_id, page_is_redirect, page_len, score_cur); + ++page_count; + if (page_count == sample_len) { + if ((++range_count % 100) == 0) Gfo_log_.Instance.Prog("updating range", "ns", ns.Id(), "score_cur", score_cur, "score_end", score_end); + Save_sample(page_ary, wkr, wiki.Domain_bry(), ns.Id(), score_cur, score_end, summary_stmt, detail_stmt); + + // reset + page_count = 0; + score_end = -1; + } + } + page_ary = (Page_stub[])Array_.Resize(page_ary, page_count); + Save_sample(page_ary, wkr, wiki.Domain_bry(), ns.Id(), score_cur, score_end, summary_stmt, detail_stmt); + rdr.Rls(); + } + private void Save_sample(Page_stub[] page_ary, Page_matcher_wkr wkr, byte[] domain_bry, int ns_id, int score_cur, int score_end, Db_stmt summary_stmt, Db_stmt detail_stmt) { + // calc median + Array_.Sort(page_ary); + int ary_len = page_ary.length; if (ary_len == 0) return; // occurs when ns has exact multiple of .01% pages; EX: 10,000 pages (but not 10,001, 10,002, etc..) + int median = Calc_median(page_ary); + + // insert + for (int i = 0; i < ary_len; ++i) { + Page_stub page = page_ary[i]; + int score_old = page.Score; + int score_new = score_old; + Page_matcher_itm itm = wkr.Get_by_or_null(page.Id); + if (itm != null) { + score_new = itm.Calc(score_old); + } + // if len < median, penalize by .01 of difference; + /* + EX: score=1000; page_len=80; median_len=100 + .8 <- 80 / 100 + .2 <- 1 - .8 + .002 <- .2 * .01 + 2 <- 1000 * .002 + 998 <- 1000 - 2 + */ + else if (page.Len < median) + score_new = (int)((1 - (1 - ((double)page.Len / median)) * median_factor) * score_old); + + detail_stmt.Clear() + .Val_int("page_score" , score_new) + .Val_int("score_old" , score_old) + .Val_int("len_avg" , median) + .Crt_int("page_id" , page.Id) + .Exec_update(); + } + summary_stmt.Clear().Val_int("page_ns", ns_id).Val_int("score_bgn", score_cur).Val_int("score_end", score_end) + .Val_int("len_avg", median).Val_int("page_count", ary_len) + .Exec_insert(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__enabled_)) enabled = m.ReadBool("v"); + else if (ctx.Match(k, Invk__median_factor_)) median_factor = m.ReadDouble("v"); + else if (ctx.Match(k, Invk__match_mgr)) return matcher_mgr; + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk__match_mgr = "match_mgr", Invk__enabled_ = "enabled_", Invk__median_factor_ = "median_factor_"; + + private static int Calc_median(Page_stub[] ary) { + int len = ary.length; + int redirect_end = 0; + for (int i = 0; i < len; ++i) { + Page_stub itm = ary[i]; + if (!itm.Is_redirect) { + redirect_end = i; + break; + } + } + int median_idx = (len - redirect_end) / 2; + return ary[median_idx].Len; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Page_matcher_itm.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Page_matcher_itm.java index a27517de8..079a6cad1 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Page_matcher_itm.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Page_matcher_itm.java @@ -13,3 +13,44 @@ 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.addons.wikis.searchs.bldrs.cmds.adjustments; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.bldrs.*; import gplx.xowa.addons.wikis.searchs.bldrs.cmds.*; +import gplx.core.primitives.*; +class Page_matcher_itm { + public Page_matcher_itm(byte match_type, byte calc_type, double val, String page_filter) { + this.Match_type = match_type; + this.Calc_type = calc_type; + this.Val = val; + this.Page_filter = page_filter; + } + public final byte Match_type; + public final byte Calc_type; + public final double Val; + public final String Page_filter; + public Int_obj_ref[] Page_ids; + public int Calc(int score_old) { + switch (this.Calc_type) { + case Page_matcher__calc_type.Type__set : return (int)Val; + case Page_matcher__calc_type.Type__mult : return (int)(score_old * Val); + case Page_matcher__calc_type.Type__add : return score_old + (int)Val; + default: throw Err_.new_unhandled_default(this.Calc_type); + } + } +} +class Page_matcher__match_type { + public static final byte Type__bgn = 0, Type__end = 1, Type__all = 2, Type__any = 3; + public static byte To_tid(String s) { + if (String_.Eq(s, "bgn")) return Type__bgn; + else if (String_.Eq(s, "end")) return Type__end; + else if (String_.Eq(s, "all")) return Type__all; + else throw Err_.new_unhandled_default(s); + } +} +class Page_matcher__calc_type { + public static final byte Type__set = 0, Type__mult = 1, Type__add = 2; + public static byte To_tid(String s) { + if (String_.Eq(s, "set")) return Type__set; + else if (String_.Eq(s, "mult")) return Type__mult; + else if (String_.Eq(s, "add")) return Type__add; + else throw Err_.new_unhandled_default(s); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Page_matcher_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Page_matcher_mgr.java index a27517de8..663b932d2 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Page_matcher_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Page_matcher_mgr.java @@ -13,3 +13,23 @@ 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.addons.wikis.searchs.bldrs.cmds.adjustments; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.bldrs.*; import gplx.xowa.addons.wikis.searchs.bldrs.cmds.*; +import gplx.core.lists.hashs.*; import gplx.core.primitives.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.tbls.*; +class Page_matcher_mgr implements Gfo_invk { + private final Xow_wiki wiki; + public Page_matcher_mgr(Xow_wiki wiki) {this.wiki = wiki;} + private final Hash_adp__int hash = new Hash_adp__int(); + public Page_matcher_wkr Get_by(int ns_id) { + Page_matcher_wkr rv = (Page_matcher_wkr)hash.Get_by_or_null(ns_id); + if (rv == null) { + rv = new Page_matcher_wkr(wiki, ns_id); + hash.Add(ns_id, rv); + } + return rv; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__get)) return Get_by(m.ReadInt("v")); + else return Gfo_invk_.Rv_unhandled; + } private static final String Invk__get = "get"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Page_matcher_wkr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Page_matcher_wkr.java index a27517de8..0f3f200a1 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Page_matcher_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Page_matcher_wkr.java @@ -13,3 +13,59 @@ 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.addons.wikis.searchs.bldrs.cmds.adjustments; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.bldrs.*; import gplx.xowa.addons.wikis.searchs.bldrs.cmds.*; +import gplx.core.lists.hashs.*; import gplx.core.primitives.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.tbls.*; +class Page_matcher_wkr implements Gfo_invk {// NOTE: tries would use less memory, but would be slower, especially for Has*() + private final Xow_wiki wiki; + private final List_adp rule_list = List_adp_.New(); + private final Hash_adp__int page_hash = new Hash_adp__int(); + public Page_matcher_wkr(Xow_wiki wiki, int ns_id) { + this.wiki = wiki; + this.ns_id = ns_id; + } + public int Ns_id() {return ns_id;} private final int ns_id; + public Page_matcher_itm Get_by_or_null(int page_id) {return (Page_matcher_itm)page_hash.Get_by_or_null(page_id);} + public Page_matcher_wkr Load_all() { + int len = rule_list.Len(); + for (int i = 0; i < len; ++i) { + Load((Page_matcher_itm)rule_list.Get_at(i)); + } + return this; + } + private void Load(Page_matcher_itm itm) { + Gfo_log_.Instance.Prog("loading filter", "filter", itm.Page_filter); + List_adp page_ids = List_adp_.New(); + String sql = "", filter_arg = itm.Page_filter; + switch (itm.Match_type) { + case Page_matcher__match_type.Type__bgn: sql = "SELECT page_id FROM page WHERE page_namespace = ? AND page_title LIKE ?"; filter_arg = filter_arg + "%"; break; // ANSI.Y + case Page_matcher__match_type.Type__end: sql = "SELECT page_id FROM page WHERE page_namespace = ? AND page_title LIKE ?"; filter_arg = "%" + filter_arg; break; // ANSI.Y + case Page_matcher__match_type.Type__all: sql = "SELECT page_id FROM page WHERE page_namespace = ? AND page_title = ?"; break; // ANSI.Y + } + Db_stmt stmt = wiki.Data__core_mgr().Db__core().Tbl__page().Conn().Stmt_sql(sql); + Db_rdr rdr = stmt.Clear().Crt_int("page_namespace", ns_id).Crt_str("page_title", filter_arg).Exec_select__rls_manual(); + try { + while (rdr.Move_next()) { + Int_obj_ref page_id = Int_obj_ref.New(rdr.Read_int("page_id")); + page_ids.Add(page_id); + page_hash.Add_if_dupe_use_nth(page_id, itm); + } + } finally {rdr.Rls();} + itm.Page_ids = (Int_obj_ref[])page_ids.To_ary_and_clear(Int_obj_ref.class); + stmt.Rls(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__add)) Add_by_msg(m); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk__add = "add"; + private void Add_by_msg(GfoMsg m) { + byte match_type = Page_matcher__match_type.To_tid(m.Args_getAt(0).Val_to_str_or_empty()); + byte calc_type = Page_matcher__calc_type.To_tid(m.Args_getAt(1).Val_to_str_or_empty()); + double val = Double_.parse(m.Args_getAt(2).Val_to_str_or_empty()); + int args_len = m.Args_count(); + for (int i = 3; i < args_len; ++i) { + rule_list.Add(new Page_matcher_itm(match_type, calc_type, val, m.Args_getAt(i).Val_to_str_or_empty())); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Page_stub.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Page_stub.java index a27517de8..8a9e78397 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Page_stub.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/bldrs/cmds/adjustments/Page_stub.java @@ -13,3 +13,26 @@ 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.addons.wikis.searchs.bldrs.cmds.adjustments; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.bldrs.*; import gplx.xowa.addons.wikis.searchs.bldrs.cmds.*; +class Page_stub implements CompareAble { + public Page_stub(int id, boolean is_redirect, int len, int score) { + this.Id = id; + this.Is_redirect = is_redirect; + this.Len = len; + this.Score = score; + } + public final int Id; + public final boolean Is_redirect; + public final int Len; + public final int Score; + + public int compareTo(Object obj) { + Page_stub comp = (Page_stub)obj; + // sort redirects and small pages to bottom + int is_redirect_compare = -Bool_.Compare(Is_redirect, comp.Is_redirect); + if (is_redirect_compare == CompareAble_.Same) + return Int_.Compare(Len, comp.Len); + else + return is_redirect_compare; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_cfg.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_cfg.java index a27517de8..419d0818e 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_cfg.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_cfg.java @@ -13,3 +13,28 @@ 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.addons.wikis.searchs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +public class Srch_db_cfg { + public Srch_db_cfg(int version_id, long page_count, int word_count, int link_count_score_max, int link_count_score_cutoff, int link_score_max) { + this.version_id = version_id; + this.page_count = page_count; + this.word_count = word_count; + this.link_count_score_max = link_count_score_max; + this.link_count_score_cutoff = link_count_score_cutoff; + this.link_score_max = link_score_max; + } + public int Version_id() {return version_id;} private int version_id; + public boolean Version_id__needs_upgrade() {return version_id < Srch_db_upgrade.Version__link_score;} + public long Page_count() {return page_count;} private long page_count; + public int Word_count() {return word_count;} private int word_count; + public int Link_count_score_max() {return link_count_score_max;} private int link_count_score_max; + public int Link_count_score_cutoff() {return link_count_score_cutoff;} private int link_count_score_cutoff; + public int Link_score_max() {return link_score_max;} private int link_score_max; + public void Update_link(int link_score_max) {this.link_score_max = link_score_max;} + public void Update_word(int word_count, int link_count_score_max, int link_count_score_cutoff) { + this.version_id = Srch_db_upgrade.Version__link_score; + this.word_count = word_count; + this.link_count_score_max = link_count_score_max; + this.link_count_score_cutoff = link_count_score_cutoff; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_cfg_.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_cfg_.java index a27517de8..24c357348 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_cfg_.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_cfg_.java @@ -13,3 +13,47 @@ 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.addons.wikis.searchs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.dbs.cfgs.*; +public class Srch_db_cfg_ { + public static Srch_db_cfg New(Db_cfg_tbl cfg_tbl, long page_count, int version_id) { // NOTE: dflt values are for old search dbs + int word_count = cfg_tbl.Assert_int(Grp__search__cfg, Key__word_count, 0); + int link_count_score_max = cfg_tbl.Assert_int(Grp__search__cfg, Key__link_count_score_max, 0); + int link_count_score_cutoff = cfg_tbl.Assert_int(Grp__search__cfg, Key__link_count_score_cutoff, 0); + int link_score_max = cfg_tbl.Assert_int(Grp__search__cfg, Key__link_score_max, 0); + return new Srch_db_cfg(version_id, page_count, word_count, link_count_score_max, link_count_score_cutoff, link_score_max); + } + public static void Update__bldr__link(Db_cfg_tbl cfg_tbl, Srch_db_cfg cfg, int link_score_max) { + cfg.Update_link(link_score_max); + cfg_tbl.Upsert_int(Grp__search__cfg, Key__link_score_max, link_score_max); + } + public static void Update__bldr__word(Db_cfg_tbl cfg_tbl, Srch_db_cfg cfg, int word_count, int link_count_score_max, int link_count_score_cutoff) { + cfg.Update_word(word_count, link_count_score_max, link_count_score_cutoff); + cfg_tbl.Upsert_int(Grp__search__cfg, Key__version_id, Srch_db_upgrade.Version__link_score); + cfg_tbl.Upsert_int(Grp__search__cfg, Key__word_count, word_count); + cfg_tbl.Upsert_int(Grp__search__cfg, Key__link_count_score_max, link_count_score_max); + cfg_tbl.Upsert_int(Grp__search__cfg, Key__link_count_score_cutoff, link_count_score_cutoff); + } + public static int Select__version_id(Db_cfg_tbl cfg_tbl, Srch_word_tbl word_tbl) { + int rv = cfg_tbl.Select_int_or(Grp__search__cfg, Key__version_id, -1); + if (rv >= Srch_db_upgrade.Version__link_score) return rv; // version_id exists; return it; + if (rv == Srch_db_upgrade.Version__link_score_alpha) { // R.16.03.13 has same schema as later version + rv = Srch_db_upgrade.Version__link_score; + } else { + boolean version_is_page_count = word_tbl.conn.Meta_fld_exists(word_tbl.tbl_name, "word_page_count"); + rv = version_is_page_count ? Srch_db_upgrade.Version__page_count : Srch_db_upgrade.Version__initial; + } + cfg_tbl.Upsert_int(Grp__search__cfg, Key__version_id, rv); + return rv; + } + + public static final int Link_count_score_cutoff = 300; + public static final String + Grp__search__cfg = "xowa.search.cfg" + , Key__version_id = "version_id" + , Key__word_count = "word_count" + , Key__link_count_score_max = "link_count_score_max" + , Key__link_count_score_cutoff = "link_count_score_cutoff" + , Key__link_score_max = "link_score_max" + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_mgr.java index a27517de8..c0679c6f7 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_mgr.java @@ -13,3 +13,120 @@ 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.addons.wikis.searchs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.dbs.cfgs.*; +import gplx.xowa.wikis.data.*; +public class Srch_db_mgr { + private final Xow_db_mgr db_mgr; + public Srch_db_mgr(Xow_db_mgr db_mgr) { + this.db_mgr = db_mgr; + } + public Srch_db_cfg Cfg() {return cfg;} private Srch_db_cfg cfg; + public Db_cfg_tbl Tbl__cfg() {return tbl__cfg;} private Db_cfg_tbl tbl__cfg; + public Srch_word_tbl Tbl__word() {return tbl__word;} private Srch_word_tbl tbl__word; + public int Tbl__link__len() {return tbl__link__ary.length;} + public Srch_link_tbl Tbl__link__get_at(int i) {return tbl__link__ary[i];} + public int Tbl__link__get_idx(int ns) {return ns == gplx.xowa.wikis.nss.Xow_ns_.Tid__main ? 0 : tbl__link__ary.length - 1;} + public Srch_link_tbl[] Tbl__link__ary() {return tbl__link__ary;} private Srch_link_tbl[] tbl__link__ary = Srch_link_tbl.Ary_empty; + public Srch_db_mgr Init(long num_pages) { + Xowd_core_db_props db_props = db_mgr.Db__core().Db_props(); + Xow_db_file word_db = null; + if ( db_props.Schema() == 1 + || db_props.Layout_text().Tid_is_all_or_few()) { + // single_db; core_db has search_word and search_link + word_db = db_mgr.Db__core(); + tbl__cfg = gplx.xowa.wikis.data.Xowd_cfg_tbl_.New(word_db.Conn()); + tbl__word = new Srch_word_tbl(word_db.Conn()); + tbl__link__ary = new Srch_link_tbl[1]; + Tbl__link__ary__set(tbl__link__ary, 0, word_db.Conn()); + } else { + // many_db; figure out link_dbs + word_db = db_mgr.Dbs__get_by_tid_or_null(Srch_db_mgr_.Dbtid__search_core); + if (word_db == null) return this; // HACK: called during db build; skip; + tbl__cfg = gplx.xowa.wikis.data.Xowd_cfg_tbl_.New(word_db.Conn()); + tbl__word = new Srch_word_tbl(word_db.Conn()); + Ordered_hash hash = db_mgr.Dbs__get_hash_by_tid(Srch_db_mgr_.Dbtid__search_link); + if (hash == null) { // v2 file layout where search_word and search_link is in 1 search_db + tbl__link__ary = new Srch_link_tbl[1]; + Tbl__link__ary__set(tbl__link__ary, 0, word_db.Conn()); + } else { // v3 file layout where search_link is in many db + int dbs_len = hash.Count(); + tbl__link__ary = new Srch_link_tbl[dbs_len]; + for (int i = 0; i < dbs_len; ++i) { + Xow_db_file db_file = (Xow_db_file)hash.Get_at(i); + Tbl__link__ary__set(tbl__link__ary, i, db_file.Conn()); + } + } + } + cfg = Srch_db_cfg_.New(tbl__cfg, num_pages, Srch_db_cfg_.Select__version_id(tbl__cfg, tbl__word)); + return this; + } + public void Delete_all(Xow_db_mgr core_data_mgr) { + tbl__word.conn.Meta_tbl_delete(Srch_link_reg_tbl.Tbl_name); + Xowd_core_db_props db_props = db_mgr.Db__core().Db_props(); + if ( db_props.Schema() == 1 + || db_props.Layout_text().Tid_is_all_or_few()) { + // single_db; just drop tables + tbl__word.conn.Meta_tbl_delete(tbl__word.tbl_name); + Srch_link_tbl link_tbl = tbl__link__ary[0]; + link_tbl.conn.Meta_tbl_delete(link_tbl.Tbl_name()); + } else { + // many_db; drop databases + core_data_mgr.Dbs__delete_by_tid(Xow_db_file_.Tid__search_core); + core_data_mgr.Dbs__delete_by_tid(Xow_db_file_.Tid__search_link); + } + } + public void Create_all() { + Xowd_core_db_props db_props = db_mgr.Db__core().Db_props(); + if ( db_props.Schema() == 1 + || db_props.Layout_text().Tid_is_all_or_few()) { + // single_db; put both in core_db + Xow_db_file search_db = db_mgr.Db__core(); + tbl__word = new Srch_word_tbl(search_db.Conn()); tbl__word.Create_tbl(); + Srch_link_tbl tbl__link = new Srch_link_tbl(search_db.Conn()); tbl__link.Create_tbl(); + tbl__link__ary = new Srch_link_tbl[] {tbl__link}; + Srch_link_reg_tbl tbl__lreg = new Srch_link_reg_tbl(search_db.Conn()); tbl__lreg.Create_tbl(); + Tbl__link__ary__new(tbl__lreg, tbl__link__ary, db_mgr, 0, Bool_.N, search_db); + } else { + // many_db: put in 3 db; + Xow_db_file word_db = db_mgr.Dbs__make_by_tid(Srch_db_mgr_.Dbtid__search_core); + tbl__word = new Srch_word_tbl(word_db.Conn()); tbl__word.Create_tbl(); + tbl__link__ary = new Srch_link_tbl[2]; + Srch_link_reg_tbl tbl__lreg = new Srch_link_reg_tbl(word_db.Conn()); tbl__lreg.Create_tbl(); + Tbl__link__ary__new(tbl__lreg, tbl__link__ary, db_mgr, 0, Bool_.Y, null); + Tbl__link__ary__new(tbl__lreg, tbl__link__ary, db_mgr, 1, Bool_.N, null); + } + } + public void Update_links(int ns_id, int old_id, int new_id) { + if (!tbl__word.conn.Meta_tbl_exists(Srch_word_tbl.TABLE_NAME)) return; // NOTE: personal_wikis may not have search_link; exit early else assert will fail; DATE:2017-02-15 + int search_link_db_id = this.Tbl__link__get_idx(ns_id); + Srch_link_tbl search_link_tbl = this.Tbl__link__get_at(search_link_db_id); + search_link_tbl.Update_page_id(old_id, new_id); + } + + public static Srch_link_tbl Tbl__link__ary__set(Srch_link_tbl[] ary, int idx, gplx.dbs.Db_conn conn) { + Srch_link_tbl tbl = new Srch_link_tbl(conn); + ary[idx] = tbl; + return tbl; + } + private static void Tbl__link__ary__new(Srch_link_reg_tbl lreg_tbl, Srch_link_tbl[] ary, Xow_db_mgr db_mgr, int idx, boolean ns_ids_is_main, Xow_db_file db) { + if (db == null) { + String ns_ids = (ns_ids_is_main ? "000" : "999"); + String suffix = "-xtn.search.link-title-ns." + ns_ids + "-db.001.xowa"; // -xtn.search.link-title-ns.main-db.001.xowa + db = db_mgr.Dbs__make_by_tid(Srch_db_mgr_.Dbtid__search_link, ns_ids, idx, suffix); + } + Srch_link_tbl tbl = Tbl__link__ary__set(ary, idx, db.Conn()); + tbl.Create_tbl(); + lreg_tbl.Insert(idx, db.Id(), Srch_link_reg_tbl.Db_type__title, ns_ids_is_main ? Srch_link_reg_tbl.Ns_type__main : Srch_link_reg_tbl.Ns_type__rest, 0, -1, -1); + } + public static void Optimize_unsafe_(gplx.dbs.Db_conn conn, boolean v) { + if (v) { + conn.Exec_qry(gplx.dbs.engines.sqlite.Sqlite_pragma.New__journal__off()); + conn.Exec_qry(gplx.dbs.engines.sqlite.Sqlite_pragma.New__synchronous__off()); + } + else { + conn.Exec_qry(gplx.dbs.engines.sqlite.Sqlite_pragma.New__journal__delete()); + conn.Exec_qry(gplx.dbs.engines.sqlite.Sqlite_pragma.New__synchronous__full()); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_mgr_.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_mgr_.java index a27517de8..71763bec8 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_mgr_.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_mgr_.java @@ -13,3 +13,8 @@ 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.addons.wikis.searchs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.xowa.wikis.data.*; +public class Srch_db_mgr_ { + public static final byte Dbtid__search_core = 4, Dbtid__search_link = 16; // SYNC:Xow_db_file_ +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_upgrade.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_upgrade.java index a27517de8..7dd7a958a 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_upgrade.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_db_upgrade.java @@ -13,3 +13,38 @@ 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.addons.wikis.searchs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; +public class Srch_db_upgrade { + private final Xow_wiki wiki; + public Srch_db_upgrade(Xow_wiki wiki, Srch_db_mgr search_db_mgr) { + this.wiki = wiki; + } + public void Upgrade() { + if (!wiki.App().Mode().Tid_is_gui()) return; // ignore if html-server or drd-app + Xoae_app app = ((Xoae_app)wiki.App()); + boolean ok = app.Gui_mgr().Kit().Ask_ok_cancel("", "", String_.Concat_lines_nl_skip_last + ( "XOWA would like to upgrade your search database for " + wiki.Domain_str() + "." + , "" + , "* Press OK to upgrade. This may take an hour for English Wikipedia." + , "* Press Cancel to skip. You will not be able to search." + , "" + , "If you cancel, XOWA will ask you to upgrade this wiki again next time you restart the application." + , "" + , "Note that you can run this upgrade process manually by doing:" + , " Main Menu -> Tools -> Wiki Maintenance -> Search" + )); + if (!ok) return; + Xowe_wiki wikie = (Xowe_wiki)wiki; + gplx.xowa.addons.wikis.searchs.bldrs.Srch_bldr_mgr_.Setup(wikie); + gplx.xowa.bldrs.Xob_bldr bldr = app.Bldr(); + bldr.Cmd_mgr().Add(new gplx.xowa.bldrs.cmds.utils.Xob_alert_cmd(bldr, wikie).Msg_("search upgrade finished")); + gplx.core.threads.Thread_adp_.Start_by_key("search_upgrade", app.Bldr(), gplx.xowa.bldrs.Xob_bldr.Invk_run); + } + public static final int + Version__link_score_alpha = 0 // in 2016-02 android alpha + , Version__initial = 1 + , Version__page_count = 2 + , Version__link_score = 3 + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_link_reg_tbl.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_link_reg_tbl.java index a27517de8..198a4d75d 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_link_reg_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_link_reg_tbl.java @@ -13,3 +13,30 @@ 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.addons.wikis.searchs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.dbs.*; +public class Srch_link_reg_tbl implements Rls_able { + private final String tbl_name; public final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_id, fld_db_id, fld_db_type, fld_ns_ids, fld_sub_id, fld_score_min, fld_score_max; + private final Db_conn conn; + public Srch_link_reg_tbl(Db_conn conn) { + this.conn = conn; + tbl_name = Tbl_name; + fld_id = flds.Add_int_pkey ("reg_id"); // corresponds to link_tbl_ary_idx + fld_db_id = flds.Add_int ("reg_db_id"); // corresponds to xowa_db + fld_db_type = flds.Add_int ("reg_db_type"); // "title"; "text" + fld_ns_ids = flds.Add_int ("reg_ns_ids"); // "0"; "*" + fld_sub_id = flds.Add_int ("reg_sub_id"); // "0"; "*" + fld_score_min = flds.Add_int ("reg_score_min"); // -1 + fld_score_max = flds.Add_int ("reg_score_max"); // -1 + } + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Rls() {} + public void Insert(int id, int db_id, String db_type, String ns_ids, int sub_id, int score_min, int score_max) { + Db_stmt stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Clear().Val_int(fld_id, id).Val_int(fld_db_id, db_id).Val_str(fld_db_type, db_type).Val_str(fld_ns_ids, ns_ids).Val_int(fld_sub_id, sub_id).Val_int(fld_score_min, score_min).Val_int(fld_score_max, score_max).Exec_insert(); + } + public static final String Tbl_name = "search_link_reg"; + public static final String Db_type__title = "title" , Db_type__text = "text"; + public static final String Ns_type__main = "0" , Ns_type__rest = "*"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_link_row.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_link_row.java index a27517de8..e5b31d670 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_link_row.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_link_row.java @@ -13,3 +13,18 @@ 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.addons.wikis.searchs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +public class Srch_link_row { + public Srch_link_row(int word_id, int page_id, int link_score) { + this.Word_id = word_id; + this.Page_id = page_id; + this.Link_score = link_score; + } + public final int Word_id; + public final int Page_id; + public final int Link_score; + public int Trg_db_id; + + public int Db_row_size() {return Db_row_size_fixed;} + private static final int Db_row_size_fixed = (3 * 4); // 5 ints +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_link_tbl.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_link_tbl.java index a27517de8..e1fdce23b 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_link_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_link_tbl.java @@ -13,3 +13,42 @@ 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.addons.wikis.searchs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.dbs.*; +public class Srch_link_tbl { + public final String fld_word_id, fld_page_id, fld_link_score; + public final Db_conn conn; + public Srch_link_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "search_link"; + fld_word_id = flds.Add_int("word_id"); + fld_page_id = flds.Add_int("page_id"); + fld_link_score = flds.Add_int_dflt(Fld_link_score, 0); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public Dbmeta_fld_list Flds() {return flds;} private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + public int Id; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Create_idx__page_id() {} // TODO_OLD: conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "page_id", fld_page_id));} + public void Create_idx__link_score() {conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "word_id__link_score", fld_word_id, Fld_link_score));} + + public Srch_link_row New_row(Db_rdr rdr) { + return new Srch_link_row(rdr.Read_int(fld_word_id), rdr.Read_int(fld_page_id), rdr.Read_int(fld_link_score)); + } + public void Fill_for_insert(Db_stmt stmt, Srch_link_row row) { + stmt.Val_int(fld_word_id, row.Word_id).Val_int(fld_page_id, row.Page_id).Val_int(fld_link_score, row.Link_score); + } + public void Delete(int page_id) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.search_link: delete started: db=~{0} page_id=~{1}", conn.Conn_info().Raw(), page_id); + conn.Stmt_delete(tbl_name, fld_page_id).Crt_int(fld_page_id, page_id).Exec_delete(); + Gfo_usr_dlg_.Instance.Log_many("", "", "db.search_link: delete done"); + } + public void Update_page_id(int old_id, int new_id) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.search_link: update_page_id started: db=~{0} old_id=~{1} new_id=~{2}", conn.Conn_info().Raw(), old_id, new_id); + conn.Stmt_update(tbl_name, String_.Ary(fld_page_id), fld_page_id).Val_int(fld_page_id, old_id).Crt_int(fld_page_id, new_id).Exec_update(); + Gfo_usr_dlg_.Instance.Log_many("", "", "db.search_link: update done"); + } + + public static final Srch_link_tbl[] Ary_empty = new Srch_link_tbl[0]; + public static final String Fld_link_score = "link_score"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_temp_tbl.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_temp_tbl.java index a27517de8..9abd3e135 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_temp_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_temp_tbl.java @@ -13,3 +13,34 @@ 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.addons.wikis.searchs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.dbs.*; +public class Srch_temp_tbl { + public final String tbl_name = "search_temp"; + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_word_id, fld_page_id, fld_word_text; + public final Db_conn conn; private Db_stmt stmt_insert; + public Srch_temp_tbl(Db_conn conn) { + this.conn = conn; + flds.Add_int_pkey_autonum("word_uid"); + fld_word_id = flds.Add_int("word_id"); + fld_page_id = flds.Add_int("page_id"); + fld_word_text = flds.Add_str("word_text", 255); + } + public void Insert_bgn() { + conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds)); + stmt_insert = conn.Stmt_insert(tbl_name, flds); + conn.Txn_bgn("schema__search_temp__insert"); + } + public void Insert_cmd_by_batch(int word_id, int page_id, byte[] word) { + stmt_insert.Clear().Val_int(fld_word_id, word_id).Val_int(fld_page_id, page_id).Val_bry_as_str(fld_word_text, word).Exec_insert(); + } + public void Insert_end() { + conn.Txn_end(); + stmt_insert = Db_stmt_.Rls(stmt_insert); + // Srch_db_mgr.Optimize_unsafe_(conn, Bool_.Y); // NOTE: fails in multi-db due to transaction + conn.Meta_idx_create(Xoa_app_.Usr_dlg(), Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "word_text__word_id", fld_word_text, fld_word_id)); + // conn.Meta_idx_create(Xoa_app_.Usr_dlg(), Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "page_id", fld_page_id)); + // Srch_db_mgr.Optimize_unsafe_(conn, Bool_.N); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_word_row.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_word_row.java index a27517de8..4c69de51a 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_word_row.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_word_row.java @@ -13,3 +13,22 @@ 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.addons.wikis.searchs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +public class Srch_word_row { + public Srch_word_row(int id, byte[] text, int link_count, int link_count_score, int link_score_min, int link_score_max) { + this.Id = id; this.Text = text; + this.Link_count = link_count; this.Link_count_score = link_count_score; + this.Link_score_min = link_score_min; this.Link_score_max = link_score_max; + } + public final int Id; + public final byte[] Text; + public final int Link_count; + public final int Link_count_score; + public final int Link_score_min; + public final int Link_score_max; + + public int Db_row_size() {return Db_row_size_fixed + Text.length;} + private static final int Db_row_size_fixed = (5 * 4); // 5 ints + + public static final Srch_word_row Empty = new Srch_word_row(-1, Bry_.Empty, 0, 0, 0, 0); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_word_tbl.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_word_tbl.java index a27517de8..5bc96b84d 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_word_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/dbs/Srch_word_tbl.java @@ -13,3 +13,67 @@ 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.addons.wikis.searchs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.dbs.*; +public class Srch_word_tbl implements Rls_able { + public final String tbl_name; + public final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + public final String fld_id, fld_text, fld_link_count, fld_link_count_score, fld_link_score_min, fld_link_score_max; + public final Db_conn conn; private Db_stmt stmt_insert, stmt_select_by; + public Srch_word_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = TABLE_NAME; + this.fld_id = flds.Add_int_pkey("word_id"); + this.fld_text = flds.Add_str("word_text", 255); + this.fld_link_count = flds.Add_int("link_count"); + this.fld_link_count_score = Dbmeta_fld_itm.Make_or_null(conn, flds, tbl_name, Dbmeta_fld_tid.Tid__int, 0, "link_count_score"); + this.fld_link_score_min = Dbmeta_fld_itm.Make_or_null(conn, flds, tbl_name, Dbmeta_fld_tid.Tid__int, Int_.Max_value__31, "link_score_min"); + this.fld_link_score_max = Dbmeta_fld_itm.Make_or_null(conn, flds, tbl_name, Dbmeta_fld_tid.Tid__int, 0, "link_score_max"); + conn.Rls_reg(this); + } + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Create_idx() { + // idx for rng_bgn, rng_end + conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "word_text__link_score_max__link_score_min", fld_text, Fld_link_score_max, Fld_link_score_min)); + + // idx for like + conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "link_score_max__link_score_min", Fld_link_score_max, Fld_link_score_min)); + } + public void Insert_bgn() {conn.Txn_bgn("schema__search_word__insert"); stmt_insert = conn.Stmt_insert(tbl_name, flds);} + public void Insert_end() {conn.Txn_end(); stmt_insert = Db_stmt_.Rls(stmt_insert);} + public void Insert_cmd_by_batch(int id, byte[] word, int page_count) { + stmt_insert.Clear().Val_int(fld_id, id).Val_bry_as_str(fld_text, word).Val_int(fld_link_count, page_count).Exec_insert(); + } + public void Insert_by_itm(Db_stmt stmt, Srch_word_row row) { + stmt.Clear() + .Val_int(fld_id, row.Id).Val_bry_as_str(fld_text, row.Text).Val_int(fld_link_count, row.Link_count) + .Val_int(fld_link_count_score, row.Link_count_score).Val_int(fld_link_score_min, row.Link_score_min).Val_int(fld_link_score_max, row.Link_score_max) + .Exec_insert(); + } + public Srch_word_row Select_or_empty(byte[] word) { + if (stmt_select_by == null) stmt_select_by = conn.Stmt_select(tbl_name, flds, fld_text); + Db_rdr rdr = stmt_select_by.Clear().Crt_bry_as_str(fld_text, word).Exec_select__rls_manual(); + try {return rdr.Move_next() ? New_row(rdr) : Srch_word_row.Empty;} + finally {rdr.Rls();} + } + public Srch_word_row New_row(Db_rdr rdr) { + int page_count = fld_link_count == Dbmeta_fld_itm.Key_null ? 0 : rdr.Read_int(fld_link_count); + int link_score_min = fld_link_score_min == Dbmeta_fld_itm.Key_null ? page_count : rdr.Read_int(fld_link_score_min); + int link_score_max = fld_link_score_max == Dbmeta_fld_itm.Key_null ? page_count : rdr.Read_int(fld_link_score_max); + int link_count_score = 0; + if (fld_link_count_score != Dbmeta_fld_itm.Key_null) { + try {link_count_score = rdr.Read_int(fld_link_count_score);} + catch (Exception e) {// handle 2016-05 and earlier wikis which stored value as double instead of int + Err_.Noop(e); + link_count_score = (int)rdr.Read_double(fld_link_count_score); + } + } + return new Srch_word_row(rdr.Read_int(fld_id), rdr.Read_bry_by_str(fld_text), page_count, link_count_score, link_score_min, link_score_max); + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + stmt_select_by = Db_stmt_.Rls(stmt_select_by); + } + public static final String Fld_link_score_min = "link_score_min", Fld_link_score_max = "link_score_max"; + public static final String TABLE_NAME = "search_word"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/gui/htmlbars/Srch_htmlbar_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/gui/htmlbars/Srch_htmlbar_mgr.java index a27517de8..7c4788a99 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/gui/htmlbars/Srch_htmlbar_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/gui/htmlbars/Srch_htmlbar_mgr.java @@ -13,3 +13,40 @@ 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.addons.wikis.searchs.gui.htmlbars; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.gui.*; +import gplx.core.net.*; import gplx.core.net.qargs.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.addons.wikis.searchs.searchers.cbks.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +public class Srch_htmlbar_mgr implements Gfo_invk { + private Srch_search_addon addon; + private int results_max = 25; + public void Init_by_kit(Xoae_app app, gplx.gfui.kits.core.Gfui_kit kit) { + app.Cfg().Bind_many_app(this, Cfg__enabled, Cfg__results_max); + } + public boolean Enabled() {return enabled;} private boolean enabled = true; + public void Search(Xowe_wiki wiki, byte[] search_bry, byte[] cbk_func) { + if ( !enabled + || search_bry.length == 0 + ) return; + if (addon == null) + addon = Srch_search_addon.Get(wiki); + else { + if (!Bry_.Eq(wiki.Domain_bry(), addon.Wiki_domain())) // NOTE: suggest-box caches addon at wiki level; need to check if wiki has changed + addon = Srch_search_addon.Get(wiki); + } + // tab_close_mgr.Add(this); + Srch_search_qry qry = Srch_search_qry.New__suggest_box(wiki, wiki.App().Addon_mgr().Itms__search__special().Ns_mgr(), wiki.App().Addon_mgr().Itms__search__special().Auto_wildcard(), results_max, search_bry); + Srch_rslt_cbk__suggest_box cbk = new Srch_rslt_cbk__suggest_box(wiki.Appe(), cbk_func, search_bry); + addon.Search(qry, cbk); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Cfg__enabled)) enabled = m.ReadYn("v"); + else if (ctx.Match(k, Cfg__results_max)) results_max = m.ReadInt("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Cfg__enabled = "xowa.addon.search.suggest.enabled" + , Cfg__results_max = "xowa.addon.search.suggest.results_max" + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/gui/urlbars/Srch_urlbar_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/gui/urlbars/Srch_urlbar_mgr.java index a27517de8..7efcbe1ea 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/gui/urlbars/Srch_urlbar_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/gui/urlbars/Srch_urlbar_mgr.java @@ -13,3 +13,88 @@ 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.addons.wikis.searchs.gui.urlbars; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.gui.*; +import gplx.gfui.controls.standards.*; +import gplx.gfui.kits.core.*; +import gplx.xowa.addons.wikis.searchs.searchers.*; import gplx.xowa.addons.wikis.searchs.searchers.cbks.*; import gplx.xowa.addons.wikis.searchs.searchers.crts.*; +import gplx.xowa.guis.views.*; +public class Srch_urlbar_mgr implements Gfo_invk { // NOTE: needs to be app-level b/c binding to key events in urlbar + private Srch_search_addon addon; + private Xoae_app app; + private GfuiComboBox url_bar; + private boolean enabled = true; + private int max_results = 10; + private boolean auto_wildcard = true; + private final Srch_ns_mgr ns_mgr = new Srch_ns_mgr().Add_main_if_empty(); + private Srch_crt_scanner_syms syms = Srch_crt_scanner_syms.New__dflt(); + private void Ns_ids_(String s) { + int[] ns_ids = Int_ary_.Empty; + if (String_.Eq(s, "*")) {} // leave as int[0]; ns_mgr will interpret as wildcard + else { + ns_ids = Int_ary_.Parse(s, ","); + } + ns_mgr.Add_by_int_ids(ns_ids); + if (addon != null) addon.Clear_rslts_cache(); // invalidate cache when ns changes; else ns_0 rslts will show up in ns_100; DATE:2016-03-24 + } + + public void Init_by_kit(Xoae_app app, Gfui_kit kit) { + // get url_bar and set defaults + this.url_bar = app.Gui_mgr().Browser_win().Url_box(); + url_bar.Items__jump_len_(5); + url_bar.Items__visible_rows_(10); + + this.app = app; + app.Cfg().Bind_many_app(this, Cfg__enabled, Cfg__max_results, Cfg__auto_wildcard, Cfg__ns_ids, Cfg__visible_rows, Cfg__jump_len); + } + public void Search() { + if (!enabled) return; + Xog_tab_itm active_tab = app.Gui_mgr().Browser_win().Tab_mgr().Active_tab(); if (active_tab == null) return; + Xow_wiki wiki = active_tab.Wiki(); + + String search_str = url_bar.Text(); + url_bar.Text_fallback_(search_str); + + // remove "en.wikipedia.org/wiki/" + // String url_bgn = wiki.Domain_str() + gplx.xowa.htmls.hrefs.Xoh_href_.Str__wiki; + // if (String_.Has_at_bgn(search_str, url_bgn)) + // search_str = String_.Mid(search_str, String_.Len(url_bgn)); + if (String_.Len_eq_0(search_str)) { + url_bar.Items__update(String_.Ary_empty); + return; + } + + if (addon == null) { + addon = Srch_search_addon.Get(wiki); + } + else { + if (!Bry_.Eq(wiki.Domain_bry(), addon.Wiki_domain())) // NOTE: url_bar_api caches addon at wiki level; need to check if wiki has changed + addon = Srch_search_addon.Get(wiki); + } + if (addon.Db_mgr().Cfg().Version_id__needs_upgrade()) return; // exit early, else will flash "searching" message below; note that url-bar should not trigger upgrade; + url_bar.List_sel_idx_(0); // clear selected list item; EX: search "a" -> page down; sel is row #5 -> search "b" -> sel should not be #5; DATE:2016-03-24 + if (!url_bar.List_visible()) url_bar.Items__size_to_fit(max_results); // resize offscreen; handles 1st search when dropdown flashes briefly in middle of screen before being moved under bar; DATE:2016-03-24 + + Srch_search_qry qry = Srch_search_qry.New__url_bar(wiki, ns_mgr, syms, auto_wildcard, max_results, Bry_.new_u8(search_str)); + Srch_rslt_cbk__url_bar cbk = new Srch_rslt_cbk__url_bar(app, url_bar, max_results); + Xoa_app_.Usr_dlg().Prog_one("", "", "searching (please wait): ~{0}", search_str); + addon.Search(qry, cbk); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Cfg__enabled)) enabled = m.ReadYn("v"); + else if (ctx.Match(k, Cfg__max_results)) max_results = m.ReadInt("v"); + else if (ctx.Match(k, Cfg__auto_wildcard)) auto_wildcard = m.ReadYn("v"); + else if (ctx.Match(k, Cfg__ns_ids)) Ns_ids_(m.ReadStr("v")); + else if (ctx.Match(k, Cfg__visible_rows)) url_bar.Items__visible_rows_(m.ReadInt("v")); + else if (ctx.Match(k, Cfg__jump_len)) url_bar.Items__jump_len_(m.ReadInt("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Cfg__enabled = "xowa.gui.url_bar.search.enabled" + , Cfg__max_results = "xowa.gui.url_bar.search.max_results" + , Cfg__auto_wildcard = "xowa.gui.url_bar.search.auto_wildcard" + , Cfg__ns_ids = "xowa.gui.url_bar.search.ns_ids" + , Cfg__visible_rows = "xowa.gui.url_bar.search.visible_rows" + , Cfg__jump_len = "xowa.gui.url_bar.search.jump_len" + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_highlight_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_highlight_mgr.java index a27517de8..de01a7321 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_highlight_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_highlight_mgr.java @@ -13,3 +13,103 @@ 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.addons.wikis.searchs.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.core.btries.*; +import gplx.xowa.langs.cases.*; +public class Srch_highlight_mgr { + private final Xol_case_mgr case_mgr; + private final Bry_bfr tmp_bfr = Bry_bfr_.New_w_size(32); + private Srch_highlight_itm[] srch_lc_itms; + private int srch_words_len; + public Srch_highlight_mgr(Xol_case_mgr case_mgr) {this.case_mgr = case_mgr;} + public Srch_highlight_mgr Search_(byte[] srch_mc_bry) { + synchronized (tmp_bfr) { + // build array of search_words + byte[] srch_lc_bry = case_mgr.Case_build_lower(srch_mc_bry); + byte[][] srch_lc_ary = Bry_split_.Split(srch_lc_bry, Byte_ascii.Space, Bool_.Y); + this.srch_words_len = srch_lc_ary.length; + this.srch_lc_itms = new Srch_highlight_itm[srch_words_len]; + for (int i = 0; i < srch_words_len; ++i) { + byte[] srch_lc_itm = srch_lc_ary[i]; + srch_lc_itms[i] = new Srch_highlight_itm(i, srch_lc_itm); + } + + // sort to search first by longest search_word; needed for searches like "A Abc" and titles like "Abc A", else "A" will match "Abc" and "Abc" will match nothing + Array_.Sort(srch_lc_itms, Srch_highlight_bry_sorter.Instance); + return this; + } + } + public byte[] Highlight(byte[] page_mc_bry) { + synchronized (tmp_bfr) { + byte[][] page_mc_words = Bry_split_.Split(page_mc_bry, Byte_ascii.Space, Bool_.Y); + byte[][] page_lc_words = Bry_split_.Split(case_mgr.Case_build_lower(page_mc_bry), Byte_ascii.Space, Bool_.Y); + int page_words_len = page_lc_words.length; + boolean[] page_words_done = new boolean[page_words_len]; + + // loop over search_words; EX: search_raw="a b" -> "a", "b" + for (int i = 0; i < srch_words_len; ++i) { + Srch_highlight_itm srch_lc_itm = srch_lc_itms[i]; + byte[] srch_lc_word = srch_lc_itm.Word; + + // loop over page_title words; EX: page_raw="A B C" -> "a", "b", "c" + for (int j = 0; j < page_words_len; ++j) { + byte[] page_lc_word = page_lc_words[j]; + int find_pos = Bry_find_.Find_fwd(page_lc_word, srch_lc_word); + + // skip: search is not in page; EX: page_raw="D e"; should not happen, but exit now else Array out of bounds exception below + if (find_pos == Bry_find_.Not_found) continue; + + // skip: find_pos is not BOS and prv byte is not dash or paren; EX: "Za" should be skipped; "-a" and "(a" should not + if (find_pos > 0) { + byte prv_byte = page_lc_word[find_pos - 1]; + if ( prv_byte != Byte_ascii.Dash + && prv_byte != Byte_ascii.Paren_bgn + ) + continue; + } + + // skip: item already matched; EX: "a"; "a a" + if (page_words_done[j]) continue; + page_words_done[j] = true; + + // NOTE: lc_idx is guaranteed to equal mc_idx + byte[] page_mc_word = page_mc_words[j]; + int srch_lc_word_len = srch_lc_word.length; + + // build new word; EX: "A" + tmp_bfr.Clear(); + tmp_bfr.Add_mid(page_mc_word, 0, find_pos); + tmp_bfr.Add(gplx.langs.htmls.Gfh_tag_.Strong_lhs); + tmp_bfr.Add_mid(page_mc_word, find_pos, find_pos + srch_lc_word_len); + tmp_bfr.Add(gplx.langs.htmls.Gfh_tag_.Strong_rhs); + tmp_bfr.Add_mid(page_mc_word, find_pos + srch_lc_word_len, page_mc_word.length); + byte[] repl = tmp_bfr.To_bry_and_clear(); + + // overwrite page_title word + page_mc_words[j] = repl; + } + } + + // rebuild page_words ary + for (int i = 0; i < page_words_len; ++i) { + if (i != 0) tmp_bfr.Add_byte_space(); // NOTE: this assumes one space separating titles which is true for all MW titles + tmp_bfr.Add(page_mc_words[i]); + } + return tmp_bfr.To_bry_and_clear(); + } + } +} +class Srch_highlight_itm { + public Srch_highlight_itm(int idx, byte[] word) {this.Idx = idx; this.Word = word; this.Word_len = word.length;} + public final int Idx; + public final byte[] Word; + public final int Word_len; +} +class Srch_highlight_bry_sorter implements gplx.core.lists.ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + Srch_highlight_itm lhs = (Srch_highlight_itm)lhsObj; + Srch_highlight_itm rhs = (Srch_highlight_itm)rhsObj; + return -Int_.Compare(lhs.Word_len, rhs.Word_len); // - for descending + } + public static final Srch_highlight_bry_sorter Instance = new Srch_highlight_bry_sorter(); Srch_highlight_bry_sorter() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_highlight_mgr_tst.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_highlight_mgr_tst.java index a27517de8..72fd4468f 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_highlight_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_highlight_mgr_tst.java @@ -13,3 +13,33 @@ 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.addons.wikis.searchs.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import org.junit.*; import gplx.xowa.langs.cases.*; +public class Srch_highlight_mgr_tst { + private final Srch_highlight_mgr_tstr tstr = new Srch_highlight_mgr_tstr(); + @Test public void Full__one() {tstr.Test("a" , "A" , "A");} + @Test public void Full__many() {tstr.Test("a b" , "A B" , "A B");} + @Test public void Part__one() {tstr.Test("a" , "A1" , "A1");} + @Test public void Part__many() {tstr.Test("a b" , "A1 B1" , "A1 B1");} + @Test public void Unordered() {tstr.Test("b a" , "A1 B1" , "A1 B1");} + @Test public void Repeat__part() {tstr.Test("a ab" , "Ab A" , "Ab A");} + @Test public void Repeat__full() {tstr.Test("a" , "A A" , "A A");} + @Test public void Close() {tstr.Test("a" , "Ba Aa" , "Ba Aa");} + @Test public void Comma() {tstr.Test("a" , "A, b" , "A, b");} + @Test public void Dash() {tstr.Test("b" , "A-B c" , "A-B c");} + @Test public void Parens() {tstr.Test("a" , "(A)" , "(A)");} + @Test public void Strong() {tstr.Test("strong" , "strong strong" , "strong strong");} + @Test public void Dash_w_mixed_cases() {tstr.Test("b" , "A-a B" , "A-a B");} // search_parser treats A-a separately from a-a + @Test public void Acronymn() {tstr.Test("ab" , "A.B." , "A.B.");} + // @Test public void Slash() {tstr.Test("b" , "A/B/C" , "A/B/C");} +} +class Srch_highlight_mgr_tstr { + private final Srch_highlight_mgr mgr; + private final Xol_case_mgr case_mgr = Xol_case_mgr_.A7(); + public Srch_highlight_mgr_tstr() { + mgr = new Srch_highlight_mgr(case_mgr); + } + public void Test(String search, String title, String expd) { + Tfds.Eq(expd, String_.new_u8(mgr.Search_(Bry_.new_u8(search)).Highlight(Bry_.new_u8(title)))); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_sym_parser.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_sym_parser.java index a27517de8..d3f43e6dc 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_sym_parser.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_sym_parser.java @@ -13,3 +13,248 @@ 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.addons.wikis.searchs.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.core.btries.*; import gplx.core.primitives.*; +interface Srch_sym_parser { + int Tid(); + byte[][] Hooks_ary(); + int Parse(Srch_text_parser mgr, byte[] src, int src_end, int hook_bgn, int hook_end); +} +class Srch_sym_parser_ { + public static final int Tid__terminal = 1, Tid__split = 2, Tid__enclosure = 3, Tid__dot = 4, Tid__ellipsis = 5, Tid__apos = 6, Tid__dash = 7; +} +class Srch_sym_parser__terminal implements Srch_sym_parser { + public Srch_sym_parser__terminal(byte[] hook_bry) {this.hooks_ary = Bry_.Ary(hook_bry);} + public int Tid() {return Srch_sym_parser_.Tid__terminal;} + public byte[][] Hooks_ary() {return hooks_ary;} private final byte[][] hooks_ary; + public int Parse(Srch_text_parser mgr, byte[] src, int src_end, int hook_bgn, int hook_end) { + if (mgr.Cur__end__chk(hook_end)) { // hook at word_end; EX: "a, b" -> "a", "b" + int word_bgn = mgr.Cur__bgn(); + if (word_bgn == Srch_text_parser.None) return hook_end; + int word_end = hook_bgn; + for (int i = hook_bgn - 1; i >= word_bgn; --i) { // loop bwd to gobble up streams of terminals; EX: "?!" + byte b = src[i]; + if (mgr.word_end_trie.Match_bgn_w_byte(b, src, i, hook_bgn) == null) break; + word_end = i; + } + mgr.Words__add_if_pending_and_clear(word_end); // make word; note that " , " will not make word b/c word_bgn == -1 + return hook_end; + } + else {}// hook at word_mid or word_bgn; noop; EX: "1,000" -> "1,000"; note that " ,abc" will ignore "," +// if (mgr.Cur__bgn() == - 1) { +// mgr.Cur__bgn__set(hook_bgn); +// } +// } + return hook_end; + } +} +class Srch_sym_parser__split implements Srch_sym_parser { + private final Btrie_slim_mgr trie = Btrie_slim_mgr.cs(); + private final boolean handle_eos; + public Srch_sym_parser__split(boolean handle_eos, String... hooks_ary_as_str) { + this.handle_eos = handle_eos; + this.hooks_ary = Bry_.Ary(hooks_ary_as_str); + int hooks_len = hooks_ary.length; + for (int i = 0; i < hooks_len; ++i) { + byte[] hook = hooks_ary[i]; + trie.Add_obj(hook, new Int_obj_val(hook.length)); + } + } + public int Tid() {return Srch_sym_parser_.Tid__split;} + public byte[][] Hooks_ary() {return hooks_ary;} private final byte[][] hooks_ary; + public int Parse(Srch_text_parser mgr, byte[] src, int src_end, int hook_bgn, int hook_end) { + mgr.Words__add_if_pending_and_clear(hook_bgn); + + int rv = hook_end; + while (rv < src_end) { // loop to skip multiple syms in same group; EX: "a \n\t\r b" + byte b = src[rv]; + Object o = trie.Match_bgn_w_byte(b, src, rv, src_end); + if (o == null) break; // current sequence is not in grp; stop + Int_obj_val itm_len = (Int_obj_val)o; + rv += itm_len.Val(); // add len of sym to pos + } + return rv; + } + public int Find_fwd(byte[] src, int src_bgn, int src_end) { + for (int i = src_bgn; i < src_end; ++i) { + byte b = src[i]; + Object o = trie.Match_bgn_w_byte(b, src, i, src_end); + if (o != null) return i; + } + return src_end; + } + public boolean Is_next(byte[] src, int pos, int end) { + if (pos >= end) return handle_eos; + byte b = src[pos]; + Object o = trie.Match_bgn_w_byte(b, src, pos, end); + return o != null; + } +} +class Srch_sym_parser__paren_bgn implements Srch_sym_parser { + private final byte bgn_byte, end_byte; + public Srch_sym_parser__paren_bgn(byte bgn_byte, byte end_byte) { + this.bgn_byte = bgn_byte; this.end_byte = end_byte; + this.hooks_ary = Bry_.Ary(Bry_.New_by_byte(bgn_byte)); + } + public int Tid() {return Srch_sym_parser_.Tid__enclosure;} + public byte[][] Hooks_ary() {return hooks_ary;} private final byte[][] hooks_ary; + public int Parse(Srch_text_parser mgr, byte[] src, int src_end, int paren_lhs_bgn, int paren_lhs_end) { + int word_bgn = mgr.Cur__bgn(); + paren_lhs_end = Bry_find_.Find_fwd_while(src, paren_lhs_end, src_end, bgn_byte); + int paren_rhs_bgn = Bry_find_.Find_fwd(src, end_byte, paren_lhs_end, src_end); + if (paren_rhs_bgn == Bry_find_.Not_found) return paren_lhs_end; // paren_rhs missing; noop; NOTE: handles both "a(b" -> "a(b" and "a (b" -> "a", "b" + int paren_rhs_end = Bry_find_.Find_fwd_while(src, paren_rhs_bgn, src_end, end_byte); + int word_end = mgr.Cur__end__find__text_only(paren_lhs_end + 1); + if (word_end < paren_rhs_bgn) + word_end = mgr.Cur__end__find__text_only(paren_rhs_end); + boolean recurse = false; + if (word_bgn == -1) { // paren_lhs at word_bgn; EX: "a (b)" + if (word_end == paren_rhs_end) // paren_rhs at word_end; EX: "a (b) c" + recurse = true; + else { // paren_rhs at word_mid; EX: "a (b)c" + mgr.Words__add_direct(paren_lhs_bgn, word_end); + mgr.Words__add_direct(paren_rhs_end, word_end); + } + } + else { // paren_lhs at word_mid; EX: "a(b)" + mgr.Words__add_direct(word_bgn, word_end); + if (word_end == paren_rhs_end) // paren_rhs at word_end; EX: "a(b) c" + mgr.Words__add_direct(word_bgn, paren_lhs_bgn); + else {} // paren_rhs at word_mid; EX: "a (b)c" + mgr.Cur__bgn__reset(); + } + if (recurse) + mgr.Make_copy().Parse(mgr.lcase, src, paren_lhs_end, paren_rhs_bgn); + return word_end; + } +} +class Srch_sym_parser__dot implements Srch_sym_parser { // handle periods which will add two entries; EX: "H. G. Wells" -> "H.", "G.", "H", "G", "Wells" + private final byte[] sym; private final int sym_len; + public Srch_sym_parser__dot(String sym_str) {this.sym = Bry_.new_u8(sym_str); this.sym_len = sym.length;} + public int Tid() {return Srch_sym_parser_.Tid__dot;} + public byte[][] Hooks_ary() {return Hooks_const;} private static final byte[][] Hooks_const = Bry_.Ary("."); + public int Parse(Srch_text_parser mgr, byte[] src, int src_end, int hook_bgn, int hook_end) { + int word_bgn = mgr.Cur__bgn(); + if (word_bgn == -1) { // dot at start of word; EX: ".NET", "Colt .45" + int word_end = mgr.Cur__end__find(hook_bgn + 1); + if (word_end - hook_bgn == 1) return hook_end; // ignore stand-alone sym; EX: "a . . . b" + mgr.Words__add__chk_dash(hook_bgn, word_end); // make word; EX: ".45" + if (Bry_find_.Find_fwd(src, sym, hook_bgn + 1, word_end) == -1) // only add "root" word, if sym is not in middle of String; EX: ".int" -> "int"; ".A.B" x> "A.B" + mgr.Words__add__chk_dash(hook_bgn + 1, word_end); // make word; EX: "45" + mgr.Cur__bgn__reset(); + return word_end; + } + else { + if (mgr.Cur__end__chk(hook_end)) { // at end of word; EX: "U.S.A. b" vs. "H. G. b" + mgr.Words__add__chk_dash(word_bgn, hook_end); // make word; EX: "vs." + if (Bry_find_.Find_fwd(src, sym, word_bgn, hook_bgn) == -1) // no dots in word; add "root" word; "vs." -> "vs.", "vs"; "U.S.A." -> "U.S.A." x> "U.S.A" + mgr.Words__add__chk_dash(word_bgn, hook_bgn); // make word; EX: "vs" + else { + for (int i = word_bgn; i < hook_end; ++i) { // dots in middle of String; extract word; EX: "U.S.A." -> USA + if (Bry_.Eq(src, i, i + sym_len, sym)) continue;// skip syms + mgr.Tmp_bfr.Add_byte(src[i]); + } + mgr.Words__add_direct(mgr.Tmp_bfr.To_bry_and_clear()); + } + mgr.Cur__bgn__reset(); + } + } + return hook_end; + } +} +class Srch_sym_parser__ellipsis implements Srch_sym_parser { // ellipsis which have variable length; EX: "..", "...", ".... " + private final byte ellipsis_byte; + public Srch_sym_parser__ellipsis(byte ellipsis_byte, String hook) { + this.ellipsis_byte = ellipsis_byte; + this.hooks_ary = Bry_.Ary(hook); + } + public int Tid() {return Srch_sym_parser_.Tid__ellipsis;} + public byte[][] Hooks_ary() {return hooks_ary;} private final byte[][] hooks_ary; + public int Parse(Srch_text_parser mgr, byte[] src, int src_end, int hook_bgn, int hook_end) { + mgr.Words__add_if_pending_and_clear(hook_bgn); // add word; EX: "Dreams" in "Dreams..." + int rv = Bry_find_.Find_fwd_while(src, hook_end, src_end, ellipsis_byte); // skip multiple ellipsis; EX: ...... + mgr.Words__add__chk_dash(hook_bgn, rv); // add ellipsis + return rv; + } +} +class Srch_sym_parser__apos implements Srch_sym_parser { // apos which can be contraction ("I'm"), possessive ("today's") and plural ("plans'") + private final byte[] hook_bry; + public Srch_sym_parser__apos(String hook) { + this.hook_bry = Bry_.new_u8(hook); + this.hooks_ary = Bry_.Ary(hook_bry); + } + public int Tid() {return Srch_sym_parser_.Tid__apos;} + public byte[][] Hooks_ary() {return hooks_ary;} private final byte[][] hooks_ary; + public int Parse(Srch_text_parser mgr, byte[] src, int src_end, int hook_bgn, int hook_end) { + int word_bgn = mgr.Cur__bgn(); + int word_end = -1; + if (word_bgn == -1) { // sym at word_bgn; EX: "'n'" + word_bgn = hook_bgn; + word_end = mgr.Cur__end__find(hook_bgn + 1); + mgr.Words__add__chk_dash(word_bgn, word_end); + + // "'n'" -> "n" + int alt_word_bgn = word_bgn + 1; + int alt_word_end = Bry_find_.Find_bwd__skip(src, word_end, alt_word_bgn, hook_bry); + if (alt_word_end - alt_word_bgn > 0) // do not add if 0-len; EX: "' (disambiguation)" + mgr.Words__add__chk_dash(alt_word_bgn, alt_word_end); + mgr.Cur__bgn__reset(); + return word_end; + } + word_end = mgr.Cur__end__find(hook_end); + if (hook_end == word_end) { // sym at word_end; EX: "A' " + mgr.Words__add_if_pending_and_clear(hook_bgn); // add root only; EX: "a'" -> "a" x> "a", "a'" + return hook_end; + } + else { + byte[] root_word = null; + if (hook_bgn - word_bgn == 1) + root_word = mgr.Tmp_bfr.Add_byte(src[word_bgn]).Add_mid(src, hook_bgn + 1, word_end).To_bry_and_clear(); + else if (word_end - hook_end == 1) + root_word = mgr.Tmp_bfr.Add_mid(src, word_bgn, hook_bgn).Add_byte(src[hook_end]).To_bry_and_clear(); + // Tfds.Write(word_bgn, hook_bgn, hook_end, word_end); + if (root_word != null) { + mgr.Words__add__chk_dash(word_bgn, word_end); // add full; EX: "o'clock" + mgr.Words__add_direct(root_word); // add root; EX: "oclock" + mgr.Cur__bgn__reset(); + } + return word_end; + } + } +} +class Srch_sym_parser__dash implements Srch_sym_parser { + public Srch_sym_parser__dash(String hook) {this.hooks_ary = Bry_.Ary(hook);} + public int Tid() {return Srch_sym_parser_.Tid__dash;} + public byte[][] Hooks_ary() {return hooks_ary;} private final byte[][] hooks_ary; + public int Parse(Srch_text_parser mgr, byte[] src, int src_end, int hook_bgn, int hook_end) { + int cur_bgn = mgr.Cur__bgn(); // get word_bgn + if (cur_bgn == -1) { // no word_bgn; "-" is 1st char + if (mgr.Cur__end__chk(hook_end)) { // next char is space + mgr.Words__add_direct(hook_bgn, hook_end); // add dash; EX: "a - b" -> "a", "-", "b" + } + else { // next char is something else + mgr.Cur__bgn__set(hook_bgn); // update word_bgn to dash + mgr.Dash__bgn_(hook_end); + } + return hook_end; + } + else { // word_bgn exists + int dash_bgn = mgr.Dash__bgn(); + if (dash_bgn == Srch_text_parser.None) { // 1st dash + mgr.Words__add__chk_dash(cur_bgn, hook_bgn);// add word; EX: "a" in "a-b" + } else { // 2nd or more; add stub; EX: "a-b-c"; 2nd "-" should add "b" + if (hook_bgn > dash_bgn) // only add if len > 0; handles multiple dashes; EX: "---" + mgr.Words__add_direct(dash_bgn, hook_bgn); + } + } + mgr.Dash__bgn_(hook_end); + return hook_end; + } + public void Add_pending_word(Srch_text_parser mgr, byte[] src, int word_end) { + int dash_bgn = mgr.Dash__bgn(); + if (dash_bgn == Srch_text_parser.None) return; + if (word_end - dash_bgn > 0) // only add if there is word to right of dash; EX: "a-" + mgr.Words__add_direct(dash_bgn, word_end); + mgr.Dash__bgn_(Srch_text_parser.None); // clear the dash + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_text_parser.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_text_parser.java index a27517de8..ce4ed6110 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_text_parser.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_text_parser.java @@ -13,3 +13,160 @@ 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.addons.wikis.searchs.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.core.btries.*; import gplx.xowa.langs.cases.*; +public class Srch_text_parser { + private Btrie_slim_mgr parser_trie = Btrie_slim_mgr.cs(); public Btrie_slim_mgr word_end_trie = Btrie_slim_mgr.cs(); private Btrie_slim_mgr word_bgn_trie = Btrie_slim_mgr.cs(); + private final Btrie_rv trv = new Btrie_rv(); + private Xol_case_mgr case_mgr; + public final Bry_bfr Tmp_bfr = Bry_bfr_.New_w_size(32); + private byte[] src; private int end; + private Srch_sym_parser__split parser__ws; private Srch_sym_parser__dash parser__dash; + public Srch_word_hash word_hash = new Srch_word_hash(); + public boolean lcase = true; + public Srch_text_parser Init_for_ttl(Xol_case_mgr case_mgr) { + this.case_mgr = case_mgr; + parser_trie.Clear(); word_end_trie.Clear(); + parser__ws = new Srch_sym_parser__split(Bool_.Y, " ", "\t", "\n", "\r", "_"); + parser__dash = new Srch_sym_parser__dash("-"); + Parsers__reg(Parsers__make__word_end + ( "!", "?", ",", ":", ";", "\"", "~" + //, "@", "&", "*", "`", "+" // should add for symmetry of word_bgn trie but strips "@Home" to "Home" only; also, several have many "*", "`", "+" + )); + Parsers__reg(new Srch_sym_parser__split(Bool_.N, "/")); + Parsers__reg(parser__ws, parser__dash + , new Srch_sym_parser__paren_bgn(Byte_ascii.Paren_bgn, Byte_ascii.Paren_end) + , new Srch_sym_parser__dot("."), new Srch_sym_parser__ellipsis(Byte_ascii.Dot, ".."), new Srch_sym_parser__apos("'")); // NOTE: [ ] { } do not exist in titles + word_bgn_trie.Add_many_int(1 + // , "!", "?", ",", ":", ";" // low #; should add for symmetry of word_end trie; + , "\"" // adding for symmetry of word_end trie; + , "@", "&", "*", "`", "~", "+" // "@Home", "&c.", "&NSYNC", "Muhammad_ibn_`Ali_at-Tirmidhi"; "Phantom_~Requiem_for_the_Phantom~"; "+pool" + ); + return this; + } + public Srch_text_parser Make_copy() { + Srch_text_parser rv = new Srch_text_parser(); + rv.parser_trie = parser_trie; rv.word_end_trie = word_end_trie; rv.word_bgn_trie = word_bgn_trie; + rv.parser__ws = parser__ws; rv.parser__dash = parser__dash; + rv.word_hash = word_hash; rv.case_mgr = case_mgr; + rv.lcase = lcase; + return rv; + } + public byte[][] Parse_to_bry_ary(boolean lcase, byte[] src_orig) { + word_hash.Clear(); + this.lcase = lcase; + Parse(lcase, src_orig, 0, src_orig.length); + int hash_len = word_hash.Len(); + byte[][] rv = new byte[hash_len][]; + for (int i = 0; i < hash_len; ++i) { + Srch_word_itm itm = (Srch_word_itm)word_hash.Get_at(i); + rv[i] = itm.Word; + } + return rv; + } + public void Parse(Srch_text_parser_wkr wkr, byte[] src_orig) { + word_hash.Clear(); + Parse(Bool_.Y, src_orig, 0, src_orig.length); + int hash_len = word_hash.Len(); + for (int i = 0; i < hash_len; ++i) { + Srch_word_itm itm = (Srch_word_itm)word_hash.Get_at(i); + wkr.Parse_done(itm); + } + } + public void Parse(boolean lcase, byte[] src_orig, int bgn, int end_orig) { + this.src = lcase ? case_mgr.Case_build_lower(src_orig) : src_orig; + this.end = end_orig + (src.length - src_orig.length); + this.cur_bgn = dash_bgn = Srch_text_parser.None; + int pos = bgn; + while (true) { + boolean pos_is_last = pos == end; + if (pos_is_last) { // EOS and pending word; add it + if (cur_bgn != -1) + Words__add__chk_dash(cur_bgn, end); + break; + } + byte b = src[pos]; + Object o = parser_trie.Match_at_w_b0(trv, b, src, pos, end); + if (o == null) { // unknown sequence; word-char + if (cur_bgn == -1) cur_bgn = pos; // set 1st char for word + ++pos; + } + else { + Srch_sym_parser parser = (Srch_sym_parser)o; + pos = parser.Parse(this, src, end, pos, trv.Pos()); + } + } + } + public int Cur__bgn() {return cur_bgn;} private int cur_bgn; public void Cur__bgn__reset() {this.cur_bgn = -1;} + public void Cur__bgn__set(int v) {this.cur_bgn = v;} // called from dash parser + public int Dash__bgn() {return dash_bgn;} private int dash_bgn; public void Dash__bgn_(int v) {this.dash_bgn = v;} + public int Cur__end__find(int pos) {return parser__ws.Find_fwd(src, pos, end);} + public boolean Cur__end__chk(int pos) {return parser__ws.Is_next(src, pos, end);} + public int Cur__end__find__text_only(int pos) { // primarily for parens and finding word_end after ")"; EX: "(city), " vs "(a)b " + while (pos < end) { + byte b = src[pos]; + Object parser_obj = parser_trie.Match_bgn_w_byte(b, src, pos, end); + if (parser_obj == null) // b is text; increment by 1 and continue searching + ++pos; + else // b is some sort of symbol; end word here; EX: "a," should produce "a", not "a," + return pos; + } + return end; + } + public void Words__add_if_pending_and_clear(int word_end) { + if (cur_bgn == -1) return; // exit; no pending words + this.Words__add__chk_dash(cur_bgn, word_end); + cur_bgn = -1; + } + public void Words__add__chk_dash(int word_bgn, int word_end) { + parser__dash.Add_pending_word(this, src, word_end); + byte[] word = Bry_.Mid(src, word_bgn, word_end); + Words__add_direct(word); + } + public void Words__add_direct(int bgn, int end) {Words__add_direct(Bry_.Mid(src, bgn, end));} + public void Words__add_direct(byte[] bry) { + word_hash.Add(bry); + + // remove punctuation at bgn of word; EX: "@Home" -> "Home" + boolean dirty = false; + int pos = 0; int len = bry.length; + while (pos < len) { + byte b = bry[pos]; + if (word_bgn_trie.Match_at_w_b0(trv, b, bry, pos, len) != null) { // b is symbol; + dirty = true; + pos = trv.Pos(); + } + else { + break; + } + } + if (dirty && pos < len) { + byte[] trunc = Bry_.Mid(bry, pos, len); + // if (!word_hash.Has(trunc)) // don't add if it exists; EX: "'tis" + word_hash.Add(trunc); + } + } + + private void Parsers__reg(Srch_sym_parser... parsers_ary) { + int parsers_len = parsers_ary.length; + for (int i = 0; i < parsers_len; ++i) { + Srch_sym_parser parser = parsers_ary[i]; + byte[][] hooks_ary = parser.Hooks_ary(); + int hooks_len = hooks_ary.length; + for (int j = 0; j < hooks_len; ++j) { + byte[] hook = hooks_ary[j]; + parser_trie.Add_obj(hook, parser); + if (parser.Tid() == Srch_sym_parser_.Tid__terminal) + word_end_trie.Add_obj(hook, parser); + } + } + } + private static Srch_sym_parser[] Parsers__make__word_end(String... hooks) { + int rv_len = hooks.length; + Srch_sym_parser[] rv = new Srch_sym_parser[rv_len]; + for (int i = 0; i < rv_len; ++i) + rv[i] = new Srch_sym_parser__terminal(Bry_.new_u8(hooks[i])); + return rv; + } + public static final int None = -1; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_text_parser_tst.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_text_parser_tst.java index a27517de8..7c07f16f5 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_text_parser_tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_text_parser_tst.java @@ -13,3 +13,133 @@ 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.addons.wikis.searchs.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import org.junit.*; import gplx.xowa.langs.cases.*; +public class Srch_text_parser_tst { + private final Srch_text_parser_fxt fxt = new Srch_text_parser_fxt(); + @Before public void init() {fxt.Init();} + @Test public void Word__one() {fxt.Clear().Test__split("abcd" , "abcd");} + @Test public void Word__many() {fxt.Clear().Test__split("abc d ef" , "abc", "d", "ef");} + @Test public void Ws__many() {fxt.Clear().Test__split("a b" , "a", "b");} + @Test public void Ws__bgn() {fxt.Clear().Test__split(" a" , "a");} + @Test public void Ws__end() {fxt.Clear().Test__split("a " , "a");} + @Test public void Under() {fxt.Clear().Test__split("a_b" , "a", "b");} // NOTE: same as space + @Test public void Lowercase() {fxt.Clear().Test__split("A B C" , "a", "b", "c");} + @Test public void Dupe() {fxt.Clear().Test__split("a a a" , fxt.Make_word("a", 3));} + @Test public void Dupe__lowercase() {fxt.Clear().Test__split("a A" , fxt.Make_word("a", 2));} + @Test public void Comma__end() {fxt.Clear().Test__split("a, b" , "a", "b");} // EX: "Henry VI, Part 3"; "Bergen County, New Jersey" + @Test public void Comma__mid() {fxt.Clear().Test__split("a,b" , "a,b");} // EX: "20,000 Leagues Under the Sea" + @Test public void Comma__bgn() {fxt.Clear().Test__split(",a b" , "a", "b");} // EX: skip bad usages; EX: "Little Harbour,Pictou ,Nova Scotia"; "The Hindu Succession Act ,1956" + @Test public void Colon__end() {fxt.Clear().Test__split("a: b" , "a", "b");} + @Test public void Colon__mid() {fxt.Clear().Test__split("a:b" , "a:b");} // EX: "3:10 to Yuma (2007 film)"; "6:02 AM EST"; "24:7 Theatre Festival"; "Library of Congress Classification:Class P -- Language and Literature" + @Test public void Colon__bgn() {fxt.Clear().Test__split(":a b" , "a", "b");} + @Test public void Semic__end() {fxt.Clear().Test__split("a; b" , "a", "b");} + @Test public void Semic__mid() {fxt.Clear().Test__split("a;b" , "a;b");} + @Test public void Semic__bgn() {fxt.Clear().Test__split(";a b" , "a", "b");} + @Test public void Bang__end() {fxt.Clear().Test__split("a! b" , "a", "b");} + @Test public void Bang__mid() {fxt.Clear().Test__split("a!b" , "a!b");} + @Test public void Bang__bgn() {fxt.Clear().Test__split("!a b" , "a", "b");} + @Test public void Question__end() {fxt.Clear().Test__split("a? b" , "a", "b");} + @Test public void Question__mid() {fxt.Clear().Test__split("a?b" , "a?b");} + @Test public void Question__bgn() {fxt.Clear().Test__split("?a b" , "a", "b");} + @Test public void Question__sentence() {fxt.Clear().Test__split("a?" , "a");} + @Test public void Multiple__1() {fxt.Clear().Test__split("a?!" , "a");} + @Test public void Multiple__2() {fxt.Clear().Test__split("a!?" , "a");} + @Test public void Dot__word() {fxt.Clear().Test__split("a.org" , "a.org");} // EX: "en.wikipedia.org"; "Earth.png"; "IEEE_802.15" + @Test public void Dot__abrv() {fxt.Clear().Test__split("a vs. b" , "a", "vs.", "vs", "b");} // EX: "vs.", "no.", "dr.", "st.", "inc." + @Test public void Dot__initials() {fxt.Clear().Test__split("a. b. cde" , "a.", "a", "b.", "b", "cde");} // EX: "H. G. Wells" + @Test public void Dot__acronym() {fxt.Clear().Test__split("abc D.E.F. ghi" , "abc", "d.e.f.", "def", "ghi");} // EX: "History of U.S.A. Science", "G.I. Bill", "Washington, D.C."; "Barcelona F.C."; "The Office (U.S.)"; "Agents of S.H.I.E.L.D."; "Gunfight at the O.K. Corral"; "H.M.S. Pinafore"; "R.E.M. discography" + @Test public void Dot__bgn() {fxt.Clear().Test__split("a .bcd e" , "a", ".bcd", "bcd", "e");} // EX: "Colt .45", "List of organizations with .int domain names" + @Test public void Dot__bgn__end() {fxt.Clear().Test__split("a .b. c" , "a", ".b.", "c");} + @Test public void Dot__ellipsis_like() {fxt.Clear().Test__split("a . . . b" , "a", "b");} // EX: "Did you know . . ." + @Test public void Ellipsis__len_3() {fxt.Clear().Test__split("a... bc d" , "a", "...", "bc", "d");} // EX: "Nights into Dreams..." + @Test public void Ellipsis__len_3__bgn() {fxt.Clear().Test__split("a ...b" , "a", "...", "b"); ;} // NOTE: make sure "dot_bgn" code doesn't break this + @Test public void Ellipsis__len_2() {fxt.Clear().Test__split("a.. b" , "a", "..", "b");} // EX: "3.. 6.. 9 Seconds of Light" + @Test public void Ellipsis__bgn() {fxt.Clear().Test__split("...a" , "...", "a");} + @Test public void Ellipsis__end() {fxt.Clear().Test__split("a..." , "a", "...");} + @Test public void Ellipsis__no_ws() {fxt.Clear().Test__split("a...b" , "a", "...", "b");} + @Test public void Ellipsis__term() {fxt.Clear().Test__split("a...?!" , "a", "...");} // EX: "Wetten, dass..?" + @Test public void Apos__merge__end__eos() {fxt.Clear().Test__split("ab's" , "ab's", "abs");} // EX: "A Midsummer Night's Dream"; "Director's cut" + @Test public void Apos__merge__end__word() {fxt.Clear().Test__split("ab's c" , "ab's", "abs", "c");} // EX: "Director's cut"; "Cap'n Crunch"; + @Test public void Apos__merge__bgn() {fxt.Clear().Test__split("a o'bc" , "a", "o'bc", "obc");} // EX: "Twelve O'Clock High"; "Shaqille O'Neal"; "Banca d'Italia" + @Test public void Apos__merge__mid() {fxt.Clear().Test__split("i'm" , "i'm", "im");} + @Test public void Apos__bgn__long() {fxt.Clear().Test__split("a 'tis b" , "a", "'tis", "tis", "b");} // EX: "My Country, 'Tis of Thee"; "Omaha hold 'em"; "Slash'EM"; "Expo '92" + @Test public void Apos__end__eos() {fxt.Clear().Test__split("a'" , "a");} + @Test public void Apos__end__short() {fxt.Clear().Test__split("a' b" , "a", "b");} // EX: "Will-o'-the-wisp"; "Portuguese man o' war"; + @Test public void Apos__end__long() {fxt.Clear().Test__split("ab' c" , "ab", "c");} // EX: "Dunkin' Donuts"; "'Allo 'Allo!"; "Catherine de' Medici" + @Test public void Apos__both__n() {fxt.Clear().Test__split("a 'n' b" , "a", "'n'", "n", "b");} // EX: "Rock 'n' Roll"; "Town 'n' Country, Florida"; "Hill 'n Dale, Florida"; "Chip 'n Dale Rescue Rangers" + @Test public void Apos__multiple() {fxt.Clear().Test__split("ab''cd" , "ab''cd");} + @Test public void Apos__lone() {fxt.Clear().Test__split("' a" , "'", "a");} // EX: "' (disambiguation)" + @Test public void Dash__one() {fxt.Clear().Test__split("a-b" , "a", "b", "a-b");} // EX: "The Amazing Spider-Man"; "On-super percentage"; "Basic Role-Playing"; "Context-sensitive"; "Cross-country skiing"; "Double-barreled shotgun"; "Dot-com bubble"; "Many-worlds interpretation"; "Faster-than-light"; "Gram-positive bacteria"; "Half-life", "Jean-Paul Sartre"; "Austria-Hungary" + @Test public void Dash__many() {fxt.Clear().Test__split("a-b-c" , "a", "b", "c", "a-b-c");} + @Test public void Dash__ws() {fxt.Clear().Test__split("a - b" , "a", "-", "b");} + @Test public void Dash__eos() {fxt.Clear().Test__split("a-" , "a", "a-");} + @Test public void Dash__bos() {fxt.Clear().Test__split("-a" , "a", "-a");} + @Test public void Dash__mult__2() {fxt.Clear().Test__split("--" , "--");} + @Test public void Dash__mult__3() {fxt.Clear().Test__split("---" , "---");} + @Test public void Dash__mult__2__words() {fxt.Clear().Test__split("a--b" , "a", "b", "a--b");} + @Test public void Dash__w_comma() {fxt.Clear().Test__split("a-, b" , "a", "a-", "b");} + @Test public void Slash__one() {fxt.Clear().Test__split("a/b" , "a", "b");} // EX: "Good cop/bad cop"; "Snooker world rankings 2004/2005"; "Debian GNU/Hurd"; "HIV/AIDS in the United States"; "List of minor planets/1�100" + @Test public void Slash__many() {fxt.Clear().Test__split("a/b/c" , "a", "b", "c");} // EX: "Age/sex/location"; + @Test public void Slash__ws() {fxt.Clear().Test__split("a / b" , "a", "b");} + @Test public void Dash__slash() {fxt.Clear().Test__split("a-b/c-d-e/f-g" , "a", "b", "a-b", "c", "d", "e", "c-d-e", "f", "g", "f-g");} + @Test public void Paren__both__one() {fxt.Clear().Test__split("a (b) c" , "a", "b", "c");} // EX: "A (letter)" + @Test public void Paren__both__many() {fxt.Clear().Test__split("a (b c) d" , "a", "b", "c", "d");} // EX: "A (2016 film)" + @Test public void Paren__bgn__multiple() {fxt.Clear().Test__split("a (((b)))" , "a", "b");} + @Test public void Paren__unmatched() {fxt.Clear().Test__split("a(b" , "a(b");} + @Test public void Paren__unmatched__bgn() {fxt.Clear().Test__split("a (b" , "a", "b");} + @Test public void Paren__mid() {fxt.Clear().Test__split("a(b)c" , "a(b)c");} // EX: "Chloro(pyridine)cobaloxime"; "Exi(s)t" + @Test public void Paren__end() {fxt.Clear().Test__split("a(b)" , "a(b)", "a");} // EX: "Come What(ever) May"; "501(c) organization"; "Reindeer(s) Are Better Than People"; "(Miss)understood"; "Chromium(III) picolinate" + @Test public void Paren__bgn() {fxt.Clear().Test__split("(a)b" , "(a)b", "b");} // EX: "International Student Congress of (bio)Medical Sciences" + @Test public void Paren__end__dash() {fxt.Clear().Test__split("a(b-c) d" , "a(b-c)", "a", "d");} // EX: "Bis(2-ethylhexyl) phthalate" + @Test public void Paren__end__comma() {fxt.Clear().Test__split("a(b,c) d" , "a(b,c)", "a", "d");} // EX: "Iron(II,III) oxide" + @Test public void Paren__comma() {fxt.Clear().Test__split("a (b), c" , "a", "b", "c");} // EX: "Corning (city), New York" + @Test public void Paren__multiple() {fxt.Clear().Test__split("(a) (b)" , "a", "b");} + @Test public void Quote__both() {fxt.Clear().Test__split("a \"b\" c" , "a", "b", "c");} + @Test public void Word_bgn__at() {fxt.Clear().Test__split("@a" , "@a", "a");} + @Test public void Word_bgn__tilde() {fxt.Clear().Test__split("~a~" , "a");} // EX: "Phantom ~Requiem for the Phantom~" +} +class Srch_text_parser_fxt { + private final Srch_text_parser word_parser = new Srch_text_parser(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New_w_size(32); + private Xol_case_mgr case_mgr; + public void Init() { + case_mgr = Xol_case_mgr_.A7(); + word_parser.Init_for_ttl(case_mgr); + } + public Srch_text_parser_fxt Clear() { + word_parser.word_hash.Clear(); + return this; + } + public Srch_word_itm Make_word(String raw, int count) {return new Srch_word_itm(Bry_.new_u8(raw)).Count_(count);} + public void Test__split(String src, String... expd_words) { + int len = expd_words.length; + Srch_word_itm[] ary = new Srch_word_itm[len]; + for (int i = 0; i < len; ++i) { + ary[i] = Make_word(expd_words[i], 1); + } + Test__split(src, ary); + } + public void Test__split(String src, Srch_word_itm... expd_words) { + byte[] src_bry = Bry_.new_u8(src); + word_parser.Parse(Bool_.Y, src_bry, 0, src_bry.length); + Tfds.Eq_str_lines(To_str(expd_words), To_str(word_parser.word_hash)); + } + private String To_str(Srch_word_itm[] word_ary) { + int len = word_ary.length; + for (int i = 0; i < len; ++i) { + if (i != 0) tmp_bfr.Add_byte_nl(); + Srch_word_itm word = word_ary[i]; + tmp_bfr.Add(word.Word).Add_byte_pipe(); + tmp_bfr.Add_int_variable(word.Count()); + } + return tmp_bfr.To_str_and_clear(); + } + private String To_str(Srch_word_hash word_mgr) { + int len = word_mgr.Len(); + Srch_word_itm[] ary = new Srch_word_itm[len]; + for (int i = 0; i < len; ++i) + ary[i] = word_mgr.Get_at(i); + return To_str(ary); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_text_parser_wkr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_text_parser_wkr.java index a27517de8..4c420adf5 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_text_parser_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_text_parser_wkr.java @@ -13,3 +13,11 @@ 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.addons.wikis.searchs.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +public interface Srch_text_parser_wkr { + void Parse_done(Srch_word_itm word); +} +class Srch_text_parser_wkr__noop implements Srch_text_parser_wkr { + public void Parse_done(Srch_word_itm word) {} + public static final Srch_text_parser_wkr__noop Instance = new Srch_text_parser_wkr__noop(); Srch_text_parser_wkr__noop() {} // TS.static +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_word_hash.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_word_hash.java index a27517de8..df64408ec 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_word_hash.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_word_hash.java @@ -13,3 +13,19 @@ 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.addons.wikis.searchs.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +public class Srch_word_hash { + private final Ordered_hash hash = Ordered_hash_.New_bry(); + public void Clear() {hash.Clear();} + public int Len() {return hash.Count();} + public boolean Has(byte[] word) {return hash.Has(word);} + public Srch_word_itm Get_at(int i) {return (Srch_word_itm)hash.Get_at(i);} + public void Add(byte[] word) { + Srch_word_itm itm = (Srch_word_itm)hash.Get_by(word); + if (itm == null) { + itm = new Srch_word_itm(word); + hash.Add(word, itm); + } + itm.Count_add_1_(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_word_itm.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_word_itm.java index a27517de8..5ee575be7 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_word_itm.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/parsers/Srch_word_itm.java @@ -13,3 +13,14 @@ 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.addons.wikis.searchs.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +public class Srch_word_itm { + public Srch_word_itm(byte[] word) { + this.Word = word; + this.count = 0; + } + public final byte[] Word; + public int Count() {return count;} private int count; + public void Count_add_1_() {++count;} + @gplx.Internal protected Srch_word_itm Count_(int v) {this.count = v; return this;} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_ns_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_ns_mgr.java index a27517de8..d0a70a523 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_ns_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_ns_mgr.java @@ -13,3 +13,85 @@ 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.addons.wikis.searchs.searchers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.core.primitives.*; +import gplx.xowa.wikis.nss.*; +public class Srch_ns_mgr { + private final Ordered_hash ns_hash = Ordered_hash_.New(); private final Int_obj_ref tmp_ns_id = Int_obj_ref.New_neg1(); + private final Bry_bfr tmp_bfr = Bry_bfr_.Reset(32); + private boolean ns_all, ns_main; + public void Clear() { + ns_hash.Clear(); + ns_all = ns_main = false; + } + public boolean Ns_main_only() {return ns_main && !ns_all;} // Ns_main_only is used by Searcher to only search main srch_link db + public boolean Has(int ns_id) { + return ns_all // ns_all always returns true + || ns_main && ns_id == Xow_ns_.Tid__main // ns_main returns true if main_ns + || ns_hash.Has(tmp_ns_id.Val_(ns_id)); // ns_hash returns true if has ns_id + } + public void Add_all() {ns_all = true;} + public Srch_ns_mgr Add_main_if_empty() {if (ns_hash.Count() == 0) ns_main = true; return this;} + public void Add_by_id(int ns_id) { + if (ns_hash.Has(tmp_ns_id.Val_(ns_id))) ns_hash.Del(tmp_ns_id); + ns_hash.Add_as_key_and_val(Int_obj_ref.New(ns_id)); + } + public void Add_by_name(byte[] ns_name) { + int id = Xow_ns_canonical_.To_id(ns_name); + if (id != Xow_ns_.Tid__null) + Add_by_id(id); + } + public void Add_by_parse(byte[] key, byte[] val) { + int ns_enabled = Bry_.To_int_or_neg1(val); + if (ns_enabled == 1) { // make sure set to 1; EX: ignore &ns0=0 + int key_len = key.length; + if (key_len == 3 && key[2] == Srch_search_addon.Wildcard__star) // key=ns* sets ns_all to true + ns_all = true; + else { + int ns_id = Bry_.To_int_or(key, 2, key_len, Int_.Min_value); + if (ns_id != Int_.Min_value) { // ignore invalid ints; EX: &nsabc=1; + Add_by_id(ns_id); + ns_main = ns_all = false; + } + } + } + } + public byte[] To_hash_key() { + if (ns_all) return Hash_key_all; + else if (ns_main) return Hash_key_main; + else { + int ns_hash_len = ns_hash.Count(); + for (int i = 0; i < ns_hash_len; i++) { + if (i != 0) tmp_bfr.Add_byte_semic(); + Int_obj_ref ns_id_ref = (Int_obj_ref)ns_hash.Get_at(i); + tmp_bfr.Add_int_variable(ns_id_ref.Val()); + } + return tmp_bfr.To_bry_and_clear(); + } + } + public void Add_by_int_ids(int[] ns_ids) { + this.Clear(); + if (ns_ids.length == 0) { + this.Add_all(); + } else if (ns_ids.length == 1 && ns_ids[0] == Xow_ns_.Tid__main) { + this.Add_main_if_empty(); + } else { + for (int ns_id : ns_ids) + this.Add_by_id(ns_id); + } + } + public int[] To_int_ary() { + if (ns_all) return Int_ary_.Empty; + else if (ns_main) return Int_ary_.New(Xow_ns_.Tid__main); + else { + int len = ns_hash.Count(); + int[] rv = new int[len]; + for (int i = 0; i < len; i++) { + Int_obj_ref ns_id_ref = (Int_obj_ref)ns_hash.Get_at(i); + rv[i] = ns_id_ref.Val(); + } + return rv; + } + } + private static final byte[] Hash_key_all = new byte[] {Srch_search_addon.Wildcard__star}, Hash_key_main = new byte[] {Byte_ascii.Num_0}; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_cmd.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_cmd.java index a27517de8..a392e89b4 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_cmd.java @@ -13,3 +13,41 @@ 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.addons.wikis.searchs.searchers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.xowa.addons.wikis.searchs.searchers.rslts.*; +import gplx.xowa.addons.wikis.searchs.searchers.crts.*; +public class Srch_search_cmd implements Cancelable, Gfo_invk { + private final Srch_search_mgr search_mgr; + public final Srch_search_qry qry; + public final Srch_crt_mgr crt_mgr; + private final Srch_rslt_cbk rslt_cbk; + private final Srch_rslt_list rslts_list; + public Srch_search_cmd(Srch_search_mgr search_mgr, Srch_search_qry qry, Srch_crt_mgr crt_mgr, Srch_rslt_cbk rslt_cbk, Srch_rslt_list rslts_list) { + this.search_mgr = search_mgr; this.qry = qry; this.crt_mgr = crt_mgr; this.rslt_cbk = rslt_cbk; this.rslts_list = rslts_list; + } + public boolean Canceled() {return canceled;} private boolean canceled; + public void Cancel() { + canceled = true; + rslt_cbk.On_cancel(); + } + public void Search() { + try { + search_mgr.Search_async(this, qry, crt_mgr, rslt_cbk, rslts_list); // NOTE: must handle any errors in async mode + } + catch(Exception e) { + Xoa_app_.Usr_dlg().Prog_many("", "", "error during search: err=~{0}", Err_.Message_gplx_log(e)); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__search)) Search(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk__search = "search"; + public static Srch_search_cmd Noop() { + if (noop == null) { + noop = new Srch_search_cmd(null, null, null, Srch_rslt_cbk_.Noop, null); + } + return noop; + } private static Srch_search_cmd noop; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_ctx.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_ctx.java index a27517de8..7dc3908f8 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_ctx.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_ctx.java @@ -13,3 +13,55 @@ 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.addons.wikis.searchs.searchers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.xowa.langs.cases.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.addons.wikis.searchs.dbs.*; +import gplx.xowa.addons.wikis.searchs.searchers.crts.*; import gplx.xowa.addons.wikis.searchs.searchers.rslts.*; import gplx.dbs.percentiles.*; import gplx.xowa.addons.wikis.searchs.searchers.wkrs.*; +import gplx.xowa.addons.wikis.searchs.parsers.*; +public class Srch_search_ctx { + public Srch_search_ctx(Cancelable cxl, Xow_wiki wiki, Srch_search_addon addon + , Srch_rslt_list cache__page, Hash_adp_bry cache__word_counts + , Srch_search_qry qry, Srch_crt_scanner_syms scanner_syms, Srch_crt_mgr crt_mgr, Srch_rslt_list rslts_list) { + this.Cxl = cxl; + this.Wiki = wiki; + this.Wiki_domain = wiki.Domain_bry(); + this.Case_mgr = wiki.Case_mgr(); + this.Addon = addon; + this.Cache__page = cache__page; + this.Cache__word_counts = cache__word_counts; + this.Qry = qry; + this.Scanner_syms = scanner_syms; + this.Crt_mgr = crt_mgr; + this.Crt_mgr__root = crt_mgr.Root; + this.Rslts_list = rslts_list; + this.Db__core = wiki.Data__core_mgr().Db__core(); + this.Tbl__page = Db__core.Tbl__page(); + this.Tbl__word = addon.Db_mgr().Tbl__word(); + this.Tbl__link__ary = addon.Db_mgr().Tbl__link__ary(); + long page_count = wiki.Stats().Num_pages(); + this.Score_rng.Init(page_count, addon.Db_mgr().Cfg().Link_score_max()); + int rslts_needed = qry.Slab_end - rslts_list.Len(); + if (rslts_needed < 0) rslts_needed = 0; + this.Rslts_needed = rslts_needed; + this.Highlight_mgr = new Srch_highlight_mgr(this.Case_mgr).Search_(qry.Phrase.Orig); + } + public final Cancelable Cxl; + public final Xow_wiki Wiki; + public final byte[] Wiki_domain; + public final Srch_search_addon Addon; + public final Xol_case_mgr Case_mgr; + public final Srch_rslt_list Cache__page; + public final Hash_adp_bry Cache__word_counts; + public final Xow_db_file Db__core; + public final Xowd_page_tbl Tbl__page; + public final Srch_word_tbl Tbl__word; + public final Srch_link_tbl[] Tbl__link__ary; + public final Srch_search_qry Qry; + public final Srch_crt_scanner_syms Scanner_syms; + public final Srch_rslt_list Rslts_list; + public final int Rslts_needed; + public final Percentile_rng Score_rng = new Percentile_rng(); + public final Srch_crt_mgr Crt_mgr; + public final Srch_crt_itm Crt_mgr__root; + public final Srch_highlight_mgr Highlight_mgr; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_mgr.java index a27517de8..f49aae814 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_mgr.java @@ -13,3 +13,88 @@ 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.addons.wikis.searchs.searchers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.xowa.addons.wikis.searchs.dbs.*; +import gplx.xowa.addons.wikis.searchs.searchers.rslts.*; import gplx.xowa.addons.wikis.searchs.searchers.wkrs.*; import gplx.xowa.addons.wikis.searchs.parsers.*; import gplx.xowa.addons.wikis.searchs.searchers.crts.*; +import gplx.xowa.addons.wikis.searchs.searchers.crts.visitors.*; +import gplx.core.net.*; import gplx.core.net.qargs.*; +public class Srch_search_mgr implements Gfo_invk { + private final Srch_search_addon addon; + private final Xow_wiki wiki; + private final Srch_rslt_list cache__page = new Srch_rslt_list(); + private final Hash_adp_bry cache__word_counts = Hash_adp_bry.cs(); + private final Srch_rslt_cache cache__rslts = new Srch_rslt_cache(); + private final Srch_page_tbl_wkr page_tbl_searcher = new Srch_page_tbl_wkr(); + private final Srch_crt_parser crt_parser; + private final Srch_search_cmd[] cur_cmds; + private final Object mutex = new Object(); + private int search_count; + private boolean upgrade_prompted; + public Srch_search_mgr(Srch_search_addon addon, Xow_wiki wiki, Srch_text_parser parser) { + this.addon = addon; this.wiki = wiki; + crt_parser = new Srch_crt_parser(Srch_crt_scanner_syms.Dflt); // NOTE: hard-coded to dflt; should change to use qry.Phrase.Syms, but requires more work + + // init cur_cmds with Noop cmd to make cancel logic below easier + int len = Srch_search_qry.Tid_len; + this.cur_cmds = new Srch_search_cmd[Srch_search_qry.Tid_len]; + for (int i = 0; i < len; ++i) + cur_cmds[i] = Srch_search_cmd.Noop(); + + wiki.App().Cfg().Bind_many_wiki(this, wiki, Cfg__args_default); + } + public void Clear_rslts_cache() {cache__rslts.Clear();} + public void Search_cancel() { + cur_cmds[Srch_search_qry.Tid__suggest_box].Cancel(); + } + public void Search(Srch_search_qry qry, Srch_rslt_cbk cbk) { // NOTE: main entry point for search + if (qry.Phrase.Orig.length == 0) return; + + // handle obsolete search dbs; + if (addon.Db_mgr().Cfg().Version_id__needs_upgrade() + && !upgrade_prompted) { + upgrade_prompted = true; + Srch_db_upgrade upgrade_mgr = new Srch_db_upgrade(wiki, addon.Db_mgr()); + upgrade_mgr.Upgrade(); + return; + } + + // cancel existing cmd + Srch_search_cmd cur_cmd = cur_cmds[qry.Tid]; + cur_cmd.Cancel(); + + // create new one; run it; + Srch_crt_mgr crt_mgr = crt_parser.Parse_or_invalid(qry.Phrase.Compiled); + if (crt_mgr == Srch_crt_mgr.Invalid) return; // handle "\\" which is invalid or other fatal errors + Srch_rslt_list rslts_list = cache__rslts.Get_or_new(crt_mgr.Key); + cur_cmd = new Srch_search_cmd(this, qry, crt_mgr, cbk, rslts_list); + cur_cmds[qry.Tid] = cur_cmd; + if (wiki.App().Mode().Tid_is_http()) // FUTURE: use api async flag instead; WHEN: long polling support + cur_cmd.Search(); + else + gplx.core.threads.Thread_adp_.Start_by_key(gplx.xowa.apps.Xoa_thread_.Key_special_suggest, cur_cmd, Srch_search_cmd.Invk__search); + } + public void Search_async(Cancelable cxl, Srch_search_qry qry, Srch_crt_mgr crt_mgr, Srch_rslt_cbk rslt_cbk, Srch_rslt_list rslts_list) { + synchronized (mutex) { // force only one search at a time; do not (a) place around Thread_sleep; (b) reuse for any other locks + if (++search_count > 64) this.Clear(); // lazy way of clearing memory + Srch_search_ctx ctx = new Srch_search_ctx(cxl, wiki, addon, cache__page, cache__word_counts, qry, qry.Phrase.Syms, crt_mgr, rslts_list); + ctx.Score_rng.Select_init(ctx.Rslts_needed, rslts_list.Score_bgn, rslts_list.Score_len, Srch_link_wkr.Percentile_rng__calc_adj(crt_mgr.Words_nth__len())); + page_tbl_searcher.Search(ctx, rslt_cbk); + if (cxl.Canceled()) return; + Srch_link_wkr link_wkr = new Srch_link_wkr(); + link_wkr.Search(rslts_list, rslt_cbk, ctx); + } + } + private void Clear() { + Gfo_usr_dlg_.Instance.Log_many("", "", "search.clear"); + search_count = 0; + cache__page.Clear(); + cache__word_counts.Clear(); + cache__rslts.Clear(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Cfg__args_default)) this.Clear_rslts_cache(); // NOTE: must clear cache after args_dflt changed + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Cfg__args_default = "xowa.addon.search.special.args_default"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_phrase.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_phrase.java index a27517de8..3185009ed 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_phrase.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_phrase.java @@ -13,3 +13,122 @@ 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.addons.wikis.searchs.searchers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.core.btries.*; +import gplx.xowa.addons.wikis.searchs.searchers.crts.*; +public class Srch_search_phrase { + public Srch_search_phrase(boolean wildcard, byte[] orig, byte[] compiled, Srch_crt_scanner_syms syms) { + this.Orig = orig; + this.Compiled = compiled; + this.Wildcard = wildcard; + this.Syms = syms; + } + public final boolean Wildcard; + public final byte[] Orig; // EX: "Earth" + public final byte[] Compiled; // EX: "earth*" + public final Srch_crt_scanner_syms Syms; + + public static Srch_search_phrase New(gplx.xowa.langs.cases.Xol_case_mgr case_mgr, Srch_crt_scanner_syms syms, boolean auto_wildcard, byte[] orig) { + int orig_len = orig.length; + if ( orig_len > 0 // if "*" at end, remove and change to wildcard; needed for Special:Search which will send in "earth*" but "earth" needed for highlighting + && orig[orig_len - 1] == syms.Wild()) { + orig = Bry_.Mid(orig, 0, orig_len - 1); + auto_wildcard = true; + } + byte[] lcase = case_mgr.Case_build_lower(orig); + lcase = Auto_wildcard(lcase, auto_wildcard, syms); + return new Srch_search_phrase(auto_wildcard, orig, lcase, syms); + } + public static byte[] Auto_wildcard(byte[] raw, boolean auto_wildcard, Srch_crt_scanner_syms syms) { + Btrie_slim_mgr trie = syms.Trie(); + int raw_len = raw.length; + int insert_pos = -1; + int fail_pos = -1; + for (int i = raw_len - 1; i > -1; --i) { + byte b = raw[i]; + byte tid = trie.Match_byte_or(b, raw, i, i + 1, Byte_.Max_value_127); + if (tid == Byte_.Max_value_127) { // unknown sym + if (b == syms.Wild()) { // wildcard is not tokenized + fail_pos = i; + break; + } + else { // alphanum-char + insert_pos = i; + break; + } + } + else { + switch (tid) { + case Srch_crt_tkn.Tid__quote: + case Srch_crt_tkn.Tid__space: + case Srch_crt_tkn.Tid__not: + case Srch_crt_tkn.Tid__and: + case Srch_crt_tkn.Tid__or: + case Srch_crt_tkn.Tid__paren_bgn: + fail_pos = i; // these symbols will not auto-wildcard, unless they are escaped + i = -1; + break; + case Srch_crt_tkn.Tid__escape: + if (i > 0) { + int prv_pos = i -1; + if (raw[prv_pos] == syms.Escape()) { // an escaped escape can be wildcarded; EX: "\\" -> "\\*" + insert_pos = i; + i = -1; + } + else + fail_pos = i; + } + else + fail_pos = i; + i = -1; + break; + case Srch_crt_tkn.Tid__paren_end: + break; + } + } + } + + // check if preceded by escape + if (insert_pos == -1) { + if ( fail_pos > 0 + && raw[fail_pos - 1] == syms.Escape()) { + insert_pos = fail_pos; + } + else + return raw; + } + + // check if word already has wildcard; EX: "a*b" x> "a*b*" + for (int i = insert_pos - 1; i > -1; --i) { + byte b = raw[i]; + if (b == syms.Wild()) { + int prv_pos = i - 1; + if (prv_pos > -1) { + if (raw[prv_pos] == syms.Escape()) { // ignore escaped wildcard + i = prv_pos; + continue; + } + } + return raw; // existing wildcard cancels auto-wildcard + } + else if (b == syms.Space()) { // stop looking when word ends + break; + } + else {} // alphanum; keep going + } + + // add wildcard + if (insert_pos == raw_len - 1) + return auto_wildcard ? Bry_.Add(raw, syms.Wild()) : raw; + else { + byte[] rv = new byte[raw_len + 1]; + int wildcard_pos = insert_pos + 1; + for (int i = 0; i < wildcard_pos; ++i) + rv[i] = raw[i]; + rv[wildcard_pos] = syms.Wild(); + for (int i = wildcard_pos; i < raw_len; ++i) + rv[i + 1] = raw[i]; + return rv; + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_phrase_tst.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_phrase_tst.java index a27517de8..064ccb09f 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_phrase_tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_phrase_tst.java @@ -13,3 +13,34 @@ 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.addons.wikis.searchs.searchers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import org.junit.*; import gplx.xowa.addons.wikis.searchs.parsers.*; import gplx.xowa.addons.wikis.searchs.searchers.crts.*; +public class Srch_search_phrase_tst { + private final Srch_search_phrase_fxt fxt = new Srch_search_phrase_fxt(); + @Test public void Word() {fxt.Test__auto_wildcard("a" , "a*");} + @Test public void Paren_end() {fxt.Test__auto_wildcard("(a)" , "(a*)");} + @Test public void Quoted() {fxt.Test__auto_wildcard("\"a\"" , "\"a\"");} + @Test public void Space() {fxt.Test__auto_wildcard(" " , " ");} + @Test public void Not() {fxt.Test__auto_wildcard("-" , "-");} + @Test public void And() {fxt.Test__auto_wildcard("+" , "+");} + @Test public void Or() {fxt.Test__auto_wildcard("," , ",");} + @Test public void Paren_bgn() {fxt.Test__auto_wildcard("(" , "(");} + @Test public void Star() {fxt.Test__auto_wildcard("*" , "*");} + @Test public void Wildcard__exists__y() {fxt.Test__auto_wildcard("a*b" , "a*b");} + @Test public void Wildcard__exists__escaped() {fxt.Test__auto_wildcard("a\\*b" , "a\\*b*");} + @Test public void Wildcard__exists__n() {fxt.Test__auto_wildcard("a* bc" , "a* bc*");} + @Test public void Escape() {fxt.Test__auto_wildcard("\\*" , "\\**");} + @Test public void Escape__incomplete() {fxt.Test__auto_wildcard("a\\" , "a\\");} + @Test public void Escape__escaped() {fxt.Test__auto_wildcard("a\\\\" , "a\\\\*");} + @Test public void Auto_wildcard_n() {fxt.Init__auto_wildcard_n_().Test__auto_wildcard("a", "a");} +} +class Srch_search_phrase_fxt { + private final Srch_crt_scanner_syms syms = Srch_crt_scanner_syms.Dflt; + private boolean auto_wildcard = true; + public Srch_search_phrase_fxt Init__auto_wildcard_n_() {this.auto_wildcard = false; return this;} + public void Test__auto_wildcard(String src_str, String expd) { + byte[] src_raw = Bry_.new_u8(src_str); + byte[] actl = Srch_search_phrase.Auto_wildcard(src_raw, auto_wildcard, syms); + Tfds.Eq(expd, String_.new_u8(actl)); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_qry.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_qry.java index a27517de8..2186749c4 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_qry.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/Srch_search_qry.java @@ -13,3 +13,37 @@ 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.addons.wikis.searchs.searchers; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.xowa.wikis.domains.*; +import gplx.xowa.addons.wikis.searchs.gui.htmlbars.*; +import gplx.xowa.addons.wikis.searchs.searchers.crts.*; +public class Srch_search_qry { + public Srch_search_qry(byte tid, Srch_ns_mgr ns_mgr, Srch_search_phrase phrase, int slab_bgn, int slab_end) { + this.Tid = tid; + this.Ns_mgr = ns_mgr; + this.Phrase = phrase; + this.Slab_bgn = slab_bgn; + this.Slab_end = slab_end; + } + public final byte Tid; + public final Srch_ns_mgr Ns_mgr; + public final Srch_search_phrase Phrase; + public final int Slab_bgn; // EX: 0 + public final int Slab_end; // EX: 20 + + public static final byte Tid_len = 4, Tid__url_bar = 0, Tid__suggest_box = 1, Tid__search_page = 2, Tid__android = 3; + public static Srch_search_qry New__url_bar(Xow_wiki wiki, Srch_ns_mgr ns_mgr, Srch_crt_scanner_syms syms, boolean auto_wildcard, int max_results, byte[] search_orig) { + return new Srch_search_qry(Tid__url_bar, ns_mgr, Srch_search_phrase.New(wiki.Case_mgr(), syms, auto_wildcard, search_orig), 0, max_results); + } + public static Srch_search_qry New__suggest_box(Xow_wiki wiki, Srch_ns_mgr ns_mgr, boolean auto_wildcard, int results_max, byte[] search_orig) { + return new Srch_search_qry(Tid__suggest_box, ns_mgr, Srch_search_phrase.New(wiki.Case_mgr(), Srch_crt_scanner_syms.Dflt, auto_wildcard, search_orig), 0, results_max); + } + public static Srch_search_qry New__search_page(Xow_domain_itm[] domains, Xow_wiki wiki, Srch_ns_mgr ns_mgr, boolean auto_wildcard, byte[] search_orig, int slab_idx, int slab_len) { + int slab_bgn = slab_idx * slab_len; + int slab_end = slab_bgn + slab_len; + return new Srch_search_qry(Tid__search_page, ns_mgr, Srch_search_phrase.New(wiki.Case_mgr(), Srch_crt_scanner_syms.Dflt, auto_wildcard, search_orig), slab_bgn, slab_end); + } + public static Srch_search_qry New__drd(Xow_wiki wiki, Srch_ns_mgr ns_mgr, byte[] search_orig, int slab_bgn, int slab_end) { + return new Srch_search_qry(Tid__android, ns_mgr, Srch_search_phrase.New(wiki.Case_mgr(), Srch_crt_scanner_syms.Dflt, Bool_.Y, search_orig), slab_bgn, slab_end); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/cbks/Srch_rslt_cbk__suggest_box.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/cbks/Srch_rslt_cbk__suggest_box.java index a27517de8..484dd39b9 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/cbks/Srch_rslt_cbk__suggest_box.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/cbks/Srch_rslt_cbk__suggest_box.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.addons.wikis.searchs.searchers.cbks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import gplx.core.js.*; +import gplx.xowa.addons.wikis.searchs.searchers.*; import gplx.xowa.addons.wikis.searchs.searchers.rslts.*; +public class Srch_rslt_cbk__suggest_box implements Srch_rslt_cbk, Gfo_invk { + private final Js_wtr js_wtr = new Js_wtr(); + private final Xoae_app app; + private final byte[] cbk_func; + private final byte[] search_raw; + public Srch_rslt_cbk__suggest_box(Xoae_app app, byte[] cbk_func, byte[] search_raw) { + this.app = app; this.cbk_func = cbk_func; + this.search_raw = search_raw; + } + public void On_cancel() {} + public void On_rslts_found(Srch_search_qry qry, Srch_rslt_list rslts_list, int rslts_bgn, int rslts_end) { + if (!rslts_list.Rslts_are_enough && !rslts_list.Rslts_are_done) return; + js_wtr.Func_init(cbk_func); + js_wtr.Prm_bry(search_raw); + js_wtr.Prm_spr(); + js_wtr.Ary_init(); + int rslts_len = rslts_list.Len(); + for (int i = 0; i < qry.Slab_end; i++) { + if (i >= rslts_len) break; // rslts_end will overshoot actual rslts_len; check for out of bounds and exit; EX: default suggest will have rslts_end of 25, but "earth time" will retrieve 15 results + Srch_rslt_row row = rslts_list.Get_at(i); + js_wtr.Ary_bry(row.Page_ttl.Full_txt_w_ttl_case()); + js_wtr.Ary_bry(row.Page_ttl_display(Bool_.Y)); + } + js_wtr.Ary_term(); + js_wtr.Func_term(); + Gfo_invk_.Invk_by_key(app.Gui_mgr().Kit().New_cmd_sync(this), Srch_rslt_cbk__suggest_box.Invk__notify); + } + private void Notify() { + app.Gui_mgr().Browser_win().Active_html_box().Html_js_eval_script(js_wtr.To_str_and_clear()); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__notify)) Notify(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk__notify = "notify"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/cbks/Srch_rslt_cbk__url_bar.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/cbks/Srch_rslt_cbk__url_bar.java index a27517de8..462ce6269 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/cbks/Srch_rslt_cbk__url_bar.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/cbks/Srch_rslt_cbk__url_bar.java @@ -13,3 +13,71 @@ 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.addons.wikis.searchs.searchers.cbks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import gplx.gfui.*; import gplx.gfui.controls.standards.*; +import gplx.xowa.addons.wikis.searchs.searchers.rslts.*; +import gplx.xowa.apps.apis.xowa.addons.searchs.*; +public class Srch_rslt_cbk__url_bar implements Srch_rslt_cbk, Gfo_invk { + private final Xoae_app app; + private final GfuiComboBox url_bar; + private String[] cbo_ary; + private boolean rslts_finished; + private int rslts_in_this_pass; + private boolean rslts_shown = false; + private int max_results; + public Srch_rslt_cbk__url_bar(Xoae_app app, GfuiComboBox url_bar, int max_results) { + this.app = app; this.url_bar = url_bar; this.max_results = max_results; + } + public void On_cancel() {} + public void On_rslts_found(Srch_search_qry qry, Srch_rslt_list rslts_list, int rslts_bgn, int rslts_end) { + int rslts_len = rslts_list.Len(); + this.rslts_finished = rslts_list.Rslts_are_enough || rslts_list.Rslts_are_done; + + // get # of items for drop-down; note special logic to reduce blinking + rslts_in_this_pass = rslts_end - rslts_bgn; + if ( rslts_in_this_pass == 0 // no new results; + && rslts_bgn != 0 // if first one, still update; blanks out results from previous try; + && !rslts_finished) // if rslts_finished, still update to force cbo to "shrink" + return; // exit now else will "blink" when refreshing; + int cbo_len = max_results; // force cbo_len to be max_rslts; reduces "blinking" when typing by keeping visible area to same size + if (rslts_list.Rslts_are_done) { // "shrink" cbo_len to rslts_len; EX: 10 wanted; 2 returned; shrink to 2 rows; + cbo_len = rslts_len; + } + + // fill cbo_ary with rslts from search, while "blanking" out rest + this.cbo_ary = new String[cbo_len]; + for (int i = 0; i < cbo_len; ++i) { + String cbo_itm = ""; + if (i >= max_results) break; + if (i < rslts_len) { + Srch_rslt_row rslt = rslts_list.Get_at(i); + cbo_itm = String_.new_u8(rslt.Page_ttl_display(Bool_.N)); + } + cbo_ary[i] = cbo_itm; + } + + Gfo_invk_.Invk_by_key(app.Gui_mgr().Kit().New_cmd_sync(this), Srch_rslt_cbk__url_bar.Invk__items__update); // NOTE: needs to be sync, b/c page_wkr and link_wkr must execute in order; EX:"Portal:Science" does not show; DATE:2016-03-24 + } + private void Items__update() { + url_bar.Items__update(cbo_ary); + if (!url_bar.List_visible() // rslt_list not visible + && !rslts_shown // auto-dropdown hasn't happened yet + && (rslts_in_this_pass > 0 || rslts_finished) // at least 1 rslt, or search done + ) { + rslts_shown = true; // only auto-show dropdown once; allows user to close drop-down and not have it continually flashing + url_bar.List_visible_(Bool_.Y); + } + Xoa_app_.Usr_dlg().Prog_none("", "", ""); + if (rslts_finished) { + if (cbo_ary.length == 0) + url_bar.List_visible_(Bool_.N); + else + url_bar.Items__size_to_fit(cbo_ary.length); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__items__update)) Items__update(); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk__items__update = "items__update"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_itm.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_itm.java index a27517de8..37e7d597c 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_itm.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_itm.java @@ -13,3 +13,34 @@ 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.addons.wikis.searchs.searchers.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import gplx.langs.regxs.*; +public class Srch_crt_itm { + public Srch_crt_itm(int idx, int tid, Srch_crt_itm[] subs, byte[] raw, Srch_crt_sql raw_data) { + this.Idx = idx; this.Tid = tid; this.Subs = subs; + this.Raw = raw; + this.Sql_data = raw_data; + } + public final int Idx; // itm index; EX: "a b" -> a:0 b:1 + public final int Tid; + public final byte[] Raw; + public final Srch_crt_itm[] Subs; + public final Srch_crt_sql Sql_data; + public void Accept_visitor(Srch_crt_visitor visitor) {visitor.Visit(this);} + + public static final int + Tid__word = 0 // EX: 'A' + , Tid__and = 1 // EX: 'A B' + , Tid__or = 2 // EX: 'A OR B' + , Tid__not = 3 // EX: '-A' + , Tid__word_quote = 4 // EX: '"A B"' + , Tid__invalid = 5 // EX: 'A OR'; incomplete or otherwise invalid + ; + public static Srch_crt_itm[] Ary_empty = new Srch_crt_itm[0]; + public static final Srch_crt_itm Invalid = new Srch_crt_itm(-1, Srch_crt_itm.Tid__invalid, Srch_crt_itm.Ary_empty, null, null); + public static Srch_crt_itm New_join(int tid, int idx, Srch_crt_itm... ary) {return new Srch_crt_itm(idx, tid, ary, null, Srch_crt_sql.New_or_null(null, Byte_ascii.Null));} + public static Srch_crt_itm New_word(byte wildcard_byte, Srch_crt_tkn tkn, int idx, byte[] src) { + int tid = tkn.Tid == Srch_crt_tkn.Tid__word_w_quote ? Srch_crt_itm.Tid__word_quote : Srch_crt_itm.Tid__word; + return new Srch_crt_itm(idx, tid, Srch_crt_itm.Ary_empty, tkn.Val, Srch_crt_sql.New_or_null(src, wildcard_byte)); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_mgr.java index a27517de8..f5fd2a719 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_mgr.java @@ -13,3 +13,30 @@ 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.addons.wikis.searchs.searchers.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +public class Srch_crt_mgr { + public Srch_crt_mgr(byte[] key, Srch_crt_tkn[] tkns, Srch_crt_itm root, byte words_tid, Srch_crt_itm[] words_ary, Srch_crt_itm words_nth) { + this.Key = key; + this.Tkns = tkns; + this.Root = root; + this.Words_tid = words_tid; + this.Words_ary = words_ary; + this.Words_nth = words_nth; + } + public final byte[] Key; + public final Srch_crt_tkn[] Tkns; + public final Srch_crt_itm Root; + public final byte Words_tid; + public final Srch_crt_itm[] Words_ary; + public final Srch_crt_itm Words_nth; + public int Words_nth__len() { + return Words_nth == null ? 0 : Words_nth.Raw.length; + } + + public static final byte + Tid__one = 0 + , Tid__ands = 1 + , Tid__mixed = 2 + ; + public static Srch_crt_mgr Invalid = new Srch_crt_mgr(Bry_.Empty, Srch_crt_tkn.Ary_empty, Srch_crt_itm.Invalid, Tid__one, Srch_crt_itm.Ary_empty, Srch_crt_itm.Invalid); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_parser.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_parser.java index a27517de8..f4726b84c 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_parser.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_parser.java @@ -13,3 +13,132 @@ 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.addons.wikis.searchs.searchers.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import gplx.xowa.addons.wikis.searchs.searchers.crts.visitors.*; +public class Srch_crt_parser { + private final Srch_crt_scanner scanner; + private final Srch_crt_visitor__words words_visitor = new Srch_crt_visitor__words(); + private final Srch_crt_visitor__print print_visitor = new Srch_crt_visitor__print(); + private final byte wildcard_byte; + private int uid_next; + public Srch_crt_parser(Srch_crt_scanner_syms trie_bldr) { + this.wildcard_byte = trie_bldr.Wild(); + this.scanner = new Srch_crt_scanner(trie_bldr); + } + public int Next_uid() {return ++uid_next;} + public Srch_crt_mgr Parse_or_invalid(byte[] src) { + this.uid_next = -1; + + Srch_crt_tkn[] tkns_ary = scanner.Scan(src); + Srch_crt_parser_frame root_frame = new Srch_crt_parser_frame(this); + Parse_tkns(root_frame, tkns_ary, 0, tkns_ary.length); + Srch_crt_itm root_itm = root_frame.Produce_or_null(); + + if (root_itm == null) return Srch_crt_mgr.Invalid; + byte[] key = print_visitor.Print(root_itm); + words_visitor.Gather(root_itm); + return new Srch_crt_mgr(key, tkns_ary, root_itm, words_visitor.Words_tid(), words_visitor.Words_ary(), words_visitor.Words_nth()); + } + private int Parse_tkns(Srch_crt_parser_frame frame, Srch_crt_tkn[] tkns_ary, int tkns_bgn, int tkns_end) { + int tkns_cur = tkns_bgn; + while (tkns_cur < tkns_end) { + Srch_crt_tkn cur_tkn = tkns_ary[tkns_cur]; + int new_tkns_cur = Process_tkn(frame, tkns_ary, tkns_cur, tkns_end, cur_tkn); + if (new_tkns_cur < 0) { + tkns_cur = -new_tkns_cur; + break; + } + else + tkns_cur = new_tkns_cur; + } + return tkns_cur; + } + private int Process_tkn(Srch_crt_parser_frame frame, Srch_crt_tkn[] tkns_ary, int tkns_cur, int tkns_end, Srch_crt_tkn cur_tkn) { + byte cur_tid = cur_tkn.Tid; + switch (cur_tid) { + case Srch_crt_tkn.Tid__word: + case Srch_crt_tkn.Tid__word_w_quote: + frame.Subs__add(Srch_crt_itm.New_word(wildcard_byte, cur_tkn, frame.Next_uid(), cur_tkn.Val)); + break; + case Srch_crt_tkn.Tid__and: + frame.Eval_join(Srch_crt_itm.Tid__and); + break; + case Srch_crt_tkn.Tid__or: + frame.Eval_join(Srch_crt_itm.Tid__or); + break; + case Srch_crt_tkn.Tid__paren_bgn: { + Srch_crt_parser_frame paren_frame = new Srch_crt_parser_frame(this); + int new_tkns_cur = Parse_tkns(paren_frame, tkns_ary, tkns_cur + 1, tkns_end); + Srch_crt_itm paren_itm = paren_frame.Produce_or_null(); + if (paren_itm != null) { + frame.Subs__add(paren_itm); + } + return new_tkns_cur; + } + case Srch_crt_tkn.Tid__paren_end: + return -(tkns_cur + 1); + case Srch_crt_tkn.Tid__not: + frame.Notted_y_(); + break; + } + return tkns_cur + 1; + } +} +class Srch_crt_parser_frame { + public Srch_crt_parser_frame(Srch_crt_parser parser) { + this.parser = parser; + } + private int join_tid = Srch_crt_tkn.Tid__null; + private boolean notted = false; + private final List_adp subs = List_adp_.New(); + private final Srch_crt_parser parser; + public int Next_uid() {return parser.Next_uid();} + public void Notted_y_() { + if (notted) // already notted; disable; EX: "--a" + notted = false; + else + notted = true; + } + public void Subs__add(Srch_crt_itm itm) { + // if notted, wrap itm in not; EX: "-a"; "-(a & b)" + if (notted) { + itm = Srch_crt_itm.New_join(Srch_crt_itm.Tid__not, this.Next_uid(), itm); + notted = false; + } + subs.Add(itm); + + // auto-and behavior; EX: "a b" -> "a & b"; EX: "a (b | c)" -> "a & (b | c)" + if ( join_tid == Srch_crt_tkn.Tid__null // if currently null + && subs.Len() > 1 // but 2 items in list + ) + join_tid = Srch_crt_itm.Tid__and; // default to AND; EX: "a (b) c" + } + public void Eval_join(int tid) { + if (join_tid == Srch_crt_tkn.Tid__null) join_tid = tid; + else if (join_tid == tid) {} // tid is same; ignore; note that this handles dupes; EX: "a & & b" + else { // tid changed; EX: a & b | c + Merge_and_add(); + join_tid = tid; + } + } + public Srch_crt_itm Produce_or_null() { + int subs_len = subs.Len(); + switch (subs_len) { + case 0: + return null; + case 1: + join_tid = Srch_crt_tkn.Tid__null; + return (Srch_crt_itm)subs.Get_at(0); + default: + Srch_crt_itm[] subs_ary = (Srch_crt_itm[])subs.To_ary_and_clear(Srch_crt_itm.class); + Srch_crt_itm rv = Srch_crt_itm.New_join(join_tid, parser.Next_uid(), subs_ary); + join_tid = Srch_crt_tkn.Tid__null; + return rv; + } + } + private void Merge_and_add() { + int subs_len = subs.Len(); + if (subs_len > 1) + subs.Add(Produce_or_null()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_parser_tst.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_parser_tst.java index a27517de8..9f2be3a02 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_parser_tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_parser_tst.java @@ -13,3 +13,54 @@ 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.addons.wikis.searchs.searchers.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import org.junit.*; import gplx.xowa.addons.wikis.searchs.parsers.*; import gplx.xowa.addons.wikis.searchs.searchers.crts.visitors.*; +public class Srch_crt_parser_tst { + private final Srch_crt_parser_fxt fxt = new Srch_crt_parser_fxt(); + @Test public void Word__one() {fxt.Test__parse("a" , "a");} + @Test public void And__one() {fxt.Test__parse("a + b" , "(a AND b)");} + @Test public void And__many() {fxt.Test__parse("a + b + c" , "(a AND b AND c)");} + @Test public void And__dangling() {fxt.Test__parse("a +" , "a");} + @Test public void And__dupe() {fxt.Test__parse("a + + b" , "(a AND b)");} + @Test public void Quote() {fxt.Test__parse("\"a b\"" , "\"a b\"");} + @Test public void Auto__word() {fxt.Test__parse("a b" , "(a AND b)");} + @Test public void Auto__quote() {fxt.Test__parse("a \"b\"" , "(a AND \"b\")");} + @Test public void Auto__parens() {fxt.Test__parse("a (b , c)" , "(a AND (b OR c))");} + @Test public void Or__one() {fxt.Test__parse("a , b" , "(a OR b)");} + @Test public void Or__many() {fxt.Test__parse("a , b , c" , "(a OR b OR c)");} + @Test public void Mixed__3() {fxt.Test__parse("a + b , c" , "((a AND b) OR c)");} + @Test public void Mixed__5() {fxt.Test__parse("a + b , c + d , e" , "((((a AND b) OR c) AND d) OR e)");} + @Test public void Parens__basic() {fxt.Test__parse("a + (b , c)" , "(a AND (b OR c))");} + @Test public void Parens__nest() {fxt.Test__parse("a + (b , (c + d))" , "(a AND (b OR (c AND d)))");} + @Test public void Parens__mid() {fxt.Test__parse("a + (b , c) + d)" , "(a AND (b OR c) AND d)");} + @Test public void Parens__mixed() {fxt.Test__parse("a + (b , c) , d)" , "((a AND (b OR c)) OR d)");} + @Test public void Parens__dupe() {fxt.Test__parse("((a))" , "a");} + @Test public void Parens__dangling__lhs() {fxt.Test__parse("(a" , "a");} + @Test public void Parens__dangling__rhs() {fxt.Test__parse("a)" , "a");} + @Test public void Parens__empty__bos() {fxt.Test__parse("()" , "");} + @Test public void Parens__empty__mid() {fxt.Test__parse("a () b" , "(a AND b)");} + @Test public void Not__bos() {fxt.Test__parse("-abc" , "NOT abc");} + @Test public void Not__mid() {fxt.Test__parse("a -b" , "(a AND NOT b)");} + @Test public void Not__2() {fxt.Test__parse("a -b -c" , "(a AND NOT b AND NOT c)");} + @Test public void Not__dangling__eos() {fxt.Test__parse("a -" , "a");} + @Test public void Not__dangling__mid() {fxt.Test__parse("a -- b" , "(a AND b)");} // NOTE: scanner will remove spaces and convert to "a", "--", "b" + @Test public void Not__dupe__2() {fxt.Test__parse("a --b" , "(a AND b)");} + @Test public void Not__dupe__3() {fxt.Test__parse("a ---b" , "(a AND NOT b)");} + @Test public void Not__parens() {fxt.Test__parse("a -(b + c)" , "(a AND NOT (b AND c))");} + @Test public void Escape__eos() {fxt.Test__parse("\\" , "");} + @Test public void Escape__escaped() {fxt.Test__parse("\\\\*" , "\\*");} // '\\' -> '\*' +} +class Srch_crt_parser_fxt { + private final Srch_crt_parser crt_parser; + private final Srch_crt_visitor__print visitor__to_str = new Srch_crt_visitor__print(); + public Srch_crt_parser_fxt() { + crt_parser = new Srch_crt_parser(Srch_crt_scanner_syms.Dflt); + Srch_text_parser text_parser = new Srch_text_parser(); + text_parser.Init_for_ttl(gplx.xowa.langs.cases.Xol_case_mgr_.A7()); + } + public void Test__parse(String src_str, String expd) { + byte[] src_bry = Bry_.new_a7(src_str); + Srch_crt_mgr crt_mgr = crt_parser.Parse_or_invalid(src_bry); + Tfds.Eq(expd, String_.new_u8(visitor__to_str.Print(crt_mgr.Root))); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_scanner.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_scanner.java index a27517de8..9c52b88e1 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_scanner.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_scanner.java @@ -13,3 +13,122 @@ 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.addons.wikis.searchs.searchers.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import gplx.core.btries.*; import gplx.xowa.langs.cases.*; +import gplx.xowa.addons.wikis.searchs.parsers.*; +class Srch_crt_scanner { + private final List_adp tkns = List_adp_.New(); private byte[] src; private int src_len, pos, txt_bgn; + private final Srch_crt_scanner_syms trie_bldr; private final Btrie_slim_mgr trie; private final Btrie_rv trv = new Btrie_rv(); + private final Bry_bfr word_bfr = Bry_bfr_.New(); private boolean word_is_dirty; + public Srch_crt_scanner(Srch_crt_scanner_syms trie_bldr) { + this.trie_bldr = trie_bldr; + this.trie = trie_bldr.Trie(); + } + public Srch_crt_tkn[] Scan(byte[] src) { + this.src = src; this.src_len = src.length; + tkns.Clear(); pos = 0; txt_bgn = -1; + while (pos < src_len) { + byte cur_b = src[pos]; + byte cur_tid = trie.Match_byte_or(trv, cur_b, src, pos, src_len, Byte_.Max_value_127); + if (cur_tid == Byte_.Max_value_127) { // text character + if (txt_bgn == -1) txt_bgn = pos; // 1st character not set; set it + if (word_is_dirty) word_bfr.Add_byte(cur_b); + ++pos; + } + else { // \ \s " - & | ( ) + int pos_end = trv.Pos(); + if ( cur_tid == Srch_crt_tkn.Tid__not // if "-" + && txt_bgn != -1) { // && "word has started" + ++pos; + continue; // ignore; EX: "a-b" -> "a-b"; "-ab" -> "NOT" "ab" + } + if ( txt_bgn != -1 // pending word + && cur_tid != Srch_crt_tkn.Tid__escape // not escape + ) { + Add_word(Srch_crt_tkn.Tid__word, txt_bgn, pos); + } + switch (cur_tid) { + case Srch_crt_tkn.Tid__escape: // handle escape + int nxt_pos = pos + 1; + if (txt_bgn == -1) { + txt_bgn = nxt_pos; + word_is_dirty = true; + } else { // word has started; transfer existing word to bfr; + if (!word_is_dirty) { + word_is_dirty = true; + word_bfr.Add_mid(src, txt_bgn, pos); + } + } + pos = nxt_pos; // skip "\" + if (pos < src_len) { + word_bfr.Add_byte(src[pos]); // add next char literally + ++pos; + } + break; + case Srch_crt_tkn.Tid__space: // discard spaces + pos = Bry_find_.Find_fwd_while(src, pos, src_len, trie_bldr.Space()); + break; + case Srch_crt_tkn.Tid__quote: // find end quote and add as word + int quote_bgn = pos + 1; + int quote_end = Int_.Min_value; + int tmp_pos = quote_bgn; + while (true) { + quote_end = Bry_find_.Find_fwd(src, trie_bldr.Quote(), tmp_pos, src_len); + if (quote_end == Bry_find_.Not_found) { // no end-quote found; use space + quote_end = Bry_find_.Find_fwd(src, trie_bldr.Space(), quote_bgn, src_len); + if (quote_end == Bry_find_.Not_found) quote_end = src_len; // no space found; use EOS + } + else { // end-quote found; check if it's doubled + int double_pos = quote_end + 1; + if ( double_pos < src_len + && src[double_pos] == Byte_ascii.Quote) { + if (!word_is_dirty) { + word_is_dirty = true; + } + word_bfr.Add_mid(src, tmp_pos, double_pos); + tmp_pos = double_pos + 1; + continue; + } + } + break; + } + if (word_is_dirty) + word_bfr.Add_mid(src, tmp_pos, quote_end); + Add_word(Srch_crt_tkn.Tid__word_w_quote, quote_bgn, quote_end); + pos = quote_end + 1; // +1 to place after quote + break; + case Srch_crt_tkn.Tid__not: + Add_word(Srch_crt_tkn.Tid__not, pos, pos_end); + pos = pos_end; + break; + case Srch_crt_tkn.Tid__paren_bgn: case Srch_crt_tkn.Tid__paren_end: + case Srch_crt_tkn.Tid__and: case Srch_crt_tkn.Tid__or: + tkns.Add(New_tkn(cur_tid, Bry_.Mid(src, pos, pos_end))); + pos = pos_end; + break; + default: throw Err_.new_unhandled_default(cur_tid); + } + } + } + if (txt_bgn != -1) { // pending word; create + Add_word(Srch_crt_tkn.Tid__word, txt_bgn, pos); + } + return (Srch_crt_tkn[])tkns.To_ary_and_clear(Srch_crt_tkn.class); + } + private void Add_word(byte tid, int src_bgn, int src_end) { + // generate word_bry + byte[] word_bry = null; + if (word_is_dirty) { + word_is_dirty = false; + if (word_bfr.Len_eq_0()) return; + word_bry = word_bfr.To_bry_and_clear(); + } + else { + if (src_end - src_bgn == 0) return; + word_bry = Bry_.Mid(src, src_bgn, src_end); + } + tkns.Add(New_tkn(tid, word_bry)); + txt_bgn = -1; + } + private static Srch_crt_tkn New_tkn(byte tid, byte[] val) {return new Srch_crt_tkn(tid, val);} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_scanner_syms.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_scanner_syms.java index a27517de8..4b3acd427 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_scanner_syms.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_scanner_syms.java @@ -13,3 +13,100 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.addons.wikis.searchs.searchers.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import gplx.core.btries.*; +public class Srch_crt_scanner_syms { + public Srch_crt_scanner_syms(byte escape, byte space, byte quote, byte not, byte and, byte or, byte paren_bgn, byte paren_end, byte wild) { + this.escape = escape; this.space = space; this.quote = quote; + this.not = not; this.and = and; this.and_bry = Bry_.New_by_byte(and); this.or = or; + this.paren_bgn = paren_bgn; this.paren_end = paren_end; + this.wild = wild; + this.trie = Btrie_slim_mgr.cs(); + Make_trie(trie, this); + } + public byte Escape() {return escape;} private byte escape; + public byte Space() {return space;} private byte space; + public byte Quote() {return quote;} private byte quote; + public byte Not() {return not;} private byte not; + public byte And() {return and;} private byte and; + public byte[] And_bry() {return and_bry;} private byte[] and_bry; + public byte Or() {return or;} private byte or; + public byte Paren_bgn() {return paren_bgn;} private byte paren_bgn; + public byte Paren_end() {return paren_end;} private byte paren_end; + public byte Wild() {return wild;} private byte wild; + public Btrie_slim_mgr Trie() {return trie;} private final Btrie_slim_mgr trie; + public byte[] To_bry() { + Bry_bfr bfr = Bry_bfr_.New(); + To_bry__add(bfr, "wild" , wild); + To_bry__add(bfr, "not" , not); + To_bry__add(bfr, "or" , or); + To_bry__add(bfr, "and" , and); + To_bry__add(bfr, "quote" , quote); + To_bry__add(bfr, "paren_bgn" , paren_bgn); + To_bry__add(bfr, "paren_end" , paren_end); + To_bry__add(bfr, "escape" , escape); + To_bry__add(bfr, "space" , space); + return bfr.To_bry_and_clear(); + } + public void Parse(byte[] src) { + byte[][] lines = Bry_split_.Split_lines(src); + escape = space = quote = not = and = or = paren_bgn = paren_end = wild = Byte_.Zero; + for (byte[] line : lines) { + int line_len = line.length; + int eq_pos = Bry_find_.Find_fwd(line, Byte_ascii.Eq, 0, line_len); if (eq_pos == Bry_find_.Not_found) continue; + String key = String_.new_u8(Bry_.Mid(line, 0, eq_pos)); + byte val = Parse__val(line, eq_pos + 1, line_len); + if (String_.Eq(key, "wild" )) wild = val; + else if (String_.Eq(key, "not" )) not = val; + else if (String_.Eq(key, "or" )) or = val; + else if (String_.Eq(key, "and" )) and = val; + else if (String_.Eq(key, "quote" )) quote = val; + else if (String_.Eq(key, "paren_bgn" )) paren_bgn = val; + else if (String_.Eq(key, "paren_end" )) paren_end = val; + else if (String_.Eq(key, "escape" )) escape = val; + else if (String_.Eq(key, "space" )) space = val; + } + } + private static void To_bry__add(Bry_bfr bfr, String key, byte val) { + bfr.Add_str_u8(key).Add_byte_eq(); + switch (val) { + case Byte_ascii.Null : bfr.Add_str_a7("\\0"); break; + case Byte_ascii.Space : bfr.Add_str_a7("\\s"); break; + default : bfr.Add_byte(val); break; + } + bfr.Add_byte_nl(); + } + private static void Make_trie(Btrie_slim_mgr trie, Srch_crt_scanner_syms bldr) { + Make_trie__add(trie, bldr.Escape() , Srch_crt_tkn.Tid__escape); + Make_trie__add(trie, bldr.Space() , Srch_crt_tkn.Tid__space); + Make_trie__add(trie, bldr.Quote() , Srch_crt_tkn.Tid__quote); + Make_trie__add(trie, bldr.Not() , Srch_crt_tkn.Tid__not); + Make_trie__add(trie, bldr.And() , Srch_crt_tkn.Tid__and); + Make_trie__add(trie, bldr.Or() , Srch_crt_tkn.Tid__or); + Make_trie__add(trie, bldr.Paren_bgn() , Srch_crt_tkn.Tid__paren_bgn); + Make_trie__add(trie, bldr.Paren_end() , Srch_crt_tkn.Tid__paren_end); + } + private static void Make_trie__add(Btrie_slim_mgr rv, byte b, byte tid) { + if (b == Byte_ascii.Null) return; + rv.Add_bry_byte(b, tid); + } + public static Srch_crt_scanner_syms New__dflt() { + return new Srch_crt_scanner_syms + ( Byte_ascii.Backslash, Byte_ascii.Space, Byte_ascii.Quote, Byte_ascii.Dash, Byte_ascii.Plus, Byte_ascii.Comma + , Byte_ascii.Paren_bgn, Byte_ascii.Paren_end, Byte_ascii.Star + ); + } + public static final Srch_crt_scanner_syms Dflt = New__dflt(); + private static byte Parse__val(byte[] line, int val_bgn, int line_len) { + if (line_len - val_bgn == 1) return line[val_bgn]; + if ( line_len - val_bgn == 2 + && line[val_bgn] == Byte_ascii.Backslash) { + byte val_byte = line[val_bgn + 1]; + switch (val_byte) { + case Byte_ascii.Num_0: return Byte_ascii.Null; + case Byte_ascii.Ltr_s: return Byte_ascii.Space; + } + } + return Byte_ascii.Null; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_scanner_tst.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_scanner_tst.java index a27517de8..b64426b31 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_scanner_tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_scanner_tst.java @@ -13,3 +13,71 @@ 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.addons.wikis.searchs.searchers.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import org.junit.*; import gplx.xowa.addons.wikis.searchs.parsers.*; +public class Srch_crt_scanner_tst { + private final Srch_crt_scanner_fxt fxt = new Srch_crt_scanner_fxt(); + @Test public void Word() {fxt.Test__scan("abc" , "abc");} + @Test public void Word__many() {fxt.Test__scan("abc d ef" , "abc", "d", "ef");} + @Test public void Word__symbol() {fxt.Test__scan("a; b" , "a;", "b");} + @Test public void And() {fxt.Test__scan("a + b" , "a", "+", "b");} + @Test public void And__parens() {fxt.Test__scan("a +(b)" , "a", "+", "(", "b", ")");} // check that ( causes and to be treated as separate word + @Test public void Or() {fxt.Test__scan("a , b" , "a", ",", "b");} + @Test public void Or__no_space() {fxt.Test__scan("a, b" , "a", ",", "b");} + @Test public void Not() {fxt.Test__scan("-abc" , "-", "abc");} + @Test public void Not__mid__1() {fxt.Test__scan("a-b" , "a-b");} // fails if "a", "-", "b" + @Test public void Not__mid__2() {fxt.Test__scan("a b-c" , "a", "b-c");} // ignore - if in middle of word + @Test public void Not__and() {fxt.Test__scan("a -bc" , "a", "-", "bc");} // auto-add AND for - + @Test public void Not__dangling() {fxt.Test__scan("a -" , "a", "-");} + @Test public void Space() {fxt.Test__scan(" a b " , "a", "b");} // spaces should not generate tkns + @Test public void Quote() {fxt.Test__scan("\"a b\"" , "a b");} + @Test public void Quote__mid() {fxt.Test__scan("a\"b" , "a", "b");} + @Test public void Quote__double() {fxt.Test__scan("\"a\"\"b\"" , "a\"b");} + @Test public void Quote__missing__one() {fxt.Test__scan("\"abc" , "abc");} + @Test public void Quote__missing__many() {fxt.Test__scan("\"abc a" , "abc", "a");} + @Test public void Escape__bgn() {fxt.Test__scan("\\-a" , "-a");} // fails if "-", "a" + @Test public void Escape__and__bgn() {fxt.Test__scan("\\+" , "+");} // fails if "a", "&", "b" + @Test public void Escape__and__mid() {fxt.Test__scan("a\\+b" , "a+b");} // fails if "a", "&", "b" + @Test public void Escape__eos__1() {fxt.Test__scan("\\" , String_.Ary_empty);} + @Test public void Escape__eos__2() {fxt.Test__scan("a \\" , "a");} + @Test public void Escape__eos__3() {fxt.Test__scan("a\\" , "a");} + @Test public void Escape__many() {fxt.Test__scan("c\\+\\+" , "c++");} + @Test public void Escape__end() {fxt.Test__scan("a\\\\" , "a\\");} + @Test public void Complicated() {fxt.Test__scan("(a + \"b\") , -c", "(", "a", "+", "b", ")", ",", "-", "c");} +} +class Srch_crt_scanner_fxt { + private final Srch_crt_scanner scanner; + public Srch_crt_scanner_fxt() { + this.scanner = new Srch_crt_scanner(Srch_crt_scanner_syms.Dflt); + Srch_text_parser text_parser = new Srch_text_parser(); + text_parser.Init_for_ttl(gplx.xowa.langs.cases.Xol_case_mgr_.A7()); + } + public void Test__scan(String src_str, String... expd) { + byte[] src_bry = Bry_.new_a7(src_str); + Srch_crt_tkn[] actl_itms = scanner.Scan(src_bry); + Tfds.Eq_ary(expd, To_vals(src_bry, actl_itms)); + } + public void Test__scan_tids(String src_str, byte... expd) { + byte[] src_bry = Bry_.new_a7(src_str); + Srch_crt_tkn[] actl_itms = scanner.Scan(src_bry); + Tfds.Eq_ary(expd, To_tids(actl_itms)); + } + private String[] To_vals(byte[] src, Srch_crt_tkn[] ary) { + int len = ary.length; + String[] rv = new String[len]; + for (int i = 0; i < len; i++) { + Srch_crt_tkn tkn = ary[i]; + rv[i] = String_.new_a7(tkn.Val); + } + return rv; + } + private byte[] To_tids(Srch_crt_tkn[] ary) { + int len = ary.length; + byte[] rv = new byte[len]; + for (int i = 0; i < len; i++) { + Srch_crt_tkn tkn = ary[i]; + rv[i] = tkn.Tid; + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_sql.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_sql.java index a27517de8..73efa1358 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_sql.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_sql.java @@ -13,3 +13,62 @@ 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.addons.wikis.searchs.searchers.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import gplx.langs.regxs.*; +public class Srch_crt_sql { + public Srch_crt_sql(int tid, String eq, String rng_bgn, String rng_end, String like, Gfo_pattern pattern) { + this.Tid = tid; + this.Eq = eq; + this.Rng_bgn = rng_bgn; + this.Rng_end = rng_end; + this.Like = like; + this.Pattern = pattern; + } + public final int Tid; + public final String Eq; + public final String Rng_bgn; + public final String Rng_end; + public final String Like; + public final Gfo_pattern Pattern; // NOTE: only supports LIKE; GLOB requires regex + public int Eq_id; + + public static final int + Tid__eq = 0 // EX: 'ab' -> "word_text = 'ab'" + , Tid__rng = 1 // EX: 'ab*' -> "word_text >= 'ab' AND word_text < 'ac'" + , Tid__like = 2 // EX: 'a*b', '*a*b'-> "word_text LIKE 'a%b%'" + ; + public static Srch_crt_sql New_or_null(byte[] raw, byte wildcard_byte) { + if (raw == null) return null; // null for join itms; EX: "+", "," + int raw_len = raw.length; + + // get tid + int wildcard_pos = Bry_find_.Find_fwd(raw, wildcard_byte, 0, raw_len); + int tid = -1; + if (wildcard_pos == Bry_find_.Not_found) tid = Srch_crt_sql.Tid__eq; // EX: 'a' + else if (wildcard_pos == raw_len - 1) tid = Srch_crt_sql.Tid__rng; // EX: 'a*' + else tid = Srch_crt_sql.Tid__like; // EX: '*a' + + // get rng_bgn, rng_end or like + String eq = "", rng_bgn = "", rng_end = "", like = ""; + byte[] pattern_raw = raw; + switch (tid) { + case Srch_crt_sql.Tid__eq: + eq = String_.new_a7(raw); + break; + case Srch_crt_sql.Tid__rng: + byte[] rng_tmp = Bry_.Mid(raw, 0, raw_len - 1); + rng_bgn = String_.new_u8(rng_tmp); + rng_end = String_.new_u8(gplx.core.intls.Utf8_.Increment_char_at_last_pos(rng_tmp)); + break; + case Srch_crt_sql.Tid__like: + like = String_.new_u8(Bry_.Replace(raw, wildcard_byte, gplx.dbs.sqls.Sql_qry_wtr_.Like_wildcard)); + byte like_escape_byte = gplx.xowa.addons.wikis.searchs.searchers.wkrs.Srch_link_wkr_sql.Like_escape_byte; + Bry_bfr tmp_bfr = Bry_bfr_.Get(); + try {pattern_raw = Bry_.Resolve_escape(tmp_bfr, like_escape_byte, raw, 0, raw.length);} + finally {tmp_bfr.Mkr_rls();} + break; + } + Gfo_pattern pattern = tid == Srch_crt_sql.Tid__eq ? null : new Gfo_pattern(pattern_raw); + return new Srch_crt_sql(tid, eq, rng_bgn, rng_end, like, pattern); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_sql_tst.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_sql_tst.java index a27517de8..e94d18103 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_sql_tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_sql_tst.java @@ -13,3 +13,26 @@ 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.addons.wikis.searchs.searchers.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import org.junit.*; +public class Srch_crt_sql_tst { + private final Srch_crt_sql_fxt fxt = new Srch_crt_sql_fxt(); + @Test public void Eq() {fxt.Exec__new_or_null("a").Test__tid(Srch_crt_sql.Tid__eq).Test__eq("a");} + @Test public void Rng() {fxt.Exec__new_or_null("a*").Test__tid(Srch_crt_sql.Tid__rng).Test__rng_bgn("a").Test__rng_end("b").Test__pattern("a*");} + @Test public void Like() {fxt.Exec__new_or_null("a*b").Test__tid(Srch_crt_sql.Tid__like).Test__like("a%b").Test__pattern("a*b");} + @Test public void Like__escape() {fxt.Exec__new_or_null("a|\\*b").Test__tid(Srch_crt_sql.Tid__like).Test__like("a|\\%b").Test__pattern("a\\*b");} // "a\*b" + @Test public void Quote() {fxt.Exec__new_or_null("\"a b\"").Test__tid(Srch_crt_sql.Tid__eq).Test__eq("\"a b\"");} +} +class Srch_crt_sql_fxt { + private Srch_crt_sql actl; + public Srch_crt_sql_fxt Exec__new_or_null(String src_str) { + this.actl = Srch_crt_sql.New_or_null(Bry_.new_u8(src_str), Srch_search_addon.Wildcard__star); + return this; + } + public Srch_crt_sql_fxt Test__tid(int expd) {Tfds.Eq(expd, actl.Tid); return this;} + public Srch_crt_sql_fxt Test__eq(String expd) {Tfds.Eq(expd, actl.Eq); return this;} + public Srch_crt_sql_fxt Test__rng_bgn(String expd) {Tfds.Eq(expd, actl.Rng_bgn); return this;} + public Srch_crt_sql_fxt Test__rng_end(String expd) {Tfds.Eq(expd, actl.Rng_end); return this;} + public Srch_crt_sql_fxt Test__like(String expd) {Tfds.Eq(expd, actl.Like); return this;} + public Srch_crt_sql_fxt Test__pattern(String expd) {Tfds.Eq(expd, String_.new_u8(actl.Pattern.Raw()), "pattern"); return this;} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_tkn.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_tkn.java index a27517de8..1cdb9cde8 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_tkn.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_tkn.java @@ -13,3 +13,24 @@ 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.addons.wikis.searchs.searchers.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +public class Srch_crt_tkn { + public Srch_crt_tkn(byte tid, byte[] val) {this.Tid = tid; this.Val = val;} + public final byte Tid; + public final byte[] Val; + public static final byte + Tid__escape = 0 + , Tid__space = 1 + , Tid__quote = 2 + , Tid__not = 3 + , Tid__and = 4 + , Tid__or = 5 + , Tid__paren_bgn = 6 + , Tid__paren_end = 7 + , Tid__word = 8 + , Tid__word_w_quote = 9 + , Tid__eos = 10 + , Tid__null = 11 + ; + public static final Srch_crt_tkn[] Ary_empty = new Srch_crt_tkn[0]; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_visitor.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_visitor.java index a27517de8..10d4a7f95 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_visitor.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/Srch_crt_visitor.java @@ -13,3 +13,7 @@ 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.addons.wikis.searchs.searchers.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +public interface Srch_crt_visitor { + void Visit(Srch_crt_itm node); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/visitors/Srch_crt_visitor__print.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/visitors/Srch_crt_visitor__print.java index a27517de8..b2d9df2c0 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/visitors/Srch_crt_visitor__print.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/visitors/Srch_crt_visitor__print.java @@ -13,3 +13,35 @@ 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.addons.wikis.searchs.searchers.crts.visitors; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; import gplx.xowa.addons.wikis.searchs.searchers.crts.*; +public class Srch_crt_visitor__print implements Srch_crt_visitor { + private final Bry_bfr bfr = Bry_bfr_.New(); + public byte[] Print(Srch_crt_itm root) { + Visit(root); + return bfr.To_bry_and_clear(); + } + public void Visit(Srch_crt_itm node) { + switch (node.Tid) { + case Srch_crt_itm.Tid__word: bfr.Add(node.Raw); break; + case Srch_crt_itm.Tid__word_quote: bfr.Add_byte_quote().Add(node.Raw).Add_byte_quote(); break; + case Srch_crt_itm.Tid__and: + case Srch_crt_itm.Tid__or: + bfr.Add_byte(Byte_ascii.Paren_bgn); + Srch_crt_itm[] subs = node.Subs; + int subs_len = subs.length; + for (int i = 0; i < subs_len; ++i) { + if (i != 0) + bfr.Add_str_a7(node.Tid == Srch_crt_itm.Tid__and ? " AND " : " OR "); + subs[i].Accept_visitor(this); + } + bfr.Add_byte(Byte_ascii.Paren_end); + break; + case Srch_crt_itm.Tid__not: + bfr.Add_str_a7("NOT "); + node.Subs[0].Accept_visitor(this); + break; + case Srch_crt_itm.Tid__invalid: break; // should not happen + default: throw Err_.new_unhandled_default(node.Tid); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/visitors/Srch_crt_visitor__words.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/visitors/Srch_crt_visitor__words.java index a27517de8..f4f5925fc 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/visitors/Srch_crt_visitor__words.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/crts/visitors/Srch_crt_visitor__words.java @@ -13,3 +13,48 @@ 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.addons.wikis.searchs.searchers.crts.visitors; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; import gplx.xowa.addons.wikis.searchs.searchers.crts.*; +public class Srch_crt_visitor__words implements Srch_crt_visitor { + private final List_adp words_list = List_adp_.New(); + public byte Words_tid() {return words_tid;} private byte words_tid; + public Srch_crt_itm Words_nth() {return words_nth;} private Srch_crt_itm words_nth; + public Srch_crt_itm[] Words_ary() {return (Srch_crt_itm[])words_list.To_ary_and_clear(Srch_crt_itm.class);} + public void Gather(Srch_crt_itm root) { + words_list.Clear(); + words_tid = Srch_crt_mgr.Tid__ands; + words_nth = null; + Visit(root); + if (words_list.Count() == 1) + words_tid = Srch_crt_mgr.Tid__one; + } + public void Visit(Srch_crt_itm itm) { + int itm_tid = itm.Tid; + switch (itm_tid) { + case Srch_crt_itm.Tid__and: + case Srch_crt_itm.Tid__or: + if (itm_tid == Srch_crt_itm.Tid__or) + words_tid = Srch_crt_mgr.Tid__mixed; + Srch_crt_itm[] subs = itm.Subs; + int subs_len = subs.length; + for (int i = 0; i < subs_len; ++i) + Visit(subs[i]); + break; + case Srch_crt_itm.Tid__word: + case Srch_crt_itm.Tid__word_quote: + case Srch_crt_itm.Tid__not: + if ( itm_tid == Srch_crt_itm.Tid__not + && itm.Subs.length == 1) { + Srch_crt_itm lone = itm.Subs[0]; + if ( lone.Tid == Srch_crt_itm.Tid__word + || lone.Tid == Srch_crt_itm.Tid__word_quote) + words_nth = lone; + } + else + words_nth = itm; + words_list.Add(itm); + break; + case Srch_crt_itm.Tid__invalid: break; // should not happen + default: throw Err_.new_unhandled_default(itm.Tid); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_cache.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_cache.java index a27517de8..b5e0380b9 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_cache.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_cache.java @@ -13,3 +13,18 @@ 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.addons.wikis.searchs.searchers.rslts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +public class Srch_rslt_cache { + private final Hash_adp_bry hash = Hash_adp_bry.cs(); + public void Clear() {hash.Clear();} + public Srch_rslt_list Get_or_new(byte[] key) { + Srch_rslt_list rv = (Srch_rslt_list)hash.Get_by(key); + if (rv == null) { + rv = new Srch_rslt_list(); + hash.Add(key, rv); + } + rv.Rslts_are_first = true; + rv.Rslts_are_enough = false; + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_cbk.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_cbk.java index a27517de8..50ae36a7e 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_cbk.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_cbk.java @@ -13,3 +13,8 @@ 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.addons.wikis.searchs.searchers.rslts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +public interface Srch_rslt_cbk { + void On_rslts_found(Srch_search_qry qry, Srch_rslt_list rslts_list, int rslts_bgn, int rslts_end); + void On_cancel(); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_cbk_.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_cbk_.java index a27517de8..c6917fe8d 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_cbk_.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_cbk_.java @@ -13,3 +13,11 @@ 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.addons.wikis.searchs.searchers.rslts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +public class Srch_rslt_cbk_ { + public static final Srch_rslt_cbk Noop = new Srch_rslt_cbk__noop(); +} +class Srch_rslt_cbk__noop implements Srch_rslt_cbk { + public void On_rslts_found(Srch_search_qry qry, Srch_rslt_list rslts_list, int rslts_bgn, int rslts_end) {} + public void On_cancel() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_list.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_list.java index a27517de8..4eab38e58 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_list.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_list.java @@ -13,3 +13,49 @@ 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.addons.wikis.searchs.searchers.rslts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import gplx.core.lists.hashs.*; +public class Srch_rslt_list { + private final Ordered_hash key_hash = Ordered_hash_.New_bry(); + private final Hash_adp__int id_hash = new Hash_adp__int(); + public int Score_bgn = gplx.dbs.percentiles.Percentile_rng.Score_null; + public int Score_len = gplx.dbs.percentiles.Percentile_rng.Score_null; + public boolean Rslts_are_first; + public boolean Rslts_are_enough; + public boolean Rslts_are_done; + public int Len() {return key_hash.Len();} + public boolean Has(byte[] key) {return key_hash.Has(key);} + public Srch_rslt_row Get_by(byte[] key) {return (Srch_rslt_row)key_hash.Get_by(key);} + public Srch_rslt_row Get_at(int i) {return (Srch_rslt_row)key_hash.Get_at(i);} + public void Clear() {key_hash.Clear(); id_hash.Clear();} + public void Add(Srch_rslt_row row) {key_hash.Add(row.Key, row);} + public void Sort() {key_hash.Sort_by(Srch_rslt_row_sorter.Score_dsc);} + public boolean Ids__has(int id) {return (Srch_rslt_row)id_hash.Get_by_or_null(id) != null;} + public Srch_rslt_row Ids__get_or_null(int id) {return (Srch_rslt_row)id_hash.Get_by_or_null(id);} + public void Ids__add(int id, Srch_rslt_row r) {id_hash.Add(id, r);} + public void Merge(Srch_rslt_list list) { + list.Sort(); + int list_len = list.Len(); + for (int i = 0; i < list_len; ++i) { + Srch_rslt_row row = list.Get_at(i); + this.Add(row); + } + list.Clear(); + } + public void Process_rdr_done(gplx.dbs.percentiles.Percentile_rng rng, boolean rslts_are_enough, boolean rslts_are_done) { + this.Score_bgn = rng.Score_bgn(); + this.Score_len = rng.Score_len(); + this.Rslts_are_enough = rslts_are_enough; + this.Rslts_are_done = rslts_are_done; + } +} +class Srch_rslt_row_sorter implements gplx.core.lists.ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + Srch_rslt_row lhs = (Srch_rslt_row)lhsObj; + Srch_rslt_row rhs = (Srch_rslt_row)rhsObj; + int rv = -Int_.Compare(lhs.Page_score, rhs.Page_score); + if (rv != CompareAble_.Same) return rv; + return Bry_.Compare(lhs.Page_ttl.Page_txt(), rhs.Page_ttl.Page_txt()); + } + public static final Srch_rslt_row_sorter Score_dsc = new Srch_rslt_row_sorter(); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_list_.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_list_.java index a27517de8..9d3805ba6 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_list_.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_list_.java @@ -13,3 +13,39 @@ 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.addons.wikis.searchs.searchers.rslts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import gplx.xowa.wikis.data.tbls.*; +public class Srch_rslt_list_ { + public static boolean Add_if_new(Srch_search_ctx ctx, Srch_rslt_list rslts, Srch_rslt_row row) { + Srch_rslt_list_.Highlight(ctx, row); // always highlight title first; needed for suggest_box to update highlighting when increasing word; EX: Eart -> Earth; "Earth" should be highlighted, not "Eart" + return + ( !rslts.Has(row.Key) // ignore: page already added by another word; EX: "A B"; word is "B", but "A B" already added by "A" + && !rslts.Ids__has(row.Page_id) // ignore: page already added by page-tbl or by redirect + && !Redirect_exists(rslts, row) // ignore: page is redirect, and target page already added + ); + } + public static void Get_redirect_ttl(Xowd_page_tbl page_tbl, Xowd_page_itm tmp_page_itm, Srch_rslt_row row) { + int redirect_id = row.Page_redirect_id; + if (redirect_id == Srch_rslt_row.Page_redirect_id_null) return; + if (!page_tbl.Select_by_id(tmp_page_itm, redirect_id)) {Xoa_app_.Usr_dlg().Warn_many("", "", "page not found for redirect_id; redirect_id=~{0}", redirect_id); return;} + row.Page_redirect_ttl = Xoa_ttl.Replace_unders(tmp_page_itm.Ttl_page_db()); + } + private static void Highlight(Srch_search_ctx ctx, Srch_rslt_row row) { + try {row.Page_ttl_highlight = ctx.Highlight_mgr.Highlight(row.Page_ttl.Full_txt_w_ttl_case());} // NOTE: always highlight row; needed for when search done in url_bar (highlight=n) and then same search reused for search (highlight=y) + catch (Exception e) {Xoa_app_.Usr_dlg().Warn_many("", "", "highlight failed; ttl=~{0} err=~{1}", row.Page_ttl_wo_ns, Err_.Message_gplx_log(e));} + } + private static boolean Redirect_exists(Srch_rslt_list rslts, Srch_rslt_row cur_row) { + int trg_id = cur_row.Page_redirect_id; + if (trg_id == Srch_rslt_row.Page_redirect_id_null) { // src_page is not redirect + return false; + } else { // src_page is redirect + Srch_rslt_row trg_row = rslts.Ids__get_or_null(trg_id); + if (trg_row == null) { // trg_page has not been seen before + rslts.Ids__add(trg_id, cur_row); // add trg_id to known ids; handles double-redirects; 1 -> 2 -> 3; + return false; + } + else // trg_page has been seen before + return true; + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_row.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_row.java index a27517de8..dd713c1cb 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_row.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/rslts/Srch_rslt_row.java @@ -13,3 +13,47 @@ 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.addons.wikis.searchs.searchers.rslts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +public class Srch_rslt_row { + public Srch_rslt_row(byte[] key, byte[] wiki_bry, Xoa_ttl page_ttl, int page_ns, byte[] page_ttl_wo_ns, int page_id, int page_len, int page_score, int page_redirect_id) { + this.Key = key; + this.Wiki_bry = wiki_bry; + this.Page_id = page_id; + this.Page_ttl = page_ttl; + this.Page_ns = page_ns; + this.Page_ttl_wo_ns = page_ttl_wo_ns; + this.Page_len = page_len; + this.Page_redirect_id = page_redirect_id; + this.Page_score = page_score; + } + public final byte[] Key; + public final byte[] Wiki_bry; + public final int Page_id; + public final Xoa_ttl Page_ttl; + public final int Page_ns; + public final byte[] Page_ttl_wo_ns; + public final int Page_len; + public final int Page_redirect_id; + public final int Page_score; + public byte[] Page_redirect_ttl; + public byte[] Page_ttl_highlight; + public byte[] Page_ttl_display(boolean html) { + byte[] rv = html ? Page_ttl_highlight : Page_ttl.Full_txt_w_ttl_case(); + if (Page_redirect_id == Page_redirect_id_null) + return rv; + else { + byte[] redirect_dlm = html ? Bry__redirect__html : Bry__redirect__text; + return Bry_.Add(rv, redirect_dlm, Page_redirect_ttl); + } + } + + public static byte[] Bld_key(byte[] wiki_domain, int page_id) {return Bry_.Add(wiki_domain, Byte_ascii.Pipe_bry, Int_.To_bry(page_id));} + public static Srch_rslt_row New(byte[] wiki_bry, Xoa_ttl page_ttl, int page_id, int page_len, int page_score, int redirect_id) { + return new Srch_rslt_row(Bld_key(wiki_bry, page_id), wiki_bry, page_ttl, page_ttl.Ns().Id(), page_ttl.Page_db(), page_id, page_len, page_score, redirect_id); + } + public static final int Page_redirect_id_null = gplx.xowa.wikis.data.tbls.Xowd_page_itm.Redirect_id_null; + public static final String Str__redirect__text = " -> "; + private static final byte[] + Bry__redirect__html = Bry_.new_u8(" → ") // 8592; 8594 + , Bry__redirect__text = Bry_.new_a7(Str__redirect__text); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/slabs/Srch_slab_itm.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/slabs/Srch_slab_itm.java index a27517de8..a9bdd316c 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/slabs/Srch_slab_itm.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/slabs/Srch_slab_itm.java @@ -13,3 +13,10 @@ 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.addons.wikis.searchs.searchers.slabs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +public class Srch_slab_itm { + public Srch_slab_itm(byte[] wiki, int bgn, int end) {this.wiki = wiki; this.bgn = bgn; this.end = end;} + public byte[] Wiki() {return wiki;} private final byte[] wiki; + public int Bgn() {return bgn;} private final int bgn; + public int End() {return end;} private final int end; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/slabs/Srch_slab_itm_parser.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/slabs/Srch_slab_itm_parser.java index a27517de8..caa434842 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/slabs/Srch_slab_itm_parser.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/slabs/Srch_slab_itm_parser.java @@ -13,3 +13,20 @@ 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.addons.wikis.searchs.searchers.slabs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import gplx.core.brys.*; +public class Srch_slab_itm_parser { + private final List_adp itm_list = List_adp_.New(); + private final Bry_rdr rdr = new Bry_rdr(); + public Srch_slab_itm[] Parse(byte[] raw) { // EX: en.wikipedia.org|41|60;en.wiktionary.org|21|40; + rdr.Init_by_src(raw); + while (!rdr.Pos_is_eos()) { + byte[] wiki = rdr.Read_bry_to(Byte_ascii.Pipe); + int bgn = rdr.Read_int_to(Byte_ascii.Pipe); + int end = rdr.Read_int_to(Byte_ascii.Semic); + Srch_slab_itm itm = new Srch_slab_itm(wiki, bgn, end); + itm_list.Add(itm); + } + return (Srch_slab_itm[])itm_list.To_ary_and_clear(Srch_slab_itm.class); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_link_wkr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_link_wkr.java index a27517de8..4dc930ffa 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_link_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_link_wkr.java @@ -13,3 +13,150 @@ 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.addons.wikis.searchs.searchers.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.wikis.searchs.dbs.*; import gplx.xowa.addons.wikis.searchs.searchers.crts.*; import gplx.xowa.addons.wikis.searchs.searchers.rslts.*; import gplx.dbs.percentiles.*; +import gplx.xowa.langs.cases.*; import gplx.xowa.addons.wikis.searchs.parsers.*; +public class Srch_link_wkr extends Percentile_select_base { + private final Srch_link_wkr_sql sql_mkr = new Srch_link_wkr_sql(); + private final Db_attach_mgr attach_mgr = new Db_attach_mgr(); + private final Srch_rslt_list tmp_rslts = new Srch_rslt_list(); + private Srch_rslt_list rslts_list; private Srch_rslt_cbk rslt_cbk; private Srch_search_ctx ctx; + private Xowd_page_tbl page_tbl; + private Db_stmt stmt; + private int rslts_bgn, rslts_end; + private Srch_rslt_row cur_row; private final Xowd_page_itm tmp_page_itm = new Xowd_page_itm(); + private int link_tbl_idx, link_tbl_nth; private boolean link_loop_done; + private Srch_crt_itm sql_root; + public void Search(Srch_rslt_list rslts_list, Srch_rslt_cbk rslt_cbk, Srch_search_ctx ctx) { + // init + Gfo_usr_dlg_.Instance.Log_many("", "", "search.search by link_tbl; search=~{0}", ctx.Qry.Phrase.Orig); + super.cxl = ctx.Cxl; + super.rng = ctx.Score_rng; + super.rng_log = new Percentile_rng_log(ctx.Addon.Db_mgr().Cfg().Link_score_max()); + rng_log.Init(ctx.Qry.Phrase.Orig, ctx.Rslts_needed); + this.rslts_list = rslts_list; this.rslt_cbk = rslt_cbk; this.ctx = ctx; + this.rslts_bgn = rslts_list.Len(); this.rslts_end = rslts_bgn; + this.page_tbl = ctx.Tbl__page; + + try { + // enough results at start; occurs in Special:Search when revisiting slabs; EX: 1-100 -> 101-200 -> 1-100 + if (ctx.Qry.Slab_end < rslts_list.Len()) { + rslts_list.Rslts_are_enough = true; + rslt_cbk.On_rslts_found(ctx.Qry, rslts_list, 0, rslts_list.Len()); + return; + } + + // prepare for iteration + this.link_tbl_idx = 0; + this.link_tbl_nth = ctx.Tbl__link__ary.length - 1; + sql_root = Srch_link_wkr_.Find_sql_root(ctx); + attach_mgr.Conn_links_(new Db_attach_itm("page_db", ctx.Db__core.Conn()), new Db_attach_itm("word_db", ctx.Tbl__word.conn)); + super.Select(); + } + finally { + try { + Gfo_usr_dlg_.Instance.Log_many("", "", "search.detaching; phrase=~{0} score_bgn=~{1} score_end=~{2}", ctx.Qry.Phrase.Orig, ctx.Score_rng.Score_bgn(), ctx.Score_rng.Score_end()); + attach_mgr.Detach(); + stmt = Db_stmt_.Rls(stmt); + } + catch (Exception e) { + Gfo_usr_dlg_.Instance.Log_many("", "", "search.detaching fail; phrase=~{0} score_bgn=~{1} err=~{2}", ctx.Qry.Phrase.Orig, ctx.Score_rng.Score_bgn(), Err_.Message_gplx_log(e)); + } + } + } + @Override protected Db_rdr Rdr__init() { + try { + Db_conn link_tbl_conn = ctx.Tbl__link__ary[link_tbl_idx].conn; + attach_mgr.Conn_main_(link_tbl_conn); + sql_mkr.Init(ctx, attach_mgr, sql_root); + if (stmt == null) stmt = sql_mkr.Make(ctx, attach_mgr, link_tbl_conn); + sql_mkr.Fill(stmt); + return stmt.Exec_select__rls_manual(); + } finally {sql_mkr.Clear();} + } + @Override protected boolean Found_enough() {return (rslts_list.Len() + tmp_rslts.Len()) >= ctx.Qry.Slab_end;} + @Override protected void Rng__update(int rdr_found) { + link_loop_done = false; + if (ctx.Qry.Ns_mgr.Ns_main_only()) { + link_loop_done = true; + } + else { + if (link_tbl_idx == link_tbl_nth) { + link_tbl_idx = 0; + link_loop_done = true; + } + else { + ++link_tbl_idx; + } + // NOTE: must do detach_database and rls_stmt b/c link_tbl_conn changes + attach_mgr.Detach(); + stmt = Db_stmt_.Rls(stmt); + } + if (link_loop_done) + rng.Update(rslts_end - rslts_bgn); + } + @Override protected void Rdr__done(boolean rslts_are_enough, boolean rslts_are_done) { + if (!link_loop_done) return; + int tmp_rslts_len = tmp_rslts.Len(); + + // get redirect ttl; note that main rdr should be closed + for (int i = 0; i < tmp_rslts_len; ++i) { + Srch_rslt_row row = tmp_rslts.Get_at(i); + int redirect_id = row.Page_redirect_id; + if (redirect_id != Srch_rslt_row.Page_redirect_id_null) + Srch_rslt_list_.Get_redirect_ttl(page_tbl, tmp_page_itm, row); + } + + // merge to rslts_list; notify; cleanup; + if (tmp_rslts_len > 0) rslts_list.Merge(tmp_rslts); + rslts_list.Process_rdr_done(rng, rslts_are_enough, rslts_are_done); + rslt_cbk.On_rslts_found(ctx.Qry, rslts_list, rslts_bgn, rslts_end); + rslts_list.Rslts_are_first = false; + rslts_bgn = rslts_end; + // Gfo_usr_dlg_.Instance.Log_many("", "", "search.search rslts; rslts=~{0}", rng_log.To_str_and_clear()); + } + @Override protected boolean Row__read(Db_rdr rdr) { + if (!rdr.Move_next()) return false; + byte[] wiki_bry = ctx.Wiki_domain; + int page_id = rdr.Read_int(page_tbl.Fld_page_id()); + byte[] key = Srch_rslt_row.Bld_key(wiki_bry, page_id); + this.cur_row = ctx.Cache__page.Get_by(key); // note that page could have been added from another word + if (cur_row == null) { + int page_len = rdr.Read_int(page_tbl.Fld_page_len()); + int page_score = page_tbl.Fld_page_score() == Dbmeta_fld_itm.Key_null ? page_len : rdr.Read_int(page_tbl.Fld_page_score()); + int page_ns_id = rdr.Read_int(page_tbl.Fld_page_ns()); + byte[] page_ttl_wo_ns = rdr.Read_bry_by_str(page_tbl.Fld_page_title()); + Xoa_ttl page_ttl = ctx.Wiki.Ttl_parse(page_ns_id, page_ttl_wo_ns); + this.cur_row = new Srch_rslt_row(key, wiki_bry, page_ttl, page_ns_id, page_ttl_wo_ns, page_id, page_len, page_score, rdr.Read_int(page_tbl.Fld_redirect_id())); + ctx.Cache__page.Add(cur_row); + } + return true; + } + @Override protected boolean Row__eval() { + if ( !ctx.Qry.Ns_mgr.Has(cur_row.Page_ns) // ignore: ns doesn't match + || !Srch_link_wkr_.Matches(ctx.Crt_mgr__root, ctx.Addon.Ttl_parser(), ctx.Case_mgr, cur_row.Page_ttl_wo_ns) // ignore: ttl doesn't match ttl_matcher; EX: "A B" + ) + return false; + boolean rv = Srch_rslt_list_.Add_if_new(ctx, rslts_list, cur_row); + if (rv) { + ++rslts_end; + rslts_list.Ids__add(cur_row.Page_id, cur_row); + tmp_rslts.Add(cur_row); + } + return rv; + } + public static int Percentile_rng__calc_adj(int last_word_len) { + switch (last_word_len) { + case 1: return 0; + case 2: return 10; + case 3: return 20; + case 4: return 30; + case 5: return 40; + case 6: return 50; + case 7: return 60; + case 8: return 70; + default: return 80; + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_link_wkr_.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_link_wkr_.java index a27517de8..3341cf770 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_link_wkr_.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_link_wkr_.java @@ -13,3 +13,127 @@ 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.addons.wikis.searchs.searchers.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import gplx.xowa.addons.wikis.searchs.parsers.*; import gplx.xowa.langs.cases.*; +import gplx.xowa.addons.wikis.searchs.dbs.*; import gplx.xowa.addons.wikis.searchs.searchers.crts.*; +class Srch_link_wkr_ { + private static final Srch_word_count_wkr word_count_wkr = new Srch_word_count_wkr(); + public static Srch_crt_itm Find_sql_root(Srch_search_ctx ctx) { + Srch_crt_mgr crt_mgr = ctx.Crt_mgr; + // lookup word_ids; needed for one, mixed, and ands + Srch_crt_itm[] words_ary = crt_mgr.Words_ary; + int words_len = words_ary.length; + for (int i = 0; i < words_len; ++i) { + Srch_crt_itm word = words_ary[i]; + switch (word.Tid) { + case Srch_crt_itm.Tid__word: + if (word.Sql_data.Tid == Srch_crt_sql.Tid__eq) // look up word_id + word.Sql_data.Eq_id = ctx.Tbl__word.Select_or_empty(word.Raw).Id; + break; + case Srch_crt_itm.Tid__word_quote: + Srch_word_row[] rows = Find_sql_root__quoted(ctx, word.Raw); + if (rows != null && rows.length > 0) // override eq_id + word.Sql_data.Eq_id = rows[0].Id; + break; + } + } + + if (crt_mgr.Words_tid != Srch_crt_mgr.Tid__ands) return ctx.Crt_mgr__root; // one and mixed returns root; + + // ands need to do more db_lookup for rng words and identify the sql_root + Srch_crt_itm rv = null; + Srch_word_tbl word_tbl = ctx.Tbl__word; + int count_min = Int_.Max_value; + for (int i = 0; i < words_len; ++i) { + Srch_crt_itm sub = words_ary[i]; + int sub_count = Find_sql_root__ands(ctx, word_tbl, sub); + if (sub_count < count_min) { + count_min = sub_count; + rv = sub; + } + } + return rv; + } + private static Srch_word_row[] Find_sql_root__quoted(Srch_search_ctx ctx, byte[] raw) { + List_adp tmp_list = List_adp_.New(); + byte[][] ary = Bry_split_.Split(raw, Byte_ascii.Space, Bool_.Y); // TODO_OLD: splitting by space is simplistic; should call Srch2_split_words + int words_len = ary.length; + for (int i = 0; i < words_len; ++i) { + byte[] word = ary[i]; + Srch_word_row word_row = ctx.Tbl__word.Select_or_empty(word); if (word_row == Srch_word_row.Empty) continue; + tmp_list.Add(word_row); + } + if (tmp_list.Count() == 0) return null; // no words exist in db; EX: "xyz1 xyz2" + tmp_list.Sort_by(Srch_word_row_sorter__link_count.Desc); + Srch_word_row[] rows = (Srch_word_row[])tmp_list.To_ary_and_clear(Srch_word_row.class); + return rows; + } + private static int Find_sql_root__ands(Srch_search_ctx ctx, Srch_word_tbl word_tbl, Srch_crt_itm sub) { + if (sub.Tid == Srch_crt_itm.Tid__not) return Int_.Max_value; + int cached_count = ctx.Cache__word_counts.Get_as_int_or(sub.Raw, Int_.Min_value); + if (cached_count != Int_.Min_value) return cached_count; + int rv = Int_.Max_value; + if (sub.Sql_data.Tid == Srch_crt_sql.Tid__eq) { + Srch_word_row word = word_tbl.Select_or_empty(sub.Raw); + if (word != Srch_word_row.Empty) rv = word.Link_count; + } + else { + rv = word_count_wkr.Get_top_10(ctx, word_tbl, sub); + } + ctx.Cache__word_counts.Add_bry_int(sub.Raw, rv); + return rv; + } + public static boolean Matches(Srch_crt_itm node, Srch_text_parser text_parser, Xol_case_mgr case_mgr, byte[] ttl) { + byte[] ttl_lower = case_mgr.Case_build_lower(Xoa_ttl.Replace_unders(ttl)); + byte[][] ttl_words = text_parser.Parse_to_bry_ary(Bool_.Y, ttl); + return Matches(node, ttl_lower, ttl_words); + } + private static boolean Matches(Srch_crt_itm node, byte[] ttl_lower, byte[][] ttl_words) { + int tid = node.Tid; + byte[] raw = node.Raw; + Srch_crt_itm[] subs = node.Subs; + int subs_len = subs.length; + switch (tid) { + case Srch_crt_itm.Tid__word: { + int len = ttl_words.length; + for (int i = 0; i < len; ++i) { + byte[] word = ttl_words[i]; + if (node.Sql_data.Pattern == null) { + if (Bry_.Eq(word, raw)) return true; + } + else { + if (node.Sql_data.Pattern.Match(word)) return true; + } + } + return false; + } + case Srch_crt_itm.Tid__word_quote: return Bry_find_.Find_fwd(ttl_lower, raw) != Bry_find_.Not_found;// note that raw does not have quotes; EX: "B*" -> B* + case Srch_crt_itm.Tid__not: return !Matches(subs[0], ttl_lower, ttl_words); + case Srch_crt_itm.Tid__or: { + for (int i = 0; i < subs_len; ++i) { + Srch_crt_itm sub = subs[i]; + if (Matches(sub, ttl_lower, ttl_words)) + return true; + } + return false; + } + case Srch_crt_itm.Tid__and: + for (int i = 0; i < subs_len; ++i) { + Srch_crt_itm sub = subs[i]; + if (!Matches(sub, ttl_lower, ttl_words)) + return false; + } + return true; + case Srch_crt_itm.Tid__invalid: return false; + default: throw Err_.new_unhandled(tid); + } + } +} +class Srch_word_row_sorter__link_count implements gplx.core.lists.ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + Srch_word_row lhs = (Srch_word_row)lhsObj; + Srch_word_row rhs = (Srch_word_row)rhsObj; + return -Int_.Compare(lhs.Link_count, rhs.Link_count); + } + public static final Srch_word_row_sorter__link_count Desc = new Srch_word_row_sorter__link_count(); Srch_word_row_sorter__link_count() {} +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_link_wkr_sql.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_link_wkr_sql.java index a27517de8..ce95cb16c 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_link_wkr_sql.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_link_wkr_sql.java @@ -13,3 +13,142 @@ 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.addons.wikis.searchs.searchers.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import gplx.dbs.*; import gplx.dbs.stmts.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.wikis.searchs.searchers.crts.*; import gplx.xowa.addons.wikis.searchs.dbs.*; import gplx.xowa.addons.wikis.searchs.searchers.rslts.*; +public class Srch_link_wkr_sql { + private final Db_stmt_mgr stmt_mgr = new Db_stmt_mgr(); + public void Clear() {stmt_mgr.Clear();} + public String Dbg(Srch_search_ctx ctx, Db_attach_mgr attach_mgr, Srch_crt_itm sql_root) { + stmt_mgr.Mode_is_stmt_(Bool_.N); + Init(ctx, attach_mgr, sql_root); + stmt_mgr.Mode_is_stmt_(Bool_.Y); + String rv = Write(ctx, attach_mgr); + stmt_mgr.Clear(); + return rv; + } + public void Init(Srch_search_ctx ctx, Db_attach_mgr attach_mgr, Srch_crt_itm sql_root) { + synchronized (Fmt__link) { // THREAD:must synchronized on static Object, else 2 wikis with simultaneous search commands will write to same fmtr + stmt_mgr.Bfr().Add(Bry__page__bgn); + Bld_where(ctx, sql_root); + stmt_mgr.Bfr().Add(Bry__page__end); + } + } + public String Write(Srch_search_ctx ctx, Db_attach_mgr attach_mgr) { + String sql = stmt_mgr.Bfr().To_str_and_clear(); + try { + Gfo_usr_dlg_.Instance.Log_many("", "", "search.resolving; phrase=~{0} score_bgn=~{1} score_end=~{2}", ctx.Qry.Phrase.Orig, ctx.Score_rng.Score_bgn(), ctx.Score_rng.Score_end()); + sql = attach_mgr.Resolve_sql(sql); + } + catch (Exception e) { + Gfo_usr_dlg_.Instance.Log_many("", "", "search.resolving err; phrase=~{0} score_bgn=~{1} score_end=~{2} err=~{3}", ctx.Qry.Phrase.Orig, ctx.Score_rng.Score_bgn(), ctx.Score_rng.Score_end(), Err_.Message_gplx_log(e)); + } + return sql; + } + public Db_stmt Make(Srch_search_ctx ctx, Db_attach_mgr attach_mgr, Db_conn cur_link_conn) { + String sql = Write(ctx, attach_mgr); + attach_mgr.Attach(); + return cur_link_conn.Stmt_sql(sql); + } + public void Fill(Db_stmt stmt) { + stmt_mgr.Fill_stmt_and_clear(stmt); + } + private void Bld_where(Srch_search_ctx ctx, Srch_crt_itm node) { + switch (node.Tid) { + case Srch_crt_itm.Tid__word: + case Srch_crt_itm.Tid__word_quote: // NOTE: quoted word is treated as Eq, except Eq_id is set to "lowest" word_id + Bld_leaf(ctx, node); + break; + case Srch_crt_itm.Tid__or: + case Srch_crt_itm.Tid__and: + Srch_crt_itm[] subs = node.Subs; + int subs_len = subs.length; + for (int i = 0; i < subs_len; ++i) { + Srch_crt_itm sub = subs[i]; + if (sub.Tid == Srch_crt_itm.Tid__not) continue; // do not build sql for NOT itms; EX: a + (b, c) + -d + if (i != 0) + stmt_mgr.Bfr().Add_str_a7(node.Tid == Srch_crt_itm.Tid__and ? "INTERSECT\n" : "UNION\n"); + Bld_where(ctx, sub); + } + break; + case Srch_crt_itm.Tid__not: break; // never check database for NOT node + case Srch_crt_itm.Tid__invalid: break; // should not happen + default: throw Err_.new_unhandled_default(node.Tid); + } + } + private void Bld_leaf(Srch_search_ctx ctx, Srch_crt_itm node) { + int node_idx = node.Idx; + int node_sql_tid = node.Sql_data.Tid; + int score_bgn = ctx.Score_rng.Score_bgn(); + int score_end = ctx.Score_rng.Score_end(); + Srch_word_tbl word_tbl = ctx.Tbl__word; + stmt_mgr.Bfr().Add(Bry__link__bgn); + switch (node_sql_tid) { + case Srch_crt_sql.Tid__eq: // EX: "earth" + stmt_mgr.Add_crt_int(word_tbl.fld_id, node.Sql_data.Eq_id); + stmt_mgr.Write_fmt(Fmt__word_id); + break; + case Srch_crt_sql.Tid__rng: // EX: "earth*" + stmt_mgr.Add_var_many(node_idx, "AND ", "search_word__word_text__link_score_max__link_score_min"); + stmt_mgr.Add_crt_str(word_tbl.fld_text, node.Sql_data.Rng_bgn); + stmt_mgr.Add_crt_str(word_tbl.fld_text, node.Sql_data.Rng_end); + stmt_mgr.Add_crt_int(word_tbl.fld_link_score_max, score_bgn); + stmt_mgr.Add_crt_int(word_tbl.fld_link_score_min, score_end); + stmt_mgr.Write_fmt(Fmt__word_text__rng); + break; + case Srch_crt_sql.Tid__like: // EX: "*earth" + stmt_mgr.Add_var_many(node_idx, "WHERE ", "search_word__link_score_max__link_score_min"); + stmt_mgr.Add_crt_int(word_tbl.fld_link_score_max, score_bgn); + stmt_mgr.Add_crt_int(word_tbl.fld_link_score_min, score_end); + stmt_mgr.Add_crt_str(word_tbl.fld_text, node.Sql_data.Like); + stmt_mgr.Write_fmt(Fmt__word_text__like); + break; + } + Srch_link_tbl link_tbl = ctx.Tbl__link__ary[0]; + stmt_mgr.Add_crt_int(link_tbl.fld_link_score, score_bgn); + stmt_mgr.Add_crt_int(link_tbl.fld_link_score, score_end); + stmt_mgr.Write_fmt(Fmt__link); + } + private static final byte[] + Bry__page__bgn = Bry_.new_a7(String_.Concat_lines_nl_skip_last + ( "SELECT p.page_id, p.page_namespace, p.page_title, p.page_len, p.page_score, p.page_redirect_id" + , "FROM page p" + , "WHERE p.page_id IN" + , "(" + , "" + )) + , Bry__page__end = Bry_.new_a7(")\n") + , Bry__link__bgn = Bry_.new_a7(String_.Concat_lines_nl_skip_last + ( "SELECT l.page_id" + , "FROM search_link l INDEXED BY search_link__word_id__link_score" + , "WHERE " + )) + ; + private static final String + Str__link__end = String_.Concat_lines_nl_skip_last + ( "AND l.link_score >= ~{score_bgn}" + , "AND l.link_score < ~{score_end}" + , "" + ) + , Str__word__text__bgn = String_.Concat_lines_nl_skip_last + ( "l.word_id IN" + , "(" + , "SELECT w~{uid}.word_id" + , "FROM search_word w~{uid} INDEXED BY ~{index}" + , "" + ) + , Str__word__text__rng = "WHERE w~{uid}.word_text >= ~{word_bgn} AND w~{uid}.word_text < ~{word_end}\n" + , Str__word__text__like = "AND w~{uid}.word_text LIKE ~{word_like} ESCAPE '|'\n" + , Str__word__text__mnx = String_.Concat_lines_nl_skip_last + ( "~{and} w~{uid}.link_score_max >= ~{score_bgn}" + , "AND w~{uid}.link_score_min < ~{score_end}" + , "" + ); + private static final Bry_fmt + Fmt__link = Bry_fmt.Auto(Str__link__end) + , Fmt__word_id = Bry_fmt.Auto("l.word_id = ~{word_uid}\n") + , Fmt__word_text__rng = Bry_fmt.New(Str__word__text__bgn + Str__word__text__rng + Str__word__text__mnx + ")\n", "uid", "and", "index", "word_bgn", "word_end", "score_bgn", "score_end") + , Fmt__word_text__like = Bry_fmt.New(Str__word__text__bgn + Str__word__text__mnx + Str__word__text__like + ")\n", "uid", "and", "index", "score_bgn", "score_end", "word_like") + ; + public static final byte Like_escape_byte = Byte_ascii.Pipe; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_link_wkr_sql_tst.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_link_wkr_sql_tst.java index a27517de8..fb54670e4 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_link_wkr_sql_tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_link_wkr_sql_tst.java @@ -13,3 +13,25 @@ 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.addons.wikis.searchs.searchers.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import org.junit.*; import gplx.xowa.addons.wikis.searchs.parsers.*; import gplx.xowa.addons.wikis.searchs.searchers.crts.*; import gplx.xowa.addons.wikis.searchs.searchers.crts.visitors.*; +public class Srch_link_wkr_sql_tst { + private final Srch_link_wkr_sql_fxt fxt = new Srch_link_wkr_sql_fxt(); + @Test public void Rng() { + fxt.Run__search("a").Test__eq + ( "select" + ); + } +} +class Srch_link_wkr_sql_fxt { + private final Srch_link_wkr_sql link_wkr = new Srch_link_wkr_sql(); + public Srch_link_wkr_sql_fxt Run__search(String search) { + // attach_mgr.Init(cur_link_tbl.conn, new Db_attach_itm("page_db", ctx.Db__core.Conn()), new Db_attach_itm("word_db", ctx.Tbl__word.conn)); + // link_wkr.Init(ctx, attach_mgr); + // this.actl_sql = link_wkr.Write(ctx, attach_mgr); + link_wkr.Clear(); + return this; + } + public void Test__eq(String... v) { + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_page_tbl_wkr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_page_tbl_wkr.java index a27517de8..5d86f20df 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_page_tbl_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_page_tbl_wkr.java @@ -13,3 +13,59 @@ 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.addons.wikis.searchs.searchers.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.wikis.searchs.searchers.rslts.*; +import gplx.xowa.addons.wikis.searchs.searchers.crts.*; +public class Srch_page_tbl_wkr { + private final Xowd_page_itm tmp_page_row = new Xowd_page_itm(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + public void Search(Srch_search_ctx ctx, Srch_rslt_cbk cbk) { + byte[] search_raw = To_bry_or_null(tmp_bfr, ctx.Scanner_syms.Wild(), ctx.Crt_mgr); // build up search String but handle escapes "\+" -> "+" + if (search_raw == null) return; // search-term has not or symbols; EX: "earth -history"; "(earth & history)" + Gfo_usr_dlg_.Instance.Log_many("", "", "search.search by page_tbl; search=~{0}", search_raw); + Xoa_ttl ttl = ctx.Wiki.Ttl_parse(search_raw); if (ttl == null) return; + Xowd_page_tbl page_tbl = ctx.Tbl__page; + if (ctx.Cxl.Canceled()) return; + if (page_tbl.Select_by_ttl(tmp_page_row, ttl.Ns(), ttl.Page_db())) { + if (ctx.Cxl.Canceled()) return; + Srch_rslt_row row = Srch_rslt_row.New(ctx.Wiki_domain, ttl, tmp_page_row.Id(), tmp_page_row.Text_len(), ctx.Addon.Db_mgr().Cfg().Link_score_max() * 3, tmp_page_row.Redirect_id()); + if (Srch_rslt_list_.Add_if_new(ctx, ctx.Rslts_list, row)) { + Srch_rslt_list_.Get_redirect_ttl(page_tbl, tmp_page_row, row); + ctx.Rslts_list.Add(row); + ctx.Rslts_list.Ids__add(row.Page_id, row); + cbk.On_rslts_found(ctx.Qry, ctx.Rslts_list, 0, 1); + ctx.Rslts_list.Rslts_are_first = false; + } + } + } + public static byte[] To_bry_or_null(Bry_bfr bfr, byte wildcard_byte, Srch_crt_mgr mgr) { + if (mgr.Words_tid == Srch_crt_mgr.Tid__mixed) return null; + Srch_crt_tkn[] tkns = mgr.Tkns; + int len = tkns.length; + for (int i = 0; i < len; ++i) { + Srch_crt_tkn tkn = tkns[i]; + switch (tkn.Tid) { + case Srch_crt_tkn.Tid__word: + case Srch_crt_tkn.Tid__word_w_quote: + break; + default: + return null; + } + if (i != 0) bfr.Add_byte_space(); + byte[] tkn_raw = tkn.Val; + int tkn_raw_len = tkn_raw.length; + int wildcard_pos = Bry_find_.Find_fwd(tkn_raw, wildcard_byte, 0, tkn_raw_len); + if (wildcard_pos != Bry_find_.Not_found) { + int last_pos = tkn_raw_len - 1; + if (wildcard_pos == last_pos) + bfr.Add_mid(tkn_raw, 0, last_pos); + else + return null; + } + else + bfr.Add(tkn.Val); + } + return bfr.To_bry_and_clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_page_tbl_wkr_tst.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_page_tbl_wkr_tst.java index a27517de8..3e6fa88c8 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_page_tbl_wkr_tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_page_tbl_wkr_tst.java @@ -13,3 +13,33 @@ 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.addons.wikis.searchs.searchers.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import org.junit.*; import gplx.xowa.addons.wikis.searchs.parsers.*; import gplx.xowa.addons.wikis.searchs.searchers.crts.*; import gplx.xowa.addons.wikis.searchs.searchers.crts.visitors.*; +public class Srch_page_tbl_wkr_tst { + private final Srch_page_tbl_wkr_fxt fxt = new Srch_page_tbl_wkr_fxt(); + @Test public void Word__one() {fxt.Test__to_bry_or_null("a" , "a");} + @Test public void Word__many() {fxt.Test__to_bry_or_null("a b c" , "a b c");} + @Test public void Wild__end() {fxt.Test__to_bry_or_null("a*" , "a");} + @Test public void Wild__both() {fxt.Test__to_bry_or_null("a*b*" , null);} + @Test public void Quote() {fxt.Test__to_bry_or_null("\"a b\"" , "a b");} + @Test public void Quote__mixed() {fxt.Test__to_bry_or_null("a \"b \"\" c\" d" , "a b \" c d");} + @Test public void Escape() {fxt.Test__to_bry_or_null("a\\+" , "a+");} + @Test public void Not() {fxt.Test__to_bry_or_null("a -b" , null);} + @Test public void And() {fxt.Test__to_bry_or_null("a + b" , null);} + @Test public void Or() {fxt.Test__to_bry_or_null("a , b" , null);} + @Test public void Parens() {fxt.Test__to_bry_or_null("(a)" , null);} +} +class Srch_page_tbl_wkr_fxt { + private final Srch_crt_parser crt_parser; + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + public Srch_page_tbl_wkr_fxt() { + crt_parser = new Srch_crt_parser(Srch_crt_scanner_syms.Dflt); + Srch_text_parser text_parser = new Srch_text_parser(); + text_parser.Init_for_ttl(gplx.xowa.langs.cases.Xol_case_mgr_.A7()); + } + public void Test__to_bry_or_null(String src_str, String expd) { + byte[] src_bry = Bry_.new_a7(src_str); + Srch_crt_mgr crt_mgr = crt_parser.Parse_or_invalid(src_bry); + Tfds.Eq(expd, String_.new_u8(Srch_page_tbl_wkr.To_bry_or_null(tmp_bfr, Srch_search_addon.Wildcard__star, crt_mgr))); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_word_count_wkr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_word_count_wkr.java index a27517de8..fb91c4ea5 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_word_count_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/searchers/wkrs/Srch_word_count_wkr.java @@ -13,3 +13,64 @@ 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.addons.wikis.searchs.searchers.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*; +import gplx.dbs.*; import gplx.dbs.stmts.*; import gplx.dbs.percentiles.*; +import gplx.xowa.addons.wikis.searchs.dbs.*; import gplx.xowa.addons.wikis.searchs.searchers.crts.*; +class Srch_word_count_wkr extends Percentile_select_base { + private Srch_word_tbl word_tbl; private Srch_db_cfg db_cfg; + private Srch_crt_itm sub; + private int total_link_count, rows_read; private boolean score_too_low; + private final Db_stmt_mgr stmt_mgr = new Db_stmt_mgr(); private Db_stmt stmt; + public int Get_top_10(Srch_search_ctx ctx, Srch_word_tbl word_tbl, Srch_crt_itm sub) { + super.cxl = ctx.Cxl; + this.db_cfg = ctx.Addon.Db_mgr().Cfg(); + super.rng = new Percentile_rng().Init(db_cfg.Word_count(), db_cfg.Link_count_score_max()); + super.rng.Select_init(10, Percentile_rng.Score_null, Percentile_rng.Score_null, 0); + super.rng_log = new Percentile_rng_log(db_cfg.Link_count_score_max()); + + rng_log.Init(sub.Raw, 10); + this.word_tbl = word_tbl; + this.sub = sub; + this.total_link_count = 0; + this.rows_read = 0; + this.score_too_low = false; + try {this.Select();} + finally {stmt = Db_stmt_.Rls(stmt);} + if (score_too_low) return Srch_db_cfg_.Link_count_score_cutoff; + else if (rows_read == 1) return total_link_count * 2; + else return total_link_count; + } + @Override protected Db_rdr Rdr__init() { + stmt_mgr.Add_crt_int(word_tbl.fld_link_count_score, rng.Score_bgn()); + stmt_mgr.Add_crt_int(word_tbl.fld_link_count_score, rng.Score_end()); + stmt_mgr.Add_crt_str(word_tbl.fld_text, sub.Sql_data.Rng_bgn); + stmt_mgr.Add_crt_str(word_tbl.fld_text, sub.Sql_data.Rng_end); + if (stmt == null) stmt = stmt_mgr.Make_stmt(word_tbl.conn, Fmt__main); + stmt_mgr.Fill_stmt_and_clear(stmt); + return stmt.Exec_select__rls_manual(); + } + @Override protected boolean Row__read(Db_rdr rdr) { + if (!rdr.Move_next()) return false; + int cur_link_count = rdr.Read_int(word_tbl.fld_link_count); + total_link_count += cur_link_count; + ++rows_read; + return true; + } + @Override protected boolean Found_enough() { + if (rng.Score_bgn() <= db_cfg.Link_count_score_cutoff()) score_too_low = true; + return rows_read > 0 || score_too_low; + } + @Override protected void Rdr__done(boolean rslts_are_enough, boolean rslts_are_done) { + if (rslts_are_enough) Gfo_usr_dlg_.Instance.Log_many("", "", "search.word_count; rng=~{0}", rng_log.To_str_and_clear()); + } + private static Bry_fmt + Fmt__main = Bry_fmt.Auto(String_.Concat_lines_nl_skip_last + ( "SELECT w.link_count" + , "FROM search_word w INDEXED BY search_word__link_count_score__word_text" + , "WHERE w.link_count_score >= ~{score_min}" + , "AND w.link_count_score < ~{score_max}" + , "AND w.word_text >= ~{rng_bgn}" + , "AND w.word_text < ~{rng_end}" + , "LIMIT 1" + )); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_qarg_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_qarg_mgr.java index a27517de8..271f6b816 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_qarg_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_qarg_mgr.java @@ -13,3 +13,50 @@ 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.addons.wikis.searchs.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.core.net.*; import gplx.core.net.qargs.*; +import gplx.xowa.addons.wikis.searchs.searchers.*; import gplx.xowa.addons.wikis.searchs.searchers.rslts.*; +public class Srch_qarg_mgr { + public Srch_qarg_mgr(Srch_ns_mgr ns_mgr) {this.ns_mgr = ns_mgr;} + public Srch_ns_mgr Ns_mgr() {return ns_mgr;} private final Srch_ns_mgr ns_mgr; + public byte[] Search_raw() {return search_raw;} private byte[] search_raw; public Srch_qarg_mgr Search_raw_(byte[] v) {search_raw = v; return this;} + public int Slab_idx() {return slab_idx;} private int slab_idx; + public byte[] Cancel() {return cancel;} private byte[] cancel; + public Srch_qarg_mgr Clear() { + ns_mgr.Clear(); + this.search_raw = null; + this.slab_idx = 0; + this.cancel = null; + return this; + } + public void Parse(Gfo_qarg_itm[] qargs_ary) { + if (qargs_ary == null) return; + int len = qargs_ary.length; + for (int i = 0; i < len; ++i) { + Gfo_qarg_itm qarg = qargs_ary[i]; + byte[] key = qarg.Key_bry(); + byte tid = qarg_regy.Get_as_byte_or(key, Byte_.Max_value_127); + if (tid == Byte_.Max_value_127) { // unknown qarg; check for ns*; EX: &ns0=1&ns8=1; NOTE: lowercase only + if (Bry_.Has_at_bgn(key, Ns_bry)) + ns_mgr.Add_by_parse(key, qarg.Val_bry()); + } + else { + switch (tid) { + case Uid__search: this.search_raw = Bry_.Replace(qarg.Val_bry(), Byte_ascii.Plus, Byte_ascii.Space); break; + case Uid__slab_idx: this.slab_idx = Bry_.To_int_or(qarg.Val_bry(), 0); break; + case Uid__cancel: this.cancel = qarg.Val_bry(); break; + default: break; + } + } + } + ns_mgr.Add_main_if_empty(); + } + private static byte[] Ns_bry = Bry_.new_a7("ns"); + private static final byte Uid__search = 0, Uid__slab_idx = 1, Uid__cancel = 2; + public static final byte[] Bry__slab_idx = Bry_.new_a7("xowa_page_index"), Bry__cancel = Bry_.new_a7("cancel"); + private static final Hash_adp_bry qarg_regy = Hash_adp_bry.ci_a7() + .Add_str_byte("search" , Uid__search) + .Add_bry_byte(Bry__slab_idx , Uid__slab_idx) + .Add_bry_byte(Bry__cancel , Uid__cancel) + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_cfg.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_cfg.java index a27517de8..d8b2ab444 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_cfg.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_cfg.java @@ -13,3 +13,55 @@ 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.addons.wikis.searchs.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.core.net.*; import gplx.core.net.qargs.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.domains.crts.*; +import gplx.xowa.addons.wikis.searchs.searchers.*; +public class Srch_special_cfg implements Gfo_invk { + private final Xow_domain_crt_kv_itm_mgr multi_wikis_mgr = new Xow_domain_crt_kv_itm_mgr(); private byte[] multi_wikis_bry = Dflt_multi_wikis_bry; + private final Xow_domain_crt_kv_itm_mgr multi_sorts_mgr = new Xow_domain_crt_kv_itm_mgr(); private byte[] multi_sorts_bry = Dflt_multi_sorts_bry; + public Srch_special_cfg() { + multi_wikis_mgr.Parse_as_itms(multi_wikis_bry); + multi_sorts_mgr.Parse_as_arys(multi_sorts_bry); + ns_mgr.Add_main_if_empty(); + } + public void Init_by_kit(Xoae_app app, gplx.gfui.kits.core.Gfui_kit kit) { + app.Cfg().Bind_many_app(this, Cfg__results_per_page, Cfg__async_db, Cfg__auto_wildcard, Cfg__multi_wikis, Cfg__multi_sorts); + } + public int Results_per_page() {return results_per_page;} private int results_per_page = 100; + public boolean Async_db() {return async_db;} private boolean async_db = true; + public boolean Auto_wildcard() {return auto_wildcard;} private boolean auto_wildcard = false; // automatically add wild-card; EX: Earth -> *Earth* + public Srch_ns_mgr Ns_mgr() {return ns_mgr;} private final Srch_ns_mgr ns_mgr = new Srch_ns_mgr(); + public Xow_domain_crt_itm Multi_wikis_crt (Xow_domain_itm cur_domain) {return multi_wikis_mgr.Find_itm(cur_domain, cur_domain);} + public Xow_domain_crt_itm[] Multi_sorts_crt (Xow_domain_itm cur_domain) {return multi_sorts_mgr.Find_ary(cur_domain, cur_domain);} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Cfg__results_per_page)) results_per_page = m.ReadInt("v"); + else if (ctx.Match(k, Cfg__async_db)) async_db = m.ReadYn("v"); + else if (ctx.Match(k, Cfg__auto_wildcard)) auto_wildcard = m.ReadYn("v"); + else if (ctx.Match(k, Cfg__multi_wikis)) { + byte[] multi_wikis_temp = m.ReadBry("v"); + if (multi_wikis_mgr.Parse_as_itms(multi_wikis_temp)) { + this.multi_wikis_bry = multi_wikis_temp; + } + } + else if (ctx.Match(k, Cfg__multi_sorts)) { + byte[] multi_sorts_temp = m.ReadBry("v"); + if (multi_sorts_mgr.Parse_as_arys(multi_sorts_temp)) { + this.multi_sorts_bry = multi_sorts_temp; + } + } + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Cfg__results_per_page = "xowa.addon.search.special.results_per_page" + , Cfg__async_db = "xowa.addon.search.special.async_db" + , Cfg__auto_wildcard = "xowa.addon.search.special.auto_wildcard" + , Cfg__multi_wikis = "xowa.addon.search.special.multi_wikis" + , Cfg__multi_sorts = "xowa.addon.search.special.multi_sorts" + ; + public static final byte[] + Dflt_multi_wikis_bry = Bry_.new_a7("|") + , Dflt_multi_sorts_bry = Bry_.new_a7("|,*.wikipedia,*.wikivoyage,*.wiktionary,*.wikisource,*.wikiquote,*.wikibooks,*.wikiversity,*.wikinews") + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_cmd.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_cmd.java index a27517de8..ac1147c65 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_cmd.java @@ -13,3 +13,91 @@ 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.addons.wikis.searchs.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.core.threads.*; +import gplx.xowa.guis.cbks.js.*; import gplx.xowa.guis.views.*; +import gplx.xowa.addons.wikis.searchs.specials.htmls.*; import gplx.xowa.addons.wikis.searchs.searchers.*; import gplx.xowa.addons.wikis.searchs.searchers.rslts.*; +public class Srch_special_cmd implements Gfo_invk, Srch_rslt_cbk, Xog_tab_close_lnr { + private final Srch_special_searcher mgr; private final Srch_search_qry qry; + public final Xow_wiki wiki; private final Xog_tab_close_mgr tab_close_mgr; private final Xog_js_wkr js_wkr; + private Srch_html_row_wkr html_row_wkr; private final boolean async; + public final byte[] key; private boolean canceled = false; + public Srch_special_cmd(Srch_special_searcher mgr, Srch_search_qry qry, Xow_wiki wiki, Xog_tab_close_mgr tab_close_mgr, Xog_js_wkr js_wkr, byte[] key, boolean search_is_async) { + this.mgr = mgr; this.qry = qry; this.wiki = wiki; this.tab_close_mgr = tab_close_mgr; this.js_wkr = js_wkr; this.key = key; + this.async = wiki.App().Mode().Tid_is_gui() && search_is_async; + } + public void On_cancel() { + canceled = true; + Xoa_app_.Usr_dlg().Prog_many("", "", "search canceled: key=~{0}", key); + this.Hide_cancel_btn(); + } + public void Search() { + Srch_html_row_bldr html_row_bldr = new Srch_html_row_bldr(new gplx.xowa.htmls.core.htmls.utls.Xoh_lnki_bldr(wiki.App(), wiki.Html__href_wtr())); + this.html_row_wkr = new Srch_html_row_wkr(html_row_bldr, js_wkr, qry.Slab_end - qry.Slab_bgn, wiki.Domain_bry()); // NOTE: must set member reference for html_row_wkr else On_rslts_found will fail with null ref when async_db is false; DATE:2016-12-22 + if (async) { // NOTE: async useful with multiple wikis; allows parallel searches; + Thread_adp_.Start_by_key(gplx.xowa.apps.Xoa_thread_.Key_special_search_db, this, Invk_search_db); + } + else + Search_db(); + } + private void Search_db() { + synchronized (mgr) { // THREAD: needed else multiple Special:Search pages will fail at startup; DATE:2016-03-27 + tab_close_mgr.Add(this); + // DEPRECATE: causes search to fail when using go back / go forward; DELETE:2016-05; DATE:2016-03-27 + // if (async) { + // while (!page.Html_data().Mode_wtxt_shown()) // NOTE:must check to see if page is shown; else async can happen first, and then be overwritten by page_showing; DATE:2015-04-26 + // Thread_adp_.Sleep(100); + // } + Srch_search_addon.Get(wiki).Search(qry, this); + mgr.Search__done(this); + if (canceled) return; // NOTE: must check else throws SWT exception + this.Hide_cancel_btn(); + } + Xoa_app_.Usr_dlg().Prog_many("", "", ""); + } + private void Hide_cancel_btn() {Thread_adp_.Start_by_key(gplx.xowa.apps.Xoa_thread_.Key_special_search_cancel, this, Invk_hide_cancel);} + private void Hide_cancel_btn_async() {js_wkr.Html_atr_set("xowa_cancel_" + wiki.Domain_str(), "style", "display:none;");} + public void On_rslts_found(Srch_search_qry qry, Srch_rslt_list rslts_list, int rslts_bgn, int rslts_end) { + if (rslts_list.Rslts_are_first) { + if (rslts_bgn > qry.Slab_bgn) { + for (int i = qry.Slab_bgn; i < rslts_bgn; ++i) { + Srch_rslt_row row = rslts_list.Get_at(i); + html_row_wkr.On_rslt_found(row); + } + } + } + for (int i = rslts_bgn; i < rslts_end; ++i) { + if (i < qry.Slab_bgn) continue; // do not write row if < slab_bgn; occurs when restarting app directly at page > 1; EX: 11-20 requested; 1-20 returned; do not write 1-10; + if (i >= qry.Slab_end) break; // do not write row if > slab_end; occurs when paging forward; EX: 01-10 requested; 1-12 retrieved; do not write 11, 12 + Srch_rslt_row row = rslts_list.Get_at(i); + html_row_wkr.On_rslt_found(row); + } + } + public boolean When_close(Xog_tab_itm tab, Xoa_url url) { + if (url != Xoa_url.Null) { // not called by close_tab (Ctrl+W) + byte[] cancel_arg = url.Qargs_mgr().Get_val_bry_or(Srch_qarg_mgr.Bry__cancel, null); + if (cancel_arg != null) return true; // cancel arg exists; assume tab is not being closed; note that cancel will be processed by Xows_page__special; DATE:2015-04-30 + } + this.On_cancel(); + return true; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_search_db)) Search_db(); + else if (ctx.Match(k, Invk_hide_cancel)) Hide_cancel_btn_async(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_search_db = "search_db", Invk_hide_cancel = "hide_cancel"; +} +/* +NOTE:show_existing. code needed to show A1 +EX: search="A*": "A" has 400 words; "A1" has 1; +. search 1-20 returns 20 words for A and 1 word for A1. +.. the 1st A word has a len of 999 and the 20th A word has a length of 900; +.. A1 has a length of 799 +. search 21-40 returns 20 words for A +.. the 21st word has a len of 899 and the 40th has a len of 800 +.. A1 should show up briefly, and then get pushed off screen by 21-40 +. search 61-40 returns 20 words for A +.. A1 must show up +*/ diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_page.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_page.java index a27517de8..0174f472a 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_page.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_page.java @@ -13,3 +13,99 @@ 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.addons.wikis.searchs.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.core.primitives.*; import gplx.xowa.addons.wikis.searchs.specials.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.domains.crts.*; +import gplx.xowa.specials.*; import gplx.xowa.addons.wikis.searchs.searchers.*; import gplx.xowa.addons.wikis.searchs.searchers.cbks.*; +import gplx.xowa.addons.wikis.searchs.gui.htmlbars.*; +public class Srch_special_page implements Xow_special_page { + public Xow_special_meta Special__meta() {return Xow_special_meta_.Itm__search;} + public void Special__gen(Xow_wiki wikii, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl) { + Xowe_wiki wiki = (Xowe_wiki)wikii; Xoae_page page = (Xoae_page)pagei; + Srch_special_cfg search_cfg = wiki.Appe().Addon_mgr().Itms__search__special(); + Xow_domain_itm[] search_domain_ary = Get_domains(wiki.Appe(), search_cfg, wiki.Domain_itm()); + + // get args from urls while applying defaults from search_cfg + Srch_qarg_mgr qargs_mgr = new Srch_qarg_mgr(wiki.App().Addon_mgr().Itms__search__special().Ns_mgr()); + qargs_mgr.Clear(); + + // parse args_default_str + String args_default_str = wiki.App().Cfg().Get_str_wiki_or(wiki, Srch_search_mgr.Cfg__args_default, ""); + if (String_.Len_gt_0(args_default_str)) { + byte[] bry = Bry_.new_a7("http://x.org/a?" + args_default_str); + gplx.core.net.Gfo_url tmp_url = wiki.App().User().Wikii().Utl__url_parser().Url_parser().Parse(bry, 0, bry.length); + qargs_mgr.Parse(tmp_url.Qargs()); + } + qargs_mgr.Parse(url.Qargs_ary()); + qargs_mgr.Ns_mgr().Add_main_if_empty(); + + // get search_raw + byte[] search_raw = qargs_mgr.Search_raw(); + if (search_raw == null) { // search is not in qarg; EX:Special:Search?search=Earth + search_raw = ttl.Leaf_txt_wo_qarg(); // assume search is in leaf; EX: Special:Search/Earth + qargs_mgr.Search_raw_(search_raw); + } + if (Bry_.Len_eq_0(search_raw)) return; // emptry String; exit now, else null ref error; DATE:2015-08-11 + + // get page directly from url + boolean fulltext_invoked = url.Qargs_mgr().Match(Qarg__fulltext, Qarg__fulltext__y); + Xoa_ttl search_ttl = Xoa_ttl.Parse(wiki, search_raw); + Xoae_page search_page = page; + if ( !fulltext_invoked + && !Bry_.Eq(search_raw, Xow_special_meta_.Itm__search.Ttl_bry())) // do not lookup self else stack overflow; happens when going directly to Special:Search (from history) + search_page = wiki.Data_mgr().Load_page_by_ttl(search_ttl); // try to find page; EX:Special:Search?search=Earth -> en.w:Earth; needed for search suggest + + // page not found, or explicit_search invoked + if (search_page.Db().Page().Exists_n() || fulltext_invoked) { + Srch_special_searcher search_mgr = new Srch_special_searcher(wiki.Appe().Wiki_mgr()); + if (qargs_mgr.Cancel() != null) { // cancel any existing searches + search_mgr.Search__cancel(qargs_mgr.Cancel()); + page.Tab_data().Cancel_show_y_(); + return; + } + page.Html_data().Html_restricted_n_(); + page.Html_data().Xtn_search_text_(search_raw); + Srch_search_qry qry = Srch_search_qry.New__search_page(search_domain_ary, wiki, wiki.App().Addon_mgr().Itms__search__special().Ns_mgr(), search_cfg.Auto_wildcard(), search_raw, qargs_mgr.Slab_idx(), search_cfg.Results_per_page()); + search_mgr.Search(wiki, page, search_cfg.Async_db(), search_domain_ary, qry); + } + // page found; return it; + else { + wiki.Parser_mgr().Parse(search_page, true); + page.Db().Text().Text_bry_(search_page.Db().Text().Text_bry()); + if (page.Root() != null) // NOTE: null when going from w:Earth -> q:Earth; DATE:2013-03-20 + page.Root().Data_htm_(search_page.Root().Data_htm()); + Xoa_url redirect_url = Xoa_url.New(wiki, search_ttl); + page.Ttl_(search_ttl).Url_(redirect_url); + page.Redirect_trail().Itms__add__article(redirect_url, search_ttl, null); + } + } + public static final byte Match_tid_all = 0, Match_tid_bgn = 1; + public static final byte Version_null = 0, Version_1 = 1, Version_2 = 2; + private static final byte[] Qarg__fulltext = Bry_.new_a7("fulltext"), Qarg__fulltext__y = Bry_.new_a7("y"); + private static Xow_domain_itm[] Get_by_crt(gplx.xowa.wikis.xwikis.Xow_xwiki_mgr xwiki_mgr, Xow_domain_itm cur, gplx.xowa.wikis.domains.crts.Xow_domain_crt_itm crt) { + List_adp rv = List_adp_.New(); + int len = xwiki_mgr.Len(); + for (int i = 0; i < len; ++i) { + gplx.xowa.wikis.xwikis.Xow_xwiki_itm xwiki = xwiki_mgr.Get_at(i); + if ( !xwiki.Offline() // note that filters are broad (*.wiktionary); skip offline wikis which won't be available on system + && xwiki.Domain_tid() != Xow_domain_tid_.Tid__home) // note that home is marked "offline" so it won't show up in wikis sidebar + continue; + Xow_domain_itm domain_itm = Xow_domain_itm_.parse(xwiki.Domain_bry()); + if (crt.Matches(cur, domain_itm)) rv.Add(domain_itm); + } + return (Xow_domain_itm[])rv.To_ary_and_clear(Xow_domain_itm.class); + } + private static Xow_domain_itm[] Get_domains(Xoae_app app, Srch_special_cfg cfg, Xow_domain_itm wiki_domain) { + Xow_domain_crt_itm crt = cfg.Multi_wikis_crt(wiki_domain); + Xow_domain_itm[] rv = Get_by_crt(app.Usere().Wiki().Xwiki_mgr(), wiki_domain, crt); + if (rv.length == 0) rv = new Xow_domain_itm[] {wiki_domain}; // default to current if bad input + + Xow_domain_crt_itm[] ary = cfg.Multi_sorts_crt(wiki_domain); + if (ary == null) return Xow_domain_itm_.Ary_empty; // default to no sort if bad input + Xow_domain_sorter__manual sorter = new Xow_domain_sorter__manual(wiki_domain, ary); + Xow_domain_sorter__manual.Sort(sorter, rv); + return rv; + } + + public Xow_special_page Special__clone() {return this;} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_searcher.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_searcher.java index a27517de8..158a9ed78 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_searcher.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_searcher.java @@ -13,3 +13,56 @@ 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.addons.wikis.searchs.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; +import gplx.xowa.addons.wikis.searchs.searchers.*; import gplx.xowa.addons.wikis.searchs.specials.htmls.*; import gplx.xowa.addons.wikis.searchs.searchers.rslts.*; +public class Srch_special_searcher { + private final Xoae_wiki_mgr wiki_mgr; + private final Ordered_hash cancel_hash = Ordered_hash_.New_bry(); + private final Srch_html_page_bldr html_page_bldr = new Srch_html_page_bldr(); + public Srch_special_searcher(Xoae_wiki_mgr wiki_mgr) {this.wiki_mgr = wiki_mgr;} + public void Search(Xow_wiki search_wiki, Xoae_page page, boolean search_is_async, Xow_domain_itm[] domains_ary, Srch_search_qry qry) { + Bry_bfr tmp_bfr = Bry_bfr_.New(); + html_page_bldr.Init_by_wiki(search_wiki, search_wiki.Lang().Num_mgr(), qry); + cancel_hash.Clear(); + int domains_len = domains_ary.length; + for (int i = 0; i < domains_len; ++i) { + Xow_domain_itm domain = domains_ary[i]; + try { + Xowe_wiki wiki = wiki_mgr.Get_by_or_make(domain.Domain_bry()); wiki.Init_assert(); + byte[] key = gplx.langs.htmls.Gfh_utl.Encode_id_as_bry(Bry_.Add(qry.Phrase.Orig, Byte_ascii.Pipe_bry, qry.Ns_mgr.To_hash_key(), Byte_ascii.Pipe_bry, wiki.Domain_bry())); + Srch_rslt_list rslt_list; + if (wiki.App().Mode().Tid_is_http()) { + Srch_rslt_cbk__synchronous cbk_synchronous = new Srch_rslt_cbk__synchronous(); + Srch_search_addon.Get(wiki).Search(qry, cbk_synchronous); + rslt_list = cbk_synchronous.Rslts(); + } + else { + Srch_special_cmd cmd = new Srch_special_cmd(this, qry, wiki, page.Tab_data().Close_mgr(), page.Tab_data().Tab().Html_itm(), key, search_is_async); + cancel_hash.Add(key, cmd); + cmd.Search(); // do search; will return immediately b/c async + rslt_list = new Srch_rslt_list(); // NOTE: create an empty rslt which will be populated by async calls + } + html_page_bldr.Bld_tbl(tmp_bfr, rslt_list, key, wiki.Domain_bry(), search_is_async, qry.Slab_bgn, qry.Slab_end); + } catch (Exception e) {Xoa_app_.Usr_dlg().Warn_many("", "", "search:wiki failed; wiki=~{0} err=~{1}", domain.Domain_str(), Err_.Message_lang(e));} // handle bad wikis, like "en.wikipedia.org-old"; DATE:2015-04-24 + } + + // generate html; note if async, this will just generate the page header + page.Db().Text().Text_bry_(html_page_bldr.Bld_page(tmp_bfr.To_bry_and_clear())); + } + public void Search__done(Srch_special_cmd cmd) { + cancel_hash.Del(cmd.key); + } + public void Search__cancel(byte[] cmd_key) { + Srch_special_cmd cmd = (Srch_special_cmd)cancel_hash.Get_by(cmd_key); // if (cmd == null) return; // ignore false calls to cancel + cmd.On_cancel(); + cancel_hash.Del(cmd.key); + } +} +class Srch_rslt_cbk__synchronous implements Srch_rslt_cbk { + public Srch_rslt_list Rslts() {return rslts;} private Srch_rslt_list rslts; + public void On_rslts_found(Srch_search_qry qry, Srch_rslt_list rslts_list, int rslts_bgn, int rslts_end) { + this.rslts = rslts_list; // just assign it + } + public void On_cancel() {} // synchronous cannot be canceled +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_searcher_tst.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_searcher_tst.java index a27517de8..c7787a1be 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_searcher_tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Srch_special_searcher_tst.java @@ -13,3 +13,209 @@ 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.addons.wikis.searchs.v1s { +// import org.junit.*; using gplx.xowa.wikis.tdbs; using gplx.xowa.wikis.data.tbls; +// public class Xosrh_core_tst { +// @Before public void Init() {fxt.Clear();} private Xos_search_mgr_fxt fxt = new Xos_search_mgr_fxt(); +// @Test public void Basic() { +// fxt.Init_basic(); +// fxt.Test_search_exact("b2", "B2_22", "B2_12", "B2__2"); +// fxt.Test_search_exact("a" , "A___0"); +// fxt.Test_search_exact("b1a"); // missing: mid +// fxt.Test_search_exact("d"); // missing: end +// fxt.Test_search_exact("$"); // missing: bgn +// fxt.Test_search_match_bgn("b*", "B3_23", "B2_22", "B1_21", "B3_13", "B2_12", "B1_11", "B3__3", "B2__2", "B1__1"); +// } +// @Test public void Page_size() { +// fxt.Init_basic(); +// fxt.Search_mgr().Page_mgr().Itms_per_page_(1); +// fxt.Test_search("b*", 0, "B3_23"); +// fxt.Test_search("b*", 1, "B2_22"); +// fxt.Test_search("b*", 2, "B1_21"); +// fxt.Test_search("b*", 3, "B3_13"); +// } +// @Test public void Url() { +// Xoa_url url = Xow_url_parser_old.Parse_url(fxt.App(), fxt.Wiki(), "Special:Search/Abc?fulltext=y&xowa_sort=len_desc"); +// fxt.Search_mgr().Args_mgr().Clear().Parse(url.Args()); +// Tfds.Eq(Srch_rslt_row_sorter.Tid_len_dsc, fxt.Search_mgr().Args_mgr().Sort_tid()); +// } +// @Test public void Url_arg_title() {// http://en.wikipedia.org/wiki/Special:Search/Earth?fulltext=yes&title=Mars +// fxt.Test_url_search_bry("Special:Search?fulltext=y&search=Abc" , "Abc"); // query arg +//// fxt.Test_url_search_bry("Special:Search/Abc?fulltext=y" , "Abc"); // leaf +// fxt.Test_url_search_bry("Special:Search/Abc?fulltext=y&search=Def" , "Def"); // leaf overrides query arg +// } +// @Test public void Url_ns() { +// fxt.Test_url__ns("Special:Search?search=Abc&ns0=1&ns1=1", "0|1"); +// fxt.Test_url__ns("Special:Search?search=Abc&ns*=1", "*"); +// fxt.Test_url__ns("Special:Search?search=Abc", "0"); +// } +// @Test public void Html() { +// fxt.Init_basic(); +// fxt.Test_html_by_url("B1", "", String_.Concat_lines_nl +// ( "Result '''1''' of '''3''' for '''B1'''
    " +// , "{|" +// , "|-" +// , "| [[Special:Search/B1?fulltext=y&xowa_page_index=0|<]]" +// , "| [[Special:Search/B1?fulltext=y&xowa_page_index=1|>]]" +// , "|-" +// , "| [[Special:Search/B1?fulltext=y&xowa_sort=len_desc|length]]" +// , "| [[Special:Search/B1?fulltext=y&xowa_sort=title_asc|title]]" +// , "|-" +// , "| 42 || [[B1 21]]" +// , "|-" +// , "| 22 || [[B1 11]]" +// , "|-" +// , "| 2 || [[B1 1]]" +// , "|-" +// , "| [[Special:Search/B1?fulltext=y&xowa_page_index=0|<]]" +// , "| [[Special:Search/B1?fulltext=y&xowa_page_index=1|>]]" +// , "|}" +// )); +// } +//// @Test public void Page_next() { +//// fxt.Init_basic(); +//// fxt.Search_mgr().Page_size_(1); +//// fxt.Test_search(Srch_special_page.Match_tid_all, "B1", 0, "B1 1"); +//// fxt.Test_search(Srch_special_page.Match_tid_all, "B1", 1, "B1 11"); +//// } +//// @Test public void Misc_url() { +//// fxt.Init_basic(); +//// fxt.Search_mgr().Page_size_(1); +//// fxt.Expd_address_page_("Special:Search/B1"); +//// fxt.Test_search(Srch_special_page.Match_tid_all, "B1", 0, "B1 1"); +//// } +// @Test public void Sort_defaults_to_len_desc() { +// fxt.Init_basic(); +// fxt.Search_mgr().Page_mgr().Itms_per_page_(3); +// fxt.Test_search2(Srch_special_page.Match_tid_bgn, "b" , 0, Srch_rslt_row_sorter.Tid_ttl_asc , "B1_11", "B1_21", "B1__1"); // sort by name; note that _ sorts after alphabet +// fxt.Test_search2(Srch_special_page.Match_tid_bgn, "b" , 1, Srch_rslt_row_sorter.Tid_none , "B2_12", "B2_22", "B2__2"); // sort by name still; next page should not reset +// fxt.Test_search2(Srch_special_page.Match_tid_bgn, "b2" , 0, Srch_rslt_row_sorter.Tid_none , "B2_22", "B2_12", "B2__2"); // sort by len desc; new search should reset +// } +// } +// class Xos_search_mgr_fxt { +// Xoae_app app; Xowe_wiki wiki; Bry_bfr bfr = Bry_bfr_.Reset(500); Srch_special_page search_mgr; +// public Xoae_app App() {return app;} +// public Xowe_wiki Wiki() {return wiki;} +// public Xobl_regy_itm regy_itm_(int id, String bgn, String end, int count) {return new Xobl_regy_itm(id, Bry_.new_u8(bgn), Bry_.new_u8(end), count);} +// public Xowd_page_itm data_ttl_(int id, String ttl) {return data_ttl_(id, 0, 0, false, 0, ttl);} +// public Xowd_page_itm data_ttl_(int id, int fil, int row, boolean redirect, int len, String ttl) {return new Xowd_page_itm().Init(id, Bry_.new_u8(ttl), redirect, len, fil, row);} +// public Xowd_page_itm data_id_(int id, String ttl) {return data_id_(id, Xow_ns_.Tid__main, ttl);} +// public Xowd_page_itm data_id_(int id, int ns, String ttl) {return new Xowd_page_itm().Id_(id).Ns_id_(ns).Ttl_page_db_(Bry_.new_u8(ttl)).Text_db_id_(0).Text_len_(0);} +// public Xobl_search_ttl data_sttl_(String word, params int[] ids) {return new Xobl_search_ttl(Bry_.new_u8(word), data_ttl_word_page_ary_(ids));} +// public Xobl_search_ttl_page[] data_ttl_word_page_ary_(params int[] ids) { +// int ids_len = ids.length; +// Xobl_search_ttl_page[] rv = new Xobl_search_ttl_page[ids_len]; +// for (int i = 0; i < ids_len; i++) { +// int id = ids[i]; +// rv[i] = new Xobl_search_ttl_page(id, id * 2); +// } +// return rv; +// } +// public void Init_regy_site(byte dir_info, params Xobl_regy_itm[] ary) {Init_regy(wiki.Tdb_fsys_mgr().Url_site_reg(dir_info), ary);} +// public void Init_regy_ns (String ns_num, byte tid, params Xobl_regy_itm[] ary) {Init_regy(wiki.Tdb_fsys_mgr().Url_ns_reg(ns_num, tid), ary);} +// public void Init_regy(Io_url url, Xobl_regy_itm[] ary) { +// int ary_len = ary.length; +// for (int i = 0; i < ary_len; i++) { +// Xobl_regy_itm itm = ary[i]; +// itm.Srl_save(tmp_bfr); +// } +// Io_mgr.Instance.SaveFilBfr(url, tmp_bfr); +// } private Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); +// public void Init_data(Io_url fil, params Xobl_data_itm[] ary) { +// Xob_xdat_file xdat_file = new Xob_xdat_file(); +// int ary_len = ary.length; +// for (int i = 0; i < ary_len; i++) { +// Xobl_data_itm itm = ary[i]; +// itm.Srl_save(tmp_bfr); +// xdat_file.Insert(bfr, tmp_bfr.To_bry_and_clear()); +// } +// xdat_file.Save(fil); +// } +// public void Init_basic() { +// this.Init_regy_ns(wiki.Ns_mgr().Ns_main().Num_str(), Xotdb_dir_info_.Tid_search_ttl, this.regy_itm_(0, "A", "C", 5)); +// this.Init_data(wiki.Tdb_fsys_mgr().Url_ns_fil(Xotdb_dir_info_.Tid_search_ttl, Xow_ns_.Tid__main, 0) +// , this.data_sttl_("a" , 0) +// , this.data_sttl_("b1" , 1, 11, 21) +// , this.data_sttl_("b2" , 2, 12, 22) +// , this.data_sttl_("b3" , 3, 13, 23) +// , this.data_sttl_("c" , 4) +// ); +// this.Init_regy_site(Xotdb_dir_info_.Tid_id, this.regy_itm_(0, "A", "C", 11)); +// this.Init_data(wiki.Tdb_fsys_mgr().Url_site_fil(Xotdb_dir_info_.Tid_id, 0) +// , this.data_id_( 0, "A___0") +// , this.data_id_( 1, "B1__1") +// , this.data_id_( 2, "B2__2") +// , this.data_id_( 3, "B3__3") +// , this.data_id_( 4, "C___4") +// , this.data_id_(11, "B1_11") +// , this.data_id_(12, "B2_12") +// , this.data_id_(13, "B3_13") +// , this.data_id_(21, "B1_21") +// , this.data_id_(22, "B2_22") +// , this.data_id_(23, "B3_23") +// ); +// search_mgr.Page_mgr().Ns_mgr().Add_all(); // WORKAROUND: xdat fmt does not store ns with search data; pages will be retrieved with ns_id = null; force ns_all (instead of allowing ns_main default); +// } +// public void Clear() { +// Io_mgr.Instance.InitEngine_mem(); +// app = Xoa_app_fxt.Make__app__edit(); +// wiki = Xoa_app_fxt.Make__wiki__edit(app); +// search_mgr = wiki.Special_mgr().Page_search(); +// wiki.Appe().Gui_mgr().Search_suggest_mgr().Args_default_str_("ns*=1"); // WORKAROUND: xdat fmt does not store ns with search data; pages will be retrieved with ns_id = null; force ns_all (instead of allowing ns_main default); +// } +// public Srch_special_page Search_mgr() {return search_mgr;} +// public void Test_url_search_bry(String url_str, String expd) { +// Xoa_url url = Xow_url_parser_old.Parse_url(app, wiki, url_str); +// search_mgr.Args_mgr().Clear().Parse(url.Args()); +// Tfds.Eq(expd, String_.new_u8(search_mgr.Args_mgr().Search_bry())); +// } +// public void Test_url__ns(String url_str, String expd) { +// Xoa_url url = Xow_url_parser_old.Parse_url(app, wiki, url_str); +// search_mgr.Args_mgr().Clear().Parse(url.Args()); +// Tfds.Eq(expd, String_.new_a7(search_mgr.Args_mgr().Ns_mgr().Xto_hash_key())); +// } +// public void Test_search_exact(String ttl_str, params String[] expd_ary) {Test_search(ttl_str, 0, expd_ary);} +// public void Test_search_match_bgn(String ttl_str, params String[] expd_ary) {Test_search(ttl_str, 0, expd_ary);} +// public void Test_search(String ttl_str, int page_idx, params String[] expd_ary) { +// byte[] ttl_bry = Bry_.new_a7(ttl_str); +// Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_b128(); +// Xosrh_rslt_grp page = search_mgr.Page_mgr().Search(bfr, wiki, ttl_bry, page_idx, search_mgr.Page_mgr()); +// bfr.Mkr_rls(); +// Tfds.Eq_ary(expd_ary, Search_itms_to_int_ary(page)); +// } +// public void Test_html_by_url(String ttl_str, String args_str, String expd_html) { +// wiki.Init_needed_(false); +// byte[] ttl_bry = Bry_.new_a7(ttl_str); +// Xoa_ttl ttl = Xoa_ttl.Parse(wiki, ttl_bry); +// Xoae_page page = Xoae_page.New_test(wiki, ttl); +// byte[] url_bry = Bry_.new_a7("http://en.wikipedia.org/wiki/Special:Search/" + ttl_str + args_str); +// Xoa_url url = wiki.Appe().Url_parser().Parse(url_bry); +// search_mgr.Special__gen(url, page, wiki, ttl); +// Tfds.Eq_str_lines(expd_html, String_.new_u8(page.Root().Data_htm())); +// } +// public void Test_search2(byte match_tid, String ttl_str, int page_idx, byte sort_tid, params String[] expd_ary) { +// Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_b128(); +// Xow_url_parser_old url_parser = new Xow_url_parser_old(); +// byte[] url_raw = Bry_.new_a7("Special:Search/" + ttl_str + ((match_tid == Srch_special_page.Match_tid_all) ? "" : "*") + "?fulltext=y" + Srch_rslt_row_sorter.Xto_url_arg(sort_tid) + "&xowa_page_size=1&xowa_page_index=" + page_idx); +// Xoa_url url = url_parser.Parse(url_raw); +// Xoa_ttl ttl = Xoa_ttl.Parse(wiki, url_raw); +// Xoae_page page = wiki.Ctx().Page(); +// search_mgr.Special__gen(url, page, wiki, ttl); +// Xosrh_rslt_grp cur_grp = search_mgr.Cur_grp(); +// bfr.Mkr_rls(); +// Tfds.Eq_ary(expd_ary, Search_itms_to_int_ary(cur_grp)); +// } +// String[] Search_itms_to_int_ary(Xosrh_rslt_grp page) { +// int itms_len = page.Itms_len(); +// String[] rv = new String[itms_len]; +// for (int i = 0; i < itms_len; i++) { +// Xowd_page_itm itm = page.Itms_get_at(i); +// rv[i] = String_.new_u8(itm.Ttl_page_db()); +// } +// return rv; +// } +// } +// interface Xobl_data_itm { +// void Srl_save(Bry_bfr bfr); +// } +//} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Xow_domain_sorter__manual.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Xow_domain_sorter__manual.java index a27517de8..8db431d24 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Xow_domain_sorter__manual.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/Xow_domain_sorter__manual.java @@ -13,3 +13,107 @@ 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.addons.wikis.searchs.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; +import gplx.core.primitives.*; import gplx.xowa.langs.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.domains.crts.*; +public class Xow_domain_sorter__manual implements gplx.core.lists.ComparerAble { + private final Xow_domain_itm cur_domain; + private final Xow_domain_crt_itm[] ary; private final int ary_len; + public Xow_domain_sorter__manual(Xow_domain_itm cur_domain, Xow_domain_crt_itm[] ary) { + this.cur_domain = cur_domain; this.ary = ary; this.ary_len = ary.length; + } + public int compare(Object lhsObj, Object rhsObj) { + Xow_domain_itm lhs = (Xow_domain_itm)lhsObj; + Xow_domain_itm rhs = (Xow_domain_itm)rhsObj; + int lhs_sort = Get_sort_idx_or_neg1(lhs); + int rhs_sort = Get_sort_idx_or_neg1(rhs); + if (lhs_sort == -1 && rhs_sort != -1) return rhs_sort; + else if (lhs_sort != -1 && rhs_sort == -1) return lhs_sort; + else if (lhs_sort != -1 && rhs_sort != -1) return Int_.Compare(lhs_sort, rhs_sort); + else return Bry_.Compare(lhs.Domain_bry(), rhs.Domain_bry()); + } + private int Get_sort_idx_or_neg1(Xow_domain_itm domain) { + int sort_idx = domain.Sort_idx(); if (sort_idx != -1) return sort_idx; + sort_idx = Int_.Max_value; + for (int i = 0; i < ary_len; ++i) { + Xow_domain_crt_itm crt = ary[i]; + if (crt.Matches(cur_domain, domain)) {sort_idx = i; break;} + } + domain.Sort_idx_(sort_idx); + return sort_idx; + } + public static void Sort(Xow_domain_sorter__manual sorter, Xow_domain_itm[] ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) + ary[i].Sort_idx_(-1); + Array_.Sort(ary, sorter); + } +} +class Xow_domain_sorter__manual_tid implements gplx.core.lists.ComparerAble { + private final Hash_adp sort_hash = Hash_adp_.New(); private final Int_obj_ref sort_key = Int_obj_ref.New_neg1(); + public Xow_domain_sorter__manual_tid(int[] id_ary) { + int len = id_ary.length; + for (int i = 0; i < len; ++i) { + int id_itm = id_ary[i]; + sort_hash.Add_if_dupe_use_nth(Int_obj_ref.New(id_itm), Int_obj_ref.New(i)); + } + } + public int compare(Object lhsObj, Object rhsObj) { + Xow_domain_itm lhs = (Xow_domain_itm)lhsObj; + Xow_domain_itm rhs = (Xow_domain_itm)rhsObj; + int lhs_sort = Get_sort_idx_or_neg1(lhs.Domain_type_id()); + int rhs_sort = Get_sort_idx_or_neg1(rhs.Domain_type_id()); + if (lhs_sort == -1 && rhs_sort != -1) return rhs_sort; + else if (lhs_sort != -1 && rhs_sort == -1) return lhs_sort; + else if (lhs_sort != -1 && rhs_sort != -1) return Int_.Compare(lhs_sort, rhs_sort); + else return Bry_.Compare(Xow_domain_tid_.Get_type_as_bry(lhs.Domain_type_id()), Xow_domain_tid_.Get_type_as_bry(rhs.Domain_type_id())); + } + private int Get_sort_idx_or_neg1(int tid) { + Object o = sort_hash.Get_by(sort_key.Val_(tid)); + return o == null ? -1 : ((Int_obj_ref)o).Val(); + } + public static Xow_domain_sorter__manual_tid new_(byte[]... id_brys) { + int len = id_brys.length; + int[] id_ints = new int[len]; + for (int i = 0; i < len; ++i) { + byte[] id_bry = id_brys[i]; + int id_int = Xow_domain_tid_.Get_type_as_tid(id_bry); + id_ints[i] = id_int; + } + return new Xow_domain_sorter__manual_tid(id_ints); + } +} +class Xow_domain_sorter__manual_lang implements gplx.core.lists.ComparerAble { + private final Hash_adp sort_hash = Hash_adp_.New(); private final Int_obj_ref sort_key = Int_obj_ref.New_neg1(); + public Xow_domain_sorter__manual_lang(int[] id_ary) { + int len = id_ary.length; + for (int i = 0; i < len; ++i) { + int id_int = id_ary[i]; + sort_hash.Add_if_dupe_use_nth(Int_obj_ref.New(id_int), Int_obj_ref.New(i)); + } + } + public int compare(Object lhsObj, Object rhsObj) { + Xow_domain_itm lhs = (Xow_domain_itm)lhsObj; + Xow_domain_itm rhs = (Xow_domain_itm)rhsObj; + int lhs_sort = Get_sort_idx_or_neg1(lhs.Lang_actl_uid()); + int rhs_sort = Get_sort_idx_or_neg1(rhs.Lang_actl_uid()); + if (lhs_sort == -1 && rhs_sort != -1) return rhs_sort; + else if (lhs_sort != -1 && rhs_sort == -1) return lhs_sort; + else if (lhs_sort != -1 && rhs_sort != -1) return Int_.Compare(lhs_sort, rhs_sort); + else return Bry_.Compare(lhs.Lang_actl_key(), rhs.Lang_actl_key()); + } + private int Get_sort_idx_or_neg1(int tid) { + Object o = sort_hash.Get_by(sort_key.Val_(tid)); + return o == null ? -1 : ((Int_obj_ref)o).Val(); + } + public static Xow_domain_sorter__manual_lang new_(byte[]... id_brys) { + int len = id_brys.length; + int[] id_ints = new int[len]; + for (int i = 0; i < len; ++i) { + byte[] id_bry = id_brys[i]; + int id_int = Xol_lang_stub_.Get_by_key_or_intl(id_bry).Id(); + id_ints[i] = id_int; + } + return new Xow_domain_sorter__manual_lang(id_ints); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/htmls/Srch_html_page_bldr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/htmls/Srch_html_page_bldr.java index a27517de8..50e0c6c50 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/htmls/Srch_html_page_bldr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/htmls/Srch_html_page_bldr.java @@ -13,3 +13,85 @@ 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.addons.wikis.searchs.specials.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.specials.*; +import gplx.core.brys.fmtrs.*; +import gplx.langs.htmls.*; import gplx.xowa.htmls.core.htmls.utls.*; import gplx.xowa.langs.numbers.*; import gplx.langs.htmls.entitys.*; +import gplx.xowa.addons.wikis.searchs.specials.*; import gplx.xowa.addons.wikis.searchs.searchers.*; import gplx.xowa.addons.wikis.searchs.searchers.rslts.*; +public class Srch_html_page_bldr { + private final Bry_bfr tmp_bfr = Bry_bfr_.New_w_size(255); + private Srch_search_qry qry; private Xow_wiki wiki; private Xol_num_mgr num_mgr; + private int slab_idx; + private Xoh_lnki_bldr lnki_bldr; private Xoh_anchor_kv_bldr self_lnkr = new Xoh_anchor_kv_bldr(); private Srch_html_row_bldr html_row_bldr; + public void Init_by_wiki(Xow_wiki wiki, Xol_num_mgr num_mgr, Srch_search_qry qry) { + this.wiki = wiki; this.num_mgr = num_mgr; this.qry = qry; + this.lnki_bldr = wiki.Html__lnki_bldr(); + int slab_len = qry.Slab_end - qry.Slab_bgn; + this.slab_idx = qry.Slab_bgn / slab_len; + this.html_row_bldr = new Srch_html_row_bldr(lnki_bldr); + self_lnkr.Init_w_qarg(tmp_bfr.Add(Bry__special_search).Add(qry.Phrase.Orig).Add(Bry__fulltext).To_bry_and_clear()); + } + public byte[] Bld_page(byte[] html_tbls_bry) { + byte[] rslts_hdr = fmtr_rslts.Bld_bry_many(tmp_bfr, num_mgr.Format_num(qry.Slab_bgn + List_adp_.Base1), num_mgr.Format_num(qry.Slab_end), qry.Phrase.Orig); + byte[] option_link = lnki_bldr.Href_(Bry_.new_a7("home"), wiki.Ttl_parse(Bry_.new_a7("Options/Search"))).Img_16x16(Xoh_img_path.Img_option).Bld_to_bry(); // HOME + fmtr_page.Bld_bfr_many(tmp_bfr, rslts_hdr, option_link, Bld_paging_link(Bool_.N), Bld_paging_link(Bool_.Y), html_tbls_bry); + return tmp_bfr.To_bry_and_clear(); + } + public void Bld_tbl(Bry_bfr bfr, Srch_rslt_list rslt_list, byte[] cmd_key, byte[] wiki_domain, boolean searching_db, int slab_bgn, int slab_end) { + html_row_bldr.Init(rslt_list, slab_bgn, slab_end); + byte[] search_link = lnki_bldr.Href_(wiki_domain, wiki.Ttl_parse(self_lnkr.Bld_to_bry())).Caption_(wiki_domain).Img_16x16(Xoh_img_path.Img_search).Img_pos_is_left_(Bool_.Y).Bld_to_bry(); + fmtr_tbl.Bld_bfr_many(bfr, search_link, searching_db ? Bld_cancel_link(wiki_domain, cmd_key) : Bry_.Empty, Bry_hdr_len, Bry_hdr_ttl, Srch_html_row_wkr.Gen_insert_key(wiki_domain), html_row_bldr); + } + private byte[] Bld_cancel_link(byte[] domain, byte[] cmd_key) { // DELETE: remove cancel link; clicking will blank out results; DATE:2016-06-14 + // lnki_bldr.Id_(Bry_.Add(Bry_.new_a7("xowa_cancel_"), domain)); + // lnki_bldr.Href_(wiki, self_lnkr.Add_int(Srch_qarg_mgr.Bry__slab_idx, slab_idx).Add_bry(Srch_qarg_mgr.Bry__cancel, cmd_key).Bld_to_bry()); + // lnki_bldr.Title_(Bry_cancel); + // lnki_bldr.Img_16x16(Xoh_img_path.Img_cancel); + // return lnki_bldr.Bld_to_bry(); + return Bry_.Empty; + } + public byte[] Bld_paging_link(boolean fwd) { + byte[] title = null, img_path = Bry_.Empty; + boolean img_pos_is_left = true; + int qarg_slab_idx = slab_idx; + if (fwd) { + ++qarg_slab_idx; + // if (slab_idx > qry.Page_max()) return Gfh_entity_.Nbsp_num_bry; + img_pos_is_left = false; + img_path = Xoh_img_path.Img_go_fwd; + title = Bry_paging_fwd; + } + else { + --qarg_slab_idx; + if (qarg_slab_idx < 0) return Gfh_entity_.Nbsp_num_bry; + img_path = Xoh_img_path.Img_go_bwd; + title = Bry_paging_bwd; + } + return lnki_bldr.Title_(title).Href_wo_escape_(wiki.Domain_bry(), self_lnkr.Add_int(Srch_qarg_mgr.Bry__slab_idx, qarg_slab_idx).Bld_to_bry()).Img_16x16(img_path).Img_pos_is_left_(img_pos_is_left).Caption_(title).Bld_to_bry(); + } + private static final Bry_fmtr fmtr_page = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "~{rslts_hdr}~{option_link}" + , "
    ~{bwd_a}~{fwd_a}
    ~{tbls}" + , "
    ~{bwd_a}~{fwd_a}
    " + ), "rslts_hdr", "option_link", "bwd_a", "fwd_a", "tbls"); + private static final Bry_fmtr fmtr_tbl = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "
    , + , Bry__scope = Bry_.new_a7("scope") + , Bry__rowspan = Bry_.new_a7("rowspan") + , Bry__colspan = Bry_.new_a7("colspan") + , Bry__align = Bry_.new_a7("align") // HTML.v4 + , Bry__bgcolor = Bry_.new_a7("bgcolor") // HTML.v4 + , Bry__abbr = Bry_.new_a7("abbr") // HTML.ua + , Bry__srcset = Bry_.new_a7("srcset") + ; + public static byte[] Make(Bry_bfr bfr, byte[] key, byte[] val) { + return bfr.Add_byte_space().Add(key).Add_byte_eq().Add_byte_quote().Add(val).Add_byte_quote().To_bry_and_clear(); + } + public static byte[] Add_to_bry(Bry_bfr bfr, byte[] key, byte[] val) { + bfr.Add_byte_space().Add(key).Add_byte_eq().Add_byte_quote().Add(val).Add_byte_quote(); + return bfr.To_bry_and_clear(); + } + public static void Add(Bry_bfr bfr, byte[] key, byte[] val) { + bfr.Add_byte_space().Add(key).Add_byte_eq().Add_byte_quote().Add(val).Add_byte_quote(); + } + public static void Add(Bry_bfr bfr, byte[] key, int val) { + bfr.Add_byte_space().Add(key).Add_byte_eq().Add_byte_quote(); + bfr.Add_int_variable(val); + bfr.Add_byte_quote(); + } + public static void Add_double(Bry_bfr bfr, byte[] key, double val) { + bfr.Add_byte_space().Add(key).Add_byte_eq().Add_byte_quote(); + bfr.Add_double(val); + bfr.Add_byte_quote(); + } +} diff --git a/400_xowa/src/gplx/langs/htmls/Gfh_bldr_.java b/400_xowa/src/gplx/langs/htmls/Gfh_bldr_.java index a27517de8..69941a878 100644 --- a/400_xowa/src/gplx/langs/htmls/Gfh_bldr_.java +++ b/400_xowa/src/gplx/langs/htmls/Gfh_bldr_.java @@ -13,3 +13,28 @@ 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.langs.htmls; import gplx.*; import gplx.langs.*; +public class Gfh_bldr_ { + public static final byte[] + Bry__a_lhs_bgn = Bry_.new_a7("") + , Bry__a_lhs_w_href = Bry_.new_a7("") + , Bry__id__1st = Bry_.new_a7(" id=\"") + , Bry__id__nth = Bry_.new_a7("\" id=\"") + , Bry__cls__1st = Bry_.new_a7(" class=\"") + , Bry__cls__nth = Bry_.new_a7("\" class=\"") + , Bry__title__nth = Bry_.new_a7("\" title=\"") + , Bry__alt__nth = Bry_.new_a7("\" alt=\"") + , Bry__src__nth = Bry_.new_a7("\" src=\"") + , Bry__width__nth = Bry_.new_a7("\" width=\"") + , Bry__height__nth = Bry_.new_a7("\" height=\"") + , Bry__lhs_end_head = Bry_.new_a7(">") + , Bry__lhs_end_head_w_quote = Bry_.new_a7("\">") + , Bry__lhs_end_inline = Bry_.new_a7("/>") + , Bry__lhs_end_inline_w_quote = Bry_.new_a7("\"/>") + ; +} diff --git a/400_xowa/src/gplx/langs/htmls/Gfh_nde.java b/400_xowa/src/gplx/langs/htmls/Gfh_nde.java index a27517de8..fe918ac24 100644 --- a/400_xowa/src/gplx/langs/htmls/Gfh_nde.java +++ b/400_xowa/src/gplx/langs/htmls/Gfh_nde.java @@ -13,3 +13,80 @@ 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.langs.htmls; import gplx.*; import gplx.langs.*; +public class Gfh_nde { + public Gfh_nde(byte[] src, boolean tag_tid_is_inline, int tag_lhs_bgn, int tag_lhs_end, int tag_rhs_bgn, int tag_rhs_end, int name_bgn, int name_end, int[] cur_atrs, int atrs_idx) { + this.src = src; + this.tag_tid_is_inline = tag_tid_is_inline; + this.tag_lhs_bgn = tag_lhs_bgn; this.tag_lhs_end = tag_lhs_end; this.tag_rhs_bgn = tag_rhs_bgn; this.tag_rhs_end = tag_rhs_end; this.name_bgn = name_bgn; this.name_end = name_end; + if (atrs_idx > 0) { + atrs = new int[atrs_idx]; + for (int i = 0; i < atrs_idx; i++) + atrs[i] = cur_atrs[i]; + atrs_len = atrs_idx / 5; + } + } + public byte[] Src() {return src;} private byte[] src; + public int[] Atrs() {return atrs;} private int[] atrs = Int_ary_.Empty; + public int Atrs_len() {return atrs_len;} private int atrs_len; + public boolean Tag_tid_is_inline() {return tag_tid_is_inline;} private boolean tag_tid_is_inline; + public int Tag_lhs_bgn() {return tag_lhs_bgn;} public Gfh_nde Tag_lhs_bgn_(int v) {tag_lhs_bgn = v; return this;} private int tag_lhs_bgn; + public int Tag_lhs_end() {return tag_lhs_end;} public Gfh_nde Tag_lhs_end_(int v) {tag_lhs_end = v; return this;} private int tag_lhs_end; + public int Tag_rhs_bgn() {return tag_rhs_bgn;} public Gfh_nde Tag_rhs_bgn_(int v) {tag_rhs_bgn = v; return this;} private int tag_rhs_bgn; + public int Tag_rhs_end() {return tag_rhs_end;} public Gfh_nde Tag_rhs_end_(int v) {tag_rhs_end = v; return this;} private int tag_rhs_end; + public int Name_bgn() {return name_bgn;} public Gfh_nde Name_bgn_(int v) {name_bgn = v; return this;} private int name_bgn; + public int Name_end() {return name_end;} public Gfh_nde Name_end_(int v) {name_end = v; return this;} private int name_end; + public void Clear() {tag_lhs_bgn = tag_rhs_bgn = -1;} + public String Atrs_val_by_key_str(String find_key_str) {return String_.new_u8(Atrs_val_by_key_bry(Bry_.new_u8(find_key_str)));} + public byte[] Atrs_val_by_key_bry(byte[] find_key_bry) { + for (int i = 0; i < atrs_len; i ++) { + int atrs_idx = i * 5; + int atr_key_bgn = atrs[atrs_idx + 1]; + int atr_key_end = atrs[atrs_idx + 2]; + if (Bry_.Match(src, atr_key_bgn, atr_key_end, find_key_bry)) + return Atrs_vals_by_pos(src, atrs[atrs_idx + 0], atrs[atrs_idx + 3], atrs[atrs_idx + 4]); + } + return null; + } + byte[] Atrs_vals_by_pos(byte[] src, int quote_byte, int bgn, int end) { + Bry_bfr tmp_bfr = Bry_bfr_.New(); + boolean dirty = false; + for (int i = bgn; i < end; i++) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Backslash: + if (!dirty) {dirty = true; tmp_bfr.Add_mid(src, bgn, i);} + ++i; + tmp_bfr.Add_byte(src[i]); + break; + default: + if (b == quote_byte) { + byte next_byte = src[i + 1]; + if (next_byte == b) { + if (!dirty) {dirty = true; tmp_bfr.Add_mid(src, bgn, i);} + ++i; + tmp_bfr.Add_byte(src[i]); + } + } + else { + if (dirty) + tmp_bfr.Add_byte(b); + } + break; + } + } + return dirty ? tmp_bfr.To_bry_and_clear() : Bry_.Mid(src, bgn, end); + } + public byte[] Data(byte[] src) { + return Bry_.Mid(src, tag_lhs_end, tag_rhs_bgn); + } +} +// class Xoh_atr { +// public byte[] Key_bry() {return key_bry;} private byte[] key_bry; +// public byte[] Val_bry() {return val_bry;} private byte[] val_bry; +// public int Key_bgn() {return key_bgn;} private int key_bgn; +// public int Key_end() {return key_end;} private int key_end; +// public int Val_bgn() {return val_bgn;} private int val_bgn; +// public int Val_end() {return val_end;} private int val_end; +// public byte Val_quote_tid() {return val_quote_tid;} private byte val_quote_tid; +// } diff --git a/400_xowa/src/gplx/langs/htmls/Gfh_parser.java b/400_xowa/src/gplx/langs/htmls/Gfh_parser.java index a27517de8..0176d409c 100644 --- a/400_xowa/src/gplx/langs/htmls/Gfh_parser.java +++ b/400_xowa/src/gplx/langs/htmls/Gfh_parser.java @@ -13,3 +13,151 @@ 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.langs.htmls; import gplx.*; import gplx.langs.*; +import gplx.core.brys.*; +public class Gfh_parser { + public Gfh_parser() { + Bry_bldr bry_bldr = new Bry_bldr(); + bry_xnde_name = bry_bldr.New_256().Set_rng_xml_identifier(Scan_valid).Set_rng_ws(Scan_stop).Val(); + bry_atr_key = bry_bldr.New_256().Set_rng_xml_identifier(Scan_valid).Set_rng_ws(Scan_stop).Set_many(Scan_stop, Byte_ascii.Eq).Val(); + } + byte[] src; int pos, end; byte[] bry_xnde_name, bry_atr_key; + int cur_atrs_idx = 0; int[] cur_atrs = new int[250];// define max of 50 atrs; + public Gfh_nde[] Parse_as_ary(byte[] src) {return Parse_as_ary(src, 0, src.length, Wildcard, Wildcard);} + public Gfh_nde[] Parse_as_ary(byte[] src, int bgn, int end) {return Parse_as_ary(src, bgn, end, Wildcard, Wildcard);} + public Gfh_nde[] Parse_as_ary(byte[] src, int bgn, int end, byte[] find_key, byte[] find_val) { // flattens html into a list of hndes; only used for Options + this.src = src; pos = bgn; this.end = end; + List_adp rv = List_adp_.New(); + while (pos < end) { + byte b = src[pos++]; + switch (b) { + case Byte_ascii.Lt: + if (xnde_init) { + if (Parse_xnde_lhs()) { + if (tag_tid_is_inline) + rv.Add(new Gfh_nde(src, tag_tid_is_inline, cur_lhs_bgn, cur_lhs_end, cur_rhs_bgn, pos, cur_name_bgn, cur_name_end, cur_atrs, cur_atrs_idx)); + else + xnde_init = false; + } + } + else { + if (Parse_xnde_rhs()) { + rv.Add(new Gfh_nde(src, tag_tid_is_inline, cur_lhs_bgn, cur_lhs_end, cur_rhs_bgn, pos, cur_name_bgn, cur_name_end, cur_atrs, cur_atrs_idx)); + } + xnde_init = true; + } + break; + default: + break; + } + } + return (Gfh_nde[])rv.To_ary(Gfh_nde.class); + } + int cur_lhs_bgn, cur_lhs_end, cur_name_bgn, cur_name_end, cur_rhs_bgn; boolean xnde_init = true, tag_tid_is_inline = false; + private boolean Parse_xnde_rhs() { + cur_rhs_bgn = pos - 1; // -1 b/c "<" is already read + byte b = src[pos]; + if (b != Byte_ascii.Slash) return false; + ++pos; + int name_len = cur_name_end - cur_name_bgn; + if (pos + name_len >= end) return false; + if (!Bry_.Match(src, pos, pos + name_len, src, cur_name_bgn, cur_name_end)) return false; + pos += name_len; + if (src[pos] != Byte_ascii.Gt) return false; + ++pos; + return true; + } + private boolean Parse_xnde_lhs() { + cur_atrs_idx = 0; + cur_lhs_bgn = pos - 1; + cur_name_bgn = pos; + tag_tid_is_inline = false; + byte rslt = Skip_while_valid(this.bry_atr_key); + if (rslt == Scan_invalid) return false; + cur_name_end = pos; + int key_bgn, key_end, val_bgn, quote_type; + while (true) { + if (pos >= end) return false; + key_bgn = key_end = val_bgn = quote_type = -1; + Skip_ws(); + byte b = src[pos]; + if (b == Byte_ascii.Slash) { + ++pos; + if (pos == end) return false; + byte next = src[pos]; + if (next == Byte_ascii.Gt) { + tag_tid_is_inline = true; + ++pos; + break; + } + else return false; // NOTE: don't consume byte b/c false + } + else if (b == Byte_ascii.Gt) { + ++pos; + break; + } + key_bgn = pos; + rslt = Skip_while_valid(this.bry_atr_key); + if (rslt == Scan_invalid) return false; + key_end = pos; + Skip_ws(); + if (src[pos++] != Byte_ascii.Eq) return false; + Skip_ws(); + byte quote_byte = src[pos]; + switch (quote_byte) { + case Byte_ascii.Quote: quote_type = quote_byte; break; + case Byte_ascii.Apos: quote_type = quote_byte; break; + default: return false; + } + val_bgn = ++pos; // ++pos: start val after quote + if (!Skip_to_quote_end(quote_byte)) return false; + cur_atrs[cur_atrs_idx + 0] = quote_type; + cur_atrs[cur_atrs_idx + 1] = key_bgn; + cur_atrs[cur_atrs_idx + 2] = key_end; + cur_atrs[cur_atrs_idx + 3] = val_bgn; + cur_atrs[cur_atrs_idx + 4] = pos - 1; // NOTE: Skip_to_quote_end positions after quote + cur_atrs_idx += 5; + } + cur_lhs_end = pos; + return true; + } + private void Skip_ws() { + while (pos < end) { + switch (src[pos]) { + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: + ++pos; + break; + default: + return; + } + } + } + boolean Skip_to_quote_end(byte v) { + while (pos < end) { + byte b = src[pos++]; + if (b == v) { + if (pos == end) return false; + byte next = src[pos]; + if (next != v) return true; + else ++pos; + } + else if (b == Byte_ascii.Backslash) { + ++pos; + } + } + return false; + } + byte Skip_while_valid(byte[] comp) { + while (pos < end) { + byte rv = comp[src[pos]]; + if (rv == Scan_valid) + ++pos; + else + return rv; + } + return Scan_invalid; + } + private static final byte Scan_invalid = 0, Scan_valid = 1, Scan_stop = 2; + public static final byte[] Wildcard = null; + public static final String Wildcard_str = null; +} diff --git a/400_xowa/src/gplx/langs/htmls/Gfh_parser_tst.java b/400_xowa/src/gplx/langs/htmls/Gfh_parser_tst.java index a27517de8..71e8a03fb 100644 --- a/400_xowa/src/gplx/langs/htmls/Gfh_parser_tst.java +++ b/400_xowa/src/gplx/langs/htmls/Gfh_parser_tst.java @@ -13,3 +13,39 @@ 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.langs.htmls; import gplx.*; import gplx.langs.*; +import org.junit.*; +public class Gfh_parser_tst { + @Before public void init() {fxt.Clear();} private Gfh_parser_fxt fxt = new Gfh_parser_fxt(); + @Test public void One() {fxt.Test_parse_find_all("", "id0");} + @Test public void Many() {fxt.Test_parse_find_all("", "id0", "id1", "id2");} + @Test public void Inline() {fxt.Test_parse_find_all("", "id0");} + @Test public void Mix() {fxt.Test_parse_find_all("012id=id2345abc", "id0", "id1", "id2");} + @Test public void Quote_double() {fxt.Test_parse_find_all("", "id'0");} + @Test public void Quote_escape() {fxt.Test_parse_find_all("", "id'0");} +} +class Gfh_parser_fxt { + public void Clear() { + if (parser == null) { + parser = new Gfh_parser(); + } + } private Gfh_parser parser; + public Gfh_parser_fxt Test_parse_find_all(String raw_str, String... expd) {return Test_parse_find(raw_str, Gfh_parser.Wildcard_str, Gfh_parser.Wildcard_str, expd);} + public Gfh_parser_fxt Test_parse_find(String raw_str, String find_key, String find_val, String... expd) { + byte[] raw = Bry_.new_a7(raw_str); + Gfh_nde[] actl_ndes = parser.Parse_as_ary(raw, 0, raw.length, Bry_.new_a7(find_key), Bry_.new_a7(find_val)); + String[] actl = Xto_ids(raw, actl_ndes); + Tfds.Eq_ary_str(expd, actl); + return this; + } + private String[] Xto_ids(byte[] src, Gfh_nde[] ary) { + int len = ary.length; + String[] rv = new String[len]; + for (int i = 0; i < len; i++) { + Gfh_nde itm = ary[i]; + String atr_val = itm.Atrs_val_by_key_str("id"); + rv[i] = atr_val; + } + return rv; + } +} diff --git a/400_xowa/src/gplx/langs/htmls/Gfh_selecter.java b/400_xowa/src/gplx/langs/htmls/Gfh_selecter.java index a27517de8..6e64bc116 100644 --- a/400_xowa/src/gplx/langs/htmls/Gfh_selecter.java +++ b/400_xowa/src/gplx/langs/htmls/Gfh_selecter.java @@ -13,3 +13,26 @@ 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.langs.htmls; import gplx.*; import gplx.langs.*; +public class Gfh_selecter { + public static Gfh_nde[] Select(byte[] src, Gfh_nde[] ary, Hash_adp_bry hash) { + List_adp list = List_adp_.New(); + int xndes_len = ary.length; + for (int i = 0; i < xndes_len; i++) { + Gfh_nde hnde = ary[i]; + int[] atrs = hnde.Atrs(); + int atrs_len = atrs.length; + for (int j = 0; j < atrs_len; j += 5) { + int atr_key_bgn = atrs[j + 1]; + int atr_key_end = atrs[j + 2]; + if (hash.Get_by_mid(src, atr_key_bgn, atr_key_end) != null) { + list.Add(hnde); + break; + } + } + } + Gfh_nde[] rv = (Gfh_nde[])list.To_ary(Gfh_nde.class); + list.Clear(); + return rv; + } +} diff --git a/400_xowa/src/gplx/langs/htmls/Gfh_tag_.java b/400_xowa/src/gplx/langs/htmls/Gfh_tag_.java index a27517de8..78a990a7f 100644 --- a/400_xowa/src/gplx/langs/htmls/Gfh_tag_.java +++ b/400_xowa/src/gplx/langs/htmls/Gfh_tag_.java @@ -13,3 +13,273 @@ 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.langs.htmls; import gplx.*; import gplx.langs.*; +public class Gfh_tag_ { // NOTE: not serialized; used by tag_rdr + public static final int + Id__comment = -3 + , Id__eos = -2 + , Id__any = -1 + , Id__unknown = 0 + , Id__h1 = 1 + , Id__h2 = 2 + , Id__h3 = 3 + , Id__h4 = 4 + , Id__h5 = 5 + , Id__h6 = 6 + , Id__a = 7 + , Id__span = 8 + , Id__div = 9 + , Id__img = 10 + , Id__ul = 11 + , Id__ol = 12 + , Id__li = 13 + , Id__dl = 14 + , Id__dd = 15 + , Id__dt = 16 + , Id__p = 17 + , Id__br = 18 + , Id__hr = 19 + , Id__table = 20 + , Id__tr = 21 + , Id__td = 22 + , Id__th = 23 + , Id__thead = 24 + , Id__tbody = 25 + , Id__caption = 26 + , Id__pre = 27 + , Id__small = 28 + , Id__i = 29 + , Id__b = 30 + , Id__sup = 31 + , Id__sub = 32 + , Id__bdi = 33 + , Id__font = 34 + , Id__strong = 35 + , Id__s = 36 + , Id__abbr = 37 + , Id__cite = 38 + , Id__var = 39 + , Id__u = 40 + , Id__big = 41 + , Id__del = 42 + , Id__strike = 43 + , Id__tt = 44 + , Id__code = 45 + , Id__wbr = 46 + , Id__center = 47 // not HTML5, but used by en.v:Vandalism_in_progress + , Id__dfn = 48 + , Id__kbd = 49 + , Id__samp = 50 + , Id__ins = 51 + , Id__em = 52 + , Id__blockquote = 53 + , Id__map = 54 + , Id__bdo = 55 + , Id__time = 56 + , Id__ruby = 57 + , Id__rb = 58 + , Id__rp = 59 + , Id__rt = 60 + ; +// private static final int Id__ary_max = 60; + public static final byte[] + Bry__a = Bry_.new_a7("a") + , Bry__ul = Bry_.new_a7("ul") + , Bry__td = Bry_.new_a7("td") + , Bry__th = Bry_.new_a7("th") + , Bry__div = Bry_.new_a7("div") + , Bry__link = Bry_.new_a7("link") + , Bry__style = Bry_.new_a7("style") + , Bry__script = Bry_.new_a7("script") + , Bry__xowa_any = Bry_.new_a7("xowa_any") + , Bry__xowa_comment = Bry_.new_a7("xowa_comment") + , Bry__img = Bry_.new_a7("img") + ; + +// private static final Gfh_tag_meta[] Ary = new Gfh_tag_meta[Id__ary_max]; +// private static final Hash_adp_bry tags_by_bry = Hash_adp_bry.ci_a7(); +// public static Gfh_tag_meta New_tag(int id, String key_str) { +// Gfh_tag_meta rv = new Gfh_tag_meta(id, key_str); +// Ary[id] = rv; +// tags_by_bry.Add_bry_int(rv.Key_bry(), id); +// return rv; +// } + public static final Hash_adp_bry Hash = Hash_adp_bry.ci_a7() + .Add_bry_int(Bry__a , Id__a) + .Add_str_int("h1" , Id__h1) + .Add_str_int("h2" , Id__h2) + .Add_str_int("h3" , Id__h3) + .Add_str_int("h4" , Id__h4) + .Add_str_int("h5" , Id__h5) + .Add_str_int("h6" , Id__h6) + .Add_str_int("span" , Id__span) + .Add_str_int("div" , Id__div) + .Add_str_int("img" , Id__img) + .Add_str_int("br" , Id__br) + .Add_str_int("hr" , Id__hr) + .Add_str_int("ul" , Id__ul) + .Add_str_int("ol" , Id__ol) + .Add_str_int("li" , Id__li) + .Add_str_int("dl" , Id__dl) + .Add_str_int("dd" , Id__dd) + .Add_str_int("dt" , Id__dt) + .Add_str_int("table" , Id__table) + .Add_str_int("tr" , Id__tr) + .Add_str_int("td" , Id__td) + .Add_str_int("th" , Id__th) + .Add_str_int("thead" , Id__thead) + .Add_str_int("tbody" , Id__tbody) + .Add_str_int("caption" , Id__caption) + .Add_str_int("p" , Id__p) + .Add_str_int("pre" , Id__pre) + .Add_str_int("small" , Id__small) + .Add_str_int("i" , Id__i) + .Add_str_int("b" , Id__b) + .Add_str_int("sup" , Id__sup) + .Add_str_int("sub" , Id__sub) + .Add_str_int("bdi" , Id__bdi) + .Add_str_int("font" , Id__font) + .Add_str_int("strong" , Id__strong) + .Add_str_int("s" , Id__s) + .Add_str_int("abbr" , Id__abbr) + .Add_str_int("cite" , Id__cite) + .Add_str_int("var" , Id__var) + .Add_str_int("u" , Id__u) + .Add_str_int("big" , Id__big) + .Add_str_int("del" , Id__del) + .Add_str_int("strike" , Id__strike) + .Add_str_int("tt" , Id__tt) + .Add_str_int("code" , Id__code) + .Add_str_int("wbr" , Id__wbr) + .Add_str_int("center" , Id__center) + .Add_str_int("dfn" , Id__dfn) + .Add_str_int("kbd" , Id__kbd) + .Add_str_int("samp" , Id__samp) + .Add_str_int("ins" , Id__ins) + .Add_str_int("em" , Id__em) + .Add_str_int("blockquote" , Id__blockquote) + .Add_str_int("map" , Id__map) + .Add_str_int("bdo" , Id__bdo) + .Add_str_int("time" , Id__time) + .Add_str_int("ruby" , Id__ruby) + .Add_str_int("rb" , Id__rb) + .Add_str_int("rp" , Id__rp) + .Add_str_int("rt" , Id__rt) + ; + public static String To_str(int tid) { + switch (tid) { + case Id__eos: return "EOS"; + case Id__any: return "any"; + case Id__unknown: return "unknown"; + case Id__comment: return "comment"; + case Id__h1: return "h1"; + case Id__h2: return "h2"; + case Id__h3: return "h2"; + case Id__h4: return "h2"; + case Id__h5: return "h2"; + case Id__h6: return "h2"; + case Id__a: return "a"; + case Id__span: return "span"; + case Id__div: return "div"; + case Id__img: return "img"; + case Id__p: return "p"; + case Id__br: return "br"; + case Id__hr: return "hr"; + case Id__ul: return "ul"; + case Id__ol: return "ol"; + case Id__li: return "li"; + case Id__dl: return "dl"; + case Id__dd: return "dd"; + case Id__dt: return "dt"; + case Id__table: return "table"; + case Id__tr: return "tr"; + case Id__td: return "td"; + case Id__th: return "th"; + case Id__thead: return "thead"; + case Id__tbody: return "tbody"; + case Id__caption: return "caption"; + case Id__pre: return "pre"; + case Id__small: return "small"; + case Id__i: return "i"; + case Id__b: return "b"; + case Id__sup: return "sup"; + case Id__sub: return "sub"; + case Id__bdi: return "bdi"; + case Id__font: return "font"; + case Id__strong: return "strong"; + case Id__s: return "s"; + case Id__abbr: return "abbr"; + case Id__cite: return "cite"; + case Id__var: return "var"; + case Id__u: return "u"; + case Id__big: return "big"; + case Id__del: return "del"; + case Id__strike: return "strike"; + case Id__tt: return "tt"; + case Id__code: return "code"; + case Id__wbr: return "wbr"; + case Id__center: return "center"; + case Id__dfn: return "dfn"; + case Id__kbd: return "kbd"; + case Id__samp: return "samp"; + case Id__ins: return "ins"; + case Id__em: return "em"; + case Id__blockquote: return "blockquote"; + case Id__map: return "map"; + case Id__bdo: return "bdo"; + case Id__time: return "time"; + case Id__ruby: return "ruby"; + case Id__rb: return "rb"; + case Id__rp: return "rp"; + case Id__rt: return "rt"; + default: throw Err_.new_unhandled(tid); + } + } + public static final byte[] + Br_inl = Bry_.new_a7("
    ") + , Br_lhs = Bry_.new_a7("
    ") + , Hr_inl = Bry_.new_a7("
    ") + , Body_lhs = Bry_.new_a7("") , Body_rhs = Bry_.new_a7("") + , B_lhs = Bry_.new_a7("") , B_rhs = Bry_.new_a7("") + , I_lhs = Bry_.new_a7("") , I_rhs = Bry_.new_a7("") + , P_lhs = Bry_.new_a7("

    ") , P_rhs = Bry_.new_a7("

    ") + , Pre_lhs = Bry_.new_a7("
    ")			, Pre_rhs					= Bry_.new_a7("
    ") + , Div_lhs = Bry_.new_a7("
    ") , Div_rhs = Bry_.new_a7("
    ") , Div_lhs_bgn = Bry_.new_a7("") + , Head_lhs_bgn = Bry_.new_a7("") + , Style_lhs_w_type = Bry_.new_a7("") + , Script_lhs = Bry_.new_a7("") + , Script_lhs_w_type = Bry_.new_a7("" + , "bc"); + } +} +class Xoscript_doc_head__fxt { + private final Xoscript_page spg; + private Xoscript_doc_sect_base sect; + public Xoscript_doc_head__fxt() { + Bry_bfr rv = Bry_bfr_.New(); + Xoscript_env env = new Xoscript_env(new gplx.core.scripts.Gfo_script_engine__noop(), Io_url_.new_any_("mem/wiki/test_wiki/bin/script/")); + Xoscript_url url = new Xoscript_url("test_wiki", "test_page"); + spg = new Xoscript_page(rv, env, url); + } + public void Init__sect(String sect_name) { + if (String_.Eq(sect_name, "head")) + sect = spg.doc().head(); + else if (String_.Eq(sect_name, "tail")) + sect = spg.doc().tail(); + } + public void Exec__doc__html(String html) {spg.doc().html(html);} + public void Exec__reg_marker(String marker, String... pos_ary) {sect.reg_marker(marker, pos_ary);} + public void Exec__add_js_file(String pos, String file) {sect.add_js_file(pos, file);} + public void Exec__add_html(String html) {sect.add_html(html);} + public void Exec__add_html(String pos, String html) {sect.add_html(pos, html);} + public void Exec__add_tag(String pos, String tag, String body, Object... head_atrs) {sect.add_tag(pos, tag, body, head_atrs);} + public void Test__html(String... expd) { + Gftest.Eq__ary__lines(String_.Concat_lines_nl_skip_last(expd), spg.doc().html(), "html"); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_doc_sect_base.java b/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_doc_sect_base.java index a27517de8..66a72085e 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_doc_sect_base.java +++ b/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_doc_sect_base.java @@ -13,3 +13,68 @@ 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.addons.apps.scripts.apis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.scripts.*; +public abstract class Xoscript_doc_sect_base { + protected final Xoscript_doc doc; + private final Hash_adp_bry marker_hash = Hash_adp_bry.cs(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + public Xoscript_doc_sect_base(Xoscript_doc doc) {this.doc = doc;} + private byte[] get_marker_by_pos(byte[] pos_bry) { + return (byte[])marker_hash.Get_by_or_fail(pos_bry); + } + public void reg_marker(String marker_str, String... pos_ary) { + int len = pos_ary.length; + byte[] marker_bry = Bry_.new_u8(marker_str); + for (int i = 0; i < len; ++i) { + marker_hash.Add_if_dupe_use_nth(Bry_.new_u8(pos_ary[i]), marker_bry); + } + } + public void add_html(String html) {add_html(Pos__default, html);} + public void add_html(String pos_str, String html) {add_html(Pos__default, Bry_.new_u8(html));} + public void add_html(String pos_str, byte[] html) { + doc.html_by_marker(get_marker_by_pos(Bry_.new_u8(pos_str)), html); + } + public void add_tag(String pos_str, String tag_str, String body, Object... head_atrs) { + // build tag.bgn; EX: '' + tmp_bfr.Add_byte(Byte_ascii.Angle_bgn); + tmp_bfr.Add_str_u8(tag_str); + int head_atrs_len = head_atrs.length; + for (int i = 0; i < head_atrs_len; i += 2) { + tmp_bfr.Add_byte_space(); + tmp_bfr.Add_obj_strict(head_atrs[i]); + tmp_bfr.Add_byte_eq(); + tmp_bfr.Add_byte_quote(); + tmp_bfr.Add_obj_strict(head_atrs[i + 1]); + tmp_bfr.Add_byte_quote(); + } + tmp_bfr.Add_byte(Byte_ascii.Angle_end); + + // build tag.body; EX: 'some body' + tmp_bfr.Add_str_u8(body); + + // build tag.end; EX: '\n' + tmp_bfr.Add_byte(Byte_ascii.Angle_bgn).Add_byte(Byte_ascii.Slash); + tmp_bfr.Add_str_u8(tag_str); + tmp_bfr.Add_byte(Byte_ascii.Angle_end); + tmp_bfr.Add_byte_nl(); + + add_html(pos_str, tmp_bfr.To_bry_and_clear()); + } + public void add_js_file(String file_str) {add_js_file(Pos__default, file_str);} + public void add_js_file(String pos_str, String file_str) { + add_tag(pos_str, "script", Body__empty, "src", Xoscript_env.Resolve_file(Bool_.Y, doc.page().env().Root_dir(), file_str), "type", "text/javascript"); + } + public void add_js_code(String code_str) {add_js_file(Pos__default, code_str);} + public void add_js_code(String pos_str, String code_str) { + add_tag(pos_str, "script", code_str, "type", "text/javascript"); + } + public void add_css_file(String file_str) {add_js_file(Pos__default, file_str);} + public void add_css_file(String pos_str, String file_str) { + add_tag(pos_str, "link", Body__empty, "rel", "stylesheet", "href", Xoscript_env.Resolve_file(Bool_.Y, doc.page().env().Root_dir(), file_str), "type", "text/css"); + } + public void add_css_code(String code_str) {add_css_code(Pos__default, code_str);} + public void add_css_code(String pos_str, String code_str) { + add_tag(pos_str, "style", code_str, "type", "text/css"); + } + public static final String Pos__default = "", Body__empty = ""; +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_doc_tail.java b/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_doc_tail.java index a27517de8..1e1d1025f 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_doc_tail.java +++ b/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_doc_tail.java @@ -13,3 +13,7 @@ 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.addons.apps.scripts.apis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.scripts.*; +public class Xoscript_doc_tail extends Xoscript_doc_sect_base { + public Xoscript_doc_tail(Xoscript_doc doc) {super(doc);} +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_log.java b/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_log.java index a27517de8..5f5bba037 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_log.java +++ b/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_log.java @@ -13,3 +13,9 @@ 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.addons.apps.scripts.apis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.scripts.*; +public class Xoscript_log { + public void log(String... v) { + Gfo_usr_dlg_.Instance.Log_many("", "", String_.Concat_with_str(" ", v)); + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_page.java b/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_page.java index a27517de8..c16494fd8 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_page.java +++ b/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_page.java @@ -13,3 +13,14 @@ 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.addons.apps.scripts.apis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.scripts.*; +public class Xoscript_page { + public Xoscript_page(Bry_bfr rv, Xoscript_env env_var, Xoscript_url url_var) { + this.env_var = env_var; + this.url_var = url_var; + this.doc_var = new Xoscript_doc(rv, this); + } + public Xoscript_env env() {return env_var;} private final Xoscript_env env_var; + public Xoscript_url url() {return url_var;} private final Xoscript_url url_var; + public Xoscript_doc doc() {return doc_var;} private final Xoscript_doc doc_var; +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_url.java b/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_url.java index a27517de8..430f47f32 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_url.java +++ b/400_xowa/src/gplx/xowa/addons/apps/scripts/apis/Xoscript_url.java @@ -13,3 +13,9 @@ 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.addons.apps.scripts.apis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.scripts.*; +public class Xoscript_url { + public Xoscript_url(String wiki_name_var, String page_name_var) {this.wiki_name_var = wiki_name_var; this.page_name_var = page_name_var;} + public String wiki_name() {return wiki_name_var;} private final String wiki_name_var; + public String page_name() {return page_name_var;} private final String page_name_var; +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/scripts/xtns/Xoscript_xtn_itm.java b/400_xowa/src/gplx/xowa/addons/apps/scripts/xtns/Xoscript_xtn_itm.java index a27517de8..8eee1b820 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/scripts/xtns/Xoscript_xtn_itm.java +++ b/400_xowa/src/gplx/xowa/addons/apps/scripts/xtns/Xoscript_xtn_itm.java @@ -13,3 +13,15 @@ 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.addons.apps.scripts.xtns; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.scripts.*; +import gplx.core.scripts.*; +public class Xoscript_xtn_itm { + public Xoscript_xtn_itm(String key, Io_url url, Gfo_script_engine engine) { + this.key = key; + this.url = url; + this.engine = engine; + } + public String Key() {return key;} private final String key; + public Io_url Url() {return url;} private final Io_url url; + public Gfo_script_engine Engine() {return engine;} private final Gfo_script_engine engine; +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/scripts/xtns/Xoscript_xtn_mgr.java b/400_xowa/src/gplx/xowa/addons/apps/scripts/xtns/Xoscript_xtn_mgr.java index a27517de8..b295f6a47 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/scripts/xtns/Xoscript_xtn_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/apps/scripts/xtns/Xoscript_xtn_mgr.java @@ -13,3 +13,55 @@ 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.addons.apps.scripts.xtns; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.scripts.*; +import gplx.core.scripts.*; +import gplx.xowa.addons.apps.scripts.apis.*; +public class Xoscript_xtn_mgr { + private Xoscript_xtn_itm root_itm; + private final Io_url root_dir; + private final Ordered_hash hash = Ordered_hash_.New(); + public Xoscript_xtn_mgr(Io_url root_dir) { + this.root_dir = root_dir; + } + public void reg_xtn(String key, String file) { + Io_url url = Io_url_.new_fil_(Xoscript_env.Resolve_file(Bool_.N, root_dir, file)); + Xoscript_xtn_itm itm = new Xoscript_xtn_itm(key, url, Gfo_script_engine_.New_by_ext(url.Ext())); + hash.Add(key, itm); + } + public void Load_root() { + Io_url[] root_urls = Io_mgr.Instance.QueryDir_args(root_dir).ExecAsUrlAry(); + int root_urls_len = root_urls.length; + for (int i = 0; i < root_urls_len; ++i) { + Io_url root_url = root_urls[0]; + String root_name_and_ext = root_url.NameAndExt(); + if (String_.EqAny(root_name_and_ext, "xowa.script.main.js", "xowa.script.main.lua")) { + this.root_itm = new Xoscript_xtn_itm("xowa.root", root_url, Gfo_script_engine_.New_by_ext(root_url.Ext())); + break; + } + } + root_itm.Engine().Load_script(root_itm.Url()); + root_itm.Engine().Invoke_function("xoscript__main", this); + } + public void Run(Bry_bfr rv, Xow_wiki wiki, Xoa_page page) { + int len = hash.Len(); + Xoscript_log log = new Xoscript_log(); + for (int i = 0; i < len; ++i) { + Xoscript_xtn_itm itm = (Xoscript_xtn_itm)hash.Get_at(i); + Gfo_script_engine engine = (Gfo_script_engine)itm.Engine(); + Xoscript_env env = new Xoscript_env(engine, itm.Url().OwnerDir()); + Xoscript_page spg = new Xoscript_page(rv, env, new Xoscript_url(page.Wiki().Domain_str(), String_.new_u8(page.Url().Page_bry()))); + engine.Put_object("xolog", log); + engine.Load_script(itm.Url()); + try {engine.Invoke_function("xoscript__init", env);} + catch (Exception e) {Gfo_usr_dlg_.Instance.Note_many("", "", "xoscript__init failed; url=~{0} err=~{1}", itm.Url(), Err_.Message_lang(e));} + try {engine.Invoke_function("xoscript__page_write_end", spg);} + catch (Exception e) {Gfo_usr_dlg_.Instance.Note_many("", "", "xoscript__page_write_end failed; url=~{0} err=~{1}", itm.Url(), Err_.Message_lang(e));} + + // overwrite html + if (spg.doc().dirty()) { + rv.Clear(); + rv.Add_str_u8(spg.doc().html()); + } + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/Xoa_update_addon.java b/400_xowa/src/gplx/xowa/addons/apps/updates/Xoa_update_addon.java index a27517de8..38b7994b8 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/Xoa_update_addon.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/Xoa_update_addon.java @@ -13,3 +13,19 @@ 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.addons.apps.updates; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; +import gplx.xowa.specials.*; import gplx.xowa.htmls.bridges.*; +public class Xoa_update_addon implements Xoax_addon_itm, Xoax_addon_itm__special, Xoax_addon_itm__json { + public Xow_special_page[] Special_pages() { + return new Xow_special_page[] + { gplx.xowa.addons.apps.updates.specials.Xoa_update_special.Prototype + }; + } + public Bridge_cmd_itm[] Json_cmds() { + return new Bridge_cmd_itm[] + { gplx.xowa.addons.apps.updates.specials.svcs.Xoa_update_bridge.Prototype + }; + } + + public String Addon__key() {return ADDON__KEY;} private static final String ADDON__KEY = "xowa.app.update"; +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/Xoa_update_startup.java b/400_xowa/src/gplx/xowa/addons/apps/updates/Xoa_update_startup.java index a27517de8..a0bf0e146 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/Xoa_update_startup.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/Xoa_update_startup.java @@ -13,3 +13,35 @@ 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.addons.apps.updates; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; +import gplx.xowa.addons.apps.cfgs.*; +import gplx.xowa.addons.apps.updates.dbs.*; +public class Xoa_update_startup { + public static boolean Show_at_startup(Xoa_app app) { + try { + // set default to this version + Xocfg_mgr cfg = app.Cfg(); + cfg.Dflt_mgr().Add(Cfg__version_cutoff, Int_.To_str(Xoa_app_.Version_id)); + + // exit if disabled + if (!cfg.Get_bool_app_or(Cfg__enabled, true)) return false; + + // check online for updates + Io_url db_url = Xoa_update_db_mgr_.Url(app); + Xoa_update_db_mgr_.Download_from_inet(app, Bool_.Y, db_url); + + // check offline for updates + int version_cutoff = cfg.Get_int_app_or(Cfg__version_cutoff, Xoa_app_.Version_id); + return Xoa_update_db_mgr_.Select(db_url, version_cutoff).length > 0; + } catch (Exception exc) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "starup:fatal error while looking up app-update-reminder; err=~{0}", Err_.Message_gplx_log(exc)); + return false; + } + } + public static int Version_cutoff(Xoa_app app) {return app.Cfg().Get_int_app_or(Cfg__version_cutoff, Xoa_app_.Version_id);} + public static void Version_cutoff_(Xoa_app app, int id) {app.Cfg().Set_int_app(Cfg__version_cutoff, id);} + + private static final String + Cfg__enabled = "xowa.app.update.startup.enabled" + , Cfg__version_cutoff = "xowa.app.update.startup.version_cutoff"; +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/apps/Xoa_manifest_item.java b/400_xowa/src/gplx/xowa/addons/apps/updates/apps/Xoa_manifest_item.java index a27517de8..1f5cbdedc 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/apps/Xoa_manifest_item.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/apps/Xoa_manifest_item.java @@ -13,3 +13,12 @@ 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.addons.apps.updates.apps; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; +public class Xoa_manifest_item { + public Xoa_manifest_item(Io_url src, Io_url trg) { + this.src = src; + this.trg = trg; + } + public Io_url Src() {return src;} private final Io_url src; + public Io_url Trg() {return trg;} private final Io_url trg; +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/apps/Xoa_manifest_list.java b/400_xowa/src/gplx/xowa/addons/apps/updates/apps/Xoa_manifest_list.java index a27517de8..1cfe9440c 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/apps/Xoa_manifest_list.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/apps/Xoa_manifest_list.java @@ -13,3 +13,47 @@ 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.addons.apps.updates.apps; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; +public class Xoa_manifest_list { + private final Ordered_hash hash = Ordered_hash_.New(); + public int Len() {return hash.Len();} + public Xoa_manifest_item Get_at(int i) {return (Xoa_manifest_item)hash.Get_at(i);} + public void Del_at(int i) { + Xoa_manifest_item item = (Xoa_manifest_item)hash.Get_at(i); + hash.Del(item.Src().Raw()); + } + public void Add(Io_url src, Io_url trg) { + Xoa_manifest_item item = new Xoa_manifest_item(src, trg); + hash.Add(src.Raw(), item); + } + public void Save(Bry_bfr bfr) { + // save as "src\ttrg\n" + int len = hash.Len(); + for (int i = 0; i < len; i++) { + Xoa_manifest_item item = (Xoa_manifest_item)hash.Get_at(i); + bfr.Add_str_u8(item.Src().Raw()); + bfr.Add_byte(Byte_ascii.Tab); + bfr.Add_str_u8(item.Trg().Raw()); + bfr.Add_byte(Byte_ascii.Nl); + } + } + public void Load(byte[] src, int src_bgn, int src_end) { + int pos = src_bgn; + // load by "src\ttrg\n" + while (pos < src_end) { + // get pos + int tab_pos = Bry_find_.Find_fwd(src, Byte_ascii.Tab, pos, src_end); + if (tab_pos == Bry_find_.Not_found) throw Err_.new_wo_type("failed to find tab", "excerpt", Bry_.Mid(src, pos, src_end)); + int nl_pos = Bry_find_.Find_fwd(src, Byte_ascii.Nl, tab_pos + 1, src_end); + if (nl_pos == Bry_find_.Not_found) throw Err_.new_wo_type("failed to find nl", "excerpt", Bry_.Mid(src, pos, src_end)); + + // create + Io_url src_url = Io_url_.new_fil_(String_.new_u8(src, pos, tab_pos)); + Io_url trg_url = Io_url_.new_fil_(String_.new_u8(src, tab_pos + 1, nl_pos)); + Xoa_manifest_item item = new Xoa_manifest_item(src_url, trg_url); + hash.Add(src_url.Raw(), item); + + pos = nl_pos + 1; + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/apps/Xoa_manifest_view.java b/400_xowa/src/gplx/xowa/addons/apps/updates/apps/Xoa_manifest_view.java index a27517de8..fd3dd3c07 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/apps/Xoa_manifest_view.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/apps/Xoa_manifest_view.java @@ -13,3 +13,100 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.addons.apps.updates.apps; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; +import gplx.core.envs.*; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.net.*; +import javax.swing.*; +import javax.swing.border.Border; +public class Xoa_manifest_view extends JFrame implements Gfo_invk { + private final Xoa_manifest_wkr wkr; + private String run_xowa_cmd; + public Xoa_manifest_view(Io_url manifest_url) { + super("XOWA Application Update"); + // init window + this.setTitle("XOWA Application Update"); + try { + UIManager.setLookAndFeel( + UIManager.getSystemLookAndFeelClassName()); + } + catch (Exception e) {System.out.println(e.getMessage());} + this.setSize(700, 580); + this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + this.setLocationRelativeTo(null); + this.setBackground(Color.WHITE); + + // init panel + JPanel main_panel = new JPanel(); + main_panel.setSize(700, 580); + this.setContentPane(main_panel); + New_text_area(main_panel); + run_xowa_lbl = New_link_lbl(this, main_panel, Invk__run_xowa, "
    Run XOWA"); + this.setVisible(true); + this.wkr = new Xoa_manifest_wkr(this); + wkr.Init(manifest_url); + } + private JTextArea text_area; + private JLabel run_xowa_lbl; + private JScrollPane New_text_area(JPanel owner) { + // init textarea + text_area = new JTextArea(); + text_area.setForeground(Color.BLACK); + text_area.setBackground(Color.WHITE); + text_area.setMargin(new Insets(0, 0, 0,0)); + text_area.setLineWrap(true); + text_area.setWrapStyleWord(true); // else text will wrap in middle of words + text_area.setCaretColor(Color.BLACK); + text_area.getCaret().setBlinkRate(0); + + // init scrollpane + JScrollPane text_scroll_pane = new JScrollPane(text_area); + text_scroll_pane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + owner.add(text_scroll_pane, BorderLayout.CENTER); + text_scroll_pane.setPreferredSize(new Dimension(675, 500)); + return text_scroll_pane; + } + private static JLabel New_link_lbl(Gfo_invk invk, JPanel owner, String invk_cmd, String text) { + JLabel rv = new JLabel(); + rv.setText(text); + rv.setPreferredSize(new Dimension(80, 20)); + owner.add(rv, BorderLayout.PAGE_END); + return rv; + } + public void Append(String s) { + text_area.setText(text_area.getText() + "> " + s + "\n"); + } + public void Mark_done(String s) { + this.run_xowa_cmd = s; + run_xowa_lbl.setText("Run XOWA"); + run_xowa_lbl.setCursor(new Cursor(Cursor.HAND_CURSOR)); + run_xowa_lbl.addMouseListener(new Swing_mouse_adapter(Gfo_invk_cmd.New_by_key(this, Invk__run_xowa))); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__run_xowa)) { + Runtime_.Exec(run_xowa_cmd); + System_.Exit(); + } + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk__run_xowa = "run_xowa"; + + public static void Run() { + Io_url manifest_url = Env_.AppUrl().GenNewExt(".txt"); + if (!Op_sys.Cur().Tid_is_osx()) + new Xoa_manifest_view(manifest_url); + } +} +class Swing_mouse_adapter extends MouseAdapter { + private final Gfo_invk_cmd cmd; + public Swing_mouse_adapter(Gfo_invk_cmd cmd) {this.cmd = cmd;} + @Override public void mouseClicked(MouseEvent ev) { + try {cmd.Exec();} + catch (Exception e) { + System.out.println(Err_.Message_gplx_full(e)); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/apps/Xoa_manifest_wkr.java b/400_xowa/src/gplx/xowa/addons/apps/updates/apps/Xoa_manifest_wkr.java index a27517de8..39665f6c7 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/apps/Xoa_manifest_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/apps/Xoa_manifest_wkr.java @@ -13,3 +13,72 @@ 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.addons.apps.updates.apps; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; +import gplx.core.envs.*; +class Xoa_manifest_wkr { + private final Xoa_manifest_view view; + private final Xoa_manifest_list list = new Xoa_manifest_list(); + private Io_url manifest_url; + private String run_xowa_cmd; + public Xoa_manifest_wkr(Xoa_manifest_view view) { + this.view = view; + } + public void Init(Io_url manifest_url) { + // load manifest + this.manifest_url = manifest_url; + view.Append("loading manifest from: " + manifest_url.Raw()); + byte[] src = Io_mgr.Instance.LoadFilBry(manifest_url); + + // parse manifest + int nl_pos = Bry_find_.Find_fwd(src, Byte_ascii.Nl); + if (nl_pos == Bry_find_.Not_found) throw Err_.new_wo_type("could not find nl in manifest", "manifest_url", manifest_url.Raw()); + this.run_xowa_cmd = String_.new_u8(src, 0, nl_pos); + list.Load(src, nl_pos + 1, src.length); + + this.Wait(); + } + private void Wait() { + int tries = 0; + while (tries++ < 100) { + gplx.core.threads.Thread_adp_.Sleep(1000); + if (Copy_files()) + break; + else { + String topmost = "#error"; + if (list.Len() > 0) { + Xoa_manifest_item item = (Xoa_manifest_item)list.Get_at(0); + topmost = item.Src().Raw(); + } + view.Append("waiting for XOWA to release file: " + topmost); + } + } + this.On_exit(); + } + public void On_exit() { + Io_mgr.Instance.DeleteFil(manifest_url); + view.Mark_done(run_xowa_cmd); + } + private boolean Copy_files() { + // loop list and copy items + int len = list.Len(); + int idx = 0; + for (idx = 0; idx < len; idx++) { + Xoa_manifest_item item = (Xoa_manifest_item)list.Get_at(idx); + try { + Io_mgr.Instance.DeleteFil_args(item.Trg()).MissingFails_off().Exec(); + Io_mgr.Instance.CopyFil(item.Src(), item.Trg(), true); + view.Append(String_.Format("copied file: src={0} trg={1}", item.Src().Raw(), item.Trg().Raw())); + } + catch (Exception exc) { + view.Append(String_.Format("failed to copy file: src={0} trg={1} err={2}", item.Src().Raw(), item.Trg().Raw(), Err_.Message_lang(exc))); + return false; + } + } + + // remove completed items + for (int i = 0; i < idx; i++) { + list.Del_at(0); + } + return true; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/dbs/Xoa_app_version_itm.java b/400_xowa/src/gplx/xowa/addons/apps/updates/dbs/Xoa_app_version_itm.java index a27517de8..6890246fc 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/dbs/Xoa_app_version_itm.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/dbs/Xoa_app_version_itm.java @@ -13,3 +13,38 @@ 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.addons.apps.updates.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; +public class Xoa_app_version_itm { + public Xoa_app_version_itm(int id, String name, String date, int priority, String url, String summary, String details) { + this.id = id; + this.name = name; + this.date = date; + this.priority = priority; + this.url = url; + this.summary = summary; + this.details = details; + } + public int Id() {return id;} private final int id; + public String Name() {return name;} private final String name; + public String Date() {return date;} private final String date; + public int Priority() {return priority;} private final int priority; + public String Url() {return url;} private final String url; + public String Summary() {return summary;} private final String summary; + public String Details() {return details;} private final String details; + public String Package_url() { + String folder = url; + if (String_.Len_eq_0(folder)) + folder = "https://github.com/gnosygnu/xowa/releases/releases/tag"; + return String_.Format("{0}/v{1}/xowa_app_{2}_v{1}.zip", folder, name, Xoa_app_.Op_sys_str); + } + + public static final int Priority__major = 7, Priority__minor = 5, Priority__trivial = 3; + public static String Priority__to_name(int v) { + switch (v) { + case Priority__trivial: return "trivial"; + case Priority__minor: return "minor"; + case Priority__major: return "major"; + default: throw Err_.new_unhandled_default(v); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/dbs/Xoa_app_version_tbl.java b/400_xowa/src/gplx/xowa/addons/apps/updates/dbs/Xoa_app_version_tbl.java index a27517de8..9807887c8 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/dbs/Xoa_app_version_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/dbs/Xoa_app_version_tbl.java @@ -13,3 +13,61 @@ 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.addons.apps.updates.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; +import gplx.dbs.*; import gplx.dbs.utls.*; +public class Xoa_app_version_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld__version_id, fld__version_name, fld__version_date, fld__version_priority, fld__version_url, fld__version_summary, fld__version_details; + private final Db_conn conn; + public Xoa_app_version_tbl(Db_conn conn) { + this.conn = conn; + this.fld__version_id = flds.Add_int_pkey("version_id"); + this.fld__version_name = flds.Add_str("version_name", 32); + this.fld__version_date = flds.Add_str("version_date", 32); + this.fld__version_priority = flds.Add_int("version_priority"); // 3:trivial; 5:minor; 7:major; + this.fld__version_url = flds.Add_str("version_url", 255); + this.fld__version_summary = flds.Add_str("version_summary", 255); + this.fld__version_details = flds.Add_text("version_details"); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name = TBL_NAME; + public void Create_tbl() { + conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds)); + } + public Xoa_app_version_itm[] Select_by_id(int id) { + String sql = Db_sql_.Make_by_fmt(String_.Ary + ( "SELECT *" + , "FROM app_version" + , "WHERE version_id > {0}" + , "ORDER BY version_id" + ), id); + + Db_rdr rdr = conn.Stmt_sql(sql).Exec_select__rls_auto(); + try { + List_adp list = List_adp_.New(); + while (rdr.Move_next()) { + list.Add(Load(rdr)); + } + return (Xoa_app_version_itm[])list.To_ary_and_clear(Xoa_app_version_itm.class); + } finally {rdr.Rls();} + } + public Xoa_app_version_itm Select_by_name_or_null(String name) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld__version_name).Crt_str(fld__version_name, name).Exec_select__rls_auto(); + try { + return rdr.Move_next() ? Load(rdr) : null; + } finally {rdr.Rls();} + } + private Xoa_app_version_itm Load(Db_rdr rdr) { + return new Xoa_app_version_itm + ( rdr.Read_int(fld__version_id) + , rdr.Read_str(fld__version_name) + , rdr.Read_str(fld__version_date) + , rdr.Read_int(fld__version_priority) + , rdr.Read_str(fld__version_url) + , rdr.Read_str(fld__version_summary) + , rdr.Read_str(fld__version_details) + ); + } + public void Rls() {} + public static final String TBL_NAME = "app_version"; +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/dbs/Xoa_update_db_mgr.java b/400_xowa/src/gplx/xowa/addons/apps/updates/dbs/Xoa_update_db_mgr.java index a27517de8..493b03490 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/dbs/Xoa_update_db_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/dbs/Xoa_update_db_mgr.java @@ -13,3 +13,16 @@ 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.addons.apps.updates.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; +import gplx.dbs.*; import gplx.dbs.utls.*; +public class Xoa_update_db_mgr { + public Xoa_update_db_mgr(Io_url url) { + this.url = url; + this.conn = Db_conn_bldr.Instance.Get_or_autocreate(true, url); + this.tbl__app_version = new Xoa_app_version_tbl(conn); + conn.Meta_tbl_assert(tbl__app_version); + } + public Io_url Url() {return url;} private final Io_url url; + public Db_conn Conn() {return conn;} private final Db_conn conn; + public Xoa_app_version_tbl Tbl__app_version() {return tbl__app_version;} private final Xoa_app_version_tbl tbl__app_version; +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/dbs/Xoa_update_db_mgr_.java b/400_xowa/src/gplx/xowa/addons/apps/updates/dbs/Xoa_update_db_mgr_.java index a27517de8..3af1cb83e 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/dbs/Xoa_update_db_mgr_.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/dbs/Xoa_update_db_mgr_.java @@ -13,3 +13,47 @@ 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.addons.apps.updates.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; +import gplx.langs.jsons.*; +import gplx.xowa.addons.apps.cfgs.*; +public class Xoa_update_db_mgr_ { + public static Io_url Url(Xoa_app app) {return app.Fsys_mgr().Root_dir().GenSubFil_nest("user", "install", "update", "xoa_update.sqlite3");} + public static Xoa_app_version_itm[] Select(Io_url db_url, int id) { + Xoa_update_db_mgr db_mgr = new Xoa_update_db_mgr(db_url); + return db_mgr.Tbl__app_version().Select_by_id(id); + } + public static boolean Download_from_inet(Xoa_app app, boolean exit_if_too_soon, Io_url db_url) { + try { + // exit if web_access disabled + if (!gplx.core.ios.IoEngine_system.Web_access_enabled) return false; + + // exit if inet checked too soon + if (exit_if_too_soon) { + Xocfg_mgr cfg = app.Cfg(); + int inet_interval = cfg.Get_int_app_or(Cfg__inet_interval, 7); + DateAdp inet_date = cfg.Get_date_app_or(Cfg__inet_date, DateAdp_.MinValue); + if (Datetime_now.Get().Diff_days(inet_date) < inet_interval) return false; + cfg.Set_date_app(Cfg__inet_date, Datetime_now.Get()); + } + + // get online version + String latest_version_url = app.Cfg().Get_str_app_or("xowa.app.update.inet.server_url", "http://xowa.org"); // CFG:Cfg__ + byte[] latest_version_bry = Io_mgr.Instance.DownloadFil_args("", Io_url_.Empty).Exec_as_bry(latest_version_url + "/admin/app_update/xoa_update.json"); + Json_doc jdoc = new Json_parser().Parse(latest_version_bry); + int latest_version_id = jdoc.Root_nde().Get_as_int_or("version_id", -1); + + // compare and download if necessary + if (latest_version_id > Xoa_app_.Version_id) { + String update_db_url = latest_version_url + "/admin/app_update/xoa_update.sqlite3"; + Io_mgr.Instance.DownloadFil(update_db_url, db_url); + } + return true; + } catch (Exception exc) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "app_update:db download failed;err=~{0}", Err_.Message_gplx_log(exc)); + return false; + } + } + private static final String + Cfg__inet_interval = "xowa.app.update.inet.check_interval" + , Cfg__inet_date = "xowa.app.update.inet.check_date"; +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/js/Xojs_wkr__base.java b/400_xowa/src/gplx/xowa/addons/apps/updates/js/Xojs_wkr__base.java index a27517de8..0d737fb38 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/js/Xojs_wkr__base.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/js/Xojs_wkr__base.java @@ -13,3 +13,86 @@ 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.addons.apps.updates.js; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; +import gplx.core.gfobjs.*; import gplx.core.progs.*; import gplx.core.progs.rates.*; +import gplx.xowa.guis.cbks.*; +public class Xojs_wkr__base implements Gfo_prog_ui, Gfo_invk { + private final Gfo_invk_cmd done_cbk; + private final Gfo_invk_cmd fail_cbk; + private final Gfo_rate_list rate_list = new Gfo_rate_list(32); + private final long notify_delay = 1000; + private final double delta_threshold = .25d; // allow variance of up to 25% before updating rate + private final String js_cbk, task_type; + private long time_prv; + private double rate_cur; + public Xojs_wkr__base(Xog_cbk_mgr cbk_mgr, Xog_cbk_trg cbk_trg, String js_cbk, Gfo_invk_cmd done_cbk, Gfo_invk_cmd fail_cbk, String task_type) { + this.cbk_mgr = cbk_mgr; + this.cbk_trg = cbk_trg; + this.js_cbk = js_cbk; + this.done_cbk = done_cbk; + this.fail_cbk = fail_cbk; + this.task_type = task_type; + rate_list.Add(1024 * 1024, 1); // add default rate of 1 MB per second; + } + public void Exec() { + try { + this.time_prv = gplx.core.envs.System_.Ticks(); + this.Exec_run(); + done_cbk.Exec_by_ctx(GfsCtx.Instance, GfoMsg_.new_cast_("m").Add("v", this)); + } catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to run task; task=~{0} err=~{1}", task_type, Err_.Message_gplx_log(e)); + cbk_mgr.Send_notify(cbk_trg, String_.Format("failed to run task: task={0} err={1}", task_type, Err_.Message_lang(e))); + if (fail_cbk != null) + fail_cbk.Exec(); + } + } + @gplx.Virtual protected void Exec_run() {} + public void Exec_async(String thread_name) { + gplx.core.threads.Thread_adp_.Start_by_key(thread_name + ".download", this, Invk__exec); + } + + public Xog_cbk_mgr Cbk_mgr() {return cbk_mgr;} private final Xog_cbk_mgr cbk_mgr; + public Xog_cbk_trg Cbk_trg() {return cbk_trg;} private final Xog_cbk_trg cbk_trg; + + public boolean Canceled() {return canceled;} private boolean canceled; + public void Cancel() {this.canceled = true;} + public byte Prog_status() {return status;} + public void Prog_status_(byte v) {status = v;} private byte status; + public long Prog_data_cur() {return data_cur;} private long data_cur; + public long Prog_data_end() {return data_end;} private long data_end; + protected void Prog_data_end_(long v) {this.data_end = v;} + public void Prog_notify_by_msg(String msg) {} + public boolean Prog_notify_and_chk_if_suspended(long new_data_cur, long new_data_end) { + if (status == Gfo_prog_ui_.Status__suspended) return true; // task paused by ui; exit now; + long time_cur = gplx.core.envs.System_.Ticks(); + if (time_cur < time_prv + notify_delay) return false; // message came too soon. ignore it + + // update rate + double rate_now = (rate_list.Add(new_data_cur - data_cur, (time_cur - time_prv))) * 1000; + double delta = Math_.Abs_double((rate_now - rate_cur) / rate_cur); + if ( rate_cur == 0 // rate not set + || delta > delta_threshold) { // rate_now is at least 25% different than rate_prv + if (delta > delta_threshold * 2) // rate_now is > 50% different + rate_cur = rate_now; // update it now + else { + double rate_new = ((rate_now - rate_cur) * .05) + rate_cur; // calc new rate as 5% of difference + // Tfds.Dbg(delta, rate_now, rate_cur, rate_new); + rate_cur = rate_new; + } + } + + // update prog vals + this.time_prv = time_cur; + this.data_cur = new_data_cur; + this.data_end = new_data_end; + + cbk_mgr.Send_json(cbk_trg, js_cbk + , Gfobj_nde.New().Add_str("task_type", task_type).Add_long("prog_data_cur", data_cur).Add_long("prog_data_end", data_end).Add_int("prog_rate", (int)rate_cur)); + return false; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__exec)) this.Exec(); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk__exec = "exec"; +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/js/Xojs_wkr__download.java b/400_xowa/src/gplx/xowa/addons/apps/updates/js/Xojs_wkr__download.java index a27517de8..8d6f15da8 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/js/Xojs_wkr__download.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/js/Xojs_wkr__download.java @@ -13,3 +13,22 @@ 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.addons.apps.updates.js; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; +import gplx.xowa.guis.cbks.*; +import gplx.core.net.downloads.*; +public class Xojs_wkr__download extends Xojs_wkr__base { + public Xojs_wkr__download(Xog_cbk_mgr cbk_mgr, Xog_cbk_trg cbk_trg, String js_cbk, Gfo_invk_cmd done_cbk, Gfo_invk_cmd fail_cbk, String src, Io_url trg, long src_len) {super(cbk_mgr, cbk_trg, js_cbk, done_cbk, fail_cbk, "downloading"); + this.src = src; + this.src_len = src_len; + this.trg = trg; + this.Prog_data_end_(src_len); + } + public String Src() {return src;} private final String src; + public Io_url Trg() {return trg;} private final Io_url trg; + public long Src_len() {return src_len;} private final long src_len; + @Override protected void Exec_run() { + Http_download_wkr wkr = Http_download_wkr_.Proto.Make_new(); + if (wkr.Exec(this, src, trg, src_len) != gplx.core.progs.Gfo_prog_ui_.Status__done) + this.Cbk_mgr().Send_json(this.Cbk_trg(), "xo.app_updater.write_status", gplx.core.gfobjs.Gfobj_nde.New().Add_str("msg", "failed to download update: " + wkr.Fail_msg())); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/js/Xojs_wkr__replace.java b/400_xowa/src/gplx/xowa/addons/apps/updates/js/Xojs_wkr__replace.java index a27517de8..20db9f84e 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/js/Xojs_wkr__replace.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/js/Xojs_wkr__replace.java @@ -13,3 +13,39 @@ 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.addons.apps.updates.js; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; +import gplx.xowa.guis.cbks.*; +public class Xojs_wkr__replace extends Xojs_wkr__base { + private final Io_url src_dir, trg_dir; + private final Io_url[] src_fils; + public Xojs_wkr__replace(Xog_cbk_mgr cbk_mgr, Xog_cbk_trg cbk_trg, String js_cbk, Gfo_invk_cmd done_cbk, Io_url src_dir, Io_url trg_dir) {super(cbk_mgr, cbk_trg, js_cbk, done_cbk, null, "replacing"); + this.src_dir = src_dir; + this.trg_dir = trg_dir; + this.src_fils = Io_mgr.Instance.QueryDir_args(src_dir).Recur_().ExecAsUrlAry(); + this.Prog_data_end_(src_fils.length); + } + public Keyval[] Failed() {return failed;} private Keyval[] failed = Keyval_.Ary_empty; + @Override protected void Exec_run() { + List_adp failed_list = List_adp_.New(); + + int len = src_fils.length; + for (int i = 0; i < len; i++) { + if (this.Prog_notify_and_chk_if_suspended(i, len)) return; // stop if canceled + + Io_url src_fil = src_fils[i]; + Io_url trg_fil = trg_dir.GenSubFil(src_fil.GenRelUrl_orEmpty(src_dir)); + try { + Io_mgr.Instance.DeleteFil(trg_fil); // delete first; will fail if file is in use + Io_mgr.Instance.MoveFil_args(src_fil, trg_fil, true).Exec(); // replace with src file + } catch (Exception exc) { + Gfo_usr_dlg_.Instance.Log_many("failed to delete and move file; file=~{0} msg=~{1}", trg_fil.Raw(), Err_.Message_gplx_log(exc)); + failed_list.Add(Keyval_.new_(src_fil.Raw(), trg_fil.Raw())); + + // try {Io_mgr.Instance.CopyFil(src_fil, trg_fil, true);} // try to copy file anyway + // catch (Exception exc2) {Gfo_usr_dlg_.Instance.Log_many("failed to fopy file; file=~{0} msg=~{1}", trg_fil.Raw(), Err_.Message_gplx_log(exc2));} + } + } + + this.failed = (Keyval[])failed_list.To_ary_and_clear(Keyval.class); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/js/Xojs_wkr__unzip.java b/400_xowa/src/gplx/xowa/addons/apps/updates/js/Xojs_wkr__unzip.java index a27517de8..8c3a99b95 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/js/Xojs_wkr__unzip.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/js/Xojs_wkr__unzip.java @@ -13,3 +13,19 @@ 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.addons.apps.updates.js; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; +import gplx.xowa.guis.cbks.*; +import gplx.core.ios.zips.*; +public class Xojs_wkr__unzip extends Xojs_wkr__base { + public Xojs_wkr__unzip(Xog_cbk_mgr cbk_mgr, Xog_cbk_trg cbk_trg, String js_cbk, Gfo_invk_cmd done_cbk, Io_url src, Io_url trg, long prog_data_end) {super(cbk_mgr, cbk_trg, js_cbk, done_cbk, null, "unzipping"); + this.src = src; this.trg = trg; + this.Prog_data_end_(prog_data_end); + } + public Io_url Src() {return src;} private final Io_url src; + public Io_url Trg() {return trg;} private final Io_url trg; + @Override protected void Exec_run() { + Io_zip_decompress_cmd decompress = Io_zip_decompress_cmd_.Proto.Make_new(); + List_adp unzip_urls = List_adp_.New(); + decompress.Exec(this, src, trg, unzip_urls); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/specials/Xoa_update_html.java b/400_xowa/src/gplx/xowa/addons/apps/updates/specials/Xoa_update_html.java index a27517de8..1f89d4db1 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/specials/Xoa_update_html.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/specials/Xoa_update_html.java @@ -13,3 +13,62 @@ 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.addons.apps.updates.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; +import gplx.xowa.addons.apps.updates.dbs.*; +import gplx.xowa.specials.*; import gplx.langs.mustaches.*; import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.tags.*; +public class Xoa_update_html extends Xow_special_wtr__base { + @Override protected Io_url Get_addon_dir(Xoa_app app) {return app.Fsys_mgr().Http_root().GenSubDir_nest("bin", "any", "xowa", "addon", "app", "update");} + @Override protected Io_url Get_mustache_fil(Io_url addon_dir) {return addon_dir.GenSubFil_nest("bin", "xoa_update.mustache.html");} + @Override protected Mustache_doc_itm Bld_mustache_root(Xoa_app app) { + return Load(app); + } + private static Mustache_doc_itm Load(Xoa_app app) { + Io_url db_url = Xoa_update_db_mgr_.Url(app); + + // get from internet + boolean web_access_enabled = gplx.core.ios.IoEngine_system.Web_access_enabled; + Xoa_update_db_mgr_.Download_from_inet(app, Bool_.N, db_url); + + // load from db + Xoa_app_version_itm[] db_itms = Xoa_update_db_mgr_.Select(db_url, Xoa_update_startup.Version_cutoff(app)); + + // build root + String check_date = app.Cfg().Get_str_app_or("xowa.app.update.startup.inet_date", null); // CFG:Cfg__ + if (check_date == null) check_date = Datetime_now.Get().XtoStr_fmt_yyyy_MM_dd_HH_mm_ss(); + String build_date = String_.Mid(Xoa_app_.Build_date, 0, String_.FindFwd(Xoa_app_.Build_date, " ")); // remove time to show date only + if (db_itms.length == 0) return new Xoa_update_itm__root(Xoa_app_.Version, build_date, check_date, web_access_enabled, "", "", Xoa_app_version_itm.Priority__trivial, "", "", ""); + + // convert to gui itm + Xoa_app_version_itm db_itm = db_itms[0]; + Xoa_update_itm__root root = new Xoa_update_itm__root(Xoa_app_.Version, build_date, check_date, web_access_enabled, db_itm.Name(), db_itm.Date(), db_itm.Priority(), db_itm.Summary(), db_itm.Details(), db_itm.Package_url()); + root.Itms_(To_gui_itm(db_itms)); + return root; + } + private static Xoa_update_itm__leaf[] To_gui_itm(Xoa_app_version_itm[] db_itms) { + int len = db_itms.length; + Xoa_update_itm__leaf[] rv = new Xoa_update_itm__leaf[len]; + for (int i = 0; i < len; i++) { + rv[i] = To_gui_itm(db_itms[i]); + } + return rv; + } + private static Xoa_update_itm__leaf To_gui_itm(Xoa_app_version_itm db_itm) { + return new Xoa_update_itm__leaf(db_itm.Name(), db_itm.Date(), db_itm.Priority(), db_itm.Summary(), db_itm.Details(), db_itm.Package_url()); + } + @Override protected void Bld_tags(Xoa_app app, Io_url addon_dir, Xopage_html_data page_data) { + Xopg_tag_mgr head_tags = page_data.Head_tags(); + Xopg_tag_wtr_.Add__baselib (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xocss (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xohelp (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xolog (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__gui__progbars (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xoajax (head_tags, app.Fsys_mgr().Http_root(), app); + Xopg_tag_wtr_.Add__jquery (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xonotify (head_tags, app.Fsys_mgr().Http_root()); + Xopg_alertify_.Add_tags (head_tags, app.Fsys_mgr().Http_root()); + + head_tags.Add(Xopg_tag_itm.New_css_file(addon_dir.GenSubFil_nest("bin", "xoa_update.css"))); + head_tags.Add(Xopg_tag_itm.New_js_file(addon_dir.GenSubFil_nest("bin", "xoa_update.js"))); + head_tags.Add(Xopg_tag_itm.New_js_file(addon_dir.GenSubFil_nest("bin", "xobc.util.js"))); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/specials/Xoa_update_itm__leaf.java b/400_xowa/src/gplx/xowa/addons/apps/updates/specials/Xoa_update_itm__leaf.java index a27517de8..d1177670a 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/specials/Xoa_update_itm__leaf.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/specials/Xoa_update_itm__leaf.java @@ -13,3 +13,33 @@ 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.addons.apps.updates.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; +import gplx.langs.mustaches.*; +import gplx.xowa.addons.apps.updates.dbs.*; +class Xoa_update_itm__leaf implements Mustache_doc_itm { + private final String version, date, summary, details, package_url; + private final int priority; + public Xoa_update_itm__leaf(String version, String date, int priority, String summary, String details, String package_url) { + this.version = version; + this.date = date; + this.priority = priority; + this.summary = summary; + this.details = details; + this.package_url = package_url; + } + @gplx.Virtual public boolean Mustache__write(String k, Mustache_bfr bfr) { + if (String_.Eq(k, "version")) bfr.Add_str_u8(version); + else if (String_.Eq(k, "date")) bfr.Add_str_u8(date); + else if (String_.Eq(k, "priority")) bfr.Add_str_u8(Xoa_app_version_itm.Priority__to_name(priority)); + else if (String_.Eq(k, "summary")) bfr.Add_str_u8(summary); + else if (String_.Eq(k, "details")) bfr.Add_str_u8(details); + else if (String_.Eq(k, "package_url")) bfr.Add_str_u8(package_url); + return true; + } + @gplx.Virtual public Mustache_doc_itm[] Mustache__subs(String key) { + if (String_.Eq(key, "priority_is_major")) return Mustache_doc_itm_.Ary__bool(priority >= Xoa_app_version_itm.Priority__major); + return Mustache_doc_itm_.Ary__empty; + } + + protected static final Xoa_update_itm__leaf[] Ary__empty = new Xoa_update_itm__leaf[0]; +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/specials/Xoa_update_itm__root.java b/400_xowa/src/gplx/xowa/addons/apps/updates/specials/Xoa_update_itm__root.java index a27517de8..daac22fb4 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/specials/Xoa_update_itm__root.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/specials/Xoa_update_itm__root.java @@ -13,3 +13,33 @@ 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.addons.apps.updates.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; +import gplx.langs.mustaches.*; +class Xoa_update_itm__root extends Xoa_update_itm__leaf { private final String current_version, current_date, check_date; + private final boolean web_access_enabled; + private Xoa_update_itm__leaf[] itms = Xoa_update_itm__leaf.Ary__empty; + public Xoa_update_itm__root + ( String current_version, String current_date, String check_date, boolean web_access_enabled + , String version, String date, int priority, String summary, String details, String package_url + ) {super(version, date, priority, summary, details, package_url); + this.current_version = current_version; + this.current_date = current_date; + this.check_date = check_date; + this.web_access_enabled = web_access_enabled; + } + public void Itms_(Xoa_update_itm__leaf[] v) { + this.itms = v; + } + @Override public boolean Mustache__write(String k, Mustache_bfr bfr) { + if (String_.Eq(k, "current_version")) bfr.Add_str_u8(current_version); + else if (String_.Eq(k, "current_date")) bfr.Add_str_u8(current_date); + else if (String_.Eq(k, "check_date")) bfr.Add_str_u8(check_date); + return super.Mustache__write (k, bfr); + } + @Override public Mustache_doc_itm[] Mustache__subs(String k) { + if (String_.Eq(k, "itms")) return itms; + else if (String_.Eq(k, "itms_exist")) return Mustache_doc_itm_.Ary__bool(itms.length > 0); + else if (String_.Eq(k, "web_access_enabled")) return Mustache_doc_itm_.Ary__bool(web_access_enabled); + return super.Mustache__subs(k); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/specials/Xoa_update_special.java b/400_xowa/src/gplx/xowa/addons/apps/updates/specials/Xoa_update_special.java index a27517de8..ae562a2f8 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/specials/Xoa_update_special.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/specials/Xoa_update_special.java @@ -13,3 +13,14 @@ 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.addons.apps.updates.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; +import gplx.xowa.specials.*; import gplx.core.net.qargs.*; +public class Xoa_update_special implements Xow_special_page { + public void Special__gen(Xow_wiki wiki, Xoa_page page, Xoa_url url, Xoa_ttl ttl) { + new Xoa_update_html().Bld_page_by_mustache(wiki.App(), page, this); + } + Xoa_update_special(Xow_special_meta special__meta) {this.special__meta = special__meta;} + public Xow_special_meta Special__meta() {return special__meta;} private final Xow_special_meta special__meta; + public Xow_special_page Special__clone() {return this;} + public static final Xow_special_page Prototype = new Xoa_update_special(Xow_special_meta.New_xo("XowaAppUpdate", "App Update")); +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/specials/svcs/Xoa_update_bridge.java b/400_xowa/src/gplx/xowa/addons/apps/updates/specials/svcs/Xoa_update_bridge.java index a27517de8..8ba83e313 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/specials/svcs/Xoa_update_bridge.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/specials/svcs/Xoa_update_bridge.java @@ -13,3 +13,32 @@ 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.addons.apps.updates.specials.svcs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; import gplx.xowa.addons.apps.updates.specials.*; +import gplx.langs.jsons.*; import gplx.xowa.htmls.bridges.*; +public class Xoa_update_bridge implements Bridge_cmd_itm { + private Xoa_app app; + public void Init_by_app(Xoa_app app) { + this.app = app; + } + public String Exec(Json_nde data) { + byte proc_id = proc_hash.Get_as_byte_or(data.Get_as_bry_or(Bridge_cmd_mgr.Msg__proc, null), Byte_ascii.Max_7_bit); + Json_nde args = data.Get_kv(Bridge_cmd_mgr.Msg__args).Val_as_nde(); + + Xoa_update_svc svc = new Xoa_update_svc(app); + switch (proc_id) { + case Proc__install: svc.Install(args.Get_as_str("version"));break; + case Proc__skip: svc.Skip(args.Get_as_str("version")); break; + default: throw Err_.new_unhandled_default(proc_id); + } + return ""; + } + + private static final byte Proc__install = 0, Proc__skip = 1; + private static final Hash_adp_bry proc_hash = Hash_adp_bry.cs() + .Add_str_byte("install" , Proc__install) + .Add_str_byte("skip" , Proc__skip) + ; + + public byte[] Key() {return BRIDGE_KEY;} public static final byte[] BRIDGE_KEY = Bry_.new_a7("app.updater"); + public static final Xoa_update_bridge Prototype = new Xoa_update_bridge(); Xoa_update_bridge() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/specials/svcs/Xoa_update_svc.java b/400_xowa/src/gplx/xowa/addons/apps/updates/specials/svcs/Xoa_update_svc.java index a27517de8..b3e146564 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/specials/svcs/Xoa_update_svc.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/specials/svcs/Xoa_update_svc.java @@ -13,3 +13,134 @@ 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.addons.apps.updates.specials.svcs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; import gplx.xowa.addons.apps.updates.specials.*; +import gplx.xowa.guis.cbks.*; +import gplx.core.gfobjs.*; +import gplx.xowa.addons.apps.updates.dbs.*; import gplx.xowa.addons.apps.updates.js.*; +import gplx.xowa.addons.apps.updates.apps.*; +import gplx.core.envs.*; +class Xoa_update_svc implements Gfo_invk { + private Xoa_app app; + private Io_url app_root_dir, update_dir, update_jar_fil, version_root; + private Xoa_app_version_itm version_itm; + private Xoa_update_db_mgr db_mgr; + public Xoa_update_svc(Xoa_app app) {this.app = app;} + private Xoa_update_db_mgr Init_db() { + this.app_root_dir = app.Fsys_mgr().Root_dir(); + this.update_dir = app_root_dir.GenSubDir_nest("user", "install", "update"); + Io_url update_db_fil = update_dir.GenSubFil_nest("xoa_update.sqlite3"); + return new Xoa_update_db_mgr(update_db_fil); + } + public void Install(String version_name) { + // get app_version from db + this.db_mgr = Init_db(); + this.version_itm = db_mgr.Tbl__app_version().Select_by_name_or_null(version_name); + + // get src, trg, etc.. + String src = version_itm.Package_url(); + Io_url trg = update_dir.GenSubFil_nest("temp", version_itm.Name(), version_itm.Name() + ".zip"); // EX: "/xowa/user/app/install/update/temp/4.1.0/4.1.0.zip" + this.version_root = trg.OwnerDir(); + Io_mgr.Instance.DeleteDirDeep(version_root); + long src_len = -1; + + // start download + Xojs_wkr__download download_wkr = new Xojs_wkr__download + ( app.Gui__cbk_mgr(), Xog_cbk_trg.New(Xoa_update_special.Prototype.Special__meta().Ttl_bry()) + , "xo.app_updater.download__prog", Gfo_invk_cmd.New_by_key(this, Invk__download_done), Gfo_invk_cmd.New_by_key(this, Invk__download_fail), src, trg, src_len); + download_wkr.Exec_async("app_updater"); + } + public void Skip(String version_name) { + Xoa_update_db_mgr db_mgr = Init_db(); + Xoa_update_startup.Version_cutoff_(app, db_mgr.Tbl__app_version().Select_by_name_or_null(version_name).Id()); + } + private void On_download_done(GfoMsg m) { + Xojs_wkr__download download_wkr = (Xojs_wkr__download)m.ReadObj("v"); + Io_url src = download_wkr.Trg(); + Io_url trg = src.OwnerDir().GenSubDir(src.NameOnly() + "_unzip"); + Xojs_wkr__unzip unzip_wkr = new Xojs_wkr__unzip(download_wkr.Cbk_mgr(), download_wkr.Cbk_trg(), "xo.app_updater.download__prog", Gfo_invk_cmd.New_by_key(this, Invk__unzip_done), src, trg, -1); + unzip_wkr.Exec_async("app_updater"); + } + private void On_download_fail(GfoMsg m) { + // in case of bad urls or withdrawn patches, delete update_db and force redownload + db_mgr.Conn().Rls_conn(); + Io_mgr.Instance.DeleteFil(db_mgr.Url()); + Xoa_update_db_mgr_.Download_from_inet(app, Bool_.N, db_mgr.Url()); + } + private void On_unzip_done(GfoMsg m) { + Xojs_wkr__unzip unzip_wkr = (Xojs_wkr__unzip)m.ReadObj("v"); + Io_url src = unzip_wkr.Trg(); + Io_url trg = app_root_dir; + + // delete zip + Io_mgr.Instance.DeleteFil(unzip_wkr.Src()); + + // copy update_jar + Io_url src_jar_fil = src.GenSubFil_nest("bin", "any", "xowa", "addon", "app", "update", "xoa_update.jar"); + this.update_jar_fil = app_root_dir.GenSubFil_nest("user", "install", "update", "xoa_update.jar"); + Io_mgr.Instance.MoveFil_args(src_jar_fil, update_jar_fil, true).Exec(); + + // run replace_wkr + Xojs_wkr__replace replace_wkr = new Xojs_wkr__replace(unzip_wkr.Cbk_mgr(), unzip_wkr.Cbk_trg(), "xo.app_updater.download__prog", Gfo_invk_cmd.New_by_key(this, Invk__replace_done), src, trg); + replace_wkr.Exec_async("app_updater"); + } + private void On_replace_done(GfoMsg m) { + Xojs_wkr__replace replace_wkr = (Xojs_wkr__replace)m.ReadObj("v"); + + // get failed + Xoa_manifest_list list = new Xoa_manifest_list(); + Keyval[] failed_ary = replace_wkr.Failed(); + int len = failed_ary.length; + for (int i = 0; i < len; i++) { + Keyval failed = failed_ary[i]; + list.Add(Io_url_.new_fil_(failed.Key()), Io_url_.new_fil_(failed.Val_to_str_or_empty())); + } + + // write failed + Bry_bfr bfr = Bry_bfr_.New(); + String app_update_cfg = app.Cfg().Get_str_app_or("xowa.app.update.restart_cmd", ""); // CFG:Cfg__ + bfr.Add_str_u8(App__update__restart_cmd(app_update_cfg, Env_.AppUrl(), Op_sys.Cur().Tid(), Op_sys.Cur().Bitness()) + "\n"); + list.Save(bfr); + Io_url manifest_url = update_dir.GenSubFil("xoa_update_manifest.txt"); + Io_mgr.Instance.SaveFilBfr(manifest_url, bfr); + + // update prog as finished + replace_wkr.Cbk_mgr().Send_json(replace_wkr.Cbk_trg(), "xo.app_updater.download__prog", Gfobj_nde.New().Add_bool("done", true)); + Xoa_update_startup.Version_cutoff_(app, version_itm.Id()); + + // run standalone app + Runtime_.Exec("java -jar " + update_jar_fil.Raw()+ " " + manifest_url.Raw() + " " + version_root.Raw()); + System_.Exit(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__download_done)) On_download_done(m); + else if (ctx.Match(k, Invk__download_fail)) On_download_fail(m); + else if (ctx.Match(k, Invk__unzip_done)) On_unzip_done(m); + else if (ctx.Match(k, Invk__replace_done)) On_replace_done(m); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk__download_done = "download_done", Invk__download_fail = "download_fail", Invk__unzip_done = "unzip_done", Invk__replace_done = "replace_done"; + public static String App__update__restart_cmd(String current, Io_url app_url, byte op_sys_tid, byte bitness) { + String rv = current; + if (!String_.Eq(rv, String_.Empty)) return rv; // something specified; return it + String bitness_str = bitness == Op_sys.Bitness_32 ? "" : "_64"; + Io_url root_dir = app_url.OwnerDir(); + + String prepend_sh = op_sys_tid == Op_sys.Tid_wnt ? "" : "sh "; + String file_fmt = ""; + switch (op_sys_tid) { + case Op_sys.Tid_lnx: + file_fmt = "xowa_linux{0}.sh"; + break; + case Op_sys.Tid_osx: + file_fmt = "xowa_macosx{0}"; + break; + case Op_sys.Tid_wnt: + file_fmt = "xowa{0}.exe"; + break; + default: + throw Err_.new_unhandled_default(op_sys_tid); + } + return prepend_sh + root_dir.GenSubFil(String_.Format(file_fmt, bitness_str)).Raw(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/apps/updates/specials/svcs/Xoa_update_svc__tst.java b/400_xowa/src/gplx/xowa/addons/apps/updates/specials/svcs/Xoa_update_svc__tst.java index a27517de8..13c6bd646 100644 --- a/400_xowa/src/gplx/xowa/addons/apps/updates/specials/svcs/Xoa_update_svc__tst.java +++ b/400_xowa/src/gplx/xowa/addons/apps/updates/specials/svcs/Xoa_update_svc__tst.java @@ -13,3 +13,23 @@ 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.addons.apps.updates.specials.svcs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.apps.*; import gplx.xowa.addons.apps.updates.*; import gplx.xowa.addons.apps.updates.specials.*; +import org.junit.*; import gplx.core.tests.*; import gplx.core.envs.*; +public class Xoa_update_svc__tst { + private final Xoa_update_svc__fxt fxt = new Xoa_update_svc__fxt(); + @Test public void Restart_cmd() { + Io_url jar_fil = Io_url_.new_dir_("/home/gnosygnu/xowa/xowa.jar"); + fxt.Test__restart_cmd("manual" , jar_fil, Op_sys.Tid_lnx, Op_sys.Bitness_64, "manual"); + fxt.Test__restart_cmd("" , jar_fil, Op_sys.Tid_lnx, Op_sys.Bitness_64, "sh /home/gnosygnu/xowa/xowa_linux_64.sh"); + fxt.Test__restart_cmd("" , jar_fil, Op_sys.Tid_lnx, Op_sys.Bitness_32, "sh /home/gnosygnu/xowa/xowa_linux.sh"); + fxt.Test__restart_cmd("" , jar_fil, Op_sys.Tid_osx, Op_sys.Bitness_64, "sh /home/gnosygnu/xowa/xowa_macosx_64"); + fxt.Test__restart_cmd("" , jar_fil, Op_sys.Tid_osx, Op_sys.Bitness_32, "sh /home/gnosygnu/xowa/xowa_macosx"); + fxt.Test__restart_cmd("" , jar_fil, Op_sys.Tid_wnt, Op_sys.Bitness_64, "/home/gnosygnu/xowa/xowa_64.exe"); + fxt.Test__restart_cmd("" , jar_fil, Op_sys.Tid_wnt, Op_sys.Bitness_32, "/home/gnosygnu/xowa/xowa.exe"); + } +} +class Xoa_update_svc__fxt { + public void Test__restart_cmd(String current, Io_url app_url, byte op_sys_tid, byte bitness, String expd) { + Gftest.Eq__str(expd, Xoa_update_svc.App__update__restart_cmd(current, app_url, op_sys_tid, bitness), "restart_cmd"); + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/Xoac_wiki_cfg_bldr_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/Xoac_wiki_cfg_bldr_cmd.java index a27517de8..5afbd3349 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/Xoac_wiki_cfg_bldr_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/Xoac_wiki_cfg_bldr_cmd.java @@ -13,3 +13,16 @@ 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.addons.bldrs.app_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.core.strings.*; +public class Xoac_wiki_cfg_bldr_cmd { + public Xoac_wiki_cfg_bldr_cmd(String key, String text) {this.key = key; this.text = text;} + public String Key() {return key;} private String key; + public String Text() {return text;} private String text; + public String Exec(String_bldr sb, String wiki, String src) { + String sect_txt_bgn = sb.Add("// ").Add(key).Add(".bgn\n").To_str_and_clear(); + String sect_txt_end = sb.Add("// ").Add(key).Add(".end\n").To_str_and_clear(); + String sect_txt_all = sb.Add(sect_txt_bgn).Add(text + "\n").Add(sect_txt_end).To_str_and_clear(); // NOTE: always add \n; convenience for single line cmds + return src + sect_txt_all; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/Xoac_wiki_cfg_bldr_fil.java b/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/Xoac_wiki_cfg_bldr_fil.java index a27517de8..fb5fd5f63 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/Xoac_wiki_cfg_bldr_fil.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/Xoac_wiki_cfg_bldr_fil.java @@ -13,3 +13,21 @@ 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.addons.bldrs.app_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +public class Xoac_wiki_cfg_bldr_fil implements Gfo_invk { + public Xoac_wiki_cfg_bldr_fil(String wiki) {this.wiki = wiki;} + public String Wiki() {return wiki;} private String wiki; + public int Itms_count() {return list.Count();} + public Xoac_wiki_cfg_bldr_cmd Itms_get_at(int i) {return (Xoac_wiki_cfg_bldr_cmd)list.Get_at(i);} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_new_cmd_)) {Itms_add(m.ReadStr("id"), m.ReadStr("text"));} + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_new_cmd_ = "new_cmd_"; + public Xoac_wiki_cfg_bldr_cmd Itms_add(String key, String text) { + Xoac_wiki_cfg_bldr_cmd rv = new Xoac_wiki_cfg_bldr_cmd(key, text); + list.Add(rv); + return rv; + } + List_adp list = List_adp_.New(); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/Xob_wiki_cfg_bldr.java b/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/Xob_wiki_cfg_bldr.java index a27517de8..07ae06ae0 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/Xob_wiki_cfg_bldr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/Xob_wiki_cfg_bldr.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.addons.bldrs.app_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.core.strings.*; +import gplx.xowa.bldrs.*; +public class Xob_wiki_cfg_bldr implements Gfo_invk { + public Xob_wiki_cfg_bldr(Xob_bldr bldr) {this.app = bldr.App();} private Xoae_app app; + public void Exec() { + int len = hash.Count(); + for (int i = 0; i < len; i++) { + Xoac_wiki_cfg_bldr_fil fil = (Xoac_wiki_cfg_bldr_fil)hash.Get_at(i); + Exec_fil(fil); + } + } + private void Exec_fil(Xoac_wiki_cfg_bldr_fil fil) { + String wiki_key = fil.Wiki(); + Io_url cfg_file = app.Fsys_mgr().Cfg_wiki_core_dir().GenSubFil(wiki_key + ".gfs"); + String cfg_text = Io_mgr.Instance.LoadFilStr_args(cfg_file).MissingIgnored_().Exec(); + int len = fil.Itms_count(); + String_bldr sb = String_bldr_.new_(); + for (int i = 0; i < len; i++) { + Xoac_wiki_cfg_bldr_cmd cmd = fil.Itms_get_at(i); + cfg_text = cmd.Exec(sb, wiki_key, cfg_text); + } + Io_mgr.Instance.SaveFilStr(cfg_file, cfg_text); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_get)) return Itms_get_or_new(m.ReadStr("v")); + else if (ctx.Match(k, Invk_run)) Exec(); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_get = "get", Invk_run = "run"; + public void Clear() {hash.Clear();} + public Xoac_wiki_cfg_bldr_fil Itms_get_or_new(String wiki) { + Xoac_wiki_cfg_bldr_fil rv = (Xoac_wiki_cfg_bldr_fil)hash.Get_by(wiki); + if (rv == null) { + rv = new Xoac_wiki_cfg_bldr_fil(wiki); + hash.Add(wiki, rv); + } + return rv; + } private Ordered_hash hash = Ordered_hash_.New(); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/Xob_wiki_cfg_bldr_tst.java b/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/Xob_wiki_cfg_bldr_tst.java index a27517de8..475408e43 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/Xob_wiki_cfg_bldr_tst.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/Xob_wiki_cfg_bldr_tst.java @@ -13,3 +13,165 @@ 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.addons.bldrs.app_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import org.junit.*; import gplx.core.strings.*; +public class Xob_wiki_cfg_bldr_tst { + Xob_wiki_cfg_bldr_fxt fxt = new Xob_wiki_cfg_bldr_fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Exec() { + fxt .Init_cmd("en.wikipedia.org", "key0", "en.val0") + .Init_cmd("en.wikipedia.org", "key1", "en.val1") + .Init_cmd("fr.wikipedia.org", "key0", "fr.val0") + .Init_cmd("fr.wikipedia.org", "key1", "fr.val1") + .Expd_txt("en.wikipedia.org", String_.Concat_lines_nl + ( "// key0.bgn" + , "en.val0" + , "// key0.end" + , "// key1.bgn" + , "en.val1" + , "// key1.end" + )) + .Expd_txt("fr.wikipedia.org", String_.Concat_lines_nl + ( "// key0.bgn" + , "fr.val0" + , "// key0.end" + , "// key1.bgn" + , "fr.val1" + , "// key1.end" + )) + .Test() + ; + fxt .Clear() + .Init_cmd("en.wikipedia.org", "key2", "en.val2") + .Expd_txt("en.wikipedia.org", String_.Concat_lines_nl + ( "// key0.bgn" + , "en.val0" + , "// key0.end" + , "// key1.bgn" + , "en.val1" + , "// key1.end" + , "// key2.bgn" + , "en.val2" + , "// key2.end" + )); + } +// @Test public void Lang_names_run() { +// Io_url dir = Io_url_.new_dir_("/var/www/mediawiki/languages/messages/"); +// Io_url[] fils = Io_mgr.Instance.QueryDir_args(dir).ExecAsUrlAry(); +// int fils_len = fils.length; +// String_bldr sb = String_bldr_.new_(); +// for (int i = 0; i < fils_len; i++) { +// Io_url fil = fils[i]; +// String lang_code = String_.Lower(String_.Replace(fil.NameOnly(), "Messages", "")); +// String txt = Io_mgr.Instance.LoadFilStr(fil); +// String[] lines = String_.Split(txt, '\n'); +// String line = lines[1]; +// line = String_.Replace(line, "/** ", ""); +// int pos = String_.FindBwd(line, " ("); +// if (pos == String_.Find_none) continue; // en; en_rtl have no "language" line +// if ( String_.Has_at_bgn(lang_code, "be_") +// || String_.Has_at_bgn(lang_code, "crh_") +// || String_.Has_at_bgn(lang_code, "kk_") +// || String_.Has_at_bgn(lang_code, "ku_") +// || String_.Has_at_bgn(lang_code, "sr_") +// || String_.In(lang_code, "de_formal", "nb", "nl_informal", "nn", "no") +// ) { +// int new_pos = String_.FindBwd(line, " (", pos - 1); +// if (new_pos != String_.Find_none) +// pos = new_pos; +// } +// line = Replace_by_pos(line, pos, pos + 2, "|"); +// int line_len = String_.Len(line); +// if (line_len > 0) +// line = String_.MidByLen(line, 0, line_len - 1); +// String[] terms = String_.Split(line, '|'); +// sb.Add(lang_code).Add("|").Add(String_.Trim(terms[0])).Add("|").Add(String_.Trim(terms[1])).Add("\n"); +// } +// Tfds.Dbg(sb.To_str_and_clear()); +// } + @Test public void Ns_aliases() { + Io_mgr.Instance.InitEngine_mem(); + Io_mgr.Instance.SaveFilStr("mem/en.wikipedia.org/w/api.php?action=query&format=xml&meta=siteinfo&siprop=namespacealiases", String_.Concat_lines_nl + ( "" + , "" + , "" + , "WP" + , "" + , "WT" + , "" + , "Image" + , "" + , "Image talk" + , "" + , "" + , "" + , "" + )); + String[] wikis = new String[] {"en.wikipedia.org"}; String protocol = "mem/"; + Tfds.Eq_str_lines(Query_ns(protocol, gplx.core.ios.IoEngine_.MemKey, wikis), String_.Concat_lines_nl + ( "app.bldr.wiki_cfg_bldr.get('en.wikipedia.org').new_cmd_('wiki.ns_mgr.aliases', 'ns_mgr.add_alias_bulk(\"" + , "4|WP" + , "5|WT" + , "6|Image" + , "7|Image_talk" + , "\");');" + )); + } + String Query_ns(String protocol, String trg_engine_key, String[] wikis) { + String_bldr sb = String_bldr_.new_(); + int wikis_len = wikis.length; + for (int i = 0; i < wikis_len; i++) { + String wiki = wikis[i]; + if (String_.Len_eq_0(wiki)) continue; + try { + String api = protocol + wiki + "/w/api.php?action=query&format=xml&meta=siteinfo&siprop=namespacealiases"; + String xml = String_.new_u8(Io_mgr.Instance.DownloadFil_args("", null).Trg_engine_key_(trg_engine_key).Exec_as_bry(api)); + if (xml == null) continue; // not found + gplx.langs.xmls.XmlDoc xdoc = gplx.langs.xmls.XmlDoc_.parse(xml); + gplx.langs.xmls.XmlNde xnde = gplx.langs.xmls.Xpath_.SelectFirst(xdoc.Root(), "query/namespacealiases"); + sb.Add("app.bldr.wiki_cfg_bldr.get('").Add(wiki).Add("').new_cmd_('wiki.ns_mgr.aliases', 'ns_mgr.add_alias_bulk(\"\n"); + int xndes_len = xnde.SubNdes().Count(); + for (int j = 0; j < xndes_len; j++) { + gplx.langs.xmls.XmlNde ns_nde = xnde.SubNdes().Get_at(j); + if (!String_.Eq(ns_nde.Name(), "ns")) continue; + int id = Int_.Parse(ns_nde.Atrs().FetchValOr("id", "-1")); + String name = String_.Replace(String_.Replace(ns_nde.Text_inner(), " ", "_"), "'", "''"); + sb.Add(Int_.To_str(id)).Add("|").Add(String_.Trim(name)).Add_char_nl(); + } + sb.Add("\");');\n"); + } + catch(Exception e) {sb.Add("// fail: " + wiki + " " + Err_.Message_gplx_full(e)).Add_char_nl();} + } + return sb.To_str_and_clear(); + } +} +class Xob_wiki_cfg_bldr_fxt { + public Xob_wiki_cfg_bldr_fxt Clear() { + if (app == null) { + app = Xoa_app_fxt.Make__app__edit(); + wiki_cfg_bldr = app.Bldr().Wiki_cfg_bldr(); + } + wiki_cfg_bldr.Clear(); + hash.Clear(); + return this; + } private Xoae_app app; Xob_wiki_cfg_bldr wiki_cfg_bldr; Ordered_hash hash = Ordered_hash_.New(); + public Xob_wiki_cfg_bldr_fxt Init_cmd(String wiki, String key, String text) { + wiki_cfg_bldr.Itms_get_or_new(wiki).Itms_add(key, text); + return this; + } + public Xob_wiki_cfg_bldr_fxt Expd_txt(String wiki, String text) { + hash.Add(wiki, Keyval_.new_(wiki, text)); + return this; + } + public void Test() { + wiki_cfg_bldr.Exec(); + int len = hash.Count(); + for (int i = 0; i < len; i++) { + Keyval kv = (Keyval)hash.Get_at(i); + String wiki = kv.Key(); + String expd = (String)kv.Val(); + String actl = Io_mgr.Instance.LoadFilStr(app.Fsys_mgr().Cfg_wiki_core_dir().GenSubFil(wiki + ".gfs")); + Tfds.Eq_str_lines(expd, actl); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/wm_server_cfgs/Xowm_server_cfg_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/wm_server_cfgs/Xowm_server_cfg_cmd.java index a27517de8..fcc7ede5b 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/wm_server_cfgs/Xowm_server_cfg_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/wm_server_cfgs/Xowm_server_cfg_cmd.java @@ -13,3 +13,16 @@ 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.addons.bldrs.app_cfgs.wm_server_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.app_cfgs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xowm_server_cfg_cmd extends Xob_cmd__base { + public Xowm_server_cfg_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + new Xowm_server_cfg_mgr().Exec(bldr.App()); + } + + public static final String BLDR_CMD_KEY = "cfg.wikis.wm_server_cfg"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xowm_server_cfg_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xowm_server_cfg_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/wm_server_cfgs/Xowm_server_cfg_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/wm_server_cfgs/Xowm_server_cfg_mgr.java index a27517de8..73f1b87b1 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/wm_server_cfgs/Xowm_server_cfg_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/app_cfgs/wm_server_cfgs/Xowm_server_cfg_mgr.java @@ -13,3 +13,69 @@ 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.addons.bldrs.app_cfgs.wm_server_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.app_cfgs.*; +import gplx.core.ios.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.langs.phps.*; +import gplx.xowa.wikis.domains.*; +public class Xowm_server_cfg_mgr { + public void Exec(Xoa_app app) { + // get local file + Io_url tmp_url = app.Fsys_mgr().Bin_xowa_dir().GenSubFil_nest("cfg", "wiki", "InitialiseSettings.php.txt"); + Assert_recent_or_download(tmp_url, "https://noc.wikimedia.org/conf/InitialiseSettings.php.txt", 1440); + byte[] src = Io_mgr.Instance.LoadFilBry(tmp_url); + Parse_cat_collation(src); + } + private void Parse_cat_collation(byte[] all) { + // extract cat_collation + int bgn_pos = Bry_find_.Find_fwd(all, Bry_.new_a7("wgCategoryCollation")); + if (bgn_pos == Bry_find_.Not_found) throw Err_.new_wo_type("could not find wgCategoryCollation bgn"); + bgn_pos = Bry_find_.Move_fwd(all, Byte_ascii.Nl_bry, bgn_pos); + + int end_pos = Bry_find_.Find_fwd(all, Bry_.new_a7("\n],"), bgn_pos); + if (end_pos == Bry_find_.Not_found) throw Err_.new_wo_type("could not find wgCategoryCollation end"); + byte[] src = Bry_.Mid(all, bgn_pos, end_pos + 2); // +2="]," + src = Bry_.Add(Bry_.new_a7("$test=["), src, Bry_.new_a7("];")); + + // parse php + Php_parser php_parser = new Php_parser(); + Php_evaluator eval = new Php_evaluator(new gplx.core.log_msgs.Gfo_msg_log("test")); + php_parser.Parse_tkns(src, eval); + Php_line[] lines = (Php_line[])eval.List().To_ary(Php_line.class); + Php_line_assign line = (Php_line_assign)lines[0]; + Php_itm_ary root_ary = (Php_itm_ary)line.Val(); + + // loop tkns and build String + Bry_bfr bfr = Bry_bfr_.New(); + bfr.Add_byte_nl(); + int len = root_ary.Subs_len(); + byte[] wiki_abrv__default = Bry_.new_a7("default"); + for (int i = 0; i < len; ++i) { + Php_itm_kv kv = (Php_itm_kv)root_ary.Subs_get(i); + byte[] wiki_abrv = kv.Key().Val_obj_bry(); + byte[] collation = kv.Val().Val_obj_bry(); + + if (Bry_.Eq(wiki_abrv, wiki_abrv__default)) continue; // skip "'default' => 'uppercase'," + try { + byte[] wiki_domain = Xow_abrv_wm_.Parse_to_domain_bry(wiki_abrv); + + Xow_domain_itm itm = Xow_domain_itm_.parse(wiki_domain); + bfr.Add_str_u8_fmt("app.bldr.wiki_cfg_bldr.get('{0}').new_cmd_('wiki.ctgs.collations', \"catpage_mgr.collation_('{1}');\");\n", itm.Domain_bry(), collation); + } catch (Exception e) {throw Err_.new_("failed to parse line", "wiki", wiki_abrv, "collation", collation, "err", Err_.Message_lang(e));} + } + Tfds.Write(bfr.To_str_and_clear()); + } + private static void Assert_recent_or_download(Io_url trg, String src, int min) { + // get file + IoItmFil trg_fil = Io_mgr.Instance.QueryFil(trg); + + // exit if file exists and is recent + if (trg_fil != null) { + DateAdp now = Datetime_now.Get(); + if (now.Diff(trg_fil.ModifiedTime()).Total_mins().To_int() < min) return; + } + + // load b/c file doesn't exist, or is not recent + Io_mgr.Instance.DownloadFil(src, trg); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_addon.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_addon.java index a27517de8..e78c5ccbd 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_addon.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_addon.java @@ -13,3 +13,27 @@ 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.addons.bldrs.centrals; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.xowa.specials.*; import gplx.xowa.htmls.bridges.*; +import gplx.xowa.addons.bldrs.infos.*; import gplx.xowa.addons.bldrs.xodirs.*; +public class Xobc_task_addon implements Xoax_addon_itm, Xoax_addon_itm__special, Xoax_addon_itm__json, Xoax_addon_itm__init { + public Xobc_xodir_mgr Xodir_mgr() {return xodir_mgr;} private Xobc_xodir_mgr xodir_mgr; + public void Xodir_mgr_(Xobc_xodir_mgr v) {this.xodir_mgr = v;} + public void Init_addon_by_app(Xoa_app app) { + this.xodir_mgr = new Xobc_xodir_mgr__pc(app); + } + public void Init_addon_by_wiki(Xow_wiki wiki) {} + public Xow_special_page[] Special_pages() { + return new Xow_special_page[] + { Xobc_task_special.Prototype + , Xobc_info_special.Prototype + }; + } + public Bridge_cmd_itm[] Json_cmds() { + return new Bridge_cmd_itm[] + { gplx.xowa.addons.bldrs.centrals.Xobc_task_bridge.Prototype + }; + } + + public String Addon__key() {return ADDON__KEY;} public static final String ADDON__KEY = "xowa.imports.downloads"; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_bridge.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_bridge.java index a27517de8..62e178377 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_bridge.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_bridge.java @@ -13,3 +13,50 @@ 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.addons.bldrs.centrals; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.langs.jsons.*; +import gplx.xowa.htmls.bridges.*; +import gplx.xowa.addons.bldrs.centrals.cmds.*; +public class Xobc_task_bridge implements Bridge_cmd_itm { + private Xoa_app app; + public void Init_by_app(Xoa_app app) {this.app = app;} + public String Exec(Json_nde data) { + Xobc_task_mgr task_mgr = Xobc_task_special.Task_mgr(app); + byte proc_id = proc_hash.Get_as_byte_or(data.Get_as_bry_or(Bridge_cmd_mgr.Msg__proc, null), Byte_ascii.Max_7_bit); + Json_nde args = data.Get_kv(Bridge_cmd_mgr.Msg__args).Val_as_nde(); + switch (proc_id) { + case Proc__reload: task_mgr.Reload(); break; + case Proc__add_work: task_mgr.Todo_mgr().Add_work(args.Get_as_int("task_id")); break; + case Proc__del_work: task_mgr.Work_mgr().Del_work(args.Get_as_int("task_id")); break; + case Proc__del_done: task_mgr.Done_mgr().Del_done(args.Get_as_int("task_id")); break; + case Proc__del_todo: task_mgr.Todo_mgr().Del_todo(args.Get_as_int("task_id")); break; + case Proc__run_next: task_mgr.Work_mgr().Run_next(); break; + case Proc__stop_cur: task_mgr.Work_mgr().Stop_cur(); break; + case Proc__redo_cur: task_mgr.Work_mgr().Redo_cur(); break; + case Proc__filter_todo: task_mgr.Filter_todo(args.Get_as_str("lang_key"), args.Get_as_str("type_key")); break; + case Proc__download_db: gplx.xowa.addons.bldrs.centrals.dbs.Xobc_data_db_upgrader.Check_for_updates(task_mgr); break; + default: throw Err_.new_unhandled_default(proc_id); + } + return ""; + } + private static final byte + Proc__reload = 0, Proc__add_work = 1, Proc__del_work = 2, Proc__del_done = 3 + , Proc__run_next = 4, Proc__stop_cur = 5, Proc__redo_cur = 6, Proc__download_db = 7, Proc__filter_todo = 8 + , Proc__del_todo = 9 + ; + private static final Hash_adp_bry proc_hash = Hash_adp_bry.cs() + .Add_str_byte("reload" , Proc__reload) + .Add_str_byte("add_work" , Proc__add_work) + .Add_str_byte("del_work" , Proc__del_work) + .Add_str_byte("del_done" , Proc__del_done) + .Add_str_byte("run_next" , Proc__run_next) + .Add_str_byte("stop_cur" , Proc__stop_cur) + .Add_str_byte("redo_cur" , Proc__redo_cur) + .Add_str_byte("download_db" , Proc__download_db) + .Add_str_byte("filter_todo" , Proc__filter_todo) + .Add_str_byte("del_todo" , Proc__del_todo) + ; + + public byte[] Key() {return BRIDGE_KEY;} public static final byte[] BRIDGE_KEY = Bry_.new_a7("builder_central.exec"); + public static final Xobc_task_bridge Prototype = new Xobc_task_bridge(); Xobc_task_bridge() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_doc.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_doc.java index a27517de8..58236db62 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_doc.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_doc.java @@ -13,3 +13,20 @@ 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.addons.bldrs.centrals; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.langs.mustaches.*; +class Xobc_task_doc implements Mustache_doc_itm { + private final boolean app_is_drd; + private final String link_help; + public Xobc_task_doc(boolean app_is_drd, String link_help) { + this.app_is_drd = app_is_drd; this.link_help = link_help; + } + public boolean Mustache__write(String key, Mustache_bfr bfr) { + if (String_.Eq(key, "link_help")) bfr.Add_str_u8(link_help); + return false; + } + public Mustache_doc_itm[] Mustache__subs(String key) { + if (String_.Eq(key, "app_is_drd")) return Mustache_doc_itm_.Ary__bool(app_is_drd); + return Mustache_doc_itm_.Ary__empty; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_html.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_html.java index a27517de8..226739edd 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_html.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_html.java @@ -13,3 +13,30 @@ 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.addons.bldrs.centrals; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.xowa.specials.*; import gplx.langs.mustaches.*; import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.tags.*; +class Xobc_task_html extends Xow_special_wtr__base { + @Override protected Io_url Get_addon_dir(Xoa_app app) {return app.Fsys_mgr().Http_root().GenSubDir_nest("bin", "any", "xowa", "addon", "bldr", "central");} + @Override protected Io_url Get_mustache_fil(Io_url addon_dir) {return addon_dir.GenSubFil_nest("bin", "xobc.main.mustache.html");} + @Override protected Mustache_doc_itm Bld_mustache_root(Xoa_app app) { + return new Xobc_task_doc(gplx.core.envs.Op_sys.Cur().Tid_is_drd(), "/site/home/wiki/App/Import/Download_Central"); + } + @Override protected void Bld_tags(Xoa_app app, Io_url addon_dir, Xopage_html_data page_data) { + Xopg_tag_mgr head_tags = page_data.Head_tags(); + Xopg_alertify_.Add_tags (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xocss (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xohelp (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__mustache (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__jquery (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xonotify (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xolog (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xoajax (head_tags, app.Fsys_mgr().Http_root(), app); + + head_tags.Add(Xopg_tag_itm.New_css_file(addon_dir.GenSubFil_nest("bin", "xobc.css"))); + head_tags.Add(Xopg_tag_itm.New_htm_frag(addon_dir.GenSubFil_nest("bin", "xobc.row.mustache.html"), "xobc.row")); + head_tags.Add(Xopg_tag_itm.New_js_file(addon_dir.GenSubFil_nest("js", "xo.elem.js"))); + head_tags.Add(Xopg_tag_itm.New_js_file(addon_dir.GenSubFil_nest("js", "xo.tmpl.js"))); + head_tags.Add(Xopg_tag_itm.New_js_file(addon_dir.GenSubFil_nest("js", "xobc.util.js"))); + head_tags.Add(Xopg_tag_itm.New_js_file(addon_dir.GenSubFil_nest("js", "xobc.js"))); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_mgr.java index a27517de8..72a045c09 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_mgr.java @@ -13,3 +13,69 @@ 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.addons.bldrs.centrals; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.core.brys.evals.*; import gplx.core.gfobjs.*; import gplx.core.progs.rates.*; import gplx.core.threads.*; +import gplx.xowa.addons.bldrs.centrals.tasks.*; import gplx.xowa.addons.bldrs.centrals.steps.*; import gplx.xowa.addons.bldrs.centrals.cmds.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; import gplx.xowa.addons.bldrs.centrals.mgrs.*; +import gplx.xowa.guis.cbks.*; +public class Xobc_task_mgr implements Xog_json_wkr { + private final Xog_cbk_trg cbk_trg = Xog_cbk_trg.New(Xobc_task_special.Prototype.Special__meta().Ttl_bry()); + public Xobc_task_mgr(Xoa_app app, Io_url data_db_url) { + this.app = app; + this.cbk_mgr = app.Gui__cbk_mgr(); + this.user_db = new Xobc_user_db(app.User().User_db_mgr().Conn()); + this.data_db = new Xobc_data_db(data_db_url); + this.work_mgr = new Xobc_task_regy__work(this, app); + this.todo_mgr = new Xobc_task_regy__todo(this); + this.done_mgr = new Xobc_task_regy__done(this); + this.step_mgr = new Xobc_step_factory(this, data_db, app.Fsys_mgr().Wiki_dir()); + this.skip_mgr = new Xobc_skip_mgr(this, app); + this.ary = new Xobc_task_regy__base[] {work_mgr, todo_mgr, done_mgr}; + this.rate_mgr = Xobc_cmd__base.New_rate_mgr(); + } + public Xoa_app App() {return app;} private final Xoa_app app; + public Xog_cbk_mgr Cbk_mgr() {return cbk_mgr;} private final Xog_cbk_mgr cbk_mgr; + public Xobc_task_regy__work Work_mgr() {return work_mgr;} private final Xobc_task_regy__work work_mgr; + public Xobc_task_regy__todo Todo_mgr() {return todo_mgr;} private final Xobc_task_regy__todo todo_mgr; + public Xobc_task_regy__done Done_mgr() {return done_mgr;} private final Xobc_task_regy__done done_mgr; + public Xobc_task_regy__base Get_at(int i) {return ary[i];} private final Xobc_task_regy__base[] ary; + public Xobc_data_db Data_db() {return data_db;} private final Xobc_data_db data_db; + public Xobc_user_db User_db() {return user_db;} private final Xobc_user_db user_db; + public Gfo_rate_mgr Rate_mgr() {return rate_mgr;} private final Gfo_rate_mgr rate_mgr; + public Xobc_step_factory Step_mgr() {return step_mgr;} private final Xobc_step_factory step_mgr; + public Xobc_filter_mgr Filter_mgr() {return filter_mgr;} private final Xobc_filter_mgr filter_mgr = new Xobc_filter_mgr(); + public Xobc_skip_mgr Skip_mgr() {return skip_mgr;} private final Xobc_skip_mgr skip_mgr; + public Xobc_lang_mgr Lang_mgr() {return lang_mgr;} private final Xobc_lang_mgr lang_mgr = new Xobc_lang_mgr(); + public void Send_json(String func, Gfobj_nde data) {cbk_mgr.Send_json(cbk_trg, func, data);} + public Xobc_task_mgr Load_or_init() { + Gfo_log_.Instance.Info("task_mgr.load_or_init.bgn"); + data_db.Tbl__task_regy().Select_all(todo_mgr); + user_db.Work_task_tbl().Select_all(this, todo_mgr, work_mgr); + user_db.Done_task_tbl().Select_all(todo_mgr, done_mgr); + return this; + } + public void Reload() { + Gfo_log_.Instance.Info("task_mgr.reload.bgn"); + Gfobj_nde root = Gfobj_nde.New(); + Gfobj_nde lists_nde = root.New_nde("lists"); + work_mgr.Save_to(lists_nde.New_ary("work")); + todo_mgr.Save_to(lists_nde.New_ary("todo"), filter_mgr.Filter(todo_mgr)); + done_mgr.Save_to(lists_nde.New_ary("done")); + root.Add_nde("filters", filter_mgr.Make_init_msg()); + root.Add_ary("langs", lang_mgr.Make_init_msg(data_db.Tbl__lang_regy().Select_all())); + cbk_mgr.Send_json(cbk_trg, "xo.bldr.core.reload__recv", root); + } + public void Filter_todo(String lang_key, String type_key) { + Gfo_log_.Instance.Info("task_mgr.filter_by_lang.bgn"); + Gfobj_nde root = Gfobj_nde.New(); + Gfobj_nde lists_nde = root.New_nde("lists").Add_str("list_name", "todo"); + todo_mgr.Save_to(lists_nde.New_ary("todo"), filter_mgr.Filter(todo_mgr, lang_key, type_key)); + cbk_mgr.Send_json(cbk_trg, "xo.bldr.core.reload_list__recv", root); + } + public void Transfer(Xobc_task_regy__base src, Xobc_task_regy__base trg, Xobc_task_itm task) { + Gfo_log_.Instance.Info("task_mgr.transfer.bgn", "task_uid", task.Task_id(), "src", src.Name(), "trg", trg.Name()); + src.Del_by(task.Task_id()); + trg.Add(task); + cbk_mgr.Send_json(cbk_trg, "xo.bldr.core.transfer__recv", Gfobj_nde.New().Add_str("src", src.Name()).Add_str("trg", trg.Name()).Add_nde("task", task.Save_to(Gfobj_nde.New()))); + } + public static final int Lists_len = 3; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_special.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_special.java index a27517de8..6400a37ac 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_special.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/Xobc_task_special.java @@ -13,3 +13,32 @@ 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.addons.bldrs.centrals; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.xowa.specials.*; import gplx.core.net.*; import gplx.core.net.qargs.*; import gplx.xowa.wikis.pages.*; +public class Xobc_task_special implements Xow_special_page { + public void Special__gen(Xow_wiki wiki, Xoa_page page, Xoa_url url, Xoa_ttl ttl) { + // init task_mgr + page.Html_data().Cbk_enabled_(true); // apply cbk_enabled early to capture logging statements + Xoa_app app = wiki.App(); + if (task_mgr == null) task_mgr = New_task_mgr(app); + task_mgr.Load_or_init(); + + new Xobc_task_html().Bld_page_by_mustache(app, page, this); + } + public static Xobc_task_mgr Task_mgr(Xoa_app app) { + if (task_mgr == null) { + task_mgr = New_task_mgr(app); + } + return task_mgr; + } private static Xobc_task_mgr task_mgr; + private static Xobc_task_mgr New_task_mgr(Xoa_app app) { + Io_url data_db_url = app.Fsys_mgr().Bin_addon_dir().GenSubFil_nest("bldr", "central", "bldr_central.data_db.xowa"); + app.User().User_db_mgr().Init_site_mgr(); + return new Xobc_task_mgr(app, data_db_url); + } + + Xobc_task_special(Xow_special_meta special__meta) {this.special__meta = special__meta;} + public Xow_special_meta Special__meta() {return special__meta;} private final Xow_special_meta special__meta; + public Xow_special_page Special__clone() {return this;} + public static final Xow_special_page Prototype = new Xobc_task_special(Xow_special_meta.New_xo("XowaDownloadCentral", "Download Central")); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__base.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__base.java index a27517de8..cb41ad79b 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__base.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__base.java @@ -13,3 +13,144 @@ 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.addons.bldrs.centrals.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.gfobjs.*; import gplx.core.progs.*; import gplx.core.progs.rates.*; +import gplx.xowa.apps.apis.*; +public abstract class Xobc_cmd__base implements Xobc_cmd_itm { + private final Xobc_task_mgr task_mgr; + private final Gfo_rate_list rate_list; private final long notify_delay = 1000; + private final double delta_threshold = .25d; // allow variance of up to 25% before updating rate + private long time_prv; + private double rate_cur; + private boolean log_verbose; + public Xobc_cmd__base(Xobc_task_mgr task_mgr, int task_id, int step_id, int cmd_id) { + this.task_mgr = task_mgr; this.task_id = task_id; this.step_id = step_id; this.cmd_id = cmd_id; + this.cmd_uid = String_.Concat_with_str(":", Int_.To_str(task_id), Int_.To_str(step_id), Int_.To_str(cmd_id)); + this.rate_list = task_mgr == null ? null : task_mgr.Rate_mgr().Get_or_new(this.Cmd_type()); + } + public long Prog_data_cur() {return data_cur;} private long data_cur; public void Prog_data_cur_(long v) {this.data_cur = v;} + public long Prog_data_end() {return data_end;} private long data_end; public void Prog_data_end_(long v) {this.data_end = v;} + public byte Prog_status() {return status;} private byte status = Gfo_prog_ui_.Status__init; public void Prog_status_(byte v) {status = v;} + public void Prog_notify_by_msg(String msg) {task_mgr.Work_mgr().On_stat(task_id, msg);} + public boolean Canceled() {return status == Gfo_prog_ui_.Status__suspended;} + public void Cancel() {status = Gfo_prog_ui_.Status__suspended;} + + public int Task_id() {return task_id;} private final int task_id; + public int Step_id() {return step_id;} private final int step_id; + public int Cmd_id() {return cmd_id;} private final int cmd_id; + public abstract String Cmd_type(); + public abstract String Cmd_name(); + @gplx.Virtual public boolean Cmd_suspendable() {return false;} + public String Cmd_uid() {return cmd_uid;} private final String cmd_uid; + @gplx.Virtual public String Cmd_fallback() {return this.Cmd_type();} + @gplx.Virtual public void Cmd_clear() {// called when restarting failed task + this.status = Gfo_prog_ui_.Status__init; + this.cmd_exec_err = null; // reset error + this.data_cur = 0; // reset progress else bad progress updates; DATE:2016-06-29 + this.Cmd_cleanup(); // do any cleanup, such as deleting bad downloads + } + + public void Cmd_exec(Xobc_cmd_ctx ctx) { + // rate_list.Clear(); this.rate_cur = 0; // TOMBSTONE: do not reset rate else pause and resume will show different numbers + Xoapi_root api_root = task_mgr.App().Api_root(); + if (api_root != null) + this.log_verbose = api_root.Addon().Bldr().Central().Log_verbose(); + try { + Gfo_log_.Instance.Info("xobc_cmd task bgn", "task_id", task_id, "step_id", step_id, "cmd_id", cmd_id); + this.time_prv = gplx.core.envs.System_.Ticks(); + this.status = Gfo_prog_ui_.Status__working; + this.Cmd_exec_hook(ctx); + Gfo_log_.Instance.Info("xobc_cmd task end", "task_id", task_id, "step_id", step_id, "cmd_id", cmd_id); + switch (status) { + case Gfo_prog_ui_.Status__suspended: task_mgr.Work_mgr().On_suspended(this); break; + case Gfo_prog_ui_.Status__fail: task_mgr.Work_mgr().On_fail(this, this.Cmd_fail_resumes(), cmd_exec_err); break; + case Gfo_prog_ui_.Status__working: + this.Prog_notify_and_chk_if_suspended(data_end, data_end); // fire one more time for 100%; note that 100% may not fire due to timer logic below + task_mgr.Work_mgr().On_done(this, true); + break; + } + } catch (Exception e) { + this.status = Gfo_prog_ui_.Status__fail; + Gfo_log_.Instance.Warn("xobc_cmd task fail", "task_id", task_id, "step_id", step_id, "cmd_id", cmd_id, "err", Err_.Message_gplx_log(e)); + task_mgr.Work_mgr().On_fail(this, this.Cmd_fail_resumes(), Err_.Message_lang(e)); + } + finally { + Gfo_log_.Instance.Flush(); + } + } + protected abstract void Cmd_exec_hook(Xobc_cmd_ctx ctx); + @gplx.Virtual protected boolean Cmd_fail_resumes() {return false;} + protected void Cmd_exec_err_(String v) { + Gfo_log_.Instance.Warn("xobc_cmd task err", "task_id", task_id, "step_id", step_id, "cmd_id", cmd_id, "err", v); + this.status = Gfo_prog_ui_.Status__fail; + this.cmd_exec_err = v; + } private String cmd_exec_err; + @gplx.Virtual public void Cmd_cleanup() {} + + public Gfobj_nde Save_to(Gfobj_nde nde) { + nde.Add_int ("task_id" , task_id); + nde.Add_int ("step_id" , step_id); + nde.Add_int ("cmd_id" , cmd_id); + nde.Add_str ("cmd_type" , this.Cmd_type()); + nde.Add_str ("cmd_name" , this.Cmd_name()); + nde.Add_bool("cmd_suspendable" , this.Cmd_suspendable()); + nde.Add_byte("prog_status" , this.Prog_status()); + nde.Add_long("prog_data_cur" , this.Prog_data_cur()); + nde.Add_long("prog_data_end" , this.Prog_data_end()); + nde.Add_long("prog_time_end" , 0); + return nde; + } + public void Load_checkpoint() { + long data_cur = this.Load_checkpoint_hook(); + this.Prog_data_cur_(data_cur); + if (data_cur > 0) + this.Prog_status_(Gfo_prog_ui_.Status__suspended); // set status to suspended, else js won't warn about accidental removal + } + @gplx.Virtual protected long Load_checkpoint_hook() {return 0;} + + public boolean Prog_notify_and_chk_if_suspended(long new_data_cur, long new_data_end) { + if (status == Gfo_prog_ui_.Status__suspended) return true; // task paused by ui; exit now; + long time_cur = gplx.core.envs.System_.Ticks(); + if (time_cur < time_prv + notify_delay) return false; // message came too soon. ignore it + + // update rate + double rate_now = (rate_list.Add(new_data_cur - data_cur, (time_cur - time_prv))) * 1000; + double delta = Math_.Abs_double((rate_now - rate_cur) / rate_cur); + if ( rate_cur == 0 // rate not set + || delta > delta_threshold) { // rate_now is at least 25% different than rate_prv + if (delta > delta_threshold * 2) // rate_now is > 50% different + rate_cur = rate_now; // update it now + else { + double rate_new = ((rate_now - rate_cur) * .05) + rate_cur; // calc new rate as 5% of difference + // Tfds.Dbg(delta, rate_now, rate_cur, rate_new); + rate_cur = rate_new; + } + } + + // update prog vals + this.time_prv = time_cur; + this.data_cur = new_data_cur; + this.data_end = new_data_end; + + task_mgr.Send_json("xo.bldr.work.prog__update__recv", Gfobj_nde.New() + .Add_int ("task_id", task_id).Add_long("prog_data_cur", data_cur).Add_long("prog_data_end", data_end).Add_int("prog_rate", (int)rate_cur)); + if (log_verbose) + Gfo_usr_dlg_.Instance.Note_many("", "", "xobc:notify: task_id=~{0} cmd_id=~{1} prog_data_cur=~{2} prog_data_end=~{3} rate_cur=~{4}", task_id, cmd_id, data_cur, data_end, rate_cur); + return false; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__exec)) Cmd_exec((Xobc_cmd_ctx)m.ReadObj("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk__exec = "exec"; + public static final int Seqn__0 = 0; + public static Gfo_rate_mgr New_rate_mgr() { + Gfo_rate_mgr rv = new Gfo_rate_mgr(128); + rv.Add_new(Xobc_cmd__verify_dir.CMD_TYPE); + rv.Add_new(Xobc_cmd__verify_fil.CMD_TYPE); + rv.Add_new(Xobc_cmd__download.CMD_TYPE); + rv.Add_new(Xobc_cmd__unzip.CMD_TYPE); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__download.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__download.java index a27517de8..8d38f97f5 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__download.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__download.java @@ -13,3 +13,70 @@ 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.addons.bldrs.centrals.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.progs.*; import gplx.core.net.downloads.*; +public class Xobc_cmd__download extends Xobc_cmd__base { + private final Xobc_task_mgr task_mgr; + private final String src_url; private final Io_url trg_url; + private final long expd_size; + private final Http_download_wkr wkr; + public Xobc_cmd__download(Xobc_task_mgr task_mgr, int task_id, int step_id, int cmd_id, String src_url, Io_url trg_url, long file_size_zip) {super(task_mgr, task_id, step_id, cmd_id); + this.task_mgr = task_mgr; + this.src_url = src_url; this.trg_url = trg_url; this.expd_size = file_size_zip; + this.wkr = Http_download_wkr_.Proto.Make_new(); + this.Prog_data_end_(expd_size); + } + @Override public String Cmd_type() {return CMD_TYPE;} public static final String CMD_TYPE = "xowa.core.download"; + @Override public String Cmd_name() {return "download";} + @Override public boolean Cmd_suspendable() {return true;} + + @Override protected void Cmd_exec_hook(Xobc_cmd_ctx ctx) { + int error_wait = 10000, error_tries_max = 6, error_tries_cur = 0; // retry every 10 seconds for a total of 6 tries (1 min) + + // loop while "server ... " error + while (true) { + long trg_size_bgn = Tmp_url_size(); + byte status = wkr.Exec(this, src_url, trg_url, expd_size); + if (status == Gfo_prog_ui_.Status__fail) { + + // check if server error; note: must not loop if bad size; DATE:2016-09-24 + String fail_msg = wkr.Fail_msg(); + if (String_.Has_at_bgn(fail_msg, Http_download_wkr__jre.Err__server_download_failed)) { + + // check if anything more downloaded; if so, then reset to 0; DATE:2016-09-03 + long trg_size_cur = Tmp_url_size(); + if (trg_size_cur > trg_size_bgn) { + error_tries_cur = 0; + trg_size_bgn = trg_size_cur; + } + + // retry + if (error_tries_cur++ < error_tries_max) { + task_mgr.Work_mgr().On_stat(this.Task_id(), String_.Format("connection interrupted: retrying in {0} seconds; attempt {1} of {2}", error_wait / 1000, error_tries_cur, error_tries_max)); + Gfo_usr_dlg_.Instance.Log_many("", "", "xobc_cmd task download interrupted; ~{0} ~{1} ~{2} ~{3}", this.Task_id(), this.Step_id(), trg_url, Io_mgr.Instance.QueryFil(trg_url).Size()); + gplx.core.threads.Thread_adp_.Sleep(error_wait); + continue; + } + } + + // otherewise exit loop + this.Cmd_exec_err_(fail_msg); + break; + } + else + break; + } + Gfo_log_.Instance.Info("xobc_cmd task download", "task_id", this.Task_id(), "step_id", this.Step_id(), "trg_url", trg_url, "trg_len", Io_mgr.Instance.QueryFil(trg_url).Size()); + } + private long Tmp_url_size() { + return wkr.Tmp_url() == null ? 0 : Io_mgr.Instance.QueryFil(wkr.Tmp_url()).Size(); // NOTE: wkr.Tmp_url is null in some extreme exceptions; DATE:2016-09-24 + } + @Override public void Cmd_cleanup() { + wkr.Exec_cleanup(); + } + @Override protected boolean Cmd_fail_resumes() {return true;} + + @Override protected long Load_checkpoint_hook() { + return wkr.Checkpoint__load_by_trg_fil(trg_url); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__fsdb_delete.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__fsdb_delete.java index a27517de8..631acae74 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__fsdb_delete.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__fsdb_delete.java @@ -13,3 +13,32 @@ 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.addons.bldrs.centrals.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.dbs.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.bldrs.updates.files.*; +public class Xobc_cmd__fsdb_delete extends Xobc_cmd__base { + private final Io_url deletion_db_url; + public Xobc_cmd__fsdb_delete(Xobc_task_mgr task_mgr, int task_id, int step_id, int cmd_idx, Io_url deletion_db_url) {super(task_mgr, task_id, step_id, cmd_idx); + this.deletion_db_url = deletion_db_url; + } + @Override public String Cmd_type() {return CMD_TYPE;} public static final String CMD_TYPE = "xowa.fsdb.delete"; + @Override public String Cmd_name() {return "deleting old files";} + @Override public boolean Cmd_suspendable() {return true;} + @Override protected void Cmd_exec_hook(Xobc_cmd_ctx ctx) { + if (!Io_mgr.Instance.ExistsFil(deletion_db_url)) throw Err_.New("deletion db does not exist; file={0}", deletion_db_url.Raw()); + boolean pass = false; + try { + new Xodel_exec_mgr().Exec_delete(this, ctx.App().Bldr(), deletion_db_url); + pass = true; + } + catch (Exception e) { + this.Cmd_exec_err_(Err_.Message_gplx_log(e)); + } + Gfo_log_.Instance.Info("xobc_cmd task delete", "task_id", this.Task_id(), "step_id", this.Step_id(), "delete_url", deletion_db_url.Raw(), "pass", pass); + } + @Override public void Cmd_cleanup() { + if (Io_mgr.Instance.ExistsFil(deletion_db_url)) + new Xodel_exec_mgr().Exec_cleanup(deletion_db_url); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__move_fils.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__move_fils.java index a27517de8..500e9456a 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__move_fils.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__move_fils.java @@ -13,3 +13,27 @@ 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.addons.bldrs.centrals.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +public class Xobc_cmd__move_fils extends Xobc_cmd__base { + private final Io_url src_dir, trg_dir; + private final List_adp trg_fils = List_adp_.New(); + public Xobc_cmd__move_fils(Xobc_task_mgr task_mgr, int task_id, int step_id, int cmd_idx, Io_url src_dir, Io_url trg_dir) {super(task_mgr, task_id, step_id, cmd_idx); + this.src_dir = src_dir; this.trg_dir = trg_dir; + } + @Override public String Cmd_type() {return CMD_TYPE;} public static final String CMD_TYPE = "xowa.core.move_fil"; + @Override public String Cmd_name() {return "move";} + @Override protected void Cmd_exec_hook(Xobc_cmd_ctx ctx) { + Io_url[] src_fils = Io_mgr.Instance.QueryDir_fils(src_dir); + int len = src_fils.length; + for (int i = 0; i < len; ++i) { + Io_url src_fil = src_fils[i]; + if (String_.Eq(src_fil.Ext(), ".md5")) continue; + Io_url trg_fil = trg_dir.GenSubFil(src_fil.NameAndExt()); + Io_mgr.Instance.MoveFil_args(src_fil, trg_fil, true).Exec(); + trg_fils.Add(trg_fil); + } + } + @Override public void Cmd_cleanup() { + Io_mgr.Instance.Delete_dir_empty(trg_dir.GenSubDir("tmp")); // deletes dir only if empty + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__unzip.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__unzip.java index a27517de8..70f3b9727 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__unzip.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__unzip.java @@ -13,3 +13,32 @@ 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.addons.bldrs.centrals.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.ios.zips.*; import gplx.core.progs.*; +public class Xobc_cmd__unzip extends Xobc_cmd__base { + private final Io_url src_fil, trg_dir; + private final Io_zip_decompress_cmd wkr; + private final List_adp trg_fils = List_adp_.New(); + public Xobc_cmd__unzip(Xobc_task_mgr task_mgr, int task_id, int step_id, int cmd_id, Io_url src_fil, Io_url trg_dir, long prog_data_end) {super(task_mgr, task_id, step_id, cmd_id); + this.src_fil = src_fil; this.trg_dir = trg_dir; + this.Prog_data_end_(prog_data_end); + this.wkr = Io_zip_decompress_cmd_.Proto.Make_new(); + } + @Override public String Cmd_type() {return CMD_TYPE;} public static final String CMD_TYPE = "xowa.core.ios.zips.zip_unzip"; + @Override public String Cmd_name() {return "unzip";} + @Override public boolean Cmd_suspendable() {return true;} + @Override public String Cmd_fallback() {return Xobc_cmd__verify_fil.CMD_TYPE;} // if unzip fails, backtrack to verify; if verify fails, it'll backtrack to download; DATE:2016-07-25 + + @Override protected void Cmd_exec_hook(Xobc_cmd_ctx ctx) { + if (wkr.Exec(this, src_fil, trg_dir, trg_fils) == Gfo_prog_ui_.Status__fail) + this.Cmd_exec_err_(wkr.Fail_msg()); + } + @Override public void Cmd_cleanup() { + wkr.Exec_cleanup(); + Io_mgr.Instance.DeleteFil(src_fil); + Io_mgr.Instance.DeleteDirDeep(trg_dir); + } + @Override protected long Load_checkpoint_hook() { + return wkr.Checkpoint__load_by_src_fil(src_fil); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__verify_dir.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__verify_dir.java index a27517de8..258005824 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__verify_dir.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__verify_dir.java @@ -13,3 +13,46 @@ 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.addons.bldrs.centrals.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.progs.*; +import gplx.core.security.*; import gplx.core.security.files.*; +public class Xobc_cmd__verify_dir extends Xobc_cmd__base { + private final Io_url delete_fil, checksum_fil; + public Xobc_cmd__verify_dir(Xobc_task_mgr task_mgr, int task_id, int step_id, int cmd_idx, Io_url checksum_fil, Io_url delete_fil) {super(task_mgr, task_id, step_id, cmd_idx); + this.checksum_fil = checksum_fil; + this.delete_fil = delete_fil; + } + @Override public String Cmd_type() {return CMD_TYPE;} public static final String CMD_TYPE = "xowa.core.hash_dir"; + @Override public String Cmd_name() {return "verify";} + @Override public String Cmd_fallback() {return Xobc_cmd__unzip.CMD_TYPE;} + + @Override protected void Cmd_exec_hook(Xobc_cmd_ctx ctx) { + // parse file + Cksum_list list = Cksum_list.Parse_by_fil(checksum_fil); + this.Prog_data_end_(list.Itms_size); + + // verify itms + Hash_algo algo = Hash_algo_.New_by_tid(list.Type); + Cksum_itm[] itms = list.Itms; + int len = itms.length; + long prog_data_cur = 0; + for (int i = 0; i < len; ++i) { + Cksum_itm itm = itms[i]; + gplx.core.ios.streams.IoStream stream = Io_mgr.Instance.OpenStreamRead(itm.File_url); + byte[] actl_hash = Bry_.Empty; + this.Prog_data_cur_(prog_data_cur); + try {actl_hash = algo.Hash_stream_as_bry(this, stream);} + finally {stream.Rls();} + prog_data_cur += itm.File_size; + if (this.Prog_notify_and_chk_if_suspended(prog_data_cur, Prog_data_end())) return; + if (this.Prog_status() != Gfo_prog_ui_.Status__suspended && !Bry_.Eq(itm.Hash, actl_hash)) { + this.Cmd_exec_err_(String_.Format("bad hash; file={0} bad={1} good={2}", itm.File_url.Raw(), actl_hash, itm.Hash)); + return; + } + } + } + @Override public void Cmd_cleanup() { + Io_mgr.Instance.DeleteFil(checksum_fil); + Io_mgr.Instance.DeleteFil(delete_fil); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__verify_fil.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__verify_fil.java index a27517de8..c09c33453 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__verify_fil.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__verify_fil.java @@ -13,3 +13,28 @@ 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.addons.bldrs.centrals.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.progs.*; import gplx.core.security.*; +public class Xobc_cmd__verify_fil extends Xobc_cmd__base { + private final Io_url src_url; private final byte[] expd_hash; + public Xobc_cmd__verify_fil(Xobc_task_mgr task_mgr, int task_id, int step_id, int cmd_id, Io_url src_url, String expd_hash_str, long prog_data_end) {super(task_mgr, task_id, step_id, cmd_id); + this.src_url = src_url; + this.expd_hash = Bry_.new_a7(expd_hash_str); + this.Prog_data_end_(prog_data_end); + } + @Override public String Cmd_type() {return CMD_TYPE;} public static final String CMD_TYPE = "xowa.core.hash_fil"; + @Override public String Cmd_name() {return "verify";} + @Override public String Cmd_fallback() {return Xobc_cmd__download.CMD_TYPE;} + + @Override protected void Cmd_exec_hook(Xobc_cmd_ctx ctx) { + Hash_algo algo = Hash_algo_.New__md5(); + gplx.core.ios.streams.IoStream stream = Io_mgr.Instance.OpenStreamRead(src_url); + byte[] actl_hash = Bry_.Empty; + try {actl_hash = algo.Hash_stream_as_bry(this, stream);} + finally {stream.Rls();} + if (this.Prog_status() != Gfo_prog_ui_.Status__suspended && !Bry_.Eq(expd_hash, actl_hash)) + this.Cmd_exec_err_(Xobc_cmd__verify_fil.Err_make(actl_hash, expd_hash)); + } + + public static String Err_make(byte[] actl, byte[] expd) {return String_.Format("bad hash: bad={0} good={1}", String_.new_u8(actl), String_.new_u8(expd));} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__wiki_merge.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__wiki_merge.java index a27517de8..1e93918b9 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__wiki_merge.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__wiki_merge.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.addons.bldrs.centrals.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.dbs.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.bldrs.exports.splits.mgrs.*; import gplx.xowa.addons.bldrs.exports.merges.*; +public class Xobc_cmd__wiki_merge extends Xobc_cmd__base { + private final String wiki_domain; + private final Io_url src_dir; + private final Merge2_mgr merge_mgr; + private final Merge_prog_wkr prog_wkr; + private final int idx_cur; + public Xobc_cmd__wiki_merge(Xobc_task_mgr task_mgr, int task_id, int step_id, int cmd_idx, Merge2_mgr merge_mgr, String wiki_domain, Io_url src_dir + , long prog_size_end, int prog_count_end, int idx_cur) {super(task_mgr, task_id, step_id, cmd_idx); + this.merge_mgr = merge_mgr; + this.wiki_domain = wiki_domain; + this.src_dir = src_dir; + this.prog_wkr = new Merge_prog_wkr(this, src_dir.GenSubFil("merge.checkpoint"), prog_count_end, prog_size_end); + merge_mgr.Prog_wkr_(prog_wkr); + this.Prog_data_end_(prog_count_end); + this.idx_cur = idx_cur; + } + @Override public String Cmd_type() {return CMD_TYPE;} public static final String CMD_TYPE = "xowa.wiki.merge"; + @Override public String Cmd_name() {return "merge";} + @Override public boolean Cmd_suspendable() {return true;} + @Override protected void Cmd_exec_hook(Xobc_cmd_ctx ctx) { + Xow_wiki wiki = ctx.App().Wiki_mgri().Make(Bry_.new_u8(wiki_domain), ctx.App().Fsys_mgr().Wiki_dir().GenSubDir(wiki_domain)); + Io_url[] fils = Io_mgr.Instance.QueryDir_fils(src_dir); + for (Io_url fil : fils) { + if (prog_wkr.Checkpoint__skip_fil(fil)) continue; + switch (Split_file_tid_.To_tid(fil)) { + case Split_file_tid_.Tid__core: merge_mgr.ctx.Idx_cur_add_(); merge_mgr.Merge_core(wiki, fil); break; + case Split_file_tid_.Tid__data: merge_mgr.ctx.Idx_cur_add_(); merge_mgr.Merge_data(wiki, fil, idx_cur); break; + } + } + prog_wkr.Checkpoint__delete(); + } + @Override public void Cmd_cleanup() { + Io_mgr.Instance.DeleteDirDeep(src_dir.OwnerDir()); // src_dir is "unzip" dir; owner dir is actual archive dir + } + @Override protected long Load_checkpoint_hook() {return prog_wkr.Checkpoint__load();} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__wiki_reg.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__wiki_reg.java index a27517de8..ef9c725ad 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__wiki_reg.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd__wiki_reg.java @@ -13,3 +13,39 @@ 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.addons.bldrs.centrals.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.security.*; +import gplx.xowa.wikis.*; +public class Xobc_cmd__wiki_reg extends Xobc_cmd__base { + private final Io_url wiki_dir; + private final String wiki_domain; + public Xobc_cmd__wiki_reg(Xobc_task_mgr task_mgr, int task_id, int step_id, int cmd_idx, Io_url wiki_dir, String wiki_domain) {super(task_mgr, task_id, step_id, cmd_idx); + this.wiki_dir = wiki_dir; + this.wiki_domain = wiki_domain; + } + @Override public String Cmd_type() {return CMD_TYPE;} public static final String CMD_TYPE = "xowa.wiki.reg"; + @Override public String Cmd_name() {return "import";} + @Override protected void Cmd_exec_hook(Xobc_cmd_ctx ctx) { + ctx.App().User().User_db_mgr().Init_site_mgr(); // must init for wiki.register cmd + + // get wiki_core_url + Io_url[] wiki_fils = Io_mgr.Instance.QueryDir_fils(wiki_dir); + Io_url wiki_core_url = null; + int len = wiki_fils.length; + for (int i = 0; i < len; ++i) { + Io_url url = wiki_fils[i]; + if (gplx.xowa.wikis.data.Xow_db_file__core_.Is_core_fil_name(wiki_domain, url.NameAndExt())) { + wiki_core_url = url; + break; + } + } + if (wiki_core_url == null) throw Err_.new_("wiki_import", "import_url not found", "domain", wiki_domain); + + // import; open + Xoa_wiki_mgr wiki_mgr = ctx.App().Wiki_mgri(); + Gfo_invk_.Invk_by_val(wiki_mgr, gplx.xowa.wikis.Xoa_wiki_mgr_.Invk__import_by_url, wiki_core_url); + // COMMENTED: do not auto-open wiki; wait for true-pack mode + // Xow_wiki wiki = wiki_mgr.Get_by_or_null(Bry_.new_u8(wiki_domain)); + // Gfo_invk_.Invk_by_msg(ctx.App().Gui__tab_mgr() , gplx.xowa.guis.tabs.Xog_tab_mgr_.Invk__new_tab, GfoMsg_.new_cast_("").Add("focus", true).Add("site", wiki_domain).Add("page", String_.new_u8(wiki.Props().Main_page()))); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd_ctx.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd_ctx.java index a27517de8..597846fc0 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd_ctx.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd_ctx.java @@ -13,3 +13,8 @@ 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.addons.bldrs.centrals.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +public class Xobc_cmd_ctx { + public Xobc_cmd_ctx(Xoa_app app) {this.app = app;} + public Xoa_app App() {return app;} private final Xoa_app app; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd_itm.java index a27517de8..9cd281e46 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/cmds/Xobc_cmd_itm.java @@ -13,3 +13,20 @@ 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.addons.bldrs.centrals.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.progs.*; import gplx.core.gfobjs.*; +public interface Xobc_cmd_itm extends Gfo_prog_ui, Gfo_invk { + int Task_id(); + int Step_id(); + int Cmd_id(); + String Cmd_type(); // "xowa.core.http_download" + String Cmd_name(); // "download" + boolean Cmd_suspendable(); // "true" + String Cmd_uid(); // for thread_pool_mgr: "0:0:0" + void Cmd_cleanup(); + String Cmd_fallback(); + void Cmd_clear(); + + Gfobj_nde Save_to(Gfobj_nde nde); + void Load_checkpoint(); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/Xobc_data_db.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/Xobc_data_db.java index a27517de8..52a1dad11 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/Xobc_data_db.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/Xobc_data_db.java @@ -13,3 +13,49 @@ 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.addons.bldrs.centrals.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.dbs.*; +import gplx.xowa.addons.bldrs.centrals.dbs.datas.*; import gplx.xowa.addons.bldrs.centrals.dbs.datas.imports.*; +public class Xobc_data_db { + public Xobc_data_db(Io_url db_url) { + this.url = db_url; + this.conn = Db_conn_bldr.Instance.Get_or_autocreate(true, db_url); + this.tbl__task_regy = new Xobc_task_regy_tbl(conn); + this.tbl__step_regy = new Xobc_step_regy_tbl(conn); + this.tbl__step_map = new Xobc_step_map_tbl(conn); + this.tbl__import_step = new Xobc_import_step_tbl(conn); + this.tbl__host_regy = new Xobc_host_regy_tbl(conn); + this.tbl__version_regy = new Xobc_version_regy_tbl(conn); + this.tbl__lang_regy = new Xobc_lang_regy_tbl(conn); + conn.Meta_tbl_assert(tbl__task_regy, tbl__step_regy, tbl__step_map, tbl__import_step, tbl__host_regy, tbl__version_regy, tbl__lang_regy); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public Io_url Url() {return url;} private final Io_url url; + public Xobc_task_regy_tbl Tbl__task_regy() {return tbl__task_regy;} private final Xobc_task_regy_tbl tbl__task_regy; + public Xobc_step_regy_tbl Tbl__step_regy() {return tbl__step_regy;} private final Xobc_step_regy_tbl tbl__step_regy; + public Xobc_step_map_tbl Tbl__step_map() {return tbl__step_map;} private final Xobc_step_map_tbl tbl__step_map; + public Xobc_import_step_tbl Tbl__import_step() {return tbl__import_step;} private final Xobc_import_step_tbl tbl__import_step; + public Xobc_host_regy_tbl Tbl__host_regy() {return tbl__host_regy;} private final Xobc_host_regy_tbl tbl__host_regy; + public Xobc_version_regy_tbl Tbl__version_regy() {return tbl__version_regy;} private final Xobc_version_regy_tbl tbl__version_regy; + public Xobc_lang_regy_tbl Tbl__lang_regy() {return tbl__lang_regy;} private final Xobc_lang_regy_tbl tbl__lang_regy; + + public void Delete_by_import(byte[] wiki_abrv, String wiki_date) { + // get all step ids from import_regy + Xobc_task_step_hash task_step_hash = new Xobc_task_step_hash(); + tbl__import_step.Select_tasks_steps(task_step_hash, tbl__step_map, wiki_abrv, wiki_date); + for (int i = 0; i < task_step_hash.Tasks__len(); ++i) { + int task_id = task_step_hash.Tasks__get_at(i); + tbl__task_regy.Delete(task_id); + tbl__step_map.Delete_by_task_id(task_id); + } + for (int i = 0; i < task_step_hash.Steps__len(); ++i) { + int step_id = task_step_hash.Steps__get_at(i); + tbl__step_regy.Delete(step_id); + tbl__import_step.Delete(step_id); + } + } + + public static Xobc_data_db New(gplx.xowa.apps.fsys.Xoa_fsys_mgr fsys_mgr) { + return new Xobc_data_db(fsys_mgr.Bin_addon_dir().GenSubFil_nest("bldr", "central", "bldr_central.data_db.xowa")); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/Xobc_data_db_upgrader.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/Xobc_data_db_upgrader.java index a27517de8..0760f05d3 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/Xobc_data_db_upgrader.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/Xobc_data_db_upgrader.java @@ -13,3 +13,75 @@ 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.addons.bldrs.centrals.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.dbs.*; +import gplx.core.progs.*; import gplx.core.net.downloads.*; import gplx.core.gfobjs.*; +import gplx.xowa.users.data.*; import gplx.xowa.addons.bldrs.centrals.dbs.datas.*; +import gplx.xowa.wikis.pages.tags.*; +public class Xobc_data_db_upgrader { + private static final String + Cfg__last_check_date = "xowa.bldr_central.data_db.last_check_date" + // , Cfg__next_check_interval = "xowa.bldr_central.data_db.next_check_interval" + , Date_fmt = "yyyy-MM-dd HH:mm" + ; + public static void Check_for_updates(Xobc_task_mgr task_mgr) { + Io_url data_db_url = task_mgr.Data_db().Url(); + Xoud_cfg_mgr cfg_mgr = task_mgr.App().User().User_db_mgr().Cfg_mgr(); + + // check if update needed + //String last_check_str = cfg_mgr.Select_str_or("", Cfg__last_check_date, ""); + //int next_check_interval = cfg_mgr.Select_int_or("", Cfg__next_check_interval, 24 * 7); + //DateAdp last_check = DateAdp_.parse_fmt_or(last_check_str, Date_fmt, null); + //if (last_check != null) { // check if last_check_str exists + // Time_span span = Datetime_now.Get().Diff(last_check); + // if (span.Total_hours().To_double() < next_check_interval) { // check if enough time passed + // Gfo_log_.Instance.Info("xobc_db update not needed", "last_check", last_check_str, "next_check_interval", next_check_interval); + // return; + // } + // else + // Gfo_log_.Instance.Info("xobc_db update needed b/c of last_check", "last_check", last_check_str); + //} + //else + // Gfo_log_.Instance.Info("xobc_db update needed b/c of missing or invalid last_check_str", "last_check", last_check_str); + + // update needed; first, update cfg_key, then get host + cfg_mgr.Upsert_str("", Cfg__last_check_date, Datetime_now.Get().XtoStr_fmt(Date_fmt)); + Xobc_data_db bc_db = task_mgr.Data_db(); + Xobc_host_regy_itm host_itm = bc_db.Tbl__host_regy().Select(Xobc_host_regy_tbl.Host_id__archive_org); + + // download manifest + Http_download_wkr download_wkr = Http_download_wkr_.Proto.Make_new(); + Io_url manifest_url = data_db_url.GenNewExt(".txt"); + download_wkr.Exec(Gfo_prog_ui_.Always + , String_.Format("http://{0}/{1}/bldr_central.data_db.txt", host_itm.Host_domain(), host_itm.Host_update_dir()) + , manifest_url, -1); + + // parse manifest and get version_id + byte[] manifest_txt = Io_mgr.Instance.LoadFilBry(manifest_url); + byte[][] manifest_data = Bry_split_.Split(manifest_txt, Byte_ascii.Pipe); + int expd_version_id = Bry_.To_int(manifest_data[0]); + Xobc_version_regy_itm actl_version = bc_db.Tbl__version_regy().Select_latest(); + + // cleanup + Io_mgr.Instance.DeleteFil(manifest_url); + bc_db.Conn().Rls_conn(); + if (expd_version_id == actl_version.Id()) { + Gfo_log_.Instance.Info("xobc_db update not needed", "version", expd_version_id); + Xopg_alertify_.Exec_log(task_mgr, "Wikis are up-to-date", 30); + return; // version matches; exit + } + + // version doesn't match; download new + // next_check_interval = Bry_.To_int(manifest_data[2]); + // cfg_mgr.Upsert_int("", Cfg__next_check_interval, next_check_interval); + // Gfo_log_.Instance.Info("xobc_db update needed", "version", expd_version_id, "next_check_interval", next_check_interval); + byte[] new_db_url = manifest_data[1]; + String note = String_.new_u8(manifest_data[3]); + download_wkr.Exec(Gfo_prog_ui_.Always + , String_.new_u8(new_db_url) + , data_db_url, -1); + Xopg_alertify_.Exec_log(task_mgr, "Wikis have been updated:
    " + note, 30); + task_mgr.Load_or_init(); + task_mgr.Reload(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/Xobc_task_step_hash.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/Xobc_task_step_hash.java index a27517de8..0d5d7d744 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/Xobc_task_step_hash.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/Xobc_task_step_hash.java @@ -13,3 +13,17 @@ 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.addons.bldrs.centrals.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +public class Xobc_task_step_hash { + private final Ordered_hash tasks_hash = Ordered_hash_.New(); + private final Ordered_hash steps_hash = Ordered_hash_.New(); + public int Tasks__len() {return tasks_hash.Len();} + public int Tasks__get_at(int i) {return Int_.Cast(tasks_hash.Get_at(i));} + public int Steps__len() {return steps_hash.Len();} + public int Steps__get_at(int i) {return Int_.Cast(steps_hash.Get_at(i));} + public void Clear() {tasks_hash.Clear(); steps_hash.Clear();} + public void Add(int task_id, int step_id) { + tasks_hash.Add_if_dupe_use_nth(task_id, task_id); + steps_hash.Add_if_dupe_use_nth(step_id, step_id); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/Xobc_user_db.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/Xobc_user_db.java index a27517de8..c7a664cc5 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/Xobc_user_db.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/Xobc_user_db.java @@ -13,3 +13,17 @@ 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.addons.bldrs.centrals.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.dbs.*; +import gplx.xowa.addons.bldrs.centrals.dbs.users.*; +public class Xobc_user_db { + public Xobc_user_db(Db_conn conn) { + this.work_task_tbl = new Xobc_work_task_tbl(conn); + this.done_task_tbl = new Xobc_done_task_tbl(conn); + this.done_step_tbl = new Xobc_done_step_tbl(conn); + conn.Meta_tbl_assert(work_task_tbl, done_task_tbl, done_step_tbl); + } + public Xobc_work_task_tbl Work_task_tbl() {return work_task_tbl;} private final Xobc_work_task_tbl work_task_tbl; + public Xobc_done_task_tbl Done_task_tbl() {return done_task_tbl;} private final Xobc_done_task_tbl done_task_tbl; + public Xobc_done_step_tbl Done_step_tbl() {return done_step_tbl;} private final Xobc_done_step_tbl done_step_tbl; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_host_regy_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_host_regy_itm.java index a27517de8..fd30b94d9 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_host_regy_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_host_regy_itm.java @@ -13,3 +13,16 @@ 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.addons.bldrs.centrals.dbs.datas; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; +public class Xobc_host_regy_itm { + public Xobc_host_regy_itm(int host_id, String host_domain, String host_data_dir, String host_update_dir) { + this.host_id = host_id; + this.host_domain = host_domain; + this.host_data_dir = host_data_dir; + this.host_update_dir = host_update_dir; + } + public int Host_id() {return host_id;} private final int host_id; + public String Host_domain() {return host_domain;} private final String host_domain; + public String Host_data_dir() {return host_data_dir;} private final String host_data_dir; + public String Host_update_dir() {return host_update_dir;} private final String host_update_dir; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_host_regy_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_host_regy_tbl.java index a27517de8..690dc2e51 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_host_regy_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_host_regy_tbl.java @@ -13,3 +13,37 @@ 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.addons.bldrs.centrals.dbs.datas; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; +import gplx.dbs.*; +public class Xobc_host_regy_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_host_id, fld_host_domain, fld_host_data_dir, fld_host_update_dir; + private final Db_conn conn; + public Xobc_host_regy_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "host_regy"; + this.fld_host_id = flds.Add_int_pkey("host_id"); + this.fld_host_domain = flds.Add_str("host_domain", 255); // EX: archive.org + this.fld_host_data_dir = flds.Add_str("host_data_dir", 255); // EX: download/Xowa_~{host_regy|wiki_abrv}_latest + this.fld_host_update_dir = flds.Add_str("host_update_dir", 255); // EX: download/Xowa_app_support + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() { + conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds)); + conn.Stmt_insert(tbl_name, flds) + .Val_int(fld_host_id, Host_id__archive_org).Val_str(fld_host_domain, "archive.org") + .Val_str(fld_host_data_dir, "download/Xowa_~{host_regy|wiki_abrv}_latest").Val_str(fld_host_update_dir, "download/Xowa_app_support") + .Exec_insert(); + } + public Xobc_host_regy_itm Select(int host_id) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_host_id).Crt_int(fld_host_id, host_id).Exec_select__rls_auto(); + try {return rdr.Move_next() ? Load_itm(rdr) : null;} + finally {rdr.Rls();} + } + private Xobc_host_regy_itm Load_itm(Db_rdr rdr) { + return new Xobc_host_regy_itm(rdr.Read_int(fld_host_id), rdr.Read_str(fld_host_domain), rdr.Read_str(fld_host_data_dir), rdr.Read_str(fld_host_update_dir)); + } + public void Rls() {} + public static final int Host_id__archive_org = 1; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_lang_regy_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_lang_regy_itm.java index a27517de8..866e1d64e 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_lang_regy_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_lang_regy_itm.java @@ -13,3 +13,14 @@ 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.addons.bldrs.centrals.dbs.datas; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; +public class Xobc_lang_regy_itm { + public Xobc_lang_regy_itm(int id, String key, String name) { + this.id = id; + this.key = key; + this.name = name; + } + public int Id() {return id;} private final int id; + public String Key() {return key;} private final String key; + public String Name() {return name;} private final String name; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_lang_regy_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_lang_regy_tbl.java index a27517de8..efe9dd5b3 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_lang_regy_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_lang_regy_tbl.java @@ -13,3 +13,31 @@ 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.addons.bldrs.centrals.dbs.datas; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; +import gplx.dbs.*; +public class Xobc_lang_regy_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld__lang_id, fld__lang_key, fld__lang_name; + private final Db_conn conn; + public Xobc_lang_regy_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "lang_regy"; + this.fld__lang_id = flds.Add_int_pkey("lang_id"); + this.fld__lang_key = flds.Add_str("lang_key", 255); + this.fld__lang_name = flds.Add_str("lang_name", 255); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public Xobc_lang_regy_itm[] Select_all() { + List_adp list = List_adp_.New(); + Db_rdr rdr = conn.Stmt_select(tbl_name, flds).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) + list.Add(new Xobc_lang_regy_itm(rdr.Read_int(fld__lang_id), rdr.Read_str(fld__lang_key), rdr.Read_str(fld__lang_name))); + } + finally {rdr.Rls();} + return (Xobc_lang_regy_itm[])list.To_ary_and_clear(Xobc_lang_regy_itm.class); + } + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_step_map_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_step_map_itm.java index a27517de8..33ca5fd19 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_step_map_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_step_map_itm.java @@ -13,3 +13,16 @@ 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.addons.bldrs.centrals.dbs.datas; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; +public class Xobc_step_map_itm { + public Xobc_step_map_itm(int sm_id, int task_id, int step_id, int step_seqn) { + this.Sm_id = sm_id; + this.Task_id = task_id; + this.Step_id = step_id; + this.Step_seqn = step_seqn; + } + public final int Sm_id; + public final int Task_id; + public final int Step_id; + public final int Step_seqn; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_step_map_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_step_map_tbl.java index a27517de8..cb194a648 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_step_map_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_step_map_tbl.java @@ -13,3 +13,78 @@ 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.addons.bldrs.centrals.dbs.datas; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; +import gplx.dbs.*; +public class Xobc_step_map_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_sm_id, fld_step_seqn, fld_task_id, fld_step_id; + private final Db_conn conn; private Db_stmt insert_stmt; + public Xobc_step_map_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "step_map"; + this.fld_sm_id = flds.Add_int_pkey("sm_id"); + this.fld_task_id = flds.Add_int("task_id"); + this.fld_step_id = flds.Add_int("step_id"); + this.fld_step_seqn = flds.Add_int("step_seqn"); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public int Select_step_id(int task_id, int step_seqn) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_task_id, fld_step_seqn).Crt_int(fld_task_id, task_id).Crt_int(fld_step_seqn, step_seqn).Exec_select__rls_auto(); + try { + if (rdr.Move_next()) + return rdr.Read_int(fld_step_id); + else + throw Err_.new_("", "xobc:could not find step_id", "task_id", task_id, "step_seqn", step_seqn); + } + finally {rdr.Rls();} + } + public Xobc_step_map_itm Select_one(int task_id, int step_id) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_task_id, fld_step_id).Crt_int(fld_task_id, task_id).Crt_int(fld_step_id, step_id).Exec_select__rls_auto(); + try { + if (rdr.Move_next()) + return new Xobc_step_map_itm + ( rdr.Read_int(fld_sm_id) + , rdr.Read_int(fld_task_id) + , rdr.Read_int(fld_step_id) + , rdr.Read_int(fld_step_seqn) + ); + else + throw Err_.new_("", "bldr.central:could not find step_id", "task_id", task_id, "step_id", step_id); + } + finally {rdr.Rls();} + } + public int Select_seqn_but_skip_done(int task_id, Hash_adp step_ids) { + Db_rdr rdr = conn.Stmt_select_order(tbl_name, flds, String_.Ary(fld_task_id), fld_step_seqn).Crt_int(fld_task_id, task_id).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + int step_id = rdr.Read_int("step_id"); + if (step_ids.Has(step_id)) + return rdr.Read_int("step_seqn"); + } + } finally {rdr.Rls();} + throw Err_.new_("", "xobc:could not find next sort", "task_id", task_id); + } + public List_adp Select_all(int task_id) { + List_adp rv = List_adp_.New(); + Db_rdr rdr = conn.Stmt_select_order(tbl_name, flds, String_.Ary(fld_task_id), fld_step_seqn).Crt_int(fld_task_id, task_id).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + rv.Add(rdr.Read_int("step_id")); + } + } finally {rdr.Rls();} + return rv; + } + public void Insert(int sm_id, int task_id, int step_id, int step_seqn) { + if (insert_stmt == null) insert_stmt = conn.Stmt_insert(tbl_name, flds); + insert_stmt.Clear().Val_int(fld_sm_id, sm_id).Val_int(fld_task_id, task_id).Val_int(fld_step_id, step_id).Val_int(fld_step_seqn, step_seqn) + .Exec_insert(); + } + public void Delete_by_task_id(int task_id) { + conn.Stmt_delete(tbl_name, fld_task_id).Crt_int(fld_task_id, task_id).Exec_delete(); + } + public void Rls() { + insert_stmt = Db_stmt_.Rls(insert_stmt); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_step_regy_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_step_regy_tbl.java index a27517de8..87885b321 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_step_regy_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_step_regy_tbl.java @@ -13,3 +13,41 @@ 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.addons.bldrs.centrals.dbs.datas; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; +import gplx.dbs.*; +import gplx.xowa.addons.bldrs.centrals.cmds.*; import gplx.xowa.addons.bldrs.centrals.tasks.*; +public class Xobc_step_regy_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_step_id, fld_step_type; + private final Db_conn conn; private Db_stmt insert_stmt; + public Xobc_step_regy_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "step_regy"; + this.fld_step_id = flds.Add_int_pkey("step_id"); + this.fld_step_type = flds.Add_int("step_type"); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public int Select_type(int step_id) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_step_id).Crt_int(fld_step_id, step_id).Exec_select__rls_auto(); + try { + if (rdr.Move_next()) + return rdr.Read_int(fld_step_type); + else + throw Err_.new_("", "bldr.central:could not find step_type", "step_id", step_id); + } + finally {rdr.Rls();} + } + public void Insert(int step_id, int step_type) { + if (insert_stmt == null) insert_stmt = conn.Stmt_insert(tbl_name, flds); + insert_stmt.Clear().Val_int(fld_step_id, step_id).Val_int(fld_step_type, step_type) + .Exec_insert(); + } + public void Delete(int step_id) { + conn.Stmt_delete(tbl_name, fld_step_id).Crt_int(fld_step_id, step_id).Exec_delete(); + } + public void Rls() { + insert_stmt = Db_stmt_.Rls(insert_stmt); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_task_regy_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_task_regy_itm.java index a27517de8..f0a7e4a43 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_task_regy_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_task_regy_itm.java @@ -13,3 +13,21 @@ 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.addons.bldrs.centrals.dbs.datas; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; +public class Xobc_task_regy_itm { + public Xobc_task_regy_itm(int id, int seqn, byte[] key, byte[] name, int step_count) { + this.id = id; + this.seqn = seqn; + this.key = key; + this.name = name; + this.step_count = step_count; + } + public int Id() {return id;} private final int id; + public int Seqn() {return seqn;} private final int seqn; + public byte[] Key() {return key;} private final byte[] key; + public byte[] Name() {return name;} private final byte[] name; + public int Step_count() {return step_count;} private final int step_count; + + public static final String Type__text = "text", Type__html = "html", Type__file = "file"; + public static final int Seqn__obsolete = 999999; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_task_regy_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_task_regy_tbl.java index a27517de8..272810d06 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_task_regy_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_task_regy_tbl.java @@ -13,3 +13,86 @@ 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.addons.bldrs.centrals.dbs.datas; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; +import gplx.dbs.*; +import gplx.xowa.addons.bldrs.centrals.cmds.*; import gplx.xowa.addons.bldrs.centrals.tasks.*; +public class Xobc_task_regy_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_task_id, fld_task_seqn, fld_step_count, fld_task_key, fld_task_name; + private final Db_conn conn; private Db_stmt insert_stmt; + public Xobc_task_regy_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "task_regy"; + this.fld_task_id = flds.Add_int_pkey("task_id"); + this.fld_task_seqn = flds.Add_int("task_seqn"); + this.fld_step_count = flds.Add_int("step_count"); + this.fld_task_key = flds.Add_str("task_key", 255); + this.fld_task_name = flds.Add_str("task_name", 255); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() { + conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds)); + conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "task_seqn", fld_task_seqn)); + } + public void Select_all(Xobc_task_regy__base todo_regy) { + todo_regy.Clear(); + Db_rdr rdr = conn.Stmt_select_order(tbl_name, flds, String_.Ary_empty, fld_task_seqn).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + int task_seqn = rdr.Read_int(fld_task_seqn); + if (task_seqn == Xobc_task_regy_itm.Seqn__obsolete) continue; // WORKAROUND: do not show old tasks; should add a status column, but don't want to change schema yet; DATE:2016-10-20 + + int task_id = rdr.Read_int(fld_task_id); + int step_count = rdr.Read_int(fld_step_count); + String task_key = rdr.Read_str(fld_task_key); + String task_name = rdr.Read_str(fld_task_name); + todo_regy.Add(new Xobc_task_itm(task_id, task_seqn, step_count, task_key, task_name)); + } + } finally {rdr.Rls();} + } + public int Select_by_key(String key) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_task_key).Exec_select__rls_auto(); + try {return rdr.Move_next() ? rdr.Read_int(fld_task_id) : -1;} + finally {rdr.Rls();} + } + public String Select_key_by_id_or_null(int id) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_task_id).Crt_int(fld_task_id, id).Exec_select__rls_auto(); + try {return rdr.Move_next() ? rdr.Read_str(fld_task_key) : null;} + finally {rdr.Rls();} + } + public void Insert(int task_id, int task_seqn, int step_count, String task_key, String task_name) { + if (insert_stmt == null) insert_stmt = conn.Stmt_insert(tbl_name, flds); + insert_stmt.Clear().Val_int(fld_task_id, task_id).Val_int(fld_task_seqn, task_seqn).Val_int(fld_step_count, step_count) + .Val_str(fld_task_key, task_key).Val_str(fld_task_name, task_name) + .Exec_insert(); + } + public Xobc_task_regy_itm[] Select_by_wiki(byte[] wiki_domain) { + String sql = Db_sql_.Make_by_fmt(String_.Ary + ( "SELECT *" + , "FROM task_regy" + , "WHERE task_key LIKE '{0}%'" // DEPENDENCY:Xobc_task_key + , "AND task_seqn != 999999" + ), wiki_domain); + List_adp list = List_adp_.New(); + Db_rdr rdr = conn.Stmt_sql(sql).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + list.Add(new Xobc_task_regy_itm + ( rdr.Read_int("task_id") + , rdr.Read_int("task_seqn") + , rdr.Read_bry_by_str("task_key") + , rdr.Read_bry_by_str("task_name") + , rdr.Read_int("step_count") + )); + } + } finally {rdr.Rls();} + return (Xobc_task_regy_itm[])list.To_ary_and_clear(Xobc_task_regy_itm.class); + } + public void Delete(int task_id) { + conn.Stmt_delete(tbl_name, fld_task_id).Crt_int(fld_task_id, task_id).Exec_delete(); + } + public void Rls() { + insert_stmt = Db_stmt_.Rls(insert_stmt); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_version_regy_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_version_regy_itm.java index a27517de8..35b5218ed 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_version_regy_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_version_regy_itm.java @@ -13,3 +13,14 @@ 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.addons.bldrs.centrals.dbs.datas; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; +public class Xobc_version_regy_itm { + public Xobc_version_regy_itm(int id, String date, String note) { + this.id = id; + this.date = date; + this.note = note; + } + public int Id() {return id;} private final int id; + public String Date() {return date;} private final String date; + public String Note() {return note;} private final String note; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_version_regy_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_version_regy_tbl.java index a27517de8..97f68e713 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_version_regy_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/Xobc_version_regy_tbl.java @@ -13,3 +13,36 @@ 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.addons.bldrs.centrals.dbs.datas; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; +import gplx.dbs.*; +public class Xobc_version_regy_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_version_id, fld_version_date, fld_version_note; + private final Db_conn conn; + public Xobc_version_regy_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "version_regy"; + this.fld_version_id = flds.Add_int_pkey("version_id"); + this.fld_version_date = flds.Add_str("version_date", 16); + this.fld_version_note = flds.Add_str("version_note", 255); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() { + conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds)); + conn.Stmt_insert(tbl_name, flds) + .Val_int(fld_version_id, 1).Val_str(fld_version_date, Datetime_now.Get().XtoStr_fmt_yyyy_MM_dd_HH_mm()).Val_str(fld_version_note, "initial") + .Exec_insert(); + } + public Xobc_version_regy_itm Select_latest() { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds).Exec_select__rls_auto(); + try { + if (rdr.Move_next()) + return new Xobc_version_regy_itm(rdr.Read_int(fld_version_id), rdr.Read_str(fld_version_date), rdr.Read_str(fld_version_note)); + else + throw Err_.new_("", "version_regy does not have version"); + } + finally {rdr.Rls();} + } + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_import_date.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_import_date.java index a27517de8..029f5de13 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_import_date.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_import_date.java @@ -13,3 +13,9 @@ 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.addons.bldrs.centrals.dbs.datas.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; import gplx.xowa.addons.bldrs.centrals.dbs.datas.*; +public class Xobc_import_date { + public static String To_str__yyyy_mm(String raw, String dlm) { + return String_.Mid(raw, 0, 4) + dlm + String_.Mid(raw, 4, 6); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_import_step_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_import_step_itm.java index a27517de8..335fc2cc2 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_import_step_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_import_step_itm.java @@ -13,3 +13,38 @@ 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.addons.bldrs.centrals.dbs.datas.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; import gplx.xowa.addons.bldrs.centrals.dbs.datas.*; +import gplx.dbs.*; +public class Xobc_import_step_itm { + public Xobc_import_step_itm(int step_id, int host_id, byte[] wiki_abrv, String wiki_date + , String import_name, int import_type, byte import_zip_type, long import_size_zip, long import_size_raw, String import_md5 + , long import_prog_data_max, int import_prog_row_max + ) { + this.Step_id = step_id; + this.Host_id = host_id; + this.wiki_abrv = wiki_abrv; + this.Wiki_date = wiki_date; + this.Import_name = import_name; + this.Import_type = import_type; + this.Import_zip_type = import_zip_type; + this.Import_size_zip = import_size_zip; + this.Import_size_raw = import_size_raw; + this.Import_md5 = import_md5; + this.Import_prog_data_max = import_prog_data_max; + this.Import_prog_row_max = import_prog_row_max; + } + public final int Step_id; + public final int Host_id; + public byte[] Wiki_abrv() {return wiki_abrv;} private final byte[] wiki_abrv; + public final String Wiki_date; + public final String Import_name; + public final int Import_type; + public final byte Import_zip_type; + public final long Import_size_zip; + public final long Import_size_raw; + public final String Import_md5; + public final long Import_prog_data_max; + public final int Import_prog_row_max; + + public static final Xobc_import_step_itm Null = null; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_import_step_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_import_step_tbl.java index a27517de8..bb0cff2c3 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_import_step_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_import_step_tbl.java @@ -13,3 +13,106 @@ 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.addons.bldrs.centrals.dbs.datas.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; import gplx.xowa.addons.bldrs.centrals.dbs.datas.*; +import gplx.dbs.*; +public class Xobc_import_step_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_step_id, fld_host_id, fld_wiki_abrv, fld_wiki_date, fld_import_name, fld_import_type, fld_import_zip, fld_import_md5, fld_import_size_zip, fld_import_size_raw, fld_prog_size_end, fld_prog_count_end; + public final Db_conn conn; private Db_stmt insert_stmt; + public Xobc_import_step_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "import_step"; + this.fld_step_id = flds.Add_int_pkey("step_id"); + this.fld_host_id = flds.Add_int("host_id"); + this.fld_wiki_abrv = flds.Add_str("wiki_abrv", 255); + this.fld_wiki_date = flds.Add_str("wiki_date", 8); + this.fld_import_name = flds.Add_str("import_name", 255); + this.fld_import_type = flds.Add_int("import_type"); + this.fld_import_zip = flds.Add_byte("import_zip"); + this.fld_import_size_zip = flds.Add_long("import_size_zip"); + this.fld_import_size_raw = flds.Add_long("import_size_raw"); + this.fld_import_md5 = flds.Add_str("import_md5", 48); + this.fld_prog_size_end = flds.Add_long("prog_size_end"); + this.fld_prog_count_end = flds.Add_long("prog_count_end"); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public Xobc_import_step_itm Select_one(int step_id) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_step_id).Crt_int(fld_step_id, step_id).Exec_select__rls_auto(); + try { + return (rdr.Move_next()) + ? New_itm(rdr) + : Xobc_import_step_itm.Null; + } + finally {rdr.Rls();} + } + public void Delete(int step_id) { + conn.Stmt_delete(tbl_name, fld_step_id).Crt_int(fld_step_id, step_id).Exec_delete(); + } + public void Insert(int step_id, int host_id, byte[] wiki_abrv, String wiki_date, String import_name, int import_type, byte zip_type, byte[] md5, long size_zip, long size_raw + , long prog_size_end, int prog_count_end) { + if (insert_stmt == null) insert_stmt = conn.Stmt_insert(tbl_name, flds); + insert_stmt.Clear().Val_int(fld_step_id, step_id).Val_int(fld_host_id, host_id) + .Val_bry_as_str(fld_wiki_abrv, wiki_abrv).Val_str(fld_wiki_date, wiki_date) + .Val_str(fld_import_name, import_name) + .Val_int(fld_import_type, import_type).Val_byte(fld_import_zip, zip_type) + .Val_long(fld_import_size_zip, size_zip).Val_long(fld_import_size_raw, size_raw).Val_bry_as_str(fld_import_md5, md5) + .Val_long(fld_prog_size_end, prog_size_end).Val_int(fld_prog_count_end, prog_count_end) + .Exec_insert(); + } + public void Select_tasks_steps(Xobc_task_step_hash task_step_hash, Xobc_step_map_tbl step_map_tbl, byte[] wiki_abrv, String wiki_date) { + task_step_hash.Clear(); + Db_rdr rdr = conn.Stmt_sql(String_.Concat_lines_nl_skip_last + ( "SELECT DISTINCT sm.task_id, sm.step_id" + , "FROM " + tbl_name + " imps" + , " JOIN " + step_map_tbl.Tbl_name() + " sm ON sm.step_id = imps.step_id" + , "WHERE imps.wiki_abrv = ?" + , "AND imps.wiki_date = ?" + )) + .Crt_bry_as_str(fld_wiki_abrv, wiki_abrv) + .Crt_str(fld_wiki_date, wiki_date) + .Exec_select__rls_auto() + ; + try { + while (rdr.Move_next()) { + task_step_hash.Add(rdr.Read_int("task_id"), rdr.Read_int("step_id")); + } + } finally {rdr.Rls();} + } + public Xobc_import_step_itm[] Select_by_task_id(int task_id) { + List_adp list = List_adp_.New(); + Db_rdr rdr = conn.Stmt_sql(Db_sql_.Make_by_fmt(String_.Ary + ( "SELECT s.*" + , "FROM import_step s" + , " JOIN step_map sm ON s.step_id = sm.step_id" + , "WHERE sm.task_id = {0}" + ), task_id)) + .Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + list.Add(New_itm(rdr)); + } + } finally {rdr.Rls();} + return (Xobc_import_step_itm[])list.To_ary_and_clear(Xobc_import_step_itm.class); + } + public void Rls() { + insert_stmt = Db_stmt_.Rls(insert_stmt); + } + private Xobc_import_step_itm New_itm(Db_rdr rdr) { + return new Xobc_import_step_itm + ( rdr.Read_int(fld_step_id) + , rdr.Read_int(fld_host_id) + , rdr.Read_bry_by_str(fld_wiki_abrv) + , rdr.Read_str(fld_wiki_date) + , rdr.Read_str(fld_import_name) + , rdr.Read_int(fld_import_type) + , rdr.Read_byte(fld_import_zip) + , rdr.Read_long(fld_import_size_zip) + , rdr.Read_long(fld_import_size_raw) + , rdr.Read_str(fld_import_md5) + , rdr.Read_long(fld_prog_size_end) + , rdr.Read_int(fld_prog_count_end) + ); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_import_type.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_import_type.java index a27517de8..e237decf6 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_import_type.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_import_type.java @@ -13,3 +13,21 @@ 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.addons.bldrs.centrals.dbs.datas.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; import gplx.xowa.addons.bldrs.centrals.dbs.datas.*; +public class Xobc_import_type { + public static final int // SERIALIZED: bc_db; import_step + Tid__ignore = 0 + , Tid__pack = 1 + , Tid__wiki__core = 2 + , Tid__wiki__srch = 3 + , Tid__wiki__html = 4 + , Tid__wiki__text = 5 + , Tid__file__core = 6 + , Tid__file__data = 7 + , Tid__fsdb__delete = 8 + , Tid__wiki__ctg = 9 + , Tid__misc = 10 + , Tid__wiki__wbase = 11 + , Tid__wiki__lucene = 12 + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_zip_type.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_zip_type.java index a27517de8..e61ba27ed 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_zip_type.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/datas/imports/Xobc_zip_type.java @@ -13,3 +13,13 @@ 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.addons.bldrs.centrals.dbs.datas.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; import gplx.xowa.addons.bldrs.centrals.dbs.datas.*; +public class Xobc_zip_type { + public static String To_str(byte type) { + switch (type) { + case Type__zip: return "zip"; + default: throw Err_.new_unhandled_default(type); + } + } + public static final byte Type__zip = 1; // SERIALIZED +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/users/Xobc_done_step_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/users/Xobc_done_step_tbl.java index a27517de8..732b28f10 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/users/Xobc_done_step_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/users/Xobc_done_step_tbl.java @@ -13,3 +13,41 @@ 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.addons.bldrs.centrals.dbs.users; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; +import gplx.dbs.*; +import gplx.xowa.addons.bldrs.centrals.cmds.*; import gplx.xowa.addons.bldrs.centrals.tasks.*; +public class Xobc_done_step_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_task_id, fld_step_id; + private final Db_conn conn; + public Xobc_done_step_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "xobc_done_step"; + this.fld_task_id = flds.Add_int("task_id"); + this.fld_step_id = flds.Add_int("step_id"); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() { + conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds)); + conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "main", fld_task_id, fld_step_id)); + } + public void Insert(int task_id, int step_id) { + conn.Stmt_insert(tbl_name, fld_task_id, fld_step_id) + .Val_int(fld_task_id, task_id).Val_int(fld_step_id, step_id) + .Exec_insert(); + } + public Hash_adp Select_all(int task_id) { + Hash_adp rv = null; + Db_rdr rdr = conn.Stmt_select(tbl_name, String_.Ary(fld_step_id), fld_task_id).Crt_int(fld_task_id, task_id).Exec_select__rls_auto(); + try { + if (rdr.Move_next()) { + if (rv == null) rv = Hash_adp_.New(); + rv.Add_as_key_and_val(rdr.Read_int("step_id")); + } + } + finally {rdr.Rls();} + return null; + } + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/users/Xobc_done_task_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/users/Xobc_done_task_tbl.java index a27517de8..844b57452 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/users/Xobc_done_task_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/users/Xobc_done_task_tbl.java @@ -13,3 +13,50 @@ 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.addons.bldrs.centrals.dbs.users; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; +import gplx.dbs.*; +import gplx.xowa.addons.bldrs.centrals.cmds.*; import gplx.xowa.addons.bldrs.centrals.tasks.*; +public class Xobc_done_task_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_task_id, fld_task_seqn; + private final Db_conn conn; + public Xobc_done_task_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "xobc_done_task"; + this.fld_task_id = flds.Add_int_pkey("task_id"); + this.fld_task_seqn = flds.Add_int("task_seqn"); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Insert(int task_id, int task_seqn) { + conn.Stmt_insert(tbl_name, fld_task_id, fld_task_seqn) + .Val_int(fld_task_id, task_id).Val_int(fld_task_seqn, task_seqn) + .Exec_insert(); + } + public void Delete(int task_id) { + conn.Stmt_delete(tbl_name, fld_task_id) + .Crt_int(fld_task_id, task_id) + .Exec_delete(); + } + public void Select_all(Xobc_task_regy__base todo_regy, Xobc_task_regy__base done_regy) { + done_regy.Clear(); + Db_rdr rdr = conn.Stmt_select_all(tbl_name, flds).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + int task_id = rdr.Read_int(fld_task_id); + int task_seqn = rdr.Read_int(fld_task_seqn); + if (task_seqn == gplx.xowa.addons.bldrs.centrals.dbs.datas.Xobc_task_regy_itm.Seqn__obsolete) continue; // WORKAROUND: do not show old tasks; should add a status column, but don't want to change schema yet; DATE:2016-11-03 + Xobc_task_itm itm = (Xobc_task_itm)todo_regy.Get_by(task_id); + if (itm == null) { + Gfo_log_.Instance.Warn("task exists in done, but not in todo", "task_id", task_id); + continue; + } + done_regy.Add(itm); + todo_regy.Del_by(task_id); + itm.Task_seqn_(task_seqn); + } + } finally {rdr.Rls();} + } + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/users/Xobc_work_task_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/users/Xobc_work_task_tbl.java index a27517de8..3e88aec6f 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/users/Xobc_work_task_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/dbs/users/Xobc_work_task_tbl.java @@ -13,3 +13,59 @@ 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.addons.bldrs.centrals.dbs.users; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; +import gplx.dbs.*; +import gplx.xowa.addons.bldrs.centrals.cmds.*; import gplx.xowa.addons.bldrs.centrals.tasks.*; +public class Xobc_work_task_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_task_id, fld_task_seqn, fld_step_id, fld_cmd_id; + private final Db_conn conn; + public Xobc_work_task_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "xobc_work_task"; + this.fld_task_id = flds.Add_int_pkey("task_id"); + this.fld_task_seqn = flds.Add_int("task_seqn"); + this.fld_step_id = flds.Add_int("step_id"); + this.fld_cmd_id = flds.Add_int("cmd_id"); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Select_all(Xobc_task_mgr task_mgr, Xobc_task_regy__base todo_regy, Xobc_task_regy__base work_regy) { + work_regy.Clear(); + Db_rdr rdr = conn.Stmt_select_all(tbl_name, flds).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + int task_id = rdr.Read_int(fld_task_id); + int task_seqn = rdr.Read_int(fld_task_seqn); + int step_id = rdr.Read_int(fld_step_id); + int cmd_id = rdr.Read_int(fld_cmd_id); + Xobc_task_itm task = (Xobc_task_itm)todo_regy.Get_by(task_id); + if (task == null) { + Gfo_log_.Instance.Warn("task exists in work, but not in todo", "task_id", task_id); + continue; + } + work_regy.Add(task); + todo_regy.Del_by(task_id); + task.Task_seqn_(task_seqn); + task.Task_status_(cmd_id == Xobc_cmd__base.Seqn__0 ? gplx.core.progs.Gfo_prog_ui_.Status__init : gplx.core.progs.Gfo_prog_ui_.Status__working); + task_mgr.Step_mgr().Load(task, step_id, cmd_id); + } + work_regy.Sort(); + } finally {rdr.Rls();} + } + public void Insert(int task_id, int task_seqn, int step_id, int cmd_id) { + conn.Stmt_insert(tbl_name, fld_task_id, fld_task_seqn, fld_step_id, fld_cmd_id) + .Val_int(fld_task_id, task_id).Val_int(fld_task_seqn, task_seqn).Val_int(fld_step_id, step_id).Val_int(fld_cmd_id, cmd_id) + .Exec_insert(); + } + public void Update(int task_id, int task_seqn, int step_id, int cmd_id) { + conn.Stmt_update_exclude(tbl_name, flds, fld_task_id) + .Val_int(fld_task_seqn, task_seqn).Val_int(fld_step_id, step_id).Val_int(fld_cmd_id, cmd_id).Crt_int(fld_task_id, task_id) + .Exec_update(); + } + public void Delete(int task_id) { + conn.Stmt_delete(tbl_name, fld_task_id).Val_int(fld_task_id, task_id).Exec_insert(); + } + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/hosts/Host_eval_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/hosts/Host_eval_itm.java index a27517de8..cec1ed809 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/hosts/Host_eval_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/hosts/Host_eval_itm.java @@ -13,3 +13,32 @@ 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.addons.bldrs.centrals.hosts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.xowa.addons.bldrs.centrals.dbs.*; import gplx.xowa.addons.bldrs.centrals.dbs.datas.*; +import gplx.core.brys.evals.*; +import gplx.xowa.wikis.domains.*; +public class Host_eval_itm { + private final Bry_eval_mgr mgr = Bry_eval_mgr.Dflt(); + private final Host_eval_wkr wkr = new Host_eval_wkr(); + private final Hash_adp host_hash = Hash_adp_.New(); + public Host_eval_itm() { + mgr.Add_many(wkr); + } + public byte[] Eval_dir_name(Xow_domain_itm domain_itm) { + wkr.Domain_itm_(domain_itm); + return mgr.Eval(Bry_.new_u8("Xowa_~{host_regy|wiki_abrv}_latest")); + } + public String Eval_src_fil(Xobc_data_db data_db, int host_id, Xow_domain_itm domain, String file_name) { + return Eval_src_dir(data_db, host_id, domain) + file_name; + } + public String Eval_src_dir(Xobc_data_db data_db, int host_id, Xow_domain_itm domain) { + Xobc_host_regy_itm host_itm = (Xobc_host_regy_itm)host_hash.Get_by(host_id); + if (host_itm == null) { + host_itm = data_db.Tbl__host_regy().Select(host_id); + host_hash.Add(host_id, host_itm); + } + wkr.Domain_itm_(domain); + String host_dir = String_.new_u8(mgr.Eval(Bry_.new_u8(host_itm.Host_data_dir()))); + return String_.Format("http://{0}/{1}/", host_itm.Host_domain(), host_dir); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/hosts/Host_eval_wkr.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/hosts/Host_eval_wkr.java index a27517de8..5d5b00d7b 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/hosts/Host_eval_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/hosts/Host_eval_wkr.java @@ -13,3 +13,37 @@ 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.addons.bldrs.centrals.hosts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.brys.evals.*; +import gplx.xowa.wikis.domains.*; +public class Host_eval_wkr implements Bry_eval_wkr { + private Xow_domain_itm domain_itm; + public String Key() {return "host_regy";} + public Host_eval_wkr Domain_itm_(Xow_domain_itm domain_itm) {this.domain_itm = domain_itm; return this;} + public void Resolve(Bry_bfr rv, byte[] src, int args_bgn, int args_end) { + // EX: "~{host_regy|wiki_abrv}" -> "enwiki" + int type = hash.Get_as_byte_or(src, args_bgn, args_end, Byte_.Max_value_127); + switch (type) { + case Type__wiki_abrv: + // handle wikidata, commonswiki separately; DATE:2016-10-20 + if (String_.Eq(domain_itm.Domain_str(), "www.wikidata.org")) + rv.Add_str_a7("wikidatawiki"); + else if (String_.Eq(domain_itm.Domain_str(), "commons.wikimedia.org")) + rv.Add_str_a7("commonswiki"); + // do not use Abrv_mw(); all other wikis will be "generalized" to their language url; EX:"en.wiktionary.org" -> "enwiki" x> "enwiktionary" + else { + byte[] lang_key = domain_itm.Lang_orig_key(); + if (lang_key == Bry_.Empty) lang_key = Bry_.new_a7("en"); // handle species + rv.Add(lang_key); + rv.Add_str_a7("wiki"); + } + break; + default: throw Err_.new_unhandled_default(type); + } + } + + public static final byte Type__wiki_abrv = 0; + private static final Hash_adp_bry hash = Hash_adp_bry.cs() + .Add_str_byte("wiki_abrv", Type__wiki_abrv) + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/hosts/Host_eval_wkr__tst.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/hosts/Host_eval_wkr__tst.java index a27517de8..43f423117 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/hosts/Host_eval_wkr__tst.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/hosts/Host_eval_wkr__tst.java @@ -13,3 +13,18 @@ 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.addons.bldrs.centrals.hosts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import org.junit.*; import gplx.core.tests.*; import gplx.xowa.wikis.domains.*; +public class Host_eval_wkr__tst { + private final Host_eval_wkr__fxt fxt = new Host_eval_wkr__fxt(); + @Test public void En_w() {fxt.Test__resolve_quick("en.wikipedia.org" , "Xowa_enwiki_latest");} + @Test public void Fr_d() {fxt.Test__resolve_quick("fr.wiktionary.org" , "Xowa_frwiki_latest");} + @Test public void Species() {fxt.Test__resolve_quick("species.wikimedia.org" , "Xowa_enwiki_latest");} +} +class Host_eval_wkr__fxt { + public void Test__resolve_quick(String domain_str, String expd) { + Host_eval_itm eval_itm = new Host_eval_itm(); + Xow_domain_itm domain_itm = Xow_domain_itm_.parse(Bry_.new_u8(domain_str)); + Gftest.Eq__bry(Bry_.new_u8(expd), eval_itm.Eval_dir_name(domain_itm)); + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/mgrs/Xobc_filter_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/mgrs/Xobc_filter_mgr.java index a27517de8..628c6f8e2 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/mgrs/Xobc_filter_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/mgrs/Xobc_filter_mgr.java @@ -13,3 +13,38 @@ 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.addons.bldrs.centrals.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.gfobjs.*; +import gplx.xowa.addons.bldrs.centrals.tasks.*; +import gplx.xowa.wikis.domains.*; +import gplx.xowa.langs.*; +public class Xobc_filter_mgr { + private String lang_key_str = Xow_domain_itm_.Lang_key__all, type_key_str = Xow_domain_itm_.Type_key__all; + public Xobc_task_itm[] Filter(Xobc_task_regy__base task_list) {return Filter(task_list, lang_key_str, type_key_str);} + public Xobc_task_itm[] Filter(Xobc_task_regy__base task_list, String lang_key_str, String type_key_str) { + this.lang_key_str = lang_key_str; + this.type_key_str = type_key_str; + + List_adp tmp = List_adp_.New(); + + // loop tasks and find matches + int len = task_list.Len(); + for (int i = 0; i < len; ++i) { + Xobc_task_itm task = (Xobc_task_itm)task_list.Get_at(i); + Xobc_task_key task_key_itm = Xobc_task_key.To_itm(task.Task_key()); + Xow_domain_itm task_domain = task_key_itm.Wiki_domain_itm(); + if ( Xow_domain_itm_.Match_lang(task_domain, lang_key_str) + && Xow_domain_itm_.Match_type(task_domain, type_key_str) + ) + tmp.Add(task); + } + + return (Xobc_task_itm[])tmp.To_ary_and_clear(Xobc_task_itm.class); + } + public Gfobj_nde Make_init_msg() { + Gfobj_nde root = Gfobj_nde.New(); + root.New_nde("langs").Add_str("active", lang_key_str); + root.New_nde("types").Add_str("active", type_key_str); + return root; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/mgrs/Xobc_lang_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/mgrs/Xobc_lang_mgr.java index a27517de8..bcbf26ec5 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/mgrs/Xobc_lang_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/mgrs/Xobc_lang_mgr.java @@ -13,3 +13,20 @@ 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.addons.bldrs.centrals.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.gfobjs.*; +import gplx.xowa.addons.bldrs.centrals.dbs.datas.*; +public class Xobc_lang_mgr { + public Gfobj_ary Make_init_msg(Xobc_lang_regy_itm[] itms) { + List_adp list = List_adp_.New(); + int len = itms.length; + for (int i = 0; i < len; ++i) { + Xobc_lang_regy_itm itm = itms[i]; + Gfobj_nde itm_nde = Gfobj_nde.New(); + list.Add(itm_nde); + itm_nde.Add_str("key", itm.Key()); + itm_nde.Add_str("name", itm.Name()); + } + return new Gfobj_ary((Gfobj_nde[])list.To_ary_and_clear(Gfobj_nde.class)); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/mgrs/Xobc_skip_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/mgrs/Xobc_skip_mgr.java index a27517de8..c904292da 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/mgrs/Xobc_skip_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/mgrs/Xobc_skip_mgr.java @@ -13,3 +13,25 @@ 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.addons.bldrs.centrals.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.xowa.addons.bldrs.centrals.dbs.datas.imports.*; +public class Xobc_skip_mgr implements Gfo_invk { + private boolean category_enabled; + public Xobc_skip_mgr(Xobc_task_mgr task_mgr, Xoa_app app) { + app.Cfg().Bind_many_app(this, Cfg__namespaces_category); + } + public boolean Should_skip(Xobc_import_step_itm step_itm) { + if (category_enabled && step_itm.Import_type == Xobc_import_type.Tid__wiki__ctg) + return true; + else + return false; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Cfg__namespaces_category)) category_enabled = m.ReadBool("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Cfg__namespaces_category = "xowa.bldr.download_central.namespaces.category" + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/steps/Xobc_step_factory.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/steps/Xobc_step_factory.java index a27517de8..b5ad98fba 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/steps/Xobc_step_factory.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/steps/Xobc_step_factory.java @@ -13,3 +13,77 @@ 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.addons.bldrs.centrals.steps; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.brys.evals.*; import gplx.core.primitives.*; +import gplx.xowa.addons.bldrs.centrals.tasks.*; import gplx.xowa.addons.bldrs.centrals.cmds.*; import gplx.xowa.addons.bldrs.centrals.steps.*; import gplx.xowa.addons.bldrs.centrals.utils.*; +import gplx.xowa.addons.bldrs.centrals.dbs.*; import gplx.xowa.addons.bldrs.centrals.dbs.datas.*; import gplx.xowa.addons.bldrs.centrals.dbs.datas.imports.*; import gplx.xowa.addons.bldrs.centrals.hosts.*; +import gplx.xowa.addons.bldrs.exports.merges.*; +import gplx.xowa.addons.bldrs.updates.files.*; +import gplx.xowa.addons.bldrs.exports.packs.files.*; +import gplx.xowa.bldrs.*; +import gplx.xowa.wikis.domains.*; +public class Xobc_step_factory { + private final Xobc_task_mgr task_mgr; + private final Xobc_data_db data_db; + private final Bry_eval_mgr eval_mgr = Bry_eval_mgr.Dflt(); private final Host_eval_itm host_eval = new Host_eval_itm(); + private final Host_eval_wkr eval_wkr__host_regy = new Host_eval_wkr(); + public Xobc_step_factory(Xobc_task_mgr task_mgr, Xobc_data_db data_db, Io_url wiki_dir) { + this.task_mgr = task_mgr; + this.data_db = data_db; + eval_mgr.Add_many(new Bry_eval_wkr__builder_central(wiki_dir)); + } + public void Load(Xobc_task_itm task, int step_id, int cmd_idx) { + int step_type = data_db.Tbl__step_regy().Select_type(step_id); + switch (step_type) { + case Xobc_step_itm.Type__wiki_import: Load_wiki_import(task, step_id, cmd_idx); break; + default: throw Err_.new_unhandled_default(step_type); + } + } + private void Load_wiki_import(Xobc_task_itm task, int step_id, int cmd_idx) { + int step_seqn = data_db.Tbl__step_map().Select_one(task.Task_id(), step_id).Step_seqn; + Xobc_import_step_itm import_itm = data_db.Tbl__import_step().Select_one(step_id); String_obj_ref step_name = String_obj_ref.empty_(); + Xobc_cmd_itm[] cmds = Make_wiki_import_cmds(import_itm, task.Task_id(), step_id, step_name, step_seqn); + Xobc_step_itm step = new Xobc_step_itm(step_id, step_seqn, cmds).Cmd_idx_(cmd_idx); + step.Step_name_(String_.Format("{0} ·({1}/{2})", step_name.Val(), step_seqn + List_adp_.Base1, task.Step_count())); + task.Step_(step); + step.Cmd().Load_checkpoint(); + if (step.Cmd().Prog_status() == gplx.core.progs.Gfo_prog_ui_.Status__suspended) + task.Task_status_(step.Cmd().Prog_status()); + } + private Xobc_cmd_itm[] Make_wiki_import_cmds(Xobc_import_step_itm import_itm, int task_id, int step_id, String_obj_ref step_name, int step_seqn) { + List_adp list = List_adp_.New(); + Xow_domain_itm domain_itm = Xow_abrv_xo_.To_itm(import_itm.Wiki_abrv()); + String wiki_domain = domain_itm.Domain_str(); + String file_name = import_itm.Import_name; + step_name.Val_(file_name); + eval_wkr__host_regy.Domain_itm_(domain_itm); + String src_http_url = host_eval.Eval_src_fil(data_db, import_itm.Host_id, domain_itm, file_name); + Io_url zip_file_url = Eval_url(Bry_eval_wkr__builder_central.Make_str(Bry_eval_wkr__builder_central.Type__download_fil, wiki_domain, file_name)); + Io_url unzip_dir_url = Eval_url(Bry_eval_wkr__builder_central.Make_str(Bry_eval_wkr__builder_central.Type__unzip_dir, wiki_domain, file_name)); + Io_url wiki_dir_url = Eval_url(Bry_eval_wkr__builder_central.Make_str(Bry_eval_wkr__builder_central.Type__wiki_dir, wiki_domain, file_name)); + + // if lucene, move to /data/search/ + if (import_itm.Import_type == Xobc_import_type.Tid__wiki__lucene) { + wiki_dir_url = gplx.xowa.addons.wikis.fulltexts.Xosearch_fulltext_addon.Get_index_dir(wiki_dir_url); + } + + Io_url checksum_url = unzip_dir_url.GenSubFil(file_name + ".md5"); + int cmd_idx = 0; + list.Add(new Xobc_cmd__download (task_mgr, task_id, step_id, cmd_idx++, src_http_url, zip_file_url, import_itm.Import_size_zip)); + list.Add(new Xobc_cmd__verify_fil (task_mgr, task_id, step_id, cmd_idx++, zip_file_url, import_itm.Import_md5, import_itm.Import_size_zip)); + list.Add(new Xobc_cmd__unzip (task_mgr, task_id, step_id, cmd_idx++, zip_file_url, unzip_dir_url, import_itm.Import_size_raw)); + list.Add(new Xobc_cmd__verify_dir (task_mgr, task_id, step_id, cmd_idx++, checksum_url, zip_file_url)); + // list.Add(new Xobc_cmd__wiki_merge (task_mgr, task_id, step_id, cmd_idx++, merge_mgr, wiki_domain, unzip_dir_url, import_itm.Import_prog_data_max, import_itm.Import_prog_row_max, step_seqn)); + list.Add(new Xobc_cmd__move_fils (task_mgr, task_id, step_id, cmd_idx++, unzip_dir_url, wiki_dir_url)); + switch (import_itm.Import_type) { + case Xobc_import_type.Tid__wiki__core: list.Add(new Xobc_cmd__wiki_reg (task_mgr, task_id, step_id, cmd_idx++, wiki_dir_url, wiki_domain)); break; + case Xobc_import_type.Tid__fsdb__delete: list.Add(new Xobc_cmd__fsdb_delete (task_mgr, task_id, step_id, cmd_idx++, Pack_zip_name_bldr.To_wiki_url(wiki_dir_url, zip_file_url.OwnerDir()))); break; + } + return (Xobc_cmd_itm[])list.To_ary_and_clear(Xobc_cmd_itm.class); + } + private Io_url Eval_url(String src) {return Io_url_.new_any_(String_.new_u8(eval_mgr.Eval(Bry_.new_u8(src))));} + public static Xow_wiki Get_wiki_by_abrv(Xoa_app app, byte[] wiki_abrv) { + Xow_domain_itm domain_itm = Xow_abrv_xo_.To_itm(wiki_abrv); + return app.Wiki_mgri().Get_by_or_make_init_y(domain_itm.Domain_bry()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/steps/Xobc_step_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/steps/Xobc_step_itm.java index a27517de8..f7c426e66 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/steps/Xobc_step_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/steps/Xobc_step_itm.java @@ -13,3 +13,49 @@ 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.addons.bldrs.centrals.steps; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.gfobjs.*; +import gplx.xowa.addons.bldrs.centrals.cmds.*; +public class Xobc_step_itm { + private int cmd_idx = 0; + private final Xobc_cmd_itm[] cmds; + public Xobc_step_itm(int step_id, int step_seqn, Xobc_cmd_itm[] cmds) { + this.step_id = step_id; this.step_seqn = step_seqn; this.cmds = cmds; + } + public int Step_id() {return step_id;} private final int step_id; + public int Step_seqn() {return step_seqn;} private final int step_seqn; + public String Step_name() {return step_name;} private String step_name; + public Xobc_cmd_itm Cmd() {return cmds[cmd_idx];} + public Xobc_step_itm Cmd_idx_(int v) {cmd_idx = v; return this;} + public void Cmd_idx_next_() {++cmd_idx;} + public boolean Cmd_is_last() {return cmd_idx == cmds.length - 1;} + public Xobc_cmd_itm Step_fallback_to(String fallback_id) { + int fallback_idx = 0; + int len = cmds.length; + for (int i = 0; i < len; ++i) { + Xobc_cmd_itm cmd = cmds[i]; + if (String_.Eq(cmd.Cmd_type(), fallback_id)) { + fallback_idx = cmd.Cmd_id(); + break; + } + } + cmd_idx = fallback_idx; + return Cmd(); + } + public void Step_name_(String v) {this.step_name = v;} + public void Step_cleanup() { + int len = cmds.length; + for (int i = 0; i < len; ++i) { + cmds[i].Cmd_cleanup(); + } + } + public Gfobj_nde Save_to(Gfobj_nde nde) { + nde.Add_int ("step_id", step_id); + nde.Add_str ("step_name", step_name); + this.Cmd().Save_to(nde.New_nde("cmd")); + return nde; // fluent + } + + public static final int Type__wiki_import = 1; + public static final int Seqn__0 = 0; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_itm.java index a27517de8..4ad407d11 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_itm.java @@ -13,3 +13,39 @@ 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.addons.bldrs.centrals.tasks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.gfobjs.*; +import gplx.xowa.addons.bldrs.centrals.cmds.*; import gplx.xowa.addons.bldrs.centrals.steps.*; +public class Xobc_task_itm implements gplx.CompareAble { + private final int task_sort; + public Xobc_task_itm(int task_id, int task_seqn, int step_count, String task_key, String task_name) { + this.task_id = task_id; + this.task_seqn = task_seqn; + this.step_count = step_count; + this.task_key = task_key; + this.task_name = task_name; + this.task_sort = task_seqn; // set task_sort to task_seqn; note that task_sort needs to be memorialized b/c "work" changes task_seqn to add-order + } + public int Task_id() {return task_id;} private final int task_id; + public String Task_key() {return task_key;} private final String task_key; + public String Task_name() {return task_name;} private final String task_name; + public int Step_count() {return step_count;} private final int step_count; + public int Task_seqn() {return task_seqn;} private int task_seqn; + public byte Task_status() {return task_status;} private byte task_status; + public Xobc_step_itm Step() {return step;} public void Step_(Xobc_step_itm v) {this.step = v;} private Xobc_step_itm step; + public boolean Step_is_last() {return step.Step_seqn() == step_count - 1;} + public void Task_status_(byte v) {task_status = v;} // called when task moves from init -> working -> suspended -> done + public void Task_seqn_(int v) {this.task_seqn = v;} // called when task is init'd from db, or added to list + public int compareTo(Object obj) {Xobc_task_itm comp = (Xobc_task_itm)obj; return Int_.Compare(task_seqn, comp.task_seqn);} + + public Gfobj_nde Save_to(Gfobj_nde nde) { + nde.Add_int ("task_id" , task_id); + nde.Add_str ("task_name" , task_name); + nde.Add_byte("task_status" , task_status); + nde.Add_int ("task_sort" , task_sort); + if (step != null) { + step.Save_to(nde.New_nde("step")); + } + return nde; // FLUENT + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_key.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_key.java index a27517de8..460978e14 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_key.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_key.java @@ -13,3 +13,32 @@ 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.addons.bldrs.centrals.tasks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.xowa.wikis.domains.*; +public class Xobc_task_key { + public Xobc_task_key(String wiki_domain, String wiki_date, String task_type) { + this.wiki_domain = wiki_domain; + this.wiki_date = wiki_date; + this.task_type = task_type; + } + public String Wiki_domain() {return wiki_domain;} private final String wiki_domain; + public String Wiki_date() {return wiki_date;} private final String wiki_date; + public String Wiki_date_ui() {return String_.Replace(wiki_date, ".", "-");} + public String Task_type() {return task_type;} private final String task_type; + public String Task_type_ui() { + if (String_.Eq(task_type, "html")) return "Articles"; + else if (String_.Eq(task_type, "file")) return "Images"; + else if (String_.Eq(task_type, "text")) return "Source"; + else if (String_.Eq(task_type, "patch")) return "Patch"; + else return task_type; + } + public Xow_domain_itm Wiki_domain_itm() {return Xow_domain_itm_.parse(Bry_.new_u8(wiki_domain));} + + public static Xobc_task_key To_itm(String task_key) { + String[] ary = String_.Split(task_key, "|"); + return new Xobc_task_key(ary[0], ary[1], ary[2]); + } + public static String To_str(String wiki_domain, String wiki_date, String task_type) { + return String_.Concat(wiki_domain, "|", wiki_date, "|", task_type); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_regy__base.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_regy__base.java index a27517de8..097ac03ab 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_regy__base.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_regy__base.java @@ -13,3 +13,29 @@ 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.addons.bldrs.centrals.tasks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.gfobjs.*; +public abstract class Xobc_task_regy__base { + private final Ordered_hash hash = Ordered_hash_.New(); + protected final Xobc_task_mgr task_mgr; + public Xobc_task_regy__base(Xobc_task_mgr task_mgr, String name) {this.task_mgr = task_mgr; this.name = name;} + public String Name() {return name;} private final String name; + public int Len() {return hash.Len();} + public void Add(Xobc_task_itm t) {hash.Add(t.Task_id(), t);} + public void Clear() {hash.Clear();} + public Xobc_task_itm Get_at(int i) {return (Xobc_task_itm)hash.Get_at(i);} + public Xobc_task_itm Get_by(int i) {return (Xobc_task_itm)hash.Get_by(i);} + public void Del_by(int i) {hash.Del(i);} + public void Sort() {hash.Sort();} + + public void Save_to(Gfobj_ary ary) {Save_to(ary, (Xobc_task_itm[])hash.To_ary(Xobc_task_itm.class));} + public void Save_to(Gfobj_ary ary, Xobc_task_itm[] itms) { + int len = itms.length; + Gfobj_nde[] sub_ndes = new Gfobj_nde[len]; + for (int i = 0; i < len; ++i) { + Gfobj_nde sub_nde = sub_ndes[i] = Gfobj_nde.New(); + itms[i].Save_to(sub_nde); + } + ary.Ary_(sub_ndes); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_regy__done.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_regy__done.java index a27517de8..52d1dc998 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_regy__done.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_regy__done.java @@ -13,3 +13,11 @@ 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.addons.bldrs.centrals.tasks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +public class Xobc_task_regy__done extends Xobc_task_regy__base { + public Xobc_task_regy__done(Xobc_task_mgr task_mgr) {super(task_mgr, "done");} + public void Del_done(int task_id) { + task_mgr.User_db().Done_task_tbl().Delete(task_id); + task_mgr.Transfer(this, task_mgr.Todo_mgr(), this.Get_by(task_id)); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_regy__todo.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_regy__todo.java index a27517de8..01675aebc 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_regy__todo.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_regy__todo.java @@ -13,3 +13,34 @@ 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.addons.bldrs.centrals.tasks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.gfobjs.*; +import gplx.xowa.addons.bldrs.centrals.cmds.*; import gplx.xowa.addons.bldrs.centrals.steps.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; +public class Xobc_task_regy__todo extends Xobc_task_regy__base { + public Xobc_task_regy__todo(Xobc_task_mgr task_mgr) {super(task_mgr, "todo");} + public void Add_work(int task_id) { + Xobc_user_db user_db = task_mgr.User_db(); + Xobc_task_itm task = task_mgr.Todo_mgr().Get_by(task_id); + task.Task_status_(gplx.core.progs.Gfo_prog_ui_.Status__init); + task.Task_seqn_(task_mgr.Work_mgr().Len()); + + // get step_id; default is 1st step with seqn=0, but skip any steps that have already been downloaded; handles user accidentally removing item from work; + int step_seqn = Xobc_step_itm.Seqn__0; + Hash_adp done_steps = user_db.Done_step_tbl().Select_all(task_id); + if (done_steps != null) + step_seqn = task_mgr.Data_db().Tbl__step_map().Select_seqn_but_skip_done(task_id, done_steps); + int step_id = task_mgr.Data_db().Tbl__step_map().Select_step_id(task_id, step_seqn); + + // load task, cur_step, and all cmds + task_mgr.Step_mgr().Load(task, step_id, Xobc_cmd__base.Seqn__0); + + // do transfer + user_db.Work_task_tbl().Insert(task_id, task.Task_seqn(), step_id, Xobc_cmd__base.Seqn__0); + task_mgr.Transfer(this, task_mgr.Work_mgr(), task); + } + public void Del_todo(int task_id) { + Xobc_task_itm task = this.Get_by(task_id); + task_mgr.User_db().Done_task_tbl().Insert(task_id, task.Task_seqn()); + task_mgr.Transfer(this, task_mgr.Done_mgr(), task); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_regy__work.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_regy__work.java index a27517de8..94b53b2cd 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_regy__work.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/tasks/Xobc_task_regy__work.java @@ -13,3 +13,149 @@ 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.addons.bldrs.centrals.tasks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.gfobjs.*; import gplx.core.progs.*; import gplx.core.threads.*; +import gplx.xowa.drds.powers.*; +import gplx.xowa.addons.bldrs.centrals.steps.*; import gplx.xowa.addons.bldrs.centrals.cmds.*; +import gplx.xowa.addons.bldrs.centrals.dbs.datas.imports.*; +public class Xobc_task_regy__work extends Xobc_task_regy__base { + private final Thread_adp_mgr thread_mgr = new Thread_adp_mgr(1000, 5000); + private final Xobc_cmd_ctx ctx; + public Xobc_task_regy__work(Xobc_task_mgr task_mgr, Xoa_app app) {super(task_mgr, "work"); + this.ctx = new Xobc_cmd_ctx(app); + } + public void Del_work(int task_id) { + Xobc_task_itm task = this.Get_by(task_id); + if (task.Step() != null) task.Step().Step_cleanup(); + this.Del_task(task, task_mgr.Todo_mgr()); + } + private void Del_task(Xobc_task_itm task, Xobc_task_regy__base trg) { + task_mgr.User_db().Work_task_tbl().Delete(task.Task_id()); + task_mgr.Transfer(this, trg, task); + } + public void Run_next() { + Xod_power_mgr_.Instance.Wake_lock__get("task_mgr"); + int len = this.Len(); + for (int i = 0; i < len; ++i) { + Xobc_task_itm task = this.Get_at(i); + Xobc_cmd_itm cmd = task.Step().Cmd(); + if (gplx.core.bits.Bitmask_.Has_byte(Gfo_prog_ui_.Status__runnable, cmd.Prog_status())) { // must be runnable + Run_task(task, cmd); + break; + } + } + } + public void Run_task(Xobc_task_itm task, Xobc_cmd_itm cmd) { + // if task marked for skip, launch skip-cmd on separate thread and exit; + if (task_mgr.Skip_mgr().Should_skip(task_mgr.Data_db().Tbl__import_step().Select_one(cmd.Step_id()))) { + thread_mgr.Add("skip_" + cmd.Cmd_uid(), Thread_adp_.Start_by_key("skip_xobc: " + cmd.Cmd_name(), new Xobc_task_skip(this, cmd), "")); + return; + } + + task.Task_status_(gplx.core.progs.Gfo_prog_ui_.Status__working); + task_mgr.Send_json("xo.bldr.work.prog__start__recv", task.Save_to(Gfobj_nde.New())); + thread_mgr.Add(cmd.Cmd_uid(), Thread_adp_.Start_by_val("xobc: " + cmd.Cmd_name(), cmd, cmd, Xobc_cmd__base.Invk__exec, ctx)); + } + public void Stop_cur() { + Xod_power_mgr_.Instance.Wake_lock__rls("task_mgr"); + int len = this.Len(); + for (int i = 0; i < len; ++i) { + Xobc_task_itm task = this.Get_at(i); + Xobc_cmd_itm cmd = task.Step().Cmd(); + if (gplx.core.bits.Bitmask_.Has_byte(Gfo_prog_ui_.Status__working, cmd.Prog_status())) { // must be runnable + Stop_task(cmd.Cmd_uid()); + break; + } + } + } + public void Redo_cur() { + int len = this.Len(); + for (int i = 0; i < len; ++i) { + Xobc_task_itm task = this.Get_at(i); + Xobc_step_itm step = task.Step(); + Xobc_cmd_itm cmd = step.Cmd(); + if (gplx.core.bits.Bitmask_.Has_byte(Gfo_prog_ui_.Status__fail, cmd.Prog_status())) { // must be runnable + Redo_task(task, step, cmd); + break; + } + } + } + private void Stop_task(String cmd_uid) { + thread_mgr.Halt(cmd_uid, Thread_halt_cbk_.Noop); + } + private void Redo_task(Xobc_task_itm task, Xobc_step_itm step, Xobc_cmd_itm cmd) { + cmd = step.Step_fallback_to(cmd.Cmd_fallback()); + cmd.Cmd_clear(); + task.Task_status_(gplx.core.progs.Gfo_prog_ui_.Status__working); + task_mgr.Send_json("xo.bldr.work.prog__start__recv", task.Save_to(Gfobj_nde.New())); + thread_mgr.Add(cmd.Cmd_uid(), Thread_adp_.Start_by_val("xobc: " + cmd.Cmd_name(), cmd, cmd, Xobc_cmd__base.Invk__exec, ctx)); + } + public void On_done(Xobc_cmd_itm cmd, boolean notify) { + Xobc_task_itm task = this.Get_by(cmd.Task_id()); + Xobc_step_itm step = task.Step(); + boolean step_is_done = step.Cmd_is_last(); + boolean task_is_done = step_is_done && task.Step_is_last(); + if (notify) + task_mgr.Send_json("xo.bldr.work.prog__done__recv", Gfobj_nde.New().Add_int("task_id", task.Task_id()).Add_bool("task_is_done", task_is_done)); + + // step.done -> task.done || step.next + if (step_is_done) { + step.Step_cleanup(); + task_mgr.User_db().Done_step_tbl().Insert(cmd.Task_id(), cmd.Step_id()); + // task.done + if (task_is_done) { + task.Task_status_(Gfo_prog_ui_.Status__done); + task_mgr.User_db().Done_task_tbl().Insert(cmd.Task_id(), task.Task_seqn()); // NOTE: this will order by todo's sort, not by actually completed sorted + Del_task(task, task_mgr.Done_mgr()); + } + // step.next + else { + int next_step = task_mgr.Data_db().Tbl__step_map().Select_step_id(task.Task_id(), step.Step_seqn() + 1); + task_mgr.Step_mgr().Load(task, next_step, Xobc_cmd__base.Seqn__0); + } + } + // step.work -> cmd.next + else { + step.Cmd_idx_next_(); + } + + // release wake_lock; will be acquired when task is run_next; DATE:2016-06-29 + Xod_power_mgr_.Instance.Wake_lock__rls("task_mgr"); + + // task_regy.done + if (task_is_done) { + if (this.Len() > 0) + this.Run_next(); + } + // task_regy.work + else { + cmd = task.Step().Cmd(); + task_mgr.User_db().Work_task_tbl().Update(cmd.Task_id(), task.Task_seqn(), cmd.Step_id(), cmd.Cmd_id()); + this.Run_next(); + } + } + public void On_suspended(Xobc_cmd_itm cmd) { + Xobc_task_itm task = this.Get_by(cmd.Task_id()); + task.Task_status_(Gfo_prog_ui_.Status__suspended); + task_mgr.Send_json("xo.bldr.work.stop_cur__recv", Gfobj_nde.New().Add_int("task_id", task.Task_id())); + } + public void On_fail(Xobc_cmd_itm task, boolean resume, String msg) { + Xod_power_mgr_.Instance.Wake_lock__rls("task_mgr"); + task_mgr.Send_json("xo.bldr.work.prog__fail__recv", Gfobj_nde.New().Add_int("task_id", task.Task_id()).Add_str("err", msg).Add_bool("resume", resume)); + } + public void On_stat(int task_id, String msg) { + task_mgr.Send_json("xo.bldr.work.prog__stat__recv", Gfobj_nde.New().Add_int("task_id", task_id).Add_str("msg", msg)); + } +} +class Xobc_task_skip implements Gfo_invk { + private final Xobc_task_regy__work work_regy; + private final Xobc_cmd_itm cmd; + public Xobc_task_skip(Xobc_task_regy__work work_regy, Xobc_cmd_itm cmd) { + this.work_regy = work_regy; + this.cmd = cmd; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + work_regy.On_done(cmd, false); + return Gfo_invk_.Rv_handled; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/utils/Bry_eval_wkr__builder_central.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/utils/Bry_eval_wkr__builder_central.java index a27517de8..bbd5b00fd 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/utils/Bry_eval_wkr__builder_central.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/utils/Bry_eval_wkr__builder_central.java @@ -13,3 +13,53 @@ 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.addons.bldrs.centrals.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import gplx.core.brys.evals.*; +public class Bry_eval_wkr__builder_central implements Bry_eval_wkr { + private final byte[] wiki_dir; + public Bry_eval_wkr__builder_central(Io_url wiki_dir) {this.wiki_dir = wiki_dir.RawBry();} + public String Key() {return "builder_central";} + public void Resolve(Bry_bfr rv, byte[] src, int args_bgn, int args_end) { + // EX: "~{builder_central|download_fil|en.wikipedia.org|Xowa_enwiki_2016-05_html_ns.000_db.001.zip}" -> "/xowa/wiki/en.wikipedia.org/tmp/bldr/Xowa_enwiki_2016-05_html_ns.000_db.001.zip/download.zip" + byte[][] args = Bry_split_.Split(src, args_bgn, args_end, Byte_ascii.Pipe, Bool_.N); + int type = hash.Get_as_byte_or(args[0], Byte_.Max_value_127); + if (type == Byte_.Max_value_127) throw Err_.new_wo_type("unknown eval type", "src", src); + byte dir_spr = gplx.core.envs.Op_sys.Cur().Fsys_dir_spr_byte(); + rv.Add(wiki_dir); // "/xowa/wiki/" + rv.Add(args[1]).Add_byte(dir_spr); // "en.wikipedia.org/" + if (type == Type__wiki_dir) return; + rv.Add_str_a7("tmp").Add_byte(dir_spr); // "tmp/" + rv.Add_str_a7("bldr").Add_byte(dir_spr); // "bldr/" + rv.Add(args[2]).Add_byte(dir_spr); // "Xowa_enwiki_2016-05_html_ns.000_db.001.zip/" + switch (type) { + case Type__download_fil: rv.Add_str_a7("download.zip"); break; + case Type__unzip_dir: rv.Add_str_a7("unzip").Add_byte(dir_spr); break; + default: throw Err_.new_unhandled_default(type); + } + } + + public static final byte Type__download_fil = 0, Type__unzip_dir = 1, Type__wiki_dir = 2; + private static final Hash_adp_bry hash = Hash_adp_bry.cs() + .Add_str_byte("download_fil", Type__download_fil) + .Add_str_byte("unzip_dir", Type__unzip_dir) + .Add_str_byte("wiki_dir", Type__wiki_dir) + ; + public static String Make_str(byte type, String domain, String file_name) { + String type_name = null; + switch (type) { + case Type__download_fil: type_name = "download_fil"; break; + case Type__unzip_dir: type_name = "unzip_dir"; break; + case Type__wiki_dir: type_name = "wiki_dir"; break; + default: throw Err_.new_unhandled_default(type); + } + return "~{" + String_.Format("builder_central|{0}|{1}|{2}", type_name, domain, file_name) + "}"; + } + public static String Make_str(byte type, String domain) { + String type_name = null; + switch (type) { + case Type__wiki_dir: type_name = "wiki_dir"; break; + default: throw Err_.new_unhandled_default(type); + } + return "~{" + String_.Format("builder_central|{0}|{1}", type_name, domain) + "}"; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/utils/Time_dhms_.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/utils/Time_dhms_.java index a27517de8..bd521ded3 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/utils/Time_dhms_.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/utils/Time_dhms_.java @@ -13,3 +13,40 @@ 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.addons.bldrs.centrals.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +public class Time_dhms_ { + public static String To_str(Bry_bfr bfr, long val, boolean show_unit, int max_places) {To_bfr(bfr, val, show_unit, max_places); return bfr.To_str_and_clear();} + public static void To_bfr(Bry_bfr bfr, long val, boolean show_unit, int max_places) { + byte suffix = Byte_ascii.Null; + int count = 0; + for (int i = 0; i < 4; ++i) { + // get factor and unit + long factor = 0; + byte unit = Byte_ascii.Null; + switch(i) { + case 0: unit = Byte_ascii.Ltr_d; factor = 86400; break; + case 1: unit = Byte_ascii.Ltr_h; factor = 3600; break; + case 2: unit = Byte_ascii.Ltr_m; factor = 60; break; + case 3: unit = Byte_ascii.Ltr_s; factor = 1; break; + } + // calc cur and update val; EX: 3690 -> cur:1,mod:90 + int cur = (int)(val / factor); + val %= factor; + if (count == 0) { // no str yet + if ( cur == 0 // cur is 0; EX: 3690 and factor = 86400 -> 0 days; don't write anything + && i != 3) // unless it is the seconds place; need to handle "0 s" specifically + continue; + suffix = unit; // set suffix + bfr.Add_int_variable(cur); // write cur; note that this is not zero-padded + } + else { // str exists + bfr.Add_int_pad_bgn(Byte_ascii.Num_0, 2, cur); // write cur; note that this zero-padded; also, note will write "00" if cur == 0 + } + if (++count == max_places) break; // stop if max-places reached; EX: 86400 should write 1:00, not 1:00:00:00 + if (i != 3) // do not add ":" if seconds + bfr.Add_byte_colon(); + } + if (show_unit) // add units; EX: " s" for seconds + bfr.Add_byte_space().Add_byte(suffix); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/utils/Time_dhms__tst.java b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/utils/Time_dhms__tst.java index a27517de8..796f0da71 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/centrals/utils/Time_dhms__tst.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/centrals/utils/Time_dhms__tst.java @@ -13,3 +13,23 @@ 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.addons.bldrs.centrals.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.centrals.*; +import org.junit.*; import gplx.core.tests.*; +public class Time_dhms__tst { + private final Time_dhms__fxt fxt = new Time_dhms__fxt(); + @Test public void S__0() {fxt.Test__to_str( 0, 4, "0 s");} + @Test public void S__1() {fxt.Test__to_str( 1, 4, "1 s");} + @Test public void S__2() {fxt.Test__to_str( 30, 4, "30 s");} + @Test public void M__1() {fxt.Test__to_str( 60, 4, "1:00 m");} + @Test public void M__2() {fxt.Test__to_str( 600, 4, "10:00 m");} + @Test public void H__1() {fxt.Test__to_str( 3600, 4, "1:00:00 h");} + @Test public void H__2() {fxt.Test__to_str( 5025, 4, "1:23:45 h");} + @Test public void D__1() {fxt.Test__to_str( 86400, 4, "1:00:00:00 d");} + @Test public void Max_places() {fxt.Test__to_str( 86400, 2, "1:00 d");} +} +class Time_dhms__fxt { + private final Bry_bfr bfr = Bry_bfr_.New(); + public Time_dhms__fxt Test__to_str(long v, int max_places, String expd) { + Gftest.Eq__str(expd, Time_dhms_.To_str(bfr, v, Bool_.Y, max_places)); + return this;} +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/Export_addon.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/Export_addon.java index a27517de8..d4435b89e 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/Export_addon.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/Export_addon.java @@ -13,3 +13,17 @@ 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.addons.bldrs.exports; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.xowa.bldrs.wkrs.*; +public class Export_addon implements Xoax_addon_itm, Xoax_addon_itm__bldr { + public Xob_cmd[] Bldr_cmds() { + return new Xob_cmd[] + { gplx.xowa.addons.bldrs.exports.splits.Split_bldr_cmd.Prototype + , gplx.xowa.addons.bldrs.exports.merges.Merge_bldr_cmd.Prototype + , gplx.xowa.addons.bldrs.exports.packs.splits.Pack_split_bldr_cmd.Prototype + , gplx.xowa.addons.bldrs.exports.packs.files.Pack_file_bldr_cmd.Prototype + }; + } + + public String Addon__key() {return "xowa.builds.repacks";} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/Xow_db_file_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/Xow_db_file_mgr.java index a27517de8..58dc5e256 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/Xow_db_file_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/Xow_db_file_mgr.java @@ -13,3 +13,19 @@ 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.addons.bldrs.exports; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +public class Xow_db_file_mgr { + private final Ordered_hash hash = Ordered_hash_.New(); + public int Len() {return hash.Len();} + public Xow_db_file Get_at(int i) {return (Xow_db_file)hash.Get_at(i);} + public Xow_db_file_mgr Load_by_type(Xow_db_mgr db_mgr, int type) { + int len = db_mgr.Dbs__len(); + for (int i = 0; i < len; ++i) { + Xow_db_file db_file = db_mgr.Dbs__get_at(i); + if (db_file.Tid() != type) continue; + hash.Add(db_file.Url().Raw(), db_file); + } + return this; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_copy_utl.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_copy_utl.java index a27517de8..944a661aa 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_copy_utl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_copy_utl.java @@ -13,3 +13,98 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; +public class Merge2_copy_utl { + private final Bry_bfr bfr = Bry_bfr_.New(); + private final String tbl_name, fld_pkey, fld_blob; + private final boolean tbl_has_blob; + private Db_conn conn; + public Merge2_copy_utl(String tbl_name, String fld_pkey, String fld_blob, boolean tbl_has_blob) { + this.tbl_name = tbl_name; + this.fld_pkey = fld_pkey; + this.fld_blob = fld_blob; + this.tbl_has_blob = tbl_has_blob; + } + public void Init_conn(Db_conn conn) { + this.conn = conn; + } + public byte[] Select_blob(Db_rdr data_rdr, String fld_blob_pkey) { // NOTE: need to SELECT entire BLOB; cannot UPDATE in 1 MB increments b/c SQLite does not really concat BLOBS; REF.SQLITE: http://sqlite.1065341.n5.nabble.com/Append-data-to-a-BLOB-field-td46003.html + int id = data_rdr.Read_int(fld_blob_pkey); + int len_max = data_rdr.Read_int("blob_len"); + int len_cur = 1, len_gap = 1000000; + while (len_cur < len_max) { + // determine substr args; EX: Substr(blob_col, 1, 1000000); Substr(blob_col, 1000001, 1000000); Substr(blob_col, 2000001, 1234); + int len_new = len_cur + len_gap; + if (len_new >= len_max) len_gap = len_max - len_cur; // last SELECT; Substr remainder, not full 1 MB + + // read data; note that stmt needs to be new'd for each loop b/c SQL is different + Db_stmt stmt = conn.Stmt_sql(String_.Format(String_.Concat_lines_nl_skip_last // ANSI.N; NOTE: will only run on Android SQLite + ( "SELECT Substr({0}, {1}, {2}) AS blob_data" + , "FROM {3}" + , "WHERE {4} = {5}" + ), fld_blob, len_cur, len_gap, tbl_name, fld_pkey, id)); + Db_rdr rdr = stmt.Exec_select__rls_auto(); + try { + if (rdr.Move_next()) + bfr.Add(rdr.Read_bry("blob_data")); + else + throw Err_.new_wo_type("failed to read blob in increments", "id", id, "len_cur", len_cur); + } + finally { + rdr.Rls(); + stmt.Rls(); + } + + len_cur = len_new; + } + return bfr.To_bry_and_clear(); + } + public String Bld_sql(Dbmeta_fld_list flds, int flds_end, boolean src_is_pack, byte mode, int resume__db_id) { + bfr.Add_str_a7("SELECT"); + for (int i = 0; i < flds_end; ++i) { + bfr.Add_str_a7(i == 0 ? " " : ", "); + bfr.Add_str_u8(flds.Get_at(i).Name()); + } + bfr.Add_str_u8(Bld_select_fld(mode)); + bfr.Add_str_a7("\nFROM ").Add_str_u8(tbl_name); + if (!src_is_pack) { + } + else { + bfr.Add_str_a7("\nWHERE "); + if (src_is_pack) { + bfr.Add_str_a7("trg_db_id >= "); + bfr.Add_int_variable(resume__db_id); + if (mode != Merge2_copy_utl.Mode__all) + bfr.Add_str_a7("\nAND "); + } + bfr.Add_str_a7(Bld_where(mode)); + } + if (!src_is_pack) { + bfr.Add_str_a7("\nORDER BY "); + bfr.Add_str_u8(fld_pkey); + } + else { + bfr.Add_str_a7("\nORDER BY trg_db_id, "); + bfr.Add_str_u8(fld_pkey); + } + return bfr.To_str_and_clear(); + } + public String Bld_select_fld(byte mode) { + switch (mode) { + case Merge2_copy_utl.Mode__all: + case Merge2_copy_utl.Mode__drd__small: return tbl_has_blob ? ", " + fld_blob : ""; + case Merge2_copy_utl.Mode__drd__large: return ", blob_len"; + default: throw Err_.new_unhandled_default(mode); + } + } + public String Bld_where(byte mode) { + switch (mode) { + case Merge2_copy_utl.Mode__all: return ""; + case Merge2_copy_utl.Mode__drd__small: return "blob_len <= 1000000"; + case Merge2_copy_utl.Mode__drd__large: return "blob_len > 1000000"; + default: throw Err_.new_unhandled_default(mode); + } + } + public static final byte Mode__all = 0, Mode__drd__small = 1, Mode__drd__large = 2; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_copy_wkr__lot.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_copy_wkr__lot.java index a27517de8..b4938a0bc 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_copy_wkr__lot.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_copy_wkr__lot.java @@ -13,3 +13,131 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.exports.utls.*; +class Merge2_copy_wkr__lot { + private final Split_tbl tbl; + private final String tbl_name, fld_blob_pkey, fld_blob_data; + private final Merge2_copy_utl copy_utl; + private int resume__db_id; + private boolean tbl_has_blob; + public Merge2_copy_wkr__lot(Split_tbl tbl) { + this.tbl = tbl; + this.tbl_name = tbl.Tbl_name(); + this.fld_blob_pkey = tbl.Fld_pkeys()[0]; + this.fld_blob_data = tbl.Fld_blob(); + this.tbl_has_blob = Split_tbl_.Tbl_has_blob(tbl); + this.copy_utl = new Merge2_copy_utl(tbl.Tbl_name(), fld_blob_pkey, fld_blob_data, tbl_has_blob); + } + public void Copy_by_sql(String msg, Merge_ctx ctx, Merge_prog_wkr prog_wkr, Db_conn src_conn, Merge2_trg_itm trg_db, Merge2_trg_mgr trg_mgr, boolean src_is_pack) { + Xow_wiki wiki = ctx.Wiki(); + if (src_is_pack) { + int[] trg_db_idxs = Get_trg_dbs(src_conn, tbl_name); + int trg_db_idxs_len = trg_db_idxs.length; + for (int i = 0; i < trg_db_idxs_len; ++i) { + int trg_db_id = trg_db_idxs[i]; + Merge2_trg_itm trg_db_new = trg_mgr.Cur_or_new(ctx, prog_wkr, wiki, trg_db_id); + Copy_by_trg_conn(msg, prog_wkr, src_conn, trg_db_new, trg_db_id, src_is_pack); + } + } + else { + Copy_by_trg_conn(msg, prog_wkr, src_conn, trg_db, trg_db.Idx(), src_is_pack); + } + } + private void Copy_by_trg_conn(String msg, Merge_prog_wkr prog_wkr, Db_conn src_conn, Merge2_trg_itm trg_db, int trg_db_id, boolean src_is_pack) { + Dbmeta_fld_list src_flds = tbl.Flds().Clone(); // src_flds.Insert(0, Dbmeta_fld_itm.new_int("trg_db_id")); + Merge_wkr_utl.Merge_by_sql(prog_wkr, msg, tbl.Tbl_name(), src_flds, src_conn, trg_db, trg_db_id, !src_is_pack); + } + private int[] Get_trg_dbs(Db_conn conn, String tbl_name) { + List_adp list = List_adp_.New(); + Db_rdr rdr = conn.Stmt_sql(String_.Format("SELECT trg_db_id FROM {0} GROUP BY trg_db_id", tbl_name)).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + list.Add(rdr.Read_int("trg_db_id")); + } + } + finally { + rdr.Rls(); + } + return (int[])list.To_ary_and_clear(int.class); + } + public void Copy_entire_src(Merge_ctx ctx, Merge_prog_wkr prog_wkr, Db_conn src_conn, Merge2_trg_itm trg_db, Merge2_trg_mgr trg_mgr, boolean src_is_pack) { + Xow_wiki wiki = ctx.Wiki(); + + if (gplx.core.envs.Op_sys.Cur().Tid_is_drd() && tbl_has_blob) { + Copy_rows(ctx, prog_wkr, wiki, tbl, src_conn, trg_db, trg_mgr, src_is_pack, Merge2_copy_utl.Mode__drd__small); + Copy_rows(ctx, prog_wkr, wiki, tbl, src_conn, trg_db, trg_mgr, src_is_pack, Merge2_copy_utl.Mode__drd__large); + } + else + Copy_rows(ctx, prog_wkr, wiki, tbl, src_conn, trg_db, trg_mgr, src_is_pack, Merge2_copy_utl.Mode__all); + } + private void Copy_rows(Merge_ctx ctx, Merge_prog_wkr prog_wkr, Xow_wiki wiki, Split_tbl tbl, Db_conn src_conn, Merge2_trg_itm trg_db, Merge2_trg_mgr trg_mgr, boolean src_is_pack, byte select_mode) { + // init src + Dbmeta_fld_list src_flds = tbl.Flds().Clone(); + if (src_is_pack) + src_flds.Insert(0, Dbmeta_fld_itm.new_int("trg_db_id")); + int flds_nth = src_flds.Len(); + if (tbl_has_blob) { + Split_tbl_.Flds__add_blob_len(src_flds); + flds_nth = src_flds.Len() - 1; // ignore blob which is last fld by convention + } + String src_sql = copy_utl.Bld_sql(src_flds, flds_nth, src_is_pack, select_mode, resume__db_id); + Db_rdr src_rdr = src_conn.Stmt_sql(src_sql).Exec_select__rls_auto(); + copy_utl.Init_conn(src_conn); + + Dbmeta_fld_list trg_flds = tbl.Flds().Clone(); + if (tbl_has_blob && src_is_pack) { + Split_tbl_.Flds__add_blob_len(trg_flds); + } + + Db_stmt trg_stmt = null; Db_conn trg_conn = null; int cur_trg = -1; + if (trg_db != null) { + trg_conn = trg_db.Conn(); + trg_conn.Txn_bgn("merge_" + tbl_name); + trg_stmt = trg_conn.Stmt_insert(tbl_name, trg_flds); + cur_trg = trg_db.Idx(); + } + try { + while (src_rdr.Move_next()) { + // switch trg_db if null, or diff than cur; note that trg_stmt also changes + if (src_is_pack) { + int trg_db_id = src_rdr.Read_int("trg_db_id"); + if (cur_trg != trg_db_id) { + if (trg_conn != null) trg_conn.Txn_end(); + trg_conn = trg_mgr.Cur_or_new(ctx, prog_wkr, wiki, trg_db_id).Conn(); + trg_conn.Txn_bgn("merge_" + tbl_name); + trg_stmt = trg_conn.Stmt_insert(tbl_name, trg_flds); + cur_trg = trg_db_id; + resume__db_id = trg_db_id; + prog_wkr.Checkpoint__save(); + } + } + + // read and insert + trg_stmt.Clear(); + gplx.dbs.diffs.Gfdb_rdr_utl_.Stmt_args(trg_stmt, trg_flds, 0, trg_flds.Len() - (tbl_has_blob ? 1 : 0), src_rdr); + if (tbl_has_blob) { + byte[] val_blob = select_mode == Merge2_copy_utl.Mode__drd__large + ? copy_utl.Select_blob(src_rdr, fld_blob_pkey) + : src_rdr.Read_bry(fld_blob_data); + trg_stmt.Val_bry(fld_blob_data , val_blob); + } + trg_stmt.Exec_insert(); + + if (prog_wkr.Prog__insert_and_stop_if_suspended(0)) break; + } + } + finally { + src_rdr.Rls(); + if (trg_conn != null) { + trg_conn.Txn_end(); + } + trg_stmt = Db_stmt_.Rls(trg_stmt); + } + } +} +/* +2 situations: +- pack (trg_db,blob_len,*) -> heap (blob_len,*) +- heap (blob_len,*) -> wiki (*) +*/ diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_heap_db.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_heap_db.java index a27517de8..7b3fd941b 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_heap_db.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_heap_db.java @@ -13,3 +13,16 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.exports.utls.*; +public class Merge2_heap_db implements Merge2_trg_itm { + public Merge2_heap_db(Split_tbl tbl, Dbmeta_fld_list flds, int idx, Io_url url, Db_conn conn) { + this.tbl = tbl; this.flds = flds; this.idx = idx; + this.url = url; this.conn = conn; + } + public Split_tbl Tbl() {return tbl;} private final Split_tbl tbl; + public Dbmeta_fld_list Flds() {return flds;} private final Dbmeta_fld_list flds; + public int Idx() {return idx;} private final int idx; + public Io_url Url() {return url;} private final Io_url url; + public Db_conn Conn() {return conn;} private final Db_conn conn; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_heap_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_heap_mgr.java index a27517de8..1439d6042 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_heap_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_heap_mgr.java @@ -13,3 +13,99 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.exports.utls.*; +public class Merge2_heap_mgr {// data tbls will only have 1 itms; blob tbls will have N itms + private final Split_tbl tbl; + private final List_adp list = List_adp_.New(), deleted = List_adp_.New(); + private Dbmeta_fld_list heap_flds; + public Merge2_heap_mgr(Split_tbl tbl) {this.tbl = tbl;} + public Merge2_heap_db Cur() {return cur;} private Merge2_heap_db cur; + public Merge2_heap_db Make_itm(Xow_wiki wiki, int trg_db_id) { + // clone tbl_flds; disable primary key + this.heap_flds = tbl.Flds().Clone(); + int len = heap_flds.Len(); + for (int i = 0; i < len; ++i) { + Dbmeta_fld_itm itm = heap_flds.Get_at(i); + if (itm.Primary()) itm.Primary_n_(); + } + + // if blob, add "blob_len" in penultimate pos; note that last fld is "blob_fld" +// if (Split_tbl_.Tbl_has_blob(tbl)) +// Split_tbl_.Flds__add_blob_len(heap_flds); + + // init heap_conn + String tbl_name = tbl.Tbl_name(); + Io_url heap_dir = wiki.Fsys_mgr().Root_dir().GenSubDir_nest("tmp", "merge", tbl_name); + Io_url heap_url = heap_dir.GenSubFil(String_.Format("xowa.merge.temp.{0}{1}.sqlite3", tbl_name, trg_db_id == -1 ? "" : "." + Int_.To_str_pad_bgn_zero(trg_db_id, 4))); + Db_conn_bldr_data bldr_data = Db_conn_bldr.Instance.Get_or_new(heap_url); + Db_conn heap_conn = bldr_data.Conn(); + if (bldr_data.Created()) + heap_conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, heap_flds)); + + // make itm and return it + Merge2_heap_db rv = new Merge2_heap_db(tbl, heap_flds, trg_db_id, heap_url, heap_conn); + this.Add(rv); + return rv; + } + public void Copy_to_wiki(Merge_ctx ctx, Merge_prog_wkr prog_wkr, Merge2_wkr__heap_base wkr) { + // do copy for all itms; only 1 itm for page, fsdb_fil, etc..; but many for html, fsdb_bin + int len = list.Len(); + for (int i = 0; i < len; ++i) + Copy_to_wiki__itm(ctx, prog_wkr, wkr, this.Get_at(i), i == len - 1); + + // delete any old dbs + len = deleted.Len(); + for (int i = 0; i < len; ++i) { + Merge2_heap_db itm = (Merge2_heap_db)deleted.Get_at(i); + list.Del(itm); + } + deleted.Clear(); + + // if last idx, cleanup + if (ctx.Heap__last_idx()) this.Cleanup(); + } + public void Cleanup() { + int len = list.Len(); + for (int i = 0; i < len; ++i) + Cleanup_file(this.Get_at(i)); + } + private Merge2_heap_db Get_at(int i) {return (Merge2_heap_db)list.Get_at(i);} + private void Add(Merge2_heap_db itm) { + list.Add(itm); + cur = itm; + } + private void Copy_to_wiki__itm(Merge_ctx ctx, Merge_prog_wkr prog_wkr, Merge2_wkr__heap_base wkr, Merge2_heap_db itm, boolean itm_is_cur) { + // init + Split_tbl tbl = wkr.Tbl(); + String tbl_name = tbl.Tbl_name(); + Db_conn heap_conn = itm.Conn(); + Dbmeta_fld_list tbl_flds = tbl.Flds(); + Db_conn wiki_conn = tbl.Wiki_conn__get_or_new(ctx.Wiki(), itm.Idx()); + + // index heap table + // String[] pkey_flds = tbl.Fld_pkeys(); + // heap_conn.Meta_idx_create(tbl_name, "sort", pkey_flds); + + // do bulk copy into wiki tbls; note ORDER BY + wkr.Copy_to_wiki(ctx, prog_wkr, tbl_name, tbl_flds, heap_conn, new Merge2_trg_itm__wiki(itm.Idx(), wiki_conn), String_.Ary_empty); + + // cleanup + if (itm_is_cur) { // if cur, rebuild heap table; vaccum; + // heap_conn.Meta_tbl_remake(Dbmeta_tbl_itm.New(tbl_name, heap_flds)); + // heap_conn.Env_vacuum(); + heap_conn.Rls_conn(); + Io_mgr.Instance.DeleteFil(itm.Url()); // SQLite: file delete is faster than DROP TABLE or DELETE FROM + heap_conn.Reopen_conn(); + heap_conn.Meta_tbl_remake(Dbmeta_tbl_itm.New(tbl_name, heap_flds)); + } + else { // else, delete file; EX: fsdb_bin has 2 heap files; delete 1st, but still keep 2nd + Cleanup_file(itm); + deleted.Add(itm); + } + } + private void Cleanup_file(Merge2_heap_db itm) { + itm.Conn().Rls_conn(); + Io_mgr.Instance.DeleteFil(itm.Url()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_mgr.java index a27517de8..e95288715 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_mgr.java @@ -13,3 +13,57 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.exports.utls.*; +public class Merge2_mgr { + public final Merge_ctx ctx; + private final Ordered_hash wkr_hash; + + public Merge2_mgr() { + this.ctx = new Merge_ctx(); + this.wkr_hash = Make_wkrs + ( new Merge2_wkr__heap_one(Split_tbl_.Page) + , new Merge2_wkr__heap_lot(Split_tbl_.Html) + , new Merge2_wkr__heap_one(Split_tbl_.Srch_word) + , new Merge2_wkr__heap_lot(Split_tbl_.Srch_link) + , new Merge2_wkr__heap_one(Split_tbl_.Fsdb_fil) + , new Merge2_wkr__heap_one(Split_tbl_.Fsdb_thm) + , new Merge2_wkr__heap_one(Split_tbl_.Fsdb_org) + , new Merge2_wkr__heap_lot(Split_tbl_.Fsdb_bin) + ); + } + public void Prog_wkr_(Merge_prog_wkr prog_wkr) {this.prog_wkr = prog_wkr;} private Merge_prog_wkr prog_wkr; + public void Merge_core(Xow_wiki wiki, Io_url src_url) { + Db_conn src_conn = Db_conn_bldr.Instance.Get_or_autocreate(false, src_url); + new Merge_wkr__core().Copy_to_temp(null, wiki, src_conn); + Gfo_invk_.Invk_by_val(wiki.App().Wiki_mgri(), gplx.xowa.wikis.Xoa_wiki_mgr_.Invk__import_by_url, src_url); + src_conn.Rls_conn(); // NOTE: must close conn else pack_conn will stay open + // fails b/c no Main_Page; Gfo_invk_.Invk_by_msg(wiki.App().Gui__tab_mgr(), gplx.xowa.guis.tabs.Xog_tab_mgr_.Invk__new_tab, GfoMsg_.new_cast_("").Add("focus", true).Add("site", wiki.Domain_str()).Add("page", String_.new_u8(wiki.Props().Main_page()))); + } + public void Merge_data(Xow_wiki wiki, Io_url src_url, int idx_cur) { + long all_time_bgn = gplx.core.envs.System_.Ticks(); + wiki.Init_by_wiki(); + Db_conn src_conn = Db_conn_bldr.Instance.Get_or_autocreate(false, src_url); + ctx.Init(wiki, src_conn); + + // merge data + int hash_len = wkr_hash.Len(); + for (int i = 0; i < hash_len; ++i) { + if (prog_wkr.Canceled()) break; + Merge2_wkr wkr = (Merge2_wkr)wkr_hash.Get_at(i); + // if (prog_wkr.Checkpoint__skip_wkr(src_url, wkr.Tbl_name())) continue; + long wkr_time_bgn = gplx.core.envs.System_.Ticks(); + wkr.Merge_data(ctx, prog_wkr); + Gfo_log_.Instance.Info("merge.wkr.done", "data", src_url.NameAndExt() + "|" + wkr.Tbl().Tbl_name() + "|" + gplx.core.envs.System_.Ticks__elapsed_in_frac(wkr_time_bgn)); + } + if (ctx.Heap__copy_to_wiki()) ctx.Heap__increment_nxt(); + Gfo_log_.Instance.Info("merge.wkr.done", "data", src_url.NameAndExt() + "|-1|" + gplx.core.envs.System_.Ticks__elapsed_in_frac(all_time_bgn)); + src_conn.Rls_conn(); // NOTE: must close conn else pack_conn will stay open + } + private static Ordered_hash Make_wkrs(Merge2_wkr... wkrs) { + Ordered_hash rv = Ordered_hash_.New(); + for (Merge2_wkr wkr : wkrs) + rv.Add(wkr.Tbl().Tbl_name(), wkr); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_trg_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_trg_itm.java index a27517de8..b5b6df585 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_trg_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_trg_itm.java @@ -13,3 +13,14 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; +public interface Merge2_trg_itm { + int Idx(); + Db_conn Conn(); +} +class Merge2_trg_itm__wiki implements Merge2_trg_itm { + public Merge2_trg_itm__wiki(int idx, Db_conn conn) {this.idx = idx; this.conn = conn;} + public int Idx() {return idx;} private final int idx; + public Db_conn Conn() {return conn;} private final Db_conn conn; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_trg_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_trg_mgr.java index a27517de8..02d936517 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_trg_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_trg_mgr.java @@ -13,3 +13,33 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.exports.utls.*; +interface Merge2_trg_mgr { + Merge2_trg_itm Cur(); + Merge2_trg_itm Cur_or_new(Merge_ctx ctx, Merge_prog_wkr prog_wkr, Xow_wiki wiki, int trg_db_id); +} +class Merge2_trg_mgr__heap implements Merge2_trg_mgr { + private final Merge2_heap_mgr heap_mgr; + public Merge2_trg_mgr__heap(Merge2_heap_mgr heap_mgr) {this.heap_mgr = heap_mgr;} + public Merge2_trg_itm Cur() {return heap_mgr.Cur();} + public Merge2_trg_itm Cur_or_new(Merge_ctx ctx, Merge_prog_wkr prog_wkr, Xow_wiki wiki, int trg_db_id) { + Merge2_heap_db cur = heap_mgr.Cur(); + if (cur == null || cur.Idx() != trg_db_id) { + cur = heap_mgr.Make_itm(ctx.Wiki(), trg_db_id); + } + return cur; + } +} +class Merge2_trg_mgr__wiki implements Merge2_trg_mgr { + private final Split_tbl tbl; + public Merge2_trg_mgr__wiki(Split_tbl tbl) {this.tbl = tbl;} + public Merge2_trg_itm Cur() {return cur;} private Merge2_trg_itm__wiki cur; + public Merge2_trg_itm Cur_or_new(Merge_ctx ctx, Merge_prog_wkr prog_wkr, Xow_wiki wiki, int trg_db_id) { + if (cur == null || cur.Idx() != trg_db_id) { + Db_conn conn = tbl.Wiki_conn__get_or_new(wiki, trg_db_id); + cur = new Merge2_trg_itm__wiki(trg_db_id, conn); + } + return cur; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_wkr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_wkr.java index a27517de8..0542d6eff 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_wkr.java @@ -13,3 +13,9 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.exports.utls.*; +interface Merge2_wkr { + Split_tbl Tbl(); + void Merge_data(Merge_ctx ctx, Merge_prog_wkr prog_wkr); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_wkr__heap_base.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_wkr__heap_base.java index a27517de8..31ce47163 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_wkr__heap_base.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_wkr__heap_base.java @@ -13,3 +13,20 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.exports.utls.*; +public abstract class Merge2_wkr__heap_base implements Merge2_wkr { + public Merge2_wkr__heap_base(Split_tbl tbl) { + this.tbl = tbl; + this.heap_mgr = new Merge2_heap_mgr(tbl); + } + public Split_tbl Tbl() {return tbl;} private final Split_tbl tbl; + protected Merge2_heap_mgr Heap_mgr() {return heap_mgr;} private final Merge2_heap_mgr heap_mgr; + public void Merge_data(Merge_ctx ctx, Merge_prog_wkr prog_wkr) { // fires once per file + this.Copy_to_heap(ctx, prog_wkr, heap_mgr, tbl); + this.Copy_to_wiki(ctx, prog_wkr, heap_mgr, tbl); + } + protected abstract void Copy_to_heap(Merge_ctx ctx, Merge_prog_wkr prog_wkr, Merge2_heap_mgr heap_mgr, Split_tbl tbl); + protected abstract void Copy_to_wiki(Merge_ctx ctx, Merge_prog_wkr prog_wkr, Merge2_heap_mgr heap_mgr, Split_tbl tbl); + public abstract void Copy_to_wiki(Merge_ctx ctx, Merge_prog_wkr prog_wkr, String tbl_name, Dbmeta_fld_list src_flds, Db_conn src_conn, Merge2_trg_itm trg_db, String[] fld_pkeys); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_wkr__heap_lot.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_wkr__heap_lot.java index a27517de8..7bfdb09d0 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_wkr__heap_lot.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_wkr__heap_lot.java @@ -13,3 +13,25 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.exports.utls.*; +public class Merge2_wkr__heap_lot extends Merge2_wkr__heap_base { + private final Merge2_copy_wkr__lot copy_wkr; + private final Merge2_trg_mgr trg_mgr__heap; + public Merge2_wkr__heap_lot(Split_tbl tbl) {super(tbl); + this.copy_wkr = new Merge2_copy_wkr__lot(tbl); + this.trg_mgr__heap = new Merge2_trg_mgr__heap(this.Heap_mgr()); + } + @Override protected void Copy_to_heap(Merge_ctx ctx, Merge_prog_wkr prog_wkr, Merge2_heap_mgr heap_mgr, Split_tbl tbl) { + copy_wkr.Copy_by_sql("merging " + tbl.Tbl_name(), ctx, prog_wkr, ctx.Pack_conn(), null, trg_mgr__heap, Bool_.Y); + // copy_wkr.Copy_entire_src(ctx, prog_wkr, ctx.Pack_conn(), null, trg_mgr__heap, Bool_.Y); + } + @Override protected void Copy_to_wiki(Merge_ctx ctx, Merge_prog_wkr prog_wkr, Merge2_heap_mgr heap_mgr, Split_tbl tbl) { + if (ctx.Heap__copy_to_wiki()) + heap_mgr.Copy_to_wiki(ctx, prog_wkr, this); + } + @Override public void Copy_to_wiki(Merge_ctx ctx, Merge_prog_wkr prog_wkr, String tbl_name, Dbmeta_fld_list trg_flds, Db_conn src_conn, Merge2_trg_itm trg_db, String[] fld_pkeys) { + copy_wkr.Copy_by_sql("merging " + tbl_name, ctx, prog_wkr, src_conn, trg_db, trg_mgr__heap, Bool_.N); + // copy_wkr.Copy_entire_src(ctx, prog_wkr, src_conn, trg_db, trg_mgr__heap, Bool_.N); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_wkr__heap_one.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_wkr__heap_one.java index a27517de8..9c24063d6 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_wkr__heap_one.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge2_wkr__heap_one.java @@ -13,3 +13,25 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.exports.utls.*; +public class Merge2_wkr__heap_one extends Merge2_wkr__heap_base { + public Merge2_wkr__heap_one(Split_tbl tbl) {super(tbl);} + @Override protected void Copy_to_heap(Merge_ctx ctx, Merge_prog_wkr prog_wkr, Merge2_heap_mgr heap_mgr, Split_tbl tbl) { + // init + String tbl_name = tbl.Tbl_name(); + Dbmeta_fld_list flds = tbl.Flds(); + Merge2_heap_db heap_db = heap_mgr.Cur(); + if (heap_db == null) heap_db = heap_mgr.Make_itm(ctx.Wiki(), -1); + + // copy + Merge_wkr_utl.Merge_by_sql(prog_wkr, "merging " + tbl_name, tbl_name, flds, ctx.Pack_conn(), heap_db, -1, Bool_.N); + } + @Override protected void Copy_to_wiki(Merge_ctx ctx, Merge_prog_wkr prog_wkr, Merge2_heap_mgr heap_mgr, Split_tbl tbl) { + if (ctx.Heap__copy_to_wiki()) + heap_mgr.Copy_to_wiki(ctx, prog_wkr, this); + } + @Override public void Copy_to_wiki(Merge_ctx ctx, Merge_prog_wkr prog_wkr, String tbl_name, Dbmeta_fld_list trg_flds, Db_conn src_conn, Merge2_trg_itm trg_db, String[] fld_pkeys) { + Merge_wkr_utl.Merge_by_sql(prog_wkr, "merging " + tbl_name, tbl_name, trg_flds, src_conn, trg_db, -1, Bool_.Y); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_bldr_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_bldr_cmd.java index a27517de8..9a373e533 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_bldr_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_bldr_cmd.java @@ -13,3 +13,38 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.addons.bldrs.exports.splits.mgrs.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +public class Merge_bldr_cmd extends Xob_cmd__base { + public Merge_bldr_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + Io_mgr.Instance.DeleteDirDeep(wiki.Fsys_mgr().Root_dir()); + wiki.Init_assert(); + + Merge2_mgr merge_mgr = new Merge2_mgr(); + Io_url[] urls = Io_mgr.Instance.QueryDir_fils(Io_url_.new_fil_("C:\\xowa\\wiki\\simple.wikipedia.org\\tmp\\split\\")); + int i = 0; + for (Io_url url : urls) { +// if (++i == 5) break; + switch (Split_file_tid_.To_tid(url)) { + case Split_file_tid_.Tid__core: merge_mgr.Merge_core(wiki, url); break; + case Split_file_tid_.Tid__data: + Db_conn conn = Db_conn_bldr.Instance.Get_or_noop(url); + Wkr_stats_tbl stats_tbl = new Wkr_stats_tbl(conn); + Wkr_stats_itm summary_itm = stats_tbl.Select_all__summary(); + Merge_prog_wkr prog_wkr = new Merge_prog_wkr(gplx.core.progs.Gfo_prog_ui_.Always, url.GenNewNameAndExt("merge.checkpoint"), summary_itm.Count, summary_itm.Size); + merge_mgr.Prog_wkr_(prog_wkr); + merge_mgr.Merge_data(wiki, url, i); break; + default: continue; + } + ++i; + } + } + + public static final String BLDR_CMD_KEY = "bldr.export.merge"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Merge_bldr_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Merge_bldr_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_ctx.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_ctx.java index a27517de8..1677a6dc2 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_ctx.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_ctx.java @@ -13,3 +13,20 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; +public class Merge_ctx { + public void Init(Xow_wiki wiki, Db_conn pack_conn) { + this.wiki = wiki; + this.pack_conn = pack_conn; + } + public Xow_wiki Wiki() {return wiki;} private Xow_wiki wiki; + public Db_conn Pack_conn() {return pack_conn;} private Db_conn pack_conn; + public int Idx_cur() {return idx_cur;} private int idx_cur; public void Idx_cur_add_() {++idx_cur;} + public int Idx_end = 70; + public int Idx_gap = 10; + public int Idx_nxt = 10; + public boolean Heap__copy_to_wiki() {return idx_cur == Idx_nxt || idx_cur == Idx_end;} + public void Heap__increment_nxt() {Idx_nxt += Idx_gap;} + public boolean Heap__last_idx() {return idx_cur == Idx_end;} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_prog_checkpoint.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_prog_checkpoint.java index a27517de8..b9e275770 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_prog_checkpoint.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_prog_checkpoint.java @@ -13,3 +13,47 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +public class Merge_prog_checkpoint { + private final Bry_bfr bfr = Bry_bfr_.New(); + private final Io_url url; + private String resume_fil; + private int resume_wkr = -1; + private int resume_db = -1; + public Merge_prog_checkpoint(Io_url url) {this.url = url;} + public int Resume_db() {return resume_db;} + public boolean Skip_fil(Io_url fil) { + if (resume_fil == null) return false; // not resume; do not skip + boolean rv = String_.Eq(fil.NameAndExt(), resume_fil); + if (rv) resume_fil = null; // null out for next call + return !rv; + } + public boolean Skip_wkr(byte wkr_tid) { + if (resume_wkr == -1) return false; // not resume; do not skip; + boolean rv = wkr_tid == resume_wkr; + if (rv) resume_wkr = -1; // null out for next call + return !rv; + } + public int Load() { + byte[] bry = Io_mgr.Instance.LoadFilBryOrNull(url); if (bry == null) return 0; + byte[][] parts = Bry_split_.Split(bry, Byte_ascii.Pipe); + this.resume_fil = String_.new_u8(parts[0]); + this.resume_wkr = Bry_.To_int(parts[1]); + this.resume_db = Bry_.To_int(parts[2]); + return Bry_.To_int(parts[3]); + } + public void Save(Io_url fil, byte wkr_tid, int db_id, int prog_count) { // EX: file.xowa|0|4|1234 + if (fil == null) return; + bfr.Add_str_u8(fil.NameAndExt()).Add_byte_pipe(); + bfr.Add_int_variable(wkr_tid).Add_byte_pipe(); + bfr.Add_int_variable(db_id).Add_byte_pipe(); + bfr.Add_int_variable(prog_count); + Io_mgr.Instance.SaveFilBry(url, bfr.To_bry_and_clear()); + } + public void Delete() { + resume_fil = null; + resume_wkr = -1; + resume_db = -1; + Io_mgr.Instance.DeleteFil(url); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_prog_wkr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_prog_wkr.java index a27517de8..17719a5a8 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_prog_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_prog_wkr.java @@ -13,3 +13,50 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.core.progs.*; import gplx.dbs.bulks.*; +public class Merge_prog_wkr implements Db_bulk_prog { + private final Gfo_prog_ui prog_ui; + private final Merge_prog_checkpoint checkpoint; + private final int prog_count_end; + private int prog_count_cur; + private long time_nxt, time_gap = 100; + // private final int[] mergepoint_ary = new int[gplx.xowa.addons.bldrs.exports.splits.rslts.Split_rslt_tid_.Tid_max]; + public Merge_prog_wkr(Gfo_prog_ui prog_ui, Io_url checkpoint_fil, int prog_count_end, long prog_size_end) { + this.prog_ui = prog_ui; + this.checkpoint = new Merge_prog_checkpoint(checkpoint_fil); + this.prog_count_end = prog_count_end; + } + public boolean Canceled() {return prog_ui.Canceled();} + public long Checkpoint__load() { + long rv = checkpoint.Load(); + this.prog_count_cur = (int)rv; + return rv; + } + public int Checkpoint__resume_db() {return checkpoint.Resume_db();} + public void Checkpoint__delete() {checkpoint.Delete();} + public boolean Checkpoint__skip_fil(Io_url fil) {return checkpoint.Skip_fil(fil);} + private Io_url cur_fil; private byte cur_wkr_tid; + public boolean Checkpoint__skip_wkr(Io_url fil, byte wkr_tid) { + boolean rv = checkpoint.Skip_wkr(wkr_tid); + if (rv) return true; + this.cur_fil = fil; + this.cur_wkr_tid = wkr_tid; + this.Checkpoint__save(); + time_nxt = gplx.core.envs.System_.Ticks() + time_gap; + return false; + } + public void Checkpoint__save() { + // cur_wkr.Resume__db_id() + checkpoint.Save(cur_fil, cur_wkr_tid, -1, prog_count_cur); + } + public boolean Prog__insert_and_stop_if_suspended(int row_size) { + ++prog_count_cur; + long time_cur = gplx.core.envs.System_.Ticks(); + if (time_cur < time_nxt) return false; + // gplx.core.threads.Thread_adp_.Sleep(10); + time_nxt = time_cur + time_gap; + boolean rv = prog_ui.Prog_notify_and_chk_if_suspended(prog_count_cur, prog_count_end); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_wkr__core.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_wkr__core.java index a27517de8..580085e5a 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_wkr__core.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_wkr__core.java @@ -13,3 +13,82 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.dbs.bulks.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.bldrs.infos.*; +import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.dbs.*; +import gplx.fsdb.*; import gplx.fsdb.meta.*; +class Merge_wkr__core { + private final Db_attach_mgr attach_mgr = new Db_attach_mgr(); + private final Db_tbl_copy copy_mgr = new Db_tbl_copy(); + public void Copy_to_temp(Merge_prog_wkr prog_wkr, Xow_wiki wiki, Db_conn src_conn) { + Create_dbs(wiki, src_conn); + Merge_core(wiki, src_conn); + Merge_srch(wiki, src_conn); + Merge_file(wiki, src_conn); + } + private void Create_dbs(Xow_wiki wiki, Db_conn src_conn) { + Db_cfg_tbl cfg_tbl = Xowd_cfg_tbl_.New(src_conn, "xowa_cfg__core"); + Xowd_core_db_props core_db_props = Xowd_core_db_props.Cfg_load(src_conn, cfg_tbl); + Xob_info_session session = Xob_info_session.Load(cfg_tbl); + + Xow_wiki_.Create_sql_backend(wiki, core_db_props, session); + } + private void Merge_core(Xow_wiki wiki, Db_conn src_conn) { + Db_conn trg_conn = wiki.Data__core_mgr().Db__core().Conn(); + copy_mgr.Copy_many(src_conn, trg_conn, "site_ns", "css_core", "css_file"); // NOTE: "xowa_db" skipped; NOTE: css_core must go before Init_by_wiki + copy_mgr.Copy_one(src_conn, trg_conn, "xowa_cfg__core", "xowa_cfg"); + wiki.Init_by_wiki(); + + // make text_db b/c page entries have Load_page_wkr will try to load from text + Xow_db_file text_db = null; + if (wiki.Data__core_mgr().Props().Layout_text().Tid_is_lot()) { + text_db = wiki.Data__core_mgr().Dbs__get_by_tid_or_null(Xow_db_file_.Tid__text); + if (text_db == null) + text_db = wiki.Data__core_mgr().Dbs__make_by_tid(Xow_db_file_.Tid__text); + } + else + text_db = wiki.Data__core_mgr().Db__core(); + text_db.Tbl__text().Create_tbl(); + + // merge at end for site_stats + copy_mgr.Copy_many(src_conn, trg_conn, "site_stats"); + } + private void Merge_srch(Xow_wiki wiki, Db_conn src_conn) { + Srch_search_addon addon = Srch_search_addon.Get(wiki); + + Srch_db_mgr srch_db_mgr = addon.Db_mgr(); + srch_db_mgr.Create_all(); + srch_db_mgr.Tbl__word().Create_idx(); + int len = srch_db_mgr.Tbl__link__len(); + for (int i = 0; i < len; ++i) + srch_db_mgr.Tbl__link__get_at(i).Create_idx__link_score(); + + Db_conn trg_conn = addon.Db_mgr().Tbl__word().conn; + // copy_mgr.Copy_many(src_conn, trg_conn, "search_link_reg"); + Merge_cfg(src_conn, trg_conn, "xowa_cfg__srch"); + } + private void Merge_file(Xow_wiki wiki, Db_conn src_conn) { + if (wiki.Type_is_edit()) + ((Xowe_wiki)wiki).File_mgr().Init_file_mgr_by_load(wiki); + else + wiki.File__mnt_mgr().Ctor_by_load(wiki.File__fsdb_core()); + Db_conn trg_conn = wiki.File__fsdb_core().File__atr_file__at(Fsm_mnt_mgr.Mnt_idx_main).Conn(); + copy_mgr.Copy_many(src_conn, trg_conn, "fsdb_dba", "fsdb_dbb", "fsdb_dir", "fsdb_mnt"); + Merge_cfg(src_conn, trg_conn, "xowa_cfg__file"); + } + private void Merge_cfg(Db_conn src_conn, Db_conn trg_conn, String src_tbl_name) { + if (trg_conn.Meta_tbl_exists("xowa_cfg")) { + attach_mgr.Conn_main_(trg_conn).Conn_links_(new Db_attach_itm("src_db", src_conn)); + attach_mgr.Exec_sql(String_.Concat_lines_nl + ( "INSERT INTO xowa_cfg" + , "SELECT s.cfg_grp, s.cfg_key, s.cfg_val" + , "FROM " + src_tbl_name + " s" + , " LEFT JOIN xowa_cfg t ON s.cfg_grp = t.cfg_grp AND s.cfg_key = t.cfg_key" + , "WHERE t.cfg_grp IS NULL" + )); + } + else + copy_mgr.Copy_one (src_conn, trg_conn, src_tbl_name, "xowa_cfg"); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_wkr_utl.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_wkr_utl.java index a27517de8..0f00aba2c 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_wkr_utl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/merges/Merge_wkr_utl.java @@ -13,3 +13,55 @@ 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.addons.bldrs.exports.merges; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; import gplx.dbs.bulks.*; import gplx.dbs.engines.sqlite.*; +import gplx.xowa.addons.bldrs.exports.utls.*; +class Merge_wkr_utl { + public static void Merge_by_sql(Db_bulk_prog prog_wkr, String msg, String tbl_name, Dbmeta_fld_list flds, Db_conn src_conn, Merge2_trg_itm trg_db, int trg_db_id, boolean disable_synchronous) { + Bry_bfr bfr = Bry_bfr_.New(); + Db_conn trg_conn = trg_db.Conn(); + Db_attach_mgr attach_mgr = new Db_attach_mgr(trg_conn, new Db_attach_itm("src_db", src_conn)); + Io_url trg_url = Sqlite_conn_info.To_url(trg_conn); + if (disable_synchronous) { + Io_mgr.Instance.CopyFil(trg_url, trg_url.GenNewExt(".bak"), true); + trg_conn.Exec_qry(Sqlite_pragma.New__synchronous__off()); + Set_journal(trg_conn, "OFF"); + } + attach_mgr.Exec_sql(Bld_insert_into(bfr, tbl_name, flds, trg_db_id, disable_synchronous)); + if (disable_synchronous) { + trg_conn.Exec_qry(Sqlite_pragma.New__synchronous__normal()); + Io_mgr.Instance.DeleteFil(trg_url.GenNewExt(".bak")); + Set_journal(trg_conn, "DELETE"); + } + prog_wkr.Prog__insert_and_stop_if_suspended(1); + } + private static void Set_journal(Db_conn conn, String mode) { + Db_rdr rdr = conn.Stmt_sql("PRAGMA journal_mode=" + mode).Exec_select__rls_auto(); + try { + rdr.Move_next(); + rdr.Read_at(0); + } + finally { + rdr.Rls(); + } + } + private static String Bld_insert_into(Bry_bfr bfr, String tbl_name, Dbmeta_fld_list flds, int trg_db_id, boolean disable_synchronous) { + Split_tbl_.Bld_insert_by_select(bfr, tbl_name, flds); + if (trg_db_id != -1 && !disable_synchronous) + bfr.Add_str_u8_fmt("WHERE trg_db_id = {0}", trg_db_id); + return bfr.To_str_and_clear(); + } + public static void Merge_by_rows(Db_bulk_prog prog_wkr, String msg, String tbl_name, Dbmeta_fld_list flds, Db_conn src_conn, Db_conn trg_conn, String[] order_bys) { + Db_stmt src_stmt = order_bys == String_.Ary_empty + ? src_conn.Stmt_select_all(tbl_name, flds) + : src_conn.Stmt_select_order(tbl_name, flds, String_.Ary_empty, order_bys) + ; + Db_rdr src = src_stmt.Exec_select__rls_auto(); + Db_stmt trg = trg_conn.Stmt_insert(tbl_name, flds); + try {Db_bulk_exec_.Insert(prog_wkr, msg, flds.To_fld_ary(), src, trg, trg_conn);} + finally { + src.Rls(); + trg.Rls(); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_file_bldr_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_file_bldr_cmd.java index a27517de8..2416e432d 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_file_bldr_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_file_bldr_cmd.java @@ -13,3 +13,23 @@ 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.addons.bldrs.exports.packs.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.packs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Pack_file_bldr_cmd extends Xob_cmd__base { + private final Pack_file_cfg cfg = new Pack_file_cfg(); + public Pack_file_bldr_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + new Pack_file_mgr().Exec(wiki, cfg); + } + + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__cfg)) return cfg; + else return super.Invk (ctx, ikey, k, m); + } + private static final String Invk__cfg = "cfg"; + + public static final String BLDR_CMD_KEY = "bldr.export.pack.file"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Pack_file_bldr_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Pack_file_bldr_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_file_cfg.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_file_cfg.java index a27517de8..2c1b7e43e 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_file_cfg.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_file_cfg.java @@ -13,3 +13,37 @@ 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.addons.bldrs.exports.packs.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.packs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Pack_file_cfg implements Gfo_invk { + public Io_url Deploy_dir() {return deploy_dir;} private Io_url deploy_dir; + public boolean Pack_text() {return pack_text;} private boolean pack_text = false; + public boolean Pack_html() {return pack_html;} private boolean pack_html = true; + public boolean Pack_file() {return pack_file;} private boolean pack_file = true; + public boolean Pack_lucene() {return pack_lucene;} private boolean pack_lucene; + public long Lucene_max() {return lucene_max;} private long lucene_max = Io_mgr.Len_mb * 1500; + public boolean Pack_fsdb_delete() {return pack_fsdb_delete;} private boolean pack_fsdb_delete; + public boolean Pack_custom() {return pack_custom_files != null;} + public String Pack_custom_files() {return pack_custom_files;} private String pack_custom_files; + public String Pack_custom_name() {return pack_custom_name;} private String pack_custom_name; + public DateAdp Pack_file_cutoff() {return pack_file_cutoff;} private DateAdp pack_file_cutoff = null; + public String Wiki_date(DateAdp wiki_last_modified) { + return wiki_date == null ? wiki_last_modified.XtoStr_fmt("yyyy.MM") : wiki_date; + } private String wiki_date = null; + + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, "deploy_dir_")) deploy_dir = m.ReadIoUrl("v"); + else if (ctx.Match(k, "pack_text_")) pack_text = m.ReadYn("v"); + else if (ctx.Match(k, "pack_html_")) pack_html = m.ReadYn("v"); + else if (ctx.Match(k, "pack_file_")) pack_file = m.ReadYn("v"); + else if (ctx.Match(k, "pack_file_cutoff_")) pack_file_cutoff = m.ReadDate("v"); + else if (ctx.Match(k, "pack_fsdb_delete_")) pack_fsdb_delete = m.ReadYn("v"); + else if (ctx.Match(k, "pack_custom_name_")) pack_custom_name = m.ReadStr("v"); + else if (ctx.Match(k, "pack_custom_files_")) pack_custom_files = m.ReadStr("v"); // pack_custom {files='en.wikipedia.org-core.xowa|en.wikipedia.org-html-ns.008.xowa'}} + else if (ctx.Match(k, "wiki_date_")) wiki_date = m.ReadStr("v"); + else if (ctx.Match(k, "pack_lucene_")) pack_lucene = m.ReadYn("v"); + else if (ctx.Match(k, "lucene_max_")) lucene_max = m.ReadLong("v") * Io_mgr.Len_mb; + else return Gfo_invk_.Rv_unhandled; + return this; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_file_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_file_mgr.java index a27517de8..5a6502a0c 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_file_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_file_mgr.java @@ -13,3 +13,161 @@ 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.addons.bldrs.exports.packs.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.packs.*; +import gplx.core.progs.*; import gplx.core.ios.zips.*; import gplx.core.ios.streams.*; import gplx.core.security.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.*; import gplx.fsdb.*; import gplx.fsdb.meta.*; +import gplx.xowa.addons.bldrs.centrals.dbs.*; import gplx.xowa.addons.bldrs.centrals.dbs.datas.*; import gplx.xowa.addons.bldrs.centrals.dbs.datas.imports.*; import gplx.xowa.addons.bldrs.centrals.steps.*; import gplx.xowa.addons.bldrs.centrals.hosts.*; import gplx.xowa.addons.bldrs.centrals.tasks.*; +public class Pack_file_mgr { + public void Exec(Xowe_wiki wiki, Pack_file_cfg cfg) { + // init + wiki.Init_assert(); + Io_url wiki_dir = wiki.Fsys_mgr().Root_dir(); + Io_url pack_dir = wiki_dir.GenSubDir_nest("tmp", "pack"); + Io_mgr.Instance.DeleteDirDeep(pack_dir); Io_mgr.Instance.CreateDirIfAbsent(pack_dir); + String wiki_date = cfg.Wiki_date(wiki.Props().Modified_latest()); + Pack_hash hash = Pack_hash_bldr.Bld(wiki, wiki_dir, pack_dir, wiki_date, cfg); + + // get import_tbl + byte[] wiki_abrv = wiki.Domain_itm().Abrv_xo(); + Xobc_data_db bc_db = Xobc_data_db.New(wiki.App().Fsys_mgr()); + Db_conn bc_conn = bc_db.Conn(); + if (!cfg.Pack_custom()) // only delete files if not custom + bc_db.Delete_by_import(wiki_abrv, wiki_date); + bc_conn.Txn_bgn("xobc_import_insert"); + + // build zip packs + Hash_algo hash_algo = Hash_algo_.New__md5(); + Bry_bfr tmp_bfr = Bry_bfr_.New(); + int hash_len = hash.Len(); + for (int i = 0; i < hash_len; ++i) { + Pack_list list = (Pack_list)hash.Get_at(i); + int list_len = list.Len(); + for (int j = 0; j < list_len; ++j) { + Pack_itm itm = (Pack_itm)list.Get_at(j); + Make_pack(wiki, wiki_dir, wiki_abrv, wiki_date, bc_db, hash_algo, tmp_bfr, itm, 0); + } + } + + // build tasks + if (cfg.Pack_html()) + Make_task(tmp_bfr, wiki, wiki_date, bc_db, hash, Xobc_task_regy_itm.Type__html, Xobc_import_type.Tid__wiki__core, Xobc_import_type.Tid__wiki__srch, Xobc_import_type.Tid__wiki__html, Xobc_import_type.Tid__wiki__ctg, Xobc_import_type.Tid__wiki__lucene); + if (cfg.Pack_file()) + Make_task(tmp_bfr, wiki, wiki_date, bc_db, hash, Xobc_task_regy_itm.Type__file, Xobc_import_type.Tid__file__core, Xobc_import_type.Tid__file__data); // , Xobc_import_type.Tid__fsdb__delete + if (cfg.Pack_text()) // NOTE: will only add wikitext and wikibase packs; wikidata needs to pack html also; DATE:2017-04-09 + Make_task(tmp_bfr, wiki, wiki_date, bc_db, hash, Xobc_task_regy_itm.Type__text, Xobc_import_type.Tid__wiki__text, Xobc_import_type.Tid__wiki__wbase); + if (cfg.Pack_custom()) + Make_task(tmp_bfr, wiki, wiki_date, bc_db, hash, cfg.Pack_custom_name(), Xobc_import_type.Tid__misc); + bc_conn.Txn_end(); + + // deploy + Io_url deploy_dir = cfg.Deploy_dir(); + if (deploy_dir != null) { + Host_eval_itm host_eval = new Host_eval_itm(); + int len = hash.Len(); + for (int i = 0; i < len; ++i) { + Pack_list list = (Pack_list)hash.Get_at(i); + int list_len = list.Len(); + for (int j = 0; j < list_len; ++j) { + Pack_itm itm = (Pack_itm)list.Get_at(j); + byte[] owner_dir = host_eval.Eval_dir_name(wiki.Domain_itm()); + Io_url src_url = itm.Zip_url(); + Io_url trg_url = deploy_dir.GenSubFil_nest(String_.new_u8(owner_dir), src_url.NameAndExt()); + Io_mgr.Instance.MoveFil_args(src_url, trg_url, true).Exec(); + } + } + Io_mgr.Instance.Delete_dir_empty(pack_dir); + } + } + private static void Make_task(Bry_bfr tmp_bfr, Xow_wiki wiki, String wiki_date, Xobc_data_db bc_db, Pack_hash hash, String task_type, int... list_tids) { + // get packs + List_adp pack_list = List_adp_.New(); + int list_tids_len = list_tids.length; + long raw_len = 0; + for (int i = 0; i < list_tids_len; ++i) { + Pack_list list = hash.Get_by(list_tids[i]); + if (list == null) continue; // no lists for that tid; + int list_len = list.Len(); + for (int j = 0; j < list_len; ++j) { + Pack_itm itm = (Pack_itm)list.Get_at(j); + raw_len += itm.Raw_size(); + pack_list.Add(itm); + } + } + int pack_list_len = pack_list.Len(); + + // create task + String task_key = Xobc_task_key.To_str(wiki.Domain_str(), wiki_date, task_type); + String task_name = Build_task_name(tmp_bfr, wiki, wiki_date, task_type, raw_len); + Xobc_task_regy_tbl task_regy_tbl = bc_db.Tbl__task_regy(); + int task_id = bc_db.Conn().Sys_mgr().Autonum_next("task_regy.task_id"); + task_regy_tbl.Insert(task_id, task_id, pack_list_len, task_key, task_name); + + // map steps + for (int i = 0; i < pack_list_len; ++i) { + Pack_itm itm = (Pack_itm)pack_list.Get_at(i); + int sm_id = bc_db.Conn().Sys_mgr().Autonum_next("step_map.sm_id"); + bc_db.Tbl__step_map().Insert(sm_id, task_id, itm.Step_id(), i); + } + } + public static String Build_task_name(Bry_bfr tmp_bfr, Xow_wiki wiki, String wiki_date, String task_type, long raw_len) {// Simple Wikipedia - Articles (2016-06) [420.31 MB] + byte[] lang_key = wiki.Domain_itm().Lang_orig_key(); + byte[] lang_name = Bry_.Len_eq_0(lang_key) // species.wikimedia.org and other wikimedia wikis have no lang; + ? Bry_.Empty + : Bry_.Add(gplx.xowa.langs.Xol_lang_stub_.Get_by_key_or_null(lang_key).Canonical_name(), Byte_ascii.Space); // EX: "Deutsch " + byte[] wiki_name = wiki.Domain_itm().Domain_type().Display_bry(); // EX: Wikipedia + String type_name = Get_task_name_by_task_type(task_type); + wiki_date = String_.Replace(wiki_date, ".", "-"); + String file_size = gplx.core.ios.Io_size_.To_str_new(tmp_bfr, raw_len, 2); + return String_.Format("{0}{1} - {2} ({3}) [{4}]", lang_name, wiki_name, type_name, wiki_date, file_size); + } + private static String Get_task_name_by_task_type(String task_type) { + if (String_.Eq(task_type, Xobc_task_regy_itm.Type__html)) return "Articles"; + else if (String_.Eq(task_type, Xobc_task_regy_itm.Type__file)) return "Images"; + else if (String_.Eq(task_type, Xobc_task_regy_itm.Type__text)) return "Wikitext"; + else return task_type; + } + private static void Make_pack(Xowe_wiki wiki, Io_url wiki_dir, byte[] wiki_abrv, String wiki_date, Xobc_data_db bc_db, Hash_algo hash_algo, Bry_bfr tmp_bfr, Pack_itm itm, int task_id) { + // hash raws + Io_url zip_url = itm.Zip_url(); + Gfo_log_.Instance.Prog("hashing raw: " + zip_url.NameAndExt()); + Io_url md5_url = wiki_dir.GenSubFil(zip_url.NameOnly() + ".md5"); + long raw_size = 0; + Io_url[] raw_urls = itm.Raw_urls(); + int raw_urls_len = raw_urls.length; + for (int i = 0; i < raw_urls_len; ++i) { + Io_url raw_url = raw_urls[i]; + IoStream raw_stream = Io_mgr.Instance.OpenStreamRead(raw_url); + byte[] raw_md5 = null; + try {raw_md5 = hash_algo.Hash_stream_as_bry(Gfo_prog_ui_.Noop, raw_stream);} + finally {raw_stream.Rls();} + tmp_bfr.Add(raw_md5).Add_byte_space().Add_byte(Byte_ascii.Star).Add_str_a7(raw_url.NameAndExt()).Add_byte_nl(); + raw_size += raw_stream.Len(); + } + Io_mgr.Instance.SaveFilBfr(md5_url, tmp_bfr); + itm.Raw_size_(raw_size); + + // compress raws + Gfo_log_.Instance.Prog("compressing raw"); + Io_zip_compress_cmd__jre zip_cmd = new Io_zip_compress_cmd__jre(); + raw_urls = (Io_url[])Array_.Insert(raw_urls, new Io_url[] {md5_url}, 0); // add ".md5" to .zip + zip_cmd.Exec_hook(Gfo_prog_ui_.Noop, raw_urls, zip_url, "", 0, 0); + + // hash zip + Gfo_log_.Instance.Prog("hashing zip"); + IoStream zip_stream = Io_mgr.Instance.OpenStreamRead(zip_url); + byte[] zip_md5 = null; + try {zip_md5 = hash_algo.Hash_stream_as_bry(Gfo_prog_ui_.Noop, zip_stream);} + finally {zip_stream.Rls();} + long zip_len = Io_mgr.Instance.QueryFil(zip_url).Size(); + + // cleanup + Io_mgr.Instance.DeleteFil(md5_url); + + // generate import + Gfo_log_.Instance.Prog("generating tasks"); + int step_id = bc_db.Conn().Sys_mgr().Autonum_next("step_regy.step_id"); + itm.Step_id_(step_id); + bc_db.Tbl__step_regy().Insert(step_id, Xobc_step_itm.Type__wiki_import); + bc_db.Tbl__import_step().Insert(step_id, gplx.xowa.addons.bldrs.centrals.dbs.datas.Xobc_host_regy_tbl.Host_id__archive_org, wiki_abrv, wiki_date, zip_url.NameAndExt(), itm.Tid(), Xobc_zip_type.Type__zip, zip_md5, zip_len, raw_size, 0, 0); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_hash.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_hash.java index a27517de8..c4791d0ef 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_hash.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_hash.java @@ -13,3 +13,41 @@ 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.addons.bldrs.exports.packs.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.packs.*; +class Pack_hash { + private final Ordered_hash hash = Ordered_hash_.New(); + public int Len() {return hash.Len();} + public Pack_list Get_at(int i) {return (Pack_list)hash.Get_at(i);} + public Pack_list Get_by(int tid) {return (Pack_list)hash.Get_by(tid);} + public Pack_itm Add(Pack_zip_name_bldr bldr, int list_tid, Io_url file_url) {return Add(list_tid, bldr.Bld(file_url), file_url);} + public Pack_itm Add(int list_tid, Io_url pack_url, Io_url... raw_urls) { + Pack_list list = (Pack_list)hash.Get_by(list_tid); + if (list == null) { + list = new Pack_list(list_tid); + hash.Add(list_tid, list); + } + Pack_itm itm = new Pack_itm(list_tid, pack_url, raw_urls); + + // check if file exists; needed for wikitext packs which add same urls as html packs; DATE:2017-04-09 + if (list.Has(pack_url)) + return itm; + list.Add(itm); + return itm; + } + public void Consolidate(int... tids) { // merge n itms into 1 itm; needed for search-core + search-link -> search + int tids_len = tids.length; + for (int i = 0; i < tids_len; ++i) { + Pack_list list = (Pack_list)hash.Get_by(tids[i]); + if (list == null) continue; // tid doesn't exist; EX: search in Tid_few + int list_len = list.Len(); + Pack_itm itm_0 = (Pack_itm)list.Get_at(0); + Io_url[] urls = new Io_url[list_len]; + for (int j = 0; j < list_len; ++j) { + urls[j] = ((Pack_itm)list.Get_at(j)).Raw_urls()[0]; + } + list.Clear(); + itm_0.Raw_urls_(urls); + list.Add(itm_0); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_hash_bldr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_hash_bldr.java index a27517de8..dc4960f64 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_hash_bldr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_hash_bldr.java @@ -13,3 +13,149 @@ 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.addons.bldrs.exports.packs.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.packs.*; +import gplx.core.ios.*; +import gplx.fsdb.meta.*; +import gplx.xowa.wikis.data.*; +import gplx.xowa.addons.bldrs.centrals.dbs.datas.imports.*; +class Pack_hash_bldr { + public static Pack_hash Bld(Xow_wiki wiki, Io_url wiki_dir, Io_url pack_dir, String wiki_date, Pack_file_cfg cfg) { + // boolean pack_text, boolean pack_html, boolean pack_file, DateAdp pack_file_cutoff, boolean pack_fsdb_delete + Pack_hash rv = new Pack_hash(); + Pack_zip_name_bldr zip_name_bldr = new Pack_zip_name_bldr(pack_dir, wiki.Domain_str(), String_.new_a7(wiki.Domain_itm().Abrv_wm()), wiki_date, cfg.Pack_custom_name()); + Xow_db_mgr db_mgr = wiki.Data__core_mgr(); + + // bld custom_files + if (cfg.Pack_custom()) + return Bld_custom_files(rv, wiki, wiki_dir, zip_name_bldr, cfg.Pack_custom_files()); + + // bld html pack + if (cfg.Pack_html()) { + int len = db_mgr.Dbs__len(); + for (int i = 0; i < len; ++i) { + Xow_db_file file = db_mgr.Dbs__get_at(i); + int pack_tid = Get_pack_tid(file.Tid()); + if (Int_.In(pack_tid, Xobc_import_type.Tid__ignore, Xobc_import_type.Tid__wiki__text)) continue; + rv.Add(zip_name_bldr, pack_tid, file.Url()); + } + rv.Consolidate(Xobc_import_type.Tid__wiki__srch); + } + + // bld text pack + if (cfg.Pack_text()) { + int len = db_mgr.Dbs__len(); + for (int i = 0; i < len; ++i) { + Xow_db_file file = db_mgr.Dbs__get_at(i); + int pack_tid = Get_pack_tid(file.Tid()); + if (pack_tid == Xobc_import_type.Tid__ignore) continue; // NOTE: will try to add same urls as html, but noops b/c rv.Add checks for unique urls; DATE:2017-04-09 + rv.Add(zip_name_bldr, pack_tid, file.Url()); + } + } + + // bld lucene pack + if (cfg.Pack_lucene()) { + Pack_lucene(rv, wiki, zip_name_bldr, cfg); + } + + // bld file pack + if (cfg.Pack_file()) { + Fsm_mnt_itm mnt_itm = wiki.File__mnt_mgr().Mnts__get_at(Fsm_mnt_mgr.Mnt_idx_main); + rv.Add(zip_name_bldr, Xobc_import_type.Tid__file__core, wiki_dir.GenSubFil(mnt_itm.Atr_mgr().Db__core().Url_rel())); + if (db_mgr.Props().Layout_file().Tid_is_lot()) { + Fsm_bin_mgr bin_mgr = mnt_itm.Bin_mgr(); + int bin_len = bin_mgr.Dbs__len(); + for (int i = 0; i < bin_len; ++i) { + Fsm_bin_fil bin_fil = bin_mgr.Dbs__get_at(i); + Io_url bin_fil_url = bin_fil.Url(); + + // ignore if bin_fil is earlier than cutoff + if (cfg.Pack_file_cutoff() != null) { + DateAdp bin_fil_date = Io_mgr.Instance.QueryFil(bin_fil_url).ModifiedTime(); + if (bin_fil_date.Timestamp_unix() < cfg.Pack_file_cutoff().Timestamp_unix()) continue; + } + rv.Add(zip_name_bldr, Xobc_import_type.Tid__file__data, bin_fil_url); + } + } + } + + // bld pack_fsdb_delete + if (cfg.Pack_fsdb_delete()) { + gplx.xowa.bldrs.Xob_db_file fsdb_deletion_db = gplx.xowa.bldrs.Xob_db_file.New__deletion_db(wiki); + if (!Io_mgr.Instance.ExistsFil(fsdb_deletion_db.Url())) throw Err_.new_wo_type("deletion db does not exists: url=" + fsdb_deletion_db.Url().Raw()); + rv.Add(zip_name_bldr, Xobc_import_type.Tid__fsdb__delete, fsdb_deletion_db.Url()); + } + return rv; + } + private static void Pack_lucene(Pack_hash rv, Xow_wiki wiki, Pack_zip_name_bldr zip_name_bldr, Pack_file_cfg cfg) { + // read files from lucene_dir + Io_url lucene_dir = gplx.xowa.addons.wikis.fulltexts.Xosearch_fulltext_addon.Get_index_dir(wiki); + IoItmHash fils = Io_mgr.Instance.QueryDir_args(lucene_dir).ExecAsItmHash(); + + // init vars + int pack_num = 0; + long size_cur = 0; + long size_max = cfg.Lucene_max(); + List_adp url_list = List_adp_.New(); + int fil_idx = 0; + int fils_len = fils.Len(); + + // loop over each file + while (fil_idx < fils_len) { + IoItmFil fil = (IoItmFil)fils.Get_at(fil_idx); + + // calc size_new + long size_new = size_cur + fil.Size(); + + // if last file, set size_new to max and add file + boolean add_file = true; + if (fil_idx == fils_len - 1) { + size_new = size_max; + url_list.Add(fil.Url()); + add_file = false; + } + + // size exceeded; make new pack + if (size_new >= size_max) { + rv.Add(Xobc_import_type.Tid__wiki__lucene, zip_name_bldr.Bld_by_suffix("xtn.fulltext_search", pack_num), (Io_url[])url_list.To_ary_and_clear(Io_url.class)); + pack_num++; + size_cur = 0; + } + // size too small; just update + else { + size_cur = size_new; + } + + // add file to list; ignore if last file and added above + if (add_file) + url_list.Add(fil.Url()); + fil_idx++; + } + } + private static Pack_hash Bld_custom_files(Pack_hash rv, Xow_wiki wiki, Io_url wiki_dir, Pack_zip_name_bldr zip_name_bldr, String custom_files_blob) { + String[] custom_files = String_.Split(custom_files_blob, "|"); + int len = custom_files.length; + for (int i = 0; i < len; ++i) { + Io_url file_url = wiki_dir.GenSubFil(custom_files[i]); + rv.Add(zip_name_bldr, Xobc_import_type.Tid__misc, file_url); + } + return rv; + } + private static int Get_pack_tid(byte db_file_tid) { + switch (db_file_tid) { + case Xow_db_file_.Tid__core: return Xobc_import_type.Tid__wiki__core; + case Xow_db_file_.Tid__search_core: + case Xow_db_file_.Tid__search_link: return Xobc_import_type.Tid__wiki__srch; + case Xow_db_file_.Tid__html_solo: + case Xow_db_file_.Tid__html_data: return Xobc_import_type.Tid__wiki__html; + case Xow_db_file_.Tid__cat: + case Xow_db_file_.Tid__cat_core: + case Xow_db_file_.Tid__cat_link: return Xobc_import_type.Tid__wiki__ctg; + case Xow_db_file_.Tid__file_core: return Xobc_import_type.Tid__file__core; + case Xow_db_file_.Tid__file_solo: + case Xow_db_file_.Tid__file_data: return Xobc_import_type.Tid__file__data; + case Xow_db_file_.Tid__text: return Xobc_import_type.Tid__wiki__text; + case Xow_db_file_.Tid__wbase: return Xobc_import_type.Tid__wiki__wbase; + default: return Xobc_import_type.Tid__ignore; + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_itm.java index a27517de8..0b203476a 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_itm.java @@ -13,3 +13,16 @@ 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.addons.bldrs.exports.packs.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.packs.*; +class Pack_itm { + public Pack_itm(int tid, Io_url zip_url, Io_url... raw_urls) { + this.tid = tid; + this.zip_url = zip_url; + this.raw_urls = raw_urls; + } + public int Tid() {return tid;} private int tid; + public Io_url Zip_url() {return zip_url;} private Io_url zip_url; + public Io_url[] Raw_urls() {return raw_urls;} private Io_url[] raw_urls; public void Raw_urls_(Io_url[] v) {this.raw_urls = v;} + public long Raw_size() {return raw_size;} private long raw_size; public void Raw_size_(long v) {this.raw_size = v;} + public int Step_id() {return step_id;} private int step_id; public void Step_id_(int v) {this.step_id = v;} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_list.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_list.java index a27517de8..d9f953427 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_list.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_list.java @@ -13,3 +13,16 @@ 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.addons.bldrs.exports.packs.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.packs.*; +class Pack_list { + private final Ordered_hash list = Ordered_hash_.New(); + public Pack_list(int tid) {this.tid = tid;} + public int Tid() {return tid;} private final int tid; + public int Len() {return list.Len();} + public Pack_itm Get_at(int i) {return (Pack_itm)list.Get_at(i);} + public void Add(Pack_itm itm) {list.Add(itm.Zip_url().Raw(), itm);} + public void Clear() {list.Clear();} + public boolean Has(Io_url url) { + return list.Has(url.Raw()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_zip_name_bldr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_zip_name_bldr.java index a27517de8..4b93cae27 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_zip_name_bldr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_zip_name_bldr.java @@ -13,3 +13,54 @@ 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.addons.bldrs.exports.packs.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.packs.*; +public class Pack_zip_name_bldr { // en.wikipedia.org-file-ns.000-db.001.xowa -> Xowa_enwiki_2016-05_file_ns.000_db.001.zip + private final Io_url pack_dir; + private final byte[] wiki_domain, zip_name_prefix; + + public Pack_zip_name_bldr(Io_url pack_dir, String wiki_domain_str, String wiki_abrv, String wiki_date, String custom_name) { + this.pack_dir = pack_dir; + this.wiki_domain = Bry_.new_u8(wiki_domain_str); + + String zip_name_suffix = custom_name == null ? String_.Replace(wiki_date, ".", "-") : custom_name; // "Xowa_enwiki_2016-10_core.zip" vs "Xowa_enwiki_custom_core.zip" + this.zip_name_prefix = Bry_.new_u8("Xowa_" + wiki_abrv + "_" + zip_name_suffix); + } + public Io_url Bld(Io_url orig_url) { + // get name and add .zip; EX: "en.wikipedia.org-file-core.xowa" -> "en.wikipedia.org-file-core.zip" + byte[] orig_bry = Bry_.new_u8(orig_url.NameOnly() + ".zip"); + + // swap dashes with unders; EX: "en.wikipedia.org-file-core.xowa" -> "en.wikipedia.org_file_core.zip" + orig_bry = Bry_.Replace(orig_bry, Byte_ascii.Dash, Byte_ascii.Underline); + + // swap domain with xobc-style-prefix; EX: "en.wikipedia.org_file_core.zip" -> "Xowa_enwiki_2017-03_file_core.zip" + orig_bry = Bry_.Replace(orig_bry, wiki_domain, zip_name_prefix); + + return pack_dir.GenSubFil(String_.new_u8(orig_bry)); + } + public Io_url Bld_by_suffix(String suffix, int pack_num) { + // make fil_name EX: "Xowa_enwiki_2017-03" + "_" + "xtn.fulltext_search.001" + .zip + String fil_name = String_.new_u8(zip_name_prefix) + "_" + suffix + "." + Int_.To_str_pad_bgn_zero(pack_num + List_adp_.Base1, 3) + ".zip"; + return pack_dir.GenSubFil(fil_name); + } + public static Io_url To_wiki_url(Io_url wiki_dir, Io_url zip_dir) { + // get wiki_url based on wiki_dir and xobc_zip_fil; EX: "/wiki/en.wikipedia.org/", "/wiki/tmp/Xowa_enwiki_2016-09_file_core_deletion_2016-09/" -> "/wiki/en.wikipedia.org-file-core-deletion-2016.09.zip" + String name_str = zip_dir.NameOnly() + ".xowa"; + byte[] name_bry = Bry_.new_u8(name_str); + int pos = Bry_find__Find_fwd_idx(name_bry, Byte_ascii.Underline, 2); + name_bry = Bry_.Mid(name_bry, pos, name_bry.length); + name_bry = Bry_.Add(Bry_.new_u8(wiki_dir.NameOnly()), name_bry); + name_bry = Bry_.Replace(name_bry, Byte_ascii.Underline, Byte_ascii.Dash); + return wiki_dir.GenSubFil(String_.new_u8(name_bry)); + } + private static int Bry_find__Find_fwd_idx(byte[] src, byte val, int find_max) { + int src_len = src.length; + int find_cur = 0; + int cur_pos = 0; + while (true) { + int new_pos = Bry_find_.Find_fwd(src, val, cur_pos, src_len); + if (new_pos == -1) throw Err_.New("failed to find value; src={0} val={1}", src, val); + if (find_cur++ == find_max) return new_pos; + cur_pos = new_pos + 1; + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_zip_name_bldr__tst.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_zip_name_bldr__tst.java index a27517de8..12db35412 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_zip_name_bldr__tst.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/files/Pack_zip_name_bldr__tst.java @@ -13,3 +13,27 @@ 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.addons.bldrs.exports.packs.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.packs.*; +import org.junit.*; import gplx.core.tests.*; +public class Pack_zip_name_bldr__tst { + private Pack_zip_name_bldr__fxt fxt = new Pack_zip_name_bldr__fxt(); + @Test public void Basic() { + fxt.Test__to_wiki_url("mem/wiki/en.wikipedia.org/", "mem/wiki/en.wikipedia.org/tmp/Xowa_enwiki_2016-09_file_deletion_2016.09/", "mem/wiki/en.wikipedia.org/en.wikipedia.org-file-deletion-2016.09.xowa"); + } + @Test public void Bld_by_suffix() { + Pack_zip_name_bldr bldr = fxt.Make__bldr("mem/wiki/en.wikipedia.org/tmp/pack/", "en.wikipedia.org", "enwiki", "2017-03", null); + fxt.Test__bld_by_suffix(bldr, "xtn.fulltext_search", 1, "mem/wiki/en.wikipedia.org/tmp/pack/Xowa_enwiki_2017-03_xtn.fulltext_search.002.zip"); + } +} +class Pack_zip_name_bldr__fxt { + public void Test__to_wiki_url(String wiki_dir, String zip_fil, String expd) { + Gftest.Eq__str(expd, Pack_zip_name_bldr.To_wiki_url(Io_url_.mem_fil_(wiki_dir), Io_url_.mem_dir_(zip_fil)).Raw(), "wiki_url"); + } + + public Pack_zip_name_bldr Make__bldr(String wiki_dir, String domain, String wiki_abrv, String wiki_date, String custom_name) { + return new Pack_zip_name_bldr(Io_url_.new_dir_(wiki_dir), domain, wiki_abrv, wiki_date, custom_name); + } + public void Test__bld_by_suffix(Pack_zip_name_bldr bldr, String suffix, int pack_num, String expd) { + Gftest.Eq__str(expd, bldr.Bld_by_suffix(suffix, pack_num).Xto_api()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/splits/Pack_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/splits/Pack_itm.java index a27517de8..f6de91943 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/splits/Pack_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/splits/Pack_itm.java @@ -13,3 +13,16 @@ 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.addons.bldrs.exports.packs.splits; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.packs.*; +class Pack_itm { + public Pack_itm(int idx, int type, Io_url zip_url, Io_url[] raw_urls) { + this.idx = idx; + this.type = type; + this.zip_url = zip_url; + this.raw_urls = raw_urls; + } + public int Idx() {return idx;} private final int idx; + public int Type() {return type;} private final int type; + public Io_url[] Raw_urls() {return raw_urls;} private final Io_url[] raw_urls; + public Io_url Zip_url() {return zip_url;} private final Io_url zip_url; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/splits/Pack_list.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/splits/Pack_list.java index a27517de8..5c81350cf 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/splits/Pack_list.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/splits/Pack_list.java @@ -13,3 +13,13 @@ 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.addons.bldrs.exports.packs.splits; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.packs.*; +import gplx.xowa.wikis.data.*; +import gplx.xowa.addons.bldrs.centrals.dbs.datas.imports.*; +import gplx.xowa.addons.bldrs.exports.splits.*; import gplx.xowa.addons.bldrs.exports.splits.mgrs.*; +class Pack_list { + private final List_adp list = List_adp_.New(); + public int Len() {return list.Len();} + public Pack_itm Get_at(int i) {return (Pack_itm)list.Get_at(i);} + public void Add(Pack_itm itm) {list.Add(itm);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/splits/Pack_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/splits/Pack_mgr.java index a27517de8..d7c0fafe4 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/splits/Pack_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/splits/Pack_mgr.java @@ -13,3 +13,132 @@ 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.addons.bldrs.exports.packs.splits; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.packs.*; +import gplx.core.progs.*; import gplx.core.ios.zips.*; import gplx.core.ios.streams.*; import gplx.core.security.*; +import gplx.dbs.*; +import gplx.xowa.wikis.data.*; +import gplx.xowa.addons.bldrs.centrals.dbs.*; import gplx.xowa.addons.bldrs.centrals.dbs.datas.imports.*; import gplx.xowa.addons.bldrs.centrals.steps.*; +import gplx.xowa.addons.bldrs.exports.splits.mgrs.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +// NOTE: used for experimental pack / split approach (html,file,search in one db) +class Pack_mgr { + public void Exec(Xowe_wiki wiki, long pack_size_max) { + // init + Io_url wiki_dir = wiki.Fsys_mgr().Root_dir(); + wiki.Init_assert(); + String wiki_date = wiki.Props().Modified_latest().XtoStr_fmt("yyyyMMdd"); + + // build pack list + Pack_list pack_list = Make_list(wiki, wiki_date, pack_size_max); + + // get import_tbl + byte[] wiki_abrv = wiki.Domain_itm().Abrv_xo(); + Xobc_data_db bc_db = Xobc_data_db.New(wiki.App().Fsys_mgr()); + Db_conn bc_conn = bc_db.Conn(); + bc_db.Delete_by_import(wiki_abrv, wiki_date); + bc_conn.Txn_bgn("xobc_import_insert"); + + // loop packs and (a) zip raws; (b) create entry + Hash_algo hash_algo = Hash_algo_.New__md5(); + Bry_bfr tmp_bfr = Bry_bfr_.New(); + int len = pack_list.Len(); + int task_id = bc_conn.Sys_mgr().Autonum_next("task_regy.task_id"); + bc_db.Tbl__task_regy().Insert(task_id, task_id, len, wiki.Domain_str(), wiki.Domain_str()); + for (int i = 0; i < len; ++i) + Make_pack(wiki, wiki_dir, wiki_abrv, wiki_date, bc_db, hash_algo, tmp_bfr, pack_list.Get_at(i), task_id); + bc_conn.Txn_end(); + } + private static Pack_list Make_list(Xow_wiki wiki, String wiki_date, long pack_size_max) { + // init; delete dir; + String wiki_abrv = String_.new_a7(wiki.Domain_itm().Abrv_wm()); + Io_url wiki_dir = wiki.Fsys_mgr().Root_dir(); + Io_url pack_root = wiki_dir.GenSubDir_nest("tmp", "pack"); + Io_url split_root = wiki_dir.GenSubDir_nest("tmp", "split"); + Io_mgr.Instance.DeleteDirDeep(pack_root); + Io_mgr.Instance.CreateDirIfAbsent(pack_root); + Io_url[] fils = Io_mgr.Instance.QueryDir_fils(split_root); + List_adp list = List_adp_.New(); + + // delete pack dir + Pack_list rv = new Pack_list(); + long pack_size_cur = 0; + int len = fils.length; + for (int i = 0; i < len; ++i) { + Io_url fil = fils[i]; + long fil_size = Io_mgr.Instance.QueryFil(fil).Size(); + if (Split_file_tid_.To_tid(fil) == Split_file_tid_.Tid__temp) continue; // ignore temp file + list.Add(fil); + + // calc pack size + long pack_size_new = pack_size_cur + fil_size; + if ( pack_size_new > pack_size_max + || i == len - 1) { + int pack_idx = rv.Len(); + Io_url fil_1st = (Io_url)list.Get_at(0); + int ns_id = Split_file_tid_.Get_ns_by_url(fil_1st); + Io_url zip_url = pack_root.GenSubFil(Split_file_tid_.Make_file_name(wiki_abrv, wiki_date, rv.Len(), ns_id, ".zip")); + Pack_itm itm = new Pack_itm(pack_idx, Xobc_import_type.Tid__pack, zip_url, (Io_url[])list.To_ary_and_clear(Io_url.class)); + rv.Add(itm); + pack_size_cur = 0; + } + else + pack_size_cur = pack_size_new; + } + return rv; + } + private static void Make_pack(Xowe_wiki wiki, Io_url wiki_dir, byte[] wiki_abrv, String wiki_date, Xobc_data_db bc_db, Hash_algo hash_algo, Bry_bfr tmp_bfr, Pack_itm itm, int task_id) { + // hash raws + Io_url zip_url = itm.Zip_url(); + Gfo_log_.Instance.Prog("hashing raw: " + zip_url.NameAndExt()); + Io_url md5_url = wiki_dir.GenSubFil(zip_url.NameOnly() + ".md5"); + long raw_size = 0; + Io_url[] raw_urls = itm.Raw_urls(); + int raw_urls_len = raw_urls.length; + for (int i = 0; i < raw_urls_len; ++i) { + Io_url raw_url = raw_urls[i]; + IoStream raw_stream = Io_mgr.Instance.OpenStreamRead(raw_url); + byte[] raw_md5 = null; + try {raw_md5 = hash_algo.Hash_stream_as_bry(Gfo_prog_ui_.Noop, raw_stream);} + finally {raw_stream.Rls();} + tmp_bfr.Add(raw_md5).Add_byte_space().Add_byte(Byte_ascii.Star).Add_str_a7(raw_url.NameAndExt()).Add_byte_nl(); + raw_size += raw_stream.Len(); + } + Io_mgr.Instance.SaveFilBfr(md5_url, tmp_bfr); + + // calc prog_size_end + long prog_size_end = 0; int prog_count_end =0; + for (int i = 0; i < raw_urls_len; ++i) { + Io_url raw_url = raw_urls[i]; + if (Split_file_tid_.To_tid(raw_url) != Split_file_tid_.Tid__data) continue; + Db_conn src_conn = Db_conn_bldr.Instance.Get_or_noop(raw_url); + Wkr_stats_tbl tbl = new Wkr_stats_tbl(src_conn); + Wkr_stats_itm stat = tbl.Select_all__summary(); + prog_count_end += stat.Count; + prog_size_end += stat.Size; + } + + // compress raws + Gfo_log_.Instance.Prog("compressing raw"); + Io_zip_compress_cmd__jre zip_cmd = new Io_zip_compress_cmd__jre(); + raw_urls = (Io_url[])Array_.Insert(raw_urls, new Io_url[] {md5_url}, 0); // add ".md5" to .zip + zip_cmd.Exec_hook(Gfo_prog_ui_.Noop, raw_urls, zip_url, "", 0, 0); + + // hash zip + Gfo_log_.Instance.Prog("hashing zip"); + IoStream zip_stream = Io_mgr.Instance.OpenStreamRead(zip_url); + byte[] zip_md5 = null; + try {zip_md5 = hash_algo.Hash_stream_as_bry(Gfo_prog_ui_.Noop, zip_stream);} + finally {zip_stream.Rls();} + long zip_len = Io_mgr.Instance.QueryFil(zip_url).Size(); + + // generate import + Gfo_log_.Instance.Prog("generating tasks"); + int step_id = bc_db.Conn().Sys_mgr().Autonum_next("step_regy.step_id"); + int sm_id = bc_db.Conn().Sys_mgr().Autonum_next("step_map.sm_id"); + bc_db.Tbl__step_regy().Insert(step_id, Xobc_step_itm.Type__wiki_import); + bc_db.Tbl__step_map().Insert(sm_id, task_id, step_id, itm.Idx()); + bc_db.Tbl__import_step().Insert(step_id, gplx.xowa.addons.bldrs.centrals.dbs.datas.Xobc_host_regy_tbl.Host_id__archive_org, wiki_abrv, wiki_date, zip_url.NameAndExt(), itm.Type(), Xobc_zip_type.Type__zip, zip_md5, zip_len, raw_size, prog_size_end, prog_count_end); + + // cleanup + Io_mgr.Instance.DeleteFil(md5_url); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/splits/Pack_split_bldr_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/splits/Pack_split_bldr_cmd.java index a27517de8..622818945 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/splits/Pack_split_bldr_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/packs/splits/Pack_split_bldr_cmd.java @@ -13,3 +13,23 @@ 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.addons.bldrs.exports.packs.splits; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.packs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Pack_split_bldr_cmd extends Xob_cmd__base { + private long pack_size_max = 30 * Io_mgr.Len_mb; + public Pack_split_bldr_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + new Pack_mgr().Exec(wiki, pack_size_max); + } + + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__pack_size_max_)) pack_size_max = m.ReadLong("v"); + return super.Invk (ctx, ikey, k, m); + } + private static final String Invk__pack_size_max_ = "pack_size_max_"; + + public static final String BLDR_CMD_KEY = "bldr.export.pack.split"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Pack_split_bldr_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Pack_split_bldr_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/Split_bldr_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/Split_bldr_cmd.java index a27517de8..16f64314a 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/Split_bldr_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/Split_bldr_cmd.java @@ -13,3 +13,24 @@ 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.addons.bldrs.exports.splits; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.addons.bldrs.exports.splits.mgrs.*; +public class Split_bldr_cmd extends Xob_cmd__base { + private final Split_cfg cfg = new Split_cfg(); + public Split_bldr_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + wiki.Init_assert(); + new Split_mgr().Exec(wiki, cfg); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__cfg)) return cfg; + return super.Invk (ctx, ikey, k, m); + } + private static final String Invk__cfg = "cfg"; + + public static final String BLDR_CMD_KEY = "bldr.export.split"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Split_bldr_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Split_bldr_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/Split_ctx.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/Split_ctx.java index a27517de8..490869cc0 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/Split_ctx.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/Split_ctx.java @@ -13,3 +13,64 @@ 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.addons.bldrs.exports.splits; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; +import gplx.xowa.addons.bldrs.exports.splits.mgrs.*; import gplx.xowa.addons.bldrs.exports.splits.metas.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +public class Split_ctx { + private int trg_idx = -1; + private final Split_wkr[] wkrs; + private boolean trg_make = true; + private long trg_max = 32 * Io_mgr.Len_mb; + public Split_ctx(Split_cfg cfg, Xow_wiki wiki, Split_wkr[] wkrs, Split_ns_itm[] ns_itms, Db_conn wkr_conn) { + this.cfg = cfg; this.wiki = wiki; this.wkrs = wkrs; + this.trg_max = cfg.Trg_max(); + this.ns_itms = ns_itms; this.wkr_conn = wkr_conn; + this.rslt_mgr = new Split_rslt_mgr(wkr_conn); + this.html_size_calc = new Split_db_size_calc(cfg.Html().Db_max(), cfg.Html().Db_idx()); + this.file_size_calc = new Split_db_size_calc(cfg.File().Db_max(), cfg.File().Db_idx()); + } + public Split_cfg Cfg() {return cfg;} private final Split_cfg cfg; + public Xow_wiki Wiki() {return wiki;} private final Xow_wiki wiki; + public Split_ns_itm[] Ns_itms() {return ns_itms;} private final Split_ns_itm[] ns_itms; + public Db_conn Wkr_conn() {return wkr_conn;} private final Db_conn wkr_conn; + public Db_conn Trg_conn() {return trg_conn;} private Db_conn trg_conn; + public int Trg_ns() {return trg_ns;} private int trg_ns; public void Trg_ns_(int v) {this.trg_ns = v;} + public Split_page_mgr Page_mgr() {return page_mgr;} private final Split_page_mgr page_mgr = new Split_page_mgr(); + public Split_rslt_mgr Rslt_mgr() {return rslt_mgr;} private final Split_rslt_mgr rslt_mgr; + public Split_db_size_calc Html_size_calc() {return html_size_calc;} private final Split_db_size_calc html_size_calc; + public Split_db_size_calc File_size_calc() {return file_size_calc;} private final Split_db_size_calc file_size_calc; + + public void Trg_db__completed() {trg_make = true;} + public void Trg_db__assert(int ns_id) { + if (rslt_mgr.Db_size() < trg_max && !trg_make) return; + trg_make = false; + + // term trg_conn + if (trg_conn != null) Trg_db__term(); + + // init trg_con + Io_url trg_url = this.Trg_db__make(ns_id); + for (Split_wkr wkr : wkrs) + wkr.Split__trg__nth__new(this, trg_conn); + rslt_mgr.On__nth__new(trg_idx, trg_url, ns_id); + trg_conn.Txn_bgn("split"); + } + public Io_url Trg_db__make(int ns_id) { + // create new trg_conn + String trg_name = Split_file_tid_.Make_file_name(String_.new_u8(wiki.Domain_itm().Abrv_wm()), wiki.Props().Modified_latest().XtoStr_fmt("yyyy.MM"), ++trg_idx, ns_id, ".xowa"); + Io_url trg_url = wiki.Fsys_mgr().Root_dir().GenSubFil_nest("tmp", "split", trg_name); + this.trg_conn = Db_conn_bldr.Instance.Get_or_autocreate(true, trg_url); + return trg_url; + } + public void Trg_db__null() {trg_conn = null;} // null conn, else Trg_db_assert will try to close txn + public void Term() { + Trg_db__term(); + rslt_mgr.Term(); + } + private void Trg_db__term() { + rslt_mgr.On__nth__rls(trg_conn); + trg_conn.Txn_end(); + for (Split_wkr wkr : wkrs) + wkr.Split__trg__nth__rls(this, trg_conn); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/Split_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/Split_mgr.java index a27517de8..59ed1d306 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/Split_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/Split_mgr.java @@ -13,3 +13,68 @@ 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.addons.bldrs.exports.splits; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.bldrs.exports.splits.mgrs.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +class Split_mgr { + public void Exec(Xow_wiki wiki, Split_cfg cfg) { + // init + Split_ns_itm[] ns_itms = cfg.Ns_itms(); + Split_wkr[] wkrs = new Split_wkr[] + { new gplx.xowa.addons.bldrs.exports.splits.srchs.Split_wkr__srch() + , new gplx.xowa.addons.bldrs.exports.splits.htmls.Split_wkr__html() + , new gplx.xowa.addons.bldrs.exports.splits.pages.Split_wkr__page() // NOTE: page needs to follow html b/c of trg_db_id + , new gplx.xowa.addons.bldrs.exports.splits.files.Split_wkr__file() + , new gplx.xowa.addons.bldrs.exports.splits.rndms.Split_wkr__rndm() + }; + + // init ctx + Io_url split_root = wiki.Fsys_mgr().Root_dir().GenSubDir_nest("tmp", "split"); + Io_mgr.Instance.DeleteDirDeep(split_root); + Io_mgr.Instance.CreateDirIfAbsent(split_root); + Db_conn wkr_conn = Db_conn_bldr.Instance.Get_or_autocreate(true, split_root.GenSubFil("xowa.split.sqlite3")); + Split_ctx ctx = new Split_ctx(cfg, wiki, wkrs, ns_itms, wkr_conn); + ctx.Trg_db__make(-1); + new Split_mgr_init().Init(ctx, ctx.Wkr_conn(), wiki.Data__core_mgr().Tbl__page().Conn()); + for (Split_wkr wkr : wkrs) { + wkr.Split__init(ctx, wiki, wkr_conn); + wkr.Split__trg__1st__new(ctx, ctx.Trg_conn()); + } + ctx.Trg_db__null(); + + // split by ns + List_adp page_list = List_adp_.New(); + Split_page_loader loader = new Split_page_loader(wiki, cfg.Loader_rows()); + for (Split_ns_itm ns_itm : ns_itms) { + int ns_id = ns_itm.Ns_id(); + loader.Init_ns(ns_id); + ctx.Trg_ns_(ns_id); + while (true) { + ctx.Trg_db__assert(ns_id); // new db will be needed when moving between ns; EX: ns.000 goes into one db; ns.004 goes into another + boolean reading = loader.Load_pages(ctx, page_list, wkrs, ns_id); + Split_pages(ctx, page_list, wkrs, ns_id); + if (!reading) {// no more rows; ns is done; stop loop and go to next ns; + ctx.Trg_db__completed(); + break; + } + } + } + + // cleanup + loader.Rls(); + ctx.Term(); + for (Split_wkr wkr : wkrs) + wkr.Split__term(ctx); + } + private void Split_pages(Split_ctx ctx, List_adp page_list, Split_wkr[] wkrs, int ns_id) { + Split_rslt_mgr rslt_mgr = ctx.Rslt_mgr(); + int len = page_list.Len(); + for (int i = 0; i < len; ++i) { + ctx.Trg_db__assert(ns_id); // new db may be needed; EX: 10,000 will be read, and 1st 100 needs 1 db; next 100 needs another db + Xowd_page_itm page = (Xowd_page_itm)page_list.Get_at(i); + int page_id = page.Id(); + for (Split_wkr wkr : wkrs) + wkr.Split__exec(ctx, rslt_mgr, page, page_id); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/Split_wkr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/Split_wkr.java index a27517de8..b1085c3d0 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/Split_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/Split_wkr.java @@ -13,3 +13,14 @@ 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.addons.bldrs.exports.splits; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +public interface Split_wkr { + void Split__init (Split_ctx ctx, Xow_wiki wiki, Db_conn wkr_conn); + void Split__pages_loaded (Split_ctx ctx, int ns_id, int score_bgn, int score_end); + void Split__trg__1st__new (Split_ctx ctx, Db_conn trg_conn); + void Split__trg__nth__new (Split_ctx ctx, Db_conn trg_conn); + void Split__trg__nth__rls (Split_ctx ctx, Db_conn trg_conn); + void Split__exec (Split_ctx ctx, Split_rslt_mgr rslt_mgr, Xowd_page_itm page, int page_id); + void Split__term (Split_ctx ctx); +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/archives/Reindex_html_dbs_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/archives/Reindex_html_dbs_cmd.java index a27517de8..dc8cfc140 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/archives/Reindex_html_dbs_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/archives/Reindex_html_dbs_cmd.java @@ -13,3 +13,164 @@ 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.addons.bldrs.exports.splits.archives; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.dbs.metas.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.htmls.core.dbs.*; +import gplx.xowa.addons.bldrs.exports.splits.htmls.*; +class Reindex_html_dbs_cmd { + private Db_conn core_conn; + private Xowd_page_tbl page_tbl; + private Xoh_src_tbl_mgr src_tbl_mgr; + private Xoh_trg_tbl_mgr trg_tbl_mgr; + private String tbl_page, fld_page_id, fld_page_ns, fld_page_len, fld_page_score, fld_page_html_db_id; + private final String Idx_name = "page__repack"; + public void Exec(Xowe_wiki wiki, long trg_db_size_max) { + // init + wiki.Init_assert(); + Xow_db_mgr db_mgr = wiki.Data__core_mgr(); + this.page_tbl = db_mgr.Tbl__page(); + this.core_conn = page_tbl.Conn(); + this.src_tbl_mgr = new Xoh_src_tbl_mgr(wiki); + this.trg_tbl_mgr = new Xoh_trg_tbl_mgr(wiki); + this.tbl_page = page_tbl.Tbl_name(); + this.fld_page_id = page_tbl.Fld_page_id(); + this.fld_page_ns = page_tbl.Fld_page_ns(); + this.fld_page_len = page_tbl.Fld_page_len(); + this.fld_page_score = page_tbl.Fld_page_score(); + this.fld_page_html_db_id = page_tbl.Fld_html_db_id(); + + Create_repack_idx_on_page(); + Create_repack_tbl(); + Insert_repack_rows(); + Move_html_data(trg_db_size_max); + Cleanup_dbs(wiki); + core_conn.Meta_idx_delete(Idx_name); + core_conn.Meta_tbl_delete("repack"); + core_conn.Env_vacuum(); + } + private void Create_repack_idx_on_page() { + // add idx: page (page_ns DESC, page_score DESC, page_len DESC) + if (!core_conn.Meta_idx_exists(Idx_name)) { + core_conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_name(tbl_page, Idx_name + , Dbmeta_idx_fld.Dsc(fld_page_ns) + , Dbmeta_idx_fld.Dsc(fld_page_score) + , Dbmeta_idx_fld.Dsc(fld_page_len))); + } + } + private void Create_repack_tbl() { + core_conn.Meta_tbl_remake(Dbmeta_tbl_itm.New("repack" + , Dbmeta_fld_itm.new_int("sort_idx").Autonum_y_().Primary_y_() + , Dbmeta_fld_itm.new_int("page_id") + , Dbmeta_fld_itm.new_int("page_ns") + , Dbmeta_fld_itm.new_int("page_score") + , Dbmeta_fld_itm.new_int("page_len") + , Dbmeta_fld_itm.new_int("src_db") + )); + } + private void Insert_repack_rows() { + Gfo_usr_dlg_.Instance.Prog_many("", "", "inserting rows into repack table"); + Db_qry__select_cmd select_qry = (Db_qry__select_cmd)Db_qry_.select_ + ( tbl_page, fld_page_id, fld_page_ns, fld_page_len, fld_page_score, fld_page_html_db_id) + .Order_(fld_page_ns, Bool_.Y) + .Order_(fld_page_score, Bool_.N) + .Order_(fld_page_len, Bool_.N) + .Where_(Db_crt_.New_eq_not(fld_page_html_db_id, -1)); + Db_qry_.insert_("repack").Cols_("page_id", "page_ns", "page_len", "page_score", "src_db") + .Select_(select_qry) + .Exec_qry(core_conn); + } + private void Move_html_data(long trg_db_size_max) { + // read rows and move + Db_rdr rdr = core_conn.Stmt_select_order("repack", String_.Ary("sort_idx", "page_id", "page_ns", "page_len", "src_db"), String_.Ary_empty, "sort_idx").Exec_select__rls_auto(); + Db_stmt stmt_update = core_conn.Stmt_update(tbl_page, String_.Ary(fld_page_id), fld_page_html_db_id); + Xoh_page_tbl_itm trg_html_tbl = null; + try { + Xowd_html_row src_html_row = new Xowd_html_row(); + long trg_db_size = 0; + int ns_cur = -1, part_id = 0; + int trg_db = -1; + core_conn.Txn_bgn("update page"); + while (rdr.Move_next()) { + // check if ns changed + int page_ns = rdr.Read_int("page_ns"); + boolean ns_changed = false; + if (ns_cur != page_ns ) { // ns changed + ns_cur = page_ns; + part_id = 0; // reset part_id; note that 1st part will be base_1 + ns_changed = true; + } + + Xoh_page_tbl_itm src_html_tbl = src_tbl_mgr.Get_or_load(rdr.Read_int("src_db")); + int page_id = rdr.Read_int("page_id"); + if (!src_html_tbl.Html_tbl().Select_as_row(src_html_row, page_id)) throw Err_.new_wo_type("could not find html", "page_id", page_id); + + // check if new file needed + int page_size = src_html_row.Body().length; + trg_db_size += page_size; + if ( trg_html_tbl == null // will be null for 1st pass + || ns_changed // ns_changed + || trg_db_size > trg_db_size_max // file filled + ) { + if (trg_html_tbl != null) { // close trg_db if open + trg_html_tbl.Html_tbl().Insert_end(); + trg_html_tbl.Rls(); + } + trg_html_tbl = trg_tbl_mgr.Make_new(ns_cur, ++part_id); + trg_html_tbl.Html_tbl().Insert_bgn(); + trg_db = trg_html_tbl.Db_id(); + trg_db_size = page_size; + } + + // move row + trg_html_tbl.Html_tbl().Insert(src_html_row.Page_id(), src_html_row.Head_flag(), src_html_row.Body_flag() + , src_html_row.Display_ttl(), src_html_row.Content_sub(), src_html_row.Sidebar_div() + , src_html_row.Body()); + + // update page_html_db_id + stmt_update.Clear().Val_int(fld_page_html_db_id, trg_db).Crt_int(fld_page_id, page_id).Exec_update(); + } + } finally { + rdr.Rls(); + if (trg_html_tbl != null) { + trg_html_tbl.Html_tbl().Insert_end(); + trg_html_tbl.Rls(); + } + core_conn.Txn_end(); + stmt_update.Rls(); + src_tbl_mgr.Cleanup(); + } + } + private void Cleanup_dbs(Xowe_wiki wiki) { + // delete old dbs + wiki.Data__core_mgr().Rls(); + String repack_suffix = Xoh_trg_tbl_mgr.Repack_suffix; + Db_stmt delete_stmt = core_conn.Stmt_delete(Xowd_xowa_db_tbl.TBL_NAME, Xowd_xowa_db_tbl.Fld_id); + Db_rdr rdr = core_conn.Stmt_select(Xowd_xowa_db_tbl.TBL_NAME, String_.Ary(Xowd_xowa_db_tbl.Fld_id, Xowd_xowa_db_tbl.Fld_type, Xowd_xowa_db_tbl.Fld_url)).Exec_select__rls_auto(); + while (rdr.Move_next()) { + byte file_tid = rdr.Read_byte(Xowd_xowa_db_tbl.Fld_type); + if (file_tid != Xow_db_file_.Tid__html_data) continue; + String file_url = rdr.Read_str(Xowd_xowa_db_tbl.Fld_url); + if (String_.Has(file_url, repack_suffix)) continue; + delete_stmt.Clear().Crt_int(Xowd_xowa_db_tbl.Fld_id, rdr.Read_int(Xowd_xowa_db_tbl.Fld_id)).Exec_delete(); + Io_mgr.Instance.DeleteFil(wiki.Fsys_mgr().Root_dir().GenSubFil(file_url)); + } + rdr.Rls(); + delete_stmt.Rls(); + + // update new dbs + Db_stmt update_stmt = core_conn.Stmt_update(Xowd_xowa_db_tbl.TBL_NAME, String_.Ary(Xowd_xowa_db_tbl.Fld_id), Xowd_xowa_db_tbl.Fld_url); + rdr = core_conn.Stmt_select(Xowd_xowa_db_tbl.TBL_NAME, String_.Ary(Xowd_xowa_db_tbl.Fld_id, Xowd_xowa_db_tbl.Fld_type, Xowd_xowa_db_tbl.Fld_url)).Exec_select__rls_auto(); + while (rdr.Move_next()) { + byte file_tid = rdr.Read_byte(Xowd_xowa_db_tbl.Fld_type); + if (file_tid != Xow_db_file_.Tid__html_data) continue; + Io_url old_url = wiki.Fsys_mgr().Root_dir().GenSubFil(rdr.Read_str(Xowd_xowa_db_tbl.Fld_url)); + String old_raw = old_url.Raw(); + Io_url new_url = Io_url_.new_fil_(String_.Replace(old_raw, repack_suffix, "")); + if (!String_.Has(old_raw, repack_suffix)) throw Err_.new_wo_type("html db should be repack", "db_name", old_raw); + update_stmt.Clear().Val_str(Xowd_xowa_db_tbl.Fld_url, new_url.NameAndExt()).Crt_int(Xowd_xowa_db_tbl.Fld_id, rdr.Read_int(Xowd_xowa_db_tbl.Fld_id)).Exec_update(); + Io_mgr.Instance.MoveFil(old_url, new_url); + } + rdr.Rls(); + update_stmt.Rls(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Bin_meta_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Bin_meta_itm.java index a27517de8..8cea04595 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Bin_meta_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Bin_meta_itm.java @@ -13,3 +13,13 @@ 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.addons.bldrs.exports.splits.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +class Bin_meta_itm { + public Bin_meta_itm(byte bin_type, int bin_owner_id, int bin_id, int bin_db_id) { + this.bin_type = bin_type; this.bin_owner_id = bin_owner_id; this.bin_id = bin_id; this.bin_db_id = bin_db_id; + } + public byte Bin_type() {return bin_type;} private final byte bin_type; + public int Bin_owner_id() {return bin_owner_id;} private final int bin_owner_id; + public int Bin_id() {return bin_id;} private final int bin_id; + public int Bin_db_id() {return bin_db_id;} private final int bin_db_id; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_init__file.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_init__file.java index a27517de8..b693b690c 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_init__file.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_init__file.java @@ -13,3 +13,114 @@ 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.addons.bldrs.exports.splits.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; +import gplx.xowa.addons.bldrs.exports.splits.mgrs.*; +class Split_init__file { + public void Exec(Split_ctx ctx, Xow_wiki wiki, Db_conn wkr_conn, Db_conn atr_conn) { + if (!(ctx.Cfg().Force_rebuild() || !wkr_conn.Meta_tbl_exists("fsdb_img_regy"))) return; + // get min page for each score + Gfo_log_.Instance.Prog("creating fsdb_img_regy"); + wkr_conn.Meta_tbl_remake(Dbmeta_tbl_itm.New("fsdb_img_regy" + , Dbmeta_fld_itm.new_int("img_uid").Primary_y_().Autonum_y_() + , Dbmeta_fld_itm.new_byte("img_type") + , Dbmeta_fld_itm.new_int("fil_id") + , Dbmeta_fld_itm.new_int("thm_id") + , Dbmeta_fld_itm.new_int("bin_db_id") + , Dbmeta_fld_itm.new_int("page_uid") + , Dbmeta_fld_itm.new_int("page_ns") + , Dbmeta_fld_itm.new_int("page_score") + , Dbmeta_fld_itm.new_int("page_id") + )); + Db_attach_mgr attach_mgr = new Db_attach_mgr(wkr_conn + , new Db_attach_itm("page_db", wiki.Data__core_mgr().Tbl__page().Conn()) + , new Db_attach_itm("pfm_db", gplx.xowa.bldrs.Xob_db_file.New__page_file_map(wiki).Conn()) + ); + attach_mgr.Exec_sql(String_.Concat_lines_nl // ANSI.Y + ( "INSERT INTO fsdb_img_regy (img_type, fil_id, thm_id, bin_db_id, page_uid, page_ns, page_score, page_id)" + , "SELECT CASE WHEN pfm.thm_id = -1 THEN 0 ELSE 1 END, pfm.fil_id, pfm.thm_id, -1, Min(pr.page_uid), -1, -1, -1" + , "FROM page_file_map pfm" + , " JOIN page_regy pr ON pfm.page_id = pr.page_id" + , "GROUP BY CASE WHEN pfm.thm_id = -1 THEN 0 ELSE 1 END, pfm.fil_id, pfm.thm_id" + , "ORDER BY Min(pr.page_uid)" + )); + + // update page attributes; create idx + Split_mgr_init.Update_page_cols(wkr_conn, "fsdb_img_regy"); + wkr_conn.Meta_idx_create("fsdb_img_regy", "fil_id", "fil_id"); + wkr_conn.Meta_idx_create("fsdb_img_regy", "page", "page_score", "page_ns", "img_type"); + + // update bin_db_id + wkr_conn.Meta_idx_create("fsdb_img_regy", "img", "img_type", "fil_id", "thm_id"); + Gfo_log_.Instance.Prog("updating bin_db_id"); + attach_mgr.Conn_links_(new Db_attach_itm("fsdb_db", atr_conn)); + + // update bin_db_id.fil + attach_mgr.Exec_sql(String_.Concat_lines_nl // ANSI.Y + ( "UPDATE fsdb_img_regy" + , "SET bin_db_id = Coalesce((SELECT f.fil_bin_db_id FROM fsdb_fil f WHERE fsdb_img_regy.fil_id = f.fil_id AND fsdb_img_regy.thm_id = -1), bin_db_id)" + , "WHERE img_type = 0" + )); + + // update bin_db_id.thm + attach_mgr.Exec_sql(String_.Concat_lines_nl // ANSI.Y + ( "UPDATE fsdb_img_regy" + , "SET bin_db_id = Coalesce((SELECT t.thm_bin_db_id FROM fsdb_thm t WHERE fsdb_img_regy.fil_id = t.thm_owner_id AND fsdb_img_regy.thm_id = t.thm_id), bin_db_id)" + , "WHERE img_type = 1" + )); + + // promote fil if thm shows up earlier; i.e.: if thm_id=11 is score=99 and fil_id=10 is score=80, move fil_id to 99 + Gfo_log_.Instance.Prog("creating fsdb_fil_min"); + wkr_conn.Meta_tbl_remake(Dbmeta_tbl_itm.New("fsdb_fil_min" + , Dbmeta_fld_itm.new_byte("img_uid") + , Dbmeta_fld_itm.new_int("fil_id") + , Dbmeta_fld_itm.new_int("thm_id") + )); + + // add fils via thms; for thms with multiple fils, use Min(img_uid) + wkr_conn.Exec_sql(String_.Concat_lines_nl // ANSI.Y + ( "INSERT INTO fsdb_fil_min (img_uid, fil_id, thm_id)" + // get fils directly + , "SELECT f.img_uid, f.fil_id, f.thm_id" + , "FROM fsdb_img_regy f" + , "WHERE f.img_type = 0" + , "UNION" + // get fils from thms + , "SELECT Min(f.img_uid), f.fil_id, f.thm_id" + , "FROM fsdb_img_regy t" + , " JOIN fsdb_img_regy f ON t.fil_id = f.fil_id" + , "WHERE t.img_type = 1" + , "GROUP BY f.fil_id, f.thm_id" + )); + + // dupes may still exist, so do one more group by + Gfo_log_.Instance.Prog("creating fsdb_fil_min_unique"); + wkr_conn.Meta_tbl_remake(Dbmeta_tbl_itm.New("fsdb_fil_min_unique" + , Dbmeta_fld_itm.new_byte("img_uid") + , Dbmeta_fld_itm.new_int("fil_id") + )); + wkr_conn.Exec_sql(String_.Concat_lines_nl // ANSI.Y + ( "INSERT INTO fsdb_fil_min_unique (img_uid, fil_id)" + , "SELECT Min(f.img_uid), f.fil_id" + , "FROM fsdb_fil_min f" + , "GROUP BY f.fil_id" + )); + + // create fsdb_fil_regy + Gfo_log_.Instance.Prog("creating fsdb_fil_regy"); + wkr_conn.Meta_tbl_remake(Dbmeta_tbl_itm.New("fsdb_fil_regy" + , Dbmeta_fld_itm.new_int("img_uid").Primary_y_() + , Dbmeta_fld_itm.new_int("fil_id") + , Dbmeta_fld_itm.new_int("page_ns") + , Dbmeta_fld_itm.new_int("page_score") + , Dbmeta_fld_itm.new_int("page_id") + )); + wkr_conn.Exec_sql(String_.Concat_lines_nl // ANSI.Y + ( "INSERT INTO fsdb_fil_regy (img_uid, fil_id, page_ns, page_score, page_id)" + , "SELECT fmu.img_uid, fmu.fil_id, fir.page_ns, fir.page_score, fir.page_id" + , "FROM fsdb_fil_min_unique fmu" + , " JOIN fsdb_img_regy fir ON fmu.img_uid = fir.img_uid" + )); + + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_meta_wkr__bin.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_meta_wkr__bin.java index a27517de8..f290ab2be 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_meta_wkr__bin.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_meta_wkr__bin.java @@ -13,3 +13,94 @@ 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.addons.bldrs.exports.splits.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; +import gplx.fsdb.meta.*; import gplx.fsdb.data.*; +import gplx.xowa.addons.bldrs.exports.splits.metas.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +class Split_meta_wkr__bin extends Split_meta_wkr_base { + private final Fsm_bin_mgr bin_mgr; + private final Split_rslt_wkr__bin rslt_wkr = new Split_rslt_wkr__bin(); + private Fsd_bin_tbl tbl; private Db_stmt stmt; + public Split_meta_wkr__bin(Split_ctx ctx, Fsm_bin_mgr bin_mgr) { + this.bin_mgr = bin_mgr; + ctx.Rslt_mgr().Reg_wkr(rslt_wkr); + } + @Override public byte Tid() {return Split_page_list_type_.Tid__fsdb_bin;} + @Override public void On_nth_new(Split_ctx ctx, Db_conn trg_conn) { + this.tbl = new Fsd_bin_tbl(trg_conn, Bool_.N); + Dbmeta_fld_list trg_flds = Make_flds_for_split(tbl.Flds()); + trg_conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl.Tbl_name(), trg_flds)); + this.stmt = trg_conn.Stmt_insert(tbl.Tbl_name(), trg_flds); + } + @Override public void On_nth_rls(Split_ctx ctx, Db_conn trg_conn) { + this.stmt = Db_stmt_.Rls(stmt); + trg_conn.Meta_idx_create(tbl.Tbl_name(), "merge", "trg_db_id", "bin_owner_id"); + trg_conn.Meta_idx_create(tbl.Tbl_name(), "blob" , "trg_db_id", "blob_len", "bin_owner_id"); + } + @Override protected String Load_sql(Db_attach_mgr attach_mgr, int ns_id, int score_bgn, int score_end) { + return String_.Concat_lines_nl + ( "SELECT img_type" + , ", -1 AS owner_id" + , ", fil_id AS bin_id" + , ", bin_db_id" + , ", page_id" + , "FROM fsdb_img_regy" + , "WHERE page_score >= {0}" + , "AND page_score < {1}" + , "AND page_ns = {2}" + , "AND img_type = 0" + , "UNION" + , "SELECT img_type" + , ", fil_id AS owner_id" + , ", thm_id AS bin_id" + , ", bin_db_id" + , ", page_id" + , "FROM fsdb_img_regy" + , "WHERE page_score >= {0}" + , "AND page_score < {1}" + , "AND page_ns = {2}" + , "AND img_type = 1" + , "ORDER BY page_id" + ); + } + @Override protected Object Load_itm(Db_rdr rdr) { + return new Bin_meta_itm(rdr.Read_byte("img_type"), rdr.Read_int("owner_id"), rdr.Read_int("bin_id"), rdr.Read_int("bin_db_id")); + } + @Override protected void Save_itm(Split_ctx ctx, Split_rslt_mgr rslt_mgr, Object itm_obj) { + // load data + Bin_meta_itm itm = (Bin_meta_itm)itm_obj; + Fsm_bin_fil src_db = bin_mgr.Dbs__get_at(itm.Bin_db_id()); + int bin_id = itm.Bin_id(); + Fsd_bin_itm src_itm = src_db.Select_as_itm(bin_id); + byte[] bin_data = src_itm.Bin_data(); if (bin_data == null) bin_data = Bry_.Empty; // NOTE: bin_data can be NULL + int blob_len = bin_data.length; + String bin_data_url = src_itm.Bin_data_url(); + + // calc db_idx based on db_size + int db_row_size = Fsd_bin_itm.Db_row_size_fixed + blob_len + String_.Len(bin_data_url); + int trg_db_id = ctx.File_size_calc().Size_cur_add_(db_row_size); + + // do insert + stmt.Clear() + .Val_int ("bin_owner_id" , bin_id) + .Val_int ("trg_db_id" , trg_db_id) + .Val_int ("blob_len" , blob_len) + .Val_byte ("bin_owner_tid", src_itm.Bin_owner_tid()) + .Val_int ("bin_part_id" , src_itm.Bin_part_id()) + .Val_str ("bin_data_url" , bin_data_url) + .Val_bry ("bin_data" , bin_data) + .Exec_insert(); + rslt_wkr.On__nth__itm(db_row_size, bin_id); + } + private static Dbmeta_fld_list Make_flds_for_split(Dbmeta_fld_list flds) { + Dbmeta_fld_list rv = new Dbmeta_fld_list(); + rv.Add(flds.Get_by("bin_owner_id")); + rv.Add_int("trg_db_id"); + rv.Add_int("blob_len"); + rv.Add(flds.Get_by("bin_owner_tid")); + rv.Add(flds.Get_by("bin_part_id")); + rv.Add(flds.Get_by("bin_data_url")); + rv.Add(flds.Get_by("bin_data")); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_meta_wkr__fil.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_meta_wkr__fil.java index a27517de8..d50dfb676 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_meta_wkr__fil.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_meta_wkr__fil.java @@ -13,3 +13,44 @@ 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.addons.bldrs.exports.splits.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; +import gplx.fsdb.meta.*; import gplx.fsdb.data.*; +import gplx.xowa.addons.bldrs.exports.splits.metas.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +class Split_meta_wkr__fil extends Split_meta_wkr_base { + private final Split_rslt_wkr__fil rslt_wkr = new Split_rslt_wkr__fil(); + private final Db_conn atr_conn; + private Fsd_fil_tbl tbl; private Db_stmt stmt; + public Split_meta_wkr__fil(Split_ctx ctx, Db_conn atr_conn) { + this.atr_conn = atr_conn; + ctx.Rslt_mgr().Reg_wkr(rslt_wkr); + } + @Override public byte Tid() {return Split_page_list_type_.Tid__fsdb_fil;} + @Override public void On_nth_new(Split_ctx ctx, Db_conn trg_conn) { + this.tbl = new Fsd_fil_tbl(trg_conn, Bool_.N, Fsm_mnt_mgr.Mnt_idx_main); + tbl.Create_tbl(); + this.stmt = trg_conn.Stmt_insert(tbl.Tbl_name(), tbl.flds); + } + @Override public void On_nth_rls(Split_ctx ctx, Db_conn trg_conn) {this.stmt = Db_stmt_.Rls(stmt);} + @Override protected String Load_sql(Db_attach_mgr attach_mgr, int ns_id, int score_bgn, int score_end) { + attach_mgr.Conn_links_(new Db_attach_itm("atr_db", atr_conn)); + return String_.Concat_lines_nl + ( "SELECT f.fil_id, f.fil_owner_id, f.fil_xtn_id, f.fil_ext_id, f.fil_bin_db_id, f.fil_name, f.fil_size, f.fil_modified, f.fil_hash, fir.page_id" + , "FROM fsdb_fil f" + , " JOIN fsdb_fil_regy fir ON f.fil_id = fir.fil_id" + , "WHERE fir.page_score >= {0}" + , "AND fir.page_score < {1}" + , "AND fir.page_ns = {2}" + , "ORDER BY page_id" + ); + } + @Override protected Object Load_itm(Db_rdr rdr) { + return tbl.New_by_rdr(Fsm_mnt_mgr.Mnt_idx_main, rdr); + } + @Override protected void Save_itm(Split_ctx ctx, Split_rslt_mgr rslt_mgr, Object itm_obj) { + Fsd_fil_itm itm = (Fsd_fil_itm)itm_obj; + int fil_id = itm.Fil_id(); + tbl.Insert(stmt, fil_id, itm.Dir_id(), itm.Name(), itm.Xtn_id(), itm.Ext_id(), itm.Size(), ctx.File_size_calc().Idx(), itm.Modified_on(), itm.Hash_md5()); + rslt_wkr.On__nth__itm(itm.Db_row_size(), fil_id); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_meta_wkr__org.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_meta_wkr__org.java index a27517de8..6cb8b85c6 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_meta_wkr__org.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_meta_wkr__org.java @@ -13,3 +13,42 @@ 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.addons.bldrs.exports.splits.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; +import gplx.xowa.files.origs.*; +import gplx.xowa.addons.bldrs.exports.splits.metas.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +class Split_meta_wkr__org extends Split_meta_wkr_base { + private final Split_rslt_wkr__org rslt_wkr = new Split_rslt_wkr__org(); + private final Db_conn atr_conn; + private Xof_orig_tbl tbl; private Db_stmt stmt; + public Split_meta_wkr__org(Split_ctx ctx, Db_conn atr_conn) { + this.atr_conn = atr_conn; + ctx.Rslt_mgr().Reg_wkr(rslt_wkr); + } + @Override public byte Tid() {return Split_page_list_type_.Tid__fsdb_org;} + @Override public void On_nth_new(Split_ctx ctx, Db_conn trg_conn) { + this.tbl = new Xof_orig_tbl(trg_conn, Bool_.N); + tbl.Create_tbl(); + this.stmt = trg_conn.Stmt_insert(tbl.Tbl_name(), tbl.flds); + } + @Override public void On_nth_rls(Split_ctx ctx, Db_conn trg_conn) {this.stmt = Db_stmt_.Rls(stmt);} + @Override protected String Load_sql(Db_attach_mgr attach_mgr, int ns_id, int score_bgn, int score_end) { + attach_mgr.Conn_links_(new Db_attach_itm("atr_db", atr_conn)); + return String_.Concat_lines_nl + ( "SELECT o.orig_ttl, o.orig_repo, o.orig_status, o.orig_ext, o.orig_w, o.orig_h, o.orig_redirect, fir.page_id" + , "FROM orig_reg o" + , " JOIN fsdb_fil f ON o.orig_ttl = f.fil_name" + , " JOIN fsdb_fil_regy fir ON f.fil_id = fir.fil_id" + , "WHERE fir.page_score >= {0}" + , "AND fir.page_score < {1}" + , "AND fir.page_ns = {2}" + , "ORDER BY page_id" + ); + } + @Override protected Object Load_itm(Db_rdr rdr) {return tbl.Load_by_rdr(rdr);} + @Override protected void Save_itm(Split_ctx ctx, Split_rslt_mgr rslt_mgr, Object itm_obj) { + Xof_orig_itm itm = (Xof_orig_itm)itm_obj; + tbl.Insert(stmt, itm.Repo(), itm.Ttl(), itm.Ext().Id(), itm.W(), itm.H(), itm.Redirect()); + rslt_wkr.On__nth__itm(itm.Db_row_size(), String_.new_u8(itm.Ttl()), itm.Repo()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_meta_wkr__thm.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_meta_wkr__thm.java index a27517de8..f292f5152 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_meta_wkr__thm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_meta_wkr__thm.java @@ -13,3 +13,47 @@ 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.addons.bldrs.exports.splits.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; +import gplx.fsdb.meta.*; import gplx.fsdb.data.*; +import gplx.xowa.addons.bldrs.exports.splits.metas.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +class Split_meta_wkr__thm extends Split_meta_wkr_base { + private final Split_rslt_wkr__thm rslt_wkr = new Split_rslt_wkr__thm(); + private final Db_conn atr_conn; + private Fsd_thm_tbl tbl; private Db_stmt stmt; + public Split_meta_wkr__thm(Split_ctx ctx, Db_conn atr_conn) { + this.atr_conn = atr_conn; + ctx.Rslt_mgr().Reg_wkr(rslt_wkr); + } + @Override public byte Tid() {return Split_page_list_type_.Tid__fsdb_thm;} + @Override public void On_nth_new(Split_ctx ctx, Db_conn trg_conn) { + this.tbl = new Fsd_thm_tbl(trg_conn, Bool_.N, Fsm_mnt_mgr.Mnt_idx_main, Bool_.Y); + tbl.Create_tbl(); + this.stmt = trg_conn.Stmt_insert(tbl.Tbl_name(), tbl.flds); + } + @Override public void On_nth_rls(Split_ctx ctx, Db_conn trg_conn) {this.stmt = Db_stmt_.Rls(stmt);} + @Override protected String Load_sql(Db_attach_mgr attach_mgr, int ns_id, int score_bgn, int score_end) { + attach_mgr.Conn_links_(new Db_attach_itm("atr_db", atr_conn)); + return String_.Concat_lines_nl + ( "SELECT t.thm_id, t.thm_owner_id, t.thm_w, t.thm_h, t.thm_time, t.thm_page, t.thm_bin_db_id, t.thm_size, t.thm_modified, t.thm_hash, fir.page_id" + , "FROM fsdb_thm t" + , " JOIN fsdb_img_regy fir ON t.thm_owner_id = fir.fil_id AND t.thm_id = fir.thm_id" + , "WHERE fir.page_score >= {0}" + , "AND fir.page_score < {1}" + , "AND fir.page_ns = {2}" + , "AND fir.img_type = 1" + , "ORDER BY page_id" + ); + } + @Override protected Object Load_itm(Db_rdr rdr) { + Fsd_thm_itm itm = Fsd_thm_itm.new_(); + tbl.Ctor_by_load(itm, rdr, rdr.Read_int("thm_owner_id")); + return itm; + } + @Override protected void Save_itm(Split_ctx ctx, Split_rslt_mgr rslt_mgr, Object itm_obj) { + Fsd_thm_itm itm = (Fsd_thm_itm)itm_obj; + int thm_id = itm.Thm_id(); + tbl.Insert(stmt, thm_id, itm.Fil_id(), itm.W(), itm.H(), itm.Time(), itm.Page(), ctx.File_size_calc().Idx(), itm.Size(), itm.Modified(), itm.Hash()); + rslt_wkr.On__nth__itm(itm.Db_row_size(), thm_id); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_rslt_wkr__bin.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_rslt_wkr__bin.java index a27517de8..775639876 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_rslt_wkr__bin.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_rslt_wkr__bin.java @@ -13,3 +13,10 @@ 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.addons.bldrs.exports.splits.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +class Split_rslt_wkr__bin extends Split_rslt_wkr__int__base { + @Override public byte Tid() {return Split_rslt_tid_.Tid__fsdb_bin;} + @Override public String Tbl_name() {return "rslt_fsdb_bin";} + @Override public String Pkey_name() {return "bin_owner_id";} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_rslt_wkr__fil.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_rslt_wkr__fil.java index a27517de8..ec0d2072c 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_rslt_wkr__fil.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_rslt_wkr__fil.java @@ -13,3 +13,10 @@ 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.addons.bldrs.exports.splits.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +class Split_rslt_wkr__fil extends Split_rslt_wkr__int__base { + @Override public byte Tid() {return Split_rslt_tid_.Tid__fsdb_fil;} + @Override public String Tbl_name() {return "rslt_fsdb_fil";} + @Override public String Pkey_name() {return "fil_id";} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_rslt_wkr__org.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_rslt_wkr__org.java index a27517de8..9a9ad3842 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_rslt_wkr__org.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_rslt_wkr__org.java @@ -13,3 +13,13 @@ 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.addons.bldrs.exports.splits.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; +import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +class Split_rslt_wkr__org extends Split_rslt_wkr__objs__base { + @Override public byte Tid() {return Split_rslt_tid_.Tid__fsdb_org;} + @Override public String Tbl_name() {return "rslt_fsdb_org";} + @Override public Dbmeta_fld_itm[] Pkey_flds() { + return new Dbmeta_fld_itm[] {Dbmeta_fld_itm.new_str("orig_ttl", 255), Dbmeta_fld_itm.new_byte("orig_repo")}; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_rslt_wkr__thm.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_rslt_wkr__thm.java index a27517de8..a41def1c6 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_rslt_wkr__thm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_rslt_wkr__thm.java @@ -13,3 +13,10 @@ 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.addons.bldrs.exports.splits.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +class Split_rslt_wkr__thm extends Split_rslt_wkr__int__base { + @Override public byte Tid() {return Split_rslt_tid_.Tid__fsdb_thm;} + @Override public String Tbl_name() {return "rslt_fsdb_thm";} + @Override public String Pkey_name() {return "thm_id";} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_wkr__file.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_wkr__file.java index a27517de8..3cb74347e 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_wkr__file.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/files/Split_wkr__file.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.addons.bldrs.exports.splits.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; import gplx.dbs.bulks.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.bldrs.exports.splits.metas.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +public class Split_wkr__file implements Split_wkr { + private Split_meta_wkr_base[] meta_wkrs; + public void Split__init(Split_ctx ctx, Xow_wiki wiki, Db_conn wkr_conn) { + Db_conn atr_conn = wiki.File__fsdb_core().File__atr_file__at(gplx.fsdb.meta.Fsm_mnt_mgr.Mnt_idx_main).Conn(); + new Split_init__file().Exec(ctx, wiki, wkr_conn, atr_conn); + this.meta_wkrs = new Split_meta_wkr_base[] + { new Split_meta_wkr__bin(ctx, wiki.File__mnt_mgr().Mnts__get_main().Bin_mgr()) // NOTE: bin must go first b/c it sets trg_db + , new Split_meta_wkr__fil(ctx, atr_conn) + , new Split_meta_wkr__thm(ctx, atr_conn) + , new Split_meta_wkr__org(ctx, atr_conn) + }; + } + public void Split__trg__1st__new(Split_ctx ctx, Db_conn wkr_conn) { + Db_tbl_copy copy_mgr = new Db_tbl_copy(); + Db_conn atr_conn = ctx.Wiki().File__fsdb_core().File__atr_file__at(gplx.fsdb.meta.Fsm_mnt_mgr.Mnt_idx_main).Conn(); + copy_mgr.Copy_many(atr_conn, wkr_conn, "fsdb_dba", "fsdb_dbb", "fsdb_dir", "fsdb_mnt"); + copy_mgr.Copy_one (atr_conn, wkr_conn, "xowa_cfg", "xowa_cfg__file"); + } + public void Split__trg__nth__new(Split_ctx ctx, Db_conn wkr_conn) { + for (Split_meta_wkr_base wkr : meta_wkrs) + wkr.On_nth_new(ctx, wkr_conn); + } + public void Split__trg__nth__rls(Split_ctx ctx, Db_conn trg_conn) { + for (Split_meta_wkr_base wkr : meta_wkrs) + wkr.On_nth_rls(ctx, trg_conn); + } + public void Split__pages_loaded(Split_ctx ctx, int ns_id, int score_bgn, int score_end) { + for (Split_meta_wkr_base wkr : meta_wkrs) + wkr.Load(ctx.Wkr_conn(), ctx.Page_mgr(), ns_id, score_bgn, score_end); + } + public void Split__exec(Split_ctx ctx, Split_rslt_mgr rslt_mgr, Xowd_page_itm page, int page_id) { + Split_page_itm meta_page = ctx.Page_mgr().Get_by_or_null(page_id); if (meta_page == null) return; // NOTE: pages may not have images + for (Split_meta_wkr_base wkr : meta_wkrs) + wkr.Save(ctx, rslt_mgr, meta_page); + } + public void Split__term(Split_ctx ctx) {} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Split_rslt_wkr__html.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Split_rslt_wkr__html.java index a27517de8..dbf96361d 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Split_rslt_wkr__html.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Split_rslt_wkr__html.java @@ -13,3 +13,10 @@ 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.addons.bldrs.exports.splits.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +class Split_rslt_wkr__html extends Split_rslt_wkr__int__base { + @Override public byte Tid() {return Split_rslt_tid_.Tid__html;} + @Override public String Tbl_name() {return "rslt_html";} + @Override public String Pkey_name() {return "page_id";} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Split_wkr__html.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Split_wkr__html.java index a27517de8..76fff32ec 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Split_wkr__html.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Split_wkr__html.java @@ -13,3 +13,68 @@ 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.addons.bldrs.exports.splits.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.bldrs.exports.splits.metas.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +import gplx.xowa.htmls.core.dbs.*; +public class Split_wkr__html implements Split_wkr { + private Xoh_src_tbl_mgr src_tbl_mgr; + private Xowd_html_tbl tbl; private Db_stmt stmt; + private final Xowd_html_row trg_itm = new Xowd_html_row(); + private final Split_rslt_wkr__html rslt_wkr = new Split_rslt_wkr__html(); + public void Split__init(Split_ctx ctx, Xow_wiki wiki, Db_conn wkr_conn) { + this.src_tbl_mgr = new Xoh_src_tbl_mgr(wiki); + ctx.Rslt_mgr().Reg_wkr(rslt_wkr); + } + public void Split__trg__nth__new(Split_ctx ctx, Db_conn trg_conn) { + this.tbl = new Xowd_html_tbl(trg_conn); + Dbmeta_fld_list trg_flds = Make_flds_for_split(tbl.Flds()); + trg_conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl.Tbl_name(), trg_flds)); + this.stmt = trg_conn.Stmt_insert(tbl.Tbl_name(), trg_flds); + } + public void Split__trg__nth__rls(Split_ctx ctx, Db_conn trg_conn) { + trg_conn.Meta_idx_create(tbl.Tbl_name(), "merge", "trg_db_id", "page_id"); + trg_conn.Meta_idx_create(tbl.Tbl_name(), "blob" , "trg_db_id", "blob_len", "page_id"); + tbl.Rls(); + } + public void Split__exec(Split_ctx ctx, Split_rslt_mgr rslt_mgr, Xowd_page_itm page, int page_id) { + // load data + if (page.Redirected()) return; // redirects won't have html + int html_db_id = page.Html_db_id(); if (html_db_id == -1) return; // ignore pages that don't generate html; EX: Mediawiki:Commons.css + Xoh_page_tbl_itm src_itm = src_tbl_mgr.Get_or_load(html_db_id); + if (!src_itm.Html_tbl().Select_as_row(trg_itm, page_id)) throw Err_.new_wo_type("could not find html", "page_id", page_id); + byte[] body = trg_itm.Body(); + int body_len = body.length; + byte[] display_ttl = trg_itm.Display_ttl(); + byte[] content_sub = trg_itm.Content_sub(); + byte[] sidebar_div = trg_itm.Sidebar_div(); + + // calc db_idx based on db_size + int db_row_size = Xowd_html_row.Db_row_size_fixed + display_ttl.length + content_sub.length + sidebar_div.length + body_len; + int trg_db_id = ctx.Html_size_calc().Size_cur_add_(db_row_size); + + // do insert + stmt.Clear().Val_int("page_id", page_id); + stmt.Val_int("trg_db_id", trg_db_id); + stmt.Val_int("blob_len" , body_len); + tbl.Fill_stmt(stmt, trg_itm.Head_flag(), trg_itm.Body_flag(), display_ttl, content_sub, sidebar_div, body); + stmt.Exec_insert(); + rslt_wkr.On__nth__itm(db_row_size, page_id); + } + public void Split__trg__1st__new(Split_ctx ctx, Db_conn trg_conn) {} // html_dbs have no core tables + public void Split__pages_loaded(Split_ctx ctx, int ns_id, int score_bgn, int score_end) {} // html_wkr has no caching + public void Split__term(Split_ctx ctx) {} // html_wkr has no cleanup + private static Dbmeta_fld_list Make_flds_for_split(Dbmeta_fld_list flds) { + Dbmeta_fld_list rv = new Dbmeta_fld_list(); + rv.Add(flds.Get_by("page_id")); + rv.Add_int("trg_db_id"); + rv.Add_int("blob_len"); + rv.Add(flds.Get_by("head_flag")); + rv.Add(flds.Get_by("body_flag")); + rv.Add(flds.Get_by("display_ttl")); + rv.Add(flds.Get_by("content_sub")); + rv.Add(flds.Get_by("sidebar_div")); + rv.Add(flds.Get_by("body")); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Xoh_page_tbl_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Xoh_page_tbl_itm.java index a27517de8..c49a80b75 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Xoh_page_tbl_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Xoh_page_tbl_itm.java @@ -13,3 +13,19 @@ 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.addons.bldrs.exports.splits.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; import gplx.xowa.htmls.core.dbs.*; +public class Xoh_page_tbl_itm { + private final boolean trg; + public Xoh_page_tbl_itm(boolean trg, int db_id, Db_conn conn) { + this.trg = trg; + this.db_id = db_id; + this.html_tbl = new Xowd_html_tbl(conn); + } + public int Db_id() {return db_id;} private final int db_id; + public Xowd_html_tbl Html_tbl() {return html_tbl;} private final Xowd_html_tbl html_tbl; + public void Rls() { + html_tbl.Conn().Rls_conn(); + if (trg) html_tbl.Conn().Env_vacuum(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Xoh_src_tbl_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Xoh_src_tbl_mgr.java index a27517de8..29a136620 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Xoh_src_tbl_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Xoh_src_tbl_mgr.java @@ -13,3 +13,30 @@ 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.addons.bldrs.exports.splits.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.htmls.core.dbs.*; +public class Xoh_src_tbl_mgr { + private final Xow_wiki wiki; + private final Ordered_hash hash = Ordered_hash_.New(); + public Xoh_src_tbl_mgr(Xow_wiki wiki) { + this.wiki = wiki; + } + public Xoh_page_tbl_itm Get_or_load(int id) { + Xoh_page_tbl_itm rv = (Xoh_page_tbl_itm)hash.Get_by(id); + if (rv == null) { + Xow_db_file html_db = wiki.Data__core_mgr().Dbs__get_by_id_or_fail(id); + rv = new Xoh_page_tbl_itm(Bool_.N, id, html_db.Conn()); + hash.Add(id, rv); + } + return rv; + } + public void Cleanup() { + int len = hash.Len(); + for (int i = 0; i < len; ++i) { + Xoh_page_tbl_itm itm = (Xoh_page_tbl_itm)hash.Get_at(i); + itm.Rls(); + } + hash.Clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Xoh_trg_tbl_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Xoh_trg_tbl_mgr.java index a27517de8..5bca72644 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Xoh_trg_tbl_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/htmls/Xoh_trg_tbl_mgr.java @@ -13,3 +13,37 @@ 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.addons.bldrs.exports.splits.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +public class Xoh_trg_tbl_mgr { + private final Xow_db_mgr db_mgr; + public Xoh_trg_tbl_mgr(Xowe_wiki wiki) { + this.db_mgr = wiki.Data__core_mgr(); + } + public Xoh_page_tbl_itm Make_new(int ns, int part) { + Xow_db_file db_file = db_mgr.Dbs__make_by_tid(Xow_db_file_.Tid__html_data, Int_.To_str(ns), part, Make_file_name(Repack_suffix, Xow_db_file_.Tid__html_data, ns, part)); + Xoh_page_tbl_itm rv = new Xoh_page_tbl_itm(Bool_.Y, db_file.Id(), db_file.Conn()); + rv.Html_tbl().Create_tbl(); + return rv; + } + public static String Make_file_name(String suffix, byte type, int ns, int part) { + String rv = String_.Format("{0}{1}{2}{3}.xowa" // EX: .repack-html-ns.000-001.xowa + , suffix // EX: .repack + , "-" + Xow_db_file_.To_key(type) // EX: -html + , ns < 0 ? "" : "-ns." + Int_.To_str_pad_bgn_zero(ns, 3) // EX: -ns.001 + , part < 0 ? "" : "-db." + Int_.To_str_pad_bgn_zero(part, 3) // EX: -db.001 + ); + return rv; + } + public int Get_max_id() { + int len = db_mgr.Dbs__len(); + int rv = -1; + for (int i = 0; i < len; ++i) { + Xow_db_file db_file = db_mgr.Dbs__get_at(i); + int db_id = db_file.Id(); + if (db_id > rv) rv = db_id; + } + return rv; + } + public static final String Repack_suffix = ".repack"; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_meta_wkr_base.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_meta_wkr_base.java index a27517de8..94308e6be 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_meta_wkr_base.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_meta_wkr_base.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.addons.bldrs.exports.splits.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; +import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +public abstract class Split_meta_wkr_base { + private final Db_attach_mgr attach_mgr = new Db_attach_mgr(); + public void Load(Db_conn wkr_conn, Split_page_mgr page_mgr, int ns_id, int score_bgn, int score_end) { + attach_mgr.Conn_main_(wkr_conn); + String sql_fmt = Load_sql(attach_mgr, ns_id, score_bgn, score_end); + String sql = attach_mgr.Resolve_sql(String_.Format(sql_fmt, score_bgn, score_end, ns_id)); + attach_mgr.Attach(); + Db_rdr rdr = wkr_conn.Stmt_sql(sql).Exec_select__rls_auto(); + Split_page_itm page = null; + byte tid = this.Tid(); + while (rdr.Move_next()) { + int page_id = rdr.Read_int("page_id"); + if (page == null || page.Page_id() != page_id) { + page = page_mgr.Get_by_or_null(page_id); + if (page == null) { + page = new Split_page_itm(true, page_id); + page_mgr.Add(page); + } + } + Split_page_list list = page.Get_by_or_make(tid); + list.Add(Load_itm(rdr)); + } + attach_mgr.Detach(); + } + public void Save(Split_ctx ctx, Split_rslt_mgr rslt_mgr, Split_page_itm page_itm) { + Split_page_list list = page_itm.Get_by_or_null(this.Tid()); if (list == null) return; + int len = list.Len(); + for (int i = 0; i < len; ++i) + this.Save_itm(ctx, rslt_mgr, list.Get_at(i)); + } + public abstract byte Tid(); + public abstract void On_nth_new(Split_ctx ctx, Db_conn wkr_conn); + public abstract void On_nth_rls(Split_ctx ctx, Db_conn trg_conn); + protected abstract String Load_sql(Db_attach_mgr attach_mgr, int ns_id, int score_bgn, int score_end); // gen sql and attach_itms + protected abstract Object Load_itm(Db_rdr rdr); + protected abstract void Save_itm(Split_ctx ctx, Split_rslt_mgr rslt_mgr, Object itm); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_page_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_page_itm.java index a27517de8..098882c3c 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_page_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_page_itm.java @@ -13,3 +13,21 @@ 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.addons.bldrs.exports.splits.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +public class Split_page_itm { + private final Split_page_list[] lists_ary; + public Split_page_itm(boolean fsdb, int page_id) { + this.page_id = page_id; + this.lists_ary = new Split_page_list[Split_page_list_type_.Tid_max]; + } + public int Page_id() {return page_id;} private final int page_id; + public Split_page_list Get_by_or_null(byte type) {return lists_ary[type];} + public Split_page_list Get_by_or_make(byte type) { + Split_page_list rv = lists_ary[type]; + if (rv == null) { + rv = new Split_page_list(type); + lists_ary[type] = rv; + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_page_list.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_page_list.java index a27517de8..eb58f3da0 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_page_list.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_page_list.java @@ -13,3 +13,12 @@ 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.addons.bldrs.exports.splits.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +public class Split_page_list { + private final List_adp list = List_adp_.New(); + public Split_page_list(byte type) {this.type = type;} + public byte Type() {return type;} private final byte type; + public int Len() {return list.Count();} + public Object Get_at(int i) {return list.Get_at(i);} + public void Add(Object o) {list.Add(o);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_page_list_type_.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_page_list_type_.java index a27517de8..9aac28bae 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_page_list_type_.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_page_list_type_.java @@ -13,3 +13,10 @@ 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.addons.bldrs.exports.splits.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +public class Split_page_list_type_ { + public static final int Tid_max = 6; + public static final byte + Tid__fsdb_bin = 0, Tid__fsdb_fil = 1, Tid__fsdb_thm = 2, Tid__fsdb_org = 3 + , Tid__srch_word = 4, Tid__srch_link = 5; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_page_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_page_mgr.java index a27517de8..d9e701b6e 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_page_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/metas/Split_page_mgr.java @@ -13,3 +13,13 @@ 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.addons.bldrs.exports.splits.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.core.lists.hashs.*; +public class Split_page_mgr { + private final Hash_adp__int hash = new Hash_adp__int(); + public void Clear() {hash.Clear();} + public Split_page_itm Get_by_or_null(int page_id) {return (Split_page_itm)hash.Get_by_or_null(page_id);} + public void Add(Split_page_itm page) { + hash.Add(page.Page_id(), page); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_cfg.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_cfg.java index a27517de8..1756b03b2 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_cfg.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_cfg.java @@ -13,3 +13,36 @@ 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.addons.bldrs.exports.splits.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +public class Split_cfg implements Gfo_invk { + public boolean Force_rebuild() {return force_rebuild;} private boolean force_rebuild; + public long Trg_max() {return trg_max;} private long trg_max = 32 * Io_mgr.Len_mb; + public Split_ns_itm[] Ns_itms() {return ns_itms;} private Split_ns_itm[] ns_itms; + public int Loader_rows() {return loader_rows;} private int loader_rows = 10000; + public Split_type_cfg Text() {return text;} private final Split_type_cfg text = new Split_type_cfg("text", 1000); + public Split_type_cfg Html() {return html;} private final Split_type_cfg html = new Split_type_cfg("html", 2000); + public Split_type_cfg File() {return file;} private final Split_type_cfg file = new Split_type_cfg("file", 3000); + public void Ns_itms_(int[] ary) { + List_adp list = List_adp_.New(); + int len = ary.length; + for (int i = 0; i < len; ++i) { + list.Add(new Split_ns_itm(ary[i])); + } + this.ns_itms = (Split_ns_itm[])list.To_ary_and_clear(Split_ns_itm.class); + } + + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__ns_ids_)) this.Ns_itms_(Int_ary_.Parse(m.ReadStr("v"), "|")); + else if (ctx.Match(k, Invk__loader_rows_)) this.loader_rows = m.ReadInt("v"); + else if (ctx.Match(k, Invk__force_rebuild_)) this.force_rebuild = m.ReadBool("v"); + else if (ctx.Match(k, Invk__trg_max_)) this.trg_max = m.ReadLong("v"); + else if (ctx.Match(k, Invk__text)) return text; + else if (ctx.Match(k, Invk__html)) return html; + else if (ctx.Match(k, Invk__file)) return file; + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk__ns_ids_ = "ns_ids_", Invk__loader_rows_ = "loader_rows_", Invk__trg_max_ = "trg_max_", Invk__force_rebuild_ = "force_rebuild_" + , Invk__text = "text", Invk__html = "html", Invk__file = "file" + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_db_size_calc.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_db_size_calc.java index a27517de8..e6b24ed08 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_db_size_calc.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_db_size_calc.java @@ -13,3 +13,23 @@ 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.addons.bldrs.exports.splits.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +public class Split_db_size_calc { + private final long size_max; + private long size_cur; + public Split_db_size_calc(long size_max, int idx) { + this.size_max = size_max; + this.idx = idx; + } + public int Idx() {return idx;} private int idx; + public int Size_cur_add_(int v) { + long size_new = size_cur + v; + if (size_new > size_max) { + ++idx; + size_cur = 0; + } + else + size_cur = size_new; + return idx; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_file_tid_.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_file_tid_.java index a27517de8..dd081ca5f 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_file_tid_.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_file_tid_.java @@ -13,3 +13,26 @@ 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.addons.bldrs.exports.splits.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +public class Split_file_tid_ { + public static final byte Tid__core = 0, Tid__data = 1, Tid__temp = 2; + public static byte To_tid(Io_url url) { + if (!String_.Eq(url.Ext(), ".xowa")) return Tid__temp; + String raw = url.NameAndExt(); + if (String_.Has(raw, "_core.")) return Tid__core; + else if (String_.Has(raw, ".sqlite")) return Tid__temp; + else return Tid__data; + } + public static String Make_file_name(String wiki_abrv, String wiki_date, int idx, int ns, String ext) {// EX: Xowa_simplewiki_2016-05-01_pt.0001_ns.0014.xowa + String ns_str = ns == -1 ? "_core" : String_.Format("_ns.{0}", Int_.To_str_pad_bgn_zero(ns, 4)); + return String_.Format("Xowa_{0}_{1}_part.{2}{3}{4}", wiki_abrv, wiki_date, Int_.To_str_pad_bgn_zero(idx, 4), ns_str, ext); + } + public static int Get_ns_by_url(Io_url fil) { + String raw = fil.Raw(); + if (String_.Has(raw, "_core")) return -1; + int bgn = String_.FindFwd(raw, "ns."); if (bgn == Bry_find_.Not_found) throw Err_.new_wo_type("could not find ns in url", "fil", raw); + bgn += 3; // ns. + int end = String_.FindFwd(raw, ".", bgn); if (end == Bry_find_.Not_found) throw Err_.new_wo_type("could not find ns in url", "fil", raw); + return Int_.Parse(String_.Mid(raw, bgn, end)); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_mgr_init.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_mgr_init.java index a27517de8..58f77848e 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_mgr_init.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_mgr_init.java @@ -13,3 +13,59 @@ 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.addons.bldrs.exports.splits.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; +public class Split_mgr_init { + public void Init(Split_ctx ctx, Db_conn wkr_conn, Db_conn page_conn) { + this.Make_ns_regy (ctx.Cfg().Force_rebuild(), wkr_conn, ctx.Ns_itms()); + this.Make_page_regy (ctx.Cfg().Force_rebuild(), wkr_conn, page_conn); + ctx.Rslt_mgr().Init(); + } + private void Make_ns_regy(boolean force_rebuild, Db_conn wkr_conn, Split_ns_itm[] ns_itms) { + if (!(force_rebuild || !wkr_conn.Meta_tbl_exists("ns_regy"))) return; + Gfo_log_.Instance.Prog("creating ns_regy"); + wkr_conn.Meta_tbl_remake(Dbmeta_tbl_itm.New("ns_regy" + , Dbmeta_fld_itm.new_int("ns_id").Primary_y_() + , Dbmeta_fld_itm.new_int("ns_ord") + )); + int len = ns_itms.length; + Db_stmt stmt = wkr_conn.Stmt_insert("ns_regy", "ns_id", "ns_ord"); + for (int i = 0; i < len; ++i) + stmt.Clear().Crt_int("ns_id", ns_itms[i].Ns_id()).Crt_int("ns_ord", i).Exec_insert(); + wkr_conn.Meta_idx_create("ns_regy", "ns_ord", "ns_ord"); + } + private void Make_page_regy(boolean force_rebuild, Db_conn wkr_conn, Db_conn page_conn) { + // order pages by page_ns_ord ASC, page_score DESC, page_len DESC, page_id ASC + if (!(force_rebuild || !wkr_conn.Meta_tbl_exists("page_regy"))) return; + Gfo_log_.Instance.Prog("creating page_regy"); + wkr_conn.Meta_tbl_remake(Dbmeta_tbl_itm.New("page_regy" + , Dbmeta_fld_itm.new_int("page_uid").Primary_y_().Autonum_y_() + , Dbmeta_fld_itm.new_int("page_ns_id") + , Dbmeta_fld_itm.new_int("page_ns_ord") + , Dbmeta_fld_itm.new_int("page_score") + , Dbmeta_fld_itm.new_int("page_len") + , Dbmeta_fld_itm.new_int("page_id") + )); + page_conn.Meta_idx_create("page", "page_uid", "page_namespace", "page_score", "page_len", "page_id"); // index page table; will drop below + Db_attach_mgr attach_mgr = new Db_attach_mgr(wkr_conn, new Db_attach_itm("page_db", page_conn)); + attach_mgr.Exec_sql(String_.Concat_lines_nl // ANSI.Y + ( "INSERT INTO page_regy (page_ns_id, page_ns_ord, page_score, page_len, page_id)" + , "SELECT p.page_namespace, nm.ns_ord, p.page_score, p.page_len, p.page_id" + , "FROM page p" + , " JOIN ns_regy nm ON p.page_namespace = nm.ns_id" + , "ORDER BY nm.ns_ord, p.page_score DESC, p.page_len DESC, p.page_id" + )); + wkr_conn.Meta_idx_create("page_regy", "main" , "page_ns_id", "page_score", "page_len", "page_id"); + wkr_conn.Meta_idx_create("page_regy", "page_id" , "page_id"); + page_conn.Meta_idx_delete("page", "page_uid"); + } + + public static void Update_page_cols(Db_conn wkr_conn, String tbl_name) { + wkr_conn.Exec_sql(String_.Format(String_.Concat_lines_nl // ANSI.Y + ( "UPDATE {0}" + , "SET page_id = Coalesce((SELECT page_id FROM page_regy pr WHERE pr.page_uid = {0}.page_uid), -1)" + , ", page_ns = Coalesce((SELECT page_ns_id FROM page_regy pr WHERE pr.page_uid = {0}.page_uid), -1)" + , ", page_score = Coalesce((SELECT page_score FROM page_regy pr WHERE pr.page_uid = {0}.page_uid), -1)" + ), tbl_name)); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_ns_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_ns_itm.java index a27517de8..42a7ee93a 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_ns_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_ns_itm.java @@ -13,3 +13,10 @@ 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.addons.bldrs.exports.splits.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +public class Split_ns_itm { + public Split_ns_itm(int ns_id) { + this.ns_id = ns_id; + } + public int Ns_id() {return ns_id;} private final int ns_id; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_page_loader.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_page_loader.java index a27517de8..51763f973 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_page_loader.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_page_loader.java @@ -13,3 +13,58 @@ 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.addons.bldrs.exports.splits.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; import gplx.dbs.metas.*; +import gplx.xowa.wikis.data.tbls.*; +public class Split_page_loader { + private final int rows_to_read; + private final Xowd_page_tbl tbl; private final Db_stmt stmt; + private int score_max = Int_.Max_value; + public Split_page_loader(Xow_wiki wiki, int rows_to_read) { + this.rows_to_read = rows_to_read; + this.tbl = wiki.Data__core_mgr().Tbl__page(); + tbl.Conn().Meta_idx_assert("page", "score__len", Dbmeta_idx_fld.Dsc("page_score"), Dbmeta_idx_fld.Dsc("page_len")); + this.stmt = tbl.Conn().Stmt_sql("SELECT * FROM page WHERE page_namespace=? AND page_score < ? ORDER BY page_score DESC, page_len DESC"); // ANSI.Y + } + public void Init_ns(int ns_id) {score_max = Int_.Max_value;} + public boolean Load_pages(Split_ctx ctx, List_adp list, Split_wkr[] wkrs, int ns_id) { + boolean reading = true; + list.Clear(); + Gfo_log_.Instance.Prog("loading pages", "page_ns", ns_id, "score_max", score_max); + int score_end = score_max, score_prv = score_max; + Db_rdr rdr = stmt.Clear().Crt_int("page_namespace", ns_id).Crt_int("page_score", score_max).Exec_select__rls_manual(); + try { + int rows_count = 0; + boolean enough_rows_read = false; + while (true) { + reading = rdr.Move_next(); if (!reading) break; + + // read data from rdr + Xowd_page_itm itm = new Xowd_page_itm(); + tbl.Read_page__all(itm, rdr); + + // check if (a) enough rows read and (b) score range is done; (b) needed b/c WHERE is page_score < prv_score + int score_cur = itm.Score(); + if (enough_rows_read && score_prv != score_cur) { + score_max = score_prv; // update score_max for next call; note that (a) score_prv is used to ensure boundary between scores; (b) cur_record will be reread on next call + break; // stop reading + } + + // add to list; update variables + list.Add(itm); + if (++rows_count == rows_to_read) enough_rows_read = true; + score_prv = score_cur; + } + } finally {rdr.Rls();} + + // calls wkrs to cache rdrs + ctx.Page_mgr().Clear(); + for (Split_wkr wkr : wkrs) + wkr.Split__pages_loaded(ctx, ns_id, score_prv, score_end); + return reading; + } + public void Rls() { + stmt.Rls(); + tbl.Conn().Meta_idx_delete("page", "score_len"); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_type_cfg.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_type_cfg.java index a27517de8..a85c6d550 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_type_cfg.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/mgrs/Split_type_cfg.java @@ -13,3 +13,19 @@ 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.addons.bldrs.exports.splits.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +public class Split_type_cfg implements Gfo_invk { + public Split_type_cfg(String key, int db_idx) {this.key = key; this.db_idx = db_idx;} + public String Key() {return key;} private final String key; // NOTE: used for layout + public int Db_idx() {return db_idx;} private int db_idx = 1000; + public long Db_max() {return db_max;} private long db_max = 1500 * Io_mgr.Len_mb; + public String Layout() {return layout;} private String layout; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__db_max_)) this.db_max = m.ReadLong("v") * Io_mgr.Len_mb; + else if (ctx.Match(k, Invk__db_idx_)) this.db_idx = m.ReadInt("v"); + else if (ctx.Match(k, Invk__layout_)) this.layout = m.ReadStr("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk__db_max_ = "db_max_", Invk__db_idx_ = "db_idx_", Invk__layout_ = "layout_"; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/pages/Split_rslt_wkr__page.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/pages/Split_rslt_wkr__page.java index a27517de8..7d5949810 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/pages/Split_rslt_wkr__page.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/pages/Split_rslt_wkr__page.java @@ -13,3 +13,10 @@ 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.addons.bldrs.exports.splits.pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +class Split_rslt_wkr__page extends Split_rslt_wkr__int__base { + @Override public byte Tid() {return Split_rslt_tid_.Tid__page;} + @Override public String Tbl_name() {return "rslt_page";} + @Override public String Pkey_name() {return "page_id";} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/pages/Split_wkr__page.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/pages/Split_wkr__page.java index a27517de8..a5e03bec8 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/pages/Split_wkr__page.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/pages/Split_wkr__page.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.addons.bldrs.exports.splits.pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.dbs.bulks.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.bldrs.exports.splits.metas.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; import gplx.xowa.addons.bldrs.exports.splits.mgrs.*; +public class Split_wkr__page implements Split_wkr { + private final Split_rslt_wkr__page rslt_wkr = new Split_rslt_wkr__page(); + private Xowd_page_tbl tbl; private Db_stmt stmt; + public void Split__init(Split_ctx ctx, Xow_wiki wiki, Db_conn wkr_conn) { + ctx.Rslt_mgr().Reg_wkr(rslt_wkr); + } + public void Split__trg__1st__new(Split_ctx ctx, Db_conn trg_conn) { + Db_conn core_conn = ctx.Wiki().Data__core_mgr().Tbl__cfg().Conn(); + Db_tbl_copy copy_mgr = new Db_tbl_copy(); + copy_mgr.Copy_many(core_conn, trg_conn, "xowa_db", "site_ns", "site_stats", "css_core", "css_file"); + copy_mgr.Copy_one (core_conn, trg_conn, "xowa_cfg", "xowa_cfg__core"); + Update_layouts(trg_conn, ctx.Cfg().Text(), ctx.Cfg().Html(), ctx.Cfg().File()); + } + public void Split__trg__nth__new(Split_ctx ctx, Db_conn trg_conn) { + this.tbl = new Xowd_page_tbl(trg_conn, Bool_.N); + tbl.Create_tbl(); + stmt = trg_conn.Stmt_insert(tbl.Tbl_name(), tbl.Flds__all()); + } + public void Split__trg__nth__rls(Split_ctx ctx, Db_conn trg_conn) { + stmt.Rls(); + } + public void Split__exec(Split_ctx ctx, Split_rslt_mgr rslt_mgr, Xowd_page_itm page, int page_id) { + tbl.Insert_by_itm(stmt, page, ctx.Html_size_calc().Idx()); + rslt_wkr.On__nth__itm(Xowd_page_itm.Db_row_size_fixed + page.Ttl_page_db().length, page_id); + rslt_mgr.Score_set(page.Score()); + } + public void Split__pages_loaded(Split_ctx ctx, int ns_id, int score_bgn, int score_end) {} // page_wkr has no caching + public void Split__term(Split_ctx ctx) {} // page_wkr has no cleanup + private void Update_layouts(Db_conn trg_conn, Split_type_cfg... cfgs) { + Db_cfg_tbl cfg_tbl = new Db_cfg_tbl(trg_conn, "xowa_cfg__core"); + for (Split_type_cfg cfg : cfgs) { + String layout = cfg.Layout(); if (layout == null) continue; + cfg_tbl.Update_str("xowa.wiki.core", "layout_" + cfg.Key(), layout); + } + cfg_tbl.Rls(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rndms/Split_rslt_wkr__rndm.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rndms/Split_rslt_wkr__rndm.java index a27517de8..fd45e0a0b 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rndms/Split_rslt_wkr__rndm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rndms/Split_rslt_wkr__rndm.java @@ -13,3 +13,12 @@ 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.addons.bldrs.exports.splits.rndms; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +class Split_rslt_wkr__rndm extends Split_rslt_wkr__objs__base { + @Override public byte Tid() {return Split_rslt_tid_.Tid__rndm;} + @Override public String Tbl_name() {return "rslt_rndm";} + @Override public Dbmeta_fld_itm[] Pkey_flds() { + return new Dbmeta_fld_itm[] {Dbmeta_fld_itm.new_int("mgr_idx"), Dbmeta_fld_itm.new_int("rng_idx"), Dbmeta_fld_itm.new_int("seq_idx")}; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rndms/Split_wkr__rndm.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rndms/Split_wkr__rndm.java index a27517de8..752b51047 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rndms/Split_wkr__rndm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rndms/Split_wkr__rndm.java @@ -13,3 +13,58 @@ 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.addons.bldrs.exports.splits.rndms; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.dbs.bulks.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.bldrs.exports.splits.metas.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; import gplx.xowa.addons.bldrs.exports.splits.mgrs.*; +import gplx.xowa.addons.wikis.pages.randoms.*; import gplx.xowa.addons.wikis.pages.randoms.dbs.*; import gplx.xowa.addons.wikis.pages.randoms.bldrs.*; import gplx.xowa.addons.wikis.pages.randoms.mgrs.*; +import gplx.xowa.addons.bldrs.exports.utls.*; +public class Split_wkr__rndm implements Split_wkr { + private final Split_rslt_wkr__rndm rslt_wkr = new Split_rslt_wkr__rndm(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); private final Db_attach_mgr attach_mgr = new Db_attach_mgr(); + private Rndm_bldr_wkr bldr; + private int cur_ns_id = -1; + public void Split__init(Split_ctx ctx, Xow_wiki wiki, Db_conn wkr_conn) { + this.bldr = Rndm_addon.Get(wiki).Mgr().New_bldr(); + ctx.Rslt_mgr().Reg_wkr(rslt_wkr); + } + public void Split__term(Split_ctx ctx) { + bldr.Exec_qry_end(); + } + public void Split__trg__1st__new(Split_ctx ctx, Db_conn trg_conn) { + Rndm_qry_tbl mgr_tbl = new Rndm_qry_tbl(trg_conn); mgr_tbl.Create_tbl(); + } + public void Split__trg__nth__new(Split_ctx ctx, Db_conn trg_conn) { + if (cur_ns_id != ctx.Trg_ns()) { + if (cur_ns_id != -1) + bldr.Exec_qry_end(); + cur_ns_id = ctx.Trg_ns(); + bldr.Exec_qry_bgn(Rndm_qry_itm.New_by_ns(ctx.Wiki(), cur_ns_id)); + } + bldr.Exec_rng_bgn(); + bldr.Conn().Txn_bgn("rndm"); + } + public void Split__trg__nth__rls(Split_ctx ctx, Db_conn trg_conn) { + // make rndm_rng and add + Rndm_rng_itm rng_itm = bldr.Exec_rng_end_or_null(); if (rng_itm == null) return; + Rndm_rng_tbl rng_tbl = new Rndm_rng_tbl(trg_conn); rng_tbl.Create_tbl(); + Db_stmt rng_stmt = rng_tbl.Insert_stmt(); + rng_tbl.Insert(rng_stmt, rng_itm.Mgr_idx(), rng_itm.Rng_idx(), rng_itm.Seq_bgn(), rng_itm.Seq_end()); + rng_stmt.Rls(); + bldr.Conn().Txn_end(); + + // make rndm_seq and bulk copy + Rndm_seq_tbl seq_tbl = new Rndm_seq_tbl(trg_conn); seq_tbl.Create_tbl(); + Split_tbl_.Bld_insert_by_select(tmp_bfr, seq_tbl.Tbl_name(), seq_tbl.Flds()); + tmp_bfr.Add_str_u8_fmt("WHERE {0} = {1} AND {2} = {3}", seq_tbl.Fld__qry_idx(), bldr.Qry_idx(), seq_tbl.Fld__rng_idx(), bldr.Rng_idx()); + attach_mgr.Conn_main_(trg_conn).Conn_links_(new Db_attach_itm("src_db", bldr.Conn())); + attach_mgr.Exec_sql(tmp_bfr.To_str_and_clear()); +// bldr.Conn().Txn_bgn("rndm"); + } + public void Split__exec(Split_ctx ctx, Split_rslt_mgr rslt_mgr, Xowd_page_itm page, int page_id) { + if (page.Ns_id() == gplx.xowa.wikis.nss.Xow_ns_.Tid__main && !page.Redirected()) { + bldr.Exec_seq_itm(page.Id()); + rslt_wkr.On__nth__itm(Rndm_seq_tbl.Db_row_size_fixed, bldr.Qry_idx(), bldr.Rng_idx(), bldr.Seq_in_qry()); + } + } + public void Split__pages_loaded(Split_ctx ctx, int ns_id, int score_bgn, int score_end) {} // rndm_wkr has no caching +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_mgr.java index a27517de8..14dd5edd2 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_mgr.java @@ -13,3 +13,88 @@ 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.addons.bldrs.exports.splits.rslts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; +public class Split_rslt_mgr { + private final Db_conn wkr_conn; + private Db_stmt rslt_type_stmt, rslt_db_stmt; + private Io_url db_url; private int db_id, ns_id; + private int score_min, score_max; + private Split_rslt_wkr[] rslt_ary = new Split_rslt_wkr[Split_rslt_tid_.Tid_max]; + public Split_rslt_mgr(Db_conn wkr_conn) { + this.wkr_conn = wkr_conn; + for (int i = 0; i < Split_rslt_tid_.Tid_max; ++i) + rslt_ary[i] = Split_rslt_wkr_.Noop; + } + public void Init() { + // init rslt_db + wkr_conn.Meta_tbl_remake(Dbmeta_tbl_itm.New("rslt_db" + , Dbmeta_fld_itm.new_int("db_id") + , Dbmeta_fld_itm.new_long("db_size") + , Dbmeta_fld_itm.new_long("obj_size") + , Dbmeta_fld_itm.new_int("ns_id") + , Dbmeta_fld_itm.new_int("score_min") + , Dbmeta_fld_itm.new_int("score_max") + )); + this.rslt_db_stmt = wkr_conn.Stmt_insert("rslt_db", "db_id", "db_size", "obj_size", "ns_id", "score_min", "score_max"); + + // init rslt_type + wkr_conn.Meta_tbl_remake(Dbmeta_tbl_itm.New("rslt_type" + , Dbmeta_fld_itm.new_int("db_id") + , Dbmeta_fld_itm.new_byte("tbl_id") + , Dbmeta_fld_itm.new_int("row_count") + , Dbmeta_fld_itm.new_long("obj_size") + )); + this.rslt_type_stmt = wkr_conn.Stmt_insert("rslt_type", "db_id", "tbl_id", "row_count", "obj_size"); + } + public void Reg_wkr(Split_rslt_wkr wkr) { + rslt_ary[wkr.Tid()] = wkr; + wkr.On__init(this, wkr_conn); + } + public long Db_size() {return db_size;} private long db_size; + public void Db_size_add_(int v) {db_size += v;} + public void On__nth__new(int db_id, Io_url db_url, int ns_id) { + this.db_id = db_id; this.db_size = 0; this.db_url = db_url; + this.ns_id = ns_id; + this.score_min = Int_.Max_value; + this.score_max = Int_.Min_value; + for (Split_rslt_wkr wkr : rslt_ary) + wkr.On__nth__new(db_id); + } + public void Score_set(int score) { + if (score < score_min) score_min = score; + if (score > score_max) score_max = score; + } + public void On__nth__rls(Db_conn trg_conn) { + Trg_stats(trg_conn, rslt_ary); + wkr_conn.Txn_bgn("wkr_rslts"); + rslt_db_stmt.Clear().Val_int("db_id", db_id) + .Val_long("db_size", Io_mgr.Instance.QueryFil(db_url).Size()) + .Val_long("obj_size", db_size) + .Val_int("ns_id", ns_id) + .Val_int("score_min", score_min).Val_int("score_max", score_max).Exec_insert(); + for (int i = 0; i < Split_rslt_tid_.Tid_max; ++i) + rslt_type_stmt.Clear().Val_int("db_id", db_id).Val_int("tbl_id", i) + .Val_int("row_count", rslt_ary[i].Row_count()) + .Val_long("obj_size", rslt_ary[i].Obj_size()) + .Exec_insert(); + for (Split_rslt_wkr wkr : rslt_ary) + wkr.On__nth__rls(); + wkr_conn.Txn_end(); + } + private static void Trg_stats(Db_conn trg_conn, Split_rslt_wkr[] rlst_ary) { + Wkr_stats_tbl tbl = new Wkr_stats_tbl(trg_conn); + tbl.Create_tbl(); + Db_stmt stmt = tbl.Insert_stmt(); + for (int i = 0 ; i < Split_rslt_tid_.Tid_max; ++i) { + Split_rslt_wkr rslt_wkr = rlst_ary[i]; + tbl.Insert(stmt, rslt_wkr.Tid(), rslt_wkr.Row_count(), rslt_wkr.Obj_size()); + } + } + public void Term() { + rslt_db_stmt = Db_stmt_.Rls(rslt_db_stmt); + rslt_type_stmt = Db_stmt_.Rls(rslt_type_stmt); + for (Split_rslt_wkr wkr : rslt_ary) + wkr.On_term(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_tid_.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_tid_.java index a27517de8..d8e33540b 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_tid_.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_tid_.java @@ -13,3 +13,14 @@ 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.addons.bldrs.exports.splits.rslts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +public class Split_rslt_tid_ { + public static final int Tid_max = 9; + public static final int Tid__page = 0, Tid__html = 1, Tid__srch_word = 2, Tid__srch_link = 3, Tid__fsdb_bin = 4, Tid__fsdb_fil = 5, Tid__fsdb_thm = 6, Tid__fsdb_org = 7, Tid__rndm = 8; + public static String To_str(int tid) { + switch (tid) { + case Tid__page: return "page"; + default: throw Err_.new_unhandled_default(tid); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_wkr.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_wkr.java index a27517de8..05b460713 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_wkr.java @@ -13,3 +13,14 @@ 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.addons.bldrs.exports.splits.rslts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; +public interface Split_rslt_wkr { + byte Tid(); + int Row_count(); + long Obj_size(); + void On__init(Split_rslt_mgr rslt_mgr, Db_conn wkr_conn); + void On__nth__new(int db_id); + void On__nth__rls(); + void On_term(); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_wkr_.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_wkr_.java index a27517de8..bab638146 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_wkr_.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_wkr_.java @@ -13,3 +13,17 @@ 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.addons.bldrs.exports.splits.rslts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; +public class Split_rslt_wkr_ { + public static final Split_rslt_wkr Noop = new Split_rslt_wkr__noop(); +} +class Split_rslt_wkr__noop implements Split_rslt_wkr { + public byte Tid() {return Split_rslt_tid_.Tid_max;} + public int Row_count() {return 0;} + public long Obj_size() {return 0;} + public void On__init(Split_rslt_mgr rslt_mgr, Db_conn wkr_conn) {} + public void On__nth__new(int db_id) {} + public void On__nth__rls() {} + public void On_term() {} +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_wkr__int__base.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_wkr__int__base.java index a27517de8..db3ed8890 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_wkr__int__base.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_wkr__int__base.java @@ -13,3 +13,51 @@ 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.addons.bldrs.exports.splits.rslts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.core.primitives.*; import gplx.dbs.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +public abstract class Split_rslt_wkr__int__base implements Split_rslt_wkr { + private Split_rslt_mgr rslt_mgr; + private Db_conn wkr_conn; private Db_stmt stmt; private int db_id; + private final Int_ary pkey_ary = new Int_ary(128); + private final String tbl_name, pkey_name; + public Split_rslt_wkr__int__base() { + this.tbl_name = Tbl_name(); + this.pkey_name = Pkey_name(); + } + public abstract byte Tid(); + public abstract String Tbl_name(); + public abstract String Pkey_name(); + public int Row_count() {return pkey_ary.Len();} + public long Obj_size() {return obj_size;} private long obj_size; + public void On__init(Split_rslt_mgr rslt_mgr, Db_conn wkr_conn) { + this.rslt_mgr = rslt_mgr; + this.wkr_conn = wkr_conn; + wkr_conn.Meta_tbl_remake(Dbmeta_tbl_itm.New(tbl_name + , Dbmeta_fld_itm.new_int("db_id") + , Dbmeta_fld_itm.new_int(pkey_name) + )); + this.stmt = wkr_conn.Stmt_insert(tbl_name, "db_id", pkey_name); + } + public void On__nth__new(int db_id) { + this.db_id = db_id; + this.obj_size = 0; + } + public void On__nth__itm(int size, int pkey) { + obj_size += size; + rslt_mgr.Db_size_add_(size); + pkey_ary.Add(pkey); + } + public void On__nth__rls() { + wkr_conn.Txn_bgn(tbl_name); + int len = pkey_ary.Len(); + for (int i = 0; i < len; ++i) { + stmt.Clear().Val_int("db_id", db_id).Val_int(pkey_name, pkey_ary.Get_at_or_fail(i)).Exec_insert(); + } + wkr_conn.Txn_end(); + pkey_ary.Clear(); + } + public void On_term() { + stmt = Db_stmt_.Rls(stmt); + wkr_conn.Meta_idx_create(tbl_name, "page_id", pkey_name); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_wkr__objs__base.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_wkr__objs__base.java index a27517de8..c6017c14b 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_wkr__objs__base.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Split_rslt_wkr__objs__base.java @@ -13,3 +13,61 @@ 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.addons.bldrs.exports.splits.rslts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.core.primitives.*; import gplx.dbs.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +public abstract class Split_rslt_wkr__objs__base implements Split_rslt_wkr { + private Split_rslt_mgr rslt_mgr; + private Db_conn wkr_conn; private Db_stmt stmt; private int db_id; + private final List_adp pkey_list = List_adp_.New(); + private final String tbl_name; private final int pkey_flds_len; private final Dbmeta_fld_itm[] pkey_flds; private final String[] pkey_names; + public Split_rslt_wkr__objs__base() { + this.tbl_name = Tbl_name(); + this.pkey_flds = Pkey_flds(); + this.pkey_flds_len = pkey_flds.length; + this.pkey_names = new String[pkey_flds_len]; + for (int i = 0; i < pkey_flds_len; ++i) + pkey_names[i] = pkey_flds[i].Name(); + } + public abstract byte Tid(); + public abstract String Tbl_name(); + public abstract Dbmeta_fld_itm[] Pkey_flds(); + public int Row_count() {return pkey_list.Len();} + public long Obj_size() {return obj_size;} private long obj_size; + public void On__init(Split_rslt_mgr rslt_mgr, Db_conn wkr_conn) { + this.rslt_mgr = rslt_mgr; + this.wkr_conn = wkr_conn; + Dbmeta_tbl_itm meta_tbl = Dbmeta_tbl_itm.New(tbl_name, Dbmeta_fld_itm.new_int("db_id")); + for (Dbmeta_fld_itm pkey_fld : pkey_flds ) + meta_tbl.Flds().Add(pkey_fld); + wkr_conn.Meta_tbl_remake(meta_tbl); + this.stmt = wkr_conn.Stmt_insert(tbl_name, String_.Ary_add(String_.Ary("db_id"), pkey_names)); + } + public void On__nth__new(int db_id) { + this.db_id = db_id; + this.obj_size = 0; + } + public void On__nth__itm(int size, Object... pkey_objs) { + this.obj_size += size; + rslt_mgr.Db_size_add_(size); + pkey_list.Add(pkey_objs); + } + public void On__nth__rls() { + wkr_conn.Txn_bgn(tbl_name); + int len = pkey_list.Len(); + for (int i = 0; i < len; ++i) { + stmt.Clear().Val_int("db_id", db_id); + Object[] pkey_objs = (Object[])pkey_list.Get_at(i); + for (int j = 0; j < pkey_flds_len; ++j) { + Dbmeta_fld_itm pkey_fld = pkey_flds[j]; + gplx.dbs.stmts.Db_stmt_arg_list.Fill_val(stmt, pkey_fld.Type().Tid_ansi(), pkey_fld.Name(), pkey_objs[j]); + } + stmt.Exec_insert(); + } + wkr_conn.Txn_end(); + pkey_list.Clear(); + } + public void On_term() { + stmt = Db_stmt_.Rls(stmt); + wkr_conn.Meta_idx_create(tbl_name, "pkey", pkey_names); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Wkr_stats_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Wkr_stats_itm.java index a27517de8..0629a247a 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Wkr_stats_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Wkr_stats_itm.java @@ -13,3 +13,10 @@ 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.addons.bldrs.exports.splits.rslts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +public class Wkr_stats_itm { + public Wkr_stats_itm(int id, int count, long size) {this.Id = id; this.Count = count; this.Size = size;} + public final int Id; + public final int Count; + public final long Size; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Wkr_stats_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Wkr_stats_tbl.java index a27517de8..f11fd6e80 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Wkr_stats_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/rslts/Wkr_stats_tbl.java @@ -13,3 +13,49 @@ 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.addons.bldrs.exports.splits.rslts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; +public class Wkr_stats_tbl implements Rls_able { + private final String tbl_name; + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_wkr_id, fld_wkr_count, fld_wkr_size; + public final Db_conn conn; + public Wkr_stats_tbl(Db_conn conn) { + this.conn = conn; conn.Rls_reg(this); + this.tbl_name = "wkr_stats"; + this.fld_wkr_id = flds.Add_int_pkey("wkr_id"); + this.fld_wkr_count = flds.Add_int("wkr_count"); + this.fld_wkr_size = flds.Add_long("wkr_size"); + } + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public Db_stmt Insert_stmt() {return conn.Stmt_insert(tbl_name, flds);} + public void Insert(Db_stmt stmt, int wkr_id, int wkr_count, long wkr_size) { + stmt.Clear() + .Val_int(fld_wkr_id , wkr_id) + .Val_int(fld_wkr_count , wkr_count) + .Val_long(fld_wkr_size , wkr_size) + .Exec_insert(); + } + public Wkr_stats_itm[] Select_all() { + List_adp list = List_adp_.New(); + Db_rdr rdr = conn.Stmt_select_all(tbl_name, flds).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + Wkr_stats_itm itm = new Wkr_stats_itm(rdr.Read_int(fld_wkr_id), rdr.Read_int(fld_wkr_count), rdr.Read_int(fld_wkr_size)); + list.Add(itm); + } + rdr.Rls(); + } finally {rdr.Rls();} + return (Wkr_stats_itm[])list.To_ary_and_clear(Wkr_stats_itm.class); + } + public Wkr_stats_itm Select_all__summary() { + Db_rdr rdr = conn.Stmt_sql(String_.Format("SELECT Sum({0}) AS {0}, Sum({1}) AS {1} FROM {2}", fld_wkr_count, fld_wkr_size, tbl_name)).Exec_select__rls_auto(); // ANSI.Y + try { + if (rdr.Move_next()) + return new Wkr_stats_itm(-1, rdr.Read_int(fld_wkr_count), rdr.Read_long(fld_wkr_size)); + else + throw Err_.new_wo_type("failed to get sum of wkr_size", "url", conn.Conn_info().Raw()); + } finally {rdr.Rls();} + } + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_meta_wkr__link.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_meta_wkr__link.java index a27517de8..27ac014ae 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_meta_wkr__link.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_meta_wkr__link.java @@ -13,3 +13,55 @@ 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.addons.bldrs.exports.splits.srchs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; +import gplx.xowa.addons.bldrs.exports.splits.metas.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.dbs.*; +class Split_meta_wkr__link extends Split_meta_wkr_base { + private final Srch_db_mgr srch_db_mgr; + private Srch_link_tbl tbl; + private Db_stmt stmt; + private final Split_rslt_wkr__link rslt_wkr = new Split_rslt_wkr__link(); + public Split_meta_wkr__link(Split_ctx ctx, Srch_db_mgr srch_db_mgr) { + this.srch_db_mgr = srch_db_mgr; + ctx.Rslt_mgr().Reg_wkr(rslt_wkr); + } + @Override public byte Tid() {return Split_page_list_type_.Tid__srch_link;} + @Override public void On_nth_new(Split_ctx ctx, Db_conn trg_conn) { + this.tbl = new Srch_link_tbl(trg_conn); + Dbmeta_fld_list trg_flds = tbl.Flds().Clone().New_int("trg_db_id"); + trg_conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl.Tbl_name(), trg_flds)); + this.stmt = trg_conn.Stmt_insert(tbl.Tbl_name(), trg_flds); + } + @Override public void On_nth_rls(Split_ctx ctx, Db_conn trg_conn) { + this.stmt = Db_stmt_.Rls(stmt); + trg_conn.Meta_idx_create(tbl.Tbl_name(), "blob" , "trg_db_id", "word_id", "page_id"); + } + @Override protected String Load_sql(Db_attach_mgr attach_mgr, int ns_id, int score_bgn, int score_end) { + int trg_db_id = srch_db_mgr.Tbl__link__get_idx(ns_id); + attach_mgr.Conn_links_(new Db_attach_itm("link_db", srch_db_mgr.Tbl__link__get_at(trg_db_id).conn)); + return String_.Concat_lines_nl + ( "SELECT sl.word_id, sl.page_id, sl.link_score, sw.page_id, " + trg_db_id + " AS trg_db_id" + , "FROM search_link sl" + , " JOIN split_search_word sw ON sw.word_id = sl.word_id" + , "WHERE sw.page_score >= {0}" + , "AND sw.page_score < {1}" + , "AND sw.page_ns = {2}" + , "GROUP BY sl.word_id, sl.page_id, sl.link_score" + , "ORDER BY sw.page_id" + ); + } + @Override protected Object Load_itm(Db_rdr rdr) { + Srch_link_row rv = tbl.New_row(rdr); + rv.Trg_db_id = rdr.Read_int("trg_db_id"); + return rv; + } + @Override protected void Save_itm(Split_ctx ctx, Split_rslt_mgr rslt_mgr, Object itm_obj) { + Srch_link_row itm = (Srch_link_row)itm_obj; + stmt.Clear(); + tbl.Fill_for_insert(stmt, itm); + stmt.Val_int("trg_db_id", itm.Trg_db_id); + stmt.Exec_insert(); + rslt_wkr.On__nth__itm(itm.Db_row_size(), itm.Word_id, itm.Page_id); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_meta_wkr__word.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_meta_wkr__word.java index a27517de8..aa9d5c85c 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_meta_wkr__word.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_meta_wkr__word.java @@ -13,3 +13,38 @@ 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.addons.bldrs.exports.splits.srchs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; +import gplx.xowa.addons.bldrs.exports.splits.metas.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.dbs.*; +class Split_meta_wkr__word extends Split_meta_wkr_base { + private Srch_word_tbl tbl; + private Db_stmt stmt; + private final Split_rslt_wkr__word rslt_wkr = new Split_rslt_wkr__word(); + public Split_meta_wkr__word(Split_ctx ctx) { + ctx.Rslt_mgr().Reg_wkr(rslt_wkr); + } + @Override public byte Tid() {return Split_page_list_type_.Tid__srch_word;} + @Override public void On_nth_new(Split_ctx ctx, Db_conn trg_conn) { + this.tbl = new Srch_word_tbl(trg_conn); + tbl.Create_tbl(); + this.stmt = trg_conn.Stmt_insert(tbl.tbl_name, tbl.flds); + } + @Override public void On_nth_rls(Split_ctx ctx, Db_conn trg_conn) {this.stmt = Db_stmt_.Rls(stmt);} + @Override protected String Load_sql(Db_attach_mgr attach_mgr, int ns_id, int score_bgn, int score_end) { + return String_.Concat_lines_nl + ( "SELECT sw.word_id, sw.word_text, sw.link_count, sw.link_count_score, sw.link_score_min, sw.link_score_max, sw.page_id" + , "FROM split_search_word sw" + , "WHERE sw.page_score >= {0}" + , "AND sw.page_score < {1}" + , "AND sw.page_ns = {2}" + , "ORDER BY page_id" + ); + } + @Override protected Object Load_itm(Db_rdr rdr) {return tbl.New_row(rdr);} + @Override protected void Save_itm(Split_ctx ctx, Split_rslt_mgr rslt_mgr, Object itm_obj) { + Srch_word_row itm = (Srch_word_row)itm_obj; + tbl.Insert_by_itm(stmt, itm); + rslt_wkr.On__nth__itm(itm.Db_row_size(), itm.Id); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_rslt_wkr__link.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_rslt_wkr__link.java index a27517de8..d9a4f9d6a 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_rslt_wkr__link.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_rslt_wkr__link.java @@ -13,3 +13,13 @@ 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.addons.bldrs.exports.splits.srchs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; +import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +class Split_rslt_wkr__link extends Split_rslt_wkr__objs__base { + @Override public byte Tid() {return Split_rslt_tid_.Tid__srch_link;} + @Override public String Tbl_name() {return "rslt_srch_link";} + @Override public Dbmeta_fld_itm[] Pkey_flds() { + return new Dbmeta_fld_itm[] {Dbmeta_fld_itm.new_int("word_id"), Dbmeta_fld_itm.new_int("page_id")}; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_rslt_wkr__word.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_rslt_wkr__word.java index a27517de8..386ede8a1 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_rslt_wkr__word.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_rslt_wkr__word.java @@ -13,3 +13,10 @@ 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.addons.bldrs.exports.splits.srchs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +class Split_rslt_wkr__word extends Split_rslt_wkr__int__base { + @Override public byte Tid() {return Split_rslt_tid_.Tid__srch_word;} + @Override public String Tbl_name() {return "rslt_srch_word";} + @Override public String Pkey_name() {return "word_id";} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_srch_init.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_srch_init.java index a27517de8..87f1c6003 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_srch_init.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_srch_init.java @@ -13,3 +13,47 @@ 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.addons.bldrs.exports.splits.srchs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; import gplx.xowa.addons.wikis.searchs.dbs.*; +import gplx.xowa.addons.bldrs.exports.splits.mgrs.*; +class Split_srch_init { + public void Init(Split_ctx ctx, Xow_wiki wiki, Db_conn wkr_conn, Srch_db_mgr srch_db_mgr) { + // create search_word w/ page attributes + if (!(ctx.Cfg().Force_rebuild() || !wkr_conn.Meta_tbl_exists("split_search_word"))) return; + Gfo_log_.Instance.Prog("creating split_search_word"); + wkr_conn.Meta_tbl_remake(Dbmeta_tbl_itm.New("split_search_word" + , Dbmeta_fld_itm.new_int("word_id") + , Dbmeta_fld_itm.new_str("word_text", 255) + , Dbmeta_fld_itm.new_int("link_count") + , Dbmeta_fld_itm.new_int("link_count_score") + , Dbmeta_fld_itm.new_int("link_score_min") + , Dbmeta_fld_itm.new_int("link_score_max") + , Dbmeta_fld_itm.new_int("page_uid") + , Dbmeta_fld_itm.new_int("page_ns") + , Dbmeta_fld_itm.new_int("page_id") + , Dbmeta_fld_itm.new_int("page_score") + )); + + // insert search_word w/ Min(pr.page_uid) + Db_attach_mgr attach_mgr = new Db_attach_mgr(wkr_conn); + Db_conn word_conn = srch_db_mgr.Tbl__word().conn; + int len = srch_db_mgr.Tbl__link__len(); + for (int i = 0; i < len; ++i) { + Db_conn link_conn = srch_db_mgr.Tbl__link__get_at(i).conn; + attach_mgr.Conn_links_(new Db_attach_itm("word_db", word_conn), new Db_attach_itm("link_db", link_conn)); + attach_mgr.Exec_sql(String_.Concat_lines_nl // ANSI.Y + ( "INSERT INTO split_search_word (word_id, word_text, link_count, link_count_score, link_score_min, link_score_max, page_uid, page_ns, page_id, page_score)" + , "SELECT sw.word_id, sw.word_text, sw.link_count, sw.link_count_score, sw.link_score_min, sw.link_score_max, Min(pr.page_uid), -1, -1, -1" + , "FROM search_word sw" + , " JOIN search_link sl ON sw.word_id = sl.word_id" + , " JOIN page_regy pr ON sl.page_id = pr.page_id" + , " LEFT JOIN split_search_word ssw ON ssw.word_id = sw.word_id" + , "WHERE ssw.word_id IS NULL" + , "GROUP BY sw.word_id, sw.word_text, sw.link_count, sw.link_count_score, sw.link_score_min, sw.link_score_max" + )); + } + wkr_conn.Meta_idx_create("split_search_word", "page_id", "page_id"); + + Split_mgr_init.Update_page_cols(wkr_conn, "split_search_word"); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_wkr__srch.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_wkr__srch.java index a27517de8..95eaa1554 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_wkr__srch.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/splits/srchs/Split_wkr__srch.java @@ -13,3 +13,45 @@ 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.addons.bldrs.exports.splits.srchs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; import gplx.xowa.addons.bldrs.exports.splits.*; +import gplx.dbs.*; import gplx.dbs.bulks.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.dbs.*; +import gplx.xowa.addons.bldrs.exports.splits.metas.*; import gplx.xowa.addons.bldrs.exports.splits.rslts.*; +public class Split_wkr__srch implements Split_wkr { + private Srch_db_mgr srch_db_mgr; + private Split_meta_wkr_base[] meta_wkrs; + public void Split__init(Split_ctx ctx, Xow_wiki wiki, Db_conn wkr_conn) { + Srch_search_addon srch_addon = Srch_search_addon.Get(wiki); + this.srch_db_mgr = srch_addon.Db_mgr(); + new Split_srch_init().Init(ctx, wiki, ctx.Wkr_conn(), srch_db_mgr); + this.meta_wkrs = new Split_meta_wkr_base[] + { new Split_meta_wkr__word(ctx) + , new Split_meta_wkr__link(ctx, srch_db_mgr) + }; + } + public void Split__trg__1st__new(Split_ctx ctx, Db_conn trg_conn) { + Db_conn src_conn = srch_db_mgr.Tbl__word().conn; + Db_tbl_copy copy_mgr = new Db_tbl_copy(); + copy_mgr.Copy_many(src_conn, trg_conn, "search_link_reg"); + copy_mgr.Copy_one (src_conn, trg_conn, "xowa_cfg", "xowa_cfg__srch"); + } + public void Split__trg__nth__new(Split_ctx ctx, Db_conn wkr_conn) { + for (Split_meta_wkr_base wkr : meta_wkrs) + wkr.On_nth_new(ctx, wkr_conn); + } + public void Split__trg__nth__rls(Split_ctx ctx, Db_conn trg_conn) { + for (Split_meta_wkr_base wkr : meta_wkrs) + wkr.On_nth_rls(ctx, trg_conn); + } + public void Split__pages_loaded(Split_ctx ctx, int ns_id, int score_bgn, int score_end) { + for (Split_meta_wkr_base wkr : meta_wkrs) + wkr.Load(ctx.Wkr_conn(), ctx.Page_mgr(), ns_id, score_bgn, score_end); + } + public void Split__exec(Split_ctx ctx, Split_rslt_mgr rslt_mgr, Xowd_page_itm page, int page_id) { + Split_page_itm meta_page = ctx.Page_mgr().Get_by_or_null(page_id); if (meta_page == null) return; // NOTE: pages may not have images + for (Split_meta_wkr_base wkr : meta_wkrs) + wkr.Save(ctx, rslt_mgr, meta_page); + } + public void Split__term(Split_ctx ctx) { + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/utls/Split_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/utls/Split_tbl.java index a27517de8..ab10c5401 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/utls/Split_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/utls/Split_tbl.java @@ -13,3 +13,108 @@ 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.addons.bldrs.exports.utls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; +public interface Split_tbl { + String Tbl_name(); + boolean Layout_is_lot(); + String[] Fld_pkeys(); + String Fld_blob(); + Dbmeta_fld_list Flds(); + Db_conn Wiki_conn__get_or_new(Xow_wiki wiki, int db_id); +} +class Split_tbl__page implements Split_tbl { + public String Tbl_name() {return "page";} + public boolean Layout_is_lot() {return Bool_.N;} + public String[] Fld_pkeys() {return String_.Ary("page_id");} + public String Fld_blob() {return String_.Empty;} + public Dbmeta_fld_list Flds() {if (flds == null) flds = new gplx.xowa.wikis.data.tbls.Xowd_page_tbl(Db_conn_.Noop, Bool_.N).Flds__all(); return flds;} private Dbmeta_fld_list flds; + public Db_conn Wiki_conn__get_or_new(Xow_wiki wiki, int db_id) {return wiki.Data__core_mgr().Db__core().Conn();} +} +class Split_tbl__fsdb_fil implements Split_tbl { + public String Tbl_name() {return "fsdb_fil";} + public boolean Layout_is_lot() {return Bool_.N;} + public String[] Fld_pkeys() {return String_.Ary("fil_id");} + public String Fld_blob() {return String_.Empty;} + public Dbmeta_fld_list Flds() {if (flds == null) flds = new gplx.fsdb.data.Fsd_fil_tbl(Db_conn_.Noop, Bool_.N, gplx.fsdb.meta.Fsm_mnt_mgr.Mnt_idx_main).flds; return flds;} private Dbmeta_fld_list flds; + public Db_conn Wiki_conn__get_or_new(Xow_wiki wiki, int db_id) {return wiki.File__mnt_mgr().Mnts__get_main().Atr_mgr().Db__core().Conn();} +} +class Split_tbl__fsdb_thm implements Split_tbl { + public String Tbl_name() {return "fsdb_thm";} + public boolean Layout_is_lot() {return Bool_.N;} + public String[] Fld_pkeys() {return String_.Ary("thm_id");} + public String Fld_blob() {return String_.Empty;} + public Dbmeta_fld_list Flds() {if (flds == null) flds = new gplx.fsdb.data.Fsd_thm_tbl(Db_conn_.Noop, Bool_.N, gplx.fsdb.meta.Fsm_mnt_mgr.Mnt_idx_main, Bool_.Y).flds; return flds;} private Dbmeta_fld_list flds; + public Db_conn Wiki_conn__get_or_new(Xow_wiki wiki, int db_id) {return wiki.File__mnt_mgr().Mnts__get_main().Atr_mgr().Db__core().Conn();} +} +class Split_tbl__fsdb_reg implements Split_tbl { + public String Tbl_name() {return "orig_reg";} + public boolean Layout_is_lot() {return Bool_.N;} + public String[] Fld_pkeys() {return String_.Ary("orig_ttl");} + public String Fld_blob() {return String_.Empty;} + public Dbmeta_fld_list Flds() {if (flds == null) flds = new gplx.xowa.files.origs.Xof_orig_tbl(Db_conn_.Noop, Bool_.N).flds; return flds;} private Dbmeta_fld_list flds; + public Db_conn Wiki_conn__get_or_new(Xow_wiki wiki, int db_id) {return wiki.File__mnt_mgr().Mnts__get_main().Atr_mgr().Db__core().Conn();} +} +class Split_tbl__srch_word implements Split_tbl { + public String Tbl_name() {return "search_word";} + public boolean Layout_is_lot() {return Bool_.N;} + public String[] Fld_pkeys() {return String_.Ary("word_id");} + public String Fld_blob() {return String_.Empty;} + public Dbmeta_fld_list Flds() {if (flds == null) flds = new gplx.xowa.addons.wikis.searchs.dbs.Srch_word_tbl(Db_conn_.Noop).flds; return flds;} private Dbmeta_fld_list flds; + public Db_conn Wiki_conn__get_or_new(Xow_wiki wiki, int db_id) {return gplx.xowa.addons.wikis.searchs.Srch_search_addon.Get(wiki).Db_mgr().Tbl__word().conn;} +} +class Split_tbl__srch_link implements Split_tbl { + public String Tbl_name() {return "search_link";} + public boolean Layout_is_lot() {return Bool_.N;} + public String[] Fld_pkeys() {return String_.Ary("word_id", "page_id");} + public String Fld_blob() {return String_.Empty;} + public Dbmeta_fld_list Flds() {if (flds == null) flds = new gplx.xowa.addons.wikis.searchs.dbs.Srch_link_tbl(Db_conn_.Noop).Flds(); return flds;} private Dbmeta_fld_list flds; + public Db_conn Wiki_conn__get_or_new(Xow_wiki wiki, int db_id) {return gplx.xowa.addons.wikis.searchs.Srch_search_addon.Get(wiki).Db_mgr().Tbl__link__get_at(db_id).conn;} +} +class Split_tbl__rndm_seq implements Split_tbl { + public String Tbl_name() {return "rndm_seq";} + public boolean Layout_is_lot() {return Bool_.N;} + public String[] Fld_pkeys() {return String_.Ary("mgr_idx", "rng_idx", "seq_idx");} + public String Fld_blob() {return String_.Empty;} + public Dbmeta_fld_list Flds() {if (flds == null) flds = new gplx.xowa.addons.wikis.pages.randoms.dbs.Rndm_seq_tbl(Db_conn_.Noop).Flds(); return flds;} private Dbmeta_fld_list flds; + public Db_conn Wiki_conn__get_or_new(Xow_wiki wiki, int db_id) {return gplx.xowa.addons.wikis.pages.randoms.Rndm_addon.Get(wiki).Mgr().Db_mgr().Tbl__seq().Conn();} +} +class Split_tbl__html implements Split_tbl { + public String Tbl_name() {return "html";} + public boolean Layout_is_lot() {return Bool_.Y;} + public String[] Fld_pkeys() {return String_.Ary("page_id");} + public String Fld_blob() {return "body";} + public Dbmeta_fld_list Flds() {if (flds == null) flds = new gplx.xowa.htmls.core.dbs.Xowd_html_tbl(Db_conn_.Noop).Flds(); return flds;} private Dbmeta_fld_list flds; + public Db_conn Wiki_conn__get_or_new(Xow_wiki wiki, int db_id) { + if (db_id == -1) // HACK: return core_conn just so that bin_tbl below can be created + return wiki.Data__core_mgr().Db__core().Conn(); + else { + gplx.xowa.wikis.data.Xow_db_file db_file = wiki.Data__core_mgr().Dbs__get_by_id_or_null(db_id); + if (db_file == null) { + db_file = wiki.Data__core_mgr().Dbs__make_by_id(db_id, gplx.xowa.wikis.data.Xow_db_file_.Tid__html_data, "0", 0, "-html-db." + Int_.To_str_pad_bgn_zero(db_id, 4) + ".xowa"); + db_file.Tbl__html().Create_tbl(); + } + return db_file.Conn(); + } + } +} +class Split_tbl__fsdb_bin implements Split_tbl { + public String Tbl_name() {return "fsdb_bin";} + public boolean Layout_is_lot() {return Bool_.Y;} + public String[] Fld_pkeys() {return String_.Ary("bin_owner_id");} + public String Fld_blob() {return "bin_data";} + public Dbmeta_fld_list Flds() {if (flds == null) flds = new gplx.fsdb.data.Fsd_bin_tbl(Db_conn_.Noop, Bool_.N).Flds(); return flds;} private Dbmeta_fld_list flds; + public Db_conn Wiki_conn__get_or_new(Xow_wiki wiki, int db_id) { + if (db_id == -1) // HACK: return core_conn just so that bin_tbl below can be created + return wiki.Data__core_mgr().Db__core().Conn(); + else { + gplx.fsdb.meta.Fsm_bin_mgr bin_mgr = wiki.File__mnt_mgr().Mnts__get_main().Bin_mgr(); + gplx.fsdb.meta.Fsm_bin_fil db_file = bin_mgr.Dbs__get_by_or_null(db_id); // try to get existing + if (db_file == null) { // none exists; create new + db_file = bin_mgr.Dbs__make(db_id, wiki.Domain_str() + "-file-db." + Int_.To_str_pad_bgn_zero(db_id, 4) + ".xowa"); + new gplx.fsdb.data.Fsd_bin_tbl(db_file.Conn(), Bool_.N).Create_tbl(); + } + return db_file.Conn(); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/exports/utls/Split_tbl_.java b/400_xowa/src/gplx/xowa/addons/bldrs/exports/utls/Split_tbl_.java index a27517de8..4b8161638 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/exports/utls/Split_tbl_.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/exports/utls/Split_tbl_.java @@ -13,3 +13,33 @@ 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.addons.bldrs.exports.utls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.exports.*; +import gplx.dbs.*; +public class Split_tbl_ { + public static final Split_tbl + Page = new Split_tbl__page() + , Html = new Split_tbl__html() + , Srch_word = new Split_tbl__srch_word() + , Srch_link = new Split_tbl__srch_link() + , Fsdb_fil = new Split_tbl__fsdb_fil() + , Fsdb_thm = new Split_tbl__fsdb_thm() + , Fsdb_org = new Split_tbl__fsdb_reg() + , Fsdb_bin = new Split_tbl__fsdb_bin() + ; + + public static boolean Tbl_has_blob(Split_tbl tbl) {return String_.Len_gt_0(tbl.Fld_blob());} + public static void Flds__add_blob_len(Dbmeta_fld_list flds) { + flds.Insert(flds.Len() - 1, Dbmeta_fld_itm.new_int("blob_len")); // add "blob_len" in penultimate pos; note that last fld is "blob_fld" + } + public static void Bld_insert_by_select(Bry_bfr bfr, String tbl_name, Dbmeta_fld_list flds) { + int flds_len = flds.Len(); + for (int i = 0; i < flds_len; ++i) { + if (i != 0) bfr.Add_str_a7(","); + Dbmeta_fld_itm fld = flds.Get_at(i); + bfr.Add_str_a7(fld.Name()); + } + byte[] flds_bry = bfr.To_bry_and_clear(); + bfr.Add_str_u8_fmt("INSERT INTO {0}\n({1})\n", tbl_name, flds_bry); // ANSI.Y + bfr.Add_str_u8_fmt("SELECT {0}\nFROM {1}\n", flds_bry, tbl_name); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/Xoax_builds_files_addon.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/Xoax_builds_files_addon.java index a27517de8..5a8ee7b5a 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/Xoax_builds_files_addon.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/Xoax_builds_files_addon.java @@ -13,3 +13,45 @@ 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.addons.bldrs.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.addons.bldrs.files.cmds.*; +import gplx.xowa.addons.bldrs.mass_parses.inits.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; import gplx.xowa.addons.bldrs.mass_parses.makes.*; import gplx.xowa.addons.bldrs.mass_parses.resumes.*; +import gplx.xowa.addons.bldrs.files.cksums.*; import gplx.xowa.addons.bldrs.files.checks.*; +import gplx.xowa.addons.bldrs.app_cfgs.wm_server_cfgs.*; +import gplx.xowa.addons.bldrs.files.missing_origs.*; +public class Xoax_builds_files_addon implements Xoax_addon_itm, Xoax_addon_itm__bldr { + public Xob_cmd[] Bldr_cmds() { + return new Xob_cmd[] + { Xobldr__lnki_temp__create.Prototype + , Xobldr__lnki_regy__create.Prototype + , Xobldr__page_regy__create.Prototype + , Xobldr__orig_regy__create.Prototype + , Xobldr_missing_origs_cmd.Prototype + , Xobldr__xfer_temp__insert_thm.Prototype + , Xobldr__xfer_temp__insert_orig.Prototype + , Xobldr__xfer_regy__create.Prototype + , Xobldr__xfer_regy__update_downloaded.Prototype + + , Xobldr__fsdb_db__create_data.Prototype + , Xobldr__fsdb_db__create_orig.Prototype + , Xobldr__page_file_map__create.Prototype + + , Xobldr__text_db__make_page.Prototype + , Xobldr__text_db__drop_page.Prototype + , Xobldr__redirect__create.Prototype + , Xobldr__image__create.Prototype + + , Xomp_init_cmd.Prototype + , Xomp_parse_cmd.Prototype + , Xomp_make_cmd.Prototype + , Xomp_resume_cmd.Prototype + , Xocksum_calc_cmd.Prototype + , Xocheck_cmd.Prototype + + , Xowm_server_cfg_cmd.Prototype + }; + } + + public String Addon__key() {return "xowa.builds.images";} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/checks/Xocheck_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/checks/Xocheck_cmd.java index a27517de8..2c5297204 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/checks/Xocheck_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/checks/Xocheck_cmd.java @@ -13,3 +13,16 @@ 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.addons.bldrs.files.checks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xocheck_cmd extends Xob_cmd__base { // checks fsdb; needed for en.w and multiple monthly updates + public Xocheck_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + wiki.Init_assert(); + new Xocheck_mgr().Exec(wiki); + } + + @Override public String Cmd_key() {return BLDR_CMD_KEY;} private static final String BLDR_CMD_KEY = "fsdb.check"; + public static final Xob_cmd Prototype = new Xocheck_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xocheck_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/checks/Xocheck_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/checks/Xocheck_mgr.java index a27517de8..83f93ea28 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/checks/Xocheck_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/checks/Xocheck_mgr.java @@ -13,3 +13,75 @@ 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.addons.bldrs.files.checks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.core.ios.streams.*; +import gplx.dbs.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.files.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.origs.*; +import gplx.xowa.addons.bldrs.wmdumps.imglinks.*; +import gplx.xowa.htmls.*; +// TODO.XO:cache files in memory, else commonly used files (Wiki.png) will be loaded from fsdb for every usage on page +// TODO.XO:save results to db to verify unused images (images in fsdb, but not loaded during this code) +class Xocheck_mgr { + private final Xof_url_bldr url_bldr = Xof_url_bldr.new_v2(); private final Xof_img_size img_size = new Xof_img_size(); + private Xowe_wiki wiki; + public void Exec(Xowe_wiki wiki) { + // init + this.wiki = wiki; + wiki.File__bin_mgr().Wkrs__del(gplx.xowa.files.bins.Xof_bin_wkr_.Key_http_wmf); // must happen after init_file_mgr_by_load; remove wmf wkr, else will try to download images during parsing + wiki.File_mgr().Fsdb_mode().Tid__v2__mp__y_(); + wiki.App().Cfg().Set_bool_app("xowa.app.web.enabled", false); // never enable inet; rely solely on local dbs; + + // select list of pages + Xoh_page hpg = new Xoh_page(); + Xowd_page_tbl page_tbl = wiki.Data__core_mgr().Db__core().Tbl__page(); + Db_rdr rdr = page_tbl.Conn().Stmt_sql("SELECT page_id, page_namespace, page_title, page_html_db_id FROM page WHERE page_html_db_id != -1;").Exec_select__rls_auto(); + int page_count = 0, file_count = 0; + + // loop over each page + while (rdr.Move_next()) { + // init page meta + Xoa_ttl page_ttl = wiki.Ttl_parse(rdr.Read_int("page_namespace"), rdr.Read_bry_by_str("page_title")); + Xoa_url page_url = Xoa_url.New(wiki, page_ttl); + Xow_db_file html_db = wiki.Data__core_mgr().Dbs__get_by_id_or_fail(rdr.Read_int("page_html_db_id")); + int page_id = rdr.Read_int("page_id"); + + // load html + hpg.Ctor_by_hview(wiki, page_url, page_ttl, page_id); + if (!html_db.Tbl__html().Select_by_page(hpg)) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "could not load html for page; page_id=~{0}", page_id); + continue; + } + wiki.Html__hdump_mgr().Load_mgr().Parse(hpg, hpg.Db().Html().Zip_tid(), hpg.Db().Html().Hzip_tid(), hpg.Db().Html().Html_bry()); + + // load images + int imgs_len = hpg.Img_mgr().Len(); + for (int i = 0; i < imgs_len; i++) { + Xof_fsdb_itm fsdb = hpg.Img_mgr().Get_at(i); + try {Check_images(page_ttl, fsdb);} + catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "file failed; page_ttl=~{0} img_name=~{1} err=~{2}", page_ttl.Page_db(), fsdb.Lnki_ttl(), Err_.Message_gplx_log(e)); + } + file_count++; + } + + // prog + page_count++; + if ((page_count % 10000) == 0) { + Gfo_usr_dlg_.Instance.Prog_many("", "", "checking pages; pages=~{0} files=~{1}", page_count, file_count); + } + } + } + private void Check_images(Xoa_ttl page_ttl, Xof_fsdb_itm fsdb) { + // get orig + Xof_orig_itm orig = wiki.File__orig_mgr().Find_by_ttl_or_null(fsdb.Lnki_ttl()); + if (orig == null) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "file missing; page_ttl=~{0} img_name=~{1}", page_ttl.Page_db(), fsdb.Lnki_ttl()); + return; + } + Xof_file_wkr.Eval_orig(orig, fsdb, url_bldr, wiki.File__repo_mgr(), img_size); + + Io_stream_rdr img_rdr = wiki.File__bin_mgr().Find_as_rdr(Xof_exec_tid.Tid_wiki_page, fsdb); + img_rdr.Rls(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/Xocksum_calc_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/Xocksum_calc_cmd.java index a27517de8..7f6c2eb0b 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/Xocksum_calc_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/Xocksum_calc_cmd.java @@ -13,3 +13,16 @@ 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.addons.bldrs.files.cksums; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xocksum_calc_cmd extends Xob_cmd__base { + public Xocksum_calc_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + wiki.Init_assert(); + new Xocksum_calc_mgr().Exec(wiki); + } + + @Override public String Cmd_key() {return BLDR_CMD_KEY;} private static final String BLDR_CMD_KEY = "fsdb.cksums.calc"; + public static final Xob_cmd Prototype = new Xocksum_calc_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xocksum_calc_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/Xocksum_calc_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/Xocksum_calc_mgr.java index a27517de8..f336ae185 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/Xocksum_calc_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/Xocksum_calc_mgr.java @@ -13,3 +13,65 @@ 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.addons.bldrs.files.cksums; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.core.ios.streams.*; import gplx.core.security.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.files.cksums.dbs.*; +import gplx.xowa.files.*; import gplx.fsdb.*; import gplx.fsdb.data.*; +public class Xocksum_calc_mgr { + public void Exec(Xowe_wiki wiki) { + // get conn variables + Xocksum_cksum_db db = Xocksum_cksum_db.Get(wiki); + Db_conn conn = db.Conn(); + Xocksum_cksum_tbl tbl = db.Tbl__cksum(); + conn.Meta_tbl_assert(tbl); + + // insert missing items + tbl.Insert_missing(); + tbl.Create_idx(); + + // get updates + int count = 0; + Hash_algo md5_algo = Hash_algo_.New__md5(); + List_adp updates = List_adp_.New(); + String cur_date = Datetime_now.Get().XtoStr_gplx(); + Db_stmt select_stmt = tbl.Select_samples_stmt(10000); + while (true) { + // get cksum_rows + Xocksum_cksum_row[] rows = tbl.Select_samples(select_stmt); + + // loop cksum_rows and (a) get bin_data; (b) if md5 diff, then add to updates + int len = rows.length; if (len == 0) break; + for (int i = 0; i < len; ++i) { + Xocksum_cksum_row row = rows[i]; + byte[] bin_bry = Get_bin(wiki, row); + if (bin_bry == null) { + Gfo_usr_dlg_.Instance.Prog_many("", "", "null; fil_id=~{0} thm_id=~{1}", row.Fil_id(), row.Thm_id()); + bin_bry = Bry_.Empty; + } + row.Bin_size_(bin_bry.length); + byte[] md5 = md5_algo.Hash_bry_as_bry(bin_bry); + if (!Bry_.Eq(md5, row.Cksum_val())) { + row.Cksum_val_(md5); + updates.Add(row); + } + } + + // run updates + conn.Txn_bgn("cksum_update"); + len = updates.Len(); + for (int i = 0; i < len; ++i) { + Xocksum_cksum_row row = (Xocksum_cksum_row)updates.Get_at(i); + tbl.Update(row.Fil_id(), row.Thm_id(), row.Bin_db_id(), row.Bin_size(), row.Cksum_tid(), 0, row.Cksum_val(), cur_date); + if (++count % 2000 == 0) Gfo_usr_dlg_.Instance.Prog_many("", "", "updating; rows=~{0}", count); + } + updates.Clear(); + conn.Txn_end(); + } + select_stmt.Rls(); + } + private byte[] Get_bin(Xowe_wiki wiki, Xocksum_cksum_row row) { + int bin_id = row.Thm_id() == -1 ? row.Fil_id() : row.Thm_id(); + Fsd_bin_itm bin_itm = wiki.File__mnt_mgr().Mnts__get_main().Bin_mgr().Dbs__get_at(row.Bin_db_id()).Select_as_itm(bin_id); + return bin_itm.Bin_data(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/dbs/Xocksum_cksum_db.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/dbs/Xocksum_cksum_db.java index a27517de8..af8abf109 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/dbs/Xocksum_cksum_db.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/dbs/Xocksum_cksum_db.java @@ -13,3 +13,17 @@ 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.addons.bldrs.files.cksums.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; import gplx.xowa.addons.bldrs.files.cksums.*; +import gplx.dbs.*; import gplx.fsdb.meta.*; +public class Xocksum_cksum_db { + public Xocksum_cksum_db(Db_conn conn) { + this.conn = conn; + this.tbl__cksum = new Xocksum_cksum_tbl(conn); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public Xocksum_cksum_tbl Tbl__cksum() {return tbl__cksum;} private final Xocksum_cksum_tbl tbl__cksum; + + public static Xocksum_cksum_db Get(Xowe_wiki wiki) { + return new Xocksum_cksum_db(wiki.File__fsdb_core().File__abc_file__at(Fsm_mnt_mgr.Mnt_idx_main).Conn()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/dbs/Xocksum_cksum_row.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/dbs/Xocksum_cksum_row.java index a27517de8..9f66ac04b 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/dbs/Xocksum_cksum_row.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/dbs/Xocksum_cksum_row.java @@ -13,3 +13,27 @@ 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.addons.bldrs.files.cksums.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; import gplx.xowa.addons.bldrs.files.cksums.*; +public class Xocksum_cksum_row { + public Xocksum_cksum_row(int fil_id, int thm_id, int bin_db_id, long bin_size, byte cksum_tid, int cksum_count, byte[] cksum_val, String cksum_date) { + this.fil_id = fil_id; + this.thm_id = thm_id; + this.bin_db_id = bin_db_id; + this.bin_size = bin_size; + this.cksum_tid = cksum_tid; + this.cksum_count = cksum_count; + this.cksum_val = cksum_val; + this.cksum_date = cksum_date; + } + public int Fil_id() {return fil_id;} private final int fil_id; + public int Thm_id() {return thm_id;} private final int thm_id; + public int Bin_db_id() {return bin_db_id;} private final int bin_db_id; + public long Bin_size() {return bin_size;} private long bin_size; + public byte Cksum_tid() {return cksum_tid;} private final byte cksum_tid; + public int Cksum_count() {return cksum_count;} private final int cksum_count; + public byte[] Cksum_val() {return cksum_val;} private byte[] cksum_val; + public String Cksum_date() {return cksum_date;} private final String cksum_date; + + public void Bin_size_(long v) {this.bin_size = v;} + public void Cksum_val_(byte[] v) {this.cksum_val = v;} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/dbs/Xocksum_cksum_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/dbs/Xocksum_cksum_tbl.java index a27517de8..04f1731a3 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/dbs/Xocksum_cksum_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cksums/dbs/Xocksum_cksum_tbl.java @@ -13,3 +13,93 @@ 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.addons.bldrs.files.cksums.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; import gplx.xowa.addons.bldrs.files.cksums.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.xowa.addons.wikis.ctgs.*; +public class Xocksum_cksum_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld__fil_id, fld__thm_id, fld__bin_db_id, fld__bin_len, fld__cksum_tid, fld__cksum_count, fld__cksum_val, fld__cksum_date; + private Db_stmt stmt__update; + public Xocksum_cksum_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "fsdb_cksum"; + this.fld__fil_id = flds.Add_int("fil_id"); + this.fld__thm_id = flds.Add_int("thm_id"); + this.fld__bin_db_id = flds.Add_int("bin_db_id"); + this.fld__bin_len = flds.Add_long("bin_size"); + this.fld__cksum_tid = flds.Add_byte("cksum_tid"); + this.fld__cksum_count = flds.Add_int("cksum_count"); + this.fld__cksum_val = flds.Add_str("cksum_val", 255); + this.fld__cksum_date = flds.Add_str("cksum_date", 16); + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Create_idx() { + conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "fil_id__thm_id", fld__fil_id, fld__thm_id)); + conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, fld__cksum_val, fld__cksum_val)); + } + public void Insert_missing() { + // insert from fsdb_fil + conn.Exec_sql(Db_sql_.Make_by_fmt(String_.Ary + ( "INSERT INTO fsdb_cksum (fil_id, thm_id, bin_db_id, bin_size, cksum_tid, cksum_count, cksum_val, cksum_date)" + , "SELECT f.fil_id, -1, f.fil_bin_db_id, f.fil_size, {0}, 0, '', ''" + , "FROM fsdb_fil f" + , " LEFT JOIN fsdb_cksum c ON c.fil_id = f.fil_id AND c.thm_id = -1" + , "WHERE c.fil_id IS NULL" + , "AND f.fil_bin_db_id != -1" + ), Cksum_tid__md5)); + + // insert from fsdb_fil + conn.Exec_sql(Db_sql_.Make_by_fmt(String_.Ary + ( "INSERT INTO fsdb_cksum (fil_id, thm_id, bin_db_id, bin_size, cksum_tid, cksum_count, cksum_val, cksum_date)" + , "SELECT t.thm_owner_id, t.thm_id, t.thm_bin_db_id, t.thm_size, {0}, 0, '', ''" + , "FROM fsdb_thm t" + , " LEFT JOIN fsdb_cksum c ON c.fil_id = t.thm_owner_id AND c.thm_id = t.thm_id" + , "WHERE c.fil_id IS NULL" + ), Cksum_tid__md5)); + } + public Db_stmt Select_samples_stmt(int count) { + return conn.Stmt_sql(Db_sql_.Make_by_fmt(String_.Ary + ( "SELECT *" + , "FROM fsdb_cksum" + , "WHERE cksum_val = ''" + // , "ORDER BY cksum_count, cksum_date" + , "LIMIT {0}" + ), count)); + } + public Xocksum_cksum_row[] Select_samples(Db_stmt stmt) { + List_adp rv = List_adp_.New(); + + Db_rdr rdr = stmt.Exec_select__rls_manual(); + try { + while (rdr.Move_next()) { + rv.Add(new Xocksum_cksum_row + ( rdr.Read_int("fil_id") + , rdr.Read_int("thm_id") + , rdr.Read_int("bin_db_id") + , rdr.Read_long("bin_size") + , rdr.Read_byte("cksum_tid") + , rdr.Read_int("cksum_count") + , rdr.Read_bry_by_str("cksum_val") + , rdr.Read_str("cksum_date") + )); + } + } finally {rdr.Rls();} + + return (Xocksum_cksum_row[])rv.To_ary_and_clear(Xocksum_cksum_row.class); + } + public void Update(int fil_id, int thm_id, int bin_db_id, long bin_size, byte cksum_tid, int cksum_count, byte[] cksum_val, String cksum_date) { + if (stmt__update == null) stmt__update = conn.Stmt_update_exclude(tbl_name, flds, fld__fil_id, fld__thm_id); + stmt__update.Clear() + .Val_int(fld__bin_db_id, bin_db_id).Val_long(fld__bin_len, bin_size) + .Val_byte(fld__cksum_tid, cksum_tid).Val_int(fld__cksum_count, cksum_count) + .Val_bry_as_str(fld__cksum_val, cksum_val).Val_str(fld__cksum_date, cksum_date) + .Crt_int(fld__fil_id, fil_id).Crt_int(fld__thm_id, thm_id) + .Exec_update(); + } + public void Rls() { + this.stmt__update = Db_stmt_.Rls(stmt__update); + } + public static final byte Cksum_tid__md5 = 1; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xob_hdump_tbl_retriever__ns_to_db.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xob_hdump_tbl_retriever__ns_to_db.java index a27517de8..af7838848 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xob_hdump_tbl_retriever__ns_to_db.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xob_hdump_tbl_retriever__ns_to_db.java @@ -13,3 +13,20 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.xowa.wikis.nss.*; import gplx.xowa.htmls.core.dbs.*; import gplx.xowa.htmls.core.bldrs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.wikis.data.*; +class Xob_hdump_tbl_retriever__ns_to_db implements Xob_hdump_tbl_retriever { + private final Xob_ns_to_db_mgr ns_to_db_mgr; + public Xob_hdump_tbl_retriever__ns_to_db(Xowe_wiki wiki) { + Xow_db_mgr core_data_mgr = wiki.Db_mgr_as_sql().Core_data_mgr(); + this.ns_to_db_mgr = new Xob_ns_to_db_mgr(new Xob_ns_to_db_wkr__html(core_data_mgr.Db__core()), core_data_mgr, Xobldr_cfg.Max_size__html(wiki.App())); + Xob_ns_file_itm.Init_ns_bldr_data(Xow_db_file_.Tid__html_data, wiki.Ns_mgr(), gplx.xowa.bldrs.Xobldr_cfg.Ns_file_map__each); + } + public Xowd_html_tbl Get_html_tbl(Xow_ns ns, int prv_row_len) { + Xow_db_file html_db = ns_to_db_mgr.Get_by_ns(ns.Bldr_data(), prv_row_len); // get html_db + return html_db.Tbl__html(); + } + public void Commit() {ns_to_db_mgr.Commit();} + public void Rls_all() {ns_to_db_mgr.Rls_all();} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__fsdb_db__create_data.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__fsdb_db__create_data.java index a27517de8..5501ee96b 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__fsdb_db__create_data.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__fsdb_db__create_data.java @@ -13,3 +13,373 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.core.stores.*; import gplx.core.envs.*; import gplx.core.ios.streams.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.dbs.engines.sqlite.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.dbs.*; import gplx.fsdb.*; import gplx.core.ios.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.files.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.bins.*; import gplx.xowa.files.fsdb.*; +import gplx.fsdb.data.*; import gplx.fsdb.meta.*; +import gplx.xowa.addons.bldrs.files.dbs.*; import gplx.xowa.addons.bldrs.files.utls.*; +public class Xobldr__fsdb_db__create_data extends Xob_cmd__base implements Xob_cmd { + private Db_conn bldr_conn; private Db_cfg_tbl bldr_cfg_tbl; + private Xof_bin_mgr src_bin_mgr; private Xof_bin_wkr__fsdb_sql src_fsdb_wkr; private boolean src_bin_mgr__cache_enabled = Bool_.N; private String src_bin_mgr__fsdb_version; private String[] src_bin_mgr__fsdb_skip_wkrs; private boolean src_bin_mgr__wmf_enabled; + private Fsm_mnt_itm trg_mnt_itm; private Fsm_cfg_mgr trg_cfg_mgr; private Fsm_atr_fil trg_atr_fil; private Fsm_bin_fil trg_bin_fil; private long trg_bin_db_max; private String trg_bin_mgr__fsdb_version; + private final Xof_bin_updater trg_bin_updater = new Xof_bin_updater(); private Xob_bin_db_mgr bin_db_mgr; private int[] ns_ids; private int prv_lnki_tier_id = -1; + private long download_size_max = Io_mgr.Len_mb_long * 5; private int[] download_keep_tier_ids = Int_ary_.New(0); + private Xobu_poll_mgr poll_mgr; private int poll_interval; private long time_bgn; + private int select_interval = 2500, progress_interval = 1, commit_interval = 1, delete_interval = 5000; + private boolean exec_done, resume_enabled; private int exec_count, exec_count_max = Int_.Max_value, exec_fail, exec_fail_max = 10000; // 115 errors over 900k images + private int tier_id_bmk = -1, tier_id_val = -1; private int page_id_bmk = -1, page_id_val = -1, page_id_end = Int_.Max_value; private int lnki_id_bmk = -1, lnki_id_val = -1; + private boolean exit_after_commit, exit_now; + public Xobldr__fsdb_db__create_data(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki); + if (bldr != null) { + this.poll_mgr = new Xobu_poll_mgr(bldr.App()); + wiki.File__fsdb_mode().Tid__v2__bld__y_(); + this.src_bin_mgr = new Xof_bin_mgr(new Fsm_mnt_mgr(), wiki.File__repo_mgr(), app.File__img_mgr().Wkr_resize_img(), app.Wmf_mgr().Download_wkr().Download_xrg().Download_fmt()); + } + } + @Override public void Cmd_bgn(Xob_bldr bldr) { + wiki.Init_assert(); + this.poll_interval = poll_mgr.Poll_interval(); + this.bin_db_mgr = new Xob_bin_db_mgr(ns_ids); + // src_bin_mgr + if (src_bin_mgr__fsdb_version != null) { + this.src_fsdb_wkr = Xof_bin_wkr__fsdb_sql.new_(wiki.File__mnt_mgr()); + src_bin_mgr.Wkrs__add(src_fsdb_wkr); + src_fsdb_wkr.Mnt_mgr().Ctor_by_load(new_src_bin_db_mgr(wiki, src_bin_mgr__fsdb_version)); + src_fsdb_wkr.Mnt_mgr().Mnts__get_main().Txn_bgn(); // NOTE: txn on atr speeds up from 50 -> 300; DATE:2015-03-21 + if (src_bin_mgr__fsdb_skip_wkrs != null) { + src_fsdb_wkr.Skip_mgr_init(src_fsdb_wkr.Mnt_mgr().Mnts__get_main().Cfg_mgr(), src_bin_mgr__fsdb_skip_wkrs); + } + if (src_bin_mgr__cache_enabled) { + usr_dlg.Prog_many("", "", "src_bin_mgr.cache.bgn"); + src_fsdb_wkr.Mnt_mgr().Mnts__get_main().Atr_mgr().Db__core().Fil_cache_enabled_y_(); + usr_dlg.Prog_many("", "", "src_bin_mgr.cache.end"); + } + } + if (src_bin_mgr__wmf_enabled) { + Xof_bin_wkr__http_wmf wmf_wkr = Xof_bin_wkr__http_wmf.new_(wiki); + src_bin_mgr.Wkrs__add(wmf_wkr); + wmf_wkr.Fail_timeout_(0); // 1000; NOTE: set Fail_timeout here; DATE:2014-06-21; NOTE: do not put in ctor, or else will be 1st wkr; DATE:2014-06-28 + } + // trg_mnt_itm + this.trg_bin_db_max = Xobldr_cfg.Max_size__file(app); + Io_url trg_file_dir_v1 = String_.Eq(trg_bin_mgr__fsdb_version, "v1") ? wiki.Fsys_mgr().File_dir().GenNewNameOnly(wiki.Domain_str() + "-prv") : wiki.Fsys_mgr().File_dir(); // NOTE: convoluted way of setting trg to -prv if trg_bin_mgr__fsdb_version_v1 is set; otherwise set to "en.wikipedia.org" which will noop; DATE:2015-12-02 + Fsdb_db_mgr trg_db_mgr = Fsdb_db_mgr_.new_detect(wiki, wiki.Fsys_mgr().Root_dir(), trg_file_dir_v1); + if (trg_db_mgr == null) trg_db_mgr = Fsdb_db_mgr__v2_bldr.Get_or_make(wiki, Bool_.Y); + Fsm_mnt_mgr trg_mnt_mgr = new Fsm_mnt_mgr(); trg_mnt_mgr.Ctor_by_load(trg_db_mgr); + trg_mnt_mgr.Mnts__get_insert_idx_(Fsm_mnt_mgr.Mnt_idx_main); // NOTE: do not delete; mnt_mgr default to Mnt_idx_user; DATE:2014-04-25 + this.trg_mnt_itm = trg_mnt_mgr.Mnts__get_insert(); + Fsm_mnt_mgr.Patch(trg_mnt_itm.Cfg_mgr().Tbl()); // NOTE: always patch again; fsdb_make may be run separately without lnki_temp; DATE:2014-04-26 + this.trg_atr_fil = trg_mnt_itm.Atr_mgr().Db__core(); + this.trg_cfg_mgr = trg_mnt_itm.Cfg_mgr(); + bin_db_mgr.Init_by_mnt_mgr(trg_mnt_mgr); + trg_atr_fil.Conn().Txn_bgn("bldr__fsdb_make__trg_atr_fil"); + if (!trg_atr_fil.Conn().Eq(trg_cfg_mgr.Tbl().Conn())) // need to create txn for v1; DATE:2015-07-04 + trg_cfg_mgr.Tbl().Conn().Txn_bgn("bldr__fsdb_make__trg_cfg_fil"); + // bldr_db + Xob_db_file bldr_db = Xob_db_file.New__file_make(wiki.Fsys_mgr().Root_dir()); + this.bldr_conn = bldr_db.Conn(); + this.bldr_cfg_tbl = bldr_db.Tbl__cfg(); // NOTE: cfg and atr is in same db; use it + bldr_cfg_tbl.Conn().Txn_bgn("bldr__fsdb_make__bldr_cfg_tbl"); + } + @Override public void Cmd_run() { + Init_bldr_bmks(); + this.time_bgn = System_.Ticks(); + int total_pending = Xob_xfer_regy_tbl.Select_total_pending(bldr_conn); + // if (total_pending > 250000 && src_bin_mgr__fsdb_version == null) + usr_dlg.Note_many("", "", "total pending: ~{0}", total_pending); + List_adp list = List_adp_.New(); + boolean loop = true; + while (loop) { + byte rslt = Select_fsdb_itms(list); + switch (rslt) { + case Select_rv_stop: + if (bin_db_mgr.Tier_id_is_last(tier_id_val)) + loop = false; + else { + ++tier_id_val; + page_id_val = 0; + continue; + } + break; + case Select_rv_next_page: ++page_id_val; lnki_id_val = 0; continue; + case Select_rv_process: break; + } + if (!loop) break; // no more ttls found + int len = list.Count(); + usr_dlg.Prog_many("", "", "fetched pages: ~{0}", len); + for (int i = 0; i < len; ++i) { + Xodb_tbl_oimg_xfer_itm fsdb = (Xodb_tbl_oimg_xfer_itm)list.Get_at(i); + Download_itm(fsdb); + if ( exit_now + || exec_count >= exec_count_max + || exec_fail >= exec_fail_max + || page_id_val >= page_id_end + ) { + this.Txn_sav(); + return; + } + } + } + exec_done = true; + } + private void Init_bldr_bmks() { + if (!resume_enabled) // clear cfg entries if resume disabled; note that disabled by default; DATE:2014-10-24 + bldr_cfg_tbl.Delete_grp(Cfg_fsdb_make); + Db_cfg_hash bmk_hash = bldr_cfg_tbl.Select_as_hash(Cfg_fsdb_make); + String tier_id_str = bmk_hash.Get_by(Cfg_tier_id_bmk).To_str_or(null); + if (tier_id_str == null) { // bmks not found; new db; + bldr_conn.Txn_bgn("bldr__fsdb_make__bldr_conn"); + bldr_cfg_tbl.Insert_int(Cfg_fsdb_make, Cfg_tier_id_bmk , tier_id_bmk); + bldr_cfg_tbl.Insert_int(Cfg_fsdb_make, Cfg_page_id_bmk , page_id_bmk); + bldr_cfg_tbl.Insert_int(Cfg_fsdb_make, Cfg_lnki_id_bmk , lnki_id_bmk); + bldr_conn.Txn_end(); + if (tier_id_bmk == -1) tier_id_bmk = 0; + if (page_id_bmk == -1) page_id_bmk = 0; + if (lnki_id_bmk == -1) lnki_id_bmk = 0; + } + else { + if (tier_id_bmk == -1) { + tier_id_bmk = Int_.Parse(tier_id_str); + usr_dlg.Note_many("", "", "restoring from bmk: tier_id=~{0}", tier_id_bmk); + } + if (page_id_bmk == -1) { + page_id_bmk = bmk_hash.Get_by(Cfg_page_id_bmk).To_int(); + usr_dlg.Note_many("", "", "restoring from bmk: page_id=~{0}", page_id_bmk); + } + if (lnki_id_bmk == -1) { + lnki_id_bmk = bmk_hash.Get_by(Cfg_lnki_id_bmk).To_int(); + usr_dlg.Note_many("", "", "restoring from bmk: lnki_id=~{0}", lnki_id_bmk); + } + } + tier_id_val = tier_id_bmk; + page_id_val = page_id_bmk; + lnki_id_val = lnki_id_bmk; + } + private byte Select_fsdb_itms(List_adp list) { + list.Clear(); + boolean pages_found = false, links_found = false; + DataRdr rdr = Xob_xfer_regy_tbl.Select_by_tier_page(bldr_conn, tier_id_val, page_id_val, select_interval); + try { + while (rdr.MoveNextPeer()) { + pages_found = true; // at least one page found; set true + Xodb_tbl_oimg_xfer_itm itm = Xodb_tbl_oimg_xfer_itm.new_rdr_( rdr); + if ( itm.Lnki_page_id() == page_id_val // same page_id + && itm.Lnki_id() <= lnki_id_val // ... but lnki_id < last + ) + continue; // ... ignore; note that select is by page_id, not page_id + link_id; needed else restarts would not resume exactly at same point; + links_found = true; + list.Add(itm); + } + } finally {rdr.Rls();} + if (pages_found && !links_found) return Select_rv_next_page; // pages found, but all links processed + else if (!pages_found) return Select_rv_stop; // no more pages found + else return Select_rv_process; // pages and links found + } + private void Download_itm(Xodb_tbl_oimg_xfer_itm fsdb) { + try { + tier_id_val = fsdb.Lnki_tier_id(); + page_id_val = fsdb.Lnki_page_id(); + lnki_id_val = fsdb.Lnki_id(); + fsdb.Orig_repo_name_(fsdb.Orig_repo_id() == Xof_repo_tid_.Tid__local ? wiki.Domain_bry() : Xow_domain_itm_.Bry__commons); + Download_exec(fsdb); + ++exec_count; + if (exec_count % progress_interval == 0) Print_progress(fsdb); + if (exec_count % poll_interval == 0) poll_mgr.Poll(); + if (exec_count % commit_interval == 0) Txn_sav(); + if (exec_count % delete_interval == 0) Delete_files(); + } + catch (Exception exc) { + ++exec_fail; + usr_dlg.Warn_many("", "", "download error; ttl=~{0} w=~{1} err=~{2}", fsdb.Orig_ttl(), fsdb.Lnki_w(), Err_.Message_gplx_full(exc)); + } + } + private void Download_exec(Xodb_tbl_oimg_xfer_itm fsdb) { + Io_stream_rdr src_rdr = src_bin_mgr.Find_as_rdr(Xof_exec_tid.Tid_wiki_page, fsdb); + try { + if (src_rdr == Io_stream_rdr_.Noop) { // download failed + ++exec_fail; + usr_dlg.Warn_many("", "", "failed: ttl=~{0}", String_.Format("[[File:{0}|{1}px]]", fsdb.Orig_ttl(), fsdb.Html_w())); + Print_progress(fsdb); + } + else { // download passed + long src_rdr_len = src_rdr.Len(); + int lnki_tier_id = fsdb.Lnki_tier_id(); + if ( src_rdr_len > download_size_max + && !Int_.In(lnki_tier_id, download_keep_tier_ids)) { + usr_dlg.Warn_many("", "", "skipped; ttl=~{0} w=~{1} size=~{2} tier=~{3}", fsdb.Orig_ttl(), fsdb.Lnki_w(), src_rdr_len, lnki_tier_id); + return; + } + if (trg_bin_fil == null) // no trg_bin_fil + Make_trg_bin_file(Bool_.Y, fsdb, src_rdr_len); + else if (trg_bin_fil.Bin_len() + src_rdr_len > trg_bin_db_max) // or trg_bin_fil is out of space + Make_trg_bin_file(Bool_.N, fsdb, src_rdr_len); + else if (prv_lnki_tier_id != lnki_tier_id) { // or tier has changed + if ( prv_lnki_tier_id != -1 + && !bin_db_mgr.Schema_is_1()) // do not increment dbs for v1 + Make_trg_bin_file(Bool_.Y, fsdb, src_rdr_len); + prv_lnki_tier_id = lnki_tier_id; + } + trg_bin_updater.Save_bin(trg_mnt_itm, trg_atr_fil, trg_bin_fil, fsdb, src_rdr, src_rdr_len); + } + } + finally {src_rdr.Rls();} + } + private void Make_trg_bin_file(boolean try_nth, Xodb_tbl_oimg_xfer_itm fsdb, long src_rdr_len) { + boolean is_solo = trg_mnt_itm.Db_mgr().File__solo_file(); + boolean make = true, use_txn = !is_solo; // solo file; should never open txn + if (trg_bin_fil != null && use_txn) // pre-existing bin_file; + trg_bin_fil.Conn().Txn_end(); // close txn before making new db + int tier_id = fsdb.Lnki_tier_id(); + Xob_bin_db_itm nth_bin_db = bin_db_mgr.Get_nth_by_tier(tier_id); + if (try_nth) { // try_nth is true; occurs for new runs or changed tier + if ( nth_bin_db.Id() != -1 // nth exists; + && nth_bin_db.Db_len() + src_rdr_len < trg_bin_db_max) { // if src_rdr_len exceeds + make = false; // do not make; use existing + } + } + if (make) { // no nth; make it; + int ns_id = bin_db_mgr.Get_ns_id(tier_id); + int pt_id = bin_db_mgr.Increment_pt_id(nth_bin_db); + String new_bin_db_name = bin_db_mgr.Gen_name(wiki.Domain_str(), ns_id, pt_id); + this.trg_bin_fil = trg_mnt_itm.Bin_mgr().Dbs__make(new_bin_db_name); + if (!trg_mnt_itm.Db_mgr().File__solo_file()) { + Fsdb_db_file trg_bin_db = trg_mnt_itm.Db_mgr().File__bin_file__at(trg_mnt_itm.Id(), trg_bin_fil.Id(), new_bin_db_name); + if (!bin_db_mgr.Schema_is_1()) + Fsdb_db_mgr__v2_bldr.Make_cfg_data(wiki, trg_atr_fil.Url_rel(), trg_bin_db, Xow_db_file_.Tid__file_data, trg_bin_fil.Id() + List_adp_.Base1); + } + } + else { // nth available; use it + this.trg_bin_fil = trg_mnt_itm.Bin_mgr().Dbs__get_at(nth_bin_db.Id()); + trg_bin_fil.Bin_len_(nth_bin_db.Db_len()); + } + if (use_txn) + trg_bin_fil.Conn().Txn_bgn("bldr__fsdb_make__trg_bin_fil"); + } + private void Txn_sav() { + usr_dlg.Prog_many("", "", "committing data: count=~{0} failed=~{1}", exec_count, exec_fail); + bldr_cfg_tbl.Update_int(Cfg_fsdb_make, Cfg_page_id_bmk, page_id_val); + bldr_cfg_tbl.Update_int(Cfg_fsdb_make, Cfg_lnki_id_bmk, lnki_id_val); + bldr_cfg_tbl.Conn().Txn_sav(); + trg_cfg_mgr.Next_id_commit(); + trg_atr_fil.Conn().Txn_sav(); + if (!trg_atr_fil.Conn().Eq(trg_cfg_mgr.Tbl().Conn())) // need to create txn for v1 + trg_cfg_mgr.Tbl().Conn().Txn_sav(); + if (src_bin_mgr__fsdb_version != null && src_bin_mgr__fsdb_skip_wkrs != null) { + src_fsdb_wkr.Skip_mgr().Skip_term(src_fsdb_wkr.Mnt_mgr().Mnts__get_main().Cfg_mgr()); + } + if (!trg_mnt_itm.Db_mgr().File__solo_file()) + trg_bin_fil.Conn().Txn_sav(); + if (exit_after_commit) exit_now = true; + } + @Override public void Cmd_end() { + usr_dlg.Note_many("", "", "fsdb_make.done: count=~{0} rate=~{1}", exec_count, Decimal_adp_.divide_safe_(exec_count, System_.Ticks__elapsed_in_sec(time_bgn)).To_str("#,###.000")); + if (src_fsdb_wkr != null) { + src_fsdb_wkr.Mnt_mgr().Mnts__get_main().Txn_end(); // NOTE: src_fsdb_wkr will be null if no src db defined + } + trg_atr_fil.Conn().Txn_end(); trg_atr_fil.Conn().Rls_conn(); + if (!trg_atr_fil.Conn().Eq(trg_cfg_mgr.Tbl().Conn())) // need to create txn for v1 + trg_cfg_mgr.Tbl().Conn().Txn_end(); + trg_cfg_mgr.Tbl().Conn().Rls_conn(); + if (!trg_mnt_itm.Db_mgr().File__solo_file()) { + if (trg_bin_fil != null) { // NOTE: trg_bin_fil is null when there are no images in the wiki; EX: bo.b; DATE:2017-03-19 + trg_bin_fil.Conn().Txn_end(); trg_bin_fil.Conn().Rls_conn(); + } + } + if (exec_done) { + bldr_cfg_tbl.Delete_grp(Cfg_fsdb_make); // delete bmks for future reruns; DATE:2014-08-20 + Io_mgr.Instance.DeleteFil_args(wiki.Fsys_mgr().Root_dir().GenSubFil("xowa.file.make.cfg.gfs")).MissingFails_off().Exec(); + } + bldr_conn.Rls_conn(); + } + private void Print_progress(Xodb_tbl_oimg_xfer_itm itm) { + int time_elapsed = System_.Ticks__elapsed_in_sec(time_bgn); + usr_dlg.Prog_many("", "", "prog: num=~{0} err=~{1} time=~{2} rate=~{3} page=~{4} lnki=~{5} ttl=~{6}", exec_count, exec_fail, time_elapsed, Math_.Div_safe_as_int(exec_count, time_elapsed), page_id_val, lnki_id_val, itm.Orig_ttl()); + } + private void Delete_files() {}// TODO_OLD: purge /xowa/file/ dir to free up hard disk space + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_tier_id_bmk_)) tier_id_bmk = m.ReadInt("v"); + else if (ctx.Match(k, Invk_page_id_bmk_)) page_id_bmk = m.ReadInt("v"); + else if (ctx.Match(k, Invk_lnki_id_bmk_)) lnki_id_bmk = m.ReadInt("v"); + else if (ctx.Match(k, Invk_select_interval_)) select_interval = m.ReadInt("v"); + else if (ctx.Match(k, Invk_commit_interval_)) commit_interval = m.ReadInt("v"); + else if (ctx.Match(k, Invk_progress_interval_)) progress_interval = m.ReadInt("v"); + else if (ctx.Match(k, Invk_delete_interval_)) delete_interval = m.ReadInt("v"); + else if (ctx.Match(k, Invk_exec_count_max_)) exec_count_max = m.ReadInt("v"); + else if (ctx.Match(k, Invk_exec_fail_max_)) exec_fail_max = m.ReadInt("v"); + else if (ctx.Match(k, Invk_exit_after_commit_)) exit_after_commit = m.ReadYn("v"); + else if (ctx.Match(k, Invk_exit_now_)) exit_now = m.ReadYn("v"); + else if (ctx.Match(k, Invk_resume_enabled_)) resume_enabled = m.ReadYn("v"); + else if (ctx.Match(k, Invk_ns_ids_)) ns_ids = Int_ary_.Parse(m.ReadStr("v"), "|"); + else if (ctx.Match(k, Invk_src_bin_mgr__fsdb_version_)) src_bin_mgr__fsdb_version = m.ReadStr("v"); + else if (ctx.Match(k, Invk_src_bin_mgr__fsdb_skip_wkrs_)) src_bin_mgr__fsdb_skip_wkrs = m.ReadStrAry("v", "|"); + else if (ctx.Match(k, Invk_src_bin_mgr__wmf_enabled_)) src_bin_mgr__wmf_enabled = m.ReadYn("v"); + else if (ctx.Match(k, Invk_src_bin_mgr__cache_enabled_)) src_bin_mgr__cache_enabled = m.ReadYn("v"); + else if (ctx.Match(k, Invk_trg_bin_mgr__fsdb_version_)) trg_bin_mgr__fsdb_version = m.ReadStr("v"); + else if (ctx.Match(k, Invk_poll_mgr)) return poll_mgr; + else if (ctx.Match(k, Invk_download_keep_tier_ids)) download_keep_tier_ids = Int_ary_.Parse(m.ReadStr("v"), "|"); + else if (ctx.Match(k, Invk_download_size_max)) download_size_max = Io_size_.To_long_by_msg_mb(m, download_size_max); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk_tier_id_bmk_ = "tier_id_bmk_", Invk_page_id_bmk_ = "page_id_bmk_", Invk_lnki_id_bmk_ = "lnki_id_bmk_" + , Invk_select_interval_ = "select_interval_", Invk_commit_interval_ = "commit_interval_", Invk_progress_interval_ = "progress_interval_", Invk_delete_interval_ = "delete_interval_" + , Invk_exec_count_max_ = "exec_count_max_", Invk_exec_fail_max_ = "exec_fail_max_", Invk_exit_now_ = "exit_now_", Invk_exit_after_commit_ = "exit_after_commit_" + , Invk_resume_enabled_ = "resume_enabled_", Invk_poll_mgr = "poll_mgr" + , Invk_src_bin_mgr__fsdb_version_ = "src_bin_mgr__fsdb_version_", Invk_src_bin_mgr__fsdb_skip_wkrs_ = "src_bin_mgr__fsdb_skip_wkrs_" + , Invk_src_bin_mgr__wmf_enabled_ = "src_bin_mgr__wmf_enabled_" + , Invk_src_bin_mgr__cache_enabled_ = "src_bin_mgr__cache_enabled_", Invk_ns_ids_ = "ns_ids_" + , Invk_trg_bin_mgr__fsdb_version_ = "trg_bin_mgr__fsdb_version_" + , Invk_download_size_max = "download_size_max", Invk_download_keep_tier_ids = "download_keep_tier_ids" + ; + + public static final String BLDR_CMD_KEY = "file.fsdb_make"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__fsdb_db__create_data(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__fsdb_db__create_data(bldr, wiki);} + + public static Fsdb_db_mgr new_src_bin_db_mgr(Xow_wiki wiki, String version) { + String domain_str = wiki.Domain_str(); + Fsdb_db_mgr rv = null; Io_url url = null; + if (String_.Eq(version, "v1")) { + url = wiki.Fsys_mgr().File_dir().OwnerDir().GenSubDir(domain_str + "-prv"); // v1: EX: /xowa/file/en.wikipedia.org-prv/ + rv = new Fsdb_db_mgr__v1(url); + } + else if (String_.Eq(version, "v2")) { + url = wiki.Fsys_mgr().Root_dir().GenSubDir("prv"); // v2: EX: /xowa/wiki/en.wikipedia.org/prv/ + rv = Fsdb_db_mgr_.new_detect(wiki, url, url); // note that v2 is prioritized over v1 + } + else throw Err_.new_wo_type("fsdb.make:unknown fsdb_type", "version", version); + if (rv == null) throw Err_.new_wo_type("fsdb.make:source fsdb not found", "version", version, "url", url.Raw()); + return rv; + } + private static final byte Select_rv_stop = 0, Select_rv_process = 1, Select_rv_next_page = 2; + private static final String Cfg_fsdb_make = "bldr.fsdb_make", Cfg_tier_id_bmk = "tier_id_bmk", Cfg_page_id_bmk = "page_id_bmk", Cfg_lnki_id_bmk = "lnki_id_bmk"; + public static byte Status_null = 0, Status_pass = 1, Status_fail = 2; +} +class Xodb_tbl_oimg_xfer_itm extends Xof_fsdb_itm { public int Lnki_id() {return lnki_id;} private int lnki_id; + public int Lnki_tier_id() {return lnki_tier_id;} private int lnki_tier_id; + public int Lnki_page_id() {return lnki_page_id;} private int lnki_page_id; + public static Xodb_tbl_oimg_xfer_itm new_rdr_(DataRdr rdr) { + Xodb_tbl_oimg_xfer_itm rv = new Xodb_tbl_oimg_xfer_itm(); + rv.lnki_id = rdr.ReadInt(Xob_xfer_regy_tbl.Fld_lnki_id); + rv.lnki_page_id = rdr.ReadInt(Xob_xfer_regy_tbl.Fld_lnki_page_id); + rv.lnki_tier_id = rdr.ReadInt(Xob_xfer_regy_tbl.Fld_lnki_tier_id); + rv.Init_at_fsdb_make + ( rdr.ReadBryByStr(Xob_xfer_regy_tbl.Fld_lnki_ttl) + , rdr.ReadInt(Xob_xfer_regy_tbl.Fld_lnki_ext) + , rdr.ReadInt(Xob_xfer_regy_tbl.Fld_file_w), rdr.ReadInt(Xob_xfer_regy_tbl.Fld_file_h) // set lnki_size; Xof_bin_mgr uses lnki_size + , Xof_lnki_time.Db_load_double(rdr, Xob_xfer_regy_tbl.Fld_lnki_time) + , Xof_lnki_page.Db_load_int(rdr, Xob_xfer_regy_tbl.Fld_lnki_page) + , rdr.ReadByte(Xob_xfer_regy_tbl.Fld_orig_repo) + , rdr.ReadInt(Xob_xfer_regy_tbl.Fld_orig_w) + , rdr.ReadInt(Xob_xfer_regy_tbl.Fld_orig_h) + , Bry_.Empty + , rdr.ReadByte(Xob_xfer_regy_tbl.Fld_file_is_orig) == Bool_.Y_byte + ); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__fsdb_db__create_orig.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__fsdb_db__create_orig.java index a27517de8..ff3ec31dc 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__fsdb_db__create_orig.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__fsdb_db__create_orig.java @@ -13,3 +13,108 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.dbs.engines.sqlite.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.files.*; import gplx.fsdb.*; +public class Xobldr__fsdb_db__create_orig extends Xob_cmd__base { + private Db_conn conn; private boolean schema_1; + public Xobldr__fsdb_db__create_orig(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_bgn(Xob_bldr bldr) { + Xof_fsdb_mode fsdb_mode = wiki.File__fsdb_mode(); + fsdb_mode.Tid__v2__bld__y_(); + wiki.Init_assert(); + Fsdb_db_mgr db_core_mgr = Fsdb_db_mgr_.new_detect(wiki, wiki.Fsys_mgr().Root_dir(), wiki.Fsys_mgr().File_dir()); + this.schema_1 = db_core_mgr.File__schema_is_1(); + conn = db_core_mgr.File__orig_tbl_ary()[gplx.fsdb.meta.Fsm_mnt_mgr.Mnt_idx_main].Conn(); + Io_url make_db_url = Xob_db_file.New__file_make(wiki.Fsys_mgr().Root_dir()).Url(); + Sqlite_engine_.Db_attach(conn, "make_db", make_db_url.Raw()); + } + @Override public void Cmd_run() { + String tbl_name = "orig_reg", fld_status = "orig_status"; + if (schema_1) { + tbl_name = "wiki_orig"; + fld_status = "status"; + } + conn.Exec_sql_plog_txn("orig_wkr.deleting orig_reg" , String_.Format(Sql_delete_wiki_orig, tbl_name)); // always delete orig_reg, else will not pick up changed sizes / moved repos; DATE:2014-07-21 + conn.Exec_sql_plog_txn("orig_wkr.inserting xfer direct" , String_.Format(Sql_create_xfer_direct, tbl_name, fld_status)); + conn.Exec_sql_plog_txn("orig_wkr.inserting xfer redirect" , String_.Format(Sql_create_xfer_redirect, tbl_name, fld_status)); + conn.Exec_sql_plog_txn("orig_wkr.inserting orig direct" , String_.Format(Sql_create_orig_direct, tbl_name, fld_status)); + conn.Exec_sql_plog_txn("orig_wkr.inserting orig redirect" , String_.Format(Sql_create_orig_redirect, tbl_name, fld_status)); + } + private static final String + Sql_delete_wiki_orig = "DELETE FROM {0};" + , Sql_create_xfer_direct = String_.Concat_lines_nl + ( "INSERT INTO {0} " + , "(orig_ttl, {1}, orig_repo, orig_ext, orig_w, orig_h, orig_redirect)" + , "SELECT DISTINCT" + , " xfer.lnki_ttl" + , ", 1 --pass" + , ", xfer.orig_repo" + , ", xfer.lnki_ext" + , ", xfer.orig_w" + , ", xfer.orig_h" + , ", ''" + , "FROM make_db.xfer_regy xfer" + , " LEFT JOIN {0} cur ON xfer.lnki_ttl = cur.orig_ttl" + , "WHERE cur.orig_ttl IS NULL" + ) + , Sql_create_xfer_redirect = String_.Concat_lines_nl + ( "INSERT INTO {0} " + , "(orig_ttl, {1}, orig_repo, orig_ext, orig_w, orig_h, orig_redirect)" + , "SELECT DISTINCT" + , " xfer.orig_redirect_src" + , ", 1 --pass" + , ", xfer.orig_repo" + , ", xfer.lnki_ext" + , ", xfer.orig_w" + , ", xfer.orig_h" + , ", xfer.lnki_ttl" + , "FROM make_db.xfer_regy xfer" + , " LEFT JOIN {0} cur ON xfer.orig_redirect_src = cur.orig_ttl" + , "WHERE cur.orig_ttl IS NULL" + , "AND Coalesce(xfer.orig_redirect_src, '') != ''" + ) + , Sql_create_orig_direct = String_.Concat_lines_nl + ( "INSERT INTO {0} " + , "(orig_ttl, {1}, orig_repo, orig_ext, orig_w, orig_h, orig_redirect)" + , "SELECT DISTINCT" + , " orig.lnki_ttl" + , ", 0 --unknown" + , ", orig.orig_repo" + , ", orig.lnki_ext" + , ", orig.orig_w" + , ", orig.orig_h" + , ", ''" + , "FROM make_db.orig_regy orig" + , " LEFT JOIN {0} cur ON orig.lnki_ttl = cur.orig_ttl" + , "WHERE cur.orig_ttl IS NULL" // not already in orig_reg + , "AND orig.orig_repo IS NOT NULL" // not found in oimg_image.sqlite3 + , "AND Coalesce(orig.orig_w , -1) != -1" // ignore entries that are either ext_id = 0 ("File:1") or don't have any width / height info (makes it useless); need to try to get again from wmf_api + , "AND Coalesce(orig.orig_redirect_ttl, '') == ''" // direct + ) + , Sql_create_orig_redirect = String_.Concat_lines_nl + ( "INSERT INTO {0} " + , "(orig_ttl, {1}, orig_repo, orig_ext, orig_w, orig_h, orig_redirect)" + , "SELECT DISTINCT" + , " orig.orig_redirect_ttl" + , ", 0 --unknown" + , ", orig.orig_repo" + , ", orig.lnki_ext" + , ", orig.orig_w" + , ", orig.orig_h" + , ", ''" + , "FROM make_db.orig_regy orig" + , " LEFT JOIN {0} cur ON orig.orig_redirect_ttl = cur.orig_ttl" + , "WHERE cur.orig_ttl IS NULL" // not already in orig_reg + , "AND orig.orig_repo IS NOT NULL" // not found in oimg_image.sqlite3 + , "AND Coalesce(orig.orig_w, -1) != -1" // ignore entries that are either ext_id = 0 ("File:1") or don't have any width / height info (makes it useless); need to try to get again from wmf_api + , "AND Coalesce(orig.orig_redirect_ttl, '') != ''" // redirect + ) + ; + + public static final String BLDR_CMD_KEY = "file.orig_reg"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__fsdb_db__create_orig(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__fsdb_db__create_orig(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__image__create.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__image__create.java index a27517de8..e9cef06cd 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__image__create.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__image__create.java @@ -13,3 +13,115 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.core.ios.*; import gplx.xowa.files.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.bldrs.sql_dumps.*; +import gplx.xowa.addons.bldrs.files.dbs.*; +public class Xobldr__image__create extends Xob_itm_dump_base implements Xob_cmd, Gfo_invk, Xosql_dump_cbk { + private Xosql_dump_parser parser; + private Db_conn conn = null; private Db_stmt stmt = null; + private Xob_image_tbl tbl_image = new Xob_image_tbl(); + private byte[] cur_ttl, cur_media_type, cur_minor_mime, cur_timestamp; private int cur_size, cur_width, cur_height, cur_bits, cur_ext_id; + private int commit_count = 10000; + public Xobldr__image__create(Xob_bldr bldr, Xowe_wiki wiki) { + this.parser = new Xosql_dump_parser(this, "img_name", "img_size", "img_width", "img_height", "img_bits", "img_media_type", "img_minor_mime", "img_timestamp"); + this.Cmd_ctor(bldr, wiki); + } + public Io_url Src_fil() {return src_fil;} public Xobldr__image__create Src_fil_(Io_url v) {src_fil = v; return this;} private Io_url src_fil; + public Xosql_dump_parser Parser() {return parser;} + public void Cmd_init(Xob_bldr bldr) {} + public void Cmd_bgn(Xob_bldr bldr) { + wiki.Init_assert(); // NOTE: must init wiki for db_mgr_as_sql + Init_dump(BLDR_CMD_KEY); + if (src_fil == null) { + src_fil = Xob_page_wkr_cmd.Find_fil_by(wiki.Fsys_mgr().Root_dir(), "*-image.sql"); + if (src_fil == null) throw Err_.new_wo_type(".sql file not found in dir", "dir", wiki.Fsys_mgr().Root_dir()); + } + parser.Src_fil_(src_fil); + this.conn = Xob_db_file.New__wiki_image(wiki.Fsys_mgr().Root_dir()).Conn(); + conn.Txn_bgn("bldr__image"); + this.tbl_image = new Xob_image_tbl(); + tbl_image.Create_table(conn); + this.stmt = tbl_image.Insert_stmt(conn); + } + public void Cmd_run() { + parser.Parse(bldr.Usr_dlg()); + tbl_image.Create_index(conn); + conn.Txn_end(); + } + public void On_fld_done(int fld_idx, byte[] src, int val_bgn, int val_end) { + switch (fld_idx) { + case Fld_img_name: cur_ttl = Bry_.Mid(src, val_bgn, val_end); break; + case Fld_img_size: cur_size = Bry_.To_int_or(src, val_bgn, val_end, -1); break; + case Fld_img_width: cur_width = Bry_.To_int_or(src, val_bgn, val_end, -1); break; + case Fld_img_height: cur_height = Bry_.To_int_or(src, val_bgn, val_end, -1); break; + case Fld_img_bits: cur_bits = Bry_.To_int_or(src, val_bgn, val_end, -1); break; + case Fld_img_media_type: cur_media_type = Bry_.Mid(src, val_bgn, val_end); break; + case Fld_img_minor_mime: cur_minor_mime = Bry_.Mid(src, val_bgn, val_end); break; + case Fld_img_timestamp: cur_timestamp = Bry_.Mid(src, val_bgn, val_end); break; + } + } + public void On_row_done() { + cur_ext_id = Calc_ext_id(show_issues ? app.Usr_dlg() : Gfo_usr_dlg_.Noop, cur_ttl, cur_media_type, cur_minor_mime, cur_width, cur_height); + tbl_image.Insert(stmt, cur_ttl, cur_media_type, cur_minor_mime, cur_size, cur_width, cur_height, cur_bits, cur_ext_id, cur_timestamp); + ++commit_count; + if ((commit_count % 10000) == 0) { + usr_dlg.Prog_many("", "", "committing: count=~{0} last=~{1}", commit_count, String_.new_u8(cur_ttl)); + conn.Txn_sav(); + } + } + public void Cmd_end() {} + public void Cmd_term() {} + private boolean show_issues = true; + private static final int Fld_img_name = 0, Fld_img_size = 1, Fld_img_width = 2, Fld_img_height = 3, Fld_img_bits = 4, Fld_img_media_type = 5, Fld_img_minor_mime = 6, Fld_img_timestamp = 7; + // Fld_img_name = 0, Fld_img_size = 1, Fld_img_width = 2, Fld_img_height = 3, Fld_img_bits = 5, Fld_img_media_type = 6, Fld_img_minor_mime = 8, Fld_img_timestamp = 12; + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_src_fil_)) src_fil = m.ReadIoUrl("v"); + else if (ctx.Match(k, Invk_show_issues_)) show_issues = m.ReadYn("v"); + else return super.Invk(ctx, ikey, k, m); + return this; + } private static final String Invk_src_fil_ = "src_fil_", Invk_show_issues_ = "show_issues_"; + public static int Calc_ext_id(Gfo_usr_dlg usr_dlg, byte[] file, byte[] media_type, byte[] minor_mime, int w, int h) { + Xof_ext file_ext = Xof_ext_.new_by_ttl_(file); int file_ext_id = file_ext.Id(); + Xof_ext mime_ext = Xof_mime_minor_.ext_(minor_mime); int mime_ext_id = mime_ext.Id(); + int media_type_id = Xof_media_type.Xto_byte(String_.new_u8(media_type)); + if (file_ext_id != mime_ext_id) { // file_ext_id != mime_ext_id; EX: "A.png" actually has a minor_mime of "jpg" + boolean update = false, notify = true; + switch (file_ext_id) { + case Xof_ext_.Id_jpg: case Xof_ext_.Id_jpeg: + if (Int_.In(mime_ext_id, Xof_ext_.Id_jpg, Xof_ext_.Id_jpeg)) notify = false; // skip: both jpg + break; + case Xof_ext_.Id_tif: case Xof_ext_.Id_tiff: + if (Int_.In(mime_ext_id, Xof_ext_.Id_tif, Xof_ext_.Id_tiff)) notify = false; // skip: both tif + break; + case Xof_ext_.Id_ogg: case Xof_ext_.Id_oga: case Xof_ext_.Id_ogv: + if (Int_.In(mime_ext_id, Xof_ext_.Id_ogg, Xof_ext_.Id_oga, Xof_ext_.Id_ogv)) notify = false; // skip: both tif + break; + case Xof_ext_.Id_png: + if (Int_.In(mime_ext_id, Xof_ext_.Id_jpg, Xof_ext_.Id_jpeg)) + update = true; + break; + } + if (update) + file_ext_id = mime_ext_id; + else { + if (notify) + usr_dlg.Log_many("", "", "image.ext_calc.mismatch_exts: file=~{0} mime=~{1}", String_.new_u8(file), String_.new_u8(minor_mime)); + } + } + if ( file_ext_id == Xof_ext_.Id_ogg // file_ext is ".ogg" + && media_type_id == Xof_media_type.Tid_video // media_type is "VIDEO" + ) { + if (w > 0 && h > 0) // some .ogg files are "VIDEO" but have 0 width, 0 height + file_ext_id = Xof_ext_.Id_ogv; // manually specify ogv + else + usr_dlg.Log_many("", "", "image.ext_calc.ogg_video_with_null_size: media_type=~{0} minor_mime=~{1} w=~{2} h=~{3} file=~{4}", String_.new_u8(media_type), String_.new_u8(minor_mime), w, h, String_.new_u8(file)); + } + return file_ext_id; + } + + public static final String BLDR_CMD_KEY = "wiki.image"; + public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__image__create(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__image__create(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__image__create_tst.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__image__create_tst.java index a27517de8..31c66308e 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__image__create_tst.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__image__create_tst.java @@ -13,3 +13,37 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import org.junit.*; import gplx.core.ios.*; import gplx.xowa.files.*; +public class Xobldr__image__create_tst { + private Xobldr__image__create_fxt fxt = new Xobldr__image__create_fxt(); + @Test public void Basic() {fxt.Init_("A.png" , Xof_media_type.Name_bitmap , Xof_ext_.Bry_png, 220, 110) .Test(Xof_ext_.Id_png);} // A.png -> png + @Test public void Ogg_VIDEO() {fxt.Init_("A.ogg" , Xof_media_type.Name_video , Xof_ext_.Bry_ogg, 220, 110) .Test(Xof_ext_.Id_ogv);} // A.ogg and VIDEO -> ogv + @Test public void Ogg_VIDEO_null_size() {fxt.Init_("A.ogg" , Xof_media_type.Name_video , Xof_ext_.Bry_ogg, 0, 0) .Test(Xof_ext_.Id_ogg);} // A.ogg but 0,0 -> ogg (not ogv) + @Test public void Png_is_jpg() {fxt.Init_("A.png" , Xof_media_type.Name_bitmap , Xof_ext_.Bry_jpg, 220, 110) .Test(Xof_ext_.Id_jpg);} // A.png and jpg -> jpg + @Test public void Jpeg_is_jpeg() {fxt.Init_("A.jpeg" , Xof_media_type.Name_bitmap , Xof_ext_.Bry_jpg, 220, 110) .Test(Xof_ext_.Id_jpeg);} // A.jpeg and jpg -> jpeg (unchanged) +} +class Xobldr__image__create_fxt { + private byte[] name, media_type, minor_mime; int w, h; + public Xobldr__image__create_fxt Init_png() {Name_("A.png").Media_type_(Xof_media_type.Name_bitmap).Minor_mime_(Xof_ext_.Bry_png).W_(220).H_(110); + return this; + } + public Xobldr__image__create_fxt Init_(String name, String media_type, byte[] minor_mime, int w, int h) { + Name_(name); + Media_type_(media_type); + Minor_mime_(minor_mime); + W_(w); + H_(h); + return this; + } + public Xobldr__image__create_fxt Name_(String v) {name = Bry_.new_a7(v); return this;} + public Xobldr__image__create_fxt Media_type_(String v) {media_type = Bry_.new_a7(v); return this;} + public Xobldr__image__create_fxt Minor_mime_(byte[] v) {minor_mime = v; return this;} + public Xobldr__image__create_fxt Minor_mime_(String v) {return Minor_mime_(Bry_.new_a7(v));} + public Xobldr__image__create_fxt W_(int v) {w = v; return this;} + public Xobldr__image__create_fxt H_(int v) {h = v; return this;} + public Xobldr__image__create_fxt Test(int expd) { + Tfds.Eq(expd, Xobldr__image__create.Calc_ext_id(Gfo_usr_dlg_.Noop, name, media_type, minor_mime, w, h)); + return this; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__lnki_regy__create.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__lnki_regy__create.java index a27517de8..bd99173ee 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__lnki_regy__create.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__lnki_regy__create.java @@ -13,3 +13,19 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.files.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xobldr__lnki_regy__create extends Xob_cmd__base implements Xob_cmd { + public Xobldr__lnki_regy__create(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + Db_conn conn = Xob_db_file.New__file_make(wiki.Fsys_mgr().Root_dir()).Conn(); + Xob_lnki_regy_tbl.Create_table(conn); + Xob_lnki_regy_tbl.Create_data(usr_dlg, conn, Xobldr__lnki_temp__create.Ns_file_is_case_match_all(wiki)); + } + + public static final String BLDR_CMD_KEY = "file.lnki_regy"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__lnki_regy__create(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__lnki_regy__create(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__lnki_temp__create.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__lnki_temp__create.java index a27517de8..31a60e816 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__lnki_temp__create.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__lnki_temp__create.java @@ -13,3 +13,177 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.wikis.pages.*; +import gplx.xowa.files.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.logs.*; import gplx.xowa.parsers.lnkis.*; import gplx.xowa.parsers.xndes.*; +import gplx.xowa.htmls.core.bldrs.*; import gplx.xowa.xtns.scribunto.*; import gplx.xowa.xtns.wbases.*; +import gplx.fsdb.meta.*; import gplx.xowa.files.fsdb.*; import gplx.fsdb.*; +import gplx.xowa.langs.vnts.*; import gplx.xowa.parsers.vnts.*; +import gplx.xowa.parsers.lnkis.files.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.addons.bldrs.files.dbs.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.utls.*; +import gplx.xowa.addons.bldrs.wmdumps.imglinks.*; +public class Xobldr__lnki_temp__create extends Xob_dump_mgr_base implements gplx.xowa.parsers.lnkis.files.Xop_file_logger { + private Xob_lnki_temp_tbl tbl; private boolean wdata_enabled = true, xtn_ref_enabled = true, gen_html, gen_hdump, load_all_imglinks; + private Xop_log_invoke_wkr invoke_wkr; private Xop_log_property_wkr property_wkr; + private boolean ns_file_is_case_match_all = true; private Xowe_wiki commons_wiki; + private final Xob_hdump_bldr hdump_bldr = new Xob_hdump_bldr(); private Vnt_convert_lang converter_lang; + public Xobldr__lnki_temp__create(Xob_bldr bldr, Xowe_wiki wiki) {this.Cmd_ctor(bldr, wiki);} + @Override public byte Init_redirect() {return Bool_.N_byte;} // lnki_temp does not look at redirect pages + @Override public int[] Init_ns_ary() {return ns_ids;} private int[] ns_ids = Int_ary_.New(Xow_ns_.Tid__main); + @Override protected void Init_reset(Db_conn conn) { + Db_cfg_tbl cfg_tbl = gplx.xowa.wikis.data.Xowd_cfg_tbl_.New(conn); + cfg_tbl.Delete_all(); + invoke_wkr.Init_reset(); + property_wkr.Init_reset(); + } + @Override protected Db_conn Init_db_file() { + ctx.Lnki().File_logger_(this); + Xob_db_file make_db = Xob_db_file.New__file_make(wiki.Fsys_mgr().Root_dir()); + Db_conn make_conn = make_db.Conn(); + this.tbl = new Xob_lnki_temp_tbl(make_conn); tbl.Create_tbl(); + this.gen_hdump = hdump_bldr.Init(wiki, make_conn, new Xob_hdump_tbl_retriever__ns_to_db(wiki)); + Xol_vnt_mgr vnt_mgr = wiki.Lang().Vnt_mgr(); + if (vnt_mgr.Enabled()) { + this.converter_lang = vnt_mgr.Convert_lang(); + converter_lang.Log__init(make_conn); + } + return make_conn; + } + @Override protected void Cmd_bgn_end() { + ns_file_is_case_match_all = Ns_file_is_case_match_all(wiki); // NOTE: must call after wiki.init + wiki.Html_mgr().Page_wtr_mgr().Wkr(Xopg_page_.Tid_read).Ctgs_enabled_(false); // disable categories else progress messages written (also for PERF) + if (wiki.File__bin_mgr() != null) + wiki.File__bin_mgr().Wkrs__del(gplx.xowa.files.bins.Xof_bin_wkr_.Key_http_wmf); // remove wmf wkr, else will try to download images during parsing + commons_wiki = app.Wiki_mgr().Get_by_or_make(Xow_domain_itm_.Bry__commons); + + // create imglinks + Xof_orig_wkr__img_links orig_wkr = new Xof_orig_wkr__img_links(wiki); + wiki.File__orig_mgr().Wkrs__set(orig_wkr); + if (load_all_imglinks) Xof_orig_wkr__img_links_.Load_all(orig_wkr); + + Xow_wiki_utl_.Clone_repos(wiki); + + // init log_mgr / property_wkr + Xop_log_mgr log_mgr = ctx.App().Log_mgr(); + log_mgr.Log_dir_(wiki.Fsys_mgr().Root_dir()); // put log in wiki dir, instead of user.temp + invoke_wkr = this.Invoke_wkr(); // set member reference + invoke_wkr = log_mgr.Make_wkr_invoke(); + property_wkr = this.Property_wkr(); // set member reference + property_wkr = log_mgr.Make_wkr_property(); + wiki.Appe().Wiki_mgr().Wdata_mgr().Enabled_(wdata_enabled); + if (!xtn_ref_enabled) gplx.xowa.xtns.cites.References_nde.Enabled = false; + + // init log wkrs + gplx.xowa.xtns.gallery.Gallery_xnde.Log_wkr = log_mgr.Make_wkr().Save_src_str_(Bool_.Y); + gplx.xowa.xtns.imaps.Imap_xnde.Log_wkr = log_mgr.Make_wkr(); + gplx.xowa.parsers.xndes.Xop_xnde_wkr.Timeline_log_wkr = log_mgr.Make_wkr(); + gplx.xowa.xtns.scores.Score_xnde.Log_wkr = log_mgr.Make_wkr(); + gplx.xowa.xtns.hieros.Hiero_xnde.Log_wkr = log_mgr.Make_wkr(); + gplx.xowa.xtns.math.Xomath_xnde.Log_wkr = log_mgr.Make_wkr().Save_src_str_(Bool_.Y); // enabled; DATE:2015-10-10 + + // init fsdb + Xof_fsdb_mgr__sql trg_fsdb_mgr = new Xof_fsdb_mgr__sql(); + wiki.File__fsdb_mode().Tid__v2__bld__y_(); + Fsdb_db_mgr__v2 fsdb_core = Fsdb_db_mgr__v2_bldr.Get_or_make(wiki, Bool_.Y); + trg_fsdb_mgr.Init_by_wiki(wiki); + Fsm_mnt_mgr trg_mnt_mgr = trg_fsdb_mgr.Mnt_mgr(); + wiki.File_mgr().Init_file_mgr_by_load(wiki); // must happen after fsdb.make + wiki.File__bin_mgr().Wkrs__del(gplx.xowa.files.bins.Xof_bin_wkr_.Key_http_wmf); // must happen after init_file_mgr_by_load; remove wmf wkr, else will try to download images during parsing + wiki.File__orig_mgr().Wkrs__del(gplx.xowa.files.origs.Xof_orig_wkr_.Tid_wmf_api); + + trg_mnt_mgr = new Fsm_mnt_mgr(); trg_mnt_mgr.Ctor_by_load(fsdb_core); + trg_mnt_mgr.Mnts__get_insert_idx_(Fsm_mnt_mgr.Mnt_idx_main); + Fsm_mnt_mgr.Patch(trg_mnt_mgr.Mnts__get_main().Cfg_mgr().Tbl()); // NOTE: see fsdb_make; DATE:2014-04-26 + tbl.Insert_bgn(); + log_mgr.Txn_bgn(); + } + @Override public void Exec_pg_itm_hook(int ns_ord, Xow_ns ns, Xowd_page_itm db_page, byte[] page_src) { + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, ns.Gen_ttl(db_page.Ttl_page_db())); + byte[] ttl_bry = ttl.Page_db(); + byte page_tid = Xow_page_tid.Identify(wiki.Domain_tid(), ns.Id(), ttl_bry); + if (page_tid != Xow_page_tid.Tid_wikitext) return; // ignore js, css, lua, json + Xoae_page page = ctx.Page(); + page.Clear_all(); + page.Bldr__ns_ord_(ns_ord); + page.Ttl_(ttl); + page.Db().Page().Id_(db_page.Id()); + page.Html_data().Redlink_list().Clear(); + page.Url_(Xoa_url.New(wiki, ttl)); + if (ns.Id_is_tmpl()) + parser.Parse_text_to_defn_obj(ctx, ctx.Tkn_mkr(), wiki.Ns_mgr().Ns_template(), ttl_bry, page_src); + else { + parser.Parse_page_all_clear(root, ctx, ctx.Tkn_mkr(), page_src); + if ( gen_html + && page.Redirect_trail().Itms__len() == 0) // don't generate html for redirected pages + wiki.Html_mgr().Page_wtr_mgr().Gen(ctx.Page().Root_(root), Xopg_page_.Tid_read); + if (gen_hdump) + hdump_bldr.Insert(ctx, page.Root_(root)); + root.Clear(); + } + } + @Override public void Exec_commit_hook() { + tbl.Conn().Txn_sav(); +// if (converter_lang != null) converter_lang.Log__save(); + if (gen_hdump) { + hdump_bldr.Commit(); + } + } + @Override public void Exec_end_hook() { +// if (converter_lang != null) converter_lang.Log__rls(); + if (gen_hdump) hdump_bldr.Term(); + String err_filter_mgr = invoke_wkr.Err_filter_mgr().Print(); + if (String_.Len_gt_0(err_filter_mgr)) usr_dlg.Warn_many("", "", err_filter_mgr); + wiki.Appe().Log_mgr().Txn_end(); + tbl.Insert_end(); + } + public void Log_file(Xop_ctx ctx, Xop_lnki_tkn lnki, byte caller_tid) { + if (lnki.Ttl().ForceLiteralLink()) return; // ignore literal links which creat a link to file, but do not show the image; EX: [[:File:A.png|thumb|120px]] creates a link to File:A.png, regardless of other display-oriented args + byte[] ttl = lnki.Ttl().Page_db(); + Xof_ext ext = Xof_ext_.new_by_ttl_(ttl); + double lnki_time = lnki.Time(); + int lnki_page = lnki.Page(); + byte[] ttl_commons = Xomp_lnki_temp_wkr.To_commons_ttl(ns_file_is_case_match_all, commons_wiki, ttl); + if ( Xof_lnki_page.Null_n(lnki_page) // page set + && Xof_lnki_time.Null_n(lnki_time)) // thumbtime set + usr_dlg.Warn_many("", "", "page and thumbtime both set; this may be an issue with fsdb: page=~{0} ttl=~{1}", ctx.Page().Ttl().Page_db_as_str(), String_.new_u8(ttl)); + if (lnki.Ns_id() == Xow_ns_.Tid__media) + caller_tid = Xop_file_logger_.Tid__media; + tbl.Insert_cmd_by_batch(ctx.Page().Bldr__ns_ord(), ctx.Page().Db().Page().Id(), ttl, ttl_commons, Byte_.By_int(ext.Id()), lnki.Lnki_type(), caller_tid, lnki.W(), lnki.H(), lnki.Upright(), lnki_time, lnki_page); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_wdata_enabled_)) wdata_enabled = m.ReadYn("v"); + else if (ctx.Match(k, Invk_xtn_ref_enabled_)) xtn_ref_enabled = m.ReadYn("v"); + else if (ctx.Match(k, Invk_ns_ids_)) ns_ids = Int_ary_.Parse(m.ReadStr("v"), "|"); + else if (ctx.Match(k, Invk_ns_ids_by_aliases)) ns_ids = Xobldr__lnki_temp__create_.Ns_ids_by_aliases(wiki, m.ReadStrAry("v", "|")); + else if (ctx.Match(k, Invk_gen_html_)) gen_html = m.ReadYn("v"); + else if (ctx.Match(k, Invk__load_all_imglinks_)) load_all_imglinks = m.ReadYn("v"); + else if (ctx.Match(k, Invk_hdump_bldr)) return hdump_bldr; + else if (ctx.Match(k, Invk_property_wkr)) return this.Property_wkr(); + else if (ctx.Match(k, Invk_invoke_wkr)) return this.Invoke_wkr(); + else return super.Invk(ctx, ikey, k, m); + return this; + } + private static final String Invk_wdata_enabled_ = "wdata_enabled_", Invk_xtn_ref_enabled_ = "xtn_ref_enabled_", Invk_gen_html_ = "gen_html_" + , Invk_ns_ids_ = "ns_ids_", Invk_ns_ids_by_aliases = "ns_ids_by_aliases" + , Invk_invoke_wkr = "invoke_wkr", Invk_property_wkr = "property_wkr", Invk_hdump_bldr = "hdump_bldr" + , Invk__load_all_imglinks_ = "load_all_imglinks_" + ; + public static final String BLDR_CMD_KEY = "file.lnki_temp"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__lnki_temp__create(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__lnki_temp__create(bldr, wiki);} + + private Xop_log_invoke_wkr Invoke_wkr() { + if (invoke_wkr == null) invoke_wkr = ((Scrib_xtn_mgr)bldr.App().Xtn_mgr().Get_or_fail(Scrib_xtn_mgr.XTN_KEY)).Invoke_wkr_or_new(); + return invoke_wkr; + } + private Xop_log_property_wkr Property_wkr() { + if (property_wkr == null) property_wkr = bldr.App().Wiki_mgr().Wdata_mgr().Property_wkr_or_new(); + return property_wkr; + } + public static boolean Ns_file_is_case_match_all(Xow_wiki wiki) {return wiki.Ns_mgr().Ns_file().Case_match() == Xow_ns_case_.Tid__all;} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__lnki_temp__create_.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__lnki_temp__create_.java index a27517de8..c01a4e15d 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__lnki_temp__create_.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__lnki_temp__create_.java @@ -13,3 +13,39 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.xowa.wikis.nss.*; +class Xobldr__lnki_temp__create_ { + public static int[] Ns_ids_by_aliases(Xowe_wiki wiki, String[] aliases) { + int[] rv = Xobldr__lnki_temp__create_.Ids_by_aliases(wiki.Ns_mgr(), aliases); + int aliases_len = aliases.length; + int ids_len = rv.length; + for (int i = 0; i < aliases_len; i++) { + String alias = aliases[i]; + int id = i < ids_len ? rv[i] : -1; + wiki.Appe().Usr_dlg().Note_many("", "", "ns: ~{0} <- ~{1}", Int_.To_str_fmt(id, "0000"), alias); + } + if (aliases_len != ids_len) throw Err_.new_wo_type("mismatch in aliases and ids", "aliases", aliases_len, "ids", ids_len); + return rv; + } + private static int[] Ids_by_aliases(Xow_ns_mgr ns_mgr, String[] aliases) { + List_adp list = List_adp_.New(); + int len = aliases.length; + for (int i = 0; i < len; i++) { + String alias = aliases[i]; + if (String_.Eq(alias, Xow_ns_.Key__main)) + list.Add(ns_mgr.Ns_main()); + else { + Xow_ns ns = ns_mgr.Names_get_or_null(Bry_.new_u8(alias)); + if (ns != null) + list.Add(ns); + } + } + len = list.Count(); + int[] rv = new int[len]; + for (int i = 0; i < len; i++) { + rv[i] = ((Xow_ns)list.Get_at(i)).Id(); + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__lnki_temp__create__tst.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__lnki_temp__create__tst.java index a27517de8..f948e8c0c 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__lnki_temp__create__tst.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__lnki_temp__create__tst.java @@ -13,3 +13,29 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import org.junit.*; +public class Xobldr__lnki_temp__create__tst { + private Xobldr__lnki_temp__create__fxt fxt = new Xobldr__lnki_temp__create__fxt(); + @Test public void Xto_commons() { + fxt.Init__to_commons(true); + fxt.Test__to_commons("a", "A"); + fxt.Test__to_commons("A", null); + fxt.Init__to_commons(false); + fxt.Test__to_commons("a", null); + fxt.Test__to_commons("A", null); + } +} +class Xobldr__lnki_temp__create__fxt { + private boolean wiki_ns_file_is_case_match_all; + private Xowe_wiki commons_wiki; + public Xobldr__lnki_temp__create__fxt Init__to_commons(boolean wiki_ns_file_is_case_match_all) { + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + this.wiki_ns_file_is_case_match_all = wiki_ns_file_is_case_match_all; + this.commons_wiki = Xoa_app_fxt.Make__wiki__edit(app); // commons_wiki will default to Xow_ns.Id_file of case_match_1st + return this; + } + public void Test__to_commons(String ttl, String expd) { + Tfds.Eq(expd, String_.new_u8(gplx.xowa.addons.bldrs.mass_parses.parses.utls.Xomp_lnki_temp_wkr.To_commons_ttl(wiki_ns_file_is_case_match_all, commons_wiki, Bry_.new_u8(ttl)))); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__orig_regy__create.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__orig_regy__create.java index a27517de8..1685e6dfc 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__orig_regy__create.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__orig_regy__create.java @@ -13,3 +13,34 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.files.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.wikis.domains.*; +public class Xobldr__orig_regy__create extends Xob_cmd__base { + private boolean repo_0_is_remote = false; + public Xobldr__orig_regy__create(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + Db_conn conn = Xob_db_file.New__file_make(wiki.Fsys_mgr().Root_dir()).Conn(); + Xob_orig_regy_tbl.Create_table(conn); + Xowe_wiki commons_wiki = bldr.App().Wiki_mgr().Get_by_or_make(Xow_domain_itm_.Bry__commons).Init_assert(); + Xowe_wiki repo_0 = wiki, repo_1 = commons_wiki; + if (repo_0_is_remote) { // NOTE: default is false; local_wiki will be preferred over commons_wiki + repo_0 = commons_wiki; + repo_1 = wiki; + } + repo_0.Init_assert(); repo_1.Init_assert(); + Xob_db_file file_registry_db = Xob_db_file.New__page_regy(commons_wiki.Fsys_mgr().Root_dir()); + Xob_orig_regy_tbl.Create_data(bldr.Usr_dlg(), conn, file_registry_db, repo_0_is_remote, repo_0, repo_1, Xobldr__lnki_temp__create.Ns_file_is_case_match_all(wiki)); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_repo_0_is_remote_)) this.repo_0_is_remote = m.ReadYn("v"); + else return super.Invk(ctx, ikey, k, m); + return this; + } private static final String Invk_repo_0_is_remote_ = "repo_0_is_remote_"; + + public static final String BLDR_CMD_KEY = "file.orig_regy"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__orig_regy__create(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__orig_regy__create(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__page_file_map__create.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__page_file_map__create.java index a27517de8..67d725471 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__page_file_map__create.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__page_file_map__create.java @@ -13,3 +13,148 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.files.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xobldr__page_file_map__create extends Xob_cmd__base { + private Db_conn conn; + public Xobldr__page_file_map__create(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + this.conn = Xob_db_file.New__file_make(wiki.Fsys_mgr().Root_dir()).Conn(); + Append__fsdb_id(); + Create__fsdb_idxs(); + Update__fsdb_id(); + Create__page_file_map(); + } + private void Append__fsdb_id() { // NOTE: append the field; do not create with table b/c will be extra baggage during all REPLACE INTO + String[] tbls = String_.Ary("xfer_regy", "xfer_temp", "lnki_regy", "lnki_temp", "fsdb_regy"); + String fld = "fsdb_id"; + for (String tbl : tbls) { + if (!conn.Meta_fld_exists(tbl, fld)) + conn.Meta_fld_append(tbl, Dbmeta_fld_itm.new_int(fld).Default_(-1)); // EX: ALTER TABLE xfer_regy ADD fsdb_id INTEGER DEFAULT -1; + } + } + private void Create__fsdb_idxs() { + // EX: CREATE INDEX xfer_regy__fsdb_regy ON xfer_regy (lnki_ttl, file_is_orig, orig_repo, file_w, lnki_time, lnki_page); + Dbmeta_idx_itm[] idxs = new Dbmeta_idx_itm[] + { Dbmeta_idx_itm.new_normal_by_tbl("xfer_regy", "page_file_map_1", "lnki_ttl", "file_is_orig", "orig_repo", "file_w", "lnki_time", "lnki_page") + , Dbmeta_idx_itm.new_normal_by_tbl("xfer_temp", "page_file_map_1", "lnki_ttl" , "lnki_w", "lnki_h", "lnki_upright", "lnki_time", "lnki_page") + , Dbmeta_idx_itm.new_normal_by_tbl("xfer_temp", "page_file_map_2", "orig_redirect_src", "lnki_w", "lnki_h", "lnki_upright", "lnki_time", "lnki_page") + , Dbmeta_idx_itm.new_normal_by_tbl("lnki_regy", "page_file_map_1", "lnki_ttl", "lnki_w", "lnki_h", "lnki_upright", "lnki_time", "lnki_page") + , Dbmeta_idx_itm.new_normal_by_tbl("lnki_temp", "page_file_map_1", "lnki_ttl", "lnki_w", "lnki_h", "lnki_upright", "lnki_time", "lnki_page") + , Dbmeta_idx_itm.new_normal_by_tbl("fsdb_regy", "page_file_map_1", "fsdb_id", "fsdb_fil_id", "fsdb_thm_id") + }; + for (Dbmeta_idx_itm idx : idxs) { + if (!conn.Meta_idx_exists(idx.Name())) + conn.Meta_idx_create(idx); + } + } + private void Update__fsdb_id() { + conn.Exec_sql_concat_w_msg + ( "updating fsdb_id.xfer_regy" + , "UPDATE xfer_regy " + , "SET fsdb_id = Coalesce" + , "((" + , "SELECT fsdb_id " + , "FROM fsdb_regy fr" + , "WHERE fr.fsdb_name = xfer_regy.lnki_ttl" + , "AND fr.fsdb_is_orig = xfer_regy.file_is_orig" + , "AND fr.fsdb_repo = xfer_regy.orig_repo" + , "AND fr.fsdb_w = xfer_regy.file_w" + , "AND fr.fsdb_time = xfer_regy.lnki_time" + , "AND fr.fsdb_page = xfer_regy.lnki_page" + , "), -1);" + ); + conn.Exec_sql_concat_w_msg + ( "updating fsdb_id.xfer_temp" + , "UPDATE xfer_temp" + , "SET fsdb_id = Coalesce" + , "((" + , "SELECT fsdb_id " + , "FROM xfer_regy xr" + , "WHERE xr.lnki_ttl = xfer_temp.lnki_ttl" + , "AND xr.file_is_orig = xfer_temp.file_is_orig" + , "AND xr.orig_repo = xfer_temp.orig_repo" + , "AND xr.file_w = xfer_temp.file_w" + , "AND xr.lnki_time = xfer_temp.lnki_time" + , "AND xr.lnki_page = xfer_temp.lnki_page" + , "), -1);" + ); + conn.Exec_sql_concat_w_msg + ( "updating fsdb_id.lnki_regy.redirect" + , "UPDATE lnki_regy" + , "SET fsdb_id = Coalesce" + , "((" + , "SELECT fsdb_id " + , "FROM xfer_temp xt" + , "WHERE xt.orig_redirect_src = lnki_regy.lnki_ttl" + , "AND xt.lnki_w = lnki_regy.lnki_w" + , "AND xt.lnki_h = lnki_regy.lnki_h" + , "AND xt.lnki_upright = lnki_regy.lnki_upright" + , "AND xt.lnki_time = lnki_regy.lnki_time" + , "AND xt.lnki_page = lnki_regy.lnki_page" + , "), -1);" + ); + conn.Exec_sql_concat_w_msg + ( "updating fsdb_id.lnki_regy.direct" + , "UPDATE lnki_regy" + , "SET fsdb_id = Coalesce" + , "((" + , "SELECT fsdb_id " + , "FROM xfer_temp xt" + , "WHERE xt.lnki_ttl = lnki_regy.lnki_ttl" + , "AND xt.lnki_w = lnki_regy.lnki_w" + , "AND xt.lnki_h = lnki_regy.lnki_h" + , "AND xt.lnki_upright = lnki_regy.lnki_upright" + , "AND xt.lnki_time = lnki_regy.lnki_time" + , "AND xt.lnki_page = lnki_regy.lnki_page" + , "), -1)" + , "WHERE Coalesce(fsdb_id, -1) = -1" + , ";" + ); + conn.Exec_sql_concat_w_msg + ( "updating fsdb_id.lnki_temp" + , "UPDATE lnki_temp" + , "SET fsdb_id = Coalesce" + , "((" + , "SELECT fsdb_id " + , "FROM lnki_regy lr" + , "WHERE lr.lnki_ttl = lnki_temp.lnki_ttl" + , "AND lr.lnki_w = lnki_temp.lnki_w" + , "AND lr.lnki_h = lnki_temp.lnki_h" + , "AND lr.lnki_upright = lnki_temp.lnki_upright" + , "AND lr.lnki_time = lnki_temp.lnki_time" + , "AND lr.lnki_page = lnki_temp.lnki_page" + , "), -1);" + ); + } + private void Create__page_file_map() { + Xob_db_file map_db = Xob_db_file.New__page_file_map(wiki); + Db_conn map_conn = map_db.Conn(); + Page_file_map_tbl map_tbl = new Page_file_map_tbl(map_conn, "page_file_map"); + map_conn.Meta_tbl_remake(map_tbl.Meta()); + map_conn.Env_db_attach("make_db", conn); + map_conn.Exec_sql_concat_w_msg + ( "inserting page_file_map" + , "INSERT INTO page_file_map (page_id, fil_id, thm_id, sort_id, count_of)" + , "SELECT lt.lnki_page_id" + , ", fr.fsdb_fil_id" + , ", fr.fsdb_thm_id" + , ", -1" + , ", Count(fr.fsdb_thm_id)" + , "FROM make_db.lnki_temp lt" + , " JOIN make_db.fsdb_regy fr ON lt.fsdb_id = fr.fsdb_id" + , "GROUP BY lt.lnki_page_id" + , ", fr.fsdb_fil_id" + , ", fr.fsdb_thm_id" + , ";" + ); + map_conn.Env_db_detach("make_db"); + map_conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl("page_file_map", "main", "fil_id", "thm_id")); + } + + public static final String BLDR_CMD_KEY = "file.page_file_map.create"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__page_file_map__create(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__page_file_map__create(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__page_regy__create.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__page_regy__create.java index a27517de8..35bb069bf 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__page_regy__create.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__page_regy__create.java @@ -13,3 +13,38 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.dbs.engines.sqlite.*; import gplx.xowa.addons.bldrs.files.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.files.repos.*; +public class Xobldr__page_regy__create extends Xob_cmd__base { + private boolean build_commons = false; + public Xobldr__page_regy__create(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + Xowe_wiki commons_wiki = bldr.App().Wiki_mgr().Get_by_or_make(Xow_domain_itm_.Bry__commons).Init_assert(); + Db_conn page_regy_provider = Xob_db_file.New__page_regy(commons_wiki.Fsys_mgr().Root_dir()).Conn(); + commons_wiki.Init_assert(); + if (build_commons) { + Xob_page_regy_tbl.Reset_table(page_regy_provider); + Xob_page_regy_tbl.Create_data(bldr.Usr_dlg(), page_regy_provider, Xof_repo_tid_.Tid__remote, commons_wiki); + Sqlite_engine_.Idx_create(usr_dlg, page_regy_provider, "repo_page", Xob_page_regy_tbl.Idx_main); + } + else { + if (!Bry_.Eq(commons_wiki.Domain_bry(), wiki.Domain_bry())) { // skip local wiki if cur wiki is commons + wiki.Init_assert(); + Xob_page_regy_tbl.Delete_local(page_regy_provider); + Xob_page_regy_tbl.Create_data(bldr.Usr_dlg(), page_regy_provider, Xof_repo_tid_.Tid__local, wiki); + } + } + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_build_commons_)) build_commons = m.ReadYn("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_build_commons_ = "build_commons_"; + + public static final String BLDR_CMD_KEY = "file.page_regy"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__page_regy__create(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__page_regy__create(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__redirect__create.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__redirect__create.java index a27517de8..83bead2c1 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__redirect__create.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__redirect__create.java @@ -13,3 +13,51 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.xowa.wikis.dbs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.langs.htmls.encoders.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.parsers.utils.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.addons.bldrs.files.dbs.*; +public class Xobldr__redirect__create extends Xob_dump_mgr_base { + private Db_conn conn; private Xob_redirect_tbl redirect_tbl; + private Xodb_mgr_sql db_mgr; private Xop_redirect_mgr redirect_mgr; private Gfo_url_encoder encoder; + public Xobldr__redirect__create(Xob_bldr bldr, Xowe_wiki wiki) {this.Cmd_ctor(bldr, wiki); this.Reset_db_y_();} + @Override public int[] Init_ns_ary() {return Int_ary_.New(Xow_ns_.Tid__file);} // restrict to file ns + @Override public byte Init_redirect() {return Bool_.Y_byte;} // restrict to redirects + @Override protected void Init_reset(Db_conn conn) { + Db_cfg_tbl cfg_tbl = gplx.xowa.wikis.data.Xowd_cfg_tbl_.New(conn); + cfg_tbl.Delete_all(); + conn.Exec_sql("DELETE FROM " + Xob_redirect_tbl.Tbl_name); + } + @Override protected Db_conn Init_db_file() { + this.db_mgr = wiki.Db_mgr_as_sql(); + redirect_mgr = wiki.Redirect_mgr(); + encoder = gplx.langs.htmls.encoders.Gfo_url_encoder_.Http_url_ttl; + redirect_tbl = new Xob_redirect_tbl(wiki.Fsys_mgr().Root_dir(), gplx.langs.htmls.encoders.Gfo_url_encoder_.Http_url_ttl).Create_table(); + conn = redirect_tbl.Conn(); + conn.Txn_bgn("bldr__redirect"); + return conn; + } + @Override protected void Cmd_bgn_end() {} + @Override public void Exec_pg_itm_hook(int ns_ord, Xow_ns ns, Xowd_page_itm page, byte[] page_src) { + Xoa_ttl redirect_ttl = redirect_mgr.Extract_redirect(page_src); + byte[] redirect_ttl_bry = Xoa_ttl.Replace_spaces(redirect_ttl.Page_db()); // NOTE: spaces can still exist b/c redirect is scraped from #REDIRECT which sometimes has a mix; EX: "A_b c" + redirect_ttl_bry = encoder.Decode(redirect_ttl_bry); + redirect_tbl.Insert(page.Id(), Xoa_ttl.Replace_spaces(page.Ttl_page_db()), -1, redirect_ttl.Ns().Id(), redirect_ttl_bry, redirect_ttl.Anch_txt(), 1); + } + @Override public void Exec_commit_hook() { + conn.Txn_sav(); + } + @Override public void Exec_end_hook() { + conn.Txn_end(); + redirect_tbl.Create_indexes(usr_dlg); + redirect_tbl.Update_trg_redirect_id(db_mgr.Core_data_mgr().Db__core().Url(), 4); + } + + public static final String BLDR_CMD_KEY = "wiki.redirect"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__redirect__create(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__redirect__create(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__text_db__drop_page.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__text_db__drop_page.java index a27517de8..36feb48c9 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__text_db__drop_page.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__text_db__drop_page.java @@ -13,3 +13,30 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.wikis.data.*; import gplx.dbs.*; import gplx.dbs.engines.sqlite.*; import gplx.xowa.wikis.dbs.*; +import gplx.xowa.addons.bldrs.files.dbs.*; +public class Xobldr__text_db__drop_page extends Xob_cmd__base { + public Xobldr__text_db__drop_page(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + wiki.Init_assert(); + Xow_db_mgr db_mgr = wiki.Data__core_mgr(); + int len = db_mgr.Dbs__len(); + for (int i = 0; i < len; i++) { + Xow_db_file db_file = db_mgr.Dbs__get_at(i); + switch (db_file.Tid()) { + case Xow_db_file_.Tid__wiki_solo: + case Xow_db_file_.Tid__text_solo: + case Xow_db_file_.Tid__text: + db_file.Conn().Meta_tbl_delete(Xob_page_dump_tbl.Tbl_name); + break; + } + } + } + + public static final String BLDR_CMD_KEY = "wiki.page_dump.drop"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__text_db__drop_page(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__text_db__drop_page(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__text_db__make_page.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__text_db__make_page.java index a27517de8..f79c74ef6 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__text_db__make_page.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__text_db__make_page.java @@ -13,3 +13,32 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.wikis.data.*; import gplx.dbs.*; import gplx.dbs.engines.sqlite.*; import gplx.xowa.wikis.dbs.*; +import gplx.xowa.addons.bldrs.files.dbs.*; +public class Xobldr__text_db__make_page extends Xob_cmd__base { + public Xobldr__text_db__make_page(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + wiki.Init_assert(); + Xow_db_mgr db_mgr = wiki.Data__core_mgr(); + Io_url page_db_url = db_mgr.Db__core().Url(); + int len = db_mgr.Dbs__len(); + for (int i = 0; i < len; i++) { + Xow_db_file db_file = db_mgr.Dbs__get_at(i); + switch (db_file.Tid()) { + case Xow_db_file_.Tid__wiki_solo: + case Xow_db_file_.Tid__text_solo: + case Xow_db_file_.Tid__text: + Xob_page_dump_tbl tbl = new Xob_page_dump_tbl(db_file.Conn()); + tbl.Create_data(page_db_url, db_file.Id()); + break; + } + } + } + + public static final String BLDR_CMD_KEY = "wiki.page_dump.make"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__text_db__make_page(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__text_db__make_page(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__xfer_regy__create.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__xfer_regy__create.java index a27517de8..2d8960318 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__xfer_regy__create.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__xfer_regy__create.java @@ -13,3 +13,23 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.files.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xobldr__xfer_regy__create extends Xob_cmd__base { + public Xobldr__xfer_regy__create(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + Db_conn conn = Xob_db_file.New__file_make(wiki.Fsys_mgr().Root_dir()).Conn(); + conn.Txn_bgn("bldr__xfer_regy"); + Xob_xfer_regy_tbl.Create_table(conn); + Xob_xfer_regy_tbl.Create_data(usr_dlg, conn); + Xob_xfer_regy_tbl.Create_index(usr_dlg, conn); + Xob_xfer_regy_log_tbl.Create_table(conn); + conn.Txn_end(); + } + + public static final String BLDR_CMD_KEY = "file.xfer_regy"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__xfer_regy__create(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__xfer_regy__create(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__xfer_regy__update_downloaded.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__xfer_regy__update_downloaded.java index a27517de8..269488984 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__xfer_regy__update_downloaded.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__xfer_regy__update_downloaded.java @@ -13,3 +13,56 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.dbs.engines.sqlite.*; import gplx.xowa.addons.bldrs.files.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.fsdb.meta.*; +public class Xobldr__xfer_regy__update_downloaded extends Xob_cmd__base implements Xob_cmd { + public Xobldr__xfer_regy__update_downloaded(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + wiki.Init_assert(); // NOTE: must init wiki to set up db_core; DATE:2015-08-17 + Db_conn make_conn = Xob_db_file.New__file_make(wiki.Fsys_mgr().Root_dir()).Conn(); + this.Create_fsdb_regy(make_conn); + this.Update_xfer_regy(make_conn); + } + private void Create_fsdb_regy(Db_conn make_conn) { + wiki.File_mgr().Init_file_mgr_by_load(wiki); // NOTE: uses ./file.core.xowa; never uses -prv/file.core.xowa or /prv/file.core.xowa; DATE:2015-09-10 + Fsm_mnt_itm mnt_itm = wiki.File_mgr().Fsdb_mgr().Mnt_mgr().Mnts__get_main(); // 0 = fsdb.main + + // connect to fsdb_db; create fsdb_regy + Db_conn fsdb_conn = mnt_itm.Atr_mgr().Db__core().Conn(); // 0 = fsdb.atr.00 + make_conn.Env_db_attach("fsdb_db", fsdb_conn); + make_conn.Meta_tbl_delete(Xob_fsdb_regy_tbl_.Tbl_name); + Sqlite_engine_.Tbl_create_and_delete(make_conn, Xob_fsdb_regy_tbl_.Tbl_name, Xob_fsdb_regy_tbl_.Tbl_sql); + + // insert fil into fsdb_regy + make_conn.Txn_bgn("bldr__xfer_regy_update"); + make_conn.Exec_sql(Xob_fsdb_regy_tbl_.Insert_fsdb_fil); + String fsdb_thm_tbl = mnt_itm.Db_mgr().File__schema_is_1() ? "fsdb_xtn_thm" : "fsdb_thm"; + + // insert thm into fsdb_regy + String insert_sql_fsdb_thm + = wiki.File_mgr().Fsdb_mgr().Mnt_mgr().Mnts__get_main().Cfg_mgr().Schema_thm_page() + ? String_.Format(Xob_fsdb_regy_tbl_.Insert_fsdb_thm, fsdb_thm_tbl) + : Xob_fsdb_regy_tbl_.Insert_fsdb_thm_v0 + ; + make_conn.Exec_sql(insert_sql_fsdb_thm); + + // end txn; cleanup + make_conn.Txn_end(); + Sqlite_engine_.Idx_create(make_conn, Xob_fsdb_regy_tbl_.Idx_main); + Sqlite_engine_.Db_detach(make_conn, "fsdb_db"); + } + private void Update_xfer_regy(Db_conn make_conn) { + make_conn.Txn_bgn("bldr__xfer_regy_update_status"); + make_conn.Exec_sql(Xob_fsdb_regy_tbl_.Update_regy_nil); + make_conn.Exec_sql(Xob_fsdb_regy_tbl_.Update_regy_fil); + make_conn.Exec_sql(Xob_fsdb_regy_tbl_.Update_regy_thm); + make_conn.Txn_end(); + } + + public static final String BLDR_CMD_KEY = "file.xfer_regy_update"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__xfer_regy__update_downloaded(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__xfer_regy__update_downloaded(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__xfer_temp__insert_orig.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__xfer_temp__insert_orig.java index a27517de8..f98dbe171 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__xfer_temp__insert_orig.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__xfer_temp__insert_orig.java @@ -13,3 +13,94 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.core.stores.*; import gplx.dbs.*; import gplx.xowa.addons.bldrs.files.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.files.*; import gplx.xowa.files.exts.*; import gplx.xowa.parsers.lnkis.*; +public class Xobldr__xfer_temp__insert_orig extends Xob_cmd__base { + private byte[] ext_rules_key = Bry_.Empty; + public Xobldr__xfer_temp__insert_orig(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + Db_conn conn = Xob_db_file.New__file_make(wiki.Fsys_mgr().Root_dir()).Conn(); + Xob_xfer_temp_tbl.Create_table(conn); + Db_stmt trg_stmt = Xob_xfer_temp_tbl.Insert_stmt(conn); + conn.Txn_bgn("bldr__xfer_temp"); + DataRdr rdr = conn.Exec_sql_as_old_rdr(Sql_select_clause); + long[] ext_maxs = Calc_ext_max(); + while (rdr.MoveNextPeer()) { + int lnki_ext = rdr.ReadByte(Xob_lnki_regy_tbl.Fld_lnki_ext); + String orig_media_type = rdr.ReadStrOr(Xob_orig_regy_tbl.Fld_orig_media_type, ""); // convert nulls to "" + lnki_ext = rdr.ReadInt(Xob_orig_regy_tbl.Fld_orig_file_ext); + int lnki_id = rdr.ReadInt(Xob_lnki_regy_tbl.Fld_lnki_id); + int lnki_tier_id = rdr.ReadInt(Xob_lnki_regy_tbl.Fld_lnki_tier_id); + byte orig_repo = rdr.ReadByte(Xob_orig_regy_tbl.Fld_orig_repo); + int orig_page_id = rdr.ReadIntOr(Xob_orig_regy_tbl.Fld_orig_page_id, -1); + if (orig_page_id == -1) continue; // no orig found; ignore + String join_ttl = rdr.ReadStr(Xob_orig_regy_tbl.Fld_orig_file_ttl); + String redirect_src = rdr.ReadStr(Xob_orig_regy_tbl.Fld_lnki_ttl); + if (String_.Eq(join_ttl, redirect_src)) // lnki_ttl is same as redirect_src; not a redirect + redirect_src = ""; + int orig_w = rdr.ReadIntOr(Xob_orig_regy_tbl.Fld_orig_w, -1); + int orig_h = rdr.ReadIntOr(Xob_orig_regy_tbl.Fld_orig_h, -1); + int orig_size = rdr.ReadIntOr(Xob_orig_regy_tbl.Fld_orig_size, -1); + if (orig_size > ext_maxs[lnki_ext]) continue; + int lnki_page_id = rdr.ReadInt(Xob_lnki_regy_tbl.Fld_lnki_page_id); + Xob_xfer_temp_tbl.Insert(trg_stmt, lnki_id, lnki_tier_id, lnki_page_id, orig_repo, orig_page_id, join_ttl, redirect_src, lnki_ext, Xop_lnki_type.Id_none, orig_media_type + , Bool_.Y // orig is y + , orig_w, orig_h + , orig_w, orig_h // file_w, file_h is same as orig_w,orig_h; i.e.: make same file_w as orig_w + , Xof_img_size.Null, Xof_img_size.Null // html_w, html_h is -1; i.e.: will not be displayed in page at specific size (this matches logic in Xobldr__xfer_temp__insert_thm) + , rdr.ReadInt(Xob_lnki_regy_tbl.Fld_lnki_w) + , rdr.ReadInt(Xob_lnki_regy_tbl.Fld_lnki_h) + , Xop_lnki_tkn.Upright_null + , Xof_lnki_time.Null + , Xof_lnki_page.Null + , 0); + } + conn.Txn_end(); + } + private long[] Calc_ext_max() { + Xof_rule_grp ext_rules = wiki.Appe().File_mgr().Ext_rules().Get_or_new(ext_rules_key); + long[] rv = new long[Xof_ext_.Id__max]; + for (int i = 0; i < Xof_ext_.Id__max; i++) { + byte[] ext = Xof_ext_.Get_ext_by_id_(i); + Xof_rule_itm ext_rule = ext_rules.Get_or_null(ext); + long max = ext_rule == null ? 0 : ext_rule.Make_max(); + rv[i] = max; + } + return rv; + } + private static final String + Sql_select_clause = String_.Concat_lines_nl + ( "SELECT DISTINCT" + , " l.lnki_id" +// , ", lnki_ttl" + , ", l.lnki_ext" + , ", l.lnki_page_id" + , ", o.orig_repo" + , ", o.orig_page_id" +// , ", orig_file_id" + , ", o.orig_file_ttl" + , ", o.orig_file_ext" + , ", o.lnki_ttl" + , ", o.orig_size" + , ", o.orig_w" + , ", o.orig_h" + , ", o.orig_media_type" +// , ", orig_bits" + , "FROM lnki_regy l" + , " JOIN orig_regy o ON o.lnki_ttl = l.lnki_ttl" + , "WHERE o.orig_file_ttl IS NOT NULL" + , "ORDER BY o.orig_file_ttl DESC" + ); + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_ext_rules_)) ext_rules_key = m.ReadBry("v"); + else return super.Invk (ctx, ikey, k, m); + return this; + } private static final String Invk_ext_rules_ = "ext_rules_"; + + public static final String BLDR_CMD_KEY = "file.xfer_temp.orig"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__xfer_temp__insert_orig(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__xfer_temp__insert_orig(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__xfer_temp__insert_thm.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__xfer_temp__insert_thm.java index a27517de8..4ac895abe 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__xfer_temp__insert_thm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/cmds/Xobldr__xfer_temp__insert_thm.java @@ -13,3 +13,67 @@ 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.addons.bldrs.files.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.files.dbs.*; import gplx.core.stores.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.addons.bldrs.files.utls.*; +import gplx.xowa.files.*; +public class Xobldr__xfer_temp__insert_thm extends Xob_cmd__base { + public Xobldr__xfer_temp__insert_thm(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + Db_conn conn = Xob_db_file.New__file_make(wiki.Fsys_mgr().Root_dir()).Conn(); + Xob_xfer_temp_tbl.Create_table(conn); + Db_stmt trg_stmt = Xob_xfer_temp_tbl.Insert_stmt(conn); + conn.Txn_bgn("bldr__xfer_temp_thumb"); + DataRdr rdr = conn.Exec_sql_as_old_rdr(String_.Concat_lines_nl + ( "SELECT l.lnki_id" + , ", l.lnki_tier_id" + , ", l.lnki_page_id" + , ", l.lnki_ext" + , ", l.lnki_type" + , ", l.lnki_src_tid" + , ", l.lnki_w" + , ", l.lnki_h" + , ", l.lnki_upright" + , ", l.lnki_time" + , ", l.lnki_page" + , ", l.lnki_count" + , ", o.orig_repo" + , ", o.orig_page_id" + , ", o.orig_file_ttl" + , ", o.orig_file_ext" + , ", o.orig_file_id" + , ", o.lnki_ttl" + , ", o.orig_w" + , ", o.orig_h" + , ", o.orig_media_type" + , ", o.orig_minor_mime" + , "FROM lnki_regy l" + , " JOIN orig_regy o ON o.lnki_ttl = l.lnki_ttl" + , "WHERE o.orig_file_ttl IS NOT NULL" + , "ORDER BY o.orig_file_ttl, o.orig_repo DESC, l.lnki_w DESC" // NOTE: local=1,common=0; DATE:2015-03-22 + )); + Xob_xfer_temp_itm temp_itm = new Xob_xfer_temp_itm(); + Xof_img_size img_size = new Xof_img_size(); + byte[] cur_ttl = Bry_.Empty; byte cur_repo = Byte_.Max_value_127; + while (rdr.MoveNextPeer()) { + temp_itm.Clear(); + temp_itm.Load(rdr); + if (Bry_.Eq(cur_ttl, temp_itm.Orig_file_ttl())) { // same ttl; DATE:2015-03-22 + if (temp_itm.Orig_repo() != cur_repo) // if repo is different, ignore 2nd; handles images in both repos; take 1st only (which should be local) + continue; + } + else { // new ttl; update ttl, repo + cur_ttl = temp_itm.Orig_file_ttl(); + cur_repo = temp_itm.Orig_repo(); + } + if (temp_itm.Chk(img_size)) + temp_itm.Insert(trg_stmt, img_size); + } + conn.Txn_end(); + } + + public static final String BLDR_CMD_KEY = "file.xfer_temp.thumb"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr__xfer_temp__insert_thm(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr__xfer_temp__insert_thm(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Page_file_map_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Page_file_map_tbl.java index a27517de8..0ca99c50d 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Page_file_map_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Page_file_map_tbl.java @@ -13,3 +13,24 @@ 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.addons.bldrs.files.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; +public class Page_file_map_tbl implements Db_tbl { + public final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + public final String fld_page_id, fld_fil_id, fld_thm_id, fld_sort_id, fld_count_of; + public final Db_conn conn; + public Page_file_map_tbl(Db_conn conn, String tbl_name) { + this.conn = conn; + this.tbl_name = tbl_name; + this.fld_page_id = flds.Add_int("page_id"); + this.fld_fil_id = flds.Add_int("fil_id"); + this.fld_thm_id = flds.Add_int("thm_id"); + this.fld_sort_id = flds.Add_int("sort_id"); + this.fld_count_of = flds.Add_int("count_of"); + this.meta = Dbmeta_tbl_itm.New(tbl_name, flds); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(meta);} + public Dbmeta_tbl_itm Meta() {return meta;} private final Dbmeta_tbl_itm meta; + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_fsdb_regy_tbl_.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_fsdb_regy_tbl_.java index a27517de8..86181c661 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_fsdb_regy_tbl_.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_fsdb_regy_tbl_.java @@ -13,3 +13,118 @@ 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.addons.bldrs.files.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; +public class Xob_fsdb_regy_tbl_ { + public static final String Tbl_name = "fsdb_regy"; + public static final String Tbl_sql = String_.Concat_lines_nl + ( "CREATE TABLE fsdb_regy " + , "( fsdb_id integer NOT NULL PRIMARY KEY AUTOINCREMENT" + , ", fsdb_name varchar(255) NOT NULL" + , ", fsdb_is_orig tinyint NOT NULL" + , ", fsdb_repo tinyint NOT NULL" + , ", fsdb_w integer NOT NULL" + , ", fsdb_time double NOT NULL" + , ", fsdb_page integer NOT NULL" + , ", fsdb_db_id integer NOT NULL" + , ", fsdb_size bigint NOT NULL" + , ", fsdb_status tinyint NOT NULL" + , ", fsdb_fil_id integer NOT NULL" + , ", fsdb_thm_id integer NOT NULL" + , ", fsdb_deleted tinyint NOT NULL" + , ");" + ); + public static final Db_idx_itm Idx_main = Db_idx_itm.sql_("CREATE INDEX fsdb_regy__main ON fsdb_regy (fsdb_name, fsdb_is_orig, fsdb_repo, fsdb_w, fsdb_time, fsdb_page);"); + public static final String + Insert_fsdb_fil = String_.Concat_lines_nl + ( "INSERT INTO fsdb_regy (fsdb_name, fsdb_is_orig, fsdb_repo, fsdb_w, fsdb_time, fsdb_page, fsdb_db_id, fsdb_size, fsdb_status, fsdb_fil_id, fsdb_thm_id, fsdb_deleted)" + , "SELECT f.fil_name" + , ", 1" + , ", CASE WHEN d.dir_name = 'commons.wikimedia.org' THEN 0 ELSE 1 END" + , ", -1" + , ", -1" + , ", -1" + , ", f.fil_bin_db_id" + , ", f.fil_size" + , ", 0" + , ", f.fil_id" + , ", -1" + , ", 0" + , "FROM fsdb_db.fsdb_fil f" + , " JOIN fsdb_db.fsdb_dir d ON f.fil_owner_id = d.dir_id" + , "WHERE f.fil_bin_db_id != -1" + , ";" + ) + , Insert_fsdb_thm = String_.Concat_lines_nl + ( "INSERT INTO fsdb_regy (fsdb_name, fsdb_is_orig, fsdb_repo, fsdb_w, fsdb_time, fsdb_page, fsdb_db_id, fsdb_size, fsdb_status, fsdb_fil_id, fsdb_thm_id, fsdb_deleted)" + , "SELECT f.fil_name" + , ", 0" + , ", CASE WHEN d.dir_name = 'commons.wikimedia.org' THEN 0 ELSE 1 END" + , ", t.thm_w" + , ", t.thm_time" + , ", t.thm_page" + , ", t.thm_bin_db_id" + , ", t.thm_size" + , ", 0" + , ", f.fil_id" + , ", t.thm_id" + , ", 0" + , "FROM fsdb_db.fsdb_fil f" + , " JOIN fsdb_db.{0} t ON f.fil_id = t.thm_owner_id" + , " JOIN fsdb_db.fsdb_dir d ON f.fil_owner_id = d.dir_id" + , ";" + ) + , Insert_fsdb_thm_v0 = String_.Concat_lines_nl + ( "INSERT INTO fsdb_regy (fsdb_name, fsdb_is_orig, fsdb_repo, fsdb_w, fsdb_time, fsdb_page, fsdb_db_id, fsdb_size, fsdb_status, fsdb_fil_id, fsdb_thm_id, fsdb_deleted)" + , "SELECT f.fil_name" + , ", 0" + , ", CASE WHEN d.dir_name = 'commons.wikimedia.org' THEN 0 ELSE 1 END" + , ", t.thm_w" + , ", t.thm_thumbtime" + , ", -1" + , ", t.thm_bin_db_id" + , ", t.thm_size" + , ", 0" + , ", f.fil_id" + , ", t.thm_id" + , ", 0" + , "FROM fsdb_db.fsdb_fil f" + , " JOIN fsdb_db.{0} t ON f.fil_id = t.thm_owner_id" + , " JOIN fsdb_db.fsdb_dir d ON f.fil_owner_id = d.dir_id" + , ";" + ) + , Update_regy_nil = "UPDATE xfer_regy SET xfer_status = 0;" + , Update_regy_fil = String_.Concat_lines_nl + ( "REPLACE INTO xfer_regy " + , "( lnki_id, lnki_tier_id, lnki_page_id, orig_page_id, orig_repo, lnki_ttl, orig_redirect_src, lnki_ext, orig_media_type" + , ", file_is_orig, orig_w, orig_h, file_w, file_h, lnki_time, lnki_page, lnki_count" + , ", xfer_status" + , ")" + , "SELECT " + , " lnki_id, lnki_tier_id, lnki_page_id, orig_page_id, orig_repo, lnki_ttl, orig_redirect_src, lnki_ext, orig_media_type" + , ", file_is_orig, orig_w, orig_h, file_w, file_h, lnki_time, lnki_page, lnki_count" + , ", CASE WHEN f.fsdb_name IS NOT NULL THEN 1 ELSE 0 END" + , "FROM xfer_regy x" + , " LEFT JOIN fsdb_regy f ON x.lnki_ttl = f.fsdb_name" + , "WHERE x.file_is_orig = 1 AND f.fsdb_is_orig = 1" + , "AND x.orig_repo = f.fsdb_repo" + , ";" + ) + , Update_regy_thm = String_.Concat_lines_nl + ( "REPLACE INTO xfer_regy " + , "( lnki_id, lnki_tier_id, lnki_page_id, orig_page_id, orig_repo, lnki_ttl, orig_redirect_src, lnki_ext, orig_media_type" + , ", file_is_orig, orig_w, orig_h, file_w, file_h, lnki_time, lnki_page, lnki_count" + , ", xfer_status" + , ")" + , "SELECT " + , " lnki_id, lnki_tier_id, lnki_page_id, orig_page_id, orig_repo, lnki_ttl, orig_redirect_src, lnki_ext, orig_media_type" + , ", file_is_orig, orig_w, orig_h, file_w, file_h, lnki_time, lnki_page, lnki_count" + , ", CASE WHEN f.fsdb_name IS NOT NULL THEN 1 ELSE 0 END" + , "FROM xfer_regy x" + , " LEFT JOIN fsdb_regy f ON x.lnki_ttl = f.fsdb_name AND x.file_w = f.fsdb_w" + , " AND x.lnki_time = f.fsdb_time AND x.lnki_page = f.fsdb_page" + , "WHERE x.file_is_orig = 0 AND f.fsdb_is_orig = 0" + , "AND x.orig_repo = f.fsdb_repo" + , ";" + ); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_image_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_image_tbl.java index a27517de8..b2d71cbeb 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_image_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_image_tbl.java @@ -13,3 +13,44 @@ 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.addons.bldrs.files.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.dbs.engines.sqlite.*; +public class Xob_image_tbl { + public Xob_image_tbl Create_table(Db_conn p) {Sqlite_engine_.Tbl_create_and_delete(p, Tbl_name, Tbl_sql); return this;} + public Xob_image_tbl Create_index(Db_conn p) {Sqlite_engine_.Idx_create(p, Idx_img_name); return this;} + public Db_stmt Insert_stmt(Db_conn p) {return Db_stmt_.new_insert_(p, Tbl_name, Fld_img_name, Fld_img_media_type, Fld_img_minor_mime, Fld_img_size, Fld_img_width, Fld_img_height, Fld_img_bits, Fld_img_ext_id, Fld_img_timestamp);} + public void Insert(Db_stmt stmt, byte[] ttl, byte[] media_type, byte[] minor_mime, int size, int w, int h, int bits, int ext_id, byte[] img_timestamp) { + stmt.Clear() + .Val_bry_as_str(ttl) + .Val_bry_as_str(media_type) + .Val_bry_as_str(minor_mime) + .Val_int(size) + .Val_int(w) + .Val_int(h) + .Val_int(bits) + .Val_int(ext_id) + .Val_bry_as_str(img_timestamp) + .Exec_insert(); + } + public static final String Tbl_name = "image" + , Fld_img_name = "img_name", Fld_img_media_type = "img_media_type", Fld_img_minor_mime = "img_minor_mime" + , Fld_img_size = "img_size", Fld_img_width = "img_width", Fld_img_height = "img_height", Fld_img_bits = "img_bits", Fld_img_ext_id = "img_ext_id" + , Fld_img_timestamp = "img_timestamp" + ; + private static final String Tbl_sql = String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS image" + , "( img_name varchar(255) NOT NULL -- varbinary(255)" + , ", img_media_type varchar(64) NOT NULL -- enum('UNKNOWN','BITMAP','DRAWING','AUDIO','VIDEO','MULTIMEDIA','OFFICE','TEXT','EXECUTABLE','ARCHIVE')" + , ", img_minor_mime varchar(32) NOT NULL -- DEFAULT 'unknown'" + , ", img_size integer NOT NULL -- int(8) unsigned" + , ", img_width integer NOT NULL -- int(5)" + , ", img_height integer NOT NULL -- int(5)" + , ", img_bits smallint NOT NULL -- int(3)" + , ", img_ext_id int NOT NULL -- xowa" + , ", img_timestamp varchar(14) NOT NULL -- 20140101155749" + , ");" + ); + private static final Db_idx_itm + Idx_img_name = Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS image__img_name ON image (img_name, img_timestamp);") + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_lnki_regy_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_lnki_regy_tbl.java index a27517de8..d110b8575 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_lnki_regy_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_lnki_regy_tbl.java @@ -13,3 +13,101 @@ 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.addons.bldrs.files.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.dbs.engines.sqlite.*; +public class Xob_lnki_regy_tbl { + public static void Create_table(Db_conn p) {Sqlite_engine_.Tbl_create_and_delete(p, Tbl_name, Tbl_sql);} + public static void Create_data(Gfo_usr_dlg usr_dlg, Db_conn p, boolean wiki_ns_for_file_is_case_match_all) { + p.Exec_sql(Sql_create_data); + Sqlite_engine_.Idx_create(usr_dlg, p, "lnki_regy", Idx_ttl); + if (wiki_ns_for_file_is_case_match_all) + Sqlite_engine_.Idx_create(usr_dlg, p, "lnki_regy_commons", Idx_ttl_commons); + } + public static final String Tbl_name = "lnki_regy" + , Fld_lnki_id = "lnki_id", Fld_lnki_tier_id = "lnki_tier_id", Fld_lnki_page_id = "lnki_page_id", Fld_lnki_page_ns = "lnki_page_ns" + , Fld_lnki_ttl = "lnki_ttl", Fld_lnki_commons_ttl = "lnki_commons_ttl" + , Fld_lnki_ext = "lnki_ext", Fld_lnki_type = "lnki_type", Fld_lnki_src_tid = "lnki_src_tid" + , Fld_lnki_w = "lnki_w", Fld_lnki_h = "lnki_h", Fld_lnki_upright = "lnki_upright", Fld_lnki_time = "lnki_time", Fld_lnki_page = "lnki_page" + , Fld_lnki_count = "lnki_count" + ; + private static final String Tbl_sql = String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS lnki_regy" + , "( lnki_id integer NOT NULL PRIMARY KEY" + , ", lnki_tier_id integer NOT NULL" + , ", lnki_page_id integer NOT NULL" + , ", lnki_ttl varchar(255) NOT NULL" + , ", lnki_commons_ttl varchar(255) NULL" + , ", lnki_commons_flag integer NULL" + , ", lnki_ext integer NOT NULL" + , ", lnki_type integer NOT NULL" + , ", lnki_src_tid integer NOT NULL" + , ", lnki_w integer NOT NULL" + , ", lnki_h integer NOT NULL" + , ", lnki_upright double NOT NULL" + , ", lnki_time double NOT NULL" + , ", lnki_page integer NOT NULL" + , ", lnki_count integer NOT NULL" + , ");" + ); + public static final String Sql_create_data = String_.Concat_lines_nl + ( "INSERT INTO lnki_regy (lnki_id, lnki_tier_id, lnki_page_id, lnki_ttl, lnki_commons_ttl, lnki_ext, lnki_type, lnki_src_tid, lnki_w, lnki_h, lnki_upright, lnki_time, lnki_page, lnki_count)" + , "SELECT Min(lnki_id)" + , ", Min(lnki_tier_id)" + , ", Min(lnki_page_id)" + , ", lnki_ttl" + , ", lnki_commons_ttl" + , ", lnki_ext" + , ", lnki_type" + , ", lnki_src_tid" + , ", lnki_w" + , ", lnki_h" + , ", lnki_upright" + , ", lnki_time" + , ", lnki_page" + , ", Count(lnki_ttl)" + , "FROM lnki_temp" + , "GROUP BY" + , " lnki_ttl" + , ", lnki_commons_ttl" + , ", lnki_ext" + , ", lnki_type" + , ", lnki_src_tid" + , ", lnki_w" + , ", lnki_h" + , ", lnki_upright" + , ", lnki_time" + , ", lnki_page" + , ";" + ) + , Sql_cs_mark_changed = String_.Concat_lines_nl + ( "REPLACE INTO lnki_regy" + , "SELECT l.lnki_id" + , ", l.lnki_tier_id" + , ", l.lnki_page_id" + , ", l.lnki_ttl" + , ", l.lnki_commons_ttl" + , ", CASE WHEN o.lnki_ttl IS NULL THEN NULL ELSE 1 END" + , ", l.lnki_ext" + , ", l.lnki_type" + , ", l.lnki_src_tid" + , ", l.lnki_w" + , ", l.lnki_h" + , ", l.lnki_upright" + , ", l.lnki_time" + , ", l.lnki_page" + , ", l.lnki_count" + , "FROM lnki_regy l" + , " LEFT JOIN orig_regy o ON o.lnki_ttl = l.lnki_commons_ttl AND o.orig_commons_flag = 2" + , ";" + ) + , Sql_cs_update_ttls = String_.Concat_lines_nl + ( "UPDATE lnki_regy" + , "SET lnki_ttl = lnki_commons_ttl" + , "WHERE lnki_commons_flag = 1" + , ";" + ); + private static final Db_idx_itm + Idx_ttl = Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS lnki_regy__ttl ON lnki_regy (lnki_ttl, lnki_ext, lnki_id, lnki_page_id);") + , Idx_ttl_commons = Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS lnki_regy__ttl ON lnki_regy (lnki_commons_ttl, lnki_ext, lnki_id, lnki_page_id);") + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_lnki_temp_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_lnki_temp_tbl.java index a27517de8..dcdd5b33c 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_lnki_temp_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_lnki_temp_tbl.java @@ -13,3 +13,51 @@ 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.addons.bldrs.files.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.xowa.files.*; +public class Xob_lnki_temp_tbl implements Db_tbl { + private static final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private static final String tbl_name = "lnki_temp"; + public static final String + Fld_lnki_id = flds.Add_int_pkey_autonum("lnki_id"); // NOTE: insertion order index; public b/c not used and want to bypass warning + private static final String + Fld_lnki_tier_id = flds.Add_int("lnki_tier_id") + , Fld_lnki_page_id = flds.Add_int("lnki_page_id") + , Fld_lnki_ttl = flds.Add_str("lnki_ttl", 255) + , Fld_lnki_commons_ttl = flds.Add_str_null("lnki_commons_ttl", 255) + , Fld_lnki_ext = flds.Add_int("lnki_ext") + , Fld_lnki_type = flds.Add_int("lnki_type") + , Fld_lnki_src_tid = flds.Add_int("lnki_src_tid") + , Fld_lnki_w = flds.Add_int("lnki_w") + , Fld_lnki_h = flds.Add_int("lnki_h") + , Fld_lnki_upright = flds.Add_double("lnki_upright") + , Fld_lnki_time = flds.Add_double("lnki_time") // NOTE: thumbtime is float; using double b/c upright is double and would like to keep datatypes same; https://bugzilla.wikimedia.org/show_bug.cgi?id=39014 + , Fld_lnki_page = flds.Add_int("lnki_page") + ; + private Db_stmt stmt_insert; + public Xob_lnki_temp_tbl(Db_conn conn) {this.conn = conn;} + public Db_conn Conn() {return conn;} private final Db_conn conn; + public String Tbl_name() {return tbl_name;} + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Insert_bgn() {conn.Txn_bgn("bldr__lnki_temp"); stmt_insert = conn.Stmt_insert(tbl_name, flds);} + public void Insert_stmt_make() {stmt_insert = conn.Stmt_insert(tbl_name, flds);} + public void Insert_commit() {conn.Txn_sav();} + public void Insert_end() {conn.Txn_end(); stmt_insert = Db_stmt_.Rls(stmt_insert);} + public void Insert_cmd_by_batch(int tier_id, int page_id, byte[] ttl, byte[] ttl_commons, byte ext_id, byte img_type, byte lnki_src_tid, int w, int h, double upright, double time, int page) { + stmt_insert.Clear() + .Val_int (Fld_lnki_tier_id , tier_id) + .Val_int (Fld_lnki_page_id , page_id) + .Val_bry_as_str (Fld_lnki_ttl , ttl) + .Val_bry_as_str (Fld_lnki_commons_ttl , ttl_commons) + .Val_byte (Fld_lnki_ext , ext_id) + .Val_byte (Fld_lnki_type , img_type) + .Val_int (Fld_lnki_src_tid , lnki_src_tid) + .Val_int (Fld_lnki_w , w) + .Val_int (Fld_lnki_h , h) + .Val_double (Fld_lnki_upright , upright) + .Val_double (Fld_lnki_time , Xof_lnki_time.Db_save_double(time)) + .Val_int (Fld_lnki_page , page) + .Exec_insert(); + } + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_orig_regy_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_orig_regy_tbl.java index a27517de8..80fd249ef 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_orig_regy_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_orig_regy_tbl.java @@ -13,3 +13,208 @@ 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.addons.bldrs.files.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.xowa.files.repos.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.domains.*; +import gplx.dbs.engines.sqlite.*; +import gplx.xowa.bldrs.*; +public class Xob_orig_regy_tbl { + public static void Create_table(Db_conn p) {Sqlite_engine_.Tbl_create_and_delete(p, Tbl_name, Tbl_sql);} + public static void Create_data(Gfo_usr_dlg usr_dlg, Db_conn p, Xob_db_file file_registry_db, boolean repo_0_is_remote, Xowe_wiki repo_0_wiki, Xowe_wiki repo_1_wiki, boolean wiki_ns_for_file_is_case_match_all) { + usr_dlg.Prog_many("", "", "inserting lnki_regy"); + p.Exec_sql(Sql_create_data); + Sqlite_engine_.Idx_create(usr_dlg, p, "orig_regy", Idx_ttl_local); + Sqlite_engine_.Db_attach(p, "page_db", file_registry_db.Url().Raw()); + Io_url repo_0_dir = repo_0_wiki.Fsys_mgr().Root_dir(), repo_1_dir = repo_1_wiki.Fsys_mgr().Root_dir(); + byte repo_0_tid = Xof_repo_tid_.Tid__local, repo_1_tid = Xof_repo_tid_.Tid__remote; + boolean local_is_remote = Bry_.Eq(repo_0_wiki.Domain_bry(), repo_1_wiki.Domain_bry()); + Xowe_wiki local_wiki = repo_0_wiki; + if ( repo_0_is_remote // .gfs manually marked specifes repo_0 as remote + || ( Bry_.Eq(repo_0_wiki.Domain_bry(), Xow_domain_itm_.Bry__commons) // repo_0 = commons; force repo_0 to be remote; else all orig_repo will be 1; DATE:2014-02-01 + && local_is_remote // repo_0 = repo_1 + ) + ) { + repo_0_tid = Xof_repo_tid_.Tid__remote; + repo_1_tid = Xof_repo_tid_.Tid__local; + local_wiki = repo_1_wiki; + } + Create_data_for_repo(usr_dlg, p, local_wiki, Byte_.By_int(repo_0_tid), repo_0_dir.GenSubFil(Xob_db_file.Name__wiki_image)); + if (!local_is_remote) { // only run for repo_1 if local != remote; only affects commons + Create_data_for_repo(usr_dlg, p, local_wiki, Byte_.By_int(repo_1_tid), repo_1_dir.GenSubFil(Xob_db_file.Name__wiki_image)); + if (wiki_ns_for_file_is_case_match_all) { + Io_url repo_remote_dir = repo_0_is_remote ? repo_0_dir : repo_1_dir; + Create_data_for_cs(usr_dlg, p, local_wiki, repo_remote_dir); + } + } + Sqlite_engine_.Db_detach(p, "page_db"); + Sqlite_engine_.Idx_create(usr_dlg, p, "orig_regy", Idx_xfer_temp); + } + private static void Create_data_for_repo(Gfo_usr_dlg usr_dlg, Db_conn conn, Xowe_wiki local_wiki, byte repo_tid, Io_url join) { + usr_dlg.Note_many("", "", "inserting page for xowa.wiki.image: ~{0}", join.OwnerDir().NameOnly()); + boolean wiki_has_cs_file = repo_tid == Xof_repo_tid_.Tid__remote && local_wiki.Ns_mgr().Ns_file().Case_match() == Xow_ns_case_.Tid__all; + String lnki_ttl_fld = wiki_has_cs_file ? "Coalesce(o.lnki_commons_ttl, o.lnki_ttl)" : "o.lnki_ttl"; // NOTE: use lnki_commons_ttl if [[File]] is cs PAGE:en.d:water EX:[[image:wikiquote-logo.png|50px|none|alt=]]; DATE:2014-09-05 + if (wiki_has_cs_file) + Sqlite_engine_.Idx_create(usr_dlg, conn, "orig_regy", Idx_ttl_remote); + new Db_attach_mgr(conn, new Db_attach_itm("image_db", join)) + .Exec_sql_w_msg("orig_regy:updating page" , Sql_update_repo_page , repo_tid, lnki_ttl_fld) + .Exec_sql_w_msg("orig_regy:updating redirect" , Sql_update_repo_redirect , repo_tid, lnki_ttl_fld); + } + private static void Create_data_for_cs(Gfo_usr_dlg usr_dlg, Db_conn p, Xowe_wiki local_wiki, Io_url repo_remote_dir) { + p.Exec_sql(Xob_orig_regy_tbl.Sql_cs_mark_dupes); // orig_regy: find dupes; see note in SQL + p.Exec_sql(Xob_orig_regy_tbl.Sql_cs_update_ttls); // orig_regy: update lnki_ttl with lnki_commons_ttl + Create_data_for_repo(usr_dlg, p, local_wiki, Xof_repo_tid_.Tid__remote, repo_remote_dir.GenSubFil(Xob_db_file.Name__wiki_image)); + p.Exec_sql(Xob_lnki_regy_tbl.Sql_cs_mark_changed); // lnki_regy: update lnki_commons_flag + p.Exec_sql(Xob_lnki_regy_tbl.Sql_cs_update_ttls); // lnki_regy: update cs + } + public static final String Tbl_name = "orig_regy" + , Fld_lnki_id = "lnki_id", Fld_lnki_ttl = "lnki_ttl", Fld_lnki_ext = "lnki_ext", Fld_lnki_count = "lnki_count" + , Fld_orig_repo = "orig_repo", Fld_orig_page_id = "orig_page_id" + , Fld_orig_redirect_id = "orig_redirect_id", Fld_orig_redirect_ttl = "orig_redirect_ttl", Fld_orig_file_id = "orig_file_id", Fld_orig_file_ttl = "orig_file_ttl" + , Fld_orig_size = "orig_size", Fld_orig_w = "orig_w", Fld_orig_h = "orig_h", Fld_orig_bits = "orig_bits" + , Fld_orig_media_type = "orig_media_type", Fld_orig_minor_mime = "orig_minor_mime", Fld_orig_file_ext = "orig_file_ext", Fld_orig_timestamp = "orig_timestamp" + ; + private static final Db_idx_itm + Idx_ttl_local = Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS orig_regy__ttl_local ON orig_regy (lnki_ttl);") + , Idx_ttl_remote = Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS orig_regy__ttl_remote ON orig_regy (lnki_commons_ttl, lnki_ttl);") + , Idx_xfer_temp = Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS orig_regy__xfer_temp ON orig_regy (lnki_ttl, orig_file_ttl, orig_repo, orig_timestamp);") + ; + private static final String + Tbl_sql = String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS orig_regy" + , "( lnki_id integer NOT NULL PRIMARY KEY" // NOTE: must be PRIMARY KEY, else later REPLACE INTO will create dupe rows + , ", lnki_ttl varchar(256) NOT NULL" + , ", lnki_commons_ttl varchar(256) NULL" + , ", orig_commons_flag integer NULL" + , ", lnki_ext integer NOT NULL" + , ", lnki_count integer NOT NULL" + , ", orig_repo integer NULL" + , ", orig_page_id integer NULL" + , ", orig_redirect_id integer NULL" + , ", orig_redirect_ttl varchar(256) NULL" + , ", orig_file_id integer NULL" + , ", orig_file_ttl varchar(256) NULL" + , ", orig_file_ext integer NULL" + , ", orig_size integer NULL" + , ", orig_w integer NULL" + , ", orig_h integer NULL" + , ", orig_bits smallint NULL" + , ", orig_media_type varchar(64) NULL" + , ", orig_minor_mime varchar(32) NULL" + , ", orig_timestamp varchar(14) NULL" + , ");" + ) + , Sql_create_data = String_.Concat_lines_nl + ( "INSERT INTO orig_regy (lnki_id, lnki_ttl, lnki_commons_ttl, orig_commons_flag, lnki_ext, lnki_count, orig_timestamp)" + , "SELECT Min(lnki_id)" + , ", lnki_ttl" + , ", lnki_commons_ttl" + , ", NULL" + , ", lnki_ext" + , ", Sum(lnki_count)" + , ", ''" + , "FROM lnki_regy" + , "GROUP BY lnki_ttl" + , ", lnki_ext" + , "ORDER BY 1" // must order by lnki_id since it is PRIMARY KEY, else sqlite will spend hours shuffling rows in table + , ";" + ) + , Sql_update_repo_page = String_.Concat_lines_nl + ( "REPLACE INTO orig_regy" + , "SELECT o.lnki_id" + , ", o.lnki_ttl" + , ", o.lnki_commons_ttl" + , ", o.orig_commons_flag" + , ", o.lnki_ext" + , ", o.lnki_count" + , ", {0}" + , ", m.src_id" + , ", NULL" + , ", NULL" + , ", m.src_id" + , ", m.src_ttl" + , ", i.img_ext_id" + , ", i.img_size" + , ", i.img_width" + , ", i.img_height" + , ", i.img_bits" + , ", i.img_media_type" + , ", i.img_minor_mime" + , ", i.img_timestamp" + , "FROM orig_regy o" + , " JOIN image i ON {1} = i.img_name" + , " JOIN page_db.page_regy m ON m.repo_id = {0} AND m.itm_tid = 0 AND {1} = m.src_ttl" + , "WHERE o.orig_file_ttl IS NULL" // NOTE: only insert if file doesn't exist; changed from timestamp b/c old images may exist in both wikis; EX:ar.n:File:Facebook.png; DATE:2014-08-20 + // , "WHERE i.img_timestamp > o.orig_timestamp" // NOTE: this handles an image in local and remote by taking later version; DATE:2014-07-22 + , "ORDER BY 1" // must order by lnki_id since it is PRIMARY KEY, else sqlite will spend hours shuffling rows in table + , ";" + ) + , Sql_update_repo_redirect = String_.Concat_lines_nl + ( "REPLACE INTO orig_regy" + , "SELECT o.lnki_id" + , ", o.lnki_ttl" + , ", o.lnki_commons_ttl" + , ", o.orig_commons_flag" + , ", o.lnki_ext" + , ", o.lnki_count" + , ", {0}" + , ", m.src_id" + , ", m.trg_id" + , ", m.trg_ttl" + , ", m.trg_id" + , ", m.trg_ttl" + , ", i.img_ext_id" + , ", i.img_size" + , ", i.img_width" + , ", i.img_height" + , ", i.img_bits" + , ", i.img_media_type" + , ", i.img_minor_mime" + , ", i.img_timestamp" + , "FROM orig_regy o" + , " JOIN page_db.page_regy m ON m.repo_id = {0} AND m.itm_tid = 1 AND {1} = m.src_ttl" + , " JOIN image i ON m.trg_ttl = i.img_name" + , "WHERE o.orig_file_ttl IS NULL" // NOTE: only insert if file doesn't exist; changed from timestamp b/c old images may exist in both wikis; EX:ar.n:File:Facebook.png; DATE:2014-08-20 + // , "WHERE i.img_timestamp > o.orig_timestamp" // NOTE: this handles an image in local and remote by taking later version; DATE:2014-07-22 + , "ORDER BY 1" // must order by lnki_id since it is PRIMARY KEY, else sqlite will spend hours shuffling rows in table + , ";" + ) + , Sql_cs_mark_dupes = String_.Concat_lines_nl + ( "REPLACE INTO orig_regy" + , "SELECT o.lnki_id" + , ", o.lnki_ttl" + , ", o.lnki_commons_ttl" + , ", 1" + , ", o.lnki_ext" + , ", o.lnki_count" + , ", o.orig_repo" + , ", o.orig_page_id" + , ", o.orig_redirect_id" + , ", o.orig_redirect_ttl" + , ", o.orig_file_id" + , ", o.orig_file_ttl" + , ", o.orig_file_ext" + , ", o.orig_size" + , ", o.orig_w" + , ", o.orig_h" + , ", o.orig_bits" + , ", o.orig_media_type" + , ", o.orig_minor_mime" + , ", o.orig_timestamp" + , "FROM orig_regy o" + , " JOIN orig_regy o2 ON o.lnki_commons_ttl = o2.lnki_ttl" // EX: 2 rows in table (1) A.jpg; and (2) "a.jpg,A.jpg"; do not insert row (2) b/c row (1) exists; + , "WHERE o.orig_file_ttl IS NULL" // NOTE: don't use timestamp logic here + , "ORDER BY 1" // must order by lnki_id since it is PRIMARY KEY, else sqlite will spend hours shuffling rows in table + , ";" + ) + , Sql_cs_update_ttls = String_.Concat_lines_nl + ( "UPDATE orig_regy" + , "SET lnki_ttl = lnki_commons_ttl" + , ", orig_commons_flag = 2" + , "WHERE orig_file_ttl IS NULL" // orig not found + , "AND lnki_commons_ttl IS NOT NULL" // orig commons ttl exists + , "AND orig_commons_flag IS NULL" // orig is not dupe + , ";" + ) + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_page_dump_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_page_dump_tbl.java index a27517de8..661d9b60e 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_page_dump_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_page_dump_tbl.java @@ -13,3 +13,33 @@ 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.addons.bldrs.files.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; +public class Xob_page_dump_tbl { + public final static String Tbl_name = "page_dump"; + private final String fld_id, fld_title, fld_namespace, fld_is_redirect; + private final Db_conn conn; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + public Xob_page_dump_tbl(Db_conn conn) { + this.conn = conn; + this.fld_id = flds.Add_int_pkey("page_id"); + this.fld_title = flds.Add_str("page_title", 255); + this.fld_namespace = flds.Add_int("page_namespace"); + this.fld_is_redirect = flds.Add_int("page_is_redirect"); + } + public void Create_data(Io_url page_db_url, int text_db_id) { + conn.Meta_tbl_create(Dbmeta_tbl_itm.New(Tbl_name, flds)); + conn.Stmt_delete(Tbl_name).Exec_delete(); // always clear tables again; allows commands to be rerun; DATE:2015-08-04 + new Db_attach_mgr(conn, new Db_attach_itm("page_db", page_db_url)) + .Exec_sql_w_msg("text_db_prep.clone_page", Sql_insert_data, text_db_id); + conn.Meta_idx_create(Dbmeta_idx_itm.new_unique_by_tbl(Tbl_name, "main", fld_id, fld_namespace, fld_is_redirect, fld_title)); + } + private static final String Sql_insert_data = String_.Concat_lines_nl + ( "INSERT INTO page_dump (page_id, page_title, page_namespace, page_is_redirect)" + , "SELECT p.page_id" + , ", p.page_title" + , ", p.page_namespace" + , ", p.page_is_redirect" + , "FROM page p" + , "WHERE p.page_text_db_id = {0};" + ); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_page_regy_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_page_regy_tbl.java index a27517de8..2334d9264 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_page_regy_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_page_regy_tbl.java @@ -13,3 +13,73 @@ 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.addons.bldrs.files.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.*; import gplx.xowa.files.repos.*; import gplx.dbs.engines.sqlite.*; +import gplx.xowa.bldrs.*; +public class Xob_page_regy_tbl { + public static void Reset_table(Db_conn p) {Sqlite_engine_.Tbl_create_and_delete(p, Tbl_name, Tbl_sql);} + public static void Create_data(Gfo_usr_dlg usr_dlg, Db_conn p, byte repo_tid, Xowe_wiki wiki) { + Xow_db_file db_core = wiki.Db_mgr_as_sql().Core_data_mgr().Db__core(); + Create_data__insert_page(usr_dlg, p, repo_tid, db_core.Url()); + Create_data__insert_redirect(usr_dlg, p, repo_tid, wiki.Fsys_mgr().Root_dir().GenSubFil(Xob_db_file.Name__wiki_redirect)); + } + public static void Delete_local(Db_conn p) { + p.Exec_sql("DELETE FROM page_regy WHERE repo_id = " + Xof_repo_tid_.Tid__local); + } + private static void Create_data__insert_page(Gfo_usr_dlg usr_dlg, Db_conn cur, byte repo_tid, Io_url join) { + usr_dlg.Note_many("", "", "inserting page: ~{0}", join.NameOnly()); + Sqlite_engine_.Db_attach(cur, "page_db", join.Raw()); + cur.Exec_sql(String_.Format(Sql_create_page, repo_tid)); + Sqlite_engine_.Db_detach(cur, "page_db"); + } + private static void Create_data__insert_redirect(Gfo_usr_dlg usr_dlg, Db_conn cur, byte repo_tid, Io_url join) { + if (!Io_mgr.Instance.ExistsFil(join)) return; // redirect_db will not exist when commons.wikimedia.org is set up on new machine + usr_dlg.Note_many("", "", "inserting redirect: ~{0}", join.OwnerDir().NameOnly()); + Sqlite_engine_.Db_attach(cur, "redirect_db", join.Raw()); + cur.Exec_sql(String_.Format(Sql_create_redirect, repo_tid)); + Sqlite_engine_.Db_detach(cur, "redirect_db"); + } + public static final String Tbl_name = "page_regy" + , Fld_uid = "uid", Fld_repo_id = "repo_id", Fld_itm_tid = "itm_tid" + , Fld_src_id = "src_id", Fld_src_ttl = "src_ttl" + , Fld_trg_id = "trg_id", Fld_trg_ttl = "trg_ttl" + ; + public static final Db_idx_itm + Idx_main = Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS page_regy__main ON page_regy (repo_id, itm_tid, src_ttl, src_id, trg_id, trg_ttl);") + ; + private static final String + Tbl_sql = String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS page_regy" + , "( uid integer NOT NULL PRIMARY KEY AUTOINCREMENT" // NOTE: must be PRIMARY KEY, else later REPLACE INTO will create dupe rows + , ", repo_id integer NOT NULL" + , ", itm_tid tinyint NOT NULL" + , ", src_id integer NOT NULL" + , ", src_ttl varchar(255) NOT NULL" + , ", trg_id integer NOT NULL" + , ", trg_ttl varchar(255) NOT NULL" + , ");" + ) + , Sql_create_page = String_.Concat_lines_nl + ( "INSERT INTO page_regy (repo_id, itm_tid, src_id, src_ttl, trg_id, trg_ttl)" + , "SELECT {0}" + , ", 0" // 0=page + , ", p.page_id" + , ", p.page_title" + , ", -1" + , ", ''" + , "FROM page_db.page p" + , ";" + ) + , Sql_create_redirect = String_.Concat_lines_nl + ( "INSERT INTO page_regy (repo_id, itm_tid, src_id, src_ttl, trg_id, trg_ttl)" + , "SELECT {0}" + , ", 1" // 1=redirect + , ", r.src_id" + , ", r.src_ttl" + , ", r.trg_id" + , ", r.trg_ttl" + , "FROM redirect_db.redirect r" + , ";" + ) + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_redirect_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_redirect_tbl.java index a27517de8..e3c8815d6 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_redirect_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_redirect_tbl.java @@ -13,3 +13,153 @@ 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.addons.bldrs.files.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.dbs.engines.sqlite.*; +import gplx.langs.htmls.encoders.*; +import gplx.xowa.bldrs.*; +public class Xob_redirect_tbl { + private Gfo_url_encoder encoder; private Db_stmt insert_stmt; + public Xob_redirect_tbl(Io_url root_dir, Gfo_url_encoder encoder) { + this.db_file = Xob_db_file.New__wiki_redirect(root_dir); + this.conn = db_file.Conn(); + this.encoder = encoder; + } + public Xob_db_file Db_file() {return db_file;} private Xob_db_file db_file; + public Db_conn Conn() {return conn;} private Db_conn conn; + public Xob_redirect_tbl Create_table() {Sqlite_engine_.Tbl_create(conn, Tbl_name, Tbl_sql); return this;} + public void Create_indexes(Gfo_usr_dlg usr_dlg) { + Sqlite_engine_.Idx_create(usr_dlg, conn, Xob_db_file.Name__wiki_redirect, Idx_trg_id, Idx_trg_ttl); + } + public void Update_trg_redirect_id(Io_url core_url, int max_redirected_depth) { + Sqlite_engine_.Db_attach(conn, "page_db", core_url.Raw()); // link database with page table + conn.Exec_sql(Sql_get_page_data); // fill in page_id, page_ns, page_is_redirect for trg_ttl; EX: Page_A has "#REDIRECT Page_B"; Page_B is in redirect tbl; find its id, ttl, redirect status + for (int i = 0; i < max_redirected_depth; i++) { // loop to find redirected redirects; note that it is bounded by depth (to guard against circular redirects) + int affected = conn.Exec_sql(Sql_get_redirect_redirects); // find redirects that are also redirects + if (affected == 0) break; // no more redirected redirects; stop + conn.Exec_sql(Sql_get_redirect_page_data); // get page data for redirects + } + Sqlite_engine_.Db_detach(conn, "page_db"); + } + public void Update_src_redirect_id(Io_url core_url, Db_conn core_provider) { +// core_provider.Exec_sql(Sql_ddl__page_redirect_id); // create page.page_redirect_id + Sqlite_engine_.Idx_create(conn, Idx_trg_src); + Sqlite_engine_.Db_attach(conn, "page_db", core_url.Raw()); // link database with page table + conn.Exec_sql(Sql_update_redirect_id); // update page_redirect_id + Sqlite_engine_.Db_detach(conn, "page_db"); + } + public void Insert(int src_id, byte[] src_bry, Xoa_ttl trg_ttl) { + byte[] redirect_ttl_bry = Xoa_ttl.Replace_spaces(trg_ttl.Page_db()); // NOTE: spaces can still exist b/c redirect is scraped from #REDIRECT which sometimes has a mix; EX: "A_b c" + redirect_ttl_bry = encoder.Decode(redirect_ttl_bry); + this.Insert(src_id, Xoa_ttl.Replace_spaces(src_bry), -1, trg_ttl.Ns().Id(), redirect_ttl_bry, trg_ttl.Anch_txt(), 1); + } + public void Insert(int src_id, byte[] src_ttl, int trg_id, int trg_ns, byte[] trg_ttl, byte[] trg_anchor, int count) { + if (insert_stmt == null) insert_stmt = Db_stmt_.new_insert_(conn, Tbl_name, Fld_src_id, Fld_src_ttl, Fld_trg_id, Fld_trg_ns, Fld_trg_ttl, Fld_trg_anchor, Fld_trg_is_redirect, Fld_redirect_count); + insert_stmt.Clear() + .Val_int(src_id) + .Val_bry_as_str(src_ttl) + .Val_int(trg_id) + .Val_int(trg_ns) + .Val_bry_as_str(trg_ttl) + .Val_bry_as_str(trg_anchor) + .Val_byte((byte)1) + .Val_int(count) + .Exec_insert(); + } + public void Rls_all() { + insert_stmt.Rls(); + conn.Rls_conn(); + } + public static final String Tbl_name = "redirect"; + private static final String + Fld_src_id = "src_id", Fld_src_ttl = "src_ttl", Fld_trg_id = "trg_id", Fld_trg_ns = "trg_ns", Fld_trg_ttl = "trg_ttl", Fld_trg_anchor = "trg_anchor" + , Fld_trg_is_redirect = "trg_is_redirect", Fld_redirect_count = "redirect_count"; + private static final String Tbl_sql = String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS redirect" + , "( src_id integer NOT NULL PRIMARY KEY" + , ", src_ttl varchar(255) NOT NULL" + , ", trg_id integer NOT NULL" + , ", trg_ns integer NOT NULL" + , ", trg_ttl varchar(255) NOT NULL" + , ", trg_anchor varchar(255) NOT NULL" + , ", trg_is_redirect tinyint NOT NULL" + , ", redirect_count integer NOT NULL" + , ");" + ); + private static final Db_idx_itm + Idx_trg_ttl = Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS redirect__trg_ttl ON redirect (trg_ttl);") + , Idx_trg_id = Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS redirect__trg_id ON redirect (trg_id);") + , Idx_trg_src = Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS redirect__trg_src ON redirect (src_id, trg_id);") + ; +// public static final String +// Sql_ddl__page_redirect_id = "ALTER TABLE page ADD COLUMN page_redirect_id integer NOT NULL DEFAULT '-1'" +// ; + private static final String + Sql_get_page_data = String_.Concat_lines_nl // get data from page table for initial redirect dump + ( "REPLACE INTO redirect " + , "SELECT t.src_id" + , ", t.src_ttl" + , ", j.page_id" + , ", t.trg_ns" + , ", t.trg_ttl" + , ", t.trg_anchor" + , ", j.page_is_redirect" + , ", t.redirect_count" + , "FROM redirect t" + , " JOIN page_db.page j " + , " ON t.trg_ns = j.page_namespace" + , " AND t.trg_ttl = j.page_title" + , " AND t.trg_is_redirect = 1 -- limit to redirects" + , ";" + ) + , Sql_get_redirect_redirects = String_.Concat_lines_nl // find redirects that are redirected + ( "REPLACE INTO redirect" + , "SELECT t.src_id" + , ", t.src_ttl" + , ", j.trg_id" + , ", -1" + , ", ''" + , ", ''" + , ", 1" + , ", t.redirect_count + 1" + , "FROM redirect t" + , " JOIN redirect j " + , " ON t.trg_id = j.src_id" + , " AND t.trg_is_redirect = 1" + , ";" + , "" + ) + , Sql_get_redirect_page_data = String_.Concat_lines_nl // get data from page table for redirected redirects + ( "REPLACE INTO redirect" + , "SELECT t.src_id" + , ", t.src_ttl" + , ", t.trg_id" + , ", j.page_namespace" + , ", j.page_title" + , ", t.trg_anchor" + , ", j.page_is_redirect" + , ", t.redirect_count" + , "FROM redirect t" + , " JOIN page_db.page j " + , " ON t.trg_id = j.page_id " + , " AND t.trg_is_redirect = 1 -- limit to redirects" + , ";" + ) + , Sql_update_redirect_id = String_.Concat_lines_nl_skip_last + ( "REPLACE INTO" + , " page_db.page (page_id, page_namespace, page_title, page_is_redirect, page_touched, page_len, page_random_int, page_text_db_id, page_html_db_id, page_redirect_id, page_score)" + , "SELECT p.page_id" + , ", p.page_namespace" + , ", p.page_title" + , ", p.page_is_redirect" + , ", p.page_touched" + , ", p.page_len" + , ", p.page_random_int" + , ", p.page_text_db_id" + , ", p.page_html_db_id" + , ", r.trg_id" + , ", p.page_score" + , "FROM redirect r" + , " JOIN page_db.page p ON r.src_id = p.page_id" + ) + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_xfer_regy_log_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_xfer_regy_log_tbl.java index a27517de8..63466fd18 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_xfer_regy_log_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_xfer_regy_log_tbl.java @@ -13,3 +13,28 @@ 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.addons.bldrs.files.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; +public class Xob_xfer_regy_log_tbl { + public static void Create_table(Db_conn p) {gplx.dbs.engines.sqlite.Sqlite_engine_.Tbl_create_and_delete(p, Tbl_name, Tbl_sql);} + public static Db_stmt Insert_stmt(Db_conn p) {return Db_stmt_.new_insert_(p, Tbl_name, Fld_lnki_id, Fld_xfer_status, Fld_xfer_bin_tid, Fld_xfer_bin_msg);} + public static void Insert(Db_stmt stmt, byte status, int id, byte wkr_tid, String wkr_msg) { + stmt.Clear() + .Val_int(id) + .Val_byte(status) + .Val_byte(wkr_tid) + .Val_str(wkr_msg) + .Exec_insert(); + } + private static final String Tbl_sql = String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS xfer_regy_log" + , "( lnki_id integer NOT NULL" + , ", xfer_status tinyint NOT NULL" // 0=todo; 1=fail; 2=pass; 3=done + , ", xfer_bin_tid tinyint NOT NULL" + , ", xfer_bin_msg varchar(255) NOT NULL" + , ");" + ); + public static final String Tbl_name = "xfer_regy_log" + , Fld_lnki_id = "lnki_id", Fld_xfer_status = "xfer_status", Fld_xfer_bin_tid = "xfer_bin_tid", Fld_xfer_bin_msg = "xfer_bin_msg" + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_xfer_regy_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_xfer_regy_tbl.java index a27517de8..0708c6a8e 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_xfer_regy_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_xfer_regy_tbl.java @@ -13,3 +13,122 @@ 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.addons.bldrs.files.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.core.stores.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.dbs.engines.sqlite.*; +import gplx.xowa.bldrs.*; +public class Xob_xfer_regy_tbl { + public static final String Tbl_name = "xfer_regy" + , Fld_lnki_id = "lnki_id", Fld_lnki_tier_id = "lnki_tier_id", Fld_lnki_page_id = "lnki_page_id", Fld_lnki_ttl = "lnki_ttl", Fld_lnki_ext = "lnki_ext" + , Fld_lnki_time = "lnki_time", Fld_lnki_page = "lnki_page", Fld_lnki_count = "lnki_count" + , Fld_orig_repo = "orig_repo", Fld_orig_page_id = "orig_page_id", Fld_orig_redirect_src = "orig_redirect_src", Fld_orig_media_type = "orig_media_type" + , Fld_orig_w = "orig_w", Fld_orig_h = "orig_h" + , Fld_file_w = "file_w", Fld_file_h = "file_h", Fld_file_is_orig = "file_is_orig" + , Fld_xfer_status = "xfer_status" + ; + public static void Create_table(Db_conn p) {Sqlite_engine_.Tbl_create_and_delete(p, Tbl_name, Tbl_sql);} + public static void Create_data(Gfo_usr_dlg usr_dlg, Db_conn p) { + p.Exec_sql(Sql_create_data_orig); + p.Exec_sql(Sql_create_data_thumb); + } + public static void Create_index(Gfo_usr_dlg usr_dlg, Db_conn p) {Sqlite_engine_.Idx_create(usr_dlg, p, Xob_db_file.Name__file_make, Idx_lnki_page_id, Idx_lnki_ttl);} + public static DataRdr Select(Db_conn conn, byte repo_id, byte[] ttl, int limit) { + Db_qry qry = Db_qry_.select_().Cols_all_() + .From_(Tbl_name) + .Where_(gplx.core.criterias.Criteria_.And_many(Db_crt_.New_mte(Fld_orig_repo, repo_id), Db_crt_.New_mt(Fld_lnki_ttl, String_.new_u8(ttl)), Db_crt_.New_eq(Fld_xfer_status, 0))) + .Order_asc_many_(Fld_xfer_status, Fld_orig_repo, Fld_lnki_ttl, Fld_file_w) + .Limit_(limit) + ; + return conn.Exec_qry_as_old_rdr(qry); + } + public static Db_stmt Select_by_page_id_stmt(Db_conn p) {return p.Stmt_sql(Sql_select_clause);} + public static DataRdr Select_by_page_id(Db_stmt stmt, int page_id, int limit) {return stmt.Val_int(page_id).Val_int(limit).Exec_select();} + private static final String + Sql_select_clause = String_.Concat_lines_nl + ( "SELECT *" + , "FROM xfer_regy" + , "WHERE xfer_status = 0" + , "AND lnki_page_id >= ?" + , "ORDER BY lnki_tier_id, lnki_page_id, lnki_id" + , "LIMIT ?" + ) + , Sql_select_total_pending = String_.Concat_lines_nl + ( "SELECT Count(*) AS CountAll" + , "FROM xfer_regy" + , "WHERE xfer_status = 0" + ) + ; + public static DataRdr Select_by_tier_page(Db_conn conn, int tier_id, int page_id, int select_interval) { + Db_qry qry = Db_qry_.select_().Cols_all_() + .From_(Tbl_name) + .Where_(gplx.core.criterias.Criteria_.And_many(Db_crt_.New_eq(Fld_xfer_status, 0), Db_crt_.New_eq(Fld_lnki_tier_id, tier_id), Db_crt_.New_mte(Fld_lnki_page_id, page_id))) + .Order_asc_many_(Fld_lnki_tier_id, Fld_lnki_page_id, Fld_lnki_id) + .Limit_(select_interval) + ; + return conn.Exec_qry_as_old_rdr(qry); + } + public static int Select_total_pending(Db_conn p) { + DataRdr rdr = p.Exec_sql_as_old_rdr(Sql_select_total_pending); + int rv = 0; + if (rdr.MoveNextPeer()) + rv = rdr.ReadInt("CountAll"); + rdr.Rls(); + return rv; + } + private static final String Tbl_sql = String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS xfer_regy" + , "( lnki_id integer NOT NULL PRIMARY KEY" + , ", lnki_tier_id integer NOT NULL" + , ", lnki_page_id integer NOT NULL" + , ", lnki_ttl varchar(255) NOT NULL" + , ", lnki_ext integer NOT NULL" + , ", lnki_time double NOT NULL" + , ", lnki_page integer NOT NULL" + , ", lnki_count integer NOT NULL" + , ", orig_repo tinyint NOT NULL" + , ", orig_page_id integer NOT NULL" + , ", orig_redirect_src varchar(255) NOT NULL" + , ", orig_media_type varchar(64) NOT NULL" + , ", orig_w integer NOT NULL" + , ", orig_h integer NOT NULL" + , ", file_is_orig tinyint NOT NULL" + , ", file_w integer NOT NULL" + , ", file_h integer NOT NULL" + , ", xfer_status integer NOT NULL" + , ");" + ); + private static final String Sql_create_data_orig = String_.Concat_lines_nl + ( "INSERT INTO xfer_regy " + , "( lnki_id, lnki_tier_id, lnki_page_id, orig_page_id, orig_repo, lnki_ttl, orig_redirect_src, lnki_ext, orig_media_type" + , ", file_is_orig, orig_w, orig_h, file_w, file_h, lnki_time, lnki_page, lnki_count" + , ", xfer_status" + , ")" + , "SELECT " + , " Min(lnki_id), Min(lnki_tier_id), Min(lnki_page_id), Min(orig_page_id), orig_repo, lnki_ttl, Max(orig_redirect_src), lnki_ext, orig_media_type" // NOTE: Max(orig_redirect_src) not Min (else would get '') + , ", file_is_orig, orig_w, orig_h, -1, -1, lnki_time, lnki_page, Sum(lnki_count)" + , ", 0" + , "FROM xfer_temp x" + , "WHERE file_is_orig = 1" + , "GROUP BY orig_repo, lnki_ttl, lnki_ext, orig_media_type, file_is_orig, orig_w, orig_h, lnki_time, lnki_page" + ); + private static final String Sql_create_data_thumb = String_.Concat_lines_nl + ( "INSERT INTO xfer_regy " + , "( lnki_id, lnki_tier_id, lnki_page_id, orig_page_id, orig_repo, lnki_ttl, orig_redirect_src, lnki_ext, orig_media_type" + , ", file_is_orig, orig_w, orig_h, file_w, file_h, lnki_time, lnki_page, lnki_count" + , ", xfer_status" + , ")" + , "SELECT " + , " Min(lnki_id), Min(lnki_tier_id), Min(lnki_page_id), Min(orig_page_id), orig_repo, lnki_ttl, Max(orig_redirect_src), lnki_ext, orig_media_type" + , ", file_is_orig, orig_w, orig_h, file_w, file_h, lnki_time, lnki_page, Sum(lnki_count)" + , ", 0" + , "FROM xfer_temp x" + , "WHERE file_is_orig = 0" + , "GROUP BY orig_repo, lnki_ttl, lnki_ext, orig_media_type, file_is_orig, orig_w, orig_h, file_w, file_h, lnki_time, lnki_page" + ); + private static final Db_idx_itm +// Idx_select = Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS xfer_regy__select ON xfer_regy (xfer_status, orig_repo, lnki_ttl, file_w);") + Idx_lnki_page_id = Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS xfer_regy__lnki_page_id ON xfer_regy (xfer_status, lnki_tier_id, lnki_page_id, lnki_id, orig_repo, file_is_orig, lnki_ttl, lnki_ext, lnki_time, lnki_page, file_w, file_h);") + , Idx_lnki_ttl = Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS xfer_regy__lnki_ttl ON xfer_regy (lnki_ttl);") // needed for troubleshooting + ; + public static byte Status_todo = 0, Status_pass = 1, Status_fail = 2, Status_ignore_processed = 3; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_xfer_temp_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_xfer_temp_tbl.java index a27517de8..07a294f26 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_xfer_temp_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/dbs/Xob_xfer_temp_tbl.java @@ -13,3 +13,74 @@ 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.addons.bldrs.files.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; import gplx.dbs.engines.sqlite.*; import gplx.xowa.wikis.dbs.*; import gplx.xowa.files.*; +public class Xob_xfer_temp_tbl { + public static void Create_table(Db_conn p) {Sqlite_engine_.Tbl_create_and_delete(p, Tbl_name, Tbl_sql);} + public static Db_stmt Insert_stmt(Db_conn p) {return Db_stmt_.new_insert_(p, Tbl_name, Fld_lnki_id, Fld_lnki_tier_id, Fld_lnki_page_id, Fld_orig_repo, Fld_orig_page_id, Fld_lnki_ttl, Fld_orig_redirect_src, Fld_lnki_ext, Fld_lnki_type, Fld_orig_media_type, Fld_file_is_orig, Fld_orig_w, Fld_orig_h, Fld_file_w, Fld_file_h, Fld_html_w, Fld_html_h, Fld_lnki_w, Fld_lnki_h, Fld_lnki_upright, Fld_lnki_time, Fld_lnki_page, Fld_lnki_count);} + public static void Insert(Db_stmt stmt, int lnki_id, int lnki_tier_id, int lnki_page_id, byte repo_id + , int page_id, String ttl, String redirect_src, int ext_id, byte lnki_type, String orig_media_type + , boolean file_is_orig, int orig_w, int orig_h, int file_w, int file_h, int html_w, int html_h + , int lnki_w, int lnki_h, double lnki_upright, double thumbtime, int page, int count) { + stmt.Clear() + .Val_int(lnki_id) + .Val_int(lnki_tier_id) + .Val_int(lnki_page_id) + .Val_byte(repo_id) + .Val_int(page_id) + .Val_str(ttl) + .Val_str(redirect_src) + .Val_int(ext_id) + .Val_byte(lnki_type) + .Val_str(orig_media_type) + .Val_bool_as_byte(file_is_orig) + .Val_int(orig_w) + .Val_int(orig_h) + .Val_int(file_w) + .Val_int(file_h) + .Val_int(html_w) + .Val_int(html_h) + .Val_int(lnki_w) + .Val_int(lnki_h) + .Val_double(lnki_upright) + .Val_double(Xof_lnki_time.Db_save_double(thumbtime)) + .Val_int(page) + .Val_int(count) + .Exec_insert(); + } + public static final String Tbl_name = "xfer_temp" + , Fld_lnki_id = "lnki_id", Fld_lnki_tier_id = "lnki_tier_id", Fld_lnki_page_id = "lnki_page_id", Fld_lnki_ttl = "lnki_ttl", Fld_lnki_ext = "lnki_ext", Fld_lnki_type = "lnki_type" + , Fld_lnki_w = "lnki_w", Fld_lnki_h = "lnki_h", Fld_lnki_upright = "lnki_upright", Fld_lnki_time = "lnki_time", Fld_lnki_page = "lnki_page", Fld_lnki_count = "lnki_count" + , Fld_orig_repo = "orig_repo", Fld_orig_page_id = "orig_page_id", Fld_orig_redirect_src = "orig_redirect_src", Fld_orig_media_type = "orig_media_type" + , Fld_orig_w = "orig_w", Fld_orig_h = "orig_h" + , Fld_file_w = "file_w", Fld_file_h = "file_h", Fld_file_is_orig = "file_is_orig" + , Fld_html_w = "html_w", Fld_html_h = "html_h" + ; + private static final String Tbl_sql = String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS xfer_temp" + , "( lnki_id integer NOT NULL PRIMARY KEY" + , ", lnki_tier_id integer NOT NULL" + , ", lnki_page_id integer NOT NULL" + , ", lnki_ttl varchar(255) NOT NULL" + , ", lnki_ext integer NOT NULL" + , ", lnki_type integer NOT NULL" + , ", lnki_w integer NOT NULL" + , ", lnki_h integer NOT NULL" + , ", lnki_upright double NOT NULL" + , ", lnki_time double NOT NULL" + , ", lnki_page integer NOT NULL" + , ", lnki_count integer NOT NULL" + , ", orig_repo integer NOT NULL" + , ", orig_page_id integer NOT NULL" + , ", orig_redirect_src varchar(255) NOT NULL" + , ", orig_media_type varchar(64) NOT NULL" + , ", orig_w integer NOT NULL" + , ", orig_h integer NOT NULL" + , ", file_is_orig tinyint NOT NULL" + , ", file_w integer NOT NULL" + , ", file_h integer NOT NULL" + , ", html_w integer NOT NULL" + , ", html_h integer NOT NULL" + , ");" + ); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/Xobldr_missing_origs_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/Xobldr_missing_origs_cmd.java index a27517de8..297795010 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/Xobldr_missing_origs_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/Xobldr_missing_origs_cmd.java @@ -13,3 +13,141 @@ 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.addons.bldrs.files.missing_origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.files.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.origs.*; import gplx.xowa.apps.wms.apis.origs.*; +import gplx.xowa.addons.bldrs.files.dbs.*; +import gplx.xowa.addons.bldrs.files.missing_origs.apis.*; +public class Xobldr_missing_origs_cmd extends Xob_cmd__base { + private int fail_max = 100000; + private String recentchanges_bgn, recentchanges_end; + public Xobldr_missing_origs_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + gplx.xowa.files.downloads.Xof_download_wkr download_wkr = wiki.App().Wmf_mgr().Download_wkr(); + + // get recentchanges + Xowmf_recentchanges_api rc_api = new Xowmf_recentchanges_api(); + if (recentchanges_bgn == null || recentchanges_end == null) { + throw Err_.new_wo_type("bldr.find_missing: recentchanges_bgn or recentchanges_end not set"); + } + Ordered_hash rc_list = rc_api.Find(download_wkr, gplx.xowa.wikis.domains.Xow_domain_itm_.Str__commons, recentchanges_bgn, recentchanges_end, 500); + + // got orig_db + Db_conn conn = Xob_db_file.New__file_make(wiki.Fsys_mgr().Root_dir()).Conn(); + String sql_fmt = "UPDATE orig_regy SET orig_page_id = -1 WHERE lnki_ttl = '{0}' AND orig_page_id IS NULL"; + + // loop recentchanges + int rc_list_len = rc_list.Len(); + conn.Txn_bgn("orig_regy__recentchanges"); + for (int i = 0; i < rc_list_len; i++) { + Xowmf_recentchanges_item item = (Xowmf_recentchanges_item)rc_list.Get_at(i); + conn.Exec_sql_args(sql_fmt, item.Title()); + } + conn.Txn_end(); + + // get counts; fail if too many + int fail_count = conn.Exec_select_as_int(Db_sql_.Make_by_fmt(String_.Ary("SELECT Count(lnki_ttl) FROM orig_regy WHERE orig_page_id = -1")), Int_.Max_value); + if (fail_count > fail_max) { + throw Err_.new_wo_type("bldr.find_missing: too many missing: missing=~{0} max=~{1}", fail_count, fail_max); + } + Gfo_usr_dlg_.Instance.Note_many("", "", "bldr.find_missing: found=~{0}", fail_count); + + // select into list; ignore any which are invalid titles + Ordered_hash list = Ordered_hash_.New_bry(); + int invalid_count = 0; + String sql = "SELECT lnki_ttl FROM orig_regy WHERE orig_page_id IS NULL"; + Db_rdr rdr = conn.Stmt_sql(sql).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + // get lnki_ttl; check if valid + byte[] lnki_ttl = rdr.Read_bry("lnki_ttl"); + Xoa_ttl ttl = wiki.Ttl_parse(lnki_ttl); + if (ttl == null) { + invalid_count++; + continue; + } + + // create itm and add to list + Xowmf_imageinfo_item itm = new Xowmf_imageinfo_item(); + itm.Init_by_orig_tbl(lnki_ttl); + list.Add(itm.Lnki_ttl(), itm); + } + } finally {rdr.Rls();} + Gfo_usr_dlg_.Instance.Note_many("", "", "bldr.find_missing: invalid=~{0}", invalid_count); + + // call api for commons + Download(conn, list, Xof_repo_tid_.Tid__remote, gplx.xowa.wikis.domains.Xow_domain_itm_.Str__commons); + + // filter to unfound + Ordered_hash unfound = Ordered_hash_.New(); + int list_len = list.Len(); + for (int i = 0; i < list_len; i++) { + Xowmf_imageinfo_item item = (Xowmf_imageinfo_item)list.Get_at(i); + if (item.Orig_page_id() == -1) + unfound.Add(item.Lnki_ttl(), item); + } + + // call api for local + Download(conn, unfound, Xof_repo_tid_.Tid__local , wiki.Domain_str()); + } + private void Download(Db_conn conn, Ordered_hash list, byte repo_tid, String repo_domain) { + Xowmf_imageinfo_api wmf_api = new Xowmf_imageinfo_api(wiki.App().Wmf_mgr().Download_wkr()); + int list_len = list.Len(); + int list_bgn = 0; + // loop until no more entries + while (true) { + int list_end = list_bgn + 500; + if (list_end > list_len) list_end = list_len; + + // find items + wmf_api.Find_by_list(list, Xof_repo_tid_.Tid__remote, "commons.wikimedia.org", list_bgn); + + // loop list and update + conn.Txn_bgn("bldr.find_missing"); + Db_stmt update_stmt = conn.Stmt_update("orig_regy", String_.Ary("lnki_ttl") + , "orig_repo" + , "orig_page_id", "orig_redirect_id", "orig_redirect_ttl" + , "orig_file_id", "orig_file_ttl", "orig_file_ext" + , "orig_size", "orig_w", "orig_h", "orig_media_type", "orig_minor_mime", "orig_timestamp"); + // , "orig_bits" + for (int i = list_bgn; i < list_end; i++) { + Xowmf_imageinfo_item itm = (Xowmf_imageinfo_item)list.Get_at(i); + update_stmt + .Val_byte("orig_repo", itm.Orig_repo()) + .Val_int("orig_page_id", itm.Orig_page_id()) + .Val_int("orig_redirect_id", itm.Orig_redirect_id()) + .Val_bry_as_str("orig_redirect_ttl", itm.Orig_redirect_ttl()) + .Val_int("orig_file_id", itm.Orig_file_id()) + .Val_bry_as_str("orig_file_ttl", itm.Orig_file_ttl()) + .Val_int("orig_file_ext", itm.Orig_file_ext()) + .Val_int("orig_size", itm.Orig_size()) + .Val_int("orig_w", itm.Orig_w()) + .Val_int("orig_h", itm.Orig_h()) + .Val_bry_as_str("orig_media_type", itm.Orig_media_type()) + .Val_bry_as_str("orig_minor_mime", itm.Orig_minor_mime()) + .Val_bry_as_str("orig_timestamp", itm.Orig_timestamp()) + .Crt_bry_as_str("lnki_ttl", itm.Lnki_ttl()).Exec_update(); + } + conn.Txn_end(); + + // exit if done + if (list_end == list_len) break; + + // update bounds + list_bgn += 500; + } + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, "fail_max_")) this.fail_max = m.ReadInt("v"); + else if (ctx.Match(k, "recentchanges_bgn_")) this.recentchanges_bgn = m.ReadStr("v"); + else if (ctx.Match(k, "recentchanges_end_")) this.recentchanges_end = m.ReadStr("v"); + else return super.Invk(ctx, ikey, k, m); + return this; + } + + public static final String BLDR_CMD_KEY = "file.orig_regy.find_missing"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xobldr_missing_origs_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xobldr_missing_origs_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_imageinfo_api.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_imageinfo_api.java index a27517de8..065514566 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_imageinfo_api.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_imageinfo_api.java @@ -13,3 +13,120 @@ 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.addons.bldrs.files.missing_origs.apis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; import gplx.xowa.addons.bldrs.files.missing_origs.*; +import gplx.langs.htmls.encoders.*; +import gplx.langs.jsons.*; +import gplx.xowa.files.repos.*; +import gplx.xowa.files.downloads.*; +import gplx.xowa.apps.wms.apis.origs.*; +public class Xowmf_imageinfo_api { + private final Xof_download_wkr download_wkr; + private final Ordered_hash temp_hash = Ordered_hash_.New(); + public static final byte[] FILE_NS_PREFIX = Bry_.new_a7("File:"); + public Xowmf_imageinfo_api(Xof_download_wkr download_wkr) { + this.download_wkr = download_wkr; + } + public void Find_by_list(Ordered_hash src, byte repo_id, String api_domain, int idx) { + // fail if web access disabled + if (!gplx.core.ios.IoEngine_system.Web_access_enabled) { + throw Err_.new_wo_type("web access must be enabled for missing_origs cmd"); + } + + Json_parser parser = new Json_parser(); + Gfo_url_encoder encoder = Gfo_url_encoder_.New__http_url().Make(); + Bry_bfr bfr = Bry_bfr_.New(); + int len = src.Len(); + try { + // loop until all titles found + while (idx < len) { + // generate api: EX: https://commons.wikimedia.org/w/api.php?action=query&format=json&formatversion=2&prop=imageinfo&iiprop=timestamp|size|mediatype|mime&redirects&iilimit=500&titles=File:Different%20Faces%20Neptune.jpg|File:East.svg + // generate everything up to titles + bfr.Add_str_a7("https://"); + bfr.Add_str_a7(api_domain); + bfr.Add_str_a7("/w/api.php?action=query"); + bfr.Add_str_a7("&format=json"); // json easier to use than xml + bfr.Add_str_a7("&iilimit=1"); // limit to 1 revision history (default will return more); EX:File:Different_Faces_Neptune.jpg + bfr.Add_str_a7("&redirects"); // show redirects + bfr.Add_str_a7("&prop=imageinfo&iiprop=timestamp|size|mediatype|mime"); // list of props; NOTE: "url" / "sha1" for future; "bitdepth" always 0? + bfr.Add_str_a7("&titles="); + + // add titles; EX: File:A.png|File:B.png| + for (int i = idx; i < idx + 500; i++) { + Xowmf_imageinfo_item item = (Xowmf_imageinfo_item)src.Get_at(i); + + // skip "|" if first + if (i != idx) bfr.Add_byte_pipe(); + + // add ttl_bry + byte[] ttl_bry = item.Lnki_ttl(); + ttl_bry = Bry_.Add(FILE_NS_PREFIX, ttl_bry); // WMF API requires "File:" prefix; EX: "File:A.png" x> "A.png" + ttl_bry = Xoa_ttl.Replace_unders(ttl_bry); // convert to spaces else will get extra "normalize" node + ttl_bry = encoder.Encode(ttl_bry); // encode for good form + bfr.Add(ttl_bry); + } + + // call api + byte[] rslt = download_wkr.Download_xrg().Exec_as_bry(bfr.To_str_and_clear()); + + // deserialize + Json_doc jdoc = parser.Parse(rslt); + + // loop over pages + Json_ary pages_ary = (Json_ary)jdoc.Get_grp_many("query", "pages"); + int pages_len = pages_ary.Len(); + for (int i = 0; i < pages_len; i++) { + // get vars from page nde + Json_nde page = pages_ary.Get_at_as_nde(i); + int page_id = page.Get_as_int("page_id"); + byte[] title = page.Get_as_bry("title"); + + // get vars from imageinfo node + Json_ary info_ary = (Json_ary)page.Get_as_ary("imageinfo"); + Json_nde info_nde = (Json_nde)info_ary.Get_as_nde(0); + byte[] timestamp = info_nde.Get_as_bry("timestamp"); + int size = info_nde.Get_as_int("size"); + int width = info_nde.Get_as_int("width"); + int height = info_nde.Get_as_int("height"); + byte[] mime = info_nde.Get_as_bry("mime"); + byte[] mediatype = info_nde.Get_as_bry("mediatype"); + + // add to trg hash + try { + Xowmf_imageinfo_item trg_item = new Xowmf_imageinfo_item().Init_by_api_page(repo_id, page_id, title, size, width, height, mediatype, mime, timestamp); + temp_hash.Add(trg_item.Orig_file_ttl(), trg_item); + } catch (Exception e2) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "missing_origs:failed to deserialize api obj; domain=~{0} ttl=~{1} json=~{2} err=~{3}", api_domain, title, page.Print_as_json(), Err_.Message_gplx_log(e2)); + } + } + + // loop over redirects + Json_ary redirects_ary = (Json_ary)jdoc.Get_grp_many("query", "redirects"); + int redirects_len = pages_ary.Len(); + for (int i = 0; i < redirects_len; i++) { + // get vars from redirect nde + Json_nde redirect = redirects_ary.Get_at_as_nde(i); + byte[] from = redirect.Get_as_bry("from"); + byte[] to = redirect.Get_as_bry("to"); + + // get nde by "to" and copy redirect + Xowmf_imageinfo_item trg_item = (Xowmf_imageinfo_item)temp_hash.Get_by_or_fail(to); + trg_item.Init_by_api_redirect(from, to); + + // update temp_hash key + temp_hash.Del(to); + temp_hash.Add(from, trg_item); + } + + // loop over hash and copy back to src + int temp_hash_len = temp_hash.Len(); + for (int i = 0; i < temp_hash_len; i++) { + Xowmf_imageinfo_item trg_item = (Xowmf_imageinfo_item)temp_hash.Get_at(i); + Xowmf_imageinfo_item src_item = (Xowmf_imageinfo_item)temp_hash.Get_by(trg_item.Lnki_ttl()); + src_item.Copy_api_props(trg_item); + } + } + } catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "missing_origs:failure while calling wmf_api; domain=~{0} idx=~{1} err=~{2}", api_domain, idx, Err_.Message_gplx_log(e)); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_imageinfo_item.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_imageinfo_item.java index a27517de8..b48ffa399 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_imageinfo_item.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_imageinfo_item.java @@ -13,3 +13,104 @@ 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.addons.bldrs.files.missing_origs.apis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; import gplx.xowa.addons.bldrs.files.missing_origs.*; +import gplx.xowa.files.*; +public class Xowmf_imageinfo_item { + public byte[] Lnki_ttl() {return lnki_ttl;} private byte[] lnki_ttl; + public byte Orig_repo() {return orig_repo;} private byte orig_repo; + public int Orig_page_id() {return orig_page_id;} private int orig_page_id = -1; + public int Orig_file_id() {return orig_page_id;} + public byte[] Orig_file_ttl() {return orig_file_ttl;} private byte[] orig_file_ttl; + public byte[] Orig_timestamp() {return orig_timestamp;} private byte[] orig_timestamp; + public int Orig_size() {return orig_size;} private int orig_size; + public int Orig_w() {return orig_w;} private int orig_w; + public int Orig_h() {return orig_h;} private int orig_h; + public byte[] Orig_minor_mime() {return orig_minor_mime;} private byte[] orig_minor_mime; + public byte[] Orig_media_type() {return orig_media_type;} private byte[] orig_media_type; + public byte[] Orig_redirect_ttl() {return orig_redirect_ttl;} private byte[] orig_redirect_ttl; + public int Lnki_ext() {return lnki_ext;} private int lnki_ext; + public int Orig_file_ext() {return orig_file_ext;} private int orig_file_ext; + public int Orig_redirect_ext() {return orig_redirect_ext;} private int orig_redirect_ext; + public int Orig_redirect_id() {return orig_redirect_id;} private int orig_redirect_id; + + public Xowmf_imageinfo_item Init_by_orig_tbl(byte[] lnki_ttl) { + this.lnki_ttl = lnki_ttl; + return this; + } + public Xowmf_imageinfo_item Init_by_api_page(byte orig_repo, int orig_page_id, byte[] orig_file_ttl, int orig_size, int orig_w, int orig_h, byte[] orig_media_type, byte[] orig_minor_mime, byte[] orig_timestamp) { + this.orig_repo = orig_repo; + this.orig_page_id = orig_page_id; + this.orig_file_ttl = Normalize_ttl(orig_file_ttl); + this.orig_file_ext = Xof_ext_.new_by_ttl_(orig_file_ttl).Id(); + this.orig_size = orig_size; + this.orig_w = orig_w; + this.orig_h = orig_h; + this.orig_media_type = orig_media_type; + this.orig_minor_mime = Normalize_minor_mime(orig_minor_mime); + this.orig_timestamp = Normalize_timestamp(orig_timestamp); + return this; + } + public Xowmf_imageinfo_item Init_by_api_redirect(byte[] from, byte[] to) { + this.lnki_ttl = Normalize_ttl(from); + this.orig_redirect_ttl = Normalize_ttl(to); + // page_id is always redirect_id + this.orig_redirect_id = orig_page_id; + // orig_page_id is unknown; need to make 2nd call; + this.orig_page_id = -987; + return this; + } + public void Copy_api_props(Xowmf_imageinfo_item src) { + // page nde + this.orig_repo = src.orig_repo; + this.orig_page_id = src.orig_page_id; + this.orig_file_ttl = src.orig_file_ttl; + this.orig_file_ext = src.orig_file_ext; + this.orig_size = src.orig_size; + this.orig_w = src.orig_w; + this.orig_h = src.orig_h; + this.orig_media_type = src.orig_media_type; + this.orig_minor_mime = src.orig_minor_mime; + this.orig_timestamp = src.orig_timestamp; + + // revision nde + this.orig_redirect_ttl = src.orig_redirect_ttl; + + // set ext_ids + this.lnki_ext = Xof_ext_.new_by_ttl_(lnki_ttl).Id(); + this.orig_redirect_ext = Xof_ext_.new_by_ttl_(orig_redirect_ttl).Id(); + } + public static byte[] Normalize_ttl(byte[] v) { + // remove "File:" + if (Bry_.Has_at_bgn(v, Xowmf_imageinfo_api.FILE_NS_PREFIX)) { + v = Bry_.Mid(v, Xowmf_imageinfo_api.FILE_NS_PREFIX.length); + } + else { + throw Err_.new_wo_type("wmf_api does not start with 'File:'", "title", v); + } + + // convert spaces to unders + v = Xoa_ttl.Replace_spaces(v); + + return v; + } + public static byte[] Normalize_minor_mime(byte[] src) { + // convert "image/svg+xml" to "svg+xml" + int src_len = src.length; + int slash_pos = Bry_find_.Find_fwd(src, Byte_ascii.Slash, 0, src_len); + if (slash_pos == Bry_find_.Not_found) { + throw Err_.new_wo_type("wmf_api minor_mime does not have slash;", "minor_mime", src); + } + return Bry_.Mid(src, slash_pos + 1, src_len); + } + public static byte[] Normalize_timestamp(byte[] src) { + // convert 2017-03-06T08:09:10Z to 20170306080910 + byte[] rv = new byte[14]; + int rv_idx = 0; + for (byte b : src) { + if (Byte_ascii.Is_num(b)) { + rv[rv_idx++] = b; + } + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_imageinfo_item__tst.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_imageinfo_item__tst.java index a27517de8..ac39b0dbf 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_imageinfo_item__tst.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_imageinfo_item__tst.java @@ -13,3 +13,29 @@ 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.addons.bldrs.files.missing_origs.apis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; import gplx.xowa.addons.bldrs.files.missing_origs.*; +import org.junit.*; import gplx.core.tests.*; +public class Xowmf_imageinfo_item__tst { + private final Xowmf_imageinfo_item__fxt fxt = new Xowmf_imageinfo_item__fxt(); + + @Test public void Normalize_ttl() { + fxt.Test__Normalize_ttl("File:A b.png", "A_b.png"); + } + @Test public void Normalize_minor_mime() { + fxt.Test__Normalize_minor_mime("image/svg+xml", "svg+xml"); + } + @Test public void Normalize_timestamp() { + fxt.Test__Normalize_timestamp("2017-03-06T08:09:10Z", "20170306080910"); + } +} +class Xowmf_imageinfo_item__fxt { + public void Test__Normalize_ttl(String src, String expd) { + Gftest.Eq__str(expd, Xowmf_imageinfo_item.Normalize_ttl(Bry_.new_u8(src))); + } + public void Test__Normalize_timestamp(String src, String expd) { + Gftest.Eq__str(expd, Xowmf_imageinfo_item.Normalize_timestamp(Bry_.new_u8(src))); + } + public void Test__Normalize_minor_mime(String src, String expd) { + Gftest.Eq__str(expd, Xowmf_imageinfo_item.Normalize_minor_mime(Bry_.new_u8(src))); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_recentchanges_api.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_recentchanges_api.java index a27517de8..aa62774d0 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_recentchanges_api.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_recentchanges_api.java @@ -13,3 +13,109 @@ 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.addons.bldrs.files.missing_origs.apis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; import gplx.xowa.addons.bldrs.files.missing_origs.*; +import gplx.langs.jsons.*; +import gplx.xowa.files.downloads.*; +public class Xowmf_recentchanges_api { + public Ordered_hash Find(Xof_download_wkr download_wkr, String api_domain, String bgn_date, String end_date, int limit) { + // fail if web access disabled + if (!gplx.core.ios.IoEngine_system.Web_access_enabled) { + throw Err_.new_wo_type("web access must be enabled for missing_origs cmd"); + } + + Json_parser parser = new Json_parser(); + Bry_bfr bfr = Bry_bfr_.New(); + Ordered_hash list = Ordered_hash_.New_bry(); + String rcccontinue = null; + // loop until all titles found + while (true) { + // generate api: https://commons.wikimedia.org/w/api.php?format=json&formatversion=2&action=query&list=recentchanges&rcnamespace=6&rctype=log&rcdir=older&rcstart=20170306000000&rcend=20170301000000&rcprop=title|ids|sizes|loginfo&rclimit=5&rccontinue=20170305235910|537908722 + bfr.Add_str_a7("https://"); + bfr.Add_str_a7(api_domain); + bfr.Add_str_a7("/w/api.php"); + bfr.Add_str_a7("?format=json"); // json easier to use than xml + bfr.Add_str_a7("&formatversion=2"); // json easier to use than xml + bfr.Add_str_a7("&action=query"); + bfr.Add_str_a7("&list=recentchanges"); + bfr.Add_str_a7("&rcnamespace=6"); + bfr.Add_str_a7("&rctype=log"); + bfr.Add_str_a7("&rcdir=older"); // NOTE: newer can take a long time + bfr.Add_str_a7("&rcstart=").Add_str_a7(end_date); // NOTE: reverse start and end date so that api is more natural; i.e.: start=20170301 end=20170307 + bfr.Add_str_a7("&rcend=").Add_str_a7(bgn_date); + bfr.Add_str_a7("&rcprop=title|ids|sizes|loginfo"); // list of props; NOTE: "url" / "sha1" for future; "bitdepth" always 0? + bfr.Add_str_a7("&rclimit=").Add_int_variable(limit); + if (rcccontinue != null) + bfr.Add_str_a7("&rccontinue=").Add_str_a7(rcccontinue); + + // call api + byte[] rslt = null; + try { + rslt = download_wkr.Download_xrg().Exec_as_bry(bfr.To_str_and_clear()); + } + catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "wmf_api:failure while calling api; domain=~{0} err=~{1}", api_domain, Err_.Message_gplx_log(e)); + } + + // deserialize + Json_doc jdoc = parser.Parse(rslt); + + // get items + Json_ary ary = (Json_ary)jdoc.Get_grp_many("query", "recentchanges"); + int ary_len = ary.Len(); + for (int i = 0; i < ary_len; i++) { + Json_nde nde = ary.Get_as_nde(i); + try { + byte[] title = Xowmf_imageinfo_item.Normalize_ttl(nde.Get_as_bry("title")); + Xowmf_recentchanges_item item = (Xowmf_recentchanges_item)list.Get_by(title); + // not in list; add it + if (item == null) { + item = new Xowmf_recentchanges_item(); + list.Add(title, item); + } + // is in list; update with latest props + else { + item.Logtype_push(); + } + + String logaction = nde.Get_as_str("logaction"); + item.Init_base + ( nde.Get_as_int("ns") + , title + , nde.Get_as_int("pageid") + , nde.Get_as_int("revid") + , nde.Get_as_int("old_revid") + , nde.Get_as_int("rcid") + , nde.Get_as_int("oldlen") + , nde.Get_as_int("newlen") + , nde.Get_as_int("logid") + , nde.Get_as_str("logtype") + , nde.Get_as_str("logaction") + ); + + Json_nde logparams_nde = nde.Get_as_nde("logparams"); + if (String_.Eq(logaction, "upload")) { + item.Init_upload(Xowmf_imageinfo_item.Normalize_timestamp(logparams_nde.Get_as_bry("img_timestamp"))); + } + else if (String_.Eq(logaction, "move")) { + item.Init_move + ( logparams_nde.Get_as_int("target_ns") + , Xowmf_imageinfo_item.Normalize_ttl(logparams_nde.Get_as_bry("target_title")) + ); + } + else if (String_.Eq(logaction, "delete")) {}// NOTE: logparams nde is empty; DATE:2017-03-07 + } + catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "wmf_api:failure while deserializing node; domain=~{0} err=~{1}", api_domain, Err_.Message_gplx_log(e)); + } + } + + // extract continue + Json_nde continue_nde = (Json_nde)jdoc.Get_grp_many("continue"); + if (continue_nde == null) { + break; + } + rcccontinue = continue_nde.Get_as_str("rccontinue"); + } + return list; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_recentchanges_item.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_recentchanges_item.java index a27517de8..f96d2e6f8 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_recentchanges_item.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/missing_origs/apis/Xowmf_recentchanges_item.java @@ -13,3 +13,50 @@ 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.addons.bldrs.files.missing_origs.apis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; import gplx.xowa.addons.bldrs.files.missing_origs.*; +public class Xowmf_recentchanges_item { + public int Ns() {return ns;} private int ns; + public byte[] Title() {return title;} private byte[] title; + public int Pageid() {return pageid;} private int pageid; + public int Revid() {return revid;} private int revid; + public int Old_revid() {return old_revid;} private int old_revid; + public int Rcid() {return rcid;} private int rcid; + public int Oldlen() {return oldlen;} private int oldlen; + public int Newlen() {return newlen;} private int newlen; + public int Logid() {return logid;} private int logid; + public String Logtype() {return logtype;} private String logtype; + public String Logaction() {return logaction;} private String logaction; // upload, move, delete + + public byte[] Img_timestamp() {return img_timestamp;} private byte[] img_timestamp; + + public int Target_ns() {return target_ns;} private int target_ns; + public byte[] Target_title() {return target_title;} private byte[] target_title; + + public List_adp Logtypes() {return logtypes;} private List_adp logtypes; + public void Init_base(int ns, byte[] title, int pageid, int revid, int old_revid, int rcid, int oldlen, int newlen, int logid, String logtype, String logaction) { + this.ns = ns; + this.title = title; + this.pageid = pageid; + this.revid = revid; + this.old_revid = old_revid; + this.rcid = rcid; + this.oldlen = oldlen; + this.newlen = newlen; + this.logid = logid; + this.logtype = logtype; + this.logaction = logaction; + } + public void Init_upload(byte[] img_timestamp) { + this.img_timestamp = img_timestamp; + } + public void Init_move(int target_ns, byte[] target_title) { + this.target_ns = target_ns; + this.target_title = target_title; + } + public void Logtype_push() { + if (logtypes == null) { + logtypes = List_adp_.New(); + } + logtypes.Add(logtype); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/shrinks/Xoshrink_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/shrinks/Xoshrink_cmd.java index a27517de8..071da867e 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/shrinks/Xoshrink_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/shrinks/Xoshrink_cmd.java @@ -13,3 +13,16 @@ 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.addons.bldrs.files.shrinks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xoshrink_cmd extends Xob_cmd__base { + public Xoshrink_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + wiki.Init_assert(); + new Xoshrink_mgr().Exec(wiki); + } + + @Override public String Cmd_key() {return BLDR_CMD_KEY;} private static final String BLDR_CMD_KEY = "fsdb.shrink"; + public static final Xob_cmd Prototype = new Xoshrink_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xoshrink_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/shrinks/Xoshrink_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/shrinks/Xoshrink_mgr.java index a27517de8..f7d49c377 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/shrinks/Xoshrink_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/shrinks/Xoshrink_mgr.java @@ -13,3 +13,59 @@ 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.addons.bldrs.files.shrinks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.core.envs.*; +import gplx.dbs.*; +import gplx.fsdb.*; import gplx.fsdb.data.*; import gplx.fsdb.meta.*; +class Xoshrink_mgr { + private Io_url src_url, trg_url; + private Process_adp convert_cmd; + public void Exec(Xowe_wiki wiki) { + // init + src_url = wiki.Fsys_mgr().Root_dir().GenSubFil_nest("tmp", "shrink", "src.file"); + trg_url = wiki.Fsys_mgr().Root_dir().GenSubFil_nest("tmp", "shrink", "trg.file"); + Io_url convert_exe_url = wiki.Appe().Prog_mgr().App_resize_img().Exe_url(); + convert_cmd = Process_adp.New(Gfo_usr_dlg_.Instance, wiki.Appe().Url_cmd_eval(), Process_adp.Run_mode_sync_timeout, 1 * 60, convert_exe_url.Raw(), "-resample ~{w}x~{h}"); + + // get bin_mgr + Fsm_bin_mgr bin_mgr = wiki.File__mnt_mgr().Mnts__get_main().Bin_mgr(); + int len = bin_mgr.Dbs__len(); + + // loop bin_dbs + for (int i = 0; i < len; i++) { + Shrink(bin_mgr.Dbs__get_at(i)); + } + } + private void Shrink(Fsm_bin_fil fil) { + // init + Fsd_bin_tbl tbl = fil.Tbl(); + Db_conn conn = fil.Conn(); + + // prep for update + conn.Txn_bgn("tbl_update"); + Db_stmt stmt = conn.Stmt_update(tbl.Tbl_name(), String_.Ary(tbl.fld__owner_id), tbl.fld__data); + + // get rdr + Db_rdr rdr = conn.Stmt_select_all(tbl.Tbl_name(), tbl.Flds()).Exec_select__rls_auto(); + try { + // loop each row and convert + while (rdr.Move_next()) { + // db.read and fs.save + int id = rdr.Read_int(tbl.fld__owner_id); + byte[] data = rdr.Read_bry(tbl.fld__data); + Io_mgr.Instance.SaveFilBry(src_url, data); + + // convert + convert_cmd.Run();// get w and h + + // fs.load and db.save + data = Io_mgr.Instance.LoadFilBry(trg_url); + tbl.Update(stmt, id, data); + } + } finally { + conn.Txn_end(); + rdr.Rls(); + stmt.Rls(); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xob_bin_db_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xob_bin_db_itm.java index a27517de8..553fb5388 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xob_bin_db_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xob_bin_db_itm.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.addons.bldrs.files.utls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.fsdb.meta.*; +public class Xob_bin_db_itm { + public Xob_bin_db_itm(int id, Io_url db_url, int ns_id, int pt_id) {this.id = id; this.db_url = db_url; this.ns_id = ns_id; this.pt_id = pt_id;} + public int Id() {return id;} private int id; + public int Ns_id() {return ns_id;} private final int ns_id; + public int Pt_id() {return pt_id;} private int pt_id; + public long Db_len() {return db_len;} public void Db_len_(long v) {this.db_len = v;} private long db_len; + public Io_url Db_url() {return db_url;} public void Db_url_(Io_url v) {db_url = v;} private Io_url db_url; + public void Set(int id, int pt_id, Io_url db_url) { + this.id = id; this.pt_id = pt_id; this.db_url = db_url; + } + public static String Gen_name_v1(int pt_id) { + return String_.Format("fsdb.bin.{0}.sqlite3", Int_.To_str_pad_bgn_zero(pt_id, 4)); + } + public static String Gen_name_v2(String domain_str, int ns_id, int pt_id) { + String ns_id_str = Int_.To_str_pad_bgn_zero(ns_id, 3); + String pt_id_str = Int_.To_str_pad_bgn_zero(pt_id, 3); + return String_.Format("{0}-file-ns.{1}-db.{2}.xowa", domain_str, ns_id_str, pt_id_str); + } + public static Xob_bin_db_itm new_v1(Fsm_bin_fil fil) { + byte[] name = Bry_.new_u8(fil.Url_rel()); // EX: "fsdb.bin.0000.sqlite3" + int ns_id = 0; // assume v1 dbs are all in main ns + int pt_id = Bry_.To_int_or(name, 9 , 13, Int_.Min_value); if (pt_id == Int_.Min_value) throw Err_.new_wo_type("bin_db_itm.parse: invalid pt_id", "name", fil.Url_rel(), "conn", fil.Conn().Conn_info().Raw()); + return new Xob_bin_db_itm(fil.Id(), fil.Url(), ns_id, pt_id); + } + public static Xob_bin_db_itm new_v2(Fsm_bin_fil fil) { + byte[] ns_bgn_tkn = Bry_.new_a7("file-ns."), ns_end_tkn = Bry_.new_a7("-db."), pt_end_tkn = Bry_.new_a7(".xowa"); + int ns_bgn_tkn_len = ns_bgn_tkn.length, ns_end_tkn_len = ns_end_tkn.length; + byte[] name = Bry_.new_u8(fil.Url_rel()); // EX: en.wikipedia.org-file-ns.000-db.001.xowa + int ns_bgn = Bry_find_.Find_fwd(name, ns_bgn_tkn, 0); if (ns_bgn == Bry_find_.Not_found) throw Err_.new_wo_type("bin_db_itm.parse: invalid ns_bgn", "name", fil.Url_rel(), "conn", fil.Conn().Conn_info().Raw()); + ns_bgn += ns_bgn_tkn_len; + int ns_end = Bry_find_.Find_fwd(name, ns_end_tkn, ns_bgn); if (ns_end == Bry_find_.Not_found) throw Err_.new_wo_type("bin_db_itm.parse: invalid ns_end", "name", fil.Url_rel(), "conn", fil.Conn().Conn_info().Raw()); + int pt_bgn = ns_end + ns_end_tkn_len; + int pt_end = Bry_find_.Find_fwd(name, pt_end_tkn, pt_bgn); if (pt_end == Bry_find_.Not_found) throw Err_.new_wo_type("bin_db_itm.parse: invalid pt_end", "name", fil.Url_rel(), "conn", fil.Conn().Conn_info().Raw()); + int ns_id = Bry_.To_int_or(name, ns_bgn, ns_end, Int_.Min_value); if (ns_id == Int_.Min_value) throw Err_.new_wo_type("bin_db_itm.parse: invalid ns_id", "name", fil.Url_rel(), "conn", fil.Conn().Conn_info().Raw()); + int pt_id = Bry_.To_int_or(name, pt_bgn, pt_end, Int_.Min_value); if (pt_id == Int_.Min_value) throw Err_.new_wo_type("bin_db_itm.parse: invalid pt_id", "name", fil.Url_rel(), "conn", fil.Conn().Conn_info().Raw()); + return new Xob_bin_db_itm(fil.Id(), fil.Url(), ns_id, pt_id); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xob_bin_db_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xob_bin_db_mgr.java index a27517de8..223be47f6 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xob_bin_db_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xob_bin_db_mgr.java @@ -13,3 +13,58 @@ 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.addons.bldrs.files.utls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.core.primitives.*; import gplx.core.ios.*; +import gplx.fsdb.meta.*; +public class Xob_bin_db_mgr { + private final int[] ns_ids; private final int ns_ids_len; + private final Ordered_hash nth_hash = Ordered_hash_.New(); private final Int_obj_ref tier_key = Int_obj_ref.New_neg1(); + public Xob_bin_db_mgr(int[] ns_ids) { + this.ns_ids = ns_ids; this.ns_ids_len = ns_ids.length; + } + public boolean Schema_is_1() {return schema_is_1;} private boolean schema_is_1; + public void Init_by_mnt_mgr(Fsm_mnt_mgr trg_mnt_mgr) { + Fsm_mnt_itm trg_mnt_itm = trg_mnt_mgr.Mnts__get_main(); + this.schema_is_1 = trg_mnt_itm.Db_mgr().File__schema_is_1(); + Fsm_bin_mgr bin_db_mgr = trg_mnt_itm.Bin_mgr(); + int len = ns_ids_len; + for (int i = 0; i < len; ++i) { // iterate ns_ids and add default nth + int ns_id = ns_ids[i]; + Xob_bin_db_itm nth = new Xob_bin_db_itm(-1, null, ns_id, 0); + nth_hash.Add(Int_obj_ref.New(ns_ids[i]), nth); + } + len = bin_db_mgr.Dbs__len(); + for (int i = 0; i < len; ++i) { // iterate bin_dbs to find max pt_id for each ns + Fsm_bin_fil fil = bin_db_mgr.Dbs__get_at(i); + Xob_bin_db_itm itm = schema_is_1 ? Xob_bin_db_itm.new_v1(fil) : Xob_bin_db_itm.new_v2(fil); + int ns_id = itm.Ns_id(); + Xob_bin_db_itm nth = (Xob_bin_db_itm)nth_hash.Get_by(tier_key.Val_(ns_id)); + if ( nth != null // occurs when existing fsdb_dbb has "file-ns.014-db.001", but 14 no longer specified in fsdb_make; DATE:2016-09-23 + && itm.Pt_id() > nth.Pt_id()) // update max pt_id + nth.Set(itm.Id(), itm.Pt_id(), itm.Db_url()); // note that ns_id is same + } + len = nth_hash.Count(); + for (int i = 0; i < len; ++i) { // iterated tiers to calculate max_size + Xob_bin_db_itm nth = (Xob_bin_db_itm)nth_hash.Get_at(i); + if (nth.Id() == -1) continue; // ignore default nth + IoItmFil nth_itm = Io_mgr.Instance.QueryFil(nth.Db_url()); + nth.Db_len_(nth_itm.Size()); + } + } + public boolean Tier_id_is_last(int tier_id) {return tier_id >= ns_ids_len;} // assumes tier_id is 0 based; EX: 0,1,2 for + public int Get_ns_id(int tier_id) {return ns_ids[tier_id];} + public int Increment_pt_id(Xob_bin_db_itm itm) { + itm.Set(-1, itm.Pt_id() + 1, null); + itm.Db_len_(0); + return itm.Pt_id(); + } + public String Gen_name(String domain_str, int ns_id, int pt_id) { + return schema_is_1 ? Xob_bin_db_itm.Gen_name_v1(pt_id) : Xob_bin_db_itm.Gen_name_v2(domain_str, ns_id, pt_id); + } + public Xob_bin_db_itm Get_nth_by_tier(int tier_id) { + if (schema_is_1) return (Xob_bin_db_itm)nth_hash.Get_by(tier_key.Val_(0)); // v1 is always in ns_0 + if (tier_id >= ns_ids_len) throw Err_.new_wo_type("tier out of range", "tier_id", tier_id, "len", ns_ids_len); + int ns_id = ns_ids[tier_id]; + return (Xob_bin_db_itm)nth_hash.Get_by(tier_key.Val_(ns_id)); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xob_xfer_temp_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xob_xfer_temp_itm.java index a27517de8..a63a4722c 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xob_xfer_temp_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xob_xfer_temp_itm.java @@ -13,3 +13,127 @@ 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.addons.bldrs.files.utls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import gplx.core.stores.*; +import gplx.dbs.*; import gplx.xowa.files.*; +import gplx.xowa.parsers.lnkis.*; import gplx.xowa.parsers.lnkis.files.*; +import gplx.xowa.addons.bldrs.files.dbs.*; +public class Xob_xfer_temp_itm { + public int Lnki_id() {return lnki_id;} private int lnki_id; + public int Lnki_tier_id() {return lnki_tier_id;} private int lnki_tier_id; + public byte Orig_repo() {return orig_repo;} private byte orig_repo; + public int Lnki_ext() {return lnki_ext;} private int lnki_ext; + public byte[] Orig_file_ttl() {return orig_file_ttl;} private byte[] orig_file_ttl; + public String Orig_media_type() {return orig_media_type;} private String orig_media_type; + public String Orig_minor_mime() {return orig_minor_mime;} private String orig_minor_mime; + public byte Orig_media_type_tid() {return orig_media_type_tid;} private byte orig_media_type_tid; + public int Orig_page_id() {return orig_page_id;} private int orig_page_id; + public String Join_ttl() {return join_ttl;} private String join_ttl; + public String Redirect_src() {return redirect_src;} private String redirect_src; + public byte Lnki_type() {return lnki_type;} private byte lnki_type; + public byte Lnki_src_tid() {return lnki_src_tid;} private byte lnki_src_tid; + public int Lnki_w() {return lnki_w;} private int lnki_w; + public int Lnki_h() {return lnki_h;} private int lnki_h; + public int Lnki_count() {return lnki_count;} private int lnki_count; + public int Lnki_page_id() {return lnki_page_id;} private int lnki_page_id; + public int Orig_w() {return orig_w;} private int orig_w; + public int Orig_h() {return orig_h;} private int orig_h; + public int Orig_ext_id() {return orig_ext_id;} private int orig_ext_id; + public double Lnki_upright() {return lnki_upright;} private double lnki_upright; + public double Lnki_thumbtime() {return lnki_thumbtime;} private double lnki_thumbtime; + public int Lnki_page() {return lnki_page;} private int lnki_page; + public void Clear() { + orig_file_ttl = null; + lnki_ext = lnki_type = lnki_src_tid + = orig_repo = orig_media_type_tid = Byte_.Max_value_127; + chk_tid = Chk_tid_none; + lnki_id = lnki_tier_id = lnki_w = lnki_h = lnki_count = lnki_page_id + = orig_w = orig_h = orig_page_id = Int_.Neg1; + join_ttl = redirect_src = orig_media_type = null; + lnki_upright = Xop_lnki_tkn.Upright_null; + lnki_thumbtime = Xof_lnki_time.Null; + lnki_page = Xof_lnki_page.Null; + } + public void Load(DataRdr rdr) { + lnki_id = rdr.ReadInt(Xob_lnki_regy_tbl.Fld_lnki_id); + lnki_tier_id = rdr.ReadInt(Xob_lnki_regy_tbl.Fld_lnki_tier_id); + lnki_page_id = rdr.ReadInt(Xob_lnki_regy_tbl.Fld_lnki_page_id); + lnki_ext = rdr.ReadInt(Xob_lnki_regy_tbl.Fld_lnki_ext); + lnki_type = rdr.ReadByte(Xob_lnki_regy_tbl.Fld_lnki_type); + lnki_src_tid = rdr.ReadByte(Xob_lnki_regy_tbl.Fld_lnki_src_tid); + lnki_w = rdr.ReadInt(Xob_lnki_regy_tbl.Fld_lnki_w); + lnki_h = rdr.ReadInt(Xob_lnki_regy_tbl.Fld_lnki_h); + lnki_upright = rdr.ReadDouble(Xob_lnki_regy_tbl.Fld_lnki_upright); + lnki_thumbtime = Xof_lnki_time.Db_load_double(rdr, Xob_lnki_regy_tbl.Fld_lnki_time); + lnki_page = rdr.ReadInt(Xob_lnki_regy_tbl.Fld_lnki_page); + lnki_count = rdr.ReadInt(Xob_lnki_regy_tbl.Fld_lnki_count); + orig_file_ttl = rdr.ReadBryByStr(Xob_lnki_regy_tbl.Fld_lnki_ttl); + orig_repo = rdr.ReadByte(Xob_orig_regy_tbl.Fld_orig_repo); + orig_page_id = rdr.ReadIntOr(Xob_orig_regy_tbl.Fld_orig_page_id, -1); + join_ttl = rdr.ReadStr(Xob_orig_regy_tbl.Fld_orig_file_ttl); + redirect_src = rdr.ReadStr(Xob_orig_regy_tbl.Fld_lnki_ttl); + orig_w = rdr.ReadIntOr(Xob_orig_regy_tbl.Fld_orig_w, -1); + orig_h = rdr.ReadIntOr(Xob_orig_regy_tbl.Fld_orig_h, -1); + orig_media_type = rdr.ReadStrOr(Xob_orig_regy_tbl.Fld_orig_media_type, ""); // convert nulls to "" + orig_minor_mime = rdr.ReadStrOr(Xob_orig_regy_tbl.Fld_orig_minor_mime, ""); // convert nulls to "" + orig_ext_id = rdr.ReadInt(Xob_orig_regy_tbl.Fld_orig_file_ext); + } + public static final byte + Chk_tid_none = 0 + , Chk_tid_orig_page_id_is_null = 1 + , Chk_tid_orig_media_type_is_audio = 2 + , Chk_tid_ns_is_media = 3 + , Chk_tid_orig_w_is_0 = 4 + ; + public byte Chk_tid() {return chk_tid;} private byte chk_tid; + public boolean Chk(Xof_img_size img_size) { + if (String_.Eq(join_ttl, redirect_src)) // join_ttl is same as redirect_src; not a redirect; EX:(direct) join="A.png";redirect_src="A.png"; (redirect) join="A.png";redirect_src="B.png" (i.e.: B redirects to A) + redirect_src = ""; +// else { // redirect; make sure extension matches; EX: A.png redirects to B.png; lnki_ext will be .png (the lnki's ext); should be .png (the actual file's ext) +// Xof_ext join_ext = Xof_ext_.new_by_ttl_(Bry_.new_u8(join_ttl)); +// lnki_ext = join_ext.Id(); +// } + lnki_ext = orig_ext_id; + orig_media_type_tid = Xof_media_type.Xto_byte(orig_media_type); + if ( Xof_lnki_time.Null_n(lnki_thumbtime) // thumbtime defined + && orig_media_type_tid != Xof_media_type.Tid_video // video can have thumbtime + ) + lnki_thumbtime = Xof_lnki_time.Null; // set thumbtime to NULL; actually occurs for one file: [[File:Crash.arp.600pix.jpg|thumb|thumbtime=2]] + if ( Xof_lnki_page.Null_n(lnki_page) + && !Xof_ext_.Id_supports_page(orig_ext_id)) // djvu / pdf can have page parameters, which are currently being stored in thumbtime; DATE:2014-01-18 + lnki_page = Xof_lnki_page.Null; + if (orig_page_id == -1) { // no orig found (i.e.: not in local's / remote's image.sql); + chk_tid = Chk_tid_orig_page_id_is_null; + return false; + } + if (orig_media_type_tid == Xof_media_type.Tid_audio) { // ignore: audio will never have thumbs + chk_tid = Chk_tid_orig_media_type_is_audio; + return false; + } + if (orig_w <= 0) { // ignore files that have an orig_w of 0; note that ogg files that are sometimes flagged as VIDEO; EX:2009_10_08_Marc_Randazza_interview.ogg; DATE:2014-08-20 + chk_tid = Chk_tid_orig_w_is_0; + return false; + } + if (lnki_ext == Xof_ext_.Id_mid) { // NOTE: .mid does not have orig_media_type of "AUDIO" + chk_tid = Chk_tid_orig_media_type_is_audio; + return false; + } + if (lnki_src_tid == Xop_file_logger_.Tid__media) { + chk_tid = Chk_tid_ns_is_media; + return false; + } + int upright_patch = Xof_patch_upright_tid_.Tid_all; // all future blds will have upright_patch + img_size.Html_size_calc(Xof_exec_tid.Tid_wiki_page, lnki_w, lnki_h, lnki_type, upright_patch, lnki_upright, lnki_ext, orig_w, orig_h, Xof_img_size.Thumb_width_img); + return true; + } + public void Insert(Db_stmt stmt, Xof_img_size img_size) { + boolean file_is_orig = img_size.File_is_orig(); + int file_w = img_size.File_w(); + int file_h = img_size.File_h(); + if (file_is_orig) + file_w = file_h = -1; + Xob_xfer_temp_tbl.Insert(stmt, lnki_id, lnki_tier_id, lnki_page_id, orig_repo, orig_page_id, join_ttl, redirect_src, lnki_ext, lnki_type, orig_media_type + , file_is_orig, orig_w, orig_h, file_w, file_h, img_size.Html_w(), img_size.Html_h(), lnki_w, lnki_h + , lnki_upright, lnki_thumbtime, lnki_page, lnki_count); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xob_xfer_temp_itm_tst.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xob_xfer_temp_itm_tst.java index a27517de8..789707cd9 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xob_xfer_temp_itm_tst.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xob_xfer_temp_itm_tst.java @@ -13,3 +13,178 @@ 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.addons.bldrs.files.utls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +import org.junit.*; +import gplx.core.gfo_ndes.*; +import gplx.core.stores.*; import gplx.xowa.files.*; import gplx.xowa.files.repos.*; +import gplx.xowa.parsers.lnkis.*; import gplx.xowa.parsers.lnkis.files.*; +import gplx.xowa.addons.bldrs.files.dbs.*; +public class Xob_xfer_temp_itm_tst { + private Xob_xfer_temp_itm_fxt fxt = new Xob_xfer_temp_itm_fxt(); + @Before public void init() {fxt.Reset();} + @Test public void Pass() {fxt.Test_pass().Test_itm_chk_fail_id_none();} + @Test public void Missing_orig() {fxt.Test_fail(Xob_xfer_temp_itm.Chk_tid_orig_page_id_is_null , Keyval_.new_(Xob_orig_regy_tbl.Fld_orig_page_id, null));} + @Test public void File_is_audio() {fxt.Test_fail(Xob_xfer_temp_itm.Chk_tid_orig_media_type_is_audio , Keyval_.new_(Xob_orig_regy_tbl.Fld_orig_media_type, Xof_media_type.Name_audio));} + @Test public void File_is_mid() { + fxt.Test_fail(Xob_xfer_temp_itm.Chk_tid_orig_media_type_is_audio , Keyval_.new_(Xob_orig_regy_tbl.Fld_orig_file_ext, Xof_ext_.Id_mid)); + } + @Test public void Redirect_src_is_empty() { // orig_cmd sets all direct files to have "orig_join" == "lnki_ttl" + fxt.Test_bgn + ( Keyval_.new_(Xob_orig_regy_tbl.Fld_orig_file_ttl , "A.png") + , Keyval_.new_(Xob_orig_regy_tbl.Fld_lnki_ttl , "A.png") + ); + fxt.Test_lnki_redirect_src(""); // confirm redirect_src set to "" + } + @Test public void Redirect_src_has_val() { // orig_cmd sets all redirect files to have "orig_join" = redirect and "lnki_ttl" as orig; EX: A.png redirects to B.png; orig_join will be B.png (the actual image) and redirect_src will be A.png (the original lnki) + fxt.Test_bgn + ( Keyval_.new_(Xob_orig_regy_tbl.Fld_orig_file_ttl , "B.png") + , Keyval_.new_(Xob_orig_regy_tbl.Fld_lnki_ttl , "A.png") + ); + fxt.Test_lnki_redirect_src("A.png"); // confirm redirect_src set to "" + } + @Test public void Redirect_should_take_trg_ext() {// if "A.png" redirects to "B.jpg", ext_id should be ".jpg" (the actual file) not ".png (lnki_ext_id) + fxt.Test_bgn + ( Keyval_.new_(Xob_orig_regy_tbl.Fld_orig_file_ttl , "B.jpg") + , Keyval_.new_(Xob_orig_regy_tbl.Fld_lnki_ttl , "A.png") + , Keyval_.new_(Xob_orig_regy_tbl.Fld_orig_file_ext , Xof_ext_.Id_jpg) // .png b/c B.jpg + ); + fxt.Test_lnki_ext_id(Xof_ext_.Id_jpg); // confirm ext changed to .jpg + } + @Test public void Thumbtime_check() {// PURPOSE: one image actually had a thumbtime defined; EX: General_Dynamics_F-16_Fighting_Falcon; [[File:Crash.arp.600pix.jpg|thumb|thumbtime=2]] + fxt.Test_bgn + ( Keyval_.new_(Xob_orig_regy_tbl.Fld_orig_file_ext , Xof_ext_.Id_jpg) + , Keyval_.new_(Xob_lnki_regy_tbl.Fld_lnki_time , (double)3) + ); + fxt.Test_lnki_thumbtime(Xof_lnki_time.Null); + fxt.Reset().Test_bgn + ( Keyval_.new_(Xob_orig_regy_tbl.Fld_orig_media_type , Xof_media_type.Name_video) + , Keyval_.new_(Xob_lnki_regy_tbl.Fld_lnki_time , (double)3) + ); + fxt.Test_lnki_thumbtime(3); + } + @Test public void Page_check() { + fxt.Test_bgn + ( Keyval_.new_(Xob_orig_regy_tbl.Fld_orig_file_ext , Xof_ext_.Id_jpg) + , Keyval_.new_(Xob_lnki_regy_tbl.Fld_lnki_page , 3) + ); + fxt.Test_lnki_page(Xof_lnki_page.Null); + fxt.Reset().Test_bgn + ( Keyval_.new_(Xob_orig_regy_tbl.Fld_orig_file_ext , Xof_ext_.Id_pdf) + , Keyval_.new_(Xob_lnki_regy_tbl.Fld_lnki_page , 3) + ); + fxt.Test_lnki_page(3); + fxt.Reset().Test_bgn + ( Keyval_.new_(Xob_orig_regy_tbl.Fld_orig_file_ext , Xof_ext_.Id_djvu) + , Keyval_.new_(Xob_lnki_regy_tbl.Fld_lnki_page , 3) + ); + fxt.Test_lnki_page(3); + } + @Test public void Media_should_be_ignored() {// ignore [[Media:]] for xfer_thumb (needed for xfer_orig) + fxt.Test_bgn + ( Keyval_.new_(Xob_orig_regy_tbl.Fld_lnki_ttl , "A.png") + , Keyval_.new_(Xob_lnki_regy_tbl.Fld_lnki_src_tid , Xop_file_logger_.Tid__media) + ); + fxt.Test_itm_chk_fail_id(Xob_xfer_temp_itm.Chk_tid_ns_is_media); + } + @Test public void Orig_width_is_0() {// PURPOSE: ignore files with an orig width of 0; note that ogg files that are sometimes flagged as VIDEO; EX:2009_10_08_Marc_Randazza_interview.ogg; DATE:2014-08-20 + fxt.Test_bgn + ( Keyval_.new_(Xob_orig_regy_tbl.Fld_lnki_ttl , "A.ogg") + , Keyval_.new_(Xob_orig_regy_tbl.Fld_orig_media_type , Xof_media_type.Name_video) // VIDEO + , Keyval_.new_(Xob_orig_regy_tbl.Fld_orig_w , 0) // no width defined in image table + , Keyval_.new_(Xob_orig_regy_tbl.Fld_orig_h , 0) + ); + fxt.Test_itm_chk_fail_id(Xob_xfer_temp_itm.Chk_tid_orig_w_is_0); + } +} +class Xob_xfer_temp_itm_fxt { + private Xob_xfer_temp_itm itm = new Xob_xfer_temp_itm(); + private Xof_img_size img_size = new Xof_img_size(); + private DataRdr_mem rdr; + private GfoNde nde; + public static String[] Flds = new String[] + { Xob_lnki_regy_tbl.Fld_lnki_ext + , Xob_lnki_regy_tbl.Fld_lnki_id + , Xob_lnki_regy_tbl.Fld_lnki_tier_id + , Xob_orig_regy_tbl.Fld_orig_repo + , Xob_orig_regy_tbl.Fld_orig_file_ttl + , Xob_orig_regy_tbl.Fld_orig_file_ext + , Xob_orig_regy_tbl.Fld_lnki_ttl + , Xob_lnki_regy_tbl.Fld_lnki_type + , Xob_lnki_regy_tbl.Fld_lnki_src_tid + , Xob_lnki_regy_tbl.Fld_lnki_w + , Xob_lnki_regy_tbl.Fld_lnki_h + , Xob_lnki_regy_tbl.Fld_lnki_count + , Xob_lnki_regy_tbl.Fld_lnki_page_id + , Xob_orig_regy_tbl.Fld_orig_w + , Xob_orig_regy_tbl.Fld_orig_h + , Xob_orig_regy_tbl.Fld_orig_page_id + , Xob_lnki_regy_tbl.Fld_lnki_upright + , Xob_lnki_regy_tbl.Fld_lnki_time + , Xob_lnki_regy_tbl.Fld_lnki_page + , Xob_orig_regy_tbl.Fld_orig_media_type + , Xob_orig_regy_tbl.Fld_orig_minor_mime + } + ; + public Xob_xfer_temp_itm_fxt Reset() { + itm.Clear(); + return this; + } + public Xob_xfer_temp_itm_fxt Init_rdr_image() { + GfoFldList flds = GfoFldList_.str_(Flds); + nde = GfoNde_.vals_(flds, Object_.Ary + ( Xof_ext_.Id_png, 1, 1, Xof_repo_tid_.Tid__remote + , "A.png", Xof_ext_.Id_png, "A.png", Xop_lnki_type.Id_thumb, Xop_file_logger_.Tid__file + , 220, 200, 1, 2, 440, 400, 3 + , Xop_lnki_tkn.Upright_null, Xof_lnki_time.Null, Xof_lnki_page.Null + , Xof_media_type.Name_bitmap, "png" + )); + GfoNdeList subs = GfoNdeList_.new_(); + subs.Add(nde); + GfoNde root = GfoNde_.root_(nde); + rdr = DataRdr_mem.new_(root, flds, subs); + rdr.MoveNextPeer(); + return this; + } + public Xob_xfer_temp_itm_fxt Init_rdr(String key, Object val) { + nde.Write(key, val); + return this; + } + public Xob_xfer_temp_itm_fxt Test_pass() {return Test_bgn();} + public Xob_xfer_temp_itm_fxt Test_fail(byte fail_tid, Keyval... kvs) { + Test_bgn(kvs); + this.Test_itm_chk_fail_id(fail_tid); + return this; + } + public Xob_xfer_temp_itm_fxt Test_bgn(Keyval... kvs) { + Init_rdr_image(); + int len = kvs.length; + for (int i = 0; i < len; i++) { + Keyval kv = kvs[i]; + Init_rdr(kv.Key(), kv.Val()); + } + this.Exec_load(); + this.Exec_chk(); + return this; + } + public Xob_xfer_temp_itm_fxt Test_atr(String key, Object val) { + Tfds.Eq(rdr.Read(key), val); + return this; + } + public Xob_xfer_temp_itm_fxt Exec_load() { + itm.Load(rdr); + return this; + } + public Xob_xfer_temp_itm_fxt Exec_chk() { + itm.Chk(img_size); + return this; + } + public Xob_xfer_temp_itm_fxt Test_lnki_ext_id(int expd) {Tfds.Eq(expd, itm.Lnki_ext()); return this;} + public Xob_xfer_temp_itm_fxt Test_lnki_thumbtime(double expd) {Tfds.Eq(expd, itm.Lnki_thumbtime()); return this;} + public Xob_xfer_temp_itm_fxt Test_lnki_page(int expd) {Tfds.Eq(expd, itm.Lnki_page()); return this;} + public Xob_xfer_temp_itm_fxt Test_lnki_redirect_src(String expd) {Tfds.Eq(expd, itm.Redirect_src()); return this;} + public Xob_xfer_temp_itm_fxt Test_itm_chk_fail_id_none() {return Test_itm_chk_fail_id(Xob_xfer_temp_itm.Chk_tid_none);} + public Xob_xfer_temp_itm_fxt Test_itm_chk_fail_id(byte expd) { + Tfds.Eq(expd, itm.Chk_tid()); + return this; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xobu_poll_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xobu_poll_mgr.java index a27517de8..5a5fb7a46 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xobu_poll_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/files/utls/Xobu_poll_mgr.java @@ -13,3 +13,24 @@ 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.addons.bldrs.files.utls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.files.*; +public class Xobu_poll_mgr implements Gfo_invk { + public Xobu_poll_mgr(Xoae_app app) {this.app = app;} private Xoae_app app; + public int Poll_interval() {return poll_interval;} private int poll_interval = 1000; + private Io_url poll_file; + public void Poll() { + if (poll_file == null) poll_file = app.Fsys_mgr().Root_dir().GenSubFil("bldr_poll.gfs"); + if (!Io_mgr.Instance.ExistsFil(poll_file)) return; // file doesn't exist + String poll_text = Io_mgr.Instance.LoadFilStr(poll_file); + Io_mgr.Instance.DeleteFil(poll_file); + app.Usr_dlg().Note_many("", "", "poll file found: ~{0}", poll_file.Raw()); + app.Gfs_mgr().Run_str(poll_text); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_poll_interval_)) poll_interval = m.ReadInt("v"); + else if (ctx.Match(k, Invk_poll_file_)) poll_file = m.ReadIoUrl("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_poll_interval_ = "poll_interval_", Invk_poll_file_ = "poll_file_"; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_addon.java b/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_addon.java index a27517de8..e73e9f290 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_addon.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_addon.java @@ -13,3 +13,14 @@ 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.addons.bldrs.hdumps.diffs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.hdumps.*; +import gplx.xowa.bldrs.wkrs.*; +public class Dumpdiff_addon implements Xoax_addon_itm, Xoax_addon_itm__bldr { + public Xob_cmd[] Bldr_cmds() { + return new Xob_cmd[] + { Dumpdiff_cmd.Prototype + }; + } + + public String Addon__key() {return "xowa.builds.hdumps.diff";} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_cfg.java b/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_cfg.java index a27517de8..dee86d4d0 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_cfg.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_cfg.java @@ -13,3 +13,15 @@ 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.addons.bldrs.hdumps.diffs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.hdumps.*; +class Dumpdiff_cfg implements Gfo_invk { + public Io_url Prv_dir() {return prv_dir;} private Io_url prv_dir; + public Io_url Cur_dir() {return cur_dir;} private Io_url cur_dir; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__cur_dir_)) this.cur_dir = m.ReadIoUrl("v"); + else if (ctx.Match(k, Invk__prv_dir_)) this.prv_dir = m.ReadIoUrl("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk__cur_dir_ = "cur_dir_", Invk__prv_dir_ = "prv_dir_"; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_cmd.java index a27517de8..a13620a13 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_cmd.java @@ -13,3 +13,24 @@ 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.addons.bldrs.hdumps.diffs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.hdumps.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.htmls.core.htmls.*; +public class Dumpdiff_cmd extends Xob_cmd__base { + private final Dumpdiff_cfg cfg = new Dumpdiff_cfg(); + private final Dumpdiff_mgr mgr = new Dumpdiff_mgr(); + public Dumpdiff_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + wiki.Init_assert(); + mgr.Exec(bldr.App(), wiki, cfg); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__cfg)) return cfg; + else return Gfo_invk_.Rv_unhandled; + } + private static final String Invk__cfg = "cfg"; + + public static final String BLDR_CMD_KEY = "hdump.diff"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Dumpdiff_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Dumpdiff_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_log_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_log_tbl.java index a27517de8..b0da05032 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_log_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_log_tbl.java @@ -13,3 +13,44 @@ 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.addons.bldrs.hdumps.diffs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.hdumps.*; +import gplx.dbs.*; +class Dumpdiff_log_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld__page_id, fld__cur_snip, fld__prv_snip; + private Db_stmt stmt__insert; + public Dumpdiff_log_tbl(Db_conn conn) { + this.conn = conn; + flds.Add_int_pkey("uid"); + this.fld__page_id = flds.Add_int("page_id"); + this.fld__cur_snip = flds.Add_str("cur_snip", 1024); + this.fld__prv_snip = flds.Add_str("prv_snip", 1024); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name = "diff_log"; + public Db_conn Conn() {return conn;} private final Db_conn conn; + public void Create_tbl() { + conn.Meta_tbl_remake(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, fld__page_id, fld__page_id))); + } + public void Insert_bgn() { + stmt__insert = conn.Stmt_insert(tbl_name, fld__page_id, fld__cur_snip, fld__prv_snip); + conn.Txn_bgn("diff_log"); + } + public void Insert_by_batch(int page_id, byte[] prv_snip, byte[] cur_snip) { + stmt__insert.Clear().Val_int(fld__page_id, page_id).Val_bry_as_str(fld__cur_snip, cur_snip).Val_bry_as_str(fld__prv_snip, prv_snip).Exec_insert(); + } + public void Insert_end() { + conn.Txn_end(); + stmt__insert.Rls(); + } + public void Rls() { + stmt__insert = Db_stmt_.Rls(stmt__insert); + } + + public static Dumpdiff_log_tbl New(Xowe_wiki wiki) { + Db_conn conn = Db_conn_bldr.Instance.Get_or_autocreate(true, wiki.Fsys_mgr().Root_dir().GenSubFil("xowa.diff.sqlite3")); + Dumpdiff_log_tbl rv = new Dumpdiff_log_tbl(conn); + conn.Meta_tbl_remake(rv); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_mgr.java index a27517de8..54a936b68 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_mgr.java @@ -13,3 +13,64 @@ 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.addons.bldrs.hdumps.diffs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.hdumps.*; +import gplx.core.brys.*; +import gplx.dbs.*; +import gplx.xowa.htmls.*; +import gplx.xowa.wikis.data.tbls.*; +class Dumpdiff_mgr { + public void Exec(Xoae_app app, Xowe_wiki wiki, Dumpdiff_cfg cfg) { + // init log_tbl, wikis + Dumpdiff_log_tbl log_tbl = Dumpdiff_log_tbl.New(wiki); + Xowe_wiki cur_wiki = Get_wiki_by_dir(wiki, Bool_.Y, cfg.Cur_dir()); + Xowe_wiki prv_wiki = Get_wiki_by_dir(wiki, Bool_.N, cfg.Prv_dir()); + + // init html_loader, page_loader + Hdump_html_loader cur_html_loader = new Hdump_html_loader(cur_wiki); + Hdump_html_loader prv_html_loader = new Hdump_html_loader(prv_wiki); + Dumpdiff_page_loader page_loader = new Dumpdiff_page_loader(cur_wiki, prv_wiki, 10000); + List_adp list = List_adp_.New(); + + // prepare for loop + int page_count = 0, diff_count = 0; + log_tbl.Insert_bgn(); + + // loop page_table until no more + while (true) { + page_loader.Load(list); + int list_len = list.Count(); + if (list_len == 0) break; + + // loop pages, compare, and log + for (int i = 0; i < list_len; ++i) { + Dumpdiff_page_itm page = (Dumpdiff_page_itm)list.Get_at(i); + int page_id = page.Page_id(); + byte[] cur_html = cur_html_loader.Load(page_id, page.Cur_db_id()); + byte[] prv_html = prv_html_loader.Load(page_id, page.Prv_db_id()); + byte[][] diff = Bry_diff_.Diff_1st_line(cur_html, prv_html); + ++page_count; + if (diff != null) { + log_tbl.Insert_by_batch(page_id, diff[0], diff[1]); + ++diff_count; + Gfo_usr_dlg_.Instance.Warn_many("", "", "hdump diff: pages=~{0} diffs=~{1} page_id=~{2} lhs=~{3} rhs=~{4}", page_count, diff_count, page_id, diff[0], diff[1]); + } + } + list.Clear(); + } + + // cleanup + log_tbl.Insert_end(); + } + private static Xowe_wiki Get_wiki_by_dir(Xowe_wiki dflt_wiki, boolean wiki_is_cur, Io_url wiki_dir) { + Xowe_wiki rv = null; + if (wiki_dir == null) { + if (wiki_is_cur) + rv = dflt_wiki; + else + throw Err_.new_("", "prv_dir not specified"); + } + if (rv == null) + rv = gplx.xowa.addons.bldrs.mass_parses.parses.Xow_wiki_utl_.Clone_wiki(dflt_wiki, wiki_dir); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_page_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_page_itm.java index a27517de8..bceee3b3e 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_page_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_page_itm.java @@ -13,3 +13,18 @@ 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.addons.bldrs.hdumps.diffs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.hdumps.*; +class Dumpdiff_page_itm { + public Dumpdiff_page_itm(int page_id, int ns_id, byte[] ttl_bry, int cur_db_id, int prv_db_id) { + this.page_id = page_id; + this.ns_id = ns_id; + this.ttl_bry = ttl_bry; + this.cur_db_id = cur_db_id; + this.prv_db_id = prv_db_id; + } + public int Page_id() {return page_id;} private final int page_id; + public int Ns_id() {return ns_id;} private final int ns_id; + public byte[] Ttl_bry() {return ttl_bry;} private final byte[] ttl_bry; + public int Cur_db_id() {return cur_db_id;} private final int cur_db_id; + public int Prv_db_id() {return prv_db_id;} private final int prv_db_id; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_page_loader.java b/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_page_loader.java index a27517de8..04105a1f4 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_page_loader.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Dumpdiff_page_loader.java @@ -13,3 +13,40 @@ 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.addons.bldrs.hdumps.diffs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.hdumps.*; +import gplx.dbs.*; +class Dumpdiff_page_loader { + private final Db_attach_mgr attach_mgr = new Db_attach_mgr(); + private int select_count, cutoff_page_id; + public Dumpdiff_page_loader(Xowe_wiki cur_wiki, Xowe_wiki prv_wiki, int select_count) { + this.select_count = select_count; + attach_mgr.Conn_main_(cur_wiki.Data__core_mgr().Db__core().Conn()); + attach_mgr.Conn_links_(new Db_attach_itm("prv_db", prv_wiki.Data__core_mgr().Db__core().Conn())); + } + public void Load(List_adp rv) { + String sql = String_.Format(String_.Concat_lines_nl_skip_last // ANSI.Y + ( "SELECT cur.page_id" + , ", cur.page_namespace AS ns_id" + , ", cur.page_title AS ttl_bry" + , ", cur.page_html_db_id AS cur_html_db_id" + , ", prv.page_html_db_id AS prv_html_db_id" + , "FROM page cur" + , " JOIN page prv ON cur.page_id = prv.page_id" + , "WHERE cur.page_id > {0}" + , "AND cur.page_html_db_id != -1" + , "LIMIT {1}" + ), cutoff_page_id, select_count); + sql = attach_mgr.Resolve_sql(sql); + + attach_mgr.Attach(); + Db_rdr rdr = attach_mgr.Conn_main().Stmt_sql(sql).Exec_select__rls_auto(); + while (rdr.Move_next()) { + cutoff_page_id = rdr.Read_int("page_id"); + rv.Add(new Dumpdiff_page_itm(cutoff_page_id + , rdr.Read_int("ns_id"), rdr.Read_bry_by_str("ttl_bry") + , rdr.Read_int("cur_html_db_id"), rdr.Read_int("prv_html_db_id") + )); + } + attach_mgr.Detach(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Hdump_html_loader.java b/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Hdump_html_loader.java index a27517de8..7bbd97945 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Hdump_html_loader.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/hdumps/diffs/Hdump_html_loader.java @@ -13,3 +13,31 @@ 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.addons.bldrs.hdumps.diffs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.hdumps.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.htmls.*; +class Hdump_html_loader { + private final Xowe_wiki wiki; + private Io_stream_zip_mgr stream_zip_mgr = new Io_stream_zip_mgr(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + private final Xoh_page tmp_hpg = new Xoh_page(); + private final Xoa_url tmp_url; private final Xoa_ttl tmp_ttl; + + public Hdump_html_loader(Xowe_wiki wiki) { + this.wiki = wiki; + this.tmp_url = wiki.Utl__url_parser().Parse(Bry_.new_a7("temp")); + this.tmp_ttl = wiki.Ttl_parse(Bry_.new_a7("temp")); + } + + public byte[] Load(int page_id, int html_db_id) { + // load html_bry from db + Xow_db_file html_db = wiki.Data__core_mgr().Dbs__get_by_id_or_fail(html_db_id); + tmp_hpg.Ctor_by_hview(wiki, tmp_url, tmp_ttl, page_id); + html_db.Tbl__html().Select_by_page(tmp_hpg); + + // unzip it + byte[] html_hzip = stream_zip_mgr.Unzip(Io_stream_tid_.Tid__gzip, tmp_hpg.Db().Html().Html_bry()); + return wiki.Html__hdump_mgr().Load_mgr().Decode_as_bry(tmp_bfr, tmp_hpg, html_hzip, true); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/htmls/Html__dump_to_fsys__addon.java b/400_xowa/src/gplx/xowa/addons/bldrs/htmls/Html__dump_to_fsys__addon.java index a27517de8..1bafdd20c 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/htmls/Html__dump_to_fsys__addon.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/htmls/Html__dump_to_fsys__addon.java @@ -13,3 +13,14 @@ 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.addons.bldrs.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.xowa.bldrs.wkrs.*; +public class Html__dump_to_fsys__addon implements Xoax_addon_itm, Xoax_addon_itm__bldr { + public Xob_cmd[] Bldr_cmds() { + return new Xob_cmd[] + { Html__dump_to_fsys__cmd.Prototype + }; + } + + public String Addon__key() {return "xowa.builds.htmls";} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/htmls/Html__dump_to_fsys__cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/htmls/Html__dump_to_fsys__cmd.java index a27517de8..ad54f4c47 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/htmls/Html__dump_to_fsys__cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/htmls/Html__dump_to_fsys__cmd.java @@ -13,3 +13,74 @@ 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.addons.bldrs.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.htmls.core.htmls.*; +import gplx.fsdb.*; import gplx.fsdb.meta.*; import gplx.xowa.files.*; +import gplx.langs.mustaches.*; +import gplx.xowa.wikis.pages.*; +public class Html__dump_to_fsys__cmd extends Xob_cmd__base { + private Io_url template_url, fsys_root; + private byte[] http_root, page_root; + private boolean skip_unchanged = true; + public Html__dump_to_fsys__cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + // init mustache + Mustache_tkn_parser parser = new Mustache_tkn_parser(); + Mustache_tkn_itm root = parser.Parse(Io_mgr.Instance.LoadFilBry(template_url)); + Mustache_render_ctx ctx = new Mustache_render_ctx(); + Bry_bfr bfr = Bry_bfr_.New(); + Mustache_bfr mbfr = new Mustache_bfr(bfr); + Html_page_itm page_itm = new Html_page_itm(); + + // load rdr + Xoh_wtr_ctx hctx = Xoh_wtr_ctx.File_dump(page_root, Bry_.new_a7(".html")); + wiki.Init_assert(); + gplx.xowa.wikis.data.tbls.Xowd_page_tbl page_tbl = wiki.Data__core_mgr().Db__core().Tbl__page(); + Db_conn conn = page_tbl.Conn(); + Db_rdr rdr = conn.Exec_rdr("SELECT page_id, page_title, page_touched FROM page WHERE page_namespace = 0;"); + while (rdr.Move_next()) { + String page_ttl_str = rdr.Read_str("page_title"); + try { + // load page + Xoa_ttl page_ttl = wiki.Ttl_parse(Bry_.new_u8(page_ttl_str)); + DateAdp page_modified_on = DateAdp_.parse_fmt(rdr.Read_str("page_touched"), gplx.xowa.wikis.data.tbls.Xowd_page_tbl.Page_touched_fmt); + Io_url dump_fil_url = Io_url_.new_fil_(fsys_root.Gen_sub_path_for_os(page_ttl_str) + ".html"); + if (skip_unchanged && Io_mgr.Instance.QueryFil(dump_fil_url).ModifiedTime().Eq(page_modified_on)) continue; + + // parse page + Xoae_page page = wiki.Data_mgr().Load_page_and_parse(wiki.Utl__url_parser().Parse(page_ttl.Page_db()), page_ttl); + wiki.Parser_mgr().Parse(page, true); + page.Wikie().Html_mgr().Page_wtr_mgr().Page_read_fmtr().Fmt_("~{page_data}"); + page.Wikie().Html_mgr().Page_wtr_mgr().Wkr(gplx.xowa.wikis.pages.Xopg_page_.Tid_read).Write_body(bfr, wiki.Parser_mgr().Ctx(), hctx, page); + byte[] html_src = bfr.To_bry_and_clear();//page.Wikie().Html_mgr().Page_wtr_mgr().Gen(page, gplx.xowa.wikis.pages.Xopg_page_.Tid_read); // NOTE: must use wiki of page, not of owner tab; DATE:2015-03-05 + byte[] html_head = page.Html_data().Custom_head_tags().To_html__style(bfr); + + // fmt with mustache; write to file + page_itm.Init(http_root, page_root, page_ttl.Page_txt(), html_head, Bry_.Empty, html_src); + root.Render(mbfr, ctx.Init(page_itm)); + Io_mgr.Instance.SaveFilBry(dump_fil_url, mbfr.To_bry_and_clear()); + Io_mgr.Instance.UpdateFilModifiedTime(dump_fil_url, page_modified_on); + } catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "err: ~{0}", Err_.Message_gplx_log(e)); + } + } + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__template_url_)) this.template_url = m.ReadIoUrl("v"); + else if (ctx.Match(k, Invk__fsys_root_)) this.fsys_root = m.ReadIoUrl("v"); + else if (ctx.Match(k, Invk__http_root_)) this.http_root = m.ReadBry("v"); + else if (ctx.Match(k, Invk__page_root_)) this.page_root = m.ReadBry("v"); + else if (ctx.Match(k, Invk__skip_unchanged_)) this.skip_unchanged = m.ReadYn("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk__template_url_ = "template_url_", Invk__fsys_root_ = "fsys_root_" + , Invk__http_root_ = "http_root_", Invk__page_root_ = "page_root_" + , Invk__skip_unchanged_ = "skip_unchanged_"; + + public static final String BLDR_CMD_KEY = "html.dump_to_file"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Html__dump_to_fsys__cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Html__dump_to_fsys__cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/htmls/Html_page_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/htmls/Html_page_itm.java index a27517de8..443495499 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/htmls/Html_page_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/htmls/Html_page_itm.java @@ -13,3 +13,35 @@ 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.addons.bldrs.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.langs.mustaches.*; +class Html_page_itm implements Mustache_doc_itm { + private byte[] http_root; + private byte[] page_root; + private byte[] page_title; + private byte[] page_head_extra; + private byte[] page_caption; + private byte[] page_body; + public Html_page_itm Init(byte[] http_root, byte[] page_root, byte[] page_title, byte[] page_head_extra, byte[] page_caption, byte[] page_body) { + this.http_root = http_root; + this.page_root = page_root; + this.page_title = page_title; + this.page_head_extra = page_head_extra; + this.page_caption = page_caption; + this.page_body = page_body; + return this; + } + public boolean Mustache__write(String key, Mustache_bfr mbfr) { + if (String_.Eq(key, "page_title")) mbfr.Add_bry(page_title); + else if (String_.Eq(key, "http_root")) mbfr.Add_bry(http_root); + else if (String_.Eq(key, "page_root")) mbfr.Add_bry(page_root); + else if (String_.Eq(key, "page_head_extra")) mbfr.Add_bry(page_head_extra); + else if (String_.Eq(key, "page_caption")) mbfr.Add_bry(page_caption); + else if (String_.Eq(key, "page_body")) mbfr.Add_bry(page_body); + else return false; + return true; + } + public Mustache_doc_itm[] Mustache__subs(String key) { + return Mustache_doc_itm_.Ary__empty; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/infos/Xobc_info_doc.java b/400_xowa/src/gplx/xowa/addons/bldrs/infos/Xobc_info_doc.java index a27517de8..5a618fe18 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/infos/Xobc_info_doc.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/infos/Xobc_info_doc.java @@ -13,3 +13,38 @@ 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.addons.bldrs.infos; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.langs.mustaches.*; +class Xobc_info_doc implements Mustache_doc_itm { + private final byte[] wiki_domain, task_size; + private final String wiki_dir, torrent_fil; + private final Mustache_doc_itm[] urls; + public Xobc_info_doc(byte[] wiki_domain, String wiki_dir, byte[] task_size, String torrent_fil, Mustache_doc_itm[] urls) { + this.wiki_domain = wiki_domain; this.wiki_dir = wiki_dir; this.task_size = task_size; this.torrent_fil = torrent_fil; this.urls = urls; + } + public boolean Mustache__write(String key, Mustache_bfr bfr) { + if (String_.Eq(key, "wiki_domain")) bfr.Add_bry(wiki_domain); + else if (String_.Eq(key, "wiki_dir")) bfr.Add_str_u8(wiki_dir); + else if (String_.Eq(key, "task_size")) bfr.Add_bry(task_size); + else if (String_.Eq(key, "torrent_fil")) bfr.Add_str_u8(torrent_fil); + return false; + } + public Mustache_doc_itm[] Mustache__subs(String key) { + if (String_.Eq(key, "urls")) return urls; + return Mustache_doc_itm_.Ary__empty; + } +} +class Xobc_info_url implements Mustache_doc_itm { + private final String url, md5; private final byte[] size; + public Xobc_info_url(String url, byte[] size, String md5) { + this.url = url; this.size = size; this.md5 = md5; + } + public boolean Mustache__write(String key, Mustache_bfr bfr) { + if (String_.Eq(key, "url")) bfr.Add_str_u8(url); + else if (String_.Eq(key, "size")) bfr.Add_bry(size); + else if (String_.Eq(key, "md5")) bfr.Add_str_u8(md5); + else return false; + return true; + } + public Mustache_doc_itm[] Mustache__subs(String key) {return Mustache_doc_itm_.Ary__empty;} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/infos/Xobc_info_html.java b/400_xowa/src/gplx/xowa/addons/bldrs/infos/Xobc_info_html.java index a27517de8..edc0a9626 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/infos/Xobc_info_html.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/infos/Xobc_info_html.java @@ -13,3 +13,76 @@ 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.addons.bldrs.infos; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.xowa.specials.*; import gplx.langs.mustaches.*; import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.tags.*; +import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; import gplx.xowa.addons.bldrs.centrals.dbs.datas.imports.*; import gplx.xowa.addons.bldrs.centrals.hosts.*; +import gplx.xowa.wikis.domains.*; import gplx.core.ios.*; +class Xobc_info_html extends Xow_special_wtr__base { + private final Xobc_task_mgr task_mgr; + private final int task_id; + public Xobc_info_html(Xobc_task_mgr task_mgr, int task_id) {this.task_mgr = task_mgr; this.task_id = task_id;} + @Override protected Io_url Get_addon_dir(Xoa_app app) {return app.Fsys_mgr().Http_root().GenSubDir_nest("bin", "any", "xowa", "addon", "bldr", "info");} + @Override protected Io_url Get_mustache_fil(Io_url addon_dir) {return addon_dir.GenSubFil_nest("bin", "xobc_info.mustache.html");} + @Override protected Mustache_doc_itm Bld_mustache_root(Xoa_app app) { + // get steps for task + Xobc_data_db data_db = task_mgr.Data_db(); + List_adp list = data_db.Tbl__step_map().Select_all(task_id); + + // get underlying files + Host_eval_itm host_eval = new Host_eval_itm(); + int len = list.Len(); + Xobc_info_url[] step_urls = new Xobc_info_url[len]; + Xow_domain_itm wiki_domain = null; + int host_id = -1; + long total_size = 0; + Bry_bfr tmp_size_bfr = Bry_bfr_.New(); + for (int i = 0; i < len; ++i) { + int step_id = Int_.Cast(list.Get_at(i)); + Xobc_import_step_itm step_itm = data_db.Tbl__import_step().Select_one(step_id); + if (i == 0) { + wiki_domain = Xow_abrv_xo_.To_itm(step_itm.Wiki_abrv()); // ASSUME: 1st step's wiki is same for all steps + host_id = step_itm.Host_id; // ASSUME: 1st step's host_id is same for all steps + } + String src_fil = host_eval.Eval_src_fil(data_db, host_id, wiki_domain, step_itm.Import_name); + Io_size_.To_bfr_new(tmp_size_bfr, step_itm.Import_size_zip, 2); + total_size += step_itm.Import_size_raw; + Xobc_info_url step_url = new Xobc_info_url(src_fil, tmp_size_bfr.To_bry_and_clear(), step_itm.Import_md5); + step_urls[i] = step_url; + } + + // get wiki data, total_size + host_eval.Eval_dir_name(wiki_domain); + Io_url trg_dir = app.Fsys_mgr().Wiki_dir().GenSubDir(wiki_domain.Domain_str()); + Io_size_.To_bfr_new(tmp_size_bfr, total_size, 2); + byte[] total_size_bry = tmp_size_bfr.To_bry_and_clear(); + + // get torrent + String torrent_fil = null; + String key = data_db.Tbl__task_regy().Select_key_by_id_or_null(task_id); + if (key == null) torrent_fil = "failed to get torrent for " + Int_.To_str(task_id); + else { + String src_dir = host_eval.Eval_src_dir(data_db, host_id, wiki_domain); + torrent_fil = String_.Format("{0}Xowa_{1}wiki_latest_archive.torrent", src_dir, wiki_domain.Lang_orig_key()); // EX: https://archive.org/download/Xowa_dewiki_latest/Xowa_dewiki_latest_archive.torrent + } + + return new Xobc_info_doc + ( wiki_domain.Domain_bry() + , trg_dir.Raw() + , total_size_bry + , torrent_fil + , step_urls + ); + } + public static String Make_torrent_fil(String src_dir, Xow_domain_itm domain) { + return String_.Format("{0}Xowa_{1}wiki_latest_archive.torrent", src_dir, domain.Lang_orig_key()); // EX: https://archive.org/download/Xowa_dewiki_latest/Xowa_dewiki_latest_archive.torrent + } + @Override protected void Bld_tags(Xoa_app app, Io_url addon_dir, Xopage_html_data page_data) { + Xopg_tag_mgr head_tags = page_data.Head_tags(); + Xopg_tag_wtr_.Add__xocss (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xohelp (head_tags, app.Fsys_mgr().Http_root()); + head_tags.Add(Xopg_tag_itm.New_css_file(addon_dir.GenSubFil_nest("bin", "xobc_info.css"))); + } + @Override protected void Handle_invalid(Xoa_app app, Xoa_page page, Xow_special_page special) { + new Xopage_html_data(special.Special__meta().Display_ttl(), Bry_.new_u8("task has been deleted")).Apply(page); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/infos/Xobc_info_html__tst.java b/400_xowa/src/gplx/xowa/addons/bldrs/infos/Xobc_info_html__tst.java index a27517de8..05ecc3f18 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/infos/Xobc_info_html__tst.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/infos/Xobc_info_html__tst.java @@ -13,3 +13,15 @@ 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.addons.bldrs.infos; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import org.junit.*; import gplx.core.tests.*; import gplx.xowa.wikis.domains.*; +public class Xobc_info_html__tst { + private final Xobc_info_html__fxt fxt = new Xobc_info_html__fxt(); + @Test public void Torrent__en_w() {fxt.Test__torrent_link("en.wikipedia.org" , "https://archive.org/download/Xowa_enwiki_latest_archive.torrent");} + @Test public void Torrent__fr_d() {fxt.Test__torrent_link("fr.wiktionary.org" , "https://archive.org/download/Xowa_frwiki_latest_archive.torrent");} +} +class Xobc_info_html__fxt { + public void Test__torrent_link(String domain_str, String expd) { + Gftest.Eq__str(expd, Xobc_info_html.Make_torrent_fil("https://archive.org/download/", Xow_domain_itm_.parse(Bry_.new_u8(domain_str)))); + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/infos/Xobc_info_special.java b/400_xowa/src/gplx/xowa/addons/bldrs/infos/Xobc_info_special.java index a27517de8..9ba4d298f 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/infos/Xobc_info_special.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/infos/Xobc_info_special.java @@ -13,3 +13,21 @@ 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.addons.bldrs.infos; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.xowa.specials.*; import gplx.core.net.*; import gplx.core.net.qargs.*; import gplx.xowa.wikis.pages.*; +import gplx.xowa.addons.bldrs.centrals.*; +public class Xobc_info_special implements Xow_special_page { + public void Special__gen(Xow_wiki wiki, Xoa_page page, Xoa_url url, Xoa_ttl ttl) { + Gfo_qarg_mgr url_args = new Gfo_qarg_mgr().Init(url.Qargs_ary()); + + int task_id = url_args.Read_int_or("task_id", -1); + if (task_id == -1) return; + + new Xobc_info_html(Xobc_task_special.Task_mgr(wiki.App()), task_id).Bld_page_by_mustache(wiki.App(), page, this); + } + + Xobc_info_special(Xow_special_meta special__meta) {this.special__meta = special__meta;} + public Xow_special_meta Special__meta() {return special__meta;} private final Xow_special_meta special__meta; + public Xow_special_page Special__clone() {return this;} + public static final Xow_special_page Prototype = new Xobc_info_special(Xow_special_meta.New_xo("XowaDownloadCentralInfo", "Download Central Task Info")); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_lock_req_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_lock_req_tbl.java index a27517de8..4f36cbe94 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_lock_req_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_lock_req_tbl.java @@ -13,3 +13,38 @@ 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.addons.bldrs.mass_parses.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.dbs.*; +public class Xomp_lock_req_tbl implements Db_tbl { + private final String fld_machine_name, fld_req_time; + private final Db_conn conn; + public Xomp_lock_req_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "xomp_lock_req"; + this.fld_machine_name = flds.Add_str("machine_name", 255); // EX: "MACHINE1" + this.fld_req_time = flds.Add_str("req_time", 32); // EX: 20160801 010203 + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public Dbmeta_fld_list Flds() {return flds;} private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + public void Create_tbl() { + conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds + , Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "req_time", fld_req_time) + )); + } + public void Insert(String machine_name) { + conn.Stmt_insert(tbl_name, flds).Clear().Val_str(fld_machine_name, machine_name).Val_str(fld_req_time, Datetime_now.Get_force().XtoStr_fmt_yyyyMMdd_HHmmss()).Exec_insert(); + } + public String Select_1st() { + String sql = String_.Format("SELECT * FROM {0} ORDER BY {1} DESC", tbl_name, fld_machine_name); // ANSI.y + Db_rdr rdr = conn.Stmt_sql(sql).Exec_select__rls_auto(); + try { + if (!rdr.Move_next()) throw Err_.new_wo_type("xomp_lock_req has no rows"); + return rdr.Read_str(fld_machine_name);} + finally {rdr.Rls();} + } + public void Delete(String machine_name) { + conn.Stmt_delete(tbl_name, fld_machine_name).Clear().Crt_str(fld_machine_name, machine_name).Exec_delete(); + } + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_lock_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_lock_tbl.java index a27517de8..a6430eac6 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_lock_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_lock_tbl.java @@ -13,3 +13,32 @@ 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.addons.bldrs.mass_parses.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.dbs.*; +public class Xomp_lock_tbl implements Db_tbl { + private final String fld_uid_prv; + private final Db_conn conn; + public Xomp_lock_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "xomp_lock"; + this.fld_uid_prv = flds.Add_int("uid_prv"); // EX: -1 + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public Dbmeta_fld_list Flds() {return flds;} private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + public void Create_tbl() { + conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds)); + conn.Stmt_insert(tbl_name, flds).Clear().Val_int(fld_uid_prv, -1).Exec_insert(); // always add default record when creating table + } + public int Select() { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds).Exec_select__rls_auto(); + try { + if (!rdr.Move_next()) throw Err_.new_wo_type("xomp_lock has no rows"); + return rdr.Read_int(fld_uid_prv);} + finally {rdr.Rls();} + } + public void Update(int uid_prv) { + conn.Stmt_update(tbl_name, String_.Ary_empty, fld_uid_prv).Clear().Val_int(fld_uid_prv, uid_prv).Exec_update(); + } + public void Rls() {} +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_mgr_db.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_mgr_db.java index a27517de8..eccd0637e 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_mgr_db.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_mgr_db.java @@ -13,3 +13,40 @@ 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.addons.bldrs.mass_parses.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; +import gplx.xowa.addons.bldrs.mass_parses.parses.pools.*; import gplx.xowa.addons.bldrs.mass_parses.parses.locks.*; +public class Xomp_mgr_db { + public Xomp_mgr_db(Io_url url) { + this.url = url; + this.conn = Db_conn_bldr.Instance.Get_or_autocreate(true, url); + this.page_tbl = new Xomp_page_tbl(conn); + this.wkr_tbl = new Xomp_wkr_tbl(conn); + this.cfg_tbl = new Db_cfg_tbl(conn, "xowa_cfg"); + // this.lock_mgr = new Xomp_lock_mgr__db(conn, 5000); + this.lock_mgr = new Xomp_lock_mgr__fsys(5000, this.Dir()); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public Io_url Url() {return url;} private final Io_url url; + public Io_url Dir() {return url.OwnerDir();} + public Xomp_page_tbl Tbl__page() {return page_tbl;} private final Xomp_page_tbl page_tbl; + public Xomp_wkr_tbl Tbl__wkr() {return wkr_tbl;} private final Xomp_wkr_tbl wkr_tbl; + public Db_cfg_tbl Tbl__cfg() {return cfg_tbl;} private final Db_cfg_tbl cfg_tbl; + public Xomp_lock_mgr Lock_mgr() {return lock_mgr;} private final Xomp_lock_mgr lock_mgr; + + public void Remake() { + conn.Meta_tbl_remake_many(page_tbl, wkr_tbl, cfg_tbl); + lock_mgr.Remake(); + } + + public static Io_url New__url(Xowe_wiki wiki) {return wiki.Fsys_mgr().Root_dir().GenSubDir_nest("tmp", "xomp");} + public static Xomp_mgr_db New__make(Xowe_wiki wiki) { + Io_url root_dir = New__url(wiki); + Io_mgr.Instance.DeleteDirDeep(root_dir); + return new Xomp_mgr_db(root_dir.GenSubFil("xomp.sqlite3")); + } + public static Xomp_mgr_db New__load(Xowe_wiki wiki) {return New__load(wiki.Fsys_mgr().Root_dir().GenSubDir_nest("tmp", "xomp"));} + public static Xomp_mgr_db New__load(Io_url root_dir) { + return new Xomp_mgr_db(root_dir.GenSubFil("xomp.sqlite3")); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_page_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_page_tbl.java index a27517de8..8c72a0f11 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_page_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_page_tbl.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.addons.bldrs.mass_parses.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.dbs.*; +public class Xomp_page_tbl implements Db_tbl { + private final Object thread_lock = new Object(); + private final Db_conn conn; + // private final String fld_page_id, fld_page_status, fld_page_mgr_id; + public Xomp_page_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "xomp_page"; + flds.Add_int_pkey("xomp_uid"); + flds.Add_int("page_id"); + flds.Add_int("page_ns"); + flds.Add_byte("page_status"); // 0=wait; 1=done; 2=fail + flds.Add_int_dflt("html_len", -1); + flds.Add_int_dflt("xomp_wkr_id", -1); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public Dbmeta_fld_list Flds() {return flds;} private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + + public void Create_tbl() { + conn.Meta_tbl_create + ( Dbmeta_tbl_itm.New(tbl_name, flds + , Dbmeta_idx_itm.new_normal_by_tbl("xomp_page", "xomp_uid__page_status" , "xomp_uid", "page_status")// for parse + , Dbmeta_idx_itm.new_normal_by_tbl("xomp_page", "page_ns__page_id" , "page_ns", "page_id") // for make + )); + } + public void Update_wkr_uid(int wkr_uid, Db_conn wkr_conn) { + synchronized (thread_lock) { // LOCK:used by multiple threads + Db_attach_mgr attach_mgr = new Db_attach_mgr(conn, new Db_attach_itm("wkr_db", wkr_conn)); + attach_mgr.Exec_sql_w_msg("updating page_regy: wkr_id=" + wkr_uid, String_.Concat_lines_nl_skip_last // ANSI.Y + ( "UPDATE xomp_page" + , "SET xomp_wkr_id = " + Int_.To_str(wkr_uid) + , ", html_len = (SELECT length(body) FROM html h WHERE h.page_id = xomp_page.page_id)" + , "WHERE page_id IN (SELECT page_id FROM html h)" + )); + } + } + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_wkr_db.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_wkr_db.java index a27517de8..27de8478f 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_wkr_db.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_wkr_db.java @@ -13,3 +13,24 @@ 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.addons.bldrs.mass_parses.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.dbs.*; +import gplx.xowa.htmls.core.dbs.*; +public class Xomp_wkr_db { + public Xomp_wkr_db(int idx, Io_url url) { + this.idx = idx; + this.url = url; + this.conn = Db_conn_bldr.Instance.Get_or_autocreate(true, url); + this.html_tbl = new Xowd_html_tbl(conn); + conn.Meta_tbl_assert(html_tbl); + } + public int Idx() {return idx;} private final int idx; + public Io_url Url() {return url;} private Io_url url; + public Db_conn Conn() {return conn;} private Db_conn conn; + public Xowd_html_tbl Html_tbl() {return html_tbl;} private final Xowd_html_tbl html_tbl; + + public static Xomp_wkr_db New(Io_url root_dir, int uid) { + Io_url url = root_dir.GenSubFil_nest("xomp_" + Int_.To_str_fmt(uid, "000"), "xomp_wkr.sqlite3"); + return new Xomp_wkr_db(uid, url); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_wkr_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_wkr_tbl.java index a27517de8..8ee77d223 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_wkr_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/dbs/Xomp_wkr_tbl.java @@ -13,3 +13,85 @@ 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.addons.bldrs.mass_parses.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.dbs.*; +public class Xomp_wkr_tbl implements Db_tbl { + private final String fld_wkr_uid, fld_wkr_url, fld_wkr_status, fld_wkr_status_time, fld_wkr_exec_count, fld_wkr_exec_time; + private final Db_conn conn; + private final Object thread_lock = new Object(); + public Xomp_wkr_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "xomp_wkr"; + fld_wkr_uid = flds.Add_int_pkey("wkr_uid"); // EX: 0 + fld_wkr_url = flds.Add_str("wkr_url", 255); // EX: //MACHINE/C:/xowa/wiki/en.wikipedia.org/tmp/xomp + fld_wkr_status = flds.Add_int("wkr_status"); // EX: running; waiting + fld_wkr_status_time = flds.Add_str("wkr_status_time", 255); // EX: 20160801 010203 + fld_wkr_exec_count = flds.Add_int("wkr_exec_count"); // EX: 1000 + fld_wkr_exec_time = flds.Add_int("wkr_exec_time"); // EX: 123 + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public Dbmeta_fld_list Flds() {return flds;} private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + + public int Init_wkrs(String wkr_url, int wkr_len) { + // delete all with machine_name + conn.Stmt_delete(tbl_name, fld_wkr_url).Crt_str(fld_wkr_url, wkr_url).Exec_delete(); + + // get bgn_uid / end_uid + int bgn_uid = conn.Exec_select_max_as_int(tbl_name, fld_wkr_uid, -1) + 1; + int end_uid = bgn_uid + wkr_len; + + // insert into tbl + Db_stmt insert_stmt = conn.Stmt_insert(tbl_name, flds); + for (int i = bgn_uid; i < end_uid; ++i) + Insert(insert_stmt, i, wkr_url); + insert_stmt.Rls(); + return bgn_uid; + } + public int Select_count() { + return conn.Exec_select_count_as_int(tbl_name, 0); + } + + private void Insert(Db_stmt stmt, int wkr_uid, String wkr_url) { + stmt.Clear() + .Val_int(fld_wkr_uid, wkr_uid) + .Val_str(fld_wkr_url, wkr_url).Val_int(fld_wkr_status, Status__running).Val_str(fld_wkr_status_time, Datetime_now.Get_force().XtoStr_fmt_yyyyMMdd_HHmmss()) + .Val_int(fld_wkr_exec_count, 0).Val_int(fld_wkr_exec_time, 0) + .Exec_insert(); + } + public void Update_exec(int wkr_uid, int wkr_exec_count, long wkr_exec_time) { + synchronized (thread_lock) { // LOCK:wkr_tbl is shared by multiple threads / machines + int attempts = 0; + while (true) { + if (++attempts > 10) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to update status; try=~{0}", attempts); + break; + } + try { + conn.Stmt_update(tbl_name, String_.Ary(fld_wkr_uid), fld_wkr_status, fld_wkr_status_time, fld_wkr_exec_count, fld_wkr_exec_time).Clear() + .Val_int(fld_wkr_status, Status__running).Val_str(fld_wkr_status_time, Datetime_now.Get_force().XtoStr_fmt_yyyyMMdd_HHmmss()) + .Val_int(fld_wkr_exec_count, wkr_exec_count).Val_int(fld_wkr_exec_time, (int)(wkr_exec_time / 1000)) + .Crt_int(fld_wkr_uid, wkr_uid) + .Exec_update(); + break; // exit loop + } catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "unable to update status; try=~{0} err=~{1}", attempts, Err_.Message_gplx_log(e)); + gplx.core.threads.Thread_adp_.Sleep(10000); + continue; + } + } + } + } + public void Update_status(int wkr_uid, int status) { + synchronized (thread_lock) { // LOCK:wkr_tbl is shared by multiple threads + conn.Stmt_update(tbl_name, String_.Ary(fld_wkr_uid), fld_wkr_status, fld_wkr_status_time).Clear() + .Val_int(fld_wkr_status, status).Val_str(fld_wkr_status_time, Datetime_now.Get_force().XtoStr_fmt_yyyyMMdd_HHmmss()) + .Crt_int(fld_wkr_uid, wkr_uid) + .Exec_update(); + } + } + public void Rls() {} + + public static final int Status__running = 1, Status__sleeping = 2; +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/inits/Xomp_init_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/inits/Xomp_init_cmd.java index a27517de8..862bcd8d5 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/inits/Xomp_init_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/inits/Xomp_init_cmd.java @@ -13,3 +13,21 @@ 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.addons.bldrs.mass_parses.inits; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xomp_init_cmd extends Xob_cmd__base { + private final Xomp_init_mgr mgr = new Xomp_init_mgr(); + public Xomp_init_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + wiki.Init_assert(); + mgr.Exec(wiki); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__cfg)) return mgr.Cfg(); + else return super.Invk(ctx, ikey, k, m); + } private static final String Invk__cfg = "cfg"; + + @Override public String Cmd_key() {return BLDR_CMD_KEY;} private static final String BLDR_CMD_KEY = "wiki.mass_parse.init"; + public static final Xob_cmd Prototype = new Xomp_init_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xomp_init_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/inits/Xomp_init_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/inits/Xomp_init_mgr.java index a27517de8..63981ab86 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/inits/Xomp_init_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/inits/Xomp_init_mgr.java @@ -13,3 +13,39 @@ 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.addons.bldrs.mass_parses.inits; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; +import gplx.xowa.bldrs.*; +import gplx.xowa.addons.bldrs.mass_parses.parses.*; import gplx.xowa.addons.bldrs.mass_parses.dbs.*; +class Xomp_init_mgr { + public Xomp_init_mgr_cfg Cfg() {return cfg;} private final Xomp_init_mgr_cfg cfg = new Xomp_init_mgr_cfg(); + public void Exec(Xowe_wiki wiki) { + // init vars + cfg.Init(wiki); + Xomp_mgr_db mgr_db = Xomp_mgr_db.New__make(wiki); + Db_conn mgr_conn = mgr_db.Conn(); + + // remake all tbls + mgr_db.Remake(); + + // insert ns into cfg; need for lnki_temp.tier in xomp.make + mgr_db.Tbl__cfg().Insert_str("", gplx.xowa.addons.bldrs.mass_parses.parses.wkrs.Xomp_parse_wkr.Cfg__ns_ids, Int_ary_.To_str("|", cfg.Ns_ids())); + + // fill page tbl + Db_attach_mgr attach_mgr = new Db_attach_mgr(mgr_conn, new Db_attach_itm("page_db", wiki.Data__core_mgr().Db__core().Conn())); + int[] ns_ary = cfg.Ns_ids(); + int len = ns_ary.length; + String sql = String_.Concat_lines_nl_skip_last // ANSI.Y + ( "INSERT INTO xomp_page (page_id, page_ns, page_status, html_len, xomp_wkr_id)" + , "SELECT p.page_id, p.page_namespace, 0, 0, 0" + , "FROM page p" + , "WHERE p.page_namespace = {0}" + , "AND p.page_is_redirect = 0" + , "ORDER BY p.page_id" + ); + for (int i = 0; i < len; ++i) { + int ns_id = ns_ary[i]; + attach_mgr.Exec_sql_w_msg("adding rows for xomp_page: ns=" + ns_id, sql, ns_id); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/inits/Xomp_init_mgr_cfg.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/inits/Xomp_init_mgr_cfg.java index a27517de8..e82e08072 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/inits/Xomp_init_mgr_cfg.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/inits/Xomp_init_mgr_cfg.java @@ -13,3 +13,26 @@ 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.addons.bldrs.mass_parses.inits; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.xowa.wikis.nss.*; +class Xomp_init_mgr_cfg implements Gfo_invk { + public int[] Ns_ids() {return ns_ids;} private int[] ns_ids = new int[] {0, 4, 14, 100}; + public void Init(Xowe_wiki wiki) { + if (ns_ids == null) ns_ids = Ns_ids(wiki.Ns_mgr()); + } + private static int[] Ns_ids(Xow_ns_mgr ns_mgr) { + int ns_ids_len = ns_mgr.Ids_len(); + int[] rv = new int[ns_ids_len]; + for (int i = 0; i < ns_ids_len; ++i) + rv[i] = ns_mgr.Ids_get_at(i).Id(); + return rv; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__ns_ids_)) ns_ids = Int_ary_.Parse(m.ReadStr("v"), "|"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk__ns_ids_ = "ns_ids_" + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xob_lnki_temp_row.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xob_lnki_temp_row.java index a27517de8..5ec6bb72a 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xob_lnki_temp_row.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xob_lnki_temp_row.java @@ -13,3 +13,39 @@ 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.addons.bldrs.mass_parses.makes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.dbs.*; +class Xob_lnki_temp_row implements CompareAble { + public int Lnki_id() {return lnki_id;} private int lnki_id; + public int Lnki_tier_id() {return lnki_tier_id;} private int lnki_tier_id; + public int Lnki_page_id() {return lnki_page_id;} private int lnki_page_id; + public byte[] Lnki_ttl() {return lnki_ttl;} private byte[] lnki_ttl; + public byte[] Lnki_commons_ttl() {return lnki_commons_ttl;} private byte[] lnki_commons_ttl; + public byte Lnki_ext() {return lnki_ext;} private byte lnki_ext; + public byte Lnki_type() {return lnki_type;} private byte lnki_type; + public byte Lnki_src_tid() {return lnki_src_tid;} private byte lnki_src_tid; + public int Lnki_w() {return lnki_w;} private int lnki_w; + public int Lnki_h() {return lnki_h;} private int lnki_h; + public double Lnki_upright() {return lnki_upright;} private double lnki_upright; + public double Lnki_time() {return lnki_time;} private double lnki_time; + public int Lnki_page() {return lnki_page;} private int lnki_page; + public void Load(Db_rdr rdr, int lnki_id) { + this.lnki_id = lnki_id; + this.lnki_tier_id = rdr.Read_int("lnki_tier_id"); + this.lnki_page_id = rdr.Read_int("lnki_page_id"); + this.lnki_ttl = rdr.Read_bry_by_str("lnki_ttl"); + this.lnki_commons_ttl = Bry_.new_u8_safe(rdr.Read_str("lnki_commons_ttl")); + this.lnki_ext = rdr.Read_byte("lnki_ext"); + this.lnki_type = rdr.Read_byte("lnki_type"); + this.lnki_src_tid = rdr.Read_byte("lnki_src_tid"); + this.lnki_w = rdr.Read_int("lnki_w"); + this.lnki_h = rdr.Read_int("lnki_h"); + this.lnki_upright = rdr.Read_double("lnki_upright"); + this.lnki_time = rdr.Read_double("lnki_time"); + this.lnki_page = rdr.Read_int("lnki_page"); + } + public int compareTo(Object obj) { + Xob_lnki_temp_row comp = (Xob_lnki_temp_row)obj; + return Int_.Compare(lnki_id, comp.lnki_id); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_html_db_rdr.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_html_db_rdr.java index a27517de8..62882731c 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_html_db_rdr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_html_db_rdr.java @@ -13,3 +13,29 @@ 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.addons.bldrs.mass_parses.makes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.dbs.*; import gplx.xowa.htmls.core.dbs.*; +import gplx.xowa.addons.bldrs.mass_parses.dbs.*; +class Xomp_html_db_rdr { + private final Xowd_html_tbl[] src_tbls; + private final Xomp_mgr_db mgr_db; + public Xomp_html_db_rdr(Xowe_wiki wiki) { + this.mgr_db = Xomp_mgr_db.New__load(wiki); + this.src_tbls = new Xowd_html_tbl[mgr_db.Tbl__wkr().Select_count()]; + } + public void Rows__get(Xowd_html_row rv, int wkr_uid, int page_id) { + Xowd_html_tbl src_tbl = src_tbls[wkr_uid]; + if (src_tbl == null) { + Db_conn wkr_conn = Xomp_wkr_db.New(mgr_db.Dir(), wkr_uid).Conn(); + src_tbl = new Xowd_html_tbl(wkr_conn); + src_tbls[wkr_uid] = src_tbl; + } + src_tbl.Select_as_row(rv, page_id); + } + public void Rls() { + for (Xowd_html_tbl src_tbl : src_tbls) { + if (src_tbl == null) continue; // can be null if fsys has dirs, but db does not have any wkr_ids + src_tbl.Conn().Rls_conn(); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_html_db_wtr.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_html_db_wtr.java index a27517de8..4842923ff 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_html_db_wtr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_html_db_wtr.java @@ -13,3 +13,89 @@ 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.addons.bldrs.mass_parses.makes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.dbs.*; import gplx.xowa.htmls.core.dbs.*; import gplx.xowa.wikis.data.*; +import gplx.xowa.bldrs.cmds.*; +class Xomp_html_db_wtr { + private final long len_max; + private final Xowe_wiki wiki; private final Xow_db_mgr db_mgr; + private long len_cur; + private int prv_ns_id = -1; + private Xow_db_file html_db; private Xowd_html_tbl html_tbl; + private Xob_ns_file_itm ns_itm; + public Xomp_html_db_wtr(Xowe_wiki wiki) { + this.wiki = wiki; this.db_mgr = wiki.Data__core_mgr(); + this.len_max = gplx.xowa.bldrs.Xobldr_cfg.Max_size__html(wiki.App()); + } + public int Cur_db_id() {return html_db.Id();} + public Xowd_html_tbl Tbls__get_or_new(int ns_id, long html_len) { + long len_new = len_cur + html_len; + boolean not_inited = html_tbl == null, out_of_space = len_new > len_max; + boolean is_all_or_few = db_mgr.Props().Layout_html().Tid_is_all_or_few(); + boolean ns_changed = ns_id != prv_ns_id; + if (not_inited || out_of_space + || (ns_changed && !is_all_or_few) // only make new html_db if lot and ns_changed + ) { + Commit(); + if ( is_all_or_few // is not "lot" + && not_inited // not_inited; set html_db + ) { + this.html_db = wiki.Data__core_mgr().Dbs__get_by_tid_or_null(Xow_db_file_.Tid__html_data); + if (html_db == null) + Make_html_db(is_all_or_few, ns_id); + } + else + Make_html_db(is_all_or_few, ns_id); + + this.html_tbl = new Xowd_html_tbl(html_db.Conn()); + html_tbl.Create_tbl(); + html_db.Conn().Txn_bgn("xomp.html_db_wtr"); + len_cur = html_len; // NOTE: this effectively resets len_new to 0 + html_len; + } + else // initied and still has space; just update len + len_cur = len_new; + return html_tbl; + } + private void Make_html_db(boolean is_all_or_few, int ns_id) { + if (prv_ns_id != ns_id + || ns_itm == null) { + prv_ns_id = ns_id; + ns_itm = new Xob_ns_file_itm(Xow_db_file_.Tid__html_data, "ns." + Int_.To_str_pad_bgn_zero(ns_id, 3), Int_ary_.New(ns_id)); + } + String file_name = is_all_or_few ? "-html.xowa" : ns_itm.Make_file_name(); + this.html_db = wiki.Data__core_mgr().Dbs__make_by_tid(Xow_db_file_.Tid__html_data, Int_.To_str(ns_id), ns_itm.Nth_db_idx(), file_name); + } + public void Rls() { + this.Commit(); + } + private void Commit() { + if (html_tbl == null) return; + html_tbl.Conn().Txn_end(); + html_tbl.Conn().Rls_conn(); + + // update page_ids + String sql = String_.Format(String_.Concat_lines_nl_skip_last // ANSI.Y + ( "UPDATE page" + , "SET page_html_db_id = {0}" + , "WHERE page_id IN (SELECT page_id FROM html h)" + ), html_db.Id()); + Db_attach_mgr attach_mgr = new Db_attach_mgr(db_mgr.Db__core().Conn(), new Db_attach_itm("html_db", html_db.Conn())); + attach_mgr.Exec_sql_w_msg("updating page_ids: " + Int_.To_str(html_db.Id()), sql); + } + public static void Delete_html_dbs(Xowe_wiki wiki) { + // run only for few /lot + Xow_db_mgr db_mgr = wiki.Data__core_mgr(); + if (db_mgr.Props().Layout_html().Tid_is_all()) return; + + // loop thru dbs and delete files + int len = db_mgr.Dbs__len(); + for (int i = 0; i < len; ++i) { + Xow_db_file db_file = db_mgr.Dbs__get_at(i); + if (db_file.Tid() == Xow_db_file_.Tid__html_data) + Io_mgr.Instance.DeleteFil(db_file.Url()); + } + + // remove from xowa_db + db_mgr.Dbs__delete_by_tid(Xow_db_file_.Tid__html_data); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_cmd.java index a27517de8..fd285a5c9 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_cmd.java @@ -13,3 +13,22 @@ 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.addons.bldrs.mass_parses.makes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xomp_make_cmd extends Xob_cmd__base { + private final Xomp_make_cmd_cfg cfg = new Xomp_make_cmd_cfg(); + public Xomp_make_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + wiki.Init_assert(); + new Xomp_make_html().Exec(wiki, cfg); + new Xomp_make_lnki().Exec(wiki, cfg, 10000); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__cfg)) return cfg; + else return super.Invk(ctx, ikey, k, m); + } private static final String Invk__cfg = "cfg"; + + @Override public String Cmd_key() {return BLDR_CMD_KEY;} private static final String BLDR_CMD_KEY = "wiki.mass_parse.make"; + public static final Xob_cmd Prototype = new Xomp_make_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xomp_make_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_cmd_cfg.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_cmd_cfg.java index a27517de8..8dd7dd111 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_cmd_cfg.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_cmd_cfg.java @@ -13,3 +13,19 @@ 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.addons.bldrs.mass_parses.makes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +public class Xomp_make_cmd_cfg implements Gfo_invk { + public boolean Delete_html_dbs() {return delete_html_dbs;} private boolean delete_html_dbs = true; + public Ordered_hash Merger_wkrs() {return merger_wkrs;} private final Ordered_hash merger_wkrs = Ordered_hash_.New(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__delete_html_dbs_)) delete_html_dbs = m.ReadYn("v"); + else if (ctx.Match(k, Invk__merger_wkrs_)) { + String[] ary = m.ReadStrAry("k", "|"); + for (String itm : ary) + merger_wkrs.Add(itm, itm); + } + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk__delete_html_dbs_ = "delete_html_dbs_", Invk__merger_wkrs_ = "merger_wkrs_"; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_html.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_html.java index a27517de8..0d79e9ed0 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_html.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_html.java @@ -13,3 +13,57 @@ 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.addons.bldrs.mass_parses.makes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.core.brys.*; +import gplx.dbs.*; import gplx.xowa.htmls.core.dbs.*; import gplx.xowa.addons.bldrs.mass_parses.dbs.*; +class Xomp_make_html { + private final Int_flag_bldr src_body_flag_bldr = Xowd_html_tbl.Make_body_flag_bldr(); + public void Exec(Xowe_wiki wiki, Xomp_make_cmd_cfg cfg) { + // init + Xomp_mgr_db mgr_db = Xomp_mgr_db.New__load(wiki); + Db_conn mgr_conn = mgr_db.Conn(); + + // update wkr_uid; note that this cannot be done in parse_wkr, b/c multiple-writer-errors for xomp.db|page + int wkrs_len = mgr_db.Tbl__wkr().Select_count(); + for (int i = 0; i < wkrs_len; ++i) { + Xomp_wkr_db wkr_db = Xomp_wkr_db.New(mgr_db.Dir(), i); + mgr_db.Tbl__page().Update_wkr_uid(i, wkr_db.Conn()); + } + + // init more + Xomp_html_db_rdr html_db_rdr = new Xomp_html_db_rdr(wiki); + Xomp_html_db_wtr html_db_wtr = new Xomp_html_db_wtr(wiki); + if (cfg.Delete_html_dbs()) Xomp_html_db_wtr.Delete_html_dbs(wiki); + Xowd_html_row src_row = new Xowd_html_row(); + + // loop xomp|page and generate html dbs + String sql = String_.Format("SELECT * FROM xomp_page WHERE html_len != 0 ORDER BY xomp_uid;"); // NOTE: html_len == 0 when page failed + int count = 0; + Db_rdr rdr = mgr_conn.Stmt_sql(sql).Exec_select__rls_auto(); // ANSI.Y + try { + while (rdr.Move_next()) { + Make_page(html_db_rdr, rdr, html_db_wtr, src_row); + if (++count % 10000 == 0) + Gfo_usr_dlg_.Instance.Prog_many("", "", "xomp.html.insert: db=~{0} count=~{1}", Int_.To_str_pad_bgn_space(html_db_wtr.Cur_db_id(), 3), Int_.To_str_pad_bgn_space(count, 8)); + } + } finally {rdr.Rls();} + + // cleanup + mgr_conn.Rls_conn(); + html_db_rdr.Rls(); + html_db_wtr.Rls(); + } + private void Make_page(Xomp_html_db_rdr html_db_rdr, Db_rdr rdr, Xomp_html_db_wtr html_db_wtr, Xowd_html_row src_row) { + // get src_row + int page_id = rdr.Read_int("page_id"); + int html_len = rdr.Read_int("html_len"); + int wkr_id = rdr.Read_int("xomp_wkr_id"); + int ns_id = rdr.Read_int("page_ns"); + html_db_rdr.Rows__get(src_row, wkr_id, page_id); + src_body_flag_bldr.Decode(src_row.Body_flag()); + + // get trg_tbl and write + Xowd_html_tbl trg_tbl = html_db_wtr.Tbls__get_or_new(ns_id, html_len); + trg_tbl.Insert(src_row.Page_id(), src_row.Head_flag(), src_body_flag_bldr.Get_as_int(0), src_body_flag_bldr.Get_as_int(1), src_row.Display_ttl(), src_row.Content_sub(), src_row.Sidebar_div(), src_row.Body()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_lnki.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_lnki.java index a27517de8..12705a0e0 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_lnki.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_lnki.java @@ -13,3 +13,57 @@ 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.addons.bldrs.mass_parses.makes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.xowa.addons.bldrs.mass_parses.dbs.*; +class Xomp_make_lnki { + public void Exec(Xowe_wiki wiki, Xomp_make_cmd_cfg cfg, int uid_count) { + // init + Xomp_mgr_db src_mgr_db = Xomp_mgr_db.New__load(wiki); + + // make mergers; always add lnki_temp + List_adp merger_list = List_adp_.New(); + merger_list.Add(new Xomp_make_merger__lnki_temp()); + if (cfg.Merger_wkrs().Has("xnde")) merger_list.Add(new Xomp_make_merger__xnde()); + Xomp_make_merger[] merger_ary = (Xomp_make_merger[])merger_list.To_ary_and_clear(Xomp_make_merger.class); + + // create ary; add index + int wkr_count = src_mgr_db.Tbl__wkr().Select_count(); + Xomp_wkr_db[] src_mgr_dbs = new Xomp_wkr_db[wkr_count]; + for (int i = 0; i < wkr_count; ++i) { + Xomp_wkr_db src_wkr_db = Xomp_wkr_db.New(src_mgr_db.Dir(), i); + src_mgr_dbs[i] = src_wkr_db; + } + + // run init + for (Xomp_make_merger merger : merger_ary) + merger.Merger__init(wiki, src_mgr_dbs); + + // get max xomp_uid; note that xomp_uid is (a) per page; (b) ordered by page_ns, page_id; (c) starts from 1 + int max_xomp_uid = src_mgr_db.Conn().Exec_select_max_as_int("xomp_page", "xomp_uid", -1); + + // loop over wkrs using range of xomp_uid + int cur_xomp_uid = -1; + while (true) { + // load rows + Gfo_usr_dlg_.Instance.Prog_many("", "", "merging rows; bgn_uid=~{0} end_uid=~{1}", cur_xomp_uid, cur_xomp_uid + uid_count); + for (int i = 0; i < wkr_count; ++i) { + Xomp_wkr_db src_wkr_db = src_mgr_dbs[i]; + for (Xomp_make_merger merger : merger_ary) + merger.Merger__load(src_mgr_db, src_wkr_db, cur_xomp_uid, cur_xomp_uid + uid_count); + } + + // save rows + for (Xomp_make_merger merger : merger_ary) + merger.Merger__save(); + + // NOTE: not ">=" else small wikis will fail with 0 images; EX:cs.q; DATE:2016-09-04 + cur_xomp_uid += uid_count; // note that this sequentially counts up by uid_count (1000), so inevitable that cur_xomp_uid will exceed wkr_uid_max + if (cur_xomp_uid > max_xomp_uid) break; // if max_xomp_uid seen, break; note that ">" necessary because max_xomp_uid may not be in set of wkrs; + } + + // save rows + for (Xomp_make_merger merger : merger_ary) + merger.Merger__term(); + src_mgr_db.Conn().Rls_conn(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_merger.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_merger.java index a27517de8..b77abca38 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_merger.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_merger.java @@ -13,3 +13,11 @@ 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.addons.bldrs.mass_parses.makes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.xowa.addons.bldrs.mass_parses.dbs.*; +interface Xomp_make_merger { + void Merger__init(Xowe_wiki wiki, Xomp_wkr_db[] src_dbs); + int Merger__load(Xomp_mgr_db src_mgr_db, Xomp_wkr_db src_wkr_db, int uid_bgn, int uid_end); + void Merger__save(); + void Merger__term(); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_merger__base.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_merger__base.java index a27517de8..eb869230c 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_merger__base.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_merger__base.java @@ -13,3 +13,86 @@ 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.addons.bldrs.mass_parses.makes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.dbs.*; import gplx.xowa.bldrs.*; +import gplx.xowa.addons.bldrs.mass_parses.dbs.*; +// merges xomp.wkr_dbs into xowa.file.make.sqlite3 +// NOTE: can't do INSERT b/c (a) autonum id is same in diff worker dbs; (b): want autonum to match ns_id, page_id order +abstract class Xomp_make_merger__base implements Xomp_make_merger, gplx.core.lists.ComparerAble { + private Xob_db_file trg_db; + protected Db_tbl trg_tbl; + private List_adp rows = List_adp_.New(); + private String src_tbl__name, src_fld__page_id; + public void Merger__init(Xowe_wiki wiki, Xomp_wkr_db[] src_dbs) { + // get trg.db + this.trg_db = Xob_db_file.New__file_make(wiki.Fsys_mgr().Root_dir()); + + // make trg.tbl + this.trg_tbl = Init__trg_tbl(trg_db); + trg_db.Conn().Meta_tbl_remake(trg_tbl); + + // make idxs on src.tbls + this.src_tbl__name = this.trg_tbl.Tbl_name(); + this.src_fld__page_id = this.Init__src_fld__page_id(); + int len = src_dbs.length; + for (int i = 0; i < len; ++i) { + src_dbs[i].Conn().Meta_idx_assert(src_tbl__name, src_fld__page_id, src_fld__page_id); + } + + // do any other init, such as init'ing insert stmt + this.Init__trg_bgn(); + this.trg_db.Conn().Txn_bgn("merger__" + src_tbl__name); + } + protected abstract Db_tbl Init__trg_tbl(Xob_db_file trg_db); + protected abstract String Init__src_fld__page_id(); + @gplx.Virtual protected void Init__trg_bgn() {} + + public int Merger__load(Xomp_mgr_db src_mgr_db, Xomp_wkr_db src_wkr_db, int uid_bgn, int uid_end) { + // build sql + Db_attach_mgr attach_mgr = new Db_attach_mgr(src_mgr_db.Conn()); + attach_mgr.Conn_links_(new Db_attach_itm("src_wkr_db", src_wkr_db.Conn())); + String sql = Db_sql_.Make_by_fmt(String_.Ary + ( "SELECT src_mgr.xomp_uid" + , ", src_wkr.*" + , "FROM {0} src_wkr" + , " JOIN xomp_page src_mgr ON src_wkr.{1} = src_mgr.page_id" + , "WHERE src_mgr.xomp_uid > {2} AND src_mgr.xomp_uid <= {3}" // NOTE: mgr.xomp_uid will sort pages by ns_id, page_id + ) + , src_tbl__name, src_fld__page_id + , uid_bgn, uid_end + ); + sql = attach_mgr.Resolve_sql(sql); + + // load rows + attach_mgr.Attach(); + Db_rdr rdr = src_mgr_db.Conn().Stmt_sql(sql).Exec_select__rls_auto(); + int rv = -1; + try { + while (rdr.Move_next()) { + rv = rdr.Read_int("xomp_uid"); + Object row = Load__src_row(rdr); + rows.Add(row); + } + } finally {rdr.Rls();} + attach_mgr.Detach(); + return rv; + } + protected abstract Object Load__src_row(Db_rdr rdr); + + public void Merger__save() { + rows.Sort_by(this); + int len = rows.Len(); + for (int i = 0; i < len; ++i) { + Save__trg_row(rows.Get_at(i)); + } + rows.Clear(); + } + protected abstract void Save__trg_row(Object row_obj); + public void Merger__term() { + this.trg_db.Conn().Txn_end(); + } + public int compare(Object lhsObj, Object rhsObj) { + return Compare__hook(lhsObj, rhsObj); + } + protected abstract int Compare__hook(Object lhsObj, Object rhsObj); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_merger__lnki_temp.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_merger__lnki_temp.java index a27517de8..8b0a8efa5 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_merger__lnki_temp.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_merger__lnki_temp.java @@ -13,3 +13,33 @@ 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.addons.bldrs.mass_parses.makes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.dbs.*; import gplx.xowa.bldrs.*; +import gplx.xowa.addons.bldrs.files.dbs.*; +class Xomp_make_merger__lnki_temp extends Xomp_make_merger__base { + private int lnki_id; + private Xob_lnki_temp_tbl trg_tbl__lnki_temp; + @Override protected Db_tbl Init__trg_tbl(Xob_db_file trg_db) { + this.trg_tbl__lnki_temp = new Xob_lnki_temp_tbl(trg_db.Conn()); + trg_db.Conn().Meta_tbl_remake(trg_tbl__lnki_temp); + return trg_tbl__lnki_temp; + } + @Override protected String Init__src_fld__page_id() {return "lnki_page_id";} + @Override protected void Init__trg_bgn() {trg_tbl__lnki_temp.Insert_stmt_make();} + @Override protected Object Load__src_row(Db_rdr rdr) { + Xob_lnki_temp_row rv = new Xob_lnki_temp_row(); + rv.Load(rdr, ++lnki_id); + return rv; + } + @Override protected void Save__trg_row(Object row_obj) { + Xob_lnki_temp_row row = (Xob_lnki_temp_row)row_obj; + trg_tbl__lnki_temp.Insert_cmd_by_batch(row.Lnki_tier_id(), row.Lnki_page_id(), row.Lnki_ttl(), row.Lnki_commons_ttl() + , row.Lnki_ext(), row.Lnki_type(), row.Lnki_src_tid(), row.Lnki_w(), row.Lnki_h(), row.Lnki_upright() + , row.Lnki_time(), row.Lnki_page()); + } + @Override protected int Compare__hook(Object lhsObj, Object rhsObj) { + Xob_lnki_temp_row lhs = (Xob_lnki_temp_row)lhsObj; + Xob_lnki_temp_row rhs = (Xob_lnki_temp_row)rhsObj; + return Int_.Compare(lhs.Lnki_page_id(), rhs.Lnki_page_id()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_merger__xnde.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_merger__xnde.java index a27517de8..0de70534a 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_merger__xnde.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/makes/Xomp_make_merger__xnde.java @@ -13,3 +13,53 @@ 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.addons.bldrs.mass_parses.makes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.dbs.*; import gplx.xowa.bldrs.*; +import gplx.xowa.addons.bldrs.mass_parses.dbs.*; +import gplx.xowa.addons.bldrs.files.dbs.*; +import gplx.xowa.parsers.logs.*; +class Xomp_make_merger__xnde extends Xomp_make_merger__base { + private Xop_log_basic_tbl trg_tbl__log_basic_temp; + @Override protected Db_tbl Init__trg_tbl(Xob_db_file trg_db) { + this.trg_tbl__log_basic_temp = new Xop_log_basic_tbl(trg_db.Conn()); + return trg_tbl__log_basic_temp; + } + @Override protected String Init__src_fld__page_id() {return "page_id";} + @Override protected void Init__trg_bgn() {} // NOTE: trg_tbl has implicit insert_stmt creation + @Override protected Object Load__src_row(Db_rdr rdr) { + Xop_log_basic_row rv = new Xop_log_basic_row(); + rv.Load(rdr); + return rv; + } + @Override protected void Save__trg_row(Object row_obj) { + Xop_log_basic_row row = (Xop_log_basic_row)row_obj; + trg_tbl__log_basic_temp.Insert(row.Log_tid, row.Log_msg, row.Log_time, row.Page_id, row.Page_ttl, row.Args_len, row.Args_str, row.Src_len, row.Src_str); + } + @Override protected int Compare__hook(Object lhsObj, Object rhsObj) { + Xop_log_basic_row lhs = (Xop_log_basic_row)lhsObj; + Xop_log_basic_row rhs = (Xop_log_basic_row)rhsObj; + return Int_.Compare(lhs.Page_id, rhs.Page_id); + } +} +class Xop_log_basic_row { + public int Log_tid; + public String Log_msg; + public int Log_time; + public int Page_id; + public String Page_ttl; + public int Args_len; + public String Args_str; + public int Src_len; + public String Src_str; + public void Load(Db_rdr rdr) { + this.Log_tid = rdr.Read_int("log_tid"); + this.Log_msg = rdr.Read_str("log_msg"); + this.Log_time = rdr.Read_int("log_time"); + this.Page_id = rdr.Read_int("page_id"); + this.Page_ttl = rdr.Read_str("page_ttl"); + this.Args_len = rdr.Read_int("args_len"); + this.Args_str = rdr.Read_str("args_str"); + this.Src_len = rdr.Read_int("src_len"); + this.Src_str = rdr.Read_str("src_str"); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/Xomp_parse_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/Xomp_parse_cmd.java index a27517de8..eb3e24b8d 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/Xomp_parse_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/Xomp_parse_cmd.java @@ -13,3 +13,22 @@ 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.addons.bldrs.mass_parses.parses; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.addons.bldrs.mass_parses.parses.mgrs.*; +public class Xomp_parse_cmd extends Xob_cmd__base { + private final Xomp_parse_mgr mgr = new Xomp_parse_mgr(); + public Xomp_parse_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + wiki.Init_assert(); + mgr.Run(wiki); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__cfg)) return mgr.Cfg(); + else return super.Invk(ctx, ikey, k, m); + } private static final String Invk__cfg = "cfg"; + + @Override public String Cmd_key() {return BLDR_CMD_KEY;} private static final String BLDR_CMD_KEY = "wiki.mass_parse.exec"; + public static final Xob_cmd Prototype = new Xomp_parse_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xomp_parse_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/Xow_wiki_utl_.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/Xow_wiki_utl_.java index a27517de8..71ebdf5d8 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/Xow_wiki_utl_.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/Xow_wiki_utl_.java @@ -13,3 +13,30 @@ 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.addons.bldrs.mass_parses.parses; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.cases.*; +import gplx.xowa.files.*; +public class Xow_wiki_utl_ { + public static Xowe_wiki Clone_wiki(Xowe_wiki wiki, Io_url wiki_dir) { + Xol_lang_itm lang = new Xol_lang_itm(wiki.App().Lang_mgr(), wiki.Lang().Key_bry()); + Xol_lang_itm_.Lang_init(lang); + Xowe_wiki rv = new Xowe_wiki(wiki.Appe(), lang, gplx.xowa.wikis.nss.Xow_ns_mgr_.default_(lang.Case_mgr()), wiki.Domain_itm(), wiki_dir); + rv.Init_by_wiki(); + rv.File_mgr().Repo_mgr().Clone(wiki.File_mgr().Repo_mgr()); + rv.File__fsdb_mode().Tid__v2__bld__y_(); + + // copy other members + rv.Sys_cfg().Copy(wiki.Sys_cfg()); + + Clone_repos(wiki); + return rv; + } + public static void Clone_repos(Xowe_wiki wiki) { + // force all repos to be lnx; will not convert characters like *,",? to _; also force long titles + Xoa_repo_mgr repo_mgr = wiki.Appe().File_mgr().Repo_mgr(); + int len = repo_mgr.Count(); + for (int i = 0; i < len; ++i) + repo_mgr.Get_at(i).Fsys_is_wnt_(Bool_.N).Shorten_ttl_(Bool_.N); + + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/locks/Xomp_lock_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/locks/Xomp_lock_mgr.java index a27517de8..555b30c86 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/locks/Xomp_lock_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/locks/Xomp_lock_mgr.java @@ -13,3 +13,9 @@ 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.addons.bldrs.mass_parses.parses.locks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; +public interface Xomp_lock_mgr { + void Remake(); + int Uid_prv__get(String machine_name); + void Uid_prv__rls(String machine_name, int uid_prv); +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/locks/Xomp_lock_mgr__db.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/locks/Xomp_lock_mgr__db.java index a27517de8..85d18c9d6 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/locks/Xomp_lock_mgr__db.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/locks/Xomp_lock_mgr__db.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.addons.bldrs.mass_parses.parses.locks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.mass_parses.dbs.*; +public class Xomp_lock_mgr__db implements Xomp_lock_mgr { + private final Db_conn conn; + private final Xomp_lock_tbl lock_tbl; + private final Xomp_lock_req_tbl req_tbl; + private final int wait_time; + public Xomp_lock_mgr__db(Db_conn conn, int wait_time) { + this.conn = conn; + this.lock_tbl = new Xomp_lock_tbl(conn); + this.req_tbl = new Xomp_lock_req_tbl(conn); + this.wait_time = wait_time; + } + public void Remake() { + conn.Meta_tbl_remake(lock_tbl); + conn.Meta_tbl_remake(req_tbl); + } + public int Uid_prv__get(String machine_name) { + // insert into req_tbl + req_tbl.Insert(machine_name); + + // loop until req is 1st record in req_tbl + while (true) { + String machine_name_1st = req_tbl.Select_1st(); + if (String_.Eq(machine_name, machine_name_1st)) + break; + else { + Gfo_usr_dlg_.Instance.Note_many("", "", "waiting for lock: ~{0}", machine_name); + gplx.core.threads.Thread_adp_.Sleep(wait_time); + } + } + + // get next uid and fill pages + return lock_tbl.Select(); + } + public void Uid_prv__rls(String machine_name, int uid_prv) { + lock_tbl.Update(uid_prv); + req_tbl.Delete(machine_name); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/locks/Xomp_lock_mgr__fsys.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/locks/Xomp_lock_mgr__fsys.java index a27517de8..4b3f72586 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/locks/Xomp_lock_mgr__fsys.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/locks/Xomp_lock_mgr__fsys.java @@ -13,3 +13,77 @@ 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.addons.bldrs.mass_parses.parses.locks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; +public class Xomp_lock_mgr__fsys implements Xomp_lock_mgr { + private final Io_url root_dir, uid_fil, active_fil; + private final int wait_time; + private Io_url stop_fil; + public Xomp_lock_mgr__fsys(int wait_time, Io_url root_dir) { + this.wait_time = wait_time; + this.root_dir = root_dir; + this.uid_fil = root_dir.GenSubFil("xomp.semaphore.uid.txt"); + this.active_fil = root_dir.GenSubFil("xomp.semaphore.active.txt"); + } + public void Remake() { + Io_url[] fils = Io_mgr.Instance.QueryDir_fils(root_dir); + for (Io_url fil : fils) { + if (String_.Has_at_end(fil.NameAndExt(), ".sempahore.txt")) + Io_mgr.Instance.DeleteFil(fil); + } + Io_mgr.Instance.SaveFilStr(uid_fil, Int_.To_str(Uid__bos)); + } + public int Uid_prv__get(String machine_name) { + // return -1 if stop file exists; note that -1 will stop machine + if (stop_fil == null) this.stop_fil = root_dir.GenSubFil("xomp.semaphore.stop." + machine_name + ".txt"); + if (Io_mgr.Instance.ExistsFil(stop_fil)) return Uid__eos; + + // loop until permit is acquired + int tries = 0; + while (true) { + ++tries; + if (tries > 10) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to acquire permit"); + return Uid__eos; // too many tries; just exit now + } + + // if active_file exists, then assume another machine is reading; + if (Io_mgr.Instance.ExistsFil(active_fil)) { + Sleep(machine_name, "active file exists"); + continue; + } + + // write to the active_file + Io_mgr.Instance.SaveFilStr(active_fil, machine_name); + + // now read it to make sure it's the same + String cur_active = String_.new_u8(Io_mgr.Instance.LoadFilBryOr(active_fil, Bry_.Empty)); + if (!String_.Eq(cur_active, machine_name)) { + Sleep(machine_name, "active file differs: " + cur_active); + continue; + } + break; + } + + // get next uid and fill pages + byte[] cur_uid_bry = Io_mgr.Instance.LoadFilBryOr(uid_fil, null); + if (cur_uid_bry == null) return 0; // file is empty; should only occur on 1st run; return 0, which will start from beginning; + + int cur_uid = Int_.Min_value; + if (cur_uid_bry != null) + cur_uid = Bry_.To_int_or(cur_uid_bry, Int_.Min_value); + if (cur_uid == Int_.Min_value) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "uid fil has bad data: data:~{0}", cur_uid_bry); + return Uid__eos; + } + return cur_uid; + } + private void Sleep(String machine_name, String reason) { + Gfo_usr_dlg_.Instance.Note_many("", "", "waiting for permit: machine:~{0} reason:~{1}", machine_name, reason); + gplx.core.threads.Thread_adp_.Sleep(wait_time); + } + public void Uid_prv__rls(String machine_name, int uid_prv) { + Io_mgr.Instance.SaveFilStr(uid_fil, Int_.To_str(uid_prv)); + Io_mgr.Instance.DeleteFil(active_fil); + } + public static final int Uid__bos = 0, Uid__eos = -1; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/mgrs/Xomp_parse_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/mgrs/Xomp_parse_mgr.java index a27517de8..3d8a21d17 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/mgrs/Xomp_parse_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/mgrs/Xomp_parse_mgr.java @@ -13,3 +13,125 @@ 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.addons.bldrs.mass_parses.parses.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; +import gplx.core.threads.*; import gplx.core.threads.utils.*; +import gplx.core.caches.*; import gplx.xowa.wikis.caches.*; +import gplx.xowa.addons.bldrs.mass_parses.parses.wkrs.*; import gplx.xowa.addons.bldrs.mass_parses.dbs.*; import gplx.xowa.addons.bldrs.mass_parses.parses.pools.*; import gplx.xowa.addons.bldrs.mass_parses.parses.utls.*; +import gplx.xowa.addons.bldrs.wmdumps.imglinks.*; +import gplx.xowa.addons.wikis.fulltexts.indexers.bldrs.*; +public class Xomp_parse_mgr { + private Gfo_countdown_latch latch; + public Xomp_parse_mgr_cfg Cfg() {return cfg;} private final Xomp_parse_mgr_cfg cfg = new Xomp_parse_mgr_cfg(); + public void Wkrs_done_add_1() {latch.Countdown();} + public void Run(Xowe_wiki wiki) { + // init db + cfg.Init(wiki); + Xomp_mgr_db mgr_db = Xomp_mgr_db.New__load(cfg.Mgr_url()); + + // init page_pool + Xomp_page_pool_loader page_pool_loader = new Xomp_page_pool_loader(wiki, mgr_db.Conn(), cfg.Num_pages_in_pool(), cfg.Show_msg__fetched_pool()); + Xomp_page_pool page_pool = new Xomp_page_pool(page_pool_loader, cfg.Num_pages_per_wkr()); + Xomp_prog_mgr prog_mgr = new Xomp_prog_mgr(); + prog_mgr.Init(page_pool_loader.Get_pending_count(), cfg.Progress_interval(), cfg.Perf_interval(), mgr_db.Url().GenNewNameAndExt("xomp.perf.csv")); + + // cache: preload tmpls and imglinks + Xow_page_cache page_cache = Xomp_tmpl_cache_bldr.New(wiki, cfg.Load_all_templates()); + wiki.App().User().User_db_mgr().Cache_mgr().Enabled_n_(); // disable db lookups of user cache + + Gfo_cache_mgr commons_cache = new Gfo_cache_mgr().Max_size_(Int_.Max_value).Reduce_by_(Int_.Max_value); + Xow_ifexist_cache ifexist_cache = new Xow_ifexist_cache(wiki, page_cache).Cache_sizes_(Int_.Max_value, Int_.Max_value); + if (cfg.Load_ifexists_ns() != null) Load_ifexists_ns(wiki, ifexist_cache, cfg.Load_ifexists_ns()); + + Xof_orig_wkr__img_links file_orig_wkr = new Xof_orig_wkr__img_links(wiki); + if (cfg.Load_all_imglinks()) Xof_orig_wkr__img_links_.Load_all(file_orig_wkr); + + // load_wkr: init and start + // Xomp_load_wkr load_wkr = new Xomp_load_wkr(wiki, db_mgr.Mgr_db().Conn(), cfg.Num_pages_in_pool(), cfg.Num_wkrs()); + // Thread_adp_.Start_by_key("xomp.load", Cancelable_.Never, load_wkr, Xomp_load_wkr.Invk__exec); + + // assert wkr_tbl + Gfo_usr_dlg_.Instance.Prog_many("", "", "initing wkrs"); + int wkr_len = cfg.Num_wkrs(); + int wkr_uid_bgn = mgr_db.Tbl__wkr().Init_wkrs(cfg.Wkr_machine_name(), wkr_len); + latch = new Gfo_countdown_latch(wkr_len); + Xomp_parse_wkr[] wkrs = new Xomp_parse_wkr[wkr_len]; + + // init ns_ord_mgr + Xomp_ns_ord_mgr ns_ord_mgr = new Xomp_ns_ord_mgr(Int_ary_.Parse(mgr_db.Tbl__cfg().Select_str("", Xomp_parse_wkr.Cfg__ns_ids), "|")); + + // init indexer + Xofulltext_indexer_wkr indexer = cfg.Indexer_enabled() ? new Xofulltext_indexer_wkr() : null; + if (indexer != null) indexer.Init(wiki, cfg.Indexer_opt()); + + // init parse_wkrs + for (int i = 0; i < wkr_len; ++i) { + // make wiki + Xowe_wiki wkr_wiki = Xow_wiki_utl_.Clone_wiki(wiki, wiki.Fsys_mgr().Root_dir()); + wkr_wiki.Cache_mgr().Page_cache_(page_cache).Commons_cache_(commons_cache).Ifexist_cache_(ifexist_cache); + + // make wkr + Xomp_parse_wkr wkr = new Xomp_parse_wkr(this, cfg, mgr_db, page_pool, prog_mgr, file_orig_wkr, ns_ord_mgr, wkr_wiki, indexer, i + wkr_uid_bgn); + wkrs[i] = wkr; + } + + // start threads; done separately from init b/c thread issues + for (int i = 0; i < wkr_len; ++i) { + Xomp_parse_wkr wkr = wkrs[i]; + Thread_adp_.Start_by_key("xomp." + Int_.To_str_fmt(i, "000"), Cancelable_.Never, wkr, Xomp_parse_wkr.Invk__exec); + } + + // wait until wkrs are done + latch.Await(); + page_pool.Rls(); + if (indexer != null) indexer.Term(); + + // print stats + Bry_bfr bfr = Bry_bfr_.New(); + for (int i = 0; i < wkr_len; ++i) { + wkrs[i].Bld_stats(bfr); + } + Gfo_usr_dlg_.Instance.Note_many("", "", bfr.To_str_and_clear()); + } + private static void Load_ifexists_ns(Xow_wiki wiki, Xow_ifexist_cache cache, String ns_list) { + // expand "*" to all + if (String_.Eq(ns_list, "*")) { + Bry_bfr bfr = Bry_bfr_.New(); + gplx.xowa.wikis.nss.Xow_ns_mgr ns_mgr = wiki.Ns_mgr(); + int len = ns_mgr.Ids_len(); + for (int i = 0; i < len; i++) { + gplx.xowa.wikis.nss.Xow_ns ns = ns_mgr.Ids_get_at(i); + if (ns.Id() >= 0) { // skip Media / Special + if (bfr.Len() != 0) bfr.Add_byte_comma(); + bfr.Add_int_variable(ns.Id()); + } + } + ns_list = bfr.To_str_and_clear(); + } + // load all titles + gplx.xowa.wikis.data.tbls.Xowd_page_tbl page_tbl = wiki.Data__core_mgr().Db__core().Tbl__page(); + String sql = gplx.dbs.Db_sql_.Make_by_fmt(String_.Ary + ( "SELECT {0}, {1}" + , "FROM {2}" + , "WHERE {0} IN ({3})" + ), page_tbl.Fld_page_ns(), page_tbl.Fld_page_title() + , page_tbl.Tbl_name() + , ns_list + ); + gplx.dbs.Db_rdr rdr = page_tbl.Conn().Stmt_sql(sql).Exec_select__rls_auto(); + try { + int counter = 0; + while (rdr.Move_next()) { + int ns = rdr.Read_int(page_tbl.Fld_page_ns()); + byte[] page_db = rdr.Read_bry_by_str(page_tbl.Fld_page_title()); + Xoa_ttl ttl = wiki.Ttl_parse(ns, page_db); + cache.Add(ttl); + if (counter % 100000 == 0) Gfo_usr_dlg_.Instance.Prog_many("", "", "loading ifexists: " + counter); + counter++; + } + } finally {rdr.Rls();} + + // mark ns + int[] ns_ids = Int_ary_.Parse(ns_list, ","); + cache.Add_ns_loaded(ns_ids); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/mgrs/Xomp_parse_mgr_cfg.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/mgrs/Xomp_parse_mgr_cfg.java index a27517de8..925c13335 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/mgrs/Xomp_parse_mgr_cfg.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/mgrs/Xomp_parse_mgr_cfg.java @@ -13,3 +13,72 @@ 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.addons.bldrs.mass_parses.parses.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; +import gplx.core.ios.streams.*; +public class Xomp_parse_mgr_cfg implements Gfo_invk { + public int Num_wkrs() {return num_wkrs;} private int num_wkrs = -1; + public int Num_pages_in_pool() {return num_pages_in_pool;} private int num_pages_in_pool = -1; + public int Num_pages_per_wkr() {return num_pages_per_wkr;} private int num_pages_per_wkr = 1000; + public int Progress_interval() {return progress_interval;} private int progress_interval = 1000; + public int Perf_interval() {return perf_interval;} private int perf_interval = 10000; + public int Commit_interval() {return commit_interval;} private int commit_interval = 10000; + public int Cleanup_interval() {return cleanup_interval;} private int cleanup_interval = 50; // setting at 1000 uses lots of memory + public boolean Hdump_enabled() {return hdump_enabled;} private boolean hdump_enabled = true; + public boolean Hdump_catboxs() {return hdump_catboxs;} private boolean hdump_catboxs = false; + public boolean Hzip_enabled() {return hzip_enabled;} private boolean hzip_enabled = true; + public boolean Hdiff_enabled() {return hdiff_enabled;} private boolean hdiff_enabled = true; + public boolean Log_file_lnkis() {return log_file_lnkis;} private boolean log_file_lnkis = true; + public boolean Load_all_templates() {return load_all_templates;} private boolean load_all_templates = true; + public boolean Load_all_imglinks() {return load_all_imglinks;} private boolean load_all_imglinks = true; + public String Load_ifexists_ns() {return load_ifexists_ns;} private String load_ifexists_ns = null; + public boolean Log_math() {return log_math;} private boolean log_math = false; + public byte Zip_tid() {return zip_tid;} private byte zip_tid = Io_stream_tid_.Tid__gzip; + public Io_url Mgr_url() {return mgr_url;} private Io_url mgr_url; + public String Wkr_machine_name() {return wkr_machine_name;} private String wkr_machine_name; + public boolean Show_msg__fetched_pool() {return show_msg__fetched_pool;} private boolean show_msg__fetched_pool; + public boolean Indexer_enabled() {return indexer_enabled;} private boolean indexer_enabled; + public String Indexer_opt() {return indexer_opt;} private String indexer_opt = gplx.gflucene.indexers.Gflucene_idx_opt.Docs_and_freqs.Key(); + public void Init(Xowe_wiki wiki) { + if (num_wkrs == -1) num_wkrs = gplx.core.envs.Runtime_.Cpu_count(); + if (num_pages_in_pool == -1) num_pages_in_pool = num_wkrs * 1000; + if (mgr_url == null) mgr_url = wiki.Fsys_mgr().Root_dir().GenSubDir_nest("tmp", "xomp"); + if (wkr_machine_name == null) wkr_machine_name = gplx.core.envs.System_.Env__machine_name(); + } + + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__num_wkrs_)) num_wkrs = m.ReadInt("v"); + else if (ctx.Match(k, Invk__num_pages_in_pool_)) num_pages_in_pool = m.ReadInt("v"); + else if (ctx.Match(k, Invk__num_pages_per_wkr_)) num_pages_per_wkr = m.ReadInt("v"); + else if (ctx.Match(k, Invk__progress_interval_)) progress_interval = m.ReadInt("v"); + else if (ctx.Match(k, "perf_interval_")) perf_interval = m.ReadInt("v"); + else if (ctx.Match(k, Invk__commit_interval_)) commit_interval = m.ReadInt("v"); + else if (ctx.Match(k, Invk__cleanup_interval_)) cleanup_interval = m.ReadInt("v"); + else if (ctx.Match(k, Invk__hdump_enabled_)) hdump_enabled = m.ReadYn("v"); + else if (ctx.Match(k, Invk__hzip_enabled_)) hzip_enabled = m.ReadYn("v"); + else if (ctx.Match(k, Invk__hdiff_enabled_)) hdiff_enabled = m.ReadYn("v"); + else if (ctx.Match(k, Invk__zip_tid_)) zip_tid = m.ReadByte("v"); + else if (ctx.Match(k, Invk__load_all_templates_)) load_all_templates = m.ReadYn("v"); + else if (ctx.Match(k, Invk__load_all_imglinks_)) load_all_imglinks = m.ReadYn("v"); + else if (ctx.Match(k, Invk__load_ifexists_ns_)) load_ifexists_ns = m.ReadStr("v"); + else if (ctx.Match(k, Invk__manual_now_)) Datetime_now.Manual_and_freeze_(m.ReadDate("v")); + else if (ctx.Match(k, Invk__mgr_url_)) mgr_url = m.ReadIoUrl("v"); + else if (ctx.Match(k, Invk__wkr_machine_name_)) wkr_machine_name = m.ReadStr("v"); + else if (ctx.Match(k, Invk__show_msg__fetched_pool_)) show_msg__fetched_pool = m.ReadYn("v"); + else if (ctx.Match(k, Invk__hdump_catboxes_)) hdump_catboxs = m.ReadYn("v"); + else if (ctx.Match(k, Invk__log_math_)) log_math = m.ReadYn("v"); + else if (ctx.Match(k, "indexer_enabled_")) indexer_enabled = m.ReadYn("v"); + else if (ctx.Match(k, "indexer_opt_")) indexer_opt = m.ReadStr("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk__num_wkrs_ = "num_wkrs_", Invk__num_pages_in_pool_ = "num_pages_in_pool_", Invk__num_pages_per_wkr_ = "num_pages_per_wkr_" + , Invk__progress_interval_ = "progress_interval_", Invk__commit_interval_ = "commit_interval_", Invk__cleanup_interval_ = "cleanup_interval_" + , Invk__hdump_enabled_ = "hdump_enabled_", Invk__hzip_enabled_ = "hzip_enabled_", Invk__hdiff_enabled_ = "hdiff_enabled_", Invk__zip_tid_ = "zip_tid_" + , Invk__load_all_templates_ = "load_all_templates_", Invk__load_all_imglinks_ = "load_all_imglinks_", Invk__load_ifexists_ns_ = "load_ifexists_ns_", Invk__manual_now_ = "manual_now_" + , Invk__hdump_catboxes_ = "hdump_catboxes_" + , Invk__log_math_ = "log_math_" + , Invk__mgr_url_ = "mgr_url_", Invk__wkr_machine_name_ = "wkr_machine_name_" + , Invk__show_msg__fetched_pool_ = "show_msg__fetched_pool_" + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/mgrs/Xomp_prog_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/mgrs/Xomp_prog_mgr.java index a27517de8..c1cdbbf7f 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/mgrs/Xomp_prog_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/mgrs/Xomp_prog_mgr.java @@ -13,3 +13,49 @@ 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.addons.bldrs.mass_parses.parses.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; +public class Xomp_prog_mgr { + private final Object thread_lock = new Object(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + private int prog_interval, perf_interval; + private int pages_done, pages_total; + private long prog_bgn, prog_prv, prog_done, perf_prv; + private Io_url perf_url; + public void Init(int pages_total, int prog_interval, int perf_interval, Io_url perf_url) { + this.pages_total = pages_total; + this.prog_interval = prog_interval; + this.perf_interval = perf_interval; + this.perf_url = perf_url; + this.prog_bgn = this.prog_prv = this.perf_prv = gplx.core.envs.System_.Ticks(); + Io_mgr.Instance.DeleteFil(perf_url); + } + public void Mark_done(int id) { + synchronized (thread_lock) { + pages_done += 1; + if (pages_done % prog_interval == 0) { + long prog_cur = gplx.core.envs.System_.Ticks(); + int pages_left = pages_total - pages_done; + prog_done += (prog_cur - prog_prv); + double rate_cur = pages_done / (prog_done / Time_span_.Ratio_f_to_s); + String time_past = gplx.xowa.addons.bldrs.centrals.utils.Time_dhms_.To_str(tmp_bfr, (int)((prog_cur - prog_bgn) / 1000), true, 0); + String time_left = gplx.xowa.addons.bldrs.centrals.utils.Time_dhms_.To_str(tmp_bfr, (int)(pages_left / rate_cur), true, 0); + Gfo_usr_dlg_.Instance.Prog_many("", "", "done=~{1} left=~{2} rate=~{3} time_past=~{4} time_left=~{5}", id, pages_done, pages_left, (int)rate_cur, time_past, time_left); + prog_prv = prog_cur; + } + if (pages_done % perf_interval == 0) { + // get vars + long perf_cur = gplx.core.envs.System_.Ticks(); + long perf_diff = (perf_cur - perf_prv); + this.perf_prv = perf_cur; + int memory_used = (int)(gplx.core.envs.Runtime_.Memory_used() / Io_mgr.Len_mb); + + // save to file + tmp_bfr.Add_int_fixed(pages_done, 12).Add_byte_pipe(); + tmp_bfr.Add_int_fixed(memory_used, 6).Add_byte_pipe(); + tmp_bfr.Add_long_variable(perf_diff).Add_byte_pipe(); + tmp_bfr.Add_dte(Datetime_now.Get()).Add_byte_nl(); + Io_mgr.Instance.AppendFilBfr(perf_url, tmp_bfr); + } + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/pools/Xomp_load_wkr.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/pools/Xomp_load_wkr.java index a27517de8..68dc87ec7 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/pools/Xomp_load_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/pools/Xomp_load_wkr.java @@ -13,3 +13,114 @@ 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.addons.bldrs.mass_parses.parses.pools; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; +import gplx.dbs.*; import gplx.core.threads.utils.*; import gplx.xowa.addons.bldrs.mass_parses.parses.utls.*; +public class Xomp_load_wkr implements Gfo_invk { + private final Object thread_lock = new Object(); + private final Xow_wiki wiki; + private final Db_conn mgr_conn; + private final Db_attach_mgr attach_mgr; + private final Gfo_blocking_queue queue; + private final int num_wkrs; + + private final Bry_bfr prog_bfr = Bry_bfr_.New(); + private int pages_done, pages_total; + private long time_bgn, time_prv, time_done; + public Xomp_load_wkr(Xow_wiki wiki, Db_conn mgr_conn, int num_pages_in_pool, int num_wkrs) { + this.wiki = wiki; + this.mgr_conn = mgr_conn; + this.attach_mgr = new Db_attach_mgr(mgr_conn); + this.queue = new Gfo_blocking_queue(num_pages_in_pool); + this.num_wkrs = num_wkrs; + this.time_bgn = this.time_prv = gplx.core.envs.System_.Ticks(); + this.pages_total = this.Get_pending_count(); + } + public int Get_pending_count() { + Db_rdr rdr = mgr_conn.Stmt_sql("SELECT Count(*) AS Count_of FROM xomp_page mp WHERE mp.page_status = 0").Exec_select__rls_auto(); + try {return rdr.Move_next() ? rdr.Read_int("Count_of") : 0;} + finally {rdr.Rls();} + } + public Xomp_page_itm Take() {return (Xomp_page_itm)queue.Take();} + private void Exec() { + int prv_page_id = 0; + while (prv_page_id != -1) { + prv_page_id = Load_pages(prv_page_id); + } + for (int i = 0; i < num_wkrs; ++i) + queue.Put(Xomp_page_itm.Null); + } + private int Load_pages(int prv_page_id) { + // page_tbl.prep_sql + String sql = String_.Format(String_.Concat_lines_nl_skip_last // ANSI.Y + ( "SELECT mp.page_id" + , ", pp.page_namespace" + , ", pp.page_title" + , ", pp.page_text_db_id" + , ", pp.page_score" + , "FROM xomp_page mp" + , " JOIN page pp ON mp.page_id = pp.page_id" + , "WHERE mp.page_id > {0}" + , "AND mp.page_status = 0" + , "LIMIT {1}" + ), prv_page_id, queue.Capacity()); + this.attach_mgr.Conn_links_(new Db_attach_itm("page_db", wiki.Data__core_mgr().Db__core().Conn())); + sql = attach_mgr.Resolve_sql(sql); + + // page_tbl.load_sql + Xomp_text_db_loader text_db_loader = new Xomp_text_db_loader(wiki); + attach_mgr.Attach(); + Db_rdr rdr = mgr_conn.Stmt_sql(sql).Exec_select__rls_auto(); + List_adp list = List_adp_.New(); + int count = 0; + try { + while (rdr.Move_next()) { + prv_page_id = rdr.Read_int("page_id"); + int text_db_id = rdr.Read_int("page_text_db_id"); + Xomp_page_itm ppg = new Xomp_page_itm(prv_page_id); + ppg.Init_by_page + ( rdr.Read_int("page_namespace") + , rdr.Read_bry_by_str("page_title") + , text_db_id + , rdr.Read_int("page_score") + ); + list.Add(ppg); + text_db_loader.Add(text_db_id, ppg); + ++count; + } + } finally {rdr.Rls();} + attach_mgr.Detach(); + + text_db_loader.Load(); + + int len = list.Len(); + for (int i = 0; i < len; ++i) { + queue.Put((Xomp_page_itm)list.Get_at(i)); + } + + return count == 0 ? -1 : prv_page_id; + } + public void Mark_done(int id) { + synchronized (thread_lock) { + pages_done += 1; + if (pages_done % 1000 == 0) { + long time_cur = gplx.core.envs.System_.Ticks(); + int pages_left = pages_total - pages_done; + time_done += (time_cur - time_prv); + double rate_cur = pages_done / (time_done / Time_span_.Ratio_f_to_s); + String time_past = gplx.xowa.addons.bldrs.centrals.utils.Time_dhms_.To_str(prog_bfr, (int)((time_cur - time_bgn) / 1000), true, 0); + String time_left = gplx.xowa.addons.bldrs.centrals.utils.Time_dhms_.To_str(prog_bfr, (int)(pages_left / rate_cur), true, 0); + Gfo_usr_dlg_.Instance.Prog_many("", "", "done=~{0} left=~{1} rate=~{2} time_past=~{3} time_left=~{4}", pages_done, pages_left, (int)rate_cur, time_past, time_left); + time_prv = time_cur; + } + } + } + public void Rls() { + mgr_conn.Rls_conn(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__exec)) this.Exec(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk__exec = "exec"; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/pools/Xomp_page_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/pools/Xomp_page_itm.java index a27517de8..42256b910 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/pools/Xomp_page_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/pools/Xomp_page_itm.java @@ -13,3 +13,26 @@ 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.addons.bldrs.mass_parses.parses.pools; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; +import gplx.xowa.wikis.data.tbls.*; +public class Xomp_page_itm implements Xowd_text_bry_owner { + public Xomp_page_itm(int id) {this.id = id;} + public int Id() {return id;} private final int id; + public int Ns_id() {return ns_id;} private int ns_id; + public int Page_score() {return page_score;} private int page_score; + public byte[] Ttl_bry() {return ttl_bry;} private byte[] ttl_bry; + public int Text_db_id() {return text_db_id;} private int text_db_id; + public byte[] Text() {return text;} private byte[] text; + + public void Init_by_page(int ns_id, byte[] ttl_bry, int text_db_id, int page_score) { + this.ns_id = ns_id; + this.ttl_bry = ttl_bry; + this.text_db_id = text_db_id; + this.page_score = page_score; + } + + public int Page_id() {return id;} + public void Set_text_bry_by_db(byte[] v) {this.text = v;} + + public static final Xomp_page_itm Null = new Xomp_page_itm(-1); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/pools/Xomp_page_pool.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/pools/Xomp_page_pool.java index a27517de8..ae19dddaa 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/pools/Xomp_page_pool.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/pools/Xomp_page_pool.java @@ -13,3 +13,48 @@ 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.addons.bldrs.mass_parses.parses.pools; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.mass_parses.dbs.*; +public class Xomp_page_pool { + private final Object thread_lock = new Object(); + private final Xomp_page_pool_loader loader; + private final int num_pages_per_wkr; + private List_adp pool = List_adp_.New(); private int pool_idx = 0, pool_len = 0; + public Xomp_page_pool(Xomp_page_pool_loader loader, int num_pages_per_wkr) { + this.loader = loader; this.num_pages_per_wkr = num_pages_per_wkr; + } + public boolean Empty() {synchronized (thread_lock) {return empty;}} private boolean empty = false; + public void Get_next(Xomp_mgr_db mgr_db, String machine_name, List_adp wkr_list) { + synchronized (thread_lock) { // LOCK:shared by multiple wkrs + // all pages read; "empty" marked done by another wkr; return; + if (empty) return; + int wkr_end = pool_idx + num_pages_per_wkr; + + // need pages to fulfill request + if (wkr_end > pool_len) { + this.pool = loader.Load(mgr_db, machine_name, pool, pool_idx, pool_len); + this.pool_idx = 0; + this.pool_len = pool.Len(); + if (pool_len == 0) { // no more pages; return; + empty = true; + return; + } + wkr_end = num_pages_per_wkr; // recalc wkr_end + } + + // reset wkr_end; needed for very last set + if (wkr_end >= pool_len) + wkr_end = pool_len; + + // add pages to wkr_list + for (int i = pool_idx; i < wkr_end; ++i) { + Xomp_page_itm page = (Xomp_page_itm)pool.Get_at(i); + wkr_list.Add(page); + } + pool_idx = wkr_end; + } + } + public void Rls() { + loader.Conn().Rls_conn(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/pools/Xomp_page_pool_loader.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/pools/Xomp_page_pool_loader.java index a27517de8..f75270d3b 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/pools/Xomp_page_pool_loader.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/pools/Xomp_page_pool_loader.java @@ -13,3 +13,88 @@ 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.addons.bldrs.mass_parses.parses.pools; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.mass_parses.dbs.*; +import gplx.xowa.addons.bldrs.mass_parses.parses.wkrs.*; import gplx.xowa.addons.bldrs.mass_parses.parses.utls.*; import gplx.xowa.addons.bldrs.mass_parses.parses.locks.*; +public class Xomp_page_pool_loader { + private final Xow_wiki wiki; + private final int num_pages_per_load; + private final Db_attach_mgr attach_mgr; + private final boolean show_msg__fetched_pool; + public Xomp_page_pool_loader(Xow_wiki wiki, Db_conn make_conn, int num_pages_per_load, boolean show_msg__fetched_pool) { + this.wiki = wiki; + this.make_conn = make_conn; + this.attach_mgr = new Db_attach_mgr(make_conn); + this.num_pages_per_load = num_pages_per_load; + this.show_msg__fetched_pool = show_msg__fetched_pool; + } + public Db_conn Conn() {return make_conn;} private final Db_conn make_conn; + public int Get_pending_count() { + Db_rdr rdr = make_conn.Stmt_sql("SELECT Count(*) AS Count_of FROM xomp_page mp WHERE mp.page_status = 0").Exec_select__rls_auto(); + try { + return rdr.Move_next() ? rdr.Read_int("Count_of") : 0; + } finally {rdr.Rls();} + } + public List_adp Load(Xomp_mgr_db mgr_db, String machine_name, List_adp list, int list_idx, int list_len) { + List_adp rv = List_adp_.New(); + + // add remaining pages from old pool to new_pool; + for (int i = list_idx; i < list_len; ++i) { + rv.Add((Xomp_page_itm)list.Get_at(i)); + } + + // load pages into new pool + Xomp_lock_mgr lock_mgr = mgr_db.Lock_mgr(); + int uid_db = lock_mgr.Uid_prv__get(machine_name); + if (uid_db == Xomp_lock_mgr__fsys.Uid__eos) return rv; // assert that uids must be incrementally larger; handle one machine reaching end, and putting -1 in queue; + int uid_new = 0; + try {uid_new = this.Load_from_db(rv, uid_db);} + finally {lock_mgr.Uid_prv__rls(machine_name, uid_new);} + if (show_msg__fetched_pool) + Gfo_usr_dlg_.Instance.Note_many("", "", "fetched new pool: old=~{0} new=~{1}", uid_db, uid_new); + return rv; + } + private int Load_from_db(List_adp list, int uid_prv) { + // prepare for page_tbl + String sql = String_.Format(String_.Concat_lines_nl_skip_last // ANSI.Y + ( "SELECT mp.xomp_uid" + , ", pp.page_id" + , ", pp.page_namespace" + , ", pp.page_title" + , ", pp.page_text_db_id" + , ", pp.page_score" + , "FROM xomp_page mp" + , " JOIN page pp ON mp.page_id = pp.page_id" + , "WHERE mp.xomp_uid > {0}" + , "AND mp.page_status = 0" + , "LIMIT {1}" + ), uid_prv, num_pages_per_load); + this.attach_mgr.Conn_links_(new Db_attach_itm("page_db", wiki.Data__core_mgr().Db__core().Conn())); + sql = attach_mgr.Resolve_sql(sql); + + // run page_tbl + int rv = -1; + Xomp_text_db_loader text_db_loader = new Xomp_text_db_loader(wiki); + attach_mgr.Attach(); + Db_rdr rdr = make_conn.Stmt_sql(sql).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + rv = rdr.Read_int("xomp_uid"); + int text_db_id = rdr.Read_int("page_text_db_id"); + Xomp_page_itm ppg = new Xomp_page_itm(rdr.Read_int("page_id")); + ppg.Init_by_page + ( rdr.Read_int("page_namespace") + , rdr.Read_bry_by_str("page_title") + , text_db_id + , rdr.Read_int("page_score") + ); + list.Add(ppg); + text_db_loader.Add(text_db_id, ppg); + } + } finally {rdr.Rls();} + attach_mgr.Detach(); + + text_db_loader.Load(); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/utls/Xomp_lnki_temp_wkr.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/utls/Xomp_lnki_temp_wkr.java index a27517de8..827cb30ae 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/utls/Xomp_lnki_temp_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/utls/Xomp_lnki_temp_wkr.java @@ -13,3 +13,40 @@ 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.addons.bldrs.mass_parses.parses.utls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; +import gplx.dbs.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.lnkis.*; import gplx.xowa.parsers.lnkis.files.*; +import gplx.xowa.files.*; import gplx.xowa.addons.bldrs.files.cmds.*; import gplx.xowa.addons.bldrs.files.dbs.*; +import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.domains.*; +public class Xomp_lnki_temp_wkr implements Xop_file_logger { + private final Xob_lnki_temp_tbl tbl; + private final Xowe_wiki commons_wiki; + private boolean ns_file_is_case_match_all = true; + public Xomp_lnki_temp_wkr(Xowe_wiki wiki, Db_conn wkr_conn) { + this.tbl = new Xob_lnki_temp_tbl(wkr_conn); wkr_conn.Meta_tbl_assert(tbl); + this.commons_wiki = wiki.Appe().Wiki_mgr().Get_by_or_make(Xow_domain_itm_.Bry__commons); + this.ns_file_is_case_match_all = wiki.Init_assert().Ns_mgr().Ns_file().Case_match() == Xow_ns_case_.Tid__all; // NOTE: wiki must be init'd; + } + public void Bgn() { + tbl.Insert_stmt_make(); + } + public void Log_file(Xop_ctx ctx, Xop_lnki_tkn lnki, byte caller_tid) { + if (lnki.Ttl().ForceLiteralLink()) return; // ignore literal links which create a link to file, but do not show the image; EX: [[:File:A.png|thumb|120px]] creates a link to File:A.png, regardless of other display-oriented args + + // get lnki_data + byte[] ttl = lnki.Ttl().Page_db(); + Xof_ext ext = Xof_ext_.new_by_ttl_(ttl); + byte[] ttl_commons = Xomp_lnki_temp_wkr.To_commons_ttl(ns_file_is_case_match_all, commons_wiki, ttl); + if (lnki.Ns_id() == Xow_ns_.Tid__media) caller_tid = Xop_file_logger_.Tid__media; + + // do insert + tbl.Insert_cmd_by_batch(ctx.Page().Bldr__ns_ord(), ctx.Page().Db().Page().Id(), ttl, ttl_commons, Byte_.By_int(ext.Id()), lnki.Lnki_type(), caller_tid, lnki.W(), lnki.H(), lnki.Upright(), lnki.Time(), lnki.Page()); + } + public void End() {} + public static byte[] To_commons_ttl(boolean ns_file_is_case_match_all, Xowe_wiki commons_wiki, byte[] ttl_bry) { // handle case-sensitive wikis (en.d) vs case-insensitive commons + if (!ns_file_is_case_match_all) return null; // return "" if wiki matches common + Xoa_ttl ttl = Xoa_ttl.Parse(commons_wiki, Xow_ns_.Tid__file, ttl_bry); + byte[] rv = ttl.Page_db(); + return Bry_.Eq(rv, ttl_bry) ? null : rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/utls/Xomp_ns_ord_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/utls/Xomp_ns_ord_mgr.java index a27517de8..72300f2e1 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/utls/Xomp_ns_ord_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/utls/Xomp_ns_ord_mgr.java @@ -13,3 +13,15 @@ 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.addons.bldrs.mass_parses.parses.utls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; +import gplx.core.primitives.*; import gplx.core.lists.hashs.*; +public class Xomp_ns_ord_mgr { + private final Hash_adp__int hash = new Hash_adp__int(); + public Xomp_ns_ord_mgr(int[] ns_id_ary) { + int len = ns_id_ary.length; + for (int i = 0; i < len; ++i) { + hash.Add(ns_id_ary[i], new Int_obj_val(i)); + } + } + public int Get_ord_by_ns_id(int ns_id) {return ((Int_obj_val)hash.Get_by_or_fail(ns_id)).Val();} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/utls/Xomp_text_db_loader.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/utls/Xomp_text_db_loader.java index a27517de8..7ee1d6a0b 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/utls/Xomp_text_db_loader.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/utls/Xomp_text_db_loader.java @@ -13,3 +13,82 @@ 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.addons.bldrs.mass_parses.parses.utls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; +import gplx.dbs.*; +import gplx.core.ios.*; +import gplx.xowa.wikis.data.tbls.*; +public class Xomp_text_db_loader { + private final Xow_wiki wiki; + private final Ordered_hash text_db_hash = Ordered_hash_.New(); + private final Io_stream_zip_mgr zip_mgr; + public Xomp_text_db_loader(Xow_wiki wiki) { + this.wiki = wiki; + this.zip_mgr = wiki.Utl__zip_mgr(); + } + public void Add(int text_db_id, Xowd_text_bry_owner ppg) { + Xomp_text_db_itm itm = (Xomp_text_db_itm)text_db_hash.Get_by(text_db_id); + if (itm == null) { + itm = new Xomp_text_db_itm(text_db_id); + text_db_hash.Add(text_db_id, itm); + } + itm.Page_list().Add(ppg); + } + public void Load() { + int text_db_hash_len = text_db_hash.Len(); + for (int i = 0; i < text_db_hash_len; ++i) { + Xomp_text_db_itm itm = (Xomp_text_db_itm)text_db_hash.Get_at(i); + Load_list(itm.Text_db_id(), itm.Page_list()); + } + } + private void Load_list(int text_db_id, List_adp list) { + int list_len = list.Len(); + int batch_idx = 0; + Bry_bfr bry = Bry_bfr_.New(); + Ordered_hash page_hash = Ordered_hash_.New(); + byte zip_tid = wiki.Data__core_mgr().Props().Zip_tid_text(); + for (int i = 0; i < list_len; ++i) { + if (batch_idx == 0) { + page_hash.Clear(); + bry.Add_str_a7("SELECT page_id, text_data FROM text WHERE page_id IN ("); + } + + // build WHERE IN for page_ids; EX: "1, 2, 3, 4" + Xowd_text_bry_owner ppg = (Xowd_text_bry_owner)list.Get_at(i); + int page_id = ppg.Page_id(); + if (batch_idx != 0) bry.Add_byte_comma(); + bry.Add_int_variable(page_id); + page_hash.Add(page_id, ppg); + ++batch_idx; + + // load if 255 in list, or last + if ( batch_idx % 255 == 0 + || i == list_len - 1) { + bry.Add_byte(Byte_ascii.Paren_end); + Load_from_text_db(page_hash, zip_tid, text_db_id, bry.To_str_and_clear()); + batch_idx = 0; + } + } + } + private void Load_from_text_db(Ordered_hash page_hash, byte zip_tid, int text_db_id, String sql) { + Db_conn text_conn = wiki.Data__core_mgr().Dbs__get_by_id_or_fail(text_db_id).Conn(); + Db_rdr rdr = text_conn.Stmt_sql(sql).Exec_select__rls_auto(); // ANSI.Y + try { + while (rdr.Move_next()) { + int page_id = rdr.Read_int("page_id"); + byte[] text_data = rdr.Read_bry("text_data"); + text_data = zip_mgr.Unzip(zip_tid, text_data); + Xowd_text_bry_owner ppg = (Xowd_text_bry_owner)page_hash.Get_by(page_id); + ppg.Set_text_bry_by_db(text_data); + } + } + finally { + rdr.Rls(); + // text_conn.Rls_conn(); // TOMBSTONE: causes strange errors in tables; DATE:2016-07-06 + } + } +} +class Xomp_text_db_itm { + public Xomp_text_db_itm(int text_db_id) {this.text_db_id = text_db_id;} + public int Text_db_id() {return text_db_id;} private final int text_db_id; + public List_adp Page_list() {return page_list;} private final List_adp page_list = List_adp_.New(); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/utls/Xomp_tmpl_cache_bldr.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/utls/Xomp_tmpl_cache_bldr.java index a27517de8..9a408d825 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/utls/Xomp_tmpl_cache_bldr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/utls/Xomp_tmpl_cache_bldr.java @@ -13,3 +13,90 @@ 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.addons.bldrs.mass_parses.parses.utls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; +import gplx.dbs.*; +import gplx.xowa.wikis.caches.*; +public class Xomp_tmpl_cache_bldr { + public static Xow_page_cache New(Xowe_wiki wiki, boolean fill_all) { + Xow_page_cache rv = new Xow_page_cache(wiki); + if (fill_all) Fill_all(rv, wiki); + return rv; + } + private static void Fill_all(Xow_page_cache cache, Xowe_wiki wiki) { + String sql = String_.Concat_lines_nl_skip_last // ANSI.Y + ( "SELECT pp.page_id" + , ", pp.page_namespace" + , ", pp.page_title" + , ", pp.page_text_db_id" + , ", pp.page_redirect_id" + , "FROM page pp" + , "WHERE pp.page_namespace IN (8, 10, 828)" + ); + + Xomp_text_db_loader text_db_loader = new Xomp_text_db_loader(wiki); + + // load pages + int count = 0; + List_adp redirect_list = List_adp_.New(); + Ordered_hash page_regy = Ordered_hash_.New(); + Db_rdr rdr = wiki.Data__core_mgr().Db__core().Tbl__page().Conn().Stmt_sql(sql).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + // get ttl + Xoa_ttl page_ttl = wiki.Ttl_parse(rdr.Read_int("page_namespace"), rdr.Read_bry_by_str("page_title")); + + // add to text_db_loader + int page_id = rdr.Read_int("page_id"); + int page_redirect_id = rdr.Read_int("page_redirect_id"); + Xow_page_cache_itm itm = new Xow_page_cache_itm(true, page_ttl, null, null); // NOTE: "null, null;" b/c GetContent in Scrib_title checks specifically for null, not empty String; DATE:2016-10-19 + itm.Set_page_ids(page_id, page_redirect_id); + text_db_loader.Add(rdr.Read_int("page_text_db_id"), itm); + + // ignore duplicate page_titles in cache; EX:ru.n:Модуль:Weather/data DATE:2017-03-16 + if (cache.Get_or_null(page_ttl.Full_db()) == null) { + cache.Add(page_ttl.Full_db(), itm); + } + else { + Gfo_usr_dlg_.Instance.Warn_many("", "", "mass_parse: ignoring duplicate page title in page cache; title=~{0} id=~{1}", page_ttl.Full_db(), page_id); + } + + page_regy.Add(page_id, itm); + + if (page_redirect_id != -1) + redirect_list.Add(itm); + if ((++count % 10000) == 0) + Gfo_usr_dlg_.Instance.Prog_many("", "", "loading tmpls: ~{0}", count); + } + } finally {rdr.Rls();} + + // load wikitext + text_db_loader.Load(); + + // handle redirects + int redirect_len = redirect_list.Len(); + for (int i = 0; i < redirect_len; ++i) { + Xow_page_cache_itm src_itm = (Xow_page_cache_itm)redirect_list.Get_at(i); + Xow_page_cache_itm trg_itm = (Xow_page_cache_itm)page_regy.Get_by(src_itm.Redirect_id()); + byte[] trg_itm_wtxt = null; + Xoa_ttl trg_ttl = null; + if (trg_itm == null) { // template can redirect to non-template pages + Xoa_ttl src_ttl = src_itm.Ttl(); + Xoae_page wpg = Xoae_page.New(wiki, src_ttl); + wiki.Data_mgr().Load_from_db(wpg, src_ttl.Ns(), src_ttl, false); + if (wpg.Db().Page().Exists()) { + trg_itm_wtxt = wpg.Db().Text().Text_bry(); + trg_ttl = wpg.Ttl(); + } + else { + Gfo_usr_dlg_.Instance.Prog_many("", "", "missing redirect for tmpl: ~{0}", src_itm.Ttl().Full_db()); + continue; + } + } + else { + trg_itm_wtxt = trg_itm.Wtxt__direct(); + trg_ttl = trg_itm.Ttl(); + } + src_itm.Set_redirect(trg_ttl, trg_itm_wtxt); // NOTE: itm must have title of redirect, not original item; EX:Template:Ifempty -> Template:If_empty; DATE:2016-07-26 + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/wkrs/Xob_hdump_tbl_retriever__xomp.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/wkrs/Xob_hdump_tbl_retriever__xomp.java index a27517de8..47a689532 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/wkrs/Xob_hdump_tbl_retriever__xomp.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/wkrs/Xob_hdump_tbl_retriever__xomp.java @@ -13,3 +13,19 @@ 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.addons.bldrs.mass_parses.parses.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; +import gplx.dbs.*; +import gplx.xowa.wikis.nss.*; import gplx.xowa.htmls.core.bldrs.*; import gplx.xowa.htmls.core.dbs.*; +class Xob_hdump_tbl_retriever__xomp implements Xob_hdump_tbl_retriever { + private final Db_conn conn; + private final Xowd_html_tbl tbl; + public Xob_hdump_tbl_retriever__xomp(Xowd_html_tbl tbl) { + this.tbl = tbl; + this.conn = tbl.Conn(); + } + public Xowd_html_tbl Get_html_tbl(Xow_ns ns, int prv_row_len) { + return tbl; + } + public void Commit() {conn.Txn_sav();} + public void Rls_all() {conn.Txn_sav(); conn.Rls_conn();} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/wkrs/Xomp_parse_wkr.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/wkrs/Xomp_parse_wkr.java index a27517de8..11f1d1984 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/wkrs/Xomp_parse_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/parses/wkrs/Xomp_parse_wkr.java @@ -13,3 +13,183 @@ 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.addons.bldrs.mass_parses.parses.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; +import gplx.dbs.*; import gplx.xowa.addons.bldrs.mass_parses.dbs.*; +import gplx.xowa.files.origs.*; +import gplx.xowa.htmls.core.bldrs.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.logs.*; +import gplx.xowa.addons.bldrs.mass_parses.parses.mgrs.*; import gplx.xowa.addons.bldrs.mass_parses.parses.utls.*; import gplx.xowa.addons.bldrs.mass_parses.parses.*; import gplx.xowa.addons.bldrs.mass_parses.parses.pools.*; +import gplx.xowa.addons.wikis.fulltexts.indexers.bldrs.*; +public class Xomp_parse_wkr implements Gfo_invk { + // mgr vars + private final Xomp_parse_mgr mgr; + private final Xomp_mgr_db mgr_db; + private final Xomp_prog_mgr prog_mgr; + private final Xomp_page_pool page_pool; + private final Xof_orig_wkr file_orig_wkr; + private final Xomp_ns_ord_mgr ns_ord_mgr; + + // cfg vars + private final Xomp_parse_mgr_cfg cfg; + private int cleanup_interval, commit_interval; + private boolean log_file_lnkis; + + // wkr vars + private final Xowe_wiki wiki; + private final Xob_hdump_bldr hdump_bldr = new Xob_hdump_bldr(); + private final int uid; + private Xomp_wkr_db wkr_db; + + private final Xofulltext_indexer_wkr indexer; + + private final List_adp list = List_adp_.New(); private int list_idx = 0, list_len = 0; + private int done_count; private long done_time; + public Xomp_parse_wkr(Xomp_parse_mgr mgr, Xomp_parse_mgr_cfg cfg + , Xomp_mgr_db mgr_db, Xomp_page_pool page_pool + , Xomp_prog_mgr prog_mgr, Xof_orig_wkr file_orig_wkr, Xomp_ns_ord_mgr ns_ord_mgr + , Xowe_wiki wiki, Xofulltext_indexer_wkr indexer, int uid) { + // mgr vars + this.mgr = mgr; this.mgr_db = mgr_db; + this.page_pool = page_pool; this.prog_mgr = prog_mgr; this.file_orig_wkr = file_orig_wkr; + this.ns_ord_mgr = ns_ord_mgr; + this.indexer = indexer; + + // cfg vars + this.cfg = cfg; + this.cleanup_interval = cfg.Cleanup_interval(); + this.commit_interval = cfg.Commit_interval(); + this.log_file_lnkis = cfg.Log_file_lnkis(); + + // wkr-specific vars + this.wiki = wiki; this.uid = uid; + this.wkr_db = Xomp_wkr_db.New(Xomp_mgr_db.New__url(wiki), uid); + } + public void Exec() { + // init + Xow_parser_mgr parser_mgr = wiki.Parser_mgr(); + + // disable file download + wiki.File_mgr().Init_file_mgr_by_load(wiki); // must happen after fsdb.make + wiki.File__bin_mgr().Wkrs__del(gplx.xowa.files.bins.Xof_bin_wkr_.Key_http_wmf); // must happen after init_file_mgr_by_load; remove wmf wkr, else will try to download images during parsing + wiki.File__orig_mgr().Wkrs__set(file_orig_wkr); + wiki.File_mgr().Fsdb_mode().Tid__v2__mp__y_(); + + // enable disable categories according to flag + wiki.Html_mgr().Page_wtr_mgr().Wkr(gplx.xowa.wikis.pages.Xopg_page_.Tid_read).Ctgs_enabled_(cfg.Hdump_catboxs()); + + // enable lnki_temp + Xomp_lnki_temp_wkr logger = null; + if (log_file_lnkis) { + logger = new Xomp_lnki_temp_wkr(wiki, wkr_db.Conn()); + parser_mgr.Ctx().Lnki().File_logger_(logger); + logger.Bgn(); + } + + // init log_mgr / property_wkr + Xop_log_wkr_factory wkr_factory = new Xop_log_wkr_factory(wkr_db.Conn()); + if (cfg.Log_math()) wiki.Parser_mgr().Math__core().Log_wkr_(wkr_factory); + + // enable hdump + hdump_bldr.Enabled_(cfg.Hdump_enabled()).Hzip_enabled_(cfg.Hzip_enabled()).Hzip_diff_(cfg.Hdiff_enabled()).Zip_tid_(cfg.Zip_tid()); + hdump_bldr.Init(wiki, wkr_db.Conn(), new Xob_hdump_tbl_retriever__xomp(wkr_db.Html_tbl())); + wkr_db.Conn().Txn_bgn("xomp"); + + // set status to running + mgr_db.Tbl__wkr().Update_status(uid, Xomp_wkr_tbl.Status__running); + + // main loop + int prv_ns = -1; + while (true) { + // get page from page pool + Xomp_page_itm ppg = Get_next(); + if (ppg == Xomp_page_itm.Null) { + mgr_db.Tbl__wkr().Update_status(uid, Xomp_wkr_tbl.Status__sleeping); + break; // no more pages + } + if (ppg.Text() == null) continue; // some pages have no text; ignore them else null ref; PAGE: it.d:miercuri DATE:2015-12-05 + + try { + // init page + long done_bgn = gplx.core.envs.System_.Ticks(); + int cur_ns = ppg.Ns_id(); + Xoa_ttl ttl = wiki.Ttl_parse(cur_ns, ppg.Ttl_bry()); + // if ns changed and prv_ns is main + if (cur_ns != prv_ns) { + if (prv_ns == gplx.xowa.wikis.nss.Xow_ns_.Tid__main) + wiki.Cache_mgr().Free_mem__all(); // NOTE: clears page and wbase cache only; needed else OutOfMemory error for en.w in 25th hour; DATE:2017-01-11 + prv_ns = cur_ns; + } + Xoae_page wpg = Xoae_page.New(wiki, ttl); + wpg.Bldr__ns_ord_(ns_ord_mgr.Get_ord_by_ns_id(cur_ns)); // NOTE: must set ns_id for tier_id in lnki_temp; DATE:2016-09-19 + wpg.Db().Text().Text_bry_(ppg.Text()); + wpg.Db().Page().Init_by_mp(ppg.Id(), ppg.Page_score()); + + // parse page + Xop_ctx pctx = parser_mgr.Ctx(); + pctx.Clear_all(); + parser_mgr.Parse(wpg, true); + + // gen_html + hdump_bldr.Insert(pctx, wpg); + + // index + if (indexer != null) indexer.Index(wpg); + + // mark done for sake of progress + prog_mgr.Mark_done(ppg.Id()); + + // update stats + long time_cur = gplx.core.envs.System_.Ticks(); + done_time += time_cur - done_bgn; + done_bgn = time_cur; + ++done_count; + + // cleanup + // ctx.App().Utl__bfr_mkr().Clear_fail_check(); // make sure all bfrs are released + if (wiki.Cache_mgr().Tmpl_result_cache().Count() > 50000) + wiki.Cache_mgr().Tmpl_result_cache().Clear(); + if (done_count % cleanup_interval == 0) { + wiki.Cache_mgr().Free_mem__page(); + wiki.Parser_mgr().Scrib().Core_term(); + wiki.Appe().Wiki_mgr().Wdata_mgr().Clear(); + } + if (done_count % commit_interval == 0) + wkr_db.Conn().Txn_sav(); + } catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "mass_parse.fail:ns=~{0} ttl=~{1} err=~{2}", ppg.Ns_id(), ppg.Ttl_bry(), Err_.Message_gplx_log(e)); + } + } + + // cleanup + if (logger != null) logger.End(); + wkr_db.Conn().Txn_end(); + wkr_db.Conn().Rls_conn(); + mgr.Wkrs_done_add_1(); // NOTE: must release latch last else thread errors + } + public void Bld_stats(Bry_bfr bfr) { + int done_time_in_sec = (int)(done_time / 1000); if (done_time_in_sec == 0) done_time_in_sec = 1; + bfr.Add_int_pad_bgn(Byte_ascii.Space, 4, uid ); + bfr.Add_int_pad_bgn(Byte_ascii.Space, 8, (int)(done_count / done_time_in_sec)); + bfr.Add_int_pad_bgn(Byte_ascii.Space, 8, done_count); + bfr.Add_int_pad_bgn(Byte_ascii.Space, 8, done_time_in_sec); + bfr.Add_byte_nl(); + } + private Xomp_page_itm Get_next() { + if (list_idx == list_len) { + mgr_db.Tbl__wkr().Update_exec(uid, done_count, done_time); + list.Clear(); + page_pool.Get_next(mgr_db, cfg.Wkr_machine_name(), list); + list_len = list.Len(); + if (list_len == 0) return Xomp_page_itm.Null; + list_idx = 0; + } + return (Xomp_page_itm)list.Get_at(list_idx++); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__exec)) this.Exec(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk__exec = "exec"; + public static final String Cfg__ns_ids = "xomp.ns_ids"; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/resumes/Xomp_resume_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/resumes/Xomp_resume_cmd.java index a27517de8..eb7d57e50 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/resumes/Xomp_resume_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/resumes/Xomp_resume_cmd.java @@ -13,3 +13,15 @@ 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.addons.bldrs.mass_parses.resumes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xomp_resume_cmd extends Xob_cmd__base { + public Xomp_resume_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + new Xomp_resume_mgr().Exec(wiki); + } + + @Override public String Cmd_key() {return BLDR_CMD_KEY;} private static final String BLDR_CMD_KEY = "wiki.mass_parse.resume"; + public static final Xob_cmd Prototype = new Xomp_resume_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xomp_resume_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/resumes/Xomp_resume_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/resumes/Xomp_resume_mgr.java index a27517de8..464441536 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/resumes/Xomp_resume_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/mass_parses/resumes/Xomp_resume_mgr.java @@ -13,3 +13,34 @@ 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.addons.bldrs.mass_parses.resumes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.mass_parses.*; +import gplx.dbs.*; import gplx.dbs.utls.*; +import gplx.xowa.addons.bldrs.mass_parses.dbs.*; +class Xomp_resume_mgr { + public void Exec(Xowe_wiki wiki) { + // init + Xomp_mgr_db mgr_db = Xomp_mgr_db.New__load(wiki); + Db_conn mgr_conn = mgr_db.Conn(); + + // clear out page_status + Gfo_usr_dlg_.Instance.Prog_many("", "", "xomp_resume:clearing status"); + mgr_conn.Exec_sql("UPDATE xomp_page SET page_status = 0, xomp_wkr_id = -1"); + + // update mgr.xomp_page.status for each row in wkr.html + Db_attach_mgr attach_mgr = new Db_attach_mgr(mgr_conn); + int wkrs_len = mgr_db.Tbl__wkr().Select_count(); + for (int i = 0; i < wkrs_len; ++i) { + Gfo_usr_dlg_.Instance.Prog_many("", "", "xomp_resume:updating status; wkr=~{0}", i); + Xomp_wkr_db wkr_db = Xomp_wkr_db.New(mgr_db.Dir(), i); + attach_mgr.Conn_links_(new Db_attach_itm("wkr_db", wkr_db.Conn())); + String sql = Db_sql_.Make_by_fmt + ( String_.Ary + ( "UPDATE xomp_page" + , "SET page_status = 1" + , ", xomp_wkr_id = {0}" + , "WHERE page_id IN (SELECT page_id FROM html)" + ), i); + attach_mgr.Exec_sql(sql); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xob_delete_regy_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xob_delete_regy_tbl.java index a27517de8..ed4c7fd00 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xob_delete_regy_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xob_delete_regy_tbl.java @@ -13,3 +13,19 @@ 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.addons.bldrs.updates.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.updates.*; +import gplx.dbs.*; +public class Xob_delete_regy_tbl { + public final String tbl_name = "delete_regy"; + public final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + public final String fld_fil_id, fld_thm_id, fld_reason; + public final Db_conn conn; + public Xob_delete_regy_tbl(Db_conn conn) { + this.conn = conn; + this.fld_fil_id = flds.Add_int("fil_id"); + this.fld_thm_id = flds.Add_int("thm_id"); + this.fld_reason = flds.Add_str("reason", 255); + this.meta = Dbmeta_tbl_itm.New(tbl_name, flds); + } + public Dbmeta_tbl_itm Meta() {return meta;} private final Dbmeta_tbl_itm meta; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_addon.java b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_addon.java index a27517de8..6a67f984d 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_addon.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_addon.java @@ -13,3 +13,19 @@ 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.addons.bldrs.updates.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.updates.*; +import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.addons.bldrs.utils_rankings.bldrs.*; +import gplx.xowa.addons.bldrs.updates.files.find_missings.*; +public class Xodel_addon implements Xoax_addon_itm, Xoax_addon_itm__bldr { + public Xob_cmd[] Bldr_cmds() { + return new Xob_cmd[] + { Xodel_make_cmd.Prototype + , Xodel_exec_cmd.Prototype + , Xodel_small_cmd.Prototype + , Xodel_find_missing_cmd.Prototype + }; + } + + public String Addon__key() {return "xowa.updates.files";} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_exec_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_exec_cmd.java index a27517de8..c3f58b0d6 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_exec_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_exec_cmd.java @@ -13,3 +13,22 @@ 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.addons.bldrs.updates.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.updates.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xodel_exec_cmd extends Xob_cmd__base { + private Io_url deletion_db_url; + public Xodel_exec_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + new Xodel_exec_mgr().Exec_delete(gplx.core.progs.Gfo_prog_ui_.Noop, bldr, deletion_db_url); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__file_)) this.deletion_db_url = Io_url_.new_any_(m.ReadStr("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk__file_ = "file_"; + + public static final String BLDR_CMD_KEY = "fsdb.deletion_db.exec"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xodel_exec_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xodel_exec_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_exec_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_exec_mgr.java index a27517de8..48d8c2d68 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_exec_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_exec_mgr.java @@ -13,3 +13,120 @@ 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.addons.bldrs.updates.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.updates.*; +import gplx.core.progs.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.xowa.wikis.data.*; +import gplx.xowa.bldrs.*; +import gplx.xowa.files.fsdb.*; import gplx.fsdb.meta.*; +public class Xodel_exec_mgr { + public void Exec_delete(Gfo_prog_ui prog_ui, Xob_bldr bldr, Io_url deletion_db_url) { + // get domain bry from deletion_conn's cfg_tbl + Db_conn deletion_conn = Db_conn_bldr.Instance.Get_or_fail(deletion_db_url); + Db_cfg_tbl cfg_tbl = Xowd_cfg_tbl_.Get_or_fail(deletion_conn); + byte[] domain_bry = cfg_tbl.Select_bry("", Xodel_exec_mgr.Cfg__deletion_db__domain); + + // get wiki; init it; do delete + Xowe_wiki wiki = bldr.App().Wiki_mgr().Get_by_or_make(domain_bry); + wiki.Init_assert(); + Delete_by_url(prog_ui, wiki, deletion_conn, cfg_tbl); + + // cleanup + deletion_conn.Rls_conn(); + Io_mgr.Instance.DeleteFil(deletion_db_url); + } + public void Exec_cleanup(Io_url deletion_db_url) { + Db_conn deletion_conn = Db_conn_bldr.Instance.Get_or_fail(deletion_db_url); + Db_cfg_tbl cfg_tbl = Xowd_cfg_tbl_.Get_or_fail(deletion_conn); + cfg_tbl.Delete_val("", Xodel_exec_mgr.Cfg__deletion_db__db_bgn); + } + private void Delete_by_url(Gfo_prog_ui prog_ui, Xowe_wiki wiki, Db_conn deletion_conn, Db_cfg_tbl cfg_tbl) { + // get fsdb_mgr + Xof_fsdb_mgr fsdb_mgr = wiki.File_mgr().Fsdb_mgr(); + Fsm_mnt_itm mnt_itm = fsdb_mgr.Mnt_mgr().Mnts__get_main(); + Db_conn core_conn = mnt_itm.Atr_mgr().Db__core().Conn(); + try { + core_conn.Env_db_attach("delete_db", deletion_conn); + + // loop dbs + int dbs_len = mnt_itm.Bin_mgr().Dbs__len(); + String dbs_len_str = Int_.To_str(dbs_len - Int_.Base1); + int db_bgn = cfg_tbl.Select_int_or("", Cfg__deletion_db__db_bgn, 0); + for (int i = db_bgn; i < dbs_len; ++i) { + if (prog_ui.Prog_notify_and_chk_if_suspended(i, dbs_len)) return; + Fsm_bin_fil bin_db = mnt_itm.Bin_mgr().Dbs__get_at(i); + Delete_by_db(core_conn, bin_db, dbs_len_str); + cfg_tbl.Upsert_int("", Cfg__deletion_db__db_bgn, i + 1); + } + } finally {core_conn.Env_db_detach("delete_db");} + } + private void Delete_by_db(Db_conn deletion_conn, Fsm_bin_fil bin_db, String dbs_len_str) { + Gfo_usr_dlg usr_dlg = Xoa_app_.Usr_dlg(); + + // get rows to delete in db + List_adp list = List_adp_.New(); + int bin_db_id = bin_db.Id(); + String bin_db_id_str = Int_.To_str(bin_db_id); + usr_dlg.Prog_many("", "", "processing files for deletion in database " + bin_db_id_str + " of " + dbs_len_str); + Db_rdr rdr = deletion_conn.Exec_rdr(String_.Concat_lines_nl + ( "SELECT ff.fil_id AS item_id" + , ", 1 AS item_is_orig" + , "FROM fsdb_fil ff" + , "JOIN delete_db.delete_regy dr ON ff.fil_id = dr.fil_id AND dr.thm_id = -1" + , "WHERE ff.fil_bin_db_id = " + bin_db_id_str + , "UNION" + , "SELECT ft.thm_id AS item_id" + , ", 0 AS item_is_orig" + , "FROM fsdb_thm ft" + , " JOIN delete_db.delete_regy dr ON ft.thm_owner_id = dr.fil_id AND ft.thm_id = dr.thm_id" + , "WHERE ft.thm_bin_db_id = " + bin_db_id_str + )); + try { + while (rdr.Move_next()) { + int item_id = rdr.Read_int("item_id"); + int item_is_orig = rdr.Read_int("item_is_orig"); + list.Add(new Xodel_prune_itm(item_id, item_is_orig == 1)); + } + } finally {rdr.Rls();} + + int len = list.Count(); + if (len == 0) return; // no files; exit, else will vacuum below + + deletion_conn.Env_db_attach("bin_db", bin_db.Conn()); + deletion_conn.Txn_bgn("img_prune__" + bin_db_id_str); + + // delete bin + Db_stmt delete_bin_stmt = deletion_conn.Stmt_sql("DELETE FROM bin_db.fsdb_bin WHERE bin_owner_id = ?"); + for (int i = 0; i < len; ++i) { + Xodel_prune_itm itm = (Xodel_prune_itm)list.Get_at(i); + delete_bin_stmt.Clear().Crt_int("bin_owner_id", itm.Item_id); + delete_bin_stmt.Exec_delete(); + + if (i % 10000 == 0) usr_dlg.Prog_many("", "", "deleting data in database " + bin_db_id_str + " of " + dbs_len_str); + } + delete_bin_stmt.Rls(); + + // delete meta + Db_stmt delete_fil_stmt = deletion_conn.Stmt_sql("DELETE FROM fsdb_fil WHERE fil_id = ?"); + Db_stmt delete_thm_stmt = deletion_conn.Stmt_sql("DELETE FROM fsdb_thm WHERE thm_id = ?"); + for (int i = 0; i < len; ++i) { + Xodel_prune_itm itm = (Xodel_prune_itm)list.Get_at(i); + if (itm.Item_is_orig) { + delete_fil_stmt.Clear().Crt_int("fil_id", itm.Item_id); + delete_fil_stmt.Exec_delete(); + } + else { + delete_thm_stmt.Clear().Crt_int("thm_id", itm.Item_id); + delete_thm_stmt.Exec_delete(); + } + if (i % 10000 == 0) usr_dlg.Prog_many("", "", "deleting meta in database " + bin_db_id_str + " of " + dbs_len_str); + } + delete_fil_stmt.Rls(); + delete_thm_stmt.Rls(); + + // cleanup + deletion_conn.Txn_end(); + deletion_conn.Env_db_detach("bin_db"); + bin_db.Conn().Env_vacuum(); + } + public static final String Cfg__deletion_db__domain = "file.deletion_db.domain", Cfg__deletion_db__db_bgn = "file.deletion_db.db_bgn"; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_make_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_make_cmd.java index a27517de8..d8e5a534a 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_make_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_make_cmd.java @@ -13,3 +13,17 @@ 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.addons.bldrs.updates.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.updates.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xodel_make_cmd extends Xob_cmd__base implements Xob_cmd { + public Xodel_make_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + wiki.Init_assert(); + new Xodel_make_mgr().Exec(wiki); + } + + public static final String BLDR_CMD_KEY = "file.deletion_db.make"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xodel_make_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xodel_make_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_make_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_make_mgr.java index a27517de8..c88cad1f8 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_make_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_make_mgr.java @@ -13,3 +13,49 @@ 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.addons.bldrs.updates.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.updates.*; +import gplx.dbs.*; +import gplx.xowa.bldrs.*; +class Xodel_make_mgr { + public void Exec(Xow_wiki wiki) { + // mark the records deleted + Db_conn make_conn = Xob_db_file.New__file_make(wiki.Fsys_mgr().Root_dir()).Conn(); + make_conn.Exec_sql_concat_w_msg + ( "marking items deleted" + , "UPDATE fsdb_regy" + , "SET fsdb_deleted = 1" + , "WHERE NOT EXISTS " + , "(" + , "SELECT 1" + , "FROM xfer_regy xr" + , "WHERE xr.lnki_ttl = fsdb_regy.fsdb_name" + , "AND xr.file_is_orig = fsdb_regy.fsdb_is_orig" + // , "AND xr.orig_repo = fsdb_regy.fsdb_repo" // TOMBSTONE: do no reinstate; some images exist in both repos, and this will delete images from one repo; DATE:2016-09-28 + , "AND xr.file_w = fsdb_regy.fsdb_w" + , "AND xr.lnki_time = fsdb_regy.fsdb_time" + , "AND xr.lnki_page = fsdb_regy.fsdb_page" + , ")"); + + // create deletion db + Xob_db_file deletion_db = Xob_db_file.New__deletion_db(wiki); + Db_conn deletion_conn = deletion_db.Conn(); + deletion_db.Tbl__cfg().Upsert_str("", Xodel_exec_mgr.Cfg__deletion_db__domain, wiki.Domain_str()); + + // copy records over to it + Xob_delete_regy_tbl delete_regy_tbl = new Xob_delete_regy_tbl(deletion_conn); + deletion_conn.Meta_tbl_remake(delete_regy_tbl.Meta()); + deletion_conn.Env_db_attach("make_db", make_conn); + deletion_conn.Exec_sql_concat_w_msg + ( "inserting into delete_regy" + , "INSERT INTO delete_regy (fil_id, thm_id, reason)" + , "SELECT fsdb_fil_id" + , ", fsdb_thm_id" + , ", ''" + , "FROM make_db.fsdb_regy" + , "WHERE fsdb_deleted = 1" + , ";" + ); + deletion_conn.Env_db_detach("make_db"); + deletion_conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(delete_regy_tbl.tbl_name, "main", delete_regy_tbl.fld_fil_id, delete_regy_tbl.fld_thm_id)); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_prune_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_prune_itm.java index a27517de8..518319248 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_prune_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_prune_itm.java @@ -13,3 +13,12 @@ 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.addons.bldrs.updates.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.updates.*; +class Xodel_prune_itm { + public Xodel_prune_itm(int item_id, boolean item_is_orig) { + this.Item_id = item_id; + this.Item_is_orig = item_is_orig; + } + public final int Item_id; + public final boolean Item_is_orig; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_small_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_small_cmd.java index a27517de8..75a3d2d62 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_small_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_small_cmd.java @@ -13,3 +13,37 @@ 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.addons.bldrs.updates.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.updates.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.files.*; +public class Xodel_small_cmd extends Xob_cmd__base { + public Xodel_small_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + private final int[] ext_max_ary = Xobldr__fsdb_db__delete_small_files_.New_ext_max_ary(); + @Override public void Cmd_run() { + wiki.Init_assert(); + new Xodel_small_mgr().Exec(wiki, ext_max_ary); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return this;} + + public static final String BLDR_CMD_KEY = "file.deletion_db.small_files"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xodel_small_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xodel_small_cmd(bldr, wiki);} +} +class Xobldr__fsdb_db__delete_small_files_ { + public static int[] New_ext_max_ary() { + int[] rv = new int[Xof_ext_.Id__max]; + Ext_max_(rv, 35, Xof_ext_.Id_svg); + Ext_max_(rv, 40, Xof_ext_.Id_gif); + Ext_max_(rv, 100, Xof_ext_.Id_png, Xof_ext_.Id_jpg, Xof_ext_.Id_jpeg); + Ext_max_(rv, 500, Xof_ext_.Id_tif, Xof_ext_.Id_tiff); + Ext_max_(rv, 500, Xof_ext_.Id_xcf); + Ext_max_(rv, 1000, Xof_ext_.Id_bmp); + Ext_max_(rv, 700, Xof_ext_.Id_webm); + Ext_max_(rv, 1000, Xof_ext_.Id_ogv); + Ext_max_(rv, 400, Xof_ext_.Id_pdf); + Ext_max_(rv, 700, Xof_ext_.Id_djvu); + return rv; + } + private static void Ext_max_(int[] ary, int max, int... exts) {for (int ext : exts) ary[ext] = max;} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_small_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_small_mgr.java index a27517de8..34aa60acc 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_small_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/Xodel_small_mgr.java @@ -13,3 +13,42 @@ 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.addons.bldrs.updates.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.updates.*; +import gplx.dbs.*; +import gplx.xowa.bldrs.*; +import gplx.fsdb.*; import gplx.fsdb.meta.*; import gplx.xowa.files.*; +import gplx.xowa.bldrs.wkrs.*; +class Xodel_small_mgr { + public void Exec(Xowe_wiki wiki, int[] ext_max_ary) { + wiki.Init_assert(); + // get atr_conn + Fsdb_db_mgr db_core_mgr = Fsdb_db_mgr_.new_detect(wiki, wiki.Fsys_mgr().Root_dir(), wiki.Fsys_mgr().File_dir()); + Fsdb_db_file atr_db = db_core_mgr.File__atr_file__at(Fsm_mnt_mgr.Mnt_idx_main); + Db_conn atr_conn = atr_db.Conn(); + + // get deletion_db + Xob_db_file deletion_db = Xob_db_file.New__deletion_db(wiki); + atr_conn.Env_db_attach("deletion_db", deletion_db.Conn()); + + // insert into deletion_db if too small + int len = ext_max_ary.length; + for (int i = 0; i < len; ++i) { + Find_small_files(atr_conn, i, ext_max_ary[i]); + } + + atr_conn.Env_db_detach("deletion_db"); + } + private static void Find_small_files(Db_conn conn, int ext_id, int max) { + String ext_name = String_.new_u8(Xof_ext_.Get_ext_by_id_(ext_id)); + String reason = "small:" + ext_name; + conn.Exec_sql_concat_w_msg + ( String_.Format("finding small files; ext={0} max={1}", ext_name, max) + , "INSERT INTO deletion_db.delete_regy (fil_id, thm_id, reason)" + , "SELECT t.thm_owner_id, t.thm_id, '" + reason + "'" + , "FROM fsdb_thm t" + , " JOIN fsdb_fil f ON t.thm_owner_id = f.fil_id" + , "WHERE f.fil_ext_id = " + Int_.To_str(ext_id) + , "AND t.thm_size <= " + Int_.To_str(max) + ); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/find_missings/Xodel_find_missing_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/find_missings/Xodel_find_missing_cmd.java index a27517de8..1e21f7279 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/find_missings/Xodel_find_missing_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/find_missings/Xodel_find_missing_cmd.java @@ -13,3 +13,17 @@ 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.addons.bldrs.updates.files.find_missings; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.updates.*; import gplx.xowa.addons.bldrs.updates.files.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xodel_find_missing_cmd extends Xob_cmd__base implements Xob_cmd { + public Xodel_find_missing_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + wiki.Init_assert(); + new Xodel_find_missing_mgr().Exec(wiki); + } + + public static final String BLDR_CMD_KEY = "file.deletion_db.find_missing"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xodel_find_missing_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xodel_find_missing_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/find_missings/Xodel_find_missing_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/find_missings/Xodel_find_missing_mgr.java index a27517de8..ff947cf9b 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/find_missings/Xodel_find_missing_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/updates/files/find_missings/Xodel_find_missing_mgr.java @@ -13,3 +13,82 @@ 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.addons.bldrs.updates.files.find_missings; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.updates.*; import gplx.xowa.addons.bldrs.updates.files.*; +import gplx.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.addons.bldrs.files.dbs.*; +class Xodel_find_missing_mgr { + public void Exec(Xow_wiki wiki) { + // get page_file_map; note that it must have fsdb_regy + Db_conn pfm_conn = Xob_db_file.New__page_file_map(wiki).Conn(); + + // attach page_db + Db_attach_mgr attach_mgr = new Db_attach_mgr(pfm_conn, new Db_attach_itm("page_db", wiki.Data__core_mgr().Tbl__page().Conn())); + Page_file_map_tbl pfm_tbl = new Page_file_map_tbl(pfm_conn, "page_file_map_found"); + pfm_conn.Meta_tbl_remake(pfm_tbl); + attach_mgr.Exec_sql_w_msg("generating page_file_map_found", Db_sql_.Make_by_fmt(String_.Ary + ( "INSERT INTO page_file_map_found (page_id, fil_id, thm_id, sort_id, count_of)" + , "SELECT pfm.page_id, pfm.fil_id, pfm.thm_id, pfm.sort_id, pfm.count_of" + , "FROM page_file_map pfm" + , " JOIN page p ON pfm.page_id = p.page_id" + ))); + pfm_conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl("page_file_map_found", "fil_id__thm_id", "fil_id", "thm_id")); + + // update fsdb_deleted + pfm_conn.Exec_sql("", Db_sql_.Make_by_fmt(String_.Ary + ( "UPDATE fsdb_regy" + , "SET fsdb_deleted = 1" + , "WHERE NOT EXISTS " + , "(" + , "SELECT 1" + , "FROM page_file_map_found pfm" + , "WHERE pfm.fil_id = fsdb_regy.fsdb_fil_id" + , "AND pfm.thm_id = fsdb_regy.fsdb_thm_id" + , ")" + ))); + + // create deletion db + Xob_db_file deletion_db = Xob_db_file.New__deletion_db(wiki); + Db_conn deletion_conn = deletion_db.Conn(); + deletion_db.Tbl__cfg().Upsert_str("", Xodel_exec_mgr.Cfg__deletion_db__domain, wiki.Domain_str()); + + // copy records over to it + Xob_delete_regy_tbl delete_regy_tbl = new Xob_delete_regy_tbl(deletion_conn); + deletion_conn.Meta_tbl_remake(delete_regy_tbl.Meta()); + deletion_conn.Env_db_attach("make_db", pfm_conn); + deletion_conn.Exec_sql_concat_w_msg + ( "inserting into delete_regy" + , "INSERT INTO delete_regy (fil_id, thm_id, reason)" + , "SELECT fsdb_fil_id" + , ", fsdb_thm_id" + , ", ''" + , "FROM make_db.fsdb_regy" + , "WHERE fsdb_deleted = 1" + , ";" + ); + deletion_conn.Env_db_detach("make_db"); + deletion_conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(delete_regy_tbl.tbl_name, "main", delete_regy_tbl.fld_fil_id, delete_regy_tbl.fld_thm_id)); + } +/* +--create fsdb_regy in pfm_db +CREATE TABLE fsdb_regy +( fsdb_id integer NOT NULL PRIMARY KEY AUTOINCREMENT +, fsdb_name varchar(255) NOT NULL +, fsdb_is_orig tinyint NOT NULL +, fsdb_repo tinyint NOT NULL +, fsdb_w integer NOT NULL +, fsdb_time double NOT NULL +, fsdb_page integer NOT NULL +, fsdb_db_id integer NOT NULL +, fsdb_size bigint NOT NULL +, fsdb_status tinyint NOT NULL +, fsdb_fil_id integer NOT NULL +, fsdb_thm_id integer NOT NULL +, fsdb_deleted tinyint NOT NULL +); + +--export fsdb_regy from file.make +ATTACH 'en.wikipedia.org-file-page_map.xowa' AS pfm_db; +INSERT INTO pfm_db.fsdb_regy SELECT * FROM fsdb_regy; +CREATE INDEX fsdb_regy__main ON fsdb_regy (fsdb_fil_id, fsdb_thm_id); +*/ +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/utils_rankings/Xoax_builds_utils_rankings_addon.java b/400_xowa/src/gplx/xowa/addons/bldrs/utils_rankings/Xoax_builds_utils_rankings_addon.java index a27517de8..2d941465d 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/utils_rankings/Xoax_builds_utils_rankings_addon.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/utils_rankings/Xoax_builds_utils_rankings_addon.java @@ -13,3 +13,15 @@ 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.addons.bldrs.utils_rankings; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.addons.bldrs.utils_rankings.bldrs.*; +public class Xoax_builds_utils_rankings_addon implements Xoax_addon_itm, Xoax_addon_itm__bldr { + public Xob_cmd[] Bldr_cmds() { + return new Xob_cmd[] + { Sqlite_percentile_cmd.Prototype + }; + } + + public String Addon__key() {return "xowa.builds.utils_rankings";} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/utils_rankings/bldrs/Sqlite_percentile_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/utils_rankings/bldrs/Sqlite_percentile_cmd.java index a27517de8..a9d94639f 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/utils_rankings/bldrs/Sqlite_percentile_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/utils_rankings/bldrs/Sqlite_percentile_cmd.java @@ -13,3 +13,76 @@ 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.addons.bldrs.utils_rankings.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.utils_rankings.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Sqlite_percentile_cmd extends Xob_cmd__base implements Xob_cmd { + private String db_rel_url, tbl_name = "tmp_score"; private int score_max = 100000000; private String select_sql; + private Db_conn conn; + public Sqlite_percentile_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + public Sqlite_percentile_cmd Init_by_rel_url(String db_rel_url, String tbl_name, int score_max, String select_sql) { + this.db_rel_url = db_rel_url; this.tbl_name = tbl_name; this.score_max = score_max; this.select_sql = select_sql; + return this; + } + public Sqlite_percentile_cmd Init_by_conn(Db_conn conn, String tbl_name, int score_max, String select_sql) {this.conn = conn; return this.Init_by_rel_url(null, tbl_name, score_max, select_sql);} + public int count; + @Override public void Cmd_run() { + wiki.Init_assert(); + if (conn == null) { + if (db_rel_url == null) throw Err_.new_("bldr", "db_rel_url can not be empty; EX: 'xowa.page_rank.sqlite3'"); + conn = Db_conn_bldr.Instance.Get_or_autocreate(false, wiki.Fsys_mgr().Root_dir().GenSubFil(db_rel_url)); + } + Xoa_app_.Usr_dlg().Prog_many("", "", "creating temp_table: tbl=~{0}", tbl_name); + conn.Meta_tbl_delete(tbl_name); + conn.Meta_tbl_create + ( Dbmeta_tbl_itm.New(tbl_name + , Dbmeta_fld_itm.new_int("row_rank").Primary_y_().Autonum_y_() + , Dbmeta_fld_itm.new_int("row_key") + , Dbmeta_fld_itm.new_double("row_val") + , Dbmeta_fld_itm.new_double("row_score").Default_(-1) + )); + Xoa_app_.Usr_dlg().Prog_many("", "", "filling temp_table: tbl=~{0} sql=~{1}", tbl_name, select_sql); + new Db_attach_mgr(conn, new Db_attach_itm("page_db", wiki.Data__core_mgr().Tbl__page().Conn())) + .Exec_sql(Bry_fmt.Make_str("INSERT INTO ~{tbl} (row_key, row_val) ~{select}", tbl_name, select_sql)); + Xoa_app_.Usr_dlg().Prog_many("", "", "updating row_score: tbl=~{0}", tbl_name); + String score_max_as_str = Dbmeta_fld_itm.To_double_str_by_int(score_max); + this.count = conn.Exec_select_as_int("SELECT Count(*) FROM " + tbl_name, -1); if (count == -1) throw Err_.new_("bldr", "failed to get count; tbl=~{0}", tbl_name); + String count_as_str = Dbmeta_fld_itm.To_double_str_by_int(count); + conn.Exec_sql(Bry_fmt.Make_str("UPDATE ~{tbl} SET row_score = (row_rank * ~{score_max}) / ~{count}", tbl_name, score_max_as_str, count_as_str)); + Xoa_app_.Usr_dlg().Prog_many("", "", "resolving ties: tbl=~{0}", tbl_name); + conn.Meta_tbl_remake + ( Dbmeta_tbl_itm.New(tbl_name + "_avg" + , Dbmeta_fld_itm.new_double("row_val").Primary_y_() + , Dbmeta_fld_itm.new_double("row_score") + )); + conn.Exec_sql(Bry_fmt.Make_str(String_.Concat_lines_nl_skip_last + ( "INSERT INTO ~{tbl}_avg (row_val, row_score)" + , "SELECT row_val" + , ", (Avg(row_rank) * ~{score_max} / ~{count}) AS row_score" + , "FROM ~{tbl}" + , "GROUP BY row_val" + , "HAVING Count(row_val > 1)" + ), tbl_name, score_max_as_str, count_as_str)); + conn.Exec_sql(Bry_fmt.Make_str(String_.Concat_lines_nl_skip_last + ( "UPDATE ~{tbl}" + , "SET row_score = (SELECT row_score FROM ~{tbl}_avg t2 WHERE t2.row_val = ~{tbl}.row_val)" + , "WHERE row_val IN (SELECT row_val FROM ~{tbl}_avg t2)" + ), tbl_name)); + conn.Meta_tbl_delete(tbl_name + "_avg"); + conn.Meta_idx_create(Xoa_app_.Usr_dlg(), Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "row_score", "row_key", "row_score")); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__db_rel_url_)) db_rel_url = m.ReadStr("v"); + else if (ctx.Match(k, Invk__select_sql_)) select_sql = m.ReadStr("v"); + else if (ctx.Match(k, Invk__tbl_name_)) tbl_name = m.ReadStr("v"); + else if (ctx.Match(k, Invk__score_max_)) score_max = m.ReadInt("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk__db_rel_url_ = "db_rel_url_", Invk__select_sql_ = "select_sql_", Invk__tbl_name_ = "tbl_name_", Invk__score_max_ = "score_max_"; + + public static final String BLDR_CMD_KEY = "util.sqlite.normalize"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Sqlite_percentile_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Sqlite_percentile_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/utils_rankings/bldrs/Statistic_calculator.java b/400_xowa/src/gplx/xowa/addons/bldrs/utils_rankings/bldrs/Statistic_calculator.java index a27517de8..fcc81214a 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/utils_rankings/bldrs/Statistic_calculator.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/utils_rankings/bldrs/Statistic_calculator.java @@ -13,3 +13,27 @@ 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.addons.bldrs.utils_rankings.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.utils_rankings.*; +class Statistic_calculator { + private int count; + private double old_avg, cur_avg, old_sum, cur_sum; + public int Count() {return count;} + public double Avg() {return (count > 0) ? cur_avg : 0;} + public double Variance() {return (count > 1) ? cur_sum / (count - 1) : 0;} + public double Stddev() {return Math_.Sqrt(Variance());} + public void Clear() {count = 0;} + public void Push(double x) { + count++; + if (count == 1) { + old_avg = cur_avg = x; + old_sum = 0.0; + } + else { + cur_avg = old_avg + ((x - old_avg) / count); + cur_sum = old_sum + ((x - old_avg) * (x - cur_avg)); + + old_avg = cur_avg; + old_sum = cur_sum; + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/utils_rankings/bldrs/Str_ary_.java b/400_xowa/src/gplx/xowa/addons/bldrs/utils_rankings/bldrs/Str_ary_.java index a27517de8..31ef5213f 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/utils_rankings/bldrs/Str_ary_.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/utils_rankings/bldrs/Str_ary_.java @@ -13,3 +13,21 @@ 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.addons.bldrs.utils_rankings.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.utils_rankings.*; +class Str_ary_ { + public static String[][] To_str_ary_ary(String v, String val_dlm, String row_dlm) {// "a|b|c`" + String[] rows_ary = String_.Split(v, row_dlm); + int rv_len = rows_ary.length; + String[][] rv = new String[rv_len][]; + for (int i = 0; i < rv_len; ++i) { + String row = rows_ary[i]; + String[] vals_ary = String_.Split(row, val_dlm); + int vals_len = vals_ary.length; + String[] rv_row = new String[vals_len]; + rv[i] = rv_row; + for (int j = 0; j < vals_len; ++j) + rv[i][j] = vals_ary[j]; + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_make_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_make_itm.java index a27517de8..9c05aa655 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_make_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_make_itm.java @@ -13,3 +13,22 @@ 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.addons.bldrs.volumes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.core.brys.*; +class Volume_make_itm implements Bry_bfr_able { + public int Uid = 0; + public int Prep_id = 0; + public int Item_type = 0; // 0=page;1=thm + public int Item_id = 0; // either page_id or fsdb_id + public String Item_name = ""; // friendly-name + public byte[] Item_ttl = Bry_.Empty; // actual name + public long Item_size = 0; // size of page / file + public void To_bfr(Bry_bfr bfr) { + } +} +/* +1|p|Earth|123|100 +1|f|Earth.png|124|200 +1|f|Moon.png|125|300 +4|p|Sun|123|100 +*/ diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_page_loader__wiki.java b/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_page_loader__wiki.java index a27517de8..17c2e71bf 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_page_loader__wiki.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_page_loader__wiki.java @@ -13,3 +13,49 @@ 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.addons.bldrs.volumes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.xowa.parsers.lnkis.*; import gplx.xowa.wikis.nss.*; +interface Volume_page_loader { + boolean Load(Volume_page_itm rv, byte[] ttl); +} +class Volume_page_loader__wiki implements Volume_page_loader { + private final Xowe_wiki wiki; + public Volume_page_loader__wiki(Xowe_wiki wiki) {this.wiki = wiki;} + public boolean Load(Volume_page_itm rv, byte[] ttl) { + Xoa_ttl page_ttl = wiki.Ttl_parse(ttl); if (page_ttl == null) return false; + Xoa_url page_url = wiki.Utl__url_parser().Parse(ttl); + Xoae_page page = wiki.Data_mgr().Load_page_and_parse(page_url, page_ttl); + Load_links(rv, page.Lnki_list()); + return true; + } + private void Load_links(Volume_page_itm rv, List_adp list) { + int len = list.Len(); + //gplx.xowa.wikis.data.tbls.Xowd_page_tbl page_tbl; page_tbl.Select_in__ttl + for (int i = 0; i < len; i++) { + Xop_lnki_tkn lnki = (Xop_lnki_tkn)list.Get_at(i); + int ns_id = lnki.Ns_id(); + switch (ns_id) { + case Xow_ns_.Tid__special: + case Xow_ns_.Tid__media: + break; + case Xow_ns_.Tid__file: + break; // file + default: + Volume_make_itm make_itm = new Volume_make_itm(); + make_itm.Item_ttl = lnki.Ttl().Page_db(); + make_itm.Item_size = 1; + rv.Link_list().Add(make_itm); + break; + } + } + } +} +class Volume_page_itm { + public void Init(Xoa_ttl page_ttl, Xoa_url page_url) { + this.page_ttl = page_ttl; this.page_url = page_url; + } + public Xoa_ttl Page_ttl() {return page_ttl;} private Xoa_ttl page_ttl; + public Xoa_url Page_url() {return page_url;} private Xoa_url page_url; + public List_adp Link_list() {return link_list;} private final List_adp link_list = List_adp_.New(); + public List_adp File_list() {return file_list;} private final List_adp file_list = List_adp_.New(); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_cmd.java index a27517de8..cfe93ee6d 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_cmd.java @@ -13,3 +13,34 @@ 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.addons.bldrs.volumes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.core.brys.*; +import gplx.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Volume_prep_cmd extends Xob_cmd__base { + private Io_url prep_url, make_url; + public Volume_prep_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + @Override public void Cmd_run() { + Volume_prep_itm[] page_itms = new Volume_prep_rdr().Parse(prep_url); + Volume_prep_mgr prep_mgr = new Volume_prep_mgr(new Volume_page_loader__wiki(wiki)); + Volume_make_itm[] make_itms = prep_mgr.Calc_makes(page_itms); + Bry_bfr bfr = Bry_bfr_.New(); + for (Volume_make_itm make_itm : make_itms) { + make_itm.To_bfr(bfr); + bfr.Add_byte_nl(); + } + Io_mgr.Instance.SaveFilBfr(make_url, bfr); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__prep_url_)) prep_url = m.ReadIoUrl("v"); + else if (ctx.Match(k, Invk__make_url_)) make_url = m.ReadIoUrl("v"); + else return super.Invk(ctx, ikey, k, m); + return this; + } + private static final String Invk__prep_url_ = "prep_url_", Invk__make_url_ = "make_url_"; + + public static final String BLDR_CMD_KEY = "volume.prep"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Volume_prep_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Volume_prep_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_itm.java b/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_itm.java index a27517de8..51ec172eb 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_itm.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_itm.java @@ -13,3 +13,22 @@ 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.addons.bldrs.volumes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.core.brys.*; +class Volume_prep_itm implements Bry_bfr_able { + public int Prep_id = 0; + public byte[] Page_ttl = null; + public long Max_bytes = 0; + public int Max_depth = 2; + public int Max_article_count = -1; + public int Max_link_count_per_page = -1; + public boolean Skip_navbox = false; + public int Max_file_count = -1; + public long Max_file_size = -1; + public boolean Skip_audio = true; + public static final Volume_prep_itm[] Ary_empty = new Volume_prep_itm[0]; + public void To_bfr(Bry_bfr bfr) { + bfr.Add(Page_ttl); + } +} +// Earth|2|100|10|100|100MB diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_mgr.java index a27517de8..5dbccc24a 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_mgr.java @@ -13,3 +13,62 @@ 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.addons.bldrs.volumes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +class Volume_prep_mgr { + private final Volume_page_loader loader; + private final Volume_page_itm tmp_page = new Volume_page_itm(); + private final List_adp list = List_adp_.New(); + public Volume_prep_mgr(Volume_page_loader loader) {this.loader = loader;} + public Volume_make_itm[] Calc_makes(Volume_prep_itm[] ary) { + Volume_prep_ctx ctx = new Volume_prep_ctx(); + int len = ary.length; + for (int i = 0; i < len; ++i) { + Volume_prep_itm itm = ary[i]; + ctx.Init(ctx, itm); + Calc_make(ctx, itm.Page_ttl); + } + return (Volume_make_itm[])list.To_ary_and_clear(Volume_make_itm.class); + } + private void Calc_make(Volume_prep_ctx ctx, byte[] page_ttl) { + if (!loader.Load(tmp_page, page_ttl)) return; + if (ctx.Bytes_max != -1 && ctx.Bytes_count > ctx.Bytes_max) return; + if (ctx.Depth_count > ctx.Depth_max) return; + List_adp files_list = tmp_page.File_list(); + int files_len = files_list.Len(); + for (int i = 0; i < files_len; ++i) { + Volume_make_itm file_itm = (Volume_make_itm)files_list.Get_at(i); + list.Add(file_itm); + } + List_adp links_list = tmp_page.Link_list(); + int links_len = links_list.Len(); + for (int i = 0; i < links_len; ++i) { + if (ctx.Page_max != -1 && ctx.Page_count++ > ctx.Page_max) return; + Volume_make_itm link_itm = (Volume_make_itm)links_list.Get_at(i); + list.Add(link_itm); + ctx.Depth_count++; + Calc_make(ctx, link_itm.Item_ttl); + ctx.Depth_count--; + } + } +} +class Volume_prep_ctx { + public long Bytes_count; + public long Bytes_max; + public int Page_count; + public int Page_max; + public int Depth_count; + public int Depth_max; + public void Init(Volume_prep_ctx prv_ctx, Volume_prep_itm itm) { + this.Bytes_count = prv_ctx.Bytes_count; + this.Bytes_max = itm.Max_bytes; + this.Page_count = 1; + this.Page_max = itm.Max_article_count; + this.Depth_count = 0; + this.Depth_max = itm.Max_depth; + } +// public int Max_link_count_per_page = -1; +// public int Max_file_count = -1; +// public long Max_file_size = -1; +// public boolean Skip_navbox = false; +// public boolean Skip_audio = true; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_rdr.java b/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_rdr.java index a27517de8..248e63c4b 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_rdr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_rdr.java @@ -13,3 +13,31 @@ 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.addons.bldrs.volumes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +class Volume_prep_rdr { + public Volume_prep_itm[] Parse(Io_url url) {return Parse(Io_mgr.Instance.LoadFilBryOr(url, null));} + public Volume_prep_itm[] Parse(byte[] src) { + if (src == null) return Volume_prep_itm.Ary_empty; + List_adp rv = List_adp_.New(); + byte[][] lines = Bry_split_.Split_lines(src); + int lines_len = lines.length; + for (int i = 0; i < lines_len; ++i) { + Volume_prep_itm itm = Parse_line_or_null(lines[i]); + if (itm != null) rv.Add(itm); + } + return (Volume_prep_itm[])rv.To_ary_and_clear(Volume_prep_itm.class); + } + private Volume_prep_itm Parse_line_or_null(byte[] line) { + byte[][] flds = Bry_split_.Split(line, Byte_ascii.Pipe); + int flds_len = flds.length; if (flds_len == 0) return null; + Volume_prep_itm rv = new Volume_prep_itm(); + for (int i = 0; i < flds_len; ++i) { + byte[] fld = flds[i]; + switch (i) { + case 0: rv.Page_ttl = fld; break; + default: throw Err_.new_unhandled_default(i); + } + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_rdr_tst.java b/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_rdr_tst.java index a27517de8..f000100fa 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_rdr_tst.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/volumes/Volume_prep_rdr_tst.java @@ -13,3 +13,26 @@ 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.addons.bldrs.volumes; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import org.junit.*; import gplx.core.tests.*; +public class Volume_prep_rdr_tst { + private final Volume_prep_rdr_fxt fxt = new Volume_prep_rdr_fxt(); + @Test public void Parse() { + fxt.Test__parse(String_.Concat_lines_nl_skip_last("A", "", "B") + , fxt.Make__itm("A") + , fxt.Make__itm("B") + ); + } +} +class Volume_prep_rdr_fxt { + private final Volume_prep_rdr rdr = new Volume_prep_rdr(); + public Volume_prep_rdr_fxt Test__parse(String raw, Volume_prep_itm... expd) { + Gftest.Eq__ary(expd, rdr.Parse(Bry_.new_u8(raw))); + return this; + } + public Volume_prep_itm Make__itm(String page_ttl) { + Volume_prep_itm rv = new Volume_prep_itm(); + rv.Page_ttl = Bry_.new_u8(page_ttl); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Db_bulk_cmd_base.java b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Db_bulk_cmd_base.java index a27517de8..bdc6bf5ca 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Db_bulk_cmd_base.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Db_bulk_cmd_base.java @@ -13,3 +13,63 @@ 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.addons.bldrs.wmdumps.imglinks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.wmdumps.*; +import gplx.dbs.*; +public abstract class Db_bulk_cmd_base { + public void Exec() { + int uid_max = Get_uid_max(); + int uid_rng = Get_uid_rng(); + int uid_bgn = -1, uid_end = uid_rng; + this.Bulk_bgn(); + while (uid_bgn <= uid_max) { + Bulk_run(uid_bgn, uid_end); + uid_bgn += uid_rng; + uid_end += uid_rng; + } + this.Bulk_end(); + } + protected abstract int Get_uid_max(); + protected abstract int Get_uid_rng(); + protected abstract void Bulk_bgn(); + protected abstract void Bulk_end(); + protected abstract void Bulk_run(int uid_bgn, int uid_end); +} +class Imglnk_bulk_cmd__img_id extends Db_bulk_cmd_base { + private final Db_conn conn; + private final Db_attach_mgr attach_mgr; + private final int img_wiki; + private String sql; + public Imglnk_bulk_cmd__img_id(Db_conn conn, boolean wiki_is_local, Xowe_wiki wiki) { + this.conn = conn; + this.attach_mgr = new Db_attach_mgr(conn, new Db_attach_itm("page_db", wiki.Data__core_mgr().Db__core().Tbl__page().Conn())); + this.img_wiki = wiki_is_local ? 0 : 1; + // xowa.wiki.image.sqlite3 + // INSERT INTO img_link (img_name, img_wiki) SELECT img_name, Count(img_name) FROM img_link_tmp GROUP BY img_name + } + @Override protected int Get_uid_max() { + return conn.Exec_select_as_int("SELECT Max(img_uid) FROM img_link_tmp", -1); + } + @Override protected int Get_uid_rng() {return 10000;} + @Override protected void Bulk_bgn() { + sql = String_.Concat_lines_nl_skip_last // ANSI.Y + ( "UPDATE img_link_tmp" + , "SET img_wiki = {0}" + , ", img_id = (SELECT p.page_id FROM page p WHERE p.page_namespace = 6 AND p.page_title = img_link_tmp.img_name)" + , "WHERE img_uid > {1} AND img_uid <= {2}" + , "AND img_name IN (SELECT p.page_title FROM page p WHERE p.page_namespace = 6 AND p.page_title = img_link_tmp.img_name)" + ); + sql = attach_mgr.Resolve_sql(sql); + attach_mgr.Attach(); + conn.Txn_bgn("imglnk.bulk"); + } + @Override protected void Bulk_end() { + conn.Txn_end(); + attach_mgr.Detach(); + } + @Override protected void Bulk_run(int uid_bgn, int uid_end) { + conn.Exec_sql(String_.Format("updating img_link_tmp; wiki={0} uid={1}", img_wiki, uid_bgn), String_.Format(sql, img_wiki, uid_bgn, uid_end)); + } + public static void Bulk_exec(Db_conn conn, boolean wiki_is_local, Xowe_wiki wiki) { + new Imglnk_bulk_cmd__img_id(conn, wiki_is_local, wiki).Exec(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_addon.java b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_addon.java index a27517de8..850924577 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_addon.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_addon.java @@ -13,3 +13,15 @@ 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.addons.bldrs.wmdumps.imglinks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.wmdumps.*; +import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.addons.bldrs.wmdumps.pagelinks.bldrs.*; +public class Imglnk_addon implements Xoax_addon_itm, Xoax_addon_itm__bldr { + public Xob_cmd[] Bldr_cmds() { + return new Xob_cmd[] + { Imglnk_bldr_cmd.Prototype + }; + } + + public String Addon__key() {return "xowa.builds.imglinks";} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_bldr_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_bldr_cmd.java index a27517de8..9bd6149e6 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_bldr_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_bldr_cmd.java @@ -13,3 +13,38 @@ 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.addons.bldrs.wmdumps.imglinks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.wmdumps.*; +import gplx.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.bldrs.sql_dumps.*; +public class Imglnk_bldr_cmd extends Xob_sql_dump_base implements Xosql_dump_cbk { + private Imglnk_bldr_mgr mgr; + private int tmp_page_id; private byte[] tmp_il_to; + private int rows = 0; + + public Imglnk_bldr_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.Cmd_ctor(bldr, wiki); this.make_fil_len = Io_mgr.Len_mb;} + @Override public String Sql_file_name() {return "imagelinks";} + @Override protected Xosql_dump_parser New_parser() {return new Xosql_dump_parser(this, "il_from", "il_to");} + + @Override public void Cmd_bgn_hook(Xob_bldr bldr, Xosql_dump_parser parser) { + mgr = new Imglnk_bldr_mgr(wiki); + } + public void On_fld_done(int fld_idx, byte[] src, int val_bgn, int val_end) { + switch (fld_idx) { + case Fld__il_from: this.tmp_page_id = Bry_.To_int_or(src, val_bgn, val_end, -1); break; + case Fld__il_to: this.tmp_il_to = Bry_.Mid(src, val_bgn, val_end); break; + } + } private static final byte Fld__il_from = 0, Fld__il_to = 1; + public void On_row_done() { + mgr.Tmp_tbl().Insert_by_batch(tmp_page_id, tmp_il_to); + if (++rows % 100000 == 0) usr_dlg.Prog_many("", "", "reading row ~{0}", Int_.To_str_fmt(rows, "#,##0")); + } + @Override public void Cmd_end() { + if (fail) return; + mgr.On_cmd_end(); + } + + public static final String BLDR_CMD_KEY = "wiki.imagelinks"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Imglnk_bldr_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Imglnk_bldr_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_bldr_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_bldr_mgr.java index a27517de8..e1c6a6876 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_bldr_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_bldr_mgr.java @@ -13,3 +13,37 @@ 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.addons.bldrs.wmdumps.imglinks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.wmdumps.*; +import gplx.dbs.*; +import gplx.xowa.bldrs.*; +import gplx.xowa.files.repos.*; +class Imglnk_bldr_mgr { + private final Db_conn conn; + private final Xowe_wiki wiki; + public Imglnk_tmp_tbl Tmp_tbl() {return tmp_tbl;} private Imglnk_tmp_tbl tmp_tbl; + public Imglnk_bldr_mgr(Xowe_wiki wiki) { + wiki.Init_assert(); + this.wiki = wiki; + this.conn = Xob_db_file.New__img_link(wiki).Conn(); + this.tmp_tbl = new Imglnk_tmp_tbl(conn); + conn.Meta_tbl_remake(tmp_tbl); + tmp_tbl.Create_tbl(); + tmp_tbl.Insert_bgn(); + } + public void On_cmd_end() { + // finalize txn; create idx + tmp_tbl.Insert_end(); + tmp_tbl.Create_idx__img_ttl(); + + // create reg_tbl + Imglnk_reg_tbl reg_tbl = new Imglnk_reg_tbl(conn); + conn.Meta_tbl_remake(reg_tbl); + reg_tbl.Create_idx__src_ttl(); + reg_tbl.Insert(conn, Xof_repo_tid_.Tid__local , wiki); + reg_tbl.Insert(conn, Xof_repo_tid_.Tid__remote, wiki.Appe().Wiki_mgr().Wiki_commons()); + reg_tbl.Create_idx__trg_ttl(); + +// Imglnk_bulk_cmd__img_id.Bulk_exec(conn, Bool_.Y, wiki); +// Imglnk_bulk_cmd__img_id.Bulk_exec(conn, Bool_.N, wiki.Appe().Wiki_mgr().Wiki_commons()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_reg_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_reg_tbl.java index a27517de8..c31539900 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_reg_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_reg_tbl.java @@ -13,3 +13,64 @@ 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.addons.bldrs.wmdumps.imglinks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.wmdumps.*; +import gplx.dbs.*; +import gplx.xowa.bldrs.*; +public class Imglnk_reg_tbl implements Db_tbl { + private final String tbl_name = "imglnk_reg"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld__img_src, fld__img_trg, fld__img_repo; + private final Db_conn conn; + public Imglnk_reg_tbl(Db_conn conn) { + this.conn = conn; + fld__img_src = flds.Add_str("img_src", 255); + fld__img_trg = flds.Add_str("img_trg", 255); + fld__img_repo = flds.Add_byte("img_repo"); + flds.Add_int("img_count"); + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} + public String Tbl_name() {return tbl_name;} + public String Fld__img_src() {return fld__img_src;} + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Create_idx__src_ttl() {conn.Meta_idx_create(tbl_name, fld__img_src, fld__img_src, fld__img_repo);} + public void Create_idx__trg_ttl() {conn.Meta_idx_create(tbl_name, fld__img_trg, fld__img_trg, fld__img_repo);} + public Db_stmt Select_by_ttl_stmt() { + if (select_by_ttl_stmt == null) + select_by_ttl_stmt = conn.Stmt_select(tbl_name, flds, fld__img_src); + return select_by_ttl_stmt; + } private Db_stmt select_by_ttl_stmt; + public void Insert(Db_conn conn, byte repo_id, Xowe_wiki wiki) { + String repo_id_str = Byte_.To_str(repo_id); + Db_attach_mgr attach_mgr = new Db_attach_mgr(conn); + String sql = ""; + + Xob_db_file redirect_db = Xob_db_file.New__wiki_redirect(wiki.Fsys_mgr().Root_dir()); + attach_mgr.Conn_links_(new Db_attach_itm("redirect_db", redirect_db.Conn())); + sql = String_.Concat_lines_nl_skip_last // ANSI.Y + ( "INSERT INTO imglnk_reg (img_src, img_trg, img_repo, img_count)" + , "SELECT ilt.img_name, r.trg_ttl, " + repo_id_str + ", Count(ilt.img_name)" + , "FROM imglnk_tmp ilt" + , " JOIN redirect r ON ilt.img_name = r.src_ttl" + , " LEFT JOIN imglnk_reg il ON il.img_src = ilt.img_name" + , "WHERE il.img_src IS NULL" + , "GROUP BY ilt.img_name" + ); + attach_mgr.Exec_sql_w_msg("imglnk_reg.insert.redirect: repo=" + repo_id_str, sql); + + Xob_db_file image_db = Xob_db_file.New__wiki_image(wiki.Fsys_mgr().Root_dir()); + attach_mgr.Conn_links_(new Db_attach_itm("image_db", image_db.Conn())); + sql = String_.Concat_lines_nl_skip_last // ANSI.Y + ( "INSERT INTO imglnk_reg (img_src, img_trg, img_repo, img_count)" + , "SELECT ilt.img_name, ilt.img_name, " + repo_id_str + ", Count(ilt.img_name)" + , "FROM imglnk_tmp ilt" + , " JOIN image i ON i.img_name = ilt.img_name" + , " LEFT JOIN imglnk_reg il ON il.img_src = ilt.img_name" + , "WHERE il.img_src IS NULL" + , "GROUP BY ilt.img_name" + ); + attach_mgr.Exec_sql_w_msg("imglnk_reg.insert.direct: repo=" + repo_id_str, sql); + } + public void Rls() { + select_by_ttl_stmt = Db_stmt_.Rls(select_by_ttl_stmt); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_tmp_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_tmp_tbl.java index a27517de8..3717c9be2 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_tmp_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Imglnk_tmp_tbl.java @@ -13,3 +13,40 @@ 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.addons.bldrs.wmdumps.imglinks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.wmdumps.*; +import gplx.dbs.*; +public class Imglnk_tmp_tbl implements Db_tbl { + private final String tbl_name = "imglnk_tmp"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld__page_id, fld__img_name; + private final Db_conn conn; + private Db_stmt stmt__insert; + public Imglnk_tmp_tbl(Db_conn conn) { + this.conn = conn; + flds.Add_int_pkey_autonum("img_uid"); + this.fld__page_id = flds.Add_int("page_id"); + this.fld__img_name = flds.Add_str("img_name", 255); + flds.Add_int_dflt("img_wiki", -1); + flds.Add_int_dflt("img_id", -1); + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} + public String Tbl_name() {return tbl_name;} + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Insert_bgn() { + stmt__insert = conn.Stmt_insert(tbl_name, fld__page_id, fld__img_name); + conn.Txn_bgn(tbl_name); + } + public void Insert_by_batch(int page_id, byte[] img_name) { + stmt__insert.Clear().Val_int(fld__page_id, page_id).Val_bry_as_str(fld__img_name, img_name).Exec_insert(); + } + public void Insert_end() { + conn.Txn_end(); + stmt__insert.Rls(); + } + public void Create_idx__img_ttl() { + conn.Meta_idx_create(tbl_name, fld__img_name, fld__img_name); + } + public void Rls() { + stmt__insert = Db_stmt_.Rls(stmt__insert); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Xof_orig_wkr__img_links.java b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Xof_orig_wkr__img_links.java index a27517de8..7736e29aa 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Xof_orig_wkr__img_links.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Xof_orig_wkr__img_links.java @@ -13,3 +13,62 @@ 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.addons.bldrs.wmdumps.imglinks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.wmdumps.*; +import gplx.dbs.*; import gplx.xowa.bldrs.*; +import gplx.xowa.files.origs.*; import gplx.xowa.files.repos.*; +public class Xof_orig_wkr__img_links implements Xof_orig_wkr { + private final Hash_adp_bry hash = Hash_adp_bry.cs(); + private Db_conn imglnk_conn; + public Xof_orig_wkr__img_links(Xowe_wiki wiki) { + this.wiki = wiki; + } + public byte Tid() {return Xof_orig_wkr_.Tid_xowa_img_links;} + public Xof_orig_itm Find_as_itm(byte[] ttl, int list_idx, int list_len) { + Xof_orig_itm rv = (Xof_orig_itm)hash.Get_by(ttl); + if (rv == Missing) return Xof_orig_itm.Null; + else if (rv == null) rv = Load_from_db(ttl); + return rv == Missing ? Xof_orig_itm.Null : rv; + } + public void Find_by_list(Ordered_hash rv, List_adp itms) {throw Err_.new_unimplemented();} + public boolean Add_orig(byte repo, byte[] page, int ext_id, int w, int h, byte[] redirect) {return false;} + public void Db_txn_save() {} + public void Db_rls() {} + + public Xowe_wiki Wiki() {return wiki;} private final Xowe_wiki wiki; + public Imglnk_reg_tbl Tbl__imglnk_reg() { + if (tbl__imglnk_reg == null) + this.tbl__imglnk_reg = new Imglnk_reg_tbl(imglnk_conn); + return tbl__imglnk_reg; + } private Imglnk_reg_tbl tbl__imglnk_reg; + public Db_stmt Stmt__image__select(byte repo, Xowe_wiki wiki) { + Db_stmt rv = stmt__image__select[repo]; + if (rv == null) { + rv = Make__stmt__image__select(repo, wiki); + stmt__image__select[repo] = rv; + } + return rv; + } private Db_stmt[] stmt__image__select = new Db_stmt[2]; + private Db_stmt Make__stmt__image__select(byte repo, Xowe_wiki wiki) { + Xob_db_file image_db = Xob_db_file.New__wiki_image(wiki.Fsys_mgr().Root_dir()); + return image_db.Conn().Stmt_select + ( "image" + , String_.Ary("img_media_type", "img_minor_mime", "img_size", "img_width", "img_height", "img_bits", "img_ext_id", "img_timestamp") + , String_.Ary("img_name") + ); + } + public void Add_by_db(Xof_orig_itm itm) { + hash.Add(itm.Ttl(), itm); + } + private Xof_orig_itm Load_from_db(byte[] ttl) { + synchronized (hash) { // LOCK:orig_wkr is shared by multiple threads; NullPointerException on statement sometimes when concurrent; DATE:2016-09-03 + if (imglnk_conn == null) + imglnk_conn = Xob_db_file.New__img_link(wiki).Conn(); + Xof_orig_itm rv = Xof_orig_wkr__img_links_.Load_itm(this, imglnk_conn, wiki, ttl); + if (rv == Xof_orig_itm.Null) + rv = Missing; + hash.Add(ttl, rv); + return rv; + } + } + private static final Xof_orig_itm Missing = new Xof_orig_itm(Byte_.Max_value_127, Bry_.Empty, -1, -1, -1, Bry_.Empty); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Xof_orig_wkr__img_links_.java b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Xof_orig_wkr__img_links_.java index a27517de8..235f46cf8 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Xof_orig_wkr__img_links_.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/imglinks/Xof_orig_wkr__img_links_.java @@ -13,3 +13,79 @@ 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.addons.bldrs.wmdumps.imglinks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.wmdumps.*; +import gplx.dbs.*; +import gplx.xowa.bldrs.*; +import gplx.xowa.files.repos.*; import gplx.xowa.files.origs.*; +public class Xof_orig_wkr__img_links_ { + public static void Load_all(Xof_orig_wkr__img_links wkr) { + Xowe_wiki wiki = wkr.Wiki(); + Db_conn conn = Xob_db_file.New__img_link(wiki).Conn(); + Load_all_by_wiki(wkr, conn, Xof_repo_tid_.Tid__local , wiki); + Load_all_by_wiki(wkr, conn, Xof_repo_tid_.Tid__remote, wiki.Appe().Wiki_mgr().Wiki_commons()); + } + public static Xof_orig_itm Load_itm(Xof_orig_wkr__img_links wkr, Db_conn conn, Xowe_wiki wiki, byte[] ttl) { + Imglnk_reg_tbl imglnk_reg_tbl = wkr.Tbl__imglnk_reg(); + Db_rdr rdr = imglnk_reg_tbl.Select_by_ttl_stmt().Clear().Crt_bry_as_str("img_src", ttl).Exec_select__rls_manual(); + byte img_repo = Byte_.Max_value_127; + byte[] img_trg = null; + try { + if (rdr.Move_next()) { + img_repo = rdr.Read_byte("img_repo"); + img_trg = rdr.Read_bry_by_str("img_trg"); + } + else // ttl missing; EX:image i ON ilr.img_trg = i.img_name" + , "WHERE ilr.img_repo = " + repo_id + ); + + Xob_db_file img_db = Xob_db_file.New__wiki_image(wiki.Fsys_mgr().Root_dir()); + Db_attach_mgr attach_mgr = new Db_attach_mgr(conn, new Db_attach_itm("img_db", img_db.Conn())); + sql = attach_mgr.Resolve_sql(sql); + attach_mgr.Attach(); + + int count = 0; + Db_rdr rdr = conn.Stmt_sql(sql).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + rv.Add_by_db(new Xof_orig_itm + ( rdr.Read_byte("img_repo") + , rdr.Read_bry_by_str("img_src") + , rdr.Read_int("img_ext_id") + , rdr.Read_int("img_width") + , rdr.Read_int("img_height") + , rdr.Read_bry_by_str("img_redirect") + )); + if ((++count % 10000) == 0) + Gfo_usr_dlg_.Instance.Prog_many("", "", "loading img_links.orig: rows=~{0}", count); + } + } finally {rdr.Rls();} + attach_mgr.Detach(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/pagelinks/Xoax_builds_pagelinks_addon.java b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/pagelinks/Xoax_builds_pagelinks_addon.java index a27517de8..52756f561 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/pagelinks/Xoax_builds_pagelinks_addon.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/pagelinks/Xoax_builds_pagelinks_addon.java @@ -13,3 +13,15 @@ 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.addons.bldrs.wmdumps.pagelinks; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.wmdumps.*; +import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.addons.bldrs.wmdumps.pagelinks.bldrs.*; +public class Xoax_builds_pagelinks_addon implements Xoax_addon_itm, Xoax_addon_itm__bldr { + public Xob_cmd[] Bldr_cmds() { + return new Xob_cmd[] + { Pglnk_bldr_cmd.Prototype + }; + } + + public String Addon__key() {return "xowa.builds.pagelinks";} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/pagelinks/bldrs/Pglnk_bldr_cmd.java b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/pagelinks/bldrs/Pglnk_bldr_cmd.java index a27517de8..d04ded535 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/pagelinks/bldrs/Pglnk_bldr_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/pagelinks/bldrs/Pglnk_bldr_cmd.java @@ -13,3 +13,64 @@ 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.addons.bldrs.wmdumps.pagelinks.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.wmdumps.*; import gplx.xowa.addons.bldrs.wmdumps.pagelinks.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.bldrs.sql_dumps.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.xowa.wikis.data.*; import gplx.xowa.addons.bldrs.wmdumps.pagelinks.dbs.*; +public class Pglnk_bldr_cmd extends Xob_sql_dump_base implements Xosql_dump_cbk { + private Db_conn conn; + private Pglnk_page_link_temp_tbl temp_tbl; + private int tmp_src_id, tmp_trg_ns; private byte[] tmp_trg_ttl; + private int rows = 0; + public Pglnk_bldr_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.Cmd_ctor(bldr, wiki); this.make_fil_len = Io_mgr.Len_mb;} + @Override public String Sql_file_name() {return Dump_type_key;} public static final String Dump_type_key = "pagelinks"; + @Override protected Xosql_dump_parser New_parser() {return new Xosql_dump_parser(this, "pl_from", "pl_namespace", "pl_title");} + @Override public void Cmd_bgn_hook(Xob_bldr bldr, Xosql_dump_parser parser) { + wiki.Init_assert(); + Xob_db_file page_link_db = Xob_db_file.New__page_link(wiki); + this.conn = page_link_db.Conn(); + this.temp_tbl = new Pglnk_page_link_temp_tbl(conn); + conn.Meta_tbl_delete(temp_tbl.Tbl_name()); + temp_tbl.Create_tbl(); + temp_tbl.Insert_bgn(); + } + @Override public void Cmd_end() { + if (fail) return; + temp_tbl.Insert_end(); + temp_tbl.Create_idx(); + Pglnk_page_link_tbl actl_tbl = new Pglnk_page_link_tbl(conn); + conn.Meta_tbl_delete(actl_tbl.Tbl_name()); + actl_tbl.Create_tbl(); + new Db_attach_mgr(conn, new Db_attach_itm("page_db", wiki.Data__core_mgr().Db__core().Conn())) + .Exec_sql_w_msg("updating page_link", Sql__page_link__make); + conn.Meta_tbl_delete(temp_tbl.Tbl_name()); + actl_tbl.Create_idx__src_trg(); + actl_tbl.Create_idx__trg_src(); + conn.Env_vacuum(); + } + public void On_fld_done(int fld_idx, byte[] src, int val_bgn, int val_end) { + switch (fld_idx) { + case Fld__pl_from: this.tmp_src_id = Bry_.To_int_or(src, val_bgn, val_end, -1); break; + case Fld__pl_namespace: this.tmp_trg_ns = Bry_.To_int_or(src, val_bgn, val_end, -1); break; + case Fld__pl_title: this.tmp_trg_ttl = Bry_.Mid(src, val_bgn, val_end); break; + } + } + public void On_row_done() { + temp_tbl.Insert(tmp_src_id, tmp_trg_ns, tmp_trg_ttl); + if (++rows % 100000 == 0) usr_dlg.Prog_many("", "", "reading row ~{0}", Int_.To_str_fmt(rows, "#,##0")); + } + private static final byte Fld__pl_from = 0, Fld__pl_namespace = 1, Fld__pl_title = 2; + private static final String Sql__page_link__make = String_.Concat_lines_nl_skip_last + ( "INSERT INTO page_link (src_id, trg_id, trg_count)" + , "SELECT pl.src_id" + , ", p.page_id" + , ", Count(p.page_id)" + , "FROM page_link_temp pl" + , " JOIN page p ON pl.trg_ns = p.page_namespace AND pl.trg_ttl = p.page_title" + , "GROUP BY pl.src_id, p.page_id" + ); + + public static final String BLDR_CMD_KEY = "wiki.page_link"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Pglnk_bldr_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Pglnk_bldr_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/pagelinks/dbs/Pglnk_page_link_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/pagelinks/dbs/Pglnk_page_link_tbl.java index a27517de8..e8d26ed3e 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/pagelinks/dbs/Pglnk_page_link_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/pagelinks/dbs/Pglnk_page_link_tbl.java @@ -13,3 +13,23 @@ 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.addons.bldrs.wmdumps.pagelinks.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.wmdumps.*; import gplx.xowa.addons.bldrs.wmdumps.pagelinks.*; +import gplx.core.ios.*; import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.xowa.wikis.dbs.*; import gplx.dbs.cfgs.*; +public class Pglnk_page_link_tbl implements Rls_able { + private final String tbl_name = "page_link"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_src_id, fld_trg_id; + private final Db_conn conn; + public Pglnk_page_link_tbl(Db_conn conn) { + this.conn = conn; + fld_src_id = flds.Add_int("src_id"); + fld_trg_id = flds.Add_int("trg_id"); + flds.Add_int("trg_count"); + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} + public String Tbl_name() {return tbl_name;} + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Create_idx__src_trg() {conn.Meta_idx_create(Gfo_usr_dlg_.Instance, Dbmeta_idx_itm.new_unique_by_tbl(tbl_name, "src_trg", fld_src_id, fld_trg_id));} + public void Create_idx__trg_src() {conn.Meta_idx_create(Gfo_usr_dlg_.Instance, Dbmeta_idx_itm.new_unique_by_tbl(tbl_name, "trg_src", fld_trg_id, fld_src_id));} + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/pagelinks/dbs/Pglnk_page_link_temp_tbl.java b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/pagelinks/dbs/Pglnk_page_link_temp_tbl.java index a27517de8..f88850310 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/pagelinks/dbs/Pglnk_page_link_temp_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/wmdumps/pagelinks/dbs/Pglnk_page_link_temp_tbl.java @@ -13,3 +13,31 @@ 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.addons.bldrs.wmdumps.pagelinks.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; import gplx.xowa.addons.bldrs.wmdumps.*; import gplx.xowa.addons.bldrs.wmdumps.pagelinks.*; +import gplx.core.ios.*; import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.xowa.wikis.dbs.*; import gplx.dbs.cfgs.*; +public class Pglnk_page_link_temp_tbl implements Rls_able { + private final String tbl_name = "page_link_temp"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_src_id, fld_trg_ns, fld_trg_ttl; + private final Db_conn conn; private Db_stmt stmt_insert; + public Pglnk_page_link_temp_tbl(Db_conn conn) { + this.conn = conn; + flds.Add_int_pkey_autonum("uid"); + fld_src_id = flds.Add_int("src_id"); + fld_trg_ns = flds.Add_int("trg_ns"); + fld_trg_ttl = flds.Add_str("trg_ttl", 255); + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} + public String Tbl_name() {return tbl_name;} + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Create_idx() {conn.Meta_idx_create(Gfo_usr_dlg_.Instance, Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "main", fld_src_id, fld_trg_ns, fld_trg_ttl));} + public void Insert_bgn() {conn.Txn_bgn("page_link__insert_bulk"); stmt_insert = conn.Stmt_insert(tbl_name, flds);} + public void Insert_end() {conn.Txn_end(); stmt_insert = Db_stmt_.Rls(stmt_insert);} + public void Insert(int src_id, int trg_ns, byte[] trg_ttl) { + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Clear().Val_int(fld_src_id, src_id).Val_int(fld_trg_ns, trg_ns).Val_bry_as_str(fld_trg_ttl, trg_ttl).Exec_insert(); + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_addon.java b/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_addon.java index a27517de8..47c396da8 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_addon.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_addon.java @@ -13,3 +13,32 @@ 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.addons.bldrs.xodirs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.xowa.specials.*; +import gplx.xowa.addons.wikis.imports.*; +public class Xobc_xodir_addon implements Xoax_addon_itm, Xoax_addon_itm__special, Xoax_addon_itm__init { + public Xow_special_page[] Special_pages() { + return new Xow_special_page[] + { Xobc_xodir_special.Prototype + }; + } + public void Init_addon_by_app(Xoa_app app) { + } + public void Init_addon_by_wiki(Xow_wiki wiki) { + Xow_import_addon addon = Xow_import_addon.Addon__get(wiki); + addon.Dir_selected_cbks__add(Xow_import_dir_cbk__xodir.Instance); + } + + public String Addon__key() {return ADDON__KEY;} private static final String ADDON__KEY = "xowa.bldrs.xodirs"; +} +class Xow_import_dir_cbk__xodir implements Xow_import_dir_cbk { + public String Key() {return "xodir";} + public void Cbk__dir_selected(Xow_wiki wiki, Xoa_page page, String path) { + // save to prefs + wiki.App().User().User_db_mgr().Cfg().Set_app_str("xowa.xodir.custom_dir", path); + + // redirect to import_dir + page.Redirect_trail().Itms__add__special(wiki, Xobc_xodir_special.Prototype.Special__meta()); + } + public static Xow_import_dir_cbk__xodir Instance = new Xow_import_dir_cbk__xodir(); Xow_import_dir_cbk__xodir() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_cfg.java b/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_cfg.java index a27517de8..0fd0a796b 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_cfg.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_cfg.java @@ -13,3 +13,16 @@ 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.addons.bldrs.xodirs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +public class Xobc_xodir_cfg { + public static final String + Key__selected_dir = "xowa.xodir.selected_dir" + , Key__custom_dir = "xowa.xodir.custom_dir" + ; + public static void Set_app_str__selected(Xoa_app app, byte[] val_bry) { + // if wnt, replace "\"; note that url-encoding while navigating dirs will always convert "\" to "/" + if (gplx.core.envs.Op_sys.Cur().Tid_is_wnt()) val_bry = Bry_.Replace(val_bry, Byte_ascii.Slash, Byte_ascii.Backslash); + + app.User().User_db_mgr().Cfg().Set_app_bry(Xobc_xodir_cfg.Key__selected_dir, val_bry); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_dir.java b/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_dir.java index a27517de8..c4e8072a8 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_dir.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_dir.java @@ -13,3 +13,37 @@ 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.addons.bldrs.xodirs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.langs.mustaches.*; +class Xobc_xodir_doc implements Mustache_doc_itm { + private final byte[] import_root, app_root_dir; + private final Xobc_xodir_dir[] dirs; + public Xobc_xodir_doc(Xobc_xodir_dir[] dirs, byte[] import_root, byte[] app_root_dir) { + this.dirs = dirs; this.import_root = import_root; this.app_root_dir = app_root_dir; + } + public boolean Mustache__write(String key, Mustache_bfr bfr) { + if (String_.Eq(key, "import_root")) bfr.Add_bry(import_root); + else if (String_.Eq(key, "app_root_dir")) bfr.Add_bry(app_root_dir); + return false; + } + public Mustache_doc_itm[] Mustache__subs(String key) { + if (String_.Eq(key, "dirs")) return dirs; + return Mustache_doc_itm_.Ary__empty; + } +} +public class Xobc_xodir_dir implements Mustache_doc_itm { + private final boolean is_selected, is_custom; + private final byte[] path; + public Xobc_xodir_dir(boolean is_selected, boolean is_custom, byte[] path) { + this.is_selected = is_selected; this.is_custom = is_custom; this.path = path; + } + public boolean Mustache__write(String key, Mustache_bfr bfr) { + if (String_.Eq(key, "path")) bfr.Add_bry(path); + return false; + } + public Mustache_doc_itm[] Mustache__subs(String key) { + if (String_.Eq(key, "is_selected")) return Mustache_doc_itm_.Ary__bool(is_selected); + else if (String_.Eq(key, "is_custom")) return Mustache_doc_itm_.Ary__bool(is_custom); + return Mustache_doc_itm_.Ary__empty; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_html.java b/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_html.java index a27517de8..f901cf299 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_html.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_html.java @@ -13,3 +13,26 @@ 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.addons.bldrs.xodirs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.xowa.specials.*; import gplx.langs.mustaches.*; import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.tags.*; +import gplx.xowa.addons.bldrs.centrals.*; import gplx.xowa.addons.bldrs.centrals.dbs.*; import gplx.xowa.addons.bldrs.centrals.dbs.datas.imports.*; import gplx.xowa.addons.bldrs.centrals.hosts.*; +import gplx.xowa.wikis.domains.*; import gplx.core.ios.*; +class Xobc_xodir_html extends Xow_special_wtr__base { + @Override protected Io_url Get_addon_dir(Xoa_app app) {return app.Fsys_mgr().Http_root().GenSubDir_nest("bin", "any", "xowa", "addon", "bldr", "xodir");} + @Override protected Io_url Get_mustache_fil(Io_url addon_dir) {return addon_dir.GenSubFil_nest("bin", "xobc_xodir.mustache.html");} + @Override protected Mustache_doc_itm Bld_mustache_root(Xoa_app app) { + Xobc_task_addon addon = (Xobc_task_addon)app.Addon_mgr().Itms__get_or_null(Xobc_task_addon.ADDON__KEY); + return new Xobc_xodir_doc + ( addon.Xodir_mgr().Get_dirs(app) + , gplx.xowa.addons.wikis.imports.Xow_import_special.Get_root_url() + , app.Fsys_mgr().Root_dir().RawBry() + ); + } + @Override protected void Bld_tags(Xoa_app app, Io_url addon_dir, Xopage_html_data page_data) { + Xopg_tag_mgr head_tags = page_data.Head_tags(); + Xopg_alertify_.Add_tags (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xocss (head_tags, app.Fsys_mgr().Http_root()); + Xopg_tag_wtr_.Add__xohelp (head_tags, app.Fsys_mgr().Http_root()); + head_tags.Add(Xopg_tag_itm.New_css_file(addon_dir.GenSubFil_nest("bin", "xobc_xodir.css"))); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_mgr.java b/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_mgr.java index a27517de8..caa8f5f3c 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_mgr.java @@ -13,3 +13,7 @@ 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.addons.bldrs.xodirs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +public interface Xobc_xodir_mgr { + Xobc_xodir_dir[] Get_dirs(Xoa_app app); +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_mgr__pc.java b/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_mgr__pc.java index a27517de8..71c27ff7f 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_mgr__pc.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_mgr__pc.java @@ -13,3 +13,18 @@ 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.addons.bldrs.xodirs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +public class Xobc_xodir_mgr__pc implements Xobc_xodir_mgr { + public Xobc_xodir_mgr__pc(Xoa_app app) { + } + public Xobc_xodir_dir[] Get_dirs(Xoa_app app) { + int len = 2; + String dflt = app.Fsys_mgr().Root_dir().Raw(); + String selected = app.User().User_db_mgr().Cfg().Get_app_str_or(Xobc_xodir_cfg.Key__selected_dir, dflt); + String custom = app.User().User_db_mgr().Cfg().Get_app_str_or(Xobc_xodir_cfg.Key__custom_dir, "(choose your own folder)"); + Xobc_xodir_dir[] rv = new Xobc_xodir_dir[len]; + rv[0] = new Xobc_xodir_dir(String_.Eq(selected, dflt), Bool_.N, Bry_.new_u8(dflt)); + rv[1] = new Xobc_xodir_dir(String_.Eq(selected, custom), Bool_.Y, Bry_.new_u8(custom)); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_special.java b/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_special.java index a27517de8..9d2a72af9 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_special.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xobc_xodir_special.java @@ -13,3 +13,31 @@ 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.addons.bldrs.xodirs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.xowa.specials.*; import gplx.core.net.*; import gplx.core.net.qargs.*; import gplx.xowa.wikis.pages.*; +import gplx.xowa.addons.bldrs.centrals.*; +import gplx.xowa.addons.wikis.imports.*; +public class Xobc_xodir_special implements Xow_special_page { + public void Special__gen(Xow_wiki wiki, Xoa_page page, Xoa_url url, Xoa_ttl ttl) { + Gfo_qarg_mgr url_args = new Gfo_qarg_mgr().Init(url.Qargs_ary()); + + byte[] path = url_args.Read_bry_or(Bry__path, null); + if (path != null) { // path selected; set cfg and redirect to Download Central + Xobc_xodir_cfg.Set_app_str__selected(wiki.App(), path); + // On_path_selected.Invk(null, -1, "", null); + // page.Redirect().Itms__add__special(wiki, Xobc_task_special.Prototype.Special__meta()); + page.Redirect_trail().Itms__add__special(wiki, Prototype.Special__meta()); + return; + } + + new Xobc_xodir_html().Bld_page_by_mustache(wiki.App(), page, this); + } + private static final byte[] Bry__path = Bry_.new_a7("path"); + + Xobc_xodir_special(Xow_special_meta special__meta) {this.special__meta = special__meta;} + public Xow_special_meta Special__meta() {return special__meta;} private final Xow_special_meta special__meta; + public Xow_special_page Special__clone() {return this;} + public static final Xow_special_page Prototype = new Xobc_xodir_special(Xow_special_meta.New_xo("XowaRootDir", "XOWA Folder Selection")); + + public static Gfo_invk On_path_selected = Gfo_invk_.Noop; +} diff --git a/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xow_import_dir_cbk__xodir.java b/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xow_import_dir_cbk__xodir.java index a27517de8..733b72c15 100644 --- a/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xow_import_dir_cbk__xodir.java +++ b/400_xowa/src/gplx/xowa/addons/bldrs/xodirs/Xow_import_dir_cbk__xodir.java @@ -13,3 +13,5 @@ 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.addons.bldrs.xodirs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.bldrs.*; +import gplx.xowa.addons.wikis.imports.*; diff --git a/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_htmlr.java b/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_htmlr.java index a27517de8..8837422fb 100644 --- a/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_htmlr.java +++ b/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_htmlr.java @@ -13,3 +13,44 @@ 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.addons.htmls.sidebars; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.htmls.*; +import gplx.core.brys.*; import gplx.core.brys.fmts.*; +class Xoh_sidebar_htmlr { + public static byte[] To_html(Bry_bfr bfr, Xowe_wiki wiki, List_adp grps) { + Xoh_sidebar_itms_fmtr itms_fmtr = new Xoh_sidebar_itms_fmtr(); + int len = grps.Count(); + boolean popups_enabled = wiki.Html_mgr().Head_mgr().Popup_mgr().Enabled(); + for (int i = 0; i < len; ++i) { + Xoh_sidebar_itm grp = (Xoh_sidebar_itm)grps.Get_at(i); + itms_fmtr.Init_by_grp(popups_enabled, grp); + fmt.Bld_many(bfr, grp.Id(), grp.Text(), itms_fmtr); + } + return bfr.To_bry_and_clear(); + } + private static final Bry_fmt fmt = Bry_fmt.Auto_nl_skip_last + ( "
    " + , "

    ~{grp_text}

    " + , "
    " + , "
      ~{itms}" + , "
    " + , "
    " + , "
    " + , "" + ); +} +class Xoh_sidebar_itms_fmtr implements Bfr_arg { + private boolean popups_enabled; private Xoh_sidebar_itm grp; + public void Init_by_grp(boolean popups_enabled, Xoh_sidebar_itm grp) {this.popups_enabled = popups_enabled; this.grp = grp;} + public void Bfr_arg__add(Bry_bfr bfr) { + String itm_cls = popups_enabled ? " class='xowa-hover-off'" : ""; + int len = grp.Subs__len(); + for (int i = 0; i < len; ++i) { + Xoh_sidebar_itm itm = grp.Subs__get_at(i); + fmt.Bld_many(bfr, itm.Id(), itm.Href(), itm_cls, itm.Atr_accesskey_and_title(), itm.Text()); + } + } + private final Bry_fmt fmt = Bry_fmt.Auto_nl_skip_last + ( "" + , "
  • ~{itm_text}
  • " + ); +} diff --git a/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_itm.java b/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_itm.java index a27517de8..11c2a942a 100644 --- a/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_itm.java +++ b/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_itm.java @@ -13,3 +13,35 @@ 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.addons.htmls.sidebars; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.htmls.*; +public class Xoh_sidebar_itm { + public Xoh_sidebar_itm(boolean tid_is_itm, byte[] text_key, byte[] text_val, byte[] href) { + this.tid_is_itm = tid_is_itm; + this.id = gplx.langs.htmls.encoders.Gfo_url_encoder_.Id.Encode(Bry_.Add(CONST_id_prefix, text_key)); // build id; EX:"n-encoded_id" + this.text = text_val; + this.href = href; + } + public boolean Tid_is_itm() {return tid_is_itm;} private final boolean tid_is_itm; + public byte[] Id() {return id;} private final byte[] id; + public byte[] Text() {return text;} private final byte[] text; + public byte[] Href() {return href;} private final byte[] href; + public byte[] Title() {return title;} private byte[] title; + public byte[] Accesskey() {return accesskey;} private byte[] accesskey; + public byte[] Atr_accesskey_and_title() {return atr_accesskey_and_title;} private byte[] atr_accesskey_and_title; + public int Subs__len() {return subs.Count();} private final List_adp subs = List_adp_.New(); + public Xoh_sidebar_itm Subs__get_at(int i) {return (Xoh_sidebar_itm)subs.Get_at(i);} + public Xoh_sidebar_itm Subs__add(Xoh_sidebar_itm... ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) + subs.Add(ary[i]); + return this; + } + + public void Init_by_title_and_accesskey(byte[] title, byte[] accesskey, byte[] atr_accesskey_and_title) { + this.title = title; + this.accesskey = accesskey; + this.atr_accesskey_and_title = atr_accesskey_and_title; + } + + private static final byte[] CONST_id_prefix = Bry_.new_a7("n-"); +} diff --git a/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_mgr.java b/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_mgr.java index a27517de8..7e7dc790a 100644 --- a/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_mgr.java @@ -13,3 +13,40 @@ 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.addons.htmls.sidebars; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.htmls.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.nss.*; +public class Xoh_sidebar_mgr { + public Xoh_sidebar_mgr(Xowe_wiki wiki) {this.wiki = wiki;} private final Xowe_wiki wiki; + public List_adp Grps() {return grps;} private final List_adp grps = List_adp_.New(); // TEST: + public byte[] Html_bry() {return html_bry;} private byte[] html_bry; + public void Init_by_wiki() { + try { + Bry_bfr tmp_bfr = Bry_bfr_.New(); + byte[] sidebar = Get_sidebar_or_null(tmp_bfr, wiki); + if (sidebar != null) Make(tmp_bfr, sidebar); + } catch (Exception e) { + wiki.Appe().Usr_dlg().Warn_many("", "", "sidebar failed: wiki=~{0} err=~{1}", wiki.Domain_str(), Err_.Message_gplx_log(e)); + } + } + private byte[] Get_sidebar_or_null(Bry_bfr tmp_bfr, Xowe_wiki wiki) { + // if home, always return null + if (wiki.Domain_tid() == Xow_domain_tid_.Tid__home) return null; + + // check msg_mgr; note that this checks (a) en.wikipedia.org/wiki/MediaWiki:Sidebar; (b) "sidebar" in en.gfs + Xol_msg_itm rv_msg = Xol_msg_mgr_.Get_msg_itm(tmp_bfr, wiki, wiki.Lang(), Ttl__sidebar); + + // if found in MediaWiki:Sidebar, always return it + byte[] rv = rv_msg.Val(); + if (rv_msg.Defined_in() == Xol_msg_itm.Defined_in__wiki && Bry_.Len_gt_0(rv)) return rv; + + // sidebar is either (a) in lang.gfs (wikia; wmf wikis without MediaWiki:Sidebar), or (b) not in lang.gfs (wmf wikis in lang.gfs without "lang.gfs"; EX:abcde.gfs) + // if wikia, return null; else return rv; note that all "official" langs (EX: sw) fallback to en.gfs which has a sidebar; DATE:2017-01-05 + return wiki.Domain_tid() == Xow_domain_tid_.Tid__other ? null : rv; + } + public void Make(Bry_bfr tmp_bfr, byte[] src) { // TEST: + Xoh_sidebar_parser.Parse(tmp_bfr, wiki, grps, src); + html_bry = Xoh_sidebar_htmlr.To_html(tmp_bfr, wiki, grps); + } + private static final byte[] Ttl__sidebar = Bry_.new_a7("Sidebar"); // MediaWiki:Sidebar +} diff --git a/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_mgr_tst.java b/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_mgr_tst.java index a27517de8..5d9e4ecbd 100644 --- a/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_mgr_tst.java @@ -13,3 +13,223 @@ 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.addons.htmls.sidebars; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.htmls.*; +import org.junit.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; +public class Xoh_sidebar_mgr_tst { + @Before public void init() {fxt.Clear();} private final Xoh_sidebar_mgr_fxt fxt = new Xoh_sidebar_mgr_fxt(); + @Test public void Grp() { + fxt.Init__msg__grp("key", "text", "title"); + fxt.Exec__make("* key"); + fxt.Test__objs(fxt.Make__grp("text", "title")); + } + @Test public void Grp_missing_msg() { + fxt.Exec__make("* key"); + fxt.Test__objs(fxt.Make__grp("key", Null_str)); + } + @Test public void Itm() { + fxt.Init__msg__itm("href_key", "main_key", "text", "title", "accesskey", "href"); + fxt.Exec__make("** href_key|main_key"); + fxt.Test__objs(fxt.Make__itm("text", "title", "accesskey", "/wiki/Href")); + } + @Test public void Itm_missing_msg() { + fxt.Exec__make("** href_key|main_key"); + fxt.Test__objs(fxt.Make__itm("main_key", Null_str, Null_str, "/wiki/Href_key")); + } + @Test public void Itm_text() { // PURPOSE: only text msg exists; EX: ** Portal:Contents|contents; no href, accesskey, title + fxt.Init__msg__itm("href_key", "main_key", "text", Null_str, Null_str, Null_str); // only define msg for text + fxt.Exec__make("** href_key|main_key"); + fxt.Test__objs(fxt.Make__itm("text", Null_str, Null_str, "/wiki/Href_key")); + } + @Test public void Itm_href_absolute() { + fxt.Exec__make("** http://a.org|main_key"); + fxt.Test__objs(fxt.Make__itm("main_key", Null_str, Null_str, "http://a.org")); + } + @Test public void Itm_href_manual() { + fxt.Exec__make("** Help:Contents|main_key"); + fxt.Test__objs(fxt.Make__itm("main_key", Null_str, Null_str, "/wiki/Help:Contents")); + } + @Test public void Itm_href_xwiki() { + Xop_fxt.Reg_xwiki_alias(fxt.Wiki(), "c", "commons.wikimedia.org"); + fxt.Exec__make("** c:Help:Contents|main_key"); + fxt.Test__objs(fxt.Make__itm("main_key", Null_str, Null_str, "/site/commons.wikimedia.org/wiki/Help:Contents")); + } + @Test public void Itm_err_missing_key() { + fxt.Exec__make("** no_main_key"); + fxt.Test__objs(); + } + @Test public void Itm_ignore() { // PURPOSE: ignore SEARCH, TOOLBOX, LANGUAGES + fxt.Exec__make + ( "** SEARCH" + , "** TOOLBOX" + , "** LANGUAGES" + ); + fxt.Test__objs(); + } + @Test public void Itm_comment() { // PURPOSE: ignore comment; EX:de.v:MediaWiki:Sidebar; DATE:2014-03-08 + fxt.Init__msg__itm("href_key", "main_key", "text", "title", "accesskey", "href"); + fxt.Exec__make("** href_key|main_key"); + fxt.Test__objs(fxt.Make__itm("text", "title", "accesskey", "/wiki/Href")); + } + @Test public void Smoke() { + fxt.Init__msg__grp("navigation", "Grp_0_text", "Grp_0_title"); + fxt.Init__msg__itm("mainpage", "mainpage-description", "Itm_0_text", "Itm_0_title [a]", "a", "Itm_0_href"); + fxt.Init__msg__itm("Portal:Contents", "contents", "Itm_1_text", Null_str, Null_str, Null_str); + fxt.Exec__make + ( "* navigation" + , "** mainpage|mainpage-description" + , "** Portal:Contents|contents" + , "* SEARCH" + , "* interaction" + , "** helppage|help" + , "* TOOLBOX" + , "** TOOLBOXEND" + , "* LANGUAGES" + ); + fxt.Test__objs + ( fxt.Make__grp("Grp_0_text", "Grp_0_title").Subs__add + ( fxt.Make__itm("Itm_0_text", "Itm_0_title [a]", "a", "/wiki/Itm_0_href") + , fxt.Make__itm("Itm_1_text", Null_str, Null_str, "/wiki/Portal:Contents") + ) + , fxt.Make__grp("interaction", Null_str).Subs__add + ( fxt.Make__itm("help", Null_str, Null_str, "/wiki/Helppage") + )); + fxt.Test__html + ( "
    " + , "

    Grp_0_text

    " + , "
    " + , " " + , "
    " + , "
    " + , "
    " + , "

    interaction

    " + , "
    " + , "
      " + , "
    • help
    • " + , "
    " + , "
    " + , "
    " + ); + } + @Test public void Itm_template_msg() { + fxt.Init__msg__itm("href", "main", null, null, null, "{{ns:Special}}:Random"); + fxt.Exec__make("** href|main"); + fxt.Test__objs(fxt.Make__itm("main", Null_str, Null_str, "/wiki/Special:Random")); + } + @Test public void Itm_template_key() { + fxt.Exec__make("** {{ns:Special}}:Random|main"); + fxt.Test__objs(fxt.Make__itm("main", Null_str, Null_str, "/wiki/Special:Random")); + } + @Test public void Popups() { + fxt.Init__popups_enabled(true); + fxt.Exec__make + ( "* navigation" + , "** mainpage|mainpage-description" + ); + fxt.Test__objs + ( fxt.Make__grp("navigation", "").Subs__add + ( fxt.Make__itm("mainpage-description", Null_str, Null_str, "/wiki/Mainpage") + )); + fxt.Test__html + ( "
    " + , "

    navigation

    " + , "
    " + , " " + , "
    " + , "
    " + ); + } + private static final String Null_str = ""; +} +class Xoh_sidebar_mgr_fxt { + private Xoae_app app; private Xowe_wiki wiki; private Xoh_sidebar_mgr sidebar_mgr; private Bry_bfr bfr; + public Xoh_sidebar_mgr_fxt Clear() { + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + sidebar_mgr = wiki.Html_mgr().Portal_mgr().Sidebar_mgr(); + bfr = Bry_bfr_.Reset(Io_mgr.Len_kb); + Init__popups_enabled(false); + return this; + } + public Xowe_wiki Wiki() {return wiki;} + public Xoh_sidebar_itm Make__grp(String text, String title, Xoh_sidebar_itm... itms) { + Xoh_sidebar_itm rv = new Xoh_sidebar_itm(Bool_.N, Bry_.new_a7(text), Bry_.new_a7(text), null); + rv.Init_by_title_and_accesskey(Bry_.new_a7(title), null, null); + return rv; + } + public Xoh_sidebar_itm Make__itm(String text, String title, String accesskey, String href) { + Xoh_sidebar_itm rv = new Xoh_sidebar_itm(Bool_.Y, Bry_.new_a7(text), Bry_.new_a7(text), Bry_.new_a7(href)); + rv.Init_by_title_and_accesskey(Bry_.new_a7(title), Bry_.new_a7(accesskey), null); + return rv; + } + public Xoh_sidebar_mgr_fxt Init__popups_enabled(boolean v) { + wiki.Html_mgr().Head_mgr().Popup_mgr().Enabled_(v); + return this; + } + public Xoh_sidebar_mgr_fxt Init__msg__grp(String key, String text, String title) { + Init_msg(key, text); + Init_msg("tooltip-n-" + key, title); + return this; + } + public Xoh_sidebar_mgr_fxt Init__msg__itm(String href_key, String main_key, String text, String title, String accesskey, String href) { + if (text != null) Init_msg(main_key, text); + if (href != null) Init_msg(href_key, href); + if (title != null) Init_msg("tooltip-n-" + main_key, title); + if (accesskey != null) Init_msg("accesskey-n-" + main_key, accesskey); + return this; + } + public Xoh_sidebar_mgr_fxt Init_msg(String key, String val) { + Xol_msg_mgr msg_mgr = wiki.Lang().Msg_mgr(); + Xol_msg_itm msg_itm = msg_mgr.Itm_by_key_or_new(Bry_.new_a7(key)); + msg_itm.Atrs_set(Bry_.new_a7(val), false, String_.Has(val, "{{")); + return this; + } + public void Exec__make(String... raw) { + sidebar_mgr.Make(bfr, Bry_.new_u8(String_.Concat_lines_nl_skip_last(raw))); + } + public void Test__objs(Xoh_sidebar_itm... expd) { + Tfds.Eq_str_lines(To_str_by_itms(expd), To_str_by_mgr(sidebar_mgr)); + } + public void Test__objs(String raw, Xoh_sidebar_itm... expd) { + Tfds.Eq_str_lines(To_str_by_itms(expd), To_str_by_mgr(sidebar_mgr)); + } + public void Test__html(String... expd) { + Tfds.Eq_str_lines(String_.Concat_lines_nl_skip_last(expd), String_.new_u8(sidebar_mgr.Html_bry())); + } + private static String To_str_by_mgr(Xoh_sidebar_mgr mgr) { + List_adp grps = mgr.Grps(); + int len = grps.Len(); + Xoh_sidebar_itm[] ary = new Xoh_sidebar_itm[len]; + for (int i = 0; i < len; i++) + ary[i] = (Xoh_sidebar_itm)grps.Get_at(i); + return To_str_by_itms(ary); + } + + private static String To_str_by_itms(Xoh_sidebar_itm[] ary) { + Bry_bfr bfr = Bry_bfr_.New(); + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) + To_str_by_itm(bfr, ary[i]); + return bfr.To_str_and_clear(); + } + private static void To_str_by_itm(Bry_bfr bfr, Xoh_sidebar_itm cur) { + boolean tid_is_itm = cur.Tid_is_itm(); + bfr.Add_str_a7(tid_is_itm ? "itm|" : "grp|"); + bfr.Add(cur.Text()).Add_byte_pipe(); + bfr.Add(cur.Title()).Add_byte_pipe(); + if (tid_is_itm) { + bfr.Add(cur.Accesskey()).Add_byte_pipe(); + bfr.Add(cur.Href()).Add_byte_pipe(); + } + bfr.Add_byte_nl(); + + int len = cur.Subs__len(); + for (int i = 0; i< len; ++i) + To_str_by_itm(bfr, cur.Subs__get_at(i)); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_parser.java b/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_parser.java index a27517de8..719a5f5d6 100644 --- a/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_parser.java +++ b/400_xowa/src/gplx/xowa/addons/htmls/sidebars/Xoh_sidebar_parser.java @@ -13,3 +13,99 @@ 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.addons.htmls.sidebars; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.htmls.*; +import gplx.core.btries.*; +import gplx.langs.htmls.encoders.*; +import gplx.xowa.parsers.lnkis.*; +class Xoh_sidebar_parser { // TS: + public static void Parse(Bry_bfr tmp_bfr, Xowe_wiki wiki, List_adp grps, byte[] src) { + // split MediaWiki:Sidebar into lines + byte[][] lines = Bry_split_.Split(src, Byte_ascii.Nl); + + // init + Xop_link_parser link_parser = new Xop_link_parser(); + Xoa_url tmp_url = Xoa_url.blank(); + Xoh_sidebar_itm cur_grp = null; + + // loop lines + int lines_len = lines.length; + for (int i = 0; i < lines_len; i++) { + byte[] line = lines[i]; + int line_len = line.length; + if (line_len == 0) continue; // skip blank lines + if (line[0] != Byte_ascii.Star) continue; // skip non-list items; note that all items must begin with "*" + + // if **, then itm; else * is grp + boolean tid_is_itm = line[1] == Byte_ascii.Star; + + // trim ws; note that tid indicates # of asterisks; EX: '** a' -> 'a' + byte[] raw = Bry_.Trim(line, tid_is_itm ? 2 : 1, line_len); + + // strip comments; DATE:2014-03-08 + raw = gplx.langs.htmls.Gfh_utl.Del_comments(tmp_bfr, raw); + + Xoh_sidebar_itm cur_itm = null; + // parse itm + if (tid_is_itm) { + cur_itm = Parse_itm_or_null(wiki, raw, link_parser, tmp_url, tmp_bfr); if (cur_itm == null) continue; + if (cur_grp == null) // handle null_ref; should only occur for tests + grps.Add(cur_itm); + else + cur_grp.Subs__add(cur_itm); + } + // parse grp + else { + cur_itm = Parse_grp_or_null(wiki, raw); if (cur_itm == null) continue; + cur_grp = cur_itm; + grps.Add(cur_grp); + } + wiki.Msg_mgr().Val_html_accesskey_and_title(cur_itm.Id(), tmp_bfr, cur_itm); + } + } + private static Xoh_sidebar_itm Parse_grp_or_null(Xowe_wiki wiki, byte[] raw) { + // ignore SEARCH, TOOLBOX, LANGUAGES + if (ignore_trie.Match_bgn(raw, 0, raw.length) != null) return null; + + byte[] text_key = raw; + byte[] text_val = Resolve_key(wiki, text_key); + return new Xoh_sidebar_itm(Bool_.N, text_key, text_val, null); + } + private static Xoh_sidebar_itm Parse_itm_or_null(Xowe_wiki wiki, byte[] raw, Xop_link_parser link_parser, Xoa_url tmp_url, Bry_bfr bfr) { + // separate into key|val; note that grp uses entire raw for key while itm uses raw after "|" + int pipe_pos = Bry_find_.Find_fwd(raw, Byte_ascii.Pipe); + + // if no pipe, warn and return; EX: should be "href|main", but only "href" + if (pipe_pos == Bry_find_.Not_found) { + // don't bother warning if es.wikisource.org and special:Random/Pagina; occurs in 2014-02-03 dump and still present as of 2016-09; note this sidebar item does not show on WMF either + if (Bry_.Eq(wiki.Domain_bry(), Ignore_wiki_ess) && Bry_.Eq(raw, Ignore_item_ess_random)) {} + else + wiki.Appe().Usr_dlg().Warn_many("", "", "sidebar item is missing pipe; only href is available; item will be hidden: item=~{0}", String_.new_u8(raw)); + return null; + } + + // get text + byte[] text_key = Bry_.Mid(raw, pipe_pos + 1, raw.length); + byte[] text_val = Resolve_key(wiki, text_key); + + // get href + byte[] href_key = Bry_.Mid(raw, 0, pipe_pos); + byte[] href_val = Resolve_key(wiki, href_key); + href_val = link_parser.Parse(bfr, tmp_url, wiki, href_val, Bry_.Empty); + + return new Xoh_sidebar_itm(Bool_.Y, text_key, text_val, href_val); + } + private static byte[] Resolve_key(Xowe_wiki wiki, byte[] key) { + byte[] val = wiki.Msg_mgr().Val_by_key_obj(key); + if (Bry_.Len_eq_0(val)) val = key; // if key is not found, default to val + return wiki.Parser_mgr().Main().Expand_tmpl(val); + } + + private static byte[] Ignore_wiki_ess = Bry_.new_a7("es.wikisource.org"), Ignore_item_ess_random = Bry_.new_u8("special:Random/Página djvu"); + private static final byte Ignore__search = 1, Ignore__toolbox = 2, Ignore__toolbox_end = 3, Ignore__languages = 4; + private static final Btrie_slim_mgr ignore_trie = Btrie_slim_mgr.ci_a7() + .Add_str_byte("SEARCH" , Ignore__search) + .Add_str_byte("TOOLBOX" , Ignore__toolbox) + .Add_str_byte("TOOLBOXEND" , Ignore__toolbox_end) + .Add_str_byte("LANGUAGES" , Ignore__languages) + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_htmlr.java b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_htmlr.java index a27517de8..1f3c4c6f7 100644 --- a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_htmlr.java +++ b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_htmlr.java @@ -13,3 +13,78 @@ 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.addons.htmls.tocs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.htmls.*; +import gplx.langs.htmls.*; import gplx.xowa.htmls.core.htmls.*; +class Xoh_toc_htmlr implements gplx.core.brys.Bfr_arg { + private final Bry_bfr numbering_bfr = Bry_bfr_.New(); + private byte[] toc_label; + private int prv_lvl; + private Ordered_hash toc_itms; + public void Clear() { + prv_lvl = 0; + } + public void Init(byte[] toc_label) { + this.toc_label = toc_label; + } + public void To_html(Bry_bfr rv, Xoh_wtr_ctx hctx, Ordered_hash toc_itms, boolean toc_mode_is_pgbnr) { + this.toc_itms = toc_itms; + fmtr_div.Bld_many(rv, toc_mode_is_pgbnr ? Bry_.Empty : Bry_toc_cls, toc_label, this); + } + public void Test__to_html(Bry_bfr rv, Ordered_hash toc_itms) { + this.toc_itms = toc_itms; + Bfr_arg__add(rv); + } + public void Bfr_arg__add(Bry_bfr bfr) { + int len = toc_itms.Len(); + prv_lvl = 0; + for (int i = 0; i < len; ++i) { + Xoh_toc_itm itm = (Xoh_toc_itm)toc_itms.Get_at(i); + Write(bfr, itm); + } + + // close all open levels + for (int i = prv_lvl; i > 0; --i) { + int indent = i * 2; + bfr.Add_byte_repeat(Byte_ascii.Space, indent + 2).Add(Gfh_tag_.Li_rhs).Add_byte_nl(); // EX: " \n" + bfr.Add_byte_repeat(Byte_ascii.Space, indent ).Add(Gfh_tag_.Ul_rhs).Add_byte_nl(); // EX: " \n" + } + } + private void Write(Bry_bfr bfr, Xoh_toc_itm itm) { + int cur_lvl = itm.Lvl(); + int indent = cur_lvl * 2; + switch (CompareAble_.Compare(cur_lvl, prv_lvl)) { + case CompareAble_.More: // start new "
      " + bfr.Add_byte_repeat(Byte_ascii.Space, indent).Add(Gfh_tag_.Ul_lhs).Add_byte_nl(); // EX: "
        \n" + break; + case CompareAble_.Same: // close old ""; NOTE: Comparable_.Same will never be 1st item (so won't ever get
      • ) + bfr.Add_byte_repeat(Byte_ascii.Space, indent + 2).Add(Gfh_tag_.Li_rhs).Add_byte_nl(); // EX: "
      • \n" + break; + case CompareAble_.Less: // close old "
      " and "" + for (int j = prv_lvl; j > cur_lvl; --j) { + bfr.Add_byte_repeat(Byte_ascii.Space, (j * 2) + 2).Add(Gfh_tag_.Li_rhs).Add_byte_nl(); // EX: " \n" + bfr.Add_byte_repeat(Byte_ascii.Space, (j * 2) ).Add(Gfh_tag_.Ul_rhs).Add_byte_nl(); // EX: "
    \n" + } + bfr.Add_byte_repeat(Byte_ascii.Space, indent + 2).Add(Gfh_tag_.Li_rhs).Add_byte_nl(); // EX: " \n" + break; + default: throw Err_.new_unhandled_default(CompareAble_.Compare(cur_lvl, prv_lvl)); + } + + // write "
  • " + , "
    " + , "

    ~{contents_title}

    " + , "
    " + , "~{itms}" + , "" + )) + , fmtr_itm = Bry_fmt.Auto + ( "
  • ~{heading} ~{text}\n" + ); +} diff --git a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_htmlr__basic__tst.java b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_htmlr__basic__tst.java index a27517de8..82050394d 100644 --- a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_htmlr__basic__tst.java +++ b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_htmlr__basic__tst.java @@ -13,3 +13,163 @@ 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.addons.htmls.tocs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.htmls.*; +import org.junit.*; import gplx.core.tests.*; +public class Xoh_toc_htmlr__basic__tst { + @Before public void init() {fxt.Clear();} private final Xoh_toc_htmlr__basic__fxt fxt = new Xoh_toc_htmlr__basic__fxt(); + @Test public void D1_S0_S0() { + fxt.Init__add(2, "a"); + fxt.Init__add(2, "b"); + fxt.Init__add(2, "c"); + fxt.Test__html_itms + ( "
      " + , "
    • 1 a" + , "
    • " + , "
    • 2 b" + , "
    • " + , "
    • 3 c" + , "
    • " + , "
    " + ); + } + @Test public void D1_D1_D1() { + fxt.Init__add(2, "a"); + fxt.Init__add(3, "a_a"); + fxt.Init__add(4, "a_a_a"); + fxt.Test__html_itms + ( "
      " + , "
    • 1 a" + , " " + , "
    • " + , "
    " + ); + } + @Test public void D1_D1_S0_U1() { + fxt.Init__add(2, "a"); + fxt.Init__add(3, "a_a"); + fxt.Init__add(3, "a_b"); + fxt.Init__add(2, "b"); + fxt.Test__html_itms + ( "
      " + , "
    • 1 a" + , " " + , "
    • " + , "
    • 2 b" + , "
    • " + , "
    " + ); + } + @Test public void D1_D1_U1_D1() { + fxt.Init__add(2, "a"); + fxt.Init__add(3, "a_a"); + fxt.Init__add(2, "b"); + fxt.Init__add(3, "b_a"); + fxt.Test__html_itms + ( "
      " + , "
    • 1 a" + , " " + , "
    • " + , "
    • 2 b" + , " " + , "
    • " + , "
    " + ); + } + @Test public void D1_D1_D1_U2() { + fxt.Init__add(2, "a"); + fxt.Init__add(3, "a_a"); + fxt.Init__add(4, "a_a_a"); + fxt.Init__add(2, "b"); + fxt.Test__html_itms + ( "
      " + , "
    • 1 a" + , " " + , "
    • " + , "
    • 2 b" + , "
    • " + , "
    " + ); + } + @Test public void D1_D2_U1_D1() { + fxt.Init__add(2, "a"); + fxt.Init__add(4, "a_a_a_a"); + fxt.Init__add(3, "a_a_a"); + fxt.Init__add(4, "a_a_a_b"); + fxt.Test__html_itms + ( " " + ); + } + @Test public void Div() { + fxt.Init__init_page("Table of contents", false); + fxt.Init__add(2, "a"); + fxt.Init__add(2, "b"); + fxt.Init__add(2, "c"); + fxt.Test__html_div + ( "
    " + , "
    " + , "

    Table of contents

    " + , "
    " + , "
      " + , "
    • 1 a" + , "
    • " + , "
    • 2 b" + , "
    • " + , "
    • 3 c" + , "
    • " + , "
    " + , "
    " + ); + } +} +class Xoh_toc_htmlr__basic__fxt { + private final Xoh_toc_mgr wtr = new Xoh_toc_mgr(); + private final Bry_bfr bfr = Bry_bfr_.New(); + public void Clear() {wtr.Clear();} + public void Init__add(int hdr_num, String hdr_txt) {wtr.Add(hdr_num, Bry_.new_u8(hdr_txt));} + public void Init__init_page(String toc_title, boolean page_banner) {wtr.Init(gplx.xowa.htmls.core.htmls.tidy.Xow_tidy_mgr_interface_.Noop, Bry_.new_u8(toc_title), Bry_.Empty);} + public void Test__html_itms(String... expd_ary) { + Gftest.Eq__ary(expd_ary, String_.Ary(Bry_split_.Split_lines(wtr.Test__to_html()))); + } + public void Test__html_div(String... expd_ary) { + wtr.To_html(bfr, gplx.xowa.htmls.core.htmls.Xoh_wtr_ctx.Basic, false); + Gftest.Eq__ary(expd_ary, String_.Ary(Bry_split_.Split_lines(bfr.To_bry_and_clear()))); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_itm.java b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_itm.java index a27517de8..dccff94c6 100644 --- a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_itm.java +++ b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_itm.java @@ -13,3 +13,21 @@ 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.addons.htmls.tocs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.htmls.*; +public class Xoh_toc_itm {// EX:
  • 1.1.1 aaa
  • + public int Uid() {return uid;} private int uid; // uid of itm; HTML: "tocsection-3" + public int Lvl() {return lvl;} private int lvl; // indent level; HTML: "toclevel-3" + public int[] Path() {return path;} private int[] path; // path of itm; HTML: "1.1.1" + public byte[] Anch() {return anch;} private byte[] anch; // HTML: "#aaa" + public byte[] Text() {return text;} private byte[] text; // HTML: "aaa" + public byte[] Path_to_bry(Bry_bfr bfr) { + int len = path.length; + for (int i = 0; i < len; ++i) { + if (i != 0) bfr.Add_byte_dot(); + bfr.Add_int_variable(path[i]); + } + return bfr.To_bry_and_clear(); + } + public void Set__lvl(int uid, int lvl, int[] path) {this.uid = uid; this.lvl = lvl; this.path = path;} + public Xoh_toc_itm Set__txt(byte[] anch, byte[] text) {this.anch = anch; this.text = text; return this;} +} diff --git a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_mgr.java b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_mgr.java index a27517de8..9f566c0c7 100644 --- a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_mgr.java @@ -13,3 +13,42 @@ 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.addons.htmls.tocs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.htmls.*; +import gplx.xowa.htmls.core.htmls.*; import gplx.xowa.htmls.core.wkrs.tocs.*; import gplx.xowa.htmls.core.htmls.tidy.*; +public class Xoh_toc_mgr { + private final Ordered_hash itms = Ordered_hash_.New_bry(); + private final Xoh_toc_wkr__lvl lvl_wkr = new Xoh_toc_wkr__lvl(); + private final Xoh_toc_wkr__txt txt_wkr = new Xoh_toc_wkr__txt(); + private final Xoh_toc_htmlr htmlr = new Xoh_toc_htmlr(); + public boolean Exists() {return exists && Enabled;} private boolean exists; + public void Exists_y_() {exists = true;} + public int Toc_bgn() {return toc_bgn;} private int toc_bgn; + public void Toc_bgn_(int v) {this.toc_bgn = v;} + public void Clear() { + this.exists = false; + itms.Clear(); + lvl_wkr.Clear(); + txt_wkr.Clear(); + htmlr.Clear(); + toc_bgn = -1; + } + public void Init(Xow_tidy_mgr_interface tidy_mgr, byte[] toc_title, byte[] page_name) { + this.Clear(); + htmlr.Init(toc_title); + txt_wkr.Init(tidy_mgr, page_name); + } + public Xoh_toc_itm Add(int hdr_num, byte[] hdr_txt) { + Xoh_toc_itm itm = new Xoh_toc_itm(); + lvl_wkr.Calc_level(itm, hdr_num); + txt_wkr.Calc_anch_text(itm, hdr_txt); + itms.Add(itm.Anch(), itm); + return itm; + } + public void To_html(Bry_bfr rv, Xoh_wtr_ctx hctx, boolean toc_mode_is_pgbnr) {htmlr.To_html(rv, hctx, itms, toc_mode_is_pgbnr);} + public byte[] Test__to_html() { + Bry_bfr bfr = Bry_bfr_.New(); + htmlr.Test__to_html(bfr, itms); + return bfr.To_bry_and_clear(); + } + public static boolean Enabled = true; // TEST +} diff --git a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_wkr__lvl.java b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_wkr__lvl.java index a27517de8..a1922167c 100644 --- a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_wkr__lvl.java +++ b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_wkr__lvl.java @@ -13,3 +13,61 @@ 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.addons.htmls.tocs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.htmls.*; +class Xoh_toc_wkr__lvl { + private static final int Toc_lvls_max = 7; + private final int[] sub_lvl_count = new int[Toc_lvls_max], lvl_count = new int[Toc_lvls_max]; + private int prv_lvl, toc_lvl, prv_toc_lvl; + private int uid = 0; + public void Clear() { + uid = prv_lvl = toc_lvl = prv_toc_lvl = 0; + } + public void Calc_level(Xoh_toc_itm rv, int lvl) { // REF.MW:Parser.php!formatHeadings + if (lvl > prv_lvl) { // Increase TOC lvl + toc_lvl++; + sub_lvl_count[toc_lvl - List_adp_.Base1] = 0; + if (toc_lvl < Toc_lvls_max) { + prv_toc_lvl = toc_lvl; + // $toc .= Linker::tocIndent(); + } + } + else if (lvl < prv_lvl && toc_lvl > 1) {// Decrease TOC lvl, find lvl to jump to + int i = toc_lvl; + for (; i > 0; i--) { + int cur_lvl_count = lvl_count[i]; + if (cur_lvl_count == lvl) { // Found last matching lvl + toc_lvl = i; + break; + } + else if (cur_lvl_count < lvl) { // Found first matching lvl below current lvl + toc_lvl = i + 1; + break; + } + } + if (i == 0) + toc_lvl = 1; + if (toc_lvl < Toc_lvls_max) { + if (prv_toc_lvl < Toc_lvls_max) { + // Unindent only if the previous toc lvl was shown :p + // $toc .= Linker::tocUnindent( $prv_toc_lvl - $toc_lvl ); + prv_toc_lvl = toc_lvl; + } else { + // $toc .= Linker::tocLineEnd(); + } + } + } + else { // No change in lvl, end TOC line + if (toc_lvl < Toc_lvls_max) { + // $toc .= Linker::tocLineEnd(); + } + } + lvl_count[toc_lvl] = lvl; + sub_lvl_count[toc_lvl - List_adp_.Base1] = sub_lvl_count[toc_lvl - List_adp_.Base1] + 1; + prv_lvl = lvl; // NOTE: same as "if ( $toclevel ) $prevlevel = $level;" but at end of block + + // Tfds.Write(lvl, prv_lvl, lvl, toc_lvl, Int_ary_.To_str(",", lvl_count), Int_ary_.To_str(",", sub_lvl_count)); + int[] copy = new int[toc_lvl]; + Int_ary_.Copy_to(sub_lvl_count, toc_lvl, copy); + rv.Set__lvl(++uid, toc_lvl, copy); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_wkr__lvl__basic__tst.java b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_wkr__lvl__basic__tst.java index a27517de8..86491c836 100644 --- a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_wkr__lvl__basic__tst.java +++ b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_wkr__lvl__basic__tst.java @@ -13,3 +13,52 @@ 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.addons.htmls.tocs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.htmls.*; +import org.junit.*; import gplx.core.tests.*; +public class Xoh_toc_wkr__lvl__basic__tst { + @Before public void init() {fxt.Clear();} private final Xoh_toc_wkr__lvl__fxt fxt = new Xoh_toc_wkr__lvl__fxt(); + @Test public void D1_S0_S0() { + fxt.Test__calc(2, fxt.Make(1, 1, Int_ary_.New(1))); + fxt.Test__calc(2, fxt.Make(2, 1, Int_ary_.New(2))); + fxt.Test__calc(2, fxt.Make(3, 1, Int_ary_.New(3))); + } + @Test public void D1_D1_D1() { + fxt.Test__calc(2, fxt.Make(1, 1, Int_ary_.New(1))); + fxt.Test__calc(3, fxt.Make(2, 2, Int_ary_.New(1, 1))); + fxt.Test__calc(4, fxt.Make(3, 3, Int_ary_.New(1, 1, 1))); + } + @Test public void D1_D1_S0_U1() { + fxt.Test__calc(2, fxt.Make(1, 1, Int_ary_.New(1))); + fxt.Test__calc(3, fxt.Make(2, 2, Int_ary_.New(1, 1))); + fxt.Test__calc(3, fxt.Make(3, 2, Int_ary_.New(1, 2))); + fxt.Test__calc(2, fxt.Make(4, 1, Int_ary_.New(2))); + } + @Test public void D1_D1_U1_D1() { + fxt.Test__calc(2, fxt.Make(1, 1, Int_ary_.New(1))); + fxt.Test__calc(3, fxt.Make(2, 2, Int_ary_.New(1, 1))); + fxt.Test__calc(2, fxt.Make(3, 1, Int_ary_.New(2))); + fxt.Test__calc(3, fxt.Make(4, 2, Int_ary_.New(2, 1))); + } + @Test public void D1_D1_D1_U2() { + fxt.Test__calc(2, fxt.Make(1, 1, Int_ary_.New(1))); + fxt.Test__calc(3, fxt.Make(2, 2, Int_ary_.New(1, 1))); + fxt.Test__calc(4, fxt.Make(3, 3, Int_ary_.New(1, 1, 1))); + fxt.Test__calc(2, fxt.Make(4, 1, Int_ary_.New(2))); + } +} +class Xoh_toc_wkr__lvl__fxt { + private final Xoh_toc_wkr__lvl wkr = new Xoh_toc_wkr__lvl(); + private final Xoh_toc_itm actl = new Xoh_toc_itm(); + public void Clear() {wkr.Clear();} + public Xoh_toc_itm Make(int uid, int lvl, int[] path) { + Xoh_toc_itm rv = new Xoh_toc_itm(); + rv.Set__lvl(uid, lvl, path); + return rv; + } + public void Test__calc(int lvl, Xoh_toc_itm expd) { + wkr.Calc_level(actl, lvl); + Gftest.Eq__int(expd.Uid(), actl.Uid(), "uid"); + Gftest.Eq__int(expd.Lvl(), actl.Lvl(), "lvl"); + Gftest.Eq__ary(expd.Path(), actl.Path(), "path"); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_wkr__txt.java b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_wkr__txt.java index a27517de8..4c7b116f4 100644 --- a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_wkr__txt.java +++ b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_wkr__txt.java @@ -13,3 +13,162 @@ 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.addons.htmls.tocs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.htmls.*; +import gplx.langs.htmls.*; import gplx.langs.htmls.docs.*; import gplx.langs.htmls.encoders.*; import gplx.xowa.htmls.core.htmls.tidy.*; +import gplx.xowa.parsers.amps.*; import gplx.core.primitives.*; +class Xoh_toc_wkr__txt { + private final Gfh_tag_rdr tag_rdr = Gfh_tag_rdr.New__html(); + private final Bry_bfr anch_bfr = Bry_bfr_.New(), text_bfr = Bry_bfr_.New(); + private final Gfo_url_encoder anch_encoder = Gfo_url_encoder_.New__id(); + private final Xop_amp_mgr amp_mgr = Xop_amp_mgr.Instance; + private final Hash_adp anch_hash = Hash_adp_bry.ci_u8(gplx.xowa.langs.cases.Xol_case_mgr_.U8()); + private Xow_tidy_mgr_interface tidy_mgr; + private byte[] page_name; + public void Clear() { + anch_bfr.Clear(); + text_bfr.Clear(); + anch_hash.Clear(); + } + public void Init(Xow_tidy_mgr_interface tidy_mgr, byte[] page_name) { + this.tidy_mgr = tidy_mgr; + this.page_name = page_name; + } + public void Calc_anch_text(Xoh_toc_itm rv, byte[] src) { // text within hdr; EX:

    Abc

    -> Abc + int end = src.length; + src = Gfh_utl.Del_comments(text_bfr, src, 0, end); + end = src.length; + tag_rdr.Init(page_name, src, 0, end); + try { + if (!Calc_anch_text_recurse(src, 0, end)) { + Gfo_usr_dlg_.Instance.Log_many("", "", "toc:invalid html; page=~{0} src=~{1}", page_name, src); + + // tidy html; note reusing text_bfr as temp bfr + text_bfr.Clear().Add(src); + tidy_mgr.Exec_tidy(text_bfr, Bool_.N, page_name); + src = text_bfr.To_bry_and_clear(); + end = src.length; + tag_rdr.Init(page_name, src, 0, end); + + // try to calc again; if fail, give up + if (!Calc_anch_text_recurse(src, 0, end)) + throw Err_.new_wo_type("could not tidy html"); + } + } catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "toc:failed while generating anch_text; page=~{0} src=~{1} err=~{2}", page_name, src, Err_.Message_gplx_log(e)); + text_bfr.Clear().Add(src); + anch_encoder.Encode(anch_bfr, src); + } + + byte[] anch_bry = anch_bfr.To_bry_and_clear_and_trim(Bool_.Y, Bool_.Y, Trim__id); + if (anch_hash.Has(anch_bry)) { + int anch_idx = 2; + while (true) { // NOTE: this is not big-O performant, but it mirrors MW; DATE:2016-07-09 + byte[] anch_tmp = Bry_.Add(anch_bry, Byte_ascii.Underline_bry, Int_.To_bry(anch_idx++)); + if (!anch_hash.Has(anch_tmp)) { + anch_bry = anch_tmp; + break; + } + } + } + anch_hash.Add_as_key_and_val(anch_bry); + rv.Set__txt + ( anch_bry + , text_bfr.To_bry_and_clear_and_trim()); // NOTE: both id and text trim ends + } + private boolean Calc_anch_text_recurse(byte[] src, int pos, int end) { + tag_rdr.Src_rng_(pos, end); + while (pos < end) { + Gfh_tag lhs = tag_rdr.Tag__move_fwd_head(); + int tag_id = lhs.Name_id(); + byte[] span_dir = null; + + // add any text before lhs; + int txt_end = lhs.Src_bgn(); + switch (tag_id) { + case Gfh_tag_.Id__eos: txt_end = end; break; // eos; print everything until end + } + + // add any text before tag + if (pos < txt_end) { + byte[] anch_bry = amp_mgr.Decode_as_bry(Bry_.Trim(src, pos, txt_end, Bool_.Y, Bool_.Y, Trim__anch)); // trim \n else ".0a"; PAGE:en.w:List_of_U-boats_never_deployed DATE:2016-08-13 + anch_encoder.Encode(anch_bfr, anch_bry); + text_bfr.Add_mid(src, pos, txt_end); + } + + // set print_tag tag; REF.MW:Parser.php!formatHeadings + boolean print_tag = false; + switch (tag_id) { + case Gfh_tag_.Id__eos: // eos; return; + return true; + case Gfh_tag_.Id__sup: // always print tag; REF.MW:Parser.php!formatHeadings!"Allowed tags are" + case Gfh_tag_.Id__sub: + case Gfh_tag_.Id__i: + case Gfh_tag_.Id__b: + case Gfh_tag_.Id__bdi: + print_tag = true; + break; + case Gfh_tag_.Id__span: // print span only if it has a dir attribute + span_dir = lhs.Atrs__get_as_bry(Gfh_atr_.Bry__dir); + print_tag = Bry_.Len_gt_0(span_dir); + break; + case Gfh_tag_.Id__comment: // never print tag + default: + print_tag = false; + break; + case Gfh_tag_.Id__any: // unknown tags print + print_tag = true; + break; + } + + // get lhs / rhs vars + byte[] lhs_bry = lhs.Name_bry(); + int lhs_end = lhs.Src_end(); + + // ignore tags which are not closed by tidy as default; EX:
    not
    a
    or
    + boolean lhs_is_dangling = false; + switch (tag_id) { + case Gfh_tag_.Id__img: + case Gfh_tag_.Id__br: + case Gfh_tag_.Id__hr: + case Gfh_tag_.Id__wbr: + lhs_is_dangling = true; + break; + } + boolean lhs_is_pair = !lhs.Tag_is_inline() && !lhs_is_dangling; + int rhs_bgn = -1, rhs_end = -1, new_pos = lhs_end; + if (lhs_is_pair) { // get rhs unless inline + if (tag_id == Gfh_tag_.Id__any) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "unknown tag in toc: page=~{0} tag=~{1}", page_name, lhs_bry); + Gfh_tag rhs = tag_rdr.Tag__peek_fwd_tail(lhs_bry); + if (rhs.Name_id() == Gfh_tag_.Id__eos) return false; + rhs_bgn = rhs.Src_bgn(); rhs_end = rhs.Src_end(); + new_pos = rhs_end; + } + else { + Gfh_tag rhs = tag_rdr.Tag__peek_fwd_tail(tag_id); + if (rhs.Name_id() == Gfh_tag_.Id__eos) return false; + rhs_bgn = rhs.Src_bgn(); rhs_end = rhs.Src_end(); + new_pos = rhs_end; + } + } + + // print ""; also, recurse + if (print_tag) { + text_bfr.Add_byte(Byte_ascii.Angle_bgn).Add(lhs_bry); + if (span_dir != null) // if span has dir, add it; EX: -> + Gfh_atr_.Add(text_bfr, Gfh_atr_.Bry__dir, span_dir); + text_bfr.Add_byte(Byte_ascii.Angle_end); // only add name; do not add atrs; EX: -> + } + if (!Calc_anch_text_recurse(src, lhs_end, rhs_bgn)) return false; + if (print_tag && lhs_is_pair) + text_bfr.Add_mid(src, rhs_bgn, rhs_end); + + // set new_pos + pos = new_pos; + tag_rdr.Src_rng_(new_pos, end); // NOTE: must reinit pos and especially end + } + return true; + } + + private static final byte[] Trim__id = Bry_.mask_(256, Byte_ascii.Underline_bry), Trim__anch = Bry_.mask_(256, Byte_ascii.Tab, Byte_ascii.Nl, Byte_ascii.Cr); +} diff --git a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_wkr__txt__basic__tst.java b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_wkr__txt__basic__tst.java index a27517de8..b43de84d9 100644 --- a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_wkr__txt__basic__tst.java +++ b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xoh_toc_wkr__txt__basic__tst.java @@ -13,3 +13,72 @@ 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.addons.htmls.tocs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.htmls.*; +import org.junit.*; import gplx.core.tests.*; import gplx.langs.htmls.*; +public class Xoh_toc_wkr__txt__basic__tst { + @Before public void init() {fxt.Clear();} private final Xoh_toc_wkr__txt__fxt fxt = new Xoh_toc_wkr__txt__fxt(); + @Test public void Basic() { + fxt.Test__both("a b c", "a_b_c", "a b c"); + } + @Test public void Ws() { + fxt.Test__both(" a b ", "a_b", "a b"); + } + @Test public void Nl() { + fxt.Test__both("\na b\n", "a_b", "a b"); + } + @Test public void Empty() { // PAGE:s.w:Colac,_Victoria DATE:2016-07-17 + fxt.Test__both("", "", ""); + } + @Test public void Amp__ncr() { + fxt.Test__both("[a]", ".5Ba.5D", "[a]"); + } + @Test public void Encode() { + fxt.Test__both("a+b", "a.2Bb", "a+b"); + } + @Test public void Comment() { + fxt.Test__text("ac", "ac"); + } + @Test public void Remove_comment__one() { + fxt.Test__remove_comment("ac", "ac"); + } + @Test public void Remove_comment__many() { + fxt.Test__remove_comment("135", "135"); + } + @Test public void Remove_comment__dangling() { + fxt.Test__remove_comment("13ac==" + ), fxt.toc_tbl_nl_y + ( "
      " + , "
    • 1 ac" + , "
    • " + , "
    " + )); + } + @Test public void Ref() { // PURPOSE: ref contents should not print in TOC; DATE:2013-07-23 + fxt.Test_html_all(String_.Concat_lines_nl_skip_last + ( "__FORCETOC__" + , "==ab==" + ) + , String_.Concat_lines_nl + ( fxt.toc_tbl_nl_n + ( "
      " + , "
    • 1 a[1]" + , "
    • " + , "
    " + ) + , "

    a[1]

    " + )); + } + @Test public void Category() { // PURPOSE: Category should not show in in TOC; DATE:2013-12-09 + fxt.Test_html_all(String_.Concat_lines_nl_skip_last + ( "__FORCETOC__" + , "==A[[Category:B]]==" + ) + , String_.Concat_lines_nl + ( fxt.toc_tbl_nl_n + ( "
      " + , "
    • 1 A" + , "
    • " + , "
    " + ) + , "

    A

    " + )); + } + @Test public void Category_literal() { // PURPOSE: literal Category should show in in TOC; EX: de.w:1234; DATE:2014-01-21 + fxt.Test_html_all(String_.Concat_lines_nl_skip_last + ( "__FORCETOC__" + , "==A[[:Category:B]]==" + ) + , String_.Concat_lines_nl + ( fxt.toc_tbl_nl_n + ( " " + ) + , "

    ACategory:B

    " + )); + } + @Test public void File() { // PURPOSE: file should show in in TOC; EX: tr.w:D�nya_Miraslari; DATE:2014-06-06 + fxt.Test_html_all(String_.Concat_lines_nl_skip_last + ( "__FORCETOC__" + , "==[[File:A.png]] b==" + ) + , String_.Concat_lines_nl + ( fxt.toc_tbl_nl_n + ( "
      " + , "
    • 1 b" + , "
    • " + , "
    " + ) + , "

    \"\" b

    " + )); + } + @Test public void Lnki_invalid() { // PURPOSE: invalid lnki was causing null ref; DATE:2014-02-07 + fxt.Test_html_all(String_.Concat_lines_nl_skip_last + ( "__FORCETOC__" + , "==[[]]==" + ) + , String_.Concat_lines_nl + ( fxt.toc_tbl_nl_n + ( "
      " + , "
    • 1 [[]]" + , "
    • " + , "
    " + ) + , "

    [[]]

    " + )); + } + @Test public void File_in_tbl() { // PURPOSE: two issues (a) don't show file if in tbl; (b) if v2, file inside tbl fails; PAGE:en.w:Holmes County,_Mississippi; DATE:2014-06-22 + fxt.Test_html_frag(String_.Concat_lines_nl_skip_last + ( "__FORCETOC__" + , "==a
    [[File:A.png]]b
    c==" + ) + , "a b c" // note that "b" inside tbl shows + ); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xowe_hdr_bldr_fxt.java b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xowe_hdr_bldr_fxt.java index a27517de8..219903fe8 100644 --- a/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xowe_hdr_bldr_fxt.java +++ b/400_xowa/src/gplx/xowa/addons/htmls/tocs/Xowe_hdr_bldr_fxt.java @@ -13,3 +13,57 @@ 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.addons.htmls.tocs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.htmls.*; +import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.htmls.*; +public class Xowe_hdr_bldr_fxt { + private final Bry_bfr tmp = Bry_bfr_.New(); + public Xop_fxt Fxt() {return fxt;} private final Xop_fxt fxt = new Xop_fxt(); + public void Clear() { + fxt.Reset(); + tmp.Clear(); + } + public void Test_html_toc(String raw, String expd) { + fxt.Wtr_cfg().Toc__show_(Bool_.Y); + String actl = Bld_page_with_toc(tmp, fxt, raw); + + // HACK: proc only tests TOC; remove " + , "
    " + , "

    Contents

    " + , "
    " + , String_.Concat_lines_nl_skip_last(ary) + , "" + (nl ? "\n" : "") + ); + } + public static String Bld_page_with_toc(Bry_bfr bfr, Xop_fxt fxt, String raw) { + gplx.xowa.parsers.Xop_root_tkn root = fxt.Exec_parse_page_all_as_root(Bry_.new_u8(raw)); + fxt.Ctx().Page_data().Copy_to(fxt.Page()); + fxt.Wiki().Html_mgr().Html_wtr().Write_doc(bfr, fxt.Ctx(), fxt.Hctx(), root.Root_src(), root); + + gplx.xowa.htmls.core.wkrs.tocs.Xoh_toc_wtr.Write_toc(bfr, fxt.Page(), Xoh_wtr_ctx.Basic); + return bfr.To_str_and_clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xop_mediawiki_loader.java b/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xop_mediawiki_loader.java index a27517de8..3938b3dbe 100644 --- a/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xop_mediawiki_loader.java +++ b/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xop_mediawiki_loader.java @@ -13,3 +13,7 @@ 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.addons.parsers.mediawikis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.parsers.*; +public interface Xop_mediawiki_loader { + String LoadWikitext(String page); +} diff --git a/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xop_mediawiki_mgr.java b/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xop_mediawiki_mgr.java index a27517de8..ca11d6314 100644 --- a/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xop_mediawiki_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xop_mediawiki_mgr.java @@ -13,3 +13,54 @@ 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.addons.parsers.mediawikis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.parsers.*; +public class Xop_mediawiki_mgr { + private final Xoae_app app; + private boolean mode_is_prod; + public Xop_mediawiki_mgr(String root_str, boolean mode_is_prod) { + Gfo_usr_dlg usr_dlg = Xoa_app_.New__usr_dlg__console(); + Gfo_usr_dlg_.Instance = usr_dlg; + Io_url root_dir = Io_url_.new_dir_(root_str); + + this.mode_is_prod = mode_is_prod; + if (mode_is_prod) { + gplx.dbs.Db_conn_bldr.Instance.Reg_default_sqlite(); + gplx.core.envs.Env_.Init_swt(String_.Ary_empty, Type_.Type_by_obj(this)); // must call Init else unit_testing will be true + } + this.app = new Xoae_app(usr_dlg, gplx.xowa.apps.Xoa_app_mode.Itm_cmd + , root_dir + , root_dir.GenSubDir("wiki") + , root_dir.GenSubDir("file") + , root_dir.GenSubDir("user") + , root_dir.GenSubDir_nest("user", "anonymous", "wiki") + , gplx.xowa.apps.boots.Xoa_cmd_arg_mgr.Bin_dir_name() + ); + if (mode_is_prod) { + app.Init_by_app(); + app.Stage_(gplx.xowa.apps.Xoa_stage_.Tid_launch); // must set to Launch, else wiki.init_needed will never be false; DATE:2017-01-26 + } + } + public Xop_mediawiki_wkr Make(String domain_str) {return Make(domain_str, null);} + public Xop_mediawiki_wkr Make(String domain_str, Xop_mediawiki_loader loader) { + Xowe_wiki wiki = (Xowe_wiki)app.Wiki_mgr().Make(Bry_.new_u8(domain_str), app.Fsys_mgr().Wiki_dir().GenSubDir(domain_str)); + if (mode_is_prod) { + wiki.Embeddable_enabled_(true); // must mark wiki as embeddable, else orig_mgr will load wkrs which will download images DATE:2017-10-23 + wiki.Init_by_wiki(); + + // init setup data; xowa_cfg|interwikimap and ns_msg; DATE:2017-10-23 + if (gplx.xowa.wikis.data.Xow_db_file__core_.Find_core_fil_or_null(wiki) == null) { // only run if file does not exist + Xowe_wiki_.Create(wiki, 0, "embeddeable_parser"); + wiki.App().Site_cfg_mgr().Load(wiki); // load interwikimap et al from WM API + wiki.Db_mgr_as_sql().Core_data_mgr().Db__core().Tbl__ns().Insert(wiki.Ns_mgr()); // save ns to xowa_ns + } + + wiki.File_mgr().Version_2_y_(); // must set to version_2 else video files will use old v1 Meta_code; DATE:2017-01-26 + wiki.File_mgr().Fsdb_mode().Tid__v2__mp__y_(); // must set to mass_parse mode, else will use old v1 Meta_code for xfer_itm and url_bldr; DATE:2017-01-26 + } + return new Xop_mediawiki_wkr(wiki, loader); + } + + public static Xop_mediawiki_mgr New(String root_str) { + return new Xop_mediawiki_mgr(root_str, true); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xop_mediawiki_wkr.java b/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xop_mediawiki_wkr.java index a27517de8..a2c2601c5 100644 --- a/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xop_mediawiki_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xop_mediawiki_wkr.java @@ -13,3 +13,69 @@ 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.addons.parsers.mediawikis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.parsers.*; +import gplx.xowa.wikis.*; import gplx.xowa.parsers.*; import gplx.xowa.wikis.pages.*; import gplx.xowa.htmls.core.htmls.*; +import gplx.xowa.wikis.caches.*; +import gplx.xowa.addons.wikis.ctgs.htmls.pageboxs.*; +public class Xop_mediawiki_wkr { + private final Xowe_wiki wiki; + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + public Xop_mediawiki_wkr(Xowe_wiki wiki, Xop_mediawiki_loader loader) { + this.wiki = wiki; + this.Loader_(loader); + } + public void Loader_(Xop_mediawiki_loader loader) { + if (loader != null) + wiki.Cache_mgr().Load_wkr_(new Xow_page_cache_wkr__embeddable(wiki, loader)); + } + public void Free_memory() { + wiki.Cache_mgr().Tmpl_result_cache().Clear(); + wiki.Cache_mgr().Free_mem__page(); + wiki.Parser_mgr().Scrib().Core_term(); + wiki.Appe().Wiki_mgr().Wdata_mgr().Clear(); + } + public void Clear_cache(String page) { + Xoa_ttl ttl = wiki.Ttl_parse(Bry_.new_u8(page)); + wiki.Cache_mgr().Page_cache().Del(ttl.Full_db()); + } + public String Parse(String page, String wikitext) { + Xoa_ttl ttl = wiki.Ttl_parse(Bry_.new_u8(page)); + + byte[] wtxt = Bry_.new_u8(wikitext); + Xoae_page wpg = Xoae_page.New(wiki, ttl); + wpg.Db().Text().Text_bry_(wtxt); + + Xow_parser_mgr parser_mgr = wiki.Parser_mgr(); + + // parse page + Xop_ctx pctx = parser_mgr.Ctx(); + pctx.Clear_all(); + parser_mgr.Parse(wpg, true); + + // write to html + boolean is_wikitext = Xow_page_tid.Identify(wpg.Wiki().Domain_tid(), ttl.Ns().Id(), ttl.Page_db()) == Xow_page_tid.Tid_wikitext; + byte[] orig_bry = Bry_.Empty; + if (is_wikitext) { + wiki.Html_mgr().Page_wtr_mgr().Wkr(Xopg_page_.Tid_read).Write_hdump(tmp_bfr, pctx, Xoh_wtr_ctx.Hdump, wpg); + + // write categories + int ctgs_len = wpg.Wtxt().Ctgs__len(); + if ( ctgs_len > 0 // skip if no categories found while parsing wikitext + ) { + Xoctg_pagebox_itm[] pagebox_itms = new Xoctg_pagebox_itm[ctgs_len]; + for (int i = 0; i < ctgs_len; i++) { + pagebox_itms[i] = new Xoctg_pagebox_itm(wpg.Wtxt().Ctgs__get_at(i)); + } + wiki.Ctg__pagebox_wtr().Write_pagebox(tmp_bfr, wiki, wpg, pagebox_itms); + } + + orig_bry = tmp_bfr.To_bry_and_clear(); + wpg.Db().Html().Html_bry_(orig_bry); + } + else { // not wikitext; EX: pages in MediaWiki: ns; DATE:2016-09-12 + wpg.Db().Html().Html_bry_(wpg.Db().Text().Text_bry()); + } + + return String_.new_u8(orig_bry); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xop_mediawiki_wkr__tst.java b/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xop_mediawiki_wkr__tst.java index a27517de8..6aaa50088 100644 --- a/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xop_mediawiki_wkr__tst.java +++ b/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xop_mediawiki_wkr__tst.java @@ -13,3 +13,42 @@ 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.addons.parsers.mediawikis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.parsers.*; +import org.junit.*; import gplx.core.tests.*; +public class Xop_mediawiki_wkr__tst { + private final Xop_mediawiki_wkr__fxt fxt = new Xop_mediawiki_wkr__fxt(); + @After public void term() {Gfo_usr_dlg_.Instance = Gfo_usr_dlg_.Noop;} + @Test public void Basic() { + fxt.Init__wkr("en.wikipedia.org", null); + fxt.Test__parse("Page_1", "''{{PAGENAME}}''" + , "

    Page 1" + , "

    " + ); + } + @Test public void Template() { + fxt.Init__wkr("en.wikipedia.org", new Xop_mediawiki_loader__mock()); + fxt.Test__parse("Page_1", "{{bold}}" + , "

    bold" + , "

    " + ); + } +} +class Xop_mediawiki_wkr__fxt { + private final Xop_mediawiki_mgr mgr = new Xop_mediawiki_mgr("mem/xowa/wiki/en.wikipedia.org/", false); + private Xop_mediawiki_wkr wkr; + public Xop_mediawiki_wkr__fxt() { + gplx.dbs.Db_conn_bldr.Instance.Reg_default_mem(); + } + public void Init__wkr(String wiki, Xop_mediawiki_loader cbk) { + this.wkr = mgr.Make(wiki, cbk); + } + public void Test__parse(String page, String wtxt, String... expd) { + Gftest.Eq__ary__lines(String_.Concat_lines_nl_skip_last(expd), wkr.Parse(page, wtxt), "parse failed; wtxt={0}", wtxt); + } +} +class Xop_mediawiki_loader__mock implements Xop_mediawiki_loader { + public String LoadWikitext(String page) { + if (String_.Eq(page, "Template:Bold")) return "'''bold'''"; + else return "text"; + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xow_page_cache_wkr__embeddable.java b/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xow_page_cache_wkr__embeddable.java index a27517de8..88468f983 100644 --- a/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xow_page_cache_wkr__embeddable.java +++ b/400_xowa/src/gplx/xowa/addons/parsers/mediawikis/Xow_page_cache_wkr__embeddable.java @@ -13,3 +13,33 @@ 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.addons.parsers.mediawikis; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.parsers.*; +import gplx.xowa.parsers.utils.*; +class Xow_page_cache_wkr__embeddable implements gplx.xowa.wikis.caches.Xow_page_cache_wkr { + private final Xop_mediawiki_loader cbk; + private final Xop_redirect_mgr redirect_mgr; + public Xow_page_cache_wkr__embeddable(Xowe_wiki wiki, Xop_mediawiki_loader cbk) { + this.cbk = cbk; + this.redirect_mgr = new Xop_redirect_mgr(wiki); + } + public byte[] Get_page_or_null(byte[] full_db) { + byte[] wikitext = null; + + // loop to handle redirects; DATE:2017-05-29 + int loops = 0; + while (loops++ < 5) { + wikitext = Bry_.new_u8(cbk.LoadWikitext(String_.new_u8(full_db))); + Xoa_ttl redirect_ttl = redirect_mgr.Extract_redirect(wikitext); + // not a redirect; exit loop + if (redirect_ttl == null) { + break; + } + // redirect; update title and continue; + else { + full_db = redirect_ttl.Full_db(); + continue; + } + } + return wikitext; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/servers/https/Http_long_poll_cmd.java b/400_xowa/src/gplx/xowa/addons/servers/https/Http_long_poll_cmd.java index a27517de8..d6e4e1707 100644 --- a/400_xowa/src/gplx/xowa/addons/servers/https/Http_long_poll_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/servers/https/Http_long_poll_cmd.java @@ -13,3 +13,71 @@ 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.addons.servers.https; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.servers.*; +import gplx.core.envs.*; +public class Http_long_poll_cmd implements gplx.xowa.htmls.bridges.Bridge_cmd_itm { + private final List_adp msgs = List_adp_.New(); + private String prv_guid; + private long prv_start; + private long prv_timeout; + + public void Init_by_app(Xoa_app app) { + app.Gui__cbk_mgr().Reg(Xog_cbk_wkr__http.Instance); + } + public void Send_msg(String msg) { + synchronized (msgs) { + msgs.Add(msg); + } + } + // NOTE: this class is a singleton and only supports one user; need to track multiple requests by having http_server track incoming users + public String Exec(gplx.langs.jsons.Json_nde data) { + // for each new request, update guid / start_time + String cur_guid = data.Get_as_str_or("guid", ""); + long cur_timeout = data.Get_as_long_or("timeout", 5000); + synchronized (msgs) { + this.prv_guid = cur_guid; + this.prv_start = System_.Ticks(); + this.prv_timeout = cur_timeout; + } + + // check if already active; if so, return; + while (true) { + synchronized (msgs) { + if (!String_.Eq(cur_guid, prv_guid)) + return String_.Format("long-poll ignored: new long-poll arrived: prv={0} cur={1}", prv_guid, cur_guid); + + if (System_.Ticks__elapsed_in_frac(prv_start) > prv_timeout) + return String_.Format("long-poll ignored: old long-poll timed-out: guid={0}", cur_guid); + } + + // get msgs in queue + int msgs_len = 0; + synchronized (msgs) { + msgs_len = msgs.Len(); + } + + // no messages + if (msgs_len == 0) { + gplx.core.threads.Thread_adp_.Sleep(Sleep_interval); + continue; + } + + // message found; exit loop; + break; + } + + // return commands + String[] rv = null; + synchronized (msgs) { + rv = msgs.To_str_ary_and_clear(); + } + return String_.Concat_lines_nl(rv); + } + + public byte[] Key() {return BRIDGE_KEY;} private static final byte[] BRIDGE_KEY = Bry_.new_a7("long_poll"); + public static final Http_long_poll_cmd Instance = new Http_long_poll_cmd(); Http_long_poll_cmd() {} + + private static final int + Sleep_interval = 100 + ; +} diff --git a/400_xowa/src/gplx/xowa/addons/servers/https/Http_send_msg_cmd.java b/400_xowa/src/gplx/xowa/addons/servers/https/Http_send_msg_cmd.java index a27517de8..db5feeceb 100644 --- a/400_xowa/src/gplx/xowa/addons/servers/https/Http_send_msg_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/servers/https/Http_send_msg_cmd.java @@ -13,3 +13,15 @@ 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.addons.servers.https; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.servers.*; +public class Http_send_msg_cmd implements gplx.xowa.htmls.bridges.Bridge_cmd_itm { + public void Init_by_app(Xoa_app app) {} + public String Exec(gplx.langs.jsons.Json_nde data) { + gplx.langs.jsons.Json_nde jnde = (gplx.langs.jsons.Json_nde)data.Get_as_itm_or_null(Bry_.new_a7("msg")); + Http_long_poll_cmd.Instance.Send_msg(jnde.Print_as_json()); + return "{}"; + } + + public byte[] Key() {return BRIDGE_KEY;} + public static final byte[] BRIDGE_KEY = Bry_.new_a7("send_msg"); +} diff --git a/400_xowa/src/gplx/xowa/addons/servers/https/Xoax_long_poll_addon.java b/400_xowa/src/gplx/xowa/addons/servers/https/Xoax_long_poll_addon.java index a27517de8..192e304f9 100644 --- a/400_xowa/src/gplx/xowa/addons/servers/https/Xoax_long_poll_addon.java +++ b/400_xowa/src/gplx/xowa/addons/servers/https/Xoax_long_poll_addon.java @@ -13,3 +13,16 @@ 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.addons.servers.https; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.servers.*; +import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.htmls.bridges.*; +public class Xoax_long_poll_addon implements Xoax_addon_itm, Xoax_addon_itm__json { + public Bridge_cmd_itm[] Json_cmds() { + return new Bridge_cmd_itm[] + { Http_long_poll_cmd.Instance + , new Http_send_msg_cmd() + }; + } + + public String Addon__key() {return "xowa.servers.https.long_poll";} +} diff --git a/400_xowa/src/gplx/xowa/addons/servers/https/Xog_cbk_wkr__http.java b/400_xowa/src/gplx/xowa/addons/servers/https/Xog_cbk_wkr__http.java index a27517de8..04f3267e9 100644 --- a/400_xowa/src/gplx/xowa/addons/servers/https/Xog_cbk_wkr__http.java +++ b/400_xowa/src/gplx/xowa/addons/servers/https/Xog_cbk_wkr__http.java @@ -13,3 +13,17 @@ 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.addons.servers.https; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.servers.*; +import gplx.xowa.guis.cbks.*; import gplx.core.gfobjs.*; import gplx.xowa.guis.cbks.swts.*; +public class Xog_cbk_wkr__http implements Xog_cbk_wkr { + private final Gfobj_wtr__json__browser json_wtr = new Gfobj_wtr__json__browser(); + public Object Send_json(Xog_cbk_trg trg, String func, Gfobj_nde data) { + String script = json_wtr.Write_as_func__drd(func, data); + Http_long_poll_cmd.Instance.Send_msg(script); + return null; + } + public void Send_prog(String head) { + Http_long_poll_cmd.Instance.Send_msg(head); + } + public static final Xog_cbk_wkr__http Instance = new Xog_cbk_wkr__http(); Xog_cbk_wkr__http() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/Xoa_ctg_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/Xoa_ctg_mgr.java index a27517de8..aa77711b2 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/Xoa_ctg_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/Xoa_ctg_mgr.java @@ -13,3 +13,20 @@ 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.addons.wikis.ctgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; +import gplx.xowa.addons.wikis.ctgs.htmls.*; +public class Xoa_ctg_mgr { + public static final byte Version_null = Byte_.Zero, Version_1 = 1, Version_2 = 2; + public static final byte Tid__subc = 0, Tid__file = 1, Tid__page = 2, Tid___max = 3; // SERIALIZED; cat_link.cl_type_id + public static final byte Hidden_n = Byte_.Zero, Hidden_y = (byte)1; + public static final String Html__cls__str = "CategoryTreeLabel CategoryTreeLabelNs14 CategoryTreeLabelCategory"; + public static final byte[] Html__cls__bry = Bry_.new_a7(Html__cls__str); + + public static byte To_tid_by_ns(int ns) { + switch (ns) { + case gplx.xowa.wikis.nss.Xow_ns_.Tid__category: return Tid__subc; + case gplx.xowa.wikis.nss.Xow_ns_.Tid__file : return Tid__file; + default : return Tid__page; + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/Xoax_ctg_addon.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/Xoax_ctg_addon.java index a27517de8..dc52a3471 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/Xoax_ctg_addon.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/Xoax_ctg_addon.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.addons.wikis.ctgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; +import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.wikis.ctgs.dbs.*; +public class Xoax_ctg_addon implements Xoax_addon_itm { // TODO_OLD:mem_mgr + private final Xow_wiki wiki; + private final Hash_adp_bry hash = Hash_adp_bry.cs(); + public Xoax_ctg_addon(Xow_wiki wiki) {this.wiki = wiki;} + public Xoctg_ctg_itm Itms__get_or_null(byte[] key) {return (Xoctg_ctg_itm)hash.Get_by_bry(key);} + public Xoctg_ctg_itm Itms__load(byte[] key) { + Xowd_page_itm tmp_page = new Xowd_page_itm(); + wiki.Data__core_mgr().Tbl__page().Select_by_ttl(tmp_page, wiki.Ns_mgr().Ns_category(), key); + gplx.xowa.wikis.data.tbls.Xowd_category_itm itm = Xodb_cat_db_.Get_cat_core_or_fail(wiki.Data__core_mgr()).Select(tmp_page.Id()); + return Itms__add(key, itm.Count_pages(), itm.Count_subcs(), itm.Count_files()); + } + public Xoctg_ctg_itm Itms__add(byte[] key, int pages, int subcs, int files) { // TEST: + Xoctg_ctg_itm rv = new Xoctg_ctg_itm(key, pages, subcs, files); + hash.Add(key, rv); + return rv; + } +/* + public long Mem__size__max() {return mem__size__max;} private long mem__size__max; + public long Mem__size__reduce() {return mem__size__reduce;} private long mem__size__reduce; + public void Mem__free__all() {hash.Clear();} + public void Mem__free__reduce() {Mem_mgr_.Free__reduce(hash);} + public void Mem__free__unused() {Mem_mgr_.Free__unused(hash);} + public long Mem__stat__size() {return mem__stat__size;} private long mem__stat__size; + public long Mem__stat__last() {return mem__stat__last;} private long mem__stat__last; + public int Mem__stat__count() {return mem__stat__count;} private int mem__stat__count; +*/ + public static Xoax_ctg_addon Get(Xow_wiki wiki) { + Xoax_ctg_addon rv = (Xoax_ctg_addon)wiki.Addon_mgr().Itms__get_or_null(ADDON_KEY); + if (rv == null) { + rv = new Xoax_ctg_addon(wiki); + wiki.Addon_mgr().Itms__add(rv); + } + return rv; + } + + public String Addon__key() {return ADDON_KEY;} private static final String ADDON_KEY = "xowa.apps.category"; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/Xoctg_ctg_itm.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/Xoctg_ctg_itm.java index a27517de8..66ae81602 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/Xoctg_ctg_itm.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/Xoctg_ctg_itm.java @@ -13,3 +13,18 @@ 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.addons.wikis.ctgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; +public class Xoctg_ctg_itm { + public Xoctg_ctg_itm(byte[] ttl_wo_ns, int pages, int subcs, int files) { + this.Ttl_wo_ns = ttl_wo_ns; + this.Pages = pages; + this.Subcs = subcs; + this.Files = files; + this.All = pages + subcs + files; + } + public final byte[] Ttl_wo_ns; + public final int Pages; + public final int Subcs; + public final int Files; + public final int All; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/Xoctg_page_xtn.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/Xoctg_page_xtn.java index a27517de8..5577726dd 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/Xoctg_page_xtn.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/Xoctg_page_xtn.java @@ -13,3 +13,9 @@ 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.addons.wikis.ctgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; +public class Xoctg_page_xtn { + public Xoctg_page_xtn(byte tid, byte[] sortkey) {this.tid = tid; this.sortkey = sortkey;} + public byte Tid() {return tid;} private final byte tid; + public byte[] Sortkey() {return sortkey;} private final byte[] sortkey; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xoax_ctg_bldr_addon.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xoax_ctg_bldr_addon.java index a27517de8..582b60d28 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xoax_ctg_bldr_addon.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xoax_ctg_bldr_addon.java @@ -13,3 +13,15 @@ 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.addons.wikis.ctgs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; +import gplx.xowa.bldrs.wkrs.*; +public class Xoax_ctg_bldr_addon implements Xoax_addon_itm, Xoax_addon_itm__bldr { + public Xob_cmd[] Bldr_cmds() { + return new Xob_cmd[] + { gplx.xowa.addons.wikis.ctgs.bldrs.Xob_pageprop_cmd.Prototype + , gplx.xowa.addons.wikis.ctgs.bldrs.Xob_catlink_cmd.Prototype + }; + } + + public String Addon__key() {return "xowa.builds.ctgs";} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xob_catlink_cmd.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xob_catlink_cmd.java index a27517de8..e2d49e853 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xob_catlink_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xob_catlink_cmd.java @@ -13,3 +13,44 @@ 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.addons.wikis.ctgs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.bldrs.sql_dumps.*; +public class Xob_catlink_cmd extends Xob_sql_dump_base implements Xosql_dump_cbk { + private final Xob_catlink_mgr mgr = new Xob_catlink_mgr(); + private int tmp_page_id; + private byte[] tmp_ctg_ttl, tmp_sortkey, tmp_timestamp, tmp_sortkey_prefix, tmp_collation, tmp_type; + + public Xob_catlink_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.Cmd_ctor(bldr, wiki);} + @Override public String Sql_file_name() {return Dump_file_name;} public static final String Dump_file_name = "categorylinks"; + @Override protected Xosql_dump_parser New_parser() {return new Xosql_dump_parser(this, "cl_from", "cl_to", "cl_sortkey", "cl_timestamp", "cl_sortkey_prefix", "cl_collation", "cl_type");} + + @Override public void Cmd_bgn_hook(Xob_bldr bldr, Xosql_dump_parser parser) { + wiki.Init_assert(); + mgr.On_cmd_bgn(wiki); + } + @Override public void Cmd_end() { + if (fail) return; + mgr.On_cmd_end(); + this.Cmd_cleanup_sql(); + } + public void On_fld_done(int fld_idx, byte[] src, int val_bgn, int val_end) { + switch (fld_idx) { + case Fld__cl_from: this.tmp_page_id = Bry_.To_int_or(src, val_bgn, val_end, -1); break; + case Fld__cl_to: this.tmp_ctg_ttl = Bry_.Mid(src, val_bgn, val_end); break; + case Fld__cl_sortkey: this.tmp_sortkey = Bry_.Mid(src, val_bgn, val_end); break; + case Fld__cl_timestamp: this.tmp_timestamp = Bry_.Mid(src, val_bgn, val_end); break; + case Fld__cl_sortkey_prefix: this.tmp_sortkey_prefix = Bry_.Mid(src, val_bgn, val_end); break; + case Fld__cl_collation: this.tmp_collation = Bry_.Mid(src, val_bgn, val_end); break; + case Fld__cl_type: this.tmp_type = Bry_.Mid(src, val_bgn, val_end); break; + } + } + public void On_row_done() { + mgr.On_cmd_row(tmp_page_id, tmp_ctg_ttl, tmp_sortkey, tmp_timestamp, tmp_sortkey_prefix, tmp_collation, tmp_type); + } + private static final byte Fld__cl_from = 0, Fld__cl_to = 1, Fld__cl_sortkey = 2, Fld__cl_timestamp = 3, Fld__cl_sortkey_prefix = 4, Fld__cl_collation = 5, Fld__cl_type = 6; + + public static final String BLDR_CMD_KEY = "wiki.categorylinks"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xob_catlink_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xob_catlink_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xob_catlink_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xob_catlink_mgr.java index a27517de8..fbc182b7a 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xob_catlink_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xob_catlink_mgr.java @@ -13,3 +13,83 @@ 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.addons.wikis.ctgs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; +import gplx.dbs.*; import gplx.xowa.addons.wikis.ctgs.dbs.*; +import gplx.xowa.addons.wikis.ctgs.enums.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +class Xob_catlink_mgr { + private Xowe_wiki wiki; + private Xodb_tmp_cat_db tmp_db; private Db_conn tmp_conn; private Xodb_tmp_cat_link_tbl tmp_link_tbl; + private final Xoctg_collation_enum collation_enum = new Xoctg_collation_enum(); private final Xoctg_type_enum type_enum = new Xoctg_type_enum(); + private int rows = 0; + public void On_cmd_bgn(Xowe_wiki wiki) { + this.wiki = wiki; + + // init tmp_db, tmp_link_tbl + this.tmp_db = new Xodb_tmp_cat_db(wiki); + this.tmp_conn = tmp_db.Conn(); + this.tmp_link_tbl = new Xodb_tmp_cat_link_tbl(tmp_conn); + tmp_link_tbl.Insert_bgn(); + } + public void On_cmd_row(int page_id, byte[] ctg_ttl, byte[] sortkey_orig, byte[] timestamp_bry, byte[] sortkey_prefix, byte[] collation_bry, byte[] type_bry) { + // convert strings to numbers + String timestamp_str = String_.new_u8(timestamp_bry); + long timestamp = String_.Len_eq_0(timestamp_str) ? 0 : DateAdp_.parse_fmt(timestamp_str, "YYYY-MM-dd HH:mm:ss").Timestamp_unix(); + byte collation_id = collation_enum.To_tid_or_fail(collation_bry); + byte type_id = type_enum.To_tid_or_fail(type_bry); + + // sortkey + byte[] sortkey_actl = sortkey_orig; + if (collation_id != Xoctg_collation_enum.Tid__uca) { + // sortkey; handle \n + int nl_pos = Bry_find_.Find_fwd(sortkey_actl, Byte_ascii.Nl); + if (nl_pos != Bry_find_.Not_found) // some sortkeys have format of "sortkey\ntitle"; discard 2nd to conserve hard-disk space; EX: "WALES, JIMMY\nJIMMY WALES" + sortkey_actl = Bry_.Mid(sortkey_actl, 0, nl_pos); // NOTE: some sortkeys have space which will sort under " "; EX: ' \nART' -> " "; SEE: s.w:Category:Art + } + + // insert to tmp; notify; commit + tmp_link_tbl.Insert_cmd_by_batch(page_id, ctg_ttl, sortkey_actl, timestamp, sortkey_prefix, collation_id, type_id); + if (++rows % 100000 == 0) { + Gfo_usr_dlg_.Instance.Prog_many("", "", "parsing categorylinks sql: ~{0}", Int_.To_str_fmt(rows, "#,##0")); + tmp_conn.Txn_sav(); + } + } + public void On_cmd_end() { + tmp_link_tbl.Insert_end(); + + // get cat_core conn + tmp_link_tbl.Create_idx__sortkey(); // index should make SELECT DISTINCT faster + Db_conn cat_core_conn = wiki.Data__core_mgr().Db__core().Conn(); + if (wiki.Data__core_mgr().Props().Layout_text().Tid_is_lot()) { + Xow_db_file cat_core_db = wiki.Data__core_mgr().Dbs__get_by_tid_or_null(Xow_db_file_.Tid__cat_core); + if (cat_core_db == null) + cat_core_db = wiki.Data__core_mgr().Dbs__make_by_tid(Xow_db_file_.Tid__cat_core); + cat_core_conn = cat_core_db.Conn(); + } + + // create tbl + // Xodb_cat_sort_tbl cat_sort_tbl = new Xodb_cat_sort_tbl(cat_core_conn); + // cat_core_conn.Meta_tbl_remake(cat_sort_tbl); + // cat_sort_tbl.Insert_by_select(tmp_conn); + + // make catlink_dbs + // cat_sort_tbl.Create_idx__key(); // index will be needed for join + tmp_link_tbl.Create_idx(); // index will be needed for join + Db_conn page_conn = wiki.Data__core_mgr().Db__core().Conn(); + Xob_catlink_wkr wkr = new Xob_catlink_wkr(); + try { + wkr.Make_catlink_dbs(wiki, tmp_conn, page_conn, cat_core_conn); + } catch (Exception e) { + Gfo_usr_dlg_.Instance.Log_many("", "", "error while generating catlink dbs; ~{0}", Err_.Message_gplx_log(e)); + throw Err_.new_wo_type("error while generating catlink dbs", "err", Err_.Message_gplx_log(e)); + } + + // make catcore_tbl; update page!cat_db_id + wkr.Make_catcore_tbl(wiki, tmp_conn, page_conn, cat_core_conn); + wkr.Update_page_cat_db_id(wiki, page_conn); + + // cleanup + // cat_sort_tbl.Delete_idx__key(); // remove idx + tmp_db.Delete(); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xob_catlink_wkr.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xob_catlink_wkr.java index a27517de8..499f8af89 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xob_catlink_wkr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xob_catlink_wkr.java @@ -13,3 +13,131 @@ 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.addons.wikis.ctgs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; +import gplx.dbs.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.addons.wikis.ctgs.dbs.*; +class Xob_catlink_wkr { + public void Make_catlink_dbs(Xowe_wiki wiki, Db_conn tmp_conn, Db_conn page_conn, Db_conn cat_core_conn) { + // init select + Db_attach_mgr attach_mgr = new Db_attach_mgr(page_conn, new Db_attach_itm("temp_db", tmp_conn), new Db_attach_itm("cat_db", cat_core_conn)); // NOTE: main_conn must be page_conn, else sqlite will be very slow when doing insert + String sql = attach_mgr.Resolve_sql(String_.Concat_lines_nl_skip_last + ( "SELECT tcl.cl_from" + , ", p.page_id" + , ", tcl.cl_type_id" + , ", tcl.cl_timestamp" + , ", tcl.cl_sortkey" + , ", tcl.cl_sortkey_prefix" + , "FROM tmp_cat_link tcl" + , " JOIN page p ON tcl.cl_to_ttl = p.page_title AND p.page_namespace = 14" + , "ORDER BY 1" // NOTE: must sort by page_id to keep all cats for page in one db + )); + attach_mgr.Attach(); + + // select from tmp_db and insert into cat_link + Xodb_cat_link_tbl cat_link_tbl = Make_cat_link_tbl(wiki, null); + Db_rdr rdr = attach_mgr.Conn_main().Stmt_sql(sql).Exec_select__rls_auto(); + try { + // misc row vals + long db_size_cur = 0, db_size_max = wiki.Appe().Api_root().Bldr().Wiki().Import().Cat_link_db_max(); + int page_id_prv = -1; + int rows = 0; + while (rdr.Move_next()) { + // check if row can fit in db; else update db_size + int page_id_cur = rdr.Read_int("cl_from"); + byte[] sortkey = rdr.Read_bry("cl_sortkey"); + if (sortkey == null) sortkey = Bry_.Empty; // WORKAROUND: sortkey should never be null; however, sqlite.jdbc sometimes returns as null; EX:ru.s and cl_from = 1324; DATE:2016-11-19 + byte[] sortkey_prefix = rdr.Read_bry_by_str("cl_sortkey_prefix"); + long db_size_new = db_size_cur + 48 + (sortkey.length * 2) + sortkey_prefix.length;// 46 = 3 ints (12) + 1 long (8) + 1 byte (2?) + 2 index (24?) + 11 fudge factor (?); DATE:2016-09-06 + if ( db_size_cur > db_size_max // size exceeded + && page_id_cur != page_id_prv) { // and page_id is diff; keeps all page_ids in one db + cat_link_tbl = Make_cat_link_tbl(wiki, cat_link_tbl); + db_size_new = 0; + } + db_size_cur = db_size_new; + page_id_prv = page_id_cur; + + // insert; notify; + cat_link_tbl.Insert_cmd_by_batch(page_id_prv, rdr.Read_int("page_id"), rdr.Read_byte("cl_type_id"), rdr.Read_long("cl_timestamp"), sortkey, sortkey_prefix); + if (++rows % 100000 == 0) { + Gfo_usr_dlg_.Instance.Plog_many("", "", "inserting cat_link row: ~{0}", Int_.To_str_fmt(rows, "#,##0")); + } + } + } + finally {rdr.Rls();} + Term_cat_link_tbl(cat_link_tbl); + attach_mgr.Detach(); // NOTE: must detach after txn + } + private static Xodb_cat_link_tbl Make_cat_link_tbl(Xowe_wiki wiki, Xodb_cat_link_tbl cat_link_tbl) { + Term_cat_link_tbl(cat_link_tbl); + + // get cat_link_conn + Db_conn cat_link_conn = wiki.Data__core_mgr().Props().Layout_text().Tid_is_lot() + ? wiki.Data__core_mgr().Dbs__make_by_tid(Xow_db_file_.Tid__cat_link).Conn() + : wiki.Data__core_mgr().Db__core().Conn(); + + // make tbl + Xodb_cat_link_tbl rv = new Xodb_cat_link_tbl(cat_link_conn); + cat_link_conn.Meta_tbl_remake(rv); + rv.Insert_bgn(); + return rv; + } + private static void Term_cat_link_tbl(Xodb_cat_link_tbl cat_link_tbl) { + if (cat_link_tbl == null) return; + cat_link_tbl.Insert_end(); + cat_link_tbl.Create_idx__catbox(); + cat_link_tbl.Create_idx__catpage(); + } + public void Make_catcore_tbl(Xowe_wiki wiki, Db_conn tmp_conn, Db_conn page_conn, Db_conn cat_core_conn) { + Db_attach_mgr attach_mgr = new Db_attach_mgr(cat_core_conn, new Db_attach_itm("temp_db", tmp_conn), new Db_attach_itm("page_db", page_conn)); + Xowd_cat_core_tbl cat_core_tbl = new Xowd_cat_core_tbl(cat_core_conn, Bool_.N); + cat_core_conn.Meta_tbl_remake(cat_core_tbl); + String sql = String_.Concat_lines_nl_skip_last // ANSI.Y + ( "INSERT INTO cat_core (cat_id, cat_pages, cat_subcats, cat_files, cat_hidden, cat_link_db_id)" + , "SELECT p.page_id" + , ", Sum(CASE WHEN tcl.cl_type_id = 2 THEN 1 ELSE 0 END)" + , ", Sum(CASE WHEN tcl.cl_type_id = 0 THEN 1 ELSE 0 END)" + , ", Sum(CASE WHEN tcl.cl_type_id = 1 THEN 1 ELSE 0 END)" + , ", CASE WHEN h.cat_id IS NULL THEN 0 ELSE 1 END" + , ", -1" + , "FROM page p" + , " JOIN tmp_cat_link tcl ON tcl.cl_to_ttl = p.page_title" + , " LEFT JOIN tmp_cat_hidden h ON h.cat_id = p.page_id" + , "WHERE p.page_namespace = 14" + , "GROUP BY p.page_id" + ); + attach_mgr.Exec_sql(sql); + } + public void Update_page_cat_db_id(Xowe_wiki wiki, Db_conn page_conn) { + // assert page_cat_db_id exists + Xow_db_mgr db_mgr = wiki.Data__core_mgr(); + Xowd_page_tbl page_tbl = db_mgr.Db__core().Tbl__page(); + page_conn.Meta_fld_append_if_missing(page_tbl.Tbl_name(), page_tbl.Flds__all(), Dbmeta_fld_itm.new_int("page_cat_db_id").Default_(-1)); + + // prep sql + String sql = String_.Concat_lines_nl_skip_last + ( "UPDATE page" + , "SET page_cat_db_id = {0}" + , "WHERE page_id IN (SELECT cl_from FROM cat_link WHERE cl_from = page.page_id);" + ); + Db_attach_mgr attach_mgr = new Db_attach_mgr(page_conn); + + // loop cat_link dbs and update page_id + boolean layout_is_lot = wiki.Data__core_mgr().Props().Layout_text().Tid_is_lot(); + int len = db_mgr.Dbs__len(); + for (int i = 0; i < len; ++i) { + Xow_db_file link_db = db_mgr.Dbs__get_at(i); + switch (link_db.Tid()) { + case Xow_db_file_.Tid__core: + if (layout_is_lot) continue; // use core_db is all or few; skip if lot; + break; + case Xow_db_file_.Tid__cat_link: + break; + default: + continue; + } + attach_mgr.Conn_links_(new Db_attach_itm("link_db", link_db.Conn())); + Gfo_usr_dlg_.Instance.Prog_many("", "", "updating page.cat_db_id; db=~{0}", link_db.Id()); + attach_mgr.Exec_sql(sql, link_db.Id()); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xob_pageprop_cmd.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xob_pageprop_cmd.java index a27517de8..884cd9b07 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xob_pageprop_cmd.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/bldrs/Xob_pageprop_cmd.java @@ -13,3 +13,47 @@ 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.addons.wikis.ctgs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.bldrs.sql_dumps.*; +import gplx.dbs.*; import gplx.xowa.addons.wikis.ctgs.dbs.*; +public class Xob_pageprop_cmd extends Xob_sql_dump_base implements Xosql_dump_cbk { + private int tmp_id; + private boolean tmp_key_is_hiddencat; + private int rows; + private Xodb_tmp_cat_hidden_tbl tbl; + + public Xob_pageprop_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.Cmd_ctor(bldr, wiki);} + @Override public String Sql_file_name() {return Dump_file_name;} public static final String Dump_file_name = "page_props"; + @Override protected Xosql_dump_parser New_parser() {return new Xosql_dump_parser(this, "pp_page", "pp_propname", "pp_value");} // NOTE: 4 b/c MW added fld_3:pp_sortkey; DATE:2014-04-28 + + @Override public void Cmd_bgn_hook(Xob_bldr bldr, Xosql_dump_parser parser) { + wiki.Init_assert(); + Xodb_tmp_cat_db tmp_db = new Xodb_tmp_cat_db(wiki); + tbl = new Xodb_tmp_cat_hidden_tbl(tmp_db.Conn()); + tbl.Insert_bgn(); + } + @Override public void Cmd_end() { + if (fail) return; + tbl.Insert_end(); + this.Cmd_cleanup_sql(); + } + public void On_fld_done(int fld_idx, byte[] src, int val_bgn, int val_end) { + switch (fld_idx) { + case Fld__pp_page: this.tmp_id = Bry_.To_int_or(src, val_bgn, val_end, -1); break; + case Fld__pp_propname: this.tmp_key_is_hiddencat = Bry_.Eq(src, val_bgn, val_end, Key_hiddencat); break; + } + } + public void On_row_done() { + if (tmp_key_is_hiddencat) + tbl.Insert_cmd_by_batch(tmp_id); + if (++rows % 10000 == 0) usr_dlg.Prog_many("", "", "parsing pageprops sql: row=~{0}", Int_.To_str_fmt(rows, "#,##0")); + } + private static final byte Fld__pp_page = 0, Fld__pp_propname = 1; + + public static final String BLDR_CMD_KEY = "wiki.page_props"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xob_pageprop_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xob_pageprop_cmd(bldr, wiki);} + + private static final byte[] Key_hiddencat = Bry_.new_a7("hiddencat"); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_cat_db_.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_cat_db_.java index a27517de8..1c39af81c 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_cat_db_.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_cat_db_.java @@ -13,3 +13,12 @@ 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.addons.wikis.ctgs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.wikis.dbs.*; +public class Xodb_cat_db_ { + public static Xowd_cat_core_tbl Get_cat_core_or_fail(Xow_db_mgr db_mgr) { + Xow_db_file cat_core_db = db_mgr.Dbs__get_by_tid_or_core(Xow_db_file_.Tid__cat_core); + Xowd_cat_core_tbl cat_core_tbl = new Xowd_cat_core_tbl(cat_core_db.Conn(), db_mgr.Props().Schema_is_1()); + return cat_core_tbl; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_cat_link_row.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_cat_link_row.java index a27517de8..40e6a29b8 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_cat_link_row.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_cat_link_row.java @@ -13,3 +13,20 @@ 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.addons.wikis.ctgs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; +public class Xodb_cat_link_row { + public Xodb_cat_link_row(int page_id, int cat_id, byte type_id, long timestamp_unix, byte[] sortkey, byte[] sortkey_prefix) { + this.page_id = page_id; + this.cat_id = cat_id; + this.type_id = type_id; + this.timestamp_unix = timestamp_unix; + this.sortkey = sortkey; + this.sortkey_prefix = sortkey_prefix; + } + public int Page_id() {return page_id;} private final int page_id; + public int Cat_id() {return cat_id;} private final int cat_id; + public byte Type_id() {return type_id;} private final byte type_id; + public long Timestamp_unix() {return timestamp_unix;} private final long timestamp_unix; + public byte[] Sortkey() {return sortkey;} private final byte[] sortkey; + public byte[] Sortkey_prefix() {return sortkey_prefix;} private final byte[] sortkey_prefix; +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_cat_link_tbl.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_cat_link_tbl.java index a27517de8..55d7c2fe0 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_cat_link_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_cat_link_tbl.java @@ -13,3 +13,111 @@ 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.addons.wikis.ctgs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; +import gplx.dbs.*; +import gplx.xowa.wikis.data.*; +import gplx.xowa.addons.wikis.ctgs.htmls.pageboxs.*; +public class Xodb_cat_link_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld__from, fld__to_id, fld__type_id, fld__timestamp_unix, fld__sortkey, fld__sortkey_prefix; + private Db_stmt stmt_insert; + public Xodb_cat_link_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = TBL_NAME; + this.fld__from = flds.Add_int ("cl_from"); // page_id + this.fld__to_id = flds.Add_int ("cl_to_id"); // cat_id + this.fld__type_id = flds.Add_byte ("cl_type_id"); // page,file,subc + this.fld__timestamp_unix = flds.Add_long ("cl_timestamp_unix"); + this.fld__sortkey = flds.Add_bry ("cl_sortkey"); // uca key + this.fld__sortkey_prefix = flds.Add_str (FLD__cl_sortkey_prefix, 255); // page_title; needed for sorting under letter on catpage + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Create_idx__catbox() {conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "catbox", fld__from));} + public void Create_idx__catpage() {conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "catpage", fld__to_id, fld__type_id, fld__sortkey));} + public void Insert_bgn() {conn.Txn_bgn("cl__insert"); stmt_insert = conn.Stmt_insert(tbl_name, flds);} + public void Insert_end() {conn.Txn_end(); stmt_insert = Db_stmt_.Rls(stmt_insert);} + public void Insert_cmd_by_batch(int from, int to_id, byte type_id, long timestamp_unix, byte[] sortkey, byte[] sortkey_prefix) { + this.Insert_cmd_by_batch(stmt_insert, from, to_id, type_id, timestamp_unix, sortkey, sortkey_prefix); + } + public void Insert_(int from, int to_id, byte type_id, long timestamp_unix, byte[] sortkey, byte[] sortkey_prefix) { + Db_stmt stmt = conn.Stmt_insert(tbl_name, flds); + this.Insert_cmd_by_batch(stmt, from, to_id, type_id, timestamp_unix, sortkey, sortkey_prefix); + } + private void Insert_cmd_by_batch(Db_stmt stmt, int from, int to_id, byte type_id, long timestamp_unix, byte[] sortkey, byte[] sortkey_prefix) { + stmt.Clear() + .Val_int(fld__from , from) + .Val_int(fld__to_id , to_id) + .Val_byte(fld__type_id , type_id) + .Val_long(fld__timestamp_unix , timestamp_unix) + .Val_bry(fld__sortkey , sortkey) + .Val_bry_as_str(fld__sortkey_prefix , sortkey_prefix) + .Exec_insert(); + } + public Xodb_cat_link_row[] Select_by_page_id(int page_id) { + List_adp list = List_adp_.New(); + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld__from).Crt_int(fld__from, page_id).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + Xodb_cat_link_row row = new Xodb_cat_link_row + ( rdr.Read_int(fld__from) + , rdr.Read_int(fld__to_id) + , rdr.Read_byte(fld__type_id) + , rdr.Read_long(fld__timestamp_unix) + , rdr.Read_bry(fld__sortkey) + , rdr.Read_bry_by_str(fld__sortkey_prefix) + ); + list.Add(row); + } + } finally { + rdr.Rls(); + } + return (Xodb_cat_link_row[])list.To_ary_and_clear(Xodb_cat_link_row.class); + } + public void Delete_pages(int page_id) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.cat_link: delete pages started: db=~{0} page_id=~{1}", conn.Conn_info().Raw(), page_id); + conn.Stmt_delete(tbl_name, fld__from) + .Crt_int(fld__from, page_id) + .Exec_delete(); + Gfo_usr_dlg_.Instance.Log_many("", "", "db.cat_link: delete pages done"); + } + public void Delete_cats(int page_id) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.cat_link: delete cats started: db=~{0} page_id=~{1}", conn.Conn_info().Raw(), page_id); + conn.Stmt_delete(tbl_name, fld__to_id) + .Crt_int(fld__to_id, page_id) + .Exec_delete(); + Gfo_usr_dlg_.Instance.Log_many("", "", "db.cat_link: delete cats done"); + } + public void Update_page_id_for_pages(int old_id, int new_id) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.cat_link: update cl_from started: db=~{0} old_id=~{1} new_id=~{2}", conn.Conn_info().Raw(), old_id, new_id); + conn.Stmt_update(tbl_name, String_.Ary(fld__from), fld__from).Val_int(fld__from, new_id).Crt_int(fld__from, old_id).Exec_update(); + Gfo_usr_dlg_.Instance.Log_many("", "", "db.cat_link: update cl_from done"); + } + public void Update_page_id_for_cats(int old_id, int new_id) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.cat_link: update cl_to started: db=~{0} old_id=~{1} new_id=~{2}", conn.Conn_info().Raw(), old_id, new_id); + conn.Stmt_update(tbl_name, String_.Ary(fld__to_id), fld__to_id).Val_int(fld__to_id, new_id).Crt_int(fld__to_id, old_id).Exec_update(); + Gfo_usr_dlg_.Instance.Log_many("", "", "db.cat_link: update cl_to done"); + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + } + + public static final String TBL_NAME = "cat_link", FLD__cl_sortkey_prefix = "cl_sortkey_prefix"; +} +/* +NOTE_1: categorylinks row size: 34 + 20 + 12 + (cat_sortkey.length * 2) +row length (data) : 34=8+4+4+14+4 ROWID, cl_from, cl_to_id, cl_timestamp, cl_type_id +cl_main length (idx) : 20=8+4+4+4 ROWID, cl_from, cl_to_id, cl_type_id +cl_from length (idx) : 12=8+4 ROWID, cl_from +variable_data length : cat_sortkey.length * 2 sortkey is used for row and cl_main + +Note the following +. ints are 4 bytes +. tinyint is assumed to be 4 bytes (should be 1, but sqlite only has one numeric datatype, so using all 4?) +. varchar(14) is assumed to be 14 bytes (should be 15? +1 for length of varchar?) +. calculations work out "too well". comparing 4 databases gets +/- .25 bytes per row. however +.. - bytes should not be possible +.. +.25 bytes is too low (18 MB out of 5.5 GB); there must be other bytes used for page breaks / fragmentation +*/ diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_cat_sort_tbl.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_cat_sort_tbl.java index a27517de8..ac818738a 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_cat_sort_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_cat_sort_tbl.java @@ -13,3 +13,30 @@ 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.addons.wikis.ctgs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.xowa.addons.wikis.ctgs.*; +public class Xodb_cat_sort_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_key; + public Xodb_cat_sort_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "cat_sort"; + flds.Add_int_pkey_autonum("cs_id"); + this.fld_key = flds.Add_str ("cs_key", 255); + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Create_idx__key() {conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, fld_key, fld_key));} + public void Delete_idx__key() {conn.Meta_idx_delete(tbl_name, fld_key);} + public void Insert_by_select(Db_conn tmp_conn) { + Db_attach_mgr attach_mgr = new Db_attach_mgr(conn, new Db_attach_itm("temp_db", tmp_conn)); + attach_mgr.Exec_sql(String_.Concat_lines_nl + ( "INSERT INTO cat_sort (cs_key)" + , "SELECT DISTINCT cl_sortkey" + , "FROM tmp_cat_link" + )); + } + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_tmp_cat_db.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_tmp_cat_db.java index a27517de8..542c589f0 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_tmp_cat_db.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_tmp_cat_db.java @@ -13,3 +13,17 @@ 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.addons.wikis.ctgs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; +import gplx.dbs.*; +public class Xodb_tmp_cat_db { + private final Io_url url; + public Xodb_tmp_cat_db(Xowe_wiki wiki) { + this.url = wiki.Fsys_mgr().Root_dir().GenSubFil("xowa.temp.category.sqlite3"); + this.conn = Db_conn_bldr.Instance.Get_or_autocreate(true, url); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public void Delete() { + conn.Rls_conn(); + Io_mgr.Instance.DeleteFil(url); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_tmp_cat_hidden_tbl.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_tmp_cat_hidden_tbl.java index a27517de8..2f8244eb8 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_tmp_cat_hidden_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_tmp_cat_hidden_tbl.java @@ -13,3 +13,30 @@ 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.addons.wikis.ctgs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; +import gplx.dbs.*; +public class Xodb_tmp_cat_hidden_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_cat_id; + private Db_stmt stmt_insert; + public Xodb_tmp_cat_hidden_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "tmp_cat_hidden"; + this.fld_cat_id = flds.Add_int_pkey ("cat_id"); + conn.Rls_reg(this); + conn.Meta_tbl_remake(this); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Insert_bgn() {conn.Txn_bgn("tch__insert"); stmt_insert = conn.Stmt_insert(tbl_name, flds);} + public void Insert_end() {conn.Txn_end(); stmt_insert = Db_stmt_.Rls(stmt_insert);} + public void Insert_cmd_by_batch(int page_id) { + stmt_insert.Clear() + .Val_int(fld_cat_id , page_id) + .Exec_insert(); + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_tmp_cat_link_tbl.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_tmp_cat_link_tbl.java index a27517de8..d53f973ff 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_tmp_cat_link_tbl.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/dbs/Xodb_tmp_cat_link_tbl.java @@ -13,3 +13,47 @@ 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.addons.wikis.ctgs.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.xowa.addons.wikis.ctgs.*; +public class Xodb_tmp_cat_link_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_from, fld_to_ttl, fld_sortkey, fld_timestamp, fld_sortkey_prefix, fld_collation_id, fld_type_id; + private Db_stmt stmt_insert; + public Xodb_tmp_cat_link_tbl(Db_conn conn) { + this.conn = conn; + this.tbl_name = "tmp_cat_link"; + this.fld_from = flds.Add_int ("cl_from"); + this.fld_to_ttl = flds.Add_str ("cl_to_ttl", 255); + this.fld_sortkey = flds.Add_bry ("cl_sortkey"); + this.fld_timestamp = flds.Add_long ("cl_timestamp"); + this.fld_sortkey_prefix = flds.Add_str ("cl_sortkey_prefix", 230); + this.fld_collation_id = flds.Add_byte ("cl_collation_id"); + this.fld_type_id = flds.Add_byte ("cl_type_id"); + conn.Rls_reg(this); + conn.Meta_tbl_remake(this); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Insert_bgn() {conn.Txn_bgn("tcl__insert"); stmt_insert = conn.Stmt_insert(tbl_name, flds);} + public void Insert_end() {conn.Txn_end(); stmt_insert = Db_stmt_.Rls(stmt_insert);} + public void Insert_cmd_by_batch(int page_id, byte[] ctg_ttl, byte[] sortkey, long timestamp, byte[] sortkey_prefix, byte collation_id, byte type_id) { + stmt_insert.Clear() + .Val_int(fld_from , page_id) + .Val_bry_as_str(fld_to_ttl , ctg_ttl) + .Val_bry(fld_sortkey , sortkey) + .Val_long(fld_timestamp , timestamp) + .Val_bry_as_str(fld_sortkey_prefix , sortkey_prefix) + .Val_byte(fld_collation_id , collation_id) + .Val_byte(fld_type_id , type_id) + .Exec_insert(); + } + public void Create_idx__sortkey() {conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, fld_sortkey, fld_sortkey));} + public void Create_idx() { + conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, fld_from, fld_from)); + conn.Meta_idx_create(Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, fld_to_ttl + "__" + fld_type_id, fld_to_ttl, fld_type_id)); + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/edits/Xoctg_edit_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/edits/Xoctg_edit_mgr.java index a27517de8..99b2ce044 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/edits/Xoctg_edit_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/edits/Xoctg_edit_mgr.java @@ -13,3 +13,141 @@ 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.addons.wikis.ctgs.edits; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; +import gplx.dbs.*; +import gplx.xowa.parsers.*; import gplx.xowa.wikis.pages.*; +import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.wikis.domains.*; +import gplx.xowa.addons.wikis.ctgs.dbs.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.langs.*; +import gplx.xowa.addons.wikis.directorys.specials.items.bldrs.*; +public class Xoctg_edit_mgr { + public static void Update(Xowe_wiki wiki, byte[] ttl_bry, int page_id, Xoa_ttl[] ctg_ttls) { + // get ttl, page, ns_id + Xoa_ttl ttl = wiki.Ttl_parse(ttl_bry); + int ns_id = ttl.Ns().Id(); + Xoae_page wpg = Xoae_page.New_edit(wiki, ttl); + wpg.Db().Page().Id_(page_id); + + // delete old categories + Delete(wiki, ns_id, page_id); + + // insert new categories + // get page_tbl + Xow_db_mgr db_mgr = wiki.Data__core_mgr(); + Xow_db_file core_db = db_mgr.Db__core(); + Xowd_page_tbl page_tbl = core_db.Tbl__page(); + + // get cat_core_tbl + Xowd_cat_core_tbl cat_core_tbl = Xodb_cat_db_.Get_cat_core_or_fail(db_mgr); + + // get last cat_link_tbl; note that + // * cat_link tbls are organized by page + // * all old cat_links have been deleted + // * new catlinks will go into last db + Xow_db_file last_cat_link_db = db_mgr.Dbs__get_for_create(Xow_db_file_.Tid__cat_link, ns_id); + Xodb_cat_link_tbl last_cat_link_tbl = new Xodb_cat_link_tbl(last_cat_link_db.Conn()); + + // get last text tbl for new categories; EX: [[Category:New]] will create entry in page_tb with text_db_id set to last_text_db + Xow_db_file last_text_db = db_mgr.Dbs__get_for_create(Xow_db_file_.Tid__text, ns_id); + Xowd_text_tbl last_text_tbl = new Xowd_text_tbl(last_text_db.Conn(), db_mgr.Props().Schema_is_1(), db_mgr.Props().Zip_tid_text()); + + // get some variables for creating cat_link rows + int timestamp = (int)Datetime_now.Get().Timestamp_unix(); + Xoctg_collation_mgr collation_mgr = wiki.Ctg__catpage_mgr().Collation_mgr(); + Xowd_page_itm tmp_page = new Xowd_page_itm(); + + // loop over each category listed on page + for (Xoa_ttl ctg_ttl : ctg_ttls) { + // get page_tbl data for sub_cat + boolean exists = page_tbl.Select_by_ttl(tmp_page, ctg_ttl); + int ctg_id = tmp_page.Id(); + + // create category if it doesn't exist + if (!exists) { + // create [[Category]] page + ctg_id = Xopg_db_mgr.Create + ( page_tbl, last_text_tbl, last_text_db.Id(), core_db.Tbl__ns(), core_db.Tbl__cfg() + , gplx.xowa.wikis.nss.Xow_ns_.Tid__category, ctg_ttl.Page_db(), Bry_.Empty + , last_cat_link_db.Id()); // NOTE: new categories go into last cat_link_db + + // create cat_core row + cat_core_tbl.Insert_bgn(); + cat_core_tbl.Insert_cmd_by_batch(ctg_id, 0, 0, 0, Bool_.N_byte, -1); + cat_core_tbl.Insert_end(); + } + + // get cat_core_itm + Xowd_category_itm cat_core_itm = cat_core_tbl.Select(ctg_id); + + // adjust it and save it + cat_core_itm.Adjust(ns_id, 1); + cat_core_tbl.Update(cat_core_itm); + + // add to cat_link tbl + last_cat_link_tbl.Insert_(page_id, ctg_id, Xoa_ctg_mgr.To_tid_by_ns(ns_id), timestamp, collation_mgr.Get_sortkey(wpg.Ttl().Page_db()), wpg.Ttl().Page_db()); + } + + // update page.cat_db_id + page_tbl.Update__cat_db_id(page_id, last_cat_link_db.Id()); + } + public static void Delete(Xowe_wiki wiki, int ns_id, int page_id) { + Xow_db_mgr db_mgr = wiki.Data__core_mgr(); + boolean ns_id_is_category = ns_id == Xow_ns_.Tid__category; + + // get cat_core_tbl + Xowd_cat_core_tbl cat_core_tbl = Xodb_cat_db_.Get_cat_core_or_fail(db_mgr); + + // get cat_link_tbls + Xow_db_file[] cat_link_dbs = db_mgr.Dbs__get_ary(Xow_db_file_.Tid__cat_link, ns_id); + + // loop cat_link tbls to find linked categories + for (Xow_db_file cat_link_db : cat_link_dbs) { + Xodb_cat_link_tbl cat_link_tbl = new Xodb_cat_link_tbl(cat_link_db.Conn()); + Xodb_cat_link_row[] cat_link_rows = cat_link_tbl.Select_by_page_id(page_id); + + // loop linked categories + for (Xodb_cat_link_row cat_link_row : cat_link_rows) { + // get cat_core_itm + Xowd_category_itm sub_core_itm = cat_core_tbl.Select(cat_link_row.Cat_id()); + + // subtract one and save it + sub_core_itm.Adjust(ns_id, -1); + cat_core_tbl.Update(sub_core_itm); + } + + // delete cat_links + cat_link_tbl.Delete_pages(page_id); + if (ns_id_is_category) + cat_link_tbl.Delete_cats(page_id); + } + + // delete cat_core + if (ns_id_is_category) { + cat_core_tbl.Delete(page_id); + } + + // set cat_db_id to -1 + db_mgr.Tbl__page().Update__cat_db_id(page_id, -1); + } + public static void Update_page_id(Xow_db_mgr db_mgr, int ns_id, int old_id, int new_id) { + boolean ns_id_is_category = ns_id == Xow_ns_.Tid__category; + + // get cat_link_tbls + Xow_db_file[] cat_link_dbs = db_mgr.Dbs__get_ary(Xow_db_file_.Tid__cat_link, ns_id); + + // loop cat_link tbls to find linked categories + for (Xow_db_file cat_link_db : cat_link_dbs) { + Xodb_cat_link_tbl cat_link_tbl = new Xodb_cat_link_tbl(cat_link_db.Conn()); + + // delete cat_links + cat_link_tbl.Update_page_id_for_pages(old_id, new_id); + if (ns_id_is_category) + cat_link_tbl.Update_page_id_for_cats(old_id, new_id); + } + + // update cat_core + if (ns_id_is_category) { + Xowd_cat_core_tbl cat_core_tbl = Xodb_cat_db_.Get_cat_core_or_fail(db_mgr); + cat_core_tbl.Update_page_id(old_id, new_id); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/enums/Xoctg_collation_enum.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/enums/Xoctg_collation_enum.java index a27517de8..17ce02215 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/enums/Xoctg_collation_enum.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/enums/Xoctg_collation_enum.java @@ -13,3 +13,17 @@ 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.addons.wikis.ctgs.enums; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; +import gplx.core.btries.*; +public class Xoctg_collation_enum { + private final Btrie_rv trv = new Btrie_rv(); + private final Btrie_slim_mgr trie = Btrie_slim_mgr.cs() + .Add_str_byte("uppercase" , Tid__uppercase) + .Add_str_byte("uca" , Tid__uca); + public byte To_tid_or_fail(byte[] raw) { + byte tid = trie.Match_byte_or(trv, raw, 0, raw.length, Byte_.Max_value_127); + if (tid == Byte_.Max_value_127) throw Err_.new_unhandled_default(raw); + return tid; + } + public static final byte Tid__uppercase = 1, Tid__uca = 3; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/enums/Xoctg_type_enum.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/enums/Xoctg_type_enum.java index a27517de8..bdca3b4df 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/enums/Xoctg_type_enum.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/enums/Xoctg_type_enum.java @@ -13,3 +13,18 @@ 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.addons.wikis.ctgs.enums; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; +import gplx.core.btries.*; +public class Xoctg_type_enum { + private final Btrie_rv trv = new Btrie_rv(); + private final Btrie_slim_mgr trie = Btrie_slim_mgr.cs() + .Add_str_byte("subcat" , Tid__subc) + .Add_str_byte("file" , Tid__file) + .Add_str_byte("page" , Tid__page); + public byte To_tid_or_fail(byte[] raw) { + byte tid = trie.Match_byte_or(trv, raw, 0, raw.length, Byte_.Max_value_127); + if (tid == Byte_.Max_value_127) throw Err_.new_unhandled_default(raw); + return tid; + } + public static final byte Tid__subc = 0, Tid__file = 1, Tid__page = 2, Tid_max = 3; +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/Xoctg_catpage_mgr.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/Xoctg_catpage_mgr.java index a27517de8..6a46be854 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/Xoctg_catpage_mgr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/Xoctg_catpage_mgr.java @@ -13,3 +13,91 @@ 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.addons.wikis.ctgs.htmls.catpages; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; +import gplx.xowa.wikis.dbs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; import gplx.xowa.htmls.core.htmls.*; import gplx.core.intls.ucas.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.addons.wikis.ctgs.htmls.catpages.doms.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.fmts.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.dbs.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.urls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.langs.*; +public class Xoctg_catpage_mgr implements Gfo_invk { + private final Xow_wiki wiki; + private final Hash_adp_bry cache = Hash_adp_bry.cs(); + private final Xoctg_catpage_loader loader = new Xoctg_catpage_loader(); + private final Xoctg_fmt_grp fmt_subcs = Xoctg_fmt_grp.New__subc(), fmt_pages = Xoctg_fmt_grp.New__page(), fmt_files = Xoctg_fmt_grp.New__file(); + private final Uca_ltr_extractor ltr_extractor = new Uca_ltr_extractor(true); + private String missing_cls = Str__missing_cls__red; + public int Grp_max() {return grp_max;} private int grp_max = Grp_max_dflt; + public Xoctg_catpage_mgr(Xow_wiki wiki) { + this.wiki = wiki; + this.collation_mgr = new Xoctg_collation_mgr(wiki); + } + public Xoctg_collation_mgr Collation_mgr() {return collation_mgr;} private Xoctg_collation_mgr collation_mgr; + public Xoctg_fmt_grp Fmt(byte tid) { + switch (tid) { + case Xoa_ctg_mgr.Tid__subc: return fmt_subcs; + case Xoa_ctg_mgr.Tid__page: return fmt_pages; + case Xoa_ctg_mgr.Tid__file: return fmt_files; + default: throw Err_.new_unhandled(tid); + } + } + public byte[] Missing_ctg_cls_css() { + if (String_.Eq(missing_cls, Str__missing_cls__normal)) return Css__missing_cls__normal; + else if (String_.Eq(missing_cls, Str__missing_cls__hide)) return Css__missing_cls__hide; + else if (String_.Eq(missing_cls, Str__missing_cls__red)) return Css__missing_cls__red; + else return Bry_.Empty; // NOTE: do not throw error, else fatal error when regen'ing cfg db; DATE:2016-12-27 + } + public void Init_by_wiki(Xow_wiki wiki) { + wiki.App().Cfg().Bind_many_wiki(this, wiki, Cfg__missing_class); + } + public void Free_mem_all() {cache.Clear();} + public Xoctg_catpage_ctg Get_or_load_or_null(byte[] page_ttl, Xoctg_catpage_url catpage_url, Xoa_ttl cat_ttl, int limit) { + // load categories from cat dbs; exit if not found + Xoctg_catpage_ctg ctg = (Xoctg_catpage_ctg)cache.Get_by(cat_ttl.Full_db()); + if (ctg == null) { + if (gplx.core.envs.Env_.Mode_testing()) return null; // needed for dpl test + synchronized (thread_lock) { // LOCK:used by multiple wrks; DATE:2016-09-12 + ctg = loader.Load_ctg_or_null(wiki, page_ttl, this, catpage_url, cat_ttl, limit); + } + if (ctg == null) return null; // not in cache or db; exit + if (limit == Int_.Max_value) // only add to cache if Max_val (DynamicPageList); for regular catpages, always retrieve on demand + cache.Add(cat_ttl.Full_db(), ctg); + } + return ctg; + } + public void Write_catpage(Bry_bfr bfr, Xoa_page page) { + try { + // get catpage_url + Xoctg_catpage_url catpage_url = Xoctg_catpage_url_parser.Parse(page.Url()); + + // load categories from cat dbs; exit if not found + Xoctg_catpage_ctg ctg = Get_or_load_or_null(page.Ttl().Page_db(), catpage_url, page.Ttl(), grp_max); + if (ctg == null) return; + + // write html + Xol_lang_itm lang = page.Lang(); + fmt_subcs.Write_catpage_grp(bfr, wiki, lang, ltr_extractor, ctg, grp_max); + fmt_pages.Write_catpage_grp(bfr, wiki, lang, ltr_extractor, ctg, grp_max); + fmt_files.Write_catpage_grp(bfr, wiki, lang, ltr_extractor, ctg, grp_max); + } + catch (Exception e) { + Xoa_app_.Usr_dlg().Warn_many("", "", "failed to generate category: title=~{0} err=~{1}", page.Url_bry_safe(), Err_.Message_gplx_log(e)); + } + } + public void Cache__add(byte[] ttl, Xoctg_catpage_ctg ctg) { + cache.Del(ttl); + cache.Add(ttl, ctg); + } + public void Grp_max_(int v) {grp_max = v;} // TEST: + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__collation_)) collation_mgr.Collation_name_(m.ReadStr("v")); + else if (ctx.Match(k, Cfg__missing_class)) missing_cls = m.ReadStr("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk__collation_ = "collation_"; + + public static int Grp_max_dflt = 200; + private static final Object thread_lock = new Object(); + + private static final String Cfg__missing_class = "xowa.addon.category.catpage.missing_class"; + private static final String Str__missing_cls__normal = "normal", Str__missing_cls__hide = "hide", Str__missing_cls__red = "red_link"; + private static final byte[] Css__missing_cls__normal = Bry_.new_a7(".xowa-missing-category-entry {}"), Css__missing_cls__hide = Bry_.new_a7(".xowa-missing-category-entry {display: none;}"), Css__missing_cls__red = Bry_.new_a7(".xowa-missing-category-entry {color: red;}"); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/Xoctg_catpage_mgr__basic__tst.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/Xoctg_catpage_mgr__basic__tst.java index a27517de8..ddcbaf1ff 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/Xoctg_catpage_mgr__basic__tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/Xoctg_catpage_mgr__basic__tst.java @@ -13,3 +13,299 @@ 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.addons.wikis.ctgs.htmls.catpages; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; +import org.junit.*; import gplx.xowa.htmls.core.htmls.*; import gplx.core.intls.ucas.*; +import gplx.xowa.addons.wikis.ctgs.htmls.catpages.doms.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.fmts.*; +public class Xoctg_catpage_mgr__basic__tst { + @Before public void init() {fxt.Clear();} private Xoctg_catpage_mgr_fxt fxt = new Xoctg_catpage_mgr_fxt(); + @Test public void Page_itm() { + fxt .Init_itms__pages("A1") + .Test__html__page(Xoa_ctg_mgr.Tid__page, Byte_ascii.Ltr_A, "\n
  • A1
  • "); + } + @Test public void Page_itm_missing() { + fxt.Init_itms__pages("A1"); + Xoctg_catpage_itm itm = fxt.Ctg().Grp_by_tid(Xoa_ctg_mgr.Tid__page).Itms__get_at(0); + itm.Page_ttl_(Xoa_ttl.Null); + itm.Sortkey_handle_make(Bry_bfr_.New(), Bry_.Empty); + fxt.Test__html__page(Xoa_ctg_mgr.Tid__page, Byte_ascii.Ltr_A, "\n
  • missing page (0)
  • "); + } + @Test public void Visited_doesnt_work_for_space() {// PURPOSE: xowa-visited not inserted for pages with space + byte[] page_bry = Bry_.new_a7("A 1"); + Xoa_url url = Xoa_url.New(Bry_.new_a7("en.wikipedia.org"), page_bry); + Xoa_ttl ttl = Xoa_ttl.Parse(fxt.Wiki(), page_bry); + fxt.Wiki().Appe().Usere().History_mgr().Add(fxt.Wiki().App(), url, ttl, page_bry); + fxt .Init_itms__pages("A_1") + .Test__html__all(Xoa_ctg_mgr.Tid__page, String_.Concat_lines_nl_skip_last + ( "" + , "
    " + , "

    Pages in category \"Ctg_1\"

    " + , "

    This category contains only the following page.

    " + , "
    " + , " " + , " " + , " " + , " " + , "
    " + , "

    A

    " + , "
      " + , "
    • A 1
    • " + , "
    " + , "
    " + , "
    " + , "
    " + )); + } + @Test public void Page_all() { + fxt .Init_itms__pages("A1") + .Test__html__all(Xoa_ctg_mgr.Tid__page, String_.Concat_lines_nl_skip_last + ( "" + , "
    " + , "

    Pages in category \"Ctg_1\"

    " + , "

    This category contains only the following page.

    " + , "
    " + , " " + , " " + , " " + , " " + , "
    " + , "

    A

    " + , "
      " + , "
    • A1
    • " + , "
    " + , "
    " + , "
    " + , "
    " + )); + } + @Test public void File_all() { + fxt .Init_itms__files("File:A1.png") + .Test__html__all(Xoa_ctg_mgr.Tid__file, String_.Concat_lines_nl_skip_last + ( "" + , "
    " + , "

    Media in category \"Ctg_1\"

    " + , "

    This category contains only the following file.

    " + , "
    " + , " " + , " " + , " " + , " " + , "
    " + , "

    A

    " + , " " + , "
    " + , "
    " + , "
    " + )); + } + @Test public void Subc_all() { + fxt .Init_itms__subcs("Category:Subc_1") + .Test__html__all(Xoa_ctg_mgr.Tid__subc, String_.Concat_lines_nl_skip_last + ( "" + , "
    " + , "

    Subcategories

    " + , "

    This category has only the following subcategory.

    " + , "
    " + , " " + , " " + , " " + , " " + , "
    " + , "

    S

    " + , "
      " + , "
    • " + , "
      " + , "
      " + , " " + , " " + , " " + , " " + , " Subc 1" + , " " + , " " + , " " + , "
      " + , "
      " + , "
      " + , "
    • " + , "
    " + , "
    " + , "
    " + , "
    " + )); + } + @Test public void Page_all_cols() { + fxt.Init_itms__pages("A1", "A2", "A3", "B1", "C1"); + fxt.Init__grp_max_(6); + fxt.Test__html__all(Xoa_ctg_mgr.Tid__page, String_.Concat_lines_nl_skip_last + ( "" + , "
    " + , "

    Pages in category \"Ctg_1\"

    " + , "

    The following 5 pages are in this category, out of 5 total.

    " + , "
    " + , " " + , " " + , " " + , " " + , " " + , " " + , "
    " + , "

    A

    " + , "
      " + , "
    • A1
    • " + , "
    • A2
    • " + , "
    " + , "
    " + , "

    A cont.

    " + , "
      " + , "
    • A3
    • " + , "
    " + , "

    B

    " + , "
      " + , "
    • B1
    • " + , "
    " + , "
    " + , "

    C

    " + , "
      " + , "
    • C1
    • " + , "
    " + , "
    " + , "
    " + , "
    " + )); + } + @Test public void Page__numeric() { // PURPOSE: check numeric sorting; 0, 9, 10; not 0, 10, 9; DATE:2016-10-09 + fxt.Init_itms__pages("0", "9", "10"); + fxt.Init__grp_max_(6); + fxt.Test__html__all(Xoa_ctg_mgr.Tid__page, String_.Concat_lines_nl_skip_last + ( "" + , "
    " + , "

    Pages in category \"Ctg_1\"

    " + , "

    The following 3 pages are in this category, out of 3 total.

    " + , "
    " + , " " + , " " + , " " + , " " + , " " + , " " + , "
    " + , "

    0-9

    " + , "
      " + , "
    • 0
    • " + , "
    " + , "
    " + , "

    0-9 cont.

    " + , "
      " + , "
    • 9
    • " + , "
    " + , "
    " + , "

    0-9 cont.

    " + , "
      " + , "
    • 10
    • " + , "
    " + , "
    " + , "
    " + , "
    " + )); + } + @Test public void Title__escape_quotes() {// PURPOSE: quotes in title should be escaped; DATE:2015-12-28 + fxt .Init_itms__pages("A\"1") + .Test__html__all(Xoa_ctg_mgr.Tid__page, String_.Concat_lines_nl_skip_last + ( "" + , "
    " + , "

    Pages in category \"Ctg_1\"

    " + , "

    This category contains only the following page.

    " + , "
    " + , " " + , " " + , " " + , " " + , "
    " + , "

    A

    " + , "
      " + , "
    • A"1
    • " + , "
    " + , "
    " + , "
    " + , "
    " + )); + } + @Test public void Calc_col_len() { + fxt.Test__calc_col_len(10, 0, 4); // for 10 items, col 0 has 4 items + fxt.Test__calc_col_len(10, 1, 3); // for 10 items, col 1 has 3 items + fxt.Test__calc_col_len(10, 2, 3); // for 10 items, col 2 has 3 items + fxt.Test__calc_col_len(11, 0, 4); + fxt.Test__calc_col_len(11, 1, 4); + fxt.Test__calc_col_len(11, 2, 3); + fxt.Test__calc_col_len(12, 0, 4); + fxt.Test__calc_col_len(12, 1, 4); + fxt.Test__calc_col_len(12, 2, 4); + } +} +class Xoctg_catpage_mgr_fxt { + private int grp_max; + private Uca_ltr_extractor ltr_extractor = new Uca_ltr_extractor(true); + public Xoctg_catpage_mgr_fxt Clear() { + if (app == null) { + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + ctg_html = wiki.Ctg__catpage_mgr(); + } + ctg = new Xoctg_catpage_ctg(1, Bry_.new_a7("Ctg_1")); + grp_max = 3; // default to 3 paer page + return this; + } private Xoae_app app; private Xoctg_catpage_mgr ctg_html; + public void Test__calc_col_len(int grp_len, int col_idx, int expd) {Tfds.Eq(expd, Xoctg_fmt_itm_base.Calc_col_len(grp_len, col_idx, 3));} + public Xowe_wiki Wiki() {return wiki;} private Xowe_wiki wiki; + public Xoctg_catpage_ctg Ctg() {return ctg;} private Xoctg_catpage_ctg ctg; + public void Init__grp_max_(int v) {this.grp_max = v;} + public void Init__prev_hide_y_(byte tid) {ctg.Grp_by_tid(tid).Prev_disable_(true);} + public void Init__next_sortkey_(byte tid, String v) {ctg.Grp_by_tid(tid).Next_sortkey_(Bry_.new_u8(v));} + public void Test__navlink(boolean next, String ctg_str, String expd) { + byte[] actl = ctg_html.Fmt(Xoa_ctg_mgr.Tid__page).Bld_bwd_fwd(wiki, Xoa_ttl.Parse(wiki, Bry_.new_a7(ctg_str)), ctg.Grp_by_tid(Xoa_ctg_mgr.Tid__page), grp_max); + Tfds.Eq_str_lines(expd, String_.new_u8(actl)); + } + public Xoctg_catpage_mgr_fxt Init_itms__pages(String... titles) {return Init_itms(Xoa_ctg_mgr.Tid__page, titles);} + public Xoctg_catpage_mgr_fxt Init_itms__files(String... titles) {return Init_itms(Xoa_ctg_mgr.Tid__file, titles);} + public Xoctg_catpage_mgr_fxt Init_itms__subcs(String... titles) {return Init_itms(Xoa_ctg_mgr.Tid__subc, titles);} + private Xoctg_catpage_mgr_fxt Init_itms(byte tid, String[] ttls) { + int len = ttls.length; + Xoctg_catpage_tmp tmp = new Xoctg_catpage_tmp(); + Xoctg_catpage_grp grp = ctg.Grp_by_tid(tid); + grp.Count_all_(len); + for (int i = 0; i < len; ++i) { + byte[] page_ttl_bry = Bry_.new_u8(ttls[i]); + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, page_ttl_bry); + Xoctg_catpage_itm itm = Xoctg_catpage_itm.New_by_ttl(tid, i, ttl); + tmp.Add(itm); + } + tmp.Make_by_grp(grp); + return this; + } + public void Test__html__page(byte tid, byte grp_char_0, String expd) { + Xoctg_fmt_grp list_mgr = ctg_html.Fmt(tid); + Xoctg_fmt_itm_base itm_fmt = list_mgr.Itm_fmt(); + Xoctg_catpage_grp list = ctg.Grp_by_tid(tid); + itm_fmt.Init_from_ltr(wiki, list, ltr_extractor); + itm_fmt.Set_ltr_and_bgn(new byte[] {grp_char_0}, 0); + itm_fmt.Col_end_(0, 0); + Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_b512(); + itm_fmt.Bfr_arg__add(bfr); + Tfds.Eq_str_lines(expd, bfr.To_str_and_rls()); + } + public void Test__html_grp(byte tid, String expd) { + Xoctg_fmt_grp list_mgr = ctg_html.Fmt(tid); + Xoctg_fmt_ltr fmtr_grp = new Xoctg_fmt_ltr(list_mgr.Itm_fmt()); + fmtr_grp.Init_from_grp(wiki, ctg.Grp_by_tid(tid), ltr_extractor); + Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_b512(); + fmtr_grp.Bfr_arg__add(bfr); + Tfds.Eq_str_lines(expd, bfr.To_str_and_rls()); + } + public void Test__html__all(byte tid, String expd) { + Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_b512(); + ctg_html.Fmt(tid).Write_catpage_grp(bfr, wiki, wiki.Lang(), ltr_extractor, ctg, grp_max); + Tfds.Eq_str_lines(expd, bfr.To_str_and_rls()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/Xoctg_catpage_mgr__navlink__tst.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/Xoctg_catpage_mgr__navlink__tst.java index a27517de8..4a730634b 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/Xoctg_catpage_mgr__navlink__tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/Xoctg_catpage_mgr__navlink__tst.java @@ -13,3 +13,45 @@ 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.addons.wikis.ctgs.htmls.catpages; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; +import org.junit.*; import gplx.xowa.htmls.core.htmls.*; +import gplx.xowa.addons.wikis.ctgs.htmls.catpages.doms.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.fmts.*; +public class Xoctg_catpage_mgr__navlink__tst { + @Before public void init() {fxt.Clear();} private Xoctg_catpage_mgr_fxt fxt = new Xoctg_catpage_mgr_fxt(); + @Test public void Navlink__basic() { + fxt.Init_itms__pages("A2", "A3", "A4"); + fxt.Init__next_sortkey_(Xoa_ctg_mgr.Tid__page, "A5"); + fxt.Test__navlink(Bool_.Y, "Category:Ctg_1", String_.Concat_lines_nl + ( "" + , "(previous 3)" + , "(next 3)" + )); + } + @Test public void Navlink__encoded() { // escape quotes and spaces; DATE:2016-01-11 + fxt.Init_itms__pages("A\" 2", "A\" 3", "A\" 4"); + fxt.Init__next_sortkey_(Xoa_ctg_mgr.Tid__page, "A\" 5"); + fxt.Test__navlink(Bool_.Y, "Category:Ctg_1", String_.Concat_lines_nl + ( "" + , "(previous 3)" + , "(next 3)" + )); + } + @Test public void Navlink__bos() { + fxt.Init_itms__pages("A2", "A3", "A4"); + fxt.Init__prev_hide_y_(Xoa_ctg_mgr.Tid__page); + fxt.Init__next_sortkey_(Xoa_ctg_mgr.Tid__page, "A5"); + fxt.Test__navlink(Bool_.Y, "Category:Ctg_1", String_.Concat_lines_nl + ( "" + , "(previous 3)" + , "(next 3)" + )); + } + @Test public void Navlink__eos() { + fxt.Init_itms__pages("A2", "A3", "A4"); + fxt.Test__navlink(Bool_.Y, "Category:Ctg_1", String_.Concat_lines_nl + ( "" + , "(previous 3)" + , "(next 3)" + )); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/dbs/Xoctg_catlink_loader.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/dbs/Xoctg_catlink_loader.java index a27517de8..04b719c8e 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/dbs/Xoctg_catlink_loader.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/dbs/Xoctg_catlink_loader.java @@ -13,3 +13,179 @@ 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.addons.wikis.ctgs.htmls.catpages.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.wikis.ctgs.htmls.catpages.doms.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.langs.*; +class Xoctg_catlink_loader { + private byte version; + private int link_dbs_len; + private Db_attach_mgr attach_mgr; + private final Bry_bfr sortkey_val_bfr = Bry_bfr_.New(); + public void Run(Xoctg_catpage_ctg rv, Xow_wiki wiki, Xoctg_catpage_mgr catpage_mgr, Xowd_page_tbl page_tbl, int cat_id, byte grp_tid, boolean url_is_from, byte[] url_sortkey, int limit) { + String sql = Bld_sql(catpage_mgr, cat_id, grp_tid, url_is_from, url_sortkey, limit); + Load_catlinks(rv, wiki, page_tbl, rv.Grp_by_tid(grp_tid), sql, url_is_from, limit); + } + public void Make_attach_mgr__v2(Xow_db_mgr db_mgr, int cat_link_db_idx) { + this.version = 2; + this.link_dbs_len = 1; + Xow_db_file cat_link_db = db_mgr.Dbs__get_by_id_or_fail(cat_link_db_idx); + this.attach_mgr = new Db_attach_mgr(cat_link_db.Conn(), new Db_attach_itm("link_db_1", cat_link_db.Conn())); + } + public void Make_attach_mgr__v3_v4(Xow_db_mgr db_mgr, Db_conn cat_core_conn) { + // init db vars + List_adp db_list = List_adp_.New(); + Db_conn db_1st = null; + int db_idx = 0; + + // fill db_list by looping over each db unless (a) cat_link_db or (b) core_db (if all or few) + int len = db_mgr.Dbs__len(); + for (int i = 0; i < len; ++i) { + Xow_db_file cl_db = db_mgr.Dbs__get_at(i); + switch (cl_db.Tid()) { + case Xow_db_file_.Tid__cat_link: // always use cat_link db + break; + case Xow_db_file_.Tid__core: // only use core if all or few + if (db_mgr.Props().Layout_text().Tid_is_lot()) + continue; + else + break; + default: // skip all other files + continue; + } + + // add to db_list + if (db_1st == null) db_1st = cl_db.Conn(); + db_list.Add(new Db_attach_itm("link_db_" + ++db_idx, cl_db.Conn())); + } + + // make attach_mgr + this.version = 4; + this.link_dbs_len = db_list.Len(); + if (cat_core_conn.Meta_tbl_exists("cat_sort")) { + version = 3; + db_1st = cat_core_conn; + } + this.attach_mgr = new Db_attach_mgr(db_1st, (Db_attach_itm[])db_list.To_ary_and_clear(Db_attach_itm.class)); + } + private String Bld_sql (Xoctg_catpage_mgr catpage_mgr, int cat_id, byte grp_tid, boolean url_is_from, byte[] url_sortkey, int limit) { + Bry_bfr bfr = Bry_bfr_.New(); + for (int i = 0; i < link_dbs_len; ++i) + Bld_sql_by_db(catpage_mgr, cat_id, grp_tid, url_is_from, url_sortkey, i + List_adp_.Base1, bfr); + bfr.Add_str_u8_fmt + ( "\nORDER BY cl_to_id, cl_type_id, cl_sortkey {0}" + + "\nLIMIT {1}" + , url_is_from ? "ASC" : "DESC", limit + 1); + return bfr.To_str_and_clear(); + } + private void Bld_sql_by_db (Xoctg_catpage_mgr catpage_mgr, int cat_id, byte grp_tid, boolean url_is_from, byte[] url_sortkey, int link_db_id, Bry_bfr bfr) { + if (link_db_id != List_adp_.Base1) bfr.Add_str_a7("\nUNION\n"); + + // change sortkey vars per version; note that v3 differs from v2 and v4 b/c of cat_sort tbl + String sortkey_col = "cl_sortkey"; + String sortkey_join = ""; + if (version == 3) { // NOTE: version 3 takes sortkey from cat_sort + sortkey_col = "cs.cs_key"; + sortkey_join = "\n JOIN cat_sort cs ON cl.cl_sortkey_id = cs.cs_id"; + } + + // sortkey_val + byte[] sortkey_val = null; + String sortkey_prefix_fld = version == 4 ? "cl_sortkey_prefix" : "''"; + sortkey_val = Build_sortkey_val(sortkey_val_bfr, version, catpage_mgr.Collation_mgr(), url_sortkey); + + // bld sql; NOTE: building sql with args embedded b/c (a) UNION requires multiple Crt_arg for each ?; (EX: 4 unions, 3 ? require 12 .Crt_arg); (b) easier to debug + String sql = Db_sql_.Make_by_fmt(String_.Ary + ( "SELECT cl_to_id" + , ", cl_from" + , ", cl_type_id" + , ", {3} AS cl_sortkey" + , ", {7} AS cl_sortkey_prefix" + , "FROM cat_link cl{6}" + , "WHERE cl_to_id = {1}" + , "AND cl_type_id = {2}" + , "AND {3} {4} {5}" + ), link_db_id, cat_id, grp_tid, sortkey_col, url_is_from ? ">=" : "<", sortkey_val, sortkey_join, sortkey_prefix_fld); + bfr.Add_str_u8(sql); + } + private void Load_catlinks(Xoctg_catpage_ctg rv, Xow_wiki wiki, Xowd_page_tbl page_tbl, Xoctg_catpage_grp grp, String sql, boolean url_is_from, int limit) { + // init; prep sql + Xoctg_page_loader catlink_loader = new Xoctg_page_loader(wiki); + Ordered_hash hash = catlink_loader.Hash(); + sql = attach_mgr.Resolve_sql(sql); + + // run sql and create itms based on cat_link + Xoctg_catpage_itm zth_itm = null; + Db_rdr rdr = Db_rdr_.Empty; + int row_idx = 0; + try { + attach_mgr.Attach(); + rdr = attach_mgr.Conn_main().Stmt_sql(sql).Exec_select__rls_auto(); + while (rdr.Move_next()) { + Xoctg_catpage_itm itm = Xoctg_catpage_itm.New_by_rdr(rdr, version); + if (row_idx++ < limit) // row_idx >= limit for last row; EX: 200 < 200 + hash.Add(itm.Page_id(), itm); + else { // last row; EX: 201 + if (url_is_from) // from=some_key; 201st row is sort_key for "(Next 200)" + zth_itm = itm; + else // until=some_key; 201st row means that 200th row is not 1st row; show prev link + grp.Prev_disable_(false); + } + } + } + finally { + rdr.Rls(); + attach_mgr.Detach(); + } + + // load ns / ttl from page + page_tbl.Select_in__id(catlink_loader); + grp.Itms_((Xoctg_catpage_itm[])hash.To_ary_and_clear(Xoctg_catpage_itm.class)); + + // set data for Next 200 / Previous 200 + if (zth_itm != null) { + if (version == 4) { + Load_sortkey(wiki, grp, zth_itm); + grp.Next_sortkey_(zth_itm.Sortkey_handle()); + } + else + grp.Next_sortkey_(zth_itm.Sortkey_handle()); + } + } + private static void Load_sortkey(Xow_wiki wiki, Xoctg_catpage_grp grp, Xoctg_catpage_itm zth_itm) { + // load page_ttl from db + Xowd_page_itm tmp_pg = new Xowd_page_itm(); + wiki.Data__core_mgr().Tbl__page().Select_by_id(tmp_pg, zth_itm.Page_id()); + + // set ttl; skip if page is missing (Talk: ns) else null-ref; PAGE:en.wCategory:Disambig-Class_Comics_articles_of_NA-importance DATE:2016-10-12 + if (tmp_pg.Exists()) { + Xoa_ttl zth_ttl = wiki.Ttl_parse(tmp_pg.Ns_id(), tmp_pg.Ttl_page_db()); + zth_itm.Page_ttl_(zth_ttl); + } + + // make sortkey + byte[] prv_sortkey = grp.Itms__len() == 0 ? Bry_.Empty : grp.Itms__get_at(grp.Itms__len() - 1).Sortkey_handle(); + zth_itm.Sortkey_handle_make(Bry_bfr_.New(), prv_sortkey); + } + public static byte[] Build_sortkey_val(Bry_bfr sortkey_val_bfr, byte version, Xoctg_collation_mgr collation_mgr, byte[] url_sortkey) { + // find \n and ignore everything after it; needed else "< 'A\nA'" will pull up "A"; NOTE: can't find logic in MediaWiki CategoryViewer.php; DATE:2016-10-11 + // ALSO: needed for v2 else SQL will literally have WHERE cl_sortkey = 'A\nA'; + byte[] tmp_sortkey = url_sortkey; + int nl_pos = Bry_find_.Find_fwd(tmp_sortkey, Byte_ascii.Nl); + if (nl_pos != Bry_find_.Not_found) + tmp_sortkey = Bry_.Mid(tmp_sortkey, 0, nl_pos); + + if (version == 4) { + if (Bry_.Len_gt_0(url_sortkey)) { + // make sortkey_val + sortkey_val_bfr.Add_byte(Byte_ascii.Ltr_x).Add_byte_apos(); + gplx.core.encoders.Hex_utl_.Encode_bfr(sortkey_val_bfr, collation_mgr.Get_sortkey(tmp_sortkey)); + sortkey_val_bfr.Add_byte_apos(); + } + else + sortkey_val_bfr.Add_byte_apos().Add_byte_apos(); + } + else + sortkey_val_bfr.Add_byte_apos().Add(Db_sql_.Escape_arg(tmp_sortkey)).Add_byte_apos(); + return sortkey_val_bfr.To_bry_and_clear(); + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/dbs/Xoctg_catlink_loader__tst.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/dbs/Xoctg_catlink_loader__tst.java index a27517de8..bb2d86bb6 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/dbs/Xoctg_catlink_loader__tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/dbs/Xoctg_catlink_loader__tst.java @@ -13,3 +13,25 @@ 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.addons.wikis.ctgs.htmls.catpages.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +import org.junit.*; import gplx.core.tests.*; import gplx.xowa.apps.urls.*; +import gplx.xowa.langs.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.langs.*; +public class Xoctg_catlink_loader__tst { + private final Xoctg_catlink_loader__fxt fxt = new Xoctg_catlink_loader__fxt(); + @Test public void Build_sortkey_val__v4() { // PURPOSE: remove "\n" and afterwards else will omit 1 record + fxt.Test__build_sortkey_sql(4, "A\nA", "x'41'"); // fails if "x'410a41'" + } + @Test public void Build_sortkey_val__v2() { // PURPOSE: remove "\n" and afterwards else SQL will be malformed + fxt.Test__build_sortkey_sql(2, "A\nA", "'A'"); // fails if "'A\nA'" + } +} +class Xoctg_catlink_loader__fxt { + public void Test__build_sortkey_sql(int version, String sortkey, String expd) { + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + Xowe_wiki wiki = Xoa_app_fxt.Make__wiki__edit(app, "de.wikipedia.org"); // use "de.wikipedia.org" for simple "uppercase" collation + Xoctg_collation_mgr collation_mgr = new Xoctg_collation_mgr(wiki); + + Bry_bfr bfr = Bry_bfr_.New(); + Gftest.Eq__str(expd, Xoctg_catlink_loader.Build_sortkey_val(bfr, Byte_.By_int(version), collation_mgr, Bry_.new_u8(sortkey))); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/dbs/Xoctg_catpage_loader.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/dbs/Xoctg_catpage_loader.java index a27517de8..ecb18e688 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/dbs/Xoctg_catpage_loader.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/dbs/Xoctg_catpage_loader.java @@ -13,3 +13,57 @@ 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.addons.wikis.ctgs.htmls.catpages.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.addons.wikis.ctgs.dbs.*; +import gplx.xowa.addons.wikis.ctgs.htmls.catpages.doms.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.urls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.dbs.*; +public class Xoctg_catpage_loader { + public Xoctg_catpage_ctg Load_ctg_or_null(Xow_wiki wiki, byte[] page_ttl_bry, Xoctg_catpage_mgr catpage_mgr, Xoctg_catpage_url cat_url, Xoa_ttl cat_ttl, int limit) { + // get cat_id from page_tbl + Xow_db_mgr db_mgr = wiki.Data__core_mgr(); + Xowd_page_tbl page_tbl = db_mgr.Db__core().Tbl__page(); + Xowd_page_itm page_itm = page_tbl.Select_by_ttl_as_itm_or_null(cat_ttl); + if (page_itm == null) { + Gfo_usr_dlg_.Instance.Log_many("", "", "category does not exist in page table; wiki=~{0} page=~{1} ttl=~{2}", wiki.Domain_str(), page_ttl_bry, cat_ttl.Full_db()); // Log instead of Warn b/c happens many times in en.d, en.b, en.u; DATE:2016-10-22 + return null; + } + int cat_id = page_itm.Id(); + + // get cat_core_itm from cat_core_tbl + Xowd_cat_core_tbl cat_core_tbl = Xodb_cat_db_.Get_cat_core_or_fail(db_mgr); + Xowd_category_itm cat_core_itm = cat_core_tbl.Select(cat_id); + if (cat_core_itm == Xowd_category_itm.Null) { + Gfo_usr_dlg_.Instance.Log_many("", "", "category does not exist in cat_core table; ttl=~{0}", cat_ttl.Full_db()); // NOTE: this is not rare as Category pages can be created as deliberately empty, or as redirects; fr.w:Catégorie:Utilisateur_hess-4; DATE:2016-09-12 + return null; + } + + // load itms from cat_link_db for each grp_tid + Xoctg_catpage_ctg rv = new Xoctg_catpage_ctg(cat_id, cat_ttl.Page_txt()); + for (byte grp_tid = Xoa_ctg_mgr.Tid__subc; grp_tid < Xoa_ctg_mgr.Tid___max; ++grp_tid) { + Xoctg_catpage_grp grp = rv.Grp_by_tid(grp_tid); + grp.Count_all_(cat_core_itm.Count_by_tid(grp_tid)); // set total counts per grp based on cat_core_itm; + + // init url_vars + boolean url_is_from = cat_url.Grp_fwds()[grp_tid]; // EX: "pagefrom=A"; "pageuntil=B" + byte[] url_sortkey = cat_url.Grp_keys()[grp_tid]; + + // set prev / next props to dflt values + if (url_is_from) { // url is from; EX: from=A + if (Bry_.Len_eq_0(url_sortkey)) // no sortkey specified for group, so group always starts with 1st itm -> disable previous link + grp.Prev_disable_(true); + } + else { // url is until; EX: until=A + grp.Prev_disable_(true); // default prev link to disabled; loader will load 201 items and set to false if 201st found + grp.Next_sortkey_(url_sortkey); // next sortkey is always sortkey of until url arg; + } + + // load links + Xoctg_catlink_loader loader = new Xoctg_catlink_loader(); + if (cat_core_itm.File_idx() == -1) // v3 or v4: loop over all cat_link dbs { + loader.Make_attach_mgr__v3_v4 (db_mgr, cat_core_tbl.Conn()); + else // v2: use cat_link_db + loader.Make_attach_mgr__v2 (db_mgr, cat_core_itm.File_idx()); + loader.Run(rv, wiki, catpage_mgr, page_tbl, cat_id, grp_tid, url_is_from, url_sortkey, limit); + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/dbs/Xoctg_page_loader.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/dbs/Xoctg_page_loader.java index a27517de8..63b1514d9 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/dbs/Xoctg_page_loader.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/dbs/Xoctg_page_loader.java @@ -13,3 +13,28 @@ 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.addons.wikis.ctgs.htmls.catpages.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.wikis.ctgs.htmls.catpages.doms.*; +public class Xoctg_page_loader implements Select_in_cbk { + private final Xow_wiki wiki; + private final Ordered_hash hash = Ordered_hash_.New(); + public Xoctg_page_loader(Xow_wiki wiki) {this.wiki = wiki;} + public Ordered_hash Hash() {return hash;} + public int Hash_max() {return hash.Len();} + public void Write_sql(Bry_bfr bfr, int idx) { + Xoctg_catpage_itm itm = (Xoctg_catpage_itm)hash.Get_at(idx); + bfr.Add_int_variable(itm.Page_id()); + } + public void Read_data(Db_rdr rdr) { + // read values from page_tbl + int page_id = rdr.Read_int("page_id"); + int page_ns = rdr.Read_int("page_namespace"); + byte[] page_ttl = rdr.Read_bry_by_str("page_title"); + + // get itm and set data + Xoctg_catpage_itm itm = (Xoctg_catpage_itm)hash.Get_by(page_id); + if (itm == null) return; // NOTE: itms can exist in cat_links_tbl, but not in page_tbl; EX:User:Any_page + itm.Page_ttl_(wiki.Ttl_parse(page_ns, page_ttl)); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/doms/Xoctg_catpage_ctg.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/doms/Xoctg_catpage_ctg.java index a27517de8..7e66e573a 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/doms/Xoctg_catpage_ctg.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/doms/Xoctg_catpage_ctg.java @@ -13,3 +13,23 @@ 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.addons.wikis.ctgs.htmls.catpages.doms; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +public class Xoctg_catpage_ctg { + public Xoctg_catpage_ctg(int id, byte[] name) { + this.id = id; this.name = name; + } + public int Id() {return id;} private final int id; + public byte[] Name() {return name;} private final byte[] name; + public Xoctg_catpage_grp Subcs() {return subcs;} private final Xoctg_catpage_grp subcs = new Xoctg_catpage_grp(Xoa_ctg_mgr.Tid__subc); + public Xoctg_catpage_grp Pages() {return pages;} private final Xoctg_catpage_grp pages = new Xoctg_catpage_grp(Xoa_ctg_mgr.Tid__page); + public Xoctg_catpage_grp Files() {return files;} private final Xoctg_catpage_grp files = new Xoctg_catpage_grp(Xoa_ctg_mgr.Tid__file); + public int Total() {return subcs.Count_all() + pages.Count_all() + files.Count_all();} + public Xoctg_catpage_grp Grp_by_tid(byte tid) { + switch (tid) { + case Xoa_ctg_mgr.Tid__subc: return subcs; + case Xoa_ctg_mgr.Tid__page: return pages; + case Xoa_ctg_mgr.Tid__file: return files; + default: throw Err_.new_unhandled(tid); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/doms/Xoctg_catpage_grp.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/doms/Xoctg_catpage_grp.java index a27517de8..470b7b49e 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/doms/Xoctg_catpage_grp.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/doms/Xoctg_catpage_grp.java @@ -13,3 +13,40 @@ 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.addons.wikis.ctgs.htmls.catpages.doms; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +public class Xoctg_catpage_grp { + private Xoctg_catpage_itm[] itms = Xoctg_catpage_itm.Ary_empty; + private byte[] next_sortkey = Bry_.Empty; + public Xoctg_catpage_grp(byte tid) {this.tid = tid;} + public byte Tid() {return tid;} private byte tid; // subc|page|file + public int Count_all() {return count_all;} private int count_all; // count of items in entire category; EX: 456 + public boolean Prev_disable() {return prev_disable;} private boolean prev_disable; // prev_link: disable link + public byte[] Next_sortkey() {return next_sortkey;} // next_link: set sortkey + public int Itms__len() {return itms.length;} + public Xoctg_catpage_itm Itms__get_at(int i) {return itms[i];} + + public void Count_all_(int v) {this.count_all = v;} + public void Prev_disable_(boolean v) {this.prev_disable = v;} + public void Next_sortkey_(byte[] v) {this.next_sortkey = v;} + public void Itms_(Xoctg_catpage_itm[] v) { + this.itms = v; + Array_.Sort(itms, new Xoctg_catpage_itm_sorter()); // NOTE: need to reorder for page_until b/c ORDER BY DESC + + // make sortkey_handle + Bry_bfr tmp_bfr = Bry_bfr_.New(); + int itms_len = itms.length; + byte[] prv_sortkey_handle = Bry_.Empty; + for (int i = 0; i < itms_len; ++i) { + Xoctg_catpage_itm itm = itms[i]; + prv_sortkey_handle = itm.Sortkey_handle_make(tmp_bfr, prv_sortkey_handle); + } + } + +} +class Xoctg_catpage_itm_sorter implements gplx.core.lists.ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + Xoctg_catpage_itm lhs = (Xoctg_catpage_itm)lhsObj; + Xoctg_catpage_itm rhs = (Xoctg_catpage_itm)rhsObj; + return Bry_.Compare(lhs.Sortkey_binary(), rhs.Sortkey_binary()); + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/doms/Xoctg_catpage_itm.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/doms/Xoctg_catpage_itm.java index a27517de8..5c335ee81 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/doms/Xoctg_catpage_itm.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/doms/Xoctg_catpage_itm.java @@ -13,3 +13,87 @@ 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.addons.wikis.ctgs.htmls.catpages.doms; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +import gplx.dbs.*; import gplx.xowa.wikis.nss.*; +public class Xoctg_catpage_itm { + private byte version; + Xoctg_catpage_itm(byte version, byte grp_tid, int page_id, byte[] sortkey_prefix, byte[] sortkey_binary) { + this.version = version; + this.grp_tid = grp_tid; + this.page_id = page_id; + this.page_ttl = Xoa_ttl.Null; + this.sortkey_prefix = sortkey_prefix; + this.sortkey_handle = sortkey_prefix; // default handle to sortkey_prefix; + this.sortkey_binary = sortkey_binary; + } + public byte Grp_tid() {return grp_tid;} private final byte grp_tid; // v2-v4:cl_type_id; subc,page,file + public int Page_id() {return page_id;} private final int page_id; // v2-v4:cl_from + public byte[] Sortkey_prefix() {return sortkey_prefix;} private byte[] sortkey_prefix; // v2-v3:cl_sortkey; v4:cl_sortkey_prefix + public byte[] Sortkey_handle() {return sortkey_handle;} private byte[] sortkey_handle; // v2-v3:cl_sortkey; v4:cl_sortkey_prefix\nttl_txt; never "cl_sortkey" which is binary ICU value; + public byte[] Sortkey_binary() {return sortkey_binary;} private byte[] sortkey_binary; // v2-v4:cl_sortkey; note that v4 is binary icu value + public Xoa_ttl Page_ttl() {return page_ttl;} private Xoa_ttl page_ttl; + + public void Page_ttl_(Xoa_ttl ttl) { + this.page_ttl = ttl; + // sortkey_prefix will be blank for v2; use page_ttl; PAGE:s.w:Category:Computer_science DATE:2015-11-22 + if (version == Version__2 && Bry_.Len_eq_0(sortkey_prefix)) + sortkey_prefix = page_ttl.Page_txt(); + + // sortkey_binary will be blank for v2,v3; use page_ttl; PAGE:fr.w:Article_contenant_un_appel_à_traduction_en_anglais; DATE:2016-10-11 + if (version != Version__4 && Bry_.Len_eq_0(sortkey_binary)) sortkey_binary = page_ttl.Page_txt(); + } + public byte[] Sortkey_handle_make(Bry_bfr tmp_bfr, byte[] prv_sortkey_handle) { + // page.tbl missing even though in cat_link.tbl; happens for "User:" namespace pages; + if (page_ttl == Xoa_ttl.Null) { + // sortkey_prefix exists; happens for [[Category:A]] as opposed to [[Category:A|some_sortkey_prefix]]; also, {{DEFAULTSORTKEY:some_sortkey_prefix}} + if (Bry_.Len_gt_0(sortkey_prefix)) { + sortkey_handle = sortkey_prefix; + return sortkey_handle; // set sortkey_prefix as new prv_sortkey_handle; + } + else { + // set sortkey_handle to "prv_sortkey" + "page_id"; needed for multiple "missing" catlinks which span 200 page boundaries + tmp_bfr.Add(prv_sortkey_handle); + tmp_bfr.Add_byte_nl(); + tmp_bfr.Add_int_variable(page_id); + sortkey_handle = tmp_bfr.To_bry_and_clear(); + return prv_sortkey_handle; + } + } + // page.tbl exists + else { + // "In UCA, tab is the only character that can sort above LF so we strip both of them from the original prefix."; Title.php|getCategorySortKey + if (sortkey_prefix.length == 0) { + sortkey_handle = page_ttl.Page_txt(); + } + else { + byte[] sortkey_normalized = Bry_.Replace(sortkey_prefix, Sortkey_prefix_replace__src, Sortkey_prefix_replace__trg); + tmp_bfr.Add(sortkey_normalized); + tmp_bfr.Add_byte_nl().Add(page_ttl.Page_txt()); // "$prefix\n$unprefixed"; + sortkey_handle = tmp_bfr.To_bry_and_clear(); + } + return sortkey_handle; + } + } + + public static final Xoctg_catpage_itm[] Ary_empty = new Xoctg_catpage_itm[0]; + public static Xoctg_catpage_itm New_by_rdr(Db_rdr rdr, byte version) { + byte[] sortkey_binary = Bry_.Empty; + byte[] sortkey_prefix = Bry_.Empty; + if (version == Version__4) { + sortkey_binary = rdr.Read_bry("cl_sortkey"); + sortkey_prefix = rdr.Read_bry_by_str("cl_sortkey_prefix"); + } + else { + sortkey_binary = Bry_.Empty; + sortkey_prefix = rdr.Read_bry_by_str("cl_sortkey"); + } + return new Xoctg_catpage_itm(version, rdr.Read_byte("cl_type_id"), rdr.Read_int("cl_from"), sortkey_prefix, sortkey_binary); + } + public static Xoctg_catpage_itm New_by_ttl(byte grp_tid, int page_id, Xoa_ttl ttl) { // TEST + Xoctg_catpage_itm rv = new Xoctg_catpage_itm(Version__4, grp_tid, page_id, ttl.Page_txt(), Bry_.Empty); + rv.Page_ttl_(ttl); + return rv; + } + private static final byte Version__2 = 2, Version__4 = 4; + private static final byte[] Sortkey_prefix_replace__src = Bry_.new_a7("\n\t"), Sortkey_prefix_replace__trg = Bry_.new_a7(" "); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/doms/Xoctg_catpage_tmp.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/doms/Xoctg_catpage_tmp.java index a27517de8..77f3022fb 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/doms/Xoctg_catpage_tmp.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/doms/Xoctg_catpage_tmp.java @@ -13,3 +13,29 @@ 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.addons.wikis.ctgs.htmls.catpages.doms; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +public class Xoctg_catpage_tmp { + private final List_adp subc_list = List_adp_.New(), page_list = List_adp_.New(), file_list = List_adp_.New(); + public void Add(Xoctg_catpage_itm itm) { + List_adp list = Get_by_tid(itm.Grp_tid()); + list.Add(itm); + } + public void Make_by_ctg(Xoctg_catpage_ctg ctg) { // TEST: + for (byte tid = 0; tid < Xoa_ctg_mgr.Tid___max; ++tid) + Make_by_grp(ctg.Grp_by_tid(tid)); + } + public void Make_by_grp(Xoctg_catpage_grp grp) { + byte tid = grp.Tid(); + List_adp list = Get_by_tid(tid); + if (list.Len() == 0) return; + grp.Itms_((Xoctg_catpage_itm[])list.To_ary_and_clear(Xoctg_catpage_itm.class)); + } + private List_adp Get_by_tid(byte tid) { + switch (tid) { + case Xoa_ctg_mgr.Tid__subc: return subc_list; + case Xoa_ctg_mgr.Tid__page: return page_list; + case Xoa_ctg_mgr.Tid__file: return file_list; + default: throw Err_.new_unhandled_default(tid); + } + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_grp.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_grp.java index a27517de8..77a1048f3 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_grp.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_grp.java @@ -13,3 +13,95 @@ 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.addons.wikis.ctgs.htmls.catpages.fmts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; import gplx.xowa.htmls.core.htmls.*; import gplx.langs.htmls.encoders.*; import gplx.core.intls.ucas.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.addons.wikis.ctgs.htmls.catpages.doms.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.urls.*; +public class Xoctg_fmt_grp { // subc|page|file + private final byte tid; + private final byte[] div_id, url_arg_bgn, url_arg_end; + private final int msg_label_id, msg_stats_id; + private final Xoctg_fmt_ltr itms_fmt; + Xoctg_fmt_grp(byte tid, Xoctg_fmt_itm_base itm_fmt, int msg_label_id, int msg_stats_id, byte[] url_arg_bgn, byte[] url_arg_end, byte[] div_id) { + this.tid = tid; + this.itm_fmt = itm_fmt; + this.itms_fmt = new Xoctg_fmt_ltr(itm_fmt); + this.msg_label_id = msg_label_id; this.msg_stats_id = msg_stats_id; + this.url_arg_bgn = url_arg_bgn; this.url_arg_end = url_arg_end; this.div_id = div_id; + } + public Xoctg_fmt_itm_base Itm_fmt() {return itm_fmt;} private final Xoctg_fmt_itm_base itm_fmt; + public void Write_catpage_grp(Bry_bfr bfr, Xow_wiki wiki, Xol_lang_itm lang, Uca_ltr_extractor ltr_extractor, Xoctg_catpage_ctg dom_ctg, int grp_max) { // TEST: + Xoctg_catpage_grp dom_grp = dom_ctg.Grp_by_tid(tid); + if (dom_grp.Count_all() == 0) return; // no items in grp; EX: 0 items in File + + // get msgs + Xow_msg_mgr msg_mgr = wiki.Msg_mgr(); + byte[] msg_label_bry = msg_mgr.Val_by_id_args(msg_label_id, dom_ctg.Name()); + byte[] msg_stats_bry = msg_mgr.Val_by_id_args(msg_stats_id, dom_grp.Itms__len(), dom_grp.Count_all()); + + // get nav html; next / previous 200 + Xoa_ttl ctg_ttl = wiki.Ttl_parse(Xow_ns_.Tid__category, dom_ctg.Name()); + byte[] nav_html = this.Bld_bwd_fwd(wiki, ctg_ttl, dom_grp, grp_max); + + // init grp; write + itms_fmt.Init_from_grp(wiki, dom_grp, ltr_extractor); + Fmt__ctg.Bld_many(bfr, div_id, msg_label_bry, msg_stats_bry, nav_html, lang.Key_bry(), lang.Dir_ltr_bry(), itms_fmt); + } + public byte[] Bld_bwd_fwd(Xow_wiki wiki, Xoa_ttl ttl, Xoctg_catpage_grp view_grp, int grp_max) { // TEST: + if (view_grp.Count_all() < grp_max) return Bry_.Empty; // < 200; never show; + Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_k004(); + Html_nav_bry(bfr, wiki, ttl, view_grp, grp_max, Bool_.N); + Html_nav_bry(bfr, wiki, ttl, view_grp, grp_max, Bool_.Y); + return bfr.To_bry_and_rls(); + } + private void Html_nav_bry(Bry_bfr bfr, Xow_wiki wiki, Xoa_ttl ttl, Xoctg_catpage_grp grp, int grp_max, boolean url_is_from) { + Bry_bfr href_bfr = wiki.Utl__bfr_mkr().Get_b512(); + + // get nav_href; EX:href="/wiki/Category:Ctg_1?pageuntil=A1#mw-pages" + wiki.Html__href_wtr().Build_to_bfr(href_bfr, wiki.App(), Xoh_wtr_ctx.Basic, wiki.Domain_bry(), ttl); + byte[] arg_idx_lbl = null; byte[] arg_sortkey = null; + if (url_is_from) { + arg_idx_lbl = url_arg_bgn; + arg_sortkey = grp.Next_sortkey(); + } + else { + arg_idx_lbl = url_arg_end; + arg_sortkey = grp.Itms__get_at(0).Sortkey_handle(); // use 1st item as sortkey for "until" args + } + href_bfr.Add_byte(Byte_ascii.Question).Add(arg_idx_lbl).Add_byte(Byte_ascii.Eq); // filefrom= + Gfo_url_encoder_.Http_url.Encode(href_bfr, arg_sortkey); // Abc + href_bfr.Add_byte(Byte_ascii.Hash).Add(div_id); // #mw-subcategories + byte[] nav_href = href_bfr.To_bry_and_rls(); + + // get nav_text + int nav_text_id = url_is_from ? Xol_msg_itm_.Id_next_results : Xol_msg_itm_.Id_prev_results; + byte[] nav_text = wiki.Msg_mgr().Val_by_id_args(nav_text_id, grp_max); // next 200 / previous 200 + + // print text if 1st / zth page; else, print html + if ( ( url_is_from && Bry_.Len_eq_0(grp.Next_sortkey())) + || (!url_is_from && grp.Prev_disable()) + ) + Fmt__nav__text.Bld_many(bfr, nav_text); + else + Fmt__nav__href.Bld_many(bfr, nav_href, ttl.Full_url(), nav_text); + } + private static final Bry_fmt + Fmt__nav__href = Bry_fmt.New("\n(~{nav_text})") + , Fmt__nav__text = Bry_fmt.New("\n(~{nav_text})") + , Fmt__ctg = Bry_fmt.Auto_nl_skip_last + ( "" + , "
    " + , "

    ~{msg_label_bry}

    " + , "

    ~{msg_stats_bry}

    ~{nav_html}" + , "
    " + , " " + , " ~{grps}" + , " " + , "
    " + , "
    ~{nav_html}" + , "
    " + ); + public static Xoctg_fmt_grp New__subc() {return new Xoctg_fmt_grp(Xoa_ctg_mgr.Tid__subc, new Xoctg_fmt_itm_subc(), Xol_msg_itm_.Id_ctg_subc_header, Xol_msg_itm_.Id_ctg_subc_count, Xoctg_catpage_url_parser.Bry__arg_subc_bgn, Xoctg_catpage_url_parser.Bry__arg_subc_end, Bry_.new_a7("mw-subcategories"));} + public static Xoctg_fmt_grp New__page() {return new Xoctg_fmt_grp(Xoa_ctg_mgr.Tid__page, new Xoctg_fmt_itm_page(), Xol_msg_itm_.Id_ctg_page_header, Xol_msg_itm_.Id_ctg_page_count, Xoctg_catpage_url_parser.Bry__arg_page_bgn, Xoctg_catpage_url_parser.Bry__arg_page_end, Bry_.new_a7("mw-pages"));} + public static Xoctg_fmt_grp New__file() {return new Xoctg_fmt_grp(Xoa_ctg_mgr.Tid__file, new Xoctg_fmt_itm_file(), Xol_msg_itm_.Id_ctg_file_header, Xol_msg_itm_.Id_ctg_file_count, Xoctg_catpage_url_parser.Bry__arg_file_bgn, Xoctg_catpage_url_parser.Bry__arg_file_end, Bry_.new_a7("mw-category-media"));} +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_itm_base.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_itm_base.java index a27517de8..dd2a80759 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_itm_base.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_itm_base.java @@ -13,3 +13,87 @@ 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.addons.wikis.ctgs.htmls.catpages.fmts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +import gplx.langs.htmls.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.hrefs.*; import gplx.xowa.htmls.core.wkrs.lnkis.htmls.*; import gplx.xowa.htmls.core.htmls.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; import gplx.core.intls.ucas.*; +import gplx.xowa.users.history.*; +import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.doms.*; +public abstract class Xoctg_fmt_itm_base implements gplx.core.brys.Bfr_arg { + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + private Xow_wiki wiki; + private Xoctg_catpage_grp grp; + private Uca_ltr_extractor ltr_extractor; + private byte[] ltr_cur; private int loop_bgn; private int col_end; + + public int Loop_end_idx() {return loop_end_idx;} private int loop_end_idx; + public boolean Loop_ends_at_col() {return loop_ends_at_col;} private boolean loop_ends_at_col; + public void Col_end_(int col_bgn, int col_idx) { + this.col_end = col_bgn + Calc_col_len(grp.Itms__len(), col_idx, Cols_max); + } + public void Init_from_ltr(Xow_wiki wiki, Xoctg_catpage_grp grp, Uca_ltr_extractor ltr_extractor) { + this.wiki = wiki; + this.grp = grp; + this.ltr_extractor = ltr_extractor; + } + public void Set_ltr_and_bgn(byte[] ltr_cur, int loop_bgn) {this.ltr_cur = ltr_cur; this.loop_bgn = loop_bgn;} + public void Bfr_arg__add(Bry_bfr bfr) { + // init vars + Xoh_href_parser href_parser = wiki.App().Html__href_parser(); + Xou_history_mgr history_mgr = wiki.App().User().History_mgr(); + int grp_end = grp.Itms__len(); + + // loop over itms; + for (int i = loop_bgn; i < grp_end; i++) { + // reached end of col; exit + if (i == col_end) { + loop_end_idx = i; + loop_ends_at_col = true; + return; + } + + // get sortkey + Xoctg_catpage_itm itm = grp.Itms__get_at(i); + byte[] itm_sortkey = itm.Sortkey_handle(); + + // reached end of ltr; exit + byte[] ltr_1st = ltr_extractor.Get_1st_ltr(itm_sortkey); + if (!Bry_.Has_at_bgn(ltr_1st, ltr_cur, 0, ltr_1st.length)) { + loop_end_idx = i; + loop_ends_at_col = i == col_end; + return; + } + + Xoa_ttl itm_ttl = itm.Page_ttl(); + if (itm_ttl == Xoa_ttl.Null) + Fmt__missing.Bld_many(bfr, itm.Page_id()); + else + Bld_html(bfr, wiki, history_mgr, href_parser, itm, itm_ttl); + } + loop_end_idx = grp_end; + loop_ends_at_col = true; + } + @gplx.Virtual public void Bld_html(Bry_bfr bfr, Xow_wiki wiki, Xou_history_mgr history_mgr, Xoh_href_parser href_parser, Xoctg_catpage_itm itm, Xoa_ttl ttl) { + byte[] itm_full_ttl = Gfh_utl.Escape_html_as_bry(tmp_bfr, ttl.Full_txt_w_ttl_case());// NOTE: ttl.Full_txt() to get full ns; EX: Template:A instead of just "A" + byte[] itm_href = wiki.Html__href_wtr().Build_to_bry(wiki, ttl); + byte[] itm_atr_cls = Xoh_lnki_wtr.Lnki_cls_visited(history_mgr, wiki.Domain_bry(), ttl.Page_txt()); // NOTE: must be ttl.Page_txt() in order to match Xou_history_mgr.Add + Fmt__exists.Bld_many(bfr, itm_href, itm_atr_cls, itm_full_ttl, itm_full_ttl, gplx.core.encoders.Hex_utl_.Encode_bry(itm.Sortkey_binary())); + } + private static final Bry_fmt + Fmt__missing = Bry_fmt.Auto_nl_skip_last + ( "" + , "
  • missing page (~{itm_id})
  • " + ) + , Fmt__exists = Bry_fmt.Auto_nl_skip_last + ( "" + , "
  • ~{itm_text}
  • " // + ) + ; + public static final int Cols_max = 3; + public static int Calc_col_len(int grp_len, int col_idx, int max_cols) { // TEST + if (grp_len == 0) return 0; // 0 items in group; return 0; + int max_itms_in_col = ((grp_len - 1) / max_cols) + 1; // EX: grp with 4, 5, 6 items should have max of 2 items in 1 col, so (a) subtract 1; (b) divide by 3; (c) add 1 + return col_idx <= ((grp_len - 1) % max_cols) // complicated formula but works; rely on example and note that only 2 or 1 can be returned; EX: 4=2,1,1; 5=2,2,1; 6=2,2,2 + ? max_itms_in_col + : max_itms_in_col - 1; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_itm_page.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_itm_page.java index a27517de8..71d2b4633 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_itm_page.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_itm_page.java @@ -13,3 +13,28 @@ 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.addons.wikis.ctgs.htmls.catpages.fmts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +class Xoctg_fmt_itm_page extends Xoctg_fmt_itm_base {} +class Xoctg_fmt_itm_file extends Xoctg_fmt_itm_base { +// public void Bfr_arg__add(Bry_bfr bfr) { +// html_itm = wiki.Html_mgr().Gallery_xtn_mgr().Html_gallery_itm_img(); +// for (int i = list.Bgn(); i < len; i++) { +// Xoctg_catpage_itm itm = list.Itms()[i]; +// Xoa_ttl ttl = itm.Ttl(); +// byte[] ttl_page = ttl.Page_txt(); +// byte[] itm_href = href_parser.Build_to_bry(ttl, wiki); +// html_itm.Bld_bfr_many(bfr +// , 155 // "itm_box_width" +// , 155 // "itm_div_width" +// , 15 // "itm_margin" +// , -1 // "img_id" +// , ttl_page // "img_ttl" +// , itm_href // "img_href" +// , Bry_.Empty // "html_src" +// , -1 // "img_width" +// , -1 // "img_height" +// , ttl_page // "itm_caption" +// ); +// } +// } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_itm_subc.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_itm_subc.java index a27517de8..80390c75e 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_itm_subc.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_itm_subc.java @@ -13,3 +13,55 @@ 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.addons.wikis.ctgs.htmls.catpages.fmts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +import gplx.xowa.htmls.core.htmls.*; import gplx.xowa.htmls.hrefs.*; +import gplx.xowa.langs.msgs.*; +import gplx.xowa.addons.wikis.ctgs.htmls.catpages.doms.*; +import gplx.xowa.users.history.*; +class Xoctg_fmt_itm_subc extends Xoctg_fmt_itm_base { + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + @Override public void Bld_html(Bry_bfr bfr, Xow_wiki wiki, Xou_history_mgr history_mgr, Xoh_href_parser href_parser, Xoctg_catpage_itm itm, Xoa_ttl ttl) { + byte[] itm_href = wiki.Html__href_wtr().Build_to_bry(wiki, ttl); + int count_subcs = 0; + int count_pages = 0; + int count_files = 0; + Xow_msg_mgr msg_mgr = wiki.Msg_mgr(); + byte[] contains_title = wiki.Msg_mgr().Val_by_id_args(Xol_msg_itm_.Id_ctgtree_subc_counts, count_subcs, count_pages, count_files); + byte[] contains_text = Bld_contains_text(msg_mgr, count_subcs, count_pages, count_files); + byte[] ttl_page = ttl.Page_txt(); + Fmt__exists__subc.Bld_many(bfr, ttl.Page_db(), ttl_page, itm_href, ttl_page, contains_title, contains_text); + } + private byte[] Bld_contains_text(Xow_msg_mgr msg_mgr, int count_subcs, int count_pages, int count_files) { + if (count_subcs == 0 && count_pages == 0 && count_files == 0) return Bry_.Empty; + tmp_bfr.Add_byte(Byte_ascii.Paren_bgn); + Bld_contains_text_itm(tmp_bfr, msg_mgr, Xol_msg_itm_.Id_ctgtree_subc_counts_ctg, count_subcs); + Bld_contains_text_itm(tmp_bfr, msg_mgr, Xol_msg_itm_.Id_ctgtree_subc_counts_page, count_pages); + Bld_contains_text_itm(tmp_bfr, msg_mgr, Xol_msg_itm_.Id_ctgtree_subc_counts_file, count_files); + tmp_bfr.Add_byte(Byte_ascii.Paren_end); + return tmp_bfr.To_bry_and_clear(); + } + private void Bld_contains_text_itm(Bry_bfr bfr, Xow_msg_mgr msg_mgr, int msg_id, int val) { + if (val == 0) return; + if (bfr.Len() > 1) bfr.Add(Bld_contains_text_itm_dlm); // NOTE: 1 b/c Paren_bgn is always added + bfr.Add(msg_mgr.Val_by_id_args(msg_id, val)); + } private static final byte[] Bld_contains_text_itm_dlm = Bry_.new_a7(", "); + private static final Bry_fmt + Fmt__exists__subc = Bry_fmt.Auto_nl_skip_last + ( "" + , "
  • " + , "
    " + , "
    " + , " " + , " " + , " " + , " " + , " ~{itm_text}" + , " " + , " ~{itm_contains_text}" + , " " + , "
    " + , "
    " + , "
    " + , "
  • " + ); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_ltr.java b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_ltr.java index a27517de8..3676794cd 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_ltr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/ctgs/htmls/catpages/fmts/Xoctg_fmt_ltr.java @@ -13,3 +13,72 @@ 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.addons.wikis.ctgs.htmls.catpages.fmts; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.addons.wikis.ctgs.htmls.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +import gplx.xowa.htmls.core.htmls.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; import gplx.core.intls.ucas.*; +import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.doms.*; +public class Xoctg_fmt_ltr implements gplx.core.brys.Bfr_arg { // "A", "B", "C cont." + private final Xoctg_fmt_itm_base itm_fmt; + private Xoctg_catpage_grp grp; + private byte[] msg__list_continues; + private Uca_ltr_extractor ltr_extractor; + public Xoctg_fmt_ltr(Xoctg_fmt_itm_base itm_fmt) { + this.itm_fmt = itm_fmt; + } + public void Init_from_grp(Xow_wiki wiki, Xoctg_catpage_grp grp, Uca_ltr_extractor ltr_extractor) { + this.grp = grp; + this.msg__list_continues = wiki.Msg_mgr().Val_by_id(Xol_msg_itm_.Id_list_continues); + this.ltr_extractor = ltr_extractor; + itm_fmt.Init_from_ltr(wiki, grp, ltr_extractor); + } + public void Bfr_arg__add(Bry_bfr bfr) { + int itm_idx = 0; + int itm_end = grp.Itms__len(); + int itms_len = itm_end - itm_idx; if (itms_len == 0) return; // no items; exit + + int col_idx = 0; // col idx; EX: 3 cols; idx = 0, 1, 2 + boolean start_new_col = true; + byte[] ltr_prv = Bry_.Empty; + + // loop itms until no more itms + while (itm_idx < itm_end) { + Xoctg_catpage_itm itm = grp.Itms__get_at(itm_idx); + + // get ltr_head; EX: "C" or "C cont." + byte[] itm_sortkey = itm.Sortkey_handle(); + // byte[] ltr_cur = gplx.core.intls.Utf8_.Get_char_at_pos_as_bry(itm_sortkey, 0); + byte[] ltr_cur = ltr_extractor.Get_1st_ltr(itm_sortkey); + byte[] ltr_head = Bry_.Eq(ltr_prv, ltr_cur) + ? Bry_.Add(ltr_prv, Byte_ascii.Space_bry, msg__list_continues) // new col uses same ltr as last itm in old col; add "cont."; EX: "C cont." + : ltr_cur; // else, just use ltr; EX: "C" + ltr_prv = ltr_cur; + + // start new column if needed + if (start_new_col) { + itm_fmt.Col_end_(itm_idx, col_idx++); // set col_end; note col starts at itm_idx + Fmt__col_bgn.Bld_many(bfr, 100 / Xoctg_fmt_itm_base.Cols_max); // width:33% + } + + // set ltr and idx + itm_fmt.Set_ltr_and_bgn(ltr_prv, itm_idx); + + // loop until (a) end of ltr or (b) end of col + Fmt__tbl.Bld_many(bfr, ltr_head, itm_fmt); + itm_idx = itm_fmt.Loop_end_idx(); + start_new_col = itm_fmt.Loop_ends_at_col(); + + // end column if needed + if (start_new_col) + Fmt__col_end.Bld_many(bfr); + } + } + private static final Bry_fmt + Fmt__tbl = Bry_fmt.Auto_nl_skip_last + ( "" + , "

    ~{ltr_head}

    " // EX: "A", "A cont." + , "
      ~{itms}" + , "
    " + ) + , Fmt__col_bgn = Bry_fmt.New("\n
    ") + , Fmt__col_end = Bry_fmt.New("\n
    " + , " " + , " " + , " " + , " " + , " " + , " " + , " ~{rows}" + , " " + , " " + , "
    ~{wiki}~{cancel}" + , "
    ~{hdr_len}" + , " ~{hdr_ttl}" + , "
    " + ), "wiki", "cancel", "hdr_len", "hdr_ttl", "insert_key", "rows"); + private static final Bry_fmtr fmtr_rslts = Bry_fmtr.new_("Results ~{bgn} of ~{end} for ~{raw}", "bgn", "end", "raw"); + private static final byte[] Bry_paging_fwd = Bry_.new_a7("Next"), Bry_paging_bwd = Bry_.new_a7("Previous")// , Bry_cancel = Bry_.new_a7("Stop searching") + , Bry_hdr_len = Bry_.new_a7("Page score"), Bry_hdr_ttl = Bry_.new_a7("Page title") + ; + private final byte[] Bry__special_search = Bry_.new_a7("Special:Search/"), Bry__fulltext = Bry_.new_a7("?fulltext=y"); +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/htmls/Srch_html_page_bldr_tst.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/htmls/Srch_html_page_bldr_tst.java index a27517de8..ad36189b3 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/htmls/Srch_html_page_bldr_tst.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/htmls/Srch_html_page_bldr_tst.java @@ -13,3 +13,71 @@ 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.addons.wikis.searchs.specials.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.specials.*; +import org.junit.*; import gplx.xowa.htmls.core.htmls.utls.*; import gplx.xowa.wikis.tdbs.*; +import gplx.xowa.wikis.domains.*; +import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.wikis.searchs.searchers.*; import gplx.xowa.addons.wikis.searchs.searchers.rslts.*; +public class Srch_html_page_bldr_tst { + @Before public void init() {fxt.Clear();} private Srch_html_page_bldr_fxt fxt = new Srch_html_page_bldr_fxt(); + @Test public void Paging() { + fxt.Test_paging(Bool_.Y, 1, "Next"); + fxt.Test_paging(Bool_.N, 1, "Previous"); + fxt.Test_paging(Bool_.Y, 2, "Next"); + fxt.Test_paging(Bool_.N, 0, " "); + } + @Test public void Rows() { + fxt.Test_rows(new Srch_rslt_row[] {fxt.Make_row(10, "A"), fxt.Make_row(20, "B")}, String_.Concat_lines_nl_skip_last + ( "" + , " " + , " 10" + , " " + , " A" + , " " + , " " + , " " + , " 20" + , " " + , " B" + , " " + , " " + )); + } +} +class Srch_html_page_bldr_fxt { + private Xoae_app app; private Xowe_wiki wiki; private Srch_html_page_bldr html_mgr; private final Bry_bfr tmp_bfr = Bry_bfr_.New_w_size(255); + private int page_id; + public Srch_html_page_bldr_fxt Clear() { + if (app == null) { + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + html_mgr = new Srch_html_page_bldr(); + } + page_id = 0; + return this; + } + public void Test_paging(boolean fwd, int slab_idx, String expd) { + byte[] search_orig = Bry_.new_a7("A"); + Srch_search_qry qry = Srch_search_qry.New__search_page(Xow_domain_itm_.Ary_empty, wiki, app.Addon_mgr().Itms__search__special().Ns_mgr(), Bool_.N, search_orig, slab_idx, 100); + html_mgr.Init_by_wiki(wiki, wiki.Lang().Num_mgr(), qry); + byte[] paging_link = html_mgr.Bld_paging_link(fwd); + Tfds.Eq(expd, String_.new_a7(paging_link)); + } + public void Test_rows(Srch_rslt_row[] rows, String expd) { + Srch_rslt_list rslts = new Srch_rslt_list(); + Srch_html_row_bldr row_bldr = new Srch_html_row_bldr(wiki.Html__lnki_bldr()); + row_bldr.Init(rslts, 0, rows.length); + for (int i = 0; i < rows.length; ++i) + rslts.Add(rows[i]); + row_bldr.Bfr_arg__add(tmp_bfr); + Tfds.Eq_str_lines(expd, tmp_bfr.To_str_and_clear()); + } + public Srch_rslt_row Make_row(int len, String ttl_str) { + byte[] wiki_bry = Bry_.new_a7("w"); + byte[] ttl_bry = Bry_.new_u8(ttl_str); + ++page_id; + Srch_rslt_row rv = new Srch_rslt_row(Srch_rslt_row.Bld_key(wiki_bry, page_id), wiki_bry, wiki.Ttl_parse(ttl_bry), gplx.xowa.wikis.nss.Xow_ns_.Tid__main, ttl_bry, page_id, len, len, Srch_rslt_row.Page_redirect_id_null); + rv.Page_ttl_highlight = rv.Page_ttl.Full_txt_w_ttl_case(); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/htmls/Srch_html_row_bldr.java b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/htmls/Srch_html_row_bldr.java index a27517de8..a5e5ff5a8 100644 --- a/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/htmls/Srch_html_row_bldr.java +++ b/400_xowa/src/gplx/xowa/addons/wikis/searchs/specials/htmls/Srch_html_row_bldr.java @@ -13,3 +13,38 @@ 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.addons.wikis.searchs.specials.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.addons.*; import gplx.xowa.addons.wikis.*; import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.specials.*; +import gplx.xowa.htmls.core.htmls.utls.*; import gplx.langs.htmls.*; +import gplx.xowa.addons.wikis.searchs.searchers.rslts.*; +public class Srch_html_row_bldr implements gplx.core.brys.Bfr_arg { + private final Xoh_lnki_bldr lnki_bldr; + private Srch_rslt_list rslt_list; private int slab_bgn, slab_end; + private final Object thread_lock = new Object(); + public Srch_html_row_bldr(Xoh_lnki_bldr lnki_bldr) {this.lnki_bldr = lnki_bldr;} + public Srch_html_row_bldr Init(Srch_rslt_list rslt_list, int slab_bgn, int slab_end) {this.rslt_list = rslt_list; this.slab_bgn = slab_bgn; this.slab_end = slab_end; return this;} + public void Bfr_arg__add(Bry_bfr bfr) { // A + int rslts_len = rslt_list.Len(); + for (int i = slab_bgn; i < slab_end; ++i) { + if (i >= rslts_len) return; + Srch_rslt_row row = rslt_list.Get_at(i); + Bld_html(bfr, row); + } + } + public void Bld_html(Bry_bfr bfr, Srch_rslt_row row) { + synchronized (thread_lock) { + lnki_bldr.Href_(row.Wiki_bry, row.Page_ttl); + lnki_bldr.Title_(row.Page_ttl.Full_txt_w_ttl_case()); + lnki_bldr.Caption_direct_(row.Page_ttl_display(Bool_.Y)); + fmtr.Bld_many(bfr, Gfh_utl.Encode_id_as_str(row.Key), row.Page_score, lnki_bldr.Bld_to_bry()); + } + } + public Bry_fmt Fmtr() {return fmtr;} private final Bry_fmt fmtr = Bry_fmt.Auto(String_.Concat_lines_nl_skip_last + ( "" + , " " + , " ~{page_len}" + , " " + , " ~{lnki}" // SERVER:"}"); + app.Gfs_mgr().Run_str("app.shell.fetch_page('en.wikipedia.org/wiki/A' 'html');"); // this causes a nullRef error b/c app.user.lang is null + } +} diff --git a/400_xowa/src/gplx/xowa/apps/Xoa_stage_.java b/400_xowa/src/gplx/xowa/apps/Xoa_stage_.java index a27517de8..3bbc4dca5 100644 --- a/400_xowa/src/gplx/xowa/apps/Xoa_stage_.java +++ b/400_xowa/src/gplx/xowa/apps/Xoa_stage_.java @@ -13,3 +13,7 @@ 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.apps; import gplx.*; import gplx.xowa.*; +public class Xoa_stage_ { + public static final byte Tid_ctor = 0, Tid_init = 1, Tid_launch = 2; +} diff --git a/400_xowa/src/gplx/xowa/apps/Xoa_sys_cfg.java b/400_xowa/src/gplx/xowa/apps/Xoa_sys_cfg.java index a27517de8..c07c36506 100644 --- a/400_xowa/src/gplx/xowa/apps/Xoa_sys_cfg.java +++ b/400_xowa/src/gplx/xowa/apps/Xoa_sys_cfg.java @@ -13,3 +13,77 @@ 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.apps; import gplx.*; import gplx.xowa.*; +import gplx.xowa.langs.*; +public class Xoa_sys_cfg implements Gfo_invk { + private Xoae_app app; + public Xoa_sys_cfg(Xoae_app app) {this.app = app;} + public byte[] Lang() {return lang_key;} + public Xoa_sys_cfg Lang_(byte[] v) { + lang_key = Xol_lang_stub_.Get_by_key_or_en(v).Key(); + if (app.Stage() == gplx.xowa.apps.Xoa_stage_.Tid_launch) { // do not update user lang unless launched; DATE:2014-05-26 + Xol_lang_itm lang = app.Lang_mgr().Get_by_or_load(lang_key); + app.Usere().Lang_(lang); + app.Usere().Wiki().Html_mgr().Portal_mgr().Init(); + } + return this; + } private byte[] lang_key = Xol_lang_itm_.Key_en; + public void Init_by_app(Xoa_app app) { + app.Cfg().Type_mgr().Lists__add("list:" + Cfg__lang, Options_list_lang_.new_()); + app.Cfg().Bind_many_app(this, Cfg__lang); + } + private static final String Cfg__lang = "xowa.gui.app.lang"; + public int Options_version() {return options_version;} public Xoa_sys_cfg Options_version_(int v) {options_version = v; return this;} private int options_version = 1; + public Keyval[] Options_lang_list() {if (options_lang_list == null) options_lang_list = Options_list_lang_.new_(); return options_lang_list;} private Keyval[] options_lang_list; + public long Free_mem_when() {return free_mem_when;} long free_mem_when; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_version)) return Xoa_app_.Version; + else if (ctx.Match(k, Invk_build_date)) return Xoa_app_.Build_date; + else if (ctx.Match(k, Invk_free_mem_when_)) free_mem_when = gplx.core.ios.Io_size_.parse_or(m.ReadStr("v"), Io_mgr.Len_mb * 5); + else if (ctx.Match(k, Invk_lang)) return lang_key; + else if (ctx.Match(k, Invk_lang_)) Lang_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_lang_list)) return Options_lang_list(); + else if (ctx.Match(k, Invk_options_version)) return options_version; + else if (ctx.Match(k, Invk_options_version_)) options_version = m.ReadInt("v"); + else if (ctx.Match(k, Cfg__lang)) Lang_(m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_version = "version", Invk_build_date = "build_date", Invk_free_mem_when_ = "free_mem_when_", Invk_options_version = "options_version", Invk_options_version_ = "options_version_" + , Invk_lang = "lang", Invk_lang_ = "lang_", Invk_lang_list = "lang_list"; +} +class Options_list_lang_ { + public static Keyval[] new_() { + Ordered_hash translated = Ordered_hash_.New_bry(); + List_adp untranslated = List_adp_.New(); + Add_itm_many(translated, Xol_lang_stub_.Id_en, Xol_lang_stub_.Id_de, Xol_lang_stub_.Id_pl, Xol_lang_stub_.Id_zh_hans, Xol_lang_stub_.Id_zh_hant); // add langs with translations first, so they alphabetize to top of list + int len = Xol_lang_stub_.Id__max; + for (int i = 0; i < len; i++) { // add rest of langs, but sort by code + Xol_lang_stub itm = Xol_lang_stub_.Get_by_id(i); + if (translated.Has(itm.Key())) continue; + untranslated.Add(itm); + } + untranslated.Sort_by(Xol_lang_stub_.Comparer_key); + + Keyval[] rv = new Keyval[len]; + int translated_max = translated.Count(); + for (int i = 0; i < translated_max; i++) + rv[i] = new_itm((Xol_lang_stub)translated.Get_at(i)); + + for (int i = translated_max; i < len; i++) + rv[i] = new_itm((Xol_lang_stub)untranslated.Get_at(i - translated_max)); + return rv; + } + private static Keyval new_itm(Xol_lang_stub itm) { + String key_str = String_.new_u8(itm.Key()); + String name_str = String_.new_u8(itm.Canonical_name()); + return Keyval_.new_(key_str, name_str + " [" + key_str + "]"); + } + private static void Add_itm_many(Ordered_hash translated, int... langs) { + int langs_len = langs.length; + for (int i = 0; i < langs_len; i++) { + Xol_lang_stub itm = Xol_lang_stub_.Get_by_id(langs[i]); + translated.Add_if_dupe_use_nth(itm.Key(), itm); + } + } +} diff --git a/400_xowa/src/gplx/xowa/apps/Xoa_thread_.java b/400_xowa/src/gplx/xowa/apps/Xoa_thread_.java index a27517de8..f94422456 100644 --- a/400_xowa/src/gplx/xowa/apps/Xoa_thread_.java +++ b/400_xowa/src/gplx/xowa/apps/Xoa_thread_.java @@ -13,3 +13,16 @@ 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.apps; import gplx.*; import gplx.xowa.*; +public class Xoa_thread_ { + public static final String + Key_page_async = "xowa.page.async" + , Key_page_redlink = "xowa.page.redlink" + , Key_page_popup = "xowa.page.popup" + , Key_http_server_main = "xowa.http_server.main" + , Key_bldr_download = "xowa.bldr.download" + , Key_special_search_db = "xowa.special.search.db" + , Key_special_search_cancel = "xowa.special.search.cancel" + , Key_special_suggest = "xowa.special.suggest" + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/Xoa_thread_mgr.java b/400_xowa/src/gplx/xowa/apps/Xoa_thread_mgr.java index a27517de8..6c9c5774b 100644 --- a/400_xowa/src/gplx/xowa/apps/Xoa_thread_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/Xoa_thread_mgr.java @@ -13,3 +13,13 @@ 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.apps; import gplx.*; import gplx.xowa.*; +import gplx.core.threads.*; +public class Xoa_thread_mgr { + public Gfo_thread_pool Page_load_mgr() {return page_load_mgr;} private Gfo_thread_pool page_load_mgr = new Gfo_thread_pool(); + public Gfo_thread_pool File_load_mgr() {return file_load_mgr;} private Gfo_thread_pool file_load_mgr = new Gfo_thread_pool(); + public void Usr_dlg_(Gfo_usr_dlg usr_dlg) { + page_load_mgr.Usr_dlg_(usr_dlg); + file_load_mgr.Usr_dlg_(usr_dlg); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/Xoav_app.java b/400_xowa/src/gplx/xowa/apps/Xoav_app.java index a27517de8..36d775e84 100644 --- a/400_xowa/src/gplx/xowa/apps/Xoav_app.java +++ b/400_xowa/src/gplx/xowa/apps/Xoav_app.java @@ -13,3 +13,103 @@ 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.apps; import gplx.*; import gplx.xowa.*; +import gplx.core.net.*; import gplx.core.log_msgs.*; import gplx.langs.jsons.*; import gplx.core.brys.*; import gplx.core.threads.*; +import gplx.core.ios.*; +import gplx.dbs.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.fsys.*; import gplx.xowa.apps.metas.*; import gplx.xowa.parsers.amps.*; import gplx.xowa.langs.cases.*; import gplx.core.intls.*; import gplx.xowa.users.data.*; +import gplx.xowa.apps.site_cfgs.*; import gplx.xowa.apps.urls.*; import gplx.xowa.files.caches.*; import gplx.xowa.files.imgs.*; +import gplx.xowa.bldrs.css.*; +import gplx.xowa.apps.gfs.*; +import gplx.xowa.htmls.hrefs.*; import gplx.xowa.htmls.core.htmls.utls.*; import gplx.xowa.htmls.bridges.*; +import gplx.xowa.users.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.xwikis.*; import gplx.xowa.wikis.xwikis.parsers.*; import gplx.xowa.wikis.xwikis.sitelinks.*; +import gplx.xowa.guis.cbks.*; import gplx.xowa.guis.tabs.*; +import gplx.xowa.langs.*; +import gplx.xowa.bldrs.wms.*; +import gplx.langs.htmls.encoders.*; +import gplx.xowa.bldrs.*; +import gplx.xowa.addons.*; import gplx.xowa.specials.mgrs.*; +import gplx.xowa.addons.apps.cfgs.*; import gplx.xowa.apps.miscs.*; +public class Xoav_app implements Xoa_app, Gfo_invk { + public Xoav_app(Gfo_usr_dlg usr_dlg, Xoa_app_mode mode, Xog_tab_mgr tab_mgr, String plat_name, Io_url root_dir, Io_url file_dir, Io_url css_dir, Io_url http_root) { + Xoa_app_.Usr_dlg_(usr_dlg); this.usr_dlg = usr_dlg; this.mode = mode; + this.fsys_mgr = new Xoa_fsys_mgr(plat_name, root_dir, root_dir.GenSubDir("wiki"), file_dir, css_dir, http_root); + this.gfs_mgr = new Xoa_gfs_mgr("anonymous", this, fsys_mgr); + this.lang_mgr = new Xoa_lang_mgr(this, gfs_mgr); + this.meta_mgr = new Xoa_meta_mgr(this); + this.file__cache_mgr = new Xof_cache_mgr(usr_dlg, null, null); + this.file__img_mgr = new Xof_img_mgr(); + this.wiki_mgr = new Xoav_wiki_mgr(this, utl_case_mgr); + this.utl_msg_log = Gfo_msg_log.Test(); + this.html__bridge_mgr = new Xoh_bridge_mgr(utl__json_parser); + this.gui__tab_mgr = tab_mgr; + + // user + this.user = new Xouv_user(this, "anonymous", root_dir.GenSubDir_nest("user", "anonymous")); + this.fsys_mgr.Url_finder().Init_by_user(user.Fsys_mgr()); + + this.api_root = null; + this.site_cfg_mgr = new Xoa_site_cfg_mgr(this); + this.bldr = new Xob_bldr(null); + } + public boolean Tid_is_edit() {return Bool_.N;} + public Xoa_app_mode Mode() {return mode;} private final Xoa_app_mode mode; + public Xou_user User() {return user;} private final Xouv_user user; + public Xoapi_root Api_root() {return api_root;} private final Xoapi_root api_root; + public Xoa_fsys_mgr Fsys_mgr() {return fsys_mgr;} private final Xoa_fsys_mgr fsys_mgr; + public Xoav_wiki_mgr Wiki_mgr() {return wiki_mgr;} private final Xoav_wiki_mgr wiki_mgr; + public Xoa_wiki_mgr Wiki_mgri() {return wiki_mgr;} + public Xoa_lang_mgr Lang_mgr() {return lang_mgr;} private final Xoa_lang_mgr lang_mgr; + public Xoa_gfs_mgr Gfs_mgr() {return gfs_mgr;} private final Xoa_gfs_mgr gfs_mgr; + public Xof_cache_mgr File__cache_mgr() {return file__cache_mgr;} private final Xof_cache_mgr file__cache_mgr; + public Xof_img_mgr File__img_mgr() {return file__img_mgr;} private final Xof_img_mgr file__img_mgr; + public Io_download_fmt File__download_fmt() {return file__download_fmt;} private final Io_download_fmt file__download_fmt = new Io_download_fmt(); + public Xoh_href_parser Html__href_parser() {return href_parser;} private final Xoh_href_parser href_parser = new Xoh_href_parser(); + public Xoh_href_wtr Html__href_wtr() {return html__href_wtr;} private final Xoh_href_wtr html__href_wtr = new Xoh_href_wtr(); + public Xoa_css_extractor Html__css_installer() {return html__css_installer;} private final Xoa_css_extractor html__css_installer = new Xoa_css_extractor(); + public Xoh_bridge_mgr Html__bridge_mgr() {return html__bridge_mgr;} private final Xoh_bridge_mgr html__bridge_mgr; + public Xoa_meta_mgr Dbmeta_mgr() {return meta_mgr;} private final Xoa_meta_mgr meta_mgr; + public Gfo_inet_conn Utl__inet_conn() {return inet_conn;} private final Gfo_inet_conn inet_conn = Gfo_inet_conn_.new_(); + public Xoa_site_cfg_mgr Site_cfg_mgr() {return site_cfg_mgr;} private final Xoa_site_cfg_mgr site_cfg_mgr; + public boolean Xwiki_mgr__missing(byte[] domain) {return wiki_mgr.Get_by_or_null(domain) == null;} + public Xoa_sitelink_mgr Xwiki_mgr__sitelink_mgr() {return xwiki_mgr__sitelink_mgr;} private final Xoa_sitelink_mgr xwiki_mgr__sitelink_mgr = new Xoa_sitelink_mgr(); + public Xow_xwiki_itm_parser Xwiki_mgr__itm_parser() {return xwiki_mgr__itm_parser;} private final Xow_xwiki_itm_parser xwiki_mgr__itm_parser = new Xow_xwiki_itm_parser(); + public Xoax_addon_mgr Addon_mgr() {return addon_mgr;} private final Xoax_addon_mgr addon_mgr = new Xoax_addon_mgr(); + public Xob_bldr Bldr() {return bldr;} private final Xob_bldr bldr; + public Xoa_special_regy Special_regy() {return special_regy;} private final Xoa_special_regy special_regy = new Xoa_special_regy(); + public Xog_cbk_mgr Gui__cbk_mgr() {return gui__cbk_mgr;} private final Xog_cbk_mgr gui__cbk_mgr = new Xog_cbk_mgr(); + public Xog_tab_mgr Gui__tab_mgr() {return gui__tab_mgr;} private final Xog_tab_mgr gui__tab_mgr; + public Gfo_thread_mgr Thread_mgr() {return thread_mgr;} private final Gfo_thread_mgr thread_mgr = new Gfo_thread_mgr(); + public Xop_amp_mgr Parser_amp_mgr() {return parser_amp_mgr;} private final Xop_amp_mgr parser_amp_mgr = Xop_amp_mgr.Instance; + public Xocfg_mgr Cfg() {return cfg;} private final Xocfg_mgr cfg = new Xocfg_mgr(); + public Xoa_misc_mgr Misc_mgr() {return misc_mgr;} private final Xoa_misc_mgr misc_mgr = new Xoa_misc_mgr(); + + public Xowmf_mgr Wmf_mgr() {return wmf_mgr;} private final Xowmf_mgr wmf_mgr = new Xowmf_mgr(); + public Gfo_usr_dlg Usr_dlg() {return usr_dlg;} public void Usr_dlg_(Gfo_usr_dlg v) {usr_dlg = v; Xoa_app_.Usr_dlg_(usr_dlg);} private Gfo_usr_dlg usr_dlg = Gfo_usr_dlg_.Noop; + public Bry_bfr_mkr Utl__bfr_mkr() {return utl__bry_bfr_mkr;} private final Bry_bfr_mkr utl__bry_bfr_mkr = new Bry_bfr_mkr(); + public Json_parser Utl__json_parser() {return utl__json_parser;} private final Json_parser utl__json_parser = new Json_parser(); + public boolean Bldr__running() {return bldr__running;} public void Bldr__running_(boolean v) {this.bldr__running = v;} private boolean bldr__running; + public Xop_amp_mgr Utl_amp_mgr() {return utl_amp_mgr;} private Xop_amp_mgr utl_amp_mgr = Xop_amp_mgr.Instance; + public Xol_case_mgr Utl_case_mgr() {return utl_case_mgr;} private Xol_case_mgr utl_case_mgr = Xol_case_mgr_.U8(); +// public Gfo_url_encoder Utl_encoder_fsys() {return utl_encoder_fsys;} private Gfo_url_encoder utl_encoder_fsys = Gfo_url_encoder.New_fsys_lnx(); + public Gfo_msg_log Utl_msg_log() {return utl_msg_log;} private Gfo_msg_log utl_msg_log; + public Xoav_url_parser Utl_url_parser_xo() {return utl_url_parser_xo;} private Xoav_url_parser utl_url_parser_xo = new Xoav_url_parser(); + public Gfo_url_parser Utl_url_parser_gfo() {return utl_url_parser_gfo;} private final Gfo_url_parser utl_url_parser_gfo = new Gfo_url_parser(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {throw Err_.new_unimplemented_w_msg("implemented for Xoa_cfg_mgr");} + public void Init_by_app(Io_url user_db_url) { + user.Init_db(this, wiki_mgr, user_db_url); + this.Addon_mgr().Add_dflts_by_app(this).Run_by_app(this); + cfg.Init_by_app(this); + misc_mgr.Init_by_app(this); + } + public void Free_mem() { // NOTE:not yet called in drd; DATE:2016-12-04 + } + public static Xoav_app New_by_drd(gplx.xowa.drds.files.Xod_fsys_mgr fsys_mgr, Xog_tab_mgr tab_mgr) { + // create log + Gfo_usr_dlg__log_base log = new Gfo_usr_dlg__log_base(); log.Log_dir_(Io_url_.mem_dir_("mem/tmp")); + Gfo_usr_dlg usr_dlg = new Gfo_usr_dlg_base(log, Gfo_usr_dlg__gui_.Console); + Xoa_app_.Usr_dlg_(usr_dlg); + + return new Xoav_app(usr_dlg, Xoa_app_mode.Itm_gui, tab_mgr, "drd", fsys_mgr.App_root_dir(), fsys_mgr.Usr_data_dir(), fsys_mgr.Usr_data_dir().GenSubDir("temp"), Io_url_.new_any_("/android_asset/xowa/")); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/Xoav_wiki_mgr.java b/400_xowa/src/gplx/xowa/apps/Xoav_wiki_mgr.java index a27517de8..46930177b 100644 --- a/400_xowa/src/gplx/xowa/apps/Xoav_wiki_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/Xoav_wiki_mgr.java @@ -13,3 +13,36 @@ 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.apps; import gplx.*; import gplx.xowa.*; +import gplx.xowa.*; import gplx.xowa.langs.cases.*; import gplx.xowa.users.data.*; +import gplx.xowa.wikis.*; +public class Xoav_wiki_mgr implements Xoa_wiki_mgr { + private final Xoav_app app; private final Ordered_hash hash = Ordered_hash_.New_bry(); + public Xoav_wiki_mgr(Xoav_app app, Xol_case_mgr case_mgr) {this.app = app;} + public int Count() {return hash.Count();} + public boolean Has(byte[] key) {return hash.Has(key);} + public Xow_wiki Get_at(int idx) {return (Xow_wiki)hash.Get_at(idx);} + public Xow_wiki Get_by_or_null(byte[] key) {return (Xow_wiki)hash.Get_by(key);} + public Xow_wiki Get_by_or_make_init_y(byte[] key) { + Xow_wiki rv = this.Get_by_or_null(key); + rv.Init_by_wiki(); + return rv; + } + public Xow_wiki Get_by_or_make_init_n(byte[] key) {return Get_by_or_make_init_y(key);} + public void Add(Xow_wiki wiki) {hash.Add_if_dupe_use_nth(wiki.Domain_bry(), wiki);} + public Xow_wiki Make(byte[] domain_bry, Io_url wiki_root_dir) {return new Xowv_wiki(app, domain_bry, wiki_root_dir);} + public Xow_wiki Import_by_url(Io_url url) {return Xoa_wiki_mgr_.Import_by_url(app, this, url);} + public void Load_by_user_data() { + Xoud_site_row[] ary = app.User().User_db_mgr().Site_mgr().Get_all(); + int len = ary.length; + for (int i = 0; i < len; ++i) { + Xoud_site_row itm = ary[i]; + Xow_wiki wiki = Make(Bry_.new_u8(itm.Domain()), Io_url_.new_dir_(itm.Path())); + this.Add(wiki); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Xoa_wiki_mgr_.Invk__import_by_url)) return this.Import_by_url(m.ReadIoUrl("v")); + else return Gfo_invk_.Rv_unhandled; + } +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/Xoapi_root.java b/400_xowa/src/gplx/xowa/apps/apis/Xoapi_root.java index a27517de8..d7ec4fc6c 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/Xoapi_root.java +++ b/400_xowa/src/gplx/xowa/apps/apis/Xoapi_root.java @@ -13,3 +13,58 @@ 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.apps.apis; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.guis.cmds.*; +public class Xoapi_root implements Gfo_invk { + private Xoae_app app; + public Xoapi_root(Xoae_app app) { + app_api.Ctor_by_app(app); + usr_api.Ctor_by_app(app); + bldr_api.Ctor_by_app(app); + html_api.Ctor_by_app(app); + } + public void Init_by_kit(Xoae_app app) { + this.app = app; + app_api.Init_by_kit(app); + nav_api.Init_by_kit(app); + gui_api.Init_by_kit(app); + html_api.Init_by_kit(app); + usr_api.Init_by_kit(app); + xtns_api.Init_by_kit(app); + } + public void Init_by_app(Xoae_app app) { + html_api.Init_by_app(app); + usr_api.Init_by_app(app); + } + public Xoapi_addon Addon() {return addon_api;} private final Xoapi_addon addon_api = new Xoapi_addon(); + public Xoapi_app App() {return app_api;} private final Xoapi_app app_api = new Xoapi_app(); + public Xoapi_nav Nav() {return nav_api;} private final Xoapi_nav nav_api = new Xoapi_nav(); + public Xoapi_gui Gui() {return gui_api;} private final Xoapi_gui gui_api = new Xoapi_gui(); + public Xoapi_html Html() {return html_api;} private final Xoapi_html html_api = new Xoapi_html(); + public Xoapi_bldr Bldr() {return bldr_api;} private final Xoapi_bldr bldr_api = new Xoapi_bldr(); + public Xoapi_usr Usr() {return usr_api;} private final Xoapi_usr usr_api = new Xoapi_usr(); + public Xoapi_xtns Xtns() {return xtns_api;} private final Xoapi_xtns xtns_api = new Xoapi_xtns(); + public String Test_str() {return test_str;} public void Test_str_(String v) {test_str = v;} private String test_str; // TEST + private void Exec(String key) { + Xog_cmd_itm cmd_itm = app.Gui_mgr().Cmd_mgr().Get_or_null(key); + if (cmd_itm == null) app.Usr_dlg().Warn_many("", "", "could not find cmd; key=~{0}", key); + app.Gfs_mgr().Run_str_for(app, cmd_itm.Cmd()); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_app)) return app_api; + else if (ctx.Match(k, Invk_addon)) return addon_api; + else if (ctx.Match(k, Invk_bldr)) return bldr_api; + else if (ctx.Match(k, Invk_nav)) return nav_api; + else if (ctx.Match(k, Invk_gui)) return gui_api; + else if (ctx.Match(k, Invk_html)) return html_api; + else if (ctx.Match(k, Invk_usr)) return usr_api; + else if (ctx.Match(k, Invk_xtns)) return xtns_api; + else if (ctx.Match(k, Invk_exec)) Exec(m.ReadStr("v")); + else if (ctx.Match(k, Invk_test_str)) return test_str; + else if (ctx.Match(k, Invk_test_str_)) test_str = m.ReadStr("v"); + return this; + } + private static final String Invk_exec = "exec", Invk_test_str = "test_str", Invk_test_str_ = "test_str_" + , Invk_app = "app", Invk_addon = "addon" + , Invk_bldr = "bldr", Invk_nav = "nav", Invk_gui = "gui", Invk_html = "html", Invk_usr = "usr", Invk_xtns = "xtns"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_addon.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_addon.java index a27517de8..ccec942fd 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_addon.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_addon.java @@ -13,3 +13,16 @@ 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.apps.apis.xowa; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; +import gplx.xowa.apps.apis.xowa.addons.*; +public class Xoapi_addon implements Gfo_invk { + public void Ctor_by_app(Xoa_app app) {} + public Xoapi_addon_search Search() {return search;} private final Xoapi_addon_search search = new Xoapi_addon_search(); + public Xoapi_addon_bldr Bldr() {return bldr;} private final Xoapi_addon_bldr bldr = new Xoapi_addon_bldr(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__search)) return search; + else if (ctx.Match(k, Invk__bldr)) return bldr; + else return Gfo_invk_.Rv_unhandled; + } + private static final String Invk__search = "search", Invk__bldr = "bldr"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_app.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_app.java index a27517de8..147dfe86b 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_app.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_app.java @@ -13,3 +13,24 @@ 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.apps.apis.xowa; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; +import gplx.xowa.guis.views.*; +import gplx.xowa.apps.apis.xowa.apps.*; +public class Xoapi_app implements Gfo_invk { + private Xog_win_itm win; + public void Ctor_by_app(Xoae_app app) { + fsys.Ctor_by_app(app); + } + public void Init_by_kit(Xoae_app app) { + win = app.Gui_mgr().Browser_win(); + } + public Xoapi_fsys Fsys() {return fsys;} private Xoapi_fsys fsys = new Xoapi_fsys(); + public void Exit() {win.App__exit();} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_exit)) this.Exit(); + else if (ctx.Match(k, Invk_fsys)) return fsys; + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_exit = "exit", Invk_fsys = "fsys"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_bldr.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_bldr.java index a27517de8..bd4731f64 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_bldr.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_bldr.java @@ -13,3 +13,14 @@ 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.apps.apis.xowa; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; +import gplx.xowa.apps.apis.xowa.bldrs.*; +public class Xoapi_bldr implements Gfo_invk { + public void Ctor_by_app(Xoa_app app) {wiki.Ctor_by_app(app);} + public Xoapi_bldr_wiki Wiki() {return wiki;} private final Xoapi_bldr_wiki wiki = new Xoapi_bldr_wiki(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_wiki)) return wiki; + else return Gfo_invk_.Rv_unhandled; + } + private static final String Invk_wiki = "wiki"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_gui.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_gui.java index a27517de8..22d8a1f3a 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_gui.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_gui.java @@ -13,3 +13,22 @@ 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.apps.apis.xowa; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; +import gplx.xowa.apps.apis.xowa.gui.*; +public class Xoapi_gui implements Gfo_invk { + public void Init_by_kit(Xoae_app app) { + browser.Init_by_kit(app); + font.Init_by_kit(app); + page.Init_by_kit(app); + } + public Xoapi_browser Browser() {return browser;} private Xoapi_browser browser = new Xoapi_browser(); + public Xoapi_font Font() {return font;} private Xoapi_font font = new Xoapi_font(); + public Xoapi_page Page() {return page;} private Xoapi_page page = new Xoapi_page(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_browser)) return browser; + else if (ctx.Match(k, Invk_font)) return font; + else if (ctx.Match(k, Invk_page)) return page; + return this; + } + private static final String Invk_browser = "browser", Invk_font = "font", Invk_page = "page"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_html.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_html.java index a27517de8..47faa61ce 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_html.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_html.java @@ -13,3 +13,20 @@ 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.apps.apis.xowa; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; +import gplx.xowa.apps.apis.xowa.html.*; +public class Xoapi_html implements Gfo_invk { + public void Ctor_by_app(Xoae_app app) { + page.Ctor_by_app(app); + } + public void Init_by_app(Xoae_app app) {page.Init_by_app(app);} + public void Init_by_kit(Xoae_app app) {modules.Init_by_kit(app);} + public Xoapi_modules Modules() {return modules;} private final Xoapi_modules modules = new Xoapi_modules(); + public Xoapi_page Page() {return page;} private final Xoapi_page page = new Xoapi_page(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_modules)) return modules; + else if (ctx.Match(k, Invk_page)) return page; + else return Gfo_invk_.Rv_unhandled; + } + private static final String Invk_modules = "modules", Invk_page = "page"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_nav.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_nav.java index a27517de8..619ff75ee 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_nav.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_nav.java @@ -13,3 +13,26 @@ 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.apps.apis.xowa; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; +import gplx.xowa.guis.views.*; +import gplx.xowa.apps.apis.xowa.navs.*; +public class Xoapi_nav implements Gfo_invk { + private Xog_win_itm win; + public void Init_by_kit(Xoae_app app) { + win = app.Gui_mgr().Browser_win(); + wiki.Init_by_kit(app); + } + public Xoapi_nav_wiki Wiki() {return wiki;} private Xoapi_nav_wiki wiki = new Xoapi_nav_wiki(); + public void Goto(String page) {win.Page__navigate_by_url_bar(page);} + public void Go_bwd() {win.Page__navigate_by_history(Bool_.N);} + public void Go_fwd() {win.Page__navigate_by_history(Bool_.Y);} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_go_bwd)) this.Go_bwd(); + else if (ctx.Match(k, Invk_go_fwd)) this.Go_fwd(); + else if (ctx.Match(k, Invk_goto)) this.Goto(m.ReadStr("v")); + else if (ctx.Match(k, Invk_wiki)) return wiki; + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_go_bwd = "go_bwd", Invk_go_fwd = "go_fwd", Invk_goto = "goto", Invk_wiki = "wiki"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_usr.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_usr.java index a27517de8..25557d0fa 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_usr.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_usr.java @@ -13,3 +13,25 @@ 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.apps.apis.xowa; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; +import gplx.xowa.apps.apis.xowa.usrs.*; +public class Xoapi_usr implements Gfo_invk { + public void Ctor_by_app(Xoae_app app) { + bookmarks.Ctor_by_app(app); + history.Ctor_by_app(app); + } + public void Init_by_app(Xoa_app app) { + } + public void Init_by_kit(Xoae_app app) { + bookmarks.Init_by_kit(app); + history.Init_by_kit(app); + } + public Xoapi_bookmarks Bookmarks() {return bookmarks;} private final Xoapi_bookmarks bookmarks = new Xoapi_bookmarks(); + public Xoapi_history History() {return history;} private final Xoapi_history history = new Xoapi_history(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_bookmarks)) return bookmarks; + else if (ctx.Match(k, Invk_history)) return history; + else return Gfo_invk_.Rv_unhandled; + } + private static final String Invk_bookmarks = "bookmarks", Invk_history = "history"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_xtns.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_xtns.java index a27517de8..3825c0db4 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_xtns.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/Xoapi_xtns.java @@ -13,3 +13,16 @@ 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.apps.apis.xowa; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; +import gplx.xowa.apps.apis.xowa.xtns.*; +public class Xoapi_xtns implements Gfo_invk { + public void Init_by_kit(Xoae_app app) { + wikibase.Init_by_app(app); + } + public Xoapi_wikibase Wikibase() {return wikibase;} private final Xoapi_wikibase wikibase = new Xoapi_wikibase(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_wikibase)) return wikibase; + else return Gfo_invk_.Rv_unhandled; + } + private static final String Invk_wikibase = "wikibase"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/Xoapi_addon_bldr.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/Xoapi_addon_bldr.java index a27517de8..6a59c2bc5 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/Xoapi_addon_bldr.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/Xoapi_addon_bldr.java @@ -13,3 +13,13 @@ 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.apps.apis.xowa.addons; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; +import gplx.xowa.apps.apis.xowa.addons.bldrs.*; +public class Xoapi_addon_bldr implements Gfo_invk { + public Xoapi_central_api Central() {return central;} private final Xoapi_central_api central = new Xoapi_central_api(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__central)) return central; + else return Gfo_invk_.Rv_unhandled; + } + private static final String Invk__central = "central"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/Xoapi_addon_search.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/Xoapi_addon_search.java index a27517de8..59320654d 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/Xoapi_addon_search.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/Xoapi_addon_search.java @@ -13,3 +13,19 @@ 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.apps.apis.xowa.addons; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.domains.crts.*; +import gplx.xowa.apps.apis.xowa.addons.searchs.*; +public class Xoapi_addon_search implements Gfo_invk { + @gplx.Internal protected Xoapi_search_box Search_box() {return search_box;} private final Xoapi_search_box search_box = new Xoapi_search_box(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__search_box)) return search_box; + else return Gfo_invk_.Rv_unhandled; + } + private static final String Invk__search_box = "search_box"; +} +class Xoapi_search_box implements Gfo_invk { + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + return null; + } +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/bldrs/Xoapi_central_api.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/bldrs/Xoapi_central_api.java index a27517de8..b186ea280 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/bldrs/Xoapi_central_api.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/bldrs/Xoapi_central_api.java @@ -13,3 +13,15 @@ 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.apps.apis.xowa.addons.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.addons.*; +public class Xoapi_central_api implements Gfo_invk { + public boolean Log_verbose() {return log_verbose;} private boolean log_verbose = false; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__log_verbose_)) log_verbose = m.ReadBool("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk__log_verbose_ = "log_verbose_" + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/bldrs/Xopg_match_mgr.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/bldrs/Xopg_match_mgr.java index a27517de8..e4d8526b3 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/bldrs/Xopg_match_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/bldrs/Xopg_match_mgr.java @@ -13,3 +13,81 @@ 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.apps.apis.xowa.addons.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.addons.*; +import gplx.xowa.apps.urls.*; +public class Xopg_match_mgr { + private String scope_raw; + private Ordered_hash wikis; + private boolean wildcard_exists; + private Xopg_match_wiki wildcard_wiki; + public void Set(String v) { + this.scope_raw = v; + this.wikis = null; + this.wildcard_exists = false; + this.wildcard_wiki = null; + } + public boolean Match(Xow_wiki wiki, byte[] page_ttl) { + if (wikis == null) Init(wiki.App()); + + if (wildcard_exists) return true; + if (wildcard_wiki != null) { + if (wildcard_wiki.Has(page_ttl)) + return true; + } + Xopg_match_wiki match_wiki = (Xopg_match_wiki)wikis.Get_by(wiki.Domain_bry()); + if (match_wiki == null) return false; + return match_wiki.Has(page_ttl); + } + private void Init(Xoa_app app) { + this.wikis = Ordered_hash_.New_bry(); + String[] lines = String_.SplitLines_nl(scope_raw); + Xow_url_parser url_parser = app.User().Wikii().Utl__url_parser(); + for (String line : lines) { + if (String_.Eq(line, "*")) { + wildcard_exists = true; + } + else { + byte[] wiki_domain = null, page_db = null; + boolean cur_is_wildcard_wiki = false; + if (String_.Has_at_bgn(line, "*:")) { + wiki_domain = Byte_ascii.Star_bry; + page_db = Bry_.Mid(Bry_.new_u8(line), 2); + cur_is_wildcard_wiki = true; + } + else { + Xoa_url url = url_parser.Parse_by_urlbar_or_null(line); + wiki_domain = url.Wiki_bry(); + page_db = url.Page_bry(); + } + Xopg_match_wiki match_wiki = (Xopg_match_wiki)wikis.Get_by(wiki_domain); + if (match_wiki == null) { + match_wiki = new Xopg_match_wiki(wiki_domain); + wikis.Add(wiki_domain, match_wiki); + if (cur_is_wildcard_wiki) { + wildcard_wiki = match_wiki; + } + } + match_wiki.Add(page_db); + } + } + } +} +class Xopg_match_wiki { + private final Ordered_hash hash = Ordered_hash_.New_bry(); + private boolean wildcard_exists; + public Xopg_match_wiki(byte[] domain_bry) { + this.domain_bry = domain_bry; + } + public byte[] Domain_bry() {return domain_bry;} private final byte[] domain_bry; + public boolean Has(byte[] page_db) { + return wildcard_exists ? true : hash.Has(page_db); + } + public void Add(byte[] page_db) { + if (Bry_.Eq(page_db, Byte_ascii.Star_bry)) { + wildcard_exists = true; + } + else { + hash.Add_if_dupe_use_1st(page_db, page_db); + } + } +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/bldrs/Xopg_match_mgr__tst.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/bldrs/Xopg_match_mgr__tst.java index a27517de8..f10d892e9 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/bldrs/Xopg_match_mgr__tst.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/bldrs/Xopg_match_mgr__tst.java @@ -13,3 +13,56 @@ 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.apps.apis.xowa.addons.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.addons.*; +import org.junit.*; import gplx.core.tests.*; +public class Xopg_match_mgr__tst { + private final Xopg_match_mgr__fxt fxt = new Xopg_match_mgr__fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Specific() { + fxt.Init__set("en.w:A"); + fxt.Test__match_y("A"); + fxt.Test__match_n("AB"); + } + @Test public void Other_wiki() { + fxt.Init__set("fr.w:A"); + fxt.Test__match_n("A"); // note that test defaults to "en.w" as primary wiki + } + @Test public void Wildcard__app() { + fxt.Init__set("*"); + fxt.Test__match_y("A", "B"); + } + @Test public void Wildcard__page() { + fxt.Init__set("en.w:*"); + fxt.Test__match_y("A", "B"); + } + @Test public void Wildcard__page__other() { + fxt.Init__set("fr.w:*"); + fxt.Test__match_n("A", "B"); + } + @Test public void Wildcard__wiki() { + fxt.Init__set("*:A"); + fxt.Test__match_y("A"); + fxt.Test__match_n("B"); + } +} +class Xopg_match_mgr__fxt { + private final Xopg_match_mgr match_mgr = new Xopg_match_mgr(); + private Xowe_wiki wiki; + public void Clear() { + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + this.wiki = Xoa_app_fxt.Make__wiki__edit(app); + app.User().Wikii().Xwiki_mgr().Add_by_atrs(wiki.Domain_bry(), wiki.Domain_bry()); + } + public void Init__set(String url) { + match_mgr.Set(url); + } + public void Test__match_y(String... urls) {Test__match(Bool_.Y, urls);} + public void Test__match_n(String... urls) {Test__match(Bool_.N, urls);} + private void Test__match(boolean expd, String... urls) { + for (int i = 0; i < urls.length; i++) { + String url = urls[i]; + boolean actl = match_mgr.Match(wiki, Bry_.new_u8(url)); + Gftest.Eq__bool(expd, actl, "match failed", "expd", expd, "url", url); + } + } +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/searchs/Xoapi_search_mode_.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/searchs/Xoapi_search_mode_.java index a27517de8..8475626d5 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/searchs/Xoapi_search_mode_.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/addons/searchs/Xoapi_search_mode_.java @@ -13,3 +13,20 @@ 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.apps.apis.xowa.addons.searchs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.addons.*; +public class Xoapi_search_mode_ { + public static final int Tid__title_full = 0, Tid__title_word = 1; + public static final String Str__title_full = "title.full", Str__title_word = "title.word"; + public static String To_str(int v) { + switch (v) { + case Tid__title_full: return Str__title_full; + case Tid__title_word: return Str__title_word; + default: throw Err_.new_unhandled_default(v); + } + } + public static int To_int(String v) { + if (String_.Eq(v, Str__title_full)) return Tid__title_full; + else if (String_.Eq(v, Str__title_word)) return Tid__title_word; + else throw Err_.new_unhandled_default(v); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/apps/Xoapi_fsys.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/apps/Xoapi_fsys.java index a27517de8..be812cbd5 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/apps/Xoapi_fsys.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/apps/Xoapi_fsys.java @@ -13,3 +13,19 @@ 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.apps.apis.xowa.apps; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; +import gplx.core.envs.*; +public class Xoapi_fsys implements Gfo_invk { + public void Ctor_by_app(Xoae_app app) { + this.plat_jar = Env_.AppUrl(); + this.root_dir = app.Fsys_mgr().Root_dir(); + } + public Io_url Plat_jar() {return plat_jar;} private Io_url plat_jar; + public Io_url Plat_url(String s) {return Io_url_.new_any_(root_dir.Gen_sub_path_for_os(s));} private Io_url root_dir; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_plat_jar)) return plat_jar; + else if (ctx.Match(k, Invk_plat_url)) return Plat_url(m.ReadStr("v")); + else return Gfo_invk_.Rv_unhandled; + } + private static final String Invk_plat_jar = "plat_jar", Invk_plat_url = "plat_url"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/bldrs/Xoapi_bldr_wiki.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/bldrs/Xoapi_bldr_wiki.java index a27517de8..f44b5dbd4 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/bldrs/Xoapi_bldr_wiki.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/bldrs/Xoapi_bldr_wiki.java @@ -13,3 +13,20 @@ 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.apps.apis.xowa.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; +import gplx.xowa.apps.apis.xowa.bldrs.filters.*; +import gplx.xowa.apps.apis.xowa.bldrs.imports.*; +import gplx.xowa.apps.apis.xowa.bldrs.runners.*; +public class Xoapi_bldr_wiki implements Gfo_invk { + public void Ctor_by_app(Xoa_app app) { + runner.Ctor_by_app(app); + } + public Xoapi_import Import() {return import_api;} private final Xoapi_import import_api = new Xoapi_import(); + public Xoapi_runner Runner() {return runner;} private final Xoapi_runner runner = new Xoapi_runner(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_import)) return import_api; + else if (ctx.Match(k, Invk_runner)) return runner; + else return Gfo_invk_.Rv_unhandled; + } + private static final String Invk_import = "import", Invk_runner = "runner"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/bldrs/filters/titles/Xoapi_title.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/bldrs/filters/titles/Xoapi_title.java index a27517de8..eb1cd7c4b 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/bldrs/filters/titles/Xoapi_title.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/bldrs/filters/titles/Xoapi_title.java @@ -13,3 +13,35 @@ 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.apps.apis.xowa.bldrs.filters.titles; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.bldrs.*; import gplx.xowa.apps.apis.xowa.bldrs.filters.*; +public class Xoapi_title implements Gfo_invk { + public void Init_by_kit(Xoae_app app) { +// wordlist_dir = app.Fsys_mgr().Bin_xtns_dir().GenSubDir_nest("xowa", "DansGuardian"); + } + public boolean Enabled() {return enabled;} private boolean enabled = Bool_.Y; + public Io_url Wordlist_dir() {return wordlist_dir;} private Io_url wordlist_dir; + public int Score_init() {return score_init;} private int score_init = 0; + public int Score_pass() {return score_pass;} private int score_pass = 0; + public boolean Log_enabled() {return log_enabled;} private boolean log_enabled = Bool_.Y; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_enabled)) return Yn.To_str(enabled); + else if (ctx.Match(k, Invk_enabled_)) enabled = m.ReadYn("v"); + else if (ctx.Match(k, Invk_wordlist_dir)) return Int_.To_str(score_init); + else if (ctx.Match(k, Invk_wordlist_dir_)) wordlist_dir= m.ReadIoUrl("v"); + else if (ctx.Match(k, Invk_score_init)) return Int_.To_str(score_init); + else if (ctx.Match(k, Invk_score_init_)) score_init = m.ReadInt("v"); + else if (ctx.Match(k, Invk_score_pass)) return Int_.To_str(score_pass); + else if (ctx.Match(k, Invk_score_pass_)) score_pass = m.ReadInt("v"); + else if (ctx.Match(k, Invk_log_enabled)) return Yn.To_str(enabled); + else if (ctx.Match(k, Invk_log_enabled_)) log_enabled = m.ReadYn("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk_enabled = "enabled" , Invk_enabled_ = "enabled_" + , Invk_wordlist_dir = "wordlist_dir" , Invk_wordlist_dir_ = "wordlist_dir_" + , Invk_score_init = "score_init" , Invk_score_init_ = "score_init_" + , Invk_score_pass = "score_pas" , Invk_score_pass_ = "score_pass_" + , Invk_log_enabled = "log_enabled" , Invk_log_enabled_ = "log_enabled_" + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/bldrs/imports/Xoapi_import.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/bldrs/imports/Xoapi_import.java index a27517de8..e15c45599 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/bldrs/imports/Xoapi_import.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/bldrs/imports/Xoapi_import.java @@ -13,3 +13,22 @@ 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.apps.apis.xowa.bldrs.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.bldrs.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; +import gplx.xowa.wikis.data.*; +public class Xoapi_import implements Gfo_invk { + public long Cat_link_db_max() {return cat_link_db_max;} private long cat_link_db_max = Io_size_.To_long_by_int_mb(1500); // 3.6 GB; v1 + public String User_name() {return user_name;} private String user_name = "anonymous"; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_cat_link_db_max)) return Io_size_.To_str_mb(cat_link_db_max); + else if (ctx.Match(k, Invk_cat_link_db_max_)) cat_link_db_max = Io_size_.To_long_by_msg_mb(m, cat_link_db_max); + else if (ctx.Match(k, Invk_user_name)) return user_name; + else if (ctx.Match(k, Invk_user_name_)) user_name = m.ReadStr("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk_cat_link_db_max = "cat_link_db_max" , Invk_cat_link_db_max_ = "cat_link_db_max_" + , Invk_user_name = "user_name" , Invk_user_name_ = "user_name_" + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/bldrs/runners/Xoapi_runner.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/bldrs/runners/Xoapi_runner.java index a27517de8..defd97ea3 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/bldrs/runners/Xoapi_runner.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/bldrs/runners/Xoapi_runner.java @@ -13,3 +13,33 @@ 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.apps.apis.xowa.bldrs.runners; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.bldrs.*; +public class Xoapi_runner implements Gfo_invk { + // private Xoa_app app; + public void Ctor_by_app(Xoa_app app) {}//this.app = app;} + private void Exec(GfoMsg msg) { +// int len = msg.Args_count(); +// String cmd = (String)msg.Args_getAt(0).Val(); +// Keyval[] args = new Keyval[len - 1]; +// for (int i = 1; i < len; ++i) { +// String arg = (String)msg.Args_getAt(i).Val(); +// int eq_pos = String_.FindFwd(arg, "="); +// String key = String_.Mid(arg, 0, eq_pos); +// String val = String_.Mid(arg, eq_pos + 1); +// args[i - 1] = Keyval_.new_(key, val); +// } +// gplx.core.ios.zips.Io_zip_decompress_task task = new gplx.core.ios.zips.Io_zip_decompress_task(); + // task.Init(true, Gfo + // app.Gui__cbk_mgr().Send_prog("test", "key_0", "val_0"); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_exec)) Exec(m); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_exec = "exec"; +} +// class Xodl_prog_ui : gplx.core.progs.Gfo_prog_ui { +// public void Prog__update_val(long cur, long max) {} +// public void Prog__end() {} +// } diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/Xoapi_browser.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/Xoapi_browser.java index a27517de8..75471cc92 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/Xoapi_browser.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/Xoapi_browser.java @@ -13,3 +13,63 @@ 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.apps.apis.xowa.gui; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; +import gplx.xowa.apps.apis.xowa.gui.browsers.*; +public class Xoapi_browser implements Gfo_invk { + private Xoae_app app; + public void Init_by_kit(Xoae_app app) { + this.app = app; + url.Init_by_kit(app); + tabs.Init_by_kit(app); + html.Init_by_kit(app); + search.Init_by_kit(app); + allpages.Init_by_kit(app); + find.Init_by_kit(app); + prog.Init_by_kit(app); + info.Init_by_kit(app); + prog_log.Init_by_kit(app); + } + public Xoapi_url Url() {return url;} private Xoapi_url url = new Xoapi_url(); + public Xoapi_tabs Tabs() {return tabs;} private Xoapi_tabs tabs = new Xoapi_tabs(); + public Xoapi_html_box Html() {return html;} private Xoapi_html_box html = new Xoapi_html_box(); + public Xoapi_search Search() {return search;} private Xoapi_search search = new Xoapi_search(); + public Xoapi_allpages Allpages() {return allpages;} private Xoapi_allpages allpages = new Xoapi_allpages(); + public Xoapi_find Find() {return find;} private Xoapi_find find = new Xoapi_find(); + public Xoapi_prog Prog() {return prog;} private Xoapi_prog prog = new Xoapi_prog(); + public Xoapi_info Info() {return info;} private Xoapi_info info = new Xoapi_info(); + public Xoapi_prog_log Prog_log() {return prog_log;} private Xoapi_prog_log prog_log = new Xoapi_prog_log(); + public void Nightmode_toggle() { + // toggle nightmode + boolean val = !app.Gui_mgr().Nightmode_mgr().Enabled(); + app.Gui_mgr().Nightmode_mgr().Enabled_(val); + app.Cfg().Set_bool_app(gplx.xowa.guis.views.nightmodes.Xog_nightmode_mgr.Cfg__enabled, val); + this.Nightmode_reload(); + } + public void Nightmode_reload() { + // toggle nightmode for all other tabs + gplx.xowa.guis.views.Xog_tab_mgr tab_mgr = app.Gui_mgr().Browser_win().Tab_mgr(); + int len = tab_mgr.Tabs_len(); + for (int i = 0; i < len; i++) { + app.Gui_mgr().Browser_win().Page__refresh(tab_mgr.Tabs_get_at(i)); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_url)) return url; + else if (ctx.Match(k, Invk_tabs)) return tabs; + else if (ctx.Match(k, Invk_html)) return html; + else if (ctx.Match(k, Invk_search)) return search; + else if (ctx.Match(k, "allpages")) return allpages; + else if (ctx.Match(k, Invk_find)) return find; + else if (ctx.Match(k, Invk_prog)) return prog; + else if (ctx.Match(k, Invk_info)) return info; + else if (ctx.Match(k, Invk_prog_log)) return prog_log; + else if (ctx.Match(k, Invk__nightmode_toggle)) Nightmode_toggle(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk_url = "url", Invk_tabs = "tabs", Invk_html = "html", Invk_search = "search" + , Invk_find = "find", Invk_prog = "prog", Invk_info = "info", Invk_prog_log = "prog_log" + , Invk__nightmode_toggle = "nightmode_toggle" + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/Xoapi_font.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/Xoapi_font.java index a27517de8..59e3c15dd 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/Xoapi_font.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/Xoapi_font.java @@ -13,3 +13,37 @@ 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.apps.apis.xowa.gui; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; +import gplx.xowa.apps.cfgs.*; import gplx.xowa.htmls.*; +public class Xoapi_font implements Gfo_invk { + private Xoae_app app; + public void Init_by_kit(Xoae_app app) { + this.app = app; + } + public void Increase() {Adj(1);} + public void Decrease() {Adj(-1);} + public void Reset() {Set(false, Xoh_page_mgr.Font_size_default, Xocfg_win.Font_size_default);} + public void Adj(int adj) { + float html_font_size = app.Html_mgr().Page_mgr().Font_size() + adj; + float gui_font_size = app.Gui_mgr().Win_cfg().Font().Size() + adj; // (html_font_size * .75f) - 4; // .75f b/c 16px = 12 pt; -4 b/c gui font is currently 4 pt smaller + if (html_font_size < 1 || gui_font_size < 1) return; + Set(true, html_font_size, gui_font_size); + } + private void Set(boolean enabled, float html_font_size, float gui_font_size) { + if (html_font_size <= 0) return; // font must be positive + app.Cfg().Set_bool_app(gplx.xowa.htmls.Xoh_page_mgr.Cfg__font_enabled, enabled); + app.Cfg().Set_float_app(gplx.xowa.htmls.Xoh_page_mgr.Cfg__font_size, html_font_size); + app.Cfg().Set_float_app(gplx.xowa.guis.langs.Xol_font_info.Cfg__font_size, gui_font_size); + app.Gui_mgr().Browser_win().Page__reload(); // NOTE: force reload; needed if viewing home/wiki/Options/HTML, else Font size won't update + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_increase)) this.Increase(); + else if (ctx.Match(k, Invk_decrease)) this.Decrease(); + else if (ctx.Match(k, Invk_reset)) this.Reset(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk_increase = "increase", Invk_decrease = "decrease", Invk_reset = "reset" + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/Xoapi_page.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/Xoapi_page.java index a27517de8..df2e40b1b 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/Xoapi_page.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/Xoapi_page.java @@ -13,3 +13,22 @@ 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.apps.apis.xowa.gui; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; +import gplx.xowa.apps.apis.xowa.gui.pages.*; +public class Xoapi_page implements Gfo_invk { + public void Init_by_kit(Xoae_app app) { + view.Init_by_kit(app); + selection.Init_by_kit(app); + edit.Init_by_kit(app); + } + public Xoapi_view View() {return view;} private Xoapi_view view = new Xoapi_view(); + public Xoapi_edit Edit() {return edit;} private Xoapi_edit edit = new Xoapi_edit(); + public Xoapi_selection Selection() {return selection;} private Xoapi_selection selection = new Xoapi_selection(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_view)) return view; + else if (ctx.Match(k, Invk_selection)) return selection; + else if (ctx.Match(k, Invk_edit)) return edit; + else return Gfo_invk_.Rv_unhandled; + } + private static final String Invk_view = "view", Invk_selection = "selection", Invk_edit = "edit"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_allpages.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_allpages.java index a27517de8..328afccb9 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_allpages.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_allpages.java @@ -13,3 +13,20 @@ 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.apps.apis.xowa.gui.browsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.gui.*; +import gplx.gfui.*; import gplx.gfui.controls.standards.*; import gplx.xowa.guis.views.*; +public class Xoapi_allpages implements Gfo_invk { + public void Init_by_kit(Xoae_app app) {this.app = app;} private Xoae_app app; + private GfuiTextBox Allpages_box() {return app.Gui_mgr().Browser_win().Allpages_box();} + private Xog_win_itm Win() {return app.Gui_mgr().Browser_win();} + public void Focus() {this.Allpages_box().Focus_select_all();} + public void Exec() {this.Win().Page__navigate_by_allpages();} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_focus)) this.Focus(); + else if (ctx.Match(k, Invk_exec)) this.Exec(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_focus = "focus"; + public static final String Invk_exec = "exec"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_find.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_find.java index a27517de8..bb9b7767b 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_find.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_find.java @@ -13,3 +13,78 @@ 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.apps.apis.xowa.gui.browsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.gui.*; +import gplx.gfui.*; import gplx.gfui.controls.standards.*; +import gplx.xowa.wikis.pages.*; import gplx.xowa.guis.*; import gplx.xowa.guis.views.*; +public class Xoapi_find implements Gfo_invk { + private Xog_find_box find_box; + public void Init_by_kit(Xoae_app app) { + find_box = new Xog_find_box(app); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_show)) find_box.Show(); + else if (ctx.Match(k, Invk_show_by_paste)) find_box.Show_by_paste(); + else if (ctx.Match(k, Invk_exec)) find_box.Exec(); + else if (ctx.Match(k, Invk_type)) find_box.Exec(); // NOTE: same as exec; provided so that exec doesn't accidentally overwrite auto-type for find + else if (ctx.Match(k, Invk_find_fwd)) find_box.Exec_by_dir(Bool_.Y); + else if (ctx.Match(k, Invk_find_bwd)) find_box.Exec_by_dir(Bool_.N); + else if (ctx.Match(k, Invk_case_toggle)) find_box.Case_toggle(); + else if (ctx.Match(k, Invk_wrap_toggle)) find_box.Wrap_toggle(); + else if (ctx.Match(k, Invk_hide)) find_box.Hide(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_show = "show", Invk_show_by_paste = "show_by_paste", Invk_hide = "hide", Invk_exec = "exec", Invk_type = "type" + , Invk_find_bwd = "find_bwd", Invk_find_fwd = "find_fwd", Invk_case_toggle = "case_toggle", Invk_wrap_toggle = "wrap_toggle"; +} +class Xog_find_box { + private Xoae_app app; private Xog_win_itm win; private GfuiTextBox find_box; + private String prv_find_text = ""; // NOTE: must set to "", else will fail during Hide + private boolean dir_fwd = true, case_match = false, wrap_search = true; + public Xog_find_box(Xoae_app app) { + this.app = app; + this.win = app.Gui_mgr().Browser_win(); + this.find_box = win.Find_box(); + } + public void Show() {app.Gui_mgr().Layout().Find_show();} + public void Hide() { + app.Gui_mgr().Layout().Find_close(); + Xog_tab_itm tab = win.Tab_mgr().Active_tab(); if (tab == Xog_tab_itm_.Null) return; + if (tab.View_mode() == Xopg_page_.Tid_read) // do not fire find("") for edit / html, else focus issues; DATE:2015-06-10 + Exec_find(prv_find_text, Bool_.N); + } + public void Show_by_paste() { + this.Show(); + if (win.Tab_mgr().Active_tab_is_null()) return; // if no active_tab, just show box; don't try to copy what's on tab; + find_box.Text_(win.Active_html_itm().Html_selected_get_text_or_href()); + } + public void Exec_by_dir(boolean fwd) { + boolean changed = dir_fwd != fwd; + dir_fwd = fwd; + Exec(); + if (changed) // clicking on buttons calls Exec_by_dir; in case of repeated clicks on same button, don't flash changed message again + win.Usr_dlg().Prog_direct("Find direction changed to " + (fwd ? "forward" : "backward")); + } + public void Exec() { + prv_find_text = find_box.Text(); + Exec_find(prv_find_text, Bool_.Y); + } + private void Exec_find(String find, boolean highlight_matches) { + Xog_tab_itm tab = win.Tab_mgr().Active_tab(); if (tab == Xog_tab_itm_.Null) return; + find = String_.Replace(find, "\\", "\\\\"); // NOTE: backslashes are always literal, never escape codes; EX: "C:\new" "\n" means "\n", not (byte)10; DATE:2015-08-17 + boolean find_in_hdoc = tab.View_mode() == Xopg_page_.Tid_read; + if (find_in_hdoc) + tab.Html_box().Html_js_eval_proc_as_str(Xog_js_procs.Win__find_in_hdoc , find, dir_fwd, case_match, wrap_search, highlight_matches); + else + tab.Html_box().Html_js_eval_proc_as_str(Xog_js_procs.Win__find_in_textarea , find, dir_fwd, case_match, wrap_search); + win.Usr_dlg().Prog_direct(""); + } + public void Case_toggle() { + case_match = !case_match; + win.Usr_dlg().Prog_direct("Case match " + (case_match ? "enabled" : "disabled")); + } + public void Wrap_toggle() { + wrap_search = !wrap_search; + win.Usr_dlg().Prog_direct("Wrap search " + (wrap_search ? "enabled" : "disabled")); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_html_box.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_html_box.java index a27517de8..da11ea56c 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_html_box.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_html_box.java @@ -13,3 +13,29 @@ 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.apps.apis.xowa.gui.browsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.gui.*; +import gplx.gfui.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.standards.*; +import gplx.xowa.wikis.pages.*; import gplx.xowa.guis.*; import gplx.xowa.guis.views.*; +public class Xoapi_html_box implements Gfo_invk { + private Xog_win_itm win; + public void Init_by_kit(Xoae_app app) {this.win = app.Gui_mgr().Browser_win();} + public void Focus() { + Xog_tab_itm tab = win.Active_tab(); if (tab == Xog_tab_itm_.Null) return; + Gfui_html html_box = tab.Html_itm().Html_box(); + html_box.Focus(); + if (tab.View_mode() != Xopg_page_.Tid_read) // if edit / html, place focus in edit box + html_box.Html_js_eval_proc_as_str(Xog_js_procs.Doc__elem_focus, Xog_html_itm.Elem_id__xowa_edit_data_box); + } + public void Selection_focus() { + Xog_tab_itm tab = win.Active_tab(); if (tab == Xog_tab_itm_.Null) return; + Gfui_html html_box = tab.Html_itm().Html_box(); + html_box.Html_js_eval_proc_as_str(Xog_js_procs.Selection__toggle_focus_for_anchor); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_focus)) this.Focus(); + else if (ctx.Match(k, Invk_selection_focus_toggle)) this.Selection_focus(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_focus = "focus", Invk_selection_focus_toggle = "selection_focus_toggle"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_info.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_info.java index a27517de8..67cad6acb 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_info.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_info.java @@ -13,3 +13,23 @@ 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.apps.apis.xowa.gui.browsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.gui.*; +import gplx.gfui.*; import gplx.xowa.guis.views.*; +public class Xoapi_info implements Gfo_invk { + public void Init_by_kit(Xoae_app app) {this.app = app;} private Xoae_app app; + private Xog_win_itm Win() {return app.Gui_mgr().Browser_win();} + public void Focus() {this.Win().Info_box().Focus();} + public void Clear() {app.Usr_dlg().Gui_wkr().Clear();} + public void Launch() { + Io_url session_fil = app.Log_wtr().Session_fil(); + app.Prog_mgr().App_view_text().Run(session_fil); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_focus)) this.Focus(); + else if (ctx.Match(k, Invk_clear)) this.Clear(); + else if (ctx.Match(k, Invk_launch)) this.Launch(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_focus = "focus", Invk_clear = "clear", Invk_launch = "launch"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_prog.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_prog.java index a27517de8..d7c22811b 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_prog.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_prog.java @@ -13,3 +13,17 @@ 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.apps.apis.xowa.gui.browsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.gui.*; +import gplx.gfui.*; import gplx.xowa.guis.views.*; +public class Xoapi_prog implements Gfo_invk { + public void Init_by_kit(Xoae_app app) {this.app = app;} private Xoae_app app; + private Xog_win_itm Win() {return app.Gui_mgr().Browser_win();} + public void Focus() {this.Win().Prog_box().Focus();} + + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_focus)) this.Focus(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_focus = "focus"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_prog_log.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_prog_log.java index a27517de8..993490d99 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_prog_log.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_prog_log.java @@ -13,3 +13,15 @@ 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.apps.apis.xowa.gui.browsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.gui.*; +import gplx.gfui.*; import gplx.xowa.guis.views.*; +public class Xoapi_prog_log implements Gfo_invk { + public void Init_by_kit(Xoae_app app) {this.app = app;} private Xoae_app app; + public void Show() {app.Gui_mgr().Show_prog();} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_show)) this.Show(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_show = "show"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_search.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_search.java index a27517de8..555b2088d 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_search.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_search.java @@ -13,3 +13,20 @@ 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.apps.apis.xowa.gui.browsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.gui.*; +import gplx.gfui.*; import gplx.gfui.controls.standards.*; import gplx.xowa.guis.views.*; +public class Xoapi_search implements Gfo_invk { + public void Init_by_kit(Xoae_app app) {this.app = app;} private Xoae_app app; + private GfuiTextBox Search_box() {return app.Gui_mgr().Browser_win().Search_box();} + private Xog_win_itm Win() {return app.Gui_mgr().Browser_win();} + public void Focus() {this.Search_box().Focus_select_all();} + public void Exec() {this.Win().Page__navigate_by_search();} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_focus)) this.Focus(); + else if (ctx.Match(k, Invk_exec)) this.Exec(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_focus = "focus"; + public static final String Invk_exec = "exec"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_tabs.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_tabs.java index a27517de8..1be3d5653 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_tabs.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_tabs.java @@ -13,3 +13,76 @@ 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.apps.apis.xowa.gui.browsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.gui.*; +import gplx.xowa.guis.views.*; +public class Xoapi_tabs implements Gfo_invk { + private Xog_tab_mgr tab_mgr; + public void Init_by_kit(Xoae_app app) { + this.tab_mgr = app.Gui_mgr().Browser_win().Tab_mgr(); + } + private boolean Active_tab_is_null() {return tab_mgr.Active_tab_is_null();} + public void New_dupe(boolean focus) {tab_mgr.Tabs_new_dupe(focus);} + public void New_dflt(boolean focus) {tab_mgr.Tabs_new_dflt(focus);} + public void New_link(boolean focus, String link) {tab_mgr.Tabs_new_link(focus, link);} + public void New_href(boolean focus) { + Xog_tab_itm tab = tab_mgr.Active_tab(); if (tab == Xog_tab_itm_.Null) return; + String link = tab.Html_itm().Html_selected_get_href_or_text(); + if (String_.Len_eq_0(link)) {tab_mgr.Win().Usr_dlg().Prog_many("", "", "no link or text selected"); return;} + tab_mgr.Tabs_new_dflt(true); + tab_mgr.Win().Page__navigate_by_url_bar(link); + } + public void Close_cur() {tab_mgr.Tabs_close_cur();} + public void Select_bwd() {tab_mgr.Tabs_select(Bool_.N);} + public void Select_fwd() {tab_mgr.Tabs_select(Bool_.Y);} + public void Select_by_idx(int v) {tab_mgr.Tabs_select_by_idx(v - List_adp_.Base1);} + public void Move_bwd() {tab_mgr.Tabs_move(Bool_.N);} + public void Move_fwd() {tab_mgr.Tabs_move(Bool_.Y);} + public void Close_others() {tab_mgr.Tabs_close_others();} + public void Close_to_bgn() {tab_mgr.Tabs_close_to_bgn();} + public void Close_to_end() {tab_mgr.Tabs_close_to_end();} + public void Close_undo() {tab_mgr.Tabs_close_undo();} + public void Pin_toggle() {if (this.Active_tab_is_null()) return; tab_mgr.Active_tab().Pin_toggle();} + public void Tab_bookmark_all() {}//app.Gui_mgr().Browser_win().Tab_mgr().Tabs_close_others();} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_new_dflt__at_dflt__focus_y)) this.New_dflt(Bool_.Y); + else if (ctx.Match(k, Invk_new_link__at_dflt__focus_n)) this.New_link(Bool_.N, m.ReadStrOr("v", null)); + else if (ctx.Match(k, Invk_new_link__at_dflt__focus_y)) this.New_link(Bool_.Y, m.ReadStrOr("v", null)); + else if (ctx.Match(k, Invk_new_href__at_dflt__focus_y)) this.New_href(Bool_.Y); + else if (ctx.Match(k, Invk_new_dupe__at_dflt__focus_y)) this.New_dupe(Bool_.Y); + else if (ctx.Match(k, Invk_close_cur)) this.Close_cur(); + else if (ctx.Match(k, Invk_select_bwd)) this.Select_bwd(); + else if (ctx.Match(k, Invk_select_fwd)) this.Select_fwd(); + else if (ctx.Match(k, Invk_move_bwd)) this.Move_bwd(); + else if (ctx.Match(k, Invk_move_fwd)) this.Move_fwd(); + else if (ctx.Match(k, Invk_close_others)) this.Close_others(); + else if (ctx.Match(k, Invk_close_to_bgn)) this.Close_to_bgn(); + else if (ctx.Match(k, Invk_close_to_end)) this.Close_to_end(); + else if (ctx.Match(k, Invk_close_undo)) this.Close_undo(); + else if (ctx.Match(k, Invk_pin_toggle)) this.Pin_toggle(); + else if (ctx.Match(k, Invk_select_by_idx_1)) this.Select_by_idx(1); + else if (ctx.Match(k, Invk_select_by_idx_2)) this.Select_by_idx(2); + else if (ctx.Match(k, Invk_select_by_idx_3)) this.Select_by_idx(3); + else if (ctx.Match(k, Invk_select_by_idx_4)) this.Select_by_idx(4); + else if (ctx.Match(k, Invk_select_by_idx_5)) this.Select_by_idx(5); + else if (ctx.Match(k, Invk_select_by_idx_6)) this.Select_by_idx(6); + else if (ctx.Match(k, Invk_select_by_idx_7)) this.Select_by_idx(7); + else if (ctx.Match(k, Invk_select_by_idx_8)) this.Select_by_idx(8); + else if (ctx.Match(k, Invk_select_by_idx_9)) this.Select_by_idx(9); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk_new_dflt__at_dflt__focus_y = "new_dflt__at_dflt__focus_y" + , Invk_new_link__at_dflt__focus_n = "new_link__at_dflt__focus_n" + , Invk_new_link__at_dflt__focus_y = "new_link__at_dflt__focus_y" + , Invk_new_href__at_dflt__focus_y = "new_href__at_dflt__focus_y" + , Invk_new_dupe__at_dflt__focus_y = "new_dupe__at_dflt__focus_y" + , Invk_close_cur = "close_cur" + , Invk_select_bwd = "select_bwd", Invk_select_fwd = "select_fwd" + , Invk_move_bwd = "move_bwd", Invk_move_fwd = "move_fwd" + , Invk_close_others = "close_others", Invk_close_to_bgn = "close_to_bgn", Invk_close_to_end = "close_to_end", Invk_close_undo = "close_undo" + , Invk_pin_toggle = "pin_toggle" + , Invk_select_by_idx_1 = "select_by_idx_1", Invk_select_by_idx_2 = "select_by_idx_2", Invk_select_by_idx_3 = "select_by_idx_3", Invk_select_by_idx_4 = "select_by_idx_4", Invk_select_by_idx_5 = "select_by_idx_5" + , Invk_select_by_idx_6 = "select_by_idx_6", Invk_select_by_idx_7 = "select_by_idx_7", Invk_select_by_idx_8 = "select_by_idx_8", Invk_select_by_idx_9 = "select_by_idx_9" + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_url.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_url.java index a27517de8..3c9af60e8 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_url.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/browsers/Xoapi_url.java @@ -13,3 +13,71 @@ 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.apps.apis.xowa.gui.browsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.gui.*; +import gplx.gfui.*; import gplx.gfui.envs.*; import gplx.gfui.controls.standards.*; +import gplx.xowa.guis.views.*; import gplx.core.envs.*; +public class Xoapi_url implements Gfo_invk { + private Xoae_app app; + public void Init_by_kit(Xoae_app app) { + this.app = app; + } + private GfuiComboBox Url_box() {return app.Gui_mgr().Browser_win().Url_box();} + public void Focus() { + GfuiComboBox url_box = this.Url_box(); + url_box.Focus(); + url_box.Sel_(0, String_.Len(this.Url_box().Text())); + } + public void Exec() {Exec_wkr(Bool_.N, this.Url_box().Text());} + public void Exec_by_paste() {Exec_wkr(Bool_.N, ClipboardAdp_.GetText());} + public void Exec_new_tab_by_paste() {Exec_wkr(Bool_.Y, ClipboardAdp_.GetText());} + public void Restore() { + Xog_tab_itm tab = app.Gui_mgr().Browser_win().Active_tab(); if (tab == Xog_tab_itm_.Null) return; + this.Url_box().Text_(tab.Page().Url().To_str()); + } + void Type() { + app.Addon_mgr().Itms__search__urlbar().Search(); + } + private void Exec_wkr(boolean new_tab, String urls_text) { + if (Op_sys.Cur().Tid_is_wnt()) + urls_text = String_.Replace(urls_text, Op_sys.Wnt.Nl_str(), Op_sys.Lnx.Nl_str()); + String[] urls = String_.Split(String_.Trim(urls_text), Op_sys.Lnx.Nl_str()); + int urls_len = urls.length; + if (urls_len == 0) return; + if (urls_len == 1) { // 1 url; most cases + String url = urls[0]; + gplx.core.threads.Thread_adp_.Sleep(1); // HACK: sleep 1 ms, else rapid keystrokes may cause last keystroke to not register; EX: typing "w:" may show just "w" instead of "w:"; DATE:2016-03-27 + GfuiComboBox combo = this.Url_box(); + String[] itms_ary = combo.DataSource_as_str_ary(); + if ( itms_ary.length > 0 // results exist... + && combo.List_visible()) { // and dropdown is visible; use selected-item + int sel_idx = combo.List_sel_idx(); // get selected item + if (sel_idx == -1) sel_idx = 0; // if nothing selected, default to 1st; allows typing first few characters and picking 1st item from list + String itms_sel = itms_ary[sel_idx]; + // if (String_.Has(String_.Lower(itms_sel), String_.Lower(url))) // make sure itms_sel contains url; handles slow machines, where dropdown may not be updated yet + url = Xog_win_itm.Remove_redirect_if_exists(itms_sel); + } + combo.Text_(url); + app.Gui_mgr().Browser_win().Page__navigate_by_url_bar(url); + } + else { + for (int i = 0; i < urls_len; i++) { + String url = urls[i]; + if (String_.Has_at_bgn(url, "\"") && String_.Has_at_bgn(url, "\"")) + url = String_.Mid(url, 1, String_.Len(url) - 1); + app.Gui_mgr().Browser_win().Tab_mgr().Tabs_new_link(url, false); + } + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_focus)) this.Focus(); + else if (ctx.Match(k, Invk_exec)) this.Exec(); + else if (ctx.Match(k, Invk_exec_by_paste)) this.Exec_by_paste(); + else if (ctx.Match(k, Invk_exec_new_tab_by_paste)) this.Exec_new_tab_by_paste(); + else if (ctx.Match(k, Invk_restore)) this.Restore(); + else if (ctx.Match(k, Invk_type)) this.Type(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_focus = "focus", Invk_exec_by_paste = "exec_by_paste", Invk_exec_new_tab_by_paste = "exec_new_tab_by_paste", Invk_restore = "restore", Invk_type = "type"; + public static final String Invk_exec = "exec"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/pages/Xoapi_edit.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/pages/Xoapi_edit.java index a27517de8..8fbe6ab8a 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/pages/Xoapi_edit.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/pages/Xoapi_edit.java @@ -13,3 +13,44 @@ 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.apps.apis.xowa.gui.pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.gui.*; +import gplx.gfui.kits.core.*; +import gplx.xowa.guis.*; import gplx.xowa.guis.views.*; import gplx.xowa.wikis.pages.*; +public class Xoapi_edit implements Gfo_invk { + private Xog_win_itm win; + public void Init_by_kit(Xoae_app app) { + win = app.Gui_mgr().Browser_win(); + } + private boolean Active_tab_is_null() {return win.Tab_mgr().Active_tab_is_null();} + private boolean Active_tab_is_edit() {return !win.Tab_mgr().Active_tab_is_null() && win.Tab_mgr().Active_tab().View_mode() == Xopg_page_.Tid_edit;} + public void Copy() {if (Active_tab_is_null()) return; win.Kit().Clipboard().Copy(win.Active_html_itm().Html_selected_get_text_or_href());} + public void Select_all() {if (Active_tab_is_null()) return; Gfo_invk_.Invk_by_key(win.Win_box().Kit().Clipboard(), Gfui_clipboard_.Invk_select_all);} + public void Save() {if (!Active_tab_is_edit()) return; Xog_tab_itm_edit_mgr.Save(win.Active_tab(), false);} + public void Save_draft() {if (!Active_tab_is_edit()) return; Xog_tab_itm_edit_mgr.Save(win.Active_tab(), true);} + public void Preview() {if (!Active_tab_is_edit()) return; Xog_tab_itm_edit_mgr.Preview(win.Active_tab());} + public void Dbg_tmpl() {if (!Active_tab_is_edit()) return; Xog_tab_itm_edit_mgr.Debug(win, Xopg_page_.Tid_edit);} + public void Dbg_html() {if (!Active_tab_is_edit()) return; Xog_tab_itm_edit_mgr.Debug(win, Xopg_page_.Tid_html);} + public void Focus_edit_box(){if (!Active_tab_is_edit()) return; Xog_tab_itm_edit_mgr.Focus(win, Xog_html_itm.Elem_id__xowa_edit_data_box);} + public void Exec() { + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_copy)) this.Copy(); + else if (ctx.Match(k, Invk_select_all)) this.Select_all(); + else if (ctx.Match(k, Invk_save)) this.Save(); + else if (ctx.Match(k, Invk_save_draft)) this.Save_draft(); + else if (ctx.Match(k, Invk_preview)) this.Preview(); + else if (ctx.Match(k, Invk_focus_edit_box)) this.Focus_edit_box(); + else if (ctx.Match(k, Invk_dbg_tmpl)) this.Dbg_tmpl(); + else if (ctx.Match(k, Invk_dbg_html)) this.Dbg_html(); + else if (ctx.Match(k, Invk_exec)) this.Exec(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk_copy = "copy" + , Invk_select_all = "select_all" + , Invk_save = "save", Invk_save_draft = "save_draft", Invk_preview = "preview" + , Invk_focus_edit_box = "focus_edit_box" + , Invk_dbg_tmpl = "dbg_tmpl", Invk_dbg_html = "dbg_html", Invk_exec = "exec" + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/pages/Xoapi_selection.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/pages/Xoapi_selection.java index a27517de8..04155dd07 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/pages/Xoapi_selection.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/pages/Xoapi_selection.java @@ -13,3 +13,38 @@ 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.apps.apis.xowa.gui.pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.gui.*; +import gplx.gfui.*; import gplx.gfui.kits.core.*; import gplx.xowa.guis.*; import gplx.xowa.guis.views.*; import gplx.core.envs.*; +public class Xoapi_selection implements Gfo_invk { + private Xoae_app app; private Xog_win_itm win; + public void Init_by_kit(Xoae_app app) { + this.app = app; + win = app.Gui_mgr().Browser_win(); + } + private boolean Active_tab_is_null() {return win.Tab_mgr().Active_tab_is_null();} + public void Copy() {if (Active_tab_is_null()) return; win.Kit().Clipboard().Copy(win.Active_html_itm().Html_selected_get_text_or_href());} + public void Select_all() {if (Active_tab_is_null()) return; Gfo_invk_.Invk_by_key(win.Win_box().Kit().Clipboard(), Gfui_clipboard_.Invk_select_all);} + public void Save_file_as() { + if (this.Active_tab_is_null()) return; + Xog_html_itm html_itm = win.Tab_mgr().Active_tab().Html_itm(); + String src = html_itm.Html_selected_get_src_or_empty(); + if (String_.Len_eq_0(src)) {app.Usr_dlg().Prog_many("", "", "no file selected: tab=~{0}", html_itm.Owner_tab().Page().Url().To_str()); return;} + Io_url src_url = Io_url_.New__http_or_fail(src); + String trg_name = src_url.NameAndExt(); + if (String_.Has(src, "/thumb/")) trg_name = src_url.OwnerDir().NameOnly(); + String trg = app.Gui_mgr().Kit().New_dlg_file(Gfui_kit_.File_dlg_type_save, "Select file to save to:").Init_file_(trg_name).Ask(); + if (String_.Len_eq_0(trg)) return; + Io_url trg_url = Io_url_.new_fil_(trg); + Io_mgr.Instance.CopyFil(src_url, trg_url, true); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_copy)) this.Copy(); + else if (ctx.Match(k, Invk_select_all)) this.Select_all(); + else if (ctx.Match(k, Invk_save_file_as)) this.Save_file_as(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk_copy = "copy", Invk_select_all = "select_all", Invk_save_file_as = "save_file_as" + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/pages/Xoapi_view.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/pages/Xoapi_view.java index a27517de8..34467251a 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/pages/Xoapi_view.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/gui/pages/Xoapi_view.java @@ -13,3 +13,49 @@ 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.apps.apis.xowa.gui.pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.gui.*; +import gplx.gfui.*; import gplx.gfui.kits.core.*; import gplx.xowa.guis.*; import gplx.xowa.guis.views.*; import gplx.xowa.wikis.pages.*; +import gplx.langs.htmls.encoders.*; +public class Xoapi_view implements Gfo_invk { + private Xoae_app app; private Xog_win_itm win; + private final Gfo_url_encoder fsys_encoder = Gfo_url_encoder_.New__fsys_wnt().Make(); + public void Init_by_kit(Xoae_app app) { + this.app = app; this.win = app.Gui_mgr().Browser_win(); + } + private boolean Active_tab_is_null() {return win.Tab_mgr().Active_tab_is_null();} + public void Mode_read() {Mode(Xopg_page_.Tid_read);} + public void Mode_edit() {Mode(Xopg_page_.Tid_edit);} + public void Mode_html() {Mode(Xopg_page_.Tid_html);} + private void Mode(byte v) {if (Active_tab_is_null()) return; win.Page__mode_(v);} + public void Reload() {if (Active_tab_is_null()) return; win.Page__reload();} + public void Refresh() {if (Active_tab_is_null()) return; win.Page__refresh();} + public void Print() { + if (this.Active_tab_is_null()) return; + win.Active_html_box().Html_js_eval_proc_as_str(Xog_js_procs.Win__print_preview); + } + public void Save_as() { + if (this.Active_tab_is_null()) return; + Xog_tab_itm tab = win.Tab_mgr().Active_tab(); + String file_name = fsys_encoder.Encode_str(String_.new_u8(tab.Page().Ttl().Full_url())) + ".html"; + String file_url = app.Gui_mgr().Kit().New_dlg_file(Gfui_kit_.File_dlg_type_save, "Select file to save to:").Init_file_(file_name).Ask(); + if (String_.Len_eq_0(file_url)) return; + Io_mgr.Instance.SaveFilStr(file_url, tab.Html_box().Text()); + app.Usr_dlg().Prog_many("", "", "saved page: file=~{0}", file_url); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_mode_read)) this.Mode_read(); + else if (ctx.Match(k, Invk_mode_edit)) this.Mode_edit(); + else if (ctx.Match(k, Invk_mode_html)) this.Mode_html(); + else if (ctx.Match(k, Invk_reload)) this.Reload(); + else if (ctx.Match(k, Invk_refresh)) this.Refresh(); + else if (ctx.Match(k, Invk_print)) this.Print(); + else if (ctx.Match(k, Invk_save_as)) this.Save_as(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk_mode_read = "mode_read", Invk_mode_edit = "mode_edit", Invk_mode_html = "mode_html" + , Invk_reload = "reload", Invk_refresh = "refresh" + , Invk_print = "print", Invk_save_as = "save_as" + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/html/Xoapi_modules.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/html/Xoapi_modules.java index a27517de8..b45f064af 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/html/Xoapi_modules.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/html/Xoapi_modules.java @@ -13,3 +13,16 @@ 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.apps.apis.xowa.html; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; +import gplx.xowa.apps.apis.xowa.html.modules.*; +public class Xoapi_modules implements Gfo_invk { + public void Init_by_kit(Xoae_app app) { + popups.Init_by_app(app); + } + public Xoapi_popups Popups() {return popups;} private Xoapi_popups popups = new Xoapi_popups(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_popups)) return popups; + else return Gfo_invk_.Rv_unhandled; + } + private static final String Invk_popups = "popups"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/html/Xoapi_page.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/html/Xoapi_page.java index a27517de8..c77fb5683 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/html/Xoapi_page.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/html/Xoapi_page.java @@ -13,3 +13,16 @@ 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.apps.apis.xowa.html; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; +public class Xoapi_page implements Gfo_invk { + public void Ctor_by_app(Xoae_app app) { + toggle_mgr.Ctor_by_app(app); + } + public void Init_by_app(Xoae_app app) {toggle_mgr.Init_by_app(app);} + public Xoapi_toggle_mgr Toggle_mgr() {return toggle_mgr;} private final Xoapi_toggle_mgr toggle_mgr = new Xoapi_toggle_mgr(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_toggles)) return toggle_mgr; + else return Gfo_invk_.Rv_unhandled; + } + private static final String Invk_toggles = "toggles"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/html/Xoapi_toggle_itm.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/html/Xoapi_toggle_itm.java index a27517de8..5a5dd16a2 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/html/Xoapi_toggle_itm.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/html/Xoapi_toggle_itm.java @@ -13,3 +13,77 @@ 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.apps.apis.xowa.html; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; +import gplx.core.brys.fmtrs.*; +public class Xoapi_toggle_itm { + private final Xoae_app app; // NOTE: needed to get "img_dir" below + private byte[] img_title_val_y, img_title_val_n; + public Xoapi_toggle_itm(Xoae_app app, byte[] key_bry) { + this.app = app; this.key_bry = key_bry; + } + public byte[] Key_bry() {return key_bry;} private final byte[] key_bry; + public byte[] Heading_bry() {return heading_bry;} private byte[] heading_bry; + public byte[] Icon_src() {return icon_src;} private byte[] icon_src = Bry_.Empty; + public byte[] Icon_title() {return icon_title;} private byte[] icon_title = Bry_.Empty; + public byte[] Elem_display() {return elem_display;} private byte[] elem_display = Bry_.Empty; + public byte[] Html_toggle_hdr_cls() {return html_toggle_hdr_cls;} public Xoapi_toggle_itm Html_toggle_hdr_cls_(byte[] v) {html_toggle_hdr_cls = v; return this;} private byte[] html_toggle_hdr_cls = Bry_.Empty; + public boolean Visible() {return visible;} private boolean visible; + public void Visible_(boolean v) { + this.visible = v; + Html_toggle_gen(); + } + public Xoapi_toggle_itm Init(byte[] heading_bry) { + this.heading_bry = heading_bry; // NOTE: sets "Wikis" or "In other languages"; Wikidata twisties are empty; + this.icon_title = app.Usere().Msg_mgr().Val_by_key_obj(visible ? Img_title_msg_y : Img_title_msg_n); // set title ("show" or "hide") + Html_toggle_gen(); + return this; + } + public Xoapi_toggle_itm Init_fsys(Io_url img_dir) { + if (Img_src_y == null) { + Img_src_y = img_dir.GenSubFil("twisty_down.png").To_http_file_bry(); + Img_src_n = img_dir.GenSubFil("twisty_right.png").To_http_file_bry(); + } + return this; + } + public void Init_msgs(byte[] img_title_val_y, byte[] img_title_val_n) { + this.img_title_val_y = img_title_val_y; + this.img_title_val_n = img_title_val_n; + Html_toggle_gen(); + } + public byte[] Html_toggle_btn() {return html_toggle_btn;} private byte[] html_toggle_btn; + public byte[] Html_toggle_hdr() {return html_toggle_hdr;} private byte[] html_toggle_hdr; + private void Assert_img_src() { + if (Img_src_y == null) { + Io_url img_dir = app.Fsys_mgr().Bin_xowa_file_dir().GenSubDir_nest("app.general"); + Img_src_y = img_dir.GenSubFil("twisty_down.png").To_http_file_bry(); + Img_src_n = img_dir.GenSubFil("twisty_right.png").To_http_file_bry(); + } + } + private void Html_toggle_gen() { + Assert_img_src();// NOTE: must call Assert_img_src else wikidata twisties will be missing img; DATE:2015-08-05 + if (visible) { + icon_src = Img_src_y; + icon_title = img_title_val_y; + elem_display = Img_display_y; + } + else { + icon_src = Img_src_n; + icon_title = img_title_val_n; + elem_display = Img_display_n; + } + Bry_fmtr fmtr = Bry_fmtr.new_(); Bry_bfr bfr = Bry_bfr_.New_w_size(8); + html_toggle_btn + = fmtr.Fmt_("~{heading}") + .Keys_("key", "src", "title", "heading").Bld_bry_many(bfr, key_bry, icon_src, icon_title, heading_bry) + ; + html_toggle_hdr + = fmtr.Fmt_(" id='~{key}-toggle-elem' style='~{display}~{toggle_hdr_cls}'") + .Keys_("key", "display", "toggle_hdr_cls").Bld_bry_many(bfr, key_bry, elem_display, html_toggle_hdr_cls) + ; + } + private static byte[] Img_src_y, Img_src_n; // assume these are the same for all itms + private static final byte[] + Img_title_msg_y = Bry_.new_a7("hide"), Img_title_msg_n = Bry_.new_a7("show") + , Img_display_y = Bry_.new_a7("display:;"), Img_display_n = Bry_.new_a7("display:none;") + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/html/Xoapi_toggle_mgr.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/html/Xoapi_toggle_mgr.java index a27517de8..acdfd4643 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/html/Xoapi_toggle_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/html/Xoapi_toggle_mgr.java @@ -13,3 +13,87 @@ 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.apps.apis.xowa.html; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; +import gplx.xowa.apps.cfgs.*; +public class Xoapi_toggle_mgr implements Gfo_invk { + private Xoae_app app; + private final Ordered_hash hash = Ordered_hash_.New_bry(); + public void Ctor_by_app(Xoae_app app) {this.app = app;} + public void Init_by_app(Xoae_app app) { + Io_url img_dir = app.Fsys_mgr().Bin_xowa_file_dir().GenSubDir_nest("app.general"); + int len = hash.Count(); + for (int i = 0; i < len; ++i) { + Xoapi_toggle_itm itm = (Xoapi_toggle_itm)hash.Get_at(i); + itm.Init_fsys(img_dir); + } + + app.Cfg().Bind_many_app(this + , Cfg__toggles__offline_wikis + , Cfg__toggles__wikidata_langs + , Cfg__toggles__claim + , Cfg__toggles__slink_w + , Cfg__toggles__slink_d + , Cfg__toggles__slink_s + , Cfg__toggles__slink_v + , Cfg__toggles__slink_q + , Cfg__toggles__slink_b + , Cfg__toggles__slink_u + , Cfg__toggles__slink_n + , Cfg__toggles__slink_special + , Cfg__toggles__label + , Cfg__toggles__descr + , Cfg__toggles__alias + , Cfg__toggles__json + ); + } + public Xoapi_toggle_itm Get_or_new(String key_str) { + byte[] key_bry = Bry_.new_u8(key_str); + Xoapi_toggle_itm rv = (Xoapi_toggle_itm)hash.Get_by(key_bry); + if (rv == null) { + rv = new Xoapi_toggle_itm(app, key_bry); + hash.Add(key_bry, rv); + } + return rv; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.MatchIn(k + , Cfg__toggles__offline_wikis + , Cfg__toggles__wikidata_langs + , Cfg__toggles__claim + , Cfg__toggles__slink_w + , Cfg__toggles__slink_d + , Cfg__toggles__slink_s + , Cfg__toggles__slink_v + , Cfg__toggles__slink_q + , Cfg__toggles__slink_b + , Cfg__toggles__slink_u + , Cfg__toggles__slink_n + , Cfg__toggles__slink_special + , Cfg__toggles__label + , Cfg__toggles__descr + , Cfg__toggles__alias + , Cfg__toggles__json)) { + this.Get_or_new(String_.Replace(k, "xowa.html.toggles.", "")).Visible_(m.ReadYn("v")); + } + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Cfg__toggles__offline_wikis = "xowa.html.toggles.offline-wikis" + , Cfg__toggles__wikidata_langs = "xowa.html.toggles.wikidata-langs" + , Cfg__toggles__claim = "xowa.html.toggles.wikidatawiki-claim" + , Cfg__toggles__slink_w = "xowa.html.toggles.wikidatawiki-slink-wikipedia" + , Cfg__toggles__slink_d = "xowa.html.toggles.wikidatawiki-slink-wiktionary" + , Cfg__toggles__slink_s = "xowa.html.toggles.wikidatawiki-slink-wikisource" + , Cfg__toggles__slink_v = "xowa.html.toggles.wikidatawiki-slink-wikivoyage" + , Cfg__toggles__slink_q = "xowa.html.toggles.wikidatawiki-slink-wikiquote" + , Cfg__toggles__slink_b = "xowa.html.toggles.wikidatawiki-slink-wikibooks" + , Cfg__toggles__slink_u = "xowa.html.toggles.wikidatawiki-slink-wikiversity" + , Cfg__toggles__slink_n = "xowa.html.toggles.wikidatawiki-slink-wikinews" + , Cfg__toggles__slink_special = "xowa.html.toggles.wikidatawiki-slink-special" + , Cfg__toggles__label = "xowa.html.toggles.wikidatawiki-label" + , Cfg__toggles__descr = "xowa.html.toggles.wikidatawiki-descr" + , Cfg__toggles__alias = "xowa.html.toggles.wikidatawiki-alias" + , Cfg__toggles__json = "xowa.html.toggles.wikidatawiki-json" + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/html/modules/Xoapi_popups.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/html/modules/Xoapi_popups.java index a27517de8..eecb39f0f 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/html/modules/Xoapi_popups.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/html/modules/Xoapi_popups.java @@ -13,3 +13,30 @@ 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.apps.apis.xowa.html.modules; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; import gplx.xowa.apps.apis.xowa.html.*; +import gplx.xowa.htmls.modules.popups.*; +public class Xoapi_popups implements Gfo_invk, Gfo_evt_mgr_owner { + private Xoae_app app; + public Xoapi_popups() { + evt_mgr = new Gfo_evt_mgr(this); + } + public Gfo_evt_mgr Evt_mgr() {return evt_mgr;} private Gfo_evt_mgr evt_mgr; + public void Init_by_app(Xoae_app app) {this.app = app;} + public void Show_more(String popup_id) { + Xowe_wiki wiki = app.Gui_mgr().Browser_win().Active_tab().Wiki(); + wiki.Html_mgr().Head_mgr().Popup_mgr().Show_more(popup_id); + } + public void Show_all(String popup_id) { + Xowe_wiki wiki = app.Gui_mgr().Browser_win().Active_tab().Wiki(); + wiki.Html_mgr().Head_mgr().Popup_mgr().Show_all(popup_id); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_show_more)) Show_more(m.ReadStr("popup_id")); + else if (ctx.Match(k, Invk_show_all)) Show_all (m.ReadStr("popup_id")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk_show_more = "show_more", Invk_show_all = "show_all" + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/navs/Xoapi_nav_wiki.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/navs/Xoapi_nav_wiki.java index a27517de8..804c7653f 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/navs/Xoapi_nav_wiki.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/navs/Xoapi_nav_wiki.java @@ -13,3 +13,27 @@ 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.apps.apis.xowa.navs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; +import gplx.xowa.guis.views.*; +import gplx.xowa.htmls.hrefs.*; +public class Xoapi_nav_wiki implements Gfo_invk { + private Xog_win_itm win; + public void Init_by_kit(Xoae_app app) { + win = app.Gui_mgr().Browser_win(); + } + public void Main_page() { + win.Tab_mgr().Active_tab_assert(); // force an active tab in case all tabs are closed; needed for win.Active_page() below; DATE:2014-09-17 + win.Page__navigate_by_url_bar(win.Active_tab().Wiki().Domain_str() + Xoh_href_.Str__wiki); // NOTE: add "/wiki/" to generate non-page like url; EX: "home" -> "home/wiki/" which will be interpreted as a url, as opposed to "home" which will be intrepretted as page; DATE:2014-04-14 + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, "main_page")) this.Main_page(); + else if (ctx.Match(k, "random")) win.Page__navigate_by_url_bar("Special:Random"); + else if (ctx.Match(k, "sandbox")) win.Page__navigate_by_url_bar("Project:Sandbox"); + else if (ctx.Match(k, "allpages")) win.Page__navigate_by_url_bar("Special:AllPages?from=!"); // NOTE: for main_menu, default to ! else empty page + else if (ctx.Match(k, "search_title")) win.Page__navigate_by_url_bar("Special:Search?fulltext=y"); + else if (ctx.Match(k, "search_full")) win.Page__navigate_by_url_bar("Special:XowaSearch"); + else if (ctx.Match(k, "search_per_cfg")) win.Page__navigate_by_url_bar(win.Gui_mgr().Win_cfg().Search_url()); + else return Gfo_invk_.Rv_unhandled; + return this; + } +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/usrs/Xoapi_bookmarks.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/usrs/Xoapi_bookmarks.java index a27517de8..505196d04 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/usrs/Xoapi_bookmarks.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/usrs/Xoapi_bookmarks.java @@ -13,3 +13,47 @@ 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.apps.apis.xowa.usrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; +import gplx.xowa.guis.history.*; import gplx.xowa.guis.views.*; +import gplx.xowa.users.bmks.*; +import gplx.xowa.wikis.*; +public class Xoapi_bookmarks implements Gfo_invk { + private Xoae_app app; private Xog_win_itm win; + private boolean enabled = true; + public void Ctor_by_app(Xoae_app app) {this.app = app;} + public void Init_by_kit(Xoae_app app) { + this.win = app.Gui_mgr().Browser_win(); + app.Cfg().Bind_many_app(this, Cfg__enabled); + } + public boolean Delete_confirm() {return delete_confirm;} private boolean delete_confirm = true; + public void Show() {win.Page__navigate_by_url_bar("home/wiki/Special:XowaBookmarks");} + public String Add(String url_str) { + if (!enabled) return app.Html__bridge_mgr().Msg_bldr().To_json_str__empty(); + Xoa_url url = null; + if (url_str == null) { + Xog_tab_itm tab = win.Active_tab(); if (tab == Xog_tab_itm_.Null) return app.Html__bridge_mgr().Msg_bldr().Clear().Notify_pass_("bookmark added").To_json_str(); // called by http_server; return success + url = tab.Page().Url(); + } + else + url = app.User().Wikii().Utl__url_parser().Parse(Bry_.new_u8(url_str)); + app.User().User_db_mgr().Bmk_mgr().Itms__add(Xoud_bmk_mgr.Owner_root, url); + String msg = "bookmark added: " + String_.new_u8(url.Page_bry()); + String rv = app.Html__bridge_mgr().Msg_bldr().Clear().Notify_pass_(msg).To_json_str(); + win.Active_tab().Html_box().Html_js_eval_proc_as_str("xowa.cmds.exec_by_str", "xowa.notify", "{\"text\":\"" + msg + "\",\"status\":\"success\"}"); + return rv; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Cfg__enabled)) enabled = m.ReadYn("v"); + else if (ctx.Match(k, Invk_delete_confirm)) return Yn.To_str(delete_confirm); + else if (ctx.Match(k, Invk_delete_confirm_)) delete_confirm = m.ReadYn("v"); + else if (ctx.Match(k, Invk_add)) return this.Add(m.ReadStrOr("v", null)); + else if (ctx.Match(k, Invk_show)) this.Show(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk_delete_confirm = "delete_confirm", Invk_delete_confirm_ = "delete_confirm_" + , Invk_add = "add", Invk_show = "show" + , Cfg__enabled = "xowa.app.bookmarks.enabled" + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/usrs/Xoapi_history.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/usrs/Xoapi_history.java index a27517de8..1fd65370f 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/usrs/Xoapi_history.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/usrs/Xoapi_history.java @@ -13,3 +13,19 @@ 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.apps.apis.xowa.usrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; +import gplx.xowa.guis.views.*; +public class Xoapi_history implements Gfo_invk { + private Xoae_app app; private Xog_win_itm win; + public void Ctor_by_app(Xoae_app app) {this.app = app;} + public void Init_by_kit(Xoae_app app) {this.win = app.Gui_mgr().Browser_win();} + public void Goto_recent() {win.Page__navigate_by_url_bar(app.Usere().History_mgr().Get_at_last());} + public void Show() {win.Page__navigate_by_url_bar("home/wiki/Special:XowaPageHistory");} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_goto_recent)) this.Goto_recent(); + else if (ctx.Match(k, Invk_show)) this.Show(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_goto_recent = "goto_recent", Invk_show = "show"; +} diff --git a/400_xowa/src/gplx/xowa/apps/apis/xowa/xtns/Xoapi_wikibase.java b/400_xowa/src/gplx/xowa/apps/apis/xowa/xtns/Xoapi_wikibase.java index a27517de8..648479470 100644 --- a/400_xowa/src/gplx/xowa/apps/apis/xowa/xtns/Xoapi_wikibase.java +++ b/400_xowa/src/gplx/xowa/apps/apis/xowa/xtns/Xoapi_wikibase.java @@ -13,3 +13,34 @@ 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.apps.apis.xowa.xtns; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.apis.*; import gplx.xowa.apps.apis.xowa.*; +import gplx.xowa.xtns.wbases.*; +public class Xoapi_wikibase implements Gfo_invk, Gfo_evt_mgr_owner { + public Xoapi_wikibase() { + evt_mgr = new Gfo_evt_mgr(this); + } + public Gfo_evt_mgr Evt_mgr() {return evt_mgr;} private Gfo_evt_mgr evt_mgr; + public byte[][] Core_langs() {return core_langs;} private byte[][] core_langs = Bry_.Ary("en"); + public byte[][] Sort_langs() {return sort_langs;} private byte[][] sort_langs = Bry_.Ary("en", "de", "es", "fr", "it", "nl", "pl", "ru", "sv"); + public byte[] Link_wikis() {return link_wikis;} private byte[] link_wikis = Bry_.new_a7("enwiki"); + public void Init_by_app(Xoae_app app) { + app.Cfg().Bind_many_app(this, Cfg__core_langs, Cfg__link_wikis, Cfg__sort_langs); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Cfg__core_langs)) {core_langs = m.ReadBryAry("v", Byte_ascii.Semic); Gfo_evt_mgr_.Pub_val(this, Evt_core_langs_changed, core_langs);} + else if (ctx.Match(k, Cfg__sort_langs)) {sort_langs = m.ReadBryAry("v", Byte_ascii.Semic); Gfo_evt_mgr_.Pub_val(this, Evt_sort_langs_changed, sort_langs);} + else if (ctx.Match(k, Cfg__link_wikis)) {link_wikis = m.ReadBry("v"); Gfo_evt_mgr_.Pub_val(this, Evt_link_wikis_changed, link_wikis);} + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String + Evt_core_langs_changed = "core_langs_changed" + , Evt_sort_langs_changed = "sort_langs_changed" + , Evt_link_wikis_changed = "link_wikis_changed" + ; + private static final String + Cfg__core_langs = "xowa.addon.wikibase.langs.core_langs" + , Cfg__sort_langs = "xowa.addon.wikibase.langs.sort_langs" + , Cfg__link_wikis = "xowa.addon.wikibase.xwikis.link_wikis" + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/boots/Xoa_boot_mgr.java b/400_xowa/src/gplx/xowa/apps/boots/Xoa_boot_mgr.java index a27517de8..29ed4908f 100644 --- a/400_xowa/src/gplx/xowa/apps/boots/Xoa_boot_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/boots/Xoa_boot_mgr.java @@ -13,3 +13,143 @@ 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.apps.boots; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.core.consoles.*; import gplx.core.envs.*; +import gplx.dbs.*; +import gplx.gfui.*; import gplx.gfui.kits.core.*; import gplx.xowa.guis.views.boots.*; +import gplx.xowa.langs.*; +import gplx.xowa.users.*; +public class Xoa_boot_mgr { + private Gfo_usr_dlg usr_dlg; private Gfo_usr_dlg__log log_wtr; + public void Run(String[] args, Xoa_cmd_arg_mgr arg_mgr) { + try { + Init_env(args); + if (arg_mgr.Process(usr_dlg, args, Env_.AppUrl().OwnerDir())) + Run_app(arg_mgr); + } + catch (Exception e) { + String err_str = Err_.Message_gplx_full(e); + log_wtr.Log_to_err(err_str); + Console_adp__sys.Instance.Write_str_w_nl(err_str); + if (log_wtr.Log_dir() == null) log_wtr.Log_dir_(Env_.AppUrl().OwnerDir().GenSubFil("xowa.log")); + log_wtr.Queue_enabled_(false); + } + } + private void Init_env(String[] args) { + // add global loggers + Gfo_usr_dlg_.Instance = usr_dlg = Xoa_app_.New__usr_dlg__console(); + Gfo_log_.Instance__set(new gplx.xowa.apps.shells.Gfo_log__console()); + log_wtr = usr_dlg.Log_wkr(); log_wtr.Log_to_session_fmt("env.init: version=~{0}", Xoa_app_.Version); + + // init env + GfuiEnv_.Init_swt(args, Xoa_app_.class); + Io_url jar_url = Env_.AppUrl(); + Xoa_app_.Build_date = Io_mgr.Instance.QueryFil(jar_url).ModifiedTime().XtoUtc().XtoStr_fmt(Xoa_app_.Build_date_fmt); + log_wtr.Log_to_session_fmt("env.init: jar_url=~{0}; build_date=~{1}", jar_url.NameAndExt(), Xoa_app_.Build_date); + log_wtr.Log_to_session_fmt("env.init: op_sys=~{0}", Op_sys.Cur().To_str()); + } + private void Run_app(Xoa_cmd_arg_mgr arg_mgr) { + boolean app_type_is_gui = false; + Xoae_app app = null; + try { + // pull vars from command-line args + Io_url root_dir = arg_mgr.Fsys__root_dir(); + Xoa_app_.Op_sys_str = arg_mgr.Fsys__bin_dir(); + Xoa_app_.User_agent = String_.Format("XOWA/{0} ({1}) [gnosygnu@gmail.com]", Xoa_app_.Version, Xoa_app_.Op_sys_str); + + // prep splash window + Xoa_app_mode app_type = arg_mgr.App_type(); + app_type_is_gui = app_type.Tid_is_gui(); + Xog_splash_win splash_win = new Xog_splash_win(app_type_is_gui); + + // change default db from mock to sqlite + Db_conn_bldr.Instance.Reg_default_sqlite(); + + // ctor app + app = new Xoae_app(usr_dlg, app_type + , root_dir + , arg_mgr.Fsys__wiki_dir() + , root_dir.GenSubDir("file") + , arg_mgr.Fsys__user_dir() + , root_dir.GenSubDir_nest("user", "anonymous", "wiki") + , Xoa_app_.Op_sys_str); + usr_dlg.Log_wkr().Queue_enabled_(false); // NOTE: needs to be called after new Xoae_app + app.Addon_mgr().Add_dflts_by_app(app).Run_by_app(app); + + try { + app.Sys_cfg().Lang_(System_lang()); + + String launch_url = arg_mgr.Gui__home_page(); + if (launch_url != null) gplx.xowa.guis.views.Xog_startup_tabs.Manual = launch_url; + + // prep tcp-server + Gfo_usr_dlg_.Instance.Log_wkr().Log_to_session_fmt("app.boot:servers"); + app.Tcp_server().Rdr_port_(arg_mgr.Tcp__port_recv()).Wtr_port_(arg_mgr.Tcp__port_send()); + + // prep http-server + gplx.xowa.apps.servers.http.Http_server_mgr server_mgr = app.Http_server(); + server_mgr.Port_(arg_mgr.Http__port(), false); + server_mgr.Home_(Bry_.new_u8(arg_mgr.Http__home_page())); + server_mgr.Wkr_pool().Init(arg_mgr.Http__max_clients(), arg_mgr.Http__max_clients_timeout()); + + // add safelisted Special pages + String special_pages_safelist = arg_mgr.Http__special_pages_safelist(); + if (special_pages_safelist != null) { + byte[][] special_pages = Bry_split_.Split(Bry_.new_u8(special_pages_safelist), Byte_ascii.Pipe); + + // --http_server.special_pages_safelist "" should mean ignore all + if (special_pages.length == 0) { + special_pages = new byte[][] {Bry_.Empty}; + } + for (byte[] special_page : special_pages) { + app.Special_regy().Safelist_pages().Add_as_key_and_val(special_page); + } + } + + Gfo_usr_dlg_.Instance.Log_wkr().Log_to_session_fmt("app.boot:app.init"); + app.Init_by_app(); + } + catch (Exception e) {usr_dlg.Warn_many("", "", "app init failed: ~{0}", Err_.Message_gplx_full(e));} + app.Usr_dlg().Log_wkr_(app.Log_wtr()); // NOTE: log_wtr must be set for cmd-line (else process will fail); + + // run gfs; prefs.gfs and app.gfs + Gfo_usr_dlg_.Instance.Log_wkr().Log_to_session_fmt("app.boot:gfs.run"); + Io_url cmd_file = arg_mgr.Cmd__file(); + try {app.Gfs_mgr().Run_url(cmd_file);} + catch (Exception e) { + usr_dlg.Warn_many("", "", "script file failed: ~{0} ~{1}", cmd_file.Raw(), Err_.Message_gplx_full(e)); + if (app_type_is_gui) + GfuiEnv_.ShowMsg(Err_.Message_gplx_full(e)); + } + + // launch + Gfo_usr_dlg_.Instance.Log_wkr().Log_to_session_fmt("app.boot:app.launch"); + app.Launch(); + if (app_type.Tid_is_tcp()) app.Tcp_server().Run(); + else if (app_type.Tid_is_http()) app.Http_server().Run(); + else { + String cmd_text = arg_mgr.Cmd__text(); + if (cmd_text != null) { + gplx.xowa.apps.servers.Gxw_html_server.Init_gui_for_server(app, null); // NOTE: must init kit else "app.shell.fetch_page" will fail; DATE:2015-04-30 + Console_adp__sys.Instance.Write_str_w_nl_utf8(Object_.Xto_str_strict_or_empty(app.Gfs_mgr().Run_str(cmd_text))); + } + if (app_type_is_gui) + app.Gui_mgr().Run(splash_win); + else // teardown app, else lua will keep process running + gplx.xowa.xtns.scribunto.Scrib_core_mgr.Term_all(app); + } + } + catch (Exception e) {usr_dlg.Warn_many("", "", "app launch failed: ~{0}", Err_.Message_gplx_full(e));} + finally { + if (app != null && app_type_is_gui) // only cancel if app_type_is_gui is true; (force cmd_line to end process) + app.Setup_mgr().Cmd_mgr().Canceled_y_(); + } + } + private static byte[] System_lang() { + String lang_code = System_.Prop__user_language(); + byte[] lang_code_bry = Bry_.new_a7(lang_code); + Xol_lang_stub lang_itm = Xol_lang_stub_.Get_by_key_or_null(lang_code_bry); + return lang_itm == null ? Xol_lang_itm_.Key_en : lang_itm.Key(); + } +} + \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/apps/boots/Xoa_cmd_arg_mgr.java b/400_xowa/src/gplx/xowa/apps/boots/Xoa_cmd_arg_mgr.java index a27517de8..fdbe2712f 100644 --- a/400_xowa/src/gplx/xowa/apps/boots/Xoa_cmd_arg_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/boots/Xoa_cmd_arg_mgr.java @@ -13,3 +13,92 @@ 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.apps.boots; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.core.consoles.*; import gplx.core.envs.*; +import gplx.xowa.apps.*; +public class Xoa_cmd_arg_mgr { + Xoa_cmd_arg_mgr(Gfo_cmd_arg_mgr arg_mgr) {this.arg_mgr = arg_mgr;} + public Gfo_cmd_arg_mgr Arg_mgr() {return arg_mgr;} private final Gfo_cmd_arg_mgr arg_mgr; + public Xoa_app_mode App_type() {return app_type;} private Xoa_app_mode app_type; + public Io_url Fsys__root_dir() {return fsys__root_dir;} private Io_url fsys__root_dir; + public String Fsys__bin_dir() {return fsys__bin_dir;} private String fsys__bin_dir; + public Io_url Fsys__user_dir() {return fsys__user_dir;} private Io_url fsys__user_dir; + public Io_url Fsys__wiki_dir() {return fsys__wiki_dir;} private Io_url fsys__wiki_dir; + public Io_url Cmd__file() {return cmd__file;} private Io_url cmd__file; + public String Cmd__text() {return cmd__text;} private String cmd__text; + public int Tcp__port_recv() {return tcp__port_recv;} private int tcp__port_recv; + public int Tcp__port_send() {return tcp__port_send;} private int tcp__port_send; + public int Http__port() {return http__port;} private int http__port; + public String Http__home_page() {return http__home_page;} private String http__home_page; + public int Http__max_clients() {return http__max_clients;} private int http__max_clients; + public int Http__max_clients_timeout() {return http__max_clients_timeout;} private int http__max_clients_timeout; + public String Http__special_pages_safelist() {return http__special_pages_safelist;} private String http__special_pages_safelist; + public String Gui__home_page() {return gui__home_page;} private String gui__home_page; + public boolean Process(Gfo_usr_dlg usr_dlg, String[] args, Io_url jar_dir) { + arg_mgr.Parse(args); + if (!Print(usr_dlg)) return false; + this.app_type = Xoa_app_mode.parse(arg_mgr.Get_by("app_mode").Val_as_str_or("gui")); + this.fsys__root_dir = arg_mgr.Get_by("root_dir").Val_as_url__rel_dir_or(jar_dir, jar_dir); + this.fsys__user_dir = arg_mgr.Get_by("user_dir").Val_as_url__rel_dir_or(fsys__root_dir.GenSubDir("user"), fsys__root_dir.GenSubDir_nest("user", User_name_default)); + this.fsys__wiki_dir = arg_mgr.Get_by("wiki_dir").Val_as_url__rel_dir_or(fsys__root_dir.GenSubDir("wiki"), fsys__root_dir.GenSubDir("wiki")); + this.cmd__file = arg_mgr.Get_by("cmd_file").Val_as_url__rel_fil_or(jar_dir, fsys__root_dir.GenSubFil_nest("bin", "any", "xowa", "cfg" ,"app", "xowa.gfs")); + this.cmd__text = arg_mgr.Get_by("cmd_text").Val_as_str_or(null); + this.tcp__port_recv = arg_mgr.Get_by("server_port_recv").Val_as_int_or(55000); + this.tcp__port_send = arg_mgr.Get_by("server_port_send").Val_as_int_or(55001); + this.http__port = arg_mgr.Get_by("http_server_port").Val_as_int_or(8080); + this.http__home_page = arg_mgr.Get_by("http_server_home").Val_as_str_or("home/wiki/Main_Page"); + this.http__max_clients = arg_mgr.Get_by("http_server.max_clients").Val_as_int_or(0); + this.http__max_clients_timeout = arg_mgr.Get_by("http_server.max_clients_timeout").Val_as_int_or(50); + this.http__special_pages_safelist = arg_mgr.Get_by("http_server.special_pages_safelist").Val_as_str_or(null); + this.gui__home_page = arg_mgr.Get_by("url").Val_as_str_or(null); + this.fsys__bin_dir = arg_mgr.Get_by("bin_dir_name").Val_as_str_or(Bin_dir_name()); + return true; + } + private boolean Print(Gfo_usr_dlg usr_dlg) { + String header = String_.Concat_lines_nl_skip_last + ( Xoa_cmd_arg_mgr_.GenHdr(false, "XOWA", "XOWA: the XOWA Offline Wiki Application\n", "") + , String_.Repeat("-", 80) + , "" + , "version: " + Xoa_app_.Version + "; build date: " + Xoa_app_.Build_date + ); + Gfo_cmd_arg_mgr_printer printer = new Gfo_cmd_arg_mgr_printer(arg_mgr); + return printer.Print(usr_dlg, header, Xoa_app_.Name, "help", "show_license", "show_args"); + } + public static Xoa_cmd_arg_mgr new_() { + Gfo_cmd_arg_mgr arg_mgr = new Gfo_cmd_arg_mgr().Reg_many + ( Gfo_cmd_arg_itm_.opt_("root_dir").Example_url_("C:\\xowa").Note_("root directory for xowa; defaults to current directory of xowa.jar") + , Gfo_cmd_arg_itm_.opt_("user_dir").Example_url_("C:\\xowa\\user\\" + User_name_default).Note_("directory for user_data; defaults to '/xowa/user/" + User_name_default + "'") + , Gfo_cmd_arg_itm_.opt_("wiki_dir").Example_url_("C:\\xowa\\wiki\\").Note_("directory for wikis; defaults to '/xowa/wiki/'") + , Gfo_cmd_arg_itm_.opt_("bin_dir_name").Example_("windows").Note_("platform-dependent directory name inside /xowa/bin/; valid values are 'linux', 'macosx', 'windows', 'linux_64', 'macosx_64', 'windows_64'; defaults to detected version") + , Gfo_cmd_arg_itm_.opt_("app_mode").Example_("gui").Note_("type of app to run; valid values are 'gui', 'cmd', 'server', 'http_server'; defaults to 'gui'") + , Gfo_cmd_arg_itm_.opt_("cmd_file").Example_url_("C:\\xowa\\bin\\any\\xowa\\cfg\\app\\xowa.gfs").Note_("file_path of script to execute; defaults to 'xowa.gfs'") + , Gfo_cmd_arg_itm_.opt_("cmd_text").Example_("\"app.shell.fetch_page('en.wikipedia.org/wiki/Earth', 'html');\"").Note_("script to run; runs after cmd_file; does nothing if empty; default is empty.\nCurrently a useful cmd is to do 'java -jar xowa_your_platform.jar --app_mode cmd --show_license n --show_args n --cmd_text \"app.shell.fetch_page('en.wikipedia.org/wiki/Earth' 'html');\"'. This will output the page's html to the console. You can also change 'html' to 'wiki' to get the wikitext.") + , Gfo_cmd_arg_itm_.opt_("url").Example_("en.wikipedia.org/wiki/Earth").Note_("url to be shown when xowa first launches; default is home/wiki/Main_Page") + , Gfo_cmd_arg_itm_.opt_("server_port_recv").Example_("55000").Note_("applies to --app_mode server; port where xowa server will receive messages; clients should send messages to this port") + , Gfo_cmd_arg_itm_.opt_("server_port_send").Example_("55001").Note_("applies to --app_mode server; port where xowa server will send messages; clients should listen for messages from this port") + , Gfo_cmd_arg_itm_.opt_("http_server_port").Example_("8080").Note_("applies to --app_mode http_server; port used by http_server; default is 8080") + , Gfo_cmd_arg_itm_.opt_("http_server_home").Example_("home/wiki/Main_Page").Note_("applies to --app_mode http_server; default home page for root address. EX: navigating to localhost:8080 will navigate to localhost:8080/home/wiki/Main_Page. To navigate to a wiki's main page, use the domain name only. EX: --http_server_home en.wikipedia.org") + , Gfo_cmd_arg_itm_.opt_("http_server.max_clients").Example_("15").Note_("applies to --app_mode http_server; limits maximum number of concurrent connections; default is 0 (no limit)") + , Gfo_cmd_arg_itm_.opt_("http_server.max_clients_timeout").Example_("50").Note_("applies to --app_mode http_server; time in milliseconds to wait before checking again to see if a connection is free; default is 50 (wait 50 ms)") + , Gfo_cmd_arg_itm_.opt_("http_server.special_pages_safelist").Example_("Random|XowaSearch|AllPages").Note_("specifies list of permitted special pages; special-page name is case-insensitive and must be separated by pipes (|); default is '' which permits all special pages") + , Gfo_cmd_arg_itm_.sys_("show_license").Dflt_(true) + , Gfo_cmd_arg_itm_.sys_("show_args").Dflt_(true) + , Gfo_cmd_arg_itm_.new_(Gfo_cmd_arg_itm_.Tid_system, "help", Bool_.N, Gfo_cmd_arg_itm_.Val_tid_string) + ); + return new Xoa_cmd_arg_mgr(arg_mgr); + } + private static final String User_name_default = gplx.xowa.users.Xoue_user.Key_xowa_user; + public static String Bin_dir_name() { + String rv = ""; + Op_sys op_sys = Op_sys.Cur(); + switch (op_sys.Tid()) { + case Op_sys.Tid_lnx: rv = "linux"; break; + case Op_sys.Tid_wnt: rv = "windows"; break; + case Op_sys.Tid_osx: rv = "macosx"; break; + case Op_sys.Tid_arm: rv = "arm"; break; + default: throw Err_.new_unhandled("unknown platform " + Op_sys.Cur()); + } + if (op_sys.Bitness() == Op_sys.Bitness_64) rv += "_64"; + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/apps/boots/Xoa_cmd_arg_mgr_.java b/400_xowa/src/gplx/xowa/apps/boots/Xoa_cmd_arg_mgr_.java index a27517de8..44757bed2 100644 --- a/400_xowa/src/gplx/xowa/apps/boots/Xoa_cmd_arg_mgr_.java +++ b/400_xowa/src/gplx/xowa/apps/boots/Xoa_cmd_arg_mgr_.java @@ -13,3 +13,34 @@ 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.apps.boots; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.core.envs.*; +class Xoa_cmd_arg_mgr_ { + public static String GenHdr(boolean forSourceCode, String programName, String hdr_bgn, String hdr_end) { + String newLine = Op_sys.Lnx.Nl_str(); + String lineEnd = Op_sys.Lnx.Nl_str(); + String codeBgn = forSourceCode ? "/*" + newLine : ""; + String codeEnd = forSourceCode ? "*/" + newLine : ""; + String codeHdr = forSourceCode ? "This file is part of {0}." + newLine + newLine : ""; + String fmt = String_.Concat + ( codeBgn + , codeHdr + , hdr_bgn + , "Copyright (c) 2012-2017 gnosygnu@gmail.com", newLine + , newLine + , "XOWA is licensed under the terms of the General Public License (GPL) Version 3,", lineEnd + , "or alternatively under the terms of the Apache License Version 2.0.", lineEnd + , newLine + , "You may use XOWA according to either of these licenses as is most appropriate", lineEnd + , "for your project on a case-by-case basis.", lineEnd + , newLine + , "The terms of each license can be found in the following files:", lineEnd + , newLine + , "GPLv3 License: LICENSE-GPLv3.txt", lineEnd + , "Apache License: LICENSE-APACHE2.txt", lineEnd + , codeEnd + , hdr_end + ); + return String_.Format(fmt, programName); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/caches/Wdata_doc_cache.java b/400_xowa/src/gplx/xowa/apps/caches/Wdata_doc_cache.java index a27517de8..685d8d203 100644 --- a/400_xowa/src/gplx/xowa/apps/caches/Wdata_doc_cache.java +++ b/400_xowa/src/gplx/xowa/apps/caches/Wdata_doc_cache.java @@ -13,3 +13,12 @@ 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.apps.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.xowa.xtns.wbases.*; +public class Wdata_doc_cache { + private Hash_adp_bry hash = Hash_adp_bry.cs(); + public void Add(byte[] qid, Wdata_doc doc) {hash.Add(qid, doc);} + public Wdata_doc Get_or_null(byte[] qid) {return (Wdata_doc)hash.Get_by_bry(qid);} + public void Free_mem_all() {this.Clear();} + public void Clear() {hash.Clear();} +} diff --git a/400_xowa/src/gplx/xowa/apps/caches/Xoa_cache_mgr.java b/400_xowa/src/gplx/xowa/apps/caches/Xoa_cache_mgr.java index a27517de8..e4faa9888 100644 --- a/400_xowa/src/gplx/xowa/apps/caches/Xoa_cache_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/caches/Xoa_cache_mgr.java @@ -13,3 +13,10 @@ 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.apps.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +public class Xoa_cache_mgr { + public Wdata_doc_cache Doc_cache() {return doc_cache;} private Wdata_doc_cache doc_cache = new Wdata_doc_cache(); + public void Free_mem_all() { + doc_cache.Free_mem_all(); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/cfgs/Xocfg_gui_mgr.java b/400_xowa/src/gplx/xowa/apps/cfgs/Xocfg_gui_mgr.java index a27517de8..8bafc6e1b 100644 --- a/400_xowa/src/gplx/xowa/apps/cfgs/Xocfg_gui_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/cfgs/Xocfg_gui_mgr.java @@ -13,3 +13,15 @@ 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.apps.cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +public class Xocfg_gui_mgr implements Gfo_invk { + public void Init_by_app(Xoae_app app) { + win_cfg.Init_by_app(app); + } + public Xocfg_win Win() {return win_cfg;} private Xocfg_win win_cfg = new Xocfg_win(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_win)) return win_cfg; + else return Gfo_invk_.Rv_unhandled; + } + private static final String Invk_win = "win"; +} diff --git a/400_xowa/src/gplx/xowa/apps/cfgs/Xocfg_win.java b/400_xowa/src/gplx/xowa/apps/cfgs/Xocfg_win.java index a27517de8..62a2ae4d5 100644 --- a/400_xowa/src/gplx/xowa/apps/cfgs/Xocfg_win.java +++ b/400_xowa/src/gplx/xowa/apps/cfgs/Xocfg_win.java @@ -13,3 +13,39 @@ 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.apps.cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.core.brys.fmtrs.*; +import gplx.gfui.draws.*; +import gplx.xowa.guis.langs.*; +public class Xocfg_win implements Gfo_invk { + public Xol_font_info Font() {return font;} private Xol_font_info font = new Xol_font_info("Arial", 8, FontStyleAdp_.Plain); + public Bry_fmtr Search_box_fmtr() {return search_box_fmtr;} private Bry_fmtr search_box_fmtr = Bry_fmtr.new_("Special:Search?search=~{search}", "search"); + public Bry_fmtr Allpages_box_fmtr() {return allpages_box_fmtr;} private Bry_fmtr allpages_box_fmtr = Bry_fmtr.new_("Special:AllPages?from=~{search}&namespace=0&hideredirects=0", "search"); + public void Init_by_app(Xoae_app app) { + font.Init_by_app(app); + app.Cfg().Bind_many_app(this, Cfg__search__default_to_fulltext, Cfg__search__fallback_to_title); + } + public String Search_url() {return search_url;} private String search_url = "Special:Search"; + public void Search_url_(boolean default_to_fulltext) { + search_url = default_to_fulltext ? "Special:XowaSearch" : "Special:Search?fulltext=y&search=enter_search_in_url_bar"; + search_box_fmtr = Bry_fmtr.new_(search_url + "?fulltext=y&search=~{search}", "search"); + } + public boolean Search_fallsback_to_title() {return search_fallsback_to_title;} private boolean search_fallsback_to_title; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_font)) return font; + else if (ctx.Match(k, Invk_search_box_fmt_)) search_box_fmtr.Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, "search_url")) return search_url; + else if (ctx.Match(k, Cfg__search__default_to_fulltext)) Search_url_(m.ReadBool("v")); + else if (ctx.Match(k, Cfg__search__fallback_to_title)) search_fallsback_to_title = m.ReadBool("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_search_box_fmt_ = "search_box_fmt_", Invk_font = "font"; + private static final String + Cfg__search__default_to_fulltext = "xowa.addon.fulltext_search.compatibility.default_to_fulltext" + ; + public static final String + Cfg__search__fallback_to_title = "xowa.addon.fulltext_search.compatibility.fallback_to_title"; + + public static final float Font_size_default = 8; +} diff --git a/400_xowa/src/gplx/xowa/apps/cfgs/Xowc_parser.java b/400_xowa/src/gplx/xowa/apps/cfgs/Xowc_parser.java index a27517de8..8f141dca6 100644 --- a/400_xowa/src/gplx/xowa/apps/cfgs/Xowc_parser.java +++ b/400_xowa/src/gplx/xowa/apps/cfgs/Xowc_parser.java @@ -13,3 +13,24 @@ 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.apps.cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.xowa.parsers.lnkis.cfgs.*; +public class Xowc_parser implements Gfo_invk { + public Xowc_parser(Xowe_wiki wiki) { + lnki_cfg = new Xoc_lnki_cfg(wiki); + if (wiki.Domain_tid() == gplx.xowa.wikis.domains.Xow_domain_tid_.Tid__home) + display_title_restrict = false; + } + public Xoc_lnki_cfg Lnki_cfg() {return lnki_cfg;} private Xoc_lnki_cfg lnki_cfg; + public Xowc_xtns Xtns() {return xtns;} private Xowc_xtns xtns = new Xowc_xtns(); + public boolean Display_title_restrict() {return display_title_restrict;} public void Display_title_restrict_(boolean v) {display_title_restrict = v;} private boolean display_title_restrict = true; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_xtns)) return xtns; + else if (ctx.Match(k, Invk_lnki)) return lnki_cfg; + else if (ctx.Match(k, Invk_display_title_restrict)) return display_title_restrict; + else if (ctx.Match(k, Invk_display_title_restrict_)) display_title_restrict = m.ReadYn("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_xtns = "xtns", Invk_lnki = "lnki", Invk_display_title_restrict = "display_title_restrict", Invk_display_title_restrict_ = "display_title_restrict_"; +} diff --git a/400_xowa/src/gplx/xowa/apps/cfgs/Xowc_xtn_pages.java b/400_xowa/src/gplx/xowa/apps/cfgs/Xowc_xtn_pages.java index a27517de8..bb4dda1e3 100644 --- a/400_xowa/src/gplx/xowa/apps/cfgs/Xowc_xtn_pages.java +++ b/400_xowa/src/gplx/xowa/apps/cfgs/Xowc_xtn_pages.java @@ -13,3 +13,70 @@ 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.apps.cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.xowa.wikis.nss.*; +public class Xowc_xtn_pages implements Gfo_invk { + public boolean Init_needed() {return init_needed;} private boolean init_needed = true; + public int Ns_page_id() {return ns_page_id;} private int ns_page_id = Int_.Min_value; + public int Ns_page_talk_id() {return ns_page_talk_id;} private int ns_page_talk_id = Int_.Min_value; + public int Ns_index_id() {return ns_index_id;} private int ns_index_id = Int_.Min_value; + public int Ns_index_talk_id() {return ns_index_talk_id;} private int ns_index_talk_id = Int_.Min_value; + public void Ns_names_(byte[] page_name, byte[] page_talk_name, byte[] index_name, byte[] index_talk_name) { + this.page_name = Xoa_ttl.Replace_spaces(page_name); // ensure underlines, not space; EX:"Mục_lục" not "Mục lục"; PAGE:vi.s:Việt_Nam_sử_lược/Quyển_II DATE:2015-10-27 + this.page_talk_name = Xoa_ttl.Replace_spaces(page_talk_name); + this.index_name = Xoa_ttl.Replace_spaces(index_name); + this.index_talk_name = Xoa_ttl.Replace_spaces(index_talk_name); + } + private byte[] + page_name = Default_ns_page_name + , page_talk_name = Default_ns_page_talk_name + , index_name = Default_ns_index_name + , index_talk_name = Default_ns_index_talk_name; + public void Reset() { + ns_page_id = ns_page_talk_id = ns_index_id = ns_index_talk_id = Int_.Min_value; + init_needed = true; + } + public void Init(Xow_ns_mgr ns_mgr) { + init_needed = false; + int len = ns_mgr.Ords_len(); + for (int i = 0; i < len; i++) { // Page / Index ns_ids are variable per wiki; iterate over ns, and set ns_id + Xow_ns ns = ns_mgr.Ords_get_at(i); if (ns == null) continue; + byte[] ns_name = ns.Name_enc(); + if (Bry_.Eq(ns_name, page_name)) {ns_page_id = ns.Id(); ns_mgr.Ns_page_id_(ns_page_id);} + else if (Bry_.Eq(ns_name, page_talk_name)) ns_page_talk_id = ns.Id(); + else if (Bry_.Eq(ns_name, index_name)) ns_index_id = ns.Id(); + else if (Bry_.Eq(ns_name, index_talk_name)) ns_index_talk_id = ns.Id(); + } + int aliases_added = 0; + aliases_added = Set_canonical(ns_mgr, aliases_added, ns_page_id , Default_ns_page_name); + aliases_added = Set_canonical(ns_mgr, aliases_added, ns_page_talk_id , Default_ns_page_talk_name); + aliases_added = Set_canonical(ns_mgr, aliases_added, ns_index_id , Default_ns_index_name); + aliases_added = Set_canonical(ns_mgr, aliases_added, ns_index_talk_id , Default_ns_index_talk_name); + if (aliases_added > 0) // NOTE: will probably only be 0 for English Wikisource + ns_mgr.Init_w_defaults(); + } + private int Set_canonical(Xow_ns_mgr ns_mgr, int aliases_added, int id, byte[] name) { + Xow_ns ns = ns_mgr.Ids_get_or_null(id); + if ( ns == null // ns doesn't exist; should throw error; + || !Bry_.Eq(ns.Name_db(), name) // ns exists, but name doesn't match canonical + ) { + ns_mgr.Aliases_add(id, String_.new_a7(name)); + ++aliases_added; + } + return aliases_added; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_ns_names_)) Ns_names_(m.ReadBry("page"), m.ReadBry("page_talk"), m.ReadBry("index"), m.ReadBry("index_talk")); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_ns_names_ = "ns_names_"; + public static final byte[] Xtn_key = Bry_.new_a7("pages"); + public static final int Ns_index_id_default = 102, Ns_page_id_default = 104; + + private static final byte[] + Default_ns_page_name = Bry_.new_a7("Page") + , Default_ns_page_talk_name = Bry_.new_a7("Page_talk") + , Default_ns_index_name = Bry_.new_a7("Index") + , Default_ns_index_talk_name = Bry_.new_a7("Index_talk") + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/cfgs/Xowc_xtn_pages_tst.java b/400_xowa/src/gplx/xowa/apps/cfgs/Xowc_xtn_pages_tst.java index a27517de8..ef719d09c 100644 --- a/400_xowa/src/gplx/xowa/apps/cfgs/Xowc_xtn_pages_tst.java +++ b/400_xowa/src/gplx/xowa/apps/cfgs/Xowc_xtn_pages_tst.java @@ -13,3 +13,58 @@ 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.apps.cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import org.junit.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.nss.*; +public class Xowc_xtn_pages_tst { + @Before public void init() {fxt.Clear();} private Xowc_xtn_pages_fxt fxt = new Xowc_xtn_pages_fxt(); + @Test public void Init() { + fxt.Init_ns(200, "Foreign_page").Init_ns(201, "Foreign_page_talk").Init_ns(202, "Foreign_index").Init_ns(203, "Foreign_index_talk"); // ns set by + fxt.Init_names("Foreign_page", "Foreign_page_talk", "Foreign_index", "Foreign_index_talk"); // ns set by .gfs files in /user/wiki/#cfg + fxt.Exec_init(); + fxt.Test_ns_ids(200, 201, 202, 203); + fxt.Test_ns_canonical("Page", "Page_talk", "Index", "Index_talk"); + } + @Test public void Spaces() { // PURPOSE: ensure underlines, not space; EX:"Mục_lục" not "Mục lục"; PAGE:vi.s:Việt_Nam_sử_lược/Quyển_II DATE:2015-10-27 + fxt.Init_ns(200, "Foreign_page").Init_ns(201, "Foreign_page_talk").Init_ns(202, "Foreign_index").Init_ns(203, "Foreign_index_talk"); // ns set by + fxt.Init_names("Foreign page", "Foreign page talk", "Foreign index", "Foreign index talk"); // ns set by .gfs files in /user/wiki/#cfg + fxt.Exec_init(); + fxt.Test_ns_ids(200, 201, 202, 203); + fxt.Test_ns_canonical("Page", "Page_talk", "Index", "Index_talk"); + } +} +class Xowc_xtn_pages_fxt { + private Xow_ns_mgr ns_mgr; + private Xowc_xtn_pages cfg_pages; + public void Clear() { + ns_mgr = Xow_ns_mgr_.default_(gplx.xowa.langs.cases.Xol_case_mgr_.A7()); + cfg_pages = new Xowc_xtn_pages(); + } + public Xowc_xtn_pages_fxt Init_ns(int id, String name) { + ns_mgr.Add_new(id, name); + return this; + } + public void Init_names(String page_name, String page_talk_name, String index_name, String index_talk_name) { + cfg_pages.Ns_names_(Bry_.new_a7(page_name), Bry_.new_a7(page_talk_name), Bry_.new_a7(index_name), Bry_.new_a7(index_talk_name)); + } + public void Exec_init() { + ns_mgr.Init_w_defaults(); // init ns_msg + cfg_pages.Init(ns_mgr); // init cfg + } + public void Test_ns_ids(int page_id, int page_talk_id, int index_id, int index_talk_id) { + Tfds.Eq(page_id , cfg_pages.Ns_page_id()); + Tfds.Eq(page_talk_id , cfg_pages.Ns_page_talk_id()); + Tfds.Eq(index_id , cfg_pages.Ns_index_id()); + Tfds.Eq(index_talk_id , cfg_pages.Ns_index_talk_id()); + } + public void Test_ns_canonical(String page_name, String page_talk_name, String index_name, String index_talk_name) { + Test_ns_canonical_itm(page_name , cfg_pages.Ns_page_id()); + Test_ns_canonical_itm(page_talk_name , cfg_pages.Ns_page_talk_id()); + Test_ns_canonical_itm(index_name , cfg_pages.Ns_index_id()); + Test_ns_canonical_itm(index_talk_name , cfg_pages.Ns_index_talk_id()); + } + private void Test_ns_canonical_itm(String name, int expd_ns_id) { + Xow_ns ns = ns_mgr.Names_get_or_null(Bry_.new_a7(name)); + int actl_ns_id = ns == null ? Int_.Min_value : ns.Id(); + Tfds.Eq(expd_ns_id, actl_ns_id); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/cfgs/Xowc_xtns.java b/400_xowa/src/gplx/xowa/apps/cfgs/Xowc_xtns.java index a27517de8..9aff7d4f3 100644 --- a/400_xowa/src/gplx/xowa/apps/cfgs/Xowc_xtns.java +++ b/400_xowa/src/gplx/xowa/apps/cfgs/Xowc_xtns.java @@ -13,3 +13,14 @@ 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.apps.cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +public class Xowc_xtns implements Gfo_invk { + private Hash_adp_bry hash = Hash_adp_bry.ci_a7(); + public Xowc_xtns() {hash.Add(Xowc_xtn_pages.Xtn_key, itm_pages);} + public Xowc_xtn_pages Itm_pages() {return itm_pages;} private Xowc_xtn_pages itm_pages = new Xowc_xtn_pages(); + public Object Get_by_key(byte[] key) {return hash.Get_by_bry(key);} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_get)) return (Gfo_invk)hash.Get_by_bry(m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + } private static final String Invk_get = "get"; +} diff --git a/400_xowa/src/gplx/xowa/apps/fmtrs/Gfo_sort_able.java b/400_xowa/src/gplx/xowa/apps/fmtrs/Gfo_sort_able.java index a27517de8..48fd4ba73 100644 --- a/400_xowa/src/gplx/xowa/apps/fmtrs/Gfo_sort_able.java +++ b/400_xowa/src/gplx/xowa/apps/fmtrs/Gfo_sort_able.java @@ -13,3 +13,7 @@ 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.apps.fmtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +public interface Gfo_sort_able { + void Sort(gplx.core.lists.ComparerAble comparer); +} diff --git a/400_xowa/src/gplx/xowa/apps/fmtrs/Xoa_fmtr_itm.java b/400_xowa/src/gplx/xowa/apps/fmtrs/Xoa_fmtr_itm.java index a27517de8..1d139d303 100644 --- a/400_xowa/src/gplx/xowa/apps/fmtrs/Xoa_fmtr_itm.java +++ b/400_xowa/src/gplx/xowa/apps/fmtrs/Xoa_fmtr_itm.java @@ -13,3 +13,52 @@ 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.apps.fmtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.core.brys.fmtrs.*; +public class Xoa_fmtr_itm implements Gfo_invk { + public Xoa_fmtr_itm(Xoae_app app) {this.app = app;} private Xoae_app app; + public String Src() {return src;} public Xoa_fmtr_itm Src_(String v) {this.src = v; return this;} private String src; + public byte[] Fmt() {return fmt;} public Xoa_fmtr_itm Fmt_(byte[] v) {this.fmt = v; return this;} private byte[] fmt; + public Object Sorter() { + Gfo_invk src_invk = (Gfo_invk)app.Gfs_mgr().Run_str(src); + return Gfo_invk_.Invk_by_key(src_invk, Invk_sorter); + } + public String Run() { + Gfo_invk src_invk = (Gfo_invk)app.Gfs_mgr().Run_str(src); + int len = Int_.Cast(Gfo_invk_.Invk_by_key(src_invk, Invk_len)); + Bry_bfr bfr = Bry_bfr_.New(); + Bfmtr_eval_invk eval_mgr = new Bfmtr_eval_invk(app); + Bry_fmtr fmtr = Bry_fmtr.new_bry_(fmt).Eval_mgr_(eval_mgr); + for (int i = 0; i < len; i++) { + Gfo_invk itm_invk = (Gfo_invk)Gfo_invk_.Invk_by_val(src_invk, Invk_get_at, i); + eval_mgr.Invk_(itm_invk); + fmtr.Bld_bfr(bfr, Bry_.Ary_empty); + } + return bfr.To_str_and_clear(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_src)) return src; + else if (ctx.Match(k, Invk_src_)) src = m.ReadStr("v"); + else if (ctx.Match(k, Invk_fmt)) return String_.new_u8(fmt); + else if (ctx.Match(k, Invk_fmt_)) fmt = m.ReadBry("v"); + else if (ctx.Match(k, Invk_sorter)) return this.Sorter(); + else if (ctx.Match(k, Invk_run)) return Run(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_src = "src", Invk_src_ = "src_", Invk_fmt = "fmt", Invk_fmt_ = "fmt_" + , Invk_run = "run" + ; + public static final String Invk_get_at = "get_at", Invk_len = "len" + , Invk_sorter = "sorter" + ; +} +class Bfmtr_eval_invk implements Bry_fmtr_eval_mgr { + public Bfmtr_eval_invk(Xoae_app app) {this.app = app;} private Xoae_app app; + public Bfmtr_eval_invk Invk_(Gfo_invk invk) {this.invk = invk; return this;} private Gfo_invk invk; + public boolean Enabled() {return enabled;} public void Enabled_(boolean v) {enabled = v;} private boolean enabled = true; + public byte[] Eval(byte[] cmd) { + Object rslt = app.Gfs_mgr().Run_str_for(invk, String_.new_u8(cmd)); + return Bry_.new_u8(Object_.Xto_str_strict_or_null_mark(rslt)); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/fmtrs/Xoa_fmtr_itm_tst.java b/400_xowa/src/gplx/xowa/apps/fmtrs/Xoa_fmtr_itm_tst.java index a27517de8..cd23a65e1 100644 --- a/400_xowa/src/gplx/xowa/apps/fmtrs/Xoa_fmtr_itm_tst.java +++ b/400_xowa/src/gplx/xowa/apps/fmtrs/Xoa_fmtr_itm_tst.java @@ -13,3 +13,28 @@ 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.apps.fmtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import org.junit.*; import gplx.langs.gfs.*; +public class Xoa_fmtr_itm_tst { + @Before public void init() {fxt.Clear();} private Xoa_fmtr_itm_fxt fxt = new Xoa_fmtr_itm_fxt(); + @Test public void Basic() { + fxt.Init_src("app.wikis;"); + fxt.Init_fmt("domain=~{<>domain;<>};"); + fxt.Test_run("domain=en.wikipedia.org;"); + } +} +class Xoa_fmtr_itm_fxt { + private Xoae_app app; private Xoa_fmtr_itm itm; + public void Clear() { + app = Xoa_app_fxt.Make__app__edit(); + Xoa_app_fxt.Make__wiki__edit(app); // create enwiki + itm = new Xoa_fmtr_itm(app); + GfsCore.Instance.MsgParser_(gplx.langs.gfs.Gfs_msg_bldr.Instance); + } + public Xoa_fmtr_itm_fxt Init_src(String v) {itm.Src_(v); return this;} + public Xoa_fmtr_itm_fxt Init_fmt(String v) {itm.Fmt_(Bry_.new_a7(v)); return this;} + public void Test_run(String expd) { + String actl = itm.Run(); + Tfds.Eq(expd, actl); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/fmtrs/Xoa_fmtr_mgr.java b/400_xowa/src/gplx/xowa/apps/fmtrs/Xoa_fmtr_mgr.java index a27517de8..9d51932de 100644 --- a/400_xowa/src/gplx/xowa/apps/fmtrs/Xoa_fmtr_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/fmtrs/Xoa_fmtr_mgr.java @@ -13,3 +13,12 @@ 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.apps.fmtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.core.ios.*; +public class Xoa_fmtr_mgr implements Gfo_invk { + public Xoa_fmtr_mgr(Xoae_app app) {this.app = app;} private Xoae_app app; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_new_grp)) return new Xoa_fmtr_itm(app); + else return Gfo_invk_.Rv_unhandled; + } private static final String Invk_new_grp = "new_grp"; +} diff --git a/400_xowa/src/gplx/xowa/apps/fmtrs/Xoa_fmtr_sort_mgr.java b/400_xowa/src/gplx/xowa/apps/fmtrs/Xoa_fmtr_sort_mgr.java index a27517de8..ddc7d047b 100644 --- a/400_xowa/src/gplx/xowa/apps/fmtrs/Xoa_fmtr_sort_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/fmtrs/Xoa_fmtr_sort_mgr.java @@ -13,3 +13,52 @@ 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.apps.fmtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +public class Xoa_fmtr_sort_mgr implements Gfo_invk { + private Ordered_hash itms = Ordered_hash_.New(); + private Xoa_fmtr_sort_wkr wkr = new Xoa_fmtr_sort_wkr(); + private Gfo_sort_able sort_able; + public Xoa_fmtr_sort_mgr(Gfo_sort_able sort_able) {this.sort_able = sort_able;} + public void Clear() {itms.Clear();} + public void Add_many(String[] keys) { + int keys_len = keys.length; + for (int i = 0; i < keys_len; i++) { + Xoa_fmtr_sort_itm itm = new Xoa_fmtr_sort_itm(keys[i], true); + itms.Add_if_dupe_use_1st(itm.Key(), itm); + } + } + public void Exec() { + wkr.Itms_((Xoa_fmtr_sort_itm[])itms.To_ary(Xoa_fmtr_sort_itm.class)); + sort_able.Sort(wkr); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_clear)) this.Clear(); + else if (ctx.Match(k, Invk_add_many)) this.Add_many(m.ReadStrAry("k", "|")); + else if (ctx.Match(k, Invk_exec)) this.Exec(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk_clear = "clear", Invk_add_many = "add_many", Invk_exec = "exec"; +} +class Xoa_fmtr_sort_itm { + public Xoa_fmtr_sort_itm(String key, boolean asc) {this.key = key; this.comp_mult = asc ? CompareAble_.More : CompareAble_.Less;} + public int Comp_mult() {return comp_mult;} private int comp_mult; + public String Key() {return key;} private String key; +} +class Xoa_fmtr_sort_wkr implements gplx.core.lists.ComparerAble { + public Xoa_fmtr_sort_itm[] Itms() {return itms;} public void Itms_(Xoa_fmtr_sort_itm[] v) {itms = v; itms_len = v.length;} private Xoa_fmtr_sort_itm[] itms; private int itms_len; + public int compare(Object lhsObj, Object rhsObj) { + Gfo_invk lhs_invk = (Gfo_invk)lhsObj; + Gfo_invk rhs_invk = (Gfo_invk)rhsObj; + for (int i = 0; i < itms_len; i++) { + Xoa_fmtr_sort_itm itm = itms[i]; + String itm_key = itm.Key(); + Object lhs_val = Gfo_invk_.Invk_by_key(lhs_invk, itm_key); + Object rhs_val = Gfo_invk_.Invk_by_key(rhs_invk, itm_key); + int compare = CompareAble_.Compare_obj(lhs_val, rhs_val) * itm.Comp_mult(); + if (compare != CompareAble_.Same) + return compare; + } + return CompareAble_.Same; + } +} diff --git a/400_xowa/src/gplx/xowa/apps/fsys/Xoa_fsys_eval.java b/400_xowa/src/gplx/xowa/apps/fsys/Xoa_fsys_eval.java index a27517de8..914dab661 100644 --- a/400_xowa/src/gplx/xowa/apps/fsys/Xoa_fsys_eval.java +++ b/400_xowa/src/gplx/xowa/apps/fsys/Xoa_fsys_eval.java @@ -13,3 +13,29 @@ 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.apps.fsys; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.core.brys.fmtrs.*; +import gplx.core.primitives.*; import gplx.xowa.users.*; +public class Xoa_fsys_eval implements Bry_fmtr_eval_mgr { + private final Xoa_fsys_mgr app_fsys_mgr; private final Xou_fsys_mgr usr_fsys_mgr; + public Xoa_fsys_eval(Xoa_fsys_mgr app_fsys_mgr, Xou_fsys_mgr usr_fsys_mgr) {this.app_fsys_mgr = app_fsys_mgr; this.usr_fsys_mgr = usr_fsys_mgr;} + public boolean Enabled() {return enabled;} public void Enabled_(boolean v) {enabled = v;} private boolean enabled = true; + public byte[] Eval(byte[] cmd) { + Object o = hash.Get_by_bry(cmd); if (o == null) return null; + byte val = ((Byte_obj_val)o).Val(); + switch (val) { + case Tid__xowa_root_dir: return app_fsys_mgr.Root_dir().RawBry(); + case Tid__bin_plat_dir: return app_fsys_mgr.Bin_plat_dir().RawBry(); + case Tid__user_temp_dir: return usr_fsys_mgr.App_temp_dir().RawBry(); + case Tid__user_cfg_dir: return usr_fsys_mgr.App_data_cfg_dir().RawBry(); + default: throw Err_.new_unhandled(val); + } + } + private static final byte Tid__bin_plat_dir = 0, Tid__user_temp_dir = 1, Tid__xowa_root_dir = 2, Tid__user_cfg_dir = 3; + private static final Hash_adp_bry hash = Hash_adp_bry.ci_a7() + .Add_str_byte("bin_plat_dir" , Tid__bin_plat_dir) + .Add_str_byte("user_temp_dir" , Tid__user_temp_dir) + .Add_str_byte("xowa_root_dir" , Tid__xowa_root_dir) + .Add_str_byte("user_cfg_dir" , Tid__user_cfg_dir) + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/fsys/Xoa_fsys_mgr.java b/400_xowa/src/gplx/xowa/apps/fsys/Xoa_fsys_mgr.java index a27517de8..a6b5ec01d 100644 --- a/400_xowa/src/gplx/xowa/apps/fsys/Xoa_fsys_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/fsys/Xoa_fsys_mgr.java @@ -13,3 +13,52 @@ 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.apps.fsys; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.xowa.apps.gfs.*; +import gplx.xowa.wikis.domains.*; +public class Xoa_fsys_mgr implements Gfo_invk { + public Xoa_fsys_mgr(String plat_name, Io_url root_dir, Io_url wiki_dir, Io_url file_dir, Io_url css_dir, Io_url http_root) { + this.root_dir = root_dir; this.wiki_dir = wiki_dir; this.file_dir = file_dir; this.css_dir = css_dir; this.http_root = http_root; + this.bin_plat_dir = root_dir.GenSubDir("bin").GenSubDir(plat_name); + this.bin_any_dir = root_dir.GenSubDir("bin").GenSubDir("any"); + this.bin_xowa_dir = bin_any_dir.GenSubDir("xowa"); + this.bin_xowa_file_dir = bin_xowa_dir.GenSubDir_nest("file"); + this.bin_xtns_dir = bin_xowa_dir.GenSubDir_nest("xtns"); + this.bin_addon_dir = bin_xowa_dir.GenSubDir_nest("addon"); + this.cfg_app_fil = bin_xowa_dir.GenSubFil_nest("cfg", "app", "xowa.gfs"); + this.cfg_lang_core_dir = bin_xowa_dir.GenSubDir_nest("cfg", "lang", "core"); + this.cfg_wiki_core_dir = bin_xowa_dir.GenSubDir_nest("cfg", "wiki", "core"); + this.cfg_site_meta_fil = bin_xowa_dir.GenSubFil_nest("cfg", "wiki", "site_meta.sqlite3"); + this.home_wiki_dir = bin_xowa_dir.GenSubDir_nest("wiki", Xow_domain_itm_.Str__home); + this.url_finder = new Xoa_url_finder(this); + } + public Io_url Root_dir() {return root_dir;} private final Io_url root_dir; // EX: /xowa/ + public Io_url Wiki_dir() {return wiki_dir;} private final Io_url wiki_dir; // EX: /xowa/wiki/ + public Io_url File_dir() {return file_dir;} private final Io_url file_dir; // EX: /xowa/file/ + public Io_url Css_dir() {return css_dir;} private final Io_url css_dir; // EX: /xowa/user/anonymous/wiki/ + public Io_url Bin_plat_dir() {return bin_plat_dir;} private final Io_url bin_plat_dir; // EX: /xowa/bin/lnx_64/ + public Io_url Bin_any_dir() {return bin_any_dir;} private final Io_url bin_any_dir; // EX: /xowa/bin/any + public Io_url Bin_xowa_dir() {return bin_xowa_dir;} private final Io_url bin_xowa_dir; // EX: /xowa/bin/any/xowa + public Io_url Bin_xowa_file_dir() {return bin_xowa_file_dir;} private final Io_url bin_xowa_file_dir; + public Io_url Bin_xtns_dir() {return bin_xtns_dir;} private final Io_url bin_xtns_dir; + public Io_url Bin_addon_dir() {return bin_addon_dir;} private final Io_url bin_addon_dir; + public Io_url Cfg_lang_core_dir() {return cfg_lang_core_dir;} private final Io_url cfg_lang_core_dir; + public Io_url Cfg_wiki_core_dir() {return cfg_wiki_core_dir;} private final Io_url cfg_wiki_core_dir; + public Io_url Cfg_site_meta_fil() {return cfg_site_meta_fil;} private final Io_url cfg_site_meta_fil; + public Io_url Wiki_css_dir(String wiki) {return css_dir.GenSubDir_nest(wiki, "html");} // EX: /xowa/temp/simple.wikipedia.org/html/xowa_common.css + public Io_url Wiki_file_dir(String wiki) {return file_dir.GenSubDir_nest(wiki);} // EX: /xowa/temp/simple.wikipedia.org/orig/ + public Io_url Home_wiki_dir() {return home_wiki_dir;} private final Io_url home_wiki_dir; + public Io_url Cfg_app_fil() {return cfg_app_fil;} private final Io_url cfg_app_fil; + public Io_url Http_root() {return http_root;} private final Io_url http_root; // EX: file:///xowa/ or file:///android_asset/xowa/ + public Xoa_url_finder Url_finder() {return url_finder;} private final Xoa_url_finder url_finder; + public void Init_by_app(Gfo_invk app_mgr_invk) {this.app_mgr_invk = app_mgr_invk;} private Gfo_invk app_mgr_invk; // for gfs and app.launcher + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_apps)) return app_mgr_invk; + else if (ctx.Match(k, Invk_root_dir)) return root_dir; + else return Gfo_invk_.Rv_unhandled; + } + private static final String Invk_apps = "apps", Invk_root_dir = "root_dir"; + public static Xoa_fsys_mgr New_by_plat(String plat_name, Io_url root_dir) { // TEST: + return new Xoa_fsys_mgr(plat_name, root_dir, root_dir.GenSubDir("wiki"), root_dir.GenSubDir("file"), root_dir.GenSubDir("css"), root_dir.GenSubDir("html")); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/fsys/Xoa_url_finder.java b/400_xowa/src/gplx/xowa/apps/fsys/Xoa_url_finder.java index a27517de8..50f4f9bd0 100644 --- a/400_xowa/src/gplx/xowa/apps/fsys/Xoa_url_finder.java +++ b/400_xowa/src/gplx/xowa/apps/fsys/Xoa_url_finder.java @@ -13,3 +13,35 @@ 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.apps.fsys; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.xowa.users.*; +public class Xoa_url_finder { + private final Xoa_fsys_mgr app_fsys_mgr; + private Xou_fsys_mgr usr_fsys_mgr; + public Xoa_url_finder(Xoa_fsys_mgr app_fsys_mgr) { + this.app_fsys_mgr = app_fsys_mgr; + } + public void Init_by_user(Xou_fsys_mgr usr_fsys_mgr) { + this.usr_fsys_mgr = usr_fsys_mgr; + } + public Io_url Find_by_css_or_app_bin(String wiki, String file, String[] app_dir_parts) {return Find_by_css_or(wiki, file, app_dir_parts, true);} + public Io_url Find_by_css_or(String wiki, String file, String[] app_dir_parts, boolean app_bin_if_missing) { + // check wiki_css dir; EX: /xowa/user/anonymous/wiki/en.wikipedia.org/html/logo.png + Io_url usr_css_fil = usr_fsys_mgr.Wiki_html_dir(wiki).GenSubFil(file); + if (Io_mgr.Instance.ExistsFil(usr_css_fil)) + return usr_css_fil; + + // check usr_bin dir; EX: /xowa/user/app/overrides/bin/any/xowa/html/css/nightmode/logo.png + Io_url usr_bin_fil = usr_fsys_mgr.App_root_dir().GenSubDir("overrides").GenSubDir_nest(app_dir_parts).GenSubFil(file); + if (Io_mgr.Instance.ExistsFil(usr_bin_fil)) + return usr_bin_fil; + + // check app_bin dir; EX: /xowa/bin/any/xowa/html/css/nightmode/logo.png + Io_url app_bin_fil = app_fsys_mgr.Root_dir().GenSubDir_nest(app_dir_parts).GenSubFil(file); + if (Io_mgr.Instance.ExistsFil(app_bin_fil)) + return app_bin_fil; + + // nothing found + return app_bin_if_missing ? app_bin_fil : null; + } +} diff --git a/400_xowa/src/gplx/xowa/apps/fsys/Xoa_url_finder_tst.java b/400_xowa/src/gplx/xowa/apps/fsys/Xoa_url_finder_tst.java index a27517de8..056f5e3b2 100644 --- a/400_xowa/src/gplx/xowa/apps/fsys/Xoa_url_finder_tst.java +++ b/400_xowa/src/gplx/xowa/apps/fsys/Xoa_url_finder_tst.java @@ -13,3 +13,52 @@ 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.apps.fsys; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import org.junit.*; import gplx.core.tests.*; +import gplx.xowa.users.*; +public class Xoa_url_finder_tst { + private final Xoa_url_finder_fxt fxt = new Xoa_url_finder_fxt(); + @Test public void Find_by_css_or_null() { + // init + String wiki = "en.wikipedia.org"; + String file = "logo_night.png"; + String[] subs = String_.Ary("bin", "any", "xowa", "html", "css", "nightmode"); + String expd = null; + + // null case + expd = null; + fxt.Test__Find_by_css_or_null(expd, wiki, file, subs); + + // app_bin + expd = "mem/xowa/bin/any/xowa/html/css/nightmode/logo_night.png"; + fxt.Init__Fsys__save(expd); + fxt.Test__Find_by_css_or_null(expd, wiki, file, subs); + + // usr_bin + expd = "mem/xowa/user/anonymous/app/overrides/bin/any/xowa/html/css/nightmode/logo_night.png"; + fxt.Init__Fsys__save(expd); + fxt.Test__Find_by_css_or_null(expd, wiki, file, subs); + + // wiki_css + expd = "mem/xowa/user/anonymous/wiki/en.wikipedia.org/html/logo_night.png"; + fxt.Init__Fsys__save(expd); + fxt.Test__Find_by_css_or_null(expd, wiki, file, subs); + } +} +class Xoa_url_finder_fxt { + private final Xoa_url_finder finder; + public Xoa_url_finder_fxt() { + Io_url root_dir = Io_url_.mem_dir_("mem/xowa/"); + Xoa_fsys_mgr app_fsys_mgr = Xoa_fsys_mgr.New_by_plat("lnx", root_dir); + Xou_fsys_mgr usr_fsys_mgr = new Xou_fsys_mgr(root_dir.GenSubDir_nest("user", "anonymous")); + this.finder = new Xoa_url_finder(app_fsys_mgr); + finder.Init_by_user(usr_fsys_mgr); + } + public void Init__Fsys__save(String url) { + Io_mgr.Instance.SaveFilStr(url, ""); + } + public void Test__Find_by_css_or_null(String expd, String wiki, String file, String[] dir_parts) { + Io_url actl = finder.Find_by_css_or(wiki, file, dir_parts, false); + Gftest.Eq__str(expd, actl == null ? null : actl.Raw()); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/gfs/Gfs_php_converter.java b/400_xowa/src/gplx/xowa/apps/gfs/Gfs_php_converter.java index a27517de8..ac358e1a1 100644 --- a/400_xowa/src/gplx/xowa/apps/gfs/Gfs_php_converter.java +++ b/400_xowa/src/gplx/xowa/apps/gfs/Gfs_php_converter.java @@ -13,3 +13,132 @@ 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.apps.gfs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.core.brys.fmtrs.*; +import gplx.langs.phps.*; +public class Gfs_php_converter { +public static byte[] Xto_php(Bry_bfr bfr, boolean escape_backslash, byte[] src) { + int len = src.length; + int pos = 0; + boolean dirty = false; + while (pos < len) { + byte b = src[pos]; + switch (b) { + case Byte_ascii.Tilde: + if (!dirty) { + bfr.Add_mid(src, 0, pos); + dirty = true; + } + pos = Xto_php_swap(bfr, src, len, pos + 1); + break; + case Byte_ascii.Backslash: case Byte_ascii.Dollar: + case Byte_ascii.Apos: case Byte_ascii.Quote: + if (escape_backslash) { + if (!dirty) { + bfr.Add_mid(src, 0, pos); + dirty = true; + } + bfr.Add_byte(Byte_ascii.Backslash); + bfr.Add_byte(b); + } + else { + if (dirty) + bfr.Add_byte(b); + } + ++pos; + break; + default: + if (dirty) + bfr.Add_byte(b); + ++pos; + break; + } + } + return dirty ? bfr.To_bry_and_clear() : src; + } + private static int Xto_php_swap(Bry_bfr bfr, byte[] src, int len, int pos) { + if (pos >= len) throw Err_.new_wo_type("invalid gfs: tilde is last char", "src", String_.new_u8(src)); + byte b = src[pos]; + switch (b) { + case Byte_ascii.Tilde: { // ~~ -> ~ + bfr.Add_byte(Byte_ascii.Tilde); + return pos + 1; + } + case Byte_ascii.Curly_bgn: { + int num_bgn = pos + 1; + int num_end = Bry_find_.Find_fwd_while_num(src, num_bgn, len); // +1 to position after { + if ( num_end == Bry_find_.Not_found + || num_end == len + || src[num_end] != Byte_ascii.Curly_end + ) + throw Err_.new_wo_type("invalid gfs; num_end not found", "src", String_.new_u8(src)); + bfr.Add_byte(Byte_ascii.Dollar); + int arg_idx = Bry_.To_int_or(src, num_bgn, num_end, -1); + if (arg_idx == -1) { + throw Err_.new_wo_type("invalid int"); + } + bfr.Add_int_variable(arg_idx + 1); + return num_end + 1; + } + default: { + throw Err_.new_wo_type("invalid gfs; next char after tilde must be either tilde or open brace", "src", String_.new_u8(src)); + } + } + } + public static byte[] To_gfs(Bry_bfr bfr, byte[] raw) { + int raw_len = raw.length; + for (int i = 0; i < raw_len; ++i) { + byte b = raw[i]; + switch (b) { + case Byte_ascii.Backslash: // unescape; EX: '\"' -> '"' + ++i; + if (i < raw_len){ + byte escape_byte = raw[i]; + switch (escape_byte) { // REF: http://php.net/manual/en/language.types.String.php + case Byte_ascii.Ltr_t: escape_byte = Byte_ascii.Tab; break; + case Byte_ascii.Ltr_n: escape_byte = Byte_ascii.Nl; break; + case Byte_ascii.Ltr_r: escape_byte = Byte_ascii.Cr; break; + case Byte_ascii.Ltr_v: escape_byte = 11; break; // 11=vertical tab + case Byte_ascii.Ltr_e: escape_byte = 27; break; // 27=escape + case Byte_ascii.Ltr_f: escape_byte = 12; break; // 12=form fed + case Byte_ascii.Backslash: + case Byte_ascii.Quote: + case Byte_ascii.Apos: + case Byte_ascii.Dollar: break; + // FUTURE: + // //\[0-7]{1,3} the sequence of characters matching the regular expression is a character in octal notation, which silently overflows to fit in a byte (e.g. "\400" === "\000") + // \ x[0-9A-Fa-f]{1,2} the sequence of characters matching the regular expression is a character in hexadecimal notation + // \ u{[0-9A-Fa-f]+} the sequence of characters matching the regular expression is a Unicode codepoint, which will be output to the String as that codepoint's UTF-8 representation (added in PHP 7.0.0) + default: // all else seems to be rendered literally; EX:"You do not need to put \ before a /."; PAGE:en.w:MediaWiki:Spam-whitelist; DATE:2016-09-12 + bfr.Add_byte(Byte_ascii.Backslash); + bfr.Add_byte(escape_byte); + continue; + } + bfr.Add_byte(escape_byte); + } + else // if eos, just output "\"; don't fail; EX: "a\" -> "a\" + bfr.Add_byte(Byte_ascii.Backslash); + break; + case Byte_ascii.Tilde: // double up tilde; EX: '~' -> '~~' + bfr.Add_byte_repeat(Bry_fmtr.char_escape, 2); // escape tilde; EX: ~u -> ~~u; DATE:2013-11-11 + break; + case Byte_ascii.Dollar: // convert php args to gfs args; EX: $1 -> ~{0} + int int_bgn = i + 1; + int int_end = Php_text_itm_parser.Find_fwd_non_int(raw, int_bgn, raw_len); + if (int_bgn == int_end ) // no numbers after $; EX: "$ "; "$a" + bfr.Add_byte(b); + else { + int int_val = Bry_.To_int_or(raw, int_bgn, int_end, -1); + if (int_val == -1) throw Err_.new_wo_type(String_.Format("unknown php dollar sequence: raw=~{0}", raw)); + bfr.Add_byte(Bry_fmtr.char_escape).Add_byte(Bry_fmtr.char_arg_bgn).Add_int_variable(int_val - List_adp_.Base1).Add_byte(Bry_fmtr.char_arg_end); // convert "$1" -> "~{0}" + i = int_end - 1; // -1 b/c Find_fwd_non_int positions after non-int + } + break; + default: + bfr.Add_byte(b); + break; + } + } + return bfr.To_bry_and_clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/gfs/Gfs_php_converter__to_gfs__tst.java b/400_xowa/src/gplx/xowa/apps/gfs/Gfs_php_converter__to_gfs__tst.java index a27517de8..60e7d21d5 100644 --- a/400_xowa/src/gplx/xowa/apps/gfs/Gfs_php_converter__to_gfs__tst.java +++ b/400_xowa/src/gplx/xowa/apps/gfs/Gfs_php_converter__to_gfs__tst.java @@ -13,3 +13,41 @@ 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.apps.gfs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import org.junit.*; +public class Gfs_php_converter__to_gfs__tst { + @Before public void init() {fxt.Clear();} private final Gfs_php_converter_fxt fxt = new Gfs_php_converter_fxt(); + @Test public void Escape_sequences() { + fxt.Test__to_gfs("a\\\"b" , "a\"b"); + fxt.Test__to_gfs("a\\'b" , "a'b"); + fxt.Test__to_gfs("a\\$b" , "a$b"); + fxt.Test__to_gfs("a\\\\b" , "a\\b"); + fxt.Test__to_gfs("a\\nb" , "a\nb"); + fxt.Test__to_gfs("a\\tb" , "a\tb"); + fxt.Test__to_gfs("a\\rb" , "a\rb"); + fxt.Test__to_gfs("a\\ b" , "a\\ b"); // "\ " seems to be rendered literally; EX:"You do not need to put \ before a /."; PAGE:en.w:MediaWiki:Spam-whitelist; DATE:2016-09-12 + fxt.Test__to_gfs("a\\\\b\\'c\\\"d\\$e" , "a\\b'c\"d$e"); // backslash.escape + fxt.Test__to_gfs("\\" , "\\"); // backslash.eos; eos, but nothing to escape; render self but dont fail + } + @Test public void Tilde() { + fxt.Test__to_gfs("a~b" , "a~~b"); // tilde.escape + } + @Test public void Arguments() { + fxt.Test__to_gfs("a$1b" , "a~{0}b"); // dollar + fxt.Test__to_gfs("a $ b" , "a $ b"); // noop + } +} +class Gfs_php_converter_fxt { + private final Bry_bfr bfr = Bry_bfr_.New(); + public void Clear() {} + public void Test__to_gfs(String raw, String expd) { + byte[] actl = Gfs_php_converter.To_gfs(bfr, Bry_.new_u8(raw)); + Tfds.Eq(expd, String_.new_u8(actl)); + } + public void Test_Xto_php_escape_y(String raw, String expd) {Test_Xto_php(raw, Bool_.Y, expd);} + public void Test_Xto_php_escape_n(String raw, String expd) {Test_Xto_php(raw, Bool_.N, expd);} + public void Test_Xto_php(String raw, boolean escape_backslash, String expd) { + byte[] actl = Gfs_php_converter.Xto_php(bfr, escape_backslash, Bry_.new_u8(raw)); + Tfds.Eq(expd, String_.new_u8(actl)); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/gfs/Gfs_php_converter__to_php__tst.java b/400_xowa/src/gplx/xowa/apps/gfs/Gfs_php_converter__to_php__tst.java index a27517de8..89474a359 100644 --- a/400_xowa/src/gplx/xowa/apps/gfs/Gfs_php_converter__to_php__tst.java +++ b/400_xowa/src/gplx/xowa/apps/gfs/Gfs_php_converter__to_php__tst.java @@ -13,3 +13,16 @@ 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.apps.gfs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import org.junit.*; +public class Gfs_php_converter__to_php__tst { + @Before public void init() {fxt.Clear();} private final Gfs_php_converter_fxt fxt = new Gfs_php_converter_fxt(); + @Test public void Xto_php() { + fxt.Test_Xto_php_escape_y("a~{0}b" , "a$1b"); // tilde.arg.one + fxt.Test_Xto_php_escape_y("a~{0}b~{1}c~{2}d" , "a$1b$2c$3d"); // tilde.arg.many + fxt.Test_Xto_php_escape_y("a~{9}" , "a$10"); // tilde.arg.9 -> 10 + fxt.Test_Xto_php_escape_y("a~~b" , "a~b"); // tilde.escape + fxt.Test_Xto_php_escape_y("a\\b'c\"d$e" , "a\\\\b\\'c\\\"d\\$e"); // backslash.escape_y + fxt.Test_Xto_php_escape_n("a\\b'c\"d$e" , "a\\b'c\"d$e"); // backslash.escape_n + } +} diff --git a/400_xowa/src/gplx/xowa/apps/gfs/Xoa_gfs_bldr.java b/400_xowa/src/gplx/xowa/apps/gfs/Xoa_gfs_bldr.java index a27517de8..7bdc39eb0 100644 --- a/400_xowa/src/gplx/xowa/apps/gfs/Xoa_gfs_bldr.java +++ b/400_xowa/src/gplx/xowa/apps/gfs/Xoa_gfs_bldr.java @@ -13,3 +13,75 @@ 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.apps.gfs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +public class Xoa_gfs_bldr { + public Bry_bfr Bfr() {return bfr;} private Bry_bfr bfr = Bry_bfr_.New(); + public byte[] Xto_bry() {return bfr.To_bry_and_clear();} + public Xoa_gfs_bldr Add_byte(byte b) {bfr.Add_byte(b); return this;} + public Xoa_gfs_bldr Add_blob(byte[] bry) {bfr.Add(bry); return this;} + public Xoa_gfs_bldr Add_proc_init_many(String... ary) {return Add_proc_core_many(false, ary);} + public Xoa_gfs_bldr Add_proc_init_one(String itm) {return Add_proc_core_many(false, itm);} + public Xoa_gfs_bldr Add_proc_cont_one(String itm) {return Add_proc_core_many(true, itm);} + public Xoa_gfs_bldr Add_proc_cont_many(String... ary) {return Add_proc_core_many(true, ary);} + Xoa_gfs_bldr Add_proc_core_many(boolean cont, String... ary) { + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) { + if (i != 0 || cont) bfr.Add_byte(Byte_ascii.Dot); + bfr.Add_str_u8(ary[i]); + } + return this; + } + public Xoa_gfs_bldr Add_indent(int i) {bfr.Add_byte_repeat(Byte_ascii.Space, i * 2); return this;} + public Xoa_gfs_bldr Add_parens_str(String v) {return Add_parens_str(Bry_.new_u8(v));} + public Xoa_gfs_bldr Add_parens_str(byte[] v) {return this.Add_paren_bgn().Add_arg_str(v).Add_paren_end();} + public Xoa_gfs_bldr Add_parens_str_many(String... ary) { + this.Add_paren_bgn(); + int len = ary.length; + for (int i = 0; i < len; i++) { + if (i != 0) this.Add_comma(); + this.Add_arg_str(Bry_.new_u8(ary[i])); + } + this.Add_paren_end(); + return this; + } + public Xoa_gfs_bldr Add_arg_int(int v) {bfr.Add_int_variable(v); return this;} + public Xoa_gfs_bldr Add_arg_yn(boolean v) {Add_quote_0(); bfr.Add_byte(v ? Byte_ascii.Ltr_y : Byte_ascii.Ltr_n); Add_quote_0(); return this;} + public Xoa_gfs_bldr Add_arg_str(byte[] v) {bfr.Add_byte(Byte_ascii.Apos); Add_str_escape_apos(bfr, v); bfr.Add_byte(Byte_ascii.Apos); return this;} + public Xoa_gfs_bldr Add_indent() {bfr.Add_byte(Byte_ascii.Space).Add_byte(Byte_ascii.Space); return this;} + public Xoa_gfs_bldr Add_nl() {bfr.Add_byte_nl(); return this;} + public Xoa_gfs_bldr Add_comma() {bfr.Add_byte(Byte_ascii.Comma).Add_byte(Byte_ascii.Space); return this;} + public Xoa_gfs_bldr Add_curly_bgn_nl() {bfr.Add_byte(Byte_ascii.Space).Add_byte(Byte_ascii.Curly_bgn).Add_byte_nl(); return this;} + public Xoa_gfs_bldr Add_curly_end_nl() {bfr.Add_byte(Byte_ascii.Curly_end).Add_byte_nl(); return this;} + public Xoa_gfs_bldr Add_paren_bgn() {bfr.Add_byte(Byte_ascii.Paren_bgn); return this;} + public Xoa_gfs_bldr Add_paren_end() {bfr.Add_byte(Byte_ascii.Paren_end); return this;} + public Xoa_gfs_bldr Add_quote_xtn_bgn() {bfr.Add(Bry_xquote_bgn); return this;} + public Xoa_gfs_bldr Add_quote_xtn_end() {bfr.Add(Bry_xquote_end); return this;} + public Xoa_gfs_bldr Add_quote_xtn_apos_bgn() {bfr.Add_byte(Byte_ascii.Paren_bgn).Add_byte(Byte_ascii.Apos).Add_byte(Byte_ascii.Nl); return this;} + public Xoa_gfs_bldr Add_quote_xtn_apos_end() {bfr.Add_byte(Byte_ascii.Apos).Add_byte(Byte_ascii.Paren_end).Add_byte(Byte_ascii.Semic).Add_byte(Byte_ascii.Nl); return this;} + public Xoa_gfs_bldr Add_quote_0() {bfr.Add_byte(Byte_ascii.Apos); return this;} + public Xoa_gfs_bldr Add_term_nl() {bfr.Add(Bry_semic_nl); return this;} + public Xoa_gfs_bldr Add_eq_str(String k, byte[] v) { + bfr.Add_str_u8(k); + bfr.Add(Bry_eq); + bfr.Add_byte_apos(); + bfr.Add(v); + bfr.Add_byte_apos(); + bfr.Add(Bry_semic_nl); + return this; + } + private static final byte[] Bry_eq = Bry_.new_a7(" = "), Bry_semic_nl = Bry_.new_a7(";\n"); + private void Add_str_escape_apos(Bry_bfr bfr, byte[] src) { + int len = src.length; + for (int i = 0; i < len; i++) { + byte b = src[i]; + if (b == Byte_ascii.Apos) + bfr.Add_byte(Byte_ascii.Apos).Add_byte(Byte_ascii.Apos); + else + bfr.Add_byte(b); + } + } + public static final byte[] + Bry_xquote_bgn = Bry_.new_a7("<:['\n") + , Bry_xquote_end = Bry_.new_a7("']:>\n") + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/gfs/Xoa_gfs_mgr.java b/400_xowa/src/gplx/xowa/apps/gfs/Xoa_gfs_mgr.java index a27517de8..bb6d97f85 100644 --- a/400_xowa/src/gplx/xowa/apps/gfs/Xoa_gfs_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/gfs/Xoa_gfs_mgr.java @@ -13,3 +13,56 @@ 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.apps.gfs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.langs.gfs.*; +import gplx.xowa.users.*; import gplx.xowa.apps.fsys.*; +public class Xoa_gfs_mgr implements Gfo_invk, Gfo_invk_root_wkr { + private final String user_name; + public Xoa_gfs_mgr(String user_name, Gfo_invk root_invk, Xoa_fsys_mgr app_fsys_mgr) { + this.user_name = user_name; + this.root_invk = root_invk; this.app_fsys_mgr = app_fsys_mgr; + GfsCore.Instance.AddCmd(root_invk, Xoae_app.Invk_app); + GfsCore.Instance.AddCmd(root_invk, Xoae_app.Invk_xowa); + } + public Gfo_invk Root_invk() {return root_invk;} private final Gfo_invk root_invk; + public Xoa_fsys_mgr App_fsys_mgr() {return app_fsys_mgr;} private final Xoa_fsys_mgr app_fsys_mgr; + public Xoa_app_eval Eval_mgr() {return eval_mgr;} private final Xoa_app_eval eval_mgr = new Xoa_app_eval(); + public Gfs_wtr Wtr() {return wtr;} private final Gfs_wtr wtr = new Gfs_wtr(); + public void Run_url(Io_url url) { + Run_url_for(GfsCore.Instance.Root(), url); + Gfo_usr_dlg_.Instance.Log_wkr().Log_to_session_fmt("gfs.done: ~{0}", url.Raw()); + } + public void Run_url_for(Gfo_invk invk, Io_url url) { + String raw = Io_mgr.Instance.LoadFilStr_args(url).MissingIgnored_().Exec(); if (String_.Len_eq_0(raw)) return; + Run_str_for(invk, raw); + } + public Object Run_str(String raw) {return Run_str_for(GfsCore.Instance.Root(), raw);} + public Object Run_str_for(Gfo_invk invk, String raw) {return Run_str_for(invk, Xoa_gfs_mgr_.Parse_to_msg(raw));} + public Object Run_str_for(Gfo_invk invk, GfoMsg root_msg) { + try { + Object rv = null; + GfsCtx ctx = GfsCtx.new_().Fail_if_unhandled_(Fail_if_unhandled).Usr_dlg_(Gfo_usr_dlg_.Instance); + int len = root_msg.Subs_count(); + for (int i = 0; i < len; ++i) + rv = GfsCore.Instance.ExecOne_to(ctx, invk, root_msg.Subs_getAt(i)); + return rv; // return rv from last call + } catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "error while executing script: err=~{0}", Err_.Message_gplx_full(e)); + return Gfo_invk_.Rv_error; + } + } + private void Run_url_by_type(String type) { + if (String_.Eq(type, "xowa_cfg_app")) Run_url(app_fsys_mgr.Cfg_app_fil()); + else if (String_.Eq(type, "xowa.user.os")) gplx.xowa.addons.apps.cfgs.mgrs.dflts.Xocfg_dflt_mgr.Run_os_gfs(user_name, this, app_fsys_mgr); + else throw Err_.new_wo_type("invalid gfs type", "type", type); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_run_file_by_type)) Run_url_by_type(m.ReadStr("v")); + else if (ctx.Match(k, Invk_fail_if_unhandled_)) {Fail_if_unhandled = m.ReadYn("v"); ctx.Fail_if_unhandled_(Fail_if_unhandled);} + else if (ctx.Match(k, Invk_txns)) {return Gfo_invk_.Noop;} // FUTURE: handle version for upgrades + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_run_file_by_type = "run_file_by_type", Invk_fail_if_unhandled_ = "fail_if_unhandled_", Invk_txns = "txns"; + public static void Msg_parser_init() {GfsCore.Instance.MsgParser_(gplx.langs.gfs.Gfs_msg_bldr.Instance);} + public static boolean Fail_if_unhandled = false; +} diff --git a/400_xowa/src/gplx/xowa/apps/gfs/Xoa_gfs_mgr_.java b/400_xowa/src/gplx/xowa/apps/gfs/Xoa_gfs_mgr_.java index a27517de8..7b42148ea 100644 --- a/400_xowa/src/gplx/xowa/apps/gfs/Xoa_gfs_mgr_.java +++ b/400_xowa/src/gplx/xowa/apps/gfs/Xoa_gfs_mgr_.java @@ -13,3 +13,16 @@ 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.apps.gfs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.langs.gfs.*; +public class Xoa_gfs_mgr_ { + public static GfoMsg Parse_to_msg(String v) {return Gfs_msg_bldr.Instance.ParseToMsg(v);} + public static void Cfg_os_assert(Io_url orig_url) { + Io_url dflt_url = orig_url.GenNewNameOnly(orig_url.NameOnly() + "_default"); + if (!Io_mgr.Instance.ExistsFil(dflt_url)) return; // no dflt + if (!Io_mgr.Instance.ExistsFil(orig_url)) { + Io_mgr.Instance.CopyFil(dflt_url, orig_url, true); + Gfo_usr_dlg_.Instance.Log_many("", "", "xowa_cfg_os generated; url=~{0}", orig_url.Raw()); + } + } +} diff --git a/400_xowa/src/gplx/xowa/apps/gfs/Xoa_gfs_wtr_.java b/400_xowa/src/gplx/xowa/apps/gfs/Xoa_gfs_wtr_.java index a27517de8..bb627e993 100644 --- a/400_xowa/src/gplx/xowa/apps/gfs/Xoa_gfs_wtr_.java +++ b/400_xowa/src/gplx/xowa/apps/gfs/Xoa_gfs_wtr_.java @@ -13,3 +13,24 @@ 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.apps.gfs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +public class Xoa_gfs_wtr_ { + public static byte[] Escape(String v) {return Escape(Bry_.new_u8(v));} + public static byte[] Escape(byte[] v) { + return Bry_find_.Find_fwd(v, Byte_ascii.Apos) == Bry_find_.Not_found ? v : Bry_.Replace(v, Byte_ascii.Apos_bry, Bry__apos_escaped); + } private static final byte[] Bry__apos_escaped = Bry_.new_a7("''"); + public static void Write_prop(Bry_bfr bfr, byte[] prop, byte[] val) { + bfr.Add(prop).Add(Bry__val_bgn).Add(Xoa_gfs_wtr_.Escape(val)).Add(Bry__val_end); // EX: "a_('b');\n" + } private static final byte[] Bry__val_bgn = Bry_.new_a7("_('"), Bry__val_end = Bry_.new_a7("');\n"); + public static String Write_func_chain(String... ary) { // EX: "a.b.c" + Bry_bfr bfr = Bry_bfr_.New(); + try { + int len = ary.length; + for (int i = 0; i < len; ++i) { + if (i != 0) bfr.Add_byte(Byte_ascii.Dot); + bfr.Add_str_u8(ary[i]); + } + return bfr.To_str_and_clear(); + } finally {bfr.Mkr_rls();} + } +} diff --git a/400_xowa/src/gplx/xowa/apps/metas/Xoa_meta_mgr.java b/400_xowa/src/gplx/xowa/apps/metas/Xoa_meta_mgr.java index a27517de8..3b01d79c7 100644 --- a/400_xowa/src/gplx/xowa/apps/metas/Xoa_meta_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/metas/Xoa_meta_mgr.java @@ -13,3 +13,28 @@ 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.apps.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.bldrs.wms.sites.*; +public class Xoa_meta_mgr { + private final Xoa_app app; + private final Hash_adp_bry ns__hash = Hash_adp_bry.cs(); + private Site_core_db core_db; + public Xoa_meta_mgr(Xoa_app app) { + this.app = app; + } + public void Ns__add(byte[] wiki_domain, Xow_ns_mgr ns_mgr) {ns__hash.Add(wiki_domain, ns_mgr);} // TEST:public + public Xow_ns_mgr Ns__get_or_load(byte[] wiki_domain) { + Xow_ns_mgr rv = (Xow_ns_mgr)ns__hash.Get_by_bry(wiki_domain); + if (rv == null) { + Core_db__assert(); + rv = core_db.Load_namespace(wiki_domain); + Ns__add(wiki_domain, rv); + rv.Init(); // must init to fill Ords + } + return rv; + } + private void Core_db__assert() { + if (core_db == null) core_db = new Site_core_db(app.Fsys_mgr().Cfg_site_meta_fil()); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/miscs/Xoa_misc_mgr.java b/400_xowa/src/gplx/xowa/apps/miscs/Xoa_misc_mgr.java index a27517de8..fdaae7f49 100644 --- a/400_xowa/src/gplx/xowa/apps/miscs/Xoa_misc_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/miscs/Xoa_misc_mgr.java @@ -13,3 +13,33 @@ 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.apps.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +public class Xoa_misc_mgr implements Gfo_invk { + private Xoa_app app; + public void Init_by_app(Xoa_app app) { + this.app = app; + app.Cfg().Bind_many_app(this, Cfg__web_enabled, Cfg__logs_enabled, Cfg__script); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Cfg__web_enabled)) gplx.core.ios.IoEngine_system.Web_access_enabled = m.ReadYn("v"); + else if (ctx.Match(k, Cfg__logs_enabled)) { + if (app.Tid_is_edit()) { + Xoae_app appe = (Xoae_app)app; + boolean logs_enabled = m.ReadYn("v"); + appe.Log_wtr().Enabled_(logs_enabled); + if (!logs_enabled) + Io_mgr.Instance.DeleteFil_args(appe.Log_wtr().Session_fil()).MissingFails_off().Exec(); + } + } + else if (ctx.Match(k, Cfg__script)) { + String script = m.ReadStr("v"); + Object rslt = app.Gfs_mgr().Run_str(script); + if (rslt == Gfo_invk_.Rv_error) { + app.Usr_dlg().Warn_many("", "", "custom script failed: ~{0}", script); + } + } + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Cfg__web_enabled = "xowa.app.web.enabled", Cfg__logs_enabled = "xowa.app.logs.enabled", Cfg__script = "xowa.app.startup.script"; +} diff --git a/400_xowa/src/gplx/xowa/apps/progs/Xoa_prog_mgr.java b/400_xowa/src/gplx/xowa/apps/progs/Xoa_prog_mgr.java index a27517de8..ee93af59f 100644 --- a/400_xowa/src/gplx/xowa/apps/progs/Xoa_prog_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/progs/Xoa_prog_mgr.java @@ -13,3 +13,132 @@ 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.apps.progs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.xowa.apps.fsys.*; import gplx.xowa.files.*; import gplx.core.envs.*; +public class Xoa_prog_mgr implements Gfo_invk { + private Xoa_app app; + private Gfo_usr_dlg usr_dlg; + private Process_adp app_web; + private Process_adp[] apps_by_ext = new Process_adp[Xof_ext_.Id__max]; + public void Init_by_app(Xoa_app app, Xoa_fsys_eval cmd_eval) { + this.app = app; + this.usr_dlg = Xoa_app_.Usr_dlg(); + Process_adp.ini_(this, usr_dlg, app_query_img_size , cmd_eval, Process_adp.Run_mode_sync_timeout , 10 * 60, "~{<>bin_plat_dir<>}imagemagick\\identify", "-ping -format \"<{%w,%h}>\" \"~{file}\"", "file"); + Process_adp.ini_(this, usr_dlg, app_resize_img , cmd_eval, Process_adp.Run_mode_sync_timeout , 10 * 60, "~{<>bin_plat_dir<>}imagemagick\\convert", "\"~{source}\" -coalesce -resize ~{width}x~{height} \"~{target}\"", "source", "target", "width", "height"); + Process_adp.ini_(this, usr_dlg, app_convert_svg_to_png , cmd_eval, Process_adp.Run_mode_sync_timeout , 10 * 60, "~{<>bin_plat_dir<>}inkscape\\inkscape", "-z -w ~{width} -f \"~{source}\" -e \"~{target}\"", "source", "target", "width").Thread_kill_name_("inkscape.exe"); // // -z=without-gui; -w=width; -f=file -e=export-png + Process_adp.ini_(this, usr_dlg, app_convert_tex_to_dvi , cmd_eval, Process_adp.Run_mode_sync_timeout , 2 * 60, "~{<>bin_plat_dir<>}miktex\\miktex\\bin\\latex", "-quiet -output-directory=~{temp_dir} -job-name=xowa_temp ~{tex_file}", "tex_file", "temp_dir"); + Process_adp.ini_(this, usr_dlg, app_convert_dvi_to_png , cmd_eval, Process_adp.Run_mode_sync_timeout , 2 * 60, "~{<>bin_plat_dir<>}miktex\\miktex\\bin\\dvipng", "~{dvi_file} -o ~{png_file} -q* -T tight -bg Transparent", "dvi_file", "png_file", "temp_dir"); + Process_adp.ini_(this, usr_dlg, app_convert_djvu_to_tiff , cmd_eval, Process_adp.Run_mode_sync_timeout , 1 * 60, "~{<>bin_plat_dir<>}djvulibre\\ddjvu", "-format=tiff -page=1 \"~{source}\" \"~{target}\"", "source", "target"); + Process_adp.ini_(this, usr_dlg, app_decompress_bz2 , cmd_eval, Process_adp.Run_mode_sync_timeout , 0 , "~{<>bin_plat_dir<>}7-zip\\7za", "x -y \"~{src}\" -o\"~{trg_dir}\"", "src", "trg", "trg_dir"); // x=extract; -y=yes on all queries; -o=output_dir + Process_adp.ini_(this, usr_dlg, app_decompress_bz2_by_stdout, cmd_eval, Process_adp.Run_mode_sync_timeout , 0 , "~{<>bin_plat_dir<>}7-zip\\7za", "x -so \"~{src}\"", "src"); // x=extract; -so=stdout + Process_adp.ini_(this, usr_dlg, app_decompress_zip , cmd_eval, Process_adp.Run_mode_sync_timeout , 0 , "~{<>bin_plat_dir<>}7-zip\\7za", "x -y \"~{src}\" -o\"~{trg_dir}\"", "src", "trg", "trg_dir"); // x=extract; -y=yes on all queries; -o=output_dir + Process_adp.ini_(this, usr_dlg, app_decompress_gz , cmd_eval, Process_adp.Run_mode_sync_timeout , 0 , "~{<>bin_plat_dir<>}7-zip\\7za", "x -y \"~{src}\" -o\"~{trg_dir}\"", "src", "trg", "trg_dir"); // x=extract; -y=yes on all queries; -o=output_dir + Process_adp.ini_(this, usr_dlg, app_lua , cmd_eval, Process_adp.Run_mode_async , 0 , "~{<>bin_plat_dir<>}lua\\lua", "", ""); + Process_adp.ini_(this, usr_dlg, app_lilypond , cmd_eval, Process_adp.Run_mode_sync_timeout , 1 * 60, "~{<>bin_plat_dir<>}lilypond\\usr\\bin\\lilypond.exe", "\"-dsafe=#t\" -dbackend=ps --png --header=texidoc -dmidi-extension=midi \"~{file}\"", "file"); + Process_adp.ini_(this, usr_dlg, app_abc2ly , cmd_eval, Process_adp.Run_mode_sync_timeout , 1 * 60, "~{<>bin_plat_dir<>}lilypond\\usr\\bin\\python.exe", "abc2ly.py -s \"--output=~{target}\" \"~{source}\"", "source", "target"); + Process_adp.ini_(this, usr_dlg, app_trim_img , cmd_eval, Process_adp.Run_mode_sync_timeout , 1 * 60, "~{<>bin_plat_dir<>}imagemagick\\convert", "-trim \"~{source}\" \"~{target}\"", "source", "target"); + Process_adp.ini_(this, usr_dlg, app_midi_to_ogg , cmd_eval, Process_adp.Run_mode_sync_timeout , 1 * 60, "~{<>bin_plat_dir<>}timidity\\timidity", "-Ov \"--output-file=~{target}\" \"~{source}\"", "source", "target"); + Process_adp.ini_(this, usr_dlg, app_view_text , cmd_eval, Process_adp.Run_mode_async , 0 , "cmd", "/c start \"~{url}\"", "url"); + + for (int i = 0; i < apps_by_ext.length; i++) { + apps_by_ext[i] = Process_adp.New(usr_dlg, cmd_eval, Process_adp.Run_mode_async, 0, "cmd", "/c start \"\" \"~{file}\"", "file"); + } + app_web = Process_adp.New(usr_dlg, cmd_eval, Process_adp.Run_mode_async, 0, "cmd", "/c start \"\" \"~{url}\"", "url"); + app.Cfg().Bind_many_app(this + , Cfg__web, Cfg__media, Cfg__image, Cfg__svg, Cfg__pdf, Cfg__djvu + , Cfg__gz, Cfg__bz2, Cfg__bz2__stdout_cmd + , Cfg__query_size, Cfg__resize_img, Cfg__convert_svg_to_png, Cfg__convert_djvu_to_tiff + , Cfg__convert_tex_to_dvi, Cfg__convert_dvi_to_png + , Cfg__lua + , Cfg__lilypond, Cfg__abc2ly, Cfg__trim_img, Cfg__midi_to_ogg + ); + } + private Process_adp App_by_ext_key(String ext) {return apps_by_ext[Xof_ext_.Get_id_by_ext_(Bry_.new_a7(ext))];} + public Process_adp App_query_img_size() {return app_query_img_size;} private Process_adp app_query_img_size = new Process_adp(); + public Process_adp App_resize_img() {return app_resize_img;} private Process_adp app_resize_img = new Process_adp(); + public Process_adp App_convert_svg_to_png() {return app_convert_svg_to_png;} private Process_adp app_convert_svg_to_png = new Process_adp(); + public Process_adp App__tex_to_dvi() {return app_convert_tex_to_dvi;} private Process_adp app_convert_tex_to_dvi = new Process_adp(); + public Process_adp App__dvi_to_png() {return app_convert_dvi_to_png;} private Process_adp app_convert_dvi_to_png = new Process_adp(); + public Process_adp App_convert_djvu_to_tiff() {return app_convert_djvu_to_tiff;} private Process_adp app_convert_djvu_to_tiff = new Process_adp(); + public Process_adp App_view_text() {return app_view_text;} private Process_adp app_view_text = new Process_adp(); + public Process_adp App_decompress_bz2() {return app_decompress_bz2;} private Process_adp app_decompress_bz2 = new Process_adp(); + public Process_adp App_decompress_zip() {return app_decompress_zip;} private Process_adp app_decompress_zip = new Process_adp(); + public Process_adp App_decompress_gz() {return app_decompress_gz;} private Process_adp app_decompress_gz = new Process_adp(); + public Process_adp App_decompress_bz2_by_stdout() {return app_decompress_bz2_by_stdout;} private Process_adp app_decompress_bz2_by_stdout = new Process_adp(); + public Process_adp App_lua() {return app_lua;} private Process_adp app_lua = new Process_adp(); + public Process_adp App_lilypond() {return app_lilypond;} private Process_adp app_lilypond = new Process_adp(); + public Process_adp App_abc2ly() {return app_abc2ly;} private Process_adp app_abc2ly = new Process_adp(); + public Process_adp App_trim_img() {return app_trim_img;} private Process_adp app_trim_img = new Process_adp(); + public Process_adp App_convert_midi_to_ogg() {return app_midi_to_ogg;} private Process_adp app_midi_to_ogg = new Process_adp(); + public Process_adp App_by_ext(String ext) {return App_by_ext_key(String_.Mid(ext, 1));} // ignore 1st . in ext; EX: ".png" -> "png" + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_convert_tex_to_dvi)) return app_convert_tex_to_dvi; + else if (ctx.Match(k, Invk_convert_dvi_to_png)) return app_convert_dvi_to_png; + + else if (String_.Eq(k, Cfg__web)) {Init_cmd(m.ReadStr("v"), app_web);} + else if (String_.Eq(k, Cfg__media)) {Init_cmd(m.ReadStr("v"), Xof_ext_.Id_ogv, Xof_ext_.Id_webm, Xof_ext_.Id_flac, Xof_ext_.Id_ogg, Xof_ext_.Id_oga, Xof_ext_.Id_mid, Xof_ext_.Id_wav);} + else if (String_.Eq(k, Cfg__image)) {Init_cmd(m.ReadStr("v"), Xof_ext_.Id_png, Xof_ext_.Id_jpg, Xof_ext_.Id_jpeg, Xof_ext_.Id_gif, Xof_ext_.Id_tif, Xof_ext_.Id_tiff, Xof_ext_.Id_bmp);} + else if (String_.Eq(k, Cfg__svg)) {Init_cmd(m.ReadStr("v"), Xof_ext_.Id_svg);} + else if (String_.Eq(k, Cfg__pdf)) {Init_cmd(m.ReadStr("v"), Xof_ext_.Id_pdf);} + else if (String_.Eq(k, Cfg__djvu)) {Init_cmd(m.ReadStr("v"), Xof_ext_.Id_djvu);} + else if (String_.Eq(k, Cfg__gz)) {Init_cmd(m.ReadStr("v"), app_decompress_gz);} + else if (String_.Eq(k, Cfg__bz2)) {Init_cmd(m.ReadStr("v"), app_decompress_bz2);} + else if (String_.Eq(k, Cfg__bz2__stdout_cmd)) {Init_cmd(m.ReadStr("v"), app_decompress_bz2_by_stdout);} + else if (String_.Eq(k, Cfg__query_size)) {Init_cmd(m.ReadStr("v"), app_query_img_size);} + else if (String_.Eq(k, Cfg__resize_img)) {Init_cmd(m.ReadStr("v"), app_resize_img);} + else if (String_.Eq(k, Cfg__convert_svg_to_png)) {Init_cmd(m.ReadStr("v"), app_convert_svg_to_png);} + else if (String_.Eq(k, Cfg__convert_djvu_to_tiff)) {Init_cmd(m.ReadStr("v"), app_convert_djvu_to_tiff);} + else if (String_.Eq(k, Cfg__convert_tex_to_dvi)) {Init_cmd(m.ReadStr("v"), app_convert_tex_to_dvi);} + else if (String_.Eq(k, Cfg__convert_dvi_to_png)) {Init_cmd(m.ReadStr("v"), app_convert_dvi_to_png);} + else if (String_.Eq(k, Cfg__lua)) {Init_cmd(m.ReadStr("v"), app_lua); gplx.xowa.xtns.scribunto.Scrib_core_mgr.Term_all((Xoae_app)app);} + else if (String_.Eq(k, Cfg__lilypond)) {Init_cmd(m.ReadStr("v"), app_lilypond);} + else if (String_.Eq(k, Cfg__abc2ly)) {Init_cmd(m.ReadStr("v"), app_abc2ly);} + else if (String_.Eq(k, Cfg__trim_img)) {Init_cmd(m.ReadStr("v"), app_trim_img);} + else if (String_.Eq(k, Cfg__midi_to_ogg)) {Init_cmd(m.ReadStr("v"), app_midi_to_ogg);} + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static void Init_cmd(String exe_and_args, Process_adp proc) { + String[] lines = gplx.xowa.addons.apps.cfgs.Xocfg_mgr.Parse_io_cmd(exe_and_args); + proc.Exe_and_args_(lines[0], lines[1]); + } + private void Init_cmd(String exe_and_args, int... exts) { + String[] lines = gplx.xowa.addons.apps.cfgs.Xocfg_mgr.Parse_io_cmd(exe_and_args); + for (int ext_id : exts) { + apps_by_ext[ext_id].Exe_and_args_(lines[0], lines[1]); + } + } + public void Exec_view_web(byte[] url) { + String url_str = String_.new_u8(url); + url_str = String_.Replace(url_str, "\"", "\"\""); // escape quotes; DATE:2013-03-31 + url_str = Process_adp.Escape_ampersands_if_process_is_cmd(Op_sys.Cur().Tid_is_wnt(), app_web.Exe_url().Raw(), url_str); // escape ampersands; DATE:2014-05-20 + app_web.Run(url_str); + } + private static final String Invk_convert_tex_to_dvi = "convert_tex_to_dvi", Invk_convert_dvi_to_png = "convert_dvi_to_png"; + private static final String + Cfg__web = "xowa.files.apps.view.web" + , Cfg__media = "xowa.files.apps.view.media" + , Cfg__image = "xowa.files.apps.view.image" + , Cfg__svg = "xowa.files.apps.view.svg" + , Cfg__pdf = "xowa.files.apps.view.pdf" + , Cfg__djvu = "xowa.files.apps.view.djvu" + + , Cfg__query_size = "xowa.files.apps.make.img_size_get" + , Cfg__resize_img = "xowa.files.apps.make.img_size_set" + , Cfg__convert_svg_to_png = "xowa.files.apps.make.svg_to_png" + , Cfg__convert_djvu_to_tiff = "xowa.files.apps.make.djvu_to_tiff" + + , Cfg__bz2__stdout_cmd = "xowa.bldr.import.apps.bz2_stdout.cmd" + , Cfg__bz2 = "xowa.bldr.import.apps.bz2" + , Cfg__gz = "xowa.bldr.import.apps.gz" + + , Cfg__lua = "xowa.addon.scribunto.lua.cmd" + , Cfg__convert_tex_to_dvi = "xowa.addon.math.apps.tex_to_dvi" + , Cfg__convert_dvi_to_png = "xowa.addon.math.apps.dvi_to_png" + , Cfg__lilypond = "xowa.addon.score.apps.lilypond" + , Cfg__abc2ly = "xowa.addon.score.apps.abc2ly" + , Cfg__trim_img = "xowa.addon.score.apps.trim_img" + , Cfg__midi_to_ogg = "xowa.addon.score.apps.midi_to_ogg" + ; +} + diff --git a/400_xowa/src/gplx/xowa/apps/servers/Gxw_html_server.java b/400_xowa/src/gplx/xowa/apps/servers/Gxw_html_server.java index a27517de8..c1a556987 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/Gxw_html_server.java +++ b/400_xowa/src/gplx/xowa/apps/servers/Gxw_html_server.java @@ -13,3 +13,72 @@ 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.apps.servers; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.core.primitives.*; import gplx.core.js.*; +import gplx.gfui.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.gxws.*; +import gplx.xowa.apps.servers.tcp.*; +import gplx.xowa.apps.servers.http.*; import gplx.xowa.guis.views.*; +public class Gxw_html_server implements Gxw_html { + private Xosrv_socket_wtr wtr; private Gfo_usr_dlg usr_dlg; + private final Js_wtr js_wtr = new Js_wtr(); + public Gxw_html_server(Gfo_usr_dlg usr_dlg, Xosrv_socket_wtr wtr) { + this.usr_dlg = usr_dlg; this.wtr = wtr; + } + public void Html_doc_html_load_by_mem(String html) {Exec_as_str("location.reload(true);");} // HACK: force reload of page + public void Html_doc_html_load_by_url(Io_url path, String html) {Exec_as_str("location.reload(true);");} // HACK: force reload of page + public byte Html_doc_html_load_tid() {return html_doc_html_load_tid;} private byte html_doc_html_load_tid; + public void Html_doc_html_load_tid_(byte v) {html_doc_html_load_tid = v;} + public void Html_dispose() {} + public void Html_js_enabled_(boolean v) {} + public String Html_js_eval_proc_as_str(String name, Object... args) {return Exec_as_str(js_wtr.Write_statement_return_func(name, args).To_str_and_clear());} // TODO_OLD: add other params + public boolean Html_js_eval_proc_as_bool(String name, Object... args) {return Exec_as_bool(js_wtr.Write_statement_return_func(name, args).To_str_and_clear());} + public String Html_js_eval_script(String script) {return Exec_as_str(script);} + public Object Html_js_eval_script_as_obj(String script) {return Exec_as_str(script);} + public void Html_js_cbks_add(String js_func_name, Gfo_invk invk) {} + public String Html_js_send_json(String name, String data) {throw Err_.new_unimplemented();} + public void Html_invk_src_(Gfo_evt_itm v) {} + public GxwCore_base Core() {throw Err_.new_unimplemented();} + public GxwCbkHost Host() {throw Err_.new_unimplemented();} public void Host_set(GxwCbkHost host) {throw Err_.new_unimplemented();} + public Object UnderElem() {throw Err_.new_unimplemented();} + public String TextVal() {throw Err_.new_unimplemented();} public void TextVal_set(String v) {throw Err_.new_unimplemented();} + public void EnableDoubleBuffering() {throw Err_.new_unimplemented();} + private boolean Exec_as_bool(String s) { + Exec_as_str(s); + return true; // NOTE: js is async, so immediate return value is not possible; return true for now; + } + private String Exec_as_str(String s) { + if (wtr == null) return ""; // HACK: handles http_server + s = "(function () {" + s + "})();"; // NOTE: dependent on firefox_addon which does 'var result = Function("with(arguments[0]){return "+cmd_text+"}")(session.window);'; DATE:2014-01-28 + gplx.core.threads.Thread_adp_.Sleep(50); // NOTE: need to sleep, else images won't actually show up on screen; PAGE:nethackwiki.com:Weapons; DATE:2015-08-23 + Xosrv_msg msg = Xosrv_msg.new_(Xosrv_cmd_types.Browser_exec, Bry_.Empty, Bry_.Empty, Bry_.Empty, Bry_.Empty, Bry_.new_u8(s)); + usr_dlg.Note_many("", "", "sending browser.js: msg=~{0}", s); + wtr.Write(msg); + return ""; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_set)) {} + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_set = "set"; + public static void Init_gui_for_server(Xoae_app app, Xosrv_socket_wtr wtr) { + Mem_kit mem_kit = (Mem_kit)Gfui_kit_.Mem(); + mem_kit.New_html_impl_prototype_(new Gxw_html_server(app.Usr_dlg(), wtr)); // NOTE: set prototype before calling Kit_ + app.Gui_mgr().Kit_(mem_kit); + } + public static void Assert_tab(Xoae_app app, Xoae_page page) { + Xog_win_itm browser_win = app.Gui_mgr().Browser_win(); + if (browser_win.Active_tab() == null) { // no active tab + Xowe_wiki wiki = page.Wikie(); // take wiki from current page; NOTE: do not take from browser_win.Active_tab().Wiki(); DATE:2015-02-23 + browser_win.Tab_mgr().Tabs_new_init(wiki, page); // create at least one active tab; DATE:2014-07-30 + } + } + public static Xog_tab_itm Assert_tab2(Xoae_app app, Xowe_wiki wiki) { + Xog_win_itm browser_win = app.Gui_mgr().Browser_win(); + Xog_tab_itm rv = browser_win.Active_tab(); + if (rv == null) { // no active tab + Xoae_page page = Xoae_page.New(wiki, wiki.Ttl_parse(Bry_.new_a7("Empty_tab"))); + rv = browser_win.Tab_mgr().Tabs_new_init(wiki, page); // create at least one active tab; DATE:2014-07-30 + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/http/File_retrieve_mode.java b/400_xowa/src/gplx/xowa/apps/servers/http/File_retrieve_mode.java index a27517de8..30e36c7fb 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/http/File_retrieve_mode.java +++ b/400_xowa/src/gplx/xowa/apps/servers/http/File_retrieve_mode.java @@ -13,3 +13,22 @@ 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.apps.servers.http; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +class File_retrieve_mode { + public static String Xto_str(byte v) { + switch (v) { + case Mode_skip: return "skip"; + case Mode_wait: return "wait"; + case Mode_async_server: return "async_server"; + default: throw Err_.new_unimplemented(); + } + } + public static byte Xto_byte(String s) { + if (String_.Eq(s, "skip")) return Mode_skip; + else if (String_.Eq(s, "wait")) return Mode_wait; + else if (String_.Eq(s, "async_server")) return Mode_async_server; + else throw Err_.new_unimplemented(); + } + public static final byte Mode_skip = 1, Mode_wait = 2, Mode_async_server = 3; + public static Keyval[] Options__list = Keyval_.Ary(Keyval_.new_("wait"), Keyval_.new_("skip"), Keyval_.new_("async_server", "async server")); +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/http/Http_data__client.java b/400_xowa/src/gplx/xowa/apps/servers/http/Http_data__client.java index a27517de8..176d6e311 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/http/Http_data__client.java +++ b/400_xowa/src/gplx/xowa/apps/servers/http/Http_data__client.java @@ -13,3 +13,13 @@ 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.apps.servers.http; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +import gplx.langs.jsons.*; +public class Http_data__client { + public Http_data__client(byte[] server_host, String ip_address_str) { + this.server_host = server_host; + this.ip_address = Bry_.new_a7(ip_address_str); + } + public byte[] Server_host() {return server_host;} private final byte[] server_host; + public byte[] Ip_address() {return ip_address;} private final byte[] ip_address; +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/http/Http_file_utl.java b/400_xowa/src/gplx/xowa/apps/servers/http/Http_file_utl.java index a27517de8..392cfb4df 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/http/Http_file_utl.java +++ b/400_xowa/src/gplx/xowa/apps/servers/http/Http_file_utl.java @@ -13,3 +13,36 @@ 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.apps.servers.http; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +import gplx.xowa.files.*; +class Http_file_utl { + public static byte[] To_mime_type_by_path_as_bry(byte[] path_bry) { + int dot_pos = Bry_find_.Find_bwd(path_bry, Byte_ascii.Dot); + return dot_pos == Bry_find_.Not_found ? Mime_octet_stream : To_mime_type_by_ext_as_bry(path_bry, dot_pos, path_bry.length); + } + public static byte[] To_mime_type_by_ext_as_bry(byte[] ext_bry, int bgn, int end) { + Object o = mime_hash.Get_by_mid(ext_bry, bgn, end); + return o == null ? Mime_octet_stream : (byte[])o; + } + private static final byte[] + Mime_octet_stream = Xof_ext_.Mime_type__ary[Xof_ext_.Id_unknown] + , Mime_html = Bry_.new_a7("text/html") + , Mime_css = Bry_.new_a7("text/css") + , Mime_js = Bry_.new_a7("application/javascript") + ; + private static final Hash_adp_bry mime_hash = Mime_hash__new(); + private static Hash_adp_bry Mime_hash__new() { + Hash_adp_bry rv = Hash_adp_bry.ci_a7(); + int len = Xof_ext_.Id__max; + for (int i = 0; i < len; ++i) { + rv.Add_bry_obj + ( Bry_.Add(Byte_ascii.Dot, Xof_ext_.Bry__ary[i]) + , Xof_ext_.Mime_type__ary[i]); + } + rv.Add_str_obj(".htm" , Mime_html); + rv.Add_str_obj(".html" , Mime_html); + rv.Add_str_obj(".css" , Mime_css); + rv.Add_str_obj(".js" , Mime_js); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_mgr.java b/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_mgr.java index a27517de8..44b61f356 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_mgr.java @@ -13,3 +13,144 @@ 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 */ +/* +This file is part of XOWA: the XOWA Offline Wiki Application +Copyright (C) 2013 matthiasjasny@gmail.com; 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 following files: + +GPLv3 License: LICENSE-GPLv3.txt +Apache License: LICENSE-APACHE2.txt +*/ +package gplx.xowa.apps.servers.http; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +import gplx.core.threads.*; import gplx.core.net.*; import gplx.core.primitives.*; import gplx.core.envs.*; +import gplx.langs.jsons.*; import gplx.langs.htmls.encoders.*; +import gplx.xowa.wikis.pages.*; +public class Http_server_mgr implements Gfo_invk { + private final Object thread_lock = new Object(); + private final Gfo_usr_dlg usr_dlg; + private Http_server_socket wkr; + private byte retrieve_mode = File_retrieve_mode.Mode_wait; + private boolean running, init_gui_needed = true; + public Http_server_mgr(Xoae_app app) { + this.app = app; + this.usr_dlg = app.Usr_dlg(); + this.request_parser = new Http_request_parser(server_wtr, false); + } + public Xoae_app App() {return app;} private final Xoae_app app; + public Http_server_wtr Server_wtr() {return server_wtr;} private final Http_server_wtr server_wtr = Http_server_wtr_.New__console(); + public Http_request_parser Request_parser() {return request_parser;} private final Http_request_parser request_parser; + public Gfo_url_encoder Encoder() {return encoder;} private final Gfo_url_encoder encoder = Gfo_url_encoder_.New__http_url().Make(); + public int Port() {return port;} + public Http_server_mgr Port_(int v, boolean caller_is_cfg) { + if ( caller_is_cfg + && v == Port__default // new_val == 8080 + && port != Port__default) {// cur_val != 8080 + return this; // exit; do not override command-line value with cfg_value + } + port = v; + return this; + } private int port = Port__default; + public Http_server_wkr_pool Wkr_pool() {return wkr_pool;} private final Http_server_wkr_pool wkr_pool = new Http_server_wkr_pool(); + public Int_pool Uid_pool() {return uid_pool;} private final Int_pool uid_pool = new Int_pool(); + public byte[] Home() {return home;} public void Home_(byte[] v) {home = Bry_.Add(Byte_ascii.Slash_bry, v);} private byte[] home = Bry_.new_a7("/home/wiki/Main_Page"); + private void Running_(boolean val) { + if (val) { + if (running) + Note("HTTP Server already started"); + else { + Run(); + } + } + else { + if (running) { + wkr.Canceled_(true); + wkr = null; + Note("HTTP Server stopped"); + } + else + Note("HTTP Server not started"); + } + running = val; + } + public void Run() { + app.Cfg().Bind_many_app(this, Cfg__port, Cfg__file_retrieve_mode); + if (wkr == null) wkr = new Http_server_socket(this); + Thread_adp_.Start_by_key("thread:xowa.http_server.server", wkr, Http_server_socket.Invk_run); + Note("HTTP Server started: Navigate to http://localhost:" + Int_.To_str(port)); + } + public void Run_xowa_cmd(Xoae_app app, String url_encoded_str) { + Gfo_url_encoder url_converter = Gfo_url_encoder_.New__http_url().Make(); // create instance for each call + String cmd = url_converter.Decode_str(url_encoded_str); + app.Gfs_mgr().Run_str(cmd); + } + public String Parse_page_to_html(Http_data__client data__client, byte[] wiki_domain, byte[] ttl_bry) { + synchronized (thread_lock) { + // create a shim gui to automatically handle default XOWA gui JS calls + if (init_gui_needed) { + init_gui_needed = false; + Gxw_html_server.Init_gui_for_server(app, null); + } + + // get the wiki + Xowe_wiki wiki = (Xowe_wiki)app.Wiki_mgr().Get_by_or_make_init_y(wiki_domain); // assert init for Main_Page; EX:click zh.w on wiki sidebar; DATE:2015-07-19 + if (Runtime_.Memory_total() > Io_mgr.Len_gb) Xowe_wiki_.Rls_mem(wiki, true); // release memory at 1 GB; DATE:2015-09-11 + + // get the url / ttl + if (Bry_.Len_eq_0(ttl_bry)) ttl_bry = wiki.Props().Main_page(); + Xoa_url url = wiki.Utl__url_parser().Parse(ttl_bry); + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, url.To_bry_page_w_anch()); // changed from ttl_bry to page_w_anch; DATE:2017-07-24 + + // get the page + gplx.xowa.guis.views.Xog_tab_itm tab = Gxw_html_server.Assert_tab2(app, wiki); // HACK: assert tab exists + Xoae_page page = wiki.Page_mgr().Load_page(url, ttl, tab); + app.Gui_mgr().Browser_win().Active_page_(page); // HACK: init gui_mgr's page for output (which server ordinarily doesn't need) + if (page.Db().Page().Exists_n()) { // if page does not exist, replace with message; else null_ref error; DATE:2014-03-08 + page.Db().Text().Text_bry_(Bry_.new_a7("'''Page not found.'''")); + wiki.Parser_mgr().Parse(page, false); + } + page.Html_data().Head_mgr().Itm__server().Init_by_http(data__client).Enabled_y_(); + + // generate html + String rv = String_.new_u8(wiki.Html_mgr().Page_wtr_mgr().Gen(page, Xopg_page_.Tid_read)); // NOTE: must generate HTML now in order for "wait" and "async_server" to work with text_dbs; DATE:2016-07-10 + boolean rebuild_html = false; + switch (retrieve_mode) { + case File_retrieve_mode.Mode_skip: // noop + break; + case File_retrieve_mode.Mode_async_server: + rebuild_html = true; + app.Gui_mgr().Browser_win().Page__async__bgn(tab); + break; + case File_retrieve_mode.Mode_wait: + rebuild_html = true; + gplx.xowa.guis.views.Xog_async_wkr.Async(page, tab.Html_itm()); + page = wiki.Page_mgr().Load_page(url, ttl, tab); // HACK: fetch page again so that HTML will now include img data + break; + } + if (rebuild_html) rv = String_.new_u8(wiki.Html_mgr().Page_wtr_mgr().Gen(page, Xopg_page_.Tid_read)); + return rv; + } + } + private void Note(String s) { + // usr_dlg.Prog_many("", "", s); // messages should write to progress bar for gui + usr_dlg.Note_many("", "", s); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Cfg__port)) Port_(m.ReadInt("v"), true); + else if (ctx.Match(k, Cfg__file_retrieve_mode)) retrieve_mode = File_retrieve_mode.Xto_byte(m.ReadStr("v")); + else if (ctx.Match(k, Invk_running_)) Running_(m.ReadYn("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_running_ = "running_"; + private static final String + Cfg__port = "xowa.addon.http_server.port" + , Cfg__file_retrieve_mode = "xowa.addon.http_server.file_retrieve_mode"; + private static final int Port__default = 8080; +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_socket.java b/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_socket.java index a27517de8..ba76bb792 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_socket.java +++ b/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_socket.java @@ -13,3 +13,50 @@ 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.apps.servers.http; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +import gplx.core.net.*; import gplx.core.threads.*; import gplx.core.primitives.*; +class Http_server_socket implements Gfo_invk { + private final Http_server_mgr server_mgr; + private Server_socket_adp server_socket; + public Http_server_socket(Http_server_mgr server_mgr) {this.server_mgr = server_mgr;} + public boolean Canceled() {return canceled;} + public Http_server_socket Canceled_(boolean v) { + canceled = v; + if (canceled) { + server_socket.Rls(); + server_socket = null; + } + return this; + } private boolean canceled; + public void Run() { + if (server_socket == null) server_socket = new Server_socket_adp__base().Ctor(server_mgr.Port()); + while (true) { // listen for incoming requests + Socket_adp client_socket = server_socket.Accept(); // NOTE: blocking call + int wkr_uid = -1; // NOTE: default to -1 to handle cases when http_server.max_clients is not set; DATE:2015-10-11 + Http_server_wkr_pool wkr_pool = server_mgr.Wkr_pool(); + if (wkr_pool.Enabled()) { + Http_server_wtr server_wtr = server_mgr.Server_wtr(); + int timeout = wkr_pool.Timeout(); + boolean print_msg = true; + while (wkr_pool.Full()) { + if (print_msg) { + print_msg = false; + server_wtr.Write_str_w_nl("maximum # of concurrent connections reached; max=" + wkr_pool.Max() + " timeout=" + timeout); + } + Thread_adp_.Sleep(timeout); + } + wkr_uid = server_mgr.Uid_pool().Get_next(); + wkr_pool.Add(wkr_uid); + // server_wtr.Write_str_w_nl("added new worker; uid=" + wkr_uid); + } + Http_server_wkr wkr = new Http_server_wkr(server_mgr, wkr_uid); + wkr.Init_by_thread(client_socket); + Thread_adp_.Start_by_key("thread:xowa.http_server.client", wkr, Http_server_wkr.Invk_run); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_run)) this.Run(); + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String Invk_run = "run"; +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_wkr.java b/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_wkr.java index a27517de8..5af6225b1 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_wkr.java +++ b/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_wkr.java @@ -13,3 +13,171 @@ 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.apps.servers.http; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; +import gplx.core.primitives.*; import gplx.core.net.*; import gplx.langs.htmls.encoders.*; +import gplx.xowa.apps.*; +import gplx.xowa.htmls.js.*; +class Http_server_wkr implements Gfo_invk { + private final int uid; + private final Http_server_mgr server_mgr; + private final Http_server_wtr server_wtr; + private final Http_client_wtr client_wtr = Http_client_wtr_.new_stream(); + private final Http_client_rdr client_rdr = Http_client_rdr_.new_stream(); + private final Http_request_parser request_parser; + private final Gfo_url_encoder url_encoder; + private final Xoae_app app; + private final String root_dir_http; + private final byte[] root_dir_fsys; + private final Bry_bfr tmp_bfr = Bry_bfr_.New_w_size(64); + private Socket_adp socket; + private Http_data__client data__client; + public Http_server_wkr(Http_server_mgr server_mgr, int uid){ + this.server_mgr = server_mgr; this.uid = uid; + this.app = server_mgr.App(); this.server_wtr = server_mgr.Server_wtr(); this.url_encoder = server_mgr.Encoder(); + this.root_dir_http = app.Fsys_mgr().Root_dir().To_http_file_str(); + this.root_dir_fsys = Bry_.new_u8(app.Fsys_mgr().Root_dir().Raw()); + this.request_parser = server_mgr.Request_parser(); + } + public void Init_by_thread(Socket_adp socket) { + this.socket = socket; + } + public void Run(){ + Http_request_itm request = null; + try { + client_rdr.Stream_(socket.Get_input_stream()); + client_wtr.Stream_(socket.Get_output_stream()); + request = request_parser.Parse(client_rdr); + this.data__client = new Http_data__client(request.Host(), socket.Ip_address()); + byte[] url_bry = request.Url(); + if (Bry_.Eq(url_bry, Url__home)) url_bry = server_mgr.Home(); // "localhost:8080" comes thru as url of "/"; transform to custom home page; DATE:2015-10-11 + switch (request.Type()) { + case Http_request_itm.Type_get: Process_get(request, url_bry); break; + case Http_request_itm.Type_post: Process_post(request); break; + } + client_wtr.Rls(); // client_rdr.Rls(); socket.Rls(); + } + catch (Exception e) { + String request_str = request == null ? "<>" : request.To_str(tmp_bfr, Bool_.N); + server_wtr.Write_str_w_nl(String_.Format("failed to process request;\nrequest={0}\nerr_msg={1}", request_str, Err_.Message_gplx_full(e))); + } + finally { + if (uid != -1) { // only release if uid was acquired; DATE:2015-10-11 + server_mgr.Wkr_pool().Del(uid); + server_mgr.Uid_pool().Del(uid); + } + } + } + private void Process_get(Http_request_itm request, byte[] url) { + server_wtr.Write_str_w_nl(String_.new_u8(request.Host()) + "|GET|" + String_.new_u8(request.Url())); // use request url + if (Bry_.Has_at_bgn(url, Url__fsys)) Serve_file(url); + else if (Bry_.Has_at_bgn(url, Url__exec)) Exec_exec(url, Url__exec); + else if (Bry_.Has_at_bgn(url, Url__exec_2)) Exec_exec(url, Url__exec_2); + else Write_wiki(url); + } + private void Serve_file(byte[] url) { + tmp_bfr.Clear().Add(root_dir_fsys); // add "C:\xowa\" + int question_pos = Bry_find_.Find_fwd(url, Byte_ascii.Question); + int url_bgn = Bry_.Has_at_bgn(url, Url__fsys) ? Url__fsys_len : 0; // most files will have "/fsys/" at start, but Mathjax will not + int url_end = question_pos == Bry_find_.Not_found ? url.length : question_pos; // ignore files with query params; EX: /file/A.png?key=val + url_encoder.Decode(tmp_bfr, Bool_.N, url, url_bgn, url_end); // decode url to actual chars; note that XOWA stores on fsys in UTF-8 chars; "�" not "%C3" + byte[] path = tmp_bfr.To_bry_and_clear(); + if (gplx.core.envs.Op_sys.Cur().Tid_is_wnt()) path = Bry_.Replace(path, Byte_ascii.Backslash, Byte_ascii.Slash); + client_wtr.Write_bry(Xosrv_http_wkr_.Rsp__http_ok); + // client_wtr.Write_str("Expires: Sun, 17-Jan-2038 19:14:07 GMT\n"); + String mime_type = String_.new_u8(Http_file_utl.To_mime_type_by_path_as_bry(path)); + client_wtr.Write_str("Content-Type: " + mime_type + "\n\n"); + Io_stream_rdr file_stream = Io_stream_rdr_.New_by_url(Io_url_.new_fil_(String_.new_u8(path))).Open(); + client_wtr.Write_stream(file_stream); + file_stream.Rls(); client_rdr.Rls(); socket.Rls(); + } + private void Exec_exec(byte[] url, byte[] tkn_bgn) { + byte[] cmd = Bry_.Mid(url, tkn_bgn.length); + app.Http_server().Run_xowa_cmd(app, String_.new_u8(cmd)); + } + private void Write_wiki(byte[] req) { + String wiki_domain = ""; String page_name = ""; + String[] req_split = String_.Split(String_.new_u8(req), "/"); + if(req_split.length >= 1){ + wiki_domain = req_split[1]; + } + if(req_split.length >= 4){ + page_name = req_split[3]; + for(int i = 4; i <= req_split.length-1; i++){ + page_name += "/" + req_split[i]; + } + page_name = url_encoder.Decode_str(page_name); + } + String page_html = app.Http_server().Parse_page_to_html(data__client, Bry_.new_u8(wiki_domain), Bry_.new_u8(page_name)); + page_html = Convert_page(page_html, root_dir_http, wiki_domain); + Xosrv_http_wkr_.Write_response_as_html(client_wtr, Bool_.N, page_html); + } + private void Process_post(Http_request_itm request) { + byte[] msg = request.Post_data_hash().Get_by(Key__msg).Val(); + byte[] app_mode = request.Post_data_hash().Get_by(Key__app_mode).Val(); + Xoa_app_mode app_mode_itm = Xoa_app_mode.parse(String_.new_u8(app_mode)); + server_wtr.Write_str_w_nl(String_.new_u8(request.Host()) + "|POST|" + String_.new_u8(msg)); + Object url_tid_obj = post_url_hash.Get_by_bry(request.Url()); if (url_tid_obj == null) throw Err_.new_wo_type("unknown url", "url", request.Url(), "request", request.To_str(tmp_bfr, Bool_.N)); + String rv = null; + switch (((Int_obj_val)url_tid_obj).Val()) { + case Tid_post_url_json: + rv = app.Html__bridge_mgr().Cmd_mgr().Exec(msg); + break; + case Tid_post_url_gfs: + rv = Object_.Xto_str_strict_or_null_mark(app.Gfs_mgr().Run_str(String_.new_u8(msg))); + break; + } + if (app_mode_itm.Tid_is_http()) + rv = Convert_page(rv, root_dir_http , "<>"); + Xosrv_http_wkr_.Write_response_as_html(client_wtr, app_mode_itm.Tid() == Xoa_app_mode.Itm_file.Tid(), rv); + } + private static final byte[] Key__msg = Bry_.new_a7("msg"), Key__app_mode = Bry_.new_a7("app_mode"); + private static final int Tid_post_url_json = 1, Tid_post_url_gfs = 2; + private static final Hash_adp_bry post_url_hash = Hash_adp_bry.ci_a7() + .Add_str_int("/exec/json" , Tid_post_url_json) + .Add_str_int("/exec/gfs" , Tid_post_url_gfs) + ; + private static String Convert_page(String page_html, String root_dir_http, String wiki_domain) { + page_html = String_.Replace(page_html, root_dir_http , "/fsys/"); + page_html = String_.Replace(page_html, "xowa-cmd:" , "/exec/"); + page_html = String_.Replace(page_html, "= max;} + public void Init(int max, int timeout) {this.max = max; this.timeout = timeout;} + public void Add(int uid) { + if (max == 0) return; // disabled + synchronized (hash) { + Int_obj_ref wkr_key = Int_obj_ref.New(uid); + hash.Add(wkr_key, wkr_key); + ++len; + } + } + public void Del(int uid) { + if (max == 0) return; // disabled + synchronized (hash) { + hash.Del(hash_key.Val_(uid)); + --len; + } + } +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/tcp/Socket_rdr.java b/400_xowa/src/gplx/xowa/apps/servers/tcp/Socket_rdr.java index a27517de8..2ceb166b1 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/tcp/Socket_rdr.java +++ b/400_xowa/src/gplx/xowa/apps/servers/tcp/Socket_rdr.java @@ -13,3 +13,41 @@ 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.apps.servers.tcp; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Socket_rdr { + private java.net.ServerSocket server; + private java.net.Socket client; + private java.io.InputStream stream; + public IoStream Rdr_stream() {return rdr_stream;} private IoStream rdr_stream = null; + public int Port() {return port;} private int port; + public Socket_rdr Ctor(int port) {this.port = port; return this;} + public Socket_rdr Open() { + try { + // this.Rls(); + if (server == null) { + server = new java.net.ServerSocket(port); + server.setReuseAddress(true); + } + client = server.accept(); + client.setSoTimeout(10000); + stream = client.getInputStream(); + rdr_stream = new IoStream_stream_rdr().UnderRdr_(stream); + return this; + } catch (Exception e) {throw Err_.new_exc(e, "net", "failed to open socket", "port", port);} + } + public void Close() { + try { + // if (server != null) server.close(); + if (client != null) client.close(); + if (stream != null) stream.close(); + } catch (Exception e) {throw Err_.new_exc(e, "net", "failed to close socket", "port", port);} + } + public void Rls() { + try { + if (server != null) server.close(); + if (client != null) client.close(); + if (stream != null) stream.close(); + } catch (Exception e) {throw Err_.new_exc(e, "net", "failed to rls socket", "port", port);} + } +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/tcp/Socket_wtr.java b/400_xowa/src/gplx/xowa/apps/servers/tcp/Socket_wtr.java index a27517de8..337036825 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/tcp/Socket_wtr.java +++ b/400_xowa/src/gplx/xowa/apps/servers/tcp/Socket_wtr.java @@ -13,3 +13,37 @@ 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.apps.servers.tcp; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +public class Socket_wtr { + private String host; + private int port; + private java.net.Socket socket; + private java.io.OutputStream stream; + public Socket_wtr Ctor(String host, int port) {this.host = host; this.port = port; return this;} + public Socket_wtr Open() { + try { + this.Rls(); + socket = new java.net.Socket(host, port); + socket.setSoTimeout(10000); + stream = socket.getOutputStream(); + return this; + } catch (Exception e) {throw Err_.new_exc(e, "net", "failed to open socket", "host", host, "port", port);} + } + public void Write(byte[] bry) { + try { + stream.write(bry, 0, bry.length); + } catch (Exception e) {throw Err_.new_exc(e, "net", "failed to write stream", "host", host, "port", port);} + } + public void Close() { + try { + if (stream != null) stream.close(); + if (socket != null) socket.close(); + } catch (Exception e) {throw Err_.new_exc(e, "net", "failed to close socket", "host", host, "port", port);} + } + public void Rls() { + try { + if (stream != null) stream.close(); + if (socket != null) socket.close(); + } catch (Exception e) {throw Err_.new_exc(e, "net", "failed to release socket", "host", host, "port", port);} + } +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_cmd_types.java b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_cmd_types.java index a27517de8..0edb6cfa5 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_cmd_types.java +++ b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_cmd_types.java @@ -13,3 +13,11 @@ 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.apps.servers.tcp; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +public class Xosrv_cmd_types { + public static final byte[] + Cmd_exec = Bry_.new_a7("xowa.cmd.exec") , Cmd_pass = Bry_.new_a7("xowa.cmd.result") , Cmd_fail = Bry_.new_a7("xowa.cmd.error") + , Js_exec = Bry_.new_a7("xowa.js.exec") , Js_pass = Bry_.new_a7("xowa.js.result") , Js_fail = Bry_.new_a7("xowa.js.error") + , Browser_exec = Bry_.new_a7("browser.js.exec"), Browser_pass = Bry_.new_a7("browser.js.result") , Browser_fail = Bry_.new_a7("browser.js.error") + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_msg.java b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_msg.java index a27517de8..7e3b80510 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_msg.java +++ b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_msg.java @@ -13,3 +13,107 @@ 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.apps.servers.tcp; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +public class Xosrv_msg { + public byte Version_tid() {return Version_tid_0;} static final byte Version_tid_0 = 0; + public byte[] Cmd_name() {return cmd_name;} private byte[] cmd_name; + public byte[] Msg_id() {return msg_id;} private byte[] msg_id; + public byte[] Sender() {return sender;} private byte[] sender; + public byte[] Recipient() {return recipient;} private byte[] recipient; + public byte[] Msg_date() {return msg_date;} private byte[] msg_date; + public byte[] Msg_text() {return msg_text;} private byte[] msg_text; + public void Print(Bry_bfr bfr) { + int body_len = cmd_name.length + msg_id.length + sender.length + recipient.length + msg_date.length + msg_text.length + 5; // 5=5 pipes for 6 fields + int cksum = (body_len * 2) + 1; + bfr.Add_int_fixed(this.Version_tid() , 1).Add_byte_pipe(); // 0| + bfr.Add_int_fixed(body_len , 10).Add_byte_pipe(); // 0123456789| + bfr.Add_int_fixed(cksum , 10).Add_byte_pipe(); // 0123456789| + bfr.Add(cmd_name ).Add_byte_pipe(); // cmd| + bfr.Add(msg_id ).Add_byte_pipe(); // id| + bfr.Add(sender ).Add_byte_pipe(); // sender| + bfr.Add(recipient ).Add_byte_pipe(); // recipient| + bfr.Add(msg_date ).Add_byte_pipe(); // msg_date| + bfr.Add(msg_text ); // msg_text + } + public static final Xosrv_msg Exit = new Xosrv_msg(); + public static Xosrv_msg fail_(String fmt, Object... ary) { + Xosrv_msg rv = new Xosrv_msg(); + rv.msg_text = Bry_.new_u8(String_.Format(fmt, ary)); + return rv; + } + public static Xosrv_msg new_(byte[] cmd_name, byte[] msg_id, byte[] sender, byte[] recipient, byte[] msg_date, byte[] msg_text) { + Xosrv_msg rv = new Xosrv_msg(); + rv.cmd_name = cmd_name; + rv.msg_id = msg_id; + rv.sender = sender; + rv.recipient = recipient; + rv.msg_date = msg_date; + rv.msg_text = msg_text; + return rv; + } +} +/* +Message definition + Id : 0 + Purpose : Version number for message format + Data type : int + Notes : Always 0; will change to 1 if message format ever changes + Example : "0" + + Id : 1 + Description : Body length; specified total length of message field 3 (body) + Data type : int + Notes : always zero-padded to 10 bytes (not hexadecimal) + Example : "0000000123" + + Id : 2 + Description : Checksum; should equal (2 * body length) + 1 + Data type : int + Notes : always zero-padded to 10 bytes (not hexadecimal) + Example : "0000000247" + + Id : 3 + Description : Body + Data type : String + Notes : length specified by field 1 (body length) + Example : see below + +Body definition + * Pipes are not allowed in any field except for the last + * Only the first field is required + + Id : 0 + Purpose : Command name + Data type : String + Notes : unique name identifying the command + Example : "xowa.cmd.exec", "xowa.cmd.result", "xowa.cmd.error", "xowa.js.exec", "xowa.js.result", "xowa.js.error" + + Id : 1 + Purpose : Message id + Data type : String + Notes : Usage is defined by callers; can be empty + Example : "1", "" + + Id : 2 + Purpose : Sender id + Data type : String + Notes : Usage is defined by callers; can be empty + Example : "tab1", "xowa", "" + + Id : 3 + Purpose : Recipient id + Data type : String + Notes : Usage is defined by callers; can be empty + Example : "xowa", "tab1", "" + + Id : 4 + Purpose : Message date + Data type : String + Notes : ISO 8601 format; see http://www.w3.org/TR/NOTE-datetime; Usage is defined by callers; can be empty + Example : "1997-07-16T19:20:30.45+01:00", "" + + Id : 5 + Purpose : Message text + Data type : String + Notes : freeform; can contain any character + Example : "app.shell.fetch_page('simple.wikipedia.org/wiki/Earth', 'html');"*/ diff --git a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_msg_rdr.java b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_msg_rdr.java index a27517de8..835118be6 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_msg_rdr.java +++ b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_msg_rdr.java @@ -13,3 +13,50 @@ 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.apps.servers.tcp; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +import gplx.core.primitives.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; import gplx.core.texts.*; +public class Xosrv_msg_rdr { + public Xosrv_msg_rdr(byte[] default_body_bry, IoStream rdr) {this.default_body_bry = default_body_bry; default_body_bry_len = default_body_bry.length; this.rdr = rdr;} private byte[] header_bry = new byte[24], default_body_bry; int default_body_bry_len; + public IoStream Rdr() {return rdr;} private IoStream rdr; + public Xosrv_msg Read() { + int bytes_read = rdr.Read(header_bry, 0, 24); // 24 = version(1) + pipe + msg_len (10) + pipe + cksum (10) + pipe + if (bytes_read < 24) { + if (bytes_read == -1) return Xosrv_msg.Exit; // stream closed; should only occur when shutting down + else return Xosrv_msg.fail_("header is invalid; hdr:{0}", String_.new_u8(header_bry, 0, bytes_read)); + } + byte version = header_bry[0]; if (version != Byte_ascii.Num_0) return Xosrv_msg.fail_("version must be 0; version:{0}", Byte_.To_str(version)); + int body_len = Bry_.To_int_or(header_bry, 2, 12, -1); if (body_len == -1) return Xosrv_msg.fail_("body_len is not number; body_len:{0}", String_.new_u8(header_bry, 2, 23)); + int cksum = Bry_.To_int_or(header_bry, 13, 23, -1); if (cksum == -1) return Xosrv_msg.fail_("checksum is not number; cksum:{0}", String_.new_u8(header_bry, 13, 23)); + if (!Chk_bytes(header_bry, Byte_ascii.Pipe, 1, 12, 23)) return Xosrv_msg.fail_("message should be delimited by pipes at 1, 12 and 23; message:{0}", String_.new_u8(header_bry, 0, 24)); + if (cksum != (body_len * 2) + 1) return Xosrv_msg.fail_("checksum failed; body_len:{0} chksum:{1}", body_len, cksum); + byte[] body_bry = body_len > default_body_bry_len ? new byte[body_len] : default_body_bry; + rdr.Read(body_bry, 0, body_len); + Int_obj_ref fld_bgn = Int_obj_ref.New_zero(); Bool_obj_ref fail_ref = Bool_obj_ref.n_(); String_obj_ref fld_ref = String_obj_ref.null_(); + byte[] cmd_name = Read_fld(body_bry, body_len, fld_bgn, fail_ref, fld_ref.Val_("cmd_name")); if (fail_ref.Val()) return Read_fld_fail(fld_ref, body_bry); + byte[] msg_id = Read_fld(body_bry, body_len, fld_bgn, fail_ref, fld_ref.Val_("msg_id")); if (fail_ref.Val()) return Read_fld_fail(fld_ref, body_bry); + byte[] sender = Read_fld(body_bry, body_len, fld_bgn, fail_ref, fld_ref.Val_("sender")); if (fail_ref.Val()) return Read_fld_fail(fld_ref, body_bry); + byte[] recipient = Read_fld(body_bry, body_len, fld_bgn, fail_ref, fld_ref.Val_("recipient")); if (fail_ref.Val()) return Read_fld_fail(fld_ref, body_bry); + byte[] msg_date = Read_fld(body_bry, body_len, fld_bgn, fail_ref, fld_ref.Val_("msg_date")); if (fail_ref.Val()) return Read_fld_fail(fld_ref, body_bry); + byte[] msg_text = Bry_.Mid(body_bry, fld_bgn.Val(), body_len); + return Xosrv_msg.new_(cmd_name, msg_id, sender, recipient, msg_date, msg_text); + } + private static byte[] Read_fld(byte[] bry, int bry_len, Int_obj_ref fld_bgn, Bool_obj_ref fail_ref, String_obj_ref fld_ref) { + int fld_end = Bry_find_.Find_fwd(bry, Byte_ascii.Pipe, fld_bgn.Val(), bry_len); + if (fld_end == Bry_find_.Not_found) {fail_ref.Val_y_(); return null;} + byte[] rv = Bry_.Mid(bry, fld_bgn.Val(), fld_end); + fld_bgn.Val_(fld_end + 1); // +1 to place after pipe + return rv; + } + private static Xosrv_msg Read_fld_fail(String_obj_ref fld_name, byte[] body_bry) {return Xosrv_msg.fail_("pipe not found for " + fld_name.Val() + "; body:{0}", String_.new_u8(body_bry));} + private static boolean Chk_bytes(byte[] bry, byte expd, int... pos_ary) { + int len = pos_ary.length; + int bry_len = bry.length; + for (int i = 0; i < len; i++) { + int pos = pos_ary[i]; + if (pos >= bry_len) return false; // out of bounds; return false (don't fail) + if (bry[pos] != expd) return false; + } + return true; + } +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_msg_rdr_tst.java b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_msg_rdr_tst.java index a27517de8..b58dcd165 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_msg_rdr_tst.java +++ b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_msg_rdr_tst.java @@ -13,3 +13,51 @@ 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.apps.servers.tcp; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +import org.junit.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Xosrv_msg_rdr_tst { + @Before public void init() {fxt.Clear();} private Xosrv_msg_rdr_fxt fxt = new Xosrv_msg_rdr_fxt(); + @Test public void Parse() { + String raw = "0|0000000045|0000000091|cmd_0|id_0|sender_0|recipient_0|date_0|text_0"; + Xosrv_msg msg = fxt.Test_parse_msg(raw, "cmd_0", "id_0", "sender_0", "recipient_0", "date_0", "text_0"); + fxt.Test_print(msg, raw); + } + @Test public void Err_header_is_invalid() {fxt.Test_parse_err("abcde", "header is invalid");} + @Test public void Err_checksum_failed() {fxt.Test_parse_err("0|0000000000|0000000000|", "checksum failed");} + @Test public void Err_cmd_missing() {fxt.Test_parse_err("0|0000000001|0000000003|a", "pipe not found for cmd_name");} +} +class Xosrv_msg_rdr_fxt { + public Xosrv_msg_rdr_fxt Clear() { + if (msg_rdr == null) { + msg_rdr_stream = new IoStream_mock(); + msg_rdr = new Xosrv_msg_rdr(Bry_.Empty, msg_rdr_stream); + } + msg_rdr_stream.Reset(); + return this; + } private Xosrv_msg_rdr msg_rdr; private IoStream_mock msg_rdr_stream; + public Xosrv_msg Test_parse_msg(String raw, String expd_cmd, String expd_id, String expd_sender, String expd_recipient, String expd_date, String expd_text) { + byte[] raw_bry = Bry_.new_a7(raw); + msg_rdr_stream.Data_bry_(raw_bry).Read_limit_(raw_bry.length); + Xosrv_msg msg = msg_rdr.Read(); + Tfds.Eq(String_.new_a7(msg.Cmd_name()) , expd_cmd); + Tfds.Eq(String_.new_a7(msg.Msg_id()) , expd_id); + Tfds.Eq(String_.new_a7(msg.Sender()) , expd_sender); + Tfds.Eq(String_.new_a7(msg.Recipient()) , expd_recipient); + Tfds.Eq(String_.new_a7(msg.Msg_date()) , expd_date); + Tfds.Eq(String_.new_a7(msg.Msg_text()) , expd_text); + return msg; + } + public void Test_parse_err(String raw, String expd_err) { + byte[] raw_bry = Bry_.new_a7(raw); + msg_rdr_stream.Data_bry_(raw_bry).Read_limit_(raw_bry.length); + Xosrv_msg msg = msg_rdr.Read(); + String msg_text = String_.new_a7(msg.Msg_text()); + Tfds.Eq_true(String_.Has_at_bgn(msg_text, expd_err), msg_text); + } + public void Test_print(Xosrv_msg msg, String expd) { + Bry_bfr bfr = Bry_bfr_.New(); + msg.Print(bfr); + Tfds.Eq(expd, bfr.To_str_and_clear()); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_server.java b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_server.java index a27517de8..f73557da8 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_server.java +++ b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_server.java @@ -13,3 +13,116 @@ 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.apps.servers.tcp; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +import gplx.core.primitives.*; import gplx.core.ios.*; import gplx.core.envs.*; import gplx.core.threads.*; +import gplx.gfui.controls.standards.*; +import gplx.langs.jsons.*; +public class Xosrv_server implements Gfo_invk { + private long last_cmd; + public Xosrv_socket_rdr Rdr() {return rdr;} private Xosrv_socket_rdr rdr = new Xosrv_socket_rdr(); + public Xosrv_socket_wtr Wtr() {return wtr;} private Xosrv_socket_wtr wtr = new Xosrv_socket_wtr(); + public int Rdr_port() {return rdr_port;} public Xosrv_server Rdr_port_(int v) {rdr_port = v; return this;} private int rdr_port = 55000; + public int Wtr_port() {return wtr_port;} public Xosrv_server Wtr_port_(int v) {wtr_port = v; return this;} private int wtr_port = 55001; + public int Shutdown_interval() {return shutdown_interval;} public Xosrv_server Shutdown_interval_(int v) {shutdown_interval = v; return this;} private int shutdown_interval = -1; + public String Wtr_host() {return wtr_host;} private String wtr_host = "localhost"; + public boolean Running() {return running;} public Xosrv_server Running_(boolean v) {running = v; running_str = Bool_.To_str_lower(running); return this;} private boolean running = false; + public String Running_str() {return running_str;} String running_str = "false"; + public void App_ctor(Xoae_app app) {this.app = app;} + public Xoae_app App() {return app;} private Xoae_app app; + public void Run() { + rdr.Init(this, rdr_port); + wtr.Init(wtr_host, wtr_port); + Gxw_html_server.Init_gui_for_server(app, wtr); + Thread_adp_.Start_by_key(gplx.xowa.apps.Xoa_thread_.Key_http_server_main, rdr, Xosrv_socket_rdr.Invk_start); + app.Usr_dlg().Note_many("", "", "server started: listening on ~{0}. Press Ctrl+C to exit", rdr_port); + last_cmd = System_.Ticks(); + Running_(true); + while (running) { + if (shutdown_interval != -1 && System_.Ticks() - last_cmd > shutdown_interval) break; + Thread_adp_.Sleep(1000); + } + rdr.Rls(); + wtr.Rls(); + app.Usr_dlg().Note_many("", "", "server stopped", rdr_port); + } + public void Msg_rcvd(Xosrv_msg msg) { + try { + byte[] cmd_name = msg.Cmd_name(); + byte[] rsp_name = Bry_.Empty; + long time_bgn = System_.Ticks(); + last_cmd = time_bgn; + byte[] msg_bry = msg.Msg_text(); + String msg_str = String_.new_u8(msg_bry); + app.Usr_dlg().Note_many("", "", "processing cmd: ~{0}", msg_str); + String rsp_str = null; + if (Bry_.Eq(cmd_name, Xosrv_cmd_types.Cmd_exec)) {rsp_name = Xosrv_cmd_types.Cmd_pass; rsp_str = Exec_cmd(msg_str);} + else if (Bry_.Eq(cmd_name, Xosrv_cmd_types.Js_exec)) {rsp_name = Xosrv_cmd_types.Js_pass; rsp_str = Exec_js(msg.Sender(), msg_bry);} + Xosrv_msg rsp_msg = Xosrv_msg.new_(rsp_name, msg.Msg_id(), msg.Recipient(), msg.Sender(), msg.Msg_date(), Bry_.new_u8(rsp_str)); + app.Usr_dlg().Note_many("", "", "sending rsp: bytes=~{0}", String_.Len(rsp_str)); + wtr.Write(rsp_msg); + app.Usr_dlg().Note_many("", "", "rsp sent: elapsed=~{0}", Time_span_.fracs_(System_.Ticks() - time_bgn).XtoStrUiAbbrv()); + } catch (Exception e) {app.Usr_dlg().Warn_many("", "", "server error: ~{0}", Err_.Message_gplx_full(e));} + } + private String Exec_cmd(String msg_text) { + Object rv_obj = app.Gfs_mgr().Run_str(msg_text); + String rv = Type_.Eq_by_obj(rv_obj, String_.Cls_ref_type) ? (String)rv_obj : Object_.Xto_str_strict_or_null(rv_obj); + return rv; + } + public String Exec_js(byte[] sender, byte[] msg_text) { + String_obj_ref trace = String_obj_ref.new_("exec_js"); + try { + Object[] xowa_exec_args = xowa_exec_parser.Parse_xowa_exec(msg_text); + trace.Val_("js_args"); +// xowa_exec_args = (Object[])Array_.Resize(xowa_exec_args, xowa_exec_args.length + 1); +// xowa_exec_args[xowa_exec_args.length - 1] = sender; + Object rv_obj = Gfui_html.Js_args_exec(app.Gui_mgr().Browser_win().Active_html_itm().Js_cbk(), xowa_exec_args); + trace.Val_("json_write: " + Object_.Xto_str_strict_or_null_mark(rv_obj)); + return json_wtr.Write_root(Bry_xowa_js_result, rv_obj).Bld_as_str(); + } catch (Exception e) {throw Err_.new_exc(e, "http", "exec_js error", "trace", trace, "msg", msg_text);} + } private Xosrv_xowa_exec_parser xowa_exec_parser = new Xosrv_xowa_exec_parser(); private Json_doc_srl json_wtr = new Json_doc_srl(); private static final byte[] Bry_xowa_js_result = Bry_.new_a7("xowa_js_result"); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_rdr_port)) return rdr_port; + else if (ctx.Match(k, Invk_rdr_port_)) rdr_port = m.ReadInt("v"); + else if (ctx.Match(k, Invk_wtr_port)) return wtr_port; + else if (ctx.Match(k, Invk_wtr_port_)) wtr_port = m.ReadInt("v"); + else if (ctx.Match(k, Invk_wtr_host)) return wtr_host; + else if (ctx.Match(k, Invk_wtr_host_)) wtr_host = m.ReadStr("v"); + else if (ctx.Match(k, Invk_shutdown_interval)) return shutdown_interval; + else if (ctx.Match(k, Invk_shutdown_interval_)) shutdown_interval = m.ReadInt("v"); + else if (ctx.Match(k, Invk_stop)) running = false; + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk_stop = "stop", Invk_rdr_port = "rdr_port", Invk_rdr_port_ = "rdr_port_", Invk_wtr_port = "wtr_port", Invk_wtr_port_ = "wtr_port_", Invk_wtr_host = "wtr_host", Invk_wtr_host_ = "wtr_host_" + , Invk_shutdown_interval = "shutdown_interval", Invk_shutdown_interval_ = "shutdown_interval_"; +} +class Xosrv_xowa_exec_parser { + private Json_parser json_parser = new Json_parser(); + public Object[] Parse_xowa_exec(byte[] msg_text) { // parses JSON with format '{"args":["arg0","arg1","arg2"]}' + Json_doc doc = json_parser.Parse(msg_text); + Json_kv args_kv = (Json_kv)doc.Root_nde().Get_at(0); // get "args" kv + Json_ary args_ary = (Json_ary)args_kv.Val(); // get [] + int len = args_ary.Len(); + Object[] rv = new Object[len]; + for (int i = 0; i < len; i++) { // extract args + Json_itm itm = args_ary.Get_at(i); + rv[i] = Parse_ary_itm(itm); + } + return rv; + } + private Object Parse_ary_itm(Json_itm itm) { + switch (itm.Tid()) { + case Json_itm_.Tid__str: + return String_.new_u8(itm.Data_bry()); + case Json_itm_.Tid__ary: + Json_ary ary = (Json_ary)itm; + int len = ary.Len(); + String[] rv = new String[len]; + for (int i = 0; i < len; i++) + rv[i] = String_.new_u8(ary.Get_at(i).Data_bry()); + return rv; + default: + throw Err_.new_unhandled(itm.Tid()); + } + } +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_server_tst.java b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_server_tst.java index a27517de8..c56442f7c 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_server_tst.java +++ b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_server_tst.java @@ -13,3 +13,26 @@ 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.apps.servers.tcp; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +import org.junit.*; +import gplx.core.ios.*; +public class Xosrv_server_tst { + @Before public void init() {fxt.Clear();} private Xosrv_server_fxt fxt = new Xosrv_server_fxt(); + @Test public void Exec_js() { + fxt.Test_exec_js("{\"args\":[\"xowa_exec_test\",\"a\",\"b\"]}", "{\"xowa_js_result\":\"xowa_exec_test|a|b\"}"); + } + @Test public void Exec_js_ary() { + fxt.Test_exec_js("{\"args\":[\"xowa_exec_test_as_array\",\"a\",\"b\"]}", "{\"xowa_js_result\":[\"xowa_exec_test_as_array\",\"a\",\"b\"]}"); + } +} +class Xosrv_server_fxt { + public Xosrv_server_fxt Clear() { + app = Xoa_app_fxt.Make__app__edit(); + Xoa_app_fxt.Init_gui(app, null); // NOTE: null wiki does not matter for test + return this; + } private Xoae_app app; + public void Test_exec_js(String raw, String expd) { + String actl = app.Tcp_server().Exec_js(null, Bry_.new_a7(raw)); + Tfds.Eq(expd, actl); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_socket_rdr.java b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_socket_rdr.java index a27517de8..c0e0e2f91 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_socket_rdr.java +++ b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_socket_rdr.java @@ -13,3 +13,34 @@ 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.apps.servers.tcp; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Xosrv_socket_rdr implements Gfo_invk { + private Socket_rdr rdr = new Socket_rdr(); + public int Port() {return port;} private int port; + public void Init(Xosrv_server server, int port) {this.server = server; this.port = port;} private Xosrv_server server; + public void Start() { + rdr = new Socket_rdr(); + try { + rdr.Ctor(port); + while (true) { + rdr.Open(); + IoStream rdr_stream = rdr.Rdr_stream(); + Xosrv_msg_rdr msg_rdr = new Xosrv_msg_rdr(new byte[24], rdr_stream); + Xosrv_msg msg = msg_rdr.Read(); + if (msg == Xosrv_msg.Exit) continue; + server.Msg_rcvd(msg); + rdr.Close(); + } + } catch (Exception e) {Err_.Noop(e);} + finally {rdr.Rls();} + } + public void Rls() { + rdr.Rls(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_start)) this.Start(); + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String Invk_start = "start"; +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_socket_wtr.java b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_socket_wtr.java index a27517de8..c782be1b0 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_socket_wtr.java +++ b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xosrv_socket_wtr.java @@ -13,3 +13,20 @@ 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.apps.servers.tcp; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +public class Xosrv_socket_wtr { + public String Host() {return host;} private String host = "localhost"; + public int Port() {return port;} private int port; + private Socket_wtr wtr; private Bry_bfr msg_bfr = Bry_bfr_.Reset(4 * Io_mgr.Len_kb); + public void Init(String host, int port) {this.host = host; this.port = port; wtr = new Socket_wtr().Ctor(host, port);} + public void Write(Xosrv_msg msg) { + wtr.Open(); + msg.Print(msg_bfr); + byte[] msg_bry = msg_bfr.To_bry_and_clear(); + wtr.Write(msg_bry); + wtr.Close(); + } + public void Rls() { + wtr.Rls(); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xowa_tcp_console.java b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xowa_tcp_console.java index a27517de8..3dc958d65 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/tcp/Xowa_tcp_console.java +++ b/400_xowa/src/gplx/xowa/apps/servers/tcp/Xowa_tcp_console.java @@ -13,3 +13,216 @@ 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.apps.servers.tcp; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +public class Xowa_tcp_console { + public static void main(String[] args) { + Xowa_tcp_console console = new Xowa_tcp_console(); + console.Run(args); + } + private int server_send_port; + private int server_recv_port; + private String wiki_domain; + private int max_length; + private Xowa_tcp_sender sender; + public void Run(String[] args) { + Print_message_line("XOWA TCP client v0.0.0.0"); + + // parse args + if (!Parse_args(args)) { + Print_message_line("XOWA console requires 4 args: server_send_port, server_recv_port, wiki_domain, max_length."); + Print_message_line("For example, use '55000 55001 simple.wikipedia.org 1000'"); + return; + } + + // start sender + sender = new Xowa_tcp_sender(server_send_port); + + // start console + BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); + Print_message_line("Enter page name. For example 'Earth'. Press Ctrl+C to exit. Enter '|server_stop' to stop server or '|exit' to exit."); + Print_message("> "); + try { + while (true) { + String page = Read_string(input); + if (page == null) + break; + else if ("|exit".equals(page)) + break; + else if ("|server_stop".equals(page)) { + Send_message_server_stop(); + } + else + Send_message_fetch_page(page); + } + } + catch (Exception e) { + Print_error(e); + } + } + private void Send_message_fetch_page(String page) { + Print_message_line("Sending request for " + page); + // String xowa_msg = "0|0000000128|0000000257|xowa.cmd.exec|id_0|sender_0|recipient_0|2013-07-18 01:23:45.678|app.shell.fetch_page('simple.wikipedia.org/wiki/Earth', 'html');"; + String command = String.format("app.shell.fetch_page('%s/wiki/%s', 'wiki');", wiki_domain, page); + Send_message(command); + } + private void Send_message_server_stop() { + Print_message_line("Sending request to stop server"); + Send_message("app.server.stop;"); + } + private void Send_message(String command) { + String id = "id_is_for_client_usage"; + String time = "time_is_for_client_usage"; + String body = String.format("xowa.cmd.exec|%s|xowa_tcp_console|xowa_server|%s|%s", id, time, command); + int body_len = body.length(); + int cksum = (body_len * 2) + 1; + String msg = String.format("0|%s|%s|%s", String.format("%010d", body_len), String.format("%010d", cksum), body); + + Xowa_tcp_receiver receiver = new Xowa_tcp_receiver(server_recv_port, max_length); + new Thread(receiver).start(); + sender.Send_command(msg); + } + private boolean Parse_args(String[] args) { + if (args.length != 4) { + Print_message_line("4 arguments must be supplied: " + args.length); + return false; + } + server_send_port = Parse_int(args[0]); if (server_send_port == -1) return false; + server_recv_port = Parse_int(args[1]); if (server_recv_port == -1) return false; + wiki_domain = args[2]; + max_length = Parse_int(args[3]); if (max_length == -1) return false; + return true; + } + private static int Parse_int(String raw) { + try {return Integer.parseInt(raw);} + catch (Exception e) { + Print_message_line("argument must be numeric: " + raw); + return -1; + } + } + private static String Read_string(BufferedReader input) { + try {return input.readLine();} + catch (IOException e) {return null;} + } + public static void Print_message_line(String msg) { + System.out.println(msg); + } + public static void Print_message(String msg) { + System.out.print(msg); + } + public static void Print_error(Exception e) { + System.err.println(e.getMessage()); + } + public static void Sleep(long millis) { + try {Thread.sleep(millis);} + catch (InterruptedException e) {Print_error(e);} + } +} +class Xowa_tcp_sender { + private int port; + private Socket socket; + private OutputStream output_stream; + public Xowa_tcp_sender(int port) {this.port = port;} + public boolean Open_socket() { + try { + socket = new Socket("localhost", port); + // socket.setSoTimeout(10000); + output_stream = socket.getOutputStream(); + return true; + } + catch (Exception e) { + Xowa_tcp_console.Print_error(e); + return false; + } + } + public void Send_command(String msg) { + try { + while (!Open_socket()) { + Xowa_tcp_console.Sleep(100); + } + byte[] buffer = msg.getBytes(); + output_stream.write(buffer, 0, buffer.length); + Close_socket(); + } + catch (Exception e) { + Xowa_tcp_console.Print_error(e); + } + } + public void Close_socket() { + try { + output_stream.close(); + socket.close(); + } + catch (Exception e) { + Xowa_tcp_console.Print_error(e); + } + } +} +class Xowa_tcp_receiver implements Runnable { + private int port; + private int max_length; + private ServerSocket server_socket; + private Socket client_socket; + private InputStream input_stream; + public Xowa_tcp_receiver(int port, int max_length) { + this.port = port; + this.max_length = max_length; + } + public void run() { + try { + // initialization + server_socket = new ServerSocket(port); + client_socket = server_socket.accept(); + // client_socket.setSoTimeout(10000); + byte[] buffer = new byte[65536]; + input_stream = client_socket.getInputStream(); + + // read incoming messages + int read = 0; + while (true) { + String msg = ""; + // read header + int body_len_max = 0, body_len_cur = 0; + read = input_stream.read(buffer, 0, 24); + if (read == -1) break; + String body_len_max_str = new String(buffer, 2, 10); + body_len_max = Integer.parseInt(body_len_max_str); + buffer = new byte[body_len_max]; + + // read rest of body + while (body_len_cur < body_len_max) { + read = input_stream.read(buffer); + if (read == -1) break; + body_len_cur += read; + msg += new String(buffer, 0, read); + } + + int msg_length = msg.length(); + if (msg_length > max_length) msg_length = max_length; + Xowa_tcp_console.Print_message_line(msg.substring(0, msg_length)); + Xowa_tcp_console.Print_message("\n\n> "); + } + this.Close_socket(); + } + catch (Exception e) { + Xowa_tcp_console.Print_error(e); + } + } + public void Close_socket() { + try { + input_stream.close(); + client_socket.close(); + server_socket.close(); + } + catch (Exception e) { +// Xowa_tcp_console.Print_error(e); // ignore, else error will print in console + } + } +} diff --git a/400_xowa/src/gplx/xowa/apps/setups/Xoa_setup_mgr.java b/400_xowa/src/gplx/xowa/apps/setups/Xoa_setup_mgr.java index a27517de8..28258f499 100644 --- a/400_xowa/src/gplx/xowa/apps/setups/Xoa_setup_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/setups/Xoa_setup_mgr.java @@ -13,3 +13,62 @@ 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.apps.setups; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.xowa.apps.versions.*; import gplx.core.envs.*; +public class Xoa_setup_mgr { + public static void Launch(Xoae_app app) { + Delete_old_files(app); + gplx.xowa.addons.apps.cfgs.upgrades.Xocfg_upgrade_mgr.Upgrade_cfg_0(app); + Run_cfg_for_os(app); + } + private static void Delete_old_files(Xoae_app app) { + String version_previous = gplx.xowa.guis.views.Xog_startup_tabs.Version_previous(app); + Gfo_usr_dlg usr_dlg = app.Usr_dlg(); + Io_url root_dir = app.Fsys_mgr().Root_dir(); + Delete_old_dir(usr_dlg, version_previous, "1.8.2.1" , root_dir.GenSubDir_nest("user", "anonymous", "lang")); + Delete_old_dir(usr_dlg, version_previous, "1.8.2.1" , root_dir.GenSubDir_nest("user", "anonymous", "wiki", "#cfg")); + Delete_old_dir(usr_dlg, version_previous, "1.10.2.1" , root_dir.GenSubDir_nest("bin", "any", "javascript")); + Delete_old_dir(usr_dlg, version_previous, "1.10.2.1" , root_dir.GenSubDir_nest("bin", "any", "xowa", "html", "modules")); + } + public static void Delete_old_dir(Gfo_usr_dlg usr_dlg, String version_prv, String version_del, Io_url dir) { // TEST: + if (Xoa_version_.Compare(version_prv, version_del) != CompareAble_.Less) return; + usr_dlg.Log_many("", "", "setup:checking if dir exists: version_prv=~{0} version_del=~{1} dir=~{2}", version_prv, version_del, dir.Raw()); + if (!Io_mgr.Instance.ExistsDir(dir)) return; + usr_dlg.Log_many("", "", "setup:deleting dir", version_prv, version_del, dir.Raw()); + Io_mgr.Instance.DeleteDirDeep(dir); + } + private static void Run_cfg_for_os(Xoae_app app) { + // exit if wnt or drd + byte op_sys_tid = Op_sys.Cur().Tid(); + switch (op_sys_tid) { + case Op_sys.Tid_drd: + case Op_sys.Tid_wnt: return; + } + + // get list of OS for which script has been run; exit if run + String Cfg__os_script_list = "xowa.app.setup.os_script_list"; + String op_sys_name = Xoa_app_.Op_sys_str; + String setup_completed = app.Cfg().Get_str_app_or(Cfg__os_script_list, ""); + String[] plats_ary = String_.Split(setup_completed, ";"); + int plats_ary_len = plats_ary.length; + for (int i = 0; i < plats_ary_len; i++) { + if (String_.Eq(plats_ary[i], op_sys_name)) return; + } + + // run script_fil + Io_url script_fil = app.Fsys_mgr().Root_dir().GenSubFil_nest("bin", op_sys_name, "xowa", "script", "setup_lua.sh"); + String exe = "sh"; + String arg = String_.Format("\"{0}\" \"{1}\"", script_fil.Raw(), app.Fsys_mgr().Root_dir()); + boolean pass = false; String fail = ""; + try {pass = new Process_adp().Exe_url_(Io_url_.new_fil_(exe)).Args_str_(arg).Run_wait_sync().Exit_code_pass();} + catch (Exception e) { + fail = Err_.Message_gplx_full(e); + } + if (!pass) + app.Usr_dlg().Prog_many("", "", "process exec failed: ~{0} ~{1} ~{2}", exe, arg, fail); + + // update cfg + setup_completed += op_sys_name + ";"; + app.Cfg().Set_str_app(Cfg__os_script_list, setup_completed); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/setups/Xoa_setup_mgr_tst.java b/400_xowa/src/gplx/xowa/apps/setups/Xoa_setup_mgr_tst.java index a27517de8..38ee53348 100644 --- a/400_xowa/src/gplx/xowa/apps/setups/Xoa_setup_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/apps/setups/Xoa_setup_mgr_tst.java @@ -13,3 +13,22 @@ 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.apps.setups; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import org.junit.*; +public class Xoa_setup_mgr_tst { + @Before public void init() {fxt.Clear();} private Xoa_setup_mgr_fxt fxt = new Xoa_setup_mgr_fxt(); + @Test public void Compare() { + fxt.Test_delete_old_dir("mem/dir/", "1.8.1.1" , "1.8.2.1", Bool_.Y); // version is earlier than checkpoint; delete + fxt.Test_delete_old_dir("mem/dir/", "1.8.2.1" , "1.8.2.1", Bool_.N); // version is not earlier than checkpoint; don't delete + fxt.Test_delete_old_dir("mem/dir/", "" , "1.8.2.1", Bool_.Y); // version is empty; delete; + } +} +class Xoa_setup_mgr_fxt { + public void Clear() {} + public void Test_delete_old_dir(String dir_str, String version_prv, String version_del, boolean expd) { + Io_url dir = Io_url_.new_fil_(dir_str); + Io_mgr.Instance.CreateDirIfAbsent(dir); + Xoa_setup_mgr.Delete_old_dir(Gfo_usr_dlg_.Noop, version_prv, version_del, dir); + Tfds.Eq(expd, !Io_mgr.Instance.ExistsDir(dir), version_prv + "|" + version_del); + } +} diff --git a/400_xowa/src/gplx/xowa/apps/shells/Gfo_log__console.java b/400_xowa/src/gplx/xowa/apps/shells/Gfo_log__console.java index a27517de8..3987a52ee 100644 --- a/400_xowa/src/gplx/xowa/apps/shells/Gfo_log__console.java +++ b/400_xowa/src/gplx/xowa/apps/shells/Gfo_log__console.java @@ -13,3 +13,18 @@ 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.apps.shells; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.core.logs.*; +public class Gfo_log__console extends Gfo_log__base { + @Override public void Exec(byte type, long time, long elapsed, String msg, Object[] args) { + switch (type) { + case Gfo_log_itm.Type__prog: Gfo_usr_dlg_.Instance.Prog_many("", "", gplx.core.errs.Err_msg.To_str(msg, args)); break; + case Gfo_log_itm.Type__warn: Gfo_usr_dlg_.Instance.Warn_many("", "", gplx.core.errs.Err_msg.To_str(msg, args)); break; + case Gfo_log_itm.Type__note: Gfo_usr_dlg_.Instance.Note_many("", "", gplx.core.errs.Err_msg.To_str(msg, args)); break; + case Gfo_log_itm.Type__info: Gfo_usr_dlg_.Instance.Log_many ("", "", gplx.core.errs.Err_msg.To_str(msg, args)); break; + default: throw Err_.new_unhandled_default(type); + } + } + @Override public List_adp Itms() {return itms;} @Override public Gfo_log Itms_(List_adp v) {itms = v; return this;} private List_adp itms; + @Override public void Flush() {itms.Clear();} +} diff --git a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_itm__base.java b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_itm__base.java index a27517de8..92e0f2acd 100644 --- a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_itm__base.java +++ b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_itm__base.java @@ -13,3 +13,23 @@ 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.apps.site_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.langs.jsons.*; +import gplx.xowa.bldrs.wms.sites.*; +public abstract class Xoa_site_cfg_itm__base { + public void Ctor(String key_str) { + this.key_str = key_str; this.key_bry = Bry_.new_u8(key_str); + } + public String Key_str() {return key_str;} private String key_str; + public byte[] Key_bry() {return key_bry;} private byte[] key_bry; + public byte[] Parse_json(Xow_wiki wiki, Json_itm js_itm) { + Json_ary ary = Json_ary.cast(js_itm); + Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_b512(); + int len = ary.Len(); + for (int i = 0; i < len; ++i) + Parse_json_ary_itm(bfr, wiki, i, ary.Get_at(i)); + return bfr.To_bry_and_rls(); + } + public abstract void Parse_json_ary_itm(Bry_bfr bfr, Xow_wiki wiki, int i, Json_itm itm); + public abstract void Exec_csv(Xow_wiki wiki, int loader_tid, byte[] dsv_bry); +} diff --git a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_itm__extensiontags.java b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_itm__extensiontags.java index a27517de8..52ca96279 100644 --- a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_itm__extensiontags.java +++ b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_itm__extensiontags.java @@ -13,3 +13,34 @@ 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.apps.site_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; +import gplx.langs.jsons.*; +class Xoa_site_cfg_itm__extensiontags extends Xoa_site_cfg_itm__base { + public Xoa_site_cfg_itm__extensiontags() { + this.Ctor(Xoa_site_cfg_loader__inet.Qarg__extensiontags); + } + @Override public void Parse_json_ary_itm(Bry_bfr bfr, Xow_wiki wiki, int i, Json_itm itm) { + byte[] tag = itm.Data_bry(); + if (i != 0) bfr.Add_byte_nl(); + int idx_last = tag.length - 1; + if ( tag.length < 3 + || tag[0] != Byte_ascii.Angle_bgn + || tag[idx_last] != Byte_ascii.Angle_end + ) + throw Err_.new_("site_meta", "invalid extensiontag", "tag", tag); + bfr.Add_mid(tag, 1, idx_last); // convert "

    " to "pre"
    +	}
    +	@Override public void Exec_csv(Xow_wiki wiki, int loader_tid, byte[] dsv_bry) {
    +		Hash_adp_bry hash = null;
    +		if (loader_tid != Xoa_site_cfg_loader_.Tid__fallback) {	// fallback will result in null hash which will result in loading all extensions
    +			hash = Hash_adp_bry.ci_a7();						// NOTE: must be case-insensitive; EX:  vs 
    +			byte[][] lines = Bry_split_.Split_lines(dsv_bry);
    +			int lines_len = lines.length;
    +			for (int i = 0; i < lines_len; ++i) {
    +				byte[] line = lines[i]; if (Bry_.Len_eq_0(line)) continue;	// ignore blank lines
    +				hash.Add(line, line);
    +			}
    +		}
    +		wiki.Mw_parser_mgr().Xnde_tag_regy().Init_by_meta(hash);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_itm__interwikimap.java b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_itm__interwikimap.java
    index a27517de8..30fb9bd51 100644
    --- a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_itm__interwikimap.java
    +++ b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_itm__interwikimap.java
    @@ -13,3 +13,111 @@ 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.apps.site_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import gplx.core.net.*; import gplx.langs.jsons.*; import gplx.xowa.apps.gfs.*;
    +import gplx.xowa.langs.*;
    +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.xwikis.*;
    +class Xoa_site_cfg_itm__interwikimap extends Xoa_site_cfg_itm__base {
    +	private final    Bry_bfr tmp_bfr = Bry_bfr_.New();
    +	private final    Gfo_url_parser url_parser = new Gfo_url_parser();
    +	public Xoa_site_cfg_itm__interwikimap() {this.Ctor(Xoa_site_cfg_loader__inet.Qarg__interwikimap);}
    +	@Override public void Parse_json_ary_itm(Bry_bfr bfr, Xow_wiki wiki, int i, Json_itm itm) {
    +		Json_nde nde = Json_nde.cast(itm);
    +		if (i != 0) bfr.Add_byte_nl();
    +		byte[] iw_key = nde.Get_bry_or_null("prefix");	if (iw_key == null) throw Err_.new_("site_meta", "invalid interwiki", "key", iw_key);
    +		byte[] iw_url = nde.Get_bry_or_null("url");		if (iw_url == null) throw Err_.new_("site_meta", "invalid interwiki", "url", iw_key);
    +		bfr.Add(iw_key).Add_byte_pipe().Add(Gfs_php_converter.To_gfs(tmp_bfr, iw_url));
    +	}
    +	@Override public void Exec_csv(Xow_wiki wiki, int loader_tid, byte[] dsv_bry) {
    +		if (loader_tid == Xoa_site_cfg_loader_.Tid__fallback)
    +			Exec_csv__fallback(wiki);
    +		else {
    +			byte[][] lines = Bry_split_.Split_lines(dsv_bry);
    +			int lines_len = lines.length;
    +			for (int i = 0; i < lines_len; ++i) {
    +				byte[] line = lines[i]; if (Bry_.Len_eq_0(line)) continue;	// ignore blank lines
    +				byte[][] flds = Bry_split_.Split(line, Byte_ascii.Pipe);
    +				byte[] url_fmt = flds[1];
    +				byte[] domain_bry = Xow_xwiki_mgr.Get_domain_from_url(url_parser, url_fmt);
    +				wiki.Xwiki_mgr().Add_by_site_interwikimap
    +					( flds[0], domain_bry, url_fmt
    +					, Bry_.Replace(url_fmt, Arg0_xo, Arg0_wm) // NOTE: interwiki items are stored in wiki.core.xowa_cfg as https://en.wikipedia.org/wiki/~{0}
    +					);
    +			}
    +		}
    +	}
    +	private void Exec_csv__fallback(Xow_wiki wiki) {
    +		Xow_xwiki_mgr xwiki_mgr = wiki.Xwiki_mgr();
    +		int domain_tid = wiki.Domain_tid();
    +		xwiki_mgr.Add_by_csv(Csv__manual);	// adds manual xwikis that should exist in all wikis; EX: 'commons', 'wikidata', 'oldwikisource', 'wmf'
    +		switch (domain_tid) {
    +			case Xow_domain_tid_.Tid__wikipedia: case Xow_domain_tid_.Tid__wiktionary:	case Xow_domain_tid_.Tid__wikisource:	case Xow_domain_tid_.Tid__wikivoyage:
    +			case Xow_domain_tid_.Tid__wikiquote: case Xow_domain_tid_.Tid__wikibooks:	case Xow_domain_tid_.Tid__wikiversity:	case Xow_domain_tid_.Tid__wikinews:
    +				xwiki_mgr.Add_by_sitelink_mgr();									// lang: EX: [[fr:]]  -> fr.wikipedia.org
    +				xwiki_mgr.Add_by_csv(Csv__peers__lang);								// peer: EX: [[wikt]] -> en.wiktionary.org
    +				break;
    +			case Xow_domain_tid_.Tid__commons:		case Xow_domain_tid_.Tid__wikidata:
    +			case Xow_domain_tid_.Tid__wikimedia:	case Xow_domain_tid_.Tid__species:	case Xow_domain_tid_.Tid__meta: case Xow_domain_tid_.Tid__incubator:
    +			case Xow_domain_tid_.Tid__mediawiki:	case Xow_domain_tid_.Tid__wmfblog:
    +			case Xow_domain_tid_.Tid__home:			case Xow_domain_tid_.Tid__other:
    +				xwiki_mgr.Add_by_sitelink_mgr(Xow_domain_tid_.Tid__wikipedia);		// lang: hardcode to wikipedia; EX: "[[en:]] -> "en.wikipedia.org"
    +				xwiki_mgr.Add_by_csv(Csv__peers__english);							// peer: hardcode to english
    +				break;
    +		}
    +
    +		// wikivoyage
    +		switch (domain_tid) {
    +			case Xow_domain_tid_.Tid__wikivoyage: case Xow_domain_tid_.Tid__home:	// NOTE: home needed for diagnostic; DATE:2015-10-13
    +				xwiki_mgr.Add_by_csv(Csv__wikivoyage);
    +				break;
    +		}
    +
    +		// if simplewiki, add "w" -> "enwiki"
    +		if	(Bry_.Eq(wiki.Domain_bry(), Xow_domain_itm_.Bry__simplewiki))	
    +			xwiki_mgr.Add_by_csv(Csv__enwiki);
    +	}
    +	private static final    byte[] 
    +	  Csv__manual = Bry_.new_a7(String_.Concat_lines_nl_skip_last
    +	( "1|commons;c|commons.wikimedia.org|Commons"
    +	, "1|m;metawikipedia|meta.wikipedia.org"
    +	, "1|species;wikispecies|species.wikimedia.org"
    +	, "1|d;wikidata|www.wikidata.org"
    +	, "1|mw;mediawikiwiki|www.mediawiki.org"
    +	, "1|wmf;wikimedia;foundation|wikimediafoundation.org"
    +	, "1|incubator|incubator.wikimedia.org"
    +	, "0|oldwikisource|https://wikisource.org/wiki/~{0}|Old Wikisoure"
    +	, "0|mail|https://lists.wikimedia.org/mailman/listinfo/~{0}|Wikitech Mailing List"
    +	))
    +	, Csv__peers__lang = Bry_.new_a7(String_.Concat_lines_nl_skip_last
    +	( "2|w;wikipedia|wikipedia"
    +	, "2|wikt;wiktionary|wiktionary"
    +	, "2|s;wikisource|wikisource"
    +	, "2|b;wikibooks|wikibooks"
    +	, "2|v;wikiversity|wikiversity"
    +	, "2|q;wikiquote|wikiquote"
    +	, "2|n;wikinews|wikinews"
    +	, "2|voy;wikivoyage|wikivoyage"
    +	))
    +	, Csv__peers__english = Bry_.new_a7(String_.Concat_lines_nl_skip_last
    +	( "1|w|en.wikipedia.org"
    +	, "1|wikt|en.wiktionary.org"
    +	, "1|s|en.wikisource.org"
    +	, "1|b|en.wikibooks.org"
    +	, "1|v|en.wikiversity.org"
    +	, "1|q|en.wikiquote.org"
    +	, "1|n|en.wikinews.org"
    +	, "1|voy|en.wikivoyage.org"
    +	))
    +	, Csv__wikivoyage = Bry_.new_a7(String_.Concat_lines_nl_skip_last
    +	( "0|commons|commons.wikimedia.org|Wikimedia Commons"
    +	, "2|wikipedia|wikipedia|Wikipedia"
    +	, "0|dmoz|http://www.dmoz.org/~{0}|DMOZ"
    +	))
    +	, Csv__enwiki = Bry_.new_a7("2|w|wikipedia")
    +	;
    +	private static final    byte[]
    +	  Arg0_xo = Bry_.new_a7("~{0}")
    +	, Arg0_wm = Bry_.new_a7("$1")
    +	;
    +}
    +
    diff --git a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader.java b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader.java
    index a27517de8..c6221d5f5 100644
    --- a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader.java
    +++ b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader.java
    @@ -13,3 +13,22 @@ 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.apps.site_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +public interface Xoa_site_cfg_loader {
    +	int			Tid();
    +	void		Load_csv__bgn(Xoa_site_cfg_mgr mgr, Xow_wiki wiki);
    +	byte[]		Load_csv(Xoa_site_cfg_mgr mgr, Xow_wiki wiki, Xoa_site_cfg_itm__base itm);
    +}
    +class Xoa_site_cfg_loader_ {
    +	public static final int Tid__null = -1, Tid__cfg = 0, Tid__fsys = 1, Tid__inet = 2, Tid__fallback = 3;
    +	private static final String Key__cfg = "cfg", Key__fsys = "fsys", Key__inet = "inet", Key__fallback = "itm";
    +	public static String Get_key(int tid) {
    +		switch (tid) {
    +			case Tid__cfg:			return Key__cfg;
    +			case Tid__fsys:			return Key__fsys;
    +			case Tid__inet:			return Key__inet;
    +			case Tid__fallback:		return Key__fallback;
    +			default:				throw Err_.new_unhandled(tid);
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader__cfg.java b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader__cfg.java
    index a27517de8..e11601a36 100644
    --- a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader__cfg.java
    +++ b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader__cfg.java
    @@ -13,3 +13,30 @@ 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.apps.site_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import gplx.dbs.cfgs.*;
    +class Xoa_site_cfg_loader__db implements Xoa_site_cfg_loader {
    +	private Db_cfg_tbl cfg_tbl;
    +	public int Tid() {return Xoa_site_cfg_loader_.Tid__cfg;}
    +	public void Load_csv__bgn(Xoa_site_cfg_mgr mgr, Xow_wiki wiki) {
    +		this.cfg_tbl = wiki.Data__core_mgr().Tbl__cfg();
    +		cfg_tbl.Select_as_hash_bry(mgr.Data_hash(), Grp__xowa_wm_api);
    +	}
    +	public byte[] Load_csv(Xoa_site_cfg_mgr mgr, Xow_wiki wiki, Xoa_site_cfg_itm__base itm) {
    +		byte[] rv = (byte[])mgr.Data_hash().Get_by_bry(itm.Key_bry()); if (rv == null) return null;
    +		int meta_end = Bry_find_.Find_fwd(rv, Byte_ascii.Nl);
    +		if (meta_end == Bry_find_.Not_found) {// fallback will only log one line; ignore; EX: //#xowa|fallback
    +			return null;
    +		}
    +		return Bry_.Mid(rv, meta_end + 1);
    +	}
    +	public void Save_bry(int loader_tid, String db_key, byte[] val) {
    +		byte[] meta = Bry_.new_a7(Bld_meta(loader_tid));
    +		byte[] data = Bry_.Len_eq_0(val) ? meta : Bry_.Add(meta, Byte_ascii.Nl_bry, val);
    +		cfg_tbl.Upsert_bry(Grp__xowa_wm_api, db_key, data);
    +	}
    +	public static String Bld_meta(int loader_tid) {
    +		return String_.Format("//#xowa|{0}|{1}|{2}", Xoa_app_.Version, Xoa_site_cfg_loader_.Get_key(loader_tid), Datetime_now.Get().XtoUtc().XtoStr_fmt_yyyyMMdd_HHmmss());
    +	}
    +	public static final String Grp__xowa_wm_api = "xowa.site_cfg";
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader__fallback.java b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader__fallback.java
    index a27517de8..f48c47b19 100644
    --- a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader__fallback.java
    +++ b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader__fallback.java
    @@ -13,3 +13,9 @@ 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.apps.site_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +class Xoa_site_cfg_loader__fallback implements Xoa_site_cfg_loader {
    +	public int			Tid() {return Xoa_site_cfg_loader_.Tid__fallback;}
    +	public void			Load_csv__bgn(Xoa_site_cfg_mgr mgr, Xow_wiki wiki) {}
    +	public byte[]		Load_csv(Xoa_site_cfg_mgr mgr, Xow_wiki wiki, Xoa_site_cfg_itm__base itm) {return Bry_.Empty;}	// return non-null so "data != null" check works
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader__fsys.java b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader__fsys.java
    index a27517de8..ac605187b 100644
    --- a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader__fsys.java
    +++ b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader__fsys.java
    @@ -13,3 +13,16 @@ 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.apps.site_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import gplx.xowa.apps.fsys.*;
    +class Xoa_site_cfg_loader__fsys implements Xoa_site_cfg_loader {
    +	private final Io_url site_cfg_dir;
    +	public Xoa_site_cfg_loader__fsys(Io_url site_cfg_dir) {this.site_cfg_dir = site_cfg_dir;}
    +	public int Tid() {return Xoa_site_cfg_loader_.Tid__fsys;}
    +	public void Load_csv__bgn(Xoa_site_cfg_mgr mgr, Xow_wiki wiki) {}
    +	public byte[] Load_csv(Xoa_site_cfg_mgr mgr, Xow_wiki wiki, Xoa_site_cfg_itm__base itm) {
    +		return Io_mgr.Instance.LoadFilBryOrNull(Make_load_url(wiki.Domain_str(), itm.Key_str()));
    +	}
    +	public Io_url Make_load_url(String wiki, String key) {return site_cfg_dir.GenSubFil_nest(key, key + "-" + wiki + ".csv");}
    +	public static Xoa_site_cfg_loader__fsys new_(Xoa_app app) {return new Xoa_site_cfg_loader__fsys(app.Fsys_mgr().Bin_xowa_dir().GenSubDir_nest("cfg", "wiki", "api"));}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader__inet.java b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader__inet.java
    index a27517de8..ba54da579 100644
    --- a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader__inet.java
    +++ b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_loader__inet.java
    @@ -13,3 +13,52 @@ 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.apps.site_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import gplx.core.net.*; import gplx.xowa.bldrs.wms.*;
    +import gplx.langs.jsons.*;
    +import gplx.xowa.wikis.domains.*;
    +public class Xoa_site_cfg_loader__inet implements Xoa_site_cfg_loader {
    +	private final    Gfo_inet_conn inet_conn; private final    Json_parser json_parser;
    +	private String api_url; private boolean call_api = true; private Json_doc jdoc;
    +	private final    Bry_bfr tmp_bfr = Bry_bfr_.New();
    +	public Xoa_site_cfg_loader__inet(Gfo_inet_conn inet_conn, Json_parser json_parser) {this.inet_conn = inet_conn; this.json_parser = json_parser;}
    +	public int Tid() {return Xoa_site_cfg_loader_.Tid__inet;}
    +	public void Load_csv__bgn(Xoa_site_cfg_mgr mgr, Xow_wiki wiki) {
    +		this.call_api = true;
    +		this.jdoc = null;
    +		this.api_url = Bld_url(tmp_bfr, wiki.Domain_str(), mgr.Data_hash(), mgr.Itm_ary());
    +	}
    +	public String Api_url() {return api_url;}
    +	public byte[] Load_csv(Xoa_site_cfg_mgr mgr, Xow_wiki wiki, Xoa_site_cfg_itm__base itm) {
    +		if (Int_.In(wiki.Domain_tid(), Xow_domain_tid_.Tid__home, Xow_domain_tid_.Tid__other)) return null;	// do not call api if "home", "other"; for "wikia" and other non-wm wikis
    +		if (call_api) {
    +			call_api = false;
    +			byte[] json_bry = Xowm_api_mgr.Call_by_url(Xoa_app_.Usr_dlg(), inet_conn, wiki.Domain_str(), api_url);
    +			if (json_bry != null) jdoc = json_parser.Parse(json_bry);
    +		}
    +		if (jdoc == null) return null;
    +		Json_itm js_itm = jdoc.Get_grp_many(Bry__query, itm.Key_bry()); if (js_itm == null) return null;
    +		return itm.Parse_json(wiki, js_itm);
    +	}
    +	public static String Bld_url(Bry_bfr tmp_bfr, String domain_str, Hash_adp_bry db_hash, Xoa_site_cfg_itm__base[] itm_ary) {
    +		boolean first = true;
    +		int len = itm_ary.length;
    +		for (int i = 0; i < len; ++i) {
    +			Xoa_site_cfg_itm__base itm = itm_ary[i];
    +			// if (db_hash.Has(itm_key)) continue;	// TOMBSTONE: always add itm to url, even if in db; note that fallback gets saved to db; DATE:2016-04-13
    +			if (first)
    +				first = false;
    +			else
    +				tmp_bfr.Add_byte_pipe();
    +			tmp_bfr.Add(itm.Key_bry());
    +		}			
    +		return first ? null : Xowm_api_mgr.Bld_api_url(domain_str, Qarg__bgn + tmp_bfr.To_str_and_clear());
    +	}
    +	private static final    byte[] Bry__query = Bry_.new_a7("query");
    +	public static final String
    +	  Qarg__all							= "action=query&format=json&rawcontinue=&meta=siteinfo&siprop=general|namespaces|statistics|interwikimap|namespacealiases|specialpagealiases|libraries|extensions|skins|magicwords|functionhooks|showhooks|extensiontags|protocols|defaultoptions|languages"
    +	, Qarg__bgn							= "action=query&format=json&rawcontinue=&meta=siteinfo&siprop="
    +	, Qarg__extensiontags				= "extensiontags"
    +	, Qarg__interwikimap				= "interwikimap"
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_mgr.java b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_mgr.java
    index a27517de8..4109958c7 100644
    --- a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_mgr.java
    +++ b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_mgr.java
    @@ -13,3 +13,55 @@ 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.apps.site_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import gplx.xowa.bldrs.wms.sites.*;
    +public class Xoa_site_cfg_mgr {
    +	private final    Xoa_site_cfg_loader__db loader__db = new Xoa_site_cfg_loader__db();		
    +	public Xoa_site_cfg_mgr(Xoa_app app) {
    +		this.itm_ary = new Xoa_site_cfg_itm__base[]
    +		{ new Xoa_site_cfg_itm__extensiontags()
    +		, new Xoa_site_cfg_itm__interwikimap()
    +		};
    +		this.loader_ary = new Xoa_site_cfg_loader[] 
    +		{ loader__db
    +		, Xoa_site_cfg_loader__fsys.new_(app)
    +		, new Xoa_site_cfg_loader__inet(app.Utl__inet_conn(), app.Utl__json_parser())
    +		, new Xoa_site_cfg_loader__fallback()
    +		};
    +	}
    +	public Xoa_site_cfg_loader[]		Loader_ary() {return loader_ary;} private final    Xoa_site_cfg_loader[] loader_ary;
    +	public Xoa_site_cfg_itm__base[]		Itm_ary() {return itm_ary;} private final    Xoa_site_cfg_itm__base[] itm_ary;
    +	public Hash_adp_bry					Data_hash() {return data_hash;} private final    Hash_adp_bry data_hash = Hash_adp_bry.cs();
    +	public void Init_loader_bgn(Xow_wiki wiki) {
    +		data_hash.Clear();	
    +		int loader_len = loader_ary.length;
    +		for (int i = 0; i < loader_len; ++i)
    +			loader_ary[i].Load_csv__bgn(this, wiki);
    +	}
    +	public void Load(Xow_wiki wiki) {
    +		if (wiki.Data__core_mgr() == null) return;	// TEST:
    +		synchronized (loader__db) {	// THREAD: data_hash; loader_ary
    +			this.Init_loader_bgn(wiki);
    +			int len = itm_ary.length;
    +			for (int i = 0; i < len; ++i) {
    +				Xoa_site_cfg_itm__base itm = itm_ary[i];
    +				Load_by_loader(wiki, itm);
    +			}
    +		}
    +	}
    +	private void Load_by_loader(Xow_wiki wiki, Xoa_site_cfg_itm__base itm) {
    +		int len = loader_ary.length;
    +		for (int i = 0; i < len; ++i) {
    +			Xoa_site_cfg_loader loader = loader_ary[i];
    +			try {// guard against individual loader failing, particularly inet
    +				byte[] data = loader.Load_csv(this, wiki, itm);
    +				if (data != null) {
    +					if (loader.Tid() != Xoa_site_cfg_loader_.Tid__cfg) loader__db.Save_bry(loader.Tid(), itm.Key_str(), data);
    +					Xoa_app_.Usr_dlg().Log_many("", "", "site_cfg loaded; wiki=~{0} itm=~{1} tid=~{2}", wiki.Domain_str(), itm.Key_bry(), Xoa_site_cfg_loader_.Get_key(loader.Tid()));
    +					itm.Exec_csv(wiki, loader.Tid(), data);
    +					break;
    +				}
    +			} catch (Exception e) {Xoa_app_.Usr_dlg().Warn_many("", "", "error while loading site_cfg; wiki=~{0} itm=~{1} err=~{2}", wiki.Domain_str(), itm.Key_bry(), Err_.Message_gplx_log(e));}
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_mgr_tst.java b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_mgr_tst.java
    index a27517de8..b087cf35a 100644
    --- a/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_mgr_tst.java
    +++ b/400_xowa/src/gplx/xowa/apps/site_cfgs/Xoa_site_cfg_mgr_tst.java
    @@ -13,3 +13,166 @@ 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.apps.site_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import org.junit.*;
    +import gplx.core.btries.*;
    +import gplx.dbs.cfgs.*;
    +import gplx.langs.jsons.*; import gplx.xowa.wikis.nss.*;
    +import gplx.xowa.parsers.*; import gplx.xowa.bldrs.wms.*;
    +public class Xoa_site_cfg_mgr_tst {
    +	private final    Xoa_site_cfg_mgr_fxt fxt = new Xoa_site_cfg_mgr_fxt();
    +	@Before		public void init() {fxt.Init();}
    +	@After  public void term() {fxt.Term();}
    +	@Test  public void Extensiontags__cfg() {
    +		fxt.Init_db(Xoa_site_cfg_loader__inet.Qarg__extensiontags, fxt.Make_data(Xoa_site_cfg_loader_.Tid__inet, 1, "math", "source"));
    +		fxt.Exec_load();
    +		fxt.Test_extensiontags_y("math"	, "source");
    +		fxt.Test_extensiontags_n("ref"	, "graph");
    +		fxt.Test_extensiontags_y("translate", "languages");	// always whitelist , DATE:2015-10-13
    +	}
    +	@Test  public void Extensiontags__fsys() {
    +		fxt.Init_fsys(Xoa_site_cfg_loader__inet.Qarg__extensiontags, fxt.Make_data(Xoa_site_cfg_loader_.Tid__null, 1, "math", "source"));
    +		fxt.Exec_load();
    +		fxt.Test_extensiontags_y("math", "source");
    +		fxt.Test_extensiontags_n("ref"	, "graph");
    +		fxt.Test_db(Xoa_site_cfg_loader__inet.Qarg__extensiontags, fxt.Make_data(Xoa_site_cfg_loader_.Tid__fsys, 1, "math", "source"));
    +	}
    +	@Test  public void Extensiontags__inet() {
    +		fxt.Init_inet(fxt.Make_api(fxt.Make_api_extensiontags("math", "source")));
    +		fxt.Exec_load();
    +		fxt.Test_extensiontags_y("math", "source");
    +		fxt.Test_extensiontags_n("ref"	, "graph");
    +		fxt.Test_db(Xoa_site_cfg_loader__inet.Qarg__extensiontags, fxt.Make_data(Xoa_site_cfg_loader_.Tid__inet, 1, "math", "source"));
    +	}
    +	@Test  public void Extensiontags__fallback() {
    +		fxt.Exec_load();
    +		fxt.Test_db(Xoa_site_cfg_loader__inet.Qarg__extensiontags, fxt.Make_data(Xoa_site_cfg_loader_.Tid__fallback, 1));
    +		fxt.Test_extensiontags_y("math", "source", "ref", "graph");
    +	}
    +	@Test  public void Interwikimap__inet() {
    +		fxt.Init_inet(fxt.Make_api(fxt.Make_api_interwikimap("w", "https://en.wikipedia.org", "c", "https://commons.wikimedia.org")));
    +		fxt.Exec_load();
    +		fxt.Test_db(Xoa_site_cfg_loader__inet.Qarg__interwikimap, fxt.Make_data(Xoa_site_cfg_loader_.Tid__inet, 2, "w", "https://en.wikipedia.org", "c", "https://commons.wikimedia.org"));
    +	}
    +//		@Test   public void Print() {
    +//			String s = fxt.Make_api(fxt.Make_api_interwikimap("k1", "v1", "k2", "v2"), fxt.Make_api_extensiontags2("k3", "v3", "k4", "v4"));
    +//            Tfds.Dbg(s);
    +//		}
    +}
    +class Xoa_site_cfg_mgr_fxt {
    +	private final    Xoae_app app; private final    Xowe_wiki wiki;
    +	private final    Xoa_site_cfg_mgr site_cfg_mgr;
    +	private final    Db_cfg_tbl cfg_tbl;
    +	private final    Json_printer printer = new Json_printer();
    +	private final    Bry_bfr tmp_bfr = Bry_bfr_.New();
    +	public Xoa_site_cfg_mgr_fxt() {
    +		// Xoa_app_.Usr_dlg_(Xoa_app_.New__usr_dlg__console());
    +		gplx.core.ios.IoEngine_system.Web_access_enabled = true;	// HACK: must manually enable web_access else above tests will fail due to some other test disabling singleton; DATE:2016-12-15
    +		Xoa_test_.Inet__init();
    +		this.app = Xoa_app_fxt.Make__app__edit();
    +		this.wiki = Xoa_app_fxt.Make__wiki__edit(app);
    +		Xoa_test_.Init__db__edit(wiki);
    +		this.cfg_tbl = wiki.Data__core_mgr().Tbl__cfg();
    +		this.site_cfg_mgr = app.Site_cfg_mgr();
    +	}
    +	public void Init() {
    +		Datetime_now.Manual_y_(); Datetime_now.Autoincrement_n_();
    +		Io_mgr.Instance.InitEngine_mem();
    +		cfg_tbl.Delete_grp(Xoa_site_cfg_loader__db.Grp__xowa_wm_api);
    +		site_cfg_mgr.Init_loader_bgn(wiki);
    +		app.Utl__inet_conn().Clear();
    +	}
    +	public void Term() {
    +		Datetime_now.Manual_n_();
    +	}
    +	public void Init_db(String key, String data) {
    +		cfg_tbl.Assert_bry(Xoa_site_cfg_loader__db.Grp__xowa_wm_api, key, Bry_.new_u8(data));
    +	}
    +	public void Test_db(String key, String expd) {
    +		byte[] actl = cfg_tbl.Select_bry_or(Xoa_site_cfg_loader__db.Grp__xowa_wm_api, key, null);
    +		Tfds.Eq_str_lines(expd, String_.new_u8(actl));
    +	}
    +	public void Init_inet(String data) {
    +		String url = Xoa_site_cfg_loader__inet.Bld_url(tmp_bfr, wiki.Domain_str(), site_cfg_mgr.Data_hash(), site_cfg_mgr.Itm_ary());
    +		app.Utl__inet_conn().Upload_by_bytes(url, Bry_.new_u8(data));
    +	}
    +	public void Init_fsys(String key, String data) {
    +		Xoa_site_cfg_loader__fsys loader = Xoa_site_cfg_loader__fsys.new_(app);
    +		Io_url url = loader.Make_load_url(wiki.Domain_str(), key);
    +		Io_mgr.Instance.SaveFilStr(url, data);
    +	}
    +	public Xoa_site_cfg_mgr_fxt Exec_load() {
    +		site_cfg_mgr.Load(wiki);
    +		return this;
    +	}
    +	public void Test_extensiontags_y(String... ary) {Test_extensiontags(Bool_.Y, ary);}
    +	public void Test_extensiontags_n(String... ary) {Test_extensiontags(Bool_.N, ary);}
    +	public void Test_extensiontags(boolean expd_exists, String... ary) {
    +		Btrie_slim_mgr trie = wiki.Mw_parser_mgr().Xnde_tag_regy().Get_trie(Xop_parser_tid_.Tid__defn);
    +		int len = ary.length;
    +		for (int i = 0; i < len; ++i) {
    +			String str = ary[i];
    +			byte[] bry = Bry_.new_u8(str);
    +			boolean actl_exists = trie.Match_exact(bry, 0, bry.length) != null;
    +			Tfds.Eq_bool(expd_exists, actl_exists, str);
    +		}
    +	}
    +	public void Test_inet_qarg(String expd) {
    +		Xoa_site_cfg_loader__inet loader__inet = (Xoa_site_cfg_loader__inet)site_cfg_mgr.Loader_ary()[2];
    +		String api_url = loader__inet.Api_url();
    +		Tfds.Eq(expd, String_.Mid(api_url, String_.FindBwd(api_url, "=") + 1));
    +	}
    +	public String Make_api(byte[]... sections) {
    +		Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_b512();
    +		bfr.Add_str_a7("{'query':");
    +		int len = sections.length;
    +		bfr.Add_str_a7("{");
    +		for (int i = 0; i < len; ++i) {
    +			if (i != 0) bfr.Add_str_a7(",");
    +			bfr.Add(sections[i]);
    +		}
    +		bfr.Add_str_a7("}");
    +		bfr.Add_str_a7("}");
    +		return printer.Print_by_bry(Bry_.new_u8(Json_doc.Make_str_by_apos(bfr.To_str_and_rls()))).To_str();
    +	}
    +	public byte[] Make_api_interwikimap(String... ary) {
    +		Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_b512();
    +		bfr.Add_str_a7("'interwikimap':");
    +		int len = ary.length;
    +		bfr.Add_str_a7("[");
    +		for (int i = 0; i < len; i += 2) {
    +			if (i != 0) bfr.Add_str_a7(",");
    +			bfr.Add_str_a7("{'prefix':'"	+ ary[i] + "'");
    +			bfr.Add_str_a7(",'url':'"	+ ary[i + 1] + "'");
    +			bfr.Add_str_a7("}");
    +		}
    +		bfr.Add_str_a7("]");
    +		return bfr.To_bry_and_clear();
    +	}
    +	public byte[] Make_api_extensiontags(String... ary) {
    +		Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_b512();
    +		bfr.Add_str_a7("'extensiontags':");
    +		int len = ary.length;
    +		bfr.Add_str_a7("[");
    +		for (int i = 0; i < len; ++i) {
    +			if (i != 0) bfr.Add_str_a7(",");
    +			bfr.Add_str_a7("'<"	+ ary[i] + ">'");
    +		}
    +		bfr.Add_str_a7("]");
    +		return bfr.To_bry_and_clear();
    +	}
    +	public String Make_data(int loader_tid, int flds, String... ary) {
    +		Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_b512();
    +		if (loader_tid != Xoa_site_cfg_loader_.Tid__null)	// null when constructing data for fsys
    +			bfr.Add_str_u8(Xoa_site_cfg_loader__db.Bld_meta(loader_tid)).Add_byte_nl();
    +		int len = ary.length;
    +		for (int i = 0; i < len; i += flds) {
    +			if (i != 0) bfr.Add_byte_nl();
    +			for (int j = 0; j < flds; ++j) {
    +				if (j != 0) bfr.Add_byte_pipe();
    +				bfr.Add_str_u8(ary[i + j]);
    +			}
    +		}
    +		return bfr.To_str_and_rls();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/urls/Xoa_url__basic__tst.java b/400_xowa/src/gplx/xowa/apps/urls/Xoa_url__basic__tst.java
    index a27517de8..4cf1d1080 100644
    --- a/400_xowa/src/gplx/xowa/apps/urls/Xoa_url__basic__tst.java
    +++ b/400_xowa/src/gplx/xowa/apps/urls/Xoa_url__basic__tst.java
    @@ -13,3 +13,18 @@ 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.apps.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import org.junit.*;
    +public class Xoa_url__basic__tst {
    +	private final    Xoa_url_fxt fxt = new Xoa_url_fxt();
    +	@Test  public void Eq_page() {
    +		fxt.Test_eq_page(Bool_.Y, "en.wikipedia.org/wiki/A?redirect=yes", "en.wikipedia.org/wiki/A?redirect=yes");
    +		fxt.Test_eq_page(Bool_.N, "en.wikipedia.org/wiki/A?redirect=no"	, "en.wikipedia.org/wiki/A?redirect=yes");
    +	}
    +}
    +class Xoa_url_fxt extends Xow_url_parser_fxt { 	public void Test_eq_page(boolean expd, String lhs_str, String rhs_str) {
    +		Xoa_url lhs_url = parser.Parse(Bry_.new_u8(lhs_str));
    +		Xoa_url rhs_url = parser.Parse(Bry_.new_u8(rhs_str));
    +		Tfds.Eq_bool(expd, lhs_url.Eq_page(rhs_url), "Eq_page");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/urls/Xoa_url__to_str__tst.java b/400_xowa/src/gplx/xowa/apps/urls/Xoa_url__to_str__tst.java
    index a27517de8..abbfc06a4 100644
    --- a/400_xowa/src/gplx/xowa/apps/urls/Xoa_url__to_str__tst.java
    +++ b/400_xowa/src/gplx/xowa/apps/urls/Xoa_url__to_str__tst.java
    @@ -13,3 +13,42 @@ 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.apps.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import org.junit.*; import gplx.xowa.htmls.hrefs.*;
    +import gplx.xowa.wikis.nss.*;
    +public class Xoa_url__to_str__tst {
    +	private final    Xoa_url__to_str__fxt fxt = new Xoa_url__to_str__fxt();
    +	@Test   public void Http()				{fxt.Chk_to_str_href(Bool_.N, "http://a.org/b"						, "http://a.org/b");}
    +	@Test   public void File()				{fxt.Chk_to_str_href(Bool_.N, "file:///C/xowa/file/a.png"			, "file:///C/xowa/file/a.png");}
    +	@Test   public void Abrv__page()		{fxt.Chk_to_str_href(Bool_.N, "/wiki/A"								, "A");}
    +	@Test   public void Abrv__anch()		{fxt.Chk_to_str_href(Bool_.N, "#b"									, "#b");}
    +	@Test   public void Full__page()		{fxt.Chk_to_str_href(Bool_.Y, "/wiki/A"								, "en.wikipedia.org/wiki/A");}
    +	@Test   public void Full__anch()		{fxt.Chk_to_str_href(Bool_.Y, "#b"									, "en.wikipedia.org/wiki/Page_1#b");}
    +	@Test   public void Vnt() {
    +		Xowe_wiki zh_wiki = fxt.Prep_create_wiki("zh.wikipedia.org");
    +		gplx.xowa.langs.vnts.Xol_vnt_regy_fxt.Init__vnt_mgr(zh_wiki.Lang().Vnt_mgr(), 0, String_.Ary("zh-hans", "zh-hant"));
    +		fxt.Chk_to_str_href(zh_wiki, Bool_.Y, "/site/zh.wikipedia.org/zh-hans/A"	, "zh.wikipedia.org/zh-hans/A");
    +		fxt.Chk_to_str_href(zh_wiki, Bool_.Y, "/site/zh.wikipedia.org/zh-hant/A"	, "zh.wikipedia.org/zh-hant/A");
    +		fxt.Chk_to_str_href(zh_wiki, Bool_.Y, "/site/zh.wikipedia.org/zh-cn/A"		, "zh.wikipedia.org/wiki/A");
    +		fxt.Chk_to_str_href(zh_wiki, Bool_.Y, "/site/zh.wikipedia.org/wiki/A"		, "zh.wikipedia.org/wiki/A");
    +	}
    +	@Test   public void Xwiki() {
    +		fxt.Prep_add_xwiki_to_user("fr.wikipedia.org");
    +		fxt.Chk_to_str_href(Bool_.N, "/site/fr.wikipedia.org/wiki/Page", "fr.wikipedia.org/wiki/Page");
    +	}
    +	@Test   public void Alias() {
    +		fxt.Prep_add_xwiki_to_wiki("wikt", "en.wiktionary.org");
    +		Xow_wiki en_d = fxt.Prep_create_wiki("en.wiktionary.org");
    +		en_d.Ns_mgr().Ns_main().Case_match_(Xow_ns_case_.Tid__all);
    +		fxt.Chk_to_str_href(Bool_.N, "/wiki/wikt:a"	, "en.wiktionary.org/wiki/a");
    +	}
    +	@Test   public void Unknown()			{fxt.Chk_to_str_href(Bool_.N, "/wiki/{{{extlink}}}"					, "");}	// {{{extlink}}} not a valid title; return empty
    +}
    +class Xoa_url__to_str__fxt extends Xow_url_parser_fxt { 	private final    Xoh_href_parser href_parser = new Xoh_href_parser();
    +	public void Chk_to_str_href(boolean full, String raw, String expd) {Chk_to_str_href(cur_wiki, full, raw, expd);}
    +	public void Chk_to_str_href(Xowe_wiki wiki, boolean full, String raw, String expd) {
    +		href_parser.Parse_as_url(actl_url, Bry_.new_u8(raw), wiki, Bry__page);
    +		this.Test__to_str(full, expd);
    +	}
    +	private static final    byte[] Bry__page = Bry_.new_a7("Page_1");
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/urls/Xoav_url.java b/400_xowa/src/gplx/xowa/apps/urls/Xoav_url.java
    index a27517de8..0483fa5ed 100644
    --- a/400_xowa/src/gplx/xowa/apps/urls/Xoav_url.java
    +++ b/400_xowa/src/gplx/xowa/apps/urls/Xoav_url.java
    @@ -13,3 +13,11 @@ 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.apps.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +public class Xoav_url {
    +	public byte[] Wiki_bry() {return wiki_bry;} public void Wiki_bry_(byte[] v) {wiki_bry = v;} private byte[] wiki_bry;
    +	public byte[] Page_bry() {return page_bry;} public void Page_bry_(byte[] v) {page_bry = v;} private byte[] page_bry;
    +	public byte[] Anch_bry() {return anch_bry;} public void Anch_bry_(byte[] v) {anch_bry = v;} private byte[] anch_bry;
    +	public byte[] Qarg_bry() {return qarg_bry;} public void Qarg_bry_(byte[] v) {qarg_bry = v;} private byte[] qarg_bry;
    +	public void Clear() {wiki_bry = page_bry = null;}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/urls/Xoav_url_parser.java b/400_xowa/src/gplx/xowa/apps/urls/Xoav_url_parser.java
    index a27517de8..020a82f3d 100644
    --- a/400_xowa/src/gplx/xowa/apps/urls/Xoav_url_parser.java
    +++ b/400_xowa/src/gplx/xowa/apps/urls/Xoav_url_parser.java
    @@ -13,3 +13,29 @@ 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.apps.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +public class Xoav_url_parser {
    +	private static final byte[] Bry_site = Bry_.new_a7("/site/"), Bry_wiki = Bry_.new_a7("/wiki/"), Bry_http = Bry_.new_a7("http:");
    +	public void Parse_xo_href(Xoav_url rv, byte[] src, byte[] cur_wiki_bry) {
    +		rv.Clear();
    +		int pos = Bry_.Has_at_bgn(src, Bry_http) ? Bry_http.length  : 0;	// DRD: DRD:2.2 adds "http:" to all links
    +		int src_len = src.length;
    +		if (Bry_.Has_at_bgn(src, Bry_site, pos, src_len))
    +			pos = Parse_wiki(rv, src, src_len, pos);
    +		else
    +			rv.Wiki_bry_(cur_wiki_bry);
    +		if (Bry_.Has_at_bgn(src, Bry_wiki, pos, src_len)) pos = Parse_page(rv, src, src_len, pos);
    +	}
    +	private int Parse_wiki(Xoav_url rv, byte[] src, int src_len, int pos) {
    +		int wiki_bgn = pos + Bry_site.length;
    +		int wiki_end = Bry_find_.Find_fwd(src, Byte_ascii.Slash, wiki_bgn, src_len); if (wiki_end == Bry_find_.Not_found) throw Err_.new_wo_type("could not find wiki_end", "src", String_.new_u8(src));
    +		rv.Wiki_bry_(Bry_.Mid(src, wiki_bgn, wiki_end));
    +		return wiki_end;
    +	}
    +	private int Parse_page(Xoav_url rv, byte[] src, int src_len, int pos) {
    +		int page_bgn = pos + Bry_wiki.length;
    +		int page_end = src_len;
    +		rv.Page_bry_(Bry_.Mid(src, page_bgn, page_end));
    +		return page_end;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/urls/Xoav_url_parser_tst.java b/400_xowa/src/gplx/xowa/apps/urls/Xoav_url_parser_tst.java
    index a27517de8..0a20b8973 100644
    --- a/400_xowa/src/gplx/xowa/apps/urls/Xoav_url_parser_tst.java
    +++ b/400_xowa/src/gplx/xowa/apps/urls/Xoav_url_parser_tst.java
    @@ -13,3 +13,29 @@ 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.apps.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import org.junit.*;
    +public class Xoav_url_parser_tst {		
    +	@Before public void init() {fxt.Clear();} private final Xoav_url_parser_fxt fxt = new Xoav_url_parser_fxt();
    +	@Test   public void Page() {
    +		fxt.Exec_parse_xo_href("http:/wiki/Earth").Test_wiki("en.wikipedia.org").Test_page("Earth");
    +	}
    +	@Test   public void Site() {
    +		fxt.Exec_parse_xo_href("http:/site/en.wikipedia.org/wiki/Earth").Test_wiki("en.wikipedia.org").Test_page("Earth");
    +	}
    +}
    +class Xoav_url_parser_fxt {
    +	private Xoav_url_parser url_parser = new Xoav_url_parser(); private Xoav_url url = new Xoav_url();
    +	public void Clear() {
    +		cur_wiki = Bry_.new_a7("en.wikipedia.org");
    +		url.Clear();
    +	}
    +	public Xoav_url_parser_fxt Init_cur_wiki(String v) {cur_wiki = Bry_.new_u8(v); return this;} private byte[] cur_wiki;
    +	public Xoav_url_parser_fxt Test_wiki(String v) {Tfds.Eq_bry(Bry_.new_u8(v), url.Wiki_bry()); return this;}
    +	public Xoav_url_parser_fxt Test_page(String v) {Tfds.Eq_bry(Bry_.new_u8(v), url.Page_bry()); return this;}
    +	public Xoav_url_parser_fxt Exec_parse_xo_href(String src_str) {
    +		byte[] src_bry = Bry_.new_u8(src_str);
    +		url_parser.Parse_xo_href(url, src_bry, cur_wiki);
    +		return this;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser.java b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser.java
    index a27517de8..69d657a11 100644
    --- a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser.java
    +++ b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser.java
    @@ -13,3 +13,308 @@ 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.apps.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import gplx.core.primitives.*; import gplx.core.net.*; import gplx.core.net.qargs.*; import gplx.langs.htmls.encoders.*;
    +import gplx.xowa.htmls.hrefs.*;
    +import gplx.xowa.langs.*; import gplx.xowa.langs.vnts.*;
    +import gplx.xowa.wikis.nss.*;
    +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.xwikis.*; import gplx.xowa.files.*;
    +public class Xow_url_parser {
    +	private final    Object thread_lock = new Object();
    +	private final    Gfo_url_encoder encoder;
    +	private final    Bry_bfr tmp_bfr = Bry_bfr_.Reset(255);
    +	private final    Gfo_url_parser url_parser = new Gfo_url_parser();
    +	private final    Gfo_url_encoder gfs_encoder = Gfo_url_encoder_.New__gfs().Make();
    +	private final    Xoa_app app; private final    Xow_wiki wiki; private final    byte[] domain_bry;
    +	private byte tmp_protocol_tid;
    +	private int tmp_tid;
    +	private byte[] tmp_raw, tmp_orig, tmp_wiki, tmp_page, tmp_anch, tmp_protocol_bry; private Gfo_qarg_itm[] tmp_qargs;
    +	private byte[][] tmp_segs; private int tmp_segs_len;
    +	private boolean tmp_protocol_is_relative, tmp_page_is_main, tmp_wiki_is_missing;
    +	private byte[] tmp_vnt;
    +	private final    Xol_vnt_mgr vnt_mgr;
    +	public Xow_url_parser(Xow_wiki wiki) {
    +		this.app = wiki.App();
    +		this.wiki = wiki; this.domain_bry = wiki.Domain_bry();
    +		this.encoder = gplx.langs.htmls.encoders.Gfo_url_encoder_.Xourl;
    +		this.vnt_mgr = wiki.Type_is_edit() ? wiki.Lang().Vnt_mgr() : null;
    +	}
    +	public Xoa_url Parse_by_urlbar_or_null(String str) {
    +		Xoae_app app = (Xoae_app)wiki.App();
    +		byte[] bry = Strip_mobile_segment(Bry_.new_u8(str));
    +		byte[] fmt = app.Gui_mgr().Url_macro_mgr().Fmt_or_null(bry);
    +		if (fmt != null) bry = fmt;
    +		Xoa_url rv = Xoa_url.blank(); 
    +		this.Parse(rv, bry, 0, bry.length); if (rv.Page_bry() == null) {Xoa_url_.Invalid_warn(str); return null;}
    +		byte[] wiki_bry = rv.Wiki_bry();
    +		Xow_xwiki_itm xwiki_itm = app.User().Wikii().Xwiki_mgr().Get_by_key(wiki_bry); 
    +		if (xwiki_itm == null) {Xoa_url_.Invalid_warn(str); return null;}	// if wiki doesn't exist, warn and return nothing; DATE:2015-08-25
    +		if (rv.Page_is_main()) {		// Main_Page requested; EX: "zh.wikipedia.org"; "zh.wikipedia.org/wiki/"; DATE:2014-02-16
    +			Xow_wiki wiki_itm = app.Wiki_mgri().Get_by_or_make_init_y(wiki_bry); // NOTE: must call Init to load Main_Page; only call if from url_bar, else all sister wikis will be loaded when parsing Sister_wikis panel
    +			rv.Page_bry_(wiki_itm.Props().Main_page());
    +		}
    +		Xow_wiki parse_wiki = wiki;
    +		if (!Bry_.Eq(wiki_bry, wiki.Domain_bry()))							// NOTE: url's wiki is different than current wiki
    +			parse_wiki = app.Wiki_mgr().Get_by_or_make_init_y(wiki_bry);	// NOTE: change parse_wiki to url's wiki; needed to handle transition from home to en.d or other case-sensitivity wiki; EX: "d:earth" -> "earth" x> "Earth"; DATE:2016-04-28
    +		Xoa_ttl ttl = parse_wiki .Ttl_parse(rv.Page_bry());					// NOTE: parse to ttl to get proper casing; EX: "earth" -> "Earth" x> "earth"; DATE:2016-03-25
    +		rv.Page_bry_(ttl.Full_db());
    +		return rv;
    +	}
    +	public Gfo_url_parser Url_parser() {return url_parser;}
    +	public Xoa_url Parse(byte[] src) {Xoa_url rv = Xoa_url.blank(); Parse(rv, src); return rv;}
    +	public Xoa_url Parse(byte[] src, int bgn, int end) {Xoa_url rv = Xoa_url.blank(); Parse(rv, src, bgn, end); return rv;}
    +	public boolean Parse(Xoa_url rv, byte[] src) {return Parse(rv, src, 0, src.length);}
    +	public boolean Parse(Xoa_url rv, byte[] src, int bgn, int end) {
    +		synchronized (thread_lock) {
    +			if (end - bgn == 0) {Init_tmp_vars(Gfo_url.Empty); Make(rv); return false;}
    +			tmp_orig = (bgn == 0 && end == src.length) ? src : Bry_.Mid(src, bgn, end);
    +			// src = encoder.Decode(src, bgn, end);		// NOTE: must decode any url-encoded parameters; TOMBSTONE:do not auto-decode DATE:2016-10-10
    +			int src_len = end - bgn;
    +			Gfo_url gfo_url = url_parser.Parse(src, bgn, end);	// parse to plain gfo_url
    +			Init_tmp_vars(gfo_url);
    +			if (src[0] == Byte_ascii.Hash)				// src is anch; EX: #A
    +				Bld_anch();
    +			else {
    +				switch (tmp_protocol_tid) {
    +					case Gfo_protocol_itm.Tid_file:
    +						if (src_len > 5 && src[5] != Byte_ascii.Slash)			// src is ttl in [[File]] ns; EX: "File:A.png"
    +							Bld_page_by_file_ns();
    +						else													// src is file:///; EX: EX: "file:///C:/A.png"
    +							tmp_tid = Xoa_url_.Tid_file;
    +						break;
    +					case Gfo_protocol_itm.Tid_xowa:
    +						Bld_xowa();
    +						break;
    +					case Gfo_protocol_itm.Tid_http:
    +					case Gfo_protocol_itm.Tid_https:
    +					case Gfo_protocol_itm.Tid_relative_1:
    +						if (tmp_protocol_tid == Gfo_protocol_itm.Tid_relative_1)	// interpret relative protocol links to match wiki's protocol; EX: [//a.org] -> https://a.org for all WMF wikis; DATE:2015-07-27
    +							tmp_protocol_tid = wiki.Props().Protocol_tid();
    +						if (app.User().Wikii().Xwiki_mgr().Get_by_key(tmp_wiki) != null)// src is offline wiki; EX: http://fr.wikipedia.org/wiki/A
    +							Bld_page(0);
    +						else if (Bry_.Eq(tmp_wiki, Bry_upload_wikimedia_org))			// src is upload.wikimedia.org; EX: "http://upload.wikimedia.org/wikipedia/commons/a/ab/C.svg"
    +							Bld_page_from_upload_wikimedia_org();
    +						else															// src is unknown site: EX: "http://a.org"
    +							tmp_tid = Xoa_url_.Tid_inet;
    +						break;
    +					case Gfo_protocol_itm.Tid_unknown:
    +						Bld_page(0);
    +						break;
    +					default:
    +						tmp_tid = Xoa_url_.Tid_inet;
    +						break;
    +				} 
    +			}
    +			Bld_qargs();
    +			if (tmp_page_is_main) tmp_page = Xoa_page_.Main_page_bry_empty;
    +			if (tmp_anch != null) {
    +				byte[] anchor_bry = gplx.langs.htmls.encoders.Gfo_url_encoder_.Id.Encode(tmp_anch);	// reencode for anchors (which use . encoding, not % encoding); PAGE:en.w:Enlightenment_Spain#Enlightened_despotism_.281759%E2%80%931788.29
    +				tmp_anch = anchor_bry;
    +			}
    +			Make(rv);
    +			return true;
    +		}
    +	}
    +	private void Init_tmp_vars(Gfo_url gfo_url) {
    +		tmp_tid = Xoa_url_.Tid_unknown;
    +		tmp_raw = gfo_url.Raw();
    +		tmp_wiki = gfo_url.Segs__get_at_1st();
    +		tmp_page = gfo_url.Segs__get_at_nth();
    +		tmp_anch = gfo_url.Anch(); tmp_qargs = gfo_url.Qargs();
    +		tmp_wiki_is_missing = false;
    +		tmp_page_is_main = false;
    +		tmp_protocol_tid = gfo_url.Protocol_tid();
    +		tmp_protocol_bry = gfo_url.Protocol_bry();
    +		tmp_protocol_is_relative = gfo_url.Protocol_tid() == Gfo_protocol_itm.Tid_relative_1; // gfo_url.Protocol_is_relative();
    +		if (tmp_protocol_is_relative) tmp_protocol_tid = Gfo_protocol_itm.Tid_https;
    +		tmp_vnt = null;
    +		tmp_segs = gfo_url.Segs();
    +		tmp_segs_len = tmp_segs.length;
    +	}
    +	private Xoa_url Make(Xoa_url rv) {
    +		rv.Ctor
    +		( tmp_tid, tmp_orig, tmp_raw, tmp_protocol_tid, tmp_protocol_bry, tmp_protocol_is_relative
    +		, tmp_wiki, tmp_page, tmp_qargs, tmp_anch
    +		, tmp_segs, tmp_vnt, tmp_wiki_is_missing, Bry_.Eq(tmp_wiki, wiki.Domain_bry()), tmp_page_is_main);
    +		return rv;
    +	}
    +	private void Bld_anch() {
    +		tmp_tid = Xoa_url_.Tid_anch;
    +		tmp_wiki = domain_bry; tmp_wiki_is_missing = true;
    +		tmp_page = Bry_.Empty;
    +	}
    +	private void Bld_xowa() {
    +		tmp_tid = Xoa_url_.Tid_xcmd;
    +		tmp_page = Bry_.Mid(tmp_raw, Gfo_protocol_itm.Len_xcmd);		// NOTE: just get String after protocol; anchor (#) or query params (?) must not be parsed
    +		tmp_page = gfs_encoder.Decode(tmp_page);	// NOTE: should be decoded; EX: %20 -> " "
    +	}
    +	private void Bld_page_by_file_ns() {
    +		tmp_tid = Xoa_url_.Tid_page;
    +		tmp_segs[0] = Bry_.Add(Bry_file, tmp_wiki);			
    +		tmp_page = Make_page_from_segs(0);
    +		tmp_wiki = domain_bry; tmp_wiki_is_missing = true;
    +	}
    +	private void Bld_page_from_upload_wikimedia_org() {
    +		//	orig: https://upload.wikimedia.org/wikipedia/commons/a/ab/A.jpg
    +		//	thum: https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/A.jpg/220px-A.jpg
    +		byte[] domain_type = tmp_segs[1];						// seg[0] = type; EX: "/wikipedia/"
    +		byte[] lang = tmp_segs[2];								// seg[1] = lang; EX: "en", "fr"; "commons"
    +		if (Bry_.Eq(lang, Xow_domain_tid_.Bry__commons))	// commons links will have fmt of "/wikipedia/commons"; must change to wikimedia
    +			domain_type = Xow_domain_tid_.Bry__wikimedia;
    +		tmp_wiki = tmp_bfr.Clear()
    +			.Add(lang).Add_byte(Byte_ascii.Dot)					// add lang/type + .;	EX: "en."; "fr."; "commons."
    +			.Add(domain_type).Add(Bry_dot_org)					// add type + .org;		EX: "wikipedia.org"; "wikimedia.org";
    +			.To_bry_and_clear();
    +		if (tmp_segs_len > 6 && Bry_.Eq(tmp_segs[3], Xof_url_bldr.Bry_thumb)) tmp_page = tmp_segs[6];	// if "/thumb/", set page from seg[n-1] to seg[6]; EX: using thumb example above, "A.jpg", not "220px-A.jpg"
    +		tmp_page = Bry_.Add(Bry_file, tmp_page);				// always add "File:" ns
    +	}
    +	private boolean Find_wiki(byte[] domain) {
    +		Xow_xwiki_itm xwiki = app.User().Wikii().Xwiki_mgr().Get_by_key(domain);	// check if tmp_wiki is known wiki
    +		if (xwiki != null) {
    +			if (!Bry_.Eq(xwiki.Domain_bry(), domain))	// ignore aliases by checking that xwiki.domain == wiki; avoids false lang matches like "so/page" or "C/page"; PAGE: ca.s:So/Natura_del_so; DATE:2014-04-26; PAGE:no.b:C/Variabler; DATE:2014-10-14
    +				xwiki = null;
    +		}
    +		if (xwiki != null) return true;
    +		if (app.Wiki_mgri().Has(domain)) return true;
    +		return Byte_.Match_any(tmp_protocol_tid, Gfo_protocol_itm.Tid_http, Gfo_protocol_itm.Tid_https);
    +	}
    +	private void Bld_page(int bgn_seg) {
    +		tmp_tid = Xoa_url_.Tid_page;
    +		tmp_wiki = tmp_segs[bgn_seg];	// try seg[0] as wiki
    +		int seg_idx = bgn_seg + 1;
    +		boolean wiki_exists = Find_wiki(tmp_wiki);
    +		if (wiki_exists) {
    +			int tmp_seg_idx = Bld_main_page_or_vnt(bgn_seg);
    +			seg_idx = tmp_seg_idx;
    +			int tmp_vnt_seg = bgn_seg + 1;
    +			if (vnt_mgr.Enabled() && tmp_vnt_seg < tmp_segs_len && vnt_mgr.Regy().Has(tmp_segs[tmp_vnt_seg])) {	// check if "/zh-hans/"
    +				tmp_vnt = tmp_segs[tmp_vnt_seg];
    +			}
    +			if (tmp_seg_idx == -1) return;	// main_page or vnt; exit
    +		}
    +		else {
    +			tmp_wiki = domain_bry;		// tmp_wiki is current
    +			tmp_wiki_is_missing = true;
    +			--seg_idx;					// move seg_idx back to wiki
    +		}
    +		tmp_page = tmp_segs[seg_idx];
    +		byte[] frag = Bld_page_by_alias(tmp_page);	// handle en.wikipedia.org/wiki/fr:A
    +		if (frag != null) {
    +			tmp_segs[seg_idx] = frag;	// alias found;	set page to rhs; EX: "fr:A" -> "A"
    +			if (frag.length == 0)		// handle main_page; EX: "fr:"
    +				tmp_page_is_main = true;
    +		}
    +		tmp_page = Make_page_from_segs(seg_idx);	// join segs together; needed for nesting; EX: A/B/C
    +	}
    +	private int Bld_main_page_or_vnt(int bgn_seg) {
    +		int rv = -1;
    +		switch (tmp_segs_len - bgn_seg) {
    +			case 1:	 // "en.wikipedia.org"
    +				if (Bry_.Eq(tmp_segs[0 + bgn_seg], Xow_domain_itm_.Bry__home)) {	// ignore "home" which should always go to "home" of current wiki, not "home" wiki
    +					tmp_wiki = domain_bry;
    +					return 0;
    +				}
    +				else {
    +					tmp_page_is_main = true;
    +					return -1;
    +				}
    +			case 2:	// "en.wikipedia.org/"; "en.wikipedia.org/wiki"; "en.wikipedia.org/A"
    +				if (Bry_.Len_eq_0(tmp_segs[1 + bgn_seg])) {	// check for "en.wikipedia.org/"
    +					tmp_page_is_main = true;
    +					return -1;
    +				}
    +				else
    +					rv = 1;
    +				break;
    +			case 3: 
    +				if (Bry_.Len_gt_0(tmp_segs[2 + bgn_seg]))	// check only for "en.wikipedia.org/wiki/" where seg[2] is blank
    +					return 2;
    +				else
    +					rv = 2;
    +				break;
    +			default:
    +				return 2;
    +		}
    +		byte[] mid = tmp_segs[1 + bgn_seg];
    +		if (Bry_.Eq(mid, Bry_wiki)) {	// check if "/wiki/"
    +			tmp_page_is_main = true;
    +			return -1;
    +		}
    +		else
    +			return rv;
    +	}
    +	private byte[] Bld_page_by_alias(byte[] bry) {
    +		if (bry == null) return null;
    +		int colon_pos = Bry_find_.Find_fwd(bry, Byte_ascii.Colon);						// check for colon; EX: commons:Earth
    +		if (colon_pos == Bry_find_.Not_found) return null;									// no colon
    +		Xow_wiki alias_wiki = wiki;														// default alias_wiki to cur_wiki
    +		if (!tmp_wiki_is_missing)														// tmp_wiki exists; use it for alias wikis; DATE:2015-09-17
    +			alias_wiki = wiki.App().Wiki_mgri().Get_by_or_make_init_n(tmp_wiki);
    +		Xow_xwiki_itm alias_itm = alias_wiki.Xwiki_mgr().Get_by_mid(bry, 0, colon_pos);	// check for alias;
    +		if (alias_itm == null) return null;												// colon-word is not alias; EX:A:B
    +		Xow_ns_mgr ns_mgr = tmp_wiki_is_missing ? wiki.Ns_mgr() : wiki.App().Dbmeta_mgr().Ns__get_or_load(tmp_wiki);
    +		if (ns_mgr.Names_get_or_null(alias_itm.Key_bry()) != null)						// special case to handle collision between "wikipedia" alias and "Wikipedia" namespace; if alias exists as ns, ignore it; EX:sv.wikipedia.org/wiki/Wikipedia:Main_Page DATE:2015-07-31
    +			return null;
    +		byte[] rv = Bry_.Mid(bry, colon_pos + 1); 
    +		tmp_wiki = alias_itm.Domain_bry();
    +		return rv;
    +	}
    +	private void Bld_qargs() {
    +		int qargs_len = tmp_qargs.length;
    +		for (int i = 0; i < qargs_len; ++i) {
    +			Gfo_qarg_itm qarg = tmp_qargs[i];
    +			if (Bry_.Eq(qarg.Key_bry(), Qarg__title))
    +				tmp_page = qarg.Val_bry();					// handle /w/index.php?title=Earth
    +		}
    +	}
    +	private byte[] Make_page_from_segs(int bgn) {
    +		if (tmp_segs_len - bgn == 1) return tmp_segs[tmp_segs_len - 1];	// only 1 item; just return it; don't build bry
    +		for (int i = bgn; i < tmp_segs_len; i++) {
    +			if (i != bgn) tmp_bfr.Add_byte(Byte_ascii.Slash);
    +			tmp_bfr.Add(tmp_segs[i]);
    +		}
    +		return tmp_bfr.To_bry_and_clear();
    +	}
    +	public String Build_str(Xoa_url url) {									// transform to "canonical" form that fits url box for both XOWA and Mozilla Firefox
    +		tmp_bfr.Add(url.Wiki_bry());										// add wiki;		EX: "en.wikipedia.org"
    +		tmp_bfr.Add(Xoh_href_.Bry__wiki);									// add "/wiki/"		EX: "/wiki/"
    +		tmp_bfr.Add(encoder.Decode(url.Page_bry()));						// add page;		EX: "A"
    +		int args_len = url.Qargs_ary().length;
    +		if (args_len > 0) {
    +			for (int i = 0; i < args_len; i++) {
    +				byte dlm = i == 0 ? Byte_ascii.Question : Byte_ascii.Amp;
    +				tmp_bfr.Add_byte(dlm);
    +				Gfo_qarg_itm arg = url.Qargs_ary()[i];
    +				tmp_bfr.Add(arg.Key_bry()).Add_byte(Byte_ascii.Eq).Add(arg.Val_bry());
    +			}
    +		}
    +		if (url.Anch_bry() != null)
    +			tmp_bfr.Add_byte(Byte_ascii.Hash).Add(url.Anch_bry());		// add anchor;		EX: "#B"
    +		return tmp_bfr.To_str_and_clear();
    +	}
    +	private static byte[] Strip_mobile_segment(byte[] v) {// DATE:2014-05-03
    +		int pos = Bry_find_.Find_fwd(v, Byte_ascii.Dot);
    +		if (	pos == Bry_find_.Not_found		// no dot; EX: "A"
    +			||	pos + 2 >= v.length				// not enough space for .m.; EX: "A.b"
    +			)	
    +			return v;
    +		switch (v[pos + 1]) {	// check for m
    +			case Byte_ascii.Ltr_M:
    +			case Byte_ascii.Ltr_m:
    +				break;
    +			default:
    +				return v;
    +		}
    +		if (v[pos + 2] != Byte_ascii.Dot) return v;
    +		return Bry_.Add(Bry_.Mid(v, 0, pos), Bry_.Mid(v, pos + 2));	// skip ".m"
    +	}
    +	private static final    byte[] Qarg__title = Bry_.new_a7("title");
    +	private static final    byte[]
    +	  Bry_upload_wikimedia_org = Bry_.new_a7("upload.wikimedia.org")
    +	, Bry_file = Bry_.new_a7("File:")	// NOTE: File does not need i18n; is a canonical namespace 
    +	, Bry_wiki = Bry_.new_a7("wiki")
    +	;
    +	public static final    byte[]
    +	  Bry_dot_org = Bry_.new_a7(".org")
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__proto_tst.java b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__proto_tst.java
    index a27517de8..956ed259e 100644
    --- a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__proto_tst.java
    +++ b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__proto_tst.java
    @@ -13,3 +13,28 @@ 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.apps.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import org.junit.*;
    +public class Xow_url_parser__proto_tst {
    +	private final    Xow_url_parser_fxt tstr = new Xow_url_parser_fxt();
    +	@Test  public void Relative() {
    +		tstr.Exec__parse("//en.wikipedia.org/wiki/A").Test__wiki("en.wikipedia.org").Test__page("A");
    +	}
    +	@Test  public void Http__basic() {
    +		tstr.Exec__parse("http://en.wikipedia.org/wiki/A").Test__wiki("en.wikipedia.org").Test__page("A");
    +	}
    +	@Test  public void Upload__basic() { 
    +		tstr.Prep_add_xwiki_to_user("commons.wikimedia.org");	// NOTE: need to add xwiki to be able to resolve "/commons/"
    +		tstr.Exec__parse("http://upload.wikimedia.org/wikipedia/commons/a/ab/C.svg").Test__wiki("commons.wikimedia.org").Test__page("File:C.svg");	// orig
    +		tstr.Exec__parse("http://upload.wikimedia.org/wikipedia/commons/thumb/7/70/A.png/220px-A.png").Test__wiki("commons.wikimedia.org").Test__page("File:A.png"); // thum
    +	}
    +	@Test  public void File__basic() {
    +		tstr.Exec__parse("file:///C:/a/b/c").Test__tid(Xoa_url_.Tid_file);
    +	}
    +	@Test  public void Ftp__basic() {
    +		tstr.Exec__parse("ftp://en.wikipedia.org/wiki/A").Test__tid(Xoa_url_.Tid_inet);
    +	}
    +	@Test  public void Extended() {
    +		tstr.Exec__parse("http://en.wikipedia.org/w/index.php?A=B").Test__wiki("en.wikipedia.org").Test__page("index.php").Test__qargs("?A=B").Test__anch(null);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__qarg__tst.java b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__qarg__tst.java
    index a27517de8..6dda076bc 100644
    --- a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__qarg__tst.java
    +++ b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__qarg__tst.java
    @@ -13,3 +13,50 @@ 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.apps.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import org.junit.*;
    +public class Xow_url_parser__qarg__tst {
    +	private final    Xow_url_parser_fxt fxt = new Xow_url_parser_fxt();
    +	@Test  public void Redirect() {
    +		fxt.Exec__parse("A?redirect=no").Test__wiki("en.wikipedia.org").Test__page("A").Test__qargs("?redirect=no");
    +	}
    +	@Test  public void Action_is_edit() {
    +		fxt.Exec__parse("A?action=edit").Test__wiki("en.wikipedia.org").Test__page("A").Test__action_is_edit_y();
    +	}
    +	@Test  public void Assert_state_cleared() {	// PURPOSE.fix: action_is_edit (et. al.) was not being cleared on parse even though Xoa_url reused; DATE:20121231
    +		fxt.Exec__parse("A?action=edit")	.Test__action_is_edit_y();
    +		fxt.Exec__parse_reuse("B")			.Test__action_is_edit_n();
    +	}
    +	@Test  public void Query_arg() {	// PURPOSE.fix: query args were not printing out
    +		fxt.Exec__parse("en.wikipedia.org/wiki/Special:Search/Earth?fulltext=yes").Test__build_str_is_same();
    +	}
    +	@Test   public void Dupe_key() {
    +		fxt.Exec__parse("A?B=C1&B=C2").Test__page("A").Test__qargs("?B=C1&B=C2");
    +	}
    +	@Test  public void Question_is_eos() {
    +		fxt.Exec__parse("A?").Test__wiki("en.wikipedia.org").Test__page("A?").Test__qargs("");
    +	}
    +	@Test  public void Question_is_page() {
    +		fxt.Exec__parse("A?B").Test__wiki("en.wikipedia.org").Test__page("A?B").Test__qargs("");
    +	}
    +	@Test  public void Question_is_anchor() {
    +		fxt.Exec__parse("A#b?c").Test__wiki("en.wikipedia.org").Test__page("A").Test__anch("b.3Fc");
    +	}
    +	@Test  public void Title_remove_w() {	// PURPOSE: fix /w/ showing up as seg; DATE:2014-05-30
    +		fxt.Exec__parse("http://en.wikipedia.org/w/index.php?title=A").Test__wiki("en.wikipedia.org").Test__page("A");
    +	}
    +	@Test  public void Ctg() {
    +		fxt.Exec__parse("Category:A?pagefrom=A#mw-pages").Test__page("Category:A").Test__qargs("?pagefrom=A").Test__anch("mw-pages");
    +	}
    +	@Test  public void Anch() {
    +		fxt.Exec__parse("A?k1=v1#anch");
    +		fxt.Test__page("A");
    +		fxt.Test__anch("anch");
    +		fxt.Test__qargs("?k1=v1");
    +		fxt.Test__to_str("en.wikipedia.org/wiki/A?k1=v1#anch");
    +	}
    +	// DELETED: this is wrong as url should not handle html_entities like = instead # should strictly designate anch_href; DATE:2016-10-10
    +	// @Test  public void Encoded() {
    +	//	fxt.Exec__parse("en.wikipedia.org/wiki/A?action=edit&preload=B").Test__wiki("en.wikipedia.org").Test__page("A").Test__qargs("?action==edit=&preload=&=").Test__anch("61.3BB");	// NOTE: this is wrong; fix later
    +	// }
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__ttl_tst.java b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__ttl_tst.java
    index a27517de8..512df316e 100644
    --- a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__ttl_tst.java
    +++ b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__ttl_tst.java
    @@ -13,3 +13,42 @@ 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.apps.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import org.junit.*;
    +public class Xow_url_parser__ttl_tst {
    +	private final    Xow_url_parser_fxt tstr = new Xow_url_parser_fxt();
    +	@Test  public void Name() {
    +		tstr.Exec__parse("A").Test__wiki("en.wikipedia.org").Test__page("A");
    +	}
    +	@Test  public void Sub_1() {
    +		tstr.Exec__parse("A/b").Test__wiki("en.wikipedia.org").Test__page("A/b");
    +	}
    +	@Test  public void Sub_2() {
    +		tstr.Exec__parse("A/b/c").Test__wiki("en.wikipedia.org").Test__page("A/b/c");
    +	}
    +	@Test  public void Anch() {
    +		tstr.Exec__parse("A#b").Test__wiki("en.wikipedia.org").Test__page("A").Test__anch("b");
    +	}
    +	@Test   public void Anch_w_slash() {	// PURPOSE: A/b#c/d was not parsing correctly; PAGE:en.w:Enlightenment_Spain#Enlightened_despotism_.281759%E2%80%931788.29
    +		tstr.Exec__parse("A/b#c/d").Test__page("A/b").Test__anch("c.2Fd");
    +	}
    +	@Test  public void Ns_category() {
    +		tstr.Exec__parse("Category:A").Test__wiki("en.wikipedia.org").Test__page("Category:A");
    +	}
    +	@Test  public void Main_page__basic() {
    +		tstr.Exec__parse("en.wikipedia.org")			.Test__wiki("en.wikipedia.org").Test__page_is_main_y();
    +		tstr.Exec__parse("en.wikipedia.org/")			.Test__wiki("en.wikipedia.org").Test__page_is_main_y();
    +		tstr.Exec__parse("en.wikipedia.org/wiki")		.Test__wiki("en.wikipedia.org").Test__page_is_main_y();
    +		tstr.Exec__parse("en.wikipedia.org/wiki/")	.Test__wiki("en.wikipedia.org").Test__page_is_main_y();
    +		tstr.Exec__parse("en.wikipedia.org/wiki/A")	.Test__wiki("en.wikipedia.org").Test__page_is_main_n();
    +	}
    +	@Test  public void Ns_file__basic() {// PURPOSE: "File:A" should not be mistaken for "file:///" ns
    +		tstr.Exec__parse("File:A").Test__wiki("en.wikipedia.org").Test__page("File:A");
    +	}
    +	@Test  public void Ns_file__nested() {// PURPOSE: handle fictitious "File:A/B/C.png"
    +		tstr.Exec__parse("File:A/B/C.png").Test__wiki("en.wikipedia.org").Test__page("File:A/B/C.png");	// should not be C.png b/c of Gfo_url_parser_old
    +	}
    +	@Test  public void Anch__basic() {// DATE:2015-07-26
    +		tstr.Exec__parse("#A").Test__tid(Xoa_url_.Tid_anch).Test__wiki_is_missing(true).Test__page("").Test__anch("A");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__url_bar_tst.java b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__url_bar_tst.java
    index a27517de8..45c4b0ac7 100644
    --- a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__url_bar_tst.java
    +++ b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__url_bar_tst.java
    @@ -13,3 +13,54 @@ 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.apps.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import org.junit.*;
    +public class Xow_url_parser__url_bar_tst {
    +	private final    Xow_url_parser_fxt tstr = new Xow_url_parser_fxt();
    +	@Test  public void Basic() {
    +		tstr.Exec__parse_from_url_bar("Page_1").Test__to_str("en.wikipedia.org/wiki/Page_1");			// basic
    +	}
    +	@Test  public void Lang() {
    +		tstr.Prep_add_xwiki_to_user("uk", "uk.wikipedia.org");
    +		tstr.Exec__parse_from_url_bar("uk").Test__to_str("en.wikipedia.org/wiki/Uk");					// lang-like page (uk=Ukraine) should not try to open wiki; DATE:2014-02-07
    +	}
    +	@Test  public void Lang_like() {
    +		tstr.Prep_add_xwiki_to_user("uk", "uk.wikipedia.org", "http://~{1}.wikipedia.org");			// NOTE: fmt needed for Type_is_lang
    +		tstr.Exec__parse_from_url_bar("uk/A").Test__to_str("en.wikipedia.org/wiki/Uk/A");				// uk/A should not try be interpreted as wiki="uk" page="A"; DATE:2014-04-26
    +	}
    +	@Test  public void Macro() {
    +		tstr.Prep_add_xwiki_to_user("fr.wikisource.org");
    +		tstr.Exec__parse_from_url_bar("fr.s:Auteur:Shakespeare").Test__to_str("fr.wikisource.org/wiki/Auteur:Shakespeare");	// url_macros
    +	}
    +	@Test  public void Main_page__home() {
    +		tstr.Exec__parse_from_url_bar("home").Test__to_str("en.wikipedia.org/wiki/Home");				// home should go to current wiki's home; DATE:2014-02-09
    +		tstr.Exec__parse_from_url_bar("home/wiki/Main_Page").Test__to_str("home/wiki/Main_Page");		// home Main_Page should go to home; DATE:2014-02-09
    +	}
    +	@Test  public void Main_page__zhw() {
    +		Xowe_wiki zh_wiki = tstr.Prep_create_wiki("zh.wikipedia.org");
    +		zh_wiki.Props().Main_page_(Bry_.new_a7("Zh_Main_Page"));
    +		tstr.Exec__parse_from_url_bar("zh.w:Main_Page")	.Test__page_is_main_n().Test__to_str("zh.wikipedia.org/wiki/Main_Page");
    +		tstr.Exec__parse_from_url_bar("zh.w:")			.Test__page_is_main_y().Test__to_str("zh.wikipedia.org/wiki/Zh_Main_Page");
    +		tstr.Exec__parse_from_url_bar("en.w:")			.Test__page_is_main_y().Test__to_str("en.wikipedia.org/wiki/Main_Page");		// old bug: still stuck at zh main page due to reused objects
    +	}
    +	@Test  public void Mobile() {	// PURPOSE: handle mobile links; DATE:2014-05-03
    +		tstr.Exec__parse_from_url_bar("en.m.wikipedia.org/wiki/A"	).Test__to_str("en.wikipedia.org/wiki/A");		// basic
    +		tstr.Exec__parse_from_url_bar("en.M.wikipedia.org/wiki/A"	).Test__to_str("en.wikipedia.org/wiki/A");		// upper
    +		tstr.Exec__parse_from_url_bar("A"							).Test__to_str("en.wikipedia.org/wiki/A");		// bounds-check: 0
    +		tstr.Exec__parse_from_url_bar("A."						).Test__to_str("en.wikipedia.org/wiki/A.");		// bounds-check: 1
    +		tstr.Exec__parse_from_url_bar("A.b"						).Test__to_str("en.wikipedia.org/wiki/A.b");		// bounds-check: 2
    +		tstr.Exec__parse_from_url_bar("A.b.m."					).Test__to_str("en.wikipedia.org/wiki/A.b.m.");	// false-match
    +		tstr.Exec__parse_from_url_bar("en.x.wikipedia.org/wiki/A"	).Test__to_str("en.wikipedia.org/wiki/En.x.wikipedia.org/wiki/A");	// fail
    +	}
    +	@Test  public void Missing_page() {
    +		tstr.Exec__parse_from_url_bar("http://a.org").Test__is_null();	// unknown wiki; return null;
    +		tstr.Exec__parse_from_url_bar("http://en.wikipedia.org").Test__to_str("en.wikipedia.org/wiki/Main_Page");	// known wiki; return Main_Page
    +	}
    +	@Test  public void Invalid_names() {
    +		tstr.Exec__parse_from_url_bar("http://a/b/c").Test__is_null();				// unknown url
    +		tstr.Exec__parse_from_url_bar("war").Test__to_str("en.wikipedia.org/wiki/War");	// word looks like lang, but is actually page; default to current
    +	}
    +	@Test  public void Proper_case() {
    +		tstr.Exec__parse_from_url_bar("a"							).Test__to_str("en.wikipedia.org/wiki/A");		// "a" -> "A" x> "a"
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__wiki_tst.java b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__wiki_tst.java
    index a27517de8..d4011d33f 100644
    --- a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__wiki_tst.java
    +++ b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__wiki_tst.java
    @@ -13,3 +13,27 @@ 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.apps.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import org.junit.*;
    +public class Xow_url_parser__wiki_tst {
    +	private final    Xow_url_parser_fxt tstr = new Xow_url_parser_fxt();
    +	@Test  public void Basic() {
    +		tstr.Exec__parse("en.wikipedia.org/wiki/A").Test__tid(Xoa_url_.Tid_page).Test__wiki("en.wikipedia.org").Test__page("A");
    +	}
    +	@Test  public void No_wiki() {	// PURPOSE: no "/wiki/"
    +		tstr.Exec__parse("en.wikipedia.org/A").Test__wiki("en.wikipedia.org").Test__page("A");
    +	}
    +	@Test  public void Nested() {
    +		tstr.Exec__parse("en.wikipedia.org/wiki/A/b").Test__wiki("en.wikipedia.org").Test__page("A/b");
    +	}
    +	@Test  public void Slash() {
    +		tstr.Exec__parse("en.wikipedia.org/wiki//A").Test__wiki("en.wikipedia.org").Test__page("/A");
    +		tstr.Exec__parse("en.wikipedia.org/wiki/A//b").Test__wiki("en.wikipedia.org").Test__page("A//b");
    +		tstr.Exec__parse("en.wikipedia.org/wiki///A").Test__wiki("en.wikipedia.org").Test__page("//A");
    +	}
    +	@Test  public void Vnt() {
    +		Xowe_wiki wiki = tstr.Wiki();
    +		gplx.xowa.langs.vnts.Xol_vnt_regy_fxt.Init__vnt_mgr(wiki.Lang().Vnt_mgr(), 0, String_.Ary("zh-hans", "zh-hant"));
    +		tstr.Exec__parse("en.wikipedia.org/zh-hans/A").Test__wiki("en.wikipedia.org").Test__page("A").Test__vnt("zh-hans");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__xcmd_tst.java b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__xcmd_tst.java
    index a27517de8..ccd585fef 100644
    --- a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__xcmd_tst.java
    +++ b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__xcmd_tst.java
    @@ -13,3 +13,17 @@ 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.apps.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import org.junit.*;
    +public class Xow_url_parser__xcmd_tst {
    +	private final    Xow_url_parser_fxt tstr = new Xow_url_parser_fxt();
    +	@Test  public void Basic() {
    +		tstr.Exec__parse("xowa-cmd:xowa.app.version").Test__tid(Xoa_url_.Tid_xcmd).Test__page("xowa.app.version");
    +	}
    +	@Test  public void Encoded() {
    +		tstr.Exec__parse("xowa-cmd:a%22b*c").Test__tid(Xoa_url_.Tid_xcmd).Test__page("a\"b*c");
    +	}
    +	@Test  public void Ignore_anchor_and_qargs() {
    +		tstr.Exec__parse("xowa-cmd:a/b/c?d=e#f").Test__tid(Xoa_url_.Tid_xcmd).Test__page("a/b/c?d=e#f");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__xwiki_tst.java b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__xwiki_tst.java
    index a27517de8..37299b134 100644
    --- a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__xwiki_tst.java
    +++ b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser__xwiki_tst.java
    @@ -13,3 +13,66 @@ 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.apps.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import org.junit.*; import gplx.xowa.wikis.nss.*;
    +public class Xow_url_parser__xwiki_tst {
    +	private final    Xow_url_parser_fxt tstr = new Xow_url_parser_fxt();
    +	@Test  public void Commons() {	// PURPOSE: "C" was being picked up as an xwiki to commons; PAGE:no.b:C/Variabler; DATE:2014-10-14
    +		tstr.Prep_add_xwiki_to_user("c", "commons.wikimedia.org");		// add alias of "c"
    +		tstr.Exec__parse("C/D").Test__tid(Xoa_url_.Tid_page).Test__wiki("en.wikipedia.org").Test__page("C/D");	// should use current wiki (enwiki), not xwiki to commons; also, page should be "C/D", not "D"
    +	}
    +	@Test  public void Parse_lang() {
    +		tstr.Prep_add_xwiki_to_wiki("fr", "fr.wikipedia.org", "http://fr.wikipedia.org/~{0}");
    +		tstr.Exec__parse("http://en.wikipedia.org/wiki/fr:A").Test__tid(Xoa_url_.Tid_page).Test__wiki("fr.wikipedia.org").Test__page("A");
    +	}
    +	@Test  public void Alias_wiki() {
    +		tstr.Prep_add_xwiki_to_wiki("s", "en.wikisource.org");
    +		tstr.Exec__parse("s:A/b/c").Test__wiki("en.wikisource.org").Test__page("A/b/c");
    +	}
    +	@Test  public void Xwiki_no_segs() {	// PURPOSE: handle xwiki without full url; EX: "commons:Commons:Media_of_the_day"; DATE:2014-02-19
    +		tstr.Prep_add_xwiki_to_wiki("s", "en.wikisource.org");
    +		tstr.Exec__parse("s:Project:A").Test__wiki("en.wikisource.org").Test__page("Project:A");
    +	}
    +	@Test  public void Domain_only() {
    +		tstr.Prep_add_xwiki_to_user("fr.wikipedia.org");
    +		tstr.Exec__parse("fr.wikipedia.org").Test__wiki("fr.wikipedia.org").Test__page("");
    +	}
    +	@Test  public void Domain_and_wiki() {
    +		tstr.Prep_add_xwiki_to_user("fr.wikipedia.org");
    +		tstr.Exec__parse("fr.wikipedia.org/wiki").Test__wiki("fr.wikipedia.org").Test__page("");
    +	}
    +	@Test  public void Domain_and_wiki_w_http() {
    +		tstr.Prep_add_xwiki_to_user("fr.wikipedia.org");
    +		tstr.Exec__parse("http://fr.wikipedia.org/wiki").Test__wiki("fr.wikipedia.org").Test__page("");
    +	}		
    +	@Test  public void Namespace_in_different_wiki() {	// PURPOSE.fix: namespaced titles would default to default_wiki instead of current_wiki
    +		Xowe_wiki en_s = tstr.Prep_create_wiki("en.wikisource.org");
    +		tstr.Exec__parse(en_s, "Category:A").Test__wiki("en.wikisource.org").Test__page("Category:A");
    +	}		
    +	@Test  public void Case_sensitive() {
    +		// tstr.Exec__parse("en.wikipedia.org/wiki/a").Test__wiki("en.wikipedia.org").Test__page("A");
    +		Xowe_wiki en_d = tstr.Prep_create_wiki("en.wiktionary.org");
    +		Xow_ns_mgr ns_mgr = en_d.Ns_mgr();
    +
    +		ns_mgr.Ns_main().Case_match_(Xow_ns_case_.Tid__all);
    +		tstr.Exec__parse("en.wiktionary.org/wiki/a").Test__wiki("en.wiktionary.org").Test__page("a");
    +
    +		ns_mgr.Ns_category().Case_match_(Xow_ns_case_.Tid__all);
    +		tstr.Exec__parse("en.wiktionary.org/wiki/Category:a").Test__wiki("en.wiktionary.org").Test__page("Category:a");
    +
    +		tstr.Exec__parse("en.wiktionary.org/wiki/A/B/C").Test__page("A/B/C");
    +	}
    +	@Test  public void Xwiki__to_enwiki() {	// PURPOSE: handle alias of "wikipedia" and sv.wikipedia.org/wiki/Wikipedia:Main_Page; DATE:2015-07-31
    +		Xowe_wiki xwiki = tstr.Prep_create_wiki("sv.wikipedia.org");
    +		tstr.Prep_xwiki(xwiki, "wikipedia", "en.wikipedia.org", null);
    +		tstr.Prep_get_ns_mgr_from_meta("sv.wikipedia.org").Add_new(Xow_ns_.Tid__project, "Wikipedia");
    +		tstr.Exec__parse(xwiki, "sv.wikipedia.org/wiki/wikipedia:X").Test__wiki("sv.wikipedia.org").Test__page("wikipedia:X");
    +		tstr.Exec__parse(xwiki, "sv.wikipedia.org/wiki/Wikipedia:X").Test__wiki("sv.wikipedia.org").Test__page("Wikipedia:X");
    +	}
    +	@Test  public void Xwiki__to_ns() {	// PURPOSE: handle alias of "wikipedia" in current, but no "Wikipedia" ns in other wiki; PAGE:pt.w:Wikipedia:P�gina_de_testes DATE:2015-09-17
    +		tstr.Prep_create_wiki("pt.wikipedia.org");
    +		tstr.Prep_get_ns_mgr_from_meta("pt.wikipedia.org").Add_new(Xow_ns_.Tid__project, "Project");	// clear ns_mgr and add only "Project" ns, not "Wikipedia" ns
    +		tstr.Prep_xwiki(tstr.Wiki(), "wikipedia", "en.wikipedia.org", null);	// add alias of "wikipedia" in current wiki
    +		tstr.Exec__parse(tstr.Wiki(), "pt.wikipedia.org/wiki/Wikipedia:X").Test__wiki("pt.wikipedia.org").Test__page("Wikipedia:X");	// should get "pt.wikipedia.org", not "en.wikipedia.org" (through alias)
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser_fxt.java b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser_fxt.java
    index a27517de8..ff8fa4fc6 100644
    --- a/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser_fxt.java
    +++ b/400_xowa/src/gplx/xowa/apps/urls/Xow_url_parser_fxt.java
    @@ -13,3 +13,69 @@ 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.apps.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import gplx.xowa.wikis.nss.*;
    +public class Xow_url_parser_fxt {
    +	protected final    Xoae_app app; protected final    Xowe_wiki cur_wiki;
    +	protected final    Xow_url_parser parser;
    +	protected Xoa_url actl_url;
    +	public Xow_url_parser_fxt() {
    +		this.app = Xoa_app_fxt.Make__app__edit();
    +		this.cur_wiki = Prep_create_wiki("en.wikipedia.org");
    +		this.parser = cur_wiki.Utl__url_parser();
    +		this.actl_url = Xoa_url.blank();	// default to blank for subclasses
    +	}
    +	public Xoae_app App() {return app;}
    +	public Xowe_wiki Wiki() {return cur_wiki;}
    +	public Xowe_wiki Prep_create_wiki(String domain) {
    +		Xowe_wiki rv = Xoa_app_fxt.Make__wiki__edit(app, domain);
    +		Prep_add_xwiki_to_user(domain);
    +		return rv;
    +	}
    +	public Xow_url_parser_fxt Prep_add_xwiki_to_wiki(String alias, String domain)				{return Prep_xwiki(cur_wiki, alias, domain, null);}
    +	public Xow_url_parser_fxt Prep_add_xwiki_to_wiki(String alias, String domain, String fmt)	{return Prep_xwiki(cur_wiki, alias, domain, fmt);}
    +	public Xow_url_parser_fxt Prep_add_xwiki_to_user(String domain)								{return Prep_xwiki(app.Usere().Wiki(), domain, domain, null);}
    +	public Xow_url_parser_fxt Prep_add_xwiki_to_user(String alias, String domain)				{return Prep_xwiki(app.Usere().Wiki(), alias, domain, null);}
    +	public Xow_url_parser_fxt Prep_add_xwiki_to_user(String alias, String domain, String fmt)	{return Prep_xwiki(app.Usere().Wiki(), alias, domain, fmt);}
    +	public Xow_url_parser_fxt Prep_xwiki(Xow_wiki wiki, String alias, String domain, String fmt) {
    +		wiki.Xwiki_mgr().Add_by_atrs(Bry_.new_u8(alias), Bry_.new_u8(domain), Bry_.new_u8_safe(fmt));
    +		return this;
    +	}
    +	public Xow_ns_mgr Prep_get_ns_mgr_from_meta(String wiki) {
    +		return app.Dbmeta_mgr().Ns__get_or_load(Bry_.new_u8(wiki));
    +	}
    +	public Xow_url_parser_fxt Exec__parse(String actl_str) {return Exec__parse(cur_wiki, actl_str);}
    +	public Xow_url_parser_fxt Exec__parse(Xow_wiki wiki, String actl_str) {
    +		this.actl_url = wiki.Utl__url_parser().Parse(Bry_.new_u8(actl_str));
    +		return this;
    +	}
    +	public Xow_url_parser_fxt Exec__parse_reuse(String actl_str) {
    +		this.actl_url = parser.Parse(Bry_.new_u8(actl_str));
    +		return this;
    +	}
    +	public Xow_url_parser_fxt Exec__parse_from_url_bar(String raw) {
    +		this.actl_url = parser.Parse_by_urlbar_or_null(raw);
    +		return this;
    +	}
    +	public Xow_url_parser_fxt	Test__tid(int v) 					{Tfds.Eq_int(v, actl_url.Tid()		, "tid"); return this;}
    +	public Xow_url_parser_fxt	Test__is_null() 					{Tfds.Eq_bool(true, actl_url == null); return this;}
    +	public Xow_url_parser_fxt	Test__vnt(String v) 				{Tfds.Eq_str(v, actl_url.Vnt_bry()	, "vnt"); return this;}
    +	public Xow_url_parser_fxt	Test__wiki(String v) 				{Tfds.Eq_str(v, actl_url.Wiki_bry()	, "wiki"); return this;}
    +	public Xow_url_parser_fxt	Test__wiki_is_missing(boolean v)		{Tfds.Eq_bool(v, actl_url.Wiki_is_missing(), "wiki_is_missing"); return this;}
    +	public Xow_url_parser_fxt	Test__page(String v) 				{Tfds.Eq_str(v, actl_url.Page_bry()	, "page"); return this;}
    +	public Xow_url_parser_fxt	Test__qargs(String v) 				{Tfds.Eq_str(v, actl_url.Qargs_mgr().To_bry(), "qargs"); return this;}
    +	public Xow_url_parser_fxt	Test__page_is_main_y() 				{return Test__page_is_main(Bool_.Y);}
    +	public Xow_url_parser_fxt	Test__page_is_main_n() 				{return Test__page_is_main(Bool_.N);}
    +	public Xow_url_parser_fxt	Test__page_is_main(boolean v)			{Tfds.Eq_bool(v, actl_url.Page_is_main()	, "page_is_main"); return this;}
    +	public Xow_url_parser_fxt	Test__anch(String v) 				{Tfds.Eq_str(v, actl_url.Anch_bry(), "anch"); return this;}
    +	public Xow_url_parser_fxt	Test__action_is_edit_y() 			{return Test__action_is_edit_(Bool_.Y);}
    +	public Xow_url_parser_fxt	Test__action_is_edit_n() 			{return Test__action_is_edit_(Bool_.N);}
    +	private Xow_url_parser_fxt	Test__action_is_edit_(boolean v)		{Tfds.Eq_bool(v, actl_url.Qargs_mgr().Match(Xoa_url_.Qarg__action, Xoa_url_.Qarg__action__edit), "action_is_edit"); return this;}
    +	public Xow_url_parser_fxt	Test__to_str(String v) 				{return Test__to_str(Bool_.Y, v);}
    +	public Xow_url_parser_fxt	Test__to_str(boolean full, String v)	{Tfds.Eq_str(v, actl_url.To_bry(full, Bool_.Y), "To_bry"); return this;}
    +	public Xow_url_parser_fxt	Test__build_str_is_same() {
    +		Xow_url_parser parser = new Xow_url_parser(cur_wiki);
    +		Tfds.Eq_str(actl_url.Raw(), parser.Build_str(actl_url), "build_str");
    +		return this;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/utls/Xoa_url_encoder.java b/400_xowa/src/gplx/xowa/apps/utls/Xoa_url_encoder.java
    index a27517de8..80dbffc00 100644
    --- a/400_xowa/src/gplx/xowa/apps/utls/Xoa_url_encoder.java
    +++ b/400_xowa/src/gplx/xowa/apps/utls/Xoa_url_encoder.java
    @@ -13,3 +13,42 @@ 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.apps.utls; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +public class Xoa_url_encoder {	// NOTE: redundant with Gfo_url_encoder, but is simpler; DATE:2016-09-15
    +	private final    Bry_bfr bfr = Bry_bfr_.New();
    +	public byte[] Encode(byte[] src) {
    +		int src_len = src.length;
    +		boolean dirty = false;
    +		for (int i = 0; i < src_len; ++i) {
    +			byte b = src[i];
    +			byte[] repl = null;
    +			switch (b) {
    +				case Byte_ascii.Space:		repl = Bry__underline; break;
    +				case Byte_ascii.Amp:		repl = Bry__amp; break;
    +				case Byte_ascii.Apos:		repl = Bry__apos; break;
    +				case Byte_ascii.Eq:			repl = Bry__eq; break;
    +				case Byte_ascii.Plus:		repl = Bry__plus; break;
    +			}
    +
    +			// not a replacement sequence
    +			if (repl == null) {
    +				// if dirty, add to bfr; else, ignore
    +				if (dirty)
    +					bfr.Add_byte(b);
    +			}
    +			else {
    +				// if clean, add everything before cur_pos to bfr
    +				if (!dirty) {
    +					bfr.Add_mid(src, 0, i);
    +					dirty = true;
    +				}
    +				bfr.Add(repl);
    +			}
    +		}
    +		return dirty ? bfr.To_bry_and_clear() : src;
    +	}
    +	private static final    byte[] Bry__amp = Bry_.new_a7("%26"), Bry__eq = Bry_.new_a7("%3D")
    +	, Bry__plus = Bry_.new_a7("%2B"), Bry__apos = Bry_.new_a7("%27")
    +	, Bry__underline = new byte[] {Byte_ascii.Underline}
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/utls/Xoa_url_encoder__tst.java b/400_xowa/src/gplx/xowa/apps/utls/Xoa_url_encoder__tst.java
    index a27517de8..03bcb7d07 100644
    --- a/400_xowa/src/gplx/xowa/apps/utls/Xoa_url_encoder__tst.java
    +++ b/400_xowa/src/gplx/xowa/apps/utls/Xoa_url_encoder__tst.java
    @@ -13,3 +13,17 @@ 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.apps.utls; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import org.junit.*; import gplx.core.tests.*;
    +public class Xoa_url_encoder__tst {
    +	private final    Xoa_url_encoder__fxt fxt = new Xoa_url_encoder__fxt();
    +	@Test  public void Syms__diff() 	{fxt.Test__encode(" &'=+", "_%26%27%3D%2B");}
    +	@Test  public void Syms__same() 	{fxt.Test__encode("!\"#$%()*,-./:;<>?@[\\]^_`{|}~", "!\"#$%()*,-./:;<>?@[\\]^_`{|}~");}
    +	@Test  public void Mixed() 			{fxt.Test__encode("a &'=+b", "a_%26%27%3D%2Bb");}	// PURPOSE: make sure dirty flag is set
    +}
    +class Xoa_url_encoder__fxt {
    +	private final    Xoa_url_encoder encoder = new Xoa_url_encoder();
    +	public void Test__encode(String raw, String expd) {
    +		Gftest.Eq__bry(Bry_.new_u8(expd), encoder.Encode(Bry_.new_u8(raw)));
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/versions/Xoa_version_.java b/400_xowa/src/gplx/xowa/apps/versions/Xoa_version_.java
    index a27517de8..50bc078bd 100644
    --- a/400_xowa/src/gplx/xowa/apps/versions/Xoa_version_.java
    +++ b/400_xowa/src/gplx/xowa/apps/versions/Xoa_version_.java
    @@ -13,3 +13,24 @@ 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.apps.versions; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +public class Xoa_version_ {
    +	public static int Compare(String lhs_str, String rhs_str) {
    +		String[] lhs_ary = String_.Split(lhs_str, ".");
    +		String[] rhs_ary = String_.Split(rhs_str, ".");
    +		return Compare_as_int(lhs_ary, rhs_ary);
    +	}
    +	private static int Compare_as_int(String[] lhs_ary, String[] rhs_ary) {
    +		int lhs_ary_len = lhs_ary.length;
    +		int rhs_ary_len = rhs_ary.length;
    +		int len_comp = Int_.Compare(lhs_ary_len, rhs_ary_len);
    +		if (len_comp != CompareAble_.Same) return len_comp;
    +		for (int i = 0; i < lhs_ary_len; ++i) {
    +			String lhs_itm = lhs_ary[i];
    +			String rhs_itm = rhs_ary[i];
    +			int itm_comp = Int_.Compare(Int_.Parse_or(lhs_itm, 0), Int_.Parse_or(rhs_itm, 0));
    +			if (itm_comp != CompareAble_.Same) return itm_comp;
    +		}
    +		return CompareAble_.Same;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/versions/Xoa_version_tst.java b/400_xowa/src/gplx/xowa/apps/versions/Xoa_version_tst.java
    index a27517de8..7d67d8662 100644
    --- a/400_xowa/src/gplx/xowa/apps/versions/Xoa_version_tst.java
    +++ b/400_xowa/src/gplx/xowa/apps/versions/Xoa_version_tst.java
    @@ -13,3 +13,23 @@ 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.apps.versions; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*;
    +import org.junit.*;
    +public class Xoa_version_tst {
    +	@Before public void init() {fxt.Clear();} private Xoa_version_fxt fxt = new Xoa_version_fxt();
    +	@Test  public void Compare() {
    +		fxt.Test_compare("1.8.1.1", "1.8.2.1"	, CompareAble_.Less);	// rev:less
    +		fxt.Test_compare("1.8.2.1", "1.8.1.1"	, CompareAble_.More);	// rev:more
    +		fxt.Test_compare("1.8.1.1", "1.8.1.1"	, CompareAble_.Same);	// rev:same
    +		fxt.Test_compare("1.7.9.1", "1.8.1.1"	, CompareAble_.Less);	// min:less
    +		fxt.Test_compare("", "1.8.1.1"			, CompareAble_.Less);	// empty:less
    +		fxt.Test_compare("1.8.1.1", ""			, CompareAble_.More);	// empty:more
    +		fxt.Test_compare("", ""					, CompareAble_.Same);	// empty:more
    +	}
    +}
    +class Xoa_version_fxt {
    +	public void Clear() {}
    +	public void Test_compare(String lhs, String rhs, int expd) {
    +		Tfds.Eq(expd, Xoa_version_.Compare(lhs, rhs), lhs + "|" + rhs);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/wms/apis/Xowm_api_bldr.java b/400_xowa/src/gplx/xowa/apps/wms/apis/Xowm_api_bldr.java
    index a27517de8..705cf6635 100644
    --- a/400_xowa/src/gplx/xowa/apps/wms/apis/Xowm_api_bldr.java
    +++ b/400_xowa/src/gplx/xowa/apps/wms/apis/Xowm_api_bldr.java
    @@ -13,3 +13,11 @@ 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.apps.wms.apis; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.wms.*;
    +public class Xowm_api_bldr {
    +	public static void Bld_bgn(Bry_bfr bfr, byte[] wiki) {
    +		bfr.Add_str_a7("https://");
    +		bfr.Add(wiki);
    +		bfr.Add_str_a7("/w/api.php?");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/wms/apis/Xowmf_api_mgr.java b/400_xowa/src/gplx/xowa/apps/wms/apis/Xowmf_api_mgr.java
    index a27517de8..013c9aeed 100644
    --- a/400_xowa/src/gplx/xowa/apps/wms/apis/Xowmf_api_mgr.java
    +++ b/400_xowa/src/gplx/xowa/apps/wms/apis/Xowmf_api_mgr.java
    @@ -13,3 +13,8 @@ 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.apps.wms.apis; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.wms.*;
    +import gplx.xowa.apps.wms.apis.origs.*;
    +public class Xowmf_api_mgr {
    +	public Xoapi_orig_base	Api_orig() {return api_orig;} public void Api_orig_(Xoapi_orig_base v) {api_orig = v;} private Xoapi_orig_base api_orig = new Xoapi_orig_wmf();
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_base.java b/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_base.java
    index a27517de8..422197e86 100644
    --- a/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_base.java
    +++ b/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_base.java
    @@ -13,3 +13,20 @@ 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.apps.wms.apis.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.wms.*; import gplx.xowa.apps.wms.apis.*;
    +import gplx.core.ios.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.downloads.*;
    +public abstract class Xoapi_orig_base {
    +	public boolean Api_query_size(Xoapi_orig_rslts rv, Xof_download_wkr download_wkr, Xow_repo_mgr repo_mgr, byte[] ttl, int width, int height) {
    +		if (!gplx.core.ios.IoEngine_system.Web_access_enabled) return false;	// don't check api if download disabled else "download_failed" messages in log (particularly during pkg_make) DATE:2015-02-12
    +		Gfo_usr_dlg usr_dlg = Gfo_usr_dlg_.Instance;
    +		Xof_repo_pair[] repos = repo_mgr.Repos_ary();
    +		int len = repos.length;
    +		for (int i = 0; i < len; i++) {
    +			Xof_repo_pair repo_pair = repos[i];
    +			if (Api_query_size_exec(rv, download_wkr, ttl, width, height, usr_dlg, repo_pair.Wiki_domain())) return true;
    +		}
    +		usr_dlg.Warn_many(Xoapi_orig_wmf.GRP_KEY, "download_failed", "download_failed: ~{0}", String_.new_u8(ttl));
    +		return false;
    +	}
    +	public abstract boolean Api_query_size_exec(Xoapi_orig_rslts rv, Xof_download_wkr download_wkr, byte[] ttl, int width, int height, Gfo_usr_dlg usr_dlg, byte[] repo_wiki_key);
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_base_tst.java b/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_base_tst.java
    index a27517de8..5f8d64016 100644
    --- a/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_base_tst.java
    +++ b/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_base_tst.java
    @@ -13,3 +13,48 @@ 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.apps.wms.apis.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.wms.*; import gplx.xowa.apps.wms.apis.*;
    +import org.junit.*;
    +// https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles=Main Page&rvprop=timestamp|content
    +public class Xoapi_orig_base_tst {
    +	Xoapi_orig_base_fxt fxt = new Xoapi_orig_base_fxt();
    +	@Before public void init() {fxt.Clear();}
    +	@Test  public void Bld_api_url() {
    +		fxt.Bld_api_url_tst("A.png"		, 220, 110, "https://en.wikipedia.org/w/api.php?action=query&format=xml&prop=imageinfo&iiprop=size|url&redirects&titles=File:A.png&iiurlwidth=220&iiurlheight=110");
    +		fxt.Bld_api_url_tst("A.png"		, 220,   0, "https://en.wikipedia.org/w/api.php?action=query&format=xml&prop=imageinfo&iiprop=size|url&redirects&titles=File:A.png&iiurlwidth=220");
    +		fxt.Bld_api_url_tst("A.png"		,   0, 110, "https://en.wikipedia.org/w/api.php?action=query&format=xml&prop=imageinfo&iiprop=size|url&redirects&titles=File:A.png");	// assert that null width does not write height
    +		fxt.Bld_api_url_tst("A b.png"	, 220,   0, "https://en.wikipedia.org/w/api.php?action=query&format=xml&prop=imageinfo&iiprop=size|url&redirects&titles=File:A_b.png&iiurlwidth=220");
    +		fxt.Bld_api_url_tst("A&b.png"	, 220,   0, "https://en.wikipedia.org/w/api.php?action=query&format=xml&prop=imageinfo&iiprop=size|url&redirects&titles=File:A%26b.png&iiurlwidth=220");
    +	}
    +	@Test  public void Parse_size() {
    +		String raw = "";
    +		fxt.Parse_size_tst(raw, 220, 110);
    +	}
    +	@Test  public void Parse_reg() {
    +		String raw = "";
    +		fxt.Parse_reg_tst(raw, "commons.wikimedia.org", "Berkheyde-Haarlem.png");
    +	}
    +}
    +class Xoapi_orig_base_fxt {
    +	private Xoae_app app; private Xowe_wiki wiki; private Xoapi_orig_rslts rv = new Xoapi_orig_rslts();
    +	public void Clear() {
    +		this.app = Xoa_app_fxt.Make__app__edit();
    +		this.wiki = Xoa_app_fxt.Make__wiki__edit(app);
    +	}
    +	public void Bld_api_url_tst(String ttl_str, int w, int h, String expd) {
    +		String actl = Xoapi_orig_wmf.Bld_api_url(wiki.Domain_bry(), Bry_.new_u8(ttl_str), w, h);
    +		Tfds.Eq(expd, actl);
    +	}
    +	public void Parse_size_tst(String xml_str, int expd_w, int expd_h) {
    +		byte[] xml_bry = Bry_.new_u8(xml_str);
    +		Xoapi_orig_wmf.Parse_xml(rv, app.Usr_dlg(), xml_bry);
    +		Tfds.Eq(expd_w, rv.Orig_w());
    +		Tfds.Eq(expd_h, rv.Orig_h());
    +	}
    +	public void Parse_reg_tst(String xml_str, String expd_wiki, String expd_page) {
    +		byte[] xml_bry = Bry_.new_u8(xml_str);
    +		Xoapi_orig_wmf.Parse_xml(rv, app.Usr_dlg(), xml_bry);
    +		Tfds.Eq(expd_wiki, String_.new_u8(rv.Orig_wiki()));
    +		Tfds.Eq(expd_page, String_.new_u8(rv.Orig_page()));
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_mok.java b/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_mok.java
    index a27517de8..923b5fc76 100644
    --- a/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_mok.java
    +++ b/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_mok.java
    @@ -13,3 +13,22 @@ 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.apps.wms.apis.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.wms.*; import gplx.xowa.apps.wms.apis.*;
    +import gplx.xowa.files.downloads.*;
    +public class Xoapi_orig_mok extends Xoapi_orig_base {
    +	private String wiki_str = "", ttl_str = "", redirect_str = ""; private int orig_w, orig_h; private boolean fail = false;
    +	public Xoapi_orig_mok Ini(String wiki_str, String ttl_str, String redirect_str, int orig_w, int orig_h, boolean pass) {
    +		this.wiki_str = wiki_str; this.ttl_str = ttl_str; this.redirect_str = redirect_str; this.orig_w = orig_w; this.orig_h = orig_h; this.fail = !pass;
    +		return this;
    +	}
    +	public void Clear() {wiki_str = ttl_str = redirect_str = ""; orig_w = orig_h = 0;}
    +	@Override public boolean Api_query_size_exec(Xoapi_orig_rslts rv, Xof_download_wkr download_wkr, byte[] ttl, int width, int height, Gfo_usr_dlg usr_dlg, byte[] repo_wiki_key) {
    +		if (!Bry_.Eq(ttl, Bry_.new_u8(ttl_str))) return false;
    +		if (!String_.Eq(wiki_str, String_.new_a7(repo_wiki_key))) return false;
    +		if (fail) return false;
    +		byte[] orig_page = String_.Eq(redirect_str, "") ? ttl : Bry_.new_u8(redirect_str);
    +		rv.Init_all(repo_wiki_key, orig_page, orig_w, orig_h);
    +		return true;
    +	}
    +	public static final    Xoapi_orig_mok Instance = new Xoapi_orig_mok(); Xoapi_orig_mok() {}
    +}
    \ No newline at end of file
    diff --git a/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_rslts.java b/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_rslts.java
    index a27517de8..da6735ebb 100644
    --- a/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_rslts.java
    +++ b/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_rslts.java
    @@ -13,3 +13,17 @@ 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.apps.wms.apis.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.wms.*; import gplx.xowa.apps.wms.apis.*;
    +public class Xoapi_orig_rslts {
    +	public byte[] Orig_wiki() {return orig_wiki;} private byte[] orig_wiki;
    +	public byte[] Orig_page() {return orig_page;} private byte[] orig_page;
    +	public int Orig_w() {return orig_w;} private int orig_w;
    +	public int Orig_h() {return orig_h;} private int orig_h;
    +	public void Init_all(byte[] wiki, byte[] page, int w, int h) {
    +		this.orig_wiki = wiki; this.orig_page = page; this.orig_w = w; this.orig_h = h;
    +	}
    +	public void Clear() {
    +		orig_wiki = orig_page = null;
    +		orig_w = orig_h = 0;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_wmf.java b/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_wmf.java
    index a27517de8..dfd1e07b4 100644
    --- a/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_wmf.java
    +++ b/400_xowa/src/gplx/xowa/apps/wms/apis/origs/Xoapi_orig_wmf.java
    @@ -13,3 +13,83 @@ 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.apps.wms.apis.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.wms.*; import gplx.xowa.apps.wms.apis.*;
    +import gplx.core.primitives.*; import gplx.core.net.*; import gplx.core.envs.*;
    +import gplx.langs.htmls.encoders.*; 
    +import gplx.xowa.files.downloads.*;
    +import gplx.xowa.htmls.hrefs.*;
    +public class Xoapi_orig_wmf extends Xoapi_orig_base {
    +	@Override public boolean Api_query_size_exec(Xoapi_orig_rslts rv, Xof_download_wkr download_wkr, byte[] ttl, int width, int height, Gfo_usr_dlg usr_dlg, byte[] repo_wiki_key) {
    +		if (Env_.Mode_testing()) return false; // TEST: disable during tests else scrib_lib_title will try to call WMF API; DATE:2015-03-20			
    +		String src = Bld_api_url(repo_wiki_key, ttl, width, height);
    +		// xrg.Prog_fmt_hdr_(); // TOMBSTONE: do not uncomment; api will reuse whatever's in place
    +		byte[] xml = download_wkr.Download_xrg().Exec_as_bry(src);
    +		return xml == null ? false : Parse_xml(rv, usr_dlg, xml);
    +	}
    +	public static boolean Parse_xml(Xoapi_orig_rslts rv, Gfo_usr_dlg usr_dlg, byte[] xml) {
    +		synchronized (tmp_rng) {
    +			rv.Clear();
    +			int xml_len = xml.length;
    +			int pos = 0;
    +			pos = Bry_find_.Find_fwd(xml, Bry_xml_ii			, pos, xml_len); 
    +			if (pos == Bry_find_.Not_found) {usr_dlg.Log_many(GRP_KEY, "api_failed", "api failed: ~{0}", String_.new_u8(xml)); return false;}
    +			pos += Bry_xml_ii.length;
    +
    +			byte[] orig_wiki = null, orig_page = null; int orig_w = 0, orig_h = 0;
    +			if (Parse_xml_val(tmp_rng, usr_dlg, xml, xml_len, pos, Bry_xml_width))
    +				orig_w = Bry_.To_int_or(xml, tmp_rng.Val_0(), tmp_rng.Val_1(), 0);
    +
    +			if (Parse_xml_val(tmp_rng, usr_dlg, xml, xml_len, pos, Bry_xml_height))
    +				orig_h = Bry_.To_int_or(xml, tmp_rng.Val_0(), tmp_rng.Val_1(), 0);
    +
    +			if (Parse_xml_val(tmp_rng, usr_dlg, xml, xml_len, pos, Bry_xml_descriptionurl)) {
    +				byte[] file_url = Bry_.Mid(xml, tmp_rng.Val_0(), tmp_rng.Val_1());
    +				Gfo_url url = url_parser.Parse(file_url, 0, file_url.length);
    +				orig_wiki = url.Segs__get_at_1st();
    +				byte[] page = Xoa_ttl.Replace_spaces(url.Segs__get_at_nth());
    +				int colon_pos = Bry_find_.Find_fwd(page, Byte_ascii.Colon, 0, page.length);
    +				if (colon_pos != Bry_find_.Not_found)
    +					page = Bry_.Mid(page, colon_pos + 1, page.length);
    +				orig_page = page;
    +			}
    +			rv.Init_all(orig_wiki, orig_page, orig_w, orig_h);
    +			return true;
    +		}
    +	}
    +	private static Int_2_ref tmp_rng = new Int_2_ref();
    +	private static Gfo_url_parser url_parser = new Gfo_url_parser();
    +	private static boolean Parse_xml_val(Int_2_ref rv, Gfo_usr_dlg usr_dlg, byte[] xml, int xml_len, int pos, byte[] key) {
    +		int bgn = 0, end = 0;
    +		bgn = Bry_find_.Find_fwd(xml, key, pos, xml_len); if (bgn == Bry_find_.Not_found) return false;
    +		bgn += key.length;
    +		end = Bry_find_.Find_fwd(xml, Byte_ascii.Quote	, bgn, xml_len); if (end == Bry_find_.Not_found) return false;
    +		rv.Val_all_(bgn, end);
    +		return true;
    +	}
    +	public static String Bld_api_url(byte[] wiki_key, byte[] ttl, int width, int height) {
    +		synchronized (tmp_bfr) {
    +			tmp_bfr.Add(Xoh_href_.Bry__https)						// "https://"
    +				.Add(wiki_key)										// "commons.wikimedia.org"
    +				.Add(Bry_api)										// "/w/api.php?action=query&format=xml&prop=imageinfo&iiprop=size|url&titles=File:"
    +				.Add(tmp_encoder.Encode(ttl))						// "A%20B.png"
    +				;
    +			if (width > 0)
    +				tmp_bfr.Add(Bry_width).Add_int_variable(width);		// "&iiurlwidth=800"
    +			if (height > 0 && width > 0)							// NOTE: height cannot be used alone; width must also exist; "iiurlheight cannot be used without iiurlwidth"
    +				tmp_bfr.Add(Bry_height).Add_int_variable(height);	// "&iiurlheight=600"
    +			return tmp_bfr.To_str_and_clear();
    +		}
    +	}
    +	private static Gfo_url_encoder tmp_encoder = Gfo_url_encoder_.New__http_url().Init__diff__one(Byte_ascii.Space, Byte_ascii.Underline).Make();
    +	private static final    Bry_bfr tmp_bfr = Bry_bfr_.New();
    +	private static final    byte[]
    +	  Bry_api					= Bry_.new_a7("/w/api.php?action=query&format=xml&prop=imageinfo&iiprop=size|url&redirects&titles=File:")	// NOTE: using File b/c it should be canonical
    +	, Bry_width					= Bry_.new_a7("&iiurlwidth=")
    +	, Bry_height				= Bry_.new_a7("&iiurlheight=")
    +	, Bry_xml_ii				= Bry_.new_a7("");
    +	}
    +	public void Rls() {
    +		this.Wiki().Db_mgr_as_sql().Core_data_mgr().Rls();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/Xob_base_fxt.java b/400_xowa/src/gplx/xowa/bldrs/Xob_base_fxt.java
    index a27517de8..68a69aa33 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/Xob_base_fxt.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/Xob_base_fxt.java
    @@ -13,3 +13,65 @@ 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.bldrs; import gplx.*; import gplx.xowa.*;
    +import gplx.core.ios.*;
    +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.wikis.data.tbls.*;
    +public class Xob_base_fxt {
    +	public Xob_base_fxt Clear() {
    +		if (app == null) {
    +			app = Xoa_app_fxt.Make__app__edit();
    +			wiki = Xoa_app_fxt.Make__wiki__edit(app);
    +			bldr = Xoa_app_fxt.bldr_(app);
    +		}
    +		this.Init_(bldr, wiki);
    +		Clear_hook();
    +		return this;
    +	}
    +	@gplx.Virtual public void Clear_hook() {}
    +	public Xob_base_fxt Init_(Xob_bldr bldr, Xowe_wiki wiki) {this.bldr = bldr; this.wiki = wiki; return this;}
    +	public Xoae_app App() {return app;} private Xoae_app app;
    +	public Xob_bldr Bldr() {return bldr;} private Xob_bldr bldr;
    +	public Xowe_wiki Wiki() {return wiki;} private Xowe_wiki wiki;
    +	public Gfo_invk Bldr_itm() {return bldr_itm;} Gfo_invk bldr_itm;
    +	public Xowd_page_itm page_(String ttl) {return page_(ttl, "");}
    +	public Xowd_page_itm page_(String ttl, String text) {return new Xowd_page_itm().Ttl_(Bry_.new_u8(ttl), wiki.Ns_mgr()).Text_(Bry_.new_u8(text));}
    +	public Io_fil_chkr meta_(String url, String data) {return new Io_fil_chkr(Io_url_.mem_fil_(url), data);}
    +	public void Init_fxts(Xob_bldr bldr, Xowe_wiki wiki, Xob_base_fxt... fxt_ary) {
    +		int fxt_ary_len = fxt_ary.length;
    +		for (int i = 0; i < fxt_ary_len; i++)
    +			fxt_ary[i].Init_(bldr, wiki);
    +	}
    +	public Xob_base_fxt Init_fil(String url, String raw) {return Init_fil(Io_url_.new_fil_(url), raw);}
    +	public Xob_base_fxt Init_fil(Io_url url, String raw) {Io_mgr.Instance.SaveFilStr(url, raw); return this;}
    +	public Xob_base_fxt Exec_cmd(String cmd_key, GfoMsg... msgs) {
    +		Xob_cmd cmd = (Xob_cmd)bldr.Cmd_mgr().Add_cmd(wiki, cmd_key);
    +		this.bldr_itm = cmd;
    +		int len = msgs.length;
    +		GfsCtx ctx = GfsCtx.new_();
    +		for (int i = 0; i < len; i++) {
    +			GfoMsg msg = msgs[i];
    +			cmd.Invk(ctx, GfsCtx.Ikey_null, msg.Key(), msg);
    +		}
    +		Run_cmd(bldr, cmd);
    +		return this;
    +	}
    +	public Xob_base_fxt Test_fil(String url, String expd) {return Test_fil(Io_url_.new_fil_(url), expd);}
    +	public Xob_base_fxt Test_fil(Io_url url, String expd) {
    +		Tfds.Eq_str_lines(expd, Io_mgr.Instance.LoadFilStr(url));
    +		return this;
    +	}
    +	public static void Run_cmd(Xob_bldr bldr, Xob_cmd cmd) {
    +		cmd.Cmd_bgn(bldr);
    +		cmd.Cmd_run();
    +		cmd.Cmd_end();
    +	}
    +	public static void Run_wkr(Xob_bldr bldr, Xob_page_wkr wkr, Xowd_page_itm[] page_ary) {
    +		wkr.Page_wkr__bgn();
    +		int page_ary_len = page_ary.length;
    +		for (int i = 0; i < page_ary_len; i++) {
    +			Xowd_page_itm page = page_ary[i];
    +			wkr.Page_wkr__run(page);
    +		}
    +		wkr.Page_wkr__end();		
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/Xob_bldr.java b/400_xowa/src/gplx/xowa/bldrs/Xob_bldr.java
    index a27517de8..5e2d6b934 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/Xob_bldr.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/Xob_bldr.java
    @@ -13,3 +13,155 @@ 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.bldrs; import gplx.*; import gplx.xowa.*;
    +import gplx.core.consoles.*; import gplx.core.envs.*;
    +import gplx.xowa.apps.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.xmls.*; import gplx.xowa.langs.bldrs.*;
    +import gplx.xowa.bldrs.wkrs.*;
    +import gplx.langs.jsons.*;
    +import gplx.xowa.addons.bldrs.app_cfgs.*;
    +public class Xob_bldr implements Gfo_invk {
    +	private boolean pause_at_end = false; private long prv_prog_time; private Xob_xml_parser dump_parser;
    +	public Xob_bldr(Xoae_app app) {
    +		this.app = app;
    +		this.cmd_mgr = new Xob_cmd_mgr(this, cmd_regy);
    +		this.import_marker = new Xob_import_marker();
    +		this.wiki_cfg_bldr = new Xob_wiki_cfg_bldr(this);
    +	}
    +	public Xoae_app				App() {return app;} private final    Xoae_app app;
    +	public Xob_cmd_regy			Cmd_regy() {return cmd_regy;} private final    Xob_cmd_regy cmd_regy = new Xob_cmd_regy();
    +	public Xob_cmd_mgr			Cmd_mgr() {return cmd_mgr;} private final    Xob_cmd_mgr cmd_mgr;
    +	public Gfo_usr_dlg			Usr_dlg() {return app.Usr_dlg();}
    +	public int					Sort_mem_len() {return sort_mem_len;} public Xob_bldr Sort_mem_len_(int v) {sort_mem_len = v; return this;} private int sort_mem_len = 16 * Io_mgr.Len_mb;
    +	public int					Dump_fil_len() {return dump_fil_len;} public Xob_bldr Dump_fil_len_(int v) {dump_fil_len = v; return this;} private int dump_fil_len =  1 * Io_mgr.Len_mb;
    +	public int					Make_fil_len() {return make_fil_len;} public Xob_bldr Make_fil_len_(int v) {make_fil_len = v; return this;} private int make_fil_len = 64 * Io_mgr.Len_kb;
    +	public Xob_xml_parser		Dump_parser() {if (dump_parser == null) this.dump_parser = new Xob_xml_parser(); return dump_parser;}
    +	public Xob_import_marker	Import_marker() {return import_marker;} private Xob_import_marker import_marker;
    +	public Xob_wiki_cfg_bldr	Wiki_cfg_bldr() {return wiki_cfg_bldr;} private Xob_wiki_cfg_bldr wiki_cfg_bldr;
    +	public void					Pause_at_end_(boolean v) {this.pause_at_end = v;}
    +	public void					Print_prog_msg(long cur, long end, int pct_idx, String fmt, Object... ary) {
    +		long now = System_.Ticks(); if (now - prv_prog_time < 100) return;
    +		this.prv_prog_time = now;
    +		if (pct_idx > -1) ary[pct_idx] = Decimal_adp_.CalcPctStr(cur, end, "00.00");
    +		app.Usr_dlg().Prog_many("", "", fmt, ary);
    +	}
    +	public Xob_bldr Exec_json(String script) {
    +		try {
    +			this.cmd_mgr.Clear();
    +			Json_parser jdoc_parser = new Json_parser();
    +			Json_doc jdoc = jdoc_parser.Parse(script);
    +			Json_ary cmds = jdoc.Root_ary();
    +			int cmds_len = cmds.Len();
    +			for (int i = 0; i < cmds_len; ++i) {
    +				Json_nde cmd = cmds.Get_at_as_nde(i);
    +				byte[] key = cmd.Get_bry_or_null("key");
    +				Xob_cmd prime = cmd_regy.Get_or_null(String_.new_u8(key));
    +				if (prime == null) throw Err_.new_("bldr", "bldr.cmd does not exists: cmd={0}", key);
    +				byte[] wiki_key = cmd.Get_bry_or_null("wiki");
    +				Xowe_wiki wiki = wiki_key == null ? app.Usere().Wiki() : app.Wiki_mgr().Get_by_or_make(wiki_key);
    +				Xob_cmd clone = prime.Cmd_clone(this, wiki);
    +				int atrs_len = cmd.Len();
    +				for (int j = 0; j < atrs_len; ++j) {
    +					Json_kv atr_kv = cmd.Get_at_as_kv(j);
    +					String atr_key = atr_kv.Key_as_str();
    +					if	(	String_.Eq(atr_key, "key")
    +						||	String_.Eq(atr_key, "wiki"))	continue;
    +					byte[] atr_val = atr_kv.Val_as_bry();
    +					Gfo_invk_.Invk_by_val(clone, atr_key + Gfo_invk_.Mutator_suffix, String_.new_u8(atr_val));
    +				}
    +				cmd_mgr.Add(clone);
    +			}
    +			gplx.core.threads.Thread_adp_.Start_by_key("bldr_by_json", this, Invk_run_by_kit);
    +		} catch (Exception e) {
    +			app.Gui_mgr().Kit().Ask_ok("", "", "error: ~{0}", Err_.Message_gplx_log(e));
    +		}
    +		return this;
    +	}
    +	private void Run_by_kit() {	// same as Run, but shows exception; don't want to change backward compatibility on Run
    +		try {this.Run();}
    +		catch (Exception e) {
    +			String log_msg = Err_.Message_gplx_log(e);
    +			Xoa_app_.Usr_dlg().Log_many("", "", log_msg);
    +			app.Gui_mgr().Kit().Ask_ok("", "", "error: ~{0}", Err_.Message_gplx_full(e));
    +		}
    +	}
    +	public void Run() {
    +		try {
    +			app.Bldr__running_(true);
    +			app.Launch();	// HACK: bldr will be called by a gfs file which embeds "bldr.run" inside it; need to call Launch though before Run; DATE:2013-03-23
    +			long time_bgn = System_.Ticks();
    +			int cmd_mgr_len = cmd_mgr.Len();
    +			for (int i = 0; i < cmd_mgr_len; i++) {
    +				Xob_cmd cmd = cmd_mgr.Get_at(i);
    +				cmd.Cmd_init(this);
    +			}
    +			cmd_mgr_len = cmd_mgr.Len(); // NOTE: refresh len b/c other cmds may have added new ones in Cmd_init
    +			for (int i = 0; i < cmd_mgr_len; i++) {
    +				Xob_cmd cmd = cmd_mgr.Get_at(i);
    +				app.Usr_dlg().Note_many("", "", "cmd bgn: ~{0}", cmd.Cmd_key());
    +				long time_cur = System_.Ticks();
    +				try {
    +					cmd.Cmd_bgn(this);
    +					cmd.Cmd_run();
    +					cmd.Cmd_end();
    +				} catch (Exception e) {
    +					throw Err_.new_exc(e, "bldr", "unknown error", "key", cmd.Cmd_key());
    +				}
    +				System_.Garbage_collect();
    +				app.Usr_dlg().Note_many("", "", "cmd end: ~{0} ~{1}", cmd.Cmd_key(), Time_span_.from_(time_cur).XtoStrUiAbbrv());
    +			}
    +			for (int i = 0; i < cmd_mgr_len; i++) {
    +				Xob_cmd cmd = cmd_mgr.Get_at(i);
    +				cmd.Cmd_term();
    +			}
    +			app.Usr_dlg().Note_many("", "", "bldr done: ~{0}", Time_span_.from_(time_bgn).XtoStrUiAbbrv());
    +			cmd_mgr.Clear();
    +			if (pause_at_end && !Env_.Mode_testing()) {Console_adp__sys.Instance.Read_line("press enter to continue");}
    +		}
    +		catch (Exception e) {
    +			app.Bldr__running_(false);
    +			throw Err_.new_exc(e, "bldr", "unknown error");
    +		}
    +	}
    +	private void Cancel() {
    +		int cmd_mgr_len = cmd_mgr.Len();
    +		for (int i = 0; i < cmd_mgr_len; i++) {
    +			Xob_cmd cmd = cmd_mgr.Get_at(i);
    +			cmd.Cmd_end();
    +		}
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_pause_at_end_))			pause_at_end = m.ReadBoolOrTrue("val");
    +		else if	(ctx.Match(k, Invk_cmds))					return cmd_mgr;
    +		else if	(ctx.Match(k, Invk_wiki_cfg_bldr))			return wiki_cfg_bldr;
    +		else if	(ctx.Match(k, Invk_sort_mem_len_)) 			sort_mem_len = gplx.core.ios.Io_size_.Load_int_(m);
    +		else if	(ctx.Match(k, Invk_dump_fil_len_)) 			dump_fil_len = gplx.core.ios.Io_size_.Load_int_(m);
    +		else if	(ctx.Match(k, Invk_make_fil_len_)) 			make_fil_len = gplx.core.ios.Io_size_.Load_int_(m);
    +		else if	(ctx.Match(k, Invk_run)) 					Run();
    +		else if	(ctx.Match(k, Invk_run_by_kit)) 			Run_by_kit();
    +		else if	(ctx.Match(k, Invk_cancel)) 				Cancel();
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	private static final String 
    +	  Invk_cmds = "cmds", Invk_wiki_cfg_bldr = "wiki_cfg_bldr"
    +	, Invk_pause_at_end_ = "pause_at_end_", Invk_sort_mem_len_ = "sort_mem_len_", Invk_dump_fil_len_ = "dump_fil_len_", Invk_make_fil_len_ = "make_fil_len_"
    +	, Invk_cancel = "cancel"
    +	, Invk_run_by_kit = "run_by_kit"
    +	;
    +	public static final String Invk_run = "run";
    +}
    +/*
    +. make_fil_len: max size of made file; EX: /id/..../0000000001.csv will have max len of 64 KB
    +. dump_fil_len: max size of temp file; EX: /tmp/.../0000000001.csv will have max len of 1 MB
    +. sort_mem_len: max size of memory for external merge process; note the following
    +.. a continguous range of memory of that size will be needed: "Bry_bfr_.New(sort_mem_len)" will be called
    +.. large sort_mem_len will result in smaller number of merge files
    +... EX: 16 MB will take en.wikipedia.org's 640 MB title files and generate 40 temp files of 8 MB each
    +.. number of merge files is number of open file channels during merge process
    +... 40 is a "reasonable" number; the 1st max is 512 (for older windows OS's) and 2048 for Windows XP; Linux seems to be about 7000
    +.. small sort_mem_len will use smaller buffer; 16 MB / 40 files -> 400 kb buffer for each file
    +... do not go under max page size for a given row
    +... for example, a 100 b buffer will fail if a given row is > 100 b (the entire row won't be loaded in memory)
    +.. smaller buffer will mean more refills which will require more I/O
    +... EX: 400 kb buffer will require at least 20 refills to read the entire 8 MB file
    +*/
    diff --git a/400_xowa/src/gplx/xowa/bldrs/Xob_cmd_keys.java b/400_xowa/src/gplx/xowa/bldrs/Xob_cmd_keys.java
    index a27517de8..f4c366029 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/Xob_cmd_keys.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/Xob_cmd_keys.java
    @@ -13,3 +13,37 @@ 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.bldrs; import gplx.*; import gplx.xowa.*;
    +public class Xob_cmd_keys {
    +	public static final String
    +	  Key_text_init					= "text.init"				// "import.sql.init"
    +	, Key_text_page					= "text.page"				// "import.sql.page"
    +	, Key_text_css					= "text.css"
    +	, Key_text_search_cmd			= "text.search.cmd"			// "import.sql.search_title.cmd"
    +	, Key_text_search_wkr			= "text.search"				// "import.sql.search_title.wkr"
    +	, Key_text_term					= "text.term"				// "import.sql.term"
    +	, Key_html_redlinks				= "html.redlinks"
    +	, Key_util_cleanup				= "util.cleanup"			// "core.cleanup"
    +	, Key_util_download				= "util.download"			// "file.download"
    +	, Key_util_xml_dump				= "util.xml_dump"
    +	, Key_util_random				= "util.random"
    +	, Key_util_delete				= "util.delete"
    +	, Key_wbase_qid					= "wbase.qid"				// "text.wdata.qid"
    +	, Key_wbase_pid					= "wbase.pid"				// "text.wdata.pid"
    +	, Key_wbase_db					= "wbase.db"				// "wiki.wdata_db"
    +	, Key_site_meta					= "util.site_meta"
    +	, Key_diff_build				= "diff.build"
    +	, Key_diff_merge				= "diff.merge"
    +	, Key_text_delete_page			= "text.delete_page"
    +
    +	, Key_tdb_text_init				= "tdb.text.init"			// "core.init"
    +	, Key_tdb_make_page				= "tdb.text.page"			// "core.make_page"
    +	, Key_tdb_make_id				= "core.make_id"
    +	, Key_tdb_calc_stats			= "core.calc_stats"
    +	, Key_tdb_text_wdata_qid		= "tdb.text.wdata.qid"
    +	, Key_tdb_text_wdata_pid		= "tdb.text.wdata.pid"
    +	, Key_exec_sql					= "import.sql.exec_sql"
    +	, Key_decompress_bz2			= "core.decompress_bz2"
    +	;
    +}
    + 
    \ No newline at end of file
    diff --git a/400_xowa/src/gplx/xowa/bldrs/Xob_cmd_mgr.java b/400_xowa/src/gplx/xowa/bldrs/Xob_cmd_mgr.java
    index a27517de8..fa5271c5e 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/Xob_cmd_mgr.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/Xob_cmd_mgr.java
    @@ -13,3 +13,118 @@ 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.bldrs; import gplx.*; import gplx.xowa.*;
    +import gplx.core.primitives.*;
    +import gplx.xowa.wikis.*; import gplx.xowa.xtns.wbases.imports.*;
    +import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.bldrs.cmds.texts.*; import gplx.xowa.bldrs.cmds.texts.sqls.*; import gplx.xowa.bldrs.cmds.texts.tdbs.*; import gplx.xowa.addons.bldrs.files.*; import gplx.xowa.addons.wikis.ctgs.bldrs.*; import gplx.xowa.bldrs.cmds.utils.*;
    +import gplx.xowa.bldrs.cmds.diffs.*;
    +import gplx.xowa.files.origs.*; import gplx.xowa.htmls.core.bldrs.*;
    +import gplx.xowa.addons.wikis.searchs.bldrs.*;
    +import gplx.xowa.addons.bldrs.files.cmds.*; import gplx.xowa.addons.wikis.htmls.css.bldrs.*;
    +public class Xob_cmd_mgr implements Gfo_invk {
    +	private final    Xob_bldr bldr;
    +	public final    Xob_cmd_regy cmd_regy;
    +	public Xob_cmd_mgr(Xob_bldr bldr, Xob_cmd_regy cmd_regy) {this.bldr = bldr; this.cmd_regy = cmd_regy;}
    +	public void Clear() {list.Clear(); dump_rdrs.Clear();}
    +	public int Len() {return list.Count();} private final    List_adp list = List_adp_.New();
    +	public Xob_cmd Get_at(int i) {return (Xob_cmd)list.Get_at(i);} 
    +	public Xob_cmd Add(Xob_cmd cmd) {list.Add(cmd); return cmd;}
    +	public Gfo_invk Add_cmd(Xowe_wiki wiki, String cmd_key) {
    +		Xob_cmd prime = cmd_regy.Get_or_null(cmd_key);
    +		if (prime != null) {
    +			Xob_cmd clone = prime.Cmd_clone(bldr, wiki);
    +			Add(clone);
    +			return clone;
    +		}
    +		if		(String_.Eq(cmd_key, Xob_cmd_keys.Key_text_init))					return Add(new Xob_init_cmd(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_text_page))					return Xml_rdr_direct_add(wiki, new Xob_page_cmd(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_text_css))					return Add(new Xob_css_cmd(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_text_search_wkr))				return Xml_rdr_direct_add(wiki, new gplx.xowa.addons.wikis.searchs.bldrs.Srch_bldr_wkr(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_text_search_cmd))				return Add(new Srch_bldr_cmd(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_text_term))					return Add(new Xob_term_cmd(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_text_delete_page))			return Add(new Xob_page_delete_cmd(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_html_redlinks))				return Add(new Xob_redlink_mkr_cmd(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_util_cleanup))				return Add(new Xob_cleanup_cmd(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_util_delete))					return Add(new Xob_delete_cmd(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_util_download))				return Add(new Xob_download_cmd(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_util_xml_dump))				return Add(new Xob_xml_dumper_cmd(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_wbase_qid))					return Xml_rdr_direct_add(wiki, new Xob_wdata_qid_sql().Ctor(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_wbase_pid))					return Xml_rdr_direct_add(wiki, new Xob_wdata_pid_sql().Ctor(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_wbase_db))					return Add(new Xob_wdata_db_cmd(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_site_meta))					return Add(new Xob_site_meta_cmd(bldr, wiki));
    +
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_tdb_text_init))				return Add(new Xob_init_tdb(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_tdb_make_id))					return Xml_rdr_direct_add(wiki, new Xob_make_id_wkr(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_tdb_calc_stats))				return Add(new Xob_calc_stats_cmd(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_tdb_text_wdata_qid))			return Xml_rdr_direct_add(wiki, new Xob_wdata_qid_txt().Ctor(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_tdb_text_wdata_pid))			return Xml_rdr_direct_add(wiki, new Xob_wdata_pid_txt().Ctor(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_diff_build))					return Add(new Xob_diff_build_cmd(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_exec_sql))					return Add(new Xob_exec_sql_cmd(bldr, wiki));
    +		else if	(String_.Eq(cmd_key, Xob_cmd_keys.Key_decompress_bz2))				return Add(new Xob_decompress_bz2_cmd(bldr, wiki));
    +		else 																		throw Err_.new_unimplemented_w_msg("builder command is not supported: " + cmd_key);
    +	}
    +	private Xob_page_wkr Xml_rdr_direct_add(Xowe_wiki wiki, Xob_page_wkr wkr) {
    +		Xob_page_wkr_cmd dump_rdr = Xml_rdr_get(wiki);
    +		dump_rdr.Wkr_add(wkr);
    +		return wkr;
    +	}
    +	private Xob_page_wkr_cmd Xml_rdr_get(Xowe_wiki wiki) {
    +		byte[] wiki_key = wiki.Domain_bry();
    +		Xob_page_wkr_cmd rv = (Xob_page_wkr_cmd)dump_rdrs.Get_by(dump_rdrs_ref.Val_(wiki_key));
    +		if (rv == null) {
    +			rv = new Xob_page_wkr_cmd(bldr, wiki);
    +			dump_rdrs.Add(Bry_obj_ref.New(wiki_key), rv);
    +			this.Add(rv);
    +		}
    +		return rv;
    +	}
    +	private Hash_adp dump_rdrs = Hash_adp_.New(); private Bry_obj_ref dump_rdrs_ref = Bry_obj_ref.New_empty();
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if			(ctx.Match(k, Invk_add))				return Add_cmd(Wiki_get_or_make(m), m.ReadStr("v"));
    +		else if		(ctx.Match(k, Invk_add_many))			return Add_many(m);
    +		else if		(ctx.Match(k, Invk_get_first))			return Get_first(m);
    +		else if		(ctx.Match(k, Invk_new_batch))			return new Xob_core_batch_utl(bldr, m.ReadBry("v"));
    +		else	return Gfo_invk_.Rv_unhandled;
    +	}
    +	private static final String Invk_add = "add", Invk_add_many = "add_many", Invk_new_batch = "new_batch", Invk_get_first = "get_first";
    +	private Object Get_first(GfoMsg m) {
    +		String cmd_key = m.ReadStr("v");
    +		int cmds_len = list.Count();
    +		for (int i = 0;i < cmds_len; i++) {
    +			Xob_cmd cmd = (Xob_cmd)list.Get_at(i);
    +			if (String_.Eq(cmd.Cmd_key(), cmd_key)) return cmd;
    +		}
    +		throw Err_.new_wo_type("cmd not found", "key", cmd_key);
    +	}
    +	private Object Add_many(GfoMsg m) {
    +		Xowe_wiki wiki = Wiki_get_or_make(m);
    +		wiki.Lang().Init_by_load_assert();	// NOTE: must check that lang is loaded; else case_mgr will not initialize; DATE:2013-05-11
    +		int args_len = m.Args_count();
    +		String[] cmds = new String[args_len - 1];	// -1 b/c 1st arg is wiki
    +		for (int i = 1; i < args_len; i++) {
    +			Keyval kv = m.Args_getAt(i);
    +			cmds[i - 1] = kv.Val_to_str_or_empty();
    +		}
    +		return Add_many(wiki, cmds);
    +	}
    +	public Object Add_many(Xowe_wiki wiki, String... cmds) {
    +		int len = cmds.length; if (len == 0) throw Err_.new_wo_type("add_many cannot have 0 cmds");
    +		Object rv = null;
    +		for (int i = 0; i < len; i++)
    +			rv = Add_cmd(wiki, cmds[i]);
    +		return rv;
    +	}
    +	public void Add_cmd_ary(Xob_cmd... cmds_ary) {
    +		int cmds_len = cmds_ary.length;
    +		for (int i = 0; i < cmds_len; ++i)
    +			this.Add(cmds_ary[i]);
    +	}
    +	private Xowe_wiki Wiki_get_or_make(GfoMsg m) {
    +		byte[] wiki_key = m.ReadBry("v");
    +		Xoae_wiki_mgr wiki_mgr = bldr.App().Wiki_mgr();
    +		Xowe_wiki rv = wiki_mgr.Get_by_or_make(wiki_key);
    +		rv.Lang().Init_by_load();
    +		return rv;
    +	}
    +	public static final String GRP_KEY = "xowa.bldr.cmds";
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/Xob_cmd_regy.java b/400_xowa/src/gplx/xowa/bldrs/Xob_cmd_regy.java
    index a27517de8..8ee6f9052 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/Xob_cmd_regy.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/Xob_cmd_regy.java
    @@ -13,3 +13,16 @@ 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.bldrs; import gplx.*; import gplx.xowa.*;
    +import gplx.xowa.bldrs.wkrs.*;
    +public class Xob_cmd_regy {
    +	private final    Ordered_hash regy = Ordered_hash_.New();
    +	public Xob_cmd Get_or_null(String key) {return (Xob_cmd)regy.Get_by(key);}
    +	public void Add_many(Xob_cmd... ary) {
    +		int len = ary.length;
    +		for (int i = 0; i < len; ++i) {
    +			Xob_cmd cmd = ary[i];
    +			regy.Add(cmd.Cmd_key(), cmd);
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/Xob_db_file.java b/400_xowa/src/gplx/xowa/bldrs/Xob_db_file.java
    index a27517de8..4eb7a4953 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/Xob_db_file.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/Xob_db_file.java
    @@ -13,3 +13,42 @@ 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.bldrs; import gplx.*; import gplx.xowa.*;
    +import gplx.dbs.*; import gplx.dbs.cfgs.*;
    +public class Xob_db_file {
    +	Xob_db_file(Io_url url, Db_conn conn) {
    +		this.url = url; this.conn = conn;
    +		this.tbl__cfg = gplx.xowa.wikis.data.Xowd_cfg_tbl_.New(conn);
    +	}
    +	public Io_url			Url()		{return url;} private final    Io_url url;
    +	public Db_conn			Conn()		{return conn;} private final    Db_conn conn;
    +	public Db_cfg_tbl		Tbl__cfg()	{return tbl__cfg;} private final    Db_cfg_tbl tbl__cfg;
    +	public static Xob_db_file New__file_make(Io_url dir)			{return New(dir, Name__file_make);}
    +	public static Xob_db_file New__page_regy(Io_url dir)			{return New(dir, Name__page_regy);}
    +	public static Xob_db_file New__wiki_image(Io_url dir)			{return New(dir, Name__wiki_image);}
    +	public static Xob_db_file New__wiki_redirect(Io_url dir)		{return New(dir, Name__wiki_redirect);}
    +	public static Xob_db_file New__temp_log(Io_url dir)				{return New(dir, Name__temp_log);}
    +	public static Xob_db_file New__redlink(Io_url dir)				{return New(dir, Name__redlink);}
    +	public static Xob_db_file New__page_link(Xow_wiki wiki)			{return New(wiki.Fsys_mgr().Root_dir(), Name__page_link);}
    +	public static Xob_db_file New__page_file_map(Xow_wiki wiki)		{return New(wiki.Fsys_mgr().Root_dir(), wiki.Domain_str() + "-file-page_map.xowa");}
    +	public static Xob_db_file New__img_link(Xow_wiki wiki)			{return New(wiki.Fsys_mgr().Root_dir(), "xowa.wiki.imglinks.sqlite3");}
    +	public static Xob_db_file New__deletion_db(Xow_wiki wiki)		{
    +		String name = String_.Format("{0}-file-deletion-{1}.xowa", wiki.Domain_str(), Datetime_now.Get().XtoStr_fmt("yyyy.MM"));
    +		return New(wiki.Fsys_mgr().Root_dir(), name);
    +	}
    +	public static Xob_db_file New(Io_url dir, String name) {
    +		Io_url url = dir.GenSubFil(name);
    +		Db_conn_bldr_data conn_data = Db_conn_bldr.Instance.Get_or_new(url);
    +		Db_conn conn = conn_data.Conn();
    +		Xob_db_file rv = new Xob_db_file(url, conn);
    +		if (conn_data.Created())
    +			rv.Tbl__cfg().Create_tbl();
    +		return rv;
    +	}
    +	public static final String 
    +	  Name__wiki_image = "xowa.wiki.image.sqlite3", Name__wiki_redirect = "xowa.wiki.redirect.sqlite3"
    +	, Name__file_make = "xowa.file.make.sqlite3", Name__temp_log = "xowa.temp.log.sqlite3"
    +	, Name__page_regy = "xowa.file.page_regy.sqlite3", Name__redlink = "xowa.temp.redlink.sqlite3"
    +	, Name__page_link = "xowa.wiki.pagelinks.sqlite3"
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/Xob_fxt.java b/400_xowa/src/gplx/xowa/bldrs/Xob_fxt.java
    index a27517de8..e02cf2b16 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/Xob_fxt.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/Xob_fxt.java
    @@ -13,3 +13,140 @@ 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.bldrs; import gplx.*; import gplx.xowa.*;
    +import gplx.core.tests.*; import gplx.core.ios.*; import gplx.core.times.*;
    +import gplx.dbs.*; import gplx.xowa.wikis.tdbs.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.bldrs.cmds.texts.tdbs.*;
    +import gplx.xowa.bldrs.wkrs.*;
    +public class Xob_fxt {
    +	public Xob_fxt Ctor_mem() {
    +		Io_mgr.Instance.InitEngine_mem();
    +		return Ctor(Io_url_.mem_dir_("mem/xowa/"));
    +	}
    +	public Xob_fxt Ctor(Io_url root_dir) {
    +		Db_conn_bldr.Instance.Reg_default_sqlite();
    +		app = Xoa_app_fxt.Make__app__edit("linux", root_dir);
    +		wiki = Xoa_app_fxt.Make__wiki__edit(app);
    +		bldr = Xoa_app_fxt.bldr_(app);
    +		return this;
    +	}
    +	public Xoae_app App() {return app;} private Xoae_app app;
    +	public Xob_bldr Bldr() {return bldr;} private Xob_bldr bldr;
    +	public Xowe_wiki Wiki() {return wiki;} private Xowe_wiki wiki;
    +	public Io_url fil_ns_title(int ns_id, int idx)	{return wiki.Tdb_fsys_mgr().Url_ns_fil(Xotdb_dir_info_.Tid_ttl, ns_id, idx);}
    +	public Io_url fil_ns_page(int ns_id, int idx)	{return wiki.Tdb_fsys_mgr().Url_ns_fil(Xotdb_dir_info_.Tid_page, ns_id, idx);}
    +	public Io_url fil_ns_sttl(int ns_id, int idx)	{return wiki.Tdb_fsys_mgr().Url_ns_fil(Xotdb_dir_info_.Tid_search_ttl, ns_id, idx);}
    +	public Io_url fil_site(byte tid, int idx)		{return wiki.Tdb_fsys_mgr().Url_site_fil(tid, idx);}
    +	public Io_url fil_site_ctg(int idx)				{return wiki.Tdb_fsys_mgr().Url_site_fil(Xotdb_dir_info_.Tid_category, idx);}
    +	public Io_url fil_site_id(int idx)				{return wiki.Tdb_fsys_mgr().Url_site_fil(Xotdb_dir_info_.Tid_id, idx);}
    +	public Io_url fil_reg(byte tid) 				{return wiki.Tdb_fsys_mgr().Url_site_reg(tid);}
    +	public Io_url fil_reg(int ns_id, byte tid) 		{return wiki.Tdb_fsys_mgr().Url_ns_reg(Int_.To_str_pad_bgn_zero(ns_id, 3), tid);}
    +	public Xob_fxt Fil_expd(Io_url url, String... expd) {
    +		String text = String_.Concat_lines_nl_skip_last(expd);	// skipLast b/c if trailing line wanted, easier to pass in extra argument for ""
    +		expd_list.Add(new Io_fil_chkr(url, text));
    +		return this;
    +	} List_adp expd_list = List_adp_.New();
    +	public Xob_fxt Fil_skip(Io_url... urls) {
    +		for (int i = 0; i < urls.length; i++)
    +			skip_list.Add(urls[i]);
    +		return this;
    +	} 	List_adp skip_list = List_adp_.New();
    +	public Xob_fxt doc_ary_(Xowd_page_itm... v) {doc_ary = v; return this;} private Xowd_page_itm[] doc_ary;
    +	public Xowd_page_itm doc_wo_date_(int id, String title, String text) {return doc_(id, "2012-01-02 13:14", title, text);}
    +	public Xowd_page_itm doc_(int id, String date, String title, String text) {
    +		Xowd_page_itm rv = new Xowd_page_itm().Id_(id).Ttl_(Bry_.new_u8(title), wiki.Ns_mgr()).Text_(Bry_.new_u8(text));
    +		int[] modified_on = new int[7];
    +		dateParser.Parse_iso8651_like(modified_on, date);
    +		rv.Modified_on_(DateAdp_.seg_(modified_on));
    +		return rv;
    +	}
    +	public Xob_fxt Run_id() {
    +		Xob_make_id_wkr wkr = new Xob_make_id_wkr(bldr, wiki);
    +		Run(wkr);
    +		return this;
    +	}
    +	private void Run_wkr(Xob_page_wkr wkr) {
    +		wkr.Page_wkr__bgn();
    +		for (int i = 0; i < doc_ary.length; i++) {
    +			Xowd_page_itm page = doc_ary[i];
    +			wkr.Page_wkr__run(page);
    +		}
    +		wkr.Page_wkr__end();		
    +	}
    +	private void tst_fils(Io_url[] ary) {
    +		Io_fil[] actls = Get_actl(ary);
    +		Io_fil_chkr[] expds = (Io_fil_chkr[])expd_list.To_ary(Io_fil_chkr.class);
    +		tst_mgr.Tst_ary("all", expds, actls);		
    +	}
    +	Io_fil[] Get_actl(Io_url[] ary) {
    +		int len = ary.length;
    +		Io_fil[] rv = new Io_fil[len];
    +		for (int i = 0; i < len; i++) {
    +			Io_url url = ary[i];
    +			String data = Io_mgr.Instance.LoadFilStr(url);
    +			rv[i] = new Io_fil(url, data);
    +		}
    +		return rv;
    +	}
    +	public Xob_fxt Run_tmpl_dump() {
    +		Xob_parse_dump_templates_cmd wkr = new Xob_parse_dump_templates_cmd(bldr, wiki);
    +		Run_wkr(wkr);
    +		tst_fils(wkr.Dump_url_gen().Prv_urls());
    +		return this;
    +	}
    +	public Xob_fxt Run(Xobd_parser_wkr... wkrs) {
    +		Xobd_parser parser_wkr = new Xobd_parser(bldr);
    +		int len = wkrs.length;
    +		for (int i = 0; i < len; i++)
    +			parser_wkr.Wkr_add(wkrs[i]);
    +		Run(parser_wkr);
    +		return this;
    +	}
    +	public Xob_fxt Run(Xob_page_wkr... wkrs) {
    +		int doc_ary_len = doc_ary.length;
    +		for (int j = 0; j < wkrs.length; j++) {
    +			Xob_page_wkr wkr = wkrs[j];
    +			wkr.Page_wkr__bgn();
    +			for (int i = 0; i < doc_ary_len; i++) {
    +				Xowd_page_itm page = doc_ary[i];
    +				wkr.Page_wkr__run(page);
    +			}
    +			wkr.Page_wkr__end();
    +		}
    +		Test_expd_files();
    +		return this;
    +	}
    +	public Xob_fxt Run_cmds(Xob_cmd... cmds) {
    +		for (int j = 0; j < cmds.length; j++) {
    +			Xob_cmd cmd = cmds[j];
    +			cmd.Cmd_bgn(bldr);
    +			cmd.Cmd_run();
    +			cmd.Cmd_end();
    +		}
    +		Test_expd_files();
    +		return this;
    +	}
    +	private void Test_expd_files() {
    +		if (expd_list.Count() > 0) {
    +			Io_fil_chkr[] expd = (Io_fil_chkr[])expd_list.To_ary(Io_fil_chkr.class);
    +			Io_fil[] actl = wiki_();
    +			tst_mgr.Tst_ary("all", expd, actl);
    +		}
    +	}
    +	Io_fil[] wiki_() {
    +		List_adp rv = List_adp_.New();
    +		wiki_fil_add(rv, wiki.Tdb_fsys_mgr().Ns_dir());
    +		wiki_fil_add(rv, wiki.Tdb_fsys_mgr().Site_dir());
    +		rv.Sort();
    +		return (Io_fil[])rv.To_ary(Io_fil.class);
    +	}
    +	private void wiki_fil_add(List_adp list, Io_url root_dir) {
    +		Io_url[] ary = Io_mgr.Instance.QueryDir_args(root_dir).Recur_().ExecAsUrlAry();
    +		for (int i = 0; i < ary.length; i++) {
    +			Io_url url = ary[i]; 
    +			Io_fil fil = new Io_fil(url, Io_mgr.Instance.LoadFilStr_args(url).MissingIgnored_().Exec());
    +			list.Add(fil);
    +		}		
    +	}
    +	Tst_mgr tst_mgr = new Tst_mgr();
    +	DateAdp_parser dateParser = DateAdp_parser.new_();
    +} 
    diff --git a/400_xowa/src/gplx/xowa/bldrs/Xob_ns_to_db_mgr.java b/400_xowa/src/gplx/xowa/bldrs/Xob_ns_to_db_mgr.java
    index a27517de8..bdf38fe35 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/Xob_ns_to_db_mgr.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/Xob_ns_to_db_mgr.java
    @@ -13,3 +13,75 @@ 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.bldrs; import gplx.*; import gplx.xowa.*;
    +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*;
    +import gplx.xowa.bldrs.cmds.*;
    +public class Xob_ns_to_db_mgr {
    +	private final    Xob_ns_to_db_wkr wkr; private final    Xow_db_mgr db_mgr; private final    long db_max; private boolean one_file_conn_init = true;
    +	private final    Ordered_hash db_list = Ordered_hash_.New();
    +	public Xob_ns_to_db_mgr(Xob_ns_to_db_wkr wkr, Xow_db_mgr db_mgr, long db_max) {
    +		this.wkr = wkr; this.db_mgr = db_mgr; this.db_max = db_max;
    +	}
    +	public Xow_db_file Get_by_ns(Xob_ns_file_itm ns_file_itm, int data_len) {
    +		Xow_db_file rv = null;
    +		if		(db_mgr.Props().Layout_text().Tid_is_all()) {
    +			rv = db_mgr.Db__core();
    +			if (one_file_conn_init) {
    +				one_file_conn_init = false;
    +				Init_tbl(rv);
    +			}
    +		}
    +		else if (wkr.Db_tid() == Xow_db_file_.Tid__html_data	&& db_mgr.Props().Layout_html().Tid_is_all_or_few()) {
    +			if (one_file_conn_init) {
    +				one_file_conn_init = false;
    +				rv = db_mgr.Dbs__make_by_tid(wkr.Db_tid());
    +				Init_tbl(rv);
    +			}
    +			else
    +				rv = db_mgr.Db__html();
    +		}
    +		else {
    +			int db_id = ns_file_itm.Nth_db_id();
    +			if (db_id == Xob_ns_file_itm.Nth_db_id_null)	// ns not assigned yet to db
    +				rv = Init_db(ns_file_itm);
    +			else
    +				rv = db_mgr.Dbs__get_by_id_or_fail(db_id);
    +			long file_len = rv.File_len();
    +			if (file_len + data_len > db_max) {				// file is "full"
    +				Term_tbl(rv);
    +				rv = Init_db(ns_file_itm);
    +			}
    +		}
    +		rv.File_len_add(data_len);
    +		return rv;
    +	}
    +	private Xow_db_file Init_db(Xob_ns_file_itm ns_file_itm) {
    +		Xow_db_file rv = db_mgr.Dbs__make_by_tid(ns_file_itm.Db_file_tid(), Int_ary_.To_str("|", ns_file_itm.Ns_ids()), ns_file_itm.Nth_db_idx(), ns_file_itm.Make_file_name());
    +		ns_file_itm.Nth_db_id_(rv.Id());
    +		Init_tbl(rv);
    +		return rv;
    +	}
    +	private void Init_tbl(Xow_db_file db) {
    +		wkr.Tbl_init(db);
    +		db_list.Add(db.Id(), db);
    +	}
    +	private void Term_tbl(Xow_db_file db) {
    +		wkr.Tbl_term(db);
    +		db_list.Del(db.Id());
    +	}
    +	public void Rls_all() {
    +		Xow_db_file[] ary = (Xow_db_file[])db_list.To_ary(Xow_db_file.class);
    +		int len = ary.length;
    +		for (int i = 0; i < len; ++i) {
    +			Xow_db_file db = (Xow_db_file)ary[i];
    +			Term_tbl(db); // SQLITE:1_TXN; may call close on db where txn is already closed
    +		}
    +	}
    +	public void Commit() {
    +		int len = db_list.Count();
    +		for (int i = 0; i < len; ++i) {
    +			Xow_db_file db = (Xow_db_file)db_list.Get_at(i);
    +			db.Conn().Txn_sav();
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/Xob_ns_to_db_wkr.java b/400_xowa/src/gplx/xowa/bldrs/Xob_ns_to_db_wkr.java
    index a27517de8..e72b098fd 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/Xob_ns_to_db_wkr.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/Xob_ns_to_db_wkr.java
    @@ -13,3 +13,10 @@ 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.bldrs; import gplx.*; import gplx.xowa.*;
    +import gplx.xowa.wikis.data.*;
    +public interface Xob_ns_to_db_wkr {
    +	byte Db_tid();
    +	void Tbl_init(Xow_db_file db);
    +	void Tbl_term(Xow_db_file db);
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/Xob_page_wkr_cmd.java b/400_xowa/src/gplx/xowa/bldrs/Xob_page_wkr_cmd.java
    index a27517de8..b8a4222ce 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/Xob_page_wkr_cmd.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/Xob_page_wkr_cmd.java
    @@ -13,3 +13,84 @@ 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.bldrs; import gplx.*; import gplx.xowa.*;
    +import gplx.core.consoles.*; import gplx.core.ios.*;
    +import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.parsers.tmpls.*;
    +import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.bldrs.xmls.*; 
    +public class Xob_page_wkr_cmd implements Xob_cmd {
    +	private final    Xob_bldr bldr; private final    Xowe_wiki wiki;
    +	public Xob_page_wkr_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.bldr = bldr; this.wiki = wiki;}
    +	public String Cmd_key() {return KEY;} public static final    String KEY = "dump_mgr";
    +	public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return null;}
    +	public void Cmd_run() {
    +		Xob_page_wkr[] wkr_ary = (Xob_page_wkr[])wkrs.To_ary(Xob_page_wkr.class); int wkr_ary_len = wkr_ary.length;
    +		for (int i = 0; i < wkr_ary_len; i++)
    +			wkr_ary[i].Page_wkr__bgn();
    +		Io_buffer_rdr fil = Io_buffer_rdr.Null; Xowd_page_itm page = new Xowd_page_itm(); Xow_ns_mgr ns_mgr = wiki.Ns_mgr();
    +		Xob_xml_parser parser = bldr.Dump_parser().Data_bfr_len_(Io_mgr.Len_mb); 
    +		long fil_len = 0;
    +		Gfo_usr_dlg usr_dlg = bldr.App().Usr_dlg();
    +		try {
    +			gplx.core.ios.streams.Io_stream_rdr src_rdr = wiki.Import_cfg().Src_rdr();
    +			fil = Io_buffer_rdr.new_(src_rdr, optRdrBfrSize);
    +			fil_len = fil.Fil_len();
    +			if (src_rdr.Tid() == gplx.core.ios.streams.Io_stream_tid_.Tid__bzip2) fil_len = (fil_len * 100) / 18;	// HACK: no way to get actual file progress; assume 18% compression
    +			// fil.Seek(bldr.Opts().ResumeAt());
    +			int prv_pos = 0;
    +			while (true) {
    +				int cur_pos = parser.Parse_page(page, usr_dlg, fil, fil.Bfr(), prv_pos, ns_mgr); if (cur_pos == Bry_find_.Not_found) break;
    +				if (cur_pos < prv_pos)
    +					bldr.Print_prog_msg(fil.Fil_pos(), fil_len, 1, optRdrFillFmt, Int_.To_str_pad_bgn_zero((int)(fil.Fil_pos() / Io_mgr.Len_mb), Int_.DigitCount((int)(fil.Fil_len() / Io_mgr.Len_mb))), "", String_.new_u8(page.Ttl_full_db()));
    +				prv_pos = cur_pos;
    +				try {
    +					for (int i = 0; i < wkr_ary_len; i++)
    +						wkr_ary[i].Page_wkr__run(page);
    +				}
    +				catch (Exception e) {
    +					Err_.Noop(e);
    +					long dividend = fil.Fil_pos();
    +					if (dividend >= fil_len) dividend = fil_len - 1; // prevent % from going over 100
    +					String msg = Decimal_adp_.CalcPctStr(dividend, fil_len, "00.00") + "|" + String_.new_u8(page.Ttl_full_db()) + "|" + Err_.Message_gplx_log(e);
    +					bldr.Usr_dlg().Log_wkr().Log_to_session(msg);
    +					Console_adp__sys.Instance.Write_str_w_nl(msg);
    +				}
    +			}
    +			for (int i = wkr_ary_len - 1; i > -1; --i)	// NOTE: release in reverse order; needed to make sure txns are released correctly
    +				wkr_ary[i].Page_wkr__run_cleanup();
    +		}
    +		catch (Exception e) {
    +			String msg = Err_.Message_lang(e);
    +			bldr.Usr_dlg().Log_wkr().Log_to_session(msg);
    +			Console_adp__sys.Instance.Write_str_w_nl(msg);
    +			throw Err_.new_exc(e, "xo", "error while reading dump");
    +		}
    +		finally {fil.Rls();}
    +		bldr.Usr_dlg().Prog_none("", "", "reading completed: performing post-processing clean-up");
    +		for (int i = wkr_ary_len - 1; i > -1; --i)	// NOTE: release in reverse order; needed to make sure txns are released correctly
    +			wkr_ary[i].Page_wkr__end();
    +	}
    +	public void Cmd_bgn(Xob_bldr bldr) {}
    +	public void Cmd_init(Xob_bldr bldr) {}
    +	public void Cmd_end() {}
    +	public void Cmd_term() {}
    +	public void Wkr_add(Xob_page_wkr wkr) {wkrs.Add(wkr.Page_wkr__key(), wkr);} private Ordered_hash wkrs = Ordered_hash_.New();
    +	public Xob_page_wkr Wkr_get(String key) {return (Xob_page_wkr)wkrs.Get_by(key);}
    +	public Xobd_parser Page_parser_assert() {
    +		if (page_parser == null) {
    +			page_parser = new Xobd_parser(bldr);
    +			this.Wkr_add(page_parser);
    +		}
    +		return page_parser;
    +	}	private Xobd_parser page_parser;
    +	public static Io_url Find_fil_by(Io_url dir, String filter) {
    +		Io_url[] fil_ary = Io_mgr.Instance.QueryDir_args(dir).FilPath_(filter).ExecAsUrlAry();
    +		int fil_ary_len = fil_ary.length;
    +		return fil_ary_len == 0 ? null : fil_ary[fil_ary_len - 1];	// return last
    +	}
    +	int optRdrBfrSize = 8 * Io_mgr.Len_mb;
    +	String optRdrFillFmt = "reading ~{0} MB: ~{1} ~{2}";
    +	static final String GRP_KEY = "xowa.bldr.rdr";
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		throw Err_.new_unimplemented();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/Xobd_parser.java b/400_xowa/src/gplx/xowa/bldrs/Xobd_parser.java
    index a27517de8..3594a7f09 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/Xobd_parser.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/Xobd_parser.java
    @@ -13,3 +13,52 @@ 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.bldrs; import gplx.*; import gplx.xowa.*;
    +import gplx.core.btries.*; import gplx.xowa.bldrs.wkrs.*;
    +import gplx.xowa.wikis.data.tbls.*;
    +public class Xobd_parser implements Xob_page_wkr {
    +	private final    Xob_bldr bldr;
    +	private final    Btrie_slim_mgr trie = Btrie_slim_mgr.ci_a7();		// NOTE:ci.ascii:MW_const.en; ctg.v1 assumes [[Category:
    +	private final    Btrie_rv trv = new Btrie_rv();
    +	private final    List_adp wkr_list = List_adp_.New();
    +	public String Page_wkr__key() {return KEY;} static final String KEY = "page_parser";
    +	public Xobd_parser(Xob_bldr bldr) {this.bldr = bldr;}
    +	public void Wkr_add(Xobd_parser_wkr wkr) {wkr_list.Add(wkr);}
    +	public void Page_wkr__bgn() {
    +		int wkr_list_len = wkr_list.Count();
    +		for (int i = 0; i < wkr_list_len; i++) {
    +			Xobd_parser_wkr wkr = (Xobd_parser_wkr)wkr_list.Get_at(i);
    +			wkr.Wkr_bgn(bldr);
    +			int hooks_len = wkr.Wkr_hooks().Count();
    +			for (int j = 0; j < hooks_len; j++) {
    +				byte[] bry = (byte[])wkr.Wkr_hooks().Get_at(j);
    +				trie.Add_obj(bry, wkr);
    +			}
    +		}
    +	}
    +	public void Page_wkr__run(Xowd_page_itm page) {
    +		byte[] src = page.Text(); int src_len = src.length;
    +		int pos = 0;
    +		while (true) {
    +			if (pos == src_len) break;
    +			Object o = trie.Match_at(trv, src, pos, src_len);
    +			if (o == null)
    +				++pos;
    +			else {
    +				Xobd_parser_wkr wkr = (Xobd_parser_wkr)o;
    +				pos = wkr.Wkr_run(page, src, src_len, pos, trv.Pos());
    +			}
    +		}
    +	}
    +	public void Page_wkr__run_cleanup() {}
    +	public void Page_wkr__end() {
    +		int wkr_list_len = wkr_list.Count();
    +		for (int i = 0; i < wkr_list_len; i++) {
    +			Xobd_parser_wkr wkr = (Xobd_parser_wkr)wkr_list.Get_at(i);
    +			wkr.Wkr_end();
    +		}
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		throw Err_.new_unimplemented();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/Xobd_parser_wkr.java b/400_xowa/src/gplx/xowa/bldrs/Xobd_parser_wkr.java
    index a27517de8..e661449d4 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/Xobd_parser_wkr.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/Xobd_parser_wkr.java
    @@ -13,3 +13,11 @@ 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.bldrs; import gplx.*; import gplx.xowa.*;
    +import gplx.xowa.wikis.data.tbls.*;
    +public interface Xobd_parser_wkr extends Gfo_invk {
    +	Ordered_hash Wkr_hooks();
    +	void Wkr_bgn(Xob_bldr bldr);
    +	int Wkr_run(Xowd_page_itm page, byte[] src, int src_len, int bgn, int end);
    +	void Wkr_end();
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/Xobdc_merger.java b/400_xowa/src/gplx/xowa/bldrs/Xobdc_merger.java
    index a27517de8..ead312a4e 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/Xobdc_merger.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/Xobdc_merger.java
    @@ -13,3 +13,29 @@ 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.bldrs; import gplx.*; import gplx.xowa.*;
    +import gplx.core.ios.*; import gplx.core.lists.*;
    +import gplx.xowa.wikis.nss.*;
    +import gplx.xowa.bldrs.wtrs.*;
    +public class Xobdc_merger {
    +	public static void Basic(Gfo_usr_dlg usr_dlg, Io_url_gen dump_url_gen, Io_url sort_dir, int memory_max, Io_line_rdr_key_gen key_gen, Io_sort_cmd make_cmd) {Basic(usr_dlg, dump_url_gen, sort_dir, memory_max, Io_sort_split_itm_sorter.Instance, key_gen, make_cmd);}
    +	public static void Basic(Gfo_usr_dlg usr_dlg, Io_url_gen dump_url_gen, Io_url sort_dir, int memory_max, ComparerAble row_comparer, Io_line_rdr_key_gen key_gen, Io_sort_cmd make_cmd) {
    +		Io_sort sort = new Io_sort().Memory_max_(memory_max);
    +		Io_url_gen sort_url_gen = Io_url_gen_.dir_(sort_dir);
    +		sort.Split(usr_dlg, dump_url_gen, sort_url_gen, row_comparer, key_gen);
    +		sort.Merge(usr_dlg, sort_url_gen.Prv_urls(), row_comparer, key_gen, make_cmd);
    +	}
    +	public static void Ns(Gfo_usr_dlg usr_dlg, Xob_tmp_wtr[] ttl_wtrs, String type, Io_url tmp_root, Io_url make_root, int memory_max, Io_line_rdr_key_gen key_gen, Io_make_cmd make_cmd) {
    +		int len = ttl_wtrs.length;
    +		for (int i = 0; i < len; i++) {
    +			Xob_tmp_wtr ttl_wtr = ttl_wtrs[i]; if (ttl_wtr == null) continue;
    +			Xow_ns ns = ttl_wtr.Ns_itm();
    +			Io_url make_dir = make_root.GenSubDir_nest(ns.Num_str(), type);
    +			make_cmd.Make_dir_(make_dir);
    +			Basic(usr_dlg
    +				, ttl_wtr.Url_gen()
    +				, tmp_root.GenSubDir_nest(ns.Num_str(), "sort")
    +				, memory_max, key_gen, make_cmd);
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/Xobdc_utl.java b/400_xowa/src/gplx/xowa/bldrs/Xobdc_utl.java
    index a27517de8..f229de4d3 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/Xobdc_utl.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/Xobdc_utl.java
    @@ -13,3 +13,39 @@ 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.bldrs; import gplx.*; import gplx.xowa.*;
    +import gplx.core.ios.*; import gplx.core.lists.*;
    +import gplx.xowa.wikis.tdbs.*;
    +class Io_sort_filCmd_reg implements Io_sort_filCmd { // 123|bgn|end|1
    +	public Io_sort_filCmd_reg() {}
    +	public void Bfr_add(Io_line_rdr stream) {
    +		++itm_count;
    +		int key_bgn = stream.Key_pos_bgn(), key_end = stream.Key_pos_end();
    +		Bry_.Copy_by_pos(stream.Bfr(), key_bgn, key_end, prv_key, 0); prv_key_len = key_end - key_bgn; 
    +	}	byte[] prv_key = new byte[1024]; int prv_key_len = 0;
    +	public void Fil_bgn(Io_line_rdr stream) {
    +		bfr.Add_int_variable(fil_idx++).Add_byte(Byte_ascii.Pipe);
    +		bfr.Add_mid(stream.Bfr(), stream.Key_pos_bgn(), stream.Key_pos_end()).Add_byte(Byte_ascii.Pipe);
    +	}	
    +	public void Fil_end() {
    +		bfr.Add_mid(prv_key, 0, prv_key_len).Add_byte(Byte_ascii.Pipe)
    +			.Add_int_variable(itm_count).Add_byte(Byte_ascii.Nl);
    +		itm_count = 0;
    +	}
    +	public void Flush(Io_url fil) {
    +		Io_mgr.Instance.SaveFilBry(fil, bfr.Bfr(), bfr.Len());
    +	}	private Bry_bfr bfr = Bry_bfr_.New(); int fil_idx = 0; int itm_count = 0;
    +}
    +class Io_url_gen_nest implements gplx.core.ios.Io_url_gen {
    +	public Io_url Cur_url() {return cur_url;} Io_url cur_url;
    +	public Io_url Nxt_url() {cur_url = Xotdb_fsys_mgr.Url_fil(root_dir, fil_idx++, ext); return cur_url;}
    +	public Io_url[] Prv_urls() {
    +		Io_url[] rv = new Io_url[fil_idx];
    +		for (int i = 0; i < fil_idx; i++) {
    +			rv[i] = Xotdb_fsys_mgr.Url_fil(root_dir, fil_idx++, ext);
    +		}
    +		return rv;
    +	}
    +	public void Del_all() {if (Io_mgr.Instance.ExistsDir(root_dir)) Io_mgr.Instance.DeleteDirDeep(root_dir);}
    +	public Io_url_gen_nest(Io_url root_dir, String ext) {this.root_dir = root_dir; this.ext = Bry_.new_u8(ext);} Io_url root_dir; byte[] ext; int fil_idx;
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/Xobldr_cfg.java b/400_xowa/src/gplx/xowa/bldrs/Xobldr_cfg.java
    index a27517de8..3bcd4707f 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/Xobldr_cfg.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/Xobldr_cfg.java
    @@ -13,3 +13,49 @@ 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.bldrs; import gplx.*; import gplx.xowa.*;
    +import gplx.core.ios.*;
    +import gplx.xowa.wikis.data.*;
    +public class Xobldr_cfg {
    +	private static long layout_all_max		= 0;									// disable by default; may set to 200 MB in future
    +	private static boolean hzip_enabled		= Bool_.Y;
    +	private static boolean hzip_mode_is_b256	= Bool_.Y;
    +
    +	public static byte Zip_mode__text(Xoa_app app) {return Zip_mode(app, "xowa.bldr.db.zip_mode.text");}	// CFG: Cfg__
    +	public static byte Zip_mode__html(Xoa_app app) {return Zip_mode(app, "xowa.bldr.db.zip_mode.html");}	// CFG: Cfg__
    +	private static byte Zip_mode(Xoa_app app, String key) {
    +		String val = app.Cfg().Get_str_app_or(key, "gzip");
    +		return gplx.core.ios.streams.Io_stream_tid_.To_tid(val);
    +	}
    +	public static long Max_size__text(Xoa_app app) {return Max_size(app, "xowa.bldr.db.max_size.text");}	// CFG: Cfg__
    +	public static long Max_size__html(Xoa_app app) {return Max_size(app, "xowa.bldr.db.max_size.html");}	// CFG: Cfg__
    +	public static long Max_size__file(Xoa_app app) {return Max_size(app, "xowa.bldr.db.max_size.file");}	// CFG: Cfg__
    +	private static long Max_size(Xoa_app app, String key) {
    +		long rv = app.Cfg().Get_long_app_or(key, Io_size_.To_long_by_int_mb(1500));
    +		return rv * Io_mgr.Len_mb;
    +	}
    +	public static long Layout_size__text(Xoa_app app) {return Layout_size(app, "xowa.bldr.db.layout_size.text");}	// CFG: Cfg__
    +	public static long Layout_size__html(Xoa_app app) {return Layout_size(app, "xowa.bldr.db.layout_size.html");}	// CFG: Cfg__
    +	public static long Layout_size__file(Xoa_app app) {return Layout_size(app, "xowa.bldr.db.layout_size.file");}	// CFG: Cfg__
    +	private static long Layout_size(Xoa_app app, String key) {
    +		long rv = app.Cfg().Get_long_app_or(key, Io_size_.To_long_by_int_mb(1500));
    +		return rv * Io_mgr.Len_mb;
    +	}
    +	public static byte[] New_ns_file_map(Xoa_app app, long dump_file_size) {
    +		return dump_file_size < Layout_size__text(app)
    +			? gplx.xowa.bldrs.cmds.Xob_ns_file_itm_parser.Ns_file_map__few
    +			: gplx.xowa.bldrs.cmds.Xob_ns_file_itm_parser.Ns_file_map__each; // DB.FEW: DATE:2016-06-07
    +	}
    +	public static Xowd_core_db_props New_props(Xoa_app app, String domain_str, long dump_file_size) {
    +		Xow_db_layout layout_text, layout_html, layout_file;
    +		if		(dump_file_size < layout_all_max)
    +			layout_text = layout_html = layout_file = Xow_db_layout.Itm_all;
    +		else {
    +			layout_text	= dump_file_size < Layout_size__text(app) ? Xow_db_layout.Itm_few : Xow_db_layout.Itm_lot;
    +			layout_html	= dump_file_size < Layout_size__html(app) ? Xow_db_layout.Itm_few : Xow_db_layout.Itm_lot;
    +			layout_file	= dump_file_size < Layout_size__file(app) ? Xow_db_layout.Itm_few : Xow_db_layout.Itm_lot;
    +		}
    +		return new Xowd_core_db_props(2, layout_text, layout_html, layout_file, Zip_mode__text(app), Zip_mode__html(app), hzip_enabled, hzip_mode_is_b256);
    +	}
    +	public static final    byte[] Ns_file_map__each = Bry_.new_a7("");
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/aria2/Aria2_lib_mgr.java b/400_xowa/src/gplx/xowa/bldrs/aria2/Aria2_lib_mgr.java
    index a27517de8..293842841 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/aria2/Aria2_lib_mgr.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/aria2/Aria2_lib_mgr.java
    @@ -13,3 +13,37 @@ 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.bldrs.aria2; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import gplx.core.envs.*;
    +import gplx.xowa.apps.fsys.*; import gplx.xowa.bldrs.wms.dumps.*;
    +public class Aria2_lib_mgr implements Gfo_invk {
    +	public Process_adp Lib() {return lib;} private Process_adp lib = new Process_adp();
    +	public void Init_by_app(Xoae_app app) {
    +		Xoa_fsys_eval cmd_eval = app.Url_cmd_eval();
    +		Process_adp.ini_(this, app.Usr_dlg(), lib, cmd_eval, Process_adp.Run_mode_sync_block, Int_.Max_value
    +		, "~{<>bin_plat_dir<>}aria2" + Op_sys.Cur().Fsys_dir_spr_str() +  "aria2c"
    +		, Lib_args_fmt
    +		, "wiki_abrv", "wiki_date", "wiki_type");
    +	}
    +	// private Bry_bfr tmp_bfr = Bry_bfr_.Reset(255);
    +	public void Exec(Xowm_dump_file dump_file) {			
    +		// byte[] args_bry = lib.Args_fmtr().Bld_bry_many(tmp_bfr, dump_file.Wiki_alias(), dump_file.Dump_date(), dump_file.Dump_file_type());
    +		// Process_adp process = new Process_adp().Exe_url_(lib.Exe_url()).Args_str_(String_.new_u8(args_bry));
    +		// process.Run_wait();			
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_lib))				return lib;
    +		else	return Gfo_invk_.Rv_unhandled;
    +	}
    +	private static final    String Invk_lib = "lib";
    +	private static final    String Lib_args_fmt = String_.Concat
    +	( "--max-connection-per-server=2"
    +	, " --max-concurrent-downloads=20"
    +	, " --split=4"
    +	, " --file-allocation=prealloc"
    +	, " --remote-time=true"
    +	, " --server-stat-of=serverstats.txt"
    +	, " ftp://ftpmirror.your.org/pub/wikimedia/dumps/~{wiki_abrv}/~{wiki_date}/~{wiki_abrv}-~{wiki_date}-~{wiki_type}.bz2"
    +	, " https://dumps.wikimedia.org/~{wiki_abrv}/~{wiki_date}/~{wiki_abrv}-~{wiki_date}-~{wiki_type}.xml.bz2"
    +	);
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/aria2/Gfui_process_win.java b/400_xowa/src/gplx/xowa/bldrs/aria2/Gfui_process_win.java
    index a27517de8..6b3d3ef5f 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/aria2/Gfui_process_win.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/aria2/Gfui_process_win.java
    @@ -13,3 +13,26 @@ 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.bldrs.aria2; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import gplx.gfui.*;
    +class Gfui_process_win {
    +	public void Exec_async(String process, String args, Gfo_invk_cmd done_cbk) {
    +		// Gfo_process process = new Gfo_process().Init_process_(process, args).Init_async_(done_cbk).Init_strm_out_err_(output_box).Exec();			
    +	}
    +}
    +class Gfo_process {
    +//		private Gfo_invk_cmd done_cbk;
    +//		private Gfo_process_wtr out_wtr, err_wtr;
    +	public String Cmd_path() {return cmd_path;} private String cmd_path;
    +	public String Cmd_args() {return cmd_args;} private String cmd_args;
    +	public byte Mode() {return mode;} private byte mode;
    +	public Gfo_process Init_cmd_(String cmd_path, String cmd_args) {this.cmd_path = cmd_path; this.cmd_args = cmd_args; return this;}
    +	public Gfo_process Init_mode_async_() {mode = Gfo_process_.Mode_async; return this;}
    +//		public Gfo_process Init_mode_async_(Gfo_invk_cmd done_cbk) {this.done_cbk = done_cbk; return this.Init_mode_async_();}
    +//		public Gfo_process Init_wtr_out_err_(Gfo_process_wtr wtr) {out_wtr = err_wtr = wtr; return this;}
    +}
    +class Gfo_process_wtr {}
    +class Gfo_process_rdr {}
    +class Gfo_process_ {
    +	public static final byte Mode_async = 0, Mode_sync = 1, Mode_sync_timeout = 2; 
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/Xob_dump_mgr_base.java b/400_xowa/src/gplx/xowa/bldrs/cmds/Xob_dump_mgr_base.java
    index a27517de8..71d147cf9 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/Xob_dump_mgr_base.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/Xob_dump_mgr_base.java
    @@ -13,3 +13,313 @@ 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.bldrs.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import gplx.core.envs.*;
    +import gplx.dbs.*; import gplx.xowa.wikis.caches.*; import gplx.xowa.addons.bldrs.files.*; import gplx.xowa.files.origs.*;
    +import gplx.xowa.bldrs.wkrs.*;
    +import gplx.xowa.wikis.nss.*;
    +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.dbs.*; import gplx.xowa.wikis.data.tbls.*;
    +import gplx.xowa.addons.bldrs.files.utls.*;
    +import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*;
    +public abstract class Xob_dump_mgr_base extends Xob_itm_basic_base implements Xob_cmd, Gfo_invk {
    +	private Xob_dump_src_id page_src;
    +	private Xow_db_mgr db_fsys_mgr; protected Xop_parser parser; protected Xop_ctx ctx; protected Xop_root_tkn root;
    +	private int[] ns_ary; private Xow_db_file[] db_ary;
    +	private int ns_bgn = -1, db_bgn = -1, pg_bgn = -1;
    +	private int ns_end = -1, db_end = -1, pg_end = Int_.Max_value;
    +	private int commit_interval = 1000, progress_interval = 250, cleanup_interval = 2500, select_size = 10 * Io_mgr.Len_mb;
    +	private int exec_count, exec_count_max = Int_.Max_value;
    +	private boolean reset_db = false, exit_after_commit = false, exit_now = false;
    +	private boolean load_tmpls;
    +	private Xob_dump_bmk_mgr bmk_mgr = new Xob_dump_bmk_mgr();
    +	private Xobu_poll_mgr poll_mgr; private int poll_interval = 5000;
    +	private Xob_rate_mgr rate_mgr = new Xob_rate_mgr();
    +	public abstract String Cmd_key();
    +	@Override protected void Cmd_ctor_end(Xob_bldr bldr, Xowe_wiki wiki) {
    +		poll_mgr = new Xobu_poll_mgr(bldr.App());	// init in ctor so gfs can invoke methods
    +	}
    +	public void Cmd_bgn(Xob_bldr bldr) {
    +		parser = wiki.Parser_mgr().Main();
    +		ctx = wiki.Parser_mgr().Ctx();
    +		root = ctx.Tkn_mkr().Root(Bry_.Empty);
    +		wiki.Init_assert();	// NOTE: must init wiki for db_mgr_as_sql
    +
    +		// assert by calling Db_mgr_as_sql
    +		wiki.Db_mgr_as_sql().Core_data_mgr();
    +
    +		// load db_mgr
    +		Xow_db_mgr.Init_by_load(wiki, gplx.xowa.wikis.data.Xow_db_file__core_.Find_core_fil_or_null(wiki));	// NOTE: must reinit providers as previous steps may have rls'd (and left member variable conn which is closed)
    +
    +		wiki.File__orig_mgr().Wkrs__del(Xof_orig_wkr_.Tid_wmf_api);
    +		db_fsys_mgr = wiki.Db_mgr_as_sql().Core_data_mgr();
    +		db_ary = Xob_dump_mgr_base_.Init_text_files_ary(db_fsys_mgr);
    +		poll_interval = poll_mgr.Poll_interval();
    +
    +		page_src = new Xob_dump_src_id().Init(wiki, this.Init_redirect(), select_size);
    +		ns_ary = Init_ns_ary();
    +		Db_conn conn = Init_db_file();
    +		Io_url wiki_dir = wiki.Fsys_mgr().Root_dir();
    +		bmk_mgr.Cfg_url_(wiki_dir.GenSubFil("xowa.file.make.cfg.gfs"));
    +		rate_mgr.Log_file_(wiki_dir.GenSubFil("xowa.file.make.log.csv"));
    +		if (reset_db) {
    +			bmk_mgr.Reset();
    +			Init_reset(conn);
    +		}
    +		bmk_mgr.Load(wiki.Appe(), this);
    +		Cmd_bgn_end();
    +	}
    +	protected abstract void Cmd_bgn_end();
    +	public abstract byte Init_redirect();
    +	public abstract int[] Init_ns_ary();
    +	protected abstract void Init_reset(Db_conn p);
    +	protected abstract Db_conn Init_db_file();
    +	private long time_bgn;
    +	public void Cmd_run() {Exec_ns_ary();}
    +	private void Exec_ns_ary() {
    +		if (pg_bgn == Int_.Max_value) return;
    +		if (load_tmpls) Xob_dump_mgr_base_.Load_all_tmpls(usr_dlg, wiki, page_src);
    +		time_bgn = System_.Ticks();
    +		Xob_dump_bmk dump_bmk = new Xob_dump_bmk();
    +		rate_mgr.Init();
    +		int ns_ary_len = ns_ary.length;
    +		for (int i = 0; i < ns_ary_len; i++) {
    +			int ns_id = ns_ary[i];
    +			if (ns_bgn != -1) {							// ns_bgn set
    +				if (ns_id == ns_bgn)					// ns_id is ns_bgn; null out ns_bgn and continue
    +					ns_bgn = -1;
    +				else									// ns_id is not ns_bgn; keep looking
    +					continue;
    +			}
    +			dump_bmk.Ns_id_(ns_id);
    +			Exec_db_ary(i, dump_bmk, ns_id);
    +			if (ns_id == ns_end) exit_now = true;		// ns_end set; exit
    +			if (exit_now) break;						// exit_now b/c of pg_bgn, db_bgn or something else
    +		}
    +		Exec_commit(dump_bmk.Ns_id(), dump_bmk.Db_id(), dump_bmk.Pg_id(), Bry_.Empty);
    +	}
    +	private void Exec_db_ary(int ns_ord, Xob_dump_bmk dump_bmk, int ns_id) {
    +		int db_ary_len = db_ary.length;
    +		for (int i = 0; i < db_ary_len; i++) {
    +			int db_id = db_ary[i].Id();
    +			if (db_bgn != -1) {							// db_bgn set
    +				if (db_id == db_bgn)					// db_id is db_bgn; null out db_bgn and continue
    +					db_bgn = -1;
    +				else									// db_id is not db_bgn; keep looking
    +					continue;
    +			}
    +			dump_bmk.Db_id_(db_id);
    +			Exec_db_itm(dump_bmk, ns_ord, ns_id, db_id);
    +			if (db_id == db_end) exit_now = true;		// db_end set; exit;
    +			if (exit_now) return;						// exit_now b/c of pg_bgn, db_bgn or something else
    +		}
    +	}
    +	private void Exec_db_itm(Xob_dump_bmk dump_bmk, int ns_ord, int ns_id, int db_id) {
    +		List_adp pages = List_adp_.New();
    +		Xow_ns ns = wiki.Ns_mgr().Ids_get_or_null(ns_id);
    +		int pg_id = pg_bgn;
    +		while (true) {
    +			page_src.Get_pages(pages, db_id, ns_id, pg_id);
    +			int pages_len = pages.Count();
    +			if (pages_len == 0) {	// no more pages in db;
    +				if (pg_id > pg_bgn)	// reset pg_bgn to 0 only if pg_bgn seen;
    +					pg_bgn = 0;
    +				return;	
    +			}
    +			usr_dlg.Prog_many("", "", "fetched pages: ~{0}", pages_len);
    +			for (int i = 0; i < pages_len; i++) {
    +				Xowd_page_itm page = (Xowd_page_itm)pages.Get_at(i);
    +				dump_bmk.Pg_id_(pg_id);
    +				Exec_pg_itm(ns_ord, ns, db_id, page);
    +				if (	pg_id		>= pg_end
    +					||	exec_count	>= exec_count_max) {
    +					exit_now = true;
    +				}
    +				if (exit_now) return;
    +				pg_id = page.Id();
    +			}
    +		}
    +	}
    +	private void Exec_pg_itm(int ns_ord, Xow_ns ns, int db_id, Xowd_page_itm page) {
    +		try {
    +			if ((exec_count % progress_interval) == 0)
    +				usr_dlg.Prog_many("", "", "parsing: ns=~{0} db=~{1} pg=~{2} count=~{3} time=~{4} rate=~{5} ttl=~{6}"
    +					, ns.Id(), db_id, page.Id(), exec_count
    +					, System_.Ticks__elapsed_in_sec(time_bgn), rate_mgr.Rate_as_str(), String_.new_u8(page.Ttl_page_db()));
    +			ctx.Clear_all();
    +			byte[] page_src = page.Text();
    +			if (page_src != null)	// some pages have no text; ignore them else null ref; PAGE: it.d:miercuri DATE:2015-12-05
    +				Exec_pg_itm_hook(ns_ord, ns, page, page_src);
    +			ctx.Wiki().Utl__bfr_mkr().Clear_fail_check();	// make sure all bfrs are released
    +			if (ctx.Wiki().Cache_mgr().Tmpl_result_cache().Count() > 50000) 
    +				ctx.Wiki().Cache_mgr().Tmpl_result_cache().Clear();
    +			++exec_count;
    +			rate_mgr.Increment();
    +			if ((exec_count % poll_interval) == 0)
    +				poll_mgr.Poll();
    +			if	((exec_count % commit_interval) == 0)
    +				Exec_commit(ns.Id(), db_id, page.Id(), page.Ttl_page_db());
    +			if ((exec_count % cleanup_interval) == 0)
    +				Free();
    +		}
    +		catch (Exception exc) {
    +			bldr.Usr_dlg().Warn_many("", "", "parse failed: wiki=~{0} ttl=~{1} err=~{2}", wiki.Domain_str(), page.Ttl_full_db(), Err_.Message_gplx_log(exc));
    +			ctx.Wiki().Utl__bfr_mkr().Clear();
    +			this.Free();
    +		}
    +	}
    +	public abstract void Exec_pg_itm_hook(int ns_ord, Xow_ns ns, Xowd_page_itm page, byte[] page_text);
    +	private void Exec_commit(int ns_id, int db_id, int pg_id, byte[] ttl) {
    +		usr_dlg.Prog_many("", "", "committing: ns=~{0} db=~{1} pg=~{2} count=~{3} ttl=~{4}", ns_id, db_id, pg_id, exec_count, String_.new_u8(ttl));
    +		Exec_commit_hook();
    +		bmk_mgr.Save(ns_id, db_id, pg_id);
    +		if (exit_after_commit) exit_now = true;
    +	}
    +	public abstract void Exec_commit_hook();
    +	public abstract void Exec_end_hook();
    +	public void Cmd_init(Xob_bldr bldr) {}
    +	public void Cmd_term() {}		
    +	public void Cmd_end() {
    +		if (!exit_now)
    +			pg_bgn = Int_.Max_value;
    +		Exec_commit(-1, -1, -1, Bry_.Empty);
    +		Exec_end_hook();
    +		Free();
    +		usr_dlg.Note_many("", "", "done: ~{0} ~{1}", exec_count, Decimal_adp_.divide_safe_(exec_count, System_.Ticks__elapsed_in_sec(time_bgn)).To_str("#,###.000"));
    +	}
    +	private void Free() {
    +		Xowe_wiki_.Rls_mem(wiki, true);
    +	}
    +	protected void Reset_db_y_() {this.reset_db = true;}
    +	@Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_commit_interval_))		commit_interval = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk_progress_interval_))		progress_interval = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk_rate_interval_))			rate_mgr.Reset_interval_(m.ReadInt("v"));
    +		else if	(ctx.Match(k, Invk_cleanup_interval_))		cleanup_interval = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk_select_size_))			select_size = m.ReadInt("v") * Io_mgr.Len_mb;
    +		else if	(ctx.Match(k, Invk_ns_bgn_))				{ns_bgn = m.ReadInt("v"); Notify_restoring("ns", ns_bgn);}
    +		else if	(ctx.Match(k, Invk_db_bgn_))				{db_bgn = m.ReadInt("v"); Notify_restoring("db", db_bgn);}
    +		else if	(ctx.Match(k, Invk_pg_bgn_))				{pg_bgn = m.ReadInt("v"); Notify_restoring("pg", pg_bgn);}
    +		else if	(ctx.Match(k, Invk_ns_end_))				ns_end = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk_db_end_))				db_end = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk_pg_end_))				pg_end = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk_load_tmpls_))			load_tmpls = m.ReadYn("v");
    +		else if	(ctx.Match(k, Invk_poll_mgr))				return poll_mgr;
    +		else if	(ctx.Match(k, Invk_reset_db_))				reset_db = m.ReadYn("v");
    +		else if	(ctx.Match(k, Invk_exec_count_max_))		exec_count_max = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk_exit_now_))				exit_now = m.ReadYn("v");
    +		else if	(ctx.Match(k, Invk_exit_after_commit_))		exit_after_commit = m.ReadYn("v");
    +		else if	(ctx.Match(k, Invk__manual_now_))			Datetime_now.Manual_and_freeze_(m.ReadDate("v"));
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	private void Notify_restoring(String itm, int val) {
    +		usr_dlg.Note_many("", "", "restoring: itm=~{0} val=~{1}", itm, val);
    +	}
    +	public static final    String 
    +	  Invk_progress_interval_ = "progress_interval_", Invk_commit_interval_ = "commit_interval_", Invk_cleanup_interval_ = "cleanup_interval_", Invk_rate_interval_ = "rate_interval_"
    +	, Invk_select_size_ = "select_size_"
    +	, Invk_ns_bgn_ = "ns_bgn_", Invk_db_bgn_ = "db_bgn_", Invk_pg_bgn_ = "pg_bgn_"
    +	, Invk_ns_end_ = "ns_end_", Invk_db_end_ = "db_end_", Invk_pg_end_ = "pg_end_"
    +	, Invk_load_tmpls_ = "load_tmpls_"
    +	, Invk_poll_mgr = "poll_mgr", Invk_reset_db_ = "reset_db_"
    +	, Invk_exec_count_max_ = "exec_count_max_", Invk_exit_now_ = "exit_now_", Invk_exit_after_commit_ = "exit_after_commit_"
    +	, Invk__manual_now_ = "manual_now_"
    +	;
    +}
    +class Xob_dump_mgr_base_ {
    +	public static void Load_all_tmpls(Gfo_usr_dlg usr_dlg, Xowe_wiki wiki, Xob_dump_src_id page_src) {
    +		List_adp pages = List_adp_.New();
    +		Xow_ns ns_tmpl = wiki.Ns_mgr().Ns_template();
    +		Xow_defn_cache defn_cache = wiki.Cache_mgr().Defn_cache();
    +		int cur_page_id = -1;
    +		int load_count = 0;
    +		usr_dlg.Note_many("", "", "tmpl_load init");
    +		while (true) {
    +			page_src.Get_pages(pages, 0, Xow_ns_.Tid__template, cur_page_id);	// 0 is always template db
    +			int page_count = pages.Count();
    +			if (page_count == 0) break;	// no more pages in db;
    +			Xowd_page_itm page = null;
    +			for (int i = 0; i < page_count; i++) {
    +				page = (Xowd_page_itm)pages.Get_at(i);
    +				Xot_defn_tmpl defn = new Xot_defn_tmpl();
    +				defn.Init_by_new(ns_tmpl, ns_tmpl.Gen_ttl(page.Ttl_page_db()), page.Text(), null, false);	// NOTE: passing null, false; will be overriden later when Parse is called
    +				defn_cache.Add(defn, ns_tmpl.Case_match());
    +				++load_count;
    +				if ((load_count % 10000) == 0) usr_dlg.Prog_many("", "", "tmpl_loading: ~{0}", load_count);
    +			}
    +			cur_page_id = page.Id();
    +		}
    +		usr_dlg.Note_many("", "", "tmpl_load done: ~{0}", load_count);
    +	}
    +	public static Xow_db_file[] Init_text_files_ary(Xow_db_mgr core_data_mgr) {
    +		List_adp text_files_list = List_adp_.New();
    +		int len = core_data_mgr.Dbs__len();
    +		if (len == 1) return new Xow_db_file[] {core_data_mgr.Dbs__get_at(0)};	// single file: return core; note that there are no Tid = Text
    +		for (int i = 0; i < len; i++) {
    +			Xow_db_file file = core_data_mgr.Dbs__get_at(i);
    +			switch (file.Tid()) {
    +				case Xow_db_file_.Tid__text:
    +				case Xow_db_file_.Tid__text_solo:
    +					text_files_list.Add(file);
    +					break;
    +			}
    +		}
    +		return (Xow_db_file[])text_files_list.To_ary_and_clear(Xow_db_file.class);
    +	}
    +}
    +class Xob_dump_bmk_mgr {
    +	private Bry_bfr save_bfr = Bry_bfr_.Reset(1024);
    +	public Io_url Cfg_url() {return cfg_url;} public Xob_dump_bmk_mgr Cfg_url_(Io_url v) {cfg_url = v; return this;} private Io_url cfg_url;
    +	public void Reset() {Io_mgr.Instance.DeleteFil(cfg_url);}
    +	public void Load(Xoae_app app, Xob_dump_mgr_base dump_mgr) {
    +		app.Gfs_mgr().Run_url_for(dump_mgr, cfg_url);
    +	}
    +	public void Save(int ns_id, int db_id, int pg_id) {
    +		Save_itm(save_bfr, Xob_dump_mgr_base.Invk_ns_bgn_, ns_id);
    +		Save_itm(save_bfr, Xob_dump_mgr_base.Invk_db_bgn_, db_id);
    +		Save_itm(save_bfr, Xob_dump_mgr_base.Invk_pg_bgn_, pg_id);
    +		Io_mgr.Instance.SaveFilBfr(cfg_url, save_bfr);
    +	}
    +	private void Save_itm(Bry_bfr save_bfr, String key, int val) {
    +		String fmt = "{0}('{1}');\n";
    +		String str = String_.Format(fmt, key, val);
    +		save_bfr.Add_str_u8(str);
    +	}
    +}
    +class Xob_rate_mgr {
    +	private long time_bgn;
    +	private int item_len;
    +	private Bry_bfr save_bfr = Bry_bfr_.Reset(255);
    +	public int Reset_interval() {return reset_interval;} public Xob_rate_mgr Reset_interval_(int v) {reset_interval = v; return this;} private int reset_interval = 10000;
    +	public Io_url Log_file_url() {return log_file;} public Xob_rate_mgr Log_file_(Io_url v) {log_file = v; return this;} private Io_url log_file;
    +	public void Init() {time_bgn = System_.Ticks();}
    +	public void Increment() {
    +		++item_len;
    +		if (item_len % reset_interval == 0) {
    +			long time_end = System_.Ticks();
    +			Save(item_len, time_bgn, time_end);
    +			time_bgn = time_end;
    +			item_len = 0;
    +		}
    +	}
    +	private void Save(int count, long bgn, long end) {
    +		int dif = (int)(end - bgn) / 1000;
    +		Decimal_adp rate = Decimal_adp_.divide_safe_(count, dif);
    +		save_bfr
    +			.Add_str_a7(rate.To_str("#,##0.000")).Add_byte_pipe()
    +			.Add_int_variable(count).Add_byte_pipe()
    +			.Add_int_variable(dif).Add_byte_nl()
    +			;
    +		Io_mgr.Instance.AppendFilByt(log_file, save_bfr.To_bry_and_clear());
    +	}
    +	public String Rate_as_str() {return Int_.To_str(Rate());}
    +	public int Rate() {
    +		int elapsed = System_.Ticks__elapsed_in_sec(time_bgn);
    +		return Math_.Div_safe_as_int(item_len, elapsed);
    +	}
    +}
    +class Xob_dump_bmk {
    +	public int Ns_id() {return ns_id;} public Xob_dump_bmk Ns_id_(int v) {ns_id = v; return this;} private int ns_id;
    +	public int Db_id() {return db_id;} public Xob_dump_bmk Db_id_(int v) {db_id = v; return this;} private int db_id;
    +	public int Pg_id() {return pg_id;} public Xob_dump_bmk Pg_id_(int v) {pg_id = v; return this;} private int pg_id;
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/Xob_ns_file_itm.java b/400_xowa/src/gplx/xowa/bldrs/cmds/Xob_ns_file_itm.java
    index a27517de8..651271171 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/Xob_ns_file_itm.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/Xob_ns_file_itm.java
    @@ -13,3 +13,50 @@ 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.bldrs.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import gplx.xowa.wikis.nss.*;
    +import gplx.xowa.wikis.data.*;
    +public class Xob_ns_file_itm {
    +	public Xob_ns_file_itm(byte db_file_tid, String file_name, int[] ns_ids) {
    +		this.db_file_tid = db_file_tid; this.file_name = file_name; this.ns_ids = ns_ids;
    +		this.nth_db_id = Nth_db_id_null; this.nth_db_idx = 1;			
    +	}
    +	public byte		Db_file_tid() {return db_file_tid;} private final    byte db_file_tid;
    +	public String	File_name() {return file_name;} private final    String file_name;
    +	public int[]	Ns_ids() {return ns_ids;} private final    int[] ns_ids;
    +	public int		Nth_db_id() {return nth_db_id;} public void Nth_db_id_(int v) {nth_db_id = v;} private int nth_db_id;
    +	public int		Nth_db_idx() {return nth_db_idx;} private int nth_db_idx;
    +	public String Make_file_name() {								// EX: en.wikipedia.org-text-ns.000-001.xowa
    +		String rv = String_.Format("-{0}{1}{2}.xowa"				// EX: -text-ns.000-db.001.xowa
    +			, Xow_db_file_.To_key(db_file_tid)						// text
    +			, String_.Len_eq_0(file_name) ? "" : "-" + file_name	// if empty, don't add "ns.000" segment; produces en.wikipedia.org-text-001.xowa
    +			, nth_db_idx == 1 ? "" : "-db." + Int_.To_str_pad_bgn_zero(nth_db_idx, 3)			// "-db.001"
    +			);
    +		++nth_db_idx;
    +		return rv;
    +	}
    +	public static final int Nth_db_id_null = -1;
    +
    +	public static void Init_ns_bldr_data(byte db_file_tid, Xow_ns_mgr ns_mgr, byte[] ns_file_map) {
    +		int ns_len = ns_mgr.Ords_len();
    +		Xob_ns_file_itm ns_file_itm_default = new Xob_ns_file_itm(db_file_tid, "", null);
    +		for (int i = 0; i < ns_len; ++i) {
    +			Xow_ns ns = ns_mgr.Ords_get_at(i);
    +			ns.Bldr_data_(ns_file_itm_default);
    +		}
    +		Xob_ns_file_itm_parser ns_itm_parser = new Xob_ns_file_itm_parser();
    +		ns_itm_parser.Ctor(db_file_tid, ns_mgr);
    +		Xob_ns_file_itm[] ns_itm_ary = ns_itm_parser.To_ary(ns_file_map);
    +		int ns_itm_ary_len = ns_itm_ary.length;
    +		for (int i = 0; i < ns_itm_ary_len; ++i) {
    +			Xob_ns_file_itm itm = ns_itm_ary[i];
    +			int[] ns_ids = itm.Ns_ids();
    +			int ns_ids_len = ns_ids.length;
    +			for (int j = 0; j < ns_ids_len; j++) {
    +				int ns_id = ns_ids[j];
    +				Xow_ns ns = ns_mgr.Ids_get_or_null(ns_id); if (ns == null) continue; // some dumps may not have ns; for example, pre-2013 dumps won't have Module (828)
    +				ns.Bldr_data_(itm);
    +			}
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/Xob_ns_file_itm_parser.java b/400_xowa/src/gplx/xowa/bldrs/cmds/Xob_ns_file_itm_parser.java
    index a27517de8..c2fc5d561 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/Xob_ns_file_itm_parser.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/Xob_ns_file_itm_parser.java
    @@ -13,3 +13,79 @@ 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.bldrs.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import gplx.langs.dsvs.*;
    +import gplx.xowa.wikis.nss.*;
    +public class Xob_ns_file_itm_parser extends Dsv_wkr_base {
    +	private byte[] ns_ids_bry; private String name; private final    List_adp rslts = List_adp_.New();
    +	private Xow_ns_mgr ns_mgr; private byte db_file_tid; private boolean mode_each = false;
    +	public void Ctor(byte db_file_tid, Xow_ns_mgr ns_mgr) {
    +		this.db_file_tid = db_file_tid; this.ns_mgr = ns_mgr;
    +		this.mode_each = false; rslts.Clear();
    +	}
    +	@Override public Dsv_fld_parser[] Fld_parsers() {return new Dsv_fld_parser[] {Dsv_fld_parser_.Bry_parser, Dsv_fld_parser_.Bry_parser};}
    +	@Override public boolean Write_bry(Dsv_tbl_parser parser, int fld_idx, byte[] src, int bgn, int end) {
    +		switch (fld_idx) {
    +			case 0: ns_ids_bry	= Bry_.Mid(src, bgn, end); return true;
    +			case 1: name		= String_.new_u8(src, bgn, end); return true;
    +			default: return false;
    +		}
    +	}		
    +	@Override public void Commit_itm(Dsv_tbl_parser parser, int pos) {
    +		if (ns_ids_bry == null)		throw parser.Err_row_bgn("ns_itm missing ns_ids", pos);
    +		if (mode_each) return;
    +
    +		// mode is ; create map with each ns in separate file 
    +		if	(Bry_.Eq(ns_ids_bry, Ns_file_map__each)) {
    +			mode_each = true;
    +			int len = ns_mgr.Ords_len();
    +			for (int i = 0; i < len; ++i) {
    +				Xow_ns ns = ns_mgr.Ords_get_at(i);
    +				int ns_id = ns.Id();
    +				rslts.Add(new Xob_ns_file_itm(db_file_tid, "ns." + Int_.To_str_pad_bgn_zero(ns_id, 3), Int_ary_.New(ns_id)));
    +			}
    +			return;
    +		}
    +		// mode is ; create map with each ns in one file; // DB.FEW: DATE:2016-06-07
    +		else if	(Bry_.Eq(ns_ids_bry, Ns_file_map__few)) {
    +			int len = ns_mgr.Ords_len();
    +			int[] ns_ary_for_few = new int[len];
    +			for (int i = 0; i < len; ++i) {
    +				ns_ary_for_few[i] = ns_mgr.Ords_get_at(i).Id();
    +			}
    +			rslts.Add(new Xob_ns_file_itm(db_file_tid, String_.Empty, ns_ary_for_few));
    +			return;
    +		}
    +
    +		int[] ns_ids = null;
    +		if (ns_ids_bry.length == 1 && ns_ids_bry[0] == Byte_ascii.Star) {	// "*"
    +			int len = ns_mgr.Ords_len();
    +			ns_ids = new int[len];
    +			for (int i = 0; i < len; ++i)
    +				ns_ids[i] = ns_mgr.Ords_get_at(i).Id();
    +		}
    +		else
    +			ns_ids = Int_ary_.Parse(String_.new_u8(ns_ids_bry), ",");
    +		if (ns_ids.length == 0) throw Err_.new_wo_type("map.invalid.ns_missing", "src", this.Src());
    +		if (String_.Len_eq_0(name)) {	// no name; auto-generate
    +			int ns_id_1st = ns_ids[0];	// take 1st ns_id
    +			name = "ns." + Int_.To_str_pad_bgn_zero(ns_id_1st, 3);	// EX: ns.000
    +		}
    +		Xob_ns_file_itm ns_itm = new Xob_ns_file_itm(db_file_tid, name, ns_ids);
    +		rslts.Add(ns_itm);
    +		ns_itm.toString();
    +		ns_ids = null; name = null;
    +	}
    +	public Xob_ns_file_itm[] To_ary(byte[] bry) {
    +		this.Load_by_bry(bry);
    +		return (Xob_ns_file_itm[])rslts.To_ary(Xob_ns_file_itm.class);
    +	}
    +	public static final    byte[] Ns_file_map__few = Bry_.new_a7("few"), Ns_file_map__each = Bry_.new_a7("");
    +	/*
    +"" -> no rules; return "default"; generates "text-001" and lumps all ns into it
    +"*||3700|2" -> auto-generate per ns
    +
    +||gzip
    +||gzip
    +	*/
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/Xob_parse_all_src_sql.java b/400_xowa/src/gplx/xowa/bldrs/cmds/Xob_parse_all_src_sql.java
    index a27517de8..859f45b93 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/Xob_parse_all_src_sql.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/Xob_parse_all_src_sql.java
    @@ -13,3 +13,79 @@ 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.bldrs.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import gplx.core.stores.*;
    +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.dbs.*; import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.xowa.wikis.data.tbls.*;
    +class Xob_dump_src_id {
    +	private Xodb_mgr_sql db_mgr; private byte redirect;
    +	private String page_db_url; private int size_max;
    +	private Db_stmt text_stmt; int cur_text_db_idx = -1;
    +	public Xob_dump_src_id Init(Xowe_wiki wiki, byte redirect, int size_max) {
    +		this.db_mgr = wiki.Db_mgr_as_sql(); this.redirect = redirect;
    +		this.size_max = size_max;
    +		this.page_db_url = db_mgr.Core_data_mgr().Db__core().Url().Raw();
    +		return this;
    +	}
    +	public void Get_pages(List_adp list, int text_db_idx, int cur_ns, int prv_id) {
    +		DataRdr rdr = DataRdr_.Null;
    +		int size_len = 0;
    +		list.Clear();
    +		try {
    +			rdr = New_rdr(db_mgr, page_db_url, text_db_idx, cur_ns, prv_id, redirect);
    +			while (rdr.MoveNextPeer()) {
    +				Xowd_page_itm page = New_page(db_mgr, cur_ns, rdr);
    +				list.Add(page);
    +				size_len += page.Text_len();
    +				if (size_len > size_max)
    +					break;
    +			}
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	private DataRdr New_rdr(Xodb_mgr_sql db_mgr, String page_db_url, int text_db_idx, int cur_ns, int prv_id, byte redirect) {
    +		if (cur_text_db_idx != text_db_idx) {
    +			cur_text_db_idx = text_db_idx;
    +			Xow_db_file text_db = db_mgr.Core_data_mgr().Dbs__get_by_id_or_fail(text_db_idx);
    +			Db_conn conn = text_db.Conn();
    +			String sql = String_.Format(Sql_select_clause, New_rdr__redirect_clause(redirect));
    +			text_stmt = conn.Stmt_sql(sql);
    +		}
    +		return text_stmt.Clear().Val_int(prv_id).Val_int(cur_ns).Exec_select();
    +	}
    +	private static Xowd_page_itm New_page(Xodb_mgr_sql db_mgr, int ns_id, DataRdr rdr) {
    +		Xowd_page_tbl page_core_tbl = db_mgr.Core_data_mgr().Tbl__page();
    +		Xowd_page_itm rv = new Xowd_page_itm();
    +		rv.Id_(rdr.ReadInt(page_core_tbl.Fld_page_id()));
    +		rv.Ns_id_(ns_id);
    +		rv.Ttl_page_db_(rdr.ReadBryByStr(page_core_tbl.Fld_page_title()));
    +		
    +		String text_data_name = db_mgr.Core_data_mgr().Db__core().Tbl__text().Fld_text_data();
    +		byte[] text_data = rdr.ReadBry(text_data_name);
    +		text_data = db_mgr.Wiki().Appe().Zip_mgr().Unzip(db_mgr.Core_data_mgr().Props().Zip_tid_text(), text_data);
    +		rv.Text_(text_data);
    +		return rv;
    +	}
    +	private static String New_rdr__redirect_clause(byte redirect) {
    +		switch (redirect) {
    +			case Bool_.Y_byte:	return Sql_select__redirect_y;
    +			case Bool_.N_byte:	return Sql_select__redirect_n;
    +			case Bool_.__byte:	return Sql_select__redirect__;
    +			default:			throw Err_.new_unhandled(redirect);
    +		}
    +	}
    +	private static final    String Sql_select_clause = String_.Concat_lines_nl
    +	( "SELECT  p.page_id"
    +	, ",       p.page_title"
    +	, ",       t.text_data"
    +	, "FROM    page_dump p"
    +	, "        JOIN text t ON t.page_id = p.page_id"
    +	, "WHERE   p.page_id > ?"
    +	, "AND     p.page_namespace = ?{0}" 
    +	, "ORDER BY p.page_id"
    +	);
    +	private static final String
    +	  Sql_select__redirect_y = "\nAND     p.page_is_redirect = 1"
    +	, Sql_select__redirect_n = "\nAND     p.page_is_redirect = 0"
    +	, Sql_select__redirect__ = ""
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Bfr_arg__dump_dir.java b/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Bfr_arg__dump_dir.java
    index a27517de8..5e96d8efa 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Bfr_arg__dump_dir.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Bfr_arg__dump_dir.java
    @@ -13,3 +13,31 @@ 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.bldrs.cmds.diffs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*;
    +import gplx.core.brys.*; import gplx.xowa.wikis.*;
    +class Bfr_arg__dump_dir implements Bfr_arg {	// .dump_dir = "/xowa/wiki/en.wikipedia.org/"
    +	private final Xow_wiki wiki;
    +	public Bfr_arg__dump_dir(Xow_wiki wiki) {this.wiki = wiki;}
    +	public void Bfr_arg__add(Bry_bfr bfr) {
    +		bfr.Add(wiki.Fsys_mgr().Root_dir().RawBry());
    +	}
    +}
    +class Bfr_arg__dump_core implements Bfr_arg {// .dump_core = "en.wikipedia.org-core.xowa"
    +	private final Xow_wiki wiki;
    +	public Bfr_arg__dump_core(Xow_wiki wiki) {this.wiki = wiki;}
    +	public void Bfr_arg__add(Bry_bfr bfr) {
    +		bfr.Add_str_u8(wiki.Data__core_mgr().Db__core().Url().NameAndExt());
    +	}
    +}
    +class Bfr_arg__dump_domain implements Bfr_arg {// .dump_domain = en.wikipedia.org
    +	private final Xow_wiki wiki;
    +	public Bfr_arg__dump_domain(Xow_wiki wiki) {this.wiki = wiki;}
    +	public void Bfr_arg__add(Bry_bfr bfr) {
    +		bfr.Add(wiki.Domain_bry());
    +	}
    +}
    +class Bfr_arg__dir_spr implements Bfr_arg {// .dir_spr = "/"
    +	public void Bfr_arg__add(Bry_bfr bfr) {
    +		bfr.Add_byte(gplx.core.envs.Op_sys.Cur().Fsys_dir_spr_byte());
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Xob_diff_build_cmd.java b/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Xob_diff_build_cmd.java
    index a27517de8..054674fe9 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Xob_diff_build_cmd.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Xob_diff_build_cmd.java
    @@ -13,3 +13,32 @@ 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.bldrs.cmds.diffs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*;
    +import gplx.xowa.bldrs.wkrs.*;
    +public class Xob_diff_build_cmd implements Xob_cmd {
    +	private final    Xob_bldr bldr; private final    Xowe_wiki wiki;
    +	private String prev_url, curr_url, diff_url; private int commit_interval;
    +	private int[] db_ids = Int_ary_.Empty; private String bld_name = "all";
    +	public Xob_diff_build_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.bldr = bldr; this.wiki = wiki;}
    +	public String Cmd_key()		{return Xob_cmd_keys.Key_diff_build;}
    +	public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return null;}
    +	public void Cmd_run() {
    +		new Xob_diff_build_wkr(bldr, wiki, prev_url, curr_url, diff_url, commit_interval, new Xowd_tbl_mapr(bld_name, db_ids)).Exec();
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk__prev_url_))				prev_url = m.ReadStr("v");
    +		else if	(ctx.Match(k, Invk__curr_url_))				curr_url = m.ReadStr("v");
    +		else if	(ctx.Match(k, Invk__diff_url_))				diff_url = m.ReadStr("v");
    +		else if	(ctx.Match(k, Invk__commit_interval_))		commit_interval = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk__db_ids_))				db_ids = Int_ary_.Parse(m.ReadStr("v"), "|");
    +		else if	(ctx.Match(k, Invk__bld_name_))				bld_name = m.ReadStr("v");
    +		else												return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	public void Cmd_init(Xob_bldr bldr) {}
    +	public void Cmd_bgn(Xob_bldr bldr) {}
    +	public void Cmd_end() {}
    +	public void Cmd_term() {}
    +	private static final String Invk__prev_url_ = "prev_url_", Invk__curr_url_ = "curr_url_", Invk__diff_url_ = "diff_url_"
    +	, Invk__commit_interval_ = "commit_interval_", Invk__db_ids_ = "db_ids_", Invk__bld_name_ = "bld_name_";
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Xob_diff_build_wkr.java b/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Xob_diff_build_wkr.java
    index a27517de8..cc7bfd1e3 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Xob_diff_build_wkr.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Xob_diff_build_wkr.java
    @@ -13,3 +13,81 @@ 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.bldrs.cmds.diffs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*;
    +import gplx.core.brys.*; import gplx.core.brys.fmts.*;
    +import gplx.dbs.*; import gplx.dbs.metas.*; import gplx.dbs.diffs.*; import gplx.dbs.diffs.builds.*; import gplx.dbs.diffs.itms.*;
    +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*;
    +class Xob_diff_build_wkr {		
    +	private final    Gfdb_diff_bldr dif_bldr = new Gfdb_diff_bldr();
    +	private final    Xowe_wiki wiki;
    +	private Db_conn old_conn, new_conn, dif_conn;
    +	private final    Xowd_tbl_mapr tbl_mapr;
    +	public Xob_diff_build_wkr(Xob_bldr bldr, Xowe_wiki wiki, String old_url, String new_url, String dif_url, int commit_interval, Xowd_tbl_mapr tbl_mapr) {
    +		this.wiki = wiki;
    +		wiki.Init_by_wiki();
    +		Bry_fmt url_fmt = Bry_fmt.New("").Args_(New_url_args(wiki, tbl_mapr.Name));
    +		Bry_bfr tmp_bfr = Bry_bfr_.New();
    +		old_conn = New_conn(tmp_bfr, wiki, url_fmt, Bool_.N, old_url);
    +		new_conn = New_conn(tmp_bfr, wiki, url_fmt, Bool_.N, new_url);
    +		dif_conn = New_conn(tmp_bfr, wiki, url_fmt, Bool_.Y, dif_url);
    +		this.tbl_mapr = tbl_mapr;
    +	}
    +	public void Exec() {
    +		Gdif_core dif_core = new Gdif_core(dif_conn);
    +		String name = String_.Format("{0}|{1}|diffs|{2}", wiki.Domain_str(), tbl_mapr.Name, wiki.Props().Modified_latest().XtoStr_fmt(DateAdp_.Fmt__yyyyMMdd));	// EX: "simple.wikipedia.org|text|diffs|20160112"
    +		String made_by = wiki.App().User().Key();
    +		Gdif_job_itm job_itm = dif_core.New_job(name, made_by);
    +		Gdif_bldr_ctx ctx = new Gdif_bldr_ctx().Init(dif_core, job_itm);
    +		Gfdb_diff_wkr__db dif_wkr = new Gfdb_diff_wkr__db();
    +		Gdif_db dif_db = dif_core.Db();
    +		dif_wkr.Init_conn(dif_db, 1000);
    +		dif_bldr.Init(dif_wkr);
    +		// wiki.Data__core_mgr().Db__core().Conn().Conn_info();
    +		Xow_db_file[] db_file_ary = wiki.Data__core_mgr().Db__core().Tbl__db().Select_all(wiki.Data__core_mgr().Props(), Io_url_.Empty);
    +		int db_files_len = db_file_ary.length;
    +		for (int i = 0; i < db_files_len; ++i) {
    +			Xow_db_file db_file = db_file_ary[i];
    +			if (tbl_mapr.Db_ids__has(db_file.Tid()))
    +				Compare(ctx);
    +		}
    +//			int old_tbl_len = old_tbl_mgr.Len();
    +//			for (int i = 0; i < old_tbl_len; ++i) {
    +//				Dbmeta_tbl_itm old_tbl = old_tbl_mgr.Get_at(i);
    +//				Dbmeta_tbl_itm new_tbl = new_tbl_mgr.Get_by(old_tbl.Name());
    +//				if (new_tbl == null) {
    +//					// delete all
    +//				}
    +//			}
    +	}
    +	private void Compare(Gdif_bldr_ctx ctx) {
    +		Dbmeta_tbl_mgr old_tbl_mgr = old_conn.Meta_mgr();
    +		Dbmeta_tbl_mgr new_tbl_mgr = old_conn.Meta_mgr();
    +		int new_tbl_len = new_tbl_mgr.Len();
    +		for (int i = 0; i < new_tbl_len; ++i) {
    +			Dbmeta_tbl_itm new_tbl = new_tbl_mgr.Get_at(i);
    +			Dbmeta_tbl_itm old_tbl = old_tbl_mgr.Get_by(new_tbl.Name()); if (old_tbl == null) continue;
    +			Gfdb_diff_tbl dif_tbl = Gfdb_diff_tbl.New(new_tbl);
    +			dif_bldr.Compare(ctx, dif_tbl, old_conn, new_conn);
    +			// save txn
    +		}
    +	}
    +	public static Db_conn New_conn(Bry_bfr tmp_bfr, Xow_wiki wiki, Bry_fmt fmtr, boolean autocreate, String url_fmt) {
    +		fmtr.Fmt_(url_fmt).Bld_many(tmp_bfr);
    +		return Db_conn_bldr.Instance.Get_or_autocreate(autocreate, Io_url_.new_any_(tmp_bfr.To_str_and_clear()));
    +	}
    +	private static Bfr_fmt_arg[] New_url_args(Xow_wiki wiki, String db_mapr_name) {
    +		Bfr_fmt_arg[] rv = new Bfr_fmt_arg[]
    +		{ new Bfr_fmt_arg(Bry_.new_a7(".dump_dir"), new Bfr_arg__dump_dir(wiki))
    +		, new Bfr_fmt_arg(Bry_.new_a7(".dump_core"), new Bfr_arg__dump_core(wiki))
    +		, new Bfr_fmt_arg(Bry_.new_a7(".dump_domain"), new Bfr_arg__dump_domain(wiki))
    +		, new Bfr_fmt_arg(Bry_.new_a7(".dir_spr"), new Bfr_arg__dir_spr())
    +		, new Bfr_fmt_arg(Bry_.new_a7(".dif_name"), Bfr_arg_.New_bry(db_mapr_name))
    +		};
    +		return rv;
    +	}		
    +	//old_url='~{.dump_dir}-prev/~{.dump_core}';
    +	//new_url='~{.dump_dir}/~{.dump_core}';
    +	//dif_url='~{.dump_dir}/~{.dump_domain}-{.dif_name}-diff.xowa';
    +	// old_conn='data source="~{.dump_dir}/~{.dump_core}";url='
    +	// dif_conn='gplx_key=sqlite;url='
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Xob_diff_manifest.java b/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Xob_diff_manifest.java
    index a27517de8..3f48af4e1 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Xob_diff_manifest.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Xob_diff_manifest.java
    @@ -13,3 +13,35 @@ 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.bldrs.cmds.diffs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*;
    +import gplx.dbs.*; import gplx.dbs.metas.*; import gplx.dbs.diffs.*;
    +class Xob_diff_manifest {
    +	// page|page_id|*
    +	public static Gfdb_diff_tbl[] Parse(Db_conn conn, String src_str) {
    +//			byte[][] rows_ary = Bry_split_.Split_lines(Bry_.new_u8(src_str));
    +//			int rows_len = rows_ary.length;
    +//			for (int i = 0; i < rows_len; ++i) {
    +//				byte[] row = rows_ary[i];
    +//				byte[][] itms_ary = Bry_split_.Split(row, Byte_ascii.Pipe);
    +//				byte[] tbl_name = itms_ary[0];
    +//				conn.Meta_tbl_exists
    +//				int itms_len = itms_ary.length;
    +//				for (int j = 0; j < itms_len; ++j) {
    +//					byte[] itm = itms_ary[j];
    +//                    Tfds.Dbg(itm);
    +//				}
    +//				Gfdb_diff_tbl tbl = new Gfdb_diff_tbl(String_.new_u8(itms_ary[0]),keys, vals, Db_rdr_.Empty);
    +//			}
    +		return null;
    +	}
    +}
    +/*
    +class Wkr {
    +	public void Make() {		
    +		sdif_db_mgr sdif_db = new Sdif_db_mgr(conn);
    +		for (int i = 0; i < rhs_tbl_len; ++i) {
    +			
    +		}
    +	}		
    +}
    +*/
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Xowd_tbl_mapr.java b/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Xowd_tbl_mapr.java
    index a27517de8..aa18ba717 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Xowd_tbl_mapr.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/diffs/Xowd_tbl_mapr.java
    @@ -13,3 +13,20 @@ 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.bldrs.cmds.diffs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*;
    +import gplx.xowa.wikis.data.*;
    +class Xowd_tbl_mapr {
    +	public Xowd_tbl_mapr(String name, int[] db_ids) {
    +		this.Name = name;
    +		this.Db_ids = db_ids; 
    +	}
    +	public final    String Name;
    +	public final    int[] Db_ids;
    +	public boolean Db_ids__has(int id) {return true;}
    +//		private static List_adp Fill_tbl_names(List_adp rv, int db_tid) {
    +//			switch (db_tid) {
    +//				case Xow_db_file_.Tid__cat:
    +//					return 
    +//					break;
    +//		}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/Xob_init_base.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/Xob_init_base.java
    index a27517de8..f20f29b01 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/Xob_init_base.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/Xob_init_base.java
    @@ -13,3 +13,53 @@ 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.bldrs.cmds.texts; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*;
    +import gplx.xowa.xtns.wbases.*;
    +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.bldrs.xmls.*; import gplx.xowa.bldrs.cmds.texts.xmls.*;
    +import gplx.xowa.bldrs.css.*; import gplx.xowa.wikis.domains.*;
    +import gplx.xowa.wikis.data.*;
    +public abstract class Xob_init_base implements Xob_cmd, Gfo_invk {
    +	private Xob_bldr bldr; private Xowe_wiki wiki; private Gfo_usr_dlg usr_dlg;
    +	private byte wbase_enabled = Bool_.__byte;
    +	public Xob_init_base Ctor(Xob_bldr bldr, Xowe_wiki wiki) {this.bldr = bldr; this.wiki = wiki; this.usr_dlg = wiki.Appe().Usr_dlg(); return this;}
    +	public abstract String Cmd_key();
    +	public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return null;}
    +	public abstract void Cmd_ini_wdata(Xob_bldr bldr, Xowe_wiki wiki);
    +	public abstract void Cmd_run_end(Xowe_wiki wiki);
    +	@gplx.Virtual public void Cmd_init(Xob_bldr bldr) {		// add other cmds; EX: wikidata
    +		bldr.Import_marker().Bgn(wiki);
    +		if (wbase_enabled == Bool_.__byte) wbase_enabled = wiki.Domain_tid() == Xow_domain_tid_.Tid__wikidata ? Bool_.Y_byte : Bool_.N_byte;	// if wbase_enabled not explicitly set, set it to y if wiki is "www.wikidata.org"
    +		if (wbase_enabled == Bool_.Y_byte)		// if wbase_enabled, auto-add wdata_wkrs bldr
    +			this.Cmd_ini_wdata(bldr, wiki);
    +	}
    +	public void Cmd_bgn(Xob_bldr bldr) {}
    +	public void Cmd_run() {						// parse site_info
    +		gplx.core.ios.streams.Io_stream_rdr src_rdr = wiki.Import_cfg().Src_rdr(); usr_dlg.Plog_many("", "", "reading dump header: ~{0}", src_rdr.Url().Raw());
    +		Xob_siteinfo_parser_.Parse(Xob_siteinfo_parser_.Extract(src_rdr), wiki);
    +		this.Cmd_run_end(wiki);					// save site info
    +	}
    +	public void Cmd_end() {
    +		wiki.Appe().Gui_mgr().Html_mgr().Portal_mgr().Wikis().Itms_reset();	// dirty wiki list so that next refresh will load itm
    +
    +		// if (wiki.Appe().Setup_mgr().Dump_mgr().Css_wiki_update()) {	// NOTE: used to be option, but was no longer being set; may need to reinstate; DATE:2016-12-21
    +			Io_url url = wiki.Appe().Fsys_mgr().Wiki_css_dir(wiki.Domain_str()).GenSubFil(Xoa_css_extractor.Css_wiki_name);
    +			usr_dlg.Log_many("", "", "deleting css: ~{0}", url.Raw());
    +			Io_mgr.Instance.DeleteFil_args(url).MissingFails_off().Exec();
    +		// }
    +
    +		// always save xowa_cfg data at end of init step, not term step; else, other builder commands will load empty cfg and import data will be null; DATE:2017-02-20
    +		if (!gplx.core.envs.Env_.Mode_testing()) {	// need else Xob_init_base_tst fails; DATE:2017-02-20
    +			Xowd_cfg_tbl_.Upsert__import(wiki);
    +			Xowd_cfg_tbl_.Upsert__create(wiki);
    +		}
    +	}
    +	@gplx.Virtual public void Cmd_term() {}
    +	@gplx.Virtual public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_src_xml_fil_))				wiki.Import_cfg().Src_fil_xml_(m.ReadIoUrl("v"));
    +		else if	(ctx.Match(k, Invk_src_bz2_fil_))				wiki.Import_cfg().Src_fil_bz2_(m.ReadIoUrl("v"));
    +		else if	(ctx.Match(k, Invk_wdata_enabled_))				wbase_enabled = m.ReadYn("v") ? Bool_.Y_byte : Bool_.N_byte;
    +		else if	(ctx.Match(k, Invk_owner))						return bldr.Cmd_mgr();
    +		else return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}	private static final String Invk_src_xml_fil_ = "src_xml_fil_", Invk_src_bz2_fil_ = "src_bz2_fil_", Invk_owner = "owner", Invk_wdata_enabled_ = "wdata_enabled_";
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/Xob_term_base.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/Xob_term_base.java
    index a27517de8..9312d7a1c 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/Xob_term_base.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/Xob_term_base.java
    @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository:
     GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
     Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
     */
    +package gplx.xowa.bldrs.cmds.texts; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*;
    +import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.bldrs.xmls.*; import gplx.xowa.xtns.wbases.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.wikis.dbs.*;	
    +public abstract class Xob_term_base implements Xob_cmd, Gfo_invk {
    +	public Xob_term_base Ctor(Xob_bldr bldr, Xowe_wiki wiki) {this.wiki = wiki; return this;} private Xowe_wiki wiki;
    +	public abstract String Cmd_key();
    +	public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return null;}
    +	public void Cmd_init(Xob_bldr bldr) {}
    +	public void Cmd_bgn(Xob_bldr bldr) {}
    +	public void Cmd_run() {}
    +	public void Cmd_end() {
    +		Xoae_app app = wiki.Appe();
    +
    +		// dirty wiki list so that next refresh will load wiki
    +		app.Gui_mgr().Html_mgr().Portal_mgr().Wikis().Itms_reset();
    +
    +		// clear cache, else import will load new page with old items from cache; DATE:2013-11-21
    +		app.Free_mem(false);
    +
    +		// update main page
    +		byte[] new_main_page = gplx.xowa.langs.msgs.Xow_mainpage_finder.Find_or(wiki, wiki.Props().Siteinfo_mainpage());	// get new main_page from mainpage_finder
    +		wiki.Props().Main_page_(new_main_page);
    +		wiki.Data__core_mgr().Db__core().Tbl__cfg().Upsert_bry(gplx.xowa.wikis.data.Xowd_cfg_key_.Grp__wiki_init, gplx.xowa.wikis.data.Xowd_cfg_key_.Key__init__main_page         , new_main_page);
    +
    +		// remove import marker
    +		app.Bldr().Import_marker().End(wiki);
    +
    +		// flag init_needed prior to show; dir_info will show page_txt instead of page_gz;
    +		wiki.Init_needed_(true);
    +
    +		// force load; needed to pick up MediaWiki ns for MediaWiki:mainpage
    +		wiki.Init_assert();
    +
    +		Cmd_end_hook();
    +	}
    +	public abstract void Cmd_end_hook();
    +	public void Cmd_term() {}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		return this;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_init_cmd.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_init_cmd.java
    index a27517de8..514fc6ae4 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_init_cmd.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_init_cmd.java
    @@ -13,3 +13,29 @@ 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.bldrs.cmds.texts.sqls; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.*;
    +import gplx.xowa.bldrs.*; import gplx.xowa.apps.apis.xowa.bldrs.imports.*;
    +import gplx.xowa.xtns.wbases.imports.*;
    +public class Xob_init_cmd extends Xob_init_base {
    +	public Xob_init_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.Ctor(bldr, wiki);}
    +	@Override public String Cmd_key() {return Xob_cmd_keys.Key_text_init;}
    +	@Override public void Cmd_ini_wdata(Xob_bldr bldr, Xowe_wiki wiki) {
    +		bldr.Cmd_mgr().Add_cmd(wiki, Xob_cmd_keys.Key_wbase_qid);
    +		bldr.Cmd_mgr().Add_cmd(wiki, Xob_cmd_keys.Key_wbase_pid);
    +	}
    +	@Override public void Cmd_init(Xob_bldr bldr) {
    +		super.Cmd_init(bldr);
    +//			gplx.dbs.qrys.bats.Db_batch__journal_wal.Batch__init(gplx.dbs.Db_conn_pool.Instance.Batch_mgr());
    +	}
    +
    +	@Override public void Cmd_run_end(Xowe_wiki wiki) {
    +		if (gplx.xowa.wikis.data.Xow_db_file__core_.Find_core_fil_or_null(wiki) != null)
    +			throw wiki.Appe().Bldr().Usr_dlg().Fail_many("", "", "directory must not contain any .xowa or .sqlite3 files: dir=~{0}", wiki.Fsys_mgr().Root_dir().Raw());
    +		Xowe_wiki_.Create(wiki, wiki.Import_cfg().Src_rdr_len(), wiki.Import_cfg().Src_fil().NameOnly());
    +	}
    +	@Override public void Cmd_term() {
    +		super.Cmd_term();
    +//			gplx.dbs.qrys.bats.Db_batch__journal_wal.Batch__term(gplx.dbs.Db_conn_pool.Instance.Batch_mgr());
    +//			gplx.dbs.Db_conn_pool.Instance.Rls_all();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_ns_to_db_wkr__text.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_ns_to_db_wkr__text.java
    index a27517de8..8e8104b14 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_ns_to_db_wkr__text.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_ns_to_db_wkr__text.java
    @@ -13,3 +13,16 @@ 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.bldrs.cmds.texts.sqls; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.*;
    +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*;
    +public class Xob_ns_to_db_wkr__text implements Xob_ns_to_db_wkr {
    +	public byte Db_tid() {return Xow_db_file_.Tid__text;}
    +	public void Tbl_init(Xow_db_file db) {
    +		Xowd_text_tbl tbl = db.Tbl__text();
    +		tbl.Create_tbl();
    +		tbl.Insert_bgn();
    +	}
    +	public void Tbl_term(Xow_db_file db) {
    +		db.Tbl__text().Insert_end(); 
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_page_cmd.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_page_cmd.java
    index a27517de8..cfaaa8522 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_page_cmd.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_page_cmd.java
    @@ -13,3 +13,97 @@ 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.bldrs.cmds.texts.sqls; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.*;
    +import gplx.dbs.*; import gplx.core.ios.*; import gplx.xowa.bldrs.cmds.*;
    +import gplx.xowa.bldrs.wkrs.*;
    +import gplx.xowa.wikis.nss.*;
    +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.wikis.dbs.*; 
    +import gplx.xowa.wikis.*; import gplx.xowa.bldrs.filters.dansguardians.*; import gplx.xowa.apps.apis.xowa.bldrs.imports.*;
    +import gplx.xowa.parsers.utils.*; import gplx.xowa.addons.bldrs.files.cmds.*; import gplx.xowa.addons.bldrs.files.dbs.*;
    +public class Xob_page_cmd extends Xob_itm_basic_base implements Xob_page_wkr, Gfo_invk {
    +	private Xow_db_mgr db_mgr; private Db_idx_mode idx_mode = Db_idx_mode.Itm_end; private Xowd_page_tbl page_core_tbl; private Io_stream_zip_mgr text_zip_mgr; private byte text_zip_tid;
    +	private Xop_redirect_mgr redirect_mgr; private Xob_redirect_tbl redirect_tbl; private boolean redirect_id_enabled;
    +	private DateAdp modified_latest = DateAdp_.MinValue; private int page_count_all, page_count_main = 0; private int commit_interval = 100000;	// 100 k				
    +	private Dg_match_mgr dg_match_mgr; private Xob_ns_to_db_mgr ns_to_db_mgr; 
    +	public Xob_page_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.Cmd_ctor(bldr, wiki);}
    +	public String Page_wkr__key() {return Xob_cmd_keys.Key_text_page;}
    +	public void Page_wkr__bgn() {
    +		Xoae_app app = wiki.Appe();
    +		this.redirect_mgr = wiki.Redirect_mgr(); 
    +		this.db_mgr = wiki.Db_mgr_as_sql().Core_data_mgr();
    +		this.page_core_tbl = db_mgr.Tbl__page();
    +		this.text_zip_mgr = wiki.Utl__zip_mgr();
    +		this.text_zip_tid = Xobldr_cfg.Zip_mode__text(app);
    +
    +		// NOTE: rebuild needed to add canonical namespaces as templates; else, redirects to English namespaces won't work in non-English wikis; EX: gu.w and #REDIRECT [[Template:COLON]]; DATE:2017-02-20
    +		Xow_ns_mgr_.rebuild_(wiki.Lang(), wiki.Ns_mgr());
    +
    +		this.ns_to_db_mgr = new Xob_ns_to_db_mgr(new Xob_ns_to_db_wkr__text(), db_mgr, Xobldr_cfg.Max_size__text(app));
    +		this.dg_match_mgr = Dg_match_mgr.New_mgr(app, wiki);
    +		if (dg_match_mgr != null) redirect_id_enabled = true; // always enable redirect_id if dg_match_mgr enabled; DATE:2016-01-04
    +		if (redirect_id_enabled) {
    +			this.redirect_tbl = new Xob_redirect_tbl(wiki.Fsys_mgr().Root_dir(), gplx.langs.htmls.encoders.Gfo_url_encoder_.Http_url_ttl).Create_table();
    +			redirect_tbl.Conn().Txn_bgn("bldr__page__redirect");
    +		}
    +		app.Bldr().Dump_parser().Trie_tab_del_();	// disable swapping 	 for \t
    +		byte[] ns_file_map = Xobldr_cfg.New_ns_file_map(app, wiki.Import_cfg().Src_rdr_len());
    +		Xob_ns_file_itm.Init_ns_bldr_data(Xow_db_file_.Tid__text, wiki.Ns_mgr(), ns_file_map);
    +		if (idx_mode.Tid_is_bgn()) page_core_tbl.Create_idx();
    +		page_core_tbl.Insert_bgn();
    +		usr_dlg.Prog_many("", "", "import.page.bgn");
    +	}
    +	public void Page_wkr__run(Xowd_page_itm page) {
    +		int id = page.Id();
    +		DateAdp modified = page.Modified_on(); if (modified.compareTo(modified_latest) == CompareAble_.More) modified_latest = modified;
    +		byte[] text_raw = page.Text(); int text_raw_len = page.Text_len();
    +		Xoa_ttl redirect_ttl = redirect_mgr.Extract_redirect(text_raw, text_raw_len); boolean redirect = redirect_ttl != null;
    +		page.Redirected_(redirect);
    +		Xow_ns ns = page.Ns();
    +		int random_int = ns.Count() + 1; ns.Count_(random_int);
    +		if (dg_match_mgr != null) {
    +			if (dg_match_mgr.Match(1, id, ns.Id(), page.Ttl_page_db(), page.Ttl_full_db(), wiki.Lang(), text_raw)) return;
    +		}
    +		byte[] text_zip = text_zip_mgr.Zip(text_zip_tid, text_raw);
    +		Xow_db_file text_db = ns_to_db_mgr.Get_by_ns(ns.Bldr_data(), text_zip.length);
    +		try {db_mgr.Create_page(page_core_tbl, text_db.Tbl__text(), id, page.Ns_id(), page.Ttl_page_db(), redirect, modified, text_zip, text_raw_len, random_int, text_db.Id(), -1);}
    +		catch (Exception e) {
    +			throw Err_.new_exc(e, "bldr", "create page in db failed; skipping page", "id", id, "ns", page.Ns_id(), "name", page.Ttl_page_db(), "redirect", redirect, "modified", modified, "text_len", text_raw_len, "text_db_id", text_db.Id());
    +		}
    +		if (redirect && redirect_id_enabled)
    +			redirect_tbl.Insert(id, page.Ttl_page_db(), redirect_ttl);
    +		++page_count_all;
    +		if (ns.Id_is_main() && !page.Redirected()) ++page_count_main;
    +		if (page_count_all % commit_interval == 0) {
    +			page_core_tbl.Conn().Txn_sav(); text_db.Conn().Txn_sav();
    +			if (redirect_id_enabled) redirect_tbl.Conn().Txn_sav();
    +			if (dg_match_mgr != null) dg_match_mgr.Commit();
    +		}
    +	}
    +	public void Page_wkr__run_cleanup() {
    +		usr_dlg.Log_many("", "", "import.page: insert done; committing pages; pages=~{0}", page_count_all);
    +		ns_to_db_mgr.Rls_all();
    +		page_core_tbl.Insert_end();
    +	}
    +	public void Page_wkr__end() {
    +		if (dg_match_mgr != null) dg_match_mgr.Rls();
    +		usr_dlg.Log_many("", "", "import.page: updating core stats");
    +		Xow_ns_mgr ns_mgr = wiki.Ns_mgr();
    +		Xow_db_file db_core = db_mgr.Db__core();
    +		db_core.Tbl__site_stats().Update(page_count_main, page_count_all, ns_mgr.Ns_file().Count());	// save page stats
    +		db_core.Tbl__ns().Insert(ns_mgr);															// save ns
    +		if (idx_mode.Tid_is_end()) page_core_tbl.Create_idx();
    +		if (redirect_id_enabled) {
    +			redirect_tbl.Conn().Txn_end();
    +			redirect_tbl.Update_trg_redirect_id(db_core.Url(), 1);
    +			redirect_tbl.Update_src_redirect_id(db_core.Url(), page_core_tbl.Conn());
    +		}
    +	}
    +	@Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_commit_interval_))			commit_interval = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk_idx_mode_))					idx_mode = Db_idx_mode.Xto_itm(m.ReadStr("v"));
    +		else if	(ctx.Match(k, Invk_redirect_id_enabled_))		redirect_id_enabled = m.ReadYn("v");
    +		else													return super.Invk(ctx, ikey, k, m);
    +		return this;
    +	}
    +	private static final String Invk_commit_interval_ = "commit_interval_", Invk_idx_mode_ = "idx_mode_", Invk_redirect_id_enabled_ = "redirect_id_enabled_";
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_page_delete_cmd.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_page_delete_cmd.java
    index a27517de8..6f66ea5cc 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_page_delete_cmd.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_page_delete_cmd.java
    @@ -13,3 +13,77 @@ 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.bldrs.cmds.texts.sqls; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.*;
    +import gplx.dbs.*; import gplx.xowa.wikis.data.*;
    +import gplx.xowa.bldrs.wkrs.*;
    +public class Xob_page_delete_cmd extends Xob_cmd_base {
    +	private final    Xow_wiki wiki;
    +	public Xob_page_delete_cmd(Xob_bldr bldr, Xow_wiki wiki) {this.wiki = wiki;}
    +	@Override public String Cmd_key() {return Xob_cmd_keys.Key_text_delete_page;}
    +	@Override public void Cmd_run() {
    +		wiki.Init_by_wiki();
    +		Xow_db_file core_db = wiki.Data__core_mgr().Db__core();
    +		Db_conn core_db_conn = core_db.Conn();
    +		Gfo_usr_dlg usr_dlg = Gfo_usr_dlg_.Instance;
    +		usr_dlg.Plog_many("", "", "creating page_filter");
    +		if (!core_db_conn.Meta_tbl_exists("page_filter")) {
    +			core_db_conn.Meta_tbl_create
    +			( Dbmeta_tbl_itm.New("page_filter", new Dbmeta_fld_itm[]
    +			{	Dbmeta_fld_itm.new_int("page_id").Primary_y_()
    +			,	Dbmeta_fld_itm.new_int("page_text_db_id")
    +			}
    +			,   Dbmeta_idx_itm.new_normal_by_tbl("page_filter", "db_id__page", "page_text_db_id", "page_id")
    +			,   Dbmeta_idx_itm.new_normal_by_tbl("page_filter", "page_id", "page_id")
    +			));
    +		}
    +		core_db_conn.Exec_sql_plog_ntx("finding missing redirects", String_.Concat_lines_nl_skip_last
    +		( "INSERT INTO page_filter (page_id, page_text_db_id)"
    +		, "SELECT  ptr.page_id, ptr.page_text_db_id"
    +		, "FROM    page ptr"
    +		, "        LEFT JOIN page orig ON ptr.page_redirect_id = orig.page_id"
    +		, "WHERE   ptr.page_is_redirect = 1"
    +		, "AND     orig.page_id IS NULL"
    +		, "UNION"
    +		, "SELECT  ptr.page_id, ptr.page_text_db_id"
    +		, "FROM    page ptr"
    +		, "WHERE   ptr.page_is_redirect = 1"
    +		, "AND     ptr.page_redirect_id = -1"
    +		, ";"
    +		));
    +		
    +		String db_file_cur = "";
    +		try {
    +			Xow_db_file[] db_file_ary = core_db.Tbl__db().Select_all(wiki.Data__core_mgr().Props(), wiki.Fsys_mgr().Root_dir());
    +			int len = db_file_ary.length;
    +			for (int i = 0; i < len; ++i) {
    +				boolean db_file_is_text = Bool_.N, db_file_is_cat = Bool_.N, db_file_is_search = Bool_.N;
    +				Xow_db_file db_file = db_file_ary[i];
    +				switch (db_file.Tid()) {
    +					case Xow_db_file_.Tid__core: case Xow_db_file_.Tid__wiki_solo: case Xow_db_file_.Tid__text_solo:
    +						// if mode is lot, then "core" db does not have cat, search; skip; DATE:2016-01-31
    +						if (wiki.Data__core_mgr().Props().Layout_text().Tid_is_lot()) continue;
    +						db_file_is_cat = db_file_is_search = Bool_.Y;	// do not set db_file_is_text to true; DATE:2016-10-18
    +						break;
    +					case Xow_db_file_.Tid__text:		db_file_is_text = Bool_.Y; break;
    +					case Xow_db_file_.Tid__cat:			db_file_is_cat = Bool_.Y; break;
    +					case Xow_db_file_.Tid__search_link:	db_file_is_search = Bool_.Y; break;		// changed from search_data to search_link; DATE:2016-10-19
    +				}
    +				db_file_cur = db_file.Url().Raw();
    +				int db_id = db_file.Id();
    +				if	(db_file_is_text)	Run_sql(core_db_conn, db_file.Url(), db_id, "deleting text: "  + db_id, "DELETE FROM text WHERE page_id IN (SELECT page_id FROM page_filter WHERE page_text_db_id = {0});");
    +				if	(db_file_is_cat)	Run_sql(core_db_conn, db_file.Url(), db_id, "deleting cat: "   + db_id, "DELETE FROM cat_link WHERE cl_from IN (SELECT page_id FROM page_filter);");
    +				if	(db_file_is_search)	Run_sql(core_db_conn, db_file.Url(), db_id, "deleting search:" + db_id, "DELETE FROM search_link WHERE page_id IN (SELECT page_id FROM page_filter);");
    +				if	(db_file_is_text || db_file_is_cat || db_file_is_search)
    +					db_file.Conn().Env_vacuum();
    +			}
    +		} catch (Exception e) {Gfo_usr_dlg_.Instance.Warn_many("", "", "fatal error during page deletion: cur=~{0} err=~{1}", db_file_cur, Err_.Message_gplx_log(e));}
    +		core_db_conn.Exec_sql_plog_ntx("deleting from table: page", "DELETE FROM page WHERE page_id IN (SELECT page_id FROM page_filter);");
    +		// core_db_conn.Meta_tbl_delete("page_filter");
    +		core_db_conn.Env_vacuum();
    +		usr_dlg.Plog_many("", "", "");
    +	}
    +	private void Run_sql(Db_conn core_db_conn, Io_url db_url, int db_id, String prog_msg, String sql) {
    +		new Db_attach_mgr(core_db_conn, new Db_attach_itm("data_db", db_url))
    +			.Exec_sql_w_msg(prog_msg			, sql, db_id);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_term_cmd.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_term_cmd.java
    index a27517de8..1b569d55b 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_term_cmd.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/sqls/Xob_term_cmd.java
    @@ -13,3 +13,23 @@ 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.bldrs.cmds.texts.sqls; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.*;
    +import gplx.dbs.cfgs.*; import gplx.xowa.wikis.dbs.*; import gplx.xowa.wikis.*;
    +import gplx.xowa.wikis.data.*;
    +public class Xob_term_cmd extends Xob_term_base {
    +	public Xob_term_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.Ctor(bldr, wiki); this.wiki = wiki;} private Xowe_wiki wiki;
    +	@Override public String Cmd_key() {return KEY;} public static final    String KEY = "text.term";
    +	@Override public void Cmd_end_hook() {
    +		// delete wiki's temp dir
    +		Io_mgr.Instance.DeleteDirDeep(wiki.Fsys_mgr().Tmp_dir());
    +
    +		// build fsdb
    +		gplx.fsdb.Fsdb_db_mgr__v2_bldr.Get_or_make(wiki, false);// always build file.user db; DATE:2015-05-12
    +
    +		// dansguardian
    +		if (wiki.App().Cfg().Get_bool_wiki_or(wiki, gplx.xowa.bldrs.filters.dansguardians.Dg_match_mgr.Cfg__enabled, false))
    +			new Xob_page_delete_cmd(wiki.Appe().Bldr(), wiki).Cmd_run();
    +
    +		wiki.Data__core_mgr().Rls();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Io_sort_cmd_ns.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Io_sort_cmd_ns.java
    index a27517de8..9ceaf46b0 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Io_sort_cmd_ns.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Io_sort_cmd_ns.java
    @@ -13,3 +13,47 @@ 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.bldrs.cmds.texts.tdbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.*;
    +import gplx.core.ios.*;
    +import gplx.xowa.wikis.tdbs.*; import gplx.xowa.wikis.tdbs.xdats.*;
    +public class Io_sort_cmd_ns implements Io_make_cmd {
    +	Xob_xdat_file_wtr fil_wtr; Bry_bfr reg_bfr = Bry_bfr_.New(), key_bfr_0 = Bry_bfr_.New_w_size(512), key_bfr_n = Bry_bfr_.New_w_size(512);
    +	int fil_count = 0, itm_count = 0;
    +	public Io_sort_cmd_ns(Gfo_usr_dlg usr_dlg) {this.usr_dlg = usr_dlg;} Gfo_usr_dlg usr_dlg;
    +	public int Trg_fil_max() {return trg_fil_max;} public Io_sort_cmd_ns Trg_fil_max_(int v) {trg_fil_max = v; return this;} private int trg_fil_max = 65 * Io_mgr.Len_kb;
    +	Io_url reg_url;
    +	public Io_sort_cmd Make_dir_(Io_url v) {make_dir = v; return this;} Io_url make_dir;
    +	public void Sort_bgn() {
    +		fil_count = itm_count = 0;
    +		fil_wtr = Xob_xdat_file_wtr.new_file_(trg_fil_max, make_dir);
    +		reg_url = make_dir.GenSubFil(Xotdb_dir_info_.Name_reg_fil);
    +	}
    +	public void Sort_do(Io_line_rdr rdr) {
    +		int itm_bgn = rdr.Itm_pos_bgn(), itm_end = rdr.Itm_pos_end(), key_bgn = rdr.Key_pos_bgn(), key_end = rdr.Key_pos_end();
    +		int itm_len = itm_end - itm_bgn;
    +		if (fil_wtr.FlushNeeded(itm_len)) Flush();
    +		byte[] bfr = rdr.Bfr();
    +		if (key_bfr_0.Len() == 0) {key_bfr_0.Add_mid(bfr, key_bgn, key_end);}
    +		key_bfr_n.Clear().Add_mid(bfr, key_bgn, key_end);
    +		fil_wtr.Bfr().Add_mid(rdr.Bfr(), itm_bgn, itm_end);
    +		fil_wtr.Add_idx(Byte_ascii.Null);
    +		++itm_count;
    +	}
    +	public void Sort_end() {
    +		Flush();
    +		Io_mgr.Instance.AppendFilBfr(reg_url, reg_bfr);
    +		//fil_wtr.Rls(); reg_bfr.Rls(); key_bfr_0.Rls(); key_bfr_n.Rls();
    +	}
    +	private void Flush() {
    +		reg_bfr
    +			.Add_int_variable(fil_count++).Add_byte(Byte_ascii.Pipe)
    +			.Add_bfr_and_preserve(key_bfr_0).Add_byte(Byte_ascii.Pipe)
    +			.Add_bfr_and_preserve(key_bfr_n).Add_byte(Byte_ascii.Pipe)
    +			.Add_int_variable(itm_count).Add_byte(Byte_ascii.Nl);
    +		itm_count = 0;
    +		key_bfr_0.Clear();
    +		if (fil_wtr.Fil_idx() % 10 == 0)
    +			usr_dlg.Prog_many("cmd_ns", "prog", "saving: ~{0} ~{1}", reg_url.OwnerDir().OwnerDir().NameOnly(), fil_wtr.Fil_url().NameOnly());			
    +		fil_wtr.Flush(usr_dlg);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Srch_bldr_wkr_base.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Srch_bldr_wkr_base.java
    index a27517de8..e785aeb12 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Srch_bldr_wkr_base.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Srch_bldr_wkr_base.java
    @@ -13,3 +13,93 @@ 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.bldrs.cmds.texts.tdbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.*;
    +import gplx.core.primitives.*; import gplx.core.ios.*;
    +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.bldrs.wtrs.*;
    +import gplx.xowa.langs.*; import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.dbs.*; import gplx.xowa.wikis.tdbs.*; import gplx.xowa.wikis.data.tbls.*;
    +public abstract class Srch_bldr_wkr_base extends Xob_itm_dump_base implements Xob_page_wkr {
    +	private final    Ordered_hash list = Ordered_hash_.New(); private Xol_lang_itm lang;
    +	public abstract String Page_wkr__key();
    +	public void Page_wkr__bgn() {
    +		make_dir = wiki.Tdb_fsys_mgr().Ns_dir();
    +		this.Init_dump(this.Page_wkr__key(), make_dir);
    +		lang = wiki.Lang(); // wiki.Appe().Lang_mgr().Lang_en();	// NOTE: was .Lang_en which is wrong (should match lang of wiki); DATE:2013-05-11
    +		tmp_wtr_mgr = new Xob_tmp_wtr_mgr(new Xob_tmp_wtr_wkr__ttl(temp_dir, dump_fil_len));
    +		if (wiki.Db_mgr().Tid() == Xodb_mgr_sql.Tid_sql)	// if sqlite, hard-code to ns_main; aggregates all ns into one
    +			ns_main = wiki.Ns_mgr().Ns_main();
    +	}	private Xob_tmp_wtr_mgr tmp_wtr_mgr; private Xow_ns ns_main;
    +	public void Page_wkr__run(Xowd_page_itm page) {
    +		// if (page.Ns_id() != Xow_ns_.Tid__main) return; // limit to main ns for now
    +		try {
    +			byte[] ttl = page.Ttl_page_db();
    +			byte[][] words = Split_ttl_into_words(lang, list, dump_bfr, ttl);
    +			Xob_tmp_wtr wtr = tmp_wtr_mgr.Get_or_new(ns_main == null ? page.Ns() : ns_main);
    +			int words_len = words.length;
    +			int row_len = 0;
    +			for (int i = 0; i < words_len; i++) {
    +				byte[] word = words[i];	
    +				row_len += word.length + 13;	// 13=5(id) + 5(page_len) + 3(dlms)
    +			}
    +			if (wtr.FlushNeeded(row_len)) wtr.Flush(bldr.Usr_dlg());
    +			for (int i = 0; i < words_len; i++) {
    +				byte[] word = words[i];				
    +				wtr.Bfr()	.Add(word)								.Add_byte(Byte_ascii.Pipe)
    +							.Add_base85_len_5(page.Id())			.Add_byte(Byte_ascii.Semic)
    +							.Add_base85_len_5(page.Text().length)	.Add_byte(Byte_ascii.Nl);
    +			}
    +		} catch (Exception e) {bldr.Usr_dlg().Warn_many("", "", "search_index:fatal error: err=~{0}", Err_.Message_gplx_full(e));}	// never let single page crash entire import
    +	}
    +	public void Page_wkr__run_cleanup() {}
    +	public void Page_wkr__end() {
    +		tmp_wtr_mgr.Flush_all(bldr.Usr_dlg());
    +		dump_bfr.ClearAndReset();
    +		Xobdc_merger.Ns(bldr.Usr_dlg(), tmp_wtr_mgr.Regy(), Xotdb_dir_info_.Name_search_ttl, temp_dir, make_dir, sort_mem_len, Io_line_rdr_key_gen_.first_pipe, this.Make_cmd_site());
    +		tmp_wtr_mgr.Rls_all();
    +		if (delete_temp) Io_mgr.Instance.DeleteDirDeep(temp_dir);
    +	}
    +	public abstract Io_make_cmd Make_cmd_site();
    +	public static byte[][] Split_ttl_into_words(Xol_lang_itm lang, Ordered_hash list, Bry_bfr bfr, byte[] ttl) {
    +		if (lang != null)	// null lang passed in by searcher
    +			ttl = lang.Case_mgr().Case_build_lower(ttl);
    +		int ttl_len = ttl.length; Bry_obj_ref word_ref = Bry_obj_ref.New(Bry_.Empty);
    +		int i = 0; boolean word_done = false;
    +		while (true) {
    +			if (word_done || i == ttl_len) {
    +				if (bfr.Len() > 0) {
    +					byte[] word = bfr.To_bry_and_clear();
    +					word_ref.Val_(word);
    +					if (!list.Has(word_ref)) list.Add(word_ref, word);	// don't add same word twice; EX: Title of "Can Can" should only have "Can" in index
    +				}
    +				if (i == ttl_len) break;
    +				word_done = false;
    +			}
    +			byte b = ttl[i];
    +			switch (b) {
    +				case Byte_ascii.Underline:	// underline is word-breaking; EX: A_B -> A, B
    +				case Byte_ascii.Space:		// should not occur, but just in case (only underscores)
    +				case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr:	// should not occur in titles, but just in case
    +
    +				case Byte_ascii.Dash:	// treat hypenated words separately
    +				case Byte_ascii.Dot:	// treat abbreviations as separate words; EX: A.B.C.
    +				case Byte_ascii.Bang: case Byte_ascii.Hash: case Byte_ascii.Dollar: case Byte_ascii.Percent:
    +				case Byte_ascii.Amp: case Byte_ascii.Paren_bgn: case Byte_ascii.Paren_end: case Byte_ascii.Star:
    +				case Byte_ascii.Comma: case Byte_ascii.Slash:
    +				case Byte_ascii.Colon: case Byte_ascii.Semic: case Byte_ascii.Gt:
    +				case Byte_ascii.Question: case Byte_ascii.At: case Byte_ascii.Brack_bgn: case Byte_ascii.Brack_end:
    +				case Byte_ascii.Pow: case Byte_ascii.Tick:
    +				case Byte_ascii.Curly_bgn: case Byte_ascii.Pipe: case Byte_ascii.Curly_end: case Byte_ascii.Tilde:
    +				case Byte_ascii.Quote:	case Byte_ascii.Apos: // FUTURE: apos will split "Earth's" to Earth and s; should remove latter
    +					++i;
    +					word_done = true;
    +					break;
    +				default:
    +					bfr.Add_byte(b);
    +					++i;
    +					break;
    +			}
    +		}
    +		byte[][] rv = (byte[][])list.To_ary(byte[].class);
    +		list.Clear(); list.Resize_bounds(16);
    +		return rv;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_calc_stats_cmd.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_calc_stats_cmd.java
    index a27517de8..5390f9115 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_calc_stats_cmd.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_calc_stats_cmd.java
    @@ -13,3 +13,101 @@ 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.bldrs.cmds.texts.tdbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.*;
    +import gplx.xowa.bldrs.wkrs.*;
    +import gplx.xowa.wikis.nss.*;
    +import gplx.xowa.wikis.data.site_stats.*; import gplx.xowa.wikis.data.tbls.*;
    +import gplx.xowa.wikis.tdbs.*; import gplx.xowa.wikis.tdbs.hives.*; import gplx.xowa.wikis.tdbs.xdats.*;
    +public class Xob_calc_stats_cmd extends Xob_itm_basic_base implements Xob_cmd {
    +	public Xob_calc_stats_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.Cmd_ctor(bldr, wiki);}
    +	public String Cmd_key() {return Xob_cmd_keys.Key_tdb_calc_stats;}
    +	public void Cmd_init(Xob_bldr bldr) {}
    +	public void Cmd_bgn(Xob_bldr bldr) {}
    +	public void Cmd_run() {Exec();}
    +	public void Cmd_end() {}
    +	public void Cmd_term() {}
    +	private void Exec() {
    +		int ns_len = wiki.Ns_mgr().Ords_len();
    +		int total = 0;
    +		for (int i = 0; i < ns_len; i++) {
    +			Xow_ns ns = wiki.Ns_mgr().Ords_ary()[i];
    +			int ns_count = Calc_counts(ns);
    +			ns.Count_(ns_count);
    +			total += ns_count;
    +		}
    +		int count_main = Calc_count_articles(wiki.Ns_mgr().Ns_main());
    +		int count_file = Calc_count_articles(wiki.Ns_mgr().Ns_file());
    +		Bry_bfr bfr = Bry_bfr_.New();
    +		Gen_call(Bool_.Y, bfr, Xowe_wiki.Invk_stats);
    +		Gen_call(Bool_.N, bfr, Xowd_site_stats_mgr.Invk_number_of_articles_, count_main);
    +		Gen_call(Bool_.N, bfr, Xowd_site_stats_mgr.Invk_number_of_files_, count_file);
    +		Gen_call(Bool_.N, bfr, Xowd_site_stats_mgr.Invk_number_of_pages_, total);
    +		for (int i = 0; i < ns_len; i++) {
    +			Xow_ns ns = wiki.Ns_mgr().Ords_ary()[i];
    +			if (ns.Id() < 0) continue;
    +			bfr.Add_byte_nl();
    +			Gen_call(Bool_.N, bfr, Xowd_site_stats_mgr.Invk_number_of_articles_in_ns_, ns.Num_str(), Int_.To_str_pad_bgn_zero(ns.Count(), 10));
    +		}
    +		bfr.Add_byte_nl().Add_byte(Byte_ascii.Semic).Add_byte_nl();
    +		Io_url wiki_gfs = Wiki_gfs_url(wiki);
    +		Io_mgr.Instance.SaveFilBfr(wiki_gfs, bfr);
    +	}
    +	private void Gen_call(boolean first, Bry_bfr bfr, String key, Object... vals) {
    +		if (!first) bfr.Add_byte(Byte_ascii.Dot);
    +		bfr.Add_str_u8(key);
    +		int len = vals.length;
    +		if (len > 0) {
    +			bfr.Add_byte(Byte_ascii.Paren_bgn);
    +			for (int i = 0; i < len; i++) {
    +				if (i != 0) bfr.Add_byte(Byte_ascii.Comma).Add_byte(Byte_ascii.Space);
    +				Object val = vals[i];
    +				bfr.Add_str_u8(Object_.Xto_str_strict_or_null_mark(val));
    +			}
    +			bfr.Add_byte(Byte_ascii.Paren_end);
    +		}
    +	}
    +	int Calc_counts(Xow_ns ns) {
    +		Io_url reg_url = wiki.Tdb_fsys_mgr().Url_ns_reg(ns.Num_str(), Xotdb_dir_info_.Tid_ttl);
    +		Xowd_regy_mgr reg_mgr = new Xowd_regy_mgr(reg_url);
    +		int files_ary_len = reg_mgr.Files_ary().length;
    +		int count = 0;
    +		for (int i = 0; i < files_ary_len; i++) {
    +			count += reg_mgr.Files_ary()[i].Count();
    +		}
    +		return count;
    +	}
    +	int Calc_count_articles(Xow_ns ns) {
    +		Io_url hive_dir = wiki.Fsys_mgr().Root_dir().GenSubDir_nest(Xotdb_dir_info_.Name_ns, ns.Num_str(), Xotdb_dir_info_.Name_title);
    +		return Calc_count_articles_dir(ns, hive_dir);
    +	}
    +	int Calc_count_articles_dir(Xow_ns ns, Io_url dir) {
    +		Io_url[] subs = Io_mgr.Instance.QueryDir_args(dir).DirInclude_().ExecAsUrlAry();
    +		int count = 0;
    +		int subs_len = subs.length;
    +		bldr.Usr_dlg().Prog_one(GRP_KEY, "count", "calculating: ~{0}", dir.Raw());
    +		for (int i = 0; i < subs_len; i++) {
    +			Io_url sub = subs[i];
    +			if (sub.Type_dir())
    +				count += Calc_count_articles_dir(ns, sub);
    +			else
    +				count += Calc_count_articles_fil(ns, sub);
    +		}
    +		return count;
    +	}
    +	int Calc_count_articles_fil(Xow_ns ns, Io_url fil) {
    +		if (String_.Eq(fil.NameAndExt(), Xotdb_dir_info_.Name_reg_fil)) return 0;
    +		int rv = 0;
    +		byte[] bry = Io_mgr.Instance.LoadFilBry(fil);
    +		Xob_xdat_file xdat_file = new Xob_xdat_file().Parse(bry, bry.length, fil);
    +		Xowd_page_itm page = Xowd_page_itm.new_tmp();
    +		int count = xdat_file.Count();
    +		for (int i = 0; i < count; i++) {
    +			byte[] ttl_bry = xdat_file.Get_bry(i);
    +			Xotdb_page_itm_.Txt_ttl_load(page, ttl_bry);
    +			rv += page.Redirected() ? 0 : 1;
    +		}
    +		return rv;
    +	}
    +	static final String GRP_KEY = "xowa.bldr.calc_stats";
    +	public static Io_url Wiki_gfs_url(Xowe_wiki wiki) {return wiki.Fsys_mgr().Root_dir().GenSubFil_nest("cfg", "wiki_stats.gfs");}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_init_base_tst.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_init_base_tst.java
    index a27517de8..68ce799b4 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_init_base_tst.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_init_base_tst.java
    @@ -13,3 +13,29 @@ 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.bldrs.cmds.texts.tdbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.*;
    +import org.junit.*; import gplx.xowa.htmls.portal.*; import gplx.xowa.wikis.xwikis.*;
    +public class Xob_init_base_tst {
    +	@Before public void init() {fxt.Clear();} private Xob_init_base_fxt fxt = new Xob_init_base_fxt();
    +	@Test  public void Dirty_wiki_itms() {
    +		Xoae_app app = fxt.App(); Xowe_wiki wiki = fxt.Wiki();
    +		Xoa_available_wikis_mgr wikis_list = fxt.App().Gui_mgr().Html_mgr().Portal_mgr().Wikis();
    +		Tfds.Eq("", wikis_list.Itms_as_html());			// assert
    +		Xow_xwiki_itm xwiki_itm = app.Usere().Wiki().Xwiki_mgr().Add_by_atrs("en.wikipedia.org", "en.wikipedia.org");
    +		xwiki_itm.Offline_(Bool_.Y);	// simulate add via Available_from_fsys; DATE:2014-09-21
    +		Tfds.Eq("", wikis_list.Itms_as_html());			// still empty
    +		new Xob_init_tdb(app.Bldr(), wiki).Cmd_end();	// mock "init" task
    +		Tfds.Eq("\n        
  • en.wikipedia.org
  • ", wikis_list.Itms_as_html()); // no longer empty + } +} +class Xob_init_base_fxt { + public void Clear() { + if (app == null) { + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + } + Io_mgr.Instance.InitEngine_mem(); + } + public Xoae_app App() {return app;} private Xoae_app app; + public Xowe_wiki Wiki() {return wiki;} private Xowe_wiki wiki; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_init_tdb.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_init_tdb.java index a27517de8..2d529de69 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_init_tdb.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_init_tdb.java @@ -13,3 +13,14 @@ 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.bldrs.cmds.texts.tdbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.*; +import gplx.xowa.xtns.wbases.imports.*; +public class Xob_init_tdb extends Xob_init_base { + public Xob_init_tdb(Xob_bldr bldr, Xowe_wiki wiki) {this.Ctor(bldr, wiki);} + @Override public String Cmd_key() {return Xob_cmd_keys.Key_tdb_text_init;} + @Override public void Cmd_ini_wdata(Xob_bldr bldr, Xowe_wiki wiki) { + bldr.Cmd_mgr().Add_cmd(wiki, Xob_cmd_keys.Key_tdb_text_wdata_qid); + bldr.Cmd_mgr().Add_cmd(wiki, Xob_cmd_keys.Key_tdb_text_wdata_pid); + } + @Override public void Cmd_run_end(Xowe_wiki wiki) {} +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_make_cmd_site.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_make_cmd_site.java index a27517de8..15f010863 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_make_cmd_site.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_make_cmd_site.java @@ -13,3 +13,124 @@ 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.bldrs.cmds.texts.tdbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; +import gplx.xowa.wikis.tdbs.*; import gplx.xowa.wikis.tdbs.xdats.*; +public class Xob_make_cmd_site implements Io_make_cmd { + Xob_xdat_file_wtr fil_wtr; Bry_bfr cur_bfr = Bry_bfr_.New(), reg_bfr = Bry_bfr_.New(), reg_key_0 = Bry_bfr_.New_w_size(512), reg_key_n = Bry_bfr_.New_w_size(512); + int make_fil_max = 65 * Io_mgr.Len_kb, fil_count = 0, itm_count = 0, itm_key_end = 0; Io_url reg_url; + public Xob_make_cmd_site(Gfo_usr_dlg usr_dlg, Io_url make_dir, int make_fil_max) {this.usr_dlg = usr_dlg; this.make_dir = make_dir; this.make_fil_max = make_fil_max;} Gfo_usr_dlg usr_dlg; + public Io_sort_cmd Make_dir_(Io_url v) {make_dir = v; return this;} Io_url make_dir; + public byte Line_dlm() {return line_dlm;} public Xob_make_cmd_site Line_dlm_(byte v) {line_dlm = v; return this;} private byte line_dlm = Byte_ascii.Null; + public void Sort_bgn() { + fil_count = itm_count = itm_key_end = 0; + reg_url = make_dir.GenSubFil(Xotdb_dir_info_.Name_reg_fil); + fil_wtr = Xob_xdat_file_wtr.new_file_(make_fil_max, make_dir); + } + public void Sort_do(Io_line_rdr rdr) { + if (line_dlm == Byte_ascii.Null) line_dlm = rdr.Line_dlm(); + int rdr_key_bgn = rdr.Key_pos_bgn(), rdr_key_end = rdr.Key_pos_end(); + int rdr_key_len = rdr_key_end - rdr_key_bgn; + int rdr_val_bgn = rdr_key_end, /* NOTE: no +1: want to include fld_dlm for below*/ rdr_val_end = rdr.Itm_pos_end() - 1; // -1: ignore rdr_dlm + if (Bry_.Match(cur_bfr.Bfr(), 0, itm_key_end, rdr.Bfr(), rdr_key_bgn, rdr_key_end)) // key is same; add rest of line as val + cur_bfr.Add_mid(rdr.Bfr(), rdr_val_bgn, rdr_val_end); + else { + if (fil_wtr.FlushNeeded(cur_bfr.Len() + rdr_key_len)) Flush(); + byte[] bfr = rdr.Bfr(); + if (reg_key_0.Len() == 0) { + if (cur_bfr.Len() == 0) + reg_key_0.Add_mid(bfr, rdr_key_bgn, rdr_key_end); + else + reg_key_0.Add_mid(cur_bfr.Bfr(), 0, itm_key_end); + } + if (cur_bfr.Len() > 0) { + reg_key_n.Clear().Add_mid(cur_bfr.Bfr(), 0, itm_key_end); + fil_wtr.Bfr().Add_bfr_and_clear(cur_bfr); + fil_wtr.Add_idx(line_dlm); + } + cur_bfr.Add_mid(rdr.Bfr(), rdr.Itm_pos_bgn(), rdr.Itm_pos_end() - 1); // -1 to ignore closing newline + itm_key_end = rdr_key_len; // NOTE: must be set last + ++itm_count; + } + } + public void Do_bry(byte[] bry, int key_bgn, int key_end, int itm_bgn, int itm_end) { + int val_bgn = key_end, /* NOTE: no +1: want to include fld_dlm for below*/ val_end = itm_end - 1; // -1: ignore rdr_dlm + if (Bry_.Match(cur_bfr.Bfr(), 0, itm_key_end, bry, key_bgn, key_end)) // key is same; add rest of line as val + cur_bfr.Add_mid(bry, val_bgn, val_end); + else { // key changed; + int itm_len = itm_end - itm_bgn; + if (cur_bfr.Len() > 0) { // pending itm + fil_wtr.Bfr().Add_bfr_and_clear(cur_bfr); // add cur_bfr to fil_bfr + fil_wtr.Add_idx(line_dlm); // add cur_itm to hdr + if (fil_wtr.FlushNeeded(cur_bfr.Len() + itm_len)) + Flush(); + } + if (reg_key_0.Len() == 0) // regy.key_0 bfr is empty + reg_key_0.Add_mid(bry, key_bgn, key_end); // update reg_0key_0 + reg_key_n.Clear().Add_mid(bry, key_bgn, key_end); // always update reg_key_n + if (itm_len > 100 * Io_mgr.Len_mb) + Flush_large(bry, itm_bgn, itm_end, itm_len); + else { + cur_bfr.Add_mid(bry, itm_bgn, itm_end - 1); // add incoming itm; -1 to ignore closing newline + itm_key_end = key_end; // NOTE: must be set last + ++itm_count; + } + } + } + public void Sort_end() { + reg_key_n.Clear().Add_mid(cur_bfr.Bfr(), 0, itm_key_end); + fil_wtr.Bfr().Add_bfr_and_clear(cur_bfr); + fil_wtr.Add_idx(line_dlm); + Flush(); + Io_mgr.Instance.AppendFilBfr(reg_url, reg_bfr); + //fil_wtr.Rls(); cur_bfr.Rls(); fil_wtr.Rls(); reg_bfr.Rls(); reg_key_0.Rls(); reg_key_n.Rls(); + } +// private void Flush_large(byte[] bry, int itm_bgn, int itm_end, int itm_len) { +// ++itm_count; +// this.Flush_reg(); +// fil_wtr.Add_idx_direct(itm_len, Byte_.Zero); +// IoStream stream = IoStream_.Null; +// try { +// stream = Io_mgr.Instance.OpenStreamWrite(fil_wtr.Fil_url()); +// fil_wtr.FlushIdx(stream); +// stream.Write_and_flush(bry, itm_bgn, itm_end); +// fil_wtr.Clear(); +// fil_wtr.Url_gen_add(); +// } +// finally {stream.Rls();} +// } + private void Flush_large(byte[] bry, int itm_bgn, int itm_end, int itm_len) { + ++itm_count; + this.Flush_reg(); + fil_wtr.Add_idx_direct(itm_len, Byte_.Zero); + Io_stream_wtr wtr = null; + try { + wtr = Io_stream_wtr_.New__raw(fil_wtr.Fil_url()); + wtr.Open(); + fil_wtr.FlushIdx(wtr); + wtr.Write(bry, itm_bgn, itm_end); + wtr.Flush(); + fil_wtr.Clear(); + fil_wtr.Url_gen_add(); + } + finally {if (wtr != null) wtr.Rls();} + } + private void Flush() { + Flush_reg(); + Flush_fil(); + } + private void Flush_reg() { + reg_bfr + .Add_int_variable(fil_count++).Add_byte(Byte_ascii.Pipe) + .Add_bfr_and_preserve(reg_key_0).Add_byte(Byte_ascii.Pipe) + .Add_bfr_and_preserve(reg_key_n).Add_byte(Byte_ascii.Pipe) + .Add_int_variable(itm_count).Add_byte(Byte_ascii.Nl); + itm_count = 0; + reg_key_0.Clear(); + } + private void Flush_fil() { + if (fil_wtr.Fil_idx() % 10 == 0) + usr_dlg.Prog_many("cmd_site", "prog", "saving: ~{0} ~{1}", reg_url.OwnerDir().NameOnly(), fil_wtr.Fil_url().NameOnly()); + fil_wtr.Flush(usr_dlg); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_make_id_wkr.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_make_id_wkr.java index a27517de8..92fd0bfb2 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_make_id_wkr.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_make_id_wkr.java @@ -13,3 +13,24 @@ 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.bldrs.cmds.texts.tdbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.*; +import gplx.core.ios.*; import gplx.xowa.wikis.tdbs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.bldrs.wkrs.*; +public class Xob_make_id_wkr extends Xob_itm_dump_base implements Xob_page_wkr, Gfo_invk { + public Xob_make_id_wkr(Xob_bldr bldr, Xowe_wiki wiki) {this.Cmd_ctor(bldr, wiki);} + public String Page_wkr__key() {return KEY;} public static final String KEY = "core.make_id"; + public void Page_wkr__bgn() { + this.Init_dump(KEY, wiki.Tdb_fsys_mgr().Site_dir().GenSubDir(Xotdb_dir_info_.Name_id)); + } + public void Page_wkr__run(Xowd_page_itm page) { + byte[] ttl = page.Ttl_page_db(); + if (dump_bfr.Len() + row_fixed_len + ttl.length > dump_fil_len) Io_mgr.Instance.AppendFilBfr(dump_url_gen.Nxt_url(), dump_bfr); + Xotdb_page_itm_.Txt_id_save(dump_bfr, page); + } + public void Page_wkr__run_cleanup() {} + public void Page_wkr__end() { + this.Term_dump(new Xob_make_cmd_site(bldr.Usr_dlg(), make_dir, make_fil_len)); + if (delete_temp) Io_mgr.Instance.DeleteDirDeep(temp_dir); + } + static final int row_fixed_len = 25 + 1 + 7; // 25=5 base_85 flds; 1=Redirect; 7=dlm +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_parse_dump_templates_cmd.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_parse_dump_templates_cmd.java index a27517de8..ccc5e8325 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_parse_dump_templates_cmd.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/tdbs/Xob_parse_dump_templates_cmd.java @@ -13,3 +13,22 @@ 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.bldrs.cmds.texts.tdbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.*; +import gplx.core.ios.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.wikis.tdbs.*; import gplx.xowa.wikis.nss.*; +import gplx.xowa.bldrs.wkrs.*; +public class Xob_parse_dump_templates_cmd extends Xob_itm_dump_base implements Xob_page_wkr, Gfo_invk { + public Xob_parse_dump_templates_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.Cmd_ctor(bldr, wiki);} + public String Page_wkr__key() {return KEY;} public static final String KEY = "parse.dump_templates"; + public static final int FixedLen_page = 1 + 5 + 1 + 5 + 1 + 1 + 1; // \tid|date|title|text\n + public void Page_wkr__bgn() { + Init_dump(KEY); + } + public void Page_wkr__run(Xowd_page_itm page) { + if (page.Ns_id() != Xow_ns_.Tid__template) return; + int id = page.Id(); byte[] title = page.Ttl_page_db(), text = page.Text(); int title_len = title.length, text_len = text.length; + if (FixedLen_page + title_len + text_len + dump_bfr.Len() > dump_fil_len) super.Flush_dump(); + Xotdb_page_itm_.Txt_page_save(dump_bfr, id, page.Modified_on(), title, text, true); + } + public void Page_wkr__run_cleanup() {} + public void Page_wkr__end() {super.Flush_dump();} +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/xmls/Xob_siteinfo_nde.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/xmls/Xob_siteinfo_nde.java index a27517de8..5c5675b71 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/xmls/Xob_siteinfo_nde.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/xmls/Xob_siteinfo_nde.java @@ -13,3 +13,35 @@ 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.bldrs.cmds.texts.xmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.*; +import gplx.xowa.wikis.nss.*; +public class Xob_siteinfo_nde { + public Xob_siteinfo_nde(String site_name, String db_name, byte[] main_page, String generator, String case_dflt, Xow_ns_mgr ns_mgr) { + this.site_name = site_name; + this.db_name = db_name; + this.main_page = main_page; + this.generator = generator; + this.case_dflt = case_dflt; + this.ns_mgr = ns_mgr; + } + public String Site_name() {return site_name;} private final String site_name; + public String Db_name() {return db_name;} private final String db_name; + public byte[] Main_page() {return main_page;} private final byte[] main_page; + public String Generator() {return generator;} private final String generator; + public String Case_dflt() {return case_dflt;} private final String case_dflt; + public Xow_ns_mgr Ns_mgr() {return ns_mgr;} private final Xow_ns_mgr ns_mgr; + public void To_bfr(Bry_bfr bfr) { + bfr.Add (main_page).Add_byte_pipe(); + bfr.Add_str_u8(case_dflt).Add_byte_pipe(); + bfr.Add_str_u8(site_name).Add_byte_pipe(); + bfr.Add_str_u8(db_name).Add_byte_pipe(); + bfr.Add_str_u8(generator).Add_byte_nl(); + int len = ns_mgr.Count(); + for (int i = 0; i < len; ++i) { + Xow_ns ns = ns_mgr.Ords_get_at(i); + bfr.Add_int_variable(ns.Id()).Add_byte_pipe(); + bfr.Add_str_u8(Xow_ns_case_.To_str(ns.Case_match())).Add_byte_pipe(); + bfr.Add(ns.Name_ui()).Add_byte_nl(); + } + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/xmls/Xob_siteinfo_parser_.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/xmls/Xob_siteinfo_parser_.java index a27517de8..eb3e64cf3 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/xmls/Xob_siteinfo_parser_.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/xmls/Xob_siteinfo_parser_.java @@ -13,3 +13,67 @@ 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.bldrs.cmds.texts.xmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; import gplx.langs.xmls.*; // NOTE: gplx.langs.xmls does not support Android; DATE:2013-01-17 +import gplx.xowa.wikis.nss.*; +public class Xob_siteinfo_parser_ { + public static byte[] Extract(Io_stream_rdr src_rdr) { + Io_buffer_rdr rdr = Io_buffer_rdr.Null; + try { + rdr = Io_buffer_rdr.new_(src_rdr, Io_mgr.Len_mb); // ASSUME: siteInfo is fully contained in the 1st MB of the src_xml + byte[] src = rdr.Bfr(); + int bgn = Bry_find_.Find_fwd(src, Bry_siteinfo_bgn, 0) ; if (bgn == Bry_find_.Not_found) throw Err_.new_("Xob_siteinfo_parser_", "could not find ", "src", src); + int end = Bry_find_.Move_fwd(src, Bry_siteinfo_end, bgn); if (end == Bry_find_.Not_found) throw Err_.new_("Xob_siteinfo_parser_", "could not find ", "src", src); + return Bry_.Mid(src, bgn, end); + } + finally {rdr.Rls();} + } + public static void Parse(byte[] siteinfo_bry, Xowe_wiki wiki) { + Xob_siteinfo_nde nde = Parse(String_.new_u8(siteinfo_bry), wiki.Ns_mgr()); + wiki.Props().Bldr_version_(Bry_.new_a7(Xoa_app_.Version)); + wiki.Props().Main_page_(nde.Main_page()); + wiki.Props().Siteinfo_mainpage_(nde.Main_page()); + Bry_bfr bfr = Bry_bfr_.New().Add_str_u8(nde.Site_name()).Add_byte_pipe().Add_str_u8(nde.Generator()).Add_byte_pipe().Add_str_u8(nde.Case_dflt()).Add_byte_pipe(); + wiki.Props().Siteinfo_misc_(bfr.To_bry_and_clear()); + } + public static Xob_siteinfo_nde Parse(String xdoc_src, Xow_ns_mgr ns_mgr) { + XmlDoc xdoc = XmlDoc_.parse(xdoc_src); XmlNde root = xdoc.Root(); + String site_name = "", db_name = "", generator = "", case_dflt = Xow_ns_case_.Key__1st; byte[] main_page = Xoa_page_.Main_page_bry; + int root_len = root.SubNdes().Count(); + for (int i = 0; i < root_len; ++i) { + XmlNde sub_nde = root.SubNdes().Get_at(i); String sub_name = sub_nde.Name(); + if (String_.Eq(sub_name, "sitename")) site_name = sub_nde.Text_inner(); + else if (String_.Eq(sub_name, "generator")) generator = sub_nde.Text_inner(); + else if (String_.Eq(sub_name, "case")) case_dflt = sub_nde.Text_inner(); + else if (String_.Eq(sub_name, "dbname")) db_name = sub_nde.Text_inner(); + else if (String_.Eq(sub_name, "base")) main_page = Parse_base(Bry_.new_u8(sub_nde.Text_inner())); + else if (String_.Eq(sub_name, "namespaces")) Parse_namespaces(sub_nde, ns_mgr, case_dflt); + else if (String_.Eq(sub_name, "#text")) {} // JAVA.XML.#text: ignore unexpected #text nodes + } + return new Xob_siteinfo_nde(site_name, db_name, main_page, generator, case_dflt, ns_mgr); + } + private static byte[] Parse_base(byte[] url) { + int page_bgn = Bry_find_.Find_fwd(url, gplx.xowa.htmls.hrefs.Xoh_href_.Bry__wiki, 0); + if (page_bgn == Bry_find_.Not_found) { // "/wiki/" not found; EX: "http://mywiki/My_main_page" + page_bgn = Bry_find_.Find_bwd(url, Byte_ascii.Slash); // ASSUME last segment is page + if (page_bgn == Bry_find_.Not_found) throw Err_.new_("Xob_siteinfo_parser_", "could not parse main page url", "url", url); + ++page_bgn; // add 1 to position after slash + } + else // "/wiki/" found + page_bgn += gplx.xowa.htmls.hrefs.Xoh_href_.Len__wiki; // position bgn after "/wiki/" + return Bry_.Mid(url, page_bgn, url.length); // extract everything after "page_bgn"; EX: "http://en.wikipedia.org/wiki/Main_Page" -> "Main_Page" + } + private static void Parse_namespaces(XmlNde grp_nde, Xow_ns_mgr ns_mgr, String case_dflt) { + ns_mgr.Clear(); // NOTE: wipe out any preexisting ns; use siteinfo.xml as definitive list + int grp_len = grp_nde.SubNdes().Count(); + for (int i = 0; i < grp_len; ++i) { + XmlNde itm_nde = grp_nde.SubNdes().Get_at(i); if (itm_nde.Atrs().Count() == 0) continue; // JAVA.XML.#text: ignore unexpected #text nodes + String ns_id = itm_nde.Atrs().FetchValOr("key", null); if (ns_id == null) throw Err_.new_("Xob_siteinfo_parser_", "missing key for ns", "ns_xml", itm_nde.Text_inner()); + String case_match = itm_nde.Atrs().FetchValOr("case", case_dflt); // NOTE: some dumps can omit "case"; EX: https://dumps.wikimedia.org/sep11wiki; DATE:2015-11-01 + String name = itm_nde.Text_inner(); + ns_mgr.Add_new(Int_.Parse(ns_id), Bry_.new_u8(name), Xow_ns_case_.To_tid(case_match), false); + } + ns_mgr.Init_w_defaults(); + } + private static final byte[] Bry_siteinfo_bgn = Bry_.new_a7(""), Bry_siteinfo_end = Bry_.new_a7(""); +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/xmls/Xob_siteinfo_parser__tst.java b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/xmls/Xob_siteinfo_parser__tst.java index a27517de8..182407d7b 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/texts/xmls/Xob_siteinfo_parser__tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/texts/xmls/Xob_siteinfo_parser__tst.java @@ -13,3 +13,96 @@ 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.bldrs.cmds.texts.xmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.*; +import org.junit.*; import gplx.xowa.wikis.nss.*; +public class Xob_siteinfo_parser__tst { + private final Xob_siteinfo_parser__fxt fxt = new Xob_siteinfo_parser__fxt(); + @Test public void Basic__simplewikt() { // PURPOSE: basic test of siteinfo parse; DATE:2015-11-01 + fxt.Test__parse(String_.Concat_lines_nl_skip_last + ( " " + , " Wiktionary" + , " simplewiktionary" + , " https://simple.wiktionary.org/wiki/Main_Page" + , " MediaWiki 1.27.0-wmf.3" + , " case-sensitive" + , " " + , " Media" + , " Special" + , " " + , " Talk" + , " User" + , " User talk" + , " Wiktionary" + , " Wiktionary talk" + , " File" + , " File talk" + , " MediaWiki" + , " MediaWiki talk" + , " Template" + , " Template talk" + , " Help" + , " Help talk" + , " Category" + , " Category talk" + , " Module" + , " Module talk" + , " Gadget" + , " Gadget talk" + , " Gadget definition" + , " Gadget definition talk" + , " Topic" + , " " + , " " + ), String_.Concat_lines_nl + ( "Main_Page|case-sensitive|Wiktionary|simplewiktionary|MediaWiki 1.27.0-wmf.3" + , "-2|case-sensitive|Media" + , "-1|first-letter|Special" + , "0|case-sensitive|" + , "1|case-sensitive|Talk" + , "2|first-letter|User" + , "3|first-letter|User talk" + , "4|case-sensitive|Wiktionary" + , "5|case-sensitive|Wiktionary talk" + , "6|case-sensitive|File" + , "7|case-sensitive|File talk" + , "8|first-letter|MediaWiki" + , "9|first-letter|MediaWiki talk" + , "10|case-sensitive|Template" + , "11|case-sensitive|Template talk" + , "12|case-sensitive|Help" + , "13|case-sensitive|Help talk" + , "14|case-sensitive|Category" + , "15|case-sensitive|Category talk" + , "828|case-sensitive|Module" + , "829|case-sensitive|Module talk" + , "2300|case-sensitive|Gadget" + , "2301|case-sensitive|Gadget talk" + , "2302|case-sensitive|Gadget definition" + , "2303|case-sensitive|Gadget definition talk" + , "2600|first-letter|Topic" + , "2601|first-letter|2601" // NOTE: Topic_talk doesn't exist in , but added by XOWA b/c every subj ns must have a talk ns + )); + } + @Test public void Case_dflt() { // PURPOSE: missing case should use dflt DATE:2015-11-01 + fxt.Test__parse(String_.Concat_lines_nl_skip_last + ( " " + , " case-sensitive" + , " " + , " Media" + , " " + , " " + ), String_.Concat_lines_nl + ( "Main_Page|case-sensitive|||" + , "-2|case-sensitive|Media" + )); + } +} +class Xob_siteinfo_parser__fxt { + private final Xow_ns_mgr ns_mgr = new Xow_ns_mgr(gplx.xowa.langs.cases.Xol_case_mgr_.U8()); + private final Bry_bfr bfr = Bry_bfr_.New(); + public void Test__parse(String src_str, String expd) { + Xob_siteinfo_nde nde = Xob_siteinfo_parser_.Parse(src_str, ns_mgr); + nde.To_bfr(bfr); + Tfds.Eq_str_lines(expd, bfr.To_str_and_clear()); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_alert_cmd.java b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_alert_cmd.java index a27517de8..27eb5cd2e 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_alert_cmd.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_alert_cmd.java @@ -13,3 +13,26 @@ 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.bldrs.cmds.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; +import gplx.xowa.bldrs.wkrs.*; +import gplx.gfui.*; import gplx.gfui.kits.core.*; +public class Xob_alert_cmd extends Xob_cmd__base implements Xob_cmd { + public Xob_alert_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + public Xob_alert_cmd Msg_(String v) {this.msg = v; return this;} private String msg = "no message specified"; + @Override public void Cmd_run() { + Gfui_kit kit = app.Gui_mgr().Kit(); + if (kit.Tid() != Gfui_kit_.Swt_tid) return; + kit.Ask_ok("", "", msg); + Xoa_app_.Usr_dlg().Prog_many("", "", msg); + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk__text_)) this.msg = m.ReadStr("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk__text_ = "text_"; + + public static final String BLDR_CMD_KEY = "ui.alert"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xob_alert_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xob_alert_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_cleanup_cmd.java b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_cleanup_cmd.java index a27517de8..cbab69ae9 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_cleanup_cmd.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_cleanup_cmd.java @@ -13,3 +13,119 @@ 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.bldrs.cmds.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; +import gplx.core.criterias.*; +import gplx.xowa.bldrs.wkrs.*; +public class Xob_cleanup_cmd extends Xob_itm_basic_base implements Xob_cmd { + private String bz2_cmd; + private boolean delete_all, delete_tmp; + private Criteria_ioMatch[] delete_by_match_ary; + public Xob_cleanup_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.Cmd_ctor(bldr, wiki);} + public String Cmd_key() {return Xob_cmd_keys.Key_util_cleanup;} + public Xob_cleanup_cmd Delete_sqlite3_(boolean v){delete_sqlite3 = v; return this;} private boolean delete_sqlite3; + public Xob_cleanup_cmd Delete_xml_(boolean v) {delete_xml = v; return this;} private boolean delete_xml; + public Xob_cleanup_cmd Delete_tdb_(boolean v) {delete_tdb = v; return this;} private boolean delete_tdb; + public void Bz2_fil_(Io_url v) {bz2_fil = v;} private Io_url bz2_fil; + public void Cmd_run() { + Io_url wiki_root_dir = wiki.Fsys_mgr().Root_dir(); + if (bz2_fil != null) { + if (String_.Eq(bz2_cmd, "delete")) + Io_mgr.Instance.DeleteFil(bz2_fil); + else if (String_.Eq(bz2_cmd, "move")) + Io_mgr.Instance.MoveFil(bz2_fil, bz2_fil.OwnerDir().OwnerDir().GenSubFil_nest("done", bz2_fil.NameAndExt())); + } + if (delete_xml) Io_mgr.Instance.DeleteFil(Xob_page_wkr_cmd.Find_fil_by(wiki_root_dir, "*.xml")); + if (delete_tdb) { + usr_dlg.Note_many("", "", "bldr.wiki:deleting tdb wiki"); + Delete_tdb(wiki_root_dir); + } + if (delete_sqlite3) + Delete_wiki_sql(wiki); + if (delete_all) { + Io_mgr.Instance.DeleteDir_cmd(wiki_root_dir).Exec(); // do not delete subdirs; needed to support "/prv" for fsdb; DATE:2015-04-01 + Io_mgr.Instance.DeleteDirDeep(app.Usere().Fsys_mgr().Wiki_root_dir().GenSubDir(wiki.Domain_str())); // delete css dir; DATE:2015-07-06 + } + if (delete_by_match_ary != null) + Delete_by_match(wiki_root_dir, delete_by_match_ary); + if (delete_tmp) + Io_mgr.Instance.DeleteDirDeep(wiki_root_dir.GenSubDir("tmp")); + } + public void Cmd_init(Xob_bldr bldr) {} + public void Cmd_bgn(Xob_bldr bldr) {} + public void Cmd_end() {} + public void Cmd_term() {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_bz2_cmd_)) bz2_cmd = m.ReadStr("v"); + else if (ctx.Match(k, Invk_delete_xml_)) delete_xml = m.ReadYn("v"); + else if (ctx.Match(k, Invk_delete_wiki_)) delete_tdb = m.ReadYn("v"); + else if (ctx.Match(k, Invk_delete_sqlite3_)) delete_sqlite3 = m.ReadYn("v"); + else if (ctx.Match(k, Invk_delete_all_)) delete_all = m.ReadYn("v"); + else if (ctx.Match(k, Invk_bz2_fil_)) bz2_fil = m.ReadIoUrl("v"); + else if (ctx.Match(k, Invk_delete_by_match_)) delete_by_match_ary = Delete_by_match_parse(m.ReadStr("v")); + else if (ctx.Match(k, Invk_delete_tmp_)) delete_tmp = m.ReadYn("v"); + else return super.Invk(ctx, ikey, k, m); + return this; + } + private static final String Invk_bz2_cmd_ = "bz2_cmd_", Invk_bz2_fil_ = "bz2_fil_" + , Invk_delete_xml_ = "delete_xml_", Invk_delete_wiki_ = "delete_wiki_", Invk_delete_sqlite3_ = "delete_sqlite3_" + , Invk_delete_all_ = "delete_all_" + , Invk_delete_tmp_ = "delete_tmp_" + , Invk_delete_by_match_ = "delete_by_match" + ; + private static Criteria_ioMatch[] Delete_by_match_parse(String raw) { + String[] match_ary = String_.Split(raw, '|'); + int match_ary_len = match_ary.length; + Criteria_ioMatch[] rv = new Criteria_ioMatch[match_ary_len]; + for (int i = 0; i < rv.length; i++) { + String match = match_ary[i]; + rv[i] = Criteria_ioMatch.parse(true, match, false); + } + return rv; + } + private static void Delete_by_match(Io_url dir, Criteria_ioMatch[] match_ary) { + int match_len = match_ary.length; + Io_url[] subs = Io_mgr.Instance.QueryDir_fils(dir); + int subs_len = subs.length; + for (int i = 0; i < subs_len; i++) { + Io_url sub = subs[i]; + for (int j = 0; j < match_len; j++) { + Criteria_ioMatch match = match_ary[j]; + if (match.Matches(sub)) { + if (sub.Type_fil()) + Io_mgr.Instance.DeleteFil(sub); + } + } + } + } + private static void Delete_tdb(Io_url wiki_root_dir) { + Io_url[] dirs = Io_mgr.Instance.QueryDir_args(wiki_root_dir).DirOnly_().DirInclude_().ExecAsUrlAry(); + int dirs_len = dirs.length; + for (int i = 0; i < dirs_len; i++) { + Io_url dir = dirs[i]; + if (gplx.xowa.wikis.tdbs.Xotdb_dir_info_.Dir_name_is_tdb(dir.NameOnly())) + Io_mgr.Instance.DeleteDirDeep(dir); + } + } + public static void Delete_wiki_sql(Xowe_wiki wiki) { + Gfo_usr_dlg usr_dlg = wiki.Appe().Usr_dlg(); Io_url wiki_root_dir = wiki.Fsys_mgr().Root_dir(); + if (wiki.Db_mgr().Tid() == gplx.xowa.wikis.dbs.Xodb_mgr_sql.Tid_sql) // NOTE: must check; if empty dir (or text db) than db_mgr will be txt + wiki.Db_mgr_as_sql().Core_data_mgr().Rls(); // NOTE: if sqlite files, must rls; + Io_url[] files = Io_mgr.Instance.QueryDir_fils(wiki_root_dir); + int files_len = files.length; + int deleted = 0; + String file_prefix = wiki.Domain_str() + "-file"; // NOTE: skip anything with "-file"; EX: "en.wikipedia.org-file.xowa" + String html_prefix = wiki.Domain_str() + "-html"; // NOTE: skip anything with "-html"; EX: "en.wikipedia.org-html-ns.000-db.002.xowa" + for (int i = 0; i < files_len; i++) { + Io_url url = files[i]; + if ( !String_.Eq(url.Ext(), ".xowa") + && !String_.Eq(url.Ext(), ".sqlite3")) + continue; + if ( String_.Has_at_bgn(url.NameAndExt(), file_prefix) + || String_.Has_at_bgn(url.NameAndExt(), html_prefix) + ) continue; // skip + Io_mgr.Instance.DeleteFil(url); + deleted++; + } + usr_dlg.Note_many("", "delete_wiki", "deleting sqlite3 files: ~{0} ~{1}", deleted, wiki_root_dir.Raw()); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_core_batch_utl.java b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_core_batch_utl.java index a27517de8..aab7c2e56 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_core_batch_utl.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_core_batch_utl.java @@ -13,3 +13,31 @@ 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.bldrs.cmds.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.wikis.domains.*; +import gplx.xowa.bldrs.wms.dumps.*; +public class Xob_core_batch_utl implements Gfo_invk { + private final Xob_bldr bldr; + private final Bry_fmtr fmtr = Bry_fmtr.keys_("bz2_fil", "wiki_key"); + public Xob_core_batch_utl(Xob_bldr bldr, byte[] raw) {this.bldr = bldr; fmtr.Fmt_(raw);} + private void Run() { + Io_url[] bz2_fils = Io_mgr.Instance.QueryDir_fils(bldr.App().Fsys_mgr().Wiki_dir().GenSubDir_nest(Dir_dump, "todo")); + Bry_bfr bfr = Bry_bfr_.Reset(Io_mgr.Len_kb); + int bz2_fils_len = bz2_fils.length; + for (int i = 0; i < bz2_fils_len; i++) { + Io_url bz2_fil_url = bz2_fils[i]; + Xowm_dump_file dump_file = Xowm_dump_file_.parse(Bry_.new_u8(bz2_fil_url.NameOnly())); + String domain_str = dump_file.Domain_itm().Domain_str(); + fmtr.Bld_bfr_many(bfr, bz2_fil_url.Raw(), domain_str); + bldr.Usr_dlg().Note_many("", "", "starting script for ~{0}", domain_str); + bldr.App().Gfs_mgr().Run_str(bfr.To_str_and_clear()); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_owner)) return bldr.Cmd_mgr(); + else if (ctx.Match(k, Invk_run)) Run(); + return this; + } private static final String Invk_owner = "owner", Invk_run = "run"; + public static String Dir_dump = "#dump"; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_decompress_bz2_cmd.java b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_decompress_bz2_cmd.java index a27517de8..ab8d13773 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_decompress_bz2_cmd.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_decompress_bz2_cmd.java @@ -13,3 +13,41 @@ 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.bldrs.cmds.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; +import gplx.core.ios.*; import gplx.core.threads.*; import gplx.core.envs.*; +import gplx.xowa.bldrs.wkrs.*; +public class Xob_decompress_bz2_cmd extends Xob_itm_basic_base implements Xob_cmd { + public Xob_decompress_bz2_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.Cmd_ctor(bldr, wiki);} + public String Cmd_key() {return Xob_cmd_keys.Key_decompress_bz2;} + public void Cmd_init(Xob_bldr bldr) {} + public void Cmd_bgn(Xob_bldr bldr) {} + public void Cmd_run() { + if (Io_mgr.Instance.ExistsFil(trg)) return; // file already exists; don't decompress again + usr_dlg.Note_many(GRP_KEY, "bgn", "decompressing ~{0}", src.Raw(), trg.Raw()); + Decompress(bldr.App(), src.Raw(), trg); + } + public void Cmd_end() {} + public void Cmd_term() {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_src_)) this.Src_(m.ReadIoUrl("v")); + else return super.Invk(ctx, ikey, k, m); + return this; + } private static final String Invk_src_ = "src_"; + private void Src_(Io_url v) { + src = v; + trg = bldr.App().Fsys_mgr().Wiki_dir().GenSubFil_nest(wiki.Domain_str(), v.NameOnly()); // NOTE: NameOnly() will take "enwiki.xml.bz2" and make it "enwiki.xml" + } Io_url src, trg; + static final String GRP_KEY = "xowa.bldr.cmd.decompress_bz2"; + public static boolean Decompress(Xoae_app app, String src_fil, Io_url trg_fil) { + Io_mgr.Instance.CreateDirIfAbsent(trg_fil.OwnerDir()); // 7zip will fail if dir does not exist + Process_adp decompress = app.Prog_mgr().App_decompress_bz2(); + decompress.Prog_dlg_(app.Usr_dlg()).Run_mode_(Process_adp.Run_mode_async); + decompress.Run(src_fil, trg_fil, trg_fil.OwnerDir().Xto_api()); + while (decompress.Exit_code() == Process_adp.Exit_init) { + String size = gplx.core.ios.Io_size_.To_str(Io_mgr.Instance.QueryFil(trg_fil).Size()); + app.Usr_dlg().Prog_many(GRP_KEY, "decompress", "decompressing: ~{0}", size); + Thread_adp_.Sleep(1000); + } + return true; + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_delete_cmd.java b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_delete_cmd.java index a27517de8..975f642ce 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_delete_cmd.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_delete_cmd.java @@ -13,3 +13,35 @@ 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.bldrs.cmds.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; +import gplx.dbs.*; import gplx.core.ios.*; import gplx.core.envs.*; +import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.bldrs.wms.dumps.*; +public class Xob_delete_cmd extends Xob_cmd__base implements Xob_cmd { + private String[] patterns_ary = String_.Ary_empty; + public Xob_delete_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + public Xob_delete_cmd Patterns_ary_(String... v) {this.patterns_ary = v; return this;} + @Override public String Cmd_key() {return Xob_cmd_keys.Key_util_delete;} + @Override public void Cmd_run() { + int len = patterns_ary.length; if (len == 0) return; + + // build filter EX: '*.xml|*.txt' + Bry_bfr bfr = Bry_bfr_.New(); + for (int i = 0; i < len; ++i) { + String pattern = patterns_ary[i]; + if (i != 0) bfr.Add_byte_pipe(); + bfr.Add_str_u8(pattern); + } + + // get files; iterate and delete + String file_pattern = bfr.To_str_and_clear(); + Io_url[] files = Io_mgr.Instance.QueryDir_args(wiki.Fsys_mgr().Root_dir()).Recur_(Bool_.N).FilPath_(file_pattern).ExecAsUrlAry(); + int files_len = files.length; + for (int i = 0; i < files_len; ++i) { + Io_url file = files[i]; + if (file.Ext() == ".sqlite3") + Db_conn_bldr.Instance.Get_or_noop(file).Rls_conn(); + Io_mgr.Instance.DeleteFil(file); + } + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return Gfo_invk_.Noop;} +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_download_cmd.java b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_download_cmd.java index a27517de8..6dee31d4e 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_download_cmd.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_download_cmd.java @@ -13,3 +13,73 @@ 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.bldrs.cmds.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; +import gplx.dbs.*; import gplx.core.ios.*; import gplx.core.envs.*; +import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.bldrs.wms.dumps.*; +public class Xob_download_cmd extends Xob_cmd__base implements Xob_cmd { + private String dump_date = "latest", dump_type = null, dump_src = null; + private Io_url dump_trg_zip = null, dump_trg_bin = null; + private boolean unzip = true; + public Xob_download_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki);} + public Xob_download_cmd Dump_type_(String v) {dump_type = v; return this;} + @Override public String Cmd_key() {return Xob_cmd_keys.Key_util_download;} + @Override public void Cmd_run() { + // init vars; if no explicit values, calc defaults; + if (dump_type == null) throw Err_.new_("bldr", "dump_type must be specified"); + if (!gplx.core.ios.IoEngine_system.Web_access_enabled) return; + Xowm_dump_file dump_file = new Xowm_dump_file(wiki.Domain_str(), dump_date, dump_type); + if (dump_src == null) { + dump_file.Server_url_(gplx.xowa.bldrs.installs.Xoi_dump_mgr.Server_urls(app)[0]); + dump_src = dump_file.File_url(); + } + if (dump_trg_zip == null) + dump_trg_zip = wiki.Fsys_mgr().Root_dir().GenSubFil(dump_file.File_name()); + if (dump_trg_bin == null && unzip) + dump_trg_bin = dump_trg_zip.GenNewNameAndExt(dump_trg_zip.NameOnly()); // convert a.sql.gz -> a.sql + + // download + usr_dlg.Note_many("", "", "downloading file: now=~{0} src=~{1} trg=~{2}", Datetime_now.Get().XtoStr_fmt_yyyyMMdd_HHmmss(), dump_src, dump_trg_zip.OwnerDir()); + IoEngine_xrg_downloadFil download_wkr = app.Wmf_mgr().Download_wkr().Download_xrg(); + download_wkr.Src_last_modified_query_(false).Init(dump_src, dump_trg_zip); + if (!download_wkr.Exec()) + usr_dlg.Warn_many("", "", "download failed: src=~{0} trg=~{1} err=~{2}", dump_src, dump_trg_zip.Raw(), Err_.Message_gplx_full(download_wkr.Rslt_err())); + if (unzip) { // parsing unzipped file is faster, but takes up more storage space + usr_dlg.Note_many("", "", "unzipping file: now=~{0} trg=~{1}", Datetime_now.Get().XtoStr_fmt_yyyyMMdd_HHmmss(), dump_trg_bin.Raw()); + Xob_unzip_wkr unzip_wkr = new Xob_unzip_wkr().Init(app).Process_run_mode_(Process_adp.Run_mode_sync_block); + unzip_wkr.Decompress(dump_trg_zip, dump_trg_bin); + } + } + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (String_.Eq(k, Invk_dump_date_)) dump_date = m.ReadStr("v"); + else if (String_.Eq(k, Invk_dump_type_)) dump_type = m.ReadStr("v"); + else if (String_.Eq(k, Invk_dump_src_)) dump_src = m.ReadStr("v"); + else if (String_.Eq(k, Invk_dump_trg_zip_)) dump_trg_zip = m.ReadIoUrl("v"); + else if (String_.Eq(k, Invk_dump_trg_bin_)) dump_trg_bin = m.ReadIoUrl("v"); + else if (String_.Eq(k, Invk_unzip_)) unzip = m.ReadYn("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk_dump_date_ = "dump_date_", Invk_dump_type_ = "dump_type_", Invk_unzip_ = "unzip_" + , Invk_dump_src_ = "dump_src_", Invk_dump_trg_zip_ = "dump_trg_zip_", Invk_dump_trg_bin_ = "dump_trg_bin_"; + + public static void Add_if_not_found_many(Xob_bldr bldr, Xowe_wiki wiki, String... dump_types) { + IoItmHash itm_hash = Io_mgr.Instance.QueryDir_args(wiki.Fsys_mgr().Root_dir()).ExecAsItmHash(); + for (String dump_type : dump_types) + Add_if_not_found(bldr, wiki, itm_hash, dump_type); + } + private static void Add_if_not_found(Xob_bldr bldr, Xowe_wiki wiki, IoItmHash itm_hash, String dump_type) { + if (!Found(itm_hash, dump_type)) + bldr.Cmd_mgr().Add(new Xob_download_cmd(bldr, wiki).Dump_type_(dump_type)); + } + private static boolean Found(IoItmHash hash, String dump_type) { + String match = String_.Format("{0}.sql", dump_type); // EX: "page_props.sql" + int len = hash.Count(); + for (int i = 0; i < len; i++) { + IoItm_base fil = (IoItm_base)hash.Get_at(i); + if (String_.Has(fil.Url().NameAndExt(), match)) + return true; + } + return false; + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_exec_sql_cmd.java b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_exec_sql_cmd.java index a27517de8..4de94e877 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_exec_sql_cmd.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_exec_sql_cmd.java @@ -13,3 +13,31 @@ 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.bldrs.cmds.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; +import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.dbs.*; +public class Xob_exec_sql_cmd implements Xob_cmd { + private Xowe_wiki wiki; private int file_idx = -1; private String sql; + public Xob_exec_sql_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.wiki = wiki;} + public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return null;} + public String Cmd_key() {return Xob_cmd_keys.Key_exec_sql;} + public void Cmd_run() { + Xoae_app app = wiki.Appe(); + wiki.Init_assert(); // force load; needed to pick up MediaWiki ns for MediaWiki:mainpage + Xodb_mgr_sql db_mgr = wiki.Db_mgr_as_sql(); + Xow_db_mgr fsys_mgr = db_mgr.Core_data_mgr(); + Xow_db_file file = fsys_mgr.Dbs__get_by_id_or_fail(file_idx); + app.Usr_dlg().Plog_many("", "", "exec_sql: running sql; file_idx=~{0} sql=~{1}", file_idx, sql); + file.Conn().Exec_sql(sql); + } + public void Cmd_init(Xob_bldr bldr) {} + public void Cmd_bgn(Xob_bldr bldr) {} + public void Cmd_end() {} + public void Cmd_term() {} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_file_idx_)) file_idx = m.ReadInt("v"); + else if (ctx.Match(k, Invk_sql_)) sql = m.ReadStr("v"); + return this; + } + private static final String Invk_file_idx_ = "file_idx_", Invk_sql_ = "sql_"; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_site_meta_cmd.java b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_site_meta_cmd.java index a27517de8..ca2994d61 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_site_meta_cmd.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_site_meta_cmd.java @@ -13,3 +13,79 @@ 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.bldrs.cmds.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; +import gplx.core.net.*; +import gplx.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.bldrs.wms.*; import gplx.xowa.bldrs.wms.sites.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.apps.site_cfgs.*; +public class Xob_site_meta_cmd implements Xob_cmd { + private final Xob_bldr bldr; + private String[] wikis; private Io_url db_url; private DateAdp cutoff_time; + public Xob_site_meta_cmd(Xob_bldr bldr, Xow_wiki wiki) {this.bldr = bldr;} + public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return null;} + public String Cmd_key() {return Xob_cmd_keys.Key_site_meta;} + public void Cmd_run() { + Xoa_app app = bldr.App(); + if (wikis == null) wikis = Xow_domain_regy.All; + if (db_url == null) db_url = app.Fsys_mgr().Cfg_site_meta_fil(); + if (cutoff_time == null) cutoff_time = Datetime_now.Get().Add_day(-1); + Load_all(app, db_url, wikis, cutoff_time); + } + private void Load_all(Xoa_app app, Io_url db_url, String[] reqd_ary, DateAdp cutoff) { + Site_json_parser site_parser = new Site_json_parser(app.Utl__json_parser()); + Gfo_usr_dlg usr_dlg = app.Usr_dlg(); + Gfo_inet_conn inet_conn = app.Utl__inet_conn(); + Ordered_hash reqd_hash = Ordered_hash_.New(); + int reqd_len = reqd_ary.length; + for (int i = 0; i < reqd_len; ++i) + reqd_hash.Add_as_key_and_val(reqd_ary[i]); + + Site_core_db json_db = new Site_core_db(db_url); + Site_core_itm[] actl_ary = json_db.Tbl__core().Select_all_downloaded(cutoff); + int actl_len = actl_ary.length; + for (int i = 0; i < actl_len; ++i) { // remove items that have been completed after cutoff date + Site_core_itm actl_itm = actl_ary[i]; + reqd_hash.Del(String_.new_u8(actl_itm.Site_domain())); + } + + reqd_len = reqd_hash.Count(); + for (int i = 0; i < reqd_len; ++i) { + String domain_str = (String)reqd_hash.Get_at(i); + DateAdp json_date = Datetime_now.Get(); + byte[] json_text = null; + for (int j = 0; j < 5; ++j) { + json_text = gplx.xowa.bldrs.wms.Xowm_api_mgr.Call_by_qarg(usr_dlg, inet_conn, domain_str, Xoa_site_cfg_loader__inet.Qarg__all); + if (json_text == null) + gplx.core.threads.Thread_adp_.Sleep(1000); + else + break; + } + byte[] domain_bry = Bry_.new_u8(domain_str); + byte[] site_abrv = Xow_abrv_xo_.To_bry(domain_bry); + json_db.Tbl__core().Insert(site_abrv, domain_bry, Bool_.N, json_date, json_text); + } + + reqd_len = reqd_ary.length; + for (int i = 0; i < reqd_len; ++i) { + String domain_str = reqd_ary[i]; + byte[] site_abrv = Xow_abrv_xo_.To_bry(Bry_.new_u8(domain_str)); + Site_core_itm core_itm = json_db.Tbl__core().Select_itm(site_abrv); + if (core_itm.Json_completed()) continue; + Site_meta_itm meta_itm = new Site_meta_itm(); + site_parser.Parse_root(meta_itm, String_.new_u8(core_itm.Site_domain()), core_itm.Json_text()); + json_db.Save(meta_itm, site_abrv); + } + } + public void Cmd_init(Xob_bldr bldr) {} + public void Cmd_bgn(Xob_bldr bldr) {} + public void Cmd_end() {} + public void Cmd_term() {} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_db_url_)) this.db_url = m.ReadIoUrl("v"); + else if (ctx.Match(k, Invk_wikis_)) this.wikis = m.ReadStrAry("v", "\n"); + else if (ctx.Match(k, Invk_cutoff_time_)) this.cutoff_time = m.ReadDate("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static String Invk_db_url_ = "db_url_", Invk_wikis_ = "wikis_", Invk_cutoff_time_ = "cutoff_time_"; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_unzip_wkr.java b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_unzip_wkr.java index a27517de8..036af62f5 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_unzip_wkr.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_unzip_wkr.java @@ -13,3 +13,28 @@ 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.bldrs.cmds.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; +import gplx.core.envs.*; +public class Xob_unzip_wkr { + private Process_adp decompress_bz2, decompress_zip, decompress_gz, process; + public int Process_exit_code() {return process.Exit_code();} + public byte Process_run_mode() {return process_run_mode;} public Xob_unzip_wkr Process_run_mode_(byte v) {process_run_mode = v; return this;} private byte process_run_mode = Process_adp.Run_mode_async; + public Xob_unzip_wkr Init(Xoae_app app) {return Init(app.Prog_mgr().App_decompress_bz2(), app.Prog_mgr().App_decompress_zip(), app.Prog_mgr().App_decompress_gz());} + public Xob_unzip_wkr Init(Process_adp decompress_bz2, Process_adp decompress_zip, Process_adp decompress_gz) { + this.decompress_bz2 = decompress_bz2; + this.decompress_zip = decompress_zip; + this.decompress_gz = decompress_gz; + return this; + } + public void Decompress(Io_url src, Io_url trg) { + String src_ext = src.Ext(); + if (String_.Eq(src_ext, gplx.core.ios.streams.Io_stream_tid_.Ext__bz2)) process = decompress_bz2; + else if (String_.Eq(src_ext, gplx.core.ios.streams.Io_stream_tid_.Ext__zip)) process = decompress_zip; + else if (String_.Eq(src_ext, gplx.core.ios.streams.Io_stream_tid_.Ext__gz)) process = decompress_gz; + else throw Err_.new_unhandled(src_ext); + Io_url trg_owner_dir = trg.OwnerDir(); + Io_mgr.Instance.CreateDirIfAbsent(trg_owner_dir); + process.Run_mode_(process_run_mode); + process.Run(src, trg, trg_owner_dir.Xto_api()); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_xml_dumper_cmd.java b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_xml_dumper_cmd.java index a27517de8..b96a6a710 100644 --- a/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_xml_dumper_cmd.java +++ b/400_xowa/src/gplx/xowa/bldrs/cmds/utils/Xob_xml_dumper_cmd.java @@ -13,3 +13,52 @@ 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.bldrs.cmds.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; +import gplx.xowa.bldrs.wkrs.*; import gplx.xowa.bldrs.xmls.*; +import gplx.xowa.wikis.dbs.*; import gplx.xowa.wikis.data.tbls.*; +public class Xob_xml_dumper_cmd implements Xob_cmd { + private final Xowe_wiki wiki; private final Gfo_usr_dlg usr_dlg; + private final Xob_xml_dumper xml_dumper = new Xob_xml_dumper(); private int commit_interval = 1000; + private Io_url dump_url; + public Xob_xml_dumper_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.wiki = wiki; this.usr_dlg = wiki.Appe().Usr_dlg();} + public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return null;} + public String Cmd_key() {return Xob_cmd_keys.Key_util_xml_dump;} + public void Cmd_init(Xob_bldr bldr) { + dump_url = wiki.Fsys_mgr().Root_dir().GenSubFil(wiki.Domain_str() + "-dump.xml"); + Io_mgr.Instance.DeleteFil(dump_url); + } + public void Cmd_run() { + usr_dlg.Plog_many("", "", Cmd_key() + ":bgn;"); + String wiki_abrv = ""; + String main_page = String_.Format("https://{0}/wiki/{1}", wiki.Domain_str(), String_.new_u8(wiki.Props().Main_page())); + String ns_case = "first-letter"; // TODO_OLD: + xml_dumper.Write_root_bgn(wiki.Ns_mgr(), wiki.Domain_itm(), wiki_abrv, main_page, ns_case, "XOWA " + Xoa_app_.Version); + Xodb_page_rdr page_rdr = wiki.Db_mgr().Load_mgr().Get_page_rdr(wiki); + Xowd_page_itm page = new Xowd_page_itm(); + int page_count = 0; + try { + while (page_rdr.Move_next()) { + page_rdr.Read(page); + page.Ttl_(wiki.Ttl_parse(page.Ns_id(), page.Ttl_page_db())); + xml_dumper.Write_page(page); + if ((++page_count % commit_interval) == 0) Commit(); + } + } + catch (Exception e) {throw Err_.new_exc(e, "xo", "xml_dumper failed");} + finally {page_rdr.Rls();} + xml_dumper.Write_root_end(); + this.Commit(); + usr_dlg.Plog_many("", "", Cmd_key() + ":end;"); + } + private void Commit() { + Io_mgr.Instance.AppendFilStr(dump_url, xml_dumper.Bld_str()); + } + public void Cmd_bgn(Xob_bldr bldr) {} + public void Cmd_end() {} + public void Cmd_term() {} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_commit_interval_)) commit_interval = m.ReadInt("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_commit_interval_ = "commit_interval_"; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/css/Xoa_css_extractor.java b/400_xowa/src/gplx/xowa/bldrs/css/Xoa_css_extractor.java index a27517de8..b74720464 100644 --- a/400_xowa/src/gplx/xowa/bldrs/css/Xoa_css_extractor.java +++ b/400_xowa/src/gplx/xowa/bldrs/css/Xoa_css_extractor.java @@ -13,3 +13,283 @@ 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.bldrs.css; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.brys.fmtrs.*; import gplx.core.ios.*; import gplx.core.envs.*; +import gplx.xowa.htmls.*; import gplx.langs.htmls.encoders.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.data.*; +import gplx.xowa.files.downloads.*; +import gplx.core.net.*; +import gplx.xowa.addons.wikis.htmls.css.bldrs.*; import gplx.xowa.addons.wikis.htmls.css.mgrs.*; +import gplx.xowa.wikis.data.fetchers.*; +public class Xoa_css_extractor { + private Io_url home_css_dir; + public IoEngine_xrg_downloadFil Download_xrg() {return download_xrg;} private IoEngine_xrg_downloadFil download_xrg = Io_mgr.Instance.DownloadFil_args("", Io_url_.Empty); + public Xoa_css_extractor Wiki_domain_(byte[] v) {wiki_domain = v; return this;} private byte[] wiki_domain; + public Xoa_css_extractor Usr_dlg_(Gfo_usr_dlg v) {usr_dlg = v; return this;} private Gfo_usr_dlg usr_dlg; + public Xoa_css_extractor Failover_dir_(Io_url v) {failover_dir = v; return this;} private Io_url failover_dir; + public Xoa_css_extractor Wiki_html_dir_(Io_url v) {wiki_html_dir = v; return this;} private Io_url wiki_html_dir; + public Xoa_css_extractor Mainpage_url_(String v) {mainpage_url = v; return this;} private String mainpage_url; + public Xoa_css_extractor Protocol_prefix_(String v) {protocol_prefix = v; return this;} private String protocol_prefix = "https:";// NOTE: changed from http to https; DATE:2015-02-17 + public Xoa_css_extractor Page_fetcher_(Xow_page_fetcher v) {page_fetcher = v; return this;} private Xow_page_fetcher page_fetcher; + public Xoa_css_extractor Css_img_downloader_(Xoa_css_img_downloader v) {this.css_img_downloader = v; return this;} private Xoa_css_img_downloader css_img_downloader; + public Xoa_css_extractor Opt_download_css_common_(boolean v) {opt_download_css_common = v; return this;} private boolean opt_download_css_common; + public Xoa_css_extractor Url_encoder_(Gfo_url_encoder v) {url_encoder = v; return this;} private Gfo_url_encoder url_encoder; + public Xoa_css_extractor Wiki_code_(byte[] v) {this.wiki_code = v; return this;} private byte[] wiki_code = null; + private byte[] mainpage_html; private boolean lang_is_ltr = true; + private final Gfo_url_parser url_parser = new Gfo_url_parser(); + public void Init_by_app(Xoae_app app) { + this.usr_dlg = app.Usr_dlg(); + this.home_css_dir = app.Usere().Fsys_mgr().Wiki_html_dir("home").GenSubDir("html"); + Xof_download_wkr download_wkr = app.Wmf_mgr().Download_wkr(); + this.download_xrg = download_wkr.Download_xrg(); + css_img_downloader = new Xoa_css_img_downloader().Ctor(usr_dlg, download_wkr, Bry_.new_u8(protocol_prefix)); + failover_dir = app.Fsys_mgr().Bin_xowa_dir().GenSubDir_nest("html", "css", "failover"); + url_encoder = gplx.langs.htmls.encoders.Gfo_url_encoder_.Http_url; + } + public void Install(Xow_wiki wiki, String css_key) { + try { + this.wiki_html_dir = wiki.App().Fsys_mgr().Wiki_css_dir(wiki.Domain_str()); // EX: /xowa/user/anonymous/wiki/en.wikipedia.org + Io_url css_comm_fil = wiki_html_dir.GenSubFil(Css_common_name); + Io_url css_wiki_fil = wiki_html_dir.GenSubFil(Css_wiki_name); + wiki.Html__wtr_mgr().Init_css_urls(wiki.App(), wiki.Domain_str(), css_comm_fil, css_wiki_fil); + if (wiki.Domain_tid() == Xow_domain_tid_.Tid__home || Env_.Mode_testing()) return; // NOTE: do not download if home_wiki; also needed for TEST + if (Io_mgr.Instance.ExistsFil(css_wiki_fil)) return; // css file exists; nothing to generate + if (wiki.Html__css_installing()) return; + wiki.Html__css_installing_(true); + wiki.App().Usr_dlg().Log_many("", "", "generating css for '~{0}'", wiki.Domain_str()); + if (css_key != null) { + if (Install_by_db(wiki, wiki_html_dir, css_key)) return; + } + if (wiki.Type_is_edit()) + this.Install_by_wmf((Xowe_wiki)wiki, wiki_html_dir); + wiki.Html__css_installing_(false); + } + catch (Exception e) { // if error, failover; paranoia catch for outliers like bad network connectivity fail, or MediaWiki: message not existing; DATE:2013-11-21 + wiki.App().Usr_dlg().Warn_many("", "", "failed to get css; failing over; wiki='~{0}' err=~{1}", wiki.Domain_str(), Err_.Message_gplx_full(e)); + Css_common_failover(); // only failover xowa_common.css; xowa_wiki.css comes from MediaWiki:Common.css / Vector.css + wiki.Html__css_installing_(false); + } + } + private void Install_by_wmf(Xowe_wiki wiki, Io_url wiki_html_dir) { + opt_download_css_common = wiki.Appe().Cfg().Get_bool_app_or("xowa.bldr.import.download_xowa_common", true); // CFG: Cfg__ + + // do not download css if web_access disabled or wiki is other; DATE:2017-02-25 + boolean wiki_is_other = wiki.Domain_tid() == Xow_domain_tid_.Tid__other; + if ( !gplx.core.ios.IoEngine_system.Web_access_enabled + || wiki_is_other) + opt_download_css_common = false; // if !web_access_enabled, don't download + + this.wiki_domain = wiki.Domain_bry(); + mainpage_url = "https://" + wiki.Domain_str(); // NOTE: cannot reuse protocol_prefix b/c "//" needs to be added manually; protocol_prefix is used for logo and images which have form of "//domain/image.png"; changed to https; DATE:2015-02-17 + if (page_fetcher == null) page_fetcher = new Xow_page_fetcher_wiki(); + page_fetcher.Wiki_(wiki); + this.wiki_html_dir = wiki_html_dir; + this.lang_is_ltr = wiki.Lang().Dir_ltr(); + this.wiki_code = wiki.Domain_abrv(); + + // get mainpage; do not download css if wiki is other; DATE:2017-02-25 + mainpage_html = wiki_is_other ? Bry_.Empty : Mainpage_download_html(); + + // generate css + Css_common_setup(); + Css_wiki_setup(); + Logo_setup(); + } + private boolean Install_by_db(Xow_wiki wiki, Io_url wiki_html_dir, String css_key) { + Xow_db_mgr core_db_mgr = wiki.Data__core_mgr(); + if ( core_db_mgr == null + || core_db_mgr.Props() == null + || core_db_mgr.Props().Schema_is_1() + || !core_db_mgr.Tbl__cfg().Select_yn_or(Xowd_cfg_key_.Grp__wiki_schema, Xow_db_file_schema_props.Key__tbl_css_core, Bool_.N) + ) { + Xoa_app_.Usr_dlg().Warn_many("", "", "css.db not found; wiki=~{0} css_dir=~{1}", wiki.Domain_str(), wiki_html_dir.Raw()); + return false; + } + Xow_db_file core_db = core_db_mgr.Db__core(); + return Xowd_css_core_mgr.Get(core_db.Tbl__css_core(), core_db.Tbl__css_file(), wiki_html_dir, css_key); + } + public void Css_common_setup() { + if (opt_download_css_common) + Css_common_download(); + else + Css_common_failover(); + } + private void Css_common_failover() { + Io_url trg_fil = wiki_html_dir.GenSubFil(Css_common_name); + if (home_css_dir != null) // TEST: + Io_mgr.Instance.CopyDirDeep(home_css_dir, trg_fil.OwnerDir()); // NOTE: copy dir first b/c xowa_commons.css will be replaced below + Io_mgr.Instance.CopyFil(Css_common_failover_url(), trg_fil, true); + } + private void Css_common_download() { + boolean css_stylesheet_common_missing = true; + Io_url trg_fil = wiki_html_dir.GenSubFil(Css_common_name); + css_stylesheet_common_missing = !Css_scrape_setup(); + if (css_stylesheet_common_missing) + Io_mgr.Instance.CopyFil(Css_common_failover_url(), trg_fil, true); + else + css_img_downloader.Chk(wiki_domain, trg_fil); + } + private Io_url Css_common_failover_url() { + Io_url css_commons_url = failover_dir.GenSubDir("xowa_common_override").GenSubFil_ary("xowa_common_", String_.new_u8(wiki_code), ".css"); + if (Io_mgr.Instance.ExistsFil(css_commons_url)) return css_commons_url; // specific css exists for wiki; use it; EX: xowa_common_wiki_mediawikiwiki.css + return failover_dir.GenSubFil(lang_is_ltr ? Css_common_name_ltr : Css_common_name_rtl); + } + public void Css_wiki_setup() { + boolean css_stylesheet_wiki_missing = true; + Io_url trg_fil = wiki_html_dir.GenSubFil(Css_wiki_name); + if (Io_mgr.Instance.ExistsFil(trg_fil)) return; // don't download if already there + css_stylesheet_wiki_missing = !Css_wiki_generate(trg_fil); + if (css_stylesheet_wiki_missing) + Failover(trg_fil); + else + css_img_downloader.Chk(wiki_domain, trg_fil); + } + private boolean Css_wiki_generate(Io_url trg_fil) { + Bry_bfr bfr = Bry_bfr_.New(); + Css_wiki_generate_section(bfr, Ttl_common_css); + Css_wiki_generate_section(bfr, Ttl_vector_css); + byte[] bry = bfr.To_bry_and_clear(); + bry = Bry_.Replace(bry, gplx.xowa.bldrs.xmls.Xob_xml_parser_.Bry_tab_ent, gplx.xowa.bldrs.xmls.Xob_xml_parser_.Bry_tab); + Io_mgr.Instance.SaveFilBry(trg_fil, bry); + return true; + } private static final byte[] Ttl_common_css = Bry_.new_a7("Common.css"), Ttl_vector_css = Bry_.new_a7("Vector.css"); + private boolean Css_wiki_generate_section(Bry_bfr bfr, byte[] ttl) { + byte[] page = page_fetcher.Get_by(Xow_ns_.Tid__mediawiki, ttl); + if (page == null) return false; + if (bfr.Len() != 0) bfr.Add_byte_nl().Add_byte_nl(); // add "\n\n" between sections; !=0 checks against first + Css_wiki_section_hdr.Bld_bfr_many(bfr, ttl); // add "/*XOWA:MediaWiki:Common.css*/\n" + bfr.Add(page); // add page + return true; + } private static final Bry_fmtr Css_wiki_section_hdr = Bry_fmtr.new_("/*XOWA:MediaWiki:~{ttl}*/\n", "ttl"); + public void Logo_setup() { + boolean logo_missing = true; + Io_url logo_url = wiki_html_dir.GenSubFil("logo.png"); + if (Io_mgr.Instance.ExistsFil(logo_url)) return; // don't download if already there + logo_missing = !Logo_download(logo_url); + if (logo_missing) + Failover(logo_url); + } + private boolean Logo_download(Io_url trg_fil) { + String src_fil = Logo_find_src(); + if (src_fil == null) { + if (Logo_copy_from_css(trg_fil)) return true; + usr_dlg.Warn_many("", "", "failed to extract logo: trg_fil=~{0};", trg_fil.Raw()); + return false; + } + String log_msg = usr_dlg.Prog_many("", "", "downloading logo: '~{0}'", src_fil); + boolean rv = download_xrg.Prog_fmt_hdr_(log_msg).Src_(src_fil).Trg_(trg_fil).Exec(); + if (!rv) + usr_dlg.Warn_many("", "", "failed to download logo: src_url=~{0};", src_fil); + return rv; + } + private boolean Logo_copy_from_css(Io_url trg_fil) { + Io_url commons_file = wiki_html_dir.GenSubFil(Css_common_name); + byte[] commons_src = Io_mgr.Instance.LoadFilBry(commons_file); + int bgn_pos = Bry_find_.Find_fwd(commons_src, Bry_mw_wiki_logo); if (bgn_pos == Bry_find_.Not_found) return false; + bgn_pos += Bry_mw_wiki_logo.length; + int end_pos = Bry_find_.Find_fwd(commons_src, Byte_ascii.Quote, bgn_pos + 1); if (end_pos == Bry_find_.Not_found) return false; + byte[] src_bry = Bry_.Mid(commons_src, bgn_pos, end_pos); + src_bry = Xob_url_fixer.Fix(wiki_domain, src_bry, src_bry.length); + if (wiki_html_dir.Info().DirSpr_byte() == Byte_ascii.Backslash) + src_bry = Bry_.Replace(src_bry, Byte_ascii.Slash, Byte_ascii.Backslash); + Io_url src_fil = wiki_html_dir.GenSubFil(String_.new_u8(src_bry)); + Io_mgr.Instance.CopyFil(src_fil, trg_fil, true); + return true; + } private static final byte[] Bry_mw_wiki_logo = Bry_.new_a7(".mw-wiki-logo{background-image:url(\""); + private String Logo_find_src() { + if (mainpage_html == null) return null; + int main_page_html_len = mainpage_html.length; + int logo_bgn = Bry_find_.Find_fwd(mainpage_html, Logo_find_bgn, 0); if (logo_bgn == Bry_find_.Not_found) return null; + logo_bgn += Logo_find_bgn.length; + logo_bgn = Bry_find_.Find_fwd(mainpage_html, Logo_find_end, logo_bgn); if (logo_bgn == Bry_find_.Not_found) return null; + logo_bgn += Logo_find_end.length; + int logo_end = Bry_find_.Find_fwd(mainpage_html, Byte_ascii.Paren_end, logo_bgn, main_page_html_len); if (logo_bgn == Bry_find_.Not_found) return null; + byte[] logo_bry = Bry_.Mid(mainpage_html, logo_bgn, logo_end); + return protocol_prefix + String_.new_u8(logo_bry); + } + private static final byte[] Logo_find_bgn = Bry_.new_a7("
    & + css_url_bry = url_encoder.Decode(css_url_bry); // %2C -> %7C -> | + css_url_bry = Xoa_css_extractor.Url_root_fix(wiki_domain, css_url_bry); + Gfo_url gfo_url = url_parser.Parse(css_url_bry, 0, css_url_bry.length); + if ( gfo_url.Protocol_tid() == Gfo_protocol_itm.Tid_relative_1 // if rel url, add protocol_prefix DATE:2015-08-01 + || (Env_.Mode_testing() && gfo_url.Protocol_tid() == Gfo_protocol_itm.Tid_unknown)) // TEST: + css_url_bry = Bry_.Add(protocol_prefix_bry, css_url_bry); + rv.Add(String_.new_u8(css_url_bry)); + prv_pos = url_end; + } + return rv.To_str_ary(); + } private static final byte[] Css_find_bgn = Bry_.new_a7("" + , " " + , " " + , " " + , " " + , " " + , "
    " + , "" + ); +} diff --git a/400_xowa/src/gplx/xowa/bldrs/css/Xoa_css_extractor_wiki_tst.java b/400_xowa/src/gplx/xowa/bldrs/css/Xoa_css_extractor_wiki_tst.java index a27517de8..2e9b42da6 100644 --- a/400_xowa/src/gplx/xowa/bldrs/css/Xoa_css_extractor_wiki_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/css/Xoa_css_extractor_wiki_tst.java @@ -13,3 +13,32 @@ 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.bldrs.css; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import org.junit.*; import gplx.core.ios.*; import gplx.xowa.wikis.nss.*; +public class Xoa_css_extractor_wiki_tst { + @Before public void init() {fxt.Clear();} private Xoa_css_extractor_fxt fxt = new Xoa_css_extractor_fxt(); + @Test public void Css_wiki_generate() { + fxt.Init_page(Xow_ns_.Tid__mediawiki, "Common.css" , "css_0"); + fxt.Init_page(Xow_ns_.Tid__mediawiki, "Vector.css" , "css_1"); + fxt.Exec_css_wiki_setup(); + fxt.Test_fil("mem/xowa/user/anonymous/wiki/en.wikipedia.org/html/xowa_wiki.css", String_.Concat_lines_nl + ( "/*XOWA:MediaWiki:Common.css*/" + , "css_0" + , "" + , "/*XOWA:MediaWiki:Vector.css*/" + , "css_1" + )); + } + @Test public void Css_wiki_missing() { + fxt.Exec_css_wiki_setup(); + fxt.Test_fil("mem/xowa/user/anonymous/wiki/en.wikipedia.org/html/xowa_wiki.css", ""); + } + @Test public void Css_wiki_tab() { // PURPOSE: swap out for xdat files + fxt.Init_page(Xow_ns_.Tid__mediawiki, "Common.css" , "a b"); + fxt.Exec_css_wiki_setup(); + fxt.Test_fil("mem/xowa/user/anonymous/wiki/en.wikipedia.org/html/xowa_wiki.css", String_.Concat_lines_nl + ( "/*XOWA:MediaWiki:Common.css*/" + , "a\tb" + )); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/css/Xoa_css_img_downloader.java b/400_xowa/src/gplx/xowa/bldrs/css/Xoa_css_img_downloader.java index a27517de8..22150c868 100644 --- a/400_xowa/src/gplx/xowa/bldrs/css/Xoa_css_img_downloader.java +++ b/400_xowa/src/gplx/xowa/bldrs/css/Xoa_css_img_downloader.java @@ -13,3 +13,177 @@ 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.bldrs.css; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.xowa.files.downloads.*; import gplx.core.envs.*; +public class Xoa_css_img_downloader { + private byte[] wiki_domain; + public Xoa_css_img_downloader Ctor(Gfo_usr_dlg usr_dlg, Xof_download_wkr download_wkr, byte[] stylesheet_prefix) { + this.usr_dlg = usr_dlg; this.download_wkr = download_wkr; this.stylesheet_prefix = stylesheet_prefix; + return this; + } private Gfo_usr_dlg usr_dlg; private Xof_download_wkr download_wkr; + public Xoa_css_img_downloader Stylesheet_prefix_(byte[] v) {stylesheet_prefix = v; return this;} private byte[] stylesheet_prefix; // TEST: setter exposed b/c tests can handle "mem/" but not "//mem" + public void Chk(byte[] wiki_domain, Io_url css_fil) { + this.wiki_domain = wiki_domain; + List_adp img_list = List_adp_.New(); + byte[] old_bry = Io_mgr.Instance.LoadFilBry(css_fil); + byte[] rel_url_prefix = Bry_.Add(Bry_fwd_slashes, wiki_domain); + byte[] new_bry = Convert_to_local_urls(rel_url_prefix, old_bry, img_list); + Io_url img_dir = css_fil.OwnerDir(); + Download_fils(img_dir, img_list.To_str_ary()); + Io_mgr.Instance.SaveFilBry(css_fil, new_bry); + } + public byte[] Convert_to_local_urls(byte[] rel_url_prefix, byte[] src, List_adp list) { + try { + int src_len = src.length; + int prv_pos = 0; + Bry_bfr bfr = Bry_bfr_.New_w_size(src_len); + Hash_adp img_hash = Hash_adp_bry.cs(); + while (true) { + int url_pos = Bry_find_.Find_fwd(src, Bry_url, prv_pos); + if (url_pos == Bry_find_.Not_found) {bfr.Add_mid(src, prv_pos, src_len); break;} // no more "url("; exit; + int bgn_pos = url_pos + Bry_url_len; // set bgn_pos after "url(" + byte bgn_byte = src[bgn_pos]; + byte end_byte = Byte_ascii.Null; + boolean quoted = true; + switch (bgn_byte) { // find end_byte + case Byte_ascii.Quote: case Byte_ascii.Apos: // quoted; end_byte is ' or " + end_byte = bgn_byte; + ++bgn_pos; + break; + default: // not quoted; end byte is ")" + end_byte = Byte_ascii.Paren_end; + quoted = false; + break; + } + int end_pos = Bry_find_.Find_fwd(src, end_byte, bgn_pos, src_len); + if (end_pos == Bry_find_.Not_found) { // unclosed "url("; exit since nothing else will be found + usr_dlg.Warn_many(GRP_KEY, "parse.invalid_url.end_missing", "could not find end_sequence for 'url(': bgn='~{0}' end='~{1}'", prv_pos, String_.new_u8__by_len(src, prv_pos, prv_pos + 25)); + bfr.Add_mid(src, prv_pos, src_len); + break; + } + if (end_pos - bgn_pos == 0) { // empty; "url()"; ignore + usr_dlg.Warn_many(GRP_KEY, "parse.invalid_url.empty", "'url(' is empty: bgn='~{0}' end='~{1}'", prv_pos, String_.new_u8__by_len(src, prv_pos, prv_pos + 25)); + bfr.Add_mid(src, prv_pos, bgn_pos); + prv_pos = bgn_pos; + continue; + } + byte[] img_raw = Bry_.Mid(src, bgn_pos, end_pos); int img_raw_len = img_raw.length; + if (Bry_.Has_at_bgn(img_raw, Bry_data_image, 0, img_raw_len)) { // base64 + bfr.Add_mid(src, prv_pos, end_pos); // nothing to download; just add entire String + prv_pos = end_pos; + continue; + } + int import_url_end = Import_url_chk(rel_url_prefix, src, src_len, prv_pos, url_pos, img_raw, bfr); // check for embedded stylesheets via @import tag + if (import_url_end != Bry_find_.Not_found) { + prv_pos = import_url_end; + continue; + } + byte[] img_cleaned = Xob_url_fixer.Fix(wiki_domain, img_raw, img_raw_len); + if (img_cleaned == null) { // could not clean img + usr_dlg.Warn_many(GRP_KEY, "parse.invalid_url.clean_failed", "could not extract valid http src: bgn='~{0}' end='~{1}'", prv_pos, String_.new_u8(img_raw)); + bfr.Add_mid(src, prv_pos, bgn_pos); prv_pos = bgn_pos; continue; + } + if (!img_hash.Has(img_cleaned)) {// only add unique items for download; + img_hash.Add_as_key_and_val(img_cleaned); + list.Add(String_.new_u8(img_cleaned)); + } + img_cleaned = Replace_invalid_chars(Bry_.Copy(img_cleaned)); // NOTE: must call ByteAry.Copy else img_cleaned will change *inside* hash + bfr.Add_mid(src, prv_pos, bgn_pos); + if (!quoted) bfr.Add_byte(Byte_ascii.Quote); + bfr.Add(img_cleaned); + if (!quoted) bfr.Add_byte(Byte_ascii.Quote); + prv_pos = end_pos; + } + return bfr.To_bry_and_clear(); + } + catch (Exception e) { + usr_dlg.Warn_many("", "", "failed to convert local_urls: ~{0} ~{1}", String_.new_u8(rel_url_prefix), Err_.Message_gplx_full(e)); + return src; + } + } + public static byte[] Import_url_build(byte[] stylesheet_prefix, byte[] rel_url_prefix, byte[] css_url) { + return Bry_.Has_at_bgn(css_url, Bry_http_protocol) // css_url already starts with "http"; return self; PAGE:tr.n:Main_Page; DATE:2014-06-04 + ? css_url + : Bry_.Add(stylesheet_prefix, css_url) + ; + } + private int Import_url_chk(byte[] rel_url_prefix, byte[] src, int src_len, int old_pos, int find_bgn, byte[] url_raw, Bry_bfr bfr) { + if (find_bgn < Bry_import_len) return Bry_find_.Not_found; + if (!Bry_.Match(src, find_bgn - Bry_import_len, find_bgn, Bry_import)) return Bry_find_.Not_found; + byte[] css_url = url_raw; int css_url_len = css_url.length; + if (css_url_len > 0 && css_url[0] == Byte_ascii.Slash) { // css_url starts with "/"; EX: "/page" or "//site/page" DATE:2014-02-03 + if (css_url_len > 1 && css_url[1] != Byte_ascii.Slash) // skip if css_url starts with "//"; EX: "//site/page" + css_url = Bry_.Add(rel_url_prefix, css_url); // "/w/a.css" -> "//en.wikipedia.org/w/a.css" + } + css_url = Bry_.Replace(css_url, Byte_ascii.Space, Byte_ascii.Underline); // NOTE: must replace spaces with underlines else download will fail; EX:https://it.wikivoyage.org/w/index.php?title=MediaWiki:Container e Infobox.css&action=raw&ctype=text/css; DATE:2015-03-08 + byte[] css_src_bry = Import_url_build(stylesheet_prefix, rel_url_prefix, css_url); + String css_src_str = String_.new_u8(css_src_bry); + download_wkr.Download_xrg().Prog_fmt_hdr_(usr_dlg.Log_many(GRP_KEY, "logo.download", "downloading import for '~{0}'", css_src_str)); + byte[] css_trg_bry = download_wkr.Download_xrg().Exec_as_bry(css_src_str); + if (css_trg_bry == null) { + usr_dlg.Warn_many("", "", "could not import css: url=~{0}", css_src_str); + return Bry_find_.Not_found; // css not found + } + bfr.Add_mid(src, old_pos, find_bgn - Bry_import_len).Add_byte_nl(); + bfr.Add(Bry_comment_bgn).Add(css_url).Add(Bry_comment_end).Add_byte_nl(); + if (Bry_find_.Find_fwd(css_url, Wikisource_dynimg_ttl) != -1) css_trg_bry = Bry_.Replace(css_trg_bry, Wikisource_dynimg_find, Wikisource_dynimg_repl); // FreedImg hack; PAGE:en.s:Page:Notes_on_Osteology_of_Baptanodon._With_a_Description_of_a_New_Species.pdf/3 DATE:2014-09-06 + bfr.Add(css_trg_bry).Add_byte_nl(); + bfr.Add_byte_nl(); + int semic_pos = Bry_find_.Find_fwd(src, Byte_ascii.Semic, find_bgn + url_raw.length, src_len); + return semic_pos + Byte_ascii.Len_1; + } + private static final byte[] + Wikisource_dynimg_ttl = Bry_.new_a7("en.wikisource.org/w/index.php?title=MediaWiki:Dynimg.css") + , Wikisource_dynimg_find = Bry_.new_a7(".freedImg img[src*=\"wikipedia\"], .freedImg img[src*=\"wikisource\"], .freedImg img[src*=\"score\"], .freedImg img[src*=\"math\"] {") + , Wikisource_dynimg_repl = Bry_.new_a7(".freedImg img[src*=\"wikipedia\"], .freedImg img[src*=\"wikisource\"], /*XOWA:handle file:// paths which will have /commons.wikimedia.org/ but not /wikipedia/ */ .freedImg img[src*=\"wikimedia\"], .freedImg img[src*=\"score\"], .freedImg img[src*=\"math\"] {") + ; + public byte[] Clean_img_url(byte[] raw, int raw_len) { + int pos_bgn = 0; + if (Bry_.Has_at_bgn(raw, Bry_fwd_slashes, 0, raw_len)) pos_bgn = Bry_fwd_slashes.length; + if (Bry_.Has_at_bgn(raw, Bry_http, 0, raw_len)) pos_bgn = Bry_http.length; + int pos_slash = Bry_find_.Find_fwd(raw, Byte_ascii.Slash, pos_bgn, raw_len); + if (pos_slash == Bry_find_.Not_found) return null; // first segment is site_name; at least one slash must be present for image name; EX: site.org/img_name.jpg + if (pos_slash == raw_len - 1) return null; // "site.org/" is invalid + int pos_end = raw_len; + int pos_question = Bry_find_.Find_bwd(raw, Byte_ascii.Question); + if (pos_question != Bry_find_.Not_found) + pos_end = pos_question; // remove query params; EX: img_name?key=val + return Bry_.Mid(raw, pos_bgn, pos_end); + } + private void Download_fils(Io_url css_dir, String[] ary) { + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) { + String src = ary[i]; + Io_url trg = css_dir.GenSubFil_nest(Op_sys.Cur().Fsys_http_frag_to_url_str(Replace_invalid_chars_str(src))); + if (Io_mgr.Instance.ExistsFil(trg)) continue; + download_wkr.Download(true, "https://" + src, trg, "download: " + src); // ILN + if (Io_mgr.Instance.QueryFil(trg).Size() == 0) { // warn if 0 byte files downloaded; DATE:2015-07-06 + Xoa_app_.Usr_dlg().Warn_many("", "", "css.download; 0 byte file downloaded; file=~{0}", trg.Raw()); + } + } + } + String Replace_invalid_chars_str(String raw_str) {return String_.new_u8(Replace_invalid_chars(Bry_.new_u8(raw_str)));} + byte[] Replace_invalid_chars(byte[] raw_bry) { + int raw_len = raw_bry.length; + for (int i = 0; i < raw_len; i++) { // convert invalid wnt chars to underscores + byte b = raw_bry[i]; + switch (b) { + //case Byte_ascii.Slash: + case Byte_ascii.Backslash: case Byte_ascii.Colon: case Byte_ascii.Star: case Byte_ascii.Question: + case Byte_ascii.Quote: case Byte_ascii.Lt: case Byte_ascii.Gt: case Byte_ascii.Pipe: + raw_bry[i] = Byte_ascii.Underline; + break; + } + } + return raw_bry; + } + private static final byte[] + Bry_url = Bry_.new_a7("url("), Bry_data_image = Bry_.new_a7("data:image/") + , Bry_http = Bry_.new_a7("http://"), Bry_fwd_slashes = Bry_.new_a7("//"), Bry_import = Bry_.new_a7("@import ") + , Bry_http_protocol = Bry_.new_a7("http") + ; + public static final byte[] + Bry_comment_bgn = Bry_.new_a7("/*XOWA:"), Bry_comment_end = Bry_.new_a7("*/"); + private static final int Bry_url_len = Bry_url.length, Bry_import_len = Bry_import.length; + static final String GRP_KEY = "xowa.wikis.init.css"; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/css/Xoa_css_img_downloader_tst.java b/400_xowa/src/gplx/xowa/bldrs/css/Xoa_css_img_downloader_tst.java index a27517de8..b18aecc0b 100644 --- a/400_xowa/src/gplx/xowa/bldrs/css/Xoa_css_img_downloader_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/css/Xoa_css_img_downloader_tst.java @@ -13,3 +13,169 @@ 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.bldrs.css; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import org.junit.*; import gplx.xowa.files.downloads.*; +public class Xoa_css_img_downloader_tst { + @Before public void init() {fxt.Clear();} private Xoa_css_img_downloader_fxt fxt = new Xoa_css_img_downloader_fxt(); + @Test public void Basic() { + fxt.Test_css_convert + ( "x {url(\"//site/a.jpg\")} y {url(\"//site/b.jpg\")}" + , "x {url(\"site/a.jpg\")} y {url(\"site/b.jpg\")}" + , "site/a.jpg" + , "site/b.jpg" + ); + } + @Test public void Unquoted() { + fxt.Test_css_convert + ( "x {url(//site/a.jpg)}" + , "x {url(\"site/a.jpg\")}" + , "site/a.jpg" + ); + } + @Test public void Http() { + fxt.Test_css_convert + ( "x {url(http://site/a.jpg)}" + , "x {url(\"site/a.jpg\")}" + , "site/a.jpg" + ); + } + @Test public void Base64() { + fxt.Test_css_convert + ( "x {url(\"//site/a.jpg\")} y {url(\"data:image/png;base64,BASE64DATA;ABC=\")} z {}" + , "x {url(\"site/a.jpg\")} y {url(\"data:image/png;base64,BASE64DATA;ABC=\")} z {}" + , "site/a.jpg" + ); + } + @Test public void Exc_missing_quote() { + fxt.Test_css_convert + ( "x {url(\"//site/a.jpg\")} y {url(\"//site/b.jpg} z {}" + , "x {url(\"site/a.jpg\")} y {url(\"//site/b.jpg} z {}" + , "site/a.jpg" + ); + } + @Test public void Exc_empty() { + fxt.Test_css_convert + ( "x {url(\"//site/a.jpg\")} y {url(\"\"} z {}" + , "x {url(\"site/a.jpg\")} y {url(\"\"} z {}" + , "site/a.jpg" + ); + } +// @Test public void Exc_name_only() { // COMMENTED: not sure how to handle "b.jpg" (automatically add "current" path?); RESTORE: when example found +// fxt.Test_css_convert +// ( "x {url(\"//site/a.jpg\")} y {url(\"b.jpg\"} z {}" +// , "x {url(\"site/a.jpg\")} y {url(\"b.jpg\"} z {}" +// , "site/a.jpg" +// ); +// } + @Test public void Repeat() {// PURPOSE.fix: exact same item was being added literally + fxt.Test_css_convert + ( "x {url(\"//site/a.jpg?a=b\")} y {url(\"//site/a.jpg?a=b\"}" + , "x {url(\"site/a.jpg\")} y {url(\"site/a.jpg\"}" + , "site/a.jpg" + ); + } + @Test public void Clean_basic() {fxt.Test_clean_img_url("//site/a.jpg" , "site/a.jpg");} + @Test public void Clean_query() {fxt.Test_clean_img_url("//site/a.jpg?key=val" , "site/a.jpg");} + @Test public void Clean_dir() {fxt.Test_clean_img_url("//site/a/b/c.jpg?key=val" , "site/a/b/c.jpg");} + @Test public void Clean_exc_site_only() {fxt.Test_clean_img_url("//site" , null);} + @Test public void Clean_exc_site_only_2() {fxt.Test_clean_img_url("//site/" , null);} + @Test public void Import_url() { + Io_mgr.Instance.InitEngine_mem(); + Io_mgr.Instance.SaveFilStr("mem/www/b.css", "imported_css"); + fxt.Test_css_convert + ( "x @import url(\"mem/www/b.css\") screen; z" + , String_.Concat_lines_nl + ( "x " + , "/*XOWA:mem/www/b.css*/" + , "imported_css" + , "" + , " z" + ) + ); + } + @Test public void Import_url_make() { + fxt.Test_import_url("a.org/b" , "http:a.org/b"); // add "stylesheet_prefix" + fxt.Test_import_url("http://a.org" , "http://a.org"); // unless it starts with http + fxt.Test_import_url("https://a.org" , "https://a.org"); // unless starts with https EX:: handle @import(https://...); PAGE:tr.n:Main_Page; DATE:2014-06-04 + } + @Test public void Import_url_relative() { // PURPOSE: if directory, add domain; "/a/b.css" -> "//domain/a/b.css"; DATE:2014-02-03 + Io_mgr.Instance.InitEngine_mem(); + Io_mgr.Instance.SaveFilStr("mem/en.wikipedia.org/www/b.css", "imported_css"); + fxt.Test_css_convert + ( "x @import url(\"/www/b.css\") screen; z" // starts with "/" + , String_.Concat_lines_nl + ( "x " + , "/*XOWA:mem/en.wikipedia.org/www/b.css*/" + , "imported_css" + , "" + , " z" + ) + ); + } + @Test public void Import_url_relative_skip() { // PURPOSE: if rel path, skip; "//site/a/b.css"; DATE:2014-02-03 + fxt.Downloader().Stylesheet_prefix_(Bry_.new_a7("mem")); // stylesheet prefix prefix defaults to ""; set to "mem", else test will try to retrieve "//url" which will fail + Io_mgr.Instance.InitEngine_mem(); + Io_mgr.Instance.SaveFilStr("mem//en.wikipedia.org/a/b.css", "imported_css"); + fxt.Test_css_convert + ( "x @import url(\"//en.wikipedia.org/a/b.css\") screen; z" // starts with "//" + , String_.Concat_lines_nl + ( "x " + , "/*XOWA://en.wikipedia.org/a/b.css*/" + , "imported_css" + , "" + , " z" + ) + ); + } + @Test public void Import_url_space() { // PURPOSE: some css has spaces; replace with underlines else fails when downloaded; EX: https://it.wikivoyage.org/w/index.php?title=MediaWiki:Container e Infobox.css&action=raw&ctype=text/css; DATE:2015-03-08 + Io_mgr.Instance.InitEngine_mem(); + Io_mgr.Instance.SaveFilStr("mem/www/b_c.css", "imported_css"); + fxt.Test_css_convert + ( "x @import url(\"mem/www/b c.css\") screen; z" + , String_.Concat_lines_nl + ( "x " + , "/*XOWA:mem/www/b_c.css*/" + , "imported_css" + , "" + , " z" + ) + ); + } + @Test public void Wikisource_freedimg() { // PURPOSE: check that "wikimedia" is replaced for FreedImg hack; PAGE:en.s:Page:Notes_on_Osteology_of_Baptanodon._With_a_Description_of_a_New_Species.pdf/3 DATE:2014-09-06 + fxt.Downloader().Stylesheet_prefix_(Bry_.new_a7("mem")); // stylesheet prefix prefix defaults to ""; set to "mem", else test will try to retrieve "//url" which will fail + Io_mgr.Instance.InitEngine_mem(); + Io_mgr.Instance.SaveFilStr("mem//en.wikisource.org/w/index.php?title=MediaWiki:Dynimg.css", ".freedImg img[src*=\"wikipedia\"], .freedImg img[src*=\"wikisource\"], .freedImg img[src*=\"score\"], .freedImg img[src*=\"math\"] {"); + fxt.Test_css_convert + ( "x @import url(\"//en.wikisource.org/w/index.php?title=MediaWiki:Dynimg.css\") screen; z" // starts with "//" + , String_.Concat_lines_nl + ( "x " + , "/*XOWA://en.wikisource.org/w/index.php?title=MediaWiki:Dynimg.css*/" + , ".freedImg img[src*=\"wikipedia\"], .freedImg img[src*=\"wikisource\"], /*XOWA:handle file:// paths which will have /commons.wikimedia.org/ but not /wikipedia/ */ .freedImg img[src*=\"wikimedia\"], .freedImg img[src*=\"score\"], .freedImg img[src*=\"math\"] {" + , "" + , " z" + ) + ); + } +} +class Xoa_css_img_downloader_fxt { + public Xoa_css_img_downloader Downloader() {return downloader;} private Xoa_css_img_downloader downloader; + public void Clear() { + downloader = new Xoa_css_img_downloader(); + downloader.Ctor(Gfo_usr_dlg_.Test(), new Xof_download_wkr_test(), Bry_.Empty); + } + public void Test_css_convert(String raw, String expd, String... expd_img_ary) { + List_adp actl_img_list = List_adp_.New(); + byte[] actl_bry = downloader.Convert_to_local_urls(Bry_.new_a7("mem/en.wikipedia.org"), Bry_.new_u8(raw), actl_img_list); + Tfds.Eq_str_lines(expd, String_.new_u8(actl_bry)); + Tfds.Eq_ary_str(expd_img_ary, actl_img_list.To_str_ary()); + } + public void Test_clean_img_url(String raw_str, String expd) { + byte[] raw = Bry_.new_a7(raw_str); + byte[] actl = downloader.Clean_img_url(raw, raw.length); + Tfds.Eq(expd, actl == null ? null : String_.new_a7(actl)); + } + public void Test_import_url(String raw, String expd) { + byte[] actl = Xoa_css_img_downloader.Import_url_build(Bry_.new_a7("http:"), Bry_.new_a7("//en.wikipedia.org"), Bry_.new_u8(raw)); + Tfds.Eq(expd, String_.new_u8(actl)); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser.java b/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser.java index a27517de8..0e323a9e2 100644 --- a/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser.java +++ b/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.bldrs.css; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.btries.*; import gplx.core.primitives.*; +class Xob_css_parser { + private final Bry_bfr bfr = Bry_bfr_.New_w_size(255); + private final Xob_mirror_mgr mgr; + private final Xob_css_parser__url url_parser; private final Xob_css_parser__import import_parser; + private final Btrie_rv trv = new Btrie_rv(); + public Xob_css_parser(Xob_mirror_mgr mgr) { + this.mgr = mgr; + this.url_parser = new Xob_css_parser__url(mgr.Site_url()); + this.import_parser = new Xob_css_parser__import(url_parser); + } + public void Parse(byte[] src) { + int src_len = src.length; int pos = 0; + while (pos < src_len) { + byte b = src[pos]; + Object o = tkns_trie.Match_at_w_b0(trv, b, src, pos, src_len); + if (o == null) { + bfr.Add_byte(b); + ++pos; + } + else { + byte tkn_tid = ((Byte_obj_val)o).Val(); + int match_pos = trv.Pos(); + Xob_css_tkn__base tkn = null; + switch (tkn_tid) { + case Tkn_url: tkn = url_parser.Parse(src, src_len, pos, match_pos); break; + case Tkn_import: tkn = import_parser.Parse(src, src_len, pos, match_pos); break; + } + tkn.Process(mgr); + pos = tkn.Write(bfr, src); + } + } + } + private static final byte Tkn_import = 1, Tkn_url = 2; + private static final Btrie_slim_mgr tkns_trie = Btrie_slim_mgr.ci_a7() + .Add_str_byte("@import" , Tkn_import) + .Add_str_byte(" url(" , Tkn_url) + ; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser__import.java b/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser__import.java index a27517de8..4b443273c 100644 --- a/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser__import.java +++ b/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser__import.java @@ -13,3 +13,29 @@ 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.bldrs.css; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.xowa.files.downloads.*; +class Xob_css_parser__import { +// // "//id.wikibooks.org/w/index.php?title=MediaWiki:Common.css&oldid=43393&action=raw&ctype=text/css"; + private final Xob_css_parser__url url_parser; + public Xob_css_parser__import(Xob_css_parser__url url_parser) {this.url_parser = url_parser;} + public Xob_css_tkn__base Parse(byte[] src, int src_len, int tkn_bgn, int tkn_end) { // " @import" + int bgn_pos = Bry_find_.Find_fwd_while_ws(src, tkn_end, src_len); // skip any ws after " @import" + if (bgn_pos == src_len) return Xob_css_tkn__warn.new_(tkn_bgn, tkn_end, "mirror.parser.import:EOS after import; bgn=~{0}", tkn_bgn); + if (!Bry_.Has_at_bgn(src, Tkn_url_bry, bgn_pos, src_len)) return Xob_css_tkn__warn.new_(tkn_bgn, tkn_end, "mirror.parser.import:url missing; bgn=~{0}", tkn_bgn); + tkn_end = bgn_pos + Tkn_url_bry.length; + Xob_css_tkn__base frag = url_parser.Parse(src, src_len, bgn_pos, tkn_end); + if (frag.Tid() != Xob_css_tkn__url.Tid_url) return Xob_css_tkn__warn.new_(tkn_bgn, frag.Pos_end(), "mirror.parser.import:url invalid; bgn=~{0}", tkn_bgn); + Xob_css_tkn__url url_frag = (Xob_css_tkn__url)frag; + byte[] src_url = url_frag.Src_url(); + src_url = Bry_.Replace(src_url, Byte_ascii.Space, Byte_ascii.Underline); // NOTE: must replace spaces with underlines else download will fail; EX:https://it.wikivoyage.org/w/index.php?title=MediaWiki:Container e Infobox.css&action=raw&ctype=text/css; DATE:2015-03-08 + int semic_pos = Bry_find_.Find_fwd(src, Byte_ascii.Semic, frag.Pos_end(), src_len); + return Xob_css_tkn__import.new_(tkn_bgn, semic_pos + 1, src_url, url_frag.Trg_url(), url_frag.Quote_byte()); + } + private static final byte[] Tkn_url_bry = Bry_.new_a7("url("); + public static final byte[] + Wikisource_dynimg_ttl = Bry_.new_a7("en.wikisource.org/w/index.php?title=MediaWiki:Dynimg.css") + , Wikisource_dynimg_find = Bry_.new_a7(".freedImg img[src*=\"wikipedia\"], .freedImg img[src*=\"wikisource\"], .freedImg img[src*=\"score\"], .freedImg img[src*=\"math\"] {") + , Wikisource_dynimg_repl = Bry_.new_a7(".freedImg img[src*=\"wikipedia\"], .freedImg img[src*=\"wikisource\"], /*XOWA:handle file:// paths which will have /commons.wikimedia.org/ but not /wikipedia/ */ .freedImg img[src*=\"wikimedia\"], .freedImg img[src*=\"score\"], .freedImg img[src*=\"math\"] {") + ; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser__import_tst.java b/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser__import_tst.java index a27517de8..94d950771 100644 --- a/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser__import_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser__import_tst.java @@ -13,3 +13,24 @@ 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.bldrs.css; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import org.junit.*; +public class Xob_css_parser__import_tst { + @Before public void init() {fxt.Clear();} private Xob_css_parser__import_fxt fxt = new Xob_css_parser__import_fxt(); + @Test public void Basic() {fxt.Test_parse_import (" @import url(//site/a.png)" , " @import url('site/a.png')");} + @Test public void Warn_eos() {fxt.Test_parse_warn (" @import" , " @import" , "EOS");} + @Test public void Warn_missing() {fxt.Test_parse_warn (" @import ('//site/a.png')" , " @import" , "missing");} // no "url(" + @Test public void Warn_invalid() {fxt.Test_parse_warn (" @import url('//site')" , " @import url('//site')" , "invalid");} // invalid +} +class Xob_css_parser__import_fxt extends Xob_css_parser__url_fxt { private Xob_css_parser__import import_parser; + @Override public void Clear() { + super.Clear(); + this.import_parser = new Xob_css_parser__import(url_parser); + } + @Override protected void Exec_parse_hook() { + this.cur_frag = import_parser.Parse(src_bry, src_bry.length, 0, 8); // 8=" @import".length + } + public void Test_parse_import(String src_str, String expd) { + Exec_parse(src_str, Xob_css_tkn__base.Tid_import, expd); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser__url.java b/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser__url.java index a27517de8..231d4c8ff 100644 --- a/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser__url.java +++ b/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser__url.java @@ -13,3 +13,44 @@ 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.bldrs.css; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +class Xob_css_parser__url { + private final byte[] site; + public Xob_css_parser__url(byte[] site) {this.site = site;} + public Xob_css_tkn__base Parse(byte[] src, int src_len, int tkn_bgn, int tkn_end) { // " url" + int bgn_pos = Bry_find_.Find_fwd_while_ws(src, tkn_end, src_len); // skip any ws after " url(" + if (bgn_pos == src_len) return Xob_css_tkn__warn.new_(tkn_bgn, tkn_end, "mirror.parser.url:EOS; bgn=~{0}", tkn_bgn); + byte end_byte = src[bgn_pos]; // note that first non-ws byte should determine end_byte + byte quote_byte = end_byte; + switch (end_byte) { + case Byte_ascii.Quote: case Byte_ascii.Apos: // quoted; increment position; EX: ' url("a.png")' + ++bgn_pos; + break; + default: // not quoted; end byte is ")"; EX: ' url(a.png)' + end_byte = Byte_ascii.Paren_end; + quote_byte = Byte_ascii.Null; + break; + } + int end_pos = Bry_find_.Find_fwd(src, end_byte, bgn_pos, src_len); + if (end_pos == Bry_find_.Not_found) // unclosed "url("; exit since nothing else will be found + return Xob_css_tkn__warn.new_(tkn_bgn, tkn_end, "mirror.parser.url:dangling; bgn=~{0} excerpt=~{1}", bgn_pos, String_.new_u8__by_len(src, tkn_bgn, tkn_bgn + 128)); + if (end_pos - bgn_pos == 0) // empty; "url()"; ignore + return Xob_css_tkn__warn.new_(tkn_bgn, tkn_end, "mirror.parser.url:empty; bgn=~{0} excerpt=~{1}", bgn_pos, String_.new_u8__by_len(src, tkn_bgn, tkn_bgn + 128)); + byte[] url_orig = Bry_.Mid(src, bgn_pos, end_pos); int url_orig_len = url_orig.length; + ++end_pos; // increment end_pos so rv will be after it; + if ( end_byte != Byte_ascii.Paren_end) { // end_byte is apos / quote + if ( end_pos < src_len + && src[end_pos] == Byte_ascii.Paren_end) + ++end_pos; + else + return Xob_css_tkn__warn.new_(tkn_bgn, end_pos, "mirror.parser.url:base64 dangling; bgn=~{0} excerpt=~{1}", bgn_pos, String_.new_u8(url_orig)); + } + if (Bry_.Has_at_bgn(url_orig, Bry_data_image)) // base64 + return Xob_css_tkn__base64.new_(tkn_bgn, end_pos); + byte[] src_url = Xob_url_fixer.Fix(site, url_orig, url_orig_len); + if (src_url == null) // could not convert + return Xob_css_tkn__warn.new_(tkn_bgn, end_pos, "mirror.parser.url:invalid url; bgn=~{0} excerpt=~{1}", tkn_bgn, String_.new_u8(url_orig)); + return Xob_css_tkn__url.new_(tkn_bgn, end_pos, src_url, quote_byte); + } + private static final byte[] Bry_data_image = Bry_.new_a7("data:image/"); +} diff --git a/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser__url_tst.java b/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser__url_tst.java index a27517de8..b7a60a6e4 100644 --- a/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser__url_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_parser__url_tst.java @@ -13,3 +13,46 @@ 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.bldrs.css; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import org.junit.*; +public class Xob_css_parser__url_tst { + @Before public void init() {fxt.Clear();} private Xob_css_parser__url_fxt fxt = new Xob_css_parser__url_fxt(); + @Test public void Quote_none() {fxt.Test_parse_url(" url(//site/A.png) b" , " url('site/A.png')");} + @Test public void Quote_apos() {fxt.Test_parse_url(" url('//site/A.png') b" , " url('site/A.png')");} + @Test public void Quote_quote() {fxt.Test_parse_url(" url(\"//site/A.png\") b" , " url(\"site/A.png\")");} + @Test public void Base64() {fxt.Test_parse_base64(" url('data:image/png;base64,BASE64DATA;ABC=') b", " url('data:image/png;base64,BASE64DATA;ABC=')");} + @Test public void Base64_dangling() {fxt.Test_parse_warn(" url('data:image/png;base64,BASE64DATA;ABC=' ", " url('data:image/png;base64,BASE64DATA;ABC='", "base64 dangling");} + @Test public void Warn_eos() {fxt.Test_parse_warn(" url(" , " url(" , "EOS");} + @Test public void Warn_dangling() {fxt.Test_parse_warn(" url(a" , " url(" , "dangling");} + @Test public void Warn_empty() {fxt.Test_parse_warn(" url()" , " url(" , "empty");} + @Test public void Warn_site() {fxt.Test_parse_warn(" url('//site')" , " url('//site')" , "invalid");} +} +class Xob_css_parser__url_fxt { + protected Xob_css_parser__url url_parser; private final Bry_bfr bfr = Bry_bfr_.New_w_size(32); + protected Xob_css_tkn__base cur_frag; protected byte[] src_bry; + @gplx.Virtual public void Clear() { + url_parser = new Xob_css_parser__url(Bry_.new_a7("site")); + } + protected void Exec_parse(String src_str, int expd_tid, String expd_str) { + this.src_bry = Bry_.new_u8(src_str); + this.Exec_parse_hook(); + cur_frag.Write(bfr, src_bry); + String actl_str = bfr.To_str_and_clear(); + Tfds.Eq(expd_tid, cur_frag.Tid(), "wrong tid; expd={0}, actl={1}", expd_tid, cur_frag.Tid()); + Tfds.Eq(expd_str, actl_str); + } + @gplx.Virtual protected void Exec_parse_hook() { + this.cur_frag = url_parser.Parse(src_bry, src_bry.length, 0, 5); // 5=" url(".length + } + public void Test_parse_url(String src_str, String expd) { + Exec_parse(src_str, Xob_css_tkn__base.Tid_url, expd); + } + public void Test_parse_base64(String src_str, String expd) { + Exec_parse(src_str, Xob_css_tkn__base.Tid_base64, expd); + } + public void Test_parse_warn(String src_str, String expd, String warn) { + Exec_parse(src_str, Xob_css_tkn__base.Tid_warn, expd); + Xob_css_tkn__warn sub_frag = (Xob_css_tkn__warn)cur_frag; + Tfds.Eq(true, String_.Has(sub_frag.Fail_msg(), warn)); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_tkn__base.java b/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_tkn__base.java index a27517de8..6df0a2757 100644 --- a/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_tkn__base.java +++ b/400_xowa/src/gplx/xowa/bldrs/css/Xob_css_tkn__base.java @@ -13,3 +13,104 @@ 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.bldrs.css; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.envs.*; +abstract class Xob_css_tkn__base { + public void Init(int tid, int pos_bgn, int pos_end) { + this.tid = tid; this.pos_bgn = pos_bgn; this.pos_end = pos_end; + } + public int Tid() {return tid;} protected int tid; + public int Pos_bgn() {return pos_bgn;} protected int pos_bgn; + public int Pos_end() {return pos_end;} protected int pos_end; + @gplx.Virtual public void Process(Xob_mirror_mgr mgr) {} + public abstract int Write(Bry_bfr bfr, byte[] src); + public static final int Tid_warn = 1, Tid_base64 = 2, Tid_url = 3, Tid_import = 4; +} +class Xob_css_tkn__warn extends Xob_css_tkn__base { + public String Fail_msg() {return fail_msg;} private String fail_msg; + @Override public void Process(Xob_mirror_mgr mgr) { + mgr.Usr_dlg().Warn_many("", "", fail_msg); + } + @Override public int Write(Bry_bfr bfr, byte[] src) { + bfr.Add_mid(src, pos_bgn, pos_end); + return pos_end; + } + public static Xob_css_tkn__warn new_(int pos_bgn, int pos_end, String fmt, Object... fmt_args) { + Xob_css_tkn__warn rv = new Xob_css_tkn__warn(); + rv.Init(Tid_warn, pos_bgn, pos_end); + rv.fail_msg = String_.Format(fmt, fmt_args); + return rv; + } +} +class Xob_css_tkn__base64 extends Xob_css_tkn__base { + @Override public int Write(Bry_bfr bfr, byte[] src) { + bfr.Add_mid(src, pos_bgn, pos_end); + return pos_end; + } + public static Xob_css_tkn__base64 new_(int pos_bgn, int pos_end) { + Xob_css_tkn__base64 rv = new Xob_css_tkn__base64(); + rv.Init(Tid_base64, pos_bgn, pos_end); + return rv; + } +} +class Xob_css_tkn__url extends Xob_css_tkn__base { + public byte Quote_byte() {return quote_byte;} private byte quote_byte; + public byte[] Src_url() {return src_url;} private byte[] src_url; + public byte[] Trg_url() {return trg_url;} private byte[] trg_url; + @Override public void Process(Xob_mirror_mgr mgr) { + mgr.File_hash().Add_if_dupe_use_1st(src_url, new Xobc_download_itm(Xobc_download_itm.Tid_file, String_.new_u8(src_url), trg_url)); + } + @Override public int Write(Bry_bfr bfr, byte[] src) { + byte quote = quote_byte; if (quote == Byte_ascii.Null) quote = Byte_ascii.Apos; + bfr.Add_str_a7(" url("); // EX: ' url(' + bfr.Add_byte(quote).Add(trg_url).Add_byte(quote); // EX: '"a.png"' + bfr.Add_byte(Byte_ascii.Paren_end); // EX: ')' + return pos_end; + } + public static Xob_css_tkn__url new_(int pos_bgn, int pos_end, byte[] src_url, byte quote_byte) { + Xob_css_tkn__url rv = new Xob_css_tkn__url(); + rv.Init(Tid_url, pos_bgn, pos_end); + rv.src_url = src_url; rv.trg_url = To_fsys(src_url); rv.quote_byte = quote_byte; + return rv; + } + public static byte[] To_fsys(byte[] src) { + if (!Op_sys.Cur().Tid_is_wnt()) return src; + src = Bry_.Copy(src); // NOTE: must call ByteAry.Copy else url_actl will change *inside* bry + int len = src.length; + for (int i = 0; i < len; ++i) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Slash: + case Byte_ascii.Backslash: + break; + case Byte_ascii.Lt: case Byte_ascii.Gt: case Byte_ascii.Colon: case Byte_ascii.Pipe: case Byte_ascii.Question: case Byte_ascii.Star: case Byte_ascii.Quote: + src[i] = Byte_ascii.Underline; + break; + default: + break; + } + } + return src; + } +} +class Xob_css_tkn__import extends Xob_css_tkn__base { + public byte Quote_byte() {return quote_byte;} private byte quote_byte; + public byte[] Src_url() {return src_url;} private byte[] src_url; + public byte[] Trg_url() {return trg_url;} private byte[] trg_url; + @Override public void Process(Xob_mirror_mgr mgr) { + mgr.Code_add(src_url); + } + @Override public int Write(Bry_bfr bfr, byte[] src) { + byte quote = quote_byte; if (quote == Byte_ascii.Null) quote = Byte_ascii.Apos; + bfr.Add_str_a7(" @import url("); // EX: ' @import url(' + bfr.Add_byte(quote).Add(trg_url).Add_byte(quote); // EX: '"a.png"' + bfr.Add_byte(Byte_ascii.Paren_end); // EX: ')' + return pos_end; + } + public static Xob_css_tkn__import new_(int pos_bgn, int pos_end, byte[] src_url, byte[] trg_url, byte quote_byte) { + Xob_css_tkn__import rv = new Xob_css_tkn__import(); + rv.Init(Tid_import, pos_bgn, pos_end); + rv.src_url = src_url; rv.trg_url = trg_url; rv.quote_byte = quote_byte; + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/css/Xob_mirror_mgr.java b/400_xowa/src/gplx/xowa/bldrs/css/Xob_mirror_mgr.java index a27517de8..cd6c1ac4f 100644 --- a/400_xowa/src/gplx/xowa/bldrs/css/Xob_mirror_mgr.java +++ b/400_xowa/src/gplx/xowa/bldrs/css/Xob_mirror_mgr.java @@ -13,3 +13,45 @@ 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.bldrs.css; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.ios.*; import gplx.xowa.files.downloads.*; +public class Xob_mirror_mgr { + private final Xof_download_wkr download_wkr; private final Xob_css_parser css_parser; + private final byte[] page_url; private final Io_url fsys_root; + public Xob_mirror_mgr(Gfo_usr_dlg usr_dlg, Xof_download_wkr download_wkr, byte[] site_url, byte[] page_url, Io_url fsys_root) { + this.usr_dlg = usr_dlg; this.download_wkr = download_wkr; + this.site_url = site_url; this.page_url = page_url; this.fsys_root = fsys_root; + this.css_parser = new Xob_css_parser(this); + } + public Gfo_usr_dlg Usr_dlg() {return usr_dlg;} private final Gfo_usr_dlg usr_dlg; + public byte[] Site_url() {return site_url;} private final byte[] site_url; + public void Code_add(byte[] src_url) { + byte[] trg_url = Xob_css_tkn__url.To_fsys(src_url); + code_hash.Add_if_dupe_use_1st(src_url, new Xobc_download_itm(Xobc_download_itm.Tid_css, String_.new_u8(src_url), trg_url)); + } + public Ordered_hash Code_hash() {return code_hash;} private final Ordered_hash code_hash = Ordered_hash_.New(); + public Ordered_hash File_hash() {return file_hash;} private final Ordered_hash file_hash = Ordered_hash_.New(); + public void Exec() { + usr_dlg.Plog_many("", "", "html_mirror:download.root_page; url=~{0}", page_url); + IoEngine_xrg_downloadFil download_xrg = download_wkr.Download_xrg(); + css_parser.Parse(download_xrg.Exec_as_bry(String_.new_u8(page_url))); + while (true) { + Xobc_download_itm[] code_ary = (Xobc_download_itm[])code_hash.To_ary_and_clear(Xobc_download_itm.class); + int code_ary_len = code_ary.length; + if (code_ary_len == 0) break; + for (int i = 0; i < code_ary_len; ++i) { + Xobc_download_itm code = code_ary[i]; + byte[] code_src = download_xrg.Exec_as_bry(code.Http_str()); + Io_mgr.Instance.SaveFilBry(fsys_root.Gen_sub_path_for_os(String_.new_u8(code.Fsys_url())), code_src); + css_parser.Parse(code_src); + } + } + Xobc_download_itm[] file_ary = (Xobc_download_itm[])file_hash.To_ary_and_clear(Xobc_download_itm.class); + int file_ary_len = file_ary.length; + for (int i = 0; i < file_ary_len; ++i) { + Xobc_download_itm file = file_ary[i]; + download_xrg.Init(file.Http_str(), Io_url_.new_fil_(fsys_root.Gen_sub_path_for_os(String_.new_u8(file.Fsys_url())))); + download_xrg.Exec(); + } + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/css/Xob_mirror_mgr_tst.java b/400_xowa/src/gplx/xowa/bldrs/css/Xob_mirror_mgr_tst.java index a27517de8..b67ef17f4 100644 --- a/400_xowa/src/gplx/xowa/bldrs/css/Xob_mirror_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/css/Xob_mirror_mgr_tst.java @@ -13,3 +13,49 @@ 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.bldrs.css; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import org.junit.*; +import gplx.xowa.files.downloads.*; +public class Xob_mirror_mgr_tst { + @Before public void init() {fxt.Clear();} private Xob_mirror_mgr_fxt fxt = new Xob_mirror_mgr_fxt(); + @Test public void Download_1() { + fxt.Fsys().Init_fil("mem/http/enwiki/file/a.png"); + fxt.Fsys().Init_fil("mem/http/enwiki/wiki/Main_Page", "url('//enwiki/wiki/a.png')"); +// fxt.Test_css(); +// fxt.Fsys().Test_fil("url('//enwiki/wiki/a.png')", "url('enwiki/wiki/a.png')"); // remove "//" +// fxt.Fsys().Test_fil("mem/fsys/enwiki/file/a.png"); + } +} +class Xob_mirror_mgr_fxt { +// private Xob_mirror_mgr mirror_mgr; + public Io_fsys_fxt Fsys() {return fsys;} private final Io_fsys_fxt fsys = new Io_fsys_fxt(); + public void Clear() { + fsys.Clear(); +// mirror_mgr = new Xob_mirror_mgr(Gfo_usr_dlg_.Noop, new Xof_download_wkr_test(), Bry_.new_a7("mem/http/enwiki"), Bry_.new_a7("mem/http/enwiki/wiki/Main_Page"), Io_url_.new_dir_("mem/fsys")); + } + public void Test_css(String raw, String expd) { +// byte[] raw_bry = Bry_.new_u8(raw); +// mirror_mgr.Exec(); + } +} +class Io_fsys_fxt { + public void Clear() { + Io_mgr.Instance.InitEngine_mem(); + } + public void Init_fil(String url_str) { + Io_url url = Io_url_.new_fil_(url_str); + Init_fil(url, url.NameAndExt()); + } + public void Init_fil(String url_str, String text) {Init_fil(Io_url_.new_fil_(url_str), text);} + public void Init_fil(Io_url url, String text) { + Io_mgr.Instance.SaveFilStr(url, text); + } + public void Test_fil(String url_str) { + Io_url url = Io_url_.new_fil_(url_str); + Test_fil(url, url.NameAndExt()); + } + public void Test_fil(String url, String expd) {Test_fil(Io_url_.new_fil_(url), expd);} + public void Test_fil(Io_url url, String expd) { + Tfds.Eq_str_lines(expd, Io_mgr.Instance.LoadFilStr(url)); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/css/Xob_url_fixer.java b/400_xowa/src/gplx/xowa/bldrs/css/Xob_url_fixer.java index a27517de8..752bbea43 100644 --- a/400_xowa/src/gplx/xowa/bldrs/css/Xob_url_fixer.java +++ b/400_xowa/src/gplx/xowa/bldrs/css/Xob_url_fixer.java @@ -13,3 +13,85 @@ 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.bldrs.css; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.primitives.*; import gplx.core.btries.*; +class Xob_url_fixer { + public static byte[] Fix(byte[] site, byte[] src, int src_len) { // return "site/img.png" if "//site/img.png" or "http://site/img.png"; also, handle "img.png?key=val" + int bgn = 0; int bgn_tkn_tid = 0; + Object o = Xob_url_fixer_tkn.Bgn_trie().Match_bgn(src, bgn, src_len); + if (o != null) { + Xob_url_fixer_tkn tkn = (Xob_url_fixer_tkn)o; + bgn_tkn_tid = tkn.Tid(); + switch (bgn_tkn_tid) { + case Xob_url_fixer_tkn.Tid_bgn_slash_2: + case Xob_url_fixer_tkn.Tid_bgn_http: + case Xob_url_fixer_tkn.Tid_bgn_https: + bgn = tkn.Raw_len(); // remove "//", "http://", "https://" + break; + case Xob_url_fixer_tkn.Tid_bgn_slash_1: // convert "/a" to "site/a" + src = Bry_.Add(site, src); + src_len = src.length; + break; + } + } + int pos = bgn, end = src_len; boolean no_slashes = true; + Btrie_slim_mgr mid_trie = Xob_url_fixer_tkn.Mid_trie(); + int[] seg_ary = new int[gplx.xowa.xtns.pfuncs.ttls.Pfunc_rel2abs.Ttl_max]; + while (pos < src_len) { + byte b = src[pos]; + o = mid_trie.Match_bgn_w_byte(b, src, pos, src_len); + if (o != null) { + Xob_url_fixer_tkn tkn = (Xob_url_fixer_tkn)o; + switch (tkn.Tid()) { + case Xob_url_fixer_tkn.Tid_mid_slash: if (no_slashes) no_slashes = false; break; + case Xob_url_fixer_tkn.Tid_mid_question: end = pos; pos = src_len; break; + case Xob_url_fixer_tkn.Tid_mid_rel_1: + case Xob_url_fixer_tkn.Tid_mid_rel_2: + Bry_bfr tmp_bfr = Bry_bfr_.New_w_size(src_len); + byte[] to_rel_root = Bry_.Mid(src, bgn, pos); + byte[] to_rel_qry = Bry_.Mid(src, pos, src_len); + src = gplx.xowa.xtns.pfuncs.ttls.Pfunc_rel2abs.Rel2abs(tmp_bfr, seg_ary, to_rel_qry, to_rel_root, Int_obj_ref.New_neg1()); + bgn = pos = 0; + end = src_len = src.length; + no_slashes = true; + break; + } + } + ++pos; + } + if (no_slashes) return null; // invalid; EX: "//site" + return Bry_.Mid(src, bgn, end); + } +} +class Xob_url_fixer_tkn { + public Xob_url_fixer_tkn(int tid, byte[] raw) {this.tid = tid; this.raw = raw; this.raw_len = raw.length;} + public int Tid() {return tid;} private int tid; + public byte[] Raw() {return raw;} private byte[] raw; + public int Raw_len() {return raw_len;} private int raw_len; + public static Xob_url_fixer_tkn new_(int tid, String raw) {return new Xob_url_fixer_tkn(tid, Bry_.new_u8(raw));} + private static void trie_add(Btrie_slim_mgr trie, int tid, String s) {trie.Add_obj(s, new_(tid, s));} + public static final int Tid_bgn_slash_1 = 1, Tid_bgn_slash_2 = 2, Tid_bgn_http = 3, Tid_bgn_https = 4; + private static Btrie_slim_mgr bgn_trie; + public static Btrie_slim_mgr Bgn_trie() { + if (bgn_trie == null) { + bgn_trie = Btrie_slim_mgr.ci_a7(); + trie_add(bgn_trie, Tid_bgn_slash_1 , "/"); + trie_add(bgn_trie, Tid_bgn_slash_2 , "//"); + trie_add(bgn_trie, Tid_bgn_http , "http://"); + trie_add(bgn_trie, Tid_bgn_https , "https://"); + } + return bgn_trie; + } + public static final int Tid_mid_rel_1 = 1, Tid_mid_rel_2 = 2, Tid_mid_slash = 3, Tid_mid_question = 4; + private static Btrie_slim_mgr mid_trie; + public static Btrie_slim_mgr Mid_trie() { + if (mid_trie == null) { + mid_trie = Btrie_slim_mgr.ci_a7(); + trie_add(mid_trie, Tid_mid_rel_1 , "/../"); + trie_add(mid_trie, Tid_mid_rel_2 , "/./"); + trie_add(mid_trie, Tid_mid_slash , "/"); + trie_add(mid_trie, Tid_mid_question , "?"); + } + return mid_trie; + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/css/Xob_url_fixer_tst.java b/400_xowa/src/gplx/xowa/bldrs/css/Xob_url_fixer_tst.java index a27517de8..c1b73238c 100644 --- a/400_xowa/src/gplx/xowa/bldrs/css/Xob_url_fixer_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/css/Xob_url_fixer_tst.java @@ -13,3 +13,28 @@ 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.bldrs.css; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import org.junit.*; +public class Xob_url_fixer_tst { + @Before public void init() {fxt.Clear();} private Xob_url_fixer_fxt fxt = new Xob_url_fixer_fxt(); + @Test public void Slash2() {fxt.Test_fix("//site/a.png" , "site/a.png");} + @Test public void Http() {fxt.Test_fix("http://site/a.png" , "site/a.png");} + @Test public void Https() {fxt.Test_fix("https://site/a.png" , "site/a.png");} + @Test public void Qarg() {fxt.Test_fix("//site/a.png?key=val" , "site/a.png");} + @Test public void Qarg_dir() {fxt.Test_fix("//site/a/b/c.png?key=val" , "site/a/b/c.png");} + @Test public void Root() {fxt.Test_fix("/a/b.png" , "site/a/b.png");} // EX:/static/images/project-logos/wikivoyage.png; DATE:2015-05-09 + @Test public void Rel_dot2() {fxt.Test_fix("//site/a/../b/c.png" , "site/b/c.png");} // DATE:2015-05-09 + @Test public void Rel_dot2_mult() {fxt.Test_fix("//site/a/../b/../c/d.png" , "site/c/d.png");} // DATE:2015-05-09 + @Test public void Rel_dot1() {fxt.Test_fix("//site/a/./b/c.png" , "site/a/b/c.png");} // DATE:2015-05-09 + @Test public void Site_only() {fxt.Test_fix("//site" , null);} +} +class Xob_url_fixer_fxt { + public void Site_(String v) {site_bry = Bry_.new_u8(v);} private byte[] site_bry; + public void Clear() { + this.Site_("site"); + } + public void Test_fix(String raw, String expd) { + byte[] raw_bry = Bry_.new_u8(raw); + Tfds.Eq(expd, String_.new_u8(Xob_url_fixer.Fix(site_bry, raw_bry, raw_bry.length))); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/css/Xobc_download_itm.java b/400_xowa/src/gplx/xowa/bldrs/css/Xobc_download_itm.java index a27517de8..5a120566a 100644 --- a/400_xowa/src/gplx/xowa/bldrs/css/Xobc_download_itm.java +++ b/400_xowa/src/gplx/xowa/bldrs/css/Xobc_download_itm.java @@ -13,3 +13,11 @@ 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.bldrs.css; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +class Xobc_download_itm { + public Xobc_download_itm(int tid, String http_str, byte[] fsys_url) {this.tid = tid; this.http_str = http_str; this.fsys_url = fsys_url;} + public int Tid() {return tid;} private final int tid; + public String Http_str() {return http_str;} private final String http_str; + public byte[] Fsys_url() {return fsys_url;} private final byte[] fsys_url; + public static final int Tid_file = 1, Tid_html = 2, Tid_css = 3; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/filters/core/Xob_ttl_filter_mgr.java b/400_xowa/src/gplx/xowa/bldrs/filters/core/Xob_ttl_filter_mgr.java index a27517de8..c1e9309a1 100644 --- a/400_xowa/src/gplx/xowa/bldrs/filters/core/Xob_ttl_filter_mgr.java +++ b/400_xowa/src/gplx/xowa/bldrs/filters/core/Xob_ttl_filter_mgr.java @@ -13,3 +13,29 @@ 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.bldrs.filters.core; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.filters.*; +import gplx.xowa.wikis.ttls.*; +public class Xob_ttl_filter_mgr { + private boolean exclude_is_empty = true, include_is_empty = true; + private final Xob_ttl_filter_mgr_srl srl = new Xob_ttl_filter_mgr_srl(); + private Hash_adp_bry exclude_hash = Hash_adp_bry.cs(), include_hash = Hash_adp_bry.cs(); + public void Clear() { + exclude_hash.Clear(); + include_hash.Clear(); + exclude_is_empty = include_is_empty = true; + } + public boolean Match_include(byte[] src) {return include_is_empty ? false : include_hash.Has(src);} + public boolean Match_exclude(byte[] src) {return exclude_is_empty ? false : exclude_hash.Has(src);} + public void Load(boolean exclude, Io_url url) { + byte[] src = Io_mgr.Instance.LoadFilBry_loose(url); + if (Bry_.Len_gt_0(src)) Load(exclude, src); + } + public void Load(boolean exclude, byte[] src) { + Hash_adp_bry hash = exclude ? exclude_hash : include_hash; + srl.Init(hash).Load_by_bry(src); + if (exclude) + exclude_is_empty = exclude_hash.Count() == 0; + else + include_is_empty = include_hash.Count() == 0; + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/filters/core/Xob_ttl_filter_mgr_srl.java b/400_xowa/src/gplx/xowa/bldrs/filters/core/Xob_ttl_filter_mgr_srl.java index a27517de8..dcb72eadb 100644 --- a/400_xowa/src/gplx/xowa/bldrs/filters/core/Xob_ttl_filter_mgr_srl.java +++ b/400_xowa/src/gplx/xowa/bldrs/filters/core/Xob_ttl_filter_mgr_srl.java @@ -13,3 +13,25 @@ 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.bldrs.filters.core; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.filters.*; +import gplx.langs.dsvs.*; +class Xob_ttl_filter_mgr_srl extends Dsv_wkr_base { + private byte[] ttl; private Hash_adp_bry hash; + public Xob_ttl_filter_mgr_srl Init(Hash_adp_bry hash) {this.hash = hash; return this;} + @Override public Dsv_fld_parser[] Fld_parsers() {return new Dsv_fld_parser[] {Dsv_fld_parser_.Line_parser__comment_is_pipe};} + @Override public boolean Write_bry(Dsv_tbl_parser parser, int fld_idx, byte[] src, int bgn, int end) { + switch (fld_idx) { + case 0: + if (end - bgn == 0) return true; // ignore blank lines + if (src[bgn] == Byte_ascii.Pipe) return true; // ignore lines starting with pipe; EX: "| some comment" + ttl = Bry_.Mid(src, bgn, end); + return true; + default: return false; + } + } + @Override public void Commit_itm(Dsv_tbl_parser parser, int pos) { + if (ttl == null) return; + hash.Add(ttl, ttl); + ttl = null; + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/filters/core/Xob_ttl_filter_mgr_srl_tst.java b/400_xowa/src/gplx/xowa/bldrs/filters/core/Xob_ttl_filter_mgr_srl_tst.java index a27517de8..ce45da9cb 100644 --- a/400_xowa/src/gplx/xowa/bldrs/filters/core/Xob_ttl_filter_mgr_srl_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/filters/core/Xob_ttl_filter_mgr_srl_tst.java @@ -13,3 +13,40 @@ 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.bldrs.filters.core; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.filters.*; +import org.junit.*; +public class Xob_ttl_filter_mgr_srl_tst { + @Before public void init() {fxt.Clear();} private final Xob_ttl_filter_mgr_srl_fxt fxt = new Xob_ttl_filter_mgr_srl_fxt(); + @Test public void One() {fxt.Test_parse("a" , 1, "a");} + @Test public void Two() {fxt.Test_parse("a\nb" , 2, "a", "b");} + @Test public void Comment() {fxt.Test_parse("|x" , 0);} + @Test public void Comment_many() {fxt.Test_parse("|x||" , 0);} + @Test public void Blank() {fxt.Test_parse("\n" , 0);} + @Test public void Mix() { + fxt.Test_parse(String_.Concat_lines_nl_skip_last + ( "|comment 1" + , "a" + , "" + , "|comment 2" + , "b" + ) + , 2, "a", "b") + ;} +} +class Xob_ttl_filter_mgr_srl_fxt { + private final Xob_ttl_filter_mgr_srl mgr = new Xob_ttl_filter_mgr_srl(); + private final Hash_adp_bry hash = Hash_adp_bry.cs(); + public void Clear() { + hash.Clear(); + } + public void Test_parse(String src, int expd_count, String... expd_itms) { + mgr.Init(hash); + mgr.Load_by_bry(Bry_.new_u8(src)); + Tfds.Eq(expd_count, hash.Count()); + int expd_len = expd_itms.length; + for (int i = 0; i < expd_len; ++i) { + String expd_itm = expd_itms[i]; + Tfds.Eq_true(hash.Has(Bry_.new_u8(expd_itm))); + } + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/filters/core/Xob_ttl_filter_mgr_tst.java b/400_xowa/src/gplx/xowa/bldrs/filters/core/Xob_ttl_filter_mgr_tst.java index a27517de8..742408b2f 100644 --- a/400_xowa/src/gplx/xowa/bldrs/filters/core/Xob_ttl_filter_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/filters/core/Xob_ttl_filter_mgr_tst.java @@ -13,3 +13,37 @@ 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.bldrs.filters.core; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.filters.*; +import org.junit.*; +public class Xob_ttl_filter_mgr_tst { + @Before public void init() {fxt.Clear();} private final Xob_ttl_filter_mgr_fxt fxt = new Xob_ttl_filter_mgr_fxt(); + @Test public void One() { + fxt.Init_load_exclude("A"); + fxt.Init_load_include("B"); + fxt.Test_match_exclude_y("A"); + fxt.Test_match_exclude_n("B", "C"); + fxt.Test_match_include_y("B"); + fxt.Test_match_include_n("A", "C"); + } +} +class Xob_ttl_filter_mgr_fxt { + private final Xob_ttl_filter_mgr mgr = new Xob_ttl_filter_mgr(); + public void Clear() { + mgr.Clear(); + } + public void Init_load_exclude(String itm) {mgr.Load(Bool_.Y, Bry_.new_u8(itm));} + public void Init_load_include(String itm) {mgr.Load(Bool_.N, Bry_.new_u8(itm));} + public void Test_match_exclude_y(String... itms) {Test_match(Bool_.Y, Bool_.Y, itms);} + public void Test_match_exclude_n(String... itms) {Test_match(Bool_.Y, Bool_.N, itms);} + public void Test_match_include_y(String... itms) {Test_match(Bool_.N, Bool_.Y, itms);} + public void Test_match_include_n(String... itms) {Test_match(Bool_.N, Bool_.N, itms);} + private void Test_match(boolean exclude, boolean expd, String... itms) { + for (String itm : itms) { + byte[] itm_bry = Bry_.new_u8(itm); + if (exclude) + Tfds.Eq(expd, mgr.Match_exclude(itm_bry), itm); + else + Tfds.Eq(expd, mgr.Match_include(itm_bry), itm); + } + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Crt__match_base.java b/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Crt__match_base.java index a27517de8..64dc16aaa 100644 --- a/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Crt__match_base.java +++ b/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Crt__match_base.java @@ -13,3 +13,33 @@ 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.bldrs.filters.dansguardians; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.filters.*; +import gplx.core.criterias.*; +class Crt__match_exact implements Criteria { + public Crt__match_exact(boolean negated, byte[][] ary) {this.negated = negated; Val_as_bry_ary_(ary);} + public byte Tid() {return Tid_match_exact;} + public String To_str_name() {return "MATCH_EXACT";} + public boolean Matches(Object comp_obj) { + if (ary_len == 0) return false; // empty array never matches + byte[] comp = (byte[])comp_obj; + boolean rv = false; + for (int i = 0; i < ary_len; i++) { + byte[] val = ary[i]; + if (Bry_.Eq(val, comp)) { + rv = true; + break; + } + } + return negated ? !rv : rv; + } + public boolean Negated() {return negated;} private boolean negated; + public byte[][] Val_as_bry_ary() {return ary;} protected byte[][] ary; protected int ary_len; + protected void Val_as_bry_ary_(byte[][] v) { + this.ary = v; + ary_len = v.length; + } + public void Val_as_obj_(Object v) {Val_as_bry_ary_((byte[][])v);} + public void Val_from_args(Hash_adp args) {throw Err_.new_unimplemented();} + public String To_str() {return String_.Concat_any(this.To_str_name(), " ", String_.Ary(ary));} + public byte Tid_match_exact = 12; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_file.java b/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_file.java index a27517de8..391965e5e 100644 --- a/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_file.java +++ b/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_file.java @@ -13,3 +13,71 @@ 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.bldrs.filters.dansguardians; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.filters.*; +import gplx.core.primitives.*; +class Dg_file { + public Dg_file(int id, String rel_path, Dg_rule[] lines) {this.id = id; this.rel_path = rel_path; this.lines = lines;} + public int Id() {return id;} private final int id; + public String Rel_path() {return rel_path;} private final String rel_path; // EX: goodphrases/weighted_general + public Dg_rule[] Lines() {return lines;} private final Dg_rule[] lines; +} +class Dg_rule {// EX: < wikipedia ><-30> + private final Hash_adp_bry word_idx_hash = Hash_adp_bry.cs(); + public Dg_rule(int file_id, int id, int idx, int tid, byte[] key, int score, Dg_word[] words) { + this.file_id = file_id; + this.id = id; this.idx = idx; this.tid = tid; this.key = key; this.score = score; this.words = words; + if (words != null) { // static rules will have null byte[][] + int words_len = words.length; + for (int i = 0; i < words_len; ++i) { + Dg_word word = words[i]; + word_idx_hash.Add_bry_obj(word.Raw(), Int_obj_ref.New(i)); + } + } + } + public int File_id() {return file_id;} private final int file_id; + public int Id() {return id;} private final int id; + public int Idx() {return idx;} private final int idx; + public int Tid() {return tid;} private final int tid; + public byte[] Key() {return key;} private final byte[] key; + public Dg_word[] Words() {return words;} private final Dg_word[] words; + public Hash_adp_bry Word_idx_hash() {return word_idx_hash;} + public int Score() {return score;} private final int score; + public static final int + Tid_rule = 0 + , Tid_comment = 1 + , Tid_blank = 3 + , Tid_invalid = 4 + ; + public static final Dg_rule + Itm_comment = new Dg_rule(-1, -1, -1, Tid_comment, null, -1, null) + , Itm_blank = new Dg_rule(-1, -1, -1, Tid_blank, null, -1, null) + , Itm_invalid = new Dg_rule(-1, -1, -1, Tid_invalid, null, -1, null) + ; + public static final int Score_banned = 0; +} +class Dg_word { + public Dg_word(byte[] raw) {this.raw = raw;} + public byte[] Raw() {return raw;} private final byte[] raw; + public static String Ary_concat(Dg_word[] ary, Bry_bfr bfr, byte dlm) { + if (ary == null) return String_.Empty; + int len = ary.length; + if (len == 0) return String_.Empty; + bfr.Add_byte_apos(); + for (int i = 0; i < len; ++i) { + Dg_word itm = ary[i]; + if (i != 0) bfr.Add_byte(dlm); + bfr.Add(itm.Raw()); + } + bfr.Add_byte_apos(); + return bfr.To_str_and_clear(); + } + public static Dg_word[] Ary_new_by_str_ary(String[] ary) { + int ary_len = ary.length; + Dg_word[] rv = new Dg_word[ary_len]; + for (int i = 0; i < ary_len; ++i) { + String raw = ary[i]; + rv[i] = new Dg_word(Bry_.new_u8(raw)); + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_log_mgr.java b/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_log_mgr.java index a27517de8..72f180bd1 100644 --- a/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_log_mgr.java +++ b/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_log_mgr.java @@ -13,3 +13,161 @@ 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.bldrs.filters.dansguardians; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.filters.*; +import gplx.dbs.*; +class Dg_log_mgr { + private Db_conn conn; + private final Dg_file_tbl tbl_file = new Dg_file_tbl(); + private final Dg_rule_tbl tbl_rule = new Dg_rule_tbl(); + private final Dg_page_score_tbl tbl_page_score = new Dg_page_score_tbl(); + private final Dg_page_rule_tbl tbl_page_rule = new Dg_page_rule_tbl(); + private final Bry_bfr tmp_bfr = Bry_bfr_.Reset(16); + public void Init(Io_url db_url) { + Db_conn_bldr_data conn_data = Db_conn_bldr.Instance.Get_or_new(db_url); + conn = conn_data.Conn(); boolean created = conn_data.Created(); + tbl_file.Conn_(conn, created); + tbl_rule.Conn_(conn, created); + tbl_page_score.Conn_(conn, created); + tbl_page_rule.Conn_(conn, created); + conn.Txn_bgn("dansguardian"); + } + public void Insert_file(Dg_file file) {tbl_file.Insert(file.Id(), file.Rel_path(), file.Lines().length);} + public void Insert_rule(Dg_rule rule) {tbl_rule.Insert(rule.File_id(), rule.Id(), rule.Idx(), rule.Score(), Dg_word.Ary_concat(rule.Words(), tmp_bfr, Byte_ascii.Tilde));} + public void Insert_page_score(int log_tid, int page_id, int page_ns, byte[] page_ttl, int page_len, int page_score, int page_rule_count, int clude_type) { + tbl_page_score.Insert(log_tid, page_id, page_ns, page_ttl, page_len, page_score, page_rule_count, clude_type); + } + public void Insert_page_rule(int log_tid, int page_id, int rule_id, int rule_score_total) {tbl_page_rule.Insert(log_tid, page_id, rule_id, rule_score_total);} + public void Commit() {conn.Txn_sav();} + public void Rls() {conn.Txn_end();} +} +class Dg_file_tbl { + private String tbl_name = "dg_file"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private String fld_file_id, fld_file_path, fld_rule_count; + private Db_conn conn; private Db_stmt stmt_insert; + public void Conn_(Db_conn new_conn, boolean created) { + this.conn = new_conn; flds.Clear(); + fld_file_id = flds.Add_int("file_id"); + fld_file_path = flds.Add_str("file_path", 512); + fld_rule_count = flds.Add_int("rule_count"); + if (created) { + Dbmeta_tbl_itm meta = Dbmeta_tbl_itm.New(tbl_name, flds + , Dbmeta_idx_itm.new_unique_by_tbl(tbl_name, "file_id", fld_file_id) + ); + conn.Meta_tbl_create(meta); + } + stmt_insert = null; + } + public void Insert(int file_id, String file_path, int rule_count) { + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Clear() + .Val_int(fld_file_id , file_id) + .Val_str(fld_file_path , file_path) + .Val_int(fld_rule_count , rule_count) + .Exec_insert(); + } +} +class Dg_rule_tbl implements Rls_able { + private String tbl_name = "dg_rule"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private String fld_file_id, fld_rule_id, fld_rule_idx, fld_rule_score, fld_rule_text; + private Db_conn conn; private Db_stmt stmt_insert; + public void Conn_(Db_conn new_conn, boolean created) { + this.conn = new_conn; flds.Clear(); + fld_file_id = flds.Add_int("file_id"); + fld_rule_id = flds.Add_int("rule_id"); + fld_rule_idx = flds.Add_int("rule_idx"); + fld_rule_score = flds.Add_int("rule_score"); + fld_rule_text = flds.Add_str("rule_text", 1024); + if (created) { + Dbmeta_tbl_itm meta = Dbmeta_tbl_itm.New(tbl_name, flds + , Dbmeta_idx_itm.new_unique_by_tbl(tbl_name, "pkey", fld_rule_id) + ); + conn.Meta_tbl_create(meta); + } + conn.Rls_reg(this); + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + } + public void Insert(int file_id, int rule_id, int rule_idx, int rule_score, String rule_text) { + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Clear() + .Val_int(fld_file_id , file_id) + .Val_int(fld_rule_id , rule_id) + .Val_int(fld_rule_idx , rule_idx) + .Val_int(fld_rule_score , rule_score) + .Val_str(fld_rule_text , rule_text) + .Exec_insert(); + } +} +class Dg_page_score_tbl implements Rls_able { + private String tbl_name = "dg_page_score"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private String fld_log_tid, fld_page_id, fld_page_ns, fld_page_ttl, fld_page_len, fld_page_score, fld_page_rule_count, fld_clude_type; + private Db_conn conn; private Db_stmt stmt_insert; + public void Conn_(Db_conn new_conn, boolean created) { + this.conn = new_conn; flds.Clear(); + fld_log_tid = flds.Add_int("log_tid"); // title or text + fld_page_id = flds.Add_int("page_id"); + fld_page_ns = flds.Add_int("page_ns"); + fld_page_ttl = flds.Add_int("page_ttl"); + fld_page_len = flds.Add_int("page_len"); + fld_page_score = flds.Add_int("page_score"); + fld_page_rule_count = flds.Add_int("page_rule_count"); + fld_clude_type = flds.Add_int("page_clude_type"); + if (created) { + Dbmeta_tbl_itm meta = Dbmeta_tbl_itm.New(tbl_name, flds + , Dbmeta_idx_itm.new_unique_by_tbl(tbl_name, "pkey", fld_log_tid, fld_page_id) + ); + conn.Meta_tbl_create(meta); + } + stmt_insert = null; + conn.Rls_reg(this); + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + } + public void Insert(int log_tid, int page_id, int page_ns, byte[] page_ttl, int page_len, int page_score, int page_rule_count, int clude_type) { + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Clear() + .Val_int(fld_log_tid , log_tid) + .Val_int(fld_page_id , page_id) + .Val_int(fld_page_ns , page_ns) + .Val_bry_as_str(fld_page_ttl, page_ttl) + .Val_int(fld_page_len , page_len) + .Val_int(fld_page_score , page_score) + .Val_int(fld_page_rule_count, page_rule_count) + .Val_int(fld_clude_type , clude_type) + .Exec_insert(); + } +} +class Dg_page_rule_tbl implements Rls_able { + private String tbl_name = "dg_page_rule"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private String fld_log_tid, fld_page_id, fld_rule_id, fld_rule_score_total; + private Db_conn conn; private Db_stmt stmt_insert; + public void Conn_(Db_conn new_conn, boolean created) { + this.conn = new_conn; flds.Clear(); + fld_log_tid = flds.Add_int("log_tid"); // title or text + fld_page_id = flds.Add_int("page_id"); + fld_rule_id = flds.Add_int("rule_id"); + fld_rule_score_total = flds.Add_int("rule_score_total"); + if (created) { + Dbmeta_tbl_itm meta = Dbmeta_tbl_itm.New(tbl_name, flds + , Dbmeta_idx_itm.new_unique_by_tbl(tbl_name, "pkey", fld_log_tid, fld_page_id, fld_rule_id) + ); + conn.Meta_tbl_create(meta); + } + stmt_insert = null; + conn.Rls_reg(this); + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + } + public void Insert(int log_tid, int page_id, int rule_id, int rule_score_total) { + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Clear() + .Val_int(fld_log_tid , log_tid) + .Val_int(fld_page_id , page_id) + .Val_int(fld_rule_id , rule_id) + .Val_int(fld_rule_score_total , rule_score_total) + .Exec_insert(); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_match_mgr.java b/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_match_mgr.java index a27517de8..db2eb2513 100644 --- a/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_match_mgr.java +++ b/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_match_mgr.java @@ -13,3 +13,176 @@ 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.bldrs.filters.dansguardians; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.filters.*; +import gplx.core.primitives.*; import gplx.core.btries.*; +import gplx.xowa.addons.apps.cfgs.*; +import gplx.xowa.langs.*; +import gplx.xowa.bldrs.filters.core.*; +public class Dg_match_mgr { + private int score_init, score_fail; private boolean log_enabled, case_match; + private final Btrie_slim_mgr btrie = Btrie_slim_mgr.cs(); + private final Ordered_hash rules = Ordered_hash_.New_bry(); + private final Ordered_hash rule_group_hash = Ordered_hash_.New_bry(), rule_tally_hash = Ordered_hash_.New_bry(); + private final Dg_parser parser = new Dg_parser(); + private final Xob_ttl_filter_mgr ttl_filter_mgr = new Xob_ttl_filter_mgr(); + private final Dg_ns_skip_mgr ns_skip_mgr = new Dg_ns_skip_mgr(); + private final Dg_log_mgr log_mgr = new Dg_log_mgr(); + public Dg_match_mgr(Io_url root_dir, int score_init, int score_fail, boolean case_match, boolean log_enabled, Io_url log_url) { + this.score_init = score_init; this.score_fail = score_fail; this.case_match = case_match; this.log_enabled = log_enabled; + if (log_enabled) log_mgr.Init(log_url); + ttl_filter_mgr.Load(Bool_.N, root_dir.GenSubFil("xowa.title.include.txt")); + ttl_filter_mgr.Load(Bool_.Y, root_dir.GenSubFil("xowa.title.exclude.txt")); + ns_skip_mgr.Load(root_dir.GenSubFil("xowa.ns.skip.txt")); + Io_url dg_root_url = root_dir.GenSubDir("dansguardian"); + Dg_file[] files = parser.Parse_dir(dg_root_url); Gfo_usr_dlg_.Instance.Plog_many("", "", "import.dg.rules: url=~{0} files=~{1}", dg_root_url, files.length); + Init_by_files(files); + if (log_enabled) log_mgr.Commit(); + } + public void Clear() { + btrie.Clear(); + rules.Clear(); + rule_group_hash.Clear(); + rule_tally_hash.Clear(); + } + private void Init_by_files(Dg_file[] files) { + for (Dg_file file : files) { + Dg_rule[] rules = file.Lines(); + if (log_enabled) log_mgr.Insert_file(file); + for (Dg_rule rule : rules) + Init_by_rule(rule); + } + } + @gplx.Internal protected void Init_by_rule(Dg_rule rule) { + if (rule.Tid() != Dg_rule.Tid_rule) return; + if (log_enabled) log_mgr.Insert_rule(rule); + Dg_word[] words = rule.Words(); + for (Dg_word word : words) { + Dg_rule_group rule_group = Get_rule_group_or_new(word.Raw()); + rule_group.Rules_list().Add(rule); + btrie.Add_obj(word.Raw(), rule_group); + } + } + private Dg_rule_group Get_rule_group_or_new(byte[] word) { + Dg_rule_group rv = (Dg_rule_group)rule_group_hash.Get_by(word); + if (rv == null) { + rv = new Dg_rule_group(word); + rule_group_hash.Add(word, rv); + } + return rv; + } + private Dg_rule_tally Get_rule_tally_or_new(byte[] key, Dg_rule rule) { + Dg_rule_tally rv = (Dg_rule_tally)rule_tally_hash.Get_by(key); + if (rv == null) { + rv = new Dg_rule_tally(rule); + rule_tally_hash.Add(key, rv); + } + return rv; + } + public boolean Match(int log_tid, int page_id, int page_ns, byte[] page_ttl, byte[] page_ttl_db, Xol_lang_itm lang, byte[] src) { + // if ns is in skip_mgr, ignore; needed to skip Template and Module + if (ns_skip_mgr.Has(page_ns)) + return false; + + int src_len = src.length; + int clude_type = 0; + if (ttl_filter_mgr.Match_include(page_ttl_db)) clude_type = -1; + else if (ttl_filter_mgr.Match_exclude(page_ttl_db)) clude_type = 1; + if (clude_type != 0) { + log_mgr.Insert_page_score(log_tid, page_id, page_ns, page_ttl, src_len, 0, 0, clude_type); + return clude_type == 1; + } + if (!case_match) { + src = lang.Case_mgr().Case_build_lower(src); + src_len = src.length; + } + rules.Clear(); + rule_tally_hash.Clear(); + int pos = 0; + int score_cur = score_init; + while (pos < src_len) { + Object o = btrie.Match_bgn(src, pos, src_len); + if (o == null) + ++pos; + else { + Dg_rule_group rule_group = (Dg_rule_group)o; + Dg_rule[] rules_ary = rule_group.Rules_ary(); + for (Dg_rule rule : rules_ary) { + Dg_rule_tally rule_tally = Get_rule_tally_or_new(rule.Key(), rule); + rule_tally.Process(rule_group.Word()); + } + ++pos; + } + } + int rule_tally_len = rule_tally_hash.Count(); if (rule_tally_len == 0) return false; + int rule_match_count = 0; + for (int i = 0; i < rule_tally_len; ++i) { + Dg_rule_tally rule_tally = (Dg_rule_tally)rule_tally_hash.Get_at(i); + int min_results = rule_tally.Results_pass_count(); + if (min_results > 0) { + int rule_score = rule_tally.Rule().Score(); + int rule_score_total = rule_score * min_results; + if (log_enabled) log_mgr.Insert_page_rule(log_tid, page_id, rule_tally.Rule().Id(), rule_score_total); + if (rule_score == Dg_rule.Score_banned) {score_cur = Int_.Max_value; break;} + score_cur += rule_score_total; + ++rule_match_count; + } + } + boolean rv = score_cur > score_fail; + if (rv && log_enabled) log_mgr.Insert_page_score(log_tid, page_id, page_ns, page_ttl, src_len, score_cur, rule_match_count, 0); + return rv; + } + public void Rls() {log_mgr.Rls();} + public void Commit() {if (log_enabled) log_mgr.Commit();} + + public static void Cfg__reg(Xoa_app app) { + app.Cfg().Dflt_mgr().Add(Cfg__root_dir, app.Fsys_mgr().Bin_xowa_dir().GenSubDir_nest("cfg", "bldr", "filter").Raw()); + } + public static Dg_match_mgr New_mgr(Xoa_app app, Xow_wiki wiki) { + Xocfg_mgr cfg_mgr = app.Cfg(); + if (!cfg_mgr.Get_bool_wiki_or(wiki, Cfg__enabled, false)) return null; + String ctx = cfg_mgr.To_ctx(wiki); + return new Dg_match_mgr + ( cfg_mgr.Get_url_or(ctx, Cfg__root_dir, app.Fsys_mgr().Bin_xowa_dir().GenSubDir_nest("cfg", "bldr", "filter")).GenSubDir(wiki.Domain_str()) + , cfg_mgr.Get_int_or(ctx, "xowa.bldr.dansguardian.score_init", 0) + , cfg_mgr.Get_int_or(ctx, "xowa.bldr.dansguardian.score_fail", 0) + , cfg_mgr.Get_bool_or(ctx, "xowa.bldr.dansguardian.case_match", false) + , cfg_mgr.Get_bool_or(ctx, "xowa.bldr.dansguardian.log_enabled", true) + , wiki.Fsys_mgr().Root_dir().GenSubFil("dansguardian_log.sqlite3") + ); + } + public static final String Cfg__enabled = "xowa.bldr.dansguardian.enabled"; + private static final String Cfg__root_dir = "xowa.bldr.dansguardian.root_dir"; +} +class Dg_rule_group { + public Dg_rule_group(byte[] word) {this.word = word;} + public byte[] Word() {return word;} private final byte[] word; + public List_adp Rules_list() {return rules_list;} private final List_adp rules_list = List_adp_.New(); + public Dg_rule[] Rules_ary() { + if (rules_ary == null) + rules_ary = (Dg_rule[])rules_list.To_ary_and_clear(Dg_rule.class); + return rules_ary; + } private Dg_rule[] rules_ary; +} +class Dg_rule_tally { + public Dg_rule_tally(Dg_rule rule) { + this.rule = rule; + Dg_word[] words = rule.Words(); + this.results_len = words.length; + this.results = new int[results_len]; + } + public Dg_rule Rule() {return rule;} private final Dg_rule rule; + public int[] Results() {return results;} private final int[] results; private final int results_len; + public void Process(byte[] word) { + Int_obj_ref idx = (Int_obj_ref)rule.Word_idx_hash().Get_by_bry(word); + int idx_val = idx.Val(); + results[idx_val] = results[idx_val] + 1; + } + public int Results_pass_count() { + int rv = Int_.Max_value; + for (int i = 0; i < results_len; ++i) { + int result = results[i]; + if (rv > result) rv = result; + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_match_mgr_tst.java b/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_match_mgr_tst.java index a27517de8..6663bce9d 100644 --- a/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_match_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_match_mgr_tst.java @@ -13,3 +13,44 @@ 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.bldrs.filters.dansguardians; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.filters.*; +import org.junit.*; import gplx.dbs.*; +public class Dg_match_mgr_tst { + @Before public void init() {fxt.Clear();} private Dg_match_mgr_fxt fxt = new Dg_match_mgr_fxt(); + @Test public void One() { + fxt.Init_line(100, "a"); + fxt.Test_match_many_y("a", "ab", "ba", "abc"); + fxt.Test_match_many_n("b"); + } +} +class Dg_match_mgr_fxt { + private Dg_match_mgr match_mgr; + private final List_adp rule_list = List_adp_.New(); + public void Clear() { + Db_conn_bldr.Instance.Reg_default_mem(); + Io_url root_dir = Io_url_.mem_dir_("mem/dg/"); + match_mgr = new Dg_match_mgr(root_dir.GenSubDir("words"), 1, 0, Bool_.Y, Bool_.Y, root_dir.GenSubDir("log")); + rule_list.Clear(); + } + public void Init_line(int score, String... words) { + Dg_rule line = new Dg_rule(-1, -1, -1, Dg_rule.Tid_rule, Bry_.new_a7("key"), score, Dg_word.Ary_new_by_str_ary(words)); + rule_list.Add(line); + } + public void Test_match_many_y(String... words) {Test_match_many(Bool_.Y, words);} + public void Test_match_many_n(String... words) {Test_match_many(Bool_.N, words);} + public void Test_match_many(boolean expd, String... words) { + int words_len = words.length; + for (int i = 0; i < words_len; ++i) + Test_match_one(expd, words[i]); + } + public void Test_match_one(boolean expd, String word_str) { + match_mgr.Clear(); + int rule_list_len = rule_list.Count(); + for (int j = 0; j < rule_list_len; ++j) { + Dg_rule rule = (Dg_rule)rule_list.Get_at(j); + match_mgr.Init_by_rule(rule); + } + byte[] word_bry = Bry_.new_u8(word_str); + Tfds.Eq(expd, match_mgr.Match(1, 101, 0, Bry_.Empty, Bry_.Empty, null, word_bry), (expd ? "pass:" : "fail:") + word_str); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_ns_skip_mgr.java b/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_ns_skip_mgr.java index a27517de8..90aab1725 100644 --- a/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_ns_skip_mgr.java +++ b/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_ns_skip_mgr.java @@ -13,3 +13,28 @@ 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.bldrs.filters.dansguardians; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.filters.*; +import gplx.core.lists.hashs.*; +class Dg_ns_skip_mgr { + private final Hash_adp__int ns_hash = new Hash_adp__int(); + private boolean is_empty = true; + public boolean Has(int ns) {return is_empty ? false : ns_hash.Get_by_or_null(ns) != null;} + public void Load(Io_url url) { + // load from file + Gfo_usr_dlg_.Instance.Log_many("", "", "loading ns.skip file; url=~{0}", url.Raw()); + byte[] src = Io_mgr.Instance.LoadFilBry_loose(url); + + // parse to lines + byte[][] lines = Bry_split_.Split_lines(src); + + // add to hash + for (byte[] line : lines) { + int ns_id = Bry_.To_int_or(line, Int_.Max_value); + if (ns_id != Int_.Max_value) { + Gfo_usr_dlg_.Instance.Log_many("", "", "adding ns; ns_id=~{0}", ns_id); + ns_hash.Add_if_dupe_use_1st(ns_id, line); + is_empty = false; + } + } + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_parser.java b/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_parser.java index a27517de8..6174f7640 100644 --- a/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_parser.java +++ b/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_parser.java @@ -13,3 +13,81 @@ 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.bldrs.filters.dansguardians; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.filters.*; +class Dg_parser { + private Gfo_usr_dlg usr_dlg = Gfo_usr_dlg_.Instance; private final Bry_bfr key_bldr = Bry_bfr_.Reset(32); + private final List_adp files = List_adp_.New(), lines = List_adp_.New(), words = List_adp_.New(); + private int next_id = 0; + public Dg_file[] Parse_dir(Io_url dir) { + Io_url[] fil_urls = Io_mgr.Instance.QueryDir_args(dir).Recur_(true).ExecAsUrlAry(); + this.usr_dlg = Gfo_usr_dlg_.Instance; + files.Clear(); + int len = fil_urls.length; + for (int i = 0; i < len; ++i) { + Io_url fil_url = fil_urls[i]; + byte[] fil_src = Io_mgr.Instance.LoadFilBry_loose(fil_url); + Dg_file file = Parse_fil(i, fil_url.GenRelUrl_orEmpty(dir), fil_src); + if (file != null) files.Add(file); + } + return (Dg_file[])files.To_ary_and_clear(Dg_file.class); + } + private Dg_file Parse_fil(int file_idx, String rel_path, byte[] src) { + int line_idx = 0; int line_bgn = 0; int src_len = src.length; + lines.Clear(); + int file_id = ++next_id; + while (line_bgn < src_len) { + ++line_idx; + int line_end = Bry_find_.Find_fwd(src, Byte_ascii.Nl, line_bgn); if (line_end == Bry_find_.Not_found) line_end = src_len; + Dg_rule line = Parse_line(rel_path, file_id, line_idx, src, line_bgn, line_end); + if (line.Tid() != Dg_rule.Tid_invalid) + lines.Add(line); + line_bgn = line_end + 1; + } + return new Dg_file(file_id, rel_path, (Dg_rule[])lines.To_ary_and_clear(Dg_rule.class)); + } + public Dg_rule Parse_line(String rel_path, int file_id, int line_idx, byte[] src, int line_bgn, int line_end) { + int score = Dg_rule.Score_banned; + int brack_bgn = line_bgn; + if (line_end - line_bgn <= 1) return Dg_rule.Itm_blank; // ignore blank lines; EX: "" + if (src[line_bgn] == Byte_ascii.Hash) return Dg_rule.Itm_comment; // ignore lines starting with hash; EX: "# comment" + while (brack_bgn < line_end) { // look for terms bracketed by "<>" + if (src[brack_bgn] != Byte_ascii.Lt) {Warn("dg.invalid_line.term must start with angle_bgn", rel_path, line_idx, src, line_bgn, line_end); return Dg_rule.Itm_invalid;} + int brack_end = Bry_find_.Find_fwd(src, Byte_ascii.Gt, brack_bgn); + if (brack_end == Bry_find_.Not_found) {Warn("dg.invalid_line.angle_end not found", rel_path, line_idx, src, line_bgn, line_end); return Dg_rule.Itm_invalid;} + byte[] word = Bry_.Mid(src, brack_bgn + 1, brack_end); + words.Add(word); + int next_pos = brack_end + 1; + if (next_pos == line_end) { + score = Dg_rule.Score_banned; + break; + } + byte next = src[next_pos]; + if (next == Byte_ascii.Comma) + brack_bgn = brack_end + 2; + else { + brack_bgn = brack_end + 1; + if (src[brack_bgn] != Byte_ascii.Lt) {Warn("dg.invalid_line.wrong_term_dlm", rel_path, line_idx, src, line_bgn, line_end); break;} + brack_end = Bry_find_.Find_fwd(src, Byte_ascii.Gt, brack_bgn); + if (brack_end == Bry_find_.Not_found) {Warn("dg.invalid_line.score not found", rel_path, line_idx, src, line_bgn, line_end); break;} + int parse_score = Bry_.To_int_or(src, brack_bgn + 1, brack_end, Int_.Min_value); + if (parse_score == Int_.Min_value) {Warn("dg.invalid_line.score is invalid", rel_path, line_idx, src, line_bgn, line_end); break;} + score = parse_score; + break; + } + } + byte[] key = key_bldr.Add_int_variable(file_id).Add_byte_dot().Add_int_variable(line_idx).To_bry_and_clear(); + return new Dg_rule(file_id, ++next_id, line_idx, Dg_rule.Tid_rule, key, score, Ary_new_by_ary((byte[][])words.To_ary_and_clear(byte[].class))); + } + private static Dg_word[] Ary_new_by_ary(byte[][] ary) { + int ary_len = ary.length; + Dg_word[] rv = new Dg_word[ary_len]; + for (int i = 0; i < ary_len; ++i) { + byte[] raw = ary[i]; + rv[i] = new Dg_word(raw); + } + return rv; + } + private void Warn(String err_msg, String rel_path, int line_idx, byte[] src, int line_bgn, int line_end) { + usr_dlg.Warn_many("", "", err_msg + "; file=~{0} line_idx=~{1} line=~{2}", rel_path, line_idx, String_.new_u8(src, line_bgn, line_end)); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_parser_tst.java b/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_parser_tst.java index a27517de8..da5c271a2 100644 --- a/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_parser_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/filters/dansguardians/Dg_parser_tst.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.bldrs.filters.dansguardians; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.filters.*; +import org.junit.*; +public class Dg_parser_tst { + @Before public void init() {fxt.Init();} private Dg_parser_fxt fxt = new Dg_parser_fxt(); + @Test public void One() {fxt.Test_parse_line("<123>", fxt.Make_line(123, "a"));} + @Test public void Many() {fxt.Test_parse_line(",,<-123>", fxt.Make_line(-123, "a", "b", "c"));} + @Test public void Score_0() {fxt.Test_parse_line("<0>", fxt.Make_line(Dg_rule.Score_banned, "a"));} + @Test public void Noscore() {fxt.Test_parse_line("", fxt.Make_line(Dg_rule.Score_banned, "a"));} + @Test public void Noscore_2() {fxt.Test_parse_line(",", fxt.Make_line(Dg_rule.Score_banned, "a", "b"));} + @Test public void Comment() {fxt.Test_parse_line("# comment", Dg_rule.Itm_comment);} + @Test public void Blank() {fxt.Test_parse_line("", Dg_rule.Itm_blank);} + @Test public void Invalid_line_bgn() {fxt.Test_parse_line(" <1>", Dg_rule.Itm_invalid);} + @Test public void Dangling_word() {fxt.Test_parse_line("<12", fxt.Make_line(Dg_rule.Score_banned, "a"));} + @Test public void Invalid_dlm() {fxt.Test_parse_line(" <1>", fxt.Make_line(Dg_rule.Score_banned, "a"));} + @Test public void Invalid_dlm_2() {fxt.Test_parse_line(",<2>", fxt.Make_line(Dg_rule.Score_banned, "a", "b"));} + @Test public void Invalid_score() {fxt.Test_parse_line("<1a>", fxt.Make_line(Dg_rule.Score_banned, "a"));} +// @Test public void Parse_dir() { +// Dg_parser parser = new Dg_parser(); +// Gfo_usr_dlg_.I = Xoa_app_.New__usr_dlg__console(); +// parser.Parse_dir(Io_url_.new_dir_("C:\\xowa\\bin\\any\\xowa\\bldr\\filters\simple.wikipedia.org\\Dansguardian\\\\")); +// } +} +class Dg_parser_fxt { + private final Dg_parser parser = new Dg_parser(); private final Bry_bfr bfr = Bry_bfr_.Reset(32); + private final Bry_bfr tmp_bfr = Bry_bfr_.Reset(16); + public void Init() {} + public Dg_rule Make_line(int score, String... words) {return new Dg_rule(-1, -1, -1, Dg_rule.Tid_rule, null, score, Dg_word.Ary_new_by_str_ary(words));} + public void Test_parse_line(String str, Dg_rule expd) { + byte[] src = Bry_.new_u8(str); + Dg_rule actl = parser.Parse_line("rel_path", 0, 0, src, 0, src.length); + Tfds.Eq_str_lines(Xto_str(bfr, expd), Xto_str(bfr, actl)); + } + private String Xto_str(Bry_bfr bfr, Dg_rule line) { + bfr .Add_str_a7("score=").Add_int_variable(line.Score()).Add_byte_nl() + .Add_str_a7("words=").Add_str_u8(String_.Concat_with_str(";", Dg_word.Ary_concat(line.Words(), tmp_bfr, Byte_ascii.Tick))).Add_byte_nl() + ; + return bfr.To_str_and_clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/infos/Xob_info_file.java b/400_xowa/src/gplx/xowa/bldrs/infos/Xob_info_file.java index a27517de8..e5f7dafbb 100644 --- a/400_xowa/src/gplx/xowa/bldrs/infos/Xob_info_file.java +++ b/400_xowa/src/gplx/xowa/bldrs/infos/Xob_info_file.java @@ -13,3 +13,56 @@ 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.bldrs.infos; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; +public class Xob_info_file { + public Xob_info_file(int id, String type, String ns_ids, int part_id, Guid_adp guid, int schema_version, String core_file_name, String orig_file_name) { + this.id = id; this.type = type; this.ns_ids = ns_ids; this.part_id = part_id; this.guid = guid; + this.schema_version = schema_version; this.core_file_name = core_file_name; this.orig_file_name = orig_file_name; + } + public int Id() {return id;} private final int id; + public String Type() {return type;} private final String type; + public String Ns_ids() {return ns_ids;} private final String ns_ids; + public int Part_id() {return part_id;} private final int part_id; + public Guid_adp Guid() {return guid;} private final Guid_adp guid; + public int Schema_version() {return schema_version;} private final int schema_version; + public String Core_file_name() {return core_file_name;} private final String core_file_name; + public String Orig_file_name() {return orig_file_name;} private final String orig_file_name; + public void Save(Db_cfg_tbl tbl) { + tbl.Conn().Txn_bgn("make__info__file"); + tbl.Insert_int (Cfg_grp, Cfg_key__id , id); + tbl.Insert_str (Cfg_grp, Cfg_key__type , type); + tbl.Insert_str (Cfg_grp, Cfg_key__ns_ids , ns_ids); + tbl.Insert_int (Cfg_grp, Cfg_key__part_id , part_id); + tbl.Insert_guid (Cfg_grp, Cfg_key__guid , guid); + tbl.Insert_int (Cfg_grp, Cfg_key__schema_version , schema_version); + tbl.Insert_str (Cfg_grp, Cfg_key__core_file_name , core_file_name); + tbl.Insert_str (Cfg_grp, Cfg_key__orig_file_name , orig_file_name); + tbl.Conn().Txn_end(); + } + public static Xob_info_file Load(Db_cfg_tbl tbl) { + Db_cfg_hash hash = tbl.Select_as_hash(Cfg_grp); + return new Xob_info_file + ( hash.Get_by(Cfg_key__id ).To_int_or(-1) + , hash.Get_by(Cfg_key__type ).To_str_or("unknown") + , hash.Get_by(Cfg_key__ns_ids ).To_str_or("") + , hash.Get_by(Cfg_key__part_id ).To_int_or(-1) + , hash.Get_by(Cfg_key__guid ).To_guid_or(Guid_adp_.Empty) + , hash.Get_by(Cfg_key__schema_version ).To_int_or(2) + , hash.Get_by(Cfg_key__core_file_name ).To_str_or("") + , hash.Get_by(Cfg_key__orig_file_name ).To_str_or("") + ); + } + private static final String Cfg_grp = gplx.xowa.wikis.data.Xowd_cfg_key_.Grp__bldr_db + , Cfg_key__id = "id" // EX: 1 + , Cfg_key__type = "type" // EX: core + , Cfg_key__ns_ids = "ns_ids" // EX: 0 + , Cfg_key__part_id = "part_id" // EX: 0 + , Cfg_key__guid = "guid" // EX: 00000000-0000-0000-0000-000000000000 + , Cfg_key__schema_version = "schema_version" // EX: 2 + , Cfg_key__core_file_name = "core_file_name" // EX: en.wikipedia.org-text.xowa + , Cfg_key__orig_file_name = "orig_file_name" // EX: en.wikipedia.org-text-ns.000-db.002.xowa + ; + public static final String Ns_ids_empty = ""; + public static final int Part_id_1st = 1; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/infos/Xob_info_session.java b/400_xowa/src/gplx/xowa/bldrs/infos/Xob_info_session.java index a27517de8..de4a699d1 100644 --- a/400_xowa/src/gplx/xowa/bldrs/infos/Xob_info_session.java +++ b/400_xowa/src/gplx/xowa/bldrs/infos/Xob_info_session.java @@ -13,3 +13,47 @@ 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.bldrs.infos; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.dbs.cfgs.*; +public class Xob_info_session { + Xob_info_session(String user, String version, String wiki_domain, String dump_name, DateAdp time, Guid_adp guid) { + this.user = user; this.version = version; this.wiki_domain = wiki_domain; this.dump_name = dump_name; this.time = time; this.guid = guid; + } + public String User() {return user;} private final String user; + public String Version() {return version;} private final String version; + public String Wiki_domain() {return wiki_domain;} private final String wiki_domain; + public String Dump_name() {return dump_name;} private final String dump_name; + public DateAdp Time() {return time;} private final DateAdp time; + public Guid_adp Uuid() {return guid;} private final Guid_adp guid; + public void Save(Db_cfg_tbl tbl) { + tbl.Conn().Txn_bgn("make__info__session"); + tbl.Insert_str (Cfg_grp, Cfg_key__user , user); + tbl.Insert_str (Cfg_grp, Cfg_key__version , version); + tbl.Insert_str (Cfg_grp, Cfg_key__wiki_domain , wiki_domain); + tbl.Insert_str (Cfg_grp, Cfg_key__dump_name , dump_name); + tbl.Insert_date (Cfg_grp, Cfg_key__time , time); + tbl.Insert_guid (Cfg_grp, Cfg_key__guid , guid); + tbl.Conn().Txn_end(); + } + public static Xob_info_session Load(Db_cfg_tbl tbl) { + Db_cfg_hash hash = tbl.Select_as_hash(Cfg_grp); + return new Xob_info_session + ( hash.Get_by(Cfg_key__user).To_str_or("") + , hash.Get_by(Cfg_key__version).To_str_or("") + , hash.Get_by(Cfg_key__wiki_domain).To_str_or("") + , hash.Get_by(Cfg_key__dump_name).To_str_or("") + , hash.Get_by(Cfg_key__time).To_date_or(DateAdp_.MinValue) + , hash.Get_by(Cfg_key__guid).To_guid_or(Guid_adp_.Empty) + ); + } + public static final String Cfg_grp = gplx.xowa.wikis.data.Xowd_cfg_key_.Grp__bldr_session + , Cfg_key__user = "user" // EX: anonymous + , Cfg_key__version = "version" // EX: 2.3.1.4 + , Cfg_key__wiki_domain = "wiki_domain" // EX: en.wikipedia.org + , Cfg_key__dump_name = "dump_name" // EX: enwiki-latest-pages-articles + , Cfg_key__time = "time" // EX: 20150102 030405 + , Cfg_key__guid = "guid" // EX: 00000000-0000-0000-0000-000000000000 + ; + public static Xob_info_session new_(String user, String wiki_domain, String dump_name) {return new Xob_info_session(user, Xoa_app_.Version, wiki_domain, dump_name, Datetime_now.Get(), Guid_adp_.New());} + public static final Xob_info_session Test = new_("anonymous", "en.wikipedia.org", "enwiki-latest-pages-articles"); +} diff --git a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_base.java b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_base.java index a27517de8..7584ac190 100644 --- a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_base.java +++ b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_base.java @@ -13,3 +13,89 @@ 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.bldrs.installs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.threads.*; import gplx.xowa.bldrs.*; +import gplx.xowa.wikis.domains.*; +import gplx.xowa.bldrs.wms.dumps.*; +abstract class Xoi_cmd_base implements Gfo_thread_cmd { + public void Ctor(Xoi_setup_mgr install_mgr, String wiki_key) { + this.install_mgr = install_mgr; this.wiki_key = wiki_key; + this.Owner_(install_mgr); + } private Xoi_setup_mgr install_mgr; String wiki_key; + @gplx.Virtual public void Cmd_ctor() {} + public abstract String Async_key(); + public int Async_sleep_interval() {return Gfo_thread_cmd_.Async_sleep_interval_1_second;} + public boolean Async_prog_enabled() {return false;} + public void Async_prog_run(int async_sleep_sum) {} + public byte Async_init() {return Gfo_thread_cmd_.Init_ok;} + public boolean Async_term() {return true;} + public Gfo_invk Owner() {return owner;} public Xoi_cmd_base Owner_(Gfo_invk v) {owner = v; return this;} Gfo_invk owner; + public Gfo_thread_cmd Async_next_cmd() {return next_cmd;} public void Async_next_cmd_(Gfo_thread_cmd v) {next_cmd = v;} Gfo_thread_cmd next_cmd; + public void Async_run() { + running = true; + Thread_adp_.Start_by_key(this.Async_key(), this, Invk_process_async); + } + public boolean Async_running() {return running;} private boolean running; + public void Process_async() { + Xoae_app app = install_mgr.App(); + Xob_bldr bldr = app.Bldr(); + Xowe_wiki wiki = app.Wiki_mgr().Get_by_or_make(Bry_.new_a7(wiki_key)); + wiki.Init_assert(); + bldr.Cmd_mgr().Clear(); + Process_async_init(app, wiki, bldr); + bldr.Pause_at_end_(false); + try {bldr.Run();} + catch (Exception e) { + running = false; + install_mgr.Cmd_mgr().Working_(Bool_.N); + throw Err_.new_exc(e, "xo", "error during import"); + } + app.Usr_dlg().Prog_none("", "clear", ""); + app.Usr_dlg().Note_none("", "clear", ""); + Process_async_done(app, wiki, bldr); + running = false; + } + public abstract void Process_async_init(Xoae_app app, Xowe_wiki wiki, Xob_bldr bldr); + public abstract void Process_async_done(Xoae_app app, Xowe_wiki wiki, Xob_bldr bldr); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_process_async)) Process_async(); + else if (ctx.Match(k, Invk_owner)) return owner; + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_process_async = "run_async", Invk_owner = "owner"; +} +class Xoi_cmd_category2_page_props extends Xoi_cmd_wiki_download { public Xoi_cmd_category2_page_props(Xoi_setup_mgr install_mgr, String wiki_key, String dump_date) {this.Ctor_download_(install_mgr, wiki_key, dump_date, Xowm_dump_type_.Str__page_props);} + @Override public String Download_file_ext() {return ".sql.gz";} + public static final String KEY_category2 = "wiki.category2.download.page_props"; +} +class Xoi_cmd_category2_categorylinks extends Xoi_cmd_wiki_download { public Xoi_cmd_category2_categorylinks(Xoi_setup_mgr install_mgr, String wiki_key, String dump_date) {this.Ctor_download_(install_mgr, wiki_key, dump_date, Xowm_dump_type_.Str__categorylinks);} + @Override public String Download_file_ext() {return ".sql.gz";} + public static final String KEY_category2 = "wiki.category2.download.categorylinks"; +} +class Xoi_cmd_category2_build extends Xoi_cmd_base { + public Xoi_cmd_category2_build(Xoi_setup_mgr install_mgr, String wiki_key) {this.Ctor(install_mgr, wiki_key); this.app = install_mgr.App(); this.wiki_key = wiki_key;} private Xoae_app app; private String wiki_key; + @Override public void Cmd_ctor() { + Xowe_wiki wiki = app.Wiki_mgr().Get_by_or_make(Bry_.new_u8(wiki_key)); + wiki.Import_cfg().Category_version_(gplx.xowa.addons.wikis.ctgs.Xoa_ctg_mgr.Version_2); + } + @Override public String Async_key() {return KEY;} public static final String KEY = "wiki.category2.build"; + @Override public void Process_async_init(Xoae_app app, Xowe_wiki wiki, Xob_bldr bldr) { + wiki.Db_mgr_as_sql().Category_version_update(false); + bldr.Cmd_mgr().Add_many(wiki, gplx.xowa.addons.wikis.ctgs.bldrs.Xob_pageprop_cmd.BLDR_CMD_KEY, gplx.xowa.addons.wikis.ctgs.bldrs.Xob_catlink_cmd.BLDR_CMD_KEY); + } + @Override public void Process_async_done(Xoae_app app, Xowe_wiki wiki, Xob_bldr bldr) { + app.Usr_dlg().Prog_many("", "", "category2 setup done"); + } +} +class Xoi_cmd_search2_build extends Xoi_cmd_base { + public Xoi_cmd_search2_build(Xoi_setup_mgr install_mgr, String wiki_key) {this.Ctor(install_mgr, wiki_key);} + @Override public String Async_key() {return KEY;} public static final String KEY = "wiki.search2.build"; + @Override public void Process_async_init(Xoae_app app, Xowe_wiki wiki, Xob_bldr bldr) { + wiki.Db_mgr_as_sql().Category_version_update(false); + gplx.xowa.addons.wikis.searchs.bldrs.Srch_bldr_mgr_.Setup(wiki); + } + @Override public void Process_async_done(Xoae_app app, Xowe_wiki wiki, Xob_bldr bldr) { + app.Usr_dlg().Prog_many("", "", "search2 setup done"); + // wiki.Db_mgr().Search_version_refresh(); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_dumpfile.java b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_dumpfile.java index a27517de8..a8e818f74 100644 --- a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_dumpfile.java +++ b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_dumpfile.java @@ -13,3 +13,46 @@ 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.bldrs.installs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.threads.*; +class Xoi_cmd_dumpfile { + public byte[] Domain() {return domain;} private byte[] domain; + public Io_url Bz2_url() {return bz2_url;} Io_url bz2_url; + public Io_url Xml_url() {return xml_url;} Io_url xml_url; + public boolean Bz2_unzip() {return bz2_unzip;} private boolean bz2_unzip; + public void Clear() {domain = null; bz2_url = xml_url = null; bz2_unzip = false;} + public Xoi_cmd_dumpfile Parse_msg(GfoMsg m) { + Io_url dump_url = m.ReadIoUrl("url"); + domain = m.ReadBry("domain"); + if (Bry_.Len_eq_0(domain)) domain = Bry_.new_u8(dump_url.OwnerDir().NameOnly()); + bz2_unzip = String_.Eq(m.ReadStr("args"), "unzip"); + String dump_ext = dump_url.Ext(); + if (String_.Eq(dump_ext, ".bz2")) { + bz2_url = dump_url; + if (bz2_unzip) { + xml_url = bz2_url.GenNewExt(""); // remove .bz2 extension (new file path should be .xml) + if (!String_.Eq(xml_url.Ext(), ".xml")) + xml_url = xml_url.GenNewExt(".xml"); + } + } + else if (String_.Eq(dump_ext, ".xml")) { // user selected xml file; + bz2_url = null; + xml_url = dump_url; + bz2_unzip = false; // ignore unzip arge + } + return this; + } + public Gfo_thread_cmd Exec(Xoi_cmd_mgr cmd_mgr) { + Xowe_wiki wiki = cmd_mgr.App().Wiki_mgr().Get_by_or_make(domain); + if (bz2_unzip) { // unzip requested; add unzip cmd + GfoMsg unzip_msg = GfoMsg_.new_parse_(Gfo_thread_cmd_unzip.KEY).Add("v", Gfo_thread_cmd_unzip.KEY).Add("src", bz2_url.Raw()).Add("trg", xml_url.Raw()); + Gfo_thread_cmd_unzip unzip_cmd = (Gfo_thread_cmd_unzip)cmd_mgr.Cmd_add(unzip_msg); + unzip_cmd.Term_cmd_for_src_(Gfo_thread_cmd_unzip.Term_cmd_for_src_noop); // don't do anything with bz2 after unzip + } + if (xml_url == null) + wiki.Import_cfg().Src_fil_bz2_(bz2_url); + else + wiki.Import_cfg().Src_fil_xml_(xml_url); + return cmd_mgr.Dump_add_many_custom(String_.new_u8(domain), "", "", true); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_dumpfile_tst.java b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_dumpfile_tst.java index a27517de8..4a0538372 100644 --- a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_dumpfile_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_dumpfile_tst.java @@ -13,3 +13,62 @@ 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.bldrs.installs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import org.junit.*; +public class Xoi_cmd_dumpfile_tst { + @Before public void init() {fxt.Clear();} private Xoi_cmd_dumpfile_fxt fxt = new Xoi_cmd_dumpfile_fxt(); + @Test public void Bz2__unzip() { + fxt .Exec_parse_msg("mem/en.wikipedia.org/fil.xml.bz2", "", "unzip") + .Test_domain("en.wikipedia.org") + .Test_vals("mem/en.wikipedia.org/fil.xml.bz2", "mem/en.wikipedia.org/fil.xml", true) + ; + } + @Test public void Bz2__unzip__assert_xml_ext() { // xml ext relies on removing ".bz2" from ".xml.bz2"; if just ".bz2" add an ".xml" + fxt .Exec_parse_msg("mem/en.wikipedia.org/fil.bz2", "", "unzip") + .Test_vals("mem/en.wikipedia.org/fil.bz2", "mem/en.wikipedia.org/fil.xml", true) + ; + } + @Test public void Bz2__direct() { + fxt .Exec_parse_msg("mem/en.wikipedia.org/fil.bz2", "", "") + .Test_vals("mem/en.wikipedia.org/fil.bz2", null, false) + ; + } + @Test public void Xml__unzip_n() { + fxt .Exec_parse_msg("mem/en.wikipedia.org/fil.xml", "", "") + .Test_vals(null, "mem/en.wikipedia.org/fil.xml", false) + ; + } + @Test public void Xml__unzip_y() { + fxt .Exec_parse_msg("mem/en.wikipedia.org/fil.xml", "", "") + .Test_vals(null, "mem/en.wikipedia.org/fil.xml", false) + ; + } +} +class Xoi_cmd_dumpfile_fxt { + public void Clear() { + dumpfile.Clear(); + } private Xoi_cmd_dumpfile dumpfile = new Xoi_cmd_dumpfile(); + public Xoi_cmd_dumpfile_fxt Exec_parse_msg(String url, String domain, String args) { + GfoMsg m = GfoMsg_.new_parse_("").Add("url", url).Add("domain", domain).Add("args", args); + dumpfile.Parse_msg(m); + return this; + } + public Xoi_cmd_dumpfile_fxt Test_vals(String expd_bz2, String expd_xml, boolean expd_unzip) { + Eq_url(expd_bz2, dumpfile.Bz2_url()); + Eq_url(expd_xml, dumpfile.Xml_url()); + Tfds.Eq(expd_unzip, dumpfile.Bz2_unzip()); + return this; + } + public Xoi_cmd_dumpfile_fxt Test_domain(String expd_domain) { + Tfds.Eq(expd_domain, String_.new_u8(dumpfile.Domain())); + return this; + } + private void Eq_url(String expd, Io_url actl) { + if (expd == null && actl == null) return; + else if (expd != null && actl != null) { + Tfds.Eq(expd, actl.Raw()); + } + else if (expd == null) throw Err_.new_wo_type("actl should be null", "expd", expd); + else if (actl == null) throw Err_.new_wo_type("actl should not be null", "expd", expd); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_imageMagick_download.java b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_imageMagick_download.java index a27517de8..ec0e060a0 100644 --- a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_imageMagick_download.java +++ b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_imageMagick_download.java @@ -13,3 +13,35 @@ 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.bldrs.installs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.gfui.*; import gplx.gfui.kits.core.*; +import gplx.core.threads.*; +class Xoi_cmd_imageMagick_download extends Gfo_thread_cmd_download implements Gfo_thread_cmd {// private static final byte[] Bry_windows_zip = Bry_.new_a7("-windows.zip"); +// static final String Src_imageMagick = "ftp://ftp.sunet.se/pub/multimedia/graphics/ImageMagick/binaries/"; + public Xoi_cmd_imageMagick_download(Gfo_usr_dlg usr_dlg, Gfui_kit kit, Io_url trg) {this.Ctor(usr_dlg, kit); this.trg = trg;} Io_url trg; + @Override public byte Async_init() { // +// byte[] raw = xrg.Exec_as_bry(Src_imageMagick); +// int find_pos = Bry_find_.Find_fwd(raw, Bry_windows_zip); if (find_pos == Bry_find_.Not_found) return Fail(); +// int bgn_pos = Bry_find_.Find_bwd(raw, Byte_ascii.Quote, find_pos); if (bgn_pos == Bry_find_.Not_found) return Fail(); +// ++bgn_pos; +// int end_pos = Bry_find_.Find_fwd(raw, Byte_ascii.Quote, bgn_pos); if (end_pos == Bry_find_.Not_found) return Fail(); +// String src = Src_imageMagick + String_.new_a7(Bry_.Mid(raw, bgn_pos, end_pos)); + String src = "http://ftp.sunet.se/pub/multimedia/graphics/ImageMagick/binaries/ImageMagick-6.8.8-1-Q16-x86-windows.zip"; + this.Init("downloading", src, trg); + return super.Async_init(); + } + byte Fail() { + kit.Ask_ok(GRP_KEY, "windows_not_found", "Could not find Windows binary. Please download ImageMagick directly from the site."); + return Gfo_thread_cmd_.Init_cancel_step; + } + public static final String KEY_imageMagick = "download.imageMagick"; + static final String GRP_KEY = "xowa.install.cmds.download.imageMagick"; +} +class Xoi_cmd_msg_ok extends Gfo_thread_cmd_base implements Gfo_thread_cmd { + public Xoi_cmd_msg_ok(Gfo_usr_dlg usr_dlg, Gfui_kit kit, String msg) {this.msg = msg; this.Ctor(usr_dlg, kit);} private String msg; + @Override public boolean Async_term() { + kit.Ask_ok("msg_ok", "msg", msg); + return true; + } + public static final String KEY = "msg.ok"; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_mgr.java b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_mgr.java index a27517de8..9c569b3c8 100644 --- a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_mgr.java +++ b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_mgr.java @@ -13,3 +13,133 @@ 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.bldrs.installs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.brys.fmtrs.*; import gplx.core.threads.*; +public class Xoi_cmd_mgr implements Gfo_invk { + List_adp cmds = List_adp_.New(); + public Xoi_cmd_mgr(Xoi_setup_mgr install_mgr) {this.app = install_mgr.App(); this.install_mgr = install_mgr;} private Xoae_app app; Xoi_setup_mgr install_mgr; + public Xoae_app App() {return app;} + public void Canceled_y_() {canceled = true;} private boolean canceled = false; + public boolean Working() {return working;} private boolean working; + public void Working_(boolean v) { + working = v; + app.Bldr__running_(v); + } + private void Process_async(Gfo_thread_cmd cmd) { + byte init_rslt = cmd.Async_init(); + if (init_rslt == Gfo_thread_cmd_.Init_ok) { + cmd.Async_run(); + int async_sleep_interval = cmd.Async_sleep_interval(); + boolean async_prog_enabled = cmd.Async_prog_enabled(); + int async_sleep_sum = 0; + while (cmd.Async_running()) { + if (canceled) {this.Working_(Bool_.N); return;} + if (async_prog_enabled) cmd.Async_prog_run(async_sleep_sum); + Thread_adp_.Sleep(async_sleep_interval); + async_sleep_sum += async_sleep_interval; // NOTE: this is not exact + } + } + boolean term_pass = cmd.Async_term(); + if (cmd.Async_next_cmd() != null && init_rslt != Gfo_thread_cmd_.Init_cancel_all && term_pass) + Run_async(cmd.Async_next_cmd()); + else + this.Working_(Bool_.N); + } + private void Run_async(Gfo_thread_cmd cmd) {Thread_adp_.Start_by_val(cmd.Async_key(), this, Invk_process_async, cmd);} + private void Cmds_run() { + if (working) { + app.Gui_mgr().Kit().Ask_ok("", "", "An import is in progress. Please wait for it to complete. If you want to do multiple imports at once, see Dashboard/Import/Offline."); // HOME + return; + } + int cmds_len = cmds.Count(); + if (cmds_len == 0) return; + for (int i = 0; i < cmds_len - 1; i++) { + Gfo_thread_cmd cur_cmd = (Gfo_thread_cmd)cmds.Get_at(i); + Gfo_thread_cmd nxt_cmd = (Gfo_thread_cmd)cmds.Get_at(i + 1); + cur_cmd.Cmd_ctor(); + cur_cmd.Async_next_cmd_(nxt_cmd); + } + Gfo_thread_cmd cmd = (Gfo_thread_cmd)cmds.Get_at(0); + cmds.Clear(); + this.Working_(Bool_.Y); + app.Bldr__running_(true); + this.Run_async(cmd); + } + Object Dump_add_many(GfoMsg m) { + int args_len = m.Args_count(); + if (args_len < 4) throw Err_.new_wo_type("Please provide the following: wiki name, wiki date, dump_type, and one command; EX: ('simple.wikipedia.org', 'latest', 'pages-articles', 'wiki.download')"); + String wiki_key = m.Args_getAt(0).Val_to_str_or_empty(); + String wiki_date = m.Args_getAt(1).Val_to_str_or_empty(); + String dump_type = m.Args_getAt(2).Val_to_str_or_empty(); + Gfo_thread_cmd cmd = null; + for (int i = 3; i < args_len; i++) { + Keyval kv = m.Args_getAt(i); + String kv_val = kv.Val_to_str_or_empty(); + if (String_.Eq(kv_val, Wiki_cmd_custom)) + return Dump_add_many_custom(wiki_key, wiki_date, dump_type, false); + else { + cmd = Dump_cmd_new(wiki_key, wiki_date, dump_type, kv.Val_to_str_or_empty()); + cmds.Add(cmd); + } + } + return cmd; // return last cmd + } + public Gfo_thread_cmd Dump_add_many_custom(String wiki_key, String wiki_date, String dump_type, boolean dumpfile_cmd) { + String[] custom_cmds = (app.Cfg().Get_bool_app_or("xowa.bldr.import.unzip_bz2_file", false)) // CFG: Cfg__ + ? String_.Ary(Xoi_cmd_wiki_download.Key_wiki_download, Xoi_cmd_wiki_unzip.KEY_dump, Xoi_cmd_wiki_import.KEY) + : String_.Ary(Xoi_cmd_wiki_download.Key_wiki_download, Xoi_cmd_wiki_import.KEY); + int custom_cmds_len = custom_cmds.length; + Gfo_thread_cmd cmd = null; + for (int j = 0; j < custom_cmds_len; j++) { + cmd = Dump_cmd_new(wiki_key, wiki_date, dump_type, custom_cmds[j]); + if (dumpfile_cmd) { + if (String_.Eq(cmd.Async_key(), Xoi_cmd_wiki_download.Key_wiki_download)) continue; // skip download if wiki.dump_file + else if (String_.Eq(cmd.Async_key(), Xoi_cmd_wiki_unzip.KEY_dump)) { + Xowe_wiki wiki = app.Wiki_mgr().Get_by_or_make(Bry_.new_u8(wiki_key)); + if (wiki.Import_cfg().Src_fil_xml() != null) continue; // skip unzip if xml exists + } + else if (String_.Eq(cmd.Async_key(), Xoi_cmd_wiki_import.KEY)) { + ((Xoi_cmd_wiki_import)cmd).Import_move_bz2_to_done_(false); + } + } + cmds.Add(cmd); + } + return cmd; + } + Gfo_thread_cmd Dump_cmd_new(String wiki_key, String wiki_date, String dump_type, String cmd_key) { + if (String_.Eq(cmd_key, Xoi_cmd_wiki_download.Key_wiki_download)) return new Xoi_cmd_wiki_download().Ctor_download_(install_mgr, wiki_key, wiki_date, dump_type).Owner_(this); + else if (String_.Eq(cmd_key, Xoi_cmd_wiki_unzip.KEY_dump)) return new Xoi_cmd_wiki_unzip(install_mgr, wiki_key, wiki_date, dump_type).Owner_(this); + else if (String_.Eq(cmd_key, Xoi_cmd_wiki_import.KEY)) return new Xoi_cmd_wiki_import(install_mgr, wiki_key, wiki_date, dump_type).Owner_(this); + else if (String_.Eq(cmd_key, Xoi_cmd_category2_build.KEY)) return new Xoi_cmd_category2_build(install_mgr, wiki_key).Owner_(this); + else if (String_.Eq(cmd_key, Xoi_cmd_category2_page_props.KEY_category2)) return new Xoi_cmd_category2_page_props(install_mgr, wiki_key, wiki_date).Owner_(this); + else if (String_.Eq(cmd_key, Xoi_cmd_category2_categorylinks.KEY_category2)) return new Xoi_cmd_category2_categorylinks(install_mgr, wiki_key, wiki_date).Owner_(this); + else if (String_.Eq(cmd_key, Xoi_cmd_search2_build.KEY)) return new Xoi_cmd_search2_build(install_mgr, wiki_key).Owner_(this); + else throw Err_.new_unhandled(cmd_key); + } + public static final String Wiki_cmd_custom = "wiki.custom", Wiki_cmd_dump_file = "wiki.dump_file"; + public Gfo_thread_cmd Cmd_add(GfoMsg m) {Gfo_thread_cmd rv = Cmd_clone(m); cmds.Add(rv); return rv;} + Gfo_thread_cmd Cmd_clone(GfoMsg m) { + String cmd_key = m.ReadStr("v"); + if (String_.Eq(cmd_key, Gfo_thread_cmd_download.KEY)) return new Gfo_thread_cmd_download().Init("downloading", m.ReadStr("src"), Bry_fmtr_eval_mgr_.Eval_url(app.Url_cmd_eval(), m.ReadBry("trg"))).Url_eval_mgr_(app.Url_cmd_eval()).Owner_(this).Ctor(app.Usr_dlg(), app.Gui_mgr().Kit()); + else if (String_.Eq(cmd_key, Gfo_thread_cmd_unzip.KEY)) return new Gfo_thread_cmd_unzip().Url_eval_mgr_(app.Url_cmd_eval()).Owner_(this).Init(app.Usr_dlg(), app.Gui_mgr().Kit(), app.Prog_mgr().App_decompress_bz2(), app.Prog_mgr().App_decompress_zip(), app.Prog_mgr().App_decompress_gz(), Bry_fmtr_eval_mgr_.Eval_url(app.Url_cmd_eval(), m.ReadBry("src")), Bry_fmtr_eval_mgr_.Eval_url(app.Url_cmd_eval(), m.ReadBry("trg"))); + else if (String_.Eq(cmd_key, Gfo_thread_cmd_replace.KEY)) return new Gfo_thread_cmd_replace().Url_eval_mgr_(app.Url_cmd_eval()).Owner_(this).Init(app.Usr_dlg(), app.Gui_mgr().Kit(), Bry_fmtr_eval_mgr_.Eval_url(app.Url_cmd_eval(), m.ReadBry("fil"))); + else if (String_.Eq(cmd_key, Xoi_cmd_wiki_goto_page.KEY)) return new Xoi_cmd_wiki_goto_page(app, m.ReadStr("v")).Owner_(this); + else if (String_.Eq(cmd_key, Xoi_cmd_msg_ok.KEY)) return new Xoi_cmd_msg_ok(app.Usr_dlg(), app.Gui_mgr().Kit(), m.ReadStr("v")).Owner_(this); + else if (String_.Eq(cmd_key, Xoi_cmd_imageMagick_download.KEY_imageMagick)) return new Xoi_cmd_imageMagick_download(app.Usr_dlg(), app.Gui_mgr().Kit(), Bry_fmtr_eval_mgr_.Eval_url(app.Url_cmd_eval(), m.ReadBry("trg"))).Owner_(this); + else if (String_.Eq(cmd_key, Wiki_cmd_dump_file)) return Wiki_cmd_dump_file_make(m); + else throw Err_.new_unhandled(cmd_key); + } + Gfo_thread_cmd Wiki_cmd_dump_file_make(GfoMsg m) { // note: might be used directly in home-wiki pages to download files + Xoi_cmd_dumpfile dumpfile = new Xoi_cmd_dumpfile().Parse_msg(m); + return dumpfile.Exec(this); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_process_async)) Process_async((Gfo_thread_cmd)m.CastObj("v")); + else if (ctx.Match(k, Invk_dump_add_many)) return Dump_add_many(m); + else if (ctx.Match(k, Invk_cmd_add)) return Cmd_add(m); + else if (ctx.Match(k, Invk_run)) Cmds_run(); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_process_async = "process_async", Invk_dump_add_many = "dump_add_many", Invk_run = "run", Invk_cmd_add = "cmd_add"; + static final String GRP_KEY = "xowa.install_mgr.cmd_mgr"; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_download.java b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_download.java index a27517de8..8709fa438 100644 --- a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_download.java +++ b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_download.java @@ -13,3 +13,41 @@ 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.bldrs.installs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.gfui.*; +import gplx.core.threads.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.utils.*; +import gplx.xowa.bldrs.wms.dumps.*; +class Xoi_cmd_wiki_download extends Gfo_thread_cmd_download implements Gfo_thread_cmd { private Xoi_setup_mgr install_mgr; private String wiki_key, dump_date, dump_type; + public Xoi_cmd_wiki_download Ctor_download_(Xoi_setup_mgr install_mgr, String wiki_key, String dump_date, String dump_type) { + this.install_mgr = install_mgr; + this.wiki_key = wiki_key; + this.dump_date = dump_date; + this.dump_type = dump_type; + this.Owner_(install_mgr); + return this; + } + @gplx.Virtual public String Download_file_ext() {return ".xml.bz2";} // wiki.download is primarily used for dump files; default to .xml.bz2; NOTE: changed from ".xml"; DATE:2013-11-07 + @Override public String Async_key() {return Key_wiki_download;} public static final String Key_wiki_download = "wiki.download"; + @Override public byte Async_init() { + Xoae_app app = install_mgr.App(); + Xowm_dump_file dump_file = new Xowm_dump_file(wiki_key, dump_date, dump_type); + String[] server_urls = gplx.xowa.bldrs.installs.Xoi_dump_mgr.Server_urls(app); + boolean connected = Xowm_dump_file_.Connect_first(dump_file, server_urls); + if (connected) + app.Usr_dlg().Note_many("", "", "url: ~{0}", dump_file.File_url()); + else { + if (!Dump_servers_offline_msg_shown) { + app.Gui_mgr().Kit().Ask_ok("", "", "all dump servers are offline: ~{0}", String_.AryXtoStr(server_urls)); + Dump_servers_offline_msg_shown = true; + } + } + Xowe_wiki wiki = app.Wiki_mgr().Get_by_or_make(dump_file.Domain_itm().Domain_bry()); + Io_url root_dir = wiki.Fsys_mgr().Root_dir(); + Io_url[] trg_fil_ary = Io_mgr.Instance.QueryDir_args(root_dir).FilPath_("*." + dump_type + Download_file_ext() + "*").ExecAsUrlAry(); + Io_url trg = trg_fil_ary.length == 0 ? root_dir.GenSubFil(dump_file.File_name()) : trg_fil_ary[0]; + this.Ctor(app.Usr_dlg(), app.Gui_mgr().Kit()); + this.Init("download", dump_file.File_url(), trg); + return super.Async_init(); + } + private static boolean Dump_servers_offline_msg_shown = false; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_goto_page.java b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_goto_page.java index a27517de8..aaf6aa2cf 100644 --- a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_goto_page.java +++ b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_goto_page.java @@ -13,3 +13,16 @@ 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.bldrs.installs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.threads.*; +class Xoi_cmd_wiki_goto_page extends Gfo_thread_cmd_base implements Gfo_thread_cmd { + public Xoi_cmd_wiki_goto_page(Xoae_app app, String page) {this.app = app; this.page = page; this.Ctor(app.Usr_dlg(), app.Gui_mgr().Kit());} private Xoae_app app; String page; + @Override public void Async_run() {kit.New_cmd_sync(this).Invk(GfsCtx.new_(), 0, Invk_goto_page, GfoMsg_.Null);} + private void Goto_page(String page) {app.Gui_mgr().Browser_win().Page__navigate_by_url_bar(page);} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_goto_page)) Goto_page(page); + else return super.Invk(ctx, ikey, k, m); + return this; + } private static final String Invk_goto_page = "goto_page"; + public static final String KEY = "wiki.goto_page"; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_import.java b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_import.java index a27517de8..ac38d0ca6 100644 --- a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_import.java +++ b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_import.java @@ -13,3 +13,99 @@ 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.bldrs.installs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.threads.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.utils.*; +import gplx.xowa.guis.views.*; +import gplx.xowa.wikis.domains.*; +import gplx.xowa.htmls.hrefs.*; +import gplx.xowa.addons.wikis.ctgs.bldrs.*; +class Xoi_cmd_wiki_import implements Gfo_thread_cmd { + private boolean running; + private Xowe_wiki wiki; + public Xoi_cmd_wiki_import(Xoi_setup_mgr install_mgr, String wiki_key, String wiki_date, String dump_type) {this.install_mgr = install_mgr; this.Owner_(install_mgr); this.wiki_key = wiki_key; this.wiki_date = wiki_date; this.dump_type = dump_type;} private Xoi_setup_mgr install_mgr; String wiki_key, wiki_date, dump_type; + public static final String KEY = "wiki.import"; + public void Cmd_ctor() {} + public String Async_key() {return KEY;} + public int Async_sleep_interval() {return Gfo_thread_cmd_.Async_sleep_interval_1_second;} + public boolean Async_prog_enabled() {return false;} + public void Async_prog_run(int async_sleep_sum) {} + public byte Async_init() {return Gfo_thread_cmd_.Init_ok;} + public boolean Async_term() { + install_mgr.App().Usr_dlg().Log_many(GRP_KEY, "import.end", "import.end ~{0} ~{1} ~{2}", wiki_key, wiki_date, dump_type); + return true; + } + public Gfo_invk Owner() {return owner;} public Xoi_cmd_wiki_import Owner_(Gfo_invk v) {owner = v; return this;} Gfo_invk owner; + public Gfo_thread_cmd Async_next_cmd() {return next_cmd;} public void Async_next_cmd_(Gfo_thread_cmd v) {next_cmd = v;} Gfo_thread_cmd next_cmd; + public void Async_run() { + running = true; + install_mgr.App().Usr_dlg().Log_many(GRP_KEY, "import.bgn", "import.bgn ~{0} ~{1} ~{2}", wiki_key, wiki_date, dump_type); + Thread_adp_.Start_by_key(this.Async_key(), this, Invk_process_async); + } + public boolean Async_running() { + return running; + } + public boolean Import_move_bz2_to_done() {return import_move_bz2_to_done;} public Xoi_cmd_wiki_import Import_move_bz2_to_done_(boolean v) {import_move_bz2_to_done = v; return this;} private boolean import_move_bz2_to_done = true; + private void Process_async() { + Xoae_app app = install_mgr.App(); + app.Usr_dlg().Prog_one("", "", "preparing import: ~{0}", wiki_key); + Xob_bldr bldr = app.Bldr(); + wiki = app.Wiki_mgr().Get_by_or_make(Bry_.new_a7(wiki_key)); + wiki.Init_assert(); + bldr.Cmd_mgr().Clear(); + bldr.Pause_at_end_(false); + Io_url src_url = wiki.Import_cfg().Src_rdr().Url(); + Process_sql(bldr, src_url); + bldr.Run(); + app.Usr_dlg().Prog_none(GRP_KEY, "clear", ""); app.Usr_dlg().Note_none(GRP_KEY, "clear", ""); + app.Usere().Available_from_fsys(); + wiki.Init_needed_(true); + wiki.Html_mgr().Page_wtr_mgr().Init_(true); + wiki.Init_assert(); + if (String_.Eq(src_url.Ext(), ".xml")) { + if ( app.Cfg().Get_bool_app_or("xowa.bldr.import.delete_xml_file", true) // CFG: Cfg__ + && Io_mgr.Instance.ExistsFil(src_url.GenNewExt(".bz2")) // only delete the file if there is a corresponding bz2 file; BUG.GH:#124; DATE:2017-02-02 + ) + Io_mgr.Instance.DeleteFil(src_url); + } + else if (String_.Eq(src_url.Ext(), ".bz2")) { + Io_url trg_fil = app.Fsys_mgr().Wiki_dir().GenSubFil_nest("#dump", "done", src_url.NameAndExt()); + if (import_move_bz2_to_done) + Io_mgr.Instance.MoveFil_args(src_url, trg_fil, true).Exec(); + } + running = false; + wiki.Import_cfg().Src_fil_xml_(null).Src_fil_bz2_(null); // reset file else error when going from Import/Script to Import/List + app.Gui_mgr().Kit().New_cmd_sync(this).Invk(GfsCtx.new_(), 0, Invk_open_wiki, GfoMsg_.Null); + } + private void Process_sql(Xob_bldr bldr, Io_url dump_url) { + // setup wiki + ((Xob_cleanup_cmd)bldr.Cmd_mgr().Add_cmd(wiki, Xob_cmd_keys.Key_util_cleanup)).Delete_tdb_(true).Delete_sqlite3_(true); + bldr.Cmd_mgr().Add_cmd(wiki, Xob_cmd_keys.Key_text_init); + bldr.Cmd_mgr().Add_cmd(wiki, Xob_cmd_keys.Key_text_page); + bldr.Cmd_mgr().Add_cmd(wiki, Xob_cmd_keys.Key_text_css); +// if (wiki.Appe().Setup_mgr().Dump_mgr().Search_version() == gplx.xowa.addons.wikis.searchs.specials.Srch_special_page.Version_2) + gplx.xowa.addons.wikis.searchs.bldrs.Srch_bldr_mgr_.Setup(wiki); + bldr.Cmd_mgr().Add_cmd(wiki, Xob_cmd_keys.Key_text_term); + + // setup category + if (wiki.Domain_itm().Domain_type_id() != Xow_domain_tid_.Tid__other) { // do not add category if not wmf; note that wikia wikis will not have category dumps; DATE:2016-10-22 + Xob_download_cmd.Add_if_not_found_many(bldr, wiki, Xob_catlink_cmd.Dump_file_name, Xob_pageprop_cmd.Dump_file_name); + bldr.Cmd_mgr().Add(new gplx.xowa.addons.wikis.ctgs.bldrs.Xob_pageprop_cmd(bldr, wiki).Src_dir_manual_(dump_url.OwnerDir())); + bldr.Cmd_mgr().Add(new gplx.xowa.addons.wikis.ctgs.bldrs.Xob_catlink_cmd(bldr, wiki).Src_dir_manual_(dump_url.OwnerDir())); + } + } + private void Open_wiki(String wiki_key) { + Xog_win_itm main_win = install_mgr.App().Gui_mgr().Browser_win(); + if (main_win.Active_page() == null) return; // will be null when invoked through cmd-line + byte[] url = Bry_.Add(wiki.Domain_bry(), Xoh_href_.Bry__wiki, wiki.Props().Main_page()); + main_win.Page__navigate_by_url_bar(String_.new_u8(url)); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_process_async)) Process_async(); + else if (ctx.Match(k, Invk_owner)) return owner; + else if (ctx.Match(k, Invk_open_wiki)) Open_wiki(wiki_key); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_process_async = "run_async", Invk_owner = "owner", Invk_open_wiki = "open_wiki"; + static final String GRP_KEY = "xowa.thread.op.build"; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_tst.java b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_tst.java index a27517de8..5357a226d 100644 --- a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_tst.java @@ -13,3 +13,116 @@ 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.bldrs.installs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import org.junit.*; +import gplx.core.consoles.*; +import gplx.core.brys.args.*; import gplx.core.threads.*; import gplx.xowa.bldrs.setups.maints.*; import gplx.xowa.xtns.wbases.imports.*; +import gplx.xowa.wikis.domains.*; +import gplx.xowa.bldrs.wms.*; import gplx.xowa.bldrs.wms.dumps.*; +public class Xoi_cmd_wiki_tst { + @Test public void Run() { // MAINT:2017-03-28 +// Bld_import_list(Xow_domain_regy.All); +// Bld_cfg_files(Xow_domain_regy.All); // NOTE: remember to carry over the wikisource / page / index commands from the existing xowa_build_cfg.gfs; also, only run the xowa_build_cfg.gfs once; DATE:2013-10-15; last run: DATE:2014-09-09 + } + public void Bld_import_list(String... ary) { + int ary_len = ary.length; + Bry_bfr bfr = Bry_bfr_.Reset(255); + Wmf_latest_parser parser = new Wmf_latest_parser(); + Bfr_arg__time time_fmtr = new Bfr_arg__time(); + for (int i = 0; i < ary_len; i++) + Bld_import_list_itm2(bfr, parser, time_fmtr, ary, i); + Io_mgr.Instance.SaveFilStr("C:\\xowa\\user\\temp.txt", bfr.To_str()); + } + private void Bld_import_list_itm2(Bry_bfr bfr, Wmf_latest_parser parser, Bfr_arg__time time_fmtr, String[] ary, int i) { + String domain_str = ary[i]; + byte[] domain_bry = Bry_.new_a7(domain_str); + Xow_domain_itm domain_itm = Xow_domain_itm_.parse(domain_bry); + byte[] wmf_key_bry = Bry_.Replace(Xow_abrv_wm_.To_abrv(domain_itm), Byte_ascii.Dash, Byte_ascii.Underline); + String wmf_key = String_.new_u8(wmf_key_bry); + String url = "https://dumps.wikimedia.org/" + wmf_key + "/latest"; + byte[] latest_html = null; + for (int j = 0; j < 5; ++j) { + latest_html = Io_mgr.Instance.DownloadFil_args("", Io_url_.Empty).Exec_as_bry(url); + if (latest_html != null) break; + Tfds.Dbg("fail|" + domain_str + "|" + url); + if (j == 4) return; + } + parser.Parse(latest_html); + Xowm_dump_file dump_file = new Xowm_dump_file(domain_str, "latest", Xowm_dump_type_.Str__pages_articles); + dump_file.Server_url_(Xowm_dump_file_.Server_wmf_https); + byte[] pages_articles_key = Bry_.new_a7(wmf_key + "-latest-pages-articles.xml.bz2"); + Wmf_latest_itm latest_itm = parser.Get_by(pages_articles_key); + if (latest_itm == null) {Tfds.Dbg("missing|" + domain_str + "|" + url); return;} // NOTE: commonswiki missing entry for commonswiki-latest-pages-articles.xml.bz2 DATE:2016-05-01 + Tfds.Dbg("pass|" + domain_str + "|" + url); + bfr.Add(domain_bry).Add_byte_pipe(); + bfr.Add_str_u8(dump_file.File_url()).Add_byte_pipe(); + bfr.Add(Xow_domain_tid_.Get_type_as_bry(domain_itm.Domain_type_id())).Add_byte_pipe(); + long src_size = latest_itm.Size(); + bfr.Add_long_variable(src_size).Add_byte_pipe(); + bfr.Add_str_a7(gplx.core.ios.Io_size_.To_str(src_size)).Add_byte_pipe(); + time_fmtr.Seconds_(Math_.Div_safe_as_long(src_size, 1000000)).Bfr_arg__add(bfr); + bfr.Add_byte_pipe(); + bfr.Add_str_a7(latest_itm.Date().XtoStr_fmt_yyyy_MM_dd_HH_mm()); + bfr.Add_byte_pipe(); + bfr.Add_str_a7(dump_file.Dump_date()); + bfr.Add_byte_nl(); + } + /* + private void Bld_import_list_itm(Bry_bfr bfr, Xowm_dump_file dump_file, Bry_fmtr_arg_time time_fmtr, String[] ary, int i) { + String itm = ary[i]; + dump_file.Ctor(itm, "latest", Xowm_dump_type_.Str__pages_articles); + int count = 0; + while (count++ < 1) { + dump_file.Server_url_(Xowm_dump_file_.Server_wmf); + if (dump_file.Connect()) break; + Tfds.WriteText(String_.Format("retrying: {0} {1}\n", count, dump_file.File_modified())); + Thread_adp_.Sleep(15000); // wait for connection to reset + } + if (count == 10) { + Tfds.WriteText(String_.Format("failed: {0}\n", dump_file.File_url())); + return; + } + else + Tfds.WriteText(String_.Format("passed: {0}\n", itm)); + bfr.Add_str(itm).Add_byte_pipe(); + bfr.Add_str(dump_file.File_url()).Add_byte_pipe(); + bfr.Add(Xow_domain_tid_.Get_type_as_bry(dump_file.Wiki_type().Wiki_tid())).Add_byte_pipe(); +// Xol_lang_stub lang_itm = Xol_lang_stub_.Get_by_key(wiki_type.Lang_key()); +// if (lang_itm == null) lang_itm = Xol_lang_stub_.Get_by_key(Xol_lang_itm_.Key_en); // commons, species, meta, etc will have no lang +// bfr.Add(lang_itm.Local_name()).Add_byte_pipe(); +// bfr.Add(lang_itm.Canonical_name()).Add_byte_pipe(); + long src_size = dump_file.File_len(); + bfr.Add_long_variable(src_size).Add_byte_pipe(); + bfr.Add_str(gplx.core.ios.Io_size_.To_str(src_size)).Add_byte_pipe(); + time_fmtr.Seconds_(Math_.Div_safe_as_long(src_size, 1000000)).XferAry(bfr, 0); + bfr.Add_byte_pipe(); + bfr.Add_str(dump_file.File_modified().XtoStr_fmt_yyyy_MM_dd_HH_mm()); + bfr.Add_byte_pipe(); +// bfr.Add_str(String_.Concat_with_obj(",", (Object[])dump_file.Dump_available_dates())); +// bfr.Add_byte_pipe(); + bfr.Add_str(dump_file.Dump_date()); + bfr.Add_byte_nl(); + Thread_adp_.Sleep(1000); + } + */ + public void Bld_cfg_files(String... ary) { + Bry_bfr bfr = Bry_bfr_.Reset(255); + gplx.xowa.bldrs.wiki_cfgs.Xoi_wiki_props_api api = new gplx.xowa.bldrs.wiki_cfgs.Xoi_wiki_props_api(); + gplx.xowa.bldrs.wiki_cfgs.Xoi_wiki_props_wiki wiki = new gplx.xowa.bldrs.wiki_cfgs.Xoi_wiki_props_wiki(); + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) { + String wiki_domain = ary[i]; + try { + byte[] xml = api.Exec_api(api.Api_src(wiki_domain)); + wiki.Wiki_domain_(Bry_.new_a7(wiki_domain)); + api.Parse(wiki, String_.new_u8(xml)); + api.Build_cfg(bfr, wiki); + } + catch (Exception e) { + Console_adp__sys.Instance.Write_str_w_nl(Err_.Message_gplx_full(e)); + } + } + bfr.Add_str_a7("app.bldr.wiki_cfg_bldr.run;").Add_byte_nl(); + Io_mgr.Instance.SaveFilStr("C:\\user\\xowa_build_cfg.gfs", bfr.To_str()); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_unzip.java b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_unzip.java index a27517de8..94048e65f 100644 --- a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_unzip.java +++ b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_cmd_wiki_unzip.java @@ -13,3 +13,36 @@ 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.bldrs.installs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.gfui.*; import gplx.gfui.kits.core.*; +import gplx.core.threads.*; +class Xoi_cmd_wiki_unzip extends Gfo_thread_cmd_unzip implements Gfo_thread_cmd { public static final String KEY_dump = "wiki.unzip"; + public Xoi_cmd_wiki_unzip(Xoi_setup_mgr install_mgr, String wiki_key, String dump_date, String dump_type) {this.install_mgr = install_mgr; this.Owner_(install_mgr); this.wiki_key = wiki_key; this.dump_date = dump_date; this.dump_type = dump_type;} private Xoi_setup_mgr install_mgr; String wiki_key, dump_date, dump_type; + @Override public String Async_key() {return KEY_dump;} + @Override public byte Async_init() { + Xoae_app app = install_mgr.App(); Gfui_kit kit = app.Gui_mgr().Kit(); + Xowe_wiki wiki = app.Wiki_mgr().Get_by_or_make(Bry_.new_u8(wiki_key)); + Io_url wiki_dir = wiki.Import_cfg().Src_dir(); + Io_url[] urls = Io_mgr.Instance.QueryDir_args(wiki_dir).Recur_(false).FilPath_("*.xml.bz2").ExecAsUrlAry(); + if (urls.length == 0) { + kit.Ask_ok(GRP_KEY, "dump.unzip_latest.file_missing", "Could not find a dump file for ~{0} in ~{1}", wiki_key, wiki_dir.Raw()); + return Gfo_thread_cmd_.Init_cancel_step; + } + Io_url src = urls[urls.length - 1]; + Io_url trg = app.Fsys_mgr().Wiki_dir().GenSubFil_nest(wiki_key, src.NameOnly()); // NOTE: NameOnly() will strip trailing .bz2; EX: a.xml.bz2 -> a.xml + super.Init(app.Usr_dlg(), app.Gui_mgr().Kit(), app.Prog_mgr().App_decompress_bz2(), app.Prog_mgr().App_decompress_zip(), app.Prog_mgr().App_decompress_gz(), src, trg); + this.Term_cmd_for_src_(Term_cmd_for_src_move); + this.Term_cmd_for_src_url_(app.Fsys_mgr().Wiki_dir().GenSubFil_nest("#dump", "done", src.NameAndExt())); + if (Io_mgr.Instance.ExistsFil(trg)) { + int rslt = kit.Ask_yes_no_cancel(GRP_KEY, "target_exists", "Target file already exists: '~{0}'.\nDo you want to delete it?", trg.Raw()); + switch (rslt) { + case Gfui_dlg_msg_.Btn_yes: Io_mgr.Instance.DeleteFil(trg); break; + case Gfui_dlg_msg_.Btn_no: return Gfo_thread_cmd_.Init_cancel_step; + case Gfui_dlg_msg_.Btn_cancel: return Gfo_thread_cmd_.Init_cancel_all; + default: throw Err_.new_unhandled(rslt); + } + } + return Gfo_thread_cmd_.Init_ok; + } + static final String GRP_KEY = "xowa.thread.dump.unzip"; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_dump_mgr.java b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_dump_mgr.java index a27517de8..271b410bc 100644 --- a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_dump_mgr.java +++ b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_dump_mgr.java @@ -13,3 +13,12 @@ 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.bldrs.installs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.xowa.bldrs.wms.dumps.*; +public class Xoi_dump_mgr { + public static boolean Import_bz2_by_stdout(Xoa_app app) {return app.Cfg().Get_bool_app_or("xowa.bldr.import.apps.bz2_stdout.enabled", true);} // CFG: Cfg__ + public static String[] Server_urls(Xoa_app app) { + String[] or = String_.Ary(Xowm_dump_file_.Server_your_org, Xowm_dump_file_.Server_wmf_https, Xowm_dump_file_.Server_c3sl, Xowm_dump_file_.Server_masaryk); // promote your.org to primary url; DATE:2016-08-07 + return app.Cfg().Get_strary_app_or("xowa.bldr.import.dump_servers", ",", or); // CFG: Cfg__ + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_mirror_parser.java b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_mirror_parser.java index a27517de8..42f904089 100644 --- a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_mirror_parser.java +++ b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_mirror_parser.java @@ -13,3 +13,33 @@ 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.bldrs.installs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +public class Xoi_mirror_parser { + public String[] Parse(String raw_str) { + if (String_.Len_eq_0(raw_str)) return String_.Ary_empty; + byte[] raw = Bry_.new_u8(raw_str); + List_adp rv = List_adp_.New(); + int pos = 0; + while (true) { + int bgn = Bry_find_.Find_fwd(raw, CONST_href_bgn, pos); if (bgn == Bry_find_.Not_found) break; + bgn += CONST_href_bgn.length; + int end = Bry_find_.Find_fwd(raw, CONST_href_end, bgn); if (end == Bry_find_.Not_found) return String_.Ary_empty; + byte[] date = Bry_.Mid(raw, bgn, end); + pos = end + CONST_href_end.length; + if (Bry_.Match(date, CONST_date_parent_dir)) continue; + int date_pos_last = date.length - 1; + if (date_pos_last == -1) return String_.Ary_empty; + if (date[date_pos_last] == Byte_ascii.Slash) date = Bry_.Mid(date, 0, date_pos_last); // trim trailing /; EX: "20130101/" -> "20130101" + rv.Add(String_.new_u8(date)); + } + return rv.To_str_ary(); + } private static final byte[] CONST_href_bgn = Bry_.new_a7(" -1; i--) { + String itm = ary[i]; + if (CompareAble_.Is(CompareAble_.Less_or_same, itm, comp)) return itm; + } + return ""; + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_mirror_parser_tst.java b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_mirror_parser_tst.java index a27517de8..c11c2ed99 100644 --- a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_mirror_parser_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_mirror_parser_tst.java @@ -13,3 +13,46 @@ 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.bldrs.installs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import org.junit.*; +public class Xoi_mirror_parser_tst { + @Test public void Basic() { + Tst_parse(String_.Concat_lines_nl + ( "" + , "" + , "" + , "" + , "Index of /simplewiki/" + , "" + , "" + , "" + , "

    Index of /simplewiki/

    " + , "
    " + , "" + , "" + , "" + , "" + , "" + , "" + , "" + , "" + , "" + , "
    NameLast ModifiedSizeType
    Parent Directory/ -  Directory
    20120516/2012-May-17 01:04:39-  Directory
    20121220/2012-Dec-20 20:15:55-  Directory
    20130214/2013-Feb-14 06:28:41-  Directory
    latest/2013-Feb-14 06:28:41-  Directory
    " + , "
    " + , "
    lighttpd
    " + , "" + , "" + ), String_.Ary("20120516", "20121220", "20130214", "latest")); + } + @Test public void Find_last_lte() { + Tst_find_last_lte(String_.Ary("20120516", "20121220", "20130214", "latest"), "20130101", "20121220"); + Tst_find_last_lte(String_.Ary("20120516", "20121220", "20130214", "latest"), "20120101", ""); + } + private void Tst_parse(String raw, String[] expd) { + Xoi_mirror_parser parser = new Xoi_mirror_parser(); + Tfds.Eq_ary_str(expd, parser.Parse(raw)); + } + private void Tst_find_last_lte(String[] ary, String comp, String expd) { + Tfds.Eq(expd, Xoi_mirror_parser.Find_last_lte(ary, comp)); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_setup_mgr.java b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_setup_mgr.java index a27517de8..126834677 100644 --- a/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_setup_mgr.java +++ b/400_xowa/src/gplx/xowa/bldrs/installs/Xoi_setup_mgr.java @@ -13,3 +13,28 @@ 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.bldrs.installs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.gfui.*; import gplx.xowa.bldrs.setups.addons.*; +import gplx.xowa.bldrs.setups.maints.*; +public class Xoi_setup_mgr implements Gfo_invk { + public Xoi_setup_mgr(Xoae_app app) { + this.app = app; + cmd_mgr = new Xoi_cmd_mgr(this); + maint_mgr = new Xoa_maint_mgr(app); + } + public void Init_by_app(Xoae_app app) { + addon_mgr.Init_by_app(app); + } + public Xoae_app App() {return app;} private Xoae_app app; + public Xoi_cmd_mgr Cmd_mgr() {return cmd_mgr;} private Xoi_cmd_mgr cmd_mgr; + public Xoi_addon_mgr Addon_mgr() {return addon_mgr;} private Xoi_addon_mgr addon_mgr = new Xoi_addon_mgr(); + public Xoa_maint_mgr Maint_mgr() {return maint_mgr;} private Xoa_maint_mgr maint_mgr; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_cmds)) return cmd_mgr; + else if (ctx.Match(k, Invk_addons)) return addon_mgr; + else if (ctx.Match(k, Invk_maint)) return maint_mgr; + else return Gfo_invk_.Rv_unhandled; + } + static final String Invk_cmds = "cmds", Invk_addons = "addons", Invk_maint = "maint"; + static final String GRP_KEY = "xowa.setup"; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/setups/addons/Xoi_addon_mgr.java b/400_xowa/src/gplx/xowa/bldrs/setups/addons/Xoi_addon_mgr.java index a27517de8..ab37995d1 100644 --- a/400_xowa/src/gplx/xowa/bldrs/setups/addons/Xoi_addon_mgr.java +++ b/400_xowa/src/gplx/xowa/bldrs/setups/addons/Xoi_addon_mgr.java @@ -13,3 +13,14 @@ 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.bldrs.setups.addons; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.setups.*; +public class Xoi_addon_mgr implements Gfo_invk { + Xoi_firefox_installer Firefox() {return firefox;} private Xoi_firefox_installer firefox = new Xoi_firefox_installer(); + public void Init_by_app(Xoae_app app) { + firefox.Init_by_app(app); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_firefox)) return firefox; + else return Gfo_invk_.Rv_unhandled; + } private static final String Invk_firefox = "firefox"; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/setups/addons/Xoi_firefox_installer.java b/400_xowa/src/gplx/xowa/bldrs/setups/addons/Xoi_firefox_installer.java index a27517de8..3c50061ae 100644 --- a/400_xowa/src/gplx/xowa/bldrs/setups/addons/Xoi_firefox_installer.java +++ b/400_xowa/src/gplx/xowa/bldrs/setups/addons/Xoi_firefox_installer.java @@ -13,3 +13,50 @@ 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.bldrs.setups.addons; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.setups.*; +import gplx.core.ios.zips.*; import gplx.core.envs.*; +import gplx.xowa.apps.fsys.*; +public class Xoi_firefox_installer implements Gfo_invk { + private Io_url src_xpi, trg_xpi; + private Io_url trg_xpi_package; + private Process_adp program = new Process_adp(); + public void Init_by_app(Xoae_app app) { + src_xpi = app.Fsys_mgr().Bin_any_dir().GenSubFil_nest("firefox", "xowa_viewer", "default", "xowa_viewer@piotrex.xpi"); + trg_xpi = app.Fsys_mgr().Bin_any_dir().GenSubFil_nest("firefox", "xowa_viewer", "install", "xowa_viewer@piotrex.xpi"); + trg_xpi_package = trg_xpi.OwnerDir().GenSubDir("package"); + Xoa_fsys_eval cmd_eval = app.Url_cmd_eval(); + Process_adp.ini_(this, app.Usr_dlg(), program, cmd_eval, Process_adp.Run_mode_async, 0, "firefox", "\"~{url}\"", "url"); + } + public void Install_via_process() { + Generate(); + program.Run(trg_xpi.Raw()); + } + public void Generate() { + Io_mgr.Instance.CopyFil(src_xpi, trg_xpi, true); + Io_zip_mgr_base.Instance.Unzip_to_dir(trg_xpi, trg_xpi_package); + Pref_gen(); + Io_zip_mgr_base.Instance.Zip_dir(trg_xpi_package, trg_xpi); + } + private void Pref_gen() { + Io_url prefs_fil = trg_xpi_package.GenSubFil_nest("defaults", "preferences", "prefs.js"); + String prefs_str = Io_mgr.Instance.LoadFilStr(prefs_fil); + prefs_str = Pref_update(prefs_str, "extensions.xowa_viewer.xowa_app", Env_.AppUrl().Raw()); + Io_mgr.Instance.SaveFilStr(prefs_fil, prefs_str); + } + public static String Pref_update(String src, String key, String val) { + String find = String_.Format("pref(\"{0}\"", key); // EX: 'pref("key"' + int bgn = String_.FindFwd(src, find); // look for 'pref...' + if (bgn == String_.Find_none) return src; // key not found; return; + int end = String_.FindFwd(src, "\n", bgn + String_.Len(find)); // look for '\n'; note that this will trim any comments; EX: pref("key", "val"); // comment will be lost + if (end == String_.Find_none) return src; // nl not found; return; + String repl = String_.Format("{0}, \"{1}\");", find, val); // EX: 'pref("key", "val");' + return String_.Mid(src, 0, bgn) + + repl + + String_.Mid(src, end); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_install)) Install_via_process(); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_install = "install"; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/setups/addons/Xoi_firefox_installer_tst.java b/400_xowa/src/gplx/xowa/bldrs/setups/addons/Xoi_firefox_installer_tst.java index a27517de8..993ad4e9a 100644 --- a/400_xowa/src/gplx/xowa/bldrs/setups/addons/Xoi_firefox_installer_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/setups/addons/Xoi_firefox_installer_tst.java @@ -13,3 +13,29 @@ 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.bldrs.setups.addons; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.setups.*; +import org.junit.*; +import gplx.core.ios.*; +public class Xoi_firefox_installer_tst { + private Xoi_firefox_pref_fxt fxt = new Xoi_firefox_pref_fxt(); + @Test public void Pref_update() { + fxt.Test_pref_update(String_.Concat_lines_nl + ( "pref(\"key_0\", \"val_0\"); // comment_0" + , "pref(\"key_1\", \"val_1\"); // comment_1" + , "pref(\"key_2\", \"val_2\"); // comment_2" + ) + , "key_1", "val_1_updated" + , String_.Concat_lines_nl + ( "pref(\"key_0\", \"val_0\"); // comment_0" + , "pref(\"key_1\", \"val_1_updated\");" + , "pref(\"key_2\", \"val_2\"); // comment_2" + ) + ); + } +} +class Xoi_firefox_pref_fxt { + public void Test_pref_update(String src, String key, String val, String expd) { + String actl = Xoi_firefox_installer.Pref_update(src, key, val); + Tfds.Eq_str_lines(expd, actl); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_dump_itm.java b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_dump_itm.java index a27517de8..4dd4750a3 100644 --- a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_dump_itm.java +++ b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_dump_itm.java @@ -13,3 +13,23 @@ 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.bldrs.setups.maints; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.setups.*; +public class Wmf_dump_itm implements gplx.CompareAble { + public byte[] Wiki_abrv() {return wiki_abrv;} public void Wiki_abrv_(byte[] v) {this.wiki_abrv = v;} private byte[] wiki_abrv; // EX: enwiki + public DateAdp Dump_date() {return dump_date;} public void Dump_date_(DateAdp v) {this.dump_date = v;} private DateAdp dump_date; // EX: 20140304 + public DateAdp Status_time() {return status_time;} public void Status_time_(DateAdp v) {this.status_time = v;} private DateAdp status_time; // EX: 2014-03-15 23:22:06 + public byte[] Status_msg() {return status_msg;} // EX: Dump in progress / Dump complete + public void Status_msg_(byte[] v) { + this.status_msg = v; + if (Bry_.Eq(status_msg, Status_msg_dump_complete)) + status_tid = Status_tid_complete; + else if (Bry_.Eq(status_msg, Status_msg_dump_in_progress)) + status_tid = Status_tid_working; + else + status_tid = Status_tid_error; + } private byte[] status_msg; + public byte Status_tid() {return status_tid;} private byte status_tid; + public int compareTo(Object obj) {Wmf_dump_itm comp = (Wmf_dump_itm)obj; return Bry_.Compare(wiki_abrv, comp.wiki_abrv);} + private static byte[] Status_msg_dump_complete = Bry_.new_a7("Dump complete"), Status_msg_dump_in_progress = Bry_.new_a7("Dump in progress"); + public static final byte Status_tid_complete = 0, Status_tid_working = 1, Status_tid_error = 2; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_dump_list_parser.java b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_dump_list_parser.java index a27517de8..df0c6189c 100644 --- a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_dump_list_parser.java +++ b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_dump_list_parser.java @@ -13,3 +13,60 @@ 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.bldrs.setups.maints; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.setups.*; +public class Wmf_dump_list_parser { + public Wmf_dump_itm[] Parse(byte[] src) { + Ordered_hash itms = Ordered_hash_.New_bry(); + int pos = 0; + while (true) { + int a_pos = Bry_find_.Find_fwd(src, Find_anchor, pos); if (a_pos == Bry_find_.Not_found) break; // no more anchors found + pos = a_pos + Find_anchor.length; + try { + Wmf_dump_itm itm = new Wmf_dump_itm(); + if (!Parse_href(itm, src, a_pos)) continue; // anchor not parseable; not a link to a wmf dump + if (itms.Has(itm.Wiki_abrv())) continue; // ignore dupes + itms.Add(itm.Wiki_abrv(), itm); + itm.Status_time_(Parse_status_time(src, a_pos)); + itm.Status_msg_(Parse_status_msg(src, a_pos)); + } catch (Exception e) {Err_.Noop(e);} + } + return (Wmf_dump_itm[])itms.To_ary(Wmf_dump_itm.class); + } + private boolean Parse_href(Wmf_dump_itm itm, byte[] src, int a_pos) { // EX: http://dumps.wikimedia.org/enwiki/20130807 + int href_pos = Bry_find_.Find_fwd(src, Find_href, a_pos); if (href_pos == Bry_find_.Not_found) return false; // no
  • ; something bad happened + int href_bgn_pos = Bry_find_.Find_fwd(src, Byte_ascii.Quote, href_pos + Find_href.length); + int href_end_pos = Bry_find_.Find_fwd(src, Byte_ascii.Quote, href_bgn_pos + 1); if (href_end_pos == Bry_find_.Not_found) return false; + byte[] href_bry = Bry_.Mid(src, href_bgn_pos + 1, href_end_pos); + int date_end = href_bry.length; + int date_bgn = Bry_find_.Find_bwd(href_bry, Byte_ascii.Slash); if (date_bgn == Bry_find_.Not_found) return false; + byte[] date_bry = Bry_.Mid(href_bry, date_bgn + 1, date_end); if (date_bry.length == 0) return false; // anchors like "/other_static_dumps" should be skipped + if (Bry_.Has(date_bry, Bry_.new_u8("legal.html"))) return false; + if (Bry_.Has(date_bry, Bry_.new_u8("Privacy_policy"))) return false; + DateAdp date = DateAdp_.parse_fmt(String_.new_a7(date_bry), "yyyyMMdd"); + itm.Dump_date_(date); + int abrv_end = date_bgn; + int abrv_bgn = Bry_find_.Find_bwd(href_bry, Byte_ascii.Slash, abrv_end); if (abrv_bgn == Bry_find_.Not_found) abrv_bgn = -1; // "enwiki/20130708" + byte[] abrv_bry = Bry_.Mid(href_bry, abrv_bgn + 1, abrv_end); + itm.Wiki_abrv_(Bry_.Replace(abrv_bry, Byte_ascii.Underline, Byte_ascii.Dash)); + return true; + } + private DateAdp Parse_status_time(byte[] src, int a_pos) { + int li_pos = Bry_find_.Find_bwd(src, Find_li, a_pos); if (li_pos == Bry_find_.Not_found) return null; + int bgn = Bry_find_.Find_fwd(src, Byte_ascii.Gt, li_pos + Find_li.length); if (bgn == Bry_find_.Not_found) return null; + byte[] rv_bry = Bry_.Mid(src, bgn + 1, a_pos); + return DateAdp_.parse_fmt(String_.Trim(String_.new_a7(rv_bry)), "yyyy-MM-dd HH:mm:ss"); + } + private byte[] Parse_status_msg(byte[] src, int a_pos) { + int span_pos = Bry_find_.Find_fwd(src, Find_span_bgn, a_pos); if (span_pos == Bry_find_.Not_found) return null; + int bgn = Bry_find_.Find_fwd(src, Byte_ascii.Gt, span_pos + Find_span_bgn.length); if (bgn == Bry_find_.Not_found) return null; + int end = Bry_find_.Find_fwd(src, Find_span_end, bgn); if (end == Bry_find_.Not_found) return null; + return Bry_.Mid(src, bgn + 1, end); + } + private static byte[] + Find_anchor = Bry_.new_a7("") + ; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_dump_list_parser_tst.java b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_dump_list_parser_tst.java index a27517de8..36ef5bb21 100644 --- a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_dump_list_parser_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_dump_list_parser_tst.java @@ -13,3 +13,123 @@ 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.bldrs.setups.maints; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.setups.*; +import org.junit.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; +public class Wmf_dump_list_parser_tst { + @Before public void init() {fxt.Clear();} private Wmf_dump_list_parser_fxt fxt = new Wmf_dump_list_parser_fxt(); + @Test public void Parse() { + fxt.Test_parse + ( "
  • 2013-07-17 00:32:33 enwiki: Dump complete
  • " + , fxt.itm("enwiki", "20130708", Wmf_dump_itm.Status_tid_complete, "Dump complete", "2013-07-17 00:32:33") + ); + fxt.Test_parse(String_.Concat_lines_nl + ( "
  • 2013-07-24 02:02:13 kawiki: Dump in progress
  • " + , "
    • 2013-07-24 00:54:55 in-progress All pages with complete page edit history (.bz2)
      2013-07-24 02:02:13: kawiki (ID 18587) 22046 pages (5.5|11140.9/sec all|curr), 869000 revs (215.2|505.3/sec all|curr), 99.9%|99.9% prefetched (all|curr), ETA 2013-07-24 04:09:41 [max 2514872]
      " + , "
      • kawiki-20130724-pages-meta-history.xml.bz2 245.2 MB (written)
    " + ) + , fxt.itm("kawiki", "20130724", Wmf_dump_itm.Status_tid_working, "Dump in progress", "2013-07-24 02:02:13") + ); + fxt.Test_parse + ( "
  • 2013-07-17 00:32:33 enwiki: Error
  • " + , fxt.itm("enwiki", "20130708", Wmf_dump_itm.Status_tid_error, "Error", "2013-07-17 00:32:33") + ); + fxt.Test_parse + ( "
  • 2013-11-28 06:08:56 zh_classicalwiki: Dump complete
  • " + , fxt.itm("zh-classicalwiki", "20131128", Wmf_dump_itm.Status_tid_complete, "Dump complete", "2013-11-28 06:08:56") + ); + } +// @Test public void Update() { // MAINT:QUARTERLY:2017-03-28; COUNT=830; must run home/wiki/Dashboard/Wiki_maintenance and click "update dump status" +// Hash_adp_bry excluded_domains = Hash_adp_bry.cs().Add_many_str +// ( "advisory.wikipedia.org", "beta.wikiversity.org", "donate.wikipedia.org", "login.wikipedia.org" +// , "nostalgia.wikipedia.org", "outreach.wikipedia.org", "quality.wikipedia.org", "sources.wikipedia.org" +// , "strategy.wikipedia.org", "ten.wikipedia.org", "test2.wikipedia.org", "test.wikipedia.org" +// , "usability.wikipedia.org", "vote.wikipedia.org" +// , "bd.wikimedia.org", "dk.wikimedia.org", "mx.wikimedia.org", "nyc.wikimedia.org", "nz.wikimedia.org", "pa-us.wikimedia.org", "rs.wikimedia.org", "ua.wikimedia.org" +// ); +// Wmf_dump_itm[] itms = new Wmf_dump_list_parser().Parse(Io_mgr.Instance.LoadFilBry("C:\\xowa\\bin\\any\\xowa\\xtns\\xowa\\maintenance\\backup-index.html")); +// Array_.Sort(itms); +// Bry_bfr sql_bfr = Bry_bfr_.New(); +// Bry_bfr bld_bfr = Bry_bfr_.New(); +// int itms_len = itms.length; +// int counter = 1; +// for (int i = 0; i < itms_len; i++) { +// Wmf_dump_itm itm = itms[i]; +// byte[] abrv = itm.Wiki_abrv(); +// if (Bry_.Eq(abrv, Bry_.new_a7("testwikidatawiki"))) continue; +// byte[] domain_bry = Xow_abrv_wm_.Parse_to_domain_bry(abrv); +// if (domain_bry == null) continue; // not a standard WMF wiki; ignore +// if (Bry_find_.Find_fwd(domain_bry, Bry_.new_a7("wikimania")) != Bry_find_.Not_found) continue; +// if (excluded_domains.Has(domain_bry)) continue; +// Xow_domain_itm domain_itm = Xow_domain_itm_.parse(domain_bry); +// byte[] tid_name = Xto_display_name(Xow_domain_tid_.Get_type_as_bry(domain_itm.Domain_type_id())); +// sql_bfr +// .Add_byte(Byte_ascii.Paren_bgn) +// .Add_int_variable(counter++) +// .Add_byte(Byte_ascii.Comma) +// .Add_int_variable(1) +// .Add_byte(Byte_ascii.Comma) +// .Add_byte(Byte_ascii.Apos) +// .Add(domain_itm.Lang_orig_key()) +// .Add_byte(Byte_ascii.Apos) +// .Add_byte(Byte_ascii.Comma) +// .Add_byte(Byte_ascii.Apos) +// .Add(tid_name) +// .Add_byte(Byte_ascii.Apos) +// .Add_byte(Byte_ascii.Paren_end) +// .Add_byte(Byte_ascii.Comma) +// .Add_str_u8("--" + String_.new_u8(abrv)) +// .Add_byte_nl() +// ; +// bld_bfr +// .Add_byte(Byte_ascii.Comma) +// .Add_byte(Byte_ascii.Space) +// .Add_byte(Byte_ascii.Quote) +// .Add(domain_bry) +// .Add_byte(Byte_ascii.Quote) +// .Add_byte_nl() +// ; +// } +// Io_url temp = Io_url_.new_fil_("C:\\xowa\\user\\import_update.txt"); +// Io_mgr.Instance.SaveFilBfr(temp, sql_bfr); +//// Io_mgr.Instance.AppendFilBfr(temp, bld_bfr); +// } +// private static byte[] Xto_display_name(byte[] v) { +// if (Bry_.Eq(v, Xow_domain_tid_.Bry__wmforg)) return Bry_.new_a7("Wikimedia Foundation"); +// else if (Bry_.Eq(v, Xow_domain_tid_.Bry__species)) return Bry_.new_a7("Wikispecies"); +// else if (Bry_.Eq(v, Xow_domain_tid_.Bry__mediawiki)) return Bry_.new_a7("MediaWiki"); +// else return Bry_.Add(Byte_ascii.Case_upper(v[0]), Bry_.Mid(v, 1, v.length)); +// } +} +class Wmf_dump_list_parser_fxt { + public void Clear() {} + private Wmf_dump_list_parser parser = new Wmf_dump_list_parser(); + public String itm(String wiki_abrv, String dump_date, byte status_done, String status_msg, String status_time) { + return String_.Concat_with_str("\n", wiki_abrv, dump_date + , Byte_.To_str(status_done) + , status_msg + , status_time + ); + } + public void Test_parse(String raw, String... expd) { + Wmf_dump_itm[] actl = parser.Parse(Bry_.new_a7(raw)); + Tfds.Eq_str_lines(String_.Concat_lines_nl(expd), String_.Concat_lines_nl(Xto_str(actl))); + } + public String[] Xto_str(Wmf_dump_itm[] ary) { + int len = ary.length; + String[] rv = new String[len]; + for (int i = 0; i < len; i++) + rv[i] = Xto_str(ary[i]); + return rv; + } + public static String Xto_str(Wmf_dump_itm itm) { + DateAdp status_time = itm.Status_time(); + String status_time_str = status_time == null ? "" : status_time.XtoStr_fmt(DateAdp_.Fmt_iso8561_date_time); + return String_.Concat_with_str("\n", String_.new_a7(itm.Wiki_abrv()), itm.Dump_date().XtoStr_fmt("yyyyMMdd") + , Byte_.To_str(itm.Status_tid()) + , String_.new_a7(itm.Status_msg()) + , status_time_str + ); + + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_latest_itm.java b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_latest_itm.java index a27517de8..fcfbe0c92 100644 --- a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_latest_itm.java +++ b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_latest_itm.java @@ -13,3 +13,12 @@ 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.bldrs.setups.maints; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.setups.*; +public class Wmf_latest_itm { + public Wmf_latest_itm(byte[] name, DateAdp date, long size) { + this.name = name; this.date = date; this.size = size; + } + public byte[] Name() {return name;} private final byte[] name; + public DateAdp Date() {return date;} private final DateAdp date; + public long Size() {return size;} private final long size; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_latest_parser.java b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_latest_parser.java index a27517de8..b6b402801 100644 --- a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_latest_parser.java +++ b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_latest_parser.java @@ -13,3 +13,54 @@ 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.bldrs.setups.maints; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.setups.*; +import gplx.core.btries.*; import gplx.core.ios.*; +public class Wmf_latest_parser { + private Ordered_hash hash = Ordered_hash_.New_bry(); + private final Btrie_rv trv = new Btrie_rv(); + public int Count() {return hash.Count();} + public Wmf_latest_itm Get_at(int i) {return (Wmf_latest_itm)hash.Get_at(i);} + public Wmf_latest_itm Get_by(byte[] k) {return (Wmf_latest_itm)hash.Get_by(k);} + public Wmf_latest_itm[] To_ary() {return (Wmf_latest_itm[])hash.To_ary(Wmf_latest_itm.class);} + public void Parse(byte[] src) { + hash.Clear(); + Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); + byte[] name_bgn_bry = Bry_.new_a7("\n"); + byte[] date_end_bry = Bry_.new_a7(" "); +// byte[] size_bgn_bry = Bry_.new_a7(""); + Btrie_slim_mgr date_trie = Btrie_slim_mgr.cs() + .Add_bry("Jan", "01").Add_bry("Feb", "02").Add_bry("Mar", "03").Add_bry("Apr", "04").Add_bry("May", "05").Add_bry("Jun", "06") + .Add_bry("Jul", "07").Add_bry("Aug", "08").Add_bry("Sep", "09").Add_bry("Oct", "10").Add_bry("Nov", "11").Add_bry("Dec", "12") + ; +// Btrie_slim_mgr size_trie = Btrie_slim_mgr.cs() +// .Add_bry("B", " B").Add_bry("K", " KB").Add_bry("M", " MB").Add_bry("G", " GB"); + byte[] date_or = Bry_.new_a7("1970-01-01 00:00:00"); +// byte[] size_or = Bry_.new_a7("0 B"); + int size_end = 0; int src_len = src.length; + while (true) { + int name_bgn = Bry_find_.Move_fwd(src, name_bgn_bry, size_end, src_len); if (name_bgn == Bry_find_.Not_found) break; + int name_end = Bry_find_.Find_fwd(src, Byte_ascii.Quote, name_bgn, src_len); + byte[] name = Bry_.Mid(src, name_bgn, name_end); + int date_bgn = Bry_find_.Move_fwd(src, date_bgn_bry, name_end, src_len); if (date_bgn == Bry_find_.Not_found) {Gfo_usr_dlg_.Instance.Warn_many("", "", "date_bgn not found"); break;} + date_bgn = Bry_find_.Find_fwd_while_space_or_tab(src, date_bgn, src_len); if (date_bgn == Bry_find_.Not_found) {Gfo_usr_dlg_.Instance.Warn_many("", "", "date_bgn not found"); break;} + int date_end = Bry_find_.Find_fwd(src, date_end_bry, date_bgn, src_len); + byte[] date_bry = Bry_.Mid(src, date_bgn, date_end); + DateAdp date = DateAdp_.parse_fmt(String_.new_a7(Replace_or(tmp_bfr, date_trie, trv, date_bry, 3, date_or)), "dd-MM-yyyy HH:mm"); + int size_bgn = Bry_find_.Find_fwd_while_space_or_tab(src, date_end, src_len); if (size_bgn == Bry_find_.Not_found) {Gfo_usr_dlg_.Instance.Warn_many("", "", "size_bgn not found"); break;} + size_end = Bry_find_.Find_fwd(src, Byte_ascii.Cr, size_bgn, src_len); + byte[] size_bry = Bry_.Mid(src, size_bgn, size_end); + long size = Long_.parse_or(String_.new_u8(size_bry), -1); + Wmf_latest_itm itm = new Wmf_latest_itm(name, date, size); + hash.Add(name, itm); + } + } + private static byte[] Replace_or(Bry_bfr tmp_bfr, Btrie_slim_mgr trie, Btrie_rv trv, byte[] src, int pos, byte[] or) { + int src_len = src.length; + Object o = trie.Match_at(trv, src, pos, src_len); if (o == null) return or; + tmp_bfr.Add_mid(src, 0, pos); + tmp_bfr.Add((byte[])o); + tmp_bfr.Add_mid(src, trv.Pos(), src_len); + return tmp_bfr.To_bry_and_clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_latest_parser_tst.java b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_latest_parser_tst.java index a27517de8..330e1211c 100644 --- a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_latest_parser_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Wmf_latest_parser_tst.java @@ -13,3 +13,39 @@ 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.bldrs.setups.maints; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.setups.*; +import org.junit.*; import gplx.core.ios.*; +public class Wmf_latest_parser_tst { + @Before public void init() {fxt.Clear();} private Wmf_latest_parser_fxt fxt = new Wmf_latest_parser_fxt(); + @Test public void Parse() { + fxt.Test_parse + ( "\nenwiki-latest-pages-articles.xml.bz2 15-Jan-2015 05:43 11575640561\r\n" + , fxt.itm("enwiki-latest-pages-articles.xml.bz2", "2015-01-15 05:43", "10.781 GB") + ); + } +// @Test public void Smoke() { +// Wmf_latest_parser parser = new Wmf_latest_parser(); +// parser.Parse(Io_mgr.Instance.LoadFilBry("C:\\wmf_latest.html")); +// Tfds.Dbg(String_.Concat_lines_nl(Wmf_latest_parser_fxt.Xto_str_ary(parser.To_ary()))); +// } +} +class Wmf_latest_parser_fxt { + public void Clear() {} + private Wmf_latest_parser parser = new Wmf_latest_parser(); + public Wmf_latest_itm itm(String name, String date, String size) {return new Wmf_latest_itm(Bry_.new_a7(name), DateAdp_.parse_iso8561(date), Io_size_.parse_or(size, 0));} + public void Test_parse(String raw, Wmf_latest_itm... expd) { + parser.Parse(Bry_.new_a7(raw)); + Wmf_latest_itm[] actl = parser.To_ary(); + Tfds.Eq_str_lines(String_.Concat_lines_nl(Xto_str_ary(expd)), String_.Concat_lines_nl(Xto_str_ary(actl))); + } + public static String[] Xto_str_ary(Wmf_latest_itm[] ary) { + int len = ary.length; + String[] rv = new String[len]; + for (int i = 0; i < len; i++) + rv[i] = Xto_str(ary[i]); + return rv; + } + public static String Xto_str(Wmf_latest_itm itm) { + return String_.Concat_with_str("\n", String_.new_a7(itm.Name()), itm.Date().XtoStr_fmt_iso_8561(), Io_size_.To_str(itm.Size())); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Xoa_maint_mgr.java b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Xoa_maint_mgr.java index a27517de8..92eb77c97 100644 --- a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Xoa_maint_mgr.java +++ b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Xoa_maint_mgr.java @@ -13,3 +13,69 @@ 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.bldrs.setups.maints; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.setups.*; +import gplx.core.ios.*; +import gplx.xowa.wikis.domains.*; +import gplx.xowa.files.downloads.*; +public class Xoa_maint_mgr implements Gfo_invk { + public Xoa_maint_mgr(Xoae_app app) { + this.app = app; + wmf_dump_status_url = Wmf_dump_status_url(app); + wiki_mgr = new Xoa_maint_wikis_mgr(app); + } private Xoae_app app; private Io_url wmf_dump_status_url; + public Xoa_maint_wikis_mgr Wiki_mgr() {return wiki_mgr;} private Xoa_maint_wikis_mgr wiki_mgr; + public boolean Wmf_dump_status_loaded() {return wmf_dump_status_loaded;} private boolean wmf_dump_status_loaded; + public void Wmf_dump_status_loaded_assert() { + if (!wmf_dump_status_loaded) { + Wmf_status_parse(); + wmf_dump_status_loaded = true; + } + } + public void Wmf_status_update() { + Wmf_status_download(); + Wmf_status_parse(); + } + public boolean Wmf_status_download() { + String[] server_urls = gplx.xowa.bldrs.installs.Xoi_dump_mgr.Server_urls(app); + int len = server_urls.length; + Xof_download_wkr download_wkr = app.Wmf_mgr().Download_wkr(); + for (int i = 0; i < len; i++) { + String server_url = server_urls[i] + "backup-index.html"; + byte rslt = download_wkr.Download(true, server_url, wmf_dump_status_url, "downloading wmf status"); + if (rslt == IoEngine_xrg_downloadFil.Rslt_pass) return true; + } + app.Usr_dlg().Prog_many("", "", "could not download latest status"); + return false; + } + public boolean Wmf_status_parse() { + Wmf_dump_list_parser parser = new Wmf_dump_list_parser(); + Hash_adp_bry itms_hash = Hash_adp_bry.cs(); + Wmf_dump_itm[] itms = parser.Parse(Io_mgr.Instance.LoadFilBry(wmf_dump_status_url)); + int len = itms.length; + Xoa_app_.Usr_dlg().Log_many("", "", "maint.html count; count=~{0}", len); + for (int i = 0; i < len; i++) { + Wmf_dump_itm itm = itms[i]; + byte[] wiki_abrv = itm.Wiki_abrv(); + Xoa_app_.Usr_dlg().Log_many("", "", "maint.html itm; itm=~{0}", wiki_abrv); + byte[] wiki_domain = Xow_abrv_wm_.Parse_to_domain_bry(wiki_abrv); + if (wiki_domain == null) continue; // invalid wiki-name; ex: nycwikimedia + itms_hash.Add(wiki_domain, itm); + } + len = app.Wiki_mgr().Count(); + Xoa_app_.Usr_dlg().Log_many("", "", "maint.wiki_count; count=~{0}", len); + for (int i = 0; i < len; i++) { + Xowe_wiki wiki = app.Wiki_mgr().Get_at_or_null(i); + Xoa_app_.Usr_dlg().Log_many("", "", "maint.wiki_itm; wiki=~{0}", wiki.Domain_str()); + Wmf_dump_itm itm = (Wmf_dump_itm)itms_hash.Get_by_bry(wiki.Domain_bry()); if (itm == null) continue; + wiki.Maint_mgr().Wmf_dump_date_(itm.Dump_date()).Wmf_dump_done_(itm.Status_tid() == Wmf_dump_itm.Status_tid_complete).Wmf_dump_status_(itm.Status_msg()); + } + return true; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_wmf_status_update)) Wmf_status_update(); + else if (ctx.Match(k, Invk_wikis)) return wiki_mgr; + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_wmf_status_update = "wmf_status_update", Invk_wikis = "wikis"; + public static Io_url Wmf_dump_status_url(Xoae_app app) {return app.Fsys_mgr().Bin_xowa_dir().GenSubDir_nest("xtns", "xowa", "maintenance", "backup-index.html");} +} diff --git a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Xoa_maint_wikis_mgr.java b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Xoa_maint_wikis_mgr.java index a27517de8..5177db545 100644 --- a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Xoa_maint_wikis_mgr.java +++ b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Xoa_maint_wikis_mgr.java @@ -13,3 +13,33 @@ 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.bldrs.setups.maints; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.setups.*; +public class Xoa_maint_wikis_mgr implements Gfo_invk { + private final Ordered_hash hash = Ordered_hash_.New_bry(); + public Xoa_maint_wikis_mgr(Xoae_app app) {this.app = app;} private Xoae_app app; + public int Len() {return hash.Count();} + public Xowe_wiki Get_at(int i) { + if (init) Init(); + byte[] domain = (byte[])hash.Get_at(i); + Xowe_wiki wiki = app.Wiki_mgr().Get_by_or_make(domain); + wiki.Init_assert(); + return wiki; + } + public void Add(byte[] domain) {hash.Add_if_dupe_use_nth(domain, domain);} // NOTE: must be Add_if_dupe_use_nth to replace existing wikis + public void Init() { + int len = this.Len(); + for (int i = 0; i < len; i++) { + byte[] domain = (byte[])hash.Get_at(i); + Xowe_wiki wiki = app.Wiki_mgr().Get_by_or_make(domain); + wiki.Init_assert(); + } + init = false; + } + private boolean init = true; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_len)) return this.Len(); + else if (ctx.Match(k, Invk_get_at)) return this.Get_at(m.ReadInt("v")); + else return Gfo_invk_.Rv_unhandled; +// return this; + } private static final String Invk_len = "len", Invk_get_at = "get_at"; +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Xow_maint_mgr.java b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Xow_maint_mgr.java index a27517de8..4d53ee40a 100644 --- a/400_xowa/src/gplx/xowa/bldrs/setups/maints/Xow_maint_mgr.java +++ b/400_xowa/src/gplx/xowa/bldrs/setups/maints/Xow_maint_mgr.java @@ -13,3 +13,44 @@ 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.bldrs.setups.maints; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.setups.*; +public class Xow_maint_mgr implements Gfo_invk { + public Xow_maint_mgr(Xowe_wiki wiki) { + this.wiki = wiki; + maint_mgr = wiki.Appe().Setup_mgr().Maint_mgr(); + } private Xowe_wiki wiki; private Xoa_maint_mgr maint_mgr; + public DateAdp Wmf_dump_date() { + maint_mgr.Wmf_dump_status_loaded_assert(); + return wmf_dump_date; + } public Xow_maint_mgr Wmf_dump_date_(DateAdp v) {this.wmf_dump_date = v; return this;} private DateAdp wmf_dump_date; + public boolean Wmf_dump_done() {return wmf_dump_done;} public Xow_maint_mgr Wmf_dump_done_(boolean v) {this.wmf_dump_done = v; return this;} private boolean wmf_dump_done; + public byte[] Wmf_dump_status() {return wmf_dump_status;} public Xow_maint_mgr Wmf_dump_status_(byte[] v) {this.wmf_dump_status = v; return this;} private byte[] wmf_dump_status; + public DateAdp Wiki_dump_date() { + if (wiki_dump_date == null) + wiki_dump_date = wiki.Db_mgr().Dump_date_query(); + return wiki_dump_date; + } private DateAdp wiki_dump_date; + public boolean Wiki_update_needed() { + if (this.Wiki_dump_date() == null) return false; // will be null if a custom wiki (i.e.: not on http://dumps.wikimedia.org/backup-index.html) + if (this.Wmf_dump_date() == null) return false; // also null if custom wiki + return this.Wmf_dump_date().Diff(this.Wiki_dump_date()).Total_days().To_double() > 1; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_wmf_dump_date)) return DateAdp_.Xto_str_fmt_or(Wmf_dump_date(), "yyyy-MM-dd", ""); + else if (ctx.Match(k, Invk_wmf_dump_date_)) Wmf_dump_date_(m.ReadDate("v")); + else if (ctx.Match(k, Invk_wmf_dump_done)) return Yn.To_str(wmf_dump_done); + else if (ctx.Match(k, Invk_wmf_dump_done_)) wmf_dump_done = m.ReadYn("v"); + else if (ctx.Match(k, Invk_wmf_dump_status)) return String_.new_u8(wmf_dump_status); + else if (ctx.Match(k, Invk_wmf_dump_status_)) wmf_dump_status = m.ReadBry("v"); + else if (ctx.Match(k, Invk_wiki_dump_date)) return DateAdp_.Xto_str_fmt_or(Wiki_dump_date(), "yyyy-MM-dd", ""); + else if (ctx.Match(k, Invk_wiki_dump_date_)) wiki_dump_date = m.ReadDate("v"); + else if (ctx.Match(k, Invk_wiki_update_needed)) return Yn.To_str(Wiki_update_needed()); + else if (ctx.Match(k, Invk_wiki_dump_date_)) wiki_dump_date = m.ReadDate("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_wmf_dump_date = "wmf_dump_date", Invk_wmf_dump_date_ = "wmf_dump_date_", Invk_wmf_dump_done = "wmf_dump_done", Invk_wmf_dump_done_ = "wmf_dump_done_" + , Invk_wmf_dump_status = "wmf_dump_status", Invk_wmf_dump_status_ = "wmf_dump_status_", Invk_wiki_dump_date = "wiki_dump_date", Invk_wiki_dump_date_ = "wiki_dump_date_" + , Invk_wiki_update_needed = "wiki_update_needed" + ; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/setups/upgrades/Xoa_upgrade_mgr.java b/400_xowa/src/gplx/xowa/bldrs/setups/upgrades/Xoa_upgrade_mgr.java index a27517de8..ae0c25da8 100644 --- a/400_xowa/src/gplx/xowa/bldrs/setups/upgrades/Xoa_upgrade_mgr.java +++ b/400_xowa/src/gplx/xowa/bldrs/setups/upgrades/Xoa_upgrade_mgr.java @@ -13,3 +13,23 @@ 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.bldrs.setups.upgrades; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.setups.*; +import gplx.xowa.wikis.domains.*; +public class Xoa_upgrade_mgr { + public static void Check(Xoae_app app) { + Upgrade_history(app); + } + private static void Upgrade_history(Xoae_app app) { + Io_url old_history_dir = app.Usere().Fsys_mgr().App_data_dir(); + Io_url new_history_dir = app.Usere().Fsys_mgr().App_data_dir().GenSubDir("history"); + if (Io_mgr.Instance.ExistsDir(new_history_dir)) return; // new_history_dir exists; + app.Usr_dlg().Log_many("", "", "moving history files"); + Io_url[] old_history_fils = Io_mgr.Instance.QueryDir_args(old_history_dir).Recur_(false).ExecAsUrlAry(); + int len = old_history_fils.length; + for (int i = 0; i < len; i++) { + Io_url old_history_fil = old_history_fils[i]; + Io_mgr.Instance.CopyFil(old_history_fil, new_history_dir.GenSubFil(old_history_fil.NameAndExt()), false); + } + app.Usr_dlg().Log_many("", "", "moved history files: ~{0}", len); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/setups/upgrades/Xoa_upgrade_mgr_tst.java b/400_xowa/src/gplx/xowa/bldrs/setups/upgrades/Xoa_upgrade_mgr_tst.java index a27517de8..39983ea1d 100644 --- a/400_xowa/src/gplx/xowa/bldrs/setups/upgrades/Xoa_upgrade_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/setups/upgrades/Xoa_upgrade_mgr_tst.java @@ -13,3 +13,19 @@ 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.bldrs.setups.upgrades; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.setups.*; +import org.junit.*; +public class Xoa_upgrade_mgr_tst { + @Test public void Run() { + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + Io_url old_history_dir = app.Usere().Fsys_mgr().App_data_dir(); + Io_url new_history_dir = app.Usere().Fsys_mgr().App_data_dir().GenSubDir("history"); + Io_mgr.Instance.SaveFilStr(old_history_dir.GenSubFil("page_history.csv"), "test"); + Xoa_upgrade_mgr.Check(app); + Tfds.Eq("test", Io_mgr.Instance.LoadFilStr(old_history_dir.GenSubFil("page_history.csv"))); // old file still exists + Tfds.Eq("test", Io_mgr.Instance.LoadFilStr(new_history_dir.GenSubFil("page_history.csv"))); // new file exists + Io_mgr.Instance.SaveFilStr(new_history_dir.GenSubFil("page_history.csv"), "test1"); // dirty file + Xoa_upgrade_mgr.Check(app); // rerun + Tfds.Eq("test1", Io_mgr.Instance.LoadFilStr(new_history_dir.GenSubFil("page_history.csv"))); // dirty file remains (not replaced by old file) + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_dump_cbk.java b/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_dump_cbk.java index a27517de8..e0bf92773 100644 --- a/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_dump_cbk.java +++ b/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_dump_cbk.java @@ -13,3 +13,9 @@ 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.bldrs.sql_dumps; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.strings.*; +public interface Xosql_dump_cbk { + void On_fld_done(int fld_idx, byte[] src, int val_bgn, int val_end); + void On_row_done(); +} diff --git a/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_dump_parser.java b/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_dump_parser.java index a27517de8..bac7373a0 100644 --- a/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_dump_parser.java +++ b/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_dump_parser.java @@ -13,3 +13,147 @@ 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.bldrs.sql_dumps; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.flds.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; +public class Xosql_dump_parser { + private Xosql_dump_cbk cbk; + private Io_url src_fil; private int src_rdr_bfr_len = 8 * Io_mgr.Len_mb; + private Xosql_fld_hash cbk_flds; + private Ordered_hash tbl_flds; + public Xosql_dump_parser(Xosql_dump_cbk cbk, String... cbk_keys) { + this.cbk = cbk; + this.cbk_flds = Xosql_fld_hash.New(cbk_keys); + } + public void Src_fil_(Io_url v) {this.src_fil = v;} + public void Parse(Gfo_usr_dlg usr_dlg) { + Io_buffer_rdr rdr = Io_buffer_rdr.Null; + try { + // init bfrs, rdr + Bry_bfr val_bfr = Bry_bfr_.New(); + rdr = Io_buffer_rdr.new_(Io_stream_rdr_.New_by_url(src_fil), src_rdr_bfr_len); + byte[] bfr = rdr.Bfr(); int bfr_len = rdr.Bfr_len(), fld_idx = 0, cur_pos = 0; + + this.tbl_flds = Identify_flds(cbk_flds, bfr); + + // init fld_rdr + Gfo_fld_rdr fld_rdr = Gfo_fld_rdr.sql_(); + byte[] decode_regy = fld_rdr.Escape_decode(); + + byte mode_prv = Mode__sql_bgn; byte mode = Mode__sql_bgn; + boolean reading_file = true; + while (reading_file) { + if (cur_pos + 256 > bfr_len && rdr.Fil_pos() != rdr.Fil_len()) { // buffer 256 characters; can be 0, but erring on side of simplicity + rdr.Bfr_load_from(cur_pos); + cur_pos = 0; + bfr = rdr.Bfr(); + bfr_len = rdr.Bfr_len(); + } + if (cur_pos == bfr_len) break; + + byte b = bfr[cur_pos]; + switch (mode) { + case Mode__sql_bgn:// skip over header to 1st "VALUES" + cur_pos = Bry_find_.Find_fwd(bfr, Bry_insert_into, cur_pos); + if (cur_pos == Bry_find_.Not_found || cur_pos > bfr_len) {reading_file = false; continue;} + cur_pos = Bry_find_.Find_fwd(bfr, Bry_values, cur_pos); + if (cur_pos == Bry_find_.Not_found || cur_pos > bfr_len) throw Err_.new_wo_type("VALUES not found"); // something went wrong; + mode = Mode__fld; + cur_pos += Bry_values.length; + break; + case Mode__row_bgn: // assert "(" + switch (b) { + case Byte_ascii.Paren_bgn: mode = Mode__fld; break; + default: throw Err_.new_unhandled(mode); + } + ++cur_pos; + break; + case Mode__row_end: // handle 1st char after ")"; + switch (b) { + case Byte_ascii.Nl: break; // ignore \n + case Byte_ascii.Comma: mode = Mode__row_bgn; break; // handle ","; EX: "(1),(2)" + case Byte_ascii.Semic: mode = Mode__sql_bgn; break; // handle ";"; EX: "(1);INSERT INTO" + default: throw Err_.new_unhandled(mode); + } + ++cur_pos; + break; + case Mode__fld: // handle fld chars; EX: "(1,'ab')" + switch (b) { + case Byte_ascii.Space: // ws: skip; EX: "(1 , 2)"; "(1,\n2)" + case Byte_ascii.Nl: + break; + case Byte_ascii.Apos: // apos: switch modes; NOTE: never escape apos by doubling; will fail for empty fields; EX: ", '', ''"; DATE:2013-07-06 + mode = Mode__quote; + break; + case Byte_ascii.Backslash: // backslash: switch modes; + mode_prv = mode; + mode = Mode__escape; + break; + case Byte_ascii.Comma: // comma: end fld + Commit_fld(fld_idx++, val_bfr); + break; + case Byte_ascii.Paren_end: // paren_end: end fld and row + Commit_fld(fld_idx++, val_bfr); + cbk.On_row_done(); + fld_idx = 0; + mode = Mode__row_end; + break; + default: // all other chars; add to val_bfr + val_bfr.Add_byte(b); + break; + } + ++cur_pos; + break; + case Mode__quote: // add to val_bfr until quote encountered; also, handle backslashes; + switch (b) { + case Byte_ascii.Apos: mode = Mode__fld; break; + case Byte_ascii.Backslash: mode_prv = mode; mode = Mode__escape; break; + default: val_bfr.Add_byte(b); break; + } + ++cur_pos; + break; + case Mode__escape: // get escape_val from decode_regy; if unknown, just add original + byte escape_val = decode_regy[b]; + if (escape_val == Byte_ascii.Null) + val_bfr.Add_byte(Byte_ascii.Backslash).Add_byte(b); + else + val_bfr.Add_byte(escape_val); + mode = mode_prv; // switch back to prv_mode + ++cur_pos; + break; + default: throw Err_.new_unhandled(mode); + } + } + } + finally {rdr.Rls();} + } + private void Commit_fld(int fld_idx, Bry_bfr val_bfr) { + Xosql_fld_itm fld = (Xosql_fld_itm)tbl_flds.Get_at(fld_idx); // handle new flds added by MW, but not supported by XO; EX:hiddencat and pp_sortkey; DATE:2014-04-28 + if (fld.Uid() != Int_.Max_value) + cbk.On_fld_done(fld.Uid(), val_bfr.Bfr(), 0, val_bfr.Len()); + val_bfr.Clear(); + } + private static Ordered_hash Identify_flds(Xosql_fld_hash cbk_hash, byte[] raw) { + // parse tbl def + Xosql_tbl_parser tbl_parser = new Xosql_tbl_parser(); + Ordered_hash tbl_flds = tbl_parser.Parse(raw); + + // loop over tbl_flds + int len = tbl_flds.Len(); + for (int i = 0; i < len; ++i) { + Xosql_fld_itm tbl_itm = (Xosql_fld_itm)tbl_flds.Get_at(i); + // get cbk_itm + Xosql_fld_itm cbk_itm = cbk_hash.Get_by_key(tbl_itm.Key()); + if (cbk_itm == null) continue;// throw Err_.New("sql_dump_parser: failed to find fld; src={0} fld={1}", src_fil.Raw(), tbl_itm.Key()); + + // set tbl_def's uid to cbk_itm's uid + tbl_itm.Uid_(cbk_itm.Uid()); + } + + tbl_flds.Sort(); + return tbl_flds; + } + public Xosql_dump_parser Src_rdr_bfr_len_(int v) {src_rdr_bfr_len = v; return this;} // TEST: + + private static final byte[] Bry_insert_into = Bry_.new_a7("INSERT INTO "), Bry_values = Bry_.new_a7(" VALUES ("); + private static final byte Mode__sql_bgn = 0, Mode__row_bgn = 1, Mode__row_end = 2, Mode__fld = 3, Mode__quote = 4, Mode__escape = 5; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_dump_parser__tst.java b/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_dump_parser__tst.java index a27517de8..9075198c2 100644 --- a/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_dump_parser__tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_dump_parser__tst.java @@ -13,3 +13,76 @@ 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.bldrs.sql_dumps; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import org.junit.*; import gplx.core.ios.*; import gplx.core.tests.*; +public class Xosql_dump_parser__tst { + private final Xosql_dump_parser__fxt fxt = new Xosql_dump_parser__fxt(); + private static final String table_def = "\n KEY \n) ENGINE; "; + @Test public void One() { + fxt.Init(String_.Ary("c1", "c2"), "c2").Test__parse(table_def + "INSERT INTO 'tbl_1' VALUES (1,2);", "2|"); + } + @Test public void Many() { + fxt.Init(String_.Ary("c1", "c2"), "c2").Test__parse(table_def + "INSERT INTO 'tbl_1' VALUES (1,2),(3,4),(5,6);", "2|\n4|\n6|"); + } + @Test public void Quote_basic() { + fxt.Init(String_.Ary("c1", "c2", "c3"), "c2", "c3").Test__parse(table_def + "INSERT INTO 'tbl_1' VALUES (1,'a','b');", "a|b|"); + } + @Test public void Escape_backslash() { + fxt.Init(String_.Ary("c1", "c2", "c3"), "c2", "c3").Test__parse(table_def + "INSERT INTO 'tbl_1' VALUES (1,'a\\\\b','c');", "a\\b|c|"); + } + @Test public void Escape_quote() { + fxt.Init(String_.Ary("c1", "c2", "c3"), "c2", "c3").Test__parse(table_def + "INSERT INTO 'tbl_1' VALUES (1,'a\"b','c');", "a\"b|c|"); + } + @Test public void Fld_paren_end() { + fxt.Init(String_.Ary("c1", "c2", "c3"), "c2", "c3").Test__parse(table_def + "INSERT INTO 'tbl_1' VALUES (1,'Психостимуляторы_(лекарственные_средства)','c');", "Психостимуляторы_(лекарственные_средства)|c|"); + } + @Test public void Insert_multiple() { + fxt.Init(String_.Ary("c1", "c2"), "c2").Test__parse(table_def + "INSERT INTO 'tbl_1' VALUES (1,2);INSERT INTO 'tbl_1' VALUES (3,4)", "2|\n4|"); + } +} +class Xosql_dump_parser__fxt { + private Xosql_dump_parser parser; + private Xosql_dump_cbk__test cbk; + private String[] tbl_flds; + public Xosql_dump_parser__fxt Init(String[] tbl_flds, String... cbk_flds) { + this.tbl_flds = tbl_flds; + this.cbk = new Xosql_dump_cbk__test(); + this.parser = new Xosql_dump_parser(cbk, cbk_flds); + return this; + } + public void Test__parse(String raw_str, String expd) { + Io_url src_fil = Io_url_.new_fil_("mem/test.sql"); + Io_mgr.Instance.SaveFilBry(src_fil, Make_dump(tbl_flds, raw_str)); + parser.Src_fil_(src_fil); + parser.Parse(Gfo_usr_dlg_.Test()); + Gftest.Eq__str(expd, cbk.To_bry_and_clear()); + } + private byte[] Make_dump(String[] tbl_flds, String insert) { + Bry_bfr bfr = Bry_bfr_.New(); + bfr.Add_str_a7("CREATE TABLE tbl_0 ("); + for (int i = 0; i < tbl_flds.length; ++i) { + bfr.Add_byte_nl(); + bfr.Add_byte(Byte_ascii.Tick); + bfr.Add_str_a7(tbl_flds[i]); + bfr.Add_byte(Byte_ascii.Tick); + bfr.Add_byte_comma(); + } + bfr.Add_str_a7("\nUNIQUE KEY idx_0 (fld_0));\n"); + bfr.Add_str_u8(insert); + return bfr.To_bry_and_clear(); + } +} +class Xosql_dump_cbk__test implements Xosql_dump_cbk { + private int prv_idx = -1; + private final Bry_bfr bfr = Bry_bfr_.New(); + public void Clear() {prv_idx = -1; bfr.Clear();} + public void On_fld_done(int fld_idx, byte[] src, int val_bgn, int val_end) { + if (fld_idx <= prv_idx) { + if (prv_idx != -1) bfr.Add_byte_nl(); + } + bfr.Add_mid(src, val_bgn, val_end).Add_byte_pipe(); + prv_idx = fld_idx; + } + public void On_row_done() {} + public byte[] To_bry_and_clear() {return bfr.To_bry_and_clear();} +} diff --git a/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_fld_itm.java b/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_fld_itm.java index a27517de8..dadb84be3 100644 --- a/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_fld_itm.java +++ b/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_fld_itm.java @@ -13,3 +13,41 @@ 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.bldrs.sql_dumps; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +class Xosql_fld_itm implements gplx.CompareAble { + public Xosql_fld_itm(int uid, byte[] key, int idx) { + this.uid = uid; + this.key = key; + this.idx = idx; + } + public int Uid() {return uid;} private int uid; + public byte[] Key() {return key;} private final byte[] key; + public int Idx() {return idx;} private int idx; + public void Idx_(int v) {this.idx = v;} + public void Uid_(int v) {this.uid = v;} + + public int compareTo(Object obj) { + Xosql_fld_itm comp = (Xosql_fld_itm)obj; + return Int_.Compare(idx, comp.idx); + } +} +class Xosql_fld_hash { + private final Ordered_hash hash = Ordered_hash_.New_bry(); + private int hash_len; + public int Len() {return hash.Len();} + public Xosql_fld_itm Get_by_key(byte[] k) {return (Xosql_fld_itm)hash.Get_by(k);} + public Xosql_fld_itm Get_by_idx_or_null(int i) { + return i > -1 && i < hash_len ? (Xosql_fld_itm)hash.Get_at(i) : null; + } + public void Add(Xosql_fld_itm itm) {hash.Add(itm.Key(), itm); hash_len = hash.Len();} + public void Sort() {hash.Sort();} + public static Xosql_fld_hash New(String[] keys) { // NOTE: keys must be passed in uid order + int len = keys.length; + Xosql_fld_hash rv = new Xosql_fld_hash(); + for (int i = 0; i < len; ++i) { + Xosql_fld_itm itm = new Xosql_fld_itm(i, Bry_.new_u8(keys[i]), -1); + rv.Add(itm); + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_tbl_parser.java b/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_tbl_parser.java index a27517de8..c50970823 100644 --- a/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_tbl_parser.java +++ b/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_tbl_parser.java @@ -13,3 +13,55 @@ 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.bldrs.sql_dumps; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.ios.*; +class Xosql_tbl_parser { + public Ordered_hash Parse(byte[] raw) { + Ordered_hash rv = Ordered_hash_.New_bry(); + Parse_flds(rv, Extract_flds(raw)); + return rv; + } + private void Parse_flds(Ordered_hash rv, byte[] raw) { + byte[][] lines = Bry_split_.Split(raw, Byte_ascii.Nl); + int lines_len = lines.length; + int fld_idx = 0; + for (int i = 0; i < lines_len; i++) { + byte[] line = lines[i]; + // get fld bgn / end; EX: "`fld_1`" + int bgn = Bry_find_.Find_fwd(line, Byte_ascii.Tick); if (bgn == Bry_find_.Not_found) continue; // skip blank lines + bgn += Int_.Offset_1; + int end = Bry_find_.Find_fwd(line, Byte_ascii.Tick, bgn); if (end == Bry_find_.Not_found) continue; // skip blank lines + + // add fld + byte[] key = Bry_.Mid(line, bgn, end); + rv.Add(key, new Xosql_fld_itm(Int_.Max_value, key, fld_idx++)); + } + } + public byte[] Extract_flds(byte[] raw) { // NOTE: very dependent on MySQL dump formatter + // get bgn of flds; assume after "CREATE TABLE" + int bgn = Bry_find_.Find_fwd(raw, Tkn__create_table); if (bgn == Bry_find_.Not_found) throw Err_.new_wo_type("could not find 'CREATE TABLE'"); + bgn = Bry_find_.Find_fwd(raw, Byte_ascii.Nl, bgn); if (bgn == Bry_find_.Not_found) throw Err_.new_wo_type("could not find new line after 'CREATE TABLE'"); + bgn += 1; // position after \n + + // get end of flds; more involved, as need to find last field before indexes + // first, get absolute end; don't want to pick up "PRIMARY KEY" in data; EX:en.b:categorylinks.sql DATE:2016-10-17 + int end = Bry_find_.Find_fwd(raw, Tkn__engine); if (end == Bry_find_.Not_found) throw Err_.new_wo_type("could not find ') ENGINE'"); + + // now look for "UNIQUE KEY", "KEY", "PRIMARY KEY" + int key_idx = Bry_find_.Find_fwd_or(raw, Tkn__key , bgn, end, Int_.Max_value__31); + int pkey_idx = Bry_find_.Find_fwd_or(raw, Tkn__pkey, bgn, end, Int_.Max_value__31); + int ukey_idx = Bry_find_.Find_fwd_or(raw, Tkn__ukey, bgn, end, Int_.Max_value__31); + + // get min; fail if none found + int rv = Int_.Min_many(key_idx, pkey_idx, ukey_idx); + if (rv == Int_.Max_value__31) throw Err_.new_wo_type("could not find 'PRIMARY KEY', 'UNIQUE KEY', or 'KEY' in SQL", "raw", Bry_.Mid(raw, bgn, end)); + return Bry_.Mid(raw, bgn, rv); + } + private final byte[] + Tkn__create_table = Bry_.new_a7("CREATE TABLE") + , Tkn__ukey = Bry_.new_a7("\n UNIQUE KEY") + , Tkn__pkey = Bry_.new_a7("\n PRIMARY KEY") + , Tkn__key = Bry_.new_a7("\n KEY ") + , Tkn__engine = Bry_.new_a7("\n) ENGINE") + ; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_tbl_parser__tst.java b/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_tbl_parser__tst.java index a27517de8..5065d3954 100644 --- a/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_tbl_parser__tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/sql_dumps/Xosql_tbl_parser__tst.java @@ -13,3 +13,72 @@ 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.bldrs.sql_dumps; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import org.junit.*; import gplx.core.tests.*; +public class Xosql_tbl_parser__tst { + private final Xosql_tbl_parser__fxt fxt = new Xosql_tbl_parser__fxt(); + @Test public void Unique_key() { + fxt.Exec__parse(String_.Concat_lines_nl + ( "ignore" + , "CREATE TABLE tbl_0 (" + , " `fld_2` int," + , " `fld_1` int," + , " `fld_0` int," + , " UNIQUE KEY idx_0 (fld_2)" + , ") ENGINE;" + )); + fxt.Test__count(3); + fxt.Test__get("fld_0", 2); + fxt.Test__get("fld_1", 1); + fxt.Test__get("fld_2", 0); + fxt.Test__get("fld_3", -1); + } + @Test public void Primary_key() { + fxt.Test__extract(String_.Concat_lines_nl + ( "ignore" + , "CREATE TABLE tbl_0 (" + , " `fld_0` int," + , " PRIMARY KEY idx_0 (fld_2)" + , ") ENGINE;" + ), String_.Concat_lines_nl + ( " `fld_0` int," + )); + } + @Test public void Key() { + fxt.Test__extract(String_.Concat_lines_nl + ( "ignore" + , "CREATE TABLE tbl_0 (" + , " `fld_0` int," + , " KEY idx_0 (fld_2)" + , ") ENGINE;" + ), String_.Concat_lines_nl + ( " `fld_0` int," + )); + } + @Test public void Unique_key__key__primary_key() { + fxt.Test__extract(String_.Concat_lines_nl + ( "ignore" + , "CREATE TABLE tbl_0 (" + , " `fld_0` int," + , " UNIQUE KEY idx_0 (fld_2)," + , " KEY idx_0 (fld_2)," + , " PRIMARY KEY idx_0 (fld_2)," + , ") ENGINE;" + ), String_.Concat_lines_nl + ( " `fld_0` int," + )); + } +} +class Xosql_tbl_parser__fxt { + private final Xosql_tbl_parser parser = new Xosql_tbl_parser(); + private Ordered_hash tbl_flds; + public void Exec__parse(String v) {this.tbl_flds = parser.Parse(Bry_.new_a7(v));} + public void Test__count(int expd) {Gftest.Eq__int(expd, tbl_flds.Len());} + public void Test__get(String key, int expd) { + Xosql_fld_itm actl_itm = (Xosql_fld_itm)tbl_flds.Get_by(Bry_.new_u8(key)); + Gftest.Eq__int(expd, actl_itm == null ? Bry_find_.Not_found : actl_itm.Idx()); + } + public void Test__extract(String raw, String expd) { + Gftest.Eq__ary__lines(expd, parser.Extract_flds(Bry_.new_u8(raw)), "extract"); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/syncs/Xob_sync_itm.java b/400_xowa/src/gplx/xowa/bldrs/syncs/Xob_sync_itm.java index a27517de8..71f874be1 100644 --- a/400_xowa/src/gplx/xowa/bldrs/syncs/Xob_sync_itm.java +++ b/400_xowa/src/gplx/xowa/bldrs/syncs/Xob_sync_itm.java @@ -13,3 +13,40 @@ 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.bldrs.syncs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.ios.*; +class Xob_sync_grp { + private final Ordered_hash itms = Ordered_hash_.New(); + public Xob_sync_grp Ctor_itm(DateAdp dump_time, DateAdp upload_time) { + this.dump_time = dump_time; this.upload_time = upload_time; + return this; + } + public DateAdp Dump_time() {return dump_time;} private DateAdp dump_time; + public DateAdp Upload_time() {return upload_time;} private DateAdp upload_time; + public int Itms__len() {return itms.Count();} + public void Itms__add(Xob_sync_pkg file) {itms.Add(file.Path(), file);} + public Xob_sync_pkg Itms__get_at(int i) {return (Xob_sync_pkg)itms.Get_at(i);} +} +class Xob_sync_pkg extends Xob_sync_fil { private final Ordered_hash itms = Ordered_hash_.New(); + public Xob_sync_pkg Ctor_itm(String url, byte zip_tid) { + this.url = url; this.zip_tid = zip_tid; + return this; + } + public String Url() {return url;} private String url; + public byte Zip_tid() {return zip_tid;} private byte zip_tid; + public int Itms__len() {return itms.Count();} + public void Itms__add(Xob_sync_fil file) {itms.Add(file.Path(), file);} + public Xob_sync_fil Itms__get_at(int i) {return (Xob_sync_fil)itms.Get_at(i);} +} +class Xob_sync_fil { + public Xob_sync_fil Ctor_file(String path, String name, int ext, long len, DateAdp modified, String hash) { + this.path = path; this.name = name; this.ext = ext; this.len = len; this.modified = modified; this.hash = hash; + return this; + } + public String Path() {return path;} private String path; + public String Name() {return name;} private String name; + public int Ext() {return ext;} private int ext; + public long Len() {return len;} private long len; + public DateAdp Modified() {return modified;} private DateAdp modified; + public String Hash() {return hash;} private String hash; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xob_subpage_parser.java b/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xob_subpage_parser.java index a27517de8..9ba2401fe 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xob_subpage_parser.java +++ b/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xob_subpage_parser.java @@ -13,3 +13,75 @@ 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.bldrs.wiki_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.log_msgs.*; +import gplx.langs.phps.*; import gplx.xowa.langs.bldrs.*; +class Xob_subpage_parser { + public Xob_subpage_wiki[] Parse(byte[] src) { + src = Bry_.Add(Bry_.new_a7("$a = array("), src, Bry_.new_a7(");")); + List_adp wikis_list = List_adp_.New(); + try { + Php_parser php_parser = new Php_parser(); + Php_evaluator eval = new Php_evaluator(new Gfo_msg_log("test")); + php_parser.Parse_tkns(src, eval); + Php_line[] lines = (Php_line[])eval.List().To_ary(Php_line.class); + Php_line_assign line = (Php_line_assign)lines[0]; + Php_itm_ary root_ary = (Php_itm_ary)line.Val(); + Php_itm_kv root_kv = (Php_itm_kv)root_ary.Subs_get(0); + Php_itm_ary wiki_tkns = (Php_itm_ary)root_kv.Val(); + int wiki_tkns_len = wiki_tkns.Subs_len(); + for (int i = 0; i < wiki_tkns_len; i++) { + Xob_subpage_wiki wiki_itm = new Xob_subpage_wiki(); + Php_itm_kv wiki_tkn = (Php_itm_kv)wiki_tkns.Subs_get(i); + Parse_wiki(wiki_tkn, wiki_itm); + wikis_list.Add(wiki_itm); + } + } + catch (Exception e) { + throw Err_.new_exc(e, "xo", "parse failed", "src", String_.new_u8(src)); + } + return (Xob_subpage_wiki[])wikis_list.To_ary(Xob_subpage_wiki.class); + } + private void Parse_wiki(Php_itm_kv wiki_tkn, Xob_subpage_wiki wiki_itm) { + wiki_itm.Name_(wiki_tkn.Key().Val_obj_bry()); + Php_itm_ary ns_ary_tkns = (Php_itm_ary)wiki_tkn.Val(); + int ns_ary_tkns_len = ns_ary_tkns.Subs_len(); + for (int i = 0; i < ns_ary_tkns_len; i++) { + Php_itm_kv ns_tkn = (Php_itm_kv)ns_ary_tkns.Subs_get(i); + Xob_subpage_ns ns_itm = new Xob_subpage_ns(); + ns_itm.Id_(Parse_ns_id(ns_tkn.Key())); + ns_itm.Enabled_(Parse_ns_enabled(ns_tkn.Val())); + wiki_itm.Ns_list().Add(ns_itm); + } + } + private int Parse_ns_id(Php_itm itm) { + switch (itm.Itm_tid()) { + case Php_itm_.Tid_int: + return ((Php_itm_int)itm).Val_obj_int(); + case Php_itm_.Tid_var: + return Xol_mw_lang_parser.Id_by_mw_name(((Php_itm)itm).Val_obj_bry()); + default: + throw Err_.new_unhandled(itm.Itm_tid()); + } + } + private boolean Parse_ns_enabled(Php_itm itm) { + switch (itm.Itm_tid()) { + case Php_itm_.Tid_int: + return ((Php_itm_int)itm).Val_obj_int() == Bool_.Y_int; + case Php_itm_.Tid_bool_false: + return false; + case Php_itm_.Tid_bool_true: + return true; + default: + throw Err_.new_unhandled(itm.Itm_tid()); + } + } +} +class Xob_subpage_ns { + public int Id() {return id;} public Xob_subpage_ns Id_(int v) {id = v; return this;} private int id; + public boolean Enabled() {return enabled;} public Xob_subpage_ns Enabled_(boolean v) {enabled = v; return this;} private boolean enabled; +} +class Xob_subpage_wiki { + public byte[] Name() {return name;} public Xob_subpage_wiki Name_(byte[] v) {this.name = v; return this;} private byte[] name; + public List_adp Ns_list() {return ns_list;} private List_adp ns_list = List_adp_.New(); +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_alias.java b/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_alias.java index a27517de8..6b08b709c 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_alias.java +++ b/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_alias.java @@ -13,3 +13,13 @@ 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.bldrs.wiki_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +public class Xoi_wiki_props_alias { + public int Id() {return id;} private int id; + public String Alias() {return alias;} private String alias; + public Xoi_wiki_props_alias Init_by_ctor(int id, String alias) {this.id = id; this.alias = alias; return this;} + public void Init_by_xml(gplx.langs.xmls.XmlNde ns_nde) { + this.id = Int_.Parse(ns_nde.Atrs().FetchValOr("id", "-1")); + this.alias = String_.Replace(String_.Replace(ns_nde.Text_inner(), " ", "_"), "'", "''"); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_api.java b/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_api.java index a27517de8..7721dbb24 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_api.java +++ b/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_api.java @@ -13,3 +13,72 @@ 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.bldrs.wiki_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.langs.xmls.*; import gplx.core.ios.*; +public class Xoi_wiki_props_api { + private IoEngine_xrg_downloadFil download_args = IoEngine_xrg_downloadFil.new_("", Io_url_.Empty); + public String Api_src(String wiki_domain) { + return String_.Concat("https://", wiki_domain, "/w/api.php?action=query&format=xml&meta=siteinfo&siprop=namespacealiases|namespaces"); + } + public byte[] Exec_api(String src) { + return download_args.Exec_as_bry(src); + } + public void Build_cfg(Bry_bfr bfr, Xoi_wiki_props_wiki wiki) { + bfr.Add_str_a7("app.bldr.wiki_cfg_bldr.get('").Add(wiki.Wiki_domain()).Add_str_a7("').new_cmd_('wiki.ns_mgr.aliases', 'ns_mgr.add_alias_bulk(\"\n"); + int len = 0; + len = wiki.Alias_ary().length; + for (int i = 0; i < len; i++) { + Xoi_wiki_props_alias alias = wiki.Alias_ary()[i]; + bfr.Add_int_variable(alias.Id()).Add_byte_pipe().Add_str_u8(alias.Alias()).Add_byte_nl(); + } + bfr.Add_str_a7("\");');\n"); + bfr.Add_str_a7("app.bldr.wiki_cfg_bldr.get('").Add(wiki.Wiki_domain()).Add_str_a7("').new_cmd_('wiki.ns_mgr.subpages', \""); + len = wiki.Ns_ary().length; + boolean first = true; + for (int i = 0; i < len; i++) { + Xoi_wiki_props_ns ns = wiki.Ns_ary()[i]; + if (ns.Subpages_enabled()) { + if (first) { + first = false; + } + else + bfr.Add_byte_nl(); + bfr.Add_str_a7("ns_mgr.get_by_id_or_new(").Add_int_variable(ns.Id()).Add_str_a7(").subpages_enabled_('y');"); + } + } + bfr.Add_str_a7("\");\n"); + bfr.Add_byte_nl(); + } + public void Parse(Xoi_wiki_props_wiki wiki, String xml) { + XmlDoc xdoc = XmlDoc_.parse(xml); + XmlNde query_xnde = Xpath_.SelectFirst(xdoc.Root(), "query"); + XmlNde aliases_xnde = Xpath_.SelectFirst(query_xnde, "namespace"+"aliases"); + wiki.Alias_ary_(Parse_alias_ary(aliases_xnde)); + XmlNde ns_xnde = Xpath_.SelectFirst(query_xnde, "namespace"+"s"); + wiki.Ns_ary_(Parse_ns_ary(ns_xnde)); + } + private Xoi_wiki_props_alias[] Parse_alias_ary(XmlNde xnde) { + int xndes_len = xnde.SubNdes().Count(); + List_adp list = List_adp_.New(); + for (int i = 0; i < xndes_len; i++) { + XmlNde sub_nde = xnde.SubNdes().Get_at(i); + if (!String_.Eq(sub_nde.Name(), "ns")) continue; + Xoi_wiki_props_alias sub_itm = new Xoi_wiki_props_alias(); + sub_itm.Init_by_xml(sub_nde); + list.Add(sub_itm); + } + return (Xoi_wiki_props_alias[])list.To_ary_and_clear(Xoi_wiki_props_alias.class); + } + private Xoi_wiki_props_ns[] Parse_ns_ary(XmlNde xnde) { + int xndes_len = xnde.SubNdes().Count(); + List_adp list = List_adp_.New(); + for (int i = 0; i < xndes_len; i++) { + XmlNde sub_nde = xnde.SubNdes().Get_at(i); + if (!String_.Eq(sub_nde.Name(), "ns")) continue; + Xoi_wiki_props_ns sub_itm = new Xoi_wiki_props_ns(); + sub_itm.Init_by_xml(sub_nde); + list.Add(sub_itm); + } + return (Xoi_wiki_props_ns[])list.To_ary_and_clear(Xoi_wiki_props_ns.class); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_api_tst.java b/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_api_tst.java index a27517de8..25dda1b04 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_api_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_api_tst.java @@ -13,3 +13,128 @@ 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.bldrs.wiki_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import org.junit.*; import gplx.core.strings.*; +import gplx.langs.xmls.*; +import gplx.xowa.addons.bldrs.app_cfgs.*; +public class Xoi_wiki_props_api_tst { + private Xoi_wiki_props_fxt fxt = new Xoi_wiki_props_fxt(); + @Before public void init() {} // private Xob_subpage_tst_fxt fxt = new] Xob_subpage_tst_fxt(); + @Test public void Parse() { + fxt.Test_parse(String_.Concat_lines_nl + ( "" + , "" + , " " + , " WP" + , " WT" + , " " + , " " + , " " + , " Talk" + , " " + , "" + , "" + ), fxt.wiki_() + .Alias_ary_(fxt.alias_(4, "WP"), fxt.alias_(5, "WT")) + .Ns_ary_(fxt.ns_(0, false), fxt.ns_(1, true)) + ); + } +// @Test public void Build() { +// fxt.Test_build(fxt.wiki_("enwiki") +// .Alias_ary_(fxt.alias_(4, "WP"), fxt.alias_(5, "WT")) +// .Ns_ary_(fxt.ns_(0, false), fxt.ns_(1, true)) +// , ""); +// } +// Tfds.Eq_str_lines(Query_ns(protocol, gplx.core.ios.IoEngine_.MemKey, wikis), String_.Concat_lines_nl +// ( "app.bldr.wiki_cfg_bldr.get('en.wikipedia.org').new_cmd_('wiki.ns_mgr.aliases', 'ns_mgr.add_alias_bulk(\"" +// , "4|WP" +// , "5|WT" +// , "6|Image" +// , "7|Image_talk" +// , "\");');" +// fxt.Test_parse(String_.Concat_lines_nl +// ( "'wgNamespacesWithSubpages' => array" +// , "( 'default' => array(2 => 1)" +// , ", 'enwiki' => array(0 => 1)" +// , ")" +// )); +} +class Xoi_wiki_props_fxt { + private Xoi_wiki_props_api api = new Xoi_wiki_props_api(); + private Bry_bfr bfr = Bry_bfr_.New(); + public Xoi_wiki_props_wiki wiki_() {return wiki_("domain_doesnt_matter");} + public Xoi_wiki_props_wiki wiki_(String wiki_domain) {return new Xoi_wiki_props_wiki().Wiki_domain_(Bry_.new_a7(wiki_domain));} + public Xoi_wiki_props_alias alias_(int id, String alias) {return new Xoi_wiki_props_alias().Init_by_ctor(id, alias);} + public Xoi_wiki_props_ns ns_(int id, boolean subpages_enabled) {return new Xoi_wiki_props_ns().Init_by_ctor(id, subpages_enabled);} + public void Test_parse(String xml, Xoi_wiki_props_wiki expd) { + Xoi_wiki_props_wiki actl = new Xoi_wiki_props_wiki(); + api.Parse(actl, xml); + Tfds.Eq_str_lines(Xto_str(expd), Xto_str(actl)); + } + public void Test_build(Xoi_wiki_props_wiki wiki, String expd) { + api.Build_cfg(bfr, wiki); + Tfds.Eq_str_lines(expd, bfr.To_str_and_clear()); + } + private String Xto_str(Xoi_wiki_props_wiki v) { + int len = v.Alias_ary().length; + bfr.Add_str_a7("aliases").Add_byte_nl(); + for (int i = 0; i < len; i++) { + Xoi_wiki_props_alias alias = v.Alias_ary()[i]; + bfr.Add_int_variable(alias.Id()).Add_byte_pipe().Add_str_u8(alias.Alias()).Add_byte_nl(); + } + bfr.Add_str_a7("ns").Add_byte_nl(); + len = v.Ns_ary().length; + for (int i = 0; i < len; i++) { + Xoi_wiki_props_ns ns = v.Ns_ary()[i]; + bfr.Add_int_variable(ns.Id()).Add_byte_pipe().Add_int_bool(ns.Subpages_enabled()).Add_byte_nl(); + } + bfr.Add_byte_nl(); + return bfr.To_str_and_clear(); + } +} +class Xob_subpage_tst_fxt { + public Xob_subpage_tst_fxt Clear() { + if (app == null) { + app = Xoa_app_fxt.Make__app__edit(); + mgr = app.Bldr().Wiki_cfg_bldr(); + } + mgr.Clear(); + hash.Clear(); + return this; + } private Xoae_app app; Xob_wiki_cfg_bldr mgr; Ordered_hash hash = Ordered_hash_.New(); + private Xob_subpage_parser parser = new Xob_subpage_parser(); + public Xob_subpage_tst_fxt Init_cmd(String wiki, String key, String text) { +// mgr.Itms_get_or_new(wiki).Itms_add(key, text); + return this; + } + public Xob_subpage_tst_fxt Expd_txt(String wiki, String text) { +// hash.Add(wiki, Keyval_.new_(wiki, text)); + return this; + } + private String_bldr sb = String_bldr_.new_(); + public void Test_parse(String s) { + Xob_subpage_wiki[] actl = parser.Parse(Bry_.new_u8(s)); + Tfds.Eq_str_lines("", X_str_wikis(actl)); + } + public String X_str_wikis(Xob_subpage_wiki[] ary) { + X_str_wikis(sb, ary); + return sb.To_str_and_clear(); + + } + private void X_str_wikis(String_bldr sb, Xob_subpage_wiki[] ary) { + int len = ary.length; + for (int i = 0; i < len; i++) { + Xob_subpage_wiki wiki = ary[i]; + X_str_wiki(sb, wiki); + } + } + private void X_str_wiki(String_bldr sb, Xob_subpage_wiki wiki) { + sb.Add(wiki.Name()).Add_char_nl(); + int ns_len = wiki.Ns_list().Count(); + for (int i = 0; i < ns_len; i++) { + Xob_subpage_ns ns = (Xob_subpage_ns)wiki.Ns_list().Get_at(i); + sb.Add(ns.Id()).Add("=").Add(Bool_.To_str_lower(ns.Enabled())).Add_char_nl(); + } + sb.Add_char_nl(); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_ns.java b/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_ns.java index a27517de8..5b5bd515b 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_ns.java +++ b/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_ns.java @@ -13,3 +13,13 @@ 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.bldrs.wiki_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +public class Xoi_wiki_props_ns { + public int Id() {return id;} private int id; + public boolean Subpages_enabled() {return subpages_enabled;} private boolean subpages_enabled; + public Xoi_wiki_props_ns Init_by_ctor(int id, boolean subpages_enabled) {this.id = id; this.subpages_enabled = subpages_enabled; return this;} + public void Init_by_xml(gplx.langs.xmls.XmlNde ns_nde) { + this.id = Int_.Parse(ns_nde.Atrs().FetchValOr("id", "-1")); + this.subpages_enabled = ns_nde.Atrs().Fetch_or_null("subpages") != null;// per api, subpages="" means ns has subpages; no subpages attribute means no subpages + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_wiki.java b/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_wiki.java index a27517de8..8ae8492b4 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_wiki.java +++ b/400_xowa/src/gplx/xowa/bldrs/wiki_cfgs/Xoi_wiki_props_wiki.java @@ -13,3 +13,9 @@ 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.bldrs.wiki_cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +public class Xoi_wiki_props_wiki { + public byte[] Wiki_domain() {return wiki_domain;} public Xoi_wiki_props_wiki Wiki_domain_(byte[] v) {wiki_domain = v; return this;} private byte[] wiki_domain; + public Xoi_wiki_props_ns[] Ns_ary() {return ns_ary;} public Xoi_wiki_props_wiki Ns_ary_(Xoi_wiki_props_ns... v) {this.ns_ary = v; return this;} private Xoi_wiki_props_ns[] ns_ary; + public Xoi_wiki_props_alias[] Alias_ary() {return alias_ary;} public Xoi_wiki_props_wiki Alias_ary_(Xoi_wiki_props_alias... v) {this.alias_ary = v; return this;} private Xoi_wiki_props_alias[] alias_ary; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_cmd.java b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_cmd.java index a27517de8..1ae6a706f 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_cmd.java +++ b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_cmd.java @@ -13,3 +13,13 @@ 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.bldrs.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +public interface Xob_cmd extends Gfo_invk { + String Cmd_key(); + Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki); + void Cmd_init(Xob_bldr bldr); + void Cmd_bgn(Xob_bldr bldr); + void Cmd_run(); + void Cmd_end(); + void Cmd_term(); +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_cmd__base.java b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_cmd__base.java index a27517de8..8b9b28f91 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_cmd__base.java +++ b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_cmd__base.java @@ -13,3 +13,21 @@ 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.bldrs.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +public abstract class Xob_cmd__base implements Xob_cmd, Gfo_invk { + protected final Xoae_app app; protected final Xob_bldr bldr; protected Xowe_wiki wiki; protected final Gfo_usr_dlg usr_dlg; + public Xob_cmd__base(Xob_bldr bldr, Xowe_wiki wiki) { + this.bldr = bldr; + this.wiki = wiki; + this.app = bldr == null ? null : bldr.App();; + this.usr_dlg = bldr == null ? null : bldr.Usr_dlg(); + } + public abstract String Cmd_key(); + @gplx.Virtual public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return null;} + public abstract void Cmd_run(); + @gplx.Virtual public void Cmd_init(Xob_bldr bldr) {} + @gplx.Virtual public void Cmd_bgn(Xob_bldr bldr) {} + @gplx.Virtual public void Cmd_end() {} + @gplx.Virtual public void Cmd_term() {} + @gplx.Virtual public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return Gfo_invk_.Rv_unhandled;} +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_cmd_base.java b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_cmd_base.java index a27517de8..a598b7c51 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_cmd_base.java +++ b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_cmd_base.java @@ -13,3 +13,14 @@ 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.bldrs.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +public abstract class Xob_cmd_base implements Xob_cmd { + public abstract String Cmd_key(); + @gplx.Virtual public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return null;} + @gplx.Virtual public void Cmd_init(Xob_bldr bldr) {} + @gplx.Virtual public void Cmd_bgn(Xob_bldr bldr) {} + @gplx.Virtual public void Cmd_run() {} + @gplx.Virtual public void Cmd_end() {} + @gplx.Virtual public void Cmd_term() {} + @gplx.Virtual public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return Gfo_invk_.Rv_unhandled;} +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_idx_base.java b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_idx_base.java index a27517de8..6c6d57206 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_idx_base.java +++ b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_idx_base.java @@ -13,3 +13,25 @@ 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.bldrs.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.flds.*; import gplx.core.ios.*; +public abstract class Xob_idx_base extends Xob_itm_basic_base implements Xob_cmd, Gfo_invk { + public abstract String Cmd_key(); + public Gfo_fld_wtr Fld_wtr() {return fld_wtr;} Gfo_fld_wtr fld_wtr = Gfo_fld_wtr.xowa_(); + public Gfo_fld_rdr Fld_rdr() {return fld_rdr;} Gfo_fld_rdr fld_rdr = Gfo_fld_rdr.xowa_(); + public Io_url Temp_dir() {return temp_dir;} Io_url temp_dir; + public void Cmd_init(Xob_bldr bldr) {} + public void Cmd_bgn(Xob_bldr bldr) { + temp_dir = wiki.Fsys_mgr().Tmp_dir().GenSubDir(this.Cmd_key()); + Io_mgr.Instance.DeleteDirDeep(temp_dir); + Cmd_bgn_hook(); + } + public abstract void Cmd_bgn_hook(); + public abstract void Cmd_run(); + @gplx.Virtual public void Cmd_end() {} + public void Cmd_term() {} + public Io_line_rdr rdr_(Io_url dir) { + Io_url[] fils = Io_mgr.Instance.QueryDir_fils(dir); + return new Io_line_rdr(bldr.Usr_dlg(), fils).Key_gen_(Io_line_rdr_key_gen_.first_pipe); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_io_utl_.java b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_io_utl_.java index a27517de8..6bf10d8d2 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_io_utl_.java +++ b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_io_utl_.java @@ -13,3 +13,46 @@ 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.bldrs.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +public class Xob_io_utl_ { + public static void Delete_sql_files(Io_url wiki_dir, String sql_file_name) { + Delete_by_wildcard(wiki_dir, sql_file_name + ".sql", ".gz", ".sql"); + } + public static void Delete_by_wildcard(Io_url dir, String name_pattern, String... ext_ary) { + List_adp list = Find_by_wildcard(Io_mgr.Instance.QueryDir_args(dir).ExecAsUrlAry(), name_pattern, ext_ary); + int len = list.Len(); + for (int i = 0; i < len; ++i) { + Io_url url = (Io_url)list.Get_at(i); + Io_mgr.Instance.DeleteFil(url); + } + } + public static Io_url Find_nth_by_wildcard_or_null(Io_url dir, String name_pattern, String... ext_ary) { + return Find_nth_by_wildcard_or_null(Io_mgr.Instance.QueryDir_args(dir).ExecAsUrlAry(), name_pattern, ext_ary); + } + public static Io_url Find_nth_by_wildcard_or_null(Io_url[] fil_ary, String name_pattern, String... ext_ary) { + List_adp list = Find_by_wildcard(fil_ary, name_pattern, ext_ary); + int list_len = list.Len(); + return list_len == 0 ? null : (Io_url)list.Get_at(list_len - 1); + } + public static List_adp Find_by_wildcard(Io_url[] fil_ary, String name_pattern, String... ext_ary) { + List_adp rv = List_adp_.New(); + + // create ext_hash + Ordered_hash ext_hash = Ordered_hash_.New(); + for (String ext : ext_ary) + ext_hash.Add(ext, ext); + + // iterate fil_ary + for (Io_url fil : fil_ary) { + // file matches pattern + if ( name_pattern == Pattern__wilcard // empty String means match anything + || String_.Has(fil.NameAndExt(), name_pattern)) { // name has name_pattern; EX: "enwiki-latest-pages-articles-current.xml" and "pagelinks" + if ( ext_hash.Len() == 0 // empty hash means match any ext + || ext_hash.Has(fil.Ext())) // ext exists in hash + rv.Add(fil); + } + } + return rv; + } + public static final String Pattern__wilcard = String_.Empty; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_io_utl__tst.java b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_io_utl__tst.java index a27517de8..ab9a99a41 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_io_utl__tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_io_utl__tst.java @@ -13,3 +13,29 @@ 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.bldrs.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import org.junit.*; import gplx.core.tests.*; +public class Xob_io_utl__tst { + private final Xob_io_utl__fxt fxt = new Xob_io_utl__fxt(); + @Test public void Basic() { + fxt.Test__match(String_.Ary("a.txt", "b.txt", "c.txt"), "b", String_.Ary(".txt"), "b.txt"); + } + @Test public void Include__ext() {// PURPOSE: handle calls like "a.sql", ".sql", ".gz" + fxt.Test__match(String_.Ary("a.txt", "b.txt", "c.txt"), "b.txt", String_.Ary(".txt"), "b.txt"); + } + @Test public void Dupe__pick_last() { + fxt.Test__match(String_.Ary("b0.txt", "b1.txt", "b2.txt"), "b", String_.Ary(".txt"), "b2.txt"); + } + @Test public void Ext() { + fxt.Test__match(String_.Ary("b.txt", "b.png", "b.xml"), "b", String_.Ary(".xml", ".bz2"), "b.xml"); + } + @Test public void Ext__dupes() { + fxt.Test__match(String_.Ary("b.txt", "b.png", "b.xml"), "b", String_.Ary(".txt", ".xml"), "b.xml"); + } +} +class Xob_io_utl__fxt { + public void Test__match(String[] path_ary, String name_pattern, String[] ext_ary, String expd) { + Io_url actl = Xob_io_utl_.Find_nth_by_wildcard_or_null(Io_url_.Ary(path_ary), name_pattern, ext_ary); + Gftest.Eq__str(expd, actl == null ? "<>" : actl.Raw()); + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_itm_basic_base.java b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_itm_basic_base.java index a27517de8..4ddc0bc11 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_itm_basic_base.java +++ b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_itm_basic_base.java @@ -13,3 +13,21 @@ 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.bldrs.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +public abstract class Xob_itm_basic_base implements Gfo_invk { + protected Xoae_app app; protected Xob_bldr bldr; protected Xowe_wiki wiki; protected Gfo_usr_dlg usr_dlg; + public void Cmd_ctor(Xob_bldr bldr, Xowe_wiki wiki) { + this.bldr = bldr; + this.wiki = wiki; + this.app = bldr == null ? null : bldr.App(); + this.usr_dlg = bldr == null ? null : bldr.Usr_dlg(); + if (bldr != null && wiki != null) + this.Cmd_ctor_end(bldr, wiki); + } + @gplx.Virtual public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return null;} + @gplx.Virtual protected void Cmd_ctor_end(Xob_bldr bldr, Xowe_wiki wiki) {} + @gplx.Virtual public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_owner)) return bldr.Cmd_mgr(); + else return Gfo_invk_.Rv_unhandled; + } private static final String Invk_owner = "owner"; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_itm_dump_base.java b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_itm_dump_base.java index a27517de8..a70f8ad85 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_itm_dump_base.java +++ b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_itm_dump_base.java @@ -13,3 +13,41 @@ 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.bldrs.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.ios.*; +import gplx.xowa.apps.*; +public abstract class Xob_itm_dump_base extends Xob_itm_basic_base { + protected int sort_mem_len = Int_.Neg1, dump_fil_len = Int_.Neg1, make_fil_len = Int_.Neg1; + public Io_url Temp_dir() {return temp_dir;} + public boolean Delete_temp() {return delete_temp;} protected boolean delete_temp = true; + protected Io_url temp_dir, make_dir; + protected Bry_bfr dump_bfr; + public int Make_fil_len() {return make_fil_len;} public void Make_fil_len_(int v) {make_fil_len = v;} + public Io_url_gen Dump_url_gen() {return dump_url_gen;} protected Io_url_gen dump_url_gen; + public void Init_dump(String tmp_dir_key) {Init_dump(tmp_dir_key, null);} + public void Init_dump(String tmp_dir_key, Io_url make_dir_val) { + if (sort_mem_len == Int_.Neg1) sort_mem_len = bldr.Sort_mem_len(); + if (dump_fil_len == Int_.Neg1) dump_fil_len = bldr.Dump_fil_len(); + if (make_fil_len == Int_.Neg1) make_fil_len = bldr.Make_fil_len(); + dump_bfr = Bry_bfr_.New_w_size(dump_fil_len); + temp_dir = wiki.Fsys_mgr().Tmp_dir().GenSubDir(tmp_dir_key); + if (make_dir_val == null) make_dir = temp_dir.GenSubDir("make"); + else make_dir = make_dir_val; + Io_mgr.Instance.DeleteDirDeep_ary(temp_dir, make_dir); + dump_url_gen = Io_url_gen_.dir_(temp_dir.GenSubDir("dump")); + } + @gplx.Virtual public void Term_dump(Io_sort_cmd make_cmd) { + Io_mgr.Instance.AppendFilBfr(dump_url_gen.Nxt_url(), dump_bfr); dump_bfr.Rls(); + Xobdc_merger.Basic(bldr.Usr_dlg(), dump_url_gen, temp_dir.GenSubDir("sort"), sort_mem_len, Io_line_rdr_key_gen_.first_pipe, make_cmd); + } + protected void Flush_dump() {Io_mgr.Instance.AppendFilBfr(dump_url_gen.Nxt_url(), dump_bfr);} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_sort_mem_len_)) sort_mem_len = gplx.core.ios.Io_size_.Load_int_(m); + else if (ctx.Match(k, Invk_dump_fil_len_)) dump_fil_len = gplx.core.ios.Io_size_.Load_int_(m); + else if (ctx.Match(k, Invk_make_fil_len_)) make_fil_len = gplx.core.ios.Io_size_.Load_int_(m); + else if (ctx.Match(k, Invk_delete_temp_)) delete_temp = m.ReadBool("v"); + else return super.Invk(ctx, ikey, k, m); + return this; + } + public static final String Invk_sort_mem_len_ = "sort_mem_len_", Invk_dump_fil_len_ = "dump_fil_len_", Invk_make_fil_len_ = "make_fil_len_", Invk_delete_temp_ = "delete_temp_"; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_page_wkr.java b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_page_wkr.java index a27517de8..2367c50c4 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_page_wkr.java +++ b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_page_wkr.java @@ -13,3 +13,11 @@ 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.bldrs.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +public interface Xob_page_wkr extends Gfo_invk { + String Page_wkr__key(); + void Page_wkr__bgn(); + void Page_wkr__run(gplx.xowa.wikis.data.tbls.Xowd_page_itm page); + void Page_wkr__run_cleanup(); // close txns opened during Page_wkr__run + void Page_wkr__end(); +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_sql_dump_base.java b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_sql_dump_base.java index a27517de8..4bb9c55ec 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_sql_dump_base.java +++ b/400_xowa/src/gplx/xowa/bldrs/wkrs/Xob_sql_dump_base.java @@ -13,3 +13,58 @@ 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.bldrs.wkrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.flds.*; import gplx.core.ios.*; import gplx.xowa.wikis.tdbs.*; +import gplx.xowa.bldrs.sql_dumps.*; import gplx.xowa.wikis.tdbs.bldrs.*; +public abstract class Xob_sql_dump_base extends Xob_itm_dump_base implements Xob_cmd, Gfo_invk { + private final Xosql_dump_parser parser; protected boolean fail = false; + public abstract String Cmd_key(); + public Xob_sql_dump_base() { + this.parser = New_parser(); + } + public Io_url Src_fil() {return src_fil;} private Io_url src_fil; + public Io_url_gen Make_url_gen() {return make_url_gen;} private Io_url_gen make_url_gen; + public Xob_sql_dump_base Src_dir_manual_(Io_url v) {src_dir_manual = v; return this;} private Io_url src_dir_manual; + public abstract String Sql_file_name(); + protected abstract Xosql_dump_parser New_parser(); + public void Cmd_init(Xob_bldr bldr) {} + public void Cmd_bgn(Xob_bldr bldr) { + this.Init_dump(this.Cmd_key()); + make_url_gen = Io_url_gen_.dir_(temp_dir.GenSubDir("make")); + if (src_fil == null) { + Io_url src_dir = src_dir_manual == null ? wiki.Fsys_mgr().Root_dir() : src_dir_manual; + src_fil = Xob_io_utl_.Find_nth_by_wildcard_or_null(src_dir, Sql_file_name() + ".sql", ".gz", ".sql"); + if (src_fil == null) { + String msg = String_.Format(".sql file not found in dir. Some data will not be imported.\nPlease download the file for your wiki from dumps.wikimedia.org.\nfile={0} dir={1}", Sql_file_name(), wiki.Fsys_mgr().Root_dir()); + app.Usr_dlg().Warn_many("", "", msg); + app.Gui_mgr().Kit().Ask_ok("", "", msg); + fail = true; + return; + } + } + parser.Src_fil_(src_fil); + Cmd_bgn_hook(bldr, parser); + } protected Gfo_fld_wtr fld_wtr = Gfo_fld_wtr.xowa_(); + public abstract void Cmd_bgn_hook(Xob_bldr bldr, Xosql_dump_parser parser); + public void Cmd_run() { + if (fail) return; + parser.Parse(bldr.Usr_dlg()); + } + @gplx.Virtual public void Cmd_end() { + if (fail) return; + Xobdc_merger.Basic(bldr.Usr_dlg(), dump_url_gen, temp_dir.GenSubDir("sort"), sort_mem_len, Io_line_rdr_key_gen_all.Instance, new Io_sort_fil_basic(bldr.Usr_dlg(), make_url_gen, make_fil_len)); + } + public void Cmd_term() {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_src_fil_)) src_fil = m.ReadIoUrl("v"); + else return super.Invk(ctx, ikey, k, m); + return this; + } + public static final String Invk_src_fil_ = "src_fil_"; + public void Cmd_cleanup_sql() { + // get dump files to delete; EX: "*-categorylinks.sql*" matches "simplewiki-latest-categorylinks.sql" and "simplewiki-latest-categorylinks.sql.gz" + Io_url[] dump_files = Io_mgr.Instance.QueryDir_args(wiki.Fsys_mgr().Root_dir()).FilPath_("*-" + this.Sql_file_name() + ".sql*").ExecAsUrlAry(); + for (Io_url dump_file : dump_files) + Io_mgr.Instance.DeleteFil(dump_file); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/Xowm_api_mgr.java b/400_xowa/src/gplx/xowa/bldrs/wms/Xowm_api_mgr.java index a27517de8..127f7a63c 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/Xowm_api_mgr.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/Xowm_api_mgr.java @@ -13,3 +13,15 @@ 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.bldrs.wms; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.net.*; +public class Xowm_api_mgr { + public static byte[] Call_by_qarg(Gfo_usr_dlg usr_dlg, Gfo_inet_conn inet_conn, String domain_str, String api_args) {return Call_by_url(usr_dlg, inet_conn, domain_str, Bld_api_url(domain_str, api_args));} + public static byte[] Call_by_url (Gfo_usr_dlg usr_dlg, Gfo_inet_conn inet_conn, String domain_str, String url) { + if (!gplx.core.ios.IoEngine_system.Web_access_enabled) return null; + usr_dlg.Prog_many("", "", "wm.api:calling; wiki=~{0} api=~{1}", domain_str, url); + byte[] rslt = inet_conn.Download_as_bytes_or_null(url); if (rslt == null) usr_dlg.Warn_many("", "", "wm.api:wmf api returned nothing; api=~{0}", url); + return rslt; + } + public static String Bld_api_url(String wiki, String args) {return String_.Concat("https://", wiki, "/w/api.php?", args);} // EX: https://en.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=namespaces +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/Xowmf_mgr.java b/400_xowa/src/gplx/xowa/bldrs/wms/Xowmf_mgr.java index a27517de8..e657464ef 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/Xowmf_mgr.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/Xowmf_mgr.java @@ -13,3 +13,16 @@ 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.bldrs.wms; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; +import gplx.core.net.*; +import gplx.xowa.apps.wms.apis.*; import gplx.xowa.files.downloads.*; +public class Xowmf_mgr { + public Xowmf_mgr() { + download_wkr.Download_xrg().User_agent_(Xoa_app_.User_agent); + } + public void Init_by_app(Xoa_app app) { + download_wkr.Download_xrg().Prog_dlg_(Xoa_app_.Usr_dlg()); + } + public Xowmf_api_mgr Api_mgr() {return api_mgr;} private Xowmf_api_mgr api_mgr = new Xowmf_api_mgr(); + public Xof_download_wkr Download_wkr() {return download_wkr;} private final Xof_download_wkr download_wkr = new Xof_download_wkr_io(); +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/dump_pages/Xowmf_wiki_dump_dirs_parser.java b/400_xowa/src/gplx/xowa/bldrs/wms/dump_pages/Xowmf_wiki_dump_dirs_parser.java index a27517de8..e1b16f2aa 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/dump_pages/Xowmf_wiki_dump_dirs_parser.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/dump_pages/Xowmf_wiki_dump_dirs_parser.java @@ -13,3 +13,21 @@ 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.bldrs.wms.dump_pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +public class Xowmf_wiki_dump_dirs_parser { + public static String[] Parse(byte[] wiki, byte[] src) { + List_adp rv = List_adp_.New(); + int pos = 0; + while (true) { + int href_bgn = Bry_find_.Move_fwd(src, Tkn_href , pos); if (href_bgn == Bry_find_.Not_found) break; + int href_end = Bry_find_.Find_fwd(src, Byte_ascii.Quote, href_bgn); if (href_end == Bry_find_.Not_found) throw Err_.new_wo_type("unable to find date_end", "wiki", wiki, "snip", Bry_.Mid_by_len_safe(src, href_bgn - 3, 25)); + if (src[href_end - 1] != Byte_ascii.Slash) throw Err_.new_wo_type("href should end in slash", "wiki", wiki, "snip", Bry_.Mid_by_len_safe(src, href_bgn - 3, 25)); + pos = href_end + 1; + byte[] href_bry = Bry_.Mid(src, href_bgn, href_end - 1); + if (Bry_.Eq(href_bry, Tkn_owner)) continue; // ignore + rv.Add(String_.new_u8(href_bry)); + } + return (String[])rv.To_ary_and_clear(String.class); + } + private static final byte[] Tkn_href = Bry_.new_a7(" href=\""), Tkn_owner = Bry_.new_a7(".."); +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/dump_pages/Xowmf_wiki_dump_dirs_parser_tst.java b/400_xowa/src/gplx/xowa/bldrs/wms/dump_pages/Xowmf_wiki_dump_dirs_parser_tst.java index a27517de8..a7b34f6f3 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/dump_pages/Xowmf_wiki_dump_dirs_parser_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/dump_pages/Xowmf_wiki_dump_dirs_parser_tst.java @@ -13,3 +13,54 @@ 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.bldrs.wms.dump_pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import org.junit.*; +import gplx.xowa.wikis.domains.*; +public class Xowmf_wiki_dump_dirs_parser_tst { + @Before public void init() {fxt.Clear();} private final Xowmf_wiki_dump_dirs_parser_fxt fxt = new Xowmf_wiki_dump_dirs_parser_fxt(); + @Test public void Basic() { + fxt.Test_parse("20141230/20150118/", "20141230", "20150118"); + } + @Test public void Example() { // http://dumps.wikimedia.org/jawiki/ + fxt.Test_parse(String_.Concat_lines_nl_skip_last + ( "" + , "Index of /jawiki/" + , "" + , "

    Index of /jawiki/


    ../"
    +		, "20141122/                                          25-Nov-2014 22:04                   -"
    +		, "20141211/                                          14-Dec-2014 09:25                   -"
    +		, "20141230/                                          02-Jan-2015 09:02                   -"
    +		, "20150118/                                          21-Jan-2015 04:39                   -"
    +		, "20150221/                                          24-Feb-2015 17:51                   -"
    +		, "20150313/                                          16-Mar-2015 14:37                   -"
    +		, "20150402/                                          05-Apr-2015 06:19                   -"
    +		, "20150422/                                          25-Apr-2015 13:52                   -"
    +		, "20150512/                                          15-May-2015 08:17                   -"
    +		, "20150602/                                          16-Jun-2015 01:34                   -"
    +		, "20150703/                                          08-Jul-2015 14:44                   -"
    +		, "latest/                                            08-Jul-2015 14:44                   -"
    +		, "

    " + , "" + ) + , "20141122" + , "20141211" + , "20141230" + , "20150118" + , "20150221" + , "20150313" + , "20150402" + , "20150422" + , "20150512" + , "20150602" + , "20150703" + , "latest" + ); + } +} +class Xowmf_wiki_dump_dirs_parser_fxt { + public void Clear() {} + public void Test_parse(String src, String... expd_dates) { + String[] actl_dates = Xowmf_wiki_dump_dirs_parser.Parse(Xow_domain_itm_.Bry__enwiki, Bry_.new_u8(src)); + Tfds.Eq_ary_str(expd_dates, actl_dates); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_file.java b/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_file.java index a27517de8..cc1b726fa 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_file.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_file.java @@ -13,3 +13,86 @@ 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.bldrs.wms.dumps; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import gplx.core.ios.*; +import gplx.xowa.wikis.domains.*; +public class Xowm_dump_file { + public Xowm_dump_file(String domain_str, String dump_date, String dump_type_str) { + this.dump_date = dump_date; this.dump_type_str = dump_type_str; + this.domain_itm = Xow_domain_itm_.parse(Bry_.new_u8(domain_str)); + this.dump_abrv = Xow_abrv_wm_.To_abrv(domain_itm); + this.file_name = Bld_file_name(dump_abrv, dump_date, dump_type_str); + } + public Xow_domain_itm Domain_itm() {return domain_itm;} private final Xow_domain_itm domain_itm; // EX: en.wikipedia.org + public byte[] Dump_abrv() {return dump_abrv;} private final byte[] dump_abrv; // EX: enwiki + public String Dump_date() {return dump_date;} private String dump_date; // EX: 20150807 + public String Dump_type_str() {return dump_type_str;} private final String dump_type_str; // EX: pages-articles + public String Server_url() {return server_url;} private String server_url; // EX: https://dumps.wikimedia.org + public String File_url() {return file_url;} private String file_url; // EX: https://dumps.wikimedia.org/enwiki/20150807/enwiki-20150807-pages-articles.xml.bz2 + public String File_name() {return file_name;} private String file_name; // EX: enwiki-20150807-pages-articles.xml.bz2 + public long File_len() {return file_len;} private long file_len; // EX: 10 GB + public DateAdp File_modified() {return file_modified;} private DateAdp file_modified; // EX: 2015-08-10 20:12:34 + public void Dump_date_(String v) {dump_date = v;} + public void Server_url_(String server_url) { + this.server_url = server_url; + String dump_dir_url = String_.new_u8(Xowm_dump_file_.Bld_dump_dir_url(Bry_.new_u8(server_url), dump_abrv, Bry_.new_u8(dump_date))); + this.file_url = dump_dir_url + file_name; + } + public boolean Connect() { + IoEngine_xrg_downloadFil args = Io_mgr.Instance.DownloadFil_args("", Io_url_.Empty); + boolean rv = Connect_exec(args, file_url); + // WMF changed dumping approach to partial dumps; this sometimes causes /latest/ to be missing page_articles; try to get earlier dump; DATE:2015-07-09 + if ( !rv // not found + && String_.In(server_url, Xowm_dump_file_.Server_wmf_http, Xowm_dump_file_.Server_wmf_https) // server is dumps.wikimedia.org + && String_.Eq(dump_date, Xowm_dump_file_.Date_latest) // request dump was latest + ) { + Xoa_app_.Usr_dlg().Warn_many("", "", "wmf.dump:latest not found; url=~{0}", file_url); + byte[] abrv_wm_bry = Xow_abrv_wm_.To_abrv(domain_itm); + String new_dump_root = Xowm_dump_file_.Server_wmf_https + String_.new_u8(abrv_wm_bry) + "/"; // EX: http://dumps.wikimedia.org/enwiki/ + byte[] wiki_dump_dirs_src = args.Exec_as_bry(new_dump_root); + if (wiki_dump_dirs_src == null) {Xoa_app_.Usr_dlg().Warn_many("", "", "could not connect to dump server; url=~{0}", new_dump_root); return false;} + String[] dates = gplx.xowa.bldrs.wms.dump_pages.Xowmf_wiki_dump_dirs_parser.Parse(domain_itm.Domain_bry(), wiki_dump_dirs_src); + int dates_len = dates.length; + for (int i = dates_len - 1; i > -1; --i) { + String new_dump_date = dates[i]; + if (String_.Eq(new_dump_date, Xowm_dump_file_.Date_latest)) continue; // skip latest; assume it is bad + String new_dump_file = String_.Replace(file_name, Xowm_dump_file_.Date_latest, new_dump_date); // replace "-latest-" with "-20150602-"; + String new_file_url = new_dump_root + new_dump_date + "/" + new_dump_file; + rv = Connect_exec(args, new_file_url); + if (rv) { + Xoa_app_.Usr_dlg().Note_many("", "", "wmf.dump:dump found; url=~{0}", new_file_url); + dump_date = new_dump_date; + file_name = new_dump_file; + file_url = new_file_url; + break; + } + else + Xoa_app_.Usr_dlg().Warn_many("", "", "wmf.dump:dump not found; url=~{0}", new_file_url); + } + } + return rv; + } + private boolean Connect_exec(IoEngine_xrg_downloadFil args, String cur_file_url) { + boolean rv = args.Src_last_modified_query_(true).Exec_meta(cur_file_url); + long tmp_file_len = args.Src_content_length(); + DateAdp tmp_file_modified = args.Src_last_modified(); + Xoa_app_.Usr_dlg().Note_many("", "", "wmf.dump:connect rslts; url=~{0} result=~{1} fil_len=~{2} file_modified=~{3} server_url=~{4} dump_date=~{5}", cur_file_url, rv, tmp_file_len, tmp_file_modified == null ? "<>" : tmp_file_modified.XtoStr_fmt_yyyy_MM_dd_HH_mm_ss(), server_url, dump_date); + if (rv) { + if (tmp_file_modified != null && tmp_file_modified.Year() <= 1970) return false; // url has invalid file; note that dumps.wikimedia.org currently returns back an HTML page with "404 not found"; rather than try to download and parse this (since content may change), use the date_modified which always appears to be UnixTime 0; DATE:2015-07-21 + file_len = tmp_file_len; + file_modified = tmp_file_modified; + } + return rv; + } + private static String Bld_file_name(byte[] dump_abrv, String dump_date, String dump_type_str) { + byte[] dump_type_bry = Bry_.new_u8(dump_type_str); + int dump_type_int = Xowm_dump_type_.parse_by_file(dump_type_bry); + byte[] dump_file_ext = Xowm_dump_file_.Ext_xml_bz2; + switch (dump_type_int) { + case Xowm_dump_type_.Int__page_props: case Xowm_dump_type_.Int__categorylinks: case Xowm_dump_type_.Int__image: case Xowm_dump_type_.Int__pagelinks: + dump_file_ext = Xowm_dump_file_.Ext_sql_gz; + break; + } + return String_.new_u8(Xowm_dump_file_.Bld_dump_file_name(dump_abrv, Bry_.new_u8(dump_date), dump_type_bry, dump_file_ext)); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_file_.java b/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_file_.java index a27517de8..0a5019aad 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_file_.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_file_.java @@ -13,3 +13,67 @@ 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.bldrs.wms.dumps; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.bldrs.installs.*; +public class Xowm_dump_file_ { + public static Xowm_dump_file parse(byte[] src) { + int src_len = src.length; + int dash_0 = Bry_find_.Find_fwd(src, Byte_ascii.Dash, 0 , src_len); if (dash_0 == Bry_find_.Not_found) throw Err_.new_parse_type(Xowm_dump_file.class, String_.new_u8(src)); + int dash_1 = Bry_find_.Find_fwd(src, Byte_ascii.Dash, dash_0 + 1 , src_len); if (dash_1 == Bry_find_.Not_found) throw Err_.new_parse_type(Xowm_dump_file.class, String_.new_u8(src)); + byte[] domain_bry = Xow_abrv_wm_.Parse_to_domain_bry(Bry_.Mid(src, 0, dash_0)); + return new Xowm_dump_file(String_.new_u8(domain_bry), String_.new_u8(src, dash_0 + 1, dash_1), String_.new_u8(src, dash_1 + 1, src_len)); + } + public static boolean Connect_first(Xowm_dump_file rv, String[] server_urls) { + int len = server_urls.length; + for (int i = 0; i < len; ++i) { + String server_url = server_urls[i]; + rv.Server_url_(server_url); + Override_dump_date(rv, server_url); + if (rv.Connect()) return true; + } + return false; + } + private static void Override_dump_date(Xowm_dump_file rv, String dump_server) { + String dump_date = rv.Dump_date(); + if ( String_.Eq(dump_date, Xowm_dump_file_.Date_latest) + && ( String_.Eq(dump_server, Xowm_dump_file_.Server_c3sl) + || String_.Eq(dump_server, Xowm_dump_file_.Server_masaryk) + ) + ){ + Xoa_app_.Usr_dlg().Note_many("", "", "wmf.dump:dump date; server_url=~{0} dump_date=~{1}", dump_server, dump_date); + Xoi_mirror_parser mirror_parser = new Xoi_mirror_parser(); + String dump_wiki_url = dump_server + String_.new_a7(rv.Dump_abrv()) + "/"; + byte[] dump_url_wiki_html = gplx.core.ios.IoEngine_xrg_downloadFil.new_("", Io_url_.Empty).Exec_as_bry(dump_wiki_url); if (Bry_.Len_eq_0(dump_url_wiki_html)) return; + String[] dump_available_dates = mirror_parser.Parse(String_.new_u8(dump_url_wiki_html)); + String dump_dates_latest = Xoi_mirror_parser.Find_last_lte(dump_available_dates, dump_date); + if (String_.Eq(dump_dates_latest, "")) return; // nothing found + rv.Dump_date_(dump_dates_latest); + } + } + public static byte[] Bld_dump_dir_url(byte[] server_url, byte[] alias, byte[] date) { + return Bry_.Add + ( server_url // "http://dumps.wikimedia.org/" + , Bry_.Replace(alias, Byte_ascii.Dash, Byte_ascii.Underline), Bry_slash // "simplewiki/" + , date, Bry_slash // "latest/" + ); + } + public static byte[] Bld_dump_file_name(byte[] alias, byte[] date, byte[] dump_file_type, byte[] ext) { + return Bry_.Add + ( Bry_.Replace(alias, Byte_ascii.Dash, Byte_ascii.Underline), Bry_dash // "simplewiki-" + , date, Bry_dash // "latest-" + , dump_file_type // "pages-articles" + , ext // ".xml.bz2" + ); + } + private static final byte[] Bry_dash = new byte[] {Byte_ascii.Dash}, Bry_slash = new byte[] {Byte_ascii.Slash}; + public static final byte[] Ext_xml_bz2 = Bry_.new_a7(".xml.bz2"); + public static final byte[] Ext_sql_gz = Bry_.new_a7(".sql.gz"); + public static final String + Server_wmf_http = "http://dumps.wikimedia.org/" + , Server_wmf_https = "https://dumps.wikimedia.org/" + , Server_your_org = "http://dumps.wikimedia.your.org/" + , Server_c3sl = "http://wikipedia.c3sl.ufpr.br/" + , Server_masaryk = "http://ftp.fi.muni.cz/pub/wikimedia/" + , Date_latest = "latest" + ; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_file_tst.java b/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_file_tst.java index a27517de8..d4fe9f897 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_file_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_file_tst.java @@ -13,3 +13,31 @@ 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.bldrs.wms.dumps; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import org.junit.*; +public class Xowm_dump_file_tst { + private final Xowm_dump_file_fxt fxt = new Xowm_dump_file_fxt(); + @Test public void Parse() {fxt.Test_parse("enwiki-20121201-pages-articles.xml.bz2", "en.wikipedia.org", "20121201", Xowm_dump_type_.Int__pages_articles);} + @Test public void Bld_dump_dir_url() { + fxt.Test_bld_dump_dir_url("simplewiki", "latest", "http://dumps.wikimedia.your.org/simplewiki/latest/"); + } + @Test public void Bld_dump_file_name() { + fxt.Test_bld_dump_file_name("simplewiki", "latest", Xowm_dump_type_.Str__pages_articles, "simplewiki-latest-pages-articles.xml.bz2"); + } +} +class Xowm_dump_file_fxt { + public void Test_parse(String name, String domain, String date, int tid) { + Xowm_dump_file dump_file = Xowm_dump_file_.parse(Bry_.new_u8(name)); + Tfds.Eq(domain , dump_file.Domain_itm().Domain_str()); + Tfds.Eq(date , dump_file.Dump_date()); + Tfds.Eq(tid , Xowm_dump_type_.parse_by_file(Bry_.new_u8(dump_file.Dump_type_str()))); + } + public void Test_bld_dump_dir_url(String dump_file, String date, String expd) { + byte[] actl = Xowm_dump_file_.Bld_dump_dir_url(Bry_.new_a7(Xowm_dump_file_.Server_your_org), Bry_.new_a7(dump_file), Bry_.new_a7(date)); + Tfds.Eq(expd, String_.new_a7(actl)); + } + public void Test_bld_dump_file_name(String dump_file, String date, String dump_type, String expd) { + byte[] actl = Xowm_dump_file_.Bld_dump_file_name(Bry_.new_a7(dump_file), Bry_.new_a7(date), Bry_.new_a7(dump_type), Xowm_dump_file_.Ext_xml_bz2); + Tfds.Eq(expd, String_.new_a7(actl)); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_type_.java b/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_type_.java index a27517de8..06265a8d0 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_type_.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_type_.java @@ -13,3 +13,35 @@ 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.bldrs.wms.dumps; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import gplx.core.btries.*; import gplx.core.primitives.*; +public class Xowm_dump_type_ { + public static int parse_by_file(byte[] src) {return parse_by_file(src, 0, src.length);} + public static int parse_by_file(byte[] src, int bgn, int end) { // given "pages-articles.xml", get type from "pages-articles"; ignore ".xml" or ".bz2" + Object o = regy.Match_bgn(src, bgn, end); if (o == null) throw Err_.new_("wm.dump", "unknown dump file type", "src", src); + return ((Int_obj_val)o).Val(); + } + public static final int Int__pages_articles = 1, Int__pages_meta_current = 2, Int__categorylinks = 3, Int__page_props = 4, Int__image = 5, Int__pagelinks = 6; + public static final String Str__pages_articles = "pages-articles", Str__pages_meta_current = "pages-meta-current" + , Str__categorylinks = "categorylinks", Str__page_props = "page_props", Str__image = "image", Str__pagelinks = "pagelinks" + ; + private static final Btrie_slim_mgr regy = Btrie_slim_mgr.ci_a7() + .Add_str_int(Str__pages_articles , Int__pages_articles) + .Add_str_int(Str__pages_meta_current , Int__pages_meta_current) + .Add_str_int(Str__categorylinks , Int__categorylinks) + .Add_str_int(Str__page_props , Int__page_props) + .Add_str_int(Str__image , Int__image) + .Add_str_int(Str__pagelinks , Int__pagelinks) + ; + public static String To_str(byte v) { + switch (v) { + case Int__pages_articles : return Str__pages_articles; + case Int__pages_meta_current : return Str__pages_meta_current; + case Int__categorylinks : return Str__categorylinks; + case Int__page_props : return Str__page_props; + case Int__image : return Str__image; + case Int__pagelinks : return Str__pagelinks; + default : throw Err_.new_unhandled(v); + } + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_type__tst.java b/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_type__tst.java index a27517de8..d6fce505f 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_type__tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/dumps/Xowm_dump_type__tst.java @@ -13,3 +13,15 @@ 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.bldrs.wms.dumps; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import org.junit.*; import gplx.core.primitives.*; import gplx.xowa.wikis.*; +public class Xowm_dump_type__tst { + private final Xowm_dump_type__fxt fxt = new Xowm_dump_type__fxt(); + @Test public void Parse() { + fxt.Test_parse("pages-articles.xml" , Xowm_dump_type_.Int__pages_articles); + fxt.Test_parse("pages-meta-current.xml" , Xowm_dump_type_.Int__pages_meta_current); + } +} +class Xowm_dump_type__fxt { + public void Test_parse(String raw_str, int expd) {Tfds.Eq_int(expd, Xowm_dump_type_.parse_by_file(Bry_.new_u8(raw_str)), "dump_type");} +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/revs/Wmapi_itm__page.java b/400_xowa/src/gplx/xowa/bldrs/wms/revs/Wmapi_itm__page.java index a27517de8..74e0ccfc6 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/revs/Wmapi_itm__page.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/revs/Wmapi_itm__page.java @@ -13,3 +13,64 @@ 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.bldrs.wms.revs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +class Wmapi_itm__pge { + public int Page_id() {return page_id;} private int page_id; + public int Page_ns() {return page_ns;} private int page_ns; + public byte[] Page_ttl() {return page_ttl;} private byte[] page_ttl; + public Wmapi_itm__rvn[] Rvn_ary() {return rvn_ary;} private Wmapi_itm__rvn[] rvn_ary = Wmapi_itm__rvn.Ary_empty; + public Wmapi_itm__ttl[] Tml_ary() {return tml_ary;} private Wmapi_itm__ttl[] tml_ary = Wmapi_itm__ttl.Ary_empty; + public Wmapi_itm__ttl[] Img_ary() {return img_ary;} private Wmapi_itm__ttl[] img_ary = Wmapi_itm__ttl.Ary_empty; + public Wmapi_itm__ctg[] Ctg_ary() {return ctg_ary;} private Wmapi_itm__ctg[] ctg_ary = Wmapi_itm__ctg.Ary_empty; + public void Rvn_ary_(Wmapi_itm__rvn... v) {this.rvn_ary = v;} + public void Tml_ary_(Wmapi_itm__ttl... v) {this.tml_ary = v;} + public void Img_ary_(Wmapi_itm__ttl... v) {this.img_ary = v;} + public void Ctg_ary_(Wmapi_itm__ctg... v) {this.ctg_ary = v;} + public Wmapi_itm__rvn Rvn_itm_last() {return rvn_ary[rvn_ary.length - 1];} + public Wmapi_itm__pge Init_ttl(int page_ns, byte[] page_ttl) { + this.page_ns = page_ns; this.page_ttl = page_ttl; + return this; + } + public Wmapi_itm__pge Init_id(int page_id) { + this.page_id = page_id; + return this; + } + public boolean Eq_meta(Wmapi_itm__pge rhs_page, int idx) { + Wmapi_itm__rvn lhs = rvn_ary[idx]; + Wmapi_itm__rvn rhs = rhs_page.rvn_ary[idx]; + return lhs.Rvn_len() == rhs.Rvn_len() && Bry_.Eq(lhs.Rvn_time(), rhs.Rvn_time()); + } +} +class Wmapi_itm__ttl { + public Wmapi_itm__ttl(int ns_id, byte[] ttl_bry) {this.ns_id = ns_id; this.ttl_bry = ttl_bry;} + public int Ns_id() {return ns_id;} private final int ns_id; + public byte[] Ttl_bry() {return ttl_bry;} private final byte[] ttl_bry; + public static final Wmapi_itm__ttl[] Ary_empty = new Wmapi_itm__ttl[0]; +} +class Wmapi_itm__rvn { + public int Rvn_id() {return rvn_id;} private int rvn_id; + public int Rvn_len() {return rvn_len;} private int rvn_len; + public byte[] Rvn_time() {return rvn_time;} private byte[] rvn_time; + public byte[] Rvn_user() {return rvn_user;} private byte[] rvn_user; + public byte[] Rvn_note() {return rvn_note;} private byte[] rvn_note; + public byte[] Rvn_text() {return rvn_text;} private byte[] rvn_text; + public void Rvn_text_(byte[] v) {this.rvn_text = v;} + public void Init(int rvn_id, int rvn_len, byte[] rvn_time, byte[] rvn_user, byte[] rvn_note) { + this.rvn_id = rvn_id; + this.rvn_len = rvn_len; this.rvn_time = rvn_time; + this.rvn_user = rvn_user; this.rvn_note = rvn_note; + } + public static final Wmapi_itm__rvn[] Ary_empty = new Wmapi_itm__rvn[0]; +} +class Wmapi_itm__ctg { + public Wmapi_itm__ctg(int ctg_ns, byte[] ctg_ttl, byte[] ctg_sortkey, boolean ctg_sortprefix, byte[] ctg_time, boolean ctg_hidden) { + this.ctg_ns = ctg_ns; this.ctg_ttl = ctg_ttl; this.ctg_sortkey = ctg_sortkey; this.ctg_sortprefix = ctg_sortprefix; this.ctg_time = ctg_time; this.ctg_hidden = ctg_hidden; + } + public int Ctg_ns() {return ctg_ns;} private final int ctg_ns; + public byte[] Ctg_ttl() {return ctg_ttl;} private final byte[] ctg_ttl; + public byte[] Ctg_sortkey() {return ctg_sortkey;} private final byte[] ctg_sortkey; + public boolean Ctg_sortprefix() {return ctg_sortprefix;} private final boolean ctg_sortprefix; + public byte[] Ctg_time() {return ctg_time;} private final byte[] ctg_time; + public boolean Ctg_hidden() {return ctg_hidden;} private final boolean ctg_hidden; + public static final Wmapi_itm__ctg[] Ary_empty = new Wmapi_itm__ctg[0]; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/revs/Wmapi_itm_json_wtr.java b/400_xowa/src/gplx/xowa/bldrs/wms/revs/Wmapi_itm_json_wtr.java index a27517de8..90a9022df 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/revs/Wmapi_itm_json_wtr.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/revs/Wmapi_itm_json_wtr.java @@ -13,3 +13,78 @@ 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.bldrs.wms.revs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import gplx.langs.jsons.*; +class Wmapi_itm_json_wtr { + public Json_wtr Wtr() {return wtr;} private final Json_wtr wtr = new Json_wtr(); + public byte[] To_bry(Wmapi_itm__pge[] ary) { + wtr.Clear(); + Write_query(ary); + return wtr.To_bry_and_clear(); + } + private void Write_query(Wmapi_itm__pge[] ary) { + wtr.Doc_nde_bgn(); + wtr.Nde_bgn("query"); + wtr.Nde_bgn("pages"); + for (Wmapi_itm__pge itm : ary) + Write_page(itm); + wtr.Nde_end(); + wtr.Nde_end(); + wtr.Doc_nde_end(); + } + private void Write_page(Wmapi_itm__pge itm) { + wtr.Nde_bgn(Int_.To_str(itm.Page_id())); + wtr.Kv_int("pageid", itm.Page_id()); + wtr.Kv_int("ns", itm.Page_ns()); + wtr.Kv_bry("title", itm.Page_ttl()); + Write_sub_rvns(itm.Rvn_ary()); + Write_sub_ttls(itm.Tml_ary(), "templates"); + Write_sub_ttls(itm.Img_ary(), "images"); + Write_sub_ctgs(itm.Ctg_ary()); + wtr.Nde_end(); + } + private void Write_sub_rvns(Wmapi_itm__rvn[] rvn_ary) { + int len = rvn_ary.length; if (len == 0) return; + wtr.Ary_bgn("revisions"); + for (int i = 0; i < len; ++i) { + Wmapi_itm__rvn rvn = rvn_ary[i]; + wtr.Doc_nde_bgn(); + wtr.Kv_int("revid", rvn.Rvn_id()); + wtr.Kv_int("parentid", -1); + wtr.Kv_bry("timestamp", rvn.Rvn_time()); + wtr.Kv_int("size", rvn.Rvn_len()); + wtr.Kv_bry("user", rvn.Rvn_user()); + wtr.Kv_bry("parsedcomment", rvn.Rvn_note()); + wtr.Doc_nde_end(); + } + wtr.Ary_end(); + } + private void Write_sub_ttls(Wmapi_itm__ttl[] ttl_ary, String ary_name) { + int len = ttl_ary.length; if (len == 0) return; + wtr.Ary_bgn(ary_name); + for (int i = 0; i < len; ++i) { + Wmapi_itm__ttl ttl = ttl_ary[i]; + wtr.Doc_nde_bgn(); + wtr.Kv_int("ns", ttl.Ns_id()); + wtr.Kv_bry("title", ttl.Ttl_bry()); + wtr.Doc_nde_end(); + } + wtr.Ary_end(); + } + private void Write_sub_ctgs(Wmapi_itm__ctg[] ctg_ary) { + int len = ctg_ary.length; if (len == 0) return; + wtr.Ary_bgn("categories"); + for (int i = 0; i < len; ++i) { + Wmapi_itm__ctg ctg = ctg_ary[i]; + wtr.Doc_nde_bgn(); + wtr.Kv_int("ns", ctg.Ctg_ns()); + wtr.Kv_bry("title", ctg.Ctg_ttl()); + wtr.Kv_bry("sortkey", ctg.Ctg_sortkey()); + wtr.Kv_bool_as_mw("sortkeyprefix", ctg.Ctg_sortprefix()); + wtr.Kv_bry("timestamp", ctg.Ctg_time()); + wtr.Kv_bool_as_mw("hidden", ctg.Ctg_hidden()); + wtr.Doc_nde_end(); + } + wtr.Ary_end(); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xowm_json_parser__page.java b/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xowm_json_parser__page.java index a27517de8..0dd672351 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xowm_json_parser__page.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xowm_json_parser__page.java @@ -13,3 +13,39 @@ 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.bldrs.wms.revs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import gplx.langs.jsons.*; +class Xowm_json_parser__page extends Json_parser__list_nde__base { + private Wmapi_itm__pge pge; + private Xowm_json_parser__rev rev_nde_parser = new Xowm_json_parser__rev(); + private String nde_context; + public Xowm_json_parser__page() { + this.Ctor("pageid", "ns", "title", "revisions"); + } + public void Parse(String context, Wmapi_itm__pge pge, Json_nde nde) { + this.pge = pge; + this.nde_context = context + ".page"; + this.Parse_nde(context, nde); + } + @Override protected void Parse_hook_nde(Json_nde sub, Json_kv[] atrs) { + int page_id = Kv__int(atrs, 0); + Json_ary revs_ary = atrs[3].Val_as_ary(); if (revs_ary.Len() == 0) throw Err_.new_("rev.parser", "no revisions found", sub.Doc().Src()); + Json_nde rev_nde = revs_ary.Get_at_as_nde(0); + pge.Init_id(page_id); + pge.Init_ttl(Kv__int(atrs, 1), Kv__bry(atrs, 2)); + rev_nde_parser.Parse(nde_context, pge.Rvn_itm_last(), rev_nde); + } +} +class Xowm_json_parser__rev extends Json_parser__list_nde__base { + private Wmapi_itm__rvn rvn; + public Xowm_json_parser__rev() { + this.Ctor("revid", "parentid", "user", "anon", "timestamp", "size", "parsedcomment");// , "contentformat", "contentmodel", "*" + } + public void Parse(String context, Wmapi_itm__rvn rvn, Json_nde nde) { + this.rvn = rvn; + this.Parse_nde(context + ".rev", nde); + } + @Override protected void Parse_hook_nde(Json_nde sub, Json_kv[] atrs) { + rvn.Init(Kv__int(atrs, 0), Kv__int(atrs, 5), Kv__bry(atrs, 4), Kv__bry(atrs, 2), Kv__bry(atrs, 6)); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xowm_rev_sync.java b/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xowm_rev_sync.java index a27517de8..3c4b03943 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xowm_rev_sync.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xowm_rev_sync.java @@ -13,3 +13,73 @@ 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.bldrs.wms.revs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import gplx.xowa.bldrs.wms.*; +class Xowm_rev_sync { + private final Ordered_hash cur_hash = Ordered_hash_.New_bry(), new_hash = Ordered_hash_.New_bry(); + private final List_adp del_list = List_adp_.New(); + public int Batch_size() {return batch_size;} public void Batch_size_(int v) {batch_size = v;} private int batch_size = 50; + public Xowm_rev_wkr__meta Wkr__cur_meta() {return wkr__cur_meta;} private Xowm_rev_wkr__meta wkr__cur_meta; + public Xowm_rev_wkr__meta Wkr__new_meta() {return wkr__new_meta;} private Xowm_rev_wkr__meta wkr__new_meta; + public Xowm_rev_wkr__text Wkr__new_text() {return wkr__new_text;} private Xowm_rev_wkr__text wkr__new_text; + public void Init(Xowm_rev_wkr__meta wkr__new_meta, Xowm_rev_wkr__meta wkr__cur_meta, Xowm_rev_wkr__text wkr__new_text) { + this.wkr__cur_meta = wkr__cur_meta; + this.wkr__new_meta = wkr__new_meta; + this.wkr__new_text = wkr__new_text; + } + public void Exec(String domain_str, Xoa_ttl[] ttls_ary) { + synchronized (new_hash) { + Xowm_rev_sync_utl.Build_itms(cur_hash, ttls_ary); + Xowm_rev_sync_utl.Build_itms(new_hash, ttls_ary); + int len = new_hash.Count(); + for (int i = 0; i < len; i += batch_size) + Exec_batch(domain_str, i, len); + } + } + private void Exec_batch(String domain_str, int itms_bgn, int itms_len) { + int itms_end = itms_bgn + batch_size; if (itms_end > itms_len) itms_end = itms_len; + wkr__cur_meta.Fetch_meta(domain_str, cur_hash, itms_bgn, itms_end); + wkr__new_meta.Fetch_meta(domain_str, new_hash, itms_bgn, itms_end); + Xowm_rev_sync_utl.Remove_unchanged(cur_hash, new_hash, del_list, itms_bgn, itms_end); + wkr__new_text.Fetch_text(new_hash, itms_bgn, itms_end); + /* + loop (changed texts) { + parse page + } + loop (changed texts) { + get other ref + get files + } + loop (changed texts) { + update category + update search + } + */ + } +} +class Xowm_rev_sync_utl { + public static void Build_itms(Ordered_hash hash, Xoa_ttl[] ttls_ary) { + int len = ttls_ary.length; + for (int i = 0; i < len; ++i) { + Xoa_ttl ttl = ttls_ary[i]; + byte[] key = ttl.Full_db(); + if (hash.Has(key)) continue; // ignore dupes + hash.Add(key, new Wmapi_itm__pge().Init_ttl(ttl.Ns().Id(), ttl.Page_db())); + } + } + public static void Remove_unchanged(Ordered_hash cur_hash, Ordered_hash new_hash, List_adp del_list, int bgn, int end) { + del_list.Clear(); + for (int i = bgn; i < end; ++i) { + Wmapi_itm__pge new_itm = (Wmapi_itm__pge)new_hash.Get_at(i); + byte[] new_key = new_itm.Page_ttl(); + Wmapi_itm__pge cur_itm = (Wmapi_itm__pge)cur_hash.Get_by(new_key); if (cur_itm == null) continue; // itm not found; ignore + if (new_itm.Eq_meta(cur_itm, 0)) // itm is same; add to deleted list + del_list.Add(new_itm); + } + int len = del_list.Count(); + for (int i = 0; i < len; ++i) { + Wmapi_itm__pge itm = (Wmapi_itm__pge)del_list.Get_at(i); + new_hash.Del(itm.Page_ttl()); + } + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xowm_rev_wkr__meta__wm.java b/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xowm_rev_wkr__meta__wm.java index a27517de8..3440b9986 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xowm_rev_wkr__meta__wm.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xowm_rev_wkr__meta__wm.java @@ -13,3 +13,41 @@ 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.bldrs.wms.revs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import gplx.langs.jsons.*; import gplx.core.net.*; +class Xowm_rev_wkr__meta__wm implements Xowm_rev_wkr__meta { + private final Json_parser json_parser = new Json_parser(); + private final Xowm_json_parser__page json_page_parser = new Xowm_json_parser__page(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New_w_size(255); + private final Wmapi_itm__pge tmp_pge = new Wmapi_itm__pge(); + public Xowm_rev_wkr__meta__wm() { + tmp_pge.Rvn_ary_(new Wmapi_itm__rvn()); + } + public Gfo_inet_conn Inet_conn() {return inet_conn;} public void Inet_conn_(Gfo_inet_conn v) {this.inet_conn = v;} private Gfo_inet_conn inet_conn; + public void Fetch_meta(String domain_str, Ordered_hash hash, int bgn, int end) { + for (int i = bgn; i < end; ++i) { + Wmapi_itm__pge itm = (Wmapi_itm__pge)hash.Get_at(i); + if (i != bgn) tmp_bfr.Add_byte(Byte_ascii.Pipe); + tmp_bfr.Add(itm.Page_ttl()); + } + byte[] json = inet_conn.Download_as_bytes_or_null(Xowm_api_mgr.Bld_api_url(domain_str, "action=query&prop=revisions&rvprop=size|ids|timestamp|user|comment&format=json&rawcontinue=titles=" + tmp_bfr.To_str_and_clear())); + Parse_doc(hash, json); + } + private void Parse_doc(Ordered_hash hash, byte[] json) { + Json_doc jdoc = json_parser.Parse(json); + Json_nde pages_nde = Json_nde.cast(jdoc.Get_grp_many(Jpath__query_pages)); + int len = pages_nde.Len(); + for (int i = 0; i < len; ++i) { + Parse_page(hash, pages_nde.Get_at_as_kv(i).Val_as_nde()); + } + } + private void Parse_page(Ordered_hash hash, Json_nde page_nde) { + json_page_parser.Parse("update", tmp_pge, page_nde); // have to pass tmp_pge, b/c don't know which itm is in hash + Wmapi_itm__pge hash_itm = (Wmapi_itm__pge)hash.Get_by(tmp_pge.Page_ttl()); if (hash_itm == null) return; + hash_itm.Init_id(tmp_pge.Page_id()); + Wmapi_itm__rvn tmp_rvn = tmp_pge.Rvn_itm_last(); + hash_itm.Rvn_ary_(new Wmapi_itm__rvn()); + hash_itm.Rvn_itm_last().Init(tmp_rvn.Rvn_id(), tmp_rvn.Rvn_len(), tmp_rvn.Rvn_time(), tmp_rvn.Rvn_user(), tmp_rvn.Rvn_note()); + } + private static final byte[][] Jpath__query_pages = Bry_.Ary("query", "pages"); +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xowm_rev_wkr__meta__wm_tst.java b/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xowm_rev_wkr__meta__wm_tst.java index a27517de8..45d9304f6 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xowm_rev_wkr__meta__wm_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xowm_rev_wkr__meta__wm_tst.java @@ -13,3 +13,69 @@ 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.bldrs.wms.revs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import org.junit.*; import gplx.langs.jsons.*; import gplx.core.net.*; import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.domains.*; +public class Xowm_rev_wkr__meta__wm_tst { + private final Xowm_rev_wkr__meta__wm_fxt fxt = new Xowm_rev_wkr__meta__wm_fxt(); + @Before public void init() {Gfo_usr_dlg_.Instance = Gfo_usr_dlg_.Test_console();} + @After public void term() {Gfo_usr_dlg_.Instance = Gfo_usr_dlg_.Noop;} + @Test public void Basic() { + Wmapi_itm__pge[] expd = fxt.Make_pge_ary + ( fxt.Make_pge(Xow_ns_.Tid__main, "A", 1, 11, 100, "2015-01-01T01:01:01Z", "user1", "note1") + , fxt.Make_pge(Xow_ns_.Tid__main, "B", 2, 22, 200, "2015-02-02T02:02:02Z", "user2", "note2") + ); + fxt.Init_inet_upload(expd); + fxt.Test_fetch(String_.Ary("A", "B"), expd); // test get both + fxt.Init_inet_upload(expd); + fxt.Test_fetch(String_.Ary("A") , expd[0]); // test get 1 + fxt.Init_inet_upload(expd); + fxt.Test_fetch(String_.Ary("C"), fxt.Make_pge_empty(0, "C")); // test get none + fxt.Init_inet_upload(expd); + fxt.Test_fetch(String_.Ary("A", "B", "C"), expd[0], expd[1], fxt.Make_pge_empty(0, "C")); // test get too many + } +} +class Xowm_rev_wkr__meta__wm_fxt { + private final String domain_str = Xow_domain_itm_.Str__enwiki; + private final Ordered_hash rev_hash = Ordered_hash_.New_bry(); + private final Xowm_rev_wkr__meta__wm meta_wkr = new Xowm_rev_wkr__meta__wm(); + private final Wmapi_itm_json_wtr json_wtr = new Wmapi_itm_json_wtr(); + public Xowm_rev_wkr__meta__wm_fxt() { + meta_wkr.Inet_conn_(Gfo_inet_conn_.new_mem_pile()); + } + public Wmapi_itm__pge[] Make_pge_ary(Wmapi_itm__pge... ary) {return ary;} + public Wmapi_itm__pge Make_pge_empty(int page_ns, String page_ttl) { + Wmapi_itm__pge rv = Make_pge(page_ns, page_ttl, 0, 0, 0, null, null, null); + rv.Rvn_ary_(Wmapi_itm__rvn.Ary_empty); + return rv; + } + public Wmapi_itm__pge Make_pge(int page_ns, String page_ttl, int page_id, int rev_id, int rev_len, String rev_time, String rev_user, String rev_note) { + Wmapi_itm__pge rv = new Wmapi_itm__pge(); + rv.Init_id(page_id); + rv.Init_ttl(page_ns, Bry_.new_u8(page_ttl)); + Wmapi_itm__rvn rvn = new Wmapi_itm__rvn(); + rv.Rvn_ary_(rvn); + rvn.Init(rev_id, rev_len, Bry_.new_a7(rev_time), Bry_.new_a7(rev_user), Bry_.new_a7(rev_note)); + return rv; + } + public void Init_inet_upload(Wmapi_itm__pge... ary) { + Gfo_inet_conn inet_conn = meta_wkr.Inet_conn(); + byte[] page = json_wtr.To_bry(ary); + inet_conn.Clear(); + inet_conn.Upload_by_bytes(domain_str, page); + } + public void Test_fetch(String[] ttl_ary, Wmapi_itm__pge... expd) { + Init_rev_hash(ttl_ary); + meta_wkr.Fetch_meta(domain_str, rev_hash, 0, rev_hash.Count()); + Tfds.Eq_str_lines(String_.new_u8(json_wtr.To_bry(expd)), String_.new_u8(json_wtr.To_bry((Wmapi_itm__pge[])rev_hash.To_ary_and_clear(Wmapi_itm__pge.class)))); + } + private void Init_rev_hash(String[] ttl_ary) { + rev_hash.Clear(); + int len = ttl_ary.length; + for (int i = 0; i < len; ++i) { + String ttl_str = ttl_ary[i]; + byte[] ttl_bry = Bry_.new_u8(ttl_str); + rev_hash.Add(ttl_bry, new Wmapi_itm__pge().Init_ttl(Xow_ns_.Tid__main, ttl_bry)); + } + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xwom_rev_wkr__meta.java b/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xwom_rev_wkr__meta.java index a27517de8..ff09345aa 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xwom_rev_wkr__meta.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/revs/Xwom_rev_wkr__meta.java @@ -13,3 +13,14 @@ 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.bldrs.wms.revs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +interface Xowm_rev_wkr__meta { + void Fetch_meta(String domain_bry, Ordered_hash hash, int bgn, int end); +} +interface Xowm_rev_wkr__text { + void Fetch_text(Ordered_hash hash, int bgn, int end); +} +class Xowm_rev_wkr__meta__xo { + public void Fetch_meta(Ordered_hash hash, int bgn, int end) { + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_core_db.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_core_db.java index a27517de8..61c131c32 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_core_db.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_core_db.java @@ -13,3 +13,122 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import gplx.dbs.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.xwikis.*; import gplx.xowa.wikis.xwikis.bldrs.*; +public class Site_core_db { + private Db_conn conn; + private final Site_core_tbl tbl__core; + private final Site_kv_tbl tbl__general; + private final Site_namespace_tbl tbl__namespace; + private final Site_statistic_tbl tbl__statistic; + private final Site_interwikimap_tbl tbl__interwikimap; + private final Site_namespacealias_tbl tbl__namespacealias; + private final Site_specialpagealias_tbl tbl__specialpagealias; + private final Site_library_tbl tbl__library; + private final Site_extension_tbl tbl__extension; + private final Site_skin_tbl tbl__skin; + private final Site_magicword_tbl tbl__magicword; + private final Site_val_tbl tbl__functionhook; + private final Site_showhook_tbl tbl__showhook; + private final Site_val_tbl tbl__extensiontag; + private final Site_val_tbl tbl__protocol; + private final Site_kv_tbl tbl__defaultoption; + private final Site_language_tbl tbl__language; + private final Db_tbl[] tbl_ary; + public Site_core_db(Io_url db_url) { + Db_conn_bldr_data conn_data = Db_conn_bldr.Instance.Get_or_new(db_url); + this.conn = conn_data.Conn(); boolean created = conn_data.Created(); + this.tbl__core = new Site_core_tbl(conn); + this.tbl__general = new Site_kv_tbl(conn, "site_general"); + this.tbl__namespace = new Site_namespace_tbl(conn); + this.tbl__statistic = new Site_statistic_tbl(conn); + this.tbl__namespacealias = new Site_namespacealias_tbl(conn); + this.tbl__interwikimap = new Site_interwikimap_tbl(conn); + this.tbl__specialpagealias = new Site_specialpagealias_tbl(conn); + this.tbl__library = new Site_library_tbl(conn); + this.tbl__extension = new Site_extension_tbl(conn); + this.tbl__skin = new Site_skin_tbl(conn); + this.tbl__magicword = new Site_magicword_tbl(conn); + this.tbl__functionhook = new Site_val_tbl(conn, "site_functionhook"); + this.tbl__showhook = new Site_showhook_tbl(conn); + this.tbl__extensiontag = new Site_val_tbl(conn, "site_extensiontag"); + this.tbl__protocol = new Site_val_tbl(conn, "site_protocol"); + this.tbl__defaultoption = new Site_kv_tbl(conn, "site_defaultoption"); + this.tbl__language = new Site_language_tbl(conn); + this.tbl_ary = new Db_tbl[] + { tbl__core, tbl__general, tbl__namespace, tbl__statistic, tbl__interwikimap, tbl__namespacealias, tbl__specialpagealias, tbl__library + , tbl__extension, tbl__skin, tbl__magicword, tbl__functionhook, tbl__showhook, tbl__extensiontag, tbl__protocol, tbl__defaultoption, tbl__language + }; + if (created) Db_tbl_.Create_tbl(tbl_ary); // NOTE: each Create_tbl also does Create_idx; will be "slower" for inserts, but logic is simpler for updates + } + public Site_core_tbl Tbl__core() {return tbl__core;} + public Site_namespace_tbl Tbl__namespace() {return tbl__namespace;} + public void Rls() {Db_tbl_.Rls(tbl_ary);} + public void Save(Site_meta_itm site_meta, byte[] site_abrv) { + conn.Txn_bgn("site_meta"); + tbl__general.Insert(site_abrv, site_meta.General_list()); + tbl__statistic.Insert(site_abrv, site_meta.Statistic_itm()); + tbl__namespace.Insert(site_abrv, site_meta.Namespace_list()); + tbl__interwikimap.Insert(site_abrv, site_meta.Interwikimap_list()); + tbl__namespacealias.Insert(site_abrv, site_meta.Namespacealias_list()); + tbl__specialpagealias.Insert(site_abrv, site_meta.Specialpagealias_list()); + tbl__library.Insert(site_abrv, site_meta.Library_list()); + tbl__extension.Insert(site_abrv, site_meta.Extension_list()); + tbl__skin.Insert(site_abrv, site_meta.Skin_list()); + tbl__magicword.Insert(site_abrv, site_meta.Magicword_list()); + tbl__showhook.Insert(site_abrv, site_meta.Showhook_list()); + tbl__functionhook.Insert(site_abrv, site_meta.Functionhook_list()); + tbl__extensiontag.Insert(site_abrv, site_meta.Extensiontag_list()); + tbl__protocol.Insert(site_abrv, site_meta.Protocol_list()); + tbl__defaultoption.Insert(site_abrv, site_meta.Defaultoption_list()); + tbl__language.Insert(site_abrv, site_meta.Language_list()); + tbl__core.Update(site_abrv, Bool_.Y); + conn.Txn_end(); + } + public void Load(Site_meta_itm site_meta, byte[] site_abrv) { + tbl__general.Select(site_abrv, site_meta.General_list()); + tbl__statistic.Select(site_abrv, site_meta.Statistic_itm()); + tbl__namespace.Select(site_abrv, site_meta.Namespace_list()); + tbl__interwikimap.Select(site_abrv, site_meta.Interwikimap_list()); + tbl__namespacealias.Select(site_abrv, site_meta.Namespacealias_list()); + tbl__specialpagealias.Select(site_abrv, site_meta.Specialpagealias_list()); + tbl__library.Select(site_abrv, site_meta.Library_list()); + tbl__extension.Select(site_abrv, site_meta.Extension_list()); + tbl__skin.Select(site_abrv, site_meta.Skin_list()); + tbl__magicword.Insert(site_abrv, site_meta.Magicword_list()); + tbl__showhook.Select(site_abrv, site_meta.Showhook_list()); + tbl__functionhook.Select(site_abrv, site_meta.Functionhook_list()); + tbl__extensiontag.Select(site_abrv, site_meta.Extensiontag_list()); + tbl__protocol.Select(site_abrv, site_meta.Protocol_list()); + tbl__defaultoption.Select(site_abrv, site_meta.Defaultoption_list()); + tbl__language.Select(site_abrv, site_meta.Language_list()); + } + public Xow_ns_mgr Load_namespace(byte[] domain_bry) { + Xow_ns_mgr rv = new Xow_ns_mgr(gplx.xowa.langs.cases.Xol_case_mgr_.U8()); + Ordered_hash hash = Ordered_hash_.New(); + tbl__namespace.Select(Xow_abrv_xo_.To_bry(domain_bry), hash); + Ns_mgr__load(rv, hash); + return rv; + } + public void Load_interwikimap(Xow_domain_itm domain_itm, gplx.xowa.wikis.xwikis.Xow_xwiki_mgr xwiki_mgr) { + Ordered_hash hash = Ordered_hash_.New(); + tbl__interwikimap.Select(domain_itm.Abrv_xo(), hash); + int len = hash.Count(); + for (int i = 0; i < len; ++i) { + Site_interwikimap_itm itm = (Site_interwikimap_itm)hash.Get_at(i); + Xow_xwiki_itm xwiki_itm = Xow_xwiki_itm_bldr.Instance.Bld_mw(domain_itm, itm.Prefix, itm.Url, null); + xwiki_mgr.Add_itm(xwiki_itm); + } + } + private static void Ns_mgr__load(Xow_ns_mgr rv, Ordered_hash hash) { + rv.Clear(); + int len = hash.Count(); + for (int i = 0; i < len; ++i) { + Site_namespace_itm itm = (Site_namespace_itm)hash.Get_at(i); + byte case_match = Xow_ns_case_.To_tid(String_.new_u8(itm.Case_tid())); + rv.Add_new(itm.Id(), itm.Localized(), case_match, Bool_.N); + } + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_core_itm.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_core_itm.java index a27517de8..d9ed6e89b 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_core_itm.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_core_itm.java @@ -13,3 +13,19 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +public class Site_core_itm { + public Site_core_itm(byte[] site_abrv, byte[] site_domain, boolean json_completed, DateAdp json_date, byte[] json_text) { + this.site_abrv = site_abrv; + this.site_domain = site_domain; + this.json_completed = json_completed; + this.json_date = json_date; + this.json_text = json_text; + } + public byte[] Site_abrv() {return site_abrv;} private final byte[] site_abrv; + public byte[] Site_domain() {return site_domain;} private final byte[] site_domain; + public boolean Json_completed() {return json_completed;} private final boolean json_completed; + public DateAdp Json_date() {return json_date;} private final DateAdp json_date; + public byte[] Json_text() {return json_text;} private byte[] json_text; + public void Json_text_null_() {json_text = null;} +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_core_tbl.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_core_tbl.java index a27517de8..4311226da 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_core_tbl.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_core_tbl.java @@ -13,3 +13,81 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import gplx.dbs.*; +public class Site_core_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_site_abrv, fld_site_domain, fld_json_completed, fld_json_date, fld_json_text; + private final Db_conn conn; + private Db_stmt stmt_select, stmt_insert, stmt_delete, stmt_update; + public Site_core_tbl(Db_conn conn) { + this.conn = conn; + this.fld_site_abrv = flds.Add_str("site_abrv", 255); + this.fld_site_domain = flds.Add_str("site_domain", 255); + this.fld_json_completed = flds.Add_bool("json_completed"); + this.fld_json_date = flds.Add_str("json_date", 20); + this.fld_json_text = flds.Add_text("json_text"); + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} + public String Tbl_name() {return tbl_name;} private static final String tbl_name = "site_core"; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_unique_by_name(tbl_name, Dbmeta_idx_itm.Bld_idx_name(tbl_name, "main"), fld_site_abrv)));} + public void Rls() { + stmt_select = Db_stmt_.Rls(stmt_select); + stmt_insert = Db_stmt_.Rls(stmt_insert); + stmt_delete = Db_stmt_.Rls(stmt_delete); + stmt_update = Db_stmt_.Rls(stmt_update); + } + public void Update(byte[] site_abrv, boolean json_completed) { + if (stmt_update == null) stmt_update = conn.Stmt_update(tbl_name, String_.Ary(fld_site_abrv), fld_json_completed); + stmt_update.Clear() + .Val_bool_as_byte (fld_json_completed , json_completed) + .Crt_bry_as_str (fld_site_abrv , site_abrv) + .Exec_update(); + } + public void Insert(byte[] site_abrv, byte[] site_domain, boolean json_completed, DateAdp json_date, byte[] json_text) { + if (stmt_delete == null) stmt_delete = conn.Stmt_delete(tbl_name, fld_site_abrv); + stmt_delete.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_delete(); + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Clear() + .Val_bry_as_str (fld_site_abrv , site_abrv) + .Val_bry_as_str (fld_site_domain , site_domain) + .Val_bool_as_byte (fld_json_completed , json_completed) + .Val_str (fld_json_date , json_date.XtoStr_gplx()) + .Val_bry_as_str (fld_json_text , json_text) + .Exec_insert(); + } + public Site_core_itm Select_itm(byte[] site_abrv) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_site_abrv) + .Clear() + .Crt_bry_as_str(fld_site_abrv, site_abrv) + .Exec_select__rls_auto(); + try { + return (rdr.Move_next()) ? new_itm(rdr) : null; + } + finally {rdr.Rls();} + } + public Site_core_itm[] Select_all_downloaded(DateAdp cutoff) { + List_adp list = List_adp_.New(); + Db_rdr rdr = conn.Stmt_select(tbl_name, flds).Clear().Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + Site_core_itm itm = new_itm(rdr); + if (itm.Json_date().compareTo(cutoff) == CompareAble_.More_or_same) continue; // ignore those downloaded after cutoff date + itm.Json_text_null_(); + list.Add(itm); + } + } + finally {rdr.Rls();} + return (Site_core_itm[])list.To_ary_and_clear(Site_core_itm.class); + } + private Site_core_itm new_itm(Db_rdr rdr) { + return new Site_core_itm + ( rdr.Read_bry_by_str(fld_site_abrv) + , rdr.Read_bry_by_str(fld_site_domain) + , rdr.Read_bool_by_byte(fld_json_completed) + , rdr.Read_date_by_str(fld_json_date) + , rdr.Read_bry_by_str(fld_json_text) + ); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_extension_itm.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_extension_itm.java index a27517de8..d7103d2b8 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_extension_itm.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_extension_itm.java @@ -13,3 +13,29 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +class Site_extension_itm implements To_str_able { + public Site_extension_itm(byte[] type, byte[] name, byte[] namemsg, byte[] description, byte[] descriptionmsg, byte[] author, byte[] url, byte[] version + , byte[] vcs_system, byte[] vcs_version, byte[] vcs_url, byte[] vcs_date, byte[] license_name, byte[] license, byte[] credits) { + this.type = type; this.name = name; this.namemsg = namemsg; this.description = description; this.descriptionmsg = descriptionmsg; this.author = author; this.url = url; this.version = version; + this.vcs_system = vcs_system; this.vcs_version = vcs_version; this.vcs_url = vcs_url; this.vcs_date = vcs_date; this.license_name = license_name; this.license = license; this.credits = credits; + this.key = Bry_.Add_w_dlm(Byte_ascii.Pipe, type, name); + } + public byte[] Key() {return key;} private final byte[] key; + public byte[] Type() {return type;} private final byte[] type; + public byte[] Name() {return name;} private final byte[] name; + public byte[] Namemsg() {return namemsg;} private final byte[] namemsg; + public byte[] Description() {return description;} private final byte[] description; + public byte[] Descriptionmsg() {return descriptionmsg;} private final byte[] descriptionmsg; + public byte[] Author() {return author;} private final byte[] author; + public byte[] Url() {return url;} private final byte[] url; + public byte[] Version() {return version;} private final byte[] version; + public byte[] Vcs_system() {return vcs_system;} private final byte[] vcs_system; + public byte[] Vcs_version() {return vcs_version;} private final byte[] vcs_version; + public byte[] Vcs_url() {return vcs_url;} private final byte[] vcs_url; + public byte[] Vcs_date() {return vcs_date;} private final byte[] vcs_date; + public byte[] License_name() {return license_name;} private final byte[] license_name; + public byte[] License() {return license;} private final byte[] license; + public byte[] Credits() {return credits;} private final byte[] credits; + public String To_str() {return String_.Concat_with_obj("|", type, name, namemsg, description, descriptionmsg, author, url, version, vcs_system, vcs_version, vcs_url, vcs_date, license_name, license, credits);} +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_extension_tbl.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_extension_tbl.java index a27517de8..56f5625eb 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_extension_tbl.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_extension_tbl.java @@ -13,3 +13,100 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import gplx.dbs.*; +class Site_extension_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_site_abrv, fld_type, fld_name, fld_namemsg, fld_description, fld_descriptionmsg, fld_author, fld_url, fld_version + , fld_vcs_system, fld_vcs_version, fld_vcs_url, fld_vcs_date, fld_license_name, fld_license, fld_credits; + private final Db_conn conn; + private Db_stmt stmt_select, stmt_insert, stmt_delete; + public Site_extension_tbl(Db_conn conn) { + this.conn = conn; + this.fld_site_abrv = flds.Add_str("site_abrv", 255); + this.fld_type = flds.Add_str("type", 255); + this.fld_name = flds.Add_str("name", 255); + this.fld_namemsg = flds.Add_str("namemsg", 255); + this.fld_description = flds.Add_str("description", 255); + this.fld_descriptionmsg = flds.Add_str("descriptionmsg", 255); + this.fld_author = flds.Add_str("author", 255); + this.fld_url = flds.Add_str("url", 255); + this.fld_version = flds.Add_str("version", 255); + this.fld_vcs_system = flds.Add_str("vcs_system", 255); + this.fld_vcs_version = flds.Add_str("vcs_version", 255); + this.fld_vcs_url = flds.Add_str("vcs_url", 255); + this.fld_vcs_date = flds.Add_str("vcs_date", 255); + this.fld_license_name = flds.Add_str("license_name", 255); + this.fld_license = flds.Add_str("license", 255); + this.fld_credits = flds.Add_str("credits", 255); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private static final String tbl_name = "site_extension"; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_unique_by_name(tbl_name, Dbmeta_idx_itm.Bld_idx_name(tbl_name, "main"), fld_site_abrv, fld_type, fld_name)));} + public void Delete_all() {conn.Stmt_delete(tbl_name, Dbmeta_fld_itm.Str_ary_empty).Exec_delete();} + public void Rls() { + stmt_select = Db_stmt_.Rls(stmt_select); + stmt_insert = Db_stmt_.Rls(stmt_insert); + stmt_delete = Db_stmt_.Rls(stmt_delete); + } + public void Select(byte[] site_abrv, Ordered_hash list) { + if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_site_abrv); + list.Clear(); + Db_rdr rdr = stmt_select.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + Site_extension_itm itm = new Site_extension_itm + ( rdr.Read_bry_by_str(fld_type) + , rdr.Read_bry_by_str(fld_name) + , rdr.Read_bry_by_str(fld_namemsg) + , rdr.Read_bry_by_str(fld_description) + , rdr.Read_bry_by_str(fld_descriptionmsg) + , rdr.Read_bry_by_str(fld_author) + , rdr.Read_bry_by_str(fld_url) + , rdr.Read_bry_by_str(fld_version) + , rdr.Read_bry_by_str(fld_vcs_system) + , rdr.Read_bry_by_str(fld_vcs_version) + , rdr.Read_bry_by_str(fld_vcs_url) + , rdr.Read_bry_by_str(fld_vcs_date) + , rdr.Read_bry_by_str(fld_license_name) + , rdr.Read_bry_by_str(fld_license) + , rdr.Read_bry_by_str(fld_credits) + ); + list.Add(itm.Key(), itm); + } + } + finally {rdr.Rls();} + } + public void Insert(byte[] site_abrv, Ordered_hash list) { + if (stmt_delete == null) stmt_delete = conn.Stmt_delete(tbl_name, fld_site_abrv); + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_delete.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_delete(); + int len = list.Count(); + for (int i = 0; i < len; ++i) { + Site_extension_itm itm = (Site_extension_itm)list.Get_at(i); + Insert(site_abrv, itm.Type(), itm.Name(), itm.Namemsg(), itm.Description(), itm.Descriptionmsg(), itm.Author(), itm.Url(), itm.Version() + , itm.Vcs_system(), itm.Vcs_version(), itm.Vcs_url(), itm.Vcs_date(), itm.License_name(), itm.License(), itm.Credits()); + } + } + private void Insert(byte[] site_abrv, byte[] type, byte[] name, byte[] namemsg, byte[] description, byte[] descriptionmsg, byte[] author, byte[] url, byte[] version + , byte[] vcs_system, byte[] vcs_version, byte[] vcs_url, byte[] vcs_date, byte[] license_name, byte[] license, byte[] credits) { + stmt_insert.Clear() + .Val_bry_as_str(fld_site_abrv , site_abrv) + .Val_bry_as_str(fld_type , type) + .Val_bry_as_str(fld_name , name) + .Val_bry_as_str(fld_namemsg , namemsg) + .Val_bry_as_str(fld_description , description) + .Val_bry_as_str(fld_descriptionmsg , descriptionmsg) + .Val_bry_as_str(fld_author , author) + .Val_bry_as_str(fld_url , url) + .Val_bry_as_str(fld_version , version) + .Val_bry_as_str(fld_vcs_system , vcs_system) + .Val_bry_as_str(fld_vcs_version , vcs_version) + .Val_bry_as_str(fld_vcs_url , vcs_url) + .Val_bry_as_str(fld_vcs_date , vcs_date) + .Val_bry_as_str(fld_license_name , license_name) + .Val_bry_as_str(fld_license , license) + .Val_bry_as_str(fld_credits , credits) + .Exec_insert(); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_general_itm.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_general_itm.java index a27517de8..0715f1693 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_general_itm.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_general_itm.java @@ -13,3 +13,98 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +class Site_general_itm implements To_str_able { + public Site_general_itm Ctor(byte[] main_page, byte[] base_url, byte[] site_name, byte[] logo, byte[] generator + , byte[] php_version, byte[] php_sapi, byte[] hhvm_version, byte[] db_type, byte[] db_version + , boolean image_whitelist_enabled, boolean lang_conversion, boolean title_conversion + , byte[] link_prefix_charset, byte[] link_prefix, byte[] link_trail, byte[] legal_title_chars + , byte[] git_hash, byte[] git_branch + , byte[] case_type, byte[] lang, byte[][] fallback, byte[] fallback_8bit_encoding + , boolean write_api, byte[] time_zone, int time_offset + , byte[] article_path, byte[] script_path, byte[] script, byte[] variant_article_path + , byte[] server, byte[] server_name, byte[] wiki_id, byte[] time + , boolean miser_mode, long max_upload_size, int[] thumb_limits, int[] image_limits, byte[] favicon + ) { + this.main_page = main_page; + this.base_url = base_url; + this.site_name = site_name; + this.logo = logo; + this.generator = generator; + this.php_version = php_version; + this.php_sapi = php_sapi; + this.hhvm_version = hhvm_version; + this.db_type = db_type; + this.db_version = db_version; + this.image_whitelist_enabled = image_whitelist_enabled; + this.lang_conversion = lang_conversion; + this.title_conversion = title_conversion; + this.link_prefix_charset = link_prefix_charset; + this.link_prefix = link_prefix; + this.link_trail = link_trail; + this.legal_title_chars = legal_title_chars; + this.git_hash = git_hash; + this.git_branch = git_branch; + this.case_type = case_type; + this.lang = lang; + this.fallback = fallback; + this.fallback_8bit_encoding = fallback_8bit_encoding; + this.write_api = write_api; + this.time_zone = time_zone; + this.time_offset = time_offset; + this.article_path = article_path; + this.script_path = script_path; + this.script = script; + this.variant_article_path = variant_article_path; + this.server = server; + this.server_name = server_name; + this.wiki_id = wiki_id; + this.time = time; + this.miser_mode = miser_mode; + this.max_upload_size = max_upload_size; + this.thumb_limits = thumb_limits; + this.image_limits = image_limits; + this.favicon = favicon; + return this; + } + public byte[] Main_page() {return main_page;} private byte[] main_page; + public byte[] Base_url() {return base_url;} private byte[] base_url; + public byte[] Site_name() {return site_name;} private byte[] site_name; + public byte[] Logo() {return logo;} private byte[] logo; + public byte[] Generator() {return generator;} private byte[] generator; + public byte[] Php_version() {return php_version;} private byte[] php_version; + public byte[] Php_sapi() {return php_sapi;} private byte[] php_sapi; + public byte[] Hhvm_version() {return hhvm_version;} private byte[] hhvm_version; + public byte[] Db_type() {return db_type;} private byte[] db_type; + public byte[] Db_version() {return db_version;} private byte[] db_version; + public boolean Image_whitelist_enabled() {return image_whitelist_enabled;} private boolean image_whitelist_enabled; + public boolean Lang_conversion() {return lang_conversion;} private boolean lang_conversion; + public boolean Title_conversion() {return title_conversion;} private boolean title_conversion; + public byte[] Link_prefix_charset() {return link_prefix_charset;} private byte[] link_prefix_charset; + public byte[] Link_prefix() {return link_prefix;} private byte[] link_prefix; + public byte[] Link_trail() {return link_trail;} private byte[] link_trail; + public byte[] Legal_title_chars() {return legal_title_chars;} private byte[] legal_title_chars; + public byte[] Git_hash() {return git_hash;} private byte[] git_hash; + public byte[] Git_branch() {return git_branch;} private byte[] git_branch; + public byte[] Case_type() {return case_type;} private byte[] case_type; + public byte[] Lang() {return lang;} private byte[] lang; + public byte[][] Fallback() {return fallback;} private byte[][] fallback; + public byte[] Fallback_8bit_encoding() {return fallback_8bit_encoding;} private byte[] fallback_8bit_encoding; + public boolean Write_api() {return write_api;} private boolean write_api; + public byte[] Time_zone() {return time_zone;} private byte[] time_zone; + public int Time_offset() {return time_offset;} private int time_offset; + public byte[] Article_path() {return article_path;} private byte[] article_path; + public byte[] Script_path() {return script_path;} private byte[] script_path; + public byte[] Script() {return script;} private byte[] script; + public byte[] Variant_article_path() {return variant_article_path;} private byte[] variant_article_path; + public byte[] Server() {return server;} private byte[] server; + public byte[] Server_name() {return server_name;} private byte[] server_name; + public byte[] Wiki_id() {return wiki_id;} private byte[] wiki_id; + public byte[] Time() {return time;} private byte[] time; + public boolean Miser_mode() {return miser_mode;} private boolean miser_mode; + public long Max_upload_size() {return max_upload_size;} private long max_upload_size; + public int[] Thumb_limits() {return thumb_limits;} private int[] thumb_limits; + public int[] Image_limits() {return image_limits;} private int[] image_limits; + public byte[] Favicon() {return favicon;} private byte[] favicon; + public String To_str() {return "";} +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_interwikimap_itm.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_interwikimap_itm.java index a27517de8..d710efcc3 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_interwikimap_itm.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_interwikimap_itm.java @@ -13,3 +13,29 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +class Site_interwikimap_itm implements To_str_able { + public Site_interwikimap_itm(byte[] prefix, boolean local + , boolean extralanglink, byte[] linktext, byte[] sitename + , byte[] language, boolean localinterwiki, byte[] url, boolean protorel) { + this.Prefix = prefix; + this.Local = local; + this.Extralanglink = extralanglink; + this.Linktext = linktext; + this.Sitename = sitename; + this.Language = language; + this.Localinterwiki = localinterwiki; + this.Url = url; + this.Protorel = protorel; + } + public final byte[] Prefix; + public final boolean Local; + public final boolean Extralanglink; + public final byte[] Linktext; + public final byte[] Sitename; + public final byte[] Language; + public final boolean Localinterwiki; + public final byte[] Url; + public final boolean Protorel; + public String To_str() {return String_.Concat_with_obj("|", Prefix, Local, Extralanglink, Linktext, Sitename, Language, Localinterwiki, Url, Protorel);} +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_interwikimap_tbl.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_interwikimap_tbl.java index a27517de8..d0947ecb9 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_interwikimap_tbl.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_interwikimap_tbl.java @@ -13,3 +13,79 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import gplx.dbs.*; +class Site_interwikimap_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_site_abrv, fld_prefix, fld_local, fld_extralanglink, fld_linktext, fld_sitename, fld_language, fld_localinterwiki, fld_url, fld_protorel; + private final Db_conn conn; + private Db_stmt stmt_select, stmt_insert, stmt_delete; + public Site_interwikimap_tbl(Db_conn conn) { + this.conn = conn; + this.fld_site_abrv = flds.Add_str("site_abrv", 255); + this.fld_prefix = flds.Add_str("prefix", 255); + this.fld_local = flds.Add_bool("local"); + this.fld_extralanglink = flds.Add_bool("extralanglink"); + this.fld_linktext = flds.Add_str("linktext", 255); + this.fld_sitename = flds.Add_str("sitename", 255); + this.fld_language = flds.Add_str("language", 255); + this.fld_localinterwiki = flds.Add_bool("localinterwiki"); + this.fld_url = flds.Add_str("url", 255); + this.fld_protorel = flds.Add_bool("protorel"); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private static final String tbl_name = "site_interwikimap"; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_unique_by_name(tbl_name, Dbmeta_idx_itm.Bld_idx_name(tbl_name, "main"), fld_site_abrv, fld_prefix)));} + public void Delete_all() {conn.Stmt_delete(tbl_name, Dbmeta_fld_itm.Str_ary_empty).Exec_delete();} + public void Rls() { + stmt_select = Db_stmt_.Rls(stmt_select); + stmt_insert = Db_stmt_.Rls(stmt_insert); + stmt_delete = Db_stmt_.Rls(stmt_delete); + } + public void Select(byte[] site_abrv, Ordered_hash list) { + if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_site_abrv); + list.Clear(); + Db_rdr rdr = stmt_select.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + Site_interwikimap_itm itm = new Site_interwikimap_itm + ( rdr.Read_bry_by_str(fld_prefix) + , rdr.Read_bool_by_byte(fld_local) + , rdr.Read_bool_by_byte(fld_extralanglink) + , rdr.Read_bry_by_str(fld_linktext) + , rdr.Read_bry_by_str(fld_sitename) + , rdr.Read_bry_by_str(fld_language) + , rdr.Read_bool_by_byte(fld_localinterwiki) + , rdr.Read_bry_by_str(fld_url) + , rdr.Read_bool_by_byte(fld_protorel) + ); + list.Add(itm.Prefix, itm); + } + } + finally {rdr.Rls();} + } + public void Insert(byte[] site_abrv, Ordered_hash list) { + if (stmt_delete == null) stmt_delete = conn.Stmt_delete(tbl_name, fld_site_abrv); + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_delete.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_delete(); + int len = list.Count(); + for (int i = 0; i < len; ++i) { + Site_interwikimap_itm itm = (Site_interwikimap_itm)list.Get_at(i); + Insert(site_abrv, itm.Prefix, itm.Local, itm.Extralanglink, itm.Linktext, itm.Sitename, itm.Language, itm.Localinterwiki, itm.Url, itm.Protorel); + } + } + private void Insert(byte[] site_abrv, byte[] prefix, boolean local, boolean extralanglink, byte[] linktext, byte[] sitename, byte[] language, boolean localinterwiki, byte[] url, boolean protorel) { + stmt_insert.Clear() + .Val_bry_as_str(fld_site_abrv , site_abrv) + .Val_bry_as_str(fld_prefix , prefix) + .Val_bool_as_byte(fld_local , local) + .Val_bool_as_byte(fld_extralanglink , extralanglink) + .Val_bry_as_str(fld_linktext , linktext) + .Val_bry_as_str(fld_sitename , sitename) + .Val_bry_as_str(fld_language , language) + .Val_bool_as_byte(fld_localinterwiki , localinterwiki) + .Val_bry_as_str(fld_url , url) + .Val_bool_as_byte(fld_protorel , protorel) + .Exec_insert(); + } +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_json_fetcher.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_json_fetcher.java index a27517de8..312fdeb33 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_json_fetcher.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_json_fetcher.java @@ -13,3 +13,52 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import gplx.core.net.*; import gplx.dbs.cfgs.*; +import gplx.langs.jsons.*; +class Site_json_fetcher { + private Gfo_usr_dlg usr_dlg; private Gfo_inet_conn inet_conn; private String domain_str; private Db_cfg_tbl cfg_tbl; + private Io_url default_url; + public void Init(Gfo_usr_dlg usr_dlg, Gfo_inet_conn inet_conn, String domain_str, Db_cfg_tbl cfg_tbl, Io_url default_url) { + this.usr_dlg = usr_dlg; + this.inet_conn = inet_conn; + this.domain_str = domain_str; + this.cfg_tbl = cfg_tbl; + this.default_url = default_url; + } + public void Init_by_wiki(Xow_wiki wiki) { + Xoa_app app = wiki.App(); + this.usr_dlg = app.Usr_dlg(); + this.inet_conn = app.Utl__inet_conn(); + this.domain_str = wiki.Domain_str(); + this.cfg_tbl = wiki.Data__core_mgr().Tbl__cfg(); + this.default_url = app.Fsys_mgr().Bin_xowa_dir().GenSubFil_nest("cfg", "wiki", "api", "xowa_default.json"); + } + public byte[] Get_json(Io_url custom_url) { + byte[] rv = null; + int trial = 0; + while (true) { + switch (trial) { + case 0: rv = Get_json_from_fs(custom_url); break; + case 1: rv = Get_json_from_db(cfg_tbl); break; + case 2: rv = Get_json_from_wm(usr_dlg, inet_conn, domain_str, cfg_tbl); break; + case 3: rv = Get_json_from_fs(default_url); break; + default: throw Err_.new_("api.site", "could not find site json", "wiki", domain_str); + } + if (rv == null) + ++trial; + else + break; + } + return rv; + } + private byte[] Get_json_from_fs(Io_url url) {return url == null ? null : Io_mgr.Instance.LoadFilBryOrNull(url);} + private byte[] Get_json_from_db(Db_cfg_tbl cfg_tbl) {return cfg_tbl.Select_bry(Cfg_grp__xowa_bldr_api, Cfg_key__xowa_bldr_api__data);} + private byte[] Get_json_from_wm(Gfo_usr_dlg usr_dlg, Gfo_inet_conn inet_conn, String domain_str, Db_cfg_tbl cfg_tbl) { + String api_str = "action=query&format=json&meta=siteinfo&siprop=general|namespaces|statistics|interwikimap|namespacealiases|specialpagealiases|libraries|extensions|skins|magicwords|functionhooks|showhooks|extensiontags|protocols|defaultoptions|languages"; + byte[] rv = Xowm_api_mgr.Call_by_qarg(usr_dlg, inet_conn, domain_str, api_str); + if (rv != null) cfg_tbl.Assert_bry(Cfg_grp__xowa_bldr_api, Cfg_key__xowa_bldr_api__data, rv); + return rv; + } + private static final String Cfg_grp__xowa_bldr_api = "xowa.bldr.api", Cfg_key__xowa_bldr_api__data = "data"; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_json_parser.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_json_parser.java index a27517de8..dd43c2144 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_json_parser.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_json_parser.java @@ -13,3 +13,90 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import gplx.core.primitives.*; import gplx.langs.jsons.*; +public class Site_json_parser { + private final Json_parser json_parser; + private final Json_parser__list_nde__base parser__basic = new Json_parser__list_nde__base(); + private final Site_meta_parser__general parser__general = new Site_meta_parser__general(); + private final Site_meta_parser__namespace parser__namespace = new Site_meta_parser__namespace(); + private final Site_meta_parser__statistic parser__statistic = new Site_meta_parser__statistic(); + private final Site_meta_parser__interwikimap parser__interwiki = new Site_meta_parser__interwikimap(); + private final Site_meta_parser__namespacealias parser__namespacealias = new Site_meta_parser__namespacealias(); + private final Site_meta_parser__specialpagealias parser__specialpagealias = new Site_meta_parser__specialpagealias(); + private final Site_meta_parser__library parser__library = new Site_meta_parser__library(); + private final Site_meta_parser__extension parser__extension = new Site_meta_parser__extension(); + private final Site_meta_parser__skin parser__skin = new Site_meta_parser__skin(); + private final Site_meta_parser__magicword parser__magicword = new Site_meta_parser__magicword(); + private final Site_meta_parser__showhook parser__showhook = new Site_meta_parser__showhook(); + private final Site_meta_parser__language parser__language = new Site_meta_parser__language(); + public Site_json_parser(Json_parser json_parser) {this.json_parser = json_parser;} + public void Parse_root(Site_meta_itm rv, String context, byte[] src) { + Json_doc jdoc = json_parser.Parse(src); + Parse_root(rv, context, jdoc.Root_nde().Get_at_as_kv(0).Val_as_nde()); + } + public void Parse_root(Site_meta_itm rv, String context, Json_nde root) { + int len = root.Len(); + for (int i = 0; i < len; ++i) { + Json_kv sub = root.Get_at_as_kv(i); + Parse_node(rv, context, sub); + } + } + private void Parse_node(Site_meta_itm rv, String context, Json_kv sub) { + byte[] sub_key = sub.Key_as_bry(); + switch (nde_hash.Get_as_int(sub_key)) { + case Tid_general : parser__general.Parse(context, rv.General_list(), sub.Val_as_nde()); break; + case Tid_namespace : parser__namespace.Parse(context, rv.Namespace_list(), sub.Val_as_nde()); break; + case Tid_statistic : parser__statistic.Parse(context, rv.Statistic_itm(), sub.Val_as_nde()); break; + case Tid_interwikimap : parser__interwiki.Parse(context, rv.Interwikimap_list(), sub.Val_as_ary()); break; + case Tid_namespacealias : parser__namespacealias.Parse(context, rv.Namespacealias_list(), sub.Val_as_ary()); break; + case Tid_specialpagealias : parser__specialpagealias.Parse(context, rv.Specialpagealias_list(), sub.Val_as_ary()); break; + case Tid_library : parser__library.Parse(context, rv.Library_list(), sub.Val_as_ary()); break; + case Tid_extension : parser__extension.Parse(context, rv.Extension_list(), sub.Val_as_ary()); break; + case Tid_skin : parser__skin.Parse(context, rv.Skin_list(), sub.Val_as_ary()); break; + case Tid_magicword : parser__magicword.Parse(context, rv.Magicword_list(), sub.Val_as_ary()); break; + case Tid_functionhook : parser__basic.Parse_to_list_as_bry(context, sub.Val_as_ary(), rv.Functionhook_list()); break; + case Tid_showhook : parser__showhook.Parse(context, rv.Showhook_list(), sub.Val_as_ary()); break; + case Tid_extensiontag : parser__basic.Parse_to_list_as_bry(context, sub.Val_as_ary(), rv.Extensiontag_list()); break; + case Tid_protocol : parser__basic.Parse_to_list_as_bry(context, sub.Val_as_ary(), rv.Protocol_list()); break; + case Tid_defaultoption : parser__basic.Parse_to_list_as_kv(context, sub.Val_as_nde(), rv.Defaultoption_list()); break; + case Tid_language : parser__language.Parse(context, rv.Language_list(), sub.Val_as_ary()); break; + } + } + private static final int + Tid_general = 0 + , Tid_namespace = 1 + , Tid_statistic = 2 + , Tid_interwikimap = 3 + , Tid_namespacealias = 4 + , Tid_specialpagealias = 5 + , Tid_library = 6 + , Tid_extension = 7 + , Tid_skin = 8 + , Tid_magicword = 9 + , Tid_functionhook = 10 + , Tid_showhook = 11 + , Tid_extensiontag = 12 + , Tid_protocol = 13 + , Tid_defaultoption = 14 + , Tid_language = 15 + ; + private static final Hash_adp_bry nde_hash = Hash_adp_bry.cs() + .Add_str_int("general" , Tid_general) + .Add_str_int("namespaces" , Tid_namespace) + .Add_str_int("statistics" , Tid_statistic) + .Add_str_int("interwikimap" , Tid_interwikimap) + .Add_str_int("namespacealiases" , Tid_namespacealias) + .Add_str_int("specialpagealiases" , Tid_specialpagealias) + .Add_str_int("libraries" , Tid_library) + .Add_str_int("extensions" , Tid_extension) + .Add_str_int("skins" , Tid_skin) + .Add_str_int("magicwords" , Tid_magicword) + .Add_str_int("functionhooks" , Tid_functionhook) + .Add_str_int("showhooks" , Tid_showhook) + .Add_str_int("extensiontags" , Tid_extensiontag) + .Add_str_int("protocols" , Tid_protocol) + .Add_str_int("defaultoptions" , Tid_defaultoption) + .Add_str_int("languages" , Tid_language) + ; +} diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_json_parser_tst.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_json_parser_tst.java index a27517de8..35d6d5498 100644 --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_json_parser_tst.java +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_json_parser_tst.java @@ -13,3 +13,417 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*; +import org.junit.*; import gplx.langs.jsons.*; import gplx.xowa.wikis.nss.*; +public class Site_json_parser_tst { + private final Site_json_parser_fxt fxt = new Site_json_parser_fxt(); + @Before public void init() {Gfo_usr_dlg_.Instance = Gfo_usr_dlg_.Test_console();} + @After public void term() {Gfo_usr_dlg_.Instance = Gfo_usr_dlg_.Noop;} + @Test public void General() { + fxt.Exec_parse(String_.Concat_lines_nl_skip_last + ( "{ 'general':" + , " { 'mainpage': 'Main Page'" + , " , 'imagewhitelistenabled': ''" + , " , 'timeoffset': 0" + , " , 'thumblimits': " + , " [ 120" + , " , 150" + , " ]" + , " , 'imagelimits': " + , " [ " + , " { 'width': 320" + , " , 'height': 240" + , " }" + , " , " + , " { 'width': 640" + , " , 'height': 480" + , " }" + , " ]" + , " }" + , "}" + )); + fxt.Test_general(Keyval_.new_("mainpage", "Main Page"), Keyval_.new_("imagewhitelistenabled", ""), Keyval_.new_("timeoffset", "0"), Keyval_.new_("thumblimits", "120|150"), Keyval_.new_("imagelimits", "320=240|640=480")); + } + @Test public void Namespace() { + fxt.Exec_parse(String_.Concat_lines_nl_skip_last + ( "{ 'namespaces':" + , " { '0':" + , " { 'id': 0" + , " , 'case': 'first-letter'" + , " , 'content': ''" + , " , '*': ''" + , " }" + , " , '2': " + , " { 'id': 2" + , " , 'case': 'first-letter'" + , " , 'subpages': ''" + , " , 'canonical': 'User'" + , " , '*': 'User'" + , " }" + , " ,'4': " + , " { 'id': 4" + , " , 'case': 'first-letter'" + , " , 'subpages': ''" + , " , 'canonical': 'Project'" + , " , '*': 'Wikipedia'" + , " }" + , " ,'2600': " + , " { 'id': 2600" + , " , 'case': 'case-sensitive'" + , " , 'canonical': 'Topic'" + , " , 'defaultcontentmodel': 'flow-board'" + , " , '*': 'Topic'" + , " }" + , " }" + , "}" + )); + fxt.Test_namespace + ( fxt.Make_namespace(0 , Bool_.N, null , "" , Bool_.N, Bool_.Y, null) + , fxt.Make_namespace(2 , Bool_.N, "User" , "User" , Bool_.Y, Bool_.N, null) + , fxt.Make_namespace(4 , Bool_.N, "Project" , "Wikipedia" , Bool_.Y, Bool_.N, null) + , fxt.Make_namespace(2600 , Bool_.Y, "Topic" , "Topic" , Bool_.N, Bool_.N, "flow-board") + ); + } + @Test public void Statistic() { + fxt.Exec_parse(String_.Concat_lines_nl_skip_last + ( "{ 'statistics':" + , " { 'pages': 1" + , " , 'articles': 2" + , " , 'edits': 3" + , " , 'images': 4" + , " , 'users': 5" + , " , 'activeusers': 6" + , " , 'admins': 7" + , " , 'jobs': 8" + , " , 'queued-massmessages': 9" + , " }" + , "}" + )); + fxt.Test_statistic + ( fxt.Make_statistic(1, 2, 3, 4, 5, 6, 7, 8, 9) + ); + } + @Test public void Interwikimap() { + fxt.Exec_parse(String_.Concat_lines_nl_skip_last + ( "{ 'interwikimap':" + , " [" + , " { 'prefix': 'aquariumwiki'" + , " , 'url': 'http://www.theaquariumwiki.com/$1'" + , " }" + , " , { 'prefix': 'ar'" + , " , 'local': ''" + , " , 'extralanglink': ''" + , " , 'linktext': 'More languages'" + , " , 'sitename': 'Multilingual Wikisource'" + , " , 'language': '\u0627\u0644\u0639\u0631\u0628\u064a\u0629'" + , " , 'url': 'https://ar.wikipedia.org/wiki/$1'" + , " , 'protorel': ''" + , " }" + , " ]" + , "}" + )); + fxt.Test_interwikimap + ( fxt.Make_interwikimap("aquariumwiki" , Bool_.N, Bool_.N, null , null , null , Bool_.N, "http://www.theaquariumwiki.com/$1" , Bool_.N) + , fxt.Make_interwikimap("ar" , Bool_.Y, Bool_.Y, "More languages", "Multilingual Wikisource" , "العربية" , Bool_.N, "https://ar.wikipedia.org/wiki/$1" , Bool_.Y) + ); + } + @Test public void Namespacealias() { + fxt.Exec_parse(String_.Concat_lines_nl_skip_last + ( "{ 'namespacealiases':" + , " [ " + , " { 'id': 4" + , " , '*': 'WP'" + , " }" + , " , " + , " { 'id': 7" + , " , '*': 'Image talk'" + , " }" + , " ]" + , "}" + )); + fxt.Test_namespacealias + ( fxt.Make_namespacealias(4 ,"WP") + , fxt.Make_namespacealias(7 , "Image talk") + ); + } + @Test public void Specialpagealias() { + fxt.Exec_parse(String_.Concat_lines_nl_skip_last + ( "{ 'specialpagealiases':" + , " [ " + , " { 'realname': 'BrokenRedirects'" + , " , 'aliases': " + , " [ 'BrokenRedirects'" + , " ]" + , " }" + , " , " + , " { 'realname': 'Lonelypages'" + , " , 'aliases': " + , " [ 'LonelyPages'" + , " , 'OrphanedPages'" + , " ]" + , " }" + , " ]" + , "}" + )); + fxt.Test_specialpagealias + ( fxt.Make_specialpagealias("BrokenRedirects" , "BrokenRedirects") + , fxt.Make_specialpagealias("Lonelypages" , "LonelyPages", "OrphanedPages") + ); + } + @Test public void Library() { + fxt.Exec_parse(String_.Concat_lines_nl_skip_last + ( "{ 'libraries':" + , " [ " + , " { 'name': 'cssjanus/cssjanus'" + , " , 'version': '1.1.1'" + , " }" + , " , " + , " { 'name': 'leafo/lessphp'" + , " , 'version': '0.5.0'" + , " }" + , " ]" + , "}" + )); + fxt.Test_library + ( fxt.Make_library("cssjanus/cssjanus" , "1.1.1") + , fxt.Make_library("leafo/lessphp" , "0.5.0") + ); + } + @Test public void Extension() { + fxt.Exec_parse(String_.Concat_lines_nl_skip_last + ( "{ 'extensions':" + , " [ " + , " { 'type': 'media'" + , " , 'name': 'PagedTiffHandler'" + , " , 'descriptionmsg': 'tiff-desc'" + , " , 'author': '[http://www.hallowelt.biz HalloWelt! Medienwerkstatt GmbH], Sebastian Ulbricht, Daniel Lynge, Marc Reymann, Markus Glaser for Wikimedia Deutschland'" + , " , 'url': 'https://www.mediawiki.org/wiki/Extension:PagedTiffHandler'" + , " , 'vcs-system': 'git'" + , " , 'vcs-version': 'b4a6c2077e3ea70cb0295b2282fe45d2e9ae06ba'" + , " , 'vcs-url': 'https://git.wikimedia.org/tree/mediawiki%2Fextensions%2FPagedTiffHandler.git/b4a6c2077e3ea70cb0295b2282fe45d2e9ae06ba'" + , " , 'vcs-date': '2015-03-27T16:44:25Z'" + , " , 'license-name': 'GPL-2.0+'" + , " , 'license': '/wiki/Special:Version/License/PagedTiffHandler'" + , " }" + , " , " + , " { 'type': 'media'" + , " , 'name': 'A'" + , " , 'namemsg': 'A-name'" + , " , 'description': 'desc'" + , " , 'descriptionmsg': 'A-desc'" + , " , 'author': 'B'" + , " , 'url': 'C'" + , " , 'version': '0.1.0'" + , " , 'vcs-system': 'git'" + , " , 'vcs-version': 'd'" + , " , 'vcs-url': 'e'" + , " , 'vcs-date': '2015-03-27T16:44:25Z'" + , " , 'license-name': 'f'" + , " , 'license': 'g'" + , " }" + , " ]" + , "}" + )); + fxt.Test_extension + ( fxt.Make_extension("media", "PagedTiffHandler", "", "", "tiff-desc", "[http://www.hallowelt.biz HalloWelt! Medienwerkstatt GmbH], Sebastian Ulbricht, Daniel Lynge, Marc Reymann, Markus Glaser for Wikimedia Deutschland", "https://www.mediawiki.org/wiki/Extension:PagedTiffHandler", null, "git", "b4a6c2077e3ea70cb0295b2282fe45d2e9ae06ba", "https://git.wikimedia.org/tree/mediawiki%2Fextensions%2FPagedTiffHandler.git/b4a6c2077e3ea70cb0295b2282fe45d2e9ae06ba", "2015-03-27T16:44:25Z", "GPL-2.0+", "/wiki/Special:Version/License/PagedTiffHandler", null) + , fxt.Make_extension("media", "A", "A-name", "desc", "A-desc", "B", "C", "0.1.0", "git", "d", "e", "2015-03-27T16:44:25Z", "f", "g", null) + ); + } + @Test public void Skin() { + fxt.Exec_parse(String_.Concat_lines_nl_skip_last + ( "{ 'skins':" + , " [ " + , " { 'code': 'vector'" + , " , 'default': ''" + , " , '*': 'Vector'" + , " }" + , " , " + , " { 'code': 'monobook'" + , " , '*': 'MonoBook'" + , " }" + , " , " + , " { 'code': 'fallback'" + , " , 'unusable': ''" + , " , '*': 'Fallback'" + , " }" + , " ]" + , "}" + )); + fxt.Test_skin + ( fxt.Make_skin("vector" , Bool_.Y, "Vector" , Bool_.N) + , fxt.Make_skin("monobook" , Bool_.N, "MonoBook" , Bool_.N) + , fxt.Make_skin("fallback" , Bool_.N, "Fallback" , Bool_.Y) + ); + } + @Test public void Magicword() { + fxt.Exec_parse(String_.Concat_lines_nl_skip_last + ( "{ 'magicwords':" + , " [ " + , " { 'name': 'expr'" + , " , 'aliases': " + , " [ 'expr'" + , " ]" + , " }" + , " , " + , " { 'name': 'currentmonth'" + , " , 'aliases': " + , " [ 'CURRENTMONTH'" + , " , 'CURRENTMONTH2'" + , " ]" + , " , 'case-sensitive': ''" + , " }" + , " ]" + , "}" + )); + fxt.Test_magicword + ( fxt.Make_magicword("expr" , Bool_.N, "expr") + , fxt.Make_magicword("currentmonth" , Bool_.Y, "CURRENTMONTH", "CURRENTMONTH2") + ); + } + @Test public void Functionhook() { + fxt.Exec_parse(String_.Concat_lines_nl_skip_last + ( "{ 'functionhooks':" + , " [ 'ns'" + , " , 'nse'" + , " ]" + , "}" + )); + fxt.Test_functionhook("ns", "nse"); + } + @Test public void Showhook() { + fxt.Exec_parse(String_.Concat_lines_nl_skip_last + ( "{ 'showhooks':" + , " [ " + , " { 'name': 'APIAfterExecute'" + , " , 'subscribers': " + , " [ 'ApiParseExtender::onAPIAfterExecute'" + , " , 'ZeroBanner\\\\MccMncLogging::onAPIAfterExecute'" + , " , 'XAnalytics::onAPIAfterExecute'" + , " ]" + , " }" + , " , " + , " { 'name': 'ParserLimitReport'" + , " , 'subscribers': " + , " { 'scribunto': 'ScribuntoHooks::reportLimits'" + , " }" + , " }" + , " ]" + , "}" + )); + fxt.Test_showhook + ( fxt.Make_showhook("APIAfterExecute" , "", "ApiParseExtender::onAPIAfterExecute", "ZeroBanner\\MccMncLogging::onAPIAfterExecute", "XAnalytics::onAPIAfterExecute") + , fxt.Make_showhook("ParserLimitReport" , "ScribuntoHooks::reportLimits") + ); + } + @Test public void Extensiontag() { + fxt.Exec_parse(String_.Concat_lines_nl_skip_last + ( "{ 'extensiontags':" + , " [ '
    '"
    +		, "  , ''"
    +		, "  ]"
    +		, "}"
    +		));
    +		fxt.Test_extensiontag("
    ", "");
    +	}
    +	@Test  public void Protocol() {
    +		fxt.Exec_parse(String_.Concat_lines_nl_skip_last
    +		( "{ 'protocols':"
    +		, "  [ 'bitcoin:'"
    +		, "  , 'ftp://'"
    +		, "  ]"
    +		, "}"
    +		));
    +		fxt.Test_protocol("bitcoin:", "ftp://");
    +	}
    +	@Test  public void Defaultoption() {
    +		fxt.Exec_parse(String_.Concat_lines_nl_skip_last
    +		( "{ 'defaultoptions':"
    +		, "  { 'globaluserpage': true"
    +		, "  , 'cols': 80"
    +		, "  , 'echo-email-format': 'html'"
    +		, "  }"
    +		, "}"
    +		));
    +		fxt.Test_defaultoption(Keyval_.new_("globaluserpage", "true"), Keyval_.new_("cols", "80"), Keyval_.new_("echo-email-format", "html"));
    +	}
    +	@Test  public void Language() {
    +		fxt.Exec_parse(String_.Concat_lines_nl_skip_last
    +		( "{ 'languages':"
    +		, "  [ "
    +		, "    { 'code': 'aa'"
    +		, "    , '*': 'Qaf\u00e1r af'"
    +		, "    }"
    +		, "  , "
    +		, "    { 'code': 'ab'"
    +		, "    , '*': '\u0410\u04a7\u0441\u0448\u04d9\u0430'"
    +		, "    }"
    +		, "  ]"
    +		, "}"
    +		));
    +		fxt.Test_language
    +		( fxt.Make_language("aa"	, "Qafár af")
    +		, fxt.Make_language("ab"	, "Аҧсшәа")
    +		);
    +	}
    +//		@Test   public void Smoke() {
    +//			Io_url json_url = Tfds.RscDir.GenSubFil_nest("400_xowa", "site_meta__en.wikipedia.org.json");
    +//			byte[] src = Io_mgr.Instance.LoadFilBry(json_url);
    +//			Site_json_parser parser = new Site_json_parser();
    +//			Site_meta_itm meta_itm = new Site_meta_itm();
    +//			parser.Parse_root(meta_itm, "en.wikipedia.org", src);
    +//			gplx.dbs.Db_conn_bldr.Instance.Reg_default_sqlite();
    +//			Site_core_db core_db = new Site_core_db(Tfds.RscDir.GenSubFil_nest("400_xowa", "site_meta.sqlite3"));
    +//			core_db.Save(meta_itm, Bry_.new_a7("en.w"));
    +//		}
    +}
    +class Site_json_parser_fxt {
    +	private final    Json_parser json_parser = new Json_parser();
    +	private final    Site_json_parser site_meta_parser;
    +	private Site_meta_itm site_meta;
    +	public Site_json_parser_fxt() {
    +		this.site_meta_parser = new Site_json_parser(json_parser);
    +	}
    +	public void Exec_parse(String raw) {
    +		Json_doc jdoc = json_parser.Parse_by_apos(raw);
    +		site_meta = new Site_meta_itm();
    +		site_meta_parser.Parse_root(site_meta, "en.wikipedia.org", jdoc.Root_nde());
    +	}
    +	public Site_namespace_itm Make_namespace(int id, boolean case_tid_is_cs, String canonical, String localized, boolean subpages, boolean content, String defaultcontentmodel) {
    +		return new Site_namespace_itm(id, case_tid_is_cs ? Xow_ns_case_.Bry__all : Xow_ns_case_.Bry__1st, Bry_.new_u8_safe(canonical), Bry_.new_u8_safe(localized), subpages, content, Bry_.new_u8_safe(defaultcontentmodel));
    +	}
    +	public Site_statistic_itm Make_statistic(long pages, long articles, long edits, long images, long users, long activeusers, long admins, long jobs, long queued_massmessages) {return new Site_statistic_itm().Ctor(pages, articles, edits, images, users, activeusers, admins, jobs, queued_massmessages);}
    +	public Site_interwikimap_itm Make_interwikimap(String prefix, boolean local, boolean extralanglink, String linktext, String sitename, String language, boolean localinterwiki, String url, boolean protorel) {
    +		return new Site_interwikimap_itm(Bry_.new_u8_safe(prefix), local, extralanglink, Bry_.new_u8_safe(linktext), Bry_.new_u8_safe(sitename), Bry_.new_u8_safe(language), localinterwiki, Bry_.new_u8_safe(url), protorel);
    +	}
    +	public Site_namespacealias_itm Make_namespacealias(int id, String alias) {return new Site_namespacealias_itm(id, Bry_.new_u8_safe(alias));}
    +	public Site_specialpagealias_itm Make_specialpagealias(String realname, String... aliases) {return new Site_specialpagealias_itm(Bry_.new_u8_safe(realname), Bry_.Ary(aliases));}
    +	public Site_library_itm Make_library(String name, String version) {return new Site_library_itm(Bry_.new_u8_safe(name), Bry_.new_u8_safe(version));}
    +	public Site_extension_itm Make_extension
    +		( String type, String name, String namemsg, String description, String descriptionmsg, String author, String url, String version
    +		, String vcs_system, String vcs_version, String vcs_url, String vcs_date, String license_name, String license, String credits) {
    +		return new Site_extension_itm
    +		( Bry_.new_u8_safe(type), Bry_.new_u8_safe(name), Bry_.new_u8_safe(namemsg), Bry_.new_u8_safe(description), Bry_.new_u8_safe(descriptionmsg), Bry_.new_u8_safe(author), Bry_.new_u8_safe(url), Bry_.new_u8_safe(version)
    +		, Bry_.new_u8_safe(vcs_system), Bry_.new_u8_safe(vcs_version), Bry_.new_u8_safe(vcs_url), Bry_.new_u8_safe(vcs_date), Bry_.new_u8_safe(license_name), Bry_.new_u8_safe(license), Bry_.new_u8_safe(credits)
    +		);
    +	}
    +	public Site_skin_itm Make_skin(String code, boolean dflt, String name, boolean unusable) {return new Site_skin_itm(Bry_.new_u8_safe(code), dflt, Bry_.new_u8_safe(name), unusable);}
    +	public Site_magicword_itm Make_magicword(String name, boolean case_match, String... aliases) {return new Site_magicword_itm(Bry_.new_u8_safe(name), case_match, Bry_.Ary(aliases));}
    +	public Site_showhook_itm Make_showhook(String name, String scribunto, String... subscribers) {return new Site_showhook_itm(Bry_.new_u8_safe(name), Bry_.new_u8_safe(scribunto), Bry_.Ary(subscribers));}
    +	public Site_language_itm Make_language(String code, String name) {return new Site_language_itm(Bry_.new_u8_safe(code), Bry_.new_u8_safe(name));}
    +	public void Test_general(Keyval... expd) {Tfds.Eq_ary_str(expd, (Keyval[])site_meta.General_list().To_ary(Keyval.class));}
    +	public void Test_namespace(Site_namespace_itm... expd) {Tfds.Eq_ary_str(expd, (Site_namespace_itm[])site_meta.Namespace_list().To_ary(Site_namespace_itm.class));}
    +	public void Test_statistic(Site_statistic_itm expd) {Tfds.Eq_str_intf(expd, site_meta.Statistic_itm());}
    +	public void Test_interwikimap(Site_interwikimap_itm... expd) {Tfds.Eq_ary_str(expd, (Site_interwikimap_itm[])site_meta.Interwikimap_list().To_ary(Site_interwikimap_itm.class));}
    +	public void Test_namespacealias(Site_namespacealias_itm... expd) {Tfds.Eq_ary_str(expd, (Site_namespacealias_itm[])site_meta.Namespacealias_list().To_ary(Site_namespacealias_itm.class));}
    +	public void Test_specialpagealias(Site_specialpagealias_itm... expd) {Tfds.Eq_ary_str(expd, (Site_specialpagealias_itm[])site_meta.Specialpagealias_list().To_ary(Site_specialpagealias_itm.class));}
    +	public void Test_library(Site_library_itm... expd) {Tfds.Eq_ary_str(expd, (Site_library_itm[])site_meta.Library_list().To_ary(Site_library_itm.class));}
    +	public void Test_extension(Site_extension_itm... expd) {Tfds.Eq_ary_str(expd, (Site_extension_itm[])site_meta.Extension_list().To_ary(Site_extension_itm.class));}
    +	public void Test_skin(Site_skin_itm... expd) {Tfds.Eq_ary_str(expd, (Site_skin_itm[])site_meta.Skin_list().To_ary(Site_skin_itm.class));}
    +	public void Test_magicword(Site_magicword_itm... expd) {Tfds.Eq_ary_str(expd, (Site_magicword_itm[])site_meta.Magicword_list().To_ary(Site_magicword_itm.class));}
    +	public void Test_functionhook(String... expd) {Tfds.Eq_ary_str(expd, String_.Ary((byte[][])site_meta.Functionhook_list().To_ary(byte[].class)));}
    +	public void Test_showhook(Site_showhook_itm... expd) {Tfds.Eq_ary_str(expd, (Site_showhook_itm[])site_meta.Showhook_list().To_ary(Site_showhook_itm.class));}
    +	public void Test_extensiontag(String... expd) {Tfds.Eq_ary_str(expd, String_.Ary((byte[][])site_meta.Extensiontag_list().To_ary(byte[].class)));}
    +	public void Test_protocol(String... expd) {Tfds.Eq_ary_str(expd, String_.Ary((byte[][])site_meta.Protocol_list().To_ary(byte[].class)));}
    +	public void Test_defaultoption(Keyval... expd) {Tfds.Eq_ary_str(expd, (Keyval[])site_meta.Defaultoption_list().To_ary(Keyval.class));}
    +	public void Test_language(Site_language_itm... expd) {Tfds.Eq_ary_str(expd, (Site_language_itm[])site_meta.Language_list().To_ary(Site_language_itm.class));}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_kv_itm.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_kv_itm.java
    index a27517de8..336be549f 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_kv_itm.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_kv_itm.java
    @@ -13,3 +13,9 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +class Site_kv_itm {
    +	public Site_kv_itm(byte[] key, byte[] val) {this.key = key; this.val = val;}
    +	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/bldrs/wms/sites/Site_kv_tbl.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_kv_tbl.java
    index a27517de8..5e33c1a4e 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_kv_tbl.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_kv_tbl.java
    @@ -13,3 +13,56 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +import gplx.dbs.*;
    +class Site_kv_tbl implements Db_tbl {
    +	private final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	private final    String fld_site_abrv, fld_key, fld_val;
    +	private final    Db_conn conn;
    +	private Db_stmt stmt_select, stmt_insert, stmt_delete;
    +	public Site_kv_tbl(Db_conn conn, String tbl_name) {
    +		this.conn = conn; this.tbl_name = tbl_name;
    +		this.fld_site_abrv				= flds.Add_str("site_abrv", 255);
    +		this.fld_key					= flds.Add_str("key", 255);
    +		this.fld_val					= flds.Add_str("val", 255);
    +		conn.Rls_reg(this);
    +	}
    +	public String Tbl_name() {return tbl_name;} private final    String tbl_name;
    +	public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_unique_by_name(tbl_name, Dbmeta_idx_itm.Bld_idx_name(tbl_name, "main"), fld_site_abrv, fld_key)));}
    +	public void Delete_all() {conn.Stmt_delete(tbl_name, Dbmeta_fld_itm.Str_ary_empty).Exec_delete();}
    +	public void Rls() {
    +		stmt_select = Db_stmt_.Rls(stmt_select);
    +		stmt_insert = Db_stmt_.Rls(stmt_insert);
    +		stmt_delete = Db_stmt_.Rls(stmt_delete);
    +	}
    +	public void Select(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_site_abrv);
    +		list.Clear();
    +		Db_rdr rdr = stmt_select.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_select__rls_auto();
    +		try {
    +			while (rdr.Move_next()) {
    +				String key = rdr.Read_str(fld_key);
    +				String val = rdr.Read_str(fld_val);
    +				list.Add(key, Keyval_.new_(key, val));
    +			}
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	public void Insert(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_delete == null) stmt_delete = conn.Stmt_delete(tbl_name, fld_site_abrv);
    +		if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds);
    +		stmt_delete.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_delete();
    +		int len = list.Count();
    +		for (int i = 0; i < len; ++i) {
    +			Keyval itm = (Keyval)list.Get_at(i);
    +			Insert(site_abrv, itm.Key(), itm.Val_to_str_or_empty());
    +		}
    +	}
    +	private void Insert(byte[] site_abrv, String key, String val) {
    +		stmt_insert.Clear()
    +			.Val_bry_as_str(fld_site_abrv	, site_abrv)
    +			.Val_str(fld_key				, key)
    +			.Val_str(fld_val				, val)
    +			.Exec_insert();
    +	}		
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_language_itm.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_language_itm.java
    index a27517de8..c218ba0ac 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_language_itm.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_language_itm.java
    @@ -13,3 +13,10 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +class Site_language_itm implements To_str_able {
    +	public Site_language_itm(byte[] code, byte[] name) {this.code = code; this.name = name;}
    +	public byte[] Code() {return code;} private final byte[] code;
    +	public byte[] Name() {return name;} private final byte[] name;
    +	public String To_str() {return String_.Concat_with_obj("|", code, name);}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_language_tbl.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_language_tbl.java
    index a27517de8..703aeaeaa 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_language_tbl.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_language_tbl.java
    @@ -13,3 +13,58 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +import gplx.dbs.*;
    +class Site_language_tbl implements Db_tbl {
    +	private final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	private final    String fld_site_abrv, fld_code, fld_name;
    +	private final    Db_conn conn;
    +	private Db_stmt stmt_select, stmt_insert, stmt_delete;
    +	public Site_language_tbl(Db_conn conn) {
    +		this.conn = conn;
    +		this.fld_site_abrv				= flds.Add_str("site_abrv", 255);
    +		this.fld_code					= flds.Add_str("code", 255);
    +		this.fld_name					= flds.Add_str("name", 255);
    +		conn.Rls_reg(this);
    +	}
    +	public String Tbl_name() {return tbl_name;} private static final String tbl_name = "site_language";
    +	public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_unique_by_name(tbl_name, Dbmeta_idx_itm.Bld_idx_name(tbl_name, "main"), fld_site_abrv, fld_code)));}
    +	public void Delete_all() {conn.Stmt_delete(tbl_name, Dbmeta_fld_itm.Str_ary_empty).Exec_delete();}
    +	public void Rls() {
    +		stmt_select = Db_stmt_.Rls(stmt_select);
    +		stmt_insert = Db_stmt_.Rls(stmt_insert);
    +		stmt_delete = Db_stmt_.Rls(stmt_delete);
    +	}
    +	public void Select(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_site_abrv);
    +		list.Clear();
    +		Db_rdr rdr = stmt_select.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_select__rls_auto();
    +		try {
    +			while (rdr.Move_next()) {
    +				Site_language_itm itm = new Site_language_itm
    +				( rdr.Read_bry_by_str(fld_code)
    +				, rdr.Read_bry_by_str(fld_name)
    +				);
    +				list.Add(itm.Code(), itm);
    +			}
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	public void Insert(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_delete == null) stmt_delete = conn.Stmt_delete(tbl_name, fld_site_abrv);
    +		if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds);
    +		stmt_delete.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_delete();
    +		int len = list.Count();
    +		for (int i = 0; i < len; ++i) {
    +			Site_language_itm itm = (Site_language_itm)list.Get_at(i);
    +			Insert(site_abrv, itm.Code(), itm.Name());
    +		}
    +	}
    +	private void Insert(byte[] site_abrv, byte[] code, byte[] name) {
    +		stmt_insert.Clear()
    +			.Val_bry_as_str(fld_site_abrv		, site_abrv)
    +			.Val_bry_as_str(fld_code			, code)
    +			.Val_bry_as_str(fld_name			, name)
    +			.Exec_insert();
    +	}		
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_library_itm.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_library_itm.java
    index a27517de8..cae7856ad 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_library_itm.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_library_itm.java
    @@ -13,3 +13,10 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +class Site_library_itm implements To_str_able {
    +	public Site_library_itm(byte[] name, byte[] version) {this.name = name; this.version = version;}
    +	public byte[] Name() {return name;} private final byte[] name;
    +	public byte[] Version() {return version;} private final byte[] version;
    +	public String To_str() {return String_.Concat_with_obj("|", name, version);}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_library_tbl.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_library_tbl.java
    index a27517de8..fe9e54c71 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_library_tbl.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_library_tbl.java
    @@ -13,3 +13,58 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +import gplx.dbs.*;
    +class Site_library_tbl implements Db_tbl {
    +	private final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	private final    String fld_site_abrv, fld_name, fld_version;
    +	private final    Db_conn conn;
    +	private Db_stmt stmt_select, stmt_insert, stmt_delete;
    +	public Site_library_tbl(Db_conn conn) {
    +		this.conn = conn;
    +		this.fld_site_abrv				= flds.Add_str("site_abrv", 255);
    +		this.fld_name					= flds.Add_str("name", 255);
    +		this.fld_version				= flds.Add_str("version", 255);
    +		conn.Rls_reg(this);
    +	}
    +	public String Tbl_name() {return tbl_name;} private static final String tbl_name = "site_library";
    +	public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_unique_by_name(tbl_name, Dbmeta_idx_itm.Bld_idx_name(tbl_name, "main"), fld_site_abrv, fld_name)));}
    +	public void Delete_all() {conn.Stmt_delete(tbl_name, Dbmeta_fld_itm.Str_ary_empty).Exec_delete();}
    +	public void Rls() {
    +		stmt_select = Db_stmt_.Rls(stmt_select);
    +		stmt_insert = Db_stmt_.Rls(stmt_insert);
    +		stmt_delete = Db_stmt_.Rls(stmt_delete);
    +	}
    +	public void Select(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_site_abrv);
    +		list.Clear();
    +		Db_rdr rdr = stmt_select.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_select__rls_auto();
    +		try {
    +			while (rdr.Move_next()) {
    +				Site_library_itm itm = new Site_library_itm
    +				( rdr.Read_bry_by_str(fld_name)
    +				, rdr.Read_bry_by_str(fld_version)
    +				);
    +				list.Add(itm.Name(), itm);
    +			}
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	public void Insert(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_delete == null) stmt_delete = conn.Stmt_delete(tbl_name, fld_site_abrv);
    +		if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds);
    +		stmt_delete.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_delete();
    +		int len = list.Count();
    +		for (int i = 0; i < len; ++i) {
    +			Site_library_itm itm = (Site_library_itm)list.Get_at(i);
    +			Insert(site_abrv, itm.Name(), itm.Version());
    +		}
    +	}
    +	private void Insert(byte[] site_abrv, byte[] name, byte[] version) {
    +		stmt_insert.Clear()
    +			.Val_bry_as_str(fld_site_abrv		, site_abrv)
    +			.Val_bry_as_str(fld_name			, name)
    +			.Val_bry_as_str(fld_version			, version)
    +			.Exec_insert();
    +	}		
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_magicword_itm.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_magicword_itm.java
    index a27517de8..5af1058c3 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_magicword_itm.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_magicword_itm.java
    @@ -13,3 +13,13 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +class Site_magicword_itm implements To_str_able {
    +	public Site_magicword_itm(byte[] name, boolean case_match, byte[][] aliases) {
    +		this.name = name; this.case_match = case_match; this.aliases = aliases;
    +	}
    +	public byte[] Name() {return name;} private final byte[] name;
    +	public boolean Case_match() {return case_match;} private final boolean case_match;
    +	public byte[][] Aliases() {return aliases;} private final byte[][] aliases;
    +	public String To_str() {return String_.Concat_with_obj("|", case_match, name, String_.Concat_with_obj(";", (Object[])aliases));}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_magicword_tbl.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_magicword_tbl.java
    index a27517de8..c55817463 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_magicword_tbl.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_magicword_tbl.java
    @@ -13,3 +13,61 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +import gplx.dbs.*;
    +class Site_magicword_tbl implements Db_tbl {
    +	private final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	private final    String fld_site_abrv, fld_name, fld_case_match, fld_aliases;
    +	private final    Db_conn conn;
    +	private Db_stmt stmt_select, stmt_insert, stmt_delete;
    +	public Site_magicword_tbl(Db_conn conn) {
    +		this.conn = conn;
    +		this.fld_site_abrv				= flds.Add_str("site_abrv", 255);
    +		this.fld_name					= flds.Add_str("name", 255);
    +		this.fld_case_match				= flds.Add_bool("case_match");
    +		this.fld_aliases				= flds.Add_str("aliases", 2048);
    +		conn.Rls_reg(this);
    +	}
    +	public String Tbl_name() {return tbl_name;} private static final String tbl_name = "site_magicword";
    +	public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_unique_by_name(tbl_name, Dbmeta_idx_itm.Bld_idx_name(tbl_name, "main"), fld_site_abrv, fld_name)));}
    +	public void Delete_all() {conn.Stmt_delete(tbl_name, Dbmeta_fld_itm.Str_ary_empty).Exec_delete();}
    +	public void Rls() {
    +		stmt_select = Db_stmt_.Rls(stmt_select);
    +		stmt_insert = Db_stmt_.Rls(stmt_insert);
    +		stmt_delete = Db_stmt_.Rls(stmt_delete);
    +	}
    +	public void Select(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_site_abrv);
    +		list.Clear();
    +		Db_rdr rdr = stmt_select.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_select__rls_auto();
    +		try {
    +			while (rdr.Move_next()) {
    +				Site_magicword_itm itm = new Site_magicword_itm
    +				( rdr.Read_bry_by_str(fld_name)
    +				, rdr.Read_bool_by_byte(fld_case_match)
    +				, Bry_split_.Split(rdr.Read_bry_by_str(fld_aliases), Byte_ascii.Pipe_bry)
    +				);
    +				list.Add(itm.Name(), itm);
    +			}
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	public void Insert(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_delete == null) stmt_delete = conn.Stmt_delete(tbl_name, fld_site_abrv);
    +		if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds);
    +		stmt_delete.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_delete();
    +		int len = list.Count();
    +		for (int i = 0; i < len; ++i) {
    +			Site_magicword_itm itm = (Site_magicword_itm)list.Get_at(i);
    +			Insert(site_abrv, itm.Name(), itm.Case_match(), itm.Aliases());
    +		}
    +	}
    +	private void Insert(byte[] site_abrv, byte[] name, boolean case_match, byte[][] aliases) {
    +		stmt_insert.Clear()
    +			.Val_bry_as_str(fld_site_abrv		, site_abrv)
    +			.Val_bry_as_str(fld_name			, name)
    +			.Val_bool_as_byte(fld_case_match	, case_match)
    +			.Val_bry_as_str(fld_aliases			, Bry_.Add_w_dlm(Byte_ascii.Pipe, aliases))
    +			.Exec_insert();
    +	}		
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_meta_itm.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_meta_itm.java
    index a27517de8..997d47c63 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_meta_itm.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_meta_itm.java
    @@ -13,3 +13,41 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +public class Site_meta_itm {
    +	public Ordered_hash			General_list() {return general_list;} private final    Ordered_hash general_list = Ordered_hash_.New_bry();
    +	public Ordered_hash			Namespace_list() {return namespace_list;} private final    Ordered_hash namespace_list = Ordered_hash_.New();
    +	public Site_statistic_itm	Statistic_itm() {return statistic_itm;} private final    Site_statistic_itm statistic_itm = new Site_statistic_itm();
    +	public Ordered_hash			Interwikimap_list() {return interwikimap_list;} private final    Ordered_hash interwikimap_list = Ordered_hash_.New_bry();
    +	public List_adp				Namespacealias_list() {return namespacealias_list;} private final    List_adp namespacealias_list = List_adp_.New();
    +	public Ordered_hash			Specialpagealias_list() {return specialpagealias_list;} private final    Ordered_hash specialpagealias_list = Ordered_hash_.New_bry();
    +	public Ordered_hash			Library_list() {return library_list;} private final    Ordered_hash library_list = Ordered_hash_.New_bry();
    +	public Ordered_hash			Extension_list() {return extension_list;} private final    Ordered_hash extension_list = Ordered_hash_.New_bry();
    +	public Ordered_hash			Skin_list() {return skin_list;} private final    Ordered_hash skin_list = Ordered_hash_.New_bry();
    +	public Ordered_hash			Magicword_list() {return magicword_list;} private final    Ordered_hash magicword_list = Ordered_hash_.New_bry();
    +	public Ordered_hash			Functionhook_list() {return functionhook_list;} private final    Ordered_hash functionhook_list = Ordered_hash_.New_bry();
    +	public Ordered_hash			Showhook_list() {return showhook_list;} private final    Ordered_hash showhook_list = Ordered_hash_.New_bry();
    +	public Ordered_hash			Extensiontag_list() {return extensiontag_list;} private final    Ordered_hash extensiontag_list = Ordered_hash_.New_bry();
    +	public Ordered_hash			Protocol_list() {return protocol_list;} private final    Ordered_hash protocol_list = Ordered_hash_.New_bry();
    +	public Ordered_hash			Defaultoption_list() {return defaultoption_list;} private final    Ordered_hash defaultoption_list = Ordered_hash_.New();
    +	public Ordered_hash			Language_list() {return language_list;} private final    Ordered_hash language_list = Ordered_hash_.New_bry();
    +	public Site_meta_itm Clear() {
    +		general_list.Clear();
    +		namespace_list.Clear();
    +		statistic_itm.Ctor(0, 0, 0, 0, 0, 0, 0, 0, 0);
    +		interwikimap_list.Clear();
    +		namespacealias_list.Clear();
    +		specialpagealias_list.Clear();
    +		library_list.Clear();
    +		extension_list.Clear();
    +		skin_list.Clear();
    +		magicword_list.Clear();
    +		functionhook_list.Clear();
    +		showhook_list.Clear();
    +		extensiontag_list.Clear();
    +		protocol_list.Clear();
    +		defaultoption_list.Clear();
    +		language_list.Clear();
    +		return this;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_meta_parser__general.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_meta_parser__general.java
    index a27517de8..74b09395e 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_meta_parser__general.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_meta_parser__general.java
    @@ -13,3 +13,253 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +import gplx.core.primitives.*; import gplx.langs.jsons.*;
    +class Site_meta_parser__general extends Json_parser__list_nde__base {
    +	private final    Site_meta_parser__general__kv parser__image = new Site_meta_parser__general__kv("imagelimits", "width", "height");
    +	private final    Site_meta_parser__general__lone parser__fallback = new Site_meta_parser__general__lone("fallback", "code");
    +	private final    Site_meta_parser__general__kv parser__variants = new Site_meta_parser__general__kv("variants", "code", "name");
    +	private String cur_context;
    +	public void Parse(String context, Ordered_hash list, Json_nde nde) {
    +		this.cur_context = context + ".general";
    +		this.Parse_to_list_as_kv(cur_context, nde, list);
    +	}
    +	@Override protected byte[] Parse_to_list_as_kv__get_val(Json_kv sub, byte[] key) {
    +		Object o = complex_props.Get_by_bry(key);
    +		if (o == null) return sub.Val_as_bry();
    +		switch (((Int_obj_val)o).Val()) {
    +			case Tid__thumblimits:	return Bry_.Add_w_dlm(Byte_ascii.Pipe, sub.Val_as_ary().Xto_bry_ary());	// [120, 150, 180] -> "120|150|180"
    +			case Tid__fallback:		return parser__fallback.Parse(cur_context, tmp_bfr, sub.Val_as_ary());	// [{'code':'zh'},{'code':'zh-hans'}] -> "zh|zh-hans"
    +			case Tid__variants:		return parser__variants.Parse(cur_context, tmp_bfr, sub.Val_as_ary());	// [{'code':'zh','name':'a'},{'code':'zh-hans','name':'b'}] -> "zh=a|zh-hans=b"
    +			case Tid__imagelimits:	return parser__image.Parse(cur_context, tmp_bfr, sub.Val_as_ary());		// [{'width':320,'height':240},{'width':640,'height':480}] -> '320=240|640=480'
    +			default: throw Err_.new_unhandled(o);
    +		}
    +	}
    +	private static final int Tid__fallback = 1, Tid__variants = 2, Tid__thumblimits = 3, Tid__imagelimits = 4, Tid__imagelimits__width = 5, Tid__imagelimits__height = 6;
    +	private static final    Hash_adp_bry complex_props = Hash_adp_bry.cs()
    +	.Add_str_int("fallback"		,  Tid__fallback)
    +	.Add_str_int("variants"		,  Tid__variants)
    +	.Add_str_int("thumblimits"	,  Tid__thumblimits)
    +	.Add_str_int("imagelimits"	,  Tid__imagelimits)
    +	.Add_str_int("width"		,  Tid__imagelimits__width)
    +	.Add_str_int("height"		,  Tid__imagelimits__height)
    +	;
    +}
    +class Site_meta_parser__general__lone extends Json_parser__list_nde__base {
    +	private Bry_bfr bfr; private String context_name;
    +	public Site_meta_parser__general__lone(String context_name, String key) {
    +		this.context_name = context_name;
    +		this.Ctor(key);
    +	}
    +	public byte[] Parse(String context, Bry_bfr bfr, Json_ary ary) {
    +		if (ary.Len() == 0) return Bry_.Empty;	// no fallbacks
    +		this.bfr = bfr;
    +		this.Parse_grp(context + "." + context_name, ary);
    +		bfr.Del_by_1();	// delete trailing dlm at end of fallbacks
    +		return bfr.To_bry_and_clear();
    +	}
    +	@Override protected void Parse_hook_nde(Json_nde sub, Json_kv[] atrs) {
    +		bfr.Add(Kv__bry(atrs, 0)).Add_byte_pipe();
    +	}
    +}
    +class Site_meta_parser__general__kv extends Json_parser__list_nde__base {
    +	private Bry_bfr bfr; private String context_name;
    +	public Site_meta_parser__general__kv(String context_name, String key, String val) {
    +		this.context_name = context_name;
    +		this.Ctor(key, val);
    +	}
    +	public byte[] Parse(String context, Bry_bfr bfr, Json_ary ary) {
    +		this.bfr = bfr;
    +		this.Parse_grp(context + "." + context_name, ary);
    +		bfr.Del_by_1();
    +		return bfr.To_bry_and_clear();
    +	}
    +	@Override protected void Parse_hook_nde(Json_nde sub, Json_kv[] atrs) {
    +		bfr.Add(Kv__bry(atrs, 0)).Add_byte_eq().Add(Kv__bry(atrs, 1)).Add_byte_pipe();
    +	}
    +}
    +class Site_meta_parser__namespace extends Json_parser__list_nde__base {
    +	private Ordered_hash list;
    +	public Site_meta_parser__namespace() {
    +		this.Ctor("id", "canonical", "ca"+"se", "*", "subpages", "content", "defaultcontentmodel");
    +	}
    +	public void Parse(String context, Ordered_hash list, Json_nde nde) {
    +		this.list = list;
    +		this.Parse_grp(context + ".namespace", nde);
    +	}
    +	@Override protected void Parse_hook_nde(Json_nde sub, Json_kv[] atrs) {
    +		int id = Kv__int(atrs, 0);
    +		list.Add(id, new Site_namespace_itm(id, Kv__bry(atrs, 2), Kv__bry_or_empty(atrs, 1), Kv__bry(atrs, 3), Kv__mw_bool(atrs, 4), Kv__mw_bool(atrs, 5), Kv__bry_or_empty(atrs, 6)));
    +	}
    +}
    +class Site_meta_parser__statistic extends Json_parser__list_nde__base {
    +	private Site_statistic_itm itm;
    +	public Site_meta_parser__statistic() {
    +		this.Ctor("pages", "articles", "edits", "images", "users", "activeusers", "admins", "jobs", "dispatch", "queued-massmessages");
    +	}
    +	public void Parse(String context, Site_statistic_itm itm, Json_nde nde) {
    +		this.itm = itm;
    +		this.Parse_nde(context + ".statistic", nde);
    +	}
    +	@Override protected void Parse_hook_nde(Json_nde sub, Json_kv[] atrs) {
    +		itm.Ctor
    +		( Kv__long(atrs, 0)
    +		, Kv__long(atrs, 1)
    +		, Kv__long(atrs, 2)
    +		, Kv__long(atrs, 3)
    +		, Kv__long(atrs, 4)
    +		, Kv__long(atrs, 5)
    +		, Kv__long(atrs, 6)
    +		, Kv__long(atrs, 7)
    +		, Kv__long_or_0(atrs, 9)
    +		);
    +	}
    +}
    +class Site_meta_parser__interwikimap extends Json_parser__list_nde__base {
    +	private Ordered_hash list;
    +	public Site_meta_parser__interwikimap() {
    +		this.Ctor("prefix", "local", "extralanglink", "linktext", "sitename", "language", "localinterwiki", "url", "protorel");
    +	}
    +	public void Parse(String context, Ordered_hash list, Json_ary nde) {
    +		this.list = list;
    +		this.Parse_grp(context + ".interwikimap", nde);
    +	}
    +	@Override protected void Parse_hook_nde(Json_nde sub, Json_kv[] atrs) {
    +		byte[] key = Kv__bry(atrs, 0);
    +		list.Add(key
    +		, new Site_interwikimap_itm(key
    +		, Kv__mw_bool(atrs, 1), Kv__mw_bool(atrs, 2), Kv__bry_or_empty(atrs, 3), Kv__bry_or_empty(atrs, 4)
    +		, Kv__bry_or_empty(atrs, 5), Kv__mw_bool(atrs, 6), Kv__bry(atrs, 7), Kv__mw_bool(atrs, 8)));
    +	}
    +}
    +class Site_meta_parser__namespacealias extends Json_parser__list_nde__base {
    +	private List_adp list;
    +	public Site_meta_parser__namespacealias() {
    +		this.Ctor("id", "*");
    +	}
    +	public void Parse(String context, List_adp list, Json_ary nde) {
    +		this.list = list;
    +		this.Parse_grp(context + ".namespacealias", nde);
    +	}
    +	@Override protected void Parse_hook_nde(Json_nde sub, Json_kv[] atrs) {
    +		Site_namespacealias_itm itm = new Site_namespacealias_itm(Kv__int(atrs, 0), Kv__bry(atrs, 1));
    +		list.Add(itm);
    +	}
    +}
    +class Site_meta_parser__specialpagealias extends Json_parser__list_nde__base {
    +	private Ordered_hash list;
    +	public Site_meta_parser__specialpagealias() {
    +		this.Ctor("realname", "aliases");
    +	}
    +	public void Parse(String context, Ordered_hash list, Json_ary nde) {
    +		this.list = list;
    +		this.Parse_grp(context + ".specialpagealias", nde);
    +	}
    +	@Override protected void Parse_hook_nde(Json_nde sub, Json_kv[] atrs) {
    +		byte[] key = Kv__bry(atrs, 0);
    +		list.Add(key, new Site_specialpagealias_itm(key, Kv__bry_ary(atrs, 1)));
    +	}
    +}
    +class Site_meta_parser__library extends Json_parser__list_nde__base {
    +	private Ordered_hash list;
    +	public Site_meta_parser__library() {
    +		this.Ctor("name", "version");
    +	}
    +	public void Parse(String context, Ordered_hash list, Json_ary nde) {
    +		this.list = list;
    +		this.Parse_grp(context + ".library", nde);
    +	}
    +	@Override protected void Parse_hook_nde(Json_nde sub, Json_kv[] atrs) {
    +		byte[] key = Kv__bry(atrs, 0);
    +		list.Add(key, new Site_library_itm(key, Kv__bry(atrs, 1)));
    +	}
    +}
    +class Site_meta_parser__extension extends Json_parser__list_nde__base {
    +	private Ordered_hash list;
    +	public Site_meta_parser__extension() {
    +		this.Ctor("type", "name", "namemsg", "description", "descriptionmsg", "author", "url", "version", "vcs-system", "vcs-version", "vcs-url", "vcs-date", "license-name", "license", "credits");
    +	}
    +	public void Parse(String context, Ordered_hash list, Json_ary nde) {
    +		this.list = list;
    +		this.Parse_grp(context + ".extension", nde);
    +	}
    +	@Override protected void Parse_hook_nde(Json_nde sub, Json_kv[] atrs) {
    +		Site_extension_itm itm = new Site_extension_itm(Kv__bry(atrs, 0), Kv__bry(atrs, 1), Kv__bry_or_empty(atrs, 2), Kv__bry_or_empty(atrs, 3), Kv__bry_or_empty(atrs, 4)
    +		, Kv__bry_or_empty(atrs, 5), Kv__bry_or_empty(atrs, 6)
    +		, Kv__bry_or_empty(atrs, 7), Kv__bry_or_empty(atrs, 8), Kv__bry_or_empty(atrs, 9), Kv__bry_or_empty(atrs, 10), Kv__bry_or_empty(atrs, 11)
    +		, Kv__bry_or_empty(atrs, 12), Kv__bry_or_empty(atrs, 13), Kv__bry_or_empty(atrs, 14)
    +		);
    +		list.Add(itm.Key(), itm);
    +	}
    +}
    +class Site_meta_parser__skin extends Json_parser__list_nde__base {
    +	private Ordered_hash list;
    +	public Site_meta_parser__skin() {
    +		this.Ctor("code", "de"+"fault", "*", "unusable");
    +	}
    +	public void Parse(String context, Ordered_hash list, Json_ary nde) {
    +		this.list = list;
    +		this.Parse_grp(context + ".skin", nde);
    +	}
    +	@Override protected void Parse_hook_nde(Json_nde sub, Json_kv[] atrs) {
    +		byte[] key = Kv__bry(atrs, 0);
    +		list.Add(key, new Site_skin_itm(key, Kv__mw_bool(atrs, 1), Kv__bry(atrs, 2), Kv__mw_bool(atrs, 3)));
    +	}
    +}
    +class Site_meta_parser__magicword extends Json_parser__list_nde__base {
    +	private Ordered_hash list;
    +	public Site_meta_parser__magicword() {
    +		this.Ctor("name", "case-sensitive", "aliases");
    +	}
    +	public void Parse(String context, Ordered_hash list, Json_ary nde) {
    +		this.list = list;
    +		this.Parse_grp(context + ".magicword", nde);
    +	}
    +	@Override protected void Parse_hook_nde(Json_nde sub, Json_kv[] atrs) {
    +		byte[] key = Kv__bry(atrs, 0);
    +		list.Add(key, new Site_magicword_itm(key, Kv__mw_bool(atrs, 1), Kv__bry_ary(atrs, 2)));
    +	}
    +}
    +class Site_meta_parser__showhook extends Json_parser__list_nde__base {
    +	private Ordered_hash list;
    +	public Site_meta_parser__showhook() {
    +		this.Ctor("name", "subscribers");
    +	}
    +	public void Parse(String context, Ordered_hash list, Json_ary nde) {
    +		this.list = list;
    +		this.Parse_grp(context + ".showhook", nde);
    +	}
    +	@Override protected void Parse_hook_nde(Json_nde sub, Json_kv[] atrs) {
    +		byte[] key = Kv__bry(atrs, 0);
    +		Json_kv subscribers_kv = atrs[1];
    +		byte[] scribunto = Bry_.Empty;
    +		byte[][] subscribers_bry_ary = Bry_.Ary_empty;
    +		if (subscribers_kv.Val().Tid() == Json_itm_.Tid__ary)
    +			subscribers_bry_ary = Kv__bry_ary(atrs, 1);
    +		else {
    +			Json_nde subscribers_nde = subscribers_kv.Val_as_nde();
    +			int atr_len = subscribers_nde.Len();
    +			for (int j = 0; j < atr_len; ++j) {
    +				Json_kv atr = subscribers_nde.Get_at_as_kv(j);
    +				if (!Bry_.Eq(atr.Key_as_bry(), Key__scribunto)) {Warn("unknown subscriber key", atr); continue;}
    +				scribunto = atr.Val_as_bry();
    +			}
    +		}
    +		list.Add(key, new Site_showhook_itm(key, scribunto, subscribers_bry_ary));
    +	}
    +	private final    static byte[] Key__scribunto = Bry_.new_a7("scribunto");
    +}
    +class Site_meta_parser__language extends Json_parser__list_nde__base {
    +	private Ordered_hash list;
    +	public Site_meta_parser__language() {
    +		this.Ctor("code", "*");
    +	}
    +	public void Parse(String context, Ordered_hash list, Json_ary nde) {
    +		this.list = list;
    +		this.Parse_grp(context + ".language", nde);
    +	}
    +	@Override protected void Parse_hook_nde(Json_nde sub, Json_kv[] atrs) {
    +		byte[] key = Kv__bry(atrs, 0);
    +		list.Add(key, new Site_language_itm(key, Kv__bry(atrs, 1)));
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_namespace_itm.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_namespace_itm.java
    index a27517de8..74e33ff76 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_namespace_itm.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_namespace_itm.java
    @@ -13,3 +13,20 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +class Site_namespace_itm implements To_str_able {
    +	public Site_namespace_itm(int id, byte[] case_tid, byte[] canonical, byte[] localized, boolean subpages, boolean content, byte[] defaultcontentmodel) {
    +		this.id = id; this.case_tid = case_tid; this.canonical = canonical; this.localized = localized;
    +		this.subpages = subpages; this.content = content; this.defaultcontentmodel = defaultcontentmodel;
    +	}
    +	public int Id() {return id;} private final int id;
    +	public byte[] Case_tid() {return case_tid;} private final byte[] case_tid;
    +	public byte[] Canonical() {return canonical;} private final byte[] canonical;
    +	public byte[] Localized() {return localized;} private final byte[] localized;
    +	public boolean Subpages() {return subpages;} private final boolean subpages;
    +	public boolean Content() {return content;} private final boolean content;
    +	public byte[] Defaultcontentmodel() {return defaultcontentmodel;} private final byte[] defaultcontentmodel;
    +	public String To_str() {
    +		return String_.Concat_with_obj("|", id, case_tid, canonical, localized, subpages, content, defaultcontentmodel);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_namespace_tbl.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_namespace_tbl.java
    index a27517de8..53a682599 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_namespace_tbl.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_namespace_tbl.java
    @@ -13,3 +13,73 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +import gplx.dbs.*;
    +public class Site_namespace_tbl implements Db_tbl {
    +	private final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	private final    String fld_site_abrv, fld_id, fld_case_tid, fld_canonical, fld_localized, fld_subpages, fld_content, fld_defaultcontentmodel;
    +	private final    Db_conn conn;
    +	private Db_stmt stmt_select, stmt_insert, stmt_delete;
    +	public Site_namespace_tbl(Db_conn conn) {
    +		this.conn = conn;
    +		this.fld_site_abrv				= flds.Add_str("site_abrv", 255);
    +		this.fld_id						= flds.Add_int("id");
    +		this.fld_case_tid				= flds.Add_str("case_tid", 255);
    +		this.fld_canonical				= flds.Add_str("canonical", 255);
    +		this.fld_localized				= flds.Add_str("localized", 255);
    +		this.fld_subpages				= flds.Add_byte("subpages");
    +		this.fld_content				= flds.Add_byte("content");
    +		this.fld_defaultcontentmodel	= flds.Add_str("defaultcontentmodel", 255);
    +		conn.Rls_reg(this);
    +	}
    +	public String Tbl_name() {return tbl_name;} private static final String tbl_name = "site_namespace";
    +	public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_unique_by_name(tbl_name, Dbmeta_idx_itm.Bld_idx_name(tbl_name, "main"), fld_site_abrv, fld_id)));}
    +	public void Delete_all() {conn.Stmt_delete(tbl_name, Dbmeta_fld_itm.Str_ary_empty).Exec_delete();}
    +	public void Rls() {
    +		stmt_select = Db_stmt_.Rls(stmt_select);
    +		stmt_insert = Db_stmt_.Rls(stmt_insert);
    +		stmt_delete = Db_stmt_.Rls(stmt_delete);
    +	}
    +	public void Select(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_site_abrv);
    +		list.Clear();
    +		Db_rdr rdr = stmt_select.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_select__rls_manual();
    +		try {
    +			while (rdr.Move_next()) {
    +				Site_namespace_itm itm = new Site_namespace_itm
    +				( rdr.Read_int			(fld_id)
    +				, rdr.Read_bry_by_str	(fld_case_tid)
    +				, rdr.Read_bry_by_str	(fld_canonical)
    +				, rdr.Read_bry_by_str	(fld_localized)
    +				, rdr.Read_bool_by_byte	(fld_subpages)
    +				, rdr.Read_bool_by_byte	(fld_content)
    +				, rdr.Read_bry_by_str	(fld_defaultcontentmodel)
    +				);
    +				list.Add(itm.Id(), itm);
    +			}
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	public void Insert(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_delete == null) stmt_delete = conn.Stmt_delete(tbl_name, fld_site_abrv);
    +		stmt_delete.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_delete();
    +		int len = list.Count();
    +		for (int i = 0; i < len; ++i) {
    +			Site_namespace_itm itm = (Site_namespace_itm)list.Get_at(i);
    +			Insert(site_abrv, itm.Id(), itm.Case_tid(), itm.Canonical(), itm.Localized(), itm.Subpages(), itm.Content(), itm.Defaultcontentmodel());
    +		}
    +	}
    +	public void Insert(byte[] site_abrv, int id, byte[] case_tid, byte[] canonical, byte[] localized, boolean subpages, boolean content, byte[] defaultcontentmodel) {
    +		if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds);
    +		stmt_insert.Clear()
    +			.Val_bry_as_str		(fld_site_abrv				, site_abrv)
    +			.Val_int			(fld_id						, id)
    +			.Val_bry_as_str		(fld_case_tid				, case_tid)
    +			.Val_bry_as_str		(fld_canonical				, canonical)
    +			.Val_bry_as_str		(fld_localized				, localized)
    +			.Val_bool_as_byte	(fld_subpages				, subpages)
    +			.Val_bool_as_byte	(fld_content				, content)
    +			.Val_bry_as_str		(fld_defaultcontentmodel	, defaultcontentmodel)
    +			.Exec_insert();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_namespacealias_itm.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_namespacealias_itm.java
    index a27517de8..850a9bddf 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_namespacealias_itm.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_namespacealias_itm.java
    @@ -13,3 +13,14 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +class Site_namespacealias_itm implements To_str_able {
    +	public Site_namespacealias_itm(int id, byte[] alias) {
    +		this.id = id; this.alias = alias;
    +		this.key = Bry_.Add_w_dlm(Byte_ascii.Pipe, Int_.To_bry(id), alias);
    +	}
    +	public byte[] Key() {return key;} private final byte[] key;
    +	public int Id() {return id;} private final int id;
    +	public byte[] Alias() {return alias;} private final byte[] alias;
    +	public String To_str() {return String_.Concat_with_obj("|", id, alias);}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_namespacealias_tbl.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_namespacealias_tbl.java
    index a27517de8..9bf05a683 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_namespacealias_tbl.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_namespacealias_tbl.java
    @@ -13,3 +13,58 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +import gplx.dbs.*;
    +class Site_namespacealias_tbl implements Db_tbl {
    +	private final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	private final    String fld_site_abrv, fld_id, fld_alias;
    +	private final    Db_conn conn;
    +	private Db_stmt stmt_select, stmt_insert, stmt_delete;
    +	public Site_namespacealias_tbl(Db_conn conn) {
    +		this.conn = conn;
    +		this.fld_site_abrv				= flds.Add_str("site_abrv", 255);
    +		this.fld_id						= flds.Add_int("id");
    +		this.fld_alias					= flds.Add_str("alias", 2048);
    +		conn.Rls_reg(this);
    +	}
    +	public String Tbl_name() {return tbl_name;} private static final String tbl_name = "site_namespacealias";
    +	public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_normal_by_name(tbl_name, Dbmeta_idx_itm.Bld_idx_name(tbl_name, "main"), fld_site_abrv, fld_id, fld_alias)));}	// NOTE: kk.w has duplicate entries in json
    +	public void Delete_all() {conn.Stmt_delete(tbl_name, Dbmeta_fld_itm.Str_ary_empty).Exec_delete();}
    +	public void Rls() {
    +		stmt_select = Db_stmt_.Rls(stmt_select);
    +		stmt_insert = Db_stmt_.Rls(stmt_insert);
    +		stmt_delete = Db_stmt_.Rls(stmt_delete);
    +	}
    +	public void Select(byte[] site_abrv, List_adp list) {
    +		if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_site_abrv);
    +		list.Clear();
    +		Db_rdr rdr = stmt_select.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_select__rls_auto();
    +		try {
    +			while (rdr.Move_next()) {
    +				Site_namespacealias_itm itm = new Site_namespacealias_itm
    +				( rdr.Read_int(fld_id)
    +				, rdr.Read_bry_by_str(fld_alias)
    +				);
    +				list.Add(itm);
    +			}
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	public void Insert(byte[] site_abrv, List_adp list) {
    +		if (stmt_delete == null) stmt_delete = conn.Stmt_delete(tbl_name, fld_site_abrv);
    +		if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds);
    +		stmt_delete.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_delete();
    +		int len = list.Count();
    +		for (int i = 0; i < len; ++i) {
    +			Site_namespacealias_itm itm = (Site_namespacealias_itm)list.Get_at(i);
    +			Insert(site_abrv, itm.Id(), itm.Alias());
    +		}
    +	}
    +	private void Insert(byte[] site_abrv, int id, byte[] alias) {
    +		stmt_insert.Clear()
    +			.Val_bry_as_str(fld_site_abrv			, site_abrv)
    +			.Val_int(fld_id							, id)
    +			.Val_bry_as_str(fld_alias				, alias)
    +			.Exec_insert();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_showhook_itm.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_showhook_itm.java
    index a27517de8..ea046830b 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_showhook_itm.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_showhook_itm.java
    @@ -13,3 +13,13 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +class Site_showhook_itm implements To_str_able {
    +	public Site_showhook_itm(byte[] name, byte[] scribunto, byte[][] subscribers) {
    +		this.name = name; this.scribunto = scribunto; this.subscribers = subscribers;
    +	}
    +	public byte[] Name() {return name;} private final byte[] name;
    +	public byte[] Scribunto() {return scribunto;} private final byte[] scribunto;
    +	public byte[][] Subscribers() {return subscribers;} private final byte[][] subscribers;
    +	public String To_str() {return String_.Concat_with_obj("|", name, scribunto, String_.Concat_with_obj(";", (Object[])subscribers));}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_showhook_tbl.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_showhook_tbl.java
    index a27517de8..e9ae3b2da 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_showhook_tbl.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_showhook_tbl.java
    @@ -13,3 +13,61 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +import gplx.dbs.*;
    +class Site_showhook_tbl implements Db_tbl {
    +	private final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	private final    String fld_site_abrv, fld_name, fld_scribunto, fld_subscribers;
    +	private final    Db_conn conn;
    +	private Db_stmt stmt_select, stmt_insert, stmt_delete;
    +	public Site_showhook_tbl(Db_conn conn) {
    +		this.conn = conn;
    +		this.fld_site_abrv				= flds.Add_str("site_abrv", 255);
    +		this.fld_name					= flds.Add_str("name", 255);
    +		this.fld_scribunto				= flds.Add_str("scribunto", 255);
    +		this.fld_subscribers			= flds.Add_str("subscribers", 2048);
    +		conn.Rls_reg(this);
    +	}
    +	public String Tbl_name() {return tbl_name;} private static final String tbl_name = "site_showhook";
    +	public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_unique_by_name(tbl_name, Dbmeta_idx_itm.Bld_idx_name(tbl_name, "main"), fld_site_abrv, fld_name)));}
    +	public void Delete_all() {conn.Stmt_delete(tbl_name, Dbmeta_fld_itm.Str_ary_empty).Exec_delete();}
    +	public void Rls() {
    +		stmt_select = Db_stmt_.Rls(stmt_select);
    +		stmt_insert = Db_stmt_.Rls(stmt_insert);
    +		stmt_delete = Db_stmt_.Rls(stmt_delete);
    +	}
    +	public void Select(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_site_abrv);
    +		list.Clear();
    +		Db_rdr rdr = stmt_select.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_select__rls_auto();
    +		try {
    +			while (rdr.Move_next()) {
    +				Site_showhook_itm itm = new Site_showhook_itm
    +				( rdr.Read_bry_by_str(fld_name)
    +				, rdr.Read_bry_by_str(fld_scribunto)
    +				, Bry_split_.Split(rdr.Read_bry_by_str(fld_subscribers), Byte_ascii.Pipe_bry)
    +				);
    +				list.Add(itm.Name(), itm);
    +			}
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	public void Insert(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_delete == null) stmt_delete = conn.Stmt_delete(tbl_name, fld_site_abrv);
    +		if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds);
    +		stmt_delete.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_delete();
    +		int len = list.Count();
    +		for (int i = 0; i < len; ++i) {
    +			Site_showhook_itm itm = (Site_showhook_itm)list.Get_at(i);
    +			Insert(site_abrv, itm.Name(), itm.Scribunto(), itm.Subscribers());
    +		}
    +	}
    +	private void Insert(byte[] site_abrv, byte[] name, byte[] scribunto, byte[][] subscribers) {
    +		stmt_insert.Clear()
    +			.Val_bry_as_str(fld_site_abrv		, site_abrv)
    +			.Val_bry_as_str(fld_name			, name)
    +			.Val_bry_as_str(fld_scribunto		, scribunto)
    +			.Val_bry_as_str(fld_subscribers		, Bry_.Add_w_dlm(Byte_ascii.Pipe, subscribers))
    +			.Exec_insert();
    +	}		
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_skin_itm.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_skin_itm.java
    index a27517de8..ed3d00a2d 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_skin_itm.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_skin_itm.java
    @@ -13,3 +13,14 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +class Site_skin_itm implements To_str_able {
    +	public Site_skin_itm(byte[] code, boolean dflt, byte[] name, boolean unusable) {
    +		this.code = code; this.dflt = dflt; this.name = name; this.unusable = unusable;
    +	}
    +	public byte[] Code() {return code;} private final byte[] code;
    +	public boolean Dflt() {return dflt;} private final boolean dflt;
    +	public byte[] Name() {return name;} private final byte[] name;
    +	public boolean Unusable() {return unusable;} private final boolean unusable;
    +	public String To_str() {return String_.Concat_with_obj("|", code, dflt, name, unusable);}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_skin_tbl.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_skin_tbl.java
    index a27517de8..a9658d45f 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_skin_tbl.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_skin_tbl.java
    @@ -13,3 +13,64 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +import gplx.dbs.*;
    +class Site_skin_tbl implements Db_tbl {
    +	private final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	private final    String fld_site_abrv, fld_code, fld_dflt, fld_name, fld_unusable;
    +	private final    Db_conn conn;
    +	private Db_stmt stmt_select, stmt_insert, stmt_delete;
    +	public Site_skin_tbl(Db_conn conn) {
    +		this.conn = conn;
    +		this.fld_site_abrv				= flds.Add_str("site_abrv", 255);
    +		this.fld_code					= flds.Add_str("code", 255);
    +		this.fld_dflt					= flds.Add_bool("dflt");
    +		this.fld_name					= flds.Add_str("name", 255);
    +		this.fld_unusable				= flds.Add_bool("unusable");
    +		conn.Rls_reg(this);
    +	}
    +	public String Tbl_name() {return tbl_name;} private static final String tbl_name = "site_skin";
    +	public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_unique_by_name(tbl_name, Dbmeta_idx_itm.Bld_idx_name(tbl_name, "main"), fld_site_abrv, fld_code)));}
    +	public void Delete_all() {conn.Stmt_delete(tbl_name, Dbmeta_fld_itm.Str_ary_empty).Exec_delete();}
    +	public void Rls() {
    +		stmt_select = Db_stmt_.Rls(stmt_select);
    +		stmt_insert = Db_stmt_.Rls(stmt_insert);
    +		stmt_delete = Db_stmt_.Rls(stmt_delete);
    +	}
    +	public void Select(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_site_abrv);
    +		list.Clear();
    +		Db_rdr rdr = stmt_select.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_select__rls_auto();
    +		try {
    +			while (rdr.Move_next()) {
    +				Site_skin_itm itm = new Site_skin_itm
    +				( rdr.Read_bry_by_str(fld_code)
    +				, rdr.Read_bool_by_byte(fld_dflt)
    +				, rdr.Read_bry_by_str(fld_name)
    +				, rdr.Read_bool_by_byte(fld_unusable)
    +				);
    +				list.Add(itm.Name(), itm);
    +			}
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	public void Insert(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_delete == null) stmt_delete = conn.Stmt_delete(tbl_name, fld_site_abrv);
    +		if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds);
    +		stmt_delete.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_delete();
    +		int len = list.Count();
    +		for (int i = 0; i < len; ++i) {
    +			Site_skin_itm itm = (Site_skin_itm)list.Get_at(i);
    +			Insert(site_abrv, itm.Code(), itm.Dflt(), itm.Name(), itm.Unusable());
    +		}
    +	}
    +	private void Insert(byte[] site_abrv, byte[] code, boolean dflt, byte[] name, boolean unusable) {
    +		stmt_insert.Clear()
    +			.Val_bry_as_str(fld_site_abrv		, site_abrv)
    +			.Val_bry_as_str(fld_code			, code)
    +			.Val_bool_as_byte(fld_dflt			, dflt)
    +			.Val_bry_as_str(fld_name			, name)
    +			.Val_bool_as_byte(fld_unusable		, unusable)
    +			.Exec_insert();
    +	}		
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_specialpagealias_itm.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_specialpagealias_itm.java
    index a27517de8..bd5ab0133 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_specialpagealias_itm.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_specialpagealias_itm.java
    @@ -13,3 +13,10 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +class Site_specialpagealias_itm implements To_str_able {
    +	public Site_specialpagealias_itm(byte[] realname, byte[][] aliases) {this.realname = realname; this.aliases = aliases;}
    +	public byte[] Realname() {return realname;} private final byte[] realname;
    +	public byte[][] Aliases() {return aliases;} private final byte[][] aliases;
    +	public String To_str() {return String_.Concat_with_obj("|", realname, String_.Concat_with_obj(";", (Object[])aliases));}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_specialpagealias_tbl.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_specialpagealias_tbl.java
    index a27517de8..5cb5600c4 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_specialpagealias_tbl.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_specialpagealias_tbl.java
    @@ -13,3 +13,58 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +import gplx.dbs.*;
    +class Site_specialpagealias_tbl implements Db_tbl {
    +	private final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	private final    String fld_site_abrv, fld_realname, fld_aliases;
    +	private final    Db_conn conn;
    +	private Db_stmt stmt_select, stmt_insert, stmt_delete;
    +	public Site_specialpagealias_tbl(Db_conn conn) {
    +		this.conn = conn;
    +		this.fld_site_abrv				= flds.Add_str("site_abrv", 255);
    +		this.fld_realname				= flds.Add_str("realname", 255);
    +		this.fld_aliases				= flds.Add_str("aliases", 2048);
    +		conn.Rls_reg(this);
    +	}
    +	public String Tbl_name() {return tbl_name;} private static final String tbl_name = "site_specialpagealias";
    +	public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_unique_by_name(tbl_name, Dbmeta_idx_itm.Bld_idx_name(tbl_name, Dbmeta_idx_itm.Bld_idx_name(tbl_name, "main")), fld_site_abrv, fld_realname)));}
    +	public void Delete_all() {conn.Stmt_delete(tbl_name, Dbmeta_fld_itm.Str_ary_empty).Exec_delete();}
    +	public void Rls() {
    +		stmt_select = Db_stmt_.Rls(stmt_select);
    +		stmt_insert = Db_stmt_.Rls(stmt_insert);
    +		stmt_delete = Db_stmt_.Rls(stmt_delete);
    +	}
    +	public void Select(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_site_abrv);
    +		list.Clear();
    +		Db_rdr rdr = stmt_select.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_select__rls_auto();
    +		try {
    +			while (rdr.Move_next()) {
    +				Site_specialpagealias_itm itm = new Site_specialpagealias_itm
    +				( rdr.Read_bry_by_str(fld_realname)
    +				, Bry_split_.Split(rdr.Read_bry_by_str(fld_aliases), Byte_ascii.Pipe_bry)
    +				);
    +				list.Add(itm.Realname(), itm);
    +			}
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	public void Insert(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_delete == null) stmt_delete = conn.Stmt_delete(tbl_name, fld_site_abrv);
    +		if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds);
    +		stmt_delete.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_delete();
    +		int len = list.Count();
    +		for (int i = 0; i < len; ++i) {
    +			Site_specialpagealias_itm itm = (Site_specialpagealias_itm)list.Get_at(i);
    +			Insert(site_abrv, itm.Realname(), itm.Aliases());
    +		}
    +	}
    +	private void Insert(byte[] site_abrv, byte[] realname, byte[][] aliases) {
    +		stmt_insert.Clear()
    +			.Val_bry_as_str(fld_site_abrv			, site_abrv)
    +			.Val_bry_as_str(fld_realname			, realname)
    +			.Val_bry_as_str(fld_aliases				, Bry_.Add_w_dlm(Byte_ascii.Pipe_bry, aliases))
    +			.Exec_insert();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_statistic_itm.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_statistic_itm.java
    index a27517de8..7b84c58b2 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_statistic_itm.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_statistic_itm.java
    @@ -13,3 +13,28 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +public class Site_statistic_itm implements To_str_able {
    +	public Site_statistic_itm Ctor(long pages, long articles, long edits, long images, long users, long activeusers, long admins, long jobs, long queued_massmessages) {
    +		this.pages = pages;
    +		this.articles = articles;
    +		this.edits = edits;
    +		this.images = images;
    +		this.users = users;
    +		this.activeusers = activeusers;
    +		this.admins = admins;
    +		this.jobs = jobs;
    +		this.queued_massmessages = queued_massmessages;
    +		return this;
    +	}
    +	public long Pages() {return pages;} private long pages;
    +	public long Articles() {return articles;} private long articles;
    +	public long Edits() {return edits;} private long edits;
    +	public long Images() {return images;} private long images;
    +	public long Users() {return users;} private long users;
    +	public long Activeusers() {return activeusers;} private long activeusers;
    +	public long Admins() {return admins;} private long admins;
    +	public long Jobs() {return jobs;} private long jobs;
    +	public long Queued_massmessages() {return queued_massmessages;} private long queued_massmessages;
    +	public String To_str() {return String_.Concat_with_obj("|", pages, articles, edits, images, users, activeusers, admins, queued_massmessages);}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_statistic_tbl.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_statistic_tbl.java
    index a27517de8..fe281b733 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_statistic_tbl.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_statistic_tbl.java
    @@ -13,3 +13,74 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +import gplx.dbs.*;
    +class Site_statistic_tbl implements Db_tbl {
    +	private final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	private final    String fld_site_abrv, fld_pages, fld_articles, fld_edits, fld_images, fld_users, fld_activeusers, fld_admins, fld_jobs, fld_queued_massmessages;
    +	private final    Db_conn conn;
    +	private Db_stmt stmt_select, stmt_insert, stmt_delete;
    +	public Site_statistic_tbl(Db_conn conn) {
    +		this.conn = conn;
    +		this.fld_site_abrv				= flds.Add_str("site_abrv", 255);
    +		this.fld_pages					= flds.Add_long("pages");
    +		this.fld_articles				= flds.Add_long("articles");
    +		this.fld_edits					= flds.Add_long("edits");
    +		this.fld_images					= flds.Add_long("images");
    +		this.fld_users					= flds.Add_long("users");
    +		this.fld_activeusers			= flds.Add_long("activeusers");
    +		this.fld_admins					= flds.Add_long("admins");
    +		this.fld_jobs					= flds.Add_long("jobs");
    +		this.fld_queued_massmessages	= flds.Add_long("queued_massmessages");
    +		conn.Rls_reg(this);
    +	}
    +	public String Tbl_name() {return tbl_name;} private static final String tbl_name = "site_statistic";
    +	public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_unique_by_name(tbl_name, Dbmeta_idx_itm.Bld_idx_name(tbl_name, "main"), fld_site_abrv)));}
    +	public void Delete_all() {conn.Stmt_delete(tbl_name, Dbmeta_fld_itm.Str_ary_empty).Exec_delete();}
    +	public void Rls() {
    +		stmt_select = Db_stmt_.Rls(stmt_select);
    +		stmt_insert = Db_stmt_.Rls(stmt_insert);
    +		stmt_delete = Db_stmt_.Rls(stmt_delete);
    +	}
    +	public void Select(byte[] site_abrv, Site_statistic_itm itm) {
    +		if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_site_abrv);
    +		Db_rdr rdr = stmt_select.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_select__rls_auto();
    +		try {
    +			if (rdr.Move_next()) {
    +				itm.Ctor
    +				( rdr.Read_long(fld_pages)
    +				, rdr.Read_long(fld_articles)
    +				, rdr.Read_long(fld_edits)
    +				, rdr.Read_long(fld_images)
    +				, rdr.Read_long(fld_users)
    +				, rdr.Read_long(fld_activeusers)
    +				, rdr.Read_long(fld_admins)
    +				, rdr.Read_long(fld_jobs)
    +				, rdr.Read_long(fld_queued_massmessages)
    +				);
    +			}
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	public void Insert(byte[] site_abrv, Site_statistic_itm itm) {
    +		if (stmt_delete == null) stmt_delete = conn.Stmt_delete(tbl_name, fld_site_abrv);
    +		if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds);
    +		stmt_delete.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_delete();
    +		Insert(site_abrv, itm.Pages(), itm.Articles(), itm.Edits(), itm.Images(), itm.Users(), itm.Activeusers(), itm.Admins(), itm.Jobs(), itm.Queued_massmessages());
    +	}
    +	private void Insert(byte[] site_abrv, long pages, long articles, long edits, long images, long users, long activeusers, long admins, long jobs, long queued_massmessages) {
    +		if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds);
    +		stmt_insert.Clear()
    +			.Val_bry_as_str(fld_site_abrv		, site_abrv)
    +			.Val_long(fld_pages					, pages)
    +			.Val_long(fld_articles				, articles)
    +			.Val_long(fld_edits					, edits)
    +			.Val_long(fld_images				, images)
    +			.Val_long(fld_users					, users)
    +			.Val_long(fld_activeusers			, activeusers)
    +			.Val_long(fld_admins				, admins)
    +			.Val_long(fld_jobs					, jobs)
    +			.Val_long(fld_queued_massmessages	, queued_massmessages)
    +			.Exec_insert();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_val_tbl.java b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_val_tbl.java
    index a27517de8..fd6a558c6 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_val_tbl.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wms/sites/Site_val_tbl.java
    @@ -13,3 +13,54 @@ 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.bldrs.wms.sites; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wms.*;
    +import gplx.dbs.*;
    +class Site_val_tbl implements Db_tbl {
    +	private final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	private final    String fld_site_abrv, fld_val;
    +	private final    Db_conn conn;
    +	private Db_stmt stmt_select, stmt_insert, stmt_delete;
    +	public Site_val_tbl(Db_conn conn, String tbl_name) {
    +		this.conn = conn;
    +		this.tbl_name = tbl_name;
    +		this.fld_site_abrv				= flds.Add_str("site_abrv", 255);
    +		this.fld_val					= flds.Add_str("val", 255);
    +		conn.Rls_reg(this);
    +	}
    +	public String Tbl_name() {return tbl_name;} private final    String tbl_name;
    +	public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_unique_by_name(tbl_name, Dbmeta_idx_itm.Bld_idx_name(tbl_name, "main"), fld_site_abrv, fld_val)));}
    +	public void Delete_all() {conn.Stmt_delete(tbl_name, Dbmeta_fld_itm.Str_ary_empty).Exec_delete();}
    +	public void Rls() {
    +		stmt_select = Db_stmt_.Rls(stmt_select);
    +		stmt_insert = Db_stmt_.Rls(stmt_insert);
    +		stmt_delete = Db_stmt_.Rls(stmt_delete);
    +	}
    +	public void Select(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_site_abrv);
    +		list.Clear();
    +		Db_rdr rdr = stmt_select.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_select__rls_manual();
    +		try {
    +			while (rdr.Move_next()) {
    +				byte[] val = rdr.Read_bry_by_str(fld_val);
    +				list.Add(val, val);
    +			}
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	public void Insert(byte[] site_abrv, Ordered_hash list) {
    +		if (stmt_delete == null) stmt_delete = conn.Stmt_delete(tbl_name, fld_site_abrv);
    +		if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds);
    +		stmt_delete.Clear().Crt_bry_as_str(fld_site_abrv, site_abrv).Exec_delete();
    +		int len = list.Count();
    +		for (int i = 0; i < len; ++i) {
    +			byte[] itm = (byte[])list.Get_at(i);
    +			Insert(site_abrv, itm);
    +		}
    +	}
    +	private void Insert(byte[] site_abrv, byte[] val) {
    +		stmt_insert.Clear()
    +			.Val_bry_as_str(fld_site_abrv		, site_abrv)
    +			.Val_bry_as_str(fld_val				, val)
    +			.Exec_insert();
    +	}		
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr.java b/400_xowa/src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr.java
    index a27517de8..e13c7298f 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr.java
    @@ -13,3 +13,27 @@ 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.bldrs.wtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import gplx.core.ios.*;
    +import gplx.xowa.wikis.nss.*;
    +public class Xob_tmp_wtr {
    +	Xob_tmp_wtr(Xow_ns ns_itm, Io_url_gen url_gen, int fil_max) {
    +		this.ns_itm = ns_itm;
    +		this.url_gen = url_gen;
    +		this.fil_max = fil_max; 
    +		bfr = Bry_bfr_.Reset(fil_max);
    +	}	int fil_max;
    +	public Bry_bfr Bfr() {return bfr;} Bry_bfr bfr;
    +	public Io_url_gen Url_gen() {return url_gen;} Io_url_gen url_gen;
    +	public void Clear() {bfr.ClearAndReset();}
    +	public boolean FlushNeeded(int writeLen) {return bfr.Len() + writeLen > fil_max;} //int bfr_len;
    +	public Xow_ns Ns_itm() {return ns_itm;} private Xow_ns ns_itm;
    +	public void Flush(Gfo_usr_dlg usr_dlg) {
    +		if (bfr.Len() == 0) return;		// nothing to flush
    +		Io_url url = url_gen.Nxt_url();
    +		Io_mgr.Instance.AppendFilBfr(url, bfr);
    +	}
    +	public void Rls() {bfr.Rls();}
    +	public static Xob_tmp_wtr new_(Xow_ns ns_itm, Io_url_gen url_gen, int fil_max)	{return new Xob_tmp_wtr(ns_itm, url_gen, fil_max);}
    +	public static Xob_tmp_wtr new_wo_ns_(Io_url_gen url_gen, int fil_max)			{return new Xob_tmp_wtr(null, url_gen, fil_max);}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr_mgr.java b/400_xowa/src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr_mgr.java
    index a27517de8..cff8e0968 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr_mgr.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr_mgr.java
    @@ -13,3 +13,31 @@ 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.bldrs.wtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import gplx.xowa.wikis.nss.*;
    +public class Xob_tmp_wtr_mgr {
    +	public Xob_tmp_wtr[] Regy() {return regy;} private Xob_tmp_wtr[] regy = new Xob_tmp_wtr[Ns_ordinal_max];
    +	public Xob_tmp_wtr_mgr(Xob_tmp_wtr_wkr wkr) {this.wkr = wkr;} private Xob_tmp_wtr_wkr wkr;
    +	public Xob_tmp_wtr Get_or_new(Xow_ns ns) {
    +		Xob_tmp_wtr rv = regy[ns.Ord()];
    +		if (rv == null) {
    +			rv = wkr.Tmp_wtr_new(ns);
    +			regy[ns.Ord()] = rv;
    +		}
    +		return rv;
    +	}		
    +	public void Flush_all(Gfo_usr_dlg usr_dlg) {
    +		for (int i = 0; i < Ns_ordinal_max; i++) {
    +			Xob_tmp_wtr wtr = regy[i];
    +			if (wtr != null) {
    +				wtr.Flush(usr_dlg);
    +				wtr.Rls();
    +			}
    +		}
    +	}
    +	public void Rls_all() {
    +		for (int i = 0; i < Ns_ordinal_max; i++)
    +			regy[i] = null;
    +	}
    +	static final int Ns_ordinal_max = Xow_ns_mgr_.Ordinal_max;	// ASSUME: no more than 128 ns in a wiki
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr_wkr.java b/400_xowa/src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr_wkr.java
    index a27517de8..3b2738f27 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr_wkr.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr_wkr.java
    @@ -13,3 +13,9 @@ 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.bldrs.wtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import gplx.core.ios.*;
    +import gplx.xowa.wikis.nss.*;
    +public interface Xob_tmp_wtr_wkr {
    +	Xob_tmp_wtr Tmp_wtr_new(Xow_ns ns);
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr_wkr__ttl.java b/400_xowa/src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr_wkr__ttl.java
    index a27517de8..5fa6b4f00 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr_wkr__ttl.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/wtrs/Xob_tmp_wtr_wkr__ttl.java
    @@ -13,3 +13,12 @@ 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.bldrs.wtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import gplx.core.ios.*;
    +import gplx.xowa.wikis.nss.*;
    +public class Xob_tmp_wtr_wkr__ttl implements Xob_tmp_wtr_wkr {
    +	public Xob_tmp_wtr_wkr__ttl(Io_url temp_dir, int dump_fil_len) {this.temp_dir = temp_dir; this.dump_fil_len = dump_fil_len;} Io_url temp_dir; int dump_fil_len;
    +	public Xob_tmp_wtr Tmp_wtr_new(Xow_ns ns) {
    +		return Xob_tmp_wtr.new_(ns, Io_url_gen_.dir_(temp_dir.GenSubDir_nest(ns.Num_str(), "dump")), dump_fil_len);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_import_cfg.java b/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_import_cfg.java
    index a27517de8..69b6598d9 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_import_cfg.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_import_cfg.java
    @@ -13,3 +13,50 @@ 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.bldrs.xmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import gplx.core.ios.*; import gplx.core.ios.streams.*; import gplx.core.envs.*;
    +import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.wikis.tdbs.*;
    +import gplx.xowa.bldrs.wkrs.*;
    +public class Xob_import_cfg {
    +	public Xob_import_cfg(Xowe_wiki wiki) {this.wiki = wiki;} private Xowe_wiki wiki; private boolean src_fil_is_bz2 = true;
    +	public byte Category_version() {return category_version;} public Xob_import_cfg Category_version_(byte v) {category_version = v; return this;} private byte category_version = Xoa_ctg_mgr.Version_1;
    +	public long Src_rdr_len() {return src_rdr_len;} private long src_rdr_len;
    +	public Io_url Src_fil_xml() {return src_fil_xml;}
    +	public Io_url Src_fil() {return src_fil;} private Io_url src_fil;
    +	public Xob_import_cfg Src_fil_xml_(Io_url v) {src_fil_xml = v; src_fil_is_bz2 = Bool_.N; return this;} private Io_url src_fil_xml;
    +	public Xob_import_cfg Src_fil_bz2_(Io_url v) {src_fil_bz2 = v; src_fil_is_bz2 = Bool_.Y; return this;} private Io_url src_fil_bz2;
    +	public Io_url Src_dir() {
    +		if		(src_fil_xml == null && src_fil_bz2 == null)	return wiki.Fsys_mgr().Root_dir();
    +		else if (src_fil_xml != null)							return src_fil_xml.OwnerDir();
    +		else if (src_fil_bz2 != null)							return src_fil_bz2.OwnerDir();
    +		else													throw Err_.new_wo_type("unknown src dir");
    +	}
    +	public Io_stream_rdr Src_rdr() {
    +		if (src_fil_xml == null && src_fil_bz2 == null) {	// will usually be null; non-null when user specifies src through command-line
    +			Io_url url = Xob_io_utl_.Find_nth_by_wildcard_or_null(wiki.Fsys_mgr().Root_dir(), Xob_io_utl_.Pattern__wilcard, ".xml", ".bz2");
    +			if (url == null) throw Err_.new_wo_type("could not find any .xml or .bz2 file", "dir", wiki.Fsys_mgr().Root_dir().Raw());
    +			if (String_.Eq(url.Ext(), ".xml"))	Src_fil_xml_(url);
    +			else								Src_fil_bz2_(url);
    +		}
    +		if (src_fil_is_bz2) {
    +			Chk_file_ext(wiki.Appe(), src_fil_bz2, ".bz2", "xml");				
    +			src_fil = src_fil_bz2; src_rdr_len = Io_mgr.Instance.QueryFil(src_fil_bz2).Size();
    +			Xoae_app app = wiki.Appe();
    +			if (gplx.xowa.bldrs.installs.Xoi_dump_mgr.Import_bz2_by_stdout(app)) {
    +				Process_adp process = app.Prog_mgr().App_decompress_bz2_by_stdout();
    +				return Io_stream_rdr_process.new_(process.Exe_url(), src_fil_bz2, process.Xto_process_bldr_args(src_fil_bz2.Raw()));
    +			}
    +			else
    +				return Io_stream_rdr_.New__bzip2(src_fil_bz2);
    +		}
    +		else {
    +			Chk_file_ext(wiki.Appe(), src_fil_xml, ".xml", "bz2");
    +			src_fil = src_fil_xml; src_rdr_len = Io_mgr.Instance.QueryFil(src_fil_xml).Size();
    +			return Io_stream_rdr_.New__raw(src_fil_xml);
    +		}
    +	}
    +	private static void Chk_file_ext(Xoae_app app, Io_url fil, String expd_ext, String alt_ext) {
    +		if (!String_.Eq(fil.Ext(), expd_ext))
    +			app.Usr_dlg().Warn_many("", "", "File extension is not " + expd_ext + ". Please use '.src_" + alt_ext + "_fil_' instead; file=~{0}", fil.Raw());
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_import_marker.java b/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_import_marker.java
    index a27517de8..0e20e0c7d 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_import_marker.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_import_marker.java
    @@ -13,3 +13,35 @@ 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.bldrs.xmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import gplx.gfui.*; import gplx.gfui.kits.core.*; import gplx.xowa.bldrs.cmds.utils.*;
    +public class Xob_import_marker {
    +	private final    Hash_adp_bry in_progress_hash = Hash_adp_bry.cs();
    +	public void Bgn(Xowe_wiki wiki) {
    +		in_progress_hash.Add_as_key_and_val(wiki.Domain_bry());
    +		Io_mgr.Instance.SaveFilStr(url_(wiki), "XOWA has created this file to indicate that an import is in progress. This file will be deleted once the import is completed.");
    +	}
    +	public void End(Xowe_wiki wiki) {
    +		in_progress_hash.Del(wiki.Domain_bry());
    +		Io_mgr.Instance.DeleteFil_args(url_(wiki)).MissingFails_off().Exec();
    +	}
    +	public boolean Chk(Xowe_wiki wiki) {
    +		if (!wiki.App().Mode().Tid_is_gui()) return true;			// NOTE: ignore during Server / Console modes; DATE:2015-04-01
    +		if (in_progress_hash.Has(wiki.Domain_bry())) return true;	// NOTE: ignore if currently building; different bldr commands call wiki.Init_assert() which may lead to fals checks;
    +		Io_url url = url_(wiki);
    +		if (!Io_mgr.Instance.ExistsFil(url)) return true;
    +		Xoae_app app = wiki.Appe();
    +		app.Usr_dlg().Log_many("", "", "import.marker: marker found: url=~{0}", url.Raw());
    +		byte[] incompete_msg_bry = app.Usere().Msg_mgr().Val_by_key_args(Bry_.new_a7("api-xowa.import.core.incomplete"), wiki.Domain_str());
    +		int rslt = app.Gui_mgr().Kit().Ask_yes_no_cancel("", "", String_.new_u8(incompete_msg_bry));
    +		switch (rslt) {
    +			case Gfui_dlg_msg_.Btn_yes:		Xob_cleanup_cmd.Delete_wiki_sql(wiki); End(wiki); return false;	// delete wiki
    +			case Gfui_dlg_msg_.Btn_no:		End(wiki); return true;		// delete marker
    +			case Gfui_dlg_msg_.Btn_cancel:	return true;				// noop
    +			default:						throw Err_.new_unhandled(rslt);
    +		}
    +	}
    +	private static Io_url url_(Xowe_wiki wiki) {
    +		return wiki.Fsys_mgr().Root_dir().GenSubFil(wiki.Domain_str() + "-import.lock");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_dumper.java b/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_dumper.java
    index a27517de8..3b6fe70ee 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_dumper.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_dumper.java
    @@ -13,3 +13,84 @@ 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.bldrs.xmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import gplx.langs.xmls.*;
    +import gplx.xowa.wikis.nss.*;
    +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.data.tbls.*;
    +public class Xob_xml_dumper {
    +	private final Gfo_xml_wtr wtr = new Gfo_xml_wtr();
    +	public String Bld_str() {return wtr.Bld_str();}
    +	public void Write_root_bgn(Xow_ns_mgr ns_mgr, Xow_domain_itm domain, String wiki_abrv, String main_page, String ns_case, String app_version) {
    +		wtr.Nde_lhs_bgn_grp("mediawiki");
    +		wtr.Atr_kv_str_a7("xmlns"					, "http://www.mediawiki.org/xml/export-0.10/");
    +		wtr.Atr_kv_str_a7("xmlns:xsi"				, "http://www.w3.org/2001/XMLSchema-instance");
    +		wtr.Atr_kv_str_a7("xsi:schemaLocation"		, "http://www.mediawiki.org/xml/export-0.10/ http://www.mediawiki.org/xml/export-0.10.xsd");
    +		wtr.Atr_kv_str_a7("version"					, "0.10");
    +		wtr.Atr_kv_str_a7("xml:lang"				, "en");
    +		wtr.Nde_lhs_end();
    +		Write_siteinfo(domain, wiki_abrv, main_page, ns_case, app_version);
    +		Write_ns_mgr(ns_mgr);
    +	}
    +	public void Write_root_end() {
    +		wtr.Nde_rhs();
    +	}
    +	private void Write_siteinfo(Xow_domain_itm domain, String wiki_abrv, String main_page, String ns_case, String app_version) {
    +		wtr.Nde_lhs("siteinfo");
    +		wtr.Nde_txt_bry("sitename"				, Xow_domain_tid_.Get_type_as_bry(domain.Domain_type_id()));
    +		wtr.Nde_txt_str("dbname"				, wiki_abrv);
    +		wtr.Nde_txt_str("base"				, main_page);
    +		wtr.Nde_txt_str("generator"				, app_version);
    +		wtr.Nde_txt_str("case"					, ns_case);
    +		wtr.Nde_rhs();
    +	}
    +	private void Write_ns_mgr(Xow_ns_mgr ns_mgr) {
    +		wtr.Nde_lhs("namespaces");
    +		int len = ns_mgr.Ords_len();
    +		for (int i = 0; i < len; ++i) {
    +			Xow_ns ns = ns_mgr.Ords_get_at(i);
    +			Write_ns(ns);
    +		}
    +		wtr.Nde_rhs();
    +	}
    +	private void Write_ns(Xow_ns ns) {
    +		wtr.Nde_lhs_bgn_itm("namespace");
    +		wtr.Atr_kv_int("key"					, ns.Id());
    +		wtr.Atr_kv_str_a7("case"				, Xow_ns_case_.To_str(ns.Case_match()));
    +		wtr.Nde_lhs_end();
    +		wtr.Txt_bry(ns.Name_db());
    +		wtr.Nde_rhs();
    +	}
    +	public void Write_page(Xowd_page_itm page) {
    +		wtr.Nde_lhs("page");
    +		wtr.Nde_txt_bry("title"					, page.Ttl_full_db());
    +		wtr.Nde_txt_int("id"					, page.Id());
    +		Write_revision(page);
    +		wtr.Nde_rhs();
    +	}
    +	private void Write_revision(Xowd_page_itm page) {
    +		wtr.Nde_lhs("revision");
    +		wtr.Nde_txt_int("id"					, -1);
    +		wtr.Nde_txt_int("parent"				, -1);
    +		wtr.Nde_txt_str("timestamp"				, page.Modified_on().XtoStr_fmt_iso_8561());
    +		Write_revision_contributor(page);
    +		wtr.Nde_txt_str("comment"				, "");
    +		wtr.Nde_txt_str("model"					, "wikitext");
    +		wtr.Nde_txt_str("format"				, "text/x-wiki");
    +		Write_revision_text(page);
    +		wtr.Nde_txt_str("sha1"					, "");
    +		wtr.Nde_rhs();
    +	}
    +	private void Write_revision_contributor(Xowd_page_itm page) {
    +		wtr.Nde_lhs("contributor");
    +		wtr.Nde_txt_str("username"				, "");
    +		wtr.Nde_txt_int("id"					, -1);
    +		wtr.Nde_rhs();
    +	}
    +	private void Write_revision_text(Xowd_page_itm page) {
    +		wtr.Nde_lhs_bgn_itm("text");
    +		wtr.Atr_kv_str_a7("xml:space", "preserve");
    +		wtr.Nde_lhs_end();
    +		wtr.Txt_bry(page.Text());
    +		wtr.Nde_rhs();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_dumper_tst.java b/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_dumper_tst.java
    index a27517de8..b6eb98bdf 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_dumper_tst.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_dumper_tst.java
    @@ -13,3 +13,88 @@ 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.bldrs.xmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import org.junit.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.wikis.nss.*;
    +public class Xob_xml_dumper_tst {
    +	private final Xob_xml_dumper_fxt fxt = new Xob_xml_dumper_fxt();
    +	@Before public void init() {fxt.Clear();}
    +	@Test   public void Basic() {
    +		fxt.Test_page(fxt.Make_ary(fxt.Make_page(1, Xow_ns_.Tid__main, "A", "A_text")), String_.Concat_lines_nl_skip_last
    +		( ""
    +		, "  "
    +		, "    other"
    +		, "    "
    +		, "    Main_Page"
    +		, "    XOWA 2.5.2.2"
    +		, "    first-letter"
    +		, "  "
    +		, "  "
    +		, "    Media"
    +		, "    Special"
    +		, "    "
    +		, "    Talk"
    +		, "    User"
    +		, "    User_talk"
    +		, "    Wikipedia"
    +		, "    Wikipedia_talk"
    +		, "    File"
    +		, "    File_talk"
    +		, "    MediaWiki"
    +		, "    MediaWiki_talk"
    +		, "    Template"
    +		, "    Template_talk"
    +		, "    Help"
    +		, "    Help_talk"
    +		, "    Category"
    +		, "    Category_talk"
    +		, "    Portal"
    +		, "    Portal_talk"
    +		, "    Book"
    +		, "    Book_talk"
    +		, "    Module"
    +		, "    Module_talk"
    +		, "  "
    +		, "  "
    +		, "    A"
    +		, "    1"
    +		, "    "
    +		, "      -1"
    +		, "      -1"
    +		, "      0001-01-01 00:00:00"
    +		, "      "
    +		, "        "
    +		, "        -1"
    +		, "      "
    +		, "      "
    +		, "      wikitext"
    +		, "      text/x-wiki"
    +		, "      A_text"
    +		, "      "
    +		, "    "
    +		, "  "
    +		, ""
    +		));
    +	}
    +}
    +class Xob_xml_dumper_fxt {
    +	private Xowe_wiki wiki;
    +	private final Xob_xml_dumper export_wtr = new Xob_xml_dumper();
    +	public void Clear() {
    +		Xoae_app app = Xoa_app_fxt.Make__app__edit();
    +		this.wiki = Xoa_app_fxt.Make__wiki__edit(app, "enwiki");
    +	}
    +	public Xowd_page_itm[] Make_ary(Xowd_page_itm... ary) {return ary;}
    +	public Xowd_page_itm Make_page(int id, int ns_id, String ttl_str, String text) {
    +		Xoa_ttl ttl = wiki.Ttl_parse(ns_id, Bry_.new_u8(ttl_str));
    +		return new Xowd_page_itm().Id_(id).Ns_id_(ns_id).Ttl_(ttl).Text_(Bry_.new_u8(text));
    +	}
    +	public void Test_page(Xowd_page_itm[] ary, String expd) {
    +		export_wtr.Write_root_bgn(wiki.Ns_mgr(), wiki.Domain_itm(), "", String_.new_u8(wiki.Props().Main_page()), "first-letter", "XOWA 2.5.2.2");
    +		int len = ary.length;
    +		for (int i = 0; i < len; ++i)
    +			export_wtr.Write_page(ary[i]);
    +		export_wtr.Write_root_end();
    +		String actl = export_wtr.Bld_str();
    +		Tfds.Eq_str_lines(expd, actl);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_page_bldr.java b/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_page_bldr.java
    index a27517de8..079193f92 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_page_bldr.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_page_bldr.java
    @@ -13,3 +13,66 @@ 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.bldrs.xmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import gplx.core.ios.*; import gplx.xowa.wikis.data.tbls.*;
    +public class Xob_xml_page_bldr {
    +	public byte[] Xto_bry() {return bfr.To_bry_and_clear();}
    +	public Io_buffer_rdr XtoByteStreamRdr() {return XtoByteStreamRdr(Io_mgr.Len_kb);}
    +	public Io_buffer_rdr XtoByteStreamRdr(int bfr_len) {
    +		Io_url url = Io_url_.mem_fil_("mem/byteStreamRdr.txt");
    +		Io_mgr.Instance.SaveFilBry(url, bfr.To_bry_and_clear());
    +		return Io_buffer_rdr.new_(gplx.core.ios.streams.Io_stream_rdr_.New__raw(url), bfr_len);
    +	}
    +	public Bry_bfr Bfr() {return bfr;} Bry_bfr bfr = Bry_bfr_.New();
    +	public Xob_xml_page_bldr Upd(String find, String repl) {
    +		String all = bfr.To_str_and_clear();
    +		all = String_.Replace(all, find, repl);
    +		bfr.Add_str_u8(all);
    +		return this;
    +	}
    +	public Xob_xml_page_bldr Add_ary(Xowd_page_itm... ary) {
    +		for (Xowd_page_itm doc : ary)
    +			Add(doc);
    +		return this;
    +	}
    +	public Xob_xml_page_bldr Add(Xowd_page_itm doc) {
    +		bfr.Add(Indent_2).Add(Xob_xml_parser_.Bry_page_bgn).Add_byte_nl();
    +		bfr.Add(Indent_4).Add(Xob_xml_parser_.Bry_title_bgn).Add(doc.Ttl_full_db()).Add(Xob_xml_parser_.Bry_title_end).Add_byte_nl();
    +		bfr.Add(Indent_4).Add(Xob_xml_parser_.Bry_id_bgn).Add_int_variable(doc.Id()).Add(Xob_xml_parser_.Bry_id_end).Add_byte_nl();
    +		bfr.Add(Indent_4).Add(Xob_xml_parser_.Bry_redirect_bgn_frag).Add(Nde_inline).Add_byte_nl();
    +		bfr.Add(Indent_4).Add(Xob_xml_parser_.Bry_revision_bgn).Add_byte_nl();
    +		bfr.Add(Indent_6).Add(Xob_xml_parser_.Bry_id_bgn).Add_int_variable(Revision_id).Add(Xob_xml_parser_.Bry_id_end).Add_byte_nl();
    +		bfr.Add(Indent_6).Add(Xob_xml_parser_.Bry_timestamp_bgn).Add_dte(doc.Modified_on()).Add(Xob_xml_parser_.Bry_timestamp_end).Add_byte_nl();
    +		bfr.Add(Indent_6).Add(Xob_xml_parser_.Bry_contributor_bgn).Add_byte_nl();
    +		bfr.Add(Indent_8).Add(Xob_xml_parser_.Bry_username_bgn).Add(Contributor_username).Add(Xob_xml_parser_.Bry_username_end).Add_byte_nl();
    +		bfr.Add(Indent_8).Add(Xob_xml_parser_.Bry_id_bgn).Add_int_variable(Contributor_id).Add(Xob_xml_parser_.Bry_id_end).Add_byte_nl();
    +		bfr.Add(Indent_6).Add(Xob_xml_parser_.Bry_contributor_end).Add_byte_nl();
    +		bfr.Add(Indent_6).Add(Xob_xml_parser_.Bry_minor_bgn_frag).Add(Nde_inline).Add_byte_nl();
    +		bfr.Add(Indent_6).Add(Xob_xml_parser_.Bry_comment_bgn).Add(Revision_comment).Add(Xob_xml_parser_.Bry_comment_end).Add_byte_nl();
    +		bfr.Add(Indent_6).Add(Xob_xml_parser_.Bry_text_bgn).Add(doc.Text()).Add(Xob_xml_parser_.Bry_text_end).Add_byte_nl();
    +		bfr.Add(Indent_4).Add(Xob_xml_parser_.Bry_revision_end).Add_byte_nl();
    +		bfr.Add(Indent_2).Add(Xob_xml_parser_.Bry_page_end).Add_byte_nl();
    +		return this;
    +	}
    +	private static final    byte[] Nde_inline = Bry_.new_a7(" />"), Indent_2 = Bry_.Repeat_space(2), Indent_4 = Bry_.Repeat_space(4), Indent_6 = Bry_.Repeat_space(6), Indent_8 = Bry_.Repeat_space(8);
    +	private static final    int Revision_id = 1234, Contributor_id = 9876;
    +	private static final    byte[] Contributor_username = Bry_.new_a7("contributor_username"), Revision_comment = Bry_.new_a7("revision_comment");
    +}
    +/*
    +  
    +    AccessibleComputing
    +    10
    +    
    +    
    +      381202555
    +      2010-08-26T22:38:36Z
    +      
    +		OlEnglish
    +		7181920
    +      
    +      
    +      [[Help:Reverting|Reverted]] edits by [[Special:Contributions/76.28.186.133|76.28.186.133]] ([[User talk:76.28.186.133|talk]]) to last version by Gurch
    +      #REDIRECT [[Computer accessibility]] {{R from CamelCase}}
    +    
    +  
    +*/
    diff --git a/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_parser.java b/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_parser.java
    index a27517de8..80631d815 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_parser.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_parser.java
    @@ -13,3 +13,115 @@ 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.bldrs.xmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import gplx.core.btries.*; import gplx.core.ios.*; import gplx.core.times.*;
    +import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.wikis.nss.*;
    +public class Xob_xml_parser {
    +	Btrie_fast_mgr trie = Xob_xml_parser_.trie_(); Bry_bfr data_bfr = Bry_bfr_.New(); DateAdp_parser date_parser = DateAdp_parser.new_();
    +	public Xob_xml_parser Tag_len_max_(int v) {tag_len_max = v; return this;} private int tag_len_max = 255; // max size of any (a) xml tag, (b) int or (c) date; everything else goes into a data_bfr
    +	public Xob_xml_parser Data_bfr_len_(int v) {data_bfr.Resize(v); return this;} // PERF: resize data_bfr once to large size, rather than grow incremently to it
    +	public Xob_xml_parser Trie_tab_del_() {trie.Del(Xob_xml_parser_.Bry_tab); return this;}
    +	public int Parse_page(Xowd_page_itm rv, Gfo_usr_dlg usr_dlg, Io_buffer_rdr fil, byte[] src, int pos, Xow_ns_mgr ns_mgr) {
    +		rv.Clear();
    +		int src_len = fil.Bfr_len(), data_bgn = -1, page_bgn = -1;
    +		boolean data_bfr_add = false, page_id_needed = true, title_needed = true, reading = true;
    +		int[] modified_on_ary = new int[7];
    +		while (reading) {
    +			if (	pos + tag_len_max > src_len			// near end of src
    +				&&	!fil.Fil_eof())	 {					// not at fil end
    +				int refill_pos = 0;
    +				if (page_bgn == -1) {					// keep page in same data_bfr; NOTE: needed else timestamp/id may fail
    +					refill_pos = pos;
    +					pos = 0;
    +				}
    +				else {
    +					refill_pos = page_bgn;
    +					data_bgn -= page_bgn;
    +					pos -= page_bgn;
    +				}
    +				fil.Bfr_load_from(refill_pos);		// refill src from pos; 
    +				src_len = fil.Bfr_len();
    +			}
    +			if (pos >= src_len) return Bry_find_.Not_found;	// no more src left; should only happen at end of file
    +			byte b = src[pos];
    +			Object o = trie.Match_bgn_w_byte(b, src, pos, src_len);
    +			if (o == null) {								// text_data; not an xml_nde (), xml_escape (<), or tab
    +				if (data_bfr_add) data_bfr.Add_byte(b);		// add to src if data_bfr_add is on (only happens for , <text>)
    +				++pos;
    +			}
    +			else {											// is xml_nde, xml_escape, or tab
    +				Xob_xml_parser_itm itm = (Xob_xml_parser_itm)o;
    +				int hook_bgn = pos;							// mark old pos
    +				pos += itm.Hook_len();						// calc new pos
    +				switch (itm.Tid()) {
    +					case Xob_xml_parser_.Id_page_bgn:		page_bgn = hook_bgn; break;
    +					case Xob_xml_parser_.Id_id_bgn:			if (page_id_needed) data_bgn = pos; break;	// only flag if first <id>; note that 1st <id> always belongs to <page>;
    +					case Xob_xml_parser_.Id_id_end:	
    +						if (page_id_needed) {
    +							int page_id = Bry_.To_int_or(src, data_bgn, hook_bgn, -1); if (page_id == -1) usr_dlg.Warn_many(GRP_KEY, "page_id_invalid", "page_id_is_invalid: ~{0}", String_.new_u8(src, data_bgn, hook_bgn));
    +							rv.Id_(page_id);
    +							page_id_needed = false;		// turn off for other <id> tags (<contributor>; <revision>)
    +						}
    +						break;
    +					case Xob_xml_parser_.Id_timestamp_bgn:  data_bgn = pos; break;
    +					case Xob_xml_parser_.Id_timestamp_end:
    +						date_parser.Parse_iso8651_like(modified_on_ary, src, data_bgn, hook_bgn);
    +						rv.Modified_on_(DateAdp_.seg_(modified_on_ary));
    +						break;
    +					case Xob_xml_parser_.Id_title_bgn:		if (title_needed) data_bfr_add = true; break;
    +					case Xob_xml_parser_.Id_text_bgn:		data_bfr_add = true; break;
    +					case Xob_xml_parser_.Id_title_end:
    +						if (title_needed) {
    +							data_bfr_add = false;
    +							byte[] ttl = data_bfr.To_bry_and_clear();
    +							Bry_.Replace_reuse(ttl, Byte_ascii.Space, Byte_ascii.Underline);
    +							rv.Ttl_(ttl, ns_mgr);
    +							title_needed = false;
    +						}
    +						break;
    +					case Xob_xml_parser_.Id_text_end:		data_bfr_add = false; rv.Text_(data_bfr.To_bry_and_clear()); break;
    +					case Xob_xml_parser_.Id_amp: case Xob_xml_parser_.Id_quot: case Xob_xml_parser_.Id_lt: case Xob_xml_parser_.Id_gt:
    +					case Xob_xml_parser_.Id_cr_nl: case Xob_xml_parser_.Id_cr:
    +						if (data_bfr_add) data_bfr.Add_byte(itm.Subst_byte());
    +						break;
    +					case Xob_xml_parser_.Id_tab: 
    +						if (data_bfr_add) data_bfr.Add(itm.Subst_ary());	// NOTE: tab can exist in xml; see en.wiktionary.org_20120109: "<page>\n    <title>Thread:User talk:Yair rand/newentrywiz.js/quiashed\n    2578382\n\n\t
    +						break;
    +					case Xob_xml_parser_.Id_page_end:		reading = false; page_bgn = -1; break;
    +					case Xob_xml_parser_.Id_page_bgn_frag:
    +						pos = Find_gt(src, src_len, pos);
    +						/*warn*/
    +						break;
    +					case Xob_xml_parser_.Id_title_bgn_frag:
    +						if (title_needed) {
    +							pos = Find_gt(src, src_len, pos) + 1;	// +1 to get next character 
    +							if (!gt_was_inline) data_bfr_add = true;
    +						}
    +						break;
    +					case Xob_xml_parser_.Id_text_bgn_frag:
    +						pos = Find_gt(src, src_len, pos) + 1;	// +1 to get next character 
    +						if (!gt_was_inline) data_bfr_add = true;
    +						break;
    +					case Xob_xml_parser_.Id_id_bgn_frag: case Xob_xml_parser_.Id_timestamp_bgn_frag:
    +						data_bgn = pos; /*warn*/
    +						break;
    +					default:								throw Err_.new_unhandled(itm.Tid());	// shouldn't happen
    +				}
    +			}
    +		}
    +		return pos;
    +	}
    +	int Find_gt(byte[] src, int src_len, int src_pos) {
    +		gt_was_inline = false;
    +		while (src_pos < src_len) {
    +			switch (src[src_pos]) {
    +				case Byte_ascii.Slash:	gt_was_inline = true; break;
    +				case Byte_ascii.Gt:		return src_pos;
    +				default:				gt_was_inline = false; break;
    +			}
    +			++src_pos;
    +		}
    +		return -1;
    +	}	boolean gt_was_inline = false;
    +	static final String GRP_KEY = "xowa.bldrs.xmls.xml_parser";
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_parser_.java b/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_parser_.java
    index a27517de8..2b5303070 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_parser_.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_parser_.java
    @@ -13,3 +13,59 @@ 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.bldrs.xmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import gplx.core.btries.*;
    +public class Xob_xml_parser_ {
    +	public static Btrie_fast_mgr trie_() {
    +		Btrie_fast_mgr rv = Btrie_fast_mgr.cs();
    +		trie_add(rv, Bry_page_bgn, Id_page_bgn); trie_add(rv, Bry_page_bgn_frag, Id_page_bgn_frag); trie_add(rv, Bry_page_end, Id_page_end);
    +		trie_add(rv, Bry_id_bgn, Id_id_bgn); trie_add(rv, Bry_id_bgn_frag, Id_id_bgn_frag); trie_add(rv, Bry_id_end, Id_id_end);
    +		trie_add(rv, Bry_title_bgn, Id_title_bgn); trie_add(rv, Bry_title_bgn_frag, Id_title_bgn_frag); trie_add(rv, Bry_title_end, Id_title_end);
    +		trie_add(rv, Bry_timestamp_bgn, Id_timestamp_bgn); trie_add(rv, Bry_timestamp_bgn_frag, Id_timestamp_bgn_frag); trie_add(rv, Bry_timestamp_end, Id_timestamp_end);
    +		trie_add(rv, Bry_text_bgn, Id_text_bgn); trie_add(rv, Bry_text_bgn_frag, Id_text_bgn_frag); trie_add(rv, Bry_text_end, Id_text_end);
    +		trie_add(rv, Bry_amp, Id_amp, Byte_ascii.Amp); trie_add(rv, Bry_quot, Id_quot, Byte_ascii.Quote); trie_add(rv, Bry_gt, Id_gt, Byte_ascii.Gt); trie_add(rv, Bry_lt, Id_lt, Byte_ascii.Lt);
    +		trie_add(rv, Bry_tab, Id_tab, Bry_tab_ent); trie_add(rv, Bry_cr_nl, Id_cr_nl, Byte_ascii.Nl); trie_add(rv, Bry_cr, Id_cr, Byte_ascii.Nl);
    +		return rv;
    +	}
    +	public static final    byte[]
    +		  Bry_page_bgn = Bry_.new_a7(""), Bry_page_bgn_frag = Bry_.new_a7("")
    +		, Bry_title_bgn = Bry_.new_a7(""), Bry_title_bgn_frag = Bry_.new_a7("<title"), Bry_title_end = Bry_.new_a7("")
    +		, Bry_id_bgn = Bry_.new_a7(""), Bry_id_bgn_frag = Bry_.new_a7("")
    +		, Bry_redirect_bgn = Bry_.new_a7(""), Bry_redirect_bgn_frag = Bry_.new_a7("")
    +		, Bry_revision_bgn = Bry_.new_a7(""), Bry_revision_bgn_frag = Bry_.new_a7("")
    +		, Bry_timestamp_bgn = Bry_.new_a7(""), Bry_timestamp_bgn_frag = Bry_.new_a7("")
    +		, Bry_contributor_bgn = Bry_.new_a7(""), Bry_contributor_bgn_frag = Bry_.new_a7("")
    +		, Bry_username_bgn = Bry_.new_a7(""), Bry_username_bgn_frag = Bry_.new_a7("")
    +		, Bry_minor_bgn = Bry_.new_a7(""), Bry_minor_bgn_frag = Bry_.new_a7("")
    +		, Bry_comment_bgn = Bry_.new_a7(""), Bry_comment_bgn_frag = Bry_.new_a7("")
    +		, Bry_text_bgn = Bry_.new_a7(""), Bry_text_bgn_frag = Bry_.new_a7("")
    +		, Bry_amp = Bry_.new_a7("&"), Bry_quot = Bry_.new_a7("""), Bry_gt = Bry_.new_a7(">"), Bry_lt = Bry_.new_a7("<")
    +		, Bry_tab_ent = Bry_.new_a7("	"), Bry_tab = Bry_.new_a7("\t"), Bry_cr_nl = Bry_.new_a7("\r\n"), Bry_cr = Bry_.new_a7("\r")			
    +		;
    +	public static final byte
    +		  Id_page_bgn = 0, Id_page_bgn_frag = 1, Id_page_end = 2
    +		, Id_title_bgn = 3, Id_title_bgn_frag = 4, Id_title_end = 5
    +		, Id_id_bgn = 6, Id_id_bgn_frag = 7, Id_id_end = 8
    +		, Id_redirect_bgn = 9, Id_redirect_bgn_frag = 10, Id_redirect_end = 11
    +		, Id_revision_bgn = 12, Id_revision_bgn_frag = 13, Id_revision_end = 14
    +		, Id_timestamp_bgn = 15, Id_timestamp_bgn_frag = 16, Id_timestamp_end = 17
    +		, Id_contributor_bgn = 18, Id_contributor_bgn_frag = 19, Id_contributor_end = 20
    +		, Id_username_bgn = 21, Id_username_bgn_frag = 22, Id_username_end = 23
    +		, Id_minor_bgn = 24, Id_minor_bgn_frag = 25, Id_minor_end = 26
    +		, Id_comment_bgn = 27, Id_comment_bgn_frag = 28, Id_comment_end = 29
    +		, Id_text_bgn = 30, Id_text_bgn_frag = 31, Id_text_end = 32
    +		, Id_amp = 33, Id_quot = 34, Id_gt = 35, Id_lt = 36
    +		, Id_tab = 37, Id_cr_nl = 38, Id_cr = 39
    +		;
    +	private static void trie_add(Btrie_fast_mgr rv, byte[] hook, byte id)						{rv.Add(hook, new Xob_xml_parser_itm(hook, id, Byte_.Zero	, Bry_.Empty));}
    +	private static void trie_add(Btrie_fast_mgr rv, byte[] hook, byte id, byte subst_byte)	{rv.Add(hook, new Xob_xml_parser_itm(hook, id, subst_byte	, Bry_.Empty));}
    +	private static void trie_add(Btrie_fast_mgr rv, byte[] hook, byte id, byte[] subst_ary)	{rv.Add(hook, new Xob_xml_parser_itm(hook, id, Byte_.Zero	, subst_ary));}
    +}
    +class Xob_xml_parser_itm {
    +	public Xob_xml_parser_itm(byte[] hook, byte tid, byte subst_byte, byte[] subst_ary) {this.hook = hook; this.hook_len = hook.length; this.tid = tid; this.subst_byte = subst_byte; this.subst_ary = subst_ary;}
    +	public byte Tid() {return tid;} private byte tid;
    +	public byte[] Hook() {return hook;} private byte[] hook;
    +	public int Hook_len() {return hook_len;} private int hook_len;
    +	public byte Subst_byte() {return subst_byte;} private byte subst_byte;
    +	public byte[] Subst_ary() {return subst_ary;} private byte[] subst_ary;
    +}
    diff --git a/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_parser_tst.java b/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_parser_tst.java
    index a27517de8..934889181 100644
    --- a/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_parser_tst.java
    +++ b/400_xowa/src/gplx/xowa/bldrs/xmls/Xob_xml_parser_tst.java
    @@ -13,3 +13,134 @@ 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.bldrs.xmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.bldrs.*;
    +import org.junit.*; import gplx.core.ios.*; import gplx.core.times.*;
    +import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.data.tbls.*;
    +public class Xob_xml_parser_tst {
    +	@Before public void init() {
    +		Io_mgr.Instance.InitEngine_mem();
    +		Xoae_app app = Xoa_app_fxt.Make__app__edit();
    +		bldr = new Xob_bldr(app);
    +	}	private Xow_ns_mgr ns_mgr = Xow_ns_mgr_.default_(gplx.xowa.langs.cases.Xol_case_mgr_.A7());
    +	@Test  public void Basic_docs_1() {
    +		Xowd_page_itm doc = doc_(1, "a", "a a", Date_1);
    +		fil = page_bldr.Add(doc).XtoByteStreamRdr();
    +		tst_parse(fil, doc, 0);
    +	}
    +	@Test  public void Basic_docs_2() {
    +		Xowd_page_itm doc1 = doc_(1, "a", "a a", Date_1);
    +		Xowd_page_itm doc2 = doc_(2, "b", "b b", Date_2);
    +		fil = page_bldr.Add_ary(doc1, doc2).XtoByteStreamRdr();
    +		int pos = tst_parse(fil, doc1, 0);
    +		tst_parse(fil, doc2, pos);
    +	}
    +	@Test  public void Basic_space() {
    +		Xowd_page_itm doc1 = doc_(1, "a_b", "abc", Date_1);
    +		fil = page_bldr.Add_ary(doc1).Upd("a_b", "a b").XtoByteStreamRdr();
    +		tst_parse(fil, doc1, 0);
    +	}
    +	@Test  public void Xml() {
    +		Xowd_page_itm doc = doc_(1, "a", ""a & b <> a | b"", Date_1);
    +		fil = page_bldr.Add(doc).XtoByteStreamRdr();
    +		tst_parse(fil, doc.Text_(Bry_.new_a7("\"a & b <> a | b\"")), 0);
    +	}
    +	@Test  public void Tab() {
    +		Xowd_page_itm doc = doc_(1, "a", "a \t b", Date_1);
    +		fil = page_bldr.Add(doc).XtoByteStreamRdr();
    +		tst_parse(fil, doc.Text_(Bry_.new_a7("a 	 b")), 0);
    +	}
    +	@Test  public void Tab_disable() {
    +		Xowd_page_itm doc = doc_(1, "a", "a \t b", Date_1);
    +		page_parser.Trie_tab_del_();
    +		fil = page_bldr.Add(doc).XtoByteStreamRdr();
    +		tst_parse(fil, doc.Text_(Bry_.new_a7("a \t b")), 0);
    +	}
    +	@Test  public void Cr_nl() {
    +		Xowd_page_itm doc = doc_(1, "a", "a \r\n b", Date_1);
    +		fil = page_bldr.Add(doc).XtoByteStreamRdr();
    +		tst_parse(fil, doc.Text_(Bry_.new_a7("a \n b")), 0);
    +	}
    +	@Test  public void Cr() {
    +		Xowd_page_itm doc = doc_(1, "a", "a \r b", Date_1);
    +		fil = page_bldr.Add(doc).XtoByteStreamRdr();
    +		tst_parse(fil, doc.Text_(Bry_.new_a7("a \n b")), 0);
    +	}
    +	@Test  public void Text_long() {
    +		String s = String_.Repeat("a", 1024);
    +		Xowd_page_itm doc = doc_(1, "a", s, Date_1);
    +		page_parser.Tag_len_max_(32);
    +		fil = page_bldr.Add(doc).XtoByteStreamRdr(512);
    +		tst_parse(fil, doc, 0);
    +	}
    +	@Test  public void Text_empty() {
    +		Xowd_page_itm doc = doc_(1, "a", "", Date_1);
    +		fil = page_bldr.Add(doc).Upd("", "").XtoByteStreamRdr();
    +		tst_parse(fil, doc, 0);
    +	}
    +	@Test  public void Text_frag() {
    +		Xowd_page_itm doc = doc_(1, "a", "a", Date_1);
    +		fil = page_bldr.Add(doc).Upd("a", "a").XtoByteStreamRdr();
    +		tst_parse(fil, doc, 0);
    +	}
    +	@Test  public void Ns_file() {
    +		Xowd_page_itm doc = doc_(1, "File:a", "a", Date_1);
    +		Tfds.Eq(Xow_ns_.Tid__file, doc.Ns_id());
    +		Tfds.Eq("a", String_.new_u8(doc.Ttl_page_db()));
    +	}
    +	@Test  public void Ns_main() {
    +		Xowd_page_itm doc = doc_(1, "a", "a", Date_1);
    +		Tfds.Eq(Xow_ns_.Tid__main, doc.Ns_id());
    +		Tfds.Eq("a", String_.new_u8(doc.Ttl_page_db()));
    +	}
    +	@Test  public void Ns_main_book() {
    +		Xowd_page_itm doc = doc_(1, "Book", "a", Date_1);
    +		Tfds.Eq(Xow_ns_.Tid__main, doc.Ns_id());
    +		Tfds.Eq("Book", String_.new_u8(doc.Ttl_page_db()));
    +	}
    +	@Test  public void XmlEntities() {
    +		Xowd_page_itm orig = doc_(1, "A&b", "a", Date_1);
    +		Xowd_page_itm actl = new Xowd_page_itm();
    +		fil = page_bldr.Add(orig).XtoByteStreamRdr();
    +		page_parser.Parse_page(actl, usr_dlg, fil, fil.Bfr(), 0, ns_mgr);
    +		Tfds.Eq("A&b", String_.new_u8(actl.Ttl_full_db()));
    +	}
    +	@Test  public void Root() {
    +		Xowd_page_itm doc = doc_(1, "a", "a", Date_1);
    +		page_bldr.Bfr().Add_str_a7("\n");
    +		page_bldr.Add(doc);
    +		page_bldr.Bfr().Add_str_a7("");
    +		fil = page_bldr.XtoByteStreamRdr();
    +		tst_parse(fil, doc, 0);
    +	}
    +	private static final    String Date_1 = "2012-01-01T01:01:01Z", Date_2 = "2012-02-02T02:02:02Z"; DateAdp_parser dateParser = DateAdp_parser.new_();
    +	Bry_bfr bfr = Bry_bfr_.New();
    +	Xob_xml_page_bldr page_bldr = new Xob_xml_page_bldr(); Io_buffer_rdr fil; Xob_xml_parser page_parser = new Xob_xml_parser(); Xob_bldr bldr;
    +	Gfo_usr_dlg usr_dlg = Gfo_usr_dlg_.Test();
    +	int tst_parse(Io_buffer_rdr fil, Xowd_page_itm expd, int cur_pos) {
    +		Xowd_page_itm actl = new Xowd_page_itm();
    +		int rv = page_parser.Parse_page(actl, usr_dlg, fil, fil.Bfr(), cur_pos, ns_mgr);
    +		Tfds.Eq(expd.Id(), actl.Id(), "id");
    +		Tfds.Eq(String_.new_u8(expd.Ttl_full_db()), String_.new_u8(actl.Ttl_full_db()), "title");
    +		Tfds.Eq(String_.new_u8(expd.Text()), String_.new_u8(actl.Text()), "text");
    +		Tfds.Eq_date(expd.Modified_on(), actl.Modified_on(), "timestamp");
    +		return rv;
    +	}
    +	Xowd_page_itm doc_(int id, String title, String text, String date) {
    +		Xowd_page_itm rv = new Xowd_page_itm().Id_(id).Ttl_(Bry_.new_a7(title), ns_mgr).Text_(Bry_.new_a7(text));
    +		int[] modified_on = new int[7];
    +		dateParser.Parse_iso8651_like(modified_on, date);
    +		rv.Modified_on_(DateAdp_.seg_(modified_on));
    +		return rv;
    +	}
    +}
    +class Xob_xml_parser_fxt {
    +//		private final    Xob_xml_parser page_parser = new Xob_xml_parser();
    +//		public void Test__parse(Io_buffer_rdr fil, Xowd_page_itm expd, int cur_pos) {
    +//			Xowd_page_itm actl = new Xowd_page_itm();
    +//			int rv = page_parser.Parse_page(actl, usr_dlg, fil, fil.Bfr(), cur_pos, ns_mgr);
    +//			Tfds.Eq(expd.Id(), actl.Id(), "id");
    +//			Tfds.Eq(String_.new_u8(expd.Ttl_full_db()), String_.new_u8(actl.Ttl_full_db()), "title");
    +//			Tfds.Eq(String_.new_u8(expd.Text()), String_.new_u8(actl.Text()), "text");
    +//			Tfds.Eq_date(expd.Modified_on(), actl.Modified_on(), "timestamp");
    +//		}
    +}
    diff --git a/400_xowa/src/gplx/xowa/drds/Xod_app.java b/400_xowa/src/gplx/xowa/drds/Xod_app.java
    index a27517de8..031068a94 100644
    --- a/400_xowa/src/gplx/xowa/drds/Xod_app.java
    +++ b/400_xowa/src/gplx/xowa/drds/Xod_app.java
    @@ -13,3 +13,53 @@ 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.drds; import gplx.*; import gplx.xowa.*;
    +import gplx.xowa.drds.pages.*; import gplx.xowa.drds.files.*;
    +import gplx.xowa.apps.*; import gplx.xowa.wikis.data.tbls.*;
    +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.nss.*; import gplx.xowa.guis.cbks.js.*;
    +import gplx.xowa.addons.wikis.searchs.searchers.rslts.*;
    +import gplx.langs.htmls.encoders.*; import gplx.xowa.htmls.hrefs.*;
    +import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.addons.wikis.searchs.searchers.*;
    +import gplx.xowa.langs.cases.*;
    +public class Xod_app {
    +	private final    Xoav_app app;
    +	private final    Xod_page_mgr page_mgr = new Xod_page_mgr();
    +	private final    Xod_file_mgr file_mgr = new Xod_file_mgr();
    +	private final    Srch_ns_mgr ns_mgr = new Srch_ns_mgr();
    +	public Xod_app(Xoav_app app) {
    +		this.app = app;
    +		ns_mgr.Add_main_if_empty();
    +	}
    +	public Xow_wiki Wikis__get_by_domain(String wiki_domain) {
    +		Xow_wiki rv = app.Wiki_mgri().Get_by_or_make_init_y(Bry_.new_u8(wiki_domain));
    +		if (rv != null && rv.Data__core_mgr() == null) rv.Init_by_wiki();
    +		return rv;
    +	}
    +	public Xod_page_itm Wiki__get_by_url(Xow_wiki wiki, Xoa_url page_url) {
    +		return page_mgr.Get_page(wiki, page_url);
    +	}
    +	public Xod_page_itm Wiki__get_random(Xow_wiki wiki, Xow_ns ns) {
    +		byte[] random_ttl_bry = wiki.Data__core_mgr().Tbl__page().Select_random(ns);
    +		Xoa_url url = wiki.Utl__url_parser().Parse(random_ttl_bry);
    +		return Wiki__get_by_url(wiki, url);
    +	}
    +	public void Wiki__search(Cancelable cxl, Srch_rslt_cbk cbk, Xow_wiki wiki, String search, int bgn, int end) {
    +		Srch_search_addon addon = Get_addon(wiki);
    +		Srch_search_qry qry = Srch_search_qry.New__drd(wiki, ns_mgr, Bry_.new_u8(search), bgn, end);
    +		addon.Search(qry, cbk);
    +	}
    +	public void Page__on_load_end(Xow_wiki wiki, Xod_page_itm pg, Xog_js_wkr js_wkr) {
    +		file_mgr.Load_files(wiki, pg, js_wkr);
    +		app.User().User_db_mgr().Cache_mgr().Db_save();
    +		gplx.xowa.wikis.pages.lnkis.Xopg_redlink_mgr.Run_async(pg.Hpg(), js_wkr);
    +	}
    +	public static byte[] To_page_url(Xow_wiki wiki, String canonical_str) {// NOTE: need canonical_url to handle "A:B" where "A:" is not a ns, even though PageTitle treats "A:" as a namespace
    +		byte[] canonical_bry = Bry_.new_u8(canonical_str);
    +		int page_bgn = Bry_find_.Move_fwd(canonical_bry, Xoh_href_.Bry__wiki, 0); if (page_bgn == Bry_find_.Not_found) throw Err_.new_("drd", "uknown url format: no '/wiki/'", "url", canonical_bry);
    +		byte[] page_bry = Bry_.Mid(canonical_bry, page_bgn, canonical_bry.length);	// get bry; EX: https://en.wikipedia.org/wiki/A -> A
    +		page_bry = Gfo_url_encoder_.Http_url.Decode(page_bry);						// decode %-encoding; convert + to space
    +		page_bry = Xoa_ttl.Replace_spaces(page_bry);								// convert spaces to unders; canonical-url has spaces
    +		return page_bry;
    +	}
    +	private Srch_search_addon Get_addon(Xow_wiki wiki) {return Srch_search_addon.Get(wiki);}
    +}
    diff --git a/400_xowa/src/gplx/xowa/drds/Xod_app_tst.java b/400_xowa/src/gplx/xowa/drds/Xod_app_tst.java
    index a27517de8..21b216e6b 100644
    --- a/400_xowa/src/gplx/xowa/drds/Xod_app_tst.java
    +++ b/400_xowa/src/gplx/xowa/drds/Xod_app_tst.java
    @@ -13,3 +13,59 @@ 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.drds; import gplx.*; import gplx.xowa.*;
    +import org.junit.*; import gplx.xowa.drds.pages.*; import gplx.xowa.wikis.*; import gplx.xowa.htmls.sections.*;
    +public class Xod_app_tst {
    +	private final    Xod_app_tstr tstr = new Xod_app_tstr();
    +	@Before		public void init() {tstr.Init_mem();}
    +	// COMMENTED: broke from changes to auto-init wiki; DATE:2016-06-16
    +//		@Test  public void Get() {
    +//			tstr.Data_mgr().Page__insert(1, "A", "2015-10-19 00:01:02");
    +//			tstr.Data_mgr().Html__insert(1, "abc");
    +//			tstr.Test__get("A", tstr.Make_page(1, "A", "2015-10-19T00:01:02Z", tstr.Make_section(0, 2, "", "", "abc")));
    +//		}
    +	@Test   public void To_page_db() {
    +		tstr.Test__to_page_url("http://en.wikipedia.org/wiki/A"			, "A");
    +		tstr.Test__to_page_url("http://en.wikipedia.org/wiki/A:B"		, "A:B");
    +		tstr.Test__to_page_url("http://en.wikipedia.org/wiki/Help:A"	, "Help:A");
    +		tstr.Test__to_page_url("http://en.wikipedia.org/wiki/A B"		, "A_B");	// NOTE:canonical url has spaces;
    +		tstr.Test__to_page_url("http://en.wikipedia.org/wiki/A%27B"		, "A'B");	// NOTE:canonical url has percent-encoding;
    +		tstr.Test__to_page_url("http://en.wikipedia.org/wiki/A+B"		, "A_B");	// NOTE:canonical url sometimes has "+" for space
    +	}
    +}
    +class Xod_app_tstr {
    +	private final    gplx.xowa.apps.Xoav_app app; private final    Xowv_wiki wiki;
    +	private final    Xod_app drd_provider;
    +	public Xod_app_tstr() {
    +		this.app = Xoa_app_fxt.Make__app__view();
    +		this.wiki = Xoa_app_fxt.Make__wiki__view(app);
    +		data_mgr.Wiki_(wiki);
    +		Xoa_test_.Init__db__view(wiki);
    +		drd_provider = new Xod_app(app);
    +	}
    +	public Xowd_data_tstr Data_mgr() {return data_mgr;} private final    Xowd_data_tstr data_mgr = new Xowd_data_tstr();
    +	public void Init_mem() {
    +		Io_mgr.Instance.InitEngine_mem();
    +	}
    +	public void Test__get(String ttl, Xod_page_itm expd) {
    +		Xow_wiki wiki = drd_provider.Wikis__get_by_domain("en.wikipedia.org");
    +		Xoa_url page_url = wiki.Utl__url_parser().Parse(Bry_.new_u8(ttl));
    +		Xod_page_itm itm = drd_provider.Wiki__get_by_url(wiki, page_url);
    +		Tfds.Eq(expd.To_str(), itm.To_str());
    +	}
    +	public void Test__to_page_url(String raw, String expd) {
    +		// // canonical url has spaces as well as %-encoding; PAGE:en.w:List_of_Fire_Emblem:Shadow_Dragon_characters
    +		Tfds.Eq_bry(Bry_.new_u8(expd), Xod_app.To_page_url(wiki, raw));
    +	}
    +	public Xod_page_itm Make_page(int page_id, String ttl, String modified_on, Xoh_section_itm... section_ary) {
    +		Xod_page_itm rv = new Xod_page_itm();
    +		rv.Init(page_id, page_id, ttl, ttl, null, null, modified_on, Bool_.N, Bool_.N, Bool_.N, 1, null, null, null);
    +		int len = section_ary.length;
    +		for (int i = 0; i < len; ++i) {
    +			Xoh_section_itm itm = section_ary[i];
    +			rv.Section_list().Add(itm);
    +		}			
    +		return rv;
    +	}
    +	public Xoh_section_itm Make_section(int id, int level, String anchor, String heading, String content) {return new Xoh_section_itm(id, level, Bry_.new_u8(anchor), Bry_.new_u8(heading)).Content_(Bry_.new_u8(content));}
    +}
    diff --git a/400_xowa/src/gplx/xowa/drds/Xowd_data_tstr.java b/400_xowa/src/gplx/xowa/drds/Xowd_data_tstr.java
    index a27517de8..50f6c92e3 100644
    --- a/400_xowa/src/gplx/xowa/drds/Xowd_data_tstr.java
    +++ b/400_xowa/src/gplx/xowa/drds/Xowd_data_tstr.java
    @@ -13,3 +13,38 @@ 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.drds; import gplx.*; import gplx.xowa.*;
    +import gplx.xowa.wikis.data.*;
    +import gplx.xowa.htmls.*; import gplx.xowa.wikis.data.tbls.*; 
    +public class Xowd_data_tstr {
    +	public void Wiki_(Xow_wiki wiki) {this.wiki = wiki;} private Xow_wiki wiki;
    +	public void Page__insert(int page_id, String ttl_str, String modified_on) {Page__insert(page_id, ttl_str, modified_on, Bool_.N, 0, page_id, 0, 0);}
    +	public void Page__insert(int page_id, String ttl_str, String modified_on, boolean page_is_redirect, int page_len, int random_int, int text_db_id, int html_db_id) {
    +		Xoa_ttl ttl = wiki.Ttl_parse(Bry_.new_u8(ttl_str));
    +		wiki.Data__core_mgr().Tbl__page().Insert(page_id, ttl.Ns().Id(), ttl.Page_db(), page_is_redirect, DateAdp_.parse_iso8561(modified_on), page_len, page_id, text_db_id, html_db_id);
    +	}
    +	public void Html__insert(int page_id, String html) {
    +		Xow_db_file html_db = wiki.Data__core_mgr().Db__html();
    +		if (html_db == null) {
    +			html_db = wiki.Data__core_mgr().Db__core();
    +			html_db.Tbl__html().Create_tbl();
    +		}
    +		byte[] html_bry = Bry_.new_u8(html);
    +		Xoh_page hpg = new Xoh_page();
    +		hpg.Db().Html().Html_bry_(html_bry);
    +		byte[] data = html_bry;
    +		html_db.Tbl__html().Insert(page_id, 0, gplx.core.ios.streams.Io_stream_tid_.Tid__raw, gplx.xowa.htmls.core.hzips.Xoh_hzip_dict_.Hzip__none, Bry_.Empty, Bry_.Empty, Bry_.Empty, data);
    +	}
    +	public void Text__insert(int page_id, String text) {
    +		Xow_db_file db = wiki.Data__core_mgr().Db__text();
    +		if (db == null) {
    +			db = wiki.Data__core_mgr().Db__core();
    +			db.Tbl__text().Create_tbl();
    +		}
    +			db.Tbl__text().Create_tbl();
    +		byte[] text_bry = Bry_.new_u8(text);
    +		db.Tbl__text().Insert_bgn();
    +		db.Tbl__text().Insert_cmd_by_batch(page_id, text_bry);
    +		db.Tbl__text().Insert_end();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/drds/files/Xod_activity_adp.java b/400_xowa/src/gplx/xowa/drds/files/Xod_activity_adp.java
    index a27517de8..032178777 100644
    --- a/400_xowa/src/gplx/xowa/drds/files/Xod_activity_adp.java
    +++ b/400_xowa/src/gplx/xowa/drds/files/Xod_activity_adp.java
    @@ -13,3 +13,9 @@ 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.drds.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.drds.*;
    +public interface Xod_activity_adp {
    +	String Fsys__files_dir();
    +	String Fsys__cache_dir();
    +	String Fsys__sdcard_rw_or_null();
    +}
    diff --git a/400_xowa/src/gplx/xowa/drds/files/Xod_file_mgr.java b/400_xowa/src/gplx/xowa/drds/files/Xod_file_mgr.java
    index a27517de8..7a66a2442 100644
    --- a/400_xowa/src/gplx/xowa/drds/files/Xod_file_mgr.java
    +++ b/400_xowa/src/gplx/xowa/drds/files/Xod_file_mgr.java
    @@ -13,3 +13,25 @@ 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.drds.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.drds.*;
    +import gplx.core.threads.*;
    +import gplx.xowa.drds.pages.*;
    +import gplx.xowa.files.*; import gplx.xowa.guis.cbks.js.*;
    +import gplx.xowa.htmls.*;
    +public class Xod_file_mgr {
    +	private final    Gfo_thread_pool thread_pool = new Gfo_thread_pool();
    +	public void Load_files(Xow_wiki wiki, Xod_page_itm pg, Xog_js_wkr js_wkr) {
    +		Xoh_page hpg = pg.Hpg();
    +		List_adp img_list = To_img_list(hpg.Img_mgr());
    +		Xof_file_wkr img_wkr = new Xof_file_wkr(wiki.File__orig_mgr(), wiki.File__bin_mgr(), wiki.File__mnt_mgr(), wiki.App().User().User_db_mgr().Cache_mgr(), wiki.File__repo_mgr(), js_wkr, hpg, img_list);
    +		thread_pool.Add_at_end(img_wkr);
    +		thread_pool.Run();			
    +	}
    +	private static List_adp To_img_list(Xoh_img_mgr img_mgr) {
    +		List_adp rv = List_adp_.New();
    +		int len = img_mgr.Len();
    +		for (int i = 0; i < len; ++i)
    +			rv.Add(img_mgr.Get_at(i));
    +		return rv;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/drds/files/Xod_fsys_mgr.java b/400_xowa/src/gplx/xowa/drds/files/Xod_fsys_mgr.java
    index a27517de8..c46bb0705 100644
    --- a/400_xowa/src/gplx/xowa/drds/files/Xod_fsys_mgr.java
    +++ b/400_xowa/src/gplx/xowa/drds/files/Xod_fsys_mgr.java
    @@ -13,3 +13,21 @@ 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.drds.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.drds.*;
    +public class Xod_fsys_mgr {
    +	public Xod_fsys_mgr(Gfo_log log, Xod_activity_adp activity) {
    +		this.usr_data_dir = Io_url_.lnx_dir_(activity.Fsys__files_dir() + "/");
    +		// this.usr_temp_dir = Io_url_.lnx_dir_(activity.Fsys__cache_dir() + "/");
    +		this.usr_data_fil = usr_data_dir.GenSubFil_nest("usr-anonymous.sqlite3");			// should go to /xowa/usr/usr-anonymous.sqlite3
    +		this.app_root_dir = usr_data_dir.GenSubDir_nest("files", "xowa");
    +		String sdcard_rw = activity.Fsys__sdcard_rw_or_null();
    +		if (sdcard_rw != null) {
    +			app_root_dir = Io_url_.lnx_dir_(sdcard_rw + "files/xowa/");
    +		}
    +		log.Info("fsys_mgr:root_dir", "root", app_root_dir.Xto_api());
    +	}
    +	public Io_url Usr_data_dir() {return usr_data_dir;} private Io_url usr_data_dir;		// cleared by "Clear data"; maps to both @gplx.Internal protected and external storage
    +	// private Io_url Usr_temp_dir() {return usr_temp_dir;} private Io_url usr_temp_dir;	// cleared by "Clear cache"
    +	public Io_url Usr_data_fil() {return usr_data_fil;} private Io_url usr_data_fil;
    +	public Io_url App_root_dir() {return app_root_dir;} private Io_url app_root_dir;
    +}
    diff --git a/400_xowa/src/gplx/xowa/drds/ios/assets/Xod_asset_mgr.java b/400_xowa/src/gplx/xowa/drds/ios/assets/Xod_asset_mgr.java
    index a27517de8..739c3e170 100644
    --- a/400_xowa/src/gplx/xowa/drds/ios/assets/Xod_asset_mgr.java
    +++ b/400_xowa/src/gplx/xowa/drds/ios/assets/Xod_asset_mgr.java
    @@ -13,3 +13,7 @@ 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.drds.ios.assets; import gplx.*; import gplx.xowa.*; import gplx.xowa.drds.*; import gplx.xowa.drds.ios.*;
    +public interface Xod_asset_mgr extends gplx.core.ios.loaders.Io_loader {
    +	String[]	List_as_str_ary	(Io_url dir);
    +}
    diff --git a/400_xowa/src/gplx/xowa/drds/ios/media_scanners/Xod_media_scanner.java b/400_xowa/src/gplx/xowa/drds/ios/media_scanners/Xod_media_scanner.java
    index a27517de8..8529ccb67 100644
    --- a/400_xowa/src/gplx/xowa/drds/ios/media_scanners/Xod_media_scanner.java
    +++ b/400_xowa/src/gplx/xowa/drds/ios/media_scanners/Xod_media_scanner.java
    @@ -13,3 +13,8 @@ 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.drds.ios.media_scanners; import gplx.*; import gplx.xowa.*; import gplx.xowa.drds.*; import gplx.xowa.drds.ios.*;
    +public interface Xod_media_scanner extends Gfo_evt_itm {
    +	Xod_media_scanner Add(Io_url url);
    +	void Scan();
    +}
    diff --git a/400_xowa/src/gplx/xowa/drds/ios/media_scanners/Xod_media_scanner__base.java b/400_xowa/src/gplx/xowa/drds/ios/media_scanners/Xod_media_scanner__base.java
    index a27517de8..0816b8453 100644
    --- a/400_xowa/src/gplx/xowa/drds/ios/media_scanners/Xod_media_scanner__base.java
    +++ b/400_xowa/src/gplx/xowa/drds/ios/media_scanners/Xod_media_scanner__base.java
    @@ -13,3 +13,29 @@ 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.drds.ios.media_scanners; import gplx.*; import gplx.xowa.*; import gplx.xowa.drds.*; import gplx.xowa.drds.ios.*;
    +public abstract class Xod_media_scanner__base implements Xod_media_scanner {
    +	private final    List_adp list = List_adp_.New();
    +	public Xod_media_scanner__base() {
    +		this.evt_mgr = new Gfo_evt_mgr(this);
    +		Gfo_evt_mgr_.Sub_same(Io_mgr.Instance, Io_mgr.Evt__fil_created, this);
    +	}
    +	public Gfo_evt_mgr Evt_mgr() {return evt_mgr;} private final    Gfo_evt_mgr evt_mgr;
    +	public Xod_media_scanner Add(Io_url url) {list.Add(url.Xto_api()); return this;}
    +	public void Scan() {
    +		String[] urls = list.To_str_ary_and_clear();
    +		Gfo_log_.Instance.Info("xo.io:media scan", "urls", String_.Concat_with_str(":", urls));
    +		this.Scan__hook(urls);
    +	}
    +	protected abstract void Scan__hook(String[] urls);
    +	private void On_fil_created(Io_url[] ary) {
    +		for (Io_url itm : ary)
    +			this.Add(itm);
    +		this.Scan();
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Io_mgr.Evt__fil_created))			On_fil_created((Io_url[])m.ReadObj("v"));
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/drds/pages/Xod_page_itm.java b/400_xowa/src/gplx/xowa/drds/pages/Xod_page_itm.java
    index a27517de8..0ae441aa4 100644
    --- a/400_xowa/src/gplx/xowa/drds/pages/Xod_page_itm.java
    +++ b/400_xowa/src/gplx/xowa/drds/pages/Xod_page_itm.java
    @@ -13,3 +13,82 @@ 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.drds.pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.drds.*;
    +import gplx.xowa.htmls.*; import gplx.xowa.htmls.sections.*;
    +import gplx.xowa.wikis.data.tbls.*;
    +import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.tags.*;
    +public class Xod_page_itm {
    +	public int Page_id() {return page_id;} private int page_id;
    +	public long Rev_id() {return rev_id;} private long rev_id;
    +	public String Ttl_text() {return ttl_text;} private String ttl_text;
    +	public String Ttl_db() {return ttl_db;} private String ttl_db;
    +	public String Ttl_special() {return ttl_special;} public void Ttl_special_(String v) {ttl_special = v;} private String ttl_special;
    +	public String Redirected() {return redirected;} private String redirected;
    +	public String Description() {return description;} private String description;
    +	public String Modified_on() {return modified_on;} private String modified_on;
    +	public boolean Is_editable() {return is_editable;} private boolean is_editable;
    +	public boolean Is_main_page() {return is_main_page;} private boolean is_main_page;
    +	public boolean Is_disambiguation() {return is_disambiguation;} private boolean is_disambiguation;
    +	public int Lang_count() {return lang_count;} private int lang_count;
    +	public String Head_url() {return head_url;} private String head_url;
    +	public String Head_name() {return head_ttl;} private String head_ttl;
    +	public String First_allowed_editor_role() {return first_allowed_editor_role;} private String first_allowed_editor_role;
    +	public List_adp Section_list() {return section_list;} private List_adp section_list = List_adp_.New();
    +	public Xoh_page Hpg() {return hpg;} private Xoh_page hpg;
    +	public Xopg_tag_mgr Head_tags() {return head_tags;} private final    Xopg_tag_mgr head_tags = new Xopg_tag_mgr(Bool_.Y);
    +	public Xopg_tag_mgr Tail_tags() {return tail_tags;} private final    Xopg_tag_mgr tail_tags = new Xopg_tag_mgr(Bool_.N);
    +	public void Init(int page_id, int rev_id
    +		, String ttl_text, String ttl_db, String redirected, String description, String modified_on
    +		, boolean is_editable, boolean is_main_page, boolean is_disambiguation, int lang_count
    +		, String head_url, String head_ttl
    +		, String first_allowed_editor_role
    +		) {
    +		this.page_id = page_id; this.rev_id = rev_id;
    +		this.ttl_text = ttl_text; this.ttl_db = ttl_db; this.redirected = redirected; this.description = description; this.modified_on = modified_on;
    +		this.is_editable = is_editable; this.is_main_page = is_main_page; this.is_disambiguation = is_disambiguation; this.lang_count = lang_count;
    +		this.head_url = head_url; this.head_ttl= head_ttl; this.first_allowed_editor_role = first_allowed_editor_role;
    +	}
    +	public void Init_by_dbpg(Xoa_ttl ttl, Xowd_page_itm db_page) {
    +		this.page_id = db_page.Id();
    +		this.rev_id = page_id;
    +		this.ttl_text = String_.new_u8(ttl.Page_txt());
    +		this.ttl_db = ttl.Page_db_as_str();
    +		this.modified_on = db_page.Modified_on().XtoStr_fmt_iso_8561_w_tz();
    +		this.lang_count = 1;
    +		this.redirected = null;
    +		this.description = null;
    +		this.is_editable = false;
    +		this.is_main_page = false;
    +		this.is_disambiguation = false;
    +		this.head_url = null;
    +		this.head_ttl = null;
    +		this.first_allowed_editor_role = null;
    +	}
    +	public void Init_by_hpg(Xoh_page hpg) {
    +		this.hpg = hpg;
    +	}
    +	public String To_str() {
    +		Bry_bfr bfr = Bry_bfr_.New();
    +		bfr	.Add_int_variable(page_id).Add_byte_pipe()
    +			.Add_long_variable(rev_id).Add_byte_pipe()
    +			.Add_str_u8(ttl_text).Add_byte_pipe()
    +			.Add_str_u8(ttl_db).Add_byte_pipe()
    +			.Add_str_a7_null(modified_on).Add_byte_pipe()
    +			.Add_int_variable(lang_count).Add_byte_pipe()
    +			.Add_str_a7_null(redirected).Add_byte_pipe()
    +			.Add_str_a7_null(description).Add_byte_pipe()
    +			.Add_yn(is_editable).Add_byte_pipe()
    +			.Add_yn(is_main_page).Add_byte_pipe()
    +			.Add_yn(is_disambiguation).Add_byte_pipe()
    +			.Add_str_a7_null(head_url).Add_byte_pipe()
    +			.Add_str_a7_null(head_ttl).Add_byte_pipe()
    +			.Add_str_a7_null(first_allowed_editor_role).Add_byte_nl()
    +			;
    +		int len = section_list.Count();
    +		for (int i = 0; i < len; ++i) {
    +			Xoh_section_itm section = (Xoh_section_itm)section_list.Get_at(i);
    +			section.To_bfr(bfr);
    +		}
    +		return bfr.To_str_and_clear();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/drds/pages/Xod_page_mgr.java b/400_xowa/src/gplx/xowa/drds/pages/Xod_page_mgr.java
    index a27517de8..181277e73 100644
    --- a/400_xowa/src/gplx/xowa/drds/pages/Xod_page_mgr.java
    +++ b/400_xowa/src/gplx/xowa/drds/pages/Xod_page_mgr.java
    @@ -13,3 +13,65 @@ 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.drds.pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.drds.*;
    +import gplx.core.net.*; import gplx.xowa.addons.wikis.imports.*;
    +import gplx.xowa.wikis.data.tbls.*;
    +import gplx.xowa.htmls.*; import gplx.xowa.htmls.sections.*;
    +import gplx.xowa.wikis.pages.redirects.*;
    +public class Xod_page_mgr {
    +	public Xod_page_itm Get_page(Xow_wiki wiki, Xoa_url page_url) {
    +		Xod_page_itm rv = new Xod_page_itm();
    +
    +		// load meta info like page_id, modified, etc
    +		Xoa_ttl ttl = wiki.Ttl_parse(page_url.Page_bry());
    +		if (ttl.Ns().Id_is_special()) return Load_special(rv, wiki, page_url, ttl);
    +		Xowd_page_itm dbpg = new Xowd_page_itm();
    +		try {wiki.Data__core_mgr().Tbl__page().Select_by_ttl(dbpg, ttl.Ns(), ttl.Page_db());}
    +		catch (Exception e) {// throw detailed exception to track down page_score exception
    +			throw Err_.new_("", "failed to retrieve page", "wiki", wiki.Domain_str(), "page_url", page_url.Page_bry(), "err", Err_.Message_lang(e));
    +		} 
    +		rv.Init_by_dbpg(ttl, dbpg);
    +
    +		// load page data
    +		Xoh_page hpg = new Xoh_page();
    +		hpg.Ctor_by_hview(wiki, Xoa_url.New(wiki, ttl), ttl, 1);
    +		rv.Init_by_hpg(hpg);
    +		wiki.Html__hdump_mgr().Load_mgr().Load_by_xowh(hpg, ttl, Bool_.Y);
    +		Load_sections(rv, hpg);
    +		return rv;
    +	}
    +	private void Load_sections(Xod_page_itm rv, Xoh_page hpg) {
    +		Xoh_section_mgr section_mgr = hpg.Section_mgr();
    +		int len = section_mgr.Len();
    +		for (int i = 0; i < len; ++i) {
    +			Xoh_section_itm itm = section_mgr.Get_at(i);
    +			rv.Section_list().Add(itm);
    +		}
    +	}
    +	private Xod_page_itm Load_special(Xod_page_itm rv, Xow_wiki wiki, Xoa_url url, Xoa_ttl ttl) {
    +		// get prototype
    +		gplx.xowa.specials.Xow_special_page proto = wiki.App().Special_regy().Get_by_or_null(ttl.Page_txt_wo_qargs());
    +		if (proto == null) return rv;	// invalid url
    +
    +		// generate special
    +		Xoh_page page = new Xoh_page();
    +		page.Ctor_by_hview(wiki, Xoa_url.New(wiki, ttl), ttl, 1);	// NOTE: init page to set url, ttl; DATE:2016-06-23
    +		try {proto.Special__clone().Special__gen(wiki, page, url, ttl);}
    +		catch (Exception e) {Gfo_log_.Instance.Warn("failed to generate special page", "url", url.To_str(), "err", Err_.Message_gplx_log(e)); return rv;}
    +
    +		// handle redirects; EX: Special:XowaWikiInfo
    +		Xopg_redirect_itm redirect_itm = page.Redirect_trail().Itms__get_at_nth_or_null();
    +		if (redirect_itm != null)
    +			return Get_page(wiki, redirect_itm.Url());
    +
    +		rv.Init(-1, -1, String_.new_u8(ttl.Page_txt()), String_.new_u8(ttl.Page_db()), null, null, Datetime_now.Get().XtoStr_fmt_iso_8561(), false, false, false, 0, "", "", "");
    +		rv.Init_by_hpg(page);
    +		Xoh_section_itm section = new Xoh_section_itm(1, 1, Bry_.Empty, Bry_.Empty);
    +		section.Content_(page.Html_data().Custom_body());
    +		rv.Section_list().Add(section);
    +		rv.Ttl_special_(String_.new_u8(page.Html_data().Display_ttl()));
    +		rv.Head_tags().Copy(page.Html_data().Custom_head_tags());
    +		rv.Tail_tags().Copy(page.Html_data().Custom_tail_tags());
    +		return rv;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/drds/powers/Xod_power_mgr.java b/400_xowa/src/gplx/xowa/drds/powers/Xod_power_mgr.java
    index a27517de8..54cf81ba0 100644
    --- a/400_xowa/src/gplx/xowa/drds/powers/Xod_power_mgr.java
    +++ b/400_xowa/src/gplx/xowa/drds/powers/Xod_power_mgr.java
    @@ -13,3 +13,8 @@ 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.drds.powers; import gplx.*; import gplx.xowa.*; import gplx.xowa.drds.*;
    +public interface Xod_power_mgr {
    +	void Wake_lock__get(String name);
    +	void Wake_lock__rls(String name);
    +}
    diff --git a/400_xowa/src/gplx/xowa/drds/powers/Xod_power_mgr_.java b/400_xowa/src/gplx/xowa/drds/powers/Xod_power_mgr_.java
    index a27517de8..687954120 100644
    --- a/400_xowa/src/gplx/xowa/drds/powers/Xod_power_mgr_.java
    +++ b/400_xowa/src/gplx/xowa/drds/powers/Xod_power_mgr_.java
    @@ -13,3 +13,18 @@ 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.drds.powers; import gplx.*; import gplx.xowa.*; import gplx.xowa.drds.*;
    +public class Xod_power_mgr_ {
    +	public static Xod_power_mgr Instance = new Xod_power_mgr__shim();
    +}
    +class Xod_power_mgr__shim implements Xod_power_mgr {
    +	// private final    Ordered_hash hash = Ordered_hash_.New();
    +	public void Wake_lock__get(String name) {
    +		// if (hash.Has(name)) {hash.Clear(); throw Err_.new_("itm exists", "name", name);}
    +		// hash.Add(name, name);
    +	}
    +	public void Wake_lock__rls(String name) {
    +		// if (!hash.Has(name)) throw Err_.new_("itm missing", "name", name);
    +		// hash.Del(name);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xoa_file_mgr.java b/400_xowa/src/gplx/xowa/files/Xoa_file_mgr.java
    index a27517de8..9ec419cb8 100644
    --- a/400_xowa/src/gplx/xowa/files/Xoa_file_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/Xoa_file_mgr.java
    @@ -13,3 +13,24 @@ 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.files {
    +//	using gplx.xowa.files.caches;
    +//	public class Xoa_file_mgr {
    +//		private final    List_adp list = List_adp_.New();
    +//		public void Clear() {list.Clear();}
    +//		public boolean Check_cache(Xow_wiki wiki, byte[] page_url, Xof_fsdb_itm fsdb_itm) {
    +//			fsdb_itm.Init_at_cache(0, 0, null);
    +////			Xou_cache_mgr cache_mgr = new Xou_cache_mgr(null, null, null);
    +////			Xou_cache_itm cache_itm = cache_mgr.Get_or_null(fsdb_itm);
    +////			if (cache_itm == null) {
    +////				fsdb_itm.Init_at_cache(Bool_.N_byte, 0, 0, null);
    +////				return false;
    +////			}
    +////			else {
    +////				fsdb_itm.Init_at_cache(Bool_.Y_byte, cache_itm.Html_w(), cache_itm.Html_h(), cache_itm.File_url());
    +////				return true;
    +////			}
    +//			return true;
    +//		}
    +//	}
    +//}
    diff --git a/400_xowa/src/gplx/xowa/files/Xoa_repo_mgr.java b/400_xowa/src/gplx/xowa/files/Xoa_repo_mgr.java
    index a27517de8..b4e412725 100644
    --- a/400_xowa/src/gplx/xowa/files/Xoa_repo_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/Xoa_repo_mgr.java
    @@ -13,3 +13,53 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.xowa.apps.fsys.*; import gplx.xowa.files.exts.*; import gplx.xowa.files.repos.*;
    +public class Xoa_repo_mgr implements Gfo_invk {
    +	private final    Xoa_fsys_mgr app_fsys; private final    Xof_rule_mgr ext_rule_mgr;
    +	public Xoa_repo_mgr(Xoa_fsys_mgr app_fsys, Xof_rule_mgr ext_rule_mgr) {this.app_fsys = app_fsys; this.ext_rule_mgr = ext_rule_mgr;}
    +	public int Count() {return hash.Count();}
    +	public Xof_repo_itm Get_at(int i)		{return (Xof_repo_itm)hash.Get_at(i);}
    +	public Xof_repo_itm Get_by(byte[] key)	{return (Xof_repo_itm)hash.Get_by(key);}
    +	public Xof_repo_itm Get_by_primary(byte[] key)	{
    +		int len = hash.Count();
    +		for (int i = 0; i < len; i++) {
    +			Xof_repo_itm repo = (Xof_repo_itm)hash.Get_at(i);
    +			if (Bry_.Eq(key, repo.Wiki_domain()) && repo.Primary()) return repo;
    +		}
    +		return null;
    +	}
    +	public Xof_repo_itm Get_by_wmf_fsys(byte[] key) {
    +		int len = hash.Count();
    +		for (int i = 0; i < len; i++) {
    +			Xof_repo_itm repo = (Xof_repo_itm)hash.Get_at(i);
    +			if (Bry_.Eq(key, repo.Wiki_domain()) && repo.Wmf_fsys()) return repo;
    +		}
    +		return null;
    +	}
    +	public Xof_repo_itm Get_by_wiki_key(byte[] key)	{
    +		int len = hash.Count();
    +		for (int i = 0; i < len; i++) {
    +			Xof_repo_itm repo = (Xof_repo_itm)hash.Get_at(i);
    +			if (Bry_.Eq(key, repo.Wiki_domain())) return repo;
    +		}
    +		return null;
    +	}
    +	public Xof_repo_itm Add(Xof_repo_itm itm) {hash.Add(itm.Key(), itm); return itm;} private Ordered_hash hash = Ordered_hash_.New_bry();
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_set))			return Set(m.ReadStr("key"), m.ReadStr("url"), m.ReadStr("wiki"));
    +		else	return Gfo_invk_.Rv_unhandled;
    +	}	private static final String Invk_set = "set";
    +	public Xof_repo_itm Set(String key, String url_str, String wiki) {
    +		byte[] key_bry = Bry_.new_u8(key);
    +		Xof_repo_itm itm = (Xof_repo_itm)hash.Get_by(key_bry);
    +		byte[] wiki_domain = Bry_.new_u8(wiki);
    +		if (itm == null) {
    +			itm = new Xof_repo_itm(key_bry, app_fsys, ext_rule_mgr, wiki_domain);
    +			this.Add(itm);
    +		}
    +		itm.Root_str_(url_str);
    +		itm.Wiki_domain_(wiki_domain);
    +		return itm;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_bin_updater.java b/400_xowa/src/gplx/xowa/files/Xof_bin_updater.java
    index a27517de8..c3e1382c6 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_bin_updater.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_bin_updater.java
    @@ -13,3 +13,29 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.core.ios.*; import gplx.core.ios.streams.*;
    +import gplx.fsdb.data.*; import gplx.fsdb.meta.*; import gplx.xowa.files.fsdb.*;
    +public class Xof_bin_updater {
    +	private final    Fsd_thm_itm tmp_thm_itm = Fsd_thm_itm.new_(); 
    +	public int Save_bin(Fsm_mnt_itm mnt, Fsm_atr_fil atr_fil, Fsm_bin_fil bin_fil, Xof_fsdb_itm fsdb, Io_stream_rdr rdr, long rdr_len) {
    +		int db_uid = -1;
    +		int orig_ext_id = fsdb.Orig_ext().Id();
    +		if (fsdb.File_is_orig()) {
    +			if (fsdb.Orig_ext().Id_is_image()) {
    +				Fsd_img_itm img = mnt.Insert_img(atr_fil, bin_fil, fsdb.Orig_repo_name(), fsdb.Orig_ttl(), orig_ext_id, fsdb.Orig_w(), fsdb.Orig_h(), rdr_len, rdr);
    +				db_uid = img.Fil_id();
    +			}
    +			else {	// adds .pdf and .djvu b/c latter does not have w,h for fsdb_img
    +				Fsd_fil_itm fil = mnt.Insert_fil(atr_fil, bin_fil, fsdb.Orig_repo_name(), fsdb.Orig_ttl(), orig_ext_id, rdr_len, rdr);
    +				db_uid = fil.Fil_id();
    +			}
    +		}
    +		else {
    +			mnt.Insert_thm(tmp_thm_itm, atr_fil, bin_fil, fsdb.Orig_repo_name(), fsdb.Orig_ttl(), orig_ext_id, fsdb.Html_w(), fsdb.Html_h(), fsdb.Lnki_time(), fsdb.Lnki_page(), rdr_len, rdr);
    +			db_uid = tmp_thm_itm.Thm_id();
    +		}
    +		mnt.Cfg_mgr().Next_id_commit();
    +		return db_uid;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_cfg_download.java b/400_xowa/src/gplx/xowa/files/Xof_cfg_download.java
    index a27517de8..b5eb15a8c 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_cfg_download.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_cfg_download.java
    @@ -13,3 +13,41 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +public class Xof_cfg_download implements Gfo_invk {
    +	public Xof_cfg_download() {
    +		this.enabled = true;					// changed to true; DATE:2016-12-19; OLD: CFG: set to false b/c some tests only do parsing [[File:A.png]] and repos are not set up
    +		this.redownload = Redownload_none;		// CFG: set to none to be as conservative as possible
    +	}
    +	public boolean Enabled() {return enabled;} public Xof_cfg_download Enabled_(boolean v) {enabled = v; return this;} private boolean enabled;
    +	public byte Redownload() {return redownload;} public Xof_cfg_download Redownload_(byte v) {redownload = v; return this;} private byte redownload;
    +	public void Init_by_wiki(Xow_wiki wiki) {
    +		wiki.App().Cfg().Bind_many_app(this, Cfg__retrieval_enabled);
    +	}
    +
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Cfg__retrieval_enabled))	enabled = m.ReadYn("v");
    +
    +		else if	(ctx.Match(k, Invk_redownload))			return Redownload_to_str_(redownload);
    +		else if	(ctx.Match(k, Invk_redownload_))		redownload = Redownload_parse_(m.ReadStr("v"));
    +		else if	(ctx.Match(k, Invk_redownload_toggle))	redownload = redownload == Redownload_none ? Redownload_missing : Redownload_none;
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}	private static final String Invk_redownload = "redownload", Invk_redownload_ = "redownload_", Invk_redownload_toggle = "redownload_toggle";
    +	byte Redownload_parse_(String s) {
    +		if		(String_.Eq(s, "none"))		return Redownload_none;
    +		else if	(String_.Eq(s, "missing"))	return Redownload_missing;
    +		else if	(String_.Eq(s, "all"))		return Redownload_all;
    +		else								throw Err_.new_unhandled(s);
    +	}
    +	public static final byte Redownload_none = 0, Redownload_missing = 1, Redownload_all = 2; 
    +	String Redownload_to_str_(byte v) {
    +		switch (v) {
    +			case Redownload_none:		return "none";
    +			case Redownload_missing:	return "missing";
    +			case Redownload_all:		return "all";
    +			default:					throw Err_.new_unhandled(v);
    +		}
    +	}
    +	private static final String Cfg__retrieval_enabled = "xowa.files.retrieval_enabled";
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_exec_tid.java b/400_xowa/src/gplx/xowa/files/Xof_exec_tid.java
    index a27517de8..8644a8517 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_exec_tid.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_exec_tid.java
    @@ -13,3 +13,11 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +public class Xof_exec_tid {
    +	public static final int 
    +	  Tid_wiki_page			= 1	// regular page; EX: en.w:Earth
    +	, Tid_wiki_file			= 2	// "File:" ns; EX: en.w:File:A.png
    +	, Tid_viewer_app		= 3	// called by viewer_app
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_ext.java b/400_xowa/src/gplx/xowa/files/Xof_ext.java
    index a27517de8..2dcdce9b7 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_ext.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_ext.java
    @@ -13,3 +13,33 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +public class Xof_ext {
    +	public Xof_ext(int id, byte[] ext) {this.id = id; this.ext = ext;}
    +	public int		Id()					{return id;} private int id;
    +	public byte[]	Ext()					{return ext;} private byte[] ext;
    +	public byte[]	Ext_view()				{return Xof_ext_.Bry__ary[Id_view()];}
    +	public byte[]	Mime_type()				{return Xof_ext_.Mime_type__ary[id];}
    +	public boolean		Id_is_unknown()			{return id == Xof_ext_.Id_unknown;}
    +	public boolean		Id_is_svg()				{return id == Xof_ext_.Id_svg;}
    +	public boolean		Id_is_ogg()				{return id == Xof_ext_.Id_ogg;}
    +	public boolean		Id_is_oga()				{return id == Xof_ext_.Id_oga;}
    +	public boolean		Id_is_ogv()				{return id == Xof_ext_.Id_ogv;}
    +	public boolean		Id_is_djvu()			{return id == Xof_ext_.Id_djvu;}
    +	public boolean		Id_is_pdf()				{return id == Xof_ext_.Id_pdf;}
    +	public boolean		Id_is_image()			{return Xof_ext_.Id_is_image(id);}
    +	public boolean		Id_is_media()			{return Xof_ext_.Id_is_media(id);}
    +	public boolean		Id_is_audio()			{return Xof_ext_.Id_is_audio(id);}
    +	public boolean		Id_is_audio_strict()	{return Xof_ext_.Id_is_audio_strict(id);}
    +	public boolean		Id_is_video()			{return Xof_ext_.Id_is_video(id);}
    +	public boolean		Id_is_thumbable_img()	{return Xof_ext_.Id_is_thumbable_img(id);}
    +	public boolean		Id_supports_page()		{return Xof_ext_.Id_supports_page(id);}
    +	public boolean		Id_needs_convert()		{return Xof_ext_.Id_needs_convert(id);}
    +	public int		Id_view()				{return Xof_ext_.Id_view(id);}
    +	public boolean		Is_not_viewable(int exec_tid) {
    +		return	exec_tid != Xof_exec_tid.Tid_viewer_app		// only apply logic if !Tid_viewer_app; note that if Tid_viewer_app, then user clicked on file, so return true;
    +				&&	(	this.Id_is_audio()					// NOTE: was audio_strict, but v2 always redefines .ogg as .ogv; DATE:2014-02-02
    +					||	id == Xof_ext_.Id_unknown			// ignore unknown exts, else will download needlessly when viewing page; EX: .wav before .wav was registered; PAGE:pl.s:Spiaca_kr�lewna_(Oppman); DATE:2014-08-17
    +					);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_ext_.java b/400_xowa/src/gplx/xowa/files/Xof_ext_.java
    index a27517de8..b8914c4df 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_ext_.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_ext_.java
    @@ -13,3 +13,178 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.core.primitives.*;
    +public class Xof_ext_ {		
    +	public static final int Id_unknown = 0	// SERIALIZED; ids are saved to fsdb;
    +	, Id_png = 1, Id_jpg = 2, Id_jpeg = 3, Id_gif = 4, Id_tif = 5, Id_tiff = 6
    +	, Id_svg = 7, Id_djvu = 8, Id_pdf = 9
    +	, Id_mid = 10, Id_ogg = 11, Id_oga = 12, Id_ogv = 13, Id_webm = 14
    +	, Id_flac = 15, Id_bmp = 16, Id_xcf = 17, Id_wav = 18;
    +	public static final int Id__max = 19;
    +	public static final    byte[] 
    +	  Bry_png = Bry_.new_a7("png"), Bry_jpg = Bry_.new_a7("jpg"), Bry_jpeg = Bry_.new_a7("jpeg")
    +	, Bry_gif = Bry_.new_a7("gif"), Bry_tif = Bry_.new_a7("tif"), Bry_tiff = Bry_.new_a7("tiff")
    +	, Bry_svg = Bry_.new_a7("svg"), Bry_djvu = Bry_.new_a7("djvu"), Bry_pdf = Bry_.new_a7("pdf")
    +	, Bry_mid = Bry_.new_a7("mid"), Bry_ogg = Bry_.new_a7("ogg"), Bry_oga = Bry_.new_a7("oga")
    +	, Bry_ogv = Bry_.new_a7("ogv"), Bry_webm = Bry_.new_a7("webm"), Bry_flac = Bry_.new_a7("flac")
    +	, Bry_bmp = Bry_.new_a7("bmp"), Bry_xcf = Bry_.new_a7("xcf"), Bry_wav = Bry_.new_a7("wav")
    +	;
    +	public static final    byte[][] Bry__ary = new byte[][]
    +	{ Bry_.Empty, Bry_png, Bry_jpg, Bry_jpeg
    +	, Bry_gif, Bry_tif, Bry_tiff
    +	, Bry_svg, Bry_djvu, Bry_pdf
    +	, Bry_mid, Bry_ogg, Bry_oga
    +	, Bry_ogv, Bry_webm, Bry_flac
    +	, Bry_bmp, Bry_xcf, Bry_wav
    +	};
    +	public static final    byte[][] Mime_type__ary = new byte[][] 
    +	{ Bry_.new_a7("application/octet-stream"), Bry_.new_a7("image/png"), Bry_.new_a7("image/jpg"), Bry_.new_a7("image/jpeg")
    +	, Bry_.new_a7("image/gif"), Bry_.new_a7("image/tiff"), Bry_.new_a7("image/tiff")
    +	, Bry_.new_a7("image/svg+xml"), Bry_.new_a7("image/x.djvu"), Bry_.new_a7("application/pdf")
    +	, Bry_.new_a7("application/x-midi"), Bry_.new_a7("video/ogg"), Bry_.new_a7("audio/oga")
    +	, Bry_.new_a7("video/ogg"), Bry_.new_a7("video/webm"), Bry_.new_a7("audio/flac")
    +	, Bry_.new_a7("image/bmp"), Bry_.new_a7("image/xcf"), Bry_.new_a7("audio/x-wav")
    +	};
    +	private static final    Hash_adp id_hash = id_hash_new_();
    +	private static Hash_adp id_hash_new_() {
    +		Hash_adp rv = Hash_adp_bry.cs();
    +		id_hash_new_(rv, Bry_png, Id_png);		id_hash_new_(rv, Bry_jpg, Id_jpg);		id_hash_new_(rv, Bry_jpeg, Id_jpeg);
    +		id_hash_new_(rv, Bry_gif, Id_gif);		id_hash_new_(rv, Bry_tif, Id_tif);		id_hash_new_(rv, Bry_tiff, Id_tiff);
    +		id_hash_new_(rv, Bry_svg, Id_svg);		id_hash_new_(rv, Bry_djvu, Id_djvu);	id_hash_new_(rv, Bry_pdf, Id_pdf);
    +		id_hash_new_(rv, Bry_mid, Id_mid);		id_hash_new_(rv, Bry_ogg, Id_ogg);		id_hash_new_(rv, Bry_oga, Id_oga);
    +		id_hash_new_(rv, Bry_ogv, Id_ogv);		id_hash_new_(rv, Bry_webm, Id_webm);	id_hash_new_(rv, Bry_flac, Id_flac);
    +		id_hash_new_(rv, Bry_bmp, Id_bmp);		id_hash_new_(rv, Bry_xcf, Id_xcf);		id_hash_new_(rv, Bry_wav, Id_wav);
    +		return rv;
    +	}
    +	private static void id_hash_new_(Hash_adp hash, byte[] key, int val) {hash.Add(key, new Int_obj_val(val));}
    +
    +	private static final    Hash_adp_bry ext_hash = Hash_adp_bry.ci_a7()
    +	.Add_bry_bry(Bry_png).Add_bry_bry(Bry_jpg).Add_bry_bry(Bry_jpeg)
    +	.Add_bry_bry(Bry_gif).Add_bry_bry(Bry_tif).Add_bry_bry(Bry_tiff)
    +	.Add_bry_bry(Bry_svg).Add_bry_bry(Bry_djvu).Add_bry_bry(Bry_pdf)
    +	.Add_bry_bry(Bry_mid).Add_bry_bry(Bry_ogg).Add_bry_bry(Bry_oga)
    +	.Add_bry_bry(Bry_ogv).Add_bry_bry(Bry_webm).Add_bry_bry(Bry_flac)
    +	.Add_bry_bry(Bry_bmp).Add_bry_bry(Bry_xcf).Add_bry_bry(Bry_wav)
    +	;
    +	private static final    Xof_ext[] Ary = new Xof_ext[Id__max];
    +
    +	public static byte[] Get_ext_by_id_(int id) {
    +		if (id < 0 || id >= Id__max) throw Err_.new_wo_type("index out of bounds", "id", id);
    +		return Bry__ary[id];
    +	}
    +	public static int Get_id_by_ext_(byte[] ext_bry) {
    +		Object o = id_hash.Get_by(ext_bry);
    +		return o == null ? Id_unknown : ((Int_obj_val)o).Val();
    +	}
    +	public static Xof_ext new_by_ttl_(byte[] ttl) {
    +		int ttl_len = ttl.length;
    +		int dot_pos = Bry_find_.Find_bwd(ttl, Byte_ascii.Dot);
    +		byte[] ext = (dot_pos == Bry_find_.Not_found || dot_pos == ttl_len) ? Bry_.Empty : Bry_.Lcase__all(ttl, dot_pos + 1, ttl_len); // +1 to bgn after .
    +		return new_(Get_id_by_ext_(ext), ext);
    +	}
    +	public static Xof_ext new_by_ext_(byte[] ext)	{return new_(Get_id_by_ext_(ext), ext);}
    +	public static Xof_ext new_by_id_(int id)		{return new_(id, Get_ext_by_id_(id));}
    +	public static Xof_ext new_(int id, byte[] ext) {
    +		Xof_ext rv = Ary[id];
    +		if (rv == null) {
    +			rv = new Xof_ext(id, ext);
    +			Ary[id] = rv;
    +		}
    +		return rv;
    +	}
    +	public static byte[] Lower_ext(byte[] ttl) {
    +		int dot_pos = Bry_find_.Find_bwd(ttl, Byte_ascii.Dot);
    +		int ttl_len = ttl.length;
    +		if (dot_pos == Bry_find_.Not_found || dot_pos == ttl_len - 1) return ttl;
    +		Object o = ext_hash.Get_by_mid(ttl, dot_pos + 1, ttl_len);
    +		if (o == null) return ttl;
    +		byte[] ext = (byte[])o;
    +		boolean match = Bry_.Match(ttl, dot_pos, ttl_len, ext);
    +		if (match) return ttl;
    +		int ext_len = ext.length;
    +		for (int i = 0; i < ext_len; i++)
    +			ttl[i + dot_pos + 1] = ext[i];
    +		return ttl;
    +	}
    +	public static boolean Orig_file_is_img(int v) {	// identifies if orig_file can be used for ; EX: png is valid, but svg, ogv, pdf is not
    +		switch (v) {
    +			case Id_png: case Id_jpg: case Id_jpeg:
    +			case Id_gif: case Id_tif: case Id_tiff:				return true;
    +			default:											return false;
    +		}
    +	}
    +	public static boolean Id_supports_page(int v) {	// identifies if tid supports page in lnki; EX: [[File:A.pdf|page=1]]; REF: https://en.wikipedia.org/wiki/Wikipedia:Picture_tutorial; DATE:2014-01-18
    +		switch (v) {
    +			case Id_pdf: case Id_djvu:							return true;
    +			default:											return false;
    +		}
    +	}
    +	public static boolean Id_supports_time(int v) {	// identifies if tid supports thumbtime in lnki; EX: [[File:A.ogv|thumbtime=1]]; 
    +		switch (v) {
    +			case Id_ogg: case Id_ogv: case Id_webm:				return true;
    +			default:											return false;
    +		}
    +	}
    +	public static boolean Id_is_image(int id) {
    +		switch (id) {
    +			case Xof_ext_.Id_png: case Xof_ext_.Id_jpg: case Xof_ext_.Id_jpeg:
    +			case Xof_ext_.Id_gif: case Xof_ext_.Id_tif: case Xof_ext_.Id_tiff:
    +			case Xof_ext_.Id_svg:
    +			case Xof_ext_.Id_bmp: case Xof_ext_.Id_xcf:
    +				return true;
    +			default:
    +				return false;
    +		}
    +	}
    +	public static boolean Id_is_image_wo_svg(int id) {	// same as Id_is_image, but ignore svg
    +		switch (id) {
    +			case Xof_ext_.Id_png: case Xof_ext_.Id_jpg: case Xof_ext_.Id_jpeg:
    +			case Xof_ext_.Id_gif: case Xof_ext_.Id_tif: case Xof_ext_.Id_tiff:
    +			case Xof_ext_.Id_bmp: case Xof_ext_.Id_xcf:
    +				return true;
    +			default:
    +				return false;
    +		}
    +	}
    +	public static boolean Id_is_thumbable_img(int id) {
    +		switch (id) {
    +			case Xof_ext_.Id_png: case Xof_ext_.Id_jpg: case Xof_ext_.Id_jpeg:
    +			case Xof_ext_.Id_gif: case Xof_ext_.Id_tif: case Xof_ext_.Id_tiff:
    +			case Xof_ext_.Id_svg: case Xof_ext_.Id_djvu: case Xof_ext_.Id_pdf:
    +			case Xof_ext_.Id_bmp: case Xof_ext_.Id_xcf:
    +				return true;
    +			default:
    +				return false;
    +		}
    +	}
    +	public static boolean Id_is_audio(int id) {
    +		switch (id) {
    +			case Xof_ext_.Id_mid: case Xof_ext_.Id_oga: case Xof_ext_.Id_flac: case Xof_ext_.Id_ogg: case Xof_ext_.Id_wav: return true;
    +			default: return false;
    +		}
    +	}
    +	public static boolean Id_is_video(int id) {return id == Xof_ext_.Id_ogv || id == Xof_ext_.Id_ogg || id == Xof_ext_.Id_webm;}	// NOTE: ogg can be vid; PAGE:en.w:Comet; Encke_tail_rip_off.ogg
    +	public static boolean Id_is_video_strict(int id) {return id == Xof_ext_.Id_ogv || id == Xof_ext_.Id_webm;}	// NOTE: ogg can be aud / vid; PAGE:en.w:Comet; Encke_tail_rip_off.ogg
    +	public static boolean Id_is_audio_strict(int id) {	// same as above, but deliberately exclude ambiguous ogg
    +		switch (id) {
    +			case Xof_ext_.Id_mid: case Xof_ext_.Id_oga: case Xof_ext_.Id_flac: case Xof_ext_.Id_wav: return true;
    +			default: return false;
    +		}
    +	}
    +	public static boolean Id_is_media(int id) {return Id_is_audio(id) || Id_is_video(id);}
    +	public static boolean Id_needs_convert(int id) {
    +		switch (id) {
    +			case Xof_ext_.Id_svg: case Xof_ext_.Id_djvu: case Xof_ext_.Id_pdf: return true;
    +			default: return false;
    +		}
    +	}
    +	public static int Id_view(int id) {
    +		switch (id) {
    +			case Xof_ext_.Id_svg: case Xof_ext_.Id_bmp: case Xof_ext_.Id_xcf:								return Xof_ext_.Id_png;
    +			case Xof_ext_.Id_tif: case Xof_ext_.Id_tiff: case Xof_ext_.Id_djvu: case Xof_ext_.Id_pdf:
    +			case Xof_ext_.Id_ogg: case Xof_ext_.Id_ogv: case Xof_ext_.Id_webm:								return Xof_ext_.Id_jpg;
    +			default:																						return id;
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_file_fxt.java b/400_xowa/src/gplx/xowa/files/Xof_file_fxt.java
    index a27517de8..8631d1830 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_file_fxt.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_file_fxt.java
    @@ -13,3 +13,36 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.dbs.*;
    +import gplx.xowa.files.origs.*; import gplx.xowa.files.repos.*;
    +public class Xof_file_fxt {
    +	private final    Xowe_wiki wiki;
    +	Xof_file_fxt(Xowe_wiki wiki) {
    +		this.wiki = wiki;
    +		Io_mgr.Instance.InitEngine_mem();
    +		Db_conn_bldr.Instance.Reg_default_mem();
    +		wiki.File_mgr().Version_2_y_();
    +	}
    +	public static Xof_file_fxt new_(Xowe_wiki wiki) {return new Xof_file_fxt(wiki);}
    +	public static Xof_file_fxt new_all(Xowe_wiki wiki) {return new Xof_file_fxt(wiki).Init_repos().Init_cache().Init_orig();}
    +	public Xof_file_fxt Init_repos() {
    +		Xow_repo_mgr_.Assert_repos(wiki.Appe(), wiki);
    +		return this;
    +	}
    +	public Xof_file_fxt Init_cache() {
    +		wiki.App().User().User_db_mgr().Init_by_app(Bool_.N, wiki.App().Fsys_mgr().Root_dir().GenSubFil_nest("user", "xowa.user.anonymous.sqlite3"));
    +		return this;
    +	}
    +	public Xof_file_fxt Init_orig() {
    +		Db_conn conn = Db_conn_bldr.Instance.Get_or_new(Io_url_.mem_fil_("mem/xowa/wiki/" + wiki.Domain_str() + "/orig.xowa")).Conn();
    +		Xof_orig_tbl orig_tbl = new Xof_orig_tbl(conn, Bool_.Y);
    +		orig_tbl.Create_tbl();
    +		wiki.File_mgr().Orig_mgr().Init_by_wiki(wiki, Xof_fsdb_mode.New__v2__gui(), new Xof_orig_tbl[] {orig_tbl}, Xof_url_bldr.new_v2());
    +		return this;
    +	}
    +	public void Exec_orig_add(boolean repo_is_commons, String orig_ttl, int orig_ext_id, int orig_w, int orig_h, String orig_redirect) {
    +		byte repo_tid = repo_is_commons ? Xof_repo_tid_.Tid__remote : Xof_repo_tid_.Tid__local;
    +		wiki.File_mgr().Orig_mgr().Insert(repo_tid, Bry_.new_u8(orig_ttl), orig_ext_id, orig_w, orig_h, Bry_.new_u8(orig_redirect));
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_file_itm.java b/400_xowa/src/gplx/xowa/files/Xof_file_itm.java
    index a27517de8..c4157c79a 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_file_itm.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_file_itm.java
    @@ -13,3 +13,54 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.xowa.guis.cbks.js.*; import gplx.xowa.files.repos.*;
    +public interface Xof_file_itm {
    +	int					Lnki_exec_tid();
    +	byte[]				Lnki_wiki_abrv();
    +	byte[]				Lnki_ttl();
    +	byte 				Lnki_type();
    +	double				Lnki_upright();
    +	int					Lnki_w();
    +	int					Lnki_h();
    +	double				Lnki_time();
    +	int					Lnki_page();
    +	boolean				Orig_exists();
    +	byte				Orig_repo_id();
    +	byte[]				Orig_repo_name();
    +	byte[]				Orig_ttl();
    +	byte[]				Orig_ttl_md5();
    +	Xof_ext				Orig_ext();
    +	int					Orig_w();
    +	int					Orig_h();
    +	byte[]				Orig_redirect();
    +	boolean				File_is_orig();
    +	int					File_w();
    +	boolean				File_exists();
    +	boolean				File_exists_in_cache();
    +	int					Html_uid();
    +	byte				Html_elem_tid();
    +	int					Html_w();
    +	int					Html_h();
    +	Io_url				Html_view_url();
    +	Io_url				Html_orig_url();
    +	int					Html_gallery_mgr_h();
    +	Js_img_wkr			Html_img_wkr();
    +	int					Hdump_mode();
    +
    +	void				File_exists_(boolean v);
    +	void				Html_orig_url_(Io_url v);
    +	void				Html_img_wkr_(Js_img_wkr v);
    +	void				Html_elem_tid_(byte v);
    +	void				Html_size_(int w, int h);
    +	void				Html_gallery_mgr_h_(int h);
    +
    +	void				Init_at_lnki(int exec_tid, byte[] wiki_abrv, byte[] ttl, byte lnki_type, double upright, int w, int h, double time, int page, int lnki_upright_patch);
    +	void				Init_at_orig(byte orig_repo_id, byte[] orig_repo_name, byte[] orig_ttl, Xof_ext orig_ext, int orig_w, int orig_h, byte[] orig_redirect);
    +	void				Init_at_html(int exec_tid, Xof_img_size img_size, Xof_repo_itm repo, Xof_url_bldr url_bldr);
    +	void				Init_at_hdoc(int html_uid, byte html_elem_tid);
    +	void				Init_at_gallery_bgn(int html_w, int html_h, int file_w);
    +	void				Init_at_gallery_end(int html_w, int html_h, Io_url html_view_url, Io_url html_orig_url);
    +
    +	boolean				Dbmeta_is_new();
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_file_mgr.java b/400_xowa/src/gplx/xowa/files/Xof_file_mgr.java
    index a27517de8..807a2b066 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_file_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_file_mgr.java
    @@ -13,3 +13,27 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.dbs.*;
    +import gplx.xowa.files.commons.*; import gplx.xowa.files.exts.*; import gplx.xowa.files.caches.*; import gplx.xowa.files.imgs.*;
    +import gplx.xowa.bldrs.wms.*;
    +import gplx.xowa.xtns.math.*;
    +public class Xof_file_mgr implements Gfo_invk {
    +	public Xoa_repo_mgr			Repo_mgr() {return repo_mgr;} private Xoa_repo_mgr repo_mgr;
    +	public Xof_img_mgr			Img_mgr() {return img_mgr;} private final    Xof_img_mgr img_mgr = new Xof_img_mgr();
    +	public Xof_cache_mgr		Cache_mgr() {return cache_mgr;} private Xof_cache_mgr cache_mgr;
    +	public Xof_rule_mgr			Ext_rules() {return ext_rules;} private final    Xof_rule_mgr ext_rules = new Xof_rule_mgr();
    +	public void Ctor_by_app(Xoae_app app) {
    +		Gfo_usr_dlg usr_dlg = app.Usr_dlg();
    +		this.cache_mgr = new Xof_cache_mgr(usr_dlg, app.Wiki_mgr(), repo_mgr);
    +		this.repo_mgr = new Xoa_repo_mgr(app.Fsys_mgr(), ext_rules); 
    +		img_mgr.Init_by_app(app.Wmf_mgr(), app.Prog_mgr());
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_repos))				return repo_mgr;
    +		else if	(ctx.Match(k, Invk_img_mgr))			return img_mgr;
    +		else if	(ctx.Match(k, Invk_ext_rules))			return ext_rules;
    +		else if	(ctx.Match(k, Invk_cache_mgr))			return cache_mgr;
    +		else											return Gfo_invk_.Rv_unhandled;
    +	}	private static final String Invk_repos = "repos", Invk_img_mgr= "img_mgr", Invk_ext_rules = "ext_rules", Invk_cache_mgr = "cache_mgr";
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_file_wkr.java b/400_xowa/src/gplx/xowa/files/Xof_file_wkr.java
    index a27517de8..231733176 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_file_wkr.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_file_wkr.java
    @@ -13,3 +13,123 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.core.threads.*; import gplx.core.ios.*; import gplx.core.ios.streams.*;
    +import gplx.fsdb.*; import gplx.fsdb.meta.*; import gplx.fsdb.data.*; import gplx.xowa.files.fsdb.*; import gplx.xowa.files.imgs.*;
    +import gplx.xowa.files.repos.*; import gplx.xowa.files.origs.*; import gplx.xowa.files.bins.*; import gplx.xowa.files.caches.*; import gplx.xowa.guis.cbks.js.*;
    +public class Xof_file_wkr implements Gfo_thread_wkr {
    +	private final    Xof_orig_mgr orig_mgr; private final    Xof_bin_mgr bin_mgr; private final    Fsm_mnt_mgr mnt_mgr; private final    Xou_cache_mgr cache_mgr;
    +	private final    Gfo_usr_dlg usr_dlg; private final    Xow_repo_mgr repo_mgr; private final    Xog_js_wkr js_wkr;
    +	private final    Xof_url_bldr url_bldr = Xof_url_bldr.new_v2(); private final    Xof_img_size img_size = new Xof_img_size(); 	
    +	private final    Xoa_page hpg; private final    List_adp imgs;
    +	public Xof_file_wkr(Xof_orig_mgr orig_mgr, Xof_bin_mgr bin_mgr, Fsm_mnt_mgr mnt_mgr, Xou_cache_mgr cache_mgr, Xow_repo_mgr repo_mgr, Xog_js_wkr js_wkr, Xoa_page hpg, List_adp imgs) {
    +		this.orig_mgr = orig_mgr; this.bin_mgr = bin_mgr; this.mnt_mgr = mnt_mgr; this.cache_mgr = cache_mgr;
    +		this.usr_dlg = Gfo_usr_dlg_.Instance; this.repo_mgr = repo_mgr; this.js_wkr = js_wkr;			
    +		this.hpg = hpg; this.imgs = imgs;
    +	}
    +	public String			Thread__name() {return "xowa.load_imgs";}
    +	public boolean			Thread__resume() {return true;}
    +	public void Thread__exec() {
    +		int len = imgs.Count();
    +		for (int i = 0; i < len; ++i)
    +			Exec_by_fsdb((Xof_fsdb_itm)imgs.Get_at(i));
    +		Xoa_app_.Usr_dlg().Prog_none("", "", "");
    +	}
    +	private void Exec_by_fsdb(Xof_fsdb_itm fsdb) {
    +		try {
    +			if (fsdb.File_exists_in_cache()) return;
    +			fsdb.Orig_exists_n_();
    +			Xof_orig_itm orig = orig_mgr.Find_by_ttl_or_null(fsdb.Lnki_ttl()); if (orig == Xof_orig_itm.Null) return;
    +			Eval_orig(orig, fsdb, url_bldr, repo_mgr, img_size);
    +			Show_img(fsdb, usr_dlg, bin_mgr, mnt_mgr, cache_mgr, repo_mgr, js_wkr, img_size, url_bldr, hpg);
    +		} catch (Exception e) {
    +			usr_dlg.Warn_many("", "", "file.unknown: err=~{0}", Err_.Message_gplx_full(e));
    +		}
    +	}
    +	public static boolean Show_img(Xof_fsdb_itm fsdb, Gfo_usr_dlg usr_dlg, Xof_bin_mgr bin_mgr, Fsm_mnt_mgr mnt_mgr, Xou_cache_mgr cache_mgr, Xow_repo_mgr repo_mgr, Xog_js_wkr js_wkr, Xof_img_size img_size, Xof_url_bldr url_bldr, Xoa_page page) {
    +		try {
    +			usr_dlg.Log_many("", "", "file.get: file=~{0} width=~{1} page=~{2}", fsdb.Orig_ttl(), fsdb.Lnki_w(), page.Ttl().Full_db());
    +			if (fsdb.Orig_ext() == null) {
    +				usr_dlg.Warn_many("", "", "file.missing.ext: file=~{0} width=~{1} page=~{2}", fsdb.Orig_ttl(), fsdb.Lnki_w(), page.Ttl().Full_db());
    +				return false;
    +			}
    +			Xof_repo_itm repo = repo_mgr.Get_trg_by_id_or_null(fsdb.Orig_repo_id(), fsdb.Orig_ttl(), page.Url_bry_safe());
    +			if (repo == null) return false;
    +			if (fsdb.Hdump_mode() == Xof_fsdb_itm.Hdump_mode__null)
    +				fsdb.Init_at_html(fsdb.Lnki_exec_tid(), img_size, repo, url_bldr);
    +			if (fsdb.Orig_ext().Is_not_viewable(fsdb.Lnki_exec_tid())) return false;	// file not viewable; exit; EX: exec_tid = page and fsdb is audio
    +			IoItmFil file = Io_mgr.Instance.QueryFil(fsdb.Html_view_url());
    +			if (!file.Exists()) {
    +				if (bin_mgr.Find_to_url_as_bool(fsdb.Lnki_exec_tid(), fsdb)) {
    +					if (fsdb.Fsdb_insert()) Save_bin(fsdb, mnt_mgr, fsdb.Html_view_url());
    +				}
    +				else {
    +					boolean pass = false;
    +					if (fsdb.Lnki_exec_tid() == Xof_exec_tid.Tid_wiki_file) {
    +						pass = Show_img_near(fsdb, bin_mgr, repo_mgr, page, img_size, url_bldr, js_wkr);
    +					}
    +					if (!pass) {
    +						usr_dlg.Warn_many("", "", "file.missing.bin: file=~{0} width=~{1} page=~{2}", fsdb.Orig_ttl(), fsdb.Lnki_w(), page.Ttl().Full_db());
    +						fsdb.File_exists_n_();
    +						// gplx.xowa.files.gui.Js_img_mgr.Update_img_missing(usr_dlg, fsdb.Html_uid());	// TODO_OLD: update caption with "" if image is missing
    +						return false;
    +					}
    +				}
    +			}
    +			else {
    +				fsdb.File_exists_y_();
    +				fsdb.File_size_(file.Size());
    +			}
    +			Js_img_mgr.Update_img(page, js_wkr, fsdb);
    +			if (cache_mgr != null) cache_mgr.Update(fsdb);	// cache_mgr null during tests;
    +			return true;
    +		} catch (Exception e) {
    +			usr_dlg.Warn_many("", "", "file.unknown: err=~{0}", Err_.Message_gplx_full(e));
    +			return false;
    +		}
    +	}
    +	private static boolean Show_img_near(Xof_fsdb_itm fsdb, Xof_bin_mgr bin_mgr, Xow_repo_mgr repo_mgr, Xoa_page page, Xof_img_size img_size, Xof_url_bldr url_bldr, Xog_js_wkr js_wkr) {
    +		Xof_bin_wkr__fsdb_sql fsdb_sql_wkr = (Xof_bin_wkr__fsdb_sql)bin_mgr.Wkrs__get_or_null(Xof_bin_wkr_.Key_fsdb_wiki);
    +		if (fsdb_sql_wkr != null) {
    +			Io_stream_rdr file_rdr = fsdb_sql_wkr.Get_to_fsys_near(fsdb, fsdb.Orig_repo_name(), fsdb.Orig_ttl(), fsdb.Orig_ext(), fsdb.Lnki_time(), fsdb.Lnki_page());
    +			try {
    +				if (file_rdr != Io_stream_rdr_.Noop) {
    +					Xof_repo_itm repo = repo_mgr.Get_trg_by_id_or_null(fsdb.Orig_repo_id(), fsdb.Lnki_ttl(), page.Url_bry_safe());
    +					Io_url file_url = url_bldr.Init_for_trg_file(repo, Xof_img_mode_.By_bool(!fsdb.File_is_orig()), fsdb.Orig_ttl(), fsdb.Orig_ttl_md5(), fsdb.Orig_ext(), fsdb.File_w(), fsdb.Lnki_time(), fsdb.Lnki_page()).Xto_url();
    +					Io_stream_wtr_.Save_rdr(file_url, file_rdr, Io_download_fmt.Null);
    +					fsdb.File_size_(file_rdr.Len());			// must update file size for cache
    +					fsdb.Init_at_lnki_by_near(fsdb.File_w());	// change lnki to be file_w,-1
    +					fsdb.Init_at_html(fsdb.Lnki_exec_tid(), img_size, repo, url_bldr);
    +					return true;
    +				}
    +			} finally {
    +				file_rdr.Rls();
    +			}
    +		}
    +		return false;
    +	}
    +	public static void Eval_orig(Xof_orig_itm orig, Xof_fsdb_itm fsdb, Xof_url_bldr url_bldr, Xow_repo_mgr repo_mgr, Xof_img_size img_size) {
    +		fsdb.Orig_exists_y_();
    +		byte repo_id = orig.Repo();
    +		Xof_repo_itm repo_itm = repo_mgr.Get_trg_by_id_or_null(repo_id, fsdb.Lnki_ttl(), Bry_.Empty);
    +		if (repo_itm == null) return;
    +		fsdb.Init_at_orig(repo_id, repo_itm.Wiki_domain(), orig.Ttl(), orig.Ext(), orig.W(), orig.H(), orig.Redirect());
    +		fsdb.Init_at_html(fsdb.Lnki_exec_tid(), img_size, repo_itm, url_bldr);
    +	}
    +	public static void Save_bin(Xof_fsdb_itm itm, Fsm_mnt_mgr mnt_mgr, Io_url html_url) {
    +		long rdr_len = Io_mgr.Instance.QueryFil(html_url).Size();
    +		Io_stream_rdr rdr = gplx.core.ios.streams.Io_stream_rdr_.New__raw(html_url);
    +		try {
    +			rdr.Open();
    +			Fsm_mnt_itm mnt_itm = mnt_mgr.Mnts__get_insert();
    +			Fsm_atr_fil atr_fil = mnt_itm.Atr_mgr().Db__core();
    +			Fsm_bin_fil bin_fil = mnt_itm.Bin_mgr().Dbs__get_nth();
    +			Xof_bin_updater bin_updater = new Xof_bin_updater();
    +			bin_updater.Save_bin(mnt_itm, atr_fil, bin_fil, itm, rdr, rdr_len);
    +		}
    +		catch (Exception e) {
    +			Xoa_app_.Usr_dlg().Warn_many("", "", "failed to save file: ttl=~{0} url=~{1} err=~{2}", itm.Orig_ttl(), html_url.Raw(), Err_.Message_gplx_full(e));
    +		}
    +		finally {rdr.Rls();}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_file_wkr_.java b/400_xowa/src/gplx/xowa/files/Xof_file_wkr_.java
    index a27517de8..94281ee0b 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_file_wkr_.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_file_wkr_.java
    @@ -13,3 +13,46 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.core.consoles.*; import gplx.langs.htmls.encoders.*;
    +public class Xof_file_wkr_ {
    +	private static final    gplx.core.security.Hash_algo md5_hash = gplx.core.security.Hash_algo_.New__md5();
    +	public static final    Gfo_url_encoder Md5_decoder = Gfo_url_encoder_.New__http_url().Init__same__many(Byte_ascii.Plus).Make();
    +	public static byte[] Md5_fast(byte[] v) {
    +		synchronized (md5_hash) {
    +			return md5_hash.Hash_bry_as_bry(v);
    +		}
    +	}
    +	public static byte[] Md5(byte[] ttl) {
    +		synchronized (md5_hash) {
    +			ttl = Md5_decoder.Decode(Ttl_standardize(ttl));
    +			return Xof_file_wkr_.Md5_fast(ttl);					// NOTE: md5 is calculated off of url_decoded ttl; EX: A%2Cb is converted to A,b and then md5'd. note that A%2Cb still remains the title
    +		}
    +	}
    +	public static byte[] Ttl_standardize(byte[] src) {
    +		int len = src.length; if (len == 0) return src;
    +		byte[] rv = null;
    +		boolean dirty = false;
    +		byte b = src[0];
    +		if (b > 96 && b < 123) {
    +			dirty = true;
    +			rv = new byte[len];
    +			rv[0] = (byte)(b - 32);	// NOTE: [[File:]] automatically uppercases 1st letter for md5; EX:en.d:File:wikiquote-logo.png has md5 of "32" (W...) not "82" (w...); PAGE:en.d:freedom_of_speech DATE:2016-01-21
    +		}
    +		for (int i = 1; i < len; ++i) {
    +			b = src[i];
    +			if (b == Byte_ascii.Space) {
    +				if (!dirty) {
    +					dirty = true;
    +					rv = new byte[len]; Bry_.Copy_by_pos(src, 0, i, rv, 0);
    +				}
    +				rv[i] = Byte_ascii.Underline;
    +			}
    +			else {
    +				if (dirty)
    +					rv[i] = b;
    +			}
    +		}
    +		return dirty ? rv : src;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_file_wkr__tst.java b/400_xowa/src/gplx/xowa/files/Xof_file_wkr__tst.java
    index a27517de8..81f7298b0 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_file_wkr__tst.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_file_wkr__tst.java
    @@ -13,3 +13,19 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import org.junit.*;
    +public class Xof_file_wkr__tst {		
    +	private final    Xof_file_wkr___fxt fxt = new Xof_file_wkr___fxt();
    +	@Test 	public void Ttl_standardize() {
    +		fxt.Test__ttl_standardize("Abc.png"		, "Abc.png");		// basic
    +		fxt.Test__ttl_standardize("A b.png"		, "A_b.png");		// spaces -> unders
    +		fxt.Test__ttl_standardize("A b c.png"	, "A_b_c.png");		// spaces -> unders; multiple
    +		fxt.Test__ttl_standardize("abc.png"		, "Abc.png");		// ucase 1st
    +	}
    +}
    +class Xof_file_wkr___fxt {
    +	public void Test__ttl_standardize(String src_str, String expd) {
    +		Tfds.Eq_bry(Bry_.new_u8(expd), Xof_file_wkr_.Ttl_standardize(Bry_.new_u8(src_str)));
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_fsdb_itm.java b/400_xowa/src/gplx/xowa/files/Xof_fsdb_itm.java
    index a27517de8..946e09ffa 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_fsdb_itm.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_fsdb_itm.java
    @@ -13,3 +13,181 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.core.ios.*;
    +import gplx.xowa.guis.cbks.js.*; import gplx.xowa.files.repos.*;
    +import gplx.xowa.parsers.lnkis.*;
    +public class Xof_fsdb_itm implements Xof_file_itm {
    +	private int lnki_upright_patch;
    +	public byte[]				Lnki_wiki_abrv()			{return lnki_wiki_abrv;} private byte[] lnki_wiki_abrv;
    +	public int					Lnki_exec_tid()				{return lnki_exec_tid;} private int lnki_exec_tid;
    +	public byte[]				Lnki_ttl()					{return lnki_ttl;} private byte[] lnki_ttl;
    +	public byte					Lnki_type()					{return lnki_type;} private byte lnki_type;
    +	public double				Lnki_upright()				{return lnki_upright;} private double lnki_upright;
    +	public int					Lnki_w()					{return lnki_w;} private int lnki_w;
    +	public int					Lnki_h()					{return lnki_h;} private int lnki_h;
    +	public double				Lnki_time()					{return lnki_time;} private double lnki_time = Xof_lnki_time.Null;
    +	public int					Lnki_page()					{return lnki_page;} private int lnki_page = Xof_lnki_page.Null;
    +	public int					User_thumb_w()				{return user_thumb_w;} private int user_thumb_w = Xof_img_size.Thumb_width_img;
    +	public byte					Orig_repo_id()				{return orig_repo_id;} private byte orig_repo_id = Xof_repo_tid_.Tid__null;
    +	public byte[]				Orig_repo_name()			{return orig_repo_name;} private byte[] orig_repo_name;
    +	public byte[]				Orig_ttl()					{return orig_ttl;} private byte[] orig_ttl;
    +	public byte[]				Orig_ttl_md5()				{return orig_ttl_md5;} private byte[] orig_ttl_md5;
    +	public Xof_ext				Orig_ext()					{return orig_ext;} private Xof_ext orig_ext;
    +	public byte[]				Orig_redirect()				{return orig_redirect;} private byte[] orig_redirect;
    +	public int					Orig_w()					{return orig_w;} private int orig_w = Xop_lnki_tkn.Width_null;
    +	public int					Orig_h()					{return orig_h;} private int orig_h = Xop_lnki_tkn.Height_null;
    +	public int					Html_uid()					{return html_uid;} private int html_uid;
    +	public byte					Html_elem_tid()				{return html_elem_tid;} private byte html_elem_tid;
    +	public int					Html_w()					{return html_w;} private int html_w;
    +	public int					Html_h()					{return html_h;} private int html_h;
    +	public Io_url				Html_view_url()				{return html_view_url;} private Io_url html_view_url;
    +	public Io_url				Html_orig_url()				{return html_orig_url;} private Io_url html_orig_url = Io_url_.Empty;
    +	public int					Html_gallery_mgr_h()		{return html_gallery_mgr_h;} private int html_gallery_mgr_h = Int_.Neg1;
    +	public Js_img_wkr			Html_img_wkr()				{return html_img_wkr;} private Js_img_wkr html_img_wkr;
    +	public boolean				File_is_orig()				{return file_is_orig;} private boolean file_is_orig;
    +	public int					File_w()					{return file_w;} private int file_w;
    +	public long					File_size()					{return file_size;} private long file_size;
    +	public boolean				Dbmeta_is_new()				{return false;}
    +	public boolean				Orig_exists()				{return orig_exists;} public void Orig_exists_y_() {orig_exists = Bool_.Y;} public void Orig_exists_n_() {orig_exists = Bool_.N;} private boolean orig_exists;
    +	public boolean				File_exists()				{return file_exists;} public void File_exists_y_() {file_exists = Bool_.Y;} public void File_exists_n_() {file_exists = Bool_.N;} public void File_exists_(boolean v) {file_exists = v;} private boolean file_exists;
    +	public boolean 				File_exists_in_cache()		{return file_exists_in_cache;} private boolean file_exists_in_cache;
    +	public boolean				File_resized()				{return file_resized;} public void File_resized_y_() {file_resized = Bool_.Y;} private boolean file_resized;
    +	public boolean				Fsdb_insert()				{return fsdb_insert;} public void Fsdb_insert_y_() {fsdb_insert = true;} private boolean fsdb_insert;
    +	public int					Hdump_mode()				{return hdump_mode;} private int hdump_mode = Hdump_mode__null;
    +	public int					Xfer_idx()					{return xfer_idx;} private int xfer_idx;
    +	public int					Xfer_len()					{return xfer_len;} private int xfer_len;
    +	public void	Init_at_lnki(int exec_tid, byte[] wiki_abrv, byte[] lnki_ttl, byte lnki_type, double lnki_upright, int lnki_w, int lnki_h, double lnki_time, int lnki_page, int lnki_upright_patch) {
    +		this.lnki_exec_tid = exec_tid; this.lnki_wiki_abrv = wiki_abrv;
    +		this.lnki_type = lnki_type; this.lnki_upright = lnki_upright; this.lnki_upright_patch = lnki_upright_patch;
    +		this.lnki_w = lnki_w; this.lnki_h = lnki_h; this.lnki_time = lnki_time; this.lnki_page = lnki_page;			
    +		this.lnki_ttl = lnki_ttl;
    +		this.Orig_ttl_(lnki_ttl);	// default orig_ttl to lnki_ttl, since there are some classes that don't call Ctor_by_orig
    +		this.orig_ext = Xof_ext_.new_by_ttl_(lnki_ttl);
    +	}
    +	public void	Init_at_orig(byte orig_repo_id, byte[] orig_repo_name, byte[] orig_ttl, Xof_ext orig_ext, int orig_w, int orig_h, byte[] orig_redirect) {
    +		this.orig_repo_id = orig_repo_id; this.orig_repo_name = orig_repo_name; this.orig_redirect = orig_redirect;
    +		this.orig_w = orig_w; this.orig_h = orig_h;
    +		if		(Bry_.Len_gt_0(orig_redirect))				// redirect exists; EX: A.png redirected to B.png
    +			this.Orig_ttl_(orig_redirect);					// update fsdb with atrs of B.png
    +		else if	(!Bry_.Eq(lnki_ttl, orig_ttl))				// ttls differ; EX: "A_.png" vs "A.png"
    +			this.Orig_ttl_(orig_ttl);
    +		else
    +			this.Orig_ttl_(orig_ttl);
    +		this.orig_ext = orig_ext;							// NOTE: always use orig_ext since this comes directly from wmf_api; DATE:2015-05-17
    +	}
    +	public void Init_at_lnki_by_near(int file_w) {
    +		this.lnki_w = file_w; this.lnki_h = Xof_img_size.Size__neg1;
    +	}
    +	public void	Init_at_html(int exec_tid, Xof_img_size img_size, Xof_repo_itm repo, Xof_url_bldr url_bldr) {
    +		Calc_html_size(exec_tid, img_size);
    +		this.html_view_url = url_bldr.To_url_trg(repo, this, file_is_orig);
    +		this.html_orig_url = url_bldr.To_url_trg(repo, this, Bool_.Y);
    +	}
    +	public void Init_at_gallery_bgn(int html_w, int html_h, int file_w) {
    +		this.html_w = html_w; this.html_h = html_h; 
    +		this.file_w = file_w;
    +	}
    +	public void Init_at_gallery_end(int html_w, int html_h, Io_url html_view_url, Io_url html_orig_url) {
    +		this.html_w = html_w; this.html_h = html_h; 
    +		this.html_view_url = html_view_url;
    +		this.html_orig_url = html_orig_url;
    +		this.file_exists = true;
    +	}
    +	public void Init_at_hdoc(int html_uid, byte html_elem_tid) {
    +		this.html_uid = html_uid; this.html_elem_tid = html_elem_tid;
    +	}
    +	public void	Init_at_fsdb_make
    +		( byte[] orig_ttl, int orig_ext_id	// NOTE:fsdb_make uses fields named lnki_ttl and lnki_ext_id, but really is orig_ttl and orig_ext_id
    +		, int lnki_w, int lnki_h, double lnki_time, int lnki_page
    +		, byte orig_repo_id, int orig_w, int orig_h, byte[] orig_redirect
    +		, boolean file_is_orig
    +		) {
    +		this.lnki_w = lnki_w; this.lnki_h = lnki_h; this.lnki_time = lnki_time; this.lnki_page = lnki_page;
    +		this.orig_repo_id = orig_repo_id; this.orig_w = orig_w; this.orig_h = orig_h;
    +		this.file_is_orig = file_is_orig;
    +		this.file_w = lnki_w;	// must set file_w, else fsdb_make will always download origs; DATE:2016-08-25
    +		this.html_w = lnki_w; this.html_h = lnki_h;	// set html_size as file_size (may try to optimize later by removing similar thumbs (EX: 220,221 -> 220))
    +		this.Orig_ttl_(orig_ttl);
    +		this.orig_ext = Xof_ext_.new_by_id_(orig_ext_id);	// NOTE: data in fsdb_make may override lnki_ext; EX: ttl=A.png; but ext=".jpg"
    +	}
    +	public void Init_by_fsdb_near(boolean file_is_orig, int file_w) {
    +		this.file_is_orig = file_is_orig; this.file_w = file_w;
    +	}
    +	public void Init_at_xfer(int idx, int len) {
    +		this.xfer_idx = idx; this.xfer_len = len;
    +	}
    +	public void Init_at_cache(boolean file_exists_in_cache, int w, int h, Io_url view_url) {
    +		this.file_exists_in_cache = file_exists_in_cache;
    +		this.html_w = w; this.html_h = h; this.html_view_url = view_url;
    +	}
    +	public void Init_by_wm_parse(byte[] lnki_wiki_abrv, boolean repo_is_commons, boolean file_is_orig, byte[] file_ttl_bry, Xof_ext orig_ext, int file_w, double file_time, int file_page) {
    +		this.hdump_mode = Hdump_mode__v2;
    +
    +		// lnki
    +		this.lnki_ttl = file_ttl_bry;
    +		this.lnki_w = file_w;
    +		this.lnki_h = -1;
    +		this.lnki_type = file_is_orig ? Xop_lnki_type.Id_none : Xop_lnki_type.Id_thumb;
    +		this.lnki_wiki_abrv = lnki_wiki_abrv;
    +		this.lnki_time = file_time;
    +		this.lnki_page = file_page;
    +		this.lnki_exec_tid = Xof_exec_tid.Tid_wiki_page;
    +
    +		// orig
    +		this.orig_repo_id = repo_is_commons ? Xof_repo_tid_.Tid__remote : Xof_repo_tid_.Tid__local;
    +		this.file_is_orig = file_is_orig;
    +		this.Orig_ttl_(file_ttl_bry);
    +		this.orig_ext = orig_ext;
    +
    +		// html
    +		this.file_w = this.html_w = file_w;
    +	}
    +	public void Change_repo(byte orig_repo_id, byte[] orig_repo_name) {
    +		this.orig_repo_id = orig_repo_id; this.orig_repo_name = orig_repo_name;
    +	}
    +	public void File_is_orig_(boolean v) {this.file_is_orig = v;}
    +	public void Orig_repo_name_(byte[] v) {orig_repo_name = v;}
    +	public void Html_elem_tid_(byte v) {this.html_elem_tid = v;}
    +	public void Html_size_(int w, int h) {html_w = w; html_h = h;}
    +	public void Html_view_url_(Io_url v) {html_view_url = v;}
    +	public void Html_orig_url_(Io_url v) {html_orig_url = v;}
    +	public void Html_img_wkr_(Js_img_wkr v) {html_img_wkr = v;}
    +	public void Html_gallery_mgr_h_(int v) {html_gallery_mgr_h = v;}
    +	public void File_size_(long v) {this.file_size = v;}
    +	private void Orig_ttl_(byte[] v) {
    +		this.orig_ttl = v;
    +		this.orig_ttl_md5 = Xof_file_wkr_.Md5_fast(v);
    +	}
    +	private void Lnki_time_validate() {
    +		if (!orig_ext.Id_is_media() && lnki_time != Xof_lnki_time.Null)	// file is not media, but has thumbtime; this check can't be moved to Lnki_time_() b/c it needs ext
    +			lnki_time = Xof_lnki_time.Null;								// set time to null; needed else url will reference thumbtime; PAGE:en.w:Moon; EX:[[File:Lunar libration with phase Oct 2007 450px.gif|thumbtime=0:02]]; DATE:2014-07-20
    +	}
    +	private void Calc_html_size(int exec_tid, Xof_img_size img_size) {
    +		this.Lnki_time_validate();
    +		if (orig_ext.Id_is_audio_strict())								// audio does not have html size calculated; everything else does
    +			file_is_orig = Bool_.Y;
    +		else {
    +			img_size.Html_size_calc(exec_tid, lnki_w, lnki_h, lnki_type, lnki_upright_patch, lnki_upright, orig_ext.Id(), orig_w, orig_h, Xof_img_size.Thumb_width_img);
    +			if (lnki_type != Xop_lnki_type.Tid_orig_known) {	// NOTE: hdump sets html_w / html_h; don't override; needed for packed-gallery; PAGE:en.w:Mexico; DATE:2016-08-10
    +				html_w = img_size.Html_w(); html_h = img_size.Html_h();
    +			}
    +			file_w = img_size.File_w();
    +			file_is_orig = img_size.File_is_orig();
    +		}
    +	}
    +	public void To_bfr(Bry_bfr bfr) {
    +		bfr				   .Add_int_variable(html_uid);
    +		bfr.Add_byte_pipe().Add_int_variable(lnki_exec_tid);
    +		bfr.Add_byte_pipe().Add(lnki_wiki_abrv);
    +		bfr.Add_byte_pipe().Add_int_variable(lnki_type);
    +		bfr.Add_byte_pipe().Add_double(lnki_upright);
    +		bfr.Add_byte_pipe().Add_int_variable(lnki_upright_patch);
    +		bfr.Add_byte_pipe().Add_int_variable(lnki_w);
    +		bfr.Add_byte_pipe().Add_int_variable(lnki_h);
    +		bfr.Add_byte_pipe().Add_double(lnki_time);
    +		bfr.Add_byte_pipe().Add_int_variable(lnki_page);
    +		bfr.Add_byte_nl();
    +	}
    +	public static final int Hdump_mode__null = 0, Hdump_mode__v2 = 2;
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_fsdb_itm_fxt.java b/400_xowa/src/gplx/xowa/files/Xof_fsdb_itm_fxt.java
    index a27517de8..88f7b3f4a 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_fsdb_itm_fxt.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_fsdb_itm_fxt.java
    @@ -13,3 +13,67 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.xowa.wikis.domains.*;
    +import gplx.xowa.files.repos.*;
    +import gplx.xowa.parsers.lnkis.*;
    +public class Xof_fsdb_itm_fxt {
    +	private byte[] wiki_abrv;
    +	private byte[] lnki_ttl;
    +	private byte lnki_type;
    +	private double lnki_upright;
    +	private int lnki_w;
    +	private int lnki_h;
    +	private double lnki_time;
    +	private int lnki_page;
    +	private byte orig_repo_id;
    +	private byte[] orig_repo_name;
    +	private byte[] orig_ttl;
    +	private Xof_ext orig_ext;
    +	private int orig_w;
    +	private int orig_h;
    +	private byte[] orig_redirect;
    +	public Xof_fsdb_itm_fxt() {this.Clear();}
    +	public void Clear() {
    +		this.wiki_abrv = lnki_ttl = null;
    +		this.lnki_type = Xop_lnki_type.Id_null;
    +		this.lnki_upright = Xof_img_size.Upright_null;
    +		this.lnki_w = this.lnki_h = this.orig_w = this.orig_h = Xof_img_size.Size__neg1;
    +		this.lnki_h = Xof_img_size.Size__neg1;
    +		this.lnki_time = Xof_lnki_time.Null;
    +		this.lnki_page = Xof_lnki_page.Null;
    +		this.orig_repo_id = Xof_repo_tid_.Tid__null;
    +		this.orig_repo_name = orig_ttl = orig_redirect = null;
    +		this.orig_ext = null;
    +	}
    +	public Xof_fsdb_itm_fxt Lnki__en_w(String lnki_ttl_str) {
    +		this.wiki_abrv = Abrv__en_w;
    +		this.lnki_ttl = Bry_.new_u8(lnki_ttl_str);
    +		return this;
    +	}
    +	public Xof_fsdb_itm_fxt Orig__commons__lnki() {
    +		this.orig_repo_name = Xow_domain_itm_.Bry__commons;
    +		this.orig_repo_id = Xof_repo_tid_.Tid__remote;
    +		this.orig_ttl = lnki_ttl;
    +		this.orig_ext = Xof_ext_.new_by_ttl_(orig_ttl);
    +		this.orig_w = 880;
    +		this.orig_w = 440;
    +		return this;
    +	}
    +	public Xof_fsdb_itm_fxt Orig__enwiki__lnki() {
    +		this.orig_repo_name = Xow_domain_itm_.Bry__enwiki;
    +		this.orig_repo_id = Xof_repo_tid_.Tid__local;
    +		this.orig_ttl = lnki_ttl;
    +		this.orig_ext = Xof_ext_.new_by_ttl_(orig_ttl);
    +		this.orig_w = 880;
    +		this.orig_w = 440;
    +		return this;
    +	}
    +	public Xof_fsdb_itm Make() {
    +		Xof_fsdb_itm rv = new Xof_fsdb_itm();
    +		rv.Init_at_lnki(Xof_exec_tid.Tid_wiki_page, wiki_abrv, lnki_ttl, lnki_type, lnki_upright, lnki_w, lnki_h, lnki_time, lnki_page, Xof_patch_upright_tid_.Tid_all); 
    +		rv.Init_at_orig(orig_repo_id, orig_repo_name, orig_ttl, orig_ext, orig_w, orig_h, orig_redirect);
    +		return rv;
    +	}
    +	private final    static byte[] Abrv__en_w = Bry_.new_a7("en.w");
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_fsdb_mode.java b/400_xowa/src/gplx/xowa/files/Xof_fsdb_mode.java
    index a27517de8..86d6b8d3e 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_fsdb_mode.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_fsdb_mode.java
    @@ -13,3 +13,20 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +public class Xof_fsdb_mode {
    +	private int tid;
    +	Xof_fsdb_mode(int tid) {this.tid = tid;}
    +	public boolean Tid__v2__bld()                {return tid == TID__v2__bld;}
    +	public void Tid__v2__bld__y_()            {tid = TID__v2__bld;}
    +	public void Tid__v2__mp__y_()             {tid = TID__v2__mp;}
    +	public boolean Tid__bld()                    {return tid > TID__v2__gui;}
    +	private static final int
    +	  TID__v0                   = 1
    +	, TID__v2__gui              = 2
    +	, TID__v2__bld              = 3
    +	, TID__v2__mp               = 4
    +	;
    +	public static Xof_fsdb_mode New__v0()		{return new Xof_fsdb_mode(TID__v0);}
    +	public static Xof_fsdb_mode New__v2__gui()	{return new Xof_fsdb_mode(TID__v2__gui);}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_html_elem.java b/400_xowa/src/gplx/xowa/files/Xof_html_elem.java
    index a27517de8..df96ced04 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_html_elem.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_html_elem.java
    @@ -13,3 +13,13 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +public class Xof_html_elem {
    +	public static final byte Tid_none = 0, Tid_img = 1, Tid_vid = 2, Tid_gallery = 3, Tid_gallery_v2 = 4, Tid_imap = 5, Tid_aud = 6;
    +	public static boolean Tid_is_file(byte tid) {
    +		switch (tid) {
    +			case Tid_img: case Tid_vid:		return true;
    +			default:						return false;
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_img_size.java b/400_xowa/src/gplx/xowa/files/Xof_img_size.java
    index a27517de8..7b78a4e79 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_img_size.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_img_size.java
    @@ -13,3 +13,150 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.core.bits.*;
    +import gplx.xowa.parsers.lnkis.*;
    +public class Xof_img_size {
    +	public int Html_w() {return html_w;} private int html_w;
    +	public int Html_h() {return html_h;} private int html_h;
    +	public int File_w() {return file_w;} private int file_w;	// NOTE: file_w will always equal html_w, unless rounding is implemented; EX: html_w=150,151,152 -> file_w=150
    +	public int File_h() {return file_h;} private int file_h;
    +	public boolean File_is_orig() {return file_is_orig;} private boolean file_is_orig;
    +	private void Clear() {
    +		html_w = html_h = file_w = file_h = 0;
    +		file_is_orig = false;
    +	}
    +	public void Html_size_calc(int exec_tid, int lnki_w, int lnki_h, byte lnki_type, int upright_patch, double lnki_upright, int orig_ext, int orig_w, int orig_h, int thm_dflt_w) {
    +		synchronized (this) {
    +			this.Clear();											// always clear before calc; caller should be responsible, but just to be safe.
    +			if (	Xof_ext_.Id_supports_time(orig_ext)				// ext is video
    +				&&	lnki_w == Xof_img_size.Null						// no size specified
    +				&&	!Xop_lnki_type.Id_is_thumbable(lnki_type)		// not thumb which is implicitly 220; PAGE:en.w:Edward_Snowden; DATE:2015-08-17
    +				)
    +				lnki_w = orig_w;									// use original size; EX:[[File:A.ogv]] -> [[File:A.ogv|550px]] where 550px is orig_w; DATE:2015-08-07
    +			if (Bitmask_.Has_int(lnki_type, Xop_lnki_type.Id_frame)	// frame: always return orig size; Linker.php!makeThumbLink2; // Use image dimensions, don't scale
    +				&& lnki_h == Null) {								// unless lnki_h specified; DATE:2013-12-22
    +				html_w = file_w = orig_w;
    +				html_h = file_h = orig_h;
    +				file_is_orig = Xof_ext_.Orig_file_is_img(orig_ext);	// file_is_orig = true, unless svg, ogv, pdf
    +				if (file_is_orig)
    +					file_w = file_h = Size__same_as_orig;
    +				return;
    +			}
    +			html_w = lnki_w; html_h = lnki_h;						// set html vals to lnki vals
    +			file_is_orig = false;
    +			if (html_w == Null && html_h == Null) {					// no size set; NOTE: do not default to thumb if only height is set; EX: x900px should have w=0 h=900
    +				if (Xop_lnki_type.Id_defaults_to_thumb(lnki_type))
    +					html_w = thm_dflt_w;
    +				else if (	orig_ext == Xof_ext_.Id_pdf				// pdf and viewing on page; default to 220
    +						&&	exec_tid == Xof_exec_tid.Tid_wiki_page)
    +					html_w = thm_dflt_w;
    +				else
    +					html_w = orig_w;
    +			}
    +			html_w = Upright_calc(upright_patch, lnki_upright, html_w, lnki_w, lnki_h, lnki_type);
    +			if (orig_w == Null) return;								// no orig_w; just use html_w and html_h (html_h will likely be -1 and wrong)
    +			boolean ext_is_svg = orig_ext == Xof_ext_.Id_svg;
    +			if (html_w == Xof_img_size.Null) {
    +				if	(	ext_is_svg									// following strange MW logic; REF.MW:Linker.php|makeImageLink|If its a vector image, and user only specifies height, we don't want it to be limited by its "normal" width; DATE: 2013-11-26
    +					&&	html_h != Xof_img_size.Null)
    +					html_w = Svg_max_width;
    +				else
    +					html_w = orig_w;								// html_w missing >>> use orig_w; REF.MW:Linker.php|makeImageLink2|$hp['width'] = $file->getWidth( $page );				
    +			}
    +			if (html_h != Xof_img_size.Null) {						// html_h exists; REF.MW:ImageHandler.php|normaliseParams|if ( isset( $params['height'] ) && $params['height'] != -1 ) {
    +				if (	(long)html_w * (long)orig_h 
    +					>	(long)html_h * (long)orig_w)				// html ratio > orig ratio; recalc html_w; SEE:NOTE_2; NOTE: casting to long to prevent int overflow; [[File:A.png|9999999999x90px]]; DATE:2014-04-26
    +					html_w = Calc_w(orig_w, orig_h, html_h);
    +			}
    +			html_h = Scale_h(orig_w, orig_h, html_w);				// calc html_h
    +			if (	html_w >= orig_w								// html >= orig
    +				&&	(	Xof_ext_.Orig_file_is_img(orig_ext)			// orig is img (ignore for svg, ogv, pdf, etc)
    +					||	ext_is_svg && exec_tid == Xof_exec_tid.Tid_wiki_file	// limit to size if svg and [[File]] page
    +					)
    +				) {
    +				file_is_orig = true;								// use orig img (don't create thumb)
    +				file_w = file_h = Size__same_as_orig;
    +				if (Xop_lnki_type.Id_limits_large_size(lnki_type)) {// do not allow html_w > orig_w; REF.MW:Generic.php|normaliseParams
    +					html_w = orig_w;
    +					html_h = orig_h;
    +				}
    +			}
    +			else {													// html < orig
    +				file_w = html_w;
    +				file_h = html_h;
    +			}
    +		}
    +	}
    +	public static int Calc_w(int file_w, int file_h, int lnki_h) {		// REF.MW:media/MediaHandler.php|fitBoxWidth
    +		double ideal_w = (double)file_w * (double)lnki_h / (double)file_h;
    +		double ideal_w_ceil = Math_.Ceil(ideal_w);
    +		return Math_.Round(ideal_w_ceil * file_h / file_w, 0) > lnki_h
    +			? (int)Math_.Floor(ideal_w)
    +			: (int)ideal_w_ceil;
    +	}
    +	public static int Scale_h(int file_w, int file_h, int lnki_w) {
    +		return file_w == 0												// REF.MW:File.php|scaleHeight
    +			? 0
    +			: (int)Math_.Round(((double)lnki_w * file_h) / file_w, 0);	// NOTE: (double) needed else result will be int and fraction will be truncated
    +	}
    +	public static int Upright_calc(int upright_patch_tid, double upright, int cur_w, int lnki_w, int lnki_h, byte lnki_type) {
    +		boolean upright_patch_use_thumb_w = Xof_patch_upright_tid_.Split_use_thumb_w(upright_patch_tid);
    +		boolean upright_patch_fix_default = Xof_patch_upright_tid_.Split_fix_default(upright_patch_tid);
    +		double upright_default_val = upright_patch_fix_default ? .75f : 1f;
    +		if (upright_patch_use_thumb_w) {
    +			if	(upright != Upright_null							// upright set
    +				&& lnki_w == Null									// w is null; EX: ( -1, 220); must exit early or will become 0; DATE:2013-11-23
    +				&& lnki_h == Null									// h is null; EX: (220,  -1); REF:Linker.php|makeImageLink|"if (... !$hp['width'] ); 
    +				&& Xop_lnki_type.Id_supports_upright(lnki_type)
    +				) {
    +				if	(upright == Upright_default_marker) upright = upright_default_val;	// upright is default; set val to .75; EX: [[File:A.png|upright]]						
    +				int rv = (int)(Thumb_width_img * upright);
    +				return Round_10p2(rv);
    +			}
    +			else
    +				return cur_w;										// upright doesn't apply; return self;
    +		}
    +		else {														// support old broken calc
    +			if 		(upright == Upright_null)		return cur_w;	// upright is null; return width
    +			else if (upright == Upright_default_marker)upright = upright_default_val;	// upright is default; set val to .75; NOTE: wrong b/c [[File:A.png|upright=1]] -> .75
    +			if		(cur_w == Null)					return Null;	// width is null (-1); must exit early or will become 0; DATE:2013-11-23
    +			int rv = (int)(cur_w * upright);						// NOTE: wrong b/c should be Thumb_width_img, not cur_w
    +			return Round_10p2(rv);
    +		}
    +	}
    +	private static int Round_10p2(int v) {
    +		int mod = v % 10;
    +		if (mod > 4) 	v += 10 - mod;
    +		else			v -= mod;
    +		return v;
    +	}
    +	public static final int Null = -1;
    +	public static final int Thumb_width_img = 220, Thumb_width_ogv = 220;
    +	public static final    double Upright_null = -1, Upright_default_marker = 0; // REF:MW: if ( isset( $fp['upright'] ) && $fp['upright'] == 0 )
    +	public static final int Size__neg1 = -1, Size_null = 0;	// Size_null = 0, b/c either imageMagick / inkscape fails when -1 is passed
    +	public static final int Size__same_as_orig = -1;
    +	private static final int Svg_max_width = 2048;
    +}
    +/*
    +NOTE_1:proc source/layout
    +MW calls the falling procs
    +. Linker.php|makeImageLink2
    +. Linker.php|makeThumbLink2
    +. File.php|transform
    +. Bitmap.php|normaliseParams
    +. media/MediaHandler.php|fitBoxWidth
    +. File.php|scaleHeight
    +Note that this proc is a selective culling of the w,h setting code in the above (the procs do a lot of other checks/building)
    +also, MW's if branching can be combined. for now, emulating MW and not enforcing matching if/else 
    +NOTE_2: lnki_ratio > orig_ratio
    +REF.MW:media/MediaHandler.php|fitBoxWidth
    +COMMENT:"Height is the relative smaller dimension, so scale width accordingly"
    +consider file of 200,100 (2:1)
    +EX_1: view is 120,40 (3:1)
    +- dimensions are either (a) 120,80 or (b) 80,40
    +- use (b) 80,40
    +EX_2: view is 120,80 (1.5:1)
    +- dimensions are either (a) 120,60 or (b) 160,80
    +- use (a) 120,60
    +*/
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_img_size_tst.java b/400_xowa/src/gplx/xowa/files/Xof_img_size_tst.java
    index a27517de8..dd2f33698 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_img_size_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_img_size_tst.java
    @@ -13,3 +13,93 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import org.junit.*; import gplx.core.bits.*; import gplx.xowa.files.*; import gplx.xowa.parsers.lnkis.*;
    +public class Xof_img_size_tst {
    +	private final Xof_img_size_fxt fxt = new Xof_img_size_fxt();
    +	@Before public void init() {
    +		fxt.Reset();
    +		fxt.Orig_(400, 200);
    +	}
    +	@Test  	public void Lnki_lt_orig_null() 		{fxt.Lnki_type_(Xop_lnki_type.Id_null)		.Lnki_(200, 100).Test_html(200, 100, Bool_.N);}	// [[File:A.png|200px]]           -> 200,100; File_is_orig = n
    +	@Test  	public void Lnki_lt_orig_thumb() 		{fxt.Lnki_type_(Xop_lnki_type.Id_thumb)		.Lnki_(200, 100).Test_html(200, 100, Bool_.N);}	// [[File:A.png|thumb|200px]]     -> 200,100; File_is_orig = n
    +	@Test  	public void Lnki_lt_orig_frameless() 	{fxt.Lnki_type_(Xop_lnki_type.Id_frameless)	.Lnki_(200, 100).Test_html(200, 100, Bool_.N);}	// [[File:A.png|frameless|200px]] -> 200,100; File_is_orig = n
    +	@Test  	public void Lnki_lt_orig_frame() 		{fxt.Lnki_type_(Xop_lnki_type.Id_frame)		.Lnki_(200,  -1).Test_html(400, 200, Bool_.Y);}	// [[File:A.png|frame|200px]]     -> 400,200; File_is_orig = y; frame always ignores parameters and returns orig
    +	@Test  	public void Lnki_gt_orig_null() 		{fxt.Lnki_type_(Xop_lnki_type.Id_null)		.Lnki_(800, 400).Test_html(800, 400, Bool_.Y);}	// [[File:A.png|800px]]           -> 800,400; File_is_orig = n
    +	@Test  	public void Lnki_gt_orig_thumb() 		{fxt.Lnki_type_(Xop_lnki_type.Id_thumb)		.Lnki_(800, 400).Test_html(400, 200, Bool_.Y);}	// [[File:A.png|thumb|800px]]     -> 400,200; File_is_orig = n
    +	@Test  	public void Lnki_gt_orig_frameless() 	{fxt.Lnki_type_(Xop_lnki_type.Id_frameless)	.Lnki_(800, 400).Test_html(400, 200, Bool_.Y);}	// [[File:A.png|frameless|800px]] -> 400,200; File_is_orig = n
    +	@Test  	public void Lnki_gt_orig_frame() 		{fxt.Lnki_type_(Xop_lnki_type.Id_frame)		.Lnki_(800,  -1).Test_html(400, 200, Bool_.Y);}	// [[File:A.png|frame|800px]]     -> 400,200; File_is_orig = y; frame always ignores parameters and returns orig
    +	@Test  	public void Lnki_eq_orig_null() 		{fxt.Lnki_type_(Xop_lnki_type.Id_null)		.Lnki_(400, 200).Test_html(400, 200, Bool_.Y);}	// make sure File_is_orig = y
    +	@Test  	public void Lnki_gt_orig_null_svg() 	{fxt.Lnki_ext_(Xof_ext_.Id_svg).Lnki_type_(Xop_lnki_type.Id_null)		.Lnki_(800, 400).Test_html(800, 400, Bool_.N);}	// [[File:A.svg|800px]]           -> 800,400; File_is_orig = n
    +	@Test  	public void Lnki_gt_orig_thumb_svg() 	{fxt.Lnki_ext_(Xof_ext_.Id_svg).Lnki_type_(Xop_lnki_type.Id_thumb)		.Lnki_(800, 400).Test_html(800, 400, Bool_.N);}	// [[File:A.svg|thumb|800px]]     -> 800,400; File_is_orig = n
    +	@Test  	public void Lnki_gt_orig_frameless_svg(){fxt.Lnki_ext_(Xof_ext_.Id_svg).Lnki_type_(Xop_lnki_type.Id_frameless)	.Lnki_(800, 400).Test_html(800, 400, Bool_.N);}	// [[File:A.svg|frameless|800px]] -> 800,400; File_is_orig = n
    +	@Test  	public void Lnki_gt_orig_frame_svg() 	{fxt.Lnki_ext_(Xof_ext_.Id_svg).Lnki_type_(Xop_lnki_type.Id_frame)		.Lnki_(800,  -1).Test_html(400, 200, Bool_.N);}	// [[File:A.svg|frame|800px]]     -> 400,200; File_is_orig = n; frame always ignores parameters and returns orig
    +	@Test   public void Width_missing()				{fxt.Lnki_( -1, 100).Test_html(200, 100);}	// calc width based on height and orig
    +	@Test   public void Height_missing()			{fxt.Lnki_(200,  -1).Test_html(200, 100);}
    +	@Test  	public void Orig_missing() 				{fxt.Lnki_(400, 200).Orig_( -1,  -1).Test_html(400, 200);}	// no orig_size; use lnki_w and lnki_h
    +	@Test  	public void Lnki_missing() 				{fxt.Lnki_( -1,  -1).Test_html(220, 110);}					// w=thumbnail default; h=calc from orig
    +	@Test   public void Lnki_missing_frameless()    {fxt.Lnki_( -1,  -1).Lnki_type_(Xop_lnki_type.Id_frameless) .Test_html(220, 110, Bool_.N);}	// default to thumb width
    +	@Test   public void Lnki_missing_null()         {fxt.Lnki_( -1,  -1).Lnki_type_(Xop_lnki_type.Id_null)		.Test_html(400, 200, Bool_.Y);}	// default to orig width
    +	@Test  	public void Lnki_missing__orig_missing(){fxt.Lnki_( -1,  -1).Orig_( -1,  -1).Test_html(220,  -1);}	// no lnki or orig size; default to 220 with unknown height
    +	@Test   public void Prefer_height_over_width()	{fxt.Lnki_(200, 100).Test_html(200, 100);}					// prefer height; if width were preferred, size would be 200,134
    +	@Test  	public void Upright() 					{fxt.Lnki_upright_(1).Lnki_(-1, -1).Orig_(440, 400).Test_html(220, 200);}	
    +	@Test  	public void Upright_w_thumb() 			{fxt.Lnki_type_(Xop_lnki_type.Id_thumb).Lnki_upright_(2).Lnki_(-1, -1).Orig_(1500, 1125).Test_html(440, 330);}
    +	@Test  	public void Upright_ignored_by_w() 		{fxt.Lnki_type_(Xop_lnki_type.Id_thumb).Lnki_upright_(3.2).Lnki_(900, -1).Orig_(4653, 854).Test_html(900, 165);}// PAGE: fr.w:Bogota; DATE:2014-05-22
    +	@Test   public void Explicit_ratio_large()		{fxt.Lnki_(120,  40).Test_html( 80,  40);}	// see NOTE_2: lnki_ratio > orig_ratio
    +	@Test   public void Explicit_ratio_small()		{fxt.Lnki_(120,  80).Test_html(120,  60);}	// see NOTE_2: lnki_ratio > orig_ratio
    +	@Test   public void Lnki_gt_orig_null_svg_example() {	// EX:[[File:Crystal Clear app kedit.svg|50x40px]]
    +		fxt.Lnki_ext_(Xof_ext_.Id_svg).Lnki_type_(Xop_lnki_type.Id_null).Lnki_( 50,  40).Orig_( 40,  40).Test_html( 40,  40);
    +	}	
    +	@Test   public void Prefer_height_over_width_example() {// EX:[[File:Firma B.Ohiggins.svg|128x80px|alt=|Bernardo O'Higgins's signature]]
    +		fxt.Lnki_ext_(Xof_ext_.Id_svg).Lnki_type_(Xop_lnki_type.Id_null).Lnki_(128,  80).Orig_(720, 194).Test_html(128,  34);
    +	}	
    +	@Test   public void Lnki_gt_orig_thumb_example() {// EX:[[File:Adhanema Lasva.jpg|thumb|300px|The Firman given to the Bosnian Franciscans]]
    +		fxt.Lnki_type_(Xop_lnki_type.Id_thumb).Lnki_(300,  -1).Orig_(149, 408).Test_html(149, 408, Bool_.Y);
    +	}
    +	@Test  	public void Upright_and_null_width_fails() {// PURPOSE: if width = -1, but upright is specified, ignore upright (was calculating 0 for width); DATE:2013-11-23
    +		fxt.Lnki_type_(Xop_lnki_type.Id_null).Lnki_(-1, 110).Orig_(440, 220).Lnki_upright_(.50f).Test_html(220, 110, Bool_.N);
    +	}
    +	@Test  	public void Svg_null_width() {	// PURPOSE: if svg and only height is specified, default width to 2048 (and recalc); DATE: 2013-11-26
    +		fxt.Lnki_ext_(Xof_ext_.Id_svg).Lnki_(-1, 40).Orig_(1, 1).Test_html(40, 40, Bool_.N);	// NOTE: used to be 1,1
    +	}
    +	@Test  	public void Svg_max_width() {	// PURPOSE: large width causes int overflow; vi.w:Danh_sách_quốc_kỳ DATE:2014-04-26
    +		fxt.Lnki_ext_(Xof_ext_.Id_svg).Lnki_(Int_.Max_value, 90).Orig_(900, 600).Test_html(135, 90, Bool_.N);	// NOTE: used to be Int_.Max_value,90
    +	}
    +	@Test  	public void Pdf_none_defaults_to_thumb() {	// PURPOSE: if no width is specified, pdf uses thumb width default, not orig width); DATE: 2013-11-27
    +		fxt.Lnki_type_(Xop_lnki_type.Id_none).Lnki_ext_(Xof_ext_.Id_pdf).Lnki_(-1, -1).Orig_(440, 220).Test_html(220, 110, Bool_.N);	// NOTE: used to be 1,1
    +	}
    +	@Test  	public void Frame() {	// PURPOSE: frame incorrectly defaulted to file_is_orig; [[File:MESSENGER.jpg|200x200px|framed]]; DATE:2013-12-22
    +		fxt.Lnki_type_(Xop_lnki_type.Id_frame).Lnki_ext_(Xof_ext_.Id_png).Lnki_(200, 200).Orig_(2038, 1529).Test_html(200, 150, Bool_.N);
    +	}
    +	@Test  	public void Frame_and_thumb(){ // PURPOSE: frame and thumb should be treated as frame; Enm.Has(val, Id_frame) vs val == Id_frame; PAGE:en.w:History_of_Western_Civilization; DATE:2015-04-16
    +		fxt.Lnki_type_(Bitmask_.Add_byte(Xop_lnki_type.Id_frame, Xop_lnki_type.Id_thumb)).Lnki_(200,  -1).Test_html(400, 200, Bool_.Y);	// mut return same as Lnki_lt_orig_frame above
    +	}
    +	@Test  	public void Video__use_orig_w(){ // PURPOSE: video should use orig_w; DATE:2015-08-07
    +		fxt.Lnki_type_(Xop_lnki_type.Id_none).Lnki_ext_(Xof_ext_.Id_ogv).Lnki_(-1,  -1).Orig_(500, 250).Test_html(500, 250, Bool_.N);
    +	}
    +	@Test  	public void Video__use_thumb(){ // PURPOSE: video should use thumb_w, not orig_w; PAGE:en.w:Edward_Snowden DATE:2015-08-17
    +		fxt.Lnki_type_(Xop_lnki_type.Id_thumb).Lnki_ext_(Xof_ext_.Id_ogv).Lnki_(-1,  -1).Orig_(440, 220).Test_html(220, 110, Bool_.N);
    +	}
    +}
    +class Xof_img_size_fxt {
    +	private Xof_img_size img_size = new Xof_img_size();
    +	public Xof_img_size_fxt Reset() {
    +		lnki_type = Xop_lnki_type.Id_thumb;
    +		lnki_ext = Xof_ext_.Id_jpg;
    +		lnki_upright = Xof_img_size.Upright_null;
    +		orig_w = orig_h = lnki_w = lnki_h = Xof_img_size.Null;
    +		return this;
    +	}
    +	public Xof_img_size_fxt Lnki_type_(byte v) {lnki_type = v; return this;} private byte lnki_type;
    +	public Xof_img_size_fxt Lnki_ext_(int v) {lnki_ext = v; return this;} private int lnki_ext;
    +	public Xof_img_size_fxt Lnki_upright_(double v) {lnki_upright = v; return this;} private double lnki_upright;
    +	public Xof_img_size_fxt Orig_(int w, int h) {orig_w = w; orig_h = h; return this;} private int orig_w, orig_h;
    +	public Xof_img_size_fxt Lnki_(int w, int h) {lnki_w = w; lnki_h = h; return this;} private int lnki_w, lnki_h;
    +	public void Test_html(int expd_w, int expd_h) {Test_html(expd_w, expd_h, false);}
    +	public void Test_html(int expd_html_w, int expd_html_h, boolean expd_file_is_orig) {
    +		img_size.Html_size_calc(Xof_exec_tid.Tid_wiki_page, lnki_w, lnki_h, lnki_type, Xof_patch_upright_tid_.Tid_all, lnki_upright, lnki_ext, orig_w, orig_h, Xof_img_size.Thumb_width_img);
    +		Tfds.Eq(expd_html_w, img_size.Html_w(), "html_w");
    +		Tfds.Eq(expd_html_h, img_size.Html_h(), "html_h");
    +		Tfds.Eq(expd_file_is_orig, img_size.File_is_orig(), "file_is_orig");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_lnki_page.java b/400_xowa/src/gplx/xowa/files/Xof_lnki_page.java
    index a27517de8..4c7a3f639 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_lnki_page.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_lnki_page.java
    @@ -13,3 +13,14 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.core.stores.*;
    +import gplx.dbs.*;
    +public class Xof_lnki_page {
    +	public static final int		Null = -1;
    +	public static boolean		Null_y(int v) {return v == Null;}
    +	public static boolean		Null_n(int v) {return v != Null;}
    +	public static int		Db_load_int(DataRdr rdr, String fld)	{return rdr.ReadInt(fld);}
    +	public static int		Db_load_int(Db_rdr rdr, String fld)		{return rdr.Read_int(fld);}
    +	public static int		Db_save_int(int v) {return v;}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_lnki_time.java b/400_xowa/src/gplx/xowa/files/Xof_lnki_time.java
    index a27517de8..47787fd89 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_lnki_time.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_lnki_time.java
    @@ -13,3 +13,30 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.core.stores.*;
    +import gplx.dbs.*;
    +public class Xof_lnki_time {
    +	public static double	Db_save_double(double v) {return v;}
    +	public static double	Db_load_double(DataRdr rdr, String fld) {return rdr.ReadDouble(fld);}
    +	public static double	Db_load_double(Db_rdr rdr, String fld)	{return rdr.Read_double(fld);}
    +	public static int		Db_save_int(double v) {return (int)v;}
    +	public static double	Db_load_int(DataRdr rdr, String fld)	{return rdr.ReadInt(fld);}
    +	public static double	Db_load_int(Db_rdr rdr, String fld)		{return rdr.Read_int(fld);}
    +	public static int		X_int(double v) {return (int)v;}
    +	public static String	X_str(double v) {return Double_.To_str(v);}
    +	public static final double		Null = -1;
    +	public static boolean		Null_y(double v) {return v == Null;}
    +	public static boolean		Null_n(double v) {return v != Null;}
    +	public static final int		Null_as_int = -1;
    +
    +	public static double	Convert_to_xowa_thumbtime	(int ext, double val)	{return Xof_ext_.Id_supports_time(ext)	? val		: Null;}
    +	public static int		Convert_to_xowa_page		(int ext, double val)	{return Xof_ext_.Id_supports_page(ext)		? (int)val	: Xof_lnki_page.Null;}
    +	public static double	Convert_to_fsdb_thumbtime	(int ext, double thumbtime, int page) {
    +		return	page != Xof_lnki_page.Null
    +			&&	Xof_ext_.Id_supports_page(ext)		// redefine thumbtime to page if pdf
    +			?	page
    +			:	thumbtime
    +			;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_media_type.java b/400_xowa/src/gplx/xowa/files/Xof_media_type.java
    index a27517de8..8ccf9fe93 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_media_type.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_media_type.java
    @@ -13,3 +13,16 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +public class Xof_media_type {
    +	public static final byte Tid_null = 0, Tid_audio = 1, Tid_bitmap = 2, Tid_drawing = 2, Tid_office = 3, Tid_video = 4;
    +	public static final String Name_null = "", Name_audio = "AUDIO", Name_bitmap = "BITMAP", Name_drawing = "DRAWING", Name_office = "OFFICE", Name_video = "VIDEO";
    +	public static byte Xto_byte(String v) {
    +		if		(String_.Eq(v, Name_audio))		return Tid_audio;
    +		else if	(String_.Eq(v, Name_bitmap))	return Tid_bitmap;
    +		else if	(String_.Eq(v, Name_drawing))	return Tid_drawing;
    +		else if	(String_.Eq(v, Name_office))	return Tid_office;
    +		else if	(String_.Eq(v, Name_video))		return Tid_video;
    +		else									return Tid_null;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_mime_minor_.java b/400_xowa/src/gplx/xowa/files/Xof_mime_minor_.java
    index a27517de8..d50d9eee9 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_mime_minor_.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_mime_minor_.java
    @@ -13,3 +13,41 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.core.primitives.*;
    +public class Xof_mime_minor_ {
    +	public static Xof_ext ext_(byte[] minor_mime) {
    +		Int_obj_val id_obj = (Int_obj_val)mime_hash.Get_by(minor_mime);
    +		int id = id_obj == null ? Xof_ext_.Id_unknown : id_obj.Val();
    +		return Xof_ext_.new_by_id_(id);
    +	}
    +	private static final    byte[] 
    +	  Mime_svg = Bry_.new_a7("svg+xml"), Mime_djvu = Bry_.new_a7("vnd.djvu"), Mime_midi = Bry_.new_a7("midi")
    +	, Mime_xcf = Bry_.new_a7("x-xcf"), Mime_flac = Bry_.new_a7("x-flac")
    +	, Mime_bmp = Bry_.new_a7("x-bmp"), Mime_bmp_2 = Bry_.new_a7("x-ms-bmp");
    +	private static final    Hash_adp mime_hash = mime_hash_();
    +	private static Hash_adp mime_hash_() {
    +		Hash_adp rv = Hash_adp_bry.cs();
    +		mime_hash_itm_(rv, Xof_ext_.Bry_png		, Xof_ext_.Id_png);
    +		mime_hash_itm_(rv, Xof_ext_.Bry_jpg		, Xof_ext_.Id_jpg);
    +		mime_hash_itm_(rv, Xof_ext_.Bry_jpeg	, Xof_ext_.Id_jpeg);
    +		mime_hash_itm_(rv, Xof_ext_.Bry_gif		, Xof_ext_.Id_gif);
    +		mime_hash_itm_(rv, Xof_ext_.Bry_tif		, Xof_ext_.Id_tif);
    +		mime_hash_itm_(rv, Xof_ext_.Bry_tiff	, Xof_ext_.Id_tiff);
    +		mime_hash_itm_(rv, Mime_svg				, Xof_ext_.Id_svg);
    +		mime_hash_itm_(rv, Mime_djvu			, Xof_ext_.Id_djvu);
    +		mime_hash_itm_(rv, Xof_ext_.Bry_pdf		, Xof_ext_.Id_pdf);
    +		mime_hash_itm_(rv, Mime_midi			, Xof_ext_.Id_mid);
    +		mime_hash_itm_(rv, Xof_ext_.Bry_ogg		, Xof_ext_.Id_ogg);
    +		mime_hash_itm_(rv, Xof_ext_.Bry_oga		, Xof_ext_.Id_oga);
    +		mime_hash_itm_(rv, Xof_ext_.Bry_ogv		, Xof_ext_.Id_ogv);
    +		mime_hash_itm_(rv, Xof_ext_.Bry_webm	, Xof_ext_.Id_webm);
    +		mime_hash_itm_(rv, Mime_flac			, Xof_ext_.Id_flac);
    +		mime_hash_itm_(rv, Mime_bmp				, Xof_ext_.Id_bmp);
    +		mime_hash_itm_(rv, Mime_bmp_2			, Xof_ext_.Id_bmp);
    +		mime_hash_itm_(rv, Mime_xcf				, Xof_ext_.Id_xcf);
    +		mime_hash_itm_(rv, Xof_ext_.Bry_wav		, Xof_ext_.Id_wav);
    +		return rv;
    +	}
    +	private static void mime_hash_itm_(Hash_adp hash, byte[] key, int val) {hash.Add(key, new Int_obj_val(val));}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_patch_upright_tid_.java b/400_xowa/src/gplx/xowa/files/Xof_patch_upright_tid_.java
    index a27517de8..0b2bc2252 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_patch_upright_tid_.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_patch_upright_tid_.java
    @@ -13,3 +13,17 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.core.bits.*;
    +public class Xof_patch_upright_tid_ {
    +	public static final int Tid_unpatched = 0, Tid_use_thumb_w = 1, Tid_fix_default = 2;
    +	public static final int Tid_all = Tid_use_thumb_w | Tid_fix_default;
    +	public static int Merge(boolean use_thumb_w, boolean fix_default) {
    +		if		(use_thumb_w && fix_default)	return Bitmask_.Add_int(Tid_use_thumb_w, Tid_fix_default);
    +		else if (use_thumb_w)					return Tid_use_thumb_w;
    +		else if (fix_default)					return Tid_fix_default;
    +		else									return Tid_unpatched;
    +	}
    +	public static boolean Split_use_thumb_w(int tid)		{return Bitmask_.Has_int(tid, Tid_use_thumb_w);}
    +	public static boolean Split_fix_default(int tid)		{return Bitmask_.Has_int(tid, Tid_fix_default);}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_url_bldr.java b/400_xowa/src/gplx/xowa/files/Xof_url_bldr.java
    index a27517de8..17ba7db4a 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_url_bldr.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_url_bldr.java
    @@ -13,3 +13,217 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.core.envs.*;
    +import gplx.langs.htmls.encoders.*;
    +import gplx.xowa.files.repos.*; import gplx.xowa.files.fsdb.*; import gplx.xowa.files.imgs.*;
    +public class Xof_url_bldr {
    +	private final    Bry_bfr tmp_bfr = Bry_bfr_.Reset(400);
    +	private final    Gfo_url_encoder encoder_src_http = Gfo_url_encoder_.New__http_url().Make(); // NOTE: changed from new_html_href_mw_ to new_url_ on 2012-11-19; issues with A%2Cb becoming A%252Cb
    +	private byte[] ttl; private byte[] md5; private Xof_ext ext; private boolean file_is_thumb; private int file_w;
    +	private double time = Xof_lnki_time.Null; private int page = Xof_lnki_page.Null; private byte time_dlm = Byte_ascii.At;
    +	private byte repo_tid;
    +	private byte[] root; private byte dir_spr; private boolean fsys_is_wnt; private boolean wmf_dir_hive; private boolean wmf_protocol_is_file; private int md5_dir_depth; private byte[] area;
    +	public Xof_url_bldr Root_(byte[] v) {root = v; return this;}
    +	public Xof_url_bldr Init_by_repo(byte repo_tid, byte[] root, boolean fsys_is_wnt, byte dir_spr, boolean wmf_dir_hive, boolean wmf_protocol_is_file, int md5_dir_depth) {
    +		this.repo_tid = repo_tid;
    +		this.root = root; this.dir_spr = dir_spr; this.wmf_dir_hive = wmf_dir_hive; this.wmf_protocol_is_file = wmf_protocol_is_file; this.md5_dir_depth = md5_dir_depth;
    +		this.fsys_is_wnt = fsys_is_wnt;
    +		return this;
    +	}
    +	public Xof_url_bldr Init_by_itm(byte mode, byte[] ttl, byte[] md5, Xof_ext ext, int file_w, double time, int page) {
    +		this.ttl = ttl; this.md5 = md5;	this.ext = ext; this.file_w = file_w; this.time = time; this.page = page;
    +		if (wmf_protocol_is_file && fsys_is_wnt) this.ttl = Xof_itm_ttl_.Remove_invalid(tmp_bfr, ttl); // NOTE: changed ttl does not change md5
    +		this.file_is_thumb = mode == Xof_img_mode_.Tid__thumb;
    +		this.area = Xof_img_mode_.Names_ary[mode];
    +		return this;
    +	}
    +	public Xof_url_bldr Init_for_src_file(Xof_repo_itm repo, byte mode, byte[] ttl, byte[] md5, Xof_ext ext, int file_w, double time, int page) {
    +		this.repo_tid = repo.Tid();
    +		this.wmf_dir_hive = Bool_.Y; this.wmf_protocol_is_file = repo.Tarball();
    +		this.dir_spr = repo.Dir_spr(); this.root = repo.Root_bry(); this.area = repo.Mode_names()[mode];
    +		this.ttl = repo.Gen_name_src(tmp_bfr, ttl); this.md5 = md5; this.ext = ext;
    +		this.file_is_thumb = mode == Xof_img_mode_.Tid__thumb; this.file_w = file_w; this.time = time; this.page = page;
    +		return this;
    +	}
    +	public Xof_url_bldr Init_for_trg_file(Xof_repo_itm repo, byte mode, byte[] ttl, byte[] md5, Xof_ext ext, int file_w, double time, int page) {
    +		return Init(repo.Tid(), Bool_.N, Bool_.N, repo.Dir_spr(), repo.Root_bry()
    +			, repo.Mode_names()[mode], repo.Dir_depth(), repo.Gen_name_trg(tmp_bfr, ttl, md5, ext), md5, ext, mode, file_w, time, page);
    +	}
    +	public Xof_url_bldr Init_for_trg_html(Xof_repo_itm repo, byte mode, byte[] ttl, byte[] md5, Xof_ext ext, int file_w, double time, int page) {
    +		return Init(repo.Tid(), Bool_.N, Bool_.N, Byte_ascii.Slash, repo.Root_http()
    +			, repo.Mode_names()[mode], repo.Dir_depth(), repo.Gen_name_trg(tmp_bfr, ttl, md5, ext), md5, ext, mode, file_w, time, page);
    +	}
    +	private Xof_url_bldr Init(byte repo_tid, boolean wmf_dir_hive, boolean wmf_protocol_is_file, byte dir_spr
    +		, byte[] root, byte[] area, int md5_dir_depth
    +		, byte[] ttl, byte[] md5, Xof_ext ext
    +		, byte file_mode, int file_w, double time, int page
    +		) {
    +		this.repo_tid = repo_tid;
    +		this.wmf_dir_hive = wmf_dir_hive; this.wmf_protocol_is_file = wmf_protocol_is_file; this.dir_spr = dir_spr;
    +		this.root = root;  this.area = area; this.md5_dir_depth = md5_dir_depth;
    +		this.ttl = ttl; this.md5 = md5; this.ext = ext;
    +		this.file_is_thumb = file_mode == Xof_img_mode_.Tid__thumb; this.file_w = file_w; this.time = time; this.page = page;
    +		return this;
    +	}
    +	public byte[] Xto_bry() {Bld(); byte[] rv = tmp_bfr.To_bry_and_clear(); Clear(); return rv;}
    +	public String Xto_str() {Bld(); String rv = tmp_bfr.To_str(); Clear(); return rv;}
    +	public Io_url Xto_url() {Bld(); Io_url rv = Io_url_.new_fil_(tmp_bfr.To_str()); Clear(); return rv;}
    +	public Io_url Xto_url_by_http() {Bld(); Io_url rv = Io_url_.New__http_or_fail(tmp_bfr.To_str()); Clear(); return rv;}
    +	public Io_url To_url_trg(Xof_repo_itm repo_itm, Xof_fsdb_itm itm, boolean orig) {
    +		byte mode = orig ? Xof_img_mode_.Tid__orig : Xof_img_mode_.Tid__thumb;
    +		return this.Init_for_trg_file(repo_itm, mode, itm.Orig_ttl(), itm.Orig_ttl_md5(), itm.Orig_ext(), itm.File_w(), itm.Lnki_time(), itm.Lnki_page()).Xto_url();
    +	}
    +	public Io_url To_url_trg(Xof_repo_itm repo_itm, Xof_file_itm itm, boolean orig) {
    +		byte mode = orig ? Xof_img_mode_.Tid__orig : Xof_img_mode_.Tid__thumb;
    +		return this.Init_for_trg_file(repo_itm, mode, itm.Orig_ttl(), itm.Orig_ttl_md5(), itm.Orig_ext(), itm.File_w(), itm.Lnki_time(), itm.Lnki_page()).Xto_url();
    +	}
    +	public Io_url To_url_trg(Xof_repo_itm repo_itm, gplx.xowa.files.caches.Xou_cache_itm itm, boolean orig) {
    +		byte mode = orig ? Xof_img_mode_.Tid__orig : Xof_img_mode_.Tid__thumb;
    +		return this.Init_for_trg_file(repo_itm, mode, itm.Orig_ttl(), itm.Orig_ttl_md5(), itm.Orig_ext_itm(), itm.File_w(), itm.Lnki_time(), itm.Lnki_page()).Xto_url();
    +	}
    +	private static final    byte[] Bry__http = Bry_.new_a7("http");
    +	private void Bld() {
    +		if (repo_tid == Xof_repo_tid_.Tid__math) {
    +			tmp_bfr.Add(root);																// add root;				EX: "C:\xowa\file\"; assume trailing dir_spr
    +			boolean root_is_http = Bry_.Has_at_bgn(root, Bry__http);
    +			if (root_is_http)
    +				tmp_bfr.Add_mid(ttl, 0, ttl.length - 4);	// -4 to remove ".svg". note that XO stores ".svg", but WM doesn't; EX: "596f8baf206a81478afd4194b44138715dc1a05c.svg"
    +			else
    +				tmp_bfr.Add(ttl);
    +		}
    +		else {
    +			Add_core();
    +			if (file_is_thumb) {
    +				if (wmf_dir_hive)	Add_thumb_wmf();
    +				else				Add_thumb_xowa();
    +			}
    +		}
    +	}
    +	private Xof_url_bldr Add_core() {
    +		tmp_bfr.Add(root);																	// add root;				EX: "C:\xowa\file\"; assume trailing dir_spr
    +		if (area != null && !(wmf_dir_hive && !file_is_thumb))								// !(wmf_dir_hive && !thumb) means never add if wmf_dir_hive and orig
    +			tmp_bfr.Add(area).Add_byte(dir_spr);											// add area;				EX: "thumb\"
    +		byte b0 = md5[0];
    +		if (wmf_dir_hive) {
    +			tmp_bfr.Add_byte(b0).Add_byte(dir_spr);											// add md5_0				EX: "0/"
    +			tmp_bfr.Add_byte(b0).Add_byte(md5[1]).Add_byte(dir_spr);						// add md5_01				EX: "01/"
    +		}
    +		else {
    +			for (int i = 0; i < md5_dir_depth; i++)
    +				tmp_bfr.Add_byte(md5[i]).Add_byte(dir_spr);									// add md5_0123				EX: "0/1/2/3"
    +		}
    +		if (wmf_dir_hive) {
    +			if (wmf_protocol_is_file)														// sitting on local file system (as opposed to http)
    +				tmp_bfr.Add(ttl);															// NOTE: file_names are not url-encoded; this includes symbols (') and foreign characters (ö)
    +			else																			// wmf_http
    +				tmp_bfr.Add(encoder_src_http.Encode(ttl));									// NOTE: file_names must be url-encoded; JAVA will default to native charset which on Windows will be 1252; foreign character urls will fail due to conversion mismatch (1252 on windows; UTF-8 on WMF); PAGE:en.w:Möbius strip
    +		}
    +		else
    +			tmp_bfr.Add(ttl);																// add title;				EX: "A.png"
    +		return this;
    +	}
    +	private Xof_url_bldr Add_thumb_xowa() {
    +		tmp_bfr.Add_byte(dir_spr);															// add dir_spr;				EX: "\"
    +		tmp_bfr.Add_int_variable(file_w).Add(Bry_px);										// add file_w;				EX: "220px"
    +		if (Xof_lnki_time.Null_n(time))
    +			tmp_bfr.Add_byte(time_dlm).Add_str_a7(Xof_lnki_time.X_str(time));				// add time					EX: "@5"
    +		else if (page != Xof_lnki_page.Null)
    +			tmp_bfr.Add_byte(Byte_ascii.Dash).Add_int_variable(page);						// add page					EX: "-5"
    +		tmp_bfr.Add_byte(Byte_ascii.Dot);													// add .					EX: "."
    +		if (file_is_thumb)
    +			tmp_bfr.Add(ext.Ext_view());													// add view_ext				EX: ".png"
    +		else
    +			tmp_bfr.Add(ext.Ext());															// add orig_ext				EX: ".svg"
    +		return this;
    +	}
    +	private Xof_url_bldr Add_thumb_wmf() {
    +		tmp_bfr.Add_byte(dir_spr);															// add dir_spr;				EX: "\"
    +		int file_ext_id = ext.Id();
    +		switch (file_ext_id) {
    +			case Xof_ext_.Id_ogg:
    +			case Xof_ext_.Id_ogv:
    +			case Xof_ext_.Id_webm:
    +				tmp_bfr.Add_int_variable(file_w);											// add file_w;				EX: "220"; PAGE:en.w:Alice_Brady; DATE:2015-08-06
    +				tmp_bfr.Add(Bry_px_dash);													// add px;					EX: "px-"
    +				if (Xof_lnki_time.Null_n(time))
    +					tmp_bfr.Add(Bry_seek).Add_str_a7(Xof_lnki_time.X_str(time)).Add_byte(Byte_ascii.Dash);// add seek;		EX: "seek%3D5-"
    +				else
    +					tmp_bfr.Add_byte(Byte_ascii.Dash);										// add mid;					EX: "-"; NOTE: was "mid-"; DATE:2015-08-06
    +				break;
    +			case Xof_ext_.Id_tif:
    +			case Xof_ext_.Id_tiff:
    +				Add_thumb_wmf_page(Bry_lossy_page1, Bry_lossy_page);
    +				tmp_bfr.Add_int_variable(file_w);											// add file_w;				EX: "220"
    +				tmp_bfr.Add(Bry_px_dash);													// add px;					EX: "px-"
    +				break;
    +			case Xof_ext_.Id_pdf:
    +			case Xof_ext_.Id_djvu:
    +				Add_thumb_wmf_page(Bry_page1, Bry_page);
    +				tmp_bfr.Add_int_variable(file_w);											// add file_w;				EX: "220"
    +				tmp_bfr.Add(Bry_px_dash);													// add px;					EX: "px-"
    +				break;
    +			default:
    +				tmp_bfr.Add_int_variable(file_w);											// add file_w;				EX: "220"
    +				tmp_bfr.Add(Bry_px_dash);													// add px;					EX: "px-"
    +				break;
    +		}
    +		int ttl_len = ttl.length;
    +		if (ttl_len > 160) {																// long file name
    +			tmp_bfr.Add(Bry_thumnbail_w_dot);
    +			tmp_bfr.Add(ext.Ext());
    +		}
    +		else
    +			tmp_bfr.Add(encoder_src_http.Encode(ttl));										// add ttl again;			EX: "A.png"
    +		switch (file_ext_id) {
    +			case Xof_ext_.Id_svg:
    +			case Xof_ext_.Id_bmp:
    +			case Xof_ext_.Id_xcf:
    +				tmp_bfr.Add_byte(Byte_ascii.Dot).Add(Xof_ext_.Bry_png);						// add .png;				EX: "A.svg" -> "A.svg.png"		NOTE: MediaWiki always adds as lowercase
    +				break;
    +			case Xof_ext_.Id_pdf:
    +			case Xof_ext_.Id_tif:															// add .jpg					EX: "A.tif" -> "A.tif.jpg"		NOTE: MediaWiki always adds as lowercase
    +			case Xof_ext_.Id_tiff:
    +			case Xof_ext_.Id_ogg:
    +			case Xof_ext_.Id_ogv:						
    +			case Xof_ext_.Id_djvu:
    +			case Xof_ext_.Id_webm:
    +				tmp_bfr.Add_byte(Byte_ascii.Dot).Add(Xof_ext_.Bry_jpg);
    +				break;
    +		}
    +		return this;
    +	}
    +	private void Add_thumb_wmf_page(byte[] bry_page_1, byte[] bry_page) {
    +		if (Xof_lnki_time.Null_y(page))
    +			tmp_bfr.Add(bry_page_1);														// add "lossy-page1-"		EX: "lossy-page1-"
    +		else {
    +			tmp_bfr.Add(bry_page);															// add "lossy-page"			EX: "lossy-page"
    +			tmp_bfr.Add_int_variable(page);													// add page					EX: 123
    +			tmp_bfr.Add_byte(Byte_ascii.Dash);												// add -					EX: "-"
    +		}
    +	}
    +	private Xof_url_bldr Clear() {
    +		root = area = ttl = md5 = null;
    +		file_w = Xof_img_size.Null;
    +		time = Xof_lnki_time.Null;
    +		ext = null;
    +		tmp_bfr.Clear();
    +		repo_tid = Xof_repo_tid_.Tid__null;
    +		return this;
    +	}
    +	public static final    byte[]
    +	  Bry_reg = Bry_.new_a7("reg.csv")
    +	, Bry_px = Bry_.new_a7("px"), Bry_px_dash = Bry_.new_a7("px-")
    +	, Bry_thumb = Bry_.new_a7("thumb")
    +	, Bry_thumnbail_w_dot = Bry_.new_a7("thumbnail.")
    +	;
    +	private static final    byte[]
    +	  Bry_lossy_page  = Bry_.new_a7("lossy-page"), Bry_page = Bry_.new_a7("page")
    +	, Bry_lossy_page1 = Bry_.new_a7("lossy-page1-"), Bry_page1 = Bry_.new_a7("page1-"), Bry_seek = Bry_.new_a7("seek%3D");
    +	public static Xof_url_bldr new_v2() {
    +		Xof_url_bldr rv = new Xof_url_bldr();
    +		rv.time_dlm = Byte_ascii.Dash;
    +		return rv;
    +	}
    +	public static final int Md5_dir_depth_2 = 2;
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_url_bldr__tst.java b/400_xowa/src/gplx/xowa/files/Xof_url_bldr__tst.java
    index a27517de8..7e3e2e9ba 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_url_bldr__tst.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_url_bldr__tst.java
    @@ -13,3 +13,53 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import org.junit.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.imgs.*;
    +public class Xof_url_bldr__tst {
    +	private Xof_url_bldr__fxt fxt = new Xof_url_bldr__fxt();
    +	@Before public void init() {fxt.Clear().Dir_spr_http_();}
    +	@Test 	public void Ogv() 			{fxt.Root_("http://test/").Md5_("d0").Ttl_("A.ogv").W_(220).Expd_src_("http://test/thumb/d/d0/A.ogv/220px--A.ogv.jpg").Test();}
    +	@Test 	public void Ogv__seek() 	{fxt.Root_("http://test/").Md5_("d0").Ttl_("A.ogv").W_(220).Expd_src_("http://test/thumb/d/d0/A.ogv/220px-seek%3D5-A.ogv.jpg").Seek_(5).Test();}
    +	@Test 	public void Ogv__no_w() 	{fxt.Root_("http://test/").Md5_("d0").Ttl_("A.ogv").W_( -1).Expd_src_("http://test/thumb/d/d0/A.ogv/-1px--A.ogv.jpg").Test();}	// TODO_OLD: use orig_w, not -1
    +	@Test 	public void Xcf() 			{fxt.Root_("http://test/").Md5_("44").Ttl_("A.xcf").W_(220).Expd_src_("http://test/thumb/4/44/A.xcf/220px-A.xcf.png").Test();}
    +	@Test 	public void Bmp() 			{fxt.Root_("http://test/").Md5_("70").Ttl_("A.bmp").W_(220).Expd_src_("http://test/thumb/7/70/A.bmp/220px-A.bmp.png").Test();}
    +	@Test 	public void Pdf() 			{fxt.Root_("http://test/").Md5_("ef").Ttl_("A.pdf").W_(220).Expd_src_("http://test/thumb/e/ef/A.pdf/page1-220px-A.pdf.jpg").Test();}
    +	@Test 	public void Pdf__page_2() 	{fxt.Root_("http://test/").Md5_("ef").Ttl_("A.pdf").W_(220).Expd_src_("http://test/thumb/e/ef/A.pdf/page2-220px-A.pdf.jpg").Page_(2).Test();}
    +	@Test 	public void Long() {
    +		String filename = String_.Repeat("A", 200) + ".png";
    +		fxt.Root_("http://test/").Md5_("14").Ttl_(filename).W_(220)
    +			.Expd_src_("http://test/thumb/1/14/" + filename + "/220px-thumbnail.png")
    +			.Test()
    +			;
    +	}
    +	@Test 	public void Math__http() 	{fxt.Repo_tid_(Xof_repo_tid_.Tid__math).Fsys_is_wnt_(Bool_.N).Root_("http://test/").Ttl_("random_md5.svg").Expd_src_("http://test/random_md5").Test();}	// NOTE: strip ".svg" if online
    +	@Test 	public void Math__file() 	{fxt.Repo_tid_(Xof_repo_tid_.Tid__math).Fsys_is_wnt_(Bool_.Y).Root_("file://xowa/").Ttl_("random_md5.svg").Expd_src_("file://xowa/random_md5.svg").Test();}	// NOTE: keep ".svg" if online
    +}
    +class Xof_url_bldr__fxt {
    +	private final    Xof_url_bldr url_bldr = new Xof_url_bldr();
    +	public Xof_url_bldr__fxt Clear() {
    +		dir_spr = Byte_.Zero; ext = null; root = md5 = ttl = expd_src = null;
    +		seek = Xof_lnki_time.Null;
    +		page = Xof_lnki_page.Null;
    +		w = Xof_img_size.Null;
    +		return this;
    +	}
    +	public Xof_url_bldr__fxt Dir_spr_http_() {return Dir_spr_(Byte_ascii.Slash);}
    +	public Xof_url_bldr__fxt Dir_spr_fsys_wnt_() {return Dir_spr_(Byte_ascii.Backslash);}
    +	public Xof_url_bldr__fxt Dir_spr_(byte v) {dir_spr = v; return this;} private byte dir_spr;
    +	public Xof_url_bldr__fxt Root_(String v) {root = v; return this;} private String root;
    +	public Xof_url_bldr__fxt Md5_(String v) {md5 = v; return this;} private String md5;
    +	public Xof_url_bldr__fxt Ttl_(String v) {ttl = v; ext = Xof_ext_.new_by_ttl_(Bry_.new_u8(v)); return this;} private String ttl; Xof_ext ext;
    +	public Xof_url_bldr__fxt W_(int v) {this.w = v; return this;} private int w;
    +	public Xof_url_bldr__fxt Page_(int v) {page = v; return this;} private int page = Xof_lnki_page.Null;
    +	public Xof_url_bldr__fxt Seek_(int v) {seek = v; return this;} private double seek = Xof_lnki_time.Null;
    +	public Xof_url_bldr__fxt Repo_tid_(byte v) {repo_tid = v; return this;} private byte repo_tid = Xof_repo_tid_.Tid__null;
    +	public Xof_url_bldr__fxt Fsys_is_wnt_(boolean v) {fsys_is_wnt = v; return this;} private boolean fsys_is_wnt;
    +	public Xof_url_bldr__fxt Expd_src_(String v) {expd_src = v; return this;} private String expd_src;
    +	public Xof_url_bldr__fxt Test() {
    +		url_bldr.Init_by_repo(repo_tid, Bry_.new_u8(root), fsys_is_wnt, dir_spr, Bool_.Y, Bool_.N, 2);
    +		url_bldr.Init_by_itm (Xof_img_mode_.Tid__thumb, Bry_.new_u8(ttl), Bry_.new_u8_safe(md5), ext, w, seek, page);
    +		Tfds.Eq(expd_src, url_bldr.Xto_str());
    +		return this;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_wkr_mgr.java b/400_xowa/src/gplx/xowa/files/Xof_wkr_mgr.java
    index a27517de8..a06d33a2e 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_wkr_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_wkr_mgr.java
    @@ -13,3 +13,22 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.xowa.files.fsdb.*;
    +import gplx.xowa.files.fsdb.fs_roots.*;
    +class Xof_wkr_mgr implements Gfo_invk {
    +	private Xow_file_mgr file_mgr;
    +	public Xof_wkr_mgr(Xow_file_mgr file_mgr) {this.file_mgr = file_mgr;}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_get))		return Get_or_new(m.ReadStr("v"));
    +		else	return Gfo_invk_.Rv_unhandled;
    +	}
    +	private static final String Invk_get = "get";
    +	private Xof_fsdb_mgr Get_or_new(String key) {
    +		if (String_.Eq(key, "fs.dir")) {
    +			return Fs_root_core.Set_fsdb_mgr(file_mgr, file_mgr.Wiki());
    +		}
    +		else
    +			throw Err_.new_unhandled(key);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_xfer_itm.java b/400_xowa/src/gplx/xowa/files/Xof_xfer_itm.java
    index a27517de8..52cef26b0 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_xfer_itm.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_xfer_itm.java
    @@ -13,3 +13,299 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.core.primitives.*;
    +import gplx.xowa.guis.cbks.js.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.imgs.*;
    +import gplx.xowa.wikis.tdbs.metas.*;
    +import gplx.xowa.parsers.utils.*;
    +public class Xof_xfer_itm implements Xof_file_itm {
    +	private Xof_url_bldr tmp_url_bldr = dflt_url_bldr;
    +	public Xof_xfer_itm() {
    +		lnki_type = orig_repo_id = Byte_.Max_value_127;
    +		lnki_w = lnki_h = file_w = orig_w = orig_h = html_w = html_h = html_gallery_mgr_h = Int_.Neg1;
    +		orig_ext = null;
    +		lnki_upright = Int_.Neg1; lnki_time = Xof_lnki_time.Null; lnki_page = Xof_lnki_page.Null;
    +		file_exists = false; file_is_orig = true;
    +		orig_file_len = 0;	// NOTE: cannot be -1, or else will always download orig; see ext rule chk and (orig_file_len < 0)
    +		orig_repo_name = orig_ttl = orig_redirect = null; lnki_ttl = null; orig_ttl_md5 = null;
    +		html_orig_url = html_view_url = Io_url_.Empty;
    +		meta_itm = null;
    +		html_uid = Int_.Neg1; html_elem_tid = Xof_html_elem.Tid_none;
    +	}
    +	public int					Lnki_exec_tid()				{return lnki_exec_tid;} private int lnki_exec_tid;
    +	public byte[]				Lnki_wiki_abrv()			{return lnki_wiki_abrv;} private byte[] lnki_wiki_abrv;
    +	public byte[]				Lnki_ttl()					{return lnki_ttl;} private byte[] lnki_ttl;
    +	public byte					Lnki_type()					{return lnki_type;} private byte lnki_type;
    +	public double				Lnki_upright()				{return lnki_upright;} private double lnki_upright;
    +	public int					Lnki_w()					{return lnki_w;} private int lnki_w;
    +	public int					Lnki_h()					{return lnki_h;} private int lnki_h;
    +	public double				Lnki_time()					{return lnki_time;} private double lnki_time;
    +	public int					Lnki_page()					{return lnki_page;} private int lnki_page;
    +	public boolean				Orig_exists()				{return orig_exists;} private boolean orig_exists;
    +	public byte					Orig_repo_id()				{return orig_repo_id;} private byte orig_repo_id;
    +	public byte[]				Orig_repo_name()			{return orig_repo_name;} private byte[] orig_repo_name;
    +	public byte[]				Orig_ttl()					{return orig_ttl;} private byte[] orig_ttl;
    +	public byte[]				Orig_ttl_md5()				{return orig_ttl_md5;} private byte[] orig_ttl_md5;
    +	public Xof_ext				Orig_ext()					{return orig_ext;} private Xof_ext orig_ext;
    +	public int					Orig_w()					{return orig_w;} private int orig_w;
    +	public int					Orig_h()					{return orig_h;} private int orig_h;
    +	public byte[]				Orig_redirect()				{return orig_redirect;} private byte[] orig_redirect;
    +	public long					Orig_file_len()				{return orig_file_len;} private long orig_file_len;	// used for filtering downloads by file_max
    +	public boolean				File_is_orig()				{return file_is_orig;} private boolean file_is_orig; // SEE:NOTE_1:Lnki_thumbable
    +	public int					File_w()					{return file_w == -1 ? html_w : file_w;} private int file_w = -1;	// NOTE: for itm_meta, file_w == html_w
    +	public int					Html_uid()					{return html_uid;} private int html_uid;
    +	public byte					Html_elem_tid()				{return html_elem_tid;} private byte html_elem_tid;
    +	public int					Html_w()					{return html_w;} private int html_w;
    +	public int					Html_h()					{return html_h;} private int html_h;
    +	public Io_url				Html_view_url()				{return html_view_url;} private Io_url html_view_url = Io_url_.Empty;	// needed else null_err
    +	public Io_url				Html_orig_url()				{return html_orig_url;} private Io_url html_orig_url = Io_url_.Empty;	// needed else null_err
    +	public int					Html_gallery_mgr_h()		{return html_gallery_mgr_h;} private int html_gallery_mgr_h;
    +	public Js_img_wkr			Html_img_wkr()				{return html_img_wkr;} private Js_img_wkr html_img_wkr;
    +	public boolean				File_exists()				{return file_exists;} private boolean file_exists;
    +	public boolean   			File_exists_in_cache()		{return false;}
    +	public boolean				Dbmeta_is_new()				{return meta_itm.State_new();}
    +	public void					Html_elem_tid_(byte v)		{html_elem_tid = v;}
    +	public void					Html_size_(int w, int h)	{this.html_w = w; this.html_h = h;}
    +	public void					Html_gallery_mgr_h_(int v)	{html_gallery_mgr_h = v;} 
    +	public void					Html_img_wkr_(Js_img_wkr v)	{html_img_wkr = v;}
    +	public int					Hdump_mode()				{return hdump_mode;} private int hdump_mode = Xof_fsdb_itm.Hdump_mode__null;
    +
    +	public void File_exists_y_() {file_exists = Bool_.Y;} public void File_exists_n_() {file_exists = Bool_.N;} public void File_exists_(boolean v) {file_exists = v;}
    +	public void Html_orig_url_(Io_url v) {html_orig_url = v;}
    +	public void Init_at_lnki(int exec_tid, byte[] wiki_abrv, byte[] ttl, byte lnki_type, double upright, int w, int h, double time, int page, int lnki_upright_patch) {
    +		this.lnki_exec_tid = exec_tid; this.lnki_wiki_abrv = wiki_abrv;
    +		this.lnki_type = lnki_type; this.lnki_upright = upright; this.lnki_w = w; this.lnki_h = h; this.lnki_time = time; this.lnki_page = page;
    +		this.file_is_orig = !Xof_xfer_itm_.Lnki_thumbable_calc(lnki_type, lnki_w, lnki_h);
    +		this.lnki_ttl = Xof_file_wkr_.Md5_decoder.Decode(Xof_file_wkr_.Ttl_standardize(ttl));
    +		this.Orig_ttl_(ttl);
    +		this.orig_ext = Xof_ext_.new_by_ttl_(ttl);
    +		if (lnki_time != Xof_lnki_time.Null && !orig_ext.Id_is_media())	// thumbtime is set, but ext is not media; PAGE:en.w:Moon; EX:[[File:A.png|thumbtime=0:02]] DATE:2014-07-22
    +			lnki_time = Xof_lnki_time.Null;								// disable thumbtime
    +	}
    +	public void Init_at_orig(byte orig_repo_id, byte[] orig_repo_name, byte[] orig_ttl, Xof_ext orig_ext, int orig_w, int orig_h, byte[] orig_redirect) {
    +		this.orig_repo_id = orig_repo_id; this.orig_repo_name = orig_repo_name;
    +		this.orig_ttl = orig_ttl; this.orig_ttl_md5 = Xof_file_wkr_.Md5(orig_ttl);
    +		this.orig_w = orig_w; this.orig_h = orig_h; this.orig_redirect = orig_redirect;
    +		if		(Bry_.Len_gt_0(orig_redirect))				// redirect exists; EX: A.png redirected to B.png
    +			this.Orig_ttl_(orig_redirect);					// update fsdb with atrs of B.png
    +		else if	(!Bry_.Eq(lnki_ttl, orig_ttl))				// ttls differ; EX: "A_.png" vs "A.png"
    +			this.Orig_ttl_(orig_ttl);
    +		else
    +			this.Orig_ttl_(orig_ttl);
    +		this.orig_ext = orig_ext;							// overwrite ext with whatever's in file_orig; needed for ogg -> oga / ogv
    +		this.orig_exists = true;
    +	}
    +	public void Init_at_gallery_bgn(int html_w, int html_h, int file_w) {
    +		this.html_w = html_w; this.html_h = html_h; 
    +		this.file_w = file_w;
    +	}
    +	public void Init_at_gallery_end(int html_w, int html_h, Io_url html_view_url, Io_url html_orig_url) {
    +		this.html_w = html_w; this.html_h = html_h; 
    +		this.html_view_url = html_view_url;
    +		this.html_orig_url = html_orig_url;
    +		this.file_exists = true;
    +	}
    +	public void			Calc_by_fsdb(int html_w, int html_h, Io_url view_url, Io_url orig_url) {
    +		this.html_w = html_w;
    +		this.html_h = html_h;
    +		this.html_orig_url = orig_url;
    +		this.html_view_url = view_url;
    +	}
    +	public void Orig_ttl_and_redirect_(byte[] ttl, byte[] redirect) {
    +		this.orig_redirect = redirect;
    +		this.lnki_ttl = orig_redirect == Xop_redirect_mgr.Redirect_null_bry ? Bry_.Copy(ttl) : orig_redirect;
    +		this.lnki_ttl = Xof_file_wkr_.Md5_decoder.Decode(Xof_file_wkr_.Ttl_standardize(lnki_ttl));	// NOTE: this line is repeated in static method below
    +		this.orig_ttl = lnki_ttl;
    +		this.orig_ttl_md5 = Xof_file_wkr_.Md5_fast(lnki_ttl);	// NOTE: md5 is calculated off of url_decoded ttl; EX: A%2Cb is converted to A,b and then md5'd. note that A%2Cb still remains the title
    +		this.orig_ext = Xof_ext_.new_by_ttl_(lnki_ttl);
    +	}
    +	private void Orig_ttl_(byte[] v) {
    +		this.orig_ttl = Xof_file_wkr_.Ttl_standardize(v);
    +		this.orig_ttl_md5 = Xof_file_wkr_.Md5_fast(v);
    +	}
    +
    +	public Xof_meta_itm			Dbmeta_itm() {return meta_itm;} private Xof_meta_itm meta_itm;
    +	public void					Trg_repo_itm_(Xof_repo_itm v) {
    +		trg_repo_itm = v;
    +		trg_repo_root = trg_repo_itm == null ? Bry_.Empty : trg_repo_itm.Root_http();
    +	} private Xof_repo_itm trg_repo_itm;
    +	public byte[]		Trg_repo_root() {return trg_repo_root;} private byte[] trg_repo_root = Bry_.Empty;	// HACK: needed for hdump
    +	public void			Ctor_for_html(int exec_tid, int lnki_upright_patch, Xof_img_size img_size, Xof_repo_itm repo, Xof_url_bldr url_bldr) {
    +		Calc_html_size(exec_tid, lnki_upright_patch, img_size);
    +		this.html_view_url = url_bldr.To_url_trg(repo, this, file_is_orig);
    +		this.html_orig_url = url_bldr.To_url_trg(repo, this, Bool_.Y);
    +	}
    +	private void Calc_html_size(int exec_tid, int lnki_upright_patch, Xof_img_size img_size) {
    +		if (!orig_ext.Id_is_media() && lnki_time != Xof_lnki_time.Null)	// file is not media, but has thumbtime; this check can't be moved to Lnki_time_() b/c it needs ext
    +			lnki_time = Xof_lnki_time.Null;								// set time to null; needed else url will reference thumbtime; PAGE:en.w:Moon; EX:[[File:Lunar libration with phase Oct 2007 450px.gif|thumbtime=0:02]]; DATE:2014-07-20
    +		if (orig_ext.Id_is_audio_strict())								// audio does not have html size calculated; everything else does
    +			this.file_is_orig = Bool_.Y;
    +		else {
    +			img_size.Html_size_calc(exec_tid, lnki_w, lnki_h, lnki_type, lnki_upright_patch, lnki_upright, orig_ext.Id(), orig_w, orig_h, Xof_img_size.Thumb_width_img);
    +			html_w = img_size.Html_w(); html_h = img_size.Html_h(); file_w = img_size.File_w();
    +			this.file_is_orig = img_size.File_is_orig();
    +		}
    +	}
    +	public void Init_by_orig_old(int w, int h, int orig_file_len) {
    +		this.orig_w = w; this.orig_h = h; this.orig_file_len = orig_file_len;
    +	}
    +	public void Orig_repo_id_(int v) {this.orig_repo_id = (byte)v;} 
    +	public void File_w_(int v) {file_w = v;}
    +	public void	Init_for_test__img(int html_w, int html_h, Io_url html_view_url, Io_url html_orig_url) {
    +		this.html_w = html_w; this.html_h = html_h; this.html_view_url = html_view_url; this.html_orig_url = html_orig_url;
    +	}
    +	public void Init_at_hdoc(int html_uid, byte html_elem_tid) {
    +		this.html_uid = html_uid; this.html_elem_tid = html_elem_tid;
    +	}
    +
    +	public void			Set__meta(Xof_meta_itm meta_itm, Xof_repo_itm trg_repo_itm, int thumb_w_img) {
    +		this.meta_itm = meta_itm; Trg_repo_itm_(trg_repo_itm); this.thumb_w_img = thumb_w_img;
    +		this.orig_w = meta_itm.Orig_w(); this.orig_h = meta_itm.Orig_h();		// orig_w / orig_h needed for imap; DATE:2014-08-08
    +	}	private int thumb_w_img;
    +	public void			Set__meta_only(Xof_meta_itm meta_itm) {this.meta_itm = meta_itm; Orig_ttl_and_redirect_(meta_itm.Ttl(), meta_itm.Ptr_ttl());}
    +	public void	Init_at_html(int exec_tid, Xof_img_size img_size, Xof_repo_itm repo, Xof_url_bldr url_bldr) {
    +		Calc_html_size(exec_tid, img_size);
    +		this.html_view_url = url_bldr.To_url_trg(repo, this, file_is_orig);
    +		this.html_orig_url = url_bldr.To_url_trg(repo, this, Bool_.Y);
    +	}
    +	private void Calc_html_size(int exec_tid, Xof_img_size img_size) {
    +		if (!orig_ext.Id_is_media() && lnki_time != Xof_lnki_time.Null)	// file is not media, but has thumbtime; this check can't be moved to Lnki_time_() b/c it needs ext
    +			lnki_time = Xof_lnki_time.Null;								// set time to null; needed else url will reference thumbtime; PAGE:en.w:Moon; EX:[[File:Lunar libration with phase Oct 2007 450px.gif|thumbtime=0:02]]; DATE:2014-07-20
    +		if (orig_ext.Id_is_audio_strict())								// audio does not have html size calculated; everything else does
    +			file_is_orig = Bool_.Y;
    +		else {
    +			img_size.Html_size_calc(exec_tid, lnki_w, lnki_h, lnki_type, Xof_patch_upright_tid_.Tid_all, lnki_upright, orig_ext.Id(), orig_w, orig_h, Xof_img_size.Thumb_width_img);
    +			html_w = img_size.Html_w(); html_h = img_size.Html_h();
    +			file_w = img_size.File_w();
    +			file_is_orig = img_size.File_is_orig();
    +		}
    +	}
    +	private Io_url		Trg_view_url(byte mode_id, int width)	{return tmp_url_bldr.Init_for_trg_file(trg_repo_itm, mode_id, lnki_ttl, orig_ttl_md5, orig_ext, width, lnki_time, lnki_page).Xto_url();}
    +	public Io_url		Trg_orig_url(byte mode_id, int width)	{return tmp_url_bldr.Init_for_trg_file(trg_repo_itm, mode_id, lnki_ttl, orig_ttl_md5, orig_ext, width, lnki_time, lnki_page).Xto_url();}
    +	public boolean			Calc_by_meta() {return Calc_by_meta(false);}
    +	public boolean			Calc_by_meta(boolean caller_is_file_page) {
    +		file_exists = false;
    +		html_orig_url = html_view_url = Io_url_.Empty;
    +		html_w = lnki_w; html_h = lnki_h;
    +		if (meta_itm == null || trg_repo_itm == null) return false;
    +		if (meta_itm.Ptr_ttl_exists()) {
    +			lnki_ttl = meta_itm.Ptr_ttl();
    +			orig_ttl_md5 = Xof_file_wkr_.Md5(lnki_ttl);
    +		}
    +		boolean limit_size = !orig_ext.Id_is_svg() || (orig_ext.Id_is_svg() && caller_is_file_page);
    +		if (orig_ext.Id_is_media() && html_w < 1)		// if media and no width, set to default; NOTE: must be set or else dynamic download will resize play button to small size; DATE:20121227
    +			html_w = Xof_img_size.Thumb_width_ogv;	
    +		if (!file_is_orig) {				// file is thumb
    +			if (orig_ext.Id_is_video()) {		// video is a special case; src is thumb_w but html_w / html_h is based on calc
    +				html_orig_url = Trg_view_url(Xof_img_mode_.Tid__orig, Xof_img_size.Size__neg1);
    +				if (meta_itm.Thumbs_indicates_oga() && orig_ext.Id_is_ogv()) {orig_ext = Xof_ext_.new_by_ext_(Xof_ext_.Bry_oga); return true;}	// if audio, do not thumb; NOTE: must happen after html_orig_bry, b/c html must still be generated to auto-download files; NOTE: must change ext to oga b/c ogg may trigger video code elsewhere
    +				Xof_meta_thumb thumb = meta_itm.Thumbs_get_vid(Xof_lnki_time.X_int(lnki_time));
    +				if (thumb != null) {
    +					Xof_xfer_itm_.Calc_xfer_size(calc_size, lnki_type, thumb_w_img, thumb.Width(), thumb.Height(), html_w, html_h, !file_is_orig, lnki_upright);
    +					html_w = calc_size.Val_0(); html_h = calc_size.Val_1(); 
    +					html_view_url = Trg_view_url(Xof_img_mode_.Tid__thumb, thumb.Width());	// NOTE: must pass thumb.Width() not html_w b/c only one thumb generated for a video file
    +					file_exists = true;
    +					return true;
    +				}
    +			}
    +			else {							// regular thumb
    +				html_orig_url = Trg_view_url(Xof_img_mode_.Tid__orig, Xof_img_size.Size__neg1);
    +				if (orig_ext.Id_is_audio()) return true;	// if audio, do not thumb; even if user requests thumb;
    +				Xof_meta_thumb[] thumbs = meta_itm.Thumbs(); int thumbs_len = thumbs.length; Xof_meta_thumb thumb = null;
    +				if (lnki_h > 0 && orig_w < 1 && thumbs_len > 0) {		// if height is specified and no orig, then iterate over thumbs to find similar height; NOTE: this is a fallback case; orig_w/h is optimal; EX: c:Jacques-Louis David and 
    +					Xof_meta_thumb largest = meta_itm.Thumbs_get_largest(thumbs_len);	// get largest thumb
    +					Xof_xfer_itm_.Calc_xfer_size(calc_size, lnki_type, thumb_w_img, largest.Width(), largest.Height(), html_w, html_h, !file_is_orig, lnki_upright, false); // use largest to calc correct width/height; note that this is needed for gallery which passes in 120,120; EX:c:Yellowstone Park
    +					int comp_height = calc_size.Val_1();
    +					for (int i = 0; i < thumbs_len; i++) {
    +						Xof_meta_thumb tmp_thumb = thumbs[i];
    +						if (Int_.Between(tmp_thumb.Height(), comp_height - 1, comp_height + 1)) {
    +							thumb = tmp_thumb;
    +							break;
    +						}
    +					}
    +					if (thumb != null) return Calc_by_meta_found(lnki_type, thumb.Width(), thumb.Height());	// thumb found
    +				}
    +				
    +				Xof_xfer_itm_.Calc_xfer_size(calc_size, lnki_type, thumb_w_img, meta_itm.Orig_w(), meta_itm.Orig_h(), html_w, html_h, !file_is_orig, lnki_upright, limit_size); // calc html_h and html_w; can differ from lnki_w, lnki_h; note that -1 width is handled by thumb_w_img
    +				html_w = calc_size.Val_0();
    +				if (html_h != -1) html_h = calc_size.Val_1(); 	// NOTE: if -1 (no height specified) do not set height; EX:Tokage_2011-07-15.jpg; DATE:2013-06-03
    +				html_view_url = Trg_view_url(Xof_img_mode_.Tid__thumb, this.File_w());
    +				thumb = meta_itm.Thumbs_get_img(html_w, 0);
    +				if (thumb == null) {						// exact thumb not found
    +					if (html_w == meta_itm.Orig_w()			// html_w matches orig_w; occurs when thumb,upright requested, but upright size is larger than orig; PAGE:en.w:St. Petersburg
    +						&& !orig_ext.Id_needs_convert()		// but ext cannot be something that needs conversion; EX: 120,90 svg may match thumb of 120,90, but .png still needs to be generated
    +						&& meta_itm.Orig_exists() == Xof_meta_itm.Exists_y
    +						) {	
    +						html_h = meta_itm.Orig_h();
    +						html_view_url = Trg_view_url(Xof_img_mode_.Tid__orig, -1);
    +						file_exists = true;
    +						return true;
    +					}
    +					if (orig_ext.Id_is_djvu()) {			// NOTE: exact djvu w thumbs are not on server; always seems to be 1 off; EX: 90 requested, but 90 doesn't exist; 89 does
    +						thumb = meta_itm.Thumbs_get_img(html_w, 1);
    +						if (thumb != null) return Calc_by_meta_found(lnki_type, thumb.Width(), thumb.Height());	// thumb found
    +					}
    +				}
    +				else {
    +					html_h = thumb.Height();
    +					file_exists = true;
    +					return true;
    +				}
    +			}
    +		}
    +		else {								// file is orig
    +			byte mode_id = orig_ext.Id_is_svg() ? Xof_img_mode_.Tid__thumb : Xof_img_mode_.Tid__orig;	// svgs will always return thumb; EX:[[A.svg]] -> A.svg.png
    +			html_view_url = html_orig_url = Trg_view_url(mode_id, this.File_w());
    +			if (meta_itm.Thumbs_indicates_oga() && orig_ext.Id_is_ogv()) {orig_ext = Xof_ext_.new_by_ext_(Xof_ext_.Bry_oga); return true;}	// if audio, do not thumb; NOTE: must happen after html_orig_bry, b/c html must still be generated to auto-download files; NOTE: must change ext to oga b/c ogg may trigger video code elsewhere
    +			if		(orig_ext.Id_is_audio()) return true;	// if audio, return true; SEE:NOTE_2
    +			else if (orig_ext.Id_is_video()) {
    +				Xof_meta_thumb thumb = meta_itm.Thumbs_get_vid(Xof_lnki_time.X_int(lnki_time));	// get thumb at lnki_time; NOTE: in most cases this will just be the 1st thumb; note that orig video files don't have an official thumb
    +				if (thumb != null) {
    +					html_w = thumb.Width(); html_h = thumb.Height();	// NOTE: take thumb_size; do not rescale to html_w, html_h b/c html_w will default to 220; native width of thumbnail should be used; DATE:2013-04-11
    +					html_view_url = Trg_view_url(Xof_img_mode_.Tid__thumb, thumb.Width());	// NOTE: must pass thumb.Width() not html_w b/c only one thumb generated for a video file
    +					file_exists = true;
    +					return true;
    +				}
    +			}
    +			if (meta_itm.Orig_exists() == Xof_meta_itm.Exists_y) {	// file found previously >>> gen html
    +				html_w = meta_itm.Orig_w(); html_h = meta_itm.Orig_h();
    +				html_view_url = Trg_view_url(mode_id, this.File_w());
    +				file_exists = true;
    +				return true;
    +			}
    +		}
    +		// file not found >>> set size to 0 and byte[] to empty
    +		html_w = lnki_w < 0 ? 0 : lnki_w;
    +		html_h = lnki_h < 0 ? 0 : lnki_h;
    +		return false;
    +	}	private Int_2_ref calc_size = new Int_2_ref();
    +	private boolean		Calc_by_meta_found(byte lnki_type, int model_w, int model_h) {
    +		Xof_xfer_itm_.Calc_xfer_size(calc_size, lnki_type, thumb_w_img, model_w, model_h, html_w, html_h, !file_is_orig, lnki_upright, false);	// recalc html_w, html_h; note that false passed b/c truncation is not needed
    +		html_w = calc_size.Val_0(); html_h = calc_size.Val_1();
    +		html_view_url = Trg_view_url(Xof_img_mode_.Tid__thumb, model_w);	// note that thumb.Width is used (the actual file width), not html_w
    +		file_exists = true;
    +		return true;
    +	}
    +	private static final    Xof_url_bldr dflt_url_bldr = new Xof_url_bldr();	// NOTE: only used by v1
    +}
    +/*
    +NOTE_1:Lnki_thumbable
    +. false only if following form
    +[[A.png]]		-> must get orig
    +. true in almost all other cases, especially if (a) type is thumb; (b) size exists; (c) upright;
    +. basically, indicates that image will be stored on wmf server as "/thumb/" url
    +[[A.png|thumb]] -> default to 220 and check for 220px
    +[[A.png|40px]]  -> check for 40px
    +[[A.png|x40px]] -> calc n width and check for npx
    +
    +NOTE_2:return true if media
    +. this seems hackish, but if Atrs_calc_for_html returns false, then file is generally added to the queue
    +. the problem is that media/audio is usually not found
    +.. so, for example, when a wiki page does pronunciation [[File:A.oga]], A.oga will return false (since it's not there)
    +.. however, unlike images which xowa will try to fetch the thumb, xowa will never fetch audios (since audios are not "visible", most will not be played, and some audios are big)
    +... the corollary is that audios are only fetched when the play buton is clicked (and via code in Xog_win_itm)
    +.. so, if false is returned, then A.oga gets added to the queue, only to be ignored by queue rules later
    +... the problem here is that the status bar will flash "downloading: File:A.oga" which is misleading
    +. so, return true so that it is never added to the queue. this depends on the "click" of the play button code to actually download the file
    +. note that video doesn't suffer from this issue, b/c video has thumbs which can or can not be found
    +*/
    \ No newline at end of file
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_xfer_itm_.java b/400_xowa/src/gplx/xowa/files/Xof_xfer_itm_.java
    index a27517de8..9798b112d 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_xfer_itm_.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_xfer_itm_.java
    @@ -13,3 +13,81 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.core.primitives.*;
    +import gplx.gfui.*; import gplx.xowa.parsers.lnkis.*;
    +public class Xof_xfer_itm_ {
    +	public static void Calc_xfer_size(Int_2_ref rv, byte lnki_type, int thumb_default_w, int file_w, int file_h, int lnki_w, int lnki_h, boolean lnki_thumb, double lnki_upright, Xof_ext ext, int exec_tid) {
    +		boolean ext_is_svg = ext.Id_is_svg();
    +		boolean limit_size = !ext_is_svg || (ext_is_svg && exec_tid == Xof_exec_tid.Tid_wiki_file);
    +		Calc_xfer_size(rv, lnki_type, thumb_default_w, file_w, file_h, lnki_w, lnki_h, lnki_thumb, lnki_upright, limit_size);
    +	}
    +	public static void Calc_xfer_size(Int_2_ref rv, byte lnki_type, int thumb_default_w, int file_w, int file_h, int lnki_w, int lnki_h, boolean lnki_thumb, double lnki_upright) {Calc_xfer_size(rv, lnki_type, thumb_default_w, file_w, file_h, lnki_w, lnki_h, lnki_thumb, lnki_upright, true);}
    +	public static void Calc_xfer_size(Int_2_ref rv, byte lnki_type, int thumb_default_w, int file_w, int file_h, int lnki_w, int lnki_h, boolean lnki_thumb, double lnki_upright, boolean thumb_width_must_be_lt_file_width) {
    +		int rv_w = lnki_w, rv_h = lnki_h;
    +		if (lnki_w < 1 && lnki_h < 1) {
    +			if (lnki_thumb)		rv_w = thumb_default_w;		// do not default to thumb if only height is set; EX: x900px should have w=0 h=900
    +			else				rv_w = file_w;
    +		}
    +		rv_w = Xof_img_size.Upright_calc(Xof_patch_upright_tid_.Tid_all, lnki_upright, rv_w, lnki_w, lnki_h, lnki_type);	// only v1 calls Calc_xfer_size
    +		if (file_w < 1)				rv.Val_all_(rv_w, rv_h);
    +		else						Xof_xfer_itm_.Calc_view(rv, lnki_type, rv_w, rv_h, file_w, file_h, thumb_width_must_be_lt_file_width);
    +	}
    +	public static void Calc_view(Int_2_ref rv, byte lnki_type, int lnki_w, int lnki_h, int file_w, int file_h, boolean thumb_width_must_be_lt_file_width) {// SEE:NOTE_1 for proc source/layout
    +		if (lnki_w == -1) lnki_w = file_w;						// lnki_w missing >>> use file_w; REF.MW:Linker.php|makeImageLink2|$hp['width'] = $file->getWidth( $page );				
    +		if (lnki_h != -1) {										// height exists; REF.MW:Generic.php|normaliseParams|if ( isset( $params['height'] ) && $params['height'] != -1 ) {
    +			if (lnki_w * file_h > lnki_h * file_w)  {			// lnki ratio > file ratio; SEE:NOTE_2;
    +				lnki_w = Calc_w(file_w, file_h, lnki_h);
    +			}
    +		}
    +		lnki_h = Scale_h(file_w, file_h, lnki_w);
    +		if (	Xop_lnki_type.Id_limits_large_size(lnki_type)	// added on DATE:2014-04-09
    +			&&	lnki_w > file_w && thumb_width_must_be_lt_file_width) {	// do not allow lnki_w > file_w; REF.MW:Generic.php|normaliseParams
    +			lnki_w = file_w; lnki_h = file_h;
    +		}
    +		rv.Val_all_(lnki_w, lnki_h);
    +	}
    +	public static int Calc_w(int file_w, int file_h, int lnki_h) {
    +		double ideal_w = (double)file_w * (double)lnki_h / (double)file_h;
    +		double ideal_w_ceil = Math_.Ceil(ideal_w);
    +		return Math_.Round(ideal_w_ceil * file_h / file_w, 0) > lnki_h
    +			? (int)Math_.Floor(ideal_w)
    +			: (int)ideal_w_ceil;
    +	}
    +	public static int Scale_h(int file_w, int file_h, int lnki_w) {
    +		return file_w == 0												// REF.MW:File.php|scaleHeight
    +			? 0
    +			: (int)Math_.Round(((double)lnki_w * file_h) / file_w, 0);	// NOTE: (double) needed else result will be int and decimal will be automatically truncated
    +	}
    +	public static boolean Lnki_thumbable_calc(byte lnki_type, int lnki_w, int lnki_h) {
    +		return 
    +			(	lnki_type == Xop_lnki_type.Id_frame && lnki_w != -1 && lnki_h != -1)
    +			||	(Xop_lnki_type.Id_defaults_to_thumb(lnki_type) || lnki_w != -1 || lnki_h != -1)
    +			;
    +	}	// SEE:NOTE_3
    +}
    +/*
    +NOTE_1:proc source/layout
    +MW calls the falling procs
    +. Linker.php|makeImageLink2
    +. Linker.php|makeThumbLink2
    +. File.php|transform
    +. Bitmap.php|normaliseParams
    +. Generic.php|normaliseParams
    +. File.php|scaleHeight
    +Note that this proc is a selective culling of the w,h setting code in the above (the procs do a lot of other checks/building)
    +also, MW's if branching can be combined. for now, emulating MW and not enforcing matching if/else 
    +
    +NOTE_2: view ratio > file ratio
    +REF.MW:ImageFunctions.php|wfFitBoxWidth
    +don't know why this logic exists; for now, articulating example
    +
    +consider file of 200,100 (2:1)
    +EX_1: view is 120,40 (3:1)
    +- (a) 120,80 or (b) 80,40
    +- (b) 80,40
    +
    +EX_2: view is 120,80 (1.5:1)
    +- (a) 120,60 or (b) 160,80
    +- (a) 120,60
    +*/
    \ No newline at end of file
    diff --git a/400_xowa/src/gplx/xowa/files/Xof_xfer_itm_tst.java b/400_xowa/src/gplx/xowa/files/Xof_xfer_itm_tst.java
    index a27517de8..7ca2f74ee 100644
    --- a/400_xowa/src/gplx/xowa/files/Xof_xfer_itm_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/Xof_xfer_itm_tst.java
    @@ -13,3 +13,51 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import org.junit.*; import gplx.core.primitives.*; import gplx.gfui.*; import gplx.xowa.files.*; import gplx.xowa.parsers.lnkis.*;
    +public class Xof_xfer_itm_tst {		
    +	@Before public void init() {fxt.ini();} Xof_xfer_itm_fxt fxt = new Xof_xfer_itm_fxt();
    +	@Test  public void Box()						{tst_Calc_view("40,50"	, "40,40"	, "40,40");}	// EX:[[File:Crystal Clear app kedit.svg|50x40px]]
    +	@Test  public void Long_w()						{tst_Calc_view("128,80"	, "720,194"	, "128,34");}	// EX:[[File:Firma B.Ohiggins.svg|128x80px|alt=|Bernardo O'Higgins's signature]]
    +	@Test  public void Long_h()						{tst_Calc_view("300,-1"	, "149,408"	, "149,408");}	// EX:[[File:Adhanema Lasva.jpg|thumb|300px|The Firman given to the Bosnian Franciscans]]
    +	@Test  public void Width_too_long()				{tst_Calc_view("100,-1"	, "40,40"	, "40,40");}	// limit to height;
    +	@Test  public void Width_missing()				{tst_Calc_view("-1,20"	, "80,40"	, "40,20");}	// calc width based on height and file_size
    +	@Test  public void Prefer_height_over_width()	{tst_Calc_view("60,20"	, "120,60"	, "40,20");}	// prefer height; if width was preferred, size would be 60,30
    +	@Test  public void Height_missing()				{tst_Calc_view("50,-1"	, "100,200"	, "50,100");}
    +	@Test  public void Explicit_ratio_large()		{tst_Calc_view("120,40"	, "200,100"	, "80,40");}		// see NOTE_1:view ratio > file ratio
    +	@Test  public void Explicit_ratio_small()		{tst_Calc_view("120,80"	, "200,100"	, "120,60");}		// see NOTE_1:view ratio > file ratio
    +	private void tst_Calc_view(String lnki_str, String file_str, String expd_str) {
    +		Int_2_ref rv = new Int_2_ref();
    +		Int_2_val lnki = Int_2_val.parse(lnki_str);
    +		Int_2_val file = Int_2_val.parse(file_str);
    +		Int_2_val expd = Int_2_val.parse(expd_str);
    +		Xof_xfer_itm_.Calc_view(rv, Xop_lnki_type.Id_thumb, lnki.Val_0(), lnki.Val_1(), file.Val_0(), file.Val_1(), true);
    +		Tfds.Eq(expd.Val_0(), rv.Val_0());
    +		Tfds.Eq(expd.Val_1(), rv.Val_1());
    +	}
    +	@Test 	public void Thumb_lnkY() 						{fxt.Lnki_(300, 200).tst(300, 200);}							// size provided; use
    +	@Test 	public void Thumb_lnkN() 						{fxt.Lnki_( -1,  -1).tst(220,  -1);}							// w=thumbnail default
    +	@Test 	public void Thumb_lnkN_sqlY() 					{fxt.Lnki_( -1,  -1).File_(220, 200).tst(220, 200);}			// w=thumbnail default; h=calc from sql
    +	@Test 	public void Thumb_lnkN_sqlY_adjH() 				{fxt.Lnki_( -1,  -1).File_(440, 500).tst(220, 250);}			// w=thumbnail default; h=calc from sql
    +	@Test 	public void Thumb_lnkW_sqlY() 					{fxt.Lnki_(200,  -1).File_(400, 500).tst(200, 250);}			// w=lnki; h=calc from sql
    +	@Test 	public void Thumb_lnkH_sqlY() 					{fxt.Lnki_( -1, 250).File_(400, 500).tst(200, 250);}			// w=calc from sql
    +	@Test 	public void Thumb_lnkW_sqlY_W_too_large() 		{fxt.Lnki_(600, 750).File_(400, 500).tst(400, 500);}			// w/h: truncate to file
    +	@Test 	public void Thumb_w_is_wrong() 					{fxt.Lnki_( 20,  20).File_( 80, 100).tst( 16,  20);}
    +	@Test 	public void Thumb_w_is_wrong_2() 				{fxt.Lnki_( 65,  50).File_(160, 160).tst( 50,  50);}
    +	@Test 	public void Thumb_size_is_wrong() 				{fxt.Lnki_(128,  80).File_(720, 194).tst(128,  34);}
    +}
    +class Xof_xfer_itm_fxt {
    +	public Xof_xfer_itm_fxt ini() {lnki_img_type = Xop_lnki_type.Id_thumb; lnki_upright = -1; file_w = file_h = lnki_w = lnki_h = -1; return this;}
    +	public Xof_xfer_itm_fxt Lnki_img_type_(byte v) {lnki_img_type = v; return this;} private byte lnki_img_type;
    +	public Xof_xfer_itm_fxt Lnki_upright_(double v) {lnki_upright = v; return this;} double lnki_upright;
    +	public Xof_xfer_itm_fxt File_(int w, int h) {file_w = w; file_h = h; return this;} private int file_w, file_h;
    +	public Xof_xfer_itm_fxt Lnki_(int w, int h) {lnki_w = w; lnki_h = h; return this;} private int lnki_w, lnki_h;
    +	public Xof_xfer_itm_fxt tst(int expd_w, int expd_h) {
    +		boolean wmf_thumbable = Xof_xfer_itm_.Lnki_thumbable_calc(lnki_img_type, lnki_w, lnki_h);
    +		Int_2_ref calc_size = new Int_2_ref();
    +		Xof_xfer_itm_.Calc_xfer_size(calc_size, Xop_lnki_type.Id_thumb, Xof_img_size.Thumb_width_img, file_w, file_h, lnki_w, lnki_h, wmf_thumbable, lnki_upright);
    +		Tfds.Eq(expd_w, calc_size.Val_0());
    +		Tfds.Eq(expd_h, calc_size.Val_1());
    +		return this;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xofv_file_mgr_tst.java b/400_xowa/src/gplx/xowa/files/Xofv_file_mgr_tst.java
    index a27517de8..05c6b992a 100644
    --- a/400_xowa/src/gplx/xowa/files/Xofv_file_mgr_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/Xofv_file_mgr_tst.java
    @@ -13,3 +13,172 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import org.junit.*; import gplx.core.primitives.*; import gplx.dbs.*;
    +import gplx.xowa.files.fsdb.*; import gplx.xowa.files.caches.*; import gplx.xowa.parsers.lnkis.*;
    +import gplx.xowa.apps.*; import gplx.xowa.wikis.*; import gplx.xowa.files.origs.*;
    +public class Xofv_file_mgr_tst {
    +//		@Before public void init() {fxt.Clear();} private final Xofv_file_mgr_fxt fxt = new Xofv_file_mgr_fxt();
    +	@After  public void term() {Gfo_usr_dlg_.Instance = Gfo_usr_dlg_.Noop;}
    +	@Test  public void Stub() {}
    +//		@Test   public void Thumb() {
    +//			fxt	.Init_orig_add(fxt.Mkr_orig().Init_comm("A.png", 440, 400))
    +//				.Init_fsdb_add(fxt.Mkr_fsdb().Init_comm_thum("A.png", 220, 200))
    +//				.Init_xfer_add(fxt.Mkr_xfer().Init_thumb(0, "A.png", 220, 200))
    +//				.Exec_process_lnki()
    +//				.Test_html_get(fxt.Mkr_html().Init(0, "file:///mem/xowa/file/comm/thumb/7/0/A.png/220px.png", 220, 200))
    +//				.Test_fsys_get("mem/xowa/file/comm/thumb/7/0/A.png/220px.png")
    +//				.Test_fsdb_download(1);
    +//				;
    +//		}
    +//		@Test   public void Orig() {
    +//			fxt	.Init_orig_add(fxt.Mkr_orig().Init_comm("A.png", 440, 400))
    +//				.Init_fsdb_add(fxt.Mkr_fsdb().Init_comm_orig("A.png", 440, 400))
    +//				.Init_xfer_add(fxt.Mkr_xfer().Init_none(0, "A.png"))
    +//				.Exec_process_lnki()
    +//				.Test_html_get(fxt.Mkr_html().Init(0, "file:///mem/xowa/file/comm/orig/7/0/A.png", 440, 400))
    +//				.Test_fsys_get("mem/xowa/file/comm/orig/7/0/A.png")
    +//				.Test_fsdb_download(1);
    +//				;
    +//		}
    +//		@Test   public void Img_size() {	// PURPOSE: test integration of Xof_img_size
    +//			fxt	.Init_orig_add(fxt.Mkr_orig().Init_comm("A.png", 440, 400))
    +//				.Init_fsdb_add(fxt.Mkr_fsdb().Init_comm_thum("A.png", 110, 100))
    +//				.Init_xfer_add(fxt.Mkr_xfer().Init_thumb(0, "A.png", Xof_img_size.Null, Xof_img_size.Null).Upright_(.5f))
    +//				.Exec_process_lnki()
    +//				.Test_html_get(fxt.Mkr_html().Init(0, "file:///mem/xowa/file/comm/thumb/7/0/A.png/110px.png", 110, 100))
    +//				.Test_fsys_get("mem/xowa/file/comm/thumb/7/0/A.png/110px.png")
    +//				.Test_fsdb_download(1);
    +//				;
    +//		}
    +//		@Test   public void Orig_mgr() {	// PURPOSE: test integration of Orig_mgr
    +//			fxt	.Init_orig_add(fxt.Mkr_orig().Init_comm_redirect("B.jpg", "A.png", 440, 400))	// B.jpg redirects to A.png
    +//				.Init_fsdb_add(fxt.Mkr_fsdb().Init_comm_thum("A.png", 220, 200))
    +//				.Init_xfer_add(fxt.Mkr_xfer().Init_thumb(0, "B.jpg", 220, 200))
    +//				.Exec_process_lnki()
    +//				.Test_html_get(fxt.Mkr_html().Init(0, "file:///mem/xowa/file/comm/thumb/7/0/A.png/220px.png", 220, 200))
    +//				.Test_fsys_get("mem/xowa/file/comm/thumb/7/0/A.png/220px.png")
    +//				.Test_fsdb_download(1);
    +//				;
    +//		}
    +//		@Test   public void Cache_exists() {
    +//			fxt	.Init_orig_add(fxt.Mkr_orig().Init_comm("A.png", 440, 400))
    +//				.Init_fsdb_add(fxt.Mkr_fsdb().Init_comm_thum("A.png", 220, 200))
    +//				.Init_xfer_add(fxt.Mkr_xfer().Init_thumb(0, "A.png", 220, 200))
    +//				.Init_cache_add(fxt.Mkr_cache().Init("comm", "A.png", Bool_.N, 220))	// add to cache
    +//				.Init_fsys_add("mem/xowa/file/comm/thumb/7/0/A.png/220px.png")			// copy file to fsys
    +//				.Exec_process_lnki()
    +//				.Test_fsdb_download(0)	// skip download
    +//				;
    +//		}
    +//		@Test   public void Cache_absent() {
    +//			fxt	.Init_orig_add(fxt.Mkr_orig().Init_comm("A.png", 440, 400))
    +//				.Init_fsdb_add(fxt.Mkr_fsdb().Init_comm_thum("A.png", 220, 200))
    +//				.Init_xfer_add(fxt.Mkr_xfer().Init_thumb(0, "A.png", 220, 200))
    +//				.Init_cache_add(fxt.Mkr_cache().Init("commons", "A.png", Bool_.N, 220))	// add to cache
    +//				.Exec_process_lnki()
    +//				.Test_fsdb_download(1)	// do download
    +//				;
    +//		}
    +}
    +//	class Xofv_file_mgr_fxt {
    +//		private Xofv_file_mgr file_mgr;
    +//		public Xof_xfer_mkr Mkr_xfer() {return mkr_xfer;} private final Xof_xfer_mkr mkr_xfer = new Xof_xfer_mkr();
    +//		public Xof_orig_itm_mkr Mkr_orig() {return mkr_orig;} private final Xof_orig_itm_mkr mkr_orig = new Xof_orig_itm_mkr();
    +//		public Xof_fsdb_mkr Mkr_fsdb() {return mkr_fsdb;} private final Xof_fsdb_mkr mkr_fsdb = new Xof_fsdb_mkr();		
    +//		public Xou_cache_itm_mkr Mkr_cache() {return mkr_cache;} private final Xou_cache_itm_mkr mkr_cache = new Xou_cache_itm_mkr();
    +//		public void Clear() {
    +//			file_mgr = new Xofv_file_mgr(Bry_.Empty);
    +//			Clear_repos();
    +//		}
    +//		private void Clear_repos() {
    +//			Xofv_repo_mgr repo_mgr = file_mgr.Repo_mgr();
    +//			Io_url root_dir = Io_url_.mem_dir_("mem/xowa/file/");
    +//			Xofv_repo_itm repo_comm = Xofv_repo_itm.new_trg_fsys(Xofv_repo_itm.Tid_val_comm, Bry_.new_a7("comm"), root_dir.GenSubDir("comm"));
    +//			Xofv_repo_itm repo_wiki = Xofv_repo_itm.new_trg_fsys(Xofv_repo_itm.Tid_val_wiki, Bry_.new_a7("wiki"), root_dir.GenSubDir("wiki"));
    +//			repo_mgr.Add(repo_comm).Add(repo_wiki);
    +//			mkr_orig.Setup_repos(repo_comm, repo_wiki);
    +//			mkr_fsdb.Setup_repos(Bry_.new_a7("comm"), Bry_.new_a7("wiki"));
    +//		}
    +//		public Xofv_file_mgr_fxt Init_xfer_add(Xof_xfer_mkr mkr)	{file_mgr.Reg(mkr.Make()); return this;}
    +//		public Xofv_file_mgr_fxt Init_cache_add(Xou_cache_itm_mkr mkr)	{mkr.Make(file_mgr.Cache_mgr()); return this;}
    +//		public Xofv_file_mgr_fxt Init_fsys_add(String s) {Io_mgr.Instance.SaveFilStr(s, ""); return this;}
    +//		public Xofv_file_mgr_fxt Exec_process_lnki() {file_mgr.Process_lnki(); return this;}
    +//		public Xofv_file_mgr_fxt Test_fsys_get(String path) {
    +//			Tfds.Eq_true(Io_mgr.Instance.ExistsFil(Io_url_.mem_fil_(path)), "fsys: " + path);
    +//			return this;
    +//		}
    +//	}
    +class Xof_orig_itm_mkr {
    +	private byte[] ttl_bry; private int ext, orig_w, orig_h; private Xofv_repo_itm repo;
    +	private byte[] redirect_bry;
    +	private Xofv_repo_itm repo_comm, repo_wiki;
    +	public Xof_orig_itm_mkr() {this.Reset();}
    +	private void Reset() {
    +		redirect_bry = Bry_.Empty;
    +	}
    +	public void Setup_repos(Xofv_repo_itm repo_comm, Xofv_repo_itm repo_wiki) {this.repo_comm = repo_comm; this.repo_wiki = repo_wiki;}
    +	public Xof_orig_itm_mkr Init_comm_redirect(String src, String trg, int orig_w, int orig_h) {return Init(Bool_.Y, src, trg, orig_w, orig_h);}
    +	public Xof_orig_itm_mkr Init_comm(String ttl_str, int orig_w, int orig_h) {return Init(Bool_.Y, ttl_str, null, orig_w, orig_h);}
    +	public Xof_orig_itm_mkr Init_wiki(String ttl_str, int orig_w, int orig_h) {return Init(Bool_.N, ttl_str, null, orig_w, orig_h);}
    +	private Xof_orig_itm_mkr Init(boolean repo_is_comm, String ttl_str, String redirect_str, int orig_w, int orig_h) {
    +		repo = repo_is_comm ? repo_comm : repo_wiki;
    +		this.ttl_bry = Bry_.new_u8(ttl_str); this.orig_w = orig_w; this.orig_h = orig_h;
    +		this.redirect_bry = redirect_str == null ? Bry_.Empty : Bry_.new_u8(redirect_str);
    +		this.ext = Xof_ext_.new_by_ttl_(ttl_bry).Id();
    +		return this;
    +	}
    +	public void Make(Xof_orig_wkr wkr) {
    +		wkr.Add_orig(repo.Tid(), ttl_bry, ext, orig_w, orig_h, redirect_bry);
    +		this.Reset();
    +	}
    +}
    +class Xof_fsdb_mkr {
    +	private byte[] repo_comm, repo_wiki, repo;
    +	private byte[] ttl_bry; private byte lnki_type; private int file_w, file_h;
    +	private double upright, time; private int page;
    +	public Xof_fsdb_mkr() {this.Reset();}
    +	public void Setup_repos(byte[] repo_comm, byte[] repo_wiki) {this.repo_comm = repo_comm; this.repo_wiki = repo_wiki;}
    +	private void Reset() {
    +		upright = Xop_lnki_tkn.Upright_null;
    +		time = Xof_lnki_time.Null;
    +		page = Xof_lnki_page.Null;
    +	}
    +	public Xof_fsdb_mkr Init_comm_thum(String ttl_str, int file_w, int file_h)	{return Init(Bool_.Y, Bool_.N, ttl_str, file_w, file_h);}
    +	public Xof_fsdb_mkr Init_comm_orig(String ttl_str, int file_w, int file_h)	{return Init(Bool_.Y, Bool_.Y, ttl_str, file_w, file_h);}
    +	public Xof_fsdb_mkr Init(boolean repo_is_commons, boolean file_is_orig, String ttl_str, int file_w, int file_h) {
    +		this.lnki_type = file_is_orig ? Xop_lnki_type.Id_none : Xop_lnki_type.Id_thumb; 
    +		this.repo = repo_is_commons ? repo_comm : repo_wiki;
    +		this.ttl_bry = Bry_.new_u8(ttl_str);
    +		this.file_w = file_w; this.file_h = file_h;
    +		return this;
    +	}
    +	public Xof_fsdb_itm Make() {
    +		Xof_fsdb_itm rv = new Xof_fsdb_itm();
    +		rv.Init_at_lnki(Xof_exec_tid.Tid_wiki_page, Bry_.new_a7("en.w"), ttl_bry, lnki_type, upright, file_w, file_h, time, page, Xof_patch_upright_tid_.Tid_all);
    +		rv.Orig_repo_name_(repo);
    +		this.Reset();
    +		return rv;
    +	}
    +}
    +class Xou_cache_itm_mkr {
    +//		private byte[] dir; private byte[] ttl; private boolean is_orig; private int w, h; private double time; private int page; private long size;
    +	public Xou_cache_itm_mkr() {this.Reset();}
    +	private void Reset() {
    +//			this.time = Xof_lnki_time.Null;
    +//			this.page = Xof_lnki_page.Null;
    +//			this.h = 200;
    +//			this.size = 1;
    +	}
    +	public Xou_cache_itm_mkr Init(String dir_str, String ttl_str, boolean is_orig, int w) {
    +//			this.dir = Bry_.new_u8(dir_str);
    +//			this.ttl = Bry_.new_u8(ttl_str);
    +//			this.is_orig = is_orig;
    +//			this.w = w;
    +		return this;
    +	}
    +	public void Make(Xof_cache_mgr cache_mgr) {
    +//			cache_mgr.Fil__make(dir, ttl, is_orig, w, h, time, page, size);
    +		this.Reset();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xofv_repo_itm.java b/400_xowa/src/gplx/xowa/files/Xofv_repo_itm.java
    index a27517de8..f0d4e235d 100644
    --- a/400_xowa/src/gplx/xowa/files/Xofv_repo_itm.java
    +++ b/400_xowa/src/gplx/xowa/files/Xofv_repo_itm.java
    @@ -13,3 +13,21 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +public class Xofv_repo_itm {
    +	Xofv_repo_itm(byte tid, byte[] key, byte dir_spr, byte[] dir_root, byte[] dir_sub_orig, byte[] dir_sub_thumb) {
    +		this.tid = tid; this.key = key; this.dir_spr = dir_spr; this.dir_root = dir_root; this.dir_sub_orig = dir_sub_orig; this.dir_sub_thumb = dir_sub_thumb;
    +	}
    +	public byte Tid() {return tid;} private final byte tid;
    +	public byte[] Key() {return key;} private final byte[] key;
    +	public byte Dir_spr() {return dir_spr;} private final byte dir_spr;
    +	public byte[] Dir_root() {return dir_root;} private final byte[] dir_root;
    +	public byte[] Dir_sub_orig() {return dir_sub_orig;} private final byte[] dir_sub_orig;
    +	public byte[] Dir_sub_thumb() {return dir_sub_thumb;} private final byte[] dir_sub_thumb;
    +	public static Xofv_repo_itm new_trg_fsys(byte tid, byte[] key, Io_url root) {
    +		return new Xofv_repo_itm(tid, key, root.Info().DirSpr_byte(), Bry_.new_u8(root.Raw()), Dir_sub_orig_dflt, Dir_sub_thumb_dflt);
    +	}
    +	public static final byte Tid_val_comm = 0, Tid_val_wiki = 1;
    +	private static final byte[] Dir_sub_orig_dflt = Bry_.new_a7("orig"), Dir_sub_thumb_dflt = Bry_.new_a7("thumb");
    +	public static final int Id_temp = 0;
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xofv_repo_mgr.java b/400_xowa/src/gplx/xowa/files/Xofv_repo_mgr.java
    index a27517de8..3cc05c394 100644
    --- a/400_xowa/src/gplx/xowa/files/Xofv_repo_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/Xofv_repo_mgr.java
    @@ -13,3 +13,20 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.core.primitives.*;
    +public class Xofv_repo_mgr {
    +	private final    Hash_adp_bry key_regy = Hash_adp_bry.cs();
    +	private final    Hash_adp tid_regy = Hash_adp_.New(); private final    Byte_obj_ref tid_key = Byte_obj_ref.zero_();
    +	public Xofv_repo_mgr Add(Xofv_repo_itm itm) {
    +		key_regy.Add(itm.Key(), itm);
    +		tid_regy.Add(Byte_obj_ref.new_(itm.Tid()), itm);
    +		return this;
    +	}
    +	public Xofv_repo_itm Get_by_key(byte[] key) {
    +		return (Xofv_repo_itm)key_regy.Get_by(key);
    +	}
    +	public Xofv_repo_itm Get_by_tid(byte tid) {
    +		return (Xofv_repo_itm)tid_regy.Get_by(tid_key.Val_(tid));
    +	}
    +}
    \ No newline at end of file
    diff --git a/400_xowa/src/gplx/xowa/files/Xog_redlink_thread.java b/400_xowa/src/gplx/xowa/files/Xog_redlink_thread.java
    index a27517de8..6a15dbebe 100644
    --- a/400_xowa/src/gplx/xowa/files/Xog_redlink_thread.java
    +++ b/400_xowa/src/gplx/xowa/files/Xog_redlink_thread.java
    @@ -13,3 +13,17 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.core.threads.*; import gplx.xowa.guis.cbks.js.*;
    +public class Xog_redlink_thread implements Gfo_thread_wkr {
    +	private final    int[] redlink_ary; private final    Xog_js_wkr js_wkr;
    +	public Xog_redlink_thread(int[] redlink_ary, Xog_js_wkr js_wkr) {this.redlink_ary = redlink_ary; this.js_wkr = js_wkr;}
    +	public String	Thread__name() {return "xowa.gui.html.redlinks.set";}
    +	public boolean	Thread__resume() {return true;}
    +	public void Thread__exec() {
    +		int len = redlink_ary.length;
    +		for (int i = 0; i < len; ++i) {
    +			js_wkr.Html_redlink(gplx.xowa.wikis.pages.lnkis.Xopg_lnki_list.Lnki_id_prefix + Int_.To_str(redlink_ary[i]));
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/Xow_file_mgr.java b/400_xowa/src/gplx/xowa/files/Xow_file_mgr.java
    index a27517de8..d5fe773a6 100644
    --- a/400_xowa/src/gplx/xowa/files/Xow_file_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/Xow_file_mgr.java
    @@ -13,3 +13,164 @@ 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.files; import gplx.*; import gplx.xowa.*;
    +import gplx.dbs.*; import gplx.dbs.cfgs.*; 
    +import gplx.xowa.files.repos.*; import gplx.xowa.files.origs.*;
    +import gplx.fsdb.*; import gplx.fsdb.meta.*; import gplx.xowa.files.fsdb.*;
    +import gplx.xowa.wikis.tdbs.metas.*;
    +public class Xow_file_mgr implements Gfo_invk {
    +	private Xof_wkr_mgr wkr_mgr;
    +	public Xow_file_mgr(Xowe_wiki wiki) {
    +		this.wiki = wiki;
    +		repo_mgr = new Xowe_repo_mgr(wiki);
    +		meta_mgr = new Xof_meta_mgr(wiki);
    +		wkr_mgr = new Xof_wkr_mgr(this);
    +	}
    +	public Fsdb_db_mgr		Db_core() {return db_core;} private Fsdb_db_mgr db_core;
    +	public Xof_orig_mgr		Orig_mgr() {return orig_mgr;} private final    Xof_orig_mgr orig_mgr = new Xof_orig_mgr();
    +	public Xof_fsdb_mode	Fsdb_mode() {
    +		if (fsdb_mode == null) {
    +			Version();
    +		}
    +		return fsdb_mode;
    +	} private Xof_fsdb_mode fsdb_mode = null;
    +	public Xowe_wiki Wiki() {return wiki;} private Xowe_wiki wiki;
    +	public byte Version() {
    +		if (version == Bool_.__byte) {
    +			Io_url file_dir = wiki.Fsys_mgr().File_dir();
    +			if (!Io_mgr.Instance.ExistsFil(file_dir.GenSubFil(Fsdb_db_mgr__v1.Mnt_name))) {
    +				version = Version_1;
    +				fsdb_mode = Xof_fsdb_mode.New__v0();
    +			}
    +			else {
    +				version = Version_2;
    +				fsdb_mode = Xof_fsdb_mode.New__v2__gui();
    +			}
    +		}
    +		return version;
    +	}	private byte version = Version_null;
    +	public boolean Version_1_y() {return this.Version() == Version_1;}
    +	public boolean Version_2_y() {return this.Version() == Version_2;}
    +	public void Version_1_y_() {version = Version_1;}	// TEST:
    +	public void Version_2_y_() {version = Version_2; fsdb_mode = Xof_fsdb_mode.New__v2__gui();}	// TEST:
    +
    +	public void Fsdb_mgr_(Xof_fsdb_mgr fsdb_mgr) {
    +		this.fsdb_mgr = fsdb_mgr;			
    +		version = Version_2;
    +	}
    +	public int Patch_upright() {
    +		Fsm_mnt_mgr mnt_mgr = fsdb_mgr.Mnt_mgr();
    +		return this.Version() == Version_1 || mnt_mgr == null
    +			? Xof_patch_upright_tid_.Tid_all
    +			: fsdb_mgr.Mnt_mgr().Patch_upright()
    +			;
    +	}
    +	public static final byte Version_null = Byte_.Max_value_127, Version_1 = 1, Version_2 = 2;
    +	public Xowe_repo_mgr Repo_mgr() {return repo_mgr;} private Xowe_repo_mgr repo_mgr;
    +	public Xof_meta_mgr  Dbmeta_mgr() {return meta_mgr;} private Xof_meta_mgr meta_mgr;
    +	public Xof_cfg_download Cfg_download() {return cfg_download;} private Xof_cfg_download cfg_download = new Xof_cfg_download();
    +	public void Init_by_wiki(Xow_wiki wiki) {
    +		cfg_download.Init_by_wiki(wiki);
    +		
    +		// if non-wmf, set fsdb_mgr to fs.dir; DATE:2017-02-01
    +		if (wiki.Domain_tid() == gplx.xowa.wikis.domains.Xow_domain_tid_.Tid__other) {
    +			String cfg_domain_str = wiki.Data__core_mgr().Db__core().Tbl__cfg().Select_str_or("xowa.bldr.session", "wiki_domain", wiki.Domain_str());		// NOTE: or is "wiki.domain" for user_wikis
    +			// FOLDER.RENAME: do not change to fs.dir if renamed; DATE:2017-02-06
    +			if (String_.Eq(cfg_domain_str, wiki.Domain_str())) {
    +				// wiki has not been renamed; use fs.dir
    +				gplx.xowa.files.fsdb.fs_roots.Fs_root_core fsdir_core = gplx.xowa.files.fsdb.fs_roots.Fs_root_core.Set_fsdb_mgr(this, this.wiki);
    +				fsdir_core.Orig_dir_(wiki.Fsys_mgr().Root_dir().GenSubDir_nest("file", "orig"));
    +			}
    +			else {
    +				// wiki has been renamed; apply "imported name" to wikis; note that this won't support renamed wikia wikis; DATE:2017-02-07
    +				byte[] cfg_domain_bry = Bry_.new_u8(cfg_domain_str);
    +				Xof_repo_pair[] repo_pairs = wiki.File__repo_mgr().Repos_ary();
    +				for (int i = 0; i < repo_pairs.length; i++) {
    +					Xof_repo_pair repo_pair = repo_pairs[i];
    +					if (Bry_.Eq(wiki.Domain_bry(), repo_pair.Trg().Wiki_domain())) {
    +						repo_pair.Wiki_domain_(cfg_domain_bry);
    +						repo_pair.Src().Wiki_domain_(cfg_domain_bry);
    +						repo_pair.Trg().Wiki_domain_(cfg_domain_bry);
    +					}
    +				} 
    +			}
    +		}
    +	}
    +	public void Cfg_set(String grp, String key, String val) {	// TEST: should only be called by tests
    +		if (test_grps == null) test_grps = Hash_adp_.New();
    +		Db_cfg_hash grp_itm = (Db_cfg_hash)test_grps.Get_by(grp);
    +		if (grp_itm == null) {
    +			grp_itm = new Db_cfg_hash(grp);
    +			test_grps.Add(grp, grp_itm);
    +		}
    +		grp_itm.Set(key, val);
    +	}	private Hash_adp test_grps;
    +	public Db_cfg_hash Cfg_get(String grp) {
    +		if (test_grps != null) {
    +			Db_cfg_hash rv = (Db_cfg_hash)test_grps.Get_by(grp);
    +			return rv == null ? new Db_cfg_hash("") : rv;
    +		}
    +		if (this.Version() == Version_1) return new Db_cfg_hash("");
    +		this.Init_file_mgr_by_load(wiki);	// make sure fsdb is init'd
    +		Fsm_mnt_itm mnt_itm = fsdb_mgr.Mnt_mgr().Mnts__get_main_or_null(); // NOTE: can be null for embeddable parser; DATE:2017-06-06
    +		return mnt_itm == null ? new Db_cfg_hash("") : mnt_itm.Cfg_mgr().Grps_get_or_load(grp);
    +	}
    +	public Xof_fsdb_mgr Fsdb_mgr() {return fsdb_mgr;} private Xof_fsdb_mgr fsdb_mgr = new Xof_fsdb_mgr__sql();
    +	public void Clear_for_tests() {	// NOTE: must clear else fsdb_mode will be cached for multiple runs; will generally be v1, but some tests will set to v2; DATE:2015-12-22
    +		version = Bool_.__byte;
    +		fsdb_mode = null;
    +	}
    +	public boolean Find_meta(Xof_xfer_itm xfer_itm) {
    +		xfer_itm.Orig_repo_id_(Xof_meta_itm.Repo_unknown);
    +		byte[] xfer_itm_ttl = xfer_itm.Lnki_ttl();
    +		xfer_itm.Orig_ttl_and_redirect_(xfer_itm_ttl, Bry_.Empty);
    +		Xof_meta_itm meta_itm = meta_mgr.Get_itm_or_new(xfer_itm_ttl, xfer_itm.Orig_ttl_md5());
    +		xfer_itm.Set__meta_only(meta_itm);
    +		if (meta_itm.State_new()) {														// meta_itm is brand new
    +			xfer_itm.Set__meta(meta_itm, repo_mgr.Repos_get_at(0).Trg(), wiki.Html_mgr().Img_thumb_width());	// default to 1st repo to prevent null_ref in xfer_mgr; questionable, but all wikis must have at least 1 repo
    +			xfer_itm.Calc_by_meta();
    +			return false;
    +		}
    +		else {																			// meta_itm exists
    +			Xof_repo_itm cur_repo = null;
    +			cur_repo = meta_itm.Repo_itm(wiki);
    +			xfer_itm.Set__meta(meta_itm, cur_repo, wiki.Html_mgr().Img_thumb_width());
    +			return xfer_itm.Calc_by_meta();
    +		}
    +	}
    +	public boolean Exists(byte[] ttl_bry) {
    +		if (this.Version_1_y()) {
    +			Xof_meta_itm meta = meta_mgr.Get_itm_or_new(ttl_bry);
    +			return meta.Orig_exists() == Bool_.Y_byte || meta.Thumbs().length != 0;
    +		}
    +		else
    +			return orig_mgr.Find_by_ttl_or_null(ttl_bry) != Xof_orig_itm.Null;
    +	}
    +	public void Init_file_mgr_by_load(Xow_wiki wiki) {
    +		if (db_core != null) return;	// already init'd
    +		if (wiki.Data__core_mgr() == null) return; // NOTE: can be null for embeddable parser; DATE:2017-06-06
    +
    +		this.db_core = Fsdb_db_mgr_.new_detect(wiki, wiki.Fsys_mgr().Root_dir(), wiki.Fsys_mgr().File_dir());
    +		if (	db_core == null			// "-file-core.xowa" not found
    +			&&	!wiki.Data__core_mgr().Props().Layout_file().Tid_is_all()	// DATE:2015-08-10
    +			)
    +			db_core = Fsdb_db_mgr__v2_bldr.Get_or_make(wiki, false);	// make it
    +		this.version = Version_2;
    +		this.fsdb_mode = Xof_fsdb_mode.New__v2__gui();
    +		orig_mgr.Init_by_wiki(wiki, fsdb_mode, db_core.File__orig_tbl_ary(), Xof_url_bldr.new_v2());
    +		fsdb_mgr.Init_by_wiki(wiki);
    +	}
    +	public void Rls() {
    +		fsdb_mgr.Rls();
    +		db_core = null;
    +	}
    +
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_repos))					return repo_mgr;
    +		else if	(ctx.Match(k, Invk_metas))					return meta_mgr;
    +		else if	(ctx.Match(k, Invk_cfg_download))			return cfg_download;	// NOTE: documented for Schnark; https://sourceforge.net/p/xowa/tickets/344/
    +		else if	(ctx.Match(k, Invk_fsdb))					return fsdb_mgr;
    +		else if	(ctx.Match(k, Invk_wkrs))					return wkr_mgr;
    +		else	return Gfo_invk_.Rv_unhandled;
    +	}	private static final String Invk_repos = "repos", Invk_metas = "metas", Invk_cfg_download = "cfg_download", Invk_fsdb = "fsdb", Invk_wkrs = "wkrs";
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/bins/Bin_fetcher.java b/400_xowa/src/gplx/xowa/files/bins/Bin_fetcher.java
    index a27517de8..959f56ea6 100644
    --- a/400_xowa/src/gplx/xowa/files/bins/Bin_fetcher.java
    +++ b/400_xowa/src/gplx/xowa/files/bins/Bin_fetcher.java
    @@ -13,3 +13,51 @@ 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.files.bins; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.primitives.*;
    +interface Bin_fetcher {
    +	boolean Save_as_url(Io_url trg);
    +	boolean Save_as_bry(Bry_obj_ref bry);
    +}
    +class Bin_fetcher_fsys implements Bin_fetcher {
    +	public void Init_src_url(Io_url src) {this.src = src;} private Io_url src;
    +	public boolean Save_as_url(Io_url trg) {
    +		try {Io_mgr.Instance.CopyFil(src, trg, true); return true;}
    +		catch (Exception exc) {Err_.Noop(exc); return false;}
    +	}
    +	public boolean Save_as_bry(Bry_obj_ref bry_ref) {
    +		try {
    +			byte[] bry = Io_mgr.Instance.LoadFilBry(src);
    +			bry_ref.Val_(bry);
    +			return true;
    +		}
    +		catch (Exception exc) {Err_.Noop(exc); return false;}
    +	}
    +}
    +class Bin_fetcher_http implements Bin_fetcher {
    +	private gplx.core.ios.IoEngine_xrg_downloadFil download = gplx.core.ios.IoEngine_xrg_downloadFil.new_("", Io_url_.Empty);
    +	public void Init_src_str(String src) {this.src = src;} private String src;
    +	public boolean Save_as_url(Io_url trg) {
    +		return download.Src_(src).Trg_(trg).Exec();
    +	}
    +	public boolean Save_as_bry(Bry_obj_ref bry_ref) {
    +		try {
    +			byte[] rv = download.Exec_as_bry(src);
    +			bry_ref.Val_(rv);
    +			return true;
    +		}
    +		catch (Exception exc) {Err_.Noop(exc); return false;}
    +	}
    +}
    +class Bin_fetcher_fsdb {
    +	public void Init_id(int id) {}
    +	public boolean Save_as_url(Io_url trg) {
    +//			IoStream s = fsdb.Get_stream(id);
    +//			s.Save(trg);
    +		return true;
    +	}
    +	public byte[] Save_to_mem() {
    +//			return db.Get_bry(id);
    +		return null;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/bins/Io_download_mgr.java b/400_xowa/src/gplx/xowa/files/bins/Io_download_mgr.java
    index a27517de8..714a66f28 100644
    --- a/400_xowa/src/gplx/xowa/files/bins/Io_download_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/bins/Io_download_mgr.java
    @@ -13,3 +13,8 @@ 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.files.bins; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.ios.*; import gplx.core.ios.streams.*;
    +public interface Io_download_mgr {
    +	Io_stream_rdr	Download_as_rdr(String src);
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/bins/Io_download_mgr_.java b/400_xowa/src/gplx/xowa/files/bins/Io_download_mgr_.java
    index a27517de8..02a87515e 100644
    --- a/400_xowa/src/gplx/xowa/files/bins/Io_download_mgr_.java
    +++ b/400_xowa/src/gplx/xowa/files/bins/Io_download_mgr_.java
    @@ -13,3 +13,17 @@ 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.files.bins; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.ios.*; import gplx.core.ios.streams.*;
    +public class Io_download_mgr_ {
    +	public static Io_download_mgr			new_system()	{return new Io_download_mgr__system();}
    +	public static Io_download_mgr__memory	new_memory()	{return new Io_download_mgr__memory();}
    +}
    +class Io_download_mgr__system implements Io_download_mgr {
    +	private final    IoEngine_xrg_downloadFil download_wkr = IoEngine_xrg_downloadFil.new_("", Io_url_.Empty); 
    +	public void Upload_data(String url, byte[] data) {throw Err_.new_unimplemented();}
    +	public Io_stream_rdr Download_as_rdr(String url) {
    +		download_wkr.Init(url, Io_url_.Empty);
    +		return download_wkr.Exec_as_rdr();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/bins/Io_download_mgr__memory.java b/400_xowa/src/gplx/xowa/files/bins/Io_download_mgr__memory.java
    index a27517de8..6eb3db290 100644
    --- a/400_xowa/src/gplx/xowa/files/bins/Io_download_mgr__memory.java
    +++ b/400_xowa/src/gplx/xowa/files/bins/Io_download_mgr__memory.java
    @@ -13,3 +13,14 @@ 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.files.bins; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.ios.*; import gplx.core.ios.streams.*;
    +public class Io_download_mgr__memory implements Io_download_mgr {
    +	private final    Ordered_hash hash = Ordered_hash_.New();
    +	public void	Clear() {hash.Clear();}
    +	public void Upload_data(String url, byte[] data) {hash.Add(url, data);}
    +	public Io_stream_rdr Download_as_rdr(String url) {
    +		byte[] data = (byte[])hash.Get_by(url); if (data == null) return Io_stream_rdr_.Noop;
    +		return Io_stream_rdr_.New__mem(data);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/bins/Xof_bin_mgr.java b/400_xowa/src/gplx/xowa/files/bins/Xof_bin_mgr.java
    index a27517de8..55d63557c 100644
    --- a/400_xowa/src/gplx/xowa/files/bins/Xof_bin_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/bins/Xof_bin_mgr.java
    @@ -13,3 +13,181 @@ 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.files.bins; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.primitives.*; import gplx.core.ios.*; import gplx.core.ios.streams.*;
    +import gplx.fsdb.meta.*;
    +import gplx.xowa.files.repos.*; import gplx.xowa.files.fsdb.*; import gplx.xowa.files.cnvs.*; import gplx.xowa.files.caches.*;
    +import gplx.xowa.bldrs.wms.*;
    +public class Xof_bin_mgr {		
    +	private final    Fsm_mnt_mgr mnt_mgr;
    +	private final    Gfo_usr_dlg usr_dlg; private final    Xow_repo_mgr repo_mgr; private final    Xof_url_bldr url_bldr = Xof_url_bldr.new_v2();
    +	private Xof_bin_wkr[] wkrs = Xof_bin_wkr_.Ary_empty; private int wkrs_len;
    +	private final    String_obj_ref resize_warning = String_obj_ref.null_(); private final    Xof_img_size tmp_size = new Xof_img_size();
    +	private final    Io_download_fmt download_fmt;
    +	private final    Io_stream_rdr_wrapper rdr_wrapper = new Io_stream_rdr_wrapper();
    +	public Xof_bin_mgr(Fsm_mnt_mgr mnt_mgr, Xow_repo_mgr repo_mgr, Xof_img_wkr_resize_img resize_wkr, Io_download_fmt download_fmt) {
    +		this.mnt_mgr = mnt_mgr; this.repo_mgr = repo_mgr; this.download_fmt = download_fmt;
    +		this.usr_dlg = Gfo_usr_dlg_.Instance;
    +		this.Resizer_(resize_wkr);
    +	}
    +	public void Resizer_(Xof_img_wkr_resize_img v) {resizer = v;} private Xof_img_wkr_resize_img resizer;
    +	public void Wkrs__del(String key) {
    +		List_adp list = List_adp_.New();
    +		for (Xof_bin_wkr wkr : wkrs) {
    +			if (String_.Eq(key, wkr.Key())) continue;
    +			list.Add(wkr);
    +		}
    +		this.wkrs = (Xof_bin_wkr[])list.To_ary(Xof_bin_wkr.class);
    +		this.wkrs_len = wkrs.length;
    +	}
    +	public void Wkrs__add(Xof_bin_wkr v) {
    +		this.wkrs = (Xof_bin_wkr[])Array_.Resize_add_one(wkrs, wkrs_len, v);
    +		++this.wkrs_len;
    +	}
    +	public Xof_bin_wkr Wkrs__get_or_null(String key) {
    +		byte tid = Xof_bin_wkr_.X_key_to_tid(key);
    +		for (int i = 0; i < wkrs_len; ++i) {
    +			Xof_bin_wkr wkr = wkrs[i];
    +			if (wkr.Tid() == tid) return wkr;
    +		}
    +		return null;
    +	}
    +	public void Rls() {
    +		mnt_mgr.Rls();
    +	}
    +	public boolean Find_to_url_as_bool(int exec_tid, Xof_fsdb_itm fsdb) {return Find_as(Bool_.Y, rdr_wrapper, exec_tid, fsdb);}
    +//		public boolean Find_to_url_as_bool3(int exec_tid, Xof_fsdb_itm fsdb) {return Find_to_url(exec_tid, fsdb) != Io_url_.Empty;}
    +//		private Io_url Find_to_url(int exec_tid, Xof_fsdb_itm fsdb) {
    +//			Io_stream_rdr rdr = Find_as_rdr(exec_tid, fsdb);
    +//			if (rdr == Io_stream_rdr_.Noop) return Io_url_.Empty;
    +//			Io_url trg = fsdb.Html_view_url();
    +//			fsdb.File_size_(rdr.Len());
    +//			if (fsdb.File_resized()) return trg;				// rdr is opened directly from trg; return its url; occurs when url goes through imageMagick / inkscape, or when thumb is already on disk;
    +//			Io_stream_wtr_.Save_rdr(trg, rdr, download_fmt);	// rdr is stream; either from http_wmf or fsdb; save to trg and return;
    +//			return trg;
    +//		}
    +	public Io_stream_rdr Find_as_rdr(int exec_tid, Xof_fsdb_itm fsdb) {
    +		rdr_wrapper.Rdr_(Io_stream_rdr_.Noop);
    +		Find_as(Bool_.N, rdr_wrapper, exec_tid, fsdb);
    +		return rdr_wrapper.Rdr();
    +	}
    +	public Io_stream_rdr Find_as_rdr3(int exec_tid, Xof_fsdb_itm fsdb) {
    +		Io_stream_rdr rv = Io_stream_rdr_.Noop;
    +		Xof_repo_itm repo = repo_mgr.Repos_get_by_wiki(fsdb.Orig_repo_name()).Trg();
    +		boolean file_is_orig = fsdb.File_is_orig();
    +		if (file_is_orig || exec_tid == Xof_exec_tid.Tid_viewer_app) {			// orig or viewer_app; note that viewer_app always return orig
    +			Io_url trg = url_bldr.To_url_trg(repo, fsdb, Bool_.Y);
    +			fsdb.Html_view_url_(trg);
    +			for (int i = 0; i < wkrs_len; i++) {
    +				Xof_bin_wkr wkr = wkrs[i];
    +				rv = wkr.Get_as_rdr(fsdb, Bool_.N, fsdb.Html_w());
    +				if (rv == Io_stream_rdr_.Noop) continue;						// orig not found; continue;
    +				fsdb.File_exists_y_();
    +				return rv;
    +			}
    +		}
    +		else {																	// thumb
    +			Io_url trg = url_bldr.To_url_trg(repo, fsdb, Bool_.N);
    +			fsdb.Html_view_url_(trg);
    +			for (int i = 0; i < wkrs_len; i++) {
    +				Xof_bin_wkr wkr = wkrs[i];
    +				rv = wkr.Get_as_rdr(fsdb, Bool_.Y, fsdb.Html_w());				// get thumb's bin
    +				if (rv != Io_stream_rdr_.Noop) {								// thumb's bin exists;
    +					fsdb.File_exists_y_();
    +					return rv;
    +				}
    +				if (fsdb.Orig_ext().Id_is_video()) continue;					// item is video; don't download orig as imageMagick can't thumbnail it; DATE:2015-06-16
    +				rv = wkr.Get_as_rdr(fsdb, Bool_.N, fsdb.Orig_w());				// thumb missing; get orig;
    +				if (rv == Io_stream_rdr_.Noop) {
    +					usr_dlg.Log_direct(String_.Format("bin_mgr:thumb not found; wkr={0} ttl={1} w={2}", wkr.Key(), fsdb.Orig_ttl(), fsdb.Lnki_w()));
    +					continue;													// nothing found; continue;
    +				}
    +				if (!wkr.Resize_allowed()) continue;
    +				Io_url orig = url_bldr.To_url_trg(repo, fsdb, Bool_.Y);			// get orig url
    +				Io_stream_wtr_.Save_rdr(orig, rv, download_fmt);
    +				boolean resized = Resize(exec_tid, fsdb, file_is_orig, orig, trg);
    +				if (!resized) continue;
    +				fsdb.File_exists_y_();
    +				rv = Io_stream_rdr_.New__raw(trg);									// return stream of resized url; (result of imageMagick / inkscape)
    +				rv.Open();
    +				return rv;
    +			}
    +		}
    +		return Io_stream_rdr_.Noop;
    +	}
    +	private boolean Find_as(boolean save_to_fsys, Io_stream_rdr_wrapper rdr_wrapper, int exec_tid, Xof_fsdb_itm fsdb) {
    +		Xof_repo_itm repo = repo_mgr.Repos_get_by_wiki(fsdb.Orig_repo_name()).Trg();
    +		boolean file_is_orig = fsdb.File_is_orig();
    +		if (file_is_orig || exec_tid == Xof_exec_tid.Tid_viewer_app) {			// orig or viewer_app; note that viewer_app always return orig
    +			Io_url trg = url_bldr.To_url_trg(repo, fsdb, Bool_.Y);
    +			fsdb.Html_view_url_(trg);
    +			for (int i = 0; i < wkrs_len; i++) {
    +				Xof_bin_wkr wkr = wkrs[i];
    +				boolean found = Get_bin(Bool_.N, fsdb.File_w(), trg, save_to_fsys, rdr_wrapper, fsdb, wkr);	// NOTE: must use File_w, not Html_w; else missing images in packed gallery in hdump; PAGE:en.w:France; DATE:2016-08-22
    +				if (found)														// orig found; return it;
    +					return Set_found(save_to_fsys, fsdb, trg, rdr_wrapper);
    +			}
    +		}
    +		else {																	// thumb
    +			Io_url trg = url_bldr.To_url_trg(repo, fsdb, Bool_.N);
    +			fsdb.Html_view_url_(trg);
    +			for (int i = 0; i < wkrs_len; i++) {
    +				Xof_bin_wkr wkr = wkrs[i];
    +				boolean found = Get_bin(Bool_.Y, fsdb.File_w(), trg, save_to_fsys, rdr_wrapper, fsdb, wkr);	// NOTE: must use File_w, not Html_w; else missing images in packed gallery in hdump; PAGE:en.w:France; DATE:2016-08-22
    +				if (found)														// thumb found; return it;
    +					return Set_found(save_to_fsys, fsdb, trg, rdr_wrapper);
    +				if (fsdb.Orig_ext().Id_is_video()) continue;					// item is video; don't download orig as imageMagick can't thumbnail it; DATE:2015-06-16
    +				if (!wkr.Resize_allowed()) continue;							// resize code below; exit early if wkr doesn't allow resize
    +				Io_url orig = url_bldr.To_url_trg(repo, fsdb, Bool_.Y);			// get orig url
    +				found = Get_bin(Bool_.N, fsdb.Orig_w(), orig, Bool_.Y, rdr_wrapper, fsdb, wkr);	// get orig; note: save_to_fsys set to true b/c imageMagick will need actual file to convert
    +				if (!found) {
    +					usr_dlg.Log_direct(String_.Format("bin_mgr:thumb not found; wkr={0} ttl={1} w={2}", wkr.Key(), fsdb.Orig_ttl(), fsdb.Lnki_w()));
    +					continue;													// orig not found; skip rest since resize can't happen;
    +				}
    +				boolean resized = Resize(exec_tid, fsdb, file_is_orig, orig, trg);
    +				if (!resized) continue;
    +				if (save_to_fsys) {	// noop; already saved to trg
    +				}
    +				else {
    +					Io_stream_rdr rdr = Io_stream_rdr_.New__raw(trg);				// return stream of resized url; (result of imageMagick / inkscape)
    +					rdr.Open();
    +					rdr_wrapper.Rdr_(rdr);
    +				}
    +				return Set_found(save_to_fsys, fsdb, trg, rdr_wrapper);
    +			}
    +		}
    +		return false;
    +	}
    +	private boolean Get_bin(boolean is_thumb, int w, Io_url trg_url, boolean save_to_fsys, Io_stream_rdr_wrapper rdr_wrapper, Xof_fsdb_itm fsdb, Xof_bin_wkr wkr) {
    +		boolean found = false;
    +		if (save_to_fsys)
    +			found = wkr.Get_to_fsys(fsdb, is_thumb, w, trg_url);
    +		else {
    +			Io_stream_rdr rdr = wkr.Get_as_rdr(fsdb, is_thumb, w);
    +			if (rdr != Io_stream_rdr_.Noop) {
    +				found = true;
    +				rdr_wrapper.Rdr_(rdr);
    +			}
    +		}
    +		return found;
    +	}
    +	private boolean Set_found(boolean save_to_fsys, Xof_fsdb_itm fsdb, Io_url fsys_url, Io_stream_rdr_wrapper rdr_wrapper) {
    +		long fsdb_len = -1;
    +		if (save_to_fsys)
    +			fsdb_len = Io_mgr.Instance.QueryFil(fsys_url).Size();
    +		else
    +			fsdb_len = rdr_wrapper.Rdr().Len();
    +		fsdb.File_size_(fsdb_len);
    +		fsdb.File_exists_y_();
    +		return true;
    +	}
    +	private boolean Resize(int exec_tid, Xof_fsdb_itm itm, boolean file_is_orig, Io_url src, Io_url trg) {			
    +		tmp_size.Html_size_calc(exec_tid, itm.Lnki_w(), itm.Lnki_h(), itm.Lnki_type(), mnt_mgr.Patch_upright(), itm.Lnki_upright(), itm.Orig_ext().Id(), itm.Orig_w(), itm.Orig_h(), Xof_img_size.Thumb_width_img);
    +		boolean rv = resizer.Resize_exec(src, trg, tmp_size.Html_w(), tmp_size.Html_h(), itm.Orig_ext().Id(), resize_warning);
    +		itm.File_resized_y_();
    +		return rv;
    +	}
    +}
    +class Io_stream_rdr_wrapper {
    +	public Io_stream_rdr Rdr() {return rdr;} public void Rdr_(Io_stream_rdr v) {rdr = v;} private Io_stream_rdr rdr;
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/bins/Xof_bin_skip_mgr.java b/400_xowa/src/gplx/xowa/files/bins/Xof_bin_skip_mgr.java
    index a27517de8..0e9b92e57 100644
    --- a/400_xowa/src/gplx/xowa/files/bins/Xof_bin_skip_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/bins/Xof_bin_skip_mgr.java
    @@ -13,3 +13,75 @@ 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.files.bins; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.ios.*; import gplx.core.ios.streams.*;
    +import gplx.fsdb.meta.*; import gplx.xowa.files.fsdb.*;
    +public class Xof_bin_skip_mgr {
    +	private Xof_bin_skip_wkr[] wkrs = new Xof_bin_skip_wkr[0]; private int wkrs_len;
    +	public Xof_bin_skip_mgr(Fsm_cfg_mgr cfg_mgr, String[] wkr_keys) {
    +		List_adp list = List_adp_.New();
    +		for (int i = 0; i < wkrs_len; ++i) {
    +			Xof_bin_skip_wkr wkr = New_wkr(cfg_mgr, wkr_keys[i]);
    +			if (wkr != null) list.Add(wkr);
    +		}
    +		this.wkrs = (Xof_bin_skip_wkr[])list.To_ary_and_clear(Xof_bin_skip_wkr.class);
    +		this.wkrs_len = wkrs.length;
    +	}
    +	public boolean Skip(Xof_fsdb_itm fsdb, Io_stream_rdr src_rdr) {
    +		for (int i = 0; i < wkrs_len; ++i) {
    +			if (wkrs[i].Skip_exec(fsdb, src_rdr)) return true;
    +		}
    +		return false;
    +	}
    +	private Xof_bin_skip_wkr New_wkr(Fsm_cfg_mgr cfg_mgr, String key) {
    +		Xof_bin_skip_wkr rv = null;
    +		if		(String_.Eq(key, Xof_bin_skip_wkr_.Key__page_gt_1))		rv = Xof_bin_skip_wkr__page_gt_1.Instance;
    +		else if	(String_.Eq(key, Xof_bin_skip_wkr_.Key__small_size))	rv = Xof_bin_skip_wkr__small_size.Instance;
    +		else															throw Err_.new_unhandled(key);
    +		if (!rv.Skip_init(cfg_mgr)) return null;
    +		return rv;
    +	}
    +	public void Skip_term(Fsm_cfg_mgr cfg_mgr) {
    +		for (int i = 0; i < wkrs_len; ++i)
    +			wkrs[i].Skip_term(cfg_mgr);
    +	}
    +}
    +interface Xof_bin_skip_wkr {
    +	String Key();
    +	boolean Skip_init(Fsm_cfg_mgr cfg_mgr);
    +	boolean Skip_exec(Xof_fsdb_itm fsdb, Io_stream_rdr src_rdr);
    +	void Skip_term(Fsm_cfg_mgr cfg_mgr);
    +}
    +class Xof_bin_skip_wkr_ {
    +	public static final String Key__page_gt_1 = "page_gt_1", Key__small_size = "small_size";
    +}
    +class Xof_bin_skip_wkr__page_gt_1 implements Xof_bin_skip_wkr {	// prior to v2.4.3; lnkis with page > 1 was mistakenly bringing down page 1; EX: [[A.pdf|page=5]] -> page1; DATE:2015-04-21
    +	public String Key() {return Xof_bin_skip_wkr_.Key__page_gt_1;}
    +	public boolean Skip_init(Fsm_cfg_mgr cfg_mgr) {return !cfg_mgr.Patch__page_gt_1();}
    +	public boolean Skip_exec(Xof_fsdb_itm fsdb, Io_stream_rdr src_rdr) {
    +		boolean rv = fsdb.Lnki_page() > 1;
    +		if (rv)
    +			Xoa_app_.Usr_dlg().Note_many("", "", "src_bin_mgr:skip page gt 1: file=~{0} width=~{1} page=~{2}", fsdb.Orig_ttl(), fsdb.Html_w(), fsdb.Lnki_page());
    +		return rv;
    +	}
    +	public void Skip_term(Fsm_cfg_mgr cfg_mgr) {
    +		cfg_mgr.Patch__save(Fsm_cfg_mgr.Key_patch__page_gt_1);
    +	}
    +        public static final    Xof_bin_skip_wkr__page_gt_1 Instance = new Xof_bin_skip_wkr__page_gt_1(); Xof_bin_skip_wkr__page_gt_1() {}
    +}
    +class Xof_bin_skip_wkr__small_size implements Xof_bin_skip_wkr {// downloads can randomly be broken; assume that anything with a small size is broken and redownload again; DATE:2015-04-21
    +	public String Key() {return Xof_bin_skip_wkr_.Key__small_size;}
    +	public boolean Skip_init(Fsm_cfg_mgr cfg_mgr) {return true;}
    +	public boolean Skip_exec(Xof_fsdb_itm fsdb, Io_stream_rdr src_rdr) {
    +		boolean rv = 
    +			src_rdr.Len() < 500									// file is small (< 500 bytes)
    +		&&	fsdb.Html_w() > 50									// only apply to images larger than 50 px (arbitrarily chosen); needed to ignore 1x1 images as well as icon-sized images
    +		&&	Xof_ext_.Id_is_image_wo_svg(fsdb.Orig_ext().Id())	// only consider images; needed b/c of file_w check; note:ignore svg which can be small
    +		;
    +		if (rv)
    +			Xoa_app_.Usr_dlg().Note_many("", "", "src_bin_mgr:skip small file: file=~{0} width=~{1} ext=~{2} len=~{3}", fsdb.Orig_ttl(), fsdb.Lnki_w(), fsdb.Orig_ext(), src_rdr.Len());
    +		return rv;
    +	}
    +	public void Skip_term(Fsm_cfg_mgr cfg_mgr) {}
    +        public static final    Xof_bin_skip_wkr__small_size Instance = new Xof_bin_skip_wkr__small_size(); Xof_bin_skip_wkr__small_size() {}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr.java b/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr.java
    index a27517de8..b7bd5953d 100644
    --- a/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr.java
    +++ b/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr.java
    @@ -13,3 +13,13 @@ 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.files.bins; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.ios.*; import gplx.core.ios.streams.*;
    +import gplx.xowa.files.fsdb.*;
    +public interface Xof_bin_wkr {
    +	byte			Tid();
    +	String			Key();
    +	boolean			Resize_allowed(); void Resize_allowed_(boolean v);
    +	Io_stream_rdr	Get_as_rdr	(Xof_fsdb_itm itm, boolean is_thumb, int w);
    +	boolean			Get_to_fsys	(Xof_fsdb_itm itm, boolean is_thumb, int w, Io_url bin_url);
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr_.java b/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr_.java
    index a27517de8..e26e9492a 100644
    --- a/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr_.java
    +++ b/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr_.java
    @@ -13,3 +13,22 @@ 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.files.bins; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +public class Xof_bin_wkr_ {
    +	public static final Xof_bin_wkr[] Ary_empty = new Xof_bin_wkr[0];
    +	public static final byte Tid_null = Byte_.Max_value_127, Tid_noop = 1, Tid_not_found = 2
    +	, Tid_fsdb_xowa = 3	, Tid_http_wmf	= 5
    +	, Tid_fsys_wmf	= 6	, Tid_fsys_xowa = 7
    +	;
    +	public static final String 
    +	  Key_fsdb_wiki = "xowa.fsdb.wiki"	, Key_http_wmf	= "xowa.http.wmf"
    +	, Key_fsys_wmf	= "xowa.fsys.wmf"	, Key_fsys_xowa = "xowa.fsys.xowa"
    +	;
    +	public static byte X_key_to_tid(String key) {
    +		if		(String_.Eq(key, Key_fsdb_wiki))	return Tid_fsdb_xowa;
    +		else if	(String_.Eq(key, Key_http_wmf))		return Tid_http_wmf;
    +		else if	(String_.Eq(key, Key_fsys_wmf))		return Tid_fsys_wmf;
    +		else if	(String_.Eq(key, Key_fsys_xowa))	return Tid_fsys_xowa;
    +		else										return Tid_null;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr__fsdb_sql.java b/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr__fsdb_sql.java
    index a27517de8..b3741ac58 100644
    --- a/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr__fsdb_sql.java
    +++ b/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr__fsdb_sql.java
    @@ -13,3 +13,116 @@ 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.files.bins; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.dbs.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; import gplx.core.caches.*; import gplx.xowa.files.fsdb.*;
    +import gplx.fsdb.*; import gplx.fsdb.data.*; import gplx.fsdb.meta.*;
    +public class Xof_bin_wkr__fsdb_sql implements Xof_bin_wkr {
    +	private final    Xof_bin_wkr_ids tmp_ids = new Xof_bin_wkr_ids();
    +	private Xof_bin_skip_mgr skip_mgr;
    +	Xof_bin_wkr__fsdb_sql(Fsm_mnt_mgr mnt_mgr) {this.mnt_mgr = mnt_mgr;}
    +	public byte Tid() {return Xof_bin_wkr_.Tid_fsdb_xowa;}
    +	public String Key() {return Xof_bin_wkr_.Key_fsdb_wiki;}
    +	public Fsm_mnt_mgr Mnt_mgr() {return mnt_mgr;} private final    Fsm_mnt_mgr mnt_mgr;
    +	public boolean Resize_allowed() {return bin_wkr_resize;} public void Resize_allowed_(boolean v) {bin_wkr_resize = v;} private boolean bin_wkr_resize = false;		
    +	public Xof_bin_skip_mgr Skip_mgr() {return skip_mgr;}
    +	public void Skip_mgr_init(Fsm_cfg_mgr cfg_mgr, String[] wkrs) {this.skip_mgr = new Xof_bin_skip_mgr(cfg_mgr, wkrs);}
    +	public Io_stream_rdr Get_as_rdr(Xof_fsdb_itm fsdb, boolean is_thumb, int w) {
    +		Find_ids(fsdb, is_thumb, w);
    +		int bin_db_id = tmp_ids.Bin_db_id(); if (bin_db_id == Fsd_bin_tbl.Bin_db_id_null) return Io_stream_rdr_.Noop;
    +		Fsm_bin_fil bin_db = mnt_mgr.Bins__at(tmp_ids.Mnt_id(), bin_db_id);
    +		Io_stream_rdr rdr = bin_db.Select_as_rdr(tmp_ids.Itm_id());
    +		if (skip_mgr != null && skip_mgr.Skip(fsdb, rdr)) return Io_stream_rdr_.Noop;
    +		return rdr;
    +	}
    +	public boolean Get_to_fsys(Xof_fsdb_itm itm, boolean is_thumb, int w, Io_url bin_url) {return Get_to_fsys(itm.Orig_repo_name(), itm.Orig_ttl(), itm.Orig_ext(), is_thumb, w, itm.Lnki_time(), itm.Lnki_page(), bin_url);}
    +	private boolean Get_to_fsys(byte[] orig_repo, byte[] orig_ttl, Xof_ext orig_ext, boolean lnki_is_thumb, int file_w, double lnki_time, int lnki_page, Io_url file_url) {
    +		Find_ids(orig_repo, orig_ttl, orig_ext.Id(), lnki_time, lnki_page, lnki_is_thumb, file_w);
    +		int bin_db_id = tmp_ids.Bin_db_id(); if (bin_db_id == Fsd_bin_tbl.Bin_db_id_null) return false;
    +		Fsm_bin_fil bin_db = mnt_mgr.Bins__at(tmp_ids.Mnt_id(), bin_db_id);
    +		return bin_db.Select_to_url(tmp_ids.Itm_id(), file_url);
    +	}
    +	public Io_stream_rdr Get_to_fsys_near(Xof_fsdb_itm rv, byte[] orig_repo, byte[] orig_ttl, Xof_ext orig_ext, double lnki_time, int lnki_page) {
    +		Fsd_thm_itm thm_itm = Fsd_thm_itm.new_();
    +		thm_itm.Init_by_req(Int_.Max_value, lnki_time, lnki_page);
    +		boolean found = Select_thm_bin(Bool_.N, thm_itm, orig_repo, orig_ttl);
    +		if (found) {
    +			tmp_ids.Init_by_thm(found, thm_itm);
    +			rv.Init_by_fsdb_near(Bool_.N, thm_itm.W());
    +		}
    +		else {
    +			Fsd_fil_itm fil_itm = Select_fil_bin(orig_repo, orig_ttl);		// find orig
    +			if (fil_itm == Fsd_fil_itm.Null) return Io_stream_rdr_.Noop;
    +			tmp_ids.Init_by_fil(fil_itm);
    +			rv.Init_by_fsdb_near(Bool_.Y, rv.Orig_w());
    +		}
    +		Fsm_bin_fil bin_db = mnt_mgr.Bins__at(tmp_ids.Mnt_id(), tmp_ids.Bin_db_id());
    +		return bin_db.Select_as_rdr(tmp_ids.Itm_id());
    +	}
    +	private void Find_ids(Xof_fsdb_itm itm, boolean is_thumb, int w) {Find_ids(itm.Orig_repo_name(), itm.Orig_ttl(), itm.Orig_ext().Id(), itm.Lnki_time(), itm.Lnki_page(), is_thumb, w);}
    +	private void Find_ids(byte[] orig_repo, byte[] orig_ttl, int orig_ext, double lnki_time, int lnki_page, boolean is_thumb, int w) {
    +		synchronized (tmp_ids) {
    +			byte[] dir = orig_repo, fil = orig_ttl;
    +			if (is_thumb) {
    +				Fsd_thm_itm thm_itm = Fsd_thm_itm.new_();
    +				thm_itm.Init_by_req(w, lnki_time, lnki_page);
    +				boolean found = Select_thm_bin(Bool_.Y, thm_itm, dir, fil);
    +				tmp_ids.Init_by_thm(found, thm_itm);
    +			}
    +			else {
    +				Fsd_fil_itm fil_itm = Select_fil_bin(dir, fil);
    +				tmp_ids.Init_by_fil(fil_itm);
    +			}
    +		}
    +	}
    +	private Fsd_fil_itm	Select_fil_bin(byte[] dir, byte[] fil) {
    +		int len = mnt_mgr.Mnts__len();
    +		for (int i = 0; i < len; i++) {
    +			Fsd_fil_itm rv = mnt_mgr.Mnts__get_at(i).Select_fil_or_null(dir, fil);
    +			if (	rv != Fsd_fil_itm.Null 
    +				&&	rv.Bin_db_id() != Fsd_bin_tbl.Bin_db_id_null) {	// NOTE: mnt_0 can have thumb, but mnt_1 can have itm; check for itm with Db_bin_id; DATE:2013-11-16
    +				return rv;
    +			}
    +		}
    +		return Fsd_fil_itm.Null;
    +	}
    +	private boolean Select_thm_bin(boolean exact, Fsd_thm_itm rv, byte[] dir, byte[] fil) {
    +		int len = mnt_mgr.Mnts__len();
    +		for (int i = 0; i < len; i++) {
    +			boolean exists = mnt_mgr.Mnts__get_at(i).Select_thm(exact, rv, dir, fil);
    +			if (exists) return true;
    +		}
    +		return false;
    +	}
    +	public void Txn_bgn() {mnt_mgr.Mnts__get_insert().Txn_bgn();}
    +	public void Txn_end() {mnt_mgr.Mnts__get_insert().Txn_end();}
    +        public static Xof_bin_wkr__fsdb_sql new_(Fsm_mnt_mgr mnt_mgr) {return new Xof_bin_wkr__fsdb_sql(mnt_mgr);}
    +}
    +class Xof_bin_wkr_ids {
    +	public Xof_bin_wkr_ids() {this.Clear();}
    +	public int Mnt_id() {return mnt_id;} private int mnt_id;
    +	public int Bin_db_id() {return bin_db_id;} private int bin_db_id;
    +	public int Itm_id() {return itm_id;} private int itm_id;
    +	public void Init_by_thm(boolean found, Fsd_thm_itm thm) {
    +		if (found) {
    +			this.mnt_id = thm.Mnt_id();
    +			this.bin_db_id = thm.Bin_db_id();
    +			this.itm_id = thm.Thm_id();
    +		}
    +		else
    +			this.Clear();
    +	}
    +	public void Init_by_fil(Fsd_fil_itm fil) {
    +		if (fil == Fsd_fil_itm.Null)
    +			this.Clear();
    +		else {
    +			this.mnt_id = fil.Mnt_id();
    +			this.bin_db_id = fil.Bin_db_id();
    +			this.itm_id = fil.Fil_id();
    +		}
    +	}
    +	private void Clear() {
    +		this.mnt_id = -1;
    +		this.bin_db_id = Fsd_bin_tbl.Bin_db_id_null;
    +		this.itm_id = -1;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr__fsys_base.java b/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr__fsys_base.java
    index a27517de8..1a3a6f66f 100644
    --- a/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr__fsys_base.java
    +++ b/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr__fsys_base.java
    @@ -13,3 +13,56 @@ 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.files.bins; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.ios.*; import gplx.core.ios.streams.*; import gplx.core.envs.*;
    +import gplx.xowa.files.fsdb.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.imgs.*;
    +public abstract class Xof_bin_wkr__fsys_base implements Xof_bin_wkr, Gfo_invk {
    +	public Xof_bin_wkr__fsys_base() {}
    +	public abstract byte Tid();
    +	public abstract String Key();
    +	public boolean Resize_allowed() {return resize_allowed;} public void Resize_allowed_(boolean v) {resize_allowed = v;} private boolean resize_allowed = false;
    +	public Io_stream_rdr Get_as_rdr(Xof_fsdb_itm itm, boolean is_thumb, int w) {
    +		Io_url src_url = this.Get_src_url(Xof_img_mode_.By_bool(is_thumb), String_.new_u8(itm.Orig_repo_name()), itm.Orig_ttl(), itm.Orig_ttl_md5(), itm.Orig_ext(), w, itm.Lnki_time(), itm.Lnki_page());
    +		return (src_url == Io_url_.Empty) ? gplx.core.ios.streams.Io_stream_rdr_.Noop : gplx.core.ios.streams.Io_stream_rdr_.New__raw(src_url);
    +	}
    +	public boolean Get_to_fsys(Xof_fsdb_itm itm, boolean is_thumb, int w, Io_url bin_url) {
    +		return Get_to_fsys(itm.Orig_repo_name(), itm.Orig_ttl(), itm.Orig_ttl_md5(), itm.Orig_ext(), is_thumb, w, itm.Lnki_time(), itm.Lnki_page(), bin_url);
    +	}
    +	private boolean Get_to_fsys(byte[] orig_repo, byte[] orig_ttl, byte[] orig_md5, Xof_ext orig_ext, boolean lnki_is_thumb, int file_w, double lnki_time, int lnki_page, Io_url file_url) {
    +		Io_url src_url = this.Get_src_url(Xof_img_mode_.By_bool(lnki_is_thumb), String_.new_u8(orig_repo), orig_ttl, orig_md5, orig_ext, file_w, lnki_time, lnki_page);
    +		if (src_url == Io_url_.Empty) return false;
    +		byte[] bin = Io_mgr.Instance.LoadFilBry(src_url);
    +		return bin != Io_mgr.LoadFilBry_fail;
    +	}
    +	protected abstract Io_url Get_src_url(byte mode, String wiki, byte[] ttl_wo_ns, byte[] md5, Xof_ext ext, int w, double time, int page);
    +	public abstract void Url_(Io_url v);
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_url_))		this.Url_(m.ReadIoUrl("v"));
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}	private static final String Invk_url_ = "url_";
    +}
    +abstract class Xof_bin_wkr__fsys_wmf_base extends Xof_bin_wkr__fsys_base {
    +	public Xof_url_bldr Url_bldr() {return url_bldr;} private Xof_url_bldr url_bldr = new Xof_url_bldr();
    +	public abstract void Init_by_root();
    +	@Override public void Url_(Io_url v) {url_bldr.Root_(Bry_.new_u8(v.Raw()));}
    +	@Override protected Io_url Get_src_url(byte mode, String wiki, byte[] ttl_wo_ns, byte[] md5, Xof_ext ext, int w, double time, int page) {
    +		return this.Url_bldr().Init_by_itm(mode, ttl_wo_ns, md5, ext, w, time, page).Xto_url();
    +	}
    +}
    +class Xof_bin_wkr__fsys_wmf extends Xof_bin_wkr__fsys_wmf_base {
    +	@Override public byte Tid() {return Xof_bin_wkr_.Tid_fsys_wmf;}
    +	@Override public String Key() {return Xof_bin_wkr_.Key_fsys_wmf;}
    +	@Override public void Init_by_root() {
    +		this.Url_bldr().Init_by_repo(Xof_repo_tid_.Tid__null, Bry_.Empty, Op_sys.Cur().Tid_is_wnt(), Op_sys.Cur().Fsys_dir_spr_byte(), Bool_.Y, Bool_.Y, Md5_dir_depth__wmf);
    +	}
    +	private static final int Md5_dir_depth__wmf = 2;
    +}
    +class Xof_bin_wkr__fsys_xowa extends Xof_bin_wkr__fsys_wmf_base {
    +	@Override public byte Tid() {return Xof_bin_wkr_.Tid_fsys_xowa;}
    +	@Override public String Key() {return Xof_bin_wkr_.Key_fsys_xowa;}
    +	@Override public void Init_by_root() {
    +		this.Url_bldr().Init_by_repo(Xof_repo_tid_.Tid__null, Bry_.Empty, Op_sys.Cur().Tid_is_wnt(), Op_sys.Cur().Fsys_dir_spr_byte(), Bool_.N, Bool_.N, Md5_dir_depth__xowa);
    +	}
    +	private static final int Md5_dir_depth__xowa = 4;
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr__http_wmf.java b/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr__http_wmf.java
    index a27517de8..7562d3fe1 100644
    --- a/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr__http_wmf.java
    +++ b/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr__http_wmf.java
    @@ -13,3 +13,72 @@ 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.files.bins; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.ios.*; import gplx.core.ios.streams.*; import gplx.core.threads.*;
    +import gplx.xowa.apps.*;
    +import gplx.xowa.files.fsdb.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.imgs.*;
    +import gplx.xowa.wikis.domains.*;
    +public class Xof_bin_wkr__http_wmf implements Xof_bin_wkr {
    +	private final    Xow_repo_mgr repo_mgr; private final    IoEngine_xrg_downloadFil download_wkr; 
    +	private final    Io_download_mgr download_mgr;
    +	private final    Xof_url_bldr url_bldr = new Xof_url_bldr();
    +	public Xof_bin_wkr__http_wmf(Xow_repo_mgr repo_mgr, Io_download_mgr download_mgr, IoEngine_xrg_downloadFil download_wkr) {
    +		this.repo_mgr = repo_mgr; this.download_mgr = download_mgr; this.download_wkr = download_wkr;
    +	}
    +	public byte Tid() {return Xof_bin_wkr_.Tid_http_wmf;}
    +	public String Key() {return Xof_bin_wkr_.Key_http_wmf;}
    +	public boolean Resize_allowed() {return bin_wkr_resize;} public void Resize_allowed_(boolean v) {bin_wkr_resize = v;} private boolean bin_wkr_resize = true;
    +	public int Fail_timeout() {return fail_timeout;} public Xof_bin_wkr__http_wmf Fail_timeout_(int v) {fail_timeout = v; return this;} private int fail_timeout = 0;	// NOTE: always default to 0; manually set to 1000 for fsdb_make only; DATE:2014-06-21
    +	public Io_stream_rdr Get_as_rdr(Xof_fsdb_itm fsdb, boolean is_thumb, int w) {
    +		String src = Make_src(fsdb.Orig_repo_name(), fsdb.Orig_ttl(), fsdb.Orig_ttl_md5(), fsdb.Orig_ext(), is_thumb, w, fsdb.Lnki_time(), fsdb.Lnki_page(), Io_url_.Empty);
    +		Io_stream_rdr rdr = download_mgr.Download_as_rdr(src);
    +		boolean rv = rdr.Exists();	// NOTE: use Exists which detects for response_code 200, not content length > 0; DATE:2015-05-20
    +		if (!rv) {
    +			Handle_error();
    +			if (!rv && fsdb.Orig_repo_id() == Xof_repo_tid_.Tid__local) {	// image is not found in local; check commons; occurs with bldr which relies on inaccurate data in image dumps; PAGE:en.w:Apollo_13; DATE:2015-08-05
    +				src = Make_src(Xow_domain_itm_.Bry__commons, fsdb.Orig_ttl(), fsdb.Orig_ttl_md5(), fsdb.Orig_ext(), is_thumb, w, fsdb.Lnki_time(), fsdb.Lnki_page(), Io_url_.Empty);
    +				rdr = download_mgr.Download_as_rdr(src);
    +				rv = rdr.Exists();
    +				if (rv)
    +					fsdb.Change_repo(Xof_repo_tid_.Tid__remote, Xow_domain_itm_.Bry__commons);	// set commons.wikimedia.org; DATE:2015-08-05
    +				else
    +					Handle_error();
    +			}
    +		}
    +		if (rv) fsdb.Fsdb_insert_y_();
    +		return rv ? rdr : Io_stream_rdr_.Noop;
    +	}
    +	public boolean Get_to_fsys(Xof_fsdb_itm fsdb, boolean is_thumb, int w, Io_url bin_url) {
    +		boolean rv = Get_to_fsys(fsdb.Orig_repo_name(), fsdb.Orig_ttl(), fsdb.Orig_ttl_md5(), fsdb.Orig_ext(), is_thumb, w, fsdb.Lnki_time(), fsdb.Lnki_page(), bin_url);
    +		if (rv) fsdb.Fsdb_insert_y_();
    +		return rv;
    +	}
    +	private boolean Get_to_fsys(byte[] orig_repo, byte[] orig_ttl, byte[] orig_md5, Xof_ext orig_ext, boolean lnki_is_thumb, int file_w, double lnki_time, int lnki_page, Io_url file_url) {
    +		Init_download(orig_repo, orig_ttl, orig_md5, orig_ext, lnki_is_thumb, file_w, lnki_time, lnki_page, file_url);
    +		boolean rv = download_wkr.Exec();
    +		if (!rv) Handle_error();
    +		return rv;
    +	}
    +	private void Handle_error() {
    +		if (fail_timeout > 0)
    +			Thread_adp_.Sleep(fail_timeout);	// as per WMF policy, pause 1 second for every cache miss; http://lists.wikimedia.org/pipermail/wikitech-l/2013-September/071948.html
    +	}
    +	private void Init_download(byte[] orig_repo, byte[] orig_ttl, byte[] orig_md5, Xof_ext orig_ext, boolean lnki_is_thumb, int file_w, double lnki_time, int lnki_page, Io_url file_url) {
    +		byte mode = lnki_is_thumb ? Xof_img_mode_.Tid__thumb : Xof_img_mode_.Tid__orig;
    +		Xof_repo_pair repo_itm = repo_mgr.Repos_get_by_wiki(orig_repo);
    +		String src = url_bldr.Init_for_src_file(repo_itm.Src(), mode, orig_ttl, orig_md5, orig_ext, file_w, lnki_time, lnki_page).Xto_str();
    +		download_wkr.Init(src, file_url);
    +	}
    +	private String Make_src(byte[] orig_repo, byte[] orig_ttl, byte[] orig_md5, Xof_ext orig_ext, boolean lnki_is_thumb, int file_w, double lnki_time, int lnki_page, Io_url file_url) {
    +		byte mode = lnki_is_thumb ? Xof_img_mode_.Tid__thumb : Xof_img_mode_.Tid__orig;
    +		Xof_repo_pair repo_itm = repo_mgr.Repos_get_by_wiki(orig_repo);
    +		return url_bldr.Init_for_src_file(repo_itm.Src(), mode, orig_ttl, orig_md5, orig_ext, file_w, lnki_time, lnki_page).Xto_str();
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_fail_timeout_))		fail_timeout = m.ReadInt("v");
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}	private static final String Invk_fail_timeout_ = "fail_timeout_";
    +	public static Xof_bin_wkr__http_wmf new_(Xow_wiki wiki)										{return new_(wiki, Io_download_mgr_.new_system());}
    +	public static Xof_bin_wkr__http_wmf new_(Xow_wiki wiki, Io_download_mgr download_mgr)	{return new Xof_bin_wkr__http_wmf(wiki.File__repo_mgr(), download_mgr, wiki.App().Wmf_mgr().Download_wkr().Download_xrg());}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr__http_wmf__tst.java b/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr__http_wmf__tst.java
    index a27517de8..31e987f1d 100644
    --- a/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr__http_wmf__tst.java
    +++ b/400_xowa/src/gplx/xowa/files/bins/Xof_bin_wkr__http_wmf__tst.java
    @@ -13,3 +13,53 @@ 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.files.bins; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import org.junit.*; import gplx.core.ios.*; import gplx.core.ios.streams.*; import gplx.xowa.files.repos.*;
    +public class Xof_bin_wkr__http_wmf__tst {
    +	private final    Xof_bin_wkr__http_wmf__fxt fxt = new Xof_bin_wkr__http_wmf__fxt();
    +	@Before public void init() {fxt.Clear();}
    +	@Test   public void Basic() {
    +		fxt.Init__Http("mem/http/commons.wikimedia.org/thumb/7/70/A.png/220px-A.png", "test_data");
    +		fxt.Exec__Get_as_rdr(fxt.Fsdb_itm_mkr().Lnki__en_w("A.png").Orig__commons__lnki(), Bool_.Y, 220);
    +		fxt.Test__Get_as_rdr__rdr("test_data");
    +	}
    +	@Test   public void Enwiki_fails__fallback_to_commons() {
    +		fxt.Init__Http("mem/http/commons.wikimedia.org/thumb/7/70/A.png/220px-A.png", "test_data");			// put file in commons
    +		Xof_fsdb_itm fsdb_itm = fxt.Fsdb_itm_mkr().Lnki__en_w("A.png").Orig__enwiki__lnki().Make();
    +		fxt.Exec__Get_as_rdr(fsdb_itm, Bool_.Y, 220);														// look in enwiki
    +		fxt.Test__Get_as_rdr__rdr("test_data");																// test that enwiki tries commons again
    +		Tfds.Eq_str("commons.wikimedia.org", fsdb_itm.Orig_repo_name(), "repo_name");						// test that it's now commons
    +		Tfds.Eq_byte(Xof_repo_tid_.Tid__remote, fsdb_itm.Orig_repo_id(), "repo_tid");						// test that it's now commons
    +	}
    +	@Test   public void Long_filename_becomes_thumbnail() {
    +		String filename = String_.Repeat("A", 200) + ".png";
    +		fxt.Init__Http("mem/http/commons.wikimedia.org/thumb/1/14/" + filename + "/220px-thumbnail.png", "test_data");	// add file as "thumbnail.png"
    +		Xof_fsdb_itm fsdb_itm = fxt.Fsdb_itm_mkr().Lnki__en_w(filename).Orig__enwiki__lnki().Make();
    +		fxt.Exec__Get_as_rdr(fsdb_itm, Bool_.Y, 220);														// look in enwiki
    +		fxt.Test__Get_as_rdr__rdr("test_data");																// test that file is there
    +	}
    +}
    +class Xof_bin_wkr__http_wmf__fxt {
    +	private final    Xof_bin_wkr__http_wmf wkr;
    +	private final    Io_download_mgr__memory download_mgr;
    +	private Io_stream_rdr get_as_rdr__rdr;
    +	public Xof_fsdb_itm_fxt Fsdb_itm_mkr() {return fsdb_itm_mkr;} private final    Xof_fsdb_itm_fxt fsdb_itm_mkr = new Xof_fsdb_itm_fxt();
    +	public Xof_bin_wkr__http_wmf__fxt() {
    +		Xoae_app app = Xoa_app_fxt.Make__app__edit();
    +		Xowe_wiki wiki = Xoa_app_fxt.Make__wiki__edit(app);
    +		Xoa_app_fxt.repo2_(app, wiki);
    +		this.download_mgr = Io_download_mgr_.new_memory();
    +		this.wkr = Xof_bin_wkr__http_wmf.new_(wiki, download_mgr);
    +	}
    +	public void Clear() {
    +		download_mgr.Clear();
    +	}
    +	public void Init__Http(String url, String data) {download_mgr.Upload_data(url, Bry_.new_u8(data));}
    +	public void Exec__Get_as_rdr(Xof_fsdb_itm_fxt fsdb_itm_mkr, boolean is_thumb, int w) {Exec__Get_as_rdr(fsdb_itm_mkr.Make(), is_thumb, w);}
    +	public void Exec__Get_as_rdr(Xof_fsdb_itm fsdb_itm , boolean is_thumb, int w) {
    +		this.get_as_rdr__rdr = wkr.Get_as_rdr(fsdb_itm, is_thumb, w);
    +	}
    +	public void Test__Get_as_rdr__rdr(String expd) {
    +		Tfds.Eq_str(expd, Io_stream_rdr_.Load_all_as_str(get_as_rdr__rdr), "rdr_contents");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xof_cache_mgr.java b/400_xowa/src/gplx/xowa/files/caches/Xof_cache_mgr.java
    index a27517de8..494451e49 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xof_cache_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xof_cache_mgr.java
    @@ -13,3 +13,67 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.primitives.*; import gplx.core.envs.*;
    +import gplx.dbs.*;
    +import gplx.xowa.files.fsdb.*; import gplx.xowa.wikis.*;
    +public class Xof_cache_mgr implements Gfo_invk {
    +	private final    Gfo_usr_dlg usr_dlg; private final    Xoae_wiki_mgr wiki_mgr; private final    Xoa_repo_mgr repo_mgr;
    +	private final    Xofc_cfg_mgr cfg_mgr = new Xofc_cfg_mgr(); private final    Xofc_dir_mgr dir_mgr; private final    Xofc_fil_mgr fil_mgr;
    +	private final    Bool_obj_ref fil_created = Bool_obj_ref.n_();
    +	public Xof_cache_mgr(Gfo_usr_dlg usr_dlg, Xoae_wiki_mgr wiki_mgr, Xoa_repo_mgr repo_mgr) {
    +		this.usr_dlg = usr_dlg; this.wiki_mgr = wiki_mgr; this.repo_mgr = repo_mgr;
    +		this.dir_mgr = new Xofc_dir_mgr(this);
    +		this.fil_mgr = new Xofc_fil_mgr(this);
    +	}
    +	public int Next_id() {return cfg_mgr.Next_id();} public void Next_id_(int v) {cfg_mgr.Next_id_(v);}
    +	public void Init_for_db(Db_conn conn, boolean created, boolean schema_is_1) {
    +		cfg_mgr.Conn_(conn, created, schema_is_1);
    +		dir_mgr.Conn_(conn, created, schema_is_1);
    +		fil_mgr.Conn_(conn, created, schema_is_1);
    +	}
    +	public void Db_save() {
    +		try {
    +			dir_mgr.Save_all();
    +			fil_mgr.Save_all();
    +			cfg_mgr.Save_all();	// always save cfg_mgr last; fil_mgr / dir_mgr may change next_id during failed saves; DATE:2014-03-07
    +		} catch (Exception e) {usr_dlg.Warn_many("", "", "cache_mgr.save:fatal error: err=~{0}", Err_.Message_gplx_full(e));}
    +	}
    +	public void Db_term() {
    +		try {
    +			cfg_mgr.Cleanup();
    +			dir_mgr.Cleanup();
    +			fil_mgr.Cleanup();
    +		} catch (Exception e) {usr_dlg.Warn_many("", "", "cache_mgr.term:fatal error: err=~{0}", Err_.Message_gplx_full(e));}
    +	}
    +	public Xofc_fil_itm Reg(Xof_fsdb_itm itm, long bin_len) {return this.Reg(itm.Orig_repo_name(), itm.Orig_ttl(), itm.File_is_orig(), itm.File_w(), itm.File_w(), itm.Lnki_time(), itm.Orig_ext(), bin_len, DateAdp_.MaxValue, "");}
    +	private Xofc_fil_itm Reg(byte[] repo, byte[] ttl, boolean fil_is_orig, int fil_w, int fil_h, double fil_thumbtime, Xof_ext ext, long bin_len, DateAdp modified, String hash) {
    +		int dir_id = dir_mgr.Get_by_name_or_make(repo).Id();
    +		Xofc_fil_itm fil_itm = fil_mgr.Get_or_make(dir_id, ttl, fil_is_orig, fil_w, fil_h, fil_thumbtime, ext, bin_len, fil_created.Val_n_());
    +		fil_itm.Cache_time_now_();
    +		if (fil_created.Val())	// increase cache_size if item is new; (don't increase if update); NOTE: not same as Db_cmd_mode.Created, b/c itm could be created, but not saved to db yet; EX: Page_1 has A.png; A.png marked Created; Page_2 has A.png; A.png still Created, but should increase cache_size
    +			cfg_mgr.Cache_len_add(bin_len);
    +		return fil_itm;
    +	}
    +	public void Reg_and_check_for_size_0(Xof_fsdb_itm itm) {
    +		if (Env_.Mode_testing()) return;				// NOTE: needed else test breaks in sqlite mode; DATE:2015-02-21
    +		Xofc_fil_itm cache_fil_itm = this.Reg(itm, 0);	// get item
    +		if (cache_fil_itm.Size() == 0) {				// item does not exist; size will be 0, since 0 passed above
    +			long fil_size = Io_mgr.Instance.QueryFil(itm.Html_view_url()).Size();
    +			cache_fil_itm.Size_(fil_size);
    +		}
    +	}
    +	public void Compress_check() {
    +		if (cfg_mgr.Cache_len() > cfg_mgr.Cache_max())
    +			fil_mgr.Compress(usr_dlg, wiki_mgr, repo_mgr, dir_mgr, cfg_mgr);
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_cache_min))		return cfg_mgr.Cache_min() / Io_mgr.Len_mb;
    +		else if	(ctx.Match(k, Invk_cache_min_))		cfg_mgr.Cache_min_(m.ReadLong("v") * Io_mgr.Len_mb);
    +		else if	(ctx.Match(k, Invk_cache_max))		return cfg_mgr.Cache_max() / Io_mgr.Len_mb;
    +		else if	(ctx.Match(k, Invk_cache_max_))		cfg_mgr.Cache_max_(m.ReadLong("v") * Io_mgr.Len_mb);
    +		else if	(ctx.Match(k, Invk_cache_compress))	fil_mgr.Compress(usr_dlg, wiki_mgr, repo_mgr, dir_mgr, cfg_mgr);
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}	private static final String Invk_cache_min = "cache_min", Invk_cache_min_ = "cache_min_", Invk_cache_max = "cache_max", Invk_cache_max_ = "cache_max_", Invk_cache_compress = "cache_compress";
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xof_cache_mgr_tst.java b/400_xowa/src/gplx/xowa/files/caches/Xof_cache_mgr_tst.java
    index a27517de8..86ba73843 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xof_cache_mgr_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xof_cache_mgr_tst.java
    @@ -13,3 +13,58 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import org.junit.*; import gplx.dbs.*;
    +public class Xof_cache_mgr_tst {
    +//		@Before public void init() {fxt.Reset();} private Xof_cache_mgr_fxt fxt = new Xof_cache_mgr_fxt();
    +	@Test  	public void Basic() {
    +//			Xou_cache_itm itm = fxt.Bldr_itm("A.png").Make();
    +//			fxt.Test_get_n(itm);
    +//			fxt.Exec_update(itm);
    +//			fxt.Test_get_y(itm);
    +//			fxt.Test_viewed_data(1, 123);
    +//			fxt.Exec_update(itm);
    +//			fxt.Test_viewed_data(2, 124);
    +//			fxt.Exec_db_save(itm);
    +//			fxt.Test_db_itms(itm);
    +	}
    +}
    +//	class Xof_cache_mgr_fxt {
    +//		public Xof_cache_mgr_fxt Reset() {
    +//			return this;
    +//		}
    +//	}
    +//	class Xof_cache_itm_mkr {
    +////		private byte[] dir; private byte[] ttl; private boolean is_orig; private int w, h; private double time; private int page; private long size;
    +//		private byte db_state;
    +//		private int lnki_site; private byte[] lnki_ttl; private int lnki_type; private double lnki_upright; private int lnki_w; private int lnki_h; private double lnki_time; private int lnki_page;
    +//		private int orig_wiki; private byte[] orig_ttl; private int orig_ext; private int file_w; private int file_h; private double file_time; private int file_page;
    +//		private long temp_file_size; private int temp_view_count; private long temp_view_date; private int temp_w;
    +//		private Bry_bfr key_bfr = Bry_bfr_.New();
    +//		public Xof_cache_itm_mkr() {this.Reset();}
    +//		private void Reset() {
    +//			db_state = Db_cmd_mode.Tid_ignore;
    +//			lnki_site = orig_wiki = -1;
    +//			lnki_ttl = orig_ttl = null;
    +//			lnki_type = Byte_.Max_value_127;
    +//			lnki_upright = Xof_img_size.Upright_null;
    +//			lnki_w = lnki_h = file_w = file_h = temp_w = Xof_img_size.Size_null;
    +//			lnki_time = file_time = Xof_lnki_time.Null;
    +//			lnki_page = file_page = Xof_lnki_page.Null;
    +//			orig_ext = Xof_ext_.Id_unknown;
    +//			temp_file_size = -1;
    +//			temp_view_count = -1;
    +//			temp_view_date = -1;			
    +//		}
    +//		public Xof_cache_itm_mkr Init(String dir_str, String ttl_str, boolean is_orig, int w) {
    +////			this.dir = Bry_.new_u8(dir_str);
    +////			this.ttl = Bry_.new_u8(ttl_str);
    +////			this.is_orig = is_orig;
    +////			this.w = w;
    +//			return this;
    +//		}
    +//		public Xou_cache_itm Make() {
    +//			return new Xou_cache_itm(key_bfr, db_state, lnki_site, lnki_ttl, lnki_type, lnki_upright, lnki_w, lnki_h, file_w, file_h, );
    +//			this.Reset();
    +//		}
    +//	}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xofc_cfg_mgr.java b/400_xowa/src/gplx/xowa/files/caches/Xofc_cfg_mgr.java
    index a27517de8..0b5d2af64 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xofc_cfg_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xofc_cfg_mgr.java
    @@ -13,3 +13,36 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.xowa.wikis.data.tbls.*;
    +class Xofc_cfg_mgr {
    +	private Db_cfg_tbl tbl;
    +	public int Next_id() {return next_id++;} public void Next_id_(int v) {next_id = v;} private int next_id;		
    +	public long Cache_len() {return cache_len;} public void Cache_len_(long v) {cache_len = v;} private long cache_len = 0;
    +	public void Cache_len_add(long v) {cache_len += v;}
    +	public long Cache_min() {return cache_min;} public void Cache_min_(long v) {cache_min = v;} private long cache_min = Io_mgr.Len_mb * 75;
    +	public long Cache_max() {return cache_max;} public void Cache_max_(long v) {cache_max = v;} private long cache_max = Io_mgr.Len_mb * 100;
    +	public void Conn_(Db_conn v, boolean created, boolean schema_is_1) {
    +		tbl = new Db_cfg_tbl(v, schema_is_1 ? gplx.xowa.wikis.data.Xowd_cfg_tbl_.Tbl_name : "file_cache_cfg");
    +		if (created) {
    +			tbl.Create_tbl();
    +			tbl.Insert_int(Cfg_grp, Cfg_key__next_id, 1);
    +			tbl.Insert_int(Cfg_grp, Cfg_key__cache_len, 0);
    +			tbl.Insert_long(Cfg_grp, Cfg_key__cache_min, cache_min);
    +			tbl.Insert_long(Cfg_grp, Cfg_key__cache_max, cache_max);
    +		}
    +		else {
    +			next_id = tbl.Select_int(Cfg_grp, Cfg_key__next_id);
    +			cache_len = tbl.Select_int(Cfg_grp, Cfg_key__cache_len);
    +			cache_max = tbl.Select_int(Cfg_grp, Cfg_key__cache_max);
    +		}
    +	}
    +	public void Save_all() {
    +		tbl.Update_int(Cfg_grp, Cfg_key__next_id, next_id);
    +		tbl.Update_long(Cfg_grp, Cfg_key__cache_len, cache_len);
    +		tbl.Update_long(Cfg_grp, Cfg_key__cache_min, cache_min);
    +		tbl.Update_long(Cfg_grp, Cfg_key__cache_max, cache_max);
    +	}
    +	public void Cleanup() {}
    +	private static final String Cfg_grp = "fsdb.cache", Cfg_key__next_id = "next_id", Cfg_key__cache_min = "cache_min", Cfg_key__cache_max = "cache_max", Cfg_key__cache_len = "cache_len";
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xofc_dir_itm.java b/400_xowa/src/gplx/xowa/files/caches/Xofc_dir_itm.java
    index a27517de8..289df88ca 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xofc_dir_itm.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xofc_dir_itm.java
    @@ -13,3 +13,16 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.dbs.*;
    +public class Xofc_dir_itm {
    +	public Xofc_dir_itm(int id, byte[] name, byte cmd_mode) {
    +		this.id = id;
    +		this.name = name;
    +		this.cmd_mode = cmd_mode;
    +	}
    +	public int Id() {return id;} public void Id_(int v) {id = v;} private int id;
    +	public byte[] Name() {return name;} private final byte[] name;
    +	public byte Cmd_mode() {return cmd_mode;} public Xofc_dir_itm Cmd_mode_(byte v) {cmd_mode = v; return this;} private byte cmd_mode;
    +	public static final Xofc_dir_itm Null = null;
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xofc_dir_mgr.java b/400_xowa/src/gplx/xowa/files/caches/Xofc_dir_mgr.java
    index a27517de8..aafff2e29 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xofc_dir_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xofc_dir_mgr.java
    @@ -13,3 +13,78 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.dbs.*;
    +class Xofc_dir_mgr {
    +	private final    Xofc_dir_tbl tbl = new Xofc_dir_tbl();
    +	private final    Ordered_hash hash_by_names = Ordered_hash_.New_bry(); private final    Hash_adp hash_by_ids = Hash_adp_.New();
    +	private Xof_cache_mgr cache_mgr;
    +	public Xofc_dir_mgr(Xof_cache_mgr v) {this.cache_mgr = v;}
    +	public void Conn_(Db_conn v, boolean created, boolean schema_is_1) {tbl.Conn_(v, created, schema_is_1);}
    +	public Xofc_dir_itm Get_by_id(int id) {return (Xofc_dir_itm)hash_by_ids.Get_by(id);}
    +	public Xofc_dir_itm Get_by_name_or_make(byte[] name) {
    +		Xofc_dir_itm itm = Get_by_name_or_null(name);
    +		if (itm == null) {											// not in memory / db
    +			int id = cache_mgr.Next_id();							// make it
    +			itm = new Xofc_dir_itm(id, name, Db_cmd_mode.Tid_create);
    +			Add(name, itm);
    +		}
    +		return itm;
    +	}
    +	public Xofc_dir_itm Get_by_name_or_null(byte[] name) {
    +		Xofc_dir_itm itm = (Xofc_dir_itm)hash_by_names.Get_by(name);
    +		if (itm == null) {											// not in memory
    +			itm = tbl.Select_one(name);								// check db
    +			if (itm == Xofc_dir_itm.Null) return null;				// in db
    +			Add(name, itm);
    +		}
    +		return itm;
    +	}
    +	private void Add(byte[] name, Xofc_dir_itm dir) {
    +		hash_by_names.Add(name, dir);								// put it in memory
    +		hash_by_ids.Add(dir.Id(), dir);
    +	}
    +	public void Save_all() {
    +		int len = hash_by_names.Count();
    +		boolean err_seen = false;
    +		for (int i = 0; i < len; i++) {
    +			Xofc_dir_itm itm = (Xofc_dir_itm)hash_by_names.Get_at(i);
    +			if (err_seen)
    +				itm.Id_(cache_mgr.Next_id());
    +			if (itm.Cmd_mode() == Db_cmd_mode.Tid_create) {			// create; check if in db;
    +				Xofc_dir_itm cur = tbl.Select_one(itm.Name());
    +				if (cur != Xofc_dir_itm.Null)						// cur found
    +					itm.Cmd_mode_(Db_cmd_mode.Tid_update);			// change itm to update
    +			}
    +			String err = tbl.Db_save(itm);
    +			if (err != null) {
    +				Db_recalc_next_id(itm, err);
    +				err_seen = true;
    +			}
    +		}
    +	}
    +	public void Load_all() {
    +		List_adp list = List_adp_.New();
    +		tbl.Select_all(list);
    +		int len = list.Count();
    +		hash_by_ids.Clear();
    +		hash_by_names.Clear();
    +		for (int i = 0; i < len; ++i) {
    +			Xofc_dir_itm itm = (Xofc_dir_itm)list.Get_at(i);
    +			hash_by_names.Add(itm.Name(), itm);
    +			hash_by_ids.Add(itm.Id(), itm);
    +		}
    +	}
    +	public void Cleanup() {tbl.Cleanup();}
    +	private void Db_recalc_next_id(Xofc_dir_itm itm, String err) {
    +		if (String_.Has(err, "PRIMARY KEY must be unique")) { // primary key exception in strange situations (multiple xowas at same time)
    +			int next_id = tbl.Select_max_uid() + 1;				
    +			Gfo_usr_dlg_.Instance.Warn_many("", "", "uid out of sync; incrementing; uid=~{0} name=~{1} err=~{2}", itm.Id(), String_.new_u8(itm.Name()), err);
    +			itm.Id_(next_id);
    +			cache_mgr.Next_id_(next_id + 1);
    +			err = tbl.Db_save(itm);
    +			if (err == null) return;
    +		}
    +		Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to save uid; uid=~{0} name=~{1} err=~{2}", itm.Id(), String_.new_u8(itm.Name()), err);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xofc_dir_tbl.java b/400_xowa/src/gplx/xowa/files/caches/Xofc_dir_tbl.java
    index a27517de8..f35ea4cac 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xofc_dir_tbl.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xofc_dir_tbl.java
    @@ -13,3 +13,73 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.dbs.*; import gplx.dbs.engines.sqlite.*;
    +class Xofc_dir_tbl implements Rls_able {
    +	private String tbl_name = "file_cache_dir"; private final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	private String fld_id, fld_name;
    +	private Db_conn conn; private final    Db_stmt_bldr stmt_bldr = new Db_stmt_bldr(); private Db_stmt select_stmt;
    +	public void Conn_(Db_conn new_conn, boolean created, boolean schema_is_1) {
    +		this.conn = new_conn; flds.Clear();
    +		String fld_prefix = "";
    +		if (schema_is_1) {
    +			tbl_name		= "cache_dir";
    +			fld_prefix		= "dir_";
    +		}
    +		fld_id				= flds.Add_int_pkey(fld_prefix + "id");
    +		fld_name			= flds.Add_str(fld_prefix + "name", 255);
    +		if (created) {
    +			Dbmeta_tbl_itm meta = Dbmeta_tbl_itm.New(tbl_name, flds
    +			, Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "name", fld_name)
    +			);
    +			conn.Meta_tbl_create(meta);
    +		}
    +		stmt_bldr.Conn_(conn, tbl_name, flds, fld_id);
    +		conn.Rls_reg(this);
    +	}
    +	public void Rls() {
    +		select_stmt = Db_stmt_.Rls(select_stmt);
    +	}
    +	public String Db_save(Xofc_dir_itm itm) {
    +		try {
    +			Db_stmt stmt = stmt_bldr.Get(itm.Cmd_mode());
    +			switch (itm.Cmd_mode()) {
    +				case Db_cmd_mode.Tid_create:	stmt.Clear().Val_int(fld_id, itm.Id())	.Val_bry_as_str(fld_name, itm.Name()).Exec_insert(); break;
    +				case Db_cmd_mode.Tid_update:	stmt.Clear()							.Val_bry_as_str(fld_name, itm.Name()).Crt_int(fld_id, itm.Id()).Exec_update(); break;
    +				case Db_cmd_mode.Tid_delete:	stmt.Clear().Crt_int(fld_id, itm.Id()).Exec_delete(); break;
    +				case Db_cmd_mode.Tid_ignore:	break;
    +				default:						throw Err_.new_unhandled(itm.Cmd_mode());
    +			}
    +			itm.Cmd_mode_(Db_cmd_mode.Tid_ignore);
    +			return null;
    +		} catch (Exception e) {
    +			stmt_bldr.Rls(); // rls bldr, else bad stmt will lead to other failures
    +			return Err_.Message_gplx_full(e);
    +		}
    +	}
    +	public void Cleanup() {
    +		select_stmt = Db_stmt_.Rls(select_stmt);
    +		stmt_bldr.Rls();
    +	}
    +	public Xofc_dir_itm Select_one(byte[] name) {
    +		if (select_stmt == null) select_stmt = conn.Stmt_select(tbl_name, flds, fld_name);
    +		Db_rdr rdr = select_stmt.Clear().Crt_bry_as_str(fld_name, name).Exec_select__rls_manual();
    +		try {
    +			return rdr.Move_next() ? new_itm(rdr) : Xofc_dir_itm.Null;
    +		}
    +		finally {rdr.Rls();}
    +	}		
    +	public void Select_all(List_adp list) {
    +		list.Clear();
    +		Db_rdr rdr = conn.Stmt_select(tbl_name, flds, Dbmeta_fld_itm.Str_ary_empty).Exec_select__rls_auto();
    +		try {
    +			while (rdr.Move_next())
    +				list.Add(new_itm(rdr));
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	public int Select_max_uid() {return conn.Exec_select_as_int("SELECT Max(uid) AS MaxId FROM cache_dir;", -1);}
    +	private Xofc_dir_itm new_itm(Db_rdr rdr) {
    +		return new Xofc_dir_itm(rdr.Read_int(fld_id), rdr.Read_bry_by_str(fld_name), Db_cmd_mode.Tid_ignore);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xofc_fil_itm.java b/400_xowa/src/gplx/xowa/files/caches/Xofc_fil_itm.java
    index a27517de8..414506fea 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xofc_fil_itm.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xofc_fil_itm.java
    @@ -13,3 +13,59 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.dbs.*;
    +public class Xofc_fil_itm implements CompareAble {
    +	public Xofc_fil_itm(int uid, int dir_id, byte[] name, boolean is_orig, int w, int h, double time, int page, Xof_ext ext, long size, long cache_time, byte cmd_mode) {
    +		this.uid = uid; this.dir_id = dir_id;
    +		this.name = name; this.is_orig = is_orig; this.w = w; this.h = h; this.time = time; this.page = page; this.ext = ext; this.size = size;
    +		this.cache_time = cache_time; this.cmd_mode = cmd_mode;
    +	}
    +	public String Key;
    +	public int		Uid() {return uid;} public void Uid_(int v) {uid = v;} private int uid;
    +	public int		Dir_id() {return dir_id;} private final    int dir_id;
    +	public byte[]	Name() {return name;} private final    byte[] name;
    +	public boolean		Is_orig() {return is_orig;} private final    boolean is_orig;
    +	public int		W() {return w;} private final    int w;
    +	public int		H() {return h;} private final    int h;
    +	public double	Time() {return time;} private final    double time;
    +	public int		Page() {return page;} private final    int page;
    +	public Xof_ext	Ext() {return ext;} private final    Xof_ext ext;
    +	public long		Size() {return size;} private long size;
    +	public void		Size_(long v) {
    +		this.size = v;
    +		cmd_mode = Db_cmd_mode.To_update(cmd_mode);
    +	}
    +	public long		Cache_time() {return cache_time;} private long cache_time;
    +	public Xofc_fil_itm Cache_time_now_() {
    +		this.cache_time = Datetime_now.Get().XtoUtc().Timestamp_unix();
    +		cmd_mode = Db_cmd_mode.To_update(cmd_mode);
    +		return this;
    +	}
    +	public byte		Cmd_mode() {return cmd_mode;} public Xofc_fil_itm Cmd_mode_(byte v) {cmd_mode = v; return this;} private byte cmd_mode;
    +	public void		Cmd_mode_delete_() {cmd_mode = Db_cmd_mode.Tid_delete;}
    +	public byte[]	Gen_hash_key_v1(Bry_bfr bfr)	{return Gen_hash_key_v1(bfr, dir_id, name, is_orig, w, h, time);}
    +	public byte[]	Gen_hash_key_v2(Bry_bfr bfr)	{return Gen_hash_key_v2(bfr, dir_id, name, is_orig, w, time, page);}
    +	public static byte[] Gen_hash_key_v1(Bry_bfr bfr, int dir_id, byte[] name, boolean is_orig, int w, int h, double time) {
    +		bfr	.Add_int_variable(dir_id).Add_byte_pipe()
    +			.Add(name).Add_byte_pipe()
    +			.Add_yn(is_orig).Add_byte_pipe()
    +			.Add_int_variable(w).Add_byte_pipe()
    +			.Add_int_variable(h).Add_byte_pipe()
    +			.Add_int_variable(Xof_lnki_time.X_int(time))
    +			;
    +		return bfr.To_bry_and_clear();
    +	}
    +	public static byte[] Gen_hash_key_v2(Bry_bfr bfr, int dir_id, byte[] name, boolean is_orig, int w, double time, int page) {
    +		bfr	.Add_int_variable(dir_id).Add_byte_pipe()
    +			.Add(name).Add_byte_pipe()
    +			.Add_yn(is_orig).Add_byte_pipe()
    +			.Add_int_variable(w).Add_byte_pipe()
    +			.Add_double(Xof_lnki_time.Db_save_double(time)).Add_byte_pipe()
    +			.Add_int_variable(page)
    +			;
    +		return bfr.To_bry_and_clear();
    +	}
    +	public int compareTo(Object obj) {Xofc_fil_itm comp = (Xofc_fil_itm)obj; return -Long_.Compare(cache_time, comp.cache_time);}	// - for DESC sort
    +	public static final    Xofc_fil_itm Null = null;
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xofc_fil_mgr.java b/400_xowa/src/gplx/xowa/files/caches/Xofc_fil_mgr.java
    index a27517de8..089610519 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xofc_fil_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xofc_fil_mgr.java
    @@ -13,3 +13,136 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.primitives.*;
    +import gplx.dbs.*; import gplx.fsdb.*; import gplx.xowa.wikis.*;
    +import gplx.xowa.files.repos.*; import gplx.xowa.files.imgs.*;
    +class Xofc_fil_mgr {
    +	private Xof_cache_mgr cache_mgr;		
    +	private final    Xofc_fil_tbl tbl = new Xofc_fil_tbl(); private final    Ordered_hash hash = Ordered_hash_.New_bry(); private final    Bry_bfr key_bldr = Bry_bfr_.Reset(255);
    +	public Xofc_fil_mgr(Xof_cache_mgr v) {this.cache_mgr = v;}
    +	public void Conn_(Db_conn v, boolean created, boolean schema_is_1) {tbl.Conn_(v, created, schema_is_1);}
    +	public void Save_all() {
    +		int len = hash.Count();
    +		boolean err_seen = false;
    +		for (int i = 0; i < len; i++) {
    +			Xofc_fil_itm itm = (Xofc_fil_itm)hash.Get_at(i);
    +			if (err_seen)
    +				itm.Uid_(cache_mgr.Next_id());
    +			if (itm.Cmd_mode() == Db_cmd_mode.Tid_create) {		// create; check if in db;
    +				Xofc_fil_itm cur = tbl.Select_one_v1(itm.Dir_id(), itm.Name(), itm.Is_orig(), itm.W(), itm.H(), itm.Time());
    +				if (cur != Xofc_fil_itm.Null)					// cur found
    +					itm.Cmd_mode_(Db_cmd_mode.Tid_update);		// change itm to update
    +			}
    +			String err_msg = tbl.Db_save(itm);
    +			if (err_msg != null) {
    +				Db_recalc_next_id(itm, err_msg);
    +				err_seen = true;
    +			}
    +		}
    +	}
    +	public Xofc_fil_itm Get_or_make(int dir_id, byte[] name, boolean is_orig, int w, int h, double time, Xof_ext ext, long size, Bool_obj_ref created) {
    +		byte[] key = Xofc_fil_itm.Gen_hash_key_v1(key_bldr, dir_id, name, is_orig, w, h, time);
    +		Xofc_fil_itm itm = (Xofc_fil_itm)hash.Get_by(key);
    +		if (itm == Xofc_fil_itm.Null) {								// not in memory
    +			itm = tbl.Select_one_v1(dir_id, name, is_orig, w, h, time);
    +			if (itm == Xofc_fil_itm.Null) {							// not in db
    +				itm = Make_v1(dir_id, name, is_orig, w, h, time, Xof_lnki_page.Null, ext, size);
    +				created.Val_(true);
    +			}
    +			else													// NOTE: itm loaded from tbl; add to hash; do not add if created b/c Make adds to hash;
    +				hash.Add(key, itm);
    +		}
    +		return itm;
    +	}
    +	public Xofc_fil_itm Get_or_null(int dir_id, byte[] name, boolean is_orig, int w, double time, int page) {
    +		byte[] key = Xofc_fil_itm.Gen_hash_key_v2(key_bldr, dir_id, name, is_orig, w, time, page);
    +		Xofc_fil_itm itm = (Xofc_fil_itm)hash.Get_by(key);
    +		if (itm == null) {											// not in memory
    +			itm = tbl.Select_one_v2(dir_id, name, is_orig, w, time, page);
    +			if (itm == Xofc_fil_itm.Null) return itm;				// not in db
    +			hash.Add(key, itm);
    +		}
    +		return itm;
    +	}
    +	private Xofc_fil_itm Make_v1(int dir_id, byte[] name, boolean is_orig, int w, int h, double time, int page, Xof_ext ext, long size) {return Make(Bool_.Y, dir_id, name, is_orig, w, h, time, page, ext, size);}
    +	public Xofc_fil_itm Make_v2(int dir_id, byte[] name, boolean is_orig, int w, int h, double time, int page, Xof_ext ext, long size) {return Make(Bool_.N, dir_id, name, is_orig, w, h, time, page, ext, size);}
    +	private Xofc_fil_itm Make(boolean schema_is_1, int dir_id, byte[] name, boolean is_orig, int w, int h, double time, int page, Xof_ext ext, long size) {
    +		int id = cache_mgr.Next_id();
    +		Xofc_fil_itm rv = new Xofc_fil_itm(id, dir_id, name, is_orig, w, h, time, page, ext, size, 0, Db_cmd_mode.Tid_create).Cache_time_now_();
    +		byte[] key = schema_is_1 ? rv.Gen_hash_key_v1(key_bldr) : rv.Gen_hash_key_v2(key_bldr);
    +		hash.Add(key, rv);
    +		return rv;
    +	}
    +	public void Compress(Gfo_usr_dlg usr_dlg, Xoae_wiki_mgr wiki_mgr, Xoa_repo_mgr repo_mgr, Xofc_dir_mgr dir_mgr, Xofc_cfg_mgr cfg_mgr) {
    +		try {
    +			usr_dlg.Note_many("", "", "compressing cache");
    +			dir_mgr.Save_all(); dir_mgr.Load_all();				// save and load all dirs
    +			this.Save_all(); tbl.Select_all(key_bldr, hash);	// save and load all fils				
    +			hash.Sort();	// sorts by cache_time desc
    +			int len = hash.Count();
    +			long cur_size = 0, actl_size = 0;
    +			Xof_url_bldr url_bldr = new Xof_url_bldr();
    +			List_adp deleted = List_adp_.New();
    +			tbl.Conn().Txn_bgn("user__file_cache__compress");
    +			long compress_to = cfg_mgr.Cache_min();
    +			for (int i = 0; i < len; ++i) {
    +				Xofc_fil_itm itm = (Xofc_fil_itm)hash.Get_at(i);
    +				long itm_size = itm.Size();
    +				long new_size = cur_size + itm_size;
    +				if (new_size > compress_to) {
    +					itm.Cmd_mode_(gplx.dbs.Db_cmd_mode.Tid_delete);
    +					Fsys_delete(url_bldr, wiki_mgr, repo_mgr, dir_mgr, itm);
    +					deleted.Add(itm);
    +				}
    +				else
    +					actl_size += itm_size;
    +				cur_size = new_size;
    +				String err_msg = tbl.Db_save(itm);	// save to db now, b/c fils will be deleted and want to keep db and fsys in sync
    +				if (err_msg != null)
    +					Db_recalc_next_id(itm, err_msg);
    +			}
    +			len = deleted.Count();
    +			for (int i = 0; i < len; i++) {
    +				Xofc_fil_itm itm = (Xofc_fil_itm)deleted.Get_at(i);
    +				byte[] fil_key = itm.Gen_hash_key_v1(key_bldr);
    +				hash.Del(fil_key);
    +			}
    +			cfg_mgr.Cache_len_(actl_size);
    +			this.Save_all();		// save everything again
    +		}
    +		catch (Exception e) {
    +			usr_dlg.Warn_many("", "", "failed to compress cache: err=~{0}", Err_.Message_gplx_full(e));
    +		}
    +		finally {tbl.Conn().Txn_end();}
    +	}
    +	private void Fsys_delete(Xof_url_bldr url_bldr, Xoae_wiki_mgr wiki_mgr, Xoa_repo_mgr repo_mgr, Xofc_dir_mgr dir_mgr, Xofc_fil_itm itm) {
    +		byte mode_id = itm.Is_orig() ? Xof_img_mode_.Tid__orig : Xof_img_mode_.Tid__thumb;
    +		byte[] wiki_domain = dir_mgr.Get_by_id(itm.Dir_id()).Name();
    +		Xowe_wiki wiki = wiki_mgr.Get_by_or_make(wiki_domain);
    +		wiki.Init_assert();
    +		Xof_repo_itm trg_repo = repo_mgr.Get_by_primary(wiki_domain);
    +		byte[] ttl = itm.Name();			
    +		byte[] md5 = Xof_file_wkr_.Md5(ttl);
    +		int itm_ext_id = itm.Ext().Id();
    +		Io_url fil_url = url_bldr.Init_for_trg_file(trg_repo, mode_id, ttl, md5, itm.Ext(), itm.W()
    +			, Xof_lnki_time.Convert_to_xowa_thumbtime	(itm_ext_id, itm.Time())
    +			, Xof_lnki_time.Convert_to_xowa_page		(itm_ext_id, itm.Time())
    +			).Xto_url();
    +		Io_mgr.Instance.DeleteFil_args(fil_url).MissingFails_off().Exec();
    +		itm.Cmd_mode_delete_();
    +	}
    +	public void Cleanup() {tbl.Rls();}
    +	private void Db_recalc_next_id(Xofc_fil_itm fil_itm, String err_msg) {
    +		if (String_.Has(err_msg, "PRIMARY KEY must be unique")) { // primary key exception in strange situations (multiple xowas at same time)
    +			int next_id = tbl.Select_max_uid() + 1;				
    +			Gfo_usr_dlg_.Instance.Warn_many("", "", "uid out of sync; incrementing; uid=~{0} name=~{1} err=~{2}", fil_itm.Uid(), String_.new_u8(fil_itm.Name()), err_msg);
    +			fil_itm.Uid_(next_id);
    +			cache_mgr.Next_id_(next_id + 1);
    +			err_msg = tbl.Db_save(fil_itm);
    +			if (err_msg == null)
    +				return;
    +		}
    +		Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to save uid; uid=~{0} name=~{1} err=~{2}", fil_itm.Uid(), String_.new_u8(fil_itm.Name()), err_msg);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xofc_fil_tbl.java b/400_xowa/src/gplx/xowa/files/caches/Xofc_fil_tbl.java
    index a27517de8..19f0277c7 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xofc_fil_tbl.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xofc_fil_tbl.java
    @@ -13,3 +13,141 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.dbs.*; import gplx.dbs.engines.sqlite.*;
    +class Xofc_fil_tbl implements Rls_able {
    +	private String tbl_name = "file_cache_fil"; private final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	private String fld_uid, fld_dir_id, fld_name, fld_is_orig, fld_w, fld_h, fld_time, fld_page, fld_ext, fld_size, fld_cache_time;
    +	private Db_conn conn; private final    Db_stmt_bldr stmt_bldr = new Db_stmt_bldr(); private Db_stmt select_itm_stmt, select_itm_v2_stmt;
    +	public Db_conn Conn() {return conn;}
    +	public void Conn_(Db_conn new_conn, boolean created, boolean schema_is_1) {
    +		this.conn = new_conn; flds.Clear();
    +		String fld_prefix = "";
    +		if (schema_is_1) {
    +			tbl_name		= "cache_fil";
    +			fld_prefix		= "fil_";
    +		}
    +		fld_uid				= flds.Add_int_pkey("uid");
    +		fld_dir_id			= flds.Add_int("dir_id");
    +		fld_name			= flds.Add_str(fld_prefix + "name", 255);
    +		fld_is_orig			= flds.Add_byte(fld_prefix + "is_orig");
    +		fld_w				= flds.Add_int(fld_prefix + "w");
    +		fld_h				= flds.Add_int(fld_prefix + "h");
    +		fld_time			= flds.Add_int(fld_prefix + "thumbtime");
    +		if (schema_is_1) {
    +			fld_page		= Dbmeta_fld_itm.Key_null;
    +		}
    +		else {
    +			fld_page		= flds.Add_int(fld_prefix + "page");
    +		}
    +		fld_ext				= flds.Add_int(fld_prefix + "ext");
    +		fld_size			= flds.Add_long(fld_prefix + "size");
    +		fld_cache_time		= flds.Add_long("cache_time");
    +		if (created) {
    +			Dbmeta_tbl_itm meta = Dbmeta_tbl_itm.New(tbl_name, flds
    +			, Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "fil", fld_name, fld_is_orig, fld_w, fld_h, fld_time, fld_cache_time, fld_uid)
    +			);
    +			conn.Meta_tbl_create(meta);
    +		}
    +		select_itm_stmt = select_itm_v2_stmt = null;
    +		stmt_bldr.Conn_(conn, tbl_name, flds, fld_uid);
    +		conn.Rls_reg(this);
    +	}
    +	public void Rls() {
    +		select_itm_stmt = Db_stmt_.Rls(select_itm_stmt);
    +		select_itm_v2_stmt = Db_stmt_.Rls(select_itm_v2_stmt);
    +		stmt_bldr.Rls();
    +	}
    +	public String Db_save(Xofc_fil_itm itm) {
    +		try {
    +			Db_stmt stmt = stmt_bldr.Get(itm.Cmd_mode());
    +			switch (itm.Cmd_mode()) {
    +				case Db_cmd_mode.Tid_create:	stmt.Clear().Val_int(fld_uid, itm.Uid());	Db_save_modify(stmt, itm); stmt.Exec_insert(); break;
    +				case Db_cmd_mode.Tid_update:	stmt.Clear();								Db_save_modify(stmt, itm); stmt.Crt_int(fld_uid, itm.Uid()).Exec_update(); break;
    +				case Db_cmd_mode.Tid_delete:	stmt.Clear().Crt_int(fld_uid, itm.Uid()); stmt.Exec_delete();	break;
    +				case Db_cmd_mode.Tid_ignore:	break;
    +				default:						throw Err_.new_unhandled(itm.Cmd_mode());
    +			}
    +			itm.Cmd_mode_(Db_cmd_mode.Tid_ignore);
    +			return null;
    +		} catch (Exception e) {
    +			stmt_bldr.Rls();	// null out bldr, else bad stmt will lead to other failures
    +			return Err_.Message_gplx_full(e);
    +		}
    +	}
    +	private void Db_save_modify(Db_stmt stmt, Xofc_fil_itm itm) {
    +		stmt.Val_int(fld_dir_id, itm.Dir_id())
    +			.Val_bry_as_str(fld_name, itm.Name())
    +			.Val_bool_as_byte(fld_is_orig, itm.Is_orig())
    +			.Val_int(fld_w, itm.W())
    +			.Val_int(fld_h, itm.H())
    +			.Val_int(fld_time, Xof_lnki_time.Db_save_int(itm.Time()))
    +			.Val_int(fld_page, itm.Page())
    +			.Val_int(fld_ext, itm.Ext().Id())
    +			.Val_long(fld_size, itm.Size())
    +			.Val_long(fld_cache_time, itm.Cache_time())
    +			;
    +	}
    +	public Xofc_fil_itm Select_one_v1(int dir_id, byte[] fil_name, boolean fil_is_orig, int fil_w, int fil_h, double fil_thumbtime) {
    +		if (select_itm_stmt == null) select_itm_stmt = conn.Stmt_select(tbl_name, flds, String_.Ary(fld_dir_id, fld_name, fld_is_orig, fld_w, fld_h, fld_time));
    +		Db_rdr rdr = select_itm_stmt.Clear()
    +			.Crt_int(fld_dir_id, dir_id)
    +			.Crt_bry_as_str(fld_name, fil_name)
    +			.Crt_bool_as_byte(fld_is_orig, fil_is_orig)
    +			.Crt_int(fld_w, fil_w)
    +			.Crt_int(fld_h, fil_h)
    +			.Crt_int(fld_time, Xof_lnki_time.Db_save_int(fil_thumbtime))
    +			.Exec_select__rls_manual();
    +		try {
    +			return rdr.Move_next() ? new_itm(rdr) : Xofc_fil_itm.Null;
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	public Xofc_fil_itm Select_one_v2(int dir_id, byte[] name, boolean is_orig, int w, double time, int page) {
    +		if (select_itm_v2_stmt == null) select_itm_v2_stmt = conn.Stmt_select(tbl_name, flds, String_.Ary(fld_dir_id, fld_name, fld_is_orig, fld_w, fld_time, fld_page));
    +		Db_rdr rdr = select_itm_v2_stmt.Clear()
    +			.Crt_int(fld_dir_id, dir_id)
    +			.Crt_bry_as_str(fld_name, name)
    +			.Crt_bool_as_byte(fld_is_orig, is_orig)
    +			.Crt_int(fld_w, w)
    +			.Crt_int(fld_time, Xof_lnki_time.Db_save_int(time))
    +			.Crt_int(fld_page, page)
    +			.Exec_select__rls_manual();
    +		try {
    +			return rdr.Move_next() ? new_itm(rdr) : Xofc_fil_itm.Null;
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	public void Select_all(Bry_bfr fil_key_bldr, Ordered_hash hash) {
    +		hash.Clear();
    +		Db_rdr rdr = conn.Stmt_select(tbl_name, flds, Dbmeta_fld_itm.Str_ary_empty).Exec_select__rls_auto();
    +		try {
    +			while (rdr.Move_next()) {
    +				Xofc_fil_itm fil_itm = new_itm(rdr);
    +				byte[] key = fil_itm.Gen_hash_key_v1(fil_key_bldr);
    +				if (hash.Has(key))		// NOTE: need to check for uniqueness else dupe file will cause select to fail; shouldn't happen, but somehow did; DATE:2013-12-28
    +					Gfo_usr_dlg_.Instance.Warn_many("", "", "cache had duplicate itm: key=~{0}", String_.new_u8(key));
    +				else
    +					hash.Add(key, fil_itm);
    +			}
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	public int Select_max_uid() {return conn.Exec_select_as_int("SELECT Max(uid) AS MaxId FROM cache_fil;", -1);}
    +	private Xofc_fil_itm new_itm(Db_rdr rdr) {
    +		return new Xofc_fil_itm
    +		( rdr.Read_int(fld_uid)
    +		, rdr.Read_int(fld_dir_id)
    +		, rdr.Read_bry_by_str(fld_name)
    +		, rdr.Read_byte(fld_is_orig) != Byte_.Zero
    +		, rdr.Read_int(fld_w)
    +		, rdr.Read_int(fld_h)
    +		, Xof_lnki_time.Db_load_int(rdr, fld_time)
    +		, Xof_lnki_page.Null
    +		, Xof_ext_.new_by_id_(rdr.Read_int(fld_ext))
    +		, rdr.Read_long(fld_size)
    +		, rdr.Read_long(fld_cache_time)
    +		, Db_cmd_mode.Tid_ignore
    +		);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xou_cache_finder.java b/400_xowa/src/gplx/xowa/files/caches/Xou_cache_finder.java
    index a27517de8..f7110ec4d 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xou_cache_finder.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xou_cache_finder.java
    @@ -13,3 +13,9 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +public interface Xou_cache_finder {
    +	boolean Find(Xow_wiki wiki, byte[] page_url, Xof_fsdb_itm fsdb_itm);
    +	void Clear();
    +	void Add(Xof_fsdb_itm fsdb_itm);
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xou_cache_finder_.java b/400_xowa/src/gplx/xowa/files/caches/Xou_cache_finder_.java
    index a27517de8..6c62fede9 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xou_cache_finder_.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xou_cache_finder_.java
    @@ -13,3 +13,42 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.xowa.files.repos.*;
    +public class Xou_cache_finder_ {
    +	public static final    Xou_cache_finder Noop = new Xou_cache_finder_noop();
    +	public static Xou_cache_finder_mem New_mem() {return new Xou_cache_finder_mem();}
    +	public static Xou_cache_finder New_db(Xou_cache_mgr cache_mgr) {return new Xou_cache_finder_db(cache_mgr);}
    +}
    +class Xou_cache_finder_noop implements Xou_cache_finder {
    +	public boolean Find(Xow_wiki wiki, byte[] page_url, Xof_fsdb_itm fsdb_itm) {
    +		fsdb_itm.Init_at_cache(false, 0, 0, Io_url_.Empty);
    +		return false;
    +	}
    +	public void Clear() {}
    +	public void Add(Xof_fsdb_itm fsdb_itm) {}
    +}
    +class Xou_cache_finder_db implements Xou_cache_finder {
    +	private final    Xou_cache_mgr cache_mgr;
    +	private final    Xof_img_size img_size = new Xof_img_size(); private final    Xof_url_bldr url_bldr = Xof_url_bldr.new_v2();
    +	public Xou_cache_finder_db(Xou_cache_mgr cache_mgr) {this.cache_mgr = cache_mgr;}
    +	public boolean Find(Xow_wiki wiki, byte[] page_url, Xof_fsdb_itm cur) {
    +		Xou_cache_itm cache_itm = cache_mgr.Get_or_null(cur); 
    +		if (cache_itm != null) {
    +			Xof_repo_itm repo = wiki.File__repo_mgr().Get_trg_by_id_or_null(cache_itm.Orig_repo_id(), cur.Lnki_ttl(), page_url);
    +			if (repo != null) {// unknown repo; shouldn't happen, but exit, else null ref
    +				cur.Init_at_orig((byte)cache_itm.Orig_repo_id(), repo.Wiki_domain(), cache_itm.Orig_ttl(), cache_itm.Orig_ext_itm(), cache_itm.Orig_w(), cache_itm.Orig_h(), Bry_.Empty);
    +				cur.Init_at_html(Xof_exec_tid.Tid_wiki_page, img_size, repo, url_bldr);
    +				if (Io_mgr.Instance.ExistsFil(cur.Html_view_url())) {
    +					cache_itm.Update_view_stats();
    +					cur.Init_at_cache(true, cur.Html_w(), cur.Html_h(), cur.Html_view_url());
    +					return true;
    +				}
    +			}
    +		}
    +		cur.Init_at_cache(false, 0, 0, Io_url_.Empty);
    +		return false;
    +	}
    +	public void Clear() {}
    +	public void Add(Xof_fsdb_itm cur) {}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xou_cache_finder_mem.java b/400_xowa/src/gplx/xowa/files/caches/Xou_cache_finder_mem.java
    index a27517de8..7310ebdbb 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xou_cache_finder_mem.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xou_cache_finder_mem.java
    @@ -13,3 +13,29 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.xowa.files.repos.*;
    +public class Xou_cache_finder_mem implements Xou_cache_finder {
    +	private final    Ordered_hash hash = Ordered_hash_.New_bry();
    +	private final    Bry_bfr tmp_bfr = Bry_bfr_.New_w_size(255);
    +	private final    Xof_img_size img_size = new Xof_img_size();
    +	private final    Xof_url_bldr url_bldr = Xof_url_bldr.new_v2();
    +	public boolean Find(Xow_wiki wiki, byte[] page_url, Xof_fsdb_itm cur) {
    +		byte[] key = Xou_cache_itm.Key_gen(tmp_bfr, cur.Lnki_wiki_abrv(), cur.Lnki_ttl(), cur.Lnki_type(), cur.Lnki_upright(), cur.Lnki_w(), cur.Lnki_h(), cur.Lnki_time(), cur.Lnki_page(), cur.User_thumb_w());
    +		Xof_fsdb_itm mem = (Xof_fsdb_itm)hash.Get_by(key); 
    +		if (mem == null) {
    +			cur.Init_at_cache(false, 0, 0, Io_url_.Empty);
    +			return false;
    +		}
    +		Xof_repo_itm repo = wiki.File__repo_mgr().Get_trg_by_id_or_null(mem.Orig_repo_id(), mem.Lnki_ttl(), page_url);
    +		if (repo != null)	// HACK: ignore null repo for tests that don't create repos; DATE:2016-06-18
    +			mem.Init_at_html(Xof_exec_tid.Tid_wiki_page, img_size, repo, url_bldr);
    +		cur.Init_at_cache(true, mem.Html_w(), mem.Html_h(), mem.Html_view_url());
    +		return true;
    +	}
    +	public void Clear() {}
    +	public void Add(Xof_fsdb_itm cur) {
    +		byte[] key = Xou_cache_itm.Key_gen(tmp_bfr, cur.Lnki_wiki_abrv(), cur.Lnki_ttl(), cur.Lnki_type(), cur.Lnki_upright(), cur.Lnki_w(), cur.Lnki_h(), cur.Lnki_time(), cur.Lnki_page(), cur.User_thumb_w());
    +		hash.Add(key, cur);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xou_cache_itm.java b/400_xowa/src/gplx/xowa/files/caches/Xou_cache_itm.java
    index a27517de8..7682504ee 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xou_cache_itm.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xou_cache_itm.java
    @@ -13,3 +13,75 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.dbs.*;
    +public class Xou_cache_itm {
    +	public Xou_cache_itm
    +	( Bry_bfr lnki_key_bfr, byte db_state
    +	, byte[] lnki_wiki_abrv, byte[] lnki_ttl, int lnki_type, double lnki_upright, int lnki_w, int lnki_h, double lnki_time, int lnki_page, int user_thumb_w
    +	, int orig_repo_id, byte[] orig_ttl, int orig_ext_id, int orig_w, int orig_h
    +	, int html_w, int html_h, double html_time, int html_page
    +	, boolean file_is_orig, int file_w, double file_time, int file_page, long file_size
    +	, int view_count, long view_date
    +	) {
    +		this.db_state = db_state;
    +		this.lnki_wiki_abrv = lnki_wiki_abrv; this.lnki_ttl = lnki_ttl; this.lnki_type = lnki_type; this.lnki_upright = lnki_upright; this.lnki_w = lnki_w; this.lnki_h = lnki_h; this.lnki_time = lnki_time; this.lnki_page = lnki_page; this.user_thumb_w = user_thumb_w;
    +		this.orig_repo_id = orig_repo_id; this.orig_ttl = orig_ttl; this.orig_ext_id = orig_ext_id; this.orig_w = orig_w; this.orig_h = orig_h;
    +		this.file_is_orig = file_is_orig; this.html_w = html_w; this.html_h = html_h; this.html_time = html_time; this.html_page = html_page;
    +		this.file_w = file_w; this.file_time = file_time; this.file_page = file_page; this.file_size = file_size;
    +		this.view_count = view_count; this.view_date = view_date;
    +		this.lnki_key = Key_gen(lnki_key_bfr, lnki_wiki_abrv, lnki_ttl, lnki_type, lnki_upright, lnki_w, lnki_h, lnki_time, lnki_page, user_thumb_w);
    +	}
    +	public byte Db_state() {return db_state;} public void Db_state_(byte v) {db_state = v;} private byte db_state;
    +	public byte[]		Lnki_wiki_abrv() {return lnki_wiki_abrv;} private final    byte[] lnki_wiki_abrv;	// differentiate commonwiki rows inserted by one wiki vs another
    +	public byte[]		Lnki_key() {return lnki_key;} private final    byte[] lnki_key;		// unique key by lnki props
    +	public byte[]		Lnki_ttl() {return lnki_ttl;} private final    byte[] lnki_ttl;
    +	public int			Lnki_type() {return lnki_type;} private final    int lnki_type;
    +	public double		Lnki_upright() {return lnki_upright;} private final    double lnki_upright;
    +	public int			Lnki_w() {return lnki_w;} private final    int lnki_w;
    +	public int			Lnki_h() {return lnki_h;} private final    int lnki_h;
    +	public double		Lnki_time() {return lnki_time;} private final    double lnki_time;
    +	public int			Lnki_page() {return lnki_page;} private final    int lnki_page;
    +	public int			User_thumb_w() {return user_thumb_w;} private final    int user_thumb_w;
    +	public int			Orig_repo_id() {return orig_repo_id;} private final    int orig_repo_id;
    +	public byte[]		Orig_ttl() {return orig_ttl;} private final    byte[] orig_ttl;
    +	public byte[]		Orig_ttl_md5() {if (orig_ttl_md5 == null) orig_ttl_md5 = Xof_file_wkr_.Md5_fast(orig_ttl); return orig_ttl_md5;} private byte[] orig_ttl_md5;
    +	public int			Orig_ext_id() {return orig_ext_id;} private final    int orig_ext_id;
    +	public Xof_ext		Orig_ext_itm() {if (orig_ext_itm == null) orig_ext_itm = Xof_ext_.new_by_id_(orig_ext_id); return orig_ext_itm;} private Xof_ext orig_ext_itm;
    +	public int			Orig_w() {return orig_w;} private final    int orig_w;
    +	public int			Orig_h() {return orig_h;} private final    int orig_h;
    +	public int			Html_w() {return html_w;} private final    int html_w;
    +	public int			Html_h() {return html_h;} private final    int html_h;
    +	public double		Html_time() {return html_time;} private final    double html_time;
    +	public int			Html_page() {return html_page;} private final    int html_page;
    +	public boolean			File_is_orig() {return file_is_orig;} private final    boolean file_is_orig;
    +	public int			File_w() {return file_w;} private int file_w;
    +	public double		File_time() {return file_time;} private final    double file_time;
    +	public int			File_page() {return file_page;} private final    int file_page;
    +	public long			File_size() {return file_size;} private final    long file_size;
    +	public Io_url		File_url() {return file_url;} public void File_url_(Io_url v) {file_url = v;} private Io_url file_url;
    +	public int			View_count() {return view_count;} private int view_count;
    +	public long			View_date() {return view_date;} private long view_date;
    +	public void Update_view_stats() {
    +		++view_count;
    +		view_date = Datetime_now.Get().Timestamp_unix();
    +		db_state = Db_cmd_mode.To_update(db_state);
    +	}
    +	public static final    Xou_cache_itm Null = null;
    +	public static byte[] Key_gen(Bry_bfr key_bfr, byte[] lnki_wiki_abrv, byte[] ttl, int type, double upright, int w, int h, double time, int page, int user_thumb_w) {
    +		key_bfr.Add(lnki_wiki_abrv).Add_byte_pipe()
    +			.Add(ttl).Add_byte_pipe().Add_int_variable(type).Add_byte_pipe().Add_double(upright).Add_byte_pipe()
    +			.Add_int_variable(w).Add_byte_pipe().Add_int_variable(h).Add_byte_pipe().Add_double(time).Add_byte_pipe().Add_int_variable(page)
    +			.Add_int_variable(user_thumb_w)
    +			;
    +		return key_bfr.To_bry_and_clear();
    +	}
    +}
    +class Xof_cache_mgr_sorter implements gplx.core.lists.ComparerAble {
    +	public int compare(Object lhsObj, Object rhsObj) {
    +		Xou_cache_itm lhs = (Xou_cache_itm)lhsObj;
    +		Xou_cache_itm rhs = (Xou_cache_itm)rhsObj;
    +		return -Long_.Compare(lhs.View_date(), rhs.View_date());	// - for DESC sort
    +	}
    +	public static final    Xof_cache_mgr_sorter Instance = new Xof_cache_mgr_sorter(); Xof_cache_mgr_sorter() {}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xou_cache_mgr.java b/400_xowa/src/gplx/xowa/files/caches/Xou_cache_mgr.java
    index a27517de8..9f6295d44 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xou_cache_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xou_cache_mgr.java
    @@ -13,3 +13,238 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.primitives.*; import gplx.dbs.*; import gplx.dbs.cfgs.*;
    +import gplx.xowa.files.fsdb.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.imgs.*;
    +import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; import gplx.xowa.users.data.*;
    +public class Xou_cache_mgr implements Gfo_invk {
    +	private long fsys_size_cur = 0;
    +	private long fsys_size_min = Io_mgr.Len_mb * 75;
    +	private long fsys_size_max = Io_mgr.Len_mb * 100;
    +	private final    Xoa_wiki_mgr wiki_mgr; private final    Xou_cache_tbl cache_tbl; private final    Db_cfg_tbl cfg_tbl; private final    Bry_bfr tmp_bfr = Bry_bfr_.Reset(512);
    +	private final    Ordered_hash hash = Ordered_hash_.New_bry(); private final    Xof_url_bldr url_bldr = Xof_url_bldr.new_v2(); private final    Object thread_lock = new Object();
    +	private final    Io_url cache_dir; private boolean db_load_needed = true;
    +	public Xou_cache_mgr(Xoa_wiki_mgr wiki_mgr, Io_url cache_dir, Xou_db_file db_file) {
    +		this.wiki_mgr = wiki_mgr; this.cache_dir = cache_dir;
    +		this.cfg_tbl = db_file.Tbl__cfg();
    +		this.cache_tbl = db_file.Tbl__cache();
    +	}
    +	public boolean Enabled() {return enabled;} private boolean enabled = true; public void Enabled_n_() {enabled = false;}
    +	public void Init_by_app(Xoa_app app) {
    +		app.Cfg().Bind_many_app(this, Cfg__fsys_size_min, Cfg__fsys_size_max);
    +		app.Cfg().Sub_many_app(this, Run__fsys_reduce_to_min, Run__fsys_clear);
    +		app.Cfg().Dflt_mgr().Add(this, Val__fsys_info);
    +	}
    +	public Xou_cache_itm Get_or_null(Xof_fsdb_itm fsdb) {return Get_or_null(fsdb.Lnki_wiki_abrv(), fsdb.Lnki_ttl(), fsdb.Lnki_type(), fsdb.Lnki_upright(), fsdb.Lnki_w(), fsdb.Lnki_h(), fsdb.Lnki_time(), fsdb.Lnki_page(), fsdb.User_thumb_w());}
    +	public Xou_cache_itm Get_or_null(byte[] wiki, byte[] ttl, int type, double upright, int w, int h, double time, int page, int user_thumb_w) {
    +		if (!enabled) return null;
    +		synchronized (thread_lock) {
    +			this.Page_bgn();
    +			byte[] key = Xou_cache_itm.Key_gen(tmp_bfr, wiki, ttl, type, upright, w, h, time, page, user_thumb_w);
    +			Xou_cache_itm rv = (Xou_cache_itm)hash.Get_by(key);
    +			if (rv == Xou_cache_itm.Null) {
    +				rv = cache_tbl.Select_one(wiki, ttl, type, upright, w, h, time, page, user_thumb_w);
    +				if (rv == Xou_cache_itm.Null) return Xou_cache_itm.Null;
    +				hash.Add(key, rv);
    +			}
    +			return rv;
    +		}
    +	}
    +	public void Update(Xof_fsdb_itm fsdb) {
    +		synchronized (thread_lock) {
    +			Xou_cache_itm itm = Get_or_null(fsdb.Lnki_wiki_abrv(), fsdb.Lnki_ttl(), fsdb.Lnki_type(), fsdb.Lnki_upright(), fsdb.Lnki_w(), fsdb.Lnki_h(), fsdb.Lnki_time(), fsdb.Lnki_page(), fsdb.User_thumb_w());
    +			if (itm == Xou_cache_itm.Null) {
    +				itm = new Xou_cache_itm(tmp_bfr, Db_cmd_mode.Tid_create, fsdb.Lnki_wiki_abrv(), fsdb.Lnki_ttl(), fsdb.Lnki_type(), fsdb.Lnki_upright(), fsdb.Lnki_w(), fsdb.Lnki_h(), fsdb.Lnki_time(), fsdb.Lnki_page(), fsdb.User_thumb_w()
    +					, fsdb.Orig_repo_id(), fsdb.Orig_ttl(), fsdb.Orig_ext().Id(), fsdb.Orig_w(), fsdb.Orig_h()
    +					, fsdb.Html_w(), fsdb.Html_h(), fsdb.Lnki_time(), fsdb.Lnki_page()
    +					, fsdb.File_is_orig(), fsdb.File_w(), fsdb.Lnki_time(), fsdb.Lnki_page(), fsdb.File_size()
    +					, 1, Datetime_now.Get().Timestamp_unix())
    +					;
    +				hash.Add(itm.Lnki_key(), itm);
    +				fsys_size_cur += itm.File_size();
    +			}
    +			else
    +				itm.Update_view_stats();
    +		}
    +	}
    +	public void Page_bgn() {
    +		if (db_load_needed) {
    +			db_load_needed = false;
    +			fsys_size_cur = cfg_tbl.Assert_long("user.file_cache", "size_sum", 0);
    +			cache_tbl.Select_all(tmp_bfr, hash);
    +		}
    +	}
    +	public void Page_end(Xoa_wiki_mgr wiki_mgr) {	// threaded
    +		cfg_tbl.Update_long("user.file_cache", "size_sum", fsys_size_cur);
    +		this.Db_save();
    +		if (fsys_size_cur > fsys_size_max) this.Reduce(fsys_size_min);
    +	}
    +	@gplx.Internal protected void Clear() {
    +		db_load_needed = true;
    +		fsys_size_cur = 0;
    +		hash.Clear();
    +	}
    +	public void Db_save() {
    +		synchronized (thread_lock) {
    +			Db_conn conn = cache_tbl.Conn();
    +			try {
    +				conn.Txn_bgn("user__file_cache__save");
    +				int len = hash.Count();
    +				for (int i = 0; i < len; ++i) {
    +					Xou_cache_itm itm = (Xou_cache_itm)hash.Get_at(i);
    +					cache_tbl.Db_save(itm);
    +				}
    +				conn.Txn_end();
    +			}
    +			catch (Exception e) {conn.Txn_cxl(); throw Err_.new_exc(e, "cache", "unknown error while saving cache; err=~{0}", Err_.Message_gplx_log(e));}
    +		}
    +	}
    +	public void Reduce(long reduce_to) {
    +		Xoa_app_.Usr_dlg().Note_many("", "", "cache compress started");
    +		synchronized (thread_lock) {
    +			try {
    +				this.Db_save(); cache_tbl.Select_all(tmp_bfr, hash);		// save and load
    +				Ordered_hash grp_hash = Ordered_hash_.New();				// aggregate by file path; needed when same commons file used by two wikis
    +				int len = hash.Count();
    +				for (int i = 0; i < len; ++i) {
    +					Xou_cache_itm itm = (Xou_cache_itm)hash.Get_at(i);
    +					Io_url itm_url = Calc_url(itm);
    +					itm.File_url_(itm_url);
    +					Xou_cache_grp grp = (Xou_cache_grp)grp_hash.Get_by(itm_url.Raw());
    +					if (grp == null) {
    +						grp = new Xou_cache_grp(itm_url);
    +						grp_hash.Add(itm_url.Raw(), grp);
    +					}
    +					grp.Add(itm);
    +				}
    +				grp_hash.Sort_by(Xou_cache_grp_sorter.Instance);				// sorts by cache_time desc
    +				len = grp_hash.Count();
    +				long fsys_size_calc = 0, fsys_size_temp = 0;
    +				List_adp deleted = List_adp_.New();
    +				for (int i = 0; i < len; ++i) {							// iterate and find items to delete
    +					Xou_cache_grp grp = (Xou_cache_grp)grp_hash.Get_at(i);
    +					fsys_size_temp = fsys_size_calc + grp.File_size();
    +					if (	fsys_size_temp > reduce_to					// fsys_size_cur exceeded; mark itm for deletion
    +						||	fsys_size_temp == -1						// fsys_size sometimes -1; note -1 b/c file is missing; should fix, but for now, consider -1 size deleted; DATE:2015-08-05
    +						)
    +						deleted.Add(grp);
    +					else
    +						fsys_size_calc = fsys_size_temp;
    +				}
    +				len = deleted.Count();
    +				Db_conn conn = cache_tbl.Conn();
    +				conn.Txn_bgn("user__file_cache__delete");
    +				for (int i = 0; i < len; i++) {							// iterate and delete
    +					Xou_cache_grp grp = (Xou_cache_grp)deleted.Get_at(i);
    +					grp.Delete(hash, cache_tbl);
    +					fsys_size_cur -= grp.File_size();
    +				}
    +				conn.Txn_end();
    +				Io_mgr.Instance.Delete_dir_empty(cache_dir);
    +			}
    +			catch (Exception e) {Xoa_app_.Usr_dlg().Warn_many("", "", "failed to compress cache: err=~{0}", Err_.Message_gplx_full(e)); return;}
    +		}
    +		Xoa_app_.Usr_dlg().Note_many("", "", "cache compress done");
    +	}
    +	private Io_url Calc_url(Xou_cache_itm cache) {
    +		byte[] wiki_domain = Xow_abrv_xo_.To_itm(cache.Lnki_wiki_abrv()).Domain_bry();
    +		Xow_wiki wiki = wiki_mgr.Get_by_or_make_init_y(wiki_domain); if (wiki == null) return Io_url_.Empty;	// wiki is not available; should only happen in read-only mode; DATE:2015-05-23
    +		Xof_repo_itm trg_repo = wiki.File__repo_mgr().Get_trg_by_id_or_null(cache.Orig_repo_id(), cache.Lnki_ttl(), Bry_.Empty);
    +		if (trg_repo == null) return Io_url_.Empty;
    +		byte[] orig_ttl = cache.Orig_ttl();
    +		byte[] orig_md5 = cache.Orig_ttl_md5();
    +		Xof_ext orig_ext = cache.Orig_ext_itm();
    +		orig_ttl = trg_repo.Gen_name_trg(tmp_bfr, orig_ttl, orig_md5, orig_ext);
    +		byte mode_id = cache.File_is_orig() ? Xof_img_mode_.Tid__orig : Xof_img_mode_.Tid__thumb;
    +		return url_bldr.Init_for_trg_file(trg_repo, mode_id, orig_ttl, orig_md5, orig_ext, cache.File_w()
    +			, Xof_lnki_time.Convert_to_xowa_thumbtime	(orig_ext.Id(), cache.File_time())
    +			, Xof_lnki_time.Convert_to_xowa_page		(orig_ext.Id(), cache.File_page())
    +			).Xto_url();
    +	}
    +
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Cfg__fsys_size_min))			this.fsys_size_min = m.ReadLong("v") * Io_mgr.Len_mb;
    +		else if	(ctx.Match(k, Cfg__fsys_size_max))			this.fsys_size_max = m.ReadLong("v") * Io_mgr.Len_mb;
    +		else if	(ctx.Match(k, Val__fsys_info))				return this.Info_str();
    +		else if	(ctx.Match(k, Run__fsys_reduce_to_min))		{this.Reduce(fsys_size_min);}
    +		else if	(ctx.Match(k, Run__fsys_clear))				{this.Reduce(0);}
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	private static final String 
    +	  Cfg__fsys_size_min		= "xowa.files.cache.fsys_size_min"
    +	, Cfg__fsys_size_max		= "xowa.files.cache.fsys_size_max"
    +	, Run__fsys_reduce_to_min	= "xowa.files.cache.reduce_to_min"
    +	, Run__fsys_clear			= "xowa.files.cache.clear"
    +	, Val__fsys_info			= "xowa.files.cache.info"
    +	;
    +	public void Fsys_size_(long min, long max) {fsys_size_min = min; fsys_size_max = max;}	// TEST:
    +	private String Info_str() {
    +		this.Page_bgn();
    +		Bry_bfr bfr = Bry_bfr_.New_w_size(255);
    +		Keyval[] ary = this.Info_kvs();
    +		int len = ary.length;
    +		for (int i = 0; i < len; ++i) {
    +			Keyval kv = ary[i];
    +			bfr.Add_str_a7(kv.Key()).Add_str_a7(": ").Add_str_u8(kv.Val_to_str_or_empty()).Add_byte_nl();
    +		}
    +		return bfr.To_str_and_clear();
    +	}
    +	private Keyval[] Info_kvs() {
    +		long view_date = Long_.Max_value;
    +		long fsys_size = 0;
    +		int len = hash.Count();
    +		for (int i = 0; i < len; ++i) {
    +			Xou_cache_itm itm = (Xou_cache_itm)hash.Get_at(i);
    +			fsys_size += itm.File_size();
    +			if (itm.View_date() < view_date) view_date = itm.View_date();
    +		}
    +		return Keyval_.Ary
    +		( Keyval_.new_("cache folder", cache_dir.Xto_api())
    +		, Keyval_.new_("space used", gplx.core.ios.Io_size_.To_str(fsys_size))
    +		, Keyval_.new_("file count", len)
    +		, Keyval_.new_("oldest file", view_date == Long_.Max_value ? "" : DateAdp_.unixtime_utc_seconds_(view_date).XtoStr_fmt_iso_8561())
    +		);
    +	}
    +}
    +class Xou_cache_grp {
    +	private final    List_adp list = List_adp_.New();
    +	public Xou_cache_grp(Io_url file_url) {this.file_url = file_url;}
    +	public long View_date() {return view_date;} private long view_date;
    +	public long File_size() {return file_size;} private long file_size;
    +	public Io_url File_url() {return file_url;} private final    Io_url file_url;
    +	public int Len() {return list.Count();}
    +	public void Add(Xou_cache_itm itm) {
    +		if (itm.View_date() > view_date) view_date = itm.View_date();
    +		file_size += itm.File_size();
    +		list.Add(itm);
    +	}
    +	public void Delete(Ordered_hash cache_hash, Xou_cache_tbl cache_tbl) {
    +		int len = list.Count();
    +		boolean deleted = false;
    +		for (int i = 0; i < len; ++i) {
    +			Xou_cache_itm itm = (Xou_cache_itm)list.Get_at(i);
    +			cache_hash.Del(itm.Lnki_key());
    +			itm.Db_state_(Db_cmd_mode.Tid_delete);
    +			cache_tbl.Db_save(itm);
    +			gplx.core.ios.IoItmFil fil = Io_mgr.Instance.QueryFil(itm.File_url());
    +			if (fil.Exists()) {
    +				Io_mgr.Instance.DeleteFil(itm.File_url());
    +				deleted = true;
    +			}
    +			else {
    +				if (!deleted)	// multiple itms in cache resolve to same path; 1st gets deleted, but 2nd won't; ignore warning; PAGE:s.w:File:William_Shakespeare_Chandos_Portrait.jpg; DATE:2015-05-23
    +					Xoa_app_.Usr_dlg().Warn_many("", "", "cache:file not found; file=~{0}", itm.File_url().Raw());
    +			}
    +		}
    +	}
    +	public Xou_cache_itm Get_at(int i) {return (Xou_cache_itm)list.Get_at(i);}
    +}
    +class Xou_cache_grp_sorter implements gplx.core.lists.ComparerAble {
    +	public int compare(Object lhsObj, Object rhsObj) {
    +		Xou_cache_grp lhs = (Xou_cache_grp)lhsObj;
    +		Xou_cache_grp rhs = (Xou_cache_grp)rhsObj;
    +		return -Long_.Compare(lhs.View_date(), rhs.View_date());	// - for DESC sort
    +	}
    +	public static final    Xou_cache_grp_sorter Instance = new Xou_cache_grp_sorter(); Xou_cache_grp_sorter() {}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xou_cache_mgr_tst.java b/400_xowa/src/gplx/xowa/files/caches/Xou_cache_mgr_tst.java
    index a27517de8..e0cc39167 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xou_cache_mgr_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xou_cache_mgr_tst.java
    @@ -13,3 +13,100 @@ The terms of each license can be found in the source code repository:
     GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
     Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
     */
    +package gplx.xowa.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import org.junit.*; import gplx.dbs.*; import gplx.xowa.files.fsdb.*; import gplx.xowa.files.repos.*;
    +public class Xou_cache_mgr_tst {
    +	@Before public void init() {fxt.Clear();} private final    Xou_cache_mgr_fxt fxt = new Xou_cache_mgr_fxt();
    +	@Test   public void Update() {
    +		Xof_fsdb_itm itm_1 = fxt.Make_itm("en.w", "1.png", 2);
    +		fxt.Exec_update(itm_1);
    +		fxt.Test_get(itm_1, 1, 0);
    +	}
    +	@Test   public void Update_mult() {
    +		Xof_fsdb_itm itm_1 = fxt.Make_itm("en.w", "1.png", 2);
    +		fxt.Exec_update(itm_1, itm_1, itm_1);
    +		fxt.Test_get(itm_1, 3, 2);
    +	}
    +	@Test   public void Reload() {
    +		Xof_fsdb_itm itm_1 = fxt.Make_itm("en.w", "1.png", 2);
    +		fxt.Exec_update(itm_1, itm_1, itm_1);
    +		fxt.Test_get(itm_1, 3, 2);
    +		fxt.Exec_save_and_clear();
    +		fxt.Test_get(itm_1, 3, 2);
    +	}
    +	@Test  public void Reduce() {
    +		fxt.Init_delete(3, 5);
    +		Xof_fsdb_itm itm_1 = fxt.Make_itm("en.w", "1.png", 2);
    +		Xof_fsdb_itm itm_2 = fxt.Make_itm("en.w", "2.png", 2);
    +		Xof_fsdb_itm itm_3 = fxt.Make_itm("en.w", "3.png", 2);
    +		fxt.Exec_update(itm_1, itm_2, itm_3);
    +		fxt.Exec_reduce();
    +		fxt.Test_get_y(itm_3);
    +		fxt.Test_get_n(itm_1, itm_2);
    +	}
    +	@Test  public void Reduce_same() {
    +		fxt.Init_delete(3, 5);
    +		Xof_fsdb_itm itm_1 = fxt.Make_itm("en.w", "1.png", 2);
    +		Xof_fsdb_itm itm_2 = fxt.Make_itm("fr.w", "1.png", 2);
    +		Xof_fsdb_itm itm_3 = fxt.Make_itm("en.w", "2.png", 2);
    +		fxt.Exec_update(itm_3, itm_2, itm_1);
    +		fxt.Exec_reduce();
    +		fxt.Test_get_y(itm_3);
    +		fxt.Test_get_n(itm_1, itm_2);
    +	}
    +}
    +class Xou_cache_mgr_fxt {
    +	private Xou_cache_mgr mgr;
    +	private long cache_min;
    +	public void Clear() {
    +		Datetime_now.Manual_(DateAdp_.new_(1970, 1, 1, 0, 0, 0, 0));
    +		Io_mgr.Instance.InitEngine_mem();
    +		Db_conn_bldr.Instance.Reg_default_mem();
    +		Xoae_app app = Xoa_app_fxt.Make__app__edit();			
    +		app.User().User_db_mgr().Init_by_app(Bool_.N, app.Fsys_mgr().Root_dir().GenSubFil_nest("user", "xowa.user.anonymous.sqlite3"));
    +		Xoa_app_fxt.repo_(app, Xoa_app_fxt.Make__wiki__edit(app, "en.wikipedia.org"));
    +		Xoa_app_fxt.repo_(app, Xoa_app_fxt.Make__wiki__edit(app, "fr.wikipedia.org"));
    +		this.mgr = new Xou_cache_mgr(app.Wiki_mgr(), app.Fsys_mgr().File_dir(), app.User().User_db_mgr().Db_file());
    +	}
    +	public void Init_delete(long min, long max) {
    +		cache_min = min;
    +		mgr.Fsys_size_(min, max);
    +	}
    +	public Xof_fsdb_itm Make_itm(String wiki, String file, int size) {
    +		Xof_fsdb_itm rv = new Xof_fsdb_itm();
    +		byte[] wiki_domain = Bry_.new_a7(wiki);
    +		byte[] file_ttl = Bry_.new_a7(file);
    +		rv.Init_at_lnki(Xof_exec_tid.Tid_wiki_page, wiki_domain, file_ttl, Byte_.Zero, 1, 1, 1 ,1, 1, 1);
    +		rv.Init_at_orig(Xof_repo_tid_.Tid__local, wiki_domain, file_ttl, Xof_ext_.new_by_id_(Xof_ext_.Id_png), 120, 120, Bry_.Empty);
    +		rv.File_size_(size);
    +		return rv;
    +	}
    +	public void Exec_update(Xof_fsdb_itm... ary) {
    +		for (Xof_fsdb_itm itm : ary)
    +			mgr.Update(itm);
    +	}
    +	public void Exec_save_and_clear() {
    +		mgr.Db_save();
    +		mgr.Clear();
    +	}
    +	public void Exec_reduce() {
    +		mgr.Reduce(cache_min);
    +	}
    +	public void Test_get(Xof_fsdb_itm fsdb, int expd_view_count, long expd_view_date) {
    +		Xou_cache_itm cache = mgr.Get_or_null(fsdb);
    +		Tfds.Eq(expd_view_count, cache.View_count(), "count");
    +		Tfds.Eq(expd_view_date , (cache.View_date() / 60) - 1, "time");	// Tfds.Now increments by 60 seconds; also -1 b/c Gfo_log now calls Datetime_now.Get once
    +	}
    +	public void Test_get_n(Xof_fsdb_itm... ary) {
    +		for (Xof_fsdb_itm itm : ary) {
    +			Xou_cache_itm cache = mgr.Get_or_null(itm);
    +			Tfds.Eq_null(cache, String_.new_u8(itm.Lnki_ttl()));
    +		}
    +	}
    +	public void Test_get_y(Xof_fsdb_itm... ary) {
    +		for (Xof_fsdb_itm itm : ary) {
    +			Xou_cache_itm cache = mgr.Get_or_null(itm);
    +			Tfds.Eq_nullNot(cache, String_.new_u8(itm.Lnki_ttl()));
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xou_cache_tbl.java b/400_xowa/src/gplx/xowa/files/caches/Xou_cache_tbl.java
    index a27517de8..96acf34cd 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xou_cache_tbl.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xou_cache_tbl.java
    @@ -13,3 +13,183 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.dbs.*;
    +public class Xou_cache_tbl implements Rls_able {
    +	private String tbl_name = "file_cache"; private final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	private String
    +	  fld_lnki_wiki_abrv, fld_lnki_ttl, fld_lnki_type, fld_lnki_upright, fld_lnki_w, fld_lnki_h, fld_lnki_time, fld_lnki_page, fld_user_thumb_w
    +	, fld_orig_repo, fld_orig_ttl, fld_orig_ext, fld_orig_w, fld_orig_h
    +	, fld_html_w, fld_html_h, fld_html_time, fld_html_page
    +	, fld_file_is_orig, fld_file_w, fld_file_time, fld_file_page
    +	, fld_file_size, fld_view_count, fld_view_date
    +	;
    +	public String Tbl_name() {return tbl_name;}
    +	public String Fld_orig_ttl() {return fld_orig_ttl;}
    +	private final    Db_conn conn; private final    Db_stmt_bldr stmt_bldr = new Db_stmt_bldr(); private Db_stmt select_stmt;
    +	private final    Bry_bfr lnki_key_bfr = Bry_bfr_.Reset(255);
    +	public Db_conn Conn() {return conn;}
    +	public Xou_cache_tbl(Db_conn conn) {
    +		this.conn = conn;
    +		fld_lnki_wiki_abrv	= flds.Add_str("lnki_wiki_abrv", 255);
    +		fld_lnki_ttl		= flds.Add_str("lnki_ttl", 255);
    +		fld_lnki_type		= flds.Add_int("lnki_type");
    +		fld_lnki_upright	= flds.Add_double("lnki_upright");
    +		fld_lnki_w			= flds.Add_int("lnki_w");
    +		fld_lnki_h			= flds.Add_int("lnki_h");
    +		fld_lnki_time		= flds.Add_double("lnki_time");
    +		fld_lnki_page		= flds.Add_int("lnki_page");
    +		fld_user_thumb_w	= flds.Add_int("user_thumb_w");
    +		fld_orig_repo		= flds.Add_int("orig_repo");
    +		fld_orig_ttl		= flds.Add_str("orig_ttl", 255);
    +		fld_orig_ext		= flds.Add_int("orig_ext");
    +		fld_orig_w			= flds.Add_int("orig_w");
    +		fld_orig_h			= flds.Add_int("orig_h");
    +		fld_html_w			= flds.Add_int("html_w");
    +		fld_html_h			= flds.Add_int("html_h");
    +		fld_html_time		= flds.Add_double("html_time");
    +		fld_html_page		= flds.Add_int("html_page");
    +		fld_file_is_orig	= flds.Add_bool("file_is_orig");
    +		fld_file_w			= flds.Add_int("file_w");
    +		fld_file_time		= flds.Add_double("file_time");
    +		fld_file_page		= flds.Add_int("file_page");
    +		fld_file_size		= flds.Add_long("file_size");
    +		fld_view_count		= flds.Add_int("view_count");
    +		fld_view_date		= flds.Add_long("view_date");
    +		stmt_bldr.Conn_(conn, tbl_name, flds, fld_lnki_wiki_abrv, fld_lnki_ttl, fld_lnki_type, fld_lnki_upright, fld_lnki_w, fld_lnki_h, fld_lnki_time, fld_lnki_page, fld_user_thumb_w);
    +		conn.Rls_reg(this);
    +	}
    +	public void Rls() {
    +		select_stmt = Db_stmt_.Rls(select_stmt);
    +	}
    +	public void Create_tbl() {
    +		Dbmeta_tbl_itm meta = Dbmeta_tbl_itm.New(tbl_name, flds
    +		, Dbmeta_idx_itm.new_unique_by_tbl(tbl_name, "main", fld_lnki_wiki_abrv, fld_lnki_ttl, fld_lnki_type, fld_lnki_upright, fld_lnki_w, fld_lnki_h, fld_lnki_time, fld_lnki_page, fld_user_thumb_w)
    +		, Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "size", fld_file_size)
    +		, Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "date", fld_view_date)
    +		);
    +		conn.Meta_tbl_create(meta);
    +	}
    +	public Xou_cache_itm Select_one(byte[] lnki_wiki_abrv, byte[] lnki_ttl, int lnki_type, double lnki_upright, int lnki_w, int lnki_h, double lnki_time, int lnki_page, int user_thumb_w) {
    +		if (select_stmt == null) select_stmt = conn.Stmt_select(tbl_name, flds, String_.Ary(fld_lnki_wiki_abrv, fld_lnki_ttl, fld_lnki_type, fld_lnki_upright, fld_lnki_w, fld_lnki_h, fld_lnki_time, fld_lnki_page, fld_user_thumb_w));
    +		Db_rdr rdr = select_stmt.Clear()
    +			.Crt_bry_as_str	(fld_lnki_wiki_abrv	, lnki_wiki_abrv)
    +			.Crt_bry_as_str	(fld_lnki_ttl		, lnki_ttl)
    +			.Crt_int		(fld_lnki_type		, lnki_type)
    +			.Crt_double		(fld_lnki_upright	, lnki_upright)
    +			.Crt_int		(fld_lnki_w			, lnki_w)
    +			.Crt_int		(fld_lnki_h			, lnki_h)
    +			.Crt_double		(fld_lnki_time		, lnki_time)
    +			.Crt_int		(fld_lnki_page		, lnki_page)
    +			.Crt_int		(fld_user_thumb_w	, user_thumb_w)
    +			.Exec_select__rls_manual();
    +		try {return rdr.Move_next() ? new_itm(rdr) : Xou_cache_itm.Null;}
    +		finally {rdr.Rls();}
    +	}
    +	public void Select_all(Bry_bfr fil_key_bldr, Ordered_hash hash) {
    +		hash.Clear();
    +		Db_rdr rdr = conn.Stmt_select(tbl_name, flds, Dbmeta_fld_itm.Str_ary_empty).Exec_select__rls_auto();
    +		try {
    +			while (rdr.Move_next()) {
    +				Xou_cache_itm itm = new_itm(rdr);
    +				hash.Add(itm.Lnki_key(), itm);
    +			}
    +		}
    +		finally {rdr.Rls();}
    +	}
    +	public void Db_save(Xou_cache_itm itm) {
    +		try {
    +			Db_stmt stmt = stmt_bldr.Get(itm.Db_state());
    +			switch (itm.Db_state()) {
    +				case Db_cmd_mode.Tid_create:	stmt.Clear(); Db_save_crt(stmt, itm, Bool_.Y);	Db_save_val(stmt, itm); stmt.Exec_insert(); break;
    +				case Db_cmd_mode.Tid_update:	stmt.Clear();									Db_save_val(stmt, itm); Db_save_crt(stmt, itm, Bool_.N); stmt.Exec_update(); break;
    +				case Db_cmd_mode.Tid_delete:	stmt.Clear(); Db_save_crt(stmt, itm, Bool_.N);	stmt.Exec_delete();	break;
    +				case Db_cmd_mode.Tid_ignore:	break;
    +				default:						throw Err_.new_unhandled(itm.Db_state());
    +			}
    +			itm.Db_state_(Db_cmd_mode.Tid_ignore);
    +		} catch (Exception e) {stmt_bldr.Rls(); throw Err_.new_exc(e, "xo", "db_save failed");}
    +	}
    +	@gplx.Internal protected Db_rdr Select_all_for_test() {return conn.Stmt_select(tbl_name, flds, Dbmeta_fld_itm.Str_ary_empty).Exec_select__rls_manual();}
    +	private void Db_save_crt(Db_stmt stmt, Xou_cache_itm itm, boolean insert) {
    +		if (insert) {
    +			stmt.Val_bry_as_str		(fld_lnki_wiki_abrv		, itm.Lnki_wiki_abrv())
    +				.Val_bry_as_str		(fld_lnki_ttl			, itm.Lnki_ttl())
    +				.Val_int			(fld_lnki_type			, itm.Lnki_type())
    +				.Val_double			(fld_lnki_upright		, itm.Lnki_upright())
    +				.Val_int			(fld_lnki_w				, itm.Lnki_w())
    +				.Val_int			(fld_lnki_h				, itm.Lnki_h())
    +				.Val_double			(fld_lnki_time			, itm.Lnki_time())
    +				.Val_int			(fld_lnki_page			, itm.Lnki_page())
    +				.Val_int			(fld_user_thumb_w		, itm.User_thumb_w())
    +			;
    +		}
    +		else {
    +			stmt.Crt_bry_as_str		(fld_lnki_wiki_abrv		, itm.Lnki_wiki_abrv())
    +				.Crt_bry_as_str		(fld_lnki_ttl			, itm.Lnki_ttl())
    +				.Crt_int			(fld_lnki_type			, itm.Lnki_type())
    +				.Crt_double			(fld_lnki_upright		, itm.Lnki_upright())
    +				.Crt_int			(fld_lnki_w				, itm.Lnki_w())
    +				.Crt_int			(fld_lnki_h				, itm.Lnki_h())
    +				.Crt_double			(fld_lnki_time			, itm.Lnki_time())
    +				.Crt_int			(fld_lnki_page			, itm.Lnki_page())
    +				.Crt_int			(fld_user_thumb_w		, itm.User_thumb_w())
    +			;
    +		}
    +	}
    +	private void Db_save_val(Db_stmt stmt, Xou_cache_itm itm) {
    +		byte[] orig_ttl = itm.Orig_ttl(), lnki_ttl = itm.Lnki_ttl();
    +		if (Bry_.Eq(orig_ttl, lnki_ttl)) orig_ttl = Bry_.Empty;	// PERF:only store redirects in orig_ttl; DATE:2015-05-14
    +		stmt.Val_int			(fld_orig_repo			, itm.Orig_repo_id())
    +			.Val_bry_as_str		(fld_orig_ttl			, orig_ttl)
    +			.Val_int			(fld_orig_ext			, itm.Orig_ext_id())
    +			.Val_int			(fld_orig_w				, itm.Orig_w())
    +			.Val_int			(fld_orig_h				, itm.Orig_h())
    +			.Val_int			(fld_html_w				, itm.Html_w())
    +			.Val_int			(fld_html_h				, itm.Html_h())
    +			.Val_double			(fld_html_time			, itm.Html_time())
    +			.Val_int			(fld_html_page			, itm.Html_page())
    +			.Val_bool_as_byte	(fld_file_is_orig		, itm.File_is_orig())
    +			.Val_int			(fld_file_w				, itm.File_w())
    +			.Val_double			(fld_file_time			, itm.File_time())
    +			.Val_int			(fld_file_page			, itm.File_page())
    +			.Val_long			(fld_file_size			, itm.File_size())
    +			.Val_int			(fld_view_count			, itm.View_count())
    +			.Val_long			(fld_view_date			, itm.View_date())
    +			;
    +	}
    +	private Xou_cache_itm new_itm(Db_rdr rdr) {
    +		byte[] lnki_ttl = rdr.Read_bry_by_str			(fld_lnki_ttl);
    +		byte[] orig_ttl = rdr.Read_bry_by_str			(fld_orig_ttl);
    +		if (orig_ttl.length == 0) orig_ttl = lnki_ttl;	// PERF:only store redirects in orig_ttl; DATE:2015-05-14
    +		return new Xou_cache_itm
    +		( lnki_key_bfr
    +		, Db_cmd_mode.Tid_ignore
    +		, rdr.Read_bry_by_str			(fld_lnki_wiki_abrv)
    +		, lnki_ttl
    +		, rdr.Read_int					(fld_lnki_type)
    +		, rdr.Read_double				(fld_lnki_upright)
    +		, rdr.Read_int					(fld_lnki_w)
    +		, rdr.Read_int					(fld_lnki_h)
    +		, Xof_lnki_time.Db_load_double	(rdr, fld_lnki_time)
    +		, Xof_lnki_page.Db_load_int		(rdr, fld_lnki_page)
    +		, rdr.Read_int					(fld_user_thumb_w)
    +		, rdr.Read_int					(fld_orig_repo)
    +		, orig_ttl
    +		, rdr.Read_int					(fld_orig_ext)
    +		, rdr.Read_int					(fld_orig_w)
    +		, rdr.Read_int					(fld_orig_h)
    +		, rdr.Read_int					(fld_html_w)
    +		, rdr.Read_int					(fld_html_h)
    +		, Xof_lnki_time.Db_load_double	(rdr, fld_html_time)
    +		, Xof_lnki_page.Db_load_int		(rdr, fld_html_page)
    +		, rdr.Read_bool_by_byte			(fld_file_is_orig)
    +		, rdr.Read_int					(fld_file_w)
    +		, Xof_lnki_time.Db_load_double	(rdr, fld_file_time)
    +		, Xof_lnki_page.Db_load_int		(rdr, fld_file_page)
    +		, rdr.Read_long					(fld_file_size)
    +		, rdr.Read_int					(fld_view_count)
    +		, rdr.Read_long					(fld_view_date)
    +		);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xou_cache_tbl_tst.java b/400_xowa/src/gplx/xowa/files/caches/Xou_cache_tbl_tst.java
    index a27517de8..2c9441a20 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xou_cache_tbl_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xou_cache_tbl_tst.java
    @@ -13,3 +13,47 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import org.junit.*; import gplx.dbs.*;
    +public class Xou_cache_tbl_tst {
    +	@Before public void init() {fxt.Clear();} private final    Xou_cache_tbl_fxt fxt = new Xou_cache_tbl_fxt();
    +	@Test  public void Orig_ttl__same()			{fxt.Test_save_orig_ttl("A.png", "A.png", "");}
    +	@Test  public void Orig_ttl__redirect()		{fxt.Test_save_orig_ttl("A.png", "B.png", "B.png");}
    +}
    +class Xou_cache_tbl_fxt {
    +	private final    Bry_bfr lnki_key_bfr = Bry_bfr_.New_w_size(255);
    +	private Xou_cache_tbl tbl;
    +	public void Clear() {
    +		Io_mgr.Instance.InitEngine_mem();
    +		Db_conn_bldr.Instance.Reg_default_mem();
    +		Db_conn_bldr_data conn_data = Db_conn_bldr.Instance.Get_or_new(Io_url_.mem_fil_("mem/test.xowa"));
    +		this.tbl = new Xou_cache_tbl(conn_data.Conn());
    +		tbl.Create_tbl();
    +	}
    +	public Xou_cache_itm Make_itm(String lnki_wiki_abrv_xo, String lnki_ttl, int lnki_type, double lnki_upright, int lnki_w, int lnki_h, double lnki_time, int lnki_page, int user_thumb_w
    +	, int orig_repo_id, String orig_ttl, int orig_ext_id, int orig_w, int orig_h
    +	, int html_w, int html_h, double html_time, int html_page
    +	, boolean file_is_orig, int file_w, double file_time, int file_page, long file_size
    +	, int view_count, long view_date) {
    +		return new Xou_cache_itm(lnki_key_bfr, Db_cmd_mode.Tid_create
    +		, Bry_.new_u8(lnki_wiki_abrv_xo), Bry_.new_u8(lnki_ttl), lnki_type, lnki_upright, lnki_w, lnki_h, lnki_time, lnki_page, user_thumb_w
    +		, orig_repo_id, Bry_.new_u8(orig_ttl), orig_ext_id, orig_w, orig_h
    +		, html_w, html_h, html_time, html_page
    +		, file_is_orig, file_w, file_time, file_page, file_size
    +		, view_count, view_date
    +		);
    +	}
    +	public Xou_cache_itm Exec_select_one(String lnki_wiki_abrv_xo, String lnki_ttl, int lnki_type, double lnki_upright, int lnki_w, int lnki_h, double lnki_time, int lnki_page, int user_thumb_w) {
    +		return tbl.Select_one(Bry_.new_u8(lnki_wiki_abrv_xo), Bry_.new_u8(lnki_ttl), 1, 1, 1, 1, 1, 1, 1);
    +	}
    +	public void Test_save_orig_ttl(String lnki_ttl, String orig_ttl, String expd_orig_ttl) {
    +		Xou_cache_itm itm = Make_itm("en.w", lnki_ttl, 1, 1, 1, 1, 1, 1, 1, 1, orig_ttl, 1, 1, 1, 1, 1, 1, 1, Bool_.Y, 1, 1, 1, 1, 1, 1);
    +		tbl.Db_save(itm);
    +		Db_rdr rdr = tbl.Select_all_for_test();
    +		try {
    +			Tfds.Eq_true(rdr.Move_next());
    +			Tfds.Eq(expd_orig_ttl, rdr.Read_str(tbl.Fld_orig_ttl()));
    +		}
    +		finally {rdr.Rls();}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/caches/Xou_file_itm_finder.java b/400_xowa/src/gplx/xowa/files/caches/Xou_file_itm_finder.java
    index a27517de8..9146949c1 100644
    --- a/400_xowa/src/gplx/xowa/files/caches/Xou_file_itm_finder.java
    +++ b/400_xowa/src/gplx/xowa/files/caches/Xou_file_itm_finder.java
    @@ -13,3 +13,49 @@ 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.files.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.ios.*;
    +import gplx.xowa.files.origs.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.fsdb.*; import gplx.xowa.files.bins.*; import gplx.xowa.guis.cbks.js.*;
    +public class Xou_file_itm_finder {
    +	private final    Xou_cache_mgr cache_mgr; private final    Xof_img_size img_size = new Xof_img_size();
    +	public Xou_file_itm_finder(Xou_cache_mgr cache_mgr) {this.cache_mgr = cache_mgr;}
    +	public boolean Find(Xowe_wiki wiki, int exec_tid, Xof_file_itm xfer, byte[] page_url) {
    +		byte[] lnki_ttl = xfer.Lnki_ttl();
    +		Xof_url_bldr url_bldr = wiki.Parser_mgr().Url_bldr();
    +		try {
    +			if (wiki.File__fsdb_mode().Tid__bld()) return false;	// disable during build
    +			Xou_cache_itm cache_itm = cache_mgr.Get_or_null(wiki.Domain_itm().Abrv_xo(), lnki_ttl, xfer.Lnki_type(), xfer.Lnki_upright(), xfer.Lnki_w(), xfer.Lnki_h(), xfer.Lnki_time(), xfer.Lnki_page(), Xof_img_size.Thumb_width_img);
    +			Xof_repo_itm repo = null;
    +			if (cache_itm == null) {// itm not in cache;
    +				Xof_ext lnki_ext = Xof_ext_.new_by_ttl_(lnki_ttl);
    +				if (lnki_ext.Id_is_ogg()) {	// look up orig; needed for identifying .ogg to vid for html_wtr to write; PAGE:en.w:WWI; DATE:2015-05-19
    +					Xof_orig_itm orig = wiki.File__orig_mgr().Find_by_ttl_or_null(lnki_ttl);
    +					if (orig != Xof_orig_itm.Null) {	// orig found
    +						repo = wiki.File__repo_mgr().Get_trg_by_id_or_null(orig.Repo(), lnki_ttl, page_url);
    +						if (repo != null) {
    +							xfer.Init_at_orig(orig.Repo(), repo.Wiki_domain(), orig.Ttl(), orig.Ext(), orig.W(), orig.H(), orig.Redirect());
    +							img_size.Html_size_calc(exec_tid, xfer.Lnki_w(), xfer.Lnki_h(), (byte)xfer.Lnki_type(), Xof_patch_upright_tid_.Tid_all, xfer.Lnki_upright(), orig.Ext().Id(), orig.W(), orig.H(), Xof_img_size.Thumb_width_img);	// calc size for html
    +							xfer.Init_at_gallery_end(img_size.Html_w(), img_size.Html_h(), url_bldr.To_url_trg(repo, xfer, Bool_.N), url_bldr.To_url_trg(repo, xfer, Bool_.Y));
    +						}
    +					}
    +				}
    +				return false;
    +			}
    +			repo = wiki.File__repo_mgr().Get_trg_by_id_or_null(cache_itm.Orig_repo_id(), lnki_ttl, page_url);
    +			if (repo == null) return false;	// unknown repo; shouldn't happen, but exit, else null ref
    +			xfer.Init_at_orig((byte)cache_itm.Orig_repo_id(), repo.Wiki_domain(), cache_itm.Orig_ttl(), cache_itm.Orig_ext_itm(), cache_itm.Orig_w(), cache_itm.Orig_h(), Bry_.Empty);
    +//				img_size.Html_size_calc(exec_tid, xfer.Lnki_w(), xfer.Lnki_h(), (byte)xfer.Lnki_type(), Xof_patch_upright_tid_.Tid_all, xfer.Lnki_upright(), cache_itm.Orig_ext_id(), cache_itm.Orig_w(), cache_itm.Orig_h(), Xof_img_size.Thumb_width_img);
    +//				xfer.Init_at_gallery_end(img_size.Html_w(), img_size.Html_h(), url_bldr.To_url_trg(repo, cache_itm, Bool_.N), url_bldr.To_url_trg(repo, cache_itm, Bool_.Y));
    +			xfer.Init_at_html(exec_tid, img_size, repo, url_bldr);
    +			if (Io_mgr.Instance.ExistsFil(xfer.Html_view_url())) {
    +				cache_itm.Update_view_stats();
    +				return true;
    +			}
    +			else
    +				return false;
    +		} catch (Exception e) {
    +			Xoa_app_.Usr_dlg().Warn_many("", "", "failed to find img: img=~{0} err=~{1}", lnki_ttl, Err_.Message_gplx_log(e));
    +			return false;
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/cnvs/Xof_cnv_wkr_.java b/400_xowa/src/gplx/xowa/files/cnvs/Xof_cnv_wkr_.java
    index a27517de8..7fc052bff 100644
    --- a/400_xowa/src/gplx/xowa/files/cnvs/Xof_cnv_wkr_.java
    +++ b/400_xowa/src/gplx/xowa/files/cnvs/Xof_cnv_wkr_.java
    @@ -13,3 +13,7 @@ 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.files.cnvs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +public class Xof_cnv_wkr_ {
    +	public static final byte Tid_null = Byte_.Max_value_127, Tid_n = 0, Tid_y = 1;
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/cnvs/Xof_img_wkr_resize_img.java b/400_xowa/src/gplx/xowa/files/cnvs/Xof_img_wkr_resize_img.java
    index a27517de8..07616e0c2 100644
    --- a/400_xowa/src/gplx/xowa/files/cnvs/Xof_img_wkr_resize_img.java
    +++ b/400_xowa/src/gplx/xowa/files/cnvs/Xof_img_wkr_resize_img.java
    @@ -13,3 +13,8 @@ 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.files.cnvs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.primitives.*;
    +public interface Xof_img_wkr_resize_img {
    +	boolean Resize_exec(Io_url src, Io_url trg, int trg_w, int trg_h, int ext_id, String_obj_ref rslt_val);
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/cnvs/Xof_img_wkr_resize_img_imageMagick.java b/400_xowa/src/gplx/xowa/files/cnvs/Xof_img_wkr_resize_img_imageMagick.java
    index a27517de8..986fc878c 100644
    --- a/400_xowa/src/gplx/xowa/files/cnvs/Xof_img_wkr_resize_img_imageMagick.java
    +++ b/400_xowa/src/gplx/xowa/files/cnvs/Xof_img_wkr_resize_img_imageMagick.java
    @@ -13,3 +13,30 @@ 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.files.cnvs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.primitives.*; import gplx.core.envs.*;
    +import gplx.xowa.bldrs.wms.*;
    +public class Xof_img_wkr_resize_img_imageMagick implements Xof_img_wkr_resize_img {
    +	private final Xowmf_mgr wmf_mgr; private final Process_adp cmd_convert, cmd_convert_svg_to_png;
    +	private boolean init_needed = true;
    +	public Xof_img_wkr_resize_img_imageMagick(Xowmf_mgr wmf_mgr, Process_adp cmd_convert, Process_adp cmd_convert_svg_to_png) {
    +		this.wmf_mgr = wmf_mgr; this.cmd_convert = cmd_convert; this.cmd_convert_svg_to_png = cmd_convert_svg_to_png;
    +	}
    +	public boolean Resize_exec(Io_url src, Io_url trg, int trg_w, int trg_h, int ext_id, String_obj_ref rslt_val) {
    +		if (!Io_mgr.Instance.ExistsFil(src)) return false;
    +		Io_mgr.Instance.CreateDirIfAbsent(trg.OwnerDir());
    +		if (init_needed) {
    +			init_needed = false;
    +			Gfo_usr_dlg usr_dlg = Xoa_app_.Usr_dlg();
    +			cmd_convert.Prog_dlg_(usr_dlg);
    +			cmd_convert_svg_to_png.Prog_dlg_(usr_dlg);
    +		}
    +		Process_adp cmd = ext_id == Xof_ext_.Id_svg ? cmd_convert_svg_to_png : cmd_convert;
    +		cmd.Prog_fmt_(String_.Replace(wmf_mgr.Download_wkr().Download_xrg().Prog_fmt_hdr(), "~", "~~") + " converting: ~{process_seconds} second(s); ~{process_exe_name} ~{process_exe_args}");
    +		cmd.Run(src.Raw(), trg.Raw(), trg_w, trg_h);
    +		rslt_val.Val_(cmd.Rslt_out());
    +		boolean rv = cmd.Exit_code_pass();
    +		if (!rv) Xoa_app_.Usr_dlg().Log_many("", "process_warning", "process completed with warnings: ~{0}", cmd.Rslt_out());
    +		return rv;	// NOTE: was true (?); DATE:2015-06-16
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/cnvs/Xof_img_wkr_resize_img_mok.java b/400_xowa/src/gplx/xowa/files/cnvs/Xof_img_wkr_resize_img_mok.java
    index a27517de8..1f9b56ede 100644
    --- a/400_xowa/src/gplx/xowa/files/cnvs/Xof_img_wkr_resize_img_mok.java
    +++ b/400_xowa/src/gplx/xowa/files/cnvs/Xof_img_wkr_resize_img_mok.java
    @@ -13,3 +13,18 @@ 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.files.cnvs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.primitives.*;
    +import gplx.gfui.*; import gplx.gfui.imgs.*;
    +import gplx.xowa.files.cnvs.*;
    +public class Xof_img_wkr_resize_img_mok implements Xof_img_wkr_resize_img {
    +	public boolean Resize_exec(Io_url src, Io_url trg, int trg_w, int trg_h, int ext_id, String_obj_ref rslt_val) {
    +		SizeAdp src_size = ImageAdp_.txt_fil_(src).Size();
    +		int src_w = src_size.Width(), src_h = src_size.Height();
    +		if (trg_w < 1) throw Err_.new_wo_type("trg_w must be > 0", "trg_w", trg_w);
    +		if (trg_h < 1) trg_h = Xof_xfer_itm_.Scale_h(src_w, src_h, trg_w);
    +		Io_mgr.Instance.SaveFilStr(trg, SizeAdp_.new_(trg_w, trg_h).To_str());
    +		return true;
    +	}
    +	public static final    Xof_img_wkr_resize_img_mok Instance = new Xof_img_wkr_resize_img_mok(); Xof_img_wkr_resize_img_mok() {}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/commons/Xof_commons_image_itm.java b/400_xowa/src/gplx/xowa/files/commons/Xof_commons_image_itm.java
    index a27517de8..d5d30bdbe 100644
    --- a/400_xowa/src/gplx/xowa/files/commons/Xof_commons_image_itm.java
    +++ b/400_xowa/src/gplx/xowa/files/commons/Xof_commons_image_itm.java
    @@ -13,3 +13,18 @@ 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.files.commons; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +public class Xof_commons_image_itm {
    +	public Xof_commons_image_itm(String name, String media_type, String minor_mime, int size, int width, int height, int bits, int ext_id, String timestamp) {
    +		this.name = name; this.media_type = media_type; this.minor_mime = minor_mime; this.size = size; this.width = width; this.height = height; this.bits = bits; this.ext_id = ext_id; this.timestamp = timestamp;
    +	}
    +	public String Name() {return name;} private final String name;
    +	public String Media_type() {return media_type;} private final String media_type;
    +	public String Minor_mime() {return minor_mime;} private final String minor_mime;
    +	public int Size() {return size;} private final int size;
    +	public int Width() {return width;} private final int width;
    +	public int Height() {return height;} private final int height;
    +	public int Bits() {return bits;} private final int bits;
    +	public int Ext_id() {return ext_id;} private final int ext_id;
    +	public String Timestamp() {return timestamp;} private final String timestamp;
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/commons/Xof_commons_image_tbl.java b/400_xowa/src/gplx/xowa/files/commons/Xof_commons_image_tbl.java
    index a27517de8..2ebced225 100644
    --- a/400_xowa/src/gplx/xowa/files/commons/Xof_commons_image_tbl.java
    +++ b/400_xowa/src/gplx/xowa/files/commons/Xof_commons_image_tbl.java
    @@ -13,3 +13,58 @@ 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.files.commons; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.dbs.*;
    +public class Xof_commons_image_tbl implements Rls_able {
    +	private Db_stmt stmt_insert;
    +	public Db_conn Conn() {return conn;}
    +	public void Conn_(Db_conn v) {
    +		conn = v;
    +		stmt_insert = null;
    +		conn.Rls_reg(this);
    +	}	private Db_conn conn;
    +	public void Rls() {
    +		stmt_insert = Db_stmt_.Rls(stmt_insert);
    +	}
    +	public void Insert(String ttl, String media_type, String minor_mime, int size, int w, int h, int bits, int ext_id, String img_timestamp) {
    +		if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds);
    +		stmt_insert.Clear()
    +		.Val_str(ttl).Val_str(media_type).Val_str(minor_mime)
    +		.Val_int(size).Val_int(w).Val_int(h).Val_int(bits).Val_int(ext_id).Val_str(img_timestamp)
    +		.Exec_insert();
    +	}
    +	public Xof_commons_image_itm Select(byte[] ttl) {
    +		Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_img_name).Clear().Val_bry_as_str(ttl).Exec_select__rls_auto();
    +		try {
    +			if (!rdr.Move_next()) return null;
    +			return new Xof_commons_image_itm
    +			( rdr.Read_str(fld_img_name)
    +			, rdr.Read_str(fld_img_media_type)
    +			, rdr.Read_str(fld_img_minor_mime)
    +			, rdr.Read_int(fld_img_size)
    +			, rdr.Read_int(fld_img_width)
    +			, rdr.Read_int(fld_img_height)
    +			, rdr.Read_int(fld_img_bits)
    +			, rdr.Read_int(fld_img_ext_id)
    +			, rdr.Read_str(fld_img_timestamp)
    +			);
    +		}	finally {rdr.Rls();}
    +	}
    +	private static final String tbl_name = "image"; private static final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	private static final    String
    +	  fld_img_name				= flds.Add_str("img_name", 255)			// varbinary(255)
    +	, fld_img_media_type		= flds.Add_str("img_media_type", 255)	// enum('UNKNOWN','BITMAP','DRAWING','AUDIO','VIDEO','MULTIMEDIA','OFFICE','TEXT','EXECUTABLE','ARCHIVE')"
    +	, fld_img_minor_mime		= flds.Add_str("img_minor_mime", 255)	// DEFAULT 'unknown'"
    +	, fld_img_size				= flds.Add_int("img_size")				// int(8) unsigned
    +	, fld_img_width				= flds.Add_int("img_width")				// int(5)
    +	, fld_img_height			= flds.Add_int("img_height")			// int(5)
    +	, fld_img_bits				= flds.Add_short("img_bits")			// int(3)
    +	, fld_img_ext_id			= flds.Add_int("img_ext_id")			// xowa
    +	, fld_img_timestamp			= flds.Add_str("img_timestamp", 255)	// 20140101155749
    +	;
    +	public static Dbmeta_tbl_itm new_meta() {
    +		return Dbmeta_tbl_itm.New(tbl_name, flds.To_fld_ary()
    +		, Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "name", fld_img_name, fld_img_timestamp)
    +		);
    +	} 
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/downloads/Xof_download_wkr.java b/400_xowa/src/gplx/xowa/files/downloads/Xof_download_wkr.java
    index a27517de8..4f46454ea 100644
    --- a/400_xowa/src/gplx/xowa/files/downloads/Xof_download_wkr.java
    +++ b/400_xowa/src/gplx/xowa/files/downloads/Xof_download_wkr.java
    @@ -13,3 +13,9 @@ 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.files.downloads; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.ios.*;
    +public interface Xof_download_wkr {
    +	byte Download(boolean src_is_web, String src, Io_url trg, String prog_fmt_hdr);
    +	IoEngine_xrg_downloadFil Download_xrg();
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/downloads/Xof_download_wkr_io.java b/400_xowa/src/gplx/xowa/files/downloads/Xof_download_wkr_io.java
    index a27517de8..5a56be73c 100644
    --- a/400_xowa/src/gplx/xowa/files/downloads/Xof_download_wkr_io.java
    +++ b/400_xowa/src/gplx/xowa/files/downloads/Xof_download_wkr_io.java
    @@ -13,3 +13,26 @@ 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.files.downloads; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.ios.*;
    +public class Xof_download_wkr_io implements Xof_download_wkr {
    +	IoEngine_xrg_downloadFil xrg = Io_mgr.Instance.DownloadFil_args("", Io_url_.Empty);
    +	public IoEngine_xrg_downloadFil Download_xrg() {return xrg;}
    +	public String Download_err() {return download_err;} private String download_err = "";
    +	public byte Download(boolean src_is_web, String src_str, Io_url trg_url, String prog_fmt_hdr) {
    +		download_err = "";
    +		if (src_is_web) {
    +			xrg.Prog_fmt_hdr_(prog_fmt_hdr);
    +			xrg.Init(src_str, trg_url);
    +			xrg.Exec();
    +			return xrg.Rslt();
    +		}
    +		else {
    +			Io_url src_url = Io_url_.new_fil_(src_str);
    +			if (!Io_mgr.Instance.ExistsFil(src_url)) return IoEngine_xrg_downloadFil.Rslt_fail_file_not_found;
    +			try {Io_mgr.Instance.CopyFil(src_url, trg_url, true);}
    +			catch (Exception exc) {Err_.Noop(exc); return IoEngine_xrg_downloadFil.Rslt_fail_unknown;}
    +			return IoEngine_xrg_downloadFil.Rslt_pass;
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/downloads/Xof_download_wkr_test.java b/400_xowa/src/gplx/xowa/files/downloads/Xof_download_wkr_test.java
    index a27517de8..ef9ad6c97 100644
    --- a/400_xowa/src/gplx/xowa/files/downloads/Xof_download_wkr_test.java
    +++ b/400_xowa/src/gplx/xowa/files/downloads/Xof_download_wkr_test.java
    @@ -13,3 +13,16 @@ 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.files.downloads; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.ios.*;
    +public class Xof_download_wkr_test implements Xof_download_wkr {
    +	public IoEngine_xrg_downloadFil Download_xrg() {return IoEngine_xrg_downloadFil.new_("", Io_url_.Empty).Trg_engine_key_(IoEngine_.MemKey);}
    +	public byte Download(boolean src_is_web, String src_str, Io_url trg_url, String prog_fmt_hdr) {
    +		Io_mgr.Instance.CreateDirIfAbsent(trg_url.OwnerDir());
    +		Io_url src_url = Io_url_.new_fil_(src_str);
    +		if (!Io_mgr.Instance.ExistsFil(src_url)) return IoEngine_xrg_downloadFil.Rslt_fail_file_not_found;
    +		try {Io_mgr.Instance.CopyFil(src_url, trg_url, true);}
    +		catch (Exception exc) {Err_.Noop(exc); return IoEngine_xrg_downloadFil.Rslt_fail_unknown;}
    +		return IoEngine_xrg_downloadFil.Rslt_pass;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/exts/Xof_rule_grp.java b/400_xowa/src/gplx/xowa/files/exts/Xof_rule_grp.java
    index a27517de8..bcfd99a7a 100644
    --- a/400_xowa/src/gplx/xowa/files/exts/Xof_rule_grp.java
    +++ b/400_xowa/src/gplx/xowa/files/exts/Xof_rule_grp.java
    @@ -13,3 +13,28 @@ 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.files.exts; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.primitives.*;
    +public class Xof_rule_grp implements Gfo_invk {
    +	private final    Hash_adp_bry hash = Hash_adp_bry.cs();
    +	public Xof_rule_grp(Xof_rule_mgr owner, byte[] key) {this.owner = owner; this.key = key;}
    +	public Xof_rule_mgr Owner() {return owner;} private final    Xof_rule_mgr owner;
    +	public byte[] Key() {return key;} private final    byte[] key;
    +	public Xof_rule_itm Get_or_null(byte[] ext_bry) {return (Xof_rule_itm)hash.Get_by_bry(ext_bry);}
    +	public Xof_rule_itm Get_or_new(byte[] ext_bry) {
    +		Xof_rule_itm rv = Get_or_null(ext_bry);
    +		if (rv == null) {
    +			Xof_ext ext = Xof_ext_.new_by_ext_(ext_bry);
    +			rv = new Xof_rule_itm(this, ext);
    +			hash.Add(ext_bry, rv);
    +		}
    +		return rv;
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_owner))		return owner;
    +		else if	(ctx.Match(k, Invk_set))		return Get_or_new(Bry_.new_u8(m.ReadStr("v")));
    +		else	return Gfo_invk_.Rv_unhandled;
    +	}	private static final String Invk_owner = "owner", Invk_set = "set";
    +	private static final String Grp_app_default_str = "app_default";
    +	public static byte[] Grp_app_default = Bry_.new_u8(Grp_app_default_str);
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/exts/Xof_rule_itm.java b/400_xowa/src/gplx/xowa/files/exts/Xof_rule_itm.java
    index a27517de8..3834abc2a 100644
    --- a/400_xowa/src/gplx/xowa/files/exts/Xof_rule_itm.java
    +++ b/400_xowa/src/gplx/xowa/files/exts/Xof_rule_itm.java
    @@ -13,3 +13,19 @@ 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.files.exts; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.xowa.apps.*;
    +public class Xof_rule_itm implements Gfo_invk {
    +	public Xof_rule_itm(Xof_rule_grp owner, Xof_ext ext) {this.owner = owner;}	// NOTE: ext currently not used; may want to set as property in future; DATE:2015-02-14
    +	public Xof_rule_grp Owner() {return owner;} private Xof_rule_grp owner;
    +	public long Make_max() {return make_max;} public Xof_rule_itm Make_max_(long v) {make_max = v; return this;} long make_max = Max_wildcard;
    +	public long View_max() {return view_max;} public Xof_rule_itm View_max_(long v) {view_max = v; return this;} long view_max = Max_wildcard;
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_owner))		return owner;
    +		else if (ctx.Match(k, Invk_make_max_))	make_max = gplx.core.ios.Io_size_.Load_int_(m);
    +		else if (ctx.Match(k, Invk_view_max_))	view_max = gplx.core.ios.Io_size_.Load_int_(m);
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}	private static final String Invk_owner = "owner", Invk_make_max_ = "make_max_", Invk_view_max_ = "view_max_";
    +	public static final long Max_wildcard = -1;
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/exts/Xof_rule_mgr.java b/400_xowa/src/gplx/xowa/files/exts/Xof_rule_mgr.java
    index a27517de8..71bc11792 100644
    --- a/400_xowa/src/gplx/xowa/files/exts/Xof_rule_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/exts/Xof_rule_mgr.java
    @@ -13,3 +13,31 @@ 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.files.exts; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.xowa.apps.*;
    +public class Xof_rule_mgr implements Gfo_invk {
    +	private final    Hash_adp_bry hash = Hash_adp_bry.cs();
    +	public Xof_rule_mgr() {
    +		Xof_rule_grp app_default = new Xof_rule_grp(this, Xof_rule_grp.Grp_app_default);
    +		Set_app_default(app_default, Io_mgr.Len_gb, Xof_ext_.Bry__ary);
    +		hash.Add(Xof_rule_grp.Grp_app_default, app_default);
    +	}
    +	private Xof_rule_grp Get_or_null(byte[] key) {return (Xof_rule_grp)hash.Get_by_bry(key);}
    +	public Xof_rule_grp Get_or_new(byte[] key) {
    +		Xof_rule_grp rv = Get_or_null(key);
    +		if (rv == null) {
    +			rv = new Xof_rule_grp(this, key);
    +			hash.Add(key, rv);
    +		}
    +		return rv;
    +	}
    +	private static void Set_app_default(Xof_rule_grp app_default, long make_max, byte[][] keys) {
    +		int len = keys.length;
    +		for (int i = 0; i < len; i++)
    +			app_default.Get_or_new(keys[i]).Make_max_(make_max);
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_set))		return Get_or_new(Bry_.new_u8(m.ReadStr("v")));
    +		else	return Gfo_invk_.Rv_unhandled;
    +	}	private static final String Invk_set = "set";
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/Fsdb_regy_fil_tbl.java b/400_xowa/src/gplx/xowa/files/fsdb/Fsdb_regy_fil_tbl.java
    index a27517de8..05f5dd739 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/Fsdb_regy_fil_tbl.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/Fsdb_regy_fil_tbl.java
    @@ -13,3 +13,64 @@ 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.files.fsdb; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.stores.*;
    +import gplx.dbs.*;
    +class Fsdb_regy_fil_tbl {
    +	public Fsdb_regy_fil_itm Select(String name, boolean is_orig, int w, int thumbtime) {
    +		return null;
    +	}
    +	public static final String Tbl_sql = String_.Concat_lines_nl
    +	( "CREATE TABLE regy_fil"
    +	, "( regy_id           integer       NOT NULL        PRIMARY KEY       AUTOINCREMENT"
    +	, ", wiki_type_id      integer"
    +	, ", wiki_date_id      integer"
    +	, ", fil_name          varchar(255)"
    +	, ", fil_is_orig       tinyint"
    +	, ", fil_w             integer"
    +	, ", fil_h             integer"
    +	, ", fil_thumbtime     integer"
    +	, ", fil_ext           integer"
    +	, ", fil_size          bigint"
    +	, ", bin_db_id         integer"
    +	, ", fil_id            integer"
    +	, ", bin_id            integer"
    +	, ");"
    +	);
    +	public static final String 
    +	  Fld_wiki_type_id = "wiki_type_id", Fld_wiki_date_id = "wiki_date_id", Fld_fil_name = "fil_name", Fld_fil_is_orig = "fil_is_orig"
    +	, Fld_fil_w = "fil_w", Fld_fil_h = "fil_h", Fld_fil_thumbtime = "fil_thumbtime", Fld_fil_ext = "fil_ext", Fld_fil_size = "fil_size"
    +	, Fld_bin_db_id = "bin_db_id", Fld_fil_id = "fil_id", Fld_bin_id = "bin_id"
    +	;
    +	public static final Db_idx_itm
    +		Idx_ttl     		= Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS fsdb_fil_regy__fil       ON fsdb (fil_name, fil_is_orig, fil_w, fil_h, fil_thumbtime, bin_db_id, fil_id, bin_id);")
    +	;
    +}
    +class Fsdb_regy_fil_itm {
    +	public int Wiki_type_id() {return wiki_type_id;} private int wiki_type_id;
    +	public int Wiki_date_id() {return wiki_date_id;} private int wiki_date_id;
    +	public String Fil_name() {return fil_name;} private String fil_name;
    +	public boolean Fil_is_orig() {return fil_is_orig;} private boolean fil_is_orig;
    +	public int Fil_w() {return fil_w;} private int fil_w;
    +	public int Fil_h() {return fil_h;} private int fil_h;
    +	public int Fil_thumbtime() {return fil_thumbtime;} private int fil_thumbtime;
    +	public int Fil_ext() {return fil_ext;} private int fil_ext;
    +	public long Fil_size() {return fil_size;} private long fil_size;
    +	public int Bin_db_id() {return bin_db_id;} private int bin_db_id;
    +	public int Fil_id() {return fil_id;} private int fil_id;
    +	public int Bin_id() {return bin_id;} private int bin_id;
    +	public void Load(DataRdr rdr) {
    +		wiki_date_id = rdr.ReadInt(Fsdb_regy_fil_tbl.Fld_wiki_date_id);
    +		wiki_type_id = rdr.ReadInt(Fsdb_regy_fil_tbl.Fld_wiki_type_id);
    +		fil_name = rdr.ReadStr(Fsdb_regy_fil_tbl.Fld_fil_name);
    +		fil_is_orig = rdr.ReadByte(Fsdb_regy_fil_tbl.Fld_fil_is_orig) != Byte_.Zero;
    +		fil_w = rdr.ReadInt(Fsdb_regy_fil_tbl.Fld_fil_w);
    +		fil_h = rdr.ReadInt(Fsdb_regy_fil_tbl.Fld_fil_h);
    +		fil_thumbtime = rdr.ReadInt(Fsdb_regy_fil_tbl.Fld_fil_thumbtime);
    +		fil_ext = rdr.ReadInt(Fsdb_regy_fil_tbl.Fld_fil_ext);
    +		fil_size = rdr.ReadLong(Fsdb_regy_fil_tbl.Fld_fil_size);
    +		bin_db_id = rdr.ReadInt(Fsdb_regy_fil_tbl.Fld_bin_db_id);
    +		fil_id = rdr.ReadInt(Fsdb_regy_fil_tbl.Fld_fil_id);
    +		bin_id = rdr.ReadInt(Fsdb_regy_fil_tbl.Fld_bin_id);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/Xof_fsdb_mgr.java b/400_xowa/src/gplx/xowa/files/fsdb/Xof_fsdb_mgr.java
    index a27517de8..8f05f08c1 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/Xof_fsdb_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/Xof_fsdb_mgr.java
    @@ -13,3 +13,12 @@ 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.files.fsdb; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +public interface Xof_fsdb_mgr {
    +	String                              Key();
    +	gplx.xowa.files.bins.Xof_bin_mgr    Bin_mgr();
    +	gplx.fsdb.meta.Fsm_mnt_mgr          Mnt_mgr();
    +	void                                Init_by_wiki(Xow_wiki wiki);
    +	void                                Fsdb_search_by_list(List_adp itms, Xow_wiki wiki, Xoa_page page, gplx.xowa.guis.cbks.js.Xog_js_wkr js_wkr);
    +	void                                Rls();
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/Xof_fsdb_mgr__sql.java b/400_xowa/src/gplx/xowa/files/fsdb/Xof_fsdb_mgr__sql.java
    index a27517de8..b42579fd4 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/Xof_fsdb_mgr__sql.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/Xof_fsdb_mgr__sql.java
    @@ -13,3 +13,62 @@ 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.files.fsdb; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.primitives.*;
    +import gplx.core.ios.*;
    +import gplx.dbs.*; import gplx.xowa.wikis.data.*;
    +import gplx.fsdb.*; import gplx.fsdb.meta.*;	
    +import gplx.xowa.files.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.imgs.*; import gplx.xowa.files.origs.*; import gplx.xowa.files.bins.*; import gplx.xowa.files.caches.*; import gplx.xowa.guis.cbks.js.*;
    +public class Xof_fsdb_mgr__sql implements Xof_fsdb_mgr, Gfo_invk {
    +	private boolean init = false; private boolean fsdb_enabled = false;
    +	private Xow_repo_mgr repo_mgr; private Xof_url_bldr url_bldr; private final    Xof_img_size img_size = new Xof_img_size();
    +	public String Key() {return "fsdb.sql";}
    +	public Xof_bin_mgr Bin_mgr() {return bin_mgr;} private Xof_bin_mgr bin_mgr;
    +	public Fsm_mnt_mgr Mnt_mgr() {return mnt_mgr;} private Fsm_mnt_mgr mnt_mgr = new Fsm_mnt_mgr();
    +	public void Init_by_wiki(Xow_wiki wiki) {
    +		if (init) return;
    +		try {
    +			init = true;
    +//				if (wiki.File__fsdb_mode().Tid_v0()) return;
    +			this.url_bldr = Xof_url_bldr.new_v2();
    +			this.repo_mgr = wiki.File__repo_mgr();
    +			Fsdb_db_mgr fsdb_core = wiki.File__fsdb_core();
    +			// Fsdb_db_mgr fsdb_core = Fsdb_db_mgr_.new_detect(wiki, wiki.Fsys_mgr().Root_dir(), wiki.Fsys_mgr().File_dir());
    +			if (fsdb_core == null) return;
    +			fsdb_enabled = true;
    +			mnt_mgr.Ctor_by_load(fsdb_core);
    +			this.bin_mgr = new Xof_bin_mgr(mnt_mgr, repo_mgr, wiki.App().File__img_mgr().Wkr_resize_img(), wiki.App().Wmf_mgr().Download_wkr().Download_xrg().Download_fmt());
    +			bin_mgr.Wkrs__add(Xof_bin_wkr__fsdb_sql.new_(mnt_mgr));
    +			bin_mgr.Wkrs__add(Xof_bin_wkr__http_wmf.new_(wiki));
    +		}	catch (Exception e) {throw Err_.new_exc(e, "xo", "failed to initialize fsdb_mgr}", "wiki", wiki.Domain_str());}
    +	}
    +	public void Fsdb_search_by_list(List_adp itms, Xow_wiki cur_wiki, Xoa_page page, Xog_js_wkr js_wkr) {
    +		if (!fsdb_enabled) return;
    +		int len = itms.Count();
    +		Gfo_usr_dlg usr_dlg = Gfo_usr_dlg_.Instance;
    +		Xow_wiki wiki = page.Commons_mgr().Source_wiki_or(cur_wiki);
    +		Xou_cache_mgr cache_mgr = wiki.App().User().User_db_mgr().Cache_mgr();
    +		for (int i = 0; i < len; i++) {
    +			if (usr_dlg.Canceled()) return;
    +			Xof_fsdb_itm fsdb = (Xof_fsdb_itm)itms.Get_at(i);
    +			if (fsdb.Hdump_mode() == Xof_fsdb_itm.Hdump_mode__null) {
    +				Xof_orig_itm orig = wiki.File__orig_mgr().Find_by_ttl_or_null(fsdb.Lnki_ttl(), i, len);
    +				if (orig != Xof_orig_itm.Null) { // orig exists;
    +					gplx.xowa.files.repos.Xof_repo_itm repo = wiki.File__repo_mgr().Get_trg_by_id_or_null(orig.Repo(), fsdb.Lnki_ttl(), Bry_.Empty);
    +					if (repo == null) continue;
    +					fsdb.Init_at_orig(orig.Repo(), repo.Wiki_domain(), orig.Ttl(), orig.Ext(), orig.W(), orig.H(), orig.Redirect());
    +				}
    +			}
    +			fsdb.Init_at_xfer(i, len);
    +			Xof_file_wkr.Show_img(fsdb, usr_dlg, wiki.File__bin_mgr(), wiki.File__mnt_mgr(), cache_mgr, wiki.File__repo_mgr(), js_wkr, img_size, url_bldr, page);
    +		}
    +	}
    +	public void Rls() {
    +		if (bin_mgr != null) bin_mgr.Rls();
    +		init = false;
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_mnt_mgr))	return mnt_mgr;
    +		else	return Gfo_invk_.Rv_unhandled;
    +	}	private static final String Invk_mnt_mgr = "mnt_mgr";
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/Xof_fsdb_mgr_cfg.java b/400_xowa/src/gplx/xowa/files/fsdb/Xof_fsdb_mgr_cfg.java
    index a27517de8..a76954739 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/Xof_fsdb_mgr_cfg.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/Xof_fsdb_mgr_cfg.java
    @@ -13,3 +13,13 @@ 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.files.fsdb; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +public class Xof_fsdb_mgr_cfg {
    +	public static final String 
    +	  Grp_xowa						= "xowa"
    +	, Key_gallery_fix_defaults		= "gallery.fix_defaults"
    +	, Key_gallery_packed			= "gallery.packed"
    +	, Key_upright_use_thumb_w		= "file.upright_ignores_lnki_w"
    +	, Key_upright_fix_default		= "file.upright_fix_default"
    +	;
    +}
    \ No newline at end of file
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Fs_root_core.java b/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Fs_root_core.java
    index a27517de8..a3a9aa6b8 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Fs_root_core.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Fs_root_core.java
    @@ -13,3 +13,62 @@ 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.files.fsdb.fs_roots; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import gplx.xowa.guis.cbks.js.*;
    +public class Fs_root_core implements Xof_fsdb_mgr, Gfo_invk {	// reads images from file-system dir
    +	private Xowe_wiki wiki;
    +	private final    Fs_root_mgr mgr;
    +	public Fs_root_core(Xowe_wiki wiki) {
    +		this.Init_by_wiki(wiki);
    +		this.mgr = new Fs_root_mgr(wiki);
    +	}
    +	public String Key() {return Fsdb_mgr_key;}
    +	public gplx.xowa.files.bins.Xof_bin_mgr Bin_mgr() {throw Err_.new_unimplemented();}
    +	public gplx.fsdb.meta.Fsm_mnt_mgr Mnt_mgr()       {return null;}
    +	public void Init_by_wiki(Xow_wiki wiki) {
    +		this.wiki = (Xowe_wiki)wiki;
    +	}
    +	public Io_url Get_orig_url_or_null(byte[] lnki_ttl) {
    +		Orig_fil_row rv = mgr.Wkr().Get_by_ttl(lnki_ttl);
    +		return rv == null ? null : rv.Url();
    +	}
    +	public void Orig_dir_(Io_url v) {mgr.Orig_dir_(v);}
    +	public void Fsdb_search_by_list(List_adp itms, Xow_wiki wiki, Xoa_page page, Xog_js_wkr js_wkr) {
    +		int itms_len = itms.Count();
    +
    +		// NOTE: do not cache in /xowa/file b/c files will already exist in /xowa/wiki/wiki_name/file/thumb
    +		gplx.xowa.files.caches.Xou_cache_mgr cache_mgr = wiki.App().User().User_db_mgr().Cache_mgr();
    +		for (int i = 0; i < itms_len; i++) {
    +			Xof_fsdb_itm itm = (Xof_fsdb_itm)itms.Get_at(i);
    +			if (mgr.Find_file(itm)) {
    +				Js_img_mgr.Update_img(page, js_wkr, itm);
    +				cache_mgr.Update(itm);
    +			}
    +		}
    +		cache_mgr.Db_save();
    +	}
    +	public void Rls() {}
    +
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_root_dir_))		mgr.Root_dir_(To_url(m.ReadBry("v")));
    +		else if	(ctx.Match(k, Invk_orig_dir_))		mgr.Orig_dir_(To_url(m.ReadBry("v")));
    +		else if	(ctx.Match(k, Invk_thumb_dir_))		{}
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	private static final String Invk_root_dir_ = "root_dir_", Invk_orig_dir_ = "orig_dir_", Invk_thumb_dir_ = "thumb_dir_";
    +	private Io_url To_url(byte[] v) {
    +		if (gplx.core.envs.Op_sys.Cur().Tid_is_wnt())
    +			v = Bry_.Replace(v, Byte_ascii.Slash, Byte_ascii.Backslash);
    +		return gplx.core.brys.fmtrs.Bry_fmtr_eval_mgr_.Eval_url(wiki.Appe().Url_cmd_eval(), v);
    +	}
    +	public static Fs_root_core Set_fsdb_mgr(Xow_file_mgr file_mgr, Xowe_wiki wiki) {
    +		Fs_root_core rv = new Fs_root_core(wiki);
    +		file_mgr.Fsdb_mgr_(rv);
    +
    +		file_mgr.Orig_mgr().Wkrs__del(gplx.xowa.files.origs.Xof_orig_wkr_.Tid_wmf_api);
    +		file_mgr.Orig_mgr().Wkrs__set(new Xof_orig_wkr__fs_root(rv.mgr.Wkr()));
    +		return rv;
    +	}
    +	public static final String Fsdb_mgr_key = "fs.dir";
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Fs_root_mgr.java b/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Fs_root_mgr.java
    index a27517de8..d0a8ce03a 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Fs_root_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Fs_root_mgr.java
    @@ -13,3 +13,69 @@ 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.files.fsdb.fs_roots; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import gplx.core.primitives.*; import gplx.dbs.*; import gplx.dbs.cfgs.*;
    +import gplx.xowa.files.*; import gplx.xowa.files.repos.*; import gplx.fsdb.meta.*;	
    +class Fs_root_mgr {
    +	private final    Xowe_wiki wiki;
    +	private final    Fs_root_wkr wkr = new Fs_root_wkr();
    +	private final    Xof_url_bldr url_bldr = Xof_url_bldr.new_v2();
    +	private final    Xof_img_size img_size = new Xof_img_size();
    +	private final    String_obj_ref tmp_resize_result = String_obj_ref.null_();
    +	private Xof_repo_itm repo;
    +	private Io_url orig_dir;
    +	public Fs_root_mgr(Xowe_wiki wiki) {
    +		this.wiki = wiki;
    +	}
    +	public Fs_root_wkr Wkr() {return wkr;}
    +	public boolean Find_file(Xof_fsdb_itm fsdb_itm) {
    +		// get orig; exit if not found in dir
    +		byte[] orig_ttl = fsdb_itm.Orig_ttl();
    +		Orig_fil_row orig_itm = wkr.Get_by_ttl(orig_ttl);
    +		if (orig_itm == Orig_fil_row.Null) return false;
    +
    +		// update fsdb.orig metadata
    +		Xof_ext orig_ext = Xof_ext_.new_by_id_(orig_itm.Ext_id());
    +		fsdb_itm.Init_at_orig(Xof_repo_tid_.Tid__local, wiki.Domain_bry(), orig_ttl, orig_ext, orig_itm.W(), orig_itm.H(), null);			
    +
    +		// gen cache_orig_url
    +		if (repo == null) repo = wiki.File__repo_mgr().Repos_get_by_wiki(wiki.Domain_bry()).Trg(); // assert repo
    +		Io_url cache_orig_url = url_bldr.To_url_trg(repo, fsdb_itm, Bool_.Y);
    +		fsdb_itm.Html_orig_url_(cache_orig_url);
    +
    +		// update html_w, html_h, html_orig_url, html_view_url
    +		Io_url actl_orig_url = orig_itm.Url();
    +		fsdb_itm.Init_at_html(Xof_exec_tid.Tid_wiki_page, img_size, repo, url_bldr);
    +
    +		// if cache_file doesn't exist, either copy file 
    +		if (fsdb_itm.File_is_orig()) {
    +			// copy orig to cache
    +			if (!Io_mgr.Instance.ExistsFil(cache_orig_url)) {
    +				Io_mgr.Instance.CopyFil(actl_orig_url, cache_orig_url, false);
    +				fsdb_itm.File_size_(Io_mgr.Instance.QueryFil(actl_orig_url).Size()); // set size for file_cache, else clear_cache won't delete items with 0 size
    +			}
    +		}
    +		else {
    +			// make thumb by imagemagick
    +			Io_url cache_thumb_url = url_bldr.To_url_trg(repo, fsdb_itm, Bool_.N);
    +			if (!Io_mgr.Instance.ExistsFil(cache_thumb_url)) {
    +				if (!wiki.Appe().File_mgr().Img_mgr().Wkr_resize_img().Resize_exec(actl_orig_url, cache_thumb_url, fsdb_itm.Html_w(), fsdb_itm.Html_h(), fsdb_itm.Orig_ext().Id(), tmp_resize_result))
    +					return false;
    +				fsdb_itm.File_size_(Io_mgr.Instance.QueryFil(cache_thumb_url).Size()); // set size for file_cache, else clear_cache won't delete items with 0 size
    +			}
    +		}
    +		return true;
    +	}
    +	public void Root_dir_(Io_url v) {
    +		Io_url root_dir = v;
    +		orig_dir = root_dir.GenSubDir("orig");
    +		orig_dir_mgr_init(orig_dir);
    +	}
    +	public void Orig_dir_(Io_url v) {
    +		orig_dir = v;
    +		orig_dir_mgr_init(orig_dir);
    +	}
    +	private void orig_dir_mgr_init(Io_url orig_dir) {
    +		wkr.Init(wiki.Appe().File_mgr().Img_mgr().Wkr_query_img_size(), orig_dir);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Fs_root_wkr.java b/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Fs_root_wkr.java
    index a27517de8..db81466cf 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Fs_root_wkr.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Fs_root_wkr.java
    @@ -13,3 +13,136 @@ 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.files.fsdb.fs_roots; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import gplx.core.primitives.*;
    +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.gfui.*;
    +import gplx.fsdb.meta.*; import gplx.xowa.files.imgs.*;
    +class Fs_root_wkr {
    +	private Xof_img_wkr_query_img_size img_size_wkr;
    +	private Io_url orig_dir; private boolean recurse = true;
    +	private Orig_fil_mgr cache = new Orig_fil_mgr(), fs_fil_mgr;
    +	private Db_conn conn; private Db_cfg_tbl cfg_tbl;
    +	private Orig_fil_tbl orig_tbl;
    +	private int fil_id_next = 0;
    +	private long scan_time_prv = gplx.core.envs.System_.Ticks();
    +	public Orig_fil_tbl Orig_tbl() {return orig_tbl;}
    +	public void Init(Xof_img_wkr_query_img_size img_size_wkr, Io_url orig_dir) {
    +		this.img_size_wkr = img_size_wkr;
    +		this.orig_dir = orig_dir;
    +	}
    +	public Orig_fil_row Get_by_ttl(byte[] lnki_ttl) {
    +		Orig_fil_row rv = (Orig_fil_row)cache.Get_by_ttl(lnki_ttl);
    +		if (rv == null) {
    +			// not in mem; get from db
    +			rv = Get_from_db(lnki_ttl);
    +			// not in db; get from fsys
    +			if (rv == null) {
    +				rv = Get_from_fs(lnki_ttl);
    +				if (rv == null) {
    +					// HACK: if failed and not much time has passed, try rescanning the entire fs again; need to change to filesystem watcher
    +					if (gplx.core.envs.System_.Ticks__elapsed_in_sec(scan_time_prv) > 2) {	// NOTE: 2 seconds chosen just to make sure this doesn't fire multiple times during one page load
    +						Gfo_usr_dlg_.Instance.Warn_many("", "", "fs.dir:file not found; title=~{0}", lnki_ttl);
    +						fs_fil_mgr = Init_fs_fil_mgr();
    +						rv = Get_from_fs(lnki_ttl);
    +						scan_time_prv = gplx.core.envs.System_.Ticks();
    +					}
    +					if (rv == null)
    +						return Orig_fil_row.Null;
    +				}
    +			}
    +			cache.Add(rv);
    +		}
    +		return rv;
    +	}
    +	private Orig_fil_row Get_from_db(byte[] lnki_ttl) {
    +		if (conn == null) conn = Init_orig_db();
    +		return orig_tbl.Select_itm_or_null(orig_dir, lnki_ttl);
    +	}
    +	private Orig_fil_row Get_from_fs(byte[] lnki_ttl) {
    +		if (fs_fil_mgr == null) fs_fil_mgr = Init_fs_fil_mgr();
    +		Orig_fil_row rv = fs_fil_mgr.Get_by_ttl(lnki_ttl);
    +		if (rv == null) return Orig_fil_row.Null;		// not in fs
    +		SizeAdp img_size = SizeAdp_.Zero;
    +		if (Xof_ext_.Id_is_image(rv.Ext_id()))
    +			img_size = img_size_wkr.Exec(rv.Url());
    +		rv.Init_by_fs(++fil_id_next, img_size.Width(), img_size.Height());
    +		cfg_tbl.Update_int(Cfg_grp_root_dir, Cfg_key_fil_id_next, fil_id_next);	
    +		String fil_orig_dir = "~{orig_dir}" + rv.Url().OwnerDir().GenRelUrl_orEmpty(orig_dir);	// converts "/xowa/wiki/custom_wiki/file/orig/7/70/A.png" -> "~{orig_dir}7/70/"
    +		orig_tbl.Insert(rv, fil_orig_dir);
    +		return rv;
    +	}
    +	private Orig_fil_mgr Init_fs_fil_mgr() {	// NOTE: need to read entire dir, b/c ttl may be "A.png", but won't know which subdir
    +		Orig_fil_mgr rv = new Orig_fil_mgr();
    +		Io_url orig_changes_log = orig_dir.GenSubFil("xowa.orig.changes.log");
    +
    +		// loop over all fils in orig_dir
    +		Io_url[] fils = Io_mgr.Instance.QueryDir_args(orig_dir).Recur_(recurse).ExecAsUrlAry();
    +		int fils_len = fils.length;
    +		for (int i = 0; i < fils_len; i++) {
    +			Io_url fil = fils[i];
    +			byte[] fil_name_bry = Bry_.new_u8(fil.NameAndExt());
    +
    +			String orig_change_type = null;
    +			// if url has space, replace it with underscore
    +			if (Bry_.Has(fil_name_bry, Byte_ascii.Space)) {
    +				fil_name_bry = Bry_.Replace(fil_name_bry, Byte_ascii.Space, Byte_ascii.Underline);
    +				orig_change_type = "space_to_underscore";
    +			}
    +
    +			// TOMBSTONE: code below had unit_test, but not sure if needed; file's title should be title-cased, but it's possible to be lower-case for "File:" namespaces with case_match; DATE:2017-02-01
    +			// if url's first char is lowercase, uppercase it;
    +			// byte b_0 = fil_name_bry[0];
    +			// if (b_0 >= Byte_ascii.Ltr_a && b_0 <= Byte_ascii.Ltr_z) {
    +			//	 fil_name_bry = Bry_.Ucase__1st(fil_name_bry);
    +			//	 orig_change_type = "ucase_1st";
    +			// }
    +
    +			// if changed above, rename it and log it
    +			if (orig_change_type != null) {
    +				Io_url new_url = fil.GenNewNameAndExt(String_.new_u8(fil_name_bry));
    +				Io_mgr.Instance.MoveFil_args(fil, new_url, true).Exec();
    +				Io_mgr.Instance.AppendFilStr(orig_changes_log, orig_change_type + "|" + fil.Raw() + "\n");
    +				fil = new_url;
    +			}
    +
    +			// if file already seen, ignore it
    +			Orig_fil_row fil_itm = rv.Get_by_ttl(fil_name_bry);
    +			if (fil_itm != Orig_fil_row.Null) {
    +				Gfo_usr_dlg_.Instance.Warn_many("", "", "file already exists: cur=~{0} new=~{1}", fil_itm.Url().Raw(), fil.Raw());
    +				continue;
    +			}
    +
    +			// add it to cache
    +			Xof_ext ext = Xof_ext_.new_by_ttl_(fil_name_bry);
    +			fil_itm = Orig_fil_row.New_by_fs(fil, fil_name_bry, ext.Id());
    +			rv.Add(fil_itm);
    +		}
    +		return rv;
    +	}
    +	private Db_conn Init_orig_db() {
    +		Io_url orig_db = orig_dir.GenSubFil("^orig_regy.sqlite3");
    +		boolean created = false; boolean schema_is_1 = Bool_.Y;
    +		Db_conn conn = Db_conn_bldr.Instance.Get(orig_db);
    +		if (conn == null) {
    +			conn = Db_conn_bldr.Instance.New(orig_db);
    +			created = true;
    +		}
    +		cfg_tbl = new Db_cfg_tbl(conn, schema_is_1 ? "fsdb_cfg" : gplx.xowa.wikis.data.Xowd_cfg_tbl_.Tbl_name);
    +		orig_tbl = new Orig_fil_tbl(conn, schema_is_1);
    +		if (created) {
    +			cfg_tbl.Create_tbl();
    +			cfg_tbl.Insert_int(Cfg_grp_root_dir, Cfg_key_fil_id_next, fil_id_next);
    +			orig_tbl.Create_tbl();
    +		}
    +		else {
    +			fil_id_next = cfg_tbl.Select_int(Cfg_grp_root_dir, Cfg_key_fil_id_next);
    +		}
    +		return conn;
    +	}
    +	public void Rls() {
    +		cfg_tbl.Rls();
    +		orig_tbl.Rls();
    +	}
    +	private static final String Cfg_grp_root_dir = "xowa.root_dir", Cfg_key_fil_id_next = "fil_id_next";
    +	public static final String Url_orig_dir = "~{orig_dir}";
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Fs_root_wkr_tst.java b/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Fs_root_wkr_tst.java
    index a27517de8..c7b9bc6c1 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Fs_root_wkr_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Fs_root_wkr_tst.java
    @@ -13,3 +13,70 @@ 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.files.fsdb.fs_roots; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import org.junit.*;
    +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.xowa.files.imgs.*;
    +import gplx.fsdb.meta.*;
    +public class Fs_root_wkr_tst {
    +	@Before public void init() {fxt.Reset();} private Fs_root_wkr_fxt fxt = new Fs_root_wkr_fxt();
    +	@Test   public void Basic() {
    +		fxt.Init_fs("mem/dir/7/70/A.png", 200, 100);
    +		fxt.Test_get("A.png", fxt.itm_().Url_("mem/dir/7/70/A.png").Size_(200, 100));
    +		fxt.Test_db("A.png", fxt.itm_().Init(1, "mem/dir/7/70/A.png", 200, 100));
    +	}
    +	@Test   public void Recurse() {
    +		fxt.Init_fs("mem/dir/A/A1.png", 200, 100);
    +		fxt.Test_get("A1.png", fxt.itm_().Url_("mem/dir/A/A1.png").Size_(200, 100));
    +	}
    +}
    +class Fs_root_wkr_fxt {
    +	private Fs_root_wkr root_dir = new Fs_root_wkr();
    +	private Io_url url;
    +	public void Reset() {
    +		Db_conn_bldr.Instance.Reg_default_mem();
    +		Io_mgr.Instance.InitEngine_mem();
    +		url = Io_url_.mem_dir_("mem/dir/");
    +		root_dir = new Fs_root_wkr();
    +		Xof_img_wkr_query_img_size img_size_wkr = new Xof_img_wkr_query_img_size_test();
    +		root_dir.Init(img_size_wkr, url);
    +	}
    +	public Orig_fil_mok itm_() {return new Orig_fil_mok();}
    +	public void Init_fs(String url, int w, int h) {Save_img(url, w, h);}
    +	public void Test_get(String name, Orig_fil_mok expd) {
    +		Orig_fil_row actl = root_dir.Get_by_ttl(Bry_.new_u8(name));
    +		expd.Test(actl);
    +	}
    +	public void Test_db(String ttl, Orig_fil_mok expd) {
    +		Orig_fil_row actl = root_dir.Orig_tbl().Select_itm_or_null(url, Bry_.new_u8(ttl));
    +		expd.Test(actl);
    +	}
    +	public static void Save_img(String url, int w, int h) {
    +		gplx.gfui.SizeAdp img_size = gplx.gfui.SizeAdp_.new_(w, h);
    +		Io_mgr.Instance.SaveFilStr(url, img_size.To_str());
    +	}
    +}
    +class Orig_fil_mok {
    +	private int uid = -1;
    +	private int ext_id = -1;
    +	private String name = null;
    +	private int w = -1;
    +	private int h = -1;
    +	public Orig_fil_mok Url_(String v) {url = v; return this;} private String url = null;
    +	public Orig_fil_mok Size_(int w, int h) {this.w = w; this.h = h; return this;}
    +	public Orig_fil_mok Init(int uid, String url, int w, int h) {
    +		this.uid = uid;
    +		this.url = url; this.w = w; this.h = h;
    +		this.name = Io_url_.new_any_(url).NameAndExt();
    +		this.ext_id = Xof_ext_.new_by_ttl_(Bry_.new_u8(name)).Id();
    +		return this;
    +	}
    +	public void Test(Orig_fil_row actl) {
    +		if (actl == null) Tfds.Fail("actl itm is null");
    +		if (w != -1)			Tfds.Eq(w, actl.W());
    +		if (h != -1)			Tfds.Eq(h, actl.H());
    +		if (url != null)		Tfds.Eq(url, actl.Url().Raw());
    +		if (uid != -1)			Tfds.Eq(uid, actl.Uid());
    +		if (ext_id != -1)		Tfds.Eq(uid, actl.Ext_id());
    +		if (name != null)		Tfds.Eq(name, String_.new_u8(actl.Name()));
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Orig_fil_mgr.java b/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Orig_fil_mgr.java
    index a27517de8..79d71ed92 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Orig_fil_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Orig_fil_mgr.java
    @@ -13,3 +13,10 @@ 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.files.fsdb.fs_roots; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +class Orig_fil_mgr {
    +	private final    Ordered_hash hash = Ordered_hash_.New_bry();
    +	public boolean Has(byte[] lnki_ttl) {return hash.Has(lnki_ttl);}
    +	public Orig_fil_row Get_by_ttl(byte[] lnki_ttl) {return (Orig_fil_row)hash.Get_by(lnki_ttl);}
    +	public void Add(Orig_fil_row fil) {hash.Add(fil.Name(), fil);}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Orig_fil_row.java b/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Orig_fil_row.java
    index a27517de8..ceb94a6ec 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Orig_fil_row.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Orig_fil_row.java
    @@ -13,3 +13,33 @@ 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.files.fsdb.fs_roots; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +class Orig_fil_row {
    +	Orig_fil_row(int uid, byte[] name, int ext_id, int w, int h, Io_url url) {
    +		this.uid = uid;
    +		this.name = name;
    +		this.ext_id = ext_id;
    +		this.w = w;
    +		this.h = h;
    +		this.url = url;
    +	}
    +	public int            Uid()        {return uid;}      private int uid;
    +	public byte[]         Name()       {return name;}     private final    byte[] name;
    +	public int            Ext_id()     {return ext_id;}   private final    int ext_id;
    +	public int            W()          {return w;}        private int w;
    +	public int            H()          {return h;}        private int h;
    +	public Io_url         Url()        {return url;}      private final    Io_url url;
    +
    +	public Orig_fil_row Init_by_fs(int uid, int w, int h) {
    +		this.uid = uid; this.w = w; this.h = h;
    +		return this;
    +	}
    +
    +	public static final    Orig_fil_row Null = null;
    +	public static Orig_fil_row New_by_db(int uid, byte[] name, int ext_id, int w, int h, Io_url dir) {
    +		return new Orig_fil_row(uid, name, ext_id, w, h, dir.GenSubFil(String_.new_u8(name)));
    +	}
    +	public static Orig_fil_row New_by_fs(Io_url url, byte[] name, int ext_id) {
    +		return new Orig_fil_row(0, name, ext_id, 0, 0, url);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Orig_fil_tbl.java b/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Orig_fil_tbl.java
    index a27517de8..d920f1693 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Orig_fil_tbl.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Orig_fil_tbl.java
    @@ -13,3 +13,65 @@ 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.files.fsdb.fs_roots; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import gplx.dbs.*;
    +class Orig_fil_tbl implements Rls_able {
    +	private final    Db_conn conn;
    +	private final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	private String tbl_name = "orig_fil";
    +	private String fld_uid, fld_name, fld_ext_id, fld_w, fld_h, fld_dir_url;		
    +	private Db_stmt stmt_insert, stmt_select;
    +	public Orig_fil_tbl(Db_conn conn, boolean schema_is_1) {
    +		this.conn = conn; conn.Rls_reg(this);
    +		String fld_prefix = schema_is_1 ? "fil_" : "";
    +		fld_uid				= flds.Add_int(fld_prefix + "uid");
    +		fld_name			= flds.Add_str(fld_prefix + "name", 1024);
    +		fld_ext_id			= flds.Add_int(fld_prefix + "ext_id");
    +		fld_w				= flds.Add_int(fld_prefix + "w");
    +		fld_h				= flds.Add_int(fld_prefix + "h");
    +		fld_dir_url			= flds.Add_str(fld_prefix + "dir_url", 1024);	// NOTE: don't put dir in separate table; note that entire root_dir_wkr is not built to scale due to need for recursively loading all files
    +	}
    +	public void Create_tbl() {
    +		conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds
    +		, Dbmeta_idx_itm.new_unique_by_tbl(tbl_name, "main", fld_name)
    +		));
    +	}
    +	public void Rls() {
    +		stmt_insert = Db_stmt_.Rls(stmt_insert);
    +		stmt_select = Db_stmt_.Rls(stmt_select);
    +	}
    +	public Orig_fil_row Select_itm_or_null(Io_url dir, byte[] ttl) {
    +		if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_name);
    +		Db_rdr rdr = stmt_select.Clear().Crt_bry_as_str(fld_name, ttl).Exec_select__rls_manual();
    +		try {return rdr.Move_next() ? Load_itm(rdr, dir) : Orig_fil_row.Null;}
    +		finally {rdr.Rls();}
    +	}
    +	private Orig_fil_row Load_itm(Db_rdr rdr, Io_url orig_root) {
    +		String name = rdr.Read_str(fld_name);
    +		String fil_orig_dir = rdr.Read_str(fld_dir_url);
    +		Io_url dir = String_.Has_at_bgn(fil_orig_dir, Fs_root_wkr.Url_orig_dir)
    +			// swap out orig_dir; EX: "~{orig_dir}7/70/" -> "/xowa/wiki/custom_wiki/file/orig/7/70/"
    +			? Io_url_.new_dir_(orig_root.Raw() + String_.Mid(fil_orig_dir, String_.Len(Fs_root_wkr.Url_orig_dir)))
    +			// load literally;    EX: "/xowa/wiki/custom_wiki/file/orig/7/70/"
    +			: Io_url_.new_dir_(fil_orig_dir);
    +		return Orig_fil_row.New_by_db
    +		( rdr.Read_int(fld_uid)
    +		, Bry_.new_u8(name)
    +		, rdr.Read_int(fld_ext_id)
    +		, rdr.Read_int(fld_w)
    +		, rdr.Read_int(fld_h)
    +		, dir
    +		);
    +	}
    +	public void Insert(Orig_fil_row row, String dir) {
    +		if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds);
    +		stmt_insert.Clear()
    +		.Val_int       (fld_uid        , row.Uid())
    +		.Val_bry_as_str(fld_name       , row.Name())
    +		.Val_int       (fld_ext_id     , row.Ext_id())
    +		.Val_int       (fld_w          , row.W())
    +		.Val_int       (fld_h          , row.H())
    +		.Val_str       (fld_dir_url    , dir)
    +		.Exec_insert();
    +	}	
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Xof_orig_wkr__fs_root.java b/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Xof_orig_wkr__fs_root.java
    index a27517de8..ba947a502 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Xof_orig_wkr__fs_root.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/fs_roots/Xof_orig_wkr__fs_root.java
    @@ -13,3 +13,29 @@ 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.files.fsdb.fs_roots; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import gplx.dbs.*;
    +import gplx.xowa.files.origs.*;
    +class Xof_orig_wkr__fs_root implements Xof_orig_wkr {
    +	private final    Fs_root_wkr wkr;
    +	public Xof_orig_wkr__fs_root(Fs_root_wkr wkr) {this.wkr = wkr;}
    +	public byte				Tid() {return Xof_orig_wkr_.Tid_fs_root;}
    +	public void				Find_by_list(Ordered_hash rv, List_adp itms) {Xof_orig_wkr_.Find_by_list(this, rv, itms);}
    +	public Xof_orig_itm		Find_as_itm(byte[] ttl, int list_idx, int list_len) {
    +		Orig_fil_row orig_row = wkr.Get_by_ttl(ttl);
    +		if (orig_row == Orig_fil_row.Null) return Xof_orig_itm.Null;
    +
    +		Xof_orig_itm rv = new Xof_orig_itm
    +		( gplx.xowa.files.repos.Xof_repo_tid_.Tid__local
    +		, ttl
    +		, Xof_ext_.new_by_ttl_(ttl).Id()
    +		, orig_row.W()
    +		, orig_row.H()
    +		, null
    +		);
    +		return rv;
    +	}
    +	public boolean				Add_orig(byte repo, byte[] page, int ext_id, int w, int h, byte[] redirect) {return false;}
    +	public void				Db_txn_save() {}
    +	public void				Db_rls() {}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__bmp_tst.java b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__bmp_tst.java
    index a27517de8..fb51c157e 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__bmp_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__bmp_tst.java
    @@ -13,3 +13,19 @@ 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.files.fsdb.tsts; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import org.junit.*;
    +public class Xof_file_ext__bmp_tst {
    +	@Before public void init() {fxt.Reset();} private final Xof_file_fxt fxt = new Xof_file_fxt();
    +	@After public void term() {fxt.Rls();}
    +	@Test   public void Make_orig() {
    +		fxt.Init__orig_w_fsdb__commons_orig("A.bmp", 440, 400);
    +		fxt.Exec_get(Xof_exec_arg.new_orig("A.bmp").Rslt_orig_exists_y().Rslt_file_exists_y().Rslt_file_resized_y());
    +		fxt.Test_fsys("mem/root/common/thumb/7/0/A.bmp/440px.png", "440,400");
    +	}
    +	@Test   public void Make_thumb() {
    +		fxt.Init__orig_w_fsdb__commons_orig("A.bmp", 440, 400);
    +		fxt.Exec_get(Xof_exec_arg.new_thumb("A.bmp").Rslt_orig_exists_y().Rslt_file_exists_y().Rslt_file_resized_y());
    +		fxt.Test_fsys("mem/root/common/thumb/7/0/A.bmp/220px.png", "220,200");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__flac_tst.java b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__flac_tst.java
    index a27517de8..0c02bc8ee 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__flac_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__flac_tst.java
    @@ -13,3 +13,21 @@ 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.files.fsdb.tsts; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import org.junit.*;
    +public class Xof_file_ext__flac_tst {
    +	@Before public void init() {fxt.Reset();} private final Xof_file_fxt fxt = new Xof_file_fxt();
    +	@After public void term() {fxt.Rls();}
    +	@Test   public void Orig_page() {	// .flac is on page [[File:A.flac]]
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm_file("A.flac"));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_file("A.flac"));
    +		fxt.Exec_get(Xof_exec_arg.new_orig("A.flac").Rslt_orig_exists_y().Rslt_file_exists_n());	// check orig (since orig may redirect) but do not get file; (since file is not "viewable" immediately); DATE:2015-02-15
    +		fxt.Test_fsys_exists_n("mem/root/common/orig/8/b/A.flac");
    +	}
    +	@Test   public void Orig_app() {	// .flac is clicked; get file
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm_file("A.flac"));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_file("A.flac"));
    +		fxt.Exec_get(Xof_exec_arg.new_orig("A.flac").Exec_tid_(Xof_exec_tid.Tid_viewer_app).Rslt_orig_exists_y().Rslt_file_exists_y());
    +		fxt.Test_fsys_exists_y("mem/root/common/orig/8/b/A.flac");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__oga_tst.java b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__oga_tst.java
    index a27517de8..2b61cfb33 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__oga_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__oga_tst.java
    @@ -13,3 +13,21 @@ 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.files.fsdb.tsts; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import org.junit.*;
    +public class Xof_file_ext__oga_tst {
    +	@Before public void init() {fxt.Reset();} private final Xof_file_fxt fxt = new Xof_file_fxt();
    +	@After public void term() {fxt.Rls();}
    +	@Test   public void Orig_page() {	// .oga is on page [[File:A.oga]]; check orig (since orig may redirect) but do not get file; (since file is not "viewable" immediately)
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm_file("A.oga"));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_file("A.oga"));
    +		fxt.Exec_get(Xof_exec_arg.new_orig("A.oga").Rslt_orig_exists_y().Rslt_file_exists_n());
    +		fxt.Test_fsys_exists_n("mem/root/common/orig/4/f/A.oga");
    +	}
    +	@Test   public void Orig_app() {	// .oga is clicked; get file
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm_file("A.oga"));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_file("A.oga"));
    +		fxt.Exec_get(Xof_exec_arg.new_orig("A.oga").Exec_tid_(Xof_exec_tid.Tid_viewer_app).Rslt_orig_exists_y().Rslt_file_exists_y());
    +		fxt.Test_fsys_exists_y("mem/root/common/orig/4/f/A.oga");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__ogg_tst.java b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__ogg_tst.java
    index a27517de8..e4c9ec104 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__ogg_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__ogg_tst.java
    @@ -13,3 +13,23 @@ 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.files.fsdb.tsts; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import org.junit.*;
    +import gplx.xowa.files.bins.*;
    +public class Xof_file_ext__ogg_tst {
    +	@Before public void init() {fxt.Reset();} private final Xof_file_fxt fxt = new Xof_file_fxt();
    +	@After public void term() {fxt.Rls();}
    +//		@Test   public void Video() {	// disabled; .ogg will never be video; DATE:2014-02-02
    +//			fxt.Init_orig_db(Xof_orig_arg.new_comm("A.ogg", 440, 400));
    +//			fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_thumb("A.ogg", 440, 400));	// create a thumb at 440,400 (needed for preview of video)
    +//			fxt.Exec_get(Xof_exec_arg.new_orig("A.ogg").Rslt_orig_missing().Rslt_fsdb_xowa().Rslt_conv_n());
    +//			fxt.Test_fsys("mem/root/common/thumb/4/2/A.ogg/440px.jpg", "440,400");
    +//			Xof_fsdb_itm itm = fxt.Test_regy_pass("A.ogg");
    +//			fxt.Test_itm_ext(itm, Xof_ext_.Id_ogv);// chk that file is now ogv
    +//		}
    +	@Test   public void Audio() {
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm("A.ogg", 0, 0));	// audio has no size
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_thumb("A.ogg", 440, 400));	// create a thumb but it should never be used
    +		fxt.Exec_get(Xof_exec_arg.new_orig("A.ogg").Exec_tid_(Xof_exec_tid.Tid_viewer_app).Rslt_orig_exists_y().Rslt_file_exists_n().Rslt_file_resized_n());
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__ogv_tst.java b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__ogv_tst.java
    index a27517de8..3bd58f262 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__ogv_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__ogv_tst.java
    @@ -13,3 +13,22 @@ 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.files.fsdb.tsts; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import org.junit.*;
    +public class Xof_file_ext__ogv_tst {
    +	@Before public void init() {fxt.Reset();} private final Xof_file_fxt fxt = new Xof_file_fxt();
    +	@After public void term() {fxt.Rls();}
    +	@Test   public void Copy_orig() {
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm("A.ogv", 440, 400));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_thumb("A.ogv", 440, 400));
    +		fxt.Exec_get(Xof_exec_arg.new_orig("A.ogv").Rslt_orig_exists_y().Rslt_file_exists_y().Rslt_file_resized_n());
    +		fxt.Test_fsys("mem/root/common/thumb/d/0/A.ogv/440px.jpg", "440,400");
    +	}
    +	@Test   public void Copy_orig_w_thumbtime() {
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm("A.ogv", 440, 400));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_thumb("A.ogv", 440, 400, 10));
    +		fxt.Exec_get(Xof_exec_arg.new_orig("A.ogv").Lnki_time_(10).Rslt_orig_exists_y().Rslt_file_exists_y().Rslt_file_resized_n());
    +		fxt.Test_fsys("mem/root/common/thumb/d/0/A.ogv/440px-10.jpg", "440,400");
    +		// fxt.Exec_get(Xof_exec_arg.new_thumb("A.png", Xop_lnki_tkn.Width_null, 130)); DELETE: not needed; tests if new A.png can be resized from existing; DATE:2015-03-03
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__pdf_tst.java b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__pdf_tst.java
    index a27517de8..dc2e7e4ff 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__pdf_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__pdf_tst.java
    @@ -13,3 +13,15 @@ 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.files.fsdb.tsts; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import org.junit.*;
    +public class Xof_file_ext__pdf_tst {
    +	@Before public void init() {fxt.Reset();} private final Xof_file_fxt fxt = new Xof_file_fxt();
    +	@After public void term() {fxt.Rls();}
    +	@Test  public void Copy_thumb() {// PURPOSE: download pdf thumb only; [[File:Physical world.pdf|thumb]]
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm("A.pdf", 2200, 1700));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_thumb("A.pdf", 220, 170));
    +		fxt.Exec_get(Xof_exec_arg.new_thumb("A.pdf", 220));
    +		fxt.Test_fsys("mem/root/common/thumb/e/f/A.pdf/220px.jpg", "220,170");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__png_tst.java b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__png_tst.java
    index a27517de8..60c9456d9 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__png_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__png_tst.java
    @@ -13,3 +13,49 @@ 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.files.fsdb.tsts; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import org.junit.*; import gplx.xowa.parsers.lnkis.*;
    +public class Xof_file_ext__png_tst {
    +	@Before public void init() {fxt.Reset();} private final Xof_file_fxt fxt = new Xof_file_fxt();
    +	@After public void term() {fxt.Rls();}
    +	@Test   public void Copy_thumb() {
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm("A.png", 440, 400));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_thumb("A.png"));
    +		fxt.Exec_get(Xof_exec_arg.new_thumb("A.png").Rslt_orig_exists_y().Rslt_file_exists_y());
    +		fxt.Test_fsys("mem/root/common/thumb/7/0/A.png/220px.png", "220,200");
    +	}
    +	@Test   public void Copy_orig() {
    +		fxt.Init__orig_w_fsdb__commons_orig("A.png", 440, 400);
    +		fxt.Exec_get(Xof_exec_arg.new_orig("A.png").Rslt_orig_exists_y().Rslt_file_exists_y());
    +		fxt.Test_fsys("mem/root/common/orig/7/0/A.png", "440,400");
    +	}
    +	@Test   public void Copy_orig_w_width() {	// PURPOSE: if not thumb, but width is specified; do not download orig and convert; EX: [[File:The Earth seen from Apollo 17.jpg|250px]]
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm("A.png", 440, 400));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_thumb("A.png", 220, 200));
    +		fxt.Exec_get(Xof_exec_arg.new_("A.png", Xop_lnki_type.Id_null, 220, Xop_lnki_tkn.Height_null).Rslt_orig_exists_y().Rslt_file_exists_y());
    +		fxt.Test_fsys("mem/root/common/thumb/7/0/A.png/220px.png", "220,200");
    +	}
    +	@Test   public void Make_thumb() {
    +		fxt.Init__orig_w_fsdb__commons_orig("A.png", 440, 400);
    +		fxt.Exec_get(Xof_exec_arg.new_thumb("A.png").Rslt_orig_exists_y().Rslt_file_exists_y().Rslt_file_resized_y());
    +		fxt.Test_fsys("mem/root/common/thumb/7/0/A.png/220px.png", "220,200");
    +	}
    +	@Test  public void Height_only() {	// PURPOSE.fix: height only was still using old infer-size code; EX:w:[[File:Upper and Middle Manhattan.jpg|x120px]]; DATE:2012-12-27
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm("A.png", 12591, 1847));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_thumb("A.png", 887, 130));
    +		fxt.Exec_get(Xof_exec_arg.new_thumb("A.png", Xop_lnki_tkn.Width_null, 130));
    +		fxt.Test_fsys("mem/root/common/thumb/7/0/A.png/887px.png", "887,130");
    +	}
    +	@Test  public void Width_only_height_ignored() {// PURPOSE.fix: if height is not specified, do not recalc; needed when true scaled height is 150x151 but WM has 150x158; defect would discard 150x158; EX:[[File:Tokage_2011-07-15.jpg|150px]] simple.wikipedia.org/wiki/2011_Pacific_typhoon_season; DATE:2013-06-03
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm("A.png", 4884, 4932));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_thumb("A.png", 150, 150));
    +		fxt.Exec_get(Xof_exec_arg.new_thumb("A.png", 150, Xop_lnki_tkn.Height_null));
    +		fxt.Test_fsys("mem/root/common/thumb/7/0/A.png/150px.png", "150,150");
    +	}
    +	@Test  public void Upright() {	// PURPOSE.fix: upright not working;  EX: w:Beethoven; [[File:Rudolf-habsburg-olmuetz.jpg|thumb|upright|]]
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm("A.png", 1378, 1829));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_thumb("A.png", 170, 226));
    +		fxt.Exec_get(Xof_exec_arg.new_thumb("A.png", Xop_lnki_tkn.Width_null, Xop_lnki_tkn.Height_null).Lnki_upright_(Xof_img_size.Upright_default_marker));
    +		fxt.Test_fsys("mem/root/common/thumb/7/0/A.png/170px.png", "170,226");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__svg_tst.java b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__svg_tst.java
    index a27517de8..d6add5058 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__svg_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__svg_tst.java
    @@ -13,3 +13,26 @@ 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.files.fsdb.tsts; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import org.junit.*;
    +public class Xof_file_ext__svg_tst {
    +	@Before public void init() {fxt.Reset();} private final Xof_file_fxt fxt = new Xof_file_fxt();
    +	@After public void term() {fxt.Rls();}
    +	@Test   public void Make_orig() {
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm("A.svg", 440, 400));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_orig("A.svg", 440, 400));
    +		fxt.Exec_get(Xof_exec_arg.new_orig("A.svg").Rslt_orig_exists_y().Rslt_file_exists_y().Rslt_file_resized_y());
    +		fxt.Test_fsys("mem/root/common/thumb/7/5/A.svg/440px.png", "440,400");
    +	}
    +	@Test   public void Make_thumb() {
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm("A.svg", 440, 400));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_orig("A.svg", 440, 400));
    +		fxt.Exec_get(Xof_exec_arg.new_thumb("A.svg").Rslt_orig_exists_y().Rslt_file_exists_y().Rslt_file_resized_y());
    +		fxt.Test_fsys("mem/root/common/thumb/7/5/A.svg/220px.png", "220,200");
    +	}
    +	@Test  public void Thumb_can_be_bigger_than_orig() {// PURPOSE: svg thumbs allowed to exceed orig in size; EX: w:Portal:Music; [[File:Treble a.svg|left|160px]]
    +		fxt.Init__orig_w_fsdb__commons_orig("A.svg", 110, 100);							// create orig of 110,100
    +		fxt.Exec_get(Xof_exec_arg.new_thumb("A.svg", 330).Rslt_file_exists_y());	// get 330
    +		fxt.Test_fsys("mem/root/common/thumb/7/5/A.svg/330px.png", "330,300");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__unknown_tst.java b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__unknown_tst.java
    index a27517de8..8d3b5a8e0 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__unknown_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__unknown_tst.java
    @@ -13,3 +13,21 @@ 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.files.fsdb.tsts; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import org.junit.*;
    +public class Xof_file_ext__unknown_tst {
    +	@Before public void init() {fxt.Reset();} private final Xof_file_fxt fxt = new Xof_file_fxt();
    +	@After public void term() {fxt.Rls();}
    +	@Test   public void Orig_page() {	// .bin is on page [[File:A.bin]]; check orig (since orig may redirect) but do not get file; (since file is not "viewable" immediately); DATE:2014-08-17
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm_file("A.bin"));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_file("A.bin"));
    +		fxt.Exec_get(Xof_exec_arg.new_orig("A.bin").Rslt_orig_exists_y().Rslt_file_exists_n());
    +		fxt.Test_fsys_exists_n("mem/root/common/orig/f/b/A.bin");
    +	}
    +	@Test   public void Orig_app() {	// .bin is clicked; get file
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm_file("A.bin"));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_file("A.bin"));
    +		fxt.Exec_get(Xof_exec_arg.new_orig("A.bin").Exec_tid_(Xof_exec_tid.Tid_viewer_app).Rslt_orig_exists_y().Rslt_file_exists_y());
    +		fxt.Test_fsys_exists_y("mem/root/common/orig/f/b/A.bin");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__wav_tst.java b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__wav_tst.java
    index a27517de8..fd718c41d 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__wav_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__wav_tst.java
    @@ -13,3 +13,22 @@ 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.files.fsdb.tsts; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import org.junit.*;
    +import gplx.xowa.files.bins.*;
    +public class Xof_file_ext__wav_tst {
    +	@Before public void init() {fxt.Reset();} private final Xof_file_fxt fxt = new Xof_file_fxt();
    +	@After public void term() {fxt.Rls();}
    +	@Test   public void Orig_page() {	// .wav is on page [[File:A.wav]]; check orig (since orig may redirect) but do not get file; (since file is not "viewable" immediately); DATE:2014-08-17
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm_file("A.wav"));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_file("A.wav"));
    +		fxt.Exec_get(Xof_exec_arg.new_orig("A.wav").Rslt_orig_exists_y().Rslt_file_exists_n());
    +		fxt.Test_fsys_exists_n("mem/root/common/orig/c/3/A.wav");
    +	}
    +	@Test   public void Orig_app() {	// .wav is clicked; get file
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm_file("A.wav"));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_file("A.wav"));
    +		fxt.Exec_get(Xof_exec_arg.new_orig("A.wav").Exec_tid_(Xof_exec_tid.Tid_viewer_app).Rslt_orig_exists_y().Rslt_file_exists_y());
    +		fxt.Test_fsys_exists_y("mem/root/common/orig/c/3/A.wav");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__xcf_tst.java b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__xcf_tst.java
    index a27517de8..a55d35edd 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__xcf_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_ext__xcf_tst.java
    @@ -13,3 +13,21 @@ 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.files.fsdb.tsts; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import org.junit.*;
    +public class Xof_file_ext__xcf_tst {
    +	@Before public void init() {fxt.Reset();} private final Xof_file_fxt fxt = new Xof_file_fxt();
    +	@After public void term() {fxt.Rls();}
    +	@Test   public void Make_orig() {
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm("A.xcf", 440, 400));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_orig("A.xcf", 440, 400));
    +		fxt.Exec_get(Xof_exec_arg.new_orig("A.xcf").Rslt_orig_exists_y().Rslt_file_exists_y().Rslt_file_resized_y());
    +		fxt.Test_fsys("mem/root/common/thumb/4/4/A.xcf/440px.png", "440,400");
    +	}
    +	@Test   public void Make_thumb() {
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm("A.xcf", 440, 400));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_orig("A.xcf", 440, 400));
    +		fxt.Exec_get(Xof_exec_arg.new_thumb("A.xcf").Rslt_orig_exists_y().Rslt_file_exists_y().Rslt_file_resized_y());
    +		fxt.Test_fsys("mem/root/common/thumb/4/4/A.xcf/220px.png", "220,200");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_fxt.java b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_fxt.java
    index a27517de8..aeebe67be 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_fxt.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_fxt.java
    @@ -13,3 +13,178 @@ 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.files.fsdb.tsts; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import gplx.core.envs.*;
    +import gplx.fsdb.*; import gplx.fsdb.meta.*; import gplx.dbs.*; import gplx.xowa.files.origs.*; import gplx.xowa.files.bins.*; import gplx.xowa.files.cnvs.*; import gplx.xowa.files.exts.*; import gplx.xowa.guis.cbks.js.*;
    +import gplx.fsdb.data.*;
    +import gplx.xowa.wikis.domains.*; import gplx.xowa.files.repos.*; import gplx.xowa.wikis.data.*;
    +import gplx.xowa.wikis.nss.*;
    +import gplx.xowa.parsers.lnkis.*;
    +class Xof_file_fxt {		
    +	private Xoae_app app; private Xof_fsdb_mgr__sql fsdb_mgr; private Xowe_wiki wiki; private Xof_orig_mgr orig_mgr;
    +	private final    Fsd_thm_itm tmp_thm = Fsd_thm_itm.new_();
    +	public void Rls() {}
    +	public void Reset() {
    +		Io_mgr.Instance.InitEngine_mem();	// NOTE: files are downloaded to mem_engine, regardless of Db being mem or sqlite; always reset
    +		Io_url root_url = Xoa_test_.Url_root();
    +		Xoa_test_.Db_init(root_url);
    +		app = Xoa_app_fxt.Make__app__edit(Op_sys.Cur().Os_name(), root_url);
    +		wiki = Xoa_app_fxt.Make__wiki__edit(app);
    +		wiki.File__fsdb_mode().Tid__v2__bld__y_();
    +		this.fsdb_mgr = (Xof_fsdb_mgr__sql)wiki.File_mgr().Fsdb_mgr();
    +		this.orig_mgr = wiki.File__orig_mgr();
    +		Xof_repo_fxt.Repos_init(app.File_mgr(), true, wiki);
    +		Xowe_wiki_.Create(wiki, 1, "dump.xml");
    +		Xow_db_file text_db = wiki.Data__core_mgr().Dbs__make_by_tid(Xow_db_file_.Tid__text); text_db.Tbl__text().Create_tbl();
    +		Fsdb_db_mgr__v2 fsdb_core = Fsdb_db_mgr__v2_bldr.Get_or_make(wiki, Bool_.Y);
    +		fsdb_mgr.Mnt_mgr().Ctor_by_load(fsdb_core);
    +		fsdb_mgr.Mnt_mgr().Mnts__get_main().Bin_mgr().Dbs__make("temp.xowa");
    +		wiki.File_mgr().Init_file_mgr_by_load(wiki);
    +		fsdb_mgr.Bin_mgr().Wkrs__del(Xof_bin_wkr_.Key_http_wmf);	// never use http_wmf wkr for these tests
    +		Xof_bin_wkr__fsdb_sql bin_wkr_fsdb = (Xof_bin_wkr__fsdb_sql)fsdb_mgr.Bin_mgr().Wkrs__get_or_null(Xof_bin_wkr_.Key_fsdb_wiki);
    +		fsdb_mgr.Bin_mgr().Resizer_(Xof_img_wkr_resize_img_mok.Instance);
    +		bin_wkr_fsdb.Resize_allowed_(true);
    +	}
    +	public void Init__orig_w_fsdb__commons_orig(String ttl, int w, int h) {
    +		this.Init_fsdb_db(Xof_fsdb_arg.new_comm(Bool_.N, ttl, w, h));
    +		this.Init_orig_db(Xof_orig_arg.new_comm(ttl, w, h));
    +	}
    +	public void Init_orig_db(Xof_orig_arg arg) {
    +		orig_mgr.Insert(arg.Repo(), arg.Page(), Xof_ext_.new_by_ttl_(arg.Page()).Id(), arg.W(), arg.H(), arg.Redirect());
    +	}
    +	public void Init_fsdb_db(Xof_fsdb_arg arg) {
    +		Fsm_mnt_itm mnt_itm = fsdb_mgr.Mnt_mgr().Mnts__get_main();
    +		Fsm_atr_fil atr_fil = mnt_itm.Atr_mgr().Db__core();
    +		Fsm_bin_fil bin_fil = mnt_itm.Bin_mgr().Dbs__get_nth();
    +		if (arg.Is_thumb())
    +			mnt_itm.Insert_thm(tmp_thm, atr_fil, bin_fil, arg.Wiki(), arg.Ttl(), arg.Ext(), arg.W(), arg.H(), arg.Time(), arg.Page(), arg.Bin().length, gplx.core.ios.streams.Io_stream_rdr_.New__mem(arg.Bin()));
    +		else
    +			mnt_itm.Insert_img(atr_fil, bin_fil, arg.Wiki(), arg.Ttl(), arg.Ext(), arg.W(), arg.H(), arg.Bin().length, gplx.core.ios.streams.Io_stream_rdr_.New__mem(arg.Bin()));
    +	}
    +	public void Exec_get(Xof_exec_arg arg) {
    +		byte[] ttl_bry = arg.Ttl();
    +		Xof_fsdb_itm itm = new Xof_fsdb_itm();
    +		itm.Init_at_lnki(arg.Exec_tid(), wiki.Domain_itm().Abrv_xo(), ttl_bry, arg.Lnki_type(), arg.Lnki_upright(), arg.Lnki_w(), arg.Lnki_h(), arg.Lnki_time(), Xof_lnki_page.Null, Xof_patch_upright_tid_.Tid_all);
    +		List_adp itms_list = List_adp_.New(); itms_list.Add(itm);
    +		orig_mgr.Find_by_list(Ordered_hash_.New_bry(), itms_list, Xof_exec_tid.Tid_wiki_page);
    +		Xoa_ttl ttl = Xoa_ttl.Parse(wiki, Xow_ns_.Tid__main, ttl_bry);
    +		Xoae_page page = Xoae_page.New(wiki, ttl);
    +		fsdb_mgr.Fsdb_search_by_list(itms_list, wiki, page, Xog_js_wkr_.Noop);
    +		if (arg.Rslt_orig_exists()  != Bool_.__byte)	Tfds.Eq(arg.Rslt_orig_exists()  == Bool_.Y_byte, itm.Orig_exists(), "orig_exists");
    +		if (arg.Rslt_file_exists()  != Bool_.__byte)	Tfds.Eq(arg.Rslt_file_exists()  == Bool_.Y_byte, itm.File_exists(), "file_exists");
    +		if (arg.Rslt_file_resized() != Bool_.__byte)	Tfds.Eq(arg.Rslt_file_resized() == Bool_.Y_byte, itm.File_resized(), "file_resize");
    +	}
    +	public void Test_fsys_exists_y(String url)			{Test_fsys_exists(url, Bool_.Y);}
    +	public void Test_fsys_exists_n(String url)			{Test_fsys_exists(url, Bool_.N);}
    +	public void Test_fsys_exists(String url, boolean expd) {Tfds.Eq(expd, Io_mgr.Instance.ExistsFil(Io_url_.new_any_(url)));}
    +	public void Test_fsys(String url, String expd_bin)	{Tfds.Eq(expd_bin, Io_mgr.Instance.LoadFilStr(url));}
    +}
    +class Xof_repo_fxt {
    +	public static void Repos_init(Xof_file_mgr file_mgr, boolean src_repo_is_wmf, Xowe_wiki wiki) {
    +		byte[] src_commons = Bry_.new_a7("src_commons");
    +		byte[] src_en_wiki = Bry_.new_a7("src_en_wiki");
    +		byte[] trg_commons = Bry_.new_a7("trg_commons");
    +		byte[] trg_en_wiki = Bry_.new_a7("trg_en_wiki");
    +		Ini_repo_add(file_mgr, src_commons, "mem/src/commons.wikimedia.org/", Xow_domain_itm_.Str__commons, false);
    +		Ini_repo_add(file_mgr, src_en_wiki, "mem/src/en.wikipedia.org/"		, Xow_domain_itm_.Str__enwiki, false);
    +		Ini_repo_add(file_mgr, trg_commons, "mem/root/common/", Xow_domain_itm_.Str__commons, true).Primary_(true);
    +		Ini_repo_add(file_mgr, trg_en_wiki, "mem/root/enwiki/", Xow_domain_itm_.Str__enwiki, true).Primary_(true);
    +		Xowe_repo_mgr wiki_repo_mgr = wiki.File_mgr().Repo_mgr();
    +		Xof_repo_pair pair = null;
    +		pair = wiki_repo_mgr.Add_repo(src_commons, trg_commons);
    +		pair.Src().Fsys_is_wnt_(true).Wmf_fsys_(src_repo_is_wmf).Tarball_(!src_repo_is_wmf);
    +		pair.Trg().Fsys_is_wnt_(true);
    +
    +		pair = wiki_repo_mgr.Add_repo(src_en_wiki, trg_en_wiki);
    +		pair.Src().Fsys_is_wnt_(true).Wmf_fsys_(src_repo_is_wmf);
    +		pair.Trg().Fsys_is_wnt_(true);
    +	}
    +	private static Xof_repo_itm Ini_repo_add(Xof_file_mgr file_mgr, byte[] key, String root, String wiki, boolean trg) {
    +		return file_mgr.Repo_mgr().Set(String_.new_u8(key), root, wiki).Ext_rules_(Xof_rule_grp.Grp_app_default).Dir_depth_(2);
    +	}
    +}
    +class Xof_orig_arg {
    +	Xof_orig_arg(byte repo, byte[] page, int w, int h, byte[] redirect) {this.repo = repo; this.page = page; this.w = w; this.h = h; this.redirect = redirect;}
    +	public byte			Repo() {return repo;} private final    byte repo;
    +	public byte[]		Page() {return page;} private final    byte[] page;
    +	public int			W() {return w;} private final    int w;
    +	public int			H() {return h;} private final    int h;
    +	public byte[]		Redirect() {return redirect;} private final    byte[] redirect;
    +	public static Xof_orig_arg new_comm_file(String page)								{return new_(Bool_.Y, page, Xof_img_size.Size_null, Xof_img_size.Size_null);}
    +	public static Xof_orig_arg new_comm(String page, int w, int h)						{return new_(Bool_.Y, page, w, h);}
    +	public static Xof_orig_arg new_wiki(String page, int w, int h)						{return new_(Bool_.N, page, w, h);}
    +	public static Xof_orig_arg new_wiki_redirect(String src, String trg)				{return new_(Bool_.N, src, 440, 400, trg);}
    +	public static Xof_orig_arg new_comm_redirect(String src, String trg)				{return new_(Bool_.Y, src, 440, 400, trg);}
    +	private static Xof_orig_arg new_(boolean repo_is_commons, String page, int w, int h)	{return new_(repo_is_commons, page, w, h, null);}
    +	public static Xof_orig_arg new_(boolean repo_is_commons, String page, int w, int h, String redirect_str) {
    +		byte repo = repo_is_commons ? Xof_repo_tid_.Tid__remote : Xof_repo_tid_.Tid__local;
    +		byte[] redirect = redirect_str == null ? Bry_.Empty : Bry_.new_u8(redirect_str);
    +		return new Xof_orig_arg(repo, Bry_.new_u8(page), w, h, redirect);
    +	}
    +}
    +class Xof_fsdb_arg {
    +	Xof_fsdb_arg(byte[] wiki, byte[] ttl, boolean is_thumb, int ext, int w, int h, int time, byte[] bin) {
    +		this.wiki = wiki; this.ttl = ttl; this.is_thumb = is_thumb; this.w = w; this.h = h; this.time = time; this.ext = ext; this.bin = bin;
    +	}
    +	public byte[] Wiki() {return wiki;} private final    byte[] wiki;
    +	public byte[] Ttl() {return ttl;} private final    byte[] ttl;
    +	public int Ext() {return ext;} private final    int ext;
    +	public boolean Is_thumb() {return is_thumb;} private final    boolean is_thumb;
    +	public int W() {return w;} private final    int w;
    +	public int H() {return h;} private final    int h;
    +	public double Time() {return time;} private final    double time;
    +	public int Page() {return page;} private final    int page = Xof_lnki_page.Null;
    +	public byte[] Bin() {return bin;} private final    byte[] bin;
    +	public DateAdp Modified() {return modified;} private final    DateAdp modified = Fsd_thm_tbl.Modified_null;
    +	public String Hash() {return hash;} private final    String hash = Fsd_thm_tbl.Hash_null;
    +	public static Xof_fsdb_arg new_comm_file(String ttl)						{return new_(Xow_domain_itm_.Bry__commons, Bool_.N, ttl, Xof_img_size.Null, Xof_img_size.Null, Xof_lnki_time.Null_as_int);}
    +	public static Xof_fsdb_arg new_comm_thumb(String ttl)						{return new_(Xow_domain_itm_.Bry__commons, Bool_.Y, ttl, W_default, H_default, Xof_lnki_time.Null_as_int);}
    +	public static Xof_fsdb_arg new_comm_thumb(String ttl, int w, int h)			{return new_(Xow_domain_itm_.Bry__commons, Bool_.Y, ttl, w, h, Xof_lnki_time.Null_as_int);}
    +	public static Xof_fsdb_arg new_comm_thumb(String ttl, int w, int h, int s)	{return new_(Xow_domain_itm_.Bry__commons, Bool_.Y, ttl, w, h, s);}
    +	public static Xof_fsdb_arg new_comm_orig(String ttl, int w, int h)			{return new_(Xow_domain_itm_.Bry__commons, Bool_.N, ttl, w, h, Xof_lnki_time.Null_as_int);}
    +	public static Xof_fsdb_arg new_comm(boolean thumb, String ttl, int w, int h)	{return new_(Xow_domain_itm_.Bry__commons, thumb, ttl, w, h, Xof_lnki_time.Null_as_int);}
    +	public static Xof_fsdb_arg new_wiki_thumb(String ttl, int w, int h)			{return new_(Xow_domain_itm_.Bry__enwiki, Bool_.Y, ttl, w, h, Xof_lnki_time.Null_as_int);}
    +	public static Xof_fsdb_arg new_wiki_orig(String ttl, int w, int h)			{return new_(Xow_domain_itm_.Bry__enwiki, Bool_.N, ttl, w, h, Xof_lnki_time.Null_as_int);}
    +	public static Xof_fsdb_arg new_(byte[] wiki, boolean is_thumb, String ttl_str, int w, int h, int time) {
    +		byte[] ttl = Bry_.new_u8(ttl_str);
    +		int ext = Xof_ext_.new_by_ttl_(ttl).Id();
    +		String bin_str = ext == Xof_ext_.Id_svg ? file_svg_(w, h) : file_img_(w, h);
    +		byte[] bin = Bry_.new_a7(bin_str);
    +		return new Xof_fsdb_arg(wiki, ttl, is_thumb, ext, w, h, time, bin);
    +	}
    +	private static final int W_default = 220, H_default = 200;
    +	private static String file_img_(int w, int h) {return String_.Format("{0},{1}", w, h);}
    +	private static String file_svg_(int w, int h) {return String_.Format("", w, h);}
    +}
    +class Xof_exec_arg {
    +	public byte[] Ttl() {return ttl;} private byte[] ttl;
    +	public byte Lnki_type() {return lnki_type;} private byte lnki_type = Xop_lnki_type.Id_thumb;
    +	public int Lnki_w() {return lnki_w;} private int lnki_w;
    +	public int Lnki_h() {return lnki_h;} private int lnki_h = Xop_lnki_tkn.Height_null;
    +	public double Lnki_upright() {return lnki_upright;} public Xof_exec_arg Lnki_upright_(double v) {lnki_upright = v; return this;} private double lnki_upright = Xop_lnki_tkn.Upright_null;
    +	public int Lnki_time() {return lnki_time;} public Xof_exec_arg Lnki_time_(int v) {lnki_time = v; return this;} private int lnki_time = Xof_lnki_time.Null_as_int;
    +	public int Lnki_page() {return lnki_page;} public Xof_exec_arg Lnki_page_(int v) {lnki_page = v; return this;} private int lnki_page = Xof_lnki_page.Null;
    +	public int Exec_tid() {return exec_tid;} public Xof_exec_arg Exec_tid_(int v) {exec_tid = v; return this;} private int exec_tid = Xof_exec_tid.Tid_wiki_page;
    +	public byte Rslt_orig_exists() {return rslt_orig_exists;} private byte rslt_orig_exists = Bool_.__byte;
    +	public byte Rslt_file_exists() {return rslt_file_exists;} private byte rslt_file_exists = Bool_.__byte;
    +	public byte Rslt_file_resized() {return rslt_file_resized;} private byte rslt_file_resized = Bool_.__byte;
    +	public boolean Lnki_type_is_thumb() {return Xop_lnki_type.Id_defaults_to_thumb(lnki_type);}
    +	public Xof_exec_arg Rslt_orig_exists_n()	{rslt_orig_exists = Bool_.N_byte; return this;}
    +	public Xof_exec_arg Rslt_orig_exists_y()	{rslt_orig_exists = Bool_.Y_byte; return this;}
    +	public Xof_exec_arg Rslt_file_exists_n()	{rslt_file_exists = Bool_.N_byte; return this;}
    +	public Xof_exec_arg Rslt_file_exists_y()	{rslt_file_exists = Bool_.Y_byte; return this;}
    +	public Xof_exec_arg Rslt_file_resized_n()	{rslt_file_resized = Bool_.N_byte; return this;}
    +	public Xof_exec_arg Rslt_file_resized_y()	{rslt_file_resized = Bool_.Y_byte; return this;}
    +	public static Xof_exec_arg new_thumb(String ttl)					{return new_(ttl, Xop_lnki_type.Id_thumb, 220, Xop_lnki_tkn.Height_null);}
    +	public static Xof_exec_arg new_thumb(String ttl, int w)				{return new_(ttl, Xop_lnki_type.Id_thumb, w, Xop_lnki_tkn.Height_null);}
    +	public static Xof_exec_arg new_thumb(String ttl, int w, int h)		{return new_(ttl, Xop_lnki_type.Id_thumb, w, h);}
    +	public static Xof_exec_arg new_orig(String ttl)						{return new_(ttl, Xop_lnki_type.Id_null, Xop_lnki_tkn.Width_null, Xop_lnki_tkn.Height_null);}
    +	public static Xof_exec_arg new_(String ttl, byte type, int w, int h) {
    +		Xof_exec_arg rv = new Xof_exec_arg();
    +		rv.ttl = Bry_.new_u8(ttl);
    +		rv.lnki_type = type;
    +		rv.lnki_w = w;
    +		rv.lnki_h = h;
    +		return rv;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_redirect_tst.java b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_redirect_tst.java
    index a27517de8..dc2bdefda 100644
    --- a/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_redirect_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/fsdb/tsts/Xof_file_redirect_tst.java
    @@ -13,3 +13,52 @@ 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.files.fsdb.tsts; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import org.junit.*;
    +public class Xof_file_redirect_tst {
    +	@Before public void init() {fxt.Reset();} private final Xof_file_fxt fxt = new Xof_file_fxt();
    +	@After public void term() {fxt.Rls();}
    +	@Test  public void Same_wiki_orig_copy() {
    +		fxt.Init_orig_db(Xof_orig_arg.new_wiki("A.png", 440, 400));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_wiki_orig("A.png", 440, 400));
    +		fxt.Init_orig_db(Xof_orig_arg.new_wiki_redirect("B.png", "A.png"));
    +		fxt.Exec_get(Xof_exec_arg.new_orig("B.png").Rslt_file_exists_y());
    +		fxt.Test_fsys("mem/root/enwiki/orig/7/0/A.png", "440,400");
    +	}
    +	@Test  public void Same_wiki_thumb_copy() {
    +		fxt.Init_orig_db(Xof_orig_arg.new_wiki("A.png", 440, 400));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_wiki_thumb("A.png", 220, 200));
    +		fxt.Init_orig_db(Xof_orig_arg.new_wiki_redirect("B.png", "A.png"));
    +		fxt.Exec_get(Xof_exec_arg.new_thumb("B.png", 220).Rslt_file_exists_y());
    +		fxt.Test_fsys("mem/root/enwiki/thumb/7/0/A.png/220px.png", "220,200");
    +	}
    +	@Test  public void Same_wiki_thumb_make() {
    +		fxt.Init_orig_db(Xof_orig_arg.new_wiki("A.png", 440, 400));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_wiki_orig("A.png", 440, 400));
    +		fxt.Init_orig_db(Xof_orig_arg.new_wiki_redirect("B.png", "A.png"));
    +		fxt.Exec_get(Xof_exec_arg.new_thumb("B.png", 220).Rslt_file_exists_y().Rslt_file_resized_y());
    +		fxt.Test_fsys("mem/root/enwiki/thumb/7/0/A.png/220px.png", "220,200");
    +	}
    +	@Test  public void Diff_wiki_orig_copy() {
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm("A.png", 440, 400));
    +		fxt.Init_fsdb_db(Xof_fsdb_arg.new_comm_orig("A.png", 440, 400));
    +		fxt.Init_orig_db(Xof_orig_arg.new_comm_redirect("B.png", "A.png"));
    +		fxt.Exec_get(Xof_exec_arg.new_orig("B.png").Rslt_file_exists_y());
    +		fxt.Test_fsys("mem/root/common/orig/7/0/A.png", "440,400");
    +	}
    +//		@Test  public void Cross_wiki() {
    +//			fxt.Init__orig_w_fsdb__commons_orig("A.png", 440, 400);
    +//			fxt.Init_orig_db(Xof_orig_arg.new_wiki_redirect("B.png", "A.png"));
    +//			fxt.Exec_get(Xof_exec_arg.new_orig("B.png").Rslt_fsdb_xowa());
    +//			fxt.Test_fsys("mem/root/common/orig/7/0/A.png", "440,400");
    +
    +//			fxt	.ini_page_create_en_wiki_redirect	("File:B.png", "File:A.png");
    +//			fxt	.ini_page_create_commons			("File:A.png");
    +//			fxt	.Lnki_orig_("B.png")
    +//				.Src(	fxt.img_("mem/src/commons.wikimedia.org/7/70/A.png", 900, 800))
    +//				.Trg(	fxt.img_("mem/trg/commons.wikimedia.org/raw/7/0/A.png", 900, 800)
    +//					,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/5/57.csv"		, "B.png|y|A.png|1?900,800|")
    +//					);
    +//			fxt.tst();
    +//		}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/imgs/Xof_img_mgr.java b/400_xowa/src/gplx/xowa/files/imgs/Xof_img_mgr.java
    index a27517de8..7599559a8 100644
    --- a/400_xowa/src/gplx/xowa/files/imgs/Xof_img_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/imgs/Xof_img_mgr.java
    @@ -13,3 +13,15 @@ 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.files.imgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.xowa.apps.progs.*; import gplx.xowa.bldrs.wms.*; import gplx.xowa.files.cnvs.*;	
    +public class Xof_img_mgr {
    +	public Xof_img_wkr_resize_img				Wkr_resize_img() {return wkr_resize_img;} public void Wkr_resize_img_(Xof_img_wkr_resize_img v) {wkr_resize_img = v;} private Xof_img_wkr_resize_img wkr_resize_img;
    +	public Xof_img_wkr_query_img_size			Wkr_query_img_size() {return wkr_query_img_size;} public void Wkr_query_img_size_(Xof_img_wkr_query_img_size v) {wkr_query_img_size = v;} private Xof_img_wkr_query_img_size wkr_query_img_size;
    +	public Xof_img_wkr_convert_djvu_to_tiff		Wkr_convert_djvu_to_tiff() {return wkr_convert_djvu_to_tiff;} public Xof_img_mgr Wkr_convert_djvu_to_tiff_(Xof_img_wkr_convert_djvu_to_tiff v) {wkr_convert_djvu_to_tiff = v; return this;} private Xof_img_wkr_convert_djvu_to_tiff wkr_convert_djvu_to_tiff;
    +	public void Init_by_app(Xowmf_mgr wmf_mgr, Xoa_prog_mgr prog_mgr) {
    +		wkr_resize_img				= new Xof_img_wkr_resize_img_imageMagick(wmf_mgr, prog_mgr.App_resize_img(), prog_mgr.App_convert_svg_to_png());
    +		wkr_query_img_size			= new Xof_img_wkr_query_img_size_imageMagick(wmf_mgr, prog_mgr.App_query_img_size());
    +		wkr_convert_djvu_to_tiff	= new Xof_img_wkr_convert_djvu_to_tiff_app(prog_mgr.App_convert_djvu_to_tiff());
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/imgs/Xof_img_mode_.java b/400_xowa/src/gplx/xowa/files/imgs/Xof_img_mode_.java
    index a27517de8..b75e8c6f4 100644
    --- a/400_xowa/src/gplx/xowa/files/imgs/Xof_img_mode_.java
    +++ b/400_xowa/src/gplx/xowa/files/imgs/Xof_img_mode_.java
    @@ -13,3 +13,12 @@ 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.files.imgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +public class Xof_img_mode_ {
    +	public static final byte 
    +	  Tid__orig = 0
    +	, Tid__thumb = 1
    +	;
    +	public static byte By_bool(boolean is_thumb) {return is_thumb ? Tid__thumb : Tid__orig;}
    +	public static final    byte[][] Names_ary = new byte[][] {Bry_.new_a7("orig"), Bry_.new_a7("thumb")};
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/imgs/Xof_img_wkr_convert_djvu_to_tiff.java b/400_xowa/src/gplx/xowa/files/imgs/Xof_img_wkr_convert_djvu_to_tiff.java
    index a27517de8..fb46ad4f9 100644
    --- a/400_xowa/src/gplx/xowa/files/imgs/Xof_img_wkr_convert_djvu_to_tiff.java
    +++ b/400_xowa/src/gplx/xowa/files/imgs/Xof_img_wkr_convert_djvu_to_tiff.java
    @@ -13,3 +13,7 @@ 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.files.imgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +public interface Xof_img_wkr_convert_djvu_to_tiff {
    +	boolean Exec(Io_url src, Io_url trg);
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/imgs/Xof_img_wkr_convert_djvu_to_tiff_.java b/400_xowa/src/gplx/xowa/files/imgs/Xof_img_wkr_convert_djvu_to_tiff_.java
    index a27517de8..9ef8cee1c 100644
    --- a/400_xowa/src/gplx/xowa/files/imgs/Xof_img_wkr_convert_djvu_to_tiff_.java
    +++ b/400_xowa/src/gplx/xowa/files/imgs/Xof_img_wkr_convert_djvu_to_tiff_.java
    @@ -13,3 +13,23 @@ 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.files.imgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.envs.*;
    +public class Xof_img_wkr_convert_djvu_to_tiff_ {
    +	public static Xof_img_wkr_convert_djvu_to_tiff new_app(Process_adp process)	{return new Xof_img_wkr_convert_djvu_to_tiff_app(process);}
    +	public static Xof_img_wkr_convert_djvu_to_tiff new_mok(int w, int h)		{return new Xof_img_wkr_convert_djvu_to_tiff_mok(w, h);}
    +}
    +class Xof_img_wkr_convert_djvu_to_tiff_app implements Xof_img_wkr_convert_djvu_to_tiff {
    +	public Xof_img_wkr_convert_djvu_to_tiff_app(Process_adp process) {this.process = process;} Process_adp process;
    +	public boolean Exec(Io_url src, Io_url trg) {
    +		process.Run(src, trg);
    +		return process.Exit_code_pass();
    +	}
    +}
    +class Xof_img_wkr_convert_djvu_to_tiff_mok implements Xof_img_wkr_convert_djvu_to_tiff {
    +	public Xof_img_wkr_convert_djvu_to_tiff_mok(int w, int h) {this.w = w; this.h = h;} private int w, h;
    +	public boolean Exec(Io_url src, Io_url trg) {
    +		Io_mgr.Instance.SaveFilStr(trg, gplx.gfui.SizeAdp_.new_(w, h).To_str());
    +		return true;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/imgs/Xof_img_wkr_query_img_size.java b/400_xowa/src/gplx/xowa/files/imgs/Xof_img_wkr_query_img_size.java
    index a27517de8..a6c46d523 100644
    --- a/400_xowa/src/gplx/xowa/files/imgs/Xof_img_wkr_query_img_size.java
    +++ b/400_xowa/src/gplx/xowa/files/imgs/Xof_img_wkr_query_img_size.java
    @@ -13,3 +13,24 @@ 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.files.imgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.envs.*;
    +import gplx.gfui.*;
    +import gplx.xowa.bldrs.wms.*;
    +public interface Xof_img_wkr_query_img_size {
    +	SizeAdp Exec(Io_url url);
    +}
    +class Xof_img_wkr_query_img_size_imageMagick implements Xof_img_wkr_query_img_size {
    +	private final Xowmf_mgr wmf_mgr; private final Process_adp cmd;
    +	public Xof_img_wkr_query_img_size_imageMagick(Xowmf_mgr wmf_mgr, Process_adp cmd) {this.wmf_mgr = wmf_mgr; this.cmd = cmd;}
    +	public SizeAdp Exec(Io_url url) {
    +		cmd.Prog_fmt_(String_.Replace(wmf_mgr.Download_wkr().Download_xrg().Prog_fmt_hdr(), "~", "~~") + " querying: ~{process_seconds} second(s); ~{process_exe_name} ~{process_exe_args}");
    +		cmd.Run(url);
    +		String size_str = cmd.Rslt_out();
    +		int pos_bgn = String_.FindFwd(size_str, "<{", 0);		if (pos_bgn == String_.Find_none) return SizeAdp_.Zero; // NOTE: RE: "FindFwd(,0)"; multiple frames are possible; 1st frame must be used as last frame is not accurate; EX:w.Chess:[[File:ChessCastlingMovie.gif|thumb|250px]]
    +		int pos_end = String_.FindFwd(size_str, "}>", pos_bgn); if (pos_end == String_.Find_none) return SizeAdp_.Zero;
    +		size_str = String_.Mid(size_str, pos_bgn + Marker_bgn_len, pos_end);
    +		return SizeAdp_.parse_or(size_str, SizeAdp_.Zero);
    +	}
    +	static final String Marker_bgn = "<{", Marker_end = "}>"; static final int Marker_bgn_len = String_.Len(Marker_bgn);
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/imgs/Xof_img_wkr_query_img_size_test.java b/400_xowa/src/gplx/xowa/files/imgs/Xof_img_wkr_query_img_size_test.java
    index a27517de8..9772d9a75 100644
    --- a/400_xowa/src/gplx/xowa/files/imgs/Xof_img_wkr_query_img_size_test.java
    +++ b/400_xowa/src/gplx/xowa/files/imgs/Xof_img_wkr_query_img_size_test.java
    @@ -13,3 +13,11 @@ 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.files.imgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.gfui.*; import gplx.gfui.imgs.*;
    +public class Xof_img_wkr_query_img_size_test implements Xof_img_wkr_query_img_size {
    +	public SizeAdp Exec(Io_url url) {
    +		ImageAdp image = ImageAdp_.txt_fil_(url);
    +		return image.Size();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_itm.java b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_itm.java
    index a27517de8..418235f89 100644
    --- a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_itm.java
    +++ b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_itm.java
    @@ -13,3 +13,37 @@ 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.files.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.xowa.wikis.*; import gplx.xowa.files.*;
    +public class Xof_orig_itm {
    +	public Xof_orig_itm(byte repo, byte[] ttl, int ext_id, int w, int h, byte[] redirect) {
    +		this.repo = repo; this.ttl = ttl; this.ext_id = ext_id;
    +		this.w = w; this.h = h;  this.redirect = redirect;
    +	}
    +	public byte			Repo() {return repo;} private final    byte repo;
    +	public byte[]		Ttl() {return ttl;} private final    byte[] ttl;	// without file ns; EX: "A.png" not "File:A.png"
    +	public int			Ext_id() {return ext_id;} private final    int ext_id;
    +	public Xof_ext		Ext() {if (ext == null) ext = Xof_ext_.new_by_id_(ext_id); return ext;} private Xof_ext ext;
    +	public int			W() {return w;} private final    int w;
    +	public int			H() {return h;} private final    int h;
    +	public byte[]		Redirect() {return redirect;} private final    byte[] redirect;	// redirect trg; EX: A.png is redirected to B.jpg; record will have A.png|jpg|220|200|B.jpg where jpg|220|200 are the attributes of B.jpg
    +	public boolean			Insert_new() {return insert_new;} public void Insert_new_y_() {insert_new = Bool_.Y;} private boolean insert_new;
    +
    +	public int Db_row_size() {return Db_row_size_fixed + redirect.length + ttl.length;}
    +	private static final int Db_row_size_fixed = (5 * 4);	// 3 ints; 2 bytes
    +	public static final byte Repo_comm = 0, Repo_wiki = 1, Repo_null = Byte_.Max_value_127;	// SERIALIZED: "wiki_orig.orig_repo"
    +	public static final    Xof_orig_itm Null = null;
    +	public static final int File_len_null = -1;	// file_len used for filters (EX: don't download ogg > 1 MB)
    +	public static String dump(Xof_orig_itm itm) {
    +		if (itm == null)
    +			return "NULL";
    +		Bry_bfr bfr = Bry_bfr_.New_w_size(255);
    +		bfr.Add_str_a7("repo").Add_byte_eq().Add_int_variable((int)itm.repo).Add_byte_semic();
    +		bfr.Add_str_a7("ttl").Add_byte_eq().Add(itm.ttl).Add_byte_semic();
    +		bfr.Add_str_a7("ext_id").Add_byte_eq().Add_int_variable(itm.ext_id).Add_byte_semic();
    +		bfr.Add_str_a7("w").Add_byte_eq().Add_int_variable(itm.w).Add_byte_semic();
    +		bfr.Add_str_a7("h").Add_byte_eq().Add_int_variable(itm.h).Add_byte_semic();
    +		bfr.Add_str_a7("redirect").Add_byte_eq().Add(itm.redirect).Add_byte_semic();
    +		return bfr.To_str_and_clear();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_mgr.java b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_mgr.java
    index a27517de8..4acfc7192 100644
    --- a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_mgr.java
    @@ -13,3 +13,90 @@ 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.files.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.primitives.*; import gplx.dbs.*;
    +import gplx.xowa.files.repos.*; import gplx.xowa.files.fsdb.*; import gplx.xowa.files.downloads.*;
    +import gplx.xowa.apps.wms.apis.*; import gplx.xowa.apps.wms.apis.origs.*;
    +public class Xof_orig_mgr {
    +	private Xof_orig_wkr[] wkrs; private int wkrs_len;
    +	private Xof_url_bldr url_bldr; private Xow_repo_mgr repo_mgr; private final    Xof_img_size img_size = new Xof_img_size();
    +	public Xof_orig_mgr() {this.Wkrs__clear();}
    +	public void Init_by_wiki(Xow_wiki wiki, Xof_fsdb_mode fsdb_mode, Xof_orig_tbl[] orig_tbls, Xof_url_bldr url_bldr) {
    +		this.repo_mgr = wiki.File__repo_mgr(); this.url_bldr = url_bldr;
    +
    +		// if embeddable, do not load orig wkrs which will download images DATE:2017-10-23
    +		if (wiki.Embeddable_enabled())
    +			return;
    +
    +//			if (!fsdb_mode.Tid_v0()) {		// add view,make; don't add if wmf
    +			int orig_tbls_len = orig_tbls.length;
    +			for (int i = 0; i < orig_tbls_len; ++i) {
    +				Xof_orig_tbl orig_tbl = orig_tbls[i];
    +				this.Wkrs__add(new Xof_orig_wkr__orig_db(orig_tbl, i == orig_tbls_len - 1));
    +			}
    +//			}
    +		if (!fsdb_mode.Tid__v2__bld()) {	// add if gui, but not if bld
    +			Io_url wiki_meta_dir = wiki.App().Fsys_mgr().File_dir().GenSubDir_nest("#meta", wiki.Domain_str());
    +			if (Io_mgr.Instance.ExistsDir(wiki_meta_dir)) {
    +				Xof_orig_wkr__xo_meta xo_meta = new Xof_orig_wkr__xo_meta(wiki_meta_dir);
    +				this.Wkrs__add(xo_meta);
    +			}
    +			this.Wkrs__add(new Xof_orig_wkr__wmf_api(new Xoapi_orig_wmf(), wiki.App().Wmf_mgr().Download_wkr(), repo_mgr, wiki.Domain_bry()));
    +		}
    +	}
    +	public Xof_orig_itm Find_by_ttl_or_null(byte[] ttl) {return Find_by_ttl_or_null(ttl, 0, 1);}
    +	public Xof_orig_itm Find_by_ttl_or_null(byte[] ttl, int list_idx, int list_len) {
    +		for (int i = 0; i < wkrs_len; i++) {
    +			Xof_orig_wkr wkr = wkrs[i];
    +			Xof_orig_itm orig = wkr.Find_as_itm(ttl, list_idx, list_len); if (orig == Xof_orig_itm.Null) continue;
    +			if (orig.Insert_new()) this.Insert(orig.Repo(), ttl, orig.Ext_id(), orig.W(), orig.H(), orig.Redirect()); // NOTE: orig_page must be same as find_arg not orig.Page() else will not be found for next call; DATE:2015-04-14
    +			return orig;
    +		}
    +		return Xof_orig_itm.Null;
    +	}
    +	public void Find_by_list(Ordered_hash rv, List_adp itms, int exec_tid) {
    +		for (int i = 0; i < wkrs_len; i++) {
    +			Xof_orig_wkr wkr = wkrs[i];
    +			wkr.Find_by_list(rv, itms);
    +		}
    +		int len = itms.Count();
    +		for (int i = 0; i < len; i++) {
    +			try {
    +				Xof_fsdb_itm fsdb = (Xof_fsdb_itm)itms.Get_at(i);
    +				fsdb.Orig_exists_n_();	// default to status = missing
    +				Xof_orig_itm orig = (Xof_orig_itm)rv.Get_by(fsdb.Lnki_ttl()); if (orig == Xof_orig_itm.Null) continue;
    +				if (orig.Insert_new()) this.Insert(orig.Repo(), fsdb.Lnki_ttl(), orig.Ext_id(), orig.W(), orig.H(), orig.Redirect());	// NOTE: orig_page must be same as find_arg not orig.Page() else will not be found for next call; DATE:2015-04-14
    +				Xof_file_wkr.Eval_orig(orig, fsdb, url_bldr, repo_mgr, img_size);
    +				if (!Io_mgr.Instance.ExistsFil(fsdb.Html_view_url()))
    +					fsdb.File_exists_n_();
    +			} catch (Exception e) {
    +				Xoa_app_.Usr_dlg().Warn_many("", "", "orig: exc=~{0}", Err_.Message_gplx_full(e));
    +			}
    +		}
    +	}
    +	public void Insert(byte repo, byte[] page, int ext, int w, int h, byte[] redirect) {
    +		for (int i = 0; i < wkrs_len; i++) {
    +			Xof_orig_wkr wkr = wkrs[i];
    +			if (wkr.Add_orig(repo, page, ext, w, h, redirect)) break;
    +		}			
    +	}
    +	private void		Wkrs__clear() {wkrs = Xof_orig_wkr_.Ary_empty; wkrs_len = 0;}
    +	public void			Wkrs__add(Xof_orig_wkr... v) {
    +		wkrs = (Xof_orig_wkr[])Array_.Resize_add(wkrs, v);
    +		wkrs_len += v.length;
    +	}
    +	public void			Wkrs__set(Xof_orig_wkr... v) {
    +		wkrs = v;
    +		wkrs_len = v.length;
    +	}
    +	public void			Wkrs__del(byte tid) {
    +		List_adp list = List_adp_.New();
    +		for (int i = 0; i < wkrs_len; ++i) {
    +			Xof_orig_wkr wkr = wkrs[i];
    +			if (wkr.Tid() == tid) continue;	// do not add deleted wkr
    +			list.Add(wkr);
    +		}
    +		wkrs = (Xof_orig_wkr[])list.To_ary_and_clear(Xof_orig_wkr.class);
    +		wkrs_len = wkrs.length;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_tbl.java b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_tbl.java
    index a27517de8..3193834c1 100644
    --- a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_tbl.java
    +++ b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_tbl.java
    @@ -13,3 +13,100 @@ The terms of each license can be found in the source code repository:
     GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
     Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
     */
    +package gplx.xowa.files.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.primitives.*;
    +import gplx.dbs.*; import gplx.dbs.utls.*; 
    +import gplx.xowa.files.fsdb.*; import gplx.xowa.files.repos.*;
    +public class Xof_orig_tbl implements Db_tbl {
    +	public final    Dbmeta_fld_list flds = new Dbmeta_fld_list();
    +	public final    String fld_repo, fld_ttl, fld_status, fld_ext, fld_w, fld_h, fld_redirect;
    +	public final    Db_conn conn; private final    Xof_orig_tbl__in_wkr select_in_wkr = new Xof_orig_tbl__in_wkr();
    +	public Db_conn Conn() {return conn;}
    +	public Xof_orig_tbl(Db_conn conn, boolean schema_is_1) {
    +		this.conn = conn;
    +		String fld_status_name = "orig_status";
    +		if (schema_is_1)		{tbl_name = "wiki_orig"; fld_status_name = "status";}
    +		else					{tbl_name = "orig_reg";}
    +		fld_ttl					= flds.Add_str("orig_ttl", 1024);
    +		fld_repo				= flds.Add_byte("orig_repo");
    +		fld_status				= flds.Add_byte(fld_status_name);
    +		fld_ext					= flds.Add_int("orig_ext");
    +		fld_w					= flds.Add_int("orig_w");
    +		fld_h					= flds.Add_int("orig_h");
    +		fld_redirect			= flds.Add_str("orig_redirect", 1024);
    +		select_in_wkr.Ctor(this, tbl_name, flds, fld_ttl);
    +		conn.Rls_reg(this);
    +	}
    +	public String Tbl_name() {return tbl_name;} private final    String tbl_name;
    +	public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "main", fld_ttl)));}
    +	public void Select_by_list(Ordered_hash rv, List_adp itms) {select_in_wkr.Init(rv, itms).Select_in(Cancelable_.Never, conn, 0, itms.Count());}
    +	public Xof_orig_itm Select_itm(byte[] ttl) {
    +		Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_ttl).Clear().Crt_bry_as_str(fld_ttl, ttl).Exec_select__rls_auto();
    +		try {return rdr.Move_next() ? Load_by_rdr(rdr) : Xof_orig_itm.Null;}
    +		finally {rdr.Rls();}
    +	}
    +	public boolean Exists__repo_ttl(byte repo, byte[] ttl) {
    +		Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_repo, fld_ttl).Crt_byte(fld_repo, repo).Crt_bry_as_str(fld_ttl, ttl).Exec_select__rls_auto();
    +		try {return rdr.Move_next();}
    +		finally {rdr.Rls();}
    +	}
    +	public void Insert(byte repo, byte[] ttl, int ext, int w, int h, byte[] redirect) {
    +		Db_stmt stmt = conn.Stmt_insert(tbl_name, flds);
    +		this.Insert(stmt, repo, ttl, ext, w, h, redirect);
    +	}
    +	public void Insert(Db_stmt stmt, byte repo, byte[] ttl, int ext, int w, int h, byte[] redirect) {
    +		stmt.Clear()
    +			.Val_bry_as_str(fld_ttl, ttl).Val_byte(fld_repo, repo).Val_byte(fld_status, Status_found)
    +			.Val_int(fld_ext, ext).Val_int(fld_w, w).Val_int(fld_h, h).Val_bry_as_str(fld_redirect, redirect)
    +		.Exec_insert();
    +	}
    +	public void Update(byte repo, byte[] ttl, int ext, int w, int h, byte[] redirect) {
    +		Db_stmt stmt = conn.Stmt_update_exclude(tbl_name, flds, String_.Ary(fld_repo, fld_ttl));
    +		stmt.Clear()
    +			.Val_byte(fld_status, Status_found)
    +			.Val_int(fld_ext, ext).Val_int(fld_w, w).Val_int(fld_h, h).Val_bry_as_str(fld_redirect, redirect)
    +			.Crt_byte(fld_repo, repo).Crt_bry_as_str(fld_ttl, ttl)
    +		.Exec_update();
    +	}
    +	public Xof_orig_itm Load_by_rdr(Db_rdr rdr) {
    +		byte repo = rdr.Read_byte(fld_repo);
    +		Xof_orig_itm rv = new Xof_orig_itm
    +		( repo
    +		, rdr.Read_bry_by_str(fld_ttl)
    +		, rdr.Read_int(fld_ext)
    +		, rdr.Read_int(fld_w)
    +		, rdr.Read_int(fld_h)
    +		, rdr.Read_bry_by_str(fld_redirect)
    +		);
    +		return rv.W() == Xof_img_size.Null ? Xof_orig_itm.Null : rv;
    +	}
    +	private static final byte Status_found = 1;
    +	public void Rls() {}
    +}
    +class Xof_orig_tbl__in_wkr extends Db_in_wkr__base {
    +	private Xof_orig_tbl tbl; private String tbl_name; private Dbmeta_fld_list flds; private String fld_ttl;
    +	private List_adp itms; private Ordered_hash rv;		
    +	public void Ctor(Xof_orig_tbl tbl, String tbl_name, Dbmeta_fld_list flds, String fld_ttl) {
    +		this.tbl = tbl; this.tbl_name = tbl_name; this.flds = flds; this.fld_ttl = fld_ttl;
    +	}
    +	public Xof_orig_tbl__in_wkr Init(Ordered_hash rv, List_adp itms) {this.itms = itms; this.rv = rv; return this;}
    +	@Override protected Db_qry Make_qry(int bgn, int end) {
    +		Object[] part_ary = In_ary(end - bgn);			
    +		return Db_qry_.select_cols_(tbl_name, Db_crt_.New_in(fld_ttl, part_ary), flds.To_str_ary());
    +	}
    +	@Override protected void Fill_stmt(Db_stmt stmt, int bgn, int end) {
    +		for (int i = bgn; i < end; i++) {
    +			Xof_fsdb_itm fsdb_itm = (Xof_fsdb_itm)itms.Get_at(i);
    +			stmt.Crt_bry_as_str(fld_ttl, fsdb_itm.Lnki_ttl());
    +		}
    +	}
    +	@Override protected void Read_data(Cancelable cancelable, Db_rdr rdr) {
    +		while (rdr.Move_next()) {
    +			if (cancelable.Canceled()) return;
    +			Xof_orig_itm itm = tbl.Load_by_rdr(rdr);
    +			if (itm == Xof_orig_itm.Null) continue;
    +			byte[] itm_ttl = itm.Ttl();
    +			rv.Add_if_dupe_use_1st(itm_ttl, itm);	// guard against dupes; fails on en.w:Paris; DATE:2015-03-08
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_tbl_tst.java b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_tbl_tst.java
    index a27517de8..83f946284 100644
    --- a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_tbl_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_tbl_tst.java
    @@ -13,3 +13,62 @@ 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.files.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import org.junit.*;
    +import gplx.dbs.*; import gplx.xowa.*;
    +import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import gplx.xowa.parsers.lnkis.*;
    +public class Xof_orig_tbl_tst {
    +	@Before public void init() {fxt.Clear();} private Xof_orig_tbl_fxt fxt = new Xof_orig_tbl_fxt();
    +	@Test   public void Select_in() {
    +		Xof_orig_itm itm_1 = fxt.Exec_insert("A.png", 220, 330);
    +		fxt.Exec_insert("B.png", 220, 330);
    +		Xof_orig_itm itm_3 = fxt.Exec_insert("C.png", 220, 330);
    +		fxt.Test_select_in(String_.Ary("A.png", "C.png"), itm_1, itm_3);
    +	}
    +}
    +class Xof_orig_tbl_fxt {
    +	private Xof_orig_tbl tbl;
    +	public void Clear() {
    +		Io_url test_url = Io_url_.mem_fil_("mem/file/en.wikipedia.org/file/orig_regy");
    +		Db_conn_bldr.Instance.Reg_default_mem();
    +		Db_conn conn = Db_conn_bldr.Instance.New(test_url);
    +		tbl = new Xof_orig_tbl(conn, Bool_.Y);
    +		tbl.Create_tbl();
    +	}
    +	public Xof_orig_itm Exec_insert(String ttl, int w, int h) {
    +		byte[] ttl_bry = Bry_.new_u8(ttl);
    +		Xof_orig_itm rv = new Xof_orig_itm(Xof_orig_itm.Repo_comm, ttl_bry, Xof_ext_.new_by_ttl_(ttl_bry).Id(), w, h, Bry_.Empty);
    +		tbl.Insert(rv.Repo(), rv.Ttl(), rv.Ext_id(), rv.W(), rv.H(), rv.Redirect());
    +		return rv;
    +	}
    +	public void Test_select_in(String[] itms, Xof_orig_itm... expd) {
    +		Ordered_hash rv = Ordered_hash_.New();
    +		List_adp list = List_adp_.New();
    +		int itms_len = itms.length;
    +		for (int i = 0; i < itms_len; ++i) {
    +			String itm = itms[i];
    +			Xof_fsdb_itm fsdb_itm = new Xof_fsdb_itm();
    +			fsdb_itm.Init_at_lnki(Xof_exec_tid.Tid_wiki_page, Bry_.new_a7("en.w"), Bry_.new_u8(itm), Xop_lnki_type.Id_none, Xof_img_size.Upright_null, Xof_img_size.Null, Xof_img_size.Null, Xof_lnki_time.Null, Xof_lnki_page.Null, Xof_patch_upright_tid_.Tid_all);
    +			list.Add(fsdb_itm);
    +		}
    +		tbl.Select_by_list(rv, list);
    +		Tfds.Eq_str_lines(To_str_ary(expd), To_str_ary((Xof_orig_itm[])rv.To_ary(Xof_orig_itm.class)));
    +	}
    +	private static String To_str_ary(Xof_orig_itm... ary) {
    +		Bry_bfr bfr = Bry_bfr_.Reset(255);
    +		int len = ary.length;
    +		for (int i = 0; i < len; ++i) {
    +			Xof_orig_itm itm = ary[i];
    +			bfr	.Add_byte(itm.Repo()).Add_byte_pipe()
    +				.Add(itm.Ttl()).Add_byte_pipe()
    +				.Add_int_variable(itm.Ext_id()).Add_byte_pipe()
    +				.Add_int_variable(itm.W()).Add_byte_pipe()
    +				.Add_int_variable(itm.H()).Add_byte_pipe()
    +				.Add(itm.Redirect()).Add_byte_pipe()
    +				;
    +			bfr.Add_byte_nl();
    +		}
    +		return bfr.To_str_and_clear();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr.java b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr.java
    index a27517de8..790c060fb 100644
    --- a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr.java
    +++ b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr.java
    @@ -13,3 +13,13 @@ 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.files.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.xowa.files.fsdb.*;
    +public interface Xof_orig_wkr {
    +	byte			Tid();
    +	Xof_orig_itm	Find_as_itm(byte[] ttl, int list_idx, int list_len);
    +	void			Find_by_list(Ordered_hash rv, List_adp itms);
    +	boolean			Add_orig(byte repo, byte[] page, int ext_id, int w, int h, byte[] redirect);
    +	void			Db_txn_save();
    +	void			Db_rls();
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr_.java b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr_.java
    index a27517de8..592ee9284 100644
    --- a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr_.java
    +++ b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr_.java
    @@ -13,3 +13,27 @@ 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.files.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.xowa.apps.wms.apis.*; import gplx.xowa.files.fsdb.*;
    +public class Xof_orig_wkr_ {
    +	public static final    Xof_orig_wkr[] Ary_empty = new Xof_orig_wkr[0];
    +	public static void Find_by_list(Xof_orig_wkr wkr, Ordered_hash rv, List_adp itms) {
    +		int len = itms.Count();
    +		for (int i = 0; i < len; ++i) {
    +			Xof_fsdb_itm fsdb = (Xof_fsdb_itm)itms.Get_at(i);
    +			byte[] fsdb_ttl = fsdb.Lnki_ttl();
    +			if (rv.Has(fsdb_ttl)) continue;
    +			Xof_orig_itm orig = wkr.Find_as_itm(fsdb_ttl, i, len);
    +			if (orig == Xof_orig_itm.Null) continue;
    +			rv.Add(fsdb_ttl, orig);
    +		}
    +	}
    +	public static final byte
    +	  Tid_xowa_db			= 1
    +	, Tid_wmf_api			= 2
    +	, Tid_xowa_meta			= 3
    +	, Tid_xowa_img_links	= 4
    +	, Tid_mock				= 5
    +	, Tid_fs_root			= 6
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr__mock.java b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr__mock.java
    index a27517de8..45fb9574b 100644
    --- a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr__mock.java
    +++ b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr__mock.java
    @@ -13,3 +13,22 @@ 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.files.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.flds.*;
    +import gplx.dbs.*; import gplx.xowa.files.fsdb.*;
    +import gplx.xowa.wikis.tdbs.metas.*;
    +public class Xof_orig_wkr__mock implements Xof_orig_wkr {
    +	private final    Ordered_hash hash = Ordered_hash_.New_bry();
    +	public byte				Tid() {return Xof_orig_wkr_.Tid_mock;}
    +	public void				Find_by_list(Ordered_hash rv, List_adp itms) {Xof_orig_wkr_.Find_by_list(this, rv, itms);}
    +	public Xof_orig_itm		Find_as_itm(byte[] ttl, int list_idx, int list_len) {
    +		return (Xof_orig_itm)hash.Get_by(ttl); // new Xof_orig_itm((byte)meta_itm.Vrtl_repo(), ttl, Xof_ext_.new_by_ttl_(ttl).Id(), meta_itm.Orig_w(), meta_itm.Orig_h(), meta_itm.Ptr_ttl());
    +	}
    +	public boolean				Add_orig(byte repo, byte[] page, int ext_id, int w, int h, byte[] redirect) {
    +		Xof_orig_itm itm = new Xof_orig_itm(repo, page, ext_id, w, h, redirect);
    +		hash.Add(page, itm);
    +		return false;
    +	}
    +	public void				Db_txn_save() {}
    +	public void				Db_rls() {}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr__orig_db.java b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr__orig_db.java
    index a27517de8..b7fe82f71 100644
    --- a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr__orig_db.java
    +++ b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr__orig_db.java
    @@ -13,3 +13,23 @@ 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.files.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.dbs.*;
    +public class Xof_orig_wkr__orig_db implements Xof_orig_wkr {
    +	private final    boolean addable;
    +	public Xof_orig_wkr__orig_db(Xof_orig_tbl tbl, boolean addable) {this.tbl = tbl; this.addable = addable;}
    +	public byte				Tid() {return Xof_orig_wkr_.Tid_xowa_db;}
    +	public Xof_orig_tbl		Tbl() {return tbl;} private final    Xof_orig_tbl tbl;
    +	public void				Find_by_list(Ordered_hash rv, List_adp itms) {tbl.Select_by_list(rv, itms);}
    +	public Xof_orig_itm		Find_as_itm(byte[] ttl, int list_idx, int list_len) {return tbl.Select_itm(ttl);}
    +	public boolean				Add_orig(byte repo, byte[] page, int ext_id, int w, int h, byte[] redirect) {
    +		if (!addable) return false;
    +		if (tbl.Exists__repo_ttl(repo, page))
    +			tbl.Update(repo, page, ext_id, w, h, redirect);
    +		else
    +			tbl.Insert(repo, page, ext_id, w, h, redirect);
    +		return true;
    +	}
    +	public void				Db_txn_save() {tbl.Conn().Txn_end();}
    +	public void				Db_rls() {tbl.Conn().Rls_conn();}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr__wmf_api.java b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr__wmf_api.java
    index a27517de8..0fb479676 100644
    --- a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr__wmf_api.java
    +++ b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr__wmf_api.java
    @@ -13,3 +13,45 @@ 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.files.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.xowa.files.repos.*; import gplx.xowa.files.fsdb.*; import gplx.xowa.apps.wms.apis.*; import gplx.xowa.files.downloads.*;
    +import gplx.xowa.apps.wms.apis.origs.*;
    +public class Xof_orig_wkr__wmf_api implements Xof_orig_wkr {
    +	private final    Xoapi_orig_base orig_api; private final    Xof_download_wkr download_wkr; private final    Xow_repo_mgr repo_mgr; private final    byte[] wiki_domain;
    +	private final    Xoapi_orig_rslts api_rv = new Xoapi_orig_rslts();		
    +	public Xof_orig_wkr__wmf_api(Xoapi_orig_base orig_api, Xof_download_wkr download_wkr, Xow_repo_mgr repo_mgr, byte[] wiki_domain) {
    +		this.orig_api = orig_api; this.download_wkr = download_wkr; this.repo_mgr = repo_mgr; this.wiki_domain = wiki_domain;
    +	}
    +	public byte Tid() {return Xof_orig_wkr_.Tid_wmf_api;}
    +	public void			Find_by_list(Ordered_hash rv, List_adp itms) {Xof_orig_wkr_.Find_by_list(this, rv, itms);}
    +	public Xof_orig_itm Find_as_itm(byte[] ttl, int list_idx, int list_len) {
    +		if (!gplx.core.ios.IoEngine_system.Web_access_enabled) return Xof_orig_itm.Null;	// don't check api if download disabled, else prog messages; DATE:2015-06-17
    +
    +		// make call to api
    +		Xoa_app_.Usr_dlg().Prog_none("", "", Prog_msg(list_idx, list_len, ttl));
    +		boolean found = orig_api.Api_query_size(api_rv, download_wkr, repo_mgr, ttl, Xof_img_size.Null, Xof_img_size.Null);	// pass in null size to look for orig; DATE:2015-02-10
    +		if (!found) return Xof_orig_itm.Null;	// ttl not found by api; return
    +
    +		// deserialize values and return
    +		byte api_repo = Bry_.Eq(api_rv.Orig_wiki(), wiki_domain) ? Xof_orig_itm.Repo_wiki : Xof_orig_itm.Repo_comm;
    +		byte[] api_page = api_rv.Orig_page();
    +		int api_w = api_rv.Orig_w(), api_h = api_rv.Orig_h();
    +		Xof_ext api_ext = Xof_ext_.new_by_ttl_(api_page); api_ext = Ext__handle_ogg(api_ext, api_w, api_h);
    +		byte[] api_redirect = Bry_.Eq(api_page, ttl) ? Bry_.Empty : api_page;	// ttl is different; must be redirect
    +		Xof_orig_itm rv = new Xof_orig_itm(api_repo, api_page, api_ext.Id(), api_w, api_h, api_redirect);
    +		rv.Insert_new_y_();
    +		return rv;
    +	}
    +	public boolean Add_orig(byte repo, byte[] page, int ext_id, int w, int h, byte[] redirect) {return false;}
    +	public void				Db_txn_save() {}
    +	public void				Db_rls() {}
    +	public static Xof_ext Ext__handle_ogg(Xof_ext ext, int w, int h) {
    +		if (!ext.Id_is_ogg()) return ext;
    +		boolean is_audio = w == 0 && h == 0; // wmf returns back w/h of 0 if audio; non-0 if video; DATE:2013-11-11
    +		int actl_ext_id = is_audio ? Xof_ext_.Id_oga : Xof_ext_.Id_ogv;
    +		return Xof_ext_.new_by_id_(actl_ext_id);
    +	}
    +	private static String Prog_msg(int idx, int len, byte[] ttl) {
    +		return String_.Format("downloading file {0} of {1}: {2}", idx + List_adp_.Base1, len, ttl);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr__xo_meta.java b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr__xo_meta.java
    index a27517de8..82e9e2016 100644
    --- a/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr__xo_meta.java
    +++ b/400_xowa/src/gplx/xowa/files/origs/Xof_orig_wkr__xo_meta.java
    @@ -13,3 +13,36 @@ 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.files.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.flds.*;
    +import gplx.dbs.*; import gplx.xowa.files.fsdb.*;
    +import gplx.xowa.wikis.tdbs.metas.*;
    +public class Xof_orig_wkr__xo_meta implements Xof_orig_wkr {
    +	private final    Io_url wiki_meta_dir; private final    byte dir_spr_byte;  private final    Bry_bfr url_bfr = Bry_bfr_.New_w_size(255);
    +	private final    Gfo_fld_rdr meta_rdr = Gfo_fld_rdr.xowa_(); private final    Xof_meta_thumb_parser parser = new Xof_meta_thumb_parser();
    +	public Xof_orig_wkr__xo_meta(Io_url wiki_meta_dir) {this.wiki_meta_dir = wiki_meta_dir; this.dir_spr_byte = wiki_meta_dir.Info().DirSpr_byte();}
    +	public byte				Tid() {return Xof_orig_wkr_.Tid_xowa_meta;}
    +	public void				Find_by_list(Ordered_hash rv, List_adp itms) {Xof_orig_wkr_.Find_by_list(this, rv, itms);}
    +	public Xof_orig_itm		Find_as_itm(byte[] ttl, int list_idx, int list_len) {
    +		byte[] md5 = Xof_file_wkr_.Md5(ttl);
    +		url_bfr.Add(wiki_meta_dir.RawBry())					// /xowa/file/#meta/simple.wikipedia.org/
    +			.Add_byte(md5[0]).Add_byte(dir_spr_byte)		// 0/
    +			.Add_byte(md5[1]).Add_byte(dir_spr_byte)		// 6/
    +			.Add_mid(md5, 0, 3).Add_str_a7(".csv")			// 061.csv
    +			;
    +		Io_url meta_url = Io_url_.new_fil_(url_bfr.To_str_and_clear());
    +		byte[] meta_src = Io_mgr.Instance.LoadFilBry(meta_url); if (meta_src.length == 0) return Xof_orig_itm.Null;
    +		meta_rdr.Ini(meta_src, 0);
    +		Xof_meta_fil meta_fil = new Xof_meta_fil(null, md5);	// NOTE: need to register file before loading it; defect wherein 2 files with same hash prefix would skip one b/c Loaded file was not registered; EX.WS: en.wikiquote.org/The Hitchhiker's Guide to the Galaxy; NMMP_dolphin_with_locator.jpeg, da6f95736ed249f371f30bf5f1205fbd; Hoags_object.jpg, daed4a54e48e4266bd2f2763b7c4018c
    +		meta_fil.Load(meta_rdr, parser);
    +		Xof_meta_itm meta_itm = meta_fil.Get_or_null(ttl); if (meta_itm == null) return Xof_orig_itm.Null;
    +		Xof_orig_itm rv = new Xof_orig_itm((byte)meta_itm.Vrtl_repo(), ttl, Xof_ext_.new_by_ttl_(ttl).Id(), meta_itm.Orig_w(), meta_itm.Orig_h(), meta_itm.Ptr_ttl());
    +		rv.Insert_new_y_();
    +		return rv;
    +	}
    +	public boolean				Add_orig(byte repo, byte[] page, int ext_id, int w, int h, byte[] redirect) {
    +		return false;
    +	}
    +	public void				Db_txn_save() {}
    +	public void				Db_rls() {}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/origs/Xof_wiki_finder.java b/400_xowa/src/gplx/xowa/files/origs/Xof_wiki_finder.java
    index a27517de8..75ab30748 100644
    --- a/400_xowa/src/gplx/xowa/files/origs/Xof_wiki_finder.java
    +++ b/400_xowa/src/gplx/xowa/files/origs/Xof_wiki_finder.java
    @@ -13,3 +13,57 @@ 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.files.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.xowa.wikis.data.tbls.*;
    +import gplx.xowa.wikis.nss.*;
    +class Xof_wiki_finder {	// UNUSED
    +	private Xowe_wiki wiki_0, wiki_1;
    +	private Xowd_page_itm db_page = new Xowd_page_itm(); 
    +	public Xof_wiki_finder(Xowe_wiki wiki_0, Xowe_wiki wiki_1) {
    +		this.wiki_0 = wiki_0; this.wiki_1 = wiki_1;
    +	}
    +	public Xoae_page Get_page(int ns, byte[] ttl_bry) {
    +		Xoae_page rv = Get_page__by_wiki(wiki_0, ns, ttl_bry);
    +		if (rv.Db().Page().Exists_n())
    +			rv = Get_page__by_wiki(wiki_1, ns, ttl_bry);
    +		return rv;
    +	}
    +	private Xoae_page Get_page__by_wiki(Xowe_wiki wiki, int ns_id, byte[] ttl_bry) {
    +		Xoa_ttl ttl = Xoa_ttl.Parse(wiki, ns_id, ttl_bry) ;
    +		Xoa_url url = Xoa_url.New(wiki, ttl);
    +		return wiki.Data_mgr().Load_page_and_parse(url, ttl);
    +	}
    +	private int qry_count, qry_count_max = 1000;
    +	public boolean Find_page(Xof_wiki_finder_itm itm, int ns_id, byte[] ttl_bry) {
    +		Xowe_wiki wiki = null;
    +		if (Find_page__by_wiki(db_page, wiki_0, ns_id, ttl_bry)) {
    +			wiki = wiki_0;
    +			itm.Orig_repo_id_(Byte_.Zero);
    +		}
    +		else {
    +			if (Find_page__by_wiki(db_page, wiki_1, ns_id, ttl_bry)) {
    +				wiki = wiki_1;
    +				itm.Orig_repo_id_((byte)1);
    +			}
    +			else
    +				return false;
    +		}
    +		itm.Orig_ttl_(ttl_bry);
    +		if (db_page.Redirected()) {
    +			Xoae_page page = Get_page__by_wiki(wiki, ns_id, ttl_bry);
    +			Xoa_ttl redirect_ttl = wiki.Redirect_mgr().Extract_redirect_loop(page.Db().Text().Text_bry());		
    +			itm.Orig_redirect_(redirect_ttl);			
    +			++qry_count;
    +			if (qry_count >= qry_count_max) {
    +				wiki.Appe().Reset_all();
    +				qry_count = 0;
    +			}
    +		}
    +		return true;
    +	}
    +	private boolean Find_page__by_wiki(Xowd_page_itm db_page, Xowe_wiki wiki, int ns_id, byte[] ttl_bry) {
    +		Xow_ns ns = wiki.Ns_mgr().Ids_get_or_null(ns_id);
    +		wiki.Db_mgr().Load_mgr().Load_page(db_page, ns);
    +		return db_page.Exists();
    +	}	
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/origs/Xof_wiki_finder_itm.java b/400_xowa/src/gplx/xowa/files/origs/Xof_wiki_finder_itm.java
    index a27517de8..47832cf4b 100644
    --- a/400_xowa/src/gplx/xowa/files/origs/Xof_wiki_finder_itm.java
    +++ b/400_xowa/src/gplx/xowa/files/origs/Xof_wiki_finder_itm.java
    @@ -13,3 +13,14 @@ 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.files.origs; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +class Xof_wiki_finder_itm {	// UNUSED
    +//	public boolean Missing() {return missing;} public Xof_wiki_finder_itm Missing_(boolean v) {missing = v; return this;} private boolean missing;
    +	public byte Orig_repo_id() {return orig_repo_id;} public Xof_wiki_finder_itm Orig_repo_id_(byte v) {orig_repo_id = v; return this;} private byte orig_repo_id;
    +	public byte[] Orig_ttl() {return orig_ttl;} public Xof_wiki_finder_itm Orig_ttl_(byte[] v) {orig_ttl = v; return this;} private byte[] orig_ttl;
    +	public Xowe_wiki Orig_wiki() {return orig_wiki;} public Xof_wiki_finder_itm Orig_wiki_(Xowe_wiki v) {orig_wiki = v; return this;} private Xowe_wiki orig_wiki;
    +	public Xoa_ttl Orig_redirect() {return orig_redirect;} public Xof_wiki_finder_itm Orig_redirect_(Xoa_ttl v) {orig_redirect = v; return this;} private Xoa_ttl orig_redirect;
    +//	public int Orig_w() {return orig_w;} private int orig_w;
    +//	public int Orig_h() {return orig_h;} private int orig_h;
    +//	public void Orig_size_(int w, int h)  {this.orig_w = w; this.orig_h = h;}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/repos/Xof_itm_ttl_.java b/400_xowa/src/gplx/xowa/files/repos/Xof_itm_ttl_.java
    index a27517de8..24c57c6ca 100644
    --- a/400_xowa/src/gplx/xowa/files/repos/Xof_itm_ttl_.java
    +++ b/400_xowa/src/gplx/xowa/files/repos/Xof_itm_ttl_.java
    @@ -13,3 +13,30 @@ 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.files.repos; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +public class Xof_itm_ttl_ {
    +	public static byte[] Remove_invalid(Bry_bfr bfr, byte[] ttl) {
    +		int ttl_len = ttl.length;
    +		for (int i = 0; i < ttl_len; i++) {
    +			byte b = ttl[i];
    +			if (gplx.core.envs.Op_sys_.Wnt_invalid_char(b)) b = Byte_ascii.Underline;
    +			bfr.Add_byte(b);
    +		}
    +		return bfr.To_bry_and_clear();
    +	}
    +	public static byte[] Shorten(Bry_bfr bfr, byte[] ttl, int ttl_max, byte[] md5, byte[] ext_bry) {
    +		byte[] rv = ttl;
    +		int exceed_len = rv.length - ttl_max;               // calc exceed_len;        EX: 21 = 201 - 180
    +		if (exceed_len > 0) {
    +			if (ttl_max > 33) {
    +				bfr.Add_mid(rv, 0, ttl_max - 33);           // add truncated title;    33=_.length + md5.length; EX: 180 - 33
    +			    bfr.Add_byte(Byte_ascii.Underline);         // add underline;          EX: "_"
    +			}
    +			bfr.Add(md5);                                   // add md5;                EX: "abcdefghijklmnopqrstuvwxyz0123456"
    +			bfr.Add_byte(Byte_ascii.Dot);                   // add dot;                EX: "."
    +			bfr.Add(ext_bry);                               // add ext;                EX: ".png"
    +			rv = bfr.To_bry_and_clear();
    +		}
    +		return rv;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/repos/Xof_repo_itm.java b/400_xowa/src/gplx/xowa/files/repos/Xof_repo_itm.java
    index a27517de8..ffbfdc04e 100644
    --- a/400_xowa/src/gplx/xowa/files/repos/Xof_repo_itm.java
    +++ b/400_xowa/src/gplx/xowa/files/repos/Xof_repo_itm.java
    @@ -13,3 +13,107 @@ 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.files.repos; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.consoles.*;
    +import gplx.xowa.apps.fsys.*; import gplx.xowa.files.exts.*; import gplx.xowa.files.imgs.*;
    +import gplx.xowa.wikis.domains.*;
    +public class Xof_repo_itm implements Gfo_invk {
    +	private final    Xoa_fsys_mgr app_fsys; private final    Xof_rule_mgr ext_rule_mgr;
    +	public Xof_repo_itm(byte[] key, Xoa_fsys_mgr app_fsys, Xof_rule_mgr ext_rule_mgr, byte[] wiki_domain) {
    +		this.key = key; this.app_fsys = app_fsys; this.ext_rule_mgr = ext_rule_mgr;
    +		Wiki_domain_(wiki_domain);
    +	}
    +	public byte[]			Key()				{return key;}			private final    byte[] key;			// EX: "src_http_commons"
    +	public byte				Tid()				{return tid;}			private byte tid;						// EX: Xof_repo_tid_.Tid__remote
    +	public byte[]			Wiki_domain()		{return wiki_domain;}	private byte[] wiki_domain;				// EX: "commons.wikimedia.org"
    +	public byte[]			Wiki_abrv_xo()		{return wiki_abrv_xo;}	private byte[] wiki_abrv_xo;			// EX: "c"
    +	public byte				Dir_spr()			{return dir_spr;}		private byte dir_spr;					// EX: "/"
    +	public int				Dir_depth()			{return dir_depth;}		private int dir_depth = 4;				// EX: "/1/2/3/4" vs "/1/2"
    +	public byte[]			Root_bry()			{return root_bry;}		private byte[] root_bry;				// EX:
    +	public byte[]			Root_http()			{return root_http;}		private byte[] root_http = Bry_.Empty;	// EX: 
    +	public String			Root_str()			{return root_str;}		private String root_str;
    +	public boolean			Fsys_is_wnt()		{return fsys_is_wnt;}	private boolean fsys_is_wnt;
    +	public boolean			Shorten_ttl()		{return shorten_ttl;}	private boolean shorten_ttl = true;
    +	public boolean			Wmf_fsys()			{return wmf_fsys;}		private boolean wmf_fsys;
    +	public boolean			Wmf_api()			{return wmf_api;}		private boolean wmf_api;
    +	public boolean			Tarball()			{return tarball;}		private boolean tarball;
    +	public byte[][]			Mode_names()		{return mode_names;}	private byte[][] mode_names = new byte[][] {Xof_img_mode_.Names_ary[0], Xof_img_mode_.Names_ary[1]};
    +	public Xof_rule_grp		Ext_rules()			{return ext_rules;}		private Xof_rule_grp ext_rules;
    +	public boolean			Primary()			{return primary;}		private boolean primary;
    +	public void             Url_max_len_(int v) {url_max_len = v;}      private int url_max_len = 250;
    +
    +	public Xof_repo_itm		Fsys_is_wnt_(boolean v) {fsys_is_wnt = v; return this;} 
    +	public Xof_repo_itm		Shorten_ttl_(boolean v) {shorten_ttl = v; return this;} 
    +	public Xof_repo_itm		Wmf_fsys_(boolean v) {wmf_fsys = v; return this;} 
    +	public Xof_repo_itm		Wmf_api_(boolean v) {wmf_api = v; return this;}
    +	public Xof_repo_itm		Tarball_(boolean v) {tarball = v; return this;} 
    +	public Xof_repo_itm		Ext_rules_(byte[] ext_rules_key) {ext_rules = ext_rule_mgr.Get_or_new(ext_rules_key); return this;}
    +	public Xof_repo_itm		Dir_depth_(int v) {dir_depth = v; return this;} 
    +	public Xof_repo_itm		Primary_(boolean v) {primary = v; return this;} 
    +
    +	public void				Wiki_domain_(byte[] v) {
    +		this.wiki_domain = v;
    +		Xow_domain_itm domain_itm = Xow_domain_itm_.parse(v);
    +		if (domain_itm == null) {
    +			Xoa_app_.Usr_dlg().Warn_many("", "", "repo:invalid domain; raw=~{0}", v);
    +			this.wiki_abrv_xo = Bry_.Empty;
    +		}
    +		else
    +			this.wiki_abrv_xo = Xow_abrv_xo_.To_bry(v, domain_itm.Lang_actl_key(), domain_itm.Domain_type());
    +	}
    +	public Xof_repo_itm Root_str_(String root_str) {
    +		this.wmf_fsys = String_.Has_at_bgn(root_str, "http") || String_.Has_at_bgn(root_str, "ftp");
    +		if (wmf_fsys) {
    +			this.root_bry = Bry_.new_u8(root_str);
    +			this.dir_spr = Byte_ascii.Slash;
    +			this.wmf_api = true;
    +		}
    +		else {
    +			Io_url root_url = Gfo_cmd_arg_itm_.Val_as_url__rel_url_or(root_str, Bool_.Y, app_fsys.File_dir(), Io_url_.new_dir_(root_str));
    +			this.root_bry = root_url.RawBry();
    +			this.dir_spr = root_url.Info().DirSpr_byte();
    +			this.root_http = root_url.To_http_file_bry();
    +		}
    +		this.root_str = root_str;
    +		return this;
    +	}
    +	public byte[] Gen_name_src(Bry_bfr tmp_bfr, byte[] name) {
    +		if (!fsys_is_wnt || wmf_fsys) return name;
    +		return Xof_itm_ttl_.Remove_invalid(tmp_bfr, name);
    +	}
    +	public byte[] Gen_name_trg(Bry_bfr tmp_bfr, byte[] bry, byte[] md5, Xof_ext ext) {
    +		byte[] rv = Gen_name_src(tmp_bfr, bry);
    +		if (shorten_ttl) {
    +			int max = url_max_len;
    +			if (fsys_is_wnt) {
    +				max = url_max_len          // 250 is approximate max of windows path
    +					- root_bry.length      // EX: "C:\xowa\"
    +					- 5                    // EX: "file\"
    +					- 6                    // EX: "thumb\"
    +					- dir_depth * 2        // EX: "0\1\2\3\"; *2  is for "\"
    +					- 17                   // 17 is length of "\1234px@1234-1234"
    +					- ext.Ext().length + 1 // EX: ".png"; +1 is for "."
    +					;
    +			}
    +			else {
    +				max = 180; // legacy val of max title; can probably be higher
    +			}
    +			return Xof_itm_ttl_.Shorten(tmp_bfr, rv, max, md5, ext.Ext());
    +		}
    +		else {
    +			return rv;
    +		}
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_owner))				throw Err_.new_unimplemented_w_msg("deprecated repo_itm.owner");
    +		else if	(ctx.Match(k, Invk_fsys_))				fsys_is_wnt = String_.Eq(m.ReadStr("v"), "wnt");
    +		else if	(ctx.Match(k, Invk_primary_))			primary = m.ReadYn("v");
    +		else if	(ctx.Match(k, Invk_ext_rules_))			Ext_rules_(m.ReadBry("v"));
    +		else if	(ctx.Match(k, Invk_wmf_api_))			wmf_api = m.ReadYn("v");
    +		else if	(ctx.Match(k, Invk_tarball_))			tarball = m.ReadYn("v");
    +		else if	(ctx.Match(k, Invk_tid_))				tid = Xof_repo_tid_.By_str(m.ReadStr("v"));
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	private static final String Invk_owner = "owner", Invk_fsys_ = "fsys_", Invk_ext_rules_ = "ext_rules_", Invk_primary_ = "primary_", Invk_wmf_api_ = "wmf_api_", Invk_tarball_ = "tarball_", Invk_tid_ = "tid_";
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/repos/Xof_repo_itm__tst.java b/400_xowa/src/gplx/xowa/files/repos/Xof_repo_itm__tst.java
    index a27517de8..1e5afbe6c 100644
    --- a/400_xowa/src/gplx/xowa/files/repos/Xof_repo_itm__tst.java
    +++ b/400_xowa/src/gplx/xowa/files/repos/Xof_repo_itm__tst.java
    @@ -13,3 +13,44 @@ 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.files.repos; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import org.junit.*; import gplx.core.tests.*;
    +import gplx.core.envs.*;
    +import gplx.xowa.apps.fsys.*;
    +import gplx.xowa.files.exts.*;
    +public class Xof_repo_itm__tst {
    +	private final    Xof_repo_itm__fxt fxt = new Xof_repo_itm__fxt();
    +	@Test 	public void Gen_name_trg__long() {
    +		// make a windows repo with a long directory name
    +		Xof_repo_itm repo = fxt.Make__repo(Op_sys.Wnt.Os_name(), "C:\\xowa0123456789", "commons.wikimedia.org");
    +
    +		// mark it as wnt repo and reduce max ttl_len from 160 to 99 for shorter test vars
    +		repo.Fsys_is_wnt_(true).Url_max_len_(99);
    +
    +		// short: full name
    +		fxt.Test__gen_name_trg(repo, "A.png", "A.png");
    +
    +		// long: truncated_ttl + md5
    +		fxt.Test__gen_name_trg(repo, "0123456789012345678901234567890123456789.png", "0123456789_965d037bf686058361510201be299ed4.png");
    +
    +		// extremely long; md5 only
    +		repo.Root_str_("C:\\xowa012345678901234567890123456789");
    +		fxt.Test__gen_name_trg(repo, "0123456789012345678901234567890123456789.png", "965d037bf686058361510201be299ed4.png");
    +	}
    +}
    +class Xof_repo_itm__fxt {
    +	private final    Bry_bfr tmp_bfr = Bry_bfr_.New();
    +	public Xof_repo_itm Make__repo(String plat_name, String root_dir_str, String wiki_domain) {
    +		String key = "test_repo";
    +		Io_url root_dir = Io_url_.new_dir_(root_dir_str);
    +		Xoa_fsys_mgr fsys_mgr = Xoa_fsys_mgr.New_by_plat(plat_name, root_dir);
    +		Xof_repo_itm repo = new Xof_repo_itm(Bry_.new_u8(key), fsys_mgr, new Xof_rule_mgr(), Bry_.new_u8(wiki_domain));
    +		repo.Root_str_(root_dir.Raw());
    +		return repo;
    +	}
    +	public void Test__gen_name_trg(Xof_repo_itm repo, String ttl_str, String expd) {
    +		byte[] ttl_bry = Bry_.new_u8(ttl_str);
    +		byte[] md5 = Xof_file_wkr_.Md5(ttl_bry);
    +		Gftest.Eq__str(expd, repo.Gen_name_trg(tmp_bfr, ttl_bry, md5, Xof_ext_.new_by_ttl_(ttl_bry)));
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/repos/Xof_repo_pair.java b/400_xowa/src/gplx/xowa/files/repos/Xof_repo_pair.java
    index a27517de8..e601e7d7c 100644
    --- a/400_xowa/src/gplx/xowa/files/repos/Xof_repo_pair.java
    +++ b/400_xowa/src/gplx/xowa/files/repos/Xof_repo_pair.java
    @@ -13,3 +13,20 @@ 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.files.repos; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +public class Xof_repo_pair implements Gfo_invk {
    +	public Xof_repo_pair(byte id, byte[] wiki_domain, Xof_repo_itm src, Xof_repo_itm trg) {
    +		this.id = id; this.wiki_domain = wiki_domain; this.src = src; this.trg = trg;
    +	}
    +	public byte				Id()			{return id;} private byte id;
    +	public byte[]			Wiki_domain()	{return wiki_domain;} private byte[] wiki_domain;
    +	public Xof_repo_itm		Src()			{return src;} private final    Xof_repo_itm src;
    +	public Xof_repo_itm		Trg()			{return trg;} private final    Xof_repo_itm trg;
    +	public void Wiki_domain_(byte[] v) {wiki_domain = v;}
    +
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_repo_id_))		id = m.ReadByte("v");
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}	private static final String Invk_repo_id_ = "repo_id_";
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/repos/Xof_repo_tid_.java b/400_xowa/src/gplx/xowa/files/repos/Xof_repo_tid_.java
    index a27517de8..834e7c192 100644
    --- a/400_xowa/src/gplx/xowa/files/repos/Xof_repo_tid_.java
    +++ b/400_xowa/src/gplx/xowa/files/repos/Xof_repo_tid_.java
    @@ -13,3 +13,22 @@ 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.files.repos; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +public class Xof_repo_tid_ {
    +	public static final byte
    +	  Tid__remote	= 0		// EX: "https://commons.wikimedia.org/wiki/File:A.png"
    +	, Tid__local	= 1		// EX: "https://en.wikipedia.org/wiki/File:A.png"
    +	, Tid__xtns		= 2		// EX: "https://en.wikipedia.org/w/extensions/ImageMap/desc-20.png?15600"
    +	, Tid__math		= 3		// EX: "https://wikimedia.org/api/rest_v1/media/math/render/svg/596f8baf206a81478afd4194b44138715dc1a05c"
    +	, Tid__null		= Byte_.Max_value_127
    +	;
    +	public static byte By_str(String v) {
    +		if		(String_.Eq(v, "self")) return Tid__local;
    +		else if	(String_.Eq(v, "comm")) return Tid__remote;
    +		else if	(String_.Eq(v, "math")) return Tid__math;
    +		else							throw Err_.new_unhandled_default(v);
    +	}
    +	public static final    byte[] 
    +	  Bry__math		= Bry_.new_a7("wikimedia.org/math")	// EX: https://wikimedia.org/api/rest_v1/media/math/render/svg/596f8baf206a81478afd4194b44138715dc1a05c
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/repos/Xofw_file_finder_rslt.java b/400_xowa/src/gplx/xowa/files/repos/Xofw_file_finder_rslt.java
    index a27517de8..46a634c38 100644
    --- a/400_xowa/src/gplx/xowa/files/repos/Xofw_file_finder_rslt.java
    +++ b/400_xowa/src/gplx/xowa/files/repos/Xofw_file_finder_rslt.java
    @@ -13,3 +13,17 @@ 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.files.repos; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.xowa.parsers.utils.*;
    +public class Xofw_file_finder_rslt {
    +	public byte[] Ttl() {return ttl;} private byte[] ttl;
    +	public byte[] Redirect() {return redirect;} private byte[] redirect;
    +	public int Repo_idx() {return repo_idx;} private int repo_idx;
    +	public byte[] Repo_wiki_key() {return repo_wiki_key;} private byte[] repo_wiki_key;
    +	public void Init(byte[] ttl) {
    +		this.ttl = ttl; redirect = Xop_redirect_mgr.Redirect_null_bry; repo_wiki_key = null; repo_idx = Byte_.Max_value_127;
    +	}
    +	public void Done(int repo_idx, byte[] repo_wiki_key, byte[] redirect) {
    +		this.repo_idx = repo_idx; this.repo_wiki_key = repo_wiki_key; this.redirect = redirect;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/repos/Xofw_wiki_finder.java b/400_xowa/src/gplx/xowa/files/repos/Xofw_wiki_finder.java
    index a27517de8..d3660dda9 100644
    --- a/400_xowa/src/gplx/xowa/files/repos/Xofw_wiki_finder.java
    +++ b/400_xowa/src/gplx/xowa/files/repos/Xofw_wiki_finder.java
    @@ -13,3 +13,9 @@ 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.files.repos; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.xowa.wikis.*; import gplx.xowa.files.*; import gplx.xowa.files.repos.*;
    +public interface Xofw_wiki_finder {
    +	void Find(List_adp repo_pairs, Xof_xfer_itm file);
    +	boolean Locate(Xofw_file_finder_rslt rv, List_adp repo_pairs, byte[] ttl_bry);
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/repos/Xofw_wiki_wkr_base.java b/400_xowa/src/gplx/xowa/files/repos/Xofw_wiki_wkr_base.java
    index a27517de8..13a85b921 100644
    --- a/400_xowa/src/gplx/xowa/files/repos/Xofw_wiki_wkr_base.java
    +++ b/400_xowa/src/gplx/xowa/files/repos/Xofw_wiki_wkr_base.java
    @@ -13,3 +13,58 @@ 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.files.repos; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.xowa.wikis.nss.*;
    +import gplx.xowa.wikis.*; import gplx.xowa.files.*; import gplx.xowa.files.repos.*; import gplx.xowa.wikis.data.tbls.*;
    +import gplx.xowa.parsers.utils.*;
    +public class Xofw_wiki_wkr_base implements Xofw_wiki_finder {
    +	public Xofw_wiki_wkr_base(Xowe_wiki wiki, Xoae_wiki_mgr wiki_mgr) {this.wiki = wiki; this.wiki_mgr = wiki_mgr;} private Xowe_wiki wiki; Xoae_wiki_mgr wiki_mgr;
    +	public void Find(List_adp repo_pairs, Xof_xfer_itm file) {
    +		byte[] ttl_bry = file.Lnki_ttl();
    +		int repo_pairs_len = repo_pairs.Count();
    +		for (int i = 0; i < repo_pairs_len; i++) {
    +			Xof_repo_pair repo_pair = (Xof_repo_pair)repo_pairs.Get_at(i);
    +			byte[] wiki_key = repo_pair.Src().Wiki_domain();
    +			if (repo_pair.Src().Wmf_api()) continue;
    +			Xowe_wiki repo_wiki = (Xowe_wiki)wiki_mgr.Get_by_or_null(wiki_key); if (repo_wiki == null) {continue;}
    +			Xoa_ttl ttl = Xoa_ttl.Parse(repo_wiki, ttl_bry);
    +			Xow_ns file_ns = repo_wiki.Ns_mgr().Ns_file();
    +			boolean found = repo_wiki.Db_mgr().Load_mgr().Load_by_ttl(tmp_db_page, file_ns, ttl.Page_db());
    +			if (!found) {continue;}
    +			byte[] redirect = Get_redirect(repo_wiki, file_ns, tmp_db_page);
    +			file.Orig_ttl_and_redirect_(ttl.Page_txt(), redirect);
    +			file.Orig_repo_id_(i);
    +			return;
    +		}
    +		file.Orig_repo_id_(-1);
    +	}
    +	public boolean Locate(Xofw_file_finder_rslt rv, List_adp repo_pairs, byte[] ttl_bry) {
    +		Xoa_ttl ttl = Xoa_ttl.Parse(wiki, ttl_bry);	// NOTE: parse(ttl_bry) should be the same across all wikis; i.e.: there should be no aliases/namespaces
    +		Xow_ns file_ns = wiki.Ns_mgr().Ns_file();		// NOTE: file_ns should also be the same across all wikis; being used for data_mgr.Parse below
    +		byte[] ttl_db_key = ttl.Page_db();
    +		rv.Init(ttl_db_key);
    +		int repo_pairs_len = repo_pairs.Count();
    +		for (int i = 0; i < repo_pairs_len; i++) {
    +			Xof_repo_pair repo_pair = (Xof_repo_pair)repo_pairs.Get_at(i);
    +			byte[] src_wiki_key = repo_pair.Src().Wiki_domain();
    +			Xowe_wiki src_wiki = (Xowe_wiki)wiki_mgr.Get_by_or_null(src_wiki_key); if (src_wiki == null) continue;	 // src_wiki defined as repo_pair in cfg, but it has not been downloaded; continue; EX: commons set up but not downloaded
    +			boolean found = src_wiki.Db_mgr().Load_mgr().Load_by_ttl(tmp_db_page, file_ns, ttl_db_key);
    +			if (!found) continue;				// ttl does not exist in src_wiki; continue; EX: file does not exist in commons, but exists in en_wiki
    +			byte[] redirect = Get_redirect(src_wiki, file_ns, tmp_db_page);
    +			rv.Done(i, src_wiki_key, redirect);
    +			return true;
    +		}
    +		return false;
    +	}
    +	private byte[] Get_redirect(Xowe_wiki wiki, Xow_ns file_ns, Xowd_page_itm db_page) {
    +		if (db_page.Redirected()) {
    +			wiki.Db_mgr().Load_mgr().Load_page(db_page, file_ns);
    +			byte[] src = db_page.Text();
    +			Xoa_ttl redirect_ttl = wiki.Redirect_mgr().Extract_redirect(src);
    +			return redirect_ttl == Xop_redirect_mgr.Redirect_null_ttl ? Xop_redirect_mgr.Redirect_null_bry : redirect_ttl.Page_db();
    +		}
    +		else
    +			return Xop_redirect_mgr.Redirect_null_bry;
    +	}
    +	private static final    Xowd_page_itm tmp_db_page = Xowd_page_itm.new_tmp();
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/repos/Xofw_wiki_wkr_mock.java b/400_xowa/src/gplx/xowa/files/repos/Xofw_wiki_wkr_mock.java
    index a27517de8..4da56190d 100644
    --- a/400_xowa/src/gplx/xowa/files/repos/Xofw_wiki_wkr_mock.java
    +++ b/400_xowa/src/gplx/xowa/files/repos/Xofw_wiki_wkr_mock.java
    @@ -13,3 +13,29 @@ 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.files.repos; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.xowa.files.*; import gplx.xowa.wikis.tdbs.metas.*;
    +public class Xofw_wiki_wkr_mock implements Xofw_wiki_finder {
    +	int repo_idx; byte[] repo_wiki_key;
    +	public Xofw_wiki_wkr_mock Clear_en_wiki() {return Clear(1, Bry_en_wiki);}
    +	public Xofw_wiki_wkr_mock Clear_commons() {return Clear(0, Bry_commons);}
    +	Xofw_wiki_wkr_mock Clear(int repo_idx, byte[] repo_wiki_key) {
    +		this.repo_idx = repo_idx; this.repo_wiki_key = repo_wiki_key;
    +		if_ttl = then_redirect = Bry_.Empty;
    +		return this;
    +	}	
    +	private static final byte[] Bry_commons = Bry_.new_a7("commons.wikimedia.org"), Bry_en_wiki = Bry_.new_a7("en.wikipedia.org");
    +	public Xofw_wiki_wkr_mock Repo_idx_(int v) {this.repo_idx = v; return this;}
    +	public Xofw_wiki_wkr_mock Redirect_(String if_ttl_str, String then_redirect_str) {this.if_ttl = Bry_.new_u8(if_ttl_str); this.then_redirect = Bry_.new_u8(then_redirect_str); return this;} private byte[] if_ttl, then_redirect;
    +	public void Find(List_adp repo_pairs, Xof_xfer_itm file) {
    +		byte[] ttl = file.Lnki_ttl();
    +		if (Bry_.Eq(ttl, if_ttl) && repo_idx != -1)		{file.Orig_ttl_and_redirect_(ttl, then_redirect);	file.Orig_repo_id_(repo_idx);}
    +		else											{file.Orig_ttl_and_redirect_(ttl, Bry_.Empty)	;	file.Orig_repo_id_(Xof_meta_itm.Repo_unknown);}	// FUTURE: this should be missing, but haven't implemented unknown yet
    +	}
    +	public boolean Locate(Xofw_file_finder_rslt rv, List_adp repo_pairs, byte[] ttl) {
    +		rv.Init(ttl);
    +		byte[] redirect = Bry_.Eq(ttl, if_ttl) ? then_redirect : null;
    +		rv.Done(repo_idx, repo_wiki_key, redirect);
    +		return true;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/repos/Xow_repo_mgr.java b/400_xowa/src/gplx/xowa/files/repos/Xow_repo_mgr.java
    index a27517de8..9d315a661 100644
    --- a/400_xowa/src/gplx/xowa/files/repos/Xow_repo_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/repos/Xow_repo_mgr.java
    @@ -13,3 +13,11 @@ 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.files.repos; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +public interface Xow_repo_mgr {
    +	Xof_repo_pair		Repos_get_at(int i);
    +	Xof_repo_pair		Repos_get_by_wiki(byte[] wiki);
    +	Xof_repo_pair[]		Repos_ary();
    +	Xof_repo_itm		Get_trg_by_id_or_null(int id, byte[] lnki_ttl, byte[] page_url);
    +	Xof_repo_itm		Get_src_by_id_or_null(int id, byte[] lnki_ttl, byte[] page_url);		
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/repos/Xow_repo_mgr_.java b/400_xowa/src/gplx/xowa/files/repos/Xow_repo_mgr_.java
    index a27517de8..efd466c05 100644
    --- a/400_xowa/src/gplx/xowa/files/repos/Xow_repo_mgr_.java
    +++ b/400_xowa/src/gplx/xowa/files/repos/Xow_repo_mgr_.java
    @@ -13,3 +13,41 @@ 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.files.repos; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.xowa.apps.fsys.*; import gplx.xowa.files.exts.*;
    +import gplx.xowa.wikis.domains.*;
    +public class Xow_repo_mgr_ {
    +	public static void Assert_repos(Xoae_app app, Xowe_wiki wiki) {
    +		synchronized (app) {	// LOCK:app-level; DATE:2016-07-07
    +			Xoa_repo_mgr repo_mgr = app.File_mgr().Repo_mgr(); 
    +			Xoa_fsys_mgr app_fsys_mgr = app.Fsys_mgr();
    +			Xof_rule_mgr ext_rule_mgr = app.File_mgr().Ext_rules();
    +			byte[] domain_bry = wiki.Domain_bry();
    +			Xof_repo_itm repo_itm = repo_mgr.Get_by(domain_bry);
    +			if (repo_itm == null) {	// no repo for wiki exists; create it;
    +				repo_itm = new Xof_repo_itm(domain_bry, app_fsys_mgr, ext_rule_mgr, domain_bry);
    +				repo_itm.Root_str_(wiki.Fsys_mgr().Root_dir().Raw());	// NOTE: needed for mass_parse; ordinarily called by xowa.gfs; DATE:2016-07-07
    +				repo_mgr.Add(repo_itm);
    +			}
    +			Xowe_repo_mgr pair_mgr = wiki.File_mgr().Repo_mgr();
    +			if (pair_mgr.Repos_len() == 0) {	// no pairs defined; add at least 1
    +				Xof_repo_itm repo_src = repo_mgr.Get_by(File_repo_xowa_null);
    +				if (repo_src == null) {
    +					repo_itm = new Xof_repo_itm(File_repo_xowa_null, app_fsys_mgr, ext_rule_mgr, Xow_domain_tid_.Bry__home);
    +					repo_mgr.Add(repo_itm);
    +				}
    +				pair_mgr.Add_repo(File_repo_xowa_null, domain_bry);
    +
    +				// add commons; needed for mass_parse, else multiple "repo_mgr.invalid_repo" when common files exist in user_cache; DATE:2016-07-07
    +				Xof_repo_itm commons_repo = repo_mgr.Get_by(Xow_domain_itm_.Bry__commons);
    +				if (commons_repo == null) {
    +					commons_repo = new Xof_repo_itm(Xow_domain_itm_.Bry__commons, app_fsys_mgr, ext_rule_mgr, domain_bry);
    +					commons_repo.Root_str_(app.Fsys_mgr().Wiki_dir().GenSubDir(Xow_domain_itm_.Str__commons).Raw());	// NOTE: needed for mass_parse; ordinarily called by xowa.gfs; DATE:2016-07-07
    +					repo_mgr.Add(commons_repo);
    +				}
    +				pair_mgr.Add_repo(Xow_domain_itm_.Bry__commons, Xow_domain_itm_.Bry__commons);
    +			}
    +		}
    +	}
    +	private static byte[] File_repo_xowa_null = Bry_.new_a7("xowa_repo_null");
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/repos/Xowe_repo_mgr.java b/400_xowa/src/gplx/xowa/files/repos/Xowe_repo_mgr.java
    index a27517de8..39de4f1aa 100644
    --- a/400_xowa/src/gplx/xowa/files/repos/Xowe_repo_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/repos/Xowe_repo_mgr.java
    @@ -13,3 +13,262 @@ 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.files.repos; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.xowa.files.xfers.*;
    +import gplx.xowa.wikis.domains.*;
    +import gplx.xowa.parsers.utils.*;
    +import gplx.xowa.wikis.tdbs.metas.*;
    +public class Xowe_repo_mgr implements Xow_repo_mgr, Gfo_invk {
    +	private Xofw_file_finder_rslt tmp_rslt = new Xofw_file_finder_rslt();
    +	private Xowe_wiki wiki; private final    List_adp repos = List_adp_.New();
    +	public Xowe_repo_mgr(Xowe_wiki wiki) {
    +		this.wiki = wiki;
    +		Xoae_app app = wiki.Appe();
    +		xfer_mgr = new Xof_xfer_mgr(app.File_mgr(), app.Wmf_mgr());
    +		page_finder = new Xofw_wiki_wkr_base(wiki, app.Wiki_mgr());
    +	}
    +	public Xof_xfer_mgr Xfer_mgr() {return xfer_mgr;} private Xof_xfer_mgr xfer_mgr;
    +	public Xowe_repo_mgr Page_finder_(Xofw_wiki_finder v) {page_finder = v; return this;} private Xofw_wiki_finder page_finder;
    +	public int Repos_len() {return repos.Count();}
    +	public Xof_repo_pair Repos_get_by_wiki(byte[] wiki) {
    +		int len = repos.Count();
    +		for (int i = 0; i < len; i++) {
    +			Xof_repo_pair pair = (Xof_repo_pair)repos.Get_at(i);
    +			if (Bry_.Eq(wiki, pair.Wiki_domain()))
    +				return pair;
    +		}
    +		return null;
    +	}
    +	public void Repos_clear() {repos.Clear();}
    +	public void Clone(Xowe_repo_mgr src) {
    +		this.Repos_clear();
    +		int len = src.Repos_len();
    +		for (int i = 0; i < len; ++i) {
    +			Xof_repo_pair repo_pair = src.Repos_get_at(i);
    +			this.Add_repo(repo_pair.Src().Key(), repo_pair.Trg().Key());
    +		}
    +	}
    +	public Xof_repo_pair Repos_get_at(int i) {return (Xof_repo_pair)repos.Get_at(i);}
    +	private Xof_repo_pair Repos_get_by_id(int id) {
    +		int len = repos.Count();
    +		for (int i = 0; i < len; i++) {
    +			Xof_repo_pair pair = (Xof_repo_pair)repos.Get_at(i);
    +			if (pair.Id() == id) return pair;
    +		}
    +		return null;
    +	}
    +	public Xof_repo_itm Get_trg_by_id_or_null(int id, byte[] lnki_ttl, byte[] page_url) {
    +		Xof_repo_pair pair = Repos_get_by_id(id);
    +		if (pair == null) {
    +			Xoa_app_.Usr_dlg().Warn_many("", "", "repo_mgr.invalid_repo: repo=~{0} lnki_ttl=~{1} page_url=~{2}", id, lnki_ttl, page_url);
    +			return null;
    +		}
    +		else
    +			return pair.Trg();
    +	}
    +	public Xof_repo_itm Get_src_by_id_or_null(int id, byte[] lnki_ttl, byte[] page_url) {
    +		Xof_repo_pair pair = Repos_get_by_id(id);
    +		if (pair == null) {
    +			Xoa_app_.Usr_dlg().Warn_many("", "", "repo_mgr.invalid_repo: repo=~{0} lnki_ttl=~{1} page_url=~{2}", id, lnki_ttl, page_url);
    +			return null;
    +		}
    +		else
    +			return pair.Src();
    +	}
    +	public Xof_repo_itm	Get_trg_by_tid_or_null(byte[] tid) {
    +		return null;
    +	}
    +	public Xof_repo_pair[] Repos_ary() {if (repos_ary == null) repos_ary = (Xof_repo_pair[])repos.To_ary(Xof_repo_pair.class); return repos_ary;} private Xof_repo_pair[] repos_ary;
    +	public boolean Xfer_by_meta(Xof_xfer_itm xfer_itm, Xof_xfer_queue queue) {
    +		byte[] ttl = xfer_itm.Lnki_ttl();
    +		Xof_meta_itm meta_itm = xfer_itm.Dbmeta_itm();
    +		boolean chk_all = false;
    +		byte[] src_wiki_key = wiki.Domain_bry();
    +		if (meta_itm.State_new()) {
    +			byte rslt = Xfer_by_meta__find_file(ttl, meta_itm, wiki.Domain_bry());
    +			switch (rslt) {
    +				case Xof_meta_itm.Tid_null: xfer_itm.Orig_repo_id_(0); chk_all = true; break;	// NOTE: src_wiki_key becomes wiki.Key_bry() for sake of simplicity
    +				case Xof_meta_itm.Tid_main: xfer_itm.Orig_repo_id_(Xof_meta_itm.Repo_same); break;
    +				case Xof_meta_itm.Tid_ptr:	src_wiki_key = Xfer_by_meta__find_main_ptr(meta_itm, xfer_itm); break;
    +				case Xof_meta_itm.Tid_vrtl: src_wiki_key = Xfer_by_meta__find_main_vrtl(meta_itm, xfer_itm); break;
    +			}
    +		}
    +		else {
    +			Xof_repo_itm src_repo = meta_itm.Repo_itm(wiki);
    +			src_wiki_key = src_repo.Wiki_domain();
    +			xfer_itm.Orig_repo_id_(meta_itm.Vrtl_repo());	// NOTE: set trg_repo_idx b/c xfer_mgr will always set meta_itm.Vrtl_repo() with trg_repo_idx
    +		}
    +		if (meta_itm.Ptr_ttl_exists())
    +			xfer_itm.Orig_ttl_and_redirect_(meta_itm.Ttl(), meta_itm.Ptr_ttl());
    +		boolean main_exists_unknown = src_wiki_key == null;		// WORKAROUND/HACK: SEE:NOTE_1:reset_main_exists
    +		boolean rv = Xfer_by_meta__exec(chk_all, xfer_itm, meta_itm, src_wiki_key, queue, false);
    +		if (!rv && (!chk_all && !main_exists_unknown)) {	// xfer failed even with page found in wiki; try again, but chk all
    +			rv = Xfer_by_meta__exec(true, xfer_itm, meta_itm, src_wiki_key, queue, true);
    +		}
    +		return rv;
    +	}
    +	byte[] Xfer_by_meta__find_main_ptr(Xof_meta_itm meta_itm, Xof_xfer_itm xfer_itm) {
    +		byte[] redirect = meta_itm.Ptr_ttl(); int redirect_tries = 0;
    +		byte[] md5 = Xof_file_wkr_.Md5(redirect);
    +		while (true) {
    +			boolean found = page_finder.Locate(tmp_rslt, repos, redirect);
    +			if (!found) return null;
    +			Xowe_wiki trg_wiki = wiki;
    +			int repo_idx = tmp_rslt.Repo_idx();
    +			byte[] trg_wiki_key = Bry_.Empty;
    +			if (repo_idx != Xof_meta_itm.Repo_unknown) {
    +				trg_wiki_key = wiki.File_mgr().Repo_mgr().Repos_get_at(repo_idx).Wiki_domain();
    +				trg_wiki = wiki.Appe().Wiki_mgr().Get_by_or_make(trg_wiki_key);
    +			}
    +			Xof_meta_itm redirect_meta = trg_wiki.File_mgr().Dbmeta_mgr().Get_itm_or_new(redirect, md5);
    +			if (tmp_rslt.Redirect() == Xop_redirect_mgr.Redirect_null_bry) {
    +				if (redirect_meta.State_new()) {
    +					if (repo_idx == Xof_meta_itm.Repo_unknown) {
    +//							meta_itm.Vrtl_repo_(Xof_meta_itm.Repo_same);
    +						xfer_itm.Orig_repo_id_(Xof_meta_itm.Repo_same);
    +					}
    +					else {
    +						if (!Bry_.Eq(trg_wiki_key, wiki.Domain_bry())) {
    +//								meta_itm.Vrtl_repo_(tmp_rslt.Repo_idx());
    +							xfer_itm.Orig_repo_id_(tmp_rslt.Repo_idx());
    +						}
    +						else {
    +//								meta_itm.Vrtl_repo_(Xof_meta_itm.Repo_same);
    +							xfer_itm.Orig_repo_id_(Xof_meta_itm.Repo_same);
    +						}
    +					}
    +				}
    +				return trg_wiki_key;
    +			}
    +			redirect = tmp_rslt.Redirect();
    +			if (++redirect_tries > Xop_redirect_mgr.Redirect_max_allowed) return null;
    +		}
    +	}
    +	byte[] Xfer_by_meta__find_main_vrtl(Xof_meta_itm meta_itm, Xof_xfer_itm xfer_itm) {
    +		int repo_idx = meta_itm.Vrtl_repo();
    +		if (repo_idx == Xof_meta_itm.Repo_unknown) return null;;
    +		Xof_repo_itm trg_repo = wiki.File_mgr().Repo_mgr().Repos_get_at(repo_idx).Trg();
    +		xfer_itm.Orig_repo_id_(repo_idx);
    +		return trg_repo.Wiki_domain();
    +	}
    +	boolean Xfer_by_meta__exec(boolean chk_all, Xof_xfer_itm xfer_itm, Xof_meta_itm meta_itm, byte[] main_wiki_key, Xof_xfer_queue queue, boolean second_chance) {
    +		int repos_len = repos.Count();
    +		for (int i = 0; i < repos_len; i++) {								// iterate over all repo pairs
    +			Xof_repo_pair pair = (Xof_repo_pair)repos.Get_at(i);
    +			Xof_repo_itm pair_src = pair.Src();
    +			boolean main_wiki_key_is_pair_src = Bry_.Eq(main_wiki_key, pair_src.Wiki_domain());
    +			if (	(chk_all && !main_wiki_key_is_pair_src)					// only do chk_all if main_wiki is not pair_src; note that chk_all will only be called in two ways (1) with main_wiki_key as null; (2) with main_key_as val
    +				||	(!chk_all && main_wiki_key_is_pair_src)) {				// pair.Src.Wiki == main.Wiki; note that there can be multiple pairs with same src; EX: have 2 pairs for commons: one for file and another for http					
    +				xfer_mgr.Atrs_by_itm(xfer_itm, pair_src, pair.Trg());
    +				boolean make = xfer_mgr.Make_file(wiki);
    +				if (make) {
    +					xfer_itm.Trg_repo_itm_(pair.Trg());
    +					if (second_chance && xfer_itm.Dbmeta_itm().Vrtl_repo() == 0)	// second_chance and item found; change vrtl_repo from commons back to same; EX: tarball and [[Image:Rembrandt De aartsengel verlaat Tobias en zijn gezin. 1637.jpg|120px]]
    +						xfer_itm.Dbmeta_itm().Vrtl_repo_(Xof_meta_itm.Repo_same);
    +					return true;												// file was made; return; if not continue looking at other repos					
    +				}
    +			}
    +		}
    +		return false;
    +	}
    +	public Xofw_file_finder_rslt Page_finder_locate(byte[] ttl_bry) {page_finder.Locate(tmp_rslt, repos, ttl_bry); return tmp_rslt;}
    +	byte Xfer_by_meta__find_file(byte[] ttl_bry, Xof_meta_itm meta_itm, byte[] cur_wiki_key) {
    +		byte new_tid = Byte_.Max_value_127;
    +		boolean found = page_finder.Locate(tmp_rslt, repos, ttl_bry);
    +		if (found) {
    +			if (Bry_.Eq(cur_wiki_key, tmp_rslt.Repo_wiki_key())) {	// itm is in same repo as cur wiki
    +				new_tid = Xof_meta_itm.Tid_main;					
    +				byte[] redirect = tmp_rslt.Redirect();
    +				if (redirect != Xop_redirect_mgr.Redirect_null_bry) {	// redirect found
    +					meta_itm.Ptr_ttl_(redirect);
    +				}
    +				meta_itm.Vrtl_repo_(Xof_meta_itm.Repo_same);
    +			}
    +			else {														// itm is in diff repo
    +				new_tid = Xof_meta_itm.Tid_vrtl;
    +				meta_itm.Vrtl_repo_(tmp_rslt.Repo_idx());
    +				meta_itm.Ptr_ttl_(tmp_rslt.Redirect());
    +			}
    +		}
    +		else {															// itm not found; has to be vrtl, but mark repo as unknown
    +			meta_itm.Vrtl_repo_(Xof_meta_itm.Repo_unknown);
    +		}
    +		return new_tid;
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_add))			return Add_repo(m.ReadBry("src"), m.ReadBry("trg"));
    +		else if	(ctx.Match(k, Invk_clear))			{repos.Clear(); repos_ary = null;}	// reset repos_ary variable
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}	private static final String Invk_add = "add", Invk_clear = "clear";
    +	public Xof_repo_pair Add_repo(byte[] src_repo_key, byte[] trg_repo_key) {
    +		repos_ary = null;	// reset repos_ary variable
    +		Xoa_repo_mgr repo_mgr = wiki.Appe().File_mgr().Repo_mgr();
    +		Xof_repo_itm src_repo = repo_mgr.Get_by(src_repo_key), trg_repo = repo_mgr.Get_by(trg_repo_key);
    +		byte[] src_wiki_key = src_repo.Wiki_domain(), trg_wiki_key = trg_repo.Wiki_domain();
    +		if (!Bry_.Eq(src_wiki_key, trg_wiki_key) && !Bry_.Eq(src_wiki_key, Xow_domain_tid_.Bry__home)) throw Err_.new_wo_type("wiki keys do not match", "src", String_.new_u8(src_wiki_key), "trg", String_.new_u8(trg_wiki_key));
    +		Xof_repo_pair pair = new Xof_repo_pair((byte)repos.Count(), src_wiki_key, src_repo, trg_repo);
    +		repos.Add(pair);
    +		return pair;
    +	}
    +	public boolean Xfer_file(Xof_xfer_itm file) {
    +		int repo_idx = file.Orig_repo_id();
    +		boolean wiki_is_unknown = repo_idx == Xof_meta_itm.Repo_unknown;
    +		boolean make_pass = false;
    +		int len = repos.Count();			
    +		if (!wiki_is_unknown) make_pass = Xfer_file_exec(file, this.Repos_get_at(repo_idx), repo_idx);
    +		if (make_pass) return true;
    +
    +		for (int i = 0; i < len; i++) {
    +			Xof_repo_pair pair = (Xof_repo_pair)repos.Get_at(i);
    +			if (i != repo_idx) {	// try other wikis
    +				file.Dbmeta_itm().Orig_exists_(Xof_meta_itm.Exists_unknown);	// always reset orig exists; this may have been flagged to missing above and should be cleared
    +				make_pass = Xfer_file_exec(file, pair, i);
    +				if (make_pass) break;
    +			}
    +		}
    +		return make_pass;
    +	}
    +	private boolean Xfer_file_exec(Xof_xfer_itm file, Xof_repo_pair pair, int repo_idx) {
    +		xfer_mgr.Atrs_by_itm(file, pair.Src(), pair.Trg());
    +		Xof_meta_itm meta_itm = xfer_mgr.Dbmeta_itm();
    +		boolean rv = xfer_mgr.Make_file(wiki);
    +		if (rv) {
    +			meta_itm.Vrtl_repo_(repo_idx);	// update repo_idx to whatever is found
    +		}
    +		return rv;
    +	}
    +}
    +/*
    +NOTE_1:reset_main_exists
    +this is primarily for Img_missing_wiki_1 and [[Image:Alcott-L.jpg|thumb|right|Louisa May Alcott]]
    +first some details:
    +. file pages exist in both en_wiki and in commons_wiki; w:File:Alcott-L.jpg; c:File:Alcott-L.jpg
    +. images are slightly different (w: is darker than c:)
    +. c was recently updated to redirect to File:Louisa May Alcott (1881 illustration).jpg
    +
    +the actual effect:
    +. [[Image:Alcott-L.jpg|thumb|right|Louisa May Alcott]] in en.wikipedia should pull the w: one (not the c:) one
    +. this behavior seems contrary to all other wiki behavior where c: is given primacy over w:
    +.. EX: [[File:Tanks of WWI.ogg|thumb|thumbtime=12|alt=Primitive]] exists in both en_wiki and in commons, but the file only exists in commons
    +. moreover this page has a special note about the image deliberately remainining in en_wiki
    +
    +the workaround/hack (described via intended sequence):
    +1: commons is defined as first repo_pair
    +2: image found in commons page (note that en_wiki is not checked for PERF reasons)
    +3: image is searched for in commons, but not found; note that both thumb and orig are missing
    +4: image will be searched for in en_wiki and found
    +
    +note that (4) however reuses the same meta which is marked as "not found" from the commons attempt
    +. item will then have orig marked falsely as not-found
    +
    +one approach would be to create a new meta and send that into the chk_all function. this turned out to be problematic
    +
    +so, the workaround:
    +	if going into chk_all
    +	...and orig was previous unknown
    +	...but is now marked no
    +	then reset to unknown to give it a "clean" slate for the chk all
    +
    +in theory, the above statement seems fine, but it does seem hackish...
    +*/
    \ No newline at end of file
    diff --git a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_mgr.java b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_mgr.java
    index a27517de8..9e2e368cc 100644
    --- a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_mgr.java
    +++ b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_mgr.java
    @@ -13,3 +13,363 @@ 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.files.xfers; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.primitives.*; import gplx.gfui.*;
    +import gplx.xowa.files.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.exts.*; import gplx.xowa.files.downloads.*; import gplx.xowa.files.imgs.*;
    +import gplx.xowa.bldrs.wms.*; import gplx.xowa.apps.wms.apis.*; import gplx.xowa.apps.wms.apis.origs.*;
    +import gplx.xowa.wikis.tdbs.metas.*;
    +public class Xof_xfer_mgr {
    +	public Xof_xfer_mgr(Xof_file_mgr file_mgr, Xowmf_mgr wmf_mgr) {this.file_mgr = file_mgr; this.wmf_mgr = wmf_mgr;} private final    Xof_file_mgr file_mgr; private final    Xowmf_mgr wmf_mgr;
    +	public Xof_xfer_rslt Rslt() {return rslt;} private Xof_xfer_rslt rslt = new Xof_xfer_rslt();
    +	public boolean Force_orig() {return force_orig;} public Xof_xfer_mgr Force_orig_(boolean v) {force_orig = v; return this;} private boolean force_orig;
    +	public Xof_xfer_mgr Force_orig_y_() {return Force_orig_(Bool_.Y);} public Xof_xfer_mgr Force_orig_n_() {return Force_orig_(Bool_.N);}
    +	public void Atrs_by_itm(Xof_xfer_itm xfer_itm, Xof_repo_itm src_repo, Xof_repo_itm trg_repo) {
    +		this.xfer_itm = xfer_itm;
    +		this.lnki_w = xfer_itm.Lnki_w(); this.lnki_h = xfer_itm.Lnki_h(); this.lnki_thumbable = !xfer_itm.File_is_orig(); this.lnki_thumbtime = xfer_itm.Lnki_time(); this.lnki_page = xfer_itm.Lnki_page();
    +		this.lnki_type = xfer_itm.Lnki_type();
    +		lnki_upright = xfer_itm.Lnki_upright();
    +		this.orig_ttl = xfer_itm.Orig_ttl(); this.orig_ttl_md5 = xfer_itm.Orig_ttl_md5(); this.orig_ext = xfer_itm.Orig_ext();
    +		this.orig_file_len = xfer_itm.Orig_file_len();
    +		this.src_repo = src_repo; src_repo_is_wmf = src_repo.Wmf_fsys();
    +		this.trg_repo = trg_repo;
    +		this.meta_itm = xfer_itm.Dbmeta_itm();
    +		ext_rule = src_repo.Ext_rules().Get_or_null(orig_ext.Ext());
    +		orig_w = 0; orig_h = 0; file_w = 0; file_h = 0;
    +	}	private byte lnki_type;
    +	private Xof_xfer_itm xfer_itm; private double lnki_thumbtime = Xof_lnki_time.Null; private boolean lnki_thumbable; private int lnki_w, lnki_h, file_w, file_h; private double lnki_upright;
    +	private Xof_ext orig_ext; private Xof_rule_itm ext_rule; private Xof_repo_itm src_repo, trg_repo; private boolean src_repo_is_wmf; private byte[] orig_ttl, orig_ttl_md5; private int orig_w, orig_h; private long orig_file_len; 
    +	private int lnki_page = Xof_lnki_page.Null;
    +	public Xof_meta_itm Dbmeta_itm() {return meta_itm;} private Xof_meta_itm meta_itm;
    +	public boolean Download_allowed_by_ext() {return orig_file_len < ext_rule.Make_max();}
    +	public Xof_xfer_mgr Check_file_exists_before_xfer_n_() {check_file_exists_before_xfer = false; return this;} private boolean check_file_exists_before_xfer = true;
    +	public boolean Make_file(Xowe_wiki wiki) {
    +		rslt.Clear(); this.wiki = wiki;
    +		if		(	src_repo.Wmf_api()												// make sure wmf_api enabled
    +					&&	(	(	orig_ext.Id() == Xof_ext_.Id_ogg							// file is ogg; could be audio; DATE:2013-08-03
    +							&&	!meta_itm.Thumbs_indicates_oga())					// check to make sure it hasn't been called before
    +						||	xfer_itm.Html_elem_tid() == Xof_html_elem.Tid_imap		// file is imap
    +						)
    +				)
    +			Call_wmf_api();
    +		if 		(orig_ext.Id_is_thumbable_img())								Make_img();
    +		else if (orig_ext.Id_is_video()&& !meta_itm.Thumbs_indicates_oga())	Make_vid();
    +		else															Make_other();
    +		return rslt.Pass();
    +	}	private Xowe_wiki wiki; Xoapi_orig_rslts rslts = new Xoapi_orig_rslts();
    +	private boolean Make_img() {
    +		String src_str; Io_url trg_url;
    +		// BLOCK: thumb
    +		if (lnki_thumbable) {									// lnki is thumb with known width >>> try to do thumb
    +			if (lnki_w < 1 && lnki_h < 1) {						// NOTE: only give default thumb if both w and h are < 1; if h > 0, then skip; EX:Paris;[[File:IMA-Ile-St-Louis.jpg|thumb|x220]]
    +				// wiki.File_mgr().Init_file_mgr_by_load(wiki);	// commented out; causes tests to fail and (a) should never have been needed by v0; (b) v0 will not be run any more; DATE:2015-05-20
    +				lnki_w = Xof_img_size.Upright_calc(wiki.File_mgr().Patch_upright(), lnki_upright, lnki_w, lnki_w, lnki_h, lnki_type);
    +				if (lnki_w < 1)
    +					lnki_w = wiki.Html_mgr().Img_thumb_width();		// NOTE: used to be src_repo.Thumb_w()
    +			}
    +			
    +			Make_img_qry();	// NOTE: used to be "if (!Make_img_qry()) return false;" however some images are present, but don't return  node; for now, try to proceed; DATE:2013-02-03
    +			if (Make_img_thumb()) return true;
    +		}
    +
    +		// BLOCK: orig; get orig for convert; note that Img_download will not download file again if src exists
    +		src_str = this.Src_url(src_repo, Xof_img_mode_.Tid__orig, Xof_img_size.Size__neg1);
    +		trg_url = this.Trg_url(trg_repo, Xof_img_mode_.Tid__orig, Xof_img_size.Size__neg1);
    +		if (!Img_download(src_str, trg_url, false)) return false;
    +		trg_url = rslt.Trg();
    +
    +		// BLOCK: convert thumb
    +		if (lnki_thumbable || orig_ext.Id_is_svg() || orig_ext.Id_is_djvu()) {
    +			rslt.Clear();	// clear error from failed thumb
    +			Io_url src_url = trg_url;
    +			if (orig_ext.Id_is_djvu()) {	// NOTE: this block converts djvu -> tiff b/c vanilla imageMagick cannot do djvu -> jpeg
    +				trg_url = this.Trg_url(trg_repo, Xof_img_mode_.Tid__thumb, lnki_w).GenNewExt(".tiff");	// NOTE: manually change orig_ext to tiff; note that djvu has view type of jpeg
    +				wiki.Appe().File_mgr().Img_mgr().Wkr_convert_djvu_to_tiff().Exec(src_url, trg_url);
    +				if (!Cmd_query_size(trg_url)) return false;
    +//						meta_itm.Update_orig_size(file_w, file_h);	// NOTE that thumb size is always orig size
    +				src_url = trg_url;
    +			}
    +			boolean limit = !orig_ext.Id_is_svg();	// do not limit if svg
    +			Xof_xfer_itm_.Calc_xfer_size(calc_size, xfer_itm.Lnki_type(), wiki.Html_mgr().Img_thumb_width(), file_w, file_h, lnki_w, lnki_h, lnki_thumbable, xfer_itm.Lnki_upright(), limit);	// NOTE: always recalc w/h; needed for (a) when width < 1 and (b) when w/h are wrong; xfer=160,160, lnki=65,50, actl should be 50,50; PAGE:en.w:[[Image:Gnome-mime-audio-openclipart.svg|65x50px|center|link=|alt=]]
    +			lnki_w = calc_size.Val_0(); lnki_h = calc_size.Val_1();
    +			trg_url = this.Trg_url(trg_repo, Xof_img_mode_.Tid__thumb, lnki_w);
    +			if (!Img_convert(src_url, trg_url)) return false;	// convert failed; exit
    +			if (orig_ext.Id_is_djvu()) Io_mgr.Instance.DeleteFil(src_url);	// convert passed; if djvu, delete intermediary .tiff file;
    +		}
    +		return true;
    +	}	Int_2_ref calc_size = new Int_2_ref();
    +	boolean Call_wmf_api() {
    +		Xof_download_wkr download_wkr = wiki.App().Wmf_mgr().Download_wkr(); Xowe_repo_mgr repo_mgr = wiki.File_mgr().Repo_mgr();
    +		boolean found = wiki.App().Wmf_mgr().Api_mgr().Api_orig().Api_query_size(rslts, download_wkr, repo_mgr, orig_ttl, lnki_w, lnki_h);
    +		if (found) {
    +			if (rslts.Orig_wiki() != null) {
    +				src_repo = wiki.Appe().File_mgr().Repo_mgr().Get_by_wmf_fsys(rslts.Orig_wiki());
    +				trg_repo = wiki.Appe().File_mgr().Repo_mgr().Get_by_primary(rslts.Orig_wiki());
    +				if (Bry_.Eq(rslts.Orig_wiki(), wiki.Domain_bry()))	// wmf returned same wiki as current
    +					xfer_itm.Orig_repo_id_(Xof_meta_itm.Repo_same);	// set repo to "same"
    +				else {												// wmf returned other wiki (which is 99% likely to be commons)
    +					Xof_repo_pair trg_repo_pair = wiki.File_mgr().Repo_mgr().Repos_get_by_wiki(rslts.Orig_wiki());	// need to do this b/c commons is not always first; see wikinews; DATE:2013-12-04					
    +					int trg_repo_idx = trg_repo_pair == null ? 0 : (int)trg_repo_pair.Id();	// 0=commons
    +					xfer_itm.Orig_repo_id_(trg_repo_idx);
    +				}
    +				if (!Bry_.Eq(rslts.Orig_page(), orig_ttl)) {
    +					orig_ttl = rslts.Orig_page();
    +					orig_ttl_md5 = Xof_file_wkr_.Md5(orig_ttl);
    +					meta_itm.Ptr_ttl_(orig_ttl);
    +				}
    +				meta_itm.Vrtl_repo_(xfer_itm.Orig_repo_id());
    +				if (orig_ext.Id_is_ogg() && rslts.Orig_w() == 0 && rslts.Orig_h() == 0)	// file is ogg, but thumb has size of 0,0; assume audio and mark as oga
    +					meta_itm.Update_thumb_oga_();
    +			}
    +			meta_itm.Load_orig_(rslts.Orig_w(), rslts.Orig_h());
    +			return true;
    +		}
    +		else {
    +			meta_itm.Orig_exists_(Xof_meta_itm.Exists_n);	// not found; mark no
    +			meta_itm.Vrtl_repo_(Xof_meta_itm.Repo_missing);	// not found; mark missing
    +			rslt.Fail("api failed");
    +			return false;
    +		}
    +	}
    +	boolean Make_img_qry() {
    +		if (meta_itm.Orig_w() < 1) {
    +			if (src_repo.Wmf_api()) {	// api_enabled
    +				boolean wmf_api_found = Call_wmf_api();
    +				if (!wmf_api_found) return false;	// not found in wmf_api; exit now
    +			}
    +			else if (src_repo.Tarball()) {
    +				String src_str = this.Src_url(src_repo, Xof_img_mode_.Tid__orig, Xof_img_size.Size__neg1);
    +				meta_itm.Orig_exists_(Xof_meta_itm.Exists_unknown);	// mark exists unknown; note need to assertively mark unknown b/c it may have been marked n in previous pass through multiple repos; DATE:20121227
    +				meta_itm.Vrtl_repo_(Xof_meta_itm.Repo_unknown);		// mark repo unknown;
    +				if (!Cmd_query_size(Io_url_.new_fil_(src_str))) {
    +					meta_itm.Orig_exists_(Xof_meta_itm.Exists_n);	// not found; mark no
    +					meta_itm.Vrtl_repo_(Xof_meta_itm.Repo_missing);	// not found; mark missing
    +					rslt.Fail("img not found");
    +					return false;
    +				}
    +				meta_itm.Vrtl_repo_(xfer_itm.Orig_repo_id());
    +				meta_itm.Load_orig_(file_w, file_h);
    +			}
    +		}
    +		return true;
    +	}
    +	private boolean Make_img_thumb(){
    +		String src_str; Io_url trg_url;
    +		boolean limit = !orig_ext.Id_is_svg();	// do not limit if svg
    +		if (lnki_w > 0) {								// if width is -1, don't bother (wmf only has > 0 width); PAGE:en.w:Paris;[[File:IMA-Ile-St-Louis.jpg|thumb|x220]]   
    +			for (int i = 0; i < 2; i++) {
    +				Xof_xfer_itm_.Calc_xfer_size(calc_size, xfer_itm.Lnki_type(), wiki.Html_mgr().Img_thumb_width(), meta_itm.Orig_w(), meta_itm.Orig_h(), lnki_w, lnki_h, lnki_thumbable, lnki_upright, limit);
    +				lnki_w = calc_size.Val_0(); 
    +				if (lnki_h != -1) lnki_h = calc_size.Val_1(); // NOTE: if -1 (no height specified) do not set height; EX:Tokage_2011-07-15.jpg; DATE:2013-06-03
    +
    +				src_str = src_repo.Tarball() ? this.Src_url(src_repo, Xof_img_mode_.Tid__orig, Xof_img_size.Size_null) : this.Src_url(src_repo, Xof_img_mode_.Tid__thumb, lnki_w);
    +				trg_url = this.Trg_url(trg_repo, Xof_img_mode_.Tid__thumb, lnki_w);
    +				if (Make_img_exec(src_str, trg_url)) {		// download passed
    +					trg_url = rslt.Trg();
    +					if (lnki_w > 0 && lnki_h > 0) {			// lnki specified width and height; check against xfer; needed when w/h are wrong; lnki=65,50 but xfer=160,160; actl should be 50,50; PAGE:en.w:[[Image:Gnome-mime-audio-openclipart.svg|65x50px|center|link=|alt=]]; SEE:NOTE_1
    +						Xof_xfer_itm_.Calc_xfer_size(calc_size, xfer_itm.Lnki_type(), wiki.Html_mgr().Img_thumb_width(), file_w, file_h, lnki_w, lnki_h, lnki_thumbable, -1, limit);	// NOTE: do not use lnki_upright; already applied above to generate new lnki_w; using it again will double-apply it 
    +						if (Int_.Between(lnki_w, calc_size.Val_0() - 1, calc_size.Val_0() + 1))	// width matches; done
    +							return true;
    +						else {								// width fails; cleanup invalid thumb
    +							trg_url = rslt.Trg();			// NOTE: update url b/c size may have changed; PAGE:en.w:commons/Image:Tempesta.djvu which is 800px, but resized to 799px
    +							Io_mgr.Instance.DeleteFil(trg_url);	// delete file
    +							meta_itm.Thumbs_del(lnki_w);	// delete thumb
    +							lnki_w = calc_size.Val_0(); lnki_h = calc_size.Val_1();
    +						}
    +					}
    +					else									// xfer found that matches lnki; exit;
    +						return true;
    +				}
    +				else
    +					break;
    +			}
    +		}
    +		else {		// only height specified
    +			if (meta_itm.Orig_w() > 0) {	// query discovered orig_w; note: not the same as orig_exists b/c flag may not be set yet
    +				Xof_xfer_itm_.Calc_xfer_size(calc_size, xfer_itm.Lnki_type(), wiki.Html_mgr().Img_thumb_width(), file_w, file_h, lnki_w, lnki_h, lnki_thumbable, lnki_upright);// calculate again using width and height
    +				Xof_xfer_itm_.Calc_xfer_size(calc_size, xfer_itm.Lnki_type(), wiki.Html_mgr().Img_thumb_width(), meta_itm.Orig_w(), meta_itm.Orig_h(), lnki_w, lnki_h, lnki_thumbable, lnki_upright, limit);
    +				lnki_w = calc_size.Val_0(); lnki_h = calc_size.Val_1();
    +
    +				src_str = src_repo.Tarball() ? this.Src_url(src_repo, Xof_img_mode_.Tid__orig, Xof_img_size.Size_null) : this.Src_url(src_repo, Xof_img_mode_.Tid__thumb, lnki_w);
    +				trg_url = this.Trg_url(trg_repo, Xof_img_mode_.Tid__thumb, lnki_w);
    +				return Make_img_exec(src_str, trg_url);
    +			}
    +			else {	// no orig dimensions; do download
    +				if (lnki_w == Xof_img_size.Null)
    +					lnki_w = wiki.Html_mgr().Img_thumb_width();	// set lnki_w to default thumb_width (220)
    +				src_str = src_repo.Tarball() ? this.Src_url(src_repo, Xof_img_mode_.Tid__orig, Xof_img_size.Size_null) : this.Src_url(src_repo, Xof_img_mode_.Tid__thumb, lnki_w);
    +				trg_url = this.Trg_url(trg_repo, Xof_img_mode_.Tid__thumb, lnki_w);
    +				if (Make_img_exec(src_str, trg_url)) {		// download
    +					if (src_repo.Tarball()) return true;	// convert worked; no need to download again;
    +					int old_lnki_w = lnki_w;
    +					lnki_w = (file_w * lnki_h) / file_h;	// calculate correct width for specified height;
    +					lnki_w = Xof_xfer_itm_.Calc_w(file_w, file_h, lnki_h);
    +					if (lnki_w == old_lnki_w) return true;	// download at 220 actually worked; this will probably occur very infrequently, but if so, exit
    +					src_str = this.Src_url(src_repo, Xof_img_mode_.Tid__thumb, lnki_w);
    +					trg_url = this.Trg_url(trg_repo, Xof_img_mode_.Tid__thumb, lnki_w);
    +					if (Make_img_exec(src_str, trg_url)) {	// download again
    +						trg_url = rslt.Trg();
    +						Xof_xfer_itm_.Calc_xfer_size(calc_size, xfer_itm.Lnki_type(), wiki.Html_mgr().Img_thumb_width(), file_w, file_h, lnki_w, lnki_h, lnki_thumbable, lnki_upright);// calculate again using width and height
    +						if (Int_.Between(lnki_w, calc_size.Val_0() - 1, calc_size.Val_0() + 1))	// width matches; done
    +							return true;
    +						else {								// width fails; cleanup invalid thumb; EX:w:[[File:Upper and Middle Manhattan.jpg|x120px]]
    +							trg_url = rslt.Trg();			// NOTE: update url b/c size may have changed; PAGE:en.w:commons/Image:Tempesta.djvu which is 800px, but resized to 799px
    +							Io_mgr.Instance.DeleteFil(trg_url);	// delete file
    +							meta_itm.Thumbs_del(lnki_w);	// delete thumb
    +							lnki_w = calc_size.Val_0(); lnki_h = calc_size.Val_1();
    +						}
    +					}
    +				}
    +			}
    +		}
    +		return false;
    +	}
    +	boolean Make_img_exec(String src_str, Io_url trg_url) {
    +		if (src_repo_is_wmf)
    +			return Img_download(src_str, trg_url, true);
    +		else
    +			return Img_convert(Io_url_.new_fil_(src_str), trg_url);
    +	}
    +	private void Make_vid() {
    +		boolean thumb_pass = false;
    +		Make_other();													// NOTE: must go before thumb b/c rslt.Pass() is modified by both
    +		if (src_repo_is_wmf) {											// src is wmf >>> copy down thumb; NOTE: thumb not available in tar
    +			String src_str = this.Src_url(src_repo, Xof_img_mode_.Tid__thumb, lnki_w);
    +			Io_url trg_url = this.Trg_url(trg_repo, Xof_img_mode_.Tid__thumb, lnki_w);
    +			thumb_pass = Cmd_download(src_str, trg_url, false);			// NOTE: ogg audios may sometimes have thumb, but 0 size; thumb_pass will be true, but will fail on thumb_rename; PAGE:en.w:Beethoven; [[File:Ludwig van Beethoven - Symphonie 5 c-moll - 1. Allegro con brio.ogg]]
    +			if (thumb_pass) {
    +				thumb_pass = Img_rename_by_size(trg_url);					// NOTE: lnki cites view_w which will rarely match file_w; PAGE:en.w:Earth;Northwest coast of United States to Central South America at Night.ogv|250px; which is atually 640
    +				if (thumb_pass) {
    +					Xof_meta_thumb thumb = meta_itm.Update_thumb_add(file_w, file_h); // NOTE: only store 1 width; depend on browser to resize to other widths; this matches MW's behavior
    +					if (Xof_lnki_time.Null_n(lnki_thumbtime)) {		// lnki specified seek
    +						thumb.Seeks_add(Xof_lnki_time.X_int(lnki_thumbtime));
    +						meta_itm.Owner_fil().Dirty_();
    +					}
    +					rslt.Clear();						
    +				}
    +				else													// something failed; delete file
    +					Io_mgr.Instance.DeleteFil(trg_url);
    +			}
    +			if (!thumb_pass)	// NOTE: thumb failed; mark itm as oga
    +				meta_itm.Update_thumb_oga_();
    +		}
    +	}
    +	boolean Make_other() {
    +		if (!Orig_max_download() && !force_orig) return false;
    +		String src_str = this.Src_url(src_repo, Xof_img_mode_.Tid__orig, Xof_img_size.Size__neg1);
    +		Io_url trg_url = this.Trg_url(trg_repo, Xof_img_mode_.Tid__orig, Xof_img_size.Size__neg1);
    +		return Cmd_download(src_str, trg_url, true);
    +	}
    +	boolean Orig_max_download() {
    +		long ext_max = ext_rule.View_max();
    +		return (orig_file_len < ext_max)				// file_len is less than defined max
    +			|| (ext_max == Xof_rule_itm.Max_wildcard);	// max is defined as wildcard
    +//			return !(orig_file_len < ext_max || (ext_max == Xof_rule_itm.Max_wildcard && orig_file_len < 1));
    +	}
    +	boolean Img_download(String src_str, Io_url trg_url, boolean cur_is_thumb) {
    +		rslt.Atrs_src_trg_(src_str, trg_url);	// NOTE: must be set at start; Img_rename_by_size may overwrite trg
    +		if (!Cmd_download(src_str, trg_url, !cur_is_thumb)) return false;
    +		if (cur_is_thumb) {
    +			if (orig_w < 1 || orig_h < 1 || lnki_w < 1 || lnki_h < 1) {	// NOTE: if orig is unknown, calc will be based on lnki size which may be incorrect; PAGE:en.w:{{Olympic Summer Games Host Cities}};[[File:Flag of the United States.svg|22x20px]] which is really 22x12px
    +				if (!Img_rename_by_size(trg_url)) return false;
    +				trg_url = rslt.Trg();	// NOTE: update url b/c size may have changed
    +			}
    +			else {
    +				file_w = lnki_w; file_h = lnki_h;
    +			}
    +			meta_itm.Update_thumb_add(file_w, file_h);
    +		}
    +		else {
    +			if ((orig_w < 1 || orig_h < 1) && !orig_ext.Id_is_djvu()) {	// NOTE: imageMagick cannot size djvu xfer_itm so ignore
    +				if (!Cmd_query_size(trg_url)) return false;
    +				meta_itm.Update_orig_size(file_w, file_h);
    +			}
    +			meta_itm.Orig_exists_(Xof_meta_itm.Exists_y);
    +		}
    +		return true;
    +	}	String_obj_ref img_convert_rslt = String_obj_ref.null_();
    +	private boolean Img_convert(Io_url src_url, Io_url trg_url) {
    +		rslt.Atrs_src_trg_(src_url.Xto_api(), trg_url);	// NOTE: must be set at start; Img_rename_by_size may overwrite trg
    +		if (Io_mgr.Instance.ExistsFil(trg_url)) return true; // NOTE: already converted; occurs when same image used twice on same page (EX: flags)
    +		if (!file_mgr.Img_mgr().Wkr_resize_img().Resize_exec(src_url, trg_url, lnki_w, lnki_h, orig_ext.Id(), img_convert_rslt)) {
    +			return rslt.Fail("convert failed|" + src_url.Raw() + "|" + img_convert_rslt.Val());
    +		}
    +		if (lnki_w < 1 || lnki_h < 1) {	// lnki_w or lnki_h is invalid >>> get real size for thumb
    +			if (!Img_rename_by_size(trg_url)) return false;
    +			trg_url = rslt.Trg(); // NOTE: update url b/c size may have changed
    +		}
    +		else {
    +			file_w = lnki_w; file_h = lnki_h;
    +		}
    +		meta_itm.Vrtl_repo_(xfer_itm.Orig_repo_id());
    +		meta_itm.Orig_exists_(Xof_meta_itm.Exists_y);
    +		meta_itm.Update_thumb_add(file_w, file_h);
    +		return true;
    +	}
    +	private boolean Img_rename_by_size(Io_url trg_url) {
    +		if (!Cmd_query_size(trg_url)) return false;
    +		if (file_w != lnki_w) {	// NOTE: only rename if file_w is different; this proc can be called if file_w is same, but file_h < 1; EX: A.svg|thumb|30px will call this proc to get size of thumb
    +			String new_name = Xof_lnki_time.Null_y(lnki_thumbtime) ? file_w + "px" : file_w + "px" + Xof_meta_thumb_parser.Dlm_seek_str + Xof_lnki_time.X_str(lnki_thumbtime);
    +			Io_url new_trg = trg_url.GenNewNameOnly(new_name);
    +			if (trg_url.Eq(new_trg)) return true;	// HACK: io will delete file if moving unto itself; (i.e.: mv A.png A.png is same as del A.png); problem is that this proc is being called too many times
    +			try {Io_mgr.Instance.MoveFil_args(trg_url, new_trg, true).Exec();}
    +			catch (Exception exc) {Err_.Noop(exc); return rslt.Fail("move failed");}
    +			rslt.Trg_(new_trg);
    +		}
    +		return true;
    +	}
    +	private boolean Cmd_download(String src_str, Io_url trg_url, boolean cur_is_orig) {
    +		boolean exists = false;
    +		if (check_file_exists_before_xfer) {
    +			gplx.core.ios.IoItmFil fil_itm = Io_mgr.Instance.QueryFil(trg_url);
    +			exists = fil_itm.Exists() && fil_itm.Size() > 0;
    +		}
    +		boolean pass = false;
    +		if (exists) 
    +			pass = true;
    +		else {
    +			byte download_rslt = wmf_mgr.Download_wkr().Download(src_repo_is_wmf, src_str, trg_url, wmf_mgr.Download_wkr().Download_xrg().Prog_fmt_hdr());
    +			if (download_rslt == gplx.core.ios.IoEngine_xrg_downloadFil.Rslt_fail_host_not_found) {
    +				wiki.File_mgr().Cfg_download().Enabled_(false);
    +				throw Err_.new_wo_type("download_failed: host not found", "src", src_str, "trg", trg_url.Raw());
    +			}
    +			pass = download_rslt == gplx.core.ios.IoEngine_xrg_downloadFil.Rslt_pass;
    +		}
    +		// update meta attributes; placed here b/c Cmd_download is called by 3 procs; note that thumb meta is handled by calling procs as the logic is more specific
    +		if (cur_is_orig) {
    +			if (pass)	meta_itm.Orig_exists_(Xof_meta_itm.Exists_y);
    +			else		meta_itm.Orig_exists_(Xof_meta_itm.Exists_n);
    +		}
    +		if (pass)
    +			meta_itm.Vrtl_repo_(xfer_itm.Orig_repo_id());
    +		else
    +			rslt.Fail("download failed|" + src_str);
    +		return pass;
    +	}
    +	private boolean Cmd_query_size(Io_url trg_url) {
    +		SizeAdp file_size = file_mgr.Img_mgr().Wkr_query_img_size().Exec(trg_url);
    +		if (file_size == SizeAdp_.Zero) return rslt.Fail("query size failed");
    +		file_w = file_size.Width(); file_h = file_size.Height();
    +		return true;
    +	}
    +	String Src_url(Xof_repo_itm repo, byte mode, int lnki_w)	{return url_bldr.Init_for_src_file(repo, mode, orig_ttl, orig_ttl_md5, orig_ext, lnki_w, lnki_thumbtime, lnki_page).Xto_str();}
    +	Io_url Trg_url(Xof_repo_itm repo, byte mode, int lnki_w)	{return url_bldr.Init_for_trg_file(repo, mode, orig_ttl, orig_ttl_md5, orig_ext, lnki_w, lnki_thumbtime, lnki_page).Xto_url();}
    +	private Xof_url_bldr url_bldr = new Xof_url_bldr();
    +}
    +/*
    +NOTE_1:always recalc w/h
    +[[Image:Gnome-mime-audio-openclipart.svg|65x50px|center|link=|alt=]]
    +. orig size is 160,160
    +. lnki size is 65,50
    +. actl size should be 50,50
    +. however, WP actually happens to have a 65,65 on server
    +
    +The problem is that WP always knows orig size info (160,160) and can correct 65,50 to 50,50
    +XO does not know orig size info (image.sql needs to be downloaded) and needs to somehow decide that 65,50 is wrong even though 65 is on server
    +
    +So, do the following
    +. assume that dimensions of 65,65 are correctly scaled from 160,160
    +. calc 65,50 for the 65w image
    +.. if 65,50 is correct, then we should get back 65,50
    +.. if not, then the lnki is wrong. just download orig and rescale
    +... note that we can redownload thumb at 50, but simply easier to "fall-through" to orig processing
    +*/
    \ No newline at end of file
    diff --git a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue.java b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue.java
    index a27517de8..af59b32ed 100644
    --- a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue.java
    +++ b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue.java
    @@ -13,3 +13,80 @@ 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.files.xfers; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.primitives.*; import gplx.core.envs.*;
    +import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*; import gplx.xowa.files.bins.*; import gplx.xowa.files.origs.*;
    +import gplx.xowa.guis.cbks.js.*;
    +import gplx.xowa.wikis.tdbs.metas.*;
    +public class Xof_xfer_queue {
    +	private final    List_adp xfer_list = List_adp_.New(); private final    Ordered_hash dirty_meta_mgrs = Ordered_hash_.New_bry();
    +	public Int_obj_ref Html_uid() {return html_uid;} private Int_obj_ref html_uid = Int_obj_ref.New_neg1();
    +	public int Count() {return xfer_list.Count();}
    +	public void Clear() {
    +		dirty_meta_mgrs.Clear();
    +		xfer_list.Clear();
    +		html_uid.Val_neg1_();
    +	}
    +	public void Add(Xof_file_itm xfer_itm) {xfer_list.Add(xfer_itm);}
    +	public void Exec(Xowe_wiki wiki, Xoae_page page) {
    +		if (wiki.File_mgr().Version() == Xow_file_mgr.Version_2)
    +			Exec_v2(wiki, page);
    +		else
    +			Exec_v1(wiki, page);
    +	}
    +	private void Exec_v1(Xowe_wiki wiki, Xoae_page page) {
    +		Xof_meta_mgr meta_mgr = null;
    +		int xfer_len = xfer_list.Count();
    +		Gfo_usr_dlg usr_dlg = Xoa_app_.Usr_dlg();
    +		for (int i = 0; i < xfer_len; i++) {
    +			if (wiki.Appe().Usr_dlg().Canceled()) break;
    +			Xof_xfer_itm xfer_itm = (Xof_xfer_itm)xfer_list.Get_at(i);
    +			meta_mgr = xfer_itm.Dbmeta_itm().Owner_fil().Owner_mgr();
    +			byte[] meta_mgr_key = meta_mgr.Wiki().Domain_bry();
    +			if (!dirty_meta_mgrs.Has(meta_mgr_key)) dirty_meta_mgrs.Add(meta_mgr_key, meta_mgr);	// only add if new
    +			String queue_msg = usr_dlg.Prog_many("", "", "downloading ~{0} of ~{1}: ~{2};", i + List_adp_.Base1, xfer_len, xfer_itm.Lnki_ttl());
    +			wiki.App().Wmf_mgr().Download_wkr().Download_xrg().Prog_fmt_hdr_(queue_msg);
    +			wiki.File_mgr().Repo_mgr().Xfer_by_meta(xfer_itm, this);
    +			xfer_itm.Set__meta(xfer_itm.Dbmeta_itm(), xfer_itm.Dbmeta_itm().Repo_itm(wiki), wiki.Html_mgr().Img_thumb_width());
    +			xfer_itm.Calc_by_meta();
    +			if (!xfer_itm.File_exists()) continue;	// file not found; don't call Update_img, else invalid src will be passed and caption box will be incorrectly resized; EX:ar.d:جَبَّارَة; DATE:2014-04-13
    +			if (Bry_.Len_gt_0(xfer_itm.Html_view_url().To_http_file_bry())	// only update images that have been found; otherwise "Undefined" shows up in image box
    +				&& xfer_itm.Html_elem_tid() != Xof_html_elem.Tid_none) {	// skip updates when downloading orig on File page (there won't be any frame to update)
    +				Xog_js_wkr js_wkr = Env_.Mode_testing() ? Xog_js_wkr_.Noop : page.Tab_data().Tab().Html_itm(); 
    +				Js_img_mgr.Update_img(page, js_wkr, xfer_itm);
    +			}
    +		}
    +		for (int i = 0; i < dirty_meta_mgrs.Count(); i++) {
    +			meta_mgr = (Xof_meta_mgr)dirty_meta_mgrs.Get_at(i);
    +			meta_mgr.Save(true);
    +		}
    +		this.Clear();
    +	}
    +	private void Exec_v2(Xowe_wiki wiki, Xoae_page page) {
    +		wiki.File_mgr().Init_file_mgr_by_load(wiki);
    +		Xog_js_wkr js_wkr = wiki.App().Mode().Tid_supports_js() ? page.Tab_data().Tab().Html_itm() : Xog_js_wkr_.Noop;
    +		wiki.File_mgr().Fsdb_mgr().Fsdb_search_by_list(Xfer_itms_to_fsdb_itms(wiki, page, xfer_list, wiki.File_mgr().Patch_upright()), wiki, page, js_wkr);
    +	}
    +	private List_adp Xfer_itms_to_fsdb_itms(Xowe_wiki cur_wiki, Xoae_page page, List_adp xfer_list, int upright_patch) {
    +		List_adp rv = List_adp_.New();
    +		int list_len = xfer_list.Count();
    +		for (int i = 0; i < list_len; i++) {
    +			Xof_file_itm xfer = (Xof_file_itm)xfer_list.Get_at(i);
    +			if (xfer.Hdump_mode() == Xof_fsdb_itm.Hdump_mode__null) {
    +				Xof_fsdb_itm fsdb = new Xof_fsdb_itm();
    +				fsdb.Init_at_lnki(xfer.Lnki_exec_tid(), xfer.Lnki_wiki_abrv(), xfer.Lnki_ttl(), xfer.Lnki_type(), xfer.Lnki_upright(), xfer.Lnki_w(), xfer.Lnki_h(), xfer.Lnki_time(), xfer.Lnki_page(), upright_patch);
    +				fsdb.Init_at_hdoc(xfer.Html_uid(), xfer.Html_elem_tid());
    +				fsdb.Html_gallery_mgr_h_(xfer.Html_gallery_mgr_h());
    +				fsdb.Html_img_wkr_(xfer.Html_img_wkr());
    +				fsdb.File_exists_(xfer.File_exists());
    +				if (xfer.Lnki_type() == gplx.xowa.parsers.lnkis.Xop_lnki_type.Tid_orig_known)
    +					fsdb.Init_at_gallery_bgn(xfer.Html_w(), xfer.Html_h(), xfer.File_w());
    +				rv.Add(fsdb);
    +			}
    +			else
    +				rv.Add(xfer);
    +		}
    +		this.Clear();
    +		return rv;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_base_fxt.java b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_base_fxt.java
    index a27517de8..a2a5ef0b9 100644
    --- a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_base_fxt.java
    +++ b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_base_fxt.java
    @@ -13,3 +13,101 @@ 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.files.xfers; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.ios.*;
    +import gplx.xowa.wikis.domains.*;
    +import gplx.xowa.files.cnvs.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.exts.*; import gplx.xowa.files.imgs.*;
    +import gplx.xowa.apps.wms.apis.*; import gplx.xowa.apps.wms.apis.origs.*;	
    +public class Xof_xfer_queue_base_fxt {
    +	public Xoapi_orig_mok Api_size() {return api_size;} private Xoapi_orig_mok api_size = Xoapi_orig_mok.Instance;
    +	public Xof_repo_itm Src_commons_repo() {return src_commons_repo;} private Xof_repo_itm src_commons_repo;
    +	public Xof_repo_itm Src_en_wiki_repo() {return src_en_wiki_repo;} private Xof_repo_itm src_en_wiki_repo;
    +	@gplx.Virtual public void Clear(boolean src_repo_is_wmf) {
    +		Io_mgr.Instance.InitEngine_mem();
    +		if (app == null) {
    +			app = Xoa_app_fxt.Make__app__edit();
    +			en_wiki = Xoa_app_fxt.Make__wiki__edit(app, Xow_domain_itm_.Str__enwiki);
    +			commons = Xoa_app_fxt.Make__wiki__edit(app, Xow_domain_itm_.Str__commons);
    +			app.Wiki_mgr().Add(commons);
    +			app.Wiki_mgr().Add(en_wiki);
    +			
    +			Xof_file_mgr file_mgr = app.File_mgr();
    +			file_mgr.Img_mgr().Wkr_resize_img_(Xof_img_wkr_resize_img_mok.Instance);
    +			file_mgr.Img_mgr().Wkr_query_img_size_(new Xof_img_wkr_query_img_size_test());
    +			app.Wmf_mgr().Api_mgr().Api_orig_(api_size);
    +
    +			byte[] src_commons = Bry_.new_a7("src_commons");
    +			byte[] src_en_wiki = Bry_.new_a7("src_en_wiki");
    +			byte[] trg_commons = Bry_.new_a7("trg_commons");
    +			byte[] trg_en_wiki = Bry_.new_a7("trg_en_wiki");
    +			src_commons_repo = Ini_repo_add(file_mgr, src_commons, "mem/src/commons.wikimedia.org/", Xow_domain_itm_.Str__commons, false);
    +			src_en_wiki_repo = Ini_repo_add(file_mgr, src_en_wiki, "mem/src/en.wikipedia.org/"		, Xow_domain_itm_.Str__enwiki, false);
    +			Ini_repo_add(file_mgr, trg_commons, "mem/trg/commons.wikimedia.org/", Xow_domain_itm_.Str__commons, true).Primary_(true);
    +			Ini_repo_add(file_mgr, trg_en_wiki, "mem/trg/en.wikipedia.org/"		, Xow_domain_itm_.Str__enwiki, true).Primary_(true);
    +			Xowe_repo_mgr wiki_repo_mgr = en_wiki.File_mgr().Repo_mgr();
    +			Xof_repo_pair pair = null;
    +			pair = wiki_repo_mgr.Add_repo(src_commons, trg_commons);
    +			pair.Src().Fsys_is_wnt_(true).Wmf_fsys_(src_repo_is_wmf).Tarball_(!src_repo_is_wmf);
    +			pair.Trg().Fsys_is_wnt_(true);
    +
    +			pair = wiki_repo_mgr.Add_repo(src_en_wiki, trg_en_wiki);
    +			pair.Src().Fsys_is_wnt_(true).Wmf_fsys_(src_repo_is_wmf);
    +			pair.Trg().Fsys_is_wnt_(true);
    +		}
    +		en_wiki.Clear_for_tests();
    +		commons.Clear_for_tests();
    +		src_fils = trg_fils = Io_fil.Ary_empty;
    +		html_view_str = null;
    +		html_w = html_h = -1;
    +	}
    +	public Xoae_app App() {return app;} private Xoae_app app;
    +	public Xowe_wiki En_wiki() {return en_wiki;} private Xowe_wiki en_wiki;
    +	public Xowe_wiki Commons() {return commons;} private Xowe_wiki commons;
    +	public void ini_page_create_commons(String ttl)								{Init_page_create(commons, ttl, "");}
    +	public void ini_page_create_commons_redirect(String ttl, String redirect)	{Init_page_create(commons, ttl, "#REDIRECT [[" + redirect + "]]");}
    +	public void ini_page_create_en_wiki(String ttl)								{Init_page_create(en_wiki, ttl, "");}
    +	public void ini_page_create_en_wiki_redirect(String ttl, String redirect)	{Init_page_create(en_wiki, ttl, "#REDIRECT [[" + redirect + "]]");}
    +	public void Init_page_create(Xowe_wiki wiki, String ttl, String txt) {
    +		Xoa_ttl page_ttl = Xoa_ttl.Parse(wiki, Bry_.new_u8(ttl));
    +		byte[] page_raw = Bry_.new_u8(txt);
    +		wiki.Db_mgr().Save_mgr().Data_create(page_ttl, page_raw);
    +	}
    +	Xof_repo_itm Ini_repo_add(Xof_file_mgr file_mgr, byte[] key, String root, String wiki, boolean trg) {
    +		Xof_repo_itm repo = file_mgr.Repo_mgr().Set(String_.new_u8(key), root, wiki).Ext_rules_(Xof_rule_grp.Grp_app_default).Dir_depth_(2);
    +		if (trg) {
    +			byte[][] ary = repo.Mode_names();
    +			ary[0] = Bry_.new_a7("raw");
    +			ary[1] = Bry_.new_a7("fit");
    +		}
    +		return repo;
    +	}
    +	public Xof_xfer_queue_base_fxt Src_base(Io_fil... v) {src_fils = v; return this;} Io_fil[] src_fils = Io_fil.Ary_empty;
    +	public Xof_xfer_queue_base_fxt Trg_base(Io_fil... v) {trg_fils = v; return this;} Io_fil[] trg_fils = Io_fil.Ary_empty;
    +	public String Html_view_src() {return html_view_str;} protected Xof_xfer_queue_base_fxt Html_src_base_(String v) {html_view_str = v; return this;} private String html_view_str;
    +	public int Html_w() {return html_w;} public Xof_xfer_queue_base_fxt Html_w_(int v) {html_w = v; return this;} private int html_w = -1;
    +	public int Html_h() {return html_h;} public Xof_xfer_queue_base_fxt Html_h_(int v) {html_h = v; return this;} private int html_h = -1;
    +	public void ini_src_fils() {
    +		if (src_fils != null) {
    +			for (int i = 0; i < src_fils.length; i++) {
    +				Io_fil src_fil = src_fils[i];
    +				Io_mgr.Instance.SaveFilStr(src_fil.Url(), src_fil.Data());
    +			}
    +		}
    +	}
    +	public void tst_trg_fils() {
    +		for (int i = 0; i < trg_fils.length; i++) {
    +			Io_fil trg_fil = trg_fils[i];
    +			String data = Io_mgr.Instance.LoadFilStr(trg_fil.Url());
    +			Tfds.Eq_str_lines(trg_fil.Data(), data, trg_fil.Url().Raw());
    +		}		
    +	}
    +	public void	 save_(Io_fil v)						{Io_mgr.Instance.SaveFilStr(v.Url(), v.Data());}
    +	public Io_fil reg_(String url, String... v)	{return new Io_fil(Io_url_.mem_fil_(url), String_.Concat_lines_nl(v));}
    +	public Io_fil img_(String url_str, int w, int h)	{return file_(url_str, file_img(w, h));}
    +	public Io_fil svg_(String url_str, int w, int h)	{return file_(url_str, file_svg(w, h));}
    +	public Io_fil ogg_(String url_str)					{return file_(url_str, "");}
    +	public void fil_absent(String url)					{Tfds.Eq_false(Io_mgr.Instance.ExistsFil(Io_url_.mem_fil_(url)), "fil should not exist: {0}", url);}
    +	Io_fil file_(String url_str, String data)			{return new Io_fil(Io_url_.mem_fil_(url_str), data);}
    +	String file_img(int w, int h) {return String_.Format("{0},{1}", w, h);}
    +	String file_svg(int w, int h) {return String_.Format("", w, h);}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_basic_tst.java b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_basic_tst.java
    index a27517de8..4ee963715 100644
    --- a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_basic_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_basic_tst.java
    @@ -13,3 +13,120 @@ 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.files.xfers; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import org.junit.*;
    +import gplx.core.ios.*; import gplx.gfui.*; import gplx.xowa.files.*;
    +public class Xof_xfer_queue_html_basic_tst {
    +	Xof_xfer_queue_html_fxt fxt = new Xof_xfer_queue_html_fxt();
    +	@Before public void init() {fxt.Clear(true);}
    +	@Test  public void Main_orig() {
    +		fxt	.ini_page_create_en_wiki("File:A.png");
    +		fxt	.Lnki_orig_("A.png")
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/7/70/A.png", 900, 800))
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/raw/7/0/A.png", 900, 800)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv", "A.png|y||1?900,800|")
    +				);
    +		fxt.tst();
    +	}
    +	@Test  public void Main_thumb_download() {
    +		fxt	.ini_page_create_en_wiki("File:A.png");
    +		fxt	.Lnki_thumb_("A.png", 90)
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/90px-A.png", 90, 80))
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/90px.png", 90, 80)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv", "A.png|y||2?0,0|1?90,80")
    +				);
    +		fxt.tst();
    +	}
    +	@Test  public void Main_thumb_convert() {
    +		fxt	.ini_page_create_en_wiki("File:A.png");
    +		fxt	.Lnki_thumb_("A.png", 90)
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/7/70/A.png", 900, 800))
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/raw/7/0/A.png", 900, 800)
    +				,	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/90px.png", 90, 80)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv", "A.png|y||1?900,800|1?90,80")
    +				);
    +		fxt.tst();
    +	}
    +	@Test  public void Ptr_orig() {
    +		fxt	.ini_page_create_en_wiki			("File:A.png");
    +		fxt	.ini_page_create_en_wiki_redirect	("File:B.png", "File:A.png");
    +		fxt	.Lnki_orig_("B.png")
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/7/70/A.png", 900, 800))
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/raw/7/0/A.png", 900, 800)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/5/57.csv", "B.png|y|A.png|1?900,800|")
    +				);
    +		fxt.tst();
    +		fxt	.Lnki_orig_("B.png")
    +			.Html_src_("file:///mem/trg/en.wikipedia.org/raw/7/0/A.png")
    +			.tst();
    +	}
    +	@Test  public void Ptr_thumb_download() {
    +		fxt	.ini_page_create_en_wiki			("File:A.png");
    +		fxt	.ini_page_create_en_wiki_redirect	("File:B.png", "File:A.png");
    +		fxt	.Lnki_thumb_("B.png", 90)
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/90px-A.png", 90, 80))
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/90px.png", 90, 80)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/5/57.csv", "B.png|y|A.png|2?0,0|1?90,80")
    +				);
    +		fxt.tst();
    +	}
    +	@Test  public void Ptr_thumb_convert() {
    +		fxt	.ini_page_create_en_wiki			("File:A.png");
    +		fxt	.ini_page_create_en_wiki_redirect	("File:B.png", "File:A.png");
    +		fxt	.Lnki_thumb_("B.png", 90)
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/7/70/A.png", 900, 800))
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/raw/7/0/A.png", 900, 800)
    +				,	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/90px.png", 90, 80)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/5/57.csv", "B.png|y|A.png|1?900,800|1?90,80")
    +				);
    +		fxt.tst();
    +	}
    +	@Test  public void Vrtl_orig() {
    +		fxt	.ini_page_create_commons			("File:A.png");
    +		fxt	.Lnki_orig_("A.png")
    +			.Src(	fxt.img_("mem/src/commons.wikimedia.org/7/70/A.png", 900, 800))
    +			.Trg(	fxt.img_("mem/trg/commons.wikimedia.org/raw/7/0/A.png", 900, 800)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|0||1?900,800|")
    +				);
    +		fxt.tst();
    +	}
    +	@Test  public void Vrtl_thumb_download() {
    +		fxt	.ini_page_create_commons			("File:A.png");
    +		fxt	.Lnki_thumb_("A.png", 90)
    +			.Src(	fxt.img_("mem/src/commons.wikimedia.org/thumb/7/70/A.png/90px-A.png", 90, 80))
    +			.Trg(	fxt.img_("mem/trg/commons.wikimedia.org/fit/7/0/A.png/90px.png", 90, 80)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|0||2?0,0|1?90,80")
    +				);
    +		fxt.tst();
    +	}
    +	@Test  public void Vrtl_thumb_convert() {
    +		fxt	.ini_page_create_commons			("File:A.png");
    +		fxt	.Lnki_thumb_("A.png", 90)
    +			.Src(	fxt.img_("mem/src/commons.wikimedia.org/7/70/A.png", 900, 800))
    +			.Trg(	fxt.img_("mem/trg/commons.wikimedia.org/raw/7/0/A.png", 900, 800)
    +				,	fxt.img_("mem/trg/commons.wikimedia.org/fit/7/0/A.png/90px.png", 90, 80)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|0||1?900,800|1?90,80")
    +				);
    +		fxt.tst();
    +	}
    +	@Test  public void Vrtl_ptr_orig() {
    +		fxt	.ini_page_create_commons_redirect	("File:B.png", "File:A.png");
    +		fxt	.ini_page_create_commons			("File:A.png");
    +		fxt	.Lnki_orig_("B.png")
    +			.Src(	fxt.img_("mem/src/commons.wikimedia.org/7/70/A.png", 900, 800))
    +			.Trg(	fxt.img_("mem/trg/commons.wikimedia.org/raw/7/0/A.png", 900, 800)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/5/57.csv"		, "B.png|0|A.png|1?900,800|")
    +				);
    +		fxt.tst();
    +	}
    +	@Test  public void Ptr_vrtl_orig() {
    +		fxt	.ini_page_create_en_wiki_redirect	("File:B.png", "File:A.png");
    +		fxt	.ini_page_create_commons			("File:A.png");
    +		fxt	.Lnki_orig_("B.png")
    +			.Src(	fxt.img_("mem/src/commons.wikimedia.org/7/70/A.png", 900, 800))
    +			.Trg(	fxt.img_("mem/trg/commons.wikimedia.org/raw/7/0/A.png", 900, 800)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/5/57.csv"		, "B.png|y|A.png|1?900,800|")
    +				);
    +		fxt.tst();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_cases_tst.java b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_cases_tst.java
    index a27517de8..4d6534d3d 100644
    --- a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_cases_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_cases_tst.java
    @@ -13,3 +13,276 @@ 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.files.xfers; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import org.junit.*;
    +import gplx.xowa.files.*; import gplx.xowa.files.imgs.*;
    +public class Xof_xfer_queue_html_cases_tst {
    +	Xof_xfer_queue_html_fxt fxt = new Xof_xfer_queue_html_fxt();
    +	@Before public void init() {
    +		fxt.Clear(true);
    +	}
    +	@Test  public void Png_missing() {
    +		fxt	.Lnki_orig_("A.png")
    +			.Trg(	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv", "A.png|z||0?0,0|")
    +				);
    +		fxt.tst();
    +	}
    +	@Test  public void Png_missing_2() {	// PURPOSE: orig is missing; do not download again; NOTE: simulating "do not download again" check by putting in thumb and making sure it doesn't get downloaded
    +		fxt.save_(fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv", "A.png|z||0?0,0|"));	// save reg file and mark file as missing
    +		fxt	.Lnki_thumb_("A.png", 90)
    +			.Src(	fxt.img_("mem/src/commons.wikimedia.org/thumb/7/70/A.png/90px-A.png", 90, 80))
    +			.Trg(	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv", "A.png|z||0?0,0|")	// NOTE: 90,80 should not show up
    +				);
    +		fxt.tst();
    +	}
    +	@Test  public void Png_encode() {	// PURPOSE: make sure \s is converted to _; also ' should not be encoded on trg; done
    +		fxt	.ini_page_create_commons			("File:A'b c.png");
    +		fxt	.Lnki_orig_("A'b c.png")
    +			.Src(	fxt.img_("mem/src/commons.wikimedia.org/9/9c/A%27b_c.png", 90, 80))
    +			.Trg(	fxt.img_("mem/trg/commons.wikimedia.org/raw/9/c/A'b_c.png", 90, 80)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/9/9c.csv", "A'b_c.png|0||1?90,80|")
    +				);
    +		fxt.tst();
    +	}
    +	@Test  public void Ogg_vid_thumb() {
    +		fxt	.ini_page_create_commons			("File:A.ogg");
    +		fxt	.Lnki_orig_("A.ogg")
    +			.Src(	fxt.img_("mem/src/commons.wikimedia.org/thumb/4/42/A.ogg/-1px--A.ogg.jpg", 300, 40))
    +			.Trg(	fxt.img_("mem/trg/commons.wikimedia.org/fit/4/2/A.ogg/300px.jpg", 300, 40)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/4/42.csv", "A.ogg|0||0?0,0|1?300,40")
    +				);
    +		fxt.tst();
    +	}
    +	@Test  public void Ogg_vid_missing_thumb() {
    +		fxt	.Lnki_orig_("A.ogg")
    +			.Src(	)
    +			.Trg(	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/4/42.csv", "A.ogg|z||0?0,0|0?0,0")	// NOTE: mark thumb not found (since xfer_mgr checked all repos)
    +				);
    +		fxt.tst();
    +	}
    +	@Test  public void Aud_do_not_download() {
    +		fxt.save_(fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/4/42.csv", "A.ogg|z||2?0,0|0?0,0"));		// save reg file and mark file as aud
    +		fxt	.Lnki_orig_("A.ogg")
    +			.Src(	fxt.img_("mem/src/commons.wikimedia.org/thumb/4/42/A.ogg/mid-A.ogg.jpg", 300, 40)		// simulate thumb (make sure test does not download)
    +				)
    +			.Trg(	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/4/42.csv", "A.ogg|z||2?0,0|0?0,0")		// 300,40 should not show up
    +				);
    +		fxt.tst();
    +	}
    +	@Test  public void Img_thumb_djvu() {// PURPOSE: exact djvu thumbs are not on server; always seems to retrieve 1 off;
    +		fxt	.ini_page_create_commons			("File:A.djvu");
    +		fxt	.App().File_mgr().Img_mgr().Wkr_convert_djvu_to_tiff_(Xof_img_wkr_convert_djvu_to_tiff_.new_mok(199, 299));
    +		fxt	.Lnki_thumb_("A.djvu", 200)
    +			.Src(	fxt.img_("mem/src/commons.wikimedia.org/7/76/A.djvu", 1990, 2990)
    +				)
    +			.Trg(	fxt.img_("mem/trg/commons.wikimedia.org/fit/7/6/A.djvu/199px.jpg", 199, 299)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/76.csv", "A.djvu|0||1?0,0|1?199,299")
    +				);
    +		fxt.tst();
    +		fxt	.Lnki_thumb_("A.djvu", 200)
    +			.Html_src_("file:///mem/trg/commons.wikimedia.org/fit/7/6/A.djvu/199px.jpg")
    +			.Html_size_(200, 301)
    +			.tst();
    +	}
    +	@Test  public void Img_thumb_pdf() {// PURPOSE: download pdf thumb only; [[File:Physical world.pdf|thumb]]
    +		fxt	.ini_page_create_commons			("File:A.pdf");
    +		fxt	.Lnki_thumb_("A.pdf", 220)
    +			.Src(	fxt.img_("mem/src/commons.wikimedia.org/e/ef/A.pdf", 2200, 1700)
    +				,	fxt.img_("mem/src/commons.wikimedia.org/thumb/e/ef/A.pdf/page1-220px-A.pdf.jpg", 220, 170)
    +				)
    +			.Trg(	fxt.img_("mem/trg/commons.wikimedia.org/fit/e/f/A.pdf/220px.jpg", 220, 170)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/e/ef.csv", "A.pdf|0||2?0,0|1?220,170")
    +				);
    +		fxt.tst();
    +		fxt	.Lnki_thumb_("A.pdf", 220)
    +			.Html_src_("file:///mem/trg/commons.wikimedia.org/fit/e/f/A.pdf/220px.jpg")
    +			.Html_size_(220, 170)
    +			.tst();
    +	}
    +	@Test  public void Img_missing_wiki_0() {	// PURPOSE.outlier: page is in wiki_1 but file is actually in wiki_0; download from wiki_0; occurs when working with old commons/en.wikipedia.org against current wmf servers; EX: Mars Science Laboratory and File:Curiosity wheel pattern morse code.png; Curiosity rover
    +		fxt	.ini_page_create_en_wiki			("File:A.png");							// page is in en_wiki
    +		fxt	.Lnki_orig_("A.png")
    +			.Src(	fxt.img_("mem/src/commons.wikimedia.org/7/70/A.png", 900, 800))		// file is in commons
    +			.Trg(	fxt.img_("mem/trg/commons.wikimedia.org/raw/7/0/A.png", 900, 800)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|y||1?900,800|")
    +				);
    +		fxt.tst();
    +	}
    +//		@Test  public void Img_missing_wiki_1() {	// PURPOSE.outlier: page is in wiki_0 and in wiki_1; file is in wiki_1; EX:[[Image:Alcott-L.jpg|thumb|right|Louisa May Alcott]]
    +//			fxt	.ini_page_create_commons			("File:A.png");														// page is in commons also
    +//			fxt	.ini_page_create_en_wiki			("File:A.png");														// page is in en_wiki
    +//			fxt	.Lnki_thumb_("A.png", 220)
    +//				.Src(	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/220px-A.png", 220, 110))					// file is in en_wiki
    +//				.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/220px.png", 220, 110)							// download en_wiki
    +//					,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "2|A.png|y||2?0,0|1?220,110")
    +//					);
    +//			fxt.tst();
    +//		}
    +	@Test  public void Do_not_download_orig_0() {// PURPOSE: do not download orig if size can be inferred from thumb; EX: [[File:Vanadium etched.jpg|350x250px|Vanadium]]
    +		fxt	.ini_page_create_en_wiki			("File:A.png");
    +		fxt	.Lnki_thumb_("A.png", 350, 250)																			// requesting w=350 and h=250; note that h trumps w
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/350px-A.png", 350, 309)						// w=350 exists, but should not be used
    +				,	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/283px-A.png", 283, 250)						// h=250 exists, and should be used
    +				,	fxt.img_("mem/src/en.wikipedia.org/7/70/A.png", 3808, 3364)										// orig image exists, and should not be downloaded
    +				)
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/283px.png"		, 283, 250)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|y||2?0,0|1?283,250")	// note that orig does not exist
    +				);
    +		fxt.tst();
    +		fxt.fil_absent(		 "mem/trg/en.wikipedia.org/raw/7/0/A.png");
    +		fxt	.Lnki_thumb_("A.png", 350, 250)
    +			.Html_src_("file:///mem/trg/en.wikipedia.org/fit/7/0/A.png/283px.png")
    +			.tst();
    +	}
    +	@Test  public void Do_not_download_orig_1() {// PURPOSE: allow variance of 1 for thumbs (rounding errors); EX:Image:President Woodrow Wilson portrait December 2 1912.jpg|US President Woodrow Wilson
    +		fxt	.ini_page_create_en_wiki			("File:A.png");
    +		fxt	.Lnki_thumb_("A.png", 120, 120)																			// requesting 120,120 (gallery default); note that h trumps w
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/120px-A.png", 120, 146)						// w=120 exists, but should not be used
    +				,	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/99px-A.png",  99, 121)						// true width should be w=98, but b/c of rounding off of large 2976, getting 99 instead
    +				,	fxt.img_("mem/src/en.wikipedia.org/7/70/A.png", 2976, 3623)										// orig image exists, and should not be downloaded
    +				)
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/99px.png"		, 99, 121)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|y||2?0,0|1?99,121")	// note that orig does not exist
    +				);
    +		fxt.tst();
    +		fxt.fil_absent(		 "mem/trg/en.wikipedia.org/raw/7/0/A.png");
    +		fxt	.Lnki_thumb_("A.png", 120, 120)																			// note that 120 is requested
    +			.Html_src_("file:///mem/trg/en.wikipedia.org/fit/7/0/A.png/99px.png")									// note that image used is 99
    +			.Html_size_(98, 120)																					// note that view width is 98
    +			.tst();
    +	}
    +	@Test  public void Do_not_download_orig_3() {// PURPOSE.defect: account for multiple thumbs; EX: File:Rembrandt van Rijn-De Nachtwacht-1642.jpg|1642.
    +		fxt	.ini_page_create_en_wiki			("File:A.png");
    +		fxt	.Lnki_thumb_("A.png", 454, 380)																			// standard request
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/454px-A.png", 454, 380)
    +				,	fxt.img_("mem/src/en.wikipedia.org/7/70/A.png", 3344, 2796)
    +				)
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/454px.png"		, 454, 380)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|y||2?0,0|1?454,380")	// note that orig does not exist
    +				);
    +		fxt.tst();
    +		fxt.fil_absent(		 "mem/trg/en.wikipedia.org/raw/7/0/A.png");
    +		fxt	.Lnki_thumb_("A.png", 718, 600)																			// this is the defect; 718 was not being brought down; instead 454 was being reused
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/718px-A.png", 718, 600)
    +				,	fxt.img_("mem/src/en.wikipedia.org/7/70/A.png", 3344, 2796)
    +				)
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/718px.png"		, 718, 600)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|y||2?0,0|1?454,380;1?718,600")	// note that orig does not exist
    +				)
    +			.tst();
    +	}
    +	@Test  public void Do_not_download_orig_height() {// PURPOSE: handles links with only height specified; EX: [[File:Fresh_Pesto.jpeg|x210px|center]]
    +		fxt	.ini_page_create_en_wiki			("File:A.png");
    +		fxt	.Lnki_thumb_("A.png", -1, 210)																			// height-only request
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/291px-A.png", 291, 210)
    +				,	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/220px-A.png", 220, 159)	
    +				,	fxt.img_("mem/src/en.wikipedia.org/7/70/A.png", 2910, 2100)
    +				)
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/291px.png"		, 291, 210)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|y||2?0,0|1?220,159;1?291,210")	// note that orig does not exist
    +				);
    +		fxt.tst();
    +		fxt	.Lnki_thumb_("A.png", -1, 210)																	
    +		.Html_src_("file:///mem/trg/en.wikipedia.org/fit/7/0/A.png/291px.png")									
    +		.Html_size_(291, 210)																					
    +		.tst();
    +	}
    +	@Test  public void Upright_size_incorrect() {// PURPOSE.fix: incorrect image size being brought down; EX: w:ASCII; [[Image:ASCII Code Chart.svg|thumb|right|upright=1.6]]; 264, but should be 350 
    +		fxt	.ini_page_create_en_wiki			("File:A.png");
    +		fxt	.Lnki_("A.png", true, -1, -1, 1.6, Xof_lnki_time.Null_as_int)											// upright
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/7/70/A.png", 830, 328)
    +				,	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/350px-A.png", 350, 138)	
    +				)
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/350px.png"		, 350, 138)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|y||2?0,0|1?350,138")
    +				);
    +		fxt.tst();
    +	}
    +	@Test  public void Width_height_retrieves_wrong_size() {// PURPOSE.fix: EX: c:Yellowstone Park; [[Image:YellowstoneLake.jpg|Yellowstone Lake|120x120px|thumb]];
    +		fxt	.ini_page_create_en_wiki			("File:A.png");
    +		fxt	.Lnki_thumb_("A.png", 120, 120)
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/7/70/A.png", 1756, 1204)
    +				,	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/120px-A.png", 119, 82)						// NOTE: wmf has 119px width, even though 120px file_name
    +				)
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/119px.png"		, 119, 82)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|y||2?0,0|1?119,82")
    +				);
    +		fxt.tst();
    +		fxt	.Lnki_thumb_("A.png", 120, 120)																	
    +		.Html_src_("file:///mem/trg/en.wikipedia.org/fit/7/0/A.png/119px.png")
    +		.Html_size_(120, 83)
    +		.tst();
    +	}
    +	@Test  public void Svg_thumb_can_be_bigger_than_orig__convert() {// PURPOSE: svg thumbs allowed to exceed orig in size; EX: w:Portal:Music; [[File:Treble a.svg|left|160px]]
    +		fxt	.ini_page_create_en_wiki			("File:A.svg");
    +		fxt	.Lnki_thumb_("A.svg", 220)																					// thumb = 220
    +			.Src(	fxt.svg_("mem/src/en.wikipedia.org/7/75/A.svg", 110, 100)											// orig = 110
    +				)
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/5/A.svg/220px.png"		, 220, 200)							// thumb = 220; not limited to 110
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/75.csv"		, "A.svg|y||1?110,100|1?220,200")
    +				)
    +			.tst(
    +			);
    +		fxt	.Lnki_thumb_("A.svg", 220)																	
    +		.Html_src_("file:///mem/trg/en.wikipedia.org/fit/7/5/A.svg/220px.png")
    +		.Html_size_(220, 200)
    +		.tst();
    +	}
    +	@Test  public void Ogv_width_seek_again_should_dirty() { // PURPOSE: outlier case wherein (a) downloading thumb then (b) downloading thumb seek; (b) does not dirty file since (a) exists; PAGE:en.w:Wikipedia
    +		fxt.Src_en_wiki_repo().Ext_rules().Get_or_new(Xof_ext_.Bry_ogv).View_max_(0);
    +		Io_mgr.Instance.SaveFilStr("mem/xowa/file/#meta/en.wikipedia.org/d/d0.csv", "A.ogv|0||2?0,0|1?300,40\n");	// simulate (a)
    +		fxt	.Lnki_("A.ogv", true, -1, -1, -1, 5)															
    +		.Src(	fxt.img_("mem/src/commons.wikimedia.org/thumb/d/d0/A.ogv/-1px-seek%3D5-A.ogv.jpg", 300, 40)	
    +				)
    +		.Trg(	fxt.img_("mem/trg/commons.wikimedia.org/fit/d/0/A.ogv/300px@5.jpg", 300, 40)
    +			,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/d/d0.csv", "A.ogv|0||2?0,0|1?300,40@5")
    +			)
    +		.tst();
    +		fxt.Src_en_wiki_repo().Ext_rules().Get_or_new(Xof_ext_.Bry_ogv).View_max_(-1);
    +	}
    +	@Test   public void Webm() {
    +		fxt	.ini_page_create_commons			("File:A.webm");
    +		fxt	.Lnki_thumb_("A.webm", 220)
    +		.Src(	fxt.ogg_("mem/src/commons.wikimedia.org/3/34/A.webm")
    +			,	fxt.img_("mem/src/commons.wikimedia.org/thumb/3/34/A.webm/220px--A.webm.jpg", 300, 40)	
    +				)
    +		.Trg(	fxt.ogg_("mem/trg/commons.wikimedia.org/raw/3/4/A.webm")
    +			,	fxt.img_("mem/trg/commons.wikimedia.org/fit/3/4/A.webm/300px.jpg", 300, 40)
    +			,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/3/34.csv", "A.webm|0||1?0,0|1?300,40")
    +			)
    +		.tst();
    +	}
    +	@Test  public void Ogv_thumb() {// d00d1d5019e37cc219a91a2f8ad47bfe
    +		fxt	.ini_page_create_commons			("File:A.ogv");
    +		fxt	.Lnki_orig_("A.ogv")
    +			.Src(	fxt.img_("mem/src/commons.wikimedia.org/thumb/d/d0/A.ogv/-1px--A.ogv.jpg", 300, 40))
    +			.Trg(	fxt.img_("mem/trg/commons.wikimedia.org/fit/d/0/A.ogv/300px.jpg", 300, 40)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/d/d0.csv", "A.ogv|0||0?0,0|1?300,40")
    +				)
    +			.tst();
    +		fxt	.Lnki_orig_("A.ogv")																	
    +		.Html_src_("file:///mem/trg/commons.wikimedia.org/fit/d/0/A.ogv/300px.jpg")
    +		.Html_size_(300, 40)
    +		.tst();
    +	}
    +	@Test  public void Thumbtime_ignored_if_non_media() { // PURPOSE: ignore thumbtime if not media; PAGE:en.w:Moon; EX:[[File:A.png|thumbtime=0.02]] DATE:2014-07-22
    +		fxt	.ini_page_create_en_wiki("File:A.png");
    +		fxt	.Lnki_("A.png", true, 90, Xof_img_size.Size__neg1, Xof_img_size.Size__neg1, 2)	// thumbtime of 2 specified; will be ignored below
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/90px-A.png", 90, 80))
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/90px.png", 90, 80)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv", "A.png|y||2?0,0|1?90,80")
    +				);
    +		fxt.tst();
    +	}
    +
    +//		@Test  public void Ogg_full_skip() {	// DISABLED: 2012-12-03; not sure about logic
    +//			fxt	.ini_page_create_commons			("File:A.ogg");
    +//			fxt .En_wiki().File_mgr().Repo_mgr().Repos_get_at(0).Src().Ext_rules().Get_or_new(Xof_ext_.Bry_ogg).View_max_(0);		// set ogg to do not download
    +//			fxt	.Lnki_orig_("A.ogg")
    +//				.Src(	fxt.ogg_("mem/src/commons.wikimedia.org/4/2/A.ogg"))
    +//				.Trg(	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/4/42.csv", "2|A.ogg|0|2?0,0|")
    +//					);
    +//			fxt.tst();
    +//			fxt .En_wiki().File_mgr().Repo_mgr().Repos_get_at(0).Src().Ext_rules().Get_or_new(Xof_ext_.Bry_ogg).View_max_(1000);	// undo above
    +//		}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_fxt.java b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_fxt.java
    index a27517de8..e70307b88 100644
    --- a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_fxt.java
    +++ b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_fxt.java
    @@ -13,3 +13,48 @@ 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.files.xfers; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import gplx.core.primitives.*; import gplx.dbs.*;
    +import gplx.core.ios.*; import gplx.xowa.wikis.domains.*; import gplx.xowa.files.*;
    +import gplx.xowa.parsers.*; import gplx.xowa.parsers.lnkis.*;
    +public class Xof_xfer_queue_html_fxt extends Xof_xfer_queue_base_fxt {
    +	private final    Xof_xfer_queue queue = new Xof_xfer_queue();
    +	@Override public void Clear(boolean src_repo_is_wmf) {
    +		Db_conn_bldr.Instance.Reg_default_mem();
    +		super.Clear(src_repo_is_wmf);
    +		this.Api_size().Clear();
    +	}
    +	public Xof_xfer_queue_html_fxt Lnki_orig_ (String lnki_ttl)							{return Lnki_(lnki_ttl, Bool_.N, Xof_img_size.Size__neg1, Xof_img_size.Size__neg1, Xop_lnki_tkn.Upright_null, Xof_lnki_time.Null_as_int);}
    +	public Xof_xfer_queue_html_fxt Lnki_thumb_(String lnki_ttl, int lnki_w)				{return Lnki_(lnki_ttl, Bool_.Y, lnki_w, Xof_img_size.Size__neg1, Xop_lnki_tkn.Upright_null, Xof_lnki_time.Null_as_int);}
    +	public Xof_xfer_queue_html_fxt Lnki_thumb_(String lnki_ttl, int lnki_w, int lnki_h) {return Lnki_(lnki_ttl, Bool_.Y, lnki_w, lnki_h, Xop_lnki_tkn.Upright_null, Xof_lnki_time.Null_as_int);}
    +	public Xof_xfer_queue_html_fxt Lnki_(String lnki_ttl, boolean thumb, int lnki_w, int lnki_h, double upright, int seek_time) { // NOTE: only one xfer_itm; supports one Lnki_ per test only
    +		Xowe_wiki wiki = this.En_wiki();
    +		Xop_ctx ctx = wiki.Parser_mgr().Ctx();
    +		xfer_itm = wiki.Html_mgr().Html_wtr().Lnki_wtr().File_wtr().Lnki_eval(Xof_exec_tid.Tid_wiki_page, ctx, ctx.Page(), queue, Bry_.new_u8(lnki_ttl), thumb ? Xop_lnki_type.Id_thumb : Xop_lnki_type.Id_null, upright, lnki_w, lnki_h, Xof_lnki_time.X_int(seek_time), Xof_lnki_page.Null, false);
    +		return this;
    +	}
    +	public Xof_file_itm Xfer_itm() {return xfer_itm;} private Xof_file_itm xfer_itm; 
    +	public Xof_xfer_queue_html_fxt Src(Io_fil... v) {return (Xof_xfer_queue_html_fxt)Src_base(v);}
    +	public Xof_xfer_queue_html_fxt Trg(Io_fil... v) {return (Xof_xfer_queue_html_fxt)Trg_base(v);}
    +	public Xof_xfer_queue_html_fxt Html_src_(String v) {return (Xof_xfer_queue_html_fxt)Html_src_base_(v);}
    +	public Xof_xfer_queue_html_fxt Html_size_(int w, int h) {this.Html_w_(w); this.Html_h_(h); return this;}
    +	public Xof_xfer_queue_html_fxt Html_orig_src_(String v) {html_orig_src = v; return this;} private String html_orig_src;
    +	public Xof_xfer_queue_html_fxt ini_page_api(String wiki_str, String ttl_str, String redirect_str, int orig_w, int orig_h) {return ini_page_api(wiki_str, ttl_str, redirect_str, orig_w, orig_h, true);}
    +	public Xof_xfer_queue_html_fxt ini_page_api(String wiki_str, String ttl_str, String redirect_str, int orig_w, int orig_h, boolean pass) {
    +		String wiki_key = String_.Eq(wiki_str, "commons") ? Xow_domain_itm_.Str__commons : Xow_domain_itm_.Str__enwiki;
    +		this.Api_size().Ini(wiki_key, ttl_str, redirect_str, orig_w, orig_h, pass);
    +		return this;
    +	}
    +	@gplx.Virtual public void tst() {
    +		Xowe_wiki wiki = this.En_wiki();
    +		ini_src_fils();
    +		wiki.File_mgr().Cfg_download().Enabled_(true);
    +		queue.Exec(wiki, Xoae_page.New(wiki, wiki.Ttl_parse(Bry_.new_a7("A"))));
    +		tst_trg_fils();
    +		if (this.html_orig_src   != null)	Tfds.Eq(this.html_orig_src  , xfer_itm.Html_orig_url().To_http_file_str());
    +		if (this.Html_view_src() != null)	Tfds.Eq(this.Html_view_src(), xfer_itm.Html_view_url().To_http_file_str());
    +		if (this.Html_w() != -1)			Tfds.Eq(this.Html_w(), xfer_itm.Html_w());
    +		if (this.Html_h() != -1)			Tfds.Eq(this.Html_h(), xfer_itm.Html_h());
    +		queue.Clear();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_offline_tst.java b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_offline_tst.java
    index a27517de8..f44314cf1 100644
    --- a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_offline_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_offline_tst.java
    @@ -13,3 +13,16 @@ 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.files.xfers; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import org.junit.*; import gplx.xowa.files.*; import gplx.xowa.parsers.lnkis.*;
    +public class Xof_xfer_queue_html_offline_tst {
    +	Xof_xfer_queue_html_fxt fxt = new Xof_xfer_queue_html_fxt();
    +	@Before public void init()		{fxt.Clear(true); fxt.Src_commons_repo().Tarball_(true); fxt.Src_en_wiki_repo().Tarball_(true);}
    +	@Test  public void Missing() {	// PURPOSE.fix: missing image was not being marked as missing; DATE:20121227
    +		fxt	.Lnki_("A.png", true, 220, -1, Xop_lnki_tkn.Upright_null, Xof_lnki_time.Null_as_int)
    +			.Src()
    +			.Trg(	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|x||0?0,0|")
    +				)
    +			.tst();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_wmf_api_tst.java b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_wmf_api_tst.java
    index a27517de8..add7ec38f 100644
    --- a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_wmf_api_tst.java
    +++ b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_queue_html_wmf_api_tst.java
    @@ -13,3 +13,159 @@ 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.files.xfers; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +import org.junit.*; import gplx.xowa.parsers.lnkis.*;
    +public class Xof_xfer_queue_html_wmf_api_tst {
    +	private final    Xof_xfer_queue_html_fxt fxt = new Xof_xfer_queue_html_fxt();
    +	@Before public void init()		{
    +		gplx.core.ios.IoEngine_system.Web_access_enabled = true;	// NOTE: must set to true, else Wmf_api calls below will always return false
    +		fxt.Clear(true); fxt.Src_commons_repo().Wmf_api_(true); fxt.Src_en_wiki_repo().Wmf_api_(true);
    +	}
    +	@Test  public void Thumb() {
    +		fxt	.ini_page_api("en_wiki", "A.png", "", 2200, 2000);
    +		fxt	.Lnki_thumb_("A.png", 220)
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/220px-A.png", 220, 200))
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/220px.png", 220, 200)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv", "A.png|y||2?2200,2000|1?220,200")
    +				)
    +			.tst();
    +	}
    +	@Test  public void Redirect() {
    +		fxt	.ini_page_api("en_wiki", "B.png", "A.png", 2200, 2000);
    +		fxt	.Lnki_thumb_("B.png", 220)
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/220px-A.png", 220, 200))
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/220px.png", 220, 200)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/5/57.csv", "B.png|y|A.png|2?2200,2000|1?220,200")
    +				)
    +			.tst();
    +	}
    +	@Test  public void Svg_thumb_can_be_bigger_than_orig__download() {// PURPOSE: svg thumbs allowed to exceed orig in size; EX: w:Portal:Music; [[File:Treble a.svg|left|160px]]
    +		fxt	.ini_page_api("en_wiki", "A.svg", "", 110, 100);															// put orig of 110,100 on server
    +		fxt	.Lnki_thumb_("A.svg", 220)																					// request 220
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/thumb/7/75/A.svg/220px-A.svg.png", 220, 200)						// thumb = 220
    +				)
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/5/A.svg/220px.png"		, 220, 200)							// thumb = 220
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/75.csv"		, "A.svg|y||2?110,100|1?220,200")
    +			)
    +			.tst();
    +		fxt	.Lnki_thumb_("A.svg", 220)																	
    +		.Html_src_("file:///mem/trg/en.wikipedia.org/fit/7/5/A.svg/220px.png")
    +		.Html_size_(220, 200)
    +		.tst();
    +	}
    +	@Test  public void Pdf() {// PURPOSE: main page always assumes size of 800x600; if actual size does not scale to 800x600, don't redownload; [[File:Physical world.pdf|thumb]]
    +		fxt.ini_page_api("en_wiki", "A.pdf", "", 6600, 5100);
    +		fxt	.ini_page_create_commons			("File:A.pdf");
    +		fxt	.Lnki_thumb_("A.pdf", 800, 600)
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/e/ef/A.pdf", 6600, 5100)
    +				,	fxt.img_("mem/src/en.wikipedia.org/thumb/e/ef/A.pdf/page1-777px-A.pdf.jpg", 777, 600)
    +				)
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/e/f/A.pdf/777px.jpg", 777, 600)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/e/ef.csv", "A.pdf|y||2?6600,5100|1?777,600")
    +				)
    +			.tst();
    +		fxt	.Lnki_thumb_("A.pdf", 800, 600)
    +			.Html_src_("file:///mem/trg/en.wikipedia.org/fit/e/f/A.pdf/777px.jpg")
    +			.Html_size_(777, 600)
    +			.tst();
    +	}
    +	@Test  public void Upright_defect() {	// PURPOSE.fix: upright not working;  PAGE:en.w:Beethoven; [[File:Rudolf-habsburg-olmuetz.jpg|thumb|upright|]]; changed upright to = Upright_default; DATE:2014-05-23
    +		fxt	.ini_page_api("en_wiki", "A.png", "", 1378, 1829);
    +		fxt	.Lnki_("A.png", true, -1, -1, Xof_img_size.Upright_default_marker, Xof_lnki_time.Null_as_int)
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/170px-A.png", 170, 226))
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/170px.png", 170, 226)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|y||2?1378,1829|1?170,226")
    +				)
    +			.tst();
    +	}
    +	@Test  public void Height_only() {	// PURPOSE.fix: height only was still using old infer-size code; EX:w:[[File:Upper and Middle Manhattan.jpg|x120px]]; DATE:2012-12-27
    +		fxt	.ini_page_api("en_wiki", "A.png", "", 12591, 1847);
    +		fxt	.Lnki_("A.png", false, -1, 130, Xop_lnki_tkn.Upright_null, Xof_lnki_time.Null_as_int)
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/887px-A.png", 887, 130))
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/887px.png", 887, 130)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|y||2?12591,1847|1?887,130")
    +				)
    +			.tst();
    +	}
    +	@Test  public void Width_only_height_ignored() {// PURPOSE.fix: if height is not specified, do not recalc; needed when true scaled height is 150x151 but WM has 150x158; defect would discard 150x158; EX:[[File:Tokage_2011-07-15.jpg|150px]] simple.wikipedia.org/wiki/2011_Pacific_typhoon_season; DATE:2013-06-03
    +		fxt	.ini_page_api("en_wiki", "A.png", "", 4884, 4932);
    +		fxt	.Lnki_("A.png", true, 150, -1, Xop_lnki_tkn.Upright_null, Xof_lnki_time.Null_as_int)
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/thumb/7/70/A.png/150px-A.png", 150, 158))
    +			.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/7/0/A.png/150px.png", 150, 158)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|y||2?4884,4932|1?150,158")
    +				)
    +			.tst();
    +	}
    +	@Test  public void Missing_was_not_being_marked() {	// PURPOSE.fix: missing image was not showing up as repo=x in meta; DATE:2013-01-10
    +		fxt	.Lnki_("A.png", false, -1, 130, Xop_lnki_tkn.Upright_null, Xof_lnki_time.Null_as_int)
    +			.Src()
    +			.Trg(	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|x||0?0,0|")
    +				)
    +			.tst();
    +	}
    +	@Test  public void Missing_was_not_redownloaded() {	// PURPOSE.fix: missing image was not being redownloaded; DATE:2013-01-26
    +		fxt.save_(fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv", "A.png|x||0?0,0|"));			// mark file as missing
    +		fxt	.ini_page_api("en_wiki", "A.png", "", 220, 200);
    +		fxt.En_wiki().File_mgr().Cfg_download().Redownload_(Xof_cfg_download.Redownload_missing);			// redownload for missing
    +		fxt	.Lnki_orig_("A.png")
    +			.Src(	fxt.img_("mem/src/en.wikipedia.org/7/70/A.png", 220, 200))
    +			.Trg(	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv"		, "A.png|x||1?220,200|")// check that file shows up
    +				)
    +			.tst();
    +		fxt.En_wiki().File_mgr().Cfg_download().Redownload_(Xof_cfg_download.Redownload_none);				// redownload back to none (for other tests)
    +	}
    +	@Test  public void Error_should_not_abort() {	// PURPOSE: API sometimes returns xml but no  node; try to download anyway
    +		fxt	.ini_page_api("commons", "A.png", "", 2200, 2000, false);
    +		fxt	.Lnki_thumb_("A.png", 220)
    +			.Src(	fxt.img_("mem/src/commons.wikimedia.org/thumb/7/70/A.png/220px-A.png", 220, 200))
    +			.Trg(	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv", "A.png|0||0?0,0|1?220,200")
    +				)
    +			.tst();
    +	}
    +	@Test   public void Tilde() {
    +		fxt	.ini_page_api("en_wiki", "A~.png", "", 2200, 2000);
    +		fxt	.Lnki_thumb_("A~.png", 220)
    +		.Src(	fxt.img_("mem/src/en.wikipedia.org/thumb/a/a5/A%7E.png/220px-A%7E.png", 220, 200))
    +		.Trg(	fxt.img_("mem/trg/en.wikipedia.org/fit/a/5/A~.png/220px.png", 220, 200)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/a/a5.csv", "A~~.png|y||2?2200,2000|1?220,200")	// NOTE: tildes are doubled in meta file
    +				)
    +				.tst();
    +	}
    +	@Test  public void Missing_from_1st_repo() {	// PURPOSE: WMF now requires that API goes to image's actual repo (used to accept http://en.wikipedia.org and return back http://commons.wikimedia.org) DATE:2013-03-11
    +		fxt	.ini_page_api("commons", "A.png", "B.png", 2200, 2000);	// put the redirect in commons wiki
    +		fxt	.Lnki_thumb_("A.png", 220)
    +			.Src(	fxt.img_("mem/src/commons.wikimedia.org/thumb/5/57/B.png/220px-B.png", 220, 200))
    +			.Trg(	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv", "A.png|0|B.png|2?2200,2000|1?220,200")
    +				)
    +			.tst();
    +	}
    +	@Test  public void Ogg_audio() {	// PURPOSE: ogg is audio; (a) do not download thumb; (b) get from correct wiki;  DATE:2013-08-03
    +		fxt	.ini_page_create_commons("File:A.ogg");
    +		fxt	.ini_page_api("commons", "A.ogg", "", 0, 0);
    +		fxt	.Lnki_("A.ogg", false, -1, -1, 1, Xof_lnki_time.Null_as_int)
    +			.Src(	fxt.img_("mem/src/commons.wikimedia.org/4/42/A.ogg", 0, 0))
    +			.Trg(	fxt.img_("mem/trg/commons.wikimedia.org/raw/4/2/A.ogg", 0, 0)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/4/42.csv"		, "A.ogg|0||1?0,0|0?0,0")
    +				)
    +			.tst();
    +	}
    +	@Test  public void Height_should_precede_width() {// PURPOSE: height should precede width; EX: David_Self_Portrait.jpg; c:Jacques-Louis David
    +		fxt.save_(fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv", "A.png|z||2?0,0|1?86,121;1?120,168"));
    +		fxt	.Lnki_thumb_("A.png", 120, 120)
    +		.Html_src_("file:///mem/trg/en.wikipedia.org/fit/7/0/A.png/86px.png")	
    +		.Html_size_(85, 120)
    +		.tst();
    +	}
    +	@Test  public void Imap() { // PURPOSE: check that imap downloads orig, even when thumb is requested; DATE:2014-08-08
    +		fxt	.ini_page_create_commons("File:A.png");
    +		fxt	.ini_page_api("commons", "A.png", "", 180, 160);
    +		fxt	.Lnki_("A.png", true, 90, Xof_img_size.Size__neg1, Xof_img_size.Size__neg1, Xof_lnki_time.Null_as_int);	// thumbtime of 2 specified; will be ignored below
    +		fxt	.Xfer_itm().Html_elem_tid_(Xof_html_elem.Tid_imap);
    +		fxt	.Src(	fxt.img_("mem/src/commons.wikimedia.org/thumb/7/70/A.png/90px-A.png", 90, 80))
    +			.Trg(	fxt.img_("mem/trg/commons.wikimedia.org/fit/7/0/A.png/90px.png", 90, 80)
    +				,	fxt.reg_("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv", "A.png|0||2?180,160|1?90,80")	// check that orig has 180,160, not 0,0
    +				);
    +		fxt.tst();
    +	}
    +}
    +
    diff --git a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_rslt.java b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_rslt.java
    index a27517de8..585ce57f4 100644
    --- a/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_rslt.java
    +++ b/400_xowa/src/gplx/xowa/files/xfers/Xof_xfer_rslt.java
    @@ -13,3 +13,17 @@ 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.files.xfers; import gplx.*; import gplx.xowa.*; import gplx.xowa.files.*;
    +public class Xof_xfer_rslt {
    +	public boolean Pass() {return pass;} private boolean pass = true;
    +	public String Err_msg() {return err_msg;} private String err_msg = String_.Empty;
    +	public String Src() {return src;} private String src;
    +	public Io_url Trg() {return trg;} public Xof_xfer_rslt Trg_(Io_url v) {trg = v; return this;}  Io_url trg;
    +	public void Atrs_src_trg_(String src, Io_url trg) {this.src = src; this.trg = trg;}		
    +	public boolean Fail(String msg) {
    +		pass = false;
    +		err_msg = msg;
    +		return false;
    +	}
    +	public void Clear() {pass = true; err_msg = src = String_.Empty; trg = Io_url_.Empty;}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/Xoa_gui_mgr.java b/400_xowa/src/gplx/xowa/guis/Xoa_gui_mgr.java
    index a27517de8..54b3b4173 100644
    --- a/400_xowa/src/gplx/xowa/guis/Xoa_gui_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/Xoa_gui_mgr.java
    @@ -13,3 +13,150 @@ 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.guis; import gplx.*; import gplx.xowa.*;
    +import gplx.core.brys.fmtrs.*; import gplx.core.envs.*;
    +import gplx.gfui.*; import gplx.gfui.ipts.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.windows.*; import gplx.gfui.controls.standards.*;
    +import gplx.xowa.addons.wikis.searchs.*; import gplx.xowa.guis.menus.*; import gplx.xowa.guis.cmds.*; import gplx.xowa.apps.cfgs.*; import gplx.xowa.users.*;
    +import gplx.xowa.langs.*;
    +import gplx.xowa.guis.bnds.*; import gplx.xowa.guis.views.*; import gplx.xowa.guis.urls.url_macros.*; import gplx.xowa.addons.wikis.searchs.gui.htmlbars.*;
    +import gplx.xowa.guis.views.boots.*; import gplx.xowa.guis.views.nightmodes.*;
    +import gplx.gfui.layouts.swts.*;
    +public class Xoa_gui_mgr implements Gfo_evt_itm, Gfo_invk {
    +	public Xoa_gui_mgr(Xoae_app app) {
    +		this.ev_mgr = new Gfo_evt_mgr(this);
    +		this.app = app;
    +		this.browser_win = new Xog_win_itm(app, this);
    +		bnd_mgr = new Xog_bnd_mgr(browser_win);
    +		html_mgr = new Xog_html_mgr(app);
    +		menu_mgr = new Xog_menu_mgr(this);
    +	}
    +	public Gfo_evt_mgr Evt_mgr() {return ev_mgr;} private Gfo_evt_mgr ev_mgr;
    +	public Xoae_app App() {return app;} private Xoae_app app;
    +	public Xog_win_itm Browser_win() {return browser_win;} private final    Xog_win_itm browser_win;
    +	public IptCfgRegy Ipt_cfgs() {return ipt_cfgs;} IptCfgRegy ipt_cfgs = new IptCfgRegy();
    +	public Xog_bnd_mgr Bnd_mgr() {return bnd_mgr;} private Xog_bnd_mgr bnd_mgr;
    +	public Gfui_kit Kit() {return kit;} private Gfui_kit kit = Gfui_kit_.Mem();
    +	public Xog_cmd_mgr Cmd_mgr() {return cmd_mgr;} private Xog_cmd_mgr cmd_mgr = new Xog_cmd_mgr();
    +	public Xocfg_win Win_cfg() {return win_cfg;} private Xocfg_win win_cfg = new Xocfg_win();
    +	public Xog_layout Layout() {return layout;} private Xog_layout layout = new Xog_layout();
    +	public Xog_html_mgr Html_mgr() {return html_mgr;} private Xog_html_mgr html_mgr;
    +	public Xog_menu_mgr Menu_mgr() {return menu_mgr;} private Xog_menu_mgr menu_mgr;
    +	public Xog_url_macro_mgr Url_macro_mgr() {return url_macro_mgr;} private final    Xog_url_macro_mgr url_macro_mgr = new Xog_url_macro_mgr();
    +	public Xog_nightmode_mgr Nightmode_mgr() {return nightmode_mgr;} private final    Xog_nightmode_mgr nightmode_mgr = new Xog_nightmode_mgr();
    +	public void Show_prog() {
    +		// get rects for positioning
    +		RectAdp statusbar_rect = browser_win.Statusbar_grp().Rect();
    +		RectAdp prog_box_rect = browser_win.Prog_box().Rect();
    +
    +		// create window using rects
    +		GfuiWin memo_win = kit.New_win_utl("memo_win", browser_win.Win_box());
    +		memo_win.Layout_mgr_(new Swt_layout_mgr__grid().Cols_(1).Margin_w_(0).Margin_h_(0).Spacing_h_(0));
    +		memo_win.Rect_set(RectAdp_.new_
    +			( prog_box_rect.X()
    +			, statusbar_rect.Y() - 60     // 60=100 - 30 (height of title bar and window border)
    +			, prog_box_rect.Width() + 10  // 10=.Margin_w of statusbar_grp
    +			, 100));
    +
    +		// create text
    +		GfuiTextBox memo_txt = kit.New_text_box("memo_txt", memo_win, Keyval_.new_(GfuiTextBox_.Ctor_Memo, true));
    +		memo_txt.Layout_data_(new Swt_layout_data__grid().Grab_excess_w_(Bool_.Y).Grab_excess_h_(Bool_.Y).Align_w__fill_().Align_h__fill_());
    +		memo_txt.Text_(String_.Concat_lines_nl(browser_win.Usr_dlg().Gui_wkr().Prog_msgs().Xto_str_ary()));
    +
    +		// show and focus
    +		memo_win.Show();
    +		memo_win.Focus();
    +	}
    +	public void Init_by_app() {
    +		layout_Init();
    +		bnd_mgr.Init();
    +		menu_mgr.Init_by_app(app);
    +		if (app.Mode().Tid_is_gui()) {
    +			app.Gui__cbk_mgr().Reg(new gplx.xowa.guis.cbks.swts.Xog_cbk_wkr__swt(this));
    +			Io_url gfo_log_dir = app.Fsys_mgr().Root_dir().GenSubDir_nest("user", "anonymous", "app", "tmp", "xolog");
    +			Gfo_log_.Instance__set(new gplx.xowa.guis.cbks.swts.Gfo_log__swt(app.Gui__cbk_mgr()
    +				, Gfo_log_.New_url(gfo_log_dir)
    +				, new gplx.core.logs.Gfo_log_itm_wtr__csv()));
    +			gplx.core.logs.Gfo_log__file.Delete_old_files(gfo_log_dir, Gfo_log_.Instance);
    +		}
    +		win_cfg.Init_by_app(app);
    +		nightmode_mgr.Init_by_app(app);
    +	}
    +	public void Kit_(Gfui_kit kit) {
    +		this.kit = kit;
    +		kit.Kit_init(browser_win.Usr_dlg());
    +		kit.Kit_term_cbk_(Gfo_invk_cmd.New_by_key(app, Xoae_app.Invk_term_cbk));
    +		browser_win.Init_by_kit(kit);
    +		layout.Init(browser_win);
    +		cmd_mgr.Init_by_kit(app);
    +		app.Api_root().Init_by_kit(app);
    +		app.Addon_mgr().Init_by_kit(app, kit);
    +		menu_mgr.Menu_bldr().Init_by_kit(app, kit, app.Fsys_mgr().Bin_xowa_file_dir().GenSubDir_nest("app.menu"));
    +		menu_mgr.Init_by_kit();
    +		bnd_mgr.Init_by_kit(app);
    +		nightmode_mgr.Init_by_kit(app);
    +		Gfo_evt_mgr_.Sub_same_many(app.Usere(), this, Xoue_user.Evt_lang_changed);
    +		app.Sys_cfg().Lang_(app.Sys_cfg().Lang());	// NOTE: force refresh of lang. must occur after after gui_mgr init, else menu lbls will break
    +	}
    +	public void Lang_changed(Xol_lang_itm lang) {
    +		menu_mgr.Lang_changed(lang);
    +		browser_win.Lang_changed(lang);
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_kit))							return kit;
    +		else if	(ctx.Match(k, Invk_kit_))							this.kit = Gfui_kit_.Get_by_key(m.ReadStrOr("v", Gfui_kit_.Swt().Key()));
    +		else if	(ctx.Match(k, Invk_run))							Run(Rls_able_.Null);
    +		else if	(ctx.Match(k, Invk_browser_type))					kit.Cfg_set("HtmlBox", "BrowserType", Swt_kit.Cfg_Html_BrowserType_parse(m.ReadStr("v")));
    +		else if	(ctx.Match(k, Invk_xul_runner_path_))				kit.Cfg_set("HtmlBox", "XulRunnerPath", Bry_fmtr_eval_mgr_.Eval_url(app.Url_cmd_eval(), m.ReadBry("v")).Xto_api());
    +		else if	(ctx.Match(k, Invk_bnds))							return bnd_mgr;
    +		else if	(ctx.Match(k, Invk_bindings))						return ipt_cfgs;
    +		else if	(ctx.MatchIn(k, Invk_main_win, Invk_browser_win))	return browser_win;
    +		else if	(ctx.Match(k, Invk_win_opts))						return win_cfg; // used by xowa.gfs and Special:Search; DATE:2017-03-29
    +		else if	(ctx.Match(k, Invk_layout))							return layout;
    +		else if	(ctx.Match(k, Invk_html))							return html_mgr;
    +		else if	(ctx.Match(k, Invk_menus))							return menu_mgr;
    +		else if	(ctx.Match(k, Invk_cmds))							return cmd_mgr;
    +		else if	(ctx.Match(k, Invk_url_macros))						return url_macro_mgr;
    +		else if	(ctx.Match(k, Xoue_user.Evt_lang_changed))			Lang_changed((Xol_lang_itm)m.ReadObj("v", null));
    +		else throw Err_.new_unhandled(k);
    +		return this;
    +	}
    +	private static final String 
    +	  Invk_kit = "kit", Invk_kit_ = "kit_", Invk_browser_type = "browser_type_", Invk_xul_runner_path_ = "xul_runner_path_", Invk_run = "run"
    +	, Invk_main_win = "main_win", Invk_browser_win = "browser_win", Invk_bnds = "bnds"
    +	, Invk_bindings = "bindings", Invk_win_opts = "win_opts", Invk_layout = "layout", Invk_html = "html"
    +	, Invk_menus = "menus", Invk_cmds = "cmds", Invk_url_macros = "url_macros";
    +	public void Run(Rls_able splash_win) {
    +		Gfo_log_bfr log_bfr = app.Log_bfr();
    +		try {
    +			Xoa_gui_mgr ui_mgr = app.Gui_mgr();
    +			Gfui_kit kit = ui_mgr.Kit();
    +			ui_mgr.Kit_(kit); log_bfr.Add("app.gui.kit_init.done");
    +			Xog_win_itm main_win = ui_mgr.Browser_win();
    +			Xog_win_itm_.Show_win(main_win); log_bfr.Add("app.gui.win_load.done");
    +			Xog_tab_itm_read_mgr.Launch(main_win);
    +			app.Log_wtr().Log_to_session_direct(log_bfr.Xto_str());
    +			splash_win.Rls();
    +			kit.Kit_run();	// NOTE: enters thread-loop
    +		} catch (Exception e) {
    +			app.Usr_dlg().Warn_many("", "", "run_failed: ~{0} ~{1}", log_bfr.Xto_str(), Err_.Message_gplx_full(e));
    +			if (app.Gui_mgr().Kit() != null) app.Gui_mgr().Kit().Ask_ok("", "", Err_.Message_gplx_full(e));
    +		}
    +	}
    +	private void layout_Init() {
    +		Op_sys os = Op_sys.Cur();
    +		int html_box_w = -8; int html_box_h = -30;	// default adjustments since version 0.0.0.0; seems to work on XP and LNX
    +		switch (os.Tid()) {
    +			case Op_sys.Tid_wnt:
    +				switch (os.Sub_tid()) {
    +					case Op_sys.Sub_tid_win_xp:	break;	// NOOP; will keep values as above
    +					default:							// for all else, use Windows 7 border; note that Windows 8 is being detected as "Windows NT (unknown)", so need to use default; this may not work with Windows 2000
    +						html_box_w = -16; html_box_h = -40;	// Windows 7 has a thicker windows border and the html_box must be smaller else scroll bar gets cut off
    +						break;
    +				}
    +				break;
    +			default:
    +				break;
    +		}
    +		layout.Html_box().W_rel_(html_box_w).H_rel_(html_box_h);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/Xog_html_mgr.java b/400_xowa/src/gplx/xowa/guis/Xog_html_mgr.java
    index a27517de8..bf55254e6 100644
    --- a/400_xowa/src/gplx/xowa/guis/Xog_html_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/Xog_html_mgr.java
    @@ -13,3 +13,16 @@ 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.guis; import gplx.*; import gplx.xowa.*;
    +import gplx.xowa.htmls.portal.*;
    +public class Xog_html_mgr implements Gfo_invk {
    +	public Xog_html_mgr(Xoae_app app) {portal_mgr = new Xoa_portal_mgr(app);}
    +	public Xoa_portal_mgr Portal_mgr() {return portal_mgr;} private Xoa_portal_mgr portal_mgr;
    +	public String Auto_focus_id() {return auto_focus_id;} private String auto_focus_id = "";
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_auto_focus_id_))				auto_focus_id = m.ReadStr("v");
    +		else if	(ctx.Match(k, Invk_portal))						return portal_mgr;
    +		else return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}	private static final String Invk_auto_focus_id_ = "auto_focus_id_", Invk_portal = "portal";
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/Xogv_page_load_wkr.java b/400_xowa/src/gplx/xowa/guis/Xogv_page_load_wkr.java
    index a27517de8..895b4657b 100644
    --- a/400_xowa/src/gplx/xowa/guis/Xogv_page_load_wkr.java
    +++ b/400_xowa/src/gplx/xowa/guis/Xogv_page_load_wkr.java
    @@ -13,3 +13,35 @@ 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.guis; import gplx.*; import gplx.xowa.*;
    +import gplx.core.threads.*; import gplx.core.net.*;
    +import gplx.xowa.guis.history.*;
    +import gplx.xowa.apps.*; import gplx.xowa.wikis.*;
    +import gplx.xowa.htmls.*;
    +class Xogv_page_load_wkr implements Gfo_thread_wkr, Gfo_invk {
    +	private final    Xoav_wiki_mgr wiki_mgr; private final    Gfo_url_parser url_parser;
    +	private final    Xogv_tab_base tab; private final    Xog_history_itm old_itm, new_itm;
    +	public Xogv_page_load_wkr(Xoav_wiki_mgr wiki_mgr, Gfo_url_parser url_parser, Xogv_tab_base tab, Xog_history_itm old_itm, Xog_history_itm new_itm) {
    +		this.wiki_mgr = wiki_mgr; this.url_parser = url_parser; this.tab = tab; this.old_itm = old_itm; this.new_itm = new_itm;
    +	}
    +	public String			Thread__name() {return Thread_name;} public static final String Thread_name = "xowa.page_load";
    +	public boolean			Thread__resume() {return true;}
    +	public void Thread__exec() {
    +		Xoh_page new_hpg = Fetch_page(new_itm.Wiki(), new_itm.Page(), new_itm.Qarg());
    +		tab.Show_page(old_itm, new_itm, new_hpg);
    +	}
    +	private Xoh_page Fetch_page(byte[] wiki_domain, byte[] page_bry, byte[] qarg_bry) {
    +		Xowv_wiki wiki = (Xowv_wiki)wiki_mgr.Get_by_or_null(wiki_domain);
    +		if (wiki == null) return Xoh_page.New_missing();	// wiki does not exist; happens with xwiki; PAGE:s.w:Photon; EX:[[wikt:transmit]]; DATE:2015-04-27
    +		Xoa_ttl ttl = wiki.Ttl_parse(page_bry);
    +		Gfo_url url = url_parser.Parse(Bry_.Add(wiki_domain, Byte_ascii.Slash_bry, page_bry, qarg_bry));
    +		Xoh_page rv = new Xoh_page();
    +		wiki.Pages_get(rv, url, ttl);
    +		return rv;
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_exec))	this.Thread__exec();
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}	public static final String Invk_exec = "exec";
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/Xogv_tab_base.java b/400_xowa/src/gplx/xowa/guis/Xogv_tab_base.java
    index a27517de8..b8a128758 100644
    --- a/400_xowa/src/gplx/xowa/guis/Xogv_tab_base.java
    +++ b/400_xowa/src/gplx/xowa/guis/Xogv_tab_base.java
    @@ -13,3 +13,48 @@ 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.guis; import gplx.*; import gplx.xowa.*;
    +import gplx.core.net.*;
    +import gplx.xowa.htmls.*; import gplx.xowa.guis.history.*;
    +import gplx.xowa.apps.*; import gplx.xowa.wikis.*; import gplx.xowa.apps.urls.*;
    +import gplx.core.threads.*;
    +public abstract class Xogv_tab_base {
    +	private Gfo_url_parser url_parser;
    +	private Xoav_wiki_mgr wiki_mgr;
    +	private Gfo_thread_pool thread_pool;
    +	public void Ctor(Xoav_wiki_mgr wiki_mgr, Gfo_thread_pool thread_pool, Gfo_url_parser url_parser) {this.wiki_mgr = wiki_mgr; this.thread_pool = thread_pool; this.url_parser = url_parser;}
    +	public Xog_history_stack History_stack()		{return history_stack;} private final    Xog_history_stack history_stack = new Xog_history_stack(); 
    +	public Xog_history_itm Cur_itm()				{return history_stack.Cur_itm();}
    +	public Xow_wiki Get_wiki_or_null(byte[] key)	{return wiki_mgr.Get_by_or_null(key);}
    +	public Xoh_page Go_to(byte[] page)				{return Go_to(history_stack.Cur_itm().Wiki(), page, Bry_.Empty, Bry_.Empty, false, "");}
    +	public Xoh_page Go_to(byte[] wiki, byte[] page)	{return Go_to(wiki, page, Bry_.Empty, Bry_.Empty, false, "");}
    +	public Xoh_page Go_to(byte[] wiki, byte[] page, byte[] anch, byte[] qarg, boolean redirect_force, String bmk_pos) {
    +		Xog_history_itm old_itm = this.Cur_itm();
    +		Xog_history_itm new_itm = new Xog_history_itm(wiki, page, anch, qarg, redirect_force, bmk_pos);
    +		Xoh_page rv = Fetch_page_and_show(old_itm, new_itm);
    +		if (rv.Db().Page().Exists())
    +			history_stack.Add(new_itm);
    +		return rv;
    +	}
    +	public Xoh_page Go_bwd() {return Go_by_dir(Bool_.Y);}
    +	public Xoh_page Go_fwd() {return Go_by_dir(Bool_.N);}
    +	public Xoh_page Reload() {return Fetch_page_and_show(Cur_itm(), Cur_itm());}
    +	private Xoh_page Go_by_dir(boolean bwd) {
    +		Xog_history_itm old_itm = this.Cur_itm();
    +		Xog_history_itm new_itm = bwd ? history_stack.Go_bwd() : history_stack.Go_fwd();
    +		return Fetch_page_and_show(old_itm, new_itm);
    +	}
    +	private Xoh_page Fetch_page_and_show(Xog_history_itm old_itm, Xog_history_itm new_itm) {
    +		if (new_itm == Xog_history_itm.Null) return Xoh_page.New_missing();
    +		Fetch_page__bgn(new_itm.Wiki(), new_itm.Page(), new_itm.Qarg());
    +		Xoh_page new_hpg = new Xoh_page();
    +		// Thread_adp_.Start_by_key(Xogv_page_load_wkr.Thread_name, new Xogv_page_load_wkr(wiki_mgr, url_parser, this, old_itm, new_itm), Xogv_page_load_wkr.Invk_exec);
    +		thread_pool.Add_at_end(new Xogv_page_load_wkr(wiki_mgr, url_parser, this, old_itm, new_itm));
    +		thread_pool.Run();
    +		return new_hpg;
    +	}
    +	@gplx.Virtual protected void Fetch_page__bgn(byte[] wiki_domain, byte[] page_bry, byte[] qarg_bry) {}
    +	public void Srl_save(Bry_bfr bfr)					{history_stack.Srl_save(bfr);}
    +	public void Srl_load(byte[] raw)					{history_stack.Srl_load(raw);}
    +	public abstract void Show_page(Xog_history_itm old_itm, Xog_history_itm new_itm, Xoh_page new_hpg);
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_box.java b/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_box.java
    index a27517de8..7034ced7d 100644
    --- a/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_box.java
    +++ b/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_box.java
    @@ -13,3 +13,37 @@ 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.guis.bnds; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Xog_bnd_box {
    +	private final    Ordered_hash key_hash = Ordered_hash_.New();
    +	private final    Hash_adp cmd_hash = Hash_adp_.New();
    +	public Xog_bnd_box(int tid, String key) {
    +		this.tid = tid;
    +		this.key = key;
    +	}
    +	public int Tid() {return tid;} private final    int tid;
    +	public String Key() {return key;} private final    String key;
    +	public int Len() {return key_hash.Count();}
    +	public Xog_bnd_itm Get_at(int i) {return (Xog_bnd_itm)key_hash.Get_at(i);}
    +	public void Add(Xog_bnd_itm itm) {
    +		key_hash.Add_if_dupe_use_nth(itm.Key(), itm);	// Add_if_dupe_use_nth, else Xou_user_tst.Run fails; DATE:2014-05-15
    +
    +		// add by cmd; needed b/c gfui.ipt_mgr hashes by cmd ("sandbox"), not key ("sandbox-1"); DATE:2016-12-25
    +		List_adp list = (List_adp)cmd_hash.Get_by(itm.Cmd());
    +		if (list == null) {
    +			list = List_adp_.New();
    +			cmd_hash.Add(itm.Cmd(), list);
    +		}
    +		list.Add(itm);
    +	}
    +	public List_adp Get_list_by_cmd(String cmd) {return (List_adp)cmd_hash.Get_by(cmd);}
    +	public void Del(String key) {
    +		// delete from key_hash
    +		Xog_bnd_itm itm = (Xog_bnd_itm)key_hash.Get_by(key);
    +		key_hash.Del(key);
    +
    +		// delete from cmd_hash
    +		List_adp list = (List_adp)cmd_hash.Get_by(itm.Cmd());
    +		if (list != null) list.Del(itm);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_box_.java b/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_box_.java
    index a27517de8..0b4e1a4fa 100644
    --- a/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_box_.java
    +++ b/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_box_.java
    @@ -13,3 +13,127 @@ 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.guis.bnds; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.gfui.*; import gplx.gfui.ipts.*; import gplx.gfui.controls.elems.*;
    +import gplx.xowa.guis.views.*; import gplx.xowa.guis.cmds.*;
    +public class Xog_bnd_box_ {
    +	public static final    String Key_browser = "browser", Key_browser_url = "browser.url", Key_browser_search = "browser.search", Key_browser_allpages = "browser.allpages", Key_browser_html = "browser.html", Key_browser_find = "browser.find", Key_browser_prog = "browser.prog", Key_browser_info = "browser.info";
    +	public static final    String Gui_browser = "Window", Gui_browser_url = "Url bar", Gui_browser_search = "Search box", Gui_browser_allpages = "Allpages box", Gui_browser_html = "HTML browser", Gui_browser_find = "Find box", Gui_browser_prog = "Status bar", Gui_browser_info = "System Messages box";
    +	public static final int Tid__max = 8, Tid_browser = 0, Tid_browser_url = 1, Tid_browser_search = 2, Tid_browser_allpages = 3, Tid_browser_html = 4, Tid_browser_find = 5, Tid_browser_prog = 6, Tid_browser_info = 7;
    +	public static final int Ary_len = Tid__max;
    +	public static Xog_bnd_box[] Ary() {
    +		if (ary != null) return ary;
    +		ary = new Xog_bnd_box[Tid__max];
    +		ary_init(ary, Tid_browser				, Key_browser);
    +		ary_init(ary, Tid_browser_url			, Key_browser_url);
    +		ary_init(ary, Tid_browser_search		, Key_browser_search);
    +		ary_init(ary, Tid_browser_allpages		, Key_browser_allpages);
    +		ary_init(ary, Tid_browser_html			, Key_browser_html);
    +		ary_init(ary, Tid_browser_find			, Key_browser_find);
    +		ary_init(ary, Tid_browser_prog			, Key_browser_prog);
    +		ary_init(ary, Tid_browser_info			, Key_browser_info);
    +		return ary;
    +	}	private static Xog_bnd_box[] ary;
    +	private static void ary_init(Xog_bnd_box[] ary, int tid, String key) {ary[tid] = new Xog_bnd_box(tid, key);}
    +	public static String To_gui_str(String key) {
    +		int tid = Xto_sys_int(key);
    +		return Xto_gui_str(tid);
    +	}
    +	public static String To_key_str(String gui) {
    +		int tid = Xby_gui_str(gui);
    +		return Xto_sys_str(tid);
    +	}
    +	public static int[] Xto_sys_int_ary(String s) {
    +		String[] ary = String_.Split(s, "|");
    +		int len = ary.length;
    +		int[] rv = new int[len];
    +		for (int i = 0; i < len; i++)
    +			rv[i] = Xto_sys_int(ary[i]);
    +		return rv;
    +	}
    +	public static int Xto_sys_int(String s) {
    +		if		(String_.Eq(s, Key_browser))			return Tid_browser;
    +		else if	(String_.Eq(s, Key_browser_url))		return Tid_browser_url;
    +		else if	(String_.Eq(s, Key_browser_search))		return Tid_browser_search;
    +		else if	(String_.Eq(s, Key_browser_allpages))	return Tid_browser_allpages;
    +		else if	(String_.Eq(s, Key_browser_html))		return Tid_browser_html;
    +		else if	(String_.Eq(s, Key_browser_find))		return Tid_browser_find;
    +		else if	(String_.Eq(s, Key_browser_prog))		return Tid_browser_prog;
    +		else if	(String_.Eq(s, Key_browser_info))		return Tid_browser_info;
    +		else											throw Err_.new_unhandled(s);
    +	}
    +	public static String Xto_sys_str(int v) {
    +		switch (v) {
    +			case Tid_browser:					return Key_browser;
    +			case Tid_browser_url:				return Key_browser_url;
    +			case Tid_browser_search:			return Key_browser_search;
    +			case Tid_browser_allpages:			return Key_browser_allpages;
    +			case Tid_browser_html:				return Key_browser_html;
    +			case Tid_browser_find:				return Key_browser_find;
    +			case Tid_browser_prog:				return Key_browser_prog;
    +			case Tid_browser_info:				return Key_browser_info;
    +			default:							throw Err_.new_unhandled(v);
    +		}
    +	}
    +	public static String Xto_gui_str(int v) {
    +		switch (v) {
    +			case Tid_browser:					return Gui_browser;
    +			case Tid_browser_url:				return Gui_browser_url;
    +			case Tid_browser_search:			return Gui_browser_search;
    +			case Tid_browser_allpages:			return Gui_browser_allpages;
    +			case Tid_browser_html:				return Gui_browser_html;
    +			case Tid_browser_find:				return Gui_browser_find;
    +			case Tid_browser_prog:				return Gui_browser_prog;
    +			case Tid_browser_info:				return Gui_browser_info;
    +			default:							throw Err_.new_unhandled(v);
    +		}
    +	}
    +	public static int Xby_gui_str(String s) {
    +		if		(String_.Eq(s, Gui_browser))			return Tid_browser;
    +		else if	(String_.Eq(s, Gui_browser_url))		return Tid_browser_url;
    +		else if	(String_.Eq(s, Gui_browser_search))		return Tid_browser_search;
    +		else if	(String_.Eq(s, Gui_browser_allpages))	return Tid_browser_allpages;
    +		else if	(String_.Eq(s, Gui_browser_html))		return Tid_browser_html;
    +		else if	(String_.Eq(s, Gui_browser_find))		return Tid_browser_find;
    +		else if	(String_.Eq(s, Gui_browser_prog))		return Tid_browser_prog;
    +		else if	(String_.Eq(s, Gui_browser_info))		return Tid_browser_info;
    +		else											throw Err_.new_unhandled(s);
    +	}
    +	public static void Set_bnd_for_grp(byte mode, Xog_win_itm win, Xog_cmd_mgr_invk invk_mgr, Xog_bnd_box box, Xog_bnd_itm itm, IptArg ipt) {
    +		GfuiElem box_elem = null;
    +		String grp_key = box.Key();
    +		if		(String_.Eq(grp_key, Xog_bnd_box_.Key_browser_html))			{Set_bnd_for_tab(mode, win.Tab_mgr(), invk_mgr, box, itm, ipt); return;}
    +		else if	(String_.Eq(grp_key, Xog_bnd_box_.Key_browser))					box_elem = win.Win_box();
    +		else if	(String_.Eq(grp_key, Xog_bnd_box_.Key_browser_url))				box_elem = win.Url_box();
    +		else if	(String_.Eq(grp_key, Xog_bnd_box_.Key_browser_search))			box_elem = win.Search_box();
    +		else if	(String_.Eq(grp_key, Xog_bnd_box_.Key_browser_allpages))		box_elem = win.Allpages_box();
    +		else if	(String_.Eq(grp_key, Xog_bnd_box_.Key_browser_find))			box_elem = win.Find_box();
    +		else if	(String_.Eq(grp_key, Xog_bnd_box_.Key_browser_prog))			box_elem = win.Prog_box();
    +		else if	(String_.Eq(grp_key, Xog_bnd_box_.Key_browser_info))			box_elem = win.Info_box();
    +		else																	throw Err_.new_wo_type("unknown box", "grp", grp_key);
    +		Set_bnd_for_elem(mode, box, box_elem, invk_mgr, itm, ipt);
    +	}
    +	public static void Set_bnd_for_elem(byte mode, Xog_bnd_box box, GfuiElem box_elem, Xog_cmd_mgr_invk invk_mgr, Xog_bnd_itm itm, IptArg ipt) {
    +		switch (mode) {
    +			case Set_add:
    +				if (!IptArg_.Is_null_or_none(itm.Ipt()))
    +					IptBnd_.cmd_to_(IptCfg_.Null, box_elem, invk_mgr, itm.Cmd(), itm.Ipt());
    +				break;
    +			case Set_del_key:
    +				box_elem.IptBnds().Del_by_key(itm.Cmd());	// NOTE: delete by cmd, since GfuiIpts use cmd for key
    +				break;
    +			case Set_del_ipt:
    +				box_elem.IptBnds().Del_by_ipt(ipt);
    +				break;
    +			default: throw Err_.new_unhandled(mode);
    +		}
    +	}
    +	private static void Set_bnd_for_tab(byte mode, Xog_tab_mgr tab_mgr, Xog_cmd_mgr_invk invk_mgr, Xog_bnd_box box, Xog_bnd_itm itm, IptArg ipt) {
    +		int len = tab_mgr.Tabs_len();
    +		for (int i = 0; i < len; i++) {
    +			Xog_tab_itm tab_itm = tab_mgr.Tabs_get_at(i);
    +			Set_bnd_for_elem(mode, box, tab_itm.Html_box(), invk_mgr, itm, ipt);
    +		}
    +	}
    +	public static final byte Set_add = 0, Set_del_key = 1, Set_del_ipt = 2;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_itm.java b/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_itm.java
    index a27517de8..de3dad7f4 100644
    --- a/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_itm.java
    +++ b/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_itm.java
    @@ -13,3 +13,19 @@ 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.guis.bnds; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.gfui.*; import gplx.gfui.ipts.*;
    +public class Xog_bnd_itm {
    +	public Xog_bnd_itm(String key, boolean sys, String cmd, int box, IptArg ipt) {
    +		this.key = key; this.sys = sys; this.cmd = cmd; this.box = box; this.ipt = ipt;
    +		uid = ++Uid_next;
    +	}
    +	public String Key() {return key;} private String key;
    +	public int Uid() {return uid;} private int uid;
    +	public boolean Sys() {return sys;} private boolean sys;
    +	public String Cmd() {return cmd;} public void Cmd_(String v) {cmd = v;} private String cmd;
    +	public int Box() {return box;} private int box;
    +	public IptArg Ipt() {return ipt;} public void Ipt_to_none() {ipt = IptKey_.None;} private IptArg ipt;		
    +	public void Init_by_set(int box, IptArg ipt) {this.box = box; this.ipt = ipt;}
    +	private static int Uid_next = 0;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_mgr.java b/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_mgr.java
    index a27517de8..d0bd7b15a 100644
    --- a/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_mgr.java
    @@ -13,3 +13,393 @@ 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.guis.bnds; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.gfui.*; import gplx.gfui.ipts.*; import gplx.gfui.controls.elems.*;
    +import gplx.xowa.guis.views.*; import gplx.xowa.guis.cmds.*; import gplx.xowa.guis.menus.dom.*;
    +public class Xog_bnd_mgr implements Gfo_invk {
    +	private Xoae_app app;
    +	private Xog_win_itm win; private Xog_cmd_mgr_invk invk_mgr;
    +	private Xog_bnd_box[] boxs = Xog_bnd_box_.Ary();
    +	private List_adp startup_itms = List_adp_.New();
    +	private Ordered_hash regy = Ordered_hash_.New();
    +	public Xog_bnd_mgr(Xog_win_itm win) {this.win = win; invk_mgr = win.Gui_mgr().Cmd_mgr().Invk_mgr();}
    +	public int Len() {return regy.Count();}
    +	public Xog_bnd_itm Get_at(int i)			{return (Xog_bnd_itm)regy.Get_at(i);}
    +	public Xog_bnd_itm Get_or_null(String v)	{return (Xog_bnd_itm)regy.Get_by(v);}
    +	public void Init_by_kit(Xoae_app app) {
    +		this.app = app;
    +		Add_system_bnds();
    +		Add_custom_bnds();	// NOTE: should go after Add_system_bnds in case user overrides any;
    +		Bind_all();
    +
    +		app.Cfg().Sub_many_app(this, Run__show_remap_win);
    +		app.Cfg().Bind_many_app(this
    +		, "xowa.gui.shortcuts.xowa.app.exit-1"
    +		, "xowa.gui.shortcuts.xowa.nav.go_bwd-1"
    +		, "xowa.gui.shortcuts.xowa.nav.go_fwd-1"
    +		, "xowa.gui.shortcuts.xowa.nav.cfg.main-1"
    +		, "xowa.gui.shortcuts.xowa.nav.cfg.menus-1"
    +		, "xowa.gui.shortcuts.xowa.nav.wiki.main_page-1"
    +		, "xowa.gui.shortcuts.xowa.nav.wiki.sandbox-1"
    +		, "xowa.gui.shortcuts.xowa.nav.wiki.sandbox-2"
    +		, "xowa.gui.shortcuts.xowa.nav.wiki.random-1"
    +		, "xowa.gui.shortcuts.xowa.nav.wiki.allpages-1"
    +		, "xowa.gui.shortcuts.xowa.nav.wiki.search_title-1"
    +		, "xowa.gui.shortcuts.xowa.nav.wiki.search_full-1"
    +		, "xowa.gui.shortcuts.xowa.nav.wiki.search_per_cfg-1"
    +		, "xowa.gui.shortcuts.xowa.nav.help.help-1"
    +		, "xowa.gui.shortcuts.xowa.nav.help.about-1"
    +		, "xowa.gui.shortcuts.xowa.nav.help.change_log-1"
    +		, "xowa.gui.shortcuts.xowa.nav.help.diagnostics-1"
    +		, "xowa.gui.shortcuts.xowa.nav.help.xowa_update-1"
    +		, "xowa.gui.shortcuts.xowa.nav.help.xowa_main-1"
    +		, "xowa.gui.shortcuts.xowa.nav.help.xowa_blog-1"
    +		, "xowa.gui.shortcuts.xowa.nav.setup.download_central-1"
    +		, "xowa.gui.shortcuts.xowa.nav.setup.import_from_list-1"
    +		, "xowa.gui.shortcuts.xowa.nav.setup.import_from_script-1"
    +		, "xowa.gui.shortcuts.xowa.nav.setup.maintenance-1"
    +		, "xowa.gui.shortcuts.xowa.nav.setup.download-1"
    +		, "xowa.gui.shortcuts.xowa.nav.system_data.log_session-1"
    +		, "xowa.gui.shortcuts.xowa.nav.system_data.cfg_app-1"
    +		, "xowa.gui.shortcuts.xowa.nav.system_data.cfg_lang-1"
    +		, "xowa.gui.shortcuts.xowa.nav.system_data.usr_history-1"
    +		, "xowa.gui.shortcuts.xowa.nav.personal.item-1"
    +		, "xowa.gui.shortcuts.xowa.nav.personal.list-1"
    +		, "xowa.gui.shortcuts.xowa.gui.font.increase-1"
    +		, "xowa.gui.shortcuts.xowa.gui.font.decrease-1"
    +		, "xowa.gui.shortcuts.xowa.gui.font.reset-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.view.mode_read-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.view.mode_edit-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.view.mode_html-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.view.mode_html-2"
    +		, "xowa.gui.shortcuts.xowa.gui.page.view.refresh-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.view.reload-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.view.save_as-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.view.print-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.edit.save-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.edit.save_draft-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.edit.focus_edit_box-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.edit.preview-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.edit.dbg_tmpl-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.edit.dbg_tmpl-2"
    +		, "xowa.gui.shortcuts.xowa.gui.page.edit.dbg_html-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.edit.exec-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.selection.select_all-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.selection.copy-1"
    +		, "xowa.gui.shortcuts.xowa.gui.page.selection.save_file_as-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.url.focus-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.url.focus-2"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.url.exec-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.url.exec_by_paste-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.url.exec_by_paste-2"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.url.exec_new_tab_by_paste-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.url.restore-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.search.focus-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.search.exec-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.allpages.focus-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.allpages.exec-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.html.focus-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.html.focus-2"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.html.focus-3"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.html.focus-4"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.html.selection_focus_toggle-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.find.show-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.find.show_by_paste-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.find.hide-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.find.exec-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.find.find_fwd-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.find.find_bwd-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.find.case_toggle-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.find.wrap_toggle-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.prog.focus-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.prog_log.show-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.prog_log.show-2"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.info.focus-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.info.clear-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.new_dflt__at_dflt__focus_y-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.new_link__at_dflt__focus_y-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.new_link__at_dflt__focus_n-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.new_href__at_dflt__focus_y-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.select_bwd-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.select_bwd-2"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.select_fwd-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.select_fwd-2"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.select_by_idx_1-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.select_by_idx_2-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.select_by_idx_3-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.select_by_idx_4-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.select_by_idx_5-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.select_by_idx_6-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.select_by_idx_7-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.select_by_idx_8-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.select_by_idx_9-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.move_bwd-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.move_fwd-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.close_cur-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.close_others-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.close_to_bgn-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.close_to_end-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.close_undo-1"
    +		, "xowa.gui.shortcuts.xowa.gui.browser.tabs.pin_toggle-1"
    +		, "xowa.gui.shortcuts.xowa.html.tidy.toggle-1"
    +		, "xowa.gui.shortcuts.xowa.net.enabled_n_-1"
    +		, "xowa.gui.shortcuts.xowa.net.enabled_y_-1"
    +		, "xowa.gui.shortcuts.xowa.net.enabled_x_-1"
    +		, "xowa.gui.shortcuts.xowa.usr.bookmarks.add-1"
    +		, "xowa.gui.shortcuts.xowa.usr.bookmarks.show-1"
    +		, "xowa.gui.shortcuts.xowa.usr.history.goto_recent-1"
    +		, "xowa.gui.shortcuts.xowa.usr.history.show-1"
    +		);
    +	}
    +	public Xog_bnd_itm Set(Xog_bnd_itm itm, int new_box, IptArg new_ipt) {
    +		if (win.Win_box() == null) {	// kit not built yet; occurs when restoring bindings through cfg file; DATE:2014-05-16
    +			Xog_bnd_itm new_itm = new Xog_bnd_itm(itm.Key(), false, "", new_box, new_ipt);
    +			startup_itms.Add(new_itm);
    +			return new_itm;
    +		}
    +		Del(itm, new_ipt);
    +		itm.Init_by_set(new_box, new_ipt);
    +		Add(itm);
    +		return itm;
    +	}
    +	public void Del(Xog_bnd_itm new_bnd, IptArg new_ipt) {
    +		boolean new_ipt_exists = !IptArg_.Is_null_or_none(new_ipt);
    +		List_adp deleted = List_adp_.New();
    +
    +		// loop over each box
    +		for (int i = 0; i < Xog_bnd_box_.Ary_len; i++) {
    +			Xog_bnd_box old_box = boxs[i];
    +			int old_itms_len = old_box.Len();
    +
    +			// loop over each bnd
    +			for (int j = 0; j < old_itms_len; j++) {
    +				Xog_bnd_itm old_bnd = old_box.Get_at(j);
    +
    +				// if keys match, delete old_bnd
    +				if		(	String_.Eq(old_bnd.Key(), new_bnd.Key())) {
    +					Xog_bnd_box_.Set_bnd_for_grp(Xog_bnd_box_.Set_del_key, win, invk_mgr, old_box, old_bnd, new_bnd.Ipt());
    +					deleted.Add(new_bnd);
    +				}
    +				// if ipts match, delete old_bnd
    +				else if (	new_ipt_exists
    +						&&	old_bnd.Box() == new_bnd.Box()
    +						&&	String_.Eq(old_bnd.Ipt().Key(), new_ipt.Key())) {
    +					Xog_bnd_box_.Set_bnd_for_grp(Xog_bnd_box_.Set_del_ipt, win, invk_mgr, old_box, old_bnd, old_bnd.Ipt());
    +					old_bnd.Ipt_to_none();
    +				}
    +			}
    +
    +			// remove old bnd from box
    +			int deleted_len = deleted.Count();
    +			for (int j = 0; j < deleted_len; j++) {
    +				// delete from box
    +				Xog_bnd_itm deleted_itm = (Xog_bnd_itm)deleted.Get_at(j);
    +				old_box.Del(deleted_itm.Key());
    +
    +				// add back other items with same cmd but different key; needed b/c gfui.ipt_mgr hashes by cmd ("sandbox"), not key ("sandbox-1"); DATE:2016-12-25
    +				List_adp list = old_box.Get_list_by_cmd(deleted_itm.Cmd());
    +				if (list != null) {
    +					int len = list.Len();
    +					for (int k = 0; k < len; k++) {
    +						Xog_bnd_itm restore_itm = (Xog_bnd_itm)list.Get_at(k);
    +						Xog_bnd_box_.Set_bnd_for_grp(Xog_bnd_box_.Set_add, win, invk_mgr, old_box, restore_itm, restore_itm.Ipt());
    +					}
    +				}
    +			}
    +			deleted.Clear();
    +		}
    +	}
    +	private void Add(Xog_bnd_itm itm) {
    +		Xog_bnd_box box = boxs[itm.Box()];
    +		Xog_bnd_box_.Set_bnd_for_grp(Xog_bnd_box_.Set_add, win, invk_mgr, box, itm, itm.Ipt());
    +		box.Add(itm);
    +	}
    +	public void Bind(int tid, GfuiElem box_elem) {
    +		Xog_bnd_box box = boxs[tid];
    +		int len = box.Len();
    +		for (int i = 0; i < len; i++) {
    +			Xog_bnd_itm itm = box.Get_at(i);
    +			Xog_bnd_box_.Set_bnd_for_elem(Xog_bnd_box_.Set_add, box, box_elem, invk_mgr, itm, itm.Ipt());
    +		}
    +	}
    +	public void Init() {
    +		regy.Clear();	// clear regy, else 1 test will fail
    +		Init_itm(Xog_cmd_itm_.Key_app_exit										, Xog_bnd_box_.Tid_browser				, "mod.c+key.q");
    +		Init_itm(Xog_cmd_itm_.Key_nav_go_bwd									, Xog_bnd_box_.Tid_browser				, "mod.a+key.left");
    +		Init_itm(Xog_cmd_itm_.Key_nav_go_fwd									, Xog_bnd_box_.Tid_browser				, "mod.a+key.right");
    +		Init_itm(Xog_cmd_itm_.Key_nav_cfg_main									, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_cfg_menu									, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_wiki_main_page							, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_wiki_random								, Xog_bnd_box_.Tid_browser				, "mod.cs+key.r");
    +		Init_itm(Xog_cmd_itm_.Key_nav_wiki_allpages								, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_wiki_search_title							, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_wiki_search_full							, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_wiki_search_per_cfg						, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_wiki_sandbox								, Xog_bnd_box_.Tid_browser				, "mod.cs+key.g,mod.cs+key.s", "mod.c+key.f1");
    +		Init_itm(Xog_cmd_itm_.Key_nav_help_help									, Xog_bnd_box_.Tid_browser				, "key.f1");
    +		Init_itm(Xog_cmd_itm_.Key_nav_help_change_log							, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_help_diagnostics							, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_help_about								, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_help_xowa_main							, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_help_xowa_blog							, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_help_xowa_update							, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_setup_download_central					, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_setup_import_from_list					, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_setup_import_from_script					, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_setup_maintenance							, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_setup_download							, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_personal_item								, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_personal_list								, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_system_data_log_session					, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_system_data_cfg_app						, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_system_data_cfg_lang						, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_nav_system_data_usr_history					, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_gui_font_increase								, Xog_bnd_box_.Tid_browser				, "mod.c+key.equal");
    +		Init_itm(Xog_cmd_itm_.Key_gui_font_decrease								, Xog_bnd_box_.Tid_browser				, "mod.c+key.minus");
    +		Init_itm(Xog_cmd_itm_.Key_gui_font_reset								, Xog_bnd_box_.Tid_browser				, "mod.c+key.d0");
    +		Init_itm(Xog_cmd_itm_.Key_gui_page_view_mode_read						, Xog_bnd_box_.Tid_browser				, "mod.c+key.m,mod.c+key.r");
    +		Init_itm(Xog_cmd_itm_.Key_gui_page_view_mode_edit						, Xog_bnd_box_.Tid_browser				, "mod.c+key.m,mod.c+key.e");
    +		Init_itm(Xog_cmd_itm_.Key_gui_page_view_mode_html						, Xog_bnd_box_.Tid_browser				, "mod.c+key.m,mod.c+key.h", "mod.c+key.u");
    +		Init_itm(Xog_cmd_itm_.Key_gui_page_view_reload							, Xog_bnd_box_.Tid_browser				, "key.f5");
    +		Init_itm(Xog_cmd_itm_.Key_gui_page_view_refresh							, Xog_bnd_box_.Tid_browser				, "mod.s+key.f5");
    +		Init_itm(Xog_cmd_itm_.Key_gui_page_view_save_as							, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_gui_page_view_print							, Xog_bnd_box_.Tid_browser				, "mod.c+key.p");
    +		Init_itm(Xog_cmd_itm_.Key_gui_page_selection_select_all					, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_gui_page_selection_copy						, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_gui_page_selection_save_file_as				, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_gui_edit_save									, Xog_bnd_box_.Tid_browser				, "mod.as+key.s");
    +		Init_itm(Xog_cmd_itm_.Key_gui_edit_save_draft							, Xog_bnd_box_.Tid_browser				, "mod.c+key.s");
    +		Init_itm(Xog_cmd_itm_.Key_gui_edit_focus_edit_box						, Xog_bnd_box_.Tid_browser				, "mod.as+key.comma");
    +		Init_itm(Xog_cmd_itm_.Key_gui_edit_preview								, Xog_bnd_box_.Tid_browser				, "mod.as+key.p");
    +		Init_itm(Xog_cmd_itm_.Key_gui_edit_dbg_tmpl								, Xog_bnd_box_.Tid_browser				, "mod.c+key.e,mod.c+key.e", "mod.as+key.d,mod.as+key.d");
    +		Init_itm(Xog_cmd_itm_.Key_gui_edit_dbg_html								, Xog_bnd_box_.Tid_browser				, "mod.c+key.e,mod.c+key.h");
    +		Init_itm(Xog_cmd_itm_.Key_gui_edit_exec									, Xog_bnd_box_.Tid_browser				, "mod.c+key.e,mod.c+key.g");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_url_focus							, Xog_bnd_box_.Tid_browser				, "mod.a+key.d", "mod.c+key.l");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_url_exec							, Xog_bnd_box_.Tid_browser_url			, "key.enter");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_url_exec_new_tab_by_paste			, Xog_bnd_box_.Tid_browser_url			, "mod.c+key.enter");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_url_exec_by_paste					, Xog_bnd_box_.Tid_browser_url			, "mouse.middle", "mod.a+key.enter");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_url_restore						, Xog_bnd_box_.Tid_browser_url			, "key.escape");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_search_focus						, Xog_bnd_box_.Tid_browser				, "mod.ca+key.s");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_search_exec						, Xog_bnd_box_.Tid_browser_search		, "key.enter");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_allpages_focus					, Xog_bnd_box_.Tid_browser				, "mod.ca+key.a");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_allpages_exec						, Xog_bnd_box_.Tid_browser_allpages		, "key.enter");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_new_dflt__at_dflt__focus_y	, Xog_bnd_box_.Tid_browser				, "mod.c+key.t");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_new_link__at_dflt__focus_n	, Xog_bnd_box_.Tid_browser_html			, "mouse.middle");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_new_link__at_dflt__focus_y	, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_new_href__at_dflt__focus_y	, Xog_bnd_box_.Tid_browser				, "mod.c+key.g,mod.c+key.f");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_close_cur					, Xog_bnd_box_.Tid_browser				, "mod.c+key.w");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_close_others					, Xog_bnd_box_.Tid_browser				, "mod.cs+key.w,mod.cs+key.w");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_close_to_bgn					, Xog_bnd_box_.Tid_browser				, "mod.cs+key.w,mod.cs+key.left");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_close_to_end					, Xog_bnd_box_.Tid_browser				, "mod.cs+key.w,mod.cs+key.right");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_close_undo					, Xog_bnd_box_.Tid_browser				, "mod.cs+key.t");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_select_fwd					, Xog_bnd_box_.Tid_browser				, "mod.c+key.tab", "mod.c+key.pageDown");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_select_bwd					, Xog_bnd_box_.Tid_browser				, "mod.cs+key.tab", "mod.c+key.pageUp");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_select_by_idx_1				, Xog_bnd_box_.Tid_browser				, "mod.c+key.d1");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_select_by_idx_2				, Xog_bnd_box_.Tid_browser				, "mod.c+key.d2");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_select_by_idx_3				, Xog_bnd_box_.Tid_browser				, "mod.c+key.d3");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_select_by_idx_4				, Xog_bnd_box_.Tid_browser				, "mod.c+key.d4");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_select_by_idx_5				, Xog_bnd_box_.Tid_browser				, "mod.c+key.d5");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_select_by_idx_6				, Xog_bnd_box_.Tid_browser				, "mod.c+key.d6");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_select_by_idx_7				, Xog_bnd_box_.Tid_browser				, "mod.c+key.d7");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_select_by_idx_8				, Xog_bnd_box_.Tid_browser				, "mod.c+key.d8");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_select_by_idx_9				, Xog_bnd_box_.Tid_browser				, "mod.c+key.d9");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_move_bwd						, Xog_bnd_box_.Tid_browser				, "mod.a+key.pageUp");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_move_fwd						, Xog_bnd_box_.Tid_browser				, "mod.a+key.pageDown");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_pin_toggle					, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_html_focus					,  0, Xog_bnd_box_.Tid_browser_url			, "key.escape");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_html_focus					,  1, Xog_bnd_box_.Tid_browser_search		, "key.escape");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_html_focus					,  2, Xog_bnd_box_.Tid_browser_prog			, "key.escape");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_html_focus					,  3, Xog_bnd_box_.Tid_browser_info			, "key.escape");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_html_selection_focus_toggle		, Xog_bnd_box_.Tid_browser				, "mod.c+key.g,mod.c+key.g");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_find_show							, Xog_bnd_box_.Tid_browser				, "mod.c+key.f");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_find_show_by_paste				, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_find_hide							, Xog_bnd_box_.Tid_browser_find			, "key.escape");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_find_exec							, Xog_bnd_box_.Tid_browser_find			, "key.enter");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_find_find_fwd						, Xog_bnd_box_.Tid_browser_find			, "mod.a+key.n");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_find_find_bwd						, Xog_bnd_box_.Tid_browser_find			, "mod.a+key.p");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_find_case_toggle					, Xog_bnd_box_.Tid_browser_find			, "mod.a+key.c");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_find_wrap_toggle					, Xog_bnd_box_.Tid_browser_find			, "mod.a+key.w");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_prog_focus						, Xog_bnd_box_.Tid_browser				, "mod.ca+key.p");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_info_focus						, Xog_bnd_box_.Tid_browser				, "mod.ca+key.i");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_info_clear						, Xog_bnd_box_.Tid_browser				, "mod.ca+key.c");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_prog_log_show						, Xog_bnd_box_.Tid_browser_prog			, "mouse.middle", "mod.cs+key.p");
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_nightmode_toggle					, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_html_tidy_toggle								, Xog_bnd_box_.Tid_browser				, "key.f7");
    +		Init_itm(Xog_cmd_itm_.Key_usr_bookmarks_add								, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_usr_bookmarks_show							, Xog_bnd_box_.Tid_browser				, "key.f3");
    +		Init_itm(Xog_cmd_itm_.Key_usr_history_goto_recent						, Xog_bnd_box_.Tid_browser				, "mod.cs+key.l");
    +		Init_itm(Xog_cmd_itm_.Key_usr_history_show								, Xog_bnd_box_.Tid_browser				, "mod.cs+key.h");
    +		Init_itm(Xog_cmd_itm_.Key_net_enabled_n_								, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_net_enabled_y_								, Xog_bnd_box_.Tid_browser				, "");
    +		Init_itm(Xog_cmd_itm_.Key_net_enabled_x_								, Xog_bnd_box_.Tid_browser				, "");
    +	}
    +	private void Init_itm(String cmd, int box, String... ipts) {
    +		int ipts_len = ipts.length;
    +		for (int i = 0; i < ipts_len; i++) {
    +			String ipt_str = ipts[i];
    +			Init_itm(cmd, i, box, IptArg_.parse_or_none_(ipt_str));
    +		}
    +	}
    +	private void Init_itm(String cmd, int idx, int box, String ipt) {Init_itm(cmd, idx, box, IptArg_.parse_or_none_(ipt));}
    +	private void Init_itm(String cmd, int idx, int box, IptArg ipt) {
    +		String key = cmd + "-" + Int_.To_str(idx + List_adp_.Base1);		// EX: xowa.widgets.url.focus-1 xowa.widgets.url.focus-2
    +		Xog_bnd_itm itm = new Xog_bnd_itm(key, Bool_.Y, cmd, box, ipt);
    +		boxs[box].Add(itm);
    +		regy.Add(itm.Key(), itm);
    +	}
    +	private void Add_system_bnds() {
    +		IptCfg null_cfg = IptCfg_.Null; IptEventType btn_event_type = IptEventType_.add_(IptEventType_.MouseUp, IptEventType_.KeyDown); IptArg[] btn_args = IptArg_.Ary(IptMouseBtn_.Left, IptKey_.Enter, IptKey_.Space);
    +		IptBnd_.ipt_to_(null_cfg		, win.Go_bwd_btn()		 , invk_mgr, Xog_cmd_itm_.Key_nav_go_bwd					, btn_event_type, btn_args);
    +		IptBnd_.ipt_to_(null_cfg		, win.Go_fwd_btn()		 , invk_mgr, Xog_cmd_itm_.Key_nav_go_fwd					, btn_event_type, btn_args);
    +		IptBnd_.ipt_to_(null_cfg		, win.Url_exec_btn()	 , invk_mgr, Xog_cmd_itm_.Key_gui_browser_url_exec			, btn_event_type, btn_args);
    +		IptBnd_.ipt_to_(null_cfg		, win.Search_exec_btn()	 , invk_mgr, Xog_cmd_itm_.Key_gui_browser_search_exec		, btn_event_type, btn_args);
    +		IptBnd_.ipt_to_(null_cfg		, win.Allpages_exec_btn(), invk_mgr, Xog_cmd_itm_.Key_gui_browser_allpages_exec		, btn_event_type, btn_args);
    +		IptBnd_.ipt_to_(null_cfg		, win.Find_close_btn()	 , invk_mgr, Xog_cmd_itm_.Key_gui_browser_find_hide			, btn_event_type, btn_args);
    +		IptBnd_.ipt_to_(null_cfg		, win.Find_fwd_btn()	 , invk_mgr, Xog_cmd_itm_.Key_gui_browser_find_find_fwd		, btn_event_type, btn_args);
    +		IptBnd_.ipt_to_(null_cfg		, win.Find_bwd_btn()	 , invk_mgr, Xog_cmd_itm_.Key_gui_browser_find_find_bwd		, btn_event_type, btn_args);
    +		IptBnd_.ipt_to_(null_cfg		, win.Find_box()		 , invk_mgr, Xog_cmd_itm_.Key_gui_browser_find_type			, IptEventType_.KeyUp, IptKey_.printableKeys_(IptKey_.Ary(IptKey_.Back, IptKey_.Escape, IptKey_.Ctrl.Add(IptKey_.V)), IptKey_.Ary()));
    +		IptBnd_.ipt_to_(null_cfg		, win.Url_box()			 , invk_mgr, Xog_cmd_itm_.Key_gui_browser_url_type			, IptEventType_.KeyUp, IptKey_.printableKeys_(IptKey_.Ary(IptKey_.Back, IptKey_.Escape, IptKey_.Ctrl.Add(IptKey_.X), IptKey_.Ctrl.Add(IptKey_.V)), IptKey_.Ary()));
    +	}
    +	private void Add_custom_bnds() {	// NOTE: custom bnds are stored in cfg; cfg executes before Init_by_kit when all windows elements are null; run cfg now, while Init_by_kit is called and elems are now created
    +		int len = startup_itms.Count();
    +		for (int i = 0; i < len; i++) {
    +			Xog_bnd_itm new_itm = (Xog_bnd_itm)startup_itms.Get_at(i);
    +			try {
    +				Xog_bnd_itm cur_itm = (Xog_bnd_itm)regy.Get_by(new_itm.Key());
    +				if (cur_itm == null) {win.Usr_dlg().Warn_many("", "", "binding no longer exists; key=~{0}", new_itm.Key());}	// could happen when breaking backward compatibility
    +				cur_itm.Init_by_set(new_itm.Box(), new_itm.Ipt());
    +			}	catch (Exception e) {win.Usr_dlg().Warn_many("", "", "failed to set custom binding; key=~{0} err=~{1}", new_itm.Key(), Err_.Message_gplx_full(e));}
    +		}
    +		startup_itms.Clear();
    +	}
    +	private void Bind_all() {
    +		this.Bind(Xog_bnd_box_.Tid_browser				, win.Win_box());
    +		this.Bind(Xog_bnd_box_.Tid_browser_url			, win.Url_box());
    +		this.Bind(Xog_bnd_box_.Tid_browser_find			, win.Find_box());
    +		this.Bind(Xog_bnd_box_.Tid_browser_search		, win.Search_box());
    +		this.Bind(Xog_bnd_box_.Tid_browser_allpages		, win.Allpages_box());
    +		this.Bind(Xog_bnd_box_.Tid_browser_prog			, win.Prog_box());
    +		this.Bind(Xog_bnd_box_.Tid_browser_info			, win.Info_box());
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(String_.Eq(k, Run__show_remap_win)) {
    +			Xog_bnd_win win = new Xog_bnd_win();			
    +			String[] args = m.ReadStrAry("v", "\n");
    +			win.Show(app, app.Gui_mgr().Kit(), app.Gui_mgr().Browser_win().Win_box(), args[0], args[1], args[2]);
    +		}
    +		else {
    +			String val = m.ReadStr("v");
    +			if (String_.Len_eq_0(val)) { // need to check, or may fail when running newer codebase against old cfgs; DATE:2017-06-02
    +				Gfo_usr_dlg_.Instance.Warn_many("", "", "binding does not have val; key=~{0}", k);
    +				return this;
    +			}
    +			String[] flds = gplx.xowa.addons.apps.cfgs.enums.Xoitm_gui_binding.To_ary(val);
    +			int box = Xog_bnd_box_.Xto_sys_int(flds[0]);
    +			String key = String_.Replace(k, "xowa.gui.shortcuts.", "");
    +			Xog_bnd_itm bnd = app.Gui_mgr().Bnd_mgr().Get_or_null(key);
    +			Set(bnd, box, IptArg_.parse(flds[1]));
    +		}
    +		return this;
    +	}
    +	private static final String Run__show_remap_win = "xowa.gui.shortcuts.show_remap_win";
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_temp.java b/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_temp.java
    index a27517de8..cb9280492 100644
    --- a/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_temp.java
    +++ b/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_temp.java
    @@ -13,3 +13,53 @@ 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.guis.bnds; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.gfui.ipts.*; import gplx.xowa.guis.cmds.*;
    +interface Xog_bnd_wkr {
    +	void Bind_ipt_to_box(String box, String ipt);
    +}
    +class Xog_bnd_wkr__null implements Xog_bnd_wkr {
    +	public void Bind_ipt_to_box(String box, String ipt) {}
    +}
    +class Xog_bnd_temp implements Gfo_invk {
    +	private Xoae_app app;
    +	private final    Ordered_hash regy = Ordered_hash_.New();
    +	private final    Xog_bnd_wkr bnd_wkr = new Xog_bnd_wkr__null();
    +	private Xog_bnd_box[] boxs = Xog_bnd_box_.Ary();
    +	public void Init_by_app(Xoae_app app) {
    +		this.app = app;
    +		Init_itm(Xog_cmd_itm_.Key_gui_browser_tabs_new_dflt__at_dflt__focus_y	, Xog_bnd_box_.Tid_browser				, "mod.c+key.t");
    +	}
    +	private void Init_itm(String cmd, int box, String... ipts) {
    +		int ipts_len = ipts.length;
    +		for (int i = 0; i < ipts_len; i++) {
    +			String ipt_str = ipts[i];
    +			Init_itm(cmd, i, box, IptArg_.parse_or_none_(ipt_str));
    +		}
    +	}
    +	// private void Init_itm(String cmd, int idx, int box, String ipt) {Init_itm(cmd, idx, box, IptArg_.parse_or_none_(ipt));}
    +	private void Init_itm(String cmd, int idx, int box, IptArg ipt) {
    +		String key = cmd + "-" + Int_.To_str(idx + List_adp_.Base1);		// EX: xowa.widgets.url.focus-1 xowa.widgets.url.focus-2
    +		Xog_bnd_itm itm = new Xog_bnd_itm(key, Bool_.Y, cmd, box, ipt);
    +		boxs[box].Add(itm);
    +		regy.Add(itm.Key(), itm);
    +		app.Cfg().Bind_many_app(this, cmd);
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		Xog_bnd_cfg_itm itm = Xog_bnd_cfg_itm.Parse(m.ReadStr("v"));
    +		bnd_wkr.Bind_ipt_to_box(itm.Box(), itm.Ipt());
    +		return this;
    +	}
    +}
    +class Xog_bnd_cfg_itm {
    +	public Xog_bnd_cfg_itm(String box, String ipt) {
    +		this.box = box;
    +		this.ipt = ipt;
    +	}
    +	public String Box() {return box;} private final    String box;
    +	public String Ipt() {return ipt;} private final    String ipt;
    +	public static Xog_bnd_cfg_itm Parse(String s) {
    +		String[] parts = String_.Split(s, "|");
    +		return new Xog_bnd_cfg_itm(parts[0], parts[1]);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_win.java b/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_win.java
    index a27517de8..2ab33c2ef 100644
    --- a/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_win.java
    +++ b/400_xowa/src/gplx/xowa/guis/bnds/Xog_bnd_win.java
    @@ -13,3 +13,95 @@ 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.guis.bnds; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.gfui.*; import gplx.gfui.draws.*; import gplx.gfui.ipts.*; import gplx.gfui.kits.core.*; import gplx.gfui.envs.*; import gplx.gfui.controls.windows.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*;
    +import gplx.gfui.layouts.swts.*;
    +import gplx.xowa.guis.cbks.*;
    +public class Xog_bnd_win implements Gfo_invk {
    +	private GfuiWin win;
    +	private GfuiTextBox shortcut_txt, binding_txt, keycode_txt;
    +	private GfuiBtn clear_btn, ok_btn, cxl_btn;
    +	private Gfui_bnd_parser bnd_parser = Gfui_bnd_parser.new_en_();
    +	private Xoa_app app;
    +	private String key_text;
    +	public void Show(Xoa_app app, Gfui_kit kit, GfuiWin owner_win, String key_text, String shortcut_text, String binding_text) {
    +		this.app = app;
    +		this.key_text = key_text;
    +
    +		// create controls
    +		this.win = kit.New_win_utl("shortcut_win", owner_win); win.BackColor_(ColorAdp_.White).Size_(240, 140);
    +		GfuiLbl shortcut_lbl		= Make_lbl(kit, win, "shortcut_lbl"	, "Shortcut:");
    +		GfuiLbl binding_lbl			= Make_lbl(kit, win, "binding_lbl"	, "Binding:");
    +		GfuiLbl keycode_lbl			= Make_lbl(kit, win, "keycode_lbl"	, "Keycode:");
    +		this.shortcut_txt			= Make_txt(kit, win, "shortcut_txt"	, shortcut_text);
    +		this.binding_txt			= Make_txt(kit, win, "binding_txt"	, binding_text);
    +		this.keycode_txt			= Make_txt(kit, win, "keycode_txt"	, "");
    +		this.clear_btn				= Make_btn(kit, win, "clear_btn"	, "Clear");
    +		this.ok_btn					= Make_btn(kit, win, "ok_btn"		, "Ok");
    +		this.cxl_btn				= Make_btn(kit, win, "cxl_btn"		, "Cancel");
    +
    +		// layout controls
    +		Layout( 0, shortcut_lbl	, shortcut_txt);
    +		Layout(20, binding_lbl	, binding_txt);
    +		Layout(40, keycode_lbl	, keycode_txt);
    +		clear_btn.Pos_(70, 70); ok_btn.Pos_(110, 70); cxl_btn.Pos_(150, 70);
    +
    +		// hookup events
    +		IptCfg null_cfg = IptCfg_.Null; IptEventType btn_event_type = IptEventType_.add_(IptEventType_.MouseDown, IptEventType_.KeyDown); IptArg[] btn_args = IptArg_.Ary(IptMouseBtn_.Left, IptKey_.Enter, IptKey_.Space);
    +		IptBnd_.ipt_to_(null_cfg		, binding_txt	, this, Invk__when_key_down		, IptEventType_.KeyDown, IptArg_.Wildcard);
    +		IptBnd_.ipt_to_(null_cfg		, binding_txt	, this, Invk__when_key_up		, IptEventType_.KeyUp, IptArg_.Wildcard);
    +		IptBnd_.ipt_to_(null_cfg		, clear_btn		, this, Invk__when_clear		, btn_event_type, btn_args);
    +		IptBnd_.ipt_to_(null_cfg		, ok_btn		, this, Invk__when_ok			, btn_event_type, btn_args);
    +		IptBnd_.ipt_to_(null_cfg		, cxl_btn		, this, Invk__when_cxl			, btn_event_type, btn_args);
    +
    +		// open
    +		win.Pos_(SizeAdp_.center_(ScreenAdp_.Primary.Size(), win.Size()));
    +		win.Show();
    +		binding_txt.Focus();
    +	}
    +	private void When_key_down(GfoMsg m) {
    +		IptEventData event_data = (IptEventData)m.Args_getAt(0).Val();
    +		int keycode = event_data.Key().Val();
    +		binding_txt.Text_(bnd_parser.Xto_norm(IptKey_.To_str(keycode)));
    +		keycode_txt.Text_(Int_.To_str(keycode));
    +		event_data.Handled_on();
    +	}
    +	private void When_key_up(GfoMsg m) {
    +		IptEventData event_data = (IptEventData)m.Args_getAt(0).Val();
    +		event_data.Handled_on();
    +	}
    +	private GfuiLbl Make_lbl(Gfui_kit kit, GfuiWin owner_win, String key, String text) {
    +		return (GfuiLbl)kit.New_lbl(key, owner_win).Text_(text).Size_(80, 20).BackColor_(ColorAdp_.White);
    +	}
    +	private GfuiTextBox Make_txt(Gfui_kit kit, GfuiWin owner_win, String key, String text) {
    +		return (GfuiTextBox)kit.New_text_box(key, owner_win).Text_(text).Size_(120, 20).Border_on_().BackColor_(ColorAdp_.White);
    +	}
    +	private GfuiBtn Make_btn(Gfui_kit kit, GfuiWin owner_win, String key, String text) {
    +		return (GfuiBtn)kit.New_btn(key, owner_win).Text_(text).Size_(40, 20).Focus_able_(true).Border_on_().BackColor_(ColorAdp_.White);
    +	}
    +	private void Layout(int y, GfuiElem elem_1, GfuiElem elem_2) {
    +		elem_1.Y_(y);
    +		elem_2.Y_(y);
    +		elem_2.X_(elem_1.X_max());
    +	}
    +
    +	private final    Xog_cbk_trg cbk_trg = Xog_cbk_trg.New(gplx.xowa.addons.apps.cfgs.specials.edits.pages.Xocfg_edit_special.Prototype.Special__meta().Ttl_bry());
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk__when_key_down))		When_key_down(m);
    +		else if	(ctx.Match(k, Invk__when_key_press))	When_key_up(m);
    +		else if	(ctx.Match(k, Invk__when_key_up))		When_key_up(m);
    +		else if	(ctx.Match(k, Invk__when_clear))		binding_txt.Text_("None");
    +		else if	(ctx.Match(k, Invk__when_ok))			Remap_and_close(binding_txt.Text());
    +
    +		else if	(ctx.Match(k, Invk__when_cxl))			{win.Close();}
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	private static final String Invk__when_key_down = "when_key_down", Invk__when_key_press = "when_key_press", Invk__when_key_up = "when_key_up"
    +	, Invk__when_clear= "when_clear", Invk__when_ok = "when_ok", Invk__when_cxl = "when_cxl"
    +	;
    +	private void Remap_and_close(String new_ipt) {
    +		app.Gui__cbk_mgr().Send_json(cbk_trg, "xo.cfg_edit.gui_binding__remap_recv", gplx.core.gfobjs.Gfobj_nde.New().Add_str("key", key_text).Add_str("bnd", new_ipt));
    +		win.Close();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cbks/Xog_cbk_mgr.java b/400_xowa/src/gplx/xowa/guis/cbks/Xog_cbk_mgr.java
    index a27517de8..88906431d 100644
    --- a/400_xowa/src/gplx/xowa/guis/cbks/Xog_cbk_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/cbks/Xog_cbk_mgr.java
    @@ -13,3 +13,24 @@ 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.guis.cbks; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.core.gfobjs.*;
    +public class Xog_cbk_mgr {	// INSTANCE:app
    +	private Xog_cbk_wkr[] wkrs = Xog_cbk_wkr_.Ary_empty; private int wkrs_len = 0;
    +	public void Reg(Xog_cbk_wkr wkr) {
    +		this.wkrs = (Xog_cbk_wkr[])Array_.Resize_add_one(wkrs, wkrs_len, wkr);
    +		++wkrs_len;
    +	}
    +	public void Send_json(Xog_cbk_trg trg, String func, Gfobj_nde data) {
    +		for (int i = 0; i < wkrs_len; ++i) {
    +			Xog_cbk_wkr wkr = wkrs[i];
    +			wkr.Send_json(trg, func, data);
    +		}
    +	}
    +	public void Send_redirect(Xog_cbk_trg trg, String url) {
    +		this.Send_json(trg, "xo.server.redirect__recv", gplx.core.gfobjs.Gfobj_nde.New().Add_str("url", url));
    +	}
    +	public void Send_notify(Xog_cbk_trg trg, String text) {
    +		this.Send_json(trg, "xo.notify.show__recv", gplx.core.gfobjs.Gfobj_nde.New().Add_str("text", text).Add_str("status", "success"));
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cbks/Xog_cbk_trg.java b/400_xowa/src/gplx/xowa/guis/cbks/Xog_cbk_trg.java
    index a27517de8..e035d1611 100644
    --- a/400_xowa/src/gplx/xowa/guis/cbks/Xog_cbk_trg.java
    +++ b/400_xowa/src/gplx/xowa/guis/cbks/Xog_cbk_trg.java
    @@ -13,3 +13,21 @@ 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.guis.cbks; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Xog_cbk_trg {
    +	public Xog_cbk_trg(byte tid, byte[] page_ttl) {
    +		this.tid = tid; this.page_ttl = page_ttl;
    +	}
    +	public Xog_cbk_trg(String page_guid) {
    +		this.tid = Tid__page_guid;
    +		this.page_ttl = Bry_.Empty;
    +		this.Page_guid = page_guid;
    +	}
    +	public byte Tid() {return tid;} private final    byte tid;
    +	public byte[] Page_ttl() {return page_ttl;} private final    byte[] page_ttl;	// same as ttl.Full_db(); EX: Special:XowaDownloadCentral
    +	public String Page_guid; 
    +
    +	public static final byte Tid__cbk_enabled = 0, Tid__specific_page = 1, Tid__page_guid = 2;
    +	public static final    Xog_cbk_trg Any = new Xog_cbk_trg(Tid__cbk_enabled, null);
    +	public static Xog_cbk_trg New(byte[] page_ttl) {return new Xog_cbk_trg(Tid__specific_page, page_ttl);}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cbks/Xog_cbk_wkr.java b/400_xowa/src/gplx/xowa/guis/cbks/Xog_cbk_wkr.java
    index a27517de8..df665a447 100644
    --- a/400_xowa/src/gplx/xowa/guis/cbks/Xog_cbk_wkr.java
    +++ b/400_xowa/src/gplx/xowa/guis/cbks/Xog_cbk_wkr.java
    @@ -13,3 +13,11 @@ 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.guis.cbks; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.core.gfobjs.*;
    +public interface Xog_cbk_wkr {
    +	Object Send_json(Xog_cbk_trg trg, String func, Gfobj_nde data);
    +}
    +class Xog_cbk_wkr_ {
    +	public static final    Xog_cbk_wkr[] Ary_empty = new Xog_cbk_wkr[0];
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cbks/Xog_json_wkr.java b/400_xowa/src/gplx/xowa/guis/cbks/Xog_json_wkr.java
    index a27517de8..c8712a1cd 100644
    --- a/400_xowa/src/gplx/xowa/guis/cbks/Xog_json_wkr.java
    +++ b/400_xowa/src/gplx/xowa/guis/cbks/Xog_json_wkr.java
    @@ -13,3 +13,8 @@ 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.guis.cbks; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.core.gfobjs.*;
    +public interface Xog_json_wkr {
    +	void Send_json(String func, Gfobj_nde data);
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cbks/js/Js_img_mgr.java b/400_xowa/src/gplx/xowa/guis/cbks/js/Js_img_mgr.java
    index a27517de8..583fd0bb6 100644
    --- a/400_xowa/src/gplx/xowa/guis/cbks/js/Js_img_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/cbks/js/Js_img_mgr.java
    @@ -13,3 +13,45 @@ 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.guis.cbks.js; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.cbks.*;
    +import gplx.xowa.xtns.gallery.*;
    +import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import gplx.xowa.guis.views.*; import gplx.xowa.parsers.lnkis.*;
    +public class Js_img_mgr {
    +	public static void Update_link_missing(Xog_js_wkr html_itm, String html_id) {
    +		html_itm.Html_redlink(html_id);
    +	}
    +	public static void Update_img(Xoa_page page, Xog_js_wkr js_wkr, Xof_file_itm itm) {
    +		Js_img_mgr.Update_img(page, js_wkr, itm.Html_img_wkr(), itm.Html_uid(), itm.Lnki_type(), itm.Html_elem_tid(), itm.Html_w(), itm.Html_h(), itm.Html_view_url()
    +			, itm.Orig_w(), itm.Orig_h(), itm.Orig_ext(), itm.Html_orig_url(), itm.Orig_ttl(), itm.Html_gallery_mgr_h());
    +	}
    +	private static void Update_img(Xoa_page page, Xog_js_wkr js_wkr, Js_img_wkr img_wkr, int uid, byte lnki_type, byte elem_tid, int html_w, int html_h, Io_url html_view_url
    +		, int orig_w, int orig_h, Xof_ext orig_ext, Io_url html_orig_url, byte[] lnki_ttl, int gallery_mgr_h) {
    +		if (!page.Wiki().App().Mode().Tid_supports_js()) return;	// do not update html widget unless app is gui; null ref on http server; DATE:2014-09-17
    +		switch (elem_tid) {
    +			case Xof_html_elem.Tid_gallery_v2:
    +				img_wkr.Js_wkr__update_hdoc(page, js_wkr, uid, html_w, html_h, html_view_url, orig_w, orig_h, orig_ext, html_orig_url, lnki_ttl);
    +				return;
    +		}
    +		String html_id = To_doc_uid(uid);
    +		js_wkr.Html_img_update(html_id, html_view_url.To_http_file_str(), html_w, html_h);
    +		if (Xop_lnki_type.Id_is_thumbable(lnki_type)) {	// thumb needs to set cls and width
    +			js_wkr.Html_atr_set(html_id, "class", gplx.xowa.htmls.core.wkrs.imgs.atrs.Xoh_img_cls_.Str__thumbimage);
    +			js_wkr.Html_atr_set("xowa_file_div_" + uid, "style", "width:" + html_w + "px;");
    +		}
    +		switch (elem_tid) {
    +			case Xof_html_elem.Tid_gallery:
    +				js_wkr.Html_atr_set("xowa_gallery_div3_" + uid, "style", "margin:" + Gallery_mgr_wtr_.Calc_vpad(gallery_mgr_h, html_h) + "px auto;");					
    +				break;
    +			case Xof_html_elem.Tid_imap:
    +				img_wkr.Js_wkr__update_hdoc(page, js_wkr, uid, html_w, html_h, html_view_url, orig_w, orig_h, orig_ext, html_orig_url, lnki_ttl);
    +				break;
    +			case Xof_html_elem.Tid_vid:
    +				String html_id_vid = "xowa_file_play_" + uid;
    +				js_wkr.Html_atr_set(html_id_vid, "style", "width:" + html_w + "px;max-width:" + (html_w - 2) + "px;");
    +				js_wkr.Html_atr_set(html_id_vid, "href", html_orig_url.To_http_file_str());
    +				break;
    +		}
    +	}
    +	public static String To_doc_uid(int html_uid) {return gplx.xowa.htmls.Xoh_img_mgr.Str__html_uid + Int_.To_str(html_uid);}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cbks/js/Js_img_wkr.java b/400_xowa/src/gplx/xowa/guis/cbks/js/Js_img_wkr.java
    index a27517de8..9f9c8196c 100644
    --- a/400_xowa/src/gplx/xowa/guis/cbks/js/Js_img_wkr.java
    +++ b/400_xowa/src/gplx/xowa/guis/cbks/js/Js_img_wkr.java
    @@ -13,3 +13,9 @@ 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.guis.cbks.js; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.cbks.*;
    +import gplx.xowa.files.*;
    +public interface Js_img_wkr {
    +	void Js_wkr__update_hdoc(Xoa_page page, Xog_js_wkr js_wkr, int html_uid, int html_w, int html_h, Io_url html_view_url
    +		, int orig_w, int orig_h, Xof_ext orig_ext, Io_url html_orig_url, byte[] lnki_ttl);
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cbks/js/Xog_js_wkr.java b/400_xowa/src/gplx/xowa/guis/cbks/js/Xog_js_wkr.java
    index a27517de8..a7bf56c1d 100644
    --- a/400_xowa/src/gplx/xowa/guis/cbks/js/Xog_js_wkr.java
    +++ b/400_xowa/src/gplx/xowa/guis/cbks/js/Xog_js_wkr.java
    @@ -13,3 +13,15 @@ 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.guis.cbks.js; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.cbks.*;
    +public interface Xog_js_wkr {
    +	void Html_img_update				(String uid, String src, int w, int h);
    +	void Html_redlink					(String html_uid);
    +
    +	void Html_atr_set					(String uid, String key, String val);
    +	void Html_elem_replace_html			(String uid, String html);
    +	void Html_elem_append_above			(String uid, String html);
    +	void Html_elem_delete				(String elem_id);
    +	void Html_gallery_packed_exec		();
    +	void Html_popups_bind_hover_to_doc	();
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cbks/js/Xog_js_wkr_.java b/400_xowa/src/gplx/xowa/guis/cbks/js/Xog_js_wkr_.java
    index a27517de8..cddf9617d 100644
    --- a/400_xowa/src/gplx/xowa/guis/cbks/js/Xog_js_wkr_.java
    +++ b/400_xowa/src/gplx/xowa/guis/cbks/js/Xog_js_wkr_.java
    @@ -13,3 +13,17 @@ 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.guis.cbks.js; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.cbks.*;
    +public class Xog_js_wkr_ {
    +	public static final    Xog_js_wkr Noop = new Xog_js_wkr__noop();
    +}
    +class Xog_js_wkr__noop implements Xog_js_wkr {
    +	public void Html_img_update			(String uid, String src, int w, int h) {}
    +	public void Html_atr_set			(String uid, String key, String val) {}
    +	public void Html_elem_replace_html	(String uid, String html) {}
    +	public void Html_elem_append_above	(String uid, String html) {}
    +	public void Html_redlink			(String html_uid) {}
    +	public void Html_elem_delete		(String elem_id) {}
    +	public void Html_gallery_packed_exec() {}
    +	public void Html_popups_bind_hover_to_doc() {}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cbks/js/Xog_js_wkr__log.java b/400_xowa/src/gplx/xowa/guis/cbks/js/Xog_js_wkr__log.java
    index a27517de8..aa83b9fe9 100644
    --- a/400_xowa/src/gplx/xowa/guis/cbks/js/Xog_js_wkr__log.java
    +++ b/400_xowa/src/gplx/xowa/guis/cbks/js/Xog_js_wkr__log.java
    @@ -13,3 +13,24 @@ 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.guis.cbks.js; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.cbks.*;
    +public class Xog_js_wkr__log implements Xog_js_wkr {
    +	private final    List_adp log_list = List_adp_.New();
    +	public void Html_img_update			(String uid, String src, int w, int h)	{log_list.Add(Object_.Ary(Proc_img_update, uid, src, w, h));}
    +	public void Html_atr_set			(String uid, String key, String val)	{log_list.Add(Object_.Ary(Proc_atr_set, uid, key, val));}
    +	public void Html_redlink			(String uid)							{log_list.Add(Object_.Ary(Proc_redlink, uid));}
    +	public void Html_elem_replace_html	(String uid, String html)				{log_list.Add(Object_.Ary(Proc_replace_html, uid, html));}
    +	public void Html_elem_append_above	(String uid, String html)				{log_list.Add(Object_.Ary(Proc_append_above, uid, html));}
    +	public void Html_elem_delete		(String elem_id)						{log_list.Add(Object_.Ary(Proc_delete, elem_id));}
    +	public void Html_gallery_packed_exec()										{log_list.Add(Object_.Ary(Proc_gallery_packed_exec));}
    +	public void Html_popups_bind_hover_to_doc()									{log_list.Add(Object_.Ary(Proc_popups_bind_hover_to_doc));}
    +
    +	public void Log__clear()			{log_list.Clear();}
    +	public int Log__len()				{return log_list.Count();}
    +	public Object[] Log__get_at(int i)	{return (Object[])log_list.Get_at(i);}
    +
    +	public static final String
    +	  Proc_img_update = "img_update", Proc_atr_set = "atr_set", Proc_redlink = "redlink", Proc_replace_html = "replace_html"
    +	, Proc_append_above = "append_above", Proc_delete = "delete", Proc_gallery_packed_exec = "gallery_packed_exec", Proc_popups_bind_hover_to_doc = "popups_bind_hover_to_doc"
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cbks/swts/Gfo_log__swt.java b/400_xowa/src/gplx/xowa/guis/cbks/swts/Gfo_log__swt.java
    index a27517de8..5c68029d6 100644
    --- a/400_xowa/src/gplx/xowa/guis/cbks/swts/Gfo_log__swt.java
    +++ b/400_xowa/src/gplx/xowa/guis/cbks/swts/Gfo_log__swt.java
    @@ -13,3 +13,20 @@ 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.guis.cbks.swts; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.cbks.*;
    +import gplx.core.logs.*; import gplx.core.gfobjs.*; import gplx.xowa.guis.cbks.*;
    +public class Gfo_log__swt extends Gfo_log__file {		private final    Xog_cbk_mgr cbk_mgr;
    +	public Gfo_log__swt(Xog_cbk_mgr cbk_mgr, Io_url url, Gfo_log_itm_wtr fmtr) {super(url, fmtr);this.cbk_mgr = cbk_mgr;}
    +	@Override public void Exec(byte type, long time, long elapsed, String msg, Object[] args) {
    +		if (type == Gfo_log_itm.Type__prog) return;
    +		super.Exec(type, time, elapsed, msg, args);
    +		Gfobj_nde nde = Gfobj_nde.New().Add_str("msg", msg);
    +		int args_len = args.length;
    +		for (int i = 0; i < args_len; i += 2) {
    +			String key = Object_.Xto_str_strict_or_null_mark(args[i]);
    +			Object val = i + 1 < args_len ? args[i + 1] : "<>";
    +			nde.Add_str(key, Object_.Xto_str_strict_or_null_mark(val));
    +		}
    +		cbk_mgr.Send_json(Xog_cbk_trg.Any, "xo.log.add__recv", nde);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cbks/swts/Gfobj_wtr__json__browser.java b/400_xowa/src/gplx/xowa/guis/cbks/swts/Gfobj_wtr__json__browser.java
    index a27517de8..3e7e06c65 100644
    --- a/400_xowa/src/gplx/xowa/guis/cbks/swts/Gfobj_wtr__json__browser.java
    +++ b/400_xowa/src/gplx/xowa/guis/cbks/swts/Gfobj_wtr__json__browser.java
    @@ -13,3 +13,26 @@ 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.guis.cbks.swts; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.cbks.*;
    +import gplx.core.gfobjs.*;
    +public class Gfobj_wtr__json__browser extends Gfobj_wtr__json {		private final    Bry_bfr bfr;
    +	public Gfobj_wtr__json__browser() {
    +		this.Opt_ws_(Bool_.N).Opt_backslash_2x_(Bool_.Y);
    +		this.bfr = this.Bfr();
    +	}
    +	public String Write_as_func__swt(String func_name, Gfobj_grp root) {return Write_as_func(Bool_.Y, func_name, root);}
    +	public String Write_as_func__drd(String func_name, Gfobj_grp root) {return Write_as_func(Bool_.N, func_name, root);}
    +	private String Write_as_func(boolean write_return, String func_name, Gfobj_grp root) {
    +		if (write_return) bfr.Add(Bry__func_bgn);	// NOTE: Android WebView fails if return is passed; EX: "return 'true'" works on SWT.Browser, but not WebView
    +		bfr.Add_str_u8(func_name);
    +		bfr.Add(Bry__args_bgn);
    +		this.Write(root);
    +		bfr.Add(Bry__args_end);
    +		return this.To_str();
    +	}
    +	private static final    byte[]
    +	  Bry__func_bgn	= Bry_.new_a7("return ")
    +	, Bry__args_bgn = Bry_.new_a7("('")
    +	, Bry__args_end = Bry_.new_a7("');")
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cbks/swts/Gfobj_wtr__json__browser__tst.java b/400_xowa/src/gplx/xowa/guis/cbks/swts/Gfobj_wtr__json__browser__tst.java
    index a27517de8..6fed5e652 100644
    --- a/400_xowa/src/gplx/xowa/guis/cbks/swts/Gfobj_wtr__json__browser__tst.java
    +++ b/400_xowa/src/gplx/xowa/guis/cbks/swts/Gfobj_wtr__json__browser__tst.java
    @@ -13,3 +13,27 @@ 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.guis.cbks.swts; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.cbks.*;
    +import org.junit.*; import gplx.core.tests.*;
    +import gplx.core.gfobjs.*;
    +public class Gfobj_wtr__json__browser__tst {
    +	private final    Gfobj_wtr__json__browser__fxt fxt = new Gfobj_wtr__json__browser__fxt();
    +	@Test   public void Json_proc() {
    +		fxt.Test__json_proc 
    +		( "proc_name"
    +		, fxt.Make__nde
    +		(   fxt.Make__fld_str	("k1", "v1")
    +		,   fxt.Make__fld_long	("k2", 2)
    +		,   fxt.Make__fld_int	("k3", 3)
    +		)
    +		, "return proc_name('{\"k1\":\"v1\",\"k2\":2,\"k3\":3}');"
    +		);
    +	}
    +}
    +class Gfobj_wtr__json__browser__fxt extends Gfobj_wtr__json_fxt {		public Gfobj_wtr__json__browser__fxt Test__json_proc() {return this;}
    +	public void Test__json_proc(String proc_name, Gfobj_nde root, String expd) {
    +		Gfobj_wtr__json__browser wtr = new Gfobj_wtr__json__browser();
    +		String actl = wtr.Write_as_func__swt(proc_name, root);
    +		Gftest.Eq__str(expd, actl, "json_write");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cbks/swts/Xog_cbk_wkr__swt.java b/400_xowa/src/gplx/xowa/guis/cbks/swts/Xog_cbk_wkr__swt.java
    index a27517de8..ec13a36db 100644
    --- a/400_xowa/src/gplx/xowa/guis/cbks/swts/Xog_cbk_wkr__swt.java
    +++ b/400_xowa/src/gplx/xowa/guis/cbks/swts/Xog_cbk_wkr__swt.java
    @@ -13,3 +13,57 @@ 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.guis.cbks.swts; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.cbks.*;
    +import gplx.core.gfobjs.*;
    +import gplx.gfui.*; import gplx.gfui.kits.core.*; import gplx.xowa.guis.*; import gplx.xowa.guis.views.*;
    +public class Xog_cbk_wkr__swt implements Xog_cbk_wkr {
    +	private final    Xoa_gui_mgr gui_mgr;
    +	private final    Xog_browser_func browser_func;
    +	private final    Gfobj_wtr__json__browser json_wtr = new Gfobj_wtr__json__browser();
    +	public Xog_cbk_wkr__swt(Xoa_gui_mgr gui_mgr) {
    +		this.gui_mgr = gui_mgr;
    +		this.browser_func = new Xog_browser_func();
    +	}
    +	public Object Send_json(Xog_cbk_trg trg, String func, Gfobj_nde data) {
    +		if (gui_mgr.Kit().Tid() != Gfui_kit_.Swt_tid) return null;	// guard against calling when HTTP_server
    +
    +		// create cmd for script
    +		String script = json_wtr.Write_as_func__swt(func, data);
    +		GfuiInvkCmd swt_cmd = gui_mgr.Kit().New_cmd_sync(browser_func.Script_(script));
    +
    +		// iterate tabs
    +		Xog_tab_mgr tab_mgr = gui_mgr.Browser_win().Tab_mgr();
    +		int tabs_len = tab_mgr.Tabs_len();
    +		Object rv = null;
    +		for (int i = 0; i < tabs_len; ++i) {
    +			Xog_tab_itm tab = tab_mgr.Tabs_get_at(i);
    +			Xoa_page page = tab.Page();
    +			boolean match = false;
    +			switch (trg.Tid()) {
    +				case Xog_cbk_trg.Tid__page_guid:
    +					match = String_.Eq(trg.Page_guid, page.Page_guid().To_str());
    +					break;
    +				case Xog_cbk_trg.Tid__cbk_enabled:
    +					match = page.Html_data().Cbk_enabled();
    +					break;
    +				case Xog_cbk_trg.Tid__specific_page:
    +					match = Bry_.Eq(trg.Page_ttl(), page.Ttl().Full_db_wo_qarg());	// NOTE: ignore qargs to handle Special:XowaCfg?grp=some_grp; DATE:2016-12-28
    +					break;
    +			}
    +			if (match) {
    +				browser_func.Tab_(tab);
    +				rv = Gfo_invk_.Invk_no_key(swt_cmd);
    +				if (rv == null && !String_.Eq(func, "xo.log.add__recv")) throw Err_.new_("gplx.swt", "send_json was not acknowledged", "func", func, "script", script);
    +			}				
    +		}
    +		return rv;
    +	}
    +}
    +class Xog_browser_func implements Gfo_invk {
    +	private String script;
    +	public Xog_browser_func Script_(String v) {this.script = v; return this;}
    +	public void Tab_(Xog_tab_itm v) {this.tab = v;} private Xog_tab_itm tab;
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		return tab.Html_box().Html_js_eval_script_as_obj(script);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_ctg.java b/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_ctg.java
    index a27517de8..194bc3025 100644
    --- a/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_ctg.java
    +++ b/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_ctg.java
    @@ -13,3 +13,57 @@ 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.guis.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Xog_cmd_ctg {
    +	public Xog_cmd_ctg(int tid, String key_str) {this.tid = tid; this.key_str = key_str; this.key_bry = Bry_.new_u8(key_str);}
    +	public int Tid() {return tid;} private int tid;
    +	public String Key_str() {return key_str;} private String key_str;
    +	public byte[] Key_bry() {return key_bry;} private byte[] key_bry;
    +	public String Name() {return name;} public Xog_cmd_ctg Name_(String v) {name = v; return this;} private String name;
    +	public String Info() {return info;} public Xog_cmd_ctg Info_(String v) {info = v; return this;} private String info;
    +}
    +class Xog_ctg_itm_ {
    +	public static final int
    +	  Tid__max			= 15
    +	, Tid_null			=  0
    +	, Tid_app			=  1
    +	, Tid_nav			=  1
    +	, Tid_nav_pages		=  2
    +	, Tid_font			=  3
    +	, Tid_page			=  4
    +	, Tid_edit			=  5
    +	, Tid_selection		=  6
    +	, Tid_browser		=  7
    +	, Tid_tabs			=  8
    +	, Tid_html			=  9
    +	, Tid_net			= 10
    +	, Tid_bookmarks		= 11
    +	, Tid_history		= 12
    +	, Tid_xtns			= 13
    +	, Tid_custom		= 14
    +	;
    +	public static final Xog_cmd_ctg[] Ary = new Xog_cmd_ctg[Tid__max];
    +	public static final Xog_cmd_ctg
    +	  Itm_null				= new_(Tid_null				, "xowa.null")
    +	, Itm_app				= new_(Tid_app				, "xowa.app")
    +	, Itm_nav				= new_(Tid_nav				, "xowa.nav")
    +	, Itm_nav_pages			= new_(Tid_nav_pages		, "xowa.nav.pages")
    +	, Itm_font				= new_(Tid_font				, "xowa.font")
    +	, Itm_page				= new_(Tid_page				, "xowa.page")
    +	, Itm_edit				= new_(Tid_edit				, "xowa.edit")
    +	, Itm_selection			= new_(Tid_selection		, "xowa.selection")
    +	, Itm_browser			= new_(Tid_browser			, "xowa.browser")
    +	, Itm_tabs				= new_(Tid_tabs				, "xowa.tabs")
    +	, Itm_html				= new_(Tid_html				, "xowa.html")
    +	, Itm_net				= new_(Tid_net				, "xowa.net")
    +	, Itm_bookmarks			= new_(Tid_bookmarks		, "xowa.bookmarks")
    +	, Itm_history			= new_(Tid_history			, "xowa.history")
    +	, Itm_xtns				= new_(Tid_xtns				, "xowa.xtns")
    +	, Itm_custom			= new_(Tid_custom			, "custom")
    +	;
    +	private static Xog_cmd_ctg new_(int tid, String code) {
    +		Xog_cmd_ctg rv = new Xog_cmd_ctg(tid, code);
    +		Ary[tid] = rv;
    +		return rv;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_itm.java b/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_itm.java
    index a27517de8..31a58a9fa 100644
    --- a/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_itm.java
    +++ b/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_itm.java
    @@ -13,3 +13,21 @@ 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.guis.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Xog_cmd_itm {
    +	public Xog_cmd_itm(String key, Xog_cmd_ctg ctg, String cmd) {
    +		this.key = key; this.ctg = ctg; this.cmd = cmd;
    +		this.key_bry = Bry_.new_u8(key);
    +		this.uid = ++Uid_next;
    +	}
    +	public int Uid() {return uid;} private int uid;
    +	public String Key() {return key;} private String key;
    +	public byte[] Key_bry() {return key_bry;} private byte[] key_bry;
    +	public Xog_cmd_ctg Ctg() {return ctg;} private Xog_cmd_ctg ctg;
    +	public String Cmd() {return cmd;} public Xog_cmd_itm Cmd_(String v) {cmd = v; return this;} private String cmd;
    +	public String Name() {return name;} public Xog_cmd_itm Name_(String v) {name = v; return this;} private String name;
    +	public String Name_or_missing() {return name == null ? "<" + name + ">" : name;}
    +	public String Tip() {return tip;} public Xog_cmd_itm Tip_(String v) {tip = v; return this;} private String tip;
    +	public String Tip_or_missing() {return tip == null ? "<" + tip + ">" : tip;}
    +	private static int Uid_next = 0;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_itm_.java b/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_itm_.java
    index a27517de8..62077c1f5 100644
    --- a/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_itm_.java
    +++ b/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_itm_.java
    @@ -13,3 +13,170 @@ 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.guis.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Xog_cmd_itm_ {
    +	private static final    Ordered_hash regy = Ordered_hash_.New();	// NOTE: must be defined at top
    +	public static final    String 
    +	  Key_app_exit												= new_dflt_(Xog_ctg_itm_.Tid_app			, "xowa.app.exit")
    +
    +	, Key_nav_go_bwd											= new_dflt_(Xog_ctg_itm_.Tid_nav			, "xowa.nav.go_bwd")
    +	, Key_nav_go_fwd											= new_dflt_(Xog_ctg_itm_.Tid_nav			, "xowa.nav.go_fwd")
    +
    +	, Key_nav_cfg_main											= new_page_(Xog_ctg_itm_.Tid_nav			, "xowa.nav.cfg.main"								, "home/wiki/Options")				// HOME
    +	, Key_nav_cfg_menu											= new_page_(Xog_ctg_itm_.Tid_nav			, "xowa.nav.cfg.menus"								, "home/wiki/Options/Menus")		// HOME
    +
    +	, Key_nav_wiki_main_page									= new_dflt_(Xog_ctg_itm_.Tid_nav			, "xowa.nav.wiki.main_page")
    +	, Key_nav_wiki_sandbox										= new_dflt_(Xog_ctg_itm_.Tid_nav			, "xowa.nav.wiki.sandbox")
    +	, Key_nav_wiki_random										= new_dflt_(Xog_ctg_itm_.Tid_nav			, "xowa.nav.wiki.random")
    +	, Key_nav_wiki_allpages										= new_dflt_(Xog_ctg_itm_.Tid_nav			, "xowa.nav.wiki.allpages")
    +	, Key_nav_wiki_search_title									= new_dflt_(Xog_ctg_itm_.Tid_nav			, "xowa.nav.wiki.search_title")
    +	, Key_nav_wiki_search_full									= new_dflt_(Xog_ctg_itm_.Tid_nav			, "xowa.nav.wiki.search_full")
    +	, Key_nav_wiki_search_per_cfg								= new_dflt_(Xog_ctg_itm_.Tid_nav			, "xowa.nav.wiki.search_per_cfg")
    +
    +	, Key_nav_help_help											= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.help.help"								, "home/wiki/Help/Contents")               // HOME
    +	, Key_nav_help_about										= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.help.about"								, "home/wiki/Help/About")                  // HOME
    +	, Key_nav_help_change_log									= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.help.change_log"						, "home/wiki/Change_log")                  // HOME
    +	, Key_nav_help_diagnostics									= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.help.diagnostics"						, "home/wiki/Diagnostics")                 // HOME
    +	, Key_nav_help_xowa_update									= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.help.xowa_update"						, "home/wiki/Special:XowaAppUpdate")       // HOME
    +	, Key_nav_help_xowa_main									= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.help.xowa_main"							, "home/wiki/Main_Page")                   // HOME
    +	, Key_nav_help_xowa_blog									= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.help.xowa_blog"							, "home/wiki/Blog")                        // HOME
    +
    +	, Key_nav_setup_download_central							= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.setup.download_central"					, "home/wiki/Special:XowaDownloadCentral")	// HOME
    +	, Key_nav_setup_import_from_list							= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.setup.import_from_list"					, "home/wiki/Dashboard/Import/Online")		// HOME
    +	, Key_nav_setup_import_from_script							= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.setup.import_from_script"				, "home/wiki/Dashboard/Import/Offline")		// HOME
    +	, Key_nav_setup_maintenance									= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.setup.maintenance"						, "home/wiki/Dashboard/Wiki_maintenance")	// HOME
    +	, Key_nav_setup_download									= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.setup.download"							, "home/wiki/Dashboard/Image_databases")	// HOME
    +
    +	, Key_nav_system_data_log_session							= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.system_data.log_session"				, "Special:XowaSystemData?type=log_session")
    +	, Key_nav_system_data_cfg_app								= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.system_data.cfg_app"					, "Special:XowaSystemData?type=cfg_app")
    +	, Key_nav_system_data_cfg_lang								= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.system_data.cfg_lang"					, "Special:XowaSystemData?type=cfg_lang")
    +	, Key_nav_system_data_usr_history							= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.system_data.usr_history"				, "Special:XowaSystemData?type=usr_history")
    +
    +	, Key_nav_personal_item										= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.personal.item"							, "home/wiki/Special:XowaWikiItem")
    +	, Key_nav_personal_list										= new_page_(Xog_ctg_itm_.Tid_nav_pages		, "xowa.nav.personal.list"							, "home/wiki/Special:XowaWikiDirectory")
    +
    +	, Key_gui_font_increase										= new_dflt_(Xog_ctg_itm_.Tid_font			, "xowa.gui.font.increase")
    +	, Key_gui_font_decrease										= new_dflt_(Xog_ctg_itm_.Tid_font			, "xowa.gui.font.decrease")
    +	, Key_gui_font_reset										= new_dflt_(Xog_ctg_itm_.Tid_font			, "xowa.gui.font.reset")
    +
    +	, Key_gui_page_view_mode_read								= new_dflt_(Xog_ctg_itm_.Tid_page			, "xowa.gui.page.view.mode_read")
    +	, Key_gui_page_view_mode_edit								= new_dflt_(Xog_ctg_itm_.Tid_page			, "xowa.gui.page.view.mode_edit")
    +	, Key_gui_page_view_mode_html								= new_dflt_(Xog_ctg_itm_.Tid_page			, "xowa.gui.page.view.mode_html")
    +	, Key_gui_page_view_refresh									= new_dflt_(Xog_ctg_itm_.Tid_page			, "xowa.gui.page.view.refresh")
    +	, Key_gui_page_view_reload									= new_dflt_(Xog_ctg_itm_.Tid_page			, "xowa.gui.page.view.reload")
    +	, Key_gui_page_view_save_as									= new_dflt_(Xog_ctg_itm_.Tid_page			, "xowa.gui.page.view.save_as")
    +	, Key_gui_page_view_print									= new_dflt_(Xog_ctg_itm_.Tid_page			, "xowa.gui.page.view.print")
    +
    +	, Key_gui_page_selection_select_all							= new_dflt_(Xog_ctg_itm_.Tid_selection		, "xowa.gui.page.selection.select_all")
    +	, Key_gui_page_selection_copy								= new_dflt_(Xog_ctg_itm_.Tid_selection		, "xowa.gui.page.selection.copy")
    +	, Key_gui_page_selection_save_file_as						= new_dflt_(Xog_ctg_itm_.Tid_selection		, "xowa.gui.page.selection.save_file_as")
    +
    +	, Key_gui_edit_save											= new_dflt_(Xog_ctg_itm_.Tid_edit			, "xowa.gui.page.edit.save")
    +	, Key_gui_edit_save_draft									= new_dflt_(Xog_ctg_itm_.Tid_edit			, "xowa.gui.page.edit.save_draft")
    +	, Key_gui_edit_focus_edit_box								= new_dflt_(Xog_ctg_itm_.Tid_edit			, "xowa.gui.page.edit.focus_edit_box")
    +	, Key_gui_edit_preview										= new_dflt_(Xog_ctg_itm_.Tid_edit			, "xowa.gui.page.edit.preview")
    +	, Key_gui_edit_dbg_tmpl										= new_dflt_(Xog_ctg_itm_.Tid_edit			, "xowa.gui.page.edit.dbg_tmpl")
    +	, Key_gui_edit_dbg_html										= new_dflt_(Xog_ctg_itm_.Tid_edit			, "xowa.gui.page.edit.dbg_html")
    +	, Key_gui_edit_exec											= new_dflt_(Xog_ctg_itm_.Tid_edit			, "xowa.gui.page.edit.exec")
    +
    +	, Key_gui_browser_url_focus									= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.url.focus")
    +	, Key_gui_browser_url_exec									= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.url.exec")
    +	, Key_gui_browser_url_exec_by_paste							= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.url.exec_by_paste")
    +	, Key_gui_browser_url_exec_new_tab_by_paste					= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.url.exec_new_tab_by_paste")
    +	, Key_gui_browser_url_restore								= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.url.restore")
    +	, Key_gui_browser_url_type									= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.url.type")
    +
    +	, Key_gui_browser_search_focus								= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.search.focus")
    +	, Key_gui_browser_search_exec								= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.search.exec")
    +	, Key_gui_browser_allpages_focus							= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.allpages.focus")
    +	, Key_gui_browser_allpages_exec								= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.allpages.exec")
    +	, Key_gui_browser_tabs_new_dflt__at_dflt__focus_y			= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.new_dflt__at_dflt__focus_y")
    +	, Key_gui_browser_tabs_new_link__at_dflt__focus_n			= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.new_link__at_dflt__focus_n")
    +	, Key_gui_browser_tabs_new_link__at_dflt__focus_y			= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.new_link__at_dflt__focus_y")
    +	, Key_gui_browser_tabs_new_href__at_dflt__focus_y			= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.new_href__at_dflt__focus_y")
    +	, Key_gui_browser_tabs_new_dupe__at_dflt__focus_y			= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.new_dupe__at_dflt__focus_y")
    +	, Key_gui_browser_tabs_select_bwd							= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.select_bwd")
    +	, Key_gui_browser_tabs_select_fwd							= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.select_fwd")
    +	, Key_gui_browser_tabs_select_by_idx_1						= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.select_by_idx_1")
    +	, Key_gui_browser_tabs_select_by_idx_2						= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.select_by_idx_2")
    +	, Key_gui_browser_tabs_select_by_idx_3						= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.select_by_idx_3")
    +	, Key_gui_browser_tabs_select_by_idx_4						= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.select_by_idx_4")
    +	, Key_gui_browser_tabs_select_by_idx_5						= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.select_by_idx_5")
    +	, Key_gui_browser_tabs_select_by_idx_6						= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.select_by_idx_6")
    +	, Key_gui_browser_tabs_select_by_idx_7						= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.select_by_idx_7")
    +	, Key_gui_browser_tabs_select_by_idx_8						= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.select_by_idx_8")
    +	, Key_gui_browser_tabs_select_by_idx_9						= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.select_by_idx_9")
    +	, Key_gui_browser_tabs_move_bwd								= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.move_bwd")
    +	, Key_gui_browser_tabs_move_fwd								= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.move_fwd")
    +	, Key_gui_browser_tabs_close_cur							= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.close_cur")
    +	, Key_gui_browser_tabs_close_others							= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.close_others")
    +	, Key_gui_browser_tabs_close_to_bgn							= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.close_to_bgn")
    +	, Key_gui_browser_tabs_close_to_end							= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.close_to_end")
    +	, Key_gui_browser_tabs_close_undo							= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.close_undo")
    +	, Key_gui_browser_tabs_pin_toggle							= new_dflt_(Xog_ctg_itm_.Tid_tabs			, "xowa.gui.browser.tabs.pin_toggle")
    +	, Key_gui_browser_html_focus								= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.html.focus")
    +	, Key_gui_browser_html_selection_focus_toggle				= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.html.selection_focus_toggle")
    +	, Key_gui_browser_find_show									= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.find.show")
    +	, Key_gui_browser_find_show_by_paste						= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.find.show_by_paste")
    +	, Key_gui_browser_find_hide									= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.find.hide")
    +	, Key_gui_browser_find_exec									= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.find.exec")
    +	, Key_gui_browser_find_type									= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.find.type")
    +	, Key_gui_browser_find_find_fwd								= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.find.find_fwd")
    +	, Key_gui_browser_find_find_bwd								= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.find.find_bwd")
    +	, Key_gui_browser_find_case_toggle							= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.find.case_toggle")
    +	, Key_gui_browser_find_wrap_toggle							= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.find.wrap_toggle")
    +	, Key_gui_browser_prog_focus								= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.prog.focus")
    +	, Key_gui_browser_prog_log_show								= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.prog_log.show")
    +	, Key_gui_browser_info_focus								= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.info.focus")
    +	, Key_gui_browser_info_clear								= new_dflt_(Xog_ctg_itm_.Tid_browser		, "xowa.gui.browser.info.clear")
    +	, Key_gui_browser_nightmode_toggle                          = new_dflt_(Xog_ctg_itm_.Tid_browser        , "xowa.gui.browser.nightmode_toggle")
    +
    +	, Key_gui_menus_group_file									= "xowa.gui.menus.group.file"
    +	, Key_gui_menus_group_edit									= "xowa.gui.menus.group.edit"
    +	, Key_gui_menus_group_view									= "xowa.gui.menus.group.view"
    +	, Key_gui_menus_group_history								= "xowa.gui.menus.group.history"
    +	, Key_gui_menus_group_bookmarks								= "xowa.gui.menus.group.bookmarks"
    +	, Key_gui_menus_group_tools									= "xowa.gui.menus.group.tools"
    +	, Key_gui_menus_group_tools_wikis							= "xowa.gui.menus.group.tools.wikis"
    +	, Key_gui_menus_group_help									= "xowa.gui.menus.group.help"
    +	, Key_gui_menus_group_system_data							= "xowa.gui.menus.group.system_data"
    +	, Key_gui_menus_group_tabs									= "xowa.gui.menus.group.tabs"
    +
    +	, Key_html_tidy_toggle										= new_dflt_(Xog_ctg_itm_.Tid_html			, "xowa.html.tidy.toggle")
    +	, Key_html_tidy_engine_tidy_								= new_dflt_(Xog_ctg_itm_.Tid_html			, "xowa.html.tidy.engine_tidy_")
    +	, Key_html_tidy_engine_jtidy_								= new_dflt_(Xog_ctg_itm_.Tid_html			, "xowa.html.tidy.engine_jtidy_")
    +
    +	, Key_net_enabled											= new_dflt_(Xog_ctg_itm_.Tid_net			, "xowa.net.enabled")
    +	, Key_net_enabled_n_										= new_dflt_(Xog_ctg_itm_.Tid_net			, "xowa.net.enabled_n_")
    +	, Key_net_enabled_y_										= new_dflt_(Xog_ctg_itm_.Tid_net			, "xowa.net.enabled_y_")
    +	, Key_net_enabled_x_										= new_dflt_(Xog_ctg_itm_.Tid_net			, "xowa.net.enabled_x_")
    +
    +	, Key_usr_bookmarks_add										= new_dflt_(Xog_ctg_itm_.Tid_bookmarks		, "xowa.usr.bookmarks.add")
    +	, Key_usr_bookmarks_show									= new_dflt_(Xog_ctg_itm_.Tid_bookmarks		, "xowa.usr.bookmarks.show")
    +
    +	, Key_usr_history_goto_recent								= new_dflt_(Xog_ctg_itm_.Tid_history		, "xowa.usr.history.goto_recent")
    +	, Key_usr_history_show										= new_page_(Xog_ctg_itm_.Tid_history		, "xowa.usr.history.show"							, "home/wiki/Special:XowaPageHistory")
    +
    +	, Key_xtns_scribunto_engine_lua_							= new_dflt_(Xog_ctg_itm_.Tid_xtns			, "xowa.xtns.scribunto.engine_lua_")
    +	, Key_xtns_scribunto_engine_luaj_							= new_dflt_(Xog_ctg_itm_.Tid_xtns			, "xowa.xtns.scribunto.engine_luaj_")
    +	;
    +	private static String new_dflt_(int ctg, String key)				{return new_text_(ctg, key, "app.api." + String_.Mid(key, 5) + ";");}		// 5 to skip "xowa."
    +	private static String new_page_(int ctg, String key, String page)	{return new_text_(ctg, key, "app.api.nav.goto(\"" + page + "\");");}
    +	private static String new_text_(int ctg, String key, String text)	{
    +		Xog_cmd_ctg ctg_itm = Xog_ctg_itm_.Ary[ctg];
    +		regy.Add(key, new Xog_cmd_itm(key, ctg_itm, text));
    +		return key;
    +	}
    +	public static int Regy_len() {return regy.Count();}
    +	public static Xog_cmd_itm Regy_get_at(int i) {return (Xog_cmd_itm)regy.Get_at(i);}
    +	public static Xog_cmd_itm Regy_get_or_null(String key) {return (Xog_cmd_itm)regy.Get_by(key);}
    +	public static void Regy_add(Xog_cmd_itm itm) {regy.Add(itm.Key(), itm);}
    +	public static final    byte[]
    +	  Msg_pre_api		= Bry_.new_a7("api-")
    +	, Msg_pre_ctg		= Bry_.new_a7("api.ctg-")
    +	, Msg_suf_name		= Bry_.new_a7("-name")
    +	, Msg_suf_tip		= Bry_.new_a7("-tip")
    +	, Msg_suf_letter	= Bry_.new_a7("-letter")
    +	, Msg_suf_image		= Bry_.new_a7("-image")
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_mgr.java b/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_mgr.java
    index a27517de8..44fdea61d 100644
    --- a/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_mgr.java
    @@ -13,3 +13,44 @@ 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.guis.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.xowa.apps.apis.xowa.*;
    +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*;
    +public class Xog_cmd_mgr {
    +	public void Init_by_kit(Xoae_app app) {
    +		invk_mgr.Ctor(app, this);
    +		Load_ctg_msgs(app);
    +		Load_cmd_msgs(app);
    +	}
    +	public Xog_cmd_mgr_invk Invk_mgr() {return invk_mgr;} private Xog_cmd_mgr_invk invk_mgr = new Xog_cmd_mgr_invk();
    +	private void Load_ctg_msgs(Xoae_app app) {
    +		Xog_cmd_ctg[] ary = Xog_ctg_itm_.Ary;
    +		int len = ary.length;
    +		Xol_lang_itm lang = app.Usere().Lang();
    +		for (int i = 0; i < len; i++) {
    +			Xog_cmd_ctg itm = ary[i];
    +			itm.Name_(Xol_msg_mgr_.Get_msg_val_gui_or_null(app.Lang_mgr(), lang, Xog_cmd_itm_.Msg_pre_ctg, itm.Key_bry(), Xog_cmd_itm_.Msg_suf_name));
    +		}
    +	}
    +	private void Load_cmd_msgs(Xoae_app app) {
    +		int len = this.Len();
    +		Xol_lang_itm lang = app.Usere().Lang();
    +		for (int i = 0; i < len; i++) {
    +			Xog_cmd_itm itm = this.Get_at(i);
    +			itm.Name_(Xol_msg_mgr_.Get_msg_val_gui_or_null(app.Lang_mgr(), lang, Xog_cmd_itm_.Msg_pre_api, itm.Key_bry(), Xog_cmd_itm_.Msg_suf_name));
    +			itm.Tip_(Xol_msg_mgr_.Get_msg_val_gui_or_null(app.Lang_mgr(), lang, Xog_cmd_itm_.Msg_pre_api, itm.Key_bry(), Xog_cmd_itm_.Msg_suf_tip));
    +		}
    +	}
    +	public int Len() {return Xog_cmd_itm_.Regy_len();}
    +	public Xog_cmd_itm Get_at(int i) {return Xog_cmd_itm_.Regy_get_at(i);}
    +	public Xog_cmd_itm Get_or_null(String key) {return Xog_cmd_itm_.Regy_get_or_null(key);}
    +	public Xog_cmd_itm Get_or_make(String key) {
    +		Xog_cmd_itm rv = Xog_cmd_itm_.Regy_get_or_null(key);
    +		if (rv == null) {
    +			rv = new Xog_cmd_itm(key, Xog_ctg_itm_.Itm_custom, null);	// pass null for cmd; will be filled in
    +			Xog_cmd_itm_.Regy_add(rv);
    +		}
    +		return rv;
    +	}
    +//		public Xog_cmd_regy Regy() {return regy;} private Xog_cmd_regy regy = new Xog_cmd_regy();
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_mgr_invk.java b/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_mgr_invk.java
    index a27517de8..8790a2190 100644
    --- a/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_mgr_invk.java
    +++ b/400_xowa/src/gplx/xowa/guis/cmds/Xog_cmd_mgr_invk.java
    @@ -13,3 +13,13 @@ 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.guis.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Xog_cmd_mgr_invk implements Gfo_invk {
    +	private Xoae_app app; private Xog_cmd_mgr cmd_mgr;
    +	public void Ctor(Xoae_app app, Xog_cmd_mgr cmd_mgr) {this.app = app; this.cmd_mgr = cmd_mgr;}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		Xog_cmd_itm cmd_itm = cmd_mgr.Get_or_null(k);
    +		if (cmd_itm == null) return Gfo_invk_.Rv_unhandled;
    +		return app.Gfs_mgr().Run_str(cmd_itm.Cmd());
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/history/Xog_history_itm.java b/400_xowa/src/gplx/xowa/guis/history/Xog_history_itm.java
    index a27517de8..0f6b831b5 100644
    --- a/400_xowa/src/gplx/xowa/guis/history/Xog_history_itm.java
    +++ b/400_xowa/src/gplx/xowa/guis/history/Xog_history_itm.java
    @@ -13,3 +13,38 @@ 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.guis.history; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Xog_history_itm {
    +	private final boolean redirect_force;
    +	public Xog_history_itm(byte[] wiki, byte[] page, byte[] anch, byte[] qarg, boolean redirect_force, String bmk_pos) {
    +		this.key = Bry_.Add_w_dlm(Byte_ascii.Pipe, wiki, page, anch, qarg, redirect_force ? Bool_.Y_bry : Bool_.N_bry);
    +		this.wiki = wiki; this.page = page; this.anch = anch; this.qarg = qarg;
    +		this.redirect_force = redirect_force; this.bmk_pos = bmk_pos;
    +	}
    +	public byte[] Key() {return key;} private final byte[] key;
    +	public byte[] Wiki() {return wiki;} private final byte[] wiki;
    +	public byte[] Page() {return page;} private final byte[] page;
    +	public byte[] Anch() {return anch;} private final byte[] anch;
    +	public byte[] Qarg() {return qarg;} private final byte[] qarg;
    +	public String Bmk_pos() {return bmk_pos;} public void Bmk_pos_(String v) {bmk_pos = v;} private String bmk_pos;
    +	public boolean Eq_wo_bmk_pos(Xog_history_itm comp) {
    +		return	Bry_.Eq(wiki, comp.wiki)
    +			&&	Bry_.Eq(page, comp.page)
    +			&&	Bry_.Eq(anch, comp.anch)
    +			&&	Bry_.Eq(qarg, comp.qarg)
    +			&&	redirect_force == comp.redirect_force
    +			;
    +	}
    +	public void Srl_save(Bry_bfr bfr) {
    +		byte[] bmk_bry = Bry_.Replace(Bry_.new_u8(bmk_pos), Byte_ascii.Pipe, Byte_ascii.Tilde);	// replace | with ~; EX: "0|1|2" -> "0~1~2"
    +		bfr.Add(key).Add_byte_pipe().Add(bmk_bry).Add_byte_nl();
    +	}
    +	public static Xog_history_itm Srl_load(byte[] raw) {
    +		byte[][] atrs = Bry_split_.Split(raw, Byte_ascii.Pipe);
    +		byte[] bmk_bry = atrs.length == 6 ? atrs[5] : Bry_.Empty;
    +		bmk_bry = Bry_.Replace(bmk_bry, Byte_ascii.Tilde, Byte_ascii.Pipe);
    +		return new Xog_history_itm(atrs[0], atrs[1], atrs[2], atrs[3], atrs[4] == Bool_.Y_bry, String_.new_a7(bmk_bry));
    +	}
    +	public static final String Html_doc_pos_toc = "top";
    +	public static final Xog_history_itm Null = new Xog_history_itm(null, null, null, null, false, null);
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/history/Xog_history_mgr.java b/400_xowa/src/gplx/xowa/guis/history/Xog_history_mgr.java
    index a27517de8..8e488a3d3 100644
    --- a/400_xowa/src/gplx/xowa/guis/history/Xog_history_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/history/Xog_history_mgr.java
    @@ -13,3 +13,63 @@ 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.guis.history; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Xog_history_mgr {
    +	private final    Ordered_hash hash = Ordered_hash_.New_bry(); private final    Xog_history_stack stack = new Xog_history_stack();
    +	public int Count() {return hash.Count();}
    +	public Xoae_page Cur_page(Xowe_wiki wiki) {return Get_or_fetch(wiki, stack.Cur_itm());}
    +	public Xoae_page Go_bwd(Xowe_wiki wiki) {return Go_by_dir(wiki, Bool_.N);}
    +	public Xoae_page Go_fwd(Xowe_wiki wiki) {return Go_by_dir(wiki, Bool_.Y);}
    +	public Xoae_page Go_by_dir(Xowe_wiki wiki, boolean fwd) {
    +		Xog_history_itm itm = fwd ? stack.Go_fwd() : stack.Go_bwd();
    +		if (itm == Xog_history_itm.Null) return Xoae_page.Empty;
    +		Xoae_page rv = Get_or_fetch(wiki, itm);
    +		byte[] anch_key = itm.Anch();
    +		rv.Url().Anch_bry_(anch_key); // must override anchor as it may be different for cached page
    +		rv.Html_data().Bmk_pos_(itm.Bmk_pos());
    +		return rv;
    +	}
    +	public void Add(Xoae_page page) {
    +		Xog_history_itm new_itm = Xog_history_mgr.new_(page);
    +		stack.Add(new_itm);
    +		byte[] page_key = Build_page_key(page);
    +		if (!hash.Has(page_key))
    +			hash.Add(page_key, page);
    +	}
    +	public void Update_html_doc_pos(Xoae_page page, byte history_nav_type) {
    +		Xog_history_itm itm = Get_recent(page, history_nav_type);
    +		if (itm != null) itm.Bmk_pos_(page.Html_data().Bmk_pos());
    +	}
    +	private Xog_history_itm Get_recent(Xoae_page page, byte history_nav_type) {
    +		int pos = -1;
    +		int list_pos = stack.Cur_pos();
    +		switch (history_nav_type) {
    +			case Xog_history_stack.Nav_fwd:			pos = list_pos - 1; break;
    +			case Xog_history_stack.Nav_bwd:			pos = list_pos + 1; break;
    +			case Xog_history_stack.Nav_by_anchor:	pos = list_pos; break;
    +		}
    +		if (pos < 0 || pos >= stack.Len()) return null;
    +		Xog_history_itm recent = stack.Get_at(pos);
    +		Xog_history_itm page_itm = Xog_history_mgr.new_(page);
    +		return page_itm.Eq_wo_bmk_pos(recent) ? recent : null;	// check that recent page actually matches current; DATE:2014-05-10
    +	}
    +	private Xoae_page Get_or_fetch(Xowe_wiki wiki, Xog_history_itm itm) {
    +		byte[] page_key = Build_page_key(itm.Wiki(), itm.Page(), itm.Qarg());
    +		Xoae_page rv = (Xoae_page)hash.Get_by(page_key);
    +		if (rv != null) return rv;
    +		Xoa_ttl ttl = Xoa_ttl.Parse(wiki, itm.Page());
    +		return wiki.Data_mgr().Load_page_by_ttl(ttl);
    +	}
    +	private static byte[] Build_page_key(Xoae_page page) {return Build_page_key(page.Wiki().Domain_bry(), page.Ttl().Full_url(), page.Url().Qargs_mgr().To_bry());}
    +	private static byte[] Build_page_key(byte[] wiki_key, byte[] page_key, byte[] args_key) {return Bry_.Add_w_dlm(Byte_ascii.Pipe, wiki_key, page_key, args_key);}
    +	public static Xog_history_itm new_(Xoae_page pg) {
    +		byte[] wiki = pg.Wiki().Domain_bry();
    +		byte[] page = pg.Ttl().Full_url();		// get page_name only (no anchor; no query args)
    +		byte[] anch = pg.Url().Anch_bry();
    +		byte[] qarg = pg.Url().Qargs_mgr().To_bry();
    +		boolean redirect_force = pg.Url().Qargs_mgr().Match(Xoa_url_.Qarg__redirect, Xoa_url_.Qarg__redirect__no);
    +		String bmk_pos = pg.Html_data().Bmk_pos();
    +		if (bmk_pos == null) bmk_pos = Xog_history_itm.Html_doc_pos_toc;	// never allow null doc_pos; set to top
    +		return new Xog_history_itm(wiki, page, anch, qarg, redirect_force, bmk_pos);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/history/Xog_history_stack.java b/400_xowa/src/gplx/xowa/guis/history/Xog_history_stack.java
    index a27517de8..8b41ed14b 100644
    --- a/400_xowa/src/gplx/xowa/guis/history/Xog_history_stack.java
    +++ b/400_xowa/src/gplx/xowa/guis/history/Xog_history_stack.java
    @@ -13,3 +13,56 @@ 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.guis.history; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Xog_history_stack {
    +	private final    List_adp list = List_adp_.New();
    +	public int Len() {return list.Count();}
    +	public void Clear() {list.Clear(); cur_pos = 0;}
    +	public Xog_history_itm Get_at(int i) {return (Xog_history_itm)list.Get_at(i);}
    +	public int Cur_pos() {return cur_pos;} private int cur_pos = 0;
    +	public Xog_history_itm Cur_itm() {return list.Count() == 0 ? Xog_history_itm.Null : (Xog_history_itm)list.Get_at(cur_pos);}
    +	public void Add(Xog_history_itm new_itm) {
    +		Xog_history_itm cur_itm = this.Cur_itm(); 
    +		if (cur_itm != Xog_history_itm.Null && cur_itm.Eq_wo_bmk_pos(new_itm)) return;		// do not add if last itm is same;
    +		this.Del_from(cur_pos + 1);
    +		list.Add(new_itm);
    +		cur_pos = list.Count() - 1;
    +	}
    +	public Xog_history_itm Go_bwd() {
    +		if (list.Count() == 0) return Xog_history_itm.Null;
    +		if (cur_pos == 0) return Xog_history_itm.Null;
    +		--cur_pos;
    +		return this.Cur_itm();
    +	}
    +	public Xog_history_itm Go_fwd() {
    +		int list_count = list.Count();
    +		if (list_count == 0) return Xog_history_itm.Null;
    +		if (cur_pos == list_count - 1) return Xog_history_itm.Null;
    +		++cur_pos;
    +		return this.Cur_itm();
    +	}
    +	private void Del_from(int from) {
    +		int len = list.Count();
    +		if (from <= len - 1)
    +			list.Del_range(from, len - 1);
    +	}
    +	public void Srl_save(Bry_bfr bfr) {
    +		int len = list.Count();
    +		for (int i = 0; i < len; ++i) {
    +			Xog_history_itm itm = (Xog_history_itm)list.Get_at(i);
    +			itm.Srl_save(bfr);
    +		}
    +	}
    +	public void Srl_load(byte[] bry) {
    +		list.Clear();
    +		byte[][] lines = Bry_split_.Split_lines(bry);
    +		int len = lines.length;
    +		for (int i = 0; i < len; ++i) {
    +			byte[] line = lines[i];
    +			Xog_history_itm itm = Xog_history_itm.Srl_load(line);
    +			this.Add(itm);
    +		}
    +	}
    +	public void Cur_pos_(int v) {this.cur_pos = v;}
    +	public static final byte Nav_fwd = 1, Nav_bwd = 2, Nav_by_anchor = 3;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/history/Xog_history_stack_tst.java b/400_xowa/src/gplx/xowa/guis/history/Xog_history_stack_tst.java
    index a27517de8..708977913 100644
    --- a/400_xowa/src/gplx/xowa/guis/history/Xog_history_stack_tst.java
    +++ b/400_xowa/src/gplx/xowa/guis/history/Xog_history_stack_tst.java
    @@ -13,3 +13,71 @@ 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.guis.history; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import org.junit.*;
    +public class Xog_history_stack_tst {		
    +	@Before public void init() {fxt.Clear();} private Xog_history_stack_fxt fxt = new Xog_history_stack_fxt();
    +	@Test  public void Init()				{fxt.Test_cur(null);}
    +	@Test  public void Add_1()				{fxt.Exec_add_many("A").Test_cur("A").Test_len(1).Test_pos(0);}
    +	@Test  public void Add_same()			{fxt.Exec_add_many("A", "A").Test_cur("A").Test_len(1).Test_pos(0);}
    +	@Test  public void Add_3()				{fxt.Exec_add_many("A", "B", "C").Test_cur("C").Test_len(3).Test_pos(2);}
    +	@Test  public void Add_3_bwd()			{fxt.Exec_add_many("A", "B", "C").Exec_go_bwd().Test_cur("B").Test_pos(1);}
    +	@Test  public void Add_3_bwd_fwd()		{fxt.Exec_add_many("A", "B", "C").Exec_go_bwd().Exec_go_fwd().Test_cur("C").Test_pos(2);}
    +	@Test  public void Add_3_bwd_add()		{fxt.Exec_add_many("A", "B", "C").Exec_go_bwd().Exec_add_many("D").Test_len(3).Test_cur("D").Test_pos(2);}
    +	@Test  public void Add_3_bwd_bwd_add()	{fxt.Exec_add_many("A", "B", "C").Exec_go_bwd().Exec_go_bwd().Exec_add_many("D").Test_len(2).Test_cur("D").Test_pos(1);}
    +	@Test  public void Add_dif_ns()			{fxt.Exec_add_many("A", "Help:A").Test_cur("Help:A");}	// PURPOSE.fix: page_stack was only differtiating by Page_db, not Full; EX: Unicode -> Category:Unicode
    +	@Test  public void Add_qargs() {// PURPOSE.fix: page_stack was only differentiating by qtxt args
    +		fxt	.Exec_add_one("Special:AllPages", "?from=A")
    +			.Exec_add_one("Special:AllPages", "?from=B")
    +			.Exec_add_many("B")
    +			.Exec_go_bwd()
    +			.Test_cur("Special:AllPages")
    +			.Test_cur_qargs("?from=B")
    +			;
    +	}
    +}
    +class Xog_history_stack_fxt {
    +	public Xog_history_stack_fxt Clear() {
    +		stack.Clear();
    +		if (app == null) {
    +			app = Xoa_app_fxt.Make__app__edit();
    +			wiki = Xoa_app_fxt.Make__wiki__edit(app);
    +		}
    +		return this;
    +	}	private Xoae_app app; private Xowe_wiki wiki; private Xog_history_stack stack = new Xog_history_stack();
    +	public Xog_history_stack_fxt Test_cur(String expd) {
    +		Xog_history_itm page = stack.Cur_itm();
    +		String actl = page == null ? null : String_.new_u8(page.Page());
    +		Tfds.Eq(expd, actl, "cur");
    +		return this;
    +	}
    +	public Xog_history_stack_fxt Test_cur_qargs(String expd) {
    +		Xog_history_itm page = stack.Cur_itm();
    +		String actl = page == null ? null : String_.new_u8(page.Qarg());
    +		Tfds.Eq(expd, actl, "cur_qargs");
    +		return this;
    +	}
    +	public Xog_history_stack_fxt Exec_go_bwd() {stack.Go_bwd(); return this;}
    +	public Xog_history_stack_fxt Exec_go_fwd() {stack.Go_fwd(); return this;}
    +	public Xog_history_stack_fxt Exec_add_many(String... ary) {
    +		int len = ary.length;
    +		for (int i = 0; i < len; i++) {
    +			String ttl = ary[i];
    +			Exec_add_one(ttl, null);
    +		}
    +		return this;
    +	}
    +	public Xog_history_stack_fxt Exec_add_one(String ttl_str, String arg_str) {
    +		byte[] ttl_bry = Bry_.new_u8(ttl_str);
    +		Xoa_ttl ttl = Xoa_ttl.Parse(wiki, ttl_bry);
    +		Xoae_page page = Xoae_page.New_test(wiki, ttl);
    +		byte[] url_bry = ttl_bry;
    +		if (arg_str != null) url_bry = Bry_.Add(url_bry, Bry_.new_u8(arg_str));			
    +		Xoa_url url = app.User().Wikii().Utl__url_parser().Parse(url_bry);
    +		page.Url_(url);  // set url b/c history_mgr.Add uses url
    +		stack.Add(Xog_history_mgr.new_(page));
    +		return this;
    +	}
    +	public Xog_history_stack_fxt Test_pos(int expd) {Tfds.Eq(expd, stack.Cur_pos(), "pos"); return this;}
    +	public Xog_history_stack_fxt Test_len(int expd) {Tfds.Eq(expd, stack.Len(), "len"); return this;}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/langs/Xol_font_info.java b/400_xowa/src/gplx/xowa/guis/langs/Xol_font_info.java
    index a27517de8..8706862c9 100644
    --- a/400_xowa/src/gplx/xowa/guis/langs/Xol_font_info.java
    +++ b/400_xowa/src/gplx/xowa/guis/langs/Xol_font_info.java
    @@ -13,3 +13,31 @@ 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.guis.langs; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.gfui.*; import gplx.gfui.draws.*;
    +public class Xol_font_info implements Gfo_invk, Gfo_evt_mgr_owner {
    +	private FontStyleAdp style;
    +	public Xol_font_info(String name, float size, FontStyleAdp style) {
    +		this.name = name; this.size = size; this.style = style;
    +	}
    +	public Gfo_evt_mgr		Evt_mgr() {if (evt_mgr == null) evt_mgr = new Gfo_evt_mgr(this); return evt_mgr;} private Gfo_evt_mgr evt_mgr;
    +	public String			Name() {return name;} public Xol_font_info Name_(String v) {name = v; Font_changed_pub(); return this;} private String name;
    +	public float			Size() {return size;} public Xol_font_info Size_(float v) {size = v; Font_changed_pub(); return this;} private float size;
    +	public FontAdp To_font() {return FontAdp.new_(name, size, style);}
    +	public boolean Eq(FontAdp font) {return String_.Eq(name, font.Name()) && size == font.Size() && style.Val() == font.Style().Val();}
    +	public void Init_by_app(Xoae_app app) {
    +		app.Cfg().Bind_many_app(this, Cfg__font_name, Cfg__font_size);
    +	}
    +
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Cfg__font_name))		Name_(m.ReadStr("v"));
    +		else if	(ctx.Match(k, Cfg__font_size))		Size_(m.ReadFloat("v"));
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	public static final String Font_changed = "font_changed";
    +	private void Font_changed_pub() {Gfo_evt_mgr_.Pub_obj(this, Font_changed, "font", this);}
    +
    +	private static final String Cfg__font_name = "xowa.gui.app.font.name";
    +	public static final String Cfg__font_size = "xowa.gui.app.font.size";
    +}	
    diff --git a/400_xowa/src/gplx/xowa/guis/menus/Xog_menu_bldr.java b/400_xowa/src/gplx/xowa/guis/menus/Xog_menu_bldr.java
    index a27517de8..082ff59f2 100644
    --- a/400_xowa/src/gplx/xowa/guis/menus/Xog_menu_bldr.java
    +++ b/400_xowa/src/gplx/xowa/guis/menus/Xog_menu_bldr.java
    @@ -13,3 +13,51 @@ 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.guis.menus; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.xowa.guis.cmds.*;
    +class Xog_menu_bldr {
    +	private int indent = 0;
    +	private Bry_bfr bfr = Bry_bfr_.Reset(0);
    +	public String Gen_str() {return bfr.To_str_and_clear();}
    +	private Xog_menu_bldr Indent_add() {indent += 2; return this;}
    +	private Xog_menu_bldr Indent_del() {indent -= 2; return this;}
    +	private void Indent() {
    +		if (indent > 0)
    +			bfr.Add_byte_repeat(Byte_ascii.Space, indent);
    +	}
    +	public Xog_menu_bldr Add_spr() {
    +		Indent();
    +		bfr.Add(Const_spr);
    +		return this;
    +	}
    +	public Xog_menu_bldr Add_grp_bgn(String key) {
    +		Indent();
    +		bfr.Add(Const_itm_grp_bgn_lhs);
    +		bfr.Add_str_u8(key);
    +		bfr.Add(Const_itm_grp_bgn_rhs);
    +		Indent_add();
    +		return this;
    +	}
    +	public Xog_menu_bldr Add_grp_end() {
    +		Indent_del();
    +		Indent();
    +		bfr.Add(Const_itm_grp_end);
    +		return this;
    +	}
    +	public Xog_menu_bldr Add_btn(String key) {
    +		Indent();
    +		bfr.Add(Const_itm_btn_bgn_lhs);
    +		bfr.Add_str_u8(key);
    +		bfr.Add(Const_itm_btn_bgn_rhs);
    +		return this;
    +	}
    +	private static final    byte[]
    +	  Const_spr				= Bry_.new_a7("add_spr;\n")
    +	, Const_itm_btn_bgn_lhs	= Bry_.new_a7("add_btn_default('")
    +	, Const_itm_btn_bgn_rhs	= Bry_.new_a7("');\n")
    +	, Const_itm_grp_bgn_lhs	= Bry_.new_a7("add_grp_default('")
    +	, Const_itm_grp_bgn_rhs	= Bry_.new_a7("') {\n")
    +	, Const_itm_grp_end		= Bry_.new_a7("}\n")
    +	;
    +	public static final    Xog_menu_bldr Instance = new Xog_menu_bldr(); Xog_menu_bldr() {}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/menus/Xog_menu_mgr.java b/400_xowa/src/gplx/xowa/guis/menus/Xog_menu_mgr.java
    index a27517de8..7c8381302 100644
    --- a/400_xowa/src/gplx/xowa/guis/menus/Xog_menu_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/menus/Xog_menu_mgr.java
    @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository:
     GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
     Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
     */
    +package gplx.xowa.guis.menus; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.xowa.guis.menus.dom.*;
    +import gplx.xowa.langs.*;
    +public class Xog_menu_mgr implements Gfo_invk {
    +	private Xoae_app app;
    +	public Xog_menu_mgr(Xoa_gui_mgr gui_mgr) {
    +		menu_bldr = new Xog_mnu_bldr();
    +		regy = new Xog_mnu_regy(gui_mgr);
    +		popup_mnu_mgr = new Xog_popup_mnu_mgr(gui_mgr, this);
    +		window_mnu_mgr = new Xog_window_mnu_mgr(gui_mgr, this);
    +	}
    +	public Xog_mnu_regy			Regy() {return regy;} private Xog_mnu_regy regy;
    +	public Xog_popup_mnu_mgr	Popup() {return popup_mnu_mgr;} private Xog_popup_mnu_mgr popup_mnu_mgr;
    +	public Xog_window_mnu_mgr	Window() {return window_mnu_mgr;} private Xog_window_mnu_mgr window_mnu_mgr;
    +	public Xog_mnu_bldr			Menu_bldr() {return menu_bldr;} private Xog_mnu_bldr menu_bldr;
    +	public void Init_by_app(Xoae_app app) {
    +		this.app = app;
    +		regy.Init_by_app(app);
    +	}
    +	public void Init_by_kit() {
    +		try {
    +			if (!app.Mode().Tid_is_gui()) return;	// NOTE: do not try to initialize menu if http_server; will fail in headless mode when it tries to load SWT images; DATE:2015-03-27
    +			popup_mnu_mgr.Init_by_kit(app);
    +			window_mnu_mgr.Init_by_kit(app);
    +			Lang_changed(app.Usere().Lang());
    +		}
    +		catch (Exception e) {	// ignore errors while loading custom menus, else fatal error; DATE:2014-07-01
    +			app.Usr_dlg().Warn_many("", "", "error while loading menus; err=~{0}", Err_.Message_gplx_full(e));
    +		}
    +	}
    +	public void Lang_changed(Xol_lang_itm lang) {
    +		window_mnu_mgr.Lang_changed(lang);
    +		popup_mnu_mgr.Lang_changed(lang);
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_contexts))			return popup_mnu_mgr;
    +		else if	(ctx.Match(k, Invk_windows))			return window_mnu_mgr;
    +		else	return Gfo_invk_.Rv_unhandled;
    +	}	private static final String Invk_contexts = "contexts", Invk_windows = "windows";
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/menus/Xog_popup_mnu_mgr.java b/400_xowa/src/gplx/xowa/guis/menus/Xog_popup_mnu_mgr.java
    index a27517de8..dc15875a3 100644
    --- a/400_xowa/src/gplx/xowa/guis/menus/Xog_popup_mnu_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/menus/Xog_popup_mnu_mgr.java
    @@ -13,3 +13,59 @@ 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.guis.menus; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.xowa.guis.menus.dom.*;
    +import gplx.xowa.langs.*;
    +public class Xog_popup_mnu_mgr implements Gfo_invk {
    +	private final    Ordered_hash hash = Ordered_hash_.New();
    +	private final    Xog_mnu_grp[] mnus = new Xog_mnu_grp[6];
    +	private Xoa_gui_mgr gui_mgr;
    +	public Xog_popup_mnu_mgr(Xoa_gui_mgr gui_mgr, Xog_menu_mgr menu_mgr) {
    +		this.gui_mgr = gui_mgr;
    +		html_page = Ctor(0, Root_key_html_page);	// NOTE: default menu; always build first;
    +		html_link = Ctor(1, Root_key_html_link);
    +		html_file = Ctor(2, Root_key_html_file);
    +		tabs_btns = Ctor(3, Root_key_tabs_btns);
    +		prog	  = Ctor(4, Root_key_prog);
    +		info	  = Ctor(5, Root_key_info);
    +	}
    +	public Xog_mnu_grp Tabs_btns()	{return tabs_btns;} private Xog_mnu_grp tabs_btns;
    +	public Xog_mnu_grp Html_page()	{return html_page;} private Xog_mnu_grp html_page;
    +	public Xog_mnu_grp Html_link()	{return html_link;} private Xog_mnu_grp html_link;
    +	public Xog_mnu_grp Html_file()	{return html_file;} private Xog_mnu_grp html_file;
    +	public Xog_mnu_grp Prog()		{return prog;}		private Xog_mnu_grp prog;
    +	public Xog_mnu_grp Info()		{return info;}		private Xog_mnu_grp info;
    +	public void Init_by_kit(Xoae_app app) {
    +		for (int i = 0; i < mnus.length; i++)
    +			mnus[i].Source_exec(gui_mgr.App().Gfs_mgr());	// NOTE: build menu now; NOTE: do not set default here, or else will override user setting
    +		app.Cfg().Bind_many_app(this, Cfg__tabs, Cfg__html__basic, Cfg__html__file, Cfg__html__link, Cfg__status);
    +	}
    +	public void Lang_changed(Xol_lang_itm lang) {
    +		for (int i = 0; i < mnus.length; i++)
    +			Xog_mnu_base.Update_grp_by_lang(gui_mgr.Menu_mgr().Menu_bldr(), lang, mnus[i]);
    +	}
    +	private Xog_mnu_grp Ctor(int i, String key) {
    +		Xog_mnu_grp rv = new Xog_mnu_grp(gui_mgr, true, key);
    +		hash.Add(key, rv);
    +		mnus[i] = rv;
    +		return rv;
    +	}
    +	private void Source_(Xog_mnu_grp mnu, String source) {mnu.Source_(source);}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Cfg__tabs))			Source_(tabs_btns	, m.ReadStr("v"));
    +		else if	(ctx.Match(k, Cfg__html__basic))	Source_(html_page	, m.ReadStr("v"));
    +		else if	(ctx.Match(k, Cfg__html__link))		Source_(html_link	, m.ReadStr("v"));
    +		else if	(ctx.Match(k, Cfg__html__file))		Source_(html_file	, m.ReadStr("v"));
    +		else if	(ctx.Match(k, Cfg__status))			Source_(prog		, m.ReadStr("v"));
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	public static final String Root_key_tabs_btns = "browser.tabs.btns", Root_key_prog = "browser.prog", Root_key_info = "browser.info"
    +	, Root_key_html_page = "html_box", Root_key_html_link = "browser.html.link", Root_key_html_file = "browser.html.file";
    +	private static final String 
    +	  Cfg__tabs			= "xowa.gui.menus.tabs.source"
    +	, Cfg__html__basic	= "xowa.gui.menus.html.basic.source"
    +	, Cfg__html__link	= "xowa.gui.menus.html.link.source"
    +	, Cfg__html__file	= "xowa.gui.menus.html.file.source"
    +	, Cfg__status		= "xowa.gui.menus.status.source";
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/menus/Xog_window_mnu_mgr.java b/400_xowa/src/gplx/xowa/guis/menus/Xog_window_mnu_mgr.java
    index a27517de8..aec67b8d4 100644
    --- a/400_xowa/src/gplx/xowa/guis/menus/Xog_window_mnu_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/menus/Xog_window_mnu_mgr.java
    @@ -13,3 +13,41 @@ 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.guis.menus; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.xowa.guis.menus.dom.*;
    +import gplx.xowa.langs.*;
    +public class Xog_window_mnu_mgr implements Gfo_invk {
    +	private final    Ordered_hash hash = Ordered_hash_.New();
    +	public Xog_mnu_grp Browser() {return browser;} private Xog_mnu_grp browser;
    +	public Xog_window_mnu_mgr(Xoa_gui_mgr gui_mgr, Xog_menu_mgr menu_mgr) {
    +		this.gui_mgr = gui_mgr;
    +		browser = Get_or_new(Root_key_browser_win);
    +	}	private Xoa_gui_mgr gui_mgr;
    +	public void Init_by_kit(Xoae_app app) {
    +		browser.Source_exec(gui_mgr.App().Gfs_mgr());	// NOTE: build menu now; NOTE: do not set default here, or else will override user setting
    +		app.Cfg().Bind_many_app(this, Cfg__browser__enabled, Cfg__browser__source);
    +	}
    +	public Xog_mnu_grp Get_or_new(String key) {			
    +		Xog_mnu_grp rv = (Xog_mnu_grp)hash.Get_by(key);
    +		if (rv == null) {
    +			rv = new Xog_mnu_grp(gui_mgr, false, key);
    +			hash.Add(key, rv);
    +		}
    +		return rv;
    +	}
    +	public void Lang_changed(Xol_lang_itm lang) {
    +		Xog_mnu_base.Update_grp_by_lang(gui_mgr.Menu_mgr().Menu_bldr(), lang, browser);
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Cfg__browser__enabled))		browser.Enabled_(m.ReadYn("v"));
    +		else if	(ctx.Match(k, Cfg__browser__source))		browser.Source_(m.ReadStr("v"));
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	private static final String Root_key_browser_win = "main_win";
    +
    +	private static final String
    +	  Cfg__browser__enabled	= "xowa.gui.menus.browser.enabled"
    +	, Cfg__browser__source	= "xowa.gui.menus.browser.source"
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_base.java b/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_base.java
    index a27517de8..70e486316 100644
    --- a/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_base.java
    +++ b/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_base.java
    @@ -13,3 +13,104 @@ 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.guis.menus.dom; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.menus.*;
    +import gplx.gfui.*; import gplx.gfui.imgs.*;
    +import gplx.xowa.langs.*; import gplx.xowa.guis.cmds.*;
    +public abstract class Xog_mnu_base implements Gfo_invk {
    +	private List_adp list = List_adp_.New();
    +	public Xog_mnu_base() {evt_mgr = new Xog_mnu_evt_mgr(this);}
    +	public Xog_mnu_evt_mgr Evt_mgr() {return evt_mgr;} private Xog_mnu_evt_mgr evt_mgr;
    +	public void Evt_mgr_(Xog_mnu_evt_mgr v) {this.evt_mgr = v;}
    +	public Xoa_gui_mgr Gui_mgr() {return gui_mgr;} private Xoa_gui_mgr gui_mgr;
    +	public abstract boolean Tid_is_app_menu_grp();
    +	public void Ctor(Xoa_gui_mgr gui_mgr) {this.gui_mgr = gui_mgr;}
    +	public void Clear() {
    +		int len = list.Count();
    +		for (int i = 0; i < len; i++) {
    +			Xog_mnu_itm itm = (Xog_mnu_itm)list.Get_at(i);
    +			itm.Clear();
    +		}
    +		list.Clear();
    +	}
    +	public int Len() {return list.Count();}
    +	public Xog_mnu_itm Get_at(int i) {return (Xog_mnu_itm)list.Get_at(i);}
    +	public Xog_mnu_itm Add_btn_default(String key) {return Add_itm_default(Xog_mnu_itm.Tid_btn, key);}
    +	public Xog_mnu_itm Add_chk_default(String key) {return Add_itm_default(Xog_mnu_itm.Tid_chk, key);}
    +	public Xog_mnu_itm Add_rdo_default(String key) {return Add_itm_default(Xog_mnu_itm.Tid_rdo, key);}
    +	public Xog_mnu_itm Add_grp_default(String key) {return Add_itm_default(Xog_mnu_itm.Tid_grp, key);}
    +	private Xog_mnu_itm Add_itm_default(byte tid, String key) {
    +		Xog_mnu_itm itm = gui_mgr.Menu_mgr().Regy().Get_or_make(key);
    +		itm.Tid_(tid);
    +		list.Add(itm);
    +		return itm;
    +	}
    +	public Xog_mnu_itm Add_btn(String key, String text, String shortcut, String img, String cmd)	{
    +		Xog_mnu_itm rv = Add_itm(Xog_mnu_itm.Tid_btn, key, text, shortcut, img);
    +		rv.Cmd_(cmd);
    +		return rv;
    +	}
    +	public Xog_mnu_itm Add_chk(String key, String text, String shortcut, String img, String cmd)	{
    +		Xog_mnu_itm rv = Add_itm(Xog_mnu_itm.Tid_chk, key, text, shortcut, img);
    +		rv.Cmd_(cmd);
    +		return rv;
    +	}
    +	public Xog_mnu_itm Add_rdo(String key, String text, String shortcut, String img, String cmd)	{
    +		Xog_mnu_itm rv = Add_itm(Xog_mnu_itm.Tid_rdo, key, text, shortcut, img);
    +		rv.Cmd_(cmd);
    +		return rv;
    +	}
    +	public Xog_mnu_itm Add_grp(String key, String text, String shortcut, String img)				{return Add_itm(Xog_mnu_itm.Tid_grp, key, text, shortcut, img);}
    +	private Xog_mnu_itm Add_itm(byte tid, String key, String text, String shortcut, String img) {
    +		Xog_mnu_itm itm = gui_mgr.Menu_mgr().Regy().Get_or_make(key);
    +		itm.Tid_(tid);
    +		list.Add(itm);
    +		itm.Init_by_custom(text, shortcut, img);
    +		return itm;
    +	}
    +	public Xog_mnu_itm Add_spr() {
    +		String key = "xowa.spr" + Int_.To_str(list.Count());
    +		Xog_mnu_itm rv = new Xog_mnu_itm(gui_mgr, key).Tid_(Xog_mnu_itm.Tid_spr);
    +		list.Add(rv);
    +		return rv;
    +	}
    +	@gplx.Virtual public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_clear))				this.Clear();
    +//			else if	(ctx.Match(k, Invk_add))				return Add_itm_default(Xog_mnu_itm.Tid_nil, m.ReadStr("v"));
    +		else if	(ctx.Match(k, Invk_add_spr))			return Add_spr();
    +		else if	(ctx.Match(k, Invk_add_grp_default))	return Add_grp_default(m.ReadStr("v"));
    +		else if	(ctx.Match(k, Invk_add_grp))			return Add_grp(m.ReadStr("key"), m.ReadStr("text"), m.ReadStr("shortcut"), m.ReadStrOr("img", ""));
    +		else if	(ctx.Match(k, Invk_add_btn_default))	return Add_btn_default(m.ReadStr("v"));
    +		else if	(ctx.Match(k, Invk_add_btn))			return Add_btn(m.ReadStr("key"), m.ReadStr("text"), m.ReadStr("shortcut"), m.ReadStr("img"), m.ReadStr("cmd"));
    +		else if	(ctx.Match(k, Invk_add_chk_default))	return Add_chk_default(m.ReadStr("v"));
    +		else if	(ctx.Match(k, Invk_add_chk))			return Add_chk(m.ReadStr("key"), m.ReadStr("text"), m.ReadStr("shortcut"), m.ReadStr("img"), m.ReadStr("cmd"));
    +		else if	(ctx.Match(k, Invk_add_rdo_default))	return Add_rdo_default(m.ReadStr("v"));
    +		else if	(ctx.Match(k, Invk_add_rdo))			return Add_rdo(m.ReadStr("key"), m.ReadStr("text"), m.ReadStr("shortcut"), m.ReadStr("img"), m.ReadStr("cmd"));
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	private static final String 
    +	  Invk_clear = "clear"
    +	, Invk_add_spr = "add_spr"
    +	, Invk_add_grp_default = "add_grp_default", Invk_add_grp = "add_grp"
    +	, Invk_add_btn_default = "add_btn_default", Invk_add_btn = "add_btn"
    +	, Invk_add_chk_default = "add_chk_default", Invk_add_chk = "add_chk"
    +	, Invk_add_rdo_default = "add_rdo_default", Invk_add_rdo = "add_rdo"
    +	;
    +	public static void Update_grp_by_lang(Xog_mnu_bldr bldr, Xol_lang_itm lang, Xog_mnu_base grp) {
    +		int len = grp.Len();
    +		for (int i = 0; i < len; i++) {
    +			Xog_mnu_itm itm = grp.Get_at(i);
    +			itm.Init_by_lang(lang);
    +			if (itm.Under_gui() != null) {
    +				itm.Under_gui().Text_(itm.Gui_text());
    +				ImageAdp img = grp.Tid_is_app_menu_grp()
    +					? ImageAdp_.Null						// set image to null if window menu grp
    +					: bldr.Get_img(itm.Img_nest())
    +					;
    +				itm.Under_gui().Img_(img);
    +			}
    +			if (itm.Tid() == Xog_mnu_itm.Tid_grp)
    +				Update_grp_by_lang(bldr, lang, itm);
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_bldr.java b/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_bldr.java
    index a27517de8..d4bb81d9b 100644
    --- a/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_bldr.java
    +++ b/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_bldr.java
    @@ -13,3 +13,71 @@ 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.guis.menus.dom; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.menus.*;
    +import gplx.gfui.*; import gplx.gfui.kits.core.*; import gplx.gfui.imgs.*;
    +import gplx.xowa.guis.cmds.*; import gplx.xowa.apps.gfs.*;
    +public class Xog_mnu_bldr {
    +	private Xoae_app app; private Gfui_kit kit; private Io_url img_dir;
    +	public void Init_by_kit(Xoae_app app, Gfui_kit kit, Io_url img_dir) {
    +		this.app = app; this.kit = kit; this.img_dir = img_dir; 
    +	}
    +	public void Build(Gfui_mnu_grp grp_gui, Xog_mnu_grp grp_dom) {
    +		if (grp_gui == null) return;	// NOTE: hackish, ignore call from user.gfs b/c it fires before kit is inited; note that Xog_popup_mnu_mgr will call html_box's context menu explicitly
    +		grp_gui.Itms_clear();
    +		Build_owner(grp_gui, grp_dom);
    +	}
    +	private void Build_owner(Gfui_mnu_grp grp_gui, Xog_mnu_base grp_dom) {
    +		int len = grp_dom.Len();
    +		for (int i = 0; i < len; i++) {
    +			Xog_mnu_itm sub_dom = grp_dom.Get_at(i);
    +			Gfui_mnu_itm sub_gui = null;
    +			String sub_text = sub_dom.Name(), sub_shortcut = sub_dom.Shortcut();
    +			switch (sub_dom.Tid()) {
    +				case Xog_mnu_itm.Tid_spr: sub_gui = grp_gui.Itms_add_separator(); break;
    +				case Xog_mnu_itm.Tid_btn: sub_gui = Add_btn(grp_gui, sub_dom, sub_text, sub_shortcut); break;
    +				case Xog_mnu_itm.Tid_chk: sub_gui = Add_chk(grp_gui, sub_dom, sub_text, sub_shortcut); break;
    +				case Xog_mnu_itm.Tid_rdo: sub_gui = Add_rdo(grp_gui, sub_dom, sub_text, sub_shortcut); break;
    +				case Xog_mnu_itm.Tid_grp: {
    +					Gfui_mnu_grp sub_gui_grp = grp_gui.Itms_add_grp(sub_dom.Gui_text(), Get_img(sub_dom.Img_nest()));
    +					Build_owner(sub_gui_grp, (Xog_mnu_base)sub_dom);
    +					sub_gui = sub_gui_grp;
    +					break;
    +				}
    +				default: throw Err_.new_unhandled(sub_dom.Tid());
    +			}
    +			sub_dom.Under_gui_(sub_gui);
    +		}
    +	}
    +	public ImageAdp Get_img(String[] img_nest) {
    +		Io_url img_url = img_nest.length == 0 ? Io_url_.Empty : img_dir.GenSubFil_nest(img_nest);
    +		return Io_mgr.Instance.ExistsFil(img_url) ? kit.New_img_load(img_url) : ImageAdp_null.Instance;	// NOTE: must check if file exists else swt exception; NOTE: must use ImageAdp_null.Instance, not ImageAdp_.Null, else error in non-X11 environments
    +	}
    +	private Gfui_mnu_itm Add_btn(Gfui_mnu_grp owner_gui, Xog_mnu_itm sub, String sub_text, String sub_shortcut) {
    +		String cmd_text = "app.api.exec('" + sub.Key() + "');";
    +		GfoMsg msg = Xoa_gfs_mgr_.Parse_to_msg(cmd_text);
    +		ImageAdp img = Get_img(sub.Img_nest());
    +		return owner_gui.Itms_add_btn_msg(sub.Gui_text(), img, app, app.Gfs_mgr(), msg);
    +	}
    +	private Gfui_mnu_itm Add_chk(Gfui_mnu_grp owner_gui, Xog_mnu_itm sub, String sub_text, String sub_shortcut) {
    +		ImageAdp img = Get_img(sub.Img_nest());
    +		GfoMsg msg_n = Xoa_gfs_mgr_.Parse_to_msg("app.api.exec('" + sub.Key() + "n_');");
    +		GfoMsg msg_y = Xoa_gfs_mgr_.Parse_to_msg("app.api.exec('" + sub.Key() + "y_');");
    +		Gfui_mnu_itm mnu_itm = owner_gui.Itms_add_chk_msg(sub.Gui_text(), img, app, app.Gfs_mgr(), msg_n, msg_y);
    +		sub.Evt_mgr().Sub(mnu_itm);
    +		Xog_cmd_itm cmd = app.Gui_mgr().Cmd_mgr().Get_or_null(sub.Key());
    +		boolean v = Bool_.Cast(app.Gfs_mgr().Run_str_for(app, cmd.Cmd()));
    +		mnu_itm.Selected_(v);
    +		return mnu_itm;
    +	}
    +//		private void Add_chk(String key, String text, String shortcut, String img) {
    +//			Xog_mnu_itm rv = Add_itm(Xog_mnu_itm.Tid_chk, key, text, shortcut, img);
    +//			Xog_cmd_itm cmd = gui_mgr.Cmd_mgr().Regy().Get_or_null(key); if (cmd == null) throw Err_.new_wo_type("unknown cmd; key={0}", key);
    +//			Gfo_evt_itm pub = gui_mgr.App().Gfs_mgr().Get_owner_as_event_obj(cmd.Cmd());
    +//			Gfo_evt_mgr_.Sub_same(pub, Xog_mnu_evt_mgr.Evt_selected_changed, rv.Evt_mgr());
    +//		}
    +	private Gfui_mnu_itm Add_rdo(Gfui_mnu_grp owner_gui, Xog_mnu_itm sub, String sub_text, String sub_shortcut) {
    +		ImageAdp img = Get_img(sub.Img_nest());
    +		GfoMsg msg = Xoa_gfs_mgr_.Parse_to_msg("app.api.exec('" + sub.Key() + "');");
    +		return owner_gui.Itms_add_rdo_msg(sub.Gui_text(), img, app, app.Gfs_mgr(), msg);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_evt_mgr.java b/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_evt_mgr.java
    index a27517de8..728326f80 100644
    --- a/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_evt_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_evt_mgr.java
    @@ -13,3 +13,24 @@ 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.guis.menus.dom; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.menus.*;
    +import gplx.gfui.*; import gplx.gfui.kits.core.*; import gplx.xowa.guis.cmds.*;
    +public class Xog_mnu_evt_mgr implements Gfo_evt_itm {
    +	private Ordered_hash itms = Ordered_hash_.New();
    +	public Xog_mnu_evt_mgr(Xog_mnu_base owner) {this.ev_mgr = new Gfo_evt_mgr(this);}
    +	public Gfo_evt_mgr Evt_mgr() {return ev_mgr;} private Gfo_evt_mgr ev_mgr;
    +	public void Sub(Gfui_mnu_itm mnu_itm) {
    +		itms.Add(mnu_itm.Uid(), mnu_itm);
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Evt_selected_changed)) {
    +			int len = itms.Count();
    +			for (int i = 0; i < len; i++) {
    +				Gfui_mnu_itm itm = (Gfui_mnu_itm)itms.Get_at(i);
    +				itm.Selected_(m.ReadBool("v"));
    +			}
    +		}
    +		return this;
    +	}
    +	public static final String Evt_selected_changed = "selected_changed";
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_grp.java b/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_grp.java
    index a27517de8..095df7ac7 100644
    --- a/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_grp.java
    +++ b/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_grp.java
    @@ -13,3 +13,86 @@ 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.guis.menus.dom; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.menus.*;
    +import gplx.gfui.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.standards.*;
    +import gplx.xowa.apps.*; import gplx.xowa.apps.gfs.*;
    +import gplx.xowa.guis.views.*;
    +public class Xog_mnu_grp extends Xog_mnu_base {
    +	public Xog_mnu_grp(Xoa_gui_mgr gui_mgr, boolean mnu_is_popup, String key) {
    +		this.app = gui_mgr.App(); this.mnu_is_popup = mnu_is_popup; this.key = key;
    +		this.Ctor(gui_mgr);
    +	}	private Xoae_app app;
    +	public String Key() {return key;} private String key; private boolean mnu_is_popup;
    +	public Gfui_mnu_grp Under_mnu() {
    +		if (under_mnu.Disposed()) Build();	// NOTE: menu may be disposed when calling .dispose on Swt_html; rebuild if needed; DATE:2014-07-09
    +		return under_mnu;
    +	}	private Gfui_mnu_grp under_mnu; 				
    +	@Override public boolean Tid_is_app_menu_grp() {return !mnu_is_popup;}
    +	public boolean Enabled() {return enabled;} private boolean enabled = true;
    +	public void Enabled_(boolean v) {
    +		this.enabled = v;
    +		if (under_mnu != null)		// null when changed by cfg.gfs
    +			under_mnu.Itms_clear();
    +		if (v)
    +			this.Source_exec(app.Gfs_mgr());
    +		else 
    +			this.Clear();
    +	}
    +	public String Source() {return source;} private String source = "";	// NOTE: default to "" not null, else init will try to run "clear;\nnullbuild;"
    +	public void Source_(String v) {this.source = v; this.Source_exec(app.Gfs_mgr());}
    +	public Object Source_exec(Xoa_gfs_mgr gfs_mgr) {return Source_exec(gfs_mgr, source);}
    +	private Object Source_exec(Xoa_gfs_mgr gfs_mgr, String v) {
    +		if (!enabled) return Gfo_invk_.Rv_handled;
    +		String script = "clear;\n" + v + "build;";
    +		return gfs_mgr.Run_str_for(this, script);
    +	}
    +	public void Build() {
    +		Xoa_gui_mgr gui_mgr = app.Gui_mgr(); Gfui_kit kit = gui_mgr.Kit(); Xog_win_itm win = gui_mgr.Browser_win();
    +		if (!kit.Kit_mode__ready()) return;	// NOTE: .gfs will fire Build before Kit.Init; check that kit is inited
    +		if (under_mnu == null) {
    +			if (mnu_is_popup) {
    +				if		(String_.Eq(key, Xog_popup_mnu_mgr.Root_key_tabs_btns))
    +					under_mnu = kit.New_mnu_popup(key, win.Tab_mgr().Tab_mgr());
    +				else
    +					under_mnu = kit.New_mnu_popup(key, win.Win_box());
    +			}
    +			else
    +				under_mnu = kit.New_mnu_bar(key, win.Win_box());
    +		}
    +		Xog_mnu_bldr bldr = gui_mgr.Menu_mgr().Menu_bldr();
    +		bldr.Build(under_mnu, this);
    +		Xog_mnu_base.Update_grp_by_lang(bldr, app.Usere().Lang(), this);	// NOTE: always set lang after rebuild; else changes in home/wiki/Options/Menus will show blank captions; DATE:2014-06-05
    +		if (mnu_is_popup) {
    +			boolean rebind_to_win = false;
    +			if		(String_.Eq(key, Xog_popup_mnu_mgr.Root_key_tabs_btns)) {
    +				kit.Set_mnu_popup(win.Tab_mgr().Tab_mgr(), under_mnu);
    +				rebind_to_win = true;
    +			}
    +			else if (String_.Eq(key, Xog_popup_mnu_mgr.Root_key_html_page))	// rebind new popup box; DATE:2014-05-16
    +				Bind_popup_menu(gui_mgr); 
    +			else if (String_.Eq(key, Xog_popup_mnu_mgr.Root_key_html_link))
    +				rebind_to_win = true;
    +			else if (String_.Eq(key, Xog_popup_mnu_mgr.Root_key_prog))
    +				kit.Set_mnu_popup(win.Prog_box(), under_mnu);
    +			else if (String_.Eq(key, Xog_popup_mnu_mgr.Root_key_info))
    +				kit.Set_mnu_popup(win.Info_box(), under_mnu);
    +			if (rebind_to_win) {	// WORKAROUND: SWT seems to bind popup menus to window; always rebind window to html_page; DATE:2014-05-17
    +				kit.Set_mnu_popup(win.Win_box(), gui_mgr.Menu_mgr().Popup().Html_page().Under_mnu());
    +			}
    +		}
    +	}
    +	private void Bind_popup_menu(Xoa_gui_mgr gui_mgr) {
    +		Gfui_kit kit = gui_mgr.Kit(); Xog_win_itm win = gui_mgr.Browser_win(); Xog_tab_mgr tab_mgr = win.Tab_mgr();
    +		int tabs_len = tab_mgr.Tabs_len();
    +		for (int i = 0; i < tabs_len; i++) {
    +			Xog_tab_itm tab = tab_mgr.Tabs_get_at(i);
    +			kit.Set_mnu_popup(tab.Html_box(), under_mnu);
    +		}
    +	}
    +	@Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_build))					this.Build();
    +		else	return super.Invk(ctx, ikey, k, m);
    +		return this;
    +	}
    +	private static final String Invk_build = "build";
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_itm.java b/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_itm.java
    index a27517de8..c29e4b0e9 100644
    --- a/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_itm.java
    +++ b/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_itm.java
    @@ -13,3 +13,74 @@ 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.guis.menus.dom; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.menus.*;
    +import gplx.gfui.*; import gplx.gfui.kits.core.*;
    +import gplx.xowa.langs.*; import gplx.xowa.guis.cmds.*; import gplx.xowa.langs.msgs.*;
    +public class Xog_mnu_itm extends Xog_mnu_base {
    +	private Xoa_gui_mgr gui_mgr;
    +	public Xog_mnu_itm(Xoa_gui_mgr gui_mgr, String key) {
    +		this.gui_mgr = gui_mgr; this.key = key; this.key_bry = Bry_.new_u8(key);
    +		this.Ctor(gui_mgr);
    +	}
    +	public byte Tid() {return tid;} public Xog_mnu_itm Tid_(byte v) {tid = v; return this;} private byte tid;		
    +	@Override public boolean Tid_is_app_menu_grp() {return false;}
    +	public String Key() {return key;} private String key; private byte[] key_bry;
    +	public String Name() {return name;} public void Name_(String v) {this.name = v; Gui_text_calc_();} private String name = "";
    +	public String Shortcut() {return shortcut;} public void Shortcut_(String v) {this.shortcut = v; Gui_text_calc_();}  private String shortcut;
    +	public String Gui_text() {return gui_text;} public void Gui_text_calc_() {gui_text = Gui_text_calc(name, shortcut);} private String gui_text = "";
    +	public String[] Img_nest() {return img_nest;} private String[] img_nest = String_.Ary_empty;
    +	private String Cmd() {
    +		Xog_cmd_itm cmd_itm = gui_mgr.Cmd_mgr().Get_or_null(key);
    +		return cmd_itm == null ? "" : cmd_itm.Cmd();
    +	}
    +	public Xog_mnu_itm Cmd_(String cmd) {
    +		Xog_cmd_itm cmd_itm = gui_mgr.Cmd_mgr().Get_or_make(key);
    +		cmd_itm.Cmd_(cmd);
    +		return this;
    +	}
    +	public Gfui_mnu_itm Under_gui() {return under_gui;} public void Under_gui_(Gfui_mnu_itm v) {under_gui = v;} private Gfui_mnu_itm under_gui;
    +	private Xog_mnu_itm Init_by_clone(Xog_mnu_itm comp) {
    +		this.tid = comp.tid; this.name = comp.name; this.shortcut = comp.shortcut; this.img_nest = comp.img_nest; this.gui_text = comp.gui_text;
    +		return this;
    +	}
    +	public void Init_by_lang(Xol_lang_itm lang) {
    +		this.name			= Xol_msg_mgr_.Get_msg_val_gui_or(lang.Lang_mgr(), lang, Xog_cmd_itm_.Msg_pre_api, key_bry, Xog_cmd_itm_.Msg_suf_name, name);	// NOTE: custom cmds must use Get_val_or, not Get_val_or_null; DATE:2014-05-30
    +		this.shortcut		= Xol_msg_mgr_.Get_msg_val_gui_or(lang.Lang_mgr(), lang, Xog_cmd_itm_.Msg_pre_api, key_bry, Xog_cmd_itm_.Msg_suf_letter, shortcut);
    +		this.img_nest		= Img_nest_of(Xol_msg_mgr_.Get_msg_val_gui_or(lang.Lang_mgr(), lang, Xog_cmd_itm_.Msg_pre_api, key_bry, Xog_cmd_itm_.Msg_suf_image, ""));
    +		this.gui_text		= Gui_text_calc(name, shortcut);
    +	}
    +	public void Init_by_custom(String name, String shortcut, String img_nest_str) {
    +		this.name			= name;
    +		this.shortcut		= shortcut;
    +		this.img_nest		= Img_nest_of(img_nest_str);
    +		this.gui_text		= Gui_text_calc(name, shortcut);
    +	}	
    +	public static String Gui_text_calc(String name, String shortcut) {
    +		if (shortcut == null || String_.Len(shortcut) != 1) return name;			// shortcut is null or > 1 char; return name
    +		int pos = String_.FindFwd(String_.Lower(name), String_.Lower(shortcut));	// search for shortcut in name
    +		if (pos == String_.Find_none) return name;									// shortcut not found; return name; EX: "x" in "File" returns "File"
    +		return String_.Mid(name, 0, pos) + "&" + String_.Mid(name, pos);			// add & before shortcut; EX: "x" in "Exit" -> "E&xit"
    +	}
    +	public Xog_mnu_itm Clone() {
    +		Xog_mnu_itm rv = new Xog_mnu_itm(gui_mgr, key).Init_by_clone(this);
    +		rv.Evt_mgr_(this.Evt_mgr());
    +		return rv;
    +	}
    +	@Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_text))			return name;
    +		else if	(ctx.Match(k, Invk_text_))			Name_(m.ReadStr("v"));
    +		else if	(ctx.Match(k, Invk_shortcut))		return shortcut;
    +		else if	(ctx.Match(k, Invk_shortcut_))		Shortcut_(m.ReadStr("v"));
    +		else if	(ctx.Match(k, Invk_cmd))			return this.Cmd();
    +		else if	(ctx.Match(k, Invk_cmd_))			Cmd_(m.ReadStr("v"));
    +		else if	(ctx.Match(k, Invk_img))			return String_.Concat_with_str("/", img_nest);
    +		else if	(ctx.Match(k, Invk_img_))			img_nest = Img_nest_of(m.ReadStr("v"));
    +		else	return super.Invk(ctx, ikey, k, m);
    +		return this;
    +	}
    +	private static final    String Invk_text = "text", Invk_text_ = "text_", Invk_shortcut = "shortcut", Invk_shortcut_ = "shortcut_", Invk_cmd = "cmd", Invk_cmd_ = "cmd_", Invk_img = "img", Invk_img_ = "img_"
    +	;
    +	public static final byte Tid_nil = 0, Tid_grp = 1, Tid_spr = 2, Tid_btn = 3, Tid_chk = 4, Tid_rdo = 5;
    +	private static String[] Img_nest_of(String img) {return String_.Len_eq_0(img) ? String_.Ary_empty : String_.Split(img, "/");}
    +	public static final    Xog_mnu_itm Null = new Xog_mnu_itm(null, "null");
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_regy.java b/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_regy.java
    index a27517de8..e26970f08 100644
    --- a/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_regy.java
    +++ b/400_xowa/src/gplx/xowa/guis/menus/dom/Xog_mnu_regy.java
    @@ -13,3 +13,87 @@ 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.guis.menus.dom; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.menus.*;
    +import gplx.xowa.guis.cmds.*;
    +public class Xog_mnu_regy {
    +	private Ordered_hash hash;
    +	private Xoa_gui_mgr gui_mgr;
    +	public Xog_mnu_regy(Xoa_gui_mgr gui_mgr) {
    +		this.gui_mgr = gui_mgr;
    +	}
    +	public void Init_by_app(Xoae_app app) {
    +		hash = Ordered_hash_.New();
    +		Xog_cmd_mgr cmd_mgr = app.Gui_mgr().Cmd_mgr();
    +		int len = cmd_mgr.Len();
    +		for (int i = 0; i < len; i++) {
    +			Xog_cmd_itm cmd_itm = cmd_mgr.Get_at(i);
    +			String key = cmd_itm.Key();
    +			Xog_mnu_itm rv = new Xog_mnu_itm(gui_mgr, key);
    +			hash.Add(key, rv);
    +		}
    +		Init_obsolete(hash, gui_mgr);
    +	}
    +	public Xog_mnu_itm Get_or_make(String key) {
    +		Xog_mnu_itm rv = (Xog_mnu_itm)hash.Get_by(key);
    +		if (rv == null) {
    +			rv = new Xog_mnu_itm(gui_mgr, key);
    +			hash.Add(key, rv);
    +		}
    +		else
    +			rv = rv.Clone();
    +		return rv;
    +	}
    +	private static void Init_obsolete(Ordered_hash hash, Xoa_gui_mgr gui_mgr) {
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.file"										, Xog_cmd_itm_.Key_gui_menus_group_file);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.tabs.new_dflt__at_dflt__focus_y"				, Xog_cmd_itm_.Key_gui_browser_tabs_new_dflt__at_dflt__focus_y);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.tabs.close_cur"								, Xog_cmd_itm_.Key_gui_browser_tabs_close_cur);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.file.save_as"								, Xog_cmd_itm_.Key_gui_page_view_save_as);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.file.print"									, Xog_cmd_itm_.Key_gui_page_view_print);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.file.exit"									, Xog_cmd_itm_.Key_app_exit);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.edit"										, Xog_cmd_itm_.Key_gui_menus_group_edit);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.edit.select_all"								, Xog_cmd_itm_.Key_gui_page_selection_select_all);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.edit.copy"									, Xog_cmd_itm_.Key_gui_page_selection_copy);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.edit.find"									, Xog_cmd_itm_.Key_gui_browser_find_show_by_paste);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.view"										, Xog_cmd_itm_.Key_gui_menus_group_view);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.view.font.increase"							, Xog_cmd_itm_.Key_gui_font_increase);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.view.font.decrease"							, Xog_cmd_itm_.Key_gui_font_decrease);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.view.page.read"								, Xog_cmd_itm_.Key_gui_page_view_mode_read);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.view.page.edit"								, Xog_cmd_itm_.Key_gui_page_view_mode_edit);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.view.page.html"								, Xog_cmd_itm_.Key_gui_page_view_mode_html);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.view.windows.progress.show"					, Xog_cmd_itm_.Key_gui_browser_prog_log_show);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.history"										, Xog_cmd_itm_.Key_gui_menus_group_history);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.history.go_bwd"								, Xog_cmd_itm_.Key_nav_go_bwd);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.history.go_fwd"								, Xog_cmd_itm_.Key_nav_go_fwd);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.history.show"								, Xog_cmd_itm_.Key_usr_history_show);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.bookmarks"									, Xog_cmd_itm_.Key_gui_menus_group_bookmarks);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.bookmarks.goto_main_page"					, Xog_cmd_itm_.Key_nav_wiki_main_page);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.bookmarks.add"								, Xog_cmd_itm_.Key_usr_bookmarks_add);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.bookmarks.show"								, Xog_cmd_itm_.Key_usr_bookmarks_show);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.tools"										, Xog_cmd_itm_.Key_gui_menus_group_tools);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.tools.options"								, Xog_cmd_itm_.Key_nav_cfg_main);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.tools.wikis.import_from_list"				, Xog_cmd_itm_.Key_nav_setup_import_from_list);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.tools.wikis.import_from_script"				, Xog_cmd_itm_.Key_nav_setup_import_from_script);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.tools.wikis.maintenance"						, Xog_cmd_itm_.Key_nav_setup_maintenance);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.tools.wikis.download"						, Xog_cmd_itm_.Key_nav_setup_download);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.help"										, Xog_cmd_itm_.Key_gui_menus_group_help);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.help.help"									, Xog_cmd_itm_.Key_nav_help_help);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.help.change_log"								, Xog_cmd_itm_.Key_nav_help_change_log);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.help.diagnostics"							, Xog_cmd_itm_.Key_nav_help_diagnostics);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.help.context_menu"							, Xog_cmd_itm_.Key_nav_cfg_menu);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.system.data"									, Xog_cmd_itm_.Key_gui_menus_group_system_data);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.system.data.log_session"						, Xog_cmd_itm_.Key_nav_system_data_log_session);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.system.data.cfg_app"							, Xog_cmd_itm_.Key_nav_system_data_cfg_app);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.system.data.cfg_lang"						, Xog_cmd_itm_.Key_nav_system_data_cfg_lang);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.system.data.usr_history"						, Xog_cmd_itm_.Key_nav_system_data_usr_history);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.help.about"									, Xog_cmd_itm_.Key_nav_help_about);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.tabs.new_link__at_dflt__focus_n"				, Xog_cmd_itm_.Key_gui_browser_tabs_new_link__at_dflt__focus_n);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.tabs.new_link__at_dflt__focus_y"				, Xog_cmd_itm_.Key_gui_browser_tabs_new_link__at_dflt__focus_y);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.widgets.find.show_by_paste"					, Xog_cmd_itm_.Key_gui_browser_find_show_by_paste);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.file.save_file_as"							, Xog_cmd_itm_.Key_gui_page_selection_save_file_as);
    +		Init_obsolete_itm(hash, gui_mgr, "xowa.widgets.prog_log.show"						, Xog_cmd_itm_.Key_gui_browser_prog_log_show);
    +	}
    +	private static void Init_obsolete_itm(Ordered_hash hash, Xoa_gui_mgr gui_mgr, String old, String cur) {
    +		Xog_mnu_itm rv = new Xog_mnu_itm(gui_mgr, cur);
    +		hash.Add(old, rv);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/tabs/Xog_tab_mgr.java b/400_xowa/src/gplx/xowa/guis/tabs/Xog_tab_mgr.java
    index a27517de8..25a5d4831 100644
    --- a/400_xowa/src/gplx/xowa/guis/tabs/Xog_tab_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/tabs/Xog_tab_mgr.java
    @@ -13,3 +13,7 @@ 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.guis.tabs; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public interface Xog_tab_mgr extends Gfo_invk {
    +	void New_tab(boolean focus, String site, String page);
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/tabs/Xog_tab_mgr_.java b/400_xowa/src/gplx/xowa/guis/tabs/Xog_tab_mgr_.java
    index a27517de8..53d44cad6 100644
    --- a/400_xowa/src/gplx/xowa/guis/tabs/Xog_tab_mgr_.java
    +++ b/400_xowa/src/gplx/xowa/guis/tabs/Xog_tab_mgr_.java
    @@ -13,3 +13,12 @@ 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.guis.tabs; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Xog_tab_mgr_ {
    +	public static Xog_tab_mgr New_mem() {return new Xog_tab_mgr__mem();}
    +	public static final String Invk__new_tab = "new_tab";
    +}
    +class Xog_tab_mgr__mem implements Xog_tab_mgr {
    +	public void New_tab(boolean focus, String site, String page) {}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return this;}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/tabs/Xog_tab_mgr__swt.java b/400_xowa/src/gplx/xowa/guis/tabs/Xog_tab_mgr__swt.java
    index a27517de8..ad84b8cad 100644
    --- a/400_xowa/src/gplx/xowa/guis/tabs/Xog_tab_mgr__swt.java
    +++ b/400_xowa/src/gplx/xowa/guis/tabs/Xog_tab_mgr__swt.java
    @@ -13,3 +13,18 @@ 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.guis.tabs; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Xog_tab_mgr__swt implements Xog_tab_mgr {
    +	private final    Xoa_gui_mgr gui_mgr;
    +	public Xog_tab_mgr__swt(Xoa_gui_mgr gui_mgr) {this.gui_mgr = gui_mgr;}
    +	public void New_tab(boolean focus, String site, String page) {			
    +		// gui_mgr.Browser_win().Tab_mgr().Tabs_new_link(url, focus);	// TODO_OLD: handle html dumps
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Xog_tab_mgr_.Invk__new_tab))		gui_mgr.Kit().New_cmd_sync(this).Invk(ctx, ikey, Invk__new_tab_async, m);
    +		else if	(ctx.Match(k, Invk__new_tab_async))				this.New_tab(m.ReadYn("focus"), m.ReadStr("site"), m.ReadStr("page"));
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	private static final String Invk__new_tab_async = "new_tab_async";
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/urls/Xog_url_wkr.java b/400_xowa/src/gplx/xowa/guis/urls/Xog_url_wkr.java
    index a27517de8..0ba06891f 100644
    --- a/400_xowa/src/gplx/xowa/guis/urls/Xog_url_wkr.java
    +++ b/400_xowa/src/gplx/xowa/guis/urls/Xog_url_wkr.java
    @@ -13,3 +13,67 @@ 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.guis.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.core.net.*; import gplx.core.net.qargs.*; import gplx.core.envs.*;
    +import gplx.gfui.controls.standards.*;
    +import gplx.xowa.files.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.origs.*;
    +import gplx.langs.htmls.encoders.*; import gplx.xowa.htmls.hrefs.*; import gplx.xowa.htmls.doms.*;
    +import gplx.xowa.guis.views.*;
    +public class Xog_url_wkr {
    +	private final    Xoa_url tmp_url = Xoa_url.blank();
    +	private Xoae_app app; private Xog_win_itm win; private Xowe_wiki wiki; private Xoae_page page;
    +	public Xoa_url Parse(Xog_win_itm win, String href_str) {
    +		if (href_str == null) return tmp_url;	// text is not link; return;
    +		byte[] href_bry = Bry_.new_u8(href_str);
    +		this.win = win; this.app = win.App(); 
    +		this.page = win.Active_page();
    +		this.wiki = win.Active_tab().Wiki();
    +		app.Html__href_parser().Parse_as_url(tmp_url, href_bry, wiki, page.Ttl().Page_url());
    +		return tmp_url;
    +	}
    +	public void Init(Xowe_wiki wiki) {	// TEST:
    +		this.wiki = wiki;
    +	}
    +	public Xoa_url Exec_url(Xoa_url url) {
    +		switch (url.Tid()) {
    +			case Xoa_url_.Tid_unknown:		return Xoa_url.Null;										// unknown; return null which will become a noop
    +			case Xoa_url_.Tid_inet:			return Exec_url_http(app);									// http://site.org
    +			case Xoa_url_.Tid_anch:			return Exec_url_anchor(win);								// #anchor
    +			case Xoa_url_.Tid_xcmd:			return Exec_url_xowa(app);									// xowa:app.version or /xcmd/app.version
    +			case Xoa_url_.Tid_file:			return Exec_url_file(app, wiki, page, win, url.Raw());		// file:///xowa/A.png
    +			case Xoa_url_.Tid_page:			return Exec_url_page(wiki, url.Orig());						// /wiki/Page
    +			default:						throw Err_.new_unhandled(url.Tid());
    +		}
    +	}
    +	private Xoa_url Exec_url_xowa(Xoae_app app) {		// EX: xowa:app.version
    +		// NOTE: must catch exception else it will bubble to SWT browser and raise secondary exception of xowa is not a registered protocol
    +		try {app.Gfs_mgr().Run_str(String_.new_u8(tmp_url.Page_bry()));}
    +		catch (Exception e) {app.Gui_mgr().Kit().Ask_ok("", "", Err_.Message_gplx_full(e));}
    +		return Rslt_handled;
    +	}
    +	private Xoa_url Exec_url_http(Xoae_app app) {		// EX: http://a.org
    +		app.Prog_mgr().Exec_view_web(tmp_url.Raw());
    +		return Rslt_handled;
    +	}
    +	private Xoa_url Exec_url_anchor(Xog_win_itm win) {	// EX: #anchor
    +		win.Active_html_itm().Scroll_page_by_id_gui(tmp_url.Anch_str());	// NOTE: was originally directly; changed to call on thread; DATE:2014-05-03
    +		return Rslt_handled;
    +	}
    +	private Xoa_url Exec_url_file(Xoae_app app, Xowe_wiki cur_wiki, Xoae_page page, Xog_win_itm win, byte[] href_bry) {	// EX: file:///xowa/A.png
    +		Xog_url_wkr__file wkr = new Xog_url_wkr__file(app, cur_wiki, page);
    +		wkr.Extract_data(win.Active_html_box().Html_js_eval_proc_as_str(Xog_js_procs.Doc__root_html_get), href_bry);
    +		wkr.Download_and_run();
    +		return Rslt_handled;
    +	}
    +	private Xoa_url Exec_url_page(Xowe_wiki wiki, byte[] href_bry) {	// EX: "Page"; "/wiki/Page"; // rewritten; DATE:2014-01-19
    +		return wiki.Utl__url_parser().Parse(href_bry);
    +	}
    +	public static Xoa_url Rslt_handled = null;
    +	public static Xoa_url Exec_url(Xog_win_itm win, String href_str) {
    +		Xog_url_wkr url_wkr = new Xog_url_wkr();
    +		Xoa_url url = url_wkr.Parse(win, href_str);
    +		return url_wkr.Exec_url(url);
    +	}
    +	public static void Get_href_url() {
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/urls/Xog_url_wkr__file.java b/400_xowa/src/gplx/xowa/guis/urls/Xog_url_wkr__file.java
    index a27517de8..6f6c991e2 100644
    --- a/400_xowa/src/gplx/xowa/guis/urls/Xog_url_wkr__file.java
    +++ b/400_xowa/src/gplx/xowa/guis/urls/Xog_url_wkr__file.java
    @@ -13,3 +13,70 @@ 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.guis.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.xowa.files.*; import gplx.xowa.files.origs.*; import gplx.xowa.files.repos.*;
    +import gplx.xowa.htmls.doms.*;
    +import gplx.xowa.parsers.lnkis.*;
    +class Xog_url_wkr__file {
    +	private final    Xoae_app app;
    +	private final    Xowe_wiki page_wiki;
    +	private final    Xoae_page page;
    +	private final    Xof_img_size img_size = new Xof_img_size();
    +	private final    Xof_url_bldr url_bldr = Xof_url_bldr.new_v2();
    +	public Xog_url_wkr__file(Xoae_app app, Xowe_wiki page_wiki, Xoae_page page) {
    +		this.app = app;
    +		this.page_wiki = page_wiki;
    +		this.page = page;
    +	}
    +	public byte[] File_page_db() {return file_page_db;} private byte[] file_page_db;
    +	public Io_url File_url() {return file_url;} private Io_url file_url;
    +	public Xof_fsdb_itm Fsdb() {return fsdb;} private Xof_fsdb_itm fsdb;
    +	public Xowe_wiki File_wiki() {return file_wiki;} private Xowe_wiki file_wiki;
    +	public void Extract_data(String html_doc, byte[] href_bry) {	// EX: file:///xowa/A.png
    +		// find xowa_ttl from html_doc for given file; EX: file:///xowa/A.png -> xowa_ttl = "A.png"
    +		String xowa_ttl = Xoh_dom_.Title_by_href(href_bry, Bry_.new_u8(html_doc));
    +		if (xowa_ttl == null) {
    +			app.Gui_mgr().Kit().Ask_ok("", "", "could not find anchor with href in html: href=~{0}", href_bry);
    +			return;
    +		}
    +
    +		// get fsdb itm
    +		this.file_page_db = gplx.langs.htmls.encoders.Gfo_url_encoder_.Http_url.Decode(Xoa_ttl.Replace_spaces(Bry_.new_u8(xowa_ttl)));
    +		this.file_wiki = (Xowe_wiki)page.Commons_mgr().Source_wiki_or(page_wiki);
    +		this.fsdb = Xog_url_wkr__file.Make_fsdb(file_wiki, file_page_db, img_size, url_bldr);
    +
    +		this.file_url = Io_url_.New__http_or_fail(String_.new_u8(gplx.langs.htmls.encoders.Gfo_url_encoder_.Http_url.Decode(href_bry)));
    +	}
    +	public void Download_and_run() {
    +		// download file if it doesn't exist
    +		if (!Io_mgr.Instance.ExistsFil(file_url)) {
    +			if (!Xof_file_wkr.Show_img(fsdb, Xoa_app_.Usr_dlg(), file_wiki.File__bin_mgr(), file_wiki.File__mnt_mgr(), file_wiki.App().User().User_db_mgr().Cache_mgr(), file_wiki.File__repo_mgr(), gplx.xowa.guis.cbks.js.Xog_js_wkr_.Noop, img_size, url_bldr, page))
    +				return;
    +		}
    +
    +		// try to launch file
    +		gplx.core.ios.IoItmFil fil = Io_mgr.Instance.QueryFil(file_url);
    +		if (fil.Exists()) {
    +			gplx.core.envs.Process_adp media_player = app.Prog_mgr().App_by_ext(file_url.Ext());
    +			media_player.Args__include_quotes_(gplx.core.envs.Op_sys.Cur().Tid_is_wnt());	// NOTE:Windows "cmd /c start" requies first quoted argument to be title; note that url must be 2nd arg and quoted; DATE:2016-11-14
    +			media_player.Run(file_url);
    +
    +			fsdb.File_size_(fil.Size());
    +			gplx.xowa.files.caches.Xou_cache_mgr cache_mgr = file_wiki.Appe().User().User_db_mgr().Cache_mgr();
    +			cache_mgr.Update(fsdb);
    +			cache_mgr.Db_save();
    +		}
    +	}
    +	public static Xof_fsdb_itm Make_fsdb(Xowe_wiki wiki, byte[] lnki_ttl, Xof_img_size img_size, Xof_url_bldr url_bldr) {
    +		Xof_fsdb_itm fsdb = new Xof_fsdb_itm();
    +		lnki_ttl = Xoa_ttl.Replace_spaces(gplx.langs.htmls.encoders.Gfo_url_encoder_.Http_url.Decode(lnki_ttl));
    +		fsdb.Init_at_lnki(Xof_exec_tid.Tid_viewer_app, wiki.Domain_itm().Abrv_xo(), lnki_ttl, Xop_lnki_type.Id_none, Xop_lnki_tkn.Upright_null, Xof_img_size.Size__neg1, Xof_img_size.Size__neg1, Xof_lnki_time.Null, Xof_lnki_page.Null, Xof_patch_upright_tid_.Tid_all);
    +		fsdb.Init_at_hdoc(Int_.Max_value, Xof_html_elem.Tid_img);// NOTE: set elem_id to "impossible" number, otherwise it will auto-update an image on the page with a super-large size; [[File:Alfred Sisley 062.jpg]]
    +		Xof_orig_itm orig = wiki.File__orig_mgr().Find_by_ttl_or_null(lnki_ttl); if (orig == Xof_orig_itm.Null) return null;	// orig not found; need orig in order to get repo
    +		Xof_repo_itm repo = wiki.File__repo_mgr().Get_trg_by_id_or_null(orig.Repo(), lnki_ttl, Bry_.Empty); if (repo == null) return null; // repo not found
    +		fsdb.Init_at_orig(orig.Repo(), repo.Wiki_domain(), orig.Ttl(), orig.Ext(), orig.W(), orig.H(), orig.Redirect());
    +		fsdb.Init_at_html(Xof_exec_tid.Tid_viewer_app, img_size, repo, url_bldr);
    +		fsdb.File_is_orig_(true);
    +		return fsdb;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/urls/Xog_url_wkr__file__tst.java b/400_xowa/src/gplx/xowa/guis/urls/Xog_url_wkr__file__tst.java
    index a27517de8..c94734273 100644
    --- a/400_xowa/src/gplx/xowa/guis/urls/Xog_url_wkr__file__tst.java
    +++ b/400_xowa/src/gplx/xowa/guis/urls/Xog_url_wkr__file__tst.java
    @@ -13,3 +13,54 @@ 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.guis.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import org.junit.*; import gplx.core.tests.*;
    +import gplx.xowa.files.origs.*;
    +public class Xog_url_wkr__file__tst {
    +	private final    Xog_url_wkr__file__fxt fxt = new Xog_url_wkr__file__fxt();
    +	@Test   public void Basic() {
    +		fxt.Test__extract("A.png", "file:///mem/xowa/file/commons.wikimedia.org/orig/7/0/A.png", 300, 200);
    +	}
    +	@Test   public void Url_encoded() {
    +		fxt.Test__extract("A,b.png", "file:///mem/xowa/file/commons.wikimedia.org/orig/d/6/A%2Cb.png", 300, 200);
    +	}
    +	@Test   public void Utf8() {
    +		fxt.Test__extract("Volcán_Chimborazo,_\"El_Taita_Chimborazo\".jpg", "file:///mem/xowa/file/commons.wikimedia.org/orig/3/c/Volc%C3%A1n_Chimborazo%2C_%22El_Taita_Chimborazo%22.jpg", 300, 200);
    +	}
    +}
    +class Xog_url_wkr__file__fxt {
    +	private final    Xowe_wiki wiki;
    +	private final    Bry_bfr bfr = Bry_bfr_.New();
    +	private final    Xog_url_wkr__file wkr;
    +	private final    Xof_orig_wkr__mock orig_wkr = new Xof_orig_wkr__mock();
    +	public Xog_url_wkr__file__fxt() {
    +		Xoae_app app = Xoa_app_fxt.Make__app__edit();
    +		wiki = Xoa_app_fxt.Make__wiki__edit(app);
    +		Xoa_app_fxt.repo2_(app, wiki);
    +		wiki.File__orig_mgr().Wkrs__add(orig_wkr);
    +		this.wkr = new Xog_url_wkr__file(app, wiki, Xoae_page.New_edit(wiki, wiki.Ttl_parse(Bry_.new_a7("Test_page"))));
    +	}
    +	public String Make__html(byte[] href_bry, byte[] xowa_ttl) {
    +		bfr.Add_str_u8_fmt("Full resolution", href_bry, xowa_ttl);
    +		return bfr.To_str_and_clear();
    +	}
    +	public void Make__orig(byte repo, byte[] page, int w, int h, byte[] redirect) {
    +		orig_wkr.Add_orig(repo, page, gplx.xowa.files.Xof_ext_.new_by_ttl_(page).Id(), w, h, redirect);
    +	}
    +	public void Exec__extract_data(byte[] href_bry, String html_doc) {
    +		wkr.Extract_data(html_doc, href_bry);
    +	}
    +	public void Test__extract(String file_page_db, String a_href_str, int orig_w, int orig_h) {
    +		byte[] a_href = Bry_.new_u8(a_href_str);
    +		this.Make__orig(Xof_orig_itm.Repo_wiki, Bry_.new_u8(file_page_db), orig_w, orig_h, Bry_.Empty);
    +		
    +		byte[] xowa_title_bry = gplx.xowa.htmls.core.wkrs.lnkis.htmls.Xoh_file_fmtr__basic.Escape_xowa_title(Bry_.new_u8(file_page_db));
    +		this.Exec__extract_data(a_href, this.Make__html(a_href, xowa_title_bry));
    +
    +		Gftest.Eq__str(file_page_db	, wkr.File_page_db()							, "file_page_db");
    +		Gftest.Eq__bry(a_href		, wkr.File_url().To_http_file_bry()				, "file_url");
    +		Gftest.Eq__bry(a_href		, wkr.Fsdb().Html_view_url().To_http_file_bry()	, "fsdb.view_url");
    +		Gftest.Eq__int(orig_w		, wkr.Fsdb().Orig_w()							, "fsdb.orig_w");
    +		Gftest.Eq__int(orig_h		, wkr.Fsdb().Orig_h()							, "fsdb.orig_h");
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/urls/Xog_url_wkr__tst.java b/400_xowa/src/gplx/xowa/guis/urls/Xog_url_wkr__tst.java
    index a27517de8..01ef381a8 100644
    --- a/400_xowa/src/gplx/xowa/guis/urls/Xog_url_wkr__tst.java
    +++ b/400_xowa/src/gplx/xowa/guis/urls/Xog_url_wkr__tst.java
    @@ -13,3 +13,34 @@ 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.guis.urls; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import org.junit.*; import gplx.core.tests.*;
    +public class Xog_url_wkr__tst {
    +	private final    Xog_url_wkr__fxt fxt = new Xog_url_wkr__fxt();
    +	@Test   public void Basic() {
    +		fxt.Exec__parse("/wiki/A?k1=B%26C");
    +		fxt.Test__raw("/wiki/A?k1=B%26C");
    +	}
    +	@Test   public void Anch_early() {	// de.wikipedia.org/wiki/Kategorie:Begriffskl%C3%A4rung?pagefrom=#::12%20PANZERDIVISION#mw-pages
    +		fxt.Exec__parse("/wiki/A?pagefrom=%23%3A7p#mw-pages");
    +		fxt.Test__qarg("?pagefrom=#:7p");
    +		fxt.Test__anch("mw-pages");
    +	}		
    +}
    +class Xog_url_wkr__fxt {
    +	private final    Xowe_wiki wiki;
    +	private final    Xog_url_wkr wkr = new Xog_url_wkr();
    +	private Xoa_url url;
    +	public Xog_url_wkr__fxt() {
    +		Xoae_app app = Xoa_app_fxt.Make__app__edit();
    +		this.wiki = Xoa_app_fxt.Make__wiki__edit(app);
    +		wkr.Init(wiki);
    +	}
    +	public void Exec__parse(String href) {
    +		this.url = wiki.Utl__url_parser().Parse(Bry_.new_u8(href));
    +		this.url = wkr.Exec_url(url);
    +	}
    +	public void Test__raw(String expd)	{Gftest.Eq__str(expd, String_.new_u8(url.Raw()));}
    +	public void Test__qarg(String expd) {Gftest.Eq__str(expd, String_.new_u8(url.Qargs_mgr().To_bry()));}
    +	public void Test__anch(String expd) {Gftest.Eq__str(expd, String_.new_u8(url.Anch_bry()));}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/urls/url_macros/Xog_url_macro_grp.java b/400_xowa/src/gplx/xowa/guis/urls/url_macros/Xog_url_macro_grp.java
    index a27517de8..187cc3da4 100644
    --- a/400_xowa/src/gplx/xowa/guis/urls/url_macros/Xog_url_macro_grp.java
    +++ b/400_xowa/src/gplx/xowa/guis/urls/url_macros/Xog_url_macro_grp.java
    @@ -13,3 +13,30 @@ 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.guis.urls.url_macros; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.urls.*;
    +import gplx.core.btries.*; import gplx.core.brys.fmtrs.*;
    +public class Xog_url_macro_grp implements Gfo_invk {
    +	public Btrie_slim_mgr Trie() {return trie;} private Btrie_slim_mgr trie = Btrie_slim_mgr.cs();
    +	public void Del(byte[] abrv) {trie.Del(abrv);}
    +	public void Set(String abrv, String fmt) {Set(Bry_.new_u8(abrv), Bry_.new_u8(fmt));}
    +	public void Set(byte[] abrv, byte[] fmt) {trie.Add_obj(abrv, new Xog_url_macro_itm(abrv, fmt));}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_clear))						trie.Clear();
    +		else if	(ctx.Match(k, Invk_set))						Set(m.ReadBry("abrv"), m.ReadBry("fmt"));
    +		else if	(ctx.Match(k, Invk_del))						Del(m.ReadBry("abrv"));
    +		else return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	private static final String Invk_clear = "clear", Invk_set = "set", Invk_del = "del";
    +}
    +class Xog_url_macro_itm {
    +	private Bry_fmtr fmtr;
    +	public Xog_url_macro_itm(byte[] abrv, byte[] fmt) {this.abrv = abrv; this.fmt = fmt;}
    +	public byte[] Abrv() {return abrv;} private byte[] abrv;
    +	public byte[] Fmt() {return fmt;} private byte[] fmt;
    +	public byte[] Fmtr_exec(Bry_bfr bfr, Object... args) {
    +		if (fmtr == null) fmtr = new Bry_fmtr().Fmt_(fmt).Compile();
    +		fmtr.Bld_bfr_many(bfr, args);
    +		return bfr.To_bry_and_clear();
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/urls/url_macros/Xog_url_macro_mgr.java b/400_xowa/src/gplx/xowa/guis/urls/url_macros/Xog_url_macro_mgr.java
    index a27517de8..53d3cfc64 100644
    --- a/400_xowa/src/gplx/xowa/guis/urls/url_macros/Xog_url_macro_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/urls/url_macros/Xog_url_macro_mgr.java
    @@ -13,3 +13,78 @@ 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.guis.urls.url_macros; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.urls.*;
    +public class Xog_url_macro_mgr {
    +	private Bry_bfr bfr = Bry_bfr_.Reset(255);
    +	public Xog_url_macro_mgr() {
    +		this.Init();
    +	}
    +	public void Lang_default_(byte[] v) {lang_default = v;} private byte[] lang_default = Bry_.new_a7("en");
    +	public Xog_url_macro_grp Types_mgr() {return types_mgr;} private Xog_url_macro_grp types_mgr = new Xog_url_macro_grp();
    +	public Xog_url_macro_grp Custom_mgr() {return custom_mgr;} private Xog_url_macro_grp custom_mgr = new Xog_url_macro_grp();
    +	public byte[] Fmt_or_null(byte[] raw) {
    +		int raw_len = raw.length;
    +		int dot_pos = -1;
    +		for (int i = 0; i < raw_len; i++) {
    +			byte b = raw[i];
    +			switch (b) {
    +				case Byte_ascii.Dot:
    +					dot_pos = i;
    +					break;
    +				case Byte_ascii.Colon:
    +					return Fmt_or_null__type(raw, raw_len, dot_pos, i);
    +			}
    +		}
    +		return Unhandled;
    +	}
    +	private byte[] Fmt_or_null__type(byte[] raw, int raw_len, int dot_pos, int colon_pos) {
    +		boolean dot_missing = dot_pos == -1;
    +		int type_bgn = dot_pos + 1, type_end = colon_pos;	// +1 to start type after dot;
    +		if (dot_missing) type_bgn = 0;
    +		Object custom_obj = custom_mgr.Trie().Match_exact(raw, 0, type_end);	// match entire prefix
    +		if (custom_obj == null) {
    +			Object type_obj = types_mgr.Trie().Match_exact(raw, type_bgn, type_end);
    +			if (type_obj == null) return Unhandled;	// type abrv is not known; exit; EX: "en.unknown:Page"; "Page"
    +			byte[] lang_bry = dot_missing ? lang_default : Bry_.Mid(raw, 0, dot_pos);
    +			Xog_url_macro_itm type_itm = (Xog_url_macro_itm)type_obj;
    +			return type_itm.Fmtr_exec(bfr, lang_bry, Bry_.Mid(raw, colon_pos + 1, raw_len));
    +		}
    +		else {
    +			Xog_url_macro_itm custom_itm = (Xog_url_macro_itm)custom_obj;
    +			return custom_itm.Fmtr_exec(bfr, Bry_.Mid(raw, colon_pos + 1, raw_len));
    +		}
    +	}
    +	private void Init() {
    +		types_mgr.Set("w"		, "~{0}.wikipedia.org/wiki/~{1}");
    +		types_mgr.Set("d"		, "~{0}.wiktionary.org/wiki/~{1}");
    +		types_mgr.Set("s"		, "~{0}.wikisource.org/wiki/~{1}");
    +		types_mgr.Set("v"		, "~{0}.wikivoyage.org/wiki/~{1}");
    +		types_mgr.Set("q"		, "~{0}.wikiquote.org/wiki/~{1}");
    +		types_mgr.Set("b"		, "~{0}.wikibooks.org/wiki/~{1}");
    +		types_mgr.Set("u"		, "~{0}.wikiversity.org/wiki/~{1}");
    +		types_mgr.Set("n"		, "~{0}.wikinews.org/wiki/~{1}");
    +		types_mgr.Set("a"		, "~{0}.wikia.com/wiki/~{1}");
    +		types_mgr.Set("m"		, "~{0}.wikimedia.com/wiki/~{1}");
    +		custom_mgr.Set("c"		, "commons.wikimedia.org/wiki/~{0}");
    +		custom_mgr.Set("wd"		, "www.wikidata.org/wiki/~{0}");
    +		custom_mgr.Set("wd.q"	, "www.wikidata.org/wiki/Q~{0}");
    +		custom_mgr.Set("wd.p"	, "www.wikidata.org/wiki/Property:P~{0}");
    +		custom_mgr.Set("sp"		, "wikispecies.wikimedia.org/wiki/~{0}");
    +		custom_mgr.Set("meta"	, "meta.wikimedia.org/wiki/~{0}");
    +		custom_mgr.Set("s.w"	, "simple.wikipedia.org/wiki/~{0}");
    +		custom_mgr.Set("s.d"	, "simple.wiktionary.org/wiki/~{0}");
    +		custom_mgr.Set("s.b"	, "simple.wikibooks.org/wiki/~{0}");
    +		custom_mgr.Set("s.q"	, "simple.wikiquote.org/wiki/~{0}");
    +		custom_mgr.Set("?"		, "Special:Search/~{0}?fulltext=y");
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_lang_default))					return String_.new_u8(lang_default);
    +		else if	(ctx.Match(k, Invk_lang_default_))					lang_default = m.ReadBry("v");
    +		else if	(ctx.Match(k, Invk_types))							return types_mgr;
    +		else if	(ctx.Match(k, Invk_custom))							return custom_mgr;
    +		else return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	private static final String Invk_lang_default = "lang_default", Invk_lang_default_ = "lang_default_", Invk_types = "types", Invk_custom = "custom";
    +	public static final    byte[] Unhandled = null;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/urls/url_macros/Xog_url_macro_mgr_tst.java b/400_xowa/src/gplx/xowa/guis/urls/url_macros/Xog_url_macro_mgr_tst.java
    index a27517de8..10926040c 100644
    --- a/400_xowa/src/gplx/xowa/guis/urls/url_macros/Xog_url_macro_mgr_tst.java
    +++ b/400_xowa/src/gplx/xowa/guis/urls/url_macros/Xog_url_macro_mgr_tst.java
    @@ -13,3 +13,42 @@ 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.guis.urls.url_macros; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.urls.*;
    +import org.junit.*;
    +public class Xog_url_macro_mgr_tst {
    +	@Before public void init() {fxt.Clear();} private Xog_url_macro_mgr_fxt fxt = new Xog_url_macro_mgr_fxt();
    +	@Test  public void Custom_basic()		{fxt.Test("?:Page"				, "Special:Search/Page?fulltext=y");}
    +	@Test  public void Type_basic()			{fxt.Test("en.w:Page"			, "en.wikipedia.org/wiki/Page");}
    +	@Test  public void Type_main()			{fxt.Test("en.w:"				, "en.wikipedia.org/wiki/");}
    +	@Test  public void Type_tid()			{fxt.Test("w:Page"				, "en.wikipedia.org/wiki/Page");}
    +	@Test  public void Type_lang()			{fxt.Test("fr.w:Page"			, "fr.wikipedia.org/wiki/Page");}
    +	@Test  public void Type_unhandled()		{fxt.Test("x:A"					, null);}
    +	@Test  public void Type_unhandled_ns()	{fxt.Test("Help:A"				, null);}
    +	@Test  public void Type_custom()		{fxt.Test("wd.q:123"			, "www.wikidata.org/wiki/Q123");}
    +	@Test  public void Type_del() {
    +		fxt.Test("w:A", "en.wikipedia.org/wiki/A");
    +		fxt.Abrv_mgr().Types_mgr().Del(Bry_.new_a7("w"));
    +		fxt.Test("w:A", null);
    +	}
    +	@Test  public void Type_set() {
    +		fxt.Abrv_mgr().Types_mgr().Set("w", "~{0}.~{1}");
    +		fxt.Test("w.A", null);
    +	}
    +	@Test  public void Lang_default() {
    +		fxt.Abrv_mgr().Lang_default_(Bry_.new_a7("fr"));
    +		fxt.Test("w:Page", "fr.wikipedia.org/wiki/Page");
    +	}
    +	@Test  public void Precedence()	{	// PURPOSE: Custom should take precedence over type
    +		fxt.Abrv_mgr().Custom_mgr().Set("w", "custom:~{0}");
    +		fxt.Test("w:Page"				, "custom:Page");
    +	}
    +}
    +class Xog_url_macro_mgr_fxt {
    +	public void Clear() {
    +		abrv_mgr = new Xog_url_macro_mgr();
    +	}
    +	public Xog_url_macro_mgr Abrv_mgr() {return abrv_mgr;} private Xog_url_macro_mgr abrv_mgr;
    +	public void Test(String raw, String expd) {
    +		Tfds.Eq(expd, String_.new_a7(abrv_mgr.Fmt_or_null(Bry_.new_a7(raw))));
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Gfo_usr_dlg__gui__swt.java b/400_xowa/src/gplx/xowa/guis/views/Gfo_usr_dlg__gui__swt.java
    index a27517de8..221d6bbb8 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Gfo_usr_dlg__gui__swt.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Gfo_usr_dlg__gui__swt.java
    @@ -13,3 +13,42 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.core.lists.rings.*;
    +import gplx.gfui.*; import gplx.gfui.draws.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.standards.*;
    +public class Gfo_usr_dlg__gui__swt implements Gfo_usr_dlg__gui, Gfo_invk {
    +	private final    GfuiInvkCmd cmd_sync; private final    GfuiTextBox prog_box, info_box;
    +	private boolean show_warn, show_note;
    +	public Gfo_usr_dlg__gui__swt(Xoa_app app, Gfui_kit kit, GfuiTextBox prog_box, GfuiTextBox info_box, GfuiTextBox warn_box) {
    +		this.cmd_sync	= kit.New_cmd_sync(this);	// NOTE: cmd_sync needed else progress messages may be sent out of order
    +		this.prog_box = prog_box; this.info_box = info_box;
    +		app.Cfg().Bind_many_app(this, Cfg__show_warn, Cfg__show_note);
    +	}
    +	public void Clear() {Write(Invk_write_prog, ""); info_box.Text_(""); info_box.ForeColor_(ColorAdp_.Black); info_box.BackColor_(ColorAdp_.White); info_box.Redraw(); info_box_is_warn = false;}
    +	public Ring__string Prog_msgs() {return prog_msgs;} Ring__string prog_msgs = new Ring__string().Max_(128);
    +	public void Write_prog(String text) {Write(Invk_write_prog, text);}
    +	public void Write_note(String text) {if (show_note) Write(Invk_write_note, text);}
    +	public void Write_warn(String text) {if (show_warn) Write(Invk_write_warn, text);}
    +	public void Write_stop(String text) {Write(Invk_write_stop, text);}
    +	private void Write(String invk, String text) {
    +		GfoMsg m = GfoMsg_.new_cast_(invk).Add("v", text);
    +		Gfo_invk_.Invk_by_msg(cmd_sync, invk, m);
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_write_prog))			{String v = m.ReadStr("v"); prog_box.Text_(v); prog_box.Redraw(); if (!String_.Eq(v, "")) prog_msgs.Push(v);}
    +		else if	(ctx.Match(k, Invk_write_note))			{Info_box_write(m.ReadStr("v"), false); info_box.Redraw();}
    +		else if	(ctx.Match(k, Invk_write_warn))			{Info_box_write(m.ReadStr("v"), true); info_box.ForeColor_(ColorAdp_.White); info_box.BackColor_(ColorAdp_.Red); info_box.Redraw();}
    +		else if	(ctx.Match(k, Cfg__show_warn))			show_warn = m.ReadYn("v");
    +		else if	(ctx.Match(k, Cfg__show_note))			show_note = m.ReadYn("v");
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	private void Info_box_write(String v, boolean warn) {
    +		if (info_box_is_warn) return;
    +		info_box.Text_(v);
    +		info_box_is_warn = warn;
    +	}	boolean info_box_is_warn;
    +
    +	private static final String Invk_write_prog = "write_prog", Invk_write_note = "write_note", Invk_write_warn = "write_warn", Invk_write_stop = "write_stop";
    +	private static final String Cfg__show_warn = "xowa.app.debug.show_warn", Cfg__show_note = "xowa.app.debug.show_note";
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Gfo_usr_dlg_fmt.java b/400_xowa/src/gplx/xowa/guis/views/Gfo_usr_dlg_fmt.java
    index a27517de8..35985edd3 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Gfo_usr_dlg_fmt.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Gfo_usr_dlg_fmt.java
    @@ -13,3 +13,20 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Gfo_usr_dlg_fmt {
    +	public boolean Write_prog_cur(int cur, Gfo_usr_dlg usr_dlg) {
    +		if (cur < prog_prv + prog_interval) return usr_dlg.Canceled();
    +		prog_prv = cur;
    +		String pct = Decimal_adp_.CalcPctStr(cur + List_adp_.Base1, end, "00.00");
    +		usr_dlg.Prog_many(grp_key, msg_key, fmt, Int_.To_str_pad_bgn_zero(cur + List_adp_.Base1, endLen), end, pct);
    +		return usr_dlg.Canceled();
    +	}	String fmt; int end, endLen;
    +	public static Gfo_usr_dlg_fmt fmt_(String grp_key, String msg_key, String fmt, int end, float pct) {
    +		Gfo_usr_dlg_fmt rv = new Gfo_usr_dlg_fmt();
    +		rv.grp_key = grp_key; rv.msg_key = msg_key;
    +		rv.fmt = fmt; rv.end = end; rv.endLen = Int_.DigitCount(end); rv.prog_interval = (int)((float)end * (float)(pct / (float)100));;
    +		return rv;
    +	}	String grp_key, msg_key;
    +	int prog_interval; int prog_prv = Int_.Min_value;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Load_page_wkr.java b/400_xowa/src/gplx/xowa/guis/views/Load_page_wkr.java
    index a27517de8..69222a8af 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Load_page_wkr.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Load_page_wkr.java
    @@ -13,3 +13,47 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.core.threads.*; import gplx.core.envs.*;
    +public class Load_page_wkr implements Gfo_thread_wkr {
    +	private final    Xog_tab_itm tab;
    +	public Load_page_wkr(Xog_tab_itm tab, Xowe_wiki wiki, Xoa_url url, Xoa_ttl ttl) {this.tab = tab; this.wiki = wiki; this.url = url; this.ttl = ttl;}
    +	public String				Thread__name()		{return "xowa.load_page_wkr";}
    +	public boolean				Thread__resume()	{return false;}
    +	public Xowe_wiki			Wiki()				{return wiki;}			private final    Xowe_wiki wiki;
    +	public Xoa_url				Url()				{return url;}			private final    Xoa_url url;
    +	public Xoa_ttl				Ttl()				{return ttl;}			private final    Xoa_ttl ttl;
    +	public Xoae_page			Page()				{return page;}			private Xoae_page page;
    +	public Exception		Exec_err()			{return exec_err;}		private Exception exec_err;
    +	public void Thread__exec() {
    +		try {
    +			Running_(true);
    +
    +			// wait_for_popups; free mem check;
    +			this.page = wiki.Page_mgr().Load_page(url, ttl, tab);
    +
    +			// launch thread to show page
    +			Gfo_invk_.Invk_by_val(tab.Cmd_sync(), Xog_tab_itm.Invk_show_url_loaded_swt, this);
    +		}
    +		catch (Exception e) {
    +			this.exec_err = e;
    +			Gfo_invk_.Invk_by_val(tab.Cmd_sync(), Xog_tab_itm.Invk_show_url_failed_swt, this);
    +		}
    +		finally {
    +			Running_(false);
    +		}
    +	}
    +	private static final    Object thread_lock = new Object(); private static boolean running = false;
    +	public static boolean Running() {
    +		boolean rv = false;
    +		synchronized (thread_lock) {
    +			rv = running;
    +		}
    +		return rv;
    +	}
    +	private static void Running_(boolean v) {
    +		synchronized (thread_lock) {
    +			running = v;
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Rect_ref.java b/400_xowa/src/gplx/xowa/guis/views/Rect_ref.java
    index a27517de8..df2545de1 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Rect_ref.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Rect_ref.java
    @@ -13,3 +13,24 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Rect_ref {
    +	public Rect_ref(int x, int y, int w, int h) {this.x = x; this.y = y; this.w = w; this.h = h;}
    +	public int X() {return x;} public Rect_ref X_(int v) {x = v; return this;} private int x;
    +	public int Y() {return y;} public Rect_ref Y_(int v) {y = v; return this;} private int y;
    +	public int W() {return w;} public Rect_ref W_(int v) {w = v; return this;} private int w;
    +	public int H() {return h;} public Rect_ref H_(int v) {h = v; return this;} private int h;
    +	public int X_max() {return x + w;}
    +	public int Y_max() {return y + h;}
    +	public gplx.gfui.RectAdp XtoRectAdp() {return gplx.gfui.RectAdp_.new_(x, y, w, h);}
    +	public gplx.gfui.RectAdp XtoRectAdp_add(Rect_ref v) {return gplx.gfui.RectAdp_.new_(x + v.x, y + v.y, w + v.w, h + v.h);}
    +	@Override public String toString() {return String_.Format("{0},{1},{2},{3}", x, y, w, h);}
    +	public static final    Rect_ref Zero = new Rect_ref(0, 0, 0, 0);
    +	public static Rect_ref rectAdp_(gplx.gfui.RectAdp v) {return new Rect_ref(v.X(), v.Y(), v.Width(), v.Height());}
    +	public static Rect_ref parse(String raw) {
    +		try {
    +			String[] ary = String_.Split(raw, ",");
    +			return new Rect_ref(Int_.Parse(ary[0]), Int_.Parse(ary[1]), Int_.Parse(ary[2]), Int_.Parse(ary[3]));
    +		}	catch(Exception exc) {throw Err_.new_parse_exc(exc, Rect_ref.class, raw);}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_async_wkr.java b/400_xowa/src/gplx/xowa/guis/views/Xog_async_wkr.java
    index a27517de8..b911d7a61 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_async_wkr.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_async_wkr.java
    @@ -13,3 +13,66 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.core.threads.*;
    +import gplx.xowa.wikis.pages.lnkis.*;
    +import gplx.xowa.guis.cbks.js.*;
    +public class Xog_async_wkr {
    +	public static void Async(Xog_tab_itm tab) {Xog_async_wkr.Async(tab.Page(), tab.Html_itm());}
    +	public static void Async(Xoae_page page, Xog_html_itm js_wkr) {
    +		if (page == null) return;	// TEST: occurs during Xog_win_mgr_tst
    +
    +		// get wiki
    +		Xowe_wiki wiki = page.Wikie(); Xoae_app app = wiki.Appe(); Gfo_usr_dlg usr_dlg = app.Usr_dlg();
    +		app.Usr_dlg().Log_many("", "", "page.async: url=~{0}", page.Url().To_str());
    +		if (page.Url().Anch_str() != null)
    +			js_wkr.Scroll_page_by_id_gui(page.Url().Anch_str());
    +		if (usr_dlg.Canceled()) {
    +			usr_dlg.Prog_none("", "", "");
    +			app.Log_wtr().Queue_enabled_(false);
    +			return;
    +		}
    +
    +		Async_imgs(usr_dlg, app, wiki, page, js_wkr);
    +		gplx.xowa.xtns.math.Xomath_latex_bldr.Async(app, page, js_wkr);
    +		Async_score(usr_dlg, app, page);
    +		Async_redlinks(usr_dlg, app, page, js_wkr);
    +
    +		// cache maintenance
    +		usr_dlg.Prog_none("", "imgs.done", "");
    +		try {app.File_mgr().Cache_mgr().Compress_check();}
    +		catch (Exception e) {usr_dlg.Warn_many("", "", "page.thread.cache: page=~{0} err=~{1}", page.Ttl().Raw(), Err_.Message_gplx_full(e));}
    +		app.Usere().User_db_mgr().Cache_mgr().Page_end(app.Wiki_mgr());
    +		app.Log_wtr().Queue_enabled_(false);	// flush queue
    +	}
    +	private static void Async_imgs(Gfo_usr_dlg usr_dlg, Xoae_app app, Xowe_wiki wiki, Xoae_page page, Xog_js_wkr js_wkr) {
    +		// get images
    +		int len = page.File_queue().Count(); 
    +		if (len > 0) {
    +			usr_dlg.Prog_one("", "", "downloading images: ~{0}", len);
    +			try {page.File_queue().Exec(wiki, page);}
    +			catch (Exception e) {usr_dlg.Warn_many("", "", "page.thread.image: page=~{0} err=~{1}", page.Ttl().Raw(), Err_.Message_gplx_full(e));}
    +		}
    +
    +		// if gallery.packed exists, call pack; NOTE:must fire even when there are 0 items in queue b/c hdump will restore images without placing in queue; PAGE:en.w:Mexico DATE:2016-08-14
    +		if (page.Html_data().Xtn_gallery_packed_exists())	// packed_gallery exists; fire js once; PAGE:en.w:National_Sculpture_Museum_(Valladolid); DATE:2014-07-21
    +			js_wkr.Html_gallery_packed_exec();
    +
    +		// call imap
    +		if (	page.Html_data().Xtn_imap_exists()			// imap exists; DATE:2014-08-07
    +			&&	page.Html_data().Head_mgr().Itm__popups().Enabled()
    +			)
    +			js_wkr.Html_popups_bind_hover_to_doc();			// rebind all elements to popup
    +	}
    +	private static void Async_score(Gfo_usr_dlg usr_dlg, Xoae_app app, Xoae_page page) {
    +		// run other cmds
    +		if (page.Html_cmd_mgr().Count() > 0) {
    +			try {page.Html_cmd_mgr().Exec(app, page);}
    +			catch (Exception e) {usr_dlg.Warn_many("", "", "page.thread.cmds: page=~{0} err=~{1}", page.Ttl().Raw(), Err_.Message_gplx_full(e));}
    +		}
    +	}
    +	private static void Async_redlinks(Gfo_usr_dlg usr_dlg, Xoae_app app, Xoae_page page, Xog_js_wkr js_wkr) {
    +		if (page.Tab_data().Tab() == null) return;	// needed b/c Preview has page.Tab of null which causes null_ref error in redlinks
    +		Xopg_redlink_mgr.Run_async(page, js_wkr);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_html_itm.java b/400_xowa/src/gplx/xowa/guis/views/Xog_html_itm.java
    index a27517de8..1a3c1d014 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_html_itm.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_html_itm.java
    @@ -13,3 +13,221 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.core.primitives.*; import gplx.core.btries.*;
    +import gplx.gfui.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*;
    +import gplx.xowa.guis.menus.*; import gplx.xowa.guis.menus.dom.*; import gplx.xowa.guis.cbks.js.*;
    +import gplx.langs.htmls.*; import gplx.xowa.htmls.hrefs.*; import gplx.xowa.htmls.js.*; import gplx.xowa.htmls.heads.*; import gplx.xowa.wikis.pages.*;
    +import gplx.xowa.htmls.*;
    +public class Xog_html_itm implements Xog_js_wkr, Gfo_invk, Gfo_evt_itm, Xoh_page_html_source {
    +	private Xoae_app app; private final    Object thread_lock = new Object();
    +	private final    String_obj_ref scroll_top = String_obj_ref.null_(), node_path = String_obj_ref.null_();
    +	protected Xog_html_itm() {}	// TEST: for prefs_mgr
    +	public Xog_html_itm(Xog_tab_itm owner_tab) {
    +		this.owner_tab = owner_tab;
    +		app = owner_tab.Tab_mgr().Win().App();
    +		js_cbk = new Xoh_js_cbk(this);
    +		Gfui_kit kit = owner_tab.Tab_mgr().Win().Kit();
    +		cmd_sync = kit.New_cmd_sync(this);	// NOTE: always use sync; async will cause some images to be "lost" in update;
    +		cmd_async = kit.New_cmd_async(this);
    +		ev_mgr = new Gfo_evt_mgr(this);
    +	}
    +	public Gfo_evt_mgr		Evt_mgr() {return ev_mgr;} private Gfo_evt_mgr ev_mgr;
    +	public Xog_tab_itm		Owner_tab() {return owner_tab;} private Xog_tab_itm owner_tab;
    +	public Gfui_html		Html_box() {return html_box;} private Gfui_html html_box;
    +	public Xoh_js_cbk		Js_cbk() {return js_cbk;} private Xoh_js_cbk js_cbk;
    +	public Gfo_invk			Cmd_sync() {return cmd_sync;} private Gfo_invk cmd_sync;
    +	public Gfo_invk			Cmd_async() {return cmd_async;} private Gfo_invk cmd_async; 
    +	public void Switch_mem(Xog_html_itm comp) {
    +		Xog_tab_itm temp_owner_tab = owner_tab;		// NOTE: reparent owner_tab, since owner_tab will be switching its html_itm
    +		this.owner_tab = comp.owner_tab;
    +		comp.owner_tab = temp_owner_tab;
    +	}
    +	public void Html_box_(Gfui_html html_box) {
    +		this.html_box = html_box;
    +		html_box.Html_js_cbks_add("xowa_exec", js_cbk);
    +		Gfo_evt_mgr_.Sub_same(html_box, Gfui_html.Evt_zoom_changed, this);
    +	}
    +	public byte[] Get_page_html() {
    +		return Bry_.new_u8(html_box.Text());
    +	}
    +	public String Html_selected_get_src_or_empty()			{return html_box.Html_js_eval_proc_as_str(Xog_js_procs.Selection__get_src_or_empty);}
    +	public String Html_selected_get_href_or_text()			{return Html_extract_text(html_box.Html_js_eval_proc_as_str(Xog_js_procs.Selection__get_href_or_text));}
    +	public String Html_selected_get_text_or_href()			{return Html_extract_text(html_box.Html_js_eval_proc_as_str(Xog_js_procs.Selection__get_text_or_href));}
    +	public String Html_selected_get_active_or_selection()	{return Html_extract_text(html_box.Html_js_eval_proc_as_str(Xog_js_procs.Selection__get_active_or_selection));}
    +	private String Html_extract_text(String v) {
    +		if (v == null) return "";	// NOTE: Selection__get_text_or_href never gets called on blank hdoc, which is what happens for Special:XowaDefaultTab; DATE:2015-07-09
    +		Xoae_page page = owner_tab.Page();
    +		String site = owner_tab.Wiki().Domain_str();
    +		String ttl = String_.new_u8(page.Ttl().Full_db());
    +		return Xoh_href_gui_utl.Html_extract_text(site, ttl, v);
    +	}
    +	public void Show(Xoae_page page) {
    +		byte view_mode = owner_tab.View_mode();			
    +		byte[] html_src = page.Wikie().Html_mgr().Page_wtr_mgr().Gen(page, this, view_mode);	// NOTE: must use wiki of page, not of owner tab; DATE:2015-03-05
    +		Html_src_(page, html_src);
    +		if (view_mode == Xopg_page_.Tid_read){						// used only for Xosrh test; DATE:2014-01-29
    +			html_box.Html_js_eval_proc_as_str(Xog_js_procs.Win__focus_body);	// NOTE: only focus if read so up / down will scroll box; edit / html should focus edit-box; DATE:2014-06-05
    +			page.Root().Data_htm_(html_src);
    +		}
    +	}
    +	private void Html_src_(Xoae_page page, byte[] html_bry) {
    +		String html_str = String_.new_u8(html_bry);
    +		if (owner_tab.Tab_mgr().Page_load_mode_is_url()) {
    +			Io_url html_url = app.Usere().Fsys_mgr().App_temp_html_dir().GenSubFil_ary(owner_tab.Tab_key(), ".html");
    +			try {html_box.Html_doc_html_load_by_url(html_url, html_str);}
    +			catch (Exception e) {
    +				app.Usr_dlg().Warn_many("", "", "failed to write html to file; writing directly by memory: page=~{0} file=~{1} err=~{2}", page.Url().To_str(), html_url.Raw(), Err_.Message_gplx_full(e));
    +				html_box.Html_doc_html_load_by_mem(html_str);
    +			}
    +		}
    +		else
    +			html_box.Html_doc_html_load_by_mem(html_str);
    +	}
    +	public void Html_swap(Xog_html_itm trg_itm) {
    +		Xog_html_itm src_itm = this;
    +		Gfui_html src_html = html_box;
    +		Gfui_html trg_html = trg_itm.html_box;
    +		Xoh_js_cbk src_js_cbk = js_cbk;
    +		Xoh_js_cbk trg_js_cbk = trg_itm.js_cbk;
    +		src_itm.html_box = trg_html;
    +		trg_itm.html_box = src_html;
    +		src_itm.js_cbk = trg_js_cbk;
    +		trg_itm.js_cbk = src_js_cbk;
    +	}
    +	public byte[] Get_elem_value_for_edit_box_as_bry()	{return Bry_.new_u8(this.Get_elem_value_for_edit_box());}
    +	public String Get_elem_value_for_edit_box()			{return Html_elem_atr_get_str(Elem_id__xowa_edit_data_box, Gfui_html.Atr_value);}
    +	public String Get_elem_value(String elem_id)		{return Html_elem_atr_get_str(elem_id, Gfui_html.Atr_value);}
    +	public void Html_img_update(String elem_id, String elem_src, int elem_width, int elem_height) {
    +		GfoMsg m = GfoMsg_.new_cast_(Invk_html_img_update).Add("elem_id", elem_id).Add("elem_src", elem_src).Add("elem_width", elem_width).Add("elem_height", elem_height);
    +		Gfo_invk_.Invk_by_msg(cmd_sync, Invk_html_img_update, m);
    +	}
    +	public void Html_elem_delete(String elem_id) {
    +		GfoMsg m = GfoMsg_.new_cast_(Invk_html_elem_delete).Add("elem_id", elem_id);
    +		Gfo_invk_.Invk_by_msg(cmd_sync, Invk_html_elem_delete, m);
    +	}
    +	@gplx.Virtual public String	Html_elem_atr_get_str(String id, String atr_key)		{return html_box.Html_js_eval_proc_as_str(Xog_js_procs.Doc__atr_get_as_obj, id, atr_key);}
    +	@gplx.Virtual public boolean		Html_elem_atr_get_bool(String id, String atr_key)		{return Bool_.Parse(html_box.Html_js_eval_proc_as_str(Xog_js_procs.Doc__atr_get_to_str, id, atr_key));}
    +	
    +
    +	public void Html_atr_set(String elem_id, String atr_key, String atr_val) {
    +		synchronized (thread_lock) {	// needed for Special:Search and async cancel; DATE:2015-05-02
    +			GfoMsg m = GfoMsg_.new_cast_(Invk_html_elem_atr_set).Add("elem_id", elem_id).Add("atr_key", atr_key).Add("atr_val", atr_val);
    +			Gfo_invk_.Invk_by_msg(cmd_sync, Invk_html_elem_atr_set, m);
    +		}
    +	}
    +	public void Html_redlink(String html_uid) {Html_doc_atr_append_or_set(html_uid, "class", gplx.xowa.wikis.pages.lnkis.Xoh_redlink_utl.New_str);}
    +	private void Html_doc_atr_append_or_set(String elem_id, String atr_key, String atr_val) {
    +		GfoMsg m = GfoMsg_.new_cast_(Invk_html_doc_atr_append_or_set).Add("elem_id", elem_id).Add("atr_key", atr_key).Add("atr_val", atr_val);
    +		Gfo_invk_.Invk_by_msg(cmd_sync, Invk_html_doc_atr_append_or_set, m);
    +	}
    +	public void Html_elem_replace_html(String id, String html) {
    +		synchronized (thread_lock) {	// needed for Special:Search and async; DATE:2015-04-23
    +			GfoMsg m = GfoMsg_.new_cast_(Invk_html_elem_replace_html).Add("id", id).Add("html", html);
    +			Gfo_invk_.Invk_by_msg(cmd_sync, Invk_html_elem_replace_html, m);
    +		}
    +	}
    +	public void Html_elem_append_above(String id, String html) {
    +		synchronized (thread_lock) {	// needed for Special:Search and async; DATE:2015-04-23
    +			GfoMsg m = GfoMsg_.new_cast_(Invk_html_elem_append_above).Add("id", id).Add("html", html);
    +			Gfo_invk_.Invk_by_msg(cmd_sync, Invk_html_elem_append_above, m);
    +		}
    +	}
    +	public void Html_gallery_packed_exec() {
    +		if (!String_.Eq(owner_tab.Tab_key(), owner_tab.Tab_mgr().Active_tab().Tab_key())) return;	// do not exec unless active;
    +		GfoMsg m = GfoMsg_.new_cast_(Invk_html_gallery_packed_exec);
    +		Gfo_invk_.Invk_by_msg(cmd_sync, Invk_html_gallery_packed_exec, m);
    +		module_packed_done = true;
    +	}
    +	public void Html_popups_bind_hover_to_doc() {
    +		if (!String_.Eq(owner_tab.Tab_key(), owner_tab.Tab_mgr().Active_tab().Tab_key())) return;	// do not exec unless active;
    +		GfoMsg m = GfoMsg_.new_cast_(Invk_html_popups_bind_hover_to_doc);
    +		Gfo_invk_.Invk_by_msg(cmd_sync, Invk_html_popups_bind_hover_to_doc, m);
    +		module_popups_done = true;
    +	}
    +	private boolean module_packed_done = false, module_popups_done = false;
    +	public void Tab_selected(Xoae_page page) {
    +		Xoh_head_mgr module_mgr = page.Html_data().Head_mgr();
    +		if (module_mgr.Itm__gallery().Enabled() && !module_packed_done)
    +			this.Html_gallery_packed_exec();
    +		if (module_mgr.Itm__popups().Enabled() && !module_popups_done)
    +			this.Html_popups_bind_hover_to_doc();
    +	}
    +	public void Scroll_page_by_bmk_gui()	{Gfo_invk_.Invk_by_key(cmd_async, Invk_scroll_page_by_bmk);}
    +	private void Scroll_page_by_bmk() {
    +		if (!String_.Eq(owner_tab.Tab_key(), owner_tab.Tab_mgr().Active_tab().Tab_key())) return; // only set html page position on active tab; otherwise, page "scrolls down" mysteriously on unseen tab; DATE:2014-05-02
    +		String html_doc_pos = owner_tab.Page().Html_data().Bmk_pos();
    +		if (html_doc_pos == null) {
    +			String auto_focus_id = app.Gui_mgr().Html_mgr().Auto_focus_id();
    +			if (String_.Len_eq_0(auto_focus_id)) return;					// don't focus anything
    +			if (String_.Eq(auto_focus_id, " first_anchor"))					// NOTE: HTML 4/5 do not allow space as id; XOWA using space here to create a unique_key that will never collide with any id
    +				html_box.Html_js_eval_proc_as_str(Xog_js_procs.Win__focus_body);	// NOTE: will focus body if content-editable, else first_anchor
    +			else
    +				html_box.Html_js_eval_proc_as_str(Xog_js_procs.Doc__elem_focus, auto_focus_id);
    +		}
    +		else if (String_.Eq(html_doc_pos, gplx.xowa.guis.history.Xog_history_itm.Html_doc_pos_toc))	// NOTE: special case to handle TOC clicks; DATE:2013-07-17
    +			Scroll_page_by_id("toc");
    +		else {
    +			Html_window_vpos_parse(html_doc_pos, scroll_top, node_path);
    +			html_box.Html_js_eval_proc_as_str(Xog_js_procs.Win__vpos_set, node_path.Val(), scroll_top.Val());
    +		}
    +	}
    +	public void Scroll_page_by_id_gui(String id)	{Gfo_invk_.Invk_by_val(cmd_async, Invk_scroll_page_by_id, id);}
    +	private boolean Scroll_page_by_id(String id) {
    +		return (id == null) 
    +			? false
    +			: html_box.Html_js_eval_proc_as_bool(Xog_js_procs.Win__scroll_elem_into_view, gplx.langs.htmls.encoders.Gfo_url_encoder_.Id.Encode_str(id));
    +	}
    +	public void Js_enabled_(boolean v) {
    +		html_box.Html_js_enabled_(v);
    +	}
    +	private void When_menu_detected() {
    +		Xoa_gui_mgr gui_mgr = app.Gui_mgr(); Gfui_kit kit = gui_mgr.Kit();
    +		Xog_popup_mnu_mgr popup_mnu_mgr = gui_mgr.Menu_mgr().Popup();
    +		Xog_mnu_grp popup_mnu = popup_mnu_mgr.Html_page();
    +		if		(String_.Len_gt_0(this.Html_selected_get_src_or_empty()))
    +			popup_mnu = popup_mnu_mgr.Html_file();
    +		else if (String_.Len_gt_0(this.Html_selected_get_text_or_href()))
    +			popup_mnu = popup_mnu_mgr.Html_link();
    +		kit.Set_mnu_popup(html_box, popup_mnu.Under_mnu());
    +	}
    +	private void When_zoom_changed(boolean clicks_is_positive) {
    +		app.Api_root().Gui().Font().Adj(clicks_is_positive ? 1 : -1);
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_html_img_update))					html_box.Html_js_eval_proc_as_bool	(Xog_js_procs.Doc__elem_img_update		, m.ReadStr("elem_id"), m.ReadStr("elem_src"), m.ReadInt("elem_width"), m.ReadInt("elem_height"));
    +		else if	(ctx.Match(k, Invk_html_elem_atr_set))					html_box.Html_js_eval_proc_as_str	(Xog_js_procs.Doc__atr_set				, m.ReadStr("elem_id"), m.ReadStr("atr_key"), m.ReadStr("atr_val"));
    +		else if	(ctx.Match(k, Invk_html_doc_atr_append_or_set))			html_box.Html_js_eval_proc_as_str	(Xog_js_procs.Doc__atr_append_or_set	, m.ReadStr("elem_id"), m.ReadStr("atr_key"), m.ReadStr("atr_val"));
    +		else if	(ctx.Match(k, Invk_html_elem_delete))					html_box.Html_js_eval_proc_as_bool	(Xog_js_procs.Doc__elem_delete			, m.ReadStr("elem_id"));
    +		else if	(ctx.Match(k, Invk_html_elem_replace_html))				html_box.Html_js_eval_proc_as_str	(Xog_js_procs.Doc__elem_replace_html	, m.ReadStr("id"), m.ReadStr("html"));
    +		else if	(ctx.Match(k, Invk_html_elem_append_above))				html_box.Html_js_eval_proc_as_str	(Xog_js_procs.Doc__elem_append_above	, m.ReadStr("id"), m.ReadStr("html"));
    +		else if	(ctx.Match(k, Invk_html_gallery_packed_exec))			html_box.Html_js_eval_proc_as_str	(Xog_js_procs.Xtn__gallery_packed_exec);
    +		else if	(ctx.Match(k, Invk_html_popups_bind_hover_to_doc))		html_box.Html_js_eval_script("xowa_popups_bind_doc();");
    +		else if (ctx.Match(k, Invk_scroll_page_by_bmk))					Scroll_page_by_bmk();
    +		else if (ctx.Match(k, Invk_scroll_page_by_id))					Scroll_page_by_id(m.ReadStr("v"));
    +		else if (ctx.Match(k, Invk_html_elem_focus))					html_box.Html_js_eval_proc_as_str(Xog_js_procs.Doc__elem_focus, m.ReadStr("v"));
    +		else if (ctx.Match(k, GfuiElemKeys.Evt_menu_detected))			When_menu_detected();
    +		else if	(ctx.Match(k, Gfui_html.Evt_zoom_changed))				When_zoom_changed(m.ReadBool("clicks_is_positive"));
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	private static final String 
    +	  Invk_html_gallery_packed_exec = "html_gallery_packed_exec", Invk_html_popups_bind_hover_to_doc = "html_popups_bind_hover_to_doc"
    +	, Invk_html_img_update = "html_img_update", Invk_html_elem_atr_set = "html_elem_atr_set"
    +	, Invk_html_doc_atr_append_or_set = "html_doc_atr_append_or_set", Invk_html_elem_delete = "html_elem_delete", Invk_html_elem_replace_html = "html_elem_replace_html", Invk_html_elem_append_above = "html_elem_append_above"
    +	, Invk_scroll_page_by_bmk = "scroll_page_by_bmk", Invk_scroll_page_by_id = "scroll_page_by_id"
    +	;
    +	public static final String 
    +	  Elem_id__xowa_edit_data_box		= "xowa_edit_data_box"
    +	, Elem_id__xowa_edit_rename_box		= "xowa_edit_rename_box"
    +	, Elem_id__first_heading			= "firstHeading"
    +	, Invk_html_elem_focus				= "html_elem_focus"
    +	;
    +	public static void Html_window_vpos_parse(String v, String_obj_ref scroll_top, String_obj_ref node_path) {
    +		int pipe_pos = String_.FindFwd(v, "|"); if (pipe_pos == String_.Find_none) return; // if elem_get_path returns invalid value, don't fail; DATE:2014-04-05
    +		scroll_top.Val_(String_.Mid(v, 0, pipe_pos));
    +		String node_path_val = String_.Mid(v, pipe_pos + 1, String_.Len(v));
    +		node_path_val = "'" + String_.Replace(node_path_val, ",", "','") + "'";
    +		node_path.Val_(node_path_val);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_js_procs.java b/400_xowa/src/gplx/xowa/guis/views/Xog_js_procs.java
    index a27517de8..904c5f275 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_js_procs.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_js_procs.java
    @@ -13,3 +13,32 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Xog_js_procs {
    +	public static final String
    +	  Win__focus_body							= "xowa.js.win.focus_body"
    +	, Win__vpos_get								= "xowa.js.win.vpos_get"
    +	, Win__vpos_set								= "xowa.js.win.vpos_set"
    +	, Win__find_in_hdoc							= "xowa.js.win.find_in_hdoc"
    +	, Win__find_in_textarea						= "xowa.js.win.find_in_textarea"
    +	, Win__scroll_elem_into_view				= "xowa.js.win.scroll_elem_into_view"
    +	, Win__print_preview						= "xowa.js.win.print_preview"
    +	, Doc__root_html_get						= "xowa.js.doc.root_html_get"
    +	, Doc__atr_get_as_obj						= "xowa.js.doc.atr_get_as_obj"
    +	, Doc__atr_get_to_str						= "xowa.js.doc.atr_get_to_str"
    +	, Doc__atr_set								= "xowa.js.doc.atr_set"
    +	, Doc__atr_append_or_set					= "xowa.js.doc.atr_append_or_set"
    +	, Doc__elem_focus							= "xowa.js.doc.elem_focus"
    +	, Doc__elem_delete							= "xowa.js.doc.elem_delete"
    +	, Doc__elem_append_above					= "xowa.js.doc.elem_append_above"
    +	, Doc__elem_replace_html					= "xowa.js.doc.elem_replace_html"
    +	, Doc__elem_img_update						= "xowa.js.doc.elem_img_update"
    +	, Selection__get_text_or_href				= "xowa.js.selection.get_text_or_href"
    +	, Selection__get_href_or_text				= "xowa.js.selection.get_href_or_text"
    +	, Selection__get_active_or_selection		= "xowa.js.selection.get_active_or_selection"
    +	, Selection__get_src_or_empty				= "xowa.js.selection.get_src_or_empty"
    +	, Selection__get_active_for_editable_mode	= "xowa.js.selection.get_active_for_editable_mode"
    +	, Selection__toggle_focus_for_anchor		= "xowa.js.selection.toggle_focus_for_anchor"
    +	, Xtn__gallery_packed_exec					= "xowa.js.xtn.gallery_packed_exec"
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_launcher_tabs.java b/400_xowa/src/gplx/xowa/guis/views/Xog_launcher_tabs.java
    index a27517de8..3adbb3adf 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_launcher_tabs.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_launcher_tabs.java
    @@ -13,3 +13,89 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.xowa.apps.urls.*;
    +class Xog_launcher_tabs {
    +	public void Launch(Xog_win_itm win) {
    +		Xoae_app app = win.App(); Gfo_log_bfr log_bfr = app.Log_bfr();
    +		log_bfr.Add("app.launch.page.bgn");			
    +		Io_fil_marker fil_marker = new Io_fil_marker().Usr_dlg_(app.Usr_dlg()).Url_(app.Usere().Fsys_mgr().App_temp_dir().GenSubFil_nest("session", "launch.tabs.marker"));
    +		boolean tabs_restored = false;
    +		Xowe_wiki home_wiki = app.Usere().Wiki();
    +		if (fil_marker.Bgn())
    +			tabs_restored = Restore_tabs(app, home_wiki, win, fil_marker);
    +		if (!tabs_restored)
    +			Restore_tab_failover(app, home_wiki, win);
    +		// tab.Html_itm().Html_box().Focus(); // focus the html_box so wheel scroll works; DATE:2013-02-08
    +		app.Usr_dlg().Prog_none("", "", "");
    +		log_bfr.Add("app.launch.page.end");
    +		app.Usr_dlg().Log_wkr().Log_to_session_direct(log_bfr.Xto_str());
    +	}
    +	private boolean Restore_tabs(Xoae_app app, Xowe_wiki home_wiki, Xog_win_itm win, Io_fil_marker fil_marker) {
    +		Xog_startup_tabs startup_tabs = new Xog_startup_tabs().Init_by_app(app).Calc();
    +		String[] launch_urls = startup_tabs.Startup_urls();
    +		try {
    +			int launch_urls_len = launch_urls.length;
    +			for (int i = 0; i < launch_urls_len; ++i) {
    +				String launch_url = launch_urls[i];
    +				Launch_tab(win, home_wiki, launch_url);
    +			}
    +			if (startup_tabs.Startup_idx() != -1)
    +				app.Gui_mgr().Browser_win().Tab_mgr().Tabs_select_by_idx(startup_tabs.Startup_idx());
    +			fil_marker.End();
    +			return true;
    +		}
    +		catch (Exception e) {
    +			app.Usr_dlg().Warn_many("", "", "failed to launch urls: urls=~{0} err=~{1}", String_.AryXtoStr(launch_urls), Err_.Message_gplx_full(e));
    +			Restore_tab_failover(app, home_wiki, win);
    +			return false;
    +		}
    +	}
    +	private void Restore_tab_failover(Xoae_app app, Xowe_wiki home_wiki, Xog_win_itm win) {
    +		try {Launch_tab(win, home_wiki, Xog_startup_tabs.Url__home_main);}
    +		catch (Exception e) {app.Usr_dlg().Warn_many("", "", "failed to launch failover tab: err=~{0}", Err_.Message_gplx_full(e));}
    +	}
    +	private void Launch_tab(Xog_win_itm win, Xowe_wiki home_wiki, String launch_str) {
    +		Xoae_app app = win.App();
    +		Xoa_url launch_url = home_wiki.Utl__url_parser().Parse_by_urlbar_or_null(launch_str); if (launch_url == null) return;
    +		Xowe_wiki launch_wiki = (Xowe_wiki)app.Wiki_mgr().Get_by_or_make_init_y(launch_url.Wiki_bry());
    +		Xoa_ttl launch_ttl = Xoa_ttl.Parse(launch_wiki, launch_url.Page_bry());
    +		Xog_tab_itm tab = win.Tab_mgr().Tabs_new_init(launch_wiki, Xoae_page.New(launch_wiki, launch_ttl)); // WORKAROUND: set the tab to an empty page, else null ref later; DATE:2014-07-23
    +		tab.Show_url_bgn(launch_url);
    +	}
    +	public static final    Xog_launcher_tabs Instance = new Xog_launcher_tabs(); Xog_launcher_tabs() {}
    +}
    +class Io_fil_marker {
    +	private Io_url url;
    +	private Gfo_usr_dlg usr_dlg = Gfo_usr_dlg_.Instance;
    +	public Io_fil_marker Usr_dlg_(Gfo_usr_dlg v) {this.usr_dlg = v; return this;}
    +	public Io_fil_marker Url_(Io_url url) {this.url = url; return this;}
    +	public boolean Bgn() {
    +		boolean rv = false;
    +		synchronized (this) {
    +			try {
    +				rv = !Io_mgr.Instance.ExistsFil(url);		// exists = fail; !exists = pass;
    +				if (rv)										// pass: file does not exist;
    +					Io_mgr.Instance.SaveFilStr(url, "");	// create
    +				else										// file exists from previous run
    +					Io_mgr.Instance.DeleteFil(url);			// delete
    +			}
    +			catch (Exception exc) {					// something unexpected happened
    +				usr_dlg.Warn_many("", "", "marker.bgn failed: url=~{0} err=~{1}", url.Raw(), Err_.Message_gplx_full(exc));
    +				Io_mgr.Instance.DeleteFil(url);				// try to delete it again
    +			}
    +		}
    +		return rv;
    +	}
    +	public void End() {
    +		synchronized (this) {
    +			try {
    +				Io_mgr.Instance.DeleteFil(url);				// delete
    +			}
    +			catch (Exception exc) {
    +				usr_dlg.Warn_many("", "", "marker.end failed: url=~{0} err=~{1}", url.Raw(), Err_.Message_gplx_full(exc));
    +				Io_mgr.Instance.DeleteFil(url);				// try to delete it again
    +			}
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_layout.java b/400_xowa/src/gplx/xowa/guis/views/Xog_layout.java
    index a27517de8..a2511308f 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_layout.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_layout.java
    @@ -13,3 +13,104 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.gfui.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*; import gplx.xowa.guis.views.*;
    +public class Xog_layout implements Gfo_invk {
    +	public Xog_layout() {
    +		go_bwd_btn.Owner_(this);
    +		go_fwd_btn.Owner_(this);
    +		url_box.Owner_(this);
    +		url_exec_btn.Owner_(this);
    +		find_close_btn.Owner_(this);
    +		search_box.Owner_(this);
    +		search_exec_btn.Owner_(this);
    +		allpages_box.Owner_(this);
    +		allpages_exec_btn.Owner_(this);
    +		html_box.Owner_(this);
    +		find_box.Owner_(this);
    +		prog_box.Owner_(this);
    +		note_box.Owner_(this);
    +		main_win.Owner_(this);
    +	}
    +	public Xog_layout_box Go_fwd_btn() {return go_fwd_btn;} private Xog_layout_box go_fwd_btn = new Xog_layout_box();
    +	public Xog_layout_box Go_bwd_btn() {return go_bwd_btn;} private Xog_layout_box go_bwd_btn = new Xog_layout_box();
    +	public Xog_layout_box Url_box() {return url_box;} private Xog_layout_box url_box = new Xog_layout_box();
    +	public Xog_layout_box Url_exec_btn() {return url_exec_btn;} private Xog_layout_box url_exec_btn = new Xog_layout_box();
    +	public Xog_layout_box Find_close_btn() {return find_close_btn;} private Xog_layout_box find_close_btn = new Xog_layout_box();
    +	public Xog_layout_box Search_box() {return search_box;} private Xog_layout_box search_box = new Xog_layout_box();
    +	public Xog_layout_box Search_exec_btn() {return search_exec_btn;} private Xog_layout_box search_exec_btn = new Xog_layout_box();
    +	public Xog_layout_box Allpages_box() {return allpages_box;} private Xog_layout_box allpages_box = new Xog_layout_box();
    +	public Xog_layout_box Allpages_exec_btn() {return allpages_exec_btn;} private Xog_layout_box allpages_exec_btn = new Xog_layout_box();
    +	public Xog_layout_box Html_box() {return html_box;} private Xog_layout_box html_box = new Xog_layout_box();
    +	public Xog_layout_box Find_box() {return find_box;} private Xog_layout_box find_box = new Xog_layout_box();
    +	public Xog_layout_box Find_fwd_btn() {return find_fwd_btn;} private Xog_layout_box find_fwd_btn = new Xog_layout_box();
    +	public Xog_layout_box Find_bwd_btn() {return find_bwd_btn;} private Xog_layout_box find_bwd_btn = new Xog_layout_box();
    +	public Xog_layout_box Prog_box() {return prog_box;} private Xog_layout_box prog_box = new Xog_layout_box();
    +	public Xog_layout_box Note_box() {return note_box;} private Xog_layout_box note_box = new Xog_layout_box();
    +	public Xog_layout_box Browser_win() {return main_win;} private Xog_layout_box main_win = new Xog_layout_box();
    +	public void Find_show() {
    +		Visible_(true, win.Find_box(), win.Find_bwd_btn(), win.Find_fwd_btn(), win.Find_close_btn());
    +		GfuiTextBox find_box = win.Find_box();
    +		find_box.Focus();
    +		int text_len = String_.Len(find_box.Text());
    +		if (text_len > 0) {	// if text exists, select it; GUI:Firefox
    +			find_box.SelBgn_set(0);
    +			find_box.SelLen_set(text_len);
    +		}
    +	}
    +	public void Find_close() {
    +		Visible_(false, win.Find_box(), win.Find_bwd_btn(), win.Find_fwd_btn(), win.Find_close_btn());
    +		win.Active_html_box().Focus();
    +	}
    +	private void Visible_(boolean v, GfuiElem... ary) {
    +		int ary_len = ary.length;
    +		for (int i = 0; i < ary_len; i++)
    +			ary[i].Visible_set(v);
    +	}
    +	public void Init(Xog_win_itm win) {
    +		this.win = win;
    +		go_bwd_btn.Adj_text(win.Go_bwd_btn());
    +		go_fwd_btn.Adj_text(win.Go_fwd_btn());
    +		url_box.Adj_text(win.Url_box());
    +		url_exec_btn.Adj_text(win.Url_exec_btn());
    +		find_close_btn.Adj_text(win.Find_close_btn());
    +		search_box.Adj_text(win.Search_box());
    +		search_exec_btn.Adj_text(win.Search_exec_btn());
    +		allpages_box.Adj_text(win.Allpages_box());
    +		allpages_exec_btn.Adj_text(win.Allpages_exec_btn());
    +		find_box.Adj_text(win.Find_box());
    +		find_fwd_btn.Adj_text(win.Find_fwd_btn());
    +		find_bwd_btn.Adj_text(win.Find_bwd_btn());
    +		prog_box.Adj_text(win.Prog_box());
    +		note_box.Adj_text(win.Info_box());
    +		win.Tab_mgr().Tab_mgr().TextMgr().Font_(win.Url_box().TextMgr().Font());
    +		Visible_(false, win.Find_box(), win.Find_bwd_btn(), win.Find_fwd_btn(), win.Find_close_btn());
    +	}	private Xog_win_itm win;
    +	public int Box_height_calc(Gfui_kit kit, GfuiElem url_box) {
    +		if (box_height > 0) return box_height;
    +		float font_height = kit.Calc_font_height(url_box, "I");
    +		box_height = (int)(font_height * 2);
    +		return box_height;
    +	}	private int box_height = 0;
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_go_fwd_btn))				return go_fwd_btn;
    +		else if	(ctx.Match(k, Invk_go_bwd_btn))				return go_bwd_btn;
    +		else if	(ctx.Match(k, Invk_url_box))				return url_box;
    +		else if	(ctx.Match(k, Invk_url_exec_btn))			return url_exec_btn;
    +		else if	(ctx.Match(k, Invk_find_close_btn))			return find_close_btn;
    +		else if	(ctx.Match(k, Invk_search_box))				return search_box;
    +		else if	(ctx.Match(k, Invk_search_exec_btn))		return search_exec_btn;
    +		else if	(ctx.Match(k, "allpages_box"))				return allpages_box;
    +		else if	(ctx.Match(k, "allpages_exec_btn"))			return allpages_exec_btn;
    +		else if	(ctx.Match(k, Invk_html_box))				return html_box;
    +		else if	(ctx.Match(k, Invk_find_box))				return find_box;
    +		else if	(ctx.Match(k, Invk_find_fwd_btn))			return find_fwd_btn;
    +		else if	(ctx.Match(k, Invk_find_bwd_btn))			return find_bwd_btn;
    +		else if	(ctx.Match(k, Invk_prog_box))				return prog_box;
    +		else if	(ctx.Match(k, Invk_note_box))				return note_box;
    +		else if	(ctx.Match(k, Invk_main_win))				return main_win;
    +		else	return Gfo_invk_.Rv_unhandled;
    +	}
    +	static final String Invk_coord_mode_ = "coord_mode_", Invk_go_fwd_btn = "go_fwd_btn", Invk_go_bwd_btn = "go_bwd_btn", Invk_url_box = "url_box", Invk_search_box = "search_box", Invk_html_box = "html_box", Invk_find_box = "find_box", Invk_prog_box = "prog_box", Invk_note_box = "note_box"
    +		, Invk_main_win = "main_win", Invk_find_fwd_btn = "find_fwd_btn", Invk_find_bwd_btn = "find_bwd_btn", Invk_url_exec_btn = "url_exec_btn", Invk_search_exec_btn = "search_exec_btn", Invk_find_close_btn = "find_close_btn";
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_layout_box.java b/400_xowa/src/gplx/xowa/guis/views/Xog_layout_box.java
    index a27517de8..525b08caa 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_layout_box.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_layout_box.java
    @@ -13,3 +13,69 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.gfui.*; import gplx.gfui.draws.*; import gplx.gfui.controls.elems.*;
    +public class Xog_layout_box implements Gfo_invk {
    +	public Gfo_invk Owner() {return owner;} public Xog_layout_box Owner_(Gfo_invk v) {owner = v; return this;} Gfo_invk owner;
    +	public int X_abs() {return x_abs;} private int x_abs = Int_.Min_value;
    +	public int Y_abs() {return y_abs;} private int y_abs = Int_.Min_value;
    +	public int W_abs() {return w_abs;} private int w_abs = Int_.Min_value;
    +	public int H_abs() {return h_abs;} private int h_abs = Int_.Min_value;
    +	private int x_rel = Int_.Min_value;
    +	private int y_rel = Int_.Min_value;
    +	public Xog_layout_box W_rel_(int v) {w_rel = v; return this;} private int w_rel = Int_.Min_value;
    +	public Xog_layout_box H_rel_(int v) {h_rel = v; return this;} private int h_rel = Int_.Min_value;
    +	private String text;
    +	private String font_name;
    +	private float font_size = Float_.NaN;
    +	private FontStyleAdp font_style;
    +	public byte Mode() {return mode;} private byte mode = Mode_rel;
    +	public void Adj_size(Rect_ref rect) {
    +		if (w_abs > -1) rect.W_(w_abs);	if (w_rel != Int_.Min_value) rect.W_(w_rel + rect.W());
    +		if (h_abs > -1) rect.H_(h_abs);	if (h_rel != Int_.Min_value) rect.H_(h_rel + rect.H());
    +	}
    +	public void Adj_pos(Rect_ref rect) {
    +		if (x_abs > -1) rect.X_(x_abs);	if (x_rel != Int_.Min_value) rect.X_(x_rel + rect.X());
    +		if (y_abs > -1) rect.Y_(y_abs);	if (y_rel != Int_.Min_value) rect.Y_(y_rel + rect.Y());
    +	}
    +	public void Adj_text(GfuiElem elem) {
    +		if (text != null) elem.Text_(text);
    +		if (font_name != null || !Float_.IsNaN(font_size) || font_style != null)
    +			elem.TextMgr().Font_(Font_make(font_name, font_size, font_style));
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_x_abs_))					x_abs = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk_y_abs_))					y_abs = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk_w_abs_))					w_abs = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk_h_abs_))					h_abs = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk_x_rel_))					x_rel = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk_y_rel_))					y_rel = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk_w_rel_))					w_rel = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk_h_rel_))					h_rel = m.ReadInt("v");
    +		else if	(ctx.Match(k, Invk_size_abs_))				{int[] ary = Int_ary_.Parse(m.ReadStr("v"), 2, null); if (ary != null) {w_abs = ary[0]; h_abs = ary[1];}}
    +		else if	(ctx.Match(k, Invk_size_rel_))				{int[] ary = Int_ary_.Parse(m.ReadStr("v"), 2, null); if (ary != null) {w_rel = ary[0]; h_rel = ary[1];}}
    +		else if	(ctx.Match(k, Invk_pos_abs_))				{int[] ary = Int_ary_.Parse(m.ReadStr("v"), 2, null); if (ary != null) {x_abs = ary[0]; y_abs = ary[1];}}
    +		else if	(ctx.Match(k, Invk_pos_rel_))				{int[] ary = Int_ary_.Parse(m.ReadStr("v"), 2, null); if (ary != null) {x_rel = ary[0]; y_rel = ary[1];}}
    +		else if	(ctx.Match(k, Invk_rect_abs_))				{int[] ary = Int_ary_.Parse(m.ReadStr("v"), 4, null); if (ary != null) {w_abs = ary[0]; h_abs = ary[1]; x_abs = ary[2]; y_abs = ary[3];}}
    +		else if	(ctx.Match(k, Invk_rect_rel_))				{int[] ary = Int_ary_.Parse(m.ReadStr("v"), 4, null); if (ary != null) {w_rel = ary[0]; h_rel = ary[1]; x_rel = ary[2]; y_rel = ary[3];}}
    +		else if	(ctx.Match(k, Invk_text_))					text = m.ReadStr("v");
    +		else if	(ctx.Match(k, Invk_font_name_))				font_name = m.ReadStr("v");
    +		else if	(ctx.Match(k, Invk_font_size_))				font_size = m.ReadFloat("v");
    +		else if	(ctx.Match(k, Invk_font_style_))			font_style = FontStyleAdp_.parse(m.ReadStr("v"));
    +		else if	(ctx.Match(k, Invk_mode_))					mode = String_.Eq(m.ReadStr("v"), "abs") ? Mode_abs : Mode_rel;
    +		else if	(ctx.Match(k, Invk_owner))					return owner;
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	public static final byte Mode_abs = 0, Mode_rel = 1;
    +	private static final String Invk_x_abs_ = "x_abs_", Invk_y_abs_ = "y_abs_", Invk_w_abs_ = "w_abs_", Invk_h_abs_ = "h_abs_", Invk_x_rel_ = "x_rel_", Invk_y_rel_ = "y_rel_", Invk_w_rel_ = "w_rel_", Invk_h_rel_ = "h_rel_"
    +	, Invk_size_abs_ = "size_abs_", Invk_pos_abs_ = "pos_abs_", Invk_rect_abs_ = "rect_abs_", Invk_size_rel_ = "size_rel_", Invk_pos_rel_ = "pos_rel_", Invk_rect_rel_ = "rect_rel_"
    +	, Invk_text_ = "text_"
    +	, Invk_font_name_ = "font_name_", Invk_font_size_ = "font_size_", Invk_font_style_ = "font_style_", Invk_mode_ = "mode_", Invk_owner = "owner";
    +	private static FontAdp Font_make(String font_name, float font_size, FontStyleAdp font_style) {
    +		String new_font_name = font_name == null ? "Arial" : font_name;
    +		float new_font_size = Float_.IsNaN(font_size) ? 8 : font_size;
    +		FontStyleAdp new_font_style = font_style == null ? FontStyleAdp_.Plain : font_style;
    +		return FontAdp.new_(new_font_name, new_font_size, new_font_style);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_startup_tabs.java b/400_xowa/src/gplx/xowa/guis/views/Xog_startup_tabs.java
    index a27517de8..3256e113a 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_startup_tabs.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_startup_tabs.java
    @@ -13,3 +13,114 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.xowa.addons.apps.cfgs.*; import gplx.xowa.addons.apps.updates.specials.*;
    +public class Xog_startup_tabs {
    +	private String type, custom_list, prev_list, prev_version, curr_version;
    +	private boolean show_app_update;
    +	public int Startup_idx() {return startup_idx;} private int startup_idx;
    +	public String[] Startup_urls() {return startup_urls;} private String[] startup_urls = String_.Ary_empty;
    +	public Xog_startup_tabs Init_by_app(Xoae_app app) {
    +		Xocfg_mgr cfg = app.Cfg();
    +		this.type = cfg.Get_str_app_or(Cfg__startup_type, Opt__startup_type__previous);
    +		this.custom_list = cfg.Get_str_app_or(Cfg__custom_list, "");
    +		this.prev_list = cfg.Get_str_app_or(Cfg__prev_list, "");
    +		this.prev_version = cfg.Get_str_app_or(Cfg__prev_version, "");
    +		this.curr_version = Xoa_app_.Version;
    +		this.startup_idx = cfg.Get_int_app_or(Cfg__prev_selected, -1);
    +		this.show_app_update = gplx.xowa.addons.apps.updates.Xoa_update_startup.Show_at_startup(app);
    +		return this;
    +	}
    +	public Xog_startup_tabs Calc() {
    +		List_adp list = List_adp_.New();	// NOTE: do not change to hash; duplicate urls are possible
    +
    +		// process main types
    +		if (Manual == null) {
    +			if		(String_.Eq(type, "blank"))						list.Add(gplx.xowa.specials.Xow_special_meta_.Itm__default_tab.Ttl_str());
    +			else if (String_.Eq(type, "xowa"))						list.Add(Url__home_main);
    +			else if (String_.Eq(type, "custom"))					Parse_ary(list, custom_list);
    +			else if (String_.Eq(type, Opt__startup_type__previous))	Parse_ary(list, prev_list);
    +			else													throw Err_.new_unhandled(type);
    +		}
    +		else
    +			list.Add(Manual);
    +
    +		// if new version, add home/wiki/Main_Page
    +		if (gplx.xowa.apps.versions.Xoa_version_.Compare(prev_version, curr_version) == CompareAble_.Less) {
    +			startup_idx = Add_if_absent(list, Url__home_main);
    +		}
    +
    +		// if show_app_update, add page
    +		if (show_app_update) {
    +			startup_idx = Add_if_absent(list, Xoa_update_special.Prototype.Special__meta().Url__home());
    +		}
    +
    +		// generate urls
    +		startup_urls = (String[])list.To_ary_and_clear(String.class);
    +
    +		// do bounds check
    +		if (startup_idx < 0 || startup_idx >= startup_urls.length)
    +			startup_idx = startup_urls.length - 1;
    +		return this;
    +	}
    +	private static int Add_if_absent(List_adp list, String page) {
    +		// check list for page
    +		int len = list.Len();
    +		for (int i = 0; i < len; i++) {
    +			String itm = (String)list.Get_at(i);
    +
    +			// page found; return its index
    +			if (String_.Eq(itm, page)) {
    +				return i;
    +			}
    +		}
    +
    +		// add if not found
    +		list.Add(page);
    +		return list.Len();
    +	}
    +	private static void Parse_ary(List_adp list, String s) {
    +		if (String_.Len_eq_0(s)) return;
    +		String[] ary = String_.SplitLines_nl(String_.Trim(s));
    +		int len = ary.length;
    +		for (int i = 0; i < len; i++) {
    +			String itm = ary[i];
    +			if (String_.Len_eq_0(itm)) continue;
    +			list.Add(itm);
    +		}
    +	}
    +	public static String Version_previous(Xoa_app app) {return app.Cfg().Get_str_app_or(Cfg__prev_version, "");}
    +	public static void Shutdown(Xoae_app app) {
    +		app.Cfg().Set_str_app(Cfg__prev_version, Xoa_app_.Version);
    +		if (String_.Eq(app.Cfg().Get_str_app_or(Cfg__startup_type, Opt__startup_type__previous), Opt__startup_type__previous)) {
    +			app.Cfg().Set_str_app(Cfg__prev_list	, Calc_previous_tabs(app.Gui_mgr().Browser_win().Tab_mgr()));
    +
    +			// save prev_selected
    +			int prev_selected = app.Gui_mgr().Browser_win().Tab_mgr().Tabs_len() == 0 // must check for 0 tabs, else null ref
    +				? -1
    +				: app.Gui_mgr().Browser_win().Tab_mgr().Active_tab().Tab_idx();
    +			app.Cfg().Set_int_app(Cfg__prev_selected, prev_selected);
    +		}
    +	}
    +	private static String Calc_previous_tabs(gplx.xowa.guis.views.Xog_tab_mgr tab_mgr) {
    +		Bry_bfr bfr = Bry_bfr_.New();
    +		int len = tab_mgr.Tabs_len();
    +		for (int i = 0; i < len; ++i) {
    +			if (i != 0) bfr.Add_byte_nl();
    +			gplx.xowa.guis.views.Xog_tab_itm tab = tab_mgr.Tabs_get_at(i);
    +			bfr.Add_str_u8(tab.Page().Url().To_str());
    +		}
    +		return bfr.To_str_and_clear();
    +	}
    +	public static String Manual = null;	// note set by command-line at startup;
    +	private static final String
    +	  Cfg__startup_type							= "xowa.app.startup.tabs.type"
    +	, Cfg__custom_list							= "xowa.app.startup.tabs.custom"
    +	, Cfg__prev_list							= "xowa.app.startup.tabs.previous_list"
    +	, Cfg__prev_selected						= "xowa.app.startup.tabs.previous_selected"
    +	, Cfg__prev_version							= "xowa.app.setup.previous_version"
    +	, Opt__startup_type__previous				= "previous";
    +	public static final String
    +	  Url__home_main							= "home/wiki/Main_Page"
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_startup_win_.java b/400_xowa/src/gplx/xowa/guis/views/Xog_startup_win_.java
    index a27517de8..67ab6eed1 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_startup_win_.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_startup_win_.java
    @@ -13,3 +13,95 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.core.envs.*;
    +import gplx.gfui.*; import gplx.gfui.envs.*; import gplx.gfui.controls.windows.*;
    +public class Xog_startup_win_ {
    +	public static void Startup(Xoa_app app, GfuiWin win) {
    +		gplx.xowa.addons.apps.cfgs.Xocfg_mgr cfg_mgr = app.Cfg();
    +		String window_mode = cfg_mgr.Get_str_app_or(Cfg__window_mode, "previous");
    +		Rect_ref manual_rect = Rect_ref.parse(cfg_mgr.Get_str_app_or(Cfg__manual_rect, "0,0,800,640"));
    +
    +		// change win_rect per mode: previous; absolute; etc.
    +		boolean init_is_maximized = false;
    +		if		(String_.Eq(window_mode, "previous")) {
    +			if (cfg_mgr.Get_bool_app_or(Cfg__prev_maximized, false)) {
    +				win.Maximized_(true);
    +				init_is_maximized = true;
    +			}
    +			else {
    +				Rect_ref previous_rect = null;
    +				String s = cfg_mgr.Get_str_app_or(Cfg__prev_rect, "");
    +				if (String_.Eq(s, "")) {
    +					SizeAdp size = Screen_maximized_calc();
    +					previous_rect = new Rect_ref(0, 0, size.Width(), size.Height());
    +				}
    +				else
    +					previous_rect = Rect_ref.parse(s);
    +				win.Rect_set(previous_rect.XtoRectAdp());
    +			}
    +		}
    +		else if (String_.Eq(window_mode, "absolute")) {
    +			win.Rect_set(manual_rect.XtoRectAdp());
    +		}
    +		else if (String_.Eq(window_mode, "maximized")) {
    +			win.Maximized_(true);
    +			init_is_maximized = true; 
    +		}
    +		else if (String_.Eq(window_mode, "default")) {} // noop
    +		else if (String_.Eq(window_mode, "relative")) {
    +			SizeAdp screen_maximized = Screen_maximized_calc();
    +			Rect_ref win_rect = new Rect_ref(0, 0, screen_maximized.Width(), screen_maximized.Height());
    +			win.Rect_set(win_rect.XtoRectAdp_add(manual_rect));
    +		}
    +
    +		// make sure win_rect is safe
    +		boolean safe_mode = cfg_mgr.Get_bool_app_or(Cfg__manual_safe, true);
    +		if (safe_mode && !init_is_maximized) {
    +			RectAdp rect = win.Rect();
    +			boolean force = false; int x = rect.X(), y = rect.Y(), w = rect.Width(), h = rect.Height();
    +			SizeAdp screen_size = Screen_maximized_calc();
    +			int max_w = screen_size.Width(), max_h = screen_size.Height();
    +			int Safe_mode_buffer = 20; // allow minor negative positioning (x=-5), off-screen positioning (w=1605)
    +			if (x < -Safe_mode_buffer || x		> max_w + Safe_mode_buffer)		{force = true; x = 0;}
    +			if (y < -Safe_mode_buffer || y		> max_h + Safe_mode_buffer)		{force = true; y = 0;}
    +			if (w <  Safe_mode_buffer || x + w	> max_w + Safe_mode_buffer)		{force = true; w = max_w - x;}
    +			if (h <  Safe_mode_buffer || y + h	> max_h + Safe_mode_buffer)		{force = true; h = max_h - y;}
    +			if (force)
    +				win.Rect_set(RectAdp_.new_(x, y, w, h));
    +		}
    +	}
    +	public static void Shutdown(Xoae_app app, GfuiWin win) {
    +		gplx.xowa.addons.apps.cfgs.Xocfg_mgr cfg_mgr = app.Cfg();
    +		if (String_.Eq(cfg_mgr.Get_str_app_or(Cfg__window_mode, "previous"), "previous")) {
    +			cfg_mgr.Set_str_app(Cfg__prev_rect			, win.Rect().Xto_str());
    +			cfg_mgr.Set_str_app(Cfg__prev_maximized		, Yn.To_str(win.Maximized()));
    +		}
    +		Xog_startup_tabs.Shutdown(app);
    +	}
    +	public static SizeAdp Screen_maximized_calc() {
    +		Op_sys os = Op_sys.Cur();
    +		SizeAdp screen = ScreenAdp_.screen_(0).Size();
    +		int w = screen.Width();
    +		int h = screen.Height() - 30;	// -20=application menu bar; -10 for start bar padding
    +		switch (os.Tid()) {
    +			case Op_sys.Tid_wnt:
    +				switch (os.Sub_tid()) {
    +					case Op_sys.Sub_tid_win_xp:	h += -4; break;	// NOOP; will keep values as above
    +					default: break;		// for all else, use Windows 7 border (which is thicker); note that Windows 8 is being detected as "Windows NT (unknown)", so need to use default; this may not work with Windows 2000
    +				}
    +				break;
    +			default:
    +				h += -4;	// default adjustments since version 0.0.0.0; seems to work on XP and LNX
    +				break;
    +		}
    +		return SizeAdp_.new_(w, h);
    +	}
    +	private static final String 
    +	  Cfg__window_mode		= "xowa.app.startup.window.mode"
    +	, Cfg__manual_rect		= "xowa.app.startup.window.manual_rect"
    +	, Cfg__manual_safe		= "xowa.app.startup.window.manual_safe"
    +	, Cfg__prev_rect		= "xowa.app.startup.window.previous_rect"
    +	, Cfg__prev_maximized	= "xowa.app.startup.window.previous_maximized"
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_tab_close_lnr.java b/400_xowa/src/gplx/xowa/guis/views/Xog_tab_close_lnr.java
    index a27517de8..6c01126e8 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_tab_close_lnr.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_tab_close_lnr.java
    @@ -13,3 +13,7 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public interface Xog_tab_close_lnr {
    +	boolean When_close(Xog_tab_itm tab, Xoa_url url);
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_tab_close_mgr.java b/400_xowa/src/gplx/xowa/guis/views/Xog_tab_close_mgr.java
    index a27517de8..910af1fde 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_tab_close_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_tab_close_mgr.java
    @@ -13,3 +13,19 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Xog_tab_close_mgr {
    +	private List_adp list = List_adp_.New();
    +	public void Clear() {list.Clear();}
    +	public void Add(Xog_tab_close_lnr lnr) {list.Add(lnr);} 
    +	public int Len() {return list.Count();} 
    +	public Xog_tab_close_lnr Get_at(int i) {return (Xog_tab_close_lnr)list.Get_at(i);}
    +	public boolean When_close(Xog_tab_itm tab, Xoa_url url) {
    +		int len = list.Count();
    +		for (int i = 0; i < len; ++i) {
    +			Xog_tab_close_lnr lnr = Get_at(i);
    +			if (!lnr.When_close(tab, url)) return false;
    +		}
    +		return true;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm.java b/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm.java
    index a27517de8..ceeb074a7 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm.java
    @@ -13,3 +13,242 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.core.threads.*; import gplx.core.envs.*;
    +import gplx.gfui.*; import gplx.gfui.ipts.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*;
    +import gplx.xowa.guis.history.*; import gplx.xowa.guis.bnds.*;
    +import gplx.xowa.files.*; import gplx.xowa.files.fsdb.*;
    +import gplx.xowa.langs.vnts.*;
    +import gplx.xowa.parsers.*; import gplx.xowa.wikis.pages.lnkis.*;
    +import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.skins.*;
    +public class Xog_tab_itm implements Gfo_invk {
    +	private Xog_win_itm win;
    +	public Xog_tab_itm(Xog_tab_mgr tab_mgr, Gfui_tab_itm_data tab_data, Xowe_wiki wiki, Xoae_page page) {
    +		this.tab_mgr = tab_mgr; this.tab_data = tab_data; this.wiki = wiki; this.page = page;
    +		this.win = tab_mgr.Win();
    +		this.html_itm = new Xog_html_itm(this);
    +		cmd_sync = win.Kit().New_cmd_sync(this);
    +	}
    +	public Xowe_wiki Wiki() {return wiki;} public void Wiki_(Xowe_wiki v) {this.wiki = v;} private Xowe_wiki wiki;
    +	public Gfo_invk Cmd_sync() {return cmd_sync;} private Gfo_invk cmd_sync;
    +	public void Make_html_box(int uid, Gfui_tab_itm tab_box, Xog_win_itm win, GfuiElem owner) {
    +		this.tab_box = tab_box;
    +		Xoae_app app = win.App(); Xoa_gui_mgr gui_mgr = win.Gui_mgr(); Gfui_kit kit = win.Kit();
    +		Gfui_html html_box	= kit.New_html("html_box" + Int_.To_str(uid), owner);
    +		html_box.Html_js_enabled_(tab_mgr.Javascript_enabled());
    +		html_box.Html_invk_src_(win);
    +		html_itm.Html_box_(html_box);
    +		if (app.Mode().Tid_is_gui()) {	// NOTE: only run for gui; will cause firefox_addon to fail; DATE:2014-05-03								
    +			// NOTE: must set source, else control will be empty, and key events will not be raised; DATE:2014-04-30
    +			// NOTE: use load_by_url for nightmode else load_by_mem causes "white flashing" DATE:2017-03-04
    +			if (app.Gui_mgr().Nightmode_mgr().Enabled()) {
    +				html_box.Html_doc_html_load_by_url
    +				( app.Usere().Fsys_mgr().App_temp_html_dir().GenSubFil("tab_new.html")
    +				, String_.new_u8(gplx.xowa.specials.xowa.default_tab.Default_tab_page.DEFAULT_HTML_NIGHT)
    +				);
    +			}
    +			else {
    +				html_box.Html_doc_html_load_by_mem("");
    +			}
    +			IptBnd_.ipt_to_(IptCfg_.Null, html_box, this, "popup", IptEventType_.MouseDown, IptMouseBtn_.Right);
    +			IptBnd_.cmd_to_(IptCfg_.Null, html_box, win, Xog_win_itm.Invk_exit, IptKey_.add_(IptKey_.Alt, IptKey_.F4));	// WORKAROUND:SWT: xulrunner_v24 no longer sends Alt+F4 to SwtShell; must manually subscribe it to quit; DATE:2015-07-31
    +			Gfo_evt_mgr_.Sub_same(html_box, GfuiElemKeys.Evt_menu_detected, html_itm);
    +			gui_mgr.Bnd_mgr().Bind(Xog_bnd_box_.Tid_browser_html, html_box);
    +			if (!Env_.Mode_testing())
    +				kit.Set_mnu_popup(html_box, gui_mgr.Menu_mgr().Popup().Html_page().Under_mnu());
    +		}
    +	}
    +	public void Switch_mem(Xog_tab_itm comp) {
    +		html_itm.Switch_mem(comp.html_itm);					// switch html_itm.owner_tab reference only
    +		Xog_html_itm temp_html_itm = html_itm;				// switch .html_itm, since the underlying CTabFolder has reparented the control
    +		this.html_itm = comp.html_itm;
    +		comp.html_itm = temp_html_itm;
    +
    +		Xoae_page temp_page = page;							// switch .page, since its underlying html_box has changed and .page must reflect html_box
    +		this.page = comp.page;
    +		comp.page = temp_page;
    +		page.Tab_data().Tab_(this); comp.Page().Tab_data().Tab_(comp);
    +
    +		byte temp_view_mode = view_mode;					// switch .view_mode to match .page
    +		this.view_mode = comp.view_mode;
    +		comp.view_mode = temp_view_mode;
    +
    +		Xog_history_mgr temp_history_mgr = history_mgr;		// switch .history_mgr along with .page
    +		this.history_mgr = comp.history_mgr;
    +		comp.history_mgr = temp_history_mgr;
    +	}
    +	public Gfui_tab_itm_data	Tab_data() {return tab_data;} private Gfui_tab_itm_data tab_data;
    +	public String				Tab_key() {return tab_data.Key();}
    +	public int					Tab_idx() {return tab_data.Idx();} public void Tab_idx_(int v) {tab_data.Idx_(v);}
    +	public Xog_tab_mgr			Tab_mgr() {return tab_mgr;} private Xog_tab_mgr tab_mgr;
    +	public Gfui_tab_itm			Tab_box() {return tab_box;} private Gfui_tab_itm tab_box;
    +	public boolean				Tab_is_loading() {return tab_is_loading;} private boolean tab_is_loading;
    +	public Xog_html_itm			Html_itm() {return html_itm;} private Xog_html_itm html_itm;
    +	public Gfui_html			Html_box() {return html_itm.Html_box();}
    +	public Xoae_page			Page() {return page;}
    +	public void Page_ref_(Xoae_page v) {
    +		this.page = v;
    +		this.wiki = page.Wikie();	// NOTE: must update wiki else back doesn't work; DATE:2015-03-05
    +	}
    +	public void Page_(Xoae_page page) {
    +		Page_ref_(page);
    +		this.Page_update_ui();	// force tab button to update when page changes
    +	}	private Xoae_page page;		
    +	public void Page_update_ui() {
    +		this.Tab_name_();
    +		tab_box.Tab_tip_text_(page.Url().To_str());
    +	}
    +	public void Tab_name_() {
    +		byte[] tab_name = page.Html_data().Custom_tab_name();				// Custom_tab_name set by Special:Default_tab or variants; DATE:2015-10-05
    +		if (tab_name == null) tab_name = page.Ttl().Full_txt_w_ttl_case();	// no custom_tab_name; use ttl's text
    +		Tab_name_(String_.new_u8(tab_name));
    +	}
    +	public void Tab_name_(String tab_name) {
    +		tab_name = Xog_tab_itm_.Tab_name_min(tab_name, tab_mgr.Btns__min_chars());
    +		tab_name = Xog_tab_itm_.Tab_name_max(tab_name, tab_mgr.Btns__max_chars());
    +		tab_box.Tab_name_(tab_name);
    +	}
    +	public Xog_history_mgr		History_mgr() {return history_mgr;} private Xog_history_mgr history_mgr = new Xog_history_mgr();
    +	public byte					View_mode() {return view_mode;} public Xog_tab_itm View_mode_(byte v) {view_mode = v; return this;} private byte view_mode = Xopg_page_.Tid_read;
    +	public void Pin_toggle() {}
    +	public void Show_url_bgn(Xoa_url url) {
    +		this.tab_is_loading = true;
    +		Xoae_app app = win.App(); Gfo_usr_dlg usr_dlg = app.Usr_dlg();
    +
    +		// get new_tab_name
    +		this.wiki = (Xowe_wiki)app.Wiki_mgr().Get_by_or_make_init_y(url.Wiki_bry());	// NOTE: must go before wiki.Props().Main_page(); DATE:2016-08-02; NOTE: must load wiki; DATE:2015-07-22
    +		if (url.Page_is_main()) url.Page_bry_(wiki.Props().Main_page());				// NOTE: must go before ttl.Make; DATE:2016-07-31
    +		Xoa_ttl ttl = wiki.Ttl_parse(url.Page_bry());
    +		if (ttl == null) {usr_dlg.Prog_one("", "", "title is invalid: ~{0}", String_.new_u8(url.Raw())); return;}
    +		String new_tab_name = String_.new_u8(ttl.Full_txt_w_ttl_case());
    +
    +		// if clicking on anchor, just scroll; do not load page
    +		if (	url.Anch_str() != null							// url has anchor
    +			&&	url.Eq_page(page.Url())							// url has same page_name as existing page
    +			&&	url.Qargs_ary().length == 0						// url has no args; needed for Category:A?from=b#mw-pages
    +			&&	String_.Eq(new_tab_name, tab_data.Name())		// NOTE: name will be null / empty when starting app and last session had page with #anchor; EX:Main_Page#Links; DATE:2016-07-21
    +			) {
    +			html_itm.Scroll_page_by_id_gui(url.Anch_str());	
    +			return;
    +		}
    +		if (win.Page__async__working(url)) return;
    +		if (page != null) page.Tab_data().Close_mgr().When_close(this, url);			// cancel any current search cmds
    +		app.Log_wtr().Queue_enabled_(true);
    +		usr_dlg.Gui_wkr().Clear();
    +		if (url.Vnt_bry() != null) {
    +			byte[] vnt = url.Vnt_bry();
    +			if (!Bry_.Eq(vnt, wiki.Lang().Vnt_mgr().Cur_itm().Key()))
    +				wiki.Appe().Cfg().Set_bry_wiki(wiki, Xowe_wiki.Cfg__variant__current, vnt);
    +		}
    +		Tab_name_(new_tab_name);
    +		usr_dlg.Prog_one("", "", "loading: ~{0}", String_.new_u8(ttl.Raw()));
    +		if (wiki.Html_mgr().Head_mgr().Popup_mgr().Enabled())
    +			this.Html_box().Html_js_eval_script("if (window.xowa_popups_hide_all != null) window.xowa_popups_hide_all();");	// should be more configurable; DATE:2014-07-09
    +		app.Thread_mgr_old().Page_load_mgr().Add_at_end(new Load_page_wkr(this, wiki, url, ttl)).Run();
    +	}
    +	private void Show_url_loaded(Load_page_wkr wkr) {
    +		Xowe_wiki wiki = wkr.Wiki(); Xoae_page page = wkr.Page();
    +		Xoa_url url = page.Url(); Xoa_ttl ttl = page.Ttl();
    +		Xoae_app app = wiki.Appe(); Gfo_usr_dlg usr_dlg = app.Usr_dlg();
    +		try {
    +			if (page.Tab_data().Cancel_show()) return;	// Special:Search canceled show; NOTE: must be inside try b/c finally handles thread
    +			wiki.Parser_mgr().Ctx().Page_(page);
    +			if (	page.Db().Page().Exists_n() 
    +				&& !page.Commons_mgr().Xowa_mockup()) {	// do not enter "missing" section if File_mockup; EX:en.wikipedia.org/wiki/File:Protoplanetary-disk.jpg DATE:2016-11-13
    +				if (wiki.Db_mgr().Save_mgr().Create_enabled()
    +					|| wiki.Page_mgr().Sync_mgr().Auto_enabled()) {
    +					page = Xoae_page.New_edit(wiki, ttl);
    +					view_mode = Xopg_page_.Tid_edit;
    +					history_mgr.Add(page);	// NOTE: must put new_page on stack so that pressing back will pop new_page, not previous page
    +					Xog_tab_itm_read_mgr.Show_page(this, page, false);
    +				}
    +				else {
    +					wkr.Page().Tab_data().Tab().Page_(page);	// NOTE: must set tab's page to current page, so that switching to it will update url bar; EX:pt.b:A"MANUAL_DE_PROCEDURI_.Sectiunea:""CONTABILITATE_SI_MANAGEMENT_FINANCIAR""" DATE:2015-09-17
    +					if (page.Redirect_trail().Itms__len() > 0)
    +						usr_dlg.Prog_many("", "", "could not find page in wiki: ~{0} (redirected from ~{1})", String_.new_u8(page.Url().Page_bry()), page.Redirect_trail().Itms__get_at_0th_or_null());
    +					else {
    +						if (ttl.Ns().Id_is_file())
    +							usr_dlg.Prog_one("", "", "commons.wikimedia.org must be installed in order to view the file. See [[App/Wiki_types/Commons]]: ~{0}", String_.new_u8(url.Raw()));// HOME
    +						else
    +							usr_dlg.Prog_one("", "", "could not find page in wiki: ~{0}", String_.new_u8(url.Raw()));
    +					}
    +				}
    +				app.Log_wtr().Queue_enabled_(false);
    +				return;
    +			}
    +			// if (!page.Redirected()) page.Url_(url);	// NOTE: handle redirect from commons; COMMENTED: part of redirect rewrite; DATE:2016-07-05
    +			if (page.Ttl().Anch_bgn() != Bry_find_.Not_found) page.Url().Anch_bry_(page.Ttl().Anch_txt());	// NOTE: occurs when page is a redirect to an anchor; EX: w:Duck race -> Rubber duck#Races
    +			history_mgr.Add(page);
    +			Xog_tab_itm_read_mgr.Show_page(this, page, true);
    +			if (app.Usere().History_mgr().Enabled()) {
    +				app.Usere().History_mgr().Add(page);
    +				app.User().User_db_mgr().History_mgr().Update_async(app.Async_mgr(), ttl, url);
    +			}
    +			usr_dlg.Prog_none("", "", "rendering html");
    +			// html_itm.Html_box().Size_(tab_mgr.Tab_mgr().Size()); // COMMENTED: causes clicks on macosx to be off by 4 px; NOTE: must resize tab here, else scrolling to anchor in background tab doesn't work (html_box has size of 0, 0) DATE:2015-05-03
    +			//	win.Page__async__bgn(this);
    +			Gfo_thread_wkr async_wkr = null;
    +			if (page.Html_data().Hdump_exists()) {
    +//					wiki.File_mgr().Init_file_mgr_by_load(wiki);
    +//					Xof_fsdb_mgr fsdb_mgr = wiki.File_mgr().Fsdb_mgr();
    +//					async_wkr = new Xof_file_wkr(wiki.File__orig_mgr(), fsdb_mgr.Bin_mgr(), fsdb_mgr.Mnt_mgr(), app.Usere().User_db_mgr().Cache_mgr(), wiki.File__repo_mgr(), html_itm, page, page.Hdump_mgr().Imgs());
    +				async_wkr = new Load_files_wkr(this);
    +				if (wiki.Html__hdump_enabled() && page.Db().Page().Html_db_id() == -1) {
    +					wiki.Html__hdump_mgr().Save_mgr().Save(page);
    +				}
    +			}
    +			else
    +				async_wkr = new Load_files_wkr(this);
    +
    +			page.Html_data().Mode_wtxt_shown_y_();
    +			app.Thread_mgr_old().File_load_mgr().Add_at_end(async_wkr).Run();
    +			// app.Thread_mgr().Get_by_or_new("on_page_load").Add(new Xopg_img_thread(), new Xopg_rl_thread());
    +		}
    +		finally {
    +			app.Thread_mgr_old().Page_load_mgr().Resume();
    +			this.tab_is_loading = false;
    +		}
    +	}
    +	public void Exec_async_hdump(Xoa_app app, Xow_wiki wiki, gplx.xowa.guis.cbks.js.Xog_js_wkr js_wkr, gplx.core.threads.Gfo_thread_pool thread_pool, Xoa_page page, List_adp imgs, int[] redlink_ary) {
    +		if (imgs.Count() > 0) {
    +			Xof_file_wkr file_thread = new Xof_file_wkr
    +				( wiki.File__orig_mgr(), wiki.File__bin_mgr(), wiki.File__mnt_mgr()
    +				, app.User().User_db_mgr().Cache_mgr(), wiki.File__repo_mgr(), html_itm, page, imgs
    +				);
    +			thread_pool.Add_at_end(file_thread); thread_pool.Run();
    +		}
    +		if (redlink_ary.length > 0) {
    +			Xog_redlink_thread redlink_thread = new Xog_redlink_thread(redlink_ary, js_wkr);
    +			thread_pool.Add_at_end(redlink_thread); thread_pool.Run();
    +		}
    +	}
    +	public void Exec_notify(boolean pass, String msg) {
    +		this.Html_box().Html_js_eval_proc_as_str("xowa.cmds.exec_by_str", "xowa.notify", "{\"text\":\"" + msg + "\",\"status\":\"" + (pass ? "success" : "error") + "\"}");
    +	}
    +	@gplx.Internal protected void Show_url_failed(Load_page_wkr wkr) {
    +		try {
    +			Xog_tab_itm_read_mgr.Show_page_err(win, this, wkr.Wiki(), wkr.Url(), wkr.Ttl(), wkr.Exec_err());
    +		} finally {
    +			wkr.Wiki().Appe().Thread_mgr_old().Page_load_mgr().Resume();
    +		}
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_show_url_loaded_swt))	this.Show_url_loaded((Load_page_wkr)m.ReadObj("v"));
    +		else if	(ctx.Match(k, Invk_show_url_failed_swt))	this.Show_url_failed((Load_page_wkr)m.ReadObj("v"));
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	public static final String Invk_show_url_loaded_swt = "show_url_loaded_swt", Invk_show_url_failed_swt = "show_url_failed_swt";
    +}
    +class Load_files_wkr implements Gfo_thread_wkr {
    +	private Xog_tab_itm tab;
    +	public Load_files_wkr(Xog_tab_itm tab) {this.tab = tab;}
    +	public String			Thread__name() {return "xowa.load_files_wkr";}
    +	public boolean			Thread__resume() {return true;}
    +	public void Thread__exec() {
    +		try {Xog_async_wkr.Async(tab);}
    +		catch (Exception e) {
    +			tab.Tab_mgr().Win().App().Usr_dlg().Warn_many("error while running file wkr; page=~{0} err=~{1}", tab.Page().Url().To_str(), Err_.Message_gplx_full(e));
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm_.java b/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm_.java
    index a27517de8..dae13bffd 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm_.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm_.java
    @@ -13,3 +13,16 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Xog_tab_itm_ {
    +	public static String Tab_name_min(String name, int min) {
    +		int name_len = String_.Len(name);
    +		return min == Tab_name_min_disabled || name_len > min ? name : name + String_.Repeat(" ", min - name_len);
    +	}
    +	public static String Tab_name_max(String name, int max) {
    +		int name_len = String_.Len(name);
    +		return max == Tab_name_max_disabled || name_len <= max ? name : String_.Mid(name, 0, max) + "...";
    +	}
    +	public static final int Tab_name_min_disabled = -1, Tab_name_max_disabled = -1;
    +	public static final    Xog_tab_itm Null = null;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm_edit_mgr.java b/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm_edit_mgr.java
    index a27517de8..ca75ddf0b 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm_edit_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm_edit_mgr.java
    @@ -13,3 +13,146 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.gfui.*; import gplx.gfui.controls.standards.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.htmls.*; import gplx.xowa.wikis.pages.*;
    +import gplx.xowa.wikis.nss.*;
    +import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*;
    +public class Xog_tab_itm_edit_mgr {
    +	public static void Save(Xog_tab_itm tab, boolean quick_save) {
    +		if (tab.View_mode() != Xopg_page_.Tid_edit) return;	// exit if not edit; handles ctrl+s being pressed in read/html modes
    +
    +		// get text and save directly to text_db
    +		Xoae_page page = tab.Page(); Xowe_wiki wiki = tab.Wiki(); Xog_win_itm win_itm = tab.Tab_mgr().Win();
    +		byte[] new_text = Get_new_text(tab, page.Db().Text().Text_bry());
    +		int page_id = page.Db().Page().Id();
    +		if (page.Edit_mode() == Xoa_page_.Edit_mode_create) {
    +			page_id = wiki.Db_mgr().Save_mgr().Data_create(page.Ttl(), new_text);
    +			page.Db().Page().Id_(page_id);
    +			page.Edit_mode_update_();	// set to update so that next save does not try to create
    +		}
    +		else {
    +			wiki.Db_mgr().Save_mgr().Data_update(page, new_text);
    +		}
    +		Invalidate(wiki);
    +		page.Db().Text().Text_bry_(new_text);
    +
    +		// refresh html
    +		wiki.Parser_mgr().Parse(page, true);
    +		if (wiki.Html__hdump_enabled()) wiki.Html__hdump_mgr().Save_mgr().Save(page);	// must go after wiki.Parse
    +
    +		// NOTE: show message after Parse, b/c Parse will flash "Loading page"; DATE:2014-05-17
    +		win_itm.Usr_dlg().Prog_many("", "", "saved ~{0} (~{1})"
    +			, String_.new_u8(page.Ttl().Full_txt_raw())
    +			, Datetime_now.Get().XtoStr_fmt("HH:mm:ss.fff")
    +			);
    +
    +		// full_save; save page and go to read mode
    +		if (!quick_save) {
    +			gplx.xowa.addons.wikis.pagebaks.Pagebaks_addon.On_page_saved(wiki.Appe(), wiki, page.Ttl(), new_text);
    +
    +			// update categories
    +			try {
    +				wiki.Html_mgr().Page_wtr_mgr().Gen(page, Xopg_page_.Tid_read); // NOTE: need to write html to fill Wtxt().Ctgs
    +				gplx.xowa.addons.wikis.ctgs.edits.Xoctg_edit_mgr.Update(wiki, page.Ttl().Page_db(), page_id, page.Wtxt().Ctgs__to_ary());
    +			} catch (Exception e) {
    +				Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to update categories; err=~{0}", Err_.Message_gplx_log(e));
    +			}
    +
    +			// TODO: save html copy
    +			//wiki.Db_mgr().Hdump_mgr().Save(page);
    +
    +			// parse page and show it
    +			page.Html_data().Edit_preview_(Bry_.Empty);
    +			Xoae_page stack_page = tab.History_mgr().Cur_page(wiki);		// NOTE: must be to CurPage() else changes will be lost when going Bwd,Fwd
    +			stack_page.Db().Text().Text_bry_(page.Db().Text().Text_bry());	// NOTE: overwrite with "saved" changes
    +			stack_page.Wikie().Parser_mgr().Parse(page, true);				// NOTE: must reparse page if (a) Edit -> Read; or (b) "Options" save
    +			page.Url_(Xoa_url.New(tab.Wiki().Domain_bry(), tab.Page().Ttl().Full_db()));
    +
    +			// force full reload of page; needed to refresh page_modified; DATE:2017-03-06
    +			tab.Show_url_bgn(page.Url());
    +//				win_itm.Page__mode_(Xopg_page_.Tid_read);
    +//				win_itm.Page__async__bgn(tab);
    +		}
    +	}
    +	public static void Preview(Xog_tab_itm tab) {
    +		if (tab.View_mode() != Xopg_page_.Tid_edit) return;	// exit if not edit; handles preview somehow being called?
    +		Xoae_page page = tab.Page(); Xowe_wiki wiki = tab.Wiki(); Xog_win_itm win_itm = tab.Tab_mgr().Win();
    +		Xog_html_itm html_itm = tab.Html_itm();
    +
    +		byte[] new_text = Get_new_text(tab, null);
    +		Xoae_page new_page = Xoae_page.New(wiki, page.Ttl());
    +		new_page.Db().Page().Id_(page.Db().Page().Id());	// NOTE: page_id needed for sqlite (was not needed for xdat)
    +		new_page.Db().Text().Text_bry_(new_text);
    +		wiki.Parser_mgr().Parse(new_page, true);			// refresh html
    +		tab.Page_(new_page); new_page.Tab_data().Tab_(tab);			// replace old page with new_page; DATE:2014-10-09
    +
    +		Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_m001();
    +		Xoh_page_wtr_wkr wkr = wiki.Html_mgr().Page_wtr_mgr().Wkr(Xopg_page_.Tid_read);
    +		wkr.Write_body(tmp_bfr, wiki.Parser_mgr().Ctx(), Xoh_wtr_ctx.Basic, new_page);
    +		byte[] new_html = tmp_bfr.To_bry_and_rls();
    +		new_page.Html_data().Edit_preview_(new_html);
    +
    +		Invalidate(wiki);
    +		win_itm.Page__mode_(Xopg_page_.Tid_edit);
    +		html_itm.Scroll_page_by_id_gui(Xog_html_itm.Elem_id__first_heading);// NOTE: was originally directly; changed to call on thread; DATE:2014-05-03
    +		win_itm.Page__async__bgn(tab);	// NOTE: needed to show images during preview; DATE:2014-06-21
    +	}
    +	public static void Rename(Xog_tab_itm tab) {
    +		if (tab.View_mode() != Xopg_page_.Tid_edit) return;	// exit if not edit; handles ctrl+r being pressed
    +		Xoae_page page = tab.Page(); Xowe_wiki wiki = tab.Wiki(); Xog_win_itm win_itm = tab.Tab_mgr().Win();
    +		if (Bry_.Eq(page.Ttl().Page_db(), wiki.Props().Main_page())) {
    +			win_itm.Usr_dlg().Warn_many("", "", "The Main Page cannot be renamed");
    +			win_itm.Kit().Ask_ok("", "", "The Main Page cannot be renamed");
    +			return;
    +		}
    +		byte[] new_text = Bry_.new_u8(tab.Html_itm().Get_elem_value(Xog_html_itm.Elem_id__xowa_edit_rename_box));
    +		if (Bry_.Len_eq_0(new_text)) return;		// no ttl given; exit
    +		new_text = Xoa_ttl.Replace_spaces(new_text);	// ttls cannot have spaces; only underscores
    +		Xoa_ttl new_ttl = Xoa_ttl.Parse(wiki, new_text);
    +		int new_ns_id = new_ttl.Ns().Id();
    +		if (new_ns_id != Xow_ns_.Tid__main) {
    +			win_itm.Usr_dlg().Warn_many("", "", "The new page name must remain in the same namespace");
    +			return;
    +		}
    +		wiki.Db_mgr().Save_mgr().Data_rename(page, new_ns_id, new_text);
    +		page.Ttl_(Xoa_ttl.Parse(wiki, Bry_.Add(page.Ttl().Ns().Name_db_w_colon(), new_text)));
    +		win_itm.Page__mode_(Xopg_page_.Tid_read);
    +		win_itm.Usr_dlg().Prog_one("", "", "renamed page to {0}", String_.new_u8(page.Ttl().Full_txt_raw()));
    +	}
    +	public static void Focus(Xog_win_itm win, String elem_focus_id) {
    +		Gfui_html html_box = win.Active_html_box();
    +		html_box.Focus();
    +		html_box.Html_js_eval_proc_as_str(Xog_js_procs.Doc__elem_focus, elem_focus_id);
    +	}
    +	public static void Debug(Xog_win_itm win, byte view_tid) {
    +		Xog_tab_itm tab = win.Tab_mgr().Active_tab(); Xoae_page page = tab.Page();
    +		Xowe_wiki wiki = tab.Wiki(); Xop_ctx ctx = wiki.Parser_mgr().Ctx();
    +		ctx.Defn_trace().Clear(); // TODO_OLD: move_me
    +		ctx.Defn_trace_(Xot_defn_trace_dbg.Instance);
    +		Xoa_ttl ttl = page.Ttl();
    +		Xoae_page new_page = Xoae_page.New(wiki, ttl);
    +		byte[] data = tab.Html_itm().Get_elem_value_for_edit_box_as_bry();
    +		new_page.Db().Text().Text_bry_(data);
    +		wiki.Parser_mgr().Parse(new_page, true);
    +		Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_m001();
    +		bfr.Add(new_page.Root().Root_src());
    +		wiki.Parser_mgr().Ctx().Defn_trace().Print(data, bfr);
    +		new_page.Db().Text().Text_bry_(bfr.To_bry_and_rls());
    +		byte old = tab.View_mode();
    +		tab.View_mode_(view_tid);
    +		Xog_tab_itm_read_mgr.Show_page(tab, new_page, false);
    +		tab.History_mgr().Add(new_page);
    +		tab.View_mode_(old);
    +	}
    +	private static void Invalidate(Xowe_wiki wiki) {// invalidate everything on updates; especially needed for page transclusion; {{/my_subpage}} DATE:2014-04-10
    +		wiki.Parser_mgr().Scrib().Core_term();
    +		wiki.Cache_mgr().Free_mem__all();
    +	}
    +	private static byte[] Get_new_text(Xog_tab_itm tab, byte[] orig) {
    +		byte[] rv = tab.Html_itm().Get_elem_value_for_edit_box_as_bry();
    +		if (orig != null)	// orig == null for Preview
    +			rv = tab.Wiki().Parser_mgr().Hdr__section_editable__mgr().Merge_section(tab.Page().Url(), rv, orig);
    +		rv = Bry_.Trim(rv, 0, rv.length, false, true, Bry_.Trim_ary_ws);	// MW: trim all trailing ws; REF:EditPage.php!safeUnicodeInput; DATE:2014-04-25
    +		return rv;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm_read_mgr.java b/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm_read_mgr.java
    index a27517de8..971a0ffcc 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm_read_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm_read_mgr.java
    @@ -13,3 +13,80 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.core.threads.*;
    +import gplx.gfui.*; import gplx.gfui.controls.standards.*; import gplx.xowa.guis.langs.*; import gplx.xowa.guis.history.*;
    +import gplx.xowa.wikis.pages.lnkis.*; import gplx.xowa.wikis.pages.*;
    +import gplx.xowa.guis.views.url_box_fmts.*;
    +public class Xog_tab_itm_read_mgr {
    +	public static void Show_page(Xog_tab_itm tab, Xoae_page new_page, boolean reset_to_read) {Show_page(tab, new_page, reset_to_read, false, false, Xog_history_stack.Nav_fwd);}
    +	public static void Show_page(Xog_tab_itm tab, Xoae_page new_page, boolean reset_to_read, boolean new_page_is_same, boolean show_is_err, byte history_nav_type) {
    +		if (reset_to_read)
    +			tab.View_mode_(Xopg_page_.Tid_read);
    +		if (new_page.Url().Qargs_mgr().Match(Xoa_url_.Qarg__action, Xoa_url_.Qarg__action__edit)) 
    +			tab.View_mode_(Xopg_page_.Tid_edit);
    +
    +		Xoae_page cur_page = tab.Page(); Xog_html_itm html_itm = tab.Html_itm(); Gfui_html html_box = html_itm.Html_box();
    +		Xog_win_itm win = tab.Tab_mgr().Win();
    +		if (cur_page != null && !new_page_is_same) {	// if new_page_is_same, don't update DocPos; will "lose" current position
    +			cur_page.Html_data().Bmk_pos_(html_box.Html_js_eval_proc_as_str(Xog_js_procs.Win__vpos_get));
    +			tab.History_mgr().Update_html_doc_pos(cur_page, history_nav_type);	// HACK: old_page is already in stack, but need to update its hdoc_pos
    +		}
    +		win.Usr_dlg().Prog_none("", "", "locating images");
    +		Xowe_wiki wiki = new_page.Wikie();
    +		try	{tab.Html_itm().Show(new_page);}
    +		catch (Exception e) {
    +			if (String_.Eq(Err_.Message_lang(e), "class org.eclipse.swt.SWTException Widget is disposed")) return; // ignore errors caused by user closing tab early; DATE:2014-07-26
    +			if (show_is_err) {	// trying to show error page, but failed; don't show again, else recursion until out of memory; TODO_OLD:always load error page; no reason it should fail; WHEN:html_skin; DATE:2014-06-08
    +				Gfo_usr_dlg_.Instance.Warn_many("", "", "fatal error trying to load error page; page=~{0} err=~{1}" + new_page.Url().To_str(), Err_.Message_gplx_full(e));
    +				return;
    +			}
    +			else
    +				Show_page_err(win, tab, wiki, new_page.Url(), new_page.Ttl(), e);
    +			return;
    +		}
    +		tab.Page_(new_page);
    +		if (tab == tab.Tab_mgr().Active_tab())
    +			Update_selected_tab(win, new_page.Url(), new_page.Ttl());
    +		Xol_font_info lang_font = wiki.Lang().Gui_font();
    +		if (lang_font.Name() == null) lang_font = win.Gui_mgr().Win_cfg().Font();
    +		Xog_win_itm_.Font_update(win, lang_font);
    +		tab.Tab_mgr().Tab_mgr().Focus();
    +		html_box.Focus();
    +		win.Usr_dlg().Prog_none("", "", "");	// blank out status bar
    +		if (tab.View_mode() == Xopg_page_.Tid_read)
    +			html_itm.Scroll_page_by_bmk_gui();
    +		else
    +			Gfo_invk_.Invk_by_val(tab.Html_itm().Cmd_async(), Xog_html_itm.Invk_html_elem_focus, Xog_html_itm.Elem_id__xowa_edit_data_box);	// NOTE: must be async, else won't work; DATE:2014-06-05
    +	}
    +	public static void Update_selected_tab_blank(Xog_win_itm win) {Update_selected_tab(win, null, null);} // called when all tabs are null
    +	public static void Update_selected_tab(Xog_win_itm win, Xoa_url url, Xoa_ttl ttl) {
    +		String url_str = "", win_str = Win_text_blank;
    +		if (url != null && ttl != null) {
    +			url_str = url.To_str();
    +			win_str = String_.new_u8(Bry_.Add(ttl.Full_txt_w_ttl_case(), Win_text_suffix_page));
    +
    +			// fmt to url if set
    +			Xog_urlfmtr_mgr url_box_fmtr = win.Url_box_fmtr();
    +			if (url_box_fmtr.Exists()) {
    +				url_str = url_box_fmtr.Gen(url);
    +			}
    +		}
    +		win.Url_box().Text_(url_str);
    +		win.Win_box().Text_(win_str);
    +	}
    +	private static final    byte[] Win_text_suffix_page = Bry_.new_a7(" - XOWA"); private static final String Win_text_blank = "XOWA";
    +	public static void Show_page_err(Xog_win_itm win, Xog_tab_itm tab, Xowe_wiki wiki, Xoa_url url, Xoa_ttl ttl, Exception e) {
    +		String err_msg = String_.Format("page_load fail: page={0} err={1}", String_.new_u8(url.Raw()), Err_.Message_gplx_full(e));
    +		win.Usr_dlg().Warn_many("", "", err_msg);
    +		win.App().Log_wtr().Queue_enabled_(false);
    +		Xoae_page fail_page = wiki.Data_mgr().Load_page_by_ttl(ttl);
    +		tab.View_mode_(Xopg_page_.Tid_edit);
    +		Update_selected_tab(win, url, ttl);
    +		Show_page(tab, fail_page, false, false, true, Xog_history_stack.Nav_fwd);
    +		win.Win_box().Text_(err_msg);
    +	}
    +	public static void Launch(Xog_win_itm win) {
    +		Xog_launcher_tabs.Instance.Launch(win);
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_tab_mgr.java b/400_xowa/src/gplx/xowa/guis/views/Xog_tab_mgr.java
    index a27517de8..3d61a78d7 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_tab_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_tab_mgr.java
    @@ -13,3 +13,264 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.gfui.*; import gplx.gfui.kits.core.*; import gplx.gfui.draws.*; import gplx.gfui.controls.gxws.*; import gplx.gfui.controls.tabs.*; import gplx.gfui.controls.standards.*;
    +import gplx.xowa.apps.apis.xowa.gui.browsers.*; import gplx.xowa.specials.*;
    +import gplx.xowa.apps.urls.*;
    +public class Xog_tab_mgr implements Gfo_evt_itm {
    +	private Ordered_hash tab_regy = Ordered_hash_.New(); private int tab_uid = 0;
    +	private boolean btns__hide_if_one; private int btns__height;
    +	public Xog_tab_mgr(Xog_win_itm win) {
    +		this.win = win;
    +		ev_mgr = new Gfo_evt_mgr(this);
    +	}
    +	public Gfo_evt_mgr Evt_mgr() {return ev_mgr;} private Gfo_evt_mgr ev_mgr;
    +	public Xog_win_itm Win() {return win;} private Xog_win_itm win;
    +	public Gfui_tab_mgr Tab_mgr() {return tab_mgr;} private Gfui_tab_mgr tab_mgr;
    +	public int Btns__min_chars() {return btns__min_chars;} private int btns__min_chars;
    +	public int Btns__max_chars() {return btns__max_chars;} private int btns__max_chars;
    +	public boolean Javascript_enabled() {return javascript_enabled;} private boolean javascript_enabled = true;
    +	private byte page_load_mode;
    +	public boolean Page_load_mode_is_url() {return page_load_mode == Gxw_html_load_tid_.Tid_url;}
    +	public void Init_by_kit(Gfui_kit kit) {
    +		tab_mgr = kit.New_tab_mgr("xowa.tab_mgr", win.Win_box());
    +		active_tab = Xog_tab_itm_.Null;
    +		Gfo_evt_mgr_.Sub_same_many(tab_mgr, this, Gfui_tab_mgr.Evt_tab_selected, Gfui_tab_mgr.Evt_tab_closed, Gfui_tab_mgr.Evt_tab_switched);			
    +		win.App().Cfg().Bind_many_app(this, Cfg__page_load_mode
    +		, Cfg__place_on_top, Cfg__height, Cfg__hide_if_one, Cfg__curved, Cfg__close_btn_visible, Cfg__unselected_close_btn_visible, Cfg__max_chars, Cfg__min_chars);
    +	}
    +	public Xog_tab_itm Active_tab() {return active_tab;} private Xog_tab_itm active_tab;
    +	public Xog_tab_itm Active_tab_assert() {
    +		if (active_tab == Xog_tab_itm_.Null) this.Tabs_new_dflt(true);
    +		return active_tab;
    +	}
    +	public boolean Active_tab_is_null() {return active_tab == Xog_tab_itm_.Null;}
    +	private void Btns_text_recalc() {
    +		int len = this.Tabs_len();
    +		for (int i = 0; i < len; i++) {
    +			Xog_tab_itm tab_itm = this.Tabs_get_at(i);
    +			tab_itm.Tab_name_();
    +		}
    +	}
    +	public int Tabs_len() {return tab_regy.Count();}
    +	public Xog_tab_itm Tabs_new_init(Xowe_wiki wiki, Xoae_page page) {return this.Tabs_new(true, true, wiki, page);}
    +	public Xog_tab_itm Tabs_get_at(int i) {return (Xog_tab_itm)tab_regy.Get_at(i);}
    +	public Xog_tab_itm Tabs_new_dflt() {return Tabs_new_dflt(false);}
    +	public Xog_tab_itm Tabs_new_dflt(boolean focus) {
    +		boolean active_tab_is_null = this.Active_tab_is_null();
    +		Xowe_wiki cur_wiki = active_tab_is_null ? win.App().Usere().Wiki() : active_tab.Wiki();
    +		Xoa_ttl ttl = Xoa_ttl.Parse(cur_wiki, Xow_special_meta_.Itm__default_tab.Ttl_bry());
    +		Xoa_url url = cur_wiki.Utl__url_parser().Parse_by_urlbar_or_null(ttl.Full_db_as_str()); if (url == null) throw Err_.new_("url", "invalid url", "url", url);
    +		Xog_tab_itm rv = Tabs_new(focus, active_tab_is_null, cur_wiki, Xoae_page.New(cur_wiki, ttl));
    +		rv.Page_update_ui();
    +		rv.Show_url_bgn(url);
    +		return rv;
    +	}
    +	private Xog_tab_itm Tabs_new(boolean focus, boolean active_tab_is_null, Xowe_wiki wiki, Xoae_page page) {
    +		String tab_key = "tab_" + Int_.To_str(tab_uid++); int tab_idx = tab_regy.Count();
    +		Gfui_tab_itm_data tab_data = new Gfui_tab_itm_data(tab_key, tab_idx);
    +		Xog_tab_itm rv = new Xog_tab_itm(this, tab_data, wiki, page);
    +		Gfui_tab_itm tab_box = tab_mgr.Tabs_add(tab_data);
    +		rv.Make_html_box(tab_uid, tab_box, win, tab_mgr);
    +		rv.Html_itm().Js_enabled_(javascript_enabled);
    +		tab_box.Subs_add(rv.Html_itm().Html_box());
    +		tab_regy.Add(tab_key, rv);
    +		if (	focus
    +			||	active_tab_is_null // NOTE: must select 1st tab, else nothing will show in tab box
    +			) {
    +			tab_mgr.Tabs_select_by_idx(rv.Tab_idx());
    +			active_tab = rv;
    +		}
    +		Tabs_hide_if_one_chk(false);
    +		return rv;
    +	}
    +	public void Tabs_new_dupe(boolean focus) {
    +		if (this.Active_tab_is_null()) return;
    +		String url = active_tab.Page().Url().To_str();
    +		Tabs_new_dflt(focus);
    +		win.Page__navigate_by_url_bar(url);
    +	}
    +	public void Tabs_javascript_enabled_(boolean v) {
    +		this.javascript_enabled = v;
    +		int len = tab_regy.Count();
    +		for (int i = 0; i < len; i++) {
    +			Xog_tab_itm tab = Tabs_get_by_idx_or_warn(i);
    +			tab.Html_itm().Js_enabled_(v);
    +		}
    +	}
    +	private void Tabs_selected(String key) {
    +		Xog_tab_itm tab = Tabs_get_by_key_or_warn(key); if (tab == null) return;
    +		active_tab = tab;
    +		Xoae_page page = tab.Page();
    +		Xog_tab_itm_read_mgr.Update_selected_tab(win, page.Url(), page.Ttl());
    +		tab.Html_itm().Tab_selected(page);
    +	}
    +	public void Tabs_close_cur() {
    +		if (this.Active_tab_is_null()) return;
    +		Tabs__pub_close(active_tab);
    +		tab_mgr.Tabs_close_by_idx(active_tab.Tab_idx());
    +		Xog_tab_itm cur_tab = this.Active_tab();			// get new current tab for line below
    +		if (cur_tab != null) cur_tab.Html_box().Focus();	// NOTE: needed to focus tab box else tab button will be focused; DATE:2014-07-13
    +	}
    +	public void Tabs_close_others() {this.Tabs_close_to_bgn(); this.Tabs_close_to_end();}
    +	public void Tabs_close_to_bgn() {if (Active_tab_is_null()) return; Tabs_close_rng(0							, active_tab.Tab_idx());}
    +	public void Tabs_close_to_end() {if (Active_tab_is_null()) return; Tabs_close_rng(active_tab.Tab_idx() + 1	, tab_regy.Count());}
    +	public void Tabs_close_rng(int bgn, int end) {
    +		for (int i = bgn; i < end; i++) {
    +			Xog_tab_itm tab = Tabs_get_at(bgn);
    +			if (!Tabs__pub_close(tab)) return;
    +		}
    +		for (int i = bgn; i < end; i++)
    +			tab_mgr.Tabs_close_by_idx(bgn);	// NOTE: close at bgn, not at i, b/c each close will remove a tab from collection
    +	}
    +	public boolean Tabs__pub_close_all() {return Tabs__pub_close_rng(0, this.Tabs_len());}
    +	public boolean Tabs__pub_close_rng(int bgn, int end) {
    +		boolean rv = true;
    +		for (int i = bgn; i < end; i++) {
    +			Xog_tab_itm tab = Tabs_get_at(i);
    +			boolean close_allowed = Tabs__pub_close(tab);
    +			if (!close_allowed) rv = false;
    +		}
    +		return rv;
    +	}
    +	public boolean Tabs__pub_close(Xog_tab_itm tab) {
    +		return tab.Page().Tab_data().Close_mgr().When_close(tab, Xoa_url.Null);
    +	}
    +	public void Tabs_close_undo() {
    +		if (closed_undo_list.Count() == 0) return;
    +		String url = (String)List_adp_.Pop(closed_undo_list);
    +		Tabs_new_dflt(true);
    +		win.Page__navigate_by_url_bar(url);
    +	}
    +	private List_adp closed_undo_list = List_adp_.New();
    +	private void Tabs_closed(String key) {
    +		Xog_tab_itm itm = Tabs_get_by_key_or_warn(key); if (itm == null) return;
    +		itm.Html_box().Html_dispose();
    +		closed_undo_list.Add(itm.Page().Url().To_str());
    +		tab_regy.Del(key);
    +		if (tab_regy.Count() == 0) {
    +			active_tab = Xog_tab_itm_.Null;
    +			Xog_tab_itm_read_mgr.Update_selected_tab_blank(win);
    +		}
    +		else
    +			Tabs_recalc_idx();
    +		Tabs_hide_if_one_chk(false);
    +	}
    +	private Xog_tab_itm Tabs_get_by_key_or_warn(String key) {
    +		Xog_tab_itm rv = (Xog_tab_itm)tab_regy.Get_by(key); if (rv == null) win.App().Usr_dlg().Warn_many("", "", "tab.selected could not find tab; key={0}", key);
    +		return rv;
    +	}
    +	private Xog_tab_itm Tabs_get_by_idx_or_warn(int idx) {
    +		Xog_tab_itm rv = (Xog_tab_itm)tab_regy.Get_at(idx); if (rv == null) win.App().Usr_dlg().Warn_many("", "", "tab.selected could not find tab; idx={0}", idx);
    +		return rv;
    +	}
    +	private void Tabs_recalc_idx() {
    +		int len = tab_regy.Count();
    +		for (int i = 0; i < len; i++) {
    +			Xog_tab_itm itm = Tabs_get_by_idx_or_warn(i);
    +			itm.Tab_idx_(i);
    +		}
    +	}
    +	public void Tabs_select(boolean fwd) {
    +		if (this.Active_tab_is_null()) return;
    +		int new_idx = TabBox_.Cycle(fwd, active_tab.Tab_idx(), tab_regy.Count());
    +		tab_mgr.Tabs_select_by_idx(new_idx);
    +	}
    +	public void Tabs_select_by_idx(int v) {
    +		if (v < 0 || v >= tab_regy.Count()) return;
    +		tab_mgr.Tabs_select_by_idx(v);
    +	}
    +	public void Tabs_move(boolean fwd) {
    +		if (this.Active_tab_is_null()) return;
    +		int src_idx = active_tab.Tab_idx();
    +		int trg_idx = TabBox_.Cycle(fwd, src_idx, tab_regy.Count());
    +		tab_mgr.Tabs_switch(src_idx, trg_idx);
    +	}
    +	private void Tabs_switched(String src_key, String trg_key) {
    +		Xog_tab_itm src_itm = Tabs_get_by_key_or_warn(src_key);
    +		Xog_tab_itm trg_itm = Tabs_get_by_key_or_warn(trg_key);
    +		src_itm.Switch_mem(trg_itm);
    +		active_tab = trg_itm;	// NOTE: src_itm initiated switch, but trg_itm is now active b/c everything in src_itm has now been reparented to trg_itm; DATE:2014-05-12
    +	}
    +	public void Tabs_new_link(boolean focus, String link) {	// handle empty link
    +		if (String_.Len_eq_0(link)) {
    +			if (this.Active_tab_is_null()) return;
    +			link = gplx.langs.htmls.encoders.Gfo_url_encoder_.Http_url.Decode_str(active_tab.Html_itm().Html_selected_get_active_or_selection());	// NOTE: must decode else url-encoded special pages don't work; EX:home/wiki/Special:XowaCfg%3Fgrp%3Dxowa.html.css; DATE:2017-01-02
    +		}
    +		if (String_.Len_eq_0(link)) {win.App().Usr_dlg().Prog_many("", "", "no link or text selected"); return;}
    +		Tabs_new_link(link, focus);
    +	}
    +	public void Tabs_new_link(String link, boolean focus) {
    +		Xowe_wiki wiki = active_tab.Wiki();
    +		Xog_tab_itm new_tab = Tabs_new(focus, false, wiki, Xoae_page.New(wiki, active_tab.Page().Ttl()));	// NOTE: do not use ttl from link, else middle-clicking pages with anchors won't work; DATE:2015-05-03
    +		Xoa_url url = wiki.Utl__url_parser().Parse_by_urlbar_or_null(link);	if (url == null) return; // NOTE: link must be of form domain/wiki/page; DATE:2014-05-27			
    +		new_tab.Show_url_bgn(url);
    +		if (focus)
    +			tab_mgr.Tabs_select_by_idx(new_tab.Tab_idx());
    +	}
    +	private void Tabs_hide_if_one_chk(boolean force) {
    +		if (btns__hide_if_one || force) {// run code only if enabled or forced
    +			if (tab_regy.Count() == 1) {
    +				int desired_height = btns__hide_if_one ? 0 : btns__height;
    +				if (tab_mgr.Btns_height() != desired_height)
    +					tab_mgr.Btns_height_(desired_height);
    +			}
    +			else {
    +				if (tab_mgr.Btns_height() != btns__height)
    +					tab_mgr.Btns_height_(btns__height);
    +			}
    +		}
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_tabs_new_dflt__at_dflt__focus_y))						Tabs_new_dflt(Bool_.Y);
    +		else if	(ctx.Match(k, Invk_tabs_new_link__at_dflt__focus_n))						Tabs_new_link(Bool_.N, m.ReadStrOr("v", null));
    +		else if	(ctx.Match(k, Invk_tabs_new_link__at_dflt__focus_y))						Tabs_new_link(Bool_.Y, m.ReadStrOr("v", null));
    +		else if	(ctx.Match(k, Gfui_tab_mgr.Evt_tab_selected))								Tabs_selected(m.ReadStr("key"));
    +		else if	(ctx.Match(k, Gfui_tab_mgr.Evt_tab_closed))									Tabs_closed(m.ReadStr("key"));
    +		else if	(ctx.Match(k, Gfui_tab_mgr.Evt_tab_switched))								Tabs_switched(m.ReadStr("src"), m.ReadStr("trg"));
    +		else if	(ctx.Match(k, Invk_tabs_close_cur))											Tabs_close_cur();
    +		else if	(ctx.Match(k, Invk_tabs_select_bwd))										Tabs_select(Bool_.N);
    +		else if	(ctx.Match(k, Invk_tabs_select_fwd))										Tabs_select(Bool_.Y);
    +		else if	(ctx.Match(k, Invk_tabs_switch_cur_bwd))									Tabs_move(Bool_.N);
    +		else if	(ctx.Match(k, Invk_tabs_switch_cur_fwd))									Tabs_move(Bool_.Y);
    +
    +		else if	(ctx.Match(k, Cfg__place_on_top))											tab_mgr.Btns_place_on_top_(m.ReadYn("v"));
    +		else if	(ctx.Match(k, Cfg__height))													{btns__height = m.ReadInt("v"); tab_mgr.Btns_height_(btns__height);}
    +		else if	(ctx.Match(k, Cfg__hide_if_one))											{btns__hide_if_one = m.ReadYn("v"); Tabs_hide_if_one_chk(true);}
    +		else if	(ctx.Match(k, Cfg__curved))													tab_mgr.Btns_curved_(m.ReadYn("v"));
    +		else if	(ctx.Match(k, Cfg__close_btn_visible))										tab_mgr.Btns_close_visible_(m.ReadYn("v"));
    +		else if	(ctx.Match(k, Cfg__unselected_close_btn_visible))							tab_mgr.Btns_unselected_close_visible_(m.ReadYn("v"));
    +		else if	(ctx.Match(k, Cfg__max_chars))												{btns__max_chars = m.ReadInt("v"); Btns_text_recalc();}
    +		else if	(ctx.Match(k, Cfg__min_chars))												{btns__min_chars = m.ReadInt("v"); Btns_text_recalc();}
    +
    +		else if	(ctx.Match(k, Cfg__javascript_enabled))										Tabs_javascript_enabled_(m.ReadYnOrY("v"));	// NOTE: must be "OrY" else broken cfg.db will break cfg_maint; DATE:2016-12-15
    +		else if	(ctx.Match(k, Cfg__page_load_mode))											Page_load_mode_(m.ReadStr("v"));
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	private void Page_load_mode_(String v) {
    +		page_load_mode = Gxw_html_load_tid_.Xto_tid(v);
    +		// Gfo_evt_mgr_.Pub_val(this, Evt_load_tid_changed, load_tid);
    +	}
    +
    +	public static final String
    +	  Invk_tabs_select_fwd		= "tabs_select_fwd"		, Invk_tabs_select_bwd = "tabs_select_bwd"
    +	, Invk_tabs_switch_cur_fwd	= "tabs_switch_cur_fwd"	, Invk_tabs_switch_cur_bwd = "tabs_switch_cur_bwd"
    +	, Invk_tabs_new_dflt__at_dflt__focus_y = "tabs_new_dflt__at_dflt__focus_y"
    +	, Invk_tabs_new_link__at_dflt__focus_n = "tabs_new_link__at_dflt__focus_n"
    +	, Invk_tabs_new_link__at_dflt__focus_y = "tabs_new_link__at_dflt__focus_y"
    +    , Invk_tabs_close_cur		= "tabs_close_cur"
    +	;
    +	private static final    String 
    +	  Cfg__place_on_top						= "xowa.gui.tabs.place_on_top"
    +	, Cfg__height							= "xowa.gui.tabs.height"
    +	, Cfg__hide_if_one						= "xowa.gui.tabs.hide_if_one"
    +	, Cfg__curved							= "xowa.gui.tabs.curved"
    +	, Cfg__close_btn_visible				= "xowa.gui.tabs.close_btn_visible"
    +	, Cfg__unselected_close_btn_visible		= "xowa.gui.tabs.unselected_close_btn_visible"
    +	, Cfg__max_chars						= "xowa.gui.tabs.max_chars"
    +	, Cfg__min_chars						= "xowa.gui.tabs.min_chars"
    +	, Cfg__javascript_enabled				= "xowa.gui.html_box.javascript_enabled"
    +	, Cfg__page_load_mode					= "xowa.gui.html_box.page_load_mode"
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_win_itm.java b/400_xowa/src/gplx/xowa/guis/views/Xog_win_itm.java
    index a27517de8..96149fe96 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_win_itm.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_win_itm.java
    @@ -13,3 +13,409 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.core.threads.*; import gplx.core.envs.*;
    +import gplx.gfui.*; import gplx.gfui.draws.*; import gplx.gfui.kits.core.*; import gplx.gfui.controls.windows.*; import gplx.gfui.controls.standards.*;
    +import gplx.xowa.guis.*; import gplx.xowa.guis.history.*; import gplx.xowa.guis.langs.*; import gplx.xowa.guis.urls.*; import gplx.xowa.guis.views.*; 
    +import gplx.gfui.layouts.swts.*;
    +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*;
    +import gplx.xowa.wikis.pages.*; import gplx.xowa.apps.urls.*; import gplx.xowa.files.*;
    +import gplx.xowa.htmls.hrefs.*;
    +import gplx.xowa.wikis.pages.lnkis.*; import gplx.xowa.specials.*; import gplx.xowa.xtns.math.*; 	
    +import gplx.xowa.guis.views.url_box_fmts.*;
    +public class Xog_win_itm implements Gfo_invk, Gfo_evt_itm {
    +	private Gfo_invk sync_cmd;
    +	private Xog_url_box__selection_changed url_box__selection_changed;
    +	public Xog_win_itm(Xoae_app app, Xoa_gui_mgr gui_mgr) {
    +		this.app = app; this.gui_mgr = gui_mgr;
    +		this.tab_mgr = new Xog_tab_mgr(this);
    +	}
    +	public Gfui_kit			Kit() {return kit;} private Gfui_kit kit;
    +	public Xoa_gui_mgr		Gui_mgr() {return gui_mgr;} private Xoa_gui_mgr gui_mgr;
    +
    +	public GfuiWin          Win_box()            {return win_box;}            private GfuiWin        win_box;
    +	public Gfui_grp         Toolbar_grp()        {return toolbar_grp;}        private Gfui_grp       toolbar_grp;
    +	public GfuiBtn          Go_bwd_btn()         {return go_bwd_btn;}         private GfuiBtn        go_bwd_btn;
    +	public GfuiBtn          Go_fwd_btn()         {return go_fwd_btn;}         private GfuiBtn        go_fwd_btn;
    +	public GfuiComboBox     Url_box()            {return url_box;}            private GfuiComboBox   url_box;
    +	public GfuiBtn          Url_exec_btn()       {return url_exec_btn;}       private GfuiBtn        url_exec_btn;
    +	public GfuiTextBox      Search_box()         {return search_box;}         private GfuiTextBox    search_box;
    +	public GfuiBtn          Search_exec_btn()    {return search_exec_btn;}    private GfuiBtn        search_exec_btn;
    +	public GfuiTextBox      Allpages_box()       {return allpages_box;}       private GfuiTextBox    allpages_box;
    +	public GfuiBtn          Allpages_exec_btn()  {return allpages_exec_btn;}  private GfuiBtn        allpages_exec_btn;
    +	public GfuiTextBox      Find_box()           {return find_box;}           private GfuiTextBox    find_box;
    +	public Gfui_grp         Statusbar_grp()      {return statusbar_grp;}      private Gfui_grp       statusbar_grp;
    +	public GfuiBtn          Find_close_btn()     {return find_close_btn;}     private GfuiBtn        find_close_btn;
    +	public GfuiBtn          Find_fwd_btn()       {return find_fwd_btn;}       private GfuiBtn        find_fwd_btn;
    +	public GfuiBtn          Find_bwd_btn()       {return find_bwd_btn;}       private GfuiBtn        find_bwd_btn;
    +	public GfuiTextBox      Prog_box()           {return prog_box;}           private GfuiTextBox    prog_box;
    +	public GfuiTextBox      Info_box()           {return info_box;}           private GfuiTextBox    info_box;
    +
    +	public Gfo_evt_mgr		Evt_mgr() {if (evt_mgr == null) evt_mgr = new Gfo_evt_mgr(this); return evt_mgr;} private Gfo_evt_mgr evt_mgr;
    +	public Xoae_app			App()				{return app;} private Xoae_app app;
    +	public Xog_tab_mgr		Tab_mgr()			{return tab_mgr;} private Xog_tab_mgr tab_mgr;
    +	public Xog_tab_itm		Active_tab()		{return tab_mgr.Active_tab();}
    +	public Xoae_page		Active_page()		{return tab_mgr.Active_tab().Page();} public void Active_page_(Xoae_page v) {tab_mgr.Active_tab().Page_(v);}
    +	public Xowe_wiki		Active_wiki()		{return tab_mgr.Active_tab().Wiki();}
    +	public Xog_html_itm		Active_html_itm()	{return tab_mgr.Active_tab().Html_itm();}
    +	public Gfui_html		Active_html_box()	{return tab_mgr.Active_tab().Html_itm().Html_box();}
    +	public Gfo_usr_dlg		Usr_dlg() {return app.Usr_dlg();}
    +	public Xog_urlfmtr_mgr  Url_box_fmtr() {return url_box_fmtr;} private final    Xog_urlfmtr_mgr url_box_fmtr = new Xog_urlfmtr_mgr();
    +	public Xog_win_itm_cfg	Cfg() {return cfg;} private final    Xog_win_itm_cfg cfg = new Xog_win_itm_cfg();
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_link_click))								Win__link_click();
    +		else if	(ctx.Match(k, Invk_link_print))								Xog_win_itm__prog_href_mgr.Print(this);
    +		else if	(ctx.Match(k, Gfui_html.Evt_location_changed))				Win__link_clicked(m.ReadStr("v"));
    +		else if	(ctx.Match(k, Gfui_html.Evt_location_changing))				Page__navigate_by_href(tab_mgr.Active_tab(), Xoh_href_gui_utl.Standardize_xowa_link(m.ReadStr("v")));
    +		else if (ctx.Match(k, Invk_page_refresh))							Page__refresh();
    +		else if	(ctx.Match(k, Invk_page_async_exec))						Xog_async_wkr.Async(((Xog_tab_itm)m.ReadObj("v")));
    +		else if	(ctx.Match(k, Invk_page_view_read))							Page__mode_(Xopg_page_.Tid_read);
    +		else if	(ctx.Match(k, Invk_page_view_edit))							Page__mode_edit_();
    +		else if	(ctx.Match(k, Invk_page_view_html))							Page__mode_(Xopg_page_.Tid_html);
    +		else if (ctx.Match(k, Invk_page_edit_save))							Xog_tab_itm_edit_mgr.Save(tab_mgr.Active_tab(), Bool_.N);
    +		else if (ctx.Match(k, Invk_page_edit_save_draft))					Xog_tab_itm_edit_mgr.Save(tab_mgr.Active_tab(), Bool_.Y);
    +		else if (ctx.Match(k, Invk_page_edit_preview))						Xog_tab_itm_edit_mgr.Preview(tab_mgr.Active_tab());
    +		else if (ctx.Match(k, Invk_page_edit_rename))						Xog_tab_itm_edit_mgr.Rename(tab_mgr.Active_tab());
    +		else if	(ctx.Match(k, Invk_page_edit_focus_box)) 					Xog_tab_itm_edit_mgr.Focus(this, Xog_html_itm.Elem_id__xowa_edit_data_box);
    +		else if	(ctx.Match(k, Invk_page_edit_focus_first)) 					Xog_tab_itm_edit_mgr.Focus(this, Xog_html_itm.Elem_id__first_heading);
    +		else if	(ctx.Match(k, Invk_page_dbg_html))							Xog_tab_itm_edit_mgr.Debug(this, Xopg_page_.Tid_html);
    +		else if	(ctx.Match(k, Invk_page_dbg_wiki))							Xog_tab_itm_edit_mgr.Debug(this, Xopg_page_.Tid_edit);
    +		else if	(ctx.Match(k, Invk_page_goto))								Page__navigate_by_url_bar(m.ReadStr("v"));
    +		else if	(ctx.Match(k, Invk_page_goto_recent))						Page__navigate_by_url_bar(app.Usere().History_mgr().Get_at_last());
    +		else if	(ctx.Match(k, Invk_history_bwd))							{Page__navigate_by_history(Bool_.N);}
    +		else if	(ctx.Match(k, Invk_history_fwd))							{Page__navigate_by_history(Bool_.Y);}
    +		else if	(ctx.Match(k, Invk_eval))									App__eval(m.ReadStr("cmd"));
    +		else if	(ctx.Match(k, Invk_page_async_cancel_wait))					Page__async__cancel__wait();
    +		else if	(ctx.Match(k, Invk_page_async_restart))						Page__async__restart();
    +		else if	(ctx.Match(k, Invk_search))									Page__navigate_by_search();
    +		else if	(ctx.Match(k, Invk_window_font_changed))					Xog_win_itm_.Font_update(this, (Xol_font_info)m.CastObj("font"));
    +		else if	(ctx.Match(k, Invk_app))									return app;
    +		else if	(ctx.Match(k, Invk_page))									return this.Active_page();
    +		else if	(ctx.Match(k, Invk_wiki))									return this.Active_tab().Wiki();
    +		else if	(ctx.Match(k, Invk_exit))									App__exit();
    +		else if	(ctx.Match(k, Gfui_html.Evt_link_hover)) {
    +			if (this.Active_tab() != null)	// NOTE: this.Active_tab() should not be null, but is null when running on raspberry pi; DATE:2016-09-23
    +				Xog_win_itm__prog_href_mgr.Hover(app, cfg.Status__show_short_url(), this.Active_tab().Wiki(), this.Active_page(), Xoh_href_gui_utl.Standardize_xowa_link(m.ReadStr("v")));
    +		}
    +		else																return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	private static final String
    +	  Invk_page_async_exec = "page_async_exec"
    +	, Invk_page_async_cancel_wait = "page_async_cancel_wait", Invk_page_async_restart = "page_async_restart"
    +	; 
    +	public static final String
    +	  Invk_app = "app", Invk_wiki = "wiki", Invk_page = "page", Invk_shortcuts = "shortcuts"
    +	, Invk_link_click = "link_click", Invk_link_print = "link_print"
    +	, Invk_window_font_changed = "winow_font_changed"
    +	, Invk_search = "search"
    +	, Invk_page_view_edit = "page_view_edit", Invk_page_view_read = "page_view_read", Invk_page_view_html = "page_view_html"
    +	, Invk_history_fwd = "history_fwd", Invk_history_bwd = "history_bwd"
    +	, Invk_page_refresh = "page_refresh"
    +	, Invk_page_edit_focus_box = "page_edit_focus_box", Invk_page_edit_focus_first = "page_edit_focus_first"
    +	, Invk_page_edit_save = "page_edit_save", Invk_page_edit_save_draft = "page_edit_save_draft", Invk_page_edit_preview = "page_edit_preview", Invk_page_edit_rename = "page_edit_rename"
    +	, Invk_page_dbg_wiki = "page_dbg_wiki", Invk_page_dbg_html = "page_dbg_html"
    +	, Invk_eval = "eval"
    +	, Invk_exit = "exit"
    +	// xowa.gfs: shortcuts
    +	, Invk_page_goto = "page_goto", Invk_page_goto_recent = "page_goto_recent"
    +	; 
    +	private void Win__link_click() {	// NOTE: only applies when content_editable=y; if n, then link_click will be handled by SwtBrowser location changed (Win__link_clicked)
    +		// COMMENT: ignore content editable; DATE:2016-12-25
    +		//Xog_tab_itm tab = tab_mgr.Active_tab(); Xowe_wiki wiki = tab.Wiki();
    +		//if (wiki.Gui_mgr().Cfg_browser().Content_editable()) {	
    +		//	String href = tab.Html_itm().Html_box().Html_js_eval_proc_as_str(Xog_js_procs.Selection__get_active_for_editable_mode, Gfui_html.Atr_href, null);
    +		//	if (String_.Len_eq_0(href)) return; // NOTE: href can be null for images; EX: [[File:Loudspeaker.svg|11px|link=|alt=play]]; link= basically means don't link to image
    +		//	Page__navigate_by_href(tab, href);
    +		//}
    +	}
    +	private void Win__link_clicked(String anchor_raw) {
    +		String url = url_box.Text();
    +		int pos = String_.FindFwd(url, gplx.langs.htmls.Gfh_tag_.Anchor_str);
    +		if (pos != Bry_find_.Not_found) url = String_.Mid(url, 0, pos);
    +		String anchor_str = Parse_evt_location_changing(anchor_raw);
    +		byte[] anchor_bry = Bry_.new_u8(anchor_str);
    +		Xog_tab_itm tab = tab_mgr.Active_tab(); Xoae_page page = tab.Page();
    +		if (anchor_str != null) {									// link has anchor
    +			url_box.Text_(url + "#" + anchor_str);					// update url box
    +			page.Html_data().Bmk_pos_(Xog_history_itm.Html_doc_pos_toc); // HACK: anchor clicked; set docPos of curentPage to TOC (so back will go back to TOC)
    +			tab.History_mgr().Update_html_doc_pos(page, Xog_history_stack.Nav_by_anchor); // HACK: update history_mgr; note that this must occur before setting Anchor (since Anchor will generate a new history itm)
    +			page.Url().Anch_bry_(anchor_bry);						// update url
    +		}
    +		tab.History_mgr().Add(page);
    +		app.Usere().History_mgr().Add(page.Wiki().App(), page.Url(), page.Ttl(), Bry_.Add_w_dlm(Byte_ascii.Hash, page.Url().Page_bry(), anchor_bry));
    +	}
    +	public void App__exit() {
    +		kit.Kit_term();	// NOTE: Kit_term calls shell.close() which in turn is hooked up to app.Term_cbk() event; DATE:2014-09-09
    +	}
    +	private void App__eval(String s) {
    +		String snippet = this.Active_html_itm().Html_elem_atr_get_str(s, Gfui_html.Atr_innerHTML);
    +		app.Gfs_mgr().Run_str(snippet);
    +	}
    +	private static String Parse_evt_location_changing(String v) { // EX: about:blank#anchor -> anchor
    +		int pos = String_.FindFwd(v, gplx.langs.htmls.Gfh_tag_.Anchor_str);
    +		return pos == Bry_find_.Not_found
    +			? null
    +			: String_.Mid(v, pos + 1);
    +	}
    +	public void Page__mode_edit_() {	// only called from by link
    +		// HACK: when "edit" is clicked, always reload page from database; handles rarely-reproducible issue of "edit-after-rename" causing older versions to show up
    +		Xog_tab_itm tab = tab_mgr.Active_tab(); Xoae_page page = tab.Page(); Xowe_wiki wiki = tab.Wiki();
    +		page = wiki.Page_mgr().Load_page(page.Url(), page.Ttl(), tab);
    +		Page__mode_(Xopg_page_.Tid_edit);
    +	}
    +	public void Page__mode_(byte new_mode_tid) {
    +		Xog_tab_itm tab = tab_mgr.Active_tab(); Xoae_page page = tab.Page(); Xowe_wiki wiki = tab.Wiki();
    +		if (	new_mode_tid == Xopg_page_.Tid_read	// used to be && cur_view_tid == Edit; removed clause else redlinks wouldn't show when going form html to read (or clicking read multiple times) DATE: 2013-11-26;
    +			&&	page.Db().Page().Exists()			// if new page, don't try to reload
    +			) {
    +			// NOTE: if moving from "Edit" to "Read", reload page (else Preview changes will still show); NOTE: do not call Exec_page_reload / Exec_page_refresh, which will fire redlinks code
    +			page = tab_mgr.Active_tab().History_mgr().Cur_page(wiki);	// NOTE: must set to CurPage() else changes will be lost when going Bwd,Fwd
    +			tab.Page_(page);			
    +			wiki.Parser_mgr().Parse(page, true);		// NOTE: must reparse page if (a) Edit -> Read; or (b) "Options" save
    +			Xoa_url url = page.Url();
    +			if (url.Qargs_mgr().Match(Xoa_url_.Qarg__action, Xoa_url_.Qarg__action__edit))	// url has ?action=edit
    +				url = tab.Wiki().Utl__url_parser().Parse(url.To_bry_full_wo_qargs());	// remove all query args; handle (1) s.w:Earth?action=edit; (2) click on Read; DATE:2014-03-06
    +		}
    +		tab.View_mode_(new_mode_tid);
    +		if (page.Db().Page().Exists_n()) return;
    +		Xog_tab_itm_read_mgr.Show_page(tab, page, false);
    +		// Exec_page_refresh(); // commented out; causes lnke to show as [2] instead of [1] when saving page; EX: [http://a.org b] DATE:2014-04-24
    +	}
    +	public void Page__navigate_by_search()   {Page__navigate_by_url_bar(app.Gui_mgr().Win_cfg().Search_box_fmtr().Bld_str_many(search_box.Text()));}
    +	public void Page__navigate_by_allpages() {Page__navigate_by_url_bar(app.Gui_mgr().Win_cfg().Allpages_box_fmtr().Bld_str_many(allpages_box.Text()));}
    +	public void Page__navigate_by_url_bar(String href) {
    +		Xog_tab_itm tab = tab_mgr.Active_tab_assert();
    +		Xoa_url url = tab.Wiki().Utl__url_parser().Parse_by_urlbar_or_null(href); if (url == null) return;
    +		tab.Show_url_bgn(url);
    +	}
    +	private void Page__navigate_by_href(Xog_tab_itm tab, String href) {	// NOTE: different from Navigate_by_url_bar in that it handles "file:///" and other @gplx.Internal protected formats; EX: "/site/", "about:blank"; etc..
    +		Xoa_url url = Xog_url_wkr.Exec_url(this, href);
    +		if (url != Xog_url_wkr.Rslt_handled)
    +			tab.Show_url_bgn(url);
    +	}
    +	public void Page__navigate_by_history(boolean fwd) {
    +		Xog_tab_itm tab = tab_mgr.Active_tab();
    +		if (tab == Xog_tab_itm_.Null) return;
    +		Xoae_page cur_page = tab.Page(); Xowe_wiki cur_wiki = tab.Wiki();
    +		Xoae_page new_page = tab.History_mgr().Go_by_dir(cur_wiki, fwd);
    +		if (new_page.Db().Page().Exists_n()) return;
    +		if (new_page.Ttl().Ns().Id_is_special())		// if Special, reload page; needed for Special:Search (DATE:2015-04-19; async loading) and Special:XowaBookmarks DATE:2015-10-05
    +			new_page = new_page.Wikie().Data_mgr().Load_page_and_parse(new_page.Url(), new_page.Ttl());	// NOTE: must reparse page if (a) Edit -> Read; or (b) "Options" save
    +		else {
    +			// WORKAROUND: if wikinews, then reload page; DATE:2016-11-03
    +			// fixes bug wherein dump_html points images to wrong repo and causes images to be blank when going backwards / forwards
    +			// note that this workaround will cause Wikitext Wikinews pages to reload page when going bwd / fwd, but this should be a smalldifference
    +			if (new_page.Wiki().Domain_tid() == gplx.xowa.wikis.domains.Xow_domain_tid_.Tid__wikinews)
    +				new_page = new_page.Wikie().Page_mgr().Load_page(new_page.Url(), new_page.Ttl(), tab);
    +		}
    +		byte history_nav_type = fwd ? Xog_history_stack.Nav_fwd : Xog_history_stack.Nav_bwd;
    +		boolean new_page_is_same = Bry_.Eq(cur_page.Ttl().Full_txt_by_orig(), new_page.Ttl().Full_txt_by_orig());
    +		Xog_tab_itm_read_mgr.Show_page(tab, new_page, true, new_page_is_same, false, history_nav_type);
    +		Page__async__bgn(tab);
    +	}
    +	public void Page__reload() {
    +		Xog_tab_itm tab = tab_mgr.Active_tab();
    +		Xoae_page page = tab.History_mgr().Cur_page(tab.Wiki());	// NOTE: must set to CurPage() else changes will be lost when going Bwd,Fwd
    +		tab.Page_(page);
    +		page = page.Wikie().Page_mgr().Load_page(page.Url(), page.Ttl(), tab);	// NOTE: must reparse page if (a) Edit -> Read; or (b) "Options" save
    +		Page__refresh();
    +	}
    +	public void Page__refresh() {
    +		Page__refresh(tab_mgr.Active_tab());
    +	}
    +	public void Page__refresh(Xog_tab_itm tab) {
    +		Xoae_page page = tab.Page(); Xog_html_itm html_itm = tab.Html_itm();
    +		page = page.Wikie().Page_mgr().Load_page(page.Url(), page.Ttl(), tab);	// NOTE: refresh should always reload and regen page; DATE:2017-02-15
    +		page.Html_data().Bmk_pos_(html_itm.Html_box().Html_js_eval_proc_as_str(Xog_js_procs.Win__vpos_get));
    +		html_itm.Show(page);
    +		if (page.Url().Anch_str() == null)
    +			html_itm.Scroll_page_by_bmk_gui();
    +		else
    +			html_itm.Scroll_page_by_id_gui(page.Url().Anch_str());
    +		Page__async__bgn(tab);
    +	}
    +	public void Page__async__bgn(Xog_tab_itm tab) {
    +		page__async__thread = Thread_adp_.Start_by_val(gplx.xowa.apps.Xoa_thread_.Key_page_async, this, Invk_page_async_exec, tab);
    +	}	private Thread_adp page__async__thread = Thread_adp.Noop;
    +	public boolean Page__async__working(Xoa_url url) {
    +		if (page__async__thread.Thread__is_alive()) {				// cancel pending image downloads
    +			page__async__restart_url = url;
    +			this.Usr_dlg().Canceled_y_();
    +			app.Wmf_mgr().Download_wkr().Download_xrg().Prog_cancel_y_();
    +			Thread_adp_.Start_by_key(Invk_page_async_cancel_wait, this, Invk_page_async_cancel_wait);
    +			return true;
    +		}
    +		return false;
    +	}
    +	private void Page__async__cancel__wait() {
    +		while (page__async__thread.Thread__is_alive()) {
    +			Thread_adp_.Sleep(10);
    +		}
    +		this.Active_page().File_queue().Clear();
    +		this.Usr_dlg().Canceled_n_();	// NOTE: must mark "uncanceled", else one cancelation will stop all future downloads; DATE:2014-05-04
    +		Gfo_invk_.Invk_by_key(sync_cmd, Invk_page_async_restart);
    +	}
    +	private void Page__async__restart() {
    +		tab_mgr.Active_tab().Show_url_bgn(page__async__restart_url);
    +	}	private Xoa_url page__async__restart_url;
    +	public void Lang_changed(Xol_lang_itm lang) {
    +		Xoae_app app = gui_mgr.App();
    +		Xog_win_itm_.Update_tiptext(app, go_bwd_btn			, Xol_msg_itm_.Id_xowa_window_go_bwd_btn_tooltip);
    +		Xog_win_itm_.Update_tiptext(app, go_fwd_btn			, Xol_msg_itm_.Id_xowa_window_go_fwd_btn_tooltip);
    +		Xog_win_itm_.Update_tiptext(app, url_box			, Xol_msg_itm_.Id_xowa_window_url_box_tooltip);
    +		Xog_win_itm_.Update_tiptext(app, url_exec_btn		, Xol_msg_itm_.Id_xowa_window_url_btn_tooltip);
    +		Xog_win_itm_.Update_tiptext(app, search_box			, Xol_msg_itm_.Id_xowa_window_search_box_tooltip);
    +		Xog_win_itm_.Update_tiptext(app, search_exec_btn	, Xol_msg_itm_.Id_xowa_window_search_btn_tooltip);
    +		Xog_win_itm_.Update_tiptext(app, allpages_box		, Xol_msg_itm_.Id_xowa_window_allpages_box_tooltip);
    +		Xog_win_itm_.Update_tiptext(app, allpages_exec_btn	, Xol_msg_itm_.Id_xowa_window_allpages_btn_tooltip);
    +		Xog_win_itm_.Update_tiptext(app, find_close_btn		, Xol_msg_itm_.Id_xowa_window_find_close_btn_tooltip);
    +		Xog_win_itm_.Update_tiptext(app, find_box			, Xol_msg_itm_.Id_xowa_window_find_box_tooltip);
    +		Xog_win_itm_.Update_tiptext(app, find_bwd_btn		, Xol_msg_itm_.Id_xowa_window_find_bwd_btn_tooltip);
    +		Xog_win_itm_.Update_tiptext(app, find_fwd_btn		, Xol_msg_itm_.Id_xowa_window_find_fwd_btn_tooltip);
    +		Xog_win_itm_.Update_tiptext(app, prog_box			, Xol_msg_itm_.Id_xowa_window_prog_box_tooltip);
    +		Xog_win_itm_.Update_tiptext(app, info_box			, Xol_msg_itm_.Id_xowa_window_info_box_tooltip);
    +	}
    +	public byte[] App__retrieve_by_url(String url_str, String output_str) {
    +		synchronized (App__retrieve__lock) {
    +			boolean output_html = String_.Eq(output_str, "html");
    +
    +			// parse url according to rules of home_wiki; 
    +			Xowe_wiki home_wiki = app.Usere().Wiki();
    +			Xoa_url url = home_wiki.Utl__url_parser().Parse_by_urlbar_or_null(url_str); if (url == null) return Bry_.Empty;
    +
    +			// get wiki from url
    +			Xowe_wiki wiki = (Xowe_wiki)app.Wiki_mgr().Get_by_or_make_init_y(url.Wiki_bry());
    +
    +			// parse url again, but this time according to rules of actual wiki
    +			url = wiki.Utl__url_parser().Parse(Bry_.new_u8(url_str));
    +
    +			// get title
    +			Xoa_ttl ttl = Xoa_ttl.Parse(wiki, url.Page_bry());
    +
    +			// get tab for Load_page
    +			gplx.xowa.apps.servers.Gxw_html_server.Assert_tab(app, Xoae_page.Empty);		// HACK: assert at least 1 tab for Firefox addon; DATE:2015-01-23
    +			Xog_tab_itm tab = tab_mgr.Active_tab();
    +
    +			// get page
    +			Xoae_page new_page = wiki.Page_mgr().Load_page(url, ttl, tab);
    +			if (new_page.Db().Page().Exists_n()) {return Bry_.Empty;}
    +
    +			// update tab-specific vars
    +			tab.Page_(new_page);
    +			tab.History_mgr().Add(new_page);
    +
    +			// gen html
    +			byte[] rv = output_html
    +				? wiki.Html_mgr().Page_wtr_mgr().Gen(new_page, tab.View_mode())
    +				: new_page.Db().Text().Text_bry();
    +			if (app.Shell().Fetch_page_exec_async())
    +				app.Gui_mgr().Browser_win().Page__async__bgn(tab);
    +			return rv;
    +		}
    +	}	private Object App__retrieve__lock = new Object();
    +	public void Init_by_kit(Gfui_kit kit) {
    +		this.kit = kit;
    +		this.win_box = kit.New_win_app("win");
    +		this.sync_cmd		= win_box.Kit().New_cmd_sync(this);
    +		FontAdp ui_font		= app.Gui_mgr().Win_cfg().Font().To_font();
    +
    +		win_box.Layout_mgr_(new Swt_layout_mgr__grid().Cols_(1).Margin_w_(0).Margin_h_(0).Spacing_h_(0));
    +
    +		// toolbar
    +		this.toolbar_grp    = Xog_win_itm_.new_grp(app, kit, win_box, "toolbar_grp");
    +		go_bwd_btn			= Xog_win_itm_.new_btn(app, kit, toolbar_grp, "go_bwd_btn");
    +		go_fwd_btn			= Xog_win_itm_.new_btn(app, kit, toolbar_grp, "go_fwd_btn");
    +		url_box				= Xog_win_itm_.new_cbo(app, kit, toolbar_grp, ui_font, "url_box", true);
    +		url_exec_btn		= Xog_win_itm_.new_btn(app, kit, toolbar_grp, "url_exec_btn");
    +		search_box			= Xog_win_itm_.new_txt(app, kit, toolbar_grp, ui_font, "search_box", true);
    +		search_exec_btn		= Xog_win_itm_.new_btn(app, kit, toolbar_grp, "search_exec_btn");
    +		allpages_box		= Xog_win_itm_.new_txt(app, kit, toolbar_grp, ui_font, "allpages_box", true);
    +		allpages_exec_btn	= Xog_win_itm_.new_btn(app, kit, toolbar_grp, "allpages_exec_btn");
    +
    +		toolbar_grp.Layout_data_(new Swt_layout_data__grid().Grab_excess_w_(true).Align_w__fill_().Hint_h_(28));
    +		toolbar_grp.Layout_mgr_(new Swt_layout_mgr__grid().Cols_(8)
    +			.Margin_w_(4)   // sets space to far-left / right window edges
    +			.Margin_h_(1)   // sets space to top-menu and bot-html
    +			.Spacing_w_(4)  // sets space between buttons, or else very squished
    +			.Spacing_h_(0)  // not needed since only one row, but be explicit
    +			);
    +		int toolbar_grp_h = Xog_win_itm_.Toolbar_grp_h; // WORKAROUND.SWT: need to specify height, else SWT will shrink textbox on re-layout when showing / hiding search / allpages; DATE:2017-03-28
    +		int toolbar_txt_w = Xog_win_itm_.Toolbar_txt_w;
    +		int toolbar_btn_w = Xog_win_itm_.Toolbar_btn_w;
    +		url_box.Layout_data_(new Swt_layout_data__grid().Grab_excess_w_(true).Align_w__fill_().Min_w_(100).Hint_h_(toolbar_grp_h));
    +		search_box.Layout_data_(new Swt_layout_data__grid().Hint_w_(toolbar_txt_w).Hint_h_(toolbar_grp_h));
    +		search_exec_btn.Layout_data_(new Swt_layout_data__grid().Align_w__fill_().Hint_w_(toolbar_btn_w).Hint_h_(toolbar_grp_h));
    +		allpages_box.Layout_data_(new Swt_layout_data__grid().Hint_w_(toolbar_txt_w).Hint_h_(toolbar_grp_h));
    +		allpages_exec_btn.Layout_data_(new Swt_layout_data__grid().Align_w__fill_().Hint_w_(toolbar_btn_w).Hint_h_(toolbar_grp_h)); // force 20 width to add even more space to right-hand of screen
    +
    +		// tab / html space
    +		tab_mgr.Init_by_kit(kit);
    +		tab_mgr.Tab_mgr().Layout_data_(new Swt_layout_data__grid().Grab_excess_h_(true).Align_w__fill_().Align_h__fill_().Grab_excess_w_(true)); 
    +
    +		// statusbar
    +		this.statusbar_grp = Xog_win_itm_.new_grp(app, kit, win_box, "statusbar_grp");
    +		find_close_btn		= Xog_win_itm_.new_btn(app, kit, statusbar_grp, "find_close_btn");
    +		find_box			= Xog_win_itm_.new_txt(app, kit, statusbar_grp, ui_font, "find_box"								, true);
    +		find_fwd_btn		= Xog_win_itm_.new_btn(app, kit, statusbar_grp, "find_fwd_btn");
    +		find_bwd_btn		= Xog_win_itm_.new_btn(app, kit, statusbar_grp, "find_bwd_btn");
    +		prog_box			= Xog_win_itm_.new_txt(app, kit, statusbar_grp, ui_font, "prog_box"								, false);
    +		info_box			= Xog_win_itm_.new_txt(app, kit, statusbar_grp, ui_font, "note_box"								, false);
    +
    +		statusbar_grp.Layout_data_(new Swt_layout_data__grid().Grab_excess_w_(true).Align_w__fill_().Hint_h_(28));
    +		statusbar_grp.Layout_mgr_(new Swt_layout_mgr__grid().Cols_(6)
    +			.Margin_w_(4)	// adds buffer on far-left / right window edges
    +			.Margin_h_(0)	// sets space to top-html / bot-window
    +			.Spacing_w_(4)  // sets space between buttons, or else very squished
    +			.Spacing_h_(0)  // not needed since only one row, but be explicit
    +			);
    +		find_box.Layout_data_(new Swt_layout_data__grid().Hint_w_(108)); // 108 is a magic number to have status bar text line up with vertical bar on Wikipedia style sheets
    +		prog_box.Layout_data_(new Swt_layout_data__grid().Grab_excess_w_(true).Align_w__fill_().Min_w_(100));
    +		info_box.Layout_data_(new Swt_layout_data__grid().Hint_w_(0));  // hide for now; may add back later if need static text in corner; DATE:2017-02-15
    +
    +		this.Lang_changed(app.Usere().Lang());
    +
    +		Gfo_evt_mgr_.Sub_same_many(this, this, Gfui_html.Evt_location_changed, Gfui_html.Evt_location_changing, Gfui_html.Evt_link_hover);
    +		Gfo_evt_mgr_.Sub(app.Gui_mgr().Win_cfg().Font(), Xol_font_info.Font_changed, this, Invk_window_font_changed);
    +		url_box__selection_changed = new Xog_url_box__selection_changed(app, url_box);
    +		Gfo_evt_mgr_.Sub_same(url_box, GfuiComboBox.Evt__selected_changed, url_box__selection_changed);
    +		Gfo_evt_mgr_.Sub_same(url_box, GfuiComboBox.Evt__selected_accepted, url_box__selection_changed);
    +
    +		if (	!Env_.Mode_testing()
    +			&&	app.Mode().Tid_is_gui())	// only run for gui; do not run for tcp/http server; DATE:2014-05-03
    +			app.Usr_dlg().Gui_wkr_(new Gfo_usr_dlg__gui__swt(app, kit, prog_box, info_box, info_box));
    +		cfg.Init_by_app(app);
    +
    +		url_box_fmtr.Init_by_app(app);
    +	}
    +	public static String Remove_redirect_if_exists(String text) {
    +		// remove redirect target; EX: "A -> B" -> "A"
    +		int redirect_pos = String_.FindFwd(text, gplx.xowa.addons.wikis.searchs.searchers.rslts.Srch_rslt_row.Str__redirect__text);
    +		if (redirect_pos != Bry_find_.Not_found) {
    +			text = String_.Mid(text, 0, redirect_pos);
    +		}
    +		return text;
    +	}
    +}
    +class Xog_url_box__selection_changed implements Gfo_evt_itm {
    +	private final    GfuiComboBox url_box;
    +	private final    Xoae_app app;
    +	public Xog_url_box__selection_changed(Xoae_app app, GfuiComboBox url_box) {this.app = app; this.url_box = url_box; this.ev_mgr = new Gfo_evt_mgr(this);}
    +	public Gfo_evt_mgr Evt_mgr() {return ev_mgr;} private final    Gfo_evt_mgr ev_mgr;
    +	private void On_selection_changed() {
    +		String text = url_box.Text();
    +		text = Xog_win_itm.Remove_redirect_if_exists(text);
    +		// always move cursor to end; emulates firefox url_bar behavior
    +		url_box.Text_(text);
    +		url_box.Sel_(String_.Len(text), String_.Len(text));
    +	}
    +	private void On_selection_accepted() {
    +		app.Api_root().Nav().Goto(url_box.Text());
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, GfuiComboBox.Evt__selected_changed))			On_selection_changed();
    +		else if	(ctx.Match(k, GfuiComboBox.Evt__selected_accepted))			On_selection_accepted();
    +		else																return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_win_itm_.java b/400_xowa/src/gplx/xowa/guis/views/Xog_win_itm_.java
    index a27517de8..41cdfbf7a 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_win_itm_.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_win_itm_.java
    @@ -13,3 +13,67 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.gfui.*; import gplx.gfui.draws.*; import gplx.gfui.kits.core.*; import gplx.gfui.imgs.*; import gplx.gfui.controls.windows.*; import gplx.gfui.controls.elems.*; import gplx.gfui.controls.standards.*;
    +import gplx.xowa.guis.bnds.*; import gplx.xowa.guis.cmds.*;
    +import gplx.xowa.guis.langs.*;
    +public class Xog_win_itm_ {
    +	public static void Show_win(Xog_win_itm win) {
    +		Xoae_app app = win.App(); GfuiWin win_box = win.Win_box();			
    +		win_box.Focus_able_(false);
    +		app.Gui_mgr().Nightmode_mgr().Enabled_by_cfg();
    +		Xog_startup_win_.Startup(app, win_box);
    +
    +		win_box.Icon_(IconAdp.file_or_blank(app.Fsys_mgr().Bin_xowa_dir().GenSubFil_nest("file", "app.window", "app_icon.png")));
    +	}
    +	public static void Show_widget(boolean show, GfuiElem box, GfuiElem btn) {
    +		int box_w, btn_w;
    +		if (show) {
    +			box_w = Toolbar_txt_w;
    +			btn_w = Toolbar_btn_w;
    +		}
    +		else {
    +			box_w = 0;
    +			btn_w = 0;
    +		}
    +		box.Layout_data_(new gplx.gfui.layouts.swts.Swt_layout_data__grid().Hint_w_(box_w).Hint_h_(Toolbar_grp_h));	// WORKAROUND.SWT: need to specify height, else SWT will shrink textbox on re-layout when showing / hiding search / allpages; DATE:2017-03-28
    +		btn.Layout_data_(new gplx.gfui.layouts.swts.Swt_layout_data__grid().Hint_w_(btn_w).Align_w__fill_());
    +	}
    +	public static Gfui_grp new_grp(Xoae_app app, Gfui_kit kit, GfuiElem win, String id) {
    +		return kit.New_grp(id, win);
    +	}
    +	public static GfuiBtn new_btn(Xoae_app app, Gfui_kit kit, GfuiElem win, String id) {
    +		return kit.New_btn(id, win);
    +	}
    +	public static GfuiComboBox new_cbo(Xoae_app app, Gfui_kit kit, GfuiElem win, FontAdp ui_font, String id, boolean border_on) {
    +		GfuiComboBox rv = kit.New_combo(id, win, Keyval_.new_(GfuiTextBox.CFG_border_on_, border_on));
    +		rv.TextMgr().Font_(ui_font);
    +		return rv;
    +	}
    +	public static GfuiTextBox new_txt(Xoae_app app, Gfui_kit kit, GfuiElem win, FontAdp ui_font, String id, boolean border_on) {
    +		GfuiTextBox rv = kit.New_text_box(id, win, Keyval_.new_(GfuiTextBox.CFG_border_on_, border_on));
    +		rv.TextMgr().Font_(ui_font);
    +		return rv;
    +	}
    +	public static void Update_tiptext(Xoae_app app, GfuiElem elem, int tiptext_id) {
    +		elem.TipText_(Xog_win_itm_.new_tiptext(app, tiptext_id));
    +	}
    +	public static void Font_update(Xog_win_itm win, Xol_font_info itm_font) {
    +		FontAdp gui_font = win.Url_box().TextMgr().Font();
    +		if (!itm_font.Eq(gui_font)) {
    +			FontAdp new_font = itm_font.To_font();
    +			win.Url_box().TextMgr().Font_(new_font);
    +			win.Search_box().TextMgr().Font_(new_font);
    +			win.Allpages_box().TextMgr().Font_(new_font);
    +			win.Find_box().TextMgr().Font_(new_font);
    +			win.Prog_box().TextMgr().Font_(new_font);
    +			win.Info_box().TextMgr().Font_(new_font);
    +			win.Tab_mgr().Tab_mgr().TextMgr().Font_(new_font);
    +		}
    +	}
    +	public static String new_tiptext(Xoae_app app, int id) {return String_.new_u8(app.Usere().Lang().Msg_mgr().Val_by_id(app.Usere().Wiki(), id));}
    +	public static final int 
    +	  Toolbar_grp_h = 24
    +	, Toolbar_txt_w = 160
    +	, Toolbar_btn_w = 16;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_win_itm__prog_href_mgr.java b/400_xowa/src/gplx/xowa/guis/views/Xog_win_itm__prog_href_mgr.java
    index a27517de8..bd499c7c0 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_win_itm__prog_href_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_win_itm__prog_href_mgr.java
    @@ -13,3 +13,24 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +import gplx.gfui.*; import gplx.gfui.controls.standards.*; import gplx.xowa.htmls.hrefs.*;
    +public class Xog_win_itm__prog_href_mgr {
    +	public static void Print(Xog_win_itm win) {	// PURPOSE: print href in prog box when in content editable mode
    +		String href = win.Active_html_box().Html_js_eval_proc_as_str(Xog_js_procs.Selection__get_active_for_editable_mode, Gfui_html.Atr_href, "");// get selected href from html_box
    +		href = gplx.langs.htmls.encoders.Gfo_url_encoder_.Href.Decode_str(href);								// remove url encodings
    +		if (!String_.Eq(href, win.Prog_box().Text()))
    +			win.Usr_dlg().Prog_direct(href);
    +	}
    +	public static void Hover(Xoae_app app, boolean show_status_url, Xowe_wiki wiki, Xoae_page page, String href) {
    +		Gfo_usr_dlg usr_dlg = app.Usr_dlg();
    +		if (	String_.Len_eq_0(href)			// href is null / empty; occurs when hovering over empty space
    +			||	String_.Eq(href, "file:///")) {
    +			usr_dlg.Prog_direct("");			// clear out previous entry
    +			return;
    +		}
    +		Xoa_url url = Xoa_url.blank();
    +		app.Html__href_parser().Parse_as_url(url, Bry_.new_u8(href), wiki, page.Ttl().Page_txt());
    +		usr_dlg.Prog_direct(String_.new_u8(url.To_bry(!show_status_url, Bool_.Y)));
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_win_itm_cfg.java b/400_xowa/src/gplx/xowa/guis/views/Xog_win_itm_cfg.java
    index a27517de8..d9cace3a7 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/Xog_win_itm_cfg.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_win_itm_cfg.java
    @@ -13,3 +13,24 @@ 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.guis.views; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
    +public class Xog_win_itm_cfg implements Gfo_invk {
    +	private Xoae_app app;
    +	public boolean Status__show_short_url() {return status__show_short_url;} private boolean status__show_short_url = true;
    +	public void Init_by_app(Xoae_app app) {
    +		this.app = app;
    +		app.Cfg().Bind_many_app(this, Cfg__status__show_short_url, Cfg__toolbar__show_search, Cfg__toolbar__show_allpages);
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Cfg__status__show_short_url))			status__show_short_url = m.ReadYn("v");
    +		else if	(ctx.Match(k, Cfg__toolbar__show_search))		    Xog_win_itm_.Show_widget(m.ReadYn("v"), app.Gui_mgr().Browser_win().Search_box(), app.Gui_mgr().Browser_win().Search_exec_btn());
    +		else if	(ctx.Match(k, Cfg__toolbar__show_allpages))         Xog_win_itm_.Show_widget(m.ReadYn("v"), app.Gui_mgr().Browser_win().Allpages_box(), app.Gui_mgr().Browser_win().Allpages_exec_btn());
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	private static final String
    +	  Cfg__status__show_short_url = "xowa.gui.prog_box.show_short_url"
    +	, Cfg__toolbar__show_search   = "xowa.gui.toolbar.show_search"
    +	, Cfg__toolbar__show_allpages = "xowa.gui.toolbar.show_allpages"
    +	;
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/boots/Xog_error_data.java b/400_xowa/src/gplx/xowa/guis/views/boots/Xog_error_data.java
    index a27517de8..81d165a0f 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/boots/Xog_error_data.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/boots/Xog_error_data.java
    @@ -13,3 +13,54 @@ 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.guis.views.boots; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.views.*;
    +import gplx.core.envs.*;
    +public class Xog_error_data {
    +	public Xog_error_data(String full_msg, String err_details, String err_msg) {
    +		this.full_msg = full_msg;
    +		this.err_details = err_details;
    +		this.err_msg = err_msg;
    +	}
    +	public String Full_msg() {return full_msg;} private final    String full_msg;
    +	public String Err_details() {return err_details;} private final    String err_details;
    +	public String Err_msg() {return err_msg;} private final    String err_msg;
    +	public static Xog_error_data new_(String err_msg, String err_trace) {
    +		String err_details = String_.Concat_lines_nl_skip_last
    +		( "OS: "		+ Op_sys.Cur().Os_name()
    +		, "Java: "		+ System_.Prop__java_version() + " (" + Op_sys.Cur().Bitness_str() + " bit)"
    +		, "Java path: " + System_.Prop__java_home()
    +		, "XOWA: "		+ Xoa_app_.Version
    +		, "XOWA path: " + Env_.AppUrl().Raw()
    +		, ""
    +		, "Error: "		+ err_msg
    +		, "Stack: "		+ err_trace
    +		);
    +		String advice = Make_advice(err_msg);
    +		String full_msg = String_.Concat_lines_nl_skip_last
    +		( "Sorry! XOWA failed to run!"
    +		, ""
    +		, advice
    +		, ""
    +		, "You can also open an issue or send an email with the data below."
    +		, ""
    +		, "Thanks!"
    +		, ""
    +		, "----"
    +		, err_details
    +		);
    +		return new Xog_error_data(full_msg, err_details, err_msg);
    +	}
    +	private static String Make_advice(String err_msg) {
    +		String check_troubleshooting_section = "check the TROUBLESHOOTING section in the readme.txt for known issues.";
    +		if (String_.Has(err_msg, "Cannot load 64-bit SWT libraries on 32-bit JVM"))
    +			return String_.Concat_lines_nl_skip_last
    +			( "Your Java installation looks like it's 32-bit. Please use the 32-bit package of XOWA."
    +			, ""
    +			, "For example, if you downloaded xowa_app_windows_64_v2.10.1.1.zip (64-bit), download xowa_app_windows_v2.10.1.1.zip (32-bit)"
    +			, ""
    +			, "You may also want to " + check_troubleshooting_section
    +			);
    +		else
    +			return "Please " + check_troubleshooting_section;
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/boots/Xog_error_win.java b/400_xowa/src/gplx/xowa/guis/views/boots/Xog_error_win.java
    index a27517de8..57021d151 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/boots/Xog_error_win.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/boots/Xog_error_win.java
    @@ -13,3 +13,102 @@ 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.guis.views.boots; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.views.*;
    +import gplx.langs.htmls.encoders.*; import gplx.core.envs.*;
    +import java.awt.*;
    +import java.awt.event.*;
    +import java.io.IOException;
    +import java.net.*;
    +import java.awt.*; import java.awt.event.*;
    +import javax.swing.*;
    +public class Xog_error_win extends JFrame implements Gfo_invk {
    +	private Xog_error_data error_data;
    +	public Xog_error_win(Xog_error_data error_data) {
    +				super("XOWA Error");
    +		this.setTitle("XOWA Error");
    +		this.error_data = error_data;
    +	    try {
    +	    	UIManager.setLookAndFeel(
    +            UIManager.getSystemLookAndFeelClassName());
    +	    }
    +	    catch (Exception e) {System.out.println(e.getMessage());}
    +	    this.setSize(700, 580);
    +		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    +		this.setLocationRelativeTo(null);
    +		this.setBackground(Color.WHITE);
    +		JPanel main_panel = new JPanel();
    +		main_panel.setSize(700, 580);
    +		this.setContentPane(main_panel);
    +		this.setLayout(null);
    +		new_text_area(main_panel, error_data);
    +		new_link_lbl(this, main_panel,  10, 520, Invk_open_site, "open issue");
    +		new_link_lbl(this, main_panel, 605, 520, Invk_send_mail, "send email");
    +		this.setVisible(true);
    +			}
    +		private static JScrollPane new_text_area(JPanel owner, Xog_error_data error_data) {
    +		JTextArea text_area = new JTextArea();
    +		text_area.setForeground(Color.BLACK);
    +		text_area.setBackground(Color.WHITE);
    +		text_area.setMargin(new Insets(0, 0, 0,0));
    +		text_area.setLineWrap(true);
    +		text_area.setWrapStyleWord(true);	// else text will wrap in middle of words
    +		text_area.setCaretColor(Color.BLACK);
    +		text_area.getCaret().setBlinkRate(0);
    +		text_area.setText(error_data.Full_msg());
    +		JScrollPane text_scroll_pane = new JScrollPane(text_area);
    +		text_scroll_pane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
    +		owner.add(text_scroll_pane);
    +		text_scroll_pane.setSize(675, 500);
    +		text_scroll_pane.setLocation(10, 10);
    +		return text_scroll_pane;
    +	}
    +	private static JLabel new_link_lbl(Gfo_invk invk, JPanel owner, int x, int y, String invk_cmd, String text) {
    +		JLabel rv = new JLabel(); 
    +		rv.setText(text);
    +		rv.setCursor(new Cursor(Cursor.HAND_CURSOR));
    +		rv.addMouseListener(new Swing_mouse_adapter(Gfo_invk_cmd.New_by_key(invk, invk_cmd)));
    +		rv.setLocation(x, y);
    +		rv.setSize(80, 20);
    +		owner.add(rv);
    +		return rv;
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Invk_send_mail)) {
    +			try {
    +				Gfo_url_encoder url_encoder = Gfo_url_encoder_.New__fsys_wnt().Make();
    +				String subject = url_encoder.Encode_str("XOWA boot error: " + error_data.Err_msg());
    +				String body = url_encoder.Encode_str(error_data.Err_details());
    +				Desktop.getDesktop().mail(new URI("mailto:gnosygnu+xowa_error_boot@gmail.com?subject=" + subject + "&body=" + body));
    +			} 
    +			catch (URISyntaxException ex) {}
    +			catch (IOException ex) {}			
    +		}
    +		else if	(ctx.Match(k, Invk_open_site)) {
    +			try {
    +				Desktop.getDesktop().browse(new URI("https://github.com/gnosygnu/xowa/issues"));
    +			}
    +			catch (URISyntaxException ex) {}
    +			catch (IOException ex) {}			
    +		}
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}	private static final String Invk_send_mail = "send_mail", Invk_open_site = "open_site";
    +		public static void Run(String err_msg, String err_trace) {
    +		Xog_error_data error_data = Xog_error_data.new_(err_msg, err_trace);
    +		Gfo_usr_dlg_.Instance.Log_many("", "", error_data.Err_details());
    +		if (Op_sys.Cur().Tid_is_osx())
    +			gplx.core.consoles.Console_adp__sys.Instance.Write_str(error_data.Err_msg());
    +		else
    +			new Xog_error_win(error_data);
    +	}
    +}
    +class Swing_mouse_adapter extends MouseAdapter {
    +	private final Gfo_invk_cmd cmd;
    +	public Swing_mouse_adapter(Gfo_invk_cmd cmd) {this.cmd = cmd;}
    +	@Override public void mouseClicked(MouseEvent ev) {
    +		try {cmd.Exec();}
    +		catch (Exception e) {
    +			System.out.println(Err_.Message_gplx_full(e));
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/boots/Xog_splash_win.java b/400_xowa/src/gplx/xowa/guis/views/boots/Xog_splash_win.java
    index a27517de8..5cc87c736 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/boots/Xog_splash_win.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/boots/Xog_splash_win.java
    @@ -13,3 +13,39 @@ 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.guis.views.boots; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.views.*;
    +import java.awt.*;
    +import java.awt.event.*;
    +public class Xog_splash_win implements Rls_able {
    +		private SplashScreen splash;
    +	private Graphics2D graphics; private boolean graphics_init = true;
    +		public Xog_splash_win(boolean app_mode_is_gui) {
    +		        if (app_mode_is_gui) {
    +			Gfo_usr_dlg_.Instance.Log_many("", "", "gui.splash.bgn");
    +	        this.splash = SplashScreen.getSplashScreen();
    +	        if (splash == null) System.out.println("SplashScreen.getSplashScreen() returned null");
    +			Gfo_usr_dlg_.Instance.Log_many("", "", "gui.splash.end");
    +        }
    +        	}
    +	public void Write(String msg) {
    +				if (splash == null) return;
    +    	if (graphics_init) {
    +    		graphics_init = false;
    +	        if (graphics == null) {
    +	        	graphics = splash.createGraphics();
    +	        	if (graphics == null) System.out.println("graphics is null");
    +	        }        
    +    	}
    +    	if (graphics == null) return;
    +        graphics.setComposite(AlphaComposite.Clear);
    +        graphics.fillRect(120,140,200,40);
    +        graphics.setPaintMode();
    +        graphics.setColor(Color.BLACK);
    +        graphics.drawString(msg, 0, 0);
    +        splash.update();
    +    		}
    +	public void Rls() {
    +				if (splash == null) return;
    +    	splash.close();
    +    		}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/nightmodes/Xog_nightmode_mgr.java b/400_xowa/src/gplx/xowa/guis/views/nightmodes/Xog_nightmode_mgr.java
    index a27517de8..559ffdc51 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/nightmodes/Xog_nightmode_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/nightmodes/Xog_nightmode_mgr.java
    @@ -13,3 +13,139 @@ 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.guis.views.nightmodes; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.views.*;
    +import gplx.gfui.controls.elems.*; import gplx.gfui.draws.*;
    +import gplx.xowa.specials.xowa.default_tab.*;
    +public class Xog_nightmode_mgr implements Gfo_invk {
    +	private Xoae_app app;
    +	private boolean enabled;
    +	private Xog_win_itm win;
    +	private GfuiElemBase[] backcolor_elems, forecolor_elems;
    +	public void Init_by_app(Xoae_app app) {
    +		this.app = app;
    +		this.win = app.Gui_mgr().Browser_win();
    +		app.Cfg().Sub_many_app(this
    +			, Cfg__enabled
    +			, Cfg__night_back, Cfg__night_fore, Cfg__night_edge
    +			, Cfg__day_back, Cfg__day_fore, Cfg__day_edge
    +			);
    +	}
    +	public void Init_by_kit(Xoae_app app) {
    +		// set back / fore for other elems
    +		this.backcolor_elems = this.forecolor_elems = new GfuiElemBase[] 
    +		{ win.Toolbar_grp()
    +		, win.Go_bwd_btn()
    +		, win.Go_fwd_btn()
    +		, win.Url_box()
    +		, win.Url_exec_btn()
    +		, win.Search_box()
    +		, win.Search_exec_btn()
    +		, win.Allpages_box()
    +		, win.Allpages_exec_btn()
    +		, win.Statusbar_grp()
    +		, win.Find_close_btn()
    +		, win.Find_bwd_btn()
    +		, win.Find_fwd_btn()
    +		, win.Find_box()
    +		, win.Prog_box()
    +		};
    +	}
    +	private void Set_color(int color_group_type, ColorAdp color) {
    +		if (color == null) return; // null passed by invk
    +		switch (color_group_type) {
    +			case COLOR_GROUP_BACK:
    +				win.Win_box().BackColor_(color);
    +				win.Tab_mgr().Tab_mgr().BackColor_(color);
    +				win.Tab_mgr().Tab_mgr().Btns_selected_background_(color);
    +				win.Tab_mgr().Tab_mgr().Btns_unselected_background_(color);
    +				win.Url_box().Items__backcolor_(color);
    +				for (GfuiElemBase elem : backcolor_elems)
    +					elem.BackColor_(color);
    +				win.Prog_box().Border_color_(color);
    +				break;
    +			case COLOR_GROUP_FORE:
    +				win.Tab_mgr().Tab_mgr().Btns_selected_foreground_(color);
    +				win.Tab_mgr().Tab_mgr().Btns_unselected_foreground_(color);
    +				win.Url_box().Items__forecolor_(color);
    +				for (GfuiElemBase elem : forecolor_elems)
    +					elem.ForeColor_(color);
    +				break;
    +			case COLOR_GROUP_EDGE:
    +				win.Url_box().Border_color_(color);
    +				win.Search_box().Border_color_(color);
    +				win.Find_box().Border_color_(color);
    +				break;
    +		}
    +
    +	}
    +	public boolean Enabled() {return enabled;}
    +	public void Enabled_by_cfg() {
    +		Enabled_(app.Cfg().Get_bool_app_or(Cfg__enabled, false));
    +	}
    +	public void Enabled_(boolean v) {
    +		// set enabled
    +		this.enabled = v;
    +
    +		// set colors
    +		ColorAdp backcolor, forecolor, edgecolor;
    +		if (enabled) {
    +			backcolor = Parse_from_cfg(app, Cfg__night_back, ColorAdp_.White);
    +			forecolor = Parse_from_cfg(app, Cfg__night_fore, ColorAdp_.Black);
    +			edgecolor = Parse_from_cfg(app, Cfg__night_edge, ColorAdp_.LightGray);
    +		}
    +		else {
    +			backcolor = Parse_from_cfg(app, Cfg__day_back, ColorAdp_.Black);
    +			forecolor = Parse_from_cfg(app, Cfg__day_fore, ColorAdp_.White);
    +			edgecolor = Parse_from_cfg(app, Cfg__day_edge, ColorAdp_.Black);
    +		}
    +		Set_color(COLOR_GROUP_BACK, backcolor);
    +		Set_color(COLOR_GROUP_FORE, forecolor);
    +		Set_color(COLOR_GROUP_EDGE, edgecolor);
    +
    +		// set button icons
    +		// note that nightmode needs 16px and unresized b/c swt interpolates white pixels when resizing images (even when downsizing?)
    +		// note that daymode needs 32px and resized b/c resizing "blurs" image which looks better
    +		Io_url img_dir = app.Fsys_mgr().Bin_xowa_file_dir().GenSubDir_nest("app.window", enabled ? "16px" : "32px");
    +		win.Go_bwd_btn().Btn_img_(app.Gui_mgr().Kit().New_img_load(img_dir.GenSubFil("go_bwd.png")));
    +		win.Go_fwd_btn().Btn_img_(app.Gui_mgr().Kit().New_img_load(img_dir.GenSubFil("go_fwd.png")));
    +		win.Url_exec_btn().Btn_img_(app.Gui_mgr().Kit().New_img_load(img_dir.GenSubFil("url_exec.png")));
    +		win.Search_exec_btn().Btn_img_(app.Gui_mgr().Kit().New_img_load(img_dir.GenSubFil("search_exec.png")));
    +		win.Allpages_exec_btn().Btn_img_(app.Gui_mgr().Kit().New_img_load(img_dir.GenSubFil("allpages_exec.png")));
    +		win.Find_close_btn().Btn_img_(app.Gui_mgr().Kit().New_img_load(img_dir.GenSubFil("find_close.png")));
    +		win.Find_bwd_btn().Btn_img_(app.Gui_mgr().Kit().New_img_load(img_dir.GenSubFil("find_bwd.png")));
    +		win.Find_fwd_btn().Btn_img_(app.Gui_mgr().Kit().New_img_load(img_dir.GenSubFil("find_fwd.png")));
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Cfg__enabled))	  {this.Enabled_(m.ReadBool("v")); app.Api_root().Gui().Browser().Nightmode_reload();}
    +		else if	(ctx.MatchIn(k, Cfg__night_back)) {if (enabled) this.Set_color(COLOR_GROUP_BACK, Parse(k, m.ReadStr("v"), null));}
    +		else if	(ctx.MatchIn(k, Cfg__night_fore)) {if (enabled) this.Set_color(COLOR_GROUP_FORE, Parse(k, m.ReadStr("v"), null));}
    +		else if	(ctx.MatchIn(k, Cfg__night_edge)) {if (enabled) this.Set_color(COLOR_GROUP_EDGE, Parse(k, m.ReadStr("v"), null));}
    +		else if	(ctx.MatchIn(k, Cfg__day_back))   {if (!enabled) this.Set_color(COLOR_GROUP_BACK, Parse(k, m.ReadStr("v"), null));}
    +		else if	(ctx.MatchIn(k, Cfg__day_fore))   {if (!enabled) this.Set_color(COLOR_GROUP_FORE, Parse(k, m.ReadStr("v"), null));}
    +		else if	(ctx.MatchIn(k, Cfg__day_edge))   {if (!enabled) this.Set_color(COLOR_GROUP_EDGE, Parse(k, m.ReadStr("v"), null));}
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}
    +	public static final    String
    +	  Cfg__enabled     = "xowa.gui.nightmode.enabled"
    +	, Cfg__night_back  = "xowa.gui.nightmode.nightcolors.backcolor"
    +	, Cfg__night_fore  = "xowa.gui.nightmode.nightcolors.forecolor"
    +	, Cfg__night_edge  = "xowa.gui.nightmode.nightcolors.edgecolor"
    +	, Cfg__day_back    = "xowa.gui.nightmode.daycolors.backcolor"
    +	, Cfg__day_fore    = "xowa.gui.nightmode.daycolors.forecolor"
    +	, Cfg__day_edge    = "xowa.gui.nightmode.daycolors.edgecolor"
    +	;
    +
    +	private static final int COLOR_GROUP_BACK = 0, COLOR_GROUP_FORE = 1, COLOR_GROUP_EDGE = 2;
    +	private static ColorAdp Parse_from_cfg(Xoa_app app, String key, ColorAdp or) {
    +		return Parse(key, app.Cfg().Get_str_app_or(key, null), or);
    +	}
    +	private static ColorAdp Parse(String key, String val, ColorAdp or) {
    +		try {
    +			return val == null ? or : ColorAdp_.parse_hex_("#00" + val);	// parse_hex requires leading "#00"
    +		} catch (Exception e) {
    +			Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to parse color; key=~{0} val=~{1} err=~{2}", key, val, Err_.Message_gplx_log(e));
    +			return or;
    +		}
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/url_box_fmts/Xog_urlfmtr_mgr.java b/400_xowa/src/gplx/xowa/guis/views/url_box_fmts/Xog_urlfmtr_mgr.java
    index a27517de8..3daf8faba 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/url_box_fmts/Xog_urlfmtr_mgr.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/url_box_fmts/Xog_urlfmtr_mgr.java
    @@ -13,3 +13,62 @@ 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.guis.views.url_box_fmts; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.views.*;
    +public class Xog_urlfmtr_mgr implements Gfo_invk {
    +	private Xog_urlfmtr_itm wildcard = new Xog_urlfmtr_itm(Byte_ascii.Star_bry, Bry_.new_a7("~{wiki_domain}/wiki/~{page_title}"));
    +	private final    Bry_bfr bfr = Bry_bfr_.New();
    +	private final    Hash_adp_bry hash = Hash_adp_bry.cs();
    +	public boolean Exists() {return exists;} private boolean exists = false;
    +	public void Init_by_app(Xoa_app app) {
    +		app.Cfg().Bind_many_app(this, Cfg__url_format);
    +	}
    +	public void Parse(byte[] src) {
    +		// clear
    +		exists = false;
    +		wildcard = new Xog_urlfmtr_itm(Byte_ascii.Star_bry, Bry_.new_a7("~{wiki_domain}/wiki/~{page_title}"));
    +		hash.Clear();
    +
    +		// exit if blank
    +		if (Bry_.Len_eq_0(src)) return;
    +
    +		// parse lines
    +		exists = true;
    +		byte[][] lines = Bry_split_.Split_lines(src);
    +		for (byte[] line : lines) {
    +			byte[][] parts = Bry_split_.Split(line, Byte_ascii.Pipe);
    +			if (parts.length != 2) {
    +				Gfo_usr_dlg_.Instance.Warn_many("", "", "xog_urlfmtr:invalid_line; line=~{0}", line);
    +				continue;
    +			}
    +			byte[] domain = parts[0];
    +			Xog_urlfmtr_itm itm = new Xog_urlfmtr_itm(domain, parts[1]);
    +			if (Bry_.Eq(domain, Byte_ascii.Star_bry)) {
    +				wildcard = itm;
    +			}
    +			else {
    +				hash.Add_if_dupe_use_nth(domain, itm);
    +			}
    +		}
    +	}
    +	public String Gen(Xoa_url url) {
    +		Xog_urlfmtr_itm itm = (Xog_urlfmtr_itm)hash.Get_by(url.Wiki_bry());
    +		if (itm == null) {
    +			itm = wildcard;
    +		}
    +		return itm.Gen(bfr, url);
    +	}
    +	public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {
    +		if		(ctx.Match(k, Cfg__url_format)) {Parse(m.ReadBry("v"));}
    +		else	return Gfo_invk_.Rv_unhandled;
    +		return this;
    +	}	private static final String Cfg__url_format = "xowa.gui.url_bar.url_format";
    +}
    +class Xog_urlfmtr_itm {
    +	private final    Bry_fmt fmt;
    +	public Xog_urlfmtr_itm(byte[] wiki_domain, byte[] fmt_str) {
    +		this.fmt = Bry_fmt.New(fmt_str, "wiki_domain", "page_title", "page_title_spaces");
    +	}
    +	public String Gen(Bry_bfr bfr, Xoa_url url) {
    +		return fmt.Bld_many_to_str(bfr, url.Wiki_bry(), url.Page_bry(), Bry_.Replace(url.Page_bry(), Byte_ascii.Underline, Byte_ascii.Space));
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/guis/views/url_box_fmts/Xog_urlfmtr_mgr_tst.java b/400_xowa/src/gplx/xowa/guis/views/url_box_fmts/Xog_urlfmtr_mgr_tst.java
    index a27517de8..cf27e0de5 100644
    --- a/400_xowa/src/gplx/xowa/guis/views/url_box_fmts/Xog_urlfmtr_mgr_tst.java
    +++ b/400_xowa/src/gplx/xowa/guis/views/url_box_fmts/Xog_urlfmtr_mgr_tst.java
    @@ -13,3 +13,50 @@ 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.guis.views.url_box_fmts; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*; import gplx.xowa.guis.views.*;
    +import org.junit.*; import gplx.core.tests.*;
    +import gplx.xowa.apps.urls.*;
    +public class Xog_urlfmtr_mgr_tst {
    +	private Xog_urlfmtr_mgr_fxt fxt = new Xog_urlfmtr_mgr_fxt();
    +	@Test  public void Basic() {
    +		String fmt_suffix = "domain:~{wiki_domain}; page_unders:~{page_title_unders}; page_spaces:~{page_title_spaces}";
    +		fxt.Init__init_by_parse
    +		( "*|wild -- " + fmt_suffix
    +		, "en.wikipedia.org|en.w -- " + fmt_suffix
    +		, "de.wikibooks.org|de.b -- " + fmt_suffix
    +		);
    +		fxt.Test__gen_or_null("en.wikipedia.org/wiki/Page_1", "en.w -- domain:en.wikipedia.org; page_unders:Page_1; page_spaces:Page 1");
    +		fxt.Test__gen_or_null("de.wikibooks.org/wiki/Page_1", "de.b -- domain:de.wikibooks.org; page_unders:Page_1; page_spaces:Page 1");
    +		fxt.Test__gen_or_null("fr.wikibooks.org/wiki/Page_1", "wild -- domain:fr.wikibooks.org; page_unders:Page_1; page_spaces:Page 1");
    +	}
    +	@Test  public void Wildcard_default() {
    +		String fmt_suffix = "domain:~{wiki_domain}; page_unders:~{page_title_unders}; page_spaces:~{page_title_spaces}";
    +		fxt.Init__init_by_parse
    +		( "en.wikipedia.org|en.w -- " + fmt_suffix
    +		, "de.wikibooks.org|de.b -- " + fmt_suffix
    +		);
    +		fxt.Test__gen_or_null("en.wikipedia.org/wiki/Page_1", "en.w -- domain:en.wikipedia.org; page_unders:Page_1; page_spaces:Page 1");
    +		fxt.Test__gen_or_null("fr.wikibooks.org/wiki/Page_1", "fr.wikibooks.org/wiki/Page_1");
    +	}
    +}
    +class Xog_urlfmtr_mgr_fxt {
    +	private final    Xog_urlfmtr_mgr mgr = new Xog_urlfmtr_mgr();
    +	private final    Xow_url_parser url_parser;
    +	public Xog_urlfmtr_mgr_fxt() {
    +		// create url parser
    +		Xoae_app app = Xoa_app_fxt.Make__app__edit();
    +		Xow_wiki wiki = Xoa_app_fxt.Make__wiki__edit(app);
    +		this.url_parser = new Xow_url_parser(wiki);
    +
    +		// reg xwikis
    +		wiki.App().User().Wikii().Xwiki_mgr().Add_by_atrs("de.wikibooks.org", "de.wikibooks.org");
    +		wiki.App().User().Wikii().Xwiki_mgr().Add_by_atrs("fr.wikibooks.org", "fr.wikibooks.org");
    +	}
    +	public void Init__init_by_parse(String... lines) {
    +		mgr.Parse(Bry_.new_u8(String_.Concat_lines_nl_skip_last(lines)));
    +	}
    +	public void Test__gen_or_null(String url_str, String expd) {
    +		Xoa_url url = url_parser.Parse(Bry_.new_u8(url_str));
    +		Gftest.Eq__str(expd, mgr.Gen(url));
    +	}
    +}
    diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_cfg_file.java b/400_xowa/src/gplx/xowa/htmls/Xoh_cfg_file.java
    index a27517de8..44310ff3d 100644
    --- a/400_xowa/src/gplx/xowa/htmls/Xoh_cfg_file.java
    +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_cfg_file.java
    @@ -13,3 +13,16 @@ 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.htmls; import gplx.*; import gplx.xowa.*;
    +import gplx.langs.htmls.encoders.*;
    +public class Xoh_cfg_file {
    +	public Xoh_cfg_file(Gfo_url_encoder url_encoder, Io_url xowa_dir) {
    +		Io_url mw_file_dir = xowa_dir.GenSubDir_nest("file", "mediawiki.file");
    +		img_media_play_btn = url_encoder.Encode_to_file_protocol(mw_file_dir.GenSubFil("play.png"));
    +		img_media_info_btn = url_encoder.Encode_to_file_protocol(mw_file_dir.GenSubFil("info.png"));
    +		img_thumb_magnify = url_encoder.Encode_to_file_protocol(mw_file_dir.GenSubFil("magnify-clip.png"));
    +	}
    +	public byte[] Img_media_play_btn() {return img_media_play_btn;} private final byte[] img_media_play_btn;
    +	public byte[] Img_media_info_btn() {return img_media_info_btn;} private final byte[] img_media_info_btn;
    +	public byte[] Img_thumb_magnify() {return img_thumb_magnify;} private final byte[] img_thumb_magnify;
    +}
    diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_cmd_itm.java b/400_xowa/src/gplx/xowa/htmls/Xoh_cmd_itm.java
    index a27517de8..3773df561 100644
    --- a/400_xowa/src/gplx/xowa/htmls/Xoh_cmd_itm.java
    +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_cmd_itm.java
    @@ -13,3 +13,9 @@ 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.htmls; import gplx.*; import gplx.xowa.*;
    +public interface Xoh_cmd_itm {
    +	String Hcmd_id();
    +	void Hcmd_exec(Xoae_app app, Gfo_usr_dlg usr_dlg, Xoae_page page);
    +	void Hcmd_write(Xoae_app app, Gfo_usr_dlg usr_dlg, Xoae_page page);
    +}
    diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_cmd_mgr.java b/400_xowa/src/gplx/xowa/htmls/Xoh_cmd_mgr.java
    index a27517de8..1f2e4d374 100644
    --- a/400_xowa/src/gplx/xowa/htmls/Xoh_cmd_mgr.java
    +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_cmd_mgr.java
    @@ -13,3 +13,26 @@ 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.htmls; import gplx.*; import gplx.xowa.*;
    +public class Xoh_cmd_mgr {
    +	public int Count() {return cmds.Count();}
    +	public void Clear() {cmds.Clear();}
    +	public void Add(Xoh_cmd_itm itm) {cmds.Add(itm);} List_adp cmds = List_adp_.New();
    +	public void Exec(Xoae_app app, Xoae_page page) {
    +		int len = cmds.Count();
    +		if (len == 0) return;
    +		Gfo_usr_dlg usr_dlg = app.Usr_dlg();
    +		usr_dlg.Prog_one(GRP_KEY, "bgn", "cmds bgn: ~{0}", len);	// NOTE: this message will not show, but is needed for other messages to show; SWT swallowing 1st msg before showing others?; DATE:2013-04-25
    +		for (int i = 0; i < len; i++) {
    +			if (usr_dlg.Canceled()) {usr_dlg.Prog_none(GRP_KEY, "cmds.done", ""); app.Log_wtr().Queue_enabled_(false); return;}
    +			Xoh_cmd_itm itm = null;
    +			try {
    +				itm = (Xoh_cmd_itm)cmds.Get_at(i);
    +				itm.Hcmd_exec(app, usr_dlg, page);
    +				itm.Hcmd_write(app, usr_dlg, page);
    +			} catch (Exception e) {throw Err_.new_exc(e, "xo", "failed to execute html cmd", "name", itm == null ? "unknown" : itm.Hcmd_id());}
    +		}
    +		this.Clear();
    +	}
    +	static final String GRP_KEY = "xowa.html.cmd_mgr";
    +}
    diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_consts.java b/400_xowa/src/gplx/xowa/htmls/Xoh_consts.java
    index a27517de8..becb8bef1 100644
    --- a/400_xowa/src/gplx/xowa/htmls/Xoh_consts.java
    +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_consts.java
    @@ -13,3 +13,39 @@ 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.htmls; import gplx.*; import gplx.xowa.*;
    +public class Xoh_consts {
    +	public static final String
    +	  Atr_xowa_title_str		= "xowa_title"
    +	, Img_w_str					= "width"
    +	, Img_h_str					= "height"
    +	;
    +	public static final byte[] 
    +	  __end				= Bry_.new_a7(">")
    +	, __inline			= Bry_.new_a7("/>")
    +	, __end_quote		= Bry_.new_a7("\">")
    +	, __inline_quote	= Bry_.new_a7("\"/>")
    +	, Space_2			= Bry_.new_a7("  ")
    +
    +	, A_mid_id = Bry_.new_a7("\" id=\"xolnki_")
    +	, Div_bgn_open = Bry_.new_a7("
    ") + , Span_bgn = Bry_.new_a7("") + + , Pre_bgn = Bry_.new_a7("
    "), Pre_end = Bry_.new_a7("
    ") + , Pre_bgn_open = Bry_.new_a7("") + + , Code_bgn_closed = Bry_.new_a7("") + , Code_bgn_open = Bry_.new_a7("") + , Id_atr = Bry_.new_a7(" id=\"") + , Style_atr = Bry_.new_a7(" style=\"") + , Atr_xowa_title_bry = Bry_.new_a7(Atr_xowa_title_str) + ; + public static final int Nbsp_int = 160; + public static String Escape_apos(String s) {return String_.Replace(s, "'", "\"");} +} diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_html_mgr.java b/400_xowa/src/gplx/xowa/htmls/Xoh_html_mgr.java index a27517de8..5fb1f3ec2 100644 --- a/400_xowa/src/gplx/xowa/htmls/Xoh_html_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_html_mgr.java @@ -13,3 +13,19 @@ 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.htmls; import gplx.*; import gplx.xowa.*; +import gplx.xowa.htmls.core.htmls.tidy.*; import gplx.xowa.htmls.js.*; import gplx.xowa.htmls.skins.*; +import gplx.xowa.parsers.xndes.*; +public class Xoh_html_mgr implements Gfo_invk { + public Xoh_html_mgr(Xoae_app app) {} + public void Init_by_app(Xoae_app app) { + page_mgr.Init_by_app(app); + } + public Xoh_page_mgr Page_mgr() {return page_mgr;} private final Xoh_page_mgr page_mgr = new Xoh_page_mgr(); + public Xoh_skin_mgr Skin_mgr() {return skin_mgr;} private final Xoh_skin_mgr skin_mgr = new Xoh_skin_mgr(); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_page)) return page_mgr; + else if (ctx.Match(k, Invk_skins)) return skin_mgr; + else return Gfo_invk_.Rv_unhandled; + } private static final String Invk_page = "page", Invk_skins = "skins"; +} diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_img_mgr.java b/400_xowa/src/gplx/xowa/htmls/Xoh_img_mgr.java index a27517de8..107635839 100644 --- a/400_xowa/src/gplx/xowa/htmls/Xoh_img_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_img_mgr.java @@ -13,3 +13,38 @@ 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.htmls; import gplx.*; import gplx.xowa.*; +import gplx.xowa.files.*; +public class Xoh_img_mgr { + private final List_adp list = List_adp_.New(); + private int uid_nxt = -1; + public void Clear() { + this.uid_nxt = -1; + list.Clear(); + } + public int Len() {return list.Count();} + public Xof_fsdb_itm Get_at(int i) {return (Xof_fsdb_itm)list.Get_at(i);} + public Xof_fsdb_itm Make_aud() { + Xof_fsdb_itm itm = new Xof_fsdb_itm(); + itm.Init_at_hdoc(++uid_nxt, Xof_html_elem.Tid_aud); + list.Add(itm); + return itm; + } + public Xof_fsdb_itm Make_img(boolean img_is_gallery) { + Xof_fsdb_itm itm = new Xof_fsdb_itm(); + itm.Init_at_hdoc(++uid_nxt, img_is_gallery ? Xof_html_elem.Tid_gallery : Xof_html_elem.Tid_img); + if (img_is_gallery) + itm.Html_gallery_mgr_h_(gplx.xowa.xtns.gallery.Gallery_xnde.Default); // TODO_OLD:set to ; PAGE:en.w:National_Gallery_of_Art; DATE:2016-06-25 + list.Add(itm); + return itm; + } + public void To_bfr(Bry_bfr bfr) { + int len = this.Len(); + for (int i = 0; i < len; ++i) { + Xof_fsdb_itm itm = this.Get_at(i); + itm.To_bfr(bfr); + } + } + public static final String Str__html_uid = "xoimg_"; + public static final byte[] Bry__html_uid = Bry_.new_a7(Str__html_uid); +} diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_page.java b/400_xowa/src/gplx/xowa/htmls/Xoh_page.java index a27517de8..c5f3ad062 100644 --- a/400_xowa/src/gplx/xowa/htmls/Xoh_page.java +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_page.java @@ -13,3 +13,92 @@ 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.htmls; import gplx.*; import gplx.xowa.*; +import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.skins.*; import gplx.xowa.wikis.pages.lnkis.*; import gplx.xowa.wikis.pages.redirects.*; +import gplx.xowa.files.*; +import gplx.xowa.langs.*; +import gplx.xowa.htmls.heads.*; import gplx.xowa.htmls.sections.*; import gplx.xowa.addons.htmls.tocs.*; +import gplx.xowa.wikis.pages.dbs.*; import gplx.xowa.wikis.pages.hdumps.*; import gplx.xowa.wikis.pages.htmls.*; import gplx.xowa.wikis.pages.wtxts.*; +public class Xoh_page implements Xoa_page { + // core + public Xow_wiki Wiki() {return wiki;} private Xow_wiki wiki; + public Xoa_url Url() {return page_url;} private Xoa_url page_url; + public Xoa_ttl Ttl() {return page_ttl;} private Xoa_ttl page_ttl; + public Xopg_db_data Db() {return db;} private final Xopg_db_data db = new Xopg_db_data(); + public Xopg_redirect_mgr Redirect_trail() {return redirect;} private final Xopg_redirect_mgr redirect = new Xopg_redirect_mgr(); + public Xopg_html_data Html_data() {return html;} private final Xopg_html_data html = new Xopg_html_data(); + public Xopg_wtxt_data Wtxt() {return wtxt;} private final Xopg_wtxt_data wtxt = new Xopg_wtxt_data(); + public Xopg_hdump_data Hdump_mgr() {return hdump;} private final Xopg_hdump_data hdump = new Xopg_hdump_data(); + public Xol_lang_itm Lang() {return lang;} private Xol_lang_itm lang; + private Guid_adp page_guid; + public Guid_adp Page_guid() { + if (page_guid == null) { + page_guid = Guid_adp_.New(); + } + return page_guid; + } + + public boolean Xtn__timeline_exists() {return xtn__timeline_exists;} private boolean xtn__timeline_exists; public void Xtn__timeline_exists_y_() {xtn__timeline_exists = true;} + public boolean Xtn__gallery_exists() {return xtn__gallery_exists;} private boolean xtn__gallery_exists; public void Xtn__gallery_exists_y_() {xtn__gallery_exists = true;} + + // props + public int Page_id() {return page_id;} private int page_id; + public byte[] Display_ttl() {return display_ttl;} private byte[] display_ttl; + public byte[] Content_sub() {return content_sub;} private byte[] content_sub; + public byte[] Sidebar_div() {return sidebar_div;} private byte[] sidebar_div; + public Xoh_section_mgr Section_mgr() {return section_mgr;} private final Xoh_section_mgr section_mgr = new Xoh_section_mgr(); + public Xoh_img_mgr Img_mgr() {return img_mgr;} private Xoh_img_mgr img_mgr = new Xoh_img_mgr(); + public Xopg_module_mgr Head_mgr() {return head_mgr;} private Xopg_module_mgr head_mgr = new Xopg_module_mgr(); + + // util + public Xoa_page__commons_mgr Commons_mgr() {return commons_mgr;} private final Xoa_page__commons_mgr commons_mgr = new Xoa_page__commons_mgr(); + public int Exec_tid() {return exec_tid;} private int exec_tid = Xof_exec_tid.Tid_wiki_page; + public byte[] Html_head_xtn() {return html_head_xtn;} public void Html_head_xtn_(byte[] v) {html_head_xtn = v;} private byte[] html_head_xtn = Bry_.Empty; // drd:web_browser + public byte[] Url_bry_safe() {return Xoa_page_.Url_bry_safe(page_url, wiki, page_ttl);} + public void Ctor_by_hview(Xow_wiki wiki, Xoa_url page_url, Xoa_ttl page_ttl, int page_id) { + this.wiki = wiki; this.page_url = page_url; this.page_ttl = page_ttl; this.page_id = page_id; + this.lang = wiki.Lang(); + this.Clear(); + html.Redlink_list().Disabled_(page_ttl.Ns().Id_is_module()); // never redlink in Module ns; particularly since Lua has multi-line comments for [[ ]] + html.Toc_mgr().Init(gplx.xowa.htmls.core.htmls.tidy.Xow_tidy_mgr_interface_.Noop, wiki.Lang().Msg_mgr().Itm_by_id_or_null(gplx.xowa.langs.msgs.Xol_msg_itm_.Id_toc).Val(), page_url.Raw()); + } + public Xoh_page Ctor_by_hdiff(Bry_bfr tmp_bfr, Xoae_page page, byte[] toc_label) { + this.wiki = page.Wiki(); this.page_url = page.Url(); this.page_ttl = page.Ttl(); this.page_id = page.Db().Page().Id(); + this.lang = wiki.Lang(); + + db.Html().Html_bry_(page.Db().Html().Html_bry()); + + Xopg_html_data html = page.Html_data(); + html.Init_by_page(page.Ttl()); + Xoh_head_mgr mod_mgr = html.Head_mgr(); + head_mgr.Init(mod_mgr.Itm__mathjax().Enabled(), mod_mgr.Itm__popups().Bind_hover_area(), mod_mgr.Itm__gallery().Enabled(), mod_mgr.Itm__hiero().Enabled()); + this.display_ttl = html.Display_ttl(); + this.content_sub = html.Content_sub(); + this.sidebar_div = Xoh_page_.Save_sidebars(tmp_bfr, page, html); + + html.Toc_mgr().Init(page.Wikie().Html_mgr().Tidy_mgr(), toc_label, page_url.Page_bry()); // NOTE: do not pass in noop tidy_mgr, else broken TOC html will never get corrected during hdump; DATE:2016-08-14 + return this; + } + public void Ctor_by_db(int head_flag, byte[] display_ttl, byte[] content_sub, byte[] sidebar_div, int zip_tid, int hzip_tid, byte[] body) { + head_mgr.Flag_(head_flag); + this.display_ttl = display_ttl; this.content_sub = content_sub; this.sidebar_div = sidebar_div; + db.Html().Html_bry_(body); + db.Html().Zip_tids_(zip_tid, hzip_tid); + } + public void Clear() { + redirect.Clear(); + html.Clear(); + wtxt.Clear(); + hdump.Clear(); + db.Clear(); + + display_ttl = content_sub = sidebar_div = Bry_.Empty; + head_mgr.Clear(); commons_mgr.Clear(); + section_mgr.Clear(); img_mgr.Clear(); + } + public static Xoh_page New_missing() { + Xoh_page rv = new Xoh_page(); + rv.Db().Page().Exists_n_(); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_page_.java b/400_xowa/src/gplx/xowa/htmls/Xoh_page_.java index a27517de8..9f418ebcf 100644 --- a/400_xowa/src/gplx/xowa/htmls/Xoh_page_.java +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_page_.java @@ -13,3 +13,20 @@ 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.htmls; import gplx.*; import gplx.xowa.*; +import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.skins.*; import gplx.xowa.wikis.pages.htmls.*; +public class Xoh_page_ { + public static byte[] Save_sidebars(Bry_bfr tmp_bfr, Xoae_page page, Xopg_html_data html_data) { + Xopg_xtn_skin_mgr mgr = html_data.Xtn_skin_mgr(); + int len = mgr.Count(); + boolean sidebar_exists = false; + for (int i = 0; i < len; ++i) { + Xopg_xtn_skin_itm itm = mgr.Get_at(i); + if (itm.Tid() == Xopg_xtn_skin_itm_tid.Tid_sidebar) { + sidebar_exists = true; + itm.Write(tmp_bfr, page); + } + } + return sidebar_exists ? tmp_bfr.To_bry_and_clear() : null; + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_page_bfr.java b/400_xowa/src/gplx/xowa/htmls/Xoh_page_bfr.java index a27517de8..adb53cebb 100644 --- a/400_xowa/src/gplx/xowa/htmls/Xoh_page_bfr.java +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_page_bfr.java @@ -13,3 +13,36 @@ 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.htmls; import gplx.*; import gplx.xowa.*; +import gplx.xowa.htmls.core.wkrs.tocs.*; +public class Xoh_page_bfr { + private byte toc_mode; + private Bry_bfr head_bfr; private final Bry_bfr body_bfr = Bry_bfr_.New(); + public void Init(Bry_bfr head_bfr) { + this.toc_mode = Xoh_toc_data.Toc_mode__none; + this.head_bfr = head_bfr; + body_bfr.Clear(); + } + public Bry_bfr Split_by_toc(byte toc_mode) { + if (this.toc_mode != Xoh_toc_data.Toc_mode__pgbnr)// NOTE: "none" and "pgbnr" can exist on same page (especially in en.v); must make sure that "none" does not overwrite "pgbnr" else wide images; PAGE:en.v:UNESCO_World_Heritage_List DATE:2016-11-03 + this.toc_mode = toc_mode; + return body_bfr; + } + public void Commit(Xoa_page pg) { + boolean toc_mode_enabled = true, toc_mode_is_pgbnr = false; // default to Xoh_toc_data_.Toc_mode__basic + switch (toc_mode) { + case Xoh_toc_data.Toc_mode__none : toc_mode_enabled = false; break; + case Xoh_toc_data.Toc_mode__pgbnr : toc_mode_is_pgbnr = true; break; + } + + // set flags + pg.Html_data().Toc_mgr().Exists_y_(); + pg.Html_data().Head_mgr().Itm__pgbnr().Enabled_(toc_mode_is_pgbnr); + + // build bfr by add bfr_0, toc, body_bfr + if (toc_mode_enabled) { + pg.Html_data().Toc_mgr().To_html(head_bfr, gplx.xowa.htmls.core.htmls.Xoh_wtr_ctx.Basic, toc_mode_is_pgbnr); + head_bfr.Add_bfr_and_clear(body_bfr); + } + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_page_html_source.java b/400_xowa/src/gplx/xowa/htmls/Xoh_page_html_source.java index a27517de8..67ce73c6f 100644 --- a/400_xowa/src/gplx/xowa/htmls/Xoh_page_html_source.java +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_page_html_source.java @@ -13,3 +13,7 @@ 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.htmls; import gplx.*; import gplx.xowa.*; +public interface Xoh_page_html_source { + byte[] Get_page_html(); +} diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_page_html_source_.java b/400_xowa/src/gplx/xowa/htmls/Xoh_page_html_source_.java index a27517de8..8aad91da1 100644 --- a/400_xowa/src/gplx/xowa/htmls/Xoh_page_html_source_.java +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_page_html_source_.java @@ -13,3 +13,16 @@ 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.htmls; import gplx.*; import gplx.xowa.*; +public class Xoh_page_html_source_ { + public static Xoh_page_html_source + Wtr = new Xoh_page_html_source__wtr() + , Noop = new Xoh_page_html_source__noop() + ; +} +class Xoh_page_html_source__wtr implements Xoh_page_html_source { + public byte[] Get_page_html() {return null;} +} +class Xoh_page_html_source__noop implements Xoh_page_html_source { + public byte[] Get_page_html() {return null;} +} diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_page_mgr.java b/400_xowa/src/gplx/xowa/htmls/Xoh_page_mgr.java index a27517de8..b6d8cdf46 100644 --- a/400_xowa/src/gplx/xowa/htmls/Xoh_page_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_page_mgr.java @@ -13,3 +13,49 @@ 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.htmls; import gplx.*; import gplx.xowa.*; +import gplx.xowa.htmls.portal.*; +public class Xoh_page_mgr implements Gfo_invk { + private boolean font_enabled = false; + private String font_name = "Arial"; + private byte[] font_css_bry = Bry_.Empty, custom_script = Bry_.Empty; + private final Bry_fmt font_css_fmt = Bry_fmt.Auto("body {font-family: ~{font_name}; font-size: ~{font_size}px;}"); + public float Font_size() {return font_size;} private float font_size = Font_size_default; + private void Font_css_bry_() { + font_css_bry = font_css_fmt.Bld_many_to_bry(Bry_bfr_.New(), font_name, font_size); + } + public void Write_css(gplx.xowa.htmls.heads.Xoh_head_wtr wtr) { + if (font_enabled) + wtr.Write_css_style_itm(font_css_bry); + if (Bry_.Len_gt_0(custom_script)) + wtr.Write_css_style_itm(custom_script); + } + + public Bry_fmt Content_code_fmt() {return content_code_fmt;} private final Bry_fmt content_code_fmt = Bry_fmt.Auto("
    ~{page_text}
    "); + public Xoh_subpages_bldr Subpages_bldr() {return subpages_bldr;} private final Xoh_subpages_bldr subpages_bldr = new Xoh_subpages_bldr(); + public void Init_by_app(Xoa_app app) { + app.Cfg().Bind_many_app(this, Cfg__font_enabled, Cfg__font_name, Cfg__font_size, Cfg__font_format, Cfg__custom_script, Cfg__content_code_fmt); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Cfg__font_enabled)) font_enabled = m.ReadYn("v"); + else if (ctx.Match(k, Cfg__font_name)) {font_name = m.ReadStr("v"); Font_css_bry_();} + else if (ctx.Match(k, Cfg__font_size)) {font_size = m.ReadFloat("v"); Font_css_bry_();} + else if (ctx.Match(k, Cfg__font_format)) {font_css_fmt.Fmt_(m.ReadBry("v")); Font_css_bry_();} + else if (ctx.Match(k, Cfg__custom_script)) custom_script = m.ReadBry("v"); + else if (ctx.Match(k, Cfg__content_code_fmt)) content_code_fmt.Fmt_(m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + + public static final String + Cfg__font_enabled = "xowa.html.css.font.enabled" + , Cfg__font_size = "xowa.html.css.font.size" + ; + private static final String + Cfg__font_name = "xowa.html.css.font.name" + , Cfg__font_format = "xowa.html.css.font.format" + , Cfg__custom_script = "xowa.html.css.custom.script" + , Cfg__content_code_fmt = "xowa.html.page.content_code_fmt" + ; + public static final float Font_size_default = 16; +} diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_mgr.java b/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_mgr.java index a27517de8..60f376639 100644 --- a/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_mgr.java @@ -13,3 +13,99 @@ 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.htmls; import gplx.*; import gplx.xowa.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.wikis.pages.*; +public class Xoh_page_wtr_mgr implements Gfo_invk { + private final Bry_bfr tmp_bfr = Bry_bfr_.Reset(255), html_bfr = Bry_bfr_.Reset(Io_mgr.Len_mb); + private Xoh_page_wtr_wkr edit_wtr, html_wtr, read_wtr; + public Xoh_page_wtr_mgr(boolean html_capable) { + this.html_capable = html_capable; + this.read_wtr = new Xoh_page_wtr_wkr(this, Xopg_page_.Tid_read); + this.edit_wtr = new Xoh_page_wtr_wkr(this, Xopg_page_.Tid_edit); + this.html_wtr = new Xoh_page_wtr_wkr(this, Xopg_page_.Tid_html); + } + public boolean Html_capable() {return html_capable;} public Xoh_page_wtr_mgr Html_capable_(boolean v) {html_capable = v; return this;} private boolean html_capable; + public byte[] Css_common_bry() {return css_common_bry;} private byte[] css_common_bry; + public byte[] Css_wiki_bry() {return css_wiki_bry;} private byte[] css_wiki_bry; + public byte[] Css_night_bry(boolean nightmode_enabled) {return nightmode_enabled ? css_night_bry : Bry_.Empty;} private byte[] css_night_bry; + public boolean Scripting_enabled() {return scripting_enabled;} private boolean scripting_enabled; + public Bry_fmtr Page_read_fmtr() {return page_read_fmtr;} private Bry_fmtr page_read_fmtr = Bry_fmtr.new_("", Fmtr_keys); + public Bry_fmtr Page_edit_fmtr() {return page_edit_fmtr;} private Bry_fmtr page_edit_fmtr = Bry_fmtr.new_("", Fmtr_keys); + public Bry_fmtr Page_html_fmtr() {return page_html_fmtr;} private Bry_fmtr page_html_fmtr = Bry_fmtr.new_("", Fmtr_keys); + public byte[] Edit_rename_div_bry(Xoa_ttl ttl) {return div_edit_rename_fmtr.Bld_bry_many(tmp_bfr, ttl.Full_db());} + public void Init_css_urls(Xoa_app app, String wiki_domain, Io_url css_common_url, Io_url css_wiki_url) { + this.css_common_bry = css_common_url.To_http_file_bry(); + this.css_wiki_bry = css_wiki_url.To_http_file_bry(); + + // xowa_night.css; + Io_url css_night_url = app.Fsys_mgr().Url_finder().Find_by_css_or(wiki_domain, "xowa_night.css", String_.Ary("bin", "any", "xowa", "html", "css", "nightmode"), true); + this.css_night_bry = Bry_.new_u8(""); + } + public void Init_(boolean v) {init = v;} private boolean init = true; + public void Init_by_wiki(Xow_wiki wiki) { + wiki.App().Cfg().Bind_many_wiki(this, wiki, Cfg__scripting_enabled); + } + public byte[] Gen(Xoae_page page, byte output_tid) {return Gen(page, Xoh_page_html_source_.Noop, output_tid);} + public byte[] Gen(Xoae_page page, Xoh_page_html_source page_html_source, byte output_tid) { + Xoh_page_wtr_wkr wtr = Wkr(output_tid); + Xowe_wiki wiki = page.Wikie(); + if (init) { + init = false; + page_read_fmtr.Eval_mgr_(wiki.Eval_mgr()); + page_edit_fmtr.Eval_mgr_(wiki.Eval_mgr()); + page_html_fmtr.Eval_mgr_(wiki.Eval_mgr()); + } + wtr.Write_page(html_bfr, page, wiki.Parser_mgr().Ctx(), page_html_source); + return html_bfr.To_bry_and_clear_and_rls(); + } + public Xoh_page_wtr_wkr Wkr(byte output_tid) { + switch (output_tid) { + case Xopg_page_.Tid_edit: return edit_wtr; + case Xopg_page_.Tid_html: return html_wtr; + case Xopg_page_.Tid_read: return read_wtr; + default: throw Err_.new_unhandled(output_tid); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_page_read_)) page_read_fmtr.Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_page_edit_)) page_edit_fmtr.Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_page_html_)) page_html_fmtr.Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_xowa_div_edit_rename_)) div_edit_rename_fmtr.Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Cfg__scripting_enabled)) scripting_enabled = m.ReadYn("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private Bry_fmtr div_edit_rename_fmtr = Bry_fmtr.new_(String_.Concat_lines_nl + ( " " + , " " + , " Rename page" + , " " + , " " + , " Special:MovePage" + , " " + ), "src_full_db"); + public static final String Invk_page_read_ = "page_read_", Invk_page_edit_ = "page_edit_", Invk_page_html_ = "page_html_", Invk_xowa_div_edit_rename_ = "xowa_div_edit_rename_"; + private static final String[] Fmtr_keys = new String[] + { "app_root_dir", "app_version", "app_build_date", "xowa_mode_is_server" + , "page_id", "page_ttl_full", "page_name", "page_heading", "page_modified_on_msg" + , "html_css_common_path", "html_css_wiki_path", "html_css_night_tag", "xowa_head" + , "page_lang_ltr", "page_indicators", "page_content_sub", "page_jumpto", "page_pgbnr", "page_body_cls", "html_content_editable" + , "page_data", "page_langs" + , "portal_div_footer" + , "portal_div_personal", "portal_div_ns", "portal_div_view" + , "portal_div_logo", "portal_div_home", "portal_div_xtn" + , "portal_div_admin", "portal_div_wikis", "portal_sidebar" + , "edit_div_rename", "edit_div_preview", "js_edit_toolbar" + }; + private static final String Cfg__scripting_enabled = "xowa.html.scripting.enabled"; +} +/* +NOTE_1:xowa_anchor_button +. used for media (WP forces javascript with oggplayer. wanted "simpler" model) +. display:inline-block; must be set for centering to work; see USSR and anthem; (display:block; must be enabled in order for padding to work) +. text-align:center; forces img to be in center + +General notes: +. contentSub div is needed; PAGE:en.w:Battle of Spotsylvania Court House +*/ diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_mgr_tst.java b/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_mgr_tst.java index a27517de8..f8f8e0f0a 100644 --- a/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_mgr_tst.java @@ -13,3 +13,33 @@ 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.htmls; import gplx.*; import gplx.xowa.*; +import org.junit.*; +import gplx.xowa.guis.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.portal.*; import gplx.xowa.wikis.pages.*; +public class Xoh_page_wtr_mgr_tst { + @Before public void init() {} + @Test public void Logo_has_correct_main_page() { // PURPOSE: Logo href should be "/site/en.wikipedia.org/wiki/", not "/wiki/Main_Page" + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + Xowe_wiki wiki = Xoa_app_fxt.Make__wiki__edit(app); + Xow_portal_mgr portal_mgr = wiki.Html_mgr().Portal_mgr(); + Gfo_invk_.Invk_by_val(portal_mgr, Xow_portal_mgr.Invk_div_logo_, Bry_.new_a7("~{portal_nav_main_href}")); + portal_mgr.Init_assert(); + Xoh_page_wtr_mgr page_wtr_mgr = new Xoh_page_wtr_mgr(true); + page_wtr_mgr.Gen(wiki.Parser_mgr().Ctx().Page(), Xopg_page_.Tid_read); + Tfds.Eq(String_.new_a7(portal_mgr.Div_logo_bry(true)), "/site/en.wikipedia.org/wiki/"); + } + @Test public void Skip__math__basic() { + Xop_fxt fxt = Xop_fxt.New_app_html(); + fxt.Init_lang_vnts("zh-hans", "zh-hant"); + + fxt.Test__parse_to_html_mgr(String_.Concat_lines_nl_skip_last + ( "x_{1}-1" + , "x-{1+2}-1" + , "-{zh-hans:A;zh-hant:B;}-" + ), String_.Concat_lines_nl_skip_last + ( "x_{1}-1" // not converted + , "x-{1+2}-1" // not converted + , "A" // converted + )); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_wkr.java b/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_wkr.java index a27517de8..b05949444 100644 --- a/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_wkr.java +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_wkr.java @@ -13,3 +13,242 @@ 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.htmls; import gplx.*; import gplx.xowa.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; import gplx.langs.htmls.*; import gplx.xowa.langs.vnts.*; import gplx.xowa.htmls.core.htmls.*; +import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.skins.*; +import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; import gplx.xowa.parsers.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.xtns.pagebanners.*; +import gplx.xowa.apps.gfs.*; import gplx.xowa.htmls.portal.*; +import gplx.xowa.addons.wikis.ctgs.htmls.pageboxs.*; +import gplx.xowa.htmls.core.*; +public class Xoh_page_wtr_wkr { + private final Object thread_lock_1 = new Object(), thread_lock_2 = new Object(); + private final Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); + private final Xoh_page_wtr_mgr mgr; private final byte page_mode; + private final Wdata_xwiki_link_wtr wdata_lang_wtr = new Wdata_xwiki_link_wtr(); // In other languages + private final gplx.xowa.addons.apps.scripts.Xoscript_mgr scripting_mgr = new gplx.xowa.addons.apps.scripts.Xoscript_mgr(); + private Xoae_app app; private Xowe_wiki wiki; private Xoae_page page; private byte[] root_dir_bry; + public Xoh_page_wtr_wkr(Xoh_page_wtr_mgr mgr, byte page_mode) {this.mgr = mgr; this.page_mode = page_mode;} + public Xoh_page_wtr_wkr Ctgs_enabled_(boolean v) {ctgs_enabled = v; return this;} private boolean ctgs_enabled = true; + public void Write_page(Bry_bfr rv, Xoae_page page, Xop_ctx ctx, Xoh_page_html_source page_html_source) { + synchronized (thread_lock_1) { + this.page = page; this.wiki = page.Wikie(); this.app = wiki.Appe(); + ctx.Page_(page); // HACK: must update page for toc_mgr; WHEN: Xoae_page rewrite + Bry_fmtr fmtr = null; + if (mgr.Html_capable()) { + wdata_lang_wtr.Page_(page); + byte view_mode = page_mode; + switch (page_mode) { + case Xopg_page_.Tid_edit: fmtr = mgr.Page_edit_fmtr(); break; + case Xopg_page_.Tid_html: fmtr = mgr.Page_read_fmtr(); view_mode = Xopg_page_.Tid_read; break; // set view_mode to read, so that "read" is highlighted in HTML + case Xopg_page_.Tid_read: fmtr = mgr.Page_read_fmtr(); + // ctx.Page().Redlink_list().Clear(); // not sure if this is the best place to put it, but redlinks (a) must only fire once; (b) must fire before html generation; (c) cannot fire during edit (preview will handle separately); NOTE: probably put in to handle reusable redlink lists; redlink lists are now instantiated per page, so clear is not useful + break; + } + Bry_bfr page_bfr = wiki.Utl__bfr_mkr().Get_m001(); // NOTE: get separate page rv to output page; do not reuse tmp_bfr b/c it will be used inside Fmt_do + Xoh_wtr_ctx hctx = null; + if (page_mode == Xopg_page_.Tid_html && wiki.Html__hdump_mgr().Load_mgr().Html_mode().Tid_is_custom()) { + byte[] html_bry = null; + + // get html from html dump + if (wiki.Html__hdump_mgr().Load_mgr().Html_mode().Tid() == Xow_hdump_mode.Hdump_save.Tid()) { + hctx = Xoh_wtr_ctx.Hdump; + Write_body(page_bfr, ctx, hctx, page); + html_bry = page_bfr.To_bry_and_clear(); + } + // get from swt browser + else { + html_bry = page_html_source.Get_page_html(); + } + Write_page_by_tid(ctx, hctx, page_mode, rv, mgr.Page_html_fmtr(), Gfh_utl.Escape_html_as_bry(html_bry)); + } + else { + hctx = Xoh_wtr_ctx.Basic; + Write_body(page_bfr, ctx, hctx, page); + Write_page_by_tid(ctx, hctx, view_mode, rv, fmtr, page_bfr.To_bry_and_rls()); + scripting_mgr.Write(rv, wiki, page); + if (page_mode == Xopg_page_.Tid_html) // if html, write page again, but wrap it in html skin this time + Write_page_by_tid(ctx, hctx, page_mode, rv, mgr.Page_html_fmtr(), Gfh_utl.Escape_html_as_bry(rv.To_bry_and_clear())); + wdata_lang_wtr.Page_(null); + } + } + else + Write_body(rv, ctx, Xoh_wtr_ctx.Basic, page); + this.page = null; + } + } + private void Write_page_by_tid(Xop_ctx ctx, Xoh_wtr_ctx hctx, byte html_gen_tid, Bry_bfr bfr, Bry_fmtr fmtr, byte[] page_data) { + // if custom_html, use it and exit; needed for Default_tab + byte[] custom_html = page.Html_data().Custom_html(); + if (custom_html != null) {bfr.Add(custom_html); return;} + // temp variables + if (root_dir_bry == null) this.root_dir_bry = app.Fsys_mgr().Root_dir().To_http_file_bry(); + Xoa_ttl page_ttl = page.Ttl(); int page_ns_id = page_ttl.Ns().Id(); + byte page_tid = Xow_page_tid.Identify(wiki.Domain_tid(), page_ns_id, page_ttl.Page_db()); + DateAdp modified_on = page.Db().Page().Modified_on(); + byte[] modified_on_msg = wiki.Msg_mgr().Val_by_id_args(Xol_msg_itm_.Id_portal_lastmodified, modified_on.XtoStr_fmt_yyyy_MM_dd(), modified_on.XtoStr_fmt_HHmm()); + byte[] page_body_class = Xoh_page_body_cls.Calc(tmp_bfr, page_ttl, page_tid); + // byte[] html_content_editable = wiki.Gui_mgr().Cfg_browser().Content_editable() ? Content_editable_bry : Bry_.Empty; + byte[] html_content_editable = Bry_.Empty; + byte[] page_content_sub = Xoh_page_wtr_wkr_.Bld_page_content_sub(app, wiki, page, tmp_bfr); + byte[] js_edit_toolbar_bry = html_gen_tid == Xopg_page_.Tid_edit ? wiki.Fragment_mgr().Html_js_edit_toolbar() : Bry_.Empty; + Xol_vnt_mgr vnt_mgr = wiki.Lang().Vnt_mgr(); + if (vnt_mgr.Enabled()) { + byte[] converted_title = vnt_mgr.Convert_lang().Converted_title(); // prefer converted title + if (converted_title == null) // converted title does not exist; use regular page title and convert it + converted_title = vnt_mgr.Convert_lang().Auto_convert(vnt_mgr.Cur_itm(), page_ttl.Page_txt()); + page_ttl = Xoa_ttl.Parse(wiki, page_ttl.Ns().Id(), converted_title); + } + byte[] page_name = Xoh_page_wtr_wkr_.Bld_page_name(tmp_bfr, page_ttl, null); // NOTE: page_name does not show display_title (). always pass in null + byte[] page_display_title = Xoh_page_wtr_wkr_.Bld_page_name(tmp_bfr, page_ttl, page.Html_data().Display_ttl()); + page.Html_data().Custom_tab_name_(page_name); // set tab_name to page_name; note that if null, gui code will ignore and use Ttl.Page_txt; PAGE: zh.w:釣魚臺列嶼主權問題 DATE:2015-10-05 + Xow_portal_mgr portal_mgr = wiki.Html_mgr().Portal_mgr().Init_assert(); + boolean nightmode_enabled = app.Gui_mgr().Nightmode_mgr().Enabled(); + fmtr.Bld_bfr_many(bfr + , root_dir_bry, Xoa_app_.Version, Xoa_app_.Build_date, app.Tcp_server().Running_str() + , page.Db().Page().Id(), page.Ttl().Full_db() + , page_name, page.Html_data().Page_heading().Init(wiki, html_gen_tid == Xopg_page_.Tid_read, page.Html_data(), page.Ttl().Full_db(), page_display_title) + , modified_on_msg + , mgr.Css_common_bry(), mgr.Css_wiki_bry() + , mgr.Css_night_bry(nightmode_enabled) + , page.Html_data().Head_mgr().Init(app, wiki, page).Init_dflts() + , page.Lang().Dir_ltr_bry(), page.Html_data().Indicators(), page_content_sub, wiki.Html_mgr().Portal_mgr().Div_jump_to(), wiki.Xtn_mgr().Xtn_pgbnr().Write_html(page, ctx, hctx), page_body_class, html_content_editable + , page_data, wdata_lang_wtr + , portal_mgr.Div_footer(modified_on_msg, Xoa_app_.Version, Xoa_app_.Build_date) + + // sidebar divs + , portal_mgr.Div_personal_bry() + , portal_mgr.Div_ns_bry(wiki.Utl__bfr_mkr(), page_ttl, wiki.Ns_mgr()) + , portal_mgr.Div_view_bry(wiki.Utl__bfr_mkr(), html_gen_tid, page.Html_data().Xtn_search_text()) + , portal_mgr.Div_logo_bry(nightmode_enabled), portal_mgr.Div_home_bry(), new Xopg_xtn_skin_fmtr_arg(page, Xopg_xtn_skin_itm_tid.Tid_sidebar) + , portal_mgr.Div_sync_bry(tmp_bfr, wiki.Page_mgr().Sync_mgr().Manual_enabled(), wiki, page) + , portal_mgr.Div_wikis_bry(wiki.Utl__bfr_mkr()) + , portal_mgr.Sidebar_mgr().Html_bry() + , mgr.Edit_rename_div_bry(page_ttl), page.Html_data().Edit_preview_w_dbg(), js_edit_toolbar_bry + ); + Xoh_page_wtr_wkr_.Bld_head_end(bfr, tmp_bfr, page); // add after + Xoh_page_wtr_wkr_.Bld_html_end(bfr, tmp_bfr, page); // add after + } + public void Write_hdump(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, Xoae_page wpg) { + if (wpg.Html_data().Xtn_pgbnr() != null) { + ctx.Wiki().Xtn_mgr().Xtn_pgbnr().Write_html(wpg, ctx, hctx).Bfr_arg__add(bfr); // if pgbnr exists, write to top of html + } + this.Write_body(bfr, ctx, hctx, wpg); + } + public void Write_body(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, Xoae_page page) { + synchronized (thread_lock_2) { + this.page = page; this.wiki = page.Wikie(); this.app = wiki.Appe(); + Xoa_ttl page_ttl = page.Ttl(); int page_ns_id = page_ttl.Ns().Id(); + byte page_tid = Xow_page_tid.Identify(wiki.Domain_tid(), page_ns_id, page_ttl.Page_db()); // NOTE: can't cache page_tid b/c Write_body is called directly; DATE:2014-10-02 + byte[] data_raw = page.Db().Text().Text_bry(); + int bfr_page_bgn = bfr.Len(); + boolean page_tid_uses_pre = false; + if (page_mode == Xopg_page_.Tid_edit) + Write_body_edit(bfr, data_raw, page_ns_id, page_tid); + else { + switch (page_tid) { + case Xow_page_tid.Tid_msg: + case Xow_page_tid.Tid_js: + case Xow_page_tid.Tid_css: + case Xow_page_tid.Tid_lua: Write_body_pre (bfr, app, wiki, hctx, data_raw, tmp_bfr); page_tid_uses_pre = true; break; + case Xow_page_tid.Tid_json: app.Wiki_mgr().Wdata_mgr().Write_json_as_html(bfr, page_ttl.Full_db(), data_raw); break; + case Xow_page_tid.Tid_wikitext: Write_body_wikitext (bfr, app, wiki, data_raw, ctx, hctx, page, page_tid, page_ns_id); break; + } + } + if ( wiki.Domain_tid() != Xow_domain_tid_.Tid__home // allow home wiki to use javascript + && !page.Html_data().Js_enabled() // allow special pages to use js + && !page_tid_uses_pre) { // if .js, .css or .lua, skip test; may have js fragments, but entire text is escaped and put in pre; don't show spurious warning; DATE:2013-11-21 + wiki.Html_mgr().Js_cleaner().Clean_bfr(wiki, page_ttl, bfr, bfr_page_bgn); + } + } + } + private void Write_body_wikitext(Bry_bfr bfr, Xoae_app app, Xowe_wiki wiki, byte[] data_raw, Xop_ctx ctx, Xoh_wtr_ctx hctx, Xoae_page page, byte page_tid, int ns_id) { + // dump and exit if pre-generated html from html dumps + byte[] hdump_data = page.Db().Html().Html_bry(); + if (Bry_.Len_gt_0(hdump_data)) { + bfr.Add(hdump_data); + return; + } + + // dump and exit if MediaWiki message; + if (ns_id == Xow_ns_.Tid__mediawiki) { // if MediaWiki and wikitext, must be a message; convert args back to php; DATE:2014-06-13 + bfr.Add(Gfs_php_converter.Xto_php(tmp_bfr, Bool_.N, data_raw)); + return; + } + + // if [[File]], add boilerplate header; note that html is XOWA-generated so does not need to be tidied + if (ns_id == Xow_ns_.Tid__file) app.Ns_file_page_mgr().Bld_html(wiki, ctx, page, bfr, page.Ttl(), wiki.Cfg_file_page(), page.File_queue()); + + // get separate bfr; note that bfr already has and written to it, so this can't be passed to tidy; DATE:2014-06-11 + Bry_bfr tidy_bfr = wiki.Utl__bfr_mkr().Get_m001(); + + // write wikitext + if (page.Html_data().Skip_parse()) { + tidy_bfr.Add(page.Html_data().Custom_body()); + } + else { + if (page.Root() != null) { // NOTE: will be null if blank; occurs for one test: Logo_has_correct_main_page; DATE:2015-09-29 + page.Html_data().Toc_mgr().Clear(); // NOTE: always clear tocs before writing html; toc_itms added when writing html_hdr; DATE:2016-07-17 + wiki.Html_mgr().Html_wtr().Write_doc(tidy_bfr, ctx, hctx, page.Root().Data_mid(), page.Root()); + if (wiki.Html_mgr().Html_wtr().Cfg().Toc__show()) + gplx.xowa.htmls.core.wkrs.tocs.Xoh_toc_wtr.Write_toc(tidy_bfr, page, hctx); + } + } + + // if [[Category]], add catpage data + if (ns_id == Xow_ns_.Tid__category) tidy_bfr.Add_safe(page.Html_data().Catpage_data()); + // if (ns_id == Xow_ns_.Tid__category) wiki.Ctg__catpage_mgr().Write_catpage(tidy_bfr, page, hctx); + + // tidy html + wiki.Html_mgr().Tidy_mgr().Exec_tidy(tidy_bfr, !hctx.Mode_is_hdump(), page.Url_bry_safe()); + + // add back to main bfr + bfr.Add_bfr_and_clear(tidy_bfr); + tidy_bfr.Mkr_rls(); + + // handle Categories at bottom of page; note that html is XOWA-generated so does not need to be tidied + int ctgs_len = page.Wtxt().Ctgs__len(); + if ( ctgs_enabled + && ctgs_len > 0 // skip if no categories found while parsing wikitext + && !wiki.Html_mgr().Importing_ctgs() // do not show categories if importing categories, page will wait for category import to be done; DATE:2014-10-15 + && !hctx.Mode_is_hdump() // do not dump categories during hdump; DATE:2016-10-12 + ) { + if (app.Mode().Tid_is_gui()) app.Usr_dlg().Prog_many("", "", "loading categories: count=~{0}", ctgs_len); + Xoctg_pagebox_itm[] pagebox_itms = wiki.Ctg__pagebox_wtr().Get_catlinks_by_page(wiki, page); + wiki.Ctg__pagebox_wtr().Write_pagebox(bfr, wiki, page, pagebox_itms); + } + + // translate if variants are enabled + Xol_vnt_mgr vnt_mgr = wiki.Lang().Vnt_mgr(); + if (vnt_mgr.Enabled()) bfr.Add(vnt_mgr.Convert_lang().Parse_page(vnt_mgr.Cur_itm(), page.Db().Page().Id(), bfr.To_bry_and_clear())); + + // handle uniqs + wiki.Parser_mgr().Uniq_mgr().Parse(bfr); + } + private void Write_body_pre(Bry_bfr bfr, Xoae_app app, Xowe_wiki wiki, Xoh_wtr_ctx hctx, byte[] data_raw, Bry_bfr tmp_bfr) { + Xoh_html_wtr_escaper.Escape(app.Parser_amp_mgr(), tmp_bfr, data_raw, 0, data_raw.length, false, false); + if (hctx.Mode_is_hdump()) + bfr.Add(data_raw); + else + app.Html_mgr().Page_mgr().Content_code_fmt().Bld_many(bfr, tmp_bfr); + tmp_bfr.Clear(); + } + private void Write_body_edit(Bry_bfr bfr, byte[] data_raw, int ns_id, byte page_tid) { + if ( ns_id == Xow_ns_.Tid__mediawiki // if MediaWiki and wikitext, must be a message; convert args back to php; DATE:2014-06-13 + && page_tid == Xow_page_tid.Tid_wikitext + ) + data_raw = Gfs_php_converter.Xto_php(tmp_bfr, Bool_.N, data_raw); + int data_raw_len = data_raw.length; + if (mgr.Html_capable()) { + data_raw = wiki.Parser_mgr().Hdr__section_editable__mgr().Slice_section(page.Url(), page.Ttl(), data_raw); + data_raw_len = data_raw.length; + Xoh_html_wtr_escaper.Escape(page.Wikie().Appe().Parser_amp_mgr(), bfr, data_raw, 0, data_raw_len, false, false); // NOTE: must escape; assume that browser will automatically escape (<) (which Mozilla does) + } + else + bfr.Add(data_raw); + if (data_raw_len > 0) // do not add nl if empty String + bfr.Add_byte_nl(); // per MW:EditPage.php: "Ensure there's a newline at the end, otherwise adding lines is awkward." + } + // private static final byte[] Content_editable_bry = Bry_.new_a7(" contenteditable=\"true\""); +} diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_wkr_.java b/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_wkr_.java index a27517de8..aaa5d48e2 100644 --- a/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_wkr_.java +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_wkr_.java @@ -13,3 +13,44 @@ 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.htmls; import gplx.*; import gplx.xowa.*; +import gplx.langs.htmls.*; import gplx.xowa.xtns.relatedSites.*; +import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.tags.*; +import gplx.xowa.parsers.utils.*; +public class Xoh_page_wtr_wkr_ { + public static byte[] Bld_page_content_sub(Xoae_app app, Xowe_wiki wiki, Xoae_page page, Bry_bfr tmp_bfr) { + byte[] subpages = app.Html_mgr().Page_mgr().Subpages_bldr().Bld(wiki.Ns_mgr(), page.Ttl()); + byte[] page_content_sub = page.Html_data().Content_sub(); // contentSub exists; SEE: {{#isin}} + byte[] redirect_msg = Xop_redirect_mgr.Bld_redirect_msg(app, wiki, page.Redirect_trail()); + return Bry_.Add(subpages, page_content_sub, redirect_msg); + } + public static byte[] Bld_page_name(Bry_bfr tmp_bfr, Xoa_ttl ttl, byte[] display_ttl) { + if (Bry_.Len_gt_0(display_ttl)) return display_ttl; // display_ttl explicitly set; use it + if (ttl.Ns().Id() == Xow_ns_.Tid__special) { // special: omit query args, else excessively long titles: EX:"Special:Search/earth?fulltext=y&xowa page index=1" + tmp_bfr.Add(ttl.Ns().Name_ui_w_colon()).Add(ttl.Page_txt_wo_qargs()); + return tmp_bfr.To_bry_and_clear(); + } + else + return ttl.Full_txt_w_ttl_case(); // NOTE: include ns with ttl as per defect d88a87b3 + } + public static void Bld_head_end(Bry_bfr html_bfr, Bry_bfr tmp_bfr, Xoae_page page) { + byte[] head_end = Xopg_tag_wtr.Write(tmp_bfr, Bool_.Y, Xopg_tag_wtr_cbk_.Basic, page.Html_data().Custom_head_tags()); + if (Bry_.Len_eq_0(head_end)) return; + int insert_pos = Bry_find_.Find_fwd(html_bfr.Bfr(), Gfh_tag_.Head_rhs); + if (insert_pos == Bry_find_.Not_found) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "could not find "); + return; + } + html_bfr.Insert_at(insert_pos, head_end); + } + public static void Bld_html_end(Bry_bfr html_bfr, Bry_bfr tmp_bfr, Xoae_page page) { + byte[] html_end = Xopg_tag_wtr.Write(tmp_bfr, Bool_.Y, Xopg_tag_wtr_cbk_.Basic, page.Html_data().Custom_tail_tags()); + if (html_end == null) return; + int insert_pos = Bry_find_.Find_bwd(html_bfr.Bfr(), Gfh_tag_.Html_rhs, html_bfr.Len()); + if (insert_pos == Bry_find_.Not_found) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "could not find "); + return; + } + html_bfr.Insert_at(insert_pos, html_end); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_wkr_tst.java b/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_wkr_tst.java index a27517de8..402dc216c 100644 --- a/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_wkr_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/Xoh_page_wtr_wkr_tst.java @@ -13,3 +13,62 @@ 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.htmls; import gplx.*; import gplx.xowa.*; +import org.junit.*; +import gplx.xowa.guis.*; import gplx.xowa.wikis.pages.*; +import gplx.xowa.htmls.core.htmls.*; +public class Xoh_page_wtr_wkr_tst { + @Before public void init() {fxt.Clear();} private Xoh_page_wtr_fxt fxt = new Xoh_page_wtr_fxt(); + @Test public void Page_name() { + fxt.Test_page_name_by_ttl("Earth", "Earth"); + fxt.Test_page_name_by_ttl("File:A.png", "File:A.png"); + fxt.Test_page_name_by_ttl("Special:Search/earth?fulltext=y", "Special:Search/earth"); + fxt.Test_page_name_by_ttl("Special:Search/earth", "Special:Search/earth"); + fxt.Test_page_name_by_display("Special:Allpages", "All pages", "All pages"); + } + @Test public void Edit() { + fxt.Test_edit(" ", "&#9;\n"); // NOTE: cannot by or will show up in edit box as "\t" and save as "\t" instead of + } + @Test public void Css() { + fxt.App().Html_mgr().Page_mgr().Content_code_fmt().Fmt_("
    ~{page_text}
    "); + fxt.Test_read("MediaWiki:Common.css", ".xowa {}", "
    .xowa {}
    "); + fxt.App().Html_mgr().Page_mgr().Content_code_fmt().Fmt_("
    ~{page_text}
    "); + } + @Test public void Amp_disable() { // PURPOSE: in js documents; " should be rendered as ", not as "; DATE:2013-11-07 + fxt.Test_read("MediaWiki:Gadget.js", """, "
    &quot;
    "); + } +} +class Xoh_page_wtr_fxt { + public void Clear() { + if (app == null) { + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + } + } private Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); private Xowe_wiki wiki; + public Xoae_app App() {return app;} private Xoae_app app; + public void Test_page_name_by_display(String ttl, String display, String expd) { + Tfds.Eq(expd, String_.new_a7(Xoh_page_wtr_wkr_.Bld_page_name(tmp_bfr, Xoa_ttl.Parse(wiki, Bry_.new_a7(ttl)), Bry_.new_a7(display)))); + } + public void Test_page_name_by_ttl(String raw, String expd) { + Tfds.Eq(expd, String_.new_a7(Xoh_page_wtr_wkr_.Bld_page_name(tmp_bfr, Xoa_ttl.Parse(wiki, Bry_.new_a7(raw)), null))); + } + public void Test_edit(String raw, String expd) { + wiki.Html_mgr().Page_wtr_mgr().Html_capable_(true); + Xoae_page page = wiki.Parser_mgr().Ctx().Page(); + page.Db().Text().Text_bry_(Bry_.new_u8(raw)); + Xoh_page_wtr_mgr mgr = wiki.Html_mgr().Page_wtr_mgr(); + Xoh_page_wtr_wkr wkr = mgr.Wkr(Xopg_page_.Tid_edit); + wkr.Write_body(tmp_bfr, wiki.Parser_mgr().Ctx(), Xoh_wtr_ctx.Basic, page); + Tfds.Eq(expd, tmp_bfr.To_str_and_clear()); + } + public void Test_read(String page_name, String page_text, String expd) { + wiki.Html_mgr().Page_wtr_mgr().Html_capable_(true); + Xoae_page page = wiki.Parser_mgr().Ctx().Page(); + page.Ttl_(Xoa_ttl.Parse(wiki, Bry_.new_a7(page_name))); + page.Db().Text().Text_bry_(Bry_.new_u8(page_text)); + Xoh_page_wtr_mgr mgr = wiki.Html_mgr().Page_wtr_mgr(); + Xoh_page_wtr_wkr wkr = mgr.Wkr(Xopg_page_.Tid_read); + wkr.Write_body(tmp_bfr, wiki.Parser_mgr().Ctx(), Xoh_wtr_ctx.Basic, page); + Tfds.Eq(expd, tmp_bfr.To_str_and_clear()); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/Xow_html_mgr.java b/400_xowa/src/gplx/xowa/htmls/Xow_html_mgr.java index a27517de8..4765405a8 100644 --- a/400_xowa/src/gplx/xowa/htmls/Xow_html_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/Xow_html_mgr.java @@ -13,3 +13,53 @@ 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.htmls; import gplx.*; import gplx.xowa.*; +import gplx.gfui.kits.core.*; +import gplx.xowa.langs.*; +import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.xtns.gallery.*; +import gplx.xowa.parsers.xndes.*; +import gplx.xowa.htmls.portal.*; import gplx.xowa.addons.htmls.tocs.*; import gplx.xowa.wikis.modules.*; import gplx.xowa.htmls.core.htmls.*; import gplx.xowa.htmls.core.hzips.*; import gplx.xowa.htmls.core.htmls.tidy.*; import gplx.xowa.htmls.js.*; +import gplx.langs.htmls.encoders.*; +import gplx.xowa.addons.wikis.ctgs.htmls.pageboxs.*; import gplx.xowa.addons.wikis.ctgs.htmls.pageboxs.singles.*; +public class Xow_html_mgr implements Gfo_invk { + private final Gfo_url_encoder fsys_lnx_encoder = Gfo_url_encoder_.New__fsys_lnx().Make(); + public Xow_html_mgr(Xowe_wiki wiki) { + this.wiki = wiki; + html_wtr = new Xoh_html_wtr(wiki, this); + Xoae_app app = wiki.Appe(); + page_wtr_mgr = new Xoh_page_wtr_mgr(app.Gui_mgr().Kit().Tid() != Gfui_kit_.Swing_tid); // reverse logic to handle swt,drd but not mem + img_xowa_protocol = fsys_lnx_encoder.Encode_to_file_protocol(app.Fsys_mgr().Bin_xowa_file_dir().GenSubFil_nest("app.general", "xowa_exec.png")); + portal_mgr = new Xow_portal_mgr(wiki); + module_mgr = new Xow_module_mgr(wiki); + this.js_cleaner = new Xoh_js_cleaner(app); + } + public void Init_by_wiki(Xowe_wiki wiki) { + html_wtr.Init_by_wiki(wiki); + module_mgr.Init_by_wiki(wiki); + tidy_mgr.Init_by_wiki(wiki); + portal_mgr.Init_by_wiki(wiki); + page_wtr_mgr.Init_by_wiki(wiki); + } + public void Init_by_lang(Xol_lang_itm lang) { + portal_mgr.Init_by_lang(lang); + } + public Xowe_wiki Wiki() {return wiki;} private Xowe_wiki wiki; + public Xoh_html_wtr Html_wtr() {return html_wtr;} private Xoh_html_wtr html_wtr; + public Xoh_page_wtr_mgr Page_wtr_mgr() {return page_wtr_mgr;} private Xoh_page_wtr_mgr page_wtr_mgr; + public Xow_tidy_mgr Tidy_mgr() {return tidy_mgr;} private final Xow_tidy_mgr tidy_mgr = new Xow_tidy_mgr(); + public Xoh_js_cleaner Js_cleaner() {return js_cleaner;} private final Xoh_js_cleaner js_cleaner; + public Xop_xatr_whitelist_mgr Whitelist_mgr() {return whitelist_mgr;} private final Xop_xatr_whitelist_mgr whitelist_mgr = new Xop_xatr_whitelist_mgr().Ini(); + public Xow_portal_mgr Portal_mgr() {return portal_mgr;} private Xow_portal_mgr portal_mgr; + public Xow_module_mgr Head_mgr() {return module_mgr;} private Xow_module_mgr module_mgr; + public boolean Importing_ctgs() {return importing_ctgs;} public void Importing_ctgs_(boolean v) {importing_ctgs = v;} private boolean importing_ctgs; + public int Img_thumb_width() {return img_thumb_width;} private int img_thumb_width = 220; + public byte[] Img_xowa_protocol() {return img_xowa_protocol;} private byte[] img_xowa_protocol; + public boolean Img_suppress_missing_src() {return img_suppress_missing_src;} public Xow_html_mgr Img_suppress_missing_src_(boolean v) {img_suppress_missing_src = v; return this;} private boolean img_suppress_missing_src = true; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_article)) return page_wtr_mgr; + else if (ctx.Match(k, Invk_portal)) return portal_mgr; + else if (ctx.Match(k, Invk_modules)) return module_mgr; + else return Gfo_invk_.Rv_unhandled; + } + public static final String Invk_article = "article", Invk_portal = "portal", Invk_modules = "modules"; +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/Bridge_cmd_itm.java b/400_xowa/src/gplx/xowa/htmls/bridges/Bridge_cmd_itm.java index a27517de8..9fb214761 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/Bridge_cmd_itm.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/Bridge_cmd_itm.java @@ -13,3 +13,10 @@ 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.htmls.bridges; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.langs.jsons.*; +public interface Bridge_cmd_itm { + byte[] Key(); + void Init_by_app(Xoa_app app); + String Exec(Json_nde data); +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/Bridge_cmd_mgr.java b/400_xowa/src/gplx/xowa/htmls/bridges/Bridge_cmd_mgr.java index a27517de8..aa93a4510 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/Bridge_cmd_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/Bridge_cmd_mgr.java @@ -13,3 +13,31 @@ 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.htmls.bridges; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.langs.jsons.*; +public class Bridge_cmd_mgr { + private final Json_parser parser; + private final Hash_adp_bry cmd_hash = Hash_adp_bry.cs(); + public Bridge_cmd_mgr(Json_parser parser) {this.parser = parser;} + public void Add(Bridge_cmd_itm cmd) {cmd_hash.Add_bry_obj(cmd.Key(), cmd);} + public String Exec(GfoMsg m) { + if (m.Args_count() == 0) throw Err_.new_("bridge.cmds", "no json specified for json_exec"); + return Exec(m.Args_getAt(0).Val_to_bry()); + } + public String Exec(byte[] jdoc_bry) { + Json_doc jdoc = null; + try {jdoc = parser.Parse(jdoc_bry);} + catch (Exception e) {throw Err_.new_exc(e, "bridge.cmds", "invalid json", "json", jdoc_bry);} + Json_nde msg = jdoc.Root_nde(); + byte[] key_bry = msg.Get_bry(Key_cmd); + Bridge_cmd_itm cmd = (Bridge_cmd_itm)cmd_hash.Get_by_bry(key_bry); if (cmd == null) throw Err_.new_("bridge.cmds", "unknown cmd", "key", key_bry); + try {return cmd.Exec(msg.Get(Key_data));} + catch (Exception e) { + Xoa_app_.Usr_dlg().Warn_many("", "", "exec json failed: ~{0}", "json", jdoc_bry); + throw Err_.new_exc(e, "bridge.cmds", "exec json failed", "json", jdoc_bry); + } + } + private static final byte[] Key_cmd = Bry_.new_a7("cmd"), Key_data = Bry_.new_a7("data"); + public static final byte[] Msg__proc = Bry_.new_a7("proc"), Msg__args = Bry_.new_a7("args"); + public static String Msg__ok = String_.Empty; +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/Bridge_msg_bldr.java b/400_xowa/src/gplx/xowa/htmls/bridges/Bridge_msg_bldr.java index a27517de8..ceb514cc4 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/Bridge_msg_bldr.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/Bridge_msg_bldr.java @@ -13,3 +13,89 @@ 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.htmls.bridges; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.langs.jsons.*; +public class Bridge_msg_bldr { + private final Json_wtr wtr = new Json_wtr(); + private boolean rslt_pass; private String rslt_msg; + private String notify_text; private String notify_status; + private final Gfo_tree_list data_root = new Gfo_tree_list("data"); + public Bridge_msg_bldr() { + wtr.Opt_ws_(Bool_.N); + this.Clear(); + } + public Bridge_msg_bldr Opt_quote_byte_apos_() {wtr.Opt_quote_byte_(Byte_ascii.Apos); return this;} + public Bridge_msg_bldr Rslt_pass_y_() {return Rslt_pass_(Bool_.Y);} + public Bridge_msg_bldr Rslt_pass_n_(String v) {Rslt_msg_(v); return Rslt_pass_(Bool_.N);} + private Bridge_msg_bldr Rslt_pass_(boolean v) {synchronized(wtr){this.rslt_pass = v;} return this;} + private Bridge_msg_bldr Rslt_msg_(String v) {synchronized(wtr){this.rslt_msg = v;} return this;} + public Bridge_msg_bldr Notify_hint_(String v) {synchronized(wtr){this.notify_hint = v;} return this;} private String notify_hint; + public Bridge_msg_bldr Notify_pass_(String v){synchronized(wtr){this.notify_text = v; this.notify_status = "success";} return this;} + public Bridge_msg_bldr Notify_fail_(String v){synchronized(wtr){this.notify_text = v; this.notify_status = "error"; this.rslt_pass = false;} return this;} + public Bridge_msg_bldr Data(String key, boolean val) {return Data_obj(key, val, Type_ids_.Id__bool);} + public Bridge_msg_bldr Data(String key, int val) {return Data_obj(key, val, Type_ids_.Id__int);} + public Bridge_msg_bldr Data(String key, String val) {return Data_obj(key, val, Type_ids_.Id__str);} + public Bridge_msg_bldr Data(String key, byte[] val) {return Data_obj(key, val, Type_ids_.Id__bry);} + private Bridge_msg_bldr Data_obj(String key, Object val, int val_tid) { + synchronized (wtr) {data_root.Add_data(key, val, val_tid);} + return this; + } + public byte[] To_json_bry() {synchronized(wtr){ Bld_json(); return wtr.To_bry_and_clear();}} + public String To_json_str() {synchronized(wtr){ Bld_json(); return wtr.To_str_and_clear();}} + public String To_json_str__empty() {return "{}";} + public Bridge_msg_bldr Clear() { + synchronized (wtr) { + rslt_pass = true; // by default, set all msgs to pass==true + rslt_msg = null; + notify_hint = null; + notify_text = null; + notify_status = null; + data_root.Clear(); + } + return this; + } + private void Bld_json() { + wtr.Clear(); + wtr.Doc_nde_bgn(); + wtr.Nde_bgn(Key_rslt); + wtr.Kv_bool(Key_rslt_pass, rslt_pass); + if (rslt_msg != null) wtr.Kv_str(Key_rslt_msg, rslt_msg); + wtr.Nde_end(); + if (notify_text != null) { + wtr.Nde_bgn(Key_notify); + wtr.Kv_str(Key_notify_text, notify_text); + wtr.Kv_str(Key_notify_status, notify_status); + if (notify_hint != null) + wtr.Kv_str(Key_notify_hint, notify_hint); + wtr.Nde_end(); + } + Bld_json_for_hash(wtr, data_root); + wtr.Doc_nde_end(); + } + private void Bld_json_for_hash(Json_wtr wtr, Gfo_tree_list hash) { + int len = hash.Len(); if (len == 0) return; + wtr.Nde_bgn(hash.Key()); + for (int i = 0; i < len; ++i) { + Gfo_tree_itm itm = hash.Get_at(i); + if (itm.Tid() == Gfo_tree_itm_.Tid_data) { + Gfo_tree_data sub_kv = (Gfo_tree_data)itm; + String key = sub_kv.Key(); Object val = sub_kv.Val(); + switch (sub_kv.Val_tid()) { + case Type_ids_.Id__bool: wtr.Kv_bool(key, Bool_.Cast(val)); break; + case Type_ids_.Id__int: wtr.Kv_int(key, Int_.Cast(val)); break; + case Type_ids_.Id__bry: wtr.Kv_bry(key, (byte[])val); break; + default: wtr.Kv_str(key, Object_.Xto_str_strict_or_null_mark(val)); break; + } + } + else { + Gfo_tree_list sub_hash = (Gfo_tree_list)itm; + Bld_json_for_hash(wtr, sub_hash); + } + } + wtr.Nde_end(); + } + private static final byte[] + Key_rslt = Bry_.new_a7("rslt"), Key_rslt_pass = Bry_.new_a7("pass"), Key_rslt_msg = Bry_.new_a7("msg") + , Key_notify = Bry_.new_a7("notify"), Key_notify_text = Bry_.new_a7("text"), Key_notify_status = Bry_.new_a7("status"), Key_notify_hint = Bry_.new_a7("hint") + ; +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/Bridge_msg_bldr_tst.java b/400_xowa/src/gplx/xowa/htmls/bridges/Bridge_msg_bldr_tst.java index a27517de8..e83300419 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/Bridge_msg_bldr_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/Bridge_msg_bldr_tst.java @@ -13,3 +13,19 @@ 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.htmls.bridges; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import org.junit.*; +public class Bridge_msg_bldr_tst { + @Before public void init() {fxt.Clear();} private Bridge_msg_bldr_fxt fxt = new Bridge_msg_bldr_fxt(); + @Test public void Bld() { + fxt.Bldr().Rslt_pass_y_().Notify_pass_("passed").Data("key1", true).Data("key2", 1).Data("key3", "val3"); + fxt.Test_to_json_str("{'rslt':{'pass':true},'notify':{'text':'passed','status':'success'},'data':{'key1':true,'key2':1,'key3':'val3'}}"); + } +} +class Bridge_msg_bldr_fxt { + public Bridge_msg_bldr Bldr() {return bldr;} private final Bridge_msg_bldr bldr = new Bridge_msg_bldr().Opt_quote_byte_apos_(); + public void Clear() {} + public void Test_to_json_str(String expd) { + Tfds.Eq_str_lines(expd, bldr.To_json_str()); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/Gfo_tree_itm.java b/400_xowa/src/gplx/xowa/htmls/bridges/Gfo_tree_itm.java index a27517de8..8cd50545f 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/Gfo_tree_itm.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/Gfo_tree_itm.java @@ -13,3 +13,31 @@ 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.htmls.bridges; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +interface Gfo_tree_itm { + int Tid(); +} +class Gfo_tree_itm_ { + public static final int Tid_data = 1, Tid_list = 2; +} +class Gfo_tree_data implements Gfo_tree_itm { + public int Tid() {return Gfo_tree_itm_.Tid_data;} + public Gfo_tree_data(String key, Object val, int val_tid) { + this.key = key; this.val = val; this.val_tid = val_tid; + } + public String Key() {return key;} private final String key; + public Object Val() {return val;} private final Object val; + public int Val_tid() {return val_tid;} private final int val_tid; +} +class Gfo_tree_list implements Gfo_tree_itm { + private final Ordered_hash list = Ordered_hash_.New(); + public Gfo_tree_list(String key) {this.key = key;} + public int Tid() {return Gfo_tree_itm_.Tid_list;} + public String Key() {return key;} private final String key; + public void Clear() {list.Clear();} + public int Len() {return list.Count();} + public Gfo_tree_data Add_data(String key, Object val, int val_tid) {Gfo_tree_data rv = new Gfo_tree_data(key, val, val_tid); this.Add(key, rv); return rv;} + public Gfo_tree_list Add_list(String key) {Gfo_tree_list rv = new Gfo_tree_list(key); this.Add(key, rv); return rv;} + private void Add(String key, Gfo_tree_itm itm) {list.Add(key, itm);} + public Gfo_tree_itm Get_at(int i) {return (Gfo_tree_itm)list.Get_at(i);} +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/Xoh_bridge_mgr.java b/400_xowa/src/gplx/xowa/htmls/bridges/Xoh_bridge_mgr.java index a27517de8..089150488 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/Xoh_bridge_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/Xoh_bridge_mgr.java @@ -13,3 +13,11 @@ 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.htmls.bridges; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.langs.jsons.*; +import gplx.xowa.htmls.bridges.dbuis.tbls.*; +public class Xoh_bridge_mgr { + public Xoh_bridge_mgr(Json_parser parser) {this.cmd_mgr = new Bridge_cmd_mgr(parser);} + public Bridge_cmd_mgr Cmd_mgr() {return cmd_mgr;} private final Bridge_cmd_mgr cmd_mgr; + public Bridge_msg_bldr Msg_bldr() {return msg_bldr;} private final Bridge_msg_bldr msg_bldr = new Bridge_msg_bldr(); +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/Dbui_cmd_mgr.java b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/Dbui_cmd_mgr.java index a27517de8..b6f59f1ba 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/Dbui_cmd_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/Dbui_cmd_mgr.java @@ -13,3 +13,77 @@ 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.htmls.bridges.dbuis; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.bridges.*; +import gplx.langs.jsons.*; import gplx.xowa.htmls.bridges.dbuis.tbls.*; +public class Dbui_cmd_mgr { + private final Hash_adp_bry hash = Hash_adp_bry.cs(); + private boolean init; + public void Init_by_bridge(Bridge_cmd_mgr cmd_mgr) { + if (init) return; + init = true; + cmd_mgr.Add(new Dbui_cmd_row_edit ("xowa.dbui.edit_bgn", this)); + cmd_mgr.Add(new Dbui_cmd_row_save ("xowa.dbui.save_bgn", this)); + cmd_mgr.Add(new Dbui_cmd_row_del ("xowa.dbui.delete_bgn", this)); + cmd_mgr.Add(new Dbui_cmd_row_reorder ("xowa.dbui.reorder_bgn", this)); + } + public void Add(Dbui_tbl_itm tbl) {hash.Add_bry_obj(tbl.Key(), tbl);} + public String Del(Json_nde data) {return Get_tbl(data).Del (data.Get_bry(Arg_row_id), data.Get_bry(Arg_row_pkey));} + public String Edit(Json_nde data) {return Get_tbl(data).Edit(data.Get_bry(Arg_row_id), data.Get_bry(Arg_row_pkey));} + public String Save(Json_nde data) {return Get_tbl(data).Save(data.Get_bry(Arg_row_id), data.Get_bry(Arg_row_pkey), To_hash(data.Get(Arg_vals)));} + public String Reorder(Json_nde data){ + byte[] pkeys_concat = data.Get_bry(Arg_pkeys); + return Get_tbl(data).Reorder(Bry_split_.Split(pkeys_concat, Byte_ascii.Pipe), -1); + } + private Dbui_tbl_itm Get_tbl(Json_nde data) { + byte[] tbl_key = data.Get_bry(Arg_tbl_key); + Dbui_tbl_itm rv = (Dbui_tbl_itm)hash.Get_by(tbl_key); if (rv == null) throw Err_.new_("dbui", "unknown tbl_key", "tbl_key", tbl_key); + return rv; + } + private static Dbui_val_hash To_hash(Json_grp grp) { + Dbui_val_hash rv = new Dbui_val_hash(); + int len = grp.Len(); + for (int i = 0; i < len; ++i) { + Json_kv kv = (Json_kv)grp.Get_at(i); + Json_nde nde = (Json_nde)kv.Val(); + Json_kv key = (Json_kv)nde.Get_itm(Arg_key); + Json_kv val = (Json_kv)nde.Get_itm(Arg_val); + Dbui_val_itm fld = new Dbui_val_itm(val.Val().Data_bry(), Bry_.Empty); + rv.Add(key.Val().Data_bry(), fld); + } + return rv; + } + public static final Dbui_cmd_mgr Instance = new Dbui_cmd_mgr(); Dbui_cmd_mgr() {} + private static final byte[] + Arg_tbl_key = Bry_.new_a7("tbl_key"), Arg_row_pkey = Bry_.new_a7("row_pkey"), Arg_row_id = Bry_.new_a7("row_id") + , Arg_vals = Bry_.new_a7("vals"), Arg_key = Bry_.new_a7("key"), Arg_val = Bry_.new_a7("val") + , Arg_pkeys = Bry_.new_a7("pkeys") + ; +} +class Dbui_cmd_row_del implements Bridge_cmd_itm { + private final Dbui_cmd_mgr mgr; + public Dbui_cmd_row_del(String key, Dbui_cmd_mgr mgr) {this.key = Bry_.new_u8(key); this.mgr = mgr;} + public byte[] Key() {return key;} private final byte[] key; + public void Init_by_app(Xoa_app app) {} + public String Exec(Json_nde data) {return mgr.Del(data);} +} +class Dbui_cmd_row_edit implements Bridge_cmd_itm { + private final Dbui_cmd_mgr mgr; + public Dbui_cmd_row_edit(String key, Dbui_cmd_mgr mgr) {this.key = Bry_.new_u8(key); this.mgr = mgr;} + public byte[] Key() {return key;} private final byte[] key; + public void Init_by_app(Xoa_app app) {} + public String Exec(Json_nde data) {return mgr.Edit(data);} +} +class Dbui_cmd_row_save implements Bridge_cmd_itm { + private final Dbui_cmd_mgr mgr; + public Dbui_cmd_row_save(String key, Dbui_cmd_mgr mgr) {this.key = Bry_.new_u8(key); this.mgr = mgr;} + public byte[] Key() {return key;} private final byte[] key; + public void Init_by_app(Xoa_app app) {} + public String Exec(Json_nde data) {return mgr.Save(data);} +} +class Dbui_cmd_row_reorder implements Bridge_cmd_itm { + private final Dbui_cmd_mgr mgr; + public Dbui_cmd_row_reorder(String key, Dbui_cmd_mgr mgr) {this.key = Bry_.new_u8(key); this.mgr = mgr;} + public byte[] Key() {return key;} private final byte[] key; + public void Init_by_app(Xoa_app app) {} + public String Exec(Json_nde data) {return mgr.Reorder(data);} +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_cells_fmtr.java b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_cells_fmtr.java index a27517de8..3b65e6e1f 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_cells_fmtr.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_cells_fmtr.java @@ -13,3 +13,64 @@ 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.htmls.bridges.dbuis.fmtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.bridges.*; import gplx.xowa.htmls.bridges.dbuis.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.htmls.bridges.dbuis.tbls.*; +public class Dbui_cells_fmtr implements gplx.core.brys.Bfr_arg { + private final Dbui_cell_fmtr cell_fmtr = new Dbui_cell_fmtr(); + private final Dbui_btn_fmtr btn_fmtr = new Dbui_btn_fmtr(); + private Dbui_btn_itm[] btns; + private byte[] row_key; private Dbui_row_itm row_itm; + public void Ctor(Dbui_val_fmtr val_fmtr, Dbui_btn_itm[] btns) { + cell_fmtr.Ctor(val_fmtr); this.btns = btns; + } + public Dbui_cells_fmtr Init(byte[] row_key, Dbui_row_itm row_itm) { + this.row_key = row_key; this.row_itm = row_itm; + return this; + } + public void Bfr_arg__add(Bry_bfr bfr) { + fmtr.Bld_bfr_many(bfr, cell_fmtr.Init(row_key, row_itm), btn_fmtr.Init(row_key, btns)); + } + private static final Bry_fmtr fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , "
    ~{vals}" + , "
    ~{btns}" + , "
    " + ), "vals", "btns"); +} +class Dbui_cell_fmtr implements gplx.core.brys.Bfr_arg { + private byte[] row_key; private Dbui_row_itm row_itm; + private Dbui_val_fmtr val_fmtr; + public void Ctor(Dbui_val_fmtr val_fmtr) {this.val_fmtr = val_fmtr;} + public Dbui_cell_fmtr Init(byte[] row_key, Dbui_row_itm row_itm) {this.row_key = row_key; this.row_itm = row_itm; return this;} + public void Bfr_arg__add(Bry_bfr bfr) { + Dbui_col_itm[] cols = row_itm.Tbl().Cols(); + Dbui_val_itm[] vals = row_itm.Vals(); int len = vals.length; + for (int i = 0; i < len; ++i) { + Dbui_val_itm val = vals[i]; + fmtr.Bld_bfr_many(bfr, row_key, i, val_fmtr.Init(cols[i], row_key, val)); + } + } + private static final Bry_fmtr fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , "
    ~{html}
    " + ), "row_key", "val_idx", "html"); +} +class Dbui_btn_fmtr implements gplx.core.brys.Bfr_arg { + private byte[] row_key; private Dbui_btn_itm[] btns; + public Dbui_btn_fmtr Init(byte[] row_key, Dbui_btn_itm[] btns) { + this.row_key = row_key; this.btns = btns; return this; + } + public void Bfr_arg__add(Bry_bfr bfr) { + int len = btns.length; + Io_url img_dir = gplx.xowa.htmls.heads.Xoh_head_itm__dbui.Img_dir(); + for (int i = 0; i < len; ++i) { + Dbui_btn_itm btn = btns[i]; + fmtr.Bld_bfr_many(bfr, row_key, btn.Cmd(), img_dir.GenSubFil(btn.Img()).To_http_file_bry(), btn.Text()); + } + } + private static final Bry_fmtr fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , " " + ), "row_key", "btn_cmd", "btn_img", "btn_text"); +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_tbl_fmtr.java b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_tbl_fmtr.java index a27517de8..bf72acd2a 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_tbl_fmtr.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_tbl_fmtr.java @@ -13,3 +13,60 @@ 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.htmls.bridges.dbuis.fmtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.bridges.*; import gplx.xowa.htmls.bridges.dbuis.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.htmls.bridges.dbuis.tbls.*; +public class Dbui_tbl_fmtr { + private final Dbui_head_cell_fmtr head_cell_fmtr = new Dbui_head_cell_fmtr(); + private final Dbui_row_fmtr row_fmtr = new Dbui_row_fmtr(); + public void Write(Bry_bfr bfr, Dbui_tbl_itm tbl, byte[] origin_html, byte[] delete_confirm_msg, Dbui_row_itm[] rows) { + tbl_fmtr.Bld_bfr_many(bfr, tbl.Key(), Dbui_tbl_itm_.Calc_width(tbl), origin_html, delete_confirm_msg, head_cell_fmtr.Init(tbl), row_fmtr.Init(tbl, rows)); + } + private static final Bry_fmtr tbl_fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , "
    " + , "
    " + , "
    ~{origin}
    ~{head_cells}" + , "
    ~{data_rows}" + , "
    " + ), "tbl_key", "width", "origin", "delete_confirm_msg", "head_cells", "data_rows"); +} +class Dbui_head_cell_fmtr implements gplx.core.brys.Bfr_arg { + private Dbui_tbl_itm tbl; + public Dbui_head_cell_fmtr Init(Dbui_tbl_itm tbl) {this.tbl = tbl; return this;} + public void Bfr_arg__add(Bry_bfr bfr) { + Dbui_col_itm[] cols = tbl.Cols(); int len = cols.length; + for (int i = 0; i < len; ++i) { + Dbui_col_itm col = cols[i]; + fmtr.Bld_bfr_many(bfr, col.Width(), col.Display()); + } + bfr.Add_str_a7("\n
     
    "); // btns headers + } + private static final Bry_fmtr fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , "
    ~{display}
    " + ), "width", "display"); +} +class Dbui_row_fmtr implements gplx.core.brys.Bfr_arg { + private final Dbui_cells_fmtr cells_fmtr = new Dbui_cells_fmtr(); + private final Dbui_val_fmtr val_fmtr = Dbui_val_fmtr_.new_view(); + private final Bry_bfr row_key_bfr = Bry_bfr_.New_w_size(255); + private Dbui_tbl_itm tbl; private Dbui_row_itm[] rows; + public Dbui_row_fmtr Init(Dbui_tbl_itm tbl, Dbui_row_itm[] rows) {this.tbl = tbl; this.rows = rows; return this;} + public void Bfr_arg__add(Bry_bfr bfr) { + byte[] tbl_key = tbl.Key(); + int len = rows.length; + cells_fmtr.Ctor(val_fmtr, tbl.View_btns()); + for (int i = 0; i < len; ++i) { + Dbui_row_itm row = rows[i]; + row_key_bfr.Add(tbl_key).Add_byte(Byte_ascii.Underline).Add_int_variable(i); + byte[] row_key = row_key_bfr.To_bry_and_clear(); + fmtr.Bld_bfr_many(bfr, row_key, row.Pkey(), cells_fmtr.Init(row_key, row)); + } + } + private static final Bry_fmtr fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , "
    ~{cells}" + , "
    " + ), "row_key", "pkey", "cells"); +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_tbl_fmtr_tst.java b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_tbl_fmtr_tst.java index a27517de8..be7444eae 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_tbl_fmtr_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_tbl_fmtr_tst.java @@ -13,3 +13,27 @@ 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.htmls.bridges.dbuis.fmtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.bridges.*; import gplx.xowa.htmls.bridges.dbuis.*; +import gplx.xowa.htmls.bridges.dbuis.tbls.*; +import org.junit.*; +public class Dbui_tbl_fmtr_tst { + @Before public void init() {fxt.Clear();} private final Dbui_tbl_fmtr_fxt fxt = new Dbui_tbl_fmtr_fxt(); + @Test public void Basic() { +// fxt.Test_write +// ( fxt.Make_tbl() +// , String_.Concat_lines_nl_skip_last() +// ); + } +} +class Dbui_tbl_fmtr_fxt { + private final Bry_bfr bfr = Bry_bfr_.New_w_size(255); + private final Dbui_tbl_fmtr tbl_fmtr = new Dbui_tbl_fmtr(); + public void Clear() {} + public Dbui_tbl_itm Make_tbl() { + return null; + } + public void Test_write(Dbui_tbl_itm tbl, String expd) { + tbl_fmtr.Write(bfr, tbl, null, null, null); + Tfds.Eq_str_lines(expd, bfr.To_str_and_clear()); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_val_fmtr.java b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_val_fmtr.java index a27517de8..682ee86d5 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_val_fmtr.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_val_fmtr.java @@ -13,3 +13,35 @@ 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.htmls.bridges.dbuis.fmtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.bridges.*; import gplx.xowa.htmls.bridges.dbuis.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.htmls.bridges.dbuis.tbls.*; +public interface Dbui_val_fmtr { + Dbui_val_fmtr Init(Dbui_col_itm col, byte[] row_id, Dbui_val_itm val); +} +class Dbui_val_fmtr__view implements gplx.core.brys.Bfr_arg, Dbui_val_fmtr { + private Dbui_val_itm val; + public Dbui_val_fmtr Init(Dbui_col_itm col, byte[] row_id, Dbui_val_itm val) {this.val = val; return this;} + public void Bfr_arg__add(Bry_bfr bfr) { + bfr.Add(val.Html()); + } +} +class Dbui_val_fmtr__edit implements gplx.core.brys.Bfr_arg, Dbui_val_fmtr { + private Dbui_col_itm col; private byte[] row_id; private Dbui_val_itm val; + public Dbui_val_fmtr Init(Dbui_col_itm col, byte[] row_id, Dbui_val_itm val) {this.col = col; this.row_id = row_id; this.val = val; return this;} + public void Bfr_arg__add(Bry_bfr bfr) { + switch (col.Type()) { + case Dbui_col_itm.Type_id_str: input_fmtr_str.Bld_bfr_many(bfr, col.Key(), col.Width(), val.Data(), row_id); break; + case Dbui_col_itm.Type_id_text: textarea_fmtr_str.Bld_bfr_many(bfr, col.Key(), col.Width(), val.Data(), row_id); break; + default: throw Err_.new_unimplemented(); + } + } + private static final Bry_fmtr input_fmtr_str = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , " " + ), "col_key", "width", "value", "row_id"); + private static final Bry_fmtr textarea_fmtr_str = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , " " + ), "col_key", "width", "value", "row_id"); +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_val_fmtr_.java b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_val_fmtr_.java index a27517de8..f0f77b2dd 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_val_fmtr_.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/fmtrs/Dbui_val_fmtr_.java @@ -13,3 +13,8 @@ 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.htmls.bridges.dbuis.fmtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.bridges.*; import gplx.xowa.htmls.bridges.dbuis.*; +public class Dbui_val_fmtr_ { + public static Dbui_val_fmtr new_view() {return new Dbui_val_fmtr__view();} + public static Dbui_val_fmtr new_edit() {return new Dbui_val_fmtr__edit();} +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_btn_itm.java b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_btn_itm.java index a27517de8..86b6983fe 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_btn_itm.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_btn_itm.java @@ -13,3 +13,12 @@ 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.htmls.bridges.dbuis.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.bridges.*; import gplx.xowa.htmls.bridges.dbuis.*; +public class Dbui_btn_itm { + public Dbui_btn_itm(String cmd, String img, String text) {this.cmd = cmd; this.img = img; this.text = text;} + public String Key() {return text;} + public String Cmd() {return cmd;} private final String cmd; + public String Img() {return img;} private final String img; + public String Text() {return text;} private final String text; + public static final Dbui_btn_itm[] Ary_empty = new Dbui_btn_itm[0]; +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_col_itm.java b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_col_itm.java index a27517de8..2abe00ee8 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_col_itm.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_col_itm.java @@ -13,3 +13,12 @@ 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.htmls.bridges.dbuis.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.bridges.*; import gplx.xowa.htmls.bridges.dbuis.*; +public class Dbui_col_itm { + public Dbui_col_itm(int type, int width, String key, String display) {this.type = type; this.width = width; this.key = key; this.display = display;} + public int Type() {return type;} private final int type; + public String Key() {return key;} private final String key; + public String Display() {return display;} private final String display; + public int Width() {return width;} private final int width; + public static final int Type_id_str = 1, Type_id_text = 2, Type_id_int = 3, Type_id_datetime = 4; +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_row_itm.java b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_row_itm.java index a27517de8..7859b68d6 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_row_itm.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_row_itm.java @@ -13,3 +13,12 @@ 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.htmls.bridges.dbuis.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.bridges.*; import gplx.xowa.htmls.bridges.dbuis.*; +public class Dbui_row_itm { + public Dbui_row_itm(Dbui_tbl_itm tbl, byte[] pkey, Dbui_val_itm[] vals) { + this.tbl = tbl; this.pkey = pkey; this.vals = vals; + } + public Dbui_tbl_itm Tbl() {return tbl;} private final Dbui_tbl_itm tbl; + public byte[] Pkey() {return pkey;} private final byte[] pkey; + public Dbui_val_itm[] Vals() {return vals;} private Dbui_val_itm[] vals; +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_tbl_itm.java b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_tbl_itm.java index a27517de8..285ef3b8b 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_tbl_itm.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_tbl_itm.java @@ -13,3 +13,14 @@ 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.htmls.bridges.dbuis.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.bridges.*; import gplx.xowa.htmls.bridges.dbuis.*; +public interface Dbui_tbl_itm { + byte[] Key(); + Dbui_col_itm[] Cols(); + Dbui_btn_itm[] View_btns(); + Dbui_btn_itm[] Edit_btns(); + String Del (byte[] row_id, byte[] row_pkey); + String Edit(byte[] row_id, byte[] row_pkey); + String Save(byte[] row_id, byte[] row_pkey, Dbui_val_hash vals); + String Reorder(byte[][] pkeys, int owner); +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_tbl_itm_.java b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_tbl_itm_.java index a27517de8..abd5ce104 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_tbl_itm_.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_tbl_itm_.java @@ -13,3 +13,15 @@ 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.htmls.bridges.dbuis.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.bridges.*; import gplx.xowa.htmls.bridges.dbuis.*; +public class Dbui_tbl_itm_ { + public static int Calc_width(Dbui_tbl_itm tbl) { + int rv = 40; // 40 for button col + Dbui_col_itm[] ary = tbl.Cols(); + int len = ary.length; + for (int i = 0; i < len; ++i) { + rv += ary[i].Width(); + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_val_hash.java b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_val_hash.java index a27517de8..0ab6911e1 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_val_hash.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_val_hash.java @@ -13,3 +13,13 @@ 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.htmls.bridges.dbuis.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.bridges.*; import gplx.xowa.htmls.bridges.dbuis.*; +public class Dbui_val_hash { + private final Ordered_hash hash = Ordered_hash_.New_bry(); + public void Add(byte[] key, Dbui_val_itm itm) {hash.Add(key, itm);} + public byte[] Get_val_as_bry(String key) {return Get_val_as_bry(Bry_.new_u8(key));} + public byte[] Get_val_as_bry(byte[] key) { + Dbui_val_itm itm = (Dbui_val_itm)hash.Get_by(key); if (itm == null) throw Err_.new_wo_type("dbui.val_hash; unknown key", "key", key); + return itm.Data(); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_val_itm.java b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_val_itm.java index a27517de8..dcacc5596 100644 --- a/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_val_itm.java +++ b/400_xowa/src/gplx/xowa/htmls/bridges/dbuis/tbls/Dbui_val_itm.java @@ -13,3 +13,10 @@ 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.htmls.bridges.dbuis.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.bridges.*; import gplx.xowa.htmls.bridges.dbuis.*; +public class Dbui_val_itm { + public Dbui_val_itm(byte[] data, byte[] html) {this.data = data; this.html = html;} + public byte[] Data() {return data;} private byte[] data; + public byte[] Html() {return html;} private final byte[] html; + public void Data_(byte[] v) {this.data = v;} +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/Xow_hdump_mgr.java b/400_xowa/src/gplx/xowa/htmls/core/Xow_hdump_mgr.java index a27517de8..c1865ee28 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/Xow_hdump_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/core/Xow_hdump_mgr.java @@ -13,3 +13,35 @@ 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.htmls.core; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.core.ios.*; +import gplx.xowa.htmls.core.hzips.*; +import gplx.xowa.wikis.data.*; +public class Xow_hdump_mgr { + private final Xoh_page tmp_hpg = new Xoh_page(); private final Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); + private final Io_stream_zip_mgr zip_mgr = new Io_stream_zip_mgr(); + public Xow_hdump_mgr(Xow_wiki wiki) { + this.save_mgr = new Xow_hdump_mgr__save(wiki, hzip_mgr, zip_mgr, tmp_hpg); + this.load_mgr = new Xow_hdump_mgr__load(wiki, hzip_mgr, zip_mgr, tmp_hpg, tmp_bfr); + } + public Xow_hdump_mgr__save Save_mgr() {return save_mgr;} private Xow_hdump_mgr__save save_mgr; + public Xow_hdump_mgr__load Load_mgr() {return load_mgr;} private Xow_hdump_mgr__load load_mgr; + public Xoh_hzip_mgr Hzip_mgr() {return hzip_mgr;} private final Xoh_hzip_mgr hzip_mgr = new Xoh_hzip_mgr(); + public void Init_by_db(Xow_wiki wiki) { + byte dflt_zip_tid = gplx.core.ios.streams.Io_stream_tid_.Tid__raw; + boolean dflt_hzip_enable = false; + boolean mode_is_b256 = false; + if (wiki.Data__core_mgr() != null) { // TEST: handle null data mgr + Xowd_core_db_props props = wiki.Data__core_mgr().Props(); + dflt_zip_tid = props.Zip_tid_html(); + dflt_hzip_enable = props.Hzip_enabled(); + // mode_is_b256 = true; + } + Init_by_db(dflt_zip_tid, dflt_hzip_enable, mode_is_b256); + load_mgr.Init_by_wiki(wiki); + } + public void Init_by_db(byte dflt_zip_tid, boolean dflt_hzip_enable, boolean mode_is_b256) { + int dflt_hzip_tid = dflt_hzip_enable ? Xoh_hzip_dict_.Hzip__v1 : Xoh_hzip_dict_.Hzip__none; + save_mgr.Init_by_db(dflt_zip_tid, dflt_hzip_tid, Bool_.N); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/Xow_hdump_mgr__load.java b/400_xowa/src/gplx/xowa/htmls/core/Xow_hdump_mgr__load.java index a27517de8..d07b6f923 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/Xow_hdump_mgr__load.java +++ b/400_xowa/src/gplx/xowa/htmls/core/Xow_hdump_mgr__load.java @@ -13,3 +13,174 @@ 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.htmls.core; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.core.ios.*; +import gplx.xowa.htmls.heads.*; import gplx.xowa.htmls.core.makes.*; import gplx.xowa.htmls.core.hzips.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.pages.skins.*; import gplx.xowa.wikis.pages.lnkis.*; import gplx.xowa.wikis.pages.htmls.*; +import gplx.xowa.addons.wikis.ctgs.htmls.pageboxs.*; +public class Xow_hdump_mgr__load implements Gfo_invk { + private final Xow_wiki wiki; private final Xoh_hzip_mgr hzip_mgr; private final Io_stream_zip_mgr zip_mgr; + private final Xoh_page tmp_hpg; private final Bry_bfr tmp_bfr; private final Xowd_page_itm tmp_dbpg = new Xowd_page_itm(); + private Xow_override_mgr override_mgr__html, override_mgr__page; + public Xow_hdump_mgr__load(Xow_wiki wiki, Xoh_hzip_mgr hzip_mgr, Io_stream_zip_mgr zip_mgr, Xoh_page tmp_hpg, Bry_bfr tmp_bfr) { + this.wiki = wiki; this.hzip_mgr = hzip_mgr; this.zip_mgr = zip_mgr; this.tmp_hpg = tmp_hpg; this.tmp_bfr = tmp_bfr; + this.make_mgr = new Xoh_make_mgr(); + } + public boolean Read_preferred() {return read_preferred;} private boolean read_preferred = true; + public Xow_hdump_mode Html_mode() {return html_mode;} private Xow_hdump_mode html_mode = Xow_hdump_mode.Shown; + public Xoh_make_mgr Make_mgr() {return make_mgr;} private final Xoh_make_mgr make_mgr; + public void Init_by_wiki(Xow_wiki wiki) { + gplx.xowa.addons.apps.cfgs.Xocfg_mgr cfg_mgr = wiki.App().Cfg(); + Xow_hdump_mode.Cfg__reg_type(cfg_mgr.Type_mgr()); + cfg_mgr.Bind_many_wiki(this, wiki, Cfg__read_preferred, Cfg__html_mode); + } + public void Load_by_xowe(Xoae_page wpg) { + tmp_hpg.Ctor_by_hview(wpg.Wiki(), wpg.Url(), wpg.Ttl(), wpg.Db().Page().Id()); + Load_by_xowh(tmp_hpg, wpg.Ttl(), Bool_.Y); + wpg.Db().Html().Html_bry_(tmp_hpg.Db().Html().Html_bry()); + wpg.Root_(new gplx.xowa.parsers.Xop_root_tkn()); // HACK: set root, else load page will fail + Fill_page(wpg, tmp_hpg); + } + public boolean Load_by_xowh(Xoh_page hpg, Xoa_ttl ttl, boolean load_ctg) { + synchronized (tmp_dbpg) { + if (override_mgr__page == null) { + Io_url override_root_url = wiki.Fsys_mgr().Root_dir().GenSubDir_nest("data", "wiki"); + this.override_mgr__page = new Xow_override_mgr(override_root_url.GenSubDir_nest("page")); + this.override_mgr__html = new Xow_override_mgr(override_root_url.GenSubDir_nest("html")); + } + boolean loaded = Load__dbpg(wiki, tmp_dbpg.Clear(), hpg, ttl); + hpg.Ctor_by_hview(wiki, hpg.Url(), ttl, tmp_dbpg.Id()); + if (!loaded) { // nothing in "page" table + byte[] page_override = override_mgr__page.Get_or_same(ttl.Page_db(), null); + if (page_override == null) return Load__fail(hpg); + hpg.Db().Html().Html_bry_(page_override); + return true; + } + Xow_db_file html_db = wiki.Data__core_mgr().Dbs__get_by_id_or_fail(tmp_dbpg.Html_db_id()); + if (!html_db.Tbl__html().Select_by_page(hpg)) return Load__fail(hpg); // nothing in "html" table + byte[] src = Parse(hpg, hpg.Db().Html().Zip_tid(), hpg.Db().Html().Hzip_tid(), hpg.Db().Html().Html_bry()); + + // write ctgs + if (load_ctg) { + Xoctg_pagebox_itm[] pagebox_itms = wiki.Ctg__pagebox_wtr().Get_catlinks_by_page(wiki, hpg); + if (pagebox_itms.length > 0) { + tmp_bfr.Add(src); + wiki.Ctg__pagebox_wtr().Write_pagebox(tmp_bfr, wiki, hpg, pagebox_itms); + src = tmp_bfr.To_bry_and_clear(); + } + } + + hpg.Db().Html().Html_bry_(src); + return true; + } + } + public byte[] Decode_as_bry(Bry_bfr bfr, Xoh_page hpg, byte[] src, boolean mode_is_diff) {hzip_mgr.Hctx().Mode_is_diff_(mode_is_diff); hzip_mgr.Decode(bfr, wiki, hpg, src); return bfr.To_bry_and_clear();} + public byte[] Parse(Xoh_page hpg, int zip_tid, int hzip_tid, byte[] src) { + if (zip_tid > gplx.core.ios.streams.Io_stream_tid_.Tid__raw) + src = zip_mgr.Unzip((byte)zip_tid, src); + switch (hzip_tid) { + case Xoh_hzip_dict_.Hzip__none: + src = make_mgr.Parse(src, hpg, hpg.Wiki()); + break; + case Xoh_hzip_dict_.Hzip__v1: + if (override_mgr__html != null) // null when Parse is called directly + src = override_mgr__html.Get_or_same(hpg.Ttl().Page_db(), src); + hpg.Section_mgr().Add(0, 2, Bry_.Empty, Bry_.Empty).Content_bgn_(0); // +1 to skip \n + src = Decode_as_bry(tmp_bfr.Clear(), hpg, src, Bool_.N); + hpg.Section_mgr().Set_content(hpg.Section_mgr().Len() - 1, src, src.length); + break; + case Xoh_hzip_dict_.Hzip__plain: + gplx.xowa.addons.wikis.pages.syncs.core.loaders.Xosync_page_loader page_loader = new gplx.xowa.addons.wikis.pages.syncs.core.loaders.Xosync_page_loader(); + src = page_loader.Parse(wiki, hpg, src); + break; + } + return src; + } + private void Fill_page(Xoae_page wpg, Xoh_page hpg) { + Xopg_html_data html_data = wpg.Html_data(); + html_data.Display_ttl_(tmp_hpg.Display_ttl()); + html_data.Content_sub_(tmp_hpg.Content_sub()); + html_data.Xtn_skin_mgr().Add(new Xopg_xtn_skin_itm_stub(tmp_hpg.Sidebar_div())); + Xoh_head_mgr wpg_head = html_data.Head_mgr(); + Xopg_module_mgr hpg_head = hpg.Head_mgr(); + wpg_head.Itm__mathjax().Enabled_ (hpg_head.Math_exists()); + wpg_head.Itm__popups().Bind_hover_area_ (hpg_head.Imap_exists()); + wpg_head.Itm__gallery().Enabled_ (hpg_head.Gallery_packed_exists()); + wpg_head.Itm__hiero().Enabled_ (hpg_head.Hiero_exists()); + wpg_head.Itm__timeline().Enabled_ (hpg.Xtn__timeline_exists()); + wpg_head.Itm__gallery_styles().Enabled_ (hpg.Xtn__gallery_exists()); + wpg_head.Itm__toc().Enabled_(hpg.Html_data().Toc_mgr().Exists()); + wpg_head.Itm__pgbnr().Enabled_(hpg.Html_data().Head_mgr().Itm__pgbnr().Enabled()); + + // transfer Xtn_gallery_packed_exists; needed for hdump; PAGE:en.w:Mexico; DATE:2016-08-14 + if (hpg.Html_data().Xtn_gallery_packed_exists()) + wpg.Html_data().Xtn_gallery_packed_exists_y_(); + + // transfer images from Xoh_page to Xoae_page + Xoh_img_mgr src_imgs = hpg.Img_mgr(); + int len = src_imgs.Len(); + for (int i = 0; i < len; ++i) { + gplx.xowa.files.Xof_fsdb_itm itm = src_imgs.Get_at(i); + wpg.Hdump_mgr().Imgs().Add(itm); + if (!Io_mgr.Instance.ExistsFil(itm.Html_view_url())) // if exists, don't add to file_queue; needed for packed; PAGE:en.w:Mexico; DATE:2016-08-14 + wpg.File_queue().Add(itm); // add to file_queue for http_server + } + + // transfer redlinks + Xopg_lnki_list src_list = hpg.Html_data().Redlink_list(); + Xopg_lnki_list trg_list = wpg.Html_data().Redlink_list(); + len = src_list.Len(); + for (int i = 0; i < len; ++i) { + trg_list.Add_direct(src_list.Get_at(i)); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Cfg__read_preferred)) read_preferred = m.ReadYn("v"); + else if (ctx.Match(k, Cfg__html_mode)) html_mode = Xow_hdump_mode.Parse(m.ReadStr("v")); + return this; + } + private static final String + Cfg__read_preferred = "xowa.wiki.hdumps.read_preferred" + , Cfg__html_mode = "xowa.wiki.hdumps.html_mode" + ; + + private static boolean Load__fail(Xoh_page hpg) {hpg.Db().Page().Exists_n_(); return false;} + private static boolean Load__dbpg(Xow_wiki wiki, Xowd_page_itm dbpg, Xoh_page hpg, Xoa_ttl ttl) { + wiki.Data__core_mgr().Tbl__page().Select_by_ttl(dbpg, ttl.Ns(), ttl.Page_db()); + if (dbpg.Redirect_id() != -1) Load__dbpg__redirects(wiki, dbpg); + return dbpg.Html_db_id() != -1; + } + private static void Load__dbpg__redirects(Xow_wiki wiki, Xowd_page_itm dbpg) { + int redirect_count = 0; + while (++redirect_count < 5) { + int redirect_id = dbpg.Redirect_id(); + wiki.Data__core_mgr().Tbl__page().Select_by_id(dbpg, redirect_id); + if (redirect_id == -1) break; + } + } +} +class Xow_override_mgr { + private final Hash_adp_bry hash = Hash_adp_bry.cs(); + private final Io_url root_dir; + private boolean init = true; + public Xow_override_mgr(Io_url root_dir) {this.root_dir = root_dir;} + public void Clear() {hash.Clear();} + public byte[] Get_or_same(byte[] ttl, byte[] src) { + if (init) {init = false; Load_from_fsys(hash, root_dir);} + byte[] rv = (byte[])hash.Get_by_bry(ttl); + return rv == null ? src : rv; + } + private static void Load_from_fsys(Hash_adp_bry hash, Io_url root_dir) { + Io_url[] urls = Io_mgr.Instance.QueryDir_args(root_dir).Recur_(true).ExecAsUrlAry(); + int urls_len = urls.length; + for (int i = 0; i < urls_len; ++i) { + Io_url url = urls[i]; + byte[] raw = Io_mgr.Instance.LoadFilBry(url); int bry_len = raw.length; + int nl_pos = Bry_find_.Find_fwd(raw, Byte_ascii.Nl, 0, bry_len); if (nl_pos == Bry_find_.Not_found) continue; + byte[] ttl = Bry_.Mid(raw, 0, nl_pos); + byte[] src = Bry_.Mid(raw, nl_pos + 1, bry_len); + hash.Add_bry_obj(ttl, src); + } + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/Xow_hdump_mgr__save.java b/400_xowa/src/gplx/xowa/htmls/core/Xow_hdump_mgr__save.java index a27517de8..ed2d4511e 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/Xow_hdump_mgr__save.java +++ b/400_xowa/src/gplx/xowa/htmls/core/Xow_hdump_mgr__save.java @@ -13,3 +13,62 @@ 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.htmls.core; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.xowa.htmls.core.htmls.*; import gplx.xowa.htmls.core.wkrs.*; import gplx.xowa.htmls.core.hzips.*; import gplx.xowa.htmls.heads.*; import gplx.xowa.htmls.core.dbs.*; +import gplx.core.ios.*; import gplx.core.primitives.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.pages.*; +public class Xow_hdump_mgr__save { + private final Xow_wiki wiki; private final Xoh_hzip_mgr hzip_mgr; private final Io_stream_zip_mgr zip_mgr; + private final Xoh_page tmp_hpg; private final Xoh_hzip_bfr tmp_bfr = Xoh_hzip_bfr.New_txt(32); private Bool_obj_ref html_db_is_new = Bool_obj_ref.n_(); + private int dflt_zip_tid, dflt_hzip_tid; + public Xow_hdump_mgr__save(Xow_wiki wiki, Xoh_hzip_mgr hzip_mgr, Io_stream_zip_mgr zip_mgr, Xoh_page tmp_hpg) { + this.wiki = wiki; this.hzip_mgr = hzip_mgr; this.zip_mgr = zip_mgr; this.tmp_hpg = tmp_hpg; + } + public void Init_by_db(int dflt_zip_tid, int dflt_hzip_tid, boolean mode_is_b256) { + this.dflt_zip_tid = dflt_zip_tid; this.dflt_hzip_tid = dflt_hzip_tid; tmp_bfr.Mode_is_b256_(mode_is_b256); + } + public byte[] Src_as_hzip() {return src_as_hzip;} private byte[] src_as_hzip; + public int Save(Xoae_page page) { + synchronized (tmp_hpg) { + Bld_hdump(page); + tmp_hpg.Ctor_by_hdiff(tmp_bfr, page, page.Wikie().Msg_mgr().Val_by_id(gplx.xowa.langs.msgs.Xol_msg_itm_.Id_toc)); + Xow_db_file html_db = Get_html_db(wiki, page, html_db_is_new.Val_n_()); + return Save(tmp_hpg, html_db.Tbl__html(), html_db_is_new.Val(), true); + } + } + public int Save(Xoh_page hpg, Xowd_html_tbl html_tbl, boolean insert, boolean use_hzip_dflt) { + int hzip_tid = use_hzip_dflt ? dflt_hzip_tid : Xoh_hzip_dict_.Hzip__none; + byte[] db_body = Write(tmp_bfr, wiki, hpg, hzip_mgr, zip_mgr, dflt_zip_tid, hzip_tid, hpg.Db().Html().Html_bry()); + if (insert) html_tbl.Insert(hpg, dflt_zip_tid, dflt_hzip_tid, db_body); + else html_tbl.Update(hpg, dflt_zip_tid, dflt_hzip_tid, db_body); + return db_body.length; + } + public void Bld_hdump(Xoae_page page) { + page.File_queue().Clear(); // need to reset uid to 0, else xowa_file_# will keep incrementing upwards + wiki.Html__wtr_mgr().Wkr(Xopg_page_.Tid_read).Write_body(tmp_bfr, page.Wikie().Parser_mgr().Ctx(), Xoh_wtr_ctx.Hdump, page); // save as hdump_fmt + page.Db().Html().Html_bry_(tmp_bfr.To_bry_and_clear()); + } + private byte[] Write(Xoh_hzip_bfr bfr, Xow_wiki wiki, Xoh_page hpg, Xoh_hzip_mgr hzip_mgr, Io_stream_zip_mgr zip_mgr, int zip_tid, int hzip_tid, byte[] src) { + if (hzip_tid != Xoh_hzip_dict_.Hzip__none) src = hzip_mgr.Encode_as_bry((Xoh_hzip_bfr)bfr.Clear(), wiki, hpg, src); + src_as_hzip = src; + if (zip_tid > gplx.core.ios.streams.Io_stream_tid_.Tid__raw) + src = zip_mgr.Zip((byte)zip_tid, src); + return src; + } + private static Xow_db_file Get_html_db(Xow_wiki wiki, Xoae_page page, Bool_obj_ref html_db_is_new) { + Xow_db_file rv = Xow_db_file.Null; + Xow_db_mgr core_data_mgr = wiki.Data__core_mgr(); + int html_db_id = page.Db().Page().Html_db_id(); + if (html_db_id == -1) { + html_db_is_new.Val_y_(); + rv = core_data_mgr.Db__html(); + if (rv == null) rv = core_data_mgr.Dbs__make_by_tid(Xow_db_file_.Tid__html_data); + html_db_id = rv.Id(); + page.Db().Page().Html_db_id_(html_db_id); + core_data_mgr.Tbl__page().Update__html_db_id(page.Db().Page().Id(), html_db_id); + } + else { + rv = core_data_mgr.Dbs__get_by_id_or_fail(html_db_id); + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/Xow_hdump_mode.java b/400_xowa/src/gplx/xowa/htmls/core/Xow_hdump_mode.java index a27517de8..bcff05f5e 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/Xow_hdump_mode.java +++ b/400_xowa/src/gplx/xowa/htmls/core/Xow_hdump_mode.java @@ -13,3 +13,34 @@ 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.htmls.core; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +public class Xow_hdump_mode { + private final int tid; + private final String key; + private final String gui; + + public Xow_hdump_mode(int tid, String key, String gui) { + this.tid = tid; this.key = key; this.gui = gui; + } + public int Tid() {return tid;} +// boolean Tid_is_hdump_save() {return tid == Hdump_save.tid;} + public boolean Tid_is_custom() {return tid < Shown.tid;} + + public static final Xow_hdump_mode + Swt_browser = new Xow_hdump_mode(0, "swt_browser" , "SWT Browser") + , Hdump_save = new Xow_hdump_mode(1, "hdump_save" , "Saved for HTML DB") + , Shown = new Xow_hdump_mode(2, "shown" , "Shown") + , Hdump_load = new Xow_hdump_mode(3, "hdump_load" , "Loaded by HTML DB") + ; + public static void Cfg__reg_type(gplx.xowa.addons.apps.cfgs.mgrs.types.Xocfg_type_mgr type_mgr) { + type_mgr.Lists__add("list:xowa.wiki.hdumps.html_mode", To_kv(Shown), To_kv(Swt_browser), To_kv(Hdump_save), To_kv(Hdump_load)); + } + private static Keyval To_kv(Xow_hdump_mode mode) {return Keyval_.new_(mode.key, mode.gui);} + public static Xow_hdump_mode Parse(String v) { + if (String_.Eq(v, Shown.key)) return Shown; + else if (String_.Eq(v, Hdump_save.key)) return Hdump_save; + else if (String_.Eq(v, Hdump_load.key)) return Hdump_load; + else if (String_.Eq(v, Swt_browser.key)) return Swt_browser; + else throw Err_.new_unhandled(v); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_hdump_bldr.java b/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_hdump_bldr.java index a27517de8..b3f77c01c 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_hdump_bldr.java +++ b/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_hdump_bldr.java @@ -13,3 +13,81 @@ 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.htmls.core.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; +import gplx.core.brys.*; import gplx.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.apps.apis.xowa.bldrs.imports.*; +import gplx.xowa.htmls.core.htmls.*; import gplx.xowa.htmls.core.hzips.*; import gplx.xowa.htmls.core.dbs.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.data.*; +import gplx.xowa.parsers.*; +public class Xob_hdump_bldr implements Gfo_invk { + private boolean enabled, hzip_enabled, hzip_diff, hzip_b256; private byte zip_tid = Byte_.Max_value_127; + private Xowe_wiki wiki; private Xob_hdump_tbl_retriever html_tbl_retriever; + private Xoh_stat_tbl stat_tbl; private Xoh_stat_itm stat_itm; + private int prv_row_len = 0; + private final Xoh_page tmp_hpg = new Xoh_page(); private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + private boolean op_sys_is_wnt; + private byte[] toc_label = Bry_.Empty; + public Xob_hdump_bldr Enabled_(boolean v) {this.enabled = v; return this;} + public Xob_hdump_bldr Hzip_enabled_(boolean v) {this.hzip_enabled = v; return this;} + public Xob_hdump_bldr Hzip_diff_(boolean v) {this.hzip_diff = v; return this;} + public Xob_hdump_bldr Zip_tid_(byte v) {this.zip_tid = v; return this;} + public Xow_hdump_mgr Hdump_mgr() {return hdump_mgr;} private Xow_hdump_mgr hdump_mgr; + public boolean Init(Xowe_wiki wiki, Db_conn make_conn, Xob_hdump_tbl_retriever html_tbl_retriever) { + if (!enabled) return false; + this.op_sys_is_wnt = gplx.core.envs.Op_sys.Cur().Tid_is_wnt(); + this.wiki = wiki; this.hdump_mgr = wiki.Html__hdump_mgr(); this.html_tbl_retriever = html_tbl_retriever; + this.stat_tbl = new Xoh_stat_tbl(make_conn); this.stat_itm = hdump_mgr.Hzip_mgr().Hctx().Hzip__stat(); + this.toc_label = wiki.Msg_mgr().Val_by_id(gplx.xowa.langs.msgs.Xol_msg_itm_.Id_toc); + + if (zip_tid == Byte_.Max_value_127) zip_tid = Xobldr_cfg.Zip_mode__html(wiki.App()); + hdump_mgr.Init_by_db(zip_tid, hzip_enabled, hzip_b256); + return true; + } + public void Insert(Xop_ctx ctx, Xoae_page wpg) { + // clear + tmp_hpg.Clear(); // NOTE: must clear tmp_hpg or else will leak memory during mass build; DATE:2016-01-09 + wpg.File_queue().Clear(); // need to reset uid to 0, else xowa_file_# will resume from last + + // write to html + Xoa_ttl ttl = wpg.Ttl(); + boolean is_wikitext = Xow_page_tid.Identify(wpg.Wiki().Domain_tid(), ttl.Ns().Id(), ttl.Page_db()) == Xow_page_tid.Tid_wikitext; + byte[] orig_bry = Bry_.Empty; + if (is_wikitext) { + wiki.Html_mgr().Page_wtr_mgr().Wkr(Xopg_page_.Tid_read).Write_hdump(tmp_bfr, ctx, Xoh_wtr_ctx.Hdump, wpg); + orig_bry = tmp_bfr.To_bry_and_clear(); + wpg.Db().Html().Html_bry_(orig_bry); + } + else { // not wikitext; EX: pages in MediaWiki: ns; DATE:2016-09-12 + wpg.Db().Html().Html_bry_(wpg.Db().Text().Text_bry()); + } + + // save to db + Xowd_html_tbl html_tbl = html_tbl_retriever.Get_html_tbl(wpg.Ttl().Ns(), prv_row_len); // get html_tbl + this.prv_row_len = hdump_mgr.Save_mgr().Save(tmp_hpg.Ctor_by_hdiff(tmp_bfr, wpg, toc_label), html_tbl, true, is_wikitext); // save to db + stat_tbl.Insert(tmp_hpg, stat_itm, wpg.Root().Root_src().length, tmp_hpg.Db().Html().Html_bry().length, prv_row_len); // save stats + + // run hzip diff if enabled + if (hzip_diff && is_wikitext) { + byte[] expd_bry = op_sys_is_wnt ? Bry_.Replace(tmp_bfr, orig_bry, Byte_ascii.Cr_lf_bry, Byte_ascii.Nl_bry) : orig_bry; // tidy adds crlf if wnt + byte[] actl_bry = hdump_mgr.Load_mgr().Decode_as_bry(tmp_bfr, tmp_hpg, hdump_mgr.Save_mgr().Src_as_hzip(), Bool_.Y); + byte[][] diff = Bry_diff_.Diff_1st_line(expd_bry, actl_bry); + if (diff != null) + Gfo_usr_dlg_.Instance.Warn_many("", "", String_.Format("hzip diff: page={0} lhs='{1}' rhs='{2}'", tmp_hpg.Url_bry_safe(), diff[0], diff[1])); + } + } + public void Commit() { + html_tbl_retriever.Commit(); + // wiki_db_mgr.Tbl__cfg().Update_long(Cfg_grp_hdump_make, Cfg_itm_hdump_size, hdump_db_size); // update cfg; should happen after commit entries + } + public void Term() {this.Commit(); html_tbl_retriever.Rls_all();} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_enabled_)) enabled = m.ReadYn("v"); + else if (ctx.Match(k, Invk_zip_tid_)) zip_tid = m.ReadByte("v"); + else if (ctx.Match(k, Invk_hzip_enabled_)) hzip_enabled = m.ReadYn("v"); + else if (ctx.Match(k, Invk_hzip_diff_)) hzip_diff = m.ReadYn("v"); + else if (ctx.Match(k, Invk_hzip_b256_)) hzip_b256 = m.ReadYn("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_enabled_ = "enabled_", Invk_zip_tid_ = "zip_tid_", Invk_hzip_enabled_ = "hzip_enabled_", Invk_hzip_diff_ = "hzip_diff_", Invk_hzip_b256_ = "hzip_b256_"; +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_hdump_tbl_retriever.java b/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_hdump_tbl_retriever.java index a27517de8..caf966d93 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_hdump_tbl_retriever.java +++ b/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_hdump_tbl_retriever.java @@ -13,3 +13,10 @@ 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.htmls.core.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; +import gplx.xowa.wikis.nss.*; import gplx.xowa.htmls.core.dbs.*; +public interface Xob_hdump_tbl_retriever { + Xowd_html_tbl Get_html_tbl(Xow_ns ns, int prv_row_len); + void Commit(); + void Rls_all(); +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_link_dump_cmd.java b/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_link_dump_cmd.java index a27517de8..11adde154 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_link_dump_cmd.java +++ b/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_link_dump_cmd.java @@ -13,3 +13,41 @@ 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.htmls.core.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; +import gplx.core.brys.*; import gplx.dbs.*; +public class Xob_link_dump_cmd { + private Xob_link_dump_tbl tbl; private int src_page_id; private Io_url page_db_url; + public void Init_by_wiki(Xowe_wiki wiki) { + this.page_db_url = wiki.Data__core_mgr().Db__core().Url(); + this.tbl = Xob_link_dump_tbl.Get_or_new(wiki); + tbl.Insert_bgn(); + } + public void Page_bgn(int src_page_id) {this.src_page_id = src_page_id;} + public void Add(int src_html_uid, int trg_ns_id, byte[] trg_ttl) {tbl.Insert_cmd_by_batch(src_page_id, src_html_uid, trg_ns_id, trg_ttl);} + public void Wkr_commit() {tbl.Conn().Txn_sav();} + public void Wkr_end() { + try { + tbl.Insert_end(); + tbl.Create_idx_1(); + Db_conn conn = tbl.Conn(); + new Db_attach_mgr(conn, new Db_attach_itm("page_db", page_db_url)) + .Exec_sql_w_msg("update trg_page_id", String_.Concat_lines_nl_skip_last + ( "REPLACE INTO link_dump" + , "SELECT r.uid" + , ", r.src_page_id" + , ", r.src_html_uid" + , ", Coalesce(p.page_id, -1)" + , ", r.trg_ns" + , ", r.trg_ttl" + , "FROM link_dump r" + , " LEFT JOIN page p ON r.trg_ns = p.page_namespace AND r.trg_ttl = p.page_title" + , ";" + ));; + conn.Exec_sql("UPDATE link_dump SET trg_ns = -1 AND trg_ttl = '' WHERE trg_page_id != -1;"); + tbl.Create_idx_2(); + conn.Env_vacuum(); + } catch (Exception e) { + Tfds.Dbg(Err_.Message_gplx_full(e)); + } + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_link_dump_tbl.java b/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_link_dump_tbl.java index a27517de8..0f5f42ee2 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_link_dump_tbl.java +++ b/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_link_dump_tbl.java @@ -13,3 +13,55 @@ 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.htmls.core.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; +import gplx.dbs.*; +class Xob_link_dump_tbl implements Rls_able { + public static final String Tbl_name = "link_dump"; private static final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + public static final String + Fld_uid = flds.Add_int_pkey_autonum("uid") + , Fld_src_page_id = flds.Add_int("src_page_id") + , Fld_src_html_uid = flds.Add_int("src_html_uid") + , Fld_trg_page_id = flds.Add_int_dflt("trg_page_id", -1) + , Fld_trg_ns = flds.Add_int("trg_ns") + , Fld_trg_ttl = flds.Add_str("trg_ttl", 255) + ; + private Db_stmt stmt_insert; + public Xob_link_dump_tbl(Db_conn conn) { + this.conn = conn; + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(Tbl_name, flds));} + public void Create_idx_1() { + conn.Meta_idx_create + ( Dbmeta_idx_itm.new_normal_by_tbl(Tbl_name, "src", Fld_src_page_id, Fld_src_html_uid) + , Dbmeta_idx_itm.new_normal_by_tbl(Tbl_name, "trg_temp", Fld_trg_ns, Fld_trg_ttl) + ); + } + public void Create_idx_2() { + conn.Meta_idx_create + ( Dbmeta_idx_itm.new_normal_by_tbl(Tbl_name, "trg", Fld_trg_page_id, Fld_src_page_id, Fld_src_html_uid) + ); + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + } + public void Insert_bgn() {conn.Txn_bgn("bldr__link_dump");} + public void Insert_end() {conn.Txn_end(); stmt_insert = Db_stmt_.Rls(stmt_insert);} + public void Insert_cmd_by_batch(int src_page_id, int src_html_uid, int trg_ns, byte[] trg_ttl) { + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(Tbl_name, flds.To_str_ary_wo_autonum()); + stmt_insert.Clear().Val_int(Fld_src_page_id, src_page_id) + .Val_int(Fld_src_html_uid, src_html_uid).Val_int(Fld_trg_page_id, -1).Val_int(Fld_trg_ns, trg_ns).Val_bry_as_str(Fld_trg_ttl, trg_ttl) + .Exec_insert(); + } + public Db_rdr Select_missing() { + return conn.Stmt_select_order(Tbl_name, flds, String_.Ary(Fld_trg_page_id), Fld_src_page_id, Fld_src_html_uid) + .Crt_int(Fld_trg_page_id, -1).Exec_select__rls_auto(); + } + public static Xob_link_dump_tbl Get_or_new(Xow_wiki wiki) { + Db_conn_bldr_data conn_data = Db_conn_bldr.Instance.Get_or_new(wiki.Fsys_mgr().Root_dir().GenSubFil("xowa.temp.redlink.sqlite3")); + Xob_link_dump_tbl rv = new Xob_link_dump_tbl(conn_data.Conn()); + if (conn_data.Created()) rv.Create_tbl(); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_ns_to_db_wkr__html.java b/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_ns_to_db_wkr__html.java index a27517de8..227bab9cb 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_ns_to_db_wkr__html.java +++ b/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_ns_to_db_wkr__html.java @@ -13,3 +13,39 @@ 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.htmls.core.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; +import gplx.dbs.*; import gplx.xowa.bldrs.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.htmls.core.dbs.*; +public class Xob_ns_to_db_wkr__html implements Xob_ns_to_db_wkr { + private final Xow_db_file page_db; + public Xob_ns_to_db_wkr__html(Xow_db_file page_db) {this.page_db = page_db;} + public byte Db_tid() {return Xow_db_file_.Tid__html_data;} + public void Tbl_init(Xow_db_file db) { + Xowd_html_tbl tbl = db.Tbl__html(); + tbl.Create_tbl(); + tbl.Insert_bgn(); + } + public void Tbl_term(Xow_db_file db) { + db.Tbl__text().Insert_end(); + Db_conn db_conn = db.Conn(); + new Db_attach_mgr(page_db.Conn(), new Db_attach_itm("html_db", db.Url())) + .Exec_sql_w_msg("hdump.update page.html_db_id", Sql_update_page_html_db_id, db.Id()); + db_conn.Rls_conn(); + } + private static final String Sql_update_page_html_db_id = String_.Concat_lines_nl_skip_last + ( "REPLACE INTO page (page_id, page_namespace, page_title, page_is_redirect, page_touched, page_len, page_random_int, page_text_db_id, page_html_db_id, page_redirect_id, page_score)" + , "SELECT p.page_id" + , ", p.page_namespace" + , ", p.page_title" + , ", p.page_is_redirect" + , ", p.page_touched" + , ", p.page_len" + , ", p.page_random_int" + , ", p.page_text_db_id" + , ", {0}" + , ", p.page_redirect_id" + , ", p.page_score" + , "FROM page p" + , " JOIN html h ON p.page_id = h.page_id" + ); +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_redlink_mkr_cmd.java b/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_redlink_mkr_cmd.java index a27517de8..f45329323 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_redlink_mkr_cmd.java +++ b/400_xowa/src/gplx/xowa/htmls/core/bldrs/Xob_redlink_mkr_cmd.java @@ -13,3 +13,78 @@ 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.htmls.core.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; +import gplx.dbs.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.htmls.core.dbs.*; +public class Xob_redlink_mkr_cmd extends Xob_itm_basic_base implements Xob_cmd { + private int commit_interval = 10000, commit_count = 0; + public Xob_redlink_mkr_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.Cmd_ctor(bldr, wiki);} + public String Cmd_key() {return Xob_cmd_keys.Key_html_redlinks;} + public void Cmd_run() {Read_data();} + private void Read_data() { + Bry_bfr bfr = Bry_bfr_.Reset(255); + wiki.Init_assert(); + Xow_db_file core_db = wiki.Data__core_mgr().Db__core(); + Xob_db_file link_dump_db = Xob_db_file.New__redlink(wiki.Fsys_mgr().Root_dir()); + Db_attach_mgr attach_mgr = new Db_attach_mgr(link_dump_db.Conn(), new Db_attach_itm("page_db", wiki.Data__core_mgr().Db__core().Conn())); + String attach_sql = attach_mgr.Resolve_sql(Sql_select_clause); + attach_mgr.Attach(); + Xowd_page_tbl page_tbl = core_db.Tbl__page(); + int cur_html_db_id = -1, cur_page_id = -1; + Xoh_redlink_tbl redlink_tbl = new Xoh_redlink_tbl(page_tbl.Conn()); + Db_rdr rdr = link_dump_db.Conn().Exec_rdr(attach_sql); + try { + while (rdr.Move_next()) { + // switch html_db if needed + int html_db_id = rdr.Read_int(page_tbl.Fld_html_db_id()); + if (html_db_id != cur_html_db_id) { + if (redlink_tbl != null) redlink_tbl.Conn().Txn_end(); + // redlink_tbl = wiki.Data__core_mgr().Dbs__get_by_id(html_db_id).Tbl__html_redlink(); + redlink_tbl.Conn().Txn_bgn("bldr__redlink"); + cur_html_db_id = html_db_id; + } + // commit page_id if needed + int page_id = rdr.Read_int(page_tbl.Fld_page_id()); + if (cur_page_id != page_id) { + if (cur_page_id != -1) Commit(redlink_tbl, cur_page_id, bfr); + bfr.Add_int_variable(2).Add_byte_pipe(); // 2=gplx.xowa.htmls.core.makes.imgs.Xohd_img_tid.Tid_redlink + cur_page_id = page_id; + } + // add html_uid to cur_page's bfr + int html_uid = rdr.Read_int(Xob_link_dump_tbl.Fld_src_html_uid); + bfr.Add_int_variable(html_uid).Add_byte_pipe(); + } + } + finally {rdr.Rls();} + Commit(redlink_tbl, cur_page_id, bfr); // commit cur page + redlink_tbl.Conn().Txn_end(); // close cur tbl + attach_mgr.Detach(); + } + private void Commit(Xoh_redlink_tbl redlink_tbl, int cur_page_id, Bry_bfr bfr) { + redlink_tbl.Insert(cur_page_id, bfr.To_bry_and_clear()); + ++commit_count; + if ((commit_count % commit_interval ) == 0) + redlink_tbl.Conn().Txn_sav(); + } + private static final String Sql_select_clause = String_.Concat_lines_nl_skip_last + ( "SELECT p.page_html_db_id" + , ", p.page_id" + , ", ld.src_html_uid" + , "FROM link_dump ld" + , " JOIN page p ON p.page_id = ld.src_page_id " + , "WHERE ld.trg_page_id = -1" + , "ORDER BY p.page_html_db_id, p.page_id" + , ";" + ); + public void Cmd_init(Xob_bldr bldr) {} + public void Cmd_bgn(Xob_bldr bldr) {} + public void Cmd_end() {} + public void Cmd_term() {} + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_commit_interval_)) commit_interval = m.ReadInt("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_commit_interval_ = "commit_interval_"; +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/dbs/Xoh_redlink_tbl.java b/400_xowa/src/gplx/xowa/htmls/core/dbs/Xoh_redlink_tbl.java index a27517de8..6d92f747a 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/dbs/Xoh_redlink_tbl.java +++ b/400_xowa/src/gplx/xowa/htmls/core/dbs/Xoh_redlink_tbl.java @@ -13,3 +13,46 @@ 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.htmls.core.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; +import gplx.dbs.*; +public class Xoh_redlink_tbl implements Rls_able { + private final String tbl_name = "html_redlink"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_page_id, fld_redlink_uids; + private final Db_conn conn; private Db_stmt stmt_select, stmt_insert, stmt_delete, stmt_update; + public Xoh_redlink_tbl(Db_conn conn) { + this.conn = conn; + this.fld_page_id = flds.Add_int_pkey("page_id"); + this.fld_redlink_uids = flds.Add_bry("redlink_uids"); + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Insert_bgn() {conn.Txn_bgn("html_redlink__insert"); stmt_insert = conn.Stmt_insert(tbl_name, flds);} + public void Insert_end() {conn.Txn_end(); stmt_insert = Db_stmt_.Rls(stmt_insert);} + public void Insert(int page_id, byte[] redlink_uids) { + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Clear().Val_int(fld_page_id, page_id).Val_bry(fld_redlink_uids, redlink_uids).Exec_insert(); + } + public void Update(int page_id, byte[] redlink_uids) { + if (stmt_update == null) stmt_update = conn.Stmt_update_exclude(tbl_name, flds, fld_page_id); + stmt_update.Clear().Val_bry(fld_redlink_uids, redlink_uids).Crt_int(fld_page_id, page_id).Exec_update(); + } + public void Delete(int page_id) { + if (stmt_delete == null) stmt_delete = conn.Stmt_delete(tbl_name, fld_page_id); + stmt_delete.Clear().Crt_int(fld_page_id, page_id).Exec_delete(); + } + public byte[] Select_or_null(int page_id) { + if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_page_id); + Db_rdr rdr = stmt_select.Clear().Crt_int(fld_page_id, page_id).Exec_select__rls_manual(); + try { + return rdr.Move_next() ? rdr.Read_bry(fld_redlink_uids) : null; + } + finally {rdr.Rls();} + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + stmt_delete = Db_stmt_.Rls(stmt_delete); + stmt_select = Db_stmt_.Rls(stmt_select); + stmt_update = Db_stmt_.Rls(stmt_update); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/dbs/Xowd_html_row.java b/400_xowa/src/gplx/xowa/htmls/core/dbs/Xowd_html_row.java index a27517de8..e9e8e8d69 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/dbs/Xowd_html_row.java +++ b/400_xowa/src/gplx/xowa/htmls/core/dbs/Xowd_html_row.java @@ -13,3 +13,24 @@ 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.htmls.core.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; +public class Xowd_html_row { + public void Load(int page_id, int head_flag, int body_flag, byte[] display_ttl, byte[] content_sub, byte[] sidebar_div, byte[] body) { + this.page_id = page_id; + this.head_flag = head_flag; + this.body_flag = body_flag; + this.display_ttl = display_ttl; + this.content_sub = content_sub; + this.sidebar_div = sidebar_div; + this.body = body; + } + public int Page_id() {return page_id;} private int page_id; + public int Head_flag() {return head_flag;} private int head_flag; + public int Body_flag() {return body_flag;} private int body_flag; + public byte[] Display_ttl() {return display_ttl;} private byte[] display_ttl; + public byte[] Content_sub() {return content_sub;} private byte[] content_sub; + public byte[] Sidebar_div() {return sidebar_div;} private byte[] sidebar_div; + public byte[] Body() {return body;} private byte[] body; + + public static final int Db_row_size_fixed = (3 * 4); // page_id, head_flag, body_flag +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/dbs/Xowd_html_tbl.java b/400_xowa/src/gplx/xowa/htmls/core/dbs/Xowd_html_tbl.java index a27517de8..605817c7e 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/dbs/Xowd_html_tbl.java +++ b/400_xowa/src/gplx/xowa/htmls/core/dbs/Xowd_html_tbl.java @@ -13,3 +13,110 @@ 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.htmls.core.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; +import gplx.dbs.*; import gplx.core.brys.*; +public class Xowd_html_tbl implements Db_tbl { + private final String fld_page_id, fld_head_flag, fld_body_flag, fld_display_ttl, fld_content_sub, fld_sidebar_div, fld_body; + private Db_stmt stmt_select, stmt_insert, stmt_update; + private final Int_flag_bldr body_flag_bldr = Make_body_flag_bldr(); + public Xowd_html_tbl(Db_conn conn) { + this.conn = conn; + this.fld_page_id = flds.Add_int_pkey("page_id"); + this.fld_head_flag = flds.Add_int("head_flag"); + this.fld_body_flag = flds.Add_int("body_flag"); + this.fld_display_ttl = flds.Add_str("display_ttl", 1024); + this.fld_content_sub = flds.Add_str("content_sub", 1024); + this.fld_sidebar_div = flds.Add_str("sidebar_div", 2048); + this.fld_body = flds.Add_bry("body"); + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public String Tbl_name() {return tbl_name;} private final String tbl_name = "html"; + public Dbmeta_fld_list Flds() {return flds;} private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + + public void Insert_bgn() {conn.Txn_bgn("html__insert"); stmt_insert = conn.Stmt_insert(tbl_name, flds);} + public void Insert_end() {conn.Txn_end(); stmt_insert = Db_stmt_.Rls(stmt_insert);} + public void Insert(Xoh_page hpg, int zip_tid, int hzip_tid, byte[] body) {Insert(hpg.Page_id(), hpg.Head_mgr().Flag(), zip_tid, hzip_tid, hpg.Display_ttl(), hpg.Content_sub(), hpg.Sidebar_div(), body);} + public void Insert(int page_id, int head_flag, int zip_tid, int hzip_tid, byte[] display_ttl, byte[] content_sub, byte[] sidebar_div, byte[] body) { + int body_flag = body_flag_bldr.Set(0, zip_tid).Set(1, hzip_tid).Encode(); + Insert(page_id, head_flag, body_flag, display_ttl, content_sub, sidebar_div, body); + } + public void Insert(int page_id, int head_flag, int body_flag, byte[] display_ttl, byte[] content_sub, byte[] sidebar_div, byte[] body) { + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Clear().Val_int(fld_page_id, page_id); + Fill_stmt(stmt_insert, head_flag, body_flag, display_ttl, content_sub, sidebar_div, body); + stmt_insert.Exec_insert(); + } + public void Update(Xoh_page hpg, int zip_tid, int hzip_tid, byte[] body) {Update(hpg.Page_id(), hpg.Head_mgr().Flag(), zip_tid, hzip_tid, hpg.Display_ttl(), hpg.Content_sub(), hpg.Sidebar_div(), body);} + public void Update(int page_id, int head_flag, int zip_tid, int hzip_tid, byte[] display_ttl, byte[] content_sub, byte[] sidebar_div, byte[] body) { + if (stmt_update == null) stmt_update = conn.Stmt_update_exclude(tbl_name, flds, fld_page_id); + int body_flag = body_flag_bldr.Set(0, zip_tid).Set(1, hzip_tid).Encode(); + stmt_update.Clear(); + Fill_stmt(stmt_update, head_flag, body_flag, display_ttl, content_sub, sidebar_div, body); + stmt_update.Crt_int(fld_page_id, page_id).Exec_update(); + } + public void Upsert(int page_id, int head_flag, int zip_tid, int hzip_tid, byte[] display_ttl, byte[] content_sub, byte[] sidebar_div, byte[] body) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_page_id).Clear().Crt_int(fld_page_id, page_id).Exec_select__rls_auto(); + boolean exists = rdr.Move_next(); + rdr.Rls(); + if (exists) + Update(page_id, head_flag, zip_tid, hzip_tid, display_ttl, content_sub, sidebar_div, body); + else + Insert(page_id, head_flag, zip_tid, hzip_tid, display_ttl, content_sub, sidebar_div, body); + } + public void Delete(int page_id) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.html: delete started: db=~{0} page_id=~{1}", conn.Conn_info().Raw(), page_id); + conn.Stmt_delete(tbl_name, fld_page_id).Crt_int(fld_page_id, page_id).Exec_delete(); + Gfo_usr_dlg_.Instance.Log_many("", "", "db.html: delete done"); + } + public void Update_page_id(int old_id, int new_id) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.html: update page_id started: db=~{0} old_id=~{1} new_id=~{2}", conn.Conn_info().Raw(), old_id, new_id); + conn.Stmt_update(tbl_name, String_.Ary(fld_page_id), fld_page_id).Val_int(fld_page_id, new_id).Crt_int(fld_page_id, old_id).Exec_update(); + Gfo_usr_dlg_.Instance.Log_many("", "", "db.html: update page_id done"); + } + public boolean Select_by_page(Xoh_page hpg) { + if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_page_id); + Db_rdr rdr = stmt_select.Clear().Crt_int(fld_page_id, hpg.Page_id()).Exec_select__rls_manual(); + try { + if (rdr.Move_next()) { + int body_flag = rdr.Read_int(fld_body_flag); + body_flag_bldr.Decode(body_flag); + hpg.Ctor_by_db(rdr.Read_int(fld_head_flag), rdr.Read_bry_by_str(fld_display_ttl), rdr.Read_bry_by_str(fld_content_sub), rdr.Read_bry_by_str(fld_sidebar_div), body_flag_bldr.Get_as_int(0), body_flag_bldr.Get_as_int(1), rdr.Read_bry(fld_body)); + return true; + } + return false; + } + finally {rdr.Rls();} + } + public boolean Select_as_row(Xowd_html_row rv, int page_id) { + if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_page_id); + Db_rdr rdr = stmt_select.Clear().Crt_int(fld_page_id, page_id).Exec_select__rls_manual(); + try { + if (rdr.Move_next()) { + rv.Load + ( page_id + , rdr.Read_int(fld_head_flag) + , rdr.Read_int(fld_body_flag) + , rdr.Read_bry_by_str(fld_display_ttl) + , rdr.Read_bry_by_str(fld_content_sub) + , rdr.Read_bry_by_str(fld_sidebar_div) + , rdr.Read_bry(fld_body) + ); + return true; + } + return false; + } + finally {rdr.Rls();} + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + stmt_select = Db_stmt_.Rls(stmt_select); + stmt_update = Db_stmt_.Rls(stmt_update); + } + public void Fill_stmt(Db_stmt stmt, int head_flag, int body_flag, byte[] display_ttl, byte[] content_sub, byte[] sidebar_div, byte[] body) { + stmt.Val_int(fld_head_flag, head_flag).Val_int(fld_body_flag, body_flag) + .Val_bry_as_str(fld_display_ttl, Bry_.Coalesce_to_empty(display_ttl)).Val_bry_as_str(fld_content_sub, Bry_.Coalesce_to_empty(content_sub)).Val_bry_as_str(fld_sidebar_div, Bry_.Coalesce_to_empty(sidebar_div)).Val_bry(fld_body, body); + } + public static Int_flag_bldr Make_body_flag_bldr() {return new Int_flag_bldr().Pow_ary_bld_(3, 2);} // 8 different zip types; 4 different hzip types +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_display_ttl_wtr.java b/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_display_ttl_wtr.java index a27517de8..9b7b7a018 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_display_ttl_wtr.java +++ b/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_display_ttl_wtr.java @@ -13,3 +13,33 @@ 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.htmls.core.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; +import gplx.core.btries.*; +import gplx.xowa.parsers.htmls.*; +class Xoh_display_ttl_wtr { + public static boolean Is_style_restricted(Bry_bfr bfr, Xoh_wtr_ctx hctx, byte[] src, Mwh_atr_itm atr, byte[] atr_key) { + if (atr_key != null + && Bry_.Eq(atr_key, Atr_key_style) + ) { + byte[] atr_val = atr.Val_as_bry(); if (atr_val == null) return false; // bounds_chk + int atr_val_len = atr_val.length; + int atr_pos = 0; + while (atr_pos < atr_val_len) { + byte b = atr_val[atr_pos]; + Object o = style_trie.Match_bgn_w_byte(b, atr_val, atr_pos, atr_val_len); + if (o != null) { + bfr.Add(Msg_style_restricted); + return true; + } + ++atr_pos; + } + } + return false; + } + private static final byte[] + Atr_key_style = Bry_.new_a7("style") + , Msg_style_restricted = Bry_.new_a7(" style='/* attempt to bypass $wgRestrictDisplayTitle */'") + ; + private static final Btrie_slim_mgr style_trie = Btrie_slim_mgr.ci_a7() + .Add_str_byte__many(Byte_.By_int(0), "display", "user-select", "visibility"); // if ( preg_match( '/(display|user-select|visibility)\s*:/i', $decoded['style'] ) ) { +} 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 a27517de8..010fb2522 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 @@ -13,3 +13,541 @@ 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.htmls.core.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; +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.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; + private Xoae_page page; + private int indent_level; + private int stack_counter; + public Xoh_html_wtr(Xowe_wiki wiki, Xow_html_mgr html_mgr) { + this.wiki = wiki; this.app = wiki.Appe(); + this.html_mgr = html_mgr; this.whitelist_mgr = html_mgr.Whitelist_mgr(); + this.lnki_wtr = new Xoh_lnki_wtr(this, wiki, html_mgr, cfg); + this.ref_wtr = new Ref_html_wtr(wiki); + } + public Xoh_html_wtr_cfg Cfg() {return cfg;} private final Xoh_html_wtr_cfg cfg = new Xoh_html_wtr_cfg(); + public Xoh_lnke_html Wkr__lnke() {return wkr__lnke;} private final Xoh_lnke_html wkr__lnke = new Xoh_lnke_html(); + public Xoh_hdr_html Wkr__hdr() {return wkr__hdr;} private final Xoh_hdr_html wkr__hdr = new Xoh_hdr_html(); + public Xoh_lnki_wtr Lnki_wtr() {return lnki_wtr;} private final Xoh_lnki_wtr lnki_wtr; + public Ref_html_wtr Ref_wtr() {return ref_wtr;} private final Ref_html_wtr ref_wtr; + + public void Init_by_wiki(Xowe_wiki wiki) { + cfg.Toc__show_(Bool_.Y).Lnki__title_(true).Lnki__visited_y_().Lnki__id_(Bool_.Y); // NOTE: set during Init_by_wiki, b/c all tests assume these are false + ref_wtr.Init_by_wiki(wiki); + lnki_wtr.Init_by_wiki(wiki); + } + public void Init_by_page(Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xoae_page page) { + this.page = page; + lnki_wtr.Init_by_page(ctx, hctx, src, page); + } + + public void Write_doc(Bry_bfr rv, Xop_ctx ctx, byte[] src, Xop_root_tkn root) {Write_doc(rv, ctx, Xoh_wtr_ctx.Basic, src, root);} + public void Write_doc(Bry_bfr rv, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xop_root_tkn root) { + // init + this.indent_level = 0; + this.stack_counter = 0; + this.page = ctx.Page(); + page.Slink_list().Clear(); // HACK: always clear langs; necessary for reload + lnki_wtr.Init_by_page(ctx, hctx, src, ctx.Page()); + + // write document starting from root + Write_tkn(rv, ctx, hctx, src, null, -1, root); + } + public void Write_tkn_to_html(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xop_tkn_grp grp, int sub_idx, Xop_tkn_itm tkn) { + this.Write_tkn(bfr, ctx, hctx, src, grp, sub_idx, tkn); + } + private void Write_tkn(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xop_tkn_grp grp, int sub_idx, Xop_tkn_itm tkn) { + if (tkn.Ignore()) return; + if (++stack_counter > 1500) { // NOTE:some deeply nested pages can go to 1500; PAGE:cs.s:Page:Hejčl,_Jan_-_Pentateuch.pdf/128 DATE:2016-09-01; PAGE:en.w:Wikipedia:People_by_year/Reports/Stats; DATE:2016-09-11 + Gfo_usr_dlg_.Instance.Warn_many("", "", "stack overflow while generating html; wiki=~{0} page=~{1}", ctx.Wiki().Domain_bry(), ctx.Page().Ttl().Full_db()); + return; + } + switch (tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_arg_itm: + case Xop_tkn_itm_.Tid_root: + int subs_len = tkn.Subs_len(); + for (int i = 0; i < subs_len; i++) + Write_tkn(bfr, ctx, hctx, src, tkn, i, tkn.Subs_get(i)); + break; + case Xop_tkn_itm_.Tid_ignore: break; + case Xop_tkn_itm_.Tid_html_ncr: Html_ncr (bfr, ctx, hctx, src, (Xop_amp_tkn_num)tkn); break; + case Xop_tkn_itm_.Tid_html_ref: Html_ref (bfr, ctx, hctx, src, (Xop_amp_tkn_ent)tkn); break; + case Xop_tkn_itm_.Tid_hr: Hr (bfr, ctx, hctx, src, (Xop_hr_tkn)tkn); break; + case Xop_tkn_itm_.Tid_apos: Apos (bfr, ctx, hctx, src, (Xop_apos_tkn)tkn); break; + case Xop_tkn_itm_.Tid_list: List (bfr, ctx, hctx, src, (Xop_list_tkn)tkn); break; + case Xop_tkn_itm_.Tid_xnde: Xnde (bfr, ctx, hctx, src, (Xop_xnde_tkn)tkn); break; + case Xop_tkn_itm_.Tid_under: Under (bfr, ctx, hctx, src, (Xop_under_tkn)tkn); break; + case Xop_tkn_itm_.Tid_tblw_tb: Tblw (bfr, ctx, hctx, src, (Xop_tblw_tkn)tkn, Gfh_tag_.Table_lhs_bgn , Gfh_tag_.Table_rhs, true); break; + case Xop_tkn_itm_.Tid_tblw_tr: Tblw (bfr, ctx, hctx, src, (Xop_tblw_tkn)tkn, Gfh_tag_.Tr_lhs_bgn , Gfh_tag_.Tr_rhs, false); break; + case Xop_tkn_itm_.Tid_tblw_td: Tblw (bfr, ctx, hctx, src, (Xop_tblw_tkn)tkn, Gfh_tag_.Td_lhs_bgn , Gfh_tag_.Td_rhs, false); break; + case Xop_tkn_itm_.Tid_tblw_th: Tblw (bfr, ctx, hctx, src, (Xop_tblw_tkn)tkn, Gfh_tag_.Th_lhs_bgn , Gfh_tag_.Th_rhs, false); break; + 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_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; + case Xop_tkn_itm_.Tid_para: + case Xop_tkn_itm_.Tid_pre: + case Xop_tkn_itm_.Tid_space: + case Xop_tkn_itm_.Tid_escape: + tkn.Html__write(bfr, this, wiki, page, ctx, hctx, cfg, grp, sub_idx, src); break; + default: + Xoh_html_wtr_escaper.Escape(app.Parser_amp_mgr(), bfr, src, tkn.Src_bgn(), tkn.Src_end(), true, false); // NOTE: always escape text including (a) lnki_alt text; and (b) any other text, especially failed xndes; DATE:2013-06-18 + break; + } + --stack_counter; + } + private void Html_ncr(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xop_amp_tkn_num tkn) { + bfr.Add_byte(Byte_ascii.Amp).Add_byte(Byte_ascii.Hash).Add_int_variable(tkn.Val()).Add_byte(Byte_ascii.Semic); // NOTE: do not literalize, else browser may not display multi-char bytes properly; EX:   gets added as   not as {192,160}; DATE:2013-12-09 + } + private void Html_ref(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xop_amp_tkn_ent tkn) { + if (tkn.Itm_is_custom()) // used by ; EX:< -> &xowa_lt; DATE:2014-11-07 + tkn.Print_literal(bfr); + else + tkn.Print_ncr(bfr); + } + private void Hr(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xop_hr_tkn tkn) {bfr.Add(Gfh_tag_.Hr_inl);} + private void Apos(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xop_apos_tkn apos) { + if (hctx.Mode_is_alt()) return; // ignore apos if alt; EX: [[File:A.png|''A'']] should have alt of A; DATE:2013-10-25 + int literal_apos = apos.Apos_lit(); + if (literal_apos > 0) + bfr.Add_byte_repeat(Byte_ascii.Apos, literal_apos); + switch (apos.Apos_cmd()) { + case Xop_apos_tkn_.Cmd_b_bgn: bfr.Add(Gfh_tag_.B_lhs); break; + case Xop_apos_tkn_.Cmd_b_end: bfr.Add(Gfh_tag_.B_rhs); break; + case Xop_apos_tkn_.Cmd_i_bgn: bfr.Add(Gfh_tag_.I_lhs); break; + case Xop_apos_tkn_.Cmd_i_end: bfr.Add(Gfh_tag_.I_rhs); break; + case Xop_apos_tkn_.Cmd_bi_bgn: bfr.Add(Gfh_tag_.B_lhs).Add(Gfh_tag_.I_lhs); break; + case Xop_apos_tkn_.Cmd_ib_end: bfr.Add(Gfh_tag_.I_rhs).Add(Gfh_tag_.B_rhs); break; + case Xop_apos_tkn_.Cmd_ib_bgn: bfr.Add(Gfh_tag_.I_lhs).Add(Gfh_tag_.B_lhs); break; + case Xop_apos_tkn_.Cmd_bi_end: bfr.Add(Gfh_tag_.B_rhs).Add(Gfh_tag_.I_rhs);; break; + case Xop_apos_tkn_.Cmd_bi_end__b_bgn: bfr.Add(Gfh_tag_.B_rhs).Add(Gfh_tag_.I_rhs).Add(Gfh_tag_.B_lhs); break; + case Xop_apos_tkn_.Cmd_ib_end__i_bgn: bfr.Add(Gfh_tag_.I_rhs).Add(Gfh_tag_.B_rhs).Add(Gfh_tag_.I_lhs); break; + case Xop_apos_tkn_.Cmd_b_end__i_bgn: bfr.Add(Gfh_tag_.B_rhs).Add(Gfh_tag_.I_lhs); break; + case Xop_apos_tkn_.Cmd_i_end__b_bgn: bfr.Add(Gfh_tag_.I_rhs).Add(Gfh_tag_.B_lhs); break; + case Xop_apos_tkn_.Cmd_nil: break; + default: throw Err_.new_unhandled(apos.Apos_cmd()); + } + } + private void List(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xop_list_tkn list) { + if (hctx.Mode_is_alt()) { // alt; add literally; EX: "*" for "\n*"; note that \n is added in New_line() + if (list.List_bgn() == Bool_.Y_byte) { // bgn tag + bfr.Add_byte(list.List_itmTyp()); // add literal byte + } + else {} // end tag; ignore + } + else { + byte list_itm_type = list.List_itmTyp(); + if (list.List_bgn() == Bool_.Y_byte) { + if (list.List_sub_first()) List_grp_bgn(bfr, ctx, hctx, src, list_itm_type); + List_itm_bgn(bfr, ctx, hctx, src, list_itm_type); + } + else { + List_itm_end(bfr, ctx, hctx, src, list_itm_type); + if (list.List_sub_last() == Bool_.Y_byte) List_grp_end(bfr, ctx, hctx, src, list_itm_type); + } + } + } + private void List_grp_bgn(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, byte type) { + byte[] tag = null; + switch (type) { + case Xop_list_tkn_.List_itmTyp_ol: tag = Gfh_tag_.Ol_lhs; break; + case Xop_list_tkn_.List_itmTyp_ul: tag = Gfh_tag_.Ul_lhs; break; + case Xop_list_tkn_.List_itmTyp_dd: + case Xop_list_tkn_.List_itmTyp_dt: tag = Gfh_tag_.Dl_lhs; break; + default: throw Err_.new_unhandled(type); + } + if (!page.Html_data().Writing_hdr_for_toc()) { + if (bfr.Len() > 0) bfr.Add_byte_nl(); // NOTE: do not add newLine if start + if (indent_level > 0) bfr.Add_byte_repeat(Byte_ascii.Space, indent_level * 2); + } + bfr.Add(tag); + ++indent_level; + } + private void List_itm_bgn(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, byte type) { + byte[] tag = null; + switch (type) { + case Xop_list_tkn_.List_itmTyp_ol: + case Xop_list_tkn_.List_itmTyp_ul: tag = Gfh_tag_.Li_lhs; break; + case Xop_list_tkn_.List_itmTyp_dt: tag = Gfh_tag_.Dt_lhs; break; + case Xop_list_tkn_.List_itmTyp_dd: tag = Gfh_tag_.Dd_lhs; break; + default: throw Err_.new_unhandled(type); + } + if (!page.Html_data().Writing_hdr_for_toc()) { + bfr.Add_byte_nl(); + if (indent_level > 0) bfr.Add_byte_repeat(Byte_ascii.Space, indent_level * 2); + } + bfr.Add(tag); + ++indent_level; + } + private void List_grp_end(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, byte type) { + --indent_level; + byte[] tag = null; + switch (type) { + case Xop_list_tkn_.List_itmTyp_ol: tag = Gfh_tag_.Ol_rhs; break; + case Xop_list_tkn_.List_itmTyp_ul: tag = Gfh_tag_.Ul_rhs; break; + case Xop_list_tkn_.List_itmTyp_dd: + case Xop_list_tkn_.List_itmTyp_dt: tag = Gfh_tag_.Dl_rhs; break; + default: throw Err_.new_unhandled(type); + } + if (!page.Html_data().Writing_hdr_for_toc()) { + bfr.Add_byte_nl(); + if (indent_level > 0) bfr.Add_byte_repeat(Byte_ascii.Space, indent_level * 2); + } + bfr.Add(tag); + } + + private void List_itm_end(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, byte type) { + --indent_level; + byte[] tag = null; + switch (type) { + case Xop_list_tkn_.List_itmTyp_ol: + case Xop_list_tkn_.List_itmTyp_ul: tag = Gfh_tag_.Li_rhs; break; + case Xop_list_tkn_.List_itmTyp_dt: tag = Gfh_tag_.Dt_rhs; break; + case Xop_list_tkn_.List_itmTyp_dd: tag = Gfh_tag_.Dd_rhs; break; + default: throw Err_.new_unhandled(type); + } + if (!page.Html_data().Writing_hdr_for_toc()) { + bfr.Add_byte_if_not_last(Byte_ascii.Nl); + if (indent_level > 0) bfr.Add_byte_repeat(Byte_ascii.Space, indent_level * 2); + } + bfr.Add(tag); + } + private void New_line(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xop_nl_tkn tkn) { + if (hctx.Mode_is_alt()) + bfr.Add_byte_space(); + else { + if (tkn.Nl_tid() == Xop_nl_tkn.Tid_char) { + bfr.Add_byte_if_not_last(Byte_ascii.Nl); + } + } + } + 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 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()) { + case Xol_kwd_grp_.Id_toc: + if (cfg.Toc__show()) + gplx.xowa.htmls.core.wkrs.tocs.Xoh_toc_wtr.Write_placeholder(page, bfr); + break; + case Xol_kwd_grp_.Id_notoc: case Xol_kwd_grp_.Id_forcetoc: // NOTE: skip output; changes flag on page only + break; + } + } + private void Xnde(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xop_xnde_tkn xnde) { + if (hctx.Mode_is_alt()) { + if (xnde.Tag_close_bgn() > 0) // NOTE: some tags are not closed; WP.EX: France;

    + Xoh_html_wtr_escaper.Escape(app.Parser_amp_mgr(), bfr, src, xnde.Tag_open_end(), xnde.Tag_close_bgn(), true, false); + else + Xnde_subs(bfr, ctx, hctx, src, xnde); + return; + } + Xop_xnde_tag tag = xnde.Tag(); + int tag_id = tag.Id(); + switch (tag_id) { + case Xop_xnde_tag_.Tid__br: + if (xnde.Src_end() - xnde.Src_bgn() < 4 + || xnde.Src_bgn() == -1) + bfr.Add(Gfh_tag_.Br_inl); else bfr.Add_mid(src, xnde.Src_bgn(), xnde.Src_end()); break; + case Xop_xnde_tag_.Tid__hr: bfr.Add(Gfh_tag_.Hr_inl); break; + case Xop_xnde_tag_.Tid__includeonly: // NOTE: do not write tags or content + break; + case Xop_xnde_tag_.Tid__noinclude: // NOTE: do not write tags + case Xop_xnde_tag_.Tid__onlyinclude: + Xnde_subs_escape(bfr, ctx, hctx, src, xnde, false, false); + break; + case Xop_xnde_tag_.Tid__nowiki: + Xnde_subs_escape(bfr, ctx, hctx, src, xnde, false, false); + break; + case Xop_xnde_tag_.Tid__b: case Xop_xnde_tag_.Tid__strong: + case Xop_xnde_tag_.Tid__i: case Xop_xnde_tag_.Tid__em: case Xop_xnde_tag_.Tid__cite: case Xop_xnde_tag_.Tid__dfn: case Xop_xnde_tag_.Tid__var: + case Xop_xnde_tag_.Tid__u: case Xop_xnde_tag_.Tid__ins: case Xop_xnde_tag_.Tid__abbr: + case Xop_xnde_tag_.Tid__strike: case Xop_xnde_tag_.Tid__s: case Xop_xnde_tag_.Tid__del: + case Xop_xnde_tag_.Tid__sub: case Xop_xnde_tag_.Tid__sup: case Xop_xnde_tag_.Tid__big: case Xop_xnde_tag_.Tid__small: + case Xop_xnde_tag_.Tid__code: case Xop_xnde_tag_.Tid__tt: case Xop_xnde_tag_.Tid__kbd: case Xop_xnde_tag_.Tid__samp: case Xop_xnde_tag_.Tid__blockquote: + case Xop_xnde_tag_.Tid__font: case Xop_xnde_tag_.Tid__center: + case Xop_xnde_tag_.Tid__p: case Xop_xnde_tag_.Tid__span: case Xop_xnde_tag_.Tid__div: + case Xop_xnde_tag_.Tid__h1: case Xop_xnde_tag_.Tid__h2: case Xop_xnde_tag_.Tid__h3: case Xop_xnde_tag_.Tid__h4: case Xop_xnde_tag_.Tid__h5: case Xop_xnde_tag_.Tid__h6: + case Xop_xnde_tag_.Tid__dt: case Xop_xnde_tag_.Tid__dd: case Xop_xnde_tag_.Tid__ol: case Xop_xnde_tag_.Tid__ul: case Xop_xnde_tag_.Tid__dl: + case Xop_xnde_tag_.Tid__table: case Xop_xnde_tag_.Tid__tr: case Xop_xnde_tag_.Tid__td: case Xop_xnde_tag_.Tid__th: case Xop_xnde_tag_.Tid__caption: case Xop_xnde_tag_.Tid__tbody: + case Xop_xnde_tag_.Tid__ruby: case Xop_xnde_tag_.Tid__rt: case Xop_xnde_tag_.Tid__rb: case Xop_xnde_tag_.Tid__rp: + case Xop_xnde_tag_.Tid__time: case Xop_xnde_tag_.Tid__bdi: case Xop_xnde_tag_.Tid__data: case Xop_xnde_tag_.Tid__mark: case Xop_xnde_tag_.Tid__wbr: case Xop_xnde_tag_.Tid__bdo: // HTML 5: write literally and let browser handle them + case Xop_xnde_tag_.Tid__q: + Write_xnde(bfr, ctx, hctx, xnde, tag, tag_id, src); + break; + case Xop_xnde_tag_.Tid__pre: { + if (xnde.Tag_open_end() == xnde.Tag_close_bgn()) return; // ignore empty tags, else blank pre line will be printed; DATE:2014-03-12 + byte[] name = tag.Name_bry(); + bfr.Add_byte(Byte_ascii.Angle_bgn).Add(name); + if (xnde.Atrs_bgn() > Xop_tblw_wkr.Atrs_ignore_check) Xnde_atrs(tag_id, hctx, src, xnde.Atrs_bgn(), xnde.Atrs_end(), xnde.Atrs_ary(), bfr); + bfr.Add_byte(Byte_ascii.Angle_end); + Xnde_subs_escape(bfr, ctx, hctx, src, xnde, false, true); + Gfh_tag_.Bld_rhs(bfr, name); + break; + } + case Xop_xnde_tag_.Tid__li: { + byte[] name = tag.Name_bry(); + int bfr_len = bfr.Len(); + if (!page.Html_data().Writing_hdr_for_toc()) { + if (bfr_len > 0 && bfr.Bfr()[bfr_len - 1] != Byte_ascii.Nl) bfr.Add_byte_nl(); // NOTE: always add nl before li else some lists will merge and force long horizontal bar; EX:w:Music + } + if (xnde.Tag_visible()) { + bfr.Add_byte(Byte_ascii.Angle_bgn).Add(name); + if (xnde.Atrs_bgn() > Xop_tblw_wkr.Atrs_ignore_check) Xnde_atrs(tag_id, hctx, src, xnde.Atrs_bgn(), xnde.Atrs_end(), xnde.Atrs_ary(), bfr); + bfr.Add_byte(Byte_ascii.Angle_end); + } + Xnde_subs(bfr, ctx, hctx, src, xnde); + if (xnde.Tag_visible()) + Gfh_tag_.Bld_rhs(bfr, name); // NOTE: inline is never written as ; will be written as ; SEE: NOTE_1 + break; + } + case Xop_xnde_tag_.Tid__timeline: { + bfr.Add(gplx.xowa.htmls.core.wkrs.addons.timelines.Xoh_timeline_data.Hook_bry); + Xox_mgr_base.Xtn_write_escape(app, bfr, src, xnde.Tag_open_end(), xnde.Tag_close_bgn()); // NOTE: do not embed tag inside pre, else timeline will render in black; EX:

    a
    will fail; DATE:2014-05-22 + bfr.Add_str_a7("
    "); + break; + } + case Xop_xnde_tag_.Tid__gallery: + case Xop_xnde_tag_.Tid__poem: + case Xop_xnde_tag_.Tid__hiero: + case Xop_xnde_tag_.Tid__score: + case Xop_xnde_tag_.Tid__ref: + case Xop_xnde_tag_.Tid__references: + case Xop_xnde_tag_.Tid__inputBox: + case Xop_xnde_tag_.Tid__imageMap: + case Xop_xnde_tag_.Tid__pages: + case Xop_xnde_tag_.Tid__pagequality: + case Xop_xnde_tag_.Tid__pagelist: + case Xop_xnde_tag_.Tid__section: + case Xop_xnde_tag_.Tid__translate: + case Xop_xnde_tag_.Tid__dynamicPageList: + case Xop_xnde_tag_.Tid__languages: + case Xop_xnde_tag_.Tid__templateData: + case Xop_xnde_tag_.Tid__source: // DATE:2015-09-29 + case Xop_xnde_tag_.Tid__syntaxHighlight: + case Xop_xnde_tag_.Tid__listing_buy: + case Xop_xnde_tag_.Tid__listing_do: + case Xop_xnde_tag_.Tid__listing_drink: + case Xop_xnde_tag_.Tid__listing_eat: + case Xop_xnde_tag_.Tid__listing_listing: + case Xop_xnde_tag_.Tid__listing_see: + case Xop_xnde_tag_.Tid__listing_sleep: + case Xop_xnde_tag_.Tid__xowa_cmd: + case Xop_xnde_tag_.Tid__rss: + case Xop_xnde_tag_.Tid__quiz: + case Xop_xnde_tag_.Tid__math: + case Xop_xnde_tag_.Tid__indicator: + case Xop_xnde_tag_.Tid__xowa_html: + case Xop_xnde_tag_.Tid__xowa_wiki_setup: + case Xop_xnde_tag_.Tid__graph: + case Xop_xnde_tag_.Tid__random_selection: + case Xop_xnde_tag_.Tid__tabber: + case Xop_xnde_tag_.Tid__tabview: + case Xop_xnde_tag_.Tid__mapframe: + case Xop_xnde_tag_.Tid__maplink: + Xox_xnde xtn = xnde.Xnde_xtn(); + xtn.Xtn_write(bfr, app, ctx, this, hctx, page, xnde, src); + break; + // do not write ; PAGE:fr.s:La_Dispute DATE:2017-05-28 + case Xop_xnde_tag_.Tid__meta: + case Xop_xnde_tag_.Tid__link: + break; + case Xop_xnde_tag_.Tid__xowa_tag_bgn: + case Xop_xnde_tag_.Tid__xowa_tag_end: + break; + default: // unknown tag + if (tag.Restricted()) { // a; img; script; etc.. + if ( !page.Html_data().Html_restricted() // page is not marked restricted (only [[Special:]]) + || page.Wiki().Domain_tid() == Xow_domain_tid_.Tid__home) { // page is in home wiki + bfr.Add_mid(src, xnde.Src_bgn(), xnde.Src_end()); + return; + } + } + bfr.Add_byte(Byte_ascii.Angle_bgn).Add(tag.Name_bry()); // escape bgn + if (xnde.Atrs_bgn() > Xop_tblw_wkr.Atrs_ignore_check) Xnde_atrs(tag_id, hctx, src, xnde.Atrs_bgn(), xnde.Atrs_end(), xnde.Atrs_ary(), bfr); + switch (xnde.CloseMode()) { + case Xop_xnde_tkn.CloseMode_inline: + bfr.Add_byte(Byte_ascii.Slash).Add_byte(Byte_ascii.Angle_end); + break; + default: // NOTE: close tag, even if dangling; EX:
    a ->
    a
    + bfr.Add_byte(Byte_ascii.Angle_end); + Xnde_subs(bfr, ctx, hctx, src, xnde); + bfr.Add_byte(Byte_ascii.Angle_bgn).Add_byte(Byte_ascii.Slash).Add(tag.Name_bry()).Add_byte(Byte_ascii.Angle_end); + break; + } + break; + } + } + private void Write_xnde(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, Xop_xnde_tkn xnde, Xop_xnde_tag tag, int tag_id, byte[] src) { + byte[] name = tag.Name_bry(); + boolean at_bgn = true; + Bry_bfr ws_bfr = wiki.Utl__bfr_mkr().Get_b512(); // create separate ws_bfr to handle "a c d" -> "a c d" + int subs_len = xnde.Subs_len(); + for (int i = 0; i < subs_len; i++) { + Xop_tkn_itm sub = xnde.Subs_get(i); + byte tkn_tid = sub.Tkn_tid(); + switch (tkn_tid) { + case Xop_tkn_itm_.Tid_space: // space; add to ws_bfr; + ws_bfr.Add_mid(src, sub.Src_bgn(), sub.Src_end()); + break; + default: + if (tkn_tid == Xop_tkn_itm_.Tid_html_ncr) { // html_entity needed for fr.wikipedia.org and many spans with ; DATE:2013-06-18 + Xop_amp_tkn_num ncr_tkn = (Xop_amp_tkn_num)sub; + if (ncr_tkn.Val() == Byte_ascii.Space + || ncr_tkn.Val() == 160 + ) { + + ws_bfr.Add_mid(src, ncr_tkn.Src_bgn(), ncr_tkn.Src_end()); + continue; // just add entity; don't process rest; + } + } + if (ws_bfr.Len() > 0) bfr.Add_bfr_and_clear(ws_bfr); // dump ws_bfr to real bfr + if (at_bgn) { // 1st non-ws tkn; add open tag; + at_bgn = false; + bfr.Add_byte(Byte_ascii.Angle_bgn).Add(name); + if (xnde.Atrs_bgn() > Xop_tblw_wkr.Atrs_ignore_check) Xnde_atrs(tag_id, hctx, src, xnde.Atrs_bgn(), xnde.Atrs_end(), xnde.Atrs_ary(), bfr); + bfr.Add_byte(Byte_ascii.Angle_end); + } + Write_tkn(bfr, ctx, hctx, src, xnde, i, sub); // NOTE: never escape;

    , ,
    etc may have nested nodes + break; + } + } + if (at_bgn) { // occurs when xnde is empty; EX: + bfr.Add_byte(Byte_ascii.Angle_bgn).Add(name); + if (xnde.Atrs_bgn() > Xop_tblw_wkr.Atrs_ignore_check) Xnde_atrs(tag_id, hctx, src, xnde.Atrs_bgn(), xnde.Atrs_end(), xnde.Atrs_ary(), bfr); + bfr.Add_byte(Byte_ascii.Angle_end); + } + Gfh_tag_.Bld_rhs(bfr, name); // NOTE: inline is never written as ; will be written as ; SEE: NOTE_1 + if (ws_bfr.Len() > 0) bfr.Add_bfr_and_clear(ws_bfr); // dump any leftover ws to bfr; handles "c " -> "c " + ws_bfr.Mkr_rls(); + } + private void Xnde_atrs(int tag_id, Xoh_wtr_ctx hctx, byte[] src, int bgn, int end, Mwh_atr_itm[] ary, Bry_bfr bfr) { + if (ary == null) return; // NOTE: some nodes will have null xatrs b/c of whitelist; EX:
    a
    ; style is not on whitelist so not xatr generated, but xatr_bgn will != -1 + int ary_len = ary.length; + 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; + Xnde_atr_write(bfr, app, hctx, src, atr); + } + } + private static void Xnde_atr_write(Bry_bfr bfr, Xoae_app app, Xoh_wtr_ctx hctx, byte[] src, Mwh_atr_itm atr) { + byte[] atr_key = atr.Key_bry(); + if ( hctx.Mode_is_display_title() + && Xoh_display_ttl_wtr.Is_style_restricted(bfr, hctx, src, atr, atr_key)) + return; + + bfr.Add_byte(Byte_ascii.Space); // add space before every attribute + if (atr_key != null) { + bfr.Add(atr_key); + bfr.Add_byte(Byte_ascii.Eq); + } + byte quote_byte = atr.Qte_byte(); if (quote_byte == Byte_ascii.Null) quote_byte = Byte_ascii.Quote; + bfr.Add_byte(quote_byte); + if (atr.Key_tid() == Mwh_atr_itm_.Key_tid__id) { // ids should not have spaces; DATE:2013-04-01 + if (atr.Val_bry() == null) + Xnde_atr_write_id(bfr, app, atr.Src(), atr.Val_bgn(), atr.Val_end()); + else { + byte[] atr_val = atr.Val_bry(); + Xnde_atr_write_id(bfr, app, atr_val, 0, atr_val.length); + } + } + else { + if (atr.Val_bry() == null) + bfr.Add_mid(src, atr.Val_bgn(), atr.Val_end()); + else + bfr.Add(atr.Val_bry()); + } + bfr.Add_byte(quote_byte); + } + private static void Xnde_atr_write_id(Bry_bfr bfr, Xoae_app app, byte[] bry, int bgn, int end) {gplx.langs.htmls.encoders.Gfo_url_encoder_.Id.Encode(bfr, bry, bgn, end);} + private void Xnde_subs(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xop_xnde_tkn xnde) { + int subs_len = xnde.Subs_len(); + for (int i = 0; i < subs_len; i++) + Write_tkn(bfr, ctx, hctx, src, xnde, i, xnde.Subs_get(i)); + } + private void Xnde_subs_escape(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xop_xnde_tkn xnde, boolean amp_enable, boolean nowiki) { + int xndesubs_len = xnde.Subs_len(); + for (int i = 0; i < xndesubs_len; i++) { + Xop_tkn_itm sub = xnde.Subs_get(i); + switch (sub.Tkn_tid()) { + case Xop_tkn_itm_.Tid_xnde: + Xop_xnde_tkn sub_xnde = (Xop_xnde_tkn)sub; + switch (sub_xnde.Tag().Id()) { + case Xop_xnde_tag_.Tid__noinclude: + case Xop_xnde_tag_.Tid__onlyinclude: + case Xop_xnde_tag_.Tid__includeonly: + break; + default: + byte[] tag_name = sub_xnde.Tag().Name_bry(); + bfr.Add(Gfh_entity_.Lt_bry).Add(tag_name); + if (xnde.Atrs_bgn() > Xop_tblw_wkr.Atrs_ignore_check) Xnde_atrs(sub_xnde.Tag().Id(), hctx, src, sub_xnde.Atrs_bgn(), sub_xnde.Atrs_end(), xnde.Atrs_ary(), bfr); + bfr.Add(Gfh_entity_.Gt_bry); + break; + } + Xnde_subs_escape(bfr, ctx, hctx, src, sub_xnde, amp_enable, false); + break; + case Xop_tkn_itm_.Tid_txt: + if (amp_enable) + bfr.Add_mid(src, sub.Src_bgn(), sub.Src_end()); + else + Xoh_html_wtr_escaper.Escape(app.Parser_amp_mgr(), bfr, src, sub.Src_bgn(), sub.Src_end(), true, nowiki); + break; + default: + Write_tkn(bfr, ctx, hctx, src, xnde, i, sub); + break; + } + } + } + private void Tblw(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xop_tblw_tkn tkn, byte[] bgn, byte[] end, boolean tblw_bgn) { + if (hctx.Mode_is_alt()) // add \s for each \n + bfr.Add_byte_space(); + else { + if (!page.Html_data().Writing_hdr_for_toc()) { + bfr.Add_byte_if_not_last(Byte_ascii.Nl); + if (indent_level > 0) bfr.Add_byte_repeat(Byte_ascii.Space, indent_level * 2); + } + bfr.Add(bgn); + int atrs_bgn = tkn.Atrs_bgn(); + if (atrs_bgn != -1) Xnde_atrs(tkn.Tblw_tid(), hctx, src, atrs_bgn, tkn.Atrs_end(), tkn.Atrs_ary(), bfr); //bfr.Add_byte(Byte_ascii.Space).Add_mid(src, atrs_bgn, tkn.Atrs_end()); + bfr.Add_byte(Byte_ascii.Angle_end); + ++indent_level; + } + int subs_len = tkn.Subs_len(); + for (int i = 0; i < subs_len; i++) + Write_tkn(bfr, ctx, hctx, src, tkn, i, tkn.Subs_get(i)); + if (hctx.Mode_is_alt()) { + if (tblw_bgn) // only add \s for closing table; |} -> "\s" + bfr.Add_byte_space(); + } + else { + --indent_level; + if (!page.Html_data().Writing_hdr_for_toc()) { + bfr.Add_byte_if_not_last(Byte_ascii.Nl); + if (indent_level > 0) bfr.Add_byte_repeat(Byte_ascii.Space, indent_level * 2); + } + bfr.Add(end); + if (!page.Html_data().Writing_hdr_for_toc()) { + bfr.Add_byte_if_not_last(Byte_ascii.Nl); + } + } + } + public static final int Sub_idx_null = -1; // DELETE: placeholder for sub_idx; WHEN: need to remove Sub_grp +} +/* +NOTE_1:inline always written as , not +see WP:Permian–Triassic extinction event +this will cause firefox to swallow up rest of text +
    +this will not +
    +*/ \ No newline at end of file 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 a27517de8..02c7ddf97 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 @@ -13,3 +13,10 @@ 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.htmls.core.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; +public class Xoh_html_wtr_ { + public static void Para__assert_tag_starts_on_nl(Bry_bfr bfr, int src_bgn) { + if (!bfr.Match_end_byt_nl_or_bos()) bfr.Add_byte_nl(); + if (src_bgn != 0) bfr.Add_byte_nl(); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_html_wtr_cfg.java b/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_html_wtr_cfg.java index a27517de8..813fa0823 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_html_wtr_cfg.java +++ b/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_html_wtr_cfg.java @@ -13,3 +13,10 @@ 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.htmls.core.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; +public class Xoh_html_wtr_cfg { + public boolean Toc__show() {return toc__show;} public Xoh_html_wtr_cfg Toc__show_(boolean v) {toc__show = v; return this;} private boolean toc__show; + public boolean Lnki__id() {return lnki__id;} public Xoh_html_wtr_cfg Lnki__id_(boolean v) {lnki__id = v; return this;} private boolean lnki__id; + public boolean Lnki__title() {return lnki__title;} public Xoh_html_wtr_cfg Lnki__title_(boolean v) {lnki__title = v; return this;} private boolean lnki__title; + public boolean Lnki__visited() {return lnki__visited;} public Xoh_html_wtr_cfg Lnki__visited_y_() {lnki__visited = true; return this;} private boolean lnki__visited; +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_html_wtr_escaper.java b/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_html_wtr_escaper.java index a27517de8..601364a9f 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_html_wtr_escaper.java +++ b/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_html_wtr_escaper.java @@ -13,3 +13,116 @@ 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.htmls.core.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; +import gplx.core.btries.*; +import gplx.langs.htmls.*; import gplx.langs.htmls.entitys.*; +import gplx.xowa.parsers.amps.*; import gplx.xowa.parsers.xndes.*; +public class Xoh_html_wtr_escaper { + public static String Escape_str(Xop_amp_mgr amp_mgr, Bry_bfr tmp_bfr, String src) { + return String_.new_u8(Escape(amp_mgr, tmp_bfr, Bry_.new_u8(src))); + } + public static byte[] Escape(Xop_amp_mgr amp_mgr, Bry_bfr tmp_bfr, byte[] src) { + Escape(amp_mgr, tmp_bfr, src, 0, src.length, true, false); + return tmp_bfr.To_bry_and_clear(); + } + public static void Escape(Xop_amp_mgr amp_mgr, Bry_bfr bfr, byte[] src, int bgn, int end, boolean interpret_amp, boolean nowiki_skip) { + Btrie_slim_mgr amp_trie = amp_mgr.Amp_trie(); + Btrie_rv trv = new Btrie_rv(); + for (int i = bgn; i < end; i++) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Lt: + if (nowiki_skip) { + byte[] nowiki_name = Xop_xnde_tag_.Tag__nowiki.Name_bry(); + int nowiki_name_len = nowiki_name.length; + if (Bry_.Eq(src, i + 1, i + 1 + nowiki_name_len, nowiki_name)) { // > found + break; + case Byte_ascii.Lt: + if ( tag_is_bgn // end) // not enough chars for "/nowiki>" + || src[i + 1] != Byte_ascii.Slash // / + || !Bry_.Eq(src, i + 2, i + 2 + nowiki_name_len, nowiki_name) // nowiki + || src[i + 2 + nowiki_name_len] != Byte_ascii.Gt // > + ) return Bry_find_.Not_found; + end_lt = i; + end_gt = i + 2 + nowiki_name_len; + i = end; + break; + } + } + if (end_gt == -1) return Bry_find_.Not_found; // ">" of not found + bfr.Add_mid(src, bgn_gt + 1, end_lt); + return end_gt; + } + catch (Exception e) { + Xoa_app_.Usr_dlg().Warn_many("", "", "unknown error in escape.nowiki: ~{0} ~{1}", String_.new_u8(src, bgn, end), Err_.Message_gplx_full(e)); + return Bry_find_.Not_found; + } + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_html_wtr_tst.java b/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_html_wtr_tst.java index a27517de8..d23fe3972 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_html_wtr_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_html_wtr_tst.java @@ -13,3 +13,336 @@ 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.htmls.core.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; +import org.junit.*; +public class Xoh_html_wtr_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @After public void term() {fxt.Init_para_n_(); fxt.Reset();} + @Test public void Hr_basic() {fxt.Test_parse_page_wiki_str("----" , "
    ");} + @Test public void Hr_extended() {fxt.Test_parse_page_wiki_str("--------" , "
    ");} + @Test public void Lnki_basic() {fxt.Test_parse_page_wiki_str("[[a]]" , "a");} + @Test public void Lnki_caption() {fxt.Test_parse_page_wiki_str("[[a|b]]" , "b");} + @Test public void Lnki_caption_fmt() {fxt.Test_parse_page_wiki_str("[[a|''b'']]" , "b");} + @Test public void Lnki_tail_trg() {fxt.Test_parse_page_wiki_str("[[a]]b" , "ab");} + @Test public void Lnki_tail_caption() {fxt.Test_parse_page_wiki_str("[[a|b]]c" , "bc");} + @Test public void Lnki_title() { + fxt.Wtr_cfg().Lnki__title_(true); + fxt.Test_parse_page_wiki_str("[[a|b]]", "b"); + fxt.Wtr_cfg().Lnki__title_(false); + } + @Test public void Lnki_title_page_text() { + fxt.Wtr_cfg().Lnki__title_(true); + fxt.Test_parse_page_wiki_str("[[a_b]]", "a_b"); + fxt.Wtr_cfg().Lnki__title_(false); + } + @Test public void Lnki_category() {fxt.Test_parse_page_wiki_str("[[Category:A]]" , "");} // NOTE: Category does not get written in main page bfr + @Test public void Lnki_category_force() {fxt.Test_parse_page_wiki_str("[[:Category:A]]" , "Category:A");} + @Test public void Lnki_matches_page() {fxt.Test_parse_page_wiki_str("[[test page|t1]]", "t1");} // NOTE: "Test page" is hardcoded to be the test page name + @Test public void Lnki_matches_page_but_has_anchor() {fxt.Test_parse_page_wiki_str("[[Test page#a|test 1]]", "test 1");} // NOTE: "Test page" is hardcoded to be the test page name + @Test public void Lnki_anchor() {fxt.Test_parse_page_wiki_str("[[A#b]]" , "A#b");} +// @Test public void Img_invalid_wnt_char() { +// fxt.Test_parse_page_wiki_str +// ( "[[File:A*b.png]]" +// , "
    \"\"
    " +// ); +// } +// @Test public void Img_alt() { // FUTURE: enable; WHEN: after fixing xnde to handle bad xnde; EX: France +// fxt.Test_parse_page_wiki_str("[[File:A.png|none|9x8px|alt=ab\"c\"d]]", Xop_fxt.html_img_none("File:A.png", "ab"c"d")); +// } + @Test public void Url_encode() {fxt.Test_parse_page_wiki_str("[[a;@$!*(),/ _^b|z]]" , "z");} // NOTE: was "a" instead of "A"; "__" instead of "_" DATE:2014-09-07 + @Test public void Url_encode_space() {fxt.Test_parse_page_wiki_str("[[a _b|z]]" , "z");} + @Test public void Apos_i() {fxt.Test_parse_page_wiki_str("''a''" , "a");} + @Test public void Apos_b() {fxt.Test_parse_page_wiki_str("'''a'''" , "a");} + @Test public void Apos_ib() {fxt.Test_parse_page_wiki_str("'''''a'''''" , "a");} + @Test public void Html_ent() {fxt.Test_parse_page_wiki_str("!" , "!");} // PURPOSE:ncrs should be literal, not decoded (!); DATE:2014-11-06 + @Test public void Html_ref() {fxt.Test_parse_page_wiki_str(">" , ">");} + @Test public void List_1_itm() { + fxt.Test_parse_page_wiki_str("*a", String_.Concat_lines_nl_skip_last + ( "
      " + , "
    • a" + , "
    • " + , "
    " + )); + } + @Test public void List_2_itms() { + fxt.Test_parse_page_wiki_str("*a\n*b", String_.Concat_lines_nl_skip_last + ( "
      " + , "
    • a" + , "
    • " + , "
    • b" + , "
    • " + , "
    " + )); + } + @Test public void List_nest_ul() { + fxt.Test_parse_page_wiki_str("*a\n**b", String_.Concat_lines_nl_skip_last + ( "
      " + , "
    • a" + , "
        " + , "
      • b" + , "
      • " + , "
      " + , "
    • " + , "
    " + )); + } + @Test public void List_dt_dd() { + fxt.Test_parse_page_wiki_str(";a:b", String_.Concat_lines_nl_skip_last + ( "
    " + , "
    a" + , "
    " + , "
    b" + , "
    " + , "
    " + )); + } + @Test public void List_dd_nest2() { + fxt.Test_parse_page_wiki_str("::a", String_.Concat_lines_nl_skip_last + ( "
    " + , "
    " + , "
    " + , "
    a" + , "
    " + , "
    " + , "
    " + , "
    " + )); + } + @Test public void Tblw_basic() { + fxt.Test_parse_page_wiki_str("{|\n|+a\n!b||c\n|-\n|d||e\n|}", String_.Concat_lines_nl + ( "
    " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , "
    a" + , "
    b" + , " c" + , "
    d" + , " e" + , "
    " + )); + } + @Test public void Tblw_atrs() { + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|style='z'" + , "|+a" + , "!style='y'|b||style='x'|c" + , "|-style='w'" + , "|style='v'|d||style='u'|e" + , "|}" + ), String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , "
    a" + , "
    b" + , " c" + , "
    d" + , " e" + , "
    " + )); + } + @Test public void Para_hdr_list() { + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "==a==" + , "" + , "*b" + , "*c" + ), String_.Concat_lines_nl_skip_last + ( "

    a

    " + , "" + , "
      " + , "
    • b" + , "
    • " + , "
    • c" + , "
    • " + , "
    " + )); + fxt.Init_para_n_(); + } + @Test public void Para_nl_is_space() { + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "a" + , "b" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "b" + , "

    " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Para_nl_2_2() { + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "a" + , "" + , "b" + , "" + , "c" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "

    b" + , "

    " + , "" + , "

    c" + , "

    " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Div_2() { // WP:[[Air]]#Density of air + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "
    a
    " + , "" + , "
    b
    " + ), String_.Concat_lines_nl_skip_last + ( "
    a
    " + , "
    b
    " + )); + fxt.Init_para_n_(); + } + @Test public void Tblw() { + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl + ( "{|" + , "|-" + , "|a" + , "|b" + , "|-" + , "|c" + , "|d" + , "|}" + ) + , String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , "
    a" + , " b" + , "
    c" + , " d" + , "
    " + , "" + )); + } + @Test public void Tblw_lnki_bang() { + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl + ( "{|" + , "|-" + , "|[[a|!]]" + , "|}" + ) + , String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
    !" + , "
    " + , "" + )); + } + @Test public void Tr_inside_tblw_td() { // WP:[[Earth]] + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl + ( "{|" + , "|-" + , "a" + , "|}" + ) + , String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + )); + } + @Test public void Tblw_tr_with_newlines() {// WP:[[John Adams]] Infobox Officeholder + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl + ( "{|" + , "|-" + , "" + , "" + , "" + , "|a" + , "|}" + ) + , String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + )); + } + @Test public void Bang_doesnt_force_tbl() { + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str("a! b! c", "

    a! b! c\n

    \n"); + fxt.Init_para_n_(); + } + @Test public void Err_nlOnly() { + fxt.Test_parse_page_wiki_str("{{\n}}", "{{\n}}"); // NOTE: was {{}} + } + @Test public void Xnde_inline() { + fxt.Test_parse_page_wiki_str("
    ", "
    "); + } + @Test public void Xnde_id_encode() { // PURPOSE: id should be url-encoded; DATE: 2013-11-13; + fxt.Test_parse_page_wiki_str("
    ", "
    "); + fxt.Test_parse_page_wiki_str("
    ", "
    "); + } + @Test public void Timeline() {// PURPOSE: embed timeline contents in pre; DATE:2014-05-22 + fxt.Test_parse_page_wiki_str("a", "
    a
    "); + } + @Test public void Amp_ncr_should_not_be_rendered_as_bytes() { // PURPOSE:   should be rendered as   not as literal bytes {192,160}; DATE:2013-12-09 + fxt.Test_parse_page_wiki_str("a b", "a b"); + } + + // @Test public void Fix_PositionAbsolute_stripped() { +// fxt.Test_parse_page_wiki_str("", ""); +// } +// @Test public void Xnde_nl() { +// fxt.Test_parse_page_wiki_str("
    c
    ", String_.Concat_lines_nl_skip_last +// ( "
    c
    " +// )); +// } +// @Test public void Tblw() { +// fxt.Test_parse_page_wiki_str("{|\n|}", String_.Concat_lines_nl +// ( "" +// , " " +// , " " +// , " " +// , " " +// , "
    a" +// , " b" +// , "
    " +// )); +// } +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_wtr_ctx.java b/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_wtr_ctx.java index a27517de8..f3be34b41 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_wtr_ctx.java +++ b/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_wtr_ctx.java @@ -13,3 +13,29 @@ 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.htmls.core.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; +public class Xoh_wtr_ctx { + Xoh_wtr_ctx(int mode, byte[] anch__href__bgn, byte[] anch__href__end) { + this.mode = mode; this.anch__href__bgn = anch__href__bgn; this.anch__href__end = anch__href__end; + } + public int Mode() {return mode;} private final int mode; + public boolean Mode_is_alt() {return mode == Mode_alt;} + public boolean Mode_is_display_title() {return mode == Mode_display_title;} + public boolean Mode_is_popup() {return mode == Mode_popup;} + public boolean Mode_is_hdump() {return mode == Mode_hdump;} + public byte[] Anch__href__bgn() {return anch__href__bgn;} private final byte[] anch__href__bgn; + public byte[] Anch__href__end() {return anch__href__end;} private final byte[] anch__href__end; + + public static final int Mode_basic = 0, Mode_alt = 1, Mode_display_title = 2, Mode_popup = 3, Mode_hdump = 4, Mode_file_dump = 5; + + public static final Xoh_wtr_ctx + Basic = new Xoh_wtr_ctx(Mode_basic , gplx.xowa.htmls.hrefs.Xoh_href_.Bry__wiki, null) + , Alt = new Xoh_wtr_ctx(Mode_alt , gplx.xowa.htmls.hrefs.Xoh_href_.Bry__wiki, null) + , Display_title = new Xoh_wtr_ctx(Mode_display_title , gplx.xowa.htmls.hrefs.Xoh_href_.Bry__wiki, null) + , Popup = new Xoh_wtr_ctx(Mode_popup , gplx.xowa.htmls.hrefs.Xoh_href_.Bry__wiki, null) + , Hdump = new Xoh_wtr_ctx(Mode_hdump , gplx.xowa.htmls.hrefs.Xoh_href_.Bry__wiki, null) + ; + public static Xoh_wtr_ctx File_dump(byte[] anch__href__bgn, byte[] anch__href__end) { + return new Xoh_wtr_ctx(Mode_file_dump, anch__href__bgn, anch__href__end); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/htmls/tidy/Xoh_tidy_mgr_tst.java b/400_xowa/src/gplx/xowa/htmls/core/htmls/tidy/Xoh_tidy_mgr_tst.java index a27517de8..dd19390c5 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/htmls/tidy/Xoh_tidy_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/core/htmls/tidy/Xoh_tidy_mgr_tst.java @@ -13,3 +13,78 @@ 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.htmls.core.htmls.tidy; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; import gplx.xowa.htmls.core.htmls.*; +import org.junit.*; +public class Xoh_tidy_mgr_tst { + @Before public void init() {fxt.Clear();} private Xoh_tidy_mgr_fxt fxt = new Xoh_tidy_mgr_fxt(); + @Test public void Wrap() { + fxt.Test_wrap("a" + , "" + + "" + + "" + + "test" + + "" + + "a" + + "" + + "" + ); + } + @Test public void Unwrap_pass() { + fxt.Test_unwrap + ( "" + + "" + + "" + + "test" + + "" + + "a" + + "" + + "" + , Bool_.Y, "a" + ); + } + @Test public void Unwrap_fail_bgn() { + fxt.Test_unwrap + ( "" + + "" + + "" + + "test" + + "" + + "a" + + "" + + "" + , Bool_.N, "" + ); + } + @Test public void Unwrap_fail_end() { + fxt.Test_unwrap + ( "" + + "" + + "" + + "test" + + "" + + "a" + + "" + + "" + , Bool_.N, "" + ); + } +} +class Xoh_tidy_mgr_fxt { + private Bry_bfr bfr = Bry_bfr_.Reset(255); + public void Clear() { + bfr.Clear(); + } + public void Test_wrap(String val, String expd) { + bfr.Add_str_u8(val); + Xow_tidy_mgr.Tidy_wrap(bfr); + Tfds.Eq(expd, bfr.To_str_and_clear()); + } + public void Test_unwrap(String val, boolean expd_pass, String expd) { + bfr.Add_str_u8(val); + boolean actl_pass = Xow_tidy_mgr.Tidy_unwrap(bfr); + if (actl_pass != expd_pass) Tfds.Fail("expd={0} actl={1}", expd_pass, actl_pass); + else if (expd_pass) { + Tfds.Eq(expd, bfr.To_str_and_clear()); + } + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/htmls/tidy/Xoh_tidy_wkr.java b/400_xowa/src/gplx/xowa/htmls/core/htmls/tidy/Xoh_tidy_wkr.java index a27517de8..62dcc1c1a 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/htmls/tidy/Xoh_tidy_wkr.java +++ b/400_xowa/src/gplx/xowa/htmls/core/htmls/tidy/Xoh_tidy_wkr.java @@ -13,3 +13,10 @@ 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.htmls.core.htmls.tidy; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; import gplx.xowa.htmls.core.htmls.*; +public interface Xoh_tidy_wkr { + byte Tid(); + void Indent_(boolean v); + void Init_by_app(Xoae_app app); + void Exec_tidy(Bry_bfr bfr, byte[] page_url); +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/htmls/tidy/Xoh_tidy_wkr_.java b/400_xowa/src/gplx/xowa/htmls/core/htmls/tidy/Xoh_tidy_wkr_.java index a27517de8..6bf847082 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/htmls/tidy/Xoh_tidy_wkr_.java +++ b/400_xowa/src/gplx/xowa/htmls/core/htmls/tidy/Xoh_tidy_wkr_.java @@ -13,3 +13,14 @@ 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.htmls.core.htmls.tidy; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; import gplx.xowa.htmls.core.htmls.*; +public class Xoh_tidy_wkr_ { + public static final byte Tid_null = 0, Tid_tidy = 1, Tid_jtidy = 2; + public static final Xoh_tidy_wkr Wkr_null = new Xoh_tidy_wkr_null(); +} +class Xoh_tidy_wkr_null implements Xoh_tidy_wkr { + public byte Tid() {return Xoh_tidy_wkr_.Tid_null;} + public void Indent_(boolean v) {} + public void Init_by_app(Xoae_app app) {} + public void Exec_tidy(Bry_bfr bfr, byte[] page_url) {} +} diff --git a/400_xowa/src/gplx/xowa/htmls/core/htmls/tidy/Xoh_tidy_wkr_jtidy.java b/400_xowa/src/gplx/xowa/htmls/core/htmls/tidy/Xoh_tidy_wkr_jtidy.java index a27517de8..7988b8d19 100644 --- a/400_xowa/src/gplx/xowa/htmls/core/htmls/tidy/Xoh_tidy_wkr_jtidy.java +++ b/400_xowa/src/gplx/xowa/htmls/core/htmls/tidy/Xoh_tidy_wkr_jtidy.java @@ -13,3 +13,70 @@ 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.htmls.core.htmls.tidy; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.*; import gplx.xowa.htmls.core.htmls.*; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +import org.w3c.tidy.Configuration; +import org.w3c.tidy.Tidy; +import gplx.core.envs.*; +import gplx.core.envs.*; +class Xoh_tidy_wkr_jtidy implements Xoh_tidy_wkr { + private Tidy tidy; + private ByteArrayOutputStream wtr; + public void tidy_init() { + long bgn = System_.Ticks(); + wtr = new ByteArrayOutputStream(); + System.setProperty("line.separator", "\n"); + tidy = new Tidy(); // obtain a new Tidy instance + tidy.setInputEncoding("utf-8"); // -utf8 + tidy.setOutputEncoding("utf-8"); // -utf8 + tidy.setDocType("\"\""); // --doctype \"\"; set to empty else some wikis will show paragraph text with little vertical gap; PAGE:tr.b: + tidy.setForceOutput(true); // --force-output y + tidy.setQuiet(true); // --quiet y + tidy.setTidyMark(false); // --tidy-mark n + tidy.setWraplen(0); // --wrap 0 + tidy.setIndentContent(true); // --indent y; NOTE: true indents all content in edit box + tidy.setQuoteNbsp(true); // --quote-nbsp y + tidy.setLiteralAttribs(true); // --literal-attributes y + tidy.setWrapAttVals(false); // --wrap-attributes n + tidy.setFixUri(false); // --fix-url n + tidy.setFixBackslash(false); // --fix-backslash n + tidy.setEncloseBlockText(true); // --enclose-block-text y; NOTE: true creates extra

    ; very noticeable in sidebar + tidy.setNumEntities(false); // NOTE: true will convert all UTF-8 chars to &#val; which ruins readability + tidy.setTrimEmptyElements(true); // NOTE: tidy always trims (not even an option) + tidy.setShowWarnings(false); // NOTE: otherwise warnings printed to output window + tidy.setShowErrors(0); // NOTE: otherwise errors printed to output window; EX: Error:

    +// , " --show-body-only y" // prevent tidy from surrounding input with // removed; strips " + )); + } + @Test public void Toc() { + fxt.Init_msg(Xoh_head_itm__toc.Key_showtoc, "Sh\"ow"); + fxt.Init_msg(Xoh_head_itm__toc.Key_hidetoc, "Hi'de"); + fxt.Mgr().Itm__toc().Enabled_y_(); + fxt.Test_write(String_.Concat_lines_nl_skip_last + ( "" + , " " + )); + } + @Test public void Globals() { + fxt.Init_msg(Xol_msg_itm_.Id_dte_month_name_january, "Jan'uary" ); // add apos to simulate apostrophes in Hebrew months; DATE:2014-07-28 + fxt.Mgr().Itm__globals().Enabled_y_(); + fxt.Test_write(String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , " " + , " " + )); + fxt.Init_msg(Xol_msg_itm_.Id_dte_month_name_january, "January" ); // set it back + } + @Test public void Globals_la() { // PURPOSE: la.gfs only specifies "," not "."; make sure both "." and "," show up, or else null ref error during import; DATE:2014-05-13 + Xol_lang_itm la_lang = fxt.Wiki().Lang(); + gplx.xowa.langs.numbers.Xol_transform_mgr separators_mgr = la_lang.Num_mgr().Separators_mgr(); + separators_mgr.Clear(); + separators_mgr.Set(gplx.xowa.langs.numbers.Xol_num_mgr.Separators_key__grp, Bry_.new_a7(" ")); + fxt.Mgr().Itm__globals().Enabled_y_(); + fxt.Test_write(String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , " " + , " " + )); + } +} +class Xoh_head_mgr_fxt { + private final Xop_fxt fxt = new Xop_fxt(); + private Xoh_head_mgr mgr; + private Bry_bfr bfr = Bry_bfr_.Reset(255); + public Xowe_wiki Wiki() {return wiki;} private Xowe_wiki wiki; + public Xoh_head_mgr Mgr() {return mgr;} + public Xol_lang_itm Make_lang(String key) {return wiki.Appe().Lang_mgr().Get_by_or_new(Bry_.new_a7(key));} + public void Clear() { + fxt.Reset(); + mgr = fxt.Page().Html_data().Head_mgr(); + wiki = fxt.Wiki(); + } + public void Init_msg(byte[] key, String val) { + wiki.Msg_mgr().Get_or_make(key).Atrs_set(Bry_.new_a7(val), false, false); + } + public void Init_msg(int id, String val) { + Xol_msg_itm msg_itm = wiki.Lang().Msg_mgr().Itm_by_id_or_null(id); + msg_itm.Atrs_set(Bry_.new_a7(val), false, false); + } + public void Test_write(String expd) { + mgr.Write(bfr, fxt.App(), wiki, fxt.Page()); + Tfds.Eq_str_lines(expd, bfr.To_str_and_clear()); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/heads/Xoh_head_wtr.java b/400_xowa/src/gplx/xowa/htmls/heads/Xoh_head_wtr.java index a27517de8..2ff6a831a 100644 --- a/400_xowa/src/gplx/xowa/htmls/heads/Xoh_head_wtr.java +++ b/400_xowa/src/gplx/xowa/htmls/heads/Xoh_head_wtr.java @@ -13,3 +13,220 @@ 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.htmls.heads; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.langs.htmls.*; +public class Xoh_head_wtr { + private int indent; private int reset_bgn, reset_end; + private boolean js_tail_inited = false; + public Bry_bfr Bfr() {return bfr;} private Bry_bfr bfr; + public Xoh_head_wtr Init(Bry_bfr bfr) {this.bfr = bfr; return this;} + public void Term() { + this.bfr = null; + js_tail_inited = true; + } + public void Write_css_include(Io_url url) {Write_css_include(url.To_http_file_bry());} + public void Write_css_include(byte[] url) { + Write_nl_and_indent(); + bfr.Add(Css_include_bgn).Add(url).Add(Css_include_end); + } + public void Write_js_include(Io_url url) {Write_js_include(url.To_http_file_bry());} + public void Write_js_include(byte[] url) { + Write_nl_and_indent(); + bfr.Add(Js_include_bgn).Add(url).Add(Js_include_end); + } + public void Write_css_style_bgn() { + reset_bgn = bfr.Len(); + Write_nl_and_indent(); + bfr.Add(Gfh_tag_.Style_lhs_w_type); + Indent_add(); + reset_end = bfr.Len(); + } + public void Write_css_style_end() { + Indent_del(); + if (Reset()) return; + Write_nl_and_indent(); + bfr.Add(Gfh_tag_.Style_rhs); + } + public void Write_css_style_ary(byte[][] ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) + Write_css_style_itm(ary[i]); + } + public void Write_css_style_itm(byte[] bry) { + Write_nl_and_indent(); + bfr.Add(bry); + } + public void Write_js_lines(byte[][] ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) + Write_js_line(ary[i]); + } + public void Write_js_line(String str) {Write_js_line(Bry_.new_u8(str));} + public void Write_js_line(byte[] bry) { + Write_nl_and_indent(); + bfr.Add(bry); + } + public void Write_js_line_ary(byte[]... ary) { + Write_nl_and_indent(); + for (byte[] bry : ary) + bfr.Add(bry); + } + public void Write_js_script_bgn() { + reset_bgn = bfr.Len(); + Write_nl_and_indent(); + bfr.Add(Gfh_tag_.Script_lhs_w_type); + Indent_add(); + reset_end = bfr.Len(); + } + public void Write_js_script_end() { + Indent_del(); + if (Reset()) return; + Write_nl_and_indent(); + bfr.Add(Gfh_tag_.Script_rhs); + } + public void Write_js_head_global_bgn() { + reset_bgn = bfr.Len(); + Write_nl_and_indent(); + bfr.Add(Js_globals_ini_var_bgn); + Indent_add(); + reset_end = bfr.Len(); + } + public void Write_js_head_global_end() { + Indent_del(); + if (Reset()) return; + Write_nl_and_indent(); + bfr.Add(Js_globals_ini_var_end); + } + private void Write_js_tail_init() { + if (js_tail_inited) return; + js_tail_inited = true; + Write_js_line(Js_line_1); + } + public void Write_js_tail_load_lib(Io_url url) {Write_js_tail_load_lib(url.To_http_file_bry());} + public void Write_js_tail_load_lib(byte[] url) { + Write_js_tail_init(); + Write_nl_and_indent(); + bfr.Add(Js_line_2_bgn); + bfr.Add(url); + bfr.Add(Js_line_2_end); + } + public byte[] To_bry_and_clear() {return bfr.To_bry_and_clear();} + private static final byte[] + Js_line_1 = Bry_.new_a7("xowa.js.jquery.init();") + , Js_line_2_bgn = Bry_.new_a7("xowa.js.load_lib('") + , Js_line_2_end = Bry_.new_a7("');") + ; + private boolean Reset() { + if (bfr.Len() == reset_end) { // itms wrote nothing + bfr.Delete_rng_to_end(reset_bgn); // delete bgn + return true; + } + else + return false; + } + public void Write_js_global_ini_atr_val(byte[] key, boolean val) {Write_js_global_ini_atr(key, Bool_.N, val ? Bool_.True_bry : Bool_.False_bry);} + public void Write_js_global_ini_atr_val(byte[] key, byte[] val) {Write_js_global_ini_atr(key, Bool_.Y, val);} + public void Write_js_global_ini_atr_obj(byte[] key, byte[] val) {Write_js_global_ini_atr(key, Bool_.N, val);} + public void Write_js_global_ini_atr_msg(Xowe_wiki wiki, byte[] key) {Write_js_global_ini_atr(key, Bool_.Y, wiki.Msg_mgr().Val_by_key_obj(key));} + private void Write_js_global_ini_atr(byte[] key, boolean quote_val, byte[] val) { + Write_js_global_ini_atr_bgn(key); + if (quote_val) + Write_js_quote(Byte_ascii.Apos, val); + else + bfr.Add(val); + bfr.Add_byte(Byte_ascii.Comma); + } + public void Write_js_global_ini_atr_val(byte[] key, int val) { + Write_js_global_ini_atr_bgn(key); + bfr.Add_int_variable(val); + bfr.Add_byte(Byte_ascii.Comma); + } + private void Write_js_global_ini_atr_bgn(byte[] key) { + Write_nl_and_indent(); + bfr.Add_byte_apos(); + bfr.Add(key); + bfr.Add_byte_apos(); + bfr.Add(Js_globals_ini_atr_mid); + } + public void Write_js_ary_bgn() {js_ary_idx = 0; bfr.Add_byte(Byte_ascii.Brack_bgn);} + public void Write_js_ary_itm(byte[] val) { + if (++js_ary_idx != 1) bfr.Add(js_ary_dlm); + Write_js_quote(Byte_ascii.Apos, val); + } private int js_ary_idx = 0; private static final byte[] js_ary_dlm = Bry_.new_a7(", "); + public void Write_js_ary_end() {js_ary_idx = 0; bfr.Add_byte(Byte_ascii.Brack_end);} + public void Write_js_init_global(byte[] key) { // EX: xowa.client = {}; + Write_nl_and_indent(); + bfr.Add(key); + bfr.Add(Js_init_obj); + } + public void Write_js_alias_var(byte[] alias, byte[] key) { // EX: var x_s = xowa.server; + Write_nl_and_indent(); + bfr.Add(Js_var_bgn); + bfr.Add(alias); + bfr.Add(Js_var_mid); + bfr.Add(key); + bfr.Add(Js_var_end); + } + public void Write_js_alias_kv(byte[] alias, byte[] key, byte[] val) { // EX: x_s.port = 8080; + Write_nl_and_indent(); + bfr.Add(alias).Add_byte_dot().Add(key); + bfr.Add(Js_var_mid); + Write_js_quote(Byte_ascii.Apos, val); + bfr.Add(Js_var_end); + } + public void Write_js_xowa_var(byte[] key, boolean quote_val, byte[] val) { // EX: var xowa.app.mode = 'gui'; + Write_nl_and_indent(); + bfr.Add(key); + bfr.Add(Js_var_mid); + if (quote_val) + Write_js_quote(Byte_ascii.Apos, val); + else + bfr.Add(val); + bfr.Add(Js_var_end); + } + public void Write_js_var(byte[] key, boolean quote_val, byte[] val) { + Write_nl_and_indent(); + bfr.Add(Js_var_bgn); + bfr.Add(key); + bfr.Add(Js_var_mid); + if (quote_val) + Write_js_quote(Byte_ascii.Apos, val); + else + bfr.Add(val); + bfr.Add(Js_var_end); + } + public void Write(byte[] v) { + Indent(); + bfr.Add(v); + } + private void Write_js_quote(byte quote_byte, byte[] val) { + int val_len = val.length; + bfr.Add_byte(quote_byte); + for (int i = 0; i < val_len; i++) { + byte b = val[i]; + if (b == quote_byte) bfr.Add_byte_backslash(); // escape quote + else if (b == Byte_ascii.Backslash) bfr.Add_byte_backslash(); // escape backslash + bfr.Add_byte(b); + } + bfr.Add_byte(quote_byte); + } + private void Write_nl_and_indent() { + bfr.Add_byte_nl(); Indent(); + } + private void Indent() {bfr.Add_byte_repeat(Byte_ascii.Space, indent);} + public Xoh_head_wtr Indent_add() {indent += 2; return this;} + public Xoh_head_wtr Indent_del() {indent -= 2; return this;} + private static final byte[] + Css_include_bgn = Bry_.new_a7("") + , Js_include_bgn = Bry_.new_a7("") + , Js_globals_ini_var_bgn = Bry_.new_a7("var xowa_global_values = {") + , Js_globals_ini_var_end = Bry_.new_a7("}") + , Js_globals_ini_atr_mid = Bry_.new_a7(" : ") + , Js_var_bgn = Bry_.new_a7("var ") + , Js_var_mid = Bry_.new_a7(" = ") + , Js_var_end = Bry_.new_a7(";") + , Js_init_obj = Bry_.new_a7(" = {};") + ; +} diff --git a/400_xowa/src/gplx/xowa/htmls/heads/Xoh_head_wtr_tst.java b/400_xowa/src/gplx/xowa/htmls/heads/Xoh_head_wtr_tst.java index a27517de8..24e2ce185 100644 --- a/400_xowa/src/gplx/xowa/htmls/heads/Xoh_head_wtr_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/heads/Xoh_head_wtr_tst.java @@ -13,3 +13,49 @@ 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.htmls.heads; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import org.junit.*; +import gplx.xowa.guis.*; +public class Xoh_head_wtr_tst { + @Before public void init() {fxt.Clear();} private Xoh_head_wtr_fxt fxt = new Xoh_head_wtr_fxt(); + @Test public void Globals_none() { + Xoh_head_wtr wtr = fxt.Wtr(); + wtr.Write_js_head_global_bgn(); + wtr.Write_js_head_global_end(); + fxt.Test(""); + } + @Test public void Globals_some() { + Xoh_head_wtr wtr = fxt.Wtr(); + wtr.Write_js_head_global_bgn(); + fxt.Exec_Write_js_global_ini_atr_val("key_1", "val_1"); + fxt.Exec_Write_js_global_ini_atr_val("key_2", "val_2"); + fxt.Exec_Write_js_global_ini_atr_val("key_3", "apos_'_1"); + wtr.Write_js_head_global_end(); + fxt.Test(String_.Concat_lines_nl_skip_last + ( "" + , "var xowa_global_values = {" + , " 'key_1' : 'val_1'," + , " 'key_2' : 'val_2'," + , " 'key_3' : 'apos_\\'_1'," + , "}" + )); + } +} +class Xoh_head_wtr_fxt { + private Bry_bfr bfr = Bry_bfr_.Reset(255); + public Xoh_head_wtr Wtr() {return wtr;} private Xoh_head_wtr wtr = new Xoh_head_wtr(); + public void Clear() { + wtr.Init(bfr); + } + public void Exec_Write_js_global_ini_atr_val(String key, String val) {wtr.Write_js_global_ini_atr_val(Bry_.new_u8(key), Bry_.new_u8(val));} + public void Test(String expd) { + Tfds.Eq_str_lines(expd, bfr.To_str_and_clear()); + } +// public void Init_msg(byte[] key, String val) { +// wiki.Msg_mgr().Get_or_make(key).Atrs_set(Bry_.new_a7(val), false, false); +// } +// public void Test_write(String expd) { +// mgr.Write(bfr, fxt.App(), wiki, fxt.Page()); +// Tfds.Eq_str_lines(expd, bfr.To_str_and_clear()); +// } +} diff --git a/400_xowa/src/gplx/xowa/htmls/heads/Xow_fragment_mgr.java b/400_xowa/src/gplx/xowa/htmls/heads/Xow_fragment_mgr.java index a27517de8..7fbcb6333 100644 --- a/400_xowa/src/gplx/xowa/htmls/heads/Xow_fragment_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/heads/Xow_fragment_mgr.java @@ -13,3 +13,52 @@ 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.htmls.heads; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; import gplx.xowa.langs.numbers.*; +public class Xow_fragment_mgr implements Gfo_invk { + public Xow_fragment_mgr(Xowe_wiki wiki) {this.wiki = wiki;} private Xowe_wiki wiki; + public byte[] Html_js_edit_toolbar() {return html_js_edit_toolbar;} private byte[] html_js_edit_toolbar; + private Bry_fmtr html_js_edit_toolbar_fmtr = Bry_fmtr.new_(String_.Concat_lines_nl + ( " var xowa_edit_i18n = {" + , " 'bold_tip' : '~{bold_tip}'," + , " 'bold_sample' : '~{bold_sample}'," + , " 'italic_tip' : '~{italic_tip}'," + , " 'italic_sample' : '~{italic_sample}'," + , " 'link_tip' : '~{link_tip}'," + , " 'link_sample' : '~{link_sample}'," + , " 'headline_tip' : '~{headline_tip}'," + , " 'headline_sample' : '~{headline_sample}'," + , " 'ulist_tip' : '~{ulist_tip}'," + , " 'ulist_sample' : '~{ulist_sample}'," + , " 'olist_tip' : '~{olist_tip}'," + , " 'olist_sample' : '~{olist_sample}'" + , " };" + ), "bold_tip", "bold_sample", "italic_tip", "italic_sample", "link_tip", "link_sample", "headline_tip", "headline_sample", "ulist_tip", "ulist_sample", "olist_tip", "olist_sample"); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_html_js_edit_toolbar_fmt_)) html_js_edit_toolbar_fmtr.Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_html_js_edit_toolbar)) return html_js_edit_toolbar; + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk_html_js_edit_toolbar_fmt_ = "html_js_edit_toolbar_fmt_", Invk_html_js_edit_toolbar = "html_js_edit_toolbar"; + public void Evt_lang_changed(Xol_lang_itm lang) { + Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_b512(); + Xow_msg_mgr msg_mgr = wiki.Appe().Usere().Msg_mgr(); + html_js_edit_toolbar = html_js_edit_toolbar_fmtr.Bld_bry_many(bfr + , msg_mgr.Val_by_id(Xol_msg_itm_.Id_edit_toolbar_bold_tip) + , msg_mgr.Val_by_id(Xol_msg_itm_.Id_edit_toolbar_bold_sample) + , msg_mgr.Val_by_id(Xol_msg_itm_.Id_edit_toolbar_italic_tip) + , msg_mgr.Val_by_id(Xol_msg_itm_.Id_edit_toolbar_italic_sample) + , msg_mgr.Val_by_id(Xol_msg_itm_.Id_edit_toolbar_link_tip) + , msg_mgr.Val_by_id(Xol_msg_itm_.Id_edit_toolbar_link_sample) + , msg_mgr.Val_by_id(Xol_msg_itm_.Id_edit_toolbar_headline_tip) + , msg_mgr.Val_by_id(Xol_msg_itm_.Id_edit_toolbar_headline_sample) + , msg_mgr.Val_by_id(Xol_msg_itm_.Id_edit_toolbar_ulist_tip) + , msg_mgr.Val_by_id(Xol_msg_itm_.Id_edit_toolbar_ulist_sample) + , msg_mgr.Val_by_id(Xol_msg_itm_.Id_edit_toolbar_olist_tip) + , msg_mgr.Val_by_id(Xol_msg_itm_.Id_edit_toolbar_olist_sample) + ); + bfr.Mkr_rls(); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/heads/Xow_fragment_mgr_tst.java b/400_xowa/src/gplx/xowa/htmls/heads/Xow_fragment_mgr_tst.java index a27517de8..2a1395cee 100644 --- a/400_xowa/src/gplx/xowa/htmls/heads/Xow_fragment_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/heads/Xow_fragment_mgr_tst.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.htmls.heads; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import org.junit.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.numbers.*; +public class Xow_fragment_mgr_tst { + Xow_fragment_mgr_fxt fxt = new Xow_fragment_mgr_fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Html_js_edit_toolbar_fmt() { + fxt.Test_fragment(Xow_fragment_mgr.Invk_html_js_edit_toolbar, String_.Concat_lines_nl + ( " var xowa_edit_i18n = {" + , " 'bold_tip' : 'Bold text'," + , " 'bold_sample' : 'Bold text'," + , " 'italic_tip' : 'Italic text'," + , " 'italic_sample' : 'Italic text'," + , " 'link_tip' : 'Internal link'," + , " 'link_sample' : 'Link title'," + , " 'headline_tip' : 'Level 2 headline'," + , " 'headline_sample' : 'Headline text'," + , " 'ulist_tip' : 'Bulleted list'," + , " 'ulist_sample' : 'Bulleted list item'," + , " 'olist_tip' : 'Numbered list'," + , " 'olist_sample' : 'Numbered list item'" + , " };" + )); + } +} +class Xow_fragment_mgr_fxt { + public void Clear() { + if (wiki == null) { + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + } + } private Xowe_wiki wiki; + public Xol_lang_itm Make_lang(String key) {return wiki.Appe().Lang_mgr().Get_by_or_new(Bry_.new_a7(key));} + public void Test_fragment(String key, String expd) {Test_fragment(wiki.Lang(), key, expd);} + public void Test_fragment(Xol_lang_itm lang, String key, String expd) { + wiki.Fragment_mgr().Evt_lang_changed(lang); + byte[] actl = (byte[])Gfo_invk_.Invk_by_key(wiki.Fragment_mgr(), key); + Tfds.Eq_str_lines(expd, String_.new_u8(actl)); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_.java b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_.java index a27517de8..ca6dda490 100644 --- a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_.java +++ b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_.java @@ -13,3 +13,26 @@ 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.htmls.hrefs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +public class Xoh_href_ { + public static final String + Str__file = "file://" + , Str__site = "/site/" + , Str__wiki = "/wiki/" + , Str__anch = "#" + ; + public static final byte[] + Bry__file = Bry_.new_a7(Str__file) + , Bry__site = Bry_.new_a7(Str__site) + , Bry__wiki = Bry_.new_a7(Str__wiki) + , Bry__anch = Bry_.new_a7(Str__anch) + , Bry__https = Bry_.new_a7("https://") // NOTE: must be "https:" or wmf api won't work; DATE:2015-06-17 + , Bry__xcmd = Bry_.new_a7("/xcmd/") + ; + public static final int + Len__file = Bry__file.length + , Len__site = Bry__site.length + , Len__wiki = Bry__wiki.length + , Len__anch = Bry__anch.length + ; +} diff --git a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_gui_utl.java b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_gui_utl.java index a27517de8..c740ca5c1 100644 --- a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_gui_utl.java +++ b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_gui_utl.java @@ -13,3 +13,80 @@ 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.htmls.hrefs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.core.btries.*; import gplx.core.primitives.*; +public class Xoh_href_gui_utl { + public static String Html_extract_text(String site, String page, String text_str) { + byte[] text_bry = Bry_.new_u8(text_str); + int text_len = text_bry.length; + int text_tid = Byte_ascii.To_a7_int(text_bry[0]); + switch (text_tid) { + case Text_tid_none: return ""; // "0" + case Text_tid_text: return String_.new_u8(text_bry, 2, text_len); // 2 to skip "1|" + case Text_tid_href: break; // fall through to below + default: throw Err_.new_unhandled(text_tid); + } + int href_bgn = 2; // 2 to skip "2|" + if (Bry_.Has_at_bgn(text_bry, Xoh_href_.Bry__file, href_bgn, text_len)) + href_bgn += Xoh_href_.Len__file; // skip "file://" + Byte_obj_val href_tid = (Byte_obj_val)href_trie.Match_bgn(text_bry, href_bgn, text_len); + if (href_tid != null) { + switch (href_tid.Val()) { + case Href_tid_wiki: return site + String_.new_u8(text_bry, href_bgn, text_len); + case Href_tid_site: return String_.new_u8(text_bry, href_bgn + 6, text_len); // +6 to skip "site/" + case Href_tid_anch: return site + "/wiki/" + page + String_.new_u8(text_bry, href_bgn, text_len); + } + } + return String_.new_u8(text_bry, 2, text_len); // 2 to skip "2|"; handles "http://" text as well as any fall-thru from above + } + public static String Standardize_xowa_link(String str) { + byte[] bry = Bry_.new_u8(str); + int skip = Skip_start_of_xowa_link(bry, bry.length, 0); + return skip == 0 ? str : String_.Mid(str, skip); + } + private static int Skip_start_of_xowa_link(byte[] src, int src_len, int bgn) { + if (!Bry_.Has_at_bgn(src, Xoh_href_.Bry__file, bgn, src_len)) return bgn; // does not start with "file://" + int pos = bgn + Xoh_href_.Len__file; // skip "file://" + Object tid_obj = href_trie.Match_bgn(src, pos, src_len); + if (tid_obj == null) { + return bgn; // if not a known xowa link, return original bgn; + } + switch (((Byte_obj_val)tid_obj).Val()) { + case Href_tid_site: return pos; + case Href_tid_wiki: return pos; + case Href_tid_anch: return pos; + default: throw Err_.new_unhandled(tid_obj); + } + } + private static final byte Text_tid_none = 0, Text_tid_text = 1, Text_tid_href = 2; + private static final byte Href_tid_wiki = 1, Href_tid_site = 2, Href_tid_anch = 3; + private static final Btrie_slim_mgr href_trie = Btrie_slim_mgr.cs() + .Add_bry_byte(Xoh_href_.Bry__site , Href_tid_site) + .Add_bry_byte(Xoh_href_.Bry__wiki , Href_tid_wiki) + .Add_bry_byte(Xoh_href_.Bry__anch , Href_tid_anch) + ; +} +/* +NOTE_1: +. swt/mozilla treats text differently in href="{text}" when content_editable=n; occurs in LocationListener.changing +http://a.org -> http://a.org does nothing +A -> file:///A adds "file:///" +/wiki/A -> file:///wiki/A adds "file://" +Category:A -> Category:A noops; Category is assumed to be protocol? +//en.wiktionary.org/wiki/a -> file:///wiki/a strips out site name and prepends "file://"; no idea why + +. so, to handle the above, the code does the following +http://a.org -> http://a.org does nothing; nothing needed +A -> /wiki/A always prepend /wiki/ +Category:A -> /wiki/Category:A always prepend /wiki/ +//en.wiktionary.org/wiki/A -> /site/en.wiktionary.org/wiki/A always transform relative url to /site/ + +. the href will still come here as file:///wiki/A or file:///site/en.wiktionary.org/wiki/A. +. however, the file:// can be lopped off and discarded and the rest of the href will fall into one of the following cases +.. /wiki/ +.. /site/ +.. /xcmd/ +.. # +.. anything else -> assume to be really a file:// url; EX: file://C/dir/fil.txt -> C/dir/fil.txt +. the other advantage of this approach is that this proc can be reused outside of swt calls; i.e.: it can parse both "file:///wiki/A" and "/wiki/A" +*/ diff --git a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_gui_utl_tst.java b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_gui_utl_tst.java index a27517de8..204aa876c 100644 --- a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_gui_utl_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_gui_utl_tst.java @@ -13,3 +13,54 @@ 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.htmls.hrefs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import org.junit.*; +import gplx.core.primitives.*; import gplx.xowa.htmls.hrefs.*; import gplx.xowa.guis.views.*; +public class Xoh_href_gui_utl_tst { + @Before public void init() {fxt.Clear();} private Xoh_href_gui_utl_fxt fxt = new Xoh_href_gui_utl_fxt(); + @Test public void Extract_href__text() { + fxt.Test_extract_href("0|" , ""); + fxt.Test_extract_href("1|selected_text" , "selected_text"); + fxt.Test_extract_href("2|http://a.org" , "http://a.org"); + } + @Test public void Extract_href__file() { + fxt.Test_extract_href("2|file:///site/en.wiktionary.org/wiki/Page_1" , "en.wiktionary.org/wiki/Page_1"); + fxt.Test_extract_href("2|file:///wiki/Page_2" , "en.wikipedia.org/wiki/Page_2"); + fxt.Test_extract_href("2|file://#anchor" , "en.wikipedia.org/wiki/Page_0#anchor"); + } + @Test public void Extract_href__internal() { + fxt.Test_extract_href("2|/site/en.wiktionary.org/wiki/Page_1" , "en.wiktionary.org/wiki/Page_1"); + fxt.Test_extract_href("2|/wiki/Page_2" , "en.wikipedia.org/wiki/Page_2"); + fxt.Test_extract_href("2|#anchor" , "en.wikipedia.org/wiki/Page_0#anchor"); + } + @Test public void Html_window_vpos_parse() { + fxt.Test_Html_window_vpos_parse("0|0,1,2", "0", "'0','1','2'"); + fxt.Test_Html_window_vpos_parse("org.eclipse.swt.SWTException: Permission denied for to get property Selection.rangeCount", null, null); // check that invalid path doesn't fail; DATE:2014-04-05 + } + @Test public void Standardize_xowa_link() { + fxt.Test_standardize_xowa_link("file:///site/en.wikipedia.org/wiki/A" , "/site/en.wikipedia.org/wiki/A"); + fxt.Test_standardize_xowa_link("file:///wiki/A" , "/wiki/A"); + fxt.Test_standardize_xowa_link("file://#A" , "#A"); + } +} +class Xoh_href_gui_utl_fxt { + public void Clear() { + cur_wiki = "en.wikipedia.org"; + cur_page = "Page_0"; + } + public String Cur_wiki() {return cur_wiki;} public Xoh_href_gui_utl_fxt Cur_wiki_(String v) {cur_wiki = v; return this;} private String cur_wiki; + public String Cur_page() {return cur_page;} public Xoh_href_gui_utl_fxt Cur_page_(String v) {cur_page = v; return this;} private String cur_page; + public void Test_extract_href(String text_str, String expd) { + Tfds.Eq(expd, Xoh_href_gui_utl.Html_extract_text(cur_wiki, cur_page, text_str)); + } + private String_obj_ref scroll_top = String_obj_ref.null_(), node_path = String_obj_ref.null_(); + public void Test_Html_window_vpos_parse(String raw, String expd_scroll_top, String expd_node_path) { + scroll_top.Val_null_(); node_path.Val_null_(); + Xog_html_itm.Html_window_vpos_parse(raw, scroll_top, node_path); + Tfds.Eq(expd_scroll_top, scroll_top.Val(), expd_scroll_top); + Tfds.Eq(expd_node_path, node_path.Val(), expd_node_path); + } + public void Test_standardize_xowa_link(String raw, String expd) { + Tfds.Eq_str(expd, Xoh_href_gui_utl.Standardize_xowa_link(raw), "standardize"); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_parser.java b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_parser.java index a27517de8..86b02d9de 100644 --- a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_parser.java +++ b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_parser.java @@ -13,3 +13,70 @@ 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.htmls.hrefs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.core.primitives.*; import gplx.core.btries.*; import gplx.core.net.*; +import gplx.xowa.langs.vnts.*; +public class Xoh_href_parser { + private final Btrie_rv trv = new Btrie_rv(); + public void Parse_as_url(Xoa_url rv, byte[] raw, Xowe_wiki wiki, byte[] cur_page) { + int bgn = 0; + Object seg_obj = btrie.Match_at(trv, raw, bgn, raw.length); // match /wiki/ or /site/ or /xcmd/ + if (seg_obj == null) { + Xol_vnt_mgr vnt_mgr = wiki.Lang().Vnt_mgr(); + if (vnt_mgr.Enabled() && raw[0] == Byte_ascii.Slash) { + int slash_end = Bry_find_.Find_fwd(raw, Byte_ascii.Slash, 1); + if (vnt_mgr.Regy().Has(Bry_.Mid(raw, 1, slash_end))) { + raw = Bry_.Add(wiki.Domain_bry(), raw); + } + } + } + else { // something matched + switch (((Byte_obj_val)seg_obj).Val()) { + case Seg_xcmd_tid: // convert "/xcmd/a" to "xowa-cmd:a" + raw = Bry_.Add(Gfo_protocol_itm.Bry_xcmd, Bry_.Mid(raw, trv.Pos())); + break; + case Seg_wiki_tid: // add domain_bry; NOTE: needed for url-like pages; EX:"/wiki/http://A"; PAGE:esolangs.org/wiki/Language_list; DATE:2015-11-14 + raw = Bry_.Add(wiki.Domain_bry(), raw); + break; + case Seg_site_tid: // skip "/site" + bgn = trv.Pos(); + break; + default: + break; + } + } + wiki.Utl__url_parser().Parse(rv, raw, bgn, raw.length); + switch (rv.Tid()) { + case Xoa_url_.Tid_anch: + rv.Wiki_bry_(wiki.Domain_bry()); + rv.Page_bry_(cur_page); + break; + case Xoa_url_.Tid_page: + Xow_wiki ttl_wiki = wiki.App().Wiki_mgri().Get_by_or_make_init_n(rv.Wiki_bry()); + byte[] tmp_page = rv.Page_bry(); + if (rv.Page_is_main()) + tmp_page = ttl_wiki.Props().Main_page(); + else { + if (tmp_page != null) { + if (ttl_wiki != null) { + Xoa_ttl ttl = ttl_wiki.Ttl_parse(tmp_page); + if (ttl == null) // invalid ttl; null out page; + tmp_page = Bry_.Empty; + else + tmp_page = ttl.Full_txt_w_ttl_case(); + } + } + } + rv.Page_bry_(tmp_page); + if (tmp_page == null) { // handle xwiki lnke's to history page else null ref; EX:[http://ru.wikipedia.org/w/index.php?title&diff=19103464&oldid=18910980 извещен]; PAGE:ru.w:Project:Заявки_на_снятие_флагов/Архив/Патрулирующие/2009 DATE:2016-11-24 + rv.Tid_(Xoa_url_.Tid_inet); + } + break; + } + } + private static final byte Seg_wiki_tid = 0, Seg_site_tid = 1, Seg_xcmd_tid = 2; + private static final Btrie_slim_mgr btrie = Btrie_slim_mgr.ci_a7() // NOTE:ci.ascii:XO_const.en; /wiki/, /site/ etc. + .Add_bry_tid(Xoh_href_.Bry__wiki, Seg_wiki_tid) + .Add_bry_tid(Xoh_href_.Bry__site, Seg_site_tid) + .Add_bry_tid(Xoh_href_.Bry__xcmd, Seg_xcmd_tid); +} diff --git a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_parser__basic__tst.java b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_parser__basic__tst.java index a27517de8..0f54d8014 100644 --- a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_parser__basic__tst.java +++ b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_parser__basic__tst.java @@ -13,3 +13,82 @@ 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.htmls.hrefs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import org.junit.*; import gplx.xowa.apps.urls.*; import gplx.xowa.wikis.nss.*; +public class Xoh_href_parser__basic__tst { + private final Xoh_href_parser_fxt fxt = new Xoh_href_parser_fxt(); + @Test public void Site__basic() { + fxt.Exec__parse_as_url("/site/en.wikipedia.org/wiki/A").Test__tid(Xoa_url_.Tid_page).Test__to_str("en.wikipedia.org/wiki/A").Test__page("A"); + } + @Test public void Site__ns_case() { + fxt.Exec__parse_as_url("/site/en.wikipedia.org/wiki/file:A").Test__page("File:A"); + } + @Test public void Site__main_page() { + fxt.Exec__parse_as_url("/site/en.wikipedia.org/wiki/").Test__page("Main_Page").Test__page_is_main_y(); + } + @Test public void Site__anch() { + fxt.Exec__parse_as_url("/site/en.wikipedia.org/wiki/A#b_c").Test__page("A").Test__anch("b_c"); + } + @Test public void Site__qarg() { + fxt.Exec__parse_as_url("/site/en.wikipedia.org/wiki/A?action=edit").Test__page("A").Test__qargs("?action=edit"); + } + @Test public void Site__invalid_ttl_shouldnt_fail() { // PURPOSE: invalid title shouldn't fail; EX: A{{B}} is invalid (b/c of braces); + fxt.Exec__parse_as_url("/site/en.wikipedia.org/wiki/A{{B}}").Test__page(""); + } + @Test public void Site__xwiki_cases_correctly() { // PURPOSE: xwiki links should use case_match of xwiki (en.wiktionary.org) not cur_wiki (en.wikipedia.org); EX:w:Alphabet + Xowe_wiki en_wiktionary_org = fxt.Prep_create_wiki("en.wiktionary.org"); + en_wiktionary_org.Ns_mgr().Ns_main().Case_match_(Xow_ns_case_.Tid__all); + fxt.Prep_add_xwiki_to_user("en.wiktionary.org", "en.wiktionary.org"); + fxt.Exec__parse_as_url("/site/en.wiktionary.org/wiki/alphabet"); + fxt.Test__to_str("en.wiktionary.org/wiki/alphabet").Test__page("alphabet"); + } + @Test public void Site__xwiki_compound() { // PURPOSE: [[[w:wikt:]] not handled; DATE:2013-07-25 + fxt.Prep_add_xwiki_to_wiki("wikt", "en.wiktionary.org"); + fxt.Exec__parse_as_url("/site/en.wikipedia.org/wiki/wikt:") + .Test__tid(Xoa_url_.Tid_page) + .Test__page("Main_Page") + .Test__to_str("en.wiktionary.org/wiki/Main_Page") + ; + } +// @Test public void Vnt() { +// Xowe_wiki wiki = fxt.Wiki(); +// fxt.Prep_add_xwiki_to_user("zh.wikipedia.org"); +// wiki.Lang().Vnt_mgr().Enabled_(true); +// wiki.Lang().Vnt_mgr().Vnt_grp().Add(new gplx.xowa.langs.vnts.Vnt_mnu_itm(Bry_.new_a7("zh-hans"), Bry_.new_a7("zh-hant"))); +// fxt.Exec__parse_as_url("/site/zh.wikipedia.org/zh-hant/A").Test__page("A").Chk_vnt("zh-hant"); +// } + @Test public void Http__basic() { + fxt.Exec__parse_as_url("http://a.org/b").Test__tid(Xoa_url_.Tid_inet); + } + @Test public void Prot__ftp() { // PURPOSE: check that urls with form of "ftp://" return back Tid_ftp; DATE:2014-04-25 + fxt.Exec__parse_as_url("ftp://a.org").Test__tid(Xoa_url_.Tid_inet); + } + @Test public void File__basic() { + fxt.Exec__parse_as_url("file:///C/xowa/file/a.png").Test__tid(Xoa_url_.Tid_file); + } + @Test public void Anchor__basic() { + fxt.Exec__parse_as_url("#a").Test__tid(Xoa_url_.Tid_anch).Test__to_str("en.wikipedia.org/wiki/Page 1#a").Test__anch("a"); + } + @Test public void Xcmd__basic() { + fxt.Exec__parse_as_url("/xcmd/page_edit").Test__tid(Xoa_url_.Tid_xcmd).Test__page("page_edit"); + } + @Test public void Xowa__basic() { + fxt.Exec__parse_as_url("xowa-cmd:a%22b*c").Test__tid(Xoa_url_.Tid_xcmd).Test__page("a\"b*c"); + } + // COMMENTED: this seems wrong; [//wikisource.org] should go to https://wikisource.org not https://en.wikisource.org; both sites are different; DATE:2015-08-02 +// @Test public void Site__user_wiki() {// PURPOSE: outlier for wikisource.org which is alias to en.wikisource.org; alias added in user_wiki; EX: [//wikisource.org a]; in browser, automatically goes to http://wikisource.org; in xowa, should go to /site/en.wikisource.org +// fxt.Prep_xwiki(fxt.App().User().Wikii(), "en_wiki_alias", "en.wikipedia.org", null); +// fxt.Exec__parse_as_url("/site/en_wiki_alias/wiki/") +// .Test__tid(Xoa_url_.Tid_page) +// .Test__page("Main_Page") +// .Test__to_str("en.wikipedia.org/wiki/Main_Page") +// ; +// } +} +class Xoh_href_parser_fxt extends Xow_url_parser_fxt { private final Xoh_href_parser href_parser = new Xoh_href_parser(); + public Xoh_href_parser_fxt Exec__parse_as_url(String raw) { + href_parser.Parse_as_url(actl_url, Bry_.new_u8(raw), cur_wiki, Bry__page_1); + return this; + } + private static final byte[] Bry__page_1 = Bry_.new_a7("Page 1"); +} diff --git a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_parser__qargs__tst.java b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_parser__qargs__tst.java index a27517de8..59f94e189 100644 --- a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_parser__qargs__tst.java +++ b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_parser__qargs__tst.java @@ -13,3 +13,24 @@ 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.htmls.hrefs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import org.junit.*; import gplx.xowa.apps.urls.*; import gplx.xowa.wikis.nss.*; +public class Xoh_href_parser__qargs__tst { + private final Xoh_href_parser_fxt fxt = new Xoh_href_parser_fxt(); + @Test public void Basic() { + fxt.Exec__parse_as_url("/wiki/A?k1=v1&k2=v2"); + fxt.Test__page("A"); + fxt.Test__to_str("en.wikipedia.org/wiki/A?k1=v1&k2=v2"); + } + @Test public void Anch() { // PURPOSE.fix: anchor was being placed before qargs; DATE:2016-10-08 + fxt.Exec__parse_as_url("/wiki/Category:A?pagefrom=A#mw-pages"); + fxt.Test__page("Category:A"); + fxt.Test__to_str("en.wikipedia.org/wiki/Category:A?pagefrom=A#mw-pages"); // was Category:A#mw-page?pagefrom=A + } + // FUTURE: qargs should be unencoded by default; decoded on request + @Test public void Encoded() { // PURPOSE.fix: do not use decoded String; DATE:2016-10-08 + fxt.Exec__parse_as_url("/wiki/Category:A?pagefrom=A%26B#mw-pages"); + fxt.Test__page("Category:A"); + fxt.Test__qargs("?pagefrom=A&B"); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_parser__wiki__tst.java b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_parser__wiki__tst.java index a27517de8..6d5df0941 100644 --- a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_parser__wiki__tst.java +++ b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_parser__wiki__tst.java @@ -13,3 +13,36 @@ 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.htmls.hrefs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import org.junit.*; import gplx.xowa.apps.urls.*; import gplx.xowa.wikis.nss.*; +public class Xoh_href_parser__wiki__tst { + private final Xoh_href_parser_fxt fxt = new Xoh_href_parser_fxt(); + @Test public void Basic() { + fxt.Exec__parse_as_url("/wiki/A").Test__tid(Xoa_url_.Tid_page).Test__to_str("en.wikipedia.org/wiki/A").Test__wiki("en.wikipedia.org").Test__page("A"); + } + @Test public void Page__w_question() { + fxt.Exec__parse_as_url("/wiki/%3F").Test__page("?"); + } + @Test public void Qarg() { + fxt.Exec__parse_as_url("/wiki/A?action=edit").Test__page("A").Test__qargs("?action=edit").Test__to_str("en.wikipedia.org/wiki/A?action=edit"); + } + @Test public void Qarg__w_question() { + fxt.Exec__parse_as_url("/wiki/A%3F?action=edit").Test__page("A?").Test__qargs("?action=edit"); + } + @Test public void Anchor() { + fxt.Exec__parse_as_url("/wiki/A#b").Test__to_str("en.wikipedia.org/wiki/A#b").Test__anch("b"); + } + @Test public void Xwiki__only() { + fxt.Prep_add_xwiki_to_wiki("c", "commons.wikimedia.org"); + fxt.Exec__parse_as_url("/wiki/c:").Test__page_is_main_y().Test__page("Main_Page").Test__to_str("commons.wikimedia.org/wiki/Main_Page"); + } + @Test public void Encoded() { + fxt.Exec__parse_as_url("/wiki/A%22b%22c").Test__page("A\"b\"c"); + } + @Test public void Triple_slash() { // PURPOSE: handle triple slashes; PAGE:esolangs.org/wiki/Language_list; DATE:2015-11-14 + fxt.Exec__parse_as_url("/wiki////").Test__to_str("en.wikipedia.org/wiki////").Test__wiki("en.wikipedia.org").Test__page("///"); + } + @Test public void Http() { // PURPOSE: variant of triple slashes; DATE:2015-11-14 + fxt.Exec__parse_as_url("/wiki/http://a").Test__to_str("en.wikipedia.org/wiki/Http://a").Test__wiki("en.wikipedia.org").Test__page("Http://a"); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_wtr.java b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_wtr.java index a27517de8..e1f9cf897 100644 --- a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_wtr.java +++ b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_wtr.java @@ -13,3 +13,93 @@ 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.htmls.hrefs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.core.brys.fmtrs.*; +import gplx.langs.htmls.encoders.*; import gplx.xowa.htmls.core.htmls.*; +import gplx.xowa.wikis.xwikis.*; +public class Xoh_href_wtr { // TS:do not move to app-level + private final Gfo_url_encoder encoder = Gfo_url_encoder_.New__html_href_mw(Bool_.Y).Make(); + private final Bry_bfr encoder_bfr = Bry_bfr_.Reset(255), tmp_bfr = Bry_bfr_.Reset(255); + public byte[] Build_to_bry(Xow_wiki wiki, Xoa_ttl ttl) { + Build_to_bfr(tmp_bfr, wiki.App(), Xoh_wtr_ctx.Basic, wiki.Domain_bry(), ttl); + return tmp_bfr.To_bry_and_clear(); + } + public void Build_to_bfr(Bry_bfr bfr, Xoa_app app, byte[] domain_bry, Xoa_ttl ttl) {Build_to_bfr(bfr, app, Xoh_wtr_ctx.Basic, domain_bry, ttl);} + public void Build_to_bfr(Bry_bfr bfr, Xoa_app app, Xoh_wtr_ctx hctx, byte[] domain_bry, Xoa_ttl ttl) { // given ttl, write href; EX: A -> '/wiki/A' + int hctx_mode = hctx.Mode(); + byte[] page = ttl.Full_txt_raw(); + Xow_xwiki_itm xwiki = ttl.Wik_itm(); + if (xwiki == null) // not an xwiki; EX: [[wikt:Word]] + Build_to_bfr_page(ttl, hctx, page, 0); // write page only; NOTE: changed to remove leaf logic DATE:2014-09-07 + else { // xwiki; skip wiki and encode page only; + byte[] wik_txt = ttl.Wik_txt(); + Build_to_bfr_page(ttl, hctx, page, wik_txt.length + 1); + } + if (xwiki == null) { // not an xwiki + if (ttl.Anch_bgn() != 1) { // not an anchor-only; EX: "#A" + switch (hctx_mode) { + case Xoh_wtr_ctx.Mode_popup: { // popup parser always writes as "/site/" + bfr.Add(Xoh_href_.Bry__site); // add "/site/"; EX: /site/ + bfr.Add(domain_bry); // add xwiki; EX: en_dict + bfr.Add(Xoh_href_.Bry__wiki); // add "/wiki/"; EX: /wiki/ + break; + } + case Xoh_wtr_ctx.Mode_file_dump: { + bfr.Add(hctx.Anch__href__bgn()); + break; + } + default: { + bfr.Add(Xoh_href_.Bry__wiki); // add "/wiki/"; EX: /wiki/Page + break; + } + } + } + else {} // anchor: noop + } + else { // xwiki + if ( app.Xwiki_mgr__missing(xwiki.Domain_bry()) // xwiki is not offline; use http: + || hctx_mode == Xoh_wtr_ctx.Mode_hdump // hdump should never dump as "/site/" + || hctx_mode == Xoh_wtr_ctx.Mode_file_dump // filedump should never dump as "/site/" + ) { + Bry_fmtr url_fmtr = xwiki.Url_fmtr(); + if (url_fmtr == null) { + bfr.Add(Xoh_href_.Bry__https); // add "https://"; EX: https:// + bfr.Add(xwiki.Domain_bry()); // add xwiki; EX: en_dict + bfr.Add(Xoh_href_.Bry__wiki); // add "/wiki/"; EX: /wiki/ + } + else { // url_fmtr exists; DATE:2015-04-22 + url_fmtr.Bld_bfr(bfr, encoder_bfr.To_bry_and_clear()); // use it and pass encoder_bfr for page_name; + return; + } + } + else { // xwiki is avaiable; use /site/ + bfr.Add(Xoh_href_.Bry__site); // add "/site/"; EX: /site/ + bfr.Add(xwiki.Domain_bry()); // add xwiki; EX: en_dict + bfr.Add(Xoh_href_.Bry__wiki); // add "/wiki/"; EX: /wiki/ + } + } + bfr.Add_bfr_and_clear(encoder_bfr); + } + private void Build_to_bfr_page(Xoa_ttl ttl, Xoh_wtr_ctx hctx, byte[] ttl_full, int page_bgn) { + int anch_bgn = Bry_find_.Find_fwd(ttl_full, Byte_ascii.Hash); // NOTE: cannot use Anch_bgn b/c Anch_bgn has bug with whitespace + if (anch_bgn == Bry_find_.Not_found){ // no anchor; just add page + encoder.Encode(encoder_bfr, ttl_full, page_bgn, ttl_full.length); + if (hctx.Mode() == Xoh_wtr_ctx.Mode_file_dump) { + byte[] href_end = hctx.Anch__href__end(); + if (href_end != null) encoder_bfr.Add(href_end); + } + } + else { // anchor exists; check if anchor is preceded by ws; EX: [[A #b]] -> "/wiki/A#b" + int page_end = Bry_find_.Find_bwd_last_ws(ttl_full, anch_bgn); // first 1st ws before #; handles multiple ws + page_end = page_end == Bry_find_.Not_found ? anch_bgn : page_end; // if ws not found, use # pos; else use 1st ws pos + encoder.Encode(encoder_bfr, ttl_full, page_bgn, page_end); // add page + if (hctx.Mode() == Xoh_wtr_ctx.Mode_file_dump) { + byte[] href_end = hctx.Anch__href__end(); + if ( href_end != null + && page_end - page_bgn > 0) // handle [[#A]] which will have no page; else will dump "home/page/.html#A"; DATE:2016-04-12 + encoder_bfr.Add(href_end); + } + encoder.Encode(encoder_bfr, ttl_full, anch_bgn, ttl_full.length); // add anchor + } + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_wtr_tst.java b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_wtr_tst.java index a27517de8..3fc66b5a6 100644 --- a/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_wtr_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/hrefs/Xoh_href_wtr_tst.java @@ -13,3 +13,45 @@ 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.htmls.hrefs; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import org.junit.*; +import gplx.core.net.*; import gplx.xowa.wikis.nss.*; +public class Xoh_href_wtr_tst { + private final Xoh_href_wtr_fxt fxt = new Xoh_href_wtr_fxt(); + @Test public void Xwiki_enc() {fxt.Test_build("wikt:abc?d" , "/site/en.wiktionary.org/wiki/abc%3Fd");} + @Test public void Page_quote() {fxt.Test_build("a\"b\"c" , "/wiki/A%22b%22c");} + @Test public void Page() {fxt.Test_build("abc" , "/wiki/Abc");} + @Test public void Page_ns() {fxt.Test_build("Image:A.png" , "/wiki/Image:A.png");} + @Test public void Anchor() {fxt.Test_build("#abc" , "#abc");} + @Test public void Page_anchor() {fxt.Test_build("Abc#def" , "/wiki/Abc#def");} + @Test public void Xwiki() {fxt.Test_build("wikt:abc" , "/site/en.wiktionary.org/wiki/abc");} // NOTE: "abc" not capitalized, b/c other wiki's case sensitivity is not known; this emulates WP's behavior + @Test public void Xwiki_2() {fxt.Test_build("wikt:Special:Search/a" , "/site/en.wiktionary.org/wiki/Special:Search/a");} + @Test public void Category() {fxt.Test_build("Category:abc" , "/wiki/Category:Abc");} + @Test public void Xwiki_wikimedia_mail() { // PURPOSE: DATE:2015-04-22 + fxt.Prep_xwiki_by_many("0|mail|https://lists.wikimedia.org/mailman/listinfo/~{0}|Wikitech Mailing List"); + fxt.Test_build("mail:A" , "https://lists.wikimedia.org/mailman/listinfo/A"); + } +} +class Xoh_href_wtr_fxt { + private final Xowe_wiki wiki; + private final Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); + private final Xoh_href_wtr href_wtr = new Xoh_href_wtr(); + public Xoh_href_wtr_fxt() { + this.app = Xoa_app_fxt.Make__app__edit(); + this.wiki = Xoa_app_fxt.Make__wiki__edit(app); + wiki.Xwiki_mgr().Add_by_csv(Bry_.new_a7("1|wikt|en.wiktionary.org")); + app.Usere().Wiki().Xwiki_mgr().Add_by_csv(Bry_.new_a7("1|en.wiktionary.org|en.wiktionary.org")); + } + public Xoae_app App() {return app;} private final Xoae_app app; + public Xoh_href_wtr_fxt Prep_wiki_cs(String domain) { + Xow_wiki wiki = app.Wiki_mgr().Get_by_or_make_init_n(Bry_.new_u8(domain)); + wiki.Ns_mgr().Ns_main().Case_match_(Xow_ns_case_.Tid__all); + return this; + } + public Xoh_href_wtr_fxt Prep_xwiki_by_many(String raw) {wiki.Xwiki_mgr().Add_by_csv(Bry_.new_u8(raw)); return this;} // need to add to wiki's xwiki_mgr for ttl_parse + public void Test_build(String raw, String expd) { + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, Bry_.new_u8(raw)); + href_wtr.Build_to_bfr(tmp_bfr, app, wiki.Domain_bry(), ttl); + Tfds.Eq(expd, tmp_bfr.To_str_and_clear()); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/js/Xoh_js_cbk.java b/400_xowa/src/gplx/xowa/htmls/js/Xoh_js_cbk.java index a27517de8..5d8d101b7 100644 --- a/400_xowa/src/gplx/xowa/htmls/js/Xoh_js_cbk.java +++ b/400_xowa/src/gplx/xowa/htmls/js/Xoh_js_cbk.java @@ -13,3 +13,186 @@ 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.htmls.js; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.core.threads.*; import gplx.xowa.xtns.pfuncs.ifs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.langs.jsons.*; +import gplx.xowa.htmls.js.*; +import gplx.xowa.guis.views.*; +import gplx.xowa.parsers.*; +public class Xoh_js_cbk implements Gfo_invk { + private Xoae_app app; + private Xog_html_itm html_itm; + private Xop_root_tkn root = new Xop_root_tkn(); + private final Bry_bfr bfr = Bry_bfr_.Reset(255); + public Xoh_js_cbk(Xog_html_itm html_itm) {this.html_itm = html_itm; this.app = html_itm.Owner_tab().Tab_mgr().Win().App();} + private String Xowa_exec_test(GfoMsg m) { // concat args with pipe; EX: xowa_exec('proc', 'arg0', 'arg1'); -> proc|arg0|arg1 + bfr.Clear(); + bfr.Add_str_u8(m.Key()); + int len = m.Args_count(); + for (int i = 0; i < len; i++) + bfr.Add_str_a7("|").Add_str_u8(m.Args_getAt(i).Val_to_str_or_empty()); + return bfr.To_str_and_clear(); + } + private String[] Xowa_exec_test_as_array(GfoMsg m) {// return args as array; EX: xowa_exec('proc', 'arg0', 'arg1'); -> proc,arg0,arg1 + bfr.Clear(); + int len = m.Args_count(); + String[] rv = new String[len + 1]; + rv[0] = Invk_xowa_exec_test_as_array; + for (int i = 0; i < len; i++) + rv[i + 1] = Object_.Xto_str_strict_or_empty(m.ReadValAt(i)); + return rv; + } + private String Parse_to_html(GfoMsg m) { + Xowe_wiki wiki = html_itm.Owner_tab().Wiki(); + Xop_ctx ctx = wiki.Parser_mgr().Ctx(); + boolean old_para_enabled = ctx.Para().Enabled(); + byte[] raw = Bry_.new_u8(m.Args_getAt(0).Val_to_str_or_empty()); + boolean para_enabled = m.Args_count() < 2 ? false : Bool_.Parse(m.Args_getAt(1).Val_to_str_or_empty()); + try { + ctx.Para().Enabled_(para_enabled); + wiki.Parser_mgr().Main().Parse_text_to_wdom(root, ctx, ctx.Tkn_mkr(), raw, 0); + byte[] data = root.Data_mid(); + wiki.Html_mgr().Html_wtr().Write_doc(bfr, ctx, data, root); + return bfr.To_str_and_clear(); + } + finally { + ctx.Para().Enabled_(old_para_enabled); + } + } + private String Get_page(GfoMsg m) { + Xowe_wiki wiki = html_itm.Owner_tab().Wiki(); + try { + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, m.Args_getAt(0).Val_to_bry()); + Xoae_page page = wiki.Data_mgr().Load_page_by_ttl(ttl); + return String_.new_u8(page.Db().Text().Text_bry()); + } catch (Exception e) {Err_.Noop(e); return null;} + } + private String Popups_get_async_bgn(GfoMsg m) { + try { + byte[] js_cbk = m.Args_getAt(0).Val_to_bry(); + byte[] href_bry = m.Args_getAt(1).Val_to_bry(); + return html_itm.Owner_tab().Wiki().Html_mgr().Head_mgr().Popup_mgr().Get_async_bgn(js_cbk, href_bry); + } catch (Exception e) {Err_.Noop(e); return null;} + } + private String Popups_get_html(GfoMsg m) { + try { + int popups_id = Int_.By_double(Double_.cast(m.Args_getAt(0).Val())); + byte[] href_bry = m.Args_getAt(1).Val_to_bry(); + byte[] tooltip_bry = m.Args_getAt(2).Val_to_bry(); + return html_itm.Owner_tab().Wiki().Html_mgr().Head_mgr().Popup_mgr().Show_init(popups_id, href_bry, tooltip_bry); + } catch (Exception e) {Err_.Noop(e); return null;} + } + private String[] Get_title_meta(Xowe_wiki wiki, byte[] ttl_bry) { + synchronized (tmp_page) { + tmp_page.Clear(); + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, ttl_bry); + wiki.Db_mgr().Load_mgr().Load_by_ttl(tmp_page, ttl.Ns(), ttl.Page_db()); + } + return String_.Ary(tmp_page.Exists() ? "1" : "0", Int_.To_str(tmp_page.Id()), Int_.To_str(tmp_page.Ns_id()), String_.new_u8(tmp_page.Ttl_page_db()), Bool_.To_str_lower(tmp_page.Redirected()), tmp_page.Modified_on().XtoStr_fmt("yyyy-MM-dd HH:mm:ss"), Int_.To_str(tmp_page.Text_len())); + } private static final Xowd_page_itm tmp_page = Xowd_page_itm.new_tmp(); + private String[][] Get_titles_meta(GfoMsg m) { + Xowe_wiki wiki = html_itm.Owner_tab().Wiki(); + try { + byte[][] ttls = Bry_split_.Split(Bry_.new_u8((String)m.ReadValAt(0)), Byte_ascii.Nl); + int ttls_len = ttls.length; + String[][] rv = new String[ttls_len][]; + for (int i = 0; i < ttls_len; i++) { + byte[] ttl = ttls[i]; + rv[i] = Get_title_meta(wiki, ttl); + } + return rv; + } catch (Exception e) {Err_.Noop(e); return null;} + } + private boolean Get_title_exists(Xowe_wiki wiki, byte[] ttl) { + return Pfunc_ifexist.Exists(wiki, ttl); + } + private String[] Get_titles_exists(GfoMsg m) { + Xowe_wiki wiki = html_itm.Owner_tab().Wiki(); + try { + byte[][] ttls = Bry_.Ary_obj((Object[])m.ReadValAt(0)); + int ttls_len = ttls.length; + String[] rv = new String[ttls_len]; + for (int i = 0; i < ttls_len; i++) { + byte[] ttl = ttls[i]; + rv[i] = Get_title_exists(wiki, ttl) ? "1" : "0"; + } + return rv; + } catch (Exception e) {Err_.Noop(e); return null;} + } + private String Get_search_suggestions(GfoMsg m) { + Xowe_wiki wiki = html_itm.Owner_tab().Wiki(); + byte[] search_str = Bry_.new_u8((String)m.ReadValAt(0)); + byte[] cbk_func = Bry_.new_u8((String)m.ReadValAt(1)); + app.Addon_mgr().Itms__search__htmlbar().Search(wiki, search_str, cbk_func); + return ""; + } + private String[] Wikidata_get_label(GfoMsg m) { + try { + Thread_adp_.Sleep(10); // slow down calls to prevent random crashing in XulRunner; DATE:2014-04-23 + gplx.xowa.xtns.wbases.Wdata_wiki_mgr wdata_mgr = app.Wiki_mgr().Wdata_mgr(); + wdata_mgr.Wdata_wiki().Init_assert(); // NOTE: must assert else ns_mgr won't load Property + int len = m.Args_count(); + if (len < 1) return null; + byte[][] langs = Bry_split_.Split(m.Args_getAt(0).Val_to_bry(), Byte_ascii.Semic); + int langs_len = langs.length; + String[] rv = new String[len - 1]; + for (int i = 1; i < len; i++) { + try { + byte[] ttl_bry = m.Args_getAt(i).Val_to_bry(); + gplx.xowa.xtns.wbases.Wdata_doc page = wdata_mgr.Doc_mgr.Get_by_xid_or_null(ttl_bry); if (page == null) continue; + for (int j = 0; j < langs_len; j++) { + byte[] lang_key = langs[j]; + if (Bry_.Eq(lang_key, Wikidata_get_label_xowa_ui_lang)) + lang_key = app.Sys_cfg().Lang(); + byte[] val_bry = null; + if (Bry_.Eq(lang_key, Wikidata_get_label_xowa_title)) + val_bry = ttl_bry; + else { + val_bry = page.Label_list__get(lang_key); + } + if (val_bry == null) continue; + rv[i - 1] = String_.new_u8(val_bry); + break; + } + } catch (Exception e) {Err_.Noop(e); rv[i] = null;} + finally {} + } + return rv; + } catch (Exception e) {Err_.Noop(e); return null;} + } + private String Scripts_exec(GfoMsg m) { + Object rv = null; + try { + rv = app.Gfs_mgr().Run_str(m.Args_getAt(0).Val_to_str_or_empty()); + } + catch (Exception e) {Err_.Noop(e); return null;} + return Object_.Xto_str_strict_or_empty(rv); + } + private static final byte[] Wikidata_get_label_xowa_ui_lang = Bry_.new_a7("xowa_ui_lang"), Wikidata_get_label_xowa_title = Bry_.new_a7("xowa_title"); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_parse_to_html)) return Parse_to_html(m); + else if (ctx.Match(k, Invk_wikidata_get_label)) return Wikidata_get_label(m); + else if (ctx.Match(k, Invk_get_page)) return Get_page(m); + else if (ctx.MatchIn(k, Invk_cmd, Invk_scripts_exec)) return Scripts_exec(m); + else if (ctx.Match(k, Invk_scripts_exec)) return Scripts_exec(m); + else if (ctx.Match(k, Invk_popups_get_async_bgn)) return Popups_get_async_bgn(m); + else if (ctx.Match(k, Invk_popups_get_html)) return Popups_get_html(m); + else if (ctx.Match(k, Invk_get_search_suggestions)) return Get_search_suggestions(m); + else if (ctx.Match(k, Invk_get_titles_meta)) return Get_titles_meta(m); + else if (ctx.Match(k, Invk_get_titles_exists)) return Get_titles_exists(m); + else if (ctx.Match(k, Invk_get_current_url)) return String_.new_u8(html_itm.Owner_tab().Page().Url().Raw()); + else if (ctx.Match(k, Invk_xowa_exec_test)) return Xowa_exec_test(m); + else if (ctx.Match(k, Invk_xowa_exec_test_as_array)) return Xowa_exec_test_as_array(m); + else if (ctx.Match(k, Invk_exec_json)) return app.Html__bridge_mgr().Cmd_mgr().Exec(m); + else if (ctx.Match(k, Invk_bldr_exec)) return app.Bldr().Exec_json((String)m.ReadValAt(0)); + else return Gfo_invk_.Rv_unhandled; + } + public static final String Invk_parse_to_html = "parse_to_html", Invk_wikidata_get_label = "wikidata_get_label", Invk_get_page = "get_page", Invk_cmd = "cmd", Invk_scripts_exec = "scripts_exec" + , Invk_get_search_suggestions = "get_search_suggestions", Invk_get_titles_meta = "get_titles_meta", Invk_get_titles_exists = "get_titles_exists", Invk_get_current_url = "get_current_url" + , Invk_xowa_exec_test = "xowa_exec_test", Invk_xowa_exec_test_as_array = "xowa_exec_test_as_array" + , Invk_popups_get_async_bgn = "popups_get_async_bgn" + , Invk_popups_get_html = "popups_get_html" + , Invk_exec_json = "exec_json" + , Invk_bldr_exec = "bldr_exec" + ; +} diff --git a/400_xowa/src/gplx/xowa/htmls/js/Xoh_js_cbk_tst.java b/400_xowa/src/gplx/xowa/htmls/js/Xoh_js_cbk_tst.java index a27517de8..762e78e64 100644 --- a/400_xowa/src/gplx/xowa/htmls/js/Xoh_js_cbk_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/js/Xoh_js_cbk_tst.java @@ -13,3 +13,32 @@ 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.htmls.js; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import org.junit.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.guis.views.*; +public class Xoh_js_cbk_tst { + @Before public void init() {fxt.Clear();} private Xoh_js_cbk_fxt fxt = new Xoh_js_cbk_fxt(); + @Test public void Get_title() { + fxt.Fxt().Init_page_create("exists"); + fxt.Test_get_title("exists", "1" , "0" , Int_.To_str(Int_.Min_value), "Exists", "false", "0001-01-01 00:00:00", "0"); + fxt.Test_get_title("absent", "0", "-1", Int_.To_str(Int_.Min_value), null , "false", "0001-01-01 00:00:00", "0"); + } +} +class Xoh_js_cbk_fxt { + public void Clear() { + fxt = new Xop_fxt(); + Xoa_app_fxt.Init_gui(fxt.App(), fxt.Wiki()); + } private Xop_fxt fxt; + public Xop_fxt Fxt() {return fxt;} + public void Test_get_title(String ttl, Object... expd) { + Xoae_app app = fxt.App(); + Xowe_wiki wiki = fxt.Wiki(); + Xoae_page page = Xoae_page.New_test(wiki, Xoa_ttl.Parse(wiki, Bry_.new_a7("mock_page"))); + Xog_tab_itm tab = app.Gui_mgr().Browser_win().Active_tab(); + tab.Page_(page); + Xoh_js_cbk exec = tab.Html_itm().Js_cbk(); + GfoMsg msg = GfoMsg_.new_cast_(Xoh_js_cbk.Invk_get_titles_meta).Add("ttl", ttl); + String[][] actl = (String[][])Gfo_invk_.Invk_by_msg(exec, Xoh_js_cbk.Invk_get_titles_meta, msg); + Tfds.Eq_ary_str(expd, actl[0]); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/js/Xoh_js_cbk_wdata_labels_tst.java b/400_xowa/src/gplx/xowa/htmls/js/Xoh_js_cbk_wdata_labels_tst.java index a27517de8..69333dc51 100644 --- a/400_xowa/src/gplx/xowa/htmls/js/Xoh_js_cbk_wdata_labels_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/js/Xoh_js_cbk_wdata_labels_tst.java @@ -13,3 +13,49 @@ 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.htmls.js; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import org.junit.*; import gplx.langs.jsons.*; import gplx.xowa.xtns.wbases.*; +public class Xoh_js_cbk_wdata_labels_tst { + @Before public void init() {fxt.Init();} private final Wdata_wiki_mgr_fxt fxt = new Wdata_wiki_mgr_fxt(); + @Test public void Basic() { + fxt.Init__docs__add(fxt.Wdoc_bldr("q1").Add_label("en", "en_q1").Xto_wdoc()); + fxt.Init__docs__add(fxt.Wdoc_bldr("q2").Add_label("en", "en_q2").Xto_wdoc()); + fxt.Init__docs__add(fxt.Wdoc_bldr("Property:P1").Add_label("en", "en_property_p1").Xto_wdoc()); + Tst_wikidata_label_get(String_.Ary("en", "q1", "q2", "Property:P1"), String_.Ary("en_q1", "en_q2", "en_property_p1")); + } + @Test public void Outliers() { + fxt.Init__docs__add(fxt.Wdoc_bldr("q1").Add_label("en", "en_q1").Add_label("de", "de_q1").Xto_wdoc()); + Tst_wikidata_label_get(String_.Ary("fr", "q1"), String_.Ary((String)null)); + Tst_wikidata_label_get(String_.Ary("de", "q1"), String_.Ary("de_q1")); + Tst_wikidata_label_get(String_.Ary("xowa_title", "q1"), String_.Ary("q1")); + Tst_wikidata_label_get(String_.Ary("xowa_ui_lang", "q1"), String_.Ary("en_q1")); + Tst_wikidata_label_get(String_.Ary("fr;de", "q1"), String_.Ary("de_q1")); + } + @Test public void Escaped() { // PURPOSE: \t should be escaped; EX:wd.q:2; DATE:2014-04-23 + Wdata_doc d = doc_("q1", String_.Concat_lines_nl + ( "{ 'entity':['item',1]" + , ", 'label':" + , " { 'en':'\\ta'" // NOTE: json literally has "\t", not (char)8 + , " }" + , "}" + )); + fxt.Init__docs__add(d); + Tst_wikidata_label_get(String_.Ary("en", "q1"), String_.Ary("\ta")); + } + private Wdata_doc doc_(String qid, String src) { + Json_doc jdoc = fxt.Make_json(src); + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + Wdata_doc rv = new Wdata_doc(Bry_.new_a7(qid), app.Wiki_mgr().Wdata_mgr(), jdoc); + return rv; + } + private void Tst_wikidata_label_get(String[] args, String[] expd) { + Xoa_app_fxt.Init_gui(fxt.App(), fxt.Wiki()); + Xoh_js_cbk exec = fxt.App().Gui_mgr().Browser_win().Active_html_itm().Js_cbk(); + GfoMsg msg = GfoMsg_.new_cast_(Xoh_js_cbk.Invk_wikidata_get_label); + int args_len = args.length; + for (int i = 0; i < args_len; i++) + msg.Add("v", args[i]); + String[] actl = (String[])Gfo_invk_.Invk_by_msg(exec, Xoh_js_cbk.Invk_wikidata_get_label, msg); + Tfds.Eq_ary_str(expd, actl); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/js/Xoh_js_cleaner.java b/400_xowa/src/gplx/xowa/htmls/js/Xoh_js_cleaner.java index a27517de8..282286de0 100644 --- a/400_xowa/src/gplx/xowa/htmls/js/Xoh_js_cleaner.java +++ b/400_xowa/src/gplx/xowa/htmls/js/Xoh_js_cleaner.java @@ -13,3 +13,190 @@ 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.htmls.js; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.core.btries.*; +import gplx.langs.htmls.entitys.*; +public class Xoh_js_cleaner { + private Xoae_app app; private boolean ctor = true; + public Xoh_js_cleaner(Xoae_app app) {this.app = app;} + public void Clean_bfr(Xowe_wiki wiki, Xoa_ttl ttl, Bry_bfr bfr, int bgn) { + int end = bfr.Len(); + byte[] cleaned = this.Clean(wiki, bfr.Bfr(), bgn, end); + if (cleaned != null) { + bfr.Del_by(end - bgn); + bfr.Add(cleaned); + app.Usr_dlg().Log_many("", "", "javascript detected: wiki=~{0} ~{1}", wiki.Domain_str(), String_.new_u8(ttl.Full_txt_w_ttl_case())); + } + } + public byte[] Clean(Xowe_wiki wiki, byte[] src, int bgn, int end) { + if (ctor) Ctor(); + Bry_bfr bfr = null; + boolean dirty = false; + try { + bfr = wiki.Utl__bfr_mkr().Get_m001(); + int pos = bgn; + while (pos < end) { + byte b = src[pos]; + Object o = trie.Match_bgn_w_byte(b, src, pos, end); + if (o == null) { + if (dirty) + bfr.Add_byte(b); + ++pos; + } + else { + byte[] frag = (byte[])o; + int frag_len = frag.length; + if (frag[0] == Byte_ascii.Lt) { // jscript node; EX: c", "a<script>bc");} + @Test public void Js_atr() {fxt.Test_clean("abc", "abc");} + @Test public void Js_atr_noop() {fxt.Test_clean("a onmouseover b", "a onmouseover b");} + @Test public void Js_atr_noop_regionSelect() {fxt.Test_clean("regionSelect=2", "regionSelect=2");} +} +class Xoh_js_cleaner_fxt { + public void Init() { + if (mgr == null) { + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + mgr = wiki.Html_mgr().Js_cleaner(); + } + } private Xoae_app app; Xowe_wiki wiki; Xoh_js_cleaner mgr; + public void Test_clean(String raw_str, String expd) { + byte[] raw = Bry_.new_a7(raw_str); + byte[] actl = mgr.Clean(wiki, raw, 0, raw.length); + if (actl == null) actl = raw; + Tfds.Eq(expd, String_.new_a7(actl)); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xopg_popup_mgr.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xopg_popup_mgr.java index a27517de8..6a5b82d50 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xopg_popup_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xopg_popup_mgr.java @@ -13,3 +13,10 @@ 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.htmls.modules.popups; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; +public class Xopg_popup_mgr { + public Ordered_hash Itms() {return itms;} private Ordered_hash itms = Ordered_hash_.New(); + public void Clear() { + itms.Clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_anchor_finder.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_anchor_finder.java index a27517de8..65baba1f1 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_anchor_finder.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_anchor_finder.java @@ -13,3 +13,56 @@ 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.htmls.modules.popups; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; +import gplx.xowa.parsers.*; +class Xow_popup_anchor_finder { + private byte[] src, find; + private int src_len, nl_lhs; + public int Find(byte[] src, int src_len, byte[] find, int bgn) { + this.src = src; this.src_len = src_len; this.find = find; this.nl_lhs = bgn; + if (bgn == Xop_parser_.Doc_bgn_bos && Find_hdr(bgn)) return Xop_parser_.Doc_bgn_bos;// handle BOS separately which won't fit "\n=" search below; EX: "BOS==A==\n" + int lhs_bgn = bgn; + while (true) { + lhs_bgn = Bry_find_.Find_fwd(src, Hdr_bgn, nl_lhs, src_len); + if (lhs_bgn == Bry_find_.Not_found) break; // "\n=" not found; exit; + if (Find_hdr(lhs_bgn)) return lhs_bgn; + } + return Find_id(bgn); + } + private boolean Find_hdr(int lhs_bgn) { + int nl_rhs = Bry_find_.Find_fwd(src, Byte_ascii.Nl, nl_lhs + 1, src_len); // look for \n + if (nl_rhs == Bry_find_.Not_found) nl_rhs = src_len - 1; // no more \n; set to last idx + nl_lhs = nl_rhs; // update nl_lhs for loop + int lhs_end = Bry_find_.Find_fwd_while(src, lhs_bgn + 1, nl_rhs, Byte_ascii.Eq); // skip eq; EX: "\n==="; +1 to skip eq + int rhs_end = Bry_find_.Trim_bwd_space_tab(src, nl_rhs, lhs_end); // skip ws bwd; EX: "== \n" + int rhs_bgn = Bry_find_.Find_bwd_while(src, rhs_end, lhs_end, Byte_ascii.Eq); // skip eq; EX: "==\n" -> pos before = + if (rhs_bgn < lhs_end) return false; // eq found, but is actually lhs_eq; no rhs_eq, so exit; EX: "\n== \n" + ++rhs_bgn; // rhs_bgn is 1st char before eq; position at eq; neede for txt_end below + int txt_bgn = Bry_find_.Trim_fwd_space_tab(src, lhs_end, nl_rhs); // trim ws + int txt_end = Bry_find_.Trim_bwd_space_tab(src, rhs_bgn, lhs_end); // trim ws + return Bry_.Eq(src, txt_bgn, txt_end, find); // check for strict match + } + private int Find_id(int bgn) { + byte[] quoted = Bry_.Add(Byte_ascii.Quote_bry, find, Byte_ascii.Quote_bry); + int rv = Find_id_by_quoted(bgn, quoted); + if (rv == Bry_find_.Not_found) { + quoted[0] = Byte_ascii.Apos; quoted[quoted.length - 1] = Byte_ascii.Apos; + rv = Find_id_by_quoted(bgn, quoted); + } + return rv; + } + private int Find_id_by_quoted(int bgn, byte[] quoted) { + int rv = Bry_find_.Not_found; + int pos = Bry_find_.Find_fwd(src, quoted, bgn); + if (pos == Bry_find_.Not_found) return rv; + pos = Bry_find_.Trim_bwd_space_tab(src, pos, bgn); + if (src[pos - 1] != Byte_ascii.Eq) return rv; + int id_end = Bry_find_.Trim_bwd_space_tab(src, pos - 1, bgn); + int id_bgn = Bry_find_.Find_bwd_ws(src, id_end - 1, bgn); + boolean id_match = Int_.Bounds_chk(id_bgn, id_end, src_len) && Bry_.Eq(src, id_bgn + 1, id_end, Id_bry); + if (!id_match) return rv; + rv = Bry_find_.Find_bwd(src, Byte_ascii.Nl, id_bgn); + return rv == Bry_find_.Not_found ? 0 : rv; + } + private static final byte[] Hdr_bgn = Bry_.new_a7("\n="), Id_bry = Bry_.new_a7("id"); +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_anchor_finder__hdr_tst.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_anchor_finder__hdr_tst.java index a27517de8..6ace9d35a 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_anchor_finder__hdr_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_anchor_finder__hdr_tst.java @@ -13,3 +13,71 @@ 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.htmls.modules.popups; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; +import org.junit.*; +import gplx.xowa.apps.apis.xowa.html.modules.*; +import gplx.xowa.guis.views.*; +public class Xow_popup_anchor_finder__hdr_tst { + @Before public void init() {fxt.Clear();} private Xop_popup_hdr_finder_fxt fxt = new Xop_popup_hdr_finder_fxt(); + @Test public void Basic() { + String src_str = String_.Concat_lines_nl_skip_last + ( "a" + , "==b1==" + , "c" + ); + fxt.Test_find(src_str, "b1", 1); + fxt.Test_find_not(src_str, "b"); + fxt.Test_find_not(src_str, "a"); + } + @Test public void Mid() { + String src_str = String_.Concat_lines_nl_skip_last + ( "a" + , "==b==" + , "c" + , "==d==" + , "e" + ); + fxt.Test_find(src_str, "d", 9); + } + @Test public void Eos() { + String src_str = String_.Concat_lines_nl_skip_last + ( "a" + , "==b==" + ); + fxt.Test_find(src_str, "b", 1); + } + @Test public void Bos() { + String src_str = String_.Concat_lines_nl_skip_last + ( "==a==" + , "b" + ); + fxt.Test_find(src_str, "a", -1); + } + @Test public void Trim() { + String src_str = String_.Concat_lines_nl_skip_last + ( "a" + , "== b ==" + , "c" + ); + fxt.Test_find(src_str, "b", 1); + } + @Test public void Ws() { + String src_str = String_.Concat_lines_nl_skip_last + ( "a" + , "== b c ==" + , "d" + ); + fxt.Test_find(src_str, "b c", 1); + } +} +class Xop_popup_hdr_finder_fxt { + private Xow_popup_anchor_finder finder = new Xow_popup_anchor_finder(); + public void Clear() { + } + public void Test_find_not(String src_str, String hdr_str) {Test_find(src_str, hdr_str, Bry_find_.Not_found);} + public void Test_find(String src_str, String hdr_str, int expd) { + byte[] src = Bry_.new_u8(src_str); + byte[] hdr = Bry_.new_u8(hdr_str); + Tfds.Eq(expd, finder.Find(src, src.length, hdr, 0), hdr_str); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_anchor_finder__id_tst.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_anchor_finder__id_tst.java index a27517de8..af91a8cb0 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_anchor_finder__id_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_anchor_finder__id_tst.java @@ -13,3 +13,36 @@ 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.htmls.modules.popups; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; +import org.junit.*; +import gplx.xowa.apps.apis.xowa.html.modules.*; +import gplx.xowa.guis.views.*; +public class Xow_popup_anchor_finder__id_tst { + @Before public void init() {fxt.Clear();} private Xop_popup_hdr_finder_fxt fxt = new Xop_popup_hdr_finder_fxt(); + @Test public void Basic() { + String src_str = String_.Concat_lines_nl_skip_last + ( "b" + , "" + , "c" + ); + fxt.Test_find(src_str, "a", 1); + fxt.Test_find_not(src_str, "b"); + fxt.Test_find_not(src_str, "c"); + } + @Test public void Ws() { + String src_str = String_.Concat_lines_nl_skip_last + ( "b" + , "" + , "c" + ); + fxt.Test_find(src_str, "a", 1); + } + @Test public void Fail() { + String src_str = String_.Concat_lines_nl_skip_last + ( "b" + , "" + , "c" + ); + fxt.Test_find_not(src_str, "a"); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_cfg.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_cfg.java index a27517de8..3e3ec5a82 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_cfg.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_cfg.java @@ -13,3 +13,20 @@ 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.htmls.modules.popups; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; +import gplx.xowa.apps.apis.xowa.html.modules.*; +public class Xow_popup_cfg { + public int Show_all_if_less_than() {return show_all_if_less_than;} public void Show_all_if_less_than_(int v) {show_all_if_less_than = v;} private int show_all_if_less_than; + public int Tmpl_read_max() {return tmpl_read_max;} public void Tmpl_read_max_(int v) {tmpl_read_max = v;} private int tmpl_read_max; + public int Tmpl_read_len() {return tmpl_read_len;} public void Tmpl_read_len_(int v) {tmpl_read_len = v;} private int tmpl_read_len; + public int Read_til_stop_fwd() {return read_til_stop_fwd;} public void Read_til_stop_fwd_(int v) {read_til_stop_fwd = v;} private int read_til_stop_fwd; + public int Read_til_stop_bwd() {return read_til_stop_bwd;} public void Read_til_stop_bwd_(int v) {read_til_stop_bwd = v;} private int read_til_stop_bwd; + public int Stop_if_hdr_after() {return stop_if_hdr_after;} public void Stop_if_hdr_after_(int v) {stop_if_hdr_after = v;} private int stop_if_hdr_after; + public boolean Stop_if_hdr_after_enabled() {return stop_if_hdr_after > 0;} + public byte[] Ellipsis() {return ellipsis;} public void Ellipsis_(byte[] v) {ellipsis = v;} private byte[] ellipsis = Bry_.Empty; + public byte[] Notoc() {return notoc;} public void Notoc_(byte[] v) {notoc = v;} private byte[] notoc = Notoc_const; + public static final byte[] + Notoc_const = Bry_.new_a7("\n__NOTOC__") // NOTE: always add a whitespace tkn else __NOTOC__ will be deactivated if last tkn is lnke; DATE:2014-06-22 + , Msg_key_ellipsis = Bry_.new_a7("ellipsis") + ; +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_html_mkr.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_html_mkr.java index a27517de8..5e94dba92 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_html_mkr.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_html_mkr.java @@ -13,3 +13,91 @@ 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.htmls.modules.popups; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.apps.apis.xowa.html.modules.*; +public class Xow_popup_html_mkr { + private Xoae_app app; private Xowe_wiki wiki; + public Bry_fmtr Fmtr_popup() {return fmtr_popup;} private Bry_fmtr fmtr_popup = Bry_fmtr.new_(Dflt_html_fmtr_popup, Dflt_html_fmtr_popup_keys); + public Bry_fmtr Fmtr_viewed() {return fmtr_viewed;} private Bry_fmtr fmtr_viewed = Bry_fmtr.new_(Dflt_html_fmtr_viewed, Dflt_html_fmtr_viewed_keys); + public Bry_fmtr Fmtr_wiki() {return fmtr_wiki;} private Bry_fmtr fmtr_wiki = Bry_fmtr.new_(Dflt_html_fmtr_wiki, Dflt_html_fmtr_wiki_keys); + public Bry_fmtr Fmtr_next_sect() {return fmtr_next_sect;} private Bry_fmtr fmtr_next_sect = Bry_fmtr.new_(Dflt_html_fmtr_next_sect, Dflt_html_fmtr_next_sect_keys); + public void Output_js_clean_(boolean v) {output_js_clean = v;} private boolean output_js_clean = true; + public void Output_tidy_(boolean v) {output_tidy = v;} private boolean output_tidy = true; + public void Ctor(Xoae_app app, Xowe_wiki wiki) { + this.wiki = wiki; this.app = app; + wiki.Eval_mgr().Eval_mgr_(fmtr_popup, fmtr_viewed, fmtr_wiki, fmtr_next_sect); + } + public byte[] Bld(Xowe_wiki cur_wiki, Xoae_page page, Xow_popup_itm popup_itm, Bry_bfr wrdx_bfr) { + if (output_js_clean) cur_wiki.Html_mgr().Js_cleaner().Clean_bfr(wiki, page.Ttl(), wrdx_bfr, 0); + if (output_tidy) cur_wiki.Html_mgr().Tidy_mgr().Exec_tidy(wrdx_bfr, Bool_.Y, page.Url_bry_safe()); + byte[] hdom_bry = wrdx_bfr.To_bry_and_clear(); + String page_url = wrdx_bfr.Add(page.Wiki().Domain_bry()).Add(gplx.xowa.htmls.hrefs.Xoh_href_.Bry__wiki).Add(gplx.langs.htmls.encoders.Gfo_url_encoder_.Href + .Encode(page.Ttl().Full_db())) // NOTE: was page.Url().Raw(), but that doesn't work for Special:Search; PAGE:en.w:Earth and "Quotations"; DATE:2014-06-29 + .To_str_and_clear() + ; + fmtr_popup.Bld_bfr_many + ( wrdx_bfr + , hdom_bry + , wiki.Lang().Dir_ltr_bry() + , page_url + , String_.new_u8(page.Ttl().Full_txt_w_ttl_case()) + , popup_itm.Popup_id() + , Xow_popup_html_bldr_.Bld_fmtr_wiki(fmtr_wiki, wrdx_bfr, cur_wiki.Domain_bry(), page.Wiki().Domain_bry()) // NOTE: use cur_wiki, not page_wiki; DATE:2014-06-28 + , gplx.core.ios.Io_size_.To_str(page.Db().Text().Text_bry().length) + , page.Db().Page().Modified_on().XtoStr_fmt_yyyy_MM_dd_HH_mm_ss() + , Xow_popup_html_bldr_.Bld_fmtr_viewed(fmtr_viewed, app, wiki, wrdx_bfr, page.Ttl()) + , app.Fsys_mgr().Root_dir().To_http_file_bry() + ); + return wrdx_bfr.To_bry_and_clear(); + } + + private static final byte[] + Dflt_html_fmtr_popup = Bry_.new_a7(String_.Concat_lines_nl_skip_last + ( "

    " + , "
    ~{content}" + , "
    " + , "
    " + , "
    " + , " ~{page_title}~{wiki_item}" + , " ~{<>msgs.get('api-xowa.html.modules.popups.msgs.size-name');<>}~{page_size}" + , " ~{<>msgs.get('api-xowa.html.modules.popups.msgs.edited-name');<>}~{edit_time}~{view_time_item}" + , "
    " + , "
    " + , "
    " + , " }'>" + , " }'>" + , " }'>" + , " }'>" + , " }'>" + , " }'> " + , " }'>" + , " }'>" // HOME + , "
    " + , "
    " + )) + , Dflt_html_fmtr_viewed = Bry_.new_a7("\n ~{<>msgs.get('api-xowa.html.modules.popups.msgs.view_time-name');<>}~{viewed_val}") + , Dflt_html_fmtr_wiki = Bry_.new_a7("\n ~{<>msgs.get('api-xowa.html.modules.popups.msgs.wiki-name');<>}~{wiki_val}") + , Dflt_html_fmtr_next_sect = Bry_.new_a7("\n\n~{<>msgs.get('api-xowa.html.modules.popups.msgs.next_sect-name');<>}~{next_sect_val}") + ; + private static final String[] + Dflt_html_fmtr_popup_keys = String_.Ary("content", "page_lang_ltr", "page_url", "page_title", "popup_id", "wiki_item", "page_size", "edit_time", "view_time_item", "xowa_root_dir") + , Dflt_html_fmtr_viewed_keys = String_.Ary("viewed_val") + , Dflt_html_fmtr_wiki_keys = String_.Ary("wiki_val") + , Dflt_html_fmtr_next_sect_keys = String_.Ary("next_sect_val") + ; +} +class Xow_popup_html_bldr_ { + public static byte[] Bld_fmtr_wiki(Bry_fmtr fmtr, Bry_bfr wrdx_bfr, byte[] wiki_domain, byte[] page_domain) { + return Bry_.Eq(wiki_domain, page_domain) + ? Bry_.Empty // same domain; return ""; + : fmtr.Bld_bry_many(wrdx_bfr, page_domain); + } + public static byte[] Bld_fmtr_viewed(Bry_fmtr fmtr, Xoae_app app, Xowe_wiki wiki, Bry_bfr wrdx_bfr, Xoa_ttl ttl) { + byte[] view_time_item = Bry_.Empty; + gplx.xowa.users.history.Xou_history_itm history_itm = app.Usere().History_mgr().Get_or_null(wiki.Domain_bry(), ttl.Full_txt_w_ttl_case()); + if (history_itm != null) + view_time_item = fmtr.Bld_bry_many(wrdx_bfr, history_itm.View_end().XtoStr_fmt_yyyy_MM_dd_HH_mm_ss()); + return view_time_item; + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_itm.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_itm.java index a27517de8..52885df4e 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_itm.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_itm.java @@ -13,3 +13,36 @@ 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.htmls.modules.popups; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; +public class Xow_popup_itm implements Cancelable { + public Xow_popup_itm(int id, byte[] page_href, byte[] tooltip, int init_words_needed) { + this.popup_id = gplx.xowa.apps.Xoa_thread_.Key_page_popup + Int_.To_str(id); + this.words_needed = init_words_needed; + this.page_href = page_href; + this.tooltip = tooltip; + } + public boolean Canceled() {return canceled;} private boolean canceled = false; + public void Cancel() {canceled = true;} + public byte Mode() {return mode;} private byte mode = Mode_tid_init; + public Xow_popup_itm Mode_more_(int more_words) { + mode = Mode_tid_more; + words_needed = words_found + more_words; + return this; + } + public boolean Mode_all() {return mode == Mode_tid_all;} + public Xow_popup_itm Mode_all_() { + mode = Mode_tid_all; + words_needed = Int_.Max_value; + return this; + } + public String Popup_id() {return popup_id;} private String popup_id; + public byte[] Popup_html() {return popup_html;} public void Popup_html_(byte[] v) {popup_html = v;} private byte[] popup_html; + public byte[] Tooltip() {return tooltip;} private byte[] tooltip; + public byte[] Wiki_domain() {return wiki_domain;} private byte[] wiki_domain; + public byte[] Page_href() {return page_href;} private byte[] page_href; + public Xoa_ttl Page_ttl() {return page_ttl;} private Xoa_ttl page_ttl; + public void Init(byte[] wiki_domain, Xoa_ttl page_ttl) {this.wiki_domain = wiki_domain; this.page_ttl = page_ttl;} + public int Words_needed() {return words_needed;} private int words_needed; + public int Words_found() {return words_found;} public void Words_found_(int v) {words_found = v;} private int words_found; + public static final byte Mode_tid_init = 0, Mode_tid_more = 1, Mode_tid_all = 2; +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_mgr.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_mgr.java index a27517de8..ac49e2a07 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_mgr.java @@ -13,3 +13,237 @@ 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.htmls.modules.popups; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; +import gplx.core.primitives.*; import gplx.core.threads.*; import gplx.core.envs.*; +import gplx.core.js.*; +import gplx.xowa.addons.apps.cfgs.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.guis.views.*; +import gplx.xowa.htmls.hrefs.*; +import gplx.xowa.specials.*; +import gplx.xowa.apps.apis.xowa.html.modules.*; +public class Xow_popup_mgr implements Gfo_invk, Gfo_evt_itm { + private Xoae_app app; private Xowe_wiki wiki; private Js_wtr js_wtr = new Js_wtr(); + private int show_init_word_count, show_more_word_count; + private Xoa_url tmp_url = Xoa_url.blank(); + private static final Object thread_lock = new Object(); private Xow_popup_itm async_itm; private Gfo_invk async_cmd_show; private int async_id_next = 1; + public Xow_popup_mgr(Xowe_wiki wiki) { + this.wiki = wiki; this.app = wiki.Appe(); + ev_mgr = new Gfo_evt_mgr(this); + } + public Gfo_evt_mgr Evt_mgr() {return ev_mgr;} private Gfo_evt_mgr ev_mgr; + public Xow_popup_parser Parser() {return parser;} private Xow_popup_parser parser = new Xow_popup_parser(); + public boolean Enabled() {return enabled;} public void Enabled_(boolean v) {this.enabled = v;} private boolean enabled = true; // TEST: false will fail Xob_init_base_tst; DATE:2016-12-13 + public void Init_by_wiki(Xowe_wiki wiki) { + parser.Init_by_wiki(wiki); + wiki.App().Cfg().Bind_many_wiki(this, wiki + , Cfg__enabled + , Cfg__show_init_word_count, Cfg__show_more_word_count + , Cfg__show_all_if_less_than, Cfg__read_til_stop_fwd, Cfg__read_til_stop_bwd, Cfg__stop_if_hdr_after + , Cfg__tmpl_tkn_max, Cfg__tmpl_keeplist + , Cfg__ns_allowed, Cfg__xnde_ignore_ids, Cfg__scan_len, Cfg__scan_max + ); + } + public String Show_init(int id, byte[] href, byte[] tooltip) { + Xoae_page cur_page = Cur_page(); + Xog_tab_itm tab = cur_page.Tab_data().Tab(); + if (tab != null && tab.Tab_is_loading()) return ""; // NOTE: tab is null when previewing + Xow_popup_itm itm = new Xow_popup_itm(id, href, tooltip, show_init_word_count); + String rv = String_.new_u8(Get_popup_html(Cur_wiki(), cur_page, itm)); + return tab != null && tab.Tab_is_loading() ? "" : rv; + } + public void Show_more(String popup_id) { + Xoae_page cur_page = Cur_page(); + Xow_popup_itm popup_itm = Itms_get_or_null(cur_page, popup_id).Mode_more_(show_more_word_count); + popup_itm.Popup_html_(Get_popup_html(Cur_wiki(), cur_page, popup_itm)); + Show_popup_html(Cbk_xowa_popups_show_update, Mode_show_more, popup_itm); + } + public void Show_all(String popup_id) { + Xoae_page cur_page = Cur_page(); + Xow_popup_itm popup_itm = Itms_get_or_null(cur_page, popup_id).Mode_all_(); + popup_itm.Popup_html_(Get_popup_html(Cur_wiki(), cur_page, popup_itm)); + Show_popup_html(Cbk_xowa_popups_show_update, Mode_show_all, popup_itm); + } + public String Get_async_bgn(byte[] js_cbk, byte[] href) { + if (Bry_.Has_at_bgn(href, gplx.xowa.parsers.lnkes.Xop_lnke_wkr.Bry_xowa_protocol)) return null; // ignore xowa-cmd + synchronized (thread_lock) { + if (async_itm != null) async_itm.Cancel(); + async_itm = new Xow_popup_itm(++async_id_next, href, Bry_.Empty, show_init_word_count); + String id_str = async_itm.Popup_id(); + Thread_adp_.Start_by_key(id_str, this, Invk_show_popup_async); + return id_str; + } + } + public static boolean Running() { + boolean rv = false; + synchronized (thread_lock) { + rv = running; + } + return rv; + } private static boolean running = false; + private static void Running_(boolean v) { + synchronized (thread_lock) { + running = v; + } + } + private byte[] Get_popup_html(Xowe_wiki cur_wiki, Xoae_page cur_page, Xow_popup_itm itm) { + try { + synchronized (thread_lock) { // queue popups to reduce contention with Load_page_wkr; DATE:2014-08-24 +// Load_popup_wkr load_popup_wkr = new Load_popup_wkr(wiki, cur_page, itm, temp_href, ns_allowed_regy, ns_allowed_regy_key); +// app.Thread_mgr().Page_load_mgr().Add_at_end(load_popup_wkr); +// load_popup_wkr.Exec(); +// while (!load_popup_wkr.Rslt_done()) { +// Thread_adp_.Sleep(100); +// } +// return load_popup_wkr.Rslt_bry(); + Running_(true); + if (itm.Canceled()) return null; + cur_page.Popup_mgr().Itms().Add_if_dupe_use_nth(itm.Popup_id(), itm); + app.Html__href_parser().Parse_as_url(tmp_url, itm.Page_href(), wiki, cur_page.Ttl().Full_url()); // NOTE: use Full_url, not Page_url, else anchors won't work for non-main ns; PAGE:en.w:Project:Sandbox; DATE:2014-08-07 + if (!Xoa_url_.Tid_is_pagelike(tmp_url.Tid())) return Bry_.Empty; // NOTE: do not get popups for "file:///"; DATE:2015-04-05 + Xowe_wiki popup_wiki = (Xowe_wiki)app.Wiki_mgr().Get_by_or_null(tmp_url.Wiki_bry()); + popup_wiki.Init_assert(); + Xoa_ttl popup_ttl = Xoa_ttl.Parse(popup_wiki, tmp_url.To_bry_page_w_anch()); + switch (popup_ttl.Ns().Id()) { + case Xow_ns_.Tid__media: + case Xow_ns_.Tid__file: + return Bry_.Empty; // do not popup for media or file + case Xow_ns_.Tid__special: + if (!Xow_special_meta_.Itm__popup_history.Match_ttl(popup_ttl)) return Bry_.Empty; // do not popup for special, unless popupHistory; DATE:2015-04-20 + break; + } + if (ns_allowed_regy.Count() > 0 && !ns_allowed_regy.Has(ns_allowed_regy_key.Val_(popup_ttl.Ns().Id()))) return Bry_.Empty; + itm.Init(popup_wiki.Domain_bry(), popup_ttl); + int wait_count = 0; + while (gplx.xowa.guis.views.Load_page_wkr.Running() && ++wait_count < 100) { + Thread_adp_.Sleep(10); + } + Xoae_page popup_page = popup_wiki.Data_mgr().Load_page_by_ttl(popup_ttl); + byte[] rv = popup_wiki.Html_mgr().Head_mgr().Popup_mgr().Parser().Parse(wiki, popup_page, cur_page.Tab_data().Tab(), itm); + Update_progress_bar(app, cur_wiki, cur_page, itm); + return rv; + } + } + catch(Exception e) { + app.Usr_dlg().Warn_many("", "", "failed to get popup: href=~{0} err=~{1}", itm.Page_href(), Err_.Message_gplx_full(e)); + return null; + } + finally { + Running_(false); + } + } + public static void Update_progress_bar(Xoae_app app, Xowe_wiki cur_wiki, Xoae_page cur_page, Xow_popup_itm itm) { + byte[] href = itm.Page_href(); + byte[] tooltip = itm.Tooltip(); + if (Bry_.Len_gt_0(tooltip)) + href = Bry_.Add(tooltip); + Xog_win_itm__prog_href_mgr.Hover(app, app.Gui_mgr().Browser_win().Cfg().Status__show_short_url(), cur_wiki, cur_page, String_.new_u8(href)); // set page ttl again in prog bar; DATE:2014-06-28 + } + public void Show_popup_html(String cbk, byte[] mode, Xow_popup_itm popup_itm) { + Xog_tab_itm cur_tab = app.Gui_mgr().Browser_win().Active_tab(); + cur_tab.Html_box().Html_js_eval_script(Xow_popup_mgr_.Bld_js_cmd(js_wtr, cbk, mode, popup_itm.Page_href(), popup_itm.Popup_html())); + } + private void Show_popup_async() { + try { + synchronized (thread_lock) { + Xoae_page cur_page = app.Gui_mgr().Browser_win().Active_page(); + async_itm.Popup_html_(Get_popup_html(app.Gui_mgr().Browser_win().Active_wiki(), cur_page, async_itm)); + } + if (async_cmd_show == null) + async_cmd_show = app.Gui_mgr().Kit().New_cmd_sync(this); + Gfo_invk_.Invk_by_key(async_cmd_show, Invk_show_popup); + } + catch(Exception e) { + app.Usr_dlg().Warn_many("", "", "failed to get popup: href=~{0} err=~{1}", async_itm.Page_href(), Err_.Message_gplx_full(e)); + } + } + private void Show_popup() { + if (async_itm.Canceled()) return; + Show_popup_html(Cbk_xowa_popups_show_create, Bry_.Empty, async_itm); + } + public void Ns_allowed_(byte[] raw) { + ns_allowed_regy.Clear(); + Int_obj_ref[] ns_ids = Ns_allowed_parse(wiki, raw); + int len = ns_ids.length; + for (int i = 0; i < len; i++) { + Int_obj_ref ns_id = ns_ids[i]; + ns_allowed_regy.Add(ns_id, ns_id); + } + } + public static Int_obj_ref[] Ns_allowed_parse(Xowe_wiki wiki, byte[] raw) { + List_adp rv = List_adp_.New(); + byte[][] ary = Bry_split_.Split(raw, Byte_ascii.Pipe); + int ary_len = ary.length; + Xow_ns_mgr ns_mgr = wiki.Ns_mgr(); + for (int i = 0; i < ary_len; i++) { + byte[] bry = ary[i]; + int bry_len = bry.length; if (bry_len == 0) continue; // ignore empty entries; EX: "0|" + Xow_ns ns = Bry_.Eq(bry, Xow_ns_.Bry__main) + ? ns_mgr.Ns_main() + : ns_mgr.Names_get_or_null(bry) + ; + if (ns == null) { + wiki.Appe().Usr_dlg().Log_many("", "", "popup.ns_allowed: ns not in wiki: ns=~{0} wiki=~{1}", String_.new_u8(bry), wiki.Domain_str()); // ns may not be in wiki; EX: Portal and www.wikidata.org + continue; + } + Int_obj_ref ns_id_itm = Int_obj_ref.New(ns.Id()); + rv.Add(ns_id_itm); + } + return (Int_obj_ref[])rv.To_ary(Int_obj_ref.class); + } private Hash_adp ns_allowed_regy = Hash_adp_.New(); private Int_obj_ref ns_allowed_regy_key = Int_obj_ref.New_zero(); + private Xoae_page Cur_page() {return app.Gui_mgr().Browser_win().Active_page();} + private Xowe_wiki Cur_wiki() {return app.Gui_mgr().Browser_win().Active_tab().Wiki();} + private Xow_popup_itm Itms_get_or_null(Xoae_page page, String popup_id) {return (Xow_popup_itm)page.Popup_mgr().Itms().Get_by(popup_id);} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_show_popup_async)) Show_popup_async(); + else if (ctx.Match(k, Invk_show_popup)) Show_popup(); + + else if (ctx.Match(k, Cfg__enabled)) enabled = m.ReadYn("v"); + else if (ctx.Match(k, Cfg__show_init_word_count)) show_init_word_count = m.ReadInt("v"); + else if (ctx.Match(k, Cfg__show_more_word_count)) show_more_word_count = m.ReadInt("v"); + else if (ctx.Match(k, Cfg__show_all_if_less_than)) parser.Cfg().Show_all_if_less_than_(m.ReadInt("v")); + else if (ctx.Match(k, Cfg__read_til_stop_fwd)) parser.Cfg().Read_til_stop_fwd_(m.ReadInt("v")); + else if (ctx.Match(k, Cfg__read_til_stop_bwd)) parser.Cfg().Read_til_stop_bwd_(m.ReadInt("v")); + else if (ctx.Match(k, Cfg__stop_if_hdr_after)) parser.Cfg().Stop_if_hdr_after_(m.ReadInt("v")); + else if (ctx.Match(k, Cfg__tmpl_tkn_max)) parser.Tmpl_tkn_max_(m.ReadInt("v")); + else if (ctx.Match(k, Cfg__tmpl_keeplist)) parser.Tmpl_keeplist_init_(m.ReadBry("v")); + else if (ctx.Match(k, Cfg__ns_allowed)) Ns_allowed_(m.ReadBry("v")); + else if (ctx.Match(k, Cfg__xnde_ignore_ids)) parser.Wrdx_mkr().Xnde_ignore_ids_(m.ReadBry("v")); + else if (ctx.Match(k, Cfg__scan_len)) parser.Cfg().Tmpl_read_len_(m.ReadInt("v")); + else if (ctx.Match(k, Cfg__scan_max)) parser.Cfg().Tmpl_read_max_(m.ReadInt("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk_show_popup_async = "show_popup_async", Invk_show_popup = "show_popup"; + private static final String + Cbk_xowa_popups_show_update = "xowa_popups_show_update" + , Cbk_xowa_popups_show_create = "xowa_popups_show_create" + ; + private static final byte[] + Mode_show_more = Bry_.new_a7("more") + , Mode_show_all = Bry_.new_a7("all") + ; + private static final String + Cfg__enabled = "xowa.addon.popups.enabled" + , Cfg__show_init_word_count = "xowa.addon.popups.content.show_init_word_count" + , Cfg__show_more_word_count = "xowa.addon.popups.content.show_more_word_count" + , Cfg__show_all_if_less_than = "xowa.addon.popups.content.show_all_if_less_than" + , Cfg__read_til_stop_fwd = "xowa.addon.popups.content.read_til_stop_fwd" + , Cfg__read_til_stop_bwd = "xowa.addon.popups.content.read_til_stop_bwd" + , Cfg__stop_if_hdr_after = "xowa.addon.popups.content.stop_if_hdr_after" + , Cfg__tmpl_tkn_max = "xowa.addon.popups.wtxt.tmpl_tkn_max" + , Cfg__tmpl_keeplist = "xowa.addon.popups.wtxt.tmpl_keeplist" + , Cfg__ns_allowed = "xowa.addon.popups.ns_allowed" + , Cfg__xnde_ignore_ids = "xowa.addon.popups.content.xnde_ignore_ids" + , Cfg__scan_len = "xowa.addon.popups.scanner.scan_len" + , Cfg__scan_max = "xowa.addon.popups.scanner.scan_max" + ; + public static final String + Cfg__win_show_delay = "xowa.addon.popups.window.show_delay" + , Cfg__win_hide_delay = "xowa.addon.popups.window.hide_delay" + , Cfg__win_max_w = "xowa.addon.popups.window.max_w" + , Cfg__win_max_h = "xowa.addon.popups.window.max_h" + , Cfg__win_show_all_max_w = "xowa.addon.popups.window.show_all_max_w" + , Cfg__win_bind_focus_blur = "xowa.addon.popups.window.bind_focus_blur" + ; +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_mgr_.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_mgr_.java index a27517de8..32b1544b6 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_mgr_.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_mgr_.java @@ -13,3 +13,63 @@ 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.htmls.modules.popups; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; +import gplx.core.threads.*; import gplx.core.primitives.*; import gplx.core.js.*; +import gplx.xowa.wikis.nss.*; import gplx.xowa.specials.*; +class Xow_popup_mgr_ { + public static String Bld_js_cmd(Js_wtr js_wtr, String cbk, byte[] mode, byte[] href, byte[] html) { + js_wtr.Func_init(cbk); + js_wtr.Prm_bry(mode); + js_wtr.Prm_bry(href); + js_wtr.Prm_bry(html); + js_wtr.Func_term(); + return js_wtr.To_str_and_clear(); + } +} +class Load_popup_wkr implements Gfo_thread_wkr { + private Xow_popup_itm itm; private Xoae_page cur_page; private Xoa_url tmp_url; + private Hash_adp ns_allowed_regy; + private Int_obj_ref ns_allowed_regy_key = Int_obj_ref.New_zero(); + public Load_popup_wkr(Xowe_wiki wiki, Xoae_page cur_page, Xow_popup_itm itm, Xoa_url tmp_url, Hash_adp ns_allowed_regy, Int_obj_ref ns_allowed_regy_key) { + this.wiki = wiki; this.cur_page = cur_page; this.itm = itm; this.tmp_url = tmp_url; this.ns_allowed_regy = ns_allowed_regy; this.ns_allowed_regy_key = ns_allowed_regy_key; + } + public String Thread__name() {return "xowa.load_popup_wkr";} + public boolean Thread__resume() {return false;} + public Xowe_wiki Wiki() {return wiki;} private Xowe_wiki wiki; + public byte[] Rslt_bry() {return rslt_bry;} private byte[] rslt_bry; + public boolean Rslt_done() {return rslt_done;} private boolean rslt_done; + public void Rslt_(byte[] bry) {this.rslt_done = true; rslt_bry = bry;} + public void Thread__exec() { + Xoae_app app = wiki.Appe(); + try { + if (itm.Canceled()) return; + cur_page.Popup_mgr().Itms().Add_if_dupe_use_nth(itm.Popup_id(), itm); + app.Html__href_parser().Parse_as_url(tmp_url, itm.Page_href(), wiki, cur_page.Ttl().Full_url()); // NOTE: use Full_url, not Page_url, else anchors won't work for non-main ns; PAGE:en.w:Project:Sandbox; DATE:2014-08-07 + if (!Xoa_url_.Tid_is_pagelike(tmp_url.Tid())) return; // NOTE: do not get popups for "file:///"; DATE:2015-04-05 + Xowe_wiki popup_wiki = (Xowe_wiki)app.Wiki_mgr().Get_by_or_null(tmp_url.Wiki_bry()); + popup_wiki.Init_assert(); + Xoa_ttl popup_ttl = Xoa_ttl.Parse(popup_wiki, tmp_url.To_bry_page_w_anch()); + switch (popup_ttl.Ns().Id()) { + case Xow_ns_.Tid__media: + case Xow_ns_.Tid__file: + return; // do not popup for media or file + case Xow_ns_.Tid__special: + if (!Xow_special_meta_.Itm__popup_history.Match_ttl(popup_ttl)) return; // do not popup for special, unless popupHistory; DATE:2015-04-20 + break; + } + if (ns_allowed_regy.Count() > 0 && !ns_allowed_regy.Has(ns_allowed_regy_key.Val_(popup_ttl.Ns().Id()))) return; + itm.Init(popup_wiki.Domain_bry(), popup_ttl); + Xoae_page popup_page = popup_wiki.Data_mgr().Load_page_by_ttl(popup_ttl); + byte[] rv = popup_wiki.Html_mgr().Head_mgr().Popup_mgr().Parser().Parse(wiki, popup_page, cur_page.Tab_data().Tab(), itm); + Xow_popup_mgr.Update_progress_bar(app, wiki, cur_page, itm); + Rslt_(rv); + } + catch(Exception e) { + app.Usr_dlg().Warn_many("", "", "failed to get popup: href=~{0} err=~{1}", itm.Page_href(), Err_.Message_gplx_full(e)); + Rslt_(null); + } + finally { + app.Thread_mgr_old().Page_load_mgr().Resume(); + } + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_parser.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_parser.java index a27517de8..c5ddf4e95 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_parser.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_parser.java @@ -13,3 +13,250 @@ 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.htmls.modules.popups; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; +import gplx.core.btries.*; +import gplx.xowa.wikis.domains.*; +import gplx.xowa.apps.apis.xowa.html.modules.*; import gplx.xowa.htmls.modules.popups.keeplists.*; import gplx.xowa.htmls.core.htmls.*; import gplx.xowa.htmls.core.wkrs.hdrs.*; +import gplx.xowa.guis.views.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.hdrs.*; import gplx.xowa.parsers.tblws.*; import gplx.xowa.parsers.tmpls.*; +public class Xow_popup_parser { + private Xoae_app app; private Xowe_wiki wiki; private Xop_parser parser; + private Btrie_fast_mgr tmpl_trie, wtxt_trie; private Xop_tkn_mkr tkn_mkr; + private Xop_ctx tmpl_ctx; private Xop_root_tkn tmpl_root, wtxt_root; private Xot_compile_data tmpl_props = new Xot_compile_data(); + private Xoh_wtr_ctx hctx = Xoh_wtr_ctx.Popup; + private Xow_popup_anchor_finder hdr_finder = new Xow_popup_anchor_finder(); + private final Bry_bfr hdr_html_bfr = Bry_bfr_.New(); + public Xow_popup_cfg Cfg() {return cfg;} private Xow_popup_cfg cfg = new Xow_popup_cfg(); + public Xow_popup_wrdx_mkr Wrdx_mkr() {return wrdx_mkr;} private Xow_popup_wrdx_mkr wrdx_mkr = new Xow_popup_wrdx_mkr(); + public Xow_popup_html_mkr Html_mkr() {return html_mkr;} private Xow_popup_html_mkr html_mkr = new Xow_popup_html_mkr(); + public Xow_popup_parser_data Data() {return data;} private Xow_popup_parser_data data = new Xow_popup_parser_data(); + public Xop_keeplist_wiki Tmpl_keeplist() {return tmpl_keeplist;} private Xop_keeplist_wiki tmpl_keeplist; // private byte[] tmpl_keeplist_bry = Bry_.Empty; + public Xop_ctx Wtxt_ctx() {return wtxt_ctx;} private Xop_ctx wtxt_ctx; + public void Tmpl_tkn_max_(int v) { + if (v < 0) v = Int_.Max_value; // allow -1 as shortcut to deactivate + tmpl_ctx.Tmpl_tkn_max_(v); + wtxt_ctx.Tmpl_tkn_max_(v); + } + public void Init_by_wiki(Xowe_wiki wiki) { + this.wiki = wiki; this.app = wiki.Appe(); this.parser = wiki.Parser_mgr().Main(); this.tkn_mkr = app.Parser_mgr().Tkn_mkr(); + this.tmpl_ctx = Xop_ctx.New__top(wiki); this.wtxt_ctx = Xop_ctx.New__top(wiki); + Xop_lxr_mgr tmpl_lxr_mgr = Xop_lxr_mgr.Popup_lxr_mgr; + tmpl_lxr_mgr.Init_by_wiki(wiki); + this.tmpl_trie = tmpl_lxr_mgr.Trie(); this.wtxt_trie = parser.Wtxt_lxr_mgr().Trie(); + tmpl_ctx.Parse_tid_(Xop_parser_tid_.Tid__tmpl); wtxt_ctx.Parse_tid_(Xop_parser_tid_.Tid__wtxt); + tmpl_ctx.Xnde_names_tid_(Xop_parser_tid_.Tid__wtxt); + tmpl_ctx.Tid_is_popup_(true); wtxt_ctx.Tid_is_popup_(true); + tmpl_root = tkn_mkr.Root(Bry_.Empty); wtxt_root = tkn_mkr.Root(Bry_.Empty); + html_mkr.Ctor(app, wiki); + cfg.Ellipsis_(wiki.Msg_mgr().Val_by_key_obj(Xow_popup_cfg.Msg_key_ellipsis)); + } + public void Tmpl_keeplist_(Xop_keeplist_wiki v) {this.tmpl_keeplist = v;} + public void Tmpl_keeplist_init_(byte[] raw) { + if (tmpl_keeplist == null) { + tmpl_keeplist = new Xop_keeplist_wiki(wiki); + tmpl_ctx.Tmpl_keeplist_(tmpl_keeplist); + } + if (!Bry_.Has_at_end(raw, Byte_ascii.Nl_bry)) raw = Bry_.Add(raw, Byte_ascii.Nl_bry); + tmpl_keeplist.Srl().Load_by_bry(raw); + } + private boolean Canceled(Xow_popup_itm popup_itm, Xog_tab_itm cur_tab) {return popup_itm.Canceled() || cur_tab != null && cur_tab.Tab_is_loading();} + private void Init_ctxs(byte[] tmpl_src, Xoa_ttl ttl) { + tmpl_ctx.Clear_all(); + tmpl_ctx.Page().Ttl_(ttl); // NOTE: must set cur_page, else page-dependent templates won't work; EX: {{FULLPAGENAME}}; PAGE:en.w:June_20; DATE:2014-06-20 + tmpl_ctx.Page().Html_data().Html_restricted_(data.Html_restricted()); // NOTE: must set data.Html_restricted() if Special:XowaPopupHistory + tmpl_ctx.Parser__page_init(tmpl_root, tmpl_src); + Wtxt_ctx_init(true, tmpl_src); + wtxt_ctx.Page().Ttl_(ttl); // NOTE: must set cur_page, or rel lnkis won't work; EX: [[../A]] + } + public byte[] Parse(Xowe_wiki cur_wiki, Xoae_page page, Xog_tab_itm cur_tab, Xow_popup_itm popup_itm) { // NOTE: must pass cur_wiki for xwiki label; DATE:2014-07-02 + if (Bry_.Eq(popup_itm.Wiki_domain(), Xow_domain_itm_.Bry__wikidata)) { + data.Wrdx_bfr().Add(app.Wiki_mgr().Wdata_mgr().Popup_text(page)); + } + else { + byte[] tmpl_src = page.Db().Text().Text_bry(); int tmpl_len = tmpl_src.length; if (tmpl_len == 0) return Bry_.Empty; + int tmpl_bgn_orig = Xow_popup_parser_.Tmpl_bgn_get_(app, popup_itm, page.Ttl(), hdr_finder, tmpl_src, tmpl_len); + int tmpl_bgn = tmpl_bgn_orig; + int tmpl_read_len_cur = cfg.Tmpl_read_len(); + wrdx_mkr.Init(); + data.Init(cfg, popup_itm, tmpl_len); + Init_ctxs(tmpl_src, page.Ttl()); + while (data.Words_needed_chk()) { + if (Canceled(popup_itm, cur_tab)) return null; + tmpl_root.Clear(); + int tmpl_end = tmpl_bgn + tmpl_read_len_cur; if (tmpl_end > tmpl_len) tmpl_end = tmpl_len; // limit to tmpl_len; EX: page is 16 bytes, but block is 1024 + int new_tmpl_bgn = parser.Parse_to_stack_end(tmpl_root, tmpl_ctx, tkn_mkr, tmpl_src, tmpl_len, tmpl_trie, tmpl_bgn, tmpl_end); + if (Canceled(popup_itm, cur_tab)) return null; + byte[] wtxt_bry = Parse_to_wtxt(tmpl_src); + int wtxt_len = wtxt_bry.length; + wtxt_root.Clear(); + int wtxt_bgn = (tmpl_bgn == Xop_parser_.Doc_bgn_bos) ? Xop_parser_.Doc_bgn_bos : 0; // if first pass, parse from -1; needed for lxrs which assume nl at bos; EX: "*a" + if (Canceled(popup_itm, cur_tab)) return null; + parser.Parse_to_src_end(wtxt_root, wtxt_ctx, tkn_mkr, wtxt_bry, wtxt_trie, wtxt_bgn, wtxt_len); + if ( wtxt_ctx.Stack_len() > 0 // dangling lnki / hdr / tblw + && (tmpl_bgn + tmpl_read_len_cur) < data.Tmpl_max() // too much read; stop and give whatever's available; PAGE:en.w:List_of_air_forces; DATE:2014-06-18 + && tmpl_read_len_cur < tmpl_len // only reparse if tmpl_read_len_cur is < entire page; needed for pages which have dangling items; EX:"a" + ) { + new_tmpl_bgn = tmpl_bgn; + tmpl_read_len_cur = Xow_popup_parser_.Calc_read_len(wtxt_ctx, tmpl_read_len_cur, cfg.Tmpl_read_len(), tmpl_src, tmpl_bgn, tmpl_end); + wtxt_ctx.Clear_all(); + } + else { + wrdx_mkr.Process_tkn(cfg, data, data.Wrdx_bfr(), wtxt_root, wtxt_bry, wtxt_len); + tmpl_read_len_cur = cfg.Tmpl_read_len(); + } + tmpl_bgn = new_tmpl_bgn; + data.Tmpl_loop_count_add(); + if ( tmpl_bgn == tmpl_len // end of template + || tmpl_bgn - tmpl_bgn_orig > data.Tmpl_max() // too much read; stop and give whatever's available + ) + break; + } + if (Canceled(popup_itm, cur_tab)) return null; + Parse_wrdx_to_html(popup_itm, data.Wrdx_bfr()); + } + byte[] rv = html_mkr.Bld(cur_wiki, page, popup_itm, data.Wrdx_bfr()); + return (Canceled(popup_itm, cur_tab)) ? null : rv; + } + private void Parse_wrdx_to_html(Xow_popup_itm popup_itm, Bry_bfr wrdx_bfr) { + Adjust_wrdx_end(popup_itm, wrdx_bfr); + wrdx_bfr.Add(cfg.Notoc()); // always add notoc at end + byte[] wrdx_bry = wrdx_bfr.To_bry_and_clear(); + wtxt_root.Clear(); // now start parsing wrdx_bry from wtxt to html + Wtxt_ctx_init(false, wrdx_bry); + parser.Parse_to_src_end(wtxt_root, wtxt_ctx, tkn_mkr, wrdx_bry, wtxt_trie, Xop_parser_.Doc_bgn_bos, wrdx_bry.length); + wtxt_ctx.Parser__page_term(wtxt_root, wrdx_bry, wrdx_bry.length); + wiki.Html_mgr().Html_wtr().Write_doc(wrdx_bfr, wtxt_ctx, hctx, wrdx_bry, wtxt_root); + wiki.Parser_mgr().Uniq_mgr().Parse(wrdx_bfr); + } + private void Adjust_wrdx_end(Xow_popup_itm popup_itm, Bry_bfr wrdx_bfr) { + popup_itm.Words_found_(data.Words_found()); + if (popup_itm.Mode_all()) return; // mode_all needs no adjustments + Xow_popup_word[] words = data.Words_found_ary(); + int words_len = words.length; + int last_word_idx = -1; Xow_popup_word last_hdr_tkn = null; + int words_needed_val = data.Words_needed_val(); + if (cfg.Read_til_stop_fwd() != -1) { + for (int i = words_needed_val; i < words_len; ++i) { // find hdr after orig + Xow_popup_word word = words[i]; + if (word.Tid() == Xop_tkn_itm_.Tid_hdr) { + last_hdr_tkn = word; + break; + } + } + last_word_idx = (last_hdr_tkn == null) // no hdr found + ? words_needed_val - List_adp_.Base1 // get last word + : last_hdr_tkn.Idx() - 1 // get word before hdr + ; + if (last_word_idx >= words_len) + last_word_idx = -1; + } + boolean page_partially_parsed = data.Words_found() == data.Words_needed_max(); // adhoc way of figuring out if parsing prematurely stopped before eos; PAGE:en.q:Anaximander DATE:2014-07-02 + if ( cfg.Read_til_stop_bwd() != -1 + && page_partially_parsed // never read bwd if entire tmpl is read; DATE:2014-07-01 + ) { + int read_bwd_end = last_word_idx == -1 ? words_len - 1 : last_word_idx; // if !cfg.Read_til_stop_fwd() use last_wrd, else use read_fwd's last_word + int read_bwd_bgn = read_bwd_end - cfg.Read_til_stop_bwd(); + if (read_bwd_bgn > -1) { // handle pages with "==a==" near start + int last_hdr_idx = -1; + for (int i = read_bwd_end; i >= read_bwd_bgn; i--) { + Xow_popup_word word = words[i]; + if (word.Tid() == Xop_tkn_itm_.Tid_hdr) { + if (last_hdr_idx == -1) // last_hdr_idx not set + last_hdr_idx = i; // set it + else { // last_hdr_idx set + if (i + 1 == last_hdr_idx) // two consecutive hdrs; update earlier and continue + last_hdr_idx = i; + else // earlier hdr; ignore it and take later one + break; + } + last_hdr_tkn = word; + } + } + if (last_hdr_idx != -1) // hdr found + last_word_idx = last_hdr_idx - 1; // get word before last_word_idx + } + } + if (last_word_idx != -1) { + Xow_popup_word last_word = words[last_word_idx]; + wrdx_bfr.Delete_rng_to_end(last_word.Bfr_end());// delete everything after last_word + popup_itm.Words_found_(last_word_idx + List_adp_.Base1); // last_word_idx = 0 -> words_found = 1 + if (last_word.Tid() == Xop_tkn_itm_.Tid_hdr) // on odd case where hdr is still last word, add \n else text will literally be "==A==" b/c no trailing \n + wrdx_bfr.Add_byte_nl(); + } + if (last_hdr_tkn != null) { + wrdx_bfr.Trim_end(Byte_ascii.Nl); + + // reparse hdr b/c existing hdr_tkn has Src_bgn / Src_end, but no src; + byte[] hdr_src = Bry_.Mid(wrdx_bfr.Bfr(), last_hdr_tkn.Bfr_bgn(), last_hdr_tkn.Bfr_end()); + Xop_root_tkn hdr_root = wtxt_ctx.Tkn_mkr().Root(hdr_src); + wiki.Parser_mgr().Main().Parse_wtxt_to_wdom(hdr_root, wtxt_ctx, wtxt_ctx.Tkn_mkr(), hdr_src, 0); + byte[] last_hdr_bry = Bry_.Empty; + for (int i = 0; i < hdr_root.Subs_len(); ++i) { + Xop_tkn_itm sub = hdr_root.Subs_get(i); + if (sub.Tkn_tid() == Xop_tkn_itm_.Tid_hdr) { + last_hdr_bry = Xoh_hdr_html.Bld_hdr_html(hdr_html_bfr, wiki.Html_mgr().Html_wtr(), wtxt_ctx.Page(), wtxt_ctx, hctx, hdr_src, (Xop_hdr_tkn)sub); + break; + } + } +// byte[] last_hdr_bry = ((Xop_hdr_tkn)last_hdr_tkn.Tkn()).Hdr_html_text(); + html_mkr.Fmtr_next_sect().Bld_bfr_one(wrdx_bfr, last_hdr_bry); + } + else { + if (page_partially_parsed) + wrdx_bfr.Add(cfg.Ellipsis()); + } + } + private void Wtxt_ctx_init(boolean incremental, byte[] bry) { + wtxt_ctx.Clear_all(); + wtxt_ctx.Page().Html_data().Html_restricted_(data.Html_restricted()); + wtxt_ctx.Para().Enabled_(!incremental); // NOTE: if incremental, disable para; easier to work with \n rather than

    ; also, must be enabled before Page_bgn; DATE:2014-06-18DATE:2014-06-18 + wtxt_ctx.Lnke().Dangling_goes_on_stack_(incremental); + wtxt_ctx.Parser__page_init(wtxt_root, bry); + } + private byte[] Parse_to_wtxt(byte[] src) { + int subs_len = tmpl_root.Subs_len(); + for (int i = 0; i < subs_len; i++) + tmpl_root.Subs_get(i).Tmpl_compile(tmpl_ctx, src, tmpl_props); + return Xot_tmpl_wtr.Write_all(tmpl_ctx, Xot_invk_temp.Null_frame, tmpl_root, src); + } +} +class Xow_popup_parser_ { + public static int Tmpl_bgn_get_(Xoae_app app, Xow_popup_itm itm, Xoa_ttl page_ttl, Xow_popup_anchor_finder hdr_finder, byte[] src, int src_len) { + int rv = Xop_parser_.Doc_bgn_bos; if (itm.Mode_all()) return rv; + byte[] anch = itm.Page_href()[0] == Byte_ascii.Hash ? Bry_.Mid(gplx.langs.htmls.encoders.Gfo_url_encoder_.Href.Decode(itm.Page_href()), 1) : page_ttl.Anch_txt(); + if (anch == null) return rv; + int hdr_bgn = hdr_finder.Find(src, src_len, anch, rv); // NOTE: starting search from Xop_parser_.Doc_bgn_bos + return hdr_bgn == Bry_find_.Not_found ? rv : hdr_bgn; + } + public static int Calc_read_len(Xop_ctx ctx, int tmpl_read_cur, int tmpl_read_len, byte[] src, int bgn, int end) {// DATE:2014-07-19 + int rv_default = tmpl_read_cur + tmpl_read_len; + Xop_tkn_itm tkn = Get_expensive_dangling_tkn(ctx); + if (tkn == null) return rv_default; // no expensive tkns found; return rv_default; EX: headers are not considered expensive + int tkn_end = Calc_tkn_end(tkn, src, end); + if (tkn_end == Bry_find_.Not_found) return rv_default; // no end found; return rv_default; might want to return src.length at future date + return tkn_end - bgn; + } + private static Xop_tkn_itm Get_expensive_dangling_tkn(Xop_ctx ctx) { + int stack_len = ctx.Stack_len(); + if (stack_len == 0) return null; // no dangling tkns; just add tmpl_read_len; shouldn't happen, but keep prior behavior + for (int i = 0; i < stack_len; ++i) { + Xop_tkn_itm tkn = ctx.Stack_get(i); + switch (tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_tblw_tb: + return tkn; + } + } + return null; + } + private static int Calc_tkn_end(Xop_tkn_itm tkn, byte[] src, int pos) { + byte[] end_bry = null; + switch (tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_tblw_tb: // "{|" can be expensive; PAGE:en.w:List_of_countries_and_dependencies_by_area; DATE:2014-07-19 + end_bry = Xop_tblw_lxr.Hook_te; + break; + } + if (end_bry == null) return Bry_find_.Not_found; // no end defined for tkn; return null which should revert to dflt + int end_pos = Bry_find_.Find_fwd(src, end_bry, pos); + return end_pos == Bry_find_.Not_found ? Bry_find_.Not_found : end_pos + end_bry.length; + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_parser_data.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_parser_data.java index a27517de8..6af2ee70e 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_parser_data.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_parser_data.java @@ -13,3 +13,51 @@ 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.htmls.modules.popups; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; +import gplx.xowa.apps.apis.xowa.html.modules.*; +import gplx.xowa.parsers.*; +public class Xow_popup_parser_data { + public int Tmpl_max() {return tmpl_max;} private int tmpl_max; + public int Words_needed_val() {return words_needed_val;} private int words_needed_val; + public int Words_needed_max() {return words_needed_max;} private int words_needed_max; + private int words_needed_min; + public int Words_found() {return words_found;} private int words_found; + public Bry_bfr Wrdx_bfr() {return wrdx_bfr;} private Bry_bfr wrdx_bfr = Bry_bfr_.Reset(255); + public Xow_popup_word[] Words_found_ary() {return (Xow_popup_word[])words_found_list.To_ary_and_clear(Xow_popup_word.class);} private List_adp words_found_list = List_adp_.New(); + public int Tmpl_loop_count() {return tmpl_loop_count;} private int tmpl_loop_count; + public void Tmpl_loop_count_add() {++tmpl_loop_count;} + private Xow_popup_itm popup_itm; + public boolean Html_restricted() {return html_restricted;} private boolean html_restricted; + public void Init(Xow_popup_cfg cfg, Xow_popup_itm popup_itm, int tmpl_len) { + words_found = tmpl_loop_count= 0; + words_found_list.Clear(); + wrdx_bfr.Clear(); + + html_restricted = !gplx.xowa.specials.xowa.popup_history.Popup_history_page.Ttl_chk(popup_itm.Page_ttl()); + this.popup_itm = popup_itm; + if (tmpl_len < cfg.Show_all_if_less_than()) popup_itm.Mode_all_(); + words_needed_min = popup_itm.Words_found(); + words_needed_val = words_needed_max = popup_itm.Words_needed(); + switch (popup_itm.Mode()) { + case Xow_popup_itm.Mode_tid_all: + tmpl_max = Int_.Max_value; + break; + case Xow_popup_itm.Mode_tid_init: + case Xow_popup_itm.Mode_tid_more: + tmpl_max = cfg.Tmpl_read_max(); + if (cfg.Read_til_stop_fwd() > 0) + words_needed_max += cfg.Read_til_stop_fwd(); + break; + } + } + public boolean Words_needed_chk() {return words_found < words_needed_max;} + public void Words_found_add(Xop_tkn_itm tkn) { + words_found_list.Add(new Xow_popup_word(tkn.Tkn_tid(), wrdx_bfr.Len(), words_found, tkn.Src_bgn(), tkn.Src_end(), tkn)); + ++words_found; + } + public boolean Stop_if_hdr_after_chk(Xow_popup_cfg cfg) { + boolean rv = words_found > words_needed_min + cfg.Stop_if_hdr_after() && !popup_itm.Mode_all(); + if (rv) words_needed_max = words_found; + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_parser_tst.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_parser_tst.java index a27517de8..fa9e4b874 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_parser_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_parser_tst.java @@ -13,3 +13,508 @@ 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.htmls.modules.popups; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; +import org.junit.*; import gplx.core.primitives.*; +import gplx.xowa.apps.apis.xowa.html.modules.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.guis.views.*; +public class Xow_popup_parser_tst { + @Before public void init() {fxt.Clear();} private final Xop_popup_parser_fxt fxt = new Xop_popup_parser_fxt(); + @Test public void Text_chars_one() { + fxt.Test_parse + ( "a b c d", String_.Concat_lines_nl_skip_last + ( "

    a b" + , "

    " + )); + } + @Test public void Text_chars_many() { // PURPOSE: text.read_spans_scan + fxt.Test_parse + ( "abc def ghi", String_.Concat_lines_nl_skip_last + ( "

    abc def" + , "

    " + )); + } + @Test public void Text_chars_bound() {// PURPOSE: text.word_spans_scan + fxt.Test_parse + ( "abcde fghij k l", String_.Concat_lines_nl_skip_last + ( "

    abcde fghij" + , "

    " + )); + } + @Test public void Apos() { + fxt.Test_parse + ( "'''ab''' ''c'' de", String_.Concat_lines_nl_skip_last + ( "

    ab c" + , "

    " + , "" + )); + } + @Test public void Lnki() { + fxt.Test_parse("a [[b|c d e]] f" + , String_.Concat_lines_nl_skip_last + ( "

    a c d e" + , "

    " + )); + } + @Test public void Lnke_brack() { // PURPOSE: count lnke caption words; DATE:2014-06-20 + fxt.Init_tmpl_read_len_(32).Init_word_needed_(5).Test_parse + ( "a [http://b.org b c] d e f g", String_.Concat_lines_nl_skip_last + ( "

    a b c d e" + , "

    " + )); + } + @Test public void Lnke_text() { // PURPOSE: count entire lnke as one word + fxt.Init_tmpl_read_len_(32).Init_word_needed_(5).Test_parse + ( "a http://b.org c d e f g", String_.Concat_lines_nl_skip_last + ( "

    a http://b.org c d e" + , "

    " + )); + } + @Test public void Lnke_dangling() { // PURPOSE: handle dangling lnke; DATE:2014-06-20 + fxt.Test_parse + ( "a [http://b.org c d] e f g", String_.Concat_lines_nl_skip_last // NOTE: scan_len = 4, so 1st pass will be "a [h" + ( "

    a c d" // NOTE: (a) lnke correctly parsed, else would see "[" or "http"; (b) "c d" counts as 1 word + , "

    " + )); + } + @Test public void Hdr() { + fxt.Test_parse + ( "a\n===b===\n c", String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "

    b

    " + )); + } + @Test public void Hdr_one_word() { // PURPOSE: hdr.entire_tkn_counts_as_one_word + fxt.Test_parse + ( "===a b c===\nd", String_.Concat_lines_nl_skip_last + ( "

    a b c

    " + , "" + , "

    d" + , "

    " + )); + } + @Test public void Hdr_para() { // PURPOSE: hdr.para; handle para mode and hdr (para causes trailing \n to be para, not \n); PAGE:en.w:Flavius_Valerius_Severus DATE:2014-06-17 + fxt.Init_para_enabled_(true).Test_parse(String_.Concat_lines_nl_skip_last + ( "" + , "a" + , "" + , "==b==" + , "c" + , "" + , "d" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "

    b

    " + , "" + )); + } + @Test public void List() { + fxt.Test_parse(String_.Concat_lines_nl_skip_last + ( "a" + , "*b" + , "c" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
      " + , "
    • b" + , "
    • " + , "
    " + )); + } + @Test public void Xnde_pair() { + fxt.Test_parse + ( "b" + , String_.Concat_lines_nl_skip_last + ( "

    b" + , "

    " + )); + } + @Test public void Xnde_inline() { + fxt.Test_parse + ( "" + , String_.Concat_lines_nl_skip_last + ( "

    " + , "

    " + )); + } + @Test public void Xnde_br() { // PURPOSE: check that br is added correctly; PAGE:en.q:Earth; DATE:2014-06-30 + fxt.Init_word_needed_(3).Test_parse + ( "a
    b
    " + , String_.Concat_lines_nl_skip_last + ( "

    a
    b
    " + , "

    " + )); + } + @Test public void Xnde_math() { // PURPOSE: should be treated as one word; PAGE:en.w:System_of_polynomial_equations; DATE:2014-07-01 + fxt .Init_word_needed_(5) // need to read more words to pick up 1st word after header + .Init_read_til_stop_bwd_(2) // need to do read_bwd to start counting before ==e== into node + .Test_parse + ( "a b c d \n==e==\nf g h i" + , String_.Concat_lines_nl_skip_last + ( "

    a b c d (e)" // used to fail as

    a <math>b c d (e) + , "

    " + )); + } + @Test public void Ignore_tblw() {// also checks for tbl spanning multiple blocks; PAGE:en.w:Stratosphere; DATE:2014-06-17 + fxt.Test_parse(String_.Concat_lines_nl_skip_last + ( "a " + , "{|" + , "|-" + , "|b" + , "|} c" + ), String_.Concat_lines_nl_skip_last + ( "

    a c" + , "

    " + )); + } + @Test public void Ignore_tblw_nested() {// PAGE:en.w:Cosmoloyg; DATE:2014-06-17 + fxt.Test_parse(String_.Concat_lines_nl_skip_last + ( "a" + , "{|" + , "|-" + , "|b" + , "|}" + , "" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "" + , "

    " + )); + } + @Test public void Ignore_tblx() { + fxt.Test_parse + ( "a
    b
    c" + , String_.Concat_lines_nl_skip_last + ( "

    a c" + , "

    " + )); + } + @Test public void Ignore_ref() { + fxt.Test_parse + ( "a b c" + , String_.Concat_lines_nl_skip_last + ( "

    a c" + , "

    " + )); + } + @Test public void Ignore_div() { + fxt.Test_parse + ( "a
    b
    c" + , String_.Concat_lines_nl_skip_last + ( "

    a c" + , "

    " + )); + } + @Test public void Ignore_space_bos() { // pre. ignore spaces, else pre; PAGE:en.w:Volcano; en.w:War_elephant; DATE:2014-06-17 + fxt.Test_parse + ( "
    a
    b c d" // spaces before "b" are ignored + , String_.Concat_lines_nl_skip_last + ( "

    b c" + , "

    " + )); + } + @Test public void Ignore_space_nl() { + fxt.Test_parse(String_.Concat_lines_nl_skip_last + ( "a" + , "
    b
    c" // space before "c" is ignored + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "c" + , "

    " + )); + } + @Test public void Ignore_nl_bos() { + fxt.Test_parse(String_.Concat_lines_nl_skip_last + ( "" + , "" + , "a" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + )); + } + @Test public void Ignore_nl_multiple() { + fxt.Test_parse(String_.Concat_lines_nl_skip_last + ( "a" + , "" + , "" + , "" // ignored + , "b" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "

    b" + , "

    " + )); + } + @Test public void Ignore_nl_hdr() { + fxt.Test_parse(String_.Concat_lines_nl_skip_last + ( "a" + , "" + , "" + , "" // ignored + , "==b==" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "

    b

    " + )); + } + @Test public void Ignore_lnki_file() { + fxt.Test_parse + ( "a [[File:b.png|thumb]] c" + , String_.Concat_lines_nl_skip_last + ( "

    a c" + , "

    " + )); + } + @Test public void Ignore_gallery() { + fxt.Test_parse + ( "a File:B.png|c d" + , String_.Concat_lines_nl_skip_last + ( "

    a d" + , "

    " + )); + } + @Test public void Ignore_xnde() { + fxt.Test_parse + ( "a b c" + , String_.Concat_lines_nl_skip_last + ( "

    a c" + , "

    " + )); + } + @Test public void Dangling() { // make sure dangling nodes don't fail + fxt.Test_parse + ( "a" + , String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + )); + } + @Test public void End_early_dangling() { // PURPOSE: dangling tkn is too long; end early; PAGE:en.w:List_of_air_forces; DATE:2014-06-18 + fxt.Init_tmpl_read_max_(8).Test_parse + ( "a [[File:Test.png]] k" + , String_.Concat_lines_nl_skip_last + ( "

    a " + , "

    " + )); + } + @Test public void Ellipsis_() { + fxt.Init_ellipsis_("...").Test_parse + ( "a b c d" + , String_.Concat_lines_nl_skip_last + ( "

    a b..." + , "

    " + )); + fxt.Test_parse // no ellipsis: entire extract + ( "a" + , String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + )); + fxt.Test_parse // no ellipsis: entire extract multiple reads + ( "a
    b
    " + , String_.Concat_lines_nl_skip_last + ( "

    a " + , "

    " + )); + } + @Test public void Ns_allowed() { + fxt.Test_ns_allowed("Help" , Xow_ns_.Tid__help); + fxt.Test_ns_allowed("(Main)" , Xow_ns_.Tid__main); + fxt.Test_ns_allowed("" ); + fxt.Test_ns_allowed("(Main)|Help" , Xow_ns_.Tid__main, Xow_ns_.Tid__help); + } + @Test public void Read_til_stop_fwd() { + fxt.Init_word_needed_(2).Init_read_til_stop_fwd_(2) // read fwd found hdr + .Test_parse("a b c\n==d==", String_.Concat_lines_nl_skip_last + ( "

    a b c (d)" + , "

    " + )); + fxt.Init_word_needed_(2).Init_read_til_stop_fwd_(2) // read fwd did not find hdr; reset back to min + .Test_parse("a b c d", String_.Concat_lines_nl_skip_last + ( "

    a b" + , "

    " + )); + } + @Test public void Read_til_stop_bwd() { + fxt.Init_word_needed_(8).Init_read_til_stop_bwd_(4) // read bwd found hdr + .Test_parse("01 02 03 04 05\n==06==\n07 08 09 10 11 12 13 14 15 16", String_.Concat_lines_nl_skip_last + ( "

    01 02 03 04 05 (06)" + , "

    " + )); + fxt.Init_tmpl_read_len_(40).Init_word_needed_(5).Init_read_til_stop_bwd_(3) // read bwd at eos should not return "next_sect"; DATE:2014-07-01 + .Test_parse("01 02 03 \n==04==\n", String_.Concat_lines_nl_skip_last + ( "

    01 02 03 " + , "

    " + , "" + , "

    04

    " + )); + } + @Test public void Stop_if_hdr_after() { + fxt.Init_word_needed_(5).Init_stop_if_hdr_after_(1) + .Test_parse("a b\n==c==\nd e", String_.Concat_lines_nl_skip_last + ( "

    a b" + , "

    " + , "" + , "

    c

    " + )); + } + @Test public void Anchor() { + fxt.Test_parse(String_.Concat_lines_nl_skip_last + ( "a b c d" + , "" + , "== e ==" + , "f g h i" + ), "#e", String_.Concat_lines_nl_skip_last + ( "

    e

    " + , "" + , "

    f" + , "

    " + )); + } + @Test public void Anchor_underline() { + fxt.Test_parse(String_.Concat_lines_nl_skip_last + ( "a b c d" + , "" + , "== e f ==" + , "g h i" + ), "#e_f", String_.Concat_lines_nl_skip_last + ( "

    e f

    " + , "" + , "

    g" + , "

    " + )); + } + @Test public void Tmpl_tkn_max() { + fxt.Init_tmpl_tkn_max_(5).Init_page("Template:A", "a"); // eval + fxt.Test_parse + ( "{{A}}" + , String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + )); + fxt.Test_parse("{{A|b|c}}" , ""); // skip; NOTE: output should be blank, not

    \n

    ; PAGE:en.w:List_of_countries_by_GDP_(PPP); DATE:2014-07-01 + } + @Test public void Tmpl_tkn_max__comment_and_tblw() { // PURPOSE: garbled popup when tmpl_tkn_max is set and comments in front of tblw; PAGE:en.w:Gwynedd; DATE:2014-07-01 + fxt .Init_tmpl_tkn_max_(5) // set tmpl_tkn_max + .Init_tmpl_read_len_(20) // set read_len to 20 (must read entire "\n{|" at once + .Test_parse(String_.Concat_lines_nl_skip_last + ( "{{A|b}}" + , "{{A|b}}" + , "{|" + , "|-" + , "|a b c d" + , "|}" + ), ""); // should be blank, not ] + } + @Test public void Tmpl_tkn_max__apos() { // PURPOSE: handle apos around skipped tmpl token; PAGE:en.w:Somalia; DATE:2014-07-02 + fxt.Init_tmpl_tkn_max_(5).Test_parse("a''{{A|b}}''b", String_.Concat_lines_nl_skip_last + ( "

    a b" + , "

    " + )); + } + @Test public void Notoc_and_para_issue() { // PURPOSE.fix: issue with "\s__NOTOC__" and "a\n"b; PAGE:en.w:Spain; DATE:2014-07-05 + fxt.Init_word_needed_(3).Init_notoc_(" __NOTOC__").Test_parse("a\nb", String_.Concat_lines_nl_skip_last + ( "

    a" // was

    a

    b + , "b " + , "

    " + )); + } + @Test public void Test_Assert_at_end() { + fxt.Test_Assert_at_end("a" , "a\n"); // add one + fxt.Test_Assert_at_end("a\n" , "a\n"); // noop + fxt.Test_Assert_at_end("a\n\n\n" , "a\n"); // remove til one + fxt.Test_Assert_at_end("" , ""); // empty check + } + @Test public void Skip_to_end__tblw() { // PURPOSE: skip to end of tblw; PAGE:en.w:List_of_countries_and_dependencies_by_area; DATE:2014-07-19 + fxt.Init_tmpl_read_len_(4).Test_parse + ( String_.Concat_lines_nl_skip_last + ( "a" + , "{|" + , "|-" + , "|b" + , "|c" + , "|d" + , "|}" + ) + , String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + )); + fxt.Expd_tmpl_loop_count(2); + } +} +class Xop_popup_parser_fxt { + private Xow_popup_parser parser; private Xowe_wiki wiki; + private int word_min = 2; + public void Clear() { + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + this.wiki = Xoa_app_fxt.Make__wiki__edit(app, "en.wiki"); + parser = wiki.Html_mgr().Head_mgr().Popup_mgr().Parser(); + parser.Init_by_wiki(wiki); + parser.Cfg().Tmpl_read_len_(4); + parser.Cfg().Tmpl_read_max_(32 * Io_mgr.Len_kb); + parser.Cfg().Ellipsis_(Bry_.Empty); + parser.Cfg().Notoc_(Bry_.Empty); + parser.Cfg().Show_all_if_less_than_(-1); + parser.Cfg().Read_til_stop_fwd_(-1); + parser.Cfg().Read_til_stop_bwd_(-1); + parser.Cfg().Stop_if_hdr_after_(-1); + parser.Html_mkr().Fmtr_popup().Fmt_("~{content}"); + parser.Html_mkr().Output_js_clean_(false); + parser.Html_mkr().Output_tidy_(false); + parser.Html_mkr().Fmtr_next_sect().Fmt_(" (~{next_sect_val})"); + parser.Wrdx_mkr().Xnde_ignore_ids_(Bry_.new_a7("coordinates")); + word_min = 2; + } + public Xop_popup_parser_fxt Init_notoc_(String v) {parser.Cfg().Notoc_(Bry_.new_u8(v)); return this;} + public Xop_popup_parser_fxt Init_tmpl_read_len_(int v) {parser.Cfg().Tmpl_read_len_(v); return this;} + public Xop_popup_parser_fxt Init_tmpl_read_max_(int v) {parser.Cfg().Tmpl_read_max_(v); return this;} + public Xop_popup_parser_fxt Init_word_needed_(int v) {word_min = v; return this;} + public Xop_popup_parser_fxt Init_para_enabled_(boolean v) {parser.Wtxt_ctx().Para().Enabled_(v); return this;} + public Xop_popup_parser_fxt Init_ellipsis_(String v) {parser.Cfg().Ellipsis_(Bry_.new_u8(v)); return this;} + public Xop_popup_parser_fxt Init_read_til_stop_fwd_(int v) {parser.Cfg().Read_til_stop_fwd_(v); return this;} + public Xop_popup_parser_fxt Init_read_til_stop_bwd_(int v) {parser.Cfg().Read_til_stop_bwd_(v); return this;} + public Xop_popup_parser_fxt Init_stop_if_hdr_after_(int v) {parser.Cfg().Stop_if_hdr_after_(v); return this;} + public Xop_popup_parser_fxt Init_tmpl_tkn_max_(int v) {parser.Tmpl_tkn_max_(v); return this;} + public Xop_popup_parser_fxt Init_fmtr_next_sect_(String v) {parser.Html_mkr().Fmtr_next_sect().Fmt_(v); return this;} + public Xop_popup_parser_fxt Init_page(String ttl, String txt) {Xop_fxt.Init_page_create_static(wiki, ttl, txt); return this;} + public Xop_popup_parser_fxt Expd_tmpl_loop_count(int expd) {Tfds.Eq(expd, parser.Data().Tmpl_loop_count()); return this;} + public Xop_popup_parser_fxt Test_ns_allowed(String raw, int... expd) { + Int_obj_ref[] ids = Xow_popup_mgr.Ns_allowed_parse(wiki, Bry_.new_u8(raw)); + Tfds.Eq_ary(expd, To_int_ary(ids)); + return this; + } + private static int[] To_int_ary(Int_obj_ref[] ary) { + int len = ary.length; + int[] rv = new int[len]; + for (int i = 0; i < len; ++i) + rv[i] = ary[i].Val(); + return rv; + } + public void Test_parse(String raw, String expd) {Test_parse(raw, "Test_1", expd);} + public void Test_parse(String raw, String ttl, String expd) { + Xoae_page page = Xoae_page.New_edit(wiki, Xoa_ttl.Parse(wiki, Bry_.new_a7(ttl))); + page.Db().Text().Text_bry_(Bry_.new_u8(raw)); + Xow_popup_itm itm = new Xow_popup_itm(1, Bry_.new_u8(raw), Bry_.Empty, word_min); + itm.Init(wiki.Domain_bry(), page.Ttl()); + byte[] actl = parser.Parse(wiki, page, null, itm); + Tfds.Eq_str_lines(expd, String_.new_u8(actl)); + } + public void Test_Assert_at_end(String raw, String expd) { + if (test_bfr == null) test_bfr = Bry_bfr_.New(); + test_bfr.Clear().Add_str_u8(raw); + Bry_bfr_.Assert_at_end(test_bfr, Byte_ascii.Nl); + Tfds.Eq(expd, test_bfr.To_str_and_clear()); + } private Bry_bfr test_bfr; +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_word.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_word.java index a27517de8..c159350dc 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_word.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_word.java @@ -13,3 +13,16 @@ 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.htmls.modules.popups; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; +import gplx.xowa.parsers.*; +public class Xow_popup_word { + public Xow_popup_word(int tid, int bfr_bgn, int idx, int bgn, int end, Xop_tkn_itm tkn) {this.tid = tid; this.bfr_bgn = bfr_bgn; this.idx = idx; this.bgn = bgn; this.end = end; this.tkn = tkn;} + public int Tid() {return tid;} private int tid; + public int Bfr_bgn() {return bfr_bgn;} private int bfr_bgn; + public int Bfr_end() {return bfr_bgn + this.Len();} + public int Idx() {return idx;} private int idx; + public int Bgn() {return bgn;} private int bgn; + public int End() {return end;} private int end; + public int Len() {return end - bgn;} + public Xop_tkn_itm Tkn() {return tkn;} private Xop_tkn_itm tkn; +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_wrdx_mkr.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_wrdx_mkr.java index a27517de8..77d0a1f1d 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_wrdx_mkr.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/Xow_popup_wrdx_mkr.java @@ -13,3 +13,190 @@ 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.htmls.modules.popups; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; +import gplx.langs.htmls.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.lnkes.*; import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.htmls.*; import gplx.xowa.parsers.lnkis.*; +public class Xow_popup_wrdx_mkr { + private boolean skip_space; + private Xop_tkn_itm prv_tkn_seen, prv_tkn_added; + public Hash_adp_bry Xnde_id_ignore_list() {return xnde_id_ignore_list;} private Hash_adp_bry xnde_id_ignore_list = Hash_adp_bry.ci_a7(); + public void Init() { + skip_space = false; + prv_tkn_seen = prv_tkn_added = null; + } + public void Process_tkn(Xow_popup_cfg cfg, Xow_popup_parser_data data, Bry_bfr wrdx_bfr, Xop_tkn_itm tkn, byte[] wtxt_bry, int wtxt_len) { + boolean add_tkn = true, add_subs = true; Xop_xnde_tkn xnde = null; + int tkn_src_bgn = tkn.Src_bgn(), tkn_src_end = tkn.Src_end(); + prv_tkn_seen = tkn; + switch (tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_root: + add_tkn = false; // don't add_tkn root + break; + case Xop_tkn_itm_.Tid_txt: + data.Words_found_add(tkn); + break; + case Xop_tkn_itm_.Tid_apos: + if ( prv_tkn_added != null + && prv_tkn_seen != prv_tkn_added // prv seen tkn was skipped + && prv_tkn_added.Tkn_tid() == Xop_tkn_itm_.Tid_apos // prv added tkn was apos + ) + wrdx_bfr.Add_byte_space(); // prv && cur are apos, but something was skipped inbetween; add a space so that apos doesn't combine EX:''{{skip}}'' x> ''''; PAGE:en.w:Somalia; DATE:2014-07-02 + break; + case Xop_tkn_itm_.Tid_ignore: // always skip ignores, particularly comments; PAGE:en.w:List_of_countries_by_GDP_(PPP); DATE:2014-07-01 + case Xop_tkn_itm_.Tid_tblw_tb: case Xop_tkn_itm_.Tid_tblw_tc: case Xop_tkn_itm_.Tid_tblw_td: + case Xop_tkn_itm_.Tid_tblw_te: case Xop_tkn_itm_.Tid_tblw_th: case Xop_tkn_itm_.Tid_tblw_tr: + add_tkn = add_subs = false; // skip tblws + break; + case Xop_tkn_itm_.Tid_xnde: + xnde = (Xop_xnde_tkn)tkn; + switch (xnde.Tag().Id()) { + case Xop_xnde_tag_.Tid__div: + case Xop_xnde_tag_.Tid__table: case Xop_xnde_tag_.Tid__tr: case Xop_xnde_tag_.Tid__td: case Xop_xnde_tag_.Tid__th: + case Xop_xnde_tag_.Tid__caption: case Xop_xnde_tag_.Tid__thead: case Xop_xnde_tag_.Tid__tfoot: case Xop_xnde_tag_.Tid__tbody: + case Xop_xnde_tag_.Tid__ref: case Xop_xnde_tag_.Tid__gallery: case Xop_xnde_tag_.Tid__imageMap: case Xop_xnde_tag_.Tid__timeline: + case Xop_xnde_tag_.Tid__xowa_wiki_setup: + case Xop_xnde_tag_.Tid__xowa_html: // needed for Help:Options, else \n at top of doc; DATE:2014-06-22 + add_tkn = add_subs = false; // skip tblxs + xnde = null; + break; + case Xop_xnde_tag_.Tid__math: // add as one unit; PAGE:en.w:System_of_polynomial_equations DATE:2014-07-01 + add_subs = false; // never recur + xnde = null; + data.Words_found_add(tkn); // treat it as one word + break; + case Xop_xnde_tag_.Tid__br: + add_tkn = false; // never add_tkn Src_bgn / Src_end; note add_subs should still be true; PAGE:en.q:Earth; DATE:2014-06-30 + if (wrdx_bfr.Len_eq_0()) // don't add
    to start of document; needed for Help:Options, but good to have everywhere; DATE:2014-06-22 + add_subs = false; + break; + default: + add_tkn = false; // don't add_tkn xnde, but still add_subs + if (Xnde_id_ignore_list_chk(xnde, wtxt_bry)) { + add_subs = false; + xnde = null; + } + break; + } + break; + case Xop_tkn_itm_.Tid_lnke: + Xop_lnke_tkn lnke = (Xop_lnke_tkn)tkn; + switch (lnke.Lnke_typ()) { + case Xop_lnke_tkn.Lnke_typ_brack: + Process_subs(cfg, data, wrdx_bfr, tkn, wtxt_bry, wtxt_len, Bool_.N); // add subs which are caption tkns; note that Bool_.N will add all words so that captions don't get split; EX: "a [http://a.org b c d]" -> "a b c d" if words_needed == 2; + add_tkn = add_subs = false; // ignore lnke, but add any text tkns; EX: [http://a.org b c d] -> "b c d" + break; + case Xop_lnke_tkn.Lnke_typ_text: + data.Words_found_add(tkn); // increment words_found; EX: a http://b.org c -> 3 words; + break; + } + break; + case Xop_tkn_itm_.Tid_lnki: + Xop_lnki_tkn lnki = (Xop_lnki_tkn)tkn; + switch (lnki.Ns_id()) { + case Xow_ns_.Tid__category: // skip [[Category:]] + case Xow_ns_.Tid__file: // skip [[File:]] + add_tkn = add_subs = false; + break; + default: + data.Words_found_add(tkn); // increment words_found; EX: a [[B|c d e]] f -> 3 words; + break; + } + break; + case Xop_tkn_itm_.Tid_space: + if ( skip_space // previous tkn skipped add and set skip_space to true + && wrdx_bfr.Match_end_byt_nl_or_bos() // only ignore space if it will cause pre; note that some s will have spaces that should be preserved; EX:"ab c"; PAGE:en.w:Mehmed_the_Conqueror; DATE:2014-06-18 + ) + add_tkn = false; // skip ws + break; + case Xop_tkn_itm_.Tid_newLine: { + // heuristic to handle skipped
    /
    which does not skip \n; EX:"
    a
    \nb"; div is skipped, but "\n" remains; PAGE:en.w:Eulogy;DATE:2014-06-18 + int wrdx_bfr_len = wrdx_bfr.Len(); + if (wrdx_bfr_len == 0) // don't add_tkn \n at bos; does not handle pages where bos intentionally has multiple \n\n + add_tkn = false; + else if (wrdx_bfr_len > 2) { // bounds check + if (Wtxt_bfr_ends_w_2_nl(wrdx_bfr, wrdx_bfr_len)) // don't add \n if "\n\n"; does not handle intentional sequences of 2+ \n; + add_tkn = false; + } + break; + } + case Xop_tkn_itm_.Tid_hdr: { + data.Words_found_add(tkn); // count entire header as one word; not worth counting words in header + add_subs = false; // add entire tkn; do not add_subs + int wrdx_bfr_len = wrdx_bfr.Len(); + if (wrdx_bfr_len > 2) { // bounds check + if (Wtxt_bfr_ends_w_2_nl(wrdx_bfr, wrdx_bfr_len)) // heuristic: 2 \n in bfr, and about to add a hdr tkn which starts with "\n"; delete last \n + wrdx_bfr.Del_by_1(); + } + if ( tkn_src_end < wtxt_len // bounds check + && wtxt_bry[tkn_src_end] == Byte_ascii.Nl // hdr_tkn will not include trailing "\n". add it; note that this behavior is by design. NOTE:hdr.trailing_nl; DATE:2014-06-17 + ) { + wrdx_bfr.Add_mid(wtxt_bry, tkn_src_bgn, tkn_src_end + 1); // +1 to add the trailing \n + add_tkn = false; + } + break; + } + default: + break; + } + skip_space = false; // always reset; only used once above for Tid_space; DATE:2014-06-17 + if (add_tkn && xnde == null) { + if (tkn_src_end - tkn_src_bgn > 0) { // handle paras which have src_bgn == src_end + wrdx_bfr.Add_mid(wtxt_bry, tkn_src_bgn, tkn_src_end); + prv_tkn_added = tkn; + } + } + else // tkn not added + skip_space = true; // skip next space; note this is done with member variable to handle recursive iteration; DATE:2014-06-17 + if (add_subs) { + if (xnde != null) wrdx_bfr.Add_mid(wtxt_bry, xnde.Tag_open_bgn(), xnde.Tag_open_end()); // add open tag; EX: "" + Process_subs(cfg, data, wrdx_bfr, tkn, wtxt_bry, wtxt_len, Bool_.Y); + if (xnde != null) wrdx_bfr.Add_mid(wtxt_bry, xnde.Tag_close_bgn(), xnde.Tag_close_end()); // add close tag; EX: "" + } + switch (tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_hdr: + if ( cfg.Stop_if_hdr_after_enabled() + && data.Stop_if_hdr_after_chk(cfg)) + return; + break; + } + } + private void Process_subs(Xow_popup_cfg cfg, Xow_popup_parser_data data, Bry_bfr wrdx_bfr, Xop_tkn_itm tkn, byte[] wtxt_bry, int wtxt_len, boolean chk_words_found) { + int subs_len = tkn.Subs_len(); + for (int i = 0; i < subs_len; i++) { + Xop_tkn_itm sub = tkn.Subs_get(i); + Process_tkn(cfg, data, wrdx_bfr, sub, wtxt_bry, wtxt_len); + if (chk_words_found && !data.Words_needed_chk()) break; + } + } + private boolean Xnde_id_ignore_list_chk(Xop_xnde_tkn xnde, byte[] src) { + Mwh_atr_itm[] atrs_ary = xnde.Atrs_ary(); + int atrs_len = atrs_ary.length; + for (int i = 0; i < atrs_len; i++) { + Mwh_atr_itm atr = atrs_ary[i]; + if ( Bry_.Eq(atr.Key_bry(), Gfh_atr_.Bry__id) + && xnde_id_ignore_list.Get_by_bry(atr.Val_as_bry()) != null + ) { + return true; + } + } + return false; + } + public void Xnde_ignore_ids_(byte[] xnde_id_ignore_bry) { + byte[][] ary = Bry_split_.Split(xnde_id_ignore_bry, Byte_ascii.Pipe); + int ary_len = ary.length; + xnde_id_ignore_list.Clear(); + for (int i = 0; i < ary_len; i++) { + byte[] bry = ary[i]; + if (bry.length == 0) continue; // ignore empty entries; EX: "a|" + xnde_id_ignore_list.Add(bry, bry); + } + } + private boolean Wtxt_bfr_ends_w_2_nl(Bry_bfr wrdx_bfr, int wrdx_bfr_len) { + byte[] hdom_bfr_bry = wrdx_bfr.Bfr(); + return + ( hdom_bfr_bry[wrdx_bfr_len - 1] == Byte_ascii.Nl // prv 2 bytes are \n + && hdom_bfr_bry[wrdx_bfr_len - 2] == Byte_ascii.Nl + ); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/keeplists/Xop_keeplist_rule.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/keeplists/Xop_keeplist_rule.java index a27517de8..31590b3a2 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/keeplists/Xop_keeplist_rule.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/keeplists/Xop_keeplist_rule.java @@ -13,3 +13,33 @@ 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.htmls.modules.popups.keeplists; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; import gplx.xowa.htmls.modules.popups.*; +import gplx.langs.regxs.*; +public class Xop_keeplist_rule { + private Gfo_pattern[] excludes; private int excludes_len; + public Xop_keeplist_rule(Gfo_pattern[] includes, Gfo_pattern[] excludes) { + this.includes = includes; this.includes_len = includes.length; + this.excludes = excludes; this.excludes_len = excludes.length; + } + public Gfo_pattern[] Includes() {return includes;} private Gfo_pattern[] includes; private int includes_len; + public boolean Match(byte[] ttl) { + boolean match_found = false; + for (int i = 0; i < includes_len; ++i) { + Gfo_pattern skip = includes[i]; + if (skip.Match(ttl)) { + match_found = true; + break; + } + } + if (match_found) { + for (int i = 0; i < excludes_len; ++i) { + Gfo_pattern keep = excludes[i]; + if (keep.Match(ttl)) { + match_found = false; + break; + } + } + } + return match_found; + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/keeplists/Xop_keeplist_wiki.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/keeplists/Xop_keeplist_wiki.java index a27517de8..fbddbf9d5 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/keeplists/Xop_keeplist_wiki.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/keeplists/Xop_keeplist_wiki.java @@ -13,3 +13,35 @@ 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.htmls.modules.popups.keeplists; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; import gplx.xowa.htmls.modules.popups.*; +import gplx.langs.regxs.*; +public class Xop_keeplist_wiki { + public Xop_keeplist_wiki(Xowe_wiki wiki) { + srl = new Xop_keeplist_wiki_srl(wiki); + } + public boolean Enabled() {return enabled;} public void Enabled_(boolean v) {enabled = v;} private boolean enabled = false; // NOTE: default to false, b/c wikis that are not listed in cfg will not call Rules_seal + public Xop_keeplist_rule[] Rules() {return rules;} private Xop_keeplist_rule[] rules; private int rules_len; + public Xop_keeplist_wiki_srl Srl() {return srl;} private Xop_keeplist_wiki_srl srl; + public void Rules_add(Xop_keeplist_rule rule) {rules_list.Add(rule);} private List_adp rules_list = List_adp_.New(); + public void Rules_seal() { + this.rules = (Xop_keeplist_rule[])rules_list.To_ary_and_clear(Xop_keeplist_rule.class); + this.rules_len = rules.length; + if (rules_len == 0) return; + if (rules_len == 1) { + Xop_keeplist_rule rule_0 = rules[0]; + if (rule_0.Includes().length == 0) + enabled = false; + else + enabled = true; + } + else + enabled = true; + } + public boolean Match(byte[] ttl) { + for (int i = 0; i < rules_len; ++i) { + Xop_keeplist_rule rule = rules[i]; + if (rule.Match(ttl)) return true; + } + return false; + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/keeplists/Xop_keeplist_wiki_srl.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/keeplists/Xop_keeplist_wiki_srl.java index a27517de8..1ee5a5cf4 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/keeplists/Xop_keeplist_wiki_srl.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/keeplists/Xop_keeplist_wiki_srl.java @@ -13,3 +13,52 @@ 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.htmls.modules.popups.keeplists; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; import gplx.xowa.htmls.modules.popups.*; +import gplx.langs.dsvs.*; +import gplx.langs.regxs.*; +import gplx.xowa.langs.cases.*; +public class Xop_keeplist_wiki_srl extends Dsv_wkr_base { + private Xol_case_mgr case_mgr; private Xowe_wiki wiki; + private byte[] wiki_bry; + private byte[] keeps_bry; + private byte[] skips_bry; + private int rules_count; + public Xop_keeplist_wiki_srl(Xowe_wiki wiki) {this.wiki = wiki; this.case_mgr = wiki.Lang().Case_mgr();} + @Override public Dsv_fld_parser[] Fld_parsers() {return new Dsv_fld_parser[] {Dsv_fld_parser_.Bry_parser, Dsv_fld_parser_.Bry_parser, Dsv_fld_parser_.Bry_parser};} + @Override public boolean Write_bry(Dsv_tbl_parser parser, int fld_idx, byte[] src, int bgn, int end) { + switch (fld_idx) { + case 0: wiki_bry = Xoa_ttl.Replace_spaces(case_mgr.Case_build_lower(Bry_.Mid(src, bgn, end))); return true; + case 1: keeps_bry = Xoa_ttl.Replace_spaces(case_mgr.Case_build_lower(Bry_.Mid(src, bgn, end))); return true; + case 2: skips_bry = Xoa_ttl.Replace_spaces(case_mgr.Case_build_lower(Bry_.Mid(src, bgn, end))); return true; + default: return false; + } + } + @Override public void Commit_itm(Dsv_tbl_parser parser, int pos) { + if (wiki_bry == null) throw parser.Err_row_bgn("wikis missing", pos); + if (keeps_bry == null) throw parser.Err_row_bgn("keeps missing", pos); + if (skips_bry == null) throw parser.Err_row_bgn("skips missing", pos); + if (!Bry_.Eq(wiki_bry, wiki.Domain_bry())) return; + Xop_keeplist_wiki tmpl_keeplist = Get_tmpl_keeplist(); + Gfo_pattern[] keeps = Gfo_pattern.Parse_to_ary(keeps_bry); + Gfo_pattern[] skips = Gfo_pattern.Parse_to_ary(skips_bry); + Xop_keeplist_rule rule = new Xop_keeplist_rule(keeps, skips); + tmpl_keeplist.Rules_add(rule); + wiki_bry = skips_bry = keeps_bry = null; + ++rules_count; + } + @Override public void Load_by_bry_end() { + if (rules_count == 0) return; // NOTE: keeplist set in global cfg, so fires when each wiki loads; if loading wiki does not match keeplist, then noop; DATE:2014-07-05 + Xop_keeplist_wiki tmpl_keeplist = Get_tmpl_keeplist(); + tmpl_keeplist.Rules_seal(); + rules_count = 0; + } + public Xop_keeplist_wiki Get_tmpl_keeplist() { + Xow_popup_parser popup_parser = wiki.Html_mgr().Head_mgr().Popup_mgr().Parser(); + Xop_keeplist_wiki rv = popup_parser.Tmpl_keeplist(); + if (rv == null) { + rv = new Xop_keeplist_wiki(wiki); + popup_parser.Tmpl_keeplist_(rv); + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/modules/popups/keeplists/Xop_keeplist_wiki_tst.java b/400_xowa/src/gplx/xowa/htmls/modules/popups/keeplists/Xop_keeplist_wiki_tst.java index a27517de8..d65e749ad 100644 --- a/400_xowa/src/gplx/xowa/htmls/modules/popups/keeplists/Xop_keeplist_wiki_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/modules/popups/keeplists/Xop_keeplist_wiki_tst.java @@ -13,3 +13,47 @@ 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.htmls.modules.popups.keeplists; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.modules.*; import gplx.xowa.htmls.modules.popups.*; +import org.junit.*; +public class Xop_keeplist_wiki_tst { + @Before public void init() {fxt.Clear();} private Xop_keeplist_wiki_fxt fxt = new Xop_keeplist_wiki_fxt(); + @Test public void Tmpl_keeplist() { + Xop_keeplist_wiki keeplist_wiki = fxt.keeplist_wiki_(String_.Concat_lines_nl + ( "enwiki|a*|abc*" + )); + fxt.Test_Match_y(keeplist_wiki, "a", "ab"); + fxt.Test_Match_n(keeplist_wiki, "abc", "abcd", "d"); + } + @Test public void Tmpl_keeplist2() { + Xop_keeplist_wiki keeplist_wiki = fxt.keeplist_wiki_(String_.Concat_lines_nl + ( "enwiki|a*|abc*" + , "enwiki|b*|*xyz" + )); + fxt.Test_Match_y(keeplist_wiki, "a", "ab"); + fxt.Test_Match_n(keeplist_wiki, "d", "abc", "abcd"); + fxt.Test_Match_y(keeplist_wiki, "b", "bxy"); + fxt.Test_Match_n(keeplist_wiki, "bxyz", "bcdxyz"); + } +} +class Xop_keeplist_wiki_fxt { + public void Clear() { + } + public Xop_keeplist_wiki keeplist_wiki_(String raw) { + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + Xowe_wiki wiki = Xoa_app_fxt.Make__wiki__edit(app, "enwiki"); + Xow_popup_mgr popup_mgr = wiki.Html_mgr().Head_mgr().Popup_mgr(); + popup_mgr.Init_by_wiki(wiki); + popup_mgr.Parser().Tmpl_keeplist_init_(Bry_.new_u8(raw)); + Xop_keeplist_wiki rv = popup_mgr.Parser().Tmpl_keeplist(); + return rv; + } + public void Test_Match_y(Xop_keeplist_wiki keeplist_wiki, String... itms) {Test_Match(keeplist_wiki, itms, Bool_.Y);} + public void Test_Match_n(Xop_keeplist_wiki keeplist_wiki, String... itms) {Test_Match(keeplist_wiki, itms, Bool_.N);} + private void Test_Match(Xop_keeplist_wiki keeplist_wiki, String[] itms, boolean expd) { + int len = itms.length; + for (int i = 0; i < len; i++) { + String itm = itms[i]; + Tfds.Eq(expd, keeplist_wiki.Match(Bry_.new_u8(itm)), "itm={0} expd={1}", itm, expd); + } + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/ns_files/Xoh_file_page__other_resolutions.java b/400_xowa/src/gplx/xowa/htmls/ns_files/Xoh_file_page__other_resolutions.java index a27517de8..87c937786 100644 --- a/400_xowa/src/gplx/xowa/htmls/ns_files/Xoh_file_page__other_resolutions.java +++ b/400_xowa/src/gplx/xowa/htmls/ns_files/Xoh_file_page__other_resolutions.java @@ -13,3 +13,26 @@ 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.htmls.ns_files; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.core.primitives.*; +import gplx.xowa.files.*; import gplx.xowa.files.repos.*; import gplx.xowa.parsers.lnkis.*; +class Xoh_file_page__other_resolutions implements gplx.core.brys.Bfr_arg { + private Xow_repo_mgr repo_mgr; private Xof_file_itm orig_itm; private Xoh_file_page_wtr file_page; + private final Xof_img_size img_size = new Xof_img_size(); private final Xof_url_bldr url_bldr = Xof_url_bldr.new_v2(); + public Xoh_file_page__other_resolutions Init_by_fmtr(Xow_repo_mgr repo_mgr, Xof_file_itm orig_itm, Xoh_file_page_wtr file_page) {this.repo_mgr = repo_mgr; this.orig_itm = orig_itm; this.file_page = file_page; return this;} + public void Bfr_arg__add(Bry_bfr bfr) { + Int_2_ref[] ary = file_page.Size_alts(); + Xof_file_itm xfer_itm = new Xof_fsdb_itm(); + int len = ary.length; + Xof_repo_itm repo = repo_mgr.Get_trg_by_id_or_null(orig_itm.Orig_repo_id(), orig_itm.Lnki_ttl(), Bry_.Empty); + if (repo == null) return; + for (int i = 0; i < len; ++i) { + Int_2_ref itm = ary[i]; + xfer_itm.Init_at_lnki(Xof_exec_tid.Tid_wiki_page, Bry_.Empty, orig_itm.Lnki_ttl(), Xop_lnki_type.Id_none, Xop_lnki_tkn.Upright_null, itm.Val_0(), itm.Val_1(), Xof_lnki_time.Null, Xof_lnki_page.Null, Xof_patch_upright_tid_.Tid_all); + xfer_itm.Init_at_orig(orig_itm.Orig_repo_id(), orig_itm.Orig_repo_name(), orig_itm.Orig_ttl(), orig_itm.Orig_ext(), orig_itm.Orig_w(), orig_itm.Orig_h(), Bry_.Empty); + xfer_itm.Init_at_html(Xof_exec_tid.Tid_wiki_page, img_size, repo, url_bldr); + byte[] itm_separator = i == len - 1 ? file_page.Html_alt_dlm_last() : file_page.Html_alt_dlm_dflt(); // "|" separator between itms unless last + file_page.Html_alts().Bld_bfr_many(bfr, xfer_itm.Html_w(), xfer_itm.Html_h(), xfer_itm.Html_view_url().To_http_file_bry(), itm_separator, orig_itm.Lnki_ttl()); + } + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/ns_files/Xoh_file_page_wtr.java b/400_xowa/src/gplx/xowa/htmls/ns_files/Xoh_file_page_wtr.java index a27517de8..1f1bbde48 100644 --- a/400_xowa/src/gplx/xowa/htmls/ns_files/Xoh_file_page_wtr.java +++ b/400_xowa/src/gplx/xowa/htmls/ns_files/Xoh_file_page_wtr.java @@ -13,3 +13,91 @@ 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.htmls.ns_files; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.core.primitives.*; import gplx.core.brys.fmtrs.*; +public class Xoh_file_page_wtr { + public int Main_img_w() {return 800;} + public int Main_img_h() {return 600;} + public Int_2_ref[] Size_alts() {return size_alts;} private Int_2_ref[] size_alts = new Int_2_ref[] {new Int_2_ref(320, 240), new Int_2_ref(640, 480), new Int_2_ref(800, 600), new Int_2_ref(1024, 768), new Int_2_ref(1280, 1024)}; + public byte[] Html_alt_dlm_dflt() {return html_alt_dlm_dflt;} private byte[] html_alt_dlm_dflt = Bry_.new_a7("|"); + public byte[] Html_alt_dlm_last() {return html_alt_dlm_last;} private byte[] html_alt_dlm_last = Bry_.new_a7("."); + public Bry_fmtr Html_main() {return html_main;} private final Bry_fmtr html_main = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "~{commons_notice}
    " +// , "" + , "~{media}" + ) + , "media", "commons_notice"); + public Bry_fmtr Html_main_img() {return html_main_img;} private final Bry_fmtr html_main_img = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "
    " + , " " + , " \"~{thumb_alt}\"" + , " " + , "
    Size of this preview: " + , " " + , " ~{thumb_width} × ~{thumb_height} pixels" + , " " + , " ." +// , " " +// , " Other resolutions:" +// , "~{section_alts}" +// , " " + , "
    " + , "
    " + , "
    " + , " " + , " Full resolution" + , " " + , " ‎" + , " " + , " (~{orig_width} × ~{orig_height} pixels, file size: ~{orig_file_size}, MIME type: ~{orig_mime_type})" + , " " + , "
    " + , "" + ), "orig_width", "orig_height", "orig_href", "orig_file_size", "orig_mime_type", "elem_id", "thumb_width", "thumb_height", "thumb_href", "thumb_alt", "thumb_name", "section_alts"); + public Bry_fmtr Html_main_aud() {return html_main_aud;} private final Bry_fmtr html_main_aud = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "
    " + , "
    " + , "
    " + , "" + ), "lnki_url", "xowa_title", "play_width", "play_max_width"); + public Bry_fmtr Html_main_vid() {return html_main_vid;} private final Bry_fmtr html_main_vid = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "
    " + , "
    " + , " " + , " \"~{lnki_alt}\"" + , " " + , "
    " + , "
    " + , "
    " + , "" + ), "elem_id", "lnki_href", "lnki_class", "xowa_title", "lnki_src", "lnki_width", "lnki_height", "lnki_alt", "lnki_url", "play_width", "play_max_width"); + public Bry_fmtr Html_alts() {return html_alts;} private final Bry_fmtr html_alts = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( " " + , " ~{thumb_width} × ~{thumb_height}" + , " " + , " ~{thumb_dlm} " + , "" + ), "thumb_width", "thumb_height", "thumb_href", "thumb_dlm", "xowa_title"); +} + diff --git a/400_xowa/src/gplx/xowa/htmls/ns_files/Xoh_ns_file_page_mgr.java b/400_xowa/src/gplx/xowa/htmls/ns_files/Xoh_ns_file_page_mgr.java index a27517de8..8ef947dc4 100644 --- a/400_xowa/src/gplx/xowa/htmls/ns_files/Xoh_ns_file_page_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/ns_files/Xoh_ns_file_page_mgr.java @@ -13,3 +13,95 @@ 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.htmls.ns_files; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.core.primitives.*; +import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.wkrs.lnkis.htmls.*; +import gplx.xowa.files.*; import gplx.xowa.files.repos.*; import gplx.xowa.files.xfers.*; import gplx.xowa.files.origs.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.lnkis.*; +import gplx.xowa.files.fsdb.*; import gplx.xowa.files.fsdb.fs_roots.*; +public class Xoh_ns_file_page_mgr implements gplx.core.brys.Bfr_arg { + private Xoa_ttl ttl; private Xoh_file_page_wtr html_wtr; private final Xoh_file_page__other_resolutions alt_wtr = new Xoh_file_page__other_resolutions(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + private Xow_repo_mgr repo_mgr; + private Xof_file_itm xfer_itm; private byte[] file_size_bry; + private final Xof_img_size img_size = new Xof_img_size(); private final Xof_url_bldr url_bldr = Xof_url_bldr.new_v2(); + public void Bld_html(Xowe_wiki cur_wiki, Xop_ctx ctx, Xoae_page page, Bry_bfr bfr, Xoa_ttl ttl, Xoh_file_page_wtr html_wtr, Xof_xfer_queue queue) { + Xowe_wiki wiki = (Xowe_wiki)page.Commons_mgr().Source_wiki_or(cur_wiki); + this.ttl = ttl; this.html_wtr = html_wtr; this.repo_mgr = wiki.File__repo_mgr(); + this.xfer_itm = wiki.Html_mgr().Html_wtr().Lnki_wtr().File_wtr().Lnki_eval(Xof_exec_tid.Tid_wiki_file, ctx, ctx.Page(), queue, ttl.Page_txt() + , Xop_lnki_type.Id_thumb, Xop_lnki_tkn.Upright_null, html_wtr.Main_img_w(), html_wtr.Main_img_h(), Xof_lnki_time.Null, Xof_lnki_page.Null, Bool_.N); + + // get orig + Xof_orig_itm orig = wiki.File_mgr().Orig_mgr().Find_by_ttl_or_null(xfer_itm.Lnki_ttl()); + if (orig == Xof_orig_itm.Null) return; // no orig; + Xof_repo_itm repo = wiki.File__repo_mgr().Get_trg_by_id_or_null(orig.Repo(), xfer_itm.Lnki_ttl(), Bry_.Empty); + if (repo == null) return; + xfer_itm.Init_at_orig(orig.Repo(), repo.Wiki_domain(), orig.Ttl(), orig.Ext(), orig.W(), orig.H(), orig.Redirect()); + xfer_itm.Init_at_html(Xof_exec_tid.Tid_wiki_file, img_size, repo, url_bldr); + + // if non-wmf, point orig_url to fs_dir, not cache_dir; DATE:2017-02-01 + if (wiki.Domain_tid() == gplx.xowa.wikis.domains.Xow_domain_tid_.Tid__other) { + Xof_fsdb_mgr fsdb_mgr = cur_wiki.File_mgr().Fsdb_mgr(); + if (String_.Eq(fsdb_mgr.Key(), Fs_root_core.Fsdb_mgr_key)) { + Fs_root_core fs_dir_core = (Fs_root_core)fsdb_mgr; + Io_url orig_url = fs_dir_core.Get_orig_url_or_null(xfer_itm.Lnki_ttl()); + if (orig_url != null) + xfer_itm.Html_orig_url_(orig_url); + } + } + + // get file size + this.file_size_bry = Bry_.Empty; + if (xfer_itm.File_exists()) { // file exists + long file_size = Io_mgr.Instance.QueryFil(xfer_itm.Html_orig_url()).Size(); + if (file_size == -1) file_size = 0; // QueryFil returns -1 if file doesn't exist + this.file_size_bry = Bry_.new_a7(gplx.core.ios.Io_size_.To_str(file_size)); + } + + // get commons notice + String commons_notice = page.Commons_mgr().Xowa_mockup() + ? String_.Format(Str_commons_notice, gplx.langs.htmls.Gfh_utl.Escape_for_atr_val_as_bry(tmp_bfr, Byte_ascii.Apos, page.Ttl().Full_db_as_str())) + : ""; + html_wtr.Html_main().Bld_bfr_many(bfr, this, commons_notice); + } + + public void Bld_html(Xowe_wiki wiki, Bry_bfr bfr, Xof_file_itm xfer_itm, Xoa_ttl ttl, Xoh_file_page_wtr html_wtr, byte[] file_size_bry) { // TEST: + this.ttl = ttl; this.html_wtr = html_wtr; this.repo_mgr = wiki.File__repo_mgr(); + this.xfer_itm = xfer_itm; this.file_size_bry = file_size_bry; + html_wtr.Html_main().Bld_bfr_many(bfr, this, ""); + } + public void Bfr_arg__add(Bry_bfr bfr) { + alt_wtr.Init_by_fmtr(repo_mgr, xfer_itm, html_wtr); + Xof_ext orig_ext = xfer_itm.Orig_ext(); + byte[] alt_bry = gplx.langs.htmls.encoders.Gfo_url_encoder_.Http_url.Encode(ttl.Full_txt_w_ttl_case()); + byte[] xowa_title = gplx.langs.htmls.encoders.Gfo_url_encoder_.Http_url.Encode(ttl.Page_url()); + if (orig_ext.Id_is_thumbable_img()) + html_wtr.Html_main_img().Bld_bfr_many(bfr, xfer_itm.Orig_w(), xfer_itm.Orig_h(), xfer_itm.Html_orig_url().To_http_file_bry(), file_size_bry, orig_ext.Mime_type() + , xfer_itm.Html_uid(), xfer_itm.Html_w(), xfer_itm.Html_h(), xfer_itm.Html_view_url().To_http_file_bry() + , alt_bry, xowa_title, alt_wtr); + else if (orig_ext.Id_is_video()) // NOTE: video must precede audio else File:***.ogg will not show thumbs + html_wtr.Html_main_vid().Bld_bfr_many(bfr, xfer_itm.Html_uid(), xfer_itm.Html_view_url().To_http_file_bry(), Atr_class_image, xowa_title + , xfer_itm.Html_view_url().To_http_file_bry(), xfer_itm.Html_w(), xfer_itm.Html_h(), Bry_.Empty, xfer_itm.Html_orig_url().To_http_file_bry(), xfer_itm.Html_w(), xfer_itm.Html_w()); + else if (orig_ext.Id_is_audio()) + html_wtr.Html_main_aud().Bld_bfr_many(bfr, xfer_itm.Html_orig_url().To_http_file_bry(), xowa_title, xfer_itm.Html_w(), xfer_itm.Html_w()); + } + private static final byte[] Atr_class_image = Bry_.new_a7("image"); + private static final String Str_commons_notice = String_.Concat_lines_nl_skip_last + ( "
    " + , " " + , " " + , " " + , " " + , "
    " + , " " + , "" + , "

    This offline page is a reduced version of the online one: https://commons.wikimedia.org/wiki/{0}.

    " + , "

    If you want XOWA to show an offline page just like the online version, you should download commons.wikimedia.org at Import online.

    " + , "
      " + , "
    • This wiki will use at least another 20 GB of disk space.
    • " + , "
    • This wiki only provides wikitext. No extra images are downloaded. Note that the total size of images for commons.wikimedia.org would be too large (approximately 22 TB).
    • " + , "
    " + , "
    " + , "
    " + ); +} diff --git a/400_xowa/src/gplx/xowa/htmls/ns_files/Xoh_ns_file_page_mgr_tst.java b/400_xowa/src/gplx/xowa/htmls/ns_files/Xoh_ns_file_page_mgr_tst.java index a27517de8..806701204 100644 --- a/400_xowa/src/gplx/xowa/htmls/ns_files/Xoh_ns_file_page_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/ns_files/Xoh_ns_file_page_mgr_tst.java @@ -13,3 +13,117 @@ 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.htmls.ns_files; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import org.junit.*; import gplx.xowa.files.*; +public class Xoh_ns_file_page_mgr_tst { + private final Xoh_ns_file_page_mgr_fxt fxt = new Xoh_ns_file_page_mgr_fxt(); + @Before public void init() {fxt.Reset();} + @Test public void Image() { + fxt.Ttl_str_("Test.png").Html_src_("mem/file/cur.png").Html_orig_src_("mem/file/orig.png").Html_w_(300).Html_h_(200).Html_file_size_(100) + .tst(String_.Concat_lines_nl_skip_last + ( Xoh_ns_file_page_mgr_fxt.Hdr + , "
    " + , " " + , " \"Test.png\"" + , " " + , "
    Size of this preview: " + , " " + , " 300 × 200 pixels" + , " " + , " ." +// , " " +// , " Other resolutions:" +// , "" +// , " " + , "
    " + , "
    " + , "
    " + , " " + , " Full resolution" + , " " + , " ‎" + , " " + , " (0 × 0 pixels, file size: 100, MIME type: image/png)" + , " " + , "
    " + , "" + )); + } + @Test public void Audio() { + fxt.Ttl_str_("Test.oga").Html_src_("mem/file/cur.oga").Html_orig_src_("mem/file/orig.oga").Html_w_(300).Html_h_(200).Html_file_size_(100) + .tst(String_.Concat_lines_nl_skip_last + ( Xoh_ns_file_page_mgr_fxt.Hdr + , "
    " + , "
    " + , "
    " + , "" + )); + } + @Test public void Video() { + fxt.Ttl_str_("Test.ogv").Html_src_("mem/file/thumb.png").Html_orig_src_("mem/file/orig.ogv").Html_w_(300).Html_h_(200).Html_file_size_(100) + .tst(String_.Concat_lines_nl_skip_last + ( Xoh_ns_file_page_mgr_fxt.Hdr + , "
    " + , "
    " + , " " + , " \"\"" + , " " + , "
    " + , "
    " + , "
    " + , "" + )); + } +} +class Xoh_ns_file_page_mgr_fxt { + private final Xoh_ns_file_page_mgr wkr = new Xoh_ns_file_page_mgr(); + private Xoae_app app; private Xowe_wiki wiki; private Xoh_file_page_wtr opt; + private final Xof_file_itm file = new Xof_fsdb_itm(); private final Bry_bfr bfr = Bry_bfr_.New(); + public Xoh_ns_file_page_mgr_fxt Ttl_str_(String v) {this.ttl_str = v; return this;} private String ttl_str; + public Xoh_ns_file_page_mgr_fxt Html_src_(String v) {this.html_src = v; return this;} private String html_src; + public Xoh_ns_file_page_mgr_fxt Html_orig_src_(String v) {this.html_orig_src = v; return this;} private String html_orig_src; + public Xoh_ns_file_page_mgr_fxt Html_w_(int v) {this.html_w = v; return this;} private int html_w; + public Xoh_ns_file_page_mgr_fxt Html_h_(int v) {this.html_h = v; return this;} private int html_h; + public Xoh_ns_file_page_mgr_fxt Html_file_size_(int v) {this.html_file_size = v; return this;} private int html_file_size; + public void Reset() { + if (app != null) return; + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + opt = new Xoh_file_page_wtr(); + } + public void tst(String expd) { + byte[] ttl_bry = Bry_.new_u8(ttl_str); + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, ttl_bry); + file.Init_at_gallery_end(html_w, html_h, Io_url_.mem_fil_(html_src), Io_url_.mem_fil_(html_orig_src)); +// file.Orig_ttl_and_redirect_(ttl_bry, Bry_.Empty); + file.Init_at_orig(Byte_.Zero, wiki.Domain_bry(), ttl_bry, Xof_ext_.new_by_ttl_(ttl_bry), 0, 0, Bry_.Empty); + file.Init_at_hdoc(0, Xof_html_elem.Tid_img); + wkr.Bld_html(wiki, bfr, file, ttl, opt, Bry_.To_a7_bry(html_file_size, 0)); // TEST: must pass in elem_val b/c test only uses 2nd Bld_html while app uses 1st + Tfds.Eq_str_lines(expd, bfr.To_str_and_clear()); + } + public static final String Hdr = String_.Concat_lines_nl_skip_last("
    "); +// ( "" +// ); +} + diff --git a/400_xowa/src/gplx/xowa/htmls/portal/Xoa_available_wikis_mgr.java b/400_xowa/src/gplx/xowa/htmls/portal/Xoa_available_wikis_mgr.java index a27517de8..c475307a3 100644 --- a/400_xowa/src/gplx/xowa/htmls/portal/Xoa_available_wikis_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/portal/Xoa_available_wikis_mgr.java @@ -13,3 +13,40 @@ 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.htmls.portal; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.xwikis.*; +import gplx.xowa.files.xfers.*; +public class Xoa_available_wikis_mgr implements Gfo_invk { + private Bry_fmtr itms_as_html_fmtr = Bry_fmtr.new_("\n
  • ~{domain}
  • ", "domain", "itm_cls"); + public Xoa_available_wikis_mgr(Xoae_app app) {this.app = app;} private Xoae_app app; + public String Itms_as_html() { + if (itms_as_html == null) { + String itm_cls = app.Usere().Wiki().Html_mgr().Head_mgr().Popup_mgr().Enabled() ? " class='xowa-hover-off'" : ""; // always add popup-disabled class in sidebar, even if popups aren't enabled; not worth effort to check cfg for get "current wiki"; DATE:2016-12-13 + Bry_bfr tmp_bfr = Bry_bfr_.New(); + Xow_xwiki_mgr xwiki_mgr = app.Usere().Wiki().Xwiki_mgr(); + xwiki_mgr.Sort_by_key(); + int len = xwiki_mgr.Len(); + for (int i = 0; i < len; i++) { + Xow_xwiki_itm itm = xwiki_mgr.Get_at(i); + if (itm.Domain_tid() == Xow_domain_tid_.Tid__home) continue;// don't show home wiki + if (!itm.Offline()) continue; // only show items marked Offline (added by Available_from_fsys); DATE:2014-09-21 + itms_as_html_fmtr.Bld_bfr_many(tmp_bfr, itm.Domain_bry(), itm_cls); + } + itms_as_html = tmp_bfr.To_str(); + } + return itms_as_html; + } private String itms_as_html; + public void Itms_reset() {itms_as_html = null;} + public boolean Visible() {return visible;} private boolean visible = true; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_itms_as_html)) return this.Itms_as_html(); + else if (ctx.Match(k, Invk_itms_refresh)) Itms_reset(); + else if (ctx.Match(k, Invk_visible)) return Yn.To_str(visible); + else if (ctx.Match(k, Invk_visible_)) visible = m.ReadYn("v"); + else if (ctx.Match(k, Invk_visible_toggle)) {visible = !visible; app.Gui_mgr().Browser_win().Active_html_box().Html_js_eval_proc_as_str("xowa-portal-wikis-visible-toggle", Bool_.To_str_lower(visible));} + else if (ctx.Match(k, Invk_itms_as_html_fmtr_)) itms_as_html_fmtr.Fmt_(m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_visible = "visible", Invk_visible_ = "visible_", Invk_visible_toggle = "visible_toggle", Invk_itms_as_html = "itms_as_html", Invk_itms_as_html_fmtr_ = "itms_as_html_fmtr_", Invk_itms_refresh = "itms_refresh"; +} diff --git a/400_xowa/src/gplx/xowa/htmls/portal/Xoa_portal_mgr.java b/400_xowa/src/gplx/xowa/htmls/portal/Xoa_portal_mgr.java index a27517de8..b8be0cea2 100644 --- a/400_xowa/src/gplx/xowa/htmls/portal/Xoa_portal_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/portal/Xoa_portal_mgr.java @@ -13,3 +13,12 @@ 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.htmls.portal; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +public class Xoa_portal_mgr implements Gfo_invk { + public Xoa_portal_mgr(Xoae_app app) {wikis = new Xoa_available_wikis_mgr(app);} + public Xoa_available_wikis_mgr Wikis() {return wikis;} private Xoa_available_wikis_mgr wikis; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_wikis)) return wikis; + else return Gfo_invk_.Rv_unhandled; + } private static final String Invk_wikis = "wikis"; +} diff --git a/400_xowa/src/gplx/xowa/htmls/portal/Xoh_page_body_cls.java b/400_xowa/src/gplx/xowa/htmls/portal/Xoh_page_body_cls.java index a27517de8..7ebb770a5 100644 --- a/400_xowa/src/gplx/xowa/htmls/portal/Xoh_page_body_cls.java +++ b/400_xowa/src/gplx/xowa/htmls/portal/Xoh_page_body_cls.java @@ -13,3 +13,118 @@ 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.htmls.portal; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.xowa.wikis.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.wikis.nss.*; +public class Xoh_page_body_cls { // REF.MW:Skin.php|getPageClasses + public static byte[] Calc(Bry_bfr tmp_bfr, Xoa_ttl ttl, int page_tid) { + tmp_bfr.Add(Bry_id_prefix).Add_int_variable(ttl.Ns().Id()); // ns-0; note that special is ns--1 DATE:2014-09-24 + Add_type(tmp_bfr, ttl); // ns-special || ns-talk || ns-subject + tmp_bfr.Add_byte_space().Add(Bry_page_prefix).Add(Escape_cls(ttl.Full_db())); // page-Page_title + if (page_tid == Xow_page_tid.Tid_json) { + switch (ttl.Ns().Id()) { + case Xow_ns_.Tid__main: + tmp_bfr.Add_byte_space().Add(Bry_wb_entitypage); + tmp_bfr.Add_byte_space().Add(Bry_wb_itempage); + tmp_bfr.Add_byte_space().Add(Bry_wb_itempage).Add_byte(Byte_ascii.Dash).Add(ttl.Page_db()); + break; + case Wdata_wiki_mgr.Ns_property: + tmp_bfr.Add_byte_space().Add(Bry_wb_entitypage); + tmp_bfr.Add_byte_space().Add(Bry_wb_propertypage); + tmp_bfr.Add_byte_space().Add(Bry_wb_propertypage).Add_byte(Byte_ascii.Dash).Add(ttl.Page_db()); + break; + default: + Gfo_usr_dlg_.Instance.Warn_many("", "", "unexpected ns for page_body_cls; ttl=~{0}", String_.new_u8(ttl.Raw())); + break; + } + } + return tmp_bfr.To_bry_and_clear(); + } + private static void Add_type(Bry_bfr tmp_bfr, Xoa_ttl ttl) { + tmp_bfr.Add_byte_space(); + if (ttl.Ns().Id_is_special()) { + tmp_bfr.Add(Bry_type_special); // MW_TODO: add " mw-special-$canonicalName" + } + else if (ttl.Ns().Id_is_talk()) + tmp_bfr.Add(Bry_type_talk); + else + tmp_bfr.Add(Bry_type_subject); + } + public static byte[] Escape_cls(byte[] src) { // REF.MW:Sanitizer.php|escapeClass; return rtrim( preg_replace(array( '/(^[0-9\\-])|[\\x00-\\x20!"#$%&\'()*+,.\\/:;<=>?@[\\]^`{|}~]|\\xC2\\xA0/', '/_+/' ), '_', $class ), '_' ); + Bry_bfr trg_bfr = null; + int src_len = src.length; int bgn = -1; + for (int i = 0; i < src_len; ++i) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Space: + case Byte_ascii.Bang: case Byte_ascii.Quote: case Byte_ascii.Hash: case Byte_ascii.Dollar: case Byte_ascii.Percent: + case Byte_ascii.Amp: case Byte_ascii.Apos: case Byte_ascii.Paren_bgn: case Byte_ascii.Paren_end: case Byte_ascii.Star: + case Byte_ascii.Plus: case Byte_ascii.Comma: case Byte_ascii.Dot: case Byte_ascii.Backslash: case Byte_ascii.Slash: + case Byte_ascii.Colon: case Byte_ascii.Semic: case Byte_ascii.Gt: case Byte_ascii.Eq: case Byte_ascii.Lt: + case Byte_ascii.Question: case Byte_ascii.At: case Byte_ascii.Brack_bgn: case Byte_ascii.Brack_end: + case Byte_ascii.Pow: case Byte_ascii.Tick: + case Byte_ascii.Curly_bgn: case Byte_ascii.Pipe: case Byte_ascii.Curly_end: case Byte_ascii.Tilde: + if (trg_bfr == null) + src[i] = Byte_ascii.Underline; + else { + if (bgn != -1) { + trg_bfr.Add_mid(src, bgn, i); + bgn = -1; + } + trg_bfr.Add_byte(Byte_ascii.Underline); + } + break; + case Byte_ascii.Underline: + if (trg_bfr == null) { + trg_bfr = Bry_bfr_.New_w_size(src_len); + trg_bfr.Add_mid(src, 0, i); + } + if (bgn != -1) { + trg_bfr.Add_mid(src, bgn, i); + bgn = -1; + } + int repeat = 0; + for (int j = i + 1; j < src_len; ++j) { + if (src[j] == Byte_ascii.Underline) + ++repeat; + else + break; + } + trg_bfr.Add_byte(Byte_ascii.Underline); + i += repeat; + break; + case -62: + int next = i + 1; + if (next < src_len && src[next] == -96) { + if (trg_bfr == null) { + trg_bfr = Bry_bfr_.New_w_size(src_len); + trg_bfr.Add_mid(src, 0, i); + } + trg_bfr.Add_byte(Byte_ascii.Underline); + ++i; + continue; + } + if (trg_bfr != null && bgn == -1) + bgn = i; + break; + default: + if (trg_bfr != null && bgn == -1) + bgn = i; + break; + } + } + if (bgn != -1) trg_bfr.Add_mid(src, bgn, src_len); + return trg_bfr == null ? src : trg_bfr.To_bry_and_clear(); + } + private static final byte[] + Bry_id_prefix = Bry_.new_a7("ns-") + , Bry_type_special = Bry_.new_a7("ns-special") + , Bry_type_talk = Bry_.new_a7("ns-talk") + , Bry_type_subject = Bry_.new_a7("ns-subject") + , Bry_page_prefix = Bry_.new_a7("page-") + , Bry_wb_entitypage = Bry_.new_a7("wb-entitypage") + , Bry_wb_itempage = Bry_.new_a7("wb-itempage") + , Bry_wb_propertypage = Bry_.new_a7("wb-propertypage") + ; + public static int Page_tid_wikitext = 0, Page_tid_wikidata_qid = 1, Page_tid_wikidata_pid = 2; +} diff --git a/400_xowa/src/gplx/xowa/htmls/portal/Xoh_page_body_cls_tst.java b/400_xowa/src/gplx/xowa/htmls/portal/Xoh_page_body_cls_tst.java index a27517de8..61cd87bc7 100644 --- a/400_xowa/src/gplx/xowa/htmls/portal/Xoh_page_body_cls_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/portal/Xoh_page_body_cls_tst.java @@ -13,3 +13,40 @@ 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.htmls.portal; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import org.junit.*; import gplx.xowa.wikis.*; import gplx.xowa.xtns.wbases.*; +public class Xoh_page_body_cls_tst { + @Before public void init() {} private Xoh_page_body_cls_fxt fxt = new Xoh_page_body_cls_fxt(); + @Test public void Escape_cls() { + fxt.Test_escape_cls("0123456789", "0123456789"); // noop: num + fxt.Test_escape_cls("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); // noop: ucase + fxt.Test_escape_cls("abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz"); // noop: lcase + fxt.Test_escape_cls("!\"#$%&'()*+,.\\/:;<=>?@[]^`{|}~", "______________________________"); // underline: syms + fxt.Test_escape_cls("a.bcd..ef.", "a_bcd__ef_"); // letters + syms + fxt.Test_escape_cls("a__b___c" , "a_b_c"); // multiple underlines + fxt.Test_escape_cls("a b", "a_b"); // nbsp + } + @Test public void Calc() { + fxt.Test_calc(Xow_page_tid.Tid_wikitext , "A" , "ns-0 ns-subject page-A"); + fxt.Test_calc(Xow_page_tid.Tid_wikitext , "Talk:A" , "ns-1 ns-talk page-Talk_A"); + fxt.Test_calc(Xow_page_tid.Tid_wikitext , "Wikipedia:Página principal" , "ns-4 ns-subject page-Wikipedia_Página_principal"); + fxt.Test_calc(Xow_page_tid.Tid_json , "Q2" , "ns-0 ns-subject page-Q2 wb-entitypage wb-itempage wb-itempage-Q2"); + fxt.Test_calc(Xow_page_tid.Tid_json , "Property:P1" , "ns-120 ns-subject page-Property_P1 wb-entitypage wb-propertypage wb-propertypage-P1"); + } +} +class Xoh_page_body_cls_fxt { + private Bry_bfr tmp_bfr; private Xoae_app app; private Xowe_wiki wiki; + public void Test_escape_cls(String raw, String expd) { + Tfds.Eq(expd, String_.new_u8(Xoh_page_body_cls.Escape_cls(Bry_.new_u8(raw)))); + } + public void Test_calc(byte page_tid, String ttl_str, String expd) { + if (app == null) { + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + tmp_bfr = Bry_bfr_.Reset(255); + wiki.Ns_mgr().Add_new(Wdata_wiki_mgr.Ns_property, Wdata_wiki_mgr.Ns_property_name); + } + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, Bry_.new_u8(ttl_str)); + Tfds.Eq(expd, String_.new_u8(Xoh_page_body_cls.Calc(tmp_bfr, ttl, page_tid))); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/portal/Xoh_rtl_utl.java b/400_xowa/src/gplx/xowa/htmls/portal/Xoh_rtl_utl.java index a27517de8..6c9f4d400 100644 --- a/400_xowa/src/gplx/xowa/htmls/portal/Xoh_rtl_utl.java +++ b/400_xowa/src/gplx/xowa/htmls/portal/Xoh_rtl_utl.java @@ -13,3 +13,59 @@ 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.htmls.portal; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +public class Xoh_rtl_utl { + private static final int[] tmp_ary = new int[32]; // support no more than 16 items + private static Bry_bfr bfr = Bry_bfr_.Reset(32); + public static byte[] Reverse_li(byte[] src) { + int src_len = src.length; + int pos = 0; + while (true) { + int ul_bgn = Bry_find_.Find_fwd(src, Ul_bgn, pos, src_len); + if (ul_bgn == Bry_find_.Not_found) break; // no more
      + bfr.Add_mid(src, pos, ul_bgn); // add pos ->
        + tmp_ary[tmp_idx++] = li_bgn; + tmp_ary[tmp_idx++] = li_end + Li_end.length; + pos = li_end + Li_end.length; + } + int ul_bgn_rhs = Bry_find_.Find_fwd(src, Byte_ascii.Gt, ul_bgn); + if (tmp_idx < 3 // 0 or 1 li; add everything between ul + || ul_bgn_rhs == Bry_find_.Not_found + ) { + bfr.Add_mid(src, ul_bgn, ul_end); + return; + } + int li_n_end = tmp_ary[tmp_idx - 1]; + ++ul_bgn_rhs; + bfr.Add_mid(src, ul_bgn, ul_bgn_rhs); // add from " to 1st ">" + for (int i = tmp_idx - 1; i > -1; i -= 2) { + int li_end = tmp_ary[i]; + int prv_pos = i < 2 ? ul_bgn_rhs : tmp_ary[i - 2]; + bfr.Add_mid(src, prv_pos, li_end); // add from " to "" + } + bfr.Add_mid(src, li_n_end, ul_end); // add from nth "" -> "
      " + } + private static final byte[] + Ul_bgn = Bry_.new_a7("") + , Li_bgn = Bry_.new_a7("") + ; +} diff --git a/400_xowa/src/gplx/xowa/htmls/portal/Xoh_rtl_utl_tst.java b/400_xowa/src/gplx/xowa/htmls/portal/Xoh_rtl_utl_tst.java index a27517de8..0f2fbde7f 100644 --- a/400_xowa/src/gplx/xowa/htmls/portal/Xoh_rtl_utl_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/portal/Xoh_rtl_utl_tst.java @@ -13,3 +13,50 @@ 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.htmls.portal; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import org.junit.*; +public class Xoh_rtl_utl_tst { + @Before public void init() {fxt.Init();} private Xoh_rtl_utl_fxt fxt = new Xoh_rtl_utl_fxt(); + @Test public void Basic() { + fxt.Test_reverse_li("
      • a
      • b
      ", "
      • b
      • a
      "); + } + @Test public void Zero() { + fxt.Test_reverse_li("a", "a"); + } + @Test public void One() { + fxt.Test_reverse_li("
      • a
      ", "
      • a
      "); + } + @Test public void Example() { + fxt.Test_reverse_li(String_.Concat_lines_nl_skip_last + ( "
      " + , "
        " + , "
      • a" + , "
      • " + , "
      • b" + , "
      • " + , "
      • c" + , "
      • " + , "
      " + , "
      " + ), String_.Concat_lines_nl_skip_last + ( "
      " + , "
        " + , "
      • c" + , "
      • " + , "
      • b" + , "
      • " + , "
      • a" + , "
      • " + , "
      " + , "
      " + )); + } +} +class Xoh_rtl_utl_fxt { + public void Init() { + } + public void Test_reverse_li(String raw, String expd) { + byte[] actl = Xoh_rtl_utl.Reverse_li(Bry_.new_u8(raw)); + Tfds.Eq_str_lines(expd, String_.new_u8(actl)); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/portal/Xoh_subpages_bldr.java b/400_xowa/src/gplx/xowa/htmls/portal/Xoh_subpages_bldr.java index a27517de8..6973caa61 100644 --- a/400_xowa/src/gplx/xowa/htmls/portal/Xoh_subpages_bldr.java +++ b/400_xowa/src/gplx/xowa/htmls/portal/Xoh_subpages_bldr.java @@ -13,3 +13,52 @@ 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.htmls.portal; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.htmls.hrefs.*; +import gplx.xowa.wikis.nss.*; +public class Xoh_subpages_bldr implements gplx.core.brys.Bfr_arg { + private Bry_bfr tmp_bfr = Bry_bfr_.Reset(255), ttl_bfr = Bry_bfr_.Reset(255); + private byte[][] segs; + public byte[] Bld(Xow_ns_mgr ns_mgr, Xoa_ttl ttl) { + Xow_ns ns = ttl.Ns(); + if (! ( ns.Subpages_enabled() // ns has subpages + && ttl.Leaf_bgn() != Bry_find_.Not_found // ttl has leaf text; EX: Help:A/B + && ns.Id() != ns_mgr.Ns_page_id() // ns is not [[Page:]]; PAGE:en.s:Notes_on_Osteology_of_Baptanodon._With_a_Description_of_a_New_Species DATE:2014-09-06 + ) + ) return Bry_.Empty; // doesn't match above; return empty; + byte[] raw = ttl.Raw(); + this.segs = Bry_split_.Split(raw, Byte_ascii.Slash); + fmtr_grp.Bld_bfr(tmp_bfr, this); + return tmp_bfr.To_bry_and_clear(); + } + public void Bfr_arg__add(Bry_bfr bfr) { + int segs_len = segs.length - 1; // last seg is current page; do not print + for (int i = 0; i < segs_len; ++i) { + byte[] dlm = null; + if (i == 0) + dlm = Dlm_1st; + else { + dlm = Dlm_nth; + ttl_bfr.Add_byte_slash(); + } + byte[] seg = segs[i]; + ttl_bfr.Add(seg); + byte[] seg_ttl = ttl_bfr.To_bry(); + byte[] seg_ttl_enc = gplx.langs.htmls.encoders.Gfo_url_encoder_.Href.Encode(ttl_bfr.To_bry()); + byte[] href = Bry_.Add(Xoh_href_.Bry__wiki, seg_ttl_enc); // EX: /wiki/Help:A + fmtr_itm.Bld_bfr(bfr, dlm, href, seg_ttl, seg); + } + ttl_bfr.Clear(); + } + private static final byte[] Dlm_1st = Bry_.new_a7("< "), Dlm_nth = Bry_.new_a7("‎ | "); + private static final Bry_fmtr + fmtr_grp = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "~{itms}" + , "" + ), "itms") + , fmtr_itm = Bry_fmtr.new_ + ( "\n ~{dlm}~{caption}" + , "dlm", "href", "title", "caption") + ; +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/htmls/portal/Xoh_subpages_bldr_tst.java b/400_xowa/src/gplx/xowa/htmls/portal/Xoh_subpages_bldr_tst.java index a27517de8..e4b0366b9 100644 --- a/400_xowa/src/gplx/xowa/htmls/portal/Xoh_subpages_bldr_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/portal/Xoh_subpages_bldr_tst.java @@ -13,3 +13,35 @@ 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.htmls.portal; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import org.junit.*; import gplx.xowa.wikis.nss.*; +public class Xoh_subpages_bldr_tst { + @Before public void init() {fxt.Init();} private Xoh_subpages_bldr_fxt fxt = new Xoh_subpages_bldr_fxt(); + @Test public void Basic() { + fxt.Test_bld("Help:A/B/C", String_.Concat_lines_nl_skip_last + ( "" + , " < Help:A" + , " ‎ | B" + , "" + )); + } + @Test public void Skip_page() { + fxt.Wiki().Ns_mgr().Add_new(104, "Page"); + fxt.Wiki().Ns_mgr().Ns_page_id_(104); + fxt.Test_bld("Page:A/B/C", ""); + } +} +class Xoh_subpages_bldr_fxt { + private Xoae_app app; + private Xoh_subpages_bldr subpages_bldr = new Xoh_subpages_bldr(); + public Xowe_wiki Wiki() {return wiki;} private Xowe_wiki wiki; + public void Init() { + this.app = Xoa_app_fxt.Make__app__edit(); + this.wiki = Xoa_app_fxt.Make__wiki__edit(app); + wiki.Ns_mgr().Ids_get_or_null(Xow_ns_.Tid__help).Subpages_enabled_(true); + } + public void Test_bld(String ttl_str, String expd) { + byte[] actl = subpages_bldr.Bld(wiki.Ns_mgr(), Xoa_ttl.Parse(wiki, Bry_.new_u8(ttl_str))); + Tfds.Eq_str_lines(expd, String_.new_u8(actl)); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/portal/Xow_portal_mgr.java b/400_xowa/src/gplx/xowa/htmls/portal/Xow_portal_mgr.java index a27517de8..dd9af1ee0 100644 --- a/400_xowa/src/gplx/xowa/htmls/portal/Xow_portal_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/portal/Xow_portal_mgr.java @@ -13,3 +13,191 @@ 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.htmls.portal; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.core.brys.*; import gplx.core.brys.fmtrs.*; import gplx.core.brys.fmts.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; +import gplx.xowa.guis.*; import gplx.xowa.addons.htmls.sidebars.*; import gplx.xowa.wikis.pages.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.domains.*; +import gplx.xowa.htmls.core.htmls.*; import gplx.langs.htmls.encoders.*; import gplx.xowa.htmls.hrefs.*; +import gplx.xowa.apps.apis.xowa.html.*; +import gplx.xowa.langs.vnts.*; import gplx.xowa.htmls.portal.vnts.*; +import gplx.xowa.parsers.amps.*; +public class Xow_portal_mgr implements Gfo_invk { + private Xowe_wiki wiki; private boolean lang_is_rtl; private Xoapi_toggle_itm toggle_itm; + private final Vnt_mnu_grp_fmtr vnt_menu_fmtr = new Vnt_mnu_grp_fmtr(); + private final Gfo_url_encoder fsys_lnx_encoder = Gfo_url_encoder_.New__fsys_lnx().Make(); + private boolean sidebar_enabled; + public Xow_portal_mgr(Xowe_wiki wiki) { + this.wiki = wiki; + this.sidebar_mgr = new Xoh_sidebar_mgr(wiki); + this.missing_ns_cls = Bry_.Eq(wiki.Domain_bry(), Xow_domain_tid_.Bry__home) ? Missing_ns_cls_hide : null; // if home wiki, set missing_ns to application default; if any other wiki, set to null; will be overriden during init + } + public byte[] Missing_ns_cls() {return missing_ns_cls;} public Xow_portal_mgr Missing_ns_cls_(byte[] v) {missing_ns_cls = v; return this;} private byte[] missing_ns_cls; // NOTE: must be null due to Init check above + public Xoh_sidebar_mgr Sidebar_mgr() {return sidebar_mgr;} private Xoh_sidebar_mgr sidebar_mgr; + public Bry_fmtr Div_home_fmtr() {return div_home_fmtr;} private Bry_fmtr div_home_fmtr = Bry_fmtr.new_(""); + public Xow_portal_mgr Init_assert() {if (init_needed) Init(); return this;} + public byte[] Div_jump_to() {return div_jump_to;} private byte[] div_jump_to = Bry_.Empty; + public void Init_by_lang(Xol_lang_itm lang) { + lang_is_rtl = !lang.Dir_ltr(); + } + public void Init_by_wiki(Xowe_wiki wiki) { + wiki.App().Cfg().Bind_many_wiki(this, wiki, Cfg__divs__footer, Cfg__missing_class, Cfg__sidebar_enabled__desktop, Cfg__sidebar_enabled__server); + } + private void Sidebar_enabled_(boolean is_desktop, boolean val) { + // set sidebar_enabled if (a) is_gui and is_desktop; or (b) is_server and !is_desktop + if (wiki.App().Mode().Tid_is_gui()) { + if (is_desktop) + this.sidebar_enabled = val; + } + else { + if (!is_desktop) + this.sidebar_enabled = val; + } + } + public void Init() { + init_needed = false; + if (missing_ns_cls == null) {// if missing_ns_cls not set for wiki, use the home wiki's + Missing_ns_cls_(wiki.Appe().Usere().Wiki().Html_mgr().Portal_mgr().Missing_ns_cls()); + } + Bry_fmtr_eval_mgr eval_mgr = wiki.Eval_mgr(); + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_b512(); + Init_fmtr(tmp_bfr, eval_mgr, div_view_fmtr); + Init_fmtr(tmp_bfr, eval_mgr, div_ns_fmtr); + byte[] wiki_user_name = wiki.User().Name(); + div_personal_bry = Init_fmtr(tmp_bfr, eval_mgr, div_personal_fmtr, Bry_.Add(Xoh_href_.Bry__wiki, wiki.Ns_mgr().Ids_get_or_null(Xow_ns_.Tid__user).Name_db_w_colon(), wiki_user_name), wiki_user_name, Ns_cls_by_id(wiki.Ns_mgr(), Xow_ns_.Tid__user), Bry_.Add(Xoh_href_.Bry__wiki, wiki.Ns_mgr().Ids_get_or_null(Xow_ns_.Tid__user_talk).Name_db_w_colon(), wiki_user_name), Ns_cls_by_id(wiki.Ns_mgr(), Xow_ns_.Tid__user_talk)); + byte[] main_page_href_bry = tmp_bfr.Add(Xoh_href_.Bry__site).Add(wiki.Domain_bry()).Add(Xoh_href_.Bry__wiki).To_bry_and_clear(); // NOTE: build /site/en.wikipedia.org/wiki/ href; no Main_Page, as that will be inserted by Xoh_href_parser + + // logo + Io_url wiki_css_dir = wiki.Appe().Usere().Fsys_mgr().Wiki_root_dir().GenSubDir_nest(wiki.Domain_str(), "html"); + div_logo_day = Init_fmtr(tmp_bfr, eval_mgr, div_logo_fmtr, main_page_href_bry, fsys_lnx_encoder.Encode_to_file_protocol(wiki_css_dir.GenSubFil_nest("logo.png"))); + + // night-mode logo; check if wiki-version exists, else use app-version + Io_url night_logo = wiki.App().Fsys_mgr().Url_finder().Find_by_css_or(wiki.Domain_str(), "logo_night.png", String_.Ary("bin", "any", "xowa", "html", "css", "nightmode"), true); + div_logo_night = Init_fmtr(tmp_bfr, eval_mgr, div_logo_fmtr, main_page_href_bry, fsys_lnx_encoder.Encode_to_file_protocol(night_logo)); + + div_home_bry = Init_fmtr(tmp_bfr, eval_mgr, div_home_fmtr); + div_wikis_fmtr.Eval_mgr_(eval_mgr); + Xow_msg_mgr msg_mgr = wiki.Msg_mgr(); + div_jump_to = Div_jump_to_fmtr.Bld_bry_many(tmp_bfr, msg_mgr.Val_by_key_obj("jumpto"), msg_mgr.Val_by_key_obj("jumptonavigation"), msg_mgr.Val_by_key_obj("comma-separator"), msg_mgr.Val_by_key_obj("jumptosearch")); + tmp_bfr.Mkr_rls(); + sidebar_mgr.Init_by_wiki(); + } private boolean init_needed = true; + private byte[] Init_fmtr(Bry_bfr tmp_bfr, Bry_fmtr_eval_mgr eval_mgr, Bry_fmtr fmtr, Object... fmt_args) { + fmtr.Eval_mgr_(eval_mgr); + fmtr.Bld_bfr_many(tmp_bfr, fmt_args); + return tmp_bfr.To_bry_and_clear(); + } + + // div_footer + private Bry_fmtr div_footer_fmtr = Bry_fmtr.keys_("page_modified_on_msg", "app_version", "app_build_date"); + private byte[] div_footer_bry = Bry_.Empty; + public byte[] Div_footer(byte[] page_modified_on_msg, String app_version, String app_build_date) { + Bry_bfr tmp_bfr = Bry_bfr_.New(); + div_footer_bry = Init_fmtr(tmp_bfr, wiki.Eval_mgr(), div_footer_fmtr, page_modified_on_msg, app_version, app_build_date); + return div_footer_bry; + } + + public byte[] Div_personal_bry() {return div_personal_bry;} private byte[] div_personal_bry = Bry_.Empty; + public byte[] Div_ns_bry(Bry_bfr_mkr bfr_mkr, Xoa_ttl ttl, Xow_ns_mgr ns_mgr) { + Xow_ns ns = ttl.Ns(); + byte[] subj_cls = Ns_cls_by_ord(ns_mgr, ns.Ord_subj_id()), talk_cls = Ns_cls_by_ord(ns_mgr, ns.Ord_talk_id()); + if (ns.Id_is_talk()) + talk_cls = Xow_portal_mgr.Cls_selected_y; + else + subj_cls = Xow_portal_mgr.Cls_selected_y; + Bfr_arg vnt_menu = null; + Xol_vnt_mgr vnt_mgr = wiki.Lang().Vnt_mgr(); // VNT; DATE:2015-03-03 + if (vnt_mgr.Enabled()) { + vnt_menu_fmtr.Init(vnt_mgr.Regy(), wiki.Domain_bry(), ttl.Full_db(), vnt_mgr.Cur_itm().Key()); + vnt_menu = wiki.Lang().Vnt_mgr().Enabled() ? vnt_menu_fmtr : null; + } + + // NOTE: need to escape args href for Search page b/c user can enter in quotes and apos; EX:localhost:8080/en.wikipedia.org/wiki/Special:XowaSearch?search=title:(%2Breturn%20%2B"abc") ; DATE:2017-07-16 + Bry_bfr tmp_bfr = bfr_mkr.Get_k004(); + byte[] subj_href = Xoh_html_wtr_escaper.Escape(Xop_amp_mgr.Instance, tmp_bfr, Bry_.Add(Xoh_href_.Bry__wiki, ttl.Subj_txt())); + byte[] talk_href = Xoh_html_wtr_escaper.Escape(Xop_amp_mgr.Instance, tmp_bfr, Bry_.Add(Xoh_href_.Bry__wiki, ttl.Talk_txt())); + + div_ns_fmtr.Bld_bfr_many(tmp_bfr, subj_href, subj_cls, talk_href, talk_cls, vnt_menu); + return tmp_bfr.To_bry_and_rls(); + } + private byte[] Ns_cls_by_ord(Xow_ns_mgr ns_mgr, int ns_ord) { + Xow_ns ns = ns_mgr.Ords_get_at(ns_ord); + return ns == null || ns.Exists() ? Bry_.Empty : missing_ns_cls; + } + private byte[] Ns_cls_by_id(Xow_ns_mgr ns_mgr, int ns_id) { + Xow_ns ns = ns_mgr.Ids_get_or_null(ns_id); + return ns == null || ns.Exists() ? Bry_.Empty : missing_ns_cls; + } + public byte[] Div_view_bry(Bry_bfr_mkr bfr_mkr, byte output_tid, byte[] search_text) { + Bry_bfr tmp_bfr = bfr_mkr.Get_k004(); + byte[] read_cls = Bry_.Empty, edit_cls = Bry_.Empty, html_cls = Bry_.Empty; + switch (output_tid) { + case Xopg_page_.Tid_read: read_cls = Cls_selected_y; break; + case Xopg_page_.Tid_edit: edit_cls = Cls_selected_y; break; + case Xopg_page_.Tid_html: html_cls = Cls_selected_y; break; + } + div_view_fmtr.Bld_bfr_many(tmp_bfr, read_cls, edit_cls, html_cls, search_text); + return tmp_bfr.To_bry_and_rls(); + } public static final byte[] Cls_selected_y = Bry_.new_a7("selected"), Cls_new = Bry_.new_a7("new"), Cls_display_none = Bry_.new_a7("xowa_display_none"); + public byte[] Div_logo_bry(boolean nightmode) {return nightmode ? div_logo_night : div_logo_day;} private byte[] div_logo_day = Bry_.Empty, div_logo_night = Bry_.Empty; + public byte[] Div_home_bry() {return sidebar_enabled ? div_home_bry : Bry_.Empty;} private byte[] div_home_bry = Bry_.Empty; + public byte[] Div_sync_bry(Bry_bfr tmp_bfr, boolean manual_enabled, Xow_wiki wiki, Xoa_page page) { + // only show update_html if wmf; DATE:2016-08-31 + if ( wiki.Domain_itm().Domain_type().Src() == Xow_domain_tid.Src__wmf + && manual_enabled) { + div_sync_fmtr.Bld_bfr_many(tmp_bfr, page.Ttl().Full_url()); + return tmp_bfr.To_bry_and_clear(); + } + return Bry_.Empty; + } + public byte[] Div_wikis_bry(Bry_bfr_mkr bfr_mkr) { + if (toggle_itm == null) // TEST:lazy-new b/c Init_by_wiki + toggle_itm = wiki.Appe().Api_root().Html().Page().Toggle_mgr().Get_or_new("offline-wikis").Init(Bry_.new_a7("Wikis")); + Bry_bfr tmp_bfr = bfr_mkr.Get_k004(); + div_wikis_fmtr.Bld_bfr_many(tmp_bfr, toggle_itm.Html_toggle_btn(), toggle_itm.Html_toggle_hdr()); + return tmp_bfr.To_bry_and_rls(); + } + private final Bry_fmtr + div_personal_fmtr = Bry_fmtr.new_("~{portal_personal_subj_href};~{portal_personal_subj_text};~{portal_personal_talk_cls};~{portal_personal_talk_href};~{portal_personal_talk_cls};", "portal_personal_subj_href", "portal_personal_subj_text", "portal_personal_subj_cls", "portal_personal_talk_href", "portal_personal_talk_cls") + , div_ns_fmtr = Bry_fmtr.new_("~{portal_ns_subj_href};~{portal_ns_subj_cls};~{portal_ns_talk_href};~{portal_ns_talk_cls};~{portal_div_vnts}", "portal_ns_subj_href", "portal_ns_subj_cls", "portal_ns_talk_href", "portal_ns_talk_cls", "portal_div_vnts") + , div_view_fmtr = Bry_fmtr.new_("", "portal_view_read_cls", "portal_view_edit_cls", "portal_view_html_cls", "search_text") + , div_logo_fmtr = Bry_fmtr.new_("", "portal_nav_main_href", "portal_logo_url") + , div_sync_fmtr = Bry_fmtr.new_("", "page_url") + , div_wikis_fmtr = Bry_fmtr.new_("", "toggle_btn", "toggle_hdr") + ; + public Bry_fmtr Div_logo_fmtr() {return div_logo_fmtr;} // TEST: + private byte[] Reverse_li(byte[] bry) { + return lang_is_rtl ? Xoh_rtl_utl.Reverse_li(bry) : bry; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_div_personal_)) div_personal_fmtr.Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_div_ns_)) div_ns_fmtr.Fmt_(Reverse_li(m.ReadBry("v"))); + else if (ctx.Match(k, Invk_div_view_)) div_view_fmtr.Fmt_(Reverse_li(m.ReadBry("v"))); + else if (ctx.Match(k, Invk_div_logo_)) div_logo_fmtr.Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_div_home_)) div_home_fmtr.Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_div_sync_)) div_sync_fmtr.Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_div_wikis_)) div_wikis_fmtr.Fmt_(m.ReadBry("v")); + + else if (ctx.Match(k, Cfg__missing_class)) missing_ns_cls = m.ReadBry("v"); + else if (ctx.Match(k, Cfg__sidebar_enabled__desktop)) Sidebar_enabled_(Bool_.Y, m.ReadYn("v")); + else if (ctx.Match(k, Cfg__sidebar_enabled__server)) Sidebar_enabled_(Bool_.N, m.ReadYn("v")); + else if (ctx.Match(k, Cfg__divs__footer)) div_footer_fmtr.Fmt_(m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_div_personal_ = "div_personal_", Invk_div_view_ = "div_view_", Invk_div_ns_ = "div_ns_", Invk_div_home_ = "div_home_" + , Invk_div_sync_ = "div_sync_", Invk_div_wikis_ = "div_wikis_"; + public static final String Invk_div_logo_ = "div_logo_"; + private static final byte[] Missing_ns_cls_hide = Bry_.new_a7("xowa_display_none"); + private static final Bry_fmtr Div_jump_to_fmtr = Bry_fmtr.new_ + ( "\n
      ~{jumpto}~{jumptonavigation}~{comma-separator}~{jumptosearch}
      " + , "jumpto", "jumptonavigation", "comma-separator", "jumptosearch"); + + private static final String + Cfg__missing_class = "xowa.html.portal.missing_class" + , Cfg__sidebar_enabled__desktop = "xowa.html.portal.sidebar_enabled_desktop" + , Cfg__sidebar_enabled__server = "xowa.html.portal.sidebar_enabled_server" + , Cfg__divs__footer = "xowa.html.divs.footer" + ; +} diff --git a/400_xowa/src/gplx/xowa/htmls/portal/Xow_portal_mgr_tst.java b/400_xowa/src/gplx/xowa/htmls/portal/Xow_portal_mgr_tst.java index a27517de8..04aba7d39 100644 --- a/400_xowa/src/gplx/xowa/htmls/portal/Xow_portal_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/portal/Xow_portal_mgr_tst.java @@ -13,3 +13,60 @@ 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.htmls.portal; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import org.junit.*; import gplx.core.tests.*; +public class Xow_portal_mgr_tst { + @Before public void init() {fxt.Init();} private Xowh_portal_mgr_fxt fxt = new Xowh_portal_mgr_fxt(); + @Test public void Div_ns_bry() { + fxt.Test_div_ns_bry("A" , "/wiki/A;selected;/wiki/Talk:A;xowa_display_none;"); + fxt.Test_div_ns_bry("Talk:A" , "/wiki/A;;/wiki/Talk:A;selected;"); + } + @Test public void Div_personal_bry() { + fxt.Test_div_personal_bry("/wiki/User:anonymous;anonymous;xowa_display_none;/wiki/User_talk:anonymous;xowa_display_none;"); + } + @Test public void Missing_ns_cls() { + fxt.Test_missing_ns_cls("xowa_display_none"); + } + @Test public void Logo() { + fxt.Portal_mgr().Div_logo_fmtr().Fmt_("~{portal_logo_url}"); + + // day-mode + fxt.Portal_mgr().Init(); + fxt.Test_logo_frag(Bool_.N, "file:///mem/xowa/user/test_user/wiki/en.wikipedia.org/html/logo.png"); + + // night-mode: app + fxt.Test_logo_frag(Bool_.Y, "file:///mem/xowa/bin/any/xowa/html/css/nightmode/logo_night.png"); + + // night-mode: wiki + Io_mgr.Instance.SaveFilStr("mem/xowa/user/test_user/wiki/en.wikipedia.org/html/logo_night.png", ""); + fxt.Portal_mgr().Init(); + fxt.Test_logo_frag(Bool_.Y, "file:///mem/xowa/user/test_user/wiki/en.wikipedia.org/html/logo_night.png"); + } +} +class Xowh_portal_mgr_fxt { + private Xow_portal_mgr portal_mgr; + public void Init() { + if (app == null) { + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + wiki.Ns_mgr().Ns_main().Exists_(true); // needed for ns + this.portal_mgr = wiki.Html_mgr().Portal_mgr(); + portal_mgr.Init_assert(); // needed for personal + portal_mgr.Missing_ns_cls_(Bry_.new_a7("xowa_display_none")); + } + } private Xoae_app app; Xowe_wiki wiki; + public void Test_div_ns_bry(String ttl, String expd) { + Tfds.Eq(expd, String_.new_a7(wiki.Html_mgr().Portal_mgr().Div_ns_bry(wiki.Utl__bfr_mkr(), Xoa_ttl.Parse(wiki, Bry_.new_a7(ttl)), wiki.Ns_mgr()))); + } + public void Test_div_personal_bry(String expd) { + Tfds.Eq(expd, String_.new_a7(wiki.Html_mgr().Portal_mgr().Div_personal_bry())); + } + public void Test_missing_ns_cls(String expd) { + Tfds.Eq(expd, String_.new_a7(wiki.Html_mgr().Portal_mgr().Missing_ns_cls())); + } + public Xow_portal_mgr Portal_mgr() {return wiki.Html_mgr().Portal_mgr();} + public void Test_logo_frag(boolean nightmode, String expd) { + String actl = String_.new_a7(wiki.Html_mgr().Portal_mgr().Div_logo_bry(nightmode)); + Gftest.Eq__str(expd, actl); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/portal/vnts/Vnt_mnu_grp_fmtr.java b/400_xowa/src/gplx/xowa/htmls/portal/vnts/Vnt_mnu_grp_fmtr.java index a27517de8..29c61794e 100644 --- a/400_xowa/src/gplx/xowa/htmls/portal/vnts/Vnt_mnu_grp_fmtr.java +++ b/400_xowa/src/gplx/xowa/htmls/portal/vnts/Vnt_mnu_grp_fmtr.java @@ -13,3 +13,48 @@ 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.htmls.portal.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.portal.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.langs.vnts.*; +public class Vnt_mnu_grp_fmtr implements gplx.core.brys.Bfr_arg { + private final Xolg_vnt_itm_fmtr itm_fmtr = new Xolg_vnt_itm_fmtr(); + private Xol_vnt_regy mgr; private byte[] page_vnt; + public void Init(Xol_vnt_regy mgr, byte[] wiki_domain, byte[] page_href, byte[] page_vnt) { + this.mgr = mgr; this.page_vnt = page_vnt; + itm_fmtr.Init(mgr, wiki_domain, page_href, page_vnt); + } + public void Bfr_arg__add(Bry_bfr bfr) { + Xol_vnt_itm mnu_itm = mgr.Get_by(page_vnt); + fmtr.Bld_bfr_many(bfr, mnu_itm == null ? Bry_.Empty : mnu_itm.Name(), itm_fmtr); + } + private static final Bry_fmtr fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , " " + ), "grp_text", "itms" + ); +} +class Xolg_vnt_itm_fmtr implements gplx.core.brys.Bfr_arg { + private Xol_vnt_regy mgr; private byte[] wiki_domain, page_href, page_vnt; + public void Init(Xol_vnt_regy mgr, byte[] wiki_domain, byte[] page_href, byte[] page_vnt) {this.mgr = mgr; this.wiki_domain = wiki_domain; this.page_href = page_href; this.page_vnt = page_vnt;} + public void Bfr_arg__add(Bry_bfr bfr) { + int len = mgr.Len(); + for (int i = 0; i < len; ++i) { + Xol_vnt_itm itm = mgr.Get_at(i); if (!itm.Visible()) continue; + boolean itm_is_selected = Bry_.Eq(itm.Key(), page_vnt); + byte[] itm_cls_selected = itm_is_selected ? Itm_cls_selected_y : Bry_.Empty; + fmtr.Bld_bfr_many(bfr, i, itm_cls_selected, wiki_domain, itm.Key(), itm.Name(), page_href); + } + } + private static final byte[] Itm_cls_selected_y = Bry_.new_a7(" class='selected'"); + private static final Bry_fmtr fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last // NOTE: using "/site/zh.w/zh-hans/A" instead of "/zh-hans/A" b/c it is easier for href_parser; if /site/ ever needs to truly mean "not-this-site", then change this to "/lang/"; DATE:2015-07-30 + ( "" + , "
    • ~{itm_text}
    • " + ), "itm_idx", "itm_cls_selected", "wiki_domain", "itm_lang", "itm_text", "itm_href" + ); +} diff --git a/400_xowa/src/gplx/xowa/htmls/portal/vnts/Vnt_mnu_grp_fmtr_tst.java b/400_xowa/src/gplx/xowa/htmls/portal/vnts/Vnt_mnu_grp_fmtr_tst.java index a27517de8..61f8c0d67 100644 --- a/400_xowa/src/gplx/xowa/htmls/portal/vnts/Vnt_mnu_grp_fmtr_tst.java +++ b/400_xowa/src/gplx/xowa/htmls/portal/vnts/Vnt_mnu_grp_fmtr_tst.java @@ -13,3 +13,53 @@ 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.htmls.portal.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.portal.*; +import org.junit.*; import gplx.xowa.langs.vnts.*; +public class Vnt_mnu_grp_fmtr_tst { + @Before public void init() {fxt.Clear();} private final Vnt_mnu_grp_fmtr_fxt fxt = new Vnt_mnu_grp_fmtr_fxt(); + @Test public void Basic() { +// fxt.Test_to_str("Earth", "zh-hk", String_.Concat_lines_nl_skip_last +// ( "" +// , " " +// )); + } +} +class Vnt_mnu_grp_fmtr_fxt { + private final Xol_vnt_regy mgr = new Xol_vnt_regy(); + public void Clear() { + this.Init_grp("Choose lang", "zh-hans", "Simplified", "zh-hant", "Traditional", "zh-cn", "China", "zh-hk", "Hong Kong", "zh-mo", "Macau", "zh-sg", "Singapore", "zh-tw", "Taiwan"); + } + public void Init_grp(String text, String... langs) { + mgr.Clear(); + int len = langs.length; + String lang_code = ""; + for (int i = 0; i < len; ++i) { + String lang = langs[i]; + if (i % 2 == 0) + lang_code = lang; + else { + mgr.Add(Bry_.new_u8(lang_code), Bry_.new_u8(lang)); + } + } + } + public void Test_to_str(String page_href, String selected_vnt, String expd) { + Vnt_mnu_grp_fmtr vnt_grp_fmtr = new Vnt_mnu_grp_fmtr(); + Bry_bfr bfr = Bry_bfr_.New(); + vnt_grp_fmtr.Init(mgr, Bry_.new_u8(page_href), Bry_.new_a7("zh.wikipedia.org"), Bry_.new_u8(selected_vnt)); + vnt_grp_fmtr.Bfr_arg__add(bfr); + Tfds.Eq_str_lines(expd, bfr.To_str_and_clear()); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/sections/Xoh_section_itm.java b/400_xowa/src/gplx/xowa/htmls/sections/Xoh_section_itm.java index a27517de8..9da4602dc 100644 --- a/400_xowa/src/gplx/xowa/htmls/sections/Xoh_section_itm.java +++ b/400_xowa/src/gplx/xowa/htmls/sections/Xoh_section_itm.java @@ -13,3 +13,23 @@ 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.htmls.sections; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +public class Xoh_section_itm { + public Xoh_section_itm(int uid, int level, byte[] anchor, byte[] header) { + this.uid = uid; this.level = level; this.anchor = anchor; this.header = header; + } + public int Uid() {return uid;} private final int uid; + public int Level() {return level;} private final int level; + public byte[] Anchor() {return anchor;} private final byte[] anchor; + public byte[] Header() {return header;} private final byte[] header; + public byte[] Content() {return content;} private byte[] content; + public Xoh_section_itm Content_(byte[] v) {this.content = v; return this;} + public int Content_bgn() {return content_bgn;} public Xoh_section_itm Content_bgn_(int v) {content_bgn = v; return this;} private int content_bgn; + public void To_bfr(Bry_bfr bfr) { + bfr.Add_int_variable(uid).Add_byte_pipe(); + bfr.Add_int_variable(level).Add_byte_pipe(); + bfr.Add(anchor).Add_byte_pipe(); + bfr.Add(header).Add_byte_pipe(); + bfr.Add_safe(content).Add_byte_nl(); + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/sections/Xoh_section_mgr.java b/400_xowa/src/gplx/xowa/htmls/sections/Xoh_section_mgr.java index a27517de8..5a6cf61ab 100644 --- a/400_xowa/src/gplx/xowa/htmls/sections/Xoh_section_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/sections/Xoh_section_mgr.java @@ -13,3 +13,29 @@ 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.htmls.sections; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +public class Xoh_section_mgr { + private final List_adp list = List_adp_.New(); + public void Clear() {list.Clear();} + public int Len() {return list.Count();} + public Xoh_section_itm Get_at(int i) {return (Xoh_section_itm)list.Get_at(i);} + public Xoh_section_itm Add(int uid, int level, byte[] anchor, byte[] display) { + Xoh_section_itm rv = new Xoh_section_itm(uid, level, anchor, display); + list.Add(rv); + return rv; + } + public void Set_content(int section_idx, byte[] src, int pos) { + Xoh_section_itm itm = this.Get_at(section_idx); + if (pos > itm.Content_bgn()) + itm.Content_(Bry_.Mid(src, itm.Content_bgn(), pos)); + else // TIDY:tidy will collapse "

      A

      \n\n

      B

      " to "

      A

      \n

      B

      "; this will show up as a pos < itm.Content_bgn; PAGE:en.w:Summer_solstice; DATE:2016-01-04 + itm.Content_(Bry_.Empty); + } + public void To_bfr(Bry_bfr bfr) { + int len = this.Len(); + for (int i = 0; i < len; ++i) { + Xoh_section_itm itm = this.Get_at(i); + itm.To_bfr(bfr); + } + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/skins/Xoh_skin_itm.java b/400_xowa/src/gplx/xowa/htmls/skins/Xoh_skin_itm.java index a27517de8..443a0f389 100644 --- a/400_xowa/src/gplx/xowa/htmls/skins/Xoh_skin_itm.java +++ b/400_xowa/src/gplx/xowa/htmls/skins/Xoh_skin_itm.java @@ -13,3 +13,20 @@ 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.htmls.skins; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +import gplx.core.brys.fmtrs.*; +public class Xoh_skin_itm implements Gfo_invk { + private final Bry_fmtr fmtr = Bry_fmtr.new_(); + public Xoh_skin_itm(String key, String fmt) {this.key = key; fmtr.Fmt_(fmt);} + public String Key() {return key;} private final String key; + public void Fmt_(String v) {fmtr.Fmt_(v);} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_fmt)) return String_.new_u8(fmtr.Fmt()); + else if (ctx.Match(k, Invk_fmt_)) fmtr.Fmt_(m.ReadStr("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk_fmt = "fmt", Invk_fmt_ = "fmt_" + ; +} diff --git a/400_xowa/src/gplx/xowa/htmls/skins/Xoh_skin_mgr.java b/400_xowa/src/gplx/xowa/htmls/skins/Xoh_skin_mgr.java index a27517de8..f2846fdc9 100644 --- a/400_xowa/src/gplx/xowa/htmls/skins/Xoh_skin_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/skins/Xoh_skin_mgr.java @@ -13,3 +13,37 @@ 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.htmls.skins; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +public class Xoh_skin_mgr implements Gfo_invk { + private final Xoh_skin_regy regy = new Xoh_skin_regy(); + public Xoh_skin_mgr() { + read = make_and_add(regy, "read"); + edit = make_and_add(regy, "edit"); + html = make_and_add(regy, "html"); + } + public Xoh_skin_itm Read() {return read;} private Xoh_skin_itm read; + public Xoh_skin_itm Edit() {return edit;} private Xoh_skin_itm edit; + public Xoh_skin_itm Html() {return html;} private Xoh_skin_itm html; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_read)) return read; + else if (ctx.Match(k, Invk_read_)) read = regy.Get_by_key(m.ReadStr("v")); + else if (ctx.Match(k, Invk_edit)) return edit; + else if (ctx.Match(k, Invk_edit_)) edit = regy.Get_by_key(m.ReadStr("v")); + else if (ctx.Match(k, Invk_html)) return html; + else if (ctx.Match(k, Invk_html_)) html = regy.Get_by_key(m.ReadStr("v")); + else if (ctx.Match(k, Invk_set)) regy.Set(m.ReadStr("key"), m.ReadStr("fmt")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk_read = "read", Invk_read_ = "read_" + , Invk_edit = "edit", Invk_edit_ = "edit_" + , Invk_html = "html", Invk_html_ = "html_" + , Invk_set = "set" + ; + private static Xoh_skin_itm make_and_add(Xoh_skin_regy regy, String key) { + Xoh_skin_itm rv = new Xoh_skin_itm(key, key); + regy.Add(rv); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/htmls/skins/Xoh_skin_regy.java b/400_xowa/src/gplx/xowa/htmls/skins/Xoh_skin_regy.java index a27517de8..434d5e280 100644 --- a/400_xowa/src/gplx/xowa/htmls/skins/Xoh_skin_regy.java +++ b/400_xowa/src/gplx/xowa/htmls/skins/Xoh_skin_regy.java @@ -13,3 +13,20 @@ 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.htmls.skins; import gplx.*; import gplx.xowa.*; import gplx.xowa.htmls.*; +class Xoh_skin_regy { + private final Ordered_hash hash = Ordered_hash_.New(); + public int Len() {return hash.Count();} + public Xoh_skin_itm Get_at(int i) {return (Xoh_skin_itm)hash.Get_at(i);} + public Xoh_skin_itm Get_by_key(String key) {return (Xoh_skin_itm)hash.Get_by(key);} + public void Set(String key, String fmt) { + Xoh_skin_itm itm = Get_by_key(key); + if (itm == null) { + itm = new Xoh_skin_itm(key, fmt); + Add(itm); + } + else + itm.Fmt_(fmt); + } + public void Add(Xoh_skin_itm itm) {hash.Add(itm.Key(), itm);} +} diff --git a/400_xowa/src/gplx/xowa/langs/Xoa_lang_mgr.java b/400_xowa/src/gplx/xowa/langs/Xoa_lang_mgr.java index a27517de8..462efe03e 100644 --- a/400_xowa/src/gplx/xowa/langs/Xoa_lang_mgr.java +++ b/400_xowa/src/gplx/xowa/langs/Xoa_lang_mgr.java @@ -13,3 +13,41 @@ 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.langs; import gplx.*; import gplx.xowa.*; +import gplx.xowa.apps.fsys.*; import gplx.xowa.apps.gfs.*; +import gplx.xowa.langs.bldrs.*; +public class Xoa_lang_mgr implements Gfo_invk { + private final Ordered_hash hash = Ordered_hash_.New_bry(); + private final Xobc_utl_make_lang mw_converter; + public Xoa_lang_mgr(Xoa_app app, Xoa_gfs_mgr gfs_mgr) { + this.mw_converter = new Xobc_utl_make_lang(this, app.Fsys_mgr(), app.Tid_is_edit() ? ((Xoae_app)app).Msg_log() : null); + this.lang_en = Xol_lang_itm_.Lang_en_make(this); this.Add(lang_en); + this.gfs_mgr = gfs_mgr; + } + public Xoa_gfs_mgr Gfs_mgr() {return gfs_mgr;} private final Xoa_gfs_mgr gfs_mgr; + public Xol_lang_itm Lang_en() {return lang_en;} private final Xol_lang_itm lang_en; + public void Clear() {hash.Clear();} + public int Len() {return hash.Count();} + public void Add(Xol_lang_itm itm) {hash.Add(itm.Key_bry(), itm);} + public Xol_lang_itm Get_at(int i) {return (Xol_lang_itm)hash.Get_at(i);} + public Xol_lang_itm Get_by(byte[] key) {return (Xol_lang_itm)hash.Get_by(key);} + public Xol_lang_itm Get_by_or_load(byte[] key) {return Get_by_or_new(key).Init_by_load_assert();} + public Xol_lang_itm Get_by_or_en(byte[] key) { // called by Xowv_wiki for its .Lang() + Xol_lang_itm rv = Get_by(key); + return rv == null ? lang_en : rv; + } + public Xol_lang_itm Get_by_or_new(byte[] key) { + Xol_lang_itm rv = Get_by(key); + if (rv == null) { + rv = new Xol_lang_itm(this, key); + this.Add(rv); + } + return rv; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_get)) return Get_by_or_new(m.ReadBry("key")); + else if (ctx.Match(k, Invk_mediawiki_converter)) return mw_converter; + else return Gfo_invk_.Rv_unhandled; + } private static final String Invk_get = "get", Invk_mediawiki_converter = "mediawiki_converter"; + public static final byte[] Fallback_false = Bry_.new_a7("false"); +} 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 a27517de8..63f7db49d 100644 --- a/400_xowa/src/gplx/xowa/langs/Xol_lang_itm.java +++ b/400_xowa/src/gplx/xowa/langs/Xol_lang_itm.java @@ -13,3 +13,159 @@ 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.langs; import gplx.*; import gplx.xowa.*; +import gplx.core.envs.*; +import gplx.gfui.draws.*; +import gplx.xowa.langs.cases.*; import gplx.xowa.langs.msgs.*; import gplx.xowa.langs.kwds.*; import gplx.xowa.langs.grammars.*; import gplx.xowa.langs.genders.*; import gplx.xowa.langs.plurals.*; import gplx.xowa.langs.vnts.*; import gplx.xowa.langs.vnts.converts.*; import gplx.xowa.langs.numbers.*; import gplx.xowa.langs.durations.*; import gplx.xowa.langs.lnki_trails.*; import gplx.xowa.langs.funcs.*; import gplx.xowa.langs.specials.*; import gplx.xowa.langs.bldrs.*; import gplx.xowa.langs.commas.*; +import gplx.xowa.apps.gfs.*; import gplx.xowa.apps.fsys.*; import gplx.core.intls.*; import gplx.xowa.wikis.nss.*; import gplx.xowa.xtns.lst.*; import gplx.xowa.wikis.caches.*; import gplx.xowa.parsers.lnkis.*; +import gplx.xowa.guis.langs.*; +public class Xol_lang_itm implements Gfo_invk { + private boolean loaded = false; + public Xol_lang_itm(Xoa_lang_mgr lang_mgr, byte[] key_bry) { + this.lang_mgr = lang_mgr; this.key_bry = key_bry; this.key_str = String_.new_u8(key_bry); + Xol_lang_stub lang_itm = Xol_lang_stub_.Get_by_key_or_null(key_bry); if (lang_itm == null) throw Err_.new_wo_type("unknown lang_key", "key", String_.new_u8(key_bry)); + this.lang_id = lang_itm.Id(); + this.func_regy = new Xol_func_regy(lang_mgr, this); + this.ns_names = new Xol_ns_grp(this); this.ns_aliases = new Xol_ns_grp(this); + this.kwd_mgr = new Xol_kwd_mgr(this); + this.msg_mgr = new Xol_msg_mgr(this, true); + this.specials_mgr = new Xol_specials_mgr(this); + this.case_mgr = Env_.Mode_testing() ? Xol_case_mgr_.A7() : Xol_case_mgr_.U8(); // NOTE: if test load ascii b/c utf8 is large; NOTE: placed here b/c tests do not call load; DATE:2014-07-04 + this.num_mgr = Xol_num_mgr_.new_by_lang_id(lang_id); + this.lnki_trail_mgr = new Xol_lnki_trail_mgr(this); + this.vnt_mgr = new Xol_vnt_mgr(this); + this.grammar = Xol_grammar_.new_by_lang_id(lang_id); + this.gender = Xol_gender_.new_by_lang_id(lang_id); + this.plural = Xol_plural_.new_by_lang_id(lang_id); + this.duration_mgr = new Xol_duration_mgr(this); + if (lang_id != Xol_lang_stub_.Id_en) fallback_bry_ary = Fallback_bry_ary__en; // NOTE: do not set fallback_ary for en to en, else recursive loop + } + public Xoa_lang_mgr Lang_mgr() {return lang_mgr;} private final Xoa_lang_mgr lang_mgr; + public byte[] Key_bry() {return key_bry;} private final byte[] key_bry; + public String Key_str() {return key_str;} private final String key_str; + public int Lang_id() {return lang_id;} private final int lang_id; + public Xol_ns_grp Ns_names() {return ns_names;} private final Xol_ns_grp ns_names; + public Xol_ns_grp Ns_aliases() {return ns_aliases;} private final Xol_ns_grp ns_aliases; + public Xol_kwd_mgr Kwd_mgr() {return kwd_mgr;} private final Xol_kwd_mgr kwd_mgr; + public boolean Kwd_mgr__strx() {return kwd_mgr__strx;} public Xol_lang_itm Kwd_mgr__strx_(boolean v) {kwd_mgr__strx = v; return this;} private boolean kwd_mgr__strx; + public Xol_msg_mgr Msg_mgr() {return msg_mgr;} private final Xol_msg_mgr msg_mgr; + public Xol_specials_mgr Specials_mgr() {return specials_mgr;} private final Xol_specials_mgr specials_mgr; + public Xol_case_mgr Case_mgr() {return case_mgr;} private Xol_case_mgr case_mgr; + public void Case_mgr_u8_() {case_mgr = Xol_case_mgr_.U8();} // TEST: + public Xol_lang_itm Case_mgr_(Xol_case_mgr v) {this.case_mgr = v; return this;} // TEST: + public Xol_comma_wkr Comma_wkr() {return comma_wkr;} private final Xol_comma_wkr comma_wkr = new Xol_comma_wkr__add(); + public Xol_font_info Gui_font() {return gui_font;} private final Xol_font_info gui_font = new Xol_font_info(null, 0, FontStyleAdp_.Plain); + public byte[] Fallback_bry() {return fallback_bry;} + public Xol_lang_itm Fallback_bry_(byte[] v) { + fallback_bry = v; + fallback_bry_ary = Fallbacy_bry_ary__bld(v); + return this; + } private byte[] fallback_bry; + public byte[][] Fallback_bry_ary() {return fallback_bry_ary;} private byte[][] fallback_bry_ary = Bry_.Ary_empty; + public boolean Dir_ltr() {return dir_ltr;} private boolean dir_ltr = true; + public void Dir_ltr_(boolean v) { + dir_ltr = v; + img_thumb_halign_default = dir_ltr ? Xop_lnki_align_h_.Right : Xop_lnki_align_h_.Left; + } + public byte[] Dir_ltr_bry() {return dir_ltr ? Dir_bry_ltr : Dir_bry_rtl;} + public Xol_num_mgr Num_mgr() {return num_mgr;} private final Xol_num_mgr num_mgr; + public Xol_vnt_mgr Vnt_mgr() {return vnt_mgr;} private final Xol_vnt_mgr vnt_mgr; + public Xol_grammar Grammar() {return grammar;} private final Xol_grammar grammar; + public Xol_gender Gender() {return gender;} private final Xol_gender gender; + public Xol_plural Plural() {return plural;} private final Xol_plural plural; + public Xol_duration_mgr Duration_mgr() {return duration_mgr;} private final Xol_duration_mgr duration_mgr; + public Xol_lnki_trail_mgr Lnki_trail_mgr() {return lnki_trail_mgr;} private final Xol_lnki_trail_mgr lnki_trail_mgr; + public Xop_lnki_arg_parser Lnki_arg_parser() {return lnki_arg_parser;} private Xop_lnki_arg_parser lnki_arg_parser = new Xop_lnki_arg_parser(); + public Xol_func_regy Func_regy() {return func_regy;} private final Xol_func_regy func_regy; + public int Img_thumb_halign_default() {return img_thumb_halign_default;} private int img_thumb_halign_default = Xop_lnki_align_h_.Right; + public Hash_adp_bry Xatrs_section() {if (xatrs_section == null) xatrs_section = Lst_section_nde.new_xatrs_(this); return xatrs_section;} private Hash_adp_bry xatrs_section; + public void Evt_lang_changed() { + lnki_arg_parser.Evt_lang_changed(this); + func_regy.Evt_lang_changed(this); + comma_wkr.Evt_lang_changed(this); + } + private byte[] X_axis_end() {return dir_ltr ? X_axis_end_right : X_axis_end_left;} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_ns_names)) return ns_names; + else if (ctx.Match(k, Invk_ns_aliases)) return ns_aliases; + else if (ctx.Match(k, Invk_keywords)) return kwd_mgr; + else if (ctx.Match(k, Invk_messages)) return msg_mgr; + else if (ctx.Match(k, Invk_specials)) return specials_mgr; + else if (ctx.Match(k, Invk_casings)) return case_mgr; + else if (ctx.Match(k, Invk_converts)) return vnt_mgr.Convert_mgr().Converter_regy(); + else if (ctx.Match(k, Invk_variants)) return vnt_mgr; + else if (ctx.Match(k, Invk_dir_rtl_)) Dir_ltr_(!m.ReadYn("v")); + else if (ctx.Match(k, Invk_dir_str)) return Dir_ltr_bry(); + else if (ctx.Match(k, Invk_gui_font_)) gui_font.Name_(m.ReadStr("name")).Size_(m.ReadFloatOr("size", 8)); + else if (ctx.Match(k, Invk_fallback_load)) Exec_fallback_load(m.ReadBry("v")); + else if (ctx.Match(k, Invk_numbers)) return num_mgr; + else if (ctx.Match(k, Invk_link_trail)) return lnki_trail_mgr; + else if (ctx.Match(k, Invk_x_axis_end)) return String_.new_u8(X_axis_end()); + else if (ctx.Match(k, Invk_this)) return this; + else if (ctx.Match(k, Xoae_app.Invk_app)) return lang_mgr.Gfs_mgr().Root_invk(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk_ns_names = "ns_names", Invk_ns_aliases = "ns_aliases" + , Invk_keywords = "keywords", Invk_messages = "messages", Invk_specials = "specials", Invk_casings = "casings", Invk_converts = "converts", Invk_variants = "variants" + , Invk_numbers = "numbers" + , Invk_dir_rtl_ = "dir_rtl_", Invk_gui_font_ = "gui_font_" + , Invk_fallback_load = "fallback_load", Invk_this = "this", Invk_dir_str = "dir_str", Invk_link_trail = "link_trail" + , Invk_x_axis_end = "x_axis_end" + ; + + private static final Hash_adp_bry fallback_dupes_regy = Hash_adp_bry.cs(); // to prevent cyclical loops during loading + public Xol_lang_itm Init_by_load_assert() {if (!loaded) Init_by_load(); return this;} + public boolean Init_by_load() { + if (this.loaded) return false; + fallback_dupes_regy.Clear(); + this.loaded = true; + boolean lang_is_en = lang_id == Xol_lang_stub_.Id_en; + if (!lang_is_en) Xol_lang_itm_.Lang_init(this); + msg_mgr.Itm_by_key_or_new(Bry_.new_a7("Lang")).Atrs_set(key_bry, false, false); // set "Lang" keyword; EX: for "fr", "{{int:Lang}}" -> "fr" + Load_lang(key_bry); + ns_aliases.Ary_add_(Xow_ns_canonical_.Ary); // NOTE: always add English canonical as aliases to all languages + this.Evt_lang_changed(); + return true; + } + private void Exec_fallback_load(byte[] fallback_lang) { + Fallback_bry_(fallback_lang); + if (fallback_dupes_regy.Has(fallback_lang)) return; // fallback_lang loaded; avoid recursive loop; EX: zh with fallback of zh-hans which has fallback of zh + if (Bry_.Eq(fallback_lang, Xoa_lang_mgr.Fallback_false)) return; // fallback_lang is "none" exit + fallback_dupes_regy.Add(fallback_lang, fallback_lang); + Load_lang(fallback_lang); + fallback_dupes_regy.Del(fallback_lang); + } + private void Load_lang(byte[] v) { + Xoa_gfs_mgr gfs_mgr = lang_mgr.Gfs_mgr(); Xoa_fsys_mgr app_fsys_mgr = gfs_mgr.App_fsys_mgr(); + gfs_mgr.Run_url_for(this, Xol_lang_itm_.xo_lang_fil_(app_fsys_mgr, String_.new_a7(v))); + gfs_mgr.Run_url_for(gfs_mgr.Root_invk(), Xol_convert_regy.Bld_url(app_fsys_mgr, key_str)); + } + private static final byte[] + Dir_bry_ltr = Bry_.new_a7("ltr"), Dir_bry_rtl = Bry_.new_a7("rtl") + , X_axis_end_right = Bry_.new_a7("right"), X_axis_end_left = Bry_.new_a7("left") + ; + public static final int Tid_lower = 1, Tid_upper = 2; + private static byte[][] Fallbacy_bry_ary__bld(byte[] v) { + byte[][] rv = Bry_split_.Split(v, Byte_ascii.Comma, true); // gan -> 'gan-hant, zh-hant, zh-hans' + boolean en_needed = true; + int rv_len = rv.length; + for (int i = 0; i < rv_len; i++) { + byte[] itm = rv[i]; + if (Bry_.Eq(itm, Xol_lang_itm_.Key_en)) { + en_needed = false; + break; + } + } + if (en_needed) { + int new_len = rv_len + 1; + byte[][] new_ary = new byte[new_len][]; + for (int i = 0; i < rv_len; i++) + new_ary[i] = rv[i]; + new_ary[rv_len] = Xol_lang_itm_.Key_en; + rv = new_ary; + } + return rv; + } + private static final byte[][] Fallback_bry_ary__en = new byte[][] {Xol_lang_itm_.Key_en}; +} 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 a27517de8..115b077e2 100644 --- a/400_xowa/src/gplx/xowa/langs/Xol_lang_itm_.java +++ b/400_xowa/src/gplx/xowa/langs/Xol_lang_itm_.java @@ -13,3 +13,266 @@ 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.langs; import gplx.*; import gplx.xowa.*; +import gplx.core.intls.*; import gplx.xowa.xtns.cites.*; import gplx.xowa.xtns.gallery.*; +import gplx.xowa.langs.bldrs.*; import gplx.xowa.langs.numbers.*; import gplx.xowa.langs.kwds.*; +import gplx.xowa.apps.fsys.*; +public class Xol_lang_itm_ { + public static Io_url xo_lang_fil_(Xoa_fsys_mgr app_fsys_mgr, String lang_key) {return app_fsys_mgr.Cfg_lang_core_dir().GenSubFil(lang_key + ".gfs");} + public static final byte Char_tid_ltr_l = 0, Char_tid_ltr_u = 1, Char_tid_num = 2, Char_tid_ws = 3, Char_tid_sym = 4, Char_tid_misc = 5; + public static byte Char_tid(byte b) { + switch (b) { + case Byte_ascii.Ltr_A: case Byte_ascii.Ltr_B: case Byte_ascii.Ltr_C: case Byte_ascii.Ltr_D: case Byte_ascii.Ltr_E: + case Byte_ascii.Ltr_F: case Byte_ascii.Ltr_G: case Byte_ascii.Ltr_H: case Byte_ascii.Ltr_I: case Byte_ascii.Ltr_J: + case Byte_ascii.Ltr_K: case Byte_ascii.Ltr_L: case Byte_ascii.Ltr_M: case Byte_ascii.Ltr_N: case Byte_ascii.Ltr_O: + case Byte_ascii.Ltr_P: case Byte_ascii.Ltr_Q: case Byte_ascii.Ltr_R: case Byte_ascii.Ltr_S: case Byte_ascii.Ltr_T: + case Byte_ascii.Ltr_U: case Byte_ascii.Ltr_V: case Byte_ascii.Ltr_W: case Byte_ascii.Ltr_X: case Byte_ascii.Ltr_Y: case Byte_ascii.Ltr_Z: + return Char_tid_ltr_u; + case Byte_ascii.Ltr_a: case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_e: + case Byte_ascii.Ltr_f: case Byte_ascii.Ltr_g: case Byte_ascii.Ltr_h: case Byte_ascii.Ltr_i: case Byte_ascii.Ltr_j: + case Byte_ascii.Ltr_k: case Byte_ascii.Ltr_l: case Byte_ascii.Ltr_m: case Byte_ascii.Ltr_n: case Byte_ascii.Ltr_o: + case Byte_ascii.Ltr_p: case Byte_ascii.Ltr_q: case Byte_ascii.Ltr_r: case Byte_ascii.Ltr_s: case Byte_ascii.Ltr_t: + case Byte_ascii.Ltr_u: case Byte_ascii.Ltr_v: case Byte_ascii.Ltr_w: case Byte_ascii.Ltr_x: case Byte_ascii.Ltr_y: case Byte_ascii.Ltr_z: + return Char_tid_ltr_l; + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + return Char_tid_num; + case Byte_ascii.Space: case Byte_ascii.Nl: case Byte_ascii.Tab: case Byte_ascii.Cr: + return Char_tid_ws; + default: + return Char_tid_misc; + } + } + public static final byte[] Key_en = Bry_.new_a7("en"); + public static Xol_lang_itm Lang_en_make(Xoa_lang_mgr lang_mgr) { + Xol_lang_itm rv = new Xol_lang_itm(lang_mgr, Xol_lang_itm_.Key_en); + Xol_lang_itm_.Lang_init(rv); + rv.Evt_lang_changed(); + return rv; + } + public static void Lang_init(Xol_lang_itm lang) { + lang.Num_mgr().Separators_mgr().Set(Xol_num_mgr.Separators_key__grp, Xol_num_mgr.Separators_key__grp); + lang.Num_mgr().Separators_mgr().Set(Xol_num_mgr.Separators_key__dec, Xol_num_mgr.Separators_key__dec); + lang.Lnki_trail_mgr().Add_range(Byte_ascii.Ltr_a, Byte_ascii.Ltr_z);// REF.MW:MessagesEn.php|$linkTrail = '/^([a-z]+)(.*)$/sD'; +Xol_kwd_mgr kwd_mgr = lang.Kwd_mgr(); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_redirect, "#REDIRECT"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_notoc, "__NOTOC__"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_nogallery, "__NOGALLERY__"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_forcetoc, "__FORCETOC__"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_toc, "__TOC__"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_noeditsection, "__NOEDITSECTION__"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_noheader, "__NOHEADER__"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_utc_month_int_len2, "CURRENTMONTH", "CURRENTMONTH2"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_utc_month_int, "CURRENTMONTH1"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_utc_month_name, "CURRENTMONTHNAME"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_utc_month_gen, "CURRENTMONTHNAMEGEN"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_utc_month_abrv, "CURRENTMONTHABBREV"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_utc_day_int, "CURRENTDAY"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_utc_day_int_len2, "CURRENTDAY2"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_utc_day_name, "CURRENTDAYNAME"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_utc_year, "CURRENTYEAR"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_utc_time, "CURRENTTIME"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_utc_hour, "CURRENTHOUR"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_lcl_month_int_len2, "LOCALMONTH", "LOCALMONTH2"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_lcl_month_int, "LOCALMONTH1"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_lcl_month_name, "LOCALMONTHNAME"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_lcl_month_gen, "LOCALMONTHNAMEGEN"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_lcl_month_abrv, "LOCALMONTHABBREV"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_lcl_day_int, "LOCALDAY"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_lcl_day_int_len2, "LOCALDAY2"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_lcl_day_name, "LOCALDAYNAME"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_lcl_year, "LOCALYEAR"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_lcl_time, "LOCALTIME"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_lcl_hour, "LOCALHOUR"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_num_pages, "NUMBEROFPAGES"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_num_articles, "NUMBEROFARTICLES"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_num_files, "NUMBEROFFILES"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_num_users, "NUMBEROFUSERS"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_num_users_active, "NUMBEROFACTIVEUSERS"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_num_edits, "NUMBEROFEDITS"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_num_views, "NUMBEROFVIEWS"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ttl_page_txt, "PAGENAME"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ttl_page_url, "PAGENAMEE"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ns_txt, "NAME"+"SPACE"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ns_url, "NAME"+"SPACEE"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ns_talk_txt, "TALKSPACE"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ns_talk_url, "TALKSPACEE"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ns_subj_txt, "SUBJECTSPACE", "ARTICLESPACE"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ns_subj_url, "SUBJECTSPACEE", "ARTICLESPACEE"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ttl_full_txt, "FULLPAGENAME"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ttl_full_url, "FULLPAGENAMEE"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ttl_leaf_txt, "SUBPAGENAME"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ttl_leaf_url, "SUBPAGENAMEE"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ttl_base_txt, "BASEPAGENAME"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ttl_base_url, "BASEPAGENAMEE"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ttl_talk_txt, "TALKPAGENAME"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ttl_talk_url, "TALKPAGENAMEE"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ttl_subj_txt, "SUBJECTPAGENAME", "ARTICLEPAGENAME"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ttl_subj_url, "SUBJECTPAGENAMEE", "ARTICLEPAGENAMEE"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_ttl_root_txt, "ROOTPAGENAME"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_ttl_root_url, "ROOTPAGENAMEE"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_msg, "msg"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_subst, "subst:"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_safesubst, "safesubst:"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_msgnw, "msgnw"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_thumbnail, "thumbnail", "thumb"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_manualthumb, "thumbnail", "thumb"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_framed, "framed", "enframed", "frame"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_frameless, "frameless"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_upright, "upright"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_upright_factor, "upright_factor"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_border, "border"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_align, "align"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_valign, "valign"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_alt, "alt"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_class, "class"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_caption, "caption"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_link_url, "link-url"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_link_title, "link-title"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_link_target, "link-target"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_link_none, "no-link"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_width, "px"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_page, "page"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_none, "none"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_right, "right"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_center, "center", "centre"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_left, "left"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_baseline, "baseline"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_sub, "sub"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_super, "super", "sup"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_top, "top"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_text_top, "text-top"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_middle, "middle"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_bottom, "bottom"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_text_bottom, "text-bottom"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_img_link, "link"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_i18n_int, "int"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_site_sitename, "SITENAME"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_url_ns, "ns"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_url_nse, "nse"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_url_localurl, "localurl"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_url_localurle, "localurle"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_site_articlepath, "ARTICLEPATH"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_site_server, "SERVER"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_site_servername, "SERVERNAME"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_site_scriptpath, "SCRIPTPATH"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_site_stylepath, "STYLEPATH"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_i18n_grammar, "grammar"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_i18n_gender, "gender"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_notitleconvert, "__NOTITLECONVERT__", "__NOTC__"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_nocontentconvert, "__NOCONTENTCONVERT__", "__NOCC__"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_utc_week, "CURRENTWEEK"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_utc_dow, "CURRENTDOW"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_lcl_week, "LOCALWEEK"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_lcl_dow, "LOCALDOW"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_rev_id, "REVISIONID"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_rev_day_int, "REVISIONDAY"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_rev_day_int_len2, "REVISIONDAY2"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_rev_month_int_len2, "REVISIONMONTH"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_rev_month_int, "REVISIONMONTH1"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_rev_year, "REVISIONYEAR"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_rev_timestamp, "REVISIONTIMESTAMP"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_rev_user, "REVISIONUSER"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_i18n_plural, "plural"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_url_fullurl, "fullurl"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_url_fullurle, "fullurle"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_str_lcfirst, "lcfirst"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_str_ucfirst, "ucfirst"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_str_lc, "lc"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_str_uc, "uc"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_raw, "raw"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_page_displaytitle, "DISPLAYTITLE"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_str_rawsuffix, "R"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_newsectionlink, "__NEWSECTIONLINK__"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_nonewsectionlink, "__NONEWSECTIONLINK__"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_site_currentversion, "CURRENTVERSION"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_url_urlencode, "urlencode"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_url_anchorencode, "anchorencode"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_utc_timestamp, "CURRENTTIMESTAMP"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_lcl_timestamp, "LOCALTIMESTAMP"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_site_directionmark, "DIRECTIONMARK", "DIRMARK"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_i18n_language, "#language"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_site_contentlanguage, "CONTENTLANGUAGE", "CONTENTLANG"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_site_pagesinnamespace, "PAGESINNAMESPACE", "PAGESINNS"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_num_admins, "NUMBEROFADMINS"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_str_formatnum, "formatnum"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_str_padleft, "padleft"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_str_padright, "padright"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_misc_special, "#special"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_page_defaultsort, "DEFAULTSORT", "DEFAULTSORTKEY", "DEFAULTCATEGORYSORT"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_url_filepath, "filepath"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_misc_tag, "#tag"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_hiddencat, "__HIDDENCAT__"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_site_pagesincategory, "PAGESINCATEGORY", "PAGESINCAT"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_rev_pagesize, "PAGESIZE"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_index, "__INDEX__"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_noindex, "__NOINDEX__"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_site_numberingroup, "NUMBERINGROUP", "NUMINGROUP"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_staticredirect, "__STATICREDIRECT__"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_rev_protectionlevel, "PROTECTIONLEVEL"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_str_formatdate, "#formatdate", "#dateformat"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_url_path, "path"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_url_wiki, "wiki"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_url_query, "query"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_xtn_expr, "#expr"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_xtn_if, "#if"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_xtn_ifeq, "#ifeq"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_xtn_ifexpr, "#ifexpr"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_xtn_iferror, "#iferror"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_xtn_switch, "#switch"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_xtn_default, "#default"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_xtn_ifexist, "#ifexist"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_xtn_time, "#time"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_xtn_timel, "#timel"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_xtn_rel2abs, "#rel2abs"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_xtn_titleparts, "#titleparts"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_xowa_dbg, "#xowa_dbg"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ogg_noplayer, "noplayer"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ogg_noicon, "noicon"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_ogg_thumbtime, "thumbtime"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_xtn_geodata_coordinates, "#coordinates"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_url_canonicalurl, "canonicalurl"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_url_canonicalurle, "canonicalurle"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_lst, "#lst", "#section"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_lstx, "#lstx", "#section-x"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_lsth, "#lsth", "#section-h"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_invoke, "#invoke"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_property, "#property"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_noexternallanglinks, "noexternallanglinks"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_ns_num, "namespacenumber"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_page_id, "pageid"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_disambig, "__DISAMBIG__"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_nocommafysuffix, "NOSEP"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_xowa, "#xowa"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_mapSources_deg2dd, "#deg2dd"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_mapSources_dd2dms, "#dd2dms"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_mapSources_geoLink, "#geolink"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_geoCrumbs_isin, "#isin"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_relatedArticles, "#related"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_insider, "#insider"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_massMessage_target, "#target"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_cascadingSources, "CASCADINGSOURCES"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_pendingChangeLevel, "PENDINGCHANGELEVEL"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_pagesUsingPendingChanges, "PAGESUSINGPENDINGCHANGES"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_bang, "!"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_wbreponame, "wbreponame"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_strx_len, "#len"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_strx_pos, "#pos"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_strx_rpos, "#rpos"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_strx_sub, "#sub"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_strx_count, "#count"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_strx_replace, "#replace"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_strx_explode, "#explode"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_strx_urldecode, "#urldecode"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_pagesincategory_pages, "pagesincategory_pages", "pages"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_pagesincategory_subcats, "pagesincategory_subcats", "subcats"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_pagesincategory_files, "pagesincategory_files", "files"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_rev_revisionsize, "REVISIONSIZE"); +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_pagebanner, "PAGEBANNER"); // NOTE: must be casematch; EX: in en.v, {{pagebanner}} is actually template name which calls {{PAGEBANNER}} +kwd_mgr.New(Bool_.Y, Xol_kwd_grp_.Id_rev_protectionexpiry, "PROTECTIONEXPIRY"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_new_window_link, "#NewWindowLink"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_categorytree, "#categorytree"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_assessment, "#assessment"); +kwd_mgr.New(Bool_.N, Xol_kwd_grp_.Id_statements, "#statements"); + } +} diff --git a/400_xowa/src/gplx/xowa/langs/Xol_lang_stub.java b/400_xowa/src/gplx/xowa/langs/Xol_lang_stub.java index a27517de8..4db4adba9 100644 --- a/400_xowa/src/gplx/xowa/langs/Xol_lang_stub.java +++ b/400_xowa/src/gplx/xowa/langs/Xol_lang_stub.java @@ -13,3 +13,10 @@ 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.langs; import gplx.*; import gplx.xowa.*; +public class Xol_lang_stub { + public Xol_lang_stub(int id, byte[] key, byte[] canonical_name) {this.id = id; this.key = key; this.canonical_name = canonical_name;} + public int Id() {return id;} private final int id; // EX: 1 + public byte[] Key() {return key;} private final byte[] key; // EX: de + public byte[] Canonical_name() {return canonical_name;} private final byte[] canonical_name; // EX: Deutsch +} diff --git a/400_xowa/src/gplx/xowa/langs/Xol_lang_stub_.java b/400_xowa/src/gplx/xowa/langs/Xol_lang_stub_.java index a27517de8..9fb8f9416 100644 --- a/400_xowa/src/gplx/xowa/langs/Xol_lang_stub_.java +++ b/400_xowa/src/gplx/xowa/langs/Xol_lang_stub_.java @@ -13,3 +13,950 @@ 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.langs; import gplx.*; import gplx.xowa.*; +import gplx.core.lists.*; +public class Xol_lang_stub_ { // lists all known stub_ary supported by MW + public static final byte[] Key__unknown = Bry_.Empty; + public static final ComparerAble Comparer_key = new Xol_sub_itm_comparer(); + public static final int + Id__intl = -2 +, Id__unknown = -1 +, Id_en = 0 +, Id_aa = 1 +, Id_ab = 2 +, Id_ace = 3 +, Id_ady = 4 +, Id_ady_cyrl = 5 +, Id_aeb = 6 +, Id_aeb_arab = 7 +, Id_aeb_latn = 8 +, Id_af = 9 +, Id_ak = 10 +, Id_akz = 11 +, Id_aln = 12 +, Id_als = 13 +, Id_am = 14 +, Id_an = 15 +, Id_ang = 16 +, Id_anp = 17 +, Id_ar = 18 +, Id_arc = 19 +, Id_arn = 20 +, Id_aro = 21 +, Id_arq = 22 +, Id_ary = 23 +, Id_arz = 24 +, Id_as = 25 +, Id_ase = 26 +, Id_ast = 27 +, Id_atj = 28 +, Id_av = 29 +, Id_avk = 30 +, Id_awa = 31 +, Id_ay = 32 +, Id_az = 33 +, Id_azb = 34 +, Id_ba = 35 +, Id_ban = 36 +, Id_bar = 37 +, Id_bat_smg = 38 +, Id_bbc = 39 +, Id_bbc_latn = 40 +, Id_bcc = 41 +, Id_bcl = 42 +, Id_be = 43 +, Id_be_tarask = 44 +, Id_be_x_old = 45 +, Id_bew = 46 +, Id_bg = 47 +, Id_bgn = 48 +, Id_bh = 49 +, Id_bho = 50 +, Id_bi = 51 +, Id_bjn = 52 +, Id_bm = 53 +, Id_bn = 54 +, Id_bo = 55 +, Id_bpy = 56 +, Id_bqi = 57 +, Id_br = 58 +, Id_brh = 59 +, Id_bs = 60 +, Id_bto = 61 +, Id_bug = 62 +, Id_bxr = 63 +, Id_ca = 64 +, Id_cbk_zam = 65 +, Id_cdo = 66 +, Id_ce = 67 +, Id_ceb = 68 +, Id_ch = 69 +, Id_chm = 70 +, Id_cho = 71 +, Id_chr = 72 +, Id_chy = 73 +, Id_ckb = 74 +, Id_co = 75 +, Id_cps = 76 +, Id_cr = 77 +, Id_crh = 78 +, Id_crh_cyrl = 79 +, Id_crh_latn = 80 +, Id_cs = 81 +, Id_csb = 82 +, Id_cu = 83 +, Id_cv = 84 +, Id_cy = 85 +, Id_da = 86 +, Id_de = 87 +, Id_de_at = 88 +, Id_de_ch = 89 +, Id_de_formal = 90 +, Id_diq = 91 +, Id_dsb = 92 +, Id_dtp = 93 +, Id_dty = 94 +, Id_dv = 95 +, Id_dz = 96 +, Id_ee = 97 +, Id_egl = 98 +, Id_el = 99 +, Id_eml = 100 +, Id_en_ca = 101 +, Id_en_gb = 102 +, Id_en_rtl = 103 +, Id_enrtl = 104 +, Id_eo = 105 +, Id_es = 106 +, Id_es_formal = 107 +, Id_esu = 108 +, Id_et = 109 +, Id_eu = 110 +, Id_ext = 111 +, Id_fa = 112 +, Id_ff = 113 +, Id_fi = 114 +, Id_fit = 115 +, Id_fiu_vro = 116 +, Id_fj = 117 +, Id_fo = 118 +, Id_fr = 119 +, Id_frc = 120 +, Id_frp = 121 +, Id_frr = 122 +, Id_fur = 123 +, Id_fy = 124 +, Id_ga = 125 +, Id_gag = 126 +, Id_gan = 127 +, Id_gan_hans = 128 +, Id_gan_hant = 129 +, Id_gd = 130 +, Id_gl = 131 +, Id_glk = 132 +, Id_gn = 133 +, Id_gom = 134 +, Id_gom_deva = 135 +, Id_gom_latn = 136 +, Id_gor = 137 +, Id_got = 138 +, Id_grc = 139 +, Id_gsw = 140 +, Id_gu = 141 +, Id_guc = 142 +, Id_gv = 143 +, Id_ha = 144 +, Id_hak = 145 +, Id_haw = 146 +, Id_he = 147 +, Id_hi = 148 +, Id_hif = 149 +, Id_hif_latn = 150 +, Id_hil = 151 +, Id_ho = 152 +, Id_hr = 153 +, Id_hrx = 154 +, Id_hsb = 155 +, Id_hsn = 156 +, Id_ht = 157 +, Id_hu = 158 +, Id_hu_formal = 159 +, Id_hy = 160 +, Id_hz = 161 +, Id_ia = 162 +, Id_id = 163 +, Id_ie = 164 +, Id_ig = 165 +, Id_ii = 166 +, Id_ik = 167 +, Id_ike_cans = 168 +, Id_ike_latn = 169 +, Id_ilo = 170 +, Id_inh = 171 +, Id_io = 172 +, Id_is = 173 +, Id_it = 174 +, Id_iu = 175 +, Id_izh = 176 +, Id_ja = 177 +, Id_jam = 178 +, Id_jbo = 179 +, Id_jut = 180 +, Id_jv = 181 +, Id_ka = 182 +, Id_kaa = 183 +, Id_kab = 184 +, Id_kbd = 185 +, Id_kbd_cyrl = 186 +, Id_kbp = 187 +, Id_kg = 188 +, Id_khw = 189 +, Id_ki = 190 +, Id_kiu = 191 +, Id_kj = 192 +, Id_kk = 193 +, Id_kk_arab = 194 +, Id_kk_cn = 195 +, Id_kk_cyrl = 196 +, Id_kk_kz = 197 +, Id_kk_latn = 198 +, Id_kk_tr = 199 +, Id_kl = 200 +, Id_km = 201 +, Id_kn = 202 +, Id_ko = 203 +, Id_ko_kp = 204 +, Id_koi = 205 +, Id_kok = 206 +, Id_kr = 207 +, Id_krc = 208 +, Id_kri = 209 +, Id_krj = 210 +, Id_krl = 211 +, Id_ks = 212 +, Id_ks_arab = 213 +, Id_ks_deva = 214 +, Id_ksh = 215 +, Id_ku = 216 +, Id_ku_arab = 217 +, Id_ku_latn = 218 +, Id_kv = 219 +, Id_kw = 220 +, Id_ky = 221 +, Id_la = 222 +, Id_lad = 223 +, Id_lb = 224 +, Id_lbe = 225 +, Id_lez = 226 +, Id_lfn = 227 +, Id_lg = 228 +, Id_li = 229 +, Id_lij = 230 +, Id_liv = 231 +, Id_lki = 232 +, Id_lmo = 233 +, Id_ln = 234 +, Id_lo = 235 +, Id_loz = 236 +, Id_lrc = 237 +, Id_lt = 238 +, Id_ltg = 239 +, Id_lus = 240 +, Id_luz = 241 +, Id_lv = 242 +, Id_lzh = 243 +, Id_lzz = 244 +, Id_mai = 245 +, Id_map_bms = 246 +, Id_mdf = 247 +, Id_mg = 248 +, Id_mh = 249 +, Id_mhr = 250 +, Id_mi = 251 +, Id_mic = 252 +, Id_min = 253 +, Id_mk = 254 +, Id_ml = 255 +, Id_mn = 256 +, Id_mnc = 257 +, Id_mo = 258 +, Id_mr = 259 +, Id_mrj = 260 +, Id_ms = 261 +, Id_mt = 262 +, Id_mui = 263 +, Id_mus = 264 +, Id_mwl = 265 +, Id_mwv = 266 +, Id_my = 267 +, Id_myv = 268 +, Id_mzn = 269 +, Id_na = 270 +, Id_nah = 271 +, Id_nan = 272 +, Id_nap = 273 +, Id_nb = 274 +, Id_nds = 275 +, Id_nds_nl = 276 +, Id_ne = 277 +, Id_new = 278 +, Id_ng = 279 +, Id_niu = 280 +, Id_nl = 281 +, Id_nl_informal = 282 +, Id_nn = 283 +, Id_no = 284 +, Id_nov = 285 +, Id_nrm = 286 +, Id_nso = 287 +, Id_nv = 288 +, Id_ny = 289 +, Id_oc = 290 +, Id_olo = 291 +, Id_om = 292 +, Id_or = 293 +, Id_os = 294 +, Id_pa = 295 +, Id_pag = 296 +, Id_pam = 297 +, Id_pap = 298 +, Id_pbb = 299 +, Id_pcd = 300 +, Id_pdc = 301 +, Id_pdt = 302 +, Id_pfl = 303 +, Id_pi = 304 +, Id_pih = 305 +, Id_pl = 306 +, Id_pms = 307 +, Id_pnb = 308 +, Id_pnt = 309 +, Id_ppl = 310 +, Id_prg = 311 +, Id_ps = 312 +, Id_pt = 313 +, Id_pt_br = 314 +, Id_qqq = 315 +, Id_qu = 316 +, Id_qug = 317 +, Id_rap = 318 +, Id_rgn = 319 +, Id_rif = 320 +, Id_rm = 321 +, Id_rmf = 322 +, Id_rmy = 323 +, Id_rn = 324 +, Id_ro = 325 +, Id_roa_rup = 326 +, Id_roa_tara = 327 +, Id_ru = 328 +, Id_rue = 329 +, Id_rup = 330 +, Id_ruq = 331 +, Id_ruq_cyrl = 332 +, Id_ruq_latn = 333 +, Id_rw = 334 +, Id_ryu = 335 +, Id_sa = 336 +, Id_sah = 337 +, Id_sat = 338 +, Id_saz = 339 +, Id_sc = 340 +, Id_scn = 341 +, Id_sco = 342 +, Id_sd = 343 +, Id_sdc = 344 +, Id_sdh = 345 +, Id_se = 346 +, Id_sei = 347 +, Id_ses = 348 +, Id_sg = 349 +, Id_sgs = 350 +, Id_sh = 351 +, Id_shi = 352 +, Id_shn = 353 +, Id_si = 354 +, Id_simple = 355 +, Id_sk = 356 +, Id_sl = 357 +, Id_sli = 358 +, Id_sly = 359 +, Id_sm = 360 +, Id_sma = 361 +, Id_sn = 362 +, Id_so = 363 +, Id_sq = 364 +, Id_sr = 365 +, Id_sr_ec = 366 +, Id_sr_el = 367 +, Id_srn = 368 +, Id_ss = 369 +, Id_st = 370 +, Id_stq = 371 +, Id_su = 372 +, Id_sv = 373 +, Id_sw = 374 +, Id_sxu = 375 +, Id_szl = 376 +, Id_ta = 377 +, Id_tcy = 378 +, Id_te = 379 +, Id_test = 380 +, Id_tet = 381 +, Id_tg = 382 +, Id_tg_cyrl = 383 +, Id_tg_latn = 384 +, Id_th = 385 +, Id_ti = 386 +, Id_tk = 387 +, Id_tl = 388 +, Id_tly = 389 +, Id_tn = 390 +, Id_to = 391 +, Id_tokipona = 392 +, Id_tp = 393 +, Id_tpi = 394 +, Id_tr = 395 +, Id_tru = 396 +, Id_ts = 397 +, Id_tt = 398 +, Id_tt_cyrl = 399 +, Id_tt_latn = 400 +, Id_ttt = 401 +, Id_tum = 402 +, Id_tw = 403 +, Id_ty = 404 +, Id_tyv = 405 +, Id_tzm = 406 +, Id_ua = 407 +, Id_udm = 408 +, Id_ug = 409 +, Id_ug_arab = 410 +, Id_ug_latn = 411 +, Id_uk = 412 +, Id_ur = 413 +, Id_uz = 414 +, Id_ve = 415 +, Id_vec = 416 +, Id_vep = 417 +, Id_vi = 418 +, Id_vls = 419 +, Id_vmf = 420 +, Id_vo = 421 +, Id_vot = 422 +, Id_vro = 423 +, Id_wa = 424 +, Id_war = 425 +, Id_wo = 426 +, Id_wuu = 427 +, Id_xal = 428 +, Id_xh = 429 +, Id_xmf = 430 +, Id_yi = 431 +, Id_yo = 432 +, Id_yue = 433 +, Id_za = 434 +, Id_zea = 435 +, Id_zh = 436 +, Id_zh_classical = 437 +, Id_zh_cn = 438 +, Id_zh_hans = 439 +, Id_zh_hant = 440 +, Id_zh_hk = 441 +, Id_zh_min_nan = 442 +, Id_zh_mo = 443 +, Id_zh_my = 444 +, Id_zh_sg = 445 +, Id_zh_tw = 446 +, Id_zh_yue = 447 +, Id_zu = 448 + ; + public static final int Id__max = 449; + public static Hash_adp_bry Regy() { + if (stub_hash == null) { // NOTE: any parenthetical String below will have an "unseen" character of "\xE2\x80\xAA" at the begining and "\xE2\x80\xAC" at the end. They are responsible for parentheses-orientation in RTL stub_ary. +stub_hash = Hash_adp_bry.ci_a7(); // ASCII:lang_code; NOTE: must be ci; EX: {{#languages:FR}} +Regy_add(stub_hash, Id_en, "en", "English"); +Regy_add(stub_hash, Id_aa, "aa", "Qafár af"); +Regy_add(stub_hash, Id_ab, "ab", "Аҧсуа"); +Regy_add(stub_hash, Id_ace, "ace", "Acèh"); +Regy_add(stub_hash, Id_ady, "ady", "Adyghe"); +Regy_add(stub_hash, Id_ady_cyrl, "ady-cyrl", "West Circassian (Cyrillic)"); +Regy_add(stub_hash, Id_aeb, "aeb", "زَوُن"); +Regy_add(stub_hash, Id_aeb_arab, "aeb-arab", "زَوُن"); +Regy_add(stub_hash, Id_aeb_latn, "aeb-latn", "زَوُن"); +Regy_add(stub_hash, Id_af, "af", "Afrikaans"); +Regy_add(stub_hash, Id_ak, "ak", "Akan"); +Regy_add(stub_hash, Id_akz, "akz", "Alibamu"); +Regy_add(stub_hash, Id_aln, "aln", "Gegë"); +Regy_add(stub_hash, Id_als, "als", "Alemannisch"); +Regy_add(stub_hash, Id_am, "am", "አማርኛ"); +Regy_add(stub_hash, Id_an, "an", "Aragonés"); +Regy_add(stub_hash, Id_ang, "ang", "Ænglisc"); +Regy_add(stub_hash, Id_anp, "anp", "अङ्गिका"); +Regy_add(stub_hash, Id_ar, "ar", "العربية"); +Regy_add(stub_hash, Id_arc, "arc", "ܐܪܡܝܐ"); +Regy_add(stub_hash, Id_arn, "arn", "Mapudungun"); +Regy_add(stub_hash, Id_aro, "aro", "Araona"); +Regy_add(stub_hash, Id_arq, "arq", "Algerian Arabic"); +Regy_add(stub_hash, Id_ary, "ary", "Maġribi"); +Regy_add(stub_hash, Id_arz, "arz", "مصرى"); +Regy_add(stub_hash, Id_as, "as", "অসমীয়া"); +Regy_add(stub_hash, Id_ase, "ase", "American Sign Language"); +Regy_add(stub_hash, Id_ast, "ast", "Asturianu"); +Regy_add(stub_hash, Id_atj, "atj", "Atikamekw"); +Regy_add(stub_hash, Id_av, "av", "Авар"); +Regy_add(stub_hash, Id_avk, "avk", "Kotava"); +Regy_add(stub_hash, Id_awa, "awa", "Awadhi"); +Regy_add(stub_hash, Id_ay, "ay", "Aymar aru"); +Regy_add(stub_hash, Id_az, "az", "Azərbaycanca"); +Regy_add(stub_hash, Id_azb, "azb", "South Azerbaijani"); +Regy_add(stub_hash, Id_ba, "ba", "Башҡортса"); +Regy_add(stub_hash, Id_ban, "ban", "ᬩᬲᬩᬮᬶ"); +Regy_add(stub_hash, Id_bar, "bar", "Boarisch"); +Regy_add(stub_hash, Id_bat_smg, "bat-smg", "Žemaitėška"); +Regy_add(stub_hash, Id_bbc, "bbc", "Batak Toba"); +Regy_add(stub_hash, Id_bbc_latn, "bbc-latn", "Batak Toba (Latin)"); +Regy_add(stub_hash, Id_bcc, "bcc", "بلوچی مکرانی"); +Regy_add(stub_hash, Id_bcl, "bcl", "Bikol Central"); +Regy_add(stub_hash, Id_be, "be", "Беларуская"); +Regy_add(stub_hash, Id_be_tarask, "be-tarask", "‪Беларуская (тарашкевіца)‬"); +Regy_add(stub_hash, Id_be_x_old, "be-x-old", "‪Беларуская (тарашкевіца)‬"); +Regy_add(stub_hash, Id_bew, "bew", "Betawi"); +Regy_add(stub_hash, Id_bg, "bg", "Български"); +Regy_add(stub_hash, Id_bgn, "bgn", "Balochi"); +Regy_add(stub_hash, Id_bh, "bh", "भोजपुरी"); +Regy_add(stub_hash, Id_bho, "bho", "भोजपुरी"); +Regy_add(stub_hash, Id_bi, "bi", "Bislama"); +Regy_add(stub_hash, Id_bjn, "bjn", "Bahasa Banjar"); +Regy_add(stub_hash, Id_bm, "bm", "Bamanankan"); +Regy_add(stub_hash, Id_bn, "bn", "বাংলা"); +Regy_add(stub_hash, Id_bo, "bo", "བོད་ཡིག"); +Regy_add(stub_hash, Id_bpy, "bpy", "ইমার ঠার/বিষ্ণুপ্রিয়া মণিপুরী"); +Regy_add(stub_hash, Id_bqi, "bqi", "بختياري"); +Regy_add(stub_hash, Id_br, "br", "Brezhoneg"); +Regy_add(stub_hash, Id_brh, "brh", "Bráhuí"); +Regy_add(stub_hash, Id_bs, "bs", "Bosanski"); +Regy_add(stub_hash, Id_bto, "bto", "Rinconada Bikol"); +Regy_add(stub_hash, Id_bug, "bug", "ᨅᨔ ᨕᨘᨁᨗ"); +Regy_add(stub_hash, Id_bxr, "bxr", "Буряад"); +Regy_add(stub_hash, Id_ca, "ca", "Català"); +Regy_add(stub_hash, Id_cbk_zam, "cbk-zam", "Chavacano de Zamboanga"); +Regy_add(stub_hash, Id_cdo, "cdo", "Mìng-dĕ̤ng-ngṳ̄"); +Regy_add(stub_hash, Id_ce, "ce", "Нохчийн"); +Regy_add(stub_hash, Id_ceb, "ceb", "Cebuano"); +Regy_add(stub_hash, Id_ch, "ch", "Chamoru"); +Regy_add(stub_hash, Id_chm, "chm", "Mari"); +Regy_add(stub_hash, Id_cho, "cho", "Choctaw"); +Regy_add(stub_hash, Id_chr, "chr", "ᏣᎳᎩ"); +Regy_add(stub_hash, Id_chy, "chy", "Tsetsêhestâhese"); +Regy_add(stub_hash, Id_ckb, "ckb", "کوردی"); +Regy_add(stub_hash, Id_co, "co", "Corsu"); +Regy_add(stub_hash, Id_cps, "cps", "Capiceño"); +Regy_add(stub_hash, Id_cr, "cr", "Nēhiyawēwin / ᓀᐦᐃᔭᐍᐏᐣ"); +Regy_add(stub_hash, Id_crh, "crh", "Qırımtatarca"); +Regy_add(stub_hash, Id_crh_cyrl, "crh-cyrl", "‪Къырымтатарджа (Кирилл)‬"); +Regy_add(stub_hash, Id_crh_latn, "crh-latn", "‪Qırımtatarca (Latin)‬"); +Regy_add(stub_hash, Id_cs, "cs", "Česky"); +Regy_add(stub_hash, Id_csb, "csb", "Kaszëbsczi"); +Regy_add(stub_hash, Id_cu, "cu", "Словѣ́ньскъ / ⰔⰎⰑⰂⰡⰐⰠⰔⰍⰟ"); +Regy_add(stub_hash, Id_cv, "cv", "Чӑвашла"); +Regy_add(stub_hash, Id_cy, "cy", "Cymraeg"); +Regy_add(stub_hash, Id_da, "da", "Dansk"); +Regy_add(stub_hash, Id_de, "de", "Deutsch"); +Regy_add(stub_hash, Id_de_at, "de-at", "Österreichisches Deutsch"); +Regy_add(stub_hash, Id_de_ch, "de-ch", "Schweizer Hochdeutsch"); +Regy_add(stub_hash, Id_de_formal, "de-formal", "‪Deutsch (Sie-Form)‬"); +Regy_add(stub_hash, Id_diq, "diq", "Zazaki"); +Regy_add(stub_hash, Id_dsb, "dsb", "Dolnoserbski"); +Regy_add(stub_hash, Id_dtp, "dtp", "Dusun Bundu-liwan"); +Regy_add(stub_hash, Id_dty, "dty", "Dotyali"); +Regy_add(stub_hash, Id_dv, "dv", "ދިވެހިބަސް"); +Regy_add(stub_hash, Id_dz, "dz", "ཇོང་ཁ"); +Regy_add(stub_hash, Id_ee, "ee", "Eʋegbe"); +Regy_add(stub_hash, Id_egl, "egl", "Emiliàn"); +Regy_add(stub_hash, Id_el, "el", "Ελληνικά"); +Regy_add(stub_hash, Id_eml, "eml", "Emiliàn e rumagnòl"); +Regy_add(stub_hash, Id_en_ca, "en-ca", "Canadian English"); +Regy_add(stub_hash, Id_en_gb, "en-gb", "British English"); +Regy_add(stub_hash, Id_en_rtl, "en-rtl", "English rtl"); +Regy_add(stub_hash, Id_enrtl, "enrtl", "English (right to left)"); +Regy_add(stub_hash, Id_eo, "eo", "Esperanto"); +Regy_add(stub_hash, Id_es, "es", "Español"); +Regy_add(stub_hash, Id_es_formal, "es-formal", "Spanish (formal)"); +Regy_add(stub_hash, Id_esu, "esu", "Yuk'ip"); +Regy_add(stub_hash, Id_et, "et", "Eesti"); +Regy_add(stub_hash, Id_eu, "eu", "Euskara"); +Regy_add(stub_hash, Id_ext, "ext", "Estremeñu"); +Regy_add(stub_hash, Id_fa, "fa", "فارسی"); +Regy_add(stub_hash, Id_ff, "ff", "Fulfulde"); +Regy_add(stub_hash, Id_fi, "fi", "Suomi"); +Regy_add(stub_hash, Id_fit, "fit", "meänkieli"); +Regy_add(stub_hash, Id_fiu_vro, "fiu-vro", "Võro"); +Regy_add(stub_hash, Id_fj, "fj", "Na Vosa Vakaviti"); +Regy_add(stub_hash, Id_fo, "fo", "Føroyskt"); +Regy_add(stub_hash, Id_fr, "fr", "Français"); +Regy_add(stub_hash, Id_frc, "frc", "Français cadien"); +Regy_add(stub_hash, Id_frp, "frp", "Arpetan"); +Regy_add(stub_hash, Id_frr, "frr", "Nordfriisk"); +Regy_add(stub_hash, Id_fur, "fur", "Furlan"); +Regy_add(stub_hash, Id_fy, "fy", "Frysk"); +Regy_add(stub_hash, Id_ga, "ga", "Gaeilge"); +Regy_add(stub_hash, Id_gag, "gag", "Gagauz"); +Regy_add(stub_hash, Id_gan, "gan", "贛語"); +Regy_add(stub_hash, Id_gan_hans, "gan-hans", "‪赣语(简体)‬"); +Regy_add(stub_hash, Id_gan_hant, "gan-hant", "‪贛語(繁體)‬"); +Regy_add(stub_hash, Id_gd, "gd", "Gàidhlig"); +Regy_add(stub_hash, Id_gl, "gl", "Galego"); +Regy_add(stub_hash, Id_glk, "glk", "گیلکی"); +Regy_add(stub_hash, Id_gn, "gn", "Avañe'ẽ"); +Regy_add(stub_hash, Id_gom, "gom", "कोंकणी"); +Regy_add(stub_hash, Id_gom_deva, "gom-deva", "Konkani"); +Regy_add(stub_hash, Id_gom_latn, "gom-latn", "कोंकणी (Latin)"); +Regy_add(stub_hash, Id_gor, "gor", "Gor"); +Regy_add(stub_hash, Id_got, "got", "𐌲𐌿𐍄𐌹𐍃𐌺"); +Regy_add(stub_hash, Id_grc, "grc", "Ἀρχαία ἑλληνικὴ"); +Regy_add(stub_hash, Id_gsw, "gsw", "Alemannisch"); +Regy_add(stub_hash, Id_gu, "gu", "ગુજરાતી"); +Regy_add(stub_hash, Id_guc, "guc", "Wayuu"); +Regy_add(stub_hash, Id_gv, "gv", "Gaelg"); +Regy_add(stub_hash, Id_ha, "ha", "هَوُسَ"); +Regy_add(stub_hash, Id_hak, "hak", "Hak-kâ-fa"); +Regy_add(stub_hash, Id_haw, "haw", "Hawai`i"); +Regy_add(stub_hash, Id_he, "he", "עברית"); +Regy_add(stub_hash, Id_hi, "hi", "हिन्दी"); +Regy_add(stub_hash, Id_hif, "hif", "Fiji Hindi"); +Regy_add(stub_hash, Id_hif_latn, "hif-latn", "Fiji Hindi"); +Regy_add(stub_hash, Id_hil, "hil", "Ilonggo"); +Regy_add(stub_hash, Id_ho, "ho", "Hiri Motu"); +Regy_add(stub_hash, Id_hr, "hr", "Hrvatski"); +Regy_add(stub_hash, Id_hrx, "hrx", "Hunsriker"); +Regy_add(stub_hash, Id_hsb, "hsb", "Hornjoserbsce"); +Regy_add(stub_hash, Id_hsn, "hsn", "Xiang"); +Regy_add(stub_hash, Id_ht, "ht", "Kreyòl ayisyen"); +Regy_add(stub_hash, Id_hu, "hu", "Magyar"); +Regy_add(stub_hash, Id_hu_formal, "hu-formal", "Magyar (formal)"); +Regy_add(stub_hash, Id_hy, "hy", "Հայերեն"); +Regy_add(stub_hash, Id_hz, "hz", "Otsiherero"); +Regy_add(stub_hash, Id_ia, "ia", "Interlingua"); +Regy_add(stub_hash, Id_id, "id", "Bahasa Indonesia"); +Regy_add(stub_hash, Id_ie, "ie", "Interlingue"); +Regy_add(stub_hash, Id_ig, "ig", "Igbo"); +Regy_add(stub_hash, Id_ii, "ii", "ꆇꉙ"); +Regy_add(stub_hash, Id_ik, "ik", "Iñupiak"); +Regy_add(stub_hash, Id_ike_cans, "ike-cans", "ᐃᓄᒃᑎᑐᑦ"); +Regy_add(stub_hash, Id_ike_latn, "ike-latn", "inuktitut"); +Regy_add(stub_hash, Id_ilo, "ilo", "Ilokano"); +Regy_add(stub_hash, Id_inh, "inh", "ГІалгІай Ğalğaj"); +Regy_add(stub_hash, Id_io, "io", "Ido"); +Regy_add(stub_hash, Id_is, "is", "Íslenska"); +Regy_add(stub_hash, Id_it, "it", "Italiano"); +Regy_add(stub_hash, Id_iu, "iu", "ᐃᓄᒃᑎᑐᑦ/inuktitut"); +Regy_add(stub_hash, Id_izh, "izh", "Ingrian"); +Regy_add(stub_hash, Id_ja, "ja", "日本語"); +Regy_add(stub_hash, Id_jam, "jam", "Patois"); +Regy_add(stub_hash, Id_jbo, "jbo", "Lojban"); +Regy_add(stub_hash, Id_jut, "jut", "Jysk"); +Regy_add(stub_hash, Id_jv, "jv", "Basa Jawa"); +Regy_add(stub_hash, Id_ka, "ka", "ქართული"); +Regy_add(stub_hash, Id_kaa, "kaa", "Qaraqalpaqsha"); +Regy_add(stub_hash, Id_kab, "kab", "Taqbaylit"); +Regy_add(stub_hash, Id_kbd, "kbd", "Къэбэрдеибзэ / Qabardjajəbza"); +Regy_add(stub_hash, Id_kbd_cyrl, "kbd-cyrl", "къэбэрдеибзэ"); +Regy_add(stub_hash, Id_kbp, "kbp", "Kabiye"); +Regy_add(stub_hash, Id_kg, "kg", "Kongo"); +Regy_add(stub_hash, Id_khw, "khw", "کھوار"); +Regy_add(stub_hash, Id_ki, "ki", "Gĩkũyũ"); +Regy_add(stub_hash, Id_kiu, "kiu", "Kırmancki"); +Regy_add(stub_hash, Id_kj, "kj", "Kwanyama"); +Regy_add(stub_hash, Id_kk, "kk", "Қазақша"); +Regy_add(stub_hash, Id_kk_arab, "kk-arab", "‫قازاقشا (تٴوتە)‬"); +Regy_add(stub_hash, Id_kk_cn, "kk-cn", "‫قازاقشا (جۇنگو)‬"); +Regy_add(stub_hash, Id_kk_cyrl, "kk-cyrl", "‪Қазақша (кирил)‬"); +Regy_add(stub_hash, Id_kk_kz, "kk-kz", "‪Қазақша (Қазақстан)‬"); +Regy_add(stub_hash, Id_kk_latn, "kk-latn", "‪Qazaqşa (latın)‬"); +Regy_add(stub_hash, Id_kk_tr, "kk-tr", "‪Qazaqşa (Türkïya)‬"); +Regy_add(stub_hash, Id_kl, "kl", "Kalaallisut"); +Regy_add(stub_hash, Id_km, "km", "ភាសាខ្មែរ"); +Regy_add(stub_hash, Id_kn, "kn", "ಕನ್ನಡ"); +Regy_add(stub_hash, Id_ko, "ko", "한국어"); +Regy_add(stub_hash, Id_ko_kp, "ko-kp", "한국어 (조선)"); +Regy_add(stub_hash, Id_koi, "koi", "Перем Коми"); +Regy_add(stub_hash, Id_kok, "kok", ""); +Regy_add(stub_hash, Id_kr, "kr", "Kanuri"); +Regy_add(stub_hash, Id_krc, "krc", "Къарачай-Малкъар"); +Regy_add(stub_hash, Id_kri, "kri", "Krio"); +Regy_add(stub_hash, Id_krj, "krj", "Kinaray-a"); +Regy_add(stub_hash, Id_krl, "krl", "Karelian"); +Regy_add(stub_hash, Id_ks, "ks", "कश्मीरी - (كشميري)"); +Regy_add(stub_hash, Id_ks_arab, "ks-arab", "کٲشُر"); +Regy_add(stub_hash, Id_ks_deva, "ks-deva", "कॉशुर"); +Regy_add(stub_hash, Id_ksh, "ksh", "Ripoarisch"); +Regy_add(stub_hash, Id_ku, "ku", "Kurdî"); +Regy_add(stub_hash, Id_ku_arab, "ku-arab", "‫كوردي (عەرەبی)‬"); +Regy_add(stub_hash, Id_ku_latn, "ku-latn", "‪Kurdî (latînî)‬"); +Regy_add(stub_hash, Id_kv, "kv", "Коми"); +Regy_add(stub_hash, Id_kw, "kw", "Kernowek"); +Regy_add(stub_hash, Id_ky, "ky", "Кыргызча"); +Regy_add(stub_hash, Id_la, "la", "Latina"); +Regy_add(stub_hash, Id_lad, "lad", "Ladino"); +Regy_add(stub_hash, Id_lb, "lb", "Lëtzebuergesch"); +Regy_add(stub_hash, Id_lbe, "lbe", "Лакку"); +Regy_add(stub_hash, Id_lez, "lez", "Лезги"); +Regy_add(stub_hash, Id_lfn, "lfn", "Lingua Franca Nova"); +Regy_add(stub_hash, Id_lg, "lg", "Luganda"); +Regy_add(stub_hash, Id_li, "li", "Limburgs"); +Regy_add(stub_hash, Id_lij, "lij", "Líguru"); +Regy_add(stub_hash, Id_liv, "liv", "Līvõ kēļ"); +Regy_add(stub_hash, Id_lki, "lki", "Laki"); +Regy_add(stub_hash, Id_lmo, "lmo", "Lumbaart"); +Regy_add(stub_hash, Id_ln, "ln", "Lingála"); +Regy_add(stub_hash, Id_lo, "lo", "ລາວ"); +Regy_add(stub_hash, Id_loz, "loz", "Silozi"); +Regy_add(stub_hash, Id_lrc, "lrc", "لوری"); +Regy_add(stub_hash, Id_lt, "lt", "Lietuvių"); +Regy_add(stub_hash, Id_ltg, "ltg", "Latgaļu"); +Regy_add(stub_hash, Id_lus, "lus", "Mizo ţawng"); +Regy_add(stub_hash, Id_luz, "luz", "Luri"); +Regy_add(stub_hash, Id_lv, "lv", "Latviešu"); +Regy_add(stub_hash, Id_lzh, "lzh", "文言"); +Regy_add(stub_hash, Id_lzz, "lzz", "Lazuri"); +Regy_add(stub_hash, Id_mai, "mai", "मैथिली"); +Regy_add(stub_hash, Id_map_bms, "map-bms", "Basa Banyumasan"); +Regy_add(stub_hash, Id_mdf, "mdf", "Мокшень"); +Regy_add(stub_hash, Id_mg, "mg", "Malagasy"); +Regy_add(stub_hash, Id_mh, "mh", "Ebon"); +Regy_add(stub_hash, Id_mhr, "mhr", "Олык Марий"); +Regy_add(stub_hash, Id_mi, "mi", "Māori"); +Regy_add(stub_hash, Id_mic, "mic", "Mi'kmaq"); +Regy_add(stub_hash, Id_min, "min", "Baso Minangkabau"); +Regy_add(stub_hash, Id_mk, "mk", "Македонски"); +Regy_add(stub_hash, Id_ml, "ml", "മലയാളം"); +Regy_add(stub_hash, Id_mn, "mn", "Монгол"); +Regy_add(stub_hash, Id_mnc, "mnc", "Manchu"); +Regy_add(stub_hash, Id_mo, "mo", "Молдовеняскэ"); +Regy_add(stub_hash, Id_mr, "mr", "मराठी"); +Regy_add(stub_hash, Id_mrj, "mrj", "Кырык мары"); +Regy_add(stub_hash, Id_ms, "ms", "Bahasa Melayu"); +Regy_add(stub_hash, Id_mt, "mt", "Malti"); +Regy_add(stub_hash, Id_mui, "mui", "Musi"); +Regy_add(stub_hash, Id_mus, "mus", "Mvskoke"); +Regy_add(stub_hash, Id_mwl, "mwl", "Mirandés"); +Regy_add(stub_hash, Id_mwv, "mwv", "Behase Mentawei"); +Regy_add(stub_hash, Id_my, "my", "Burmese"); +Regy_add(stub_hash, Id_myv, "myv", "Эрзянь"); +Regy_add(stub_hash, Id_mzn, "mzn", "مازِرونی"); +Regy_add(stub_hash, Id_na, "na", "Dorerin Naoero"); +Regy_add(stub_hash, Id_nah, "nah", "Nāhuatl"); +Regy_add(stub_hash, Id_nan, "nan", "Bân-lâm-gú"); +Regy_add(stub_hash, Id_nap, "nap", "Nnapulitano"); +Regy_add(stub_hash, Id_nb, "nb", "‪Norsk (bokmål)‬"); +Regy_add(stub_hash, Id_nds, "nds", "Plattdüütsch"); +Regy_add(stub_hash, Id_nds_nl, "nds-nl", "Nedersaksisch"); +Regy_add(stub_hash, Id_ne, "ne", "नेपाली"); +Regy_add(stub_hash, Id_new, "new", "नेपाल भाषा"); +Regy_add(stub_hash, Id_ng, "ng", "Oshiwambo"); +Regy_add(stub_hash, Id_niu, "niu", "Niuē"); +Regy_add(stub_hash, Id_nl, "nl", "Nederlands"); +Regy_add(stub_hash, Id_nl_informal, "nl-informal", "‪Nederlands (informeel)‬"); +Regy_add(stub_hash, Id_nn, "nn", "‪Norsk (nynorsk)‬"); +Regy_add(stub_hash, Id_no, "no", "‪Norsk (bokmål)‬"); +Regy_add(stub_hash, Id_nov, "nov", "Novial"); +Regy_add(stub_hash, Id_nrm, "nrm", "Nouormand"); +Regy_add(stub_hash, Id_nso, "nso", "Sesotho sa Leboa"); +Regy_add(stub_hash, Id_nv, "nv", "Diné bizaad"); +Regy_add(stub_hash, Id_ny, "ny", "Chi-Chewa"); +Regy_add(stub_hash, Id_oc, "oc", "Occitan"); +Regy_add(stub_hash, Id_olo, "olo", "Olo"); +Regy_add(stub_hash, Id_om, "om", "Oromoo"); +Regy_add(stub_hash, Id_or, "or", "ଓଡ଼ିଆ"); +Regy_add(stub_hash, Id_os, "os", "Иронау"); +Regy_add(stub_hash, Id_pa, "pa", "ਪੰਜਾਬੀ"); +Regy_add(stub_hash, Id_pag, "pag", "Pangasinan"); +Regy_add(stub_hash, Id_pam, "pam", "Kapampangan"); +Regy_add(stub_hash, Id_pap, "pap", "Papiamentu"); +Regy_add(stub_hash, Id_pbb, "pbb", "Páez"); +Regy_add(stub_hash, Id_pcd, "pcd", "Picard"); +Regy_add(stub_hash, Id_pdc, "pdc", "Deitsch"); +Regy_add(stub_hash, Id_pdt, "pdt", "Plautdietsch"); +Regy_add(stub_hash, Id_pfl, "pfl", "Pälzisch"); +Regy_add(stub_hash, Id_pi, "pi", "पािऴ"); +Regy_add(stub_hash, Id_pih, "pih", "Norfuk / Pitkern"); +Regy_add(stub_hash, Id_pl, "pl", "Polski"); +Regy_add(stub_hash, Id_pms, "pms", "Piemontèis"); +Regy_add(stub_hash, Id_pnb, "pnb", "پنجابی"); +Regy_add(stub_hash, Id_pnt, "pnt", "Ποντιακά"); +Regy_add(stub_hash, Id_ppl, "ppl", "Pipil"); +Regy_add(stub_hash, Id_prg, "prg", "Prūsiskan"); +Regy_add(stub_hash, Id_ps, "ps", "پښتو"); +Regy_add(stub_hash, Id_pt, "pt", "Português"); +Regy_add(stub_hash, Id_pt_br, "pt-br", "Português do Brasil"); +Regy_add(stub_hash, Id_qqq, "qqq", "MediaWiki sample"); +Regy_add(stub_hash, Id_qu, "qu", "Runa Simi"); +Regy_add(stub_hash, Id_qug, "qug", "Runa shimi"); +Regy_add(stub_hash, Id_rap, "rap", "Rapa Nui"); +Regy_add(stub_hash, Id_rgn, "rgn", "Rumagnôl"); +Regy_add(stub_hash, Id_rif, "rif", "Tarifit"); +Regy_add(stub_hash, Id_rm, "rm", "Rumantsch"); +Regy_add(stub_hash, Id_rmf, "rmf", "Finnish Kalo"); +Regy_add(stub_hash, Id_rmy, "rmy", "Romani"); +Regy_add(stub_hash, Id_rn, "rn", "Kirundi"); +Regy_add(stub_hash, Id_ro, "ro", "Română"); +Regy_add(stub_hash, Id_roa_rup, "roa-rup", "Armãneashce"); +Regy_add(stub_hash, Id_roa_tara, "roa-tara", "Tarandíne"); +Regy_add(stub_hash, Id_ru, "ru", "Русский"); +Regy_add(stub_hash, Id_rue, "rue", "Русиньскый"); +Regy_add(stub_hash, Id_rup, "rup", "Armãneașce"); +Regy_add(stub_hash, Id_ruq, "ruq", "Vlăheşte"); +Regy_add(stub_hash, Id_ruq_cyrl, "ruq-cyrl", "Влахесте"); +Regy_add(stub_hash, Id_ruq_latn, "ruq-latn", "Vlăheşte"); +Regy_add(stub_hash, Id_rw, "rw", "Kinyarwanda"); +Regy_add(stub_hash, Id_ryu, "ryu", "Okinawan"); +Regy_add(stub_hash, Id_sa, "sa", "संस्कृत"); +Regy_add(stub_hash, Id_sah, "sah", "Саха тыла"); +Regy_add(stub_hash, Id_sat, "sat", "Santali"); +Regy_add(stub_hash, Id_saz, "saz", "Saurashtra"); +Regy_add(stub_hash, Id_sc, "sc", "Sardu"); +Regy_add(stub_hash, Id_scn, "scn", "Sicilianu"); +Regy_add(stub_hash, Id_sco, "sco", "Scots"); +Regy_add(stub_hash, Id_sd, "sd", "سنڌي"); +Regy_add(stub_hash, Id_sdc, "sdc", "Sassaresu"); +Regy_add(stub_hash, Id_sdh, "sdh", "Southern Kurdish"); +Regy_add(stub_hash, Id_se, "se", "Sámegiella"); +Regy_add(stub_hash, Id_sei, "sei", "Cmique Itom"); +Regy_add(stub_hash, Id_ses, "ses", "Songhay"); +Regy_add(stub_hash, Id_sg, "sg", "Sängö"); +Regy_add(stub_hash, Id_sgs, "sgs", "Žemaitėška"); +Regy_add(stub_hash, Id_sh, "sh", "Srpskohrvatski / Српскохрватски"); +Regy_add(stub_hash, Id_shi, "shi", "Tašlḥiyt"); +Regy_add(stub_hash, Id_shn, "shn", "Shan"); +Regy_add(stub_hash, Id_si, "si", "Sinhalese"); +Regy_add(stub_hash, Id_simple, "simple", "Simple English"); +Regy_add(stub_hash, Id_sk, "sk", "Slovenčina"); +Regy_add(stub_hash, Id_sl, "sl", "Slovenščina"); +Regy_add(stub_hash, Id_sli, "sli", "Schläsch"); +Regy_add(stub_hash, Id_sly, "sly", "Selayar"); +Regy_add(stub_hash, Id_sm, "sm", "Gagana Samoa"); +Regy_add(stub_hash, Id_sma, "sma", "Åarjelsaemien"); +Regy_add(stub_hash, Id_sn, "sn", "chiShona"); +Regy_add(stub_hash, Id_so, "so", "Soomaaliga"); +Regy_add(stub_hash, Id_sq, "sq", "Shqip"); +Regy_add(stub_hash, Id_sr, "sr", "Српски / Srpski"); +Regy_add(stub_hash, Id_sr_ec, "sr-ec", "‪Српски (ћирилица)‬"); +Regy_add(stub_hash, Id_sr_el, "sr-el", "‪Srpski (latinica)‬"); +Regy_add(stub_hash, Id_srn, "srn", "Sranantongo"); +Regy_add(stub_hash, Id_ss, "ss", "SiSwati"); +Regy_add(stub_hash, Id_st, "st", "Sesotho"); +Regy_add(stub_hash, Id_stq, "stq", "Seeltersk"); +Regy_add(stub_hash, Id_su, "su", "Basa Sunda"); +Regy_add(stub_hash, Id_sv, "sv", "Svenska"); +Regy_add(stub_hash, Id_sw, "sw", "Kiswahili"); +Regy_add(stub_hash, Id_sxu, "sxu", "Saxon, Upper"); +Regy_add(stub_hash, Id_szl, "szl", "Ślůnski"); +Regy_add(stub_hash, Id_ta, "ta", "தமிழ்"); +Regy_add(stub_hash, Id_tcy, "tcy", "ತುಳು"); +Regy_add(stub_hash, Id_te, "te", "తెలుగు"); +Regy_add(stub_hash, Id_test, "test", "MediaWiki test"); +Regy_add(stub_hash, Id_tet, "tet", "Tetun"); +Regy_add(stub_hash, Id_tg, "tg", "Тоҷикӣ"); +Regy_add(stub_hash, Id_tg_cyrl, "tg-cyrl", "Тоҷикӣ"); +Regy_add(stub_hash, Id_tg_latn, "tg-latn", "tojikī"); +Regy_add(stub_hash, Id_th, "th", "ไทย"); +Regy_add(stub_hash, Id_ti, "ti", "ትግርኛ"); +Regy_add(stub_hash, Id_tk, "tk", "Türkmençe"); +Regy_add(stub_hash, Id_tl, "tl", "Tagalog"); +Regy_add(stub_hash, Id_tly, "tly", "толышә зывон"); +Regy_add(stub_hash, Id_tn, "tn", "Setswana"); +Regy_add(stub_hash, Id_to, "to", "lea faka-Tonga"); +Regy_add(stub_hash, Id_tokipona, "tokipona", "Toki Pona"); +Regy_add(stub_hash, Id_tp, "tp", "Toki Pona (deprecated:tokipona)"); +Regy_add(stub_hash, Id_tpi, "tpi", "Tok Pisin"); +Regy_add(stub_hash, Id_tr, "tr", "Türkçe"); +Regy_add(stub_hash, Id_tru, "tru", "Ṫuroyo"); +Regy_add(stub_hash, Id_ts, "ts", "Xitsonga"); +Regy_add(stub_hash, Id_tt, "tt", "Татарча/Tatarça"); +Regy_add(stub_hash, Id_tt_cyrl, "tt-cyrl", "Татарча"); +Regy_add(stub_hash, Id_tt_latn, "tt-latn", "Tatarça"); +Regy_add(stub_hash, Id_ttt, "ttt", "Tat, Muslim"); +Regy_add(stub_hash, Id_tum, "tum", "chiTumbuka"); +Regy_add(stub_hash, Id_tw, "tw", "Twi"); +Regy_add(stub_hash, Id_ty, "ty", "Reo Mā`ohi"); +Regy_add(stub_hash, Id_tyv, "tyv", "Тыва дыл"); +Regy_add(stub_hash, Id_tzm, "tzm", "ⵜⴰⵎⴰⵣⵉⵖⵜ"); +Regy_add(stub_hash, Id_ua, "ua", "Ukrainian"); +Regy_add(stub_hash, Id_udm, "udm", "Удмурт"); +Regy_add(stub_hash, Id_ug, "ug", "ئۇيغۇرچە / Uyghurche‎"); +Regy_add(stub_hash, Id_ug_arab, "ug-arab", "ئۇيغۇرچە"); +Regy_add(stub_hash, Id_ug_latn, "ug-latn", "Uyghurche‎"); +Regy_add(stub_hash, Id_uk, "uk", "Українська"); +Regy_add(stub_hash, Id_ur, "ur", "اردو"); +Regy_add(stub_hash, Id_uz, "uz", "O'zbek"); +Regy_add(stub_hash, Id_ve, "ve", "Tshivenda"); +Regy_add(stub_hash, Id_vec, "vec", "Vèneto"); +Regy_add(stub_hash, Id_vep, "vep", "Vepsan kel'"); +Regy_add(stub_hash, Id_vi, "vi", "Tiếng Việt"); +Regy_add(stub_hash, Id_vls, "vls", "West-Vlams"); +Regy_add(stub_hash, Id_vmf, "vmf", "Mainfränkisch"); +Regy_add(stub_hash, Id_vo, "vo", "Volapük"); +Regy_add(stub_hash, Id_vot, "vot", "Vaďďa"); +Regy_add(stub_hash, Id_vro, "vro", "Võro"); +Regy_add(stub_hash, Id_wa, "wa", "Walon"); +Regy_add(stub_hash, Id_war, "war", "Winaray"); +Regy_add(stub_hash, Id_wo, "wo", "Wolof"); +Regy_add(stub_hash, Id_wuu, "wuu", "吴语"); +Regy_add(stub_hash, Id_xal, "xal", "Хальмг"); +Regy_add(stub_hash, Id_xh, "xh", "isiXhosa"); +Regy_add(stub_hash, Id_xmf, "xmf", "მარგალური"); +Regy_add(stub_hash, Id_yi, "yi", "ייִדיש"); +Regy_add(stub_hash, Id_yo, "yo", "Yorùbá"); +Regy_add(stub_hash, Id_yue, "yue", "粵語"); +Regy_add(stub_hash, Id_za, "za", "Vahcuengh"); +Regy_add(stub_hash, Id_zea, "zea", "Zeêuws"); +Regy_add(stub_hash, Id_zh, "zh", "中文"); +Regy_add(stub_hash, Id_zh_classical, "zh-classical", "文言"); +Regy_add(stub_hash, Id_zh_cn, "zh-cn", "‪中文(中国大陆)‬"); +Regy_add(stub_hash, Id_zh_hans, "zh-hans", "‪中文(简体)‬"); +Regy_add(stub_hash, Id_zh_hant, "zh-hant", "‪中文(繁體)‬"); +Regy_add(stub_hash, Id_zh_hk, "zh-hk", "‪中文(香港)‬"); +Regy_add(stub_hash, Id_zh_min_nan, "zh-min-nan", "Bân-lâm-gú"); +Regy_add(stub_hash, Id_zh_mo, "zh-mo", "‪中文(澳門)‬"); +Regy_add(stub_hash, Id_zh_my, "zh-my", "‪中文(马来西亚)‬"); +Regy_add(stub_hash, Id_zh_sg, "zh-sg", "‪中文(新加坡)‬"); +Regy_add(stub_hash, Id_zh_tw, "zh-tw", "‪中文(台灣)‬"); +Regy_add(stub_hash, Id_zh_yue, "zh-yue", "粵語"); +Regy_add(stub_hash, Id_zu, "zu", "isiZulu"); + } + return stub_hash; + } + private static Hash_adp_bry stub_hash; private static final Xol_lang_stub[] stub_ary = new Xol_lang_stub[Id__max]; + public static Xol_lang_stub[] Ary() {return stub_ary;} + private static void Regy_add(Hash_adp_bry stub_hash, int uid, String code_str, String canonical) { + byte[] code = Bry_.new_a7(code_str);// ASCII:lang_code should always be ASCII + Xol_lang_stub itm = new Xol_lang_stub(uid, code, Bry_.new_u8(canonical)); + stub_ary[uid] = itm; + stub_hash.Add(code, itm); + } + public static boolean Exists(byte[] key) {return Get_by_key_or_null(key) != null;} // Language.php!isSupportedLanguage + public static Xol_lang_stub Get_by_id(int id) {if (stub_hash == null) Regy(); return stub_ary[id];} + public static Xol_lang_stub Get_by_key_or_null(byte[] key) {return Get_by_key_or_null(key, 0, key.length);} + public static Xol_lang_stub Get_by_key_or_null(byte[] key, int bgn, int end) { + if (stub_hash == null) Regy(); + return (Xol_lang_stub)stub_hash.Get_by_mid(key, bgn, end); + } + public static Xol_lang_stub Get_by_key_or_en(byte[] key) { + Xol_lang_stub rv = Get_by_key_or_null(key); + return (rv == null) ? (Xol_lang_stub)stub_hash.Get_by_bry(Xol_lang_itm_.Key_en) : rv; + } + public static Xol_lang_stub Get_by_key_or_intl(byte[] key) {return Get_by_key_or_intl(key, 0, key.length);} + public static Xol_lang_stub Get_by_key_or_intl(byte[] key, int bgn, int end) { + Xol_lang_stub rv = Get_by_key_or_null(key, bgn, end); + return rv == null ? Intl : rv; + } + public static final Xol_lang_stub Intl = new Xol_lang_stub(Xol_lang_stub_.Id__intl, Bry_.Empty, Bry_.Empty); // intended for international wikis like commons, wikidata, etc.. +} +class Xol_sub_itm_comparer implements ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + Xol_lang_stub lhs = (Xol_lang_stub)lhsObj; + Xol_lang_stub rhs = (Xol_lang_stub)rhsObj; + return Bry_.Compare(lhs.Key(), rhs.Key()); + } +} diff --git a/400_xowa/src/gplx/xowa/langs/bldrs/Json_itm_wkr__base.java b/400_xowa/src/gplx/xowa/langs/bldrs/Json_itm_wkr__base.java index a27517de8..89d79faa9 100644 --- a/400_xowa/src/gplx/xowa/langs/bldrs/Json_itm_wkr__base.java +++ b/400_xowa/src/gplx/xowa/langs/bldrs/Json_itm_wkr__base.java @@ -13,3 +13,69 @@ 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.langs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.primitives.*; import gplx.langs.jsons.*; import gplx.langs.phps.*; import gplx.langs.gfs.*; +import gplx.xowa.apps.gfs.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; import gplx.xowa.langs.parsers.*; +interface Json_itm_wkr { + void Read_kv_sub(byte[] key, byte[] val); +} +abstract class Json_itm_wkr__base implements Json_itm_wkr { + private Json_parser json_parser = new Json_parser(); + private Php_text_itm_parser php_quote_parser = new Php_text_itm_parser().Quote_is_single_(true); // assume values are equivalent to php single quote; DATE:2014-08-06 + public void Exec(byte[] src) { + List_adp tmp_list = List_adp_.New(); Byte_obj_ref tmp_result = Byte_obj_ref.zero_(); Bry_bfr tmp_bfr = Bry_bfr_.Reset(16); + Json_doc jdoc = json_parser.Parse(src); + this.Exec_bgn(); + Json_nde root = jdoc.Root_nde(); + int subs_len = root.Len(); + for (int i = 0; i < subs_len; ++i) { + Json_itm itm = root.Get_at(i); + switch (itm.Tid()) { + case Json_itm_.Tid__kv: + Json_kv kv = (Json_kv)itm; + if (kv.Key().Data_eq(Name_metadata)) continue; // ignore @metadata node + byte[] kv_key = kv.Key().Data_bry(); + byte[] kv_val = kv.Val().Data_bry(); + kv_val = php_quote_parser.Parse_as_bry(tmp_list, kv_val, tmp_result, tmp_bfr); + Read_kv_sub(kv_key, kv_val); + break; + } + } + this.Exec_end(); + } + @gplx.Virtual public void Exec_bgn() {} + @gplx.Virtual public void Exec_end() {} + public abstract void Read_kv_sub(byte[] key, byte[] val); + private static final byte[] Name_metadata = Bry_.new_a7("@metadata"); +} +class Json_itm_wkr__gfs extends Json_itm_wkr__base { + private Xoa_gfs_bldr gfs_bldr = new Xoa_gfs_bldr(); + private Xol_csv_parser csv_parser = Xol_csv_parser.Instance; + private Bry_bfr bfr; + public byte[] Xto_bry() {return gfs_bldr.Xto_bry();} + @Override public void Exec_bgn() { + bfr = gfs_bldr.Bfr(); + gfs_bldr.Add_proc_init_many("this", "messages", "load_text").Add_paren_bgn().Add_nl(); + gfs_bldr.Add_quote_xtn_bgn(); + } + @Override public void Exec_end() { + gfs_bldr.Add_quote_xtn_end().Add_paren_end().Add_term_nl(); + } + @Override public void Read_kv_sub(byte[] key, byte[] val) { + csv_parser.Save(bfr, key); // key + bfr.Add_byte_pipe(); // | + csv_parser.Save(bfr, val); // val + bfr.Add_byte_nl(); // \n + } +} +class Json_itm_wkr__msgs extends Json_itm_wkr__base { + private Xol_msg_mgr msg_mgr; private boolean dirty; + public void Ctor(boolean dirty, Xol_msg_mgr msg_mgr) {this.dirty = dirty; this.msg_mgr = msg_mgr;} + @Override public void Read_kv_sub(byte[] key, byte[] val) { + Xol_msg_itm msg_itm = msg_mgr.Itm_by_key_or_new(key); + Xol_msg_itm_.update_val_(msg_itm, val); + if (dirty) // bldr needs to dirty message to generate lang.gfs; DATE:2014-08-05 + msg_itm.Dirty_(true); + } +} diff --git a/400_xowa/src/gplx/xowa/langs/bldrs/Xob_i18n_parser.java b/400_xowa/src/gplx/xowa/langs/bldrs/Xob_i18n_parser.java index a27517de8..ac3f57b04 100644 --- a/400_xowa/src/gplx/xowa/langs/bldrs/Xob_i18n_parser.java +++ b/400_xowa/src/gplx/xowa/langs/bldrs/Xob_i18n_parser.java @@ -13,3 +13,18 @@ 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.langs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.xowa.langs.*; +public class Xob_i18n_parser { + public static void Load_msgs(boolean dirty, Xol_lang_itm lang, Io_url i18n_fil) { + String i18n_str = Io_mgr.Instance.LoadFilStr_args(i18n_fil).MissingIgnored_().Exec(); if (String_.Len_eq_0(i18n_str)) return; + Json_itm_wkr__msgs wkr = new Json_itm_wkr__msgs(); + wkr.Ctor(dirty, lang.Msg_mgr()); + wkr.Exec(Bry_.new_u8(i18n_str)); + } + public static byte[] Xto_gfs(byte[] raw) { + Json_itm_wkr__gfs wkr = new Json_itm_wkr__gfs(); + wkr.Exec(raw); + return wkr.Xto_bry(); + } +} diff --git a/400_xowa/src/gplx/xowa/langs/bldrs/Xob_i18n_parser_tst.java b/400_xowa/src/gplx/xowa/langs/bldrs/Xob_i18n_parser_tst.java index a27517de8..f42a32c62 100644 --- a/400_xowa/src/gplx/xowa/langs/bldrs/Xob_i18n_parser_tst.java +++ b/400_xowa/src/gplx/xowa/langs/bldrs/Xob_i18n_parser_tst.java @@ -13,3 +13,50 @@ 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.langs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import org.junit.*; +import gplx.core.intls.*; +public class Xob_i18n_parser_tst { + @Before public void init() {fxt.Clear();} private Xob_i18n_parser_fxt fxt = new Xob_i18n_parser_fxt(); + @Test public void Basic() { + fxt.Test_xto_gfs(String_.Concat_lines_nl_skip_last + ( "{" + , " \"@metadata\": {" + , " \"authors\": []" + , " }," + , "\"key_1\": \"val_1\"," + , "\"key_2\": \"val_2\"," + , "\"key_3\": \"val $1\"," + , "}" + ), String_.Concat_lines_nl_skip_last + ( "this.messages.load_text(" + , "<:['" + , "key_1|val_1" + , "key_2|val_2" + , "key_3|val ~{0}" + , "']:>" + , ");" + )); + } +// @Test public void Load_msgs_validate() { +// fxt.Test_load_msgs_dir("C:\\xowa\\bin\\any\\xowa\\xtns\\Insider\\i18n\\"); +// } +} +class Xob_i18n_parser_fxt { + public void Clear() { + } + public void Test_xto_gfs(String raw, String expd) { + byte[] actl = Xob_i18n_parser.Xto_gfs(Bry_.new_u8(raw)); + Tfds.Eq_str_lines(expd, String_.new_u8(actl)); + } + public void Test_load_msgs_dir(String dir_str) { + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + Xowe_wiki wiki = Xoa_app_fxt.Make__wiki__edit(app); + Io_url dir_url = Io_url_.new_dir_(dir_str); + Io_url[] fil_urls = Io_mgr.Instance.QueryDir_fils(dir_url); + int len = fil_urls.length; + for (int i = 0; i < len; ++i) { + Xob_i18n_parser.Load_msgs(false, wiki.Lang(), fil_urls[i]); + } + } +} diff --git a/400_xowa/src/gplx/xowa/langs/bldrs/Xobc_utl_make_lang.java b/400_xowa/src/gplx/xowa/langs/bldrs/Xobc_utl_make_lang.java index a27517de8..9f71fca96 100644 --- a/400_xowa/src/gplx/xowa/langs/bldrs/Xobc_utl_make_lang.java +++ b/400_xowa/src/gplx/xowa/langs/bldrs/Xobc_utl_make_lang.java @@ -13,3 +13,36 @@ 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.langs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.log_msgs.*; +import gplx.xowa.apps.fsys.*; +import gplx.xowa.langs.*; +public class Xobc_utl_make_lang implements Gfo_invk { + private final Xoa_lang_mgr lang_mgr; private final Xoa_fsys_mgr fsys_mgr; Xol_mw_lang_parser lang_parser; + public Xobc_utl_make_lang(Xoa_lang_mgr lang_mgr, Xoa_fsys_mgr fsys_mgr, Gfo_msg_log msg_log) { + this.lang_mgr = lang_mgr; this.fsys_mgr = fsys_mgr; + kwd_mgr = new Xobc_utl_make_lang_kwds(lang_mgr); + lang_parser = new Xol_mw_lang_parser(msg_log); + } + public Xobc_utl_make_lang_kwds Kwd_mgr() {return kwd_mgr;} private Xobc_utl_make_lang_kwds kwd_mgr; + public Ordered_hash Manual_text_bgn_hash() {return manual_text_bgn_hash;} private final Ordered_hash manual_text_bgn_hash = Ordered_hash_.New_bry(); + public Ordered_hash Manual_text_end_hash() {return manual_text_end_hash;} private final Ordered_hash manual_text_end_hash = Ordered_hash_.New_bry(); + public void Bld_all() { + Io_url lang_root = fsys_mgr.Cfg_lang_core_dir().OwnerDir(); // OwnerDir to get "/lang/" in "/cfg/lang/core/" + lang_parser.Parse_mediawiki(lang_mgr, lang_root.GenSubDir("mediawiki"), kwd_mgr); + kwd_mgr.Add_words(); + lang_parser.Save_langs(lang_mgr, lang_root.GenSubDir(Xol_mw_lang_parser.Dir_name_core), manual_text_bgn_hash, manual_text_end_hash); + Gfo_usr_dlg_.Instance.Prog_many("", "", "done"); + } + public void Parse_manual_text(byte[] key, byte[] text, Ordered_hash manual_text) { + manual_text.Add(key, new byte[][] {key, text}); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_kwds)) return kwd_mgr; + else if (ctx.Match(k, Invk_build_all)) Bld_all(); + else if (ctx.Match(k, Invk_manual_text_bgn)) Parse_manual_text(m.ReadBry("langs"), m.ReadBry("text"), manual_text_bgn_hash); + else if (ctx.Match(k, Invk_manual_text_end)) Parse_manual_text(m.ReadBry("langs"), m.ReadBry("text"), manual_text_end_hash); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_kwds = "keywords", Invk_manual_text_bgn = "manual_text_bgn", Invk_manual_text_end = "manual_text_end", Invk_build_all = "build_all"; +} diff --git a/400_xowa/src/gplx/xowa/langs/bldrs/Xobc_utl_make_lang_kwds.java b/400_xowa/src/gplx/xowa/langs/bldrs/Xobc_utl_make_lang_kwds.java index a27517de8..d17ae2a6d 100644 --- a/400_xowa/src/gplx/xowa/langs/bldrs/Xobc_utl_make_lang_kwds.java +++ b/400_xowa/src/gplx/xowa/langs/bldrs/Xobc_utl_make_lang_kwds.java @@ -13,3 +13,135 @@ 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.langs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.kwds.*; import gplx.xowa.langs.parsers.*; +public class Xobc_utl_make_lang_kwds implements Gfo_invk, Xol_lang_transform { + private final Xoa_lang_mgr lang_mgr; + public Xobc_utl_make_lang_kwds(Xoa_lang_mgr lang_mgr) {this.lang_mgr = lang_mgr;} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_keep_trailing_colon)) Parse_keep_trailing_colon(m.ReadBry("langs"), m.ReadBry("text")); + else if (ctx.Match(k, Invk_prepend_hash)) Parse_prepend_hash(m.ReadBry("langs"), m.ReadBry("text")); + else if (ctx.Match(k, Invk_add_words)) Parse_add_words(m.ReadBry("langs"), m.ReadBry("text")); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_keep_trailing_colon = "keep_trailing_colon", Invk_prepend_hash = "prepend_hash", Invk_add_words = "add_words"; + + public byte[] Kwd_transform(byte[] lang_key, byte[] kwd_key, byte[] kwd_word) { + byte[] rv = kwd_word; + if (!Hash_itm_applies(trailing_colons, lang_key, kwd_key, kwd_word)) { + int kwd_last = rv.length - 1; + if (kwd_last > 0 && rv[kwd_last] == Byte_ascii.Colon) + rv = Bry_.Mid(rv, 0, rv.length - 1); + } + if (Hash_itm_applies(prepend_hash, lang_key, kwd_key, kwd_word)) { + if (rv.length > 0 && rv[0] != Byte_ascii.Hash) + rv = Bry_.Add(Byte_ascii.Hash, rv); + } + return rv; + } + public void Add_words() { + Ordered_hash hash = add_words_hash; + Ordered_hash tmp = Ordered_hash_.New_bry(); + int hash_len = hash.Count(); + for (int i = 0; i < hash_len; i++) { + Xobcl_kwd_lang cfg_lang = (Xobcl_kwd_lang)hash.Get_at(i); + Xol_lang_itm lang = lang_mgr.Get_by(cfg_lang.Key_bry()); if (lang == null) continue; + int cfg_grp_len = cfg_lang.Grps().length; + for (int j = 0; j < cfg_grp_len; j++) { + Xobcl_kwd_row cfg_grp = cfg_lang.Grps()[j]; + int kwd_id = Xol_kwd_grp_.Id_by_bry(cfg_grp.Key()); + if (kwd_id == -1) throw Err_.new_wo_type("could not find kwd for key", "key", String_.new_u8(cfg_grp.Key())); + Xol_kwd_grp kwd_grp = lang.Kwd_mgr().Get_at(kwd_id); + tmp.Clear(); + if (kwd_grp == null) { + kwd_grp = lang.Kwd_mgr().Get_or_new(kwd_id); + kwd_grp.Srl_load(Bool_.N, Bry_.Ary_empty); // ASSUME: kwd explicitly added, but does not exist in language; default to !case_match + } + + for (Xol_kwd_itm itm : kwd_grp.Itms()) + tmp.Add(itm.Val(), itm.Val()); + if (cfg_grp.Itms().length == 0) { + if (!tmp.Has(cfg_grp.Key())) tmp.Add(cfg_grp.Key(), cfg_grp.Key()); + } + else { + for (byte[] itm : cfg_grp.Itms()) { + if (!tmp.Has(itm)) tmp.Add(itm, itm); + } + } + byte[][] words = (byte[][])tmp.To_ary(byte[].class); + kwd_grp.Srl_load(kwd_grp.Case_match(), words); + } + } + } + boolean Hash_itm_applies(Ordered_hash hash, byte[] lang_key, byte[] kwd_key, byte[] kwd_word) { + Xobcl_kwd_lang cfg_lang = (Xobcl_kwd_lang)hash.Get_by(lang_key); if (cfg_lang == null) return false; + Xobcl_kwd_row cfg_grp = cfg_lang.Grps_get_by_key(kwd_key); if (cfg_grp == null) return false; + return cfg_grp.Itms().length == 0 || cfg_grp.Itms_has(kwd_word); + } + public void Parse_add_words(byte[] langs_bry, byte[] kwds) {Parse(langs_bry, kwds, add_words_hash);} private Ordered_hash add_words_hash = Ordered_hash_.New_bry(); + public void Parse_prepend_hash(byte[] langs_bry, byte[] kwds) {Parse(langs_bry, kwds, prepend_hash);} private Ordered_hash prepend_hash = Ordered_hash_.New_bry(); + public void Parse_keep_trailing_colon(byte[] langs_bry, byte[] kwds) {Parse(langs_bry, kwds, trailing_colons);} private Ordered_hash trailing_colons = Ordered_hash_.New_bry(); + private void Parse(byte[] langs_bry, byte[] kwds, Ordered_hash hash) { + Xobcl_kwd_row[] itms = Parse(kwds); + Xol_lang_stub[] stub_ary = Xol_lang_stub_.Ary(); // NOTE: was "lang_mgr.To_hash(langs_bry);" which was effectively "wiki_types" -> all langs; DATE:2015-10-07 + int len = stub_ary.length; + for (int i = 0; i < len; ++i) { + Xol_lang_stub stub_itm = stub_ary[i]; + byte[] key = stub_itm.Key(); + Xobcl_kwd_lang grp = (Xobcl_kwd_lang)hash.Get_by(key); + if (grp == null) { + grp = new Xobcl_kwd_lang(key, itms); + hash.Add(key, grp); + } + else + grp.Merge(itms); + } + } + @gplx.Internal protected static Xobcl_kwd_row[] Parse(byte[] src) { + int src_len = src.length, pos = 0, fld_bgn = 0; + byte[] cur_key = Bry_.Empty; + Xol_csv_parser csv_parser = Xol_csv_parser.Instance; + List_adp rv = List_adp_.New(); int fld_idx = 0; + while (true) { + boolean last = pos == src_len; // NOTE: logic occurs b/c of \n}~-> dlm which gobbles up last \n + byte b = last ? Byte_ascii.Nl : src[pos]; + switch (b) { + case Byte_ascii.Pipe: + cur_key = csv_parser.Load(src, fld_bgn, pos); + fld_bgn = pos + 1; + ++fld_idx; + break; + case Byte_ascii.Nl: + if (pos - fld_bgn > 0 || fld_idx == 1) { + byte[] cur_val = csv_parser.Load(src, fld_bgn, pos); + Xobcl_kwd_row row = new Xobcl_kwd_row(cur_key, Bry_split_.Split(cur_val, Byte_ascii.Tilde)); + rv.Add(row); + } + fld_bgn = pos + 1; + fld_idx = 0; + break; + default: + break; + } + if (last) break; + ++pos; + } + return (Xobcl_kwd_row[])rv.To_ary(Xobcl_kwd_row.class); + } +} +class Xobcl_kwd_lang { + public Xobcl_kwd_lang(byte[] key_bry, Xobcl_kwd_row[] grps) { + this.key_bry = key_bry; this.grps = grps; + for (Xobcl_kwd_row grp : grps) + grps_hash.Add(grp.Key(), grp); + } + public void Merge(Xobcl_kwd_row[] v) { + grps = (Xobcl_kwd_row[])Array_.Resize_add(grps, v); + for (Xobcl_kwd_row grp : v) { + grps_hash.Add_if_dupe_use_nth(grp.Key(), grp); // NOTE: Add_if_dupe_use_nth instead of Add b/c kwds may be expanded; EX: lst is added to all langs but de requires #lst~#section~Abschnitt~; DATE:2013-06-02 + } + } + public Xobcl_kwd_row Grps_get_by_key(byte[] key) {return (Xobcl_kwd_row)grps_hash.Get_by(key);} private Ordered_hash grps_hash = Ordered_hash_.New_bry(); + public byte[] Key_bry() {return key_bry;} private byte[] key_bry; + public Xobcl_kwd_row[] Grps() {return grps;} private Xobcl_kwd_row[] grps; +} diff --git a/400_xowa/src/gplx/xowa/langs/bldrs/Xobc_utl_make_lang_tst.java b/400_xowa/src/gplx/xowa/langs/bldrs/Xobc_utl_make_lang_tst.java index a27517de8..a741d90bc 100644 --- a/400_xowa/src/gplx/xowa/langs/bldrs/Xobc_utl_make_lang_tst.java +++ b/400_xowa/src/gplx/xowa/langs/bldrs/Xobc_utl_make_lang_tst.java @@ -13,3 +13,166 @@ 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.langs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import org.junit.*; import gplx.core.strings.*; +import gplx.xowa.langs.*; +public class Xobc_utl_make_lang_tst { + @Before public void init() {fxt.Clear();} private Xobc_utl_make_lang_fxt fxt = new Xobc_utl_make_lang_fxt(); + @Test public void Parse() { + fxt.Parse_rows(String_.Concat_lines_nl + ( "" + , "if|#if~#si~" + , "" + , "ifeq|#ifeq~#sieq" + , "" + , "expr|" + ) + , fxt.row_("if", "#if", "#si") + , fxt.row_("ifeq", "#ifeq", "#sieq") + , fxt.row_("expr") + ); + } + @Test public void Trailing_colon() { + fxt.Kwd_mgr().Parse_keep_trailing_colon(Bry_.new_a7("fr"), Bry_.new_u8(String_.Concat_lines_nl + ( "if|if:~si:~" + , "ifeq|" + ))); + fxt.Ini_file_mw_core("fr", String_.Concat_lines_nl + ( "$magicWords = array(" + , " 'expr' => array(0, 'expr:')," + , " 'if' => array(0, 'if:', 'si:', 'if_unchanged:')," + , " 'ifeq' => array(0, 'ifeq:', 'sieq:')," + , ");" + ) + ); + fxt.Mgr().Bld_all(); + fxt.Tst_file_xo("fr", String_.Concat_lines_nl + ( "this" + , ".keywords" + , " .load_text(" + , "<:['" + , "expr|0|expr~" + , "if|0|if:~si:~if_unchanged~" + , "ifeq|0|ifeq:~sieq:~" + , "']:>" + , ").lang" + , ";" + )); + } + @Test public void Prepend_hash() { + fxt.Kwd_mgr().Parse_prepend_hash(Bry_.new_a7("fr"), Bry_.new_u8(String_.Concat_lines_nl + ( "if|if:~si:~" + , "ifeq|" + , "tag|tag~" + ))); + fxt.Ini_file_mw_core("fr", String_.Concat_lines_nl + ( "$magicWords = array(" + , " 'tag' => array( '0', 'etiqueta', 'ETIQUETA', 'tag' )," + , " 'expr' => array(0, 'expr:')," + , " 'if' => array(0, 'if:', 'si:', 'if_unchanged:')," + , " 'ifeq' => array(0, 'ifeq:', 'sieq:')," + , ");" + ) + ); + fxt.Mgr().Bld_all(); + fxt.Tst_file_xo("fr", String_.Concat_lines_nl + ( "this" + , ".keywords" + , " .load_text(" + , "<:['" + , "tag|0|etiqueta~ETIQUETA~#tag~" + , "expr|0|expr~" + , "if|0|#if~#si~if_unchanged~" + , "ifeq|0|#ifeq~#sieq~" + , "']:>" + , ").lang" + , ";" + )); + } + @Test public void Add_words_hash() { + fxt.Kwd_mgr().Parse_add_words(Bry_.new_a7("fr"), Bry_.new_u8(String_.Concat_lines_nl + ( "if|if_new:~if~" + , "ifeq|" + ))); + fxt.Ini_file_mw_core("fr", String_.Concat_lines_nl + ( "$magicWords = array(" + , " 'if' => array(0, 'if:', 'si:')," + , " 'ifeq' => array(0, 'sieq:')," + , ");" + ) + ); + fxt.Mgr().Bld_all(); + fxt.Tst_file_xo("fr", String_.Concat_lines_nl + ( "this" + , ".keywords" + , " .load_text(" + , "<:['" + , "if|0|if~si~if_new:~" + , "ifeq|0|sieq~ifeq~" + , "']:>" + , ").lang" + , ";" + )); + } + @Test public void Manual_text() { + fxt.Mgr().Parse_manual_text(Bry_.new_a7("fr"), Bry_.new_u8(String_.Concat_lines_nl + ( "app;" + )) + , fxt.Mgr().Manual_text_end_hash()); + fxt.Ini_file_mw_core("fr", String_.Concat_lines_nl + ( "$magicWords = array(" + , " 'if' => array(0, 'if:', 'si:')," + , ");" + ) + ); + fxt.Mgr().Bld_all(); + fxt.Tst_file_xo("fr", String_.Concat_lines_nl + ( "this" + , ".keywords" + , " .load_text(" + , "<:['" + , "if|0|if~si~" + , "']:>" + , ").lang" + , ";" + , "app;" + )); + } +} +class Xobc_utl_make_lang_fxt { + public Xobc_utl_make_lang Mgr() {return mgr;} private Xobc_utl_make_lang mgr; + public Xobc_utl_make_lang_kwds Kwd_mgr() {return mgr.Kwd_mgr();} + public Xobc_utl_make_lang_fxt Clear() { + app = Xoa_app_fxt.Make__app__edit(); + mgr = new Xobc_utl_make_lang(app.Lang_mgr(), app.Fsys_mgr(), app.Msg_log()); + return this; + } private String_bldr sb = String_bldr_.new_(); private Xoae_app app; + public Xobcl_kwd_row row_(String key, String... itms) {return new Xobcl_kwd_row(Bry_.new_a7(key), Bry_.Ary(itms));} + public void Parse_rows(String raw, Xobcl_kwd_row... expd) {Tfds.Eq_str_lines(Xto_str(expd), Xto_str(Xobc_utl_make_lang_kwds.Parse(Bry_.new_a7(raw))));} + public void Ini_file_mw_core(String lang, String raw) { + Io_url fil = app.Fsys_mgr().Cfg_lang_core_dir().OwnerDir().GenSubFil_nest("mediawiki", "core_php", "Messages" + String_.UpperFirst(lang) + ".php"); + Io_mgr.Instance.SaveFilStr(fil, raw); + } + public void Tst_file_xo(String lang, String expd) { + Io_url fil = Xol_lang_itm_.xo_lang_fil_(app.Fsys_mgr(), lang); + Tfds.Eq_str_lines(expd, Io_mgr.Instance.LoadFilStr(fil)); + } + private String Xto_str(Xobcl_kwd_row[] expd) { + int len = expd.length; + for (int i = 0; i < len; i++) { + Xobcl_kwd_row row = expd[i]; + sb.Add(row.Key()); + byte[][] itms = row.Itms(); + int itms_len = itms.length; + if (itms_len > 0) { + sb.Add_char_pipe(); + for (int j = 0; j < itms_len; j++) { + byte[] itm = itms[j]; + sb.Add(itm).Add_char_pipe(); + } + } + sb.Add_char_nl(); + } + return sb.To_str_and_clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/langs/bldrs/Xobcl_kwd_row.java b/400_xowa/src/gplx/xowa/langs/bldrs/Xobcl_kwd_row.java index a27517de8..d02ff0b27 100644 --- a/400_xowa/src/gplx/xowa/langs/bldrs/Xobcl_kwd_row.java +++ b/400_xowa/src/gplx/xowa/langs/bldrs/Xobcl_kwd_row.java @@ -13,3 +13,14 @@ 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.langs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xobcl_kwd_row { + public Xobcl_kwd_row(byte[] key, byte[][] itms) { + this.key = key; this.itms = itms; + for (byte[] itm : itms) + itms_hash.Add(itm, itm); + } + public byte[] Key() {return key;} private byte[] key; + public byte[][] Itms() {return itms;} private byte[][] itms; + public boolean Itms_has(byte[] key) {return itms_hash.Has(key);} private Ordered_hash itms_hash = Ordered_hash_.New_bry(); +} diff --git a/400_xowa/src/gplx/xowa/langs/bldrs/Xol_lang_transform.java b/400_xowa/src/gplx/xowa/langs/bldrs/Xol_lang_transform.java index a27517de8..6b190e302 100644 --- a/400_xowa/src/gplx/xowa/langs/bldrs/Xol_lang_transform.java +++ b/400_xowa/src/gplx/xowa/langs/bldrs/Xol_lang_transform.java @@ -13,3 +13,11 @@ 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.langs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public interface Xol_lang_transform { + byte[] Kwd_transform(byte[] lang_key, byte[] kwd_key, byte[] kwd_word); +} +class Xol_lang_transform_null implements Xol_lang_transform { + public byte[] Kwd_transform(byte[] lang_key, byte[] kwd_key, byte[] kwd_word) {return kwd_word;} + public static final Xol_lang_transform_null Instance = new Xol_lang_transform_null(); Xol_lang_transform_null() {} +} diff --git a/400_xowa/src/gplx/xowa/langs/bldrs/Xol_mw_lang_parser.java b/400_xowa/src/gplx/xowa/langs/bldrs/Xol_mw_lang_parser.java index a27517de8..ba6c46175 100644 --- a/400_xowa/src/gplx/xowa/langs/bldrs/Xol_mw_lang_parser.java +++ b/400_xowa/src/gplx/xowa/langs/bldrs/Xol_mw_lang_parser.java @@ -13,3 +13,356 @@ 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.langs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.primitives.*; import gplx.core.btries.*; import gplx.core.consoles.*; import gplx.core.intls.*; import gplx.langs.phps.*; import gplx.core.log_msgs.*; +import gplx.xowa.apps.gfs.*; +import gplx.xowa.apps.fsys.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; import gplx.xowa.langs.kwds.*; import gplx.xowa.langs.numbers.*; import gplx.xowa.langs.parsers.*; import gplx.xowa.langs.specials.*; +import gplx.xowa.wikis.nss.*; +public class Xol_mw_lang_parser { + private Php_parser parser = new Php_parser(); private Php_evaluator evaluator; + public Xol_mw_lang_parser(Gfo_msg_log msg_log) {evaluator = new Php_evaluator(msg_log);} + public void Bld_all(Xoa_lang_mgr lang_mgr, Xoa_fsys_mgr fsys_mgr) {Bld_all(lang_mgr, fsys_mgr, Xol_lang_transform_null.Instance);} + public void Bld_all(Xoa_lang_mgr lang_mgr, Xoa_fsys_mgr fsys_mgr, Xol_lang_transform lang_transform) { + Io_url lang_root = fsys_mgr.Cfg_lang_core_dir().OwnerDir(); + Parse_mediawiki(lang_mgr, lang_root.GenSubDir("mediawiki"), lang_transform); + Save_langs(lang_mgr, lang_root.GenSubDir(Xol_mw_lang_parser.Dir_name_core), Ordered_hash_.New_bry(), Ordered_hash_.New_bry()); + } + public void Save_langs(Xoa_lang_mgr lang_mgr, Io_url xowa_root, Ordered_hash manual_text_bgn, Ordered_hash manual_text_end) { + int len = lang_mgr.Len(); + Xoa_gfs_bldr bldr = new Xoa_gfs_bldr(); + for (int i = 0; i < len; i++) { + Xol_lang_itm lang = lang_mgr.Get_at(i); + String lang_key = lang.Key_str(); + Io_url lang_url = xowa_root.GenSubFil(lang_key + ".gfs"); + Save_langs_by_manual_text(bldr, manual_text_bgn, lang_key); + + Xol_lang_srl.Save_num_mgr(bldr, lang.Num_mgr()); + bldr.Add_proc_init_many("this").Add_nl(); + if (lang.Fallback_bry() != null) // NOTE: fallback will often be null; EX: en + bldr.Add_proc_cont_one(Xol_lang_itm.Invk_fallback_load).Add_parens_str(lang.Fallback_bry()).Add_nl(); + if (!lang.Dir_ltr()) // NOTE: only save dir_ltr if false; EX: en + bldr.Add_proc_cont_one(Xol_lang_itm.Invk_dir_rtl_).Add_parens_str(Yn.To_str(!lang.Dir_ltr())).Add_nl(); + Xol_lang_srl.Save_ns_grps(bldr, lang.Ns_names(), Xol_lang_itm.Invk_ns_names); + Xol_lang_srl.Save_ns_grps(bldr, lang.Ns_aliases(), Xol_lang_itm.Invk_ns_aliases); + Xol_lang_srl.Save_specials(bldr, lang.Specials_mgr()); + Xol_lang_srl.Save_keywords(bldr, lang.Kwd_mgr()); + Xol_lang_srl.Save_messages(bldr, lang.Msg_mgr(), true); + bldr.Add_term_nl(); + + Save_langs_by_manual_text(bldr, manual_text_end, lang_key); + Io_mgr.Instance.SaveFilBfr(lang_url, bldr.Bfr()); + } + } + private void Save_langs_by_manual_text(Xoa_gfs_bldr bldr, Ordered_hash manual_text_hash, String lang_key) { + byte[][] itm = (byte[][])manual_text_hash.Get_by(Bry_.new_u8(lang_key)); + if (itm != null) bldr.Bfr().Add(itm[1]); + } + public void Parse_mediawiki(Xoa_lang_mgr lang_mgr, Io_url mediawiki_root, Xol_lang_transform lang_transform) { + Bry_bfr bfr = Bry_bfr_.New(); + Parse_file_core_php(lang_mgr, mediawiki_root, bfr, lang_transform); + Parse_file_xtns_php(lang_mgr, mediawiki_root, bfr, lang_transform); + Parse_file_json(lang_mgr, bfr, lang_transform, mediawiki_root.GenSubDir("core_json")); + Parse_file_json(lang_mgr, bfr, lang_transform, mediawiki_root.GenSubDir("xtns_json")); + } + private void Parse_file_core_php(Xoa_lang_mgr lang_mgr, Io_url mediawiki_root, Bry_bfr bfr, Xol_lang_transform lang_transform) { + Io_url dir = mediawiki_root.GenSubDir("core_php"); + Io_url[] urls = Io_mgr.Instance.QueryDir_fils(dir); + int len = urls.length; + for (int i = 0; i < len; i++) { + Io_url url = urls[i]; + try { + String lang_key = String_.Replace(String_.Lower(String_.Mid(url.NameOnly(), 8)), "_", "-"); // 8=Messages.length; lower b/c format is MessagesEn.php (need "en") + // if (String_.In(lang_key, "qqq", "enrtl", "bbc", "bbc-latn")) continue; + String text = Io_mgr.Instance.LoadFilStr(url); + Xol_lang_itm lang = lang_mgr.Get_by_or_new(Bry_.new_u8(lang_key)); + this.Parse_core(text, lang, bfr, lang_transform); + } catch (Exception exc) {Err_.Noop(exc); Console_adp__sys.Instance.Write_str_w_nl("failed to parse " + url.NameOnly() + Err_.Message_gplx_log(exc));} + } + } + private void Parse_file_xtns_php(Xoa_lang_mgr lang_mgr, Io_url mediawiki_root, Bry_bfr bfr, Xol_lang_transform lang_transform) { + Io_url dir = mediawiki_root.GenSubDir("xtns_php"); + Io_url[] urls = Io_mgr.Instance.QueryDir_fils(dir); + int len = urls.length; + for (int i = 0; i < len; i++) { + Io_url url = urls[i]; + try { + String text = Io_mgr.Instance.LoadFilStr(url); + boolean prepend_hash = String_.Eq("ParserFunctions.i18n.magic", url.NameOnly()); + this.Parse_xtn(text, url, lang_mgr, bfr, prepend_hash, lang_transform); + } catch (Exception exc) {Err_.Noop(exc); Console_adp__sys.Instance.Write_str_w_nl("failed to parse " + url.NameOnly() + Err_.Message_gplx_log(exc));} + } + } + private void Parse_file_json(Xoa_lang_mgr lang_mgr, Bry_bfr bfr, Xol_lang_transform lang_transform, Io_url root_dir) { + Io_url[] dirs = Io_mgr.Instance.QueryDir_args(root_dir).DirOnly_().ExecAsUrlAry(); + int dirs_len = dirs.length; + for (int i = 0; i < dirs_len; i++) { + Io_url dir = dirs[i]; + Io_url[] fils = Io_mgr.Instance.QueryDir_args(dir).ExecAsUrlAry(); + int fils_len = fils.length; + for (int j = 0; j < fils_len; ++j) { + Io_url fil = fils[j]; + try { + Xol_lang_itm lang = lang_mgr.Get_by_or_new(Bry_.new_u8(fil.NameOnly())); + Xob_i18n_parser.Load_msgs(true, lang, fil); + } catch (Exception exc) {Err_.Noop(exc); Console_adp__sys.Instance.Write_str_w_nl(String_.Format("failed to parse json file; url={0} err={1}\n", fil.Raw(), Err_.Message_gplx_log(exc)));} + } + } + } + public void Parse_core(String text, Xol_lang_itm lang, Bry_bfr bfr, Xol_lang_transform lang_transform) { + evaluator.Clear(); + parser.Parse_tkns(text, evaluator); + Php_line[] lines = (Php_line[])evaluator.List().To_ary(Php_line.class); + int lines_len = lines.length; + List_adp bry_list = List_adp_.New(); + for (int i = 0; i < lines_len; i++) { + Php_line_assign line = (Php_line_assign)lines[i]; + byte[] key = line.Key().Val_obj_bry(); + Object o = Tid_hash.Get_by_bry(key); + if (o != null) { + Byte_obj_val stub = (Byte_obj_val)o; + switch (stub.Val()) { + case Tid_namespaceNames: + Parse_array_kv(bry_list, line); + lang.Ns_names().Ary_set_(Parse_namespace_strings(bry_list, true)); + break; + case Tid_namespaceAliases: + Parse_array_kv(bry_list, line); + lang.Ns_aliases().Ary_set_(Parse_namespace_strings(bry_list, false)); + break; + case Tid_specialPageAliases: + Parse_specials(line, lang.Key_bry(), lang.Specials_mgr()); + break; + case Tid_magicwords: + Parse_magicwords(line, lang.Key_bry(), lang.Kwd_mgr(), false, lang_transform); + break; + case Tid_messages: + Parse_array_kv(bry_list, line); + Parse_messages(bry_list, lang, bfr); + break; + case Tid_fallback: + byte[] fallback_bry = Php_itm_.Parse_bry(line.Val()); + if (!Bry_.Eq(fallback_bry, Bool_.True_bry)) // MessagesEn.php has fallback = false; ignore + lang.Fallback_bry_(fallback_bry); + break; + case Tid_rtl: + byte[] rtl_bry = Php_itm_.Parse_bry(line.Val()); + boolean dir_rtl = Bry_.Eq(rtl_bry, Bool_.True_bry); + lang.Dir_ltr_(!dir_rtl); + break; + case Tid_separatorTransformTable: + Parse_separatorTransformTable(line, lang.Num_mgr()); + break; + case Tid_digitTransformTable: + Parse_digitTransformTable(line, lang.Num_mgr()); + break; + case Tid_digitGroupingPattern: + byte[] digitGroupingPattern = Php_itm_.Parse_bry(line.Val()); + lang.Num_mgr().Num_grp_fmtr().Digit_grouping_pattern_(digitGroupingPattern); + break; + } + } + } + lang.Evt_lang_changed(); + } + // public static String[] Lang_skip = new String[] {"qqq", "enrtl", "akz", "sxu", "test", "mwv", "rup", "hu-formal", "tzm", "bbc", "bbc-latn", "lrc", "ttt", "gom", "gom-latn"}; + public static String[] Lang_skip = String_.Ary_empty; + public void Parse_xtn(String text, Io_url url, Xoa_lang_mgr lang_mgr, Bry_bfr bfr, boolean prepend_hash, Xol_lang_transform lang_transform) { + evaluator.Clear(); + parser.Parse_tkns(text, evaluator); + List_adp bry_list = List_adp_.New(); + Php_line[] lines = (Php_line[])evaluator.List().To_ary(Php_line.class); + int lines_len = lines.length; + for (int i = 0; i < lines_len; i++) { + Php_line_assign line = (Php_line_assign)lines[i]; + byte[] key = line.Key().Val_obj_bry(); + Object o = Tid_hash.Get_by_bry(key); + if (o != null) { + Php_key[] subs = line.Key_subs(); + if (subs.length == 0) continue; // ignore if no lang_key; EX: ['en'] + byte[] lang_key = subs[0].Val_obj_bry(); + try { + if (String_.In(String_.new_u8(lang_key), Lang_skip)) continue; + Xol_lang_itm lang = lang_mgr.Get_by_or_new(lang_key); + Byte_obj_val stub = (Byte_obj_val)o; + switch (stub.Val()) { + case Tid_messages: + Parse_array_kv(bry_list, line); + Parse_messages(bry_list, lang, bfr); + break; + case Tid_magicwords: + if (line.Key_subs().length == 0) continue; // ignore lines like $magicWords = array(); + if (line.Key_subs().length > 1) throw Err_.new_wo_type("magicWords in xtn must have only 1 accessor", "len", line.Key_subs().length); + Php_key accessor = line.Key_subs()[0]; + byte[] accessor_bry = accessor.Val_obj_bry(); + if (Bry_.Eq(accessor_bry, lang_key)) // accessor must match lang_key + Parse_magicwords(line, lang.Key_bry(), lang.Kwd_mgr(), prepend_hash, lang_transform); + break; + } + } catch (Exception exc) {Err_.Noop(exc); Console_adp__sys.Instance.Write_str_w_nl("failed to parse " + url.NameOnly() + Err_.Message_gplx_log(exc));} + } + } + } + private void Parse_array_kv(List_adp rv, Php_line_assign line) { + rv.Clear(); + Php_itm_ary ary = (Php_itm_ary)line.Val(); + int subs_len = ary.Subs_len(); + for (int i = 0; i < subs_len; i++) { + Php_itm_kv kv = (Php_itm_kv)ary.Subs_get(i); + rv.Add(Php_itm_.Parse_bry(kv.Key())); + rv.Add(Php_itm_.Parse_bry(kv.Val())); + } + } + public Xow_ns[] Parse_namespace_strings(List_adp list, boolean ns_names) { + byte[][] brys = (byte[][])list.To_ary(byte[].class); + int brys_len = brys.length; + Xow_ns[] rv = new Xow_ns[brys_len / 2]; + int key_dif = ns_names ? 0 : 1; + int val_dif = ns_names ? 1 : 0; + for (int i = 0; i < brys_len; i+=2) { + byte[] kv_key = brys[i + key_dif]; + byte[] kv_val = brys[i + val_dif]; + Bry_.Replace_all_direct(kv_val, Byte_ascii.Underline, Byte_ascii.Space); // NOTE: siteInfo.xml names have " " not "_" (EX: "User talk"). for now, follow that convention + int ns_id = Id_by_mw_name(kv_key); +// if (ns_id == Xow_ns_.Tid__null) throw Err_mgr.Instance.fmt_auto_(GRP_KEY, "namespace_names", String_.new_u8(kv_key)); + rv[i / 2] = new Xow_ns(ns_id, Xow_ns_case_.Tid__1st, kv_val, false); // note that Xow_ns is being used as glorified id-name struct; case_match and alias values do not matter + } + return rv; + } + private void Parse_messages(List_adp rv, Xol_lang_itm lang, Bry_bfr bfr) { + byte[][] brys = (byte[][])rv.To_ary(byte[].class); + int brys_len = brys.length; + Xol_msg_mgr mgr = lang.Msg_mgr(); + List_adp quote_itm_list = List_adp_.New(); Byte_obj_ref quote_parse_result = Byte_obj_ref.zero_(); + for (int i = 0; i < brys_len; i+=2) { + byte[] kv_key = brys[i]; + Xol_msg_itm itm = mgr.Itm_by_key_or_new(kv_key); + if (itm == null) continue; + byte[] kv_val = brys[i + 1]; + kv_val = php_quote_parser.Parse_as_bry(quote_itm_list, kv_val, quote_parse_result, bfr); + Xol_msg_itm_.update_val_(itm, kv_val); + itm.Dirty_(true); + } + } private Php_text_itm_parser php_quote_parser = new Php_text_itm_parser(); + private void Parse_magicwords(Php_line_assign line, byte[] lang_key, Xol_kwd_mgr mgr, boolean prepend_hash, Xol_lang_transform lang_transform) { + Php_itm_ary ary = (Php_itm_ary)line.Val(); + int subs_len = ary.Subs_len(); + for (int i = 0; i < subs_len; i++) { + Php_itm_sub sub = ary.Subs_get(i); + Php_itm_kv kv = (Php_itm_kv)sub; + byte[] kv_key = kv.Key().Val_obj_bry(); + Php_itm_ary kv_ary = (Php_itm_ary)kv.Val(); + int kv_ary_len = kv_ary.Subs_len(); + boolean case_match = false; // if 1 arg, default to false + int kv_ary_bgn = 0; int words_len = kv_ary_len; // if 1 arg, default to entire kv_ary; words_len + int case_match_int = Php_itm_.Parse_int_or(kv_ary.Subs_get(0), Int_.Min_value); + if (case_match_int != Int_.Min_value) { + case_match = Parse_int_as_bool(kv_ary.Subs_get(0)); // arg[0] is case_match + kv_ary_bgn = 1; // arg[1] is 1st word + words_len = kv_ary_len - 1; // words.len = kv_len - 1 (skip case_match + } + byte[][] words = new byte[words_len][]; + for (int j = kv_ary_bgn; j < kv_ary_len; j++) { + Php_itm_sub kv_sub = kv_ary.Subs_get(j); + byte[] word = Php_itm_.Parse_bry(kv_sub); +// if (prepend_hash && word[0] != Byte_ascii.Hash) word = Bry_.Add(Byte_ascii.Hash, word); + words[j - kv_ary_bgn] = lang_transform.Kwd_transform(lang_key, kv_key, word); + } + int keyword_id = Xol_kwd_grp_.Id_by_bry(kv_key); if (keyword_id == -1) continue; //throw Err_mgr.Instance.fmt_(Err_scope_keywords, "invalid_key", "key does not have id", String_.new_u8(kv_key)); + Xol_kwd_grp grp = mgr.Get_or_new(keyword_id); + grp.Srl_load(case_match, words); + } + } + private void Parse_specials(Php_line_assign line, byte[] lang_key, Xol_specials_mgr specials_mgr) { + specials_mgr.Clear(); // NOTE: always clear, else will try to readd to en.gfs + Php_itm_ary ary = (Php_itm_ary)line.Val(); + int subs_len = ary.Subs_len(); + for (int i = 0; i < subs_len; i++) { + Php_itm_sub sub = ary.Subs_get(i); + Php_itm_kv kv = (Php_itm_kv)sub; + byte[] kv_key = kv.Key().Val_obj_bry(); + Php_itm_ary kv_ary = (Php_itm_ary)kv.Val(); + int kv_ary_len = kv_ary.Subs_len(); + byte[][] aliases = new byte[kv_ary_len][]; + for (int j = 0; j < kv_ary_len; j++) { + Php_itm_sub kv_sub = kv_ary.Subs_get(j); + aliases[j] = Php_itm_.Parse_bry(kv_sub); + } + specials_mgr.Add(kv_key, aliases); + } + } + private boolean Parse_int_as_bool(Php_itm itm) { + int rv = Php_itm_.Parse_int_or(itm, Int_.Min_value); + if (rv == Int_.Min_value) throw Err_.new_wo_type("value must be 0 or 1", "val", String_.new_u8(itm.Val_obj_bry())); + return rv == 1; + } + private void Parse_separatorTransformTable(Php_line_assign line, Xol_num_mgr num_mgr) { + if (line.Val().Itm_tid() == Php_itm_.Tid_null) return;// en is null; $separatorTransformTable = null; + Php_itm_ary ary = (Php_itm_ary)line.Val(); + int subs_len = ary.Subs_len(); + List_adp tmp_list = List_adp_.New(); Byte_obj_ref tmp_result = Byte_obj_ref.zero_(); Bry_bfr tmp_bfr = Bry_bfr_.Reset(16); + for (int i = 0; i < subs_len; i++) { + Php_itm_kv kv = (Php_itm_kv)ary.Subs_get(i); + byte[] key_bry = Php_itm_.Parse_bry(kv.Key()), val_bry = Php_itm_.Parse_bry(kv.Val()); + val_bry = php_quote_parser.Parse_as_bry(tmp_list, val_bry, tmp_result, tmp_bfr); + Xol_csv_parser.Instance.Load(tmp_bfr, val_bry); + val_bry = tmp_bfr.To_bry_and_clear(); + if ( Bry_.Eq(key_bry, Bry_separatorTransformTable_dot) + || Bry_.Eq(key_bry, Bry_separatorTransformTable_comma) + ) + num_mgr.Separators_mgr().Set(key_bry, val_bry); + else throw Err_.new_unhandled(String_.new_u8(key_bry)); // NOTE: as of v1.22.2, all Messages only have a key of "." or "," DATE:2014-04-15 + } + } private static final byte[] Bry_separatorTransformTable_comma = new byte[] {Byte_ascii.Comma}, Bry_separatorTransformTable_dot = new byte[] {Byte_ascii.Dot}; + private void Parse_digitTransformTable(Php_line_assign line, Xol_num_mgr num_mgr) { + if (line.Val().Itm_tid() == Php_itm_.Tid_null) return;// en is null; $digitTransformTable = null; + Php_itm_ary ary = (Php_itm_ary)line.Val(); + int subs_len = ary.Subs_len(); + List_adp tmp_list = List_adp_.New(); Byte_obj_ref tmp_result = Byte_obj_ref.zero_(); Bry_bfr tmp_bfr = Bry_bfr_.Reset(16); + for (int i = 0; i < subs_len; i++) { + Php_itm_kv kv = (Php_itm_kv)ary.Subs_get(i); + byte[] key_bry = Php_itm_.Parse_bry(kv.Key()), val_bry = Php_itm_.Parse_bry(kv.Val()); + val_bry = php_quote_parser.Parse_as_bry(tmp_list, val_bry, tmp_result, tmp_bfr); + num_mgr.Digits_mgr().Set(key_bry, val_bry); + } + } + private static final byte + Tid_namespaceNames = 0, Tid_namespaceAliases = 1, Tid_specialPageAliases = 2 + , Tid_messages = 3, Tid_magicwords = 4 + , Tid_fallback = 5, Tid_rtl = 6 + , Tid_separatorTransformTable = 7, Tid_digitTransformTable = 8, Tid_digitGroupingPattern = 9 + ; + private static Hash_adp_bry Tid_hash = Hash_adp_bry.cs() + .Add_str_byte("namespaceNames", Tid_namespaceNames).Add_str_byte("namespaceAliases", Tid_namespaceAliases).Add_str_byte("specialPageAliases", Tid_specialPageAliases) + .Add_str_byte("messages", Tid_messages).Add_str_byte("magicWords", Tid_magicwords) + .Add_str_byte("fallback", Tid_fallback).Add_str_byte("rtl", Tid_rtl) + .Add_str_byte("separatorTransformTable", Tid_separatorTransformTable) + .Add_str_byte("digitTransformTable", Tid_digitTransformTable).Add_str_byte("digitGroupingPattern", Tid_digitGroupingPattern) + ; + public static int Id_by_mw_name(byte[] src) { + if (mw_names == null) { + mw_names = Btrie_slim_mgr.cs(); + mw_names.Add_obj("NS_MEDIA", new Int_obj_val(Xow_ns_.Tid__media)); + mw_names.Add_obj("NS_SPECIAL", new Int_obj_val(Xow_ns_.Tid__special)); + mw_names.Add_obj("NS_MAIN", new Int_obj_val(Xow_ns_.Tid__main)); + mw_names.Add_obj("NS_TALK", new Int_obj_val(Xow_ns_.Tid__talk)); + mw_names.Add_obj("NS_USER", new Int_obj_val(Xow_ns_.Tid__user)); + mw_names.Add_obj("NS_USER_TALK", new Int_obj_val(Xow_ns_.Tid__user_talk)); + mw_names.Add_obj("NS_PROJECT", new Int_obj_val(Xow_ns_.Tid__project)); + mw_names.Add_obj("NS_PROJECT_TALK", new Int_obj_val(Xow_ns_.Tid__project_talk)); + mw_names.Add_obj("NS_FILE", new Int_obj_val(Xow_ns_.Tid__file)); + mw_names.Add_obj("NS_FILE_TALK", new Int_obj_val(Xow_ns_.Tid__file_talk)); + mw_names.Add_obj("NS_MEDIAWIKI", new Int_obj_val(Xow_ns_.Tid__mediawiki)); + mw_names.Add_obj("NS_MEDIAWIKI_TALK", new Int_obj_val(Xow_ns_.Tid__mediawiki_talk)); + mw_names.Add_obj("NS_TEMPLATE", new Int_obj_val(Xow_ns_.Tid__template)); + mw_names.Add_obj("NS_TEMPLATE_TALK", new Int_obj_val(Xow_ns_.Tid__template_talk)); + mw_names.Add_obj("NS_HELP", new Int_obj_val(Xow_ns_.Tid__help)); + mw_names.Add_obj("NS_HELP_TALK", new Int_obj_val(Xow_ns_.Tid__help_talk)); + mw_names.Add_obj("NS_CATEGORY", new Int_obj_val(Xow_ns_.Tid__category)); + mw_names.Add_obj("NS_CATEGORY_TALK", new Int_obj_val(Xow_ns_.Tid__category_talk)); + } + Object o = mw_names.Match_exact(src, 0, src.length); + return o == null ? Xow_ns_.Tid__null : ((Int_obj_val)o).Val(); + } private static Btrie_slim_mgr mw_names; + public static final String Dir_name_core = "core"; +} diff --git a/400_xowa/src/gplx/xowa/langs/bldrs/Xol_mw_lang_parser_tst.java b/400_xowa/src/gplx/xowa/langs/bldrs/Xol_mw_lang_parser_tst.java index a27517de8..5a048d8bf 100644 --- a/400_xowa/src/gplx/xowa/langs/bldrs/Xol_mw_lang_parser_tst.java +++ b/400_xowa/src/gplx/xowa/langs/bldrs/Xol_mw_lang_parser_tst.java @@ -13,3 +13,303 @@ 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.langs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import org.junit.*; +import gplx.core.intls.*; import gplx.core.log_msgs.*; import gplx.xowa.parsers.lnkis.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.kwds.*; import gplx.xowa.langs.msgs.*; import gplx.xowa.langs.specials.*; +public class Xol_mw_lang_parser_tst { + @Before public void init() {fxt.Clear();} private Xol_mw_lang_parser_fxt fxt = new Xol_mw_lang_parser_fxt(); + @Test public void Core_keywords() { + fxt.Parse_core("$magicWords = array('toc' => array(0, 'a1', 'a2', 'a3'), 'notoc' => array(1, 'b1', 'b2', 'b3'));") + .Tst_keyword(Xol_kwd_grp_.Id_toc, false, "a1", "a2", "a3") + .Tst_keyword(Xol_kwd_grp_.Id_notoc, true, "b1", "b2", "b3") + ; + } + @Test public void Core_keywords_img_thumb() { + fxt.Parse_core("$magicWords = array('img_thumbnail' => array(1, 'thumb', 'thmb'));") + .Tst_keyword_img("thumb", Xop_lnki_arg_parser.Tid_thumb) + .Tst_keyword_img("thmb" , Xop_lnki_arg_parser.Tid_thumb) + .Tst_keyword_img("thum" , Xop_lnki_arg_parser.Tid_caption) + ; + } + @Test public void Core_keywords_img_upright() { + fxt.Parse_core("$magicWords = array('img_upright' => array(1, 'upright', 'upright=$1', 'upright $1'));") + .Tst_keyword_img("upright" , Xop_lnki_arg_parser.Tid_upright) + .Tst_keyword_img("upright " , Xop_lnki_arg_parser.Tid_upright) + ; + } + @Test public void Core_keywords_func_currentmonth() { + Datetime_now.Manual_y_(); + fxt.Parse_core("$magicWords = array('currentmonth' => array(0, 'MOISACTUEL'));") + .Tst_parse("{{MOISACTUEL}}", "01") + ; + Datetime_now.Manual_n_(); + } + @Test public void Core_keywords_func_ns() { + fxt.Parse_core("$magicWords = array('ns' => array(0, 'ESPACEN'));") + .Tst_parse("{{ESPACEN:6}}", "File") + ; + } + @Test public void Keywords_img_dim() { + fxt.Parse_core("$magicWords = array('img_width' => array(1, '$1pxl'));") + .Tst_keyword_img("50pxl", Xop_lnki_arg_parser.Tid_dim) + .Tst_keyword_img("50pxlpxl", Xop_lnki_arg_parser.Tid_dim) + .Tst_keyword_img("50xl", Xop_lnki_arg_parser.Tid_caption) + ; + } + @Test public void Core_namespaces_names() { + fxt.Parse_core("$namespaceNames = array(NS_FILE => 'Fichier');") + .Sync_ns() + .Tst_ns_lkp("Fichier", Xow_ns_.Tid__file) + .Tst_ns_lkp("File" , Xow_ns_.Tid__null) + ; + } + @Test public void Core_namespaces_aliases() { + fxt.Parse_core("$namespaceAliases = array('Discussion_Fichier' => NS_FILE_TALK);") + .Sync_ns() + .Tst_ns_lkp("Discussion Fichier", Xow_ns_.Tid__file_talk) + .Tst_ns_lkp("Discussion Fichierx", Xow_ns_.Tid__null) + ; + } + @Test public void Core_specialPageAliases() { + fxt.Parse_core("$specialPageAliases = array('Randompage' => array('PageAuHasard', 'Page_au_hasard'));") + .Test_specialPageAliases("Randompage", "PageAuHasard", "Page_au_hasard") + ; + } + @Test public void Xtn_keywords_fr() {fxt.Parse_xtn("$magicWords['fr'] = array('if' => array(0, 'si' ));").Tst_parse("{{si:|y|n}}", "n");} + @Test public void Xtn_keywords_de() {fxt.Parse_xtn("$magicWords['de'] = array('if' => array(0, 'si' ));").Tst_parse("{{si:|y|n}}", "Template:Si:");} // should be "Si", not "si"; ALSO, should probably be "{{si:|y|n}}" not "[[:Template:si:]]" DATE:2014-07-04 + @Test public void Core_messages() { + fxt.Parse_core("$messages = array('sunday' => 'dimanche');") + .Tst_message("sunday", 0, "dimanche", false) + ; + } + @Test public void Fallback() { + fxt.Parse_core("$fallback = 'zh-hans';"); + Tfds.Eq("zh-hans", String_.new_u8(fxt.Lang().Fallback_bry())); + } + @Test public void Separator_transform_table() { + fxt.Parse_core("$separatorTransformTable = array( ',' => '.', '.' => ',' );"); + fxt.Num_fmt_tst("1234,56", "1.234.56"); // NOTE: dot is repeated; confirmed with dewiki and {{formatnum:1234,56}} + } + @Test public void Separator_transform_table_fr() { + fxt.Parse_core("$separatorTransformTable = array( ',' => '\\xc2\\xa0', '.' => ',' );"); + fxt.Num_fmt_tst("1234,56", "1 234 56"); // NOTE: nbsp here; also, nbsp is repeated. see dewiki and {{formatnum:1234,56}} + } + @Test public void Digit_transform_table() { + fxt.Save_file("mem/xowa/bin/any/xowa/cfg/lang/mediawiki/core_php/MessagesFr.php" + , "$digitTransformTable = array(" + , " '0' => 'nulla'," + , " '1' => 'I'," + , " '2' => 'II'," + , " '3' => 'III'," + , " '4' => 'IV'," + , " '5' => 'V'," + , " '6' => 'VI'," + , " '7' => 'VII'," + , " '8' => 'VIII'," + , " '9' => 'IX'," + , ");" + ); + fxt.Run_bld_all(); + fxt.Tst_file("mem/xowa/bin/any/xowa/cfg/lang/core/fr.gfs", String_.Concat_lines_nl + ( "numbers {" + , " digits {" + , " clear;" + , " set('0', 'nulla');" + , " set('1', 'I');" + , " set('2', 'II');" + , " set('3', 'III');" + , " set('4', 'IV');" + , " set('5', 'V');" + , " set('6', 'VI');" + , " set('7', 'VII');" + , " set('8', 'VIII');" + , " set('9', 'IX');" + , " }" + , "}" + , "this" + , ";" + )); + } + @Test public void Digit_grouping_pattern() { + fxt.Save_file("mem/xowa/bin/any/xowa/cfg/lang/mediawiki/core_php/MessagesFr.php" + , "$digitGroupingPattern = '##,##,###'" + , ");" + ); + fxt.Run_bld_all(); + fxt.Tst_file("mem/xowa/bin/any/xowa/cfg/lang/core/fr.gfs", String_.Concat_lines_nl + ( "numbers {" + , " digit_grouping_pattern = '##,##,###';" + , "}" + , "this" + , ";" + )); + } + @Test public void Bld() { + fxt.Save_file("mem/xowa/bin/any/xowa/cfg/lang/mediawiki/core_php/MessagesFr.php" + , "$fallback = 'zh-hans';" + , "$rtl = true;" + , "$namespaceNames = array(NS_FILE => 'Filex');" + , "$namespaceAliases = array('File Discussion' => NS_FILE_TALK);" + , "$magicWords = array('currentmonth' => array(0, 'CUR_MONTH'));" + , "$messages = array('sunday' => 'sunday');" + ); + fxt.Save_file("mem/xowa/bin/any/xowa/cfg/lang/mediawiki/xtns_php/Test.il8n.php" + , "$magicWords['fr'] = array('currentyear' => array(0, 'CUR_YEAR'));" + , "$messages['fr'] = array('monday' => 'monday');" + ); + fxt.Save_file("mem/xowa/bin/any/xowa/cfg/lang/mediawiki/core_json/Messages/fr.json" + , "{" + , " \"@metadata\": {" + , " \"authors\": []" + , " }," + , "\"key_1\": \"val_1\"," + , "\"key_2\": \"val $1\"," + , "}" + ); + fxt.Save_file("mem/xowa/bin/any/xowa/cfg/lang/mediawiki/xtns_json/Test2/fr.json" + , "{" + , " \"@metadata\": {" + , " \"authors\": []" + , " }," + , "\"key_3\": \"val_3\"," + , "\"key_4\": \"val $1\"," + , "}" + ); + fxt.Run_bld_all(); + fxt.Tst_file("mem/xowa/bin/any/xowa/cfg/lang/core/fr.gfs", String_.Concat_lines_nl + ( "this" + , ".fallback_load('zh-hans')" + , ".dir_rtl_('y')" + , ".ns_names" + , " .load_text(" + , "<:['" + , "6|Filex" + , "']:>" + , ").lang" + , ".ns_aliases" + , " .load_text(" + , "<:['" + , "7|File Discussion" + , "']:>" + , ").lang" + , ".keywords" + , " .load_text(" + , "<:['" + , "currentmonth|0|CUR_MONTH~" + , "currentyear|0|CUR_YEAR~" + , "']:>" + , ").lang" + , ".messages" + , " .load_text(" + , "<:['" + , "sunday|sunday" + , "monday|monday" + , "key_1|val_1" + , "key_2|val ~{0}" + , "key_3|val_3" + , "key_4|val ~{0}" + , "']:>" + , ").lang" + , ";" + )); + } + @Test public void Dir_ltr() { + fxt.Parse_core("$rtl = 'true';"); + Tfds.Eq(false, fxt.Lang().Dir_ltr()); + } + @Test public void Core_keywords__only_1() { // PURPOSE: some magic words don't specify case-match; EX: Disambiguator.php; DATE:2013-12-24 + fxt.Parse_core("$magicWords = array('toc' => array('a1'));") + .Tst_keyword(Xol_kwd_grp_.Id_toc, false, "a1") + ; + } + @Test public void Core_keywords__skip_case_match__1() { // PURPOSE: some magic words don't specify case-match; EX: Disambiguator.php; DATE:2013-12-24 + fxt.Parse_core("$magicWords = array('toc' => array('a1'));") + .Tst_keyword(Xol_kwd_grp_.Id_toc, false, "a1") + ; + } + @Test public void Core_keywords__skip_case_match__2() { // PURPOSE: some magic words don't specify case-match; EX: Disambiguator.php; DATE:2013-12-24 + fxt.Parse_core("$magicWords = array('toc' => array('a1', 'a2'));") + .Tst_keyword(Xol_kwd_grp_.Id_toc, false, "a1", "a2") + ; + } +} +class Xol_mw_lang_parser_fxt { + Xoae_app app; Xowe_wiki wiki; private Xop_fxt fxt; + Xol_mw_lang_parser parser = new Xol_mw_lang_parser(Gfo_msg_log.Test()); Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); + public void Clear() { + if (app == null) { + app = Xoa_app_fxt.Make__app__edit(); + } + app.Lang_mgr().Clear();// NOTE: always clear the lang + lang = app.Lang_mgr().Get_by_or_new(Bry_.new_a7("fr")); + wiki = Xoa_app_fxt.Make__wiki__edit(app, "en.wikipedia.org", lang); + fxt = new Xop_fxt(app, wiki); + lang.Kwd_mgr().Clear(); lang.Msg_mgr().Clear(); // NOTE: clear kwds and msgs else they will be printed to file; this line must go last b/c various xtns will fill in kwds dynamically + } + public Xol_lang_itm Lang() {return lang;} private Xol_lang_itm lang; + public void Num_fmt_tst(String raw, String expd) {Tfds.Eq(expd, String_.new_u8(lang.Num_mgr().Format_num(Bry_.new_u8(raw))));} + public void Run_bld_all() {parser.Bld_all(app.Lang_mgr(), app.Fsys_mgr());} + public void Save_file(String path, String... lines) { + Io_mgr.Instance.SaveFilStr(Io_url_.mem_fil_(path), String_.Concat_lines_nl(lines)); + } + public void Tst_file(String path, String expd) { + Io_url url = Io_url_.mem_fil_(path); + String actl = Io_mgr.Instance.LoadFilStr(url); + Tfds.Eq_str_lines(expd, actl); + } + public Xol_mw_lang_parser_fxt Parse_core(String raw) {parser.Parse_core(raw, lang, tmp_bfr, Xol_lang_transform_null.Instance); return this;} + public Xol_mw_lang_parser_fxt Parse_xtn (String raw) {parser.Parse_xtn(raw, Io_url_.Empty, app.Lang_mgr(), tmp_bfr, false, Xol_lang_transform_null.Instance); lang.Evt_lang_changed(); return this;} + public Xol_mw_lang_parser_fxt Tst_keyword(int id, boolean case_sensitive, String... words) { + Xol_kwd_grp lst = lang.Kwd_mgr().Get_at(id); if (lst == null) throw Err_.new_wo_type("list should not be null"); + Tfds.Eq(case_sensitive, lst.Case_match()); + int actl_len = lst.Itms().length; + String[] actl = new String[actl_len]; + for (int i = 0; i < actl_len; i++) + actl[i] = String_.new_u8(lst.Itms()[i].Val()); + Tfds.Eq_ary_str(words, actl); + return this; + } + public Xol_mw_lang_parser_fxt Tst_keyword_img(String key_str, byte tid) { + byte[] key = Bry_.new_u8(key_str); + Tfds.Eq(tid, lang.Lnki_arg_parser().Identify_tid(key, 0, key.length)); + return this; + } + public Xol_mw_lang_parser_fxt Tst_parse(String raw, String expd) { + fxt.Reset(); + fxt.Test_parse_page_all_str(raw, expd); + return this; + } + public Xol_mw_lang_parser_fxt Tst_ns_lkp(String key_str, int id) { + byte[] key = Bry_.new_u8(key_str); + Xow_ns ns = (Xow_ns)wiki.Ns_mgr().Names_get_or_null(key, 0, key.length); + int actl = ns == null ? Xow_ns_.Tid__null : ns.Id(); + Tfds.Eq(id, actl); + return this; + } + public Xol_mw_lang_parser_fxt Test_specialPageAliases(String special, String... expd_aliases) { + Xol_specials_itm actl_aliases = lang.Specials_mgr().Get_by_key(Bry_.new_u8(special)); + Tfds.Eq_ary_str(expd_aliases, To_str_ary(actl_aliases)); + return this; + } + private String[] To_str_ary(Xol_specials_itm itm) { + int len = itm.Aliases().length; + String[] rv = new String[len]; + for (int i = 0; i < len; i++) { + rv[i] = String_.new_u8((byte[])itm.Aliases()[i]); + } + return rv; + } + public Xol_mw_lang_parser_fxt Sync_ns() { + Xow_ns_mgr_.rebuild_(lang, wiki.Ns_mgr()); + return this; + } + public Xol_mw_lang_parser_fxt Tst_message(String key, int id, String val, boolean fmt) { + Xol_msg_itm itm = lang.Msg_mgr().Itm_by_key_or_new(Bry_.new_a7(key)); + Tfds.Eq(id, itm.Id()); + Tfds.Eq(val, String_.new_u8(itm.Val())); + Tfds.Eq(fmt, itm.Has_fmt_arg()); + return this; + } +} diff --git a/400_xowa/src/gplx/xowa/langs/bldrs/Xol_ns_grp.java b/400_xowa/src/gplx/xowa/langs/bldrs/Xol_ns_grp.java index a27517de8..db40944a5 100644 --- a/400_xowa/src/gplx/xowa/langs/bldrs/Xol_ns_grp.java +++ b/400_xowa/src/gplx/xowa/langs/bldrs/Xol_ns_grp.java @@ -13,3 +13,32 @@ 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.langs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.langs.parsers.*; +public class Xol_ns_grp implements Gfo_invk { + public Xol_ns_grp(Xol_lang_itm lang) {this.lang = lang;} private Xol_lang_itm lang; + public int Len() {return ary.length;} + public Xow_ns Get_at(int i) {return ary[i];} private Xow_ns[] ary = Ary_empty; + public void Ary_set_(Xow_ns[] v) {this.ary = v;} + public void Ary_add_(Xow_ns[] add_ary) { + int old_ary_len = ary.length; + int add_ary_len = add_ary.length; + Xow_ns[] new_ary = new Xow_ns[old_ary_len + add_ary_len]; + for (int i = 0; i < old_ary_len; i++) + new_ary[i] = ary[i]; + for (int i = 0; i < add_ary_len; i++) + new_ary[i + old_ary_len] = add_ary[i]; + this.ary = new_ary; + } + private static final Xow_ns[] Ary_empty = new Xow_ns[0]; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_lang)) return lang; + else if (ctx.Match(k, Invk_load_text)) Exec_load_text(m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_lang = Xol_lang_srl.Invk_lang, Invk_load_text = Xol_lang_srl.Invk_load_text; + private void Exec_load_text(byte[] bry) { + ary = (Xow_ns[])Array_.Resize_add(ary, Xol_lang_srl.Load_ns_grps(bry)); + } +} diff --git a/400_xowa/src/gplx/xowa/langs/cases/Xol_case_itm.java b/400_xowa/src/gplx/xowa/langs/cases/Xol_case_itm.java index a27517de8..0572fb496 100644 --- a/400_xowa/src/gplx/xowa/langs/cases/Xol_case_itm.java +++ b/400_xowa/src/gplx/xowa/langs/cases/Xol_case_itm.java @@ -13,3 +13,73 @@ 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.langs.cases; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.primitives.*; +import gplx.core.intls.*; +public interface Xol_case_itm extends Gfo_case_itm { + byte Tid(); + byte[] Src_ary(); + byte[] Trg_ary(); + void Case_build_upper(Bry_bfr bfr); + void Case_build_lower(Bry_bfr bfr); + void Case_reuse_upper(byte[] ary, int bgn, int len); + void Case_reuse_lower(byte[] ary, int bgn, int len); + Xol_case_itm Clone(); +} +class Xol_case_itm_byt implements Xol_case_itm { + public Xol_case_itm_byt(byte tid, byte src_byte, byte trg_byte) { + this.tid = tid; this.src_byte = src_byte; this.trg_byte = trg_byte; this.src_ary = new byte[] {src_byte}; this.trg_ary = new byte[] {trg_byte}; + switch (tid) { + case Xol_case_itm_.Tid_both: + case Xol_case_itm_.Tid_upper: upper_byte = trg_byte; lower_byte = src_byte; break; + case Xol_case_itm_.Tid_lower: upper_byte = src_byte; lower_byte = trg_byte; break; + } + } + public byte Tid() {return tid;} private byte tid; + public boolean Is_single_byte() {return true;} + public byte[] Src_ary() {return src_ary;} private byte[] src_ary; + public byte[] Trg_ary() {return trg_ary;} private byte[] trg_ary; + public byte Src_byte() {return src_byte;} private byte src_byte; + public byte Trg_byte() {return trg_byte;} private byte trg_byte; + public void Case_build_upper(Bry_bfr bfr) {bfr.Add_byte(upper_byte);} private byte upper_byte; + public void Case_build_lower(Bry_bfr bfr) {bfr.Add_byte(lower_byte);} private byte lower_byte; + public void Case_reuse_upper(byte[] ary, int bgn, int len) {ary[bgn] = upper_byte;} + public void Case_reuse_lower(byte[] ary, int bgn, int len) {ary[bgn] = lower_byte;} + public Xol_case_itm Clone() {return new Xol_case_itm_byt(tid, src_byte, trg_byte);} + public int Utf8_id_lo() {return lower_byte;} + public int Hashcode_lo() {return lower_byte;} + public int Len_lo() {return 1;} + public byte[] Asymmetric_bry() {return null;} +} +class Xol_case_itm_bry implements Xol_case_itm { + public Xol_case_itm_bry(byte tid, byte[] src_ary, byte[] trg_ary) { + this.tid = tid; this.src_ary = src_ary; this.trg_ary = trg_ary; + switch (tid) { + case Xol_case_itm_.Tid_both: upper_ary = trg_ary; lower_ary = src_ary; break; + case Xol_case_itm_.Tid_upper: upper_ary = trg_ary; lower_ary = src_ary; asymmetric_bry = src_ary; break; + case Xol_case_itm_.Tid_lower: upper_ary = src_ary; lower_ary = trg_ary; asymmetric_bry = trg_ary; break; + } + len_lo = lower_ary.length; + utf8_id_lo = Utf16_.Decode_to_int(lower_ary, 0); + hashcode_ci_lo = Bry_obj_ref.CalcHashCode(lower_ary, 0, len_lo); + } + public byte Tid() {return tid;} public Xol_case_itm_bry Tid_(byte v) {tid = v; return this;} private byte tid; + public boolean Is_single_byte() {return false;} + public byte[] Src_ary() {return src_ary;} private byte[] src_ary; + public byte[] Trg_ary() {return trg_ary;} private byte[] trg_ary; + public void Case_build_upper(Bry_bfr bfr) {bfr.Add(upper_ary);} private byte[] upper_ary; + public void Case_build_lower(Bry_bfr bfr) {bfr.Add(lower_ary);} private byte[] lower_ary; + public void Case_reuse_upper(byte[] ary, int bgn, int len) { // ASSUME: upper/lower have same width; i.e.: upper'ing a character doesn't go from a 2-width byte to a 3-width byte + for (int i = 0; i < len; i++) + ary[i + bgn] = upper_ary[i]; + } + public void Case_reuse_lower(byte[] ary, int bgn, int len) { // ASSUME: upper/lower have same width; i.e.: upper'ing a character doesn't go from a 2-width byte to a 3-width byte + for (int i = 0; i < len; i++) + ary[i + bgn] = lower_ary[i]; + } + public Xol_case_itm Clone() {return new Xol_case_itm_bry(tid, src_ary, trg_ary);} + public int Len_lo() {return len_lo;} private int len_lo; + public int Utf8_id_lo() {return utf8_id_lo;} private int utf8_id_lo; + public byte[] Asymmetric_bry() {return asymmetric_bry;} private byte[] asymmetric_bry; + public int Hashcode_lo() {return hashcode_ci_lo;} private int hashcode_ci_lo; +} diff --git a/400_xowa/src/gplx/xowa/langs/cases/Xol_case_itm_.java b/400_xowa/src/gplx/xowa/langs/cases/Xol_case_itm_.java index a27517de8..9114f9d0c 100644 --- a/400_xowa/src/gplx/xowa/langs/cases/Xol_case_itm_.java +++ b/400_xowa/src/gplx/xowa/langs/cases/Xol_case_itm_.java @@ -13,3 +13,135 @@ 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.langs.cases; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.xowa.langs.parsers.*; +public class Xol_case_itm_ { + public static final byte Tid_both = 0, Tid_upper = 1, Tid_lower = 2; + public static Xol_case_itm new_(int tid, String src_str, String trg_str) {return new_((byte)tid, Bry_.new_u8(src_str), Bry_.new_u8(trg_str));} + public static Xol_case_itm new_(byte tid, byte[] src, byte[] trg) { + if (src.length == 1 && trg.length == 1) + return new Xol_case_itm_byt(tid, src[0], trg[0]); + else + return new Xol_case_itm_bry(tid, src, trg); + } + public static Xol_case_itm[] parse_xo_(byte[] src) { + List_adp list = List_adp_.New(); + int src_len = src.length, src_pos = 0, fld_bgn = 0, fld_idx = 0; + byte cur_cmd = Byte_.Zero; + byte[] cur_lhs = null; + Xol_csv_parser csv_parser = Xol_csv_parser.Instance; + while (true) { + boolean last = src_pos == src_len; + byte b = last ? Byte_ascii.Nl : src[src_pos]; + switch (b) { + case Byte_ascii.Pipe: + switch (fld_idx) { + case 0: + boolean fail = true; + if (src_pos - fld_bgn == 1) { + byte cmd_byte = src[src_pos - 1]; + cur_cmd = Byte_.Zero; + switch (cmd_byte) { + case Byte_ascii.Num_0: cur_cmd = Xol_case_itm_.Tid_both; fail = false; break; + case Byte_ascii.Num_1: cur_cmd = Xol_case_itm_.Tid_upper; fail = false; break; + case Byte_ascii.Num_2: cur_cmd = Xol_case_itm_.Tid_lower; fail = false; break; + } + } + if (fail) throw Err_.new_wo_type("cmd is invalid", "cmd", String_.new_u8(src, fld_bgn, src_pos)); + break; + case 1: cur_lhs = csv_parser.Load(src, fld_bgn, src_pos); break; + } + ++fld_idx; + fld_bgn = src_pos + 1; + break; + case Byte_ascii.Nl: + if (!(fld_idx == 0 && fld_bgn == src_pos)) { + byte[] cur_rhs = csv_parser.Load(src, fld_bgn, src_pos); + Xol_case_itm itm = Xol_case_itm_.new_(cur_cmd, cur_lhs, cur_rhs); + list.Add(itm); + } + cur_cmd = Byte_.Zero; + cur_lhs = null; + fld_idx = 0; + fld_bgn = src_pos + 1; + break; + } + if (last) break; + ++src_pos; + } + return (Xol_case_itm[])list.To_ary(Xol_case_itm.class); + } + public static Xol_case_itm[] parse_mw_(byte[] raw) { + Ordered_hash hash = Ordered_hash_.New_bry(); + int pos = 0; + pos = parse_mw_grp(hash, raw, Bool_.Y, pos); + pos = parse_mw_grp(hash, raw, Bool_.N, pos); + return (Xol_case_itm[])hash.To_ary(Xol_case_itm.class); + } + private static int parse_mw_grp(Ordered_hash hash, byte[] raw, boolean section_is_upper, int find_bgn) { + byte[] find = section_is_upper ? parse_mw_upper : parse_mw_lower; + int raw_len = raw.length; + int pos = Bry_find_.Find_fwd(raw, find, find_bgn); if (pos == Bry_find_.Not_found) throw Err_.new_wo_type("could not find section name", "name", String_.new_u8(find)); + pos = Bry_find_.Find_fwd(raw, Byte_ascii.Curly_bgn, pos, raw_len); if (pos == Bry_find_.Not_found) throw Err_.new_wo_type("could not find '{' after section name", "name", String_.new_u8(find)); + int itm_bgn = 0; + boolean quote_off = true, itm_is_first = true; + byte[] cur_lhs = Bry_.Empty; + boolean loop = true; + while (loop) { + if (pos >= raw_len) break; + byte b = raw[pos]; + switch (b) { + case Byte_ascii.Quote: + if (quote_off) { + itm_bgn = pos + 1; + quote_off = false; + } + else { + if (itm_is_first) { + cur_lhs = Bry_.Mid(raw, itm_bgn, pos); + itm_is_first = false; + } + else { + byte[] cur_rhs = Bry_.Mid(raw, itm_bgn, pos); + byte[] upper = null, lower = null; byte tid = Byte_.Zero, rev_tid = Byte_.Zero; + if (section_is_upper) { + upper = cur_rhs; + lower = cur_lhs; + tid = Xol_case_itm_.Tid_upper; + rev_tid = Xol_case_itm_.Tid_lower; + } + else { + upper = cur_lhs; + lower = cur_rhs; + tid = Xol_case_itm_.Tid_lower; + rev_tid = Xol_case_itm_.Tid_upper; + } + Xol_case_itm_bry itm = (Xol_case_itm_bry)hash.Get_by(upper); + if (itm == null) { + itm = new Xol_case_itm_bry(tid, upper, lower); + hash.Add(upper, itm); + } + else { + if (itm.Tid() == rev_tid && Bry_.Eq(itm.Src_ary(), upper) && Bry_.Eq(itm.Trg_ary(), lower)) + itm.Tid_(Xol_case_itm_.Tid_both); + else { + itm = new Xol_case_itm_bry(tid, cur_lhs, cur_rhs); + byte[] add_key = Bry_.Add(section_is_upper ? Bry_upper : Bry_lower, Bry_pipe, upper, Bry_pipe, lower); + hash.Add(add_key, itm); + } + } + itm_is_first = true; + } + quote_off = true; + } + break; + case Byte_ascii.Curly_end: + loop = false; + break; + } + ++pos; + } + return pos; + } private static final byte[] parse_mw_upper= Bry_.new_a7("wikiUpperChars"), parse_mw_lower= Bry_.new_a7("wikiLowerChars"), Bry_upper = Bry_.new_a7("upper"), Bry_lower = Bry_.new_a7("lower"), Bry_pipe = Bry_.new_a7("|"); + static final String GRP_KEY = "xowa.langs.case_parser"; +} diff --git a/400_xowa/src/gplx/xowa/langs/cases/Xol_case_mgr.java b/400_xowa/src/gplx/xowa/langs/cases/Xol_case_mgr.java index a27517de8..e2111222f 100644 --- a/400_xowa/src/gplx/xowa/langs/cases/Xol_case_mgr.java +++ b/400_xowa/src/gplx/xowa/langs/cases/Xol_case_mgr.java @@ -13,3 +13,127 @@ 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.langs.cases; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.btries.*; import gplx.core.intls.*; +public class Xol_case_mgr implements Gfo_invk, Gfo_case_mgr { + private final Btrie_fast_mgr upper_trie = Btrie_fast_mgr.cs(), lower_trie = Btrie_fast_mgr.cs(); private Xol_case_itm[] itms; + public Xol_case_mgr(byte tid) {this.tid = tid;} + public byte Tid() {return tid;} private byte tid; + public Gfo_case_itm Get_or_null(byte bgn_byte, byte[] src, int bgn, int end) { + Object rv = lower_trie.Match_bgn_w_byte(bgn_byte, src, bgn, end); + return rv == null + ? (Gfo_case_itm)upper_trie.Match_bgn_w_byte(bgn_byte, src, bgn, end) + : (Gfo_case_itm)rv; + } + public void Clear() {upper_trie.Clear(); lower_trie.Clear();} + public boolean Match_any_exists(byte b, byte[] src, int bgn_pos, int end_pos) { + return upper_trie.Match_bgn_w_byte(b, src, bgn_pos, end_pos) != null + || lower_trie.Match_bgn_w_byte(b, src, bgn_pos, end_pos) != null + ; + } + public Object Match_upper(byte b, byte[] src, int bgn_pos, int end_pos) {return upper_trie.Match_bgn_w_byte(b, src, bgn_pos, end_pos);} + public void Add_bulk(byte[] raw) {Add_bulk(Xol_case_itm_.parse_xo_(raw));} + public Xol_case_mgr Add_bulk(Xol_case_itm[] ary) { + itms = ary; + int itms_len = itms.length; + for (int i = 0; i < itms_len; i++) { + Xol_case_itm itm = itms[i]; + switch (itm.Tid()) { + case Xol_case_itm_.Tid_both: + upper_trie.Add(itm.Src_ary(), itm); + lower_trie.Add(itm.Trg_ary(), itm); + break; + case Xol_case_itm_.Tid_upper: + upper_trie.Add(itm.Src_ary(), itm); + break; + case Xol_case_itm_.Tid_lower: + lower_trie.Add(itm.Src_ary(), itm); + break; + } + } + return this; + } + public byte[] Case_reuse_upper(byte[] src, int bgn, int end) {return Case_reuse(Bool_.Y, src, bgn, end);} + public byte[] Case_reuse_lower(byte[] src, int bgn, int end) {return Case_reuse(Bool_.N, src, bgn, end);} + public byte[] Case_reuse(boolean upper, byte[] src, int bgn, int end) { + Btrie_fast_mgr trie = upper ? upper_trie : lower_trie; + Btrie_rv trv = new Btrie_rv(); // TS.MEM: DATE:2016-07-12 + int pos = bgn; + while (true) { + if (pos >= end) break; + byte b = src[pos]; + int b_len = gplx.core.intls.Utf8_.Len_of_char_by_1st_byte(b); + + Object o = trie.Match_at_w_b0(trv, b, src, pos, end); // NOTE: used to be (b, src, bgn, end) which would never case correctly; DATE:2013-12-25; TS: DATE:2016-07-06 + if (o != null && pos < end) { // pos < end used for casing 1st letter only; upper_1st will pass end of 1 + Xol_case_itm itm = (Xol_case_itm)o; + if (upper) + itm.Case_reuse_upper(src, pos, b_len); + else + itm.Case_reuse_lower(src, pos, b_len); + } + else {} // noop + pos += b_len; + } + return src; + } + public byte[] Case_reuse_1st_upper(byte[] src) { // NOTE: optimized version called by Frame_ttl; DATE:2014-06-21 + int src_len = src.length; + if (src_len == 0) return src; // empty bry + byte b = src[0]; + int b_len = gplx.core.intls.Utf8_.Len_of_char_by_1st_byte(b); + + Btrie_rv trv = new Btrie_rv(); // TS.MEM: DATE:2016-07-12 + Object o = upper_trie.Match_at_w_b0(trv, b, src, 0, b_len); + if (o == null) return src; // 1st letter is not a lower case char (either num, symbol, or upper) + Xol_case_itm itm = (Xol_case_itm)o; + Bry_bfr tmp_bfr = Bry_bfr_.New(); // TS.MEM: DATE:2016-07-12 + itm.Case_build_upper(tmp_bfr); + tmp_bfr.Add_mid(src, trv.Pos(), src_len); + return tmp_bfr.To_bry_and_clear(); + } + public byte[] Case_build_upper(byte[] src) {return Case_build_upper(src, 0, src.length);} + public byte[] Case_build_upper(byte[] src, int bgn, int end) {return Case_build(Bool_.Y, src, bgn, end);} + public byte[] Case_build_lower(byte[] src) {return Case_build_lower(src, 0, src.length);} + public byte[] Case_build_lower(byte[] src, int bgn, int end) {return Case_build(Bool_.N, src, bgn, end);} + public byte[] Case_build(boolean upper, byte[] src, int bgn, int end) { + Btrie_fast_mgr trie = upper ? upper_trie : lower_trie; + Btrie_rv trv = new Btrie_rv(); // TS.MEM: DATE:2016-07-12 + Bry_bfr tmp_bfr = Bry_bfr_.New(); // TS.MEM: DATE:2016-07-12 + int pos = bgn; + while (true) { + if (pos >= end) break; + byte b = src[pos]; + int b_len = gplx.core.intls.Utf8_.Len_of_char_by_1st_byte(b); + + Object o = trie.Match_at_w_b0(trv, b, src, pos, end); // NOTE: used to be (b, src, bgn, end) which would never case correctly; DATE:2013-12-25; + if (o != null && pos < end) { // pos < end used for casing 1st letter only; upper_1st will pass end of 1 + Xol_case_itm itm = (Xol_case_itm)o; + if (upper) + itm.Case_build_upper(tmp_bfr); + else + itm.Case_build_lower(tmp_bfr); + } + else { + tmp_bfr.Add_mid(src, pos, pos + b_len); + } + pos += b_len; + } + return tmp_bfr.To_bry_and_clear(); + } + public byte[] Case_build_1st_upper(Bry_bfr bfr, byte[] src, int bgn, int end) {return Case_build_1st(bfr, Bool_.Y, src, bgn, end);} + public byte[] Case_build_1st_lower(Bry_bfr bfr, byte[] src, int bgn, int end) {return Case_build_1st(bfr, Bool_.N, src, bgn, end);} + public byte[] Case_build_1st(Bry_bfr bfr, boolean upper, byte[] src, int bgn, int end) { + if (bgn == end) return Bry_.Empty; // upper "" -> "" + int b_len = gplx.core.intls.Utf8_.Len_of_char_by_1st_byte(src[bgn]); + bfr.Add(Case_build(upper, src, bgn, bgn + b_len)); + bfr.Add_mid(src, bgn + b_len, end); + return bfr.To_bry_and_clear(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_add_bulk)) Add_bulk(m.ReadBry("v")); + else if (ctx.Match(k, Invk_clear)) throw Err_.new_unimplemented(); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_clear = "clear", Invk_add_bulk = "add_bulk"; +} diff --git a/400_xowa/src/gplx/xowa/langs/cases/Xol_case_mgr_.java b/400_xowa/src/gplx/xowa/langs/cases/Xol_case_mgr_.java index a27517de8..596bfd9c9 100644 --- a/400_xowa/src/gplx/xowa/langs/cases/Xol_case_mgr_.java +++ b/400_xowa/src/gplx/xowa/langs/cases/Xol_case_mgr_.java @@ -13,3 +13,1109 @@ 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.langs.cases; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.intls.*; +public class Xol_case_mgr_ { + @gplx.Internal protected static Xol_case_mgr new_() {return new Xol_case_mgr(Gfo_case_mgr_.Tid_custom);} + public static Xol_case_mgr A7() {if (mgr_a7 == null) mgr_a7 = New__a7(); return mgr_a7;} private static Xol_case_mgr mgr_a7; + public static Xol_case_mgr U8() {if (mgr_u8 == null) mgr_u8 = New__u8(); return mgr_u8;} private static Xol_case_mgr mgr_u8; + private static Xol_case_mgr New__a7() { + Xol_case_mgr rv = new Xol_case_mgr(Gfo_case_mgr_.Tid_a7); + Xol_case_itm[] itms = new Xol_case_itm[] +{ Xol_case_itm_.new_(0, "a", "A") +, Xol_case_itm_.new_(0, "b", "B") +, Xol_case_itm_.new_(0, "c", "C") +, Xol_case_itm_.new_(0, "d", "D") +, Xol_case_itm_.new_(0, "e", "E") +, Xol_case_itm_.new_(0, "f", "F") +, Xol_case_itm_.new_(0, "g", "G") +, Xol_case_itm_.new_(0, "h", "H") +, Xol_case_itm_.new_(0, "i", "I") +, Xol_case_itm_.new_(0, "j", "J") +, Xol_case_itm_.new_(0, "k", "K") +, Xol_case_itm_.new_(0, "l", "L") +, Xol_case_itm_.new_(0, "m", "M") +, Xol_case_itm_.new_(0, "n", "N") +, Xol_case_itm_.new_(0, "o", "O") +, Xol_case_itm_.new_(0, "p", "P") +, Xol_case_itm_.new_(0, "q", "Q") +, Xol_case_itm_.new_(0, "r", "R") +, Xol_case_itm_.new_(0, "s", "S") +, Xol_case_itm_.new_(0, "t", "T") +, Xol_case_itm_.new_(0, "u", "U") +, Xol_case_itm_.new_(0, "v", "V") +, Xol_case_itm_.new_(0, "w", "W") +, Xol_case_itm_.new_(0, "x", "X") +, Xol_case_itm_.new_(0, "y", "Y") +, Xol_case_itm_.new_(0, "z", "Z") +}; + rv.Add_bulk(itms); + return rv; + } + private static Xol_case_mgr New__u8() { + Xol_case_mgr rv = new Xol_case_mgr(Gfo_case_mgr_.Tid_u8); + Xol_case_itm[] itms = new Xol_case_itm[] +{ Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(97), Bry_.New_by_ints(65)) // a -> A -- LATIN CAPITAL LETTER A +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(98), Bry_.New_by_ints(66)) // b -> B -- LATIN CAPITAL LETTER B +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(99), Bry_.New_by_ints(67)) // c -> C -- LATIN CAPITAL LETTER C +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(100), Bry_.New_by_ints(68)) // d -> D -- LATIN CAPITAL LETTER D +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(101), Bry_.New_by_ints(69)) // e -> E -- LATIN CAPITAL LETTER E +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(102), Bry_.New_by_ints(70)) // f -> F -- LATIN CAPITAL LETTER F +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(103), Bry_.New_by_ints(71)) // g -> G -- LATIN CAPITAL LETTER G +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(104), Bry_.New_by_ints(72)) // h -> H -- LATIN CAPITAL LETTER H +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(105), Bry_.New_by_ints(73)) // i -> I -- LATIN CAPITAL LETTER I +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(106), Bry_.New_by_ints(74)) // j -> J -- LATIN CAPITAL LETTER J +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(107), Bry_.New_by_ints(75)) // k -> K -- LATIN CAPITAL LETTER K +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(108), Bry_.New_by_ints(76)) // l -> L -- LATIN CAPITAL LETTER L +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(109), Bry_.New_by_ints(77)) // m -> M -- LATIN CAPITAL LETTER M +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(110), Bry_.New_by_ints(78)) // n -> N -- LATIN CAPITAL LETTER N +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(111), Bry_.New_by_ints(79)) // o -> O -- LATIN CAPITAL LETTER O +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(112), Bry_.New_by_ints(80)) // p -> P -- LATIN CAPITAL LETTER P +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(113), Bry_.New_by_ints(81)) // q -> Q -- LATIN CAPITAL LETTER Q +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(114), Bry_.New_by_ints(82)) // r -> R -- LATIN CAPITAL LETTER R +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(115), Bry_.New_by_ints(83)) // s -> S -- LATIN CAPITAL LETTER S +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(116), Bry_.New_by_ints(84)) // t -> T -- LATIN CAPITAL LETTER T +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(117), Bry_.New_by_ints(85)) // u -> U -- LATIN CAPITAL LETTER U +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(118), Bry_.New_by_ints(86)) // v -> V -- LATIN CAPITAL LETTER V +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(119), Bry_.New_by_ints(87)) // w -> W -- LATIN CAPITAL LETTER W +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(120), Bry_.New_by_ints(88)) // x -> X -- LATIN CAPITAL LETTER X +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(121), Bry_.New_by_ints(89)) // y -> Y -- LATIN CAPITAL LETTER Y +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(122), Bry_.New_by_ints(90)) // z -> Z -- LATIN CAPITAL LETTER Z +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,160), Bry_.New_by_ints(195,128)) // à -> À -- LATIN CAPITAL LETTER A GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,161), Bry_.New_by_ints(195,129)) // á -> Á -- LATIN CAPITAL LETTER A ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,162), Bry_.New_by_ints(195,130)) // â ->  -- LATIN CAPITAL LETTER A CIRCUMFLEX +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,163), Bry_.New_by_ints(195,131)) // ã -> à -- LATIN CAPITAL LETTER A TILDE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,164), Bry_.New_by_ints(195,132)) // ä -> Ä -- LATIN CAPITAL LETTER A DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,165), Bry_.New_by_ints(195,133)) // å -> Å -- LATIN CAPITAL LETTER A RING +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,166), Bry_.New_by_ints(195,134)) // æ -> Æ -- LATIN CAPITAL LETTER A E +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,167), Bry_.New_by_ints(195,135)) // ç -> Ç -- LATIN CAPITAL LETTER C CEDILLA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,168), Bry_.New_by_ints(195,136)) // è -> È -- LATIN CAPITAL LETTER E GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,169), Bry_.New_by_ints(195,137)) // é -> É -- LATIN CAPITAL LETTER E ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,170), Bry_.New_by_ints(195,138)) // ê -> Ê -- LATIN CAPITAL LETTER E CIRCUMFLEX +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,171), Bry_.New_by_ints(195,139)) // ë -> Ë -- LATIN CAPITAL LETTER E DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,172), Bry_.New_by_ints(195,140)) // ì -> Ì -- LATIN CAPITAL LETTER I GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,173), Bry_.New_by_ints(195,141)) // í -> Í -- LATIN CAPITAL LETTER I ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,174), Bry_.New_by_ints(195,142)) // î -> Î -- LATIN CAPITAL LETTER I CIRCUMFLEX +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,175), Bry_.New_by_ints(195,143)) // ï -> Ï -- LATIN CAPITAL LETTER I DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,176), Bry_.New_by_ints(195,144)) // ð -> Ð -- LATIN CAPITAL LETTER ETH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,177), Bry_.New_by_ints(195,145)) // ñ -> Ñ -- LATIN CAPITAL LETTER N TILDE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,178), Bry_.New_by_ints(195,146)) // ò -> Ò -- LATIN CAPITAL LETTER O GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,179), Bry_.New_by_ints(195,147)) // ó -> Ó -- LATIN CAPITAL LETTER O ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,180), Bry_.New_by_ints(195,148)) // ô -> Ô -- LATIN CAPITAL LETTER O CIRCUMFLEX +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,181), Bry_.New_by_ints(195,149)) // õ -> Õ -- LATIN CAPITAL LETTER O TILDE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,182), Bry_.New_by_ints(195,150)) // ö -> Ö -- LATIN CAPITAL LETTER O DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,184), Bry_.New_by_ints(195,152)) // ø -> Ø -- LATIN CAPITAL LETTER O SLASH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,185), Bry_.New_by_ints(195,153)) // ù -> Ù -- LATIN CAPITAL LETTER U GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,186), Bry_.New_by_ints(195,154)) // ú -> Ú -- LATIN CAPITAL LETTER U ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,187), Bry_.New_by_ints(195,155)) // û -> Û -- LATIN CAPITAL LETTER U CIRCUMFLEX +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,188), Bry_.New_by_ints(195,156)) // ü -> Ü -- LATIN CAPITAL LETTER U DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,189), Bry_.New_by_ints(195,157)) // ý -> Ý -- LATIN CAPITAL LETTER Y ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,190), Bry_.New_by_ints(195,158)) // þ -> Þ -- LATIN CAPITAL LETTER THORN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(195,191), Bry_.New_by_ints(197,184)) // ÿ -> Ÿ -- LATIN SMALL LETTER Y DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,129), Bry_.New_by_ints(196,128)) // ā -> Ā -- LATIN CAPITAL LETTER A MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,131), Bry_.New_by_ints(196,130)) // ă -> Ă -- LATIN CAPITAL LETTER A BREVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,133), Bry_.New_by_ints(196,132)) // ą -> Ą -- LATIN CAPITAL LETTER A OGONEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,135), Bry_.New_by_ints(196,134)) // ć -> Ć -- LATIN CAPITAL LETTER C ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,137), Bry_.New_by_ints(196,136)) // ĉ -> Ĉ -- LATIN CAPITAL LETTER C CIRCUMFLEX +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,139), Bry_.New_by_ints(196,138)) // ċ -> Ċ -- LATIN CAPITAL LETTER C DOT +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,141), Bry_.New_by_ints(196,140)) // č -> Č -- LATIN CAPITAL LETTER C HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,143), Bry_.New_by_ints(196,142)) // ď -> Ď -- LATIN CAPITAL LETTER D HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,145), Bry_.New_by_ints(196,144)) // đ -> Đ -- LATIN CAPITAL LETTER D BAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,147), Bry_.New_by_ints(196,146)) // ē -> Ē -- LATIN CAPITAL LETTER E MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,149), Bry_.New_by_ints(196,148)) // ĕ -> Ĕ -- LATIN CAPITAL LETTER E BREVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,151), Bry_.New_by_ints(196,150)) // ė -> Ė -- LATIN CAPITAL LETTER E DOT +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,153), Bry_.New_by_ints(196,152)) // ę -> Ę -- LATIN CAPITAL LETTER E OGONEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,155), Bry_.New_by_ints(196,154)) // ě -> Ě -- LATIN CAPITAL LETTER E HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,157), Bry_.New_by_ints(196,156)) // ĝ -> Ĝ -- LATIN CAPITAL LETTER G CIRCUMFLEX +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,159), Bry_.New_by_ints(196,158)) // ğ -> Ğ -- LATIN CAPITAL LETTER G BREVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,161), Bry_.New_by_ints(196,160)) // ġ -> Ġ -- LATIN CAPITAL LETTER G DOT +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,163), Bry_.New_by_ints(196,162)) // ģ -> Ģ -- LATIN CAPITAL LETTER G CEDILLA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,165), Bry_.New_by_ints(196,164)) // ĥ -> Ĥ -- LATIN CAPITAL LETTER H CIRCUMFLEX +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,167), Bry_.New_by_ints(196,166)) // ħ -> Ħ -- LATIN CAPITAL LETTER H BAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,169), Bry_.New_by_ints(196,168)) // ĩ -> Ĩ -- LATIN CAPITAL LETTER I TILDE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,171), Bry_.New_by_ints(196,170)) // ī -> Ī -- LATIN CAPITAL LETTER I MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,173), Bry_.New_by_ints(196,172)) // ĭ -> Ĭ -- LATIN CAPITAL LETTER I BREVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,175), Bry_.New_by_ints(196,174)) // į -> Į -- LATIN CAPITAL LETTER I OGONEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,179), Bry_.New_by_ints(196,178)) // ij -> IJ -- LATIN CAPITAL LETTER I J +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,181), Bry_.New_by_ints(196,180)) // ĵ -> Ĵ -- LATIN CAPITAL LETTER J CIRCUMFLEX +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,183), Bry_.New_by_ints(196,182)) // ķ -> Ķ -- LATIN CAPITAL LETTER K CEDILLA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,186), Bry_.New_by_ints(196,185)) // ĺ -> Ĺ -- LATIN CAPITAL LETTER L ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,188), Bry_.New_by_ints(196,187)) // ļ -> Ļ -- LATIN CAPITAL LETTER L CEDILLA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(196,190), Bry_.New_by_ints(196,189)) // ľ -> Ľ -- LATIN CAPITAL LETTER L HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,128), Bry_.New_by_ints(196,191)) // ŀ -> Ŀ -- LATIN CAPITAL LETTER L WITH MIDDLE DOT +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,130), Bry_.New_by_ints(197,129)) // ł -> Ł -- LATIN CAPITAL LETTER L SLASH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,132), Bry_.New_by_ints(197,131)) // ń -> Ń -- LATIN CAPITAL LETTER N ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,134), Bry_.New_by_ints(197,133)) // ņ -> Ņ -- LATIN CAPITAL LETTER N CEDILLA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,136), Bry_.New_by_ints(197,135)) // ň -> Ň -- LATIN CAPITAL LETTER N HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,139), Bry_.New_by_ints(197,138)) // ŋ -> Ŋ -- LATIN CAPITAL LETTER ENG +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,141), Bry_.New_by_ints(197,140)) // ō -> Ō -- LATIN CAPITAL LETTER O MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,143), Bry_.New_by_ints(197,142)) // ŏ -> Ŏ -- LATIN CAPITAL LETTER O BREVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,145), Bry_.New_by_ints(197,144)) // ő -> Ő -- LATIN CAPITAL LETTER O DOUBLE ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,147), Bry_.New_by_ints(197,146)) // œ -> Œ -- LATIN CAPITAL LETTER O E +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,149), Bry_.New_by_ints(197,148)) // ŕ -> Ŕ -- LATIN CAPITAL LETTER R ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,151), Bry_.New_by_ints(197,150)) // ŗ -> Ŗ -- LATIN CAPITAL LETTER R CEDILLA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,153), Bry_.New_by_ints(197,152)) // ř -> Ř -- LATIN CAPITAL LETTER R HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,155), Bry_.New_by_ints(197,154)) // ś -> Ś -- LATIN CAPITAL LETTER S ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,157), Bry_.New_by_ints(197,156)) // ŝ -> Ŝ -- LATIN CAPITAL LETTER S CIRCUMFLEX +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,159), Bry_.New_by_ints(197,158)) // ş -> Ş -- LATIN CAPITAL LETTER S CEDILLA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,161), Bry_.New_by_ints(197,160)) // š -> Š -- LATIN CAPITAL LETTER S HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,163), Bry_.New_by_ints(197,162)) // ţ -> Ţ -- LATIN CAPITAL LETTER T CEDILLA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,165), Bry_.New_by_ints(197,164)) // ť -> Ť -- LATIN CAPITAL LETTER T HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,167), Bry_.New_by_ints(197,166)) // ŧ -> Ŧ -- LATIN CAPITAL LETTER T BAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,169), Bry_.New_by_ints(197,168)) // ũ -> Ũ -- LATIN CAPITAL LETTER U TILDE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,171), Bry_.New_by_ints(197,170)) // ū -> Ū -- LATIN CAPITAL LETTER U MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,173), Bry_.New_by_ints(197,172)) // ŭ -> Ŭ -- LATIN CAPITAL LETTER U BREVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,175), Bry_.New_by_ints(197,174)) // ů -> Ů -- LATIN CAPITAL LETTER U RING +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,177), Bry_.New_by_ints(197,176)) // ű -> Ű -- LATIN CAPITAL LETTER U DOUBLE ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,179), Bry_.New_by_ints(197,178)) // ų -> Ų -- LATIN CAPITAL LETTER U OGONEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,181), Bry_.New_by_ints(197,180)) // ŵ -> Ŵ -- LATIN CAPITAL LETTER W CIRCUMFLEX +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,183), Bry_.New_by_ints(197,182)) // ŷ -> Ŷ -- LATIN CAPITAL LETTER Y CIRCUMFLEX +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,186), Bry_.New_by_ints(197,185)) // ź -> Ź -- LATIN CAPITAL LETTER Z ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,188), Bry_.New_by_ints(197,187)) // ż -> Ż -- LATIN CAPITAL LETTER Z DOT +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(197,190), Bry_.New_by_ints(197,189)) // ž -> Ž -- LATIN CAPITAL LETTER Z HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,128), Bry_.New_by_ints(201,131)) // ƀ -> Ƀ -- LATIN SMALL LETTER B BAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,147), Bry_.New_by_ints(198,129)) // ɓ -> Ɓ -- LATIN CAPITAL LETTER B HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,131), Bry_.New_by_ints(198,130)) // ƃ -> Ƃ -- LATIN CAPITAL LETTER B TOPBAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,133), Bry_.New_by_ints(198,132)) // ƅ -> Ƅ -- LATIN CAPITAL LETTER TONE SIX +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,148), Bry_.New_by_ints(198,134)) // ɔ -> Ɔ -- LATIN CAPITAL LETTER OPEN O +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,136), Bry_.New_by_ints(198,135)) // ƈ -> Ƈ -- LATIN CAPITAL LETTER C HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,150), Bry_.New_by_ints(198,137)) // ɖ -> Ɖ -- LATIN CAPITAL LETTER AFRICAN D +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,151), Bry_.New_by_ints(198,138)) // ɗ -> Ɗ -- LATIN CAPITAL LETTER D HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,140), Bry_.New_by_ints(198,139)) // ƌ -> Ƌ -- LATIN CAPITAL LETTER D TOPBAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,157), Bry_.New_by_ints(198,142)) // ǝ -> Ǝ -- LATIN CAPITAL LETTER TURNED E +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,153), Bry_.New_by_ints(198,143)) // ə -> Ə -- LATIN CAPITAL LETTER SCHWA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,155), Bry_.New_by_ints(198,144)) // ɛ -> Ɛ -- LATIN CAPITAL LETTER EPSILON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,146), Bry_.New_by_ints(198,145)) // ƒ -> Ƒ -- LATIN CAPITAL LETTER F HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,160), Bry_.New_by_ints(198,147)) // ɠ -> Ɠ -- LATIN CAPITAL LETTER G HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,163), Bry_.New_by_ints(198,148)) // ɣ -> Ɣ -- LATIN CAPITAL LETTER GAMMA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,149), Bry_.New_by_ints(199,182)) // ƕ -> Ƕ -- LATIN SMALL LETTER H V +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,169), Bry_.New_by_ints(198,150)) // ɩ -> Ɩ -- LATIN CAPITAL LETTER IOTA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,168), Bry_.New_by_ints(198,151)) // ɨ -> Ɨ -- LATIN CAPITAL LETTER BARRED I +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,153), Bry_.New_by_ints(198,152)) // ƙ -> Ƙ -- LATIN CAPITAL LETTER K HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,154), Bry_.New_by_ints(200,189)) // ƚ -> Ƚ -- LATIN SMALL LETTER BARRED L +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,175), Bry_.New_by_ints(198,156)) // ɯ -> Ɯ -- LATIN CAPITAL LETTER TURNED M +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,178), Bry_.New_by_ints(198,157)) // ɲ -> Ɲ -- LATIN CAPITAL LETTER N HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,158), Bry_.New_by_ints(200,160)) // ƞ -> Ƞ -- LATIN SMALL LETTER N WITH LONG RIGHT LEG +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,181), Bry_.New_by_ints(198,159)) // ɵ -> Ɵ -- LATIN CAPITAL LETTER BARRED O +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,161), Bry_.New_by_ints(198,160)) // ơ -> Ơ -- LATIN CAPITAL LETTER O HORN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,163), Bry_.New_by_ints(198,162)) // ƣ -> Ƣ -- LATIN CAPITAL LETTER O I +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,165), Bry_.New_by_ints(198,164)) // ƥ -> Ƥ -- LATIN CAPITAL LETTER P HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(202,128), Bry_.New_by_ints(198,166)) // ʀ -> Ʀ -- LATIN LETTER Y R +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,168), Bry_.New_by_ints(198,167)) // ƨ -> Ƨ -- LATIN CAPITAL LETTER TONE TWO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(202,131), Bry_.New_by_ints(198,169)) // ʃ -> Ʃ -- LATIN CAPITAL LETTER ESH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,173), Bry_.New_by_ints(198,172)) // ƭ -> Ƭ -- LATIN CAPITAL LETTER T HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(202,136), Bry_.New_by_ints(198,174)) // ʈ -> Ʈ -- LATIN CAPITAL LETTER T RETROFLEX HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,176), Bry_.New_by_ints(198,175)) // ư -> Ư -- LATIN CAPITAL LETTER U HORN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(202,138), Bry_.New_by_ints(198,177)) // ʊ -> Ʊ -- LATIN CAPITAL LETTER UPSILON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(202,139), Bry_.New_by_ints(198,178)) // ʋ -> Ʋ -- LATIN CAPITAL LETTER SCRIPT V +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,180), Bry_.New_by_ints(198,179)) // ƴ -> Ƴ -- LATIN CAPITAL LETTER Y HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,182), Bry_.New_by_ints(198,181)) // ƶ -> Ƶ -- LATIN CAPITAL LETTER Z BAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(202,146), Bry_.New_by_ints(198,183)) // ʒ -> Ʒ -- LATIN CAPITAL LETTER YOGH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,185), Bry_.New_by_ints(198,184)) // ƹ -> Ƹ -- LATIN CAPITAL LETTER REVERSED YOGH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,189), Bry_.New_by_ints(198,188)) // ƽ -> Ƽ -- LATIN CAPITAL LETTER TONE FIVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(198,191), Bry_.New_by_ints(199,183)) // ƿ -> Ƿ -- LATIN LETTER WYNN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,134), Bry_.New_by_ints(199,132)) // dž -> DŽ -- LATIN CAPITAL LETTER D Z HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,137), Bry_.New_by_ints(199,135)) // lj -> LJ -- LATIN CAPITAL LETTER L J +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,140), Bry_.New_by_ints(199,138)) // nj -> NJ -- LATIN CAPITAL LETTER N J +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,142), Bry_.New_by_ints(199,141)) // ǎ -> Ǎ -- LATIN CAPITAL LETTER A HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,144), Bry_.New_by_ints(199,143)) // ǐ -> Ǐ -- LATIN CAPITAL LETTER I HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,146), Bry_.New_by_ints(199,145)) // ǒ -> Ǒ -- LATIN CAPITAL LETTER O HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,148), Bry_.New_by_ints(199,147)) // ǔ -> Ǔ -- LATIN CAPITAL LETTER U HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,150), Bry_.New_by_ints(199,149)) // ǖ -> Ǖ -- LATIN CAPITAL LETTER U DIAERESIS MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,152), Bry_.New_by_ints(199,151)) // ǘ -> Ǘ -- LATIN CAPITAL LETTER U DIAERESIS ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,154), Bry_.New_by_ints(199,153)) // ǚ -> Ǚ -- LATIN CAPITAL LETTER U DIAERESIS HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,156), Bry_.New_by_ints(199,155)) // ǜ -> Ǜ -- LATIN CAPITAL LETTER U DIAERESIS GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,159), Bry_.New_by_ints(199,158)) // ǟ -> Ǟ -- LATIN CAPITAL LETTER A DIAERESIS MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,161), Bry_.New_by_ints(199,160)) // ǡ -> Ǡ -- LATIN CAPITAL LETTER A DOT MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,163), Bry_.New_by_ints(199,162)) // ǣ -> Ǣ -- LATIN CAPITAL LETTER A E MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,165), Bry_.New_by_ints(199,164)) // ǥ -> Ǥ -- LATIN CAPITAL LETTER G BAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,167), Bry_.New_by_ints(199,166)) // ǧ -> Ǧ -- LATIN CAPITAL LETTER G HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,169), Bry_.New_by_ints(199,168)) // ǩ -> Ǩ -- LATIN CAPITAL LETTER K HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,171), Bry_.New_by_ints(199,170)) // ǫ -> Ǫ -- LATIN CAPITAL LETTER O OGONEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,173), Bry_.New_by_ints(199,172)) // ǭ -> Ǭ -- LATIN CAPITAL LETTER O OGONEK MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,175), Bry_.New_by_ints(199,174)) // ǯ -> Ǯ -- LATIN CAPITAL LETTER YOGH HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,179), Bry_.New_by_ints(199,177)) // dz -> DZ -- LATIN CAPITAL LETTER DZ +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,181), Bry_.New_by_ints(199,180)) // ǵ -> Ǵ -- LATIN CAPITAL LETTER G WITH ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,185), Bry_.New_by_ints(199,184)) // ǹ -> Ǹ -- LATIN CAPITAL LETTER N WITH GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,187), Bry_.New_by_ints(199,186)) // ǻ -> Ǻ -- LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,189), Bry_.New_by_ints(199,188)) // ǽ -> Ǽ -- LATIN CAPITAL LETTER AE WITH ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(199,191), Bry_.New_by_ints(199,190)) // ǿ -> Ǿ -- LATIN CAPITAL LETTER O WITH STROKE AND ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,129), Bry_.New_by_ints(200,128)) // ȁ -> Ȁ -- LATIN CAPITAL LETTER A WITH DOUBLE GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,131), Bry_.New_by_ints(200,130)) // ȃ -> Ȃ -- LATIN CAPITAL LETTER A WITH INVERTED BREVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,133), Bry_.New_by_ints(200,132)) // ȅ -> Ȅ -- LATIN CAPITAL LETTER E WITH DOUBLE GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,135), Bry_.New_by_ints(200,134)) // ȇ -> Ȇ -- LATIN CAPITAL LETTER E WITH INVERTED BREVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,137), Bry_.New_by_ints(200,136)) // ȉ -> Ȉ -- LATIN CAPITAL LETTER I WITH DOUBLE GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,139), Bry_.New_by_ints(200,138)) // ȋ -> Ȋ -- LATIN CAPITAL LETTER I WITH INVERTED BREVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,141), Bry_.New_by_ints(200,140)) // ȍ -> Ȍ -- LATIN CAPITAL LETTER O WITH DOUBLE GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,143), Bry_.New_by_ints(200,142)) // ȏ -> Ȏ -- LATIN CAPITAL LETTER O WITH INVERTED BREVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,145), Bry_.New_by_ints(200,144)) // ȑ -> Ȑ -- LATIN CAPITAL LETTER R WITH DOUBLE GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,147), Bry_.New_by_ints(200,146)) // ȓ -> Ȓ -- LATIN CAPITAL LETTER R WITH INVERTED BREVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,149), Bry_.New_by_ints(200,148)) // ȕ -> Ȕ -- LATIN CAPITAL LETTER U WITH DOUBLE GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,151), Bry_.New_by_ints(200,150)) // ȗ -> Ȗ -- LATIN CAPITAL LETTER U WITH INVERTED BREVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,153), Bry_.New_by_ints(200,152)) // ș -> Ș -- LATIN CAPITAL LETTER S WITH COMMA BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,155), Bry_.New_by_ints(200,154)) // ț -> Ț -- LATIN CAPITAL LETTER T WITH COMMA BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,157), Bry_.New_by_ints(200,156)) // ȝ -> Ȝ -- LATIN CAPITAL LETTER YOGH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,159), Bry_.New_by_ints(200,158)) // ȟ -> Ȟ -- LATIN CAPITAL LETTER H WITH CARON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,163), Bry_.New_by_ints(200,162)) // ȣ -> Ȣ -- LATIN CAPITAL LETTER OU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,165), Bry_.New_by_ints(200,164)) // ȥ -> Ȥ -- LATIN CAPITAL LETTER Z WITH HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,167), Bry_.New_by_ints(200,166)) // ȧ -> Ȧ -- LATIN CAPITAL LETTER A WITH DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,169), Bry_.New_by_ints(200,168)) // ȩ -> Ȩ -- LATIN CAPITAL LETTER E WITH CEDILLA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,171), Bry_.New_by_ints(200,170)) // ȫ -> Ȫ -- LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,173), Bry_.New_by_ints(200,172)) // ȭ -> Ȭ -- LATIN CAPITAL LETTER O WITH TILDE AND MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,175), Bry_.New_by_ints(200,174)) // ȯ -> Ȯ -- LATIN CAPITAL LETTER O WITH DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,177), Bry_.New_by_ints(200,176)) // ȱ -> Ȱ -- LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,179), Bry_.New_by_ints(200,178)) // ȳ -> Ȳ -- LATIN CAPITAL LETTER Y WITH MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,165), Bry_.New_by_ints(200,186)) // ⱥ -> Ⱥ -- LATIN CAPITAL LETTER A WITH STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,188), Bry_.New_by_ints(200,187)) // ȼ -> Ȼ -- LATIN CAPITAL LETTER C WITH STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,166), Bry_.New_by_ints(200,190)) // ⱦ -> Ⱦ -- LATIN CAPITAL LETTER T WITH DIAGONAL STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(200,191), Bry_.New_by_ints(226,177,190)) // ȿ -> Ȿ -- LATIN SMALL LETTER S WITH SWASH TAIL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,128), Bry_.New_by_ints(226,177,191)) // ɀ -> Ɀ -- LATIN SMALL LETTER Z WITH SWASH TAIL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,130), Bry_.New_by_ints(201,129)) // ɂ -> Ɂ -- LATIN CAPITAL LETTER GLOTTAL STOP +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(202,137), Bry_.New_by_ints(201,132)) // ʉ -> Ʉ -- LATIN CAPITAL LETTER U BAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(202,140), Bry_.New_by_ints(201,133)) // ʌ -> Ʌ -- LATIN CAPITAL LETTER TURNED V +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,135), Bry_.New_by_ints(201,134)) // ɇ -> Ɇ -- LATIN CAPITAL LETTER E WITH STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,137), Bry_.New_by_ints(201,136)) // ɉ -> Ɉ -- LATIN CAPITAL LETTER J WITH STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,139), Bry_.New_by_ints(201,138)) // ɋ -> Ɋ -- LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,141), Bry_.New_by_ints(201,140)) // ɍ -> Ɍ -- LATIN CAPITAL LETTER R WITH STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,143), Bry_.New_by_ints(201,142)) // ɏ -> Ɏ -- LATIN CAPITAL LETTER Y WITH STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,144), Bry_.New_by_ints(226,177,175)) // ɐ -> Ɐ -- LATIN SMALL LETTER TURNED A +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,145), Bry_.New_by_ints(226,177,173)) // ɑ -> Ɑ -- LATIN SMALL LETTER SCRIPT A +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,146), Bry_.New_by_ints(226,177,176)) // ɒ -> Ɒ -- LATIN SMALL LETTER TURNED SCRIPT A +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,165), Bry_.New_by_ints(234,158,141)) // ɥ -> Ɥ -- LATIN SMALL LETTER TURNED H +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,166), Bry_.New_by_ints(234,158,170)) // ɦ -> Ɦ -- LATIN SMALL LETTER H HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,171), Bry_.New_by_ints(226,177,162)) // ɫ -> Ɫ -- LATIN SMALL LETTER L WITH MIDDLE TILDE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,177), Bry_.New_by_ints(226,177,174)) // ɱ -> Ɱ -- LATIN SMALL LETTER M HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(201,189), Bry_.New_by_ints(226,177,164)) // ɽ -> Ɽ -- LATIN SMALL LETTER R HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(205,177), Bry_.New_by_ints(205,176)) // ͱ -> Ͱ -- GREEK CAPITAL LETTER HETA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(205,179), Bry_.New_by_ints(205,178)) // ͳ -> Ͳ -- GREEK CAPITAL LETTER ARCHAIC SAMPI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(205,183), Bry_.New_by_ints(205,182)) // ͷ -> Ͷ -- GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(205,187), Bry_.New_by_ints(207,189)) // ͻ -> Ͻ -- GREEK SMALL REVERSED LUNATE SIGMA SYMBOL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(205,188), Bry_.New_by_ints(207,190)) // ͼ -> Ͼ -- GREEK SMALL DOTTED LUNATE SIGMA SYMBOL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(205,189), Bry_.New_by_ints(207,191)) // ͽ -> Ͽ -- GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,172), Bry_.New_by_ints(206,134)) // ά -> Ά -- GREEK CAPITAL LETTER ALPHA TONOS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,173), Bry_.New_by_ints(206,136)) // έ -> Έ -- GREEK CAPITAL LETTER EPSILON TONOS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,174), Bry_.New_by_ints(206,137)) // ή -> Ή -- GREEK CAPITAL LETTER ETA TONOS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,175), Bry_.New_by_ints(206,138)) // ί -> Ί -- GREEK CAPITAL LETTER IOTA TONOS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,140), Bry_.New_by_ints(206,140)) // ό -> Ό -- GREEK CAPITAL LETTER OMICRON TONOS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,141), Bry_.New_by_ints(206,142)) // ύ -> Ύ -- GREEK CAPITAL LETTER UPSILON TONOS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,142), Bry_.New_by_ints(206,143)) // ώ -> Ώ -- GREEK CAPITAL LETTER OMEGA TONOS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,177), Bry_.New_by_ints(206,145)) // α -> Α -- GREEK CAPITAL LETTER ALPHA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,178), Bry_.New_by_ints(206,146)) // β -> Β -- GREEK CAPITAL LETTER BETA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,179), Bry_.New_by_ints(206,147)) // γ -> Γ -- GREEK CAPITAL LETTER GAMMA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,180), Bry_.New_by_ints(206,148)) // δ -> Δ -- GREEK CAPITAL LETTER DELTA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,181), Bry_.New_by_ints(206,149)) // ε -> Ε -- GREEK CAPITAL LETTER EPSILON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,182), Bry_.New_by_ints(206,150)) // ζ -> Ζ -- GREEK CAPITAL LETTER ZETA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,183), Bry_.New_by_ints(206,151)) // η -> Η -- GREEK CAPITAL LETTER ETA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,184), Bry_.New_by_ints(206,152)) // θ -> Θ -- GREEK CAPITAL LETTER THETA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,186), Bry_.New_by_ints(206,154)) // κ -> Κ -- GREEK CAPITAL LETTER KAPPA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,187), Bry_.New_by_ints(206,155)) // λ -> Λ -- GREEK CAPITAL LETTER LAMBDA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,189), Bry_.New_by_ints(206,157)) // ν -> Ν -- GREEK CAPITAL LETTER NU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,190), Bry_.New_by_ints(206,158)) // ξ -> Ξ -- GREEK CAPITAL LETTER XI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,191), Bry_.New_by_ints(206,159)) // ο -> Ο -- GREEK CAPITAL LETTER OMICRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,128), Bry_.New_by_ints(206,160)) // π -> Π -- GREEK CAPITAL LETTER PI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,129), Bry_.New_by_ints(206,161)) // ρ -> Ρ -- GREEK CAPITAL LETTER RHO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,131), Bry_.New_by_ints(206,163)) // σ -> Σ -- GREEK CAPITAL LETTER SIGMA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,132), Bry_.New_by_ints(206,164)) // τ -> Τ -- GREEK CAPITAL LETTER TAU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,133), Bry_.New_by_ints(206,165)) // υ -> Υ -- GREEK CAPITAL LETTER UPSILON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,134), Bry_.New_by_ints(206,166)) // φ -> Φ -- GREEK CAPITAL LETTER PHI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,135), Bry_.New_by_ints(206,167)) // χ -> Χ -- GREEK CAPITAL LETTER CHI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,136), Bry_.New_by_ints(206,168)) // ψ -> Ψ -- GREEK CAPITAL LETTER PSI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,137), Bry_.New_by_ints(206,169)) // ω -> Ω -- GREEK CAPITAL LETTER OMEGA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,138), Bry_.New_by_ints(206,170)) // ϊ -> Ϊ -- GREEK CAPITAL LETTER IOTA DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,139), Bry_.New_by_ints(206,171)) // ϋ -> Ϋ -- GREEK CAPITAL LETTER UPSILON DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,151), Bry_.New_by_ints(207,143)) // ϗ -> Ϗ -- GREEK CAPITAL KAI SYMBOL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,153), Bry_.New_by_ints(207,152)) // ϙ -> Ϙ -- GREEK LETTER ARCHAIC KOPPA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,155), Bry_.New_by_ints(207,154)) // ϛ -> Ϛ -- GREEK CAPITAL LETTER STIGMA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,157), Bry_.New_by_ints(207,156)) // ϝ -> Ϝ -- GREEK CAPITAL LETTER DIGAMMA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,159), Bry_.New_by_ints(207,158)) // ϟ -> Ϟ -- GREEK CAPITAL LETTER KOPPA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,161), Bry_.New_by_ints(207,160)) // ϡ -> Ϡ -- GREEK CAPITAL LETTER SAMPI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,163), Bry_.New_by_ints(207,162)) // ϣ -> Ϣ -- GREEK CAPITAL LETTER SHEI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,165), Bry_.New_by_ints(207,164)) // ϥ -> Ϥ -- GREEK CAPITAL LETTER FEI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,167), Bry_.New_by_ints(207,166)) // ϧ -> Ϧ -- GREEK CAPITAL LETTER KHEI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,169), Bry_.New_by_ints(207,168)) // ϩ -> Ϩ -- GREEK CAPITAL LETTER HORI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,171), Bry_.New_by_ints(207,170)) // ϫ -> Ϫ -- GREEK CAPITAL LETTER GANGIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,173), Bry_.New_by_ints(207,172)) // ϭ -> Ϭ -- GREEK CAPITAL LETTER SHIMA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,175), Bry_.New_by_ints(207,174)) // ϯ -> Ϯ -- GREEK CAPITAL LETTER DEI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,178), Bry_.New_by_ints(207,185)) // ϲ -> Ϲ -- GREEK SMALL LETTER LUNATE SIGMA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,184), Bry_.New_by_ints(207,183)) // ϸ -> Ϸ -- GREEK CAPITAL LETTER SHO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(207,187), Bry_.New_by_ints(207,186)) // ϻ -> Ϻ -- GREEK CAPITAL LETTER SAN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,144), Bry_.New_by_ints(208,128)) // ѐ -> Ѐ -- CYRILLIC CAPITAL LETTER IE WITH GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,145), Bry_.New_by_ints(208,129)) // ё -> Ё -- CYRILLIC CAPITAL LETTER IO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,146), Bry_.New_by_ints(208,130)) // ђ -> Ђ -- CYRILLIC CAPITAL LETTER DJE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,147), Bry_.New_by_ints(208,131)) // ѓ -> Ѓ -- CYRILLIC CAPITAL LETTER GJE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,148), Bry_.New_by_ints(208,132)) // є -> Є -- CYRILLIC CAPITAL LETTER E +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,149), Bry_.New_by_ints(208,133)) // ѕ -> Ѕ -- CYRILLIC CAPITAL LETTER DZE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,150), Bry_.New_by_ints(208,134)) // і -> І -- CYRILLIC CAPITAL LETTER I +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,151), Bry_.New_by_ints(208,135)) // ї -> Ї -- CYRILLIC CAPITAL LETTER YI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,152), Bry_.New_by_ints(208,136)) // ј -> Ј -- CYRILLIC CAPITAL LETTER JE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,153), Bry_.New_by_ints(208,137)) // љ -> Љ -- CYRILLIC CAPITAL LETTER LJE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,154), Bry_.New_by_ints(208,138)) // њ -> Њ -- CYRILLIC CAPITAL LETTER NJE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,155), Bry_.New_by_ints(208,139)) // ћ -> Ћ -- CYRILLIC CAPITAL LETTER TSHE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,156), Bry_.New_by_ints(208,140)) // ќ -> Ќ -- CYRILLIC CAPITAL LETTER KJE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,157), Bry_.New_by_ints(208,141)) // ѝ -> Ѝ -- CYRILLIC CAPITAL LETTER I WITH GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,158), Bry_.New_by_ints(208,142)) // ў -> Ў -- CYRILLIC CAPITAL LETTER SHORT U +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,159), Bry_.New_by_ints(208,143)) // џ -> Џ -- CYRILLIC CAPITAL LETTER DZHE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(208,176), Bry_.New_by_ints(208,144)) // а -> А -- CYRILLIC CAPITAL LETTER A +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(208,177), Bry_.New_by_ints(208,145)) // б -> Б -- CYRILLIC CAPITAL LETTER BE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(208,178), Bry_.New_by_ints(208,146)) // в -> В -- CYRILLIC CAPITAL LETTER VE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(208,179), Bry_.New_by_ints(208,147)) // г -> Г -- CYRILLIC CAPITAL LETTER GE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(208,180), Bry_.New_by_ints(208,148)) // д -> Д -- CYRILLIC CAPITAL LETTER DE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(208,181), Bry_.New_by_ints(208,149)) // е -> Е -- CYRILLIC CAPITAL LETTER IE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(208,182), Bry_.New_by_ints(208,150)) // ж -> Ж -- CYRILLIC CAPITAL LETTER ZHE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(208,183), Bry_.New_by_ints(208,151)) // з -> З -- CYRILLIC CAPITAL LETTER ZE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(208,184), Bry_.New_by_ints(208,152)) // и -> И -- CYRILLIC CAPITAL LETTER II +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(208,185), Bry_.New_by_ints(208,153)) // й -> Й -- CYRILLIC CAPITAL LETTER SHORT II +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(208,186), Bry_.New_by_ints(208,154)) // к -> К -- CYRILLIC CAPITAL LETTER KA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(208,187), Bry_.New_by_ints(208,155)) // л -> Л -- CYRILLIC CAPITAL LETTER EL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(208,188), Bry_.New_by_ints(208,156)) // м -> М -- CYRILLIC CAPITAL LETTER EM +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(208,189), Bry_.New_by_ints(208,157)) // н -> Н -- CYRILLIC CAPITAL LETTER EN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(208,190), Bry_.New_by_ints(208,158)) // о -> О -- CYRILLIC CAPITAL LETTER O +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(208,191), Bry_.New_by_ints(208,159)) // п -> П -- CYRILLIC CAPITAL LETTER PE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,128), Bry_.New_by_ints(208,160)) // р -> Р -- CYRILLIC CAPITAL LETTER ER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,129), Bry_.New_by_ints(208,161)) // с -> С -- CYRILLIC CAPITAL LETTER ES +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,130), Bry_.New_by_ints(208,162)) // т -> Т -- CYRILLIC CAPITAL LETTER TE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,131), Bry_.New_by_ints(208,163)) // у -> У -- CYRILLIC CAPITAL LETTER U +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,132), Bry_.New_by_ints(208,164)) // ф -> Ф -- CYRILLIC CAPITAL LETTER EF +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,133), Bry_.New_by_ints(208,165)) // х -> Х -- CYRILLIC CAPITAL LETTER KHA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,134), Bry_.New_by_ints(208,166)) // ц -> Ц -- CYRILLIC CAPITAL LETTER TSE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,135), Bry_.New_by_ints(208,167)) // ч -> Ч -- CYRILLIC CAPITAL LETTER CHE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,136), Bry_.New_by_ints(208,168)) // ш -> Ш -- CYRILLIC CAPITAL LETTER SHA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,137), Bry_.New_by_ints(208,169)) // щ -> Щ -- CYRILLIC CAPITAL LETTER SHCHA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,138), Bry_.New_by_ints(208,170)) // ъ -> Ъ -- CYRILLIC CAPITAL LETTER HARD SIGN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,139), Bry_.New_by_ints(208,171)) // ы -> Ы -- CYRILLIC CAPITAL LETTER YERI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,140), Bry_.New_by_ints(208,172)) // ь -> Ь -- CYRILLIC CAPITAL LETTER SOFT SIGN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,141), Bry_.New_by_ints(208,173)) // э -> Э -- CYRILLIC CAPITAL LETTER REVERSED E +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,142), Bry_.New_by_ints(208,174)) // ю -> Ю -- CYRILLIC CAPITAL LETTER IU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,143), Bry_.New_by_ints(208,175)) // я -> Я -- CYRILLIC CAPITAL LETTER IA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,161), Bry_.New_by_ints(209,160)) // ѡ -> Ѡ -- CYRILLIC CAPITAL LETTER OMEGA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,163), Bry_.New_by_ints(209,162)) // ѣ -> Ѣ -- CYRILLIC CAPITAL LETTER YAT +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,165), Bry_.New_by_ints(209,164)) // ѥ -> Ѥ -- CYRILLIC CAPITAL LETTER IOTIFIED E +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,167), Bry_.New_by_ints(209,166)) // ѧ -> Ѧ -- CYRILLIC CAPITAL LETTER LITTLE YUS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,169), Bry_.New_by_ints(209,168)) // ѩ -> Ѩ -- CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,171), Bry_.New_by_ints(209,170)) // ѫ -> Ѫ -- CYRILLIC CAPITAL LETTER BIG YUS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,173), Bry_.New_by_ints(209,172)) // ѭ -> Ѭ -- CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,175), Bry_.New_by_ints(209,174)) // ѯ -> Ѯ -- CYRILLIC CAPITAL LETTER KSI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,177), Bry_.New_by_ints(209,176)) // ѱ -> Ѱ -- CYRILLIC CAPITAL LETTER PSI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,179), Bry_.New_by_ints(209,178)) // ѳ -> Ѳ -- CYRILLIC CAPITAL LETTER FITA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,181), Bry_.New_by_ints(209,180)) // ѵ -> Ѵ -- CYRILLIC CAPITAL LETTER IZHITSA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,183), Bry_.New_by_ints(209,182)) // ѷ -> Ѷ -- CYRILLIC CAPITAL LETTER IZHITSA DOUBLE GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,185), Bry_.New_by_ints(209,184)) // ѹ -> Ѹ -- CYRILLIC CAPITAL LETTER UK DIGRAPH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,187), Bry_.New_by_ints(209,186)) // ѻ -> Ѻ -- CYRILLIC CAPITAL LETTER ROUND OMEGA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,189), Bry_.New_by_ints(209,188)) // ѽ -> Ѽ -- CYRILLIC CAPITAL LETTER OMEGA TITLO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(209,191), Bry_.New_by_ints(209,190)) // ѿ -> Ѿ -- CYRILLIC CAPITAL LETTER OT +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,129), Bry_.New_by_ints(210,128)) // ҁ -> Ҁ -- CYRILLIC CAPITAL LETTER KOPPA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,139), Bry_.New_by_ints(210,138)) // ҋ -> Ҋ -- CYRILLIC CAPITAL LETTER SHORT I WITH TAIL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,141), Bry_.New_by_ints(210,140)) // ҍ -> Ҍ -- CYRILLIC CAPITAL LETTER SEMISOFT SIGN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,143), Bry_.New_by_ints(210,142)) // ҏ -> Ҏ -- CYRILLIC CAPITAL LETTER ER WITH TICK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,145), Bry_.New_by_ints(210,144)) // ґ -> Ґ -- CYRILLIC CAPITAL LETTER GE WITH UPTURN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,147), Bry_.New_by_ints(210,146)) // ғ -> Ғ -- CYRILLIC CAPITAL LETTER GE BAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,149), Bry_.New_by_ints(210,148)) // ҕ -> Ҕ -- CYRILLIC CAPITAL LETTER GE HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,151), Bry_.New_by_ints(210,150)) // җ -> Җ -- CYRILLIC CAPITAL LETTER ZHE WITH RIGHT DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,153), Bry_.New_by_ints(210,152)) // ҙ -> Ҙ -- CYRILLIC CAPITAL LETTER ZE CEDILLA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,155), Bry_.New_by_ints(210,154)) // қ -> Қ -- CYRILLIC CAPITAL LETTER KA WITH RIGHT DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,157), Bry_.New_by_ints(210,156)) // ҝ -> Ҝ -- CYRILLIC CAPITAL LETTER KA VERTICAL BAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,159), Bry_.New_by_ints(210,158)) // ҟ -> Ҟ -- CYRILLIC CAPITAL LETTER KA BAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,161), Bry_.New_by_ints(210,160)) // ҡ -> Ҡ -- CYRILLIC CAPITAL LETTER REVERSED GE KA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,163), Bry_.New_by_ints(210,162)) // ң -> Ң -- CYRILLIC CAPITAL LETTER EN WITH RIGHT DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,165), Bry_.New_by_ints(210,164)) // ҥ -> Ҥ -- CYRILLIC CAPITAL LETTER EN GE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,167), Bry_.New_by_ints(210,166)) // ҧ -> Ҧ -- CYRILLIC CAPITAL LETTER PE HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,169), Bry_.New_by_ints(210,168)) // ҩ -> Ҩ -- CYRILLIC CAPITAL LETTER O HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,171), Bry_.New_by_ints(210,170)) // ҫ -> Ҫ -- CYRILLIC CAPITAL LETTER ES CEDILLA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,173), Bry_.New_by_ints(210,172)) // ҭ -> Ҭ -- CYRILLIC CAPITAL LETTER TE WITH RIGHT DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,175), Bry_.New_by_ints(210,174)) // ү -> Ү -- CYRILLIC CAPITAL LETTER STRAIGHT U +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,177), Bry_.New_by_ints(210,176)) // ұ -> Ұ -- CYRILLIC CAPITAL LETTER STRAIGHT U BAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,179), Bry_.New_by_ints(210,178)) // ҳ -> Ҳ -- CYRILLIC CAPITAL LETTER KHA WITH RIGHT DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,181), Bry_.New_by_ints(210,180)) // ҵ -> Ҵ -- CYRILLIC CAPITAL LETTER TE TSE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,183), Bry_.New_by_ints(210,182)) // ҷ -> Ҷ -- CYRILLIC CAPITAL LETTER CHE WITH RIGHT DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,185), Bry_.New_by_ints(210,184)) // ҹ -> Ҹ -- CYRILLIC CAPITAL LETTER CHE VERTICAL BAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,187), Bry_.New_by_ints(210,186)) // һ -> Һ -- CYRILLIC CAPITAL LETTER H +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,189), Bry_.New_by_ints(210,188)) // ҽ -> Ҽ -- CYRILLIC CAPITAL LETTER IE HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(210,191), Bry_.New_by_ints(210,190)) // ҿ -> Ҿ -- CYRILLIC CAPITAL LETTER IE HOOK OGONEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,143), Bry_.New_by_ints(211,128)) // ӏ -> Ӏ -- CYRILLIC LETTER I +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,130), Bry_.New_by_ints(211,129)) // ӂ -> Ӂ -- CYRILLIC CAPITAL LETTER SHORT ZHE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,132), Bry_.New_by_ints(211,131)) // ӄ -> Ӄ -- CYRILLIC CAPITAL LETTER KA HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,134), Bry_.New_by_ints(211,133)) // ӆ -> Ӆ -- CYRILLIC CAPITAL LETTER EL WITH TAIL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,136), Bry_.New_by_ints(211,135)) // ӈ -> Ӈ -- CYRILLIC CAPITAL LETTER EN HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,138), Bry_.New_by_ints(211,137)) // ӊ -> Ӊ -- CYRILLIC CAPITAL LETTER EN WITH TAIL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,140), Bry_.New_by_ints(211,139)) // ӌ -> Ӌ -- CYRILLIC CAPITAL LETTER CHE WITH LEFT DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,142), Bry_.New_by_ints(211,141)) // ӎ -> Ӎ -- CYRILLIC CAPITAL LETTER EM WITH TAIL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,145), Bry_.New_by_ints(211,144)) // ӑ -> Ӑ -- CYRILLIC CAPITAL LETTER A WITH BREVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,147), Bry_.New_by_ints(211,146)) // ӓ -> Ӓ -- CYRILLIC CAPITAL LETTER A WITH DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,149), Bry_.New_by_ints(211,148)) // ӕ -> Ӕ -- CYRILLIC CAPITAL LIGATURE A IE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,151), Bry_.New_by_ints(211,150)) // ӗ -> Ӗ -- CYRILLIC CAPITAL LETTER IE WITH BREVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,153), Bry_.New_by_ints(211,152)) // ә -> Ә -- CYRILLIC CAPITAL LETTER SCHWA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,155), Bry_.New_by_ints(211,154)) // ӛ -> Ӛ -- CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,157), Bry_.New_by_ints(211,156)) // ӝ -> Ӝ -- CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,159), Bry_.New_by_ints(211,158)) // ӟ -> Ӟ -- CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,161), Bry_.New_by_ints(211,160)) // ӡ -> Ӡ -- CYRILLIC CAPITAL LETTER ABKHASIAN DZE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,163), Bry_.New_by_ints(211,162)) // ӣ -> Ӣ -- CYRILLIC CAPITAL LETTER I WITH MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,165), Bry_.New_by_ints(211,164)) // ӥ -> Ӥ -- CYRILLIC CAPITAL LETTER I WITH DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,167), Bry_.New_by_ints(211,166)) // ӧ -> Ӧ -- CYRILLIC CAPITAL LETTER O WITH DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,169), Bry_.New_by_ints(211,168)) // ө -> Ө -- CYRILLIC CAPITAL LETTER BARRED O +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,171), Bry_.New_by_ints(211,170)) // ӫ -> Ӫ -- CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,173), Bry_.New_by_ints(211,172)) // ӭ -> Ӭ -- CYRILLIC CAPITAL LETTER E WITH DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,175), Bry_.New_by_ints(211,174)) // ӯ -> Ӯ -- CYRILLIC CAPITAL LETTER U WITH MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,177), Bry_.New_by_ints(211,176)) // ӱ -> Ӱ -- CYRILLIC CAPITAL LETTER U WITH DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,179), Bry_.New_by_ints(211,178)) // ӳ -> Ӳ -- CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,181), Bry_.New_by_ints(211,180)) // ӵ -> Ӵ -- CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,183), Bry_.New_by_ints(211,182)) // ӷ -> Ӷ -- CYRILLIC CAPITAL LETTER GHE WITH DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,185), Bry_.New_by_ints(211,184)) // ӹ -> Ӹ -- CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,187), Bry_.New_by_ints(211,186)) // ӻ -> Ӻ -- CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,189), Bry_.New_by_ints(211,188)) // ӽ -> Ӽ -- CYRILLIC CAPITAL LETTER HA WITH HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(211,191), Bry_.New_by_ints(211,190)) // ӿ -> Ӿ -- CYRILLIC CAPITAL LETTER HA WITH STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,129), Bry_.New_by_ints(212,128)) // ԁ -> Ԁ -- CYRILLIC CAPITAL LETTER KOMI DE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,131), Bry_.New_by_ints(212,130)) // ԃ -> Ԃ -- CYRILLIC CAPITAL LETTER KOMI DJE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,133), Bry_.New_by_ints(212,132)) // ԅ -> Ԅ -- CYRILLIC CAPITAL LETTER KOMI ZJE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,135), Bry_.New_by_ints(212,134)) // ԇ -> Ԇ -- CYRILLIC CAPITAL LETTER KOMI DZJE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,137), Bry_.New_by_ints(212,136)) // ԉ -> Ԉ -- CYRILLIC CAPITAL LETTER KOMI LJE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,139), Bry_.New_by_ints(212,138)) // ԋ -> Ԋ -- CYRILLIC CAPITAL LETTER KOMI NJE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,141), Bry_.New_by_ints(212,140)) // ԍ -> Ԍ -- CYRILLIC CAPITAL LETTER KOMI SJE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,143), Bry_.New_by_ints(212,142)) // ԏ -> Ԏ -- CYRILLIC CAPITAL LETTER KOMI TJE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,145), Bry_.New_by_ints(212,144)) // ԑ -> Ԑ -- CYRILLIC CAPITAL LETTER REVERSED ZE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,147), Bry_.New_by_ints(212,146)) // ԓ -> Ԓ -- CYRILLIC CAPITAL LETTER EL WITH HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,149), Bry_.New_by_ints(212,148)) // ԕ -> Ԕ -- CYRILLIC CAPITAL LETTER LHA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,151), Bry_.New_by_ints(212,150)) // ԗ -> Ԗ -- CYRILLIC CAPITAL LETTER RHA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,153), Bry_.New_by_ints(212,152)) // ԙ -> Ԙ -- CYRILLIC CAPITAL LETTER YAE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,155), Bry_.New_by_ints(212,154)) // ԛ -> Ԛ -- CYRILLIC CAPITAL LETTER QA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,157), Bry_.New_by_ints(212,156)) // ԝ -> Ԝ -- CYRILLIC CAPITAL LETTER WE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,159), Bry_.New_by_ints(212,158)) // ԟ -> Ԟ -- CYRILLIC CAPITAL LETTER ALEUT KA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,161), Bry_.New_by_ints(212,160)) // ԡ -> Ԡ -- CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,163), Bry_.New_by_ints(212,162)) // ԣ -> Ԣ -- CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,165), Bry_.New_by_ints(212,164)) // ԥ -> Ԥ -- CYRILLIC CAPITAL LETTER PE WITH DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(212,167), Bry_.New_by_ints(212,166)) // ԧ -> Ԧ -- CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,161), Bry_.New_by_ints(212,177)) // ա -> Ա -- ARMENIAN CAPITAL LETTER AYB +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,162), Bry_.New_by_ints(212,178)) // բ -> Բ -- ARMENIAN CAPITAL LETTER BEN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,163), Bry_.New_by_ints(212,179)) // գ -> Գ -- ARMENIAN CAPITAL LETTER GIM +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,164), Bry_.New_by_ints(212,180)) // դ -> Դ -- ARMENIAN CAPITAL LETTER DA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,165), Bry_.New_by_ints(212,181)) // ե -> Ե -- ARMENIAN CAPITAL LETTER ECH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,166), Bry_.New_by_ints(212,182)) // զ -> Զ -- ARMENIAN CAPITAL LETTER ZA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,167), Bry_.New_by_ints(212,183)) // է -> Է -- ARMENIAN CAPITAL LETTER EH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,168), Bry_.New_by_ints(212,184)) // ը -> Ը -- ARMENIAN CAPITAL LETTER ET +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,169), Bry_.New_by_ints(212,185)) // թ -> Թ -- ARMENIAN CAPITAL LETTER TO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,170), Bry_.New_by_ints(212,186)) // ժ -> Ժ -- ARMENIAN CAPITAL LETTER ZHE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,171), Bry_.New_by_ints(212,187)) // ի -> Ի -- ARMENIAN CAPITAL LETTER INI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,172), Bry_.New_by_ints(212,188)) // լ -> Լ -- ARMENIAN CAPITAL LETTER LIWN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,173), Bry_.New_by_ints(212,189)) // խ -> Խ -- ARMENIAN CAPITAL LETTER XEH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,174), Bry_.New_by_ints(212,190)) // ծ -> Ծ -- ARMENIAN CAPITAL LETTER CA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,175), Bry_.New_by_ints(212,191)) // կ -> Կ -- ARMENIAN CAPITAL LETTER KEN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,176), Bry_.New_by_ints(213,128)) // հ -> Հ -- ARMENIAN CAPITAL LETTER HO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,177), Bry_.New_by_ints(213,129)) // ձ -> Ձ -- ARMENIAN CAPITAL LETTER JA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,178), Bry_.New_by_ints(213,130)) // ղ -> Ղ -- ARMENIAN CAPITAL LETTER LAD +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,179), Bry_.New_by_ints(213,131)) // ճ -> Ճ -- ARMENIAN CAPITAL LETTER CHEH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,180), Bry_.New_by_ints(213,132)) // մ -> Մ -- ARMENIAN CAPITAL LETTER MEN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,181), Bry_.New_by_ints(213,133)) // յ -> Յ -- ARMENIAN CAPITAL LETTER YI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,182), Bry_.New_by_ints(213,134)) // ն -> Ն -- ARMENIAN CAPITAL LETTER NOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,183), Bry_.New_by_ints(213,135)) // շ -> Շ -- ARMENIAN CAPITAL LETTER SHA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,184), Bry_.New_by_ints(213,136)) // ո -> Ո -- ARMENIAN CAPITAL LETTER VO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,185), Bry_.New_by_ints(213,137)) // չ -> Չ -- ARMENIAN CAPITAL LETTER CHA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,186), Bry_.New_by_ints(213,138)) // պ -> Պ -- ARMENIAN CAPITAL LETTER PEH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,187), Bry_.New_by_ints(213,139)) // ջ -> Ջ -- ARMENIAN CAPITAL LETTER JHEH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,188), Bry_.New_by_ints(213,140)) // ռ -> Ռ -- ARMENIAN CAPITAL LETTER RA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,189), Bry_.New_by_ints(213,141)) // ս -> Ս -- ARMENIAN CAPITAL LETTER SEH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,190), Bry_.New_by_ints(213,142)) // վ -> Վ -- ARMENIAN CAPITAL LETTER VEW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(213,191), Bry_.New_by_ints(213,143)) // տ -> Տ -- ARMENIAN CAPITAL LETTER TIWN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(214,128), Bry_.New_by_ints(213,144)) // ր -> Ր -- ARMENIAN CAPITAL LETTER REH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(214,129), Bry_.New_by_ints(213,145)) // ց -> Ց -- ARMENIAN CAPITAL LETTER CO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(214,130), Bry_.New_by_ints(213,146)) // ւ -> Ւ -- ARMENIAN CAPITAL LETTER YIWN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(214,131), Bry_.New_by_ints(213,147)) // փ -> Փ -- ARMENIAN CAPITAL LETTER PIWR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(214,132), Bry_.New_by_ints(213,148)) // ք -> Ք -- ARMENIAN CAPITAL LETTER KEH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(214,133), Bry_.New_by_ints(213,149)) // օ -> Օ -- ARMENIAN CAPITAL LETTER OH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(214,134), Bry_.New_by_ints(213,150)) // ֆ -> Ֆ -- ARMENIAN CAPITAL LETTER FEH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,128), Bry_.New_by_ints(225,130,160)) // ⴀ -> Ⴀ -- GEORGIAN CAPITAL LETTER AN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,129), Bry_.New_by_ints(225,130,161)) // ⴁ -> Ⴁ -- GEORGIAN CAPITAL LETTER BAN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,130), Bry_.New_by_ints(225,130,162)) // ⴂ -> Ⴂ -- GEORGIAN CAPITAL LETTER GAN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,131), Bry_.New_by_ints(225,130,163)) // ⴃ -> Ⴃ -- GEORGIAN CAPITAL LETTER DON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,132), Bry_.New_by_ints(225,130,164)) // ⴄ -> Ⴄ -- GEORGIAN CAPITAL LETTER EN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,133), Bry_.New_by_ints(225,130,165)) // ⴅ -> Ⴅ -- GEORGIAN CAPITAL LETTER VIN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,134), Bry_.New_by_ints(225,130,166)) // ⴆ -> Ⴆ -- GEORGIAN CAPITAL LETTER ZEN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,135), Bry_.New_by_ints(225,130,167)) // ⴇ -> Ⴇ -- GEORGIAN CAPITAL LETTER TAN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,136), Bry_.New_by_ints(225,130,168)) // ⴈ -> Ⴈ -- GEORGIAN CAPITAL LETTER IN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,137), Bry_.New_by_ints(225,130,169)) // ⴉ -> Ⴉ -- GEORGIAN CAPITAL LETTER KAN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,138), Bry_.New_by_ints(225,130,170)) // ⴊ -> Ⴊ -- GEORGIAN CAPITAL LETTER LAS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,139), Bry_.New_by_ints(225,130,171)) // ⴋ -> Ⴋ -- GEORGIAN CAPITAL LETTER MAN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,140), Bry_.New_by_ints(225,130,172)) // ⴌ -> Ⴌ -- GEORGIAN CAPITAL LETTER NAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,141), Bry_.New_by_ints(225,130,173)) // ⴍ -> Ⴍ -- GEORGIAN CAPITAL LETTER ON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,142), Bry_.New_by_ints(225,130,174)) // ⴎ -> Ⴎ -- GEORGIAN CAPITAL LETTER PAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,143), Bry_.New_by_ints(225,130,175)) // ⴏ -> Ⴏ -- GEORGIAN CAPITAL LETTER ZHAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,144), Bry_.New_by_ints(225,130,176)) // ⴐ -> Ⴐ -- GEORGIAN CAPITAL LETTER RAE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,145), Bry_.New_by_ints(225,130,177)) // ⴑ -> Ⴑ -- GEORGIAN CAPITAL LETTER SAN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,146), Bry_.New_by_ints(225,130,178)) // ⴒ -> Ⴒ -- GEORGIAN CAPITAL LETTER TAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,147), Bry_.New_by_ints(225,130,179)) // ⴓ -> Ⴓ -- GEORGIAN CAPITAL LETTER UN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,148), Bry_.New_by_ints(225,130,180)) // ⴔ -> Ⴔ -- GEORGIAN CAPITAL LETTER PHAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,149), Bry_.New_by_ints(225,130,181)) // ⴕ -> Ⴕ -- GEORGIAN CAPITAL LETTER KHAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,150), Bry_.New_by_ints(225,130,182)) // ⴖ -> Ⴖ -- GEORGIAN CAPITAL LETTER GHAN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,151), Bry_.New_by_ints(225,130,183)) // ⴗ -> Ⴗ -- GEORGIAN CAPITAL LETTER QAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,152), Bry_.New_by_ints(225,130,184)) // ⴘ -> Ⴘ -- GEORGIAN CAPITAL LETTER SHIN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,153), Bry_.New_by_ints(225,130,185)) // ⴙ -> Ⴙ -- GEORGIAN CAPITAL LETTER CHIN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,154), Bry_.New_by_ints(225,130,186)) // ⴚ -> Ⴚ -- GEORGIAN CAPITAL LETTER CAN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,155), Bry_.New_by_ints(225,130,187)) // ⴛ -> Ⴛ -- GEORGIAN CAPITAL LETTER JIL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,156), Bry_.New_by_ints(225,130,188)) // ⴜ -> Ⴜ -- GEORGIAN CAPITAL LETTER CIL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,157), Bry_.New_by_ints(225,130,189)) // ⴝ -> Ⴝ -- GEORGIAN CAPITAL LETTER CHAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,158), Bry_.New_by_ints(225,130,190)) // ⴞ -> Ⴞ -- GEORGIAN CAPITAL LETTER XAN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,159), Bry_.New_by_ints(225,130,191)) // ⴟ -> Ⴟ -- GEORGIAN CAPITAL LETTER JHAN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,160), Bry_.New_by_ints(225,131,128)) // ⴠ -> Ⴠ -- GEORGIAN CAPITAL LETTER HAE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,161), Bry_.New_by_ints(225,131,129)) // ⴡ -> Ⴡ -- GEORGIAN CAPITAL LETTER HE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,162), Bry_.New_by_ints(225,131,130)) // ⴢ -> Ⴢ -- GEORGIAN CAPITAL LETTER HIE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,163), Bry_.New_by_ints(225,131,131)) // ⴣ -> Ⴣ -- GEORGIAN CAPITAL LETTER WE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,164), Bry_.New_by_ints(225,131,132)) // ⴤ -> Ⴤ -- GEORGIAN CAPITAL LETTER HAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,165), Bry_.New_by_ints(225,131,133)) // ⴥ -> Ⴥ -- GEORGIAN CAPITAL LETTER HOE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,167), Bry_.New_by_ints(225,131,135)) // ⴧ -> Ⴧ -- GEORGIAN CAPITAL LETTER YN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,180,173), Bry_.New_by_ints(225,131,141)) // ⴭ -> Ⴭ -- GEORGIAN CAPITAL LETTER AEN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,181,185), Bry_.New_by_ints(234,157,189)) // ᵹ -> Ᵹ -- LATIN SMALL LETTER INSULAR G +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,181,189), Bry_.New_by_ints(226,177,163)) // ᵽ -> Ᵽ -- LATIN SMALL LETTER P WITH STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,129), Bry_.New_by_ints(225,184,128)) // ḁ -> Ḁ -- LATIN CAPITAL LETTER A WITH RING BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,131), Bry_.New_by_ints(225,184,130)) // ḃ -> Ḃ -- LATIN CAPITAL LETTER B WITH DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,133), Bry_.New_by_ints(225,184,132)) // ḅ -> Ḅ -- LATIN CAPITAL LETTER B WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,135), Bry_.New_by_ints(225,184,134)) // ḇ -> Ḇ -- LATIN CAPITAL LETTER B WITH LINE BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,137), Bry_.New_by_ints(225,184,136)) // ḉ -> Ḉ -- LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,139), Bry_.New_by_ints(225,184,138)) // ḋ -> Ḋ -- LATIN CAPITAL LETTER D WITH DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,141), Bry_.New_by_ints(225,184,140)) // ḍ -> Ḍ -- LATIN CAPITAL LETTER D WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,143), Bry_.New_by_ints(225,184,142)) // ḏ -> Ḏ -- LATIN CAPITAL LETTER D WITH LINE BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,145), Bry_.New_by_ints(225,184,144)) // ḑ -> Ḑ -- LATIN CAPITAL LETTER D WITH CEDILLA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,147), Bry_.New_by_ints(225,184,146)) // ḓ -> Ḓ -- LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,149), Bry_.New_by_ints(225,184,148)) // ḕ -> Ḕ -- LATIN CAPITAL LETTER E WITH MACRON AND GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,151), Bry_.New_by_ints(225,184,150)) // ḗ -> Ḗ -- LATIN CAPITAL LETTER E WITH MACRON AND ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,153), Bry_.New_by_ints(225,184,152)) // ḙ -> Ḙ -- LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,155), Bry_.New_by_ints(225,184,154)) // ḛ -> Ḛ -- LATIN CAPITAL LETTER E WITH TILDE BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,157), Bry_.New_by_ints(225,184,156)) // ḝ -> Ḝ -- LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,159), Bry_.New_by_ints(225,184,158)) // ḟ -> Ḟ -- LATIN CAPITAL LETTER F WITH DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,161), Bry_.New_by_ints(225,184,160)) // ḡ -> Ḡ -- LATIN CAPITAL LETTER G WITH MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,163), Bry_.New_by_ints(225,184,162)) // ḣ -> Ḣ -- LATIN CAPITAL LETTER H WITH DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,165), Bry_.New_by_ints(225,184,164)) // ḥ -> Ḥ -- LATIN CAPITAL LETTER H WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,167), Bry_.New_by_ints(225,184,166)) // ḧ -> Ḧ -- LATIN CAPITAL LETTER H WITH DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,169), Bry_.New_by_ints(225,184,168)) // ḩ -> Ḩ -- LATIN CAPITAL LETTER H WITH CEDILLA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,171), Bry_.New_by_ints(225,184,170)) // ḫ -> Ḫ -- LATIN CAPITAL LETTER H WITH BREVE BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,173), Bry_.New_by_ints(225,184,172)) // ḭ -> Ḭ -- LATIN CAPITAL LETTER I WITH TILDE BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,175), Bry_.New_by_ints(225,184,174)) // ḯ -> Ḯ -- LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,177), Bry_.New_by_ints(225,184,176)) // ḱ -> Ḱ -- LATIN CAPITAL LETTER K WITH ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,179), Bry_.New_by_ints(225,184,178)) // ḳ -> Ḳ -- LATIN CAPITAL LETTER K WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,181), Bry_.New_by_ints(225,184,180)) // ḵ -> Ḵ -- LATIN CAPITAL LETTER K WITH LINE BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,183), Bry_.New_by_ints(225,184,182)) // ḷ -> Ḷ -- LATIN CAPITAL LETTER L WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,185), Bry_.New_by_ints(225,184,184)) // ḹ -> Ḹ -- LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,187), Bry_.New_by_ints(225,184,186)) // ḻ -> Ḻ -- LATIN CAPITAL LETTER L WITH LINE BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,189), Bry_.New_by_ints(225,184,188)) // ḽ -> Ḽ -- LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,184,191), Bry_.New_by_ints(225,184,190)) // ḿ -> Ḿ -- LATIN CAPITAL LETTER M WITH ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,129), Bry_.New_by_ints(225,185,128)) // ṁ -> Ṁ -- LATIN CAPITAL LETTER M WITH DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,131), Bry_.New_by_ints(225,185,130)) // ṃ -> Ṃ -- LATIN CAPITAL LETTER M WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,133), Bry_.New_by_ints(225,185,132)) // ṅ -> Ṅ -- LATIN CAPITAL LETTER N WITH DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,135), Bry_.New_by_ints(225,185,134)) // ṇ -> Ṇ -- LATIN CAPITAL LETTER N WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,137), Bry_.New_by_ints(225,185,136)) // ṉ -> Ṉ -- LATIN CAPITAL LETTER N WITH LINE BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,139), Bry_.New_by_ints(225,185,138)) // ṋ -> Ṋ -- LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,141), Bry_.New_by_ints(225,185,140)) // ṍ -> Ṍ -- LATIN CAPITAL LETTER O WITH TILDE AND ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,143), Bry_.New_by_ints(225,185,142)) // ṏ -> Ṏ -- LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,145), Bry_.New_by_ints(225,185,144)) // ṑ -> Ṑ -- LATIN CAPITAL LETTER O WITH MACRON AND GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,147), Bry_.New_by_ints(225,185,146)) // ṓ -> Ṓ -- LATIN CAPITAL LETTER O WITH MACRON AND ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,149), Bry_.New_by_ints(225,185,148)) // ṕ -> Ṕ -- LATIN CAPITAL LETTER P WITH ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,151), Bry_.New_by_ints(225,185,150)) // ṗ -> Ṗ -- LATIN CAPITAL LETTER P WITH DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,153), Bry_.New_by_ints(225,185,152)) // ṙ -> Ṙ -- LATIN CAPITAL LETTER R WITH DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,155), Bry_.New_by_ints(225,185,154)) // ṛ -> Ṛ -- LATIN CAPITAL LETTER R WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,157), Bry_.New_by_ints(225,185,156)) // ṝ -> Ṝ -- LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,159), Bry_.New_by_ints(225,185,158)) // ṟ -> Ṟ -- LATIN CAPITAL LETTER R WITH LINE BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,161), Bry_.New_by_ints(225,185,160)) // ṡ -> Ṡ -- LATIN CAPITAL LETTER S WITH DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,163), Bry_.New_by_ints(225,185,162)) // ṣ -> Ṣ -- LATIN CAPITAL LETTER S WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,165), Bry_.New_by_ints(225,185,164)) // ṥ -> Ṥ -- LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,167), Bry_.New_by_ints(225,185,166)) // ṧ -> Ṧ -- LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,169), Bry_.New_by_ints(225,185,168)) // ṩ -> Ṩ -- LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,171), Bry_.New_by_ints(225,185,170)) // ṫ -> Ṫ -- LATIN CAPITAL LETTER T WITH DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,173), Bry_.New_by_ints(225,185,172)) // ṭ -> Ṭ -- LATIN CAPITAL LETTER T WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,175), Bry_.New_by_ints(225,185,174)) // ṯ -> Ṯ -- LATIN CAPITAL LETTER T WITH LINE BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,177), Bry_.New_by_ints(225,185,176)) // ṱ -> Ṱ -- LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,179), Bry_.New_by_ints(225,185,178)) // ṳ -> Ṳ -- LATIN CAPITAL LETTER U WITH DIAERESIS BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,181), Bry_.New_by_ints(225,185,180)) // ṵ -> Ṵ -- LATIN CAPITAL LETTER U WITH TILDE BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,183), Bry_.New_by_ints(225,185,182)) // ṷ -> Ṷ -- LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,185), Bry_.New_by_ints(225,185,184)) // ṹ -> Ṹ -- LATIN CAPITAL LETTER U WITH TILDE AND ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,187), Bry_.New_by_ints(225,185,186)) // ṻ -> Ṻ -- LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,189), Bry_.New_by_ints(225,185,188)) // ṽ -> Ṽ -- LATIN CAPITAL LETTER V WITH TILDE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,185,191), Bry_.New_by_ints(225,185,190)) // ṿ -> Ṿ -- LATIN CAPITAL LETTER V WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,129), Bry_.New_by_ints(225,186,128)) // ẁ -> Ẁ -- LATIN CAPITAL LETTER W WITH GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,131), Bry_.New_by_ints(225,186,130)) // ẃ -> Ẃ -- LATIN CAPITAL LETTER W WITH ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,133), Bry_.New_by_ints(225,186,132)) // ẅ -> Ẅ -- LATIN CAPITAL LETTER W WITH DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,135), Bry_.New_by_ints(225,186,134)) // ẇ -> Ẇ -- LATIN CAPITAL LETTER W WITH DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,137), Bry_.New_by_ints(225,186,136)) // ẉ -> Ẉ -- LATIN CAPITAL LETTER W WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,139), Bry_.New_by_ints(225,186,138)) // ẋ -> Ẋ -- LATIN CAPITAL LETTER X WITH DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,141), Bry_.New_by_ints(225,186,140)) // ẍ -> Ẍ -- LATIN CAPITAL LETTER X WITH DIAERESIS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,143), Bry_.New_by_ints(225,186,142)) // ẏ -> Ẏ -- LATIN CAPITAL LETTER Y WITH DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,145), Bry_.New_by_ints(225,186,144)) // ẑ -> Ẑ -- LATIN CAPITAL LETTER Z WITH CIRCUMFLEX +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,147), Bry_.New_by_ints(225,186,146)) // ẓ -> Ẓ -- LATIN CAPITAL LETTER Z WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,149), Bry_.New_by_ints(225,186,148)) // ẕ -> Ẕ -- LATIN CAPITAL LETTER Z WITH LINE BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,161), Bry_.New_by_ints(225,186,160)) // ạ -> Ạ -- LATIN CAPITAL LETTER A WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,163), Bry_.New_by_ints(225,186,162)) // ả -> Ả -- LATIN CAPITAL LETTER A WITH HOOK ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,165), Bry_.New_by_ints(225,186,164)) // ấ -> Ấ -- LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,167), Bry_.New_by_ints(225,186,166)) // ầ -> Ầ -- LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,169), Bry_.New_by_ints(225,186,168)) // ẩ -> Ẩ -- LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,171), Bry_.New_by_ints(225,186,170)) // ẫ -> Ẫ -- LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,173), Bry_.New_by_ints(225,186,172)) // ậ -> Ậ -- LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,175), Bry_.New_by_ints(225,186,174)) // ắ -> Ắ -- LATIN CAPITAL LETTER A WITH BREVE AND ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,177), Bry_.New_by_ints(225,186,176)) // ằ -> Ằ -- LATIN CAPITAL LETTER A WITH BREVE AND GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,179), Bry_.New_by_ints(225,186,178)) // ẳ -> Ẳ -- LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,181), Bry_.New_by_ints(225,186,180)) // ẵ -> Ẵ -- LATIN CAPITAL LETTER A WITH BREVE AND TILDE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,183), Bry_.New_by_ints(225,186,182)) // ặ -> Ặ -- LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,185), Bry_.New_by_ints(225,186,184)) // ẹ -> Ẹ -- LATIN CAPITAL LETTER E WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,187), Bry_.New_by_ints(225,186,186)) // ẻ -> Ẻ -- LATIN CAPITAL LETTER E WITH HOOK ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,189), Bry_.New_by_ints(225,186,188)) // ẽ -> Ẽ -- LATIN CAPITAL LETTER E WITH TILDE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,186,191), Bry_.New_by_ints(225,186,190)) // ế -> Ế -- LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,129), Bry_.New_by_ints(225,187,128)) // ề -> Ề -- LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,131), Bry_.New_by_ints(225,187,130)) // ể -> Ể -- LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,133), Bry_.New_by_ints(225,187,132)) // ễ -> Ễ -- LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,135), Bry_.New_by_ints(225,187,134)) // ệ -> Ệ -- LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,137), Bry_.New_by_ints(225,187,136)) // ỉ -> Ỉ -- LATIN CAPITAL LETTER I WITH HOOK ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,139), Bry_.New_by_ints(225,187,138)) // ị -> Ị -- LATIN CAPITAL LETTER I WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,141), Bry_.New_by_ints(225,187,140)) // ọ -> Ọ -- LATIN CAPITAL LETTER O WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,143), Bry_.New_by_ints(225,187,142)) // ỏ -> Ỏ -- LATIN CAPITAL LETTER O WITH HOOK ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,145), Bry_.New_by_ints(225,187,144)) // ố -> Ố -- LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,147), Bry_.New_by_ints(225,187,146)) // ồ -> Ồ -- LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,149), Bry_.New_by_ints(225,187,148)) // ổ -> Ổ -- LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,151), Bry_.New_by_ints(225,187,150)) // ỗ -> Ỗ -- LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,153), Bry_.New_by_ints(225,187,152)) // ộ -> Ộ -- LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,155), Bry_.New_by_ints(225,187,154)) // ớ -> Ớ -- LATIN CAPITAL LETTER O WITH HORN AND ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,157), Bry_.New_by_ints(225,187,156)) // ờ -> Ờ -- LATIN CAPITAL LETTER O WITH HORN AND GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,159), Bry_.New_by_ints(225,187,158)) // ở -> Ở -- LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,161), Bry_.New_by_ints(225,187,160)) // ỡ -> Ỡ -- LATIN CAPITAL LETTER O WITH HORN AND TILDE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,163), Bry_.New_by_ints(225,187,162)) // ợ -> Ợ -- LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,165), Bry_.New_by_ints(225,187,164)) // ụ -> Ụ -- LATIN CAPITAL LETTER U WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,167), Bry_.New_by_ints(225,187,166)) // ủ -> Ủ -- LATIN CAPITAL LETTER U WITH HOOK ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,169), Bry_.New_by_ints(225,187,168)) // ứ -> Ứ -- LATIN CAPITAL LETTER U WITH HORN AND ACUTE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,171), Bry_.New_by_ints(225,187,170)) // ừ -> Ừ -- LATIN CAPITAL LETTER U WITH HORN AND GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,173), Bry_.New_by_ints(225,187,172)) // ử -> Ử -- LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,175), Bry_.New_by_ints(225,187,174)) // ữ -> Ữ -- LATIN CAPITAL LETTER U WITH HORN AND TILDE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,177), Bry_.New_by_ints(225,187,176)) // ự -> Ự -- LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,179), Bry_.New_by_ints(225,187,178)) // ỳ -> Ỳ -- LATIN CAPITAL LETTER Y WITH GRAVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,181), Bry_.New_by_ints(225,187,180)) // ỵ -> Ỵ -- LATIN CAPITAL LETTER Y WITH DOT BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,183), Bry_.New_by_ints(225,187,182)) // ỷ -> Ỷ -- LATIN CAPITAL LETTER Y WITH HOOK ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,185), Bry_.New_by_ints(225,187,184)) // ỹ -> Ỹ -- LATIN CAPITAL LETTER Y WITH TILDE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,187), Bry_.New_by_ints(225,187,186)) // ỻ -> Ỻ -- LATIN CAPITAL LETTER MIDDLE-WELSH LL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,189), Bry_.New_by_ints(225,187,188)) // ỽ -> Ỽ -- LATIN CAPITAL LETTER MIDDLE-WELSH V +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,187,191), Bry_.New_by_ints(225,187,190)) // ỿ -> Ỿ -- LATIN CAPITAL LETTER Y WITH LOOP +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,128), Bry_.New_by_ints(225,188,136)) // ἀ -> Ἀ -- GREEK SMALL LETTER ALPHA WITH PSILI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,129), Bry_.New_by_ints(225,188,137)) // ἁ -> Ἁ -- GREEK SMALL LETTER ALPHA WITH DASIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,130), Bry_.New_by_ints(225,188,138)) // ἂ -> Ἂ -- GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,131), Bry_.New_by_ints(225,188,139)) // ἃ -> Ἃ -- GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,132), Bry_.New_by_ints(225,188,140)) // ἄ -> Ἄ -- GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,133), Bry_.New_by_ints(225,188,141)) // ἅ -> Ἅ -- GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,134), Bry_.New_by_ints(225,188,142)) // ἆ -> Ἆ -- GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,135), Bry_.New_by_ints(225,188,143)) // ἇ -> Ἇ -- GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,144), Bry_.New_by_ints(225,188,152)) // ἐ -> Ἐ -- GREEK SMALL LETTER EPSILON WITH PSILI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,145), Bry_.New_by_ints(225,188,153)) // ἑ -> Ἑ -- GREEK SMALL LETTER EPSILON WITH DASIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,146), Bry_.New_by_ints(225,188,154)) // ἒ -> Ἒ -- GREEK SMALL LETTER EPSILON WITH PSILI AND VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,147), Bry_.New_by_ints(225,188,155)) // ἓ -> Ἓ -- GREEK SMALL LETTER EPSILON WITH DASIA AND VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,148), Bry_.New_by_ints(225,188,156)) // ἔ -> Ἔ -- GREEK SMALL LETTER EPSILON WITH PSILI AND OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,149), Bry_.New_by_ints(225,188,157)) // ἕ -> Ἕ -- GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,160), Bry_.New_by_ints(225,188,168)) // ἠ -> Ἠ -- GREEK SMALL LETTER ETA WITH PSILI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,161), Bry_.New_by_ints(225,188,169)) // ἡ -> Ἡ -- GREEK SMALL LETTER ETA WITH DASIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,162), Bry_.New_by_ints(225,188,170)) // ἢ -> Ἢ -- GREEK SMALL LETTER ETA WITH PSILI AND VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,163), Bry_.New_by_ints(225,188,171)) // ἣ -> Ἣ -- GREEK SMALL LETTER ETA WITH DASIA AND VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,164), Bry_.New_by_ints(225,188,172)) // ἤ -> Ἤ -- GREEK SMALL LETTER ETA WITH PSILI AND OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,165), Bry_.New_by_ints(225,188,173)) // ἥ -> Ἥ -- GREEK SMALL LETTER ETA WITH DASIA AND OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,166), Bry_.New_by_ints(225,188,174)) // ἦ -> Ἦ -- GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,167), Bry_.New_by_ints(225,188,175)) // ἧ -> Ἧ -- GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,176), Bry_.New_by_ints(225,188,184)) // ἰ -> Ἰ -- GREEK SMALL LETTER IOTA WITH PSILI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,177), Bry_.New_by_ints(225,188,185)) // ἱ -> Ἱ -- GREEK SMALL LETTER IOTA WITH DASIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,178), Bry_.New_by_ints(225,188,186)) // ἲ -> Ἲ -- GREEK SMALL LETTER IOTA WITH PSILI AND VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,179), Bry_.New_by_ints(225,188,187)) // ἳ -> Ἳ -- GREEK SMALL LETTER IOTA WITH DASIA AND VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,180), Bry_.New_by_ints(225,188,188)) // ἴ -> Ἴ -- GREEK SMALL LETTER IOTA WITH PSILI AND OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,181), Bry_.New_by_ints(225,188,189)) // ἵ -> Ἵ -- GREEK SMALL LETTER IOTA WITH DASIA AND OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,182), Bry_.New_by_ints(225,188,190)) // ἶ -> Ἶ -- GREEK SMALL LETTER IOTA WITH PSILI AND PERISPOMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,188,183), Bry_.New_by_ints(225,188,191)) // ἷ -> Ἷ -- GREEK SMALL LETTER IOTA WITH DASIA AND PERISPOMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,128), Bry_.New_by_ints(225,189,136)) // ὀ -> Ὀ -- GREEK SMALL LETTER OMICRON WITH PSILI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,129), Bry_.New_by_ints(225,189,137)) // ὁ -> Ὁ -- GREEK SMALL LETTER OMICRON WITH DASIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,130), Bry_.New_by_ints(225,189,138)) // ὂ -> Ὂ -- GREEK SMALL LETTER OMICRON WITH PSILI AND VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,131), Bry_.New_by_ints(225,189,139)) // ὃ -> Ὃ -- GREEK SMALL LETTER OMICRON WITH DASIA AND VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,132), Bry_.New_by_ints(225,189,140)) // ὄ -> Ὄ -- GREEK SMALL LETTER OMICRON WITH PSILI AND OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,133), Bry_.New_by_ints(225,189,141)) // ὅ -> Ὅ -- GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,145), Bry_.New_by_ints(225,189,153)) // ὑ -> Ὑ -- GREEK SMALL LETTER UPSILON WITH DASIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,147), Bry_.New_by_ints(225,189,155)) // ὓ -> Ὓ -- GREEK SMALL LETTER UPSILON WITH DASIA AND VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,149), Bry_.New_by_ints(225,189,157)) // ὕ -> Ὕ -- GREEK SMALL LETTER UPSILON WITH DASIA AND OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,151), Bry_.New_by_ints(225,189,159)) // ὗ -> Ὗ -- GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,160), Bry_.New_by_ints(225,189,168)) // ὠ -> Ὠ -- GREEK SMALL LETTER OMEGA WITH PSILI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,161), Bry_.New_by_ints(225,189,169)) // ὡ -> Ὡ -- GREEK SMALL LETTER OMEGA WITH DASIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,162), Bry_.New_by_ints(225,189,170)) // ὢ -> Ὢ -- GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,163), Bry_.New_by_ints(225,189,171)) // ὣ -> Ὣ -- GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,164), Bry_.New_by_ints(225,189,172)) // ὤ -> Ὤ -- GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,165), Bry_.New_by_ints(225,189,173)) // ὥ -> Ὥ -- GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,166), Bry_.New_by_ints(225,189,174)) // ὦ -> Ὦ -- GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,167), Bry_.New_by_ints(225,189,175)) // ὧ -> Ὧ -- GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,176), Bry_.New_by_ints(225,190,186)) // ὰ -> Ὰ -- GREEK SMALL LETTER ALPHA WITH VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,177), Bry_.New_by_ints(225,190,187)) // ά -> Ά -- GREEK SMALL LETTER ALPHA WITH OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,178), Bry_.New_by_ints(225,191,136)) // ὲ -> Ὲ -- GREEK SMALL LETTER EPSILON WITH VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,179), Bry_.New_by_ints(225,191,137)) // έ -> Έ -- GREEK SMALL LETTER EPSILON WITH OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,180), Bry_.New_by_ints(225,191,138)) // ὴ -> Ὴ -- GREEK SMALL LETTER ETA WITH VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,181), Bry_.New_by_ints(225,191,139)) // ή -> Ή -- GREEK SMALL LETTER ETA WITH OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,182), Bry_.New_by_ints(225,191,154)) // ὶ -> Ὶ -- GREEK SMALL LETTER IOTA WITH VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,183), Bry_.New_by_ints(225,191,155)) // ί -> Ί -- GREEK SMALL LETTER IOTA WITH OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,184), Bry_.New_by_ints(225,191,184)) // ὸ -> Ὸ -- GREEK SMALL LETTER OMICRON WITH VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,185), Bry_.New_by_ints(225,191,185)) // ό -> Ό -- GREEK SMALL LETTER OMICRON WITH OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,186), Bry_.New_by_ints(225,191,170)) // ὺ -> Ὺ -- GREEK SMALL LETTER UPSILON WITH VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,187), Bry_.New_by_ints(225,191,171)) // ύ -> Ύ -- GREEK SMALL LETTER UPSILON WITH OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,188), Bry_.New_by_ints(225,191,186)) // ὼ -> Ὼ -- GREEK SMALL LETTER OMEGA WITH VARIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,189,189), Bry_.New_by_ints(225,191,187)) // ώ -> Ώ -- GREEK SMALL LETTER OMEGA WITH OXIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,128), Bry_.New_by_ints(225,190,136)) // ᾀ -> ᾈ -- GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,129), Bry_.New_by_ints(225,190,137)) // ᾁ -> ᾉ -- GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,130), Bry_.New_by_ints(225,190,138)) // ᾂ -> ᾊ -- GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,131), Bry_.New_by_ints(225,190,139)) // ᾃ -> ᾋ -- GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,132), Bry_.New_by_ints(225,190,140)) // ᾄ -> ᾌ -- GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,133), Bry_.New_by_ints(225,190,141)) // ᾅ -> ᾍ -- GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,134), Bry_.New_by_ints(225,190,142)) // ᾆ -> ᾎ -- GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,135), Bry_.New_by_ints(225,190,143)) // ᾇ -> ᾏ -- GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,144), Bry_.New_by_ints(225,190,152)) // ᾐ -> ᾘ -- GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,145), Bry_.New_by_ints(225,190,153)) // ᾑ -> ᾙ -- GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,146), Bry_.New_by_ints(225,190,154)) // ᾒ -> ᾚ -- GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,147), Bry_.New_by_ints(225,190,155)) // ᾓ -> ᾛ -- GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,148), Bry_.New_by_ints(225,190,156)) // ᾔ -> ᾜ -- GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,149), Bry_.New_by_ints(225,190,157)) // ᾕ -> ᾝ -- GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,150), Bry_.New_by_ints(225,190,158)) // ᾖ -> ᾞ -- GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,151), Bry_.New_by_ints(225,190,159)) // ᾗ -> ᾟ -- GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,160), Bry_.New_by_ints(225,190,168)) // ᾠ -> ᾨ -- GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,161), Bry_.New_by_ints(225,190,169)) // ᾡ -> ᾩ -- GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,162), Bry_.New_by_ints(225,190,170)) // ᾢ -> ᾪ -- GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,163), Bry_.New_by_ints(225,190,171)) // ᾣ -> ᾫ -- GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,164), Bry_.New_by_ints(225,190,172)) // ᾤ -> ᾬ -- GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,165), Bry_.New_by_ints(225,190,173)) // ᾥ -> ᾭ -- GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,166), Bry_.New_by_ints(225,190,174)) // ᾦ -> ᾮ -- GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,167), Bry_.New_by_ints(225,190,175)) // ᾧ -> ᾯ -- GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,176), Bry_.New_by_ints(225,190,184)) // ᾰ -> Ᾰ -- GREEK SMALL LETTER ALPHA WITH VRACHY +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,177), Bry_.New_by_ints(225,190,185)) // ᾱ -> Ᾱ -- GREEK SMALL LETTER ALPHA WITH MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,190,179), Bry_.New_by_ints(225,190,188)) // ᾳ -> ᾼ -- GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,191,131), Bry_.New_by_ints(225,191,140)) // ῃ -> ῌ -- GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,191,144), Bry_.New_by_ints(225,191,152)) // ῐ -> Ῐ -- GREEK SMALL LETTER IOTA WITH VRACHY +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,191,145), Bry_.New_by_ints(225,191,153)) // ῑ -> Ῑ -- GREEK SMALL LETTER IOTA WITH MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,191,160), Bry_.New_by_ints(225,191,168)) // ῠ -> Ῠ -- GREEK SMALL LETTER UPSILON WITH VRACHY +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,191,161), Bry_.New_by_ints(225,191,169)) // ῡ -> Ῡ -- GREEK SMALL LETTER UPSILON WITH MACRON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,191,165), Bry_.New_by_ints(225,191,172)) // ῥ -> Ῥ -- GREEK SMALL LETTER RHO WITH DASIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(225,191,179), Bry_.New_by_ints(225,191,188)) // ῳ -> ῼ -- GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,142), Bry_.New_by_ints(226,132,178)) // ⅎ -> Ⅎ -- TURNED F +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,176), Bry_.New_by_ints(226,133,160)) // ⅰ -> Ⅰ -- ROMAN NUMERAL ONE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,177), Bry_.New_by_ints(226,133,161)) // ⅱ -> Ⅱ -- ROMAN NUMERAL TWO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,178), Bry_.New_by_ints(226,133,162)) // ⅲ -> Ⅲ -- ROMAN NUMERAL THREE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,179), Bry_.New_by_ints(226,133,163)) // ⅳ -> Ⅳ -- ROMAN NUMERAL FOUR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,180), Bry_.New_by_ints(226,133,164)) // ⅴ -> Ⅴ -- ROMAN NUMERAL FIVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,181), Bry_.New_by_ints(226,133,165)) // ⅵ -> Ⅵ -- ROMAN NUMERAL SIX +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,182), Bry_.New_by_ints(226,133,166)) // ⅶ -> Ⅶ -- ROMAN NUMERAL SEVEN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,183), Bry_.New_by_ints(226,133,167)) // ⅷ -> Ⅷ -- ROMAN NUMERAL EIGHT +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,184), Bry_.New_by_ints(226,133,168)) // ⅸ -> Ⅸ -- ROMAN NUMERAL NINE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,185), Bry_.New_by_ints(226,133,169)) // ⅹ -> Ⅹ -- ROMAN NUMERAL TEN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,186), Bry_.New_by_ints(226,133,170)) // ⅺ -> Ⅺ -- ROMAN NUMERAL ELEVEN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,187), Bry_.New_by_ints(226,133,171)) // ⅻ -> Ⅻ -- ROMAN NUMERAL TWELVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,188), Bry_.New_by_ints(226,133,172)) // ⅼ -> Ⅼ -- ROMAN NUMERAL FIFTY +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,189), Bry_.New_by_ints(226,133,173)) // ⅽ -> Ⅽ -- ROMAN NUMERAL ONE HUNDRED +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,190), Bry_.New_by_ints(226,133,174)) // ⅾ -> Ⅾ -- ROMAN NUMERAL FIVE HUNDRED +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,133,191), Bry_.New_by_ints(226,133,175)) // ⅿ -> Ⅿ -- ROMAN NUMERAL ONE THOUSAND +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,134,132), Bry_.New_by_ints(226,134,131)) // ↄ -> Ↄ -- ROMAN NUMERAL REVERSED ONE HUNDRED +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,144), Bry_.New_by_ints(226,146,182)) // ⓐ -> Ⓐ -- CIRCLED LATIN CAPITAL LETTER A +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,145), Bry_.New_by_ints(226,146,183)) // ⓑ -> Ⓑ -- CIRCLED LATIN CAPITAL LETTER B +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,146), Bry_.New_by_ints(226,146,184)) // ⓒ -> Ⓒ -- CIRCLED LATIN CAPITAL LETTER C +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,147), Bry_.New_by_ints(226,146,185)) // ⓓ -> Ⓓ -- CIRCLED LATIN CAPITAL LETTER D +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,148), Bry_.New_by_ints(226,146,186)) // ⓔ -> Ⓔ -- CIRCLED LATIN CAPITAL LETTER E +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,149), Bry_.New_by_ints(226,146,187)) // ⓕ -> Ⓕ -- CIRCLED LATIN CAPITAL LETTER F +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,150), Bry_.New_by_ints(226,146,188)) // ⓖ -> Ⓖ -- CIRCLED LATIN CAPITAL LETTER G +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,151), Bry_.New_by_ints(226,146,189)) // ⓗ -> Ⓗ -- CIRCLED LATIN CAPITAL LETTER H +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,152), Bry_.New_by_ints(226,146,190)) // ⓘ -> Ⓘ -- CIRCLED LATIN CAPITAL LETTER I +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,153), Bry_.New_by_ints(226,146,191)) // ⓙ -> Ⓙ -- CIRCLED LATIN CAPITAL LETTER J +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,154), Bry_.New_by_ints(226,147,128)) // ⓚ -> Ⓚ -- CIRCLED LATIN CAPITAL LETTER K +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,155), Bry_.New_by_ints(226,147,129)) // ⓛ -> Ⓛ -- CIRCLED LATIN CAPITAL LETTER L +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,156), Bry_.New_by_ints(226,147,130)) // ⓜ -> Ⓜ -- CIRCLED LATIN CAPITAL LETTER M +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,157), Bry_.New_by_ints(226,147,131)) // ⓝ -> Ⓝ -- CIRCLED LATIN CAPITAL LETTER N +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,158), Bry_.New_by_ints(226,147,132)) // ⓞ -> Ⓞ -- CIRCLED LATIN CAPITAL LETTER O +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,159), Bry_.New_by_ints(226,147,133)) // ⓟ -> Ⓟ -- CIRCLED LATIN CAPITAL LETTER P +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,160), Bry_.New_by_ints(226,147,134)) // ⓠ -> Ⓠ -- CIRCLED LATIN CAPITAL LETTER Q +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,161), Bry_.New_by_ints(226,147,135)) // ⓡ -> Ⓡ -- CIRCLED LATIN CAPITAL LETTER R +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,162), Bry_.New_by_ints(226,147,136)) // ⓢ -> Ⓢ -- CIRCLED LATIN CAPITAL LETTER S +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,163), Bry_.New_by_ints(226,147,137)) // ⓣ -> Ⓣ -- CIRCLED LATIN CAPITAL LETTER T +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,164), Bry_.New_by_ints(226,147,138)) // ⓤ -> Ⓤ -- CIRCLED LATIN CAPITAL LETTER U +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,165), Bry_.New_by_ints(226,147,139)) // ⓥ -> Ⓥ -- CIRCLED LATIN CAPITAL LETTER V +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,166), Bry_.New_by_ints(226,147,140)) // ⓦ -> Ⓦ -- CIRCLED LATIN CAPITAL LETTER W +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,167), Bry_.New_by_ints(226,147,141)) // ⓧ -> Ⓧ -- CIRCLED LATIN CAPITAL LETTER X +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,168), Bry_.New_by_ints(226,147,142)) // ⓨ -> Ⓨ -- CIRCLED LATIN CAPITAL LETTER Y +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,147,169), Bry_.New_by_ints(226,147,143)) // ⓩ -> Ⓩ -- CIRCLED LATIN CAPITAL LETTER Z +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,176,176), Bry_.New_by_ints(226,176,128)) // ⰰ -> Ⰰ -- GLAGOLITIC CAPITAL LETTER AZU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,176,177), Bry_.New_by_ints(226,176,129)) // ⰱ -> Ⰱ -- GLAGOLITIC CAPITAL LETTER BUKY +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,176,178), Bry_.New_by_ints(226,176,130)) // ⰲ -> Ⰲ -- GLAGOLITIC CAPITAL LETTER VEDE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,176,179), Bry_.New_by_ints(226,176,131)) // ⰳ -> Ⰳ -- GLAGOLITIC CAPITAL LETTER GLAGOLI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,176,180), Bry_.New_by_ints(226,176,132)) // ⰴ -> Ⰴ -- GLAGOLITIC CAPITAL LETTER DOBRO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,176,181), Bry_.New_by_ints(226,176,133)) // ⰵ -> Ⰵ -- GLAGOLITIC CAPITAL LETTER YESTU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,176,182), Bry_.New_by_ints(226,176,134)) // ⰶ -> Ⰶ -- GLAGOLITIC CAPITAL LETTER ZHIVETE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,176,183), Bry_.New_by_ints(226,176,135)) // ⰷ -> Ⰷ -- GLAGOLITIC CAPITAL LETTER DZELO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,176,184), Bry_.New_by_ints(226,176,136)) // ⰸ -> Ⰸ -- GLAGOLITIC CAPITAL LETTER ZEMLJA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,176,185), Bry_.New_by_ints(226,176,137)) // ⰹ -> Ⰹ -- GLAGOLITIC CAPITAL LETTER IZHE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,176,186), Bry_.New_by_ints(226,176,138)) // ⰺ -> Ⰺ -- GLAGOLITIC CAPITAL LETTER INITIAL IZHE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,176,187), Bry_.New_by_ints(226,176,139)) // ⰻ -> Ⰻ -- GLAGOLITIC CAPITAL LETTER I +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,176,188), Bry_.New_by_ints(226,176,140)) // ⰼ -> Ⰼ -- GLAGOLITIC CAPITAL LETTER DJERVI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,176,189), Bry_.New_by_ints(226,176,141)) // ⰽ -> Ⰽ -- GLAGOLITIC CAPITAL LETTER KAKO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,176,190), Bry_.New_by_ints(226,176,142)) // ⰾ -> Ⰾ -- GLAGOLITIC CAPITAL LETTER LJUDIJE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,176,191), Bry_.New_by_ints(226,176,143)) // ⰿ -> Ⰿ -- GLAGOLITIC CAPITAL LETTER MYSLITE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,128), Bry_.New_by_ints(226,176,144)) // ⱀ -> Ⱀ -- GLAGOLITIC CAPITAL LETTER NASHI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,129), Bry_.New_by_ints(226,176,145)) // ⱁ -> Ⱁ -- GLAGOLITIC CAPITAL LETTER ONU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,130), Bry_.New_by_ints(226,176,146)) // ⱂ -> Ⱂ -- GLAGOLITIC CAPITAL LETTER POKOJI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,131), Bry_.New_by_ints(226,176,147)) // ⱃ -> Ⱃ -- GLAGOLITIC CAPITAL LETTER RITSI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,132), Bry_.New_by_ints(226,176,148)) // ⱄ -> Ⱄ -- GLAGOLITIC CAPITAL LETTER SLOVO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,133), Bry_.New_by_ints(226,176,149)) // ⱅ -> Ⱅ -- GLAGOLITIC CAPITAL LETTER TVRIDO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,134), Bry_.New_by_ints(226,176,150)) // ⱆ -> Ⱆ -- GLAGOLITIC CAPITAL LETTER UKU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,135), Bry_.New_by_ints(226,176,151)) // ⱇ -> Ⱇ -- GLAGOLITIC CAPITAL LETTER FRITU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,136), Bry_.New_by_ints(226,176,152)) // ⱈ -> Ⱈ -- GLAGOLITIC CAPITAL LETTER HERU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,137), Bry_.New_by_ints(226,176,153)) // ⱉ -> Ⱉ -- GLAGOLITIC CAPITAL LETTER OTU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,138), Bry_.New_by_ints(226,176,154)) // ⱊ -> Ⱊ -- GLAGOLITIC CAPITAL LETTER PE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,139), Bry_.New_by_ints(226,176,155)) // ⱋ -> Ⱋ -- GLAGOLITIC CAPITAL LETTER SHTA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,140), Bry_.New_by_ints(226,176,156)) // ⱌ -> Ⱌ -- GLAGOLITIC CAPITAL LETTER TSI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,141), Bry_.New_by_ints(226,176,157)) // ⱍ -> Ⱍ -- GLAGOLITIC CAPITAL LETTER CHRIVI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,142), Bry_.New_by_ints(226,176,158)) // ⱎ -> Ⱎ -- GLAGOLITIC CAPITAL LETTER SHA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,143), Bry_.New_by_ints(226,176,159)) // ⱏ -> Ⱏ -- GLAGOLITIC CAPITAL LETTER YERU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,144), Bry_.New_by_ints(226,176,160)) // ⱐ -> Ⱐ -- GLAGOLITIC CAPITAL LETTER YERI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,145), Bry_.New_by_ints(226,176,161)) // ⱑ -> Ⱑ -- GLAGOLITIC CAPITAL LETTER YATI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,146), Bry_.New_by_ints(226,176,162)) // ⱒ -> Ⱒ -- GLAGOLITIC CAPITAL LETTER SPIDERY HA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,147), Bry_.New_by_ints(226,176,163)) // ⱓ -> Ⱓ -- GLAGOLITIC CAPITAL LETTER YU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,148), Bry_.New_by_ints(226,176,164)) // ⱔ -> Ⱔ -- GLAGOLITIC CAPITAL LETTER SMALL YUS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,149), Bry_.New_by_ints(226,176,165)) // ⱕ -> Ⱕ -- GLAGOLITIC CAPITAL LETTER SMALL YUS WITH TAIL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,150), Bry_.New_by_ints(226,176,166)) // ⱖ -> Ⱖ -- GLAGOLITIC CAPITAL LETTER YO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,151), Bry_.New_by_ints(226,176,167)) // ⱗ -> Ⱗ -- GLAGOLITIC CAPITAL LETTER IOTATED SMALL YUS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,152), Bry_.New_by_ints(226,176,168)) // ⱘ -> Ⱘ -- GLAGOLITIC CAPITAL LETTER BIG YUS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,153), Bry_.New_by_ints(226,176,169)) // ⱙ -> Ⱙ -- GLAGOLITIC CAPITAL LETTER IOTATED BIG YUS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,154), Bry_.New_by_ints(226,176,170)) // ⱚ -> Ⱚ -- GLAGOLITIC CAPITAL LETTER FITA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,155), Bry_.New_by_ints(226,176,171)) // ⱛ -> Ⱛ -- GLAGOLITIC CAPITAL LETTER IZHITSA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,156), Bry_.New_by_ints(226,176,172)) // ⱜ -> Ⱜ -- GLAGOLITIC CAPITAL LETTER SHTAPIC +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,157), Bry_.New_by_ints(226,176,173)) // ⱝ -> Ⱝ -- GLAGOLITIC CAPITAL LETTER TROKUTASTI A +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,158), Bry_.New_by_ints(226,176,174)) // ⱞ -> Ⱞ -- GLAGOLITIC CAPITAL LETTER LATINATE MYSLITE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,161), Bry_.New_by_ints(226,177,160)) // ⱡ -> Ⱡ -- LATIN CAPITAL LETTER L WITH DOUBLE BAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,168), Bry_.New_by_ints(226,177,167)) // ⱨ -> Ⱨ -- LATIN CAPITAL LETTER H WITH DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,170), Bry_.New_by_ints(226,177,169)) // ⱪ -> Ⱪ -- LATIN CAPITAL LETTER K WITH DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,172), Bry_.New_by_ints(226,177,171)) // ⱬ -> Ⱬ -- LATIN CAPITAL LETTER Z WITH DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,179), Bry_.New_by_ints(226,177,178)) // ⱳ -> Ⱳ -- LATIN CAPITAL LETTER W WITH HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,177,182), Bry_.New_by_ints(226,177,181)) // ⱶ -> Ⱶ -- LATIN CAPITAL LETTER HALF H +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,129), Bry_.New_by_ints(226,178,128)) // ⲁ -> Ⲁ -- COPTIC CAPITAL LETTER ALFA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,131), Bry_.New_by_ints(226,178,130)) // ⲃ -> Ⲃ -- COPTIC CAPITAL LETTER VIDA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,133), Bry_.New_by_ints(226,178,132)) // ⲅ -> Ⲅ -- COPTIC CAPITAL LETTER GAMMA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,135), Bry_.New_by_ints(226,178,134)) // ⲇ -> Ⲇ -- COPTIC CAPITAL LETTER DALDA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,137), Bry_.New_by_ints(226,178,136)) // ⲉ -> Ⲉ -- COPTIC CAPITAL LETTER EIE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,139), Bry_.New_by_ints(226,178,138)) // ⲋ -> Ⲋ -- COPTIC CAPITAL LETTER SOU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,141), Bry_.New_by_ints(226,178,140)) // ⲍ -> Ⲍ -- COPTIC CAPITAL LETTER ZATA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,143), Bry_.New_by_ints(226,178,142)) // ⲏ -> Ⲏ -- COPTIC CAPITAL LETTER HATE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,145), Bry_.New_by_ints(226,178,144)) // ⲑ -> Ⲑ -- COPTIC CAPITAL LETTER THETHE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,147), Bry_.New_by_ints(226,178,146)) // ⲓ -> Ⲓ -- COPTIC CAPITAL LETTER IAUDA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,149), Bry_.New_by_ints(226,178,148)) // ⲕ -> Ⲕ -- COPTIC CAPITAL LETTER KAPA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,151), Bry_.New_by_ints(226,178,150)) // ⲗ -> Ⲗ -- COPTIC CAPITAL LETTER LAULA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,153), Bry_.New_by_ints(226,178,152)) // ⲙ -> Ⲙ -- COPTIC CAPITAL LETTER MI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,155), Bry_.New_by_ints(226,178,154)) // ⲛ -> Ⲛ -- COPTIC CAPITAL LETTER NI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,157), Bry_.New_by_ints(226,178,156)) // ⲝ -> Ⲝ -- COPTIC CAPITAL LETTER KSI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,159), Bry_.New_by_ints(226,178,158)) // ⲟ -> Ⲟ -- COPTIC CAPITAL LETTER O +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,161), Bry_.New_by_ints(226,178,160)) // ⲡ -> Ⲡ -- COPTIC CAPITAL LETTER PI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,163), Bry_.New_by_ints(226,178,162)) // ⲣ -> Ⲣ -- COPTIC CAPITAL LETTER RO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,165), Bry_.New_by_ints(226,178,164)) // ⲥ -> Ⲥ -- COPTIC CAPITAL LETTER SIMA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,167), Bry_.New_by_ints(226,178,166)) // ⲧ -> Ⲧ -- COPTIC CAPITAL LETTER TAU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,169), Bry_.New_by_ints(226,178,168)) // ⲩ -> Ⲩ -- COPTIC CAPITAL LETTER UA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,171), Bry_.New_by_ints(226,178,170)) // ⲫ -> Ⲫ -- COPTIC CAPITAL LETTER FI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,173), Bry_.New_by_ints(226,178,172)) // ⲭ -> Ⲭ -- COPTIC CAPITAL LETTER KHI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,175), Bry_.New_by_ints(226,178,174)) // ⲯ -> Ⲯ -- COPTIC CAPITAL LETTER PSI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,177), Bry_.New_by_ints(226,178,176)) // ⲱ -> Ⲱ -- COPTIC CAPITAL LETTER OOU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,179), Bry_.New_by_ints(226,178,178)) // ⲳ -> Ⲳ -- COPTIC CAPITAL LETTER DIALECT-P ALEF +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,181), Bry_.New_by_ints(226,178,180)) // ⲵ -> Ⲵ -- COPTIC CAPITAL LETTER OLD COPTIC AIN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,183), Bry_.New_by_ints(226,178,182)) // ⲷ -> Ⲷ -- COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,185), Bry_.New_by_ints(226,178,184)) // ⲹ -> Ⲹ -- COPTIC CAPITAL LETTER DIALECT-P KAPA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,187), Bry_.New_by_ints(226,178,186)) // ⲻ -> Ⲻ -- COPTIC CAPITAL LETTER DIALECT-P NI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,189), Bry_.New_by_ints(226,178,188)) // ⲽ -> Ⲽ -- COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,178,191), Bry_.New_by_ints(226,178,190)) // ⲿ -> Ⲿ -- COPTIC CAPITAL LETTER OLD COPTIC OOU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,129), Bry_.New_by_ints(226,179,128)) // ⳁ -> Ⳁ -- COPTIC CAPITAL LETTER SAMPI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,131), Bry_.New_by_ints(226,179,130)) // ⳃ -> Ⳃ -- COPTIC CAPITAL LETTER CROSSED SHEI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,133), Bry_.New_by_ints(226,179,132)) // ⳅ -> Ⳅ -- COPTIC CAPITAL LETTER OLD COPTIC SHEI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,135), Bry_.New_by_ints(226,179,134)) // ⳇ -> Ⳇ -- COPTIC CAPITAL LETTER OLD COPTIC ESH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,137), Bry_.New_by_ints(226,179,136)) // ⳉ -> Ⳉ -- COPTIC CAPITAL LETTER AKHMIMIC KHEI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,139), Bry_.New_by_ints(226,179,138)) // ⳋ -> Ⳋ -- COPTIC CAPITAL LETTER DIALECT-P HORI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,141), Bry_.New_by_ints(226,179,140)) // ⳍ -> Ⳍ -- COPTIC CAPITAL LETTER OLD COPTIC HORI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,143), Bry_.New_by_ints(226,179,142)) // ⳏ -> Ⳏ -- COPTIC CAPITAL LETTER OLD COPTIC HA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,145), Bry_.New_by_ints(226,179,144)) // ⳑ -> Ⳑ -- COPTIC CAPITAL LETTER L-SHAPED HA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,147), Bry_.New_by_ints(226,179,146)) // ⳓ -> Ⳓ -- COPTIC CAPITAL LETTER OLD COPTIC HEI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,149), Bry_.New_by_ints(226,179,148)) // ⳕ -> Ⳕ -- COPTIC CAPITAL LETTER OLD COPTIC HAT +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,151), Bry_.New_by_ints(226,179,150)) // ⳗ -> Ⳗ -- COPTIC CAPITAL LETTER OLD COPTIC GANGIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,153), Bry_.New_by_ints(226,179,152)) // ⳙ -> Ⳙ -- COPTIC CAPITAL LETTER OLD COPTIC DJA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,155), Bry_.New_by_ints(226,179,154)) // ⳛ -> Ⳛ -- COPTIC CAPITAL LETTER OLD COPTIC SHIMA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,157), Bry_.New_by_ints(226,179,156)) // ⳝ -> Ⳝ -- COPTIC CAPITAL LETTER OLD NUBIAN SHIMA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,159), Bry_.New_by_ints(226,179,158)) // ⳟ -> Ⳟ -- COPTIC CAPITAL LETTER OLD NUBIAN NGI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,161), Bry_.New_by_ints(226,179,160)) // ⳡ -> Ⳡ -- COPTIC CAPITAL LETTER OLD NUBIAN NYI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,163), Bry_.New_by_ints(226,179,162)) // ⳣ -> Ⳣ -- COPTIC CAPITAL LETTER OLD NUBIAN WAU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,172), Bry_.New_by_ints(226,179,171)) // ⳬ -> Ⳬ -- COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,174), Bry_.New_by_ints(226,179,173)) // ⳮ -> Ⳮ -- COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(226,179,179), Bry_.New_by_ints(226,179,178)) // ⳳ -> Ⳳ -- COPTIC CAPITAL LETTER BOHAIRIC KHEI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,129), Bry_.New_by_ints(234,153,128)) // ꙁ -> Ꙁ -- CYRILLIC CAPITAL LETTER ZEMLYA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,131), Bry_.New_by_ints(234,153,130)) // ꙃ -> Ꙃ -- CYRILLIC CAPITAL LETTER DZELO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,133), Bry_.New_by_ints(234,153,132)) // ꙅ -> Ꙅ -- CYRILLIC CAPITAL LETTER REVERSED DZE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,135), Bry_.New_by_ints(234,153,134)) // ꙇ -> Ꙇ -- CYRILLIC CAPITAL LETTER IOTA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,137), Bry_.New_by_ints(234,153,136)) // ꙉ -> Ꙉ -- CYRILLIC CAPITAL LETTER DJERV +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,139), Bry_.New_by_ints(234,153,138)) // ꙋ -> Ꙋ -- CYRILLIC CAPITAL LETTER MONOGRAPH UK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,141), Bry_.New_by_ints(234,153,140)) // ꙍ -> Ꙍ -- CYRILLIC CAPITAL LETTER BROAD OMEGA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,143), Bry_.New_by_ints(234,153,142)) // ꙏ -> Ꙏ -- CYRILLIC CAPITAL LETTER NEUTRAL YER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,145), Bry_.New_by_ints(234,153,144)) // ꙑ -> Ꙑ -- CYRILLIC CAPITAL LETTER YERU WITH BACK YER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,147), Bry_.New_by_ints(234,153,146)) // ꙓ -> Ꙓ -- CYRILLIC CAPITAL LETTER IOTIFIED YAT +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,149), Bry_.New_by_ints(234,153,148)) // ꙕ -> Ꙕ -- CYRILLIC CAPITAL LETTER REVERSED YU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,151), Bry_.New_by_ints(234,153,150)) // ꙗ -> Ꙗ -- CYRILLIC CAPITAL LETTER IOTIFIED A +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,153), Bry_.New_by_ints(234,153,152)) // ꙙ -> Ꙙ -- CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,155), Bry_.New_by_ints(234,153,154)) // ꙛ -> Ꙛ -- CYRILLIC CAPITAL LETTER BLENDED YUS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,157), Bry_.New_by_ints(234,153,156)) // ꙝ -> Ꙝ -- CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITTLE YUS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,159), Bry_.New_by_ints(234,153,158)) // ꙟ -> Ꙟ -- CYRILLIC CAPITAL LETTER YN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,161), Bry_.New_by_ints(234,153,160)) // ꙡ -> Ꙡ -- CYRILLIC CAPITAL LETTER REVERSED TSE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,163), Bry_.New_by_ints(234,153,162)) // ꙣ -> Ꙣ -- CYRILLIC CAPITAL LETTER SOFT DE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,165), Bry_.New_by_ints(234,153,164)) // ꙥ -> Ꙥ -- CYRILLIC CAPITAL LETTER SOFT EL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,167), Bry_.New_by_ints(234,153,166)) // ꙧ -> Ꙧ -- CYRILLIC CAPITAL LETTER SOFT EM +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,169), Bry_.New_by_ints(234,153,168)) // ꙩ -> Ꙩ -- CYRILLIC CAPITAL LETTER MONOCULAR O +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,171), Bry_.New_by_ints(234,153,170)) // ꙫ -> Ꙫ -- CYRILLIC CAPITAL LETTER BINOCULAR O +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,153,173), Bry_.New_by_ints(234,153,172)) // ꙭ -> Ꙭ -- CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,154,129), Bry_.New_by_ints(234,154,128)) // ꚁ -> Ꚁ -- CYRILLIC CAPITAL LETTER DWE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,154,131), Bry_.New_by_ints(234,154,130)) // ꚃ -> Ꚃ -- CYRILLIC CAPITAL LETTER DZWE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,154,133), Bry_.New_by_ints(234,154,132)) // ꚅ -> Ꚅ -- CYRILLIC CAPITAL LETTER ZHWE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,154,135), Bry_.New_by_ints(234,154,134)) // ꚇ -> Ꚇ -- CYRILLIC CAPITAL LETTER CCHE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,154,137), Bry_.New_by_ints(234,154,136)) // ꚉ -> Ꚉ -- CYRILLIC CAPITAL LETTER DZZE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,154,139), Bry_.New_by_ints(234,154,138)) // ꚋ -> Ꚋ -- CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,154,141), Bry_.New_by_ints(234,154,140)) // ꚍ -> Ꚍ -- CYRILLIC CAPITAL LETTER TWE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,154,143), Bry_.New_by_ints(234,154,142)) // ꚏ -> Ꚏ -- CYRILLIC CAPITAL LETTER TSWE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,154,145), Bry_.New_by_ints(234,154,144)) // ꚑ -> Ꚑ -- CYRILLIC CAPITAL LETTER TSSE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,154,147), Bry_.New_by_ints(234,154,146)) // ꚓ -> Ꚓ -- CYRILLIC CAPITAL LETTER TCHE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,154,149), Bry_.New_by_ints(234,154,148)) // ꚕ -> Ꚕ -- CYRILLIC CAPITAL LETTER HWE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,154,151), Bry_.New_by_ints(234,154,150)) // ꚗ -> Ꚗ -- CYRILLIC CAPITAL LETTER SHWE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,156,163), Bry_.New_by_ints(234,156,162)) // ꜣ -> Ꜣ -- LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,156,165), Bry_.New_by_ints(234,156,164)) // ꜥ -> Ꜥ -- LATIN CAPITAL LETTER EGYPTOLOGICAL AIN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,156,167), Bry_.New_by_ints(234,156,166)) // ꜧ -> Ꜧ -- LATIN CAPITAL LETTER HENG +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,156,169), Bry_.New_by_ints(234,156,168)) // ꜩ -> Ꜩ -- LATIN CAPITAL LETTER TZ +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,156,171), Bry_.New_by_ints(234,156,170)) // ꜫ -> Ꜫ -- LATIN CAPITAL LETTER TRESILLO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,156,173), Bry_.New_by_ints(234,156,172)) // ꜭ -> Ꜭ -- LATIN CAPITAL LETTER CUATRILLO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,156,175), Bry_.New_by_ints(234,156,174)) // ꜯ -> Ꜯ -- LATIN CAPITAL LETTER CUATRILLO WITH COMMA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,156,179), Bry_.New_by_ints(234,156,178)) // ꜳ -> Ꜳ -- LATIN CAPITAL LETTER AA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,156,181), Bry_.New_by_ints(234,156,180)) // ꜵ -> Ꜵ -- LATIN CAPITAL LETTER AO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,156,183), Bry_.New_by_ints(234,156,182)) // ꜷ -> Ꜷ -- LATIN CAPITAL LETTER AU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,156,185), Bry_.New_by_ints(234,156,184)) // ꜹ -> Ꜹ -- LATIN CAPITAL LETTER AV +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,156,187), Bry_.New_by_ints(234,156,186)) // ꜻ -> Ꜻ -- LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,156,189), Bry_.New_by_ints(234,156,188)) // ꜽ -> Ꜽ -- LATIN CAPITAL LETTER AY +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,156,191), Bry_.New_by_ints(234,156,190)) // ꜿ -> Ꜿ -- LATIN CAPITAL LETTER REVERSED C WITH DOT +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,129), Bry_.New_by_ints(234,157,128)) // ꝁ -> Ꝁ -- LATIN CAPITAL LETTER K WITH STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,131), Bry_.New_by_ints(234,157,130)) // ꝃ -> Ꝃ -- LATIN CAPITAL LETTER K WITH DIAGONAL STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,133), Bry_.New_by_ints(234,157,132)) // ꝅ -> Ꝅ -- LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,135), Bry_.New_by_ints(234,157,134)) // ꝇ -> Ꝇ -- LATIN CAPITAL LETTER BROKEN L +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,137), Bry_.New_by_ints(234,157,136)) // ꝉ -> Ꝉ -- LATIN CAPITAL LETTER L WITH HIGH STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,139), Bry_.New_by_ints(234,157,138)) // ꝋ -> Ꝋ -- LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,141), Bry_.New_by_ints(234,157,140)) // ꝍ -> Ꝍ -- LATIN CAPITAL LETTER O WITH LOOP +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,143), Bry_.New_by_ints(234,157,142)) // ꝏ -> Ꝏ -- LATIN CAPITAL LETTER OO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,145), Bry_.New_by_ints(234,157,144)) // ꝑ -> Ꝑ -- LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,147), Bry_.New_by_ints(234,157,146)) // ꝓ -> Ꝓ -- LATIN CAPITAL LETTER P WITH FLOURISH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,149), Bry_.New_by_ints(234,157,148)) // ꝕ -> Ꝕ -- LATIN CAPITAL LETTER P WITH SQUIRREL TAIL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,151), Bry_.New_by_ints(234,157,150)) // ꝗ -> Ꝗ -- LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,153), Bry_.New_by_ints(234,157,152)) // ꝙ -> Ꝙ -- LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,155), Bry_.New_by_ints(234,157,154)) // ꝛ -> Ꝛ -- LATIN CAPITAL LETTER R ROTUNDA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,157), Bry_.New_by_ints(234,157,156)) // ꝝ -> Ꝝ -- LATIN CAPITAL LETTER RUM ROTUNDA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,159), Bry_.New_by_ints(234,157,158)) // ꝟ -> Ꝟ -- LATIN CAPITAL LETTER V WITH DIAGONAL STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,161), Bry_.New_by_ints(234,157,160)) // ꝡ -> Ꝡ -- LATIN CAPITAL LETTER VY +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,163), Bry_.New_by_ints(234,157,162)) // ꝣ -> Ꝣ -- LATIN CAPITAL LETTER VISIGOTHIC Z +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,165), Bry_.New_by_ints(234,157,164)) // ꝥ -> Ꝥ -- LATIN CAPITAL LETTER THORN WITH STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,167), Bry_.New_by_ints(234,157,166)) // ꝧ -> Ꝧ -- LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,169), Bry_.New_by_ints(234,157,168)) // ꝩ -> Ꝩ -- LATIN CAPITAL LETTER VEND +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,171), Bry_.New_by_ints(234,157,170)) // ꝫ -> Ꝫ -- LATIN CAPITAL LETTER ET +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,173), Bry_.New_by_ints(234,157,172)) // ꝭ -> Ꝭ -- LATIN CAPITAL LETTER IS +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,175), Bry_.New_by_ints(234,157,174)) // ꝯ -> Ꝯ -- LATIN CAPITAL LETTER CON +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,186), Bry_.New_by_ints(234,157,185)) // ꝺ -> Ꝺ -- LATIN CAPITAL LETTER INSULAR D +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,188), Bry_.New_by_ints(234,157,187)) // ꝼ -> Ꝼ -- LATIN CAPITAL LETTER INSULAR F +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,157,191), Bry_.New_by_ints(234,157,190)) // ꝿ -> Ꝿ -- LATIN CAPITAL LETTER TURNED INSULAR G +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,158,129), Bry_.New_by_ints(234,158,128)) // ꞁ -> Ꞁ -- LATIN CAPITAL LETTER TURNED L +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,158,131), Bry_.New_by_ints(234,158,130)) // ꞃ -> Ꞃ -- LATIN CAPITAL LETTER INSULAR R +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,158,133), Bry_.New_by_ints(234,158,132)) // ꞅ -> Ꞅ -- LATIN CAPITAL LETTER INSULAR S +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,158,135), Bry_.New_by_ints(234,158,134)) // ꞇ -> Ꞇ -- LATIN CAPITAL LETTER INSULAR T +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,158,140), Bry_.New_by_ints(234,158,139)) // ꞌ -> Ꞌ -- LATIN CAPITAL LETTER SALTILLO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,158,145), Bry_.New_by_ints(234,158,144)) // ꞑ -> Ꞑ -- LATIN CAPITAL LETTER N WITH DESCENDER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,158,147), Bry_.New_by_ints(234,158,146)) // ꞓ -> Ꞓ -- LATIN CAPITAL LETTER C WITH BAR +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,158,161), Bry_.New_by_ints(234,158,160)) // ꞡ -> Ꞡ -- LATIN CAPITAL LETTER G WITH OBLIQUE STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,158,163), Bry_.New_by_ints(234,158,162)) // ꞣ -> Ꞣ -- LATIN CAPITAL LETTER K WITH OBLIQUE STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,158,165), Bry_.New_by_ints(234,158,164)) // ꞥ -> Ꞥ -- LATIN CAPITAL LETTER N WITH OBLIQUE STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,158,167), Bry_.New_by_ints(234,158,166)) // ꞧ -> Ꞧ -- LATIN CAPITAL LETTER R WITH OBLIQUE STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(234,158,169), Bry_.New_by_ints(234,158,168)) // ꞩ -> Ꞩ -- LATIN CAPITAL LETTER S WITH OBLIQUE STROKE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,129), Bry_.New_by_ints(239,188,161)) // a -> A -- FULLWIDTH LATIN CAPITAL LETTER A +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,130), Bry_.New_by_ints(239,188,162)) // b -> B -- FULLWIDTH LATIN CAPITAL LETTER B +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,131), Bry_.New_by_ints(239,188,163)) // c -> C -- FULLWIDTH LATIN CAPITAL LETTER C +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,132), Bry_.New_by_ints(239,188,164)) // d -> D -- FULLWIDTH LATIN CAPITAL LETTER D +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,133), Bry_.New_by_ints(239,188,165)) // e -> E -- FULLWIDTH LATIN CAPITAL LETTER E +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,134), Bry_.New_by_ints(239,188,166)) // f -> F -- FULLWIDTH LATIN CAPITAL LETTER F +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,135), Bry_.New_by_ints(239,188,167)) // g -> G -- FULLWIDTH LATIN CAPITAL LETTER G +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,136), Bry_.New_by_ints(239,188,168)) // h -> H -- FULLWIDTH LATIN CAPITAL LETTER H +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,137), Bry_.New_by_ints(239,188,169)) // i -> I -- FULLWIDTH LATIN CAPITAL LETTER I +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,138), Bry_.New_by_ints(239,188,170)) // j -> J -- FULLWIDTH LATIN CAPITAL LETTER J +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,139), Bry_.New_by_ints(239,188,171)) // k -> K -- FULLWIDTH LATIN CAPITAL LETTER K +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,140), Bry_.New_by_ints(239,188,172)) // l -> L -- FULLWIDTH LATIN CAPITAL LETTER L +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,141), Bry_.New_by_ints(239,188,173)) // m -> M -- FULLWIDTH LATIN CAPITAL LETTER M +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,142), Bry_.New_by_ints(239,188,174)) // n -> N -- FULLWIDTH LATIN CAPITAL LETTER N +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,143), Bry_.New_by_ints(239,188,175)) // o -> O -- FULLWIDTH LATIN CAPITAL LETTER O +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,144), Bry_.New_by_ints(239,188,176)) // p -> P -- FULLWIDTH LATIN CAPITAL LETTER P +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,145), Bry_.New_by_ints(239,188,177)) // q -> Q -- FULLWIDTH LATIN CAPITAL LETTER Q +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,146), Bry_.New_by_ints(239,188,178)) // r -> R -- FULLWIDTH LATIN CAPITAL LETTER R +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,147), Bry_.New_by_ints(239,188,179)) // s -> S -- FULLWIDTH LATIN CAPITAL LETTER S +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,148), Bry_.New_by_ints(239,188,180)) // t -> T -- FULLWIDTH LATIN CAPITAL LETTER T +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,149), Bry_.New_by_ints(239,188,181)) // u -> U -- FULLWIDTH LATIN CAPITAL LETTER U +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,150), Bry_.New_by_ints(239,188,182)) // v -> V -- FULLWIDTH LATIN CAPITAL LETTER V +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,151), Bry_.New_by_ints(239,188,183)) // w -> W -- FULLWIDTH LATIN CAPITAL LETTER W +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,152), Bry_.New_by_ints(239,188,184)) // x -> X -- FULLWIDTH LATIN CAPITAL LETTER X +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,153), Bry_.New_by_ints(239,188,185)) // y -> Y -- FULLWIDTH LATIN CAPITAL LETTER Y +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(239,189,154), Bry_.New_by_ints(239,188,186)) // z -> Z -- FULLWIDTH LATIN CAPITAL LETTER Z +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,168), Bry_.New_by_ints(240,144,144,128)) // 𐐨 -> 𐐀 -- DESERET CAPITAL LETTER LONG I +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,169), Bry_.New_by_ints(240,144,144,129)) // 𐐩 -> 𐐁 -- DESERET CAPITAL LETTER LONG E +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,170), Bry_.New_by_ints(240,144,144,130)) // 𐐪 -> 𐐂 -- DESERET CAPITAL LETTER LONG A +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,171), Bry_.New_by_ints(240,144,144,131)) // 𐐫 -> 𐐃 -- DESERET CAPITAL LETTER LONG AH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,172), Bry_.New_by_ints(240,144,144,132)) // 𐐬 -> 𐐄 -- DESERET CAPITAL LETTER LONG O +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,173), Bry_.New_by_ints(240,144,144,133)) // 𐐭 -> 𐐅 -- DESERET CAPITAL LETTER LONG OO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,174), Bry_.New_by_ints(240,144,144,134)) // 𐐮 -> 𐐆 -- DESERET CAPITAL LETTER SHORT I +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,175), Bry_.New_by_ints(240,144,144,135)) // 𐐯 -> 𐐇 -- DESERET CAPITAL LETTER SHORT E +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,176), Bry_.New_by_ints(240,144,144,136)) // 𐐰 -> 𐐈 -- DESERET CAPITAL LETTER SHORT A +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,177), Bry_.New_by_ints(240,144,144,137)) // 𐐱 -> 𐐉 -- DESERET CAPITAL LETTER SHORT AH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,178), Bry_.New_by_ints(240,144,144,138)) // 𐐲 -> 𐐊 -- DESERET CAPITAL LETTER SHORT O +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,179), Bry_.New_by_ints(240,144,144,139)) // 𐐳 -> 𐐋 -- DESERET CAPITAL LETTER SHORT OO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,180), Bry_.New_by_ints(240,144,144,140)) // 𐐴 -> 𐐌 -- DESERET CAPITAL LETTER AY +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,181), Bry_.New_by_ints(240,144,144,141)) // 𐐵 -> 𐐍 -- DESERET CAPITAL LETTER OW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,182), Bry_.New_by_ints(240,144,144,142)) // 𐐶 -> 𐐎 -- DESERET CAPITAL LETTER WU +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,183), Bry_.New_by_ints(240,144,144,143)) // 𐐷 -> 𐐏 -- DESERET CAPITAL LETTER YEE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,184), Bry_.New_by_ints(240,144,144,144)) // 𐐸 -> 𐐐 -- DESERET CAPITAL LETTER H +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,185), Bry_.New_by_ints(240,144,144,145)) // 𐐹 -> 𐐑 -- DESERET CAPITAL LETTER PEE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,186), Bry_.New_by_ints(240,144,144,146)) // 𐐺 -> 𐐒 -- DESERET CAPITAL LETTER BEE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,187), Bry_.New_by_ints(240,144,144,147)) // 𐐻 -> 𐐓 -- DESERET CAPITAL LETTER TEE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,188), Bry_.New_by_ints(240,144,144,148)) // 𐐼 -> 𐐔 -- DESERET CAPITAL LETTER DEE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,189), Bry_.New_by_ints(240,144,144,149)) // 𐐽 -> 𐐕 -- DESERET CAPITAL LETTER CHEE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,190), Bry_.New_by_ints(240,144,144,150)) // 𐐾 -> 𐐖 -- DESERET CAPITAL LETTER JEE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,144,191), Bry_.New_by_ints(240,144,144,151)) // 𐐿 -> 𐐗 -- DESERET CAPITAL LETTER KAY +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,145,128), Bry_.New_by_ints(240,144,144,152)) // 𐑀 -> 𐐘 -- DESERET CAPITAL LETTER GAY +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,145,129), Bry_.New_by_ints(240,144,144,153)) // 𐑁 -> 𐐙 -- DESERET CAPITAL LETTER EF +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,145,130), Bry_.New_by_ints(240,144,144,154)) // 𐑂 -> 𐐚 -- DESERET CAPITAL LETTER VEE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,145,131), Bry_.New_by_ints(240,144,144,155)) // 𐑃 -> 𐐛 -- DESERET CAPITAL LETTER ETH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,145,132), Bry_.New_by_ints(240,144,144,156)) // 𐑄 -> 𐐜 -- DESERET CAPITAL LETTER THEE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,145,133), Bry_.New_by_ints(240,144,144,157)) // 𐑅 -> 𐐝 -- DESERET CAPITAL LETTER ES +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,145,134), Bry_.New_by_ints(240,144,144,158)) // 𐑆 -> 𐐞 -- DESERET CAPITAL LETTER ZEE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,145,135), Bry_.New_by_ints(240,144,144,159)) // 𐑇 -> 𐐟 -- DESERET CAPITAL LETTER ESH +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,145,136), Bry_.New_by_ints(240,144,144,160)) // 𐑈 -> 𐐠 -- DESERET CAPITAL LETTER ZHEE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,145,137), Bry_.New_by_ints(240,144,144,161)) // 𐑉 -> 𐐡 -- DESERET CAPITAL LETTER ER +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,145,138), Bry_.New_by_ints(240,144,144,162)) // 𐑊 -> 𐐢 -- DESERET CAPITAL LETTER EL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,145,139), Bry_.New_by_ints(240,144,144,163)) // 𐑋 -> 𐐣 -- DESERET CAPITAL LETTER EM +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,145,140), Bry_.New_by_ints(240,144,144,164)) // 𐑌 -> 𐐤 -- DESERET CAPITAL LETTER EN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,145,141), Bry_.New_by_ints(240,144,144,165)) // 𐑍 -> 𐐥 -- DESERET CAPITAL LETTER ENG +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,145,142), Bry_.New_by_ints(240,144,144,166)) // 𐑎 -> 𐐦 -- DESERET CAPITAL LETTER OI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(240,144,145,143), Bry_.New_by_ints(240,144,144,167)) // 𐑏 -> 𐐧 -- DESERET CAPITAL LETTER EW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_upper, Bry_.New_by_ints(196,177), Bry_.New_by_ints(73)) // ı -> I -- LATIN SMALL LETTER DOTLESS I +, Xol_case_itm_.new_(Xol_case_itm_.Tid_upper, Bry_.New_by_ints(197,191), Bry_.New_by_ints(83)) // ſ -> S -- LATIN SMALL LETTER LONG S +, Xol_case_itm_.new_(Xol_case_itm_.Tid_upper, Bry_.New_by_ints(205,133), Bry_.New_by_ints(206,153)) // ͅ -> Ι -- GREEK NON-SPACING IOTA BELOW +, Xol_case_itm_.new_(Xol_case_itm_.Tid_upper, Bry_.New_by_ints(206,185), Bry_.New_by_ints(206,153)) // ι -> Ι -- GREEK SMALL LETTER IOTA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_upper, Bry_.New_by_ints(207,130), Bry_.New_by_ints(206,163)) // ς -> Σ -- GREEK SMALL LETTER FINAL SIGMA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_upper, Bry_.New_by_ints(207,144), Bry_.New_by_ints(206,146)) // ϐ -> Β -- GREEK SMALL LETTER CURLED BETA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_upper, Bry_.New_by_ints(207,145), Bry_.New_by_ints(206,152)) // ϑ -> Θ -- GREEK SMALL LETTER SCRIPT THETA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_upper, Bry_.New_by_ints(207,149), Bry_.New_by_ints(206,166)) // ϕ -> Φ -- GREEK SMALL LETTER SCRIPT PHI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_upper, Bry_.New_by_ints(207,150), Bry_.New_by_ints(206,160)) // ϖ -> Π -- GREEK SMALL LETTER OMEGA PI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_upper, Bry_.New_by_ints(207,176), Bry_.New_by_ints(206,154)) // ϰ -> Κ -- GREEK SMALL LETTER SCRIPT KAPPA +, Xol_case_itm_.new_(Xol_case_itm_.Tid_upper, Bry_.New_by_ints(207,177), Bry_.New_by_ints(206,161)) // ϱ -> Ρ -- GREEK SMALL LETTER TAILED RHO +, Xol_case_itm_.new_(Xol_case_itm_.Tid_upper, Bry_.New_by_ints(207,181), Bry_.New_by_ints(206,149)) // ϵ -> Ε -- GREEK LUNATE EPSILON SYMBOL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_upper, Bry_.New_by_ints(225,186,155), Bry_.New_by_ints(225,185,160)) // ẛ -> Ṡ -- LATIN SMALL LETTER LONG S WITH DOT ABOVE +, Xol_case_itm_.new_(Xol_case_itm_.Tid_upper, Bry_.New_by_ints(225,190,190), Bry_.New_by_ints(206,153)) // ι -> Ι -- GREEK PROSGEGRAMMENI +, Xol_case_itm_.new_(Xol_case_itm_.Tid_lower, Bry_.New_by_ints(196,176), Bry_.New_by_ints(105)) // İ -> i -- LATIN CAPITAL LETTER I DOT +, Xol_case_itm_.new_(Xol_case_itm_.Tid_lower, Bry_.New_by_ints(199,133), Bry_.New_by_ints(199,134)) // Dž -> dž -- LATIN LETTER CAPITAL D SMALL Z HACEK +, Xol_case_itm_.new_(Xol_case_itm_.Tid_lower, Bry_.New_by_ints(199,136), Bry_.New_by_ints(199,137)) // Lj -> lj -- LATIN LETTER CAPITAL L SMALL J +, Xol_case_itm_.new_(Xol_case_itm_.Tid_lower, Bry_.New_by_ints(199,139), Bry_.New_by_ints(199,140)) // Nj -> nj -- LATIN LETTER CAPITAL N SMALL J +, Xol_case_itm_.new_(Xol_case_itm_.Tid_lower, Bry_.New_by_ints(199,178), Bry_.New_by_ints(199,179)) // Dz -> dz -- LATIN CAPITAL LETTER D WITH SMALL LETTER Z +, Xol_case_itm_.new_(Xol_case_itm_.Tid_lower, Bry_.New_by_ints(206,153), Bry_.New_by_ints(206,185)) // Ι -> ι -- GREEK CAPITAL LETTER IOTA; NOTE: reversed; PAGE:en.d:ἀρχιερεύς DATE:2014-09-02 +, Xol_case_itm_.new_(Xol_case_itm_.Tid_lower, Bry_.New_by_ints(207,180), Bry_.New_by_ints(206,184)) // ϴ -> θ -- GREEK CAPITAL THETA SYMBOL +, Xol_case_itm_.new_(Xol_case_itm_.Tid_lower, Bry_.New_by_ints(225,186,158), Bry_.New_by_ints(195,159)) // ẞ -> ß -- LATIN CAPITAL LETTER SHARP S +, Xol_case_itm_.new_(Xol_case_itm_.Tid_lower, Bry_.New_by_ints(226,132,166), Bry_.New_by_ints(207,137)) // Ω -> ω -- OHM +, Xol_case_itm_.new_(Xol_case_itm_.Tid_lower, Bry_.New_by_ints(226,132,170), Bry_.New_by_ints(107)) // K -> k -- DEGREES KELVIN +, Xol_case_itm_.new_(Xol_case_itm_.Tid_lower, Bry_.New_by_ints(226,132,171), Bry_.New_by_ints(195,165)) // Å -> å -- ANGSTROM UNIT +, Xol_case_itm_.new_(Xol_case_itm_.Tid_upper, Bry_.New_by_ints(194,181), Bry_.New_by_ints(206,156)) // µ -> Μ -- MICRO SIGN; SWAPPED +, Xol_case_itm_.new_(Xol_case_itm_.Tid_both, Bry_.New_by_ints(206,188), Bry_.New_by_ints(206,156)) // μ -> Μ -- GREEK CAPITAL LETTER MU; CONSOLIDATED +//, Xol_case_itm_.new_(Xol_case_itm_.Tid_upper, Bry_.New_by_ints(206,188), Bry_.New_by_ints(206,156)) // μ -> Μ -- GREEK SMALL LETTER MU +}; + rv.Add_bulk(itms); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/langs/cases/Xol_case_mgr_tst.java b/400_xowa/src/gplx/xowa/langs/cases/Xol_case_mgr_tst.java index a27517de8..ea944e963 100644 --- a/400_xowa/src/gplx/xowa/langs/cases/Xol_case_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/langs/cases/Xol_case_mgr_tst.java @@ -13,3 +13,144 @@ 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.langs.cases; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import org.junit.*; import gplx.core.strings.*; +public class Xol_case_mgr_tst { + @Before public void init() {fxt.Clear();} private Xol_case_mgr_fxt fxt = new Xol_case_mgr_fxt(); + @Test public void Mw_parse() { + fxt.parse_mw__tst(fxt.itm_both_("A", "a"), fxt.itm_both_("B", "b")); + } + @Test public void Xo_parse() { + fxt.parse_xo__tst(fxt.Init_ltrs_raw(), fxt.itm_both_("a", "A"), fxt.itm_upper_("b", "B"), fxt.itm_lower_("C", "c")); + } + @Test public void Upper_a() {fxt.Init_ltrs().Upper("aAaz", "AAAz");} + @Test public void Upper_ab() {fxt.Init_ltrs().Upper("abac", "ABAc");} + @Test public void Lower_a() {fxt.Init_ltrs().Lower("aAaZ", "aaaZ");} + @Test public void Lower_ac() {fxt.Init_ltrs().Lower("ABAC", "aBac");} + @Test public void Upper_1st() { + fxt.Init_ltrs_universal(); + fxt.Test_reuse_1st_upper("a", "A"); + fxt.Test_reuse_1st_upper("abc", "Abc"); + fxt.Test_reuse_1st_upper(""); + fxt.Test_reuse_1st_upper("Abc"); + fxt.Test_reuse_1st_upper("é", "É"); + fxt.Test_reuse_1st_upper("É"); + fxt.Lower("Ι", "ι"); // PURPOSE:test reversal; PAGE:en.d:ἀρχιερεύς DATE:2014-09-02 + } + @Test public void Turkish_redirect() { // PURPOSE: lowercase redirect should match uppercase for asymmetric brys; PAGE:tr.w:Zvishavane DATE:2015-09-07 + Hash_adp_bry hash = Hash_adp_bry.c__u8(Bool_.N, Xol_case_mgr_.U8()); + byte[] upper = Bry_.new_u8("YÖNLENDİRME"); + byte[] lower = Bry_.new_u8("yönlendirme"); + hash.Add(upper, upper); // add upper to hash + Tfds.Eq_bry(upper, (byte[])hash.Get_by_bry(lower)); // get upper by using lower + } +// @Test public void Hack() { +// Xol_case_itm[] ary = Xol_case_mgr_.Utf_8; +// Bry_bfr bfr = Bry_bfr_.New(); +// for (int i = 0; i < ary.length; i++) { +// Xol_case_itm itm = ary[i]; +// bfr.Add_str_a7("xo|"); +// bfr.Add_bry_comma(itm.Src_ary()).Add_byte_pipe(); +// bfr.Add_bry_comma(itm.Trg_ary()).Add_byte_nl(); +// } +// Io_mgr.Instance.SaveFilStr("C:\\test1.txt", bfr.To_str_and_clear()); +// } +} +class Xol_case_mgr_fxt { + private Xol_case_mgr case_mgr = Xol_case_mgr_.new_(); private String_bldr sb = String_bldr_.new_(); + public void Clear() {case_mgr.Clear();} + public Xol_case_itm_bry itm_both_(String src, String trg) {return new Xol_case_itm_bry(Xol_case_itm_.Tid_both , Bry_.new_u8(src), Bry_.new_u8(trg));} + public Xol_case_itm_bry itm_upper_(String src, String trg) {return new Xol_case_itm_bry(Xol_case_itm_.Tid_upper, Bry_.new_u8(src), Bry_.new_u8(trg));} + public Xol_case_itm_bry itm_lower_(String src, String trg) {return new Xol_case_itm_bry(Xol_case_itm_.Tid_lower, Bry_.new_u8(src), Bry_.new_u8(trg));} + public String Init_ltrs_raw() { + return String_.Concat_lines_nl + ( "0|a|A" + , "1|b|B" + , "2|C|c" + ); + } + public Xol_case_mgr_fxt Init_ltrs() { + case_mgr = Xol_case_mgr_.new_(); + case_mgr.Add_bulk(Bry_.new_u8(Init_ltrs_raw())); + return this; + } + public Xol_case_mgr_fxt Init_ltrs_universal() { + case_mgr = Xol_case_mgr_.U8(); + return this; + } + public Xol_case_mgr_fxt Upper(String raw_str, String expd) {return Case_build(Bool_.Y, raw_str, expd);} + public Xol_case_mgr_fxt Lower(String raw_str, String expd) {return Case_build(Bool_.N, raw_str, expd);} + public Xol_case_mgr_fxt Case_build(boolean upper, String raw_str, String expd) { + byte[] raw = Bry_.new_u8(raw_str); + byte[] actl = case_mgr.Case_build(upper, raw, 0, raw.length); + Tfds.Eq(expd, String_.new_u8(actl)); + return this; + } + public void parse_xo__tst(String raw, Xol_case_itm_bry... expd) { + Tfds.Eq_str_lines(Xto_str(expd), Xto_str(Xol_case_itm_.parse_xo_(Bry_.new_u8(raw)))); + } + public void parse_mw__tst(Xol_case_itm_bry... expd) { + String raw = raw_(expd); + Xol_case_itm[] actl = Xol_case_itm_.parse_mw_(Bry_.new_u8(raw)); + Tfds.Eq_str_lines(Xto_str(expd), Xto_str(actl)); + } + public String Xto_str(Xol_case_itm[] ary) { + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) { + Xol_case_itm itm = ary[i]; + sb.Add(Byte_.To_str(itm.Tid())).Add_char_pipe().Add(String_.new_u8(itm.Src_ary())).Add_char_pipe().Add(String_.new_u8(itm.Trg_ary())).Add_char_nl(); + } + return sb.To_str_and_clear(); + } + public String raw_(Xol_case_itm_bry[] itms) { + int itms_len = itms.length; + uppers_list.Clear(); lowers_list.Clear(); + for (int i = 0; i < itms_len; i++) { + Xol_case_itm_bry itm = itms[i]; + String src = String_.new_u8(itm.Src_ary()); + String trg = String_.new_u8(itm.Trg_ary()); + switch (itm.Tid()) { + case Xol_case_itm_.Tid_both: + uppers_list.Add(trg); uppers_list.Add(src); + lowers_list.Add(src); lowers_list.Add(trg); + break; + } + } + return raw_str_(uppers_list.To_str_ary(), lowers_list.To_str_ary()); + } List_adp uppers_list = List_adp_.New(), lowers_list = List_adp_.New(); + String raw_str_(String[] uppers, String[] lowers) { + sb.Add("a:2:{s:14:\"wikiUpperChars\";a:1046:{"); + raw_ary(sb, uppers); + sb.Add("}"); + sb.Add("s:14:\"wikiLowerChars\";a:1038:{"); + raw_ary(sb, lowers); + sb.Add("}}"); + return sb.To_str_and_clear(); + } + private void raw_ary(String_bldr sb, String[] ary) { + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) { + String itm = ary[i]; + int itm_len = String_.Len(itm); + sb.Add_fmt("s:{0}:\"{1}\";", itm_len, itm); + } + } + public void Test_reuse_1st_upper(String raw) {Test_reuse_1st_upper(raw, null, Bool_.Y);} + public void Test_reuse_1st_upper(String raw, String expd) {Test_reuse_1st_upper(raw, expd, Bool_.N);} + private void Test_reuse_1st_upper(String raw, String expd, boolean expd_is_same) { + byte[] raw_bry = Bry_.new_u8(raw); + byte[] actl_bry = case_mgr.Case_reuse_1st_upper(raw_bry); + String actl_str = String_.new_u8(actl_bry); + boolean actl_is_same = Object_.Eq(raw_bry, actl_bry); // pointers will be same if no change + if (expd_is_same) { + Tfds.Eq_true(actl_is_same, "expd should be same: " + actl_str); + } + else { + Tfds.Eq_true(!actl_is_same, "expd should not be same: " + actl_str); + Tfds.Eq(expd, actl_str, expd); + } + } +} +/* +a:2:{s:14:"wikiUpperChars";a:1046:{s:1:"a";s:1:"A";s:1:"b";}s:14:"wikiLowerChars";a:1038:{s:1:"A";s:1:"a";s:1:"B";}} +*/ \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/langs/commas/Xol_comma_wkr.java b/400_xowa/src/gplx/xowa/langs/commas/Xol_comma_wkr.java index a27517de8..eba00d7e2 100644 --- a/400_xowa/src/gplx/xowa/langs/commas/Xol_comma_wkr.java +++ b/400_xowa/src/gplx/xowa/langs/commas/Xol_comma_wkr.java @@ -13,3 +13,9 @@ 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.langs.commas; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public interface Xol_comma_wkr { + void Evt_lang_changed(Xol_lang_itm lang_itm); + void Comma__itm(Bry_bfr bfr, int itm_idx, int itms_len); + void Comma__end(Bry_bfr bfr); +} diff --git a/400_xowa/src/gplx/xowa/langs/commas/Xol_comma_wkr__add.java b/400_xowa/src/gplx/xowa/langs/commas/Xol_comma_wkr__add.java index a27517de8..a63f1dd30 100644 --- a/400_xowa/src/gplx/xowa/langs/commas/Xol_comma_wkr__add.java +++ b/400_xowa/src/gplx/xowa/langs/commas/Xol_comma_wkr__add.java @@ -13,3 +13,16 @@ 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.langs.commas; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.xowa.langs.msgs.*; +public class Xol_comma_wkr__add implements Xol_comma_wkr { + private byte[] comma_bry = Bry_.new_a7(", "); // needed for TEST + public void Evt_lang_changed(Xol_lang_itm lang_itm) { + this.comma_bry = lang_itm.Msg_mgr().Val_by_bry_or(Bry_.new_a7("comma-separator"), Byte_ascii.Comma_bry); + } + public void Comma__itm(Bry_bfr bfr, int itm_idx, int itms_len) { + if (itm_idx != itms_len - 1) + bfr.Add(comma_bry); + } + public void Comma__end(Bry_bfr bfr) {} +} diff --git a/400_xowa/src/gplx/xowa/langs/durations/Xol_duration_itm.java b/400_xowa/src/gplx/xowa/langs/durations/Xol_duration_itm.java index a27517de8..046fdd495 100644 --- a/400_xowa/src/gplx/xowa/langs/durations/Xol_duration_itm.java +++ b/400_xowa/src/gplx/xowa/langs/durations/Xol_duration_itm.java @@ -13,3 +13,14 @@ 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.langs.durations; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xol_duration_itm { + public Xol_duration_itm(byte tid, String name_str, long seconds) { + this.tid = tid; this.seconds = seconds; + this.name_str = name_str; this.name_bry = Bry_.new_a7(name_str); + } + public byte Tid() {return tid;} private byte tid; + public byte[] Name_bry() {return name_bry;} private byte[] name_bry; + public String Name_str() {return name_str;} private String name_str; + public long Seconds() {return seconds;} private long seconds; +} diff --git a/400_xowa/src/gplx/xowa/langs/durations/Xol_duration_itm_.java b/400_xowa/src/gplx/xowa/langs/durations/Xol_duration_itm_.java index a27517de8..b62bfdc76 100644 --- a/400_xowa/src/gplx/xowa/langs/durations/Xol_duration_itm_.java +++ b/400_xowa/src/gplx/xowa/langs/durations/Xol_duration_itm_.java @@ -13,3 +13,66 @@ 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.langs.durations; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xol_duration_itm_ { + private static final Hash_adp_bry regy = Hash_adp_bry.ci_a7(); // ASCII:MW.consts + public static final byte + Tid_millenia = 0 + , Tid_centuries = 1 + , Tid_decades = 2 + , Tid_years = 3 + , Tid_weeks = 4 + , Tid_days = 5 + , Tid_hours = 6 + , Tid_minutes = 7 + , Tid_seconds = 8 + ; + public static final Xol_duration_itm + Itm_millenia = new_(Tid_millenia , "millenia" , 31556952000L) + , Itm_centuries = new_(Tid_centuries , "centuries" , 3155695200L) + , Itm_decades = new_(Tid_decades , "decades" , 315569520L) + , Itm_years = new_(Tid_years , "years" , 31556952L) // 86400 * (365 + (24 * 3 + 25) / 400) + , Itm_weeks = new_(Tid_weeks , "weeks" , 604800L) + , Itm_days = new_(Tid_days , "days" , 86400L) + , Itm_hours = new_(Tid_hours , "hours" , 3600L) + , Itm_minutes = new_(Tid_minutes , "minutes" , 60L) + , Itm_seconds = new_(Tid_seconds , "seconds" , 1L) + ; + private static Xol_duration_itm new_(byte tid, String name, long factor) { + Xol_duration_itm rv = new Xol_duration_itm(tid, name, factor); + regy.Add(rv.Name_bry(), rv); + return rv; + } + public static final Xol_duration_itm[] Ary_default = new Xol_duration_itm[] + { Itm_millenia + , Itm_centuries + , Itm_decades + , Itm_years + , Itm_weeks + , Itm_days + , Itm_hours + , Itm_minutes + , Itm_seconds + }; + public static Xol_duration_itm[] Xto_itm_ary(Keyval[] kv_ary) { + if (kv_ary == null) return Xol_duration_itm_.Ary_default; + List_adp rv = List_adp_.New(); + int len = kv_ary.length; + for (int i = 0; i < len; i++) { + Keyval kv = kv_ary[i]; + String name = kv.Val_to_str_or_empty(); + Xol_duration_itm itm = (Xol_duration_itm)regy.Get_by(Bry_.new_u8(name)); + if (itm != null) + rv.Add(itm); + } + return (Xol_duration_itm[])rv.To_ary(Xol_duration_itm.class); + } +} +class Xol_duration_itm_sorter implements gplx.core.lists.ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + Xol_duration_itm lhs = (Xol_duration_itm)lhsObj; + Xol_duration_itm rhs = (Xol_duration_itm)rhsObj; + return -Long_.Compare(lhs.Seconds(), rhs.Seconds()); // - to sort from largest to smallest + } + public static final Xol_duration_itm_sorter Instance = new Xol_duration_itm_sorter(); Xol_duration_itm_sorter() {}// TS.static +} diff --git a/400_xowa/src/gplx/xowa/langs/durations/Xol_duration_mgr.java b/400_xowa/src/gplx/xowa/langs/durations/Xol_duration_mgr.java index a27517de8..f4e826580 100644 --- a/400_xowa/src/gplx/xowa/langs/durations/Xol_duration_mgr.java +++ b/400_xowa/src/gplx/xowa/langs/durations/Xol_duration_mgr.java @@ -13,3 +13,80 @@ 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.langs.durations; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.parsers.*; +import gplx.xowa.langs.msgs.*; +public class Xol_duration_mgr { + private Xol_msg_itm[] interval_msgs = null; + private final Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); + private final Bry_fmtr tmp_fmtr = Bry_fmtr.New__tmp(); + public Xol_duration_mgr(Xol_lang_itm lang) {this.lang = lang;} private Xol_lang_itm lang; + public Xol_interval_itm[] Get_duration_intervals(long seconds, Xol_duration_itm[] intervals) { + if (intervals == null) intervals = Xol_duration_itm_.Ary_default; + Array_.Sort(intervals, Xol_duration_itm_sorter.Instance); + int intervals_len = intervals.length; + long val = seconds; + List_adp rv = List_adp_.New(); + for (int i = 0; i < intervals_len; i++) { + Xol_duration_itm itm = intervals[i]; + long itm_seconds = itm.Seconds(); + val = seconds / itm_seconds; + if ( val > 0 + || (i == intervals_len - 1 && rv.Count() == 0) // always add one seg; EX: 40 seconds, but minutes requested -> 0 minutes; DATE:2014-05-10 + ) { + seconds -= val * itm_seconds; + rv.Add(new Xol_interval_itm(itm, val)); + } + } + return (Xol_interval_itm[])rv.To_ary(Xol_interval_itm.class); + } + public byte[] Format_durations(Xop_ctx ctx, long seconds, Xol_duration_itm[] ary) { + if (interval_msgs == null) Format_durations_init(); + Xol_interval_itm[] intervals = Get_duration_intervals(seconds, ary); + int intervals_len = intervals.length; + byte[][] msgs_ary = new byte[intervals_len][]; + for (int i = 0; i < intervals_len; i++) { + Xol_interval_itm interval = intervals[i]; + Xol_msg_itm msg_itm = interval_msgs[interval.Duration_itm().Tid()]; + byte[] msg_bry = msg_itm.Fmt(tmp_bfr, tmp_fmtr, interval.Val()); + msg_bry = ctx.Wiki().Parser_mgr().Main().Parse_text_to_html(ctx, msg_bry); + msgs_ary[i] = msg_bry; + } + return List_to_str(msgs_ary); + } + private byte[] Msg_and, Msg_word_separator, Msg_comma_separator; + private void Format_durations_init() { + Xol_msg_mgr msg_mgr = lang.Msg_mgr(); + int len = Xol_duration_itm_.Ary_default.length; + interval_msgs = new Xol_msg_itm[len]; + for (int i = 0; i < len; i++) { + Xol_duration_itm itm = Xol_duration_itm_.Ary_default[i]; + byte[] msg_key = Bry_.Add(Bry_duration, itm.Name_bry()); + interval_msgs[i] = msg_mgr.Itm_by_key_or_new(msg_key); + } + } private static final byte[] Bry_duration = Bry_.new_a7("duration-"); + private void List_to_str_init() { + Xol_msg_mgr msg_mgr = lang.Msg_mgr(); + Msg_and = msg_mgr.Val_by_str_or_empty("and"); + Msg_word_separator = msg_mgr.Val_by_str_or_empty("word-separator"); + Msg_comma_separator = msg_mgr.Val_by_str_or_empty("comma-separator"); + } + + public byte[] List_to_str(byte[][] segs_ary) { + int len = segs_ary.length; + switch (len) { + case 0: return Bry_.Empty; + case 1: return segs_ary[0]; + default: + if (Msg_and == null) List_to_str_init(); + int last_idx = len - 1; + for (int i = 0; i < last_idx; i++) { + if (i != 0) tmp_bfr.Add(Msg_comma_separator); + tmp_bfr.Add(segs_ary[i]); + } + tmp_bfr.Add(Msg_and).Add(Msg_word_separator).Add(segs_ary[last_idx]); + return tmp_bfr.To_bry_and_clear(); + } + } +} diff --git a/400_xowa/src/gplx/xowa/langs/durations/Xol_interval_itm.java b/400_xowa/src/gplx/xowa/langs/durations/Xol_interval_itm.java index a27517de8..c5371609c 100644 --- a/400_xowa/src/gplx/xowa/langs/durations/Xol_interval_itm.java +++ b/400_xowa/src/gplx/xowa/langs/durations/Xol_interval_itm.java @@ -13,3 +13,18 @@ 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.langs.durations; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xol_interval_itm { + public Xol_interval_itm(Xol_duration_itm duration_itm, long val) {this.duration_itm = duration_itm; this.val = val;} + public Xol_duration_itm Duration_itm() {return duration_itm;} private Xol_duration_itm duration_itm; + public long Val() {return val;} private long val; + public static Keyval[] Xto_kv_ary(Xol_interval_itm[] ary) { + int len = ary.length; + Keyval[] rv = new Keyval[len]; + for (int i = 0; i < len; i++) { + Xol_interval_itm itm = ary[i]; + rv[i] = Keyval_.new_(itm.Duration_itm().Name_str(), (int)itm.Val()); // double for scribunto + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/langs/funcs/Xol_func_itm.java b/400_xowa/src/gplx/xowa/langs/funcs/Xol_func_itm.java index a27517de8..315120ab1 100644 --- a/400_xowa/src/gplx/xowa/langs/funcs/Xol_func_itm.java +++ b/400_xowa/src/gplx/xowa/langs/funcs/Xol_func_itm.java @@ -13,3 +13,23 @@ 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.langs.funcs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.xowa.parsers.tmpls.*; +public class Xol_func_itm { + public byte Tid() {return tid;} private byte tid = Xot_defn_.Tid_null; + public Xot_defn Func() {return func;} private Xot_defn func = Xot_defn_.Null; + public int Colon_pos() {return colon_pos;} private int colon_pos = -1; + public int Subst_bgn() {return subst_bgn;} private int subst_bgn = -1; + public int Subst_end() {return subst_end;} private int subst_end = -1; + public void Clear() { + tid = Xot_defn_.Tid_null; + func = Xot_defn_.Null; + colon_pos = subst_bgn = subst_end = -1; + } + public void Init_by_subst(byte tid, int bgn, int end) {this.tid = tid; this.subst_bgn = bgn; this.subst_end = end;} + public void Func_(Xot_defn v, int colon_pos) { + if (tid == Xot_defn_.Tid_null) tid = Xot_defn_.Tid_func; // only set tid if subst did not set it + this.func = v; + this.colon_pos = colon_pos; + } +} diff --git a/400_xowa/src/gplx/xowa/langs/funcs/Xol_func_regy.java b/400_xowa/src/gplx/xowa/langs/funcs/Xol_func_regy.java index a27517de8..303900595 100644 --- a/400_xowa/src/gplx/xowa/langs/funcs/Xol_func_regy.java +++ b/400_xowa/src/gplx/xowa/langs/funcs/Xol_func_regy.java @@ -13,3 +13,97 @@ 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.langs.funcs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.btries.*; import gplx.core.intls.*; import gplx.core.envs.*; import gplx.xowa.xtns.pfuncs.*; +import gplx.xowa.parsers.tmpls.*; +import gplx.xowa.langs.kwds.*; +public class Xol_func_regy { + private final Xoa_lang_mgr lang_mgr; private final Xol_lang_itm lang; + private final Btrie_slim_mgr cs_trie = Btrie_slim_mgr.cs(), ci_trie = Btrie_slim_mgr.ci_u8(); + public Xol_func_regy(Xoa_lang_mgr lang_mgr, Xol_lang_itm lang) {this.lang_mgr = lang_mgr; this.lang = lang;} + public void Evt_lang_changed(Xol_lang_itm lang) { + Xol_kwd_mgr kwd_mgr = lang.Kwd_mgr(); + ci_trie.Clear(); cs_trie.Clear(); + int[] kwd_ary = Pf_func_.Ary_get(null, !lang.Kwd_mgr__strx()); + int len = kwd_ary.length; + for (int i = 0; i < len; i++) { + int id = kwd_ary[i]; + Xol_kwd_grp list = kwd_mgr.Get_at(id); + if (list == null) { + if (Env_.Mode_testing()) + continue; // TEST: allows partial parsing of $magicWords + else + list = lang_mgr.Lang_en().Kwd_mgr().Get_at(id); // get from fallback language; TODO_OLD: allow other fallback langs besides "English" + } + Reg_defn(kwd_mgr, id, Pf_func_.Get_prototype(id)); + } + } + public void Reg_defn(Xol_kwd_mgr kwd_mgr, int id, Xot_defn defn) { + Xol_kwd_grp grp = kwd_mgr.Get_at(id); if (grp == null) return; + Xol_kwd_itm[] itms = grp.Itms(); if (itms == null) return; + int itms_len = itms.length; + for (int i = 0; i < itms_len; i++) { + byte[] name = itms[i].Val(); + this.Add(name, grp.Case_match(), defn.Clone(id, name)); + } + } + private void Add(byte[] ary, boolean case_match, Xot_defn func) { + if (case_match) + cs_trie.Add_obj(ary, func); + else { + byte[] lower_ary = lang.Case_mgr().Case_build_lower(ary, 0, ary.length); + ci_trie.Add_obj(lower_ary, func); + } + } + public void Find_defn(Xol_func_itm rv, byte[] src, int txt_bgn, int txt_end) { + rv.Clear(); + for (int i = 0; i < 2; i++) { + if (txt_bgn == txt_end) return; // NOTE: true when tmpl_name is either not loaded, or doesn't exist + Xot_defn func = Match_bgn(src, txt_bgn, txt_end); + if (func == null) return; // NOTE: null when tmpl_name is either not loaded, or doesn't exist + byte[] func_name = func.Name(); + int match_pos = func_name.length + txt_bgn; + byte defn_tid = func.Defn_tid(); + switch (defn_tid) { + case Xot_defn_.Tid_func: + if (match_pos == txt_end) // next char is ws (b/c match_pos == txt_end) + rv.Func_(func, -1); + else if (src[match_pos] == Pf_func_.Name_dlm) // next char is : + rv.Func_(func, match_pos); + else { // func is close, but not quite: ex: #ifx: or padlefts: + return; + } + break; + case Xot_defn_.Tid_safesubst: + case Xot_defn_.Tid_subst: + rv.Init_by_subst(defn_tid, txt_bgn, match_pos); + if (match_pos < txt_end) txt_bgn = Bry_find_.Find_fwd_while_not_ws(src, match_pos, txt_end); + break; + case Xot_defn_.Tid_raw: + case Xot_defn_.Tid_msg: + case Xot_defn_.Tid_msgnw: + rv.Init_by_subst(defn_tid, txt_bgn, match_pos); + if (match_pos + 1 < txt_end) // +1 to include ":" (keyword id "raw", not "raw:") + txt_bgn = Bry_find_.Find_fwd_while_not_ws(src, match_pos + 1, txt_end); + break; + default: return; + } + } + return; + } + private Xot_defn Match_bgn(byte[] src, int bgn, int end) { + Object cs_obj = cs_trie.Match_bgn(src, bgn, end); + Xot_defn rv = null; + if (cs_obj != null) { // match found for cs; could be false_match; EX: NAME"+"SPACE and NAME"+"SPACENUMBER + rv = (Xot_defn)cs_obj; + if (rv.Name().length == end - bgn) // func_name matches cur_name; DATE:2013-04-15 + return rv; + // else {} // func_name doesn't match cur_name; continue below; EX: NAME"+"SPACENUMBER passed in and matches NAME"+"SPACE (which is cs); note that NAME"+"SPACENUMBER only exists in ci + } + byte[] ary = lang.Case_mgr().Case_build_lower(src, bgn, end); // NOTE: cannot call Case_reuse_lower b/c some langs (Turkish) may have differently-sized brys between upper and lower; DATE:2017-01-26 + Xot_defn rv_alt = (Xot_defn)ci_trie.Match_bgn(ary, 0, ary.length); + return (rv != null && rv_alt == null) + ? rv // name not found in ci, but name was found in cs; return cs; handles NAME"+"SPACENUMBER + : rv_alt; // else return rv_alt + } +} diff --git a/400_xowa/src/gplx/xowa/langs/genders/Xol_gender.java b/400_xowa/src/gplx/xowa/langs/genders/Xol_gender.java index a27517de8..2bf92e4b7 100644 --- a/400_xowa/src/gplx/xowa/langs/genders/Xol_gender.java +++ b/400_xowa/src/gplx/xowa/langs/genders/Xol_gender.java @@ -13,3 +13,7 @@ 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.langs.genders; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public interface Xol_gender { + byte[] Gender_eval(int gender, byte[] when_m, byte[] when_f, byte[] when_u); +} diff --git a/400_xowa/src/gplx/xowa/langs/genders/Xol_gender_.java b/400_xowa/src/gplx/xowa/langs/genders/Xol_gender_.java index a27517de8..42685f9a2 100644 --- a/400_xowa/src/gplx/xowa/langs/genders/Xol_gender_.java +++ b/400_xowa/src/gplx/xowa/langs/genders/Xol_gender_.java @@ -13,3 +13,20 @@ 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.langs.genders; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.primitives.*; import gplx.core.btries.*; +public class Xol_gender_ { + public static Xol_gender new_by_lang_id(int lang_id) {return Xol_gender__basic.Instance;} + public static final int Tid_male = 0, Tid_female = 1, Tid_unknown = 2; +} +class Xol_gender__basic implements Xol_gender { + public byte[] Gender_eval(int gender, byte[] when_m, byte[] when_f, byte[] when_u) { + switch (gender) { + case Xol_gender_.Tid_male: return when_m; + case Xol_gender_.Tid_female: return when_f; + case Xol_gender_.Tid_unknown: return when_u; + default: throw Err_.new_unimplemented(); + } + } + public static final Xol_gender__basic Instance = new Xol_gender__basic(); Xol_gender__basic() {} +} diff --git a/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar.java b/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar.java index a27517de8..095af7fa7 100644 --- a/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar.java +++ b/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar.java @@ -13,3 +13,7 @@ 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.langs.grammars; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public interface Xol_grammar { + boolean Grammar_eval(Bry_bfr bfr, Xol_lang_itm lang, byte[] word, byte[] type); +} diff --git a/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_.java b/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_.java index a27517de8..3549ee46e 100644 --- a/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_.java +++ b/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_.java @@ -13,3 +13,44 @@ 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.langs.grammars; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.primitives.*; import gplx.core.btries.*; +public class Xol_grammar_ { + public static final byte Tid__max = 9; + public static final byte Tid_genitive = 0, Tid_elative = 1, Tid_partitive = 2, Tid_illative = 3, Tid_inessive = 4, Tid_accusative = 5, Tid_instrumental = 6, Tid_prepositional = 7, Tid_dative = 8, Tid_unknown = Byte_.Max_value_127; + private static final Btrie_slim_mgr Tid_trie = Btrie_slim_mgr.ci_a7() // NOTE:ci.ascii:MW kwds + .Add_str_byte("genitive", Tid_genitive) + .Add_str_byte("elative", Tid_elative) + .Add_str_byte("partitive", Tid_partitive) + .Add_str_byte("illative", Tid_illative) + .Add_str_byte("inessive", Tid_inessive) + .Add_str_byte("accusative", Tid_accusative) + .Add_str_byte("instrumental", Tid_instrumental) + .Add_str_byte("prepositional", Tid_prepositional) + .Add_str_byte("dative", Tid_dative) + ; + public static byte Tid_of_type(byte[] v) { + if (Bry_.Len_eq_0(v)) return Tid_unknown; + Object o = Xol_grammar_.Tid_trie.Match_exact(v, 0, v.length); + return o == null ? Tid_unknown : ((Byte_obj_val)o).Val(); + } + public static Xol_grammar new_by_lang_id(int lang_id) { + switch (lang_id) { + case Xol_lang_stub_.Id_fi: return new Xol_grammar_fi(); + case Xol_lang_stub_.Id_ru: return new Xol_grammar_ru(); + case Xol_lang_stub_.Id_he: return new Xol_grammar_he(); + case Xol_lang_stub_.Id_pl: + case Xol_lang_stub_.Id_cs: // PAGE:cs.q; DATE:2016-09-04 + return Xol_grammar__noop.Instance; + default: return Xol_grammar__unimplemented.Instance; + } + } +} +class Xol_grammar__unimplemented implements Xol_grammar { + public boolean Grammar_eval(Bry_bfr bfr, Xol_lang_itm lang, byte[] word, byte[] type) {return false;} + public static final Xol_grammar__unimplemented Instance = new Xol_grammar__unimplemented(); Xol_grammar__unimplemented() {} +} +class Xol_grammar__noop implements Xol_grammar { + public boolean Grammar_eval(Bry_bfr bfr, Xol_lang_itm lang, byte[] word, byte[] type) {bfr.Add(word); return true;} + public static final Xol_grammar__noop Instance = new Xol_grammar__noop(); Xol_grammar__noop() {} +} diff --git a/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_fi.java b/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_fi.java index a27517de8..11abd6983 100644 --- a/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_fi.java +++ b/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_fi.java @@ -13,3 +13,68 @@ 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.langs.grammars; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.primitives.*; import gplx.core.btries.*; +import gplx.xowa.apps.urls.*; +public class Xol_grammar_fi implements Xol_grammar { + public boolean Vowel_harmony(byte[] word, int word_len) { + // $aou = preg_match( '/[aou][^äöy]*$/i', $word ); + boolean aou_found = false; + for (int i = 0; i < word_len; i++) { + byte b = word[i]; + Object o = trie_vh.Match_bgn_w_byte(b, word, i, word_len); + if (o != null) { + byte vh_type = ((Byte_obj_val)o).Val(); + if (vh_type == Trie_vh_back) + aou_found = true; + else + aou_found = false; + } + } + return aou_found; + } + public boolean Grammar_eval(Bry_bfr bfr, Xol_lang_itm lang, byte[] word, byte[] type) { + if (Bry_.Len_eq_0(word)) return true; // empty_string returns "" + byte tid = Xol_grammar_.Tid_of_type(type); + if (tid == Xol_grammar_.Tid_unknown) {bfr.Add(word); return true;} // unknown type returns word + // PHP: if (isset($wgGrammarForms['fi'][$case][$word])){ return $wgGrammarForms['fi'][$case][$word]; + if (manual_regy == null) { + manual_regy = new Xol_grammar_manual_regy() + .Itms_add(Xol_grammar_.Tid_elative, "Wikiuutiset", "Wikiuutisista"); + } + byte[] manual_repl = manual_regy.Itms_get(tid, word); + if (manual_repl != null) { + bfr.Add(manual_repl); + return true; + } + bfr.Add(word); // NOTE: preemptively add word now; the rest of this function takes "word" and adds other letters to it; + int word_len = word.length; + byte[] lower = lang.Case_mgr().Case_build_lower(word, 0, word_len); + boolean aou = Vowel_harmony(lower, word_len); + // PHP: if ( preg_match( '/wiki$/i', $word ) ) $aou = false; + if (aou && Bry_.Has_at_end(lower, Bry_wiki)) + aou = false; + // PHP: if ( preg_match( '/[bcdfghjklmnpqrstvwxz]$/i', $word ) ) $word .= 'i'; + switch (lower[word_len - 1]) { + case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_f: case Byte_ascii.Ltr_g: + case Byte_ascii.Ltr_h: case Byte_ascii.Ltr_j: case Byte_ascii.Ltr_k: case Byte_ascii.Ltr_l: case Byte_ascii.Ltr_m: + case Byte_ascii.Ltr_n: case Byte_ascii.Ltr_p: case Byte_ascii.Ltr_q: case Byte_ascii.Ltr_r: case Byte_ascii.Ltr_s: + case Byte_ascii.Ltr_t: case Byte_ascii.Ltr_v: case Byte_ascii.Ltr_w: case Byte_ascii.Ltr_x: case Byte_ascii.Ltr_z: + bfr.Add_byte(Byte_ascii.Ltr_i); + break; + } + + switch (tid) { + case Xol_grammar_.Tid_genitive: bfr.Add_byte(Byte_ascii.Ltr_n); break; // case 'genitive': $word .= 'n'; + case Xol_grammar_.Tid_elative: bfr.Add(aou ? Bry_sta_y : Bry_sta_n); break; // case 'elative': $word .= ( $aou ? 'sta' : 'stä' ); + case Xol_grammar_.Tid_partitive: bfr.Add(aou ? Bry_a_y : Bry_a_n); break; // case 'partitive': $word .= ( $aou ? 'a' : 'ä' ); + case Xol_grammar_.Tid_inessive: bfr.Add(aou ? Bry_ssa_y : Bry_ssa_n); break; // case 'inessive': $word .= ( $aou ? 'ssa' : 'ssä' ); + case Xol_grammar_.Tid_illative: bfr.Add_byte(word[word_len - 1]).Add_byte(Byte_ascii.Ltr_n); break;// # Double the last letter and add 'n' + } + return true; + } private static Xol_grammar_manual_regy manual_regy; + private static final byte[] Bry_sta_y = Bry_.new_a7("sta"), Bry_sta_n = Bry_.new_u8("stä"), Bry_a_y = Bry_.new_a7("a"), Bry_a_n = Bry_.new_u8("ä"), Bry_ssa_y = Bry_.new_a7("ssa"), Bry_ssa_n = Bry_.new_u8("ssä"); + static final byte Trie_vh_back = 0, Trie_vh_front = 1; + private static Btrie_slim_mgr trie_vh = Btrie_slim_mgr.cs().Add_str_byte__many(Trie_vh_back, "a", "o", "u").Add_str_byte__many(Trie_vh_front, "ä", "ö", "y"); + private static final byte[] Bry_wiki = Bry_.new_a7("wiki"); +} diff --git a/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_he.java b/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_he.java index a27517de8..948255336 100644 --- a/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_he.java +++ b/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_he.java @@ -13,3 +13,37 @@ 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.langs.grammars; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.btries.*; +public class Xol_grammar_he implements Xol_grammar { + public boolean Grammar_eval(Bry_bfr bfr, Xol_lang_itm lang, byte[] word, byte[] type) { + // if ( isset( $wgGrammarForms['he'][$case][$word] ) ) return $wgGrammarForms['he'][$case][$word]; // TODO_OLD: implement global $wgGrammarForms; WHEN: need to find he.w entries for DefaultSettings.php + if (hash.Get_as_int_or(type, -1) == Tid__prefixed) { + // Duplicate the "Waw" if prefixed, but not if it is already double. + if ( Bry_.Match(word, 0, 2, Bry__waw__0) // "ו" + && !Bry_.Match(word, 0, 4, Bry__waw__1) // "וו" + ) + word = Bry_.Add(Bry__waw__0, word); + // Remove the "He" article if prefixed + if ( Bry_.Match(word, 0, 2, Bry__he__0)) // "ה" + word = Bry_.Mid(word, 2); + // Add a hyphen (maqaf) before non-Hebrew letters. + if ( Bry_.Match(word, 0, 2, Bry__maqaf__0) // "א" + || Bry_.Compare(word, 0, 2, Bry__maqaf__1, 0, 2) == CompareAble_.More // "ת" + ) + word = Bry_.Add(Bry__maqaf__2, word); + } + bfr.Add(word); + return true; + } + private static final int Tid__prefixed = 1; + private static final Hash_adp_bry hash = Hash_adp_bry.ci_u8(gplx.xowa.langs.cases.Xol_case_mgr_.U8()) + .Add_str_int("prefixed" , Tid__prefixed) + .Add_str_int("תחילית" , Tid__prefixed) + ; + private static final byte[] + Bry__waw__0 = Bry_.new_u8("ו"), Bry__waw__1 = Bry_.new_u8("וו") + , Bry__he__0 = Bry_.new_u8("ה") + , Bry__maqaf__0 = Bry_.new_u8("א"), Bry__maqaf__1 = Bry_.new_u8("ת"), Bry__maqaf__2 = Bry_.new_u8("־") + ; +} diff --git a/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_manual_regy.java b/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_manual_regy.java index a27517de8..776a5d991 100644 --- a/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_manual_regy.java +++ b/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_manual_regy.java @@ -13,3 +13,20 @@ 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.langs.grammars; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xol_grammar_manual_regy { + private Hash_adp_bry[] ary = new Hash_adp_bry[Xol_grammar_.Tid__max]; + public byte[] Itms_get(byte type_tid, byte[] word) { + Hash_adp_bry hash = ary[type_tid]; if (hash == null) return null; + return (byte[])hash.Get_by_bry(word); + } + public Xol_grammar_manual_regy Itms_add(byte type_tid, String orig, String repl) { + Hash_adp_bry hash = ary[type_tid]; + if (hash == null) { + hash = Hash_adp_bry.ci_a7(); // ASCII:currently only being used for Wikiuutiset; DATE:2014-07-07 + ary[type_tid] = hash; + } + hash.Add_str_obj(orig, Bry_.new_a7(repl)); + return this; + } +} diff --git a/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_ru.java b/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_ru.java index a27517de8..69683ec7d 100644 --- a/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_ru.java +++ b/400_xowa/src/gplx/xowa/langs/grammars/Xol_grammar_ru.java @@ -13,3 +13,60 @@ 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.langs.grammars; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.btries.*; +public class Xol_grammar_ru implements Xol_grammar { + static final byte Genitive_null = 0, Genitive_bnkn = 1, Genitive_Bnkn = 26, Genitive_b = 3, Genitive_nr = 4, Genitive_ka = 5, Genitive_tn = 6, Genitive_abl = 7, Genitive_hnk = 8; + private final Btrie_rv trv = new Btrie_rv(); + private static Btrie_bwd_mgr Genitive_trie; + private static Btrie_bwd_mgr genitive_trie_() { + Btrie_bwd_mgr rv = new Btrie_bwd_mgr(false); + genitive_trie_add(rv, Genitive_bnkn, "вики", null); + genitive_trie_add(rv, Genitive_Bnkn, "Вики", null); + genitive_trie_add(rv, Genitive_b, "ь", "я"); + genitive_trie_add(rv, Genitive_nr, "ия", "ии"); + genitive_trie_add(rv, Genitive_ka, "ка", "ки"); + genitive_trie_add(rv, Genitive_tn, "ти", "тей"); + genitive_trie_add(rv, Genitive_abl, "ды", "дов"); + genitive_trie_add(rv, Genitive_hnk , "ник", "ника"); + return rv; + } + private static void genitive_trie_add(Btrie_bwd_mgr trie, byte tid, String find_str, String repl_str) { + byte[] find_bry = Bry_.new_u8(find_str); + byte[] repl_bry = repl_str == null ? null : Bry_.new_u8(repl_str); + Xol_grammar_ru_genitive_itm itm = new Xol_grammar_ru_genitive_itm(tid, find_bry, repl_bry); + trie.Add(find_bry, itm); + } + public boolean Grammar_eval(Bry_bfr bfr, Xol_lang_itm lang, byte[] word, byte[] type) { + if (Bry_.Len_eq_0(word)) return true; // empty_string returns "" + byte tid = Xol_grammar_.Tid_of_type(type); + switch (tid) { + case Xol_grammar_.Tid_genitive: { + if (Genitive_trie == null) Genitive_trie = genitive_trie_(); + Object o = Genitive_trie.Match_at(trv, word, word.length - 1, -1); + if (o != null) { + Xol_grammar_ru_genitive_itm itm = (Xol_grammar_ru_genitive_itm)o; + if (!itm.Repl_is_noop()) { + bfr.Add_mid(word, 0, trv.Pos() + 1); + bfr.Add(itm.Repl()); + return true; + } + } + break; + } + case Xol_grammar_.Tid_dative: break; + case Xol_grammar_.Tid_accusative: break; + case Xol_grammar_.Tid_instrumental: break; + case Xol_grammar_.Tid_prepositional:break; + } + bfr.Add(word); + return true; + } +} +class Xol_grammar_ru_genitive_itm { + public Xol_grammar_ru_genitive_itm(byte tid, byte[] find, byte[] repl) {this.tid = tid; this.find = find; this.repl = repl;} + public byte Tid() {return tid;} private byte tid; + public byte[] Find() {return find;} private byte[] find; + public byte[] Repl() {return repl;} private byte[] repl; + public boolean Repl_is_noop() {return repl == null;} +} diff --git a/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_grp.java b/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_grp.java index a27517de8..ba7d1b497 100644 --- a/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_grp.java +++ b/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_grp.java @@ -13,3 +13,20 @@ 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.langs.kwds; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xol_kwd_grp {// REF.MW: Messages_en.php; EX: 'redirect' => array( 0, '#REDIRECT' ) + public Xol_kwd_grp(byte[] key) {this.key = key;} + public byte[] Key() {return key;} private final byte[] key; + public boolean Case_match() {return case_match;} private boolean case_match; + public Xol_kwd_itm[] Itms() {return itms;} private Xol_kwd_itm[] itms = Xol_kwd_itm.Ary_empty; + public void Srl_load(boolean case_match, byte[][] words) { + this.case_match = case_match; + int words_len = words.length; + itms = new Xol_kwd_itm[words_len]; + for (int i = 0; i < words_len; i++) { + byte[] word = words[i]; + Xol_kwd_itm itm = new Xol_kwd_itm(word); + itms[i] = itm; + } + } +} diff --git a/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_grp_.java b/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_grp_.java index a27517de8..a5e979c6f 100644 --- a/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_grp_.java +++ b/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_grp_.java @@ -13,3 +13,477 @@ 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.langs.kwds; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.primitives.*; +public class Xol_kwd_grp_ { +public static final int + Id_redirect = 0 +, Id_notoc = 1 +, Id_nogallery = 2 +, Id_forcetoc = 3 +, Id_toc = 4 +, Id_noeditsection = 5 +, Id_noheader = 6 +, Id_utc_month_int_len2 = 7 +, Id_utc_month_int = 8 +, Id_utc_month_name = 9 +, Id_utc_month_gen = 10 +, Id_utc_month_abrv = 11 +, Id_utc_day_int = 12 +, Id_utc_day_int_len2 = 13 +, Id_utc_day_name = 14 +, Id_utc_year = 15 +, Id_utc_time = 16 +, Id_utc_hour = 17 +, Id_lcl_month_int_len2 = 18 +, Id_lcl_month_int = 19 +, Id_lcl_month_name = 20 +, Id_lcl_month_gen = 21 +, Id_lcl_month_abrv = 22 +, Id_lcl_day_int = 23 +, Id_lcl_day_int_len2 = 24 +, Id_lcl_day_name = 25 +, Id_lcl_year = 26 +, Id_lcl_time = 27 +, Id_lcl_hour = 28 +, Id_num_pages = 29 +, Id_num_articles = 30 +, Id_num_files = 31 +, Id_num_users = 32 +, Id_num_users_active = 33 +, Id_num_edits = 34 +, Id_num_views = 35 +, Id_ttl_page_txt = 36 +, Id_ttl_page_url = 37 +, Id_ns_txt = 38 +, Id_ns_url = 39 +, Id_ns_talk_txt = 40 +, Id_ns_talk_url = 41 +, Id_ns_subj_txt = 42 +, Id_ns_subj_url = 43 +, Id_ttl_full_txt = 44 +, Id_ttl_full_url = 45 +, Id_ttl_leaf_txt = 46 +, Id_ttl_leaf_url = 47 +, Id_ttl_base_txt = 48 +, Id_ttl_base_url = 49 +, Id_ttl_talk_txt = 50 +, Id_ttl_talk_url = 51 +, Id_ttl_subj_txt = 52 +, Id_ttl_subj_url = 53 +, Id_msg = 54 +, Id_subst = 55 +, Id_safesubst = 56 +, Id_msgnw = 57 +, Id_img_thumbnail = 58 +, Id_img_manualthumb = 59 +, Id_img_framed = 60 +, Id_img_frameless = 61 +, Id_img_upright = 62 +, Id_img_upright_factor = 63 +, Id_img_border = 64 +, Id_img_align = 65 +, Id_img_valign = 66 +, Id_img_alt = 67 +, Id_img_class = 68 +, Id_img_caption = 69 +, Id_img_link_url = 70 +, Id_img_link_title = 71 +, Id_img_link_target = 72 +, Id_img_link_none = 73 +, Id_img_width = 74 +, Id_img_page = 75 +, Id_img_none = 76 +, Id_img_right = 77 +, Id_img_center = 78 +, Id_img_left = 79 +, Id_img_baseline = 80 +, Id_img_sub = 81 +, Id_img_super = 82 +, Id_img_top = 83 +, Id_img_text_top = 84 +, Id_img_middle = 85 +, Id_img_bottom = 86 +, Id_img_text_bottom = 87 +, Id_img_link = 88 +, Id_i18n_int = 89 +, Id_site_sitename = 90 +, Id_url_ns = 91 +, Id_url_nse = 92 +, Id_url_localurl = 93 +, Id_url_localurle = 94 +, Id_site_articlepath = 95 +, Id_site_server = 96 +, Id_site_servername = 97 +, Id_site_scriptpath = 98 +, Id_site_stylepath = 99 +, Id_i18n_grammar = 100 +, Id_i18n_gender = 101 +, Id_notitleconvert = 102 +, Id_nocontentconvert = 103 +, Id_utc_week = 104 +, Id_utc_dow = 105 +, Id_lcl_week = 106 +, Id_lcl_dow = 107 +, Id_rev_id = 108 +, Id_rev_day_int = 109 +, Id_rev_day_int_len2 = 110 +, Id_rev_month_int_len2 = 111 +, Id_rev_month_int = 112 +, Id_rev_year = 113 +, Id_rev_timestamp = 114 +, Id_rev_user = 115 +, Id_i18n_plural = 116 +, Id_url_fullurl = 117 +, Id_url_fullurle = 118 +, Id_str_lcfirst = 119 +, Id_str_ucfirst = 120 +, Id_str_lc = 121 +, Id_str_uc = 122 +, Id_raw = 123 +, Id_page_displaytitle = 124 +, Id_str_rawsuffix = 125 +, Id_newsectionlink = 126 +, Id_nonewsectionlink = 127 +, Id_site_currentversion = 128 +, Id_url_urlencode = 129 +, Id_url_anchorencode = 130 +, Id_utc_timestamp = 131 +, Id_lcl_timestamp = 132 +, Id_site_directionmark = 133 +, Id_i18n_language = 134 +, Id_site_contentlanguage = 135 +, Id_site_pagesinnamespace = 136 +, Id_num_admins = 137 +, Id_str_formatnum = 138 +, Id_str_padleft = 139 +, Id_str_padright = 140 +, Id_misc_special = 141 +, Id_page_defaultsort = 142 +, Id_url_filepath = 143 +, Id_misc_tag = 144 +, Id_hiddencat = 145 +, Id_site_pagesincategory = 146 +, Id_rev_pagesize = 147 +, Id_index = 148 +, Id_noindex = 149 +, Id_site_numberingroup = 150 +, Id_staticredirect = 151 +, Id_rev_protectionlevel = 152 +, Id_str_formatdate = 153 +, Id_url_path = 154 +, Id_url_wiki = 155 +, Id_url_query = 156 +, Id_xtn_expr = 157 +, Id_xtn_if = 158 +, Id_xtn_ifeq = 159 +, Id_xtn_ifexpr = 160 +, Id_xtn_iferror = 161 +, Id_xtn_switch = 162 +, Id_xtn_default = 163 +, Id_xtn_ifexist = 164 +, Id_xtn_time = 165 +, Id_xtn_timel = 166 +, Id_xtn_rel2abs = 167 +, Id_xtn_titleparts = 168 +, Id_xowa_dbg = 169 +, Id_ogg_noplayer = 170 +, Id_ogg_noicon = 171 +, Id_ogg_thumbtime = 172 +, Id_xtn_geodata_coordinates = 173 +, Id_url_canonicalurl = 174 +, Id_url_canonicalurle = 175 +, Id_lst = 176 +, Id_lstx = 177 +, Id_invoke = 178 +, Id_property = 179 +, Id_noexternallanglinks = 180 +, Id_ns_num = 181 +, Id_page_id = 182 +, Id_disambig = 183 +, Id_nocommafysuffix = 184 +, Id_xowa = 185 +, Id_mapSources_deg2dd = 186 +, Id_mapSources_dd2dms = 187 +, Id_mapSources_geoLink = 188 +, Id_geoCrumbs_isin = 189 +, Id_relatedArticles = 190 +, Id_insider = 191 +, Id_massMessage_target = 192 +, Id_cascadingSources = 193 +, Id_pendingChangeLevel = 194 +, Id_pagesUsingPendingChanges = 195 +, Id_bang = 196 +, Id_wbreponame = 197 +, Id_strx_len = 198 +, Id_strx_pos = 199 +, Id_strx_rpos = 200 +, Id_strx_sub = 201 +, Id_strx_count = 202 +, Id_strx_replace = 203 +, Id_strx_explode = 204 +, Id_strx_urldecode = 205 +, Id_pagesincategory_pages = 206 +, Id_pagesincategory_subcats = 207 +, Id_pagesincategory_files = 208 +, Id_rev_revisionsize = 209 +, Id_pagebanner = 210 +, Id_rev_protectionexpiry = 211 +, Id_new_window_link = 212 +, Id_categorytree = 213 +, Id_lsth = 214 +, Id_assessment = 215 +, Id_ttl_root_txt = 216 +, Id_ttl_root_url = 217 +, Id_statements = 218 +; +public static final int Id__max = 219; + + private static byte[] ary_itm_(int id) { + switch (id) { +case Xol_kwd_grp_.Id_redirect: return Bry_.new_a7("redirect"); +case Xol_kwd_grp_.Id_notoc: return Bry_.new_a7("notoc"); +case Xol_kwd_grp_.Id_nogallery: return Bry_.new_a7("nogallery"); +case Xol_kwd_grp_.Id_forcetoc: return Bry_.new_a7("forcetoc"); +case Xol_kwd_grp_.Id_toc: return Bry_.new_a7("toc"); +case Xol_kwd_grp_.Id_noeditsection: return Bry_.new_a7("noeditsection"); +case Xol_kwd_grp_.Id_noheader: return Bry_.new_a7("noheader"); +case Xol_kwd_grp_.Id_utc_month_int_len2: return Bry_.new_a7("currentmonth"); +case Xol_kwd_grp_.Id_utc_month_int: return Bry_.new_a7("currentmonth1"); +case Xol_kwd_grp_.Id_utc_month_name: return Bry_.new_a7("currentmonthname"); +case Xol_kwd_grp_.Id_utc_month_gen: return Bry_.new_a7("currentmonthnamegen"); +case Xol_kwd_grp_.Id_utc_month_abrv: return Bry_.new_a7("currentmonthabbrev"); +case Xol_kwd_grp_.Id_utc_day_int: return Bry_.new_a7("currentday"); +case Xol_kwd_grp_.Id_utc_day_int_len2: return Bry_.new_a7("currentday2"); +case Xol_kwd_grp_.Id_utc_day_name: return Bry_.new_a7("currentdayname"); +case Xol_kwd_grp_.Id_utc_year: return Bry_.new_a7("currentyear"); +case Xol_kwd_grp_.Id_utc_time: return Bry_.new_a7("currenttime"); +case Xol_kwd_grp_.Id_utc_hour: return Bry_.new_a7("currenthour"); +case Xol_kwd_grp_.Id_lcl_month_int_len2: return Bry_.new_a7("localmonth"); +case Xol_kwd_grp_.Id_lcl_month_int: return Bry_.new_a7("localmonth1"); +case Xol_kwd_grp_.Id_lcl_month_name: return Bry_.new_a7("localmonthname"); +case Xol_kwd_grp_.Id_lcl_month_gen: return Bry_.new_a7("localmonthnamegen"); +case Xol_kwd_grp_.Id_lcl_month_abrv: return Bry_.new_a7("localmonthabbrev"); +case Xol_kwd_grp_.Id_lcl_day_int: return Bry_.new_a7("localday"); +case Xol_kwd_grp_.Id_lcl_day_int_len2: return Bry_.new_a7("localday2"); +case Xol_kwd_grp_.Id_lcl_day_name: return Bry_.new_a7("localdayname"); +case Xol_kwd_grp_.Id_lcl_year: return Bry_.new_a7("localyear"); +case Xol_kwd_grp_.Id_lcl_time: return Bry_.new_a7("localtime"); +case Xol_kwd_grp_.Id_lcl_hour: return Bry_.new_a7("localhour"); +case Xol_kwd_grp_.Id_num_pages: return Bry_.new_a7("numberofpages"); +case Xol_kwd_grp_.Id_num_articles: return Bry_.new_a7("numberofarticles"); +case Xol_kwd_grp_.Id_num_files: return Bry_.new_a7("numberoffiles"); +case Xol_kwd_grp_.Id_num_users: return Bry_.new_a7("numberofusers"); +case Xol_kwd_grp_.Id_num_users_active: return Bry_.new_a7("numberofactiveusers"); +case Xol_kwd_grp_.Id_num_edits: return Bry_.new_a7("numberofedits"); +case Xol_kwd_grp_.Id_num_views: return Bry_.new_a7("numberofviews"); +case Xol_kwd_grp_.Id_ttl_page_txt: return Bry_.new_a7("pagename"); +case Xol_kwd_grp_.Id_ttl_page_url: return Bry_.new_a7("pagenamee"); +case Xol_kwd_grp_.Id_ns_txt: return Bry_.new_a7("namespace"); +case Xol_kwd_grp_.Id_ns_url: return Bry_.new_a7("namespacee"); +case Xol_kwd_grp_.Id_ns_talk_txt: return Bry_.new_a7("talkspace"); +case Xol_kwd_grp_.Id_ns_talk_url: return Bry_.new_a7("talkspacee"); +case Xol_kwd_grp_.Id_ns_subj_txt: return Bry_.new_a7("subjectspace"); +case Xol_kwd_grp_.Id_ns_subj_url: return Bry_.new_a7("subjectspacee"); +case Xol_kwd_grp_.Id_ttl_full_txt: return Bry_.new_a7("fullpagename"); +case Xol_kwd_grp_.Id_ttl_full_url: return Bry_.new_a7("fullpagenamee"); +case Xol_kwd_grp_.Id_ttl_leaf_txt: return Bry_.new_a7("subpagename"); +case Xol_kwd_grp_.Id_ttl_leaf_url: return Bry_.new_a7("subpagenamee"); +case Xol_kwd_grp_.Id_ttl_base_txt: return Bry_.new_a7("basepagename"); +case Xol_kwd_grp_.Id_ttl_base_url: return Bry_.new_a7("basepagenamee"); +case Xol_kwd_grp_.Id_ttl_talk_txt: return Bry_.new_a7("talkpagename"); +case Xol_kwd_grp_.Id_ttl_talk_url: return Bry_.new_a7("talkpagenamee"); +case Xol_kwd_grp_.Id_ttl_subj_txt: return Bry_.new_a7("subjectpagename"); +case Xol_kwd_grp_.Id_ttl_subj_url: return Bry_.new_a7("subjectpagenamee"); +case Xol_kwd_grp_.Id_ttl_root_txt: return Bry_.new_u8("rootpagename"); +case Xol_kwd_grp_.Id_ttl_root_url: return Bry_.new_u8("rootpagenamee"); +case Xol_kwd_grp_.Id_msg: return Bry_.new_a7("msg"); +case Xol_kwd_grp_.Id_subst: return Bry_.new_a7("subst"); +case Xol_kwd_grp_.Id_safesubst: return Bry_.new_a7("safesubst"); +case Xol_kwd_grp_.Id_msgnw: return Bry_.new_a7("msgnw"); +case Xol_kwd_grp_.Id_img_thumbnail: return Bry_.new_a7("img_thumbnail"); +case Xol_kwd_grp_.Id_img_manualthumb: return Bry_.new_a7("img_manualthumb"); +case Xol_kwd_grp_.Id_img_framed: return Bry_.new_a7("img_framed"); +case Xol_kwd_grp_.Id_img_frameless: return Bry_.new_a7("img_frameless"); +case Xol_kwd_grp_.Id_img_upright: return Bry_.new_a7("img_upright"); +case Xol_kwd_grp_.Id_img_upright_factor: return Bry_.new_a7("img_upright_factor"); +case Xol_kwd_grp_.Id_img_border: return Bry_.new_a7("img_border"); +case Xol_kwd_grp_.Id_img_align: return Bry_.new_a7("img_align"); +case Xol_kwd_grp_.Id_img_valign: return Bry_.new_a7("img_valign"); +case Xol_kwd_grp_.Id_img_alt: return Bry_.new_a7("img_alt"); +case Xol_kwd_grp_.Id_img_class: return Bry_.new_a7("img_class"); +case Xol_kwd_grp_.Id_img_caption: return Bry_.new_a7("img_caption"); +case Xol_kwd_grp_.Id_img_link_url: return Bry_.new_a7("img_link_url"); +case Xol_kwd_grp_.Id_img_link_title: return Bry_.new_a7("img_link_title"); +case Xol_kwd_grp_.Id_img_link_target: return Bry_.new_a7("img_link_target"); +case Xol_kwd_grp_.Id_img_link_none: return Bry_.new_a7("img_link_none"); +case Xol_kwd_grp_.Id_img_width: return Bry_.new_a7("img_width"); +case Xol_kwd_grp_.Id_img_page: return Bry_.new_a7("img_page"); +case Xol_kwd_grp_.Id_img_none: return Bry_.new_a7("img_none"); +case Xol_kwd_grp_.Id_img_right: return Bry_.new_a7("img_right"); +case Xol_kwd_grp_.Id_img_center: return Bry_.new_a7("img_center"); +case Xol_kwd_grp_.Id_img_left: return Bry_.new_a7("img_left"); +case Xol_kwd_grp_.Id_img_baseline: return Bry_.new_a7("img_baseline"); +case Xol_kwd_grp_.Id_img_sub: return Bry_.new_a7("img_sub"); +case Xol_kwd_grp_.Id_img_super: return Bry_.new_a7("img_super"); +case Xol_kwd_grp_.Id_img_top: return Bry_.new_a7("img_top"); +case Xol_kwd_grp_.Id_img_text_top: return Bry_.new_a7("img_text_top"); +case Xol_kwd_grp_.Id_img_middle: return Bry_.new_a7("img_middle"); +case Xol_kwd_grp_.Id_img_bottom: return Bry_.new_a7("img_bottom"); +case Xol_kwd_grp_.Id_img_text_bottom: return Bry_.new_a7("img_text_bottom"); +case Xol_kwd_grp_.Id_img_link: return Bry_.new_a7("img_link"); +case Xol_kwd_grp_.Id_i18n_int: return Bry_.new_a7("int"); +case Xol_kwd_grp_.Id_site_sitename: return Bry_.new_a7("sitename"); +case Xol_kwd_grp_.Id_url_ns: return Bry_.new_a7("ns"); +case Xol_kwd_grp_.Id_url_nse: return Bry_.new_a7("nse"); +case Xol_kwd_grp_.Id_url_localurl: return Bry_.new_a7("localurl"); +case Xol_kwd_grp_.Id_url_localurle: return Bry_.new_a7("localurle"); +case Xol_kwd_grp_.Id_site_articlepath: return Bry_.new_a7("articlepath"); +case Xol_kwd_grp_.Id_site_server: return Bry_.new_a7("server"); +case Xol_kwd_grp_.Id_site_servername: return Bry_.new_a7("servername"); +case Xol_kwd_grp_.Id_site_scriptpath: return Bry_.new_a7("scriptpath"); +case Xol_kwd_grp_.Id_site_stylepath: return Bry_.new_a7("stylepath"); +case Xol_kwd_grp_.Id_i18n_grammar: return Bry_.new_a7("grammar"); +case Xol_kwd_grp_.Id_i18n_gender: return Bry_.new_a7("gender"); +case Xol_kwd_grp_.Id_notitleconvert: return Bry_.new_a7("notitleconvert"); +case Xol_kwd_grp_.Id_nocontentconvert: return Bry_.new_a7("nocontentconvert"); +case Xol_kwd_grp_.Id_utc_week: return Bry_.new_a7("currentweek"); +case Xol_kwd_grp_.Id_utc_dow: return Bry_.new_a7("currentdow"); +case Xol_kwd_grp_.Id_lcl_week: return Bry_.new_a7("localweek"); +case Xol_kwd_grp_.Id_lcl_dow: return Bry_.new_a7("localdow"); +case Xol_kwd_grp_.Id_rev_id: return Bry_.new_a7("revisionid"); +case Xol_kwd_grp_.Id_rev_day_int: return Bry_.new_a7("revisionday"); +case Xol_kwd_grp_.Id_rev_day_int_len2: return Bry_.new_a7("revisionday2"); +case Xol_kwd_grp_.Id_rev_month_int_len2: return Bry_.new_a7("revisionmonth"); +case Xol_kwd_grp_.Id_rev_month_int: return Bry_.new_a7("revisionmonth1"); +case Xol_kwd_grp_.Id_rev_year: return Bry_.new_a7("revisionyear"); +case Xol_kwd_grp_.Id_rev_timestamp: return Bry_.new_a7("revisiontimestamp"); +case Xol_kwd_grp_.Id_rev_user: return Bry_.new_a7("revisionuser"); +case Xol_kwd_grp_.Id_i18n_plural: return Bry_.new_a7("plural"); +case Xol_kwd_grp_.Id_url_fullurl: return Bry_.new_a7("fullurl"); +case Xol_kwd_grp_.Id_url_fullurle: return Bry_.new_a7("fullurle"); +case Xol_kwd_grp_.Id_str_lcfirst: return Bry_.new_a7("lcfirst"); +case Xol_kwd_grp_.Id_str_ucfirst: return Bry_.new_a7("ucfirst"); +case Xol_kwd_grp_.Id_str_lc: return Bry_.new_a7("lc"); +case Xol_kwd_grp_.Id_str_uc: return Bry_.new_a7("uc"); +case Xol_kwd_grp_.Id_raw: return Bry_.new_a7("raw"); +case Xol_kwd_grp_.Id_page_displaytitle: return Bry_.new_a7("displaytitle"); +case Xol_kwd_grp_.Id_str_rawsuffix: return Bry_.new_a7("rawsuffix"); +case Xol_kwd_grp_.Id_newsectionlink: return Bry_.new_a7("newsectionlink"); +case Xol_kwd_grp_.Id_nonewsectionlink: return Bry_.new_a7("nonewsectionlink"); +case Xol_kwd_grp_.Id_site_currentversion: return Bry_.new_a7("currentversion"); +case Xol_kwd_grp_.Id_url_urlencode: return Bry_.new_a7("urlencode"); +case Xol_kwd_grp_.Id_url_anchorencode: return Bry_.new_a7("anchorencode"); +case Xol_kwd_grp_.Id_utc_timestamp: return Bry_.new_a7("currenttimestamp"); +case Xol_kwd_grp_.Id_lcl_timestamp: return Bry_.new_a7("localtimestamp"); +case Xol_kwd_grp_.Id_site_directionmark: return Bry_.new_a7("directionmark"); +case Xol_kwd_grp_.Id_i18n_language: return Bry_.new_a7("language"); +case Xol_kwd_grp_.Id_site_contentlanguage: return Bry_.new_a7("contentlanguage"); +case Xol_kwd_grp_.Id_site_pagesinnamespace: return Bry_.new_a7("pagesinnamespace"); +case Xol_kwd_grp_.Id_num_admins: return Bry_.new_a7("numberofadmins"); +case Xol_kwd_grp_.Id_str_formatnum: return Bry_.new_a7("formatnum"); +case Xol_kwd_grp_.Id_str_padleft: return Bry_.new_a7("padleft"); +case Xol_kwd_grp_.Id_str_padright: return Bry_.new_a7("padright"); +case Xol_kwd_grp_.Id_misc_special: return Bry_.new_a7("special"); +case Xol_kwd_grp_.Id_page_defaultsort: return Bry_.new_a7("defaultsort"); +case Xol_kwd_grp_.Id_url_filepath: return Bry_.new_a7("filepath"); +case Xol_kwd_grp_.Id_misc_tag: return Bry_.new_a7("tag"); +case Xol_kwd_grp_.Id_hiddencat: return Bry_.new_a7("hiddencat"); +case Xol_kwd_grp_.Id_site_pagesincategory: return Bry_.new_a7("pagesincategory"); +case Xol_kwd_grp_.Id_rev_pagesize: return Bry_.new_a7("pagesize"); +case Xol_kwd_grp_.Id_index: return Bry_.new_a7("index"); +case Xol_kwd_grp_.Id_noindex: return Bry_.new_a7("noindex"); +case Xol_kwd_grp_.Id_site_numberingroup: return Bry_.new_a7("numberingroup"); +case Xol_kwd_grp_.Id_staticredirect: return Bry_.new_a7("staticredirect"); +case Xol_kwd_grp_.Id_rev_protectionlevel: return Bry_.new_a7("protectionlevel"); +case Xol_kwd_grp_.Id_str_formatdate: return Bry_.new_a7("formatdate"); +case Xol_kwd_grp_.Id_url_path: return Bry_.new_a7("url_path"); +case Xol_kwd_grp_.Id_url_wiki: return Bry_.new_a7("url_wiki"); +case Xol_kwd_grp_.Id_url_query: return Bry_.new_a7("url_query"); +case Xol_kwd_grp_.Id_xtn_expr: return Bry_.new_a7("expr"); +case Xol_kwd_grp_.Id_xtn_if: return Bry_.new_a7("if"); +case Xol_kwd_grp_.Id_xtn_ifeq: return Bry_.new_a7("ifeq"); +case Xol_kwd_grp_.Id_xtn_ifexpr: return Bry_.new_a7("ifexpr"); +case Xol_kwd_grp_.Id_xtn_iferror: return Bry_.new_a7("iferror"); +case Xol_kwd_grp_.Id_xtn_switch: return Bry_.new_a7("switch"); +case Xol_kwd_grp_.Id_xtn_default: return Bry_.new_a7("default"); +case Xol_kwd_grp_.Id_xtn_ifexist: return Bry_.new_a7("ifexist"); +case Xol_kwd_grp_.Id_xtn_time: return Bry_.new_a7("time"); +case Xol_kwd_grp_.Id_xtn_timel: return Bry_.new_a7("timel"); +case Xol_kwd_grp_.Id_xtn_rel2abs: return Bry_.new_a7("rel2abs"); +case Xol_kwd_grp_.Id_xtn_titleparts: return Bry_.new_a7("titleparts"); +case Xol_kwd_grp_.Id_xowa_dbg: return Bry_.new_a7("xowa_dbg"); +case Xol_kwd_grp_.Id_ogg_noplayer: return Bry_.new_a7("noplayer"); +case Xol_kwd_grp_.Id_ogg_noicon: return Bry_.new_a7("noicon"); +case Xol_kwd_grp_.Id_ogg_thumbtime: return Bry_.new_a7("thumbtime"); +case Xol_kwd_grp_.Id_xtn_geodata_coordinates: return Bry_.new_a7("coordinates"); +case Xol_kwd_grp_.Id_url_canonicalurl: return Bry_.new_a7("canonicalurl"); +case Xol_kwd_grp_.Id_url_canonicalurle: return Bry_.new_a7("canonicalurle"); +case Xol_kwd_grp_.Id_lst: return Bry_.new_a7("lst"); +case Xol_kwd_grp_.Id_lstx: return Bry_.new_a7("lstx"); +case Xol_kwd_grp_.Id_lsth: return Bry_.new_u8("lsth"); +case Xol_kwd_grp_.Id_invoke: return Bry_.new_a7("invoke"); +case Xol_kwd_grp_.Id_property: return Bry_.new_a7("property"); +case Xol_kwd_grp_.Id_noexternallanglinks: return Bry_.new_a7("noexternallanglinks"); +case Xol_kwd_grp_.Id_ns_num: return Bry_.new_a7("namespacenumber"); +case Xol_kwd_grp_.Id_page_id: return Bry_.new_a7("pageid"); +case Xol_kwd_grp_.Id_disambig: return Bry_.new_a7("disambiguation"); +case Xol_kwd_grp_.Id_nocommafysuffix: return Bry_.new_a7("nosep"); +case Xol_kwd_grp_.Id_xowa: return Bry_.new_a7("xowa"); +case Xol_kwd_grp_.Id_mapSources_deg2dd: return Bry_.new_a7("deg2dd"); +case Xol_kwd_grp_.Id_mapSources_dd2dms: return Bry_.new_a7("dd2dms"); +case Xol_kwd_grp_.Id_mapSources_geoLink: return Bry_.new_a7("geolink"); +case Xol_kwd_grp_.Id_geoCrumbs_isin: return Bry_.new_a7("isin"); +case Xol_kwd_grp_.Id_relatedArticles: return Bry_.new_a7("relatedArticles"); +case Xol_kwd_grp_.Id_insider: return Bry_.new_a7("insider"); +case Xol_kwd_grp_.Id_massMessage_target: return Bry_.new_a7("target"); +case Xol_kwd_grp_.Id_cascadingSources: return Bry_.new_a7("cascadingSources"); +case Xol_kwd_grp_.Id_pendingChangeLevel: return Bry_.new_a7("pendingChangeLevel"); +case Xol_kwd_grp_.Id_pagesUsingPendingChanges: return Bry_.new_a7("pagesUsingPendingChanges"); +case Xol_kwd_grp_.Id_bang: return Bry_.new_a7("!"); +case Xol_kwd_grp_.Id_wbreponame: return Bry_.new_a7("wbreponame"); +case Xol_kwd_grp_.Id_strx_len: return Bry_.new_a7("len"); +case Xol_kwd_grp_.Id_strx_pos: return Bry_.new_a7("pos"); +case Xol_kwd_grp_.Id_strx_rpos: return Bry_.new_a7("rpos"); +case Xol_kwd_grp_.Id_strx_sub: return Bry_.new_a7("sub"); +case Xol_kwd_grp_.Id_strx_count: return Bry_.new_a7("count"); +case Xol_kwd_grp_.Id_strx_replace: return Bry_.new_a7("replace"); +case Xol_kwd_grp_.Id_strx_explode: return Bry_.new_a7("explode"); +case Xol_kwd_grp_.Id_strx_urldecode: return Bry_.new_a7("urldecode"); +case Xol_kwd_grp_.Id_pagesincategory_pages: return Bry_.new_u8("pagesincategory_pages"); +case Xol_kwd_grp_.Id_pagesincategory_subcats: return Bry_.new_u8("pagesincategory_subcats"); +case Xol_kwd_grp_.Id_pagesincategory_files: return Bry_.new_u8("pagesincategory_files"); +case Xol_kwd_grp_.Id_rev_revisionsize: return Bry_.new_u8("revisionsize"); +case Xol_kwd_grp_.Id_pagebanner: return Bry_.new_u8("pagebanner"); +case Xol_kwd_grp_.Id_rev_protectionexpiry: return Bry_.new_u8("protectionexpiry"); +case Xol_kwd_grp_.Id_new_window_link: return Bry_.new_u8("newwindowlink"); +case Xol_kwd_grp_.Id_categorytree: return Bry_.new_u8("categorytree"); +case Xol_kwd_grp_.Id_assessment: return Bry_.new_u8("assessment"); +case Xol_kwd_grp_.Id_statements: return Bry_.new_u8("statements"); +default: throw Err_.new_unhandled(id); + } + } + public static byte[] Bry_by_id(int id) { + if (Bry__ == null) Bry_init(); + return Bry__[id]; + } private static byte[][] Bry__; + public static int Id_by_bry(byte[] find) { + if (hash == null) { + hash = Hash_adp_bry.ci_a7(); // ASCII: all MW kwds appear to be ASCII; EX: "redirect", "toc", "currentmont", etc. + if (Bry__ == null) Bry_init(); + int len = Bry__.length; + for (int i = 0; i < len; i++) { + byte[] bry = Bry__[i]; + hash.Add(bry, new Int_obj_val(i)); + } + } + Object o = hash.Get_by_bry(find); + return o == null? Int_.Neg1 : ((Int_obj_val)o).Val(); + } private static Hash_adp_bry hash; + private static void Bry_init() { + Bry__ = new byte[Id__max][]; + for (int i = 0; i < Id__max; i++) + Bry__[i] = ary_itm_(i); + } +} + diff --git a/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_itm.java b/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_itm.java index a27517de8..910f049c4 100644 --- a/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_itm.java +++ b/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_itm.java @@ -13,3 +13,10 @@ 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.langs.kwds; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xol_kwd_itm {// NOTE: keeping as separate class b/c may include fmt props later; EX: thumbnail=$1 + public Xol_kwd_itm(byte[] val) {this.val = val;} + public byte[] Val() {return val;} private byte[] val; + public void Val_(byte[] v) {val = v;} // should only be called by lang + public static final Xol_kwd_itm[] Ary_empty = new Xol_kwd_itm[0]; +} diff --git a/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_mgr.java b/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_mgr.java index a27517de8..56ab35281 100644 --- a/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_mgr.java +++ b/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_mgr.java @@ -13,3 +13,83 @@ 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.langs.kwds; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.btries.*; +import gplx.xowa.langs.parsers.*; +public class Xol_kwd_mgr implements Gfo_invk { + private final Xol_lang_itm lang; private final Xol_kwd_grp[] grps = new Xol_kwd_grp[Xol_kwd_grp_.Id__max]; + private Btrie_slim_mgr kwd_default_trie; private byte[] kwd_default_key; private boolean kwd_default_init_needed = true; + public Xol_kwd_mgr(Xol_lang_itm lang) {this.lang = lang;} + public int Len() {return grps.length;} + public void Clear() { + int len = grps.length; + for (int i = 0; i < len; ++i) + grps[i] = null; + } + public Btrie_slim_mgr Trie_raw() {if (trie_raw == null) trie_raw = Xol_kwd_mgr.trie_(this, Xol_kwd_grp_.Id_str_rawsuffix); return trie_raw;} private Btrie_slim_mgr trie_raw; + public Btrie_slim_mgr Trie_nosep() {if (trie_nosep == null) trie_nosep = Xol_kwd_mgr.trie_(this, Xol_kwd_grp_.Id_nocommafysuffix); return trie_nosep;} private Btrie_slim_mgr trie_nosep; + public void Kwd_default_match_reset() {kwd_default_init_needed = true;} // TEST: + public boolean Kwd_default_match(byte[] match) { // handle multiple #default keywords; DATE:2014-07-28 + if (match == null) return false; // null never matches #default + int match_len = match.length; + if (match_len == 0) return false; // "" never matches default + if (kwd_default_init_needed) { + kwd_default_init_needed = false; + Xol_kwd_grp grp = this.Get_at(Xol_kwd_grp_.Id_xtn_default); + int len = grp.Itms().length; + if (len == 1) + kwd_default_key = grp.Itms()[0].Val(); + else { + kwd_default_trie = Btrie_slim_mgr.new_(grp.Case_match()); + for (int i = 0; i < len; i++) { + Xol_kwd_itm itm = grp.Itms()[i]; + kwd_default_trie.Add_obj(itm.Val(), itm); + } + } + } + return kwd_default_trie == null + ? Bry_.Has_at_bgn(match, kwd_default_key, 0, match_len) + : kwd_default_trie.Match_bgn(match, 0, match_len) != null + ; + } + public Xol_kwd_grp Get_at(int id) {return grps[id];} + public Xol_kwd_grp Get_or_new(int id) { + Xol_kwd_grp rv = grps[id]; + if (rv == null) { + rv = new Xol_kwd_grp(Xol_kwd_grp_.Bry_by_id(id)); + grps[id] = rv; + } + return rv; + } + public Xol_kwd_grp New(boolean case_match, int id, String... words_str) { + Xol_kwd_grp rv = Get_or_new(id); + rv.Srl_load(case_match, Bry_.Ary(words_str)); + return rv; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_lang)) return lang; + else if (ctx.Match(k, Invk_load_text)) Xol_lang_srl.Load_keywords(this, m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_lang = Xol_lang_srl.Invk_lang, Invk_load_text = Xol_lang_srl.Invk_load_text; + public static Btrie_slim_mgr trie_(Xol_kwd_mgr mgr, int id) { + Xol_kwd_grp grp = mgr.Get_at(id); + Btrie_slim_mgr rv = Btrie_slim_mgr.new_(grp.Case_match()); + int len = grp.Itms().length; + for (int i = 0; i < len; i++) { + Xol_kwd_itm itm = grp.Itms()[i]; + rv.Add_obj(itm.Val(), itm); + } + return rv; + } + public static Hash_adp_bry hash_(Xol_kwd_mgr mgr, int id) { + Xol_kwd_grp grp = mgr.Get_at(id); + Hash_adp_bry rv = Hash_adp_bry.c__u8(grp.Case_match(), mgr.lang.Case_mgr()); + int len = grp.Itms().length; + for (int i = 0; i < len; i++) { + Xol_kwd_itm itm = grp.Itms()[i]; + rv.Add(itm.Val(), itm); + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_parse_data.java b/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_parse_data.java index a27517de8..6e75f3e83 100644 --- a/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_parse_data.java +++ b/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_parse_data.java @@ -13,3 +13,66 @@ 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.langs.kwds; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.primitives.*; +public class Xol_kwd_parse_data { + public static byte[] Strip(Bry_bfr tmp, byte[] raw, Byte_obj_ref rslt) { + int raw_len = raw.length; + boolean dirty = false; + for (int i = 0; i < raw_len; i++) { + byte b = raw[i]; + switch (b) { + case Byte_ascii.Dollar: + byte prv = i == 0 ? Byte_ascii.Null : raw[i - 1]; + switch (prv) { + case Byte_ascii.Backslash: { // ignore \$ + if (dirty) tmp.Add_byte(b); + else {tmp.Add_mid(raw, 0, i - 1); dirty = true;} // i - 1 to ignore backslash + break; + } + case Byte_ascii.Eq: { // assume end; EX: link=$1 + dirty = true; + tmp.Add_mid(raw, 0, i - 1); // - 1 to ignore = + rslt.Val_(Strip_end); + i = raw_len; + break; + } + default: { + if (i == 0) { + rslt.Val_(Strip_bgn); + dirty = true; + int txt_bgn = 1; + for (int j = 1; j < raw_len; j++) { + b = raw[j]; + switch (b) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + break; + default: + txt_bgn = j; + j = raw_len; + break; + } + } + tmp.Add_mid(raw, txt_bgn, raw_len); + } + else { + dirty = true; + tmp.Add_mid(raw, 0, i); + rslt.Val_(Strip_end); + i = raw_len; + } + i = raw_len; + break; + } + } + break; + default: + if (dirty) tmp.Add_byte(b); + break; + } + } + return dirty ? tmp.To_bry_and_clear() : raw; + } + public static final byte Strip_none = 0, Strip_bgn = 1, Strip_end = 2; +} diff --git a/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_parse_data_tst.java b/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_parse_data_tst.java index a27517de8..e957dec1b 100644 --- a/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_parse_data_tst.java +++ b/400_xowa/src/gplx/xowa/langs/kwds/Xol_kwd_parse_data_tst.java @@ -13,3 +13,24 @@ 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.langs.kwds; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import org.junit.*; import gplx.core.primitives.*; +public class Xol_kwd_parse_data_tst { + @Before public void init() {Clear();} + @Test public void Basic() {Key_("upright" ).Tst_strip("upright");} + @Test public void Eq_arg() {Key_("upright" ).Tst_strip("upright=$1");} + @Test public void Space() {Key_("upright ").Tst_strip("upright $1");} + @Test public void Px() {Key_("px").Tst_strip("$1px");} + + private void Clear() { + key = null; + } + Xol_kwd_parse_data_tst Key_(String v) {this.key = v; return this;} private String key; + Xol_kwd_parse_data_tst Tst_strip(String v) { + Bry_bfr tmp = Bry_bfr_.New(); + Byte_obj_ref rslt = Byte_obj_ref.zero_(); + byte[] actl = Xol_kwd_parse_data.Strip(tmp, Bry_.new_a7(v), rslt); + Tfds.Eq(key, String_.new_a7(actl)); + return this; + } +} diff --git a/400_xowa/src/gplx/xowa/langs/lnki_trails/Xol_lnki_trail_mgr.java b/400_xowa/src/gplx/xowa/langs/lnki_trails/Xol_lnki_trail_mgr.java index a27517de8..a7379d8b6 100644 --- a/400_xowa/src/gplx/xowa/langs/lnki_trails/Xol_lnki_trail_mgr.java +++ b/400_xowa/src/gplx/xowa/langs/lnki_trails/Xol_lnki_trail_mgr.java @@ -13,3 +13,59 @@ 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.langs.lnki_trails; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.btries.*; +public class Xol_lnki_trail_mgr implements Gfo_invk { + public Xol_lnki_trail_mgr(Xol_lang_itm lang) {} + public void Clear() {trie.Clear();} + public int Count() {return trie.Count();} + public Btrie_slim_mgr Trie() {return trie;} private final Btrie_slim_mgr trie = Btrie_slim_mgr.cs(); + public void Add(byte[] v) {trie.Add_obj(v, v);} + public void Del(byte[] v) {trie.Del(v);} + private void Add(String... ary) { + for (String itm_str : ary) { + byte[] itm = Bry_.new_u8(itm_str); + trie.Add_obj(itm, itm); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_add_range)) Add_range(m); + else if (ctx.Match(k, Invk_add_many)) Add_many(m); + else if (ctx.Match(k, Invk_add_bulk)) Add_bulk(m); + else if (ctx.Match(k, Invk_clear)) Clear(); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_add_many = "add_many", Invk_add_range = "add_range", Invk_add_bulk = "add_bulk", Invk_clear = "clear"; + private void Add_bulk(GfoMsg m) {byte[] src = m.ReadBry("bulk"); Add_bulk(src);} + public void Add_bulk(byte[] src) { + int pos = 0, src_len = src.length; + while (true) { + byte[] itm = gplx.core.intls.Utf8_.Get_char_at_pos_as_bry(src, pos); + Add(itm); + pos += itm.length; + if (pos >= src_len) break; + } + } + private void Add_many(GfoMsg m) { + int len = m.Args_count(); + for (int i = 0; i < len; i++) { + Keyval kv = m.Args_getAt(i); + Add(kv.Val_to_str_or_empty()); + } + } + private void Add_range(GfoMsg m) { + byte bgn = Add_rng_extract(m, "bgn"); + byte end = Add_rng_extract(m, "end"); + for (byte i = bgn; i <= end; i++) + Add(new byte[] {i}); + } + public void Add_range(byte bgn, byte end) { + for (byte i = bgn; i <= end; i++) + Add(new byte[] {i}); + } + byte Add_rng_extract(GfoMsg m, String key) { + byte[] bry = m.ReadBry(key); + if (bry.length != 1) throw Err_.new_wo_type("argument must be ascii character", "key", key, "bry", String_.new_u8(bry)); + return bry[0]; + } +} diff --git a/400_xowa/src/gplx/xowa/langs/lnki_trails/Xol_lnki_trail_mgr_tst.java b/400_xowa/src/gplx/xowa/langs/lnki_trails/Xol_lnki_trail_mgr_tst.java index a27517de8..dacaa9f1c 100644 --- a/400_xowa/src/gplx/xowa/langs/lnki_trails/Xol_lnki_trail_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/langs/lnki_trails/Xol_lnki_trail_mgr_tst.java @@ -13,3 +13,29 @@ 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.langs.lnki_trails; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import org.junit.*; +public class Xol_lnki_trail_mgr_tst { + @Before public void init() {fxt.Clear();} private Xol_lnki_trail_mgr_fxt fxt = new Xol_lnki_trail_mgr_fxt(); + @Test public void Add_bulk() { + fxt.Test_add_bulk("äöüß", "ä", "ö", "ü", "ß"); + } +} +class Xol_lnki_trail_mgr_fxt { + Xol_lang_itm lang; Xol_lnki_trail_mgr lnki_trail_mgr; + public void Clear() { + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + lang = new Xol_lang_itm(app.Lang_mgr(), Bry_.new_a7("fr")); + lnki_trail_mgr = lang.Lnki_trail_mgr(); + } + public void Test_add_bulk(String raw, String... expd_ary) { + lnki_trail_mgr.Add_bulk(Bry_.new_u8(raw)); + int expd_len = expd_ary.length; + Tfds.Eq(expd_len, lang.Lnki_trail_mgr().Count()); + for (int i = 0; i < expd_len; i++) { + byte[] expd_bry = Bry_.new_u8(expd_ary[i]); + byte[] actl_bry = (byte[])lnki_trail_mgr.Trie().Match_bgn(expd_bry, 0, expd_bry.length); + Tfds.Eq_bry(expd_bry, actl_bry); + } + } +} diff --git a/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_itm.java b/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_itm.java index a27517de8..385c2492c 100644 --- a/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_itm.java +++ b/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_itm.java @@ -13,3 +13,34 @@ 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.langs.msgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.brys.fmtrs.*; +public class Xol_msg_itm { + public Xol_msg_itm(int id, byte[] key) {this.id = id; this.key = key;} + public int Id() {return id;} private final int id; + public byte[] Key() {return key;} private final byte[] key; + public byte[] Val() {return val;} private byte[] val; + public int Defined_in() {return defined_in;} private int defined_in; + public boolean Defined_in_none() {return defined_in == Defined_in__none;} + public boolean Has_fmt_arg() {return has_fmt_arg;} private boolean has_fmt_arg; + public boolean Has_tmpl_txt() {return has_tmpl_txt;} private boolean has_tmpl_txt; + public boolean Dirty() {return dirty;} private boolean dirty; // BLDR: + + public Xol_msg_itm Defined_in_(int v) {defined_in = v; return this;} + public Xol_msg_itm Dirty_(boolean v) {dirty = v; return this;} + + public void Atrs_set(byte[] val, boolean has_fmt_arg, boolean has_tmpl_txt) { + this.val = val; this.has_fmt_arg = has_fmt_arg; this.has_tmpl_txt = has_tmpl_txt; + } + public byte[] Fmt(Bry_bfr bfr, Bry_fmtr fmtr, Object... args) { + fmtr.Fmt_(val); + fmtr.Bld_bfr_many(bfr, args); + return bfr.To_bry_and_clear(); + } + public byte[] Fmt_tmp(Bry_bfr bfr, Bry_fmtr fmtr, byte[] tmp_val, Object... args) { + fmtr.Fmt_(tmp_val); + fmtr.Bld_bfr_many(bfr, args); + return bfr.To_bry_and_clear(); + } + public static final int Defined_in__unknown = 0, Defined_in__lang = 1, Defined_in__wiki = 2, Defined_in__none = 3; // NOTE: unknown not manually used, but is different than none (which means missing?) +} diff --git a/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_itm_.java b/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_itm_.java index a27517de8..4ff4aabe5 100644 --- a/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_itm_.java +++ b/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_itm_.java @@ -13,3 +13,512 @@ 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.langs.msgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.btries.*; import gplx.core.brys.fmtrs.*; import gplx.xowa.parsers.tmpls.*; +public class Xol_msg_itm_ { +public static final int + Id_dte_dow_name_sunday = 0 +, Id_dte_dow_name_monday = 1 +, Id_dte_dow_name_tuesday = 2 +, Id_dte_dow_name_wednesday = 3 +, Id_dte_dow_name_thursday = 4 +, Id_dte_dow_name_friday = 5 +, Id_dte_dow_name_saturday = 6 +, Id_dte_dow_abrv_sun = 7 +, Id_dte_dow_abrv_mon = 8 +, Id_dte_dow_abrv_tue = 9 +, Id_dte_dow_abrv_wed = 10 +, Id_dte_dow_abrv_thu = 11 +, Id_dte_dow_abrv_fri = 12 +, Id_dte_dow_abrv_sat = 13 +, Id_dte_month_name_january = 14 +, Id_dte_month_name_february = 15 +, Id_dte_month_name_march = 16 +, Id_dte_month_name_april = 17 +, Id_dte_month_name_may = 18 +, Id_dte_month_name_june = 19 +, Id_dte_month_name_july = 20 +, Id_dte_month_name_august = 21 +, Id_dte_month_name_september = 22 +, Id_dte_month_name_october = 23 +, Id_dte_month_name_november = 24 +, Id_dte_month_name_december = 25 +, Id_dte_month_gen_january = 26 +, Id_dte_month_gen_february = 27 +, Id_dte_month_gen_march = 28 +, Id_dte_month_gen_april = 29 +, Id_dte_month_gen_may = 30 +, Id_dte_month_gen_june = 31 +, Id_dte_month_gen_july = 32 +, Id_dte_month_gen_august = 33 +, Id_dte_month_gen_september = 34 +, Id_dte_month_gen_october = 35 +, Id_dte_month_gen_november = 36 +, Id_dte_month_gen_december = 37 +, Id_dte_month_abrv_jan = 38 +, Id_dte_month_abrv_feb = 39 +, Id_dte_month_abrv_mar = 40 +, Id_dte_month_abrv_apr = 41 +, Id_dte_month_abrv_may = 42 +, Id_dte_month_abrv_jun = 43 +, Id_dte_month_abrv_jul = 44 +, Id_dte_month_abrv_aug = 45 +, Id_dte_month_abrv_sep = 46 +, Id_dte_month_abrv_oct = 47 +, Id_dte_month_abrv_nov = 48 +, Id_dte_month_abrv_dec = 49 +, Id_pfunc_desc = 50 +, Id_pfunc_time_error = 51 +, Id_pfunc_time_too_long = 52 +, Id_pfunc_rel2abs_invalid_depth = 53 +, Id_pfunc_expr_stack_exhausted = 54 +, Id_pfunc_expr_unexpected_number = 55 +, Id_pfunc_expr_preg_match_failure = 56 +, Id_pfunc_expr_unrecognised_word = 57 +, Id_pfunc_expr_unexpected_operator = 58 +, Id_pfunc_expr_missing_operand = 59 +, Id_pfunc_expr_unexpected_closing_bracket = 60 +, Id_pfunc_expr_unrecognised_punctuation = 61 +, Id_pfunc_expr_unclosed_bracket = 62 +, Id_pfunc_expr_division_by_zero = 63 +, Id_pfunc_expr_invalid_argument = 64 +, Id_pfunc_expr_invalid_argument_ln = 65 +, Id_pfunc_expr_unknown_error = 66 +, Id_pfunc_expr_not_a_number = 67 +, Id_pfunc_string_too_long = 68 +, Id_pfunc_convert_dimensionmismatch = 69 +, Id_pfunc_convert_unknownunit = 70 +, Id_pfunc_convert_unknowndimension = 71 +, Id_pfunc_convert_invalidcompoundunit = 72 +, Id_pfunc_convert_nounit = 73 +, Id_pfunc_convert_doublecompoundunit = 74 +, Id_toc = 75 +, Id_redirectedfrom = 76 +, Id_sp_allpages_hdr = 77 +, Id_sp_allpages_bwd = 78 +, Id_sp_allpages_fwd = 79 +, Id_js_tables_collapsible_collapse = 80 +, Id_js_tables_collapsible_expand = 81 +, Id_js_tables_sort_descending = 82 +, Id_js_tables_sort_ascending = 83 +, Id_ctg_tbl_hdr = 84 +, Id_portal_lastmodified = 85 +, Id_file_enlarge = 86 +, Id_xowa_portal_exit_text = 87 +, Id_xowa_portal_exit_tooltip = 88 +, Id_xowa_portal_exit_accesskey = 89 +, Id_xowa_portal_view_html_text = 90 +, Id_xowa_portal_view_html_tooltip = 91 +, Id_xowa_portal_view_html_accesskey = 92 +, Id_xowa_portal_bookmarks_text = 93 +, Id_xowa_portal_bookmarks_add_text = 94 +, Id_xowa_portal_bookmarks_add_tooltip = 95 +, Id_xowa_portal_bookmarks_add_accesskey = 96 +, Id_xowa_portal_bookmarks_show_text = 97 +, Id_xowa_portal_bookmarks_show_tooltip = 98 +, Id_xowa_portal_bookmarks_show_accesskey = 99 +, Id_xowa_portal_page_history_text = 100 +, Id_xowa_portal_page_history_tooltip = 101 +, Id_xowa_portal_page_history_accesskey = 102 +, Id_xowa_portal_options_text = 103 +, Id_xowa_portal_options_tooltip = 104 +, Id_xowa_portal_options_accesskey = 105 +, Id_xowa_portal_version_text = 106 +, Id_xowa_portal_build_time_text = 107 +, Id_page_lang_header = 108 +, Id_xowa_window_go_bwd_btn_tooltip = 109 +, Id_xowa_window_go_fwd_btn_tooltip = 110 +, Id_xowa_window_url_box_tooltip = 111 +, Id_xowa_window_url_btn_tooltip = 112 +, Id_xowa_window_search_box_tooltip = 113 +, Id_xowa_window_search_btn_tooltip = 114 +, Id_xowa_window_find_close_btn_tooltip = 115 +, Id_xowa_window_find_box_tooltip = 116 +, Id_xowa_window_find_fwd_btn_tooltip = 117 +, Id_xowa_window_find_bwd_btn_tooltip = 118 +, Id_xowa_window_prog_box_tooltip = 119 +, Id_xowa_window_info_box_tooltip = 120 +, Id_scribunto_parser_error = 121 +, Id_ns_blankns = 122 +, Id_ctg_page_label = 123 +, Id_ctg_page_header = 124 +, Id_ctg_subc_header = 125 +, Id_ctg_file_header = 126 +, Id_ctg_empty = 127 +, Id_ctg_subc_count = 128 +, Id_ctg_subc_count_limit = 129 +, Id_ctg_page_count = 130 +, Id_ctg_page_count_limit = 131 +, Id_ctg_file_count = 132 +, Id_ctg_file_count_limit = 133 +, Id_ctgtree_subc_counts = 134 +, Id_ctgtree_subc_counts_ctg = 135 +, Id_ctgtree_subc_counts_page = 136 +, Id_ctgtree_subc_counts_file = 137 +, Id_next_results = 138 +, Id_prev_results = 139 +, Id_list_continues = 140 +, Id_xowa_wikidata_languages = 141 +, Id_xowa_wikidata_labels = 142 +, Id_xowa_wikidata_aliasesHead = 143 +, Id_xowa_wikidata_descriptions = 144 +, Id_xowa_wikidata_links = 145 +, Id_xowa_wikidata_claims = 146 +, Id_xowa_wikidata_json = 147 +, Id_xowa_wikidata_language = 148 +, Id_xowa_wikidata_wiki = 149 +, Id_xowa_wikidata_label = 150 +, Id_xowa_wikidata_aliases = 151 +, Id_xowa_wikidata_description = 152 +, Id_xowa_wikidata_link = 153 +, Id_xowa_wikidata_property = 154 +, Id_xowa_wikidata_value = 155 +, Id_xowa_wikidata_references = 156 +, Id_xowa_wikidata_qualifiers = 157 +, Id_search_results_header = 158 +, Id_edit_toolbar_bold_sample = 159 +, Id_edit_toolbar_bold_tip = 160 +, Id_edit_toolbar_italic_sample = 161 +, Id_edit_toolbar_italic_tip = 162 +, Id_edit_toolbar_link_sample = 163 +, Id_edit_toolbar_link_tip = 164 +, Id_edit_toolbar_headline_sample = 165 +, Id_edit_toolbar_headline_tip = 166 +, Id_edit_toolbar_ulist_tip = 167 +, Id_edit_toolbar_ulist_sample = 168 +, Id_edit_toolbar_olist_tip = 169 +, Id_edit_toolbar_olist_sample = 170 +, Id_xowa_portal_about_text = 171 +, Id_xowa_portal_about_tooltip = 172 +, Id_xowa_portal_about_accesskey = 173 +, Id_symbol_catseparator = 174 +, Id_symbol_semicolon_separator = 175 +, Id_symbol_comma_separator = 176 +, Id_symbol_colon_separator = 177 +, Id_symbol_autocomment_prefix = 178 +, Id_symbol_pipe_separator = 179 +, Id_symbol_word_separator = 180 +, Id_symbol_ellipsis = 181 +, Id_symbol_percent = 182 +, Id_symbol_parentheses = 183 +, Id_duration_ago = 184 +, Id_xowa_wikidata_novalue = 185 +, Id_xowa_wikidata_somevalue = 186 +, Id_xowa_wikidata_links_wiki = 187 +, Id_xowa_wikidata_links_wiktionary = 188 +, Id_xowa_wikidata_links_wikisource = 189 +, Id_xowa_wikidata_links_wikivoyage = 190 +, Id_xowa_wikidata_links_wikiquote = 191 +, Id_xowa_wikidata_links_wikibooks = 192 +, Id_xowa_wikidata_links_wikiversity = 193 +, Id_xowa_wikidata_links_wikinews = 194 +, Id_xowa_wikidata_plus = 195 +, Id_xowa_wikidata_minus = 196 +, Id_xowa_wikidata_plusminus = 197 +, Id_xowa_wikidata_degree = 198 +, Id_xowa_wikidata_minute = 199 +, Id_xowa_wikidata_second = 200 +, Id_xowa_wikidata_north = 201 +, Id_xowa_wikidata_south = 202 +, Id_xowa_wikidata_west = 203 +, Id_xowa_wikidata_east = 204 +, Id_xowa_wikidata_meters = 205 +, Id_xowa_wikidata_julian = 206 +, Id_xowa_wikidata_year = 207 +, Id_xowa_wikidata_decade = 208 +, Id_xowa_wikidata_century = 209 +, Id_xowa_wikidata_millenium = 210 +, Id_xowa_wikidata_years1e4 = 211 +, Id_xowa_wikidata_years1e5 = 212 +, Id_xowa_wikidata_years1e6 = 213 +, Id_xowa_wikidata_years1e7 = 214 +, Id_xowa_wikidata_years1e8 = 215 +, Id_xowa_wikidata_years1e9 = 216 +, Id_xowa_wikidata_bc = 217 +, Id_xowa_wikidata_inTime = 218 +, Id_ctg_tbl_hidden = 219 +, Id_ctg_help_page = 220 +, Id_statistics_title = 221 +, Id_statistics_header_pages = 222 +, Id_statistics_articles = 223 +, Id_statistics_pages = 224 +, Id_statistics_pages_desc = 225 +, Id_statistics_header_ns = 226 +, Id_wikibase_diffview_rank = 227 +, Id_xowa_wikidata_deprecated = 228 +, Id_xowa_wikidata_normal = 229 +, Id_xowa_wikidata_preferred = 230 +, Id_xowa_wikidata_links_special = 231 +, Id_xowa_window_allpages_box_tooltip = 232 +, Id_xowa_window_allpages_btn_tooltip = 233 +; + public static final int Id__max = 234; + public static Xol_msg_itm new_(int id, String key, String val) {return new_(id, Bry_.new_u8(key), Bry_.new_u8(val));} + public static Xol_msg_itm new_(int id, byte[] key, byte[] val) { + Xol_msg_itm rv = new Xol_msg_itm(id, key); + update_val_(rv, val); + return rv; + } + private static final Bry_fmtr tmp_fmtr = Bry_fmtr.New__tmp().Fail_when_invalid_escapes_(false); + private static final Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); + public static void update_val_(Xol_msg_itm itm, byte[] val) { + synchronized (tmp_fmtr) { // LOCK:static-objs; DATE:2016-07-07 + boolean has_fmt_arg = tmp_fmtr.Fmt_(val).Compile().Fmt_args_exist(); + boolean has_tmpl_txt = Bry_find_.Find_fwd(val, Xop_curly_bgn_lxr.Hook, 0) != -1; + val = trie_space.Replace(tmp_bfr, val, 0, val.length); + itm.Atrs_set(val, has_fmt_arg, has_tmpl_txt); + } + } + public static final byte[] Bry_nbsp = Byte_.Ary_by_ints(194, 160); + private static final Btrie_slim_mgr trie_space = Btrie_slim_mgr.cs() // MW:cache/MessageCache.php|get|Fix for trailing whitespace, removed by textarea|DATE:2014-04-29 + .Add_bry(" " , " ") + .Add_bry(" " , Bry_nbsp) + .Add_bry(" " , Bry_nbsp) + ; + public static Xol_msg_itm new_(int id) { + switch (id) { + case Xol_msg_itm_.Id_dte_dow_name_sunday: return new_(Xol_msg_itm_.Id_dte_dow_name_sunday, "sunday", "Sunday"); + case Xol_msg_itm_.Id_dte_dow_name_monday: return new_(Xol_msg_itm_.Id_dte_dow_name_monday, "monday", "Monday"); + case Xol_msg_itm_.Id_dte_dow_name_tuesday: return new_(Xol_msg_itm_.Id_dte_dow_name_tuesday, "tuesday", "Tuesday"); + case Xol_msg_itm_.Id_dte_dow_name_wednesday: return new_(Xol_msg_itm_.Id_dte_dow_name_wednesday, "wednesday", "Wednesday"); + case Xol_msg_itm_.Id_dte_dow_name_thursday: return new_(Xol_msg_itm_.Id_dte_dow_name_thursday, "thursday", "Thursday"); + case Xol_msg_itm_.Id_dte_dow_name_friday: return new_(Xol_msg_itm_.Id_dte_dow_name_friday, "friday", "Friday"); + case Xol_msg_itm_.Id_dte_dow_name_saturday: return new_(Xol_msg_itm_.Id_dte_dow_name_saturday, "saturday", "Saturday"); + case Xol_msg_itm_.Id_dte_dow_abrv_sun: return new_(Xol_msg_itm_.Id_dte_dow_abrv_sun, "sun", "Sun"); + case Xol_msg_itm_.Id_dte_dow_abrv_mon: return new_(Xol_msg_itm_.Id_dte_dow_abrv_mon, "mon", "Mon"); + case Xol_msg_itm_.Id_dte_dow_abrv_tue: return new_(Xol_msg_itm_.Id_dte_dow_abrv_tue, "tue", "Tue"); + case Xol_msg_itm_.Id_dte_dow_abrv_wed: return new_(Xol_msg_itm_.Id_dte_dow_abrv_wed, "wed", "Wed"); + case Xol_msg_itm_.Id_dte_dow_abrv_thu: return new_(Xol_msg_itm_.Id_dte_dow_abrv_thu, "thu", "Thu"); + case Xol_msg_itm_.Id_dte_dow_abrv_fri: return new_(Xol_msg_itm_.Id_dte_dow_abrv_fri, "fri", "Fri"); + case Xol_msg_itm_.Id_dte_dow_abrv_sat: return new_(Xol_msg_itm_.Id_dte_dow_abrv_sat, "sat", "Sat"); + case Xol_msg_itm_.Id_dte_month_name_january: return new_(Xol_msg_itm_.Id_dte_month_name_january, "january", "January"); + case Xol_msg_itm_.Id_dte_month_name_february: return new_(Xol_msg_itm_.Id_dte_month_name_february, "february", "February"); + case Xol_msg_itm_.Id_dte_month_name_march: return new_(Xol_msg_itm_.Id_dte_month_name_march, "march", "March"); + case Xol_msg_itm_.Id_dte_month_name_april: return new_(Xol_msg_itm_.Id_dte_month_name_april, "april", "April"); + case Xol_msg_itm_.Id_dte_month_name_may: return new_(Xol_msg_itm_.Id_dte_month_name_may, "may_long", "May"); + case Xol_msg_itm_.Id_dte_month_name_june: return new_(Xol_msg_itm_.Id_dte_month_name_june, "june", "June"); + case Xol_msg_itm_.Id_dte_month_name_july: return new_(Xol_msg_itm_.Id_dte_month_name_july, "july", "July"); + case Xol_msg_itm_.Id_dte_month_name_august: return new_(Xol_msg_itm_.Id_dte_month_name_august, "august", "August"); + case Xol_msg_itm_.Id_dte_month_name_september: return new_(Xol_msg_itm_.Id_dte_month_name_september, "september", "September"); + case Xol_msg_itm_.Id_dte_month_name_october: return new_(Xol_msg_itm_.Id_dte_month_name_october, "october", "October"); + case Xol_msg_itm_.Id_dte_month_name_november: return new_(Xol_msg_itm_.Id_dte_month_name_november, "november", "November"); + case Xol_msg_itm_.Id_dte_month_name_december: return new_(Xol_msg_itm_.Id_dte_month_name_december, "december", "December"); + case Xol_msg_itm_.Id_dte_month_gen_january: return new_(Xol_msg_itm_.Id_dte_month_gen_january, "january-gen", "January"); + case Xol_msg_itm_.Id_dte_month_gen_february: return new_(Xol_msg_itm_.Id_dte_month_gen_february, "february-gen", "February"); + case Xol_msg_itm_.Id_dte_month_gen_march: return new_(Xol_msg_itm_.Id_dte_month_gen_march, "march-gen", "March"); + case Xol_msg_itm_.Id_dte_month_gen_april: return new_(Xol_msg_itm_.Id_dte_month_gen_april, "april-gen", "April"); + case Xol_msg_itm_.Id_dte_month_gen_may: return new_(Xol_msg_itm_.Id_dte_month_gen_may, "may-gen", "May"); + case Xol_msg_itm_.Id_dte_month_gen_june: return new_(Xol_msg_itm_.Id_dte_month_gen_june, "june-gen", "June"); + case Xol_msg_itm_.Id_dte_month_gen_july: return new_(Xol_msg_itm_.Id_dte_month_gen_july, "july-gen", "July"); + case Xol_msg_itm_.Id_dte_month_gen_august: return new_(Xol_msg_itm_.Id_dte_month_gen_august, "august-gen", "August"); + case Xol_msg_itm_.Id_dte_month_gen_september: return new_(Xol_msg_itm_.Id_dte_month_gen_september, "september-gen", "September"); + case Xol_msg_itm_.Id_dte_month_gen_october: return new_(Xol_msg_itm_.Id_dte_month_gen_october, "october-gen", "October"); + case Xol_msg_itm_.Id_dte_month_gen_november: return new_(Xol_msg_itm_.Id_dte_month_gen_november, "november-gen", "November"); + case Xol_msg_itm_.Id_dte_month_gen_december: return new_(Xol_msg_itm_.Id_dte_month_gen_december, "december-gen", "December"); + case Xol_msg_itm_.Id_dte_month_abrv_jan: return new_(Xol_msg_itm_.Id_dte_month_abrv_jan, "jan", "Jan"); + case Xol_msg_itm_.Id_dte_month_abrv_feb: return new_(Xol_msg_itm_.Id_dte_month_abrv_feb, "feb", "Feb"); + case Xol_msg_itm_.Id_dte_month_abrv_mar: return new_(Xol_msg_itm_.Id_dte_month_abrv_mar, "mar", "Mar"); + case Xol_msg_itm_.Id_dte_month_abrv_apr: return new_(Xol_msg_itm_.Id_dte_month_abrv_apr, "apr", "Apr"); + case Xol_msg_itm_.Id_dte_month_abrv_may: return new_(Xol_msg_itm_.Id_dte_month_abrv_may, "may", "May"); + case Xol_msg_itm_.Id_dte_month_abrv_jun: return new_(Xol_msg_itm_.Id_dte_month_abrv_jun, "jun", "Jun"); + case Xol_msg_itm_.Id_dte_month_abrv_jul: return new_(Xol_msg_itm_.Id_dte_month_abrv_jul, "jul", "Jul"); + case Xol_msg_itm_.Id_dte_month_abrv_aug: return new_(Xol_msg_itm_.Id_dte_month_abrv_aug, "aug", "Aug"); + case Xol_msg_itm_.Id_dte_month_abrv_sep: return new_(Xol_msg_itm_.Id_dte_month_abrv_sep, "sep", "Sep"); + case Xol_msg_itm_.Id_dte_month_abrv_oct: return new_(Xol_msg_itm_.Id_dte_month_abrv_oct, "oct", "Oct"); + case Xol_msg_itm_.Id_dte_month_abrv_nov: return new_(Xol_msg_itm_.Id_dte_month_abrv_nov, "nov", "Nov"); + case Xol_msg_itm_.Id_dte_month_abrv_dec: return new_(Xol_msg_itm_.Id_dte_month_abrv_dec, "dec", "Dec"); + case Xol_msg_itm_.Id_pfunc_desc: return new_(Xol_msg_itm_.Id_pfunc_desc, "pfunc_desc", "Enhance parser with logical functions"); + case Xol_msg_itm_.Id_pfunc_time_error: return new_(Xol_msg_itm_.Id_pfunc_time_error, "pfunc_time_error", "Error: invalid time"); + case Xol_msg_itm_.Id_pfunc_time_too_long: return new_(Xol_msg_itm_.Id_pfunc_time_too_long, "pfunc_time_too_long", "Error: too many #time calls"); + case Xol_msg_itm_.Id_pfunc_rel2abs_invalid_depth: return new_(Xol_msg_itm_.Id_pfunc_rel2abs_invalid_depth, "pfunc_rel2abs_invalid_depth", "Error: Invalid depth in path: \"~{0}\" (tried to access a node above the root node)"); + case Xol_msg_itm_.Id_pfunc_expr_stack_exhausted: return new_(Xol_msg_itm_.Id_pfunc_expr_stack_exhausted, "pfunc_expr_stack_exhausted", "Expression error: Stack exhausted"); + case Xol_msg_itm_.Id_pfunc_expr_unexpected_number: return new_(Xol_msg_itm_.Id_pfunc_expr_unexpected_number, "pfunc_expr_unexpected_number", "Expression error: Unexpected number"); + case Xol_msg_itm_.Id_pfunc_expr_preg_match_failure: return new_(Xol_msg_itm_.Id_pfunc_expr_preg_match_failure, "pfunc_expr_preg_match_failure", "Expression error: Unexpected preg_match failure"); + case Xol_msg_itm_.Id_pfunc_expr_unrecognised_word: return new_(Xol_msg_itm_.Id_pfunc_expr_unrecognised_word, "pfunc_expr_unrecognised_word", "Expression error: Unrecognised word \"~{0}\""); + case Xol_msg_itm_.Id_pfunc_expr_unexpected_operator: return new_(Xol_msg_itm_.Id_pfunc_expr_unexpected_operator, "pfunc_expr_unexpected_operator", "Expression error: Unexpected ~{0} operator"); + case Xol_msg_itm_.Id_pfunc_expr_missing_operand: return new_(Xol_msg_itm_.Id_pfunc_expr_missing_operand, "pfunc_expr_missing_operand", "Expression error: Missing operand for ~{0}"); + case Xol_msg_itm_.Id_pfunc_expr_unexpected_closing_bracket: return new_(Xol_msg_itm_.Id_pfunc_expr_unexpected_closing_bracket, "pfunc_expr_unexpected_closing_bracket", "Expression error: Unexpected closing bracket"); + case Xol_msg_itm_.Id_pfunc_expr_unrecognised_punctuation: return new_(Xol_msg_itm_.Id_pfunc_expr_unrecognised_punctuation, "pfunc_expr_unrecognised_punctuation", "Expression error: Unrecognised punctuation character \"~{0}\""); + case Xol_msg_itm_.Id_pfunc_expr_unclosed_bracket: return new_(Xol_msg_itm_.Id_pfunc_expr_unclosed_bracket, "pfunc_expr_unclosed_bracket", "Expression error: Unclosed bracket"); + case Xol_msg_itm_.Id_pfunc_expr_division_by_zero: return new_(Xol_msg_itm_.Id_pfunc_expr_division_by_zero, "pfunc_expr_division_by_zero", "Division by zero"); + case Xol_msg_itm_.Id_pfunc_expr_invalid_argument: return new_(Xol_msg_itm_.Id_pfunc_expr_invalid_argument, "pfunc_expr_invalid_argument", "Invalid argument for ~{0}: < -1 or > 1"); + case Xol_msg_itm_.Id_pfunc_expr_invalid_argument_ln: return new_(Xol_msg_itm_.Id_pfunc_expr_invalid_argument_ln, "pfunc_expr_invalid_argument_ln", "Invalid argument for ln: <= 0"); + case Xol_msg_itm_.Id_pfunc_expr_unknown_error: return new_(Xol_msg_itm_.Id_pfunc_expr_unknown_error, "pfunc_expr_unknown_error", "Expression error: Unknown error (~{0})"); + case Xol_msg_itm_.Id_pfunc_expr_not_a_number: return new_(Xol_msg_itm_.Id_pfunc_expr_not_a_number, "pfunc_expr_not_a_number", "In ~{0}: result is not a number"); + case Xol_msg_itm_.Id_pfunc_string_too_long: return new_(Xol_msg_itm_.Id_pfunc_string_too_long, "pfunc_string_too_long", "Error: String exceeds ~{0} character limit"); + case Xol_msg_itm_.Id_pfunc_convert_dimensionmismatch: return new_(Xol_msg_itm_.Id_pfunc_convert_dimensionmismatch, "pfunc-convert-dimensionmismatch", "Error: cannot convert between units of \"~{0}\" and \"~{1}\""); + case Xol_msg_itm_.Id_pfunc_convert_unknownunit: return new_(Xol_msg_itm_.Id_pfunc_convert_unknownunit, "pfunc-convert-unknownunit", "Error: unknown unit \"~{0}\""); + case Xol_msg_itm_.Id_pfunc_convert_unknowndimension: return new_(Xol_msg_itm_.Id_pfunc_convert_unknowndimension, "pfunc-convert-unknowndimension", "Error: unknown dimension \"~{0}\""); + case Xol_msg_itm_.Id_pfunc_convert_invalidcompoundunit: return new_(Xol_msg_itm_.Id_pfunc_convert_invalidcompoundunit, "pfunc-convert-invalidcompoundunit", "Error: invalid compound unit \"~{0}\""); + case Xol_msg_itm_.Id_pfunc_convert_nounit: return new_(Xol_msg_itm_.Id_pfunc_convert_nounit, "pfunc-convert-nounit", "Error: no source unit given"); + case Xol_msg_itm_.Id_pfunc_convert_doublecompoundunit: return new_(Xol_msg_itm_.Id_pfunc_convert_doublecompoundunit, "pfunc-convert-doublecompoundunit", "Error: cannot parse double compound units like \"~{0}\""); + case Xol_msg_itm_.Id_toc: return new_(Xol_msg_itm_.Id_toc, "toc", "Contents"); + case Xol_msg_itm_.Id_redirectedfrom: return new_(Xol_msg_itm_.Id_redirectedfrom, "redirectedfrom", "(Redirected from ~{0})"); + case Xol_msg_itm_.Id_sp_allpages_hdr: return new_(Xol_msg_itm_.Id_sp_allpages_hdr, "allpages", "All pages"); + case Xol_msg_itm_.Id_sp_allpages_bwd: return new_(Xol_msg_itm_.Id_sp_allpages_bwd, "prevpage", "Previous page (~{0})"); + case Xol_msg_itm_.Id_sp_allpages_fwd: return new_(Xol_msg_itm_.Id_sp_allpages_fwd, "nextpage", "Next page (~{0})"); + case Xol_msg_itm_.Id_js_tables_collapsible_collapse: return new_(Xol_msg_itm_.Id_js_tables_collapsible_collapse, "collapsible-collapse", "Collapse"); + case Xol_msg_itm_.Id_js_tables_collapsible_expand: return new_(Xol_msg_itm_.Id_js_tables_collapsible_expand, "collapsible-expand", "Expand"); + case Xol_msg_itm_.Id_js_tables_sort_descending: return new_(Xol_msg_itm_.Id_js_tables_sort_descending, "sort-descending", "Sort descending"); + case Xol_msg_itm_.Id_js_tables_sort_ascending: return new_(Xol_msg_itm_.Id_js_tables_sort_ascending, "sort-ascending", "Sort ascending"); + case Xol_msg_itm_.Id_ctg_tbl_hdr: return new_(Xol_msg_itm_.Id_ctg_tbl_hdr, "categories", "Categories"); + case Xol_msg_itm_.Id_portal_lastmodified: return new_(Xol_msg_itm_.Id_portal_lastmodified, "lastmodifiedat", "This page was last modified on ~{0}, at ~{1}."); + case Xol_msg_itm_.Id_file_enlarge: return new_(Xol_msg_itm_.Id_file_enlarge, "thumbnail-more", "Enlarge"); +case Xol_msg_itm_.Id_xowa_portal_exit_text: return new_(Xol_msg_itm_.Id_xowa_portal_exit_text, "xowa-portal-exit", "Exit"); +case Xol_msg_itm_.Id_xowa_portal_exit_tooltip: return new_(Xol_msg_itm_.Id_xowa_portal_exit_tooltip, "tooltip-xowa-portal-exit", "Exit XOWA"); +case Xol_msg_itm_.Id_xowa_portal_exit_accesskey: return new_(Xol_msg_itm_.Id_xowa_portal_exit_accesskey, "accesskey-xowa-portal-exit", ""); +case Xol_msg_itm_.Id_xowa_portal_view_html_text: return new_(Xol_msg_itm_.Id_xowa_portal_view_html_text, "xowa-portal-view_html", "View HTML"); +case Xol_msg_itm_.Id_xowa_portal_view_html_tooltip: return new_(Xol_msg_itm_.Id_xowa_portal_view_html_tooltip, "tooltip-xowa-portal-view_html", "View HTML source for this page"); +case Xol_msg_itm_.Id_xowa_portal_view_html_accesskey: return new_(Xol_msg_itm_.Id_xowa_portal_view_html_accesskey, "accesskey-xowa-portal-view_html", "h"); +case Xol_msg_itm_.Id_xowa_portal_bookmarks_text: return new_(Xol_msg_itm_.Id_xowa_portal_bookmarks_text, "xowa-portal-bookmarks", "Bookmarks"); +case Xol_msg_itm_.Id_xowa_portal_bookmarks_add_text: return new_(Xol_msg_itm_.Id_xowa_portal_bookmarks_add_text, "xowa-portal-bookmarks-add", "Bookmark this page"); +case Xol_msg_itm_.Id_xowa_portal_bookmarks_add_tooltip: return new_(Xol_msg_itm_.Id_xowa_portal_bookmarks_add_tooltip, "tooltip-xowa-portal-bookmarks-add", "Bookmark this page"); +case Xol_msg_itm_.Id_xowa_portal_bookmarks_add_accesskey: return new_(Xol_msg_itm_.Id_xowa_portal_bookmarks_add_accesskey, "accesskey-xowa-portal-bookmarks-add", ""); +case Xol_msg_itm_.Id_xowa_portal_bookmarks_show_text: return new_(Xol_msg_itm_.Id_xowa_portal_bookmarks_show_text, "xowa-portal-bookmarks-show", "Show all bookmarks"); +case Xol_msg_itm_.Id_xowa_portal_bookmarks_show_tooltip: return new_(Xol_msg_itm_.Id_xowa_portal_bookmarks_show_tooltip, "tooltip-xowa-portal-bookmarks-show", "Show all bookmarks"); +case Xol_msg_itm_.Id_xowa_portal_bookmarks_show_accesskey: return new_(Xol_msg_itm_.Id_xowa_portal_bookmarks_show_accesskey, "accesskey-xowa-portal-bookmarks-show", ""); +case Xol_msg_itm_.Id_xowa_portal_page_history_text: return new_(Xol_msg_itm_.Id_xowa_portal_page_history_text, "xowa-portal-page_history", "Page history"); +case Xol_msg_itm_.Id_xowa_portal_page_history_tooltip: return new_(Xol_msg_itm_.Id_xowa_portal_page_history_tooltip, "tooltip-xowa-portal-page_history", "View history of opened pages"); +case Xol_msg_itm_.Id_xowa_portal_page_history_accesskey: return new_(Xol_msg_itm_.Id_xowa_portal_page_history_accesskey, "accesskey-xowa-portal-page_history", ""); +case Xol_msg_itm_.Id_xowa_portal_options_text: return new_(Xol_msg_itm_.Id_xowa_portal_options_text, "xowa-portal-options", "Options"); +case Xol_msg_itm_.Id_xowa_portal_options_tooltip: return new_(Xol_msg_itm_.Id_xowa_portal_options_tooltip, "tooltip-xowa-portal-options", "Change XOWA options"); +case Xol_msg_itm_.Id_xowa_portal_options_accesskey: return new_(Xol_msg_itm_.Id_xowa_portal_options_accesskey, "accesskey-xowa-portal-options", ""); +case Xol_msg_itm_.Id_xowa_portal_version_text: return new_(Xol_msg_itm_.Id_xowa_portal_version_text, "xowa-portal-version", "version"); +case Xol_msg_itm_.Id_xowa_portal_build_time_text: return new_(Xol_msg_itm_.Id_xowa_portal_build_time_text, "xowa-portal-build_time", "build time"); +case Xol_msg_itm_.Id_page_lang_header: return new_(Xol_msg_itm_.Id_page_lang_header, "otherlanguages", "In other languages"); +case Xol_msg_itm_.Id_xowa_window_go_bwd_btn_tooltip: return new_(Xol_msg_itm_.Id_xowa_window_go_bwd_btn_tooltip, "xowa-window-go_bwd_btn-tooltip", "Go back one page"); +case Xol_msg_itm_.Id_xowa_window_go_fwd_btn_tooltip: return new_(Xol_msg_itm_.Id_xowa_window_go_fwd_btn_tooltip, "xowa-window-go_fwd_btn-tooltip", "Go forward one page"); +case Xol_msg_itm_.Id_xowa_window_url_box_tooltip: return new_(Xol_msg_itm_.Id_xowa_window_url_box_tooltip, "xowa-window-url_box-tooltip", "Enter address"); +case Xol_msg_itm_.Id_xowa_window_url_btn_tooltip: return new_(Xol_msg_itm_.Id_xowa_window_url_btn_tooltip, "xowa-window-url_btn-tooltip", "Go to the address in the Location Bar"); +case Xol_msg_itm_.Id_xowa_window_search_box_tooltip: return new_(Xol_msg_itm_.Id_xowa_window_search_box_tooltip, "xowa-window-search_box-tooltip", "Search using {{ns:Special}}:XowaSearch"); +case Xol_msg_itm_.Id_xowa_window_search_btn_tooltip: return new_(Xol_msg_itm_.Id_xowa_window_search_btn_tooltip, "xowa-window-search_btn-tooltip", "Search"); +case Xol_msg_itm_.Id_xowa_window_allpages_box_tooltip: return new_(Xol_msg_itm_.Id_xowa_window_allpages_box_tooltip, "xowa-window-allpages_box-tooltip", "List using {{ns:Special}}:AllPages"); +case Xol_msg_itm_.Id_xowa_window_allpages_btn_tooltip: return new_(Xol_msg_itm_.Id_xowa_window_allpages_btn_tooltip, "xowa-window-allpages_btn-tooltip", "List all pages"); +case Xol_msg_itm_.Id_xowa_window_find_close_btn_tooltip: return new_(Xol_msg_itm_.Id_xowa_window_find_close_btn_tooltip, "xowa-window-find_close_btn-tooltip", "Close Find bar"); +case Xol_msg_itm_.Id_xowa_window_find_box_tooltip: return new_(Xol_msg_itm_.Id_xowa_window_find_box_tooltip, "xowa-window-find_box-tooltip", "Enter phrase to find on this page"); +case Xol_msg_itm_.Id_xowa_window_find_fwd_btn_tooltip: return new_(Xol_msg_itm_.Id_xowa_window_find_fwd_btn_tooltip, "xowa-window-find_fwd_btn-tooltip", "Find the next occurrence of the phrase"); +case Xol_msg_itm_.Id_xowa_window_find_bwd_btn_tooltip: return new_(Xol_msg_itm_.Id_xowa_window_find_bwd_btn_tooltip, "xowa-window-find_bwd_btn-tooltip", "Find the previous occurrence of the phrase"); +case Xol_msg_itm_.Id_xowa_window_prog_box_tooltip: return new_(Xol_msg_itm_.Id_xowa_window_prog_box_tooltip, "xowa-window-prog_box-tooltip", "Displays progress messages"); +case Xol_msg_itm_.Id_xowa_window_info_box_tooltip: return new_(Xol_msg_itm_.Id_xowa_window_info_box_tooltip, "xowa-window-info_box-tooltip", "Displays system messages"); +case Xol_msg_itm_.Id_scribunto_parser_error: return new_(Xol_msg_itm_.Id_scribunto_parser_error, "scribunto-parser-error", "Script error"); +case Xol_msg_itm_.Id_ns_blankns: return new_(Xol_msg_itm_.Id_ns_blankns, "blanknamespace", "(Main)"); +case Xol_msg_itm_.Id_ctg_page_label: return new_(Xol_msg_itm_.Id_ctg_page_label, "pagecategories", "{{PLURAL:~{0}|Category|Categories}}"); +case Xol_msg_itm_.Id_ctg_page_header: return new_(Xol_msg_itm_.Id_ctg_page_header, "category_header", "Pages in category \"~{0}\""); +case Xol_msg_itm_.Id_ctg_subc_header: return new_(Xol_msg_itm_.Id_ctg_subc_header, "subcategories", "Subcategories"); +case Xol_msg_itm_.Id_ctg_file_header: return new_(Xol_msg_itm_.Id_ctg_file_header, "category-media-header", "Media in category \"~{0}\""); +case Xol_msg_itm_.Id_ctg_empty: return new_(Xol_msg_itm_.Id_ctg_empty, "category-empty", "''This category currently contains no pages or media.''"); +case Xol_msg_itm_.Id_ctg_subc_count: return new_(Xol_msg_itm_.Id_ctg_subc_count, "category-subcat-count", "{{PLURAL:~{1}|This category has only the following subcategory.|This category has the following {{PLURAL:~{0}|subcategory|~{0} subcategories}}, out of ~{1} total.}}"); +case Xol_msg_itm_.Id_ctg_subc_count_limit: return new_(Xol_msg_itm_.Id_ctg_subc_count_limit, "category-subcat-count-limited", "This category has the following {{PLURAL:~{0}|subcategory|~{0} subcategories}}."); +case Xol_msg_itm_.Id_ctg_page_count: return new_(Xol_msg_itm_.Id_ctg_page_count, "category-article-count", "{{PLURAL:~{1}|This category contains only the following page.|The following {{PLURAL:~{0}|page is|~{0} pages are}} in this category, out of ~{1} total.}}"); +case Xol_msg_itm_.Id_ctg_page_count_limit: return new_(Xol_msg_itm_.Id_ctg_page_count_limit, "category-article-count-limited", "The following {{PLURAL:~{0}|page is|~{0} pages are}} in the current category."); +case Xol_msg_itm_.Id_ctg_file_count: return new_(Xol_msg_itm_.Id_ctg_file_count, "category-file-count", "{{PLURAL:~{1}|This category contains only the following file.|The following {{PLURAL:~{0}|file is|~{0} files are}} in this category, out of ~{1} total.}}"); +case Xol_msg_itm_.Id_ctg_file_count_limit: return new_(Xol_msg_itm_.Id_ctg_file_count_limit, "category-file-count-limited", "The following {{PLURAL:~{0}|file is|~{0} files are}} in the current category."); +case Xol_msg_itm_.Id_ctgtree_subc_counts: return new_(Xol_msg_itm_.Id_ctgtree_subc_counts, "categorytree-member-counts", "contains {{PLURAL:~{0}|1 subcategory|~{0} subcategories}}, {{PLURAL:~{1}|1 page|~{1} pages}}, and {{PLURAL:~{2}|1 file|~{2} files}}"); +case Xol_msg_itm_.Id_ctgtree_subc_counts_ctg: return new_(Xol_msg_itm_.Id_ctgtree_subc_counts_ctg, "categorytree-num-categories", "~{0} C"); +case Xol_msg_itm_.Id_ctgtree_subc_counts_page: return new_(Xol_msg_itm_.Id_ctgtree_subc_counts_page, "categorytree-num-pages", "~{0} P"); +case Xol_msg_itm_.Id_ctgtree_subc_counts_file: return new_(Xol_msg_itm_.Id_ctgtree_subc_counts_file, "categorytree-num-files", "~{0} F"); +case Xol_msg_itm_.Id_prev_results: return new_(Xol_msg_itm_.Id_next_results, "prevn", "previous {{PLURAL:~{0}|~{0}}}"); +case Xol_msg_itm_.Id_next_results: return new_(Xol_msg_itm_.Id_prev_results, "nextn", "next {{PLURAL:~{0}|~{0}}}"); +case Xol_msg_itm_.Id_list_continues: return new_(Xol_msg_itm_.Id_list_continues, "listingcontinuesabbrev", "cont."); +case Xol_msg_itm_.Id_xowa_wikidata_languages: return new_(Xol_msg_itm_.Id_xowa_wikidata_languages, "xowa-wikidata-languages", "en"); +case Xol_msg_itm_.Id_xowa_wikidata_labels: return new_(Xol_msg_itm_.Id_xowa_wikidata_labels, "xowa-wikidata-labels", "Labels"); +case Xol_msg_itm_.Id_xowa_wikidata_aliasesHead: return new_(Xol_msg_itm_.Id_xowa_wikidata_aliasesHead, "xowa-wikidata-aliasesHead", "Aliases"); +case Xol_msg_itm_.Id_xowa_wikidata_descriptions: return new_(Xol_msg_itm_.Id_xowa_wikidata_descriptions, "xowa-wikidata-descriptions", "Descriptions"); +case Xol_msg_itm_.Id_xowa_wikidata_links: return new_(Xol_msg_itm_.Id_xowa_wikidata_links, "xowa-wikidata-links", "Links"); +case Xol_msg_itm_.Id_xowa_wikidata_claims: return new_(Xol_msg_itm_.Id_xowa_wikidata_claims, "xowa-wikidata-claims", "Claims"); +case Xol_msg_itm_.Id_xowa_wikidata_json: return new_(Xol_msg_itm_.Id_xowa_wikidata_json, "xowa-wikidata-json", "JSON"); +case Xol_msg_itm_.Id_xowa_wikidata_language: return new_(Xol_msg_itm_.Id_xowa_wikidata_language, "xowa-wikidata-language", "language"); +case Xol_msg_itm_.Id_xowa_wikidata_wiki: return new_(Xol_msg_itm_.Id_xowa_wikidata_wiki, "xowa-wikidata-wiki", "wiki"); +case Xol_msg_itm_.Id_xowa_wikidata_label: return new_(Xol_msg_itm_.Id_xowa_wikidata_label, "xowa-wikidata-label", "label"); +case Xol_msg_itm_.Id_xowa_wikidata_aliases: return new_(Xol_msg_itm_.Id_xowa_wikidata_aliases, "xowa-wikidata-aliases", "aliases"); +case Xol_msg_itm_.Id_xowa_wikidata_description: return new_(Xol_msg_itm_.Id_xowa_wikidata_description, "xowa-wikidata-description", "description"); +case Xol_msg_itm_.Id_xowa_wikidata_link: return new_(Xol_msg_itm_.Id_xowa_wikidata_link, "xowa-wikidata-link", "link"); +case Xol_msg_itm_.Id_xowa_wikidata_property: return new_(Xol_msg_itm_.Id_xowa_wikidata_property, "xowa-wikidata-property", "property"); +case Xol_msg_itm_.Id_xowa_wikidata_value: return new_(Xol_msg_itm_.Id_xowa_wikidata_value, "xowa-wikidata-value", "value"); +case Xol_msg_itm_.Id_xowa_wikidata_references: return new_(Xol_msg_itm_.Id_xowa_wikidata_references, "xowa-wikidata-references", "references"); +case Xol_msg_itm_.Id_xowa_wikidata_qualifiers: return new_(Xol_msg_itm_.Id_xowa_wikidata_qualifiers, "xowa-wikidata-qualifiers", "qualifiers"); +case Xol_msg_itm_.Id_search_results_header: return new_(Xol_msg_itm_.Id_search_results_header, "showingresultsheader", "{{PLURAL:~{4}\u007CResult '''~{0}''' of '''~{2}'''\u007CResults '''~{0} - ~{1}''' of '''~{2}'''}} for '''~{3}'''"); +case Xol_msg_itm_.Id_edit_toolbar_bold_sample: return new_(Xol_msg_itm_.Id_edit_toolbar_bold_sample, "bold_sample", "Bold text"); +case Xol_msg_itm_.Id_edit_toolbar_bold_tip: return new_(Xol_msg_itm_.Id_edit_toolbar_bold_tip, "bold_tip", "Bold text"); +case Xol_msg_itm_.Id_edit_toolbar_italic_sample: return new_(Xol_msg_itm_.Id_edit_toolbar_italic_sample, "italic_sample", "Italic text"); +case Xol_msg_itm_.Id_edit_toolbar_italic_tip: return new_(Xol_msg_itm_.Id_edit_toolbar_italic_tip, "italic_tip", "Italic text"); +case Xol_msg_itm_.Id_edit_toolbar_link_sample: return new_(Xol_msg_itm_.Id_edit_toolbar_link_sample, "link_sample", "Link title"); +case Xol_msg_itm_.Id_edit_toolbar_link_tip: return new_(Xol_msg_itm_.Id_edit_toolbar_link_tip, "link_tip", "Internal link"); +case Xol_msg_itm_.Id_edit_toolbar_headline_sample: return new_(Xol_msg_itm_.Id_edit_toolbar_headline_sample, "headline_sample", "Headline text"); +case Xol_msg_itm_.Id_edit_toolbar_headline_tip: return new_(Xol_msg_itm_.Id_edit_toolbar_headline_tip, "headline_tip", "Level 2 headline"); +case Xol_msg_itm_.Id_edit_toolbar_ulist_tip: return new_(Xol_msg_itm_.Id_edit_toolbar_ulist_tip, "wikieditor-toolbar-tool-ulist", "Bulleted list"); +case Xol_msg_itm_.Id_edit_toolbar_ulist_sample: return new_(Xol_msg_itm_.Id_edit_toolbar_ulist_sample, "wikieditor-toolbar-tool-ulist-example", "Bulleted list item"); +case Xol_msg_itm_.Id_edit_toolbar_olist_tip: return new_(Xol_msg_itm_.Id_edit_toolbar_olist_tip, "wikieditor-toolbar-tool-olist", "Numbered list"); +case Xol_msg_itm_.Id_edit_toolbar_olist_sample: return new_(Xol_msg_itm_.Id_edit_toolbar_olist_sample, "wikieditor-toolbar-tool-olist-example", "Numbered list item"); +case Xol_msg_itm_.Id_xowa_portal_about_text: return new_(Xol_msg_itm_.Id_xowa_portal_about_text, "xowa-portal-about", "About"); +case Xol_msg_itm_.Id_xowa_portal_about_tooltip: return new_(Xol_msg_itm_.Id_xowa_portal_about_tooltip, "tooltip-xowa-portal-about", "About XOWA"); +case Xol_msg_itm_.Id_xowa_portal_about_accesskey: return new_(Xol_msg_itm_.Id_xowa_portal_about_accesskey, "accesskey-xowa-portal-about", ""); +case Xol_msg_itm_.Id_symbol_catseparator: return new_(Xol_msg_itm_.Id_symbol_catseparator, "catseparator", "\u007C"); +case Xol_msg_itm_.Id_symbol_semicolon_separator: return new_(Xol_msg_itm_.Id_symbol_semicolon_separator, "semicolon-separator", "; "); +case Xol_msg_itm_.Id_symbol_comma_separator: return new_(Xol_msg_itm_.Id_symbol_comma_separator, "comma-separator", ", "); +case Xol_msg_itm_.Id_symbol_colon_separator: return new_(Xol_msg_itm_.Id_symbol_colon_separator, "colon-separator", ": "); +case Xol_msg_itm_.Id_symbol_autocomment_prefix: return new_(Xol_msg_itm_.Id_symbol_autocomment_prefix, "autocomment-prefix", "- "); +case Xol_msg_itm_.Id_symbol_pipe_separator: return new_(Xol_msg_itm_.Id_symbol_pipe_separator, "pipe-separator", " \u007C "); +case Xol_msg_itm_.Id_symbol_word_separator: return new_(Xol_msg_itm_.Id_symbol_word_separator, "word-separator", " "); +case Xol_msg_itm_.Id_symbol_ellipsis: return new_(Xol_msg_itm_.Id_symbol_ellipsis, "ellipsis", "..."); +case Xol_msg_itm_.Id_symbol_percent: return new_(Xol_msg_itm_.Id_symbol_percent, "percent", "~{0}%"); +case Xol_msg_itm_.Id_symbol_parentheses: return new_(Xol_msg_itm_.Id_symbol_parentheses, "parentheses", "(~{0})"); +case Xol_msg_itm_.Id_duration_ago: return new_(Xol_msg_itm_.Id_duration_ago, "ago", "~{0} ago"); +case Xol_msg_itm_.Id_xowa_wikidata_novalue: return new_(Xol_msg_itm_.Id_xowa_wikidata_novalue, "xowa-wikidata-novalue", "—"); +case Xol_msg_itm_.Id_xowa_wikidata_somevalue: return new_(Xol_msg_itm_.Id_xowa_wikidata_somevalue, "xowa-wikidata-somevalue", "?"); +case Xol_msg_itm_.Id_xowa_wikidata_links_wiki: return new_(Xol_msg_itm_.Id_xowa_wikidata_links_wiki, "xowa-wikidata-links-wiki", "Links (Wikipedia)"); +case Xol_msg_itm_.Id_xowa_wikidata_links_wiktionary: return new_(Xol_msg_itm_.Id_xowa_wikidata_links_wiktionary, "xowa-wikidata-links-wiktionary", "Links (Wiktionary)"); +case Xol_msg_itm_.Id_xowa_wikidata_links_wikisource: return new_(Xol_msg_itm_.Id_xowa_wikidata_links_wikisource, "xowa-wikidata-links-wikisource", "Links (Wikisource)"); +case Xol_msg_itm_.Id_xowa_wikidata_links_wikivoyage: return new_(Xol_msg_itm_.Id_xowa_wikidata_links_wikivoyage, "xowa-wikidata-links-wikivoyage", "Links (Wikivoyage)"); +case Xol_msg_itm_.Id_xowa_wikidata_links_wikiquote: return new_(Xol_msg_itm_.Id_xowa_wikidata_links_wikiquote, "xowa-wikidata-links-wikiquote", "Links (Wikiquote)"); +case Xol_msg_itm_.Id_xowa_wikidata_links_wikibooks: return new_(Xol_msg_itm_.Id_xowa_wikidata_links_wikibooks, "xowa-wikidata-links-wikibooks", "Links (Wikibooks)"); +case Xol_msg_itm_.Id_xowa_wikidata_links_wikiversity: return new_(Xol_msg_itm_.Id_xowa_wikidata_links_wikiversity, "xowa-wikidata-links-wikiversity", "Links (Wikiversity)"); +case Xol_msg_itm_.Id_xowa_wikidata_links_wikinews: return new_(Xol_msg_itm_.Id_xowa_wikidata_links_wikinews, "xowa-wikidata-links-wikinews", "Links (Wikinews)"); +case Xol_msg_itm_.Id_xowa_wikidata_plus: return new_(Xol_msg_itm_.Id_xowa_wikidata_plus, "xowa-wikidata-plus", "+"); +case Xol_msg_itm_.Id_xowa_wikidata_minus: return new_(Xol_msg_itm_.Id_xowa_wikidata_minus, "xowa-wikidata-minus", "−"); +case Xol_msg_itm_.Id_xowa_wikidata_plusminus: return new_(Xol_msg_itm_.Id_xowa_wikidata_plusminus, "xowa-wikidata-plusminus", "±"); +case Xol_msg_itm_.Id_xowa_wikidata_degree: return new_(Xol_msg_itm_.Id_xowa_wikidata_degree, "xowa-wikidata-degree", "°"); +case Xol_msg_itm_.Id_xowa_wikidata_minute: return new_(Xol_msg_itm_.Id_xowa_wikidata_minute, "xowa-wikidata-minute", "′"); +case Xol_msg_itm_.Id_xowa_wikidata_second: return new_(Xol_msg_itm_.Id_xowa_wikidata_second, "xowa-wikidata-second", "″"); +case Xol_msg_itm_.Id_xowa_wikidata_north: return new_(Xol_msg_itm_.Id_xowa_wikidata_north, "xowa-wikidata-north", "N"); +case Xol_msg_itm_.Id_xowa_wikidata_south: return new_(Xol_msg_itm_.Id_xowa_wikidata_south, "xowa-wikidata-south", "S"); +case Xol_msg_itm_.Id_xowa_wikidata_west: return new_(Xol_msg_itm_.Id_xowa_wikidata_west, "xowa-wikidata-west", "W"); +case Xol_msg_itm_.Id_xowa_wikidata_east: return new_(Xol_msg_itm_.Id_xowa_wikidata_east, "xowa-wikidata-east", "E"); +case Xol_msg_itm_.Id_xowa_wikidata_meters: return new_(Xol_msg_itm_.Id_xowa_wikidata_meters, "xowa-wikidata-meters", " m"); +case Xol_msg_itm_.Id_xowa_wikidata_julian: return new_(Xol_msg_itm_.Id_xowa_wikidata_julian, "xowa-wikidata-julian", "jul"); +case Xol_msg_itm_.Id_xowa_wikidata_year: return new_(Xol_msg_itm_.Id_xowa_wikidata_year, "xowa-wikidata-year", "~{0}"); +case Xol_msg_itm_.Id_xowa_wikidata_decade: return new_(Xol_msg_itm_.Id_xowa_wikidata_decade, "xowa-wikidata-decade", "~{0}0s"); +case Xol_msg_itm_.Id_xowa_wikidata_century: return new_(Xol_msg_itm_.Id_xowa_wikidata_century, "xowa-wikidata-century", "~{0}. century"); +case Xol_msg_itm_.Id_xowa_wikidata_millenium: return new_(Xol_msg_itm_.Id_xowa_wikidata_millenium, "xowa-wikidata-millenium", "~{0}. millenium"); +case Xol_msg_itm_.Id_xowa_wikidata_years1e4: return new_(Xol_msg_itm_.Id_xowa_wikidata_years1e4, "xowa-wikidata-years1e4", "~{0}0,000 years"); +case Xol_msg_itm_.Id_xowa_wikidata_years1e5: return new_(Xol_msg_itm_.Id_xowa_wikidata_years1e5, "xowa-wikidata-years1e5", "~{0}00,000 years"); +case Xol_msg_itm_.Id_xowa_wikidata_years1e6: return new_(Xol_msg_itm_.Id_xowa_wikidata_years1e6, "xowa-wikidata-years1e6", "~{0} million years"); +case Xol_msg_itm_.Id_xowa_wikidata_years1e7: return new_(Xol_msg_itm_.Id_xowa_wikidata_years1e7, "xowa-wikidata-years1e7", "~{0}0 million years"); +case Xol_msg_itm_.Id_xowa_wikidata_years1e8: return new_(Xol_msg_itm_.Id_xowa_wikidata_years1e8, "xowa-wikidata-years1e8", "~{0}00 million years"); +case Xol_msg_itm_.Id_xowa_wikidata_years1e9: return new_(Xol_msg_itm_.Id_xowa_wikidata_years1e9, "xowa-wikidata-years1e9", "~{0} billion years"); +case Xol_msg_itm_.Id_xowa_wikidata_bc: return new_(Xol_msg_itm_.Id_xowa_wikidata_bc, "xowa-wikidata-bc", "~{0} BC"); +case Xol_msg_itm_.Id_xowa_wikidata_inTime: return new_(Xol_msg_itm_.Id_xowa_wikidata_inTime, "xowa-wikidata-inTime", "in ~{0}"); +case Xol_msg_itm_.Id_ctg_tbl_hidden: return new_(Xol_msg_itm_.Id_ctg_tbl_hidden, "hidden-category-category", "Hidden categories"); +case Xol_msg_itm_.Id_ctg_help_page: return new_(Xol_msg_itm_.Id_ctg_help_page, "pagecategorieslink", "Special:Categories"); +case Xol_msg_itm_.Id_statistics_title: return new_(Xol_msg_itm_.Id_statistics_title, "statistics", "Statistics"); +case Xol_msg_itm_.Id_statistics_header_pages: return new_(Xol_msg_itm_.Id_statistics_header_pages, "statistics-header-pages", "Page statistics"); +case Xol_msg_itm_.Id_statistics_articles: return new_(Xol_msg_itm_.Id_statistics_articles, "statistics-articles", "Content pages"); +case Xol_msg_itm_.Id_statistics_pages: return new_(Xol_msg_itm_.Id_statistics_pages, "statistics-pages", "Pages"); +case Xol_msg_itm_.Id_statistics_pages_desc: return new_(Xol_msg_itm_.Id_statistics_pages_desc, "statistics-pages-desc", "All pages in the wiki, including talk pages, redirects, etc."); +case Xol_msg_itm_.Id_statistics_header_ns: return new_(Xol_msg_itm_.Id_statistics_header_ns, "statistics-header-ns", "Namespace statistics"); +case Xol_msg_itm_.Id_wikibase_diffview_rank: return new_(Xol_msg_itm_.Id_wikibase_diffview_rank, "Wikibase-diffview-rank", "rank"); +case Xol_msg_itm_.Id_xowa_wikidata_deprecated: return new_(Xol_msg_itm_.Id_xowa_wikidata_deprecated, "xowa-wikidata-deprecated", "deprecated"); +case Xol_msg_itm_.Id_xowa_wikidata_normal: return new_(Xol_msg_itm_.Id_xowa_wikidata_normal, "xowa-wikidata-normal", "normal"); +case Xol_msg_itm_.Id_xowa_wikidata_preferred: return new_(Xol_msg_itm_.Id_xowa_wikidata_preferred, "xowa-wikidata-preferred", "preferred"); +case Xol_msg_itm_.Id_xowa_wikidata_links_special: return new_(Xol_msg_itm_.Id_xowa_wikidata_links_special, "xowa-wikidata-links-special", "Links (special wikis)"); + default: throw Err_.new_unhandled(id); + } + } + public static byte[] eval_(Bry_bfr bfr, Xol_msg_itm tmp_msg_itm, byte[] val, Object... args) { + synchronized (tmp_fmtr) { // LOCK:static-objs; DATE:2016-07-07 + val = gplx.xowa.apps.gfs.Gfs_php_converter.To_gfs(bfr, val); + update_val_(tmp_msg_itm, val); + return tmp_fmtr.Bld_bry_many(bfr, args); + } + } +} diff --git a/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_itm_tst.java b/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_itm_tst.java index a27517de8..6ac480b0d 100644 --- a/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_itm_tst.java +++ b/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_itm_tst.java @@ -13,3 +13,25 @@ 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.langs.msgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import org.junit.*; +public class Xol_msg_itm_tst { + @Before public void init() {fxt.Clear();} private Xol_msg_itm_fxt fxt = new Xol_msg_itm_fxt(); + @Test public void New_plain() {fxt.Test_new("a" , Bool_.N, Bool_.N);} + @Test public void New_fmt() {fxt.Test_new("a~{0}b" , Bool_.Y, Bool_.N);} + @Test public void New_tmpl() {fxt.Test_new("a{{b}}c" , Bool_.N, Bool_.Y);} + @Test public void New_fmt_tmpl() {fxt.Test_new("a{{b}}c~{0}d" , Bool_.Y, Bool_.Y);} + @Test public void New_space() {fxt.Test_val("a b" , "a b");} +} +class Xol_msg_itm_fxt { + public void Clear() {} + public void Test_new(String val, boolean has_fmt_arg, boolean has_tmpl_txt) { + Xol_msg_itm itm = Xol_msg_itm_.new_(0, "test", val); + Tfds.Eq(has_fmt_arg, itm.Has_fmt_arg(), "has_fmt_arg"); + Tfds.Eq(has_tmpl_txt, itm.Has_tmpl_txt(), "has_tmpl_txt"); + } + public void Test_val(String val, String expd) { + Xol_msg_itm itm = Xol_msg_itm_.new_(0, "test", val); + Tfds.Eq(expd, String_.new_u8(itm.Val()), "has_fmt_arg"); + } +} diff --git a/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_mgr.java b/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_mgr.java index a27517de8..b2c074ed1 100644 --- a/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_mgr.java +++ b/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_mgr.java @@ -13,3 +13,93 @@ 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.langs.msgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.xowa.langs.parsers.*; +public class Xol_msg_mgr implements Gfo_invk { + private final Gfo_invk owner; private final boolean owner_is_lang; + public Xol_msg_mgr(Gfo_invk owner, boolean owner_is_lang) { + this.owner = owner; this.owner_is_lang = owner_is_lang; + this.Clear(); + } + public void Clear() { + if (owner_is_lang) + itms = Ary_new(); + else + itms = new Xol_msg_itm[Xol_msg_itm_.Id__max]; + hash = Hash_new(itms); + itms_max = itms_id_next = Xol_msg_itm_.Id__max; + } + public int Itms_max() {return itms_max;} private Xol_msg_itm[] itms; int itms_max = Xol_msg_itm_.Id__max; int itms_id_next = Xol_msg_itm_.Id__max; + public Xol_msg_itm Itm_by_id_or_null(int id) {return id < itms_max ? itms[id] : null;} + public Xol_msg_itm Itm_by_key_or_null(byte[] key) {return (Xol_msg_itm)hash.Get_by(key);} + public Xol_msg_itm Itms_new(byte[] msg_key) { + Xol_msg_itm rv = new Xol_msg_itm(itms_id_next++, msg_key); + Itms_reg(rv); + return rv; + } + public Xol_msg_itm Itm_by_key_or_new(String key, String val) {return Itm_by_key_or_new(key, val, false);} // TEST: + public Xol_msg_itm Itm_by_key_or_new(String key, String val, boolean has_fmt_arg) { // TEST: + Xol_msg_itm rv = Itm_by_key_or_new(Bry_.new_u8(key)); + Xol_msg_itm_.update_val_(rv, Bry_.new_u8(val)); + return rv; + } + public Xol_msg_itm Itm_by_key_or_new(byte[] key) { + Object o = hash.Get_by(key); + Xol_msg_itm rv = null; + if (o == null) { // key not found; likely not a system_id; generate a custom one + rv = new Xol_msg_itm(itms_id_next++, key); + Itms_reg(rv); + } + else { + rv = (Xol_msg_itm)o; + } + return rv; + } Hash_adp hash; + public byte[] Val_by_id(int id) { // NOTE: Val_by_id needs to exist on lang (not wiki_msg_mgr); {{#time}} can pass in lang, and will need to call lang's msg_mgr directly + Xol_msg_itm itm = Itm_by_id_or_null(id); + return itm == null ? null : itm.Val(); + } + public byte[] Val_by_id(Xowe_wiki wiki, int id) { // NOTE: Val_by_id needs to exist on lang (not wiki_msg_mgr); {{#time}} can pass in lang, and will need to call lang's msg_mgr directly + Xol_msg_itm itm = Itm_by_id_or_null(id); + if (itm == null) return null; + byte[] rv = itm.Val(); + if (itm.Has_tmpl_txt()) rv = wiki.Parser_mgr().Main().Expand_tmpl(rv); + return rv; + } + public byte[] Val_by_str_or_empty(String str) {return Val_by_bry_or(Bry_.new_u8(str), Bry_.Empty);} + public byte[] Val_by_bry_or(byte[] bry, byte[] or) { + Xol_msg_itm itm = Itm_by_key_or_null(bry); + return itm == null ? or : itm.Val(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_lang)) return owner; + else if (ctx.Match(k, Invk_load_text)) Xol_lang_srl.Load_messages(this, m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_lang = Xol_lang_srl.Invk_lang, Invk_load_text = Xol_lang_srl.Invk_load_text; + private void Itms_reg(Xol_msg_itm itm) { + int id = itm.Id(); + if (id >= itms_max) { + int new_max = (id + 1) * 2; + itms = (Xol_msg_itm[])Array_.Expand(itms, new Xol_msg_itm[new_max], itms_max); + itms_max = new_max; + } + itms[id] = itm; + hash.Add(itm.Key(), itm); + } + private static Xol_msg_itm[] Ary_new() { + Xol_msg_itm[] rv = new Xol_msg_itm[Xol_msg_itm_.Id__max]; + for (int i = 0; i < Xol_msg_itm_.Id__max; i++) + rv[i] = Xol_msg_itm_.new_(i); + return rv; + } + private static Hash_adp Hash_new(Xol_msg_itm[] ary) { + Hash_adp rv = Hash_adp_bry.ci_a7(); // ASCII:MW messages are currently all ASCII + for (int i = 0; i < Xol_msg_itm_.Id__max; i++) { + Xol_msg_itm itm = ary[i]; if (itm == null) continue; // NOTE: can be null when msg_mgr is owned by wiki + rv.Add(itm.Key(), itm); + } + return rv; + } + static final String GRP_KEY = "xowa.lang.msg_mgr"; +} diff --git a/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_mgr_.java b/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_mgr_.java index a27517de8..551c15c2f 100644 --- a/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_mgr_.java +++ b/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_mgr_.java @@ -13,3 +13,147 @@ 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.langs.msgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.brys.fmtrs.*; +import gplx.langs.phps.*; import gplx.xowa.parsers.*; +import gplx.xowa.apps.gfs.*; +import gplx.xowa.htmls.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.dbs.*; +public class Xol_msg_mgr_ { + public static String Get_msg_val_gui_or_empty(Xoa_lang_mgr lang_mgr, Xol_lang_itm lang, byte[] pre, byte[] key, byte[] suf) { // get from lang, else get from en; does not use get_msg_val to skip db lookups; should only be used for gui; DATE:2014-05-28 + String rv = Get_msg_val_gui_or_null(lang_mgr, lang, pre, key, suf); + return rv == null ? "" : rv; + } + public static String Get_msg_val_gui_or(Xoa_lang_mgr lang_mgr, Xol_lang_itm lang, byte[] pre, byte[] key, byte[] suf, String or) { + String rv = Get_msg_val_gui_or_null(lang_mgr, lang, pre, key, suf); + return rv == null ? or : rv; + } + public static String Get_msg_val_gui_or_null(Xoa_lang_mgr lang_mgr, Xol_lang_itm lang, byte[] pre, byte[] key, byte[] suf) { // get from lang, else get from en; does not use get_msg_val to skip db lookups; should only be used for gui; DATE:2014-05-28 + byte[] msg_key = Bry_.Add(pre, key, suf); + Xol_msg_itm msg_itm = lang.Msg_mgr().Itm_by_key_or_null(msg_key); + if (msg_itm == null) + msg_itm = lang_mgr.Lang_en().Msg_mgr().Itm_by_key_or_null(msg_key); + return msg_itm == null ? null : String_.new_u8(msg_itm.Val()); + } + public static byte[] Get_msg_val(Xow_wiki wiki, Xol_lang_itm lang, byte[] msg_key, byte[][] fmt_args) { + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_b512(); + Xol_msg_itm msg_itm = Get_msg_itm(tmp_bfr, wiki, lang, msg_key); + byte[] rv = (msg_itm.Defined_in_none()) + ? tmp_bfr.Add_byte(Byte_ascii.Lt).Add(msg_key).Add_byte(Byte_ascii.Gt).To_bry_and_clear() // NOTE: do not use key from msg_itm; msg_itms are case-insensitive, and val should match key exactly; EX: missing should return not DATE:2016-08-01 + : Get_msg_val(tmp_bfr, wiki, msg_itm, fmt_args); + tmp_bfr.Mkr_rls(); + return rv; + } private static final byte[] Missing_bry = Bry_.new_a7("$"), Slash_bry = new byte[] {Byte_ascii.Slash}; + public static byte[] Get_msg_val(Bry_bfr tmp_bfr, Xow_wiki wiki, Xol_msg_itm msg_itm, byte[][] fmt_args) { + byte[] msg_val = msg_itm.Val(); + boolean has_fmt = msg_itm.Has_fmt_arg(), has_tmpl = msg_itm.Has_tmpl_txt(); + if (!has_fmt && !has_tmpl) // no fmt or tmpl; just add val + return msg_val; + if (has_fmt) { // fmt exists; fmt first (before tmpl text); EX: Expression error: Unrecognised word "~{0}" + Bry_fmtr tmp_fmtr = Bry_fmtr.New__tmp().Missing_bgn_(Missing_bry).Missing_end_(Bry_.Empty).Missing_adj_(1); + tmp_fmtr.Fmt_(msg_val); + tmp_fmtr.Bld_bfr(tmp_bfr, fmt_args); + msg_val = tmp_bfr.To_bry_and_clear(); + } + if (has_tmpl) { + if (wiki.Type_is_edit()) { + Xowe_wiki wikie = (Xowe_wiki)wiki; + Xop_ctx sub_ctx = Xop_ctx.New__sub__reuse_page(wikie.Parser_mgr().Ctx()); Xop_tkn_mkr tkn_mkr = sub_ctx.Tkn_mkr(); + Xop_root_tkn sub_root = tkn_mkr.Root(msg_val); + msg_val = wikie.Parser_mgr().Main().Expand_tmpl(sub_root, sub_ctx, tkn_mkr, msg_val); + } + } + return msg_val; + } + public static Xol_msg_itm Get_msg_itm(Bry_bfr tmp_bfr, Xow_wiki wiki, Xol_lang_itm lang, byte[] msg_key) { + byte[] msg_key_sub_root = msg_key; + int slash_pos = Bry_find_.Find_bwd(msg_key, Byte_ascii.Slash); + if (slash_pos != Bry_find_.Not_found) { // key is of format "key/lang"; EX: "January/en" + int msg_key_len = msg_key.length; + if (slash_pos != msg_key_len) { // get text after slash; EX: "en" + Object o = Xol_lang_stub_.Regy().Get_by_mid(msg_key, slash_pos + 1, msg_key_len); + if (o != null) { // text is known lang_code; + Xol_lang_stub lang_itm = (Xol_lang_stub)o; + lang = wiki.App().Lang_mgr().Get_by_or_new(lang_itm.Key()); // set lang + } + msg_key_sub_root = Bry_.Mid(msg_key, 0, slash_pos); // set msg to "a" (discarding "/b") + } + } + Xol_msg_itm msg_in_wiki = wiki.Msg_mgr().Get_or_null(msg_key); // check wiki; used to be check lang, but Search_mediawiki should never be toggled on lang; DATE:2014-05-13 + if (msg_in_wiki != null) return msg_in_wiki; // NOTE: all new msgs will Search_mediawiki once; EX: de.w:{{int:Autosumm-replace}}; DATE:2013-01-25 + msg_in_wiki = wiki.Msg_mgr().Get_or_make(msg_key); + byte[] msg_db = Get_msg_from_db_or_null(wiki, lang, msg_key, msg_key_sub_root); + byte[] msg_val = Bry_.Empty; + if (msg_db == null) { // [[MediaWiki:key/fallback]] still not found; search "lang.gfs"; + Xol_msg_itm msg_in_lang = Get_msg_itm_from_gfs(wiki, lang, msg_key_sub_root); + if (msg_in_lang == null) { + msg_val = tmp_bfr.Add_byte(Byte_ascii.Lt).Add(msg_key).Add_byte(Byte_ascii.Gt).To_bry_and_clear(); // set val to + msg_in_wiki.Defined_in_(Xol_msg_itm.Defined_in__none); + } + else { + msg_val = msg_in_lang.Val(); + msg_in_wiki.Defined_in_(Xol_msg_itm.Defined_in__lang); + } + } + else { // page found; dump entire contents + msg_val = Gfs_php_converter.To_gfs(tmp_bfr, msg_db); // note that MediaWiki msg's use php arg format ($1); xowa.gfs msgs are already converted + msg_in_wiki.Defined_in_(Xol_msg_itm.Defined_in__wiki); + } + Xol_msg_itm_.update_val_(msg_in_wiki, msg_val); + return msg_in_wiki; + } + private static byte[] Get_msg_from_db_or_null(Xow_wiki wiki, Xol_lang_itm lang, byte[] msg_key, byte[] msg_key_sub_root) { + byte[] ns_bry = wiki.Ns_mgr().Ns_mediawiki().Name_db_w_colon(); + Xoa_ttl ttl = wiki.Ttl_parse(Bry_.Add(ns_bry, msg_key)); // ttl="MediaWiki:msg_key"; note that there may be "/lang"; EX:pl.d:Wikislownik:Bar/Archiwum_6 and newarticletext/pl + byte[] rv = null; + if (ttl != null) + rv = Load_msg_from_db_or_null(wiki, ttl); + if (rv == null) {// [[MediaWiki:key]] not found; search for [[MediaWiki:key/fallback]] + byte[][] fallback_ary = lang.Fallback_bry_ary(); + int fallback_ary_len = fallback_ary.length; + for (int i = 0; i < fallback_ary_len; i++) { + byte[] fallback = fallback_ary[i]; + ttl = wiki.Ttl_parse(Bry_.Add(ns_bry, msg_key_sub_root, Slash_bry, fallback)); // ttl="MediaWiki:msg_key/fallback" + if (ttl != null) + rv = Load_msg_from_db_or_null(wiki, ttl); + if (rv != null) break; + } + } + return rv; + } + private static byte[] Load_msg_from_db_or_null(Xow_wiki wiki, Xoa_ttl ttl) { + Xoa_page pg = null; + if (wiki.Type_is_edit()) // NOTE: this check only works when loading pages directly (EX:en.wikipedia.org/wiki/MediaWiki:Sidebar); however, due to way msgs load, wiki is always edit, even if html_dump; DATE:2016-09-17 + pg = ((Xowe_wiki)wiki).Data_mgr().Load_page_by_ttl_for_msg(ttl); + + // HACK: handle htmp_dump wikis when loading messages such as sidebar; DATE:2016-09-17 + if ( !wiki.Type_is_edit() // app is drd; DATE:2016-09-23 + || ( pg.Db().Page().Exists() // page exists + && Bry_.Len_eq_0(pg.Db().Text().Text_bry()) // but text is empty -> check html_dump + ) + ) { + Xoh_page hpg = new Xoh_page(); + pg = hpg; + hpg.Ctor_by_hview(wiki, Xoa_url.New(wiki, ttl), ttl, -1); + wiki.Html__hdump_mgr().Load_mgr().Load_by_xowh(hpg, ttl, Bool_.N); + pg.Db().Text().Text_bry_(pg.Db().Html().Html_bry()); + } + return pg.Db().Page().Exists() ? pg.Db().Text().Text_bry() : null; + } + private static Xol_msg_itm Get_msg_itm_from_gfs(Xow_wiki wiki, Xol_lang_itm lang, byte[] msg_key_sub_root) { + Xol_msg_itm rv = lang.Msg_mgr().Itm_by_key_or_null(msg_key_sub_root); // NOTE: should always be msg_key_sub_root; EX: "msg/lang" will never be in lang.gfs + if (rv == null) { // msg not found; check fallbacks; note that this is different from MW b/c when MW constructs a lang, it automatically adds all fallback msgs to the current lang + byte[][] fallback_ary = lang.Fallback_bry_ary(); + int fallback_ary_len = fallback_ary.length; + Xoa_lang_mgr lang_mgr = wiki.App().Lang_mgr(); + for (int i = 0; i < fallback_ary_len; i++) { + byte[] fallback = fallback_ary[i]; + Xol_lang_itm fallback_lang = lang_mgr.Get_by(fallback); + if (fallback_lang == null) continue; // NOTE: en has fallback of "false"; ignore bad fallbacks; + rv = fallback_lang.Msg_mgr().Itm_by_key_or_null(msg_key_sub_root); + if (rv != null) break; + } + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_mgr_tst.java b/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_mgr_tst.java index a27517de8..3be3dbe4b 100644 --- a/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/langs/msgs/Xol_msg_mgr_tst.java @@ -13,3 +13,56 @@ 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.langs.msgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import org.junit.*; import gplx.core.tests.*; import gplx.xowa.langs.msgs.*; +public class Xol_msg_mgr_tst { + @Before public void init() {fxt.Clear();} private final Xol_msg_mgr_fxt fxt = new Xol_msg_mgr_fxt(); + @Test public void Template_msg() {fxt.Test_val_by_key("About {{SITENAME}}", "About Wikipedia");} // PURPOSE.fix: {{Template}} not working inside label tags; EX:de.wikisource.org; DATE:2013-02-10 + @Test public void Template_mediawiki() { // PURPOSE.fix: {{Template}} not working inside MediaWiki template + fxt.Test_mediaWiki_msg("About {{SITENAME}}", "About Wikipedia"); + } + @Test public void Val_html_accesskey_and_title() { + fxt.Clear().Test_val_html_accesskey_and_title("test_title" , "a" , " accesskey=\"a\" title=\"test_title [a]\""); + fxt.Clear().Test_val_html_accesskey_and_title("test_title" , null , " title=\"test_title\""); // accesskey is missing + fxt.Clear().Test_val_html_accesskey_and_title("test_title" , "" , " title=\"test_title\""); // accesskey is "" + fxt.Clear().Test_val_html_accesskey_and_title(null , "a" , " title=\"\""); // no title; leave blank + } + @Test public void Missing() { + fxt.Test__get_msg_val("missing", ""); // check that key is enclosed in <> + fxt.Test__get_msg_val("Missing", ""); // check that val matches key; used to match 1st case-insensitive variant; EX: "" b/c "" was returned above; DATE:2016-08-01 + } +} +class Xol_msg_mgr_fxt { + public Xol_msg_mgr_fxt Clear() { + if (app == null) { + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + mgr = wiki.Msg_mgr(); + } + mgr.Clear(); + wiki.Lang().Msg_mgr().Clear(); + return this; + } private Xoae_app app; Xowe_wiki wiki; Xow_msg_mgr mgr; + public void Test_val_by_key(String val, String expd) { + Xol_msg_itm itm = wiki.Lang().Msg_mgr().Itm_by_key_or_new(Bry_.new_a7("test")); + itm.Atrs_set(Bry_.new_a7(val), false, true); + Tfds.Eq(expd, String_.new_u8(wiki.Msg_mgr().Val_by_key_obj(Bry_.new_a7("test"))), "has_tmpl_txt"); + } + public void Test_mediaWiki_msg(String raw, String expd) { + byte[] msg_ttl = Bry_.new_a7("MediaWiki:msg_ttl"); + wiki.Db_mgr().Save_mgr().Data_create(Xoa_ttl.Parse(wiki, msg_ttl), Bry_.new_a7(raw)); + Tfds.Eq(expd, String_.new_u8(wiki.Msg_mgr().Val_by_key_obj(Bry_.new_a7("msg_ttl")))); + } + public void Test_val_html_accesskey_and_title(String init_title, String init_accesskey, String expd) { + if (init_title != null) new_msg_itm_("tooltip-test" , init_title); + if (init_accesskey != null) new_msg_itm_("accesskey-test" , init_accesskey); + Tfds.Eq(expd, String_.new_a7(wiki.Msg_mgr().Val_html_accesskey_and_title(Bry_.new_a7("test")))); + } + public void Test__get_msg_val(String key, String expd) { + Gftest.Eq__str(expd, Xol_msg_mgr_.Get_msg_val(wiki, wiki.Lang(), Bry_.new_a7(key), Bry_.Ary_empty)); + } + private void new_msg_itm_(String key, String val) { + Xol_msg_itm itm = wiki.Lang().Msg_mgr().Itm_by_key_or_new(Bry_.new_a7(key)); + itm.Atrs_set(Bry_.new_a7(val), false, true); + } +} diff --git a/400_xowa/src/gplx/xowa/langs/msgs/Xow_mainpage_finder.java b/400_xowa/src/gplx/xowa/langs/msgs/Xow_mainpage_finder.java index a27517de8..be796cc64 100644 --- a/400_xowa/src/gplx/xowa/langs/msgs/Xow_mainpage_finder.java +++ b/400_xowa/src/gplx/xowa/langs/msgs/Xow_mainpage_finder.java @@ -13,3 +13,17 @@ 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.langs.msgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xow_mainpage_finder { + public static byte[] Find_or(Xowe_wiki wiki, byte[] or) { + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_b512(); + Xol_msg_itm msg_itm = Xol_msg_mgr_.Get_msg_itm(tmp_bfr, wiki, wiki.Lang(), Msg_mainpage); + byte[] rv = msg_itm.Defined_in_none() + ? or + : Xol_msg_mgr_.Get_msg_val(tmp_bfr, wiki, msg_itm, Bry_.Ary_empty) + ; + tmp_bfr.Mkr_rls(); + return rv; + } + public static final byte[] Msg_mainpage = Bry_.new_a7("mainpage"); +} diff --git a/400_xowa/src/gplx/xowa/langs/msgs/Xow_mainpage_finder_tst.java b/400_xowa/src/gplx/xowa/langs/msgs/Xow_mainpage_finder_tst.java index a27517de8..3bec4e41a 100644 --- a/400_xowa/src/gplx/xowa/langs/msgs/Xow_mainpage_finder_tst.java +++ b/400_xowa/src/gplx/xowa/langs/msgs/Xow_mainpage_finder_tst.java @@ -13,3 +13,59 @@ 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.langs.msgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import org.junit.*; +public class Xow_mainpage_finder_tst { + @Before public void init() {fxt.Clear();} private Xow_mainpage_finder_fxt fxt = new Xow_mainpage_finder_fxt(); + @Test public void Mediawiki() { + fxt.Init_mediawiki_page("Mainpage_by_mediawiki"); + fxt.Test_mainpage("Mainpage_by_mediawiki"); + } + @Test public void Lang() { + fxt.Init_lang("Mainpage_by_lang"); + fxt.Test_mainpage("Mainpage_by_lang"); + } + @Test public void Siteinfo() { + fxt.Init_siteinfo("Mainpage_by_siteinfo"); + fxt.Test_mainpage("Mainpage_by_siteinfo"); + } + @Test public void Lang_and_siteinfo() { + fxt.Init_lang("Mainpage_by_lang"); + fxt.Init_siteinfo("Mainpage_by_siteinfo"); + fxt.Test_mainpage("Mainpage_by_lang"); + } + @Test public void Mediawiki_and_siteinfo() { + fxt.Init_mediawiki_page("Mainpage_by_mediawiki"); + fxt.Init_siteinfo("Mainpage_by_siteinfo"); + fxt.Test_mainpage("Mainpage_by_mediawiki"); + } + @Test public void Mediawiki_and_lang_and_siteinfo() { + fxt.Init_mediawiki_page("Mainpage_by_mediawiki"); + fxt.Init_lang("Mainpage_by_lang"); + fxt.Init_siteinfo("Mainpage_by_siteinfo"); + fxt.Test_mainpage("Mainpage_by_mediawiki"); + } + @Test public void Mediawiki_tmpl() { // PURPOSE: de.wiktionary.org has "{{ns:project}}:Hauptseite"; DATE:2013-07-07 + fxt.Init_mediawiki_page("{{ns:project}}:Hauptseite"); + fxt.Test_mainpage("Wikipedia:Hauptseite"); + } +} +class Xow_mainpage_finder_fxt { + public void Clear() { + fxt.Reset_for_msgs(); + } private final Xop_fxt fxt = new Xop_fxt(); + public void Init_siteinfo(String mainpage_val) { + fxt.Wiki().Props().Main_page_(Bry_.new_a7(mainpage_val)); + } + public void Init_mediawiki_page(String mainpage_val) { + fxt.Init_page_create(String_.new_a7(Ttl_mainpage), mainpage_val); + } private static final byte[] Ttl_mainpage = Bry_.new_a7("MediaWiki:Mainpage"); // TEST: + public void Init_lang(String mainpage_val) { + Xol_msg_itm msg_itm = fxt.Wiki().Lang().Msg_mgr().Itm_by_key_or_new(Xow_mainpage_finder.Msg_mainpage); + msg_itm.Atrs_set(Bry_.new_a7(mainpage_val), false, false); + } + public void Test_mainpage(String expd) { + byte[] actl = Xow_mainpage_finder.Find_or(fxt.Wiki(), fxt.Wiki().Props().Main_page()); + Tfds.Eq(expd, String_.new_a7(actl)); + } +} diff --git a/400_xowa/src/gplx/xowa/langs/msgs/Xow_msg_mgr.java b/400_xowa/src/gplx/xowa/langs/msgs/Xow_msg_mgr.java index a27517de8..c9ce14f4d 100644 --- a/400_xowa/src/gplx/xowa/langs/msgs/Xow_msg_mgr.java +++ b/400_xowa/src/gplx/xowa/langs/msgs/Xow_msg_mgr.java @@ -13,3 +13,101 @@ 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.langs.msgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.addons.htmls.sidebars.*; +public class Xow_msg_mgr implements Gfo_invk { + private final Xow_wiki wiki; private Xol_lang_itm lang; private final Xol_msg_mgr msg_mgr; + private final Bry_fmtr tmp_fmtr = Bry_fmtr.New__tmp(); + public Xow_msg_mgr(Xow_wiki wiki, Xol_lang_itm lang) { + this.wiki = wiki; + this.lang = lang; + this.msg_mgr = new Xol_msg_mgr(wiki, false); + } + public void Clear() {msg_mgr.Clear();} + public void Lang_(Xol_lang_itm v) { + this.lang = v; + this.Clear(); + } + public byte[] Val_by_id_args(int id, Object... args) {return Val_by_id_priv(id, args);} + public byte[] Val_by_id(int id) {return Val_by_id_priv(id, null);} + private byte[] Val_by_id_priv(int id, Object[] args) { + Xol_msg_itm itm = msg_mgr.Itm_by_id_or_null(id); + if (itm == null) + itm = lang.Msg_mgr().Itm_by_id_or_null(id); + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_b512(); + byte[] rv = Val_by_itm(tmp_bfr, itm, args); + tmp_bfr.Mkr_rls(); + return rv; + } + public Xol_msg_itm Get_or_make(byte[] key) {return msg_mgr.Itm_by_key_or_new(key);} + public Xol_msg_itm Get_or_null(byte[] key) {return msg_mgr.Itm_by_key_or_null(key);} + public Xol_msg_itm Find_or_null(byte[] key) { + Xol_msg_itm itm = msg_mgr.Itm_by_key_or_null(key); + if (itm == null) { + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_b512(); + itm = Xol_msg_mgr_.Get_msg_itm(tmp_bfr, wiki, lang, key); + if (itm.Defined_in_none()) itm = null; + tmp_bfr.Mkr_rls(); + } + return itm; + } + public byte[] Val_by_key_args(byte[] key, Object... args) {return Val_by_key(key, args);} + public byte[] Val_by_key_obj(String key) {return Val_by_key(Bry_.new_u8(key), null);} + public byte[] Val_by_key_obj(byte[] key) {return Val_by_key(key, null);} + private byte[] Val_by_key(byte[] key, Object[] args) { + Xol_msg_itm itm = msg_mgr.Itm_by_key_or_null(key); + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_b512(); + if (itm == null) + itm = Xol_msg_mgr_.Get_msg_itm(tmp_bfr, wiki, lang, key); + if (itm.Defined_in_none()) { + tmp_bfr.Mkr_rls(); + return Bry_.Empty; + } + byte[] rv = Val_by_itm(tmp_bfr, itm, args); + tmp_bfr.Mkr_rls(); + return rv; + } + public byte[] Val_by_itm(Bry_bfr tmp_bfr, Xol_msg_itm itm, Object[] args) { + byte[] rv = itm.Val(); + if (args != null) rv = itm.Fmt_tmp(tmp_bfr, tmp_fmtr, rv, args); + if (itm.Has_tmpl_txt()) rv = wiki.Wtxt__expand_tmpl(rv); + return rv; + } + public byte[] Val_html_accesskey_and_title(byte[] id) { + Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_b512(); + byte[] rv = Val_html_accesskey_and_title(id, bfr, null); + bfr.Mkr_rls(); + return rv; + } + public byte[] Val_html_accesskey_and_title(byte[] id, Bry_bfr bfr, Xoh_sidebar_itm itm) { + byte[] tooltip_key = Bry_.Add(CONST_prefix_tooltip, id); + byte[] tooltip_val = Val_by_key_obj(tooltip_key); + boolean tooltip_found = Bry_.Len_gt_0(tooltip_val); + byte[] accesskey_key = Bry_.Empty, accesskey_val = Bry_.Empty; + boolean accesskey_found = false; + if (tooltip_found) { + accesskey_key = Bry_.Add(CONST_prefix_accesskey, id); + accesskey_val = Val_by_key_obj(accesskey_key); + accesskey_found = Bry_.Len_gt_0(accesskey_val); + } + if (accesskey_found) + bfr.Add(CONST_atr_accesskey).Add(accesskey_val).Add_byte(Byte_ascii.Quote); + bfr.Add(CONST_atr_title).Add(tooltip_found ? tooltip_val : Bry_.Empty); // NOTE: if tooltip not found, make blank; don't bother showing tooltip_key + if (accesskey_found) + bfr.Add_byte(Byte_ascii.Space).Add_byte(Byte_ascii.Brack_bgn).Add(accesskey_val).Add_byte(Byte_ascii.Brack_end); + bfr.Add_byte(Byte_ascii.Quote); + byte[] rv = bfr.To_bry_and_clear(); + if (itm == null) + return rv; + else { + itm.Init_by_title_and_accesskey(tooltip_val, accesskey_val, rv); + return null; + } + } private static final byte[] CONST_prefix_tooltip = Bry_.new_a7("tooltip-"), CONST_prefix_accesskey = Bry_.new_a7("accesskey-"), CONST_atr_title = Bry_.new_a7(" title=\""), CONST_atr_accesskey = Bry_.new_a7(" accesskey=\""); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_get)) return this.Val_by_key_obj(m.ReadBry("v")); + else if (ctx.Match(k, Invk_get_html_accesskey_and_title)) return this.Val_html_accesskey_and_title(m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + } private static final String Invk_get = "get", Invk_get_html_accesskey_and_title = "get_html_accesskey_and_title"; +} diff --git a/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_fmtr_base.java b/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_fmtr_base.java index a27517de8..7ef80a572 100644 --- a/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_fmtr_base.java +++ b/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_fmtr_base.java @@ -13,3 +13,190 @@ 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.langs.numbers; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.primitives.*; import gplx.core.btries.*; +public class Xol_num_fmtr_base implements Gfo_invk { + private final Btrie_fast_mgr dlm_trie = Btrie_fast_mgr.cs(); private final Btrie_rv trv = new Btrie_rv(); + private Xol_num_grp[] grp_ary = Xol_num_grp.Ary_empty; int grp_ary_len; + private Gfo_num_fmt_wkr[] cache; int cache_len = 16; + private Bry_bfr tmp = Bry_bfr_.New(); + public boolean Standard() {return standard;} private boolean standard = true; + public byte[] Dec_dlm() {return dec_dlm;} public Xol_num_fmtr_base Dec_dlm_(byte[] v) {this.dec_dlm = v; dlm_trie.Add_bry_byte(v, Raw_tid_dec); return this;} private byte[] dec_dlm = Dec_dlm_default; + private byte[] grp_dlm; + public byte[] Raw(byte tid, byte[] src) { + int src_len = src.length; + for (int i = 0; i < src_len; i++) { + byte b = src[i]; + Object o = dlm_trie.Match_at(trv, src, i, src_len); + if (o == null) + tmp.Add_byte(b); + else { + byte dlm_tid = ((Byte_obj_val)o).Val(); + int dlm_match_pos = trv.Pos(); + switch (dlm_tid) { + case Raw_tid_dec: + if (tid == Tid_raw) + tmp.Add_byte(Byte_ascii.Dot); // NOTE: dec_dlm is always outputted as dot, not regional dec_spr; EX: for dewiki, 12,34 -> 12.34 + else + tmp.Add(dec_dlm); + break; + case Raw_tid_grp: { + if (tid == Tid_raw) {} // never add grp_sep for raw + else // add raw grp_spr + tmp.Add_mid(src, i, dlm_match_pos); + break; + } + } + i = dlm_match_pos - 1; // NOTE: handle multi-byte delims + } + } + return tmp.To_bry_and_clear(); + } + public byte[] Fmt(int val) {return Fmt(Bry_.new_a7(Int_.To_str(val)));} + public byte[] Fmt(byte[] src) { // SEE: DOC_1:Fmt + int src_len = src.length; + int num_bgn = -1, dec_pos = -1; + for (int i = 0; i < src_len; i++) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + if (dec_pos == -1) { // no decimal seen + if (num_bgn == -1) // num_bgn hasn't started + num_bgn = i; // set num_bgn + } + else // decimal seen; add rest of src literally + tmp.Add_byte(b); + break; + default: // non-number; includes alpha chars, as well as ".", "," and other potential separators + if (num_bgn != -1) { // number started; format group; EX: 1234. -> 1,234. + Gfo_num_fmt_wkr wkr = Get_or_new(i - num_bgn); + wkr.Fmt(src, num_bgn, i, tmp); + num_bgn = dec_pos = -1; // reset vars + if (b == Byte_ascii.Dot // current char is "."; NOTE: all languages treat "." as decimal separator for parse; EX: for de, "1.23" is "1,23" DATE:2013-10-21 + //|| Bry_.Has_at_bgn(src, dec_dlm, i, src_len) + ) { // current char is languages's decimal delimiter; note this can be "," or any other multi-byte separator + dec_pos = i; +// i += dec_dlm.length - 1; + tmp.Add(dec_dlm); + continue; + } + } + if (b == Byte_ascii.Comma) + tmp.Add(grp_dlm); + else + tmp.Add_byte(b); + break; + } + } + if (num_bgn != -1) { // digits left unprocessed + Gfo_num_fmt_wkr wkr = Get_or_new(src_len - num_bgn); + wkr.Fmt(src, num_bgn, src_len, tmp); + } + return tmp.To_bry_and_clear(); + } + private Gfo_num_fmt_wkr Get_or_new(int src_len) { + Gfo_num_fmt_wkr rv = null; + if (src_len < cache_len) { + rv = cache[src_len]; + if (rv != null) return rv; + } + rv = new Gfo_num_fmt_wkr(grp_ary, grp_ary_len, src_len); + if (src_len < cache_len) cache[src_len] = rv; + return rv; + } + public Xol_num_grp Grps_get_last() {return grp_ary[grp_ary_len - 1];} + public Xol_num_grp Grps_get(int i) {return grp_ary[i];} + public int Grps_len() {return grp_ary_len;} + public void Grps_add(Xol_num_grp dat_itm) { + standard = false; + this.grp_ary = (Xol_num_grp[])Array_.Resize(grp_ary, grp_ary_len + 1); + grp_ary[grp_ary_len] = dat_itm; + grp_ary_len = grp_ary.length; + for (int i = 0; i < grp_ary_len; i++) { + Xol_num_grp itm = grp_ary[i]; + byte[] itm_dlm = itm.Dlm(); + Object o = dlm_trie.Match_exact(itm_dlm, 0, itm_dlm.length); // check for existing Object + if (o == null) { + dlm_trie.Add_bry_byte(itm_dlm, Raw_tid_grp); + grp_dlm = itm_dlm; + } + } + } + public Xol_num_fmtr_base Clear() { + this.grp_ary = Xol_num_grp.Ary_empty; + grp_ary_len = 0; + cache = new Gfo_num_fmt_wkr[cache_len]; + dlm_trie.Clear(); + return this; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_dec_dlm_)) this.Dec_dlm_(m.ReadBry("v")); // NOTE: must call mutator + else if (ctx.Match(k, Invk_clear)) this.Clear(); + else if (ctx.Match(k, Invk_grps_add)) this.Grps_add(new Xol_num_grp(m.ReadBry("dlm"), m.ReadInt("digits"), m.ReadYn("repeat"))); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk_dec_dlm_ = "dec_dlm_", Invk_clear = "clear", Invk_grps_add = "grps_add"; + private static final byte Raw_tid_dec = 0, Raw_tid_grp = 1; + private static final byte[] Dec_dlm_default = new byte[] {Byte_ascii.Dot}; + public static final byte[] Grp_dlm_default = new byte[] {Byte_ascii.Comma}; + public static final byte Tid_format = 0, Tid_raw = 1, Tid_nosep = 2; +} +class Gfo_num_fmt_wkr { + public void Fmt(byte[] src, int bgn, int end, Bry_bfr bb) { + if (itm_max == 0) {bb.Add_mid(src, bgn, end); return;}; // NOTE: small numbers (<=3) will have a 0-len ary + int cur_idx = itm_max - 1; + Gfo_num_fmt_bldr cur = itm_ary[cur_idx]; + int cur_pos = cur.Pos(); + for (int i = bgn; i < end; i++) { + if (i == cur_pos + bgn) { + cur.Gen(bb); + if (cur_idx > 0) cur = itm_ary[--cur_idx]; + cur_pos = cur.Pos(); + } + bb.Add_byte(src[i]); + } + } + public Gfo_num_fmt_wkr(Xol_num_grp[] grp_ary, int grp_ary_len, int src_len) { + itm_ary = new Gfo_num_fmt_bldr[src_len]; // default to src_len; will resize below; + int src_pos = src_len, dat_idx = 0, dat_repeat = -1; + while (true) { + if (dat_idx == grp_ary_len) dat_idx = dat_repeat; // no more itms left; return to repeat + Xol_num_grp dat = grp_ary[dat_idx]; + src_pos -= dat.Digits(); + if (src_pos < 1) break; // no more digits needed; stop + byte[] dat_dlm = dat.Dlm(); + itm_ary[itm_max++] = dat_dlm.length == 1 ? new Gfo_num_fmt_bldr_one(src_pos, dat_dlm[0]) : (Gfo_num_fmt_bldr)new Gfo_num_fmt_bldr_many(src_pos, dat_dlm); + if (dat.Repeat() && dat_repeat == -1) dat_repeat = dat_idx; + ++dat_idx; + } + itm_ary = (Gfo_num_fmt_bldr[])Array_.Resize(itm_ary, itm_max); + } + private Gfo_num_fmt_bldr[] itm_ary; private int itm_max; +} +interface Gfo_num_fmt_bldr { + int Pos(); + void Gen(Bry_bfr bb); +} +class Gfo_num_fmt_bldr_one implements Gfo_num_fmt_bldr { + public int Pos() {return pos;} private int pos; + public void Gen(Bry_bfr bb) {bb.Add_byte(b);} + public Gfo_num_fmt_bldr_one(int pos, byte b) {this.pos = pos; this.b = b;} private byte b; +} +class Gfo_num_fmt_bldr_many implements Gfo_num_fmt_bldr { + public int Pos() {return pos;} private int pos; + public void Gen(Bry_bfr bb) {bb.Add(ary);} + public Gfo_num_fmt_bldr_many(int pos, byte[] ary) {this.pos = pos; this.ary = ary;} private byte[] ary; +} +/* +DOC_1:Fmt +. mediawiki does the following (from Language.php|commafy +.. split the number by digitGoupingPattern: ###,###,### -> 3,3,3 +.. use regx to search for number groups +.. for each number group, format with "," and "." +.. replace final result with languages's decimal / grouping entry from separatorTransformTable +. XOWA does the following +.. iterate over bytes until non-number reached +.. take all seen numbers and format according to lang +*/ diff --git a/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_fmtr_base_tst.java b/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_fmtr_base_tst.java index a27517de8..cfa601b83 100644 --- a/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_fmtr_base_tst.java +++ b/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_fmtr_base_tst.java @@ -13,3 +13,104 @@ 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.langs.numbers; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import org.junit.*; +public class Xol_num_fmtr_base_tst { + Xol_num_fmtr_base mgr = new Xol_num_fmtr_base(); + @Before public void init() {mgr.Clear();} + @Test public void Outliers() { + ini_(".", dat_(",", 3)); + tst_Fmt("1234a1234" , "1,234a1,234"); + tst_Fmt("1234abc1234" , "1,234abc1,234"); + tst_Fmt("1234,1234" , "1,234,1,234"); + tst_Fmt("1234.1234" , "1,234.1234"); + tst_Fmt("1234." , "1,234."); + tst_Fmt("1234.1234.1234.1234" , "1,234.1234.1234.1234"); + tst_Fmt("-1234567" , "-1,234,567"); + tst_Fmt("1,234,567" , "1,234,567"); + } + @Test public void English() { + ini_(".", dat_(",", 3)); + tst_Fmt("123" , "123"); + tst_Fmt("1234" , "1,234"); + tst_Fmt("12345678" , "12,345,678"); + tst_Fmt("12345678901234567890" , "12,345,678,901,234,567,890"); + tst_Raw("1,234.12" , "1234.12"); + } + @Test public void French() { + ini_(",", dat_(" ", 3)); + tst_Fmt("123" , "123"); + tst_Fmt("1234" , "1 234"); + tst_Fmt("12345678" , "12 345 678"); + tst_Fmt("12345678901234567890" , "12 345 678 901 234 567 890"); + tst_Fmt("1234,5678" , "1 234 5 678"); // NOTE: nbsp here; also, nbsp is repeated. see dewiki and {{formatnum:1234,56}} + } + @Test public void Croatia() { + ini_(",", dat_(".", 3), dat_(",", 3)); + tst_Fmt("123" , "123"); + tst_Fmt("1234" , "1.234"); + tst_Fmt("12345678" , "12,345.678"); + tst_Fmt("12345678901234567890" , "12,345.678,901.234,567.890"); + } + @Test public void Mexico() { + ini_(".", dat_(",", 3, false), dat_("'", 3, false), dat_(",", 3)); + tst_Fmt("123" , "123"); + tst_Fmt("1234" , "1,234"); + tst_Fmt("12345678" , "12'345,678"); + tst_Fmt("12345678901234567890" , "12,345,678,901,234'567,890"); + tst_Raw("12'345,678.90" , "12345678.90"); + } + @Test public void China() { + ini_(".", dat_(",", 4)); + tst_Fmt("123" , "123"); + tst_Fmt("1234" , "1234"); + tst_Fmt("12345678" , "1234,5678"); + tst_Fmt("12345678901234567890" , "1234,5678,9012,3456,7890"); + } + @Test public void Hindi() { + ini_(".", dat_(",", 3, false), dat_(",", 2)); + tst_Fmt("123" , "123"); + tst_Fmt("1234" , "1,234"); + tst_Fmt("12345678" , "1,23,45,678"); + tst_Fmt("12345678901234567890" , "1,23,45,67,89,01,23,45,67,890"); + } + @Test public void India() { + ini_(".", dat_(",", 3), dat_(",", 2), dat_(",", 2)); + tst_Fmt("123" , "123"); + tst_Fmt("1234" , "1,234"); + tst_Fmt("12345678" , "1,23,45,678"); + tst_Fmt("12345678901234567890" , "1,23,456,78,90,123,45,67,890"); + } + @Test public void MiddleDot() { + ini_("·", dat_("·", 3)); + tst_Fmt("123" , "123"); + tst_Fmt("1234" , "1·234"); + tst_Fmt("12345678" , "12·345·678"); + tst_Fmt("12345678901234567890" , "12·345·678·901·234·567·890"); + tst_Fmt("1234·5678" , "1·234·5·678");// NOTE: middle-dot is repeated. see dewiki and {{formatnum:1234,5678}} + tst_Raw("1234·5678" , "1234.5678"); + } + Xol_num_grp dat_(String dlm, int digits) {return new Xol_num_grp(Bry_.new_u8(dlm), digits, true);} + Xol_num_grp dat_(String dlm, int digits, boolean repeat) {return new Xol_num_grp(Bry_.new_u8(dlm), digits, repeat);} + private void tst_Fmt(String val, String expd) {Tfds.Eq(expd, String_.new_u8(mgr.Fmt(Bry_.new_u8(val))));} + private void tst_Raw(String val, String expd) {Tfds.Eq(expd, String_.new_u8(mgr.Raw(Xol_num_fmtr_base.Tid_raw, Bry_.new_u8(val))));} + private void ini_(String dec_dlm, Xol_num_grp... ary) { + mgr.Dec_dlm_(Bry_.new_u8(dec_dlm)); + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) + mgr.Grps_add(ary[i]); + } +} +/* +'france' ' 3#' ',0%' // 1 234 567,89 +'spain' '.3#' "'0%" // 1.234.567'89 +'germany' '.3#' ",0%" // 1.234.567,89 +'italy' ''3#' ",0%" // 1'234'567,89 +'en-us' ',3#' '.0%' // 1,234,567.89 +'en-sa' ',3#' '\u00120%' // 1,234,567·89 +'croatia' ',3#*' '.3#*' ',0%' // 1,234.567,890.123,45 +'china' ',4$' // 123,4567.89 +'mexico' ',3#*' "'3#" ',3#' // 1'234,567.89 +'hindi' ",2#*" ',3#' // 1,23,45,678.9 +'india' ',2#*' ',2#*' ',3#*' // 1,245,67,89,012 +*/ \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_grp.java b/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_grp.java index a27517de8..0c17f8a24 100644 --- a/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_grp.java +++ b/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_grp.java @@ -13,3 +13,12 @@ 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.langs.numbers; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xol_num_grp { + public Xol_num_grp(byte[] dlm, int digits, boolean repeat) {this.dlm = dlm; this.digits = digits; this.repeat = repeat;} + public byte[] Dlm() {return dlm;} private byte[] dlm; + public int Digits() {return digits;} private int digits; + public boolean Repeat() {return repeat;} private boolean repeat; + public static final Xol_num_grp[] Ary_empty = new Xol_num_grp[0]; + public static final Xol_num_grp Default = new Xol_num_grp(new byte[] {Byte_ascii.Comma}, 3, true); +} diff --git a/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_grp_fmtr.java b/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_grp_fmtr.java index a27517de8..6ed106ba0 100644 --- a/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_grp_fmtr.java +++ b/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_grp_fmtr.java @@ -13,3 +13,67 @@ 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.langs.numbers; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xol_num_grp_fmtr { + public boolean Mode_is_regx() {return digit_grouping_pattern == null || Bry_.Eq(digit_grouping_pattern, Digit_grouping_pattern_normal);} + public byte[] Digit_grouping_pattern() {return digit_grouping_pattern;} public void Digit_grouping_pattern_(byte[] v) {digit_grouping_pattern = v;} private byte[] digit_grouping_pattern; + public void Clear() {digit_grouping_pattern = null;} + public byte[] Fmt_regx(Bry_bfr bfr, byte[] src) {// NOTE: specific code to handle preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );"; DATE:2014-04-15 + int src_len = src.length; + int bgn = 0; + int pos = bgn; + boolean dirty = false; + int grp_len = 3; + while (true) { + if (pos == src_len) break; + byte b = src[pos]; + switch (b) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: { + int num_end = Bry_find_.Find_fwd_while_num(src, pos, src_len); + int num_len = num_end - pos; + if (num_len > grp_len) { + if (!dirty) { + bfr.Add_mid(src, bgn, pos); + dirty = true; + } + Fmt_grp(bfr, src, pos, num_end, num_len, grp_len); + } + else { + if (dirty) + bfr.Add_mid(src, pos, num_end); + } + pos = num_end; + break; + } + case Byte_ascii.Dot: { + int num_end = Bry_find_.Find_fwd_while_num(src, pos + 1, src_len); // +1 to skip dot + if (dirty) + bfr.Add_mid(src, pos, num_end); + pos = num_end; + break; + } + default: + if (dirty) + bfr.Add_byte(b); + ++pos; + break; + } + } + return dirty ? bfr.To_bry_and_clear() : src; + } + private void Fmt_grp(Bry_bfr bfr, byte[] src, int bgn, int end, int len, int grp_len) { + int seg_0 = bgn + (len % grp_len); // 5 digit number will have seg_0 of 2; 12345 -> 12,345 + for (int i = bgn; i < end; i++) { + if ( i != bgn // never format at bgn; necessary for even multiples of grp_len (6, 9) + && ( i == seg_0 // seg_0 + || (i - seg_0) % grp_len == 0 // seg_n + ) + ) { + bfr.Add_byte(Byte_ascii.Comma); // MW: hard-coded + } + bfr.Add_byte(src[i]); + } + } + private static final byte[] Digit_grouping_pattern_normal = Bry_.new_a7("###,###,###"); +} diff --git a/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_grp_fmtr_tst.java b/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_grp_fmtr_tst.java index a27517de8..8bfa739e8 100644 --- a/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_grp_fmtr_tst.java +++ b/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_grp_fmtr_tst.java @@ -13,3 +13,40 @@ 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.langs.numbers; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import org.junit.*; +public class Xol_num_grp_fmtr_tst { + @Before public void init() {fxt.Reset();} private Xol_num_grp_fmtr_fxt fxt = new Xol_num_grp_fmtr_fxt(); + @Test public void Num() { + fxt.Test_fmt_regx("" , ""); + fxt.Test_fmt_regx("1" , "1"); + fxt.Test_fmt_regx("12" , "12"); + fxt.Test_fmt_regx("123" , "123"); + fxt.Test_fmt_regx("1234" , "1,234"); + fxt.Test_fmt_regx("12345" , "12,345"); + fxt.Test_fmt_regx("123456" , "123,456"); + fxt.Test_fmt_regx("1234567" , "1,234,567"); + fxt.Test_fmt_regx("1234567890" , "1,234,567,890"); + } + @Test public void Dec() { + fxt.Test_fmt_regx("1.9876" , "1.9876"); + fxt.Test_fmt_regx("1234.9876" , "1,234.9876"); + } + @Test public void Neg() { + fxt.Test_fmt_regx("-1234.5678" , "-1,234.5678"); + } + @Test public void Char() { + fxt.Test_fmt_regx("1,234" , "1,234"); + fxt.Test_fmt_regx("1a2345" , "1a2,345"); + fxt.Test_fmt_regx("1234a5678b2345c.3456d7890e3210.f5432", "1,234a5,678b2,345c.3456d7,890e3,210.f5,432"); + } +} +class Xol_num_grp_fmtr_fxt { + private Xol_num_grp_fmtr grouper = new Xol_num_grp_fmtr(); + private Bry_bfr bfr = Bry_bfr_.New(); + public void Reset() {} + public void Test_fmt_regx(String raw, String expd) { + byte[] actl = grouper.Fmt_regx(bfr, Bry_.new_a7(raw)); + Tfds.Eq(expd, String_.new_u8(actl)); + } +} diff --git a/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_mgr.java b/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_mgr.java index a27517de8..b0c2f0aa0 100644 --- a/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_mgr.java +++ b/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_mgr.java @@ -13,3 +13,61 @@ 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.langs.numbers; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xol_num_mgr implements Gfo_invk { + private boolean digits_translate; + protected Bry_bfr tmp_bfr = Bry_bfr_.Reset(32); + private static final byte[] Comma_bry = Bry_.new_a7(","); + public Xol_num_grp_fmtr Num_grp_fmtr() {return num_grp_fmtr;} private Xol_num_grp_fmtr num_grp_fmtr = new Xol_num_grp_fmtr(); + public Xol_transform_mgr Separators_mgr() {return separators_mgr;} private Xol_transform_mgr separators_mgr = new Xol_transform_mgr(); + public Xol_transform_mgr Digits_mgr() {return digits_mgr;} private Xol_transform_mgr digits_mgr = new Xol_transform_mgr(); + public byte[] Raw(byte[] num) { + if (digits_translate) + num = digits_mgr.Replace(tmp_bfr, num, false); + num = separators_mgr.Replace(tmp_bfr, num, false); + num = Bry_.Replace_safe(tmp_bfr, num, Comma_bry, Bry_.Empty); + return num; + } + public byte[] Format_num_no_separators(byte[] num) {return Format_num(num, true);} + public byte[] Format_num_by_long(long val) {return Format_num(Bry_.new_a7(Long_.To_str(val)));} + public byte[] Format_num_by_decimal(Decimal_adp val){return Format_num(Bry_.new_a7(val.To_str()));} + public byte[] Format_num(int val) {return Format_num(Bry_.new_a7(Int_.To_str(val)));} + public byte[] Format_num(byte[] num) {return Format_num(num, false);} + public byte[] Format_num(byte[] num, boolean skip_commafy) { + if (!skip_commafy) { + num = Commafy(num); + num = separators_mgr.Replace(tmp_bfr, num, true); + } + if (digits_translate) + num = digits_mgr.Replace(tmp_bfr, num, true); + return num; + } + @gplx.Virtual public byte[] Commafy(byte[] num_bry) { + if (num_bry == null) return Bry_.Empty; // MW: if ( $number === null ) return ''; + if (num_grp_fmtr.Mode_is_regx()) + return num_grp_fmtr.Fmt_regx(tmp_bfr, num_bry); + else // NOTE: for now, return same as ###,###,###; only affects 12 languages; current implementation is bad; https://bugzilla.wikimedia.org/show_bug.cgi?id=63977 + return num_grp_fmtr.Fmt_regx(tmp_bfr, num_bry); + } + public Xol_num_mgr Clear() { + digits_mgr.Clear(); + separators_mgr.Clear(); + num_grp_fmtr.Clear(); + return this; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_clear)) this.Clear(); + else if (ctx.Match(k, Invk_separators)) return separators_mgr; + else if (ctx.Match(k, Invk_digits)) {digits_translate = true; return digits_mgr;} // NOTE: only langes with a digit_transform_table will call digits; DATE:2014-05-28 + else if (ctx.Match(k, Invk_digit_grouping_pattern)) return String_.new_u8(num_grp_fmtr.Digit_grouping_pattern()); + else if (ctx.Match(k, Invk_digit_grouping_pattern_)) num_grp_fmtr.Digit_grouping_pattern_(m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk_clear = "clear", Invk_separators = "separators" + , Invk_digits = "digits", Invk_digit_grouping_pattern = "digit_grouping_pattern", Invk_digit_grouping_pattern_ = "digit_grouping_pattern_"; + public static final byte[] + Separators_key__grp = new byte[]{Byte_ascii.Comma} + , Separators_key__dec = new byte[]{Byte_ascii.Dot} + ; +} diff --git a/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_mgr_.java b/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_mgr_.java index a27517de8..0ae6fc1cb 100644 --- a/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_mgr_.java +++ b/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_mgr_.java @@ -13,3 +13,26 @@ 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.langs.numbers; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xol_num_mgr_ { + public static Xol_num_mgr new_by_lang_id(int lang_id) { + switch (lang_id) { + case Xol_lang_stub_.Id_be_tarask: + case Xol_lang_stub_.Id_bg: + case Xol_lang_stub_.Id_ru: + case Xol_lang_stub_.Id_pl: + case Xol_lang_stub_.Id_uk: + case Xol_lang_stub_.Id_es: + case Xol_lang_stub_.Id_et: + case Xol_lang_stub_.Id_hy: + case Xol_lang_stub_.Id_kaa: + case Xol_lang_stub_.Id_kk_cyrl: + case Xol_lang_stub_.Id_ksh: + // case Xol_lang_stub_.Id_ku_ku: + return new Xol_num_mgr__commafy_5(); + case Xol_lang_stub_.Id_km: + case Xol_lang_stub_.Id_my: return new Xol_num_mgr__noop(); + default: return new Xol_num_mgr(); + } + } +} diff --git a/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_mgr__commafy_5.java b/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_mgr__commafy_5.java index a27517de8..e55f0ed39 100644 --- a/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_mgr__commafy_5.java +++ b/400_xowa/src/gplx/xowa/langs/numbers/Xol_num_mgr__commafy_5.java @@ -13,3 +13,30 @@ 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.langs.numbers; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +class Xol_num_mgr__commafy_5 extends Xol_num_mgr { @Override public byte[] Commafy(byte[] num) { + if (Bry_.Len_eq_0(num)) return num; // bounds check + int num_len = num.length; + int num_bgn = 0; + byte b = num[num_bgn]; + if (b == Byte_ascii.Dash) { + if (num_len == 1) return num; // bounds check + b = num[++num_bgn]; // skip negative sign + } + if (Byte_ascii.Is_num(b)) { // check for preg_match( '/^-?\d{1,4}(\.\d+)?$/', $_ ) + int num_end = Bry_find_.Find_fwd_while_num(num, num_bgn, num_len); + if (num_end - num_bgn < 5) { // 1-4 digits + if (num_end == num_len) return num; // no decimal; exit + b = num[num_end]; + if ( b == Byte_ascii.Dot + && num_end != num_len - 1) { // if dot at end, then no match on above regx; fall-thru to below + num_end = Bry_find_.Find_fwd_while_num(num, num_end + 1, num_len); + if (num_end == num_len) return num; // only numbers after dot; matches regx; + } + } + } + return this.Num_grp_fmtr().Fmt_regx(tmp_bfr, num); // otherwise do default grouping; '/(\d{3})(?=\d)(?!\d*\.)/', '$1,' + } +} +class Xol_num_mgr__noop extends Xol_num_mgr { @Override public byte[] Commafy(byte[] num) {return num;} +} diff --git a/400_xowa/src/gplx/xowa/langs/numbers/Xol_transform_mgr.java b/400_xowa/src/gplx/xowa/langs/numbers/Xol_transform_mgr.java index a27517de8..cc8eca55e 100644 --- a/400_xowa/src/gplx/xowa/langs/numbers/Xol_transform_mgr.java +++ b/400_xowa/src/gplx/xowa/langs/numbers/Xol_transform_mgr.java @@ -13,3 +13,40 @@ 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.langs.numbers; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.btries.*; +public class Xol_transform_mgr implements Gfo_invk { + private Btrie_fast_mgr trie_k_to_v = Btrie_fast_mgr.cs(); + private Btrie_fast_mgr trie_v_to_k = Btrie_fast_mgr.cs(); + private Ordered_hash hash = Ordered_hash_.New_bry(); + private boolean empty = true; + public void Clear() {hash.Clear(); trie_k_to_v.Clear(); trie_v_to_k.Clear(); empty = true;} + public int Len() {return hash.Count();} + public Keyval Get_at(int i) {return (Keyval)hash.Get_at(i);} + public byte[] Get_val_or_self(byte[] k) { // NOTE: return self; note that MW defaults "." and "," to self, even though MessagesLa.php only specifies ","; i.e.: always return something for "."; DATE:2014-05-13 + Keyval kv = (Keyval)hash.Get_by(k); + return kv == null ? k : (byte[])kv.Val(); + } + public Xol_transform_mgr Set(byte[] k, byte[] v) { + trie_k_to_v.Add(k, v); + trie_v_to_k.Add(v, k); + Keyval kv = Keyval_.new_(String_.new_u8(k), v); + hash.Del(k); + hash.Add(k, kv); + empty = false; + return this; + } + public byte[] Replace(Bry_bfr tmp_bfr, byte[] src, boolean k_to_v) { + if (empty || src == null) return src; + int src_len = src.length; if (src_len == 0) return src; + Btrie_fast_mgr trie = k_to_v ? trie_k_to_v : trie_v_to_k; + return trie.Replace(tmp_bfr, src, 0, src_len); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_set)) Set(m.ReadBry("k"), m.ReadBry("v")); + else if (ctx.Match(k, Invk_clear)) Clear(); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk_set = "set", Invk_clear = "clear"; +} diff --git a/400_xowa/src/gplx/xowa/langs/parsers/Xol_csv_parser.java b/400_xowa/src/gplx/xowa/langs/parsers/Xol_csv_parser.java index a27517de8..c50038593 100644 --- a/400_xowa/src/gplx/xowa/langs/parsers/Xol_csv_parser.java +++ b/400_xowa/src/gplx/xowa/langs/parsers/Xol_csv_parser.java @@ -13,3 +13,68 @@ 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.langs.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xol_csv_parser { + public void Save(Bry_bfr bfr, byte[] src) { + int len = src.length; + for (int i = 0; i < len; i++) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Cr: bfr.Add_byte(Byte_ascii.Backslash); bfr.Add_byte(Byte_ascii.Ltr_r); break; + case Byte_ascii.Nl: bfr.Add_byte(Byte_ascii.Backslash); bfr.Add_byte(Byte_ascii.Ltr_n); break; + case Byte_ascii.Tab: bfr.Add_byte(Byte_ascii.Backslash); bfr.Add_byte(Byte_ascii.Ltr_t); break; + case Byte_ascii.Backslash: bfr.Add_byte(Byte_ascii.Backslash); bfr.Add_byte(Byte_ascii.Backslash); break; + case Byte_ascii.Pipe: bfr.Add(Bry_pipe); break; + default: bfr.Add_byte(b); break; + } + } + } + public byte[] Load(byte[] src, int bgn, int end) {Load(tmp_bfr, src, bgn, end); return tmp_bfr.To_bry_and_clear();} + public void Load(Bry_bfr bfr, byte[] src) {Load(bfr, src, 0, src.length);} + public void Load(Bry_bfr bfr, byte[] src, int bgn, int end) { + for (int i = bgn; i < end; i++) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Backslash: { + int nxt_pos = i + 1; if (nxt_pos == end) throw Err_.new_wo_type("backslash cannot be last character"); + byte nxt_byte = src[nxt_pos]; + switch (nxt_byte) { + case Byte_ascii.Backslash: bfr.Add_byte(Byte_ascii.Backslash); break; + case Byte_ascii.Ltr_r: bfr.Add_byte(Byte_ascii.Cr); break; + case Byte_ascii.Ltr_n: bfr.Add_byte(Byte_ascii.Nl); break; + case Byte_ascii.Ltr_t: bfr.Add_byte(Byte_ascii.Tab); break; + case Byte_ascii.Ltr_u: + int utf_len = 1; + for (int j = i + 6; j < end; j += 6) { // iterate over rest of String; EX: \u0123 + if (j + 1 < end && src[j] == Byte_ascii.Backslash && src[j + 1] == Byte_ascii.Ltr_u) + ++utf_len; + else + break; + } + byte[] utf_bytes = new byte[utf_len]; int utf_idx = 0; + int utf_pos = i + 2; + for (int j = 0; j < utf_len; j++) { + int utf_int = Int_.By_hex_bry(src, utf_pos, utf_pos + 4); + if (utf_int == -1) throw Err_.new_wo_type("invalid value for \\u", "val", String_.new_u8(src, bgn, end)); + utf_bytes[utf_idx++] = (byte)utf_int; + utf_pos += 6; + } + int utf_int_decoded = gplx.core.intls.Utf16_.Decode_to_int(utf_bytes, 0); + bfr.Add(gplx.core.intls.Utf16_.Encode_int_to_bry(utf_int_decoded)); + nxt_pos = i + (utf_len * 6) - 1; // -1 b/c "for" will ++ + break; + default: + bfr.Add_byte(b).Add_byte(nxt_byte); + break; + } + i = nxt_pos; + break; + } + default: bfr.Add_byte(b); break; + } + } + } + private static final byte[] Bry_pipe = Bry_.new_a7("\\u007C"); + private static final Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); + public static final Xol_csv_parser Instance = new Xol_csv_parser(); Xol_csv_parser() {} +} diff --git a/400_xowa/src/gplx/xowa/langs/parsers/Xol_csv_parser_tst.java b/400_xowa/src/gplx/xowa/langs/parsers/Xol_csv_parser_tst.java index a27517de8..66c7dac78 100644 --- a/400_xowa/src/gplx/xowa/langs/parsers/Xol_csv_parser_tst.java +++ b/400_xowa/src/gplx/xowa/langs/parsers/Xol_csv_parser_tst.java @@ -13,3 +13,27 @@ 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.langs.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import org.junit.*; +public class Xol_csv_parser_tst { + Xol_csv_parser_fxt fxt = new Xol_csv_parser_fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Save() {fxt.Tst_save("a\r\n\t|d", "a\\r\\n\\t\\u007Cd");} + @Test public void Load() {fxt.Tst_load("a\r\n\t|d", "a\\r\\n\\t\\u007Cd");} + @Test public void Save_backslash() {fxt.Tst_save("a\\\\n", "a\\\\\\\\n");} + @Test public void Load_backslash() {fxt.Tst_load("a\\\\n", "a\\\\\\\\n");} + @Test public void Utf() {fxt.Tst_load(" ", "\\u00c2\\u00a0");} // NOTE: 1st String is nbsp; +} +class Xol_csv_parser_fxt { + Xol_csv_parser parser = Xol_csv_parser.Instance; Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); + public void Clear() {} + public void Tst_save(String raw, String expd) { + parser.Save(tmp_bfr, Bry_.new_u8(raw)); + Tfds.Eq(expd, tmp_bfr.To_str_and_clear()); + } + public void Tst_load(String expd, String raw_str) { + byte[] raw = Bry_.new_u8(raw_str); + parser.Load(tmp_bfr, raw, 0, raw.length); + Tfds.Eq(expd, tmp_bfr.To_str_and_clear()); + } +} diff --git a/400_xowa/src/gplx/xowa/langs/parsers/Xol_lang_srl.java b/400_xowa/src/gplx/xowa/langs/parsers/Xol_lang_srl.java index a27517de8..5ccd575e5 100644 --- a/400_xowa/src/gplx/xowa/langs/parsers/Xol_lang_srl.java +++ b/400_xowa/src/gplx/xowa/langs/parsers/Xol_lang_srl.java @@ -13,3 +13,261 @@ 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.langs.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.intls.*; +import gplx.xowa.apps.gfs.*; +import gplx.xowa.langs.numbers.*; import gplx.xowa.langs.msgs.*; import gplx.xowa.langs.kwds.*; import gplx.xowa.langs.bldrs.*; import gplx.xowa.langs.specials.*; +import gplx.xowa.wikis.nss.*; +public class Xol_lang_srl { + public static Xow_ns[] Load_ns_grps(byte[] src) { + int src_len = src.length, pos = 0, fld_bgn = 0; + int cur_id = -1; + List_adp rv = List_adp_.New(); Xol_csv_parser csv_parser = Xol_csv_parser.Instance; + while (true) { + boolean last = pos == src_len; // NOTE: logic occurs b/c of \n}~-> dlm which gobbles up last \n + byte b = last ? Byte_ascii.Nl : src[pos]; + switch (b) { + case Byte_ascii.Pipe: + cur_id = Bry_.To_int_or(src, fld_bgn, pos, Int_.Min_value); + if (cur_id == Int_.Min_value) throw Err_.new_wo_type("invalid_id", "id", String_.new_u8(src, fld_bgn, pos)); + fld_bgn = pos + 1; + break; + case Byte_ascii.Nl: + byte[] cur_name = csv_parser.Load(src, fld_bgn, pos); + cur_name = Xoa_ttl.Replace_spaces(cur_name); // NOTE: *.gfs files will have names with \s instead of _; this comes from Language.php which also has same \s convention; EX: "Template talk" instead of "Template_talk" + Xow_ns ns = new Xow_ns(cur_id, Xow_ns_case_.Tid__1st, cur_name, false); + rv.Add(ns); + fld_bgn = pos + 1; + cur_id = -1; + break; + default: + break; + } + if (last) break; + ++pos; + } + return (Xow_ns[])rv.To_ary(Xow_ns.class); + } + public static void Load_keywords(Xol_kwd_mgr keyword_mgr, byte[] src) { + int src_len = src.length, pos = 0, fld_bgn = 0, fld_idx = 0; + boolean cur_cs = false; byte[] cur_key = Bry_.Empty; + List_adp cur_words = List_adp_.New(); + Xol_csv_parser csv_parser = Xol_csv_parser.Instance; + while (true) { + boolean last = pos == src_len; // NOTE: logic occurs b/c of \n}~-> dlm which gobbles up last \n + byte b = last ? Byte_ascii.Nl : src[pos]; + switch (b) { + case Byte_ascii.Pipe: + switch (fld_idx) { + case 0: + cur_key = csv_parser.Load(src, fld_bgn, pos); + break; + case 1: + byte cs_byte = src[pos - 1]; + switch (cs_byte) { + case Byte_ascii.Num_0: cur_cs = false; break; + case Byte_ascii.Num_1: cur_cs = true; break; + default: throw Err_.new_wo_type("case sensitive should be 0 or 1", "cs", Byte_.To_str(cs_byte)); + } + break; + } + fld_bgn = pos + 1; + ++fld_idx; + break; + case Byte_ascii.Tilde: + byte[] word = csv_parser.Load(src, fld_bgn, pos); + cur_words.Add(word); + fld_bgn = pos + 1; + break; + case Byte_ascii.Nl: + if (cur_words.Count() > 0) { // guard against blank line wiping out entries; EX: "toc|0|toc1\n\n"; 2nd \n will get last grp and make 0 entries + int cur_id = Xol_kwd_grp_.Id_by_bry(cur_key); if (cur_id == -1) throw Err_.new_wo_type("key does not have id", "id", cur_id); + Xol_kwd_grp grp = keyword_mgr.Get_or_new(cur_id); + grp.Srl_load(cur_cs, (byte[][])cur_words.To_ary(byte[].class)); + } + fld_bgn = pos + 1; + fld_idx = 0; + cur_words.Clear(); + break; + default: + break; + } + if (last) break; + ++pos; + } +// return (Xol_kwd_grp[])rv.To_ary(typeof(Xol_kwd_grp)); + } + public static void Load_messages(Xol_msg_mgr msg_mgr, byte[] src) { + int src_len = src.length, pos = 0, fld_bgn = 0; + byte[] cur_key = Bry_.Empty; + Xol_csv_parser csv_parser = Xol_csv_parser.Instance; + while (true) { + boolean last = pos == src_len; // NOTE: logic occurs b/c of \n}~-> dlm which gobbles up last \n + byte b = last ? Byte_ascii.Nl : src[pos]; + switch (b) { + case Byte_ascii.Pipe: + cur_key = csv_parser.Load(src, fld_bgn, pos); + fld_bgn = pos + 1; + break; + case Byte_ascii.Nl: + byte[] cur_val = csv_parser.Load(src, fld_bgn, pos); + Xol_msg_itm itm = msg_mgr.Itm_by_key_or_new(cur_key).Defined_in_(Xol_msg_itm.Defined_in__lang); // NOTE: this proc should only be called when loading lang.gfs + Xol_msg_itm_.update_val_(itm, cur_val); + itm.Dirty_(true); + fld_bgn = pos + 1; + break; + default: + break; + } + if (last) break; + ++pos; + } + } + public static void Load_specials(Xol_specials_mgr special_mgr, byte[] src) { + int src_len = src.length, pos = 0, fld_bgn = 0; + byte[] cur_key = Bry_.Empty; + Xol_csv_parser csv_parser = Xol_csv_parser.Instance; + while (true) { + boolean last = pos == src_len; // NOTE: logic occurs b/c of \n}~-> dlm which gobbles up last \n + byte b = last ? Byte_ascii.Nl : src[pos]; + switch (b) { + case Byte_ascii.Pipe: + cur_key = csv_parser.Load(src, fld_bgn, pos); + fld_bgn = pos + 1; + break; + case Byte_ascii.Nl: + byte[] cur_val_raw = csv_parser.Load(src, fld_bgn, pos); + byte[][] cur_vals = Bry_split_.Split(cur_val_raw, Byte_ascii.Tilde); + special_mgr.Add(cur_key, cur_vals); + fld_bgn = pos + 1; + break; + default: + break; + } + if (last) break; + ++pos; + } + } + public static void Save_num_mgr(Xoa_gfs_bldr bldr, Xol_num_mgr num_mgr) { + Xol_transform_mgr separators_mgr = num_mgr.Separators_mgr(); int separators_len = separators_mgr.Len(); + Xol_transform_mgr digits_mgr = num_mgr.Digits_mgr(); int digits_len = digits_mgr.Len(); + byte[] digit_grouping_pattern = num_mgr.Num_grp_fmtr().Digit_grouping_pattern(); + if (separators_len > 0 || digits_len > 0 || digit_grouping_pattern != null) { + bldr.Add_proc_init_one(Xol_lang_itm.Invk_numbers).Add_curly_bgn_nl(); // numbers { + if (digit_grouping_pattern != null) { + bldr.Add_indent(1).Add_eq_str(Xol_num_mgr.Invk_digit_grouping_pattern, digit_grouping_pattern); + } + if (separators_len > 0) { + bldr.Add_indent(1).Add_proc_init_one(Xol_num_mgr.Invk_separators).Add_curly_bgn_nl(); // separators { + bldr.Add_indent(2).Add_proc_init_one(Xol_num_mgr.Invk_clear).Add_term_nl(); // clear; + for (int i = 0; i < separators_len; i++) { + Keyval kv = separators_mgr.Get_at(i); + String k = kv.Key(), v = kv.Val_to_str_or_empty(); + bldr.Add_indent(2).Add_proc_init_many(Xol_transform_mgr.Invk_set).Add_parens_str_many(k, v).Add_term_nl(); // set('k', 'v'); + } + bldr.Add_indent(1).Add_curly_end_nl(); // } + } + if (digits_len > 0) { + bldr.Add_indent(1).Add_proc_init_one(Xol_num_mgr.Invk_digits).Add_curly_bgn_nl(); // digits { + bldr.Add_indent(2).Add_proc_init_one(Xol_num_mgr.Invk_clear).Add_term_nl(); // clear; + for (int i = 0; i < digits_len; i++) { + Keyval kv = digits_mgr.Get_at(i); + String k = kv.Key(), v = kv.Val_to_str_or_empty(); + bldr.Add_indent(2).Add_proc_init_many(Xol_transform_mgr.Invk_set).Add_parens_str_many(k, v).Add_term_nl(); // set('k', 'v'); + } + bldr.Add_indent(1).Add_curly_end_nl(); // } + } + bldr.Add_curly_end_nl(); // } + } + } + public static void Save_ns_grps(Xoa_gfs_bldr bldr, Xol_ns_grp ns_grp, String proc_invk) { + int ns_grp_len = ns_grp.Len(); Xol_csv_parser csv_parser = Xol_csv_parser.Instance; + if (ns_grp_len == 0) return; + Bry_bfr bfr = bldr.Bfr(); + bldr.Add_proc_cont_one(proc_invk).Add_nl(); + bldr.Add_indent().Add_proc_cont_one(Invk_load_text).Add_paren_bgn().Add_nl(); // .load_text(\n + bldr.Add_quote_xtn_bgn(); // <~{\n' + for (int i = 0; i < ns_grp_len; i++) { + Xow_ns ns = ns_grp.Get_at(i); + bfr.Add_int_variable(ns.Id()).Add_byte_pipe(); // id| + csv_parser.Save(bfr, ns.Name_db()); // name + bfr.Add_byte_nl(); // \n + } + bldr.Add_quote_xtn_end(); // ']:>\n + bldr.Add_paren_end().Add_proc_cont_one(Invk_lang).Add_nl(); // ).lang\n + } + public static void Save_specials(Xoa_gfs_bldr bldr, Xol_specials_mgr specials_mgr) { + int specials_len = specials_mgr.Len(); Xol_csv_parser csv_parser = Xol_csv_parser.Instance; + if (specials_len == 0) return; + Bry_bfr bfr = bldr.Bfr(); + bldr.Add_proc_cont_one(Xol_lang_itm.Invk_specials).Add_nl(); + bldr.Add_indent().Add_proc_cont_one(Xol_specials_mgr.Invk_clear).Add_nl(); + bldr.Add_indent().Add_proc_cont_one(Invk_load_text).Add_paren_bgn().Add_nl(); // .load_text(\n + bldr.Add_quote_xtn_bgn(); // <~{\n' + for (int i = 0; i < specials_len; i++) { + Xol_specials_itm itm = specials_mgr.Get_at(i); + bfr.Add(itm.Special()).Add_byte_pipe(); // id| + int aliases_len = itm.Aliases().length; + for (int j = 0; j < aliases_len; j++) { + if (j != 0) bfr.Add_byte(Byte_ascii.Tilde); + csv_parser.Save(bfr, itm.Aliases()[j]); // name + } + bfr.Add_byte_nl(); // \n + } + bldr.Add_quote_xtn_end(); // ']:>\n + bldr.Add_paren_end().Add_proc_cont_one(Invk_lang).Add_nl(); // ).lang\n + } + public static void Save_keywords(Xoa_gfs_bldr bldr, Xol_kwd_mgr kwd_mgr) { + int len = kwd_mgr.Len(); Xol_csv_parser csv_parser = Xol_csv_parser.Instance; + int count = 0; + for (int i = 0; i < len; i++) { + Xol_kwd_grp grp = kwd_mgr.Get_at(i); if (grp == null) continue; // some items may not be loaded/set by lang + ++count; + } + if (count == 0) return; + Bry_bfr bfr = bldr.Bfr(); + bldr.Add_proc_cont_one(Xol_lang_itm.Invk_keywords).Add_nl(); // .keywords\n + bldr.Add_indent().Add_proc_cont_one(Invk_load_text).Add_paren_bgn().Add_nl(); // .load_text(\n + bldr.Add_quote_xtn_bgn(); // <~{\n' + for (int i = 0; i < len; i++) { + Xol_kwd_grp grp = kwd_mgr.Get_at(i); if (grp == null) continue; // some items may not be loaded/set by lang + csv_parser.Save(bfr, grp.Key()); // key + bfr.Add_byte_pipe(); // | + bfr.Add_int_bool(grp.Case_match()).Add_byte_pipe(); // 1| + int word_len = grp.Itms().length; + for (int j = 0; j < word_len; j++) { + Xol_kwd_itm word = grp.Itms()[j]; + csv_parser.Save(bfr, word.Val()); // word + bfr.Add_byte(Byte_ascii.Tilde); // ~ + } + bldr.Add_nl(); // \n + } + bldr.Add_quote_xtn_end(); // ']:>\n + bldr.Add_paren_end().Add_proc_cont_one(Invk_lang).Add_nl(); // ).lang\n + } + public static void Save_messages(Xoa_gfs_bldr bldr, Xol_msg_mgr msg_mgr, boolean dirty) { + int len = msg_mgr.Itms_max(); Xol_csv_parser csv_parser = Xol_csv_parser.Instance; + int count = 0; + for (int i = 0; i < len; i++) { + Xol_msg_itm itm = msg_mgr.Itm_by_id_or_null(i); if (itm == null) continue; // some items may not be loaded/set by lang + if (dirty && !itm.Dirty()) continue; // TEST: + ++count; + } + if (count == 0) return; + Bry_bfr bfr = bldr.Bfr(); + bldr.Add_proc_cont_one(Xol_lang_itm.Invk_messages).Add_nl(); // .keywords\n + bldr.Add_indent().Add_proc_cont_one(Invk_load_text).Add_paren_bgn().Add_nl(); // .load_text(\n + bldr.Add_quote_xtn_bgn(); // <~{\n' + for (int i = 0; i < len; i++) { + Xol_msg_itm itm = msg_mgr.Itm_by_id_or_null(i); if (itm == null) continue; // some items may not be loaded/set by lang + if (dirty && !itm.Dirty()) continue; // TEST: + csv_parser.Save(bfr, itm.Key()); // key + bfr.Add_byte_pipe(); // | + csv_parser.Save(bfr, itm.Val()); // val + bfr.Add_byte_nl(); // \n + } + bldr.Add_quote_xtn_end(); // ']:>\n + bldr.Add_paren_end().Add_proc_cont_one(Invk_lang).Add_nl(); // ).lang\n + } + public static final String Invk_load_text = "load_text", Invk_lang = "lang"; +} diff --git a/400_xowa/src/gplx/xowa/langs/parsers/Xol_lang_srl_tst.java b/400_xowa/src/gplx/xowa/langs/parsers/Xol_lang_srl_tst.java index a27517de8..915140a18 100644 --- a/400_xowa/src/gplx/xowa/langs/parsers/Xol_lang_srl_tst.java +++ b/400_xowa/src/gplx/xowa/langs/parsers/Xol_lang_srl_tst.java @@ -13,3 +13,334 @@ 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.langs.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import org.junit.*; import gplx.core.strings.*; +import gplx.core.intls.*; +import gplx.xowa.apps.gfs.*; +import gplx.xowa.langs.numbers.*; import gplx.xowa.langs.msgs.*; import gplx.xowa.langs.kwds.*; import gplx.xowa.langs.bldrs.*; import gplx.xowa.langs.specials.*; +import gplx.xowa.wikis.nss.*; +public class Xol_lang_srl_tst { + private Xol_lang_srl_fxt fxt = new Xol_lang_srl_fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Ns_names() { + String raw = String_.Concat_lines_nl + ( "ns_names" + , " .load_text(" + , "<:['" + , "6|Filex" + , "10|Templatex" + , "']:>" + , ").lang" + ); + fxt.Invk(raw); + Xol_ns_grp grp = fxt.Lang().Ns_names(); + fxt.Tst_ns_grp(grp, fxt.ns_(6, "Filex"), fxt.ns_(10, "Templatex")); + fxt.Run_save_ns_grp(grp, Xol_lang_itm.Invk_ns_names, raw); + } + @Test public void Ns_aliases() { + String raw = String_.Concat_lines_nl + ( "ns_aliases" + , " .load_text(" + , "<:['" + , "6|Filex" + , "10|Templatex" + , "']:>" + , ").lang" + ); + fxt.Invk(raw); + Xol_ns_grp grp = fxt.Lang().Ns_aliases(); + fxt.Tst_ns_grp(grp, fxt.ns_(6, "Filex"), fxt.ns_(10, "Templatex")); + fxt.Run_save_ns_grp(grp, Xol_lang_itm.Invk_ns_aliases, raw); + } + @Test public void Kwds() { + String raw = String_.Concat_lines_nl // NOTE: notoc must go before toc because ID order + ( "keywords" + , " .load_text(" + , "<:['" + , "notoc|1|no_table_of_contents~toc_n~" + , "toc|0|table_of_contents~toc_y~" + , "']:>" + , ").lang" + ); + fxt.Invk(raw); + Xol_kwd_mgr kwd_mgr = fxt.Lang().Kwd_mgr(); + fxt.Tst_keywords(kwd_mgr, fxt.kwd_("notoc", true, "no_table_of_contents", "toc_n"), fxt.kwd_("toc", false, "table_of_contents", "toc_y")); + fxt.Run_save_kwd_mgr(kwd_mgr, Xol_lang_itm.Invk_keywords, raw); + } + @Test public void Specials() { + String raw = String_.Concat_lines_nl // NOTE: notoc must go before toc because ID order + ( "specials" + , " .clear" + , " .load_text(" + , "<:['" + , "Randompage|PageAuHasard~Page_au_hasard" + , "Search|Recherche~Rechercher" + , "']:>" + , ").lang" + ); + fxt.Invk(raw); + Xol_specials_mgr specials_mgr = fxt.Lang().Specials_mgr(); + fxt.Tst_specials(specials_mgr, fxt.special_("Randompage", "PageAuHasard", "Page_au_hasard"), fxt.special_("Search", "Recherche", "Rechercher")); + fxt.Run_save_specials_mgr(specials_mgr, Xol_lang_itm.Invk_specials, raw); + } + @Test public void Kwds_blank_line() { // PURPOSE.fix: extra blank line negates entire entry + String raw = String_.Concat_lines_nl + ( "keywords" + , " .load_text(" + , "<:['" + , "toc|0|table_of_contents~toc_y~" + , "" + , "']:>" + , ").lang" + ); + fxt.Invk(raw); + Xol_kwd_mgr kwd_mgr = fxt.Lang().Kwd_mgr(); + fxt.Tst_keywords(kwd_mgr, fxt.kwd_("toc", false, "table_of_contents", "toc_y")); // make sure 2 items (and not 0) + } + @Test public void Msgs() { + String raw = String_.Concat_lines_nl + ( "messages" + , " .load_text(" + , "<:['" + , "sunday|dimanche" + , "monday|lundi" + , "']:>" + , ").lang" + ); + fxt.Invk(raw); + Xol_msg_mgr msg_mgr = fxt.Lang().Msg_mgr(); + fxt.Tst_messages(msg_mgr, fxt.msg_("sunday", "dimanche"), fxt.msg_("monday", "lundi")); + fxt.Run_save_msg_mgr(msg_mgr, Xol_lang_itm.Invk_messages, raw); + } + @Test public void Fallback() { + Io_mgr.Instance.SaveFilStr(Xol_lang_itm_.xo_lang_fil_(fxt.App().Fsys_mgr(), "zh-hans"), String_.Concat_lines_nl + ( "this" + , ".keywords" + , " .load_text(" + , "<:['" + , "toc|0|table_of_contents~toc_y~" + , "']:>" + , ").lang" + , ".ns_names" + , " .load_text(" + , "<:['" + , "6|FileA" + , "']:>" + , ").lang" + , ".messages" + , " .load_text(" + , "<:['" + , "sunday|sunday1" + , "']:>" + , ").lang" + , ";" + )); + String raw = String_.Concat_lines_nl // NOTE: notoc must go before toc because ID order + ( "fallback_load('zh-hans')" + , ".keywords" + , " .load_text(" + , "<:['" + , "notoc|1|no_table_of_contents~toc_n~" + , "']:>" + , ").lang" + , ".ns_names" + , " .load_text(" + , "<:['" + , "6|FileB" + , "']:>" + , ").lang" + , ".messages" + , " .load_text(" + , "<:['" + , "monday|monday1" + , "']:>" + , ").lang" + ); + fxt.Invk(raw); + Xol_kwd_mgr kwd_mgr = fxt.Lang().Kwd_mgr(); + fxt.Tst_keywords(kwd_mgr, fxt.kwd_("notoc", true, "no_table_of_contents", "toc_n"), fxt.kwd_("toc", false, "table_of_contents", "toc_y")); + fxt.Tst_ns_grp(fxt.Lang().Ns_names(), fxt.ns_(6, "FileA"), fxt.ns_(6, "FileB")); + fxt.Tst_messages(fxt.Lang().Msg_mgr(), fxt.msg_("sunday", "sunday1"), fxt.msg_("monday", "monday1")); + } + @Test public void Fallback_circular() { // PURPOSE: pt and pt-br cite each other as fallback in Messages*.php; DATE:2013-02-18 + Io_mgr.Instance.SaveFilStr(Xol_lang_itm_.xo_lang_fil_(fxt.App().Fsys_mgr(), "pt") , "fallback_load('pt-br');"); + Io_mgr.Instance.SaveFilStr(Xol_lang_itm_.xo_lang_fil_(fxt.App().Fsys_mgr(), "pt-br") , "fallback_load('pt');"); + Xol_lang_itm lang = new Xol_lang_itm(fxt.App().Lang_mgr(), Bry_.new_a7("pt")); + lang.Init_by_load(); + } + @Test public void Num_fmt() { + String raw = String_.Concat_lines_nl + ( "numbers {" + , " separators {" + , " clear;" + , " set(',', '.');" + , " set('.', ',');" + , " }" + , "}" + ); + fxt.Invk_no_semic(raw); + fxt.Tst_num_fmt("1234,56", "1.234.56"); // NOTE: dot is repeated; confirmed with dewiki and {{formatnum:1234,56}} + fxt.Run_save_num_mgr(fxt.Lang().Num_mgr(), raw); + } + @Test public void Num_fmt_apos() { // PURPOSE:de.ch has apos which breaks gfs + fxt .Init_clear() + .Init_separators(",", "'") + .Init_separators(".", ",") + ; + fxt.Run_save_num_mgr(fxt.Lang().Num_mgr(), String_.Concat_lines_nl + ( "numbers {" + , " separators {" + , " clear;" + , " set(',', '''');" + , " set('.', ',');" + , " }" + , "}" + )); + } +} +class Xol_lang_srl_fxt { + public void Clear() { + app = Xoa_app_fxt.Make__app__edit(); + lang = new Xol_lang_itm(app.Lang_mgr(), Bry_.new_a7("fr")); + Xoa_gfs_mgr.Msg_parser_init(); // required by fallback_load + } GfsCtx ctx = GfsCtx.new_(); Xoa_gfs_bldr bldr = new Xoa_gfs_bldr(); //Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); + public Xoae_app App() {return app;} private Xoae_app app; + public Xol_lang_itm Lang() {return lang;} private Xol_lang_itm lang; + public Xow_ns ns_(int id, String s) {return new Xow_ns(id, Xow_ns_case_.Tid__1st, Bry_.new_u8(s), false);} + public Xol_specials_itm special_(String key, String... words) {return new Xol_specials_itm(Bry_.new_u8(key), Bry_.Ary(words));} + public Xol_kwd_grp kwd_(String key, boolean case_match, String... words) { + Xol_kwd_grp rv = new Xol_kwd_grp(Bry_.new_u8(key)); + rv.Srl_load(case_match, Bry_.Ary(words)); + return rv; + } + public Xol_msg_itm msg_(String key, String val) { + Xol_msg_itm rv = lang.Msg_mgr().Itm_by_key_or_new(Bry_.new_u8(key)); + rv.Atrs_set(Bry_.new_u8(val), false, false); + return rv; + } + public Xol_lang_srl_fxt Init_clear() { + lang.Num_mgr().Clear(); + return this; + } + public Xol_lang_srl_fxt Init_separators(String k, String v) { + lang.Num_mgr().Separators_mgr().Set(Bry_.new_u8(k), Bry_.new_u8(v)); + return this; + } + public void Invk(String raw) { + app.Gfs_mgr().Run_str_for(lang, raw + ";"); + } + public void Invk_no_semic(String raw) { + app.Gfs_mgr().Run_str_for(lang, raw); + } + public void Tst_ns_grp(Xol_ns_grp grp, Xow_ns... expd_ns) { + Tfds.Eq_str_lines(Xto_str(expd_ns), Xto_str(To_ary(grp))); + } + public void Run_save_ns_grp(Xol_ns_grp grp, String invk, String raw) { + Xol_lang_srl.Save_ns_grps(bldr, grp, invk); + Tfds.Eq_str_lines("." + raw, bldr.Bfr().To_str_and_clear()); + } + public void Run_save_kwd_mgr(Xol_kwd_mgr kwd_mgr, String invk, String raw) { + Xol_lang_srl.Save_keywords(bldr, kwd_mgr); + Tfds.Eq_str_lines("." + raw, bldr.Bfr().To_str_and_clear()); + } + public void Run_save_msg_mgr(Xol_msg_mgr msg_mgr, String invk, String raw) { + Xol_lang_srl.Save_messages(bldr, msg_mgr, true); + Tfds.Eq_str_lines("." + raw, bldr.Bfr().To_str_and_clear()); + } + public void Run_save_num_mgr(Xol_num_mgr num_mgr, String raw) { + Xol_lang_srl.Save_num_mgr(bldr, num_mgr); + Tfds.Eq_str_lines(raw, bldr.Bfr().To_str_and_clear()); + } + public void Run_save_specials_mgr(Xol_specials_mgr specials_mgr, String invk, String raw) { + Xol_lang_srl.Save_specials(bldr, specials_mgr); + Tfds.Eq_str_lines("." + raw, bldr.Bfr().To_str_and_clear()); + } + public void Tst_num_fmt(String raw, String expd) {Tfds.Eq(expd, String_.new_u8(lang.Num_mgr().Format_num(Bry_.new_u8(raw))));} + public void Tst_keywords(Xol_kwd_mgr kwd_mgr, Xol_kwd_grp... ary) { + Tfds.Eq_str_lines(Xto_str(ary), Xto_str(To_ary(kwd_mgr))); + } + public void Tst_messages(Xol_msg_mgr msg_mgr, Xol_msg_itm... ary) { + Tfds.Eq_str_lines(Xto_str(ary), Xto_str(To_ary(msg_mgr))); + } + public void Tst_specials(Xol_specials_mgr specials_mgr, Xol_specials_itm... expd) { + Tfds.Eq_str_lines(Xto_str(expd), Xto_str(To_ary(specials_mgr))); + } + private String Xto_str(Xol_specials_itm[] ary) { + int len = ary.length; + for (int i = 0; i < len; i++) { + Xol_specials_itm itm = ary[i]; + sb.Add(itm.Special()).Add("|"); + int aliases_len = itm.Aliases().length; + for (int j = 0; j < aliases_len; j++) { + if (j != 0) sb.Add("~"); + sb.Add(itm.Aliases()[j]).Add_char_nl(); + } + } + return sb.To_str_and_clear(); + } + private Xol_specials_itm[] To_ary(Xol_specials_mgr specials_mgr) { + int len = specials_mgr.Len(); + Xol_specials_itm[] rv = new Xol_specials_itm[len]; + for (int i = 0; i < len; i++) + rv[i] = specials_mgr.Get_at(i); + return rv; + } + String Xto_str(Xow_ns[] ary) { + int len = ary.length; + for (int i = 0; i < len; i++) { + Xow_ns ns = ary[i]; + sb.Add(ns.Id()).Add("|").Add(ns.Name_db_str()).Add_char_nl(); + } + return sb.To_str_and_clear(); + } + Xow_ns[] To_ary(Xol_ns_grp ary) { + int len = ary.Len(); + Xow_ns[] rv = new Xow_ns[len]; + for (int i = 0; i < len; i++) + rv[i] = ary.Get_at(i); + return rv; + } + Xol_kwd_grp[] To_ary(Xol_kwd_mgr kwd_mgr) { + int len = kwd_mgr.Len(); + List_adp rv = List_adp_.New(); + for (int i = 0; i < len; i++) { + Xol_kwd_grp kwd_grp = kwd_mgr.Get_at(i); + if (kwd_grp == null) continue; + rv.Add(kwd_grp); + } + return (Xol_kwd_grp[])rv.To_ary(Xol_kwd_grp.class); + } + String Xto_str(Xol_kwd_grp[] ary) { + int len = ary.length; + for (int i = 0; i < len; i++) { + Xol_kwd_grp grp = ary[i]; + sb.Add(grp.Key()).Add("|").Add(grp.Case_match() ? "1" : "0").Add("|"); + Xol_kwd_itm[] itms = grp.Itms(); + int itms_len = itms.length; + for (int j = 0; j < itms_len; j++) { + sb.Add(itms[i].Val()).Add(";"); + } + sb.Add_char_nl(); + } + return sb.To_str_and_clear(); + } + Xol_msg_itm[] To_ary(Xol_msg_mgr msg_mgr) { + int len = msg_mgr.Itms_max(); + List_adp rv = List_adp_.New(); + for (int i = 0; i < len; i++) { + Xol_msg_itm itm = msg_mgr.Itm_by_id_or_null(i); + if (itm == null || !itm.Dirty()) continue; + rv.Add(itm); + } + return (Xol_msg_itm[])rv.To_ary(Xol_msg_itm.class); + } + String Xto_str(Xol_msg_itm[] ary) { + int len = ary.length; + for (int i = 0; i < len; i++) { + Xol_msg_itm itm = ary[i]; + sb.Add(itm.Key()).Add("|").Add(itm.Val()).Add_char_nl(); + } + return sb.To_str_and_clear(); + } + private static String_bldr sb = String_bldr_.new_(); +} diff --git a/400_xowa/src/gplx/xowa/langs/plurals/Xol_plural.java b/400_xowa/src/gplx/xowa/langs/plurals/Xol_plural.java index a27517de8..e8673906f 100644 --- a/400_xowa/src/gplx/xowa/langs/plurals/Xol_plural.java +++ b/400_xowa/src/gplx/xowa/langs/plurals/Xol_plural.java @@ -13,3 +13,7 @@ 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.langs.plurals; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public interface Xol_plural { + byte[] Plural_eval(Xol_lang_itm lang, int count, byte[][] words); +} diff --git a/400_xowa/src/gplx/xowa/langs/plurals/Xol_plural_.java b/400_xowa/src/gplx/xowa/langs/plurals/Xol_plural_.java index a27517de8..482228425 100644 --- a/400_xowa/src/gplx/xowa/langs/plurals/Xol_plural_.java +++ b/400_xowa/src/gplx/xowa/langs/plurals/Xol_plural_.java @@ -13,3 +13,30 @@ 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.langs.plurals; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xol_plural_ { + public static Xol_plural new_by_lang_id(int lang_id) { + switch (lang_id) { + case Xol_lang_stub_.Id_ru: return Xol_plural_ru.Instance; + default: return Xol_plural__default.Instance; + } + } + public static byte[][] Fill_ary(byte[][] words, int words_len, int reqd_len) {// convert words to an ary of at least reqd_len where new entries are filled with last item; EX: {"a", "b"}, 3 -> {"a", "b", "b"} + byte[][] rv = new byte[reqd_len][]; + byte[] last = words[words_len - 1]; + for (int i = 0; i < reqd_len; i++) + rv[i] = i < words_len ? words[i] : last; + return rv; + } +} +class Xol_plural__default implements Xol_plural { + public byte[] Plural_eval(Xol_lang_itm lang, int count, byte[][] forms) { + int forms_len = forms.length; + switch (forms_len) { + case 0: return Bry_.Empty; // forms is empty; do nothing + case 1: return forms[0]; // only one word specified; use it; REF.MW:$pluralForm = min( $pluralForm, count( $forms ) - 1 ); + default: return count == 1 ? forms[0] : forms[1]; // TODO_OLD: incorporate plurals.xml logic + } + } + public static final Xol_plural__default Instance = new Xol_plural__default(); Xol_plural__default() {} +} diff --git a/400_xowa/src/gplx/xowa/langs/plurals/Xol_plural_ru.java b/400_xowa/src/gplx/xowa/langs/plurals/Xol_plural_ru.java index a27517de8..a38514f95 100644 --- a/400_xowa/src/gplx/xowa/langs/plurals/Xol_plural_ru.java +++ b/400_xowa/src/gplx/xowa/langs/plurals/Xol_plural_ru.java @@ -13,3 +13,26 @@ 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.langs.plurals; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xol_plural_ru implements Xol_plural { + public byte[] Plural_eval(Xol_lang_itm lang, int count, byte[][] forms) { + int forms_len = forms.length; + switch (forms_len) { + case 0: return null; // forms is empty; do nothing + case 2: return count == 1 ? forms[0] : forms[1]; + default: { // either 1, 3, or >3; + if (forms_len == 1) forms = Xol_plural_.Fill_ary(forms, forms_len, 3); + if (count > 10 && ((count % 100) / 10) == 1) + return forms[2]; + else { + switch (count % 10) { + case 1: return forms[0]; + case 2: case 3: case 4: return forms[1]; + default: return forms[2]; + } + } + } + } + } + public static final Xol_plural_ru Instance = new Xol_plural_ru(); Xol_plural_ru() {} +} diff --git a/400_xowa/src/gplx/xowa/langs/plurals/Xol_plural_ru_tst.java b/400_xowa/src/gplx/xowa/langs/plurals/Xol_plural_ru_tst.java index a27517de8..1484be5d6 100644 --- a/400_xowa/src/gplx/xowa/langs/plurals/Xol_plural_ru_tst.java +++ b/400_xowa/src/gplx/xowa/langs/plurals/Xol_plural_ru_tst.java @@ -13,3 +13,21 @@ 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.langs.plurals; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import org.junit.*; +public class Xol_plural_ru_tst { + @Test public void Plural() { + Tst(1, String_.Ary_empty, null); // 0 forms + Tst(1, String_.Ary("a", "b"), "a"); // 2 forms; singluar + Tst(2, String_.Ary("a", "b"), "b"); // 2 forms; plural + Tst(111, String_.Ary("a", "b", "c"), "c"); // 3 forms; (count % 100) / 10 == 0; should not return "a" + Tst(1, String_.Ary("a", "b", "c"), "a"); // 3 forms; count % 10 == 1 + Tst(2, String_.Ary("a", "b", "c"), "b"); // 3 forms; count % 10 == (2, 3, 4) + Tst(5, String_.Ary("a", "b", "c"), "c"); // 3 forms; count % 10 != (1, 2, 3, 4) + Tst(5, String_.Ary("a"), "a"); // 1 form; count % 10 != (1, 2, 3, 4); but only 1 element, so take 1st + } + private void Tst(int count, String[] forms, String expd) { + byte[] actl = Xol_plural_ru.Instance.Plural_eval(null, count, Bry_.Ary(forms)); + Tfds.Eq_bry(Bry_.new_a7(expd), actl); + } +} diff --git a/400_xowa/src/gplx/xowa/langs/specials/Xol_specials_itm.java b/400_xowa/src/gplx/xowa/langs/specials/Xol_specials_itm.java index a27517de8..50ce6fade 100644 --- a/400_xowa/src/gplx/xowa/langs/specials/Xol_specials_itm.java +++ b/400_xowa/src/gplx/xowa/langs/specials/Xol_specials_itm.java @@ -13,3 +13,9 @@ 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.langs.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xol_specials_itm { + public Xol_specials_itm(byte[] special, byte[][] aliases) {this.special = special; this.aliases = aliases;} + public byte[] Special() {return special;} private byte[] special; + public byte[][] Aliases() {return aliases;} private byte[][] aliases; +} diff --git a/400_xowa/src/gplx/xowa/langs/specials/Xol_specials_mgr.java b/400_xowa/src/gplx/xowa/langs/specials/Xol_specials_mgr.java index a27517de8..2c112066c 100644 --- a/400_xowa/src/gplx/xowa/langs/specials/Xol_specials_mgr.java +++ b/400_xowa/src/gplx/xowa/langs/specials/Xol_specials_mgr.java @@ -13,3 +13,36 @@ 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.langs.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.xowa.langs.parsers.*; +public class Xol_specials_mgr implements Gfo_invk { + private final Ordered_hash hash_by_special = Ordered_hash_.New_bry(), hash_by_aliases = Ordered_hash_.New_bry(); + private final Xol_lang_itm lang; + public Xol_specials_mgr(Xol_lang_itm lang) {this.lang = lang;} + public void Clear() {hash_by_special.Clear();} + public int Len() {return hash_by_special.Len();} + public Xol_specials_itm Get_at(int i) {return (Xol_specials_itm)hash_by_special.Get_at(i);} + public Xol_specials_itm Get_by_alias(byte[] alias) {return (Xol_specials_itm)hash_by_aliases.Get_by(alias);} + public Xol_specials_itm Get_by_key(byte[] special) {return (Xol_specials_itm)hash_by_special.Get_by(special);} + public void Add(byte[] special, byte[][] alias_ary) { + Xol_specials_itm itm = (Xol_specials_itm)hash_by_special.Get_by(special); + if (itm == null) { // NOTE: most items will be null, but MessagesCs.php has two entries for PasswordReset; DATE:2016-07-08 + itm = new Xol_specials_itm(special, alias_ary); + hash_by_special.Add(special, itm); + } + int len = alias_ary.length; + for (int i = 0; i < len; i++) { + byte[] alias = alias_ary[i]; + if (!hash_by_aliases.Has(alias)) + hash_by_aliases.Add(alias, itm); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_lang)) return lang; + else if (ctx.Match(k, Invk_clear)) this.Clear(); + else if (ctx.Match(k, Invk_load_text)) Xol_lang_srl.Load_specials(this, m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk_lang = "lang", Invk_clear = "clear", Invk_load_text = "load_text"; +} diff --git a/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_dir_.java b/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_dir_.java index a27517de8..b01c2f800 100644 --- a/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_dir_.java +++ b/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_dir_.java @@ -13,3 +13,13 @@ 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.langs.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +public class Xol_vnt_dir_ { + public static final int Tid__none = 0, Tid__uni = 1, Tid__bi = 2; + public static int Parse(byte[] v) {return hash.Get_as_int_or(v, Tid__none);} + private static final byte[] Bry__none = Bry_.new_a7("disable"), Bry__uni = Bry_.new_a7("unidirectional"), Bry__bi = Bry_.new_a7("bidirectional"); + private static final Hash_adp_bry hash = Hash_adp_bry.cs() + .Add_bry_int(Bry__none , Tid__none) + .Add_bry_int(Bry__uni , Tid__uni) + .Add_bry_int(Bry__bi , Tid__bi); +} diff --git a/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_itm.java b/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_itm.java index a27517de8..4dddfa967 100644 --- a/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_itm.java +++ b/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_itm.java @@ -13,3 +13,36 @@ 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.langs.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.xowa.langs.vnts.converts.*; +public class Xol_vnt_itm implements Gfo_invk { + public Xol_vnt_itm(int idx, byte[] key, byte[] name, int mask__vnt) { + this.idx = idx; this.key = key; this.name = name; this.mask__vnt = mask__vnt; + this.convert_wkr = new Xol_convert_wkr(key); + } + public int Idx() {return idx;} private final int idx; // EX: 2 + public byte[] Key() {return key;} private final byte[] key; // EX: zh-cn + public byte[] Name() {return name;} private final byte[] name; // EX: 大陆简体 + public boolean Visible() {return visible;} private boolean visible = true; // visible in menu + public int Mask__vnt() {return mask__vnt;} private final int mask__vnt; // EX: 8 + public int Mask__fallbacks() {return mask_fallbacks;} private int mask_fallbacks; // EX: 11 for zh,zh-hans,zh-cn + public int Dir() {return dir;} private int dir = Xol_vnt_dir_.Tid__bi; // EX: "bidirectional" + public byte[][] Fallback_ary() {return fallback_ary;} private byte[][] fallback_ary = Bry_.Ary_empty; // EX: zh-hans|zh + public byte[][] Convert_ary() {return convert_ary;} private byte[][] convert_ary = Bry_.Ary_empty; // EX: zh-hans|zh-cn + public Xol_convert_wkr Convert_wkr() {return convert_wkr;} private final Xol_convert_wkr convert_wkr; + public void Visible_(boolean v) {this.visible = v;} + public void Convert_ary_(byte[][] v) {convert_ary = v;} + public void Init(int dir, byte[][] fallback_ary) { + this.dir = dir; this.fallback_ary = fallback_ary; + } + public void Mask__fallbacks_(Xol_vnt_regy regy, byte[][] ary) { + this.mask_fallbacks = regy.Mask__calc(Bry_.Ary_add(Bry_.Ary(key), ary));// NOTE: must add lang.key which is not part of fallback; EX: "zh-cn" has fallback of "zh-hans", but chain should calc "zh-cn","zh-hans" + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_fallbacks_)) fallback_ary = Bry_split_.Split(m.ReadBry("v"), Byte_ascii.Pipe); + else if (ctx.Match(k, Invk_converts_)) Convert_ary_(Bry_split_.Split(m.ReadBry("v"), Byte_ascii.Pipe)); + else if (ctx.Match(k, Invk_dir_)) dir = Xol_vnt_dir_.Parse(m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_fallbacks_ = "fallbacks_", Invk_converts_ = "converts_", Invk_dir_ = "dir_"; +} diff --git a/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_mgr.java b/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_mgr.java index a27517de8..8b74d15b0 100644 --- a/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_mgr.java +++ b/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_mgr.java @@ -13,3 +13,59 @@ 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.langs.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.xowa.langs.vnts.converts.*; import gplx.xowa.parsers.vnts.*; +public class Xol_vnt_mgr implements Gfo_invk { + public Xol_vnt_mgr(Xol_lang_itm lang) { + this.lang = lang; + this.convert_lang = new Vnt_convert_lang(convert_mgr, regy); + } + public Xol_lang_itm Lang() {return lang;} private final Xol_lang_itm lang; + public Xol_convert_mgr Convert_mgr() {return convert_mgr;} private final Xol_convert_mgr convert_mgr = new Xol_convert_mgr(); + public Vnt_convert_lang Convert_lang() {return convert_lang;} private final Vnt_convert_lang convert_lang; + public Xol_vnt_regy Regy() {return regy;} private final Xol_vnt_regy regy = new Xol_vnt_regy(); // EX:zh;zh-hans;zh-hant;zh-cn;zh-hk;zh-mo;zh-sg;zh-tw + public Xol_vnt_itm Cur_itm() {return cur_itm;} private Xol_vnt_itm cur_itm; // EX:zh-cn + public boolean Enabled() {return enabled;} private boolean enabled = false; + public String Html__lnki_style() {return html__lnki_style;} private String html__lnki_style = ""; // style for showing vnt lnkis in different colors + public void Enabled_(boolean v) {this.enabled = v;} // TEST + public Xol_vnt_itm Regy__get_or_new(byte[] key) { + Xol_vnt_itm rv = regy.Get_by(key); + if (rv == null) { + byte[] name = lang.Msg_mgr().Itm_by_key_or_new(Bry_.Add(Msg_variantname, key)).Val(); + rv = regy.Add(key, name); + enabled = true; // NOTE: mark enabled if any vnts have been added + } + return rv; + } + public void Cur_itm_(byte[] v) { + if (Bry_.Len_eq_0(v)) return; // Cfg is empty by default + this.cur_itm = regy.Get_by(v); if (cur_itm == null) throw Err_.new_("lang.vnt", "vnt not found", "key", v); + } + public void Init_end() { + int len = regy.Len(); + for (int i = 0; i < len; ++i) { // calc fallback_flag; needs to be run after all items added, b/c "zh-cn" added before "zh-sg" but "zh-cn" can have "zh-sg" as fallback + Xol_vnt_itm itm = regy.Get_at(i); + Xol_lang_itm vnt_lang = lang.Lang_mgr().Get_by_or_load(itm.Key()); // load vnt's language in order to get fallback; EX: "zh-mo" to get "zh-hant|zh-hk|zh-tw" + itm.Mask__fallbacks_(regy, vnt_lang.Fallback_bry_ary()); + } + convert_mgr.Init(regy); // needs to run at end b/c converts are added one at a time + } + private void Limit_visibility(byte[][] ary) { + int regy_len = regy.Len(); + for (int i = 0; i < regy_len; ++i) + regy.Get_at(i).Visible_(Bool_.N); + int ary_len = ary.length; + for (int i = 0; i < ary_len; ++i) + regy.Get_by(ary[i]).Visible_(Bool_.Y); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_get)) return Regy__get_or_new(m.ReadBry("v")); + else if (ctx.Match(k, Invk_init_end)) Init_end(); + else if (ctx.Match(k, Invk_vnt_grp_)) Limit_visibility(m.ReadBryAry("v", Byte_ascii.Pipe)); + else if (ctx.Match(k, Invk_cur_vnt_)) Cur_itm_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_html_style_)) html__lnki_style = m.ReadStr("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_get = "get", Invk_init_end = "init_end", Invk_cur_vnt_ = "cur_vnt_", Invk_vnt_grp_ = "vnt_grp_", Invk_html_style_ = "html_style_"; + private static final byte[] Msg_variantname = Bry_.new_a7("variantname-"); +} diff --git a/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_regy.java b/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_regy.java index a27517de8..115fae067 100644 --- a/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_regy.java +++ b/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_regy.java @@ -13,3 +13,47 @@ 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.langs.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.core.btries.*; import gplx.core.bits.*; +import gplx.xowa.parsers.vnts.*; +public class Xol_vnt_regy { + private final Hash_adp_bry hash = Hash_adp_bry.ci_a7(); private int hash_len; + private final List_adp list = List_adp_.New(); + public Btrie_slim_mgr Trie() {return trie;} private final Btrie_slim_mgr trie = Btrie_slim_mgr.ci_a7(); + public int Len() {return hash.Count();} + public boolean Has(byte[] k) {return hash.Has(k);} + public Xol_vnt_itm Get_at(int i) {return (Xol_vnt_itm)list.Get_at(i);} + public Xol_vnt_itm Get_by(byte[] k) {return (Xol_vnt_itm)hash.Get_by(k);} + public Xol_vnt_itm Get_by(byte[] s, int b, int e) {return (Xol_vnt_itm)hash.Get_by_mid(s, b, e);} + public void Clear() {hash.Clear(); list.Clear(); trie.Clear(); hash_len = 0;} + public Xol_vnt_itm Add(byte[] key, byte[] name) { + int mask = gplx.core.brys.Bit_.Get_flag(hash_len); + Xol_vnt_itm itm = new Xol_vnt_itm(hash_len, key, name, mask); + hash.Add(key, itm); + list.Add(itm); + trie.Add_obj(key, itm); + hash_len = hash.Count(); + return itm; + } + public int Mask__calc(byte[]... ary) { + int rv = 0; + int len = ary.length; + for (int i = 0; i < len; ++i) { + byte[] key = ary[i]; + Xol_vnt_itm itm = (Xol_vnt_itm)hash.Get_by(key); if (itm == null) continue; // handle bad vnt from user input; EX: -{zh;bad|text}- + int itm_mask = itm.Mask__vnt(); + rv = rv == 0 ? itm_mask : Bitmask_.Flip_int(true, rv, itm_mask); + } + return rv; + } + public boolean Mask__match_any(int lhs, int rhs) { // EX: match "zh-cn|zh-hans|zh-hant" against "zh|zh-hans|zh-hant" + for (int i = 0; i < hash_len; ++i) { + int mask = gplx.core.brys.Bit_.Get_flag(i); // 1,2,4,8 + if (Bitmask_.Has_int(lhs, mask)) { // lhs has mask; EX: for lhs=6, mask=1 -> 'n'; mask=2 -> 'y' + if (Bitmask_.Has_int(rhs, mask)) // if rhs does not have mask, return false; + return true; + } + } + return false; // should only occur when len = 0; + } +} diff --git a/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_regy_fxt.java b/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_regy_fxt.java index a27517de8..3ca7b96cf 100644 --- a/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_regy_fxt.java +++ b/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_regy_fxt.java @@ -13,3 +13,48 @@ 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.langs.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import gplx.xowa.parsers.vnts.*; +public class Xol_vnt_regy_fxt { + private final Xol_vnt_regy mgr = new_chinese(); + public String[] Make_lang_chain_cn() {return String_.Ary("zh-cn", "zh-hans", "zh-hant", "zh");} + public void Test_match_any(boolean expd, String[] lang_chain, String[]... vnt_chain_ary) { + int len = vnt_chain_ary.length; + int lang_flag = mgr.Mask__calc(Bry_.Ary(lang_chain)); + for (int i = 0; i < len; ++i) { + String[] vnt_chain = vnt_chain_ary[i]; // EX: -{zh;zh-hans;zh-hant}- + int vnt_flag = mgr.Mask__calc(Bry_.Ary(vnt_chain)); + Tfds.Eq(expd, mgr.Mask__match_any(vnt_flag, lang_flag), String_.Concat_with_str(";", vnt_chain) + "<>" + String_.Concat_with_str(";", lang_chain)); + } + } + public void Test_calc(String[] ary, int expd) { + Tfds.Eq(expd, mgr.Mask__calc(Bry_.Ary(ary))); + } + public static void Init__vnt_mgr(Xol_vnt_mgr vnt_mgr, int cur_idx, String[] ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) { + Xol_vnt_itm itm = vnt_mgr.Regy__get_or_new(Bry_.new_a7(ary[i])); + vnt_mgr.Lang().Lang_mgr().Get_by_or_load(itm.Key()).Fallback_bry_(Bry_.new_a7("zh-hans,zh-hant")); + } + vnt_mgr.Init_end(); + vnt_mgr.Cur_itm_(Bry_.new_a7(ary[cur_idx])); + } + public static Xol_vnt_regy new_chinese() { // REF.MW:/languages/classes/LanguageZh.php|LanguageZh|__construct + Xol_vnt_regy rv = new Xol_vnt_regy(); + new_chinese_vnt(rv, "zh" , Xol_vnt_dir_.Tid__none, "zh-hans", "zh-hant", "zh-cn", "zh-tw", "zh-hk", "zh-sg", "zh-mo", "zh-my"); + new_chinese_vnt(rv, "zh-hans" , Xol_vnt_dir_.Tid__uni , "zh-cn", "zh-sg", "zh-my"); + new_chinese_vnt(rv, "zh-hant" , Xol_vnt_dir_.Tid__uni , "zh-tw", "zh-hk", "zh-mo"); + new_chinese_vnt(rv, "zh-cn" , Xol_vnt_dir_.Tid__bi , "zh-hans", "zh-sg", "zh-my"); + new_chinese_vnt(rv, "zh-hk" , Xol_vnt_dir_.Tid__bi , "zh-hant", "zh-mo", "zh-tw"); + new_chinese_vnt(rv, "zh-my" , Xol_vnt_dir_.Tid__bi , "zh-hans", "zh-sg", "zh-cn"); + new_chinese_vnt(rv, "zh-mo" , Xol_vnt_dir_.Tid__bi , "zh-hant", "zh-hk", "zh-tw"); + new_chinese_vnt(rv, "zh-sg" , Xol_vnt_dir_.Tid__bi , "zh-hans", "zh-cn", "zh-my"); + new_chinese_vnt(rv, "zh-tw" , Xol_vnt_dir_.Tid__bi , "zh-hant", "zh-hk", "zh-mo"); + return rv; + } + private static void new_chinese_vnt(Xol_vnt_regy regy, String key, int dir, String... fallbacks) { + byte[] key_bry = Bry_.new_u8(key); + Xol_vnt_itm itm = regy.Add(key_bry, Bry_.Ucase__all(key_bry)); + itm.Init(dir, Bry_.Ary(fallbacks)); + } +} diff --git a/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_regy_tst.java b/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_regy_tst.java index a27517de8..814ba5487 100644 --- a/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_regy_tst.java +++ b/400_xowa/src/gplx/xowa/langs/vnts/Xol_vnt_regy_tst.java @@ -13,3 +13,35 @@ 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.langs.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; +import org.junit.*; +public class Xol_vnt_regy_tst { + private final Xol_vnt_regy_fxt fxt = new Xol_vnt_regy_fxt(); + @Test public void Calc() { + fxt.Test_calc(String_.Ary("zh") , 1); + fxt.Test_calc(String_.Ary("zh", "zh-hans") , 3); + fxt.Test_calc(String_.Ary("zh", "bad") , 1); + } + @Test public void Match() { + String[] lang_chain = fxt.Make_lang_chain_cn(); // zh;zh-hans;zh-hant;zh-cn + fxt.Test_match_any(Bool_.Y, lang_chain + , String_.Ary("zh") + , String_.Ary("zh-hans") + , String_.Ary("zh-hant") + , String_.Ary("zh-cn") + , String_.Ary("zh", "zh-hans") + , String_.Ary("zh-cn", "zh-hk") + ); + fxt.Test_match_any(Bool_.N, lang_chain + , String_.Ary_empty + , String_.Ary("bad") + , String_.Ary("zh-hk") + , String_.Ary("zh-hk", "zh-sg") + ); + } + @Test public void Match_2() { + fxt.Test_match_any(Bool_.Y, String_.Ary("zh-hans") + , String_.Ary("zh", "zh-hant", "zh-hans") + ); + } +} diff --git a/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_grp.java b/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_grp.java index a27517de8..caa9c0785 100644 --- a/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_grp.java +++ b/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_grp.java @@ -13,3 +13,48 @@ 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.langs.vnts.converts; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; import gplx.xowa.langs.vnts.*; +import gplx.xowa.langs.parsers.*; +public class Xol_convert_grp implements Gfo_invk {// group of convert_itm by vnt; EX: zh-hant {A -> A1; B -> B1} + private final Ordered_hash hash = Ordered_hash_.New_bry(); + public Xol_convert_grp(byte[] key) {this.key = key;} + public byte[] Key() {return key;} private final byte[] key; + public int Len() {return hash.Count();} + public Xol_convert_itm Get_at(int i) {return (Xol_convert_itm)hash.Get_at(i);} + public void Add(byte[] src, byte[] trg) {hash.Add_if_dupe_use_nth(src, new Xol_convert_itm(src, trg));} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_add_bulk)) Add_bulk(this, m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_add_bulk = "add_bulk"; + private static void Add_bulk(Xol_convert_grp grp, byte[] raw) { + int len = raw.length; + int pos = 0, fld_bgn = 0, fld_idx = 0; + byte[] src = Bry_.Empty, trg = Bry_.Empty; + Xol_csv_parser csv_parser = Xol_csv_parser.Instance; + while (true) { + boolean last = pos == len; + byte b = last ? Byte_ascii.Nl : raw[pos]; + switch (b) { + case Byte_ascii.Pipe: + switch (fld_idx) { + case 0: src = csv_parser.Load(raw, fld_bgn, pos); break; + default: throw Err_.new_unhandled(fld_idx); + } + fld_bgn = pos + 1; + ++fld_idx; + break; + case Byte_ascii.Nl: + if (fld_bgn < pos) { // guard against trailing new lines + trg = csv_parser.Load(raw, fld_bgn, pos); + grp.Add(src, trg); + } + fld_bgn = pos + 1; + fld_idx = 0; + break; + } + if (last) break; + ++pos; + } + } +} diff --git a/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_itm.java b/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_itm.java index a27517de8..e452eed7e 100644 --- a/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_itm.java +++ b/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_itm.java @@ -13,3 +13,9 @@ 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.langs.vnts.converts; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; import gplx.xowa.langs.vnts.*; +public class Xol_convert_itm { + public Xol_convert_itm(byte[] src, byte[] trg) {this.src = src; this.trg = trg;} // convert from src to trg; EX: A -> A1 + public byte[] Src() {return src;} private final byte[] src; + public byte[] Trg() {return trg;} private final byte[] trg; +} diff --git a/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_mgr.java b/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_mgr.java index a27517de8..fbbce7c78 100644 --- a/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_mgr.java +++ b/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_mgr.java @@ -13,3 +13,57 @@ 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.langs.vnts.converts; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; import gplx.xowa.langs.vnts.*; +import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.data.tbls.*; +public class Xol_convert_mgr { + private final Ordered_hash tmp_page_list = Ordered_hash_.New_bry(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + public Xol_convert_regy Converter_regy() {return converter_regy;} private final Xol_convert_regy converter_regy = new Xol_convert_regy(); + public Xol_convert_wkr[] Converter_ary() {return wkr_ary;} private Xol_convert_wkr[] wkr_ary; private int wkr_ary_len; + public void Init(Xol_vnt_regy regy) { + int len = regy.Len(); + this.wkr_ary_len = len; + this.wkr_ary = new Xol_convert_wkr[len]; + for (int i = 0; i < len; i++) { + Xol_vnt_itm itm = regy.Get_at(i); + itm.Convert_wkr().Init(converter_regy, itm.Convert_ary()); + wkr_ary[i] = itm.Convert_wkr(); + } + } + public byte[] Convert_text(int vnt_idx, byte[] src, int bgn, int end) { + Xol_convert_wkr converter = wkr_ary[vnt_idx]; + converter.Convert_text(tmp_bfr, src, bgn, end); + return tmp_bfr.To_bry_and_clear(); + } + public Xowd_page_itm Convert_ttl(Xow_wiki wiki, Xoa_ttl ttl) {return Convert_ttl(wiki, ttl.Ns(), ttl.Page_db());} // NOTE: not Full_db as ttl.Ns is passed; EX:Шаблон:Šablon:Jez-eng; PAGE:sr.w:ДНК DATE:2014-07-06 + public Xowd_page_itm Convert_ttl(Xow_wiki wiki, Xow_ns ns, byte[] ttl_bry) {return Convert_ttl(wiki, tmp_bfr, ns, ttl_bry);} + private Xowd_page_itm Convert_ttl(Xow_wiki wiki, Bry_bfr tmp_bfr, Xow_ns ns, byte[] ttl_bry) { // REF.MW:LanguageConverter.php|findVariantLink + synchronized (tmp_page_list) { // THREAD: + int converted = Convert_ttl__convert_each_vnt(wiki, tmp_bfr, ns, ttl_bry); // convert ttl for each vnt + if (converted == 0) return Xowd_page_itm.Null; // ttl_bry has no conversions; exit; + wiki.Data__core_mgr().Tbl__page().Select_in__ns_ttl(Cancelable_.Never, tmp_page_list, wiki.Ns_mgr(), true, 0, converted); // TODO_OLD: use this call; when defaulting test to use db_mgr, not txt_mgr + for (int i = 0; i < converted; i++) { + Xowd_page_itm page = (Xowd_page_itm)tmp_page_list.Get_at(i); + if (page.Exists()) return page; // return 1st found page + } + return Xowd_page_itm.Null; + } + } + private int Convert_ttl__convert_each_vnt(Xow_wiki wiki, Bry_bfr tmp_bfr, Xow_ns ns, byte[] ttl_bry) { + tmp_page_list.Clear(); + int rv = 0; + for (int i = 0; i < wkr_ary_len; i++) { // convert ttl for each variant + Xol_convert_wkr converter = wkr_ary[i]; + tmp_bfr.Clear(); + if (!converter.Convert_text(tmp_bfr, ttl_bry)) continue; // ttl is not converted for variant; ignore + Xoa_ttl ttl = wiki.Ttl_parse(ns.Id(), tmp_bfr.To_bry_and_clear()); // NOTE: must convert to ttl in order to upper 1st letter; EX:{{jez-eng|sense}} -> Jez-eng; PAGE:sr.w:ДНК DATE:2014-07-06 + if (ttl == null) continue; + Xowd_page_itm page = new Xowd_page_itm(); + page.Ttl_(ns, ttl.Page_db()); + byte[] converted_ttl = page.Ttl_full_db(); if (tmp_page_list.Has(converted_ttl)) continue; + tmp_page_list.Add(converted_ttl, page); + ++rv; + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_regy.java b/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_regy.java index a27517de8..118e9d4aa 100644 --- a/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_regy.java +++ b/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_regy.java @@ -13,3 +13,23 @@ 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.langs.vnts.converts; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; import gplx.xowa.langs.vnts.*; +import gplx.xowa.apps.fsys.*; +public class Xol_convert_regy implements Gfo_invk { // registry of convert_grp; EX: zh-hans;zh-hant; + private final Ordered_hash hash = Ordered_hash_.New_bry(); + public Xol_convert_grp Get_or_null(byte[] key) {return (Xol_convert_grp)hash.Get_by(key);} + public Xol_convert_grp Get_or_make(byte[] key) { + Xol_convert_grp rv = (Xol_convert_grp)hash.Get_by(key); + if (rv == null) { + rv = new Xol_convert_grp(key); + hash.Add(key, rv); + } + return rv; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_get)) return Get_or_make(m.ReadBry("v")); + else return Gfo_invk_.Rv_unhandled; + } private static final String Invk_get = "get"; + public static Io_url Bld_url(Xoa_fsys_mgr app_fsys_mgr, String lang) {return Bld_url(app_fsys_mgr.Cfg_lang_core_dir(), lang);} + public static Io_url Bld_url(Io_url dir, String lang) {return dir.GenSubFil_nest("variants", lang + ".gfs");} +} diff --git a/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_regy_tst.java b/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_regy_tst.java index a27517de8..872370753 100644 --- a/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_regy_tst.java +++ b/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_regy_tst.java @@ -13,3 +13,79 @@ 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.langs.vnts.converts; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; import gplx.xowa.langs.vnts.*; +import org.junit.*; +import gplx.xowa.langs.vnts.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.drds.*; +public class Xol_convert_regy_tst { + private final Xol_convert_regy_fxt fxt = new Xol_convert_regy_fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Basic() { + // fxt.Parser_fxt().Init_page_create("Template:Test_x1", "val"); + fxt.Init_page("Template:Test_x1", "val"); + fxt.Parser_fxt().Test_parse_tmpl_str_test("{{Test_x0}}", "{{test}}", "val"); + } + @Test public void Upper_1st() { // PURPOSE: convert should call Xoa_ttl.Parse(), which will upper 1st letter; EX:{{jez-eng|sense}} -> Jez-eng; PAGE:sr.w:ДНК DATE:2014-07-06 + fxt.Init_page("Template:X1", "val"); + fxt.Parser_fxt().Test_parse_tmpl_str_test("{{x0}}", "{{test}}", "val"); + } + @Test public void Redlink() { // PURPOSE: check redlink's Convert_ttl(Xowe_wiki wiki, Xoa_ttl ttl); DATE:2014-07-06 + fxt.Init_page("Template:Test_x1", "val"); + fxt.Test_convert_by_ttl("zh", "Template:Test_x0", Bool_.Y); // Template:Test_xo should not be parsed to Template:Template:Test_x0; EX:Шаблон:Šablon:Jez-eng; PAGE:sr.w:ДНК DATE:2014-07-06 + fxt.Test_convert_by_ttl("zh", "Template:Test_x1", Bool_.N); // note that convert of trg should not find title; + fxt.Test_convert_by_ttl("zh", "Template:Test_x2", Bool_.N); // test that non-convert characters return false + } + @Test public void Pfunc() { + fxt.Parser_fxt().Init_defn_clear(); + fxt.Init_page("Test_x1", ""); + fxt.Test_parse("{{#ifexist:Test_x0|y|n}}", "y"); + } +} +class Xol_convert_regy_fxt { + public Xoae_app App() {return app;} private Xoae_app app; + public Xowe_wiki Wiki() {return wiki;} private Xowe_wiki wiki; + public Xop_fxt Parser_fxt() {return parser_fxt;} private Xop_fxt parser_fxt; + public Xowd_data_tstr Data_mgr() {return data_mgr;} private final Xowd_data_tstr data_mgr = new Xowd_data_tstr(); + public void Clear() { + app = Xoa_app_fxt.Make__app__edit(); + Xol_lang_itm lang = app.Lang_mgr().Get_by_or_new(Bry_.new_a7("zh")); + Xol_lang_itm_.Lang_init(lang); + Init_cnv(app, "zh", "zh-hant", Keyval_.new_("x0", "x1")); + wiki = Xoa_app_fxt.Make__wiki__edit(app, "zh.wikipedia.org", lang); + Xoa_test_.Init__db__edit(wiki); + data_mgr.Wiki_(wiki); + gplx.xowa.langs.vnts.Xol_vnt_regy_fxt.Init__vnt_mgr(wiki.Lang().Vnt_mgr(), 1, String_.Ary("zh", "zh-hans", "zh-hant")); + parser_fxt = new Xop_fxt(app, wiki); + } + public void Init_page(String ttl, String wtxt) {Xow_data_fxt.Create(wiki, data_mgr, ttl, wtxt);} + public static void Init_cnv(Xoae_app app, String lang_key, String vnt_key, Keyval... ary) { + Xol_lang_itm lang = app.Lang_mgr().Get_by_or_new(Bry_.new_a7(lang_key)); + Xol_convert_grp grp = lang.Vnt_mgr().Convert_mgr().Converter_regy().Get_or_make(Bry_.new_a7(vnt_key)); + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) { + Keyval itm = ary[i]; + grp.Add(Bry_.new_u8(itm.Key()), Bry_.new_u8(itm.Val_to_str_or_empty())); + } + Xol_vnt_itm vnt_itm = lang.Vnt_mgr().Regy__get_or_new(Bry_.new_a7(vnt_key)); + vnt_itm.Convert_ary_(Bry_.Ary(vnt_key)); + vnt_itm.Convert_wkr().Init(lang.Vnt_mgr().Convert_mgr().Converter_regy(), vnt_itm.Convert_ary()); + } + public void Test_parse(String raw, String expd) { + parser_fxt.Test_parse_page_all_str(raw, expd); + } + public void Test_convert_by_ttl(String lang_key, String raw, boolean expd) { + Xol_lang_itm lang = app.Lang_mgr().Get_by_or_new(Bry_.new_a7(lang_key)); + Xoa_ttl ttl = wiki.Ttl_parse(Bry_.new_u8(raw)); + Xowd_page_itm page = lang.Vnt_mgr().Convert_mgr().Convert_ttl(wiki, ttl); + if (expd) + Tfds.Eq_true(page.Exists()); + else + Tfds.Eq_null(page); + } +} +class Xow_data_fxt { + public static void Create(Xow_wiki wiki, Xowd_data_tstr tstr, String ttl_str, String wtxt) { + tstr.Page__insert(1, ttl_str, "2015-10-19 00:01:02"); + tstr.Text__insert(1, wtxt); + } +} diff --git a/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_wkr.java b/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_wkr.java index a27517de8..fa27d593e 100644 --- a/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_wkr.java +++ b/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_convert_wkr.java @@ -13,3 +13,57 @@ 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.langs.vnts.converts; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; import gplx.xowa.langs.vnts.*; +import gplx.core.btries.*; import gplx.core.intls.*; +public class Xol_convert_wkr { + private final Btrie_slim_mgr trie = Btrie_slim_mgr.cs(); private final Btrie_rv trv = new Btrie_rv(); + public Xol_convert_wkr(byte[] key) {this.key = key;} + public byte[] Key() {return key;} private final byte[] key; + public void Add(byte[] src, byte[] trg) {trie.Add_obj(src, trg);} // called by -{H}- + public void Del(byte[] src) {trie.Del(src);} // called by -{-}- + public boolean Convert_text(Bry_bfr bfr, byte[] src) {return Convert_text(bfr, src, 0, src.length);} + public boolean Convert_text(Bry_bfr bfr, byte[] src, int bgn, int end) { + int pos = bgn; + boolean matched = false; + while (pos < end) { + byte b = src[pos]; + Object o = trie.Match_at_w_b0(trv, b, src, pos, end); + if (o == null) { // no match; skip to next char + int char_len = Utf8_.Len_of_char_by_1st_byte(b); // NOTE: must increment by char_len, not +1 + if (matched) { + if (char_len == 1) + bfr.Add_byte(b); + else + bfr.Add_mid(src, pos, pos + char_len); + } + pos += char_len; + } + else { + if (!matched) { + bfr.Add_mid(src, bgn, pos); // add everything up to pos + matched = true; + } + bfr.Add((byte[])o); + pos = trv.Pos(); + } + } + if (!matched) bfr.Add_mid(src, bgn, end); // no convert; make sure to add back src, else bfr will be blank + return matched; + } + public void Init(Xol_convert_regy regy, byte[][] vnt_ary) { // EX: "zh-cn" should add all converts from "zh-hans" "zh-cn" to its wkr + trie.Clear(); + int len = vnt_ary.length; + for (int i = 0; i < len; ++i) { + byte[] key = vnt_ary[i]; + Xol_convert_grp grp = regy.Get_or_null(key); if (grp == null) continue; // vnt may not have convert mapping; EX: zh-my + Init_grp(grp); + } + } + private void Init_grp(Xol_convert_grp grp) { + int len = grp.Len(); + for (int i = 0; i < len; ++i) { + Xol_convert_itm itm = grp.Get_at(i); + trie.Add_obj(itm.Src(), itm.Trg()); // NOTE: for dupes, latest value wins + } + } +} diff --git a/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_mw_parse_tst.java b/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_mw_parse_tst.java index a27517de8..a779ddd7b 100644 --- a/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_mw_parse_tst.java +++ b/400_xowa/src/gplx/xowa/langs/vnts/converts/Xol_mw_parse_tst.java @@ -13,3 +13,129 @@ 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.langs.vnts.converts; import gplx.*; import gplx.xowa.*; import gplx.xowa.langs.*; import gplx.xowa.langs.vnts.*; +import org.junit.*; import gplx.core.log_msgs.*; import gplx.langs.phps.*; +public class Xol_mw_parse_tst { + private final Xol_mw_parse_fxt fxt = new Xol_mw_parse_fxt(); +// @Test public void Basic() { +// fxt.Test_convert("$zh2Hant = array('a' => 'A', 'b' => 'B',);", String_.Concat_lines_nl +// ( "// zh_zh-hant" +// , "app.langs.get('zh').converts.get('zh-hant').add_bulk(" +// , "<:['" +// , "a|A" +// , "b|B" +// , "']:>" +// , ");" +// )); +// } +// @Test public void Run() { +// Io_url src_dir = Io_url_.new_dir_("C:\\xowa\\bin\\any\\xowa\\cfg\\lang\\mediawiki\\converts\\"); +// Io_url trg_dir = Io_url_.new_dir_("C:\\xowa\\bin\\any\\xowa\\cfg\\lang\\"); +// fxt.Test_run(src_dir, trg_dir); +// } + @Test public void Ignore() { + fxt.toString(); + } +} +class Xol_mw_parse_grp { + public byte[] Lng() {return lng;} public Xol_mw_parse_grp Lng_(byte[] v) {lng = v; return this;} private byte[] lng; + public byte[] Vnt() {return vnt;} public Xol_mw_parse_grp Vnt_(byte[] v) {vnt = v; return this;} private byte[] vnt; + public Xol_mw_parse_itm[] Itms() {return itms;} public Xol_mw_parse_grp Itms_(Xol_mw_parse_itm[] v) {itms = v; return this;} private Xol_mw_parse_itm[] itms; + public void Write_as_gfs(Bry_bfr bfr) { + int itms_len = itms.length; + Write_bgn(bfr); + for (int i = 0; i < itms_len; i++) { + Xol_mw_parse_itm itm = (Xol_mw_parse_itm)itms[i]; + Write_itm(bfr, itm); + } + Write_end(bfr); + } + private void Write_bgn(Bry_bfr bfr) { + bfr.Add_str_a7("// ").Add(lng).Add_str_a7("_").Add(vnt).Add_byte_nl(); + bfr.Add_str_a7("app.langs.get('"); + bfr.Add(lng); + bfr.Add_str_a7("').converts.get('"); + bfr.Add(vnt); + bfr.Add_str_a7("').add_bulk("); + bfr.Add_byte_nl().Add_str_a7("<:['").Add_byte_nl(); + } + private void Write_itm(Bry_bfr bfr, Xol_mw_parse_itm itm) { + bfr.Add(itm.Src()); + bfr.Add_byte_pipe(); + bfr.Add(itm.Trg()); + bfr.Add_byte_nl(); + } + private void Write_end(Bry_bfr bfr) { + bfr.Add_str_a7("']:>").Add_byte_nl(); + bfr.Add_str_a7(");").Add_byte_nl(); + } +} +class Xol_mw_parse_itm { + public Xol_mw_parse_itm(byte[] src, byte[] trg) {this.src = src; this.trg = trg;} + public byte[] Src() {return src;} private byte[] src; + public byte[] Trg() {return trg;} private byte[] trg; +} +class Xol_mw_parse_fxt { + public void Test_convert(String mw, String expd) { + Xol_mw_parse_grp[] actl_ary = Parse(Bry_.new_u8(mw)); + Bry_bfr bfr = Bry_bfr_.New(); + actl_ary[0].Write_as_gfs(bfr); + Tfds.Eq_str_lines(expd, bfr.To_str()); + } + public void Test_run(Io_url src_dir, Io_url trg_dir) { + Bry_bfr bfr = Bry_bfr_.New(); + Io_url[] fils = Io_mgr.Instance.QueryDir_fils(src_dir); + int fils_len = fils.length; + for (int i = 0; i < fils_len; i++) { + Io_url fil = fils[i]; + byte[] src = Io_mgr.Instance.LoadFilBry(fil); + Xol_mw_parse_grp[] itms = Parse(src); + int itms_len = itms.length; + String lang_name = String_.Lower(String_.Mid(fil.NameOnly(), 0, 2)); // ZhConversion.php -> Zh + for (int j = 0; j < itms_len; j++) { + Xol_mw_parse_grp itm = itms[j]; + itm.Write_as_gfs(bfr); + } + Io_url trg_fil = Xol_convert_regy.Bld_url(trg_dir, lang_name); + Io_mgr.Instance.SaveFilBry(trg_fil, bfr.To_bry_and_clear()); + } + } + public Xol_mw_parse_grp[] Parse(byte[] src) { + List_adp list = List_adp_.New(); + Php_parser parser = new Php_parser(); + Gfo_msg_log msg_log = new Gfo_msg_log("xowa"); + Php_evaluator evaluator = new Php_evaluator(msg_log); + parser.Parse_tkns(src, evaluator); + Php_line[] lines = (Php_line[])evaluator.List().To_ary(Php_line.class); + int lines_len = lines.length; + for (int i = 0; i < lines_len; i++) { + Php_line_assign line = (Php_line_assign)lines[i]; + Xol_mw_parse_grp grp = Parse_grp(line); + list.Add(grp); + } + return (Xol_mw_parse_grp[])list.To_ary(Xol_mw_parse_grp.class); + } + private List_adp tmp_itm_list = List_adp_.New(); + private Xol_mw_parse_grp Parse_grp(Php_line_assign line) { + Xol_mw_parse_grp grp = new Xol_mw_parse_grp(); + byte[] key = line.Key().Val_obj_bry(); // EX: "zh2Hant" + key = Bry_.Lcase__all(key); // EX: "zh2hant" + byte[][] parts = Bry_split_.Split(key, Byte_ascii.Num_2); // EX: "zh", "hant" + byte[] src = parts[0]; + byte[] trg = Bry_.Add(parts[0], new byte[] {Byte_ascii.Dash}, parts[1]); + grp.Lng_(src).Vnt_(trg); + Parse_itms(line, grp); + return grp; + } + private void Parse_itms(Php_line_assign line, Xol_mw_parse_grp grp) { + Php_itm_ary ary = (Php_itm_ary)line.Val(); + tmp_itm_list.Clear(); + int subs_len = ary.Subs_len(); + for (int i = 0; i < subs_len; i++) { + Php_itm_kv kv = (Php_itm_kv)ary.Subs_get(i); + Xol_mw_parse_itm itm = new Xol_mw_parse_itm(kv.Key().Val_obj_bry(), kv.Val().Val_obj_bry()); + tmp_itm_list.Add(itm); + } + grp.Itms_((Xol_mw_parse_itm[])tmp_itm_list.To_ary(Xol_mw_parse_itm.class)); + } +} diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/headingsOld/Xomw_heading_cbk.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/headingsOld/Xomw_heading_cbk.java index a27517de8..09208bee7 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/headingsOld/Xomw_heading_cbk.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/headingsOld/Xomw_heading_cbk.java @@ -13,3 +13,8 @@ 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.parsers.headingsOld; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.parsers.*; +public interface Xomw_heading_cbk { + void On_hdr_seen(Xomw_heading_wkr wkr); + void On_src_done(Xomw_heading_wkr wkr); +} diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/headingsOld/Xomw_heading_wkr.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/headingsOld/Xomw_heading_wkr.java index a27517de8..fcfab2f3f 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/headingsOld/Xomw_heading_wkr.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/headingsOld/Xomw_heading_wkr.java @@ -13,3 +13,84 @@ 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.parsers.headingsOld; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +public class Xomw_heading_wkr { + private Xomw_heading_cbk cbk; + public byte[] Src() {return src;} private byte[] src; + public int Src_end() {return src_end;} private int src_end; + public int Txt_bgn() {return txt_bgn;} private int txt_bgn; + public int Hdr_bgn() {return hdr_bgn;} private int hdr_bgn; + public int Hdr_end() {return hdr_end;} private int hdr_end; + public int Hdr_num() {return hdr_num;} private int hdr_num; + public int Hdr_lhs_bgn() {return hdr_lhs_bgn;} private int hdr_lhs_bgn; + public int Hdr_lhs_end() {return hdr_lhs_end;} private int hdr_lhs_end; + public int Hdr_rhs_bgn() {return hdr_rhs_bgn;} private int hdr_rhs_bgn; + public int Hdr_rhs_end() {return hdr_rhs_end;} private int hdr_rhs_end; + public void Parse(byte[] src, int src_bgn, int src_end, Xomw_heading_cbk cbk) { // REF.MW: /includes/parser/Parser.php|doHeadings + // init members + this.src = src; + this.src_end = src_end; + this.cbk = cbk; + + // PORTED: + // for ( $i = 6; $i >= 1; --$i ) { + // $h = str_repeat( '=', $i ); + // $text = preg_replace( "/^$h(.+)$h\\s*$/m", "\\1", $text ); + // } + + // do loop + int pos = src_bgn; + this.txt_bgn = pos == -1 ? 0 : pos; + byte b = Byte_ascii.Nl; + while (true) { + int nxt = pos + 1; + // check if (a) cur is \n; (b) nxt is '=' + if ( b == Byte_ascii.Nl + && nxt < src_end + && src[nxt] == Byte_ascii.Eq + ) { + pos = Parse_hdr_nl(txt_bgn, pos, nxt + 1); + this.txt_bgn = pos; + } + else + ++pos; + + // EOS; add all text after last "==\n" + if (pos == src_end) { + cbk.On_src_done(this); + break; + } + b = src[pos]; + } + } + private int Parse_hdr_nl(int txt_bgn, int nl_lhs, int pos) { + // calc lhs vars + this.hdr_bgn = nl_lhs; + this.hdr_lhs_bgn = nl_lhs == 0 ? 0 : nl_lhs + 1; // set pos of 1st "="; note that "==" can be at BOS; + this.hdr_lhs_end = Bry_find_.Find_fwd_while(src, pos, src_end, Byte_ascii.Eq); + + // calc rhs vars + int nl_rhs = Bry_find_.Find_fwd_or(src, Byte_ascii.Nl, hdr_lhs_end + 1, src_end, src_end); // if no "\n", src_end is rest of text; EX: "\n==EOS + this.hdr_end = nl_rhs; + this.hdr_rhs_end = Bry_find_.Find_bwd__skip_ws(src, nl_rhs, hdr_lhs_end); + this.hdr_rhs_bgn = Bry_find_.Find_bwd__skip(src, hdr_rhs_end - 1, hdr_lhs_end, Byte_ascii.Eq); + + int hdr_lhs_len = hdr_lhs_end - hdr_lhs_bgn; + int hdr_rhs_len = hdr_rhs_end - hdr_rhs_bgn; + + // handle rare situations like "\n====\n" + if (hdr_rhs_len == 0) { + int hdr_lhs_len_half = hdr_lhs_len / 2; + hdr_rhs_len = hdr_lhs_len - hdr_lhs_len_half; + hdr_lhs_len = hdr_lhs_len_half; + this.hdr_lhs_end = hdr_lhs_bgn + hdr_lhs_len; + this.hdr_rhs_bgn = hdr_lhs_end; + } + + this.hdr_num = hdr_lhs_len < hdr_rhs_len ? hdr_lhs_len : hdr_rhs_len; + + cbk.On_hdr_seen(this); + return nl_rhs; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xoa_parser_mgr.java b/400_xowa/src/gplx/xowa/parsers/Xoa_parser_mgr.java index a27517de8..56eb0ac48 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xoa_parser_mgr.java +++ b/400_xowa/src/gplx/xowa/parsers/Xoa_parser_mgr.java @@ -13,3 +13,36 @@ 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; import gplx.*; import gplx.xowa.*; +import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.htmls.*; import gplx.xowa.parsers.uniqs.*; +public class Xoa_parser_mgr { + private final Mwh_doc_wkr__atr_bldr atr_bldr = new Mwh_doc_wkr__atr_bldr(); + public Xop_tkn_mkr Tkn_mkr() {return tkn_mkr;} private final Xop_tkn_mkr tkn_mkr = new Xop_tkn_mkr(); + public Xop_uniq_mgr Core__uniq_mgr() {return core__uniq_mgr;} private final Xop_uniq_mgr core__uniq_mgr = new Xop_uniq_mgr(); + public Mwh_atr_parser Xnde__atr_parser() {return atr_parser;} private final Mwh_atr_parser atr_parser = new Mwh_atr_parser(); + public Mwh_atr_itm[] Xnde__parse_atrs(byte[] src, int src_bgn, int src_end) { + synchronized (atr_bldr) {// LOCK:app-level; DATE:2016-07-06 + //if (src_bgn < src_end) { // CHART + // src = Bry_.Mid(src, src_bgn, src_end); + // src = gplx.xowa.parsers.xndes.Xop_xnde_tkn.uniq_mgr.Parse(src); + // src_bgn = 0; + // src_end = src.length; + //} + atr_parser.Parse(atr_bldr, -1, -1, src, src_bgn, src_end); + return atr_bldr.To_atr_ary(); + } + } + public Mwh_atr_itm[] Xnde__parse_atrs_for_tblw(byte[] src, int src_bgn, int src_end) { + synchronized (atr_bldr) { // LOCK:app-level; DATE:2016-07-06 + //int angle_bgn_pos = Bry_find_.Find_fwd(src, Byte_ascii.Angle_bgn, src_bgn, src_end); + //if (angle_bgn_pos != Bry_find_.Not_found) { + // src = Bry_.Mid(src, src_bgn, src_end); + // src = Bry_.Replace(src, Byte_ascii.Angle_bgn_bry, gplx.langs.htmls.Gfh_entity_.Lt_bry); + // src_bgn = 0; + // src_end = src.length; + //} + atr_parser.Parse(atr_bldr, -1, -1, src, src_bgn, src_end); + return atr_bldr.To_atr_ary(); + } + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_ctx.java b/400_xowa/src/gplx/xowa/parsers/Xop_ctx.java index a27517de8..5b5a133f0 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_ctx.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_ctx.java @@ -13,3 +13,330 @@ 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; import gplx.*; import gplx.xowa.*; +import gplx.core.btries.*; import gplx.core.log_msgs.*; +import gplx.xowa.langs.*; +import gplx.xowa.guis.*; +import gplx.xowa.xtns.scribunto.*; import gplx.xowa.xtns.wbases.*; import gplx.xowa.xtns.lst.*; +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.logs.*; import gplx.xowa.htmls.modules.popups.keeplists.*; +public class Xop_ctx { + private final Xop_ctx_wkr[] wkrs; + Xop_ctx(Xowe_wiki wiki, Xoae_page page) { + this.wiki = wiki; this.cur_page = page; + this.app = wiki.Appe(); this.msg_log = app.Msg_log(); this.tkn_mkr = app.Parser_mgr().Tkn_mkr(); + this.lang = wiki.Lang(); + this.wkrs = new Xop_ctx_wkr[] {para, apos, xnde, list, lnki, hdr, amp, lnke, tblw, invk}; + for (Xop_ctx_wkr wkr : wkrs) + wkr.Ctor_ctx(this); + this.xnde_tag_regy = wiki.Mw_parser_mgr().Xnde_tag_regy(); + } + // public boolean Scribunto; // CHART + public Xowe_wiki Wiki() {return wiki;} private final Xowe_wiki wiki; + public Xoae_page Page() {return cur_page;} public void Page_(Xoae_page v) {cur_page = v;} private Xoae_page cur_page; + public Xol_lang_itm Lang() {return lang;} private final Xol_lang_itm lang; + public Xoae_app App() {return app;} private final Xoae_app app; + public Xop_tkn_mkr Tkn_mkr() {return tkn_mkr;} private final Xop_tkn_mkr tkn_mkr; + public Gfo_msg_log Msg_log() {return msg_log;} private final Gfo_msg_log msg_log; + public Xop_amp_wkr Amp() {return amp;} private final Xop_amp_wkr amp = new Xop_amp_wkr(); + public Xop_apos_wkr Apos() {return apos;} private final Xop_apos_wkr apos = new Xop_apos_wkr(); + public Xop_lnke_wkr Lnke() {return lnke;} private final Xop_lnke_wkr lnke = new Xop_lnke_wkr(); + public Xop_lnki_wkr Lnki() {return lnki;} private final Xop_lnki_wkr lnki = new Xop_lnki_wkr(); + public Xop_hdr_wkr Hdr() {return hdr;} private final Xop_hdr_wkr hdr = new Xop_hdr_wkr(); + public Xop_para_wkr Para() {return para;} private final Xop_para_wkr para = new Xop_para_wkr(); + public Xop_list_wkr List() {return list;} private final Xop_list_wkr list = new Xop_list_wkr(); + public Xop_tblw_wkr Tblw() {return tblw;} private final Xop_tblw_wkr tblw = new Xop_tblw_wkr(); + public Xop_xnde_wkr Xnde() {return xnde;} private final Xop_xnde_wkr xnde = new Xop_xnde_wkr(); + public Xot_invk_wkr Invk() {return invk;} private final Xot_invk_wkr invk = new Xot_invk_wkr(); + public Xop_curly_wkr Curly() {return curly;} private final Xop_curly_wkr curly = new Xop_curly_wkr(); + public Xop_xnde_tag_regy Xnde_tag_regy() {return xnde_tag_regy;} private final Xop_xnde_tag_regy xnde_tag_regy; // PERF:demeter + public Xop_tmp_mgr Tmp_mgr() {return tmp_mgr;} private final Xop_tmp_mgr tmp_mgr = new Xop_tmp_mgr(); + public Xop_ctx_page_data Page_data() {return page_data;} private final Xop_ctx_page_data page_data = new Xop_ctx_page_data(); + + public byte Xnde_names_tid() {return xnde_names_tid;} public Xop_ctx Xnde_names_tid_(byte v) {xnde_names_tid = v; return this;} private byte xnde_names_tid = Xop_parser_tid_.Tid__null; + public byte Parse_tid() {return parse_tid;} public Xop_ctx Parse_tid_(byte v) {parse_tid = v; xnde_names_tid = v; return this;} private byte parse_tid = Xop_parser_tid_.Tid__null; + public boolean Tid_is_popup() {return tid_is_popup;} public void Tid_is_popup_(boolean v) {tid_is_popup = v;} private boolean tid_is_popup = false; + public boolean Tid_is_image_map() {return tid_is_image_map;} public Xop_ctx Tid_is_image_map_(boolean v) {tid_is_image_map = v; return this;} private boolean tid_is_image_map; + + public boolean Tmpl_load_enabled() {return tmpl_load_enabled;} public void Tmpl_load_enabled_(boolean v) {tmpl_load_enabled = v;} private boolean tmpl_load_enabled = true; + public int Tmpl_tkn_max() {return tmpl_tkn_max;} public void Tmpl_tkn_max_(int v) {tmpl_tkn_max = v;} private int tmpl_tkn_max = Int_.Max_value; + public Xop_keeplist_wiki Tmpl_keeplist() {return tmpl_keeplist;} public void Tmpl_keeplist_(Xop_keeplist_wiki v) {this.tmpl_keeplist = v;} private Xop_keeplist_wiki tmpl_keeplist; + public boolean Tmpl_args_parsing() {return tmpl_args_parsing;} public Xop_ctx Tmpl_args_parsing_(boolean v) {tmpl_args_parsing = v; return this;} private boolean tmpl_args_parsing; + public Xot_defn_trace Defn_trace() {return defn_trace;} public Xop_ctx Defn_trace_(Xot_defn_trace v) {defn_trace = v; return this;} private Xot_defn_trace defn_trace = Xot_defn_trace_null.Instance; + public boolean Only_include_evaluate() {return only_include_evaluate;} public Xop_ctx Only_include_evaluate_(boolean v) {only_include_evaluate = v; return this;} private boolean only_include_evaluate; + + public Lst_section_nde_mgr Lst_section_mgr() {if (lst_section_mgr == null) lst_section_mgr = new Lst_section_nde_mgr(); return lst_section_mgr;} private Lst_section_nde_mgr lst_section_mgr; + public Hash_adp_bry Lst_page_regy() {return lst_page_regy;} private Hash_adp_bry lst_page_regy; + + public boolean Ref_ignore() {return ref_ignore;} public Xop_ctx Ref_ignore_(boolean v) {ref_ignore = v; return this;} private boolean ref_ignore; // NOTE: only applies to sub_ctx's created by and {{#lst}}; if true, does not add to page.Ref_mgr; DATE:2014-04-24 + public byte[] References_group() {return references_group;} public Xop_ctx References_group_(byte[] v) {references_group = v; return this;} private byte[] references_group; + + public Xop_log_property_wkr Xtn__wikidata__property_wkr() {return app.Wiki_mgr().Wdata_mgr().Property_wkr();} + public Xop_log_invoke_wkr Xtn__scribunto__invoke_wkr() { + if (scrib_invoke_wkr == null) + scrib_invoke_wkr = ((Scrib_xtn_mgr)(wiki.Xtn_mgr().Get_or_fail(Scrib_xtn_mgr.XTN_KEY))).Invoke_wkr(); + return scrib_invoke_wkr; + } private Xop_log_invoke_wkr scrib_invoke_wkr; + + public Xop_ctx Clear_all() {return Clear(true);} + public Xop_ctx Clear(boolean clear_scrib) { + cur_page.Clear(clear_scrib); + stack = Xop_tkn_itm_.Ary_empty; + stack_len = stack_max = 0; + if (lst_section_mgr != null) lst_section_mgr.Clear(); + if (lst_page_regy != null) lst_page_regy.Clear(); + tmpl_args_parsing = false; + page_data.Clear(); + return this; + } + public String Page_url_str() { + try {return cur_page.Url().To_str();} + catch (Exception e) {Err_.Noop(e); return "page_url shouldn't fail";} + } + public void Parser__page_init(Xop_root_tkn root, byte[] src) { + this.Msg_log().Clear(); cur_tkn_tid = Xop_tkn_itm_.Tid_null; + empty_ignored = false; + for (Xop_ctx_wkr wkr : wkrs) wkr.Page_bgn(this, root); + } + public void Parser__page_term(Xop_root_tkn root, byte[] src, int src_len) { + Stack_pop_til(root, src, 0, true, src_len, src_len, Xop_tkn_itm_.Tid_txt); + for (Xop_ctx_wkr wkr : wkrs) wkr.Page_end(this, root, src, src_len); + } + public boolean Lxr_make() {return lxr_make;} public Xop_ctx Lxr_make_(boolean v) {lxr_make = v; return this;} private boolean lxr_make = false; + public int Lxr_make_txt_(int pos) {lxr_make = false; return pos;} + public int Lxr_make_log_(Gfo_msg_itm itm, byte[] src, int bgn_pos, int cur_pos) {lxr_make = false; msg_log.Add_itm_none(itm, src, bgn_pos, cur_pos); return cur_pos;} + public boolean Empty_ignored() {return empty_ignored;} + public void Empty_ignored_y_() {empty_ignored = Bool_.Y;} private boolean empty_ignored = false; + public void Empty_ignored_n_() {empty_ignored = Bool_.N;} + public void Empty_ignore(Xop_root_tkn root, int empty_bgn) { + int empty_end = root.Subs_len(); + for (int i = empty_bgn; i < empty_end; i++) { + Xop_tkn_itm sub_tkn = root.Subs_get(i); + sub_tkn.Ignore_y_grp_(this, root, i); + } + empty_ignored = false; + } + + public byte Cur_tkn_tid() {return cur_tkn_tid;} private byte cur_tkn_tid = Xop_tkn_itm_.Tid_null; + public void Subs_add_and_stack_tblw(Xop_root_tkn root, Xop_tblw_tkn owner_tkn, Xop_tkn_itm sub) { + if (owner_tkn != null) owner_tkn.Tblw_subs_len_add_(); // owner_tkn can be null;EX: "{|" -> prv_tkn is null + Subs_add_and_stack(root, sub); + } + public void Subs_add_and_stack(Xop_root_tkn root, Xop_tkn_itm sub) {this.Subs_add(root, sub); this.Stack_add(sub);} + public void Subs_add(Xop_root_tkn root, Xop_tkn_itm sub) { + switch (sub.Tkn_tid()) { + case Xop_tkn_itm_.Tid_space: case Xop_tkn_itm_.Tid_tab: case Xop_tkn_itm_.Tid_newLine: + case Xop_tkn_itm_.Tid_para: + break; + default: + empty_ignored = false; + break; + } + root.Subs_add(sub); + } + public void StackTkn_add(Xop_root_tkn root, Xop_tkn_itm sub) { + root.Subs_add(sub); + this.Stack_add(sub); + } + public void Stack_add(Xop_tkn_itm tkn) { + int newLen = stack_len + 1; + if (newLen > stack_max) { + stack_max = newLen * 2; + stack = (Xop_tkn_itm[])Array_.Resize(stack, stack_max); + } + stack[stack_len] = tkn; + cur_tkn_tid = tkn.Tkn_tid(); + stack_len = newLen; + } private Xop_tkn_itm[] stack = Xop_tkn_itm_.Ary_empty; int stack_len = 0, stack_max = 0; + public int Stack_len() {return stack_len;} + public Xop_tkn_itm Stack_get_last() {return stack_len == 0 ? null : stack[stack_len - 1];} + public Xop_tkn_itm Stack_get(int i) {return i < 0 || i >= stack_len ? null : stack[i];} + public Xop_tblw_tkn Stack_get_tblw_tb() {// find any {| (exclude -1; i--) { + Xop_tkn_itm tkn = stack[i]; + if (tkn.Tkn_tid() == Xop_tkn_itm_.Tid_tblw_tb) { + Xop_tblw_tkn tkn_as_tbl = (Xop_tblw_tkn)tkn; + if (!tkn_as_tbl.Tblw_xml()) return tkn_as_tbl; + } + } + return null; + } + public Xop_tblw_tkn Stack_get_tbl_tb() { + for (int i = stack_len - 1; i > -1; i--) { + Xop_tkn_itm tkn = stack[i]; + switch (tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_tblw_tb: + return (Xop_tblw_tkn)tkn; + case Xop_tkn_itm_.Tid_xnde: + Xop_xnde_tkn xnde_tkn = (Xop_xnde_tkn)tkn; + switch (xnde_tkn.Tag().Id()) { + case Xop_xnde_tag_.Tid__table: + return (Xop_tblw_tkn)tkn; + } + break; + } + } + return null; + } + public Xop_tblw_tkn Stack_get_tbl() { + for (int i = stack_len - 1; i > -1; i--) { + Xop_tkn_itm tkn = stack[i]; + switch (tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_tblw_tb: + case Xop_tkn_itm_.Tid_tblw_tr: + case Xop_tkn_itm_.Tid_tblw_td: + case Xop_tkn_itm_.Tid_tblw_th: + case Xop_tkn_itm_.Tid_tblw_tc: + return (Xop_tblw_tkn)tkn; + case Xop_tkn_itm_.Tid_xnde: + Xop_xnde_tkn xnde_tkn = (Xop_xnde_tkn)tkn; + switch (xnde_tkn.Tag().Id()) { + case Xop_xnde_tag_.Tid__table: + case Xop_xnde_tag_.Tid__tr: + case Xop_xnde_tag_.Tid__td: + case Xop_xnde_tag_.Tid__th: + case Xop_xnde_tag_.Tid__caption: + return (Xop_tblw_tkn)tkn; + } + break; + } + } + return null; + } + public static final int Stack_not_found = -1; + public boolean Stack_has(int typeId) {return Stack_idx_typ(typeId) != Stack_not_found;} + public int Stack_idx_typ(int typeId) { + for (int i = stack_len - 1; i > -1; i--) + if (stack[i].Tkn_tid() == typeId) + return i; + return Stack_not_found; + } + public int Stack_idx_find_but_stop_at_tbl(int tid) { + for (int i = stack_len - 1; i > -1 ; i--) { + Xop_tkn_itm tkn_itm = stack[i]; + int tkn_itm_tid = tkn_itm.Tkn_tid(); + switch (tkn_itm_tid) { + case Xop_tkn_itm_.Tid_tblw_tb: // NOTE: added DATE:2014-06-26 + case Xop_tkn_itm_.Tid_tblw_td: + case Xop_tkn_itm_.Tid_tblw_th: + case Xop_tkn_itm_.Tid_tblw_tc: + return -1; + } + if (tkn_itm_tid == tid) + return i; + } + return -1; + } + public Xop_tkn_itm Stack_get_typ(int tid) { + for (int i = stack_len - 1; i > -1 ; i--) { + Xop_tkn_itm tkn = stack[i]; + if (tkn.Tkn_tid() == tid) return tkn; + } + return null; + } + public void Stack_del(Xop_tkn_itm del) { + if (stack_len == 0) return; + for (int i = stack_len - 1; i > -1; i--) { + Xop_tkn_itm tkn = stack[i]; + if (tkn == del) { + for (int j = i + 1; j < stack_len; j++) { + stack[j - 1] = stack[j]; + } + --stack_len; + break; + } + } + } + public Xop_tkn_itm Stack_pop_til(Xop_root_tkn root, byte[] src, int til_idx, boolean include, int bgn_pos, int cur_pos, int closing_tkn_tid) { // NOTE: closing_tkn_tid is a book-keeping variable to indicate who started auto-close; only used by xnde.AutoClose + if (stack_len == 0) return null; // nothing to pop; return; + int min_idx = include ? til_idx - 1 : til_idx; // if "include", auto-close tkn at til_idx; if not, auto-close to one before + if (min_idx < -1) min_idx = -1; // bounds-check; make sure til_idx was not -1, resulting in -2; NOTE: does not seem to be needed; DATE:2015-03-31 + Xop_tkn_itm rv = null; + for (int i = stack_len - 1; i > min_idx; i--) { // pop tkns going backwards + rv = stack[i]; + Stack_auto_close(root, src, rv, bgn_pos, cur_pos, closing_tkn_tid); + } + Stack_pop_idx(til_idx); + return include ? rv : stack[stack_len]; // if include, return popped_tkn; if not, return tkn before popped_tkn + } + public Xop_tkn_itm Stack_pop_before(Xop_root_tkn root, byte[] src, int til_idx, boolean include, int bgn_pos, int cur_pos, int closing_tkn_tid) { // used by Xop_tblw_lxr to detect \n| in lnki; seems useful as well + if (stack_len == 0) return null; + int min_idx = include ? til_idx - 1 : til_idx; + if (min_idx < -1) min_idx = -1; + Xop_tkn_itm rv = null; + for (int i = stack_len - 1; i > min_idx; i--) { + rv = stack[i]; + Stack_auto_close(root, src, rv, bgn_pos, cur_pos, closing_tkn_tid); + } + return include ? rv : stack[stack_len]; // if include, return poppedTkn; if not, return tkn before poppedTkn + } + public void Stack_auto_close(Xop_root_tkn root, byte[] src, Xop_tkn_itm tkn, int bgn_pos, int cur_pos, int closing_tkn_tid) { + int src_len = src.length; + switch (tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_newLine: break; // NOOP: just a marker + case Xop_tkn_itm_.Tid_list: list.AutoClose(this, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, tkn); break; + case Xop_tkn_itm_.Tid_xnde: xnde.AutoClose(this, root, src, src_len, bgn_pos, cur_pos, tkn, closing_tkn_tid); break; + case Xop_tkn_itm_.Tid_apos: apos.AutoClose(this, src, src_len, bgn_pos, cur_pos, tkn); break; + case Xop_tkn_itm_.Tid_lnke: lnke.AutoClose(this, src, src_len, bgn_pos, cur_pos, tkn); break; + case Xop_tkn_itm_.Tid_hdr: hdr.AutoClose(this, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, tkn); break; + case Xop_tkn_itm_.Tid_tblw_tb: + case Xop_tkn_itm_.Tid_tblw_tr: + case Xop_tkn_itm_.Tid_tblw_td: + case Xop_tkn_itm_.Tid_tblw_th: + case Xop_tkn_itm_.Tid_tblw_tc: tblw.AutoClose(this, root, src, src_len, bgn_pos, cur_pos, tkn); break; + case Xop_tkn_itm_.Tid_lnki: lnki.Auto_close(this, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, tkn); break; + case Xop_tkn_itm_.Tid_pre: para.AutoClose(this, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, tkn); break; + } + } + public void Stack_pop_idx(int tilIdx) { + stack_len = tilIdx < 0 ? 0 : tilIdx; + cur_tkn_tid = stack_len == 0 ? Xop_tkn_itm_.Tid_null : stack[stack_len - 1].Tkn_tid(); + } + public void Stack_pop_last() { // used primarily by lnke to remove lnke from stack + --stack_len; + cur_tkn_tid = stack_len == 0 ? Xop_tkn_itm_.Tid_null : stack[stack_len - 1].Tkn_tid(); + } + public void CloseOpenItms(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos) { + int stack_pos = -1, stack_len = ctx.Stack_len(); boolean stop = false; + for (int i = 0; i < stack_len; i++) { // loop over stack + Xop_tkn_itm prv_tkn = ctx.Stack_get(i); + switch (prv_tkn.Tkn_tid()) { // find first list/hdr; close everything until this + case Xop_tkn_itm_.Tid_list: + case Xop_tkn_itm_.Tid_hdr: + stack_pos = i; stop = true; break; + } + if (stop) break; + } + if (stack_pos == -1) return; + ctx.Stack_pop_til(root, src, stack_pos, true, bgn_pos, cur_pos, Xop_tkn_itm_.Tid_txt); + } + + public static Xop_ctx New__top(Xowe_wiki wiki) {return New__top(wiki, Xoa_page_.Main_page_bry);} // HACK: use "Main_Page" to put in valid page title + public static Xop_ctx New__top(Xowe_wiki wiki, byte[] ttl_bry) {return new Xop_ctx(wiki, Xoae_page.New(wiki, wiki.Ttl_parse(ttl_bry)));} + + public static Xop_ctx New__sub__reuse_page(Xop_ctx ctx) {return New__sub(ctx.wiki, ctx, ctx.cur_page);} // CALLED: many + public static Xop_ctx New__sub__reuse_lst(Xowe_wiki wiki, Xop_ctx ctx, Hash_adp_bry lst_page_regy) { + Xop_ctx rv = new Xop_ctx(wiki, ctx.cur_page); + Share_ctx_vars(ctx, rv); + rv.lst_page_regy = lst_page_regy; // NOTE: must share ref for callers of New__sub__reuse_lst only (do not share for New__sub(), else stack overflow) + return rv; + } + public static Xop_ctx New__sub(Xowe_wiki wiki, Xop_ctx ctx, Xoae_page page) {// TODO_OLD: new_sub_ should reuse ctx's page; callers who want new_page should call new_sub_page_; DATE:2014-04-10 + Xop_ctx rv = new Xop_ctx(wiki, page); + Share_ctx_vars(ctx, rv); + return rv; + } + public static Xop_ctx New__sub_and_page(Xowe_wiki wiki, Xop_ctx ctx) { // CALLED: poem + Xop_ctx rv = new Xop_ctx(wiki, Xoae_page.New(wiki, wiki.Ttl_parse(ctx.Page().Ttl().Full_db()))); + Share_ctx_vars(ctx, rv); + return rv; + } + + private static void Share_ctx_vars(Xop_ctx src, Xop_ctx trg) { + trg.Page().Db().Page().Id_(src.Page().Db().Page().Id()); + trg.Lnki().File_logger_(src.Lnki().File_logger()); // always share lnki_logger between sub contexts + trg.ref_ignore = src.ref_ignore; // copy ref_ignore; needed for refs inside poem else duplicate refs; it.s:La_Secchia_rapita/Canto_primo; DATE:2015-12-03 + trg.references_group = src.references_group; + trg.cur_page.Ref_mgr_(src.cur_page.Ref_mgr()); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_ctx_.java b/400_xowa/src/gplx/xowa/parsers/Xop_ctx_.java index a27517de8..3127ba264 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_ctx_.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_ctx_.java @@ -13,3 +13,15 @@ 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; import gplx.*; import gplx.xowa.*; +public class Xop_ctx_ { + public static String Page_as_str(Xop_ctx ctx) {return String_.new_u8(ctx.Page().Ttl().Full_db());} + public static String Src_limit_and_escape_nl(byte[] src, int bgn, int limit) { + int end = bgn + limit; + int src_len = src.length; + if (end > src_len) end = src_len; + byte[] rv = Bry_.Mid(src, bgn, end); + rv = Bry_.Replace(rv, Byte_ascii.Nl, Byte_ascii.Tab); // change nl to tab so text editor will show one warning per line + return String_.new_u8(rv); + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_ctx__tst.java b/400_xowa/src/gplx/xowa/parsers/Xop_ctx__tst.java index a27517de8..9f0512fd4 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_ctx__tst.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_ctx__tst.java @@ -13,3 +13,19 @@ 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; import gplx.*; import gplx.xowa.*; +import org.junit.*; +public class Xop_ctx__tst { + @Before public void init() {fxt.Clear();} private Xop_ctx__fxt fxt = new Xop_ctx__fxt(); + @Test public void Src_limit_and_escape_nl() { + fxt.Test_Src_limit_and_escape_nl("abcdefg", 4, 3, "efg"); // PURPOSE: bug fix; outOfBounds thrown; DATE:2014-03-31 + } +} +class Xop_ctx__fxt { + public void Clear() { + } + public void Test_Src_limit_and_escape_nl(String src, int bgn, int limit, String expd) { + String actl = Xop_ctx_.Src_limit_and_escape_nl(Bry_.new_u8(src), bgn, limit); + Tfds.Eq(expd, actl); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_ctx_page_data.java b/400_xowa/src/gplx/xowa/parsers/Xop_ctx_page_data.java index a27517de8..f8687c9f7 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_ctx_page_data.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_ctx_page_data.java @@ -13,3 +13,23 @@ 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; import gplx.*; import gplx.xowa.*; +public class Xop_ctx_page_data { + public boolean Hdr_toc() {return toc;} public void Hdr_toc_y_() {this.toc = true;} private boolean toc; + public boolean Hdr_forcetoc() {return forcetoc;} public void Hdr_forcetoc_y_() {this.forcetoc = true;} private boolean forcetoc; + public boolean Hdr_notoc() {return notoc;} public void Hdr_notoc_y_() {this.notoc = true;} private boolean notoc; + public boolean Lang_convert_content() {return lang_convert_content;} public void Lang_convert_content_(boolean v) {this.lang_convert_content = v;} private boolean lang_convert_content = true; + public boolean Lang_convert_title() {return lang_convert_title;} public void Lang_convert_title_(boolean v) {this.lang_convert_title = v;} private boolean lang_convert_title = true; + public void Clear() { + toc = forcetoc = notoc = false; + lang_convert_content = lang_convert_title = true; + } + public void Copy_to(Xoae_page page) { + gplx.xowa.wikis.pages.wtxts.Xopg_toc_mgr hdr_mgr = page.Wtxt().Toc(); + hdr_mgr.Flag__toc_(toc); + hdr_mgr.Flag__forcetoc_(forcetoc); + hdr_mgr.Flag__notoc_(notoc); + page.Html_data().Lang_convert_content_(lang_convert_content); + page.Html_data().Lang_convert_title_(lang_convert_title); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_ctx_wkr.java b/400_xowa/src/gplx/xowa/parsers/Xop_ctx_wkr.java index a27517de8..8a18bf39b 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_ctx_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_ctx_wkr.java @@ -13,3 +13,9 @@ 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; import gplx.*; import gplx.xowa.*; +public interface Xop_ctx_wkr { + void Ctor_ctx(Xop_ctx ctx); + void Page_bgn(Xop_ctx ctx, Xop_root_tkn root); + void Page_end(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int src_len); +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_lxr.java b/400_xowa/src/gplx/xowa/parsers/Xop_lxr.java index a27517de8..dd63ac8fc 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_lxr.java @@ -13,3 +13,12 @@ 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; import gplx.*; import gplx.xowa.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +public interface Xop_lxr { + int Lxr_tid(); + void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie); + void Init_by_lang(Xol_lang_itm lang, Btrie_fast_mgr core_trie); + void Term(Btrie_fast_mgr core_trie); + 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); +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_lxr_.java b/400_xowa/src/gplx/xowa/parsers/Xop_lxr_.java index a27517de8..00441dcf9 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_lxr_.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_lxr_.java @@ -13,3 +13,13 @@ 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; import gplx.*; import gplx.xowa.*; +public class Xop_lxr_ { + public static final int + Tid_pipe = 0, Tid_space = 1, Tid_nbsp = 2, Tid_tab = 3, Tid_nl = 4, Tid_amp = 5, Tid_apos = 6, Tid_colon = 7, Tid_lnki_bgn = 8, Tid_lnki_end = 9 + , 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 + ; +} 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 a27517de8..daf9b7d8a 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_lxr_mgr.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_lxr_mgr.java @@ -13,3 +13,93 @@ 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; 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.*; +public class Xop_lxr_mgr { + private final Xop_lxr[] ary; + private final List_adp page_lxr_list = List_adp_.New(); + public Xop_lxr_mgr(Xop_lxr[] ary) {this.ary = ary;} + public Btrie_fast_mgr Trie() {return trie;} private final Btrie_fast_mgr trie = Btrie_fast_mgr.cs(); + public void Page__add(Xowe_wiki wiki, Xop_lxr... ary) { + int len = ary.length; + for (int i = 0; i < len; ++i) { + Xop_lxr lxr = ary[i]; + lxr.Init_by_wiki(wiki, trie); + page_lxr_list.Add(lxr); + } + } + public void Page__del_all() { + int len = page_lxr_list.Count(); + for (int i = 0; i < len; ++i) { + Xop_lxr lxr = (Xop_lxr)page_lxr_list.Get_at(i); + lxr.Term(trie); + } + } + public void Init_by_wiki(Xowe_wiki wiki) { + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) { + Xop_lxr lxr = ary[i]; + lxr.Init_by_wiki(wiki, trie); + } + } + public void Init_by_lang(Xol_lang_itm lang) { + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) { + Xop_lxr lxr = ary[i]; + lxr.Init_by_lang(lang, trie); + } + } + public static Xop_lxr_mgr new_tmpl_() { + return new Xop_lxr_mgr(new Xop_lxr[] + { Xop_pipe_lxr.Instance, new Xop_eq_lxr(true), Xop_colon_lxr.Instance, Xop_space_lxr.Instance, Xop_tab_lxr.Instance, Xop_nl_lxr.Instance + , Xop_curly_bgn_lxr.Instance, Xop_curly_end_lxr.Instance + , Xop_brack_bgn_lxr.Instance, Xop_brack_end_lxr.Instance + , Xop_comm_lxr.Instance + , Xop_xnde_lxr.Instance // needed for xtn, noinclude, etc. + , Xop_under_lxr.Instance + , gplx.xowa.xtns.translates.Xop_tvar_lxr.Instance + , Xop_cr_lxr.Instance // always ignore \r; DATE:2014-03-02 + }); + } + public static Xop_lxr_mgr new_wiki_() { + return new Xop_lxr_mgr(new Xop_lxr[] + { Xop_pipe_lxr.Instance, new Xop_eq_lxr(false), Xop_space_lxr.Instance, Xop_tab_lxr.Instance, Xop_nl_lxr.Instance + , Xop_amp_lxr.Instance, Xop_apos_lxr.Instance, Xop_colon_lxr.Instance + , Xop_lnki_lxr_bgn.Instance, Xop_lnki_lxr_end.Instance + , Xop_list_lxr.Instance + , Xop_hdr_lxr.Instance + , Xop_hr_lxr.Instance + , Xop_xnde_lxr.Instance + , Xop_lnke_lxr.Instance, Xop_lnke_end_lxr.Instance + , Xop_tblw_lxr.Instance + , Xop_pre_lxr.Instance, Xop_nl_tab_lxr.Instance + , Xop_comm_lxr.Instance + , Xop_under_lxr.Instance + }); + } + public static Xop_lxr_mgr new_anchor_encoder() { + return new Xop_lxr_mgr(new Xop_lxr[] + { Xop_pipe_lxr.Instance, new Xop_eq_lxr(false), Xop_space_lxr.Instance, Xop_tab_lxr.Instance, Xop_nl_lxr.Instance + , Xop_curly_bgn_lxr.Instance, Xop_curly_end_lxr.Instance + , Xop_amp_lxr.Instance, Xop_colon_lxr.Instance + , Xop_apos_lxr.Instance + , Xop_lnki_lxr_bgn.Instance, Xop_lnki_lxr_end.Instance + , Xop_lnke_lxr.Instance, Xop_lnke_end_lxr.Instance + , Xop_xnde_lxr.Instance + }); + } + public static final Xop_lxr_mgr Popup_lxr_mgr // same as orig_page, except apos_lxr added + = new Xop_lxr_mgr(new Xop_lxr[] + { Xop_pipe_lxr.Instance, new Xop_eq_lxr(true), Xop_colon_lxr.Instance, Xop_space_lxr.Instance, Xop_tab_lxr.Instance, Xop_nl_lxr.Instance + , Xop_curly_bgn_lxr.Instance, Xop_curly_end_lxr.Instance + , Xop_brack_bgn_lxr.Instance, Xop_brack_end_lxr.Instance + , Xop_comm_lxr.Instance + , Xop_xnde_lxr.Instance // needed for xtn, noinclude, etc. + , Xop_under_lxr.Instance + , gplx.xowa.xtns.translates.Xop_tvar_lxr.Instance + , Xop_cr_lxr.Instance // always ignore \r; DATE:2014-03-02 + , gplx.xowa.parsers.apos.Xop_apos_lxr.Instance // needed else multiple apos may be split across blocks; + }); +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_parser.java b/400_xowa/src/gplx/xowa/parsers/Xop_parser.java index a27517de8..2889d1587 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_parser.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_parser.java @@ -13,3 +13,211 @@ 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; import gplx.*; import gplx.xowa.*; +import gplx.core.btries.*; +import gplx.xowa.langs.*; import gplx.xowa.htmls.core.htmls.*; import gplx.xowa.wikis.nss.*; +import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.tmpls.*; +public class Xop_parser { // NOTE: parsers are reused; do not keep any read-write state + private final Xowe_wiki wiki; + private final Btrie_fast_mgr tmpl_trie, wtxt_trie; + private Xot_compile_data tmpl_props = new Xot_compile_data(); // NOTE: probably should not be a member variable, but leave for now; DATE:2016-12-02 + Xop_parser(Xowe_wiki wiki, Xop_lxr_mgr tmpl_lxr_mgr, Xop_lxr_mgr wtxt_lxr_mgr) { + this.wiki = wiki; + this.tmpl_lxr_mgr = tmpl_lxr_mgr; this.tmpl_trie = tmpl_lxr_mgr.Trie(); + this.wtxt_lxr_mgr = wtxt_lxr_mgr; this.wtxt_trie = wtxt_lxr_mgr.Trie(); + } + public Xop_lxr_mgr Tmpl_lxr_mgr() {return tmpl_lxr_mgr;} private final Xop_lxr_mgr tmpl_lxr_mgr; + public Xop_lxr_mgr Wtxt_lxr_mgr() {return wtxt_lxr_mgr;} private final Xop_lxr_mgr wtxt_lxr_mgr; + public void Init_by_wiki(Xowe_wiki wiki) { + tmpl_lxr_mgr.Init_by_wiki(wiki); + wtxt_lxr_mgr.Init_by_wiki(wiki); + } + public void Init_by_lang(Xol_lang_itm lang) { + tmpl_lxr_mgr.Init_by_lang(lang); + wtxt_lxr_mgr.Init_by_lang(lang); + } + public byte[] Expand_tmpl(byte[] src) { // expands {{A}} -> some wikitext; called by tmpl_invk, lang_msgs, sidebar + Xop_ctx ctx = Xop_ctx.New__sub__reuse_page(wiki.Parser_mgr().Ctx()); // PERF: reuse root ctx + return Expand_tmpl(ctx, ctx.Tkn_mkr(), src); + } + private byte[] Expand_tmpl(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, byte[] src) {return Expand_tmpl(tkn_mkr.Root(src), ctx, Xot_invk_temp.Null_frame, tkn_mkr, src);} + public byte[] Expand_tmpl(Xop_root_tkn root, Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, byte[] src) {return Expand_tmpl(root, ctx, Xot_invk_temp.Null_frame, tkn_mkr, src);} + public byte[] Expand_tmpl(Xop_root_tkn root, Xop_ctx ctx, Xot_invk frame, Xop_tkn_mkr tkn_mkr, byte[] src) { + Parse(root, ctx, tkn_mkr, src, Xop_parser_tid_.Tid__tmpl, tmpl_trie, Xop_parser_.Doc_bgn_bos); + int len = root.Subs_len(); + for (int i = 0; i < len; ++i) + root.Subs_get(i).Tmpl_compile(ctx, src, tmpl_props); + return Xot_tmpl_wtr.Write_all(ctx, frame, root, src); + } + + public byte[] Parse_text_to_html(Xop_ctx ctx, byte[] src) { + Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_b512(); + Parse_text_to_html(bfr, ctx, ctx.Page(), false, src); + return bfr.To_bry_and_rls(); + } + public void Parse_text_to_html(Bry_bfr trg, Xop_ctx pctx, Xoae_page page, boolean para_enabled, byte[] src) {Parse_text_to_html(trg, pctx, page, Xoh_wtr_ctx.Basic, para_enabled, src);} + public void Parse_text_to_html(Bry_bfr trg, Xop_ctx pctx, Xoae_page page, Xoh_wtr_ctx hctx, boolean para_enabled, byte[] src) { + Xop_ctx ctx = Xop_ctx.New__sub(wiki, pctx, page); + Xop_tkn_mkr tkn_mkr = ctx.Tkn_mkr(); + Xop_root_tkn root = tkn_mkr.Root(src); + Xop_parser parser = wiki.Parser_mgr().Main(); + byte[] wtxt = parser.Expand_tmpl(root, ctx, tkn_mkr, src); + root.Reset(); + ctx.Para().Enabled_(para_enabled); + parser.Parse_wtxt_to_wdom(root, ctx, ctx.Tkn_mkr(), wtxt, Xop_parser_.Doc_bgn_bos); + wiki.Html_mgr().Html_wtr().Write_doc(trg, ctx, hctx, wtxt, root); + } + + public Xot_defn_tmpl Parse_text_to_defn_obj(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xow_ns ns, byte[] name, byte[] src) { + Xot_defn_tmpl rv = new Xot_defn_tmpl(); + Parse_text_to_defn(rv, ctx, tkn_mkr, ns, name, src); + return rv; + } + public void Parse_text_to_defn(Xot_defn_tmpl tmpl, Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xow_ns ns, byte[] name, byte[] src) { + Xop_root_tkn root = tkn_mkr.Root(src); + Parse(root, ctx, tkn_mkr, src, Xop_parser_tid_.Tid__defn, tmpl_trie, Xop_parser_.Doc_bgn_bos); + tmpl_props.OnlyInclude_exists = false; int subs_len = root.Subs_len(); + for (int i = 0; i < subs_len; i++) + root.Subs_get(i).Tmpl_compile(ctx, src, tmpl_props); + boolean only_include_chk = Bry_find_.Find_fwd(src, Xop_xnde_tag_.Bry__onlyinclude, 0, src.length) != Bry_find_.Not_found; + if (only_include_chk) tmpl_props.OnlyInclude_exists = true; + tmpl.Init_by_new(ns, name, src, root, tmpl_props.OnlyInclude_exists); + } + public void Parse_page_all_clear(Xop_root_tkn root, Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, byte[] src) { + ctx.Page().Clear_all(); ctx.App().Msg_log().Clear(); + Parse_text_to_wdom(root, ctx, tkn_mkr, src, Xop_parser_.Doc_bgn_bos); + } + public Xop_root_tkn Parse_text_to_wdom_old_ctx(Xop_ctx old_ctx, byte[] src, boolean doc_bgn_pos) {return Parse_text_to_wdom(Xop_ctx.New__sub__reuse_page(old_ctx), src, doc_bgn_pos);} + public Xop_root_tkn Parse_text_to_wdom(Xop_ctx new_ctx, byte[] src, boolean doc_bgn_pos) { + new_ctx.Para().Enabled_n_(); + Xop_tkn_mkr tkn_mkr = new_ctx.Tkn_mkr(); + Xop_root_tkn root = tkn_mkr.Root(src); + Parse_text_to_wdom(root, new_ctx, tkn_mkr, src, doc_bgn_pos ? Xop_parser_.Doc_bgn_bos : Xop_parser_.Doc_bgn_char_0); + return root; + } + public void Parse_text_to_wdom(Xop_root_tkn root, Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, byte[] src, int doc_bgn_pos) { + byte parse_tid_old = ctx.Parse_tid();// NOTE: must store parse_tid b/c ctx can be reused by other classes + ctx.Parse_tid_(Xop_parser_tid_.Tid__tmpl); + root.Reset(); + byte[] mid_bry = Expand_tmpl(root, ctx, tkn_mkr, src); + root.Data_mid_(mid_bry); + root.Reset(); + Parse_wtxt_to_wdom(root, ctx, tkn_mkr, mid_bry, doc_bgn_pos); + ctx.Parse_tid_(parse_tid_old); + } + public void Parse_wtxt_to_wdom(Xop_root_tkn root, Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, byte[] wtxt, int doc_bgn_pos) { + root.Root_src_(wtxt); // always set latest src; needed for Parse_all wherein src will first be raw and then parsed tmpl + Parse(root, ctx, tkn_mkr, wtxt, Xop_parser_tid_.Tid__wtxt, wtxt_trie, doc_bgn_pos); + } + private void Parse(Xop_root_tkn root, Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, byte[] src, byte parse_type, Btrie_fast_mgr trie, int doc_bgn_pos) { + int len = src.length; if (len == 0) return; // nothing to parse; + byte parse_tid_old = ctx.Parse_tid(); // NOTE: must store parse_tid b/c ctx can be reused by other classes + ctx.Parse_tid_(parse_type); + ctx.Parser__page_init(root, src); + ctx.App().Parser_mgr().Core__uniq_mgr().Clear(); + Parse_to_src_end(root, ctx, tkn_mkr, src, trie, doc_bgn_pos, len); + ctx.Parser__page_term(root, src, len); + ctx.Parse_tid_(parse_tid_old); + } + public int Parse_to_src_end(Xop_root_tkn root, Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, byte[] src, Btrie_fast_mgr trie, int pos, int len) { + byte b = pos == -1 ? Byte_ascii.Nl : src[pos]; // simulate newLine at bgn of src; needed for lxrs which rely on \n (EX: "=a=") + int txt_bgn = pos == -1 ? 0 : pos; Xop_tkn_itm txt_tkn = null; + Btrie_rv trv = new Btrie_rv(); + while (true) { + Object o = trie.Match_at_w_b0(trv, b, src, pos, len); + Xop_lxr lxr = null; + if (o == null) // no lxr found; char is txt; increment pos + pos++; + else { // lxr found + lxr = (Xop_lxr)o; + if (txt_bgn != pos) // chars exist between pos and txt_bgn; make txt_tkn; see NOTE_1 + txt_tkn = Txt_add(ctx, tkn_mkr, root, txt_tkn, txt_bgn, pos); + ctx.Lxr_make_(true); + pos = lxr.Make_tkn(ctx, tkn_mkr, root, src, len, pos, trv.Pos()); + if (ctx.Lxr_make()) {txt_bgn = pos; txt_tkn = null;} // reset txt_tkn + } + if (pos == len) break; + b = src[pos]; + } + if (txt_bgn != pos) txt_tkn = Txt_add(ctx, tkn_mkr, root, txt_tkn, txt_bgn, pos); + return pos; + } + public int Parse_to_stack_end(Xop_root_tkn root, Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, byte[] src, int src_len, Btrie_fast_mgr trie, int pos, int end) { + byte b = pos == -1 ? Byte_ascii.Nl : src[pos]; // simulate \n at bgn of src; needed for lxrs which rely on \n (EX: "=a=") + int txt_bgn = pos == -1 ? 0 : pos; Xop_tkn_itm txt_tkn = null; + Xop_lxr lxr = null; + Btrie_rv trv = new Btrie_rv(); + while (true) { + lxr = null; + + Object o = trie.Match_at_w_b0(trv, b, src, pos, src_len); + if (o == null) // no lxr found; char is txt; increment pos + pos++; + else { // lxr found + lxr = (Xop_lxr)o; + if (txt_bgn != pos) // chars exist between pos and txt_bgn; make txt_tkn; see NOTE_1 + txt_tkn = Txt_add(ctx, tkn_mkr, root, txt_tkn, txt_bgn, pos); + ctx.Lxr_make_(true); + pos = lxr.Make_tkn(ctx, tkn_mkr, root, src, src_len, pos, trv.Pos()); + if (ctx.Lxr_make()) {txt_bgn = pos; txt_tkn = null;} // reset txt_tkn + } + if ( pos >= end + && ctx.Stack_len() == 0 // check stack is 0 to avoid dangling templates + ) { + if (o == null) {} // last sequence is not text; avoids splitting words across blocks; EX: 4 block and word of "abcde" will split to "abcd" and "e" + else { + if (lxr != null) { + boolean stop = true; + switch (lxr.Lxr_tid()) { + case Xop_lxr_.Tid_eq: + case Xop_lxr_.Tid_nl: + stop = false; + break; + } + if (stop) + break; + } + else { + break; + } + } + } + if (pos >= src_len) break; + b = src[pos]; + } + if (txt_bgn != pos) txt_tkn = Txt_add(ctx, tkn_mkr, root, txt_tkn, txt_bgn, pos); + return pos; + } + private static Xop_tkn_itm Txt_add(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, Xop_tkn_itm tkn, int txt_bgn, int pos) { + if (pos == Xop_parser_.Doc_bgn_bos) return null; // don't make txt_tkn for Bos_pos + if (tkn == null) { // no existing txt_tkn; create new one + tkn = tkn_mkr.Txt(txt_bgn, pos); + ctx.Subs_add(root, tkn); + } + else // existing txt_tkn; happens for false matches; EX: abc[[\nef[[a]]; see NOTE_1 + tkn.Src_end_(pos); + return tkn; + } + public static Xop_parser new_(Xowe_wiki wiki, Xop_lxr_mgr tmpl_lxr_mgr, Xop_lxr_mgr wtxt_lxr_mgr) {return new Xop_parser(wiki, tmpl_lxr_mgr, wtxt_lxr_mgr);} + public static Xop_parser new_wiki(Xowe_wiki wiki) { + Xop_parser rv = new Xop_parser(wiki, Xop_lxr_mgr.new_tmpl_(), Xop_lxr_mgr.new_wiki_()); + rv.Init_by_wiki(wiki); + rv.Init_by_lang(wiki.Lang()); + return rv; + } +} +/* +NOTE_1 +abc[[\nef[[a]] + : txt_bgn = 0; txt_tkn = null; +abc : increment pos +[[\n : lnki lxr + : (1): txt_tkn == null, so create txt_tkn with (0, 3) + : (2): lxr.Make_tkn() entered for lnki; however \n exits lnki + : (3): note that ctx.Lxr_make == false, so txt_bgn/txt_tkn is not reset +ef : still just text; increment pos +[[a]] : lnki entered + : (1): txt_tkn != null; set end to 8 + : (2): lxr.Make_tkn() entered and lnki made + : (3): note that ctx.Lxr_make == true, so txt_bgn = 13 and txt_tkn = null +*/ diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_parser_.java b/400_xowa/src/gplx/xowa/parsers/Xop_parser_.java index a27517de8..f4917cd7c 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_parser_.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_parser_.java @@ -13,3 +13,29 @@ 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; import gplx.*; import gplx.xowa.*; +import gplx.xowa.htmls.core.htmls.*; +import gplx.xowa.langs.vnts.*; +public class Xop_parser_ { + public static final int Doc_bgn_bos = -1, Doc_bgn_char_0 = 0; + public static byte[] Parse_text_to_html(Xowe_wiki wiki, Xop_ctx owner_ctx, Xoae_page page, Xoa_ttl ttl, byte[] src, boolean para_enabled) { // NOTE: must pass in same page instance; do not do Xoa_page_.new_(), else img_idx will get reset to 0; DATE:2015-02-08 + // init + Xop_ctx ctx = Xop_ctx.New__sub(wiki, owner_ctx, page); + Xop_tkn_mkr tkn_mkr = ctx.Tkn_mkr(); + Xop_root_tkn root = tkn_mkr.Root(src); + Xop_parser parser = wiki.Parser_mgr().Main(); + + // expand template; EX: {{A}} -> wikitext + byte[] wtxt = parser.Expand_tmpl(root, ctx, tkn_mkr, src); + + // parse wikitext + root.Reset(); + ctx.Para().Enabled_(para_enabled); + parser.Parse_wtxt_to_wdom(root, ctx, ctx.Tkn_mkr(), wtxt, Xop_parser_.Doc_bgn_bos); + + // write html + Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_b512(); + wiki.Html_mgr().Html_wtr().Write_doc(bfr, ctx, Xoh_wtr_ctx.Basic, wtxt, root); + return bfr.To_bry_and_rls(); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_parser__tst.java b/400_xowa/src/gplx/xowa/parsers/Xop_parser__tst.java index a27517de8..232053c45 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_parser__tst.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_parser__tst.java @@ -13,3 +13,44 @@ 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; import gplx.*; import gplx.xowa.*; +import org.junit.*; +public class Xop_parser__tst { + @Before public void init() {fxt.Clear();} private Xop_parser__fxt fxt = new Xop_parser__fxt(); + @Test public void Para_y() { + fxt.Test_parse_to_html(String_.Concat_lines_nl_skip_last + ( "a" + , "" + , "b" + ), true, String_.Concat_lines_nl_skip_last + ( "

      a" + , "

      " + , "" + , "

      b" + , "

      " + , "" + )); + } + @Test public void Para_n() { + fxt.Test_parse_to_html(String_.Concat_lines_nl_skip_last + ( "a" + , "" + , "b" + ), false, String_.Concat_lines_nl_skip_last + ( "a" + , "b" + )); + } +} +class Xop_parser__fxt { + private final Xop_fxt fxt = new Xop_fxt(); + private Bry_bfr bfr = Bry_bfr_.Reset(255); + public void Clear() { + fxt.Reset(); + } + public void Test_parse_to_html(String raw, boolean para_enabled, String expd) { + byte[] raw_bry = Bry_.new_u8(raw); + fxt.Wiki().Parser_mgr().Main().Parse_text_to_html(bfr, fxt.Ctx(), fxt.Page(), para_enabled, raw_bry); + Tfds.Eq(expd, bfr.To_str_and_clear()); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_parser_tid_.java b/400_xowa/src/gplx/xowa/parsers/Xop_parser_tid_.java index a27517de8..f52288176 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_parser_tid_.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_parser_tid_.java @@ -13,3 +13,7 @@ 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; import gplx.*; import gplx.xowa.*; +public class Xop_parser_tid_ { + public static final byte Tid__null = 0, Tid__defn = 1, Tid__tmpl = 2, Tid__wtxt = 3; +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_root_tkn.java b/400_xowa/src/gplx/xowa/parsers/Xop_root_tkn.java index a27517de8..45a89062b 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_root_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_root_tkn.java @@ -13,3 +13,14 @@ 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; import gplx.*; import gplx.xowa.*; +public class Xop_root_tkn extends Xop_tkn_itm_base { + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_root;} + public byte[] Root_src() {return root_src;} public Xop_root_tkn Root_src_(byte[] v) {root_src = v; return this;} private byte[] root_src = Bry_.Empty; + public byte[] Data_mid() {return data_mid;} public Xop_root_tkn Data_mid_(byte[] v) {data_mid = v; return this;} private byte[] data_mid = Bry_.Empty; + public byte[] Data_htm() {return data_htm;} public Xop_root_tkn Data_htm_(byte[] v) {data_htm = v; return this;} private byte[] data_htm = Bry_.Empty; + @Override public void Reset() { + super.Reset(); + root_src = Bry_.Empty; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_tkn_chkr_base.java b/400_xowa/src/gplx/xowa/parsers/Xop_tkn_chkr_base.java index a27517de8..83897e62e 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_tkn_chkr_base.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_tkn_chkr_base.java @@ -13,3 +13,51 @@ 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; import gplx.*; import gplx.xowa.*; +import gplx.core.tests.*; +public class Xop_tkn_chkr_base implements Tst_chkr { + @gplx.Virtual public Class TypeOf() {return Xop_tkn_itm.class;} + @gplx.Virtual public byte Tkn_tid() {return Byte_.Max_value_127;} + public Xop_tkn_chkr_base TypeId_dynamic(int v) {typeId = Xop_tkn_itm_.Tid__names[v]; return this;} private String typeId = null; + public int Src_bgn() {return src_bgn;} private int src_bgn = -1; + public int Src_end() {return src_end;} private int src_end = -1; + public byte Ignore() {return ignore;} private Xop_tkn_chkr_base Ignore_(byte v) {ignore = v; return this;} private byte ignore = Bool_.__byte; + public Xop_tkn_chkr_base Ignore_y_() {return Ignore_(Bool_.Y_byte);} + public Xop_tkn_chkr_base Src_rng_(int bgn, int end) {src_bgn = bgn; src_end = end; return this;} + public String Raw() {return raw;} public Xop_tkn_chkr_base Raw_(String v) {raw = v; return this;} private String raw; + public String Raw_src() {return raw_src;} public Xop_tkn_chkr_base Raw_src_(String v) {raw_src = v; return this;} private String raw_src; + public Xop_tkn_chkr_base[] Subs() {return subs;} public Xop_tkn_chkr_base Subs_(Xop_tkn_chkr_base... v) {subs = v; return this;} private Xop_tkn_chkr_base[] subs = null; + @gplx.Virtual public int Chk(Tst_mgr mgr, String path, Object actl_obj) { + Xop_tkn_itm actl = (Xop_tkn_itm)actl_obj; + int rv = 0; + rv += Chk_basic(mgr, path, actl, rv); + rv += Chk_hook(mgr, path, actl, rv); + rv += Chk_subs(mgr, path, actl, rv); + return rv; + } + @gplx.Virtual public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) {return 0;} + int Chk_basic(Tst_mgr mgr, String path, Xop_tkn_itm actl, int err) { + if (typeId == null) typeId = Xop_tkn_itm_.Tid__names[this.Tkn_tid()]; + err += mgr.Tst_val(typeId == null, path, "typeId", typeId, Xop_tkn_itm_.Tid__names[actl.Tkn_tid()]); + if (ignore != Bool_.__byte) err += mgr.Tst_val(ignore == Bool_.__byte, path, "ignore", ignore == Bool_.Y_byte, actl.Ignore()); // "ignore !=" to skip comparison unless explicitly toggled + err += mgr.Tst_val(src_bgn == -1, path, "src_bgn", src_bgn, actl.Src_bgn()); + err += mgr.Tst_val(src_end == -1, path, "src_end", src_end, actl.Src_end()); + if (raw != null) { + String raw_actl = raw_src == null ? mgr.Vars_get_bry_as_str("raw_bry", actl.Src_bgn(), actl.Src_end()) : String_.Mid(raw_src, actl.Src_bgn(), actl.Src_end()); + err += mgr.Tst_val(raw == null, path, "raw", raw, raw_actl); + } + return err; + } + int Chk_subs(Tst_mgr mgr, String path, Xop_tkn_itm actl, int err) { + if (subs != null) { + int actl_subs_len = actl.Subs_len(); + Xop_tkn_itm[] actl_subs = new Xop_tkn_itm[actl_subs_len]; + for (int i = 0; i < actl_subs_len; i++) + actl_subs[i] = actl.Subs_get(i); + return mgr.Tst_sub_ary(subs, actl_subs, path, err); + } + return err; + } + public static final Tst_chkr Null = Tst_mgr.Null_chkr; + public static final Xop_tkn_chkr_base[] Ary_empty = new Xop_tkn_chkr_base[0]; +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_tkn_grp.java b/400_xowa/src/gplx/xowa/parsers/Xop_tkn_grp.java index a27517de8..e765b52c6 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_tkn_grp.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_tkn_grp.java @@ -13,3 +13,20 @@ 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; import gplx.*; import gplx.xowa.*; +public interface Xop_tkn_grp { + int Subs_len(); + Xop_tkn_itm Subs_get(int i); + void Subs_add(Xop_tkn_itm sub); + void Subs_add_grp(Xop_tkn_itm sub, Xop_tkn_grp old_grp, int old_sub_idx); + void Subs_del_after(int pos_bgn); + void Subs_clear(); + void Subs_move(Xop_tkn_itm tkn); + int Subs_src_bgn(int sub_idx); + int Subs_src_end(int sub_idx); + void Subs_src_pos_(int sub_idx, int bgn, int end); + Xop_tkn_itm Immutable_clone(Xop_ctx ctx, Xop_tkn_itm tkn, int sub_idx); +} +class Xop_tkn_grp_ { + public static final Xop_tkn_grp Null = null; +} 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 a27517de8..e0ef44298 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_tkn_itm.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_tkn_itm.java @@ -13,3 +13,37 @@ 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; import gplx.*; import gplx.xowa.*; +import gplx.xowa.parsers.tmpls.*; +import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.htmls.*; +public interface Xop_tkn_itm extends Xop_tkn_grp { + byte Tkn_tid(); + Xop_tkn_itm Tkn_ini_pos(boolean immutable, int bgn, int end); + Xop_tkn_itm Tkn_clone(Xop_ctx ctx, int bgn, int end); + boolean Tkn_immutable(); + Xop_tkn_grp Tkn_grp(); + int Src_bgn(); + int Src_end(); + int Src_bgn_grp(Xop_tkn_grp grp, int sub_idx); + int Src_end_grp(Xop_tkn_grp grp, int sub_idx); + int Tkn_sub_idx(); + boolean Ignore(); + Xop_tkn_itm Tkn_grp_(Xop_tkn_grp grp, int sub_idx); + void Src_end_(int v); + void Src_end_grp_(Xop_ctx ctx, Xop_tkn_grp grp, int sub_idx, int src_end); + Xop_tkn_itm Ignore_y_(); + void Ignore_y_grp_(Xop_ctx ctx, Xop_tkn_grp grp, int sub_idx); + void Clear(); + void Tmpl_fmt(Xop_ctx ctx, byte[] src, Xot_fmtr fmtr); + void Tmpl_compile(Xop_ctx ctx, byte[] src, Xot_compile_data prep_data); // SEE:NOTE_1:Tmpl_compile + boolean Tmpl_evaluate(Xop_ctx ctx, byte[] src, Xot_invk caller, Bry_bfr bfr); + void Html__write(Bry_bfr bfr, Xoh_html_wtr wtr, Xowe_wiki wiki, Xoae_page page, Xop_ctx ctx, Xoh_wtr_ctx hctx, Xoh_html_wtr_cfg cfg, Xop_tkn_grp grp, int sub_idx, byte[] src); +} +/* +NOTE_1: Tmpl_compile +- called for tmpl_defn +- identifies tkn as static or dynamic; important for evaluate later; if static, evaluate will simply extract src +- if static, parses prm; EX: {{{1|a}}} will produce member variables of idx=1 and dflt=a +- if static, parses tmpl_name; EX: {{concat|a|b}} will generate name of concat +- if mark tmpl accordingly +*/ 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 a27517de8..2a5cc21c8 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_tkn_itm_.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_tkn_itm_.java @@ -13,3 +13,116 @@ 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; 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 byte + Tid_null = 0 +, Tid_root = 1 +, Tid_txt = 2 +, Tid_ignore = 3 +, Tid_newLine = 4 +, Tid_space = 5 +, Tid_tab = 6 +, Tid_pipe = 7 +, Tid_eq = 8 +, Tid_colon = 9 +, Tid_amp = 10 +, Tid_lt = 11 +, Tid_gt = 12 +, Tid_quot = 13 +, Tid_apos = 14 +, Tid_html_ref = 15 +, Tid_html_ncr = 16 +, Tid_lnki_bgn = 17 +, Tid_lnki_end = 18 +, Tid_lnki = 19 +, Tid_lnke = 20 +, Tid_hr = 21 +, Tid_hdr = 22 +, Tid_tblw_tb = 23 +, Tid_tblw_te = 24 +, Tid_tblw_tr = 25 +, Tid_tblw_th = 26 +, Tid_tblw_td = 27 +, Tid_tblw_tc = 28 +, Tid_list = 29 +, Tid_xnde = 30 +, Tid_xatr = 31 +, Tid_tmpl_prm_bgn = 32 +, Tid_tmpl_prm_end = 33 +, Tid_tmpl_prm = 34 +, Tid_tmpl_invk_dat = 35 +, Tid_arg_nde = 36 +, Tid_arg_itm = 37 +, Tid_tmpl_invk = 38 +, Tid_tmpl_curly_bgn = 39 +, Tid_brack_bgn = 40 +, Tid_brack_end = 41 +, Tid_para = 42 +, Tid_pre = 43 +, Tid_bry = 44 +, Tid_under = 45 +, Tid_tvar = 46 +, Tid_vnt = 47 +, Tid_vnt_rule = 48 +, Tid_vnt_eqgt = 49 +, Tid_cr = 50 +, Tid_escape = 51 +; +public static final String[] Tid__names += new String[] +{ "null" +, "root" +, "text" +, "ignore" +, "newLine" +, "space" +, "tab" +, "pipe" +, "eq" +, "colon" +, "amp" +, "lt" +, "gt" +, "quot" +, "apos" +, "htmlRef" +, "htmlNcr" +, "lnki_bgn" +, "lnki_end" +, "lnki" +, "lnke" +, "hr" +, "hdr" +, "tblw_tb" +, "tblw_te" +, "tblw_tr" +, "tblw_th" +, "tblw_td" +, "tblw_tc" +, "list" +, "xnde" +, "xatr" +, "tmpl_prm_bgn" +, "tmpl_prm_end" +, "tmpl_prm" +, "tmpl_invk_dat" +, "arg" +, "arg_itm" +, "tmpl_invk" +, "tmpl_curly_bgn" +, "brack_bgn" +, "brack_end" +, "para" +, "para_pre" +, "bry" +, "under" +, "tvar" +, "vnt" +, "vnt_rule" +, "vnt_eqgt" +, "cr" +, "escape" +}; +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_tkn_itm_base.java b/400_xowa/src/gplx/xowa/parsers/Xop_tkn_itm_base.java index a27517de8..66a4bb245 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_tkn_itm_base.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_tkn_itm_base.java @@ -13,3 +13,155 @@ 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; import gplx.*; import gplx.xowa.*; +import gplx.xowa.parsers.tmpls.*; +import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.htmls.*; +public abstract class Xop_tkn_itm_base implements Xop_tkn_itm { + public abstract byte Tkn_tid(); + public Xop_tkn_grp Tkn_grp() {return grp == null ? this : grp;} private Xop_tkn_grp grp; // NOTE: not sure about this; need to handle null refs when tkns are manipulated but not yet added to a group + public Xop_tkn_itm Tkn_ini_pos(boolean immutable, int bgn, int end) {this.immutable = immutable; this.src_bgn = bgn; this.src_end = end; return this;} + public Xop_tkn_itm Tkn_grp_(Xop_tkn_grp grp, int sub_idx) {this.grp = grp; this.tkn_sub_idx = sub_idx; return this;} + @gplx.Virtual public Xop_tkn_itm Tkn_clone(Xop_ctx ctx, int bgn, int end) {throw Err_.new_wo_type("tkn_clone not implemented", "name", Xop_tkn_itm_.Tid__names[this.Tkn_tid()]);} + public boolean Tkn_immutable() {return immutable;} private boolean immutable; + public int Tkn_sub_idx() {return tkn_sub_idx;} private int tkn_sub_idx = -1; + public int Src_bgn() {return src_bgn;} private int src_bgn = -1; + public int Src_end() {return src_end;} private int src_end = -1; + public void Src_end_(int v) {src_end = v;} + public int Src_bgn_grp(Xop_tkn_grp grp, int sub_idx) {return immutable ? grp.Subs_src_bgn(sub_idx) : src_bgn;} + public int Src_end_grp(Xop_tkn_grp grp, int sub_idx) {return immutable ? grp.Subs_src_end(sub_idx) : src_end;} + public int Subs_src_bgn(int sub_idx) {if (subs_len == 0) throw Err_.new_wo_type("no subs available", "idx", sub_idx); return subs_pos_ary[ sub_idx * 2];} + public int Subs_src_end(int sub_idx) {if (subs_len == 0) throw Err_.new_wo_type("no subs available", "idx", sub_idx); return subs_pos_ary[(sub_idx * 2) + 1];} + public void Subs_src_pos_(int sub_idx, int bgn, int end) { + int pos_idx = sub_idx * 2; + int subs_pos_ary_len = subs_pos_ary.length; + if (pos_idx + 1 > subs_pos_ary_len) { + int[] new_subs_pos_ary = new int[(pos_idx + 1) * 2]; + Array_.Copy_to(subs_pos_ary, 0, new_subs_pos_ary, 0, subs_pos_ary.length); + subs_pos_ary = new_subs_pos_ary; + } + subs_pos_ary[pos_idx] = bgn; + subs_pos_ary[pos_idx + 1] = end; + } + public boolean Ignore() {return ignore;} private boolean ignore; + public Xop_tkn_itm Ignore_y_() { + ignore = true; + return this; + } + public int Subs_len() {return subs_len;} private int subs_len; + public Xop_tkn_itm[] Subs() {return subs;} + public Xop_tkn_itm Subs_get(int i) {return subs[i];} + public Xop_tkn_itm Subs_get_or_null(int i) {return i < subs_len ? subs[i] : null;} + public void Subs_add(Xop_tkn_itm sub) { + int new_len = subs_len + 1; + if (new_len > subs_max) { // ary too small >>> expand + subs_max = new_len * 2; + Xop_tkn_itm[] new_subs = new Xop_tkn_itm[subs_max]; + Array_.Copy_to(subs, 0, new_subs, 0, subs_len); + subs = new_subs; + } + subs[subs_len] = sub; + sub.Tkn_grp_(this, subs_len); + subs_len = new_len; + } private Xop_tkn_itm[] subs = Xop_tkn_itm_.Ary_empty; int subs_max; int[] subs_pos_ary = Int_ary_.Empty; + public void Subs_add_grp(Xop_tkn_itm sub, Xop_tkn_grp old_grp, int old_sub_idx) { + this.Subs_add(sub); + if (sub.Tkn_immutable()) + this.Subs_src_pos_(subs_len - 1, sub.Src_bgn_grp(old_grp, old_sub_idx), sub.Src_end_grp(old_grp, old_sub_idx)); + } + public void Subs_del_after(int tkn_sub_idx) { + if (tkn_sub_idx >= subs_len) return; // ignore delete after len; PRUNE: breaks 3 tests; + for (int i = tkn_sub_idx; i < subs_len; i++) + subs[i] = null; + subs_len = tkn_sub_idx; + } + public void Subs_del_between(Xop_ctx ctx, int idx_bgn, int idx_end) { + if (idx_bgn >= subs_len || idx_bgn >= idx_end) return; // ignore invalid bounds; PRUNE: breaks 2 tests + int idx_dif = idx_end - idx_bgn; + for (int trg_idx = idx_bgn; trg_idx < subs_len; trg_idx++) { + int src_idx = trg_idx + idx_dif; + if (src_idx < subs_len) { // trg exists >>> move tkn from src to trg + Xop_tkn_itm src_tkn = subs[src_idx]; + subs[trg_idx] = src_tkn; + src_tkn.Tkn_grp_(this, trg_idx); + subs[src_idx] = null; + } + else + subs[trg_idx] = null; + } + subs_len -= idx_dif; + } + public void Subs_clear() { + subs_len = subs_max = 0; + subs = Xop_tkn_itm_.Ary_empty; + subs_pos_ary = Int_ary_.Empty; + } + public void Subs_move(Xop_tkn_itm tkn) { + int nxt_idx = tkn_sub_idx + 1, len = tkn.Subs_len(); + for (int i = nxt_idx; i < len; i++) { + Xop_tkn_itm sub = tkn.Subs_get(i); + Subs_add_grp(sub, tkn, i); + } + tkn.Subs_del_after(nxt_idx); + } + public void Subs_move(Xop_tkn_itm owner, int sub_idx, int subs_len) { + for (int i = sub_idx; i < subs_len; i++) { + Xop_tkn_itm sub = owner.Subs_get(i); + this.Subs_add(sub); + } + owner.Subs_del_after(sub_idx); + } + public Xop_tkn_itm Immutable_clone(Xop_ctx ctx, Xop_tkn_itm tkn, int sub_idx) { + int pos_idx = sub_idx * 2; + Xop_tkn_itm rv = tkn.Tkn_clone(ctx, subs_pos_ary[pos_idx], subs_pos_ary[pos_idx + 1]); + subs[sub_idx] = rv; + rv.Tkn_grp_(this, sub_idx); + return rv; + } + public void Src_end_grp_(Xop_ctx ctx, Xop_tkn_grp grp, int sub_idx, int src_end) { + Xop_tkn_itm tkn = this; + if (immutable) tkn = grp.Immutable_clone(ctx, this, sub_idx); + tkn.Src_end_(src_end); + subs_pos_ary[(sub_idx * 2) + 1] = src_end; + } + public void Ignore_y_grp_(Xop_ctx ctx, Xop_tkn_grp grp, int sub_idx) { + Xop_tkn_itm tkn = this; + if (immutable) tkn = grp.Immutable_clone(ctx, this, sub_idx); + tkn.Ignore_y_(); + } + public void Subs_grp_(Xop_ctx ctx, Xop_tkn_itm tkn, Xop_tkn_grp grp, int sub_idx) { +// if (tkn.Tkn_immutable()) tkn = Subs_immutable_clone(ctx, tkn); +// tkn.Tkn_grp_(grp, sub_idx); + } + @gplx.Virtual public void Reset() { + src_bgn = src_end = tkn_sub_idx = -1; ignore = false; tmpl_static = false; + if (subs.length > Tkn_subs_max) { + subs = new Xop_tkn_itm[Tkn_subs_max]; + subs_max = Tkn_subs_max; + subs_pos_ary = new int[(Tkn_subs_max + 1) * 2]; + } + else { + for (int i = 0; i < subs_len; i++) + subs[i] = null; + } + subs_len = 0; + } + @gplx.Virtual public void Html__write(Bry_bfr bfr, Xoh_html_wtr wtr, Xowe_wiki wiki, Xoae_page page, Xop_ctx ctx, Xoh_wtr_ctx hctx, Xoh_html_wtr_cfg cfg, Xop_tkn_grp grp, int sub_idx, byte[] src) {throw Err_.new_unimplemented();} + public void Clear() { + src_bgn = src_end = tkn_sub_idx = -1; ignore = false; tmpl_static = false; + Subs_clear(); + } + @gplx.Virtual public void Tmpl_fmt(Xop_ctx ctx, byte[] src, Xot_fmtr fmtr) {fmtr.Reg_ary(ctx, src, tmpl_static, src_bgn, src_end, subs_len, subs);} + @gplx.Virtual public void Tmpl_compile(Xop_ctx ctx, byte[] src, Xot_compile_data prep_data) { + if (!ignore) tmpl_static = true; + for (int i = 0; i < subs_len; i++) + subs[i].Tmpl_compile(ctx, src, prep_data); + } boolean tmpl_static = false; + @gplx.Virtual public boolean Tmpl_evaluate(Xop_ctx ctx, byte[] src, Xot_invk caller, Bry_bfr bfr) { + if (tmpl_static) bfr.Add_mid(src, src_bgn, src_end); + for (int i = 0; i < subs_len; i++) + subs[i].Tmpl_evaluate(ctx, src, caller, bfr); + return true; + } + static final String GRP_KEY = "xowa.tkn_base"; + public static final int Tkn_subs_max = 16; +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_tkn_mkr.java b/400_xowa/src/gplx/xowa/parsers/Xop_tkn_mkr.java index a27517de8..8400445cb 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_tkn_mkr.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_tkn_mkr.java @@ -13,3 +13,161 @@ 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; import gplx.*; import gplx.xowa.*; +import gplx.langs.htmls.entitys.*; +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.vnts.*; import gplx.xowa.xtns.cites.*; +public class Xop_tkn_mkr { + public Xop_root_tkn Root(byte[] raw) {return new Xop_root_tkn().Root_src_(raw);} + public Xop_txt_tkn Txt(int bgn, int end) {return new Xop_txt_tkn(bgn, end);} + public Xop_space_tkn Space(Xop_tkn_grp grp, int bgn, int end) {Xop_space_tkn rv = new Xop_space_tkn(false, bgn, end); grp.Subs_src_pos_(grp.Subs_len(), bgn, end); return rv;} + public Xop_space_tkn Space_mutable(int bgn, int end) {return new Xop_space_tkn(false, bgn, end);} + public Xop_apos_tkn Apos(int bgn, int end + , int aposLen, int typ, int cmd, int lit_apos) {return new Xop_apos_tkn(bgn, end, aposLen, typ, cmd, lit_apos);} + public Xop_tkn_itm Amp_txt(int bgn, int end, Gfh_entity_itm itm) {return new Xop_amp_tkn_ent(bgn, end, itm);} + public Xop_tkn_itm Amp_num(int bgn, int end, int val_int, byte[] val_bry) {return new Xop_amp_tkn_num(bgn, end, val_int, val_bry);} + public Xop_tkn_itm Amp_num(int bgn, int end, int val_int) {return new Xop_amp_tkn_num(bgn, end, val_int, gplx.core.intls.Utf16_.Encode_int_to_bry(val_int));} + public Xop_nl_tkn NewLine(int bgn, int end, byte nl_typ, int nl_len) {return new Xop_nl_tkn(bgn, end, nl_typ, nl_len);} + public Xop_lnki_tkn Lnki(int bgn, int end) {return (Xop_lnki_tkn)new Xop_lnki_tkn().Tkn_ini_pos(false, bgn, end);} + public Xop_list_tkn List_bgn(int bgn, int end, byte listType, int symLen) {return Xop_list_tkn.bgn_(bgn, end, listType, symLen);} + public Xop_list_tkn List_end(int pos, byte listType) {return Xop_list_tkn.end_(pos, listType);} + public Xop_tkn_itm Pipe(int bgn, int end) {return new Xop_pipe_tkn(bgn, end);} + public Xop_tkn_itm Colon(int bgn, int end) {return new Xop_colon_tkn(bgn, end);} + public Xop_eq_tkn Eq(int bgn, int end) {return new Xop_eq_tkn(bgn, end, end - bgn);} + public Xop_eq_tkn Eq(int bgn, int end, int eq_len) {return new Xop_eq_tkn(bgn, end, eq_len);} + public Xot_invk_tkn Tmpl_invk(int bgn, int end) {return new Xot_invk_tkn(bgn, end);} + public Arg_nde_tkn ArgNde(int arg_idx, int bgn) {return new Arg_nde_tkn(arg_idx, bgn);} + public Arg_itm_tkn ArgItm(int bgn, int end) {return new Arg_itm_tkn_base(bgn, end);} + public Xop_xnde_tkn Xnde(int bgn, int end) {return (Xop_xnde_tkn)Xop_xnde_tkn.new_().Tkn_ini_pos(false, bgn, end);} + public Xop_hdr_tkn Hdr(int bgn, int end, int hdr_len) {return new Xop_hdr_tkn(bgn, end, hdr_len);} + public Xop_hr_tkn Hr(int bgn, int end, int hr_len) {return new Xop_hr_tkn(bgn, end, hr_len);} + public Xop_tab_tkn Tab(int bgn, int end) {return new Xop_tab_tkn(bgn, end);} + public Xop_curly_bgn_tkn Tmpl_curly_bgn(int bgn, int end) {return new Xop_curly_bgn_tkn(bgn, end);} + public Xop_tkn_itm Brack_bgn(int bgn, int end) {return new Xop_brack_bgn_tkn(bgn, end);} + public Xop_tkn_itm Brack_end(int bgn, int end) {return new Xop_brack_end_tkn(bgn, end);} + public Xop_lnke_tkn Lnke(int bgn, int end, byte[] protocol, byte proto_tid, byte lnke_typ, int lnk_bgn, int lnk_end) { + return new Xop_lnke_tkn(bgn, end, protocol, proto_tid, lnke_typ, lnk_bgn, lnk_end); + } + public Xop_tblw_tb_tkn Tblw_tb(int bgn, int end, boolean tblw_xml, boolean auto_created) {return new Xop_tblw_tb_tkn(bgn, end, tblw_xml, auto_created);} + public Xop_tblw_tr_tkn Tblw_tr(int bgn, int end, boolean tblw_xml, boolean auto_created) {return new Xop_tblw_tr_tkn(bgn, end, tblw_xml, auto_created);} + public Xop_tblw_td_tkn Tblw_td(int bgn, int end, boolean tblw_xml) {return new Xop_tblw_td_tkn(bgn, end, tblw_xml);} + public Xop_tblw_th_tkn Tblw_th(int bgn, int end, boolean tblw_xml) {return new Xop_tblw_th_tkn(bgn, end, tblw_xml);} + public Xop_tblw_tc_tkn Tblw_tc(int bgn, int end, boolean tblw_xml) {return new Xop_tblw_tc_tkn(bgn, end, tblw_xml);} + public Xot_prm_tkn Tmpl_prm(int bgn, int end) {return new Xot_prm_tkn(bgn, end);} + public Xop_para_tkn Para(int pos) {return new Xop_para_tkn(pos);} + public Xop_pre_tkn Para_pre_bgn(int pos) {return new Xop_pre_tkn(pos, pos, Xop_pre_tkn.Pre_tid_bgn, null);} + public Xop_pre_tkn Para_pre_end(int pos, Xop_tkn_itm bgn) {return new Xop_pre_tkn(pos, pos, Xop_pre_tkn.Pre_tid_end, bgn);} + public Xop_ignore_tkn Ignore(int bgn, int end, byte ignore_type) {return new Xop_ignore_tkn(bgn, end, ignore_type);} + public Xop_bry_tkn Bry_raw(int bgn, int end, byte[] bry) {return new Xop_bry_tkn(bgn, end, bry);} + public Xop_bry_tkn Bry_mid(byte[] src, int bgn, int end) {return new Xop_bry_tkn(bgn, end, Bry_.Mid(src, bgn, end));} + public Xop_under_tkn Under(int bgn, int end, int v) {return new Xop_under_tkn(bgn, end, v);} + public gplx.xowa.xtns.xowa_cmds.Xop_xowa_cmd Xnde__xowa_cmd() {return new gplx.xowa.xtns.xowa_cmds.Xop_xowa_cmd();} + public gplx.xowa.xtns.poems.Poem_nde Xnde__poem() {return new gplx.xowa.xtns.poems.Poem_nde();} + public Ref_nde Xnde__ref() {return new Ref_nde();} + public References_nde Xnde__references() {return new References_nde();} + public gplx.xowa.xtns.math.Xomath_xnde Xnde__math() {return new gplx.xowa.xtns.math.Xomath_xnde();} + public gplx.xowa.xtns.gallery.Gallery_xnde Xnde__gallery() {return new gplx.xowa.xtns.gallery.Gallery_xnde();} + public gplx.xowa.xtns.imaps.Imap_xnde Xnde__imageMap() {return new gplx.xowa.xtns.imaps.Imap_xnde();} + public gplx.xowa.xtns.hieros.Hiero_xnde Xnde__hiero() {return new gplx.xowa.xtns.hieros.Hiero_xnde();} + public gplx.xowa.xtns.graphs.Graph_xnde Xnde__graph() {return new gplx.xowa.xtns.graphs.Graph_xnde();} + public gplx.xowa.xtns.kartographers.Mapframe_xnde Xnde__mapframe() {return new gplx.xowa.xtns.kartographers.Mapframe_xnde();} + public gplx.xowa.xtns.kartographers.Maplink_xnde Xnde__maplink() {return new gplx.xowa.xtns.kartographers.Maplink_xnde();} + public gplx.xowa.xtns.proofreadPage.Pp_pages_nde Xnde__pages() {return new gplx.xowa.xtns.proofreadPage.Pp_pages_nde();} + public gplx.xowa.xtns.proofreadPage.Pp_pagelist_nde Xnde__pagelist() {return new gplx.xowa.xtns.proofreadPage.Pp_pagelist_nde();} + public gplx.xowa.xtns.proofreadPage.Pp_pagequality_nde Xnde__pagequality() {return new gplx.xowa.xtns.proofreadPage.Pp_pagequality_nde();} + public gplx.xowa.xtns.lst.Lst_section_nde Xnde__section() {return new gplx.xowa.xtns.lst.Lst_section_nde();} + public gplx.xowa.xtns.categoryList.Xtn_categorylist_nde Xnde__categoryList() {return new gplx.xowa.xtns.categoryList.Xtn_categorylist_nde();} + public gplx.xowa.xtns.dynamicPageList.Dpl_xnde Xnde__dynamicPageList() {return new gplx.xowa.xtns.dynamicPageList.Dpl_xnde();} + public gplx.xowa.xtns.syntax_highlights.Synh_xtn_nde Xnde__syntaxHighlight() {return new gplx.xowa.xtns.syntax_highlights.Synh_xtn_nde();} + public gplx.xowa.xtns.templateData.Xtn_templateData_nde Xnde__templateData() {return new gplx.xowa.xtns.templateData.Xtn_templateData_nde();} + public gplx.xowa.xtns.rss.Rss_xnde Xnde__rss() {return new gplx.xowa.xtns.rss.Rss_xnde();} + public gplx.xowa.xtns.quiz.Quiz_xnde Xnde__quiz() {return new gplx.xowa.xtns.quiz.Quiz_xnde();} + public gplx.xowa.xtns.indicators.Indicator_xnde Xnde__indicator() {return new gplx.xowa.xtns.indicators.Indicator_xnde();} + public gplx.xowa.xtns.xowa_cmds.Xox_xowa_html_cmd Xnde__xowa_html() {return new gplx.xowa.xtns.xowa_cmds.Xox_xowa_html_cmd();} + public gplx.xowa.xtns.xowa_cmds.wiki_setups.Xop_wiki_setup_xnde Xnde__xowa_wiki_setup() {return new gplx.xowa.xtns.xowa_cmds.wiki_setups.Xop_wiki_setup_xnde();} + public gplx.xowa.xtns.listings.Listing_xnde Xnde__listing(int tag_id) {return new gplx.xowa.xtns.listings.Listing_xnde(tag_id);} + public gplx.xowa.xtns.scores.Score_xnde Xnde__score() {return new gplx.xowa.xtns.scores.Score_xnde();} + public gplx.xowa.xtns.inputBox.Xtn_inputbox_nde Xnde__inputbox() {return new gplx.xowa.xtns.inputBox.Xtn_inputbox_nde();} + public gplx.xowa.xtns.translates.Xop_translate_xnde Xnde__translate() {return new gplx.xowa.xtns.translates.Xop_translate_xnde();} + public gplx.xowa.xtns.translates.Xop_languages_xnde Xnde__languages() {return new gplx.xowa.xtns.translates.Xop_languages_xnde();} + public gplx.xowa.xtns.wikias.Random_selection_xnde Xnde__random_selection() {return new gplx.xowa.xtns.wikias.Random_selection_xnde();} + public gplx.xowa.xtns.wikias.Tabber_xnde Xnde__tabber() {return new gplx.xowa.xtns.wikias.Tabber_xnde();} + public gplx.xowa.xtns.wikias.Tabview_xnde Xnde__tabview() {return new gplx.xowa.xtns.wikias.Tabview_xnde();} + + public gplx.xowa.xtns.translates.Xop_tvar_tkn Tvar(int tkn_bgn, int tkn_end, int key_bgn, int key_end, int txt_bgn, int txt_end, byte[] wikitext) {return new gplx.xowa.xtns.translates.Xop_tvar_tkn(tkn_bgn, tkn_end, key_bgn, key_end, txt_bgn, txt_end, wikitext);} +// public void Clear() { +// space_tkns_len = txt_tkns_len = 0; +// } +// public Xop_txt_tkn Txt(int bgn, int end) { +// Xop_txt_tkn rv = null; +// if (txt_tkns_len < txt_tkns_max) { +// rv = txt_tkns[txt_tkns_len]; +// if (rv == null) { +// rv = new Xop_txt_tkn(bgn, end); +// txt_tkns[txt_tkns_len] = rv; +// } +// else { +// rv.Reset(); +// rv.Src_rng_(bgn, end); +// } +// txt_tkns_len++; +// } +// else { +// rv = new Xop_txt_tkn(bgn, end); +// Txt_tkns_add(rv); +// } +// return rv; +//// return new Xop_txt_tkn(bgn, end); +// } +// public Xop_space_tkn Space(int bgn, int end) { +// Xop_space_tkn rv = null; +// if (space_tkns_len < space_tkns_max) { +// rv = space_tkns[space_tkns_len]; +// if (rv == null) { +// rv = new Xop_space_tkn(bgn, end); +// space_tkns[space_tkns_len] = rv; +// } +// else { +// rv.Reset(); +// rv.Src_rng_(bgn, end); +// } +// space_tkns_len++; +// } +// else { +// rv = new Xop_space_tkn(bgn, end); +// Space_tkns_add(rv); +// } +// return rv; +//// return new Xop_space_tkn(bgn, end); +// } +// private void Txt_tkns_add(Xop_txt_tkn sub) { +// int new_len = txt_tkns_len + 1; +// if (new_len > txt_tkns_max) { +// txt_tkns_max = new_len * 2; +// txt_tkns = Resize(txt_tkns, txt_tkns_len, txt_tkns_max); +// } +// txt_tkns[txt_tkns_len] = sub; +// txt_tkns_len = new_len; +// } private Xop_txt_tkn[] txt_tkns = new Xop_txt_tkn[0]; int txt_tkns_len, txt_tkns_max; +// Xop_txt_tkn[] Resize(Xop_txt_tkn[] src, int cur_len, int new_len) { +// Xop_txt_tkn[] rv = new Xop_txt_tkn[new_len]; +// for (int i = 0; i < cur_len; i++) +// rv[i] = src[i]; +// return rv; +// } +// private void Space_tkns_add(Xop_space_tkn sub) { +// int new_len = space_tkns_len + 1; +// if (new_len > space_tkns_max) { +// space_tkns_max = new_len * 2; +// space_tkns = Resize(space_tkns, space_tkns_len, space_tkns_max); +// } +// space_tkns[space_tkns_len] = sub; +// space_tkns_len = new_len; +// } private Xop_space_tkn[] space_tkns = new Xop_space_tkn[0]; int space_tkns_len, space_tkns_max; +// Xop_space_tkn[] Resize(Xop_space_tkn[] src, int cur_len, int new_len) { +// Xop_space_tkn[] rv = new Xop_space_tkn[new_len]; +// for (int i = 0; i < cur_len; i++) +// rv[i] = src[i]; +// return rv; +// } +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_tkn_null.java b/400_xowa/src/gplx/xowa/parsers/Xop_tkn_null.java index a27517de8..4faf445c5 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_tkn_null.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_tkn_null.java @@ -13,3 +13,41 @@ 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; import gplx.*; import gplx.xowa.*; +import gplx.xowa.parsers.tmpls.*; +import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.htmls.*; +public class Xop_tkn_null implements Xop_tkn_itm { + public byte Tkn_tid() {return Xop_tkn_itm_.Tid_null;} + public boolean Tkn_immutable() {return true;} + public Xop_tkn_grp Tkn_grp() {return Xop_tkn_grp_.Null;} + public Xop_tkn_itm Tkn_ini_pos(boolean immutable, int bgn, int end) {return this;} + public Xop_tkn_itm Tkn_grp_(Xop_tkn_grp grp, int sub_idx) {return this;} + public Xop_tkn_itm Tkn_clone(Xop_ctx ctx, int bgn, int end) {return this;} + public int Tkn_sub_idx() {return -1;} + public int Src_bgn() {return -1;} + public int Src_end() {return -1;} + public int Src_bgn_grp(Xop_tkn_grp grp, int sub_idx) {return -1;} + public int Src_end_grp(Xop_tkn_grp grp, int sub_idx) {return -1;} + public int Subs_src_bgn(int sub_idx) {return -1;} + public int Subs_src_end(int sub_idx) {return -1;} + public void Src_end_(int v) {} + public void Src_end_grp_(Xop_ctx ctx, Xop_tkn_grp grp, int sub_idx, int src_end) {} + public boolean Ignore() {return false;} public Xop_tkn_itm Ignore_y_() {return this;} + public int Subs_len() {return 0;} + public Xop_tkn_itm Subs_get(int i) {return null;} + public void Subs_add(Xop_tkn_itm sub) {} + public void Subs_add_grp(Xop_tkn_itm sub, Xop_tkn_grp old_grp, int old_sub_idx) {} + public void Subs_del_after(int pos_bgn) {} + public void Subs_clear() {} + public void Subs_move(Xop_tkn_itm tkn) {} + public Xop_tkn_itm Immutable_clone(Xop_ctx ctx, Xop_tkn_itm tkn, int sub_idx) {return this;} + public void Ignore_y_grp_(Xop_ctx ctx, Xop_tkn_grp grp, int sub_idx) {} + public void Subs_grp_(Xop_ctx ctx, Xop_tkn_itm tkn, Xop_tkn_grp grp, int sub_idx) {} + public void Subs_src_pos_(int sub_idx, int bgn, int end) {} + public void Clear() {} + public void Tmpl_fmt(Xop_ctx ctx, byte[] src, Xot_fmtr fmtr) {} + public void Tmpl_compile(Xop_ctx ctx, byte[] src, Xot_compile_data prep_data) {} + public boolean Tmpl_evaluate(Xop_ctx ctx, byte[] src, Xot_invk caller, Bry_bfr bfr) {return true;} + public void Html__write(Bry_bfr bfr, Xoh_html_wtr wtr, Xowe_wiki wiki, Xoae_page page, Xop_ctx ctx, Xoh_wtr_ctx hctx, Xoh_html_wtr_cfg cfg, Xop_tkn_grp grp, int sub_idx, byte[] src) {} + public static final Xop_tkn_null Null_tkn = new Xop_tkn_null(); +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_tmp_mgr.java b/400_xowa/src/gplx/xowa/parsers/Xop_tmp_mgr.java index a27517de8..d954dd790 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_tmp_mgr.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_tmp_mgr.java @@ -13,3 +13,16 @@ 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; import gplx.*; import gplx.xowa.*; +import gplx.core.primitives.*; import gplx.core.btries.*; +import gplx.xowa.files.*; +import gplx.xowa.xtns.pfuncs.exprs.*; import gplx.xowa.xtns.pfuncs.ttls.*; +public class Xop_tmp_mgr { + public Xof_xfer_itm Xfer_itm() {return xfer_itm;} private final Xof_xfer_itm xfer_itm = new Xof_xfer_itm(); + public Gfo_number_parser Pfunc_num_parser_0() {return num_parser_0;} private final Gfo_number_parser num_parser_0 = new Gfo_number_parser().Hex_enabled_(true); + public Gfo_number_parser Pfunc_num_parser_1() {return num_parser_1;} private final Gfo_number_parser num_parser_1 = new Gfo_number_parser().Hex_enabled_(true); + public Pfunc_expr_shunter Expr_shunter() {return expr_shunter;} private final Pfunc_expr_shunter expr_shunter = new Pfunc_expr_shunter(); + public Btrie_slim_mgr Xnde__xtn_end() {return xnde__xtn_end;} private final Btrie_slim_mgr xnde__xtn_end = Btrie_slim_mgr.ci_a7(); // NOTE:ci.ascii:MW_const.en; listed XML node names are en + public Btrie_rv Xnde__trv() {return xnde__trv;} private final Btrie_rv xnde__trv = new Btrie_rv(); + public Int_obj_ref Pfunc_rel2abs() {return pfunc_rel2abs;} private final Int_obj_ref pfunc_rel2abs = Int_obj_ref.New_zero(); +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_txt_tkn.java b/400_xowa/src/gplx/xowa/parsers/Xop_txt_tkn.java index a27517de8..c7749ca42 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xop_txt_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/Xop_txt_tkn.java @@ -13,3 +13,20 @@ 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; import gplx.*; import gplx.xowa.*; +public class Xop_txt_tkn extends Xop_tkn_itm_base { + public Xop_txt_tkn(int bgn, int end) {this.Tkn_ini_pos(false, bgn, end);} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_txt;} +} +class Xop_colon_tkn extends Xop_tkn_itm_base { + public Xop_colon_tkn(int bgn, int end) {this.Tkn_ini_pos(false, bgn, end);} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_colon;} +} +class Xop_brack_bgn_tkn extends Xop_tkn_itm_base { + public Xop_brack_bgn_tkn(int bgn, int end) {this.Tkn_ini_pos(false, bgn, end);} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_brack_bgn;} +} +class Xop_brack_end_tkn extends Xop_tkn_itm_base { + public Xop_brack_end_tkn(int bgn, int end) {this.Tkn_ini_pos(false, bgn, end);} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_brack_end;} +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xow_mw_parser_mgr.java b/400_xowa/src/gplx/xowa/parsers/Xow_mw_parser_mgr.java index a27517de8..e6b114d1e 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xow_mw_parser_mgr.java +++ b/400_xowa/src/gplx/xowa/parsers/Xow_mw_parser_mgr.java @@ -13,3 +13,8 @@ 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; import gplx.*; import gplx.xowa.*; +import gplx.xowa.parsers.xndes.*; +public class Xow_mw_parser_mgr { + public Xop_xnde_tag_regy Xnde_tag_regy() {return xnde_tag_regy;} private final Xop_xnde_tag_regy xnde_tag_regy = new Xop_xnde_tag_regy(); +} diff --git a/400_xowa/src/gplx/xowa/parsers/Xow_parser_mgr.java b/400_xowa/src/gplx/xowa/parsers/Xow_parser_mgr.java index a27517de8..3bfa27206 100644 --- a/400_xowa/src/gplx/xowa/parsers/Xow_parser_mgr.java +++ b/400_xowa/src/gplx/xowa/parsers/Xow_parser_mgr.java @@ -13,3 +13,93 @@ 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; import gplx.*; import gplx.xowa.*; +import gplx.core.primitives.*; import gplx.core.brys.fmtrs.*; +import gplx.xowa.wikis.*; import gplx.core.envs.*; +import gplx.xowa.files.*; +import gplx.xowa.xtns.scribunto.*; import gplx.xowa.xtns.wbases.hwtrs.*; import gplx.xowa.xtns.pfuncs.ifs.*; import gplx.xowa.xtns.pfuncs.times.*; import gplx.xowa.xtns.pfuncs.ttls.*; +import gplx.xowa.xtns.math.*; import gplx.xowa.parsers.uniqs.*; import gplx.xowa.parsers.hdrs.sections.*; +public class Xow_parser_mgr { + private final Xowe_wiki wiki; private final Xop_tkn_mkr tkn_mkr; + public Xow_parser_mgr(Xowe_wiki wiki) { + this.wiki = wiki; this.tkn_mkr = wiki.Appe().Parser_mgr().Tkn_mkr(); + this.ctx = Xop_ctx.New__top(wiki); + this.parser = Xop_parser.new_wiki(wiki); + } + public Xop_ctx Ctx() {return ctx;} private final Xop_ctx ctx; + public Xop_parser Main() {return parser;} private final Xop_parser parser; + public Scrib_core_mgr Scrib() {return scrib;} private final Scrib_core_mgr scrib = new Scrib_core_mgr(); + public Xof_img_size Img_size() {return img_size;} private final Xof_img_size img_size = new Xof_img_size(); + public Pfunc_ifexist_mgr Ifexist_mgr() {return ifexist_mgr;} private final Pfunc_ifexist_mgr ifexist_mgr = new Pfunc_ifexist_mgr(); + public Xof_url_bldr Url_bldr() {return url_bldr;} private final Xof_url_bldr url_bldr = Xof_url_bldr.new_v2(); + public List_adp Time_parser_itms() {return time_parser_itms;} private final List_adp time_parser_itms = List_adp_.New(); + public Pft_func_formatdate_bldr Date_fmt_bldr() {return date_fmt_bldr;} private final Pft_func_formatdate_bldr date_fmt_bldr = new Pft_func_formatdate_bldr(); + public Gfo_number_parser Pp_num_parser() {return pp_num_parser;} private final Gfo_number_parser pp_num_parser = new Gfo_number_parser().Ignore_space_at_end_y_(); + public int[] Rel2abs_ary() {return rel2abs_ary;} private final int[] rel2abs_ary = new int[Pfunc_rel2abs.Ttl_max]; + public Xop_uniq_mgr Uniq_mgr() {return uniq_mgr;} private final Xop_uniq_mgr uniq_mgr = new Xop_uniq_mgr(); + public Xomath_core Math__core() {return math__core;} private final Xomath_core math__core = new Xomath_core(); + public boolean Lst__recursing() {return lst_recursing;} private boolean lst_recursing; public void Lst__recursing_(boolean v) {lst_recursing = v;} + public Bry_bfr Wbase__time__bfr() {return wbase__time__bfr;} private final Bry_bfr wbase__time__bfr = Bry_bfr_.New(); + public Bry_fmtr Wbase__time__fmtr() {return wbase__time__fmtr;} private final Bry_fmtr wbase__time__fmtr = Bry_fmtr.new_(); + public Xop_section_mgr Hdr__section_editable__mgr() {return hdr__section_editable__mgr;} private final Xop_section_mgr hdr__section_editable__mgr = new Xop_section_mgr(); + public Wdata_hwtr_msgs Wbase__time__msgs() { + if (wbase__time__msgs == null) + wbase__time__msgs = Wdata_hwtr_msgs.new_(wiki.Msg_mgr()); + return wbase__time__msgs; + } private Wdata_hwtr_msgs wbase__time__msgs; + public Bry_bfr Tmp_bfr() {return tmp_bfr;} private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + public int Tag__next_idx() {return ++tag_idx;} private int tag_idx; // NOTE:must be wiki-level variable, not page-level, b/c pre-compiled templates can reserve tag #s; PAGE:de.s:Seite:NewtonPrincipien.djvu/465 DATE:2015-02-03 + public void Tmpl_stack_del() {--tmpl_stack_ary_len;} + public boolean Tmpl_stack_add(byte[] key) { + for (int i = 0; i < tmpl_stack_ary_len; i++) { + if (Bry_.Match(key, tmpl_stack_ary[i])) return false; + } + int new_len = tmpl_stack_ary_len + 1; + if (new_len > tmpl_stack_ary_max) { + tmpl_stack_ary_max = new_len * 2; + tmpl_stack_ary = (byte[][])Array_.Resize(tmpl_stack_ary, tmpl_stack_ary_max); + } + tmpl_stack_ary[tmpl_stack_ary_len] = key; + tmpl_stack_ary_len = new_len; + return true; + } private byte[][] tmpl_stack_ary = Bry_.Ary_empty; private int tmpl_stack_ary_len = 0, tmpl_stack_ary_max = 0; + public Pfunc_anchorencode_mgr Anchor_encoder_mgr__dflt_or_new(Xop_ctx calling_ctx) { + // lazy-instantiate anchor_encoder_mgr + if (anchor_encoder_mgr == null) anchor_encoder_mgr = new Pfunc_anchorencode_mgr(wiki); + + // default to member instance + Pfunc_anchorencode_mgr rv = anchor_encoder_mgr; + // if used, create a new one; only occurs if {{anchorencode}} is nested + if (rv.Used()) rv = new Pfunc_anchorencode_mgr(wiki); + rv.Used_(Bool_.Y); + return rv; + } private Pfunc_anchorencode_mgr anchor_encoder_mgr; + public void Init_by_wiki() { + math__core.Init_by_wiki(wiki); + hdr__section_editable__mgr.Init_by_wiki(wiki); + } + public void Parse(Xoae_page page, boolean clear) { // main parse method; should never be called nested + // init + if (!Env_.Mode_testing()) wiki.Init_assert(); // needed for html_server? + tmpl_stack_ary = Bry_.Ary_empty; + tmpl_stack_ary_len = tmpl_stack_ary_max = 0; + uniq_mgr.Clear(); + + scrib.When_page_changed(page); // notify scribunto about page changed + ctx.Page_(page); + ctx.Page_data().Clear(); // clear ctx since it gets reused between pages; PAGE:de.w:13._Jahrhundert DATE:2017-06-17 + Xop_root_tkn root = ctx.Tkn_mkr().Root(page.Db().Text().Text_bry()); + if (clear) { + page.Clear_all(); + } + Xoa_ttl ttl = page.Ttl(); + if ( Xow_page_tid.Identify(wiki.Domain_tid(), ttl.Ns().Id(), ttl.Page_db()) == Xow_page_tid.Tid_wikitext) { // only parse page if wikitext; skip .js, .css, Module; DATE:2013-11-10 + byte[] data_raw = page.Db().Text().Text_bry(); + parser.Parse_text_to_wdom(root, ctx, tkn_mkr, data_raw , Xop_parser_.Doc_bgn_bos); + } + page.Root_(root); + root.Data_htm_(root.Root_src()); + + ctx.Page_data().Copy_to(page); // copy __TOC__ from ctx to page; needed to prevent template from affecting page output; DATE:2017-06-17 + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_lxr.java b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_lxr.java index a27517de8..75b742335 100644 --- a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_lxr.java @@ -13,3 +13,15 @@ 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.amps; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +public class Xop_amp_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_amp;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Byte_ascii.Amp, 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) { + return ctx.Amp().Make_tkn(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos); + } + public static final Xop_amp_lxr Instance = new Xop_amp_lxr(); +} diff --git a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_mgr.java b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_mgr.java index a27517de8..68577f114 100644 --- a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_mgr.java +++ b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_mgr.java @@ -13,3 +13,143 @@ 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.amps; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; +import gplx.langs.htmls.entitys.*; +public class Xop_amp_mgr { // TS + private static final Btrie_rv trv = new Btrie_rv(); + public Btrie_slim_mgr Amp_trie() {return amp_trie;} private final Btrie_slim_mgr amp_trie = Gfh_entity_trie.Instance; + public Xop_amp_mgr_rslt Parse_tkn(Xop_tkn_mkr tkn_mkr, byte[] src, int src_len, int amp_pos, int bgn) { + int fail_pos = amp_pos + 1; // default to fail pos which is after & + + // check amp_trie; EX: 'lt' + Xop_amp_mgr_rslt rv = new Xop_amp_mgr_rslt(); + Gfh_entity_itm itm; int cur; + synchronized (trv) { + itm = (Gfh_entity_itm)amp_trie.Match_at(trv, src, bgn, src_len); + cur = trv.Pos(); + } + + if (itm == null) { + rv.Pass_n_(fail_pos); + return rv; + } + + // check itm + switch (itm.Tid()) { + // letters; EX: '<' + case Gfh_entity_itm.Tid_name_std: + case Gfh_entity_itm.Tid_name_xowa: + rv.Pos_(cur); + rv.Tkn_(tkn_mkr.Amp_txt(amp_pos, cur, itm)); + return rv; + // numbers; EX: '{' 'ģ' + case Gfh_entity_itm.Tid_num_hex: + case Gfh_entity_itm.Tid_num_dec: + boolean ncr_is_hex = itm.Tid() == Gfh_entity_itm.Tid_num_hex; + boolean pass = Parse_ncr(rv, ncr_is_hex, src, src_len, amp_pos, cur); + if (pass) { // NOTE: do not set rv.Pos_(); will be set by Parse_ncr + rv.Tkn_(tkn_mkr.Amp_num(amp_pos, rv.Pos(), rv.Val())); + return rv; + } + else { + rv.Pass_n_(fail_pos); + return rv; + } + default: throw Err_.new_unhandled_default(itm.Tid()); + } + } + public boolean Parse_ncr(Xop_amp_mgr_rslt rv, boolean ncr_is_hex, byte[] src, int src_len, int amp_pos, int num_bgn) { + int fail_pos = amp_pos + 1; // default to fail pos; after amp; + + // find semic; fail if none found + int semic_pos = Bry_find_.Find_fwd(src, Byte_ascii.Semic, num_bgn, src_len); + if (semic_pos == Bry_find_.Not_found) return rv.Pass_n_(fail_pos); + int num_end = semic_pos - 1; // num_end = pos before semicolon + + // calc amp_val; EX: Σ -> 931; Σ -> 931; + int multiple = ncr_is_hex ? 16 : 10, val = 0, factor = 1, cur = 0; + for (int i = num_end; i >= num_bgn; i--) { + byte b = src[i]; + if (ncr_is_hex) { + if (b >= 48 && b <= 57) cur = b - 48; + else if (b >= 65 && b <= 70) cur = b - 55; + else if (b >= 97 && b <= 102) cur = b - 87; + else if((b >= 71 && b <= 90) + || (b >= 91 && b <= 122)) continue; // NOTE: wiki discards letters G-Z; PAGE:en.w:Miscellaneous_Symbols "{{Unicode|&#xx26D0;}}"; NOTE 2nd x is discarded + else return rv.Pass_n_(fail_pos); + } + else { + cur = b - Byte_ascii.Num_0; + if (cur < 0 || cur > 10) return rv.Pass_n_(fail_pos); + } + val += cur * factor; + if (val > gplx.core.intls.Utf8_.Codepoint_max) return rv.Pass_n_(fail_pos); // fail if value > largest_unicode_codepoint + factor *= multiple; + } + return rv.Pass_y_(semic_pos + 1, val); // +1 to position after semic + } + public byte[] Decode_as_bry(byte[] src) { + if (src == null) return src; + boolean dirty = false; + int end = src.length; + int pos = 0; + Xop_amp_mgr_rslt amp_rv = null; + Bry_bfr bfr = null; + Btrie_rv trv = null; + + // scan for & + while (pos < end) { + byte b = src[pos]; + if (b == Byte_ascii.Amp) { // & found + int nxt_pos = pos + 1; + if (nxt_pos < end) { // check & is not eos + byte nxt_b = src[nxt_pos]; + + if (trv == null) trv = new Btrie_rv(); + Object amp_obj = amp_trie.Match_at_w_b0(trv, nxt_b, src, nxt_pos, end); + int amp_pos = trv.Pos(); + + if (amp_obj != null) { + if (!dirty) { // 1st amp found; add preceding String to bfr + if (bfr == null) { + bfr = Bry_bfr_.Get(); + dirty = true; + } + bfr.Add_mid(src, 0, pos); + } + Gfh_entity_itm amp_itm = (Gfh_entity_itm)amp_obj; + switch (amp_itm.Tid()) { + case Gfh_entity_itm.Tid_name_std: + case Gfh_entity_itm.Tid_name_xowa: + bfr.Add(amp_itm.U8_bry()); + pos = amp_pos; + break; + case Gfh_entity_itm.Tid_num_hex: + case Gfh_entity_itm.Tid_num_dec: + boolean ncr_is_hex = amp_itm.Tid() == Gfh_entity_itm.Tid_num_hex; + int int_bgn = amp_pos; + if (amp_rv == null) + amp_rv = new Xop_amp_mgr_rslt(); + boolean pass = Parse_ncr(amp_rv, ncr_is_hex, src, end, pos, int_bgn); + if (pass) + bfr.Add_u8_int(amp_rv.Val()); + else + bfr.Add_mid(src, pos, nxt_pos); + pos = amp_rv.Pos(); + break; + default: + throw Err_.new_unhandled_default(amp_itm.Tid()); + } + continue; + } + } + } + if (dirty) + bfr.Add_byte(b); + ++pos; + } + return dirty ? bfr.To_bry_and_clear_and_rls() : src; + } + public static final Xop_amp_mgr Instance = new Xop_amp_mgr(); Xop_amp_mgr() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_mgr__decode__tst.java b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_mgr__decode__tst.java index a27517de8..c409ac651 100644 --- a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_mgr__decode__tst.java +++ b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_mgr__decode__tst.java @@ -13,3 +13,50 @@ 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.amps; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.core.tests.*; +public class Xop_amp_mgr__decode__tst { + @Before public void init() {} private final Xop_amp_mgr_fxt fxt = new Xop_amp_mgr_fxt(); + @Test public void Text() {fxt.Test__decode_as_bry("a" , "a");} + @Test public void Name() {fxt.Test__decode_as_bry("&" , "&");} + @Test public void Name_w_text() {fxt.Test__decode_as_bry("a&b" , "a&b");} + @Test public void Name_fail_semic_missing() {fxt.Test__decode_as_bry("a&b" , "a&b");} + @Test public void Name_fail_amp_only() {fxt.Test__decode_as_bry("a&" , "a&");} + @Test public void Num_fail() {fxt.Test__decode_as_bry("&#!;" , "&#!;");} // ! is not valid num + @Test public void Hex_fail() {fxt.Test__decode_as_bry("&#x!;" , "&#x!;");} // ! is not valid hex + @Test public void Num_basic() {fxt.Test__decode_as_bry("Σ" , "Σ");} + @Test public void Num_zero_padded() {fxt.Test__decode_as_bry("Σ" , "Σ");} + @Test public void Hex_upper() {fxt.Test__decode_as_bry("Σ" , "Σ");} + @Test public void Hex_lower() {fxt.Test__decode_as_bry("Σ" , "Σ");} + @Test public void Hex_zero_padded() {fxt.Test__decode_as_bry("Σ" , "Σ");} + @Test public void Hex_upper_x() {fxt.Test__decode_as_bry("Σ" , "Σ");} + @Test public void Num_fail_large_codepoint() {fxt.Test__decode_as_bry("�" , "�");} + @Test public void Num_ignore_extra_x() {fxt.Test__decode_as_bry("&#xx26D0;" , Char_.To_str(Char_.By_int(9936)));} // 2nd x is ignored +} +class Xop_amp_mgr_fxt { + private final Xop_amp_mgr amp_mgr = Xop_amp_mgr.Instance; + public void Test__decode_as_bry(String raw, String expd) { + Gftest.Eq__str(expd, String_.new_u8(amp_mgr.Decode_as_bry(Bry_.new_u8(raw)))); + } + public void Test__parse_tkn__ent(String raw, String expd) { + Xop_amp_mgr_rslt rv = Exec__parse_tkn(raw); + Xop_amp_tkn_ent tkn = (Xop_amp_tkn_ent)rv.Tkn(); + Gftest.Eq__byte(Xop_tkn_itm_.Tid_html_ref, tkn.Tkn_tid()); + Gftest.Eq__str(expd, tkn.Xml_name_bry()); + } + public void Test__parse_tkn__ncr(String raw, int expd) { + Xop_amp_mgr_rslt rv = Exec__parse_tkn(raw); + Xop_amp_tkn_num tkn = (Xop_amp_tkn_num)rv.Tkn(); + Gftest.Eq__byte(Xop_tkn_itm_.Tid_html_ncr, tkn.Tkn_tid()); + Gftest.Eq__int(expd, tkn.Val()); + } + public void Test__parse_tkn__txt(String raw, int expd) { + Xop_amp_mgr_rslt rv = Exec__parse_tkn(raw); + Gftest.Eq__null(Bool_.Y, rv.Tkn()); + Gftest.Eq__int(expd, rv.Pos()); + } + private Xop_amp_mgr_rslt Exec__parse_tkn(String raw) { + byte[] src = Bry_.new_u8(raw); + return amp_mgr.Parse_tkn(new Xop_tkn_mkr(), src, src.length, 0, 1); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_mgr__parse_tkn__tst.java b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_mgr__parse_tkn__tst.java index a27517de8..09eba83e7 100644 --- a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_mgr__parse_tkn__tst.java +++ b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_mgr__parse_tkn__tst.java @@ -13,3 +13,13 @@ 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.amps; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.core.tests.*; +public class Xop_amp_mgr__parse_tkn__tst { + @Before public void init() {} private final Xop_amp_mgr_fxt fxt = new Xop_amp_mgr_fxt(); + @Test public void Ent() {fxt.Test__parse_tkn__ent("&" , "&");} // check for html_ref + @Test public void Ent__fail() {fxt.Test__parse_tkn__txt("&nil;" , 1);} + @Test public void Num__nex() {fxt.Test__parse_tkn__ncr("Σ" , 931);} // check for html_ncr; Σ: http://en.wikipedia.org/wiki/Numeric_character_reference + @Test public void Num__dec() {fxt.Test__parse_tkn__ncr("Σ" , 931);} + @Test public void Num__fail() {fxt.Test__parse_tkn__txt("&#" , 1);} +} diff --git a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_mgr_rslt.java b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_mgr_rslt.java index a27517de8..ed45c13f2 100644 --- a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_mgr_rslt.java +++ b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_mgr_rslt.java @@ -13,3 +13,28 @@ 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.amps; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_amp_mgr_rslt { + public Xop_amp_mgr_rslt(int pos, int val, Xop_tkn_itm tkn) { + this.pos = pos; + this.val = val; + this.tkn = tkn; + } + public Xop_amp_mgr_rslt() {} + public boolean Pass() {return pass;} private boolean pass; public void Valid_(boolean v) {this.pass = v;} + public int Pos() {return pos;} private int pos; public void Pos_(int v) {this.pos = v;} + public int Val() {return val;} private int val; public void Val_(int v) {this.val = v;} + public Xop_tkn_itm Tkn() {return tkn;} private Xop_tkn_itm tkn; public void Tkn_(Xop_tkn_itm v) {this.tkn = v;} + public boolean Pass_y_(int pos, int val) { + this.pos = pos; this.val = val; + this.pass = true; + return true; + } + public boolean Pass_n_(int pos) { + this.pass = false; + this.pos = pos; + this.val = -1; + this.tkn = null; + return false; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_tkn_ent.java b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_tkn_ent.java index a27517de8..de9399a01 100644 --- a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_tkn_ent.java +++ b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_tkn_ent.java @@ -13,3 +13,18 @@ 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.amps; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.langs.htmls.entitys.*; +public class Xop_amp_tkn_ent extends Xop_tkn_itm_base { + private Gfh_entity_itm html_ref_itm; + public Xop_amp_tkn_ent(int bgn, int end, Gfh_entity_itm html_ref_itm) { + this.html_ref_itm = html_ref_itm; + this.Tkn_ini_pos(false, bgn, end); + } + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_html_ref;} + public int Char_int() {return html_ref_itm.Char_int();} + public byte[] Xml_name_bry() {return html_ref_itm.Xml_name_bry();} + public boolean Itm_is_custom() {return html_ref_itm.Tid() == Gfh_entity_itm.Tid_name_xowa;} + public void Print_ncr(Bry_bfr bfr) {html_ref_itm.Print_ncr(bfr);} + public void Print_literal(Bry_bfr bfr) {html_ref_itm.Print_literal(bfr);} +} diff --git a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_tkn_num.java b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_tkn_num.java index a27517de8..b78ba622c 100644 --- a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_tkn_num.java +++ b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_tkn_num.java @@ -13,3 +13,13 @@ 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.amps; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_amp_tkn_num extends Xop_tkn_itm_base { + public Xop_amp_tkn_num(int bgn, int end, int val, byte[] str_as_bry) { + this.val = val; this.str_as_bry = str_as_bry; + this.Tkn_ini_pos(false, bgn, end); + } + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_html_ncr;} + public int Val() {return val;} private int val; + public byte[] Str_as_bry() {return str_as_bry;} private byte[] str_as_bry; +} diff --git a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_wkr.java b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_wkr.java index a27517de8..a3ed42535 100644 --- a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_wkr.java @@ -13,3 +13,20 @@ 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.amps; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_amp_wkr implements Xop_ctx_wkr { + public void Ctor_ctx(Xop_ctx ctx) {} + public void Page_bgn(Xop_ctx ctx, Xop_root_tkn root) {} + public void Page_end(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int src_len) {} + public int Make_tkn(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn, int cur) { + if (cur == src_len) return ctx.Lxr_make_txt_(cur); // NOTE: & is last char in page; strange and rare, but don't raise error + + Xop_amp_mgr amp_mgr = ctx.App().Parser_amp_mgr(); + Xop_amp_mgr_rslt amp_rv = amp_mgr.Parse_tkn(tkn_mkr, src, src_len, bgn, cur); + Xop_tkn_itm amp_tkn = amp_rv.Tkn(); + int rv_pos = amp_rv.Pos(); + if (amp_tkn == null) return ctx.Lxr_make_txt_(rv_pos); + ctx.Subs_add(root, amp_tkn); + return rv_pos; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_wkr_tst.java b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_wkr_tst.java index a27517de8..cbf3331c8 100644 --- a/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_wkr_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/amps/Xop_amp_wkr_tst.java @@ -13,3 +13,23 @@ 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.amps; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_amp_wkr_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Convert_to_named() {fxt.Test_parse_page_wiki_str("&" , "&");} // note that & is printed, not & + @Test public void Convert_to_named_amp() {fxt.Test_parse_page_wiki_str("&" , "&");} // PURPOSE: html_wtr was not handling & only + @Test public void Convert_to_numeric() {fxt.Test_parse_page_wiki_str("á" , "á");} // testing that á is outputted, not á + @Test public void Defect_bad_code_fails() { // PURPOSE: early rewrite of Xop_amp_mgr caused Xoh_html_wtr_escaper to fail with array out of bounds error; EX:w:Czech_Republic; DATE:2014-05-11 + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|alt=

      ]]" // basically checks amp parsing inside xnde inside lnki's alt (which uses different parsing code + , "\"
\"" + ); + } + @Test public void Ignore_ncr() { // PURPOSE: check that ncr is unescaped; PAGE:de.w:Cross-Site-Scripting; DATE:2014-07-23 + fxt.Test_parse_page_all_str + ( "a <iframe>) b" + , "a <iframe>) b" // < should not become < + ); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_dat.java b/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_dat.java index a27517de8..c6d47572c 100644 --- a/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_dat.java +++ b/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_dat.java @@ -13,3 +13,67 @@ 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.apos; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_apos_dat { + public int State() {return state;} public void State_clear() {state = Xop_apos_tkn_.State_nil;} private int state = Xop_apos_tkn_.State_nil; + public int Typ() {return typ;} private int typ; + public int Cmd() {return cmd;} private int cmd; + public int Lit_apos() {return lit_apos;} private int lit_apos; + public int Dual_cmd() {return dual_cmd;} private int dual_cmd; + public void Ident(Xop_ctx ctx, byte[] src, int apos_len, int cur_pos) { + typ = cmd = lit_apos = dual_cmd = 0; + switch (apos_len) { + case Xop_apos_tkn_.Len_ital: case Xop_apos_tkn_.Len_bold: case Xop_apos_tkn_.Len_dual: + Ident_props(apos_len); break; + case Xop_apos_tkn_.Len_apos_bold: + lit_apos = 1; + Ident_props(Xop_apos_tkn_.Len_bold); break; + default: + lit_apos = apos_len - Xop_apos_tkn_.Len_dual; + Ident_props(Xop_apos_tkn_.Len_dual); + break; + } + } + private void Ident_props(int apos_len) { + typ = apos_len; + switch (apos_len) { + case Xop_apos_tkn_.Len_ital: { + switch (state) { + case Xop_apos_tkn_.State_i: cmd = Xop_apos_tkn_.Cmd_i_end; state = Xop_apos_tkn_.State_nil; break; + case Xop_apos_tkn_.State_bi: cmd = Xop_apos_tkn_.Cmd_i_end; state = Xop_apos_tkn_.State_b; break; + case Xop_apos_tkn_.State_ib: cmd = Xop_apos_tkn_.Cmd_bi_end__b_bgn; state = Xop_apos_tkn_.State_b; break; + case Xop_apos_tkn_.State_dual: cmd = Xop_apos_tkn_.Cmd_i_end; state = Xop_apos_tkn_.State_b; dual_cmd = Xop_apos_tkn_.Cmd_bi_bgn; break; + case Xop_apos_tkn_.State_b: cmd = Xop_apos_tkn_.Cmd_i_bgn; state = Xop_apos_tkn_.State_bi; break; + case Xop_apos_tkn_.State_nil: cmd = Xop_apos_tkn_.Cmd_i_bgn; state = Xop_apos_tkn_.State_i; break; + default: throw Err_.new_unhandled(state); + } + break; + } + case Xop_apos_tkn_.Len_bold: { + switch (state) { + case Xop_apos_tkn_.State_b: cmd = Xop_apos_tkn_.Cmd_b_end; state = Xop_apos_tkn_.State_nil; break; + case Xop_apos_tkn_.State_bi: cmd = Xop_apos_tkn_.Cmd_ib_end__i_bgn; state = Xop_apos_tkn_.State_i; break; + case Xop_apos_tkn_.State_ib: cmd = Xop_apos_tkn_.Cmd_b_end; state = Xop_apos_tkn_.State_i; break; + case Xop_apos_tkn_.State_dual: cmd = Xop_apos_tkn_.Cmd_b_end; state = Xop_apos_tkn_.State_i; break; // NOTE: dual_cmd = Cmd_ib_bgn is implied + case Xop_apos_tkn_.State_i: cmd = Xop_apos_tkn_.Cmd_b_bgn; state = Xop_apos_tkn_.State_ib; break; + case Xop_apos_tkn_.State_nil: cmd = Xop_apos_tkn_.Cmd_b_bgn; state = Xop_apos_tkn_.State_b; break; + default: throw Err_.new_unhandled(state); + } + break; + } + case Xop_apos_tkn_.Len_dual: { + switch (state) { + case Xop_apos_tkn_.State_b: cmd = Xop_apos_tkn_.Cmd_b_end__i_bgn; state = Xop_apos_tkn_.State_i; break; + case Xop_apos_tkn_.State_i: cmd = Xop_apos_tkn_.Cmd_i_end__b_bgn; state = Xop_apos_tkn_.State_b; break; + case Xop_apos_tkn_.State_bi: cmd = Xop_apos_tkn_.Cmd_ib_end; state = Xop_apos_tkn_.State_nil; break; + case Xop_apos_tkn_.State_ib: cmd = Xop_apos_tkn_.Cmd_bi_end; state = Xop_apos_tkn_.State_nil; break; + case Xop_apos_tkn_.State_dual: cmd = Xop_apos_tkn_.Cmd_bi_end; state = Xop_apos_tkn_.State_nil; break; // NOTE: dual_cmd = Cmd_ib_bgn is implied + case Xop_apos_tkn_.State_nil: cmd = Xop_apos_tkn_.Cmd_ib_bgn; state = Xop_apos_tkn_.State_dual; break; + default: throw Err_.new_unhandled(state); + } + break; + } + default: throw Err_.new_unhandled(apos_len); + } + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_itm.java b/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_itm.java index a27517de8..a7eaa5d9b 100644 --- a/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_itm.java +++ b/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_itm.java @@ -13,3 +13,70 @@ 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.apos; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_apos_itm { + public int State() {return state;} public void State_clear() {state = Xop_apos_tkn_.State_nil;} private int state = Xop_apos_tkn_.State_nil; + public int Typ() {return typ;} private int typ; + public int Cmd() {return cmd;} private int cmd; + public int Lit_apos() {return lit_apos;} private int lit_apos; + public int Dual_cmd() {return dual_cmd;} private int dual_cmd; + public void Init(int state, int typ, int cmd, int lit_apos, int dual_cmd) { + this.state = state; + this.typ = typ; this.cmd = cmd; this.lit_apos = lit_apos; this.dual_cmd = dual_cmd; + } + public static void Ident(Xop_apos_itm rv, Xop_ctx ctx, byte[] src, int apos_len, int cur_pos, int state) { + switch (apos_len) { + case Xop_apos_tkn_.Len_ital: case Xop_apos_tkn_.Len_bold: case Xop_apos_tkn_.Len_dual: + Ident_props(rv, state, apos_len, 0); break; + case Xop_apos_tkn_.Len_apos_bold: + Ident_props(rv, state, Xop_apos_tkn_.Len_bold, 1); break; + default: + Ident_props(rv, state, Xop_apos_tkn_.Len_dual, apos_len - Xop_apos_tkn_.Len_dual); + break; + } + } + private static void Ident_props(Xop_apos_itm rv, int state, int apos_len, int lit_apos) { + int typ = apos_len; + int cmd = 0, dual_cmd = 0; + switch (apos_len) { + case Xop_apos_tkn_.Len_ital: { + switch (state) { + case Xop_apos_tkn_.State_i: cmd = Xop_apos_tkn_.Cmd_i_end; state = Xop_apos_tkn_.State_nil; break; + case Xop_apos_tkn_.State_bi: cmd = Xop_apos_tkn_.Cmd_i_end; state = Xop_apos_tkn_.State_b; break; + case Xop_apos_tkn_.State_ib: cmd = Xop_apos_tkn_.Cmd_bi_end__b_bgn; state = Xop_apos_tkn_.State_b; break; + case Xop_apos_tkn_.State_dual: cmd = Xop_apos_tkn_.Cmd_i_end; state = Xop_apos_tkn_.State_b; dual_cmd = Xop_apos_tkn_.Cmd_bi_bgn; break; + case Xop_apos_tkn_.State_b: cmd = Xop_apos_tkn_.Cmd_i_bgn; state = Xop_apos_tkn_.State_bi; break; + case Xop_apos_tkn_.State_nil: cmd = Xop_apos_tkn_.Cmd_i_bgn; state = Xop_apos_tkn_.State_i; break; + default: throw Err_.new_unhandled(state); + } + break; + } + case Xop_apos_tkn_.Len_bold: { + switch (state) { + case Xop_apos_tkn_.State_b: cmd = Xop_apos_tkn_.Cmd_b_end; state = Xop_apos_tkn_.State_nil; break; + case Xop_apos_tkn_.State_bi: cmd = Xop_apos_tkn_.Cmd_ib_end__i_bgn; state = Xop_apos_tkn_.State_i; break; + case Xop_apos_tkn_.State_ib: cmd = Xop_apos_tkn_.Cmd_b_end; state = Xop_apos_tkn_.State_i; break; + case Xop_apos_tkn_.State_dual: cmd = Xop_apos_tkn_.Cmd_b_end; state = Xop_apos_tkn_.State_i; break; // NOTE: dual_cmd = Cmd_ib_bgn is implied + case Xop_apos_tkn_.State_i: cmd = Xop_apos_tkn_.Cmd_b_bgn; state = Xop_apos_tkn_.State_ib; break; + case Xop_apos_tkn_.State_nil: cmd = Xop_apos_tkn_.Cmd_b_bgn; state = Xop_apos_tkn_.State_b; break; + default: throw Err_.new_unhandled(state); + } + break; + } + case Xop_apos_tkn_.Len_dual: { + switch (state) { + case Xop_apos_tkn_.State_b: cmd = Xop_apos_tkn_.Cmd_b_end__i_bgn; state = Xop_apos_tkn_.State_i; break; + case Xop_apos_tkn_.State_i: cmd = Xop_apos_tkn_.Cmd_i_end__b_bgn; state = Xop_apos_tkn_.State_b; break; + case Xop_apos_tkn_.State_bi: cmd = Xop_apos_tkn_.Cmd_ib_end; state = Xop_apos_tkn_.State_nil; break; + case Xop_apos_tkn_.State_ib: cmd = Xop_apos_tkn_.Cmd_bi_end; state = Xop_apos_tkn_.State_nil; break; + case Xop_apos_tkn_.State_dual: cmd = Xop_apos_tkn_.Cmd_bi_end; state = Xop_apos_tkn_.State_nil; break; // NOTE: dual_cmd = Cmd_ib_bgn is implied + case Xop_apos_tkn_.State_nil: cmd = Xop_apos_tkn_.Cmd_ib_bgn; state = Xop_apos_tkn_.State_dual; break; + default: throw Err_.new_unhandled(state); + } + break; + } + default: throw Err_.new_unhandled_default(apos_len); + } + rv.Init(state, typ, cmd, lit_apos, dual_cmd); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_lxr.java b/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_lxr.java index a27517de8..7951c8e08 100644 --- a/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_lxr.java @@ -13,3 +13,13 @@ 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.apos; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +public class Xop_apos_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_apos;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Apos_ary, this);} private static final byte[] Apos_ary = new byte[] {Byte_ascii.Apos, Byte_ascii.Apos}; + 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) {return ctx.Apos().Make_tkn(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos);} + public static final Xop_apos_lxr Instance = new Xop_apos_lxr(); Xop_apos_lxr() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_tkn.java b/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_tkn.java index a27517de8..2a02e4106 100644 --- a/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_tkn.java @@ -13,3 +13,15 @@ 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.apos; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_apos_tkn extends Xop_tkn_itm_base { + public Xop_apos_tkn(int bgn, int end, int apos_len, int apos_tid, int apos_cmd, int apos_lit) { + this.apos_len = apos_len; this.apos_tid = apos_tid; this.apos_cmd = apos_cmd; this.apos_lit = apos_lit; + this.Tkn_ini_pos(false, bgn, end); + } + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_apos;} + public int Apos_len() {return apos_len;} private int apos_len; + public int Apos_lit() {return apos_lit;} public Xop_apos_tkn Apos_lit_(int v) {apos_lit = v; return this;} private int apos_lit; + public int Apos_tid() {return apos_tid;} public Xop_apos_tkn Apos_tid_(int v) {apos_tid = v; return this;} private int apos_tid; + public int Apos_cmd() {return apos_cmd;} public Xop_apos_tkn Apos_cmd_(int v) {apos_cmd = v; return this;} private int apos_cmd; +} diff --git a/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_tkn_.java b/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_tkn_.java index a27517de8..3c940b312 100644 --- a/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_tkn_.java +++ b/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_tkn_.java @@ -13,3 +13,22 @@ 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.apos; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_apos_tkn_ { + public static final int + Cmd_nil = 0 + , Cmd_i_bgn = 1, Cmd_i_end = 2, Cmd_b_bgn = 3, Cmd_b_end = 4 + , Cmd_bi_bgn = 5, Cmd_ib_bgn = 6, Cmd_ib_end = 7, Cmd_bi_end = 8 + , Cmd_bi_end__b_bgn = 9, Cmd_ib_end__i_bgn = 10, Cmd_b_end__i_bgn = 11, Cmd_i_end__b_bgn = 12; + public static final byte[][] Cmds + = new byte[][] + { Bry_.new_a7("nil") + , Bry_.new_a7("i+"), Bry_.new_a7("i-"), Bry_.new_a7("b+"), Bry_.new_a7("b-") + , Bry_.new_a7("bi+"), Bry_.new_a7("ib+"), Bry_.new_a7("ib-"), Bry_.new_a7("bi-") + , Bry_.new_a7("bi-b+"), Bry_.new_a7("ib-i+"), Bry_.new_a7("b-i+"), Bry_.new_a7("i-b+") + }; + public static String Cmd_str(int id) {return String_.new_u8(Cmds[id]);} + public static final int Len_ital = 2, Len_bold = 3, Len_dual = 5, Len_apos_bold = 4; + public static final int Typ_ital = 2, Typ_bold = 3, Typ_dual = 5; + public static final int State_nil = 0, State_i = 1, State_b = 2, State_bi = 3, State_ib = 4, State_dual = 5; +} diff --git a/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_tkn_chkr.java b/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_tkn_chkr.java index a27517de8..3d814e059 100644 --- a/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_tkn_chkr.java +++ b/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_tkn_chkr.java @@ -13,3 +13,17 @@ 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.apos; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xop_apos_tkn_chkr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Xop_apos_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_apos;} + public int Apos_cmd() {return apos_cmd;} public Xop_apos_tkn_chkr Apos_cmd_(int v) {apos_cmd = v; return this;} private int apos_cmd = Xop_apos_tkn_.Cmd_nil; + public int Apos_lit() {return apos_lit;} public Xop_apos_tkn_chkr Apos_lit_(int v) {apos_lit = v; return this;} private int apos_lit = -1; + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { + Xop_apos_tkn actl = (Xop_apos_tkn)actl_obj; + err += mgr.Tst_val(apos_cmd == Xop_apos_tkn_.Cmd_nil, path, "apos_cmd", Xop_apos_tkn_.Cmd_str(apos_cmd), Xop_apos_tkn_.Cmd_str(actl.Apos_cmd())); + err += mgr.Tst_val(apos_lit == -1, path, "apos_lit", apos_lit, actl.Apos_lit()); + return err; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_wkr.java b/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_wkr.java index a27517de8..a21788e00 100644 --- a/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_wkr.java @@ -13,3 +13,100 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.apos; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_apos_wkr implements Xop_ctx_wkr { + private final List_adp stack = List_adp_.New(); // stores all apos tkns for page; needed to recalc tkn type if apos are dangling + private int bold_count, ital_count; private Xop_apos_tkn dual_tkn = null; + private Xop_apos_dat dat = new Xop_apos_dat(); + public void Ctor_ctx(Xop_ctx ctx) {} + public void Page_bgn(Xop_ctx ctx, Xop_root_tkn root) {Clear();} + public void Page_end(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int src_len) { + this.End_frame(ctx, root, src, src_len, false); + } + public void AutoClose(Xop_ctx ctx, byte[] src, int src_len, int bgn_pos, int cur_pos, Xop_tkn_itm tkn) {} + public int Stack_len() {return stack.Len();} + private void Clear() { + bold_count = ital_count = 0; + dual_tkn = null; + stack.Clear(); + dat.State_clear(); + } + 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) { + cur_pos = Bry_find_.Find_fwd_while(src, cur_pos, src_len, Byte_ascii.Apos); + int apos_len = cur_pos - bgn_pos; + dat.Ident(ctx, src, apos_len, cur_pos); + Xop_apos_tkn apos_tkn = tkn_mkr.Apos(bgn_pos, cur_pos, apos_len, dat.Typ(), dat.Cmd(), dat.Lit_apos()); + ctx.Subs_add(root, apos_tkn); + ctx.Apos().Reg_tkn(apos_tkn, cur_pos); // NOTE: register in root ctx (main document) + return cur_pos; + } + private void Reg_tkn(Xop_apos_tkn tkn, int cur_pos) { // REF.MW: Parser|doQuotes + stack.Add(tkn); + switch (tkn.Apos_tid()) { + case Xop_apos_tkn_.Len_ital: ital_count++; break; + case Xop_apos_tkn_.Len_bold: bold_count++; break; + case Xop_apos_tkn_.Len_dual: //bold_count++; ital_count++; // NOTE: removed b/c of '''''a''b'' was trying to convert ''''' to bold + dual_tkn = tkn; + break; + } + if (dat.Dual_cmd() != 0) { // earlier dual tkn assumed to be ; encountered so change dual to + if (dual_tkn == null) throw Err_.new_wo_type("dual tkn is null"); // should never happen + dual_tkn.Apos_cmd_(dat.Dual_cmd()); + dual_tkn = null; + } + } + public void End_frame(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int cur_pos, boolean skip_cancel_if_lnki_and_apos) { + int state = dat.State(); + if (state == 0) {Clear(); return;} // all apos close correctly; nothing dangling; return; + + if (bold_count % 2 == 1 && ital_count % 2 == 1) Convert_bold_to_ital(ctx, src, stack, dat); + state = dat.State(); + if (state == 0) {Clear(); return;} // all apos close correctly after converting bold to italic; return; + + int closeCmd = 0, closeTyp = 0; + byte cur_tkn_tid = ctx.Cur_tkn_tid(); + if ( skip_cancel_if_lnki_and_apos // NOTE: if \n or tblw + && cur_tkn_tid == Xop_tkn_itm_.Tid_lnki // and cur scope is lnki + ) + return; // don't end frame + switch (state) { + case Xop_apos_tkn_.State_i: closeTyp = Xop_apos_tkn_.Typ_ital; closeCmd = Xop_apos_tkn_.Cmd_i_end; break; + case Xop_apos_tkn_.State_b: closeTyp = Xop_apos_tkn_.Typ_bold; closeCmd = Xop_apos_tkn_.Cmd_b_end; break; + case Xop_apos_tkn_.State_dual: + case Xop_apos_tkn_.State_ib: closeTyp = Xop_apos_tkn_.Typ_dual; closeCmd = Xop_apos_tkn_.Cmd_bi_end; break; + case Xop_apos_tkn_.State_bi: closeTyp = Xop_apos_tkn_.Typ_dual; closeCmd = Xop_apos_tkn_.Cmd_ib_end; break; + } + ctx.Subs_add(root, ctx.Tkn_mkr().Apos(cur_pos, cur_pos, 0, closeTyp, closeCmd, 0)); + Clear(); + } + private static void Convert_bold_to_ital(Xop_ctx ctx, byte[] src, List_adp stack, Xop_apos_dat dat) { + Xop_apos_tkn idxNeg1 = null, idxNeg2 = null, idxNone = null; // look at previous tkn for spaces; EX: "a '''" -> idxNeg1; " a'''" -> idxNeg2; "ab'''" -> idxNone + int len = stack.Len(); + for (int i = 0; i < len; ++i) { + Xop_apos_tkn apos = (Xop_apos_tkn)stack.Get_at(i); + if (apos.Apos_tid() != Xop_apos_tkn_.Typ_bold) continue; // only look for bold + int tkn_bgn = apos.Src_bgn(); + boolean idxNeg1Space = tkn_bgn > 0 && src[tkn_bgn - 1] == Byte_ascii.Space; + boolean idxNeg2Space = tkn_bgn > 1 && src[tkn_bgn - 2] == Byte_ascii.Space; + if (idxNeg1 == null && idxNeg1Space) {idxNeg1 = apos;} + else if (idxNeg2 == null && idxNeg2Space) {idxNeg2 = apos;} + else if (idxNone == null && !idxNeg1Space && !idxNeg2Space) {idxNone = apos;} + } + if (idxNeg2 != null) Convert_bold_to_ital(ctx, src, idxNeg2); // 1st single letter word + else if (idxNone != null) Convert_bold_to_ital(ctx, src, idxNone); // 1st multi letter word + else if (idxNeg1 != null) Convert_bold_to_ital(ctx, src, idxNeg1); // everything else + + // now recalc all cmds for stack + dat.State_clear(); + for (int i = 0; i < len; i++) { + Xop_apos_tkn apos = (Xop_apos_tkn)stack.Get_at(i); + dat.Ident(ctx, src, apos.Apos_tid(), apos.Src_end()); // NOTE: apos.Typ() must map to apos_len + int newCmd = dat.Cmd(); + if (newCmd == apos.Apos_cmd()) continue; + apos.Apos_cmd_(newCmd); + } + } + private static void Convert_bold_to_ital(Xop_ctx ctx, byte[] src, Xop_apos_tkn oldTkn) { + oldTkn.Apos_tid_(Xop_apos_tkn_.Typ_ital).Apos_cmd_(Xop_apos_tkn_.Cmd_i_bgn).Apos_lit_(oldTkn.Apos_lit() + 1);// NOTE: Cmd_i_bgn may be overridden later + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_wkr_tst.java b/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_wkr_tst.java index a27517de8..71fa8f40c 100644 --- a/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_wkr_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/apos/Xop_apos_wkr_tst.java @@ -13,3 +13,139 @@ 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.apos; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +import gplx.xowa.parsers.lists.*; +public class Xop_apos_wkr_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Basic() { + fxt.Test_parse_page_wiki("''a''" , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn) , fxt.tkn_txt_(2, 3), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end)); + fxt.Test_parse_page_wiki("'''a'''" , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_bgn) , fxt.tkn_txt_(3, 4), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_end)); + fxt.Test_parse_page_wiki("'''''a'''''" , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_ib_bgn) , fxt.tkn_txt_(5, 6), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_bi_end)); + } + @Test public void Advanced() { + fxt.Test_parse_page_wiki("''''a''''" , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_bgn).Apos_lit_(1) , fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_end).Apos_lit_(1)); // 1 apos + bold + fxt.Test_parse_page_wiki("''''''''a''''''''" , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_ib_bgn).Apos_lit_(3) , fxt.tkn_txt_(), fxt.tkn_apos_( Xop_apos_tkn_.Cmd_bi_end).Apos_lit_(3)); // 3 apos + dual + } + @Test public void Combo() { + fxt.Test_parse_page_wiki("''a'''b'''c''", fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_end), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end)); // b{i} + fxt.Test_parse_page_wiki("'''a''b''c'''", fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_end)); // i{b} + fxt.Test_parse_page_wiki("''a''b'''c'''", fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_end)); // b_i + } + @Test public void Assume_apos() { + fxt.Test_parse_page_wiki("a01'''b01 '''c0 1'''d01''" // pick c0 1, b/c it is idxNeg2 + , fxt.tkn_txt_() , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_bgn) + , fxt.tkn_txt_(), fxt.tkn_space_() , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_end) + , fxt.tkn_txt_(), fxt.tkn_space_(), fxt.tkn_txt_() , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn) + , fxt.tkn_txt_() , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end)); // idx_neg2 + fxt.Test_parse_page_wiki("a01 '''b01 '''c01'''d01''" // pick c01, b/c it is idxNone + , fxt.tkn_txt_(), fxt.tkn_space_() , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_bgn) + , fxt.tkn_txt_(), fxt.tkn_space_() , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_end) + , fxt.tkn_txt_() , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn) + , fxt.tkn_txt_() , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end)); // idx_none + fxt.Test_parse_page_wiki("a01 '''b01 '''c01 '''d01''" // pick a01 , b/c it is idxNeg1 + , fxt.tkn_txt_(), fxt.tkn_space_() , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn) + , fxt.tkn_txt_(), fxt.tkn_space_() , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_bgn) + , fxt.tkn_txt_(), fxt.tkn_space_() , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_end) + , fxt.tkn_txt_() , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end)); // idx_neg1 + fxt.Test_parse_page_wiki("a''''b''" // strange outlier condition + , fxt.tkn_txt_() , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn).Apos_lit_(2) + , fxt.tkn_txt_() , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end)); // 4 apos -> 2 apos + ital + } + @Test public void Dual() { + fxt.Test_parse_page_wiki("'''''a'''b''" // +ib -b -i; 5apos defaults to ib + , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_ib_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_end), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end)); + fxt.Test_parse_page_wiki("'''''a''b'''" // +bi -i -b; change 5apos to bi + , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_bi_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_end)); + fxt.Test_parse_page_wiki("''b'''''c'''" // 5q toggles ital n, bold y + , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end__b_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_end)); + } + @Test public void Unclosed() { + fxt.Test_parse_page_wiki("''a" + , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end)); + fxt.Test_parse_page_wiki("'''a" + , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_end)); + fxt.Test_parse_page_wiki("'''''a" + , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_ib_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_bi_end)); + } + @Test public void Outliers() { + fxt.Test_parse_page_wiki("''a'''b'''c'''" // '''b -> ' +i b + , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end).Apos_lit_(1) + , fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_end)); + fxt.Test_parse_page_wiki("''a'''b''c''" // '''b -> ' +i b; double check with closing itals + , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end).Apos_lit_(1) + , fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end)); + fxt.Test_parse_page_wiki("''a'''b''c" // ''c -> -bi + b + , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_bgn) + , fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_bi_end__b_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_b_end)); + } + @Test public void MultiLines() { + fxt.Test_parse_page_wiki("a''b\nc''d" + , fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn), fxt.tkn_txt_(3, 4), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end), fxt.tkn_nl_char_len1_(4) + , fxt.tkn_txt_(5, 6), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end)); + } + @Test public void Lnki() { + fxt.Test_parse_page_wiki_str("[[''a''']]", "''a'''"); + } + @Test public void Dual_exceptions() { + fxt.Test_parse_page_wiki("'''''a''b''" + , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_bi_bgn), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end), fxt.tkn_txt_(), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn), fxt.tkn_apos_(Xop_apos_tkn_.Cmd_ib_end) + ); + } + @Test public void Mix_list_autoClose() { + fxt.Test_parse_page_wiki("''a\n*b" + , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn).Src_rng_(0, 2) + , fxt.tkn_txt_(2, 3) + , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end).Src_rng_(3, 3) + , fxt.tkn_list_bgn_(3, 5, Xop_list_tkn_.List_itmTyp_ul) + , fxt.tkn_txt_(5, 6) + , fxt.tkn_list_end_(6) + ); + } + @Test public void Mix_hr_autoClose() { + fxt.Test_parse_page_wiki("''a\n----" + , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_bgn).Src_rng_(0, 2) + , fxt.tkn_txt_(2, 3) + , fxt.tkn_apos_(Xop_apos_tkn_.Cmd_i_end).Src_rng_(3, 3) + , fxt.tkn_para_blank_(3) + , fxt.tkn_hr_(3, 8) + ); + } + @Test public void Mix_hdr_autoClose() { + fxt.Test_parse_page_wiki_str("''a\n==b==", "a\n\n

      b

      "); + } + @Test public void Apos_broken_by_tblw_th() { // DATE:2013-04-24 + fxt.Test_parse_page_all_str("A ''[[b!!]]'' c", "A b!! c"); + } + @Test public void Nowiki() { // PAGE:en.w:Wiki; DATE:2013-05-13 + fxt.Test_parse_page_all_str("''a''", "''a''"); + } + @Test public void Lnki_multi_line() { // PURPOSE: handle apos within multi-line lnki caption; DATE:2013-11-10 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "[[A|b '' c" + , "d '' e ]]" + ) + , "b c d e"); // NOTE: c d should be italicized, not c e (latter occurs when apos is ended on each line) + } + @Test public void French() { // PURPOSE: L'''A'' -> L'A; DATE:2014-01-06 + fxt.Test_parse_page_all_str("L''''A'''", "L'A"); + fxt.Test_parse_page_all_str("L'''A''", "L'A"); + } +// @Test public void Mix_lnke() { // FUTURE: requires rewrite of apos +// fxt.Test_parse_page_wiki("''a[irc://b c''d''e]f''" +// , fxt.tkn_apos_(0, 2, Xop_apos_tkn_.Cmd_i_bgn) +// , fxt.tkn_txt_(2, 3) +// , fxt.tkn_lnke_(3, 20).Subs_add_ary +// ( fxt.tkn_txt_(12, 13) +// , fxt.tkn_apos_(13, 15, Xop_apos_tkn_.Cmd_i_bgn) +// , fxt.tkn_txt_(15, 16) +// , fxt.tkn_apos_(16, 18, Xop_apos_tkn_.Cmd_i_end) +// , fxt.tkn_txt_(18, 19) +// ) +// , fxt.tkn_txt_(20, 21) +// , fxt.tkn_apos_(21, 23, Xop_apos_tkn_.Cmd_i_bgn) +// ); +// } +} +/* +*/ diff --git a/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_log.java b/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_log.java index a27517de8..8bf5cd7a2 100644 --- a/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_log.java +++ b/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_log.java @@ -13,3 +13,14 @@ 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.hdrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.log_msgs.*; +public class Xop_hdr_log { + private static final Gfo_msg_grp owner = Gfo_msg_grp_.new_(Xoa_app_.Nde, "hdr"); + public static final Gfo_msg_itm + Dangling_hdr = Gfo_msg_itm_.new_warn_(owner, "dangling_hdr") + , Mismatched = Gfo_msg_itm_.new_warn_(owner, "mismatched") + , Len_1 = Gfo_msg_itm_.new_warn_(owner, "len_1") + , Len_7_or_more = Gfo_msg_itm_.new_warn_(owner, "len_7_or_more") + ; +} diff --git a/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_lxr.java b/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_lxr.java index a27517de8..e1cd6fbfb 100644 --- a/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_lxr.java @@ -13,3 +13,14 @@ 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.hdrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +public class Xop_hdr_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_hdr;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Hook_bgn, this);} static final byte[] Hook_bgn = new byte[] {Byte_ascii.Nl, Byte_ascii.Eq}; + 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) {return ctx.Hdr().Make_tkn_bgn(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos);} + public static final Xop_hdr_lxr Instance = new Xop_hdr_lxr(); Xop_hdr_lxr() {} + public static final byte Hook = Byte_ascii.Eq; +} diff --git a/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_tkn.java b/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_tkn.java index a27517de8..7fc0e3f93 100644 --- a/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_tkn.java @@ -13,3 +13,27 @@ 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.hdrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_hdr_tkn extends Xop_tkn_itm_base { + public Xop_hdr_tkn(int bgn, int end, int num) {this.Tkn_ini_pos(false, bgn, end); this.num = num;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_hdr;} + public int Num() {return num;} private int num = -1; // EX: 2 for

      + public int Manual_bgn() {return manual_bgn;} private int manual_bgn; // unbalanced count; EX: === A == -> 1 + public int Manual_end() {return manual_end;} private int manual_end; // unbalanced count; EX: == A === -> 1 + public boolean First_in_doc() {return first_in_doc;} private boolean first_in_doc; // true if 1st hdr in doc + public void First_in_doc_y_() {first_in_doc = true;} + public byte[] Section_editable_page() {return section_editable_page;} private byte[] section_editable_page; // EX: Earth as in 'href="/wiki/Earth"' + public int Section_editable_idx() {return section_editable_idx;} private int section_editable_idx; // EX: 1 as in "section=1" + + public void Init_by_parse(int num, int manual_bgn, int manual_end) { + this.num = num; + this.manual_bgn = manual_bgn; + this.manual_end = manual_end; + } + public void Section_editable_(byte[] section_editable_page, int section_editable_idx) { + this.section_editable_page = section_editable_page; + this.section_editable_idx = section_editable_idx; + } + + public static final Xop_hdr_tkn[] Ary_empty = new Xop_hdr_tkn[0]; +} diff --git a/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_wkr.java b/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_wkr.java index a27517de8..144c570b9 100644 --- a/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_wkr.java @@ -13,3 +13,119 @@ 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.hdrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.parsers.xndes.*; +import gplx.xowa.parsers.hdrs.sections.*; +public class Xop_hdr_wkr implements Xop_ctx_wkr { + public void Ctor_ctx(Xop_ctx ctx) {} + public void Page_bgn(Xop_ctx ctx, Xop_root_tkn root) {} + public void Page_end(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int src_len) {} + public void AutoClose(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, Xop_tkn_itm tkn) { + // bgn never closed; mark inert; EX: "==a" + Xop_hdr_tkn bgn = (Xop_hdr_tkn)tkn; + int bgn_hdr_len = bgn.Num(); + bgn.Init_by_parse(0, bgn_hdr_len, 0); + if (bgn_hdr_len > 1 && ctx.Parse_tid() == Xop_parser_tid_.Tid__wtxt) // NOTE: \n= is not uncommon for templates; ignore them; + ctx.Msg_log().Add_itm_none(Xop_hdr_log.Dangling_hdr, src, bgn.Src_bgn(), bgn_pos); + } + public int Make_tkn_bgn(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos) { + if (bgn_pos == Xop_parser_.Doc_bgn_bos) bgn_pos = 0; // do not allow -1 pos + ctx.Apos().End_frame(ctx, root, src, bgn_pos, false); + Close_open_itms(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos); + ctx.Para().Process_block__bgn__nl_w_symbol(ctx, root, src, bgn_pos, cur_pos, Xop_xnde_tag_.Tag__h2); // pass h2; should pass h# where # is correct #, but for purpose of Para_wkr,

      tag does not matter + int new_pos = Bry_find_.Find_fwd_while(src, cur_pos, src_len, Xop_hdr_lxr.Hook); // count all = + int hdr_len = new_pos - cur_pos + 1; // +1 b/c Hook has 1 eq: "\n=" + switch (hdr_len) { + case 1: ctx.Msg_log().Add_itm_none(Xop_hdr_log.Len_1, src, bgn_pos, new_pos); break; //

      ; flag + case 2: case 3: case 4: case 5: case 6: break; //

      -

      : normal + default: ctx.Msg_log().Add_itm_none(Xop_hdr_log.Len_7_or_more, src, bgn_pos, new_pos); break; // +; limit to 6; flag; NOTE: only 14 pages in 2011-07-27 + } + + Xop_hdr_tkn tkn = tkn_mkr.Hdr(bgn_pos, new_pos, hdr_len); // make tkn + ctx.StackTkn_add(root, tkn); + return new_pos; + } + public int Make_tkn_end(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, int stackPos, int end_hdr_len) {// REF.MW: Parser|doHeadings + if (ctx.Cur_tkn_tid() == Xop_tkn_itm_.Tid_tmpl_curly_bgn) return ctx.Lxr_make_txt_(cur_pos); + + // end frame + Xop_hdr_tkn hdr = (Xop_hdr_tkn)ctx.Stack_pop_til(root, src, stackPos, false, bgn_pos, cur_pos, Xop_tkn_itm_.Tid_hdr); + ctx.Apos().End_frame(ctx, root, src, bgn_pos, false); // end any apos; EX: ==''a== + + // handle asymmetrical "="; EX: "== A ===" + int hdr_len = hdr.Num(), bgn_manual = 0, end_manual = 0; + boolean dirty = false; + if (end_hdr_len < hdr_len) { // mismatch: end has more; adjust hdr + bgn_manual = hdr_len - end_hdr_len; + hdr_len = end_hdr_len; + ctx.Msg_log().Add_itm_none(Xop_hdr_log.Mismatched, src, bgn_pos, cur_pos); + if (hdr_len == 1) ctx.Msg_log().Add_itm_none(Xop_hdr_log.Len_1, src, bgn_pos, cur_pos); + dirty = true; + } + else if (end_hdr_len > hdr_len) { // mismatch: hdr has more; adjust variables + end_manual = end_hdr_len - hdr_len; + ctx.Msg_log().Add_itm_none(Xop_hdr_log.Mismatched, src, bgn_pos, cur_pos); + dirty = true; + } + if (hdr_len > 6) { // +; limit to 6; NOTE: make both bgn/end are equal length; EX: bgn=8,end=7 -> bgn=7,end=7;bgn_manual=1 + bgn_manual = end_manual = hdr_len - 6; + hdr_len = 6; + dirty = true; + } + if (dirty) + hdr.Init_by_parse(hdr_len, bgn_manual, end_manual); + + // gobble ws; hdr gobbles up trailing ws; EX: "==a== \n\t \n \nb" gobbles up all 3 "\n"s; otherwise para_wkr will process
      + cur_pos = Find_fwd_while_ws_hdr_version(src, cur_pos, src_len); + ctx.Para().Process_block__bgn_n__end_y(Xop_xnde_tag_.Tag__h2); + + // add to root tkn; other post-processing + hdr.Subs_move(root); + hdr.Src_end_(cur_pos); + if (ctx.Parse_tid() == Xop_parser_tid_.Tid__wtxt) { // do not add if defn / tmpl mode + ctx.Page().Wtxt().Toc().Add(hdr); + } + return cur_pos; + } + private void Close_open_itms(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos) { + int stack_pos = -1, stack_len = ctx.Stack_len(); boolean stop = false; + for (int i = 0; i < stack_len; i++) { // loop over stack + Xop_tkn_itm prv_tkn = ctx.Stack_get(i); + switch (prv_tkn.Tkn_tid()) { // find first list/hdr; close everything until this + case Xop_tkn_itm_.Tid_list: + case Xop_tkn_itm_.Tid_hdr: + stack_pos = i; stop = true; break; + } + if (stop) break; + } + if (stack_pos == -1) return; + ctx.Stack_pop_til(root, src, stack_pos, true, bgn_pos, cur_pos, Xop_tkn_itm_.Tid_hdr); + } + private static int Find_fwd_while_ws_hdr_version(byte[] src, int cur, int end) { + int last_nl = -1; + while (true) { + if (cur == end) return cur; + byte b = src[cur]; + switch (b) { + case Byte_ascii.Nl: + cur++; + last_nl = cur; + break; + case Byte_ascii.Space: + case Byte_ascii.Tab: + cur++; + break; + default: + return last_nl == -1 ? cur : last_nl - 1; + } + } + } +} +/* +NOTE:hdr.trailing_nl +. by design, the hdr_tkn's src_end will not include the trailing \n +.. for example, for "\n==a==\n", the src_bgn will be 0, but the src_end will be 6 +.. note that at 6, it does not include the \n at pos 6 +. this is needed to leave the \n for the parser to handle other tkns, such as hdrs, tblws, lists. +. for example, in "\n==a==\n*b", if the \n at pos 6 was taken by the hdr_tkn, then the parser would encounter a "*" instead of a "\n*" +*/ diff --git a/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_wkr__basic_tst.java b/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_wkr__basic_tst.java index a27517de8..497848586 100644 --- a/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_wkr__basic_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_wkr__basic_tst.java @@ -13,3 +13,113 @@ 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.hdrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_hdr_wkr__basic_tst { + @Before public void init() {fxt.Reset();} private final Xop_fxt fxt = new Xop_fxt(); + @After public void term() {fxt.Init_para_n_();} + @Test public void H2() {fxt.Test_parse_page_wiki_str("==a==" , "

      a

      \n");} + @Test public void H3() {fxt.Test_parse_page_wiki_str("===a===" , "

      a

      \n");} + @Test public void H6_limit() {fxt.Test_parse_page_wiki_str("=======a=======" , "
      =a=
      \n");} + @Test public void Mismatch_bgn() {fxt.Test_parse_page_wiki_str("=====a==" , "

      ===a

      \n");} + @Test public void Mismatch_end() {fxt.Test_parse_page_wiki_str("==a=====" , "

      a===

      \n");} + @Test public void Dangling() {fxt.Test_parse_page_wiki_str("==a" , "==a");} + @Test public void Comment_bgn() {fxt.Test_parse_page_all_str ("==a==" , "

      a

      \n");} + @Test public void Comment_end() {fxt.Test_parse_page_all_str ("==a==" , "

      a

      \n");} + @Test public void Ws_end() { // PURPOSE: "==\n" merges all ws following it; \n\n\n is not transformed by Para_wkr to "
      " + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "==a== \t" + , "" + , "" + , "" + , "b" + ), String_.Concat_lines_nl_skip_last + ( "

      a

      " + , "b" + )); + } + @Test public void Many() { + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "==a==" + , "===b===" + ), String_.Concat_lines_nl_skip_last + ( "

      a

      " + , "" + , "

      b

      " + , "" + )); + } + @Test public void Hdr_w_tblw() { + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "==a==" + , "{|" + , "|+" + , "|}" + ), String_.Concat_lines_nl_skip_last + ( "

      a

      " + , "" + , " " + , "
      " + , "
      " + , "" + )); + } + @Test public void Hdr_w_hr() { + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "==a==" + , "----" + ), String_.Concat_lines_nl_skip_last + ( "

      a

      " + , "
      " + )); + } + @Test public void Mix_apos_dangling() {fxt.Test_parse_page_wiki_str("==''a==" , "

      a

      \n");} + @Test public void Mix_xnde_dangling() {fxt.Test_parse_page_wiki_str("==a==" , "

      a

      \n");} + @Test public void Mix_tblw_cell() {fxt.Test_parse_page_wiki_str("==a!!==" , "

      a!!

      \n");} + @Test public void Ws() {fxt.Test_parse_page_wiki_str("== a b ==" , "

      a b

      \n");} + @Test public void Err_hdr() {fxt.Init_log_(Xop_hdr_log.Mismatched) .Test_parse_page_wiki_str("====a== ==" , "

      ==a==

      \n").tst_Log_check();} + @Test public void Err_end_hdr_is_1() {fxt.Init_log_(Xop_hdr_log.Mismatched, Xop_hdr_log.Len_1).Test_parse_page_wiki_str("==a=" , "

      =a

      \n").tst_Log_check();} + @Test public void Html_hdr_many() { + fxt.Wtr_cfg().Toc__show_(Bool_.Y); + fxt.Test_parse_page_wiki_str__esc(String_.Concat_lines_nl_skip_last + ( "==a==" + , "==a==" + , "==a==" + ), String_.Concat_lines_nl_skip_last + ( "

      a

      " + , "" + , "

      a

      " + , "" + , "

      a

      " + , "" + )); + fxt.Wtr_cfg().Toc__show_(Bool_.N); + } + @Test public void Hdr_inside_dangling_tmpl_fix() { // PURPOSE: one-off fix to handle == inside dangling tmpl; DATE:2014-02-11 + fxt.Test_parse_page_all_str("{{a|}\n==b==" + , String_.Concat_lines_nl_skip_last + ( "{{a|}" + , "" + , "

      b

      " + , "" + )); + } + @Test public void Pfunc() {// multiple = should not be interpreted as key-val equals; PAGE:en.w:Wikipedia:Picture_of_the_day/June_2014 DATE:2014-07-21 + fxt.Test_parse_page_all_str + ( "{{#if:exists|==a==|no}}" + , String_.Concat_lines_nl_skip_last + ( "

      a

      " + , "" + )); + } +// @Test public void Hdr_inside_dangling_tmpl_fix_2() { // PURPOSE: hdr == inside dangling tmpl; DATE:2014-06-10 +// fxt.Init_defn_add("Print", "{{{1}}}"); +// fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last +// ( "=={{Print|b==" +// , "}}" +// ), String_.Concat_lines_nl_skip_last +// ( "==b=" +// , "" +// )); +// } +} diff --git a/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_wkr__para_tst.java b/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_wkr__para_tst.java index a27517de8..297f2bbf4 100644 --- a/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_wkr__para_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/hdrs/Xop_hdr_wkr__para_tst.java @@ -13,3 +13,12 @@ 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.hdrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_hdr_wkr__para_tst { + @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 Hdr_at_bos() { // PURPOSE: check that BOS==a== does not throw null ref in para; DATE:2014-02-18 + fxt.Test_parse_page_all_str("==a==", "

      a

      \n"); + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_itm.java b/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_itm.java index a27517de8..df98baf83 100644 --- a/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_itm.java +++ b/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_itm.java @@ -13,3 +13,18 @@ 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.hdrs.sections; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; import gplx.xowa.parsers.hdrs.*; +class Xop_section_itm { + public Xop_section_itm(int idx, int num, byte[] key, int src_bgn, int src_end) { + this.idx = idx; + this.num = num; + this.key = key; + this.src_bgn = src_bgn; + this.src_end = src_end; + } + public int Idx() {return idx;} private final int idx; + public int Num() {return num;} private final int num; + public byte[] Key() {return key;} private final byte[] key; + public int Src_bgn() {return src_bgn;} private final int src_bgn; + public int Src_end() {return src_end;} private final int src_end; +} 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 a27517de8..ee0fe6f11 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 @@ -13,3 +13,106 @@ 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.hdrs.sections; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; import gplx.xowa.parsers.hdrs.*; +import gplx.xowa.mediawiki.includes.parsers.headingsOld.*; +import gplx.xowa.addons.htmls.tocs.*; import gplx.xowa.htmls.core.htmls.tidy.*; +class Xop_section_list implements Xomw_heading_cbk { + private final Xomw_heading_wkr hdr_wkr = new Xomw_heading_wkr(); + private final Ordered_hash hash = Ordered_hash_.New_bry(); + private final Xoh_toc_mgr toc_mgr = new Xoh_toc_mgr(); + private byte[] src; + private Xowe_wiki wiki; + + public Xop_section_list Parse(Xowe_wiki wiki, Xow_tidy_mgr_interface tidy_mgr, byte[] src) { + // clear + this.wiki = wiki; + this.src = src; + hash.Clear(); + toc_mgr.Clear(); + toc_mgr.Init(tidy_mgr, Bry_.Empty, Bry_.Empty); + + // parse + hdr_wkr.Parse(src, 0, src.length, this); + return this; + } + public byte[] Slice_bry_or_null(byte[] key) { + int[] bounds = Get_section_bounds(key); + if (bounds == null) return null; // handle missing key + + // return slice + return Bry_.Mid(src, bounds[0], bounds[1]); + } + public byte[] Merge_bry_or_null(byte[] key, byte[] edit) { + int[] bounds = Get_section_bounds(key); + if (bounds == null) return null; // handle missing key + + // merge edit into orig + Bry_bfr bfr = Bry_bfr_.New(); + bfr.Add_mid(src, 0, bounds[0]); + bfr.Add(edit); + bfr.Add_mid(src, bounds[1], src.length); + + return bfr.To_bry_and_clear(); + } + private int[] Get_section_bounds(byte[] key) { + int src_bgn = -1, src_end = -1; + int hash_len = hash.Len(); + + // if key == "", get lead section + if (Bry_.Eq(key, Bry_.Empty)) { + src_bgn = 0; + src_end = src.length; + if (hash_len > 0) { + Xop_section_itm itm = (Xop_section_itm)hash.Get_at(0); + src_end = itm.Src_bgn(); // -1 to skip "\n" in "\n==" + } + } + // else, get section matching key + else { + Xop_section_itm itm = (Xop_section_itm)hash.Get_by(key); + if (itm == null) return null; + + // get bgn + src_bgn = itm.Src_bgn(); + if (src[src_bgn] == Byte_ascii.Nl) src_bgn++; // skip "\n" in "\n==" + + // get end + for (int i = itm.Idx() + 1; i < hash_len; i++) { + Xop_section_itm nxt = (Xop_section_itm)hash.Get_at(i); + if (nxt.Num() > itm.Num()) continue; // skip headers that are at lower level; EX: == H2 == should skip === H3 === + src_end = nxt.Src_bgn(); + break; + } + if (src_end == -1) src_end = src.length; // no headers found; default to EOS + src_end = Bry_find_.Find_bwd__skip_ws(src, src_end, src_bgn); // always remove ws at end + } + + return new int[] {src_bgn, src_end}; + } + public void On_hdr_seen(Xomw_heading_wkr wkr) { + // get key by taking everything between ==; EX: "== abc ==" -> " abc " + byte[] src = wkr.Src(); + int hdr_txt_bgn = wkr.Hdr_lhs_end(); + int hdr_txt_end = wkr.Hdr_rhs_bgn(); + + // trim ws + hdr_txt_bgn = Bry_find_.Find_fwd_while_ws(src, hdr_txt_bgn, hdr_txt_end); + hdr_txt_end = Bry_find_.Find_bwd__skip_ws(src, hdr_txt_end, hdr_txt_bgn); + byte[] key = Bry_.Mid(wkr.Src(), hdr_txt_bgn, hdr_txt_end); + + // handle nested templates; EX: "== {{A}} ==" note that calling Parse_text_to_html is expensive (called per header) but should be as long as its not nested + 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); + + // convert key to toc_text to handle (a) XML ("a" -> "a"); (b) dupes ("text" -> "text_2") + int num = wkr.Hdr_num(); + Xoh_toc_itm toc_itm = toc_mgr.Add(num, key); + key = toc_itm.Anch(); + + Xop_section_itm itm = new Xop_section_itm(hash.Count(), num, key, wkr.Hdr_bgn(), wkr.Hdr_end()); + hash.Add(key, itm); + } + public void On_src_done(Xomw_heading_wkr wkr) {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_list__merge__tst.java b/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_list__merge__tst.java index a27517de8..a87220343 100644 --- a/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_list__merge__tst.java +++ b/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_list__merge__tst.java @@ -13,3 +13,165 @@ 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.hdrs.sections; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; import gplx.xowa.parsers.hdrs.*; +import org.junit.*; import gplx.core.tests.*; +public class Xop_section_list__merge__tst { + private final Xop_section_list__fxt fxt = new Xop_section_list__fxt(); + @Test public void Basic() { + fxt.Exec__parse + ( "== Hdr 1 ==" + , "Para 1" + , "" + , "== Hdr 2 ==" + , "Para 2" + , "" + , "== Hdr 3 ==" + , "Para 3" + ); + fxt.Test__merge_bry_or_null("Hdr_2", String_.Concat_lines_nl_skip_last + ( "== Hdr 2 ==" + , "Para 2a" + ), String_.Concat_lines_nl_skip_last + ( "== Hdr 1 ==" + , "Para 1" + , "" + , "== Hdr 2 ==" + , "Para 2a" + , "" + , "== Hdr 3 ==" + , "Para 3" + ) + ); + } + @Test public void Nl_many() { + fxt.Exec__parse + ( "== Hdr 1 ==" + , "Para 1" + , "" + , "" + , "" + , "== Hdr 2 ==" + , "Para 2" + , "" + , "" + , "" + , "== Hdr 3 ==" + , "Para 3" + ); + fxt.Test__merge_bry_or_null("Hdr_2", String_.Concat_lines_nl_skip_last + ( "== Hdr 2 ==" + , "Para 2a" + ), String_.Concat_lines_nl_skip_last + ( "== Hdr 1 ==" + , "Para 1" + , "" + , "" + , "" + , "== Hdr 2 ==" + , "Para 2a" + , "" + , "" + , "" + , "== Hdr 3 ==" + , "Para 3" + ) + ); + } + @Test public void Bos() { + fxt.Exec__parse + ( "== Hdr 1 ==" + , "Para 1" + , "" + , "== Hdr 2 ==" + , "Para 2" + ); + fxt.Test__merge_bry_or_null("Hdr_1", String_.Concat_lines_nl_skip_last + ( "== Hdr 1 ==" + , "Para 1a" + ), String_.Concat_lines_nl_skip_last + ( "== Hdr 1 ==" + , "Para 1a" + , "" + , "== Hdr 2 ==" + , "Para 2" + ) + ); + } + @Test public void Bos__ws() { + fxt.Exec__parse + ( "" + , "== Hdr 1 ==" + , "Para 1" + , "" + , "== Hdr 2 ==" + , "Para 2" + ); + fxt.Test__merge_bry_or_null("Hdr_1", String_.Concat_lines_nl_skip_last + ( "== Hdr 1 ==" + , "Para 1a" + ), String_.Concat_lines_nl_skip_last + ( "" + , "== Hdr 1 ==" + , "Para 1a" + , "" + , "== Hdr 2 ==" + , "Para 2" + ) + ); + } + @Test public void Eos() { + fxt.Exec__parse + ( "== Hdr 1 ==" + , "Para 1" + , "" + , "== Hdr 2 ==" + , "Para 2" + ); + fxt.Test__merge_bry_or_null("Hdr_2", String_.Concat_lines_nl_skip_last + ( "== Hdr 2 ==" + , "Para 2a" + ), String_.Concat_lines_nl_skip_last + ( "== Hdr 1 ==" + , "Para 1" + , "" + , "== Hdr 2 ==" + , "Para 2a" + ) + ); + } + @Test public void Lead() { + fxt.Exec__parse + ( "lead para" + , "" + , "== Hdr 1 ==" + , "Para 1" + ); + fxt.Test__merge_bry_or_null("", String_.Concat_lines_nl_skip_last + ( "lead para 1" + , "" + , "lead para 2" + ), String_.Concat_lines_nl_skip_last + ( "lead para 1" + , "" + , "lead para 2" + , "== Hdr 1 ==" + , "Para 1" + ) + ); + } + @Test public void Lead__new() { + fxt.Exec__parse + ( "== Hdr 1 ==" + , "Para 1" + ); + fxt.Test__merge_bry_or_null("", String_.Concat_lines_nl_skip_last + ( "lead para 1" + , "" + ), String_.Concat_lines_nl_skip_last + ( "lead para 1" + , "== Hdr 1 ==" + , "Para 1" + ) + ); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_list__slice__tst.java b/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_list__slice__tst.java index a27517de8..044d9d965 100644 --- a/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_list__slice__tst.java +++ b/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_list__slice__tst.java @@ -13,3 +13,148 @@ 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.hdrs.sections; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; import gplx.xowa.parsers.hdrs.*; +import org.junit.*; import gplx.core.tests.*; import gplx.xowa.htmls.core.htmls.tidy.*; +public class Xop_section_list__slice__tst { + private final Xop_section_list__fxt fxt = new Xop_section_list__fxt(); + @Test public void Basic() { + fxt.Exec__parse + ( "== Hdr 1 ==" + , "Para 1" + , "" + , "== Hdr 2 ==" + , "Para 2" + , "" + , "== Hdr 3 ==" + , "Para 3" + ); + fxt.Test__slice_bry_or_null("Hdr_1" + , "== Hdr 1 ==" + , "Para 1" + ); + fxt.Test__slice_bry_or_null("Hdr_2" + , "== Hdr 2 ==" + , "Para 2" + ); + fxt.Test__slice_bry_or_null("Hdr_3" + , "== Hdr 3 ==" + , "Para 3" + ); + } + @Test public void Covering() { + fxt.Exec__parse + ( "== Hdr 1 ==" + , "Para 1" + , "" + , "=== Hdr 1a ===" + , "Para 1a" + , "" + , "=== Hdr 1b ===" + , "Para 1b" + , "" + , "== Hdr 2 ==" + , "Para 2" + ); + fxt.Test__slice_bry_or_null("Hdr_1" + , "== Hdr 1 ==" + , "Para 1" + , "" + , "=== Hdr 1a ===" + , "Para 1a" + , "" + , "=== Hdr 1b ===" + , "Para 1b" + ); + } + @Test public void Xml() { + fxt.Exec__parse + ( "== Hdr 1 ==" + , "Para 1" + , "" + , "== Hdr 2 ==" + , "Para 2" + ); + fxt.Test__slice_bry_or_null("Hdr_1", String_.Concat_lines_nl_skip_last + ( "== Hdr 1 ==" + , "Para 1" + )); + } + @Test public void Math() { + fxt.Exec__parse + ( "== \\delta ==" + , "Para 1" + , "" + , "== Hdr 2 ==" + , "Para 2" + ); + fxt.Test__slice_bry_or_null(".5Cdelta", String_.Concat_lines_nl_skip_last + ( "== \\delta ==" + , "Para 1" + )); + } + @Test public void Template() { + fxt.Init__template("mock", "''{{{1}}}''"); + fxt.Exec__parse + ( "== {{mock|a}} ==" + , "Para 1" + , "" + , "== Hdr 2 ==" + , "Para 2" + ); + fxt.Test__slice_bry_or_null("a", String_.Concat_lines_nl_skip_last + ( "== {{mock|a}} ==" + , "Para 1" + )); + } + @Test public void Lead() { + fxt.Exec__parse + ( "lead text" + , "" + , "== Hdr 1 ==" + , "Para 1" + , "" + ); + fxt.Test__slice_bry_or_null("" + , "lead text" + ); + } + @Test public void Lead__none() { + fxt.Exec__parse + ( "" + , "== Hdr 1 ==" + , "Para 1" + , "" + ); + fxt.Test__slice_bry_or_null(""); + } + @Test public void Lead__eos() { + fxt.Exec__parse + ( "lead text" + , "" + , "para 1" + , "" + ); + fxt.Test__slice_bry_or_null("" + , "lead text" + , "" + , "para 1" + ); + } +} +class Xop_section_list__fxt { + private final Xop_section_list list = new Xop_section_list(); + private final Xop_fxt parser_fxt = new Xop_fxt(); + public void Init__template(String page, String text) {parser_fxt.Init_defn_add(page, text);} + public void Exec__parse(String... lines) { + list.Parse(parser_fxt.Wiki(), Xow_tidy_mgr_interface_.Noop, Bry_.new_u8(String_.Concat_lines_nl_skip_last(lines))); + } + public void Test__slice_bry_or_null(String key, String... lines) { + String expd = String_.Concat_lines_nl_skip_last(lines); + byte[] actl = list.Slice_bry_or_null(Bry_.new_u8(key)); + Gftest.Eq__ary__lines(expd, actl, key); + } + public void Test__merge_bry_or_null(String key, String edit, String expd) { + byte[] actl = list.Merge_bry_or_null(Bry_.new_u8(key), Bry_.new_u8(edit)); + Gftest.Eq__ary__lines(expd, actl, key); + } +} 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 a27517de8..427cbe0ca 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 @@ -13,3 +13,76 @@ 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.hdrs.sections; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; import gplx.xowa.parsers.hdrs.*; +import gplx.langs.htmls.*; +import gplx.xowa.mediawiki.includes.parsers.headingsOld.*; import gplx.xowa.parsers.hdrs.*; import gplx.xowa.htmls.core.htmls.tidy.*; +public class Xop_section_mgr implements Gfo_invk { + private Xoae_app app; private Xowe_wiki wiki; + private Xow_tidy_mgr_interface tidy_mgr; + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + private byte[] bry__edit_text; + private final Bry_fmt fmt__edit_hint = Bry_fmt.New("") + , fmt__section_editable = Bry_fmt.Auto_nl_apos + ( "[~{edit_text}]" + ) + ; + public boolean Enabled() {return enabled;} private boolean enabled; + public void Init_by_wiki(Xowe_wiki wiki) { + this.app = wiki.Appe(); + this.wiki = wiki; + wiki.App().Cfg().Bind_many_wiki(this, wiki, Cfg__section_editing__enabled); + this.tidy_mgr = wiki.Html_mgr().Tidy_mgr(); + } + public byte[] Slice_section(Xoa_url url, Xoa_ttl ttl, byte[] src) { + // return orig if section_editing not enabled + if (!enabled) return src; + + // return orig if section_key not in qargs + byte[] section_key = url.Qargs_mgr().Get_val_bry_or(Qarg__section_key, null); + if (section_key == null) return src; + + // parse wikitext into list of headers + Xop_section_list section_list = new Xop_section_list().Parse(wiki, tidy_mgr, src); + byte[] rv = section_list.Slice_bry_or_null(section_key); + if (rv == null) { + app.Gui_mgr().Kit().Ask_ok("", "", String_.Format("Section extraction failed!\nPlease do not edit this page else data will be lost!!\n\nwiki={0}\npage={1}\nsection={2}", url.Wiki_bry(), ttl.Full_db(), section_key)); + throw Err_.new_wo_type("section_key not found", "wiki", url.Wiki_bry(), "page", ttl.Full_db(), "section_key", section_key); + } + return rv; + } + public byte[] Merge_section(Xoa_url url, byte[] edit, byte[] orig) { + // return edit if not enabled + if (!enabled) return edit; + + // return edit if section_key not in qargs + byte[] section_key = url.Qargs_mgr().Get_val_bry_or(Qarg__section_key, null); + if (section_key == null) return edit; + + // parse orig + Xop_section_list section_list = new Xop_section_list().Parse(wiki, tidy_mgr, orig); + byte[] rv = section_list.Merge_bry_or_null(section_key, edit); + if (rv == null) + throw Err_.new_wo_type("could not merge section_key", "page", url.To_str(), "section_key", section_key); + return rv; + } + public void Write_html(Bry_bfr bfr, byte[] page_ttl, byte[] section_key, byte[] section_hint) { + if (bry__edit_text == null) { // LAZY: cannot call in Init_by_wiki b/c of circularity; section_mgr is init'd by parser_mgr which is init'd before msg_mgr which is used below + this.bry__edit_text = wiki.Msg_mgr().Val_by_key_obj("editlink"); + 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 + 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); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Cfg__section_editing__enabled)) enabled = m.ReadBool("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + + public static final byte[] Bry__meta = Bry_.new_a7(""); +} diff --git a/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_parser_fxt.java b/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_parser_fxt.java index a27517de8..ba2036eaf 100644 --- a/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_parser_fxt.java +++ b/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_parser_fxt.java @@ -13,3 +13,61 @@ 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.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +class Mwh_doc_parser_fxt { + private final Bry_bfr expd_bfr = Bry_bfr_.New(), actl_bfr = Bry_bfr_.New(); + private final Mwh_doc_parser parser = new Mwh_doc_parser(); + private final Mwh_doc_wkr__itm_bldr wkr = new Mwh_doc_wkr__itm_bldr(); + public Mwh_doc_itm Make_txt (String raw) {return new Mwh_doc_itm(Mwh_doc_itm.Itm_tid__txt , -1, Bry_.new_u8(raw));} + public Mwh_doc_itm Make_txt (String raw, int nde_tid) {return new Mwh_doc_itm(Mwh_doc_itm.Itm_tid__txt , nde_tid, Bry_.new_u8(raw));} + public Mwh_doc_itm Make_comment (String raw) {return new Mwh_doc_itm(Mwh_doc_itm.Itm_tid__comment , -1, Bry_.new_u8(raw));} + public Mwh_doc_itm Make_entity (String raw) {return new Mwh_doc_itm(Mwh_doc_itm.Itm_tid__entity , -1, Bry_.new_u8(raw));} + public Mwh_doc_itm Make_nde_head(String raw) {return new Mwh_doc_itm(Mwh_doc_itm.Itm_tid__nde_head , -1, Bry_.new_u8(raw));} + public Mwh_doc_itm Make_nde_tail(String raw) {return new Mwh_doc_itm(Mwh_doc_itm.Itm_tid__nde_tail , -1, Bry_.new_u8(raw));} + public void Test_parse(String raw, Mwh_doc_itm... expd) { + Mwh_doc_itm[] actl = Exec_parse(raw); + Test_print(expd, actl); + } + public Mwh_doc_itm[] Exec_parse(String raw) { + byte[] bry = Bry_.new_u8(raw); + parser.Parse(wkr, bry, 0, bry.length); + return wkr.To_atr_ary(); + } + public void Test_print(Mwh_doc_itm[] expd_ary, Mwh_doc_itm[] actl_ary) { + int expd_len = expd_ary.length; + int actl_len = actl_ary.length; + int len = expd_len > actl_len ? expd_len : actl_len; + for (int i = 0; i < len; ++i) { + To_bfr(expd_bfr, i < expd_len ? expd_ary[i] : null, actl_bfr, i < actl_len ? actl_ary[i] : null); + } + Tfds.Eq_str_lines(expd_bfr.To_str_and_clear(), actl_bfr.To_str_and_clear()); + } + private void To_bfr(Bry_bfr expd_bfr, Mwh_doc_itm expd_itm, Bry_bfr actl_bfr, Mwh_doc_itm actl_itm) { + To_bfr__main(expd_bfr, expd_itm); To_bfr__main(actl_bfr, actl_itm); + if (expd_itm != null && expd_itm.Nde_tid() != -1) { + To_bfr__nde_tid(expd_bfr, expd_itm); To_bfr__nde_tid(actl_bfr, actl_itm); + } + } + private void To_bfr__main(Bry_bfr bfr, Mwh_doc_itm itm) { + if (itm == null) return; + bfr.Add_str_a7("itm_tid:").Add_int_variable(itm.Itm_tid()).Add_byte_nl(); + bfr.Add_str_a7("txt:").Add(itm.Itm_bry()).Add_byte_nl(); + } + private void To_bfr__nde_tid(Bry_bfr bfr, Mwh_doc_itm itm) { + if (itm == null) return; + bfr.Add_str_a7("nde_tid:").Add_int_variable(itm.Nde_tid()).Add_byte_nl(); + } +} +class Mwh_doc_wkr__itm_bldr implements Mwh_doc_wkr { + private final List_adp list = List_adp_.New(); + public Hash_adp_bry Nde_regy() {return nde_regy;} private final Hash_adp_bry nde_regy = Mwh_doc_wkr_.Nde_regy__mw(); + public void On_atr_each (Mwh_atr_parser mgr, byte[] src, int nde_tid, boolean valid, boolean repeated, boolean key_exists, byte[] key_bry, byte[] val_bry_manual, int[] itm_ary, int itm_idx) {} + public void On_txt_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end) {list.Add(new Mwh_doc_itm(Mwh_doc_itm.Itm_tid__txt , nde_tid, Bry_.Mid(src, itm_bgn, itm_end)));} + public void On_nde_head_bgn (Mwh_doc_parser mgr, byte[] src, int nde_tid, int key_bgn, int key_end) {} + public void On_nde_head_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end, boolean inline) {list.Add(new Mwh_doc_itm(Mwh_doc_itm.Itm_tid__nde_head , nde_tid, Bry_.Mid(src, itm_bgn, itm_end)));} + public void On_nde_tail_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end) {list.Add(new Mwh_doc_itm(Mwh_doc_itm.Itm_tid__nde_tail , nde_tid, Bry_.Mid(src, itm_bgn, itm_end)));} + public void On_comment_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end) {list.Add(new Mwh_doc_itm(Mwh_doc_itm.Itm_tid__comment , nde_tid, Bry_.Mid(src, itm_bgn, itm_end)));} + public void On_entity_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end) {list.Add(new Mwh_doc_itm(Mwh_doc_itm.Itm_tid__entity , nde_tid, Bry_.Mid(src, itm_bgn, itm_end)));} + + public Mwh_doc_itm[] To_atr_ary() {return (Mwh_doc_itm[])list.To_ary_and_clear(Mwh_doc_itm.class);} +} diff --git a/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_parser_tst.java b/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_parser_tst.java index a27517de8..4ceb6a3ef 100644 --- a/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_parser_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_parser_tst.java @@ -13,3 +13,47 @@ 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.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.xowa.parsers.xndes.*; +public class Mwh_doc_parser_tst { + private final Mwh_doc_parser_fxt fxt = new Mwh_doc_parser_fxt(); + @Test public void Text__basic() {fxt.Test_parse("abc" , fxt.Make_txt("abc"));} + @Test public void Comment() {fxt.Test_parse("ac" , fxt.Make_txt("a"), fxt.Make_comment(""), fxt.Make_txt("c"));} + @Test public void Entity() {fxt.Test_parse("a b" , fxt.Make_txt("a"), fxt.Make_entity(" "), fxt.Make_txt("b"));} + @Test public void Fail__inline_eos() {fxt.Test_parse("ad" , fxt.Make_txt("ad"));} + @Test public void Node__inline() {fxt.Test_parse("ac" , fxt.Make_txt("a"), fxt.Make_nde_head("") , fxt.Make_txt("c"));} + @Test public void Node__pair() {fxt.Test_parse("acd" , fxt.Make_txt("a"), fxt.Make_nde_head("") , fxt.Make_txt("c"), fxt.Make_nde_tail(""), fxt.Make_txt("d"));} + @Test public void Atrs__pair() { + fxt.Test_parse("
      a
      " + , fxt.Make_nde_head("
      ") + , fxt.Make_txt("a") + , fxt.Make_nde_tail("
      ")); + } + @Test public void Atrs__inline() { + fxt.Test_parse("a
      b" + , fxt.Make_txt("a") + , fxt.Make_nde_head("
      ") + , fxt.Make_txt("b")); + } + @Test public void Node__single_only() { + fxt.Test_parse("a
      b
      c" + , fxt.Make_nde_head("") + , fxt.Make_txt("a", Xop_xnde_tag_.Tid__b) + , fxt.Make_nde_head("
      ") + , fxt.Make_txt("b", Xop_xnde_tag_.Tid__b) // not
      + , fxt.Make_nde_tail("
      ") + , fxt.Make_txt("c", Xop_xnde_tag_.Tid__null) + ); + } + @Test public void Node__pre() { + fxt.Test_parse("
      a
      b
      c" + , fxt.Make_nde_head("
      ")
      +		, fxt.Make_txt("a", Xop_xnde_tag_.Tid__pre)
      +		, fxt.Make_nde_head("
      ") + , fxt.Make_txt("b", Xop_xnde_tag_.Tid__pre) //
       not 
      + , fxt.Make_nde_tail("
      ") + , fxt.Make_txt("c", Xop_xnde_tag_.Tid__null) + ); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_wkr.java b/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_wkr.java index a27517de8..462816f70 100644 --- a/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_wkr.java @@ -13,3 +13,13 @@ 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.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public interface Mwh_doc_wkr extends Mwh_atr_wkr { + Hash_adp_bry Nde_regy(); + void On_txt_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end); + void On_nde_head_bgn(Mwh_doc_parser mgr, byte[] src, int nde_tid, int key_bgn, int key_end); + void On_nde_head_end(Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end, boolean inline); + void On_nde_tail_end(Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end); + void On_comment_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end); + void On_entity_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end); +} diff --git a/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_wkr_.java b/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_wkr_.java index a27517de8..26e467fe9 100644 --- a/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_wkr_.java +++ b/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_wkr_.java @@ -13,3 +13,17 @@ 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.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.parsers.xndes.*; +public class Mwh_doc_wkr_ { + public static Hash_adp_bry Nde_regy__mw() { + Xop_xnde_tag[] ary = Xop_xnde_tag_.Ary; + int len = ary.length; + Hash_adp_bry rv = Hash_adp_bry.ci_a7(); + for (int i = 0; i < len; ++i) { + Xop_xnde_tag itm = ary[i]; + rv.Add(itm.Name_bry(), itm); + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_wkr__atr_bldr.java b/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_wkr__atr_bldr.java index a27517de8..fbf804f6a 100644 --- a/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_wkr__atr_bldr.java +++ b/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_doc_wkr__atr_bldr.java @@ -13,3 +13,33 @@ 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.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Mwh_doc_wkr__atr_bldr implements Mwh_doc_wkr { + private final List_adp list = List_adp_.New(); + public Hash_adp_bry Nde_regy() {return null;} + public void On_atr_each(Mwh_atr_parser mgr, byte[] src, int nde_tid, boolean valid, boolean repeated, boolean key_exists, byte[] key_bry, byte[] val_bry_manual, int[] data_ary, int itm_idx) { + int atr_bgn = data_ary[itm_idx + Mwh_atr_mgr.Idx_atr_bgn]; + int atr_end = data_ary[itm_idx + Mwh_atr_mgr.Idx_atr_end]; + int key_bgn = data_ary[itm_idx + Mwh_atr_mgr.Idx_key_bgn]; + int key_end = data_ary[itm_idx + Mwh_atr_mgr.Idx_key_end]; + int val_bgn = data_ary[itm_idx + Mwh_atr_mgr.Idx_val_bgn]; + int val_end = data_ary[itm_idx + Mwh_atr_mgr.Idx_val_end]; + int eql_pos = data_ary[itm_idx + Mwh_atr_mgr.Idx_eql_pos]; + int qte_tid = data_ary[itm_idx + Mwh_atr_mgr.Idx_atr_utl]; + qte_tid = Mwh_atr_itm_.Calc_qte_tid(qte_tid); + if (!key_exists) val_bry_manual = key_bry; + Mwh_atr_itm atr = new Mwh_atr_itm(src, valid, repeated, key_exists, atr_bgn, atr_end, key_bgn, key_end, key_bry, val_bgn, val_end, val_bry_manual, eql_pos, qte_tid); + list.Add(atr); + } + public void On_txt_end(Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end) {} + public void On_nde_head_bgn(Mwh_doc_parser mgr, byte[] src, int nde_tid, int key_bgn, int key_end) {} + public void On_nde_head_end(Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end, boolean inline) {} + public void On_nde_tail_end(Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end) {} + public void On_comment_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end) {} + public void On_entity_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end) {} + + public Mwh_atr_itm[] To_atr_ary() {return (Mwh_atr_itm[])list.To_ary_and_clear(Mwh_atr_itm.class);} + public int Atrs__len() {return list.Len();} + public Mwh_atr_itm Atrs__get_at(int i) {return (Mwh_atr_itm)list.Get_at(i);} + public void Atrs__clear() {list.Clear();} +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/lists/HierPosAryBldr.java b/400_xowa/src/gplx/xowa/parsers/lists/HierPosAryBldr.java index a27517de8..a7cc21885 100644 --- a/400_xowa/src/gplx/xowa/parsers/lists/HierPosAryBldr.java +++ b/400_xowa/src/gplx/xowa/parsers/lists/HierPosAryBldr.java @@ -13,3 +13,47 @@ 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.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.strings.*; +public class HierPosAryBldr { + int[] ary; int aryIdx = -1; int root = -1; + public HierPosAryBldr(int ary_max) {ary = new int[ary_max]; this.Init();} + public void Init() { + int ary_max = ary.length; + for (int i = 0; i < ary_max; i++) + ary[i] = 0; + aryIdx = -1; + root = 0; + } + public void MoveDown() { + aryIdx += 1; + if (aryIdx == 0) + ary[aryIdx] = root; + else + ary[aryIdx] = 0; + } + public void MoveUp() { + aryIdx -= 1; + MoveNext(); + } + public void MoveNext() { + if (aryIdx == -1) + root += 1; + else + ary[aryIdx] += 1; + } + public boolean Dirty() {return aryIdx > -1 || root > 0;} + public int[] XtoIntAry() { + if (aryIdx == -1) return Int_ary_.Empty; + int[] rv = new int[aryIdx + 1]; + for (int i = 0; i < aryIdx + 1; i++) + rv[i] = ary[i]; + return rv; + } + public String To_str() { + String_bldr sb = String_bldr_.new_(); + for (int i = 0; i < aryIdx; i++) + sb.Add_spr_unless_first(Int_.To_str(ary[i]), " ", i); + return sb.To_str(); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lists/HierPosAryBldr_tst.java b/400_xowa/src/gplx/xowa/parsers/lists/HierPosAryBldr_tst.java index a27517de8..b48625915 100644 --- a/400_xowa/src/gplx/xowa/parsers/lists/HierPosAryBldr_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lists/HierPosAryBldr_tst.java @@ -13,3 +13,51 @@ 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.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class HierPosAryBldr_tst { + @Before public void init() {bldr.Init();} HierPosAryBldr bldr = new HierPosAryBldr(256); + @Test public void Basic() { + tst_ary(Int_ary_.Empty); + } + @Test public void Move_d() { + bldr.MoveDown(); + tst_ary(0); + } + @Test public void Move_dd() { + bldr.MoveDown(); + bldr.MoveDown(); + tst_ary(0, 0); + } + @Test public void Move_ddu() { + bldr.MoveDown(); + bldr.MoveDown(); + bldr.MoveUp(); + tst_ary(1); + } + @Test public void Move_ddud() { + bldr.MoveDown(); + bldr.MoveDown(); + bldr.MoveUp(); + bldr.MoveDown(); + tst_ary(1, 0); + } + @Test public void Move_dud() { + bldr.MoveDown(); + bldr.MoveUp(); + bldr.MoveDown(); + tst_ary(1); + } + @Test public void Move_dn() { + bldr.MoveDown(); + bldr.MoveNext(); + tst_ary(1); + } + @Test public void Move_ddn() { + bldr.MoveDown(); + bldr.MoveDown(); + bldr.MoveNext(); + tst_ary(0, 1); + } + private void tst_ary(int... expd) {Tfds.Eq_ary(expd, bldr.XtoIntAry());} +} diff --git a/400_xowa/src/gplx/xowa/parsers/lists/Xop_colon_lxr.java b/400_xowa/src/gplx/xowa/parsers/lists/Xop_colon_lxr.java index a27517de8..bc1a5536f 100644 --- a/400_xowa/src/gplx/xowa/parsers/lists/Xop_colon_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/lists/Xop_colon_lxr.java @@ -13,3 +13,27 @@ 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.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +public class Xop_colon_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_colon;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Byte_ascii.Colon, 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) { + Xop_list_wkr listCtx = ctx.List(); + if (listCtx.Dd_chk()) { // handle ";a:b" construct; REF.MW: Parser.php|doBlockLevels|; title : definition text + int prv_pos = cur_pos -1 ; + if ( ctx.Cur_tkn_tid() != Xop_tkn_itm_.Tid_lnki // ignore if inside link + && prv_pos > 0 + && src[prv_pos] != Byte_ascii.Nl // only consider ":" which are not preceded by \n; DATE:2014-07-11 TODO_OLD: emulate Parser.php|findColonNoLinks which does much more logic to see if ";a:b" construct should apply + ) { + listCtx.Dd_chk_(false); + return listCtx.MakeTkn_bgn(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos); + } + } + ctx.Subs_add(root, tkn_mkr.Colon(bgn_pos, cur_pos)); + return cur_pos; + } + public static final Xop_colon_lxr Instance = new Xop_colon_lxr(); +} diff --git a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_lxr.java b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_lxr.java index a27517de8..d7b6892f5 100644 --- a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_lxr.java @@ -13,3 +13,14 @@ 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.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +public class Xop_list_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_list;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {Add_ary(core_trie, this, Xop_list_tkn_.Hook_ul, Xop_list_tkn_.Hook_ol, Xop_list_tkn_.Hook_dt, Xop_list_tkn_.Hook_dd);} + public void Init_by_lang(Xol_lang_itm lang, Btrie_fast_mgr core_trie) {} + public void Term(Btrie_fast_mgr core_trie) {} + private void Add_ary(Btrie_fast_mgr core_trie, Object val, byte[]... ary) {for (byte[] itm : ary) core_trie.Add(itm, val);} + 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) {return ctx.List().MakeTkn_bgn(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos);} + public static final Xop_list_lxr Instance = new Xop_list_lxr(); Xop_list_lxr() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_tkn.java b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_tkn.java index a27517de8..8051eaab3 100644 --- a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_tkn.java @@ -13,3 +13,18 @@ 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.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_list_tkn extends Xop_tkn_itm_base { + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_list;} + public int List_uid() {return list_uid;} public Xop_list_tkn List_uid_(int v) {list_uid = v; return this;} private int list_uid = -1; + public byte List_bgn() {return list_bgn;} private byte list_bgn; + public byte List_itmTyp() {return list_itmTyp;} public Xop_list_tkn List_itmTyp_(byte v) {list_itmTyp = v; return this;} private byte list_itmTyp = Xop_list_tkn_.List_itmTyp_null; + public int[] List_path() {return path;} public Xop_list_tkn List_path_(int... v) {path = v; return this;} private int[] path = Int_ary_.Empty; + public int List_path_idx() {return path[path.length - 1];} + public boolean List_sub_first() {return List_path_idx() == 0;} + public byte List_sub_last() {return list_sub_last;} public Xop_list_tkn List_sub_last_(byte v) {list_sub_last = v; return this;} private byte list_sub_last = Bool_.__byte; + public static Xop_list_tkn bgn_(int bgn, int end, byte list_itmTyp, int symLen) {return new Xop_list_tkn(bgn, end, Bool_.Y_byte, list_itmTyp);} + public static Xop_list_tkn end_(int pos, byte list_itmTyp) {return new Xop_list_tkn(pos, pos, Bool_.N_byte, list_itmTyp);} + public Xop_list_tkn(int bgn, int end, byte bgnEndType, byte list_itmTyp) {this.Tkn_ini_pos(false, bgn, end); this.list_bgn = bgnEndType; this.list_itmTyp = list_itmTyp;} + public static final Xop_list_tkn Null = new Xop_list_tkn(); Xop_list_tkn() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_tkn_.java b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_tkn_.java index a27517de8..e60d327f7 100644 --- a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_tkn_.java +++ b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_tkn_.java @@ -13,3 +13,40 @@ 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.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_list_tkn_ { + public static final byte[] + Hook_ul = new byte[] {Byte_ascii.Nl, Byte_ascii.Star} , Hook_ol = new byte[] {Byte_ascii.Nl, Byte_ascii.Hash} + , Hook_dt = new byte[] {Byte_ascii.Nl, Byte_ascii.Semic} , Hook_dd = new byte[] {Byte_ascii.Nl, Byte_ascii.Colon}; + public static final byte List_itmTyp_null = 0, List_itmTyp_ul = Byte_ascii.Star, List_itmTyp_ol = Byte_ascii.Hash, List_itmTyp_dt = Byte_ascii.Semic, List_itmTyp_dd = Byte_ascii.Colon; + public static final String Str_li = "li", Str_ol = "ol", Str_ul = "ul", Str_dl = "dl", Str_dt = "dt", Str_dd = "dd"; + public static final byte[] Byt_li = Bry_.new_a7(Str_li), Byt_ol = Bry_.new_a7(Str_ol), Byt_ul = Bry_.new_a7(Str_ul) + , Byt_dl = Bry_.new_a7(Str_dl), Byt_dt = Bry_.new_a7(Str_dt), Byt_dd = Bry_.new_a7(Str_dd); + public static byte[] XmlTag_lst(byte b) { + switch (b) { + case List_itmTyp_ul: return Byt_ul; + case List_itmTyp_ol: return Byt_ol; + case List_itmTyp_dt: + case List_itmTyp_dd: return Byt_dl; + default: throw Err_.new_unhandled(b); + } + } + public static byte[] XmlTag_itm(byte b) { + switch (b) { + case List_itmTyp_ul: + case List_itmTyp_ol: return Byt_li; + case List_itmTyp_dt: return Byt_dt; + case List_itmTyp_dd: return Byt_dd; + default: throw Err_.new_unhandled(b); + } + } + public static byte Char_lst(byte b) { + switch (b) { + case List_itmTyp_ul: return Byte_ascii.Star; + case List_itmTyp_ol: return Byte_ascii.Hash; + case List_itmTyp_dt: return Byte_ascii.Semic; + case List_itmTyp_dd: return Byte_ascii.Colon; + default: throw Err_.new_unhandled(b); + } + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_tkn_chkr.java b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_tkn_chkr.java index a27517de8..62abb4b4d 100644 --- a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_tkn_chkr.java +++ b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_tkn_chkr.java @@ -13,3 +13,23 @@ 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.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xop_list_tkn_chkr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Xop_list_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_list;} + public int List_uid() {return list_uid;} public Xop_list_tkn_chkr List_uid_(int v) {list_uid = v; return this;} private int list_uid = -1; + public byte List_bgn() {return list_bgn;} public Xop_list_tkn_chkr List_bgn_(byte v) {list_bgn = v; return this;} private byte list_bgn; + public byte List_itmTyp() {return list_itmTyp;} public Xop_list_tkn_chkr List_itmTyp_(byte v) {list_itmTyp = v; return this;} private byte list_itmTyp = Xop_list_tkn_.List_itmTyp_null; + public int[] List_path() {return list_path;} public Xop_list_tkn_chkr List_path_(int... v) {list_path = v; return this;} private int[] list_path = Int_ary_.Empty; + public byte List_sub_last() {return list_sub_last;} public Xop_list_tkn_chkr List_sub_last_(byte v) {list_sub_last = v; return this;} private byte list_sub_last = Bool_.__byte; + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { + Xop_list_tkn actl = (Xop_list_tkn)actl_obj; + err += mgr.Tst_val(list_uid == -1, path, "list_uid", list_uid, actl.List_uid()); + err += mgr.Tst_val(list_bgn == 0, path, "list_bgn", list_bgn, actl.List_bgn()); + err += mgr.Tst_val(list_itmTyp == Xop_list_tkn_.List_itmTyp_null, path, "list_itmTyp", list_itmTyp, actl.List_itmTyp()); + err += mgr.Tst_val(list_sub_last == Bool_.__byte, path, "list_sub_last", list_sub_last, actl.List_sub_last()); + err += mgr.Tst_val(list_path == Int_ary_.Empty, path, "list_path", Array_.To_str(list_path), Array_.To_str(actl.List_path())); + return err; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr.java b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr.java index a27517de8..8f92ff1ad 100644 --- a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr.java @@ -13,3 +13,172 @@ 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.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.parsers.tblws.*; import gplx.xowa.parsers.xndes.*; +public class Xop_list_wkr implements Xop_ctx_wkr { + private int listId = 0; byte[] curSymAry = new byte[Max_list_depth]; int curSymLen = 0; byte[] prvSymAry = Bry_.Empty; + private HierPosAryBldr posBldr = new HierPosAryBldr(Max_list_depth); + private boolean SymAry_fill_overflow; + public void Ctor_ctx(Xop_ctx ctx) {} + public void Page_bgn(Xop_ctx ctx, Xop_root_tkn root) {Reset(0);} + public void Page_end(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int src_len) {} + public boolean List_dirty() {return posBldr.Dirty();} + public boolean Dd_chk() {return dd_chk;} public Xop_list_wkr Dd_chk_(boolean v) {dd_chk = v; return this;} private boolean dd_chk; + public void AutoClose(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, Xop_tkn_itm tkn) { + // NOTE: list_tkns can not be explicitly closed, so auto-close will happen for all items + MakeTkn_end(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, (Xop_list_tkn)tkn, Bool_.Y_byte); + Reset(listId + 1); + ctx.Para().Process_block__bgn_n__end_y(Xop_xnde_tag_.Tag__ul); + } + public int MakeTkn_bgn(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos) {// REF.MW: Parser|doBlockLevels + if (bgn_pos == Xop_parser_.Doc_bgn_bos) bgn_pos = 0; // do not allow -1 pos + + // pop hdr if exists; EX: \n== a ==\n*b; \n* needs to close hdr + int acsPos = ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_hdr); + if (acsPos != -1) ctx.Stack_pop_til(root, src, acsPos, true, bgn_pos, cur_pos, Xop_tkn_itm_.Tid_list); + + // close apos + ctx.Apos().End_frame(ctx, root, src, bgn_pos, false); + byte symByt = src[cur_pos - 1]; // -1 b/c symByt is byte before curByt; EX: \n*a; cur_pos is at a; want to get * + int prvSymLen = curSymLen; + cur_pos = SymAry_fill(src, cur_pos, src_len, symByt); + symByt = src[cur_pos - 1]; // NOTE: get symByt again b/c cur_pos may have changed; EX: "#*"; # may have triggered list, but last symByt should be * + if (SymAry_fill_overflow) return ctx.Lxr_make_txt_(cur_pos); + PrvItm_compare(); + ctx.Para().Process_block__bgn__nl_w_symbol(ctx, root, src, bgn_pos, cur_pos - 1, Xop_xnde_tag_.Tag__li); // -1 b/c cur_pos includes sym_byte; EX: \n*; pass li; should pass correct tag, but for purposes of para_wkr,
    • doesn't matter + if (prvSymMatch) { + PopTil(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, Bool_.N_byte); + posBldr.MoveNext(); + prvSymAry = Xop_list_wkr_.MakeSymAry(curSymAry, curSymLen); + Xop_list_tkn prvItm = tkn_mkr.List_bgn(bgn_pos, cur_pos, curSymAry[curSymLen - 1], curSymLen).List_path_(posBldr.XtoIntAry()).List_uid_(listId); + ctx.Subs_add_and_stack(root, prvItm); + ctx.Empty_ignored_y_(); + } + else { + for (int i = prvSymLen; i > commonSymLen; i--) { // close all discontinued itms: EX: ##\n#\n + PopTil(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, Bool_.Y_byte); + posBldr.MoveUp(); + } + if (commonSymLen == 0 && prvSymLen != 0) { // nothing in common; reset list + listId++; + posBldr.Init(); + } + if (curSymLen == commonSymLen) { // add another itm if continuing; EX: #\n#\n + PopTil(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, Bool_.N_byte); + if ((prvSymLen - curSymLen) > 0 // moving up many levels; do not open new list; just MoveNext; EX: #1\n###3\n##2 + && curSymLen != 1) { // do not moveNext if at level 1; this has to do with strange incrementing logic in posBldr at rootLvl + posBldr.MoveNext(); + } + else { + posBldr.MoveUp(); posBldr.MoveDown(); + } + prvSymAry = Xop_list_wkr_.MakeSymAry(curSymAry, curSymLen); + symByt = src[cur_pos - 1]; + Xop_list_tkn prvItm = tkn_mkr.List_bgn(bgn_pos, cur_pos, symByt, curSymLen).List_path_(posBldr.XtoIntAry()).List_uid_(listId); + ctx.Subs_add_and_stack(root, prvItm); + ctx.Empty_ignored_y_(); + } + for (int i = commonSymLen; i < curSymLen; i++) { // open new itms; EX: #\n##\n + posBldr.MoveDown(); + symByt = curSymAry[i]; + prvSymAry = Xop_list_wkr_.MakeSymAry(curSymAry, curSymLen); + Xop_list_tkn prvItm = tkn_mkr.List_bgn(bgn_pos, cur_pos, symByt, i + List_adp_.Base1).List_path_(posBldr.XtoIntAry()).List_uid_(listId); + ctx.Subs_add_and_stack(root, prvItm); + ctx.Empty_ignored_y_(); + } + } + if (allDd) { // NOTE: if indent && next == {| then invoke table; EX: ":::{|" + int tblw_bgn = Bry_find_.Find_fwd_while(src, cur_pos, src_len, Byte_ascii.Space); // skip spaces; EX: ": {|" DATE:2017-01-26 + if (tblw_bgn < src_len - 2 && src[tblw_bgn] == '{' && src[tblw_bgn + 1] == '|') // check if next chars are "{|" + return ctx.Tblw().Make_tkn_bgn(ctx, tkn_mkr, root, src, src_len, tblw_bgn, tblw_bgn+ 2, false, Xop_tblw_wkr.Tblw_type_tb, Xop_tblw_wkr.Called_from_list, -1, -1); // NOTE: ws_enabled must be set to true; see test for Adinkras; Cato the Elder + } + dd_chk = symByt == Xop_list_tkn_.List_itmTyp_dt; + return cur_pos; + } + public void MakeTkn_end(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, Xop_list_tkn bgn, byte sub_last) { + // boolean empty_ignored = ctx.Empty_ignored(); // commented; see below; DATE:2014-06-24 + Xop_tkn_itm end_tkn = tkn_mkr.List_end(bgn_pos, bgn.List_itmTyp()).List_path_(bgn.List_path()).List_uid_(listId).List_sub_last_(sub_last); + ctx.Subs_add(root, end_tkn); + // if (empty_ignored) ctx.Empty_ignore(root, bgn.Tkn_sub_idx()); // commented; code was incorrectly deactivating "*a" when "
    • " encountered; PAGE:en.w:Bristol_Bullfinch DATE:2014-06-24 + ctx.Para().Process_block__bgn_n__end_y(Xop_xnde_tag_.Tag__ul); + } + private Xop_list_tkn PopTil(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, byte subLast) { + int acs_pos = ctx.Stack_idx_find_but_stop_at_tbl(Xop_tkn_itm_.Tid_list); + if (acs_pos == -1) return null; + Xop_list_tkn rv = (Xop_list_tkn)ctx.Stack_pop_til(root, src, acs_pos, false, bgn_pos, cur_pos, Xop_tkn_itm_.Tid_list); + MakeTkn_end(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, rv, subLast); + return rv; + } + private void PrvItm_compare() { + int prvSymLen = prvSymAry.length; + prvSymMatch = curSymLen == prvSymLen; commonSymLen = 0; + for (int i = 0; i < curSymLen; i++) { + if (i < prvSymLen && (Xop_list_wkr_.Compare_normalize(curSymAry[i]) == Xop_list_wkr_.Compare_normalize(prvSymAry[i]))) { + commonSymLen = i + 1; + } + else { + prvSymMatch = false; + break; + } + } + } boolean prvSymMatch; int commonSymLen = 0; boolean allDd = false; + private int SymAry_fill(byte[] src, int cur_pos, int src_len, byte curByt) { + curSymLen = 0; + curSymAry[curSymLen++] = curByt; + allDd = true; + boolean loop = true; + SymAry_fill_overflow = false; + while (loop) { + if (cur_pos == src_len) break; + if (curSymLen == Max_list_depth) { // WORKAROUND: xowa imposes max list depth of 256; MW is unlimited; may change for future release but 256 should accomodate all real-world usages + boolean stop = false; + for (int i = cur_pos; i < src_len; i++) { + curByt = src[i]; + switch (curByt) { + case Byte_ascii.Star: + case Byte_ascii.Hash: + case Byte_ascii.Semic: + case Byte_ascii.Colon: + cur_pos = i; + break; + default: + stop = true; + break; + } + if (stop) break; + } + for (int i = 0; i < Max_list_depth; i++) + curSymAry[i] = Byte_ascii.Null; + curSymLen = 0; + SymAry_fill_overflow = true; + return cur_pos; + } + curByt = src[cur_pos]; + switch (curByt) { + case Byte_ascii.Star: + case Byte_ascii.Hash: + case Byte_ascii.Semic: + curSymAry[curSymLen++] = curByt; + cur_pos++; + allDd = false; + break; + case Byte_ascii.Colon: + curSymAry[curSymLen++] = curByt; + cur_pos++; + break; + default: + loop = false; + break; + } + } + return cur_pos; + } + private void Reset(int newListId) { + posBldr.Init(); + curSymLen = 0; + prvSymAry = Bry_.Empty; + dd_chk = false; + listId = newListId; + } + public static final int Max_list_depth = 256; +} diff --git a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr_.java b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr_.java index a27517de8..21562e071 100644 --- a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr_.java +++ b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr_.java @@ -13,3 +13,40 @@ 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.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_list_wkr_ { + public static byte[] MakeSymAry(byte[] curSymAry, int curSymLen) { + byte[] rv = new byte[curSymLen]; + for (int i = 0; i < curSymLen; i++) + rv[i] = curSymAry[i]; + return rv; + } + public static byte Compare_normalize(byte b) { // convert : to ; for sake of determining levels; EX: ";:" is actually same group + switch (b) { + case Byte_ascii.Star: + case Byte_ascii.Hash: + case Byte_ascii.Semic: return b; + case Byte_ascii.Colon: return Byte_ascii.Semic; + default: throw Err_.new_unhandled(b); + } + } + public static void Close_list_if_present(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int bgn_pos, int cur_pos) {// close all list tkns on stack; EX: ***\n should close all 3 stars; used to only close 1 + if (ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tmpl_invk) != Xop_ctx.Stack_not_found) return; // list is inside template; do not close; + int acs_pos = -1, acs_len = ctx.Stack_len(); + for (int i = acs_len - 1; i > -1; i--) { // loop backwards until earliest list tkn + byte cur_acs_tid = ctx.Stack_get(i).Tkn_tid(); + switch (cur_acs_tid) { + case Xop_tkn_itm_.Tid_tblw_tb: + case Xop_tkn_itm_.Tid_tblw_tc: + case Xop_tkn_itm_.Tid_tblw_te: + case Xop_tkn_itm_.Tid_tblw_td: + case Xop_tkn_itm_.Tid_tblw_th: + case Xop_tkn_itm_.Tid_tblw_tr: i = -1; break; // tblw: stop loop; do not close a list above tbl; EX: ": {| |- *a |b }" should not close ":"; stops at "|-" + case Xop_tkn_itm_.Tid_list: acs_pos = i; break; // list: update acs_pos + default: break; // else: keep looping + } + } + if (acs_pos == Xop_ctx.Stack_not_found) return; // no list tokens found; exit + ctx.Stack_pop_til(root, src, acs_pos, true, bgn_pos, cur_pos, Xop_tkn_itm_.Tid_list); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr_basic_tst.java b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr_basic_tst.java index a27517de8..73aa480a4 100644 --- a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr_basic_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr_basic_tst.java @@ -13,3 +13,339 @@ 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.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_list_wkr_basic_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @After public void term() {fxt.Init_para_n_();} + @Test public void List_1() { + fxt.Test_parse_page_wiki("\n*a" + , fxt.tkn_list_bgn_(0, 2, Xop_list_tkn_.List_itmTyp_ul).List_path_(0).List_uid_(0) + , fxt.tkn_txt_(2, 3) + , fxt.tkn_list_end_(3).List_path_(0).List_uid_(0) + ); + } + @Test public void Bos() { + fxt.Test_parse_page_wiki("*a" + , fxt.tkn_list_bgn_(0, 1, Xop_list_tkn_.List_itmTyp_ul).List_path_(0).List_uid_(0) + , fxt.tkn_txt_(1, 2) + , fxt.tkn_list_end_(2).List_path_(0).List_uid_(0) + ); + } + @Test public void List_1_2() { + fxt.Test_parse_page_wiki("\n*a\n**b" + , fxt.tkn_list_bgn_(0, 2, Xop_list_tkn_.List_itmTyp_ul).List_path_(0).List_uid_(0) + , fxt.tkn_txt_(2, 3) + , fxt.tkn_list_bgn_(3, 6, Xop_list_tkn_.List_itmTyp_ul).List_path_(0, 0).List_uid_(0) + , fxt.tkn_txt_(6, 7) + , fxt.tkn_list_end_(7).List_path_(0, 0) + , fxt.tkn_list_end_(7).List_path_(0) + ); + } + @Test public void List_1_2_2() { + fxt.Test_parse_page_wiki("\n*a\n**b\n**c" + , fxt.tkn_list_bgn_(0, 2, Xop_list_tkn_.List_itmTyp_ul).List_path_(0).List_uid_(0) + , fxt.tkn_txt_(2, 3) + , fxt.tkn_list_bgn_(3, 6, Xop_list_tkn_.List_itmTyp_ul).List_path_(0, 0).List_uid_(0) + , fxt.tkn_txt_(6, 7) + , fxt.tkn_list_end_(7).List_path_(0, 0) + , fxt.tkn_list_bgn_(7, 10, Xop_list_tkn_.List_itmTyp_ul).List_path_(0, 1).List_uid_(0) + , fxt.tkn_txt_(10, 11) + , fxt.tkn_list_end_(11).List_path_(0, 1) + , fxt.tkn_list_end_(11).List_path_(0) + ); + } + @Test public void List_1_2_3() { + fxt.Test_parse_page_wiki("\n*a\n**b\n***c" + , fxt.tkn_list_bgn_(0, 2, Xop_list_tkn_.List_itmTyp_ul).List_path_(0).List_uid_(0) + , fxt.tkn_txt_(2, 3) + , fxt.tkn_list_bgn_(3, 6, Xop_list_tkn_.List_itmTyp_ul).List_path_(0, 0).List_uid_(0) + , fxt.tkn_txt_(6, 7) + , fxt.tkn_list_bgn_(7, 11, Xop_list_tkn_.List_itmTyp_ul).List_path_(0, 0, 0).List_uid_(0) + , fxt.tkn_txt_(11, 12) + , fxt.tkn_list_end_(12).List_path_(0, 0, 0) + , fxt.tkn_list_end_(12).List_path_(0, 0) + , fxt.tkn_list_end_(12).List_path_(0) + ); + } + @Test public void List_2() { + fxt.Test_parse_page_wiki("\n**a" + , fxt.tkn_list_bgn_(0, 3, Xop_list_tkn_.List_itmTyp_ul).List_path_(0).List_uid_(0) + , fxt.tkn_list_bgn_(0, 3, Xop_list_tkn_.List_itmTyp_ul).List_path_(0, 0).List_uid_(0) + , fxt.tkn_txt_(3, 4) + , fxt.tkn_list_end_(4).List_path_(0, 0) + , fxt.tkn_list_end_(4).List_path_(0) + ); + } + @Test public void List_1_3() { + fxt.Test_parse_page_wiki("\n*a\n***b" + , fxt.tkn_list_bgn_(0, 2, Xop_list_tkn_.List_itmTyp_ul).List_path_(0).List_uid_(0) + , fxt.tkn_txt_(2, 3) + , fxt.tkn_list_bgn_(3, 7, Xop_list_tkn_.List_itmTyp_ul).List_path_(0, 0).List_uid_(0) + , fxt.tkn_list_bgn_(3, 7, Xop_list_tkn_.List_itmTyp_ul).List_path_(0, 0, 0).List_uid_(0) + , fxt.tkn_txt_(7, 8) + , fxt.tkn_list_end_(8).List_path_(0, 0, 0) + , fxt.tkn_list_end_(8).List_path_(0, 0) + , fxt.tkn_list_end_(8).List_path_(0) + ); + } + @Test public void List_1_2_1() { + fxt.Test_parse_page_wiki("\n*a\n**b\n*c" + , fxt.tkn_list_bgn_(0, 2, Xop_list_tkn_.List_itmTyp_ul).List_path_(0).List_uid_(0) + , fxt.tkn_txt_(2, 3) + , fxt.tkn_list_bgn_(3, 6, Xop_list_tkn_.List_itmTyp_ul).List_path_(0, 0).List_uid_(0) + , fxt.tkn_txt_(6, 7) + , fxt.tkn_list_end_(7).List_path_(0, 0) + , fxt.tkn_list_end_(7).List_path_(0) + , fxt.tkn_list_bgn_(7, 9, Xop_list_tkn_.List_itmTyp_ul).List_path_(1).List_uid_(0) + , fxt.tkn_txt_(9, 10) + , fxt.tkn_list_end_(10).List_path_(1) + ); + } + @Test public void List_1_1_1() { + fxt.Test_parse_page_wiki("\n*a\n*b\n*c" + , fxt.tkn_list_bgn_(0, 2, Xop_list_tkn_.List_itmTyp_ul).List_path_(0).List_uid_(0) + , fxt.tkn_txt_(2, 3) + , fxt.tkn_list_end_(3).List_path_(0) + , fxt.tkn_list_bgn_(3, 5, Xop_list_tkn_.List_itmTyp_ul).List_path_(1).List_uid_(0) + , fxt.tkn_txt_(5, 6) + , fxt.tkn_list_end_(6).List_path_(1) + , fxt.tkn_list_bgn_(6, 8, Xop_list_tkn_.List_itmTyp_ul).List_path_(2).List_uid_(0) + , fxt.tkn_txt_(8, 9) + , fxt.tkn_list_end_(9).List_path_(2) + ); + } + @Test public void List_1___1() { + fxt.Test_parse_page_wiki("\n*a\n\n*b" + , fxt.tkn_list_bgn_(0, 2, Xop_list_tkn_.List_itmTyp_ul).List_path_(0).List_uid_(0) + , fxt.tkn_txt_(2, 3) + , fxt.tkn_list_end_(3).List_path_(0) + , fxt.tkn_nl_char_len1_(3) + , fxt.tkn_list_bgn_(4, 6, Xop_list_tkn_.List_itmTyp_ul).List_path_(0).List_uid_(1) + , fxt.tkn_txt_(6, 7) + , fxt.tkn_list_end_(7).List_path_(0) + ); + } + @Test public void List_1_3_1() { + fxt.Test_parse_page_wiki("\n*a\n***b\n*c" + , fxt.tkn_list_bgn_(0, 2, Xop_list_tkn_.List_itmTyp_ul).List_path_(0).List_uid_(0) + , fxt.tkn_txt_(2, 3) + , fxt.tkn_list_bgn_(3, 7, Xop_list_tkn_.List_itmTyp_ul).List_path_(0, 0).List_uid_(0) + , fxt.tkn_list_bgn_(3, 7, Xop_list_tkn_.List_itmTyp_ul).List_path_(0, 0, 0).List_uid_(0) + , fxt.tkn_txt_(7, 8) + , fxt.tkn_list_end_(8).List_path_(0, 0, 0) + , fxt.tkn_list_end_(8).List_path_(0, 0) + , fxt.tkn_list_end_(8).List_path_(0) + , fxt.tkn_list_bgn_(8, 10, Xop_list_tkn_.List_itmTyp_ul).List_path_(1).List_uid_(0) + , fxt.tkn_txt_(10, 11) + , fxt.tkn_list_end_(11).List_path_(1) + ); + } + @Test public void Mix_2o_2u() { + fxt.Test_parse_page_wiki("\n**a\n##b" + , fxt.tkn_list_bgn_(0, 3, Xop_list_tkn_.List_itmTyp_ul).List_path_(0).List_uid_(0) + , fxt.tkn_list_bgn_(0, 3, Xop_list_tkn_.List_itmTyp_ul).List_path_(0, 0).List_uid_(0) + , fxt.tkn_txt_(3, 4) + , fxt.tkn_list_end_(4).List_path_(0, 0) + , fxt.tkn_list_end_(4).List_path_(0) + , fxt.tkn_list_bgn_(4, 7, Xop_list_tkn_.List_itmTyp_ol).List_path_(0).List_uid_(1) + , fxt.tkn_list_bgn_(4, 7, Xop_list_tkn_.List_itmTyp_ol).List_path_(0, 0).List_uid_(1) + , fxt.tkn_txt_(7, 8) + , fxt.tkn_list_end_(8).List_path_(0, 0) + , fxt.tkn_list_end_(8).List_path_(0) + ); + } + @Test public void Dt_dd() { + fxt.Test_parse_page_wiki(";a\n:b" + , fxt.tkn_list_bgn_(0, 1, Xop_list_tkn_.List_itmTyp_dt).List_path_(0).List_uid_(0) + , fxt.tkn_txt_(1, 2) + , fxt.tkn_list_end_(2).List_path_(0) + , fxt.tkn_list_bgn_(2, 4, Xop_list_tkn_.List_itmTyp_dd).List_path_(1).List_uid_(0) + , fxt.tkn_txt_(4, 5) + , fxt.tkn_list_end_(5).List_path_(1) + ); + } + @Test public void Dt_dd_inline() { + fxt.Test_parse_page_wiki(";a:b" // NOTE: no line break + , fxt.tkn_list_bgn_(0, 1, Xop_list_tkn_.List_itmTyp_dt).List_path_(0).List_uid_(0) + , fxt.tkn_txt_(1, 2) + , fxt.tkn_list_end_(2).List_path_(0) + , fxt.tkn_list_bgn_(2, 3, Xop_list_tkn_.List_itmTyp_dd).List_path_(1).List_uid_(0) + , fxt.tkn_txt_(3, 4) + , fxt.tkn_list_end_(4).List_path_(1) + ); + } + @Test public void Mix_1dd_1ul() { + fxt.Test_parse_page_wiki(":*a" + , fxt.tkn_list_bgn_(0, 2, Xop_list_tkn_.List_itmTyp_dd).List_path_(0).List_uid_(0) + , fxt.tkn_list_bgn_(0, 2, Xop_list_tkn_.List_itmTyp_ul).List_path_(0, 0).List_uid_(0) + , fxt.tkn_txt_(2, 3) + , fxt.tkn_list_end_(3).List_path_(0, 0) + , fxt.tkn_list_end_(3).List_path_(0) + ); + } + @Test public void Mix_1ul__1dd_1ul() { + fxt.Test_parse_page_wiki("*a\n:*b" + , fxt.tkn_list_bgn_(0, 1, Xop_list_tkn_.List_itmTyp_ul).List_path_(0).List_uid_(0) + , fxt.tkn_txt_(1, 2) + , fxt.tkn_list_end_(2).List_path_(0).List_uid_(0) + , fxt.tkn_list_bgn_(2, 5, Xop_list_tkn_.List_itmTyp_dd).List_path_(0).List_uid_(1) + , fxt.tkn_list_bgn_(2, 5, Xop_list_tkn_.List_itmTyp_ul).List_path_(0, 0).List_uid_(1) + , fxt.tkn_txt_(5, 6) + , fxt.tkn_list_end_(6).List_path_(0, 0) + , fxt.tkn_list_end_(6).List_path_(0) + ); + } + @Test public void Mix_1dd_1ul__1dd_1ul() { + fxt.Test_parse_page_wiki(":*a\n:*b" + , fxt.tkn_list_bgn_(0, 2, Xop_list_tkn_.List_itmTyp_dd).List_path_(0).List_uid_(0) + , fxt.tkn_list_bgn_(0, 2, Xop_list_tkn_.List_itmTyp_ul).List_path_(0, 0).List_uid_(0) + , fxt.tkn_txt_(2, 3) + , fxt.tkn_list_end_(3).List_path_(0, 0) + , fxt.tkn_list_bgn_(3, 6, Xop_list_tkn_.List_itmTyp_ul).List_path_(0, 1).List_uid_(0) + , fxt.tkn_txt_(6, 7) + , fxt.tkn_list_end_(7).List_path_(0, 1) + , fxt.tkn_list_end_(7).List_path_(0) + ); + } + @Test public void Mix_1ul_1hdr() { + fxt.Test_parse_page_wiki_str("*a\n==a==\n", String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • a" + , "
      • " + , "
      " + , "" + , "

      a

      " + )); + } + @Test public void Mix_1ul_1hdr_1ul() { + fxt.Test_parse_page_wiki_str("*a\n==a==\n*b", String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • a" + , "
      • " + , "
      " + , "" + , "

      a

      " + , "" + , "
        " + , "
      • b" + , "
      • " + , "
      " + )); + } + @Test public void Mix_1ol_1hr_1ol() { + fxt.Test_parse_page_wiki("#a\n----\n#b" + , fxt.tkn_list_bgn_(0, 1, Xop_list_tkn_.List_itmTyp_ol).List_path_(0).List_uid_(0) + , fxt.tkn_txt_(1, 2) + , fxt.tkn_list_end_(2) + , fxt.tkn_para_blank_(2) + , fxt.tkn_hr_(2, 7) + , fxt.tkn_list_bgn_(7, 9, Xop_list_tkn_.List_itmTyp_ol).List_path_(0).List_uid_(1) + , fxt.tkn_txt_(9, 10) + , fxt.tkn_list_end_(10) + ); + } + @Test public void Mix_tblw() { + fxt.Test_parse_page_wiki("::{|\n|a\n|}" + , fxt.tkn_list_bgn_(0, 2, Xop_list_tkn_.List_itmTyp_dd).List_path_(0).List_uid_(0) + , fxt.tkn_list_bgn_(0, 2, Xop_list_tkn_.List_itmTyp_dd).List_path_(0, 0).List_uid_(0) + , fxt.tkn_tblw_tb_(2, 10).Subs_ + ( fxt.tkn_tblw_tr_(4, 7).Subs_ + ( fxt.tkn_tblw_td_(4, 7).Subs_(fxt.tkn_txt_(6, 7), fxt.tkn_para_blank_(8))) + + ) + , fxt.tkn_list_end_(10).List_path_(0, 0) + , fxt.tkn_list_end_(10).List_path_(0) + ); + } + @Test public void Mix_tblw_w_space() { + fxt.Test_html_full_str(": {|\n|a\n|}", String_.Concat_lines_nl_skip_last + ( "
      " + , "
      " + , " " + , " " + , " " + , " " + , "
      a" + , "
      " + , "
      " + , "
      " + )); + } + @Test public void Dif_lvls_1_3_1() { + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "*1" + , "***3" + , "*1" + ) , String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • 1" + , "
          " + , "
        • " + , "
            " + , "
          • 3" + , "
          • " + , "
          " + , "
        • " + , "
        " + , "
      • " + , "
      • 1" + , "
      • " + , "
      " + )); + } + @Test public void Dif_lvls_1_3_2() {// uneven lists + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "*1" + , "***3" + , "**2" + ) , String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • 1" + , "
          " + , "
        • " + , "
            " + , "
          • 3" + , "
          • " + , "
          " + , "
        • " + , "
        • 2" + , "
        • " + , "
        " + , "
      • " + , "
      " + )); + } + @Test public void New_lines() { + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "*a" + , "" + , "**b" + , "" + , "**c" + ) , String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • a" + , "
      • " + , "
      " + , "" + , "
        " + , "
      • " + , "
          " + , "
        • b" + , "
        • " + , "
        " + , "
      • " + , "
      " + , "" + , "
        " + , "
      • " + , "
          " + , "
        • c" + , "
        • " + , "
        " + , "
      • " + , "
      " + )); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr_para_tst.java b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr_para_tst.java index a27517de8..be255be17 100644 --- a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr_para_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr_para_tst.java @@ -13,3 +13,74 @@ 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.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_list_wkr_para_tst { + @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 Basic() { + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "*a" + ) , String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • a" + , "
      • " + , "
      " + , "" + ) + ); + } + @Test public void Multiple() { + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "*a" + , "*b" + ) , String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • a" + , "
      • " + , "
      • b" + , "
      • " + , "
      " + ) + ); + } + @Test public void Multiple_w_1_nl() { + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "*a" + , "" + , "*b" + ) , String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • a" + , "
      • " + , "
      " + , "" + , "
        " + , "
      • b" + , "
      • " + , "
      " + ) + ); + } + @Test public void Pre_between_lists() { // PURPOSE: list should close pre; EX:en.b:Knowing Knoppix/Other applications; DATE:2014-02-18 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "#a" + , " b" + , "#c" // should close
       opened by b
      +			) ,	String_.Concat_lines_nl_skip_last
      +			(	"
        " + , "
      1. a" + , "
      2. " + , "
      " + , "" + , "
      b"
      +			,	"
      " + , "" + , "
        " + , "
      1. c" + , "
      2. " + , "
      " + ) + ); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr_uncommon_tst.java b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr_uncommon_tst.java index a27517de8..b57c6d67d 100644 --- a/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr_uncommon_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lists/Xop_list_wkr_uncommon_tst.java @@ -13,3 +13,395 @@ 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.lists; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_list_wkr_uncommon_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @After public void term() {fxt.Init_para_n_();} + @Test public void Bug_specified_div() { // FIX:
    • was not clearing state for lnki; PAGE:en.w:Ananke (moon) + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "
      " + , "#a" + , "
      " + , "*b" + ), String_.Concat_lines_nl_skip_last + ( "
      " + , "
        " + , "
      1. a" + , "" + , "
      2. " + , "
      " + , "
        " + , "
      • b" + , "
      • " + , "
      " + )); + } + @Test public void Bug_mismatched() { // FIX:
      was not clearing state for lnki; PAGE:en.w:Ananke (moon) + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "::a" + , ":::1" + , "::::11" + , ":::::111" + , "::b" + ), String_.Concat_lines_nl_skip_last + ( "
      " + , "
      " + , "
      " + , "
      a" + , "
      " + , "
      1" + , "
      " + , "
      11" + , "
      " + , "
      111" + , "
      " + , "
      " + , "
      " + , "
      " + , "
      " + , "
      " + , "
      " + , "
      b" + , "
      " + , "
      " + , "
      " + , "
      " + )); + } + @Test public void Empty_li_ignored() { // PURPOSE: inner template can cause dupe li; PAGE:en.w:any Calendar day and NYT link; NOTE:deactivated prune_empty_list logic; DATE:2014-09-05 + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "*a" + , "* " + , "*b" + , "*c" + ), String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • a" + , "
      • " + , "
      • " + , "
      • " + , "
      • b" + , "
      • " + , "
      • c" + , "
      • " + , "
      " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void List_in_tblw() { // PURPOSE: list inside table should not be close outer list; PAGE:en.w:Cato the Elder + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "*a" + , "{|" + , "|b" + , "::c" + , "|}" + ), String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • a" + , "
      • " + , "
      " + , "" + , " " + , " " + , " " + , "
      b" + , "" + , "
      " + , "
      " + , "
      " + , "
      c" + , "
      " + , "
      " + , "
      " + , "
      " + , "
      " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Dt_dd_colon_at_eol() { // PURPOSE: dangling ":" should not put next line in
      ; PAGE:en.w:Stein; b was being wrapped in
      b
      ; NOTE:deactivated prune_empty_list logic; DATE:2014-09-05 + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( ";a:" + , "*b" + , "" + , ";c" + , "*d" + ), String_.Concat_lines_nl_skip_last + ( "
      " + , "
      a" + , "
      " + , "
      " + , "
      " + , "
      " + , "
        " + , "
      • b" + , "
      • " + , "
      " + , "" + , "
      " + , "
      c" + , "
      " + , "
      " + , "
        " + , "
      • d" + , "
      • " + , "
      " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Dd_should_not_print_colon() {// PURPOSE: ;a:\n should show as ";a" not ";a:". colon should still be considered as part of empty list; DATE:2013-11-07; NOTE:deactivated prune_empty_list logic; DATE:2014-09-05 + fxt.Test_parse_page_all_str + ( ";a:\nb" + , String_.Concat_lines_nl_skip_last + ( "
      " + , "
      a" + , "
      " + , "
      " + , "
      " + , "
      " + , "b" + )); + } + @Test public void Dt_dd_colon_in_lnki() { // PURPOSE: "; [[Portal:a]]" should not split lnki; PAGE:en.w:Wikipedia:WikiProject Military history/Operation Majestic Titan; "; [[Wikipedia:WikiProject Military history/Operation Majestic Titan/Phase I|Phase I]]: a b" + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( ";[[Portal:a]]" + ), String_.Concat_lines_nl_skip_last + ( "
      " + , "
      Portal:A" + , "
      " + , "
      " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Max_list_depth() { // PURPOSE: 256+ * caused list parser to fail; ignore; PAGE:en.w:Bariatric surgery + String multiple = String_.Repeat("*", 300); + fxt.Test_parse_page_all_str(multiple, multiple); + } + @Test public void Numbered_list_resets_incorrectly() { // PURPOSE: as description + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "#A" + , "#*Aa" + , "#**Aaa" + , "#*Ab" + , "#B" + ), String_.Concat_lines_nl_skip_last + ( "
        " + , "
      1. A" + , "" + , "
          " + , "
        • Aa" + , "" + , "
            " + , "
          • Aaa" + , "
          • " + , "
          " + , "
        • " + , "
        • Ab" + , "
        • " + , "
        " // was showing as
      + , " " + , "
    • B" + , "
    • " + , "" + , "" + )); + fxt.Init_para_n_(); + } + @Test public void List_should_not_end_indented_table() {// PURPOSE: :{| was being closed by \n*; EX:w:Maxwell's equations; DATE:20121231 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( ":{|" + , "|-" + , "|" + , "*a" + , "|b" + , "|}" + ), String_.Concat_lines_nl_skip_last + ( "
      " + , "
      " + , " " + , " " + , " " + , " " + , " " + , "
      " + , "
        " + , "
      • a" + , "
      • " + , "
      " + , "
      b" + , "
      " + , "
      " + , "
      " + )); + } + @Test public void Dt_dd_broken_by_xnde() { // PURPOSE.fix: xnde was resetting dl incorrectly; EX:w:Virus; DATE:2013-01-31 + fxt.Test_parse_page_all_str(";a:c" + , String_.Concat_lines_nl_skip_last + ( "
      " + , "
      a" + , "
      " + , "
      c" + , "
      " + , "
      " + )); + } + @Test public void Trim_empty_list_items() { // PURPOSE: empty list items should be ignored; DATE:2013-07-02; NOTE:deactivated prune_empty_list logic; DATE:2014-09-05 + fxt.Test_parse_page_all_str + ("*** \n" + , String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • " + , "
          " + , "
        • " + , "
            " + , "
          • " + , "
          • " + , "
          " + , "
        • " + , "
        " + , "
      • " + , "
      " + , "" + )); + } + @Test public void Trim_empty_list_items_error() { // PURPOSE.fix: do not add empty itm's nesting to current list; DATE:2013-07-07; NOTE:deactivated prune_empty_list logic; DATE:2014-09-05 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl + ( "* a" + , "** " // was: do not add ** to nest; now: add ** and \s + , "*** b" + , "* c" + ), String_.Concat_lines_nl + ( "
        " + , "
      • a" + , "
          " + , "
        • " + , "
            " + , "
          • b" + , "
          • " + , "
          " + , "
        • " + , "
        " + , "
      • " + , "
      • c" + , "
      • " + , "
      " + )); + } + @Test public void Tblw_should_autoclose() {// PURPOSE: tblw should auto-close open list + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "#a" + , "{|" + , "|b" + , "|}" + ), String_.Concat_lines_nl_skip_last + ( "
        " + , "
      1. a" + , "
      2. " + , "
      " + , "" + , " " + , " " + , " " + , "
      b" + , "
      " + , "" + )); + } + @Test public void Tblx_should_not_autoclose() { // PURPOSE: do not auto-close list if table is xnde; DATE:2014-02-05 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl + ( "#a" + , "#
      b
      " + , "c" + ), String_.Concat_lines_nl + ( "
        " + , "
      1. a" + , "
      2. " + , "
      3. " + , " " + , " " + , " " + , " " + , "
        b" + , "
        " + , "
      4. " + , "
      " + , "c" + )); + } + @Test public void Li_disappears() { // PURPOSE: "\n*" disappears when followed by "
    • "; PAGE:en.w:Bristol_Bullfinch; DATE:2014-06-24 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl + ( "a" + , "*b
    • " + ), String_.Concat_lines_nl_skip_last // NOTE: tag sequence matches MW output + ( "a" + , "
        " + , "
      • b" + , "
      • " + , "
      • " + , " " + , "
      " + )); + } + @Test public void Ul_should_end_wlst() { // PURPOSE:
    should end wiki_list; PAGE:en.w:Bristol_Bullfinch; DATE:2014-06-24 + fxt.Test_parse_page_all_str + ( "*ab" + , String_.Concat_lines_nl_skip_last + ( "
      " + , "
    • a
    b" // TIDY.dangling: tidy will correct dangling node; DATE:2014-07-22 + , " " + , "" + )); + } + @Test public void Colon_causes_dd() { // PURPOSE: colon was mistakenly being ignored due to proximity to "\n;"; PAGE:de.w:Schmach_von_Tirana#Kuriosit.C3.A4t:_EM-Qualifikationsspiel_vom_20._November_1983 DATE:2014-07-11 + fxt.Test_parse_page_all_str + ( String_.Concat_lines_nl_skip_last + ( "a:b" + , ";c" + ), String_.Concat_lines_nl_skip_last + ( "a:b" + , "
    " + , "
    c" + , "
    " + , "
    " + )); + } + @Test public void Pre_and_nested() { // PURPOSE: pre should interrupt list; PAGE:fi.w:Luettelo_hyönteisistä; DATE:2015-03-31 + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str + ( String_.Concat_lines_nl_skip_last + ( "*a" + , "**b" + , " c" // pre + , "*d" // *d treated mistakenly as **d + ), String_.Concat_lines_nl_skip_last + ( "
      " + , "
    • a" + , "" + , "
        " + , "
      • b" + , "
      • " + , "
      " + , "
    • " + , "
    " + , "" + , "
    c"
    +		, "
    " + , "" + , "
      " + , "
    • d" + , "
    • " + , "
    " + , "" + )); + fxt.Init_para_n_(); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_end_lxr.java b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_end_lxr.java index a27517de8..edfd6fdc0 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_end_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_end_lxr.java @@ -13,3 +13,13 @@ 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.lnkes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +public class Xop_lnke_end_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_lnke_end;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Byte_ascii.Brack_end, 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) {return ctx.Lnke().MakeTkn_end(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos);} + public static final Xop_lnke_end_lxr Instance = new Xop_lnke_end_lxr(); Xop_lnke_end_lxr() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_log.java b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_log.java index a27517de8..80fb59a8b 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_log.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_log.java @@ -13,3 +13,9 @@ 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.lnkes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.log_msgs.*; +public class Xop_lnke_log { + private static final Gfo_msg_grp owner = Gfo_msg_grp_.new_(Xoa_app_.Nde, "lnke"); + public static final Gfo_msg_itm Dangling = Gfo_msg_itm_.new_note_(owner, "dangling"); // NOTE: WP.BOT:YOBOT;PAGE:en.w:Pan_flute +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_lxr.java b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_lxr.java index a27517de8..dc3865027 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_lxr.java @@ -13,3 +13,32 @@ 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.lnkes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +import gplx.core.net.*; +public class Xop_lnke_lxr implements Xop_lxr { + Xop_lnke_lxr(byte lnke_typ, byte[] protocol, byte tid) {this.lnke_typ = lnke_typ; this.protocol = protocol; this.tid = tid;} private byte lnke_typ; byte[] protocol; byte tid; + public int Lxr_tid() {return Xop_lxr_.Tid_lnke_bgn;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) { + Gfo_protocol_itm[] ary = Gfo_protocol_itm.Ary(); + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) { + Gfo_protocol_itm itm = ary[i]; + Ctor_lxr_add(core_trie, itm.Key_w_colon_bry(), itm.Tid()); + } + core_trie.Add(Bry_relative_1, new Xop_lnke_lxr(Xop_lnke_tkn.Lnke_typ_brack, Gfo_protocol_itm.Bry_relative, Gfo_protocol_itm.Tid_relative_1)); + core_trie.Add(Bry_relative_2, new Xop_lnke_lxr(Xop_lnke_tkn.Lnke_typ_brack, Gfo_protocol_itm.Bry_relative, Gfo_protocol_itm.Tid_relative_2)); + Ctor_lxr_add(core_trie, Bry_.new_a7("xowa-cmd"), Gfo_protocol_itm.Tid_xowa); + } private static final byte[] Bry_relative_1 = Bry_.new_a7("[//"), Bry_relative_2 = Bry_.new_a7("[[//"); + public void Init_by_lang(Xol_lang_itm lang, Btrie_fast_mgr core_trie) {} + public void Term(Btrie_fast_mgr core_trie) {} + private void Ctor_lxr_add(Btrie_fast_mgr core_trie, byte[] protocol_bry, byte tid) { + core_trie.Add(protocol_bry , new Xop_lnke_lxr(Xop_lnke_tkn.Lnke_typ_text, protocol_bry, tid)); + core_trie.Add(Bry_.Add(Byte_ascii.Brack_bgn, protocol_bry) , new Xop_lnke_lxr(Xop_lnke_tkn.Lnke_typ_brack, protocol_bry, tid)); + } + 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) { + if (this.tid == Gfo_protocol_itm.Tid_xowa && !ctx.Wiki().Sys_cfg().Xowa_proto_enabled()) return ctx.Lxr_make_txt_(cur_pos); + return ctx.Lnke().MakeTkn_bgn(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, protocol, tid, lnke_typ); + } + public static final Xop_lnke_lxr Instance = new Xop_lnke_lxr(); Xop_lnke_lxr() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_tkn.java b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_tkn.java index a27517de8..730e67a65 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_tkn.java @@ -13,3 +13,25 @@ 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.lnkes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.net.*; import gplx.core.net.qargs.*; +public class Xop_lnke_tkn extends Xop_tkn_itm_base {//20111222 + public static final byte Lnke_typ_null = 0, Lnke_typ_brack = 1, Lnke_typ_text = 2, Lnke_typ_brack_dangling = 3; + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_lnke;} + public boolean Lnke_relative() {return lnke_relative;} public Xop_lnke_tkn Lnke_relative_(boolean v) {lnke_relative = v; return this;} private boolean lnke_relative; + public byte Lnke_typ() {return lnke_typ;} public Xop_lnke_tkn Lnke_typ_(byte v) {lnke_typ = v; return this;} private byte lnke_typ = Lnke_typ_null; + public byte[] Lnke_site() {return lnke_site;} public Xop_lnke_tkn Lnke_site_(byte[] v) {lnke_site = v; return this;} private byte[] lnke_site; + public byte[] Lnke_xwiki_wiki() {return lnke_xwiki_wiki;} private byte[] lnke_xwiki_wiki; + public byte[] Lnke_xwiki_page() {return lnke_xwiki_page;} private byte[] lnke_xwiki_page; + public Gfo_qarg_itm[] Lnke_xwiki_qargs() {return lnke_xwiki_qargs;} Gfo_qarg_itm[] lnke_xwiki_qargs; + public void Lnke_xwiki_(byte[] wiki, byte[] page, Gfo_qarg_itm[] args) {this.lnke_xwiki_wiki = wiki; this.lnke_xwiki_page = page; this.lnke_xwiki_qargs = args;} + public int Lnke_href_bgn() {return lnke_href_bgn;} private int lnke_href_bgn; + public int Lnke_href_end() {return lnke_href_end;} private int lnke_href_end; + public byte[] Protocol() {return protocol;} private byte[] protocol; + public byte Proto_tid() {return proto_tid;} private byte proto_tid; + public Xop_lnke_tkn Subs_add_ary(Xop_tkn_itm... ary) {for (Xop_tkn_itm itm : ary) super.Subs_add(itm); return this;} + + public Xop_lnke_tkn(int bgn, int end, byte[] protocol, byte proto_tid, byte lnke_typ, int lnke_href_bgn, int lnke_href_end) { + this.Tkn_ini_pos(false, bgn, end); this.protocol = protocol; this.proto_tid = proto_tid; this.lnke_typ = lnke_typ; this.lnke_href_bgn = lnke_href_bgn; this.lnke_href_end = lnke_href_end; + } Xop_lnke_tkn() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr.java b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr.java index a27517de8..876798a38 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr.java @@ -13,3 +13,305 @@ 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.lnkes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.net.*; import gplx.xowa.apps.urls.*; +import gplx.xowa.apps.progs.*; import gplx.xowa.wikis.xwikis.*; +public class Xop_lnke_wkr implements Xop_ctx_wkr { + public void Ctor_ctx(Xop_ctx ctx) {url_parser = ctx.Wiki().Utl__url_parser().Url_parser();} Gfo_url_parser url_parser; Gfo_url_site_data site_data = new Gfo_url_site_data(); + private Xoa_url xo_url_parser_url = Xoa_url.blank(); + public void Page_bgn(Xop_ctx ctx, Xop_root_tkn root) {} + public void Page_end(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int src_len) {} + public boolean Dangling_goes_on_stack() {return dangling_goes_on_stack;} public void Dangling_goes_on_stack_(boolean v) {dangling_goes_on_stack = v;} private boolean dangling_goes_on_stack; + public void AutoClose(Xop_ctx ctx, byte[] src, int src_len, int bgn_pos, int cur_pos, Xop_tkn_itm tkn) { + // "[" but no "]"; EX: "[irc://a"; NOTE: lnkes that start with protocol will be ac'd in MakeTkn_bgn; EX: "http://a" + Xop_lnke_tkn bgn_tkn = (Xop_lnke_tkn)tkn; + bgn_tkn.Lnke_typ_(Xop_lnke_tkn.Lnke_typ_brack_dangling); + bgn_tkn.Src_end_(bgn_tkn.Lnke_href_end()); // NOTE: endPos is lnke_end, not cur_pos or src_len; EX: "[irc://a b", lnk ends at a, not b; NOTE: still bgns at [ + ctx.Msg_log().Add_itm_none(Xop_lnke_log.Dangling, src, tkn.Src_bgn(), cur_pos); + } + public static final String Str_xowa_protocol = "xowa-cmd:"; + public static final byte[] Bry_xowa_protocol = Bry_.new_a7(Str_xowa_protocol); + public int MakeTkn_bgn(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, byte[] protocol, byte proto_tid, byte lnke_type) { + boolean lnke_type_brack = (lnke_type == Xop_lnke_tkn.Lnke_typ_brack); + if ( !lnke_type_brack // lnke doesn't have "["; EX: "ttl:" + && !Valid_text_lnke(ctx, src, src_len, bgn_pos, cur_pos) // tkn is part of work; EX: " ttl:" vs "attl:" + ) + return ctx.Lxr_make_txt_(cur_pos - 1); // -1 to ignore ":" in making text colon; needed to process ":" for list like "; attl: b" PAGE:de.w:Mord_(Deutschland)#Besonders_verwerfliche_Begehungsweise; DATE:2015-01-09 + if (ctx.Stack_get_typ(Xop_tkn_itm_.Tid_lnke) != null) return ctx.Lxr_make_txt_(cur_pos); // no nested lnke; return cur lnke as text; EX: "[irc://a irc://b]" -> "irc:b" + if (proto_tid == Gfo_protocol_itm.Tid_xowa) return Make_tkn_xowa(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, protocol, proto_tid, lnke_type); + + // HACK: need to disable lnke if enclosing type is lnki and (1) arg is "link=" or (2) in 1st arg; basically, only enable for caption tkns (and preferably, thumb only) (which should be neither 1 or 2) + if (ctx.Cur_tkn_tid() == Xop_tkn_itm_.Tid_lnki && lnke_type == Xop_lnke_tkn.Lnke_typ_text) { + byte mode = Lnki_linkMode_init; + int lnki_pipe_count = 0; + int tkn_idx = -1; + for (int i = root.Subs_len() - 1; i > -1; i--) { + Xop_tkn_itm link_tkn = root.Subs_get(i); + tkn_idx = i; + switch (link_tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_pipe: + if (mode == Lnki_linkMode_text) {ctx.Lxr_make_(false); return bgn_pos + 1;} // +1 to position after lnke_hook; EX:[[File:A.png|link=http:b.org]] position at t in http so http hook won't be invoked. + else {i = -1; ++lnki_pipe_count;} + break; + case Xop_tkn_itm_.Tid_txt: + if (mode == Lnki_linkMode_eq) mode = Lnki_linkMode_text; + // else i = -1; // DELETE: do not be overly strict; need to handle pattern of link=http://a.org?b=http://c.org; DATE:2013-02-03 + break; + case Xop_tkn_itm_.Tid_eq: + if (mode == Lnki_linkMode_init) mode = Lnki_linkMode_eq; + // else i = -1; // DELETE: do not be overly strict; need to handle pattern of link=http://a.org?b=http://c.org; DATE:2013-02-03 + break; + case Xop_tkn_itm_.Tid_space: case Xop_tkn_itm_.Tid_tab: + break; + } + } + if (lnki_pipe_count == 0) { + for (int i = tkn_idx; i > -1; i--) { + Xop_tkn_itm link_tkn = root.Subs_get(i); + tkn_idx = i; + switch (link_tkn.Tkn_tid()) { +// case Xop_tkn_itm_.Tid_txt: return cur_pos; // REMOVED:2012-11-12: was causing [[http://a.org a]] [[http://b.org b]] to fail; PAGE:en.w:Template:Infobox_country + case Xop_tkn_itm_.Tid_space: case Xop_tkn_itm_.Tid_tab: break; + } + } + } + } + int lnke_bgn = bgn_pos, lnke_end = -1, brack_end_pos = -1; + int lnke_end_tid = End_tid_null; + while (true) { // loop until lnke_end_tid char; + if (cur_pos == src_len) {lnke_end_tid = End_tid_eos; lnke_end = cur_pos; break;} + switch (src[cur_pos]) { + case Byte_ascii.Brack_end: + if (lnke_type_brack) { // NOTE: check that frame begins with [ in order to end with ] + lnke_end_tid = End_tid_brack; brack_end_pos = cur_pos + 1; // 1=adj_next_char + } + else { // NOTE: frame does not begin with [ but ] encountered. mark "invalid" in order to force parser to stop before "]" + lnke_end_tid = End_tid_invalid; + } + break; + case Byte_ascii.Space: lnke_end_tid = End_tid_space; break; + case Byte_ascii.Nl: lnke_end_tid = End_tid_nl; break; + case Byte_ascii.Gt: case Byte_ascii.Lt: + lnke_end_tid = End_tid_invalid; + break; + case Byte_ascii.Apos: + if (cur_pos + 1 < src_len && src[cur_pos + 1] == Byte_ascii.Apos) // NOTE: '' breaks link, but not '; EX: [http://a.org''b'']]; DATE:2013-03-18 + lnke_end_tid = End_tid_invalid; + break; + case Byte_ascii.Brack_bgn: // NOTE: always stop lnke at "[" regardless of brack_type; EX: [http:a.org[[B]]] and http:a.org[[B]]; DATE:2014-07-11 + case Byte_ascii.Quote: // NOTE: quote should also stop lnke; DATE:2014-10-10 + lnke_end_tid = End_tid_symbol; + break; + } + if (lnke_end_tid == End_tid_null) cur_pos++; + else { + lnke_end = cur_pos; + cur_pos++; + break; + } + } + if (lnke_type_brack) { + switch (lnke_end_tid) { + case End_tid_eos: + if (brack_end_pos == -1) { // eos but no ]; EX: "[irc://a" + if (dangling_goes_on_stack) { // added for Xow_popup_parser which needs to handle dangling lnke due to block_len; DATE:2014-06-20 + ctx.Subs_add_and_stack(root, tkn_mkr.Txt(bgn_pos, src_len)); // note that tkn doesn't matter, as Xow_popup_parser only cares *if* something is on stack, not *what* is on stack + return src_len; + } + ctx.Subs_add(root, tkn_mkr.Txt(bgn_pos, bgn_pos + 1));// convert open brack to txt; // FUTURE: don't make brack_tkn; just flag + bgn_pos += 1; + brack_end_pos = cur_pos; + lnke_bgn = bgn_pos; + lnke_type = Xop_lnke_tkn.Lnke_typ_brack_dangling; + } + break; + case End_tid_nl: + lnke_type = Xop_lnke_tkn.Lnke_typ_brack_dangling; + return ctx.Lxr_make_txt_(lnke_end); // textify lnk; EX: [irc://a\n] textifies "[irc://a" + default: + lnke_bgn += proto_tid == Gfo_protocol_itm.Tid_relative_2 ? 2 : 1; // if Tid_relative_2, then starts with [[; adjust by 2; EX:"[[//en" should have lnke_bgn at "//en", not "[//en" + lnke_type = Xop_lnke_tkn.Lnke_typ_brack; + break; + } + } + else { // else, plain text + brack_end_pos = lnke_end; + lnke_type = Xop_lnke_tkn.Lnke_typ_text; + if (ctx.Cur_tkn_tid() == Xop_tkn_itm_.Tid_lnki) { // SEE:NOTE_1 + Xop_tkn_itm prv_tkn = root.Subs_get(root.Subs_len() - 1); // get last tkn + if (prv_tkn.Tkn_tid() == Xop_tkn_itm_.Tid_lnki) { // is tkn lnki? + root.Subs_del_after(prv_tkn.Tkn_sub_idx()); // delete [[ tkn and replace with [ tkn + root.Subs_add(tkn_mkr.Txt(prv_tkn.Src_bgn(), prv_tkn.Src_bgn() + 1)); + ctx.Stack_pop_last(); // don't forget to remove from stack + lnke_type = Xop_lnke_tkn.Lnke_typ_brack; // change lnke_typee to brack + --bgn_pos; + } + } + } + if (proto_tid == Gfo_protocol_itm.Tid_relative_2) // for "[[//", add "["; rest of code handles "[//" normally, but still want to include literal "["; DATE:2013-02-02 + ctx.Subs_add(root, tkn_mkr.Txt(lnke_bgn - 1, lnke_bgn)); + url_parser.Parse_site_fast(site_data, src, lnke_bgn, lnke_end); + int site_bgn = site_data.Site_bgn(), site_end = site_data.Site_end(); + if (site_bgn == site_end) return ctx.Lxr_make_txt_(cur_pos); // empty proto should return text, not lnke; EX: "http:", "http://", "[http://]"; DATE:2014-10-09 + int adj = Ignore_punctuation_at_end(src, site_bgn, lnke_end); + if (adj != 0) { + lnke_end -= adj; + brack_end_pos -= adj; + cur_pos -= adj; + } + Xop_lnke_tkn tkn = tkn_mkr.Lnke(bgn_pos, brack_end_pos, protocol, proto_tid, lnke_type, lnke_bgn, lnke_end); + tkn.Lnke_relative_(site_data.Rel()); + Xow_xwiki_itm xwiki = ctx.App().Usere().Wiki().Xwiki_mgr().Get_by_mid(src, site_bgn, site_end); // NOTE: check User_wiki.Xwiki_mgr, not App.Wiki_mgr() b/c only it is guaranteed to know all wikis on system + if ( xwiki != null // lnke is to an xwiki; EX: [http://en.wikipedia.org/A a] + && Byte_.Match_any(proto_tid, Gfo_protocol_itm.Tid_relative_1, Gfo_protocol_itm.Tid_relative_2, Gfo_protocol_itm.Tid_http, Gfo_protocol_itm.Tid_https) // only consider http / https; ignore mailto and others; PAGE:uk.w:Маскалі; DATE:2015-07-28 + && Bry_.Match(src, site_bgn, site_end, xwiki.Domain_bry()) // only consider full domains, not alliases; EX: [http://w/b] should not match alias of w for en.wikipedia.org + ) { + Xowe_wiki wiki = ctx.Wiki(); + + // HACK: this is not correct; "=" or "&" is not handled by Gfo_url_parser which assumes that all "&" separates qargs; DATE:2016-10-10 + byte[] decoded_src = gplx.xowa.parsers.amps.Xop_amp_mgr.Instance.Decode_as_bry(Bry_.Mid(src, lnke_bgn, lnke_end)); + xo_url_parser_url = wiki.Utl__url_parser().Parse(decoded_src, 0, decoded_src.length); + + byte[] xwiki_wiki = xo_url_parser_url.Wiki_bry(); + byte[] xwiki_page = xo_url_parser_url.Page_bry(); + if (xwiki_page == null) { // handle xwiki lnke's to history page else null ref; EX:[http://ru.wikipedia.org/w/index.php?title&diff=19103464&oldid=18910980 извещен]; PAGE:ru.w:Project:Заявки_на_снятие_флагов/Архив/Патрулирующие/2009 DATE:2016-11-24 + xwiki_page = decoded_src; + } + else { + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, xwiki_page); + if (ttl != null && ttl.Wik_itm() != null) { + xwiki_wiki = ttl.Wik_itm().Domain_bry(); + xwiki_page = ttl.Page_url(); + } + tkn.Lnke_xwiki_(xwiki_wiki, xwiki_page, xo_url_parser_url.Qargs_ary()); + } + } + ctx.Subs_add(root, tkn); + if (lnke_type == Xop_lnke_tkn.Lnke_typ_brack) { + if (lnke_end_tid == End_tid_brack) { + tkn.Src_end_(cur_pos); + tkn.Subs_move(root); + return cur_pos; + } + ctx.Stack_add(tkn); + if (lnke_end_tid == End_tid_invalid) { + return cur_pos - 1; // -1 to return before < or > + } + } + else { + switch (lnke_end_tid) { + case End_tid_space: + ctx.Subs_add(root, tkn_mkr.Space(root, cur_pos - 1, cur_pos)); + break; + case End_tid_symbol: + case End_tid_nl: + case End_tid_invalid: // NOTE that cur_pos is set after <, must subtract 1 else will be ignored; EX: irc://a + return cur_pos - 1; + } + } + return cur_pos; + } + private static int Ignore_punctuation_at_end(byte[] src, int proto_end, int lnke_end) { // DATE:2014-10-09 + int rv = 0; + int pos = lnke_end - 1; // -1 b/c pos is after char; EX: "abc" has pos of 3; need --pos to start at src[2] = 'c' + byte paren_bgn_chk = Bool_.__byte; + while (pos >= proto_end) { + byte b = src[pos]; + switch (b) { // REF.MW: $sep = ',;\.:!?'; + case Byte_ascii.Comma: case Byte_ascii.Semic: case Byte_ascii.Backslash: case Byte_ascii.Dot: + case Byte_ascii.Bang: case Byte_ascii.Question: + break; + case Byte_ascii.Colon: // differentiate between "http:" (don't trim) and "http://a.org:" (trim) + if (pos == proto_end -1) return rv; + break; + case Byte_ascii.Paren_end: // differentiate between "(http://a.org)" (trim) and "http://a.org/b(c)" (don't trim) + if (paren_bgn_chk == Bool_.__byte) { + int paren_bgn_pos = Bry_find_.Find_fwd(src, Byte_ascii.Paren_bgn, proto_end, lnke_end); + paren_bgn_chk = paren_bgn_pos == Bry_find_.Not_found ? Bool_.N_byte : Bool_.Y_byte; + } + if (paren_bgn_chk == Bool_.Y_byte) // "(" found; do not ignore ")" + return rv; + else + break; + default: + return rv; + } + --pos; + ++rv; + } + return rv; + } + private static final byte Lnki_linkMode_init = 0, Lnki_linkMode_eq = 1, Lnki_linkMode_text = 2; + private static final byte End_tid_null = 0, End_tid_eos = 1, End_tid_brack = 2, End_tid_space = 3, End_tid_nl = 4, End_tid_symbol = 5, End_tid_invalid = 6; + public int MakeTkn_end(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos) { +// Xop_tkn_itm last_tkn = ctx.Stack_get_last(); // BLOCK:invalid_ttl_check; // TODO_OLD: backout apos changes +// if ( last_tkn != null +// && last_tkn.Tkn_tid() == Xop_tkn_itm_.Tid_lnki) { +// Xop_lnki_tkn lnki = (Xop_lnki_tkn)last_tkn; +// if ( lnki.Pipe_count_is_zero()) { // always invalid +// ctx.Stack_pop_last(); +// return Xop_lnki_wkr_.Invalidate_lnki(ctx, src, root, lnki, bgn_pos); +// } +// } + int lnke_bgn_idx = ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_lnke); + if (lnke_bgn_idx == -1) return ctx.Lxr_make_txt_(cur_pos); // no lnke_bgn tkn; occurs when just ]; EX: "a]b" + Xop_lnke_tkn bgnTkn = (Xop_lnke_tkn)ctx.Stack_pop_til(root, src, lnke_bgn_idx, false, bgn_pos, cur_pos, Xop_tkn_itm_.Tid_lnke); + bgnTkn.Src_end_(cur_pos); + bgnTkn.Subs_move(root); + return cur_pos; + } + private static boolean Valid_text_lnke(Xop_ctx ctx, byte[] src, int src_len, int bgn_pos, int cur_pos) { + if (bgn_pos == Xop_parser_.Doc_bgn_char_0) return true; // lnke starts at 0; always true + int prv_pos = bgn_pos - 1; + byte prv_byte = src[prv_pos]; + switch (prv_byte) { + case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4: + case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9: + case Byte_ascii.Ltr_A: case Byte_ascii.Ltr_B: case Byte_ascii.Ltr_C: case Byte_ascii.Ltr_D: case Byte_ascii.Ltr_E: + case Byte_ascii.Ltr_F: case Byte_ascii.Ltr_G: case Byte_ascii.Ltr_H: case Byte_ascii.Ltr_I: case Byte_ascii.Ltr_J: + case Byte_ascii.Ltr_K: case Byte_ascii.Ltr_L: case Byte_ascii.Ltr_M: case Byte_ascii.Ltr_N: case Byte_ascii.Ltr_O: + case Byte_ascii.Ltr_P: case Byte_ascii.Ltr_Q: case Byte_ascii.Ltr_R: case Byte_ascii.Ltr_S: case Byte_ascii.Ltr_T: + case Byte_ascii.Ltr_U: case Byte_ascii.Ltr_V: case Byte_ascii.Ltr_W: case Byte_ascii.Ltr_X: case Byte_ascii.Ltr_Y: case Byte_ascii.Ltr_Z: + case Byte_ascii.Ltr_a: case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_e: + case Byte_ascii.Ltr_f: case Byte_ascii.Ltr_g: case Byte_ascii.Ltr_h: case Byte_ascii.Ltr_i: case Byte_ascii.Ltr_j: + case Byte_ascii.Ltr_k: case Byte_ascii.Ltr_l: case Byte_ascii.Ltr_m: case Byte_ascii.Ltr_n: case Byte_ascii.Ltr_o: + case Byte_ascii.Ltr_p: case Byte_ascii.Ltr_q: case Byte_ascii.Ltr_r: case Byte_ascii.Ltr_s: case Byte_ascii.Ltr_t: + case Byte_ascii.Ltr_u: case Byte_ascii.Ltr_v: case Byte_ascii.Ltr_w: case Byte_ascii.Ltr_x: case Byte_ascii.Ltr_y: case Byte_ascii.Ltr_z: + return false; // alpha-numerical is invalid; EX: "titel:" should not generate a lnke for "tel:" + } + if (prv_byte >= Byte_ascii.Ascii_min && prv_byte <= Byte_ascii.Ascii_max) return true; // consider all other ASCII chars as true; EX: \t\n !, etc; + prv_pos = gplx.core.intls.Utf8_.Get_prv_char_pos0_old(src, prv_pos); + prv_byte = src[prv_pos]; + boolean prv_char_is_letter = ctx.Lang().Case_mgr().Match_any_exists(prv_byte, src, prv_pos, bgn_pos); + return !prv_char_is_letter; + } + private int Make_tkn_xowa(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, byte[] protocol, byte proto_tid, byte lnke_type) { + // NOTE: fmt is [xowa-cmd:^"app.setup_mgr.import_wiki('');"^ ] + if (lnke_type != Xop_lnke_tkn.Lnke_typ_brack) return ctx.Lxr_make_txt_(cur_pos); // NOTE: must check for [ or else C:\xowa\ will cause it to evaluate as lnke + int proto_end_pos = cur_pos + 1; // +1 to skip past : + int lhs_dlm_pos = Bry_find_.Find_fwd(src, Byte_ascii.Quote, proto_end_pos, src_len); if (lhs_dlm_pos == Bry_find_.Not_found) return ctx.Lxr_make_txt_(cur_pos); + int lnke_bgn_pos = lhs_dlm_pos + 1; + byte[] rhs_dlm_bry = Bry_quote; + if (lhs_dlm_pos - proto_end_pos > 0) { + Bry_bfr bfr = ctx.Wiki().Utl__bfr_mkr().Get_k004(); + rhs_dlm_bry = bfr.Add(Bry_quote).Add_mid(src, proto_end_pos, lhs_dlm_pos).To_bry_and_clear(); + bfr.Mkr_rls(); + } + int rhs_dlm_pos = Bry_find_.Find_fwd(src, rhs_dlm_bry, lnke_bgn_pos, src_len); if (rhs_dlm_pos == Bry_find_.Not_found) return ctx.Lxr_make_txt_(cur_pos); + int txt_bgn = Bry_find_.Find_fwd_while_space_or_tab(src, rhs_dlm_pos + rhs_dlm_bry.length, src_len); if (txt_bgn == Bry_find_.Not_found) return ctx.Lxr_make_txt_(cur_pos); + int txt_end = Bry_find_.Find_fwd(src, Byte_ascii.Brack_end, txt_bgn, src_len); if (txt_end == Bry_find_.Not_found) return ctx.Lxr_make_txt_(cur_pos); + + int end_pos = txt_end + 1; // +1 to place after ] + Xop_lnke_tkn tkn = tkn_mkr.Lnke(bgn_pos, end_pos, protocol, proto_tid, lnke_type, lnke_bgn_pos, rhs_dlm_pos); // +1 to ignore [ + ctx.Subs_add(root, tkn); + tkn.Subs_add(tkn_mkr.Txt(txt_bgn, txt_end)); + return end_pos; + } private static final byte[] Bry_quote = new byte[] {Byte_ascii.Quote}; +} +/* +NOTE_1 +lnke takes precedence over lnki. +EX: [[irc://a b]] +pass: [b] i.e. [b] where b is a lnke with caption b and trg of irc://a +fail: b i.e. b where b is a lnki with caption b and trg of irc://a +*/ diff --git a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_brack_tst.java b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_brack_tst.java index a27517de8..19d882d72 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_brack_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_brack_tst.java @@ -13,3 +13,80 @@ 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.lnkes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.xowa.parsers.xndes.*; +public class Xop_lnke_wkr_brack_tst { + @Before public void init() {fxt.Reset();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Brace_noText() { + fxt.Test_parse_page_wiki("[irc://a]", fxt.tkn_lnke_(0, 9).Lnke_typ_(Xop_lnke_tkn.Lnke_typ_brack).Lnke_rng_(1, 8)); + } + @Test public void Brace_eos() { + fxt.Test_parse_page_wiki("[irc://a", fxt.tkn_txt_(0, 1), fxt.tkn_lnke_(1, 8).Lnke_typ_(Xop_lnke_tkn.Lnke_typ_brack_dangling).Lnke_rng_(1, 8)); + } + @Test public void Brace_text() { + fxt.Test_parse_page_wiki("[irc://a b c]", fxt.tkn_lnke_(0, 13).Lnke_rng_(1, 8).Subs_(fxt.tkn_txt_(9, 10), fxt.tkn_space_(10, 11), fxt.tkn_txt_(11, 12))); + } + @Test public void Brace_lt() { + fxt.Init_log_(Xop_xnde_log.Eos_while_closing_tag).Test_parse_page_wiki("[irc://ac]" + , "c" + ); + } + @Test public void Brace_newLine() { + fxt.Test_parse_page_wiki("[irc://a\n]", fxt.tkn_txt_(0, 8), fxt.tkn_nl_char_len1_(8), fxt.tkn_txt_(9, 10)); + } + @Test public void Html_brack() { + fxt.Test_parse_page_wiki_str("[irc://a]", "[1]"); + } + @Test public void Apos() { + fxt.Test_parse_page_wiki_str("[http://www.a.org''b'']", "b"); + fxt.Test_parse_page_wiki_str("[http://www.a.org'b]", "[1]"); + } + @Test public void Nowiki() { + fxt.Test_parse_page_all_str + ( "http://a.org" + , "http://a.org" + ); + } + @Test public void Lnki_one() { // PURPOSE: parallel test for "http://a.org[[B]]"; DATE:2014-07-11 + fxt.Test_parse_page_wiki_str + ( "[http://a.org b [[C]] d]" + ,String_.Concat_lines_nl_skip_last + ( "b C d" + )); + } + @Test public void Encode_xwiki() { // PURPOSE: href title and args should always be encoded; PAGE:en.w:List_of_Category_A_listed_buildings_in_West_Lothian DATE:2014-07-15 + fxt.App().Usere().Wiki().Xwiki_mgr().Add_by_atrs(Bry_.new_a7("commons.wikimedia.org"), Bry_.new_a7("commons.wikimedia.org")); + fxt.Test__parse__wtxt_to_html // encode page + ( "[http://commons.wikimedia.org/%22%3E_A B]" + , "B" // '%22%3E' not '">' + ); + fxt.Test__parse__wtxt_to_html // encode args + ( "[http://commons.wikimedia.org/A?b=%22%3E_C D]" + , "D" // '%22%3E' not '">' + ); + } + @Test public void Encode_basic() { // PURPOSE: counterpart to Encode_xwiki; DATE:2014-07-15 + fxt.Test_parse_page_wiki_str // encode page + ( "[http://a.org/%22%3E_A B]" + , "B" // '%22%3E' not '">' + ); + fxt.Test_parse_page_wiki_str // encode args + ( "[http://a.org/A?b=%22%3E_C D]" + , "D" // '%22%3E' not '">' + ); + } + @Test public void Encode_relative() { // PURPOSE: counterpart to Encode_xwiki; DATE:2014-07-15 + fxt.Test_parse_page_wiki_str // encode page + ( "[//a.org/%22%3E_A B]" + , "B" // '%22%3E' not '">' + ); + fxt.Test_parse_page_wiki_str // encode args + ( "[//a.org/A?b=%22%3E_C D]" + , "D" // '%22%3E' not '">' + ); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_dangling_tst.java b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_dangling_tst.java index a27517de8..a6699406b 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_dangling_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_dangling_tst.java @@ -13,3 +13,25 @@ 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.lnkes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_lnke_wkr_dangling_tst { + @Before public void init() {fxt.Reset();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Dangling_eos() { + fxt.Test_parse_page_wiki("[irc://a b" + , fxt.tkn_lnke_(0, 8).Lnke_typ_(Xop_lnke_tkn.Lnke_typ_brack_dangling) + , fxt.tkn_txt_(9, 10) + ); + } + @Test public void Dangling_newLine() { + fxt.Test_parse_page_wiki("[irc://a b\nc]" + , fxt.tkn_lnke_(0, 8).Lnke_typ_(Xop_lnke_tkn.Lnke_typ_brack_dangling) + , fxt.tkn_txt_(9, 10) + , fxt.tkn_nl_char_len1_(10) + , fxt.tkn_txt_(11, 13) + ); + } + @Test public void Dangling_gt() { + fxt.Test_parse_page_wiki("[irc://a>b c]", fxt.tkn_lnke_(0, 13).Lnke_typ_(Xop_lnke_tkn.Lnke_typ_brack).Subs_(fxt.tkn_txt_(8, 10), fxt.tkn_space_(10, 11), fxt.tkn_txt_(11, 12))); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_relative_tst.java b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_relative_tst.java index a27517de8..69be997bc 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_relative_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_relative_tst.java @@ -13,3 +13,28 @@ 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.lnkes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_lnke_wkr_relative_tst { + @Before public void init() {fxt.Reset();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Relative_obj() { + fxt.Test_parse_page_wiki("[//a b]" + , fxt.tkn_lnke_(0, 7).Lnke_rng_(1, 4).Subs_(fxt.tkn_txt_(5, 6)) + ); + } + @Test public void Relative_external() { + fxt.Test__parse__wtxt_to_html("[//www.a.org a]", "a"); + } + @Test public void Relative_internal() { + fxt.Init_xwiki_add_user_("en.wikipedia.org"); + fxt.Test__parse__wtxt_to_html("[//en.wikipedia.org/wiki Wikipedia]", "Wikipedia"); + } + @Test public void Relative_w_category() { // EX: [//commons.wikimedia.org/wiki/Category:Diomedeidae A] + fxt.Init_xwiki_add_user_("en.wikipedia.org"); + fxt.Test__parse__wtxt_to_html("[//en.wikipedia.org/wiki/Category:A A]", "A"); + } + @Test public void Relurl() { + fxt.App().Usere().Wiki().Xwiki_mgr().Add_by_atrs(Bry_.new_a7("en.wikipedia.org"), Bry_.new_a7("en.wikipedia.org")); + fxt.Test__parse__wtxt_to_html("[[//en.wikipedia.org/ a]]", "[a]"); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_text_tst.java b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_text_tst.java index a27517de8..e7de5184c 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_text_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_text_tst.java @@ -13,3 +13,85 @@ 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.lnkes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.xowa.langs.cases.*; +public class Xop_lnke_wkr_text_tst { + @Before public void init() {fxt.Reset();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Text_obj() { + fxt.Test_parse_page_wiki("irc://a", fxt.tkn_lnke_(0, 7).Lnke_typ_(Xop_lnke_tkn.Lnke_typ_text).Lnke_rng_(0, 7)); + } + @Test public void Text_html() { + fxt.Test_parse_page_wiki_str("irc://a", "irc://a"); + } + @Test public void Text_after() { + fxt.Test_parse_page_wiki("irc://a b c", fxt.tkn_lnke_(0, 7).Lnke_rng_(0, 7), fxt.tkn_space_(7, 8), fxt.tkn_txt_(8, 9), fxt.tkn_space_(9, 10), fxt.tkn_txt_(10, 11)); + } + @Test public void Text_before_ascii() { // PURPOSE: free form external urls should not match if preceded by letters; EX:de.w:Sylvie_und_Bruno; DATE:2014-05-11 + fxt.Ctx().Lang().Case_mgr_u8_(); + String expd_lnke_html = "tel:a"; + fxt.Test_parse_page_wiki_str("titel:a" , "titel:a"); + fxt.Test_parse_page_wiki_str(" tel:a" , " " + expd_lnke_html); + fxt.Test_parse_page_wiki_str("!tel:a" , "!" + expd_lnke_html); + fxt.Test_parse_page_wiki_str("ätel:a" , "ätel:a"); + fxt.Test_parse_page_wiki_str("€tel:a" , "€" + expd_lnke_html); + } + @Test public void Invalid_lnki_and_list_dt_dd() { // PURPOSE: invalid lnke should still allow processing of ":" in list
    ; PAGE:de.w:Mord_(Deutschland)#Besonders_verwerfliche_Begehungsweise DATE:2015-01-08 + fxt.Test_parse_page_wiki_str("; atel: b" , String_.Concat_lines_nl_skip_last + ( "
    " + , "
    atel" + , "
    " + , "
    b" + , "
    " + , "
    " + )); + } + @Test public void Xnde() {// NOTE: compare to Brace_lt + fxt.Test_parse_page_wiki("irc://a" + , fxt.tkn_xnde_(0, 20).Subs_ + ( fxt.tkn_lnke_(6, 13) + ) + ); + } + @Test public void List() { + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "*irc://a" + , "*irc://b" + ),String_.Concat_lines_nl_skip_last + ( "" + )); + } + @Test public void Defect_reverse_caption_link() { // PURPOSE: bad lnke formatting (caption before link); ] should show up at end, but only [ shows up; PAGE:en.w:Paul Philippoteaux; [caption http://www.americanheritage.com] + fxt.Test_parse_page_wiki_str("[caption irc://a]", "[caption irc://a]"); + } + @Test public void Lnki() { // PURPOSE: trailing lnki should not get absorbed into lnke; DATE:2014-07-11 + fxt.Test_parse_page_wiki_str + ( "http://a.org[[B]]" // NOTE: [[ should create another lnki + ,String_.Concat_lines_nl_skip_last + ( "http://a.orgB" + )); + } + @Test public void Protocol_only() { // PURPOSE: protocol only should return text; DATE:2014-10-09 + fxt.Test_parse_page_wiki_str("http://" , "http://"); + fxt.Test_parse_page_wiki_str("http:" , "http:"); + fxt.Test_parse_page_wiki_str("[http://]" , "[http://]"); + fxt.Test_parse_page_wiki_str("[http:]" , "[http:]"); + } + @Test public void Ignore_punctuation_at_end() { // PURPOSE: ignore "," and related punctuation at end; DATE:2014-10-09 + fxt.Test_parse_page_wiki_str("http://a.org," , "http://a.org,"); // basic + fxt.Test_parse_page_wiki_str("http://a.org,," , "http://a.org,,"); // many + fxt.Test_parse_page_wiki_str("http://a.org/b,c" , "http://a.org/b,c"); // do not ignore if in middle + fxt.Test_parse_page_wiki_str("http://a.org:" , "http://a.org:"); // colon at end; compare to "http:" + } + @Test public void Ignore_punctuation_at_end__paren_end() { // PURPOSE: end parent has special rules; DATE:2014-10-10 + fxt.Test_parse_page_wiki_str("(http://a.org)" , "(http://a.org)"); // trim=y + fxt.Test_parse_page_wiki_str("http://a.org/b(c)", "http://a.org/b(c)"); // trim=n + } + @Test public void Sym_quote() { // PURPOSE: quote should interrupt lnke; DATE:2014-10-10 + fxt.Test_parse_page_wiki_str("http://a.org/b\"c", "http://a.org/b"c"); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_uncommon_tst.java b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_uncommon_tst.java index a27517de8..66a915df8 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_uncommon_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_uncommon_tst.java @@ -13,3 +13,35 @@ 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.lnkes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_lnke_wkr_uncommon_tst { + @Before public void init() {fxt.Reset();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Err_multiple() { + fxt.Test_parse_page_wiki("[irc://a][irc://b]" + , fxt.tkn_lnke_(0, 9) + , fxt.tkn_lnke_(9, 18) + ); + } + @Test public void Err_txt_is_protocol() { + fxt.Test_parse_page_wiki("[irc://a irc://b]" + , fxt.tkn_lnke_(0, 17).Lnke_rng_(1, 8).Subs_(fxt.tkn_txt_(9, 16)) + ); + } + @Test public void Lnke_should_precede_lnki() { // PURPOSE: [[ should not be interpreted as lnki if [irc is available + fxt.Test_parse_page_wiki("[[irc://a/b c]]" + , fxt.tkn_txt_(0, 1) + , fxt.tkn_lnke_(1, 14).Subs_ + ( fxt.tkn_txt_(12, 13) + ) + , fxt.tkn_txt_(14, 15) + ); + } + @Test public void Defect_2nd_consecutive_lnke() { // PURPOSE: bad code that was causing lnkes to show up; PAGE:en.w:Template:Infobox_country; + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "[[http://a.org a]] [[http://b.org b]]" + ), String_.Concat_lines_nl_skip_last + ( "[a] [b]" + )); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_xwiki_tst.java b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_xwiki_tst.java index a27517de8..c2fce00d4 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_xwiki_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_lnke_wkr_xwiki_tst.java @@ -13,3 +13,49 @@ 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.lnkes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_lnke_wkr_xwiki_tst { + @Before public void init() {fxt.Reset();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Xwiki() { + fxt.App().Usere().Wiki().Xwiki_mgr().Add_by_atrs(Bry_.new_a7("en.wikipedia.org"), Bry_.new_a7("en.wikipedia.org")); + fxt.Test__parse__wtxt_to_html("[http://en.wikipedia.org/wiki/A a]", "a"); + } + @Test public void Xwiki_relative() { + fxt.App().Usere().Wiki().Xwiki_mgr().Add_by_atrs(Bry_.new_a7("en.wikipedia.org"), Bry_.new_a7("en.wikipedia.org")); + fxt.Test__parse__wtxt_to_html("[//en.wikipedia.org/ a]", "a"); + } + @Test public void Xwiki_qarg() {// DATE:2013-02-02 + fxt.Init_xwiki_add_user_("en.wikipedia.org"); + fxt.Test__parse__wtxt_to_html("http://en.wikipedia.org/wiki/Special:Allpages?from=Earth", "http://en.wikipedia.org/wiki/Special:Allpages?from=Earth"); + } + @Test public void Lang_prefix() { + fxt.App().Usere().Wiki().Xwiki_mgr().Add_by_atrs(Bry_.new_a7("en.wikipedia.org"), Bry_.new_a7("en.wikipedia.org")); + fxt.Wiki().Xwiki_mgr().Add_by_atrs(Bry_.new_a7("fr"), Bry_.new_a7("fr.wikipedia.org")); + fxt.Test__parse__wtxt_to_html("[http://en.wikipedia.org/wiki/fr:A a]", "a"); + } + @Test public void Xwiki_query_arg() { + fxt.App().Usere().Wiki().Xwiki_mgr().Add_by_atrs(Bry_.new_a7("en.wikipedia.org"), Bry_.new_a7("en.wikipedia.org")); + fxt.Test__parse__wtxt_to_html("[http://en.wikipedia.org/wiki/A?action=edit a]", "a"); + } + @Test public void Xwiki__history() { // PURPOSE: handle xwiki lnke's to history page else null ref; EX:[http://ru.wikipedia.org/w/index.php?title&diff=19103464&oldid=18910980 извещен]; PAGE:ru.w:Project:Заявки_на_снятие_флагов/Архив/Патрулирующие/2009 DATE:2016-11-24 + fxt.App().Usere().Wiki().Xwiki_mgr().Add_by_atrs(Bry_.new_a7("en.wikipedia.org"), Bry_.new_a7("en.wikipedia.org")); + fxt.Test__parse__wtxt_to_html("[http://en.wikipedia.org/w/index.php?title&diff=1&oldid=2 abc]", "abc"); + } + @Test public void Ignore_proto() { // PURPOSE: handle other protocols; PAGE:uk.w:Маскалі; DATE:2015-07-28 + fxt.Test__parse__wtxt_to_html("[mailto:a b]", "b");// should be /w/, not /en.wikipedia.org + } + @Test public void Ignore_alias() { // PURPOSE: fictitious example to make sure aliases are not subbed for domains; DATE:2015-07-28 + fxt.Init_xwiki_add_user_("w", "en.wikipedia.org"); + fxt.Test__parse__wtxt_to_html("[https://w/b c]", "c");// should be /w/, not /en.wikipedia.org + } + @Test public void Xwiki__qargs() { // PURPOSE: fix null ref error; PAGE:en.w:Wikipedia:Template_standardisation/demometa DATE:2015-08-02 + fxt.Init_xwiki_add_user_("en.wikipedia.org"); + fxt.Test__parse__wtxt_to_html + ( "[http://en.wikipedia.org/w/index.php?action=edit&preload=Template:Afd2+starter&editintro=Template:Afd3+starter&title=Wikipedia:Articles+for+deletion/Template_standardisation/demometa]" + // CHANGED: lnke_now decodes html_entities; DATE:2016-10-10 + //, "[1]" + , "[1]" + ); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_tkn_chkr_lnke.java b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_tkn_chkr_lnke.java index a27517de8..3df25a498 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_tkn_chkr_lnke.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkes/Xop_tkn_chkr_lnke.java @@ -13,3 +13,19 @@ 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.lnkes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xop_tkn_chkr_lnke extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Xop_lnke_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_lnke;} + public Xop_tkn_chkr_lnke(int bgn, int end) {super.Src_rng_(bgn, end);} + public byte Lnke_typ() {return lnke_typ;} public Xop_tkn_chkr_lnke Lnke_typ_(byte v) {lnke_typ = v; return this;} private byte lnke_typ = Xop_lnke_tkn.Lnke_typ_null; + public Xop_tkn_chkr_lnke Lnke_rng_(int bgn, int end) {lnke_bgn = bgn; lnke_end = end; return this;} private int lnke_bgn = -1; int lnke_end = -1; + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { + Xop_lnke_tkn actl = (Xop_lnke_tkn)actl_obj; + err += mgr.Tst_val(lnke_typ == Xop_lnke_tkn.Lnke_typ_null, path, "lnke_typ", lnke_typ, actl.Lnke_typ()); + err += mgr.Tst_val(lnke_bgn == -1, path, "lnke_bgn", lnke_bgn, actl.Lnke_href_bgn()); + err += mgr.Tst_val(lnke_end == -1, path, "lnke_end", lnke_end, actl.Lnke_href_end()); + return err; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_link_parser.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_link_parser.java index a27517de8..635f57527 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_link_parser.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_link_parser.java @@ -13,3 +13,100 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.net.*; import gplx.xowa.wikis.xwikis.*; +import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.wkrs.lnkis.htmls.*; import gplx.xowa.htmls.hrefs.*; +import gplx.xowa.wikis.domains.*; +public class Xop_link_parser { + public byte[] Html_xowa_ttl() {return html_xowa_ttl;} private byte[] html_xowa_ttl; + public byte Html_anchor_cls() {return html_anchor_cls;} private byte html_anchor_cls; + public byte Html_anchor_rel() {return html_anchor_rel;} private byte html_anchor_rel; + public byte[] Parse(Bry_bfr tmp_bfr, Xoa_url tmp_url, Xowe_wiki wiki, byte[] raw, byte[] or) { + html_xowa_ttl = null; html_anchor_cls = Xoh_lnki_consts.Tid_a_cls_image; html_anchor_rel = Xoh_lnki_consts.Tid_a_rel_none; // default member variables for html + Xoae_app app = wiki.Appe(); int raw_len = raw.length; + wiki.Utl__url_parser().Parse(tmp_url, raw); + switch (tmp_url.Protocol_tid()) { + case Gfo_protocol_itm.Tid_http: case Gfo_protocol_itm.Tid_https: // "http:" or "https:"; check if to offline wiki and redirect + byte[] wiki_bry = tmp_url.Wiki_bry(), page_bry = tmp_url.Page_bry(); + if ( !tmp_url.Wiki_is_missing() // https://www.a.org and others will be marked "missing" by Xow_url_parser + &&( Bry_.Eq(wiki_bry, wiki.Domain_bry()) // link is to this wiki; check if alias + || app.Xwiki_mgr__exists(wiki_bry) // link is to an xwiki + ) + ) { + page_bry = tmp_url.Page_for_lnki(); + Parse__ttl(tmp_bfr, wiki, wiki_bry, page_bry); + } + else { // http is to an unknown site + if (tmp_url.Protocol_is_relative()) { // relative protocol; EX:"//www.a.org"; + Gfo_protocol_itm protocol_itm = Gfo_protocol_itm.Get_or(wiki.Props().Protocol_tid(), Gfo_protocol_itm.Itm_https); + tmp_bfr.Add(protocol_itm.Key_w_colon_bry()); // prepend protocol b/c mozilla cannot launch "//www.a.org", but can launch "https://www.a.org"; DATE:2015-07-27 + } + tmp_bfr.Add(raw); // dump everything + } + raw = tmp_bfr.To_bry_and_clear(); + html_anchor_cls = Xoh_lnki_consts.Tid_a_cls_none; + Xow_domain_itm domain_itm = Xow_domain_itm_.parse(wiki_bry); + html_anchor_rel = domain_itm.Domain_type().Tid() == Xow_domain_tid_.Tid__other ? Xoh_lnki_consts.Tid_a_rel_nofollow : Xoh_lnki_consts.Tid_a_rel_none; // rel=nofollow if not WM wiki; DATE:2015-11-19 + break; + case Gfo_protocol_itm.Tid_file: // "file:///" or "File:A.png" + int proto_len = Gfo_protocol_itm.Bry_file.length; // "file:" + if (proto_len + 1 < raw_len && raw[proto_len + 1] == Byte_ascii.Slash) { // next char is slash, assume xfer_itm refers to protocol; EX: file:///C/A.png + int slash_pos = Bry_find_.Find_bwd(raw, Byte_ascii.Slash); + if (slash_pos != Bry_find_.Not_found) // set xowa_title to file_name; TODO_OLD: call Xoa_url.build; note that this will fail sometimes when (a) xfer_itm is very long (File:ReallyLongName will be shortened to 128 chars) or (b) xfer_itm has invalid windows characters (EX:File:a"b"c.jpg) + html_xowa_ttl = Bry_.Mid(raw, slash_pos + Byte_ascii.Len_1, raw.length); + } + else // next char is not slash; assume xfer_itm refers to ns; EX:File:A.png + raw = tmp_bfr.Add(Xoh_href_.Bry__wiki).Add(raw).To_bry_and_clear(); + break; + default: // is page only; EX: Abc + if (Bry_.Len_eq_0(raw)) // empty link should not create anchor; EX:[[File:A.png|link=|abc]]; [[File:Loudspeaker.svg|11px|link=|alt=play]]; PAGE:en.w:List_of_counties_in_New_York; DATE:2016-01-10; + raw = Bry_.Empty; + else { + if (raw[0] == Byte_ascii.Colon) raw = Bry_.Mid(raw, 1, raw.length); // ignore initial colon; EX: [[:commons:A.png]] + if (!Parse__ttl(tmp_bfr, wiki, wiki.Domain_bry(), raw)) { + tmp_bfr.Clear(); + return null; + } + raw = tmp_bfr.To_bry_and_clear(); + } + break; + } + return raw; + } + private static boolean Parse__ttl(Bry_bfr tmp_bfr, Xowe_wiki wiki, byte[] wiki_bry, byte[] page_bry) { + // handle colon-only aliases; EX:"link:" PAGE:en.w:Wikipedia:Main_Page_alternative_(CSS_Update) DATE:2016-08-18 + Xoa_ttl page_ttl = wiki.Ttl_parse(page_bry); + Xow_xwiki_itm xwiki_itm = page_ttl == null ? null : page_ttl.Wik_itm(); + if ( xwiki_itm != null // ttl is xwiki; EX:[[File:A.png|link=wikt:A]] + && page_ttl.Page_db().length == 0) { // ttl is empty; EX:[[File:A.png|link=wikt:]] + Xow_wiki xwiki_wiki = wiki.App().Wiki_mgri().Get_by_or_make_init_n(page_ttl.Wik_itm().Domain_bry()); + page_bry = Bry_.Add(page_bry, xwiki_wiki.Props().Main_page()); // append Main_Page to ttl; EX:"wikt:" + "Wikipedia:Main_Page" -> "wikt:Wikipedia:Main_Page" + page_ttl = wiki.Ttl_parse(page_bry); + xwiki_itm = page_ttl.Wik_itm(); // should still be the same, but re-set it for good form + } + + // identify wiki / page + boolean page_ttl_is_valid = page_ttl != null; + if (page_ttl_is_valid) { // xwiki; need to define wiki / page + if (xwiki_itm != null) { // is alias; set wiki, page + wiki_bry = xwiki_itm.Domain_bry(); + page_bry = Bry_.Mid(page_bry, xwiki_itm.Key_bry().length + 1, page_bry.length); // +1 to skip ":" + } + else // basic; just define page; use ttl.Full_db() to normalize; EX:   -> _ + page_bry = page_ttl.Full_db_w_anch(); // add anch; PAGE:en.w:History_of_Nauru; DATE:2015-12-27 + } + + // build either "/wiki/Page" or "/site/domain/wiki/Page" + if (Bry_.Eq(wiki_bry, wiki.Domain_bry())) { // NOTE: check against wiki.Key_bry() again; EX: in en_wiki, and http://commons.wikimedia.org/wiki/w:A + // title-case by ns; needed to handle "link=w:Help:a" which needs to generate "w:Help:A" + if (page_ttl_is_valid) { // valid_ttl; parse in same ns to title-case; EX:link=w:Help:a -> Help:A; DATE:2016-01-11 + page_ttl = wiki.Ttl_parse(page_ttl.Full_db_wo_xwiki()); + page_bry = page_ttl.Full_db_w_anch(); + } + tmp_bfr.Add(Xoh_href_.Bry__wiki).Add(page_bry); + } + else + tmp_bfr.Add(Xoh_href_.Bry__site).Add(wiki_bry).Add(Xoh_href_.Bry__wiki).Add(page_bry); + return page_ttl_is_valid; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_align_h_.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_align_h_.java index a27517de8..3170bd958 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_align_h_.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_align_h_.java @@ -13,3 +13,24 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_lnki_align_h_ { + public static final byte Null = 0, None = 1, Left = 2, Center = 3, Right = 4; // SERIALIZED + public static final byte[][] Html_names = new byte[][] + { Object_.Bry__null + , Bry_.new_a7("none") + , Bry_.new_a7("left") + , Bry_.new_a7("center") + , Bry_.new_a7("right") + }; + public static final Hash_adp_bry Hash = Hash_adp_bry.ci_a7() + .Add_str_byte("tnone" , None) + .Add_str_byte("tleft" , Left) + .Add_str_byte("tcenter" , Center) + .Add_str_byte("tright" , Right) + ; + public static byte[] To_bry(int v) {return Html_names[v];} +} +class Xop_lnki_align_v_ { + public static final byte None = 0, Top = 1, Middle = 2, Bottom = 4, Super = 8, Sub = 16, TextTop = 32, TextBottom = 64, Baseline = 127; +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_arg_parser.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_arg_parser.java index a27517de8..a3904d2b2 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_arg_parser.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_arg_parser.java @@ -13,3 +13,178 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.primitives.*; import gplx.core.btries.*; import gplx.core.envs.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.kwds.*; import gplx.xowa.langs.numbers.*; +public class Xop_lnki_arg_parser { + private final Btrie_fast_mgr key_trie = Btrie_fast_mgr.cs(); + private final Bry_bfr int_bfr = Bry_bfr_.Reset(16); + private final Btrie_bwd_mgr px_trie = Btrie_bwd_mgr.cs_(); private final Btrie_fast_mgr size_trie = Btrie_fast_mgr.cs(); + private final Btrie_rv key_trie_rv = new Btrie_rv(), px_trie_rv = new Btrie_rv(), size_trie_rv = new Btrie_rv(); + private int lnki_w, lnki_h; + public void Evt_lang_changed(Xol_lang_itm lang) { + Bry_bfr tmp_bfr = int_bfr; + Byte_obj_ref rslt = Byte_obj_ref.zero_(); + Xol_kwd_mgr mgr = lang.Kwd_mgr(); + key_trie.Clear(); + Xol_kwd_grp list = null; + int len = Keys_ids.length; + for (int i = 0; i < len; i++) { + int[] val = Keys_ids[i]; + list = mgr.Get_at(val[0]); // NOTE: val[0] is magic_word id + if (list == null) { + if (Env_.Mode_testing()) + continue; // TEST: allows partial parsing of $magicWords + else + list = lang.Lang_mgr().Lang_en().Kwd_mgr().Get_at(val[0]); + } + Xol_kwd_itm[] words = list.Itms(); + int words_len = words.length; + for (int j = 0; j < words_len; j++) { + Xol_kwd_itm word = words[j]; + byte[] word_bry = Xol_kwd_parse_data.Strip(tmp_bfr, word.Val(), rslt); + Init_key_trie(word_bry, (byte)val[1]); // NOTE: val[1] is lnki_key tid; ASSUME: case_sensitive for all "img_" words; note that all Messages**.php seem to be case_sensitive ("array(1, ..."); resisting change b/c of complexity/perf (need a cs trie and a ci trie) + } + } + list = mgr.Get_at(Xol_kwd_grp_.Id_img_width); + if (list == null) + list = lang.Lang_mgr().Lang_en().Kwd_mgr().Get_at(Xol_kwd_grp_.Id_img_width); + Init_size_trie(tmp_bfr, lang.Num_mgr().Digits_mgr(), list); + } + public byte Identify_tid(byte[] src, int bgn, int end, Xop_lnki_tkn lnki) { + lnki_w = Xop_lnki_tkn.Width_null; + lnki_h = Xop_lnki_tkn.Height_null; + byte rv = Identify_tid(src, bgn, end); + if (lnki_w != Xop_lnki_tkn.Width_null) lnki.W_(lnki_w); + if (lnki_h != Xop_lnki_tkn.Height_null)lnki.H_(lnki_h); + return rv; + } + public byte Identify_tid(byte[] src, int bgn, int end) { + int len = end - bgn; + Byte_obj_val val = (Byte_obj_val)key_trie.Match_at(key_trie_rv, src, bgn, end); + if (val != null && len == key_trie_rv.Pos() - bgn) // check for false matches; EX: alternate= should not match alt= + return val.Val(); // match; return val; + Object bwd_obj = px_trie.Match_at(px_trie_rv, src, end - 1, bgn - 1); + if (bwd_obj != null && ((Byte_obj_val)bwd_obj).Val() == Tid_dim) { // ends with "px"; try to parse size + int_bfr.Clear(); + int match_len = end -1 - px_trie_rv.Pos(); + boolean mode_width = true; + int itm_end = bgn + (len - match_len); // remove trailing px + for (int i = bgn; i < itm_end; i++) { + byte b = src[i]; + Object o = size_trie.Match_at_w_b0(size_trie_rv, b, src, i, itm_end); + if (o == null) { + this.lnki_w = Xop_lnki_tkn.Width_null; // NOTE: must null out width; EX: "123xTextpx"; PAGE:es.b:Alimentación_infantil; DATE:2015-07-10; NOTE: must be -1, not 0; DATE:2015-08-05 + return Tid_caption; // letter or other invalid character; return caption + } + Byte_obj_val v = (Byte_obj_val)o; + switch (v.Val()) { // NOTE: d0 - d9 handle non-english numbers; EX:fa.w and ۲۰۰px; DATE:2015-07-18 + case Key_dim_d0: int_bfr.Add_byte(Byte_ascii.Num_0); i += (size_trie_rv.Pos() - i) - 1; break; // -1 b/c loop will ++i + case Key_dim_d1: int_bfr.Add_byte(Byte_ascii.Num_1); i += (size_trie_rv.Pos() - i) - 1; break; + case Key_dim_d2: int_bfr.Add_byte(Byte_ascii.Num_2); i += (size_trie_rv.Pos() - i) - 1; break; + case Key_dim_d3: int_bfr.Add_byte(Byte_ascii.Num_3); i += (size_trie_rv.Pos() - i) - 1; break; + case Key_dim_d4: int_bfr.Add_byte(Byte_ascii.Num_4); i += (size_trie_rv.Pos() - i) - 1; break; + case Key_dim_d5: int_bfr.Add_byte(Byte_ascii.Num_5); i += (size_trie_rv.Pos() - i) - 1; break; + case Key_dim_d6: int_bfr.Add_byte(Byte_ascii.Num_6); i += (size_trie_rv.Pos() - i) - 1; break; + case Key_dim_d7: int_bfr.Add_byte(Byte_ascii.Num_7); i += (size_trie_rv.Pos() - i) - 1; break; + case Key_dim_d8: int_bfr.Add_byte(Byte_ascii.Num_8); i += (size_trie_rv.Pos() - i) - 1; break; + case Key_dim_d9: int_bfr.Add_byte(Byte_ascii.Num_9); i += (size_trie_rv.Pos() - i) - 1; break; + case Key_dim_num: int_bfr.Add_byte(b); break; + case Key_space: break; // ignore space; EX: "100 px" + case Key_dim_px: { // 2nd px found; EX: "40pxpx"; "40px px" + int tmp_pos = size_trie_rv.Pos(); + tmp_pos = Bry_find_.Find_fwd_while_space_or_tab(src, tmp_pos, itm_end); // look for next ws pos; + if (tmp_pos == itm_end) // no non-ws found; tmp_pos == itm_end; allow itm; EX: "40pxpx"; "40px px"; DATE:2014-03-01 + i = itm_end; + else // non-ws found; consider as caption; EX: "20px20px"; "20pxpxpx" + return Tid_caption; + break; + } + case Key_dim_x: { + if (mode_width) { + this.lnki_w = int_bfr.To_int_and_clear(-1); + mode_width = false; + break; + } + else return Tid_caption; + } + } + } + int dim = int_bfr.To_int_and_clear(-1); + if (mode_width) this.lnki_w = dim; + else this.lnki_h = dim; + return Tid_dim; + } + return Tid_caption; + } + private void Init_key_trie(byte[] key, byte v) { + Byte_obj_val val = Byte_obj_val.new_(v); + key_trie.Add(key, val); + } + private void Init_size_trie(Bry_bfr tmp_bfr, Xol_transform_mgr digit_mgr, Xol_kwd_grp list) { + if (list == null && Env_.Mode_testing()) return; // TEST: allows partial parsing of $magicWords + size_trie.Clear(); px_trie.Clear(); + for (int i = 0; i < 10; i++) + size_trie.Add((byte)(i + Byte_ascii.Num_0), Byte_obj_val.new_(Key_dim_num)); + int len = digit_mgr.Len(); // NOTE: add non-english numbers; EX: ۲۰۰px; DATE:2015-07-18 + for (int i = 0; i < len; ++i) { + Keyval kv = digit_mgr.Get_at(i); + int num = (byte)Int_.Parse_or(kv.Key(), -1); if (num == -1) continue; // ignore separators; EX: "," "." + size_trie.Add((byte[])kv.Val(), Byte_obj_val.new_((byte)num)); // NOTE: num corresponds to dim_d0 -> d9 below + } + size_trie.Add(Byte_ascii.Space, Byte_obj_val.new_(Key_space)); + size_trie.Add(X_bry, Byte_obj_val.new_(Key_dim_x)); + Xol_kwd_itm[] words = list.Itms(); + int words_len = words.length; + Byte_obj_ref rslt = Byte_obj_ref.zero_(); + for (int i = 0; i < words_len; i++) { + byte[] word_bry = Xol_kwd_parse_data.Strip(tmp_bfr, words[i].Val(), rslt); + size_trie.Add(word_bry, Byte_obj_val.new_(Key_dim_px)); + px_trie.Add(word_bry, Byte_obj_val.new_(Tid_dim)); + } + } + public static final byte[] Bry_upright = Bry_.new_a7("upright"), Bry_thumbtime = Bry_.new_a7("thumbtime"), Bry_target = Bry_.new_a7("target"); + public static final byte + Tid_unknown = 0, Tid_thumb = 1, Tid_left = 2, Tid_right = 3, Tid_none = 4, Tid_center = 5, Tid_frame = 6, Tid_frameless = 7, Tid_upright = 8, Tid_border = 9 + , Tid_alt = 10, Tid_link = 11, Tid_baseline = 12, Tid_sub = 13, Tid_super = 14, Tid_top = 15, Tid_text_top = 16, Tid_middle = 17, Tid_bottom = 18, Tid_text_bottom = 19 + , Tid_dim = 20 + , Tid_trg = 21, Tid_caption = 22 + , Tid_page = 23 + , Tid_noplayer = 24, Tid_noicon = 25, Tid_thumbtime = 26 + , Tid_class = 27 + , Tid_target = 28 + ; + private static final byte[] X_bry = Bry_.new_a7("x"); + private static final byte // NOTE: d0 - d9 must match 0 - 9; DATE:2015-07-18 + Key_dim_d0 = 0, Key_dim_d1 = 1, Key_dim_d2 = 2, Key_dim_d3 = 3, Key_dim_d4 = 4 + , Key_dim_d5 = 5, Key_dim_d6 = 6, Key_dim_d7 = 7, Key_dim_d8 = 8, Key_dim_d9 = 9 + , Key_dim_num = 10, Key_dim_x = 11, Key_dim_px = 12, Key_space = 13 + ; + private static final int[][] Keys_ids = new int[][] + { new int[] {Xol_kwd_grp_.Id_img_thumbnail , Tid_thumb} + , new int[] {Xol_kwd_grp_.Id_img_manualthumb , Tid_thumb} // RESEARCH: what is manualthumb? 'thumb=$1' vs 'thumb' + , new int[] {Xol_kwd_grp_.Id_img_right , Tid_right} + , new int[] {Xol_kwd_grp_.Id_img_left , Tid_left} + , new int[] {Xol_kwd_grp_.Id_img_none , Tid_none} + , new int[] {Xol_kwd_grp_.Id_img_center , Tid_center} + , new int[] {Xol_kwd_grp_.Id_img_framed , Tid_frame} + , new int[] {Xol_kwd_grp_.Id_img_frameless , Tid_frameless} + , new int[] {Xol_kwd_grp_.Id_img_page , Tid_page} // for pdf + , new int[] {Xol_kwd_grp_.Id_img_upright , Tid_upright} + , new int[] {Xol_kwd_grp_.Id_img_border , Tid_border} + , new int[] {Xol_kwd_grp_.Id_img_baseline , Tid_baseline} + , new int[] {Xol_kwd_grp_.Id_img_sub , Tid_sub} + , new int[] {Xol_kwd_grp_.Id_img_super , Tid_super} + , new int[] {Xol_kwd_grp_.Id_img_top , Tid_top} + , new int[] {Xol_kwd_grp_.Id_img_text_top , Tid_text_top} + , new int[] {Xol_kwd_grp_.Id_img_middle , Tid_middle} + , new int[] {Xol_kwd_grp_.Id_img_bottom , Tid_bottom} + , new int[] {Xol_kwd_grp_.Id_img_text_bottom , Tid_text_bottom} + , new int[] {Xol_kwd_grp_.Id_img_link , Tid_link} + , new int[] {Xol_kwd_grp_.Id_img_alt , Tid_alt} + , new int[] {Xol_kwd_grp_.Id_img_class , Tid_class} + , new int[] {Xol_kwd_grp_.Id_ogg_noplayer , Tid_noplayer} // RESEARCH: what does noplayer do?; find example + , new int[] {Xol_kwd_grp_.Id_ogg_noicon , Tid_noicon} + , new int[] {Xol_kwd_grp_.Id_ogg_thumbtime , Tid_thumbtime} + }; +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_log.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_log.java index a27517de8..ad877769c 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_log.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_log.java @@ -13,3 +13,15 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.log_msgs.*; +public class Xop_lnki_log { + private static final Gfo_msg_grp owner = Gfo_msg_grp_.new_(Xoa_app_.Nde, "lnki"); + public static final Gfo_msg_itm + Upright_val_is_invalid = Gfo_msg_itm_.new_warn_(owner, "upright_val_is_invalid") + , Escaped_lnki = Gfo_msg_itm_.new_warn_(owner, "escaped_lnki") + , Key_is_empty = Gfo_msg_itm_.new_warn_(owner, "key_is_empty") + , Ext_is_missing = Gfo_msg_itm_.new_warn_(owner, "ext_is_missing") + , Invalid_ttl = Gfo_msg_itm_.new_warn_(owner, "invalid_ttl") + ; +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_lxr_bgn.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_lxr_bgn.java index a27517de8..6c369674c 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_lxr_bgn.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_lxr_bgn.java @@ -13,3 +13,44 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +import gplx.xowa.parsers.tmpls.*; +public class Xop_lnki_lxr_bgn implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_lnki_bgn;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Xop_tkn_.Lnki_bgn, 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) { + Xop_tkn_itm prv_tkn = ctx.Stack_get_last(); + if (prv_tkn != null + && prv_tkn.Tkn_tid() == Xop_tkn_itm_.Tid_lnki) { + Xop_lnki_tkn prv_lnki = (Xop_lnki_tkn)prv_tkn; + if (prv_lnki.Pipe_count() == 0) { + ctx.Stack_pop_last(); + return Xop_lnki_wkr_.Invalidate_lnki(ctx, src, root, prv_lnki, bgn_pos); + } + } + Xop_lnki_tkn lnki = tkn_mkr.Lnki(bgn_pos, cur_pos); + ctx.Subs_add_and_stack(root, lnki); + return cur_pos; + } + public static final Xop_lnki_lxr_bgn Instance = new Xop_lnki_lxr_bgn(); +} +class Xop_lnki_size {public static final int None = 0, Width = 1, Height = 2, WidthHeight = 4, Upright = 8;} +/* +Spaces + NewLines +. ignored near posts: '[[ '; ' ]]'; ' | ' +. not ignored in: ' ='; basically breaks key +. not ignored in: '= '; will add to value; EX: alt= a -> ' a' + +NewLines +. will break lnk if in trg area (before | or ]]); EX:[[Image:The\nFabs -> [[Image:The Fabs +. will break alt (which apparently does not like new lines) +. will be converted to space for caption + +http://en.wikipedia.org/wiki/Wikipedia:Extended_image_syntax +The image syntax begins with "[[", contains components separated by "|", and ends with "]]". The "[[" and the first "|" (or, if there is no "|", the terminating "]]") +must be on the same line; other spaces and line breaks are ignored if they are next to "|" characters or just inside the brackets. +Spaces or line breaks are not allowed just before the "=" in the following options, and may have undesirable side effects if they appear just after the "=". +*/ diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_lxr_end.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_lxr_end.java index a27517de8..685cbea4c 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_lxr_end.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_lxr_end.java @@ -13,3 +13,14 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +import gplx.xowa.parsers.tmpls.*; +public class Xop_lnki_lxr_end implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_lnki_end;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Xop_tkn_.Lnki_end, 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) {return ctx.Lnki().Make_tkn(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos);} + public static final Xop_lnki_lxr_end Instance = new Xop_lnki_lxr_end(); +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_tkn.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_tkn.java index a27517de8..3abcd8f66 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_tkn.java @@ -13,3 +13,58 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.files.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.wkrs.lnkis.htmls.*; import gplx.xowa.xtns.pfuncs.ttls.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.parsers.tmpls.*; +public class Xop_lnki_tkn extends Xop_tkn_itm_base implements gplx.xowa.wikis.pages.lnkis.Xopg_lnki_itm { + @Override public byte Tkn_tid() {return tkn_tid;} private byte tkn_tid = Xop_tkn_itm_.Tid_lnki; + public void Tkn_tid_to_txt() {tkn_tid = Xop_tkn_itm_.Tid_txt;} + public int Ns_id() {return ns_id;} public Xop_lnki_tkn Ns_id_(int v) {ns_id = v; return this;} private int ns_id; + public Xoa_ttl Ttl() {return ttl;} public Xop_lnki_tkn Ttl_(Xoa_ttl v) {ttl = v; return this;} private Xoa_ttl ttl; + public byte Lnki_type() {return lnki_type;} private byte lnki_type = Xop_lnki_type.Id_null; + public int Tail_bgn() {return tail_bgn;} public Xop_lnki_tkn Tail_bgn_(int v) {tail_bgn = v; return this;} private int tail_bgn = -1; + public int Tail_end() {return tail_end;} public Xop_lnki_tkn Tail_end_(int v) {tail_end = v; return this;} private int tail_end = -1; + public byte Border() {return border;} public Xop_lnki_tkn Border_(byte v) {border = v; return this;} private byte border = Bool_.__byte; + public int Align_h() {return align_h;} public Xop_lnki_tkn Align_h_(int v) {if (align_h == Xop_lnki_align_h_.Null) align_h = v; return this;} private int align_h = Xop_lnki_align_h_.Null; + public byte Align_v() {return align_v;} public Xop_lnki_tkn Align_v_(byte v) {align_v = v; return this;} private byte align_v = Byte_.Max_value_127; + public int W() {return w;} public Xop_lnki_tkn W_(int v) {w = v; return this;} private int w = Width_null; + public int H() {return h;} public Xop_lnki_tkn H_(int v) {h = v; return this;} private int h = Height_null; + public byte[] Lnki_cls() {return lnki_cls;} public void Lnki_cls_(byte[] v) {lnki_cls = v;} private byte[] lnki_cls; + public boolean Media_icon() {return media_icon;} public Xop_lnki_tkn Media_icon_n_() {media_icon = false; return this;} private boolean media_icon = true; + public double Upright() {return upright;} public Xop_lnki_tkn Upright_(double v) {upright = v; return this;} private double upright = Upright_null; + public double Time() {return time;} public Xop_lnki_tkn Time_(double v) {time = v; return this;} private double time = Xof_lnki_time.Null; + public int Page() {return page;} public Xop_lnki_tkn Page_(int v) {page = v; return this;} private int page = Xof_lnki_page.Null; + public Xop_tkn_itm Trg_tkn() {return trg_tkn;} public Xop_lnki_tkn Trg_tkn_(Xop_tkn_itm v) {trg_tkn = v; return this;} private Xop_tkn_itm trg_tkn = Xop_tkn_null.Null_tkn; + public Xop_tkn_itm Caption_tkn() {return caption_tkn;} public Xop_lnki_tkn Caption_tkn_(Xop_tkn_itm v) {caption_tkn = v; return this;} private Xop_tkn_itm caption_tkn = Xop_tkn_null.Null_tkn; + public boolean Caption_tkn_pipe_trick() {return caption_tkn_pipe_trick;} public Xop_lnki_tkn Caption_tkn_pipe_trick_(boolean v) {caption_tkn_pipe_trick = v; return this;} private boolean caption_tkn_pipe_trick; + public Xop_tkn_itm Caption_val_tkn() {return caption_tkn == Xop_tkn_null.Null_tkn ? Arg_itm_tkn_null.Null_arg_itm : ((Arg_nde_tkn)caption_tkn).Val_tkn();} + public Arg_nde_tkn Link_tkn() {return link_tkn;} public Xop_lnki_tkn Link_tkn_(Arg_nde_tkn v) {link_tkn = v; return this;} Arg_nde_tkn link_tkn = Arg_nde_tkn.Null; + public Arg_nde_tkn Alt_tkn() {return alt_tkn;} public Xop_lnki_tkn Alt_tkn_(Arg_nde_tkn v) {alt_tkn = v; return this;} Arg_nde_tkn alt_tkn = Arg_nde_tkn.Null; + public boolean Alt_exists() {return alt_tkn != Arg_nde_tkn.Null;} + public int Subpage_tid() {return subpage_tid;} public Xop_lnki_tkn Subpage_tid_(int v) {subpage_tid = v; return this;} private int subpage_tid = Pfunc_rel2abs.Id_null; + public boolean Subpage_slash_at_end() {return subpage_slash_at_end;} public Xop_lnki_tkn Subpage_slash_at_end_(boolean v) {subpage_slash_at_end = v; return this;} private boolean subpage_slash_at_end; + public int Html_uid() {return html_uid;} public void Html_uid_(int v) {html_uid = v;} private int html_uid; + public int Pipe_count() {return pipe_count;} private int pipe_count; + public boolean Pipe_count_is_zero() {return pipe_count++ == 0;} + public boolean Xtn_sites_link() {return xtn_sites_link;} public void Xtn_sites_link_(boolean v) {xtn_sites_link = v;} private boolean xtn_sites_link; + public Xoh_file_fmtr Lnki_file_wkr() {return lnki_file_wkr;} public void Lnki_file_wkr_(Xoh_file_fmtr v) {lnki_file_wkr = v;} private Xoh_file_fmtr lnki_file_wkr; + public byte[] Target; + public byte[] Ttl_ary() { + return ttl.ForceLiteralLink() || ns_id != Xow_ns_.Tid__main // if [[:]] or non-main (Category, Template) + ? ttl.Full_txt_w_ttl_case() // use full_txt (no initial colon; capitalize first) + : ttl.Raw(); // use raw (preserve case, white-spaces) + } + public boolean Caption_exists() { + return !((caption_tkn == Xop_tkn_null.Null_tkn) // trg only; no caption: EX: [[a]] vs. [[a|b]] which has a trg of a and a caption of b + || (ns_id == Xow_ns_.Tid__category // a Category only has a target; any caption is ignored; EX: [[Category:a|b], b is ignored + && !ttl.ForceLiteralLink())); + } + public Xop_lnki_tkn Lnki_type_(byte v) { + if (lnki_type == Xop_lnki_type.Id_null) // NOTE:per MW:1.25.2, only use 1st argument of thumb|frame|frameless;/includes/parser/Parser.php; // use first appearing option, discard others.; DATE:2015-11-01 + lnki_type = v; + return this; + } + public static final double Upright_null = -1; + public static final int Width_null = -1, Height_null = -1; +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_tkn_chkr.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_tkn_chkr.java index a27517de8..8234b00a2 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_tkn_chkr.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_tkn_chkr.java @@ -13,3 +13,45 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; import gplx.xowa.files.*; +public class Xop_lnki_tkn_chkr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Xop_lnki_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_lnki;} + public int Ns_id() {return nsId;} public Xop_lnki_tkn_chkr Ns_id_(int v) {nsId = v; return this;} private int nsId = Int_.Min_value; + public byte ImgType() {return imgType;} public Xop_lnki_tkn_chkr ImgType_(byte v) {imgType = v; return this;} private byte imgType = Byte_.Max_value_127; + public int Width() {return width;} public Xop_lnki_tkn_chkr Width_(int v) {width = v; return this;} private int width = Int_.Min_value; + public int Height() {return height;} public Xop_lnki_tkn_chkr Height_(int v) {height = v; return this;} private int height = Int_.Min_value; + public int HAlign() {return hAlign;} public Xop_lnki_tkn_chkr HAlign_(int v) {hAlign = v; return this;} private int hAlign = Byte_.Max_value_127; + public byte VAlign() {return vAlign;} public Xop_lnki_tkn_chkr VAlign_(byte v) {vAlign = v; return this;} private byte vAlign = Byte_.Max_value_127; + public byte Border() {return border;} public Xop_lnki_tkn_chkr Border_(byte v) {border = v; return this;} private byte border = Bool_.__byte; + public double Upright() {return upright;} public Xop_lnki_tkn_chkr Upright_(double v) {upright = v; return this;} double upright = Xop_lnki_tkn.Upright_null; + public int Thumbtime() {return thumbtime;} public Xop_lnki_tkn_chkr Thumbtime_(int v) {thumbtime = v; return this;} int thumbtime = Xof_lnki_time.Null_as_int; + public int Page() {return page;} public Xop_lnki_tkn_chkr Page_(int v) {page = v; return this;} int page = Xof_lnki_page.Null; + public int Tail_bgn() {return tail_bgn;} public Xop_lnki_tkn_chkr Tail_bgn_(int v) {tail_bgn = v; return this;} private int tail_bgn = String_.Pos_neg1; + public int Tail_end() {return tail_end;} public Xop_lnki_tkn_chkr Tail_end_(int v) {tail_end = v; return this;} private int tail_end = String_.Pos_neg1; + public Xop_tkn_chkr_base Trg_tkn() {return trg_tkn;} public Xop_lnki_tkn_chkr Trg_tkn_(Xop_tkn_chkr_base v) {trg_tkn = v; return this;} private Xop_tkn_chkr_base trg_tkn; + public Xop_tkn_chkr_base Caption_tkn() {return caption_tkn;} public Xop_lnki_tkn_chkr Caption_tkn_(Xop_tkn_chkr_base v) {caption_tkn = v; return this;} private Xop_tkn_chkr_base caption_tkn; + public Xop_tkn_chkr_base Alt_tkn() {return alt_tkn;} public Xop_lnki_tkn_chkr Alt_tkn_(Xop_tkn_chkr_base v) {alt_tkn = v; return this;} private Xop_tkn_chkr_base alt_tkn; + public Xop_tkn_chkr_base Link_tkn() {return link_tkn;} public Xop_lnki_tkn_chkr Link_tkn_(Xop_tkn_chkr_base v) {link_tkn = v; return this;} private Xop_tkn_chkr_base link_tkn; + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { + Xop_lnki_tkn actl = (Xop_lnki_tkn)actl_obj; + err += mgr.Tst_val(nsId == Int_.Min_value, path, "nsId", nsId, actl.Ns_id()); + err += mgr.Tst_val(imgType == Byte_.Max_value_127, path, "imgType", imgType, actl.Lnki_type()); + err += mgr.Tst_val(width == Int_.Min_value, path, "width", width, actl.W()); + err += mgr.Tst_val(height == Int_.Min_value, path, "height", height, actl.H()); + err += mgr.Tst_val(hAlign == Byte_.Max_value_127, path, "halign", hAlign, actl.Align_h()); + err += mgr.Tst_val(vAlign == Byte_.Max_value_127, path, "valign", vAlign, actl.Align_v()); + err += mgr.Tst_val(border == Bool_.__byte, path, "border", border, actl.Border()); + err += mgr.Tst_val(tail_bgn == String_.Pos_neg1, path, "tail_bgn", tail_bgn, actl.Tail_bgn()); + err += mgr.Tst_val(tail_end == String_.Pos_neg1, path, "tail_end", tail_end, actl.Tail_end()); + err += mgr.Tst_val(upright == Xop_lnki_tkn.Upright_null, path, "upright", upright, actl.Upright()); + err += mgr.Tst_val(thumbtime == Xof_lnki_time.Null, path, "thumbtime", thumbtime, Xof_lnki_time.X_int(actl.Time())); + err += mgr.Tst_val(page == Xof_lnki_page.Null, path, "page", page, actl.Page()); + if (trg_tkn != null) err += mgr.Tst_sub_obj(trg_tkn, actl.Trg_tkn(), path + "." + "trg", err); + if (caption_tkn != null) err += mgr.Tst_sub_obj(caption_tkn, actl.Caption_tkn(), path + "." + "caption", err); + if (alt_tkn != null) err += mgr.Tst_sub_obj(alt_tkn, actl.Alt_tkn(), path + "." + "alt", err); + if (link_tkn != null) err += mgr.Tst_sub_obj(link_tkn, actl.Link_tkn(), path + "." + "link", err); + return err; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_type.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_type.java index a27517de8..25d1b468c 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_type.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_type.java @@ -13,3 +13,74 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.bits.*; +public class Xop_lnki_type { + public static final byte Id_null = 0, Id_none = 1, Id_frameless = 2, Id_frame = 4, Id_thumb = 8; + public static final byte Tid_null = 0, Tid_none = 1, Tid_frameless = 2, Tid_frame = 3, Tid_thumb = 4, Tid_orig_known = 64; // SERIALIZED + public static byte To_tid(byte flag) { + switch (flag) { + case Xop_lnki_type.Id_null: return Xop_lnki_type.Tid_null; + case Xop_lnki_type.Id_none: return Xop_lnki_type.Tid_none; + case Xop_lnki_type.Id_frameless: return Xop_lnki_type.Tid_frameless; + case Xop_lnki_type.Id_frame: return Xop_lnki_type.Tid_frame; + case Xop_lnki_type.Id_thumb: return Xop_lnki_type.Tid_thumb; + default: throw Err_.new_unhandled(flag); + } + } + public static byte To_flag(byte tid) { + switch (tid) { + case Xop_lnki_type.Tid_null: return Xop_lnki_type.Id_null; + case Xop_lnki_type.Tid_none: return Xop_lnki_type.Id_none; + case Xop_lnki_type.Tid_frameless: return Xop_lnki_type.Id_frameless; + case Xop_lnki_type.Tid_frame: return Xop_lnki_type.Id_frame; + case Xop_lnki_type.Tid_thumb: return Xop_lnki_type.Id_thumb; + default: throw Err_.new_unhandled(tid); + } + } + public static boolean Id_is_thumbable(byte id) { + return ( Bitmask_.Has_int(id, Id_thumb) // for purposes of displaying images on page, thumb and frame both create a thumb box + || Bitmask_.Has_int(id, Id_frame) + ); + } + public static boolean Id_defaults_to_thumb(byte id) { // assuming original of 400,200 + if ( Bitmask_.Has_int(id, Id_thumb) // [[File:A.png|thumb]] -> 220,-1 + || Bitmask_.Has_int(id, Id_frameless) // [[File:A.png|frameless]] -> 220,-1 + ) + return true; + else if ( Bitmask_.Has_int(id, Id_frame) // [[File:A.png|frame]] -> 400,200 (frame is always default size) + || id == Id_null // [[File:A.png]] -> 400,200 (default to original size) + || Bitmask_.Has_int(id, Id_none) // TODO_OLD: deprecate; NOTE: still used by one test; DATE:2015-08-03 + ) + return false; + else // should not happen + throw Err_.new_unhandled(id); + } + public static boolean Id_limits_large_size(byte id) { // Linker.php|makeThumbLink2|Do not present an image bigger than the source, for bitmap-style images; assuming original of 400,200 + if ( Bitmask_.Has_int(id, Id_thumb) // [[File:A.png|600px|thumb]] -> 400,200 + || Bitmask_.Has_int(id, Id_frameless) // [[File:A.png|600px|frameless]] -> 400,200 + || Bitmask_.Has_int(id, Id_frame) // [[File:A.png|600px|frame]] -> 400,200 (frame is always default size) + || id == Tid_orig_known // for hdump + ) + return true; + else if ( id == Id_null // [[File:A.png|600px]] -> 600,400; uses orig file of 400,200, but tag src_width / src_height set to 600,400 + || Bitmask_.Has_int(id, Id_none) // TODO_OLD: deprecate; NOTE: leaving in b/c of above failed-deprecate; DATE:2015-08-03 + ) + return false; + else // should not happen; + throw Err_.new_unhandled(id); + } + public static boolean Id_supports_upright(byte id) { // REF:Linker.php|makeImageLink;if ( isset( $fp['thumbnail'] ) || isset( $fp['manualthumb'] ) || isset( $fp['framed'] ) || isset( $fp['frameless'] ) || !$hp['width'] ) DATE:2014-05-22 + if ( Bitmask_.Has_int(id, Id_thumb) + || Bitmask_.Has_int(id, Id_frameless) + || Bitmask_.Has_int(id, Id_frame) + ) + return true; + else if ( id == Id_null + || Bitmask_.Has_int(id, Id_none) + ) + return false; + else // should not happen; + throw Err_.new_unhandled(id); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr.java index a27517de8..7c71ab824 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr.java @@ -13,3 +13,172 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.core.primitives.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.*; import gplx.xowa.parsers.lnkis.files.*; import gplx.xowa.xtns.pfuncs.ttls.*; import gplx.xowa.xtns.relatedSites.*; +import gplx.xowa.parsers.tmpls.*; import gplx.xowa.parsers.miscs.*; +public class Xop_lnki_wkr implements Xop_ctx_wkr, Xop_arg_wkr { + private Arg_bldr arg_bldr = Arg_bldr.Instance; + private Gfo_number_parser number_parser = new Gfo_number_parser(); + private Sites_regy_mgr sites_regy_mgr; + public void Ctor_ctx(Xop_ctx ctx) {} + public void Page_bgn(Xop_ctx ctx, Xop_root_tkn root) { + sites_regy_mgr = ctx.Wiki().Xtn_mgr().Xtn_sites().Regy_mgr(); if (!sites_regy_mgr.Xtn_mgr().Enabled()) sites_regy_mgr = null; // sets sites_xtn_mgr status for page; see below + } + public void Page_end(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int src_len) {} + public Xop_file_logger File_logger() {return lnki_logger;} public Xop_lnki_wkr File_logger_(Xop_file_logger v) {lnki_logger = v; return this;} private Xop_file_logger lnki_logger = Xop_file_logger_.Noop; + public void Auto_close(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, Xop_tkn_itm tkn) { + Xop_lnki_tkn lnki = (Xop_lnki_tkn)tkn; + lnki.Tkn_tid_to_txt(); + ctx.Msg_log().Add_itm_none(Xop_misc_log.Eos, src, lnki.Src_bgn(), lnki.Src_end()); + } + 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) { + if (ctx.Cur_tkn_tid() == Xop_tkn_itm_.Tid_lnke) { // if lnke then take 1st ] in "]]" and use it close lnke + int lnke_end_pos = bgn_pos + 1; + ctx.Lnke().MakeTkn_end(ctx, tkn_mkr, root, src, src_len, bgn_pos, lnke_end_pos); + return lnke_end_pos; + } + int stack_pos = ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_lnki); + if (stack_pos == Xop_ctx.Stack_not_found) return ctx.Lxr_make_txt_(cur_pos); // "]]" found but no "[[" in stack; return literal "]]" + Xop_lnki_tkn lnki = (Xop_lnki_tkn)ctx.Stack_pop_til(root, src, stack_pos, false, bgn_pos, cur_pos, Xop_tkn_itm_.Tid_lnki_end); + if (!arg_bldr.Bld(ctx, tkn_mkr, this, Xop_arg_wkr_.Typ_lnki, root, lnki, bgn_pos, cur_pos, lnki.Tkn_sub_idx() + 1, root.Subs_len(), src)) + return Xop_lnki_wkr_.Invalidate_lnki(ctx, src, root, lnki, bgn_pos); + if (lnki.Ns_id() != Xow_ns_.Tid__main && Bry_.Len_eq_0(lnki.Ttl().Page_txt())) // handle anchor-only pages; EX:[[File:#A]] PAGE:en.w:Spindale,_North_Carolina; DATE:2015-12-28 + return Xop_lnki_wkr_.Invalidate_lnki(ctx, src, root, lnki, bgn_pos); + if (Xop_lnki_wkr_.Adjust_for_brack_end_len_of_3(ctx, tkn_mkr, root, src, src_len, cur_pos, lnki)) // convert "]]]" into "]" + "]]", not "]]" + "]" + ++cur_pos; // position "]]" at end of "]]]" + cur_pos = Xop_lnki_wkr_.Chk_for_tail(ctx.Lang(), src, cur_pos, src_len, lnki); + lnki.Src_end_(cur_pos); // NOTE: must happen after Chk_for_tail; redundant with above, but above needed b/c of returns + root.Subs_del_after(lnki.Tkn_sub_idx() + 1); // all tkns should now be converted to args in owner; delete everything in root + boolean lnki_is_file = false; + switch (lnki.Ns_id()) { + case Xow_ns_.Tid__file: + if ( Xop_lnki_type.Id_is_thumbable(lnki.Lnki_type()) // thumbs produce
    cancels pre + || lnki.Align_h() != Xop_lnki_align_h_.Null // halign (left, right, none) also produces
    ; DATE:2014-02-17 + ) + ctx.Para().Process_block_lnki_div(); + lnki_is_file = true; + break; + case Xow_ns_.Tid__media: + lnki_is_file = true; + break; + case Xow_ns_.Tid__category: + if (!lnki.Ttl().ForceLiteralLink()) // NOTE: do not remove ws if literal; EX:[[Category:A]]\n[[Category:B]] should stay the same; DATE:2013-07-10 + ctx.Para().Process_lnki_category(ctx, root, src,cur_pos, src_len); // removes excessive ws between categories; EX: [[Category:A]]\n\s[[Category:B]] -> [[Category:A]][[Category:B]] (note that both categories will not be rendered directly in html, but go to the bottom of the page) + break; + } + if (lnki_is_file) { + ctx.Page().Lnki_list().Add(lnki); + lnki_logger.Log_file(ctx, lnki, Xop_file_logger_.Tid__file); + } + Xoa_ttl lnki_ttl = lnki.Ttl(); + if ( lnki_ttl.Wik_bgn() != -1 // lnki is xwiki + && sites_regy_mgr != null // relatedSites xtn is enabled + ) { + lnki.Xtn_sites_link_(sites_regy_mgr.Match(ctx.Page(), lnki_ttl)); + } + return cur_pos; + } + public boolean Args_add(Xop_ctx ctx, byte[] src, Xop_tkn_itm tkn, Arg_nde_tkn arg, int arg_idx) { + Xop_lnki_tkn lnki = (Xop_lnki_tkn)tkn; + try { + if (arg_idx == 0) { // 1st arg; assume trg; process ns; + if (lnki.Ttl() == null) { // ttl usually set by 1st pipe, but some lnkis have no pipe; EX: [[A]] + Arg_itm_tkn ttl_tkn = arg.Val_tkn(); + if (!Xop_lnki_wkr_.Parse_ttl(ctx, src, lnki, ttl_tkn.Dat_bgn(), ttl_tkn.Dat_end())) + return false; + } + lnki.Trg_tkn_(arg); + } + else { // nth arg; guess arg type + int arg_tid = -1; + int bgn = arg.Val_tkn().Dat_bgn(), end = arg.Val_tkn().Dat_end(); + if (arg.KeyTkn_exists()) {bgn = arg.Key_tkn().Dat_bgn(); end = arg.Key_tkn().Dat_end();} + arg_tid = ctx.Wiki().Lang().Lnki_arg_parser().Identify_tid(src, bgn, end, lnki); + if (arg_tid == Xop_lnki_arg_parser.Tid_caption && ctx.Wiki().Domain_itm().Domain_type_id() == gplx.xowa.wikis.domains.Xow_domain_tid_.Tid__other) { + if (end > bgn && Bry_.Eq(src, bgn, end, Xop_lnki_arg_parser.Bry_target)) + arg_tid = Xop_lnki_arg_parser.Tid_target; + } + switch (arg_tid) { + case Xop_lnki_arg_parser.Tid_none: lnki.Align_h_(Xop_lnki_type.Id_none); break; + case Xop_lnki_arg_parser.Tid_border: lnki.Border_(Bool_.Y_byte); break; + case Xop_lnki_arg_parser.Tid_thumb: lnki.Lnki_type_(Xop_lnki_type.Id_thumb); break; + case Xop_lnki_arg_parser.Tid_frame: lnki.Lnki_type_(Xop_lnki_type.Id_frame); break; + case Xop_lnki_arg_parser.Tid_frameless: lnki.Lnki_type_(Xop_lnki_type.Id_frameless); break; + case Xop_lnki_arg_parser.Tid_left: lnki.Align_h_(Xop_lnki_align_h_.Left); break; + case Xop_lnki_arg_parser.Tid_center: lnki.Align_h_(Xop_lnki_align_h_.Center); break; + case Xop_lnki_arg_parser.Tid_right: lnki.Align_h_(Xop_lnki_align_h_.Right); break; + case Xop_lnki_arg_parser.Tid_top: lnki.Align_v_(Xop_lnki_align_v_.Top); break; + case Xop_lnki_arg_parser.Tid_middle: lnki.Align_v_(Xop_lnki_align_v_.Middle); break; + case Xop_lnki_arg_parser.Tid_bottom: lnki.Align_v_(Xop_lnki_align_v_.Bottom); break; + case Xop_lnki_arg_parser.Tid_super: lnki.Align_v_(Xop_lnki_align_v_.Super); break; + case Xop_lnki_arg_parser.Tid_sub: lnki.Align_v_(Xop_lnki_align_v_.Sub); break; + case Xop_lnki_arg_parser.Tid_text_top: lnki.Align_v_(Xop_lnki_align_v_.TextTop); break; + case Xop_lnki_arg_parser.Tid_text_bottom: lnki.Align_v_(Xop_lnki_align_v_.TextBottom); break; + case Xop_lnki_arg_parser.Tid_baseline: lnki.Align_v_(Xop_lnki_align_v_.Baseline); break; + case Xop_lnki_arg_parser.Tid_class: lnki.Lnki_cls_(Xop_lnki_wkr_.Val_extract(src, arg)); break; + case Xop_lnki_arg_parser.Tid_target: lnki.Target = Xop_lnki_wkr_.Val_extract(src, arg); break; + case Xop_lnki_arg_parser.Tid_alt: lnki.Alt_tkn_(arg); + lnki.Alt_tkn().Tkn_ini_pos(false, arg.Src_bgn(), arg.Src_end()); + break; + case Xop_lnki_arg_parser.Tid_caption: + Xop_tkn_itm cur_caption_tkn = lnki.Caption_tkn(); + if ( cur_caption_tkn == Xop_tkn_null.Null_tkn // lnki doesn't have caption; add arg as caption + || lnki.Ttl().Ns().Id_is_file_or_media()) { // or lnki is File; always take last + lnki.Caption_tkn_(arg); + if (arg.Eq_tkn() != Xop_tkn_null.Null_tkn) { // equal tkn exists; add val tkns to key and then swap key with val + Arg_itm_tkn key_tkn = arg.Key_tkn(), val_tkn = arg.Val_tkn(); + key_tkn.Subs_add(arg.Eq_tkn()); + for (int i = 0; i < val_tkn.Subs_len(); i++) { + Xop_tkn_itm sub = val_tkn.Subs_get(i); + key_tkn.Subs_add(sub); + } + key_tkn.Dat_end_(val_tkn.Dat_end()); + val_tkn.Subs_clear(); + arg.Key_tkn_(Arg_itm_tkn_null.Null_arg_itm); + arg.Val_tkn_(key_tkn); + } + else // no equal tkn + lnki.Caption_tkn_pipe_trick_(end - bgn == 0); // NOTE: pipe_trick check must go here; checks for val_tkn.Bgn == val_tkn.End; if there is an equal token but no val, then Bgn == End which would trigger false pipe trick (EX:"[[A|B=]]") + } + else { // lnki does have caption; new caption should be concatenated; EX:[[A|B|C]] -> "B|C" x> "B"; NOTE: pipe-trick and eq tkn should not matter to multiple captions; DATE:2014-05-05 + Xop_tkn_itm val_tkn = arg.Val_tkn(); + int subs_len = val_tkn.Subs_len(); + Xop_tkn_itm caption_val_tkn = ((Arg_nde_tkn)cur_caption_tkn).Val_tkn(); + int pipe_bgn = caption_val_tkn.Src_bgn(); // for bookeeping purposes, assign | pos to same pos as val_tkn; note that pos really shouldn't be used; DATE:2014-05-05 + caption_val_tkn.Subs_add(ctx.Tkn_mkr().Bry_raw(pipe_bgn, pipe_bgn + 1, Const_pipe)); // NOTE: add pipe once for entire caption tkn; used to add for every val tkn; DATE:2014-06-08 + for (int i = 0 ; i < subs_len; i++) { + Xop_tkn_itm sub_itm = val_tkn.Subs_get(i); + caption_val_tkn.Subs_add(sub_itm); + } + } + break; + case Xop_lnki_arg_parser.Tid_link: lnki.Link_tkn_(arg); break; + case Xop_lnki_arg_parser.Tid_dim: break;// NOOP: Identify_tid does actual setting + case Xop_lnki_arg_parser.Tid_upright: + if (arg.KeyTkn_exists()) { + int val_tkn_bgn = arg.Val_tkn().Src_bgn(), val_tkn_end = arg.Val_tkn().Src_end(); + val_tkn_bgn = Bry_find_.Find_fwd_while_space_or_tab(src, val_tkn_bgn, val_tkn_end); // trim ws at bgn; needed for next step + if (val_tkn_end - val_tkn_bgn > 19) val_tkn_end = val_tkn_bgn + 19; // HACK: limit upright tkn to 19 digits; 20 or more will overflow long; WHEN: rewrite number_parser to handle doubles; PAGE:de.w:Feuerland DATE:2015-02-03 + number_parser.Parse(src, val_tkn_bgn, val_tkn_end); + if (number_parser.Has_err()) + ctx.Msg_log().Add_itm_none(Xop_lnki_log.Upright_val_is_invalid, src, val_tkn_bgn, val_tkn_end); + else + lnki.Upright_(number_parser.Rv_as_dec().To_double()); + } + else // no =; EX: [[Image:a|upright]] + lnki.Upright_(gplx.xowa.files.Xof_img_size.Upright_default_marker);// NOTE: was incorrectly hardcoded as 1; DATE:2014-07-23 + break; + case Xop_lnki_arg_parser.Tid_noicon: lnki.Media_icon_n_(); break; + case Xop_lnki_arg_parser.Tid_page: Xop_lnki_wkr_.Page_parse(ctx, src, number_parser, lnki, arg); break; + case Xop_lnki_arg_parser.Tid_thumbtime: Xop_lnki_wkr_.Thumbtime_parse(ctx, src, number_parser, lnki, arg); break; + } + } + return true; + } catch (Exception e) { + ctx.App().Usr_dlg().Warn_many("", "", "fatal error in lnki: page=~{0} src=~{1} err=~{2}", String_.new_u8(ctx.Page().Ttl().Full_db()), String_.new_u8(src, lnki.Src_bgn(), lnki.Src_end()), Err_.Message_gplx_full(e)); + return false; + } + } private static final byte[] Const_pipe = Bry_.new_a7("|"); +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr_.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr_.java index a27517de8..bc336156b 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr_.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr_.java @@ -13,3 +13,133 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.primitives.*; import gplx.core.btries.*; +import gplx.xowa.langs.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.*; import gplx.xowa.xtns.pfuncs.ttls.*; import gplx.xowa.xtns.relatedSites.*; +import gplx.xowa.parsers.tmpls.*; import gplx.xowa.wikis.pages.lnkis.*; +public class Xop_lnki_wkr_ { + public static final int Invalidate_lnki_len = 128; + public static int Invalidate_lnki(Xop_ctx ctx, byte[] src, Xop_root_tkn root, Xop_lnki_tkn lnki, int cur_pos) { + lnki.Tkn_tid_to_txt(); // convert initial "[[" to text; note that this lnki has no pipes as pipe_lxr does similar check; EX: [[]]; DATE:2014-03-26 + root.Subs_del_after(lnki.Tkn_sub_idx() + 1);// remove all tkns after [[ from root + int reparse_bgn = lnki.Src_end(); // NOTE: reparse all text from "[["; needed to handle [[|a]] where "a" cannot be returned as text; DATE:2014-03-04 + ctx.App().Msg_log().Add_itm_none(Xop_lnki_log.Invalid_ttl, src, reparse_bgn, reparse_bgn + 128); + // int reparse_len = cur_pos - reparse_bgn; + // if (reparse_len > 512) ctx.App().Usr_dlg().Warn_many("", "", "lnki.reparsing large block; page=~{0} len=~{1} src=~{2}", Xop_ctx_.Page_as_str(ctx), reparse_len, Xop_ctx_.Src_limit_and_escape_nl(src, reparse_bgn, Invalidate_lnki_len)); + return reparse_bgn; + } + public static boolean Parse_ttl(Xop_ctx ctx, byte[] src, Xop_lnki_tkn lnki, int pipe_pos) { + int ttl_bgn = lnki.Src_bgn() + Xop_tkn_.Lnki_bgn_len; + ttl_bgn = Bry_find_.Find_fwd_while(src, ttl_bgn, pipe_pos, Byte_ascii.Space); // remove \s from bgn + int ttl_end = Bry_find_.Find_bwd_while(src, pipe_pos, ttl_bgn, Byte_ascii.Space); // remove \s from end + ++ttl_end; // +1 to place after non-ws; EX: [[ a ]]; ttl_end should go from 3 -> 4 + return Parse_ttl(ctx, src, lnki, ttl_bgn, ttl_end); + } + public static boolean Parse_ttl(Xop_ctx ctx, byte[] src, Xop_lnki_tkn lnki, int ttl_bgn, int ttl_end) { + byte[] ttl_bry = Bry_.Mid(src, ttl_bgn, ttl_end); + ttl_bry = gplx.langs.htmls.encoders.Gfo_url_encoder_.Http_url_ttl.Decode(ttl_bry); + int ttl_bry_len = ttl_bry.length; + Xoa_ttl page_ttl = ctx.Page().Ttl(); + Xowe_wiki wiki = ctx.Wiki(); + if (page_ttl.Ns().Subpages_enabled() + && Pfunc_rel2abs.Rel2abs_ttl(ttl_bry, 0, ttl_bry_len)) { // Linker.php|normalizeSubpageLink + Bry_bfr tmp_bfr = ctx.Wiki().Utl__bfr_mkr().Get_b512(); + Int_obj_ref rel2abs_tid = ctx.Tmp_mgr().Pfunc_rel2abs().Val_zero_(); + byte[] new_bry = Pfunc_rel2abs.Rel2abs(tmp_bfr, wiki.Parser_mgr().Rel2abs_ary(), ttl_bry, page_ttl.Raw(), rel2abs_tid); + lnki.Subpage_tid_(rel2abs_tid.Val()); + lnki.Subpage_slash_at_end_(Bry_.Get_at_end(ttl_bry) == Byte_ascii.Slash); + ttl_bry = new_bry; + tmp_bfr.Mkr_rls(); + } + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, ttl_bry); + if (ttl == null) return false; + if ( wiki.Cfg_parser_lnki_xwiki_repos_enabled() // wiki has lnki.xwiki_repos + && ttl.Wik_bgn() != Null_wik_bgn // xwiki available; EX: [[en:]] + ) + ttl = Adj_ttl_for_file(wiki, ctx, ttl, ttl_bry); + lnki.Ttl_(ttl); + lnki.Ns_id_(ttl.Ns().Id()); + return true; + } + private static Xoa_ttl Adj_ttl_for_file(Xowe_wiki wiki, Xop_ctx ctx, Xoa_ttl ttl, byte[] ttl_bry) { // NOTE: remove the xwiki part; EX: [[en:File:A.png]] -> [[File:A.png]] + byte[] xwiki_bry = ttl.Wik_txt(); if (xwiki_bry == null) return ttl; // should not happen, but just in case + int xwiki_bry_len = xwiki_bry.length; + int ttl_bry_len = ttl_bry.length; + if (xwiki_bry_len + 1 >= ttl_bry_len) return ttl; // invalid ttl; EX: [[en:]] + byte[] ttl_in_xwiki_bry = Bry_.Mid(ttl_bry, xwiki_bry_len + 1, ttl_bry_len); // +1 to position after xwiki :; EX: [[en:File:A.png]]; +1 to put after ":" at "F" + if (!wiki.Cfg_parser().Lnki_cfg().Xwiki_repo_mgr().Has(xwiki_bry)) return ttl; // alias not in xwikis; EX: [[en_bad:File:A.png]] + Xoa_ttl ttl_in_xwiki = Xoa_ttl.Parse(wiki, ttl_in_xwiki_bry); + if (ttl_in_xwiki == null) return ttl; // occurs if ttl is bad in xwiki; EX: [[en:]] + return ttl_in_xwiki.Ns().Id_is_file() ? ttl_in_xwiki : ttl; + } + public static int Chk_for_tail(Xol_lang_itm lang, byte[] src, int cur_pos, int src_len, Xop_lnki_tkn lnki) { + int bgn_pos = cur_pos; + Btrie_slim_mgr lnki_trail = lang.Lnki_trail_mgr().Trie(); + while (true) { // loop b/c there can be multiple consecutive lnki_trail_chars; EX: [[A]]bcde + if (cur_pos == src_len) break; + byte[] lnki_trail_bry = (byte[])lnki_trail.Match_bgn_w_byte(src[cur_pos], src, cur_pos, src_len); + if (lnki_trail_bry == null) break; // no longer a lnki_trail char; stop + cur_pos += lnki_trail_bry.length; // lnki_trail char; add + } + if (bgn_pos != cur_pos && lnki.Ns_id() == Xow_ns_.Tid__main) { // only mark trail if Main ns (skip trail for Image) + lnki.Tail_bgn_(bgn_pos).Tail_end_(cur_pos); + return cur_pos; + } + else + return bgn_pos; + } + public static void Page_parse(Xop_ctx ctx, byte[] src, Gfo_number_parser number_parser, Xop_lnki_tkn lnki, Arg_nde_tkn arg) { + int val_tkn_bgn = arg.Val_tkn().Src_bgn(), val_tkn_end = arg.Val_tkn().Src_end(); + byte[] val_bry = Bry_.Trim(src, val_tkn_bgn, val_tkn_end); // some tkns have trailing space; EX.WWI: [[File:Bombers of WW1.ogg|thumb |thumbtime=3]] + number_parser.Parse(val_bry); + if (number_parser.Has_err()) + ctx.Msg_log().Add_itm_none(Xop_lnki_log.Upright_val_is_invalid, src, val_tkn_bgn, val_tkn_end); + else + lnki.Page_(number_parser.Rv_as_int()); + } + public static byte[] Val_extract(byte[] src, Arg_nde_tkn arg) { + int val_tkn_bgn = arg.Val_tkn().Src_bgn(), val_tkn_end = arg.Val_tkn().Src_end(); + return Bry_.Trim(src, val_tkn_bgn, val_tkn_end); // trim trailing space + } + public static void Thumbtime_parse(Xop_ctx ctx, byte[] src, Gfo_number_parser number_parser, Xop_lnki_tkn lnki, Arg_nde_tkn arg) { + int val_tkn_bgn = arg.Val_tkn().Src_bgn(), val_tkn_end = arg.Val_tkn().Src_end(); + long fracs = Time_span_.parse_to_fracs(src, val_tkn_bgn, val_tkn_end, false); + if (fracs == Time_span_.parse_null) { + ctx.Msg_log().Add_itm_none(Xop_lnki_log.Upright_val_is_invalid, src, val_tkn_bgn, val_tkn_end); + } + else + lnki.Time_(fracs / Time_span_.Ratio_f_to_s); + } + public static boolean Adjust_for_brack_end_len_of_3(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int cur_pos, Xop_lnki_tkn lnki) { + if ( cur_pos < src_len // bounds check + && src[cur_pos] == Byte_ascii.Brack_end // is next char after "]]", "]"; i.e.: "]]]"; PAGE:en.w:Aubervilliers DATE:2014-06-25 + ) { + int nxt_pos = cur_pos + 1; + if ( nxt_pos == src_len // allow "]]]EOS" + || ( nxt_pos < src_len // bounds check + && src[nxt_pos] != Byte_ascii.Brack_end // is next char after "]]]", "]"; i.e.: not "]]]]"; PAGE:ru.w:Меркатале_ин_Валь_ди_Песа; DATE:2014-02-04 + ) + ) { + if ( lnki.Caption_exists() // does a caption exist? + && lnki.Caption_tkn().Src_end() + 2 == cur_pos // is "]]]" at end of caption?; 2="]]".Len; handle [http://a.org [[File:A.png|123px]]] PAGE:ar.w:محمد; DATE:2014-08-20 + && lnki.Ttl() != null // only change "]]]" to "]" + "]]" if lnki is not title; otherwise [[A]]] -> "A]" which will be invalid; PAGE:en.w:Tall_poppy_syndrome DATE:2014-07-23 + ) { + Xop_tkn_itm caption_val_tkn = lnki.Caption_val_tkn(); + caption_val_tkn.Subs_add(tkn_mkr.Bry_raw(cur_pos, cur_pos + 1, Byte_ascii.Brack_end_bry)); // add "]" as bry + caption_val_tkn.Src_end_(caption_val_tkn.Src_end() + 1); + return true; + } + } + } + return false; + } + public static void Write_lnki(Bry_bfr bfr, Xoa_ttl ttl, boolean literal) { + bfr.Add(Xop_tkn_.Lnki_bgn); + if (literal) bfr.Add_byte(Byte_ascii.Colon); + bfr.Add(ttl.Full_db()); + bfr.Add(Xop_tkn_.Lnki_end); + } + private static final int Null_wik_bgn = -1; +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__basic_tst.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__basic_tst.java index a27517de8..718b615a6 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__basic_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__basic_tst.java @@ -13,3 +13,303 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.cases.*; import gplx.xowa.langs.funcs.*; import gplx.xowa.langs.lnki_trails.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.parsers.paras.*; import gplx.xowa.wikis.ttls.*; +public class Xop_lnki_wkr__basic_tst { + @Before public void init() {fxt.Reset(); fxt.Init_para_n_();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Basic() { + fxt.Test_parse_page_wiki("[[a]]", fxt.tkn_lnki_().Trg_tkn_(fxt.tkn_arg_val_txt_(2, 3))); + } + @Test public void HtmlRef() { + fxt.Test_parse_page_wiki_str("[[a&b]]" + , "a&b" + ); + } + @Test public void Url_encode() { // PURPOSE:title should automatically do url decoding; DATE:2013-08-26 + fxt.Test_parse_page_all_str("[[A%20b]]", "A b"); + } + @Test public void Url_encode_plus() { // PURPOSE:do not decode plus; DATE:2013-08-26 + fxt.Test_parse_page_all_str("[[A+b]]", "A+b"); + } + @Test public void Caption() { + fxt.Test_parse_page_wiki("[[a|b]]" , fxt.tkn_lnki_().Trg_tkn_(fxt.tkn_arg_val_txt_(2, 3)).Caption_tkn_(fxt.tkn_arg_val_txt_(4, 5))); + fxt.Test_parse_page_wiki("[[a|b:c]]", fxt.tkn_lnki_().Trg_tkn_(fxt.tkn_arg_val_txt_(2, 3)).Caption_tkn_(fxt.tkn_arg_val_(fxt.tkn_txt_(4, 5), fxt.tkn_colon_(5), fxt.tkn_txt_(6, 7)))); + } + @Test public void Caption_equal() { // should ignore = if only caption arg (2 args) + fxt.Test_parse_page_wiki("[[a|=]]", fxt.tkn_lnki_().Trg_tkn_(fxt.tkn_arg_val_txt_(2, 3)).Caption_tkn_(fxt.tkn_arg_val_(fxt.tkn_eq_(4)))); + } + @Test public void Caption_ampersand() {fxt.Test_parse_page_wiki_str("[[A|a & b]]", "a & b");} + @Test public void Tail() { + fxt.Test_parse_page_wiki("[[a|b]]c" , fxt.tkn_lnki_(0, 8).Tail_bgn_(7).Tail_end_(8)); + fxt.Test_parse_page_wiki("[[a|b]] c", fxt.tkn_lnki_(0, 7).Tail_bgn_(-1), fxt.tkn_space_(7, 8), fxt.tkn_txt_(8, 9)); + fxt.Test_parse_page_wiki("[[a|b]]'c", fxt.tkn_lnki_(0, 7).Tail_bgn_(-1), fxt.tkn_txt_(7, 9)); + } + @Test public void Tail_number() {fxt.Test_parse_page_wiki("[[a|b]]1" , fxt.tkn_lnki_(0, 7).Tail_bgn_(-1), fxt.tkn_txt_(7, 8));} + @Test public void Tail_upper() {fxt.Test_parse_page_wiki("[[a|b]]A" , fxt.tkn_lnki_(0, 7).Tail_bgn_(-1), fxt.tkn_txt_(7, 8));} // make sure trie is case-insensitive + @Test public void Tail_image() {fxt.Test_parse_page_wiki("[[Image:a|b]]c", fxt.tkn_lnki_(0, 13).Tail_bgn_(-1).Tail_end_(-1), fxt.tkn_txt_(13, 14));} // NOTE: this occurs on some pages; + @Test public void Image() { + fxt.Test_parse_page_wiki("[[Image:a]]" , fxt.tkn_lnki_().Ns_id_(Xow_ns_.Tid__file).Trg_tkn_(fxt.tkn_arg_val_(fxt.tkn_txt_(2, 7), fxt.tkn_colon_(7), fxt.tkn_txt_(8, 9)))); + fxt.Test_parse_page_wiki("[[Image:a|border]]" , fxt.tkn_lnki_().Border_(Bool_.Y_byte)); + fxt.Test_parse_page_wiki("[[Image:a|thumb]]" , fxt.tkn_lnki_().ImgType_(Xop_lnki_type.Id_thumb)); + fxt.Test_parse_page_wiki("[[Image:a|left]]" , fxt.tkn_lnki_().HAlign_(Xop_lnki_align_h_.Left)); + fxt.Test_parse_page_wiki("[[Image:a|top]]" , fxt.tkn_lnki_().VAlign_(Xop_lnki_align_v_.Top)); + fxt.Test_parse_page_wiki("[[Image:a|10px]]" , fxt.tkn_lnki_().Width_(10).Height_(-1)); + fxt.Test_parse_page_wiki("[[Image:a|20x30px]]" , fxt.tkn_lnki_().Width_(20).Height_(30)); + fxt.Test_parse_page_wiki("[[Image:a|alt=b]]" , fxt.tkn_lnki_().Alt_tkn_(fxt.tkn_arg_nde_().Key_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(10, 13))).Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(14, 15))))); + fxt.Test_parse_page_wiki("[[Image:a|link=a]]" , fxt.tkn_lnki_().Link_tkn_(fxt.tkn_arg_nde_().Key_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(10, 14))).Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(15, 16))))); + fxt.Test_parse_page_wiki("[[Image:a|thumb|alt=b|c d]]" + , fxt.tkn_lnki_().Ns_id_(Xow_ns_.Tid__file) + .Trg_tkn_(fxt.tkn_arg_nde_().Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(2, 7), fxt.tkn_colon_(7), fxt.tkn_txt_(8, 9)))) + .Alt_tkn_(fxt.tkn_arg_nde_().Key_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(16, 19))).Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(20, 21)))) + .Caption_tkn_(fxt.tkn_arg_nde_(22, 25).Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(22, 23), fxt.tkn_space_(23, 24), fxt.tkn_txt_(24, 25))))) + ; + } + @Test public void Image_upright() { + fxt.Test_parse_page_wiki("[[Image:a|upright=.123]]" , fxt.tkn_lnki_().Upright_(.123)); + fxt.Test_parse_page_wiki("[[Image:a|upright]]" , fxt.tkn_lnki_().Upright_(gplx.xowa.files.Xof_img_size.Upright_default_marker)); // no eq tokn + fxt.Test_parse_page_wiki("[[Image:a|upright=.42190046219457]]", fxt.tkn_lnki_().Upright_(.42190046219457)); // many decimal places breaks upright + fxt.Init_log_(Xop_lnki_log.Upright_val_is_invalid) + .Test_parse_page_wiki("[[Image:a|upright=y]]" , fxt.tkn_lnki_().Upright_(-1)); // invalid + } + @Test public void Recurse() { + fxt.Test_parse_page_wiki("[[Image:a|b-[[c]]-d]]" + , fxt.tkn_lnki_().Ns_id_(Xow_ns_.Tid__file) + .Trg_tkn_(fxt.tkn_arg_nde_().Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(2, 7), fxt.tkn_colon_(7), fxt.tkn_txt_(8, 9)))) + .Caption_tkn_(fxt.tkn_arg_nde_(10, 19).Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(10, 12), fxt.tkn_lnki_(12, 17), fxt.tkn_txt_(17, 19)))) + ); + } + @Test public void Outliers() { + fxt.Test_parse_page_wiki("[[Image:a=b.svg|thumb|10px]]", fxt.tkn_lnki_().Ns_id_(Xow_ns_.Tid__file).Trg_tkn_(fxt.tkn_arg_nde_().Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(2, 7), fxt.tkn_colon_(7), fxt.tkn_txt_(8, 9), fxt.tkn_eq_(9), fxt.tkn_txt_(10, 15))))); + fxt.Test_parse_page_wiki("[[Category:a|b]]", fxt.tkn_lnki_().Ns_id_(Xow_ns_.Tid__category)); + } + @Test public void Exc_caption_has_eq() { + fxt.Test_parse_page_wiki("[[Image:a.svg|thumb|a=b]]", fxt.tkn_lnki_().Ns_id_(Xow_ns_.Tid__file) + .Caption_tkn_(fxt.tkn_arg_nde_(20, 23).Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(20, 21), fxt.tkn_eq_(21), fxt.tkn_txt_(22, 23))))); + } + @Test public void Border_with_space_should_not_be_caption() {// happens with {{flag}}; EX: [[Image:Flag of Argentina.svg|22x20px|border |alt=|link=]] + Xop_root_tkn root = fxt.Test_parse_page_wiki_root("[[Image:a.svg|22x20px|border |alt=|link=]]"); + Xop_lnki_tkn lnki = (Xop_lnki_tkn)root.Subs_get(0); + Tfds.Eq(Xop_tkn_itm_.Tid_null, lnki.Caption_tkn().Tkn_tid()); + } + @Test public void Ws_key_bgn() { + fxt.Test_parse_page_wiki("[[Image:a| alt=b]]", fxt.tkn_lnki_() + .Alt_tkn_(fxt.tkn_arg_nde_() + . Key_tkn_(fxt.tkn_arg_itm_(fxt.tkn_space_(10, 11).Ignore_y_(), fxt.tkn_txt_(11, 14))) + . Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(15, 16))) + )); + } + @Test public void Ws_key_end() { + fxt.Test_parse_page_wiki("[[Image:a|alt =b]]", fxt.tkn_lnki_() + .Caption_tkn_(fxt.tkn_arg_nde_(10, 16) + . Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(10, 13), fxt.tkn_space_(13, 14), fxt.tkn_eq_(14), fxt.tkn_txt_(15, 16))) + )); + } + @Test public void Ws_val_bgn() { + fxt.Test_parse_page_wiki("[[Image:a|alt= b]]", fxt.tkn_lnki_() + .Alt_tkn_(fxt.tkn_arg_nde_() + . Key_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(10, 13))) + . Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_space_(14, 15), fxt.tkn_txt_(15, 16))) + )); + } + @Test public void Ws_val_end() { + fxt.Test_parse_page_wiki("[[Image:a|alt=b ]]", fxt.tkn_lnki_() + .Alt_tkn_(fxt.tkn_arg_nde_() + . Key_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(10, 13))) + . Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(14, 15), fxt.tkn_space_(15, 16).Ignore_y_())) + )); + } + @Test public void Ws_val_only() { // simpler variation of Ws_val_size + fxt.Test_parse_page_wiki("[[Image:a| b ]]", fxt.tkn_lnki_() + .Caption_tkn_(fxt.tkn_arg_nde_() + . Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_space_(10, 11), fxt.tkn_txt_(11, 12), fxt.tkn_space_(12, 13))) + )); + } + @Test public void Ws_val_size() { + fxt.Test_parse_page_wiki("[[Image:a| 20x30px ]]" , fxt.tkn_lnki_().Width_(20).Height_(30)); + } + @Test public void Nl_pipe() { // PURPOSE: "\n|" triggers tblw; PAGE:fr.w:France; [[Fichier:Carte demographique de la France.svg + fxt.Test_parse_page_wiki("[[Image:A.png|thumb\n|alt=test]]", fxt.tkn_lnki_() + .Alt_tkn_(fxt.tkn_arg_nde_() + . Key_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(21, 24))) + . Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(25, 29))) + )); + } + @Test public void Exc_empty_caption() { + fxt.Test_parse_page_wiki("[[a|]]", fxt.tkn_lnki_().Trg_tkn_(fxt.tkn_arg_val_txt_(2, 3))); + } + @Test public void Exc_empty() { + fxt.Init_log_(Xop_lnki_log.Invalid_ttl); + fxt.Test_parse_page_wiki("[[]]", fxt.tkn_txt_(0, 2), fxt.tkn_txt_(2, 4)); + fxt.Init_log_(Xop_lnki_log.Invalid_ttl); + fxt.Test_parse_page_wiki("[[ ]]", fxt.tkn_txt_(0, 2), fxt.tkn_space_(2, 3), fxt.tkn_txt_(3, 5)); + } + @Test public void Exc_invalid_u8() { // PURPOSE: "%DO" is an invalid UTF-8 sequence (requires 2 bytes, not just %D0); DATE:2013-11-11 + fxt.Ctx().Lang().Case_mgr_u8_(); // NOTE: only occurs during Universal + fxt.Test_parse_page_all_str("[[%D0]]", "[[%D0]]"); // invalid titles render literally + } + @Test public void Ex_eq() { // make sure that eq is not evaluated for kv delimiter + fxt.Test_parse_page_wiki("[[=]]", fxt.tkn_lnki_(0, 5)); + fxt.Test_parse_page_wiki("[[a|=]]", fxt.tkn_lnki_(0, 7)); + } + @Test public void Unclosed() { // PURPOSE: unclosed lnki skips rendering of next table; PAGE:en.w:William Penn (Royal Navy officer) + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "a [[b|c]" + , "" + , "{|" + , "|-" + , "|d" + , "|}" + ),String_.Concat_lines_nl_skip_last + ( "a [[b|c] " // NOTE: \n is converted to \s b/c caption does not allow \n + , "" + , " " + , " " + , " " + , "
    d" + , "
    " + , "" + )); + } + @Test public void Caption_nl() { // PURPOSE: \n in caption should be rendered as space; PAGE:en.w:Schwarzschild radius; and the stellar [[Velocity dispersion|velocity\ndispersion]] + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "a [[b|c" + , "" + , "" + , "d]]" + ), String_.Concat_lines_nl_skip_last + ( "

    a c d" // NOTE: this depends on html viewer to collapse multiple spaces into 1 + , "

    " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Caption_nl_2() { // PURPOSE: unclosed lnki breaks paragraph unexpectedly; PAGE:en.w:Oldsmobile; + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "a" + , "" + , "b [[c" + , "" // NOTE: this new line is needed to produce strange behavior + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "

    b [[c" // NOTE: \n is converted to \s b/c caption does not allow \n; NOTE: removed \s after "c" due to new lnki invalidation;DATE:2014-04-03 + , "

    " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Caption_ref() { // PURPOSE: not handled in lnki; PAGE:en.w:WWI + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "[[File:A.png|thumb|b c]]" + ), String_.Concat_lines_nl_skip_last + ( "
    " + , "
    " + , " \"\"" + , "
    " + , "
    b [1]" + , "
    " + , "
    " + , "
    " + , "" + )); + } + @Test public void Html_ent_pound() { + fxt.Test_parse_page_wiki_str + ( "[[A#b|c]]", String_.Concat_lines_nl_skip_last + ( "c" + )); + } + @Test public void Html_ent_ltr_a() { + fxt.Test_parse_page_wiki_str + ( "[[Ab|c]]", String_.Concat_lines_nl_skip_last + ( "c" + )); + } + @Test public void Pipe_trick() { + fxt.Test_parse_page_wiki_str("[[Page1|]]" , "Page1"); + fxt.Test_parse_page_wiki_str("[[Help:Page1|]]" , "Page1"); + } + @Test public void Thumb_first_align_trumps_all() { // PURPOSE: if there are multiple alignment instructions, take the first EX:[[File:A.png|thumb|center|left]] DATE:20121226 + fxt.Test_parse_page_wiki_str("[[File:A.png|thumb|right|center]]" // NOTE: right trumps center + , String_.Concat_lines_nl_skip_last + ( Xop_para_wkr_basic_tst.File_html("File", "A.png", "7/0", "") + , "" + )); + } + @Test public void Eq() { + fxt.Test_parse_page_all_str("[[B|=]]", "="); + } + @Test public void Href_encode_anchor() { // PURPOSE: test separate encoding for ttl (%) and anchor (.) + fxt.Test_parse_page_all_str("[[^#^]]", "^#^"); + } + @Test public void Href_question() { // PURPOSE.fix: ttl with ? at end should not be considered qarg; DATE:2013-02-08 + fxt.Test_parse_page_all_str("[[A?]]", "A?"); + } + @Test public void Href_question_2() { // PURPOSE: ?action=edit should be encoded; DATE:2013-02-10 + fxt.Test_parse_page_all_str("[[A?action=edit]]", "A?action=edit"); + } + @Test public void Href_question_3() { // PURPOSE.fix: DATE:2014-01-16 + fxt.Test_parse_page_all_str("[[A?b]]", "A?b"); + } + @Test public void Encoded_url() { // PURPOSE.fix: url-encoded characters broke parser when embedded in link; DATE:2013-03-01 + fxt.Init_xwiki_add_user_("commons.wikimedia.org"); + fxt.Test_parse_page_wiki_str("[[File:A.png|link=//commons.wikimedia.org/wiki/%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0?uselang=ru|b]]" + , "\"b\""); + } + @Test public void Link__invalid() { // PURPOSE.fix: do not render invalid text; EX: link={{{1}}}; [[Fil:Randers_-_Hadsund_railway.png|120x160px|link={{{3}}}|Randers-Hadsund Jernbane]]; DATE:2013-03-04 + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link={{{1}}}|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + } + @Test public void Link__html_ent() {// PURPOSE:html entities should be converted to chars; EX:  -> _; DATE:2013-12-16 + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|link=b c]]", String_.Concat_lines_nl_skip_last + ( "\"\"" + )); + } + @Test public void Link__encode() {// PURPOSE:spaces should become underscore; DATE:2015-11-27 + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|link=File:A b ç.ogg]]", String_.Concat_lines_nl_skip_last + ( "\"\"" + )); + } + @Test public void Href_anchor_leading_space() { // PURPOSE: ?action=edit should be encoded; DATE:2013-02-10 + fxt.Test_parse_page_all_str("[[A #b]]", "A #b"); + } + @Test public void Anchor_not_shown_for_wikipedia_ns() { // PURPOSE: Help:A#b was omitting anchor; showing text of "Help:A"; DATE:2013-06-21 + fxt.Test_parse_page_all_str("[[Help:A#b]]", "Help:A#b"); + } + @Test public void Anchor_shown_for_main_ns() { // PURPOSE: counterpart to Anchor_not_shown_for_wikipedia_ns; DATE:2013-06-21 + fxt.Test_parse_page_all_str("[[A#b]]", "A#b"); + } + @Test public void Trail_en() { + fxt.Test_parse_page_all_str("[[Ab]]cd e", "Abcd e"); + } + @Test public void Trail_fr() { + byte[] ltr_c_in_french = Bry_.new_u8("ç"); + Xol_lnki_trail_mgr lnki_trail_mgr = fxt.Wiki().Lang().Lnki_trail_mgr(); + lnki_trail_mgr.Add(ltr_c_in_french); + fxt.Test_parse_page_all_str("[[Ab]]çd e", "Abçd e"); + lnki_trail_mgr.Del(ltr_c_in_french); + } + @Test public void Page() { + fxt.Test_parse_page_wiki("[[File:A.pdf|page=12]]" , fxt.tkn_lnki_().Page_(12)); + } + @Test public void Visited() { // PURPOSE: show redirected titles as visited; EX:fr.w:Alpes_Pennines; DATE:2014-02-28 + Xowe_wiki wiki = fxt.Wiki(); + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, Bry_.new_a7("Src")); // simulate requrest for "Src" page + Xoae_page previous_page = Xoae_page.New_test(wiki, ttl); + previous_page.Redirect_trail().Itms__add__article(previous_page.Url(), ttl, null); // simulate redirect from "Src" + fxt.App().Usere().History_mgr().Add(previous_page); // simulate "Src" already being clicked once; this is the key call + fxt.Wtr_cfg().Lnki__visited_y_(); + fxt.Test_parse_page_all_str("[[Src]]" , "Src"); // show [[Src]] as visited since it exists in history + fxt.Test_parse_page_all_str("[[Other]]" , "Other"); // show other pages as not visited + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__ctg_tst.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__ctg_tst.java index a27517de8..ebec52c40 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__ctg_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__ctg_tst.java @@ -13,3 +13,118 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +import gplx.xowa.langs.cases.*; +public class Xop_lnki_wkr__ctg_tst { + @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 Pre() { // PURPOSE: Category should trim preceding nl; EX:w:Mount Kailash + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "a" + , " [[Category:b]]" + , "c" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "c" + , "

    " + , "" + )); + } + @Test public void Ws() { // FUTURE: needs more para rework; conflicts with Li() test; WHEN: when issue is found + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "a" + , "x [[Category:b]]" + , "c" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "x " + , "c" + , "

    " + , "" + )); + } + @Test public void Li() { // PURPOSE: Category strips all preceding ws; PAGE:en.w:NYC (in external links) + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "*a" + , "*b" + , " [[Category:c]]" + , "*d" + ), String_.Concat_lines_nl_skip_last + ( "
      " + , "
    • a" + , "
    • " + , "
    • b" + , "
    • " + , "
    • d" + , "
    • " + , "
    " + )); + } + @Test public void Li_w_lnke() { // PURPOSE: [[Category]] was being absorbed into lnke; PAGE:de.w:ISO/IEC/IEEE_29119_Software_Testing DATE:2014-07-11 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "* http://a.org" + , "[[Category:B]]" // category should not show below + ), String_.Concat_lines_nl_skip_last + ( "" + , "" + )); + } + @Test public void Merge_li() { // PURPOSE: trim ws preceding [[Category:; de.d:plant; DATE:2014-03-27 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "*a" + , "" + , " [[Category:B]] c" + ), String_.Concat_lines_nl_skip_last + ( "
      " + , "
    • a c" + , "
    • " + , "
    " + , "" + )); + } + @Test public void Merge_pre() { // PURPOSE: leading spaces / nls should be removed from normal Category, else false pre's or excessive line breaks + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl + ( " [[Category:A]]" // removes \s + , " [[Category:B]]" // removes \n\s + ), String_.Concat_lines_nl + ( "

    " + , "

    " + )); + } + @Test public void Literal() { // PURPOSE: do not trim ws if literal Category; EX:fr.wikiquote.org/wiki/Accueil; REF: https://sourceforge.net/p/xowa/tickets/167/; DATE:2013-07-10 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl + ( "[[:Category:A]]" + , "[[:Category:B]]" + ), String_.Concat_lines_nl + ( "

    Category:A" // NOTE: technically WP converts to " " not "\n" (via HtmlTidy?) + , "Category:B" + , "

    " + )); + } + @Test public void Hdr_w_nl() { // PURPOSE: hdr code broken by Category; DATE:2014-04-17 + fxt.Test_parse_page_all_str("==a==\n[[Category:C]]" + , String_.Concat_lines_nl_skip_last + ( "

    a

    " + , "" + )); + } + @Test public void Hdr_only() { // PURPOSE: check that == is hdr; EX:ar.d:جَبَّارَة; DATE:2014-04-17 + fxt.Test_parse_page_wiki_str("==a==[[Category:C]]" + , String_.Concat_lines_nl_skip_last + ( "

    a

    " + , "" + )); + } + @Test public void Hdr_ignore() { // PURPOSE: check that hdr is ignored if next char is not nl; DATE:2014-04-17 + fxt.Test_parse_page_wiki_str("==a==[[Category:C]]b" + , String_.Concat_lines_nl_skip_last + ( "==a==b" + , "" + )); + } +} + diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__frame_tst.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__frame_tst.java index a27517de8..1a18354e5 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__frame_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__frame_tst.java @@ -13,3 +13,11 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_lnki_wkr__frame_tst { + @Before public void init() {fxt.Reset(); fxt.Init_para_n_();} private final Xop_fxt fxt = new Xop_fxt(); + // PURPOSE:use 1st imgtype param; changed between mw1.22.2 and mw1.25.2 PAGE:he.w:מספן_המודיעין EX: [[File:Osa-I class Project205 DN-SN-84-01770.jpg|thumb|frame|abcde]] + @Test public void Use_1st__thumb() {fxt.Test_parse_page_wiki("[[File:A.png|thumb|frame]]", fxt.tkn_lnki_().ImgType_(Xop_lnki_type.Id_thumb));} + @Test public void Use_1st__frame() {fxt.Test_parse_page_wiki("[[File:A.png|frame|thumb]]", fxt.tkn_lnki_().ImgType_(Xop_lnki_type.Id_frame));} +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__invalid_tst.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__invalid_tst.java index a27517de8..2c377c447 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__invalid_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__invalid_tst.java @@ -13,3 +13,83 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +import gplx.xowa.langs.cases.*; import gplx.xowa.wikis.ttls.*; +public class Xop_lnki_wkr__invalid_tst { + @Before public void init() {fxt.Reset(); fxt.Init_para_n_();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Ignore_invalid_url_encodings() { // PURPOSE: if url encoding is invalid, still render lnki as ; EX: fr.w:Bordetella; + fxt.Test_parse_page_all_str("[[%GC]]", "%GC"); + } + @Test public void Caption_still_parsed() { // PURPOSE: failed lnki should still parse xml in caption; EX:ar.w:الصومال; DATE:2014-03-04 + fxt.Test_parse_page_all_str("[[|''a'']]" , "[[|a]]"); + } + @Test public void Ttl_still_parsed() { // PURPOSE: invalid lnki should still parse ttl; BASED_ON:ar.w:الصومال; DATE:2014-03-26 + fxt.Test_parse_page_all_str("[[''[a]'']]" , "[[[a]]]"); + } + @Test public void Pipe_only() { + fxt.Init_log_(Xop_lnki_log.Invalid_ttl); + fxt.Test_parse_page_wiki("[[|]]", fxt.tkn_txt_(0, 2), fxt.tkn_pipe_(2), fxt.tkn_txt_(3, 5)); + } + @Test public void Xnde_should_force_ttl_parse() { // PURPOSE: reparse should be forced at xnde not at pipe; EX: [[ac|d]] reparse should start at ; DATE:2014-03-30 + fxt.Test_parse_page_all_str_and_chk("[[ac|d]]" , "[[ac|d]]", Xop_lnki_log.Invalid_ttl); + } + @Test public void Tblw_tb() { // PURPOSE: reparse should be forced at tblw.tb; DATE:2014-04-03 + fxt.Test_parse_page_all_str_and_chk("[[a\n{||b]]", String_.Concat_lines_nl_skip_last + ( "[[a" + , "|b]]" + , "
    " + , "" + ), Xop_lnki_log.Invalid_ttl); + } + @Test public void Tblw_tr() { // PURPOSE: reparse should be forced at tblw.tr; DATE:2014-04-03 + fxt.Test_parse_page_all_str_and_chk("[[a\n|-b]]", String_.Concat_lines_nl_skip_last + ( "[[a" + , "|-b]]" + ), Xop_lnki_log.Invalid_ttl); + } + @Test public void Tblw_tr_like() { // PURPOSE: do not invalidate if pseudo-tr; DATE:2014-04-03 + fxt.Test_parse_page_all_str_and_chk("[[a|-b]]", "-b"); + } + @Test public void Nl() { // PURPOSE: invalidate if nl; DATE:2014-04-03 + fxt.Test_parse_page_all_str_and_chk("''[[\n]]", "[[\n]]", Xop_lnki_log.Invalid_ttl); + } + @Test public void Nl_with_apos_shouldnt_fail() { // PURPOSE: apos, lnki and nl will cause parser to fail; DATE:2013-10-31 + fxt.Test_parse_page_all_str("''[[\n]]", "[[\n]]"); + } +// @Test public void Brack_end_invalid() { // PURPOSE: invalidate if ]; DATE:2014-04-03; // TODO_OLD: backout apos changes +// fxt.Test_parse_page_all_str_and_chk("[[A] ]", "[[A] ]", Xop_lnki_log.Invalid_ttl); +// } + @Test public void Module() { // PURPOSE: handle lnki_wkr parsing Module text (shouldn't happen); apos, tblw, lnki, and nl will cause parser to fail; also handles scan-bwd; EX:Module:Taxobox; DATE:2013-11-10 + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl + ( " [[''" + , " [[" + , " |" // NOTE: this is actually a tblw_ws_tkn ("\n |") not a pipe_tkn + , "]]" + ) + , String_.Concat_lines_nl_skip_last + ( "
    [["	// NOTE: should be  but scan_bwd can't undo previous apos
    +		, "[["
    +		, " |"
    +		, "
    " + , "" + , "

    ]]" + , "

    " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Tblw_in_lnki() { // PURPOSE: handle invalid tblw tkn inside lnki; DATE:2014-06-06 + fxt.Test_parse_page_all_str("[[A[]\n|b]]", "[[A[]\n|b]]"); // NOTE: \n| is tblw code for td + } +// @Test public void Tmpl() { // PURPOSE: invalid lnki breaks template +// fxt.Init_defn_clear(); +// fxt.Init_defn_add("a", "b"); +// fxt.Test_parse_page_all_str("{{a|[[}}", "b"); +// } + @Test public void Ns_is_not_main_and_starts_with_anchor() { // PURPOSE:page cannot start with # if non-main ns; PAGE:en.w:Spindale,_North_Carolina; DATE:2015-12-28 + fxt.Test_parse_page_all_str("[[File:#A]]" , "[[File:#A]]"); + } +} + diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__link__basic__tst.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__link__basic__tst.java index a27517de8..fd328336e 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__link__basic__tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__link__basic__tst.java @@ -13,3 +13,69 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +import gplx.xowa.langs.cases.*; +public class Xop_lnki_wkr__link__basic__tst { + @Before public void init() {fxt.Reset(); fxt.Init_para_n_();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Link() { + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=File:B.png|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + } + @Test public void Link_blank() { + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + } + @Test public void Link_external() { + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=http://www.b.org|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + } + @Test public void Link_file_system() { + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=file:///C/B.png|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + } + @Test public void Link_file_ns() { + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=File:B.png|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + } + @Test public void Link_external_relative() { + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=//fr.wikipedia.org/wiki/|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + } + @Test public void Link_external_absolute() { + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=http://fr.wikipedia.org/wiki/|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + } + @Test public void Link_external_double_http() {// PURPOSE.fix: link=http://a.org?b=http://c.org breaks lnki; DATE:2013-02-03 + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=//a.org?b=http://c.org]]", String_.Concat_lines_nl_skip_last + ( "\"\"" + )); + } + @Test public void Encode() {// PURPOSE: encode invalid characters in link; PAGE:en.w:List_of_cultural_heritage_sites_in_Punjab,_Pakistan DATE:2014-07-16 + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|link=//b?c\">|d]]" + , "\"d\"" + ); + } + @Test public void Anchor() {// PURPOSE: handle anchors; PAGE:en.w: en.w:History_of_Nauru; DATE:2015-12-27 + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|link=#b]]" + , "\"\"" + ); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__link__xwiki__tst.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__link__xwiki__tst.java index a27517de8..311b97091 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__link__xwiki__tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__link__xwiki__tst.java @@ -13,3 +13,90 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +import gplx.xowa.langs.cases.*; +public class Xop_lnki_wkr__link__xwiki__tst { + @Before public void init() {fxt.Reset(); fxt.Init_para_n_();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Relative() { // NOTE: changed href to return "wiki/" instead of "wiki"; DATE:2013-02-18 + fxt.Init_xwiki_add_user_("fr.wikipedia.org"); + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=//fr.wikipedia.org/wiki/|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + } + @Test public void Relative_domain_only() { // lnki_wtr fails if link is only domain; EX: wikimediafoundation.org; [[Image:Wikispecies-logo.png|35px|link=//species.wikimedia.org]]; // NOTE: changed href to return "/wiki/" instead of ""; DATE:2013-02-18 + fxt.Init_xwiki_add_user_("fr.wikipedia.org"); + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=//fr.wikipedia.org]]", String_.Concat_lines_nl_skip_last + ( "\"\"" + )); + } + @Test public void Absolute() { // NOTE: changed href to return "wiki/" instead of "wiki"; DATE:2013-02-18 + fxt.Init_xwiki_add_user_("fr.wikipedia.org"); + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=http://fr.wikipedia.org/wiki/|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + } + @Test public void Absolute_upload() { // PURPOSE: link to upload.wikimedia.org omits /wiki/; EX: wikimediafoundation.org: [[File:Page1-250px-WMF_AR11_SHIP_spreads_15dec11_72dpi.png|right|125px|border|2010–2011 Annual Report|link=https://upload.wikimedia.org/wikipedia/commons/4/48/WMF_AR11_SHIP_spreads_15dec11_72dpi.pdf]] + fxt.Init_xwiki_add_user_("commons.wikimedia.org"); + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=http://upload.wikimedia.org/wikipedia/commons/7/70/A.png|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + } + @Test public void Relative_deep() { + fxt.Init_xwiki_add_user_("fr.wikipedia.org"); + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=//fr.wikipedia.org/wiki/A/b|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + } + @Test public void Url_w_alias() { // [[File:Commons-logo.svg|25x25px|link=http://en.wikipedia.org/wiki/commons:Special:Search/Earth|alt=|Search Commons]] + fxt.Init_xwiki_add_wiki_and_user_("commons", "commons.wikimedia.org"); + fxt.Init_xwiki_add_wiki_and_user_("en.wikipedia.org", "en.wikipedia.org"); // DATE:2015-07-22 + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=http://en.wikipedia.org/wiki/commons:B|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + fxt.Init_xwiki_clear(); + } + @Test public void Url_w_alias_and_sub_page() { // same as above, but for sub-page; [[File:Commons-logo.svg|25x25px|link=http://en.wikipedia.org/wiki/commons:Special:Search/Earth|alt=|Search Commons]] + fxt.Init_xwiki_add_wiki_and_user_("commons", "commons.wikimedia.org"); + fxt.Init_xwiki_add_wiki_and_user_("en.wikipedia.org", "en.wikipedia.org"); // DATE:2015-07-22 + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=http://en.wikipedia.org/wiki/commons:Special:Search/B|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + fxt.Init_xwiki_clear(); + } + @Test public void Alias__basic() { // alias: basic; [[File:Commons-logo.svg|25x25px|link=commons:Special:Search/Earth]]; fictitious example; DATE:2013-02-18 + fxt.Init_xwiki_add_wiki_and_user_("commons", "commons.wikimedia.org"); + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=commons:Special:Search/B|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + fxt.Init_xwiki_clear(); + } + @Test public void Alias__prepended_colon() { // alias prepended with ":"; [[File:Wikipedia-logo.svg|40px|link=:w:|Wikipedia]]; DATE:2013-05-06 + fxt.Init_xwiki_add_wiki_and_user_("commons", "commons.wikimedia.org"); + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=:commons:A/B|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + fxt.Init_xwiki_clear(); + } + @Test public void Alias__colon_only() {// alias: w/ only colon; EX: [[File:Commons-logo.svg|25x25px|link=commons:]]; PAGE:en.w:Wikipedia:Main_Page_alternative_(CSS_Update) DATE:2016-08-18 + // make commons wiki + fxt.Init_xwiki_add_wiki_and_user_("commons", "commons.wikimedia.org"); + Xowe_wiki commons_wiki = (Xowe_wiki)fxt.App().Wiki_mgr().Get_by_or_make_init_n(gplx.xowa.wikis.domains.Xow_domain_itm_.Bry__commons); + commons_wiki.Props().Main_page_(Bry_.new_a7("Test:Commons_main_page")); + + fxt.Test_parse_page_wiki_str + ( "[[File:A.png|12x10px|link=commons:|c]]", String_.Concat_lines_nl_skip_last + ( "\"c\"" + )); + fxt.Init_xwiki_clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__pre_tst.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__pre_tst.java index a27517de8..8d2f8a26b 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__pre_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__pre_tst.java @@ -13,3 +13,90 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +import gplx.xowa.langs.cases.*; +public class Xop_lnki_wkr__pre_tst { + @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 Previous_pre() { // PURPOSE: if pre is already in effect, end it; EX: en.b:Knowing_Knoppix/Other_applications + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "a" + , " b" + , "[[File:A.png|thumb]]" + , "c" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
    b"			// NOTE: pre is ended; " b\n[[" -> 
    b
    " + , "
    " + , "" + , Html_A_png + , "" + , "

    c" + , "

    " + , "" + )); + } + @Test public void Current_pre_and_thumb() { // PURPOSE: current pre should be ended by thumb; EX: w:Roller coaster; it.w:Provincia_di_Pesaro_e_Urbino; DATE:2014-02-17 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "a" + , " [[File:A.png|thumb]]" + , "b" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" // NOTE: do not capture " "; confirmed against MW; DATE:2014-02-19 + , Html_A_png + , "" + , "

    b" + , "

    " + , "" + )); + } + @Test public void Current_pre_and_halign() { // PURPOSE: current pre should be ended by halign since they also produce divs; EX: w:Trombiculidae; DATE:2014-02-17 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "a" + , " [[File:A.png|right]]" + , "b" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "
    " // NOTE: do not capture " "; confirmed against MW; DATE:2014-02-19 + , "\"\"
    " + , "" + , "

    b" + , "

    " + , "" + )); + } + @Test public void Current_pre() { // PURPOSE: current pre should exist if not div; DATE:2014-02-17 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "a" + , " [[File:A.png]]" + , "b" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
     \"\""	// NOTE: capture " "; confirmed against MW; DATE:2014-04-14
    +		, "
    " + , "" + , "

    b" + , "

    " + , "" + )); + } + private static final String Html_A_png = String_.Concat_lines_nl_skip_last + ( "
    " + , "
    " + , " \"\"" + , "
    " + , "
    " + , "
    " + , "
    " + , "
    " + ); +} + diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__size_tst.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__size_tst.java index a27517de8..ab91a5296 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__size_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__size_tst.java @@ -13,3 +13,49 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.xowa.parsers.xndes.*; +public class Xop_lnki_wkr__size_tst { + @Before public void init() {fxt.Reset(); fxt.Init_para_n_();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Width__w__ws() { + fxt.Test_parse_page_wiki("[[Image:a| 123 px]]" , fxt.tkn_lnki_().Width_(123)); + } + @Test public void Height() { + fxt.Test_parse_page_wiki("[[Image:a|x40px]]" , fxt.tkn_lnki_().Height_(40).Width_(-1)); + } + @Test public void Invalid_px__accept_double() { + fxt.Test_parse_page_wiki("[[Image:a|40pxpx]]" , fxt.tkn_lnki_().Width_(40).Height_(-1)); + } + @Test public void Invalid_px__accept_double__w_ws() { + fxt.Test_parse_page_wiki("[[Image:a|40pxpx ]]" , fxt.tkn_lnki_().Width_(40).Height_(-1)); + } + @Test public void Invalid_px__accept_double__w_ws_2() {// PURPOSE: handle ws between px's; EX:sv.w:Drottningholms_slott; DATE:2014-03-01 + fxt.Test_parse_page_wiki("[[Image:a|40px px]]" , fxt.tkn_lnki_().Width_(40).Height_(-1)); + } + @Test public void Invalid_px__ignore_if_w() { + fxt.Test_parse_page_wiki("[[Image:a|40px20px]]" , fxt.tkn_lnki_().Width_(-1).Height_(-1)); // -1 b/c "40px" + } + @Test public void Large_number() { // PURPOSE: perf code identified large sizes as caption; DATE:2014-02-15 + fxt.Test_parse_page_wiki("[[Image:a|1234567890x1234567890px]]" , fxt.tkn_lnki_().Width_(1234567890).Height_(1234567890)); + } + @Test public void Large_number__discard_if_gt_int() { // PURPOSE: size larger than int should be discarded, not be Int_.Max_value: PAGE:id.w:Baho; DATE:2014-06-10 + fxt.Test_html_wiki_frag("[[File:A.png|9999999999x30px]]", " width=\"0\" height=\"30\""); // width should not be Int_.Max_value + } + @Test public void Dangling_xnde() { // PURPOSE: dangling xnde should not eat rest of lnki; PAGE:sr.w:Сићевачка_клисура DATE:2014-07-03 + fxt.Init_log_(Xop_xnde_log.Dangling_xnde).Test_parse_page_wiki("[[Image:a.png|c|40px]]" , fxt.tkn_lnki_().Width_(40).Height_(-1)); + } + @Test public void Ws_para() { // PURPOSE:

    in arg_bldr causes parse to fail; EX: w:Supreme_Court_of_the_United_States; DATE:2014-04-05; updated test; DATE:2015-03-31 + fxt.Init_para_y_(); + fxt.Test_parse_page_all("[[File:A.png| \n 40px]]" + , fxt.tkn_para_bgn_para_(0) + , fxt.tkn_lnki_().Width_(40).Height_(-1) + , fxt.tkn_para_end_para_(22)); + fxt.Init_para_n_(); + } + @Test public void Invalid_size() { // PURPOSE: handle invalid sizes + fxt.Test_parse_page_wiki("[[File:A.png|1234xSomeTextpx]]" , fxt.tkn_lnki_().Width_(-1).Height_(-1)); // PAGE:es.b:Alimentación_infantil; DATE:2015-07-10 + fxt.Test_parse_page_wiki("[[File:A.png|100 KBpx]]" , fxt.tkn_lnki_().Width_(-1).Height_(-1)); // PAGE:en.w:Bahamas; DATE:2015-08-05 + fxt.Test_parse_page_wiki("[[File:A.png|size100px]]" , fxt.tkn_lnki_().Width_(-1).Height_(-1)); // PAGE:en.w:Data_compression; DATE:2015-08-05 + fxt.Test_parse_page_wiki("[[File:A.png|20\n0px]]" , fxt.tkn_lnki_().Width_(-1).Height_(-1)); // PAGE:en.w:Double_bass; DATE:2015-08-05 + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__subpage_tst.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__subpage_tst.java index a27517de8..f8bea1af7 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__subpage_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__subpage_tst.java @@ -13,3 +13,54 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.xowa.wikis.nss.*; +public class Xop_lnki_wkr__subpage_tst { + @Before public void init() {fxt.Reset(); fxt.Init_para_n_();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Disabled() { // PURPOSE: slash being interpreted as subpage; PAGE:en.w:[[/dev/null]] + fxt.Wiki().Ns_mgr().Ids_get_or_null(Xow_ns_.Tid__main).Subpages_enabled_(false); + fxt.Test_parse_page_all_str("[[/dev/null]]", "/dev/null"); + fxt.Wiki().Ns_mgr().Ids_get_or_null(Xow_ns_.Tid__main).Subpages_enabled_(true); + } + @Test public void False_match() {// PAGE:en.w:en.wiktionary.org/wiki/Wiktionary:Requests for cleanup/archive/2006 + fxt.Test_parse_page_wiki_str + ( "[[.../compare ...]]" + , ".../compare ..." + ); + } + @Test public void Owner() { // PURPOSE: ../c does "A/c", not "c"; DATE:2014-01-02 + fxt.Page_ttl_("A/b"); + fxt.Test_parse_page_wiki_str + ( "[[../c]]" + , "A/c" + ); + } + @Test public void Owner_w_slash() { // PURPOSE: ../c/ does "c", not "A/c"; DATE:2014-01-02 + fxt.Page_ttl_("A/b"); + fxt.Test_parse_page_wiki_str + ( "[[../c/]]" + , "c" + ); + } + @Test public void Slash() { // PURPOSE: /B should show /B, not A/B; DATE:2014-01-02 + fxt.Page_ttl_("A"); + fxt.Test_parse_page_wiki_str + ( "[[/B]]", String_.Concat_lines_nl_skip_last + ( "/B" + )); + } + @Test public void Slash_w_slash() { // PURPOSE: /B/ should show B, not /B; DATE:2014-01-02 + fxt.Page_ttl_("A"); + fxt.Test_parse_page_wiki_str + ( "[[/B/]]", String_.Concat_lines_nl_skip_last + ( "B" + )); + } + @Test public void Leaf_w_ncr() { // PURPOSE: /Bc should not encode c PAGE:en.s:The_English_Constitution_(1894) DATE:2014-09-07 + fxt.Page_ttl_("A"); + fxt.Test_parse_page_wiki_str + ( "[[/Bc|B]]", String_.Concat_lines_nl_skip_last + ( "B" + )); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__uncommon_tst.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__uncommon_tst.java index a27517de8..7061672b9 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__uncommon_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__uncommon_tst.java @@ -13,3 +13,46 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.cases.*; +public class Xop_lnki_wkr__uncommon_tst { + @Before public void init() {fxt.Reset(); fxt.Init_para_n_();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Double_bracket() { // PURPOSE: handle [[[[A]]]] constructions; PAGE:ru.w:Меркатале_ин_Валь_ди_Песа; DATE:2014-02-04 + fxt.Test_parse_page_all_str("[[[[Test_1]]]]" , "[[Test_1]]"); + } + @Test public void Single_bracket() { // PURPOSE: handle [[A|[b]]] PAGE:en.w:Aubervilliers DATE:2014-06-25 + fxt.Test_html_full_str("[[A|[B]]]", "[B]"); + } + @Test public void Triple_bracket() { // PURPOSE: "]]]" shouldn't invalidate tkn; PAGE:en.w:Tall_poppy_syndrome; DATE:2014-07-23 + fxt.Test_parse_page_all_str("[[A]]]" , "A]"); // title only + fxt.Test_parse_page_all_str("[[A|B]]]" , "B]"); // title + caption; note that ] should be outside b/c MW has more logic that says "if caption starts with '['", but leaving as is; DATE:2014-07-23 + } + @Test public void Triple_bracket_with_lnke_lnki() { // PURPOSE: handle [http://a.org [[File:A.png|123px]]]; PAGE:ar.w:محمد DATE:2014-08-20 + fxt.Test_parse_page_all_str("[http://a.org [[File:A.png|123px]]]" + , "\"\"" + ); + } + @Test public void Multiple_captions() { // PURPOSE: multiple captions should be concatenated (used to only take first); EX:zh.d:维基词典:Unicode字符索引/0000–0FFF; DATE:2014-05-05 + fxt.Test_parse_page_all_str("[[A|B|C|D]]" , "B|C|D"); + } + @Test public void Multiple_captions_file() { // PURPOSE: multiple captions should take last; EX:none; DATE:2014-05-05 + fxt.Test_html_wiki_frag("[[File:A|B|C|D|thumb]]" , "

    D\n
    "); + } + @Test public void Multiple_captions_pipe() { // PURPOSE.fix: multiple captions caused multiple pipes; PAGE:w:Wikipedia:Administrators'_noticeboard/IncidentArchive24; DATE:2014-06-08 + fxt.Test_parse_page_all_str("[[a|b|c d e]]", "b|c d e"); // was b|c| |d| |e + } + @Test public void Toc_fails() { // PURPOSE: null ref when writing hdr with lnki inside xnde; EX:pl.d:head_sth_off;DATE:2014-05-07 + fxt.Test_parse_page_all_str("== [[A]] ==", "

    A

    \n"); + } + @Test public void Upright_is_large() { // PURPOSE: handle large upright which overflows int; PAGE:de.w:Feuerland DATE:2015-02-03 + fxt.Test_html_wiki_frag("[[File:A.png|upright=1.333333333333333333333333333333333333333333333333333333333333333333333]]", " width=\"0\" height=\"0\""); // failure would print out original lnki + } + @Test public void Persian() { // PURPOSE: handle il8n nums; EX:[[پرونده:Shahbazi 3.jpg|۲۰۰px]] -> 200px; PAGE:fa.w:فهرست_آثار_علیرضا_شاپور_شهبازی; DATE:2015-07-18 + Xol_lang_itm lang = fxt.Wiki().Lang(); + fxt.App().Gfs_mgr().Run_str_for(lang, gplx.xowa.xtns.pfuncs.numbers.Pf_formatnum_fa_tst.Persian_numbers_gfs); + lang.Evt_lang_changed(); // force rebuild of size_trie + fxt.Test_html_wiki_frag("[[File:A.png|۲۰۰px]]", " width=\"200\" height=\"0\""); + } +} + diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__video_tst.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__video_tst.java index a27517de8..5094681c9 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__video_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__video_tst.java @@ -13,3 +13,17 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.xowa.files.*; +public class Xop_lnki_wkr__video_tst { + @Before public void init() {fxt.Reset(); fxt.Init_para_n_();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Thumbtime() { + fxt.Test_parse_page_wiki("[[File:A.ogv|thumbtime=123]]" , fxt.tkn_lnki_().Thumbtime_(123)); + fxt.Test_parse_page_wiki("[[File:A.ogv|thumbtime=1:23]]" , fxt.tkn_lnki_().Thumbtime_(83)); + fxt.Test_parse_page_wiki("[[File:A.ogv|thumbtime=1:01:01]]" , fxt.tkn_lnki_().Thumbtime_(3661)); + fxt.Init_log_(Xop_lnki_log.Upright_val_is_invalid).Test_parse_page_wiki("[[File:A.ogv|thumbtime=a]]", fxt.tkn_lnki_().Thumbtime_(-1)); + } + @Test public void Size__null() { // NOTE: make sure that no size defaults to -1; needed for Xof_img_size logic of defaulting -1 to orig_w; DATE:2015-08-07 + fxt.Test_parse_page_wiki("[[File:A.ogv]]" , fxt.tkn_lnki_().Width_(Xof_img_size.Size__neg1)); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__xwiki_tst.java b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__xwiki_tst.java index a27517de8..729550661 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__xwiki_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/Xop_lnki_wkr__xwiki_tst.java @@ -13,3 +13,101 @@ 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.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_lnki_wkr__xwiki_tst { + @Before public void init() {fxt.Reset(); fxt.Init_para_n_();} private Xop_fxt fxt = new Xop_fxt(); + @Test public void Xwiki_file() { // PURPOSE: if xwiki and File, ignore xwiki (hackish); DATE:2013-12-22 + Reg_xwiki_alias("test", "test.wikimedia.org"); // must register xwiki, else ttl will not parse it + fxt.Wiki().Cfg_parser().Lnki_cfg().Xwiki_repo_mgr().Add_or_mod(Bry_.new_a7("test")); // must add to xwiki_repo_mgr + fxt.Test_parse_page_wiki_str + ( "[[test:File:A.png|12x10px]]", String_.Concat_lines_nl_skip_last + ( "\"\"" + )); + fxt.Wiki().Cfg_parser_lnki_xwiki_repos_enabled_(false); // disable for other tests + } + @Test public void Xwiki_anchor() { + Reg_xwiki_alias("test", "test.wikimedia.org"); + fxt.Test_parse_page_wiki_str + ( "[[test:A#b]]", String_.Concat_lines_nl_skip_last + ( "test:A#b" + )); + } + @Test public void Xwiki_empty() { + Reg_xwiki_alias("test", "test.wikimedia.org"); + fxt.Test_parse_page_wiki_str + ( "[[test:]]", String_.Concat_lines_nl_skip_last + ( "test:" + )); + } + @Test public void Xwiki_empty_literal() { + Reg_xwiki_alias("test", "test.wikimedia.org"); + fxt.Test_parse_page_wiki_str + ( "[[:test:]]", String_.Concat_lines_nl_skip_last + ( "test:" + )); + } + @Test public void Xwiki_not_registered() { + fxt.App().Usere().Wiki().Xwiki_mgr().Clear(); + fxt.Wiki().Xwiki_mgr().Add_by_atrs(Bry_.new_a7("test"), Bry_.new_a7("test.wikimedia.org")); // register alias only, but not in user_wiki + fxt.Test_parse_page_wiki_str + ( "[[test:A|A]]", String_.Concat_lines_nl_skip_last + ( "A" + )); + } + @Test public void Literal_lang() { + Reg_xwiki_alias("fr", "fr.wikipedia.org"); + fxt.Test_parse_page_wiki_str + ( "[[:fr:A]]", String_.Concat_lines_nl_skip_last + ( "A" + )); + Tfds.Eq(0, fxt.Page().Slink_list().Count()); + } + @Test public void Simple_and_english() { // PURPOSE: s.w xwiki links to en were not working b/c s.w and en had same super lang of English; PAGE:s.q:Anonymous DATE:2014-09-10 + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + Xowe_wiki wiki = Xoa_app_fxt.Make__wiki__edit(app, "simple.wikipedia.org"); + fxt = new Xop_fxt(app, wiki); // change fxt to simple.wikipedia.org + Reg_xwiki_alias("en", "en.wikipedia.org"); // register "en" alias + fxt.Test_parse_page_wiki_str // test nothing printed + ( "[[en:A]]" + , "" + ); + Tfds.Eq(1, fxt.Page().Slink_list().Count()); // test 1 xwiki lang + } + @Test public void Species_and_commons() { // PURPOSE: species xwiki links to commons should not put link in wikidata langs; PAGE:species:Scarabaeidae DATE:2014-09-10 + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + Xowe_wiki wiki = Xoa_app_fxt.Make__wiki__edit(app, "species.wikimedia.org"); + fxt = new Xop_fxt(app, wiki); // change fxt to species.wikimedia.org + Reg_xwiki_alias("commons", "commons.wikimedia.org"); // register "en" alias + fxt.Test_parse_page_wiki_str // test something printed + ( "[[commons:A]]" + , "commons:A" + ); + Tfds.Eq(0, fxt.Page().Slink_list().Count()); // no xwiki langs + } + @Test public void Wiktionary_and_wikipedia() { // PURPOSE: do not create xwiki links if same lang and differet type; PAGE:s.d:water DATE:2014-09-14 + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + Xowe_wiki wiki = Xoa_app_fxt.Make__wiki__edit(app, "simple.wiktionary.org"); + fxt = new Xop_fxt(app, wiki); // change fxt to simple.wiktionary.org + Reg_xwiki_alias("w", "simple.wikipedia.org"); // register "w" alias + fxt.Test_parse_page_wiki_str // test something printed + ( "[[w:A]]" + , "w:A" + ); + Tfds.Eq(0, fxt.Page().Slink_list().Count()); // test 0 xwiki lang + } + @Test public void Species_and_wikipedia() { // PURPOSE: species creates xwiki links to wikipedia; PAGE:species:Puccinia DATE:2014-09-14 + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + Xowe_wiki wiki = Xoa_app_fxt.Make__wiki__edit(app, "species.wikimedia.org"); + fxt = new Xop_fxt(app, wiki); // change fxt to species.wikimedia.org + Reg_xwiki_alias("fr", "fr.wikipedia.org"); // register "fr" alias + fxt.Test_parse_page_wiki_str // nothing printed + ( "[[fr:A]]" + , "" + ); + Tfds.Eq(1, fxt.Page().Slink_list().Count()); // 1 xwiki lang + } + private void Reg_xwiki_alias(String alias, String domain) { + Xop_fxt.Reg_xwiki_alias(fxt.Wiki(), alias, domain); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/cfgs/Xoc_lnki_cfg.java b/400_xowa/src/gplx/xowa/parsers/lnkis/cfgs/Xoc_lnki_cfg.java index a27517de8..2afc3ad75 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/cfgs/Xoc_lnki_cfg.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/cfgs/Xoc_lnki_cfg.java @@ -13,3 +13,13 @@ 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.lnkis.cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; import gplx.xowa.parsers.lnkis.*; +public class Xoc_lnki_cfg implements Gfo_invk { + public Xoc_lnki_cfg(Xowe_wiki wiki) {xwiki_repo_mgr = new Xoc_xwiki_repo_mgr(wiki);} + public Xoc_xwiki_repo_mgr Xwiki_repo_mgr() {return xwiki_repo_mgr;} private Xoc_xwiki_repo_mgr xwiki_repo_mgr; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_xwiki_repos)) return xwiki_repo_mgr; + else return Gfo_invk_.Rv_unhandled; + } + private static final String Invk_xwiki_repos = "xwiki_repos"; +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/cfgs/Xoc_xwiki_repo_mgr.java b/400_xowa/src/gplx/xowa/parsers/lnkis/cfgs/Xoc_xwiki_repo_mgr.java index a27517de8..5a171e096 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/cfgs/Xoc_xwiki_repo_mgr.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/cfgs/Xoc_xwiki_repo_mgr.java @@ -13,3 +13,31 @@ 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.lnkis.cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; import gplx.xowa.parsers.lnkis.*; +public class Xoc_xwiki_repo_mgr implements Gfo_invk { + private Ordered_hash hash = Ordered_hash_.New_bry(); + private Xowe_wiki wiki; + public Xoc_xwiki_repo_mgr(Xowe_wiki wiki) {this.wiki = wiki;} + public boolean Has(byte[] abrv) { + Xoc_xwiki_repo_itm itm = (Xoc_xwiki_repo_itm)hash.Get_by(abrv); + return itm != null; + } + public void Add_or_mod(byte[] abrv) { + Xoc_xwiki_repo_itm itm = (Xoc_xwiki_repo_itm)hash.Get_by(abrv); + if (itm == null) { + itm = new Xoc_xwiki_repo_itm(abrv); + hash.Add(abrv, itm); + wiki.Cfg_parser_lnki_xwiki_repos_enabled_(true); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_add)) Add_or_mod(m.ReadBry("xwiki")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_add = "add"; +} +class Xoc_xwiki_repo_itm { + public Xoc_xwiki_repo_itm(byte[] abrv) {this.abrv = abrv;} + public byte[] Abrv() {return abrv;} private byte[] abrv; +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/files/Xop_file_logger.java b/400_xowa/src/gplx/xowa/parsers/lnkis/files/Xop_file_logger.java index a27517de8..cfac5af7f 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/files/Xop_file_logger.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/files/Xop_file_logger.java @@ -13,3 +13,10 @@ 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.lnkis.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; import gplx.xowa.parsers.lnkis.*; +public interface Xop_file_logger { + void Log_file(Xop_ctx ctx, Xop_lnki_tkn lnki, byte caller_tid); +} +class Xop_file_logger__noop implements Xop_file_logger { + public void Log_file(Xop_ctx ctx, Xop_lnki_tkn lnki, byte caller_tid) {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/lnkis/files/Xop_file_logger_.java b/400_xowa/src/gplx/xowa/parsers/lnkis/files/Xop_file_logger_.java index a27517de8..fab3a0cf8 100644 --- a/400_xowa/src/gplx/xowa/parsers/lnkis/files/Xop_file_logger_.java +++ b/400_xowa/src/gplx/xowa/parsers/lnkis/files/Xop_file_logger_.java @@ -13,3 +13,8 @@ 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.lnkis.files; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; import gplx.xowa.parsers.lnkis.*; +public class Xop_file_logger_ { + public static final Xop_file_logger Noop = new Xop_file_logger__noop(); + public static final byte Tid__file = 0, Tid__media = 1, Tid__gallery = 2, Tid__imap = 3, Tid__pgbnr_main = 4; +} diff --git a/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_basic_tbl.java b/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_basic_tbl.java index a27517de8..83f57e542 100644 --- a/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_basic_tbl.java +++ b/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_basic_tbl.java @@ -13,3 +13,47 @@ 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.logs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.dbs.engines.sqlite.*; +public class Xop_log_basic_tbl implements Db_tbl { + public final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + public final String fld__log_id, fld__log_tid, fld__log_msg, fld__log_time, fld__page_id, fld__page_ttl, fld__args_len, fld__args_str, fld__src_len, fld__src_str; + private Db_stmt stmt_insert; + public Xop_log_basic_tbl(Db_conn conn){ + this.conn = conn; + this.tbl_name = "log_basic_temp"; + this.fld__log_id = flds.Add_int_pkey_autonum("log_id"); + this.fld__log_tid = flds.Add_int("log_tid"); + this.fld__log_msg = flds.Add_str("log_msg", 255); + this.fld__log_time = flds.Add_int("log_time"); + this.fld__page_id = flds.Add_int("page_id"); + this.fld__page_ttl = flds.Add_str("page_ttl", 255); + this.fld__args_len = flds.Add_int("args_len"); + this.fld__args_str = flds.Add_str("args_str", 4096); + this.fld__src_len = flds.Add_int("src_len"); + this.fld__src_str = flds.Add_str("src_str", 4096); + conn.Rls_reg(this); + this.Create_tbl(); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Delete() {conn.Exec_qry(Db_qry_delete.new_all_(tbl_name));} + public void Insert(int log_tid, String log_msg, int log_time, int page_id, String page_ttl, int args_len, String args_str, int src_len, String src_str) { + if (stmt_insert == null) stmt_insert = Db_stmt_.new_insert_(conn, tbl_name, fld__log_tid, fld__log_msg, fld__log_time, fld__page_id, fld__page_ttl, fld__args_len, fld__args_str, fld__src_len, fld__src_str); + stmt_insert.Clear() + .Val_int(fld__log_tid, log_tid) + .Val_str(fld__log_msg, log_msg) + .Val_int(fld__log_time, log_time) + .Val_int(fld__page_id, page_id) + .Val_str(fld__page_ttl, page_ttl) + .Val_int(fld__args_len, args_len) + .Val_str(fld__args_str, args_str) + .Val_int(fld__src_len, src_len) + .Val_str(fld__src_str, src_str) + .Exec_insert(); + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_basic_wkr.java b/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_basic_wkr.java index a27517de8..c71e5fa70 100644 --- a/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_basic_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_basic_wkr.java @@ -13,3 +13,63 @@ 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.logs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.envs.*; +import gplx.dbs.*; +import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.htmls.*; +public class Xop_log_basic_wkr implements Gfo_invk { + private Xop_log_basic_tbl log_tbl; + private boolean save_page_ttl, save_log_time, save_args_len, save_args_str; + public boolean Save_src_str() {return save_src_str;} public Xop_log_basic_wkr Save_src_str_(boolean v) {save_src_str = v; return this;} private boolean save_src_str; + public Xop_log_basic_wkr(Xop_log_basic_tbl log_tbl) {this.log_tbl = log_tbl;} + public boolean Log_bgn(Xoae_page page, byte[] src, Xop_xnde_tkn xnde) {return true;} + public void Log_end_xnde(Xoae_page page, int log_tid, byte[] src, Xop_xnde_tkn xnde_tkn) { + Mwh_atr_itm[] atrs_ary = xnde_tkn.Atrs_ary(); + Log_end(page, Null_log_bgn, log_tid, Null_log_msg, src + , xnde_tkn.Src_bgn(), xnde_tkn.Src_end() + , atrs_ary == null ? 0 : atrs_ary.length + , xnde_tkn.Atrs_bgn(), xnde_tkn.Atrs_end() + ); + } + public void Log_end(Xoae_page page, long log_bgn, int log_tid, byte[] log_msg, byte[] src, int src_bgn, int src_end, int args_len, int args_bgn, int args_end) { + log_tbl.Insert + ( log_tid + , log_msg == Xop_log_basic_wkr.Null_log_msg ? "" : String_.new_u8(log_msg) + , save_log_time ? System_.Ticks__elapsed_in_frac(log_bgn) : Xop_log_basic_wkr.Null_log_time + , page.Db().Page().Id() + , save_page_ttl ? String_.new_u8(page.Ttl().Full_db()) : Xop_log_basic_wkr.Null_page_ttl + , save_args_len ? args_len : Xop_log_basic_wkr.Null_args_len + , save_args_str ? String_.new_u8(src, args_bgn, args_end) : Xop_log_basic_wkr.Null_args_str + , src_end - src_bgn + , save_src_str ? String_.new_u8(src, src_bgn, src_end) : Xop_log_basic_wkr.Null_src_str + ); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_save_page_ttl_)) save_page_ttl = m.ReadYn("v"); + else if (ctx.Match(k, Invk_save_log_time_)) save_log_time = m.ReadYn("v"); + else if (ctx.Match(k, Invk_save_args_len_)) save_args_len = m.ReadYn("v"); + else if (ctx.Match(k, Invk_save_args_str_)) save_args_str = m.ReadYn("v"); + else if (ctx.Match(k, Invk_save_src_str_)) save_src_str = m.ReadYn("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String + Invk_save_page_ttl_ = "save_page_ttl_", Invk_save_log_time_ = "save_log_time_" + , Invk_save_args_len_ = "save_args_len_", Invk_save_args_str_ = "save_args_str_", Invk_save_src_str_ = "save_src_str_" + ; + public static final Xop_log_basic_wkr Null = null; + public static final int Null_page_id = -1, Null_log_bgn = -1, Null_log_time = -1, Null_args_len = -1, Null_src_len = -1; + public static final String Null_page_ttl = "", Null_args_str = "", Null_src_str = ""; + public static final byte[] Null_log_msg = null; + public static final int + Tid_gallery = 1 + , Tid_imageMap = 2 + , Tid_timeline = 3 + , Tid_score = 4 + , Tid_hiero = 5 + , Tid_math = 6 + , Tid_graph = 7 + , Tid_mapframe = 8 + , Tid_maplink = 9 + ; +} diff --git a/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_invoke_wkr.java b/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_invoke_wkr.java index a27517de8..eab401edc 100644 --- a/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_invoke_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_invoke_wkr.java @@ -13,3 +13,66 @@ 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.logs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.envs.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.dbs.engines.sqlite.*; import gplx.xowa.parsers.logs.*; +import gplx.xowa.xtns.scribunto.*; +public class Xop_log_invoke_wkr implements Gfo_invk { + private Db_conn conn; private Db_stmt stmt; + private boolean log_enabled = true; + private Hash_adp_bry exclude_mod_names = Hash_adp_bry.cs(); + public Scrib_err_filter_mgr Err_filter_mgr() {return err_filter_mgr;} private final Scrib_err_filter_mgr err_filter_mgr = new Scrib_err_filter_mgr(); + public Xop_log_invoke_wkr(Db_conn conn) { + this.conn = conn; + if (log_enabled) { + Xop_log_invoke_tbl.Create_table(conn); + stmt = Xop_log_invoke_tbl.Insert_stmt(conn); + } + } + public void Init_reset() {Xop_log_invoke_tbl.Delete(conn);} + public boolean Eval_bgn(Xoae_page page, byte[] mod_name, byte[] fnc_name) {return !exclude_mod_names.Has(mod_name);} + public void Eval_end(Xoae_page page, byte[] mod_name, byte[] fnc_name, long invoke_time_bgn) { + if (log_enabled && stmt != null) { + int eval_time = (int)(System_.Ticks() - invoke_time_bgn); + Xop_log_invoke_tbl.Insert(stmt, page.Ttl().Rest_txt(), mod_name, fnc_name, eval_time); + } + } + private void Exclude_mod_names_add(String[] v) { + int len = v.length; + for (int i = 0; i < len; i++) { + byte[] bry = Bry_.new_u8(v[i]); + exclude_mod_names.Add_bry_bry(bry); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_exclude_mod_names_add)) Exclude_mod_names_add(m.ReadStrAry("v", "|")); + else if (ctx.Match(k, Invk_log_enabled_)) log_enabled = m.ReadYn("v"); + else if (ctx.Match(k, Invk_err_filter)) return err_filter_mgr; + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_exclude_mod_names_add = "exclude_mod_names_add", Invk_log_enabled_ = "log_enabled_", Invk_err_filter = "err_filter"; +} +class Xop_log_invoke_tbl { + public static void Create_table(Db_conn conn) {Sqlite_engine_.Tbl_create(conn, Tbl_name, Tbl_sql);} + public static void Delete(Db_conn conn) {conn.Exec_qry(Db_qry_delete.new_all_(Tbl_name));} + public static Db_stmt Insert_stmt(Db_conn conn) {return Db_stmt_.new_insert_(conn, Tbl_name, Fld_invk_page_ttl, Fld_invk_mod_name, Fld_invk_fnc_name, Fld_invk_eval_time);} + public static void Insert(Db_stmt stmt, byte[] page_ttl, byte[] mod_name, byte[] fnc_name, int eval_time) { + stmt.Clear() + .Val_bry_as_str(page_ttl) + .Val_bry_as_str(mod_name) + .Val_bry_as_str(fnc_name) + .Val_int(eval_time) + .Exec_insert(); + } + public static final String Tbl_name = "log_invoke_temp", Fld_invk_page_ttl = "invk_page_ttl", Fld_invk_mod_name = "invk_mod_name", Fld_invk_fnc_name = "invk_fnc_name", Fld_invk_eval_time = "invk_eval_time"; + private static final String Tbl_sql = String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS log_invoke_temp" + , "( invk_id integer NOT NULL PRIMARY KEY AUTOINCREMENT" + , ", invk_page_ttl varchar(255) NOT NULL" + , ", invk_mod_name varchar(255) NOT NULL" + , ", invk_fnc_name varchar(255) NOT NULL" + , ", invk_eval_time integer NOT NULL" + , ");" + ); +} diff --git a/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_mgr.java b/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_mgr.java index a27517de8..9f6eb417f 100644 --- a/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_mgr.java +++ b/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_mgr.java @@ -13,3 +13,56 @@ 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.logs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.dbs.*; import gplx.xowa.bldrs.*; +public class Xop_log_mgr implements Gfo_invk { + private Db_conn conn; + private Xoae_app app; private Xop_log_basic_tbl log_tbl; + private int exec_count = 0, commit_interval = 1000; + public Xop_log_mgr(Xoae_app app) {this.app = app;} + public Io_url Log_dir() {return log_dir;} + public Xop_log_mgr Log_dir_(Io_url v) { + log_dir = v; +// if (conn != null) { // COMMENTED: need to implement a conn.Renew() +// conn.Rls(); // invalidate conn; note that during build other cmds will bind Conn which will place temp.log in /temp/ dir instead of /wiki/ dir; DATE:2014-04-16 +// } + return this; + } private Io_url log_dir; + private Db_conn Conn() { + if (conn == null) { + if (log_dir == null) log_dir = app.Usere().Fsys_mgr().App_temp_dir(); + Xob_db_file db_file = Xob_db_file.New__temp_log(log_dir); + conn = db_file.Conn(); + } + return conn; + } + public Xop_log_invoke_wkr Make_wkr_invoke() {return new Xop_log_invoke_wkr(this.Conn());} + public Xop_log_property_wkr Make_wkr_property() {return new Xop_log_property_wkr(this.Conn());} + public Xop_log_basic_wkr Make_wkr() { + if (log_tbl == null) { + log_tbl = new Xop_log_basic_tbl(this.Conn()); + this.Conn().Meta_tbl_assert(log_tbl); + } + return new Xop_log_basic_wkr(log_tbl); + } + public void Commit_chk() { + ++exec_count; + if ((exec_count % commit_interval) == 0) + conn.Txn_sav(); + } + public void Delete_all() { + log_tbl.Delete(); + } + public void Txn_bgn() {conn.Txn_bgn("log_mgr");} + public void Txn_end() {conn.Txn_end();} + public void Rls() { + if (log_tbl != null) log_tbl.Rls(); + if (conn != null) {conn.Rls_conn(); conn = null;} + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_commit_interval_)) commit_interval = m.ReadInt("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_commit_interval_ = "commit_interval_"; +} diff --git a/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_property_wkr.java b/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_property_wkr.java index a27517de8..904cb1f3f 100644 --- a/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_property_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_property_wkr.java @@ -13,3 +13,62 @@ 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.logs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.envs.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.dbs.engines.sqlite.*; +public class Xop_log_property_wkr implements Gfo_invk { + private Db_conn conn; private Db_stmt stmt; + private boolean log_enabled = true; + private boolean include_all = true; + private Hash_adp_bry include_props = Hash_adp_bry.cs(); + public Xop_log_property_wkr(Db_conn conn) { + this.conn = conn; + if (log_enabled) { + Xob_log_property_temp_tbl.Create_table(conn); + stmt = Xob_log_property_temp_tbl.Insert_stmt(conn); + } + } + public void Init_reset() {Xob_log_property_temp_tbl.Delete(conn);} + public boolean Eval_bgn(Xoae_page page, byte[] prop) {return include_all || include_props.Has(prop);} + public void Eval_end(Xoae_page page, byte[] prop, long invoke_time_bgn) { + if (log_enabled && stmt != null) { + int eval_time = (int)(System_.Ticks() - invoke_time_bgn); + Xob_log_property_temp_tbl.Insert(stmt, page.Ttl().Rest_txt(), prop, eval_time); + } + } + private void Include_props_add(String[] v) { + int len = v.length; + for (int i = 0; i < len; i++) { + byte[] bry = Bry_.new_u8(v[i]); + include_props.Add_bry_bry(bry); + } + include_all = false; // set include_all to false, since specific items added + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_include_props_add)) Include_props_add(m.ReadStrAry("v", "|")); + else if (ctx.Match(k, Invk_log_enabled_)) log_enabled = m.ReadYn("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_include_props_add = "include_props_add", Invk_log_enabled_ = "log_enabled_"; +} +class Xob_log_property_temp_tbl { + public static void Create_table(Db_conn conn) {Sqlite_engine_.Tbl_create(conn, Tbl_name, Tbl_sql);} + public static void Delete(Db_conn conn) {conn.Exec_qry(Db_qry_delete.new_all_(Tbl_name));} + public static Db_stmt Insert_stmt(Db_conn conn) {return Db_stmt_.new_insert_(conn, Tbl_name, Fld_prop_page_ttl, Fld_prop_prop_name, Fld_prop_eval_time);} + public static void Insert(Db_stmt stmt, byte[] page_ttl, byte[] prop_name, int eval_time) { + stmt.Clear() + .Val_bry_as_str(page_ttl) + .Val_bry_as_str(prop_name) + .Val_int(eval_time) + .Exec_insert(); + } + public static final String Tbl_name = "log_property_temp", Fld_prop_page_ttl = "prop_page_ttl", Fld_prop_prop_name = "prop_prop_name", Fld_prop_eval_time = "prop_eval_time"; + private static final String Tbl_sql = String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS log_property_temp" + , "( prop_id integer NOT NULL PRIMARY KEY AUTOINCREMENT" + , ", prop_page_ttl varchar(255) NOT NULL" + , ", prop_prop_name varchar(255) NOT NULL" + , ", prop_eval_time integer NOT NULL" + , ");" + ); +} diff --git a/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_wkr_factory.java b/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_wkr_factory.java index a27517de8..1b70130a7 100644 --- a/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_wkr_factory.java +++ b/400_xowa/src/gplx/xowa/parsers/logs/Xop_log_wkr_factory.java @@ -13,3 +13,18 @@ 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.logs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.dbs.*; import gplx.xowa.bldrs.*; +public class Xop_log_wkr_factory { + private final Db_conn conn; private Xop_log_basic_tbl log_tbl; + public Xop_log_wkr_factory(Db_conn conn) {this.conn = conn;} + public Xop_log_invoke_wkr Make__invoke() {return new Xop_log_invoke_wkr(conn);} + public Xop_log_property_wkr Make__property() {return new Xop_log_property_wkr(conn);} + public Xop_log_basic_wkr Make__generic() { + if (log_tbl == null) { + log_tbl = new Xop_log_basic_tbl(conn); + conn.Meta_tbl_assert(log_tbl); + } + return new Xop_log_basic_wkr(log_tbl); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_bry_tkn.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_bry_tkn.java index a27517de8..d006eb749 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_bry_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_bry_tkn.java @@ -13,3 +13,14 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.parsers.tmpls.*; +public class Xop_bry_tkn extends Xop_tkn_itm_base { + public Xop_bry_tkn(int bgn, int end, byte[] val) {this.val = val; this.Tkn_ini_pos(false, bgn, end);} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_bry;} + public byte[] Val() {return val;} private byte[] val; + @Override public boolean Tmpl_evaluate(Xop_ctx ctx, byte[] src, Xot_invk caller, Bry_bfr bfr) { + bfr.Add(val); + return true; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_comm_log.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_comm_log.java index a27517de8..83f64bd7c 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_comm_log.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_comm_log.java @@ -13,3 +13,11 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.log_msgs.*; +public class Xop_comm_log { + private static final Gfo_msg_grp owner = Gfo_msg_grp_.new_(Xoa_app_.Nde, "comment"); + public static final Gfo_msg_itm + Eos = Gfo_msg_itm_.new_warn_(owner, "eos") + ; +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_comm_lxr.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_comm_lxr.java index a27517de8..10dd79476 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_comm_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_comm_lxr.java @@ -13,3 +13,84 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +import gplx.xowa.parsers.paras.*; +public class Xop_comm_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_comment;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Bgn_ary, 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) { + int lhs_end = cur_pos; + int end_pos = Bry_find_.Find_fwd(src, End_ary, cur_pos, src_len); // search for "-->" // NOTE: do not reuse cur_pos, else cur_pos may become -1 and fatal error in ctx.Msg_log() below; DATE:2014-06-08 + int rhs_bgn = end_pos; + if (end_pos == Bry_find_.Not_found) { // "-->" not found + ctx.Msg_log().Add_itm_none(Xop_comm_log.Eos, src, bgn_pos, cur_pos); + cur_pos = src_len; // gobble up rest of content + } + else + cur_pos = end_pos + End_len; + cur_pos = Trim_ws_if_entire_line_is_commment(ctx, tkn_mkr, root, src, src_len, cur_pos, lhs_end, rhs_bgn); + ctx.Subs_add(root, tkn_mkr.Ignore(bgn_pos, cur_pos, Xop_ignore_tkn.Ignore_tid_comment)); + return cur_pos; + } + private static int Trim_ws_if_entire_line_is_commment(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int cur_pos, int lhs_end, int rhs_bgn) {// REF.MW:Preprocessor_DOM.php|preprocessToXml|handle comments; DATE:2014-02-24 + if ( ctx.Tid_is_popup() + && ctx.Parse_tid() == Xop_parser_tid_.Tid__wtxt // note that only popup parse can generate that makes it to wtxt + && Bry_.Match(src, lhs_end, rhs_bgn, Xowa_skip_text_bry) // + ) + return cur_pos; // in popup mode only do not gobble trailing \n; PAGE:en.w:Gwynedd; DATE:2014-07-01 + int nl_lhs = -1; + int subs_len = root.Subs_len(); + for (int i = subs_len - 1; i > -1; i--) { // look bwd for "\n" + Xop_tkn_itm sub = root.Subs_get(i); + switch (sub.Tkn_tid()) { + case Xop_tkn_itm_.Tid_space: case Xop_tkn_itm_.Tid_tab: + break; + case Xop_tkn_itm_.Tid_ignore: + Xop_ignore_tkn sub_as_ignore = (Xop_ignore_tkn)sub; + if (sub_as_ignore.Ignore_type() != Xop_ignore_tkn.Ignore_tid_comment) + i = -1; + break; + case Xop_tkn_itm_.Tid_newLine: // new_line found; anything afterwards is a \s or a \t; SEE.WIKT:coincidence + nl_lhs = i; + break; + default: + i = -1; + break; + } + } + if (nl_lhs == -1) return cur_pos; // non ws tkns found before \n; exit now; EX: \n\saa\n + for (int i = nl_lhs + 1; i < subs_len; i++) { // entire line is ws; trim everything from nl_lhs + 1 to nl_rhs; do not trim nl_lhs + Xop_tkn_itm sub_tkn = root.Subs_get(i); + sub_tkn.Ignore_y_grp_(ctx, root, i); + } + ctx.Subs_add(root, tkn_mkr.NewLine(nl_rhs - 1, nl_rhs, Xop_nl_tkn.Tid_char, 1).Ignore_y_()); // add tkn for nl_rhs, but mark as ignore; needed for multiple comment nls; EX: "\n\n;"; DATE:2014-02-24 + return nl_rhs; + } + public static final byte[] Bgn_ary = new byte[] {60, 33, 45, 45}, /**/ + private static final int End_len = End_ary.length; + public static final Xop_comm_lxr Instance = new Xop_comm_lxr(); Xop_comm_lxr() {} + private static final String Xowa_skip_text_str = "XOWA_SKIP"; + private static final byte[] Xowa_skip_text_bry = Bry_.new_a7(Xowa_skip_text_str); + public static final byte[] Xowa_skip_comment_bry = Bry_.new_a7(""); +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_comm_lxr_tst.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_comm_lxr_tst.java index a27517de8..c90cb58a7 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_comm_lxr_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_comm_lxr_tst.java @@ -13,3 +13,88 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_comm_lxr_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Basic() { + fxt.Test_parse_page_all_str("ac", "ac"); + } + @Test public void Err() { + fxt.Init_log_(Xop_comm_log.Eos).Test_parse_page_all_str(" " + , "c" + ), String_.Concat_lines_nl_skip_last + ( "a" + , "c" + )); + } + @Test public void Ws_bgn_end() { + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "a" + , " " + , "c" + ), String_.Concat_lines_nl_skip_last + ( "a" + , "c" + )); + } + @Test public void Ws_noop() { // PURPOSE: assert that comments do not strip ws + fxt.Test_parse_page_all_str("a c", "a c"); + } + @Test public void Noinclude() {// PURPOSE: templates can construct comments; EX:WBK: {{Subjects/allbooks|subject=Computer programming|origin=Computer programming languages|diagnose=}} + fxt.Test_parse_page_all_str("a - b -->c", "a c"); + } + @Test public void Comment_can_cause_pre() {// PURPOSE: assert that comment causes pre; DATE:2014-02-18 + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "a" + , " c" + , "d" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
    c"
    +		, "
    " + , "" + , "

    d" + , "

    " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Ws_bgn_needs_nl() { // PURPOSE: do not strip new line unles *entire* line is comment + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "a" + , " " + , "c" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "c" + , "

    " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Ws_strip_nl() { // PURPOSE: handle multiple "\n"; was only trimming 1st; DATE:2014-02-24 + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "a" + , "" + , "" + , "b" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "b" + , "

    " + , "" + )); + fxt.Init_para_n_(); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_cr_lxr.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_cr_lxr.java index a27517de8..d4a0c1fd0 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_cr_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_cr_lxr.java @@ -13,3 +13,15 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +public class Xop_cr_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_cr;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Byte_ascii.Cr, 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) { + return cur_pos; //ignore + } + public static final Xop_cr_lxr Instance = new Xop_cr_lxr(); Xop_cr_lxr() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_cr_tkn.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_cr_tkn.java index a27517de8..4c7af3d81 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_cr_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_cr_tkn.java @@ -13,3 +13,21 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_cr_tkn extends Xop_tkn_itm_base { + public Xop_cr_tkn(int bgn, int end) {this.Tkn_ini_pos(true, -1, -1);} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_cr;} +} +/* +NOTE_1:tabs +. tabs exist in wikimedia source; note that tabs (\t) are not a meaningful HTML character +. xowa uses tabs for delimiters in its xowa files +. in order to maintain some semblance of fidelity, "\t" was replaced with +. unfortunately, "\t" is generally trimmed as whitespace throughout mediawiki; " " is not +. so, as a HACK, replace " " with "\t\s\s\s\s"; +.. note that all 5 chars of " " must be replaced; hence "\t\s\s\s\s" +.. note that they all need to be ws in order to be trimmed out +.. note that shrinking the src[] would be (a) memory-expensive (b) complexity-expensive (many functions assume a static src size) +.. note that "\t\t\t\t\t" was the 1st attempt, but this resulted in exponential growth of "\t"s with each save (1 -> 5 -> 25 -> 125). "\t\s\s\s\s" is less worse with its linear growth (1 -> 5 -> 10) +. TODO_OLD: swap out the " " at point of file-read; +*/ \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_eq_lxr.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_eq_lxr.java index a27517de8..e8210db38 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_eq_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_eq_lxr.java @@ -13,3 +13,69 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +import gplx.xowa.parsers.paras.*; import gplx.xowa.parsers.tmpls.*; +public class Xop_eq_lxr implements Xop_lxr { + public Xop_eq_lxr(boolean tmpl_mode) {this.tmpl_mode = tmpl_mode;} boolean tmpl_mode; + public int Lxr_tid() {return Xop_lxr_.Tid_eq;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Byte_ascii.Eq, 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) { + cur_pos = Bry_find_.Find_fwd_while(src, cur_pos, src_len, Byte_ascii.Eq); // gobble up eq; "==" should produce 1 eq_tkn with len of 2, not 2 eq_tkn with len of 1; DATE:2014-04-17 + int eq_len = cur_pos - bgn_pos; + boolean hdr_like = false; + if (tmpl_mode) { + Xop_tkn_itm owner = ctx.Stack_get_last(); // beginning of "is == part of a hdr tkn sequence?"; DATE:2014-02-09 + if ( owner != null && owner.Tkn_tid() == Xop_tkn_itm_.Tid_tmpl_curly_bgn // inside curly + && eq_len > 1) { // only skip if at least "=="; don't want to skip "=" which could be kv delimiter; DATE:2014-04-17 + int prv_pos = bgn_pos - 1; + if (prv_pos > -1 && src[prv_pos] == Byte_ascii.Nl) // is prv char \n; EX: "\n===" + hdr_like = true; + else { + int eol_pos = Bry_find_.Find_fwd_while_space_or_tab(src, cur_pos, src_len); // skip trailing ws; EX: "== \n"; PAGE:nl.q:Geert_Wilders; DATE:2014-06-05 + if ( eol_pos == src_len // eos + || src[eol_pos] == Byte_ascii.Nl // cur_pos is \n; EX: "===\n" + ) { + hdr_like = true; + cur_pos = eol_pos; + } + } + if (hdr_like) // ignore hdr tkn; + return ctx.Lxr_make_txt_(cur_pos); + } + ctx.Subs_add(root, tkn_mkr.Eq(bgn_pos, cur_pos)); + return cur_pos; + } + + // wiki_mode; chk if hdr exists + int stack_pos = ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_hdr); + if (stack_pos == Xop_ctx.Stack_not_found) { // no hdr; make eq_tkn and return; + ctx.Subs_add(root, tkn_mkr.Eq(bgn_pos, cur_pos)); + return cur_pos; + } + int ws_end = Bry_find_.Find_fwd_while_space_or_tab(src, cur_pos, src_len); + hdr_like = ws_end == src_len || src[ws_end] == Byte_ascii.Nl; // hdr_like if next char \n or eos + if (!hdr_like) { + int ctg_end = Xop_nl_lxr.Scan_fwd_for_ctg(ctx, src, cur_pos, src_len); // check if ==[[Category:A]]; DATE:2014-04-17 + if ( ctg_end != Bry_find_.Not_found) { // [[Category: found + ctg_end = Bry_find_.Find_fwd(src, Xop_tkn_.Lnki_end, ctg_end, src_len); + if (ctg_end != Bry_find_.Not_found) { // ]] found; note that this should do more validation; EX: [[Category:]] should not be valid; DATE:2014-04-17 + ctg_end += Xop_tkn_.Lnki_end_len; + ctg_end = Bry_find_.Find_fwd_while_space_or_tab(src, ctg_end, src_len); + if (ctg_end == src_len || src[ctg_end] == Byte_ascii.Nl) // hdr_like if ]]\n after [[Category:A]] + hdr_like = true; + } + } + } + if (hdr_like) { + cur_pos = ws_end; + return ctx.Hdr().Make_tkn_end(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, stack_pos, eq_len); + } + + // = is just text; create = tkn and any other ws tkns; NOTE: also create ws tkns if scanned; EX: "== a === bad"; create "===" and " "; position at "b" + ctx.Subs_add(root, tkn_mkr.Eq(bgn_pos, cur_pos, eq_len)); + return cur_pos; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_eq_tkn.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_eq_tkn.java index a27517de8..4607f0146 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_eq_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_eq_tkn.java @@ -13,3 +13,10 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_eq_tkn extends Xop_tkn_itm_base {//20111222 + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_eq;} + public int Eq_len() {return eq_len;} private int eq_len = -1; + public int Eq_ws_rhs_bgn() {return eq_ws_rhs_bgn;} public Xop_eq_tkn Eq_ws_rhs_bgn_(int v) {eq_ws_rhs_bgn = v; return this;} private int eq_ws_rhs_bgn = -1; + public Xop_eq_tkn(int bgn, int end, int eq_len) {this.Tkn_ini_pos(false, bgn, end); this.eq_len = eq_len;} +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_hr_lxr.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_hr_lxr.java index a27517de8..9f76d1f01 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_hr_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_hr_lxr.java @@ -13,3 +13,32 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +import gplx.xowa.parsers.xndes.*; +public class Xop_hr_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_hr;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr parse_trie) {parse_trie.Add(Hook_ary, this);} static final byte[] Hook_ary = new byte[] {Byte_ascii.Nl, Byte_ascii.Dash, Byte_ascii.Dash, Byte_ascii.Dash, Byte_ascii.Dash}; + 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) { + int nl_adj = -1; // -1 to ignore nl at bgn for hr_len + boolean bos = bgn_pos == Xop_parser_.Doc_bgn_bos; + if (bos) { + bgn_pos = 0; // do not allow -1 pos + nl_adj = 0; // no nl at bgn, so nl_adj = 0 + } + ctx.Apos().End_frame(ctx, root, src, bgn_pos, false); + ctx.CloseOpenItms(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos); // close open items + cur_pos = Bry_find_.Find_fwd_while(src, cur_pos, src_len, Hook_byt); // gobble consecutive dashes + if (!bos) + ctx.Para().Process_nl(ctx, root, src, bgn_pos, bgn_pos); // simulate \n in front of ---- + ctx.Para().Process_block__bgn_y__end_n(Xop_xnde_tag_.Tag__hr); // para=n; block=y + int hr_len = cur_pos - bgn_pos + nl_adj; // TODO_OLD: syntax_check if > 4 + ctx.Subs_add(root, tkn_mkr.Hr(bgn_pos, cur_pos, hr_len)); + ctx.Para().Process_block__bgn_n__end_y(Xop_xnde_tag_.Tag__hr); // block=n; para=y; + return cur_pos; + } private static final byte Hook_byt = Byte_ascii.Dash; + public static final int Hr_len = 4; + public static final Xop_hr_lxr Instance = new Xop_hr_lxr(); Xop_hr_lxr() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_hr_lxr_basic_tst.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_hr_lxr_basic_tst.java index a27517de8..99ba91c36 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_hr_lxr_basic_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_hr_lxr_basic_tst.java @@ -13,3 +13,17 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_hr_lxr_basic_tst { + @Before public void init() {fxt.Reset();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Basic() {fxt.Test_parse_page_wiki("----" , fxt.tkn_hr_(0, 4));} + @Test public void Basic_w_nl() {fxt.Test_parse_page_wiki("\n----a" , fxt.tkn_para_blank_(0), fxt.tkn_hr_(0, 5), fxt.tkn_txt_(5, 6));} + @Test public void Many() {fxt.Test_parse_page_wiki("---------" , fxt.tkn_hr_(0, 9).Hr_len_(9));} + @Test public void Exc_short() {fxt.Test_parse_page_wiki("---" , fxt.tkn_txt_(0, 3));} + @Test public void Exc_interrupt() {fxt.Test_parse_page_wiki("\na----" , fxt.tkn_nl_char_len1_(0), fxt.tkn_txt_(1, 6));} + @Test public void Html_basic() {fxt.Test_parse_page_wiki_str("----" , "
    ");} + @Test public void Html_extended() {fxt.Test_parse_page_wiki_str("------" , "
    ");} + @Test public void Nl_bgn() {fxt.Test_parse_page_wiki_str("a\n----" , "a\n
    ");} + @Test public void Nl_end() {fxt.Test_parse_page_wiki_str("----\na" , "
    \na");} +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_hr_lxr_para_tst.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_hr_lxr_para_tst.java index a27517de8..dd1e5ad59 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_hr_lxr_para_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_hr_lxr_para_tst.java @@ -13,3 +13,40 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_hr_lxr_para_tst { + @Before public void init() {fxt.Reset(); fxt.Init_para_y_();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Bos() { // PURPOSE: check that bos rendered correctly; DATE:2014-04-18 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "----" + , "a" + ), String_.Concat_lines_nl_skip_last + ( "
    " + , "" + , "

    a" + , "

    " + )); + } + @Test public void Multiple() { // PURPOSE.fix: hr disables para for rest of page; ca.b:Xarxes; DATE:2014-04-18 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "a" + , "----" + , "b" + , "" + , "" + , "c" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "
    " + , "" + , "

    b" + , "

    " + , "" + , "


    " + , "c" + , "

    " + )); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_hr_tkn.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_hr_tkn.java index a27517de8..ac11f667a 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_hr_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_hr_tkn.java @@ -13,3 +13,9 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_hr_tkn extends Xop_tkn_itm_base { + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_hr;} + public int Hr_len() {return hr_len;} public Xop_hr_tkn Hr_len_(int v) {hr_len = v; return this;} private int hr_len; + public Xop_hr_tkn(int bgn, int end, int hr_len) {this.Tkn_ini_pos(false, bgn, end); this.hr_len = hr_len;} +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_ignore_tkn.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_ignore_tkn.java index a27517de8..2078e5138 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_ignore_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_ignore_tkn.java @@ -13,3 +13,15 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.parsers.tmpls.*; +public class Xop_ignore_tkn extends Xop_tkn_itm_base { + public Xop_ignore_tkn(int bgn, int end, byte ignore_type) {this.Tkn_ini_pos(false, bgn, end); this.ignore_type = ignore_type;} + public byte Ignore_type() {return ignore_type;} private byte ignore_type = Ignore_tid_null; + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_ignore;} + @Override public void Tmpl_compile(Xop_ctx ctx, byte[] src, Xot_compile_data prep_data) {} + @Override public boolean Tmpl_evaluate(Xop_ctx ctx, byte[] src, Xot_invk caller, Bry_bfr bfr) {return true;} + public static final byte + Ignore_tid_null = 0, Ignore_tid_comment = 1, Ignore_tid_include_tmpl = 2, Ignore_tid_include_wiki = 3, Ignore_tid_htmlTidy_tblw = 4 + , Ignore_tid_xnde_dangling = 5, Ignore_tid_nbsp = 6, Ignore_tid_empty_li = 7, Ignore_tid_pre_at_bos = 8, Ignore_tid_tr_w_td = 9, Ignore_tid_double_pipe = 10; +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_ignore_tkn_chkr.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_ignore_tkn_chkr.java index a27517de8..49e816e2a 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_ignore_tkn_chkr.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_ignore_tkn_chkr.java @@ -13,3 +13,15 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xop_ignore_tkn_chkr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Xop_ignore_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_ignore;} + public byte Ignore_type() {return ignore_type;} public Xop_ignore_tkn_chkr Ignore_tid_(byte v) {ignore_type = v; return this;} private byte ignore_type = Xop_ignore_tkn.Ignore_tid_null; + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { + Xop_ignore_tkn actl = (Xop_ignore_tkn)actl_obj; + err += mgr.Tst_val(ignore_type == Xop_ignore_tkn.Ignore_tid_null, path, "ignore_type", ignore_type, actl.Ignore_type()); + return err; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_misc_log.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_misc_log.java index a27517de8..b0b269412 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_misc_log.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_misc_log.java @@ -13,3 +13,11 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.log_msgs.*; +public class Xop_misc_log { + private static final Gfo_msg_grp owner = Gfo_msg_grp_.new_(Xoa_app_.Nde, "super"); + public static final Gfo_msg_itm + Eos = Gfo_msg_itm_.new_warn_(owner, "End_of_string") + ; +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_pipe_lxr.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_pipe_lxr.java index a27517de8..6de3b8a3e 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_pipe_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_pipe_lxr.java @@ -13,3 +13,78 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +import gplx.xowa.parsers.tblws.*; import gplx.xowa.parsers.lnkis.*; import gplx.xowa.parsers.vnts.*; +public class Xop_pipe_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_pipe;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Byte_ascii.Pipe, 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) { + int cur_stack_tid = ctx.Cur_tkn_tid(), rv = -1; + switch (cur_stack_tid) { + case Xop_tkn_itm_.Tid_brack_bgn: // used for tmpl mode where full lnki_wkr is too heavyweight; matches "[ |" + switch (ctx.Parse_tid()) { + case Xop_parser_tid_.Tid__defn: + case Xop_parser_tid_.Tid__tmpl: + ctx.Subs_add(root, tkn_mkr.Txt(bgn_pos, cur_pos)); + break; + case Xop_parser_tid_.Tid__wtxt: // should never happen? + ctx.Subs_add(root, tkn_mkr.Pipe(bgn_pos, cur_pos)); + break; + default: throw Err_.new_unhandled(ctx.Parse_tid()); + } + return cur_pos; + case Xop_tkn_itm_.Tid_tblw_tb: + case Xop_tkn_itm_.Tid_tblw_tr: + rv = Xop_tblw_lxr_ws.Make(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, Xop_tblw_wkr.Tblw_type_td, false); + if (rv == Xop_tblw_lxr_ws.Tblw_ws_cell_pipe) { + int prv_nl_pos = Bry_find_.Find_bwd(src, Byte_ascii.Nl, cur_pos - 1, 0); if (prv_nl_pos == -1) prv_nl_pos = 0; // find prv nl + if (Bry_.Match(src, prv_nl_pos, prv_nl_pos + 3, Xop_tblw_lxr.Hook_tr)) { // "\n|-" aka tblw_tr + int nl_pos = Bry_find_.Find_fwd(src, Byte_ascii.Nl, cur_pos, src_len); if (nl_pos == Bry_find_.Not_found) nl_pos = src_len; + ctx.Subs_add(root, tkn_mkr.Ignore(bgn_pos, nl_pos, Xop_ignore_tkn.Ignore_tid_tr_w_td)); // gobble up rest of content between "|" and "\n"; PAGE:lv.w:Starptautiska_kosmosa_stacija; DATE:2015-11-21 + return nl_pos; + } + else { + ctx.Subs_add(root, tkn_mkr.Pipe(bgn_pos, cur_pos)); + return cur_pos; + } + } + if (rv == Xop_tblw_lxr_ws.Tblw_ws_cell_pipe) { + ctx.Subs_add(root, tkn_mkr.Pipe(bgn_pos, cur_pos)); + return cur_pos; + } + else + return rv; + case Xop_tkn_itm_.Tid_tblw_td: + case Xop_tkn_itm_.Tid_tblw_th: + case Xop_tkn_itm_.Tid_tblw_tc: + rv = Xop_tblw_lxr_ws.Make(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, Xop_tblw_wkr.Tblw_type_td, false); + if (rv != Xop_tblw_lxr_ws.Tblw_ws_cell_pipe) return rv; + + if (ctx.Tblw().Cell_pipe_seen()) { + ctx.Subs_add(root, tkn_mkr.Pipe(bgn_pos, cur_pos)); + return cur_pos; + } + else { + Xop_tblw_tkn cur_tkn = (Xop_tblw_tkn)ctx.Stack_get_typ(cur_stack_tid); + Xop_tblw_wkr.Atrs_make(ctx, src, root, ctx.Tblw(), cur_tkn, Bool_.N); + return cur_pos; + } + case Xop_tkn_itm_.Tid_lnki: + Xop_lnki_tkn lnki = (Xop_lnki_tkn)ctx.Stack_get_last(); // BLOCK:invalid_ttl_check + if ( lnki.Pipe_count_is_zero() + && !Xop_lnki_wkr_.Parse_ttl(ctx, src, lnki, bgn_pos)) { + ctx.Stack_pop_last(); + return Xop_lnki_wkr_.Invalidate_lnki(ctx, src, root, lnki, bgn_pos); + } + ctx.Subs_add(root, tkn_mkr.Pipe(bgn_pos, cur_pos)); + return cur_pos; + default: + ctx.Subs_add(root, tkn_mkr.Pipe(bgn_pos, cur_pos)); + return cur_pos; + } + } + public static final Xop_pipe_lxr Instance = new Xop_pipe_lxr(); +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_pipe_tkn.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_pipe_tkn.java index a27517de8..eeb8c9cbc 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_pipe_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_pipe_tkn.java @@ -13,3 +13,8 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_pipe_tkn extends Xop_tkn_itm_base { + public Xop_pipe_tkn(int bgn, int end) {this.Tkn_ini_pos(false, bgn, end);} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_pipe;} +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_space_lxr.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_space_lxr.java index a27517de8..6f1e4fdac 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_space_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_space_lxr.java @@ -13,3 +13,17 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +public class Xop_space_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_space;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Byte_ascii.Space, 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) { + cur_pos = Bry_find_.Find_fwd_while(src, cur_pos, src_len, Byte_ascii.Space); + ctx.Subs_add(root, tkn_mkr.Space(root, bgn_pos, cur_pos)); + return cur_pos; + } + public static final Xop_space_lxr Instance = new Xop_space_lxr(); +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_space_lxr_tst.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_space_lxr_tst.java index a27517de8..357e4077c 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_space_lxr_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_space_lxr_tst.java @@ -13,3 +13,18 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_space_lxr_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Before public void init() {fxt.Reset();} + @After public void term() {fxt.Init_para_n_();} + @Test public void Toc_basic() { // PURPOSE: make sure nbsp char is not converted to space; PAGE:en.w:Macedonian–Carthaginian_Treaty; DATE:2014-06-07 + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str("     a", String_.Concat_lines_nl_skip_last // NOTE: ws is actually nbsp; + ( "

         a" // should be

    not

    +		, "

    " + , "" + )); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_space_tkn.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_space_tkn.java index a27517de8..74b7cb8a2 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_space_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_space_tkn.java @@ -13,3 +13,22 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.parsers.tmpls.*; +import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.htmls.*; +public class Xop_space_tkn extends Xop_tkn_itm_base { + public Xop_space_tkn(boolean immutable, int bgn, int end) {this.Tkn_ini_pos(immutable, bgn, end);} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_space;} + @Override public Xop_tkn_itm Tkn_clone(Xop_ctx ctx, int bgn, int end) {return ctx.Tkn_mkr().Space_mutable(bgn, end);} + @Override public boolean Tmpl_evaluate(Xop_ctx ctx, byte[] src, Xot_invk caller, Bry_bfr bfr) { + if (this.Tkn_immutable()) { + bfr.Add_byte(Byte_ascii.Space); + return true; + } + else + return super.Tmpl_evaluate(ctx, src, caller, bfr); + } + @Override public void Html__write(Bry_bfr bfr, Xoh_html_wtr wtr, Xowe_wiki wiki, Xoae_page page, Xop_ctx ctx, Xoh_wtr_ctx hctx, Xoh_html_wtr_cfg cfg, Xop_tkn_grp grp, int sub_idx, byte[] src) { + bfr.Add_byte_repeat(Byte_ascii.Space, this.Src_end_grp(grp, sub_idx) - this.Src_bgn_grp(grp, sub_idx)); // NOTE: lnki.caption will convert \n to \s; see Xop_nl_lxr; PAGE:en.w:Schwarzschild radius + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_tab_lxr.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_tab_lxr.java index a27517de8..03fa3ee17 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_tab_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_tab_lxr.java @@ -13,3 +13,20 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +public class Xop_tab_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_tab;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Byte_ascii.Tab, this); core_trie.Add(Xop_tab_tkn.Bry_tab_ent, 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) { + cur_pos = Bry_find_.Find_fwd_while(src, cur_pos, src_len, Byte_ascii.Tab); + src[bgn_pos] = Byte_ascii.Tab; // HACK: SEE:NOTE_1:tabs + for (int i = bgn_pos + 1; i < cur_pos; i++) + src[i] = Byte_ascii.Space; + ctx.Subs_add(root, tkn_mkr.Tab(bgn_pos, cur_pos)); + return cur_pos; + } + public static final Xop_tab_lxr Instance = new Xop_tab_lxr(); +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_tab_tkn.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_tab_tkn.java index a27517de8..d7909cef5 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_tab_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_tab_tkn.java @@ -13,3 +13,23 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; +public class Xop_tab_tkn extends Xop_tkn_itm_base { + public Xop_tab_tkn(int bgn, int end) {this.Tkn_ini_pos(false, bgn, end);} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_tab;} + public static final byte[] Bry_tab_ent = Bry_.new_a7(" "); +} +/* +NOTE_1:tabs +. tabs exist in wikimedia source; note that tabs (\t) are not a meaningful HTML character +. xowa uses tabs for delimiters in its xowa files +. in order to maintain some semblance of fidelity, "\t" was replaced with +. unfortunately, "\t" is generally trimmed as whitespace throughout mediawiki; " " is not +. so, as a HACK, replace " " with "\t\s\s\s\s"; +.. note that all 5 chars of " " must be replaced; hence "\t\s\s\s\s" +.. note that they all need to be ws in order to be trimmed out +.. note that shrinking the src[] would be (a) memory-expensive (b) complexity-expensive (many functions assume a static src size) +.. note that "\t\t\t\t\t" was the 1st attempt, but this resulted in exponential growth of "\t"s with each save (1 -> 5 -> 25 -> 125). "\t\s\s\s\s" is less worse with its linear growth (1 -> 5 -> 10) +. TODO_OLD: swap out the " " at point of file-read; +*/ \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_tkn_chkr_hr.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_tkn_chkr_hr.java index a27517de8..7922e7a71 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_tkn_chkr_hr.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_tkn_chkr_hr.java @@ -13,3 +13,16 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xop_tkn_chkr_hr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Xop_hr_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_hr;} + public Xop_tkn_chkr_hr(int bgn, int end) {super.Src_rng_(bgn, end);} + public int Hr_len() {return hr_len;} public Xop_tkn_chkr_hr Hr_len_(int v) {hr_len = v; return this;} private int hr_len = -1; + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { + Xop_hr_tkn actl = (Xop_hr_tkn)actl_obj; + err += mgr.Tst_val(hr_len == -1, path, "hr_len", hr_len, actl.Hr_len()); + return err; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_under_lxr.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_under_lxr.java index a27517de8..4d5e54a1b 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_under_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_under_lxr.java @@ -13,3 +13,137 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.kwds.*; +import gplx.xowa.addons.htmls.tocs.*; +import gplx.xowa.wikis.pages.wtxts.*; +public class Xop_under_lxr implements Xop_lxr { + private final Object thread_lock = new Object(); + private Btrie_mgr words_trie_ci, words_trie_cs; private final Btrie_rv trv_cs = new Btrie_rv(), trv_ci = new Btrie_rv(); + public int Lxr_tid() {return Xop_lxr_.Tid_under;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {} + public void Init_by_lang(Xol_lang_itm lang, Btrie_fast_mgr core_trie) { + synchronized (thread_lock) { // TS; DATE:2016-07-06 + Xol_kwd_mgr kwd_mgr = lang.Kwd_mgr(); + int under_kwds_len = under_kwds.length; + Xop_under_lxr lxr = new Xop_under_lxr(); + lxr.words_trie_cs = Btrie_slim_mgr.cs(); + lxr.words_trie_ci = Btrie_u8_mgr.new_(lang.Case_mgr()); + core_trie.Add(Xop_under_hook.Key_std, lxr); + boolean hook_alt_null = true; + for (int i = 0; i < under_kwds_len; i++) { + int kwd_id = under_kwds[i]; + Xol_kwd_grp kwd_grp = kwd_mgr.Get_or_new(kwd_id); + Xol_kwd_itm[] kwd_itms = kwd_grp.Itms(); if (kwd_itms == null) continue; + int kwd_itms_len = kwd_itms.length; + boolean kwd_case_match = kwd_grp.Case_match(); + Btrie_mgr words_trie = kwd_grp.Case_match() ? lxr.words_trie_cs : lxr.words_trie_ci; + for (int j = 0; j < kwd_itms_len; j++) { + Xol_kwd_itm kwd_itm = kwd_itms[j]; + byte[] kwd_bry = kwd_itm.Val(); + int kwd_len = kwd_bry.length; + Object hook_obj = Hook_trie.Match_bgn(kwd_bry, 0, kwd_len); + if (hook_obj != null) { + Xop_under_hook hook = (Xop_under_hook)hook_obj; + byte[] word_bry = Bry_.Mid(kwd_bry, hook.Key_len(), kwd_bry.length); + words_trie.Add_obj(word_bry, new Xop_under_word(kwd_id, word_bry)); + if (hook_alt_null && hook.Tid() == Xop_under_hook.Tid_alt) { + core_trie.Add(Xop_under_hook.Key_alt, lxr); + hook_alt_null = false; + } + } + else { // kwd doesn't start with __; no known examples, but just in case; EX: "NOTOC"; DATE:2014-02-14 + Xop_word_lxr word_lxr = new Xop_word_lxr(kwd_id); + if (kwd_case_match) // cs; add word directly to trie + core_trie.Add(kwd_bry, word_lxr); + else { // NOTE: next part is imprecise; XOWA parser is cs, but kwd is ci; for now, just add all upper and all lower + Gfo_usr_dlg_.Instance.Warn_many("", "", "under keyword does not start with __; id=~{0} key=~{1} word=~{2}", kwd_id, String_.new_u8(kwd_grp.Key()), String_.new_u8(kwd_bry)); + core_trie.Add(lang.Case_mgr().Case_build_lower(kwd_bry), word_lxr); + core_trie.Add(lang.Case_mgr().Case_build_upper(kwd_bry), word_lxr); + } + } + } + } + } + } + public void Term(Btrie_fast_mgr core_trie) {} + private static final int[] under_kwds = new int[] // REF.MW:MagicWord.php + { Xol_kwd_grp_.Id_toc, Xol_kwd_grp_.Id_notoc, Xol_kwd_grp_.Id_forcetoc + , Xol_kwd_grp_.Id_nogallery, Xol_kwd_grp_.Id_noheader, Xol_kwd_grp_.Id_noeditsection + , Xol_kwd_grp_.Id_notitleconvert, Xol_kwd_grp_.Id_nocontentconvert, Xol_kwd_grp_.Id_newsectionlink, Xol_kwd_grp_.Id_nonewsectionlink + , Xol_kwd_grp_.Id_hiddencat, Xol_kwd_grp_.Id_index, Xol_kwd_grp_.Id_noindex, Xol_kwd_grp_.Id_staticredirect + , Xol_kwd_grp_.Id_disambig + }; + private static final Btrie_fast_mgr Hook_trie = Btrie_fast_mgr.cs() + .Add(Xop_under_hook.Key_std, Xop_under_hook.Itm_std) + .Add(Xop_under_hook.Key_alt, Xop_under_hook.Itm_alt) + ; + 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) { + if (cur_pos == src_len) return ctx.Lxr_make_txt_(cur_pos); // eos + int rv = cur_pos; + Object word_obj = words_trie_cs.Match_at(trv_cs, src, cur_pos, src_len); // check cs + if (word_obj == null) { + word_obj = words_trie_ci.Match_at(trv_ci, src, cur_pos, src_len); // check ci + if (word_obj == null) + return ctx.Lxr_make_txt_(cur_pos); // kwd not found; EX: "TOCA__" + else + rv = trv_ci.Pos(); + } + else + rv = trv_cs.Pos(); + Xop_under_word word_itm = (Xop_under_word)word_obj; + Xop_under_lxr.Make_tkn(ctx, tkn_mkr, root, src, src_len, bgn_pos, rv, word_itm.Kwd_id()); + return rv; + } + public static void 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, int kwd_id) { + switch (kwd_id) { + case Xol_kwd_grp_.Id_toc: + ctx.Page_data().Hdr_toc_y_(); + ctx.Para().Process_block_lnki_div(); // NOTE: __TOC__ will manually place
    here; simulate div in order to close any pres; EX:\n\s__TOC__; PAGE:de.w: DATE:2014-07-05 + ctx.Subs_add(root, tkn_mkr.Under(bgn_pos, cur_pos, kwd_id)); // NOTE: only save under_tkn for TOC (b/c its position is needed for insertion); DATE:2013-07-01 + break; + case Xol_kwd_grp_.Id_forcetoc: ctx.Page_data().Hdr_forcetoc_y_(); break; + case Xol_kwd_grp_.Id_notoc: ctx.Page_data().Hdr_notoc_y_(); break; + case Xol_kwd_grp_.Id_noeditsection: break; // ignore; not handling edit sections + case Xol_kwd_grp_.Id_nocontentconvert: ctx.Page_data().Lang_convert_content_(false); break; + case Xol_kwd_grp_.Id_notitleconvert: ctx.Page_data().Lang_convert_title_(false); break; + default: break; // ignore anything else + } + } + public static final Xop_under_lxr Instance = new Xop_under_lxr(); Xop_under_lxr() {} +} +class Xop_word_lxr implements Xop_lxr { + private int kwd_id; + public Xop_word_lxr(int kwd_id) {this.kwd_id = kwd_id;} + public int Lxr_tid() {return Xop_lxr_.Tid_word;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {} + 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) { + Xop_under_lxr.Make_tkn(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, kwd_id); // for now, all word_lxrs only call the under_lxr; DATE:2014-02-14 + return cur_pos; + } +} +class Xop_under_hook { + Xop_under_hook(byte tid, byte[] key) {this.tid = tid; this.key = key; this.key_len = key.length;} + public byte Tid() {return tid;} private byte tid; + public byte[] Key() {return key;} private byte[] key; + public int Key_len() {return key_len;} private int key_len; + public static final byte Tid_std = 1, Tid_alt = 2; + public static final byte[] Key_std = new byte[] {Byte_ascii.Underline, Byte_ascii.Underline}, Key_alt = Bry_.new_u8("__"); // ja wikis + public static final Xop_under_hook + Itm_std = new Xop_under_hook(Tid_std, Key_std) + , Itm_alt = new Xop_under_hook(Tid_alt, Key_alt) + ; +} +class Xop_under_word { + public Xop_under_word(int kwd_id, byte[] word_bry) { + this.kwd_id = kwd_id; + this.word_bry = word_bry; + this.word_len = word_bry.length; + } + public int Kwd_id() {return kwd_id;} private int kwd_id; + public byte[] Word_bry() {return word_bry;} private byte[] word_bry; + public int Word_len() {return word_len;} private int word_len; +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_under_lxr_tst.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_under_lxr_tst.java index a27517de8..e022fdccb 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_under_lxr_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_under_lxr_tst.java @@ -13,3 +13,214 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.xowa.langs.*; import gplx.xowa.langs.kwds.*; +public class Xop_under_lxr_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Before public void init() {fxt.Reset();} + @After public void term() {fxt.Init_para_n_();} + @Test public void Toc_basic() { + fxt.Test_parse_page_all_str("a__TOC__b", "ab"); + } + @Test public void Toc_match_failed() { + fxt.Test_parse_page_all_str("a__TOCA__b", "a__TOCA__b"); + } + @Test public void Toc_match_ci() { + fxt.Test_parse_page_all_str("a__toc__b", "ab"); + } + @Test public void Notoc_basic() { + fxt.Wtr_cfg().Toc__show_(Bool_.Y); // NOTE: must enable in order for TOC to show (and to make sure NOTOC suppresses) + fxt.Test_parse_page_all_str__esc(String_.Concat_lines_nl + ( "__NOTOC__" + , "==a==" + , "==b==" + , "==c==" + , "==d==" + ), String_.Concat_lines_nl + ( "

    a

    " + , "" + , "

    b

    " + , "" + , "

    c

    " + , "" + , "

    d

    " + )); + fxt.Wtr_cfg().Toc__show_(Bool_.N); + } + @Test public void Ignore_pre() { + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str("a\n __NOTOC__\n", String_.Concat_lines_nl + ( "

    a" + , "

    " // NOTE: do not capture " " in front of __NOTOC__; confirmed against MW; DATE:2014-02-19 + , "" + , "


    " + , "

    " + )); + fxt.Init_para_n_(); + } + @Test public void Toc_works() { // PURPOSE: make sure "suppressed" pre does not somehow suppress TOC + fxt.Wtr_cfg().Toc__show_(Bool_.Y); + fxt.Init_para_y_(); + Bry_bfr tmp = Bry_bfr_.New(); + String expd = String_.Concat_lines_nl + ( "

    a" + , "

    " + , "
    " + , "
    " + , "

    Contents

    " + , "
    " + , "
      " + , "
    • 1 b" + , "
    • " + , "
    " + , "
    " + , "" + , "

    b

    " + ); + String actl = gplx.xowa.addons.htmls.tocs.Xowe_hdr_bldr_fxt.Bld_page_with_toc(tmp, fxt, "a\n__TOC__\n==b==\n"); + Tfds.Eq_str_lines(expd, actl); + fxt.Init_para_n_(); + fxt.Wtr_cfg().Toc__show_(Bool_.N); + } + @Test public void Ignore_pre_after() { // PURPOSE: "__TOC__\s\n" must be trimmed at end, else false pre; assertion only (no code exists to handle this test); DATE:2013-07-08 + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl + ( "a" + , "__NOTOC__ " + , "b" + ), String_.Concat_lines_nl + ( "

    a" + , "

    " // NOTE: do not capture " "; confirmed against MW; DATE:2014-02-19 + , "" + , "

    b" + , "

    " + )); + fxt.Init_para_n_(); + } + @Test public void Disambig() { // PURPOSE: ignore "__DISAMBIG__"; EX:{{disambiguation}} DATE:2013-07-24 + fxt.Test_parse_page_all_str("__DISAMBIG__", ""); + } + @Test public void Nocontentconvert() { // simple test; test for flag only; DATE:2014-02-06 + gplx.xowa.parsers.Xop_ctx_page_data page_data = fxt.Ctx().Page_data(); + Tfds.Eq(page_data.Lang_convert_content(), true); + Tfds.Eq(page_data.Lang_convert_title(), true); + fxt.Test_parse_page_all_str("__NOCONTENTCONVERT__ __NOTITLECONVERT__", " "); + Tfds.Eq(page_data.Lang_convert_content(), false); + Tfds.Eq(page_data.Lang_convert_title(), false); + } + @Test public void Eos() { // PURPOSE: check that __ at eos doesn't fail; es.s:Luisa de Bustamante: 3; DATE:2014-02-15 + fxt.Test_parse_page_all_str("__", "__"); + } + @Test public void Pre_toc() { // PURPOSE: make sure that "\n\s__TOC" does not create pre; PAGE:de.w:Main_Page; DATE:2014-04-07 + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "a" + , " __TOC__ " // NOTE: this should not be a pre; DATE:2014-07-05 + , "b" + ), String_.Concat_lines_nl + ( "

    a" + , "

    " + , " " // NOTE: \s should not be captured, but leaving for now + , "" + , "

    b" + , "

    " + )); + fxt.Init_para_n_(); + } + @Test public void Pre_notoc() { // PURPOSE: make sure that "\n\s__NOTOC" does not create pre. note that mechanism is different from TOC; DATE:2014-07-05 + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "a" + , " __NOTOC__ " // NOTE: does not capture " "; confirmed against MW + , "b" + ), String_.Concat_lines_nl + ( "

    a" + , "

    " + , "" + , "

    b" + , "

    " + )); + fxt.Init_para_n_(); + } + @Test public void Hook_alt() { // PURPOSE: ja wikis use alternate __; DATE:2014-03-04 + Xowe_wiki wiki = fxt.Wiki(); Xol_lang_itm lang = wiki.Lang(); + fxt.Init_lang_kwds(lang, Xol_kwd_grp_.Id_toc, true, "__TOC__"); + wiki.Parser_mgr().Main().Init_by_lang(lang); + fxt.Test_parse_page_all_str("a__TOC__b", "ab"); + } + @Test public void Ascii_ci() { // PURPOSE: case-insensitive ascii; DATE:2014-07-10 + Xowe_wiki wiki = fxt.Wiki(); Xol_lang_itm lang = wiki.Lang(); + fxt.Init_lang_kwds(lang, Xol_kwd_grp_.Id_toc, false, "__TOC__"); + wiki.Parser_mgr().Main().Init_by_lang(lang); + fxt.Test_parse_page_all_str("a__TOC__b", "ab"); + fxt.Test_parse_page_all_str("a__toc__b", "ab"); + } + @Test public void Utf8_ci() { // PURPOSE: case-insensitive UTF8; DATE:2014-07-10 + Xowe_wiki wiki = fxt.Wiki(); Xol_lang_itm lang = wiki.Lang(); + lang.Case_mgr_u8_(); + fxt.Init_lang_kwds(lang, Xol_kwd_grp_.Id_toc, false, "__AÉI__"); + wiki.Parser_mgr().Main().Init_by_lang(lang); + fxt.Test_parse_page_all_str("a__AÉI__b", "ab"); + fxt.Test_parse_page_all_str("a__aéi__b", "ab"); + } + @Test public void Utf8_ci_asymmetric() { // PURPOSE: case-insensitive UTF8; asymmetric; DATE:2014-07-10 + Xowe_wiki wiki = fxt.Wiki(); Xol_lang_itm lang = wiki.Lang(); + lang.Case_mgr_u8_(); + fxt.Init_lang_kwds(lang, Xol_kwd_grp_.Id_toc, false, "__İÇİNDEKİLER__"); // __TOC__ for tr.w + wiki.Parser_mgr().Main().Init_by_lang(lang); + fxt.Test_parse_page_all_str("a__İçindekiler__b", "ab"); + } + @Test public void Cs() { // PURPOSE: cs (ascii / utf8 doesn't matter); DATE:2014-07-11 + Xowe_wiki wiki = fxt.Wiki(); Xol_lang_itm lang = wiki.Lang(); + fxt.Init_lang_kwds(lang, Xol_kwd_grp_.Id_toc , Bool_.Y, "__TOC__"); + wiki.Parser_mgr().Main().Init_by_lang(lang); + fxt.Test_parse_page_all_str("a__TOC__b" , "ab"); // ci.pass + fxt.Test_parse_page_all_str("a__toc__b" , "a__toc__b"); // ci.pass + } + @Test public void Ascii_cs_ci() { // PURPOSE: test simultaneous cs and ci; DATE:2014-07-11 + Xowe_wiki wiki = fxt.Wiki(); Xol_lang_itm lang = wiki.Lang(); + fxt.Init_lang_kwds(lang, Xol_kwd_grp_.Id_toc , Bool_.N, "__TOC__"); + fxt.Init_lang_kwds(lang, Xol_kwd_grp_.Id_notoc , Bool_.Y, "__NOTOC__"); + wiki.Parser_mgr().Main().Init_by_lang(lang); + fxt.Test_parse_page_all_str("a__TOC__b" , "ab"); // ci.pass + fxt.Test_parse_page_all_str("a__toc__b" , "ab"); // ci.pass + fxt.Test_parse_page_all_str("a__NOTOC__b" , "ab"); // cs.pass + fxt.Test_parse_page_all_str("a__notoc__b" , "a__notoc__b"); // cs.fail + } + @Test public void Notoc_in_tmpl() { // PURPOSE: test simultaneous cs and ci; DATE:2017-06-11 + fxt.Init_page_create("Template:Notoc", "page_text\n__NOTOC__"); + fxt.Wiki().Html_mgr().Html_wtr().Cfg().Toc__show_(true); + + fxt.Test__parse_to_html_w_skin(String_.Concat_lines_nl + ( "==A1==" + , "==A2==" + , "==A3==" + , "==A4==" + , "{{Notoc}}" + ), String_.Concat_lines_nl + ( "
    " + , "
    " + , "

    Contents

    " + , "
    " + , "
      " + , "
    • 1 A1" + , "
    • " + , "
    • 2 A2" + , "
    • " + , "
    • 3 A3" + , "
    • " + , "
    • 4 A4" + , "
    • " + , "
    " + , "
    " + , "

    A1

    " + , "" + , "

    A2

    " + , "" + , "

    A3

    " + , "" + , "

    A4

    " + , "page_text" + )); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_under_tkn.java b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_under_tkn.java index a27517de8..4f6de3670 100644 --- a/400_xowa/src/gplx/xowa/parsers/miscs/Xop_under_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/miscs/Xop_under_tkn.java @@ -13,3 +13,9 @@ 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.miscs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_under_tkn extends Xop_tkn_itm_base { + public Xop_under_tkn(int bgn, int end, int under_tid) {this.under_tid = under_tid; this.Tkn_ini_pos(false, bgn, end);} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_under;} + public int Under_tid() {return under_tid;} private int under_tid; +} diff --git a/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_lxr.java b/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_lxr.java index a27517de8..9649aac1b 100644 --- a/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_lxr.java @@ -13,3 +13,104 @@ 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.paras; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +import gplx.xowa.parsers.lists.*; import gplx.xowa.parsers.tblws.*; import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.lnkis.*; import gplx.xowa.parsers.miscs.*; +public class Xop_nl_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_nl;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Byte_ascii.Nl, 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) { + if (bgn_pos == Xop_parser_.Doc_bgn_bos) return ctx.Lxr_make_txt_(cur_pos); // simulated nl at beginning of every parse + int trim_category_pos = Scan_fwd_for_ctg(ctx, src, cur_pos, src_len); + if (trim_category_pos != Bry_find_.Not_found) { // [[Category]] found after ws + int root_subs_len = root.Subs_len(); + if (root_subs_len > 0) { + Xop_tkn_itm tkn = root.Subs_get(root_subs_len - 1); + if (tkn.Tkn_tid() == Xop_tkn_itm_.Tid_eq) { + Xop_eq_tkn eq_tkn = (Xop_eq_tkn)tkn; + if (eq_tkn.Eq_len() > 1) { + Xop_nl_tkn nl_tkn = tkn_mkr.NewLine(bgn_pos, cur_pos, Xop_nl_tkn.Tid_char, 1); + ctx.Subs_add(root, nl_tkn); + } + } + } + return trim_category_pos; + } + Xop_tkn_itm last_tkn = ctx.Stack_get_last(); // BLOCK:invalid_ttl_check + if ( !ctx.Tid_is_image_map() + && last_tkn != null + && last_tkn.Tkn_tid() == Xop_tkn_itm_.Tid_lnki) { + Xop_lnki_tkn lnki = (Xop_lnki_tkn)last_tkn; + if ( lnki.Pipe_count_is_zero()) { // always invalid + ctx.Stack_pop_last(); + return Xop_lnki_wkr_.Invalidate_lnki(ctx, src, root, lnki, bgn_pos); + } + } + + ctx.Apos().End_frame(ctx, root, src, bgn_pos, true); // NOTE: frame should at end at bgn_pos (before \n) not after; else, will create tkn at (5,5), while tkn_mkr.Space creates one at (4,5); DATE:2013-10-31 + ctx.Tblw().Cell_pipe_seen_(false); // flip off "|" in tblw seq; EX: "| a\n||" needs to flip off "|" else "||" will be seen as style dlm"; NOTE: not covered by test? + + Xop_para_wkr para_wkr = ctx.Para(); + switch (ctx.Cur_tkn_tid()) { + case Xop_tkn_itm_.Tid_hdr: // last tkn was hdr; close it; EX: \n==a==\nb; "\n" should close 2nd "=="; DATE:2014-02-17 + int acs_pos = ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_hdr); + ctx.Stack_pop_til(root, src, acs_pos, true, bgn_pos, cur_pos, Xop_tkn_itm_.Tid_newLine); + para_wkr.Process_block__bgn_n__end_y(Xop_xnde_tag_.Tag__h2); + break; + case Xop_tkn_itm_.Tid_list: // close list + Xop_list_wkr_.Close_list_if_present(ctx, root, src, bgn_pos, cur_pos); + para_wkr.Process_block__bgn_n__end_y(Xop_xnde_tag_.Tag__li); + break; + case Xop_tkn_itm_.Tid_lnke: // close lnke + if (ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tmpl_invk) == -1) // only close if no tmpl; MWR: [[SHA-2]]; * {{cite journal|title=Proposed + ctx.Stack_pop_til(root, src, ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_lnke), true, bgn_pos, cur_pos, Xop_tkn_itm_.Tid_newLine); + break; + case Xop_tkn_itm_.Tid_lnki: // NOTE: \n in caption or other multipart lnki; don't call para_wkr.Process + Xop_tkn_itm nl_tkn = tkn_mkr.Space(root, bgn_pos, cur_pos); // convert \n to \s. may result in multiple \s, but rely on htmlViewer to suppress; EX: w:Schwarzschild_radius; and the stellar [[Velocity dispersion|velocity\ndispersion]]; + ctx.Subs_add(root, nl_tkn); + return cur_pos; + // case Xop_tkn_itm_.Tid_tblw_tc: case Xop_tkn_itm_.Tid_tblw_td: // STUB: tc/td should not have attributes + case Xop_tkn_itm_.Tid_tblw_tb: case Xop_tkn_itm_.Tid_tblw_tr: case Xop_tkn_itm_.Tid_tblw_th: // nl should close previous tblw's atrs range; EX {{Infobox planet}} and |-\n + Xop_tblw_wkr.Atrs_close(ctx, src, root, Bool_.N); + break; + } + if ( ctx.Parse_tid() == Xop_parser_tid_.Tid__wtxt // parse_mode is wiki + && para_wkr.Enabled() // check that para is enabled + ) + para_wkr.Process_nl(ctx, root, src, bgn_pos, cur_pos); + else { // parse mode is tmpl, or para is disabled; for latter, adding \n for pretty-print + Xop_nl_tkn nl_tkn = tkn_mkr.NewLine(bgn_pos, cur_pos, Xop_nl_tkn.Tid_char, 1); + ctx.Subs_add(root, nl_tkn); + } + return cur_pos; + } + public static int Scan_fwd_for_ctg(Xop_ctx ctx, byte[] src, int cur_pos, int src_len) { + Btrie_rv trv = new Btrie_rv(); + for (int i = cur_pos; i < src_len; i++) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: // ignore ws + break; + case Byte_ascii.Brack_bgn: // [ + if ( Bry_.Has_at(src, src_len, i + 1, Byte_ascii.Brack_bgn) // [[ + && i + 2 < src_len) { + int ttl_bgn = Bry_find_.Find_fwd_while(src, i + 2, src_len, Byte_ascii.Space); + Btrie_slim_mgr ctg_trie = ctx.Wiki().Ns_mgr().Category_trie(); + Object ctg_ns = ctg_trie.Match_at(trv, src, ttl_bgn, src_len); + if (ctg_ns != null // "[[Category" found + && Bry_.Has_at(src, src_len, trv.Pos(), Byte_ascii.Colon)) { // check that next char is : + return i;// return pos of 1st [ + } + return Bry_find_.Not_found; + } + break; + default: // non-ws; return not found + return Bry_find_.Not_found; + } + } + return Bry_find_.Not_found; + } + public static final Xop_nl_lxr Instance = new Xop_nl_lxr(); Xop_nl_lxr() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_tab_lxr.java b/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_tab_lxr.java index a27517de8..9e2829bd5 100644 --- a/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_tab_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_tab_lxr.java @@ -13,3 +13,39 @@ 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.paras; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +import gplx.xowa.parsers.tblws.*; +public class Xop_nl_tab_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_nl_tab;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Hook_nl_tab, this);} private static final byte[] Hook_nl_tab = new byte[] {Byte_ascii.Nl, Byte_ascii.Tab}; + 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) { + int non_ws_pos = Bry_find_.Find_fwd_while_space_or_tab(src, cur_pos, src_len); + if (non_ws_pos < src_len) { // bounds check + Btrie_slim_mgr tblw_trie = ctx.App().Utl_trie_tblw_ws(); + Object tblw_obj = tblw_trie.Match_bgn(src, non_ws_pos, src_len); + if (tblw_obj != null) { + Xop_tblw_ws_itm tblw_itm = (Xop_tblw_ws_itm)tblw_obj; + byte itm_type = tblw_itm.Tblw_type(); + switch (itm_type) { + case Xop_tblw_ws_itm.Type_nl: // ignore nl + case Xop_tblw_ws_itm.Type_xnde: // ignore xnde + break; + default: { // handle tblw + int tblw_rv = ctx.Tblw().Make_tkn_bgn(ctx, tkn_mkr, root, src, src_len, bgn_pos, non_ws_pos + tblw_itm.Hook_len(), false, itm_type, Xop_tblw_wkr.Called_from_pre, -1, -1); + if (tblw_rv != -1) // \n\s| is valid tblw tkn and processed; otherwise fall through; + return tblw_rv; + break; + } + } + } + } + if (bgn_pos != Xop_parser_.Doc_bgn_bos) // don't add \n if BOS; EX: " a" should be " ", not "\n " + ctx.Subs_add(root, tkn_mkr.NewLine(bgn_pos, bgn_pos + 1, Xop_nl_tkn.Tid_char, 1)); + ctx.Subs_add(root, tkn_mkr.Tab(cur_pos - 1, cur_pos)); + return cur_pos; + } + public static final Xop_nl_tab_lxr Instance = new Xop_nl_tab_lxr(); Xop_nl_tab_lxr() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_tab_lxr_tst.java b/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_tab_lxr_tst.java index a27517de8..ca2d68cc5 100644 --- a/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_tab_lxr_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_tab_lxr_tst.java @@ -13,3 +13,51 @@ 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.paras; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_nl_tab_lxr_tst { + @Before public void init() {fxt.Reset(); fxt.Init_para_y_();} private final Xop_fxt fxt = new Xop_fxt(); + @After public void teardown() {fxt.Init_para_n_();} + @Test public void Basic() { // PURPOSE: \n\t|- should be recognized as tblw; EX:zh.v:西安; DATE:2014-05-06 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl + ( "{|" + , "\t|-" + , "|a" + , "|}" + ), String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + )); + } + @Test public void Ws() { // PURPOSE: \n\t|- should be recognized as tblw; EX:zh.v:西安; DATE:2014-05-06 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl + ( "{|" + , "\t |-" // \t + , "|a" + , "|}" + ), String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + )); + } + @Test public void Ignore() {// PURPOSE: \n\t should not be pre; EX:pl.w:Main_Page; DATE:2014-05-06 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "a" + , "\t b" + , "c" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "\t b" + , "c" + , "

    " + )); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_tkn.java b/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_tkn.java index a27517de8..5bdf9bb77 100644 --- a/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_tkn.java @@ -13,3 +13,13 @@ 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.paras; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_nl_tkn extends Xop_tkn_itm_base { + public Xop_nl_tkn(int bgn, int end, byte nl_tid, int nl_len) { + this.Tkn_ini_pos(false, bgn, end); + this.nl_tid = nl_tid; + } + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_newLine;} + public byte Nl_tid() {return nl_tid;} private byte nl_tid = Xop_nl_tkn.Tid_unknown; + public static final byte Tid_unknown = 0, Tid_char = 1, Tid_hdr = 2, Tid_hr = 3, Tid_list = 4, Tid_tblw = 5, Tid_file = 6; +} diff --git a/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_tkn_chkr.java b/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_tkn_chkr.java index a27517de8..210f3c6ba 100644 --- a/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_tkn_chkr.java +++ b/400_xowa/src/gplx/xowa/parsers/paras/Xop_nl_tkn_chkr.java @@ -13,3 +13,15 @@ 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.paras; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xop_nl_tkn_chkr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Xop_nl_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_newLine;} + public byte Nl_tid() {return nl_typeId;} public Xop_nl_tkn_chkr Nl_tid_(byte v) {nl_typeId = v; return this;} private byte nl_typeId = Xop_nl_tkn.Tid_unknown; + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { + Xop_nl_tkn actl = (Xop_nl_tkn)actl_obj; + err += mgr.Tst_val(nl_typeId == Xop_nl_tkn.Tid_unknown, path, "nl_typeId", nl_typeId, actl.Nl_tid()); + return err; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_tkn.java b/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_tkn.java index a27517de8..3b962be1a 100644 --- a/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_tkn.java @@ -13,3 +13,40 @@ 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.paras; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.langs.htmls.*; import gplx.xowa.htmls.core.htmls.*; +public class Xop_para_tkn extends Xop_tkn_itm_base { + public Xop_para_tkn(int pos) {this.Tkn_ini_pos(false, pos, pos);} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_para;} + public byte Para_end() {return para_end;} public Xop_para_tkn Para_end_(byte v) {para_end = v; return this;} private byte para_end = Tid_none; + public byte Para_bgn() {return para_bgn;} public Xop_para_tkn Para_bgn_(byte v) {para_bgn = v; return this;} private byte para_bgn = Tid_none; + public int Space_bgn() {return space_bgn;} public Xop_para_tkn Space_bgn_(int v) {space_bgn = v; return this;} private int space_bgn = 0; + public boolean Nl_bgn() {return nl_bgn;} public Xop_para_tkn Nl_bgn_y_() {nl_bgn = true; return this;} private boolean nl_bgn; + public static final byte + Tid_none = 0 // + , Tid_para = 1 //

    + , Tid_pre = 2 //
    + ; + @Override public void Html__write(Bry_bfr bfr, Xoh_html_wtr wtr, Xowe_wiki wiki, Xoae_page page, Xop_ctx ctx, Xoh_wtr_ctx hctx, Xoh_html_wtr_cfg cfg, Xop_tkn_grp grp, int sub_idx, byte[] src) { + if (nl_bgn && bfr.Len() > 0) { + if (hctx.Mode_is_alt()) // write called during alt='' + bfr.Add_byte_space(); // write '\s', not '\n' + else + bfr.Add_byte_if_not_last(Byte_ascii.Nl); // write '\n' + } + switch (para_end) { + case Xop_para_tkn.Tid_none: break; + case Xop_para_tkn.Tid_para: bfr.Add(Gfh_tag_.P_rhs).Add_byte_nl(); break; // '

    ' + case Xop_para_tkn.Tid_pre: bfr.Add(Gfh_tag_.Pre_rhs).Add_byte_nl(); break; // '

    '
    +			default:						throw Err_.new_unhandled(para_end);
    +		}
    +		switch (para_bgn) {
    +			case Xop_para_tkn.Tid_none:		break;
    +			case Xop_para_tkn.Tid_para:		Xoh_html_wtr_.Para__assert_tag_starts_on_nl(bfr, this.Src_bgn()); bfr.Add(Gfh_tag_.P_lhs); break;		// '

    ' + case Xop_para_tkn.Tid_pre: Xoh_html_wtr_.Para__assert_tag_starts_on_nl(bfr, this.Src_bgn()); bfr.Add(Gfh_tag_.Pre_lhs); break; // '
    ' + default: throw Err_.new_unhandled(para_bgn); + } + if (space_bgn > 0) + bfr.Add_byte_repeat(Byte_ascii.Space, space_bgn); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_tkn_chkr.java b/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_tkn_chkr.java index a27517de8..53f5e6f08 100644 --- a/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_tkn_chkr.java +++ b/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_tkn_chkr.java @@ -13,3 +13,17 @@ 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.paras; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xop_para_tkn_chkr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Xop_para_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_para;} + public byte Para_end() {return para_end;} public Xop_para_tkn_chkr Para_end_(byte v) {para_end = v; return this;} private byte para_end = Byte_.Max_value_127; + public byte Para_bgn() {return para_bgn;} public Xop_para_tkn_chkr Para_bgn_(byte v) {para_bgn = v; return this;} private byte para_bgn = Byte_.Max_value_127; + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { + Xop_para_tkn actl = (Xop_para_tkn)actl_obj; + err += mgr.Tst_val(para_end == Byte_.Max_value_127, path, "para_end", para_end, actl.Para_end()); + err += mgr.Tst_val(para_bgn == Byte_.Max_value_127, path, "para_bgn", para_bgn, actl.Para_bgn()); + return err; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_wkr.java b/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_wkr.java index a27517de8..735b68d9e 100644 --- a/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_wkr.java @@ -13,3 +13,332 @@ 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.paras; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.parsers.tblws.*; import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.miscs.*; +import gplx.core.btries.*; +public class Xop_para_wkr implements Xop_ctx_wkr { + private boolean para_enabled; + private byte cur_mode; + private int para_stack; + private boolean in_block, block_is_bgn_xnde, block_is_end_xnde, in_blockquote, block_is_bgn_blockquote, block_is_end_blockquote; + private int prv_nl_pos; private Xop_para_tkn prv_para; private int prv_ws_bgn; + private final Btrie_rv trv = new Btrie_rv(); + public boolean Enabled() {return enabled;} public Xop_para_wkr Enabled_(boolean v) {enabled = v; return this;} private boolean enabled = true; + public Xop_para_wkr Enabled_y_() {enabled = true; return this;} public Xop_para_wkr Enabled_n_() {enabled = false; return this;} + public void Ctor_ctx(Xop_ctx ctx) {} + public void Page_bgn(Xop_ctx ctx, Xop_root_tkn root) { + this.Clear(); + para_enabled = enabled && ctx.Parse_tid() == Xop_parser_tid_.Tid__wtxt; // only enable for wikitext (not for template) + if (para_enabled) + Prv_para_new(ctx, root, -1, 0); // create at bos + } + private void Clear() { + cur_mode = Mode_none; + para_stack = Para_stack_none; + in_block = block_is_bgn_xnde = block_is_end_xnde = false; + in_blockquote = block_is_bgn_blockquote = block_is_end_blockquote = false; + prv_nl_pos = -1; + prv_para = null; + prv_ws_bgn = 0; + } + public void AutoClose(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, Xop_tkn_itm tkn) {} + public void Page_end(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int src_len) { + if (para_enabled) { + Process_nl(ctx, root, src, src_len, src_len); + this.Prv_para_end(); // close anything created by Process_nl() + } + this.Clear(); + } + public void Process_block__bgn_y__end_n(Xop_xnde_tag tag) {Process_block(tag, Bool_.Y, Bool_.N);} // NOTE: disables para for rest of page; Process_block__bgn_n__end_y must be called; DATE:2014-04-18 + public void Process_block__bgn_n__end_y(Xop_xnde_tag tag) {Process_block(tag, Bool_.N, Bool_.Y);} + public void Process_block__xnde(Xop_xnde_tag tag, byte mode) { + if (mode == Xop_xnde_tag.Block_bgn) Process_block(tag, Bool_.Y, Bool_.N); + else if (mode == Xop_xnde_tag.Block_end) Process_block(tag, Bool_.N, Bool_.Y); + } + public void Process_block_lnki_div() { // bgn_lhs is pos of [[; end_lhs is pos of ]] + if (prv_ws_bgn > 0) // if pre at start of line; ignore it b/c of div; EX: "\n\s[[File:A.png|thumb]]" should not produce thumb; also [[File:A.png|right]]; DATE:2014-02-17 + prv_ws_bgn = 0; + this.Process_block__bgn_n__end_y(Xop_xnde_tag_.Tag__div); + } + private void Process_block(Xop_xnde_tag tag, boolean bgn, boolean end) { + if (prv_ws_bgn > 0) { + prv_para.Space_bgn_(prv_ws_bgn); + prv_ws_bgn = 0; + } + block_is_bgn_xnde = bgn; + block_is_end_xnde = end; + switch (tag.Id()) { + case Xop_xnde_tag_.Tid__blockquote: + if (bgn) block_is_bgn_blockquote = true; + if (end) block_is_end_blockquote = true; + break; + } + } + public void Process_block__bgn__nl_w_symbol(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int bgn_pos, int cur_pos, Xop_xnde_tag tag) {// handle \n== and \n* \n{|; note that nl is at rng of bgn_pos to bgn_pos + 1 (not cur_pos) + if (!para_enabled) return; + Process_nl(ctx, root, src, bgn_pos, bgn_pos + 1); + Process_block__bgn_y__end_n(tag); + } + public void Process_nl(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int bgn_pos, int cur_pos) {// REF.MW:Parser.php|doBlockLevels + Dd_clear(ctx); + if (block_is_bgn_xnde || block_is_end_xnde) { + para_stack = Para_stack_none; // MW: $paragraphStack = false; + Prv_para_end(); // MW: $output .= $this->closeParagraph() + if (block_is_bgn_blockquote && !block_is_end_blockquote) // MW: if ( $preOpenMatch and !$preCloseMatch ) + in_blockquote = true; // MW: $this->mInPre = true; + else + in_blockquote = false; // XO: turn off blockquote else following para / nl won't work; w:Snappy_(software); DATE:2014-04-25 + in_block = !block_is_end_xnde; // MW: $inBlockElem = !$closematch; + } + else if (!in_block && !in_blockquote) { // MW: elseif ( !$inBlockElem && !$this->mInPre ) { + boolean line_is_ws = Line_is_ws(src, bgn_pos); + if (prv_ws_bgn > 0 && (cur_mode == Mode_pre || !line_is_ws)) { // MW: if ( ' ' == substr( $t, 0, 1 ) and ( $this->mLastSection === 'pre' || trim( $t ) != '' ) ) { + if (cur_mode != Mode_pre) { // MW: if ( $this->mLastSection !== 'pre' ) { + para_stack = Para_stack_none; // MW: $paragraphStack = false; + prv_para.Space_bgn_(prv_ws_bgn - 1); // -1 to ignore 1st "\s" in "\n\s"; note that prv_ws_bgn only includes spaces, so BOS doesn't matter; DATE:2014-04-14 + Prv_para_end(); Prv_para_bgn(Xop_para_tkn.Tid_pre); // MW: $output .= $this->closeParagraph() . '
    ';
    +					cur_mode = Mode_pre;													// MW: $this->mLastSection = 'pre';
    +				}
    +				else {																		// already in pre						
    +					if (line_is_ws) {														// line is entirely ws
    +						int next_char_pos = prv_nl_pos + 2;									// "\n\s".length
    +						if (	next_char_pos < src.length									// bounds check
    +							&&	src[next_char_pos] == Byte_ascii.Nl					// is "\n\s\n"; i.e.: "\n" only
    +							) {
    +							ctx.Subs_add(root, ctx.Tkn_mkr().Bry_raw(bgn_pos, bgn_pos, Byte_ascii.Nl_bry));	// add a "\n" tkn; note that adding a NewLine tkn doesn't work, b/c Xoh_html_wtr has code to remove consecutive \n; PAGE:en.w:Preferred_numbers DATE:2014-06-24
    +							prv_nl_pos = bgn_pos;
    +						}
    +					}
    +				}
    +				prv_ws_bgn = 0;																// MW: $t = substr( $t, 1 );
    +			}
    +			else {
    +				if (bgn_pos - prv_nl_pos == 1 || line_is_ws) {								// line is blank ("b" for blank)						MW: if ( trim( $t ) === '' ) {
    +					if (para_stack != Para_stack_none) {									// "b1"; stack has "

    " or "

    "; output "
    "; MW: if ( $paragraphStack ) { + Para_stack_end(cur_pos); Add_br(ctx, root, bgn_pos); // MW: $output .= $paragraphStack . '
    '; + para_stack = Para_stack_none; // MW: $paragraphStack = false; + cur_mode = Mode_para; // MW: $this->mLastSection = 'p'; + } + else { // stack is empty + if (cur_mode != Mode_para) { // "b2"; cur is '' or

    								MW: if ( $this->mLastSection !== 'p' ) {
    +							Prv_para_end();													//														MW: $output .= $this->closeParagraph();
    +							cur_mode = Mode_none;											//														MW: $this->mLastSection = '';
    +							para_stack = Para_stack_bgn;									// put 

    on stack MW: $paragraphStack = '

    '; + } + else // "b3"; cur is p + para_stack = Para_stack_mid; // put

    on stack MW: $paragraphStack = '

    '; + } + } + else { // line has text ("t" for text); NOTE: tkn already added before \n, so must change prv_para; EX: "a\n" -> this code is called for "\n" but "a" already processed + if (para_stack != Para_stack_none) { // "t1" MW: if ( $paragraphStack ) { + Para_stack_end(cur_pos); // MW: $output .= $paragraphStack; + para_stack = Para_stack_none; // MW: $paragraphStack = false; + cur_mode = Mode_para; // MW: $this->mLastSection = 'p'; + } + else if (cur_mode != Mode_para) { // "t2"; cur is '' or

    								MW: elseif ( $this->mLastSection !== 'p' ) {
    +						Prv_para_end(); Prv_para_bgn(Xop_para_tkn.Tid_para);				//														MW: $output .= $this->closeParagraph() . '

    '; + cur_mode = Mode_para; // MW: $this->mLastSection = 'p'; + } + else {} // "t3" + } + } + } + if (in_blockquote && prv_ws_bgn > 0) // handle blockquote separate; EX:

    \n\sa\n
    ; note that "\s" needs to be added literally; MW doesn't have this logic specifically, since it assumes all characters go into $output, whereas XO, sets aside the "\s" in "\n\s" separately + prv_para.Space_bgn_(prv_ws_bgn); + prv_ws_bgn = 0; // nl encountered and processed; always prv_ws_bgn set to 0, else ws from one line will carry over to next + // in_blockquote = false; + block_is_bgn_xnde = block_is_end_xnde = false; + // if ( $preCloseMatch && $this->mInPre ) + // $this->mInPre = false; + // prv_ws_bgn = false; + Prv_para_new(ctx, root, bgn_pos, cur_pos); // add a prv_para placeholder + if (para_stack == Para_stack_none) // "x1" MW: if ( $paragraphStack === false ) { + if (prv_para != null) prv_para.Nl_bgn_y_(); // add nl; note that "$t" has already been processed; MW: $output .= $t . "\n"; + } + public int Process_pre(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, int txt_pos) { + Dd_clear(ctx); + Btrie_slim_mgr tblw_ws_trie = ctx.App().Utl_trie_tblw_ws(); + Object o = tblw_ws_trie.Match_at(trv, src, txt_pos, src_len); + if (o != null) { // tblw_ws found + Xop_tblw_ws_itm ws_itm = (Xop_tblw_ws_itm)o; + byte tblw_type = ws_itm.Tblw_type(); + switch (tblw_type) { + case Xop_tblw_ws_itm.Type_nl: // \n\s + if (cur_mode == Mode_pre) { // already in pre; just process "\n\s" + ctx.Subs_add(root, tkn_mkr.NewLine(bgn_pos, bgn_pos, Xop_nl_tkn.Tid_char, 1)); + prv_nl_pos = bgn_pos; // NOTE: must update prv_nl_pos; PAGE:en.w:Preferred_number DATE:2014-06-24 + return txt_pos; + } + break; + case Xop_tblw_ws_itm.Type_xnde: + int nxt_pos = trv.Pos(); + if (nxt_pos < src_len) { // bounds check + switch (src[nxt_pos]) { // check that next char is "end" of xnde name; guard against false matches like " bgn_pos + 1 is \n ... + if (cur_mode == Mode_pre) // in pre_mode + ctx.Subs_add(root, tkn_mkr.Space(root, cur_pos, txt_pos)); // cur_pos to start after \s; do not capture "\s" in "\n\s"; (not sure why not before \s) + prv_ws_bgn = txt_pos - cur_pos + 1; + return txt_pos; + } + public void Process_lnki_category(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int pos, int src_len) { // REF.MW:Parser.php|replaceInternalLinks2|Strip the whitespace Category links produce; + if (!para_enabled) return; + int subs_len = root.Subs_len(); + for (int i = subs_len - 2; i > -1; i--) { // -2: -1 b/c subs_len is invalid; -1 to skip current lnki + Xop_tkn_itm sub_tkn = root.Subs_get(i); + switch (sub_tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_para: // nl found; note this means that BOL -> [[Category:]] is all ws; + if (prv_ws_bgn > 0) { // line begins with ws a + if (sub_tkn.Src_bgn() != 0) // do not ignore BOS para; needed b/c it is often

    ; needed for test; + sub_tkn.Ignore_y_(); // ignore nl (pretty-printing only) + prv_ws_bgn = 0; // remove ws + if (ctx.Stack_has(Xop_tkn_itm_.Tid_list)){ // HACK: if in list, set prv_nl_pos to EOL; only here for one test to pass + int nl_at_eol = -1; + for (int j = pos; j < src_len; j++) { // check if rest of line is ws + byte b = src[j]; + switch (b) { + case Byte_ascii.Space: case Byte_ascii.Tab: break; // ignore space / tab + case Byte_ascii.Nl: + nl_at_eol = j; + j = src_len; + break; + default: // something else besides ws; stop + j = src_len; + break; + } + if (nl_at_eol != -1) + prv_nl_pos = nl_at_eol + 1; // SEE:NOTE_2 + } + } + } + return; + default: // exit if anything except para / nl in front of [[Category:]] + i = -1; + break; + } + } +// if (para_found) // BOS exit; just remove prv_ws_bgn + prv_ws_bgn = 0; + } + private void Prv_para_new(Xop_ctx ctx, Xop_root_tkn root, int prv_nl_pos, int para_pos) { + this.prv_nl_pos = prv_nl_pos; + prv_para = ctx.Tkn_mkr().Para(para_pos); + ctx.Subs_add(root, prv_para); + } + private void Prv_para_end() { // MW: closeParagraph(); + // following switch is equivalent to: + // MW: if ( $this->mLastSection != '' ) + // MW: $result = 'mLastSection . ">\n"; + switch (cur_mode) { + case Mode_none: return; + case Mode_pre: prv_para.Para_end_(Xop_para_tkn.Tid_pre); break; + case Mode_para: prv_para.Para_end_(Xop_para_tkn.Tid_para); break; + } + // in_pre = false; // MW: $this->mInPre = false; + cur_mode = Mode_none; // MW: $this->mLastSection = ''; + } + private void Prv_para_bgn(byte mode) { + if (prv_para != null) prv_para.Para_bgn_(mode); + } + private void Para_stack_end(int cur_pos) { // MW: $output .= $paragraphStack; + switch (para_stack) { + case Para_stack_none: break; + case Para_stack_bgn: prv_para.Para_end_(Xop_para_tkn.Tid_none).Para_bgn_(Xop_para_tkn.Tid_para); break; // '

    ' + case Para_stack_mid: prv_para.Para_end_(Xop_para_tkn.Tid_para).Para_bgn_(Xop_para_tkn.Tid_para); break; // '

    ' + } + } + private void Add_br(Xop_ctx ctx, Xop_root_tkn root, int bgn_pos) { + ctx.Subs_add(root, ctx.Tkn_mkr().Xnde(bgn_pos, bgn_pos).Tag_(Xop_xnde_tag_.Tag__br)); + } + private boolean Line_is_ws(byte[] src, int pos) { + if (prv_nl_pos == -1) return false; + boolean ws = true; + for (int i = prv_nl_pos + 1; i < pos; i++) { + byte b = src[i]; + switch (b) { + case Byte_ascii.Tab: + case Byte_ascii.Space: + break; + default: + ws = false; + i = pos; + break; + } + } + return ws; + } + private void Dd_clear(Xop_ctx ctx) {ctx.List().Dd_chk_(false);} + private static final int + Para_stack_none = 0 // false + , Para_stack_bgn = 1 //

    + , Para_stack_mid = 2 //

    + ; + private static final byte + Mode_none = 0 // '' + , Mode_para = 1 // p + , Mode_pre = 2 // pre + ; +} +/* +NOTE_1: +xowa uses \n as the leading character for multi-character hooks; EX: "\n*","\n{|","\n==",etc.. +For this section of code, xowa treats \n separately from the rest of the hook for the purpose of emulating MW code. +EX: a\n==b== +MW: +- split into two lines: "a", "==b==" +- call process_nl on "a" +- call process_nl on "==b==" +XO: +- split into "tkns": "a", "\n==", "b", "==" +- add "a" +- add "\n==" + - since there is a "\n", call process_nl, which will effectively call it for "a" +- note that page_end will effectively call process_nl on "==b==" + +NOTE_2: Category needs to "trim" previous line +EX: +* a +* b + [[Category:c]] +* d + +MW does the following: (REF.MW:Parser.php|replaceInternalLinks2|Strip the whitespace Category links produce;) +- removes the \n after b (REF: $s = rtrim( $s . "\n" ); # bug 87) +- trims all space " " in front of [[ (NOTE: this makes it a non-pre line) +- plucks out the [[Category:c]] +- joins everything after ]] (starting with the \n) to the * b (REF: $s .= trim( $prefix . $trail, "\n" ) == '' ? '': $prefix . $trail;) +This effectively "blanks" out the entire line "\n [[Category:c]]" -> "" + +XOWA tries to emulate this by doing the following +- mark the para_tkn after \b as blank +- disable pre for the line +- keep the [[Category:c]], but *simulate* a blank line by moving the prv_nl_pos to after the ]] + +NOTE_3: if (last_section_is_pre) +PURPOSE: if Category trims previous nl, but nl was part of pre, deactivate it +REASON: occurs b/c MW does separate passes for pre and Category while XO does one pass. +EX: "a\n [[Category:c]]" +- pre is activated by \n\s +- [[Category:c]] indicates that \n\s should be trimmed + so, disable_pre, etc. + +*/ \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_wkr_basic_tst.java b/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_wkr_basic_tst.java index a27517de8..e665c230e 100644 --- a/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_wkr_basic_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_wkr_basic_tst.java @@ -13,3 +13,1014 @@ 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.paras; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.xowa.parsers.lists.*; +public class Xop_para_wkr_basic_tst { + private final Xop_fxt fxt = new Xop_fxt(); String raw; + @Before public void init() {fxt.Reset(); fxt.Init_para_y_();} + @After public void teardown() {fxt.Init_para_n_();} + @Test public void Nl_0() { + raw = "a"; + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + )); + fxt.Test_parse_page_wiki(raw + , fxt.tkn_para_bgn_para_(0) // x0: ->

    1=blank + , fxt.tkn_txt_(0, 1), fxt.tkn_para_end_para_(1) // t2/x1: a -> a\n

    1=bgn 2=blank + ); + } + @Test public void Nl_1() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , "b" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "b" // NOTE: depend on html editor removing \n between a and b + , "

    " + )); + fxt.Test_parse_page_wiki(raw + , fxt.tkn_para_bgn_para_(0) // x0: ->

    1=blank + , fxt.tkn_txt_(0, 1), fxt.tkn_para_blank_(2) // t2/x1: a\n -> a\n<-p> 1=bgn 2=blank + , fxt.tkn_txt_(2, 3), fxt.tkn_para_end_para_(3) // t3/x1: b -> b\n

    2=blank 3=blank + ); + } + @Test public void Nl_2() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , "" + , "b" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "

    b" + , "

    " + )); + fxt.Test_parse_page_wiki(raw + , fxt.tkn_para_bgn_para_(0) // x0: ->

    1=blank + , fxt.tkn_txt_(0, 1), fxt.tkn_para_blank_(2) // t2/x1: a\n -> a\n<-p> 1=bgn + , fxt.tkn_para_mid_para_(3) // n3 : \n ->

    3=mid NOTE:

    doesn't get converted until t1 + , fxt.tkn_txt_(3, 4), fxt.tkn_para_end_para_(4) // t1/x1: b -> b\n

    2=mid 3=blank + ); + } + @Test public void Nl_3() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , "" + , "" + , "b" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "


    " // NOTE: this looks strange, but it emulates MW + , "b" + , "

    " + )); + fxt.Test_parse_page_wiki(raw + , fxt.tkn_para_bgn_para_(0) // x0: ->

    1=blank + , fxt.tkn_txt_(0, 1), fxt.tkn_para_blank_(2) // t2/x1: a\n -> a\n<-p> 1=bgn + , fxt.tkn_para_mid_para_(3) // n3 : \n ->

    3=mid NOTE:

    doesn't get converted until n1 + , fxt.tkn_xnde_br_(3), fxt.tkn_para_blank_(4) // n1/x1: \n ->
    \n<-p> 2=mid 3=blank + , fxt.tkn_txt_(4, 5), fxt.tkn_para_end_para_(5) // t1/x1: b -> b\n

    4=blank + ); + } + @Test public void Nl_4() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , "" + , "b" + , "" + , "c" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "

    b" + , "

    " + , "" + , "

    c" + , "

    " + )); + fxt.Test_parse_page_wiki(raw + , fxt.tkn_para_bgn_para_(0) // x0: ->

    1=blank + , fxt.tkn_txt_(0, 1), fxt.tkn_para_blank_(2) // t2/x1: a\n -> a\n<-p> 1=bgn + , fxt.tkn_para_mid_para_(3) // n3 : \n ->

    3=mid NOTE:

    doesn't get converted until n1 + , fxt.tkn_txt_(3, 4), fxt.tkn_para_blank_(5) + , fxt.tkn_para_mid_para_(6) + , fxt.tkn_txt_(6, 7), fxt.tkn_para_end_para_(7) + ); + } + @Test public void Nl_5() { + raw = String_.Concat_lines_nl_skip_last + ( "

    a" + , "b" + , "

    " + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "b" + , "

    " + )); + } + @Test public void Pre() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , " b" + , "c" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
    b"
    +			, "
    " + , "" + , "

    c" + , "

    " + )); + fxt.Test_parse_page_wiki(raw + , fxt.tkn_para_bgn_para_(0) + , fxt.tkn_txt_(0, 1), fxt.tkn_para_end_para_bgn_pre_(2) + , fxt.tkn_txt_(3, 4), fxt.tkn_para_end_pre_bgn_para_(5) + , fxt.tkn_txt_(5, 6), fxt.tkn_para_end_para_(6) + ); + } + @Test public void Pre_2() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , " b" + , " c" + , "d" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
    b"
    +			, "c"
    +			, "
    " + , "" + , "

    d" + , "

    " + )); + } + @Test public void Pre_3() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , "" + , "b" + , " c" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "

    b" + , "

    " + , "" + , "
    c"
    +			, "
    " + )); + } + @Test public void Pre_4() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , "" + , " b" + , "" + , "c" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
    b"
    +			, "
    " + , "" + , "

    c" + , "

    " + )); + } + @Test public void Pre_5() { // PAGE:en.w:SHA-2 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "a" + , " b" + , "" + , " c" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
    b"
    +			, "
    " + , "" + , "
    c"
    +			, "
    " + )); + } + @Test public void Pre_6() { // PURPOSE: close list if open; PAGE:en.w:SHA-2 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "*a" + , " b" + ), String_.Concat_lines_nl_skip_last + ( "
      " + , "
    • a" + , "
    • " + , "
    " + , "" + , "
    b"
    +			, "
    " + )); + } + @Test public void Pre_init() { + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( " a" + ), String_.Concat_lines_nl_skip_last + ( "
    a"
    +			, "
    " + )); + } + @Test public void Pre_leading_ws() { // PURPOSE: preserve leading ws; PAGE:en.w:Merge sort + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( " a" + , " b" + , " c" + ), String_.Concat_lines_nl_skip_last + ( "
    a"
    +			, "  b"
    +			, "    c"
    +			, "
    " + )); + } + @Test public void Pre_newLine() { // PURPOSE: "\n \n" inside pre should be ignored; trims to "" + raw = String_.Concat_lines_nl_skip_last + ( "a" + , " b" + , " " + , " c" + , "d" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
    b"
    +			, ""
    +			, "c"
    +			, "
    " + , "" + , "

    d" + , "

    " + )); + } + @Test public void Pre_xnde() { // PURPOSE:
    and other xndes should invalidate pre on same line; EX: "\n a
    b"; SEE: any {{refimprove}} article + raw = String_.Concat_lines_nl_skip_last + ( "a" + , " b
    c
    d" + , "e" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , " b
    c
    d" + , "" + , "

    e" + , "

    " + )); + } + @Test public void Pre_lnki_in_td() { // PURPOSE: ]] in td causes strange parsing; SEE: any {{Commons category}} article; updated test; DATE:2015-03-31 + raw = String_.Concat_lines_nl_skip_last + ( "" + , "" + , "" + , "" + , "" + , "
    [[File:A.png|alt=" + , " ]]b" + , "
    " + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , "
    \"" + , " b" + , "
    " + )); + } + @Test public void Pre_trailing_end() { // PURPOSE: "\n " at end was failing + raw = String_.Concat_lines_nl_skip_last + ( "a" + , " " + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + )); + } + @Test public void Pre_xnde_code() { // PAGE:en.w:cURL + raw = String_.Concat_lines_nl_skip_last + ( "a" + , " b" + , "" + , "c" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
    b"
    +			, "
    " + , "" + , "

    c" + , "

    " + )); + } + @Test public void Pre_xnde_pre() { + raw = String_.Concat_lines_nl_skip_last + ( "
      " + , "
    • " + , "
      "
      +			, "*a"
      +			, "
      " + , "
    • " + , "
    " + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "
      " + , "
    • " + , "
      "
      +			, "*a"
      +			, "
      " + , "
    • " + , "
    " + )); + } + @Test public void Pre_td() { // PURPOSE: EX: "\n a"; deactivates pre; PAGE:en.w:AGPLv3 + raw = String_.Concat_lines_nl_skip_last + ( "" + , "" + , "" + , "" + , "
    " + , " [[File:A.png|30x30px]]
    " + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    " + , " \"\"" + , "
    " + )); + } + @Test public void Nl_2_2_space() { // test trim + raw = String_.Concat_lines_nl_skip_last + ( "a" + , "" + , "b" + , " " + , "c" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "

    b" + , "

    " // NOTE: do not capture " "; confirmed against MW; DATE:2014-02-19 + , "" + , "

    c" + , "

    " + )); + } + @Test public void File_1() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , "[[Image:Test.png|thumb|caption]]" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , File_html() + , "" + )); + fxt.Test_parse_page_wiki(raw + , fxt.tkn_para_bgn_para_(0) + , fxt.tkn_txt_(0, 1), fxt.tkn_para_end_para_(2) + , fxt.tkn_lnki_(2, 34) + , fxt.tkn_para_blank_(34) + ); + } + @Test public void File_2() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , "[[Image:Test.png|thumb|caption]]" + , "b" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , File_html() + , "" + , "

    b" + , "

    " + , "" + )); + fxt.Test_parse_page_wiki(raw + , fxt.tkn_para_bgn_para_(0) // x0: ->

    1=blank + , fxt.tkn_txt_(0, 1), fxt.tkn_para_end_para_(2) + , fxt.tkn_lnki_(2, 34) + , fxt.tkn_para_bgn_para_(35) + , fxt.tkn_txt_(35, 36), fxt.tkn_para_end_para_(36) + ); + } + @Test public void File_3() { + raw = String_.Concat_lines_nl_skip_last + ( "[[Image:Test.png|thumb|caption]]" + , "" + , "c" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( File_html() + , "" + , "

    c" + , "

    " + , "" + )); + fxt.Test_parse_page_wiki(raw + , fxt.tkn_para_blank_(0) // x0: ->

    1=blank + , fxt.tkn_lnki_(0, 32) + , fxt.tkn_para_blank_(33) + , fxt.tkn_para_bgn_para_(34) + , fxt.tkn_txt_(34, 35), fxt.tkn_para_end_para_(35) + ); + } + @Test public void File_4() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , "" + , "[[Image:Test.png|thumb|caption]]" + , "" + , "c" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , File_html() + , "" + , "

    c" + , "

    " + , "" + )); + fxt.Test_parse_page_wiki(raw + , fxt.tkn_para_bgn_para_(0) // x0: ->

    1=blank + , fxt.tkn_txt_(0, 1), fxt.tkn_para_blank_(2) + , fxt.tkn_para_end_para_(3) + , fxt.tkn_lnki_(3, 35) + , fxt.tkn_para_blank_(36) + , fxt.tkn_para_bgn_para_(37) + , fxt.tkn_txt_(37, 38), fxt.tkn_para_end_para_(38) + ); + } + @Test public void File_5() { // PURPOSE: \n in caption should not force paragraph + raw = String_.Concat_lines_nl_skip_last + ( "[[Image:Test.png|thumb|" + , "caption]]" + , "a" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( File_html() + , "" + , "

    a" + , "

    " + , "" + )); + fxt.Test_parse_page_wiki(raw + , fxt.tkn_para_blank_(0) + , fxt.tkn_lnki_(0, 33) + , fxt.tkn_para_bgn_para_(34) + , fxt.tkn_txt_(34, 35), fxt.tkn_para_end_para_(35) + ); + } + @Test public void File_6() { // PAGE:en.w:Pyotr Ilyich Tchaikovsky + raw = String_.Concat_lines_nl_skip_last + ( "a" + , " [[Image:Test.png|thumb|caption]]" + , "" + , "b" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , File_html() + , "" + , "

    b" + , "

    " + , "" + )); + } + @Test public void List() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , "*b" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
      " + , "
    • b" + , "
    • " + , "
    " + )); + fxt.Test_parse_page_wiki(raw + , fxt.tkn_para_bgn_para_(0) // x0: ->

    1=blank + , fxt.tkn_txt_(0, 1), fxt.tkn_para_end_para_(2) // t2/x1: a\n -> a\n

    1=bgn 2=blank + , fxt.tkn_list_bgn_(1, 3, Xop_list_tkn_.List_itmTyp_ul) + , fxt.tkn_txt_(3, 4) + , fxt.tkn_list_end_(4) + , fxt.tkn_para_blank_(4) + ); + } + @Test public void Hdr_1() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , "==b==" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "

    b

    " + , "" + )); + } + @Test public void Hdr_2() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , "" + , "==b==" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "

    b

    " + , "" + )); + } + @Test public void Hdr_list() { + raw = String_.Concat_lines_nl_skip_last + ( "==a==" + , "*b" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a

    " + , "" + , "
      " + , "
    • b" + , "
    • " + , "
    " + )); + } + @Test public void Hdr_list_multi() { + raw = String_.Concat_lines_nl_skip_last + ( "==a==" + , "" + , "" + , "*b" + , "*c" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a

    " + , "" + , "
      " + , "
    • b" + , "
    • " + , "
    • c" + , "
    • " + , "
    " + )); + } + @Test public void Para_hdr() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , "" + , "b" + , "" + , "==c==" + , "d" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "

    b" + , "

    " + , "" + , "

    c

    " + , "" + , "

    d" + , "

    " + , "" + )); + } + @Test public void Div() { // PURPOSE:
    should not go into para + raw = String_.Concat_lines_nl_skip_last + ( "a" + , "" + , "==b==" + , "
    c
    " + , "d" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "

    b

    " + , "
    c
    " + , "" + , "

    d" + , "

    " + , "" + )); + } + @Test public void Div_para() { // PURPOSE:

    inserted between closing divs + raw = String_.Concat_lines_nl_skip_last + ( "
    " + , "
    " + , "a" + , "
    " + , "
    " + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "
    " + , "
    " + , "" + , "

    a" + , "

    " + , "
    " + , "
    " + )); + } + @Test public void Tbl() { + raw = String_.Concat_lines_nl_skip_last + ( "" + , "" + , "" + , "" + , "" + , "
    a" + , "b" + , "
    " + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , "
    a" + , " b" + , "
    " + , "" + )); + } + @Test public void Tbl_leading_ws() { // REF: [[Corneal dystrophy (human)]] + raw = String_.Concat_lines_nl_skip_last + ( " {|" + , " |-" + , " |a" + , " |}" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + )); + } + @Test public void Bos_pipe() { // PURPOSE: | is interpreted as a tblw dlm and calls a different section of code + fxt.Test_parse_page_wiki_str("|", String_.Concat_lines_nl_skip_last + ( "

    |" + , "

    " + , "" + )); + } + @Test public void Ws_mistakenly_ignored() {// PURPOSE: ws before ''' somehow gets ignored; EX: en.w:Vacuum tube; {{Unreferenced section|date=July 2010|reason=date taken from existing cn}} + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "
    a" + , " '''b'''
    " + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , " b" + , "
    " + , "" + )); + } + @Test public void Pre_7() { // PURPOSE: alternating pres; EX:w:World Wide Web + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "a" + , " b" + , "c" + , " d" + , "e" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
    b"
    +		, "
    " + , "" + , "

    c" + , "

    " + , "" + , "
    d"
    +		, "
    " + , "" + , "

    e" + , "

    " + , "" + )); + } + @Test public void Nowiki() { + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "a" + , " b" + , "c" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
    b"
    +		, "
    " + , "" + , "

    c" + , "

    " + , "" + )); + } + @Test public void Nowiki_tbl() { // EX: de.wikipedia.org/wiki/Hilfe:Vorlagenprogrammierung + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "a" + , " bc" + , "" + , "{|" + , "|-" + , "|d" + , "|}" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
    bc"
    +		, "
    " +// , "" + , "" + , " " + , " " + , " " + , "
    d" + , "
    " + , "" + )); + } + @Test public void Pre_at_end_should_autoclose() { // PURPOSE: as per proc_name + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "a" + , " b" + , "" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
    b"
    +		, "
    " + , "" + )); + } + @Test public void Pre_xtn() { // PURPOSE: (and other xtn) was unnecessarily canceling pre; PAGE:en.w:MD5_Hash + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( " a" + , " b" + , " c" + , "" + , "d" + ), String_.Concat_lines_nl_skip_last + ( "
    a"
    +		, "[1]"
    +		, "c"
    +		, "
    " + , "" + , "

    d" + , "

    " + , "" + )); + } + @Test public void Pre_tbl() { // PURPOSE: pre was being garbled by tables b/c table was ignoring whitespace; DATE:2013-02-11 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|a" + , "|}" + , "" + , "c" + , "" + , " d" + , "" + , "{|" + , "| e" + , "|}" + , "" + , "f" + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + , "

    c" + , "

    " + , "" + , "
    d"
    +		, "
    " + , "" + , " " + , " " + , " " + , "
    e" + , "
    " + , "" + , "

    f" + , "

    " + , "" + )); + } + @Test public void Nowiki_tbl2() { // EX: de.wikipedia.org/wiki/Hilfe:Vorlagenprogrammierung + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|a" + , "|}" + , "" + , "b" + , "" + , " c d" + , "" + , "{|" + , "|e" + , "|}" + ), String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + , "

    b" + , "

    " + , "" + , "
    c d"
    +		, "
    " + , "" + , " " + , " " + , " " + , "
    e" + , "
    " + )); + } + @Test public void Pre_nowiki() { // PURPOSE: nowikis inside pre should be ignored; DATE:2013-03-30 + fxt.Test_parse_page_all_str("\n a<b" , "\n
    a<b\n
    \n"); // basic + } + @Test public void Para_bos_tblw() { + raw = String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|a" + , "|}" + ); + fxt.Test_parse_page_all_str(raw, String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + )); + } + @Test public void Para_bos_tblx() { + raw = String_.Concat_lines_nl_skip_last + ( "" + , "" + , "" + , "" + , "
    a" + , "
    " + ); + fxt.Test_parse_page_all_str(raw, String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + )); + } + @Test public void Pre_tblw_td() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , " b" + , "|" + , "c" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
    b"
    +			, "
    " + , "" + , "

    |" + , "c" + , "

    " + , "" + )); + } + @Test public void Pre_tblw_tr() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , " b" + , "|-" + , "c" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
    b"
    +			, "
    " + , "" + , "

    |-" + , "c" + , "

    " + , "" + )); + } + @Test public void Pre_tblw_te() { + raw = String_.Concat_lines_nl_skip_last + ( "a" + , " b" + , "|}" + , "c" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "
    b"
    +			, "
    " + , "" + , "

    |}" + , "c" + , "

    " + , "" + )); + } + private String File_html() {return File_html("Image", "Test.png", "d/9", "caption");} + public static String File_html(String ns, String ttl, String md5, String caption) { + return String_.Concat_lines_nl_skip_last + ( "
    " + , "
    " + , " \"\"" + , "
    " + , "
    " + caption + "" + , "
    " + , "
    " + , "
    " + ); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_wkr_para_tst.java b/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_wkr_para_tst.java index a27517de8..75e77f065 100644 --- a/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_wkr_para_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_wkr_para_tst.java @@ -13,3 +13,95 @@ 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.paras; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_para_wkr_para_tst { + @Before public void init() {fxt.Reset(); fxt.Init_para_y_();} private final Xop_fxt fxt = new Xop_fxt(); + @After public void teardown() {fxt.Init_para_n_();} + @Test public void Pre_then_xnde_pre() { // PURPOSE: if ws_pre is in effect, xnde_pre should end it; EX: b:Knowing Knoppix/Other applications + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( " a" + , "b
    c"
    +		,	"d
    " + , "e" + ), String_.Concat_lines_nl_skip_last + ( "
    a"
    +		,	"
    " + , "b
    c"
    +		,	"d
    " + , "" + , "

    e" + , "

    " + , "" + )); + } + @Test public void List_ignore_pre_lines() { // PURPOSE: "\s\n" should create new list; was continuing previous list; DATE:2013-07-12 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl + ( ": a" + , ":* b" + , " " + , ": c" + , ":* d" + ) + , String_.Concat_lines_nl_skip_last + ( "
    " + , "
    a" + , "" + , "
      " + , "
    • b" + , "
    • " + , "
    " + , "
    " + , "
    " + , "" + , "
    " + , "
    c" + , "" + , "
      " + , "
    • d" + , "
    • " + , "
    " + , "
    " + , "
    " + , "" + )); + } + @Test public void Multiple_nl_in_tblx() { // PURPOSE: "\n\n\n" was causing multiple breaks; EX:fr.w:Portail:G�nie m�canique; DATE:2014-02-17 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl + ( "" + , "" + , "" + , "" + , "" + , "" + , "
    a" + , "
    " + ) + , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + ) + ); + } + @Test public void Ignore_cr() { // PURPOSE: handle "\r\n"; EX: Special:MovePage; DATE:2014-03-02 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl + ( "a\r" + , "\r" + , "b\r" + ) + , String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "" + , "

    b" + , "

    " + , "" + ) + ); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_wkr_pre_tst.java b/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_wkr_pre_tst.java index a27517de8..ab3266d75 100644 --- a/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_wkr_pre_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/paras/Xop_para_wkr_pre_tst.java @@ -13,3 +13,246 @@ 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.paras; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_para_wkr_pre_tst { + @Before public void init() {fxt.Reset(); fxt.Init_para_y_();} private final Xop_fxt fxt = new Xop_fxt(); + @After public void teardown() {fxt.Init_para_n_();} + @Test public void Pre_ignore_bos() { // PURPOSE: ignore pre at bgn; DATE:2013-07-09 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl + ( " " + , "b" + ), String_.Concat_lines_nl + ( "

    " + , "b" + , "

    " + )); + } + @Test public void Pre_ignore_bos_tblw() { // PURPOSE: ignore pre at bgn shouldn't break tblw; EX:commons.wikimedia.org; DATE:2013-07-11 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl + ( " " + , "{|" + , "|-" + , "|a" + , "|}" + ), String_.Concat_lines_nl + ( "

    " // NOTE:

    added for TRAILING_TBLW fix; DATE:2017-04-08 + , "

    " + , "" + , " " + , " " + , " " + , "
    a" + , "
    " + )); + } + @Test public void Ignore_bos_xnde() { // PURPOSE: space at bgn shouldn't create pre; EX:commons.wikimedia.org; "
    a\n
    "; DATE:2013-11-28 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "
    a" // NOTE: leading " " matches MW; DATE:2014-06-23 + , "
    " + ), String_.Concat_lines_nl_skip_last + ( "
    a" + , "
    " + , "" + )); + } + @Test public void Ignore_pre_in_gallery() {// PURPOSE: pre in gallery should be ignored; EX:uk.w:EP2; DATE:2014-03-11 + gplx.xowa.xtns.gallery.Gallery_mgr_wtr.File_found_mode = Bool_.Y_byte; + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "a" + , "" + , " " + , " File:A.png" + , " " + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , " " + ,"" + )); + gplx.xowa.xtns.gallery.Gallery_mgr_wtr.File_found_mode = Bool_.N_byte; + } + @Test public void Pre_xnde_gallery() { // PURPOSE: should invalidate pre; EX: en.w:Mary, Queen of Scots + gplx.xowa.xtns.gallery.Gallery_mgr_wtr.File_found_mode = Bool_.Y_byte; + fxt.Wiki().Xtn_mgr().Init_by_wiki(fxt.Wiki()); + String raw = String_.Concat_lines_nl_skip_last + ( " " + , "File:A.png|b" + , "" + ); + fxt.Test_parse_page_wiki_str(raw, String_.Concat_lines_nl_skip_last + ( " " + )); + gplx.xowa.xtns.gallery.Gallery_mgr_wtr.File_found_mode = Bool_.N_byte; + } + @Test public void Ignore_pre_in_center() {// PURPOSE: pre in gallery should be ignored; EX:uk.w:EP2; DATE:2014-03-11 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "a" + , "
    b" + , "
    " + , "d" + ), String_.Concat_lines_nl_skip_last + ( "

    a" + , "

    " + , "
    b" + , "
    " + , "" + , "

    d" + , "

    " + ) + ); + } + @Test public void Remove_only_1st_space() { // PURPOSE: pre should only remove 1st space]; EX: w:Wikipedia:WikiProject_History/CategoryExample; DATE:2014-04-14 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( " a" + , " b" + , " c" + ), String_.Concat_lines_nl_skip_last + ( "
       a"
    +		, "   b"
    +		, "   c"
    +		, "
    " + ) + ); + } + @Test public void Remove_only_1st_space__bos() { // PURPOSE: similar to above but check that pre at \n\s is indented correctly; DATE:2014-04-14 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "" + , " a" + , " b" + ), String_.Concat_lines_nl_skip_last + ( "" + , "
       a"
    +		, "   b"
    +		, "
    " + ) + ); + } + @Test public void Ignore_tblw_td() {// PURPOSE: \n\s| should continue pre; EX:w:Wikipedia:WikiProject_History/CategoryExample; DATE:2014-04-14 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( " a" + , " |" + , " b" + ), String_.Concat_lines_nl_skip_last + ( "
    a"
    +		, "|"
    +		, "b"
    +		, "
    " + ) + ); + } + @Test public void Tab() { // PURPOSE: tab inside pre was being converted to space; PAGE:en.w:Cascading_Style_Sheets DATE:2014-06-23 + fxt.Test_html_full_str + ( " \ta" + , String_.Concat_lines_nl + ( "
    \ta"
    +		, "
    " + )); + } + @Test public void Style() { // PURPOSE: " " + ), String_.Concat_lines_nl + ( "
    <style>"
    +		, "</style>"
    +		, "
    " + )); + } + @Test public void Nl_only() { // PURPOSE: wiki_pre with \n only was being dropped; PAGE:en.w:Preferred_number DATE:2014-06-24 + fxt.Test_html_full_str(String_.Concat_lines_nl_skip_last + ( " a" + , " " // was being dropped + , " b" + ), String_.Concat_lines_nl + ( "
    a"
    +		, ""	// make sure it's still there
    +		, "b"
    +		, "
    " + )); + } + @Test public void Nl_w_ws() { // PURPOSE: based on Nl_only; make sure that 1 or more spaces does not add extra \n; PAGE:en.w:Preferred_number DATE:2014-06-24 + fxt.Test_html_full_str(String_.Concat_lines_nl_skip_last + ( " a" + , " " // 2 spaces + , " b" + ), String_.Concat_lines_nl + ( "
    a"
    +		, " "	// 1 space
    +		, "b"
    +		, "
    " + )); + } + @Test public void Nl_many() { // PURPOSE: handle alternating \n\s; PAGE:en.w:Preferred_number DATE:2014-06-24 + fxt.Test_html_full_str(String_.Concat_lines_nl_skip_last + ( " a" + , " " + , " b" + , " " + , " c" + ), String_.Concat_lines_nl + ( "
    a"
    +		, ""
    +		, "b"
    +		, ""
    +		, "c"
    +		, "
    " + )); + } + @Test public void Source() { // PURPOSE: " " in pre has issues; PAGE:en.w:Comment_(computer_programming) DATE:2014-06-23 + fxt.Init_para_y_(); + fxt.Test_html_wiki_str(String_.Concat_lines_nl + ( " " + , " " + , " a" + , " " + , " " + ), String_.Concat_lines_nl + ( "

    " // this is wrong, but will be stripped by tidy + , "

    " + , "
    "
    +		, " a"
    +		, "
    " + , "" + , "


    " // also wrong, but leave for now + , "

    " + )); + } + @Test public void False_match_xnde() { // PURPOSE: "\s" being evaluted as "\s"; PAGE:de.v:Via_Jutlandica/Gpx DATE:2014-11-29 + fxt.Init_para_y_(); + fxt.Test_html_wiki_str(String_.Concat_lines_nl + ( "" + , " " + ), String_.Concat_lines_nl + ( "" + , "
    <trk>"
    +		, "
    " + )); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/paras/Xop_pre_lxr.java b/400_xowa/src/gplx/xowa/parsers/paras/Xop_pre_lxr.java index a27517de8..5c5ce43d3 100644 --- a/400_xowa/src/gplx/xowa/parsers/paras/Xop_pre_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/paras/Xop_pre_lxr.java @@ -13,3 +13,80 @@ 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.paras; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +import gplx.xowa.parsers.lists.*; import gplx.xowa.parsers.tblws.*; import gplx.xowa.parsers.tmpls.*; import gplx.xowa.parsers.miscs.*; +public class Xop_pre_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_pre;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Hook_space, this);} // NOTE: do not treat \n\t as shorthand pre; EX:pl.w:Main_Page; DATE:2014-05-06 + 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) { + if ( !ctx.Para().Enabled() // para disabled; "\n\s" should just be "\n\s"; NOTE: para disabled in + || ( ctx.Stack_len() > 0 // bounds check + && ctx.Stack_get_last().Tkn_tid() == Xop_tkn_itm_.Tid_lnki // last tkn is lnki; EX: [[File:A.png|a\n\sb]]; PAGE:s.w:Virus;DATE:2015-03-31 + ) + ) { + if (bgn_pos != Xop_parser_.Doc_bgn_bos) // don't add \n if BOS; EX: " a" should be " ", not "\n " + ctx.Subs_add(root, tkn_mkr.NewLine(bgn_pos, bgn_pos + 1, Xop_nl_tkn.Tid_char, 1)); + ctx.Subs_add(root, tkn_mkr.Space(root, cur_pos - 1, cur_pos)); + return cur_pos; + } + int txt_pos = Bry_find_.Find_fwd_while(src, cur_pos, src_len, Byte_ascii.Space); // NOTE: was Find_fwd_while_tab_or_space, which incorrectly converted tabs to spaces; PAGE:en.w:Cascading_Style_Sheets; DATE:2014-06-23 + if (txt_pos == src_len) return cur_pos; // "\n\s" at EOS; treat as \n only; EX: "a\n " -> ""; also bounds check + byte b = src[txt_pos]; + if (bgn_pos == Xop_parser_.Doc_bgn_bos) { // BOS; gobble up all \s\t; EX: "BOS\s\s\sa" -> "BOSa" + if (b == Byte_ascii.Nl) { // next char is nl + cur_pos = txt_pos; // position at nl; NOTE: do not position after nl, else may break hdr, tblw, list, etc; EX: "\s\n{|" needs to preserve "\n" for tblw + ctx.Subs_add(root, tkn_mkr.Ignore(bgn_pos, cur_pos, Xop_ignore_tkn.Ignore_tid_pre_at_bos)); + return cur_pos; // ignore pre if blank line at bos; EX: "BOS\s\s\n" -> "BOS\n" + } + if (b == Byte_ascii.Lt) // next char is <; possible xnde; flag so that xnde can escape; DATE:2013-11-28; moved outside Doc_bgn_bos block above; PAGE:en.w:Comment_(computer_programming); DATE:2014-06-23 + ctx.Xnde().Pre_at_bos_(true); + } + switch (ctx.Cur_tkn_tid()) { + case Xop_tkn_itm_.Tid_tblw_tb: // close tblw attrs; NOTE: after BOS (since no tblw at BOS) but before "\n !" check + case Xop_tkn_itm_.Tid_tblw_tr: case Xop_tkn_itm_.Tid_tblw_th: + Xop_tblw_wkr.Atrs_close(ctx, src, root, Bool_.N); + break; + case Xop_tkn_itm_.Tid_list: // close all lists unless [[Category]]; SEE:NOTE_4; rewritten; DATE:2015-03-31 + boolean close_all_lists = true; + if (Bry_find_.Find_fwd(src, Xop_tkn_.Lnki_bgn, txt_pos, src_len) == txt_pos) { // look for "[[" + int tmp_pos = txt_pos + Xop_tkn_.Lnki_bgn.length; + if (Bry_find_.Find_fwd(src, ctx.Wiki().Ns_mgr().Ns_category().Name_db_w_colon(), tmp_pos, src_len) == tmp_pos) // look for "Category:" + close_all_lists = false; // "[[Category:" found; "\n\s[[Category:" should not close list; note that [[Category]] is invisible + } + if (close_all_lists) + Xop_list_wkr_.Close_list_if_present(ctx, root, src, bgn_pos, cur_pos); + break; + } + switch (b) { // handle "\n !" which can be tbl + case Byte_ascii.Bang: + switch (ctx.Cur_tkn_tid()) { + case Xop_tkn_itm_.Tid_tblw_tb: case Xop_tkn_itm_.Tid_tblw_tc: case Xop_tkn_itm_.Tid_tblw_tr: + case Xop_tkn_itm_.Tid_tblw_th: case Xop_tkn_itm_.Tid_tblw_td: case Xop_tkn_itm_.Tid_tblw_te: + int new_cur_pos = txt_pos + 1; // +1 to skip Byte_ascii.Bang + Xop_tblw_lxr_ws.Make(ctx, tkn_mkr, root, src, src_len, bgn_pos, new_cur_pos, Xop_tblw_wkr.Tblw_type_th, true); + return new_cur_pos; + } + break; + } + return ctx.Para().Process_pre(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, txt_pos); + } + public static final Xop_pre_lxr Instance = new Xop_pre_lxr(); Xop_pre_lxr() {} + private static final byte[] Hook_space = new byte[] {Byte_ascii.Nl, Byte_ascii.Space}; +} +/* +NOTE_4: Close_all_lists_unless_category; PAGE:en.w:SHA-2 +PURPOSE: \n should ordinarily close list. However, if \n[[Category:A]], then don't close list since [[Category:A]] will trim preceding \n +REASON: occurs b/c MW does separate passes for list and Category while XO does one pass. +EX: closes *a list +*a + +*b + +EX: does not close +*a +[[Category:A]] +*b +*/ \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/paras/Xop_pre_tkn.java b/400_xowa/src/gplx/xowa/parsers/paras/Xop_pre_tkn.java index a27517de8..c43f836c9 100644 --- a/400_xowa/src/gplx/xowa/parsers/paras/Xop_pre_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/paras/Xop_pre_tkn.java @@ -13,3 +13,21 @@ 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.paras; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.langs.htmls.*; import gplx.xowa.htmls.core.htmls.*; +public class Xop_pre_tkn extends Xop_tkn_itm_base { + public Xop_pre_tkn(int bgn, int end, byte pre_tid, Xop_tkn_itm pre_bgn_tkn) { + this.Tkn_ini_pos(false, bgn, end); + this.pre_tid = pre_tid; + } + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_pre;} + public byte Pre_tid() {return pre_tid;} private byte pre_tid = Pre_tid_null; + @Override public void Html__write(Bry_bfr bfr, Xoh_html_wtr wtr, Xowe_wiki wiki, Xoae_page page, Xop_ctx ctx, Xoh_wtr_ctx hctx, Xoh_html_wtr_cfg cfg, Xop_tkn_grp grp, int sub_idx, byte[] src) { + switch (pre_tid) { + case Xop_pre_tkn.Pre_tid_bgn: bfr.Add(Gfh_tag_.Pre_lhs); break; // '
    '
    +			case Xop_pre_tkn.Pre_tid_end: bfr.Add(Bry__pre__rhs); break;			// '\n
    \n\n' + } + } + public static final byte Pre_tid_null = 0, Pre_tid_bgn = 1, Pre_tid_end = 2; + private static final byte[] Bry__pre__rhs = Bry_.new_a7("\n
    \n\n"); +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_log.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_log.java index a27517de8..76d227f89 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_log.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_log.java @@ -13,3 +13,18 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.log_msgs.*; +public class Xop_tblw_log { + private static final Gfo_msg_grp owner = Gfo_msg_grp_.new_(Xoa_app_.Nde, "tblw"); + public static final Gfo_msg_itm + Dangling = Gfo_msg_itm_.new_warn_(owner, "dangling_tblw") + , Elem_without_tbl = Gfo_msg_itm_.new_warn_(owner, "elem_without_tbl") +// , Row_trailing = Gfo_msg_itm_.new_warn_(owner, "Row_trailing") + , Caption_after_tr = Gfo_msg_itm_.new_warn_(owner, "caption_after_tr") + , Caption_after_td = Gfo_msg_itm_.new_warn_(owner, "caption_after_td") + , Caption_after_tc = Gfo_msg_itm_.new_warn_(owner, "caption_after_tc") + , Hdr_after_cell = Gfo_msg_itm_.new_warn_(owner, "hdr_after_cell") + , Tbl_empty = Gfo_msg_itm_.new_warn_(owner, "tbl_empty") + ; +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_lxr.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_lxr.java index a27517de8..e153d4b51 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_lxr.java @@ -13,3 +13,123 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +import gplx.xowa.parsers.paras.*; import gplx.xowa.parsers.lnkis.*; import gplx.xowa.parsers.miscs.*; +public class Xop_tblw_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_tblw;} + 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) { + int rv = Handle_bang(wlxr_type, ctx, ctx.Tkn_mkr(), root, src, src_len, bgn_pos, cur_pos); + if (rv != Continue) return rv; + rv = Handle_lnki(wlxr_type, ctx, ctx.Tkn_mkr(), root, src, src_len, bgn_pos, cur_pos); + if (rv != Continue) return rv; + return ctx.Tblw().Make_tkn_bgn(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, false, wlxr_type, Xop_tblw_wkr.Called_from_general, -1, -1); + } + public static final int Continue = -2; // -2 b/c -1 used by Called_from_pre + public static int Handle_bang(int wlxr_type, Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos) { + // standalone "!" should be ignored if no tblw present; EX: "a b! c" should not trigger ! for header + switch (wlxr_type) { + case Xop_tblw_wkr.Tblw_type_th: // \n! + case Xop_tblw_wkr.Tblw_type_th2: // !! + case Xop_tblw_wkr.Tblw_type_td: // \n| + Xop_tkn_itm owner_tblw_tb = ctx.Stack_get_typ(Xop_tkn_itm_.Tid_tblw_tb); // check entire stack for tblw; DATE:2014-03-11 + if ( owner_tblw_tb == null // no tblw in stack; highly probably that current sequence is not tblw tkn + || ctx.Cur_tkn_tid() == Xop_tkn_itm_.Tid_lnki // cur tid is lnki; PAGE:en.w:Pink_(singer); DATE:2014-06-25 + ) { + int lnki_pos = ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_lnki); + if (lnki_pos != Xop_ctx.Stack_not_found && wlxr_type == Xop_tblw_wkr.Tblw_type_td) {// lnki present;// NOTE: added Xop_tblw_wkr.Tblw_type_td b/c th should not apply when tkn_mkr.Pipe() is called below; DATE:2013-04-24 + Xop_tkn_itm lnki_tkn = ctx.Stack_pop_til(root, src, lnki_pos, false, bgn_pos, cur_pos, Xop_tkn_itm_.Tid_tblw_td); // pop any intervening nodes until lnki + ctx.Stack_add(lnki_tkn); // push lnki back onto stack; TODO_OLD: combine these 2 lines into 1 + // NOTE: this is a "\n|" inside a [[ ]]; must create two tokens for lnki to build correctly; + ctx.Subs_add(root, tkn_mkr.NewLine(bgn_pos, bgn_pos + 1, Xop_nl_tkn.Tid_char, 1)); + return Xop_pipe_lxr.Instance.Make_tkn(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos); // NOTE: need to call pipe_lxr in order to invalidate if lnki; DATE:2014-06-06 + } + else { // \n| or \n! but no tbl + if ( bgn_pos != Xop_parser_.Doc_bgn_bos // avoid ! at BOS + && src[bgn_pos] == Byte_ascii.Nl) // handle "!" etc. + return Xop_tblw_wkr.Handle_false_tblw_match(ctx, root, src, bgn_pos, cur_pos, tkn_mkr.Txt(bgn_pos + 1, cur_pos), true); // +1 to ignore \n of "\n!", "\n!!", "\n|"; DATE:2014-02-19 + else // handle "!!" only + return ctx.Lxr_make_txt_(cur_pos); + } + } + if (wlxr_type == Xop_tblw_wkr.Tblw_type_th2) { // !!; extra check to make sure \n! exists; DATE:2014-10-19 + int prv_th_pos = Bry_find_.Find_bwd(src, Byte_ascii.Nl, bgn_pos); // search for previous \n + boolean invalid = prv_th_pos == Bry_find_.Not_found; // no \n; invalid + if (!invalid) { + ++prv_th_pos; // skip \n + prv_th_pos = Bry_find_.Find_fwd_while_space_or_tab(src, prv_th_pos, src_len); // skip \s; needed for "\n\s!" which is still a tblw + if (prv_th_pos == bgn_pos) // invalid: "\n" is directly in front of "!!" + invalid = true; + else + invalid = src[prv_th_pos] != Byte_ascii.Bang; // invalid if not "\n!" + } + if (invalid) + return Xop_tblw_wkr.Handle_false_tblw_match(ctx, root, src, bgn_pos, cur_pos, tkn_mkr.Txt(bgn_pos, cur_pos), false); + } + break; + } + return Continue; + } + public static int Handle_lnki(int wlxr_type, Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos) { + Xop_tkn_itm last_tkn = ctx.Stack_get_last(); + if ( last_tkn != null + && last_tkn.Tkn_tid() == Xop_tkn_itm_.Tid_lnki) { + Xop_lnki_tkn lnki = (Xop_lnki_tkn)last_tkn; + if ( lnki.Pipe_count_is_zero()) { // 1st pipe; EX: [[A\n|+B]] + boolean invalidate = false; + switch (wlxr_type) { // tblw found; check if in lnki and validate ttl; DATE:2014-03-29 + case Xop_tblw_wkr.Tblw_type_tb: // \n{| + case Xop_tblw_wkr.Tblw_type_tc: // \n|+ + case Xop_tblw_wkr.Tblw_type_tr: // \n|- + case Xop_tblw_wkr.Tblw_type_te: // \n|} + invalidate = true; // always invalidate + break; + case Xop_tblw_wkr.Tblw_type_td2: // ||; EX: [[A||B]] + if (ctx.Tid_is_image_map()) { // if in ImageMap, then treat "||" as "pipe" (not "pipe_text"); note that outer tbl is ignored; EX:w:United_States_presidential_election,_1992 + ctx.Subs_add(root, tkn_mkr.Pipe(bgn_pos, cur_pos)); + return cur_pos; + } + invalidate = !Xop_lnki_wkr_.Parse_ttl(ctx, src, lnki, bgn_pos); // check if invalid; EX: "[[A<||]]" would be invalid b/c of < + if (!invalidate) { // "valid" title, but "||" must be converted to pipe inside lnki; EX:cs.w:Main_Page; DATE:2014-05-09 + ctx.Subs_add(root, tkn_mkr.Pipe(bgn_pos, cur_pos)); // NOTE: technically need to check if pipe or pipe_text; for now, do pipe as pipe_text could break [[File:A.png||20px]]; DATE:2014-05-06 + return cur_pos; + } + break; + } + if (invalidate) { + ctx.Stack_pop_last(); + return Xop_lnki_wkr_.Invalidate_lnki(ctx, src, root, lnki, bgn_pos); + } + } + else { // nth pipe; no need to check for invalidate + switch (wlxr_type) { + case Xop_tblw_wkr.Tblw_type_td2: // || + ctx.Subs_add(root, tkn_mkr.Pipe(bgn_pos, cur_pos)); + return cur_pos; + case Xop_tblw_wkr.Tblw_type_th2: // !! + case Xop_tblw_wkr.Tblw_type_th: // ! + ctx.Subs_add(root, tkn_mkr.Txt(bgn_pos, cur_pos)); // NOTE: cur_pos should handle ! and !! + return cur_pos; + } + } + } + return Continue; + } + public Xop_tblw_lxr(byte wlxr_type) {this.wlxr_type = wlxr_type;} private byte wlxr_type; + public static final Xop_tblw_lxr Instance = new Xop_tblw_lxr(); Xop_tblw_lxr() {} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) { + core_trie.Add(Hook_tb, new Xop_tblw_lxr(Xop_tblw_wkr.Tblw_type_tb)); + core_trie.Add(Hook_te, new Xop_tblw_lxr(Xop_tblw_wkr.Tblw_type_te)); + core_trie.Add(Hook_tr, new Xop_tblw_lxr(Xop_tblw_wkr.Tblw_type_tr)); + core_trie.Add(Hook_td, new Xop_tblw_lxr(Xop_tblw_wkr.Tblw_type_td)); + core_trie.Add(Hook_th, new Xop_tblw_lxr(Xop_tblw_wkr.Tblw_type_th)); + core_trie.Add(Hook_tc, new Xop_tblw_lxr(Xop_tblw_wkr.Tblw_type_tc)); + core_trie.Add(Hook_td2, new Xop_tblw_lxr(Xop_tblw_wkr.Tblw_type_td2)); + core_trie.Add(Hook_th2, new Xop_tblw_lxr(Xop_tblw_wkr.Tblw_type_th2)); + } + public void Init_by_lang(Xol_lang_itm lang, Btrie_fast_mgr core_trie) {} + public void Term(Btrie_fast_mgr core_trie) {} + public static final byte[] Hook_tb = Bry_.new_a7("\n{|"), Hook_te = Bry_.new_a7("\n|}"), Hook_tr = Bry_.new_a7("\n|-") + , Hook_td = Bry_.new_a7("\n|"), Hook_th = Bry_.new_a7("\n!"), Hook_tc = Bry_.new_a7("\n|+") + , Hook_td2 = Bry_.new_a7("||"), Hook_th2 = Bry_.new_a7("!!"); +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_lxr_ws.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_lxr_ws.java index a27517de8..f3dbee0ce 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_lxr_ws.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_lxr_ws.java @@ -13,3 +13,52 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_tblw_lxr_ws { + public static int Make(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, byte wlxr_type, boolean called_from_pre) { + int rv = Xop_tblw_lxr.Handle_bang(wlxr_type, ctx, ctx.Tkn_mkr(), root, src, src_len, bgn_pos, cur_pos); + if (rv != Xop_tblw_lxr.Continue) return rv; + rv = Xop_tblw_lxr.Handle_lnki(wlxr_type, ctx, ctx.Tkn_mkr(), root, src, src_len, bgn_pos, cur_pos); + if (rv != Xop_tblw_lxr.Continue) return rv; + if (!called_from_pre) { // skip if called from pre, else will return text, since pre_lxr has not created \n tkn yet; EX: "\n ! a"; DATE:2014-02-14 + // find first non-ws tkn; check if nl or para + int root_subs_len = root.Subs_len(); + int tkn_idx = root_subs_len - 1; + boolean loop = true, nl_found = false; + while (loop) { + if (tkn_idx < 0) break; + Xop_tkn_itm tkn = root.Subs_get(tkn_idx); + switch (tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_space: case Xop_tkn_itm_.Tid_tab: // ws: keep moving backwards + tkn_idx--; + break; + case Xop_tkn_itm_.Tid_newLine: + case Xop_tkn_itm_.Tid_para: + loop = false; + nl_found = true; + break; + default: + loop = false; + break; + } + } + if (tkn_idx == -1) { // bos reached; all tkns are ws; + if (wlxr_type == Xop_tblw_wkr.Tblw_type_tb) { // wlxr_type is {|; + root.Subs_del_after(0); // trim + return ctx.Tblw().Make_tkn_bgn(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, false, wlxr_type, Xop_tblw_wkr.Called_from_general, -1, -1); // process {| + } + else // wlxr_type is something else, but invalid since no containing {| + return ctx.Lxr_make_txt_(cur_pos); + } + + if (!nl_found && wlxr_type == Xop_tblw_wkr.Tblw_type_td) // | but no nl; return control to pipe_lxr for further processing + return Tblw_ws_cell_pipe; + if (nl_found) + root.Subs_del_after(tkn_idx); + } + return ctx.Tblw().Make_tkn_bgn(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, false, wlxr_type, Xop_tblw_wkr.Called_from_general, -1, -1); + } + public static final byte[] Hook_tb = Bry_.new_a7("{|"), Hook_te = Bry_.new_a7("|}"), Hook_tr = Bry_.new_a7("|-") + , Hook_th = Bry_.new_a7("!"), Hook_tc = Bry_.new_a7("|+"); + public static final int Tblw_ws_cell_pipe = -1; +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tb_tkn.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tb_tkn.java index a27517de8..56fa0e9a1 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tb_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tb_tkn.java @@ -13,3 +13,24 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.htmls.*; +public class Xop_tblw_tb_tkn extends Xop_tkn_itm_base implements Xop_tblw_tkn { + public Xop_tblw_tb_tkn(int bgn, int end, boolean tblw_xml, boolean auto_created) { + this.tblw_xml = tblw_xml; this.Tkn_ini_pos(false, bgn, end); + if (auto_created) // auto-created should be marked as having no attributes, else text may get gobbled up incorrectly; EX:Paris#Demographics DATE:2014-03-18 + atrs_bgn = atrs_end = bgn; + } + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_tblw_tb;} + public int Tblw_tid() {return Xop_xnde_tag_.Tid__table;} + public int Atrs_bgn() {return atrs_bgn;} private int atrs_bgn = Xop_tblw_wkr.Atrs_null; + public int Atrs_end() {return atrs_end;} private int atrs_end = -1; + public void Atrs_rng_set(int bgn, int end) {this.atrs_bgn = bgn; this.atrs_end = end;} + public Mwh_atr_itm[] Atrs_ary() {return atrs_ary;} public Xop_tblw_tkn Atrs_ary_as_tblw_(Mwh_atr_itm[] v) {atrs_ary = v; return this;} private Mwh_atr_itm[] atrs_ary; + public boolean Tblw_xml() {return tblw_xml;} private boolean tblw_xml; + public void Tblw_xml_(boolean v) {tblw_xml = v;} + public int Tblw_subs_len() {return tblw_subs_len;} public void Tblw_subs_len_add_() {++tblw_subs_len;} private int tblw_subs_len; + public int Caption_count() {return caption_count;} public Xop_tblw_tb_tkn Caption_count_(int v) {caption_count = v; return this;} private int caption_count = 0; + public Xop_tblw_tb_tkn Caption_count_add_1() {++caption_count; return this;} + public Xop_tblw_tb_tkn Subs_add_ary(Xop_tkn_itm... ary) {for (Xop_tkn_itm itm : ary) super.Subs_add(itm); return this;} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tb_tkn_chkr.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tb_tkn_chkr.java index a27517de8..51a1bccfa 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tb_tkn_chkr.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tb_tkn_chkr.java @@ -13,3 +13,18 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xop_tblw_tb_tkn_chkr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Xop_tblw_tb_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_tblw_tb;} + public int Caption_count() {return caption_count;} public Xop_tblw_tb_tkn_chkr Caption_count_(int v) {caption_count = v; return this;} private int caption_count = Int_.Neg1; + public Xop_tblw_tb_tkn_chkr Atrs_rng_(int bgn, int end) {this.atrs_bgn = bgn; this.atrs_end = end; return this;} private int atrs_bgn = Xop_tblw_wkr.Atrs_null, atrs_end = Xop_tblw_wkr.Atrs_null; + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { + Xop_tblw_tb_tkn actl = (Xop_tblw_tb_tkn)actl_obj; + err += mgr.Tst_val(caption_count == Int_.Neg1, path, "caption_count", caption_count, actl.Caption_count()); + err += mgr.Tst_val(atrs_bgn == Xop_tblw_wkr.Atrs_null, path, "atrs_bgn", atrs_bgn, actl.Atrs_bgn()); + err += mgr.Tst_val(atrs_end == Xop_tblw_wkr.Atrs_null, path, "atrs_end", atrs_end, actl.Atrs_end()); + return err; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tc_tkn.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tc_tkn.java index a27517de8..8b71d43a2 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tc_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tc_tkn.java @@ -13,3 +13,17 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.htmls.*; +public class Xop_tblw_tc_tkn extends Xop_tkn_itm_base implements Xop_tblw_tkn { + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_tblw_tc;} + public int Tblw_tid() {return Xop_xnde_tag_.Tid__caption;} + public int Atrs_bgn() {return atrs_bgn;} private int atrs_bgn = Xop_tblw_wkr.Atrs_null; + public int Atrs_end() {return atrs_end;} private int atrs_end = -1; + public void Atrs_rng_set(int bgn, int end) {this.atrs_bgn = bgn; this.atrs_end = end;} + public Mwh_atr_itm[] Atrs_ary() {return atrs_ary;} public Xop_tblw_tkn Atrs_ary_as_tblw_(Mwh_atr_itm[] v) {atrs_ary = v; return this;} private Mwh_atr_itm[] atrs_ary; + public boolean Tblw_xml() {return tblw_xml;} private boolean tblw_xml; + public int Tblw_subs_len() {return tblw_subs_len;} public void Tblw_subs_len_add_() {++tblw_subs_len;} private int tblw_subs_len; + public Xop_tblw_tc_tkn Subs_add_ary(Xop_tkn_itm... ary) {for (Xop_tkn_itm itm : ary) super.Subs_add(itm); return this;} + public Xop_tblw_tc_tkn(int bgn, int end, boolean tblw_xml) {this.tblw_xml = tblw_xml; this.Tkn_ini_pos(false, bgn, end);} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tc_tkn_chkr.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tc_tkn_chkr.java index a27517de8..049fceb03 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tc_tkn_chkr.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tc_tkn_chkr.java @@ -13,3 +13,13 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xop_tblw_tc_tkn_chkr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Xop_tblw_tc_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_tblw_tc;} + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { +// Xop_tblw_tc_tkn actl = (Xop_tblw_tc_tkn)actl_obj; + return err; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_td_tkn.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_td_tkn.java index a27517de8..9f3fdd7d7 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_td_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_td_tkn.java @@ -13,3 +13,17 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.htmls.*; +public class Xop_tblw_td_tkn extends Xop_tkn_itm_base implements Xop_tblw_tkn { + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_tblw_td;} + public int Tblw_tid() {return Xop_xnde_tag_.Tid__td;} + public int Atrs_bgn() {return atrs_bgn;} private int atrs_bgn = Xop_tblw_wkr.Atrs_null; + public int Atrs_end() {return atrs_end;} private int atrs_end = -1; + public void Atrs_rng_set(int bgn, int end) {this.atrs_bgn = bgn; this.atrs_end = end;} + public Mwh_atr_itm[] Atrs_ary() {return atrs_ary;} public Xop_tblw_tkn Atrs_ary_as_tblw_(Mwh_atr_itm[] v) {atrs_ary = v; return this;} private Mwh_atr_itm[] atrs_ary; + public boolean Tblw_xml() {return tblw_xml;} private boolean tblw_xml; + public int Tblw_subs_len() {return tblw_subs_len;} public void Tblw_subs_len_add_() {++tblw_subs_len;} private int tblw_subs_len; + public Xop_tblw_td_tkn Subs_add_ary(Xop_tkn_itm... ary) {for (Xop_tkn_itm itm : ary) super.Subs_add(itm); return this;} + public Xop_tblw_td_tkn(int bgn, int end, boolean tblw_xml) {this.tblw_xml = tblw_xml; this.Tkn_ini_pos(false, bgn, end);} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_td_tkn_chkr.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_td_tkn_chkr.java index a27517de8..f6209db06 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_td_tkn_chkr.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_td_tkn_chkr.java @@ -13,3 +13,16 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xop_tblw_td_tkn_chkr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Xop_tblw_td_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_tblw_td;} + public Xop_tblw_td_tkn_chkr Atrs_rng_(int bgn, int end) {this.atrs_bgn = bgn; this.atrs_end = end; return this;} private int atrs_bgn = Xop_tblw_wkr.Atrs_null, atrs_end = Xop_tblw_wkr.Atrs_null; + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { + Xop_tblw_td_tkn actl = (Xop_tblw_td_tkn)actl_obj; + err += mgr.Tst_val(atrs_bgn == Xop_tblw_wkr.Atrs_null, path, "atrs_bgn", atrs_bgn, actl.Atrs_bgn()); + err += mgr.Tst_val(atrs_end == Xop_tblw_wkr.Atrs_null, path, "atrs_end", atrs_end, actl.Atrs_end()); + return err; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_th_tkn.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_th_tkn.java index a27517de8..7f26c2dc3 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_th_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_th_tkn.java @@ -13,3 +13,17 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.htmls.*; +public class Xop_tblw_th_tkn extends Xop_tkn_itm_base implements Xop_tblw_tkn { + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_tblw_th;} + public int Tblw_tid() {return Xop_xnde_tag_.Tid__th;} + public int Atrs_bgn() {return atrs_bgn;} private int atrs_bgn = Xop_tblw_wkr.Atrs_null; + public int Atrs_end() {return atrs_end;} private int atrs_end = -1; + public void Atrs_rng_set(int bgn, int end) {this.atrs_bgn = bgn; this.atrs_end = end;} + public Mwh_atr_itm[] Atrs_ary() {return atrs_ary;} public Xop_tblw_tkn Atrs_ary_as_tblw_(Mwh_atr_itm[] v) {atrs_ary = v; return this;} private Mwh_atr_itm[] atrs_ary; + public boolean Tblw_xml() {return tblw_xml;} private boolean tblw_xml; + public int Tblw_subs_len() {return tblw_subs_len;} public void Tblw_subs_len_add_() {++tblw_subs_len;} private int tblw_subs_len; + public Xop_tblw_th_tkn Subs_add_ary(Xop_tkn_itm... ary) {for (Xop_tkn_itm itm : ary) super.Subs_add(itm); return this;} + public Xop_tblw_th_tkn(int bgn, int end, boolean tblw_xml) {this.tblw_xml = tblw_xml; this.Tkn_ini_pos(false, bgn, end);} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_th_tkn_chkr.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_th_tkn_chkr.java index a27517de8..f69c4a311 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_th_tkn_chkr.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_th_tkn_chkr.java @@ -13,3 +13,16 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xop_tblw_th_tkn_chkr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Xop_tblw_th_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_tblw_th;} + public Xop_tblw_th_tkn_chkr Atrs_rng_(int bgn, int end) {this.atrs_bgn = bgn; this.atrs_end = end; return this;} private int atrs_bgn = Xop_tblw_wkr.Atrs_null, atrs_end = Xop_tblw_wkr.Atrs_null; + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { + Xop_tblw_th_tkn actl = (Xop_tblw_th_tkn)actl_obj; + err += mgr.Tst_val(atrs_bgn == -1, path, "atrs_bgn", atrs_bgn, actl.Atrs_bgn()); + err += mgr.Tst_val(atrs_end == -1, path, "atrs_end", atrs_end, actl.Atrs_end()); + return err; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tkn.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tkn.java index a27517de8..340de5a31 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tkn.java @@ -13,3 +13,14 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.htmls.*; +public interface Xop_tblw_tkn extends Xop_tkn_itm { + int Tblw_tid(); + boolean Tblw_xml(); + int Tblw_subs_len(); void Tblw_subs_len_add_(); + int Atrs_bgn(); + int Atrs_end(); + void Atrs_rng_set(int bgn, int end); + Mwh_atr_itm[] Atrs_ary(); Xop_tblw_tkn Atrs_ary_as_tblw_(Mwh_atr_itm[] v); +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tr_tkn.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tr_tkn.java index a27517de8..fedf0c47d 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tr_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tr_tkn.java @@ -13,3 +13,21 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.htmls.*; +public class Xop_tblw_tr_tkn extends Xop_tkn_itm_base implements Xop_tblw_tkn { + public Xop_tblw_tr_tkn(int bgn, int end, boolean tblw_xml, boolean auto_created) { + this.tblw_xml = tblw_xml; this.Tkn_ini_pos(false, bgn, end); + if (auto_created) // auto-created should be marked as having no attributes, else text may get gobbled up incorrectly; EX:Paris#Demographics DATE:2014-03-18 + atrs_bgn = atrs_end = bgn; + } + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_tblw_tr;} + public int Tblw_tid() {return Xop_xnde_tag_.Tid__tr;} + public int Atrs_bgn() {return atrs_bgn;} private int atrs_bgn = Xop_tblw_wkr.Atrs_null; + public int Atrs_end() {return atrs_end;} private int atrs_end = -1; + public void Atrs_rng_set(int bgn, int end) {this.atrs_bgn = bgn; this.atrs_end = end;} + public Mwh_atr_itm[] Atrs_ary() {return atrs_ary;} public Xop_tblw_tkn Atrs_ary_as_tblw_(Mwh_atr_itm[] v) {atrs_ary = v; return this;} private Mwh_atr_itm[] atrs_ary; + public boolean Tblw_xml() {return tblw_xml;} private boolean tblw_xml; + public int Tblw_subs_len() {return tblw_subs_len;} public void Tblw_subs_len_add_() {++tblw_subs_len;} private int tblw_subs_len; + public Xop_tblw_tr_tkn Subs_add_ary(Xop_tkn_itm... ary) {for (Xop_tkn_itm itm : ary) super.Subs_add(itm); return this;} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tr_tkn_chkr.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tr_tkn_chkr.java index a27517de8..a881272da 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tr_tkn_chkr.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_tr_tkn_chkr.java @@ -13,3 +13,16 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xop_tblw_tr_tkn_chkr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Xop_tblw_tr_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_tblw_tr;} + public Xop_tblw_tr_tkn_chkr Atrs_rng_(int bgn, int end) {this.atrs_bgn = bgn; this.atrs_end = end; return this;} private int atrs_bgn = Xop_tblw_wkr.Atrs_null, atrs_end = Xop_tblw_wkr.Atrs_null; + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { + Xop_tblw_tr_tkn actl = (Xop_tblw_tr_tkn)actl_obj; + err += mgr.Tst_val(atrs_bgn == -1, path, "atrs_bgn", atrs_bgn, actl.Atrs_bgn()); + err += mgr.Tst_val(atrs_end == -1, path, "atrs_end", atrs_end, actl.Atrs_end()); + return err; + } +} 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 a27517de8..a9811a09e 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 @@ -13,3 +13,562 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.parsers.lists.*; import gplx.xowa.parsers.paras.*; import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.htmls.*; import gplx.xowa.parsers.miscs.*; +public class Xop_tblw_wkr implements Xop_ctx_wkr { + private int tblw_te_ignore_count = 0; + public boolean Cell_pipe_seen() {return cell_pipe_seen;} public Xop_tblw_wkr Cell_pipe_seen_(boolean v) {cell_pipe_seen = v; return this;} private boolean cell_pipe_seen; // status of 1st cell pipe; EX: \n| a | b | c || -> flag pipe between a and b but ignore b and c + public void Ctor_ctx(Xop_ctx ctx) {} + public void Page_bgn(Xop_ctx ctx, Xop_root_tkn root) {cell_pipe_seen = false; tblw_te_ignore_count = 0;} + public void Page_end(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int src_len) {} + public void AutoClose(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, Xop_tkn_itm tkn) { + tkn.Subs_move(root); + tkn.Src_end_(cur_pos); + } + public static final byte Called_from_general = 0, Called_from_list = 1, Called_from_pre = 2; + public int Make_tkn_bgn(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, boolean tbl_is_xml, byte wlxr_type, byte called_from, int atrs_bgn, int atrs_end) {// REF.MW: Parser|doTableStuff + if (bgn_pos == Xop_parser_.Doc_bgn_bos) { + bgn_pos = 0; // do not allow -1 pos + } + + int list_tkn_idx = ctx.Stack_idx_find_but_stop_at_tbl(Xop_tkn_itm_.Tid_list); + if ( list_tkn_idx != -1 // list is in effect; DATE:2014-05-05 + && !tbl_is_xml // tbl is wiki-syntax; ie: auto-close if "{|" but do not close if ""; DATE:2014-02-05 + && called_from != Called_from_list // do not close if called from list; EX: consider "{|"; "* a {|" is called from list_wkr, and should not close; "* a\n{|" is called from tblw_lxr and should close; DATE:2014-02-14 + ) { + if (wlxr_type == Tblw_type_td2) { // if in list, treat "||" as lnki, not tblw; EX: es.d:casa; es.d:tres; DATE:2014-02-15 + ctx.Subs_add(root, ctx.Tkn_mkr().Pipe(bgn_pos, cur_pos)); // NOTE: technically need to check if pipe or pipe_text; for now, do pipe as pipe_text could break [[File:A.png||20px]]; DATE:2014-05-06 + return cur_pos; + } + else { + Xop_list_wkr_.Close_list_if_present(ctx, root, src, bgn_pos, cur_pos); + } + } + if (ctx.Apos().Stack_len() > 0) // open apos; note that apos keeps its own stack, as they are not "structural" (not sure about this) + ctx.Apos().End_frame(ctx, root, src, cur_pos, true);// close it + Xop_tblw_tkn prv_tkn = ctx.Stack_get_tbl(); + if ( prv_tkn == null // prv_tkn not found; i.e.: no earlier "{|" or "
    " + || ( ctx.Stack_get_tblw_tb() == null // no {| on stack; DATE:2014-05-05 + && !tbl_is_xml // and cur is tblw (i.e.: not xnde); DATE:2014-05-05 + ) + ) { + switch (wlxr_type) { + case Tblw_type_tb: { // "{|"; + // close para when table starts; needed for TRAILING_TBLW fix; PAGE:en.w:Template_engine_(web) DATE:2017-04-08 + ctx.Para().Process_block__bgn__nl_w_symbol(ctx, root, src, bgn_pos, cur_pos - 1, Xop_xnde_tag_.Tag__table); // -1 b/c cur_pos includes sym_byte; EX: \n{ + break; // noop; by definition "{|" does not need to have a previous "{|" + } + case Tblw_type_td: // "|" + case Tblw_type_td2: // "||" + if (tbl_is_xml) { // " + , "|}" + ) , String_.Concat_lines_nl_skip_last + ( "
    should automatically add + ctx.Subs_add_and_stack_tblw(root, prv_tkn, tkn_mkr.Tblw_tb(bgn_pos, bgn_pos, tbl_is_xml, true)); + prv_tkn = tkn_mkr.Tblw_tr(bgn_pos, bgn_pos, tbl_is_xml, true); + ctx.Subs_add_and_stack_tblw(root, prv_tkn, prv_tkn); + break; + } + else { + if (called_from == Called_from_pre) + return -1; + else { // DATE:2014-02-19; NOTE: do not add nl if ||; DATE:2014-04-14 + if (wlxr_type == Tblw_type_td) { // "\n|" + ctx.Subs_add(root, ctx.Tkn_mkr().NewLine(bgn_pos, bgn_pos + 1, Xop_nl_tkn.Tid_char, 1)); + ctx.Subs_add(root, ctx.Tkn_mkr().Pipe(bgn_pos + 1, cur_pos)); + } + else // "||" + ctx.Subs_add(root, ctx.Tkn_mkr().Pipe(bgn_pos, cur_pos)); + return cur_pos; + } + } + case Tblw_type_th: // "!" + case Tblw_type_th2: // "!!" + case Tblw_type_tc: // "|+" + case Tblw_type_tr: // "|-" + if (tbl_is_xml) { // should automatically add
    ; DATE:2014-02-13 + prv_tkn = tkn_mkr.Tblw_tb(bgn_pos, bgn_pos, tbl_is_xml, true); + ctx.Subs_add_and_stack_tblw(root, prv_tkn, prv_tkn); + break; + } + else { + if (called_from == Called_from_pre) + return -1; + else + return Xop_tblw_wkr.Handle_false_tblw_match(ctx, root, src, bgn_pos, cur_pos, ctx.Tkn_mkr().Txt(bgn_pos + 1, cur_pos), true); // DATE:2014-02-19 + } + case Tblw_type_te: // "|}" + if (tblw_te_ignore_count > 0) { + --tblw_te_ignore_count; + return cur_pos; + } + else { + if (called_from == Called_from_pre) + return -1; + else + return Xop_tblw_wkr.Handle_false_tblw_match(ctx, root, src, bgn_pos, cur_pos, tkn_mkr.Txt(bgn_pos + 1, cur_pos), true); // +1 to skip "\n" in "\n|}" (don't convert \n to text); DATE:2014-02-19 + } + default: throw Err_.new_unhandled(wlxr_type); + } + } + + int prv_tid = prv_tkn == null ? Xop_tkn_itm_.Tid_null : prv_tkn.Tkn_tid(); + if (prv_tkn != null && !prv_tkn.Tblw_xml()) { // note that this logic is same as Atrs_close; repeated here for "perf" + switch (prv_tid) { + case Xop_tkn_itm_.Tid_tblw_tb: case Xop_tkn_itm_.Tid_tblw_tr: + Atrs_make(ctx, src, root, this, prv_tkn, Bool_.N); + break; + } + } + if (wlxr_type == Tblw_type_te) + return Make_tkn_end(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, Xop_tkn_itm_.Tid_tblw_te, wlxr_type, prv_tkn, prv_tid, tbl_is_xml); + else + return Make_tkn_bgn_tblw(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, wlxr_type, tbl_is_xml, atrs_bgn, atrs_end, prv_tkn, prv_tid); + } + private int Make_tkn_bgn_tblw(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, byte wlxr_type, boolean tbl_is_xml, int atrs_bgn, int atrs_end, Xop_tblw_tkn prv_tkn, int prv_tid) { + if (wlxr_type != Tblw_type_tb) // NOTE: do not ignore ws if {|; will cause strange behavior with pre; DATE:2013-02-12 + Ignore_ws(ctx, root); + Xop_tblw_tkn new_tkn = null; + switch (wlxr_type) { + case Tblw_type_tb: //
    + boolean ignore_prv = false, auto_create = false; + switch (prv_tid) { + case Xop_tkn_itm_.Tid_null: // noop;
    + break; + case Xop_tkn_itm_.Tid_tblw_td: // noop; somehow rolling up everything after " + , "|-" + , "|}" + ) , String_.Concat_lines_nl_skip_last + ( "
    + case Xop_tkn_itm_.Tid_tblw_th: // noop; ; code implicitly relies on td clearing block state, but no td was created + return cur_pos; + } + int acs_typeId = typeId; + if (prv_tid != typeId // NOTE: special logic to handle auto-close of
    + break; + case Xop_tkn_itm_.Tid_tblw_tb: // fix;
    ->
    ; ignore current table; DATE:2014-02-02 + if (prv_tkn.Tblw_xml()) { // fix:
    ->
    ; earlier tbl is xnde; ignore; EX:en.b:Wikibooks:Featured books; DATE:2014-02-08 + ((Xop_tblw_tb_tkn)prv_tkn).Tblw_xml_(false); // if
    {|, discard
    , but mark {| as
    ; needed to handle
    \n{|\n| where "|" must be treated as tblw dlm; DATE:2014-02-22 + ignore_prv = true; + } + // else // fix:
    ->
    ; earlier tbl is tblw; auto-create; EX:it.w:Main_Page; DATE:2014-02-08; TIDY:depend on tidy to fix; PAGE: it.w:Portal:Animali; DATE:2014-05-31 + // auto_create = true; + break; + case Xop_tkn_itm_.Tid_tblw_tr: // noop:
    ->
    ; should probably auto-create td, but MW does not; DATE:2014-03-18 + case Xop_tkn_itm_.Tid_tblw_tc: // noop;
    ; TIDY:was
    ; PAGE: es.w:Savilla DATE:2014-06-29 + break; + } + if (ignore_prv) { + ctx.Subs_add(root, tkn_mkr.Ignore(bgn_pos, cur_pos, Xop_ignore_tkn.Ignore_tid_htmlTidy_tblw)); + ++tblw_te_ignore_count; + cur_pos = Bry_find_.Find_fwd_until(src, cur_pos, src_len, Byte_ascii.Nl); // NOTE: minor hack; this tblw tkn will be ignored, so ignore any of its attributes as well; gobble up all chars till nl. see: if two consecutive tbs, ignore attributes on 2nd; en.wikibooks.org/wiki/Wikibooks:Featured books + return cur_pos; + } + if (auto_create) { + ctx.Subs_add_and_stack_tblw(root, prv_tkn, tkn_mkr.Tblw_tr(bgn_pos, bgn_pos, tbl_is_xml, true)); + ctx.Subs_add_and_stack_tblw(root, prv_tkn, tkn_mkr.Tblw_td(bgn_pos, bgn_pos, tbl_is_xml)); + } + Xop_tblw_tb_tkn tb_tkn = tkn_mkr.Tblw_tb(bgn_pos, cur_pos, tbl_is_xml, false); + new_tkn = tb_tkn; + break; + case Tblw_type_tr: // + switch (prv_tid) { + case Xop_tkn_itm_.Tid_tblw_tb: break; // noop;
    + case Xop_tkn_itm_.Tid_tblw_tc: // fix; -> + ctx.Stack_pop_til(root, src, ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tblw_tc), true, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); + break; + case Xop_tkn_itm_.Tid_tblw_td: // fix; -> + case Xop_tkn_itm_.Tid_tblw_th: // fix; -> + if (!tbl_is_xml) + ctx.Para().Process_nl(ctx, root, src, bgn_pos, bgn_pos + 1); // simulate "\n"; 2012-12-08 + int stack_pos = ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tblw_tr); + if ( stack_pos != Xop_ctx.Stack_not_found // don't pop if none found; PAGE:en.w:Turks_in_Denmark DATE:2014-03-02 + && !tbl_is_xml // cur is "|-", not ; PAGE:en.w:Aargau; DATE:2016-08-14 + ) { + ctx.Stack_pop_til(root, src, stack_pos, true, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); + } + break; + case Xop_tkn_itm_.Tid_tblw_tr: // fix; -> + if (prv_tkn.Tblw_subs_len() == 0) { // NOTE: set prv_row to ignore, but do not pop; see Tr_dupe_xnde and [[Jupiter]]; only invoke if same type; EX: but not |-; DATE:2013-12-09 + Xop_tkn_itm prv_row = ctx.Stack_pop_til(root, src, ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tblw_tr), false, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); + prv_row.Ignore_y_(); + } + else + ctx.Stack_pop_til(root, src, ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tblw_tr), true, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); + break; + } + Xop_tblw_tr_tkn tr_tkn = tkn_mkr.Tblw_tr(bgn_pos, cur_pos, tbl_is_xml, false); + new_tkn = tr_tkn; + break; + case Tblw_type_td: // ; EX: " exists + int prv_tb_tkn_idx = ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tblw_tb); + if (prv_tb_tkn_idx < prv_tr_tkn_idx) // don't close above current tbl + ctx.Stack_pop_til(root, src, prv_tr_tkn_idx, true, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); // close + } + new_tkn = tkn_mkr.Tblw_tr(bgn_pos, cur_pos, tbl_is_xml, true); // make a new + new_tkn.Atrs_rng_set(bgn_pos, bgn_pos); + ctx.Subs_add_and_stack_tblw(root, prv_tkn, new_tkn); + } + else { + if (!tbl_is_xml) // only for "\n|" not
    + case Tblw_type_td2: + boolean create_th = false; + switch (prv_tid) { + case Xop_tkn_itm_.Tid_tblw_tr: + if (wlxr_type == Tblw_type_td2) { // ignore sequences like "\n|- ||"; PAGE: nl.w:Tabel_van_Belgische_gemeenten; DATE:2015-12-03 + ctx.Subs_add(root, tkn_mkr.Ignore(bgn_pos, cur_pos, Xop_ignore_tkn.Ignore_tid_double_pipe)); + return cur_pos; + } + break; + case Xop_tkn_itm_.Tid_tblw_td: // fix; -> + if ( prv_tkn.Tblw_xml() // prv is + && !tbl_is_xml // cur is "\n|" + ) { // insert
    \n|" -> "
    " PAGE:fi.w:Salibandyn_maailmanmestaruuskilpailut_2012 DATE:2015-09-07 + int prv_tr_tkn_idx = ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tblw_tr); + if (prv_tr_tkn_idx != Xop_ctx.Stack_not_found) { //
    + ctx.Para().Process_nl(ctx, root, src, bgn_pos, bgn_pos + 1); // simulate "\n"; DATE:2014-02-20; ru.w:;home/wiki/Dashboard/Image_databases; DATE:2014-02-20 + ctx.Para().Process_block__bgn_y__end_n(Xop_xnde_tag_.Tag__td); // + ctx.Stack_pop_til(root, src, ctx.Stack_idx_typ(prv_tid), true, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); + } + break; + case Xop_tkn_itm_.Tid_tblw_th: // fix; -> + ctx.Stack_pop_til(root, src, ctx.Stack_idx_typ(prv_tid), true, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); + if (wlxr_type == Tblw_type_td2) create_th = true; // !a||b -> ; but !a|b -> + break; + case Xop_tkn_itm_.Tid_tblw_tb: // fix;
    ->
    + if (wlxr_type == Tblw_type_td2) { // NOTE: ignore || if preceded by {|; {|a||b\n + prv_tkn.Atrs_rng_set(-1, -1); // reset atrs_bgn; remainder of line will become part of tb atr + return cur_pos; + } + else { + new_tkn = tkn_mkr.Tblw_tr(bgn_pos, cur_pos, tbl_is_xml, true); + new_tkn.Atrs_rng_set(bgn_pos, bgn_pos); + ctx.Subs_add_and_stack_tblw(root, prv_tkn, new_tkn); + prv_tid = new_tkn.Tkn_tid(); + } + break; + case Xop_tkn_itm_.Tid_tblw_tc: // fix;
    ->
    + ctx.Stack_pop_til(root, src, ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tblw_tc), true, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); + new_tkn = tkn_mkr.Tblw_tr(bgn_pos, cur_pos, tbl_is_xml, true); + ctx.Subs_add_and_stack_tblw(root, prv_tkn, new_tkn); + prv_tid = new_tkn.Tkn_tid(); + break; + } +// if (prv_tid == Xop_tkn_itm_.Tid_xnde) +// ctx.Stack_auto_close(root, src, prv_tkn, prv_tkn.Src_bgn(), prv_tkn.Src_end()); + if (create_th) new_tkn = tkn_mkr.Tblw_th(bgn_pos, cur_pos, tbl_is_xml); + else new_tkn = tkn_mkr.Tblw_td(bgn_pos, cur_pos, tbl_is_xml); + cell_pipe_seen = false; + break; + case Tblw_type_th: // + case Tblw_type_th2: + switch (prv_tid) { + case Xop_tkn_itm_.Tid_tblw_tr: break; // noop;
    + case Xop_tkn_itm_.Tid_tblw_th: // fix; -> + if (tbl_is_xml // tbl_is_xml always closes previous token + || (wlxr_type == Tblw_type_th2 || wlxr_type == Tblw_type_th)) // ! always closes; EX: "! !!"; "!! !!"; REMOVE: 2012-05-07; had (&& !ws_enabled) but caused "\n !" to fail; guard is no longer necessary since tblw_ws changed... + ctx.Stack_pop_til(root, src, ctx.Stack_idx_typ(prv_tid), true, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); + else { + ctx.Subs_add(root, tkn_mkr.Txt(bgn_pos, cur_pos)); + return cur_pos; + } + break; + case Xop_tkn_itm_.Tid_tblw_td: // fix; -> NOTE: common use of using after for formatting + if (tbl_is_xml // tbl_is_xml always closes previous token + || (wlxr_type == Tblw_type_th)) // "| !" closes; "| !!" does not; + ctx.Stack_pop_til(root, src, ctx.Stack_idx_typ(prv_tid), true, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); + else { + ctx.Subs_add(root, tkn_mkr.Txt(bgn_pos, cur_pos)); + return cur_pos; + } + break; + case Xop_tkn_itm_.Tid_tblw_tb: // fix;
    ->
    + ctx.Subs_add_and_stack_tblw(root, prv_tkn, tkn_mkr.Tblw_tr(bgn_pos, cur_pos, tbl_is_xml, true)); + break; + case Xop_tkn_itm_.Tid_tblw_tc: // fix;
    ->
    + ctx.Stack_pop_til(root, src, ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tblw_tc), true, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); + ctx.Subs_add_and_stack_tblw(root, prv_tkn, tkn_mkr.Tblw_tr(bgn_pos, cur_pos, tbl_is_xml, true)); + break; + } + new_tkn = tkn_mkr.Tblw_th(bgn_pos, cur_pos, tbl_is_xml); + cell_pipe_seen = false; + break; + case Tblw_type_tc: //
    + switch (prv_tid) { + case Xop_tkn_itm_.Tid_tblw_tb: break; // noop; in order to close
    + case Xop_tkn_itm_.Tid_tblw_tr: // fix;
    ->
    TODO_OLD: caption should be ignored and placed in quarantine + ctx.Stack_pop_til(root, src, ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tblw_tr), true, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); + break; + case Xop_tkn_itm_.Tid_tblw_td: // fix;
    ->
    + case Xop_tkn_itm_.Tid_tblw_th: // fix;
    ->
    + ctx.Stack_pop_til(root, src, ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tblw_tr), true, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); // NOTE: closing
    / + ctx.Msg_log().Add_itm_none(Xop_tblw_log.Caption_after_td, src, prv_tkn.Src_bgn(), bgn_pos); + break; + case Xop_tkn_itm_.Tid_tblw_tc: // fix;
    -> + ctx.Stack_pop_til(root, src, ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tblw_tc), true, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); + ctx.Msg_log().Add_itm_none(Xop_tblw_log.Caption_after_tc, src, prv_tkn.Src_bgn(), bgn_pos); + break; + } + new_tkn = tkn_mkr.Tblw_tc(bgn_pos, cur_pos, tbl_is_xml); + Xop_tblw_tb_tkn tblw_tb_tkn = (Xop_tblw_tb_tkn)ctx.Stack_get_typ(Xop_tkn_itm_.Tid_tblw_tb); + tblw_tb_tkn.Caption_count_add_1(); // NOTE: null check is not necessary (impossible to have a caption without a tblw); DATE:2013-12-20 + cell_pipe_seen = false; // NOTE: always mark !seen; see Atrs_tc() + break; + } + ctx.Subs_add_and_stack_tblw(root, prv_tkn, new_tkn); + if (atrs_bgn > Xop_tblw_wkr.Atrs_ignore_check) { + new_tkn.Atrs_rng_set(atrs_bgn, atrs_end); + if (ctx.Parse_tid() == Xop_parser_tid_.Tid__wtxt) { + Mwh_atr_itm[] atrs = ctx.App().Parser_mgr().Xnde__parse_atrs_for_tblw(src, atrs_bgn, atrs_end); + new_tkn.Atrs_ary_as_tblw_(atrs); + } + } + switch (wlxr_type) { + case Tblw_type_tb: + case Tblw_type_tr: + ctx.Para().Process_block__bgn_y__end_n(Xop_xnde_tag_.Tag__tr); + break; + case Tblw_type_td: + case Tblw_type_th: + ctx.Para().Process_block__bgn_n__end_y(Xop_xnde_tag_.Tag__td); + break; + } + return cur_pos; + } + public int Make_tkn_end(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, int typeId, byte wlxr_type, Xop_tblw_tkn prv_tkn, int prv_tid, boolean tbl_is_xml) { + if (!tbl_is_xml) // only for "\n|}" not
    + ctx.Para().Process_nl(ctx, root, src, bgn_pos, bgn_pos + 1); // simulate "\n"; process para (which will create paras for cells) 2012-12-08 + if (tbl_is_xml && typeId == Xop_tkn_itm_.Tid_tblw_tb // tblx:
    + && prv_tkn != null && !prv_tkn.Tblw_xml()) { // tblw is prv_tkn + ++tblw_te_ignore_count; // suppress subsequent occurrences of "|}"; EX:ru.q:Авель; DATE:2014-02-22 + } + Ignore_ws(ctx, root); + if (wlxr_type == Tblw_type_te) { + switch (prv_tid) { + case Xop_tkn_itm_.Tid_tblw_td: // fix;
    ->
    + case Xop_tkn_itm_.Tid_tblw_th: // fix;
    ->
    + ctx.Stack_pop_til(root, src, ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tblw_tr), true, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); + break; + case Xop_tkn_itm_.Tid_tblw_tc: // fix;
    ->
    + ctx.Stack_pop_til(root, src, ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tblw_tc), true, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); + break; + case Xop_tkn_itm_.Tid_tblw_tr: // fix;
    ->
    : tr but no tds; remove tr + boolean blank = true; + for (int j = prv_tkn.Tkn_sub_idx() + 1; j < root.Subs_len(); j++) { + Xop_tkn_itm t = root.Subs_get(j); + switch (t.Tkn_tid()) { + case Xop_tkn_itm_.Tid_newLine: + case Xop_tkn_itm_.Tid_para: + break; + default: + blank = false; + j = root.Subs_len(); + break; + } + } + if (blank) + root.Subs_del_after(prv_tkn.Tkn_sub_idx()); + break; + case Xop_tkn_itm_.Tid_tblw_tb: // fix;
    ->
    + boolean has_subs = false; + for (int i = prv_tkn.Tkn_sub_idx() + 1; i < root.Subs_len(); i++) { + int cur_id = root.Subs_get(i).Tkn_tid(); + switch (cur_id) { + case Xop_tkn_itm_.Tid_tblw_tc: + case Xop_tkn_itm_.Tid_tblw_td: + case Xop_tkn_itm_.Tid_tblw_th: + case Xop_tkn_itm_.Tid_tblw_tr: + has_subs = true; + i = root.Subs_len(); + break; + } + } + if (!has_subs) { + Xop_tkn_itm new_tkn = tkn_mkr.Tblw_tr(bgn_pos, bgn_pos, tbl_is_xml, true); + ctx.Subs_add_and_stack_tblw(root, prv_tkn, new_tkn); + new_tkn = tkn_mkr.Tblw_td(bgn_pos, bgn_pos, tbl_is_xml); + ctx.Subs_add_and_stack_tblw(root, prv_tkn, new_tkn); + ctx.Stack_pop_til(root, src, ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tblw_tb), true, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); + return cur_pos; + } + break; + } + int tb_idx = ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_tblw_tb); + if (tb_idx == -1) return cur_pos; // NOTE: tb_idx can be -1 when called from Pipe in Tmpl mode + Xop_tblw_tb_tkn tb = (Xop_tblw_tb_tkn)ctx.Stack_pop_til(root, src, tb_idx, false, bgn_pos, bgn_pos, Xop_tkn_itm_.Tid_tblw_td); // NOTE: need to pop manually in order to set all intermediate node ends to bgn_pos, but tb ent to cur_pos; EX: for stack of "tb,tr,td" tr and td get End_() of bgn_pos but tb gets End_() of cur_pos + tb.Subs_move(root); + tb.Src_end_(cur_pos); + ctx.Para().Process_block__bgn_n__end_y(Xop_xnde_tag_.Tag__table); // NOTE: must clear block state that was started by
    or + && ( (prv_tid == Xop_tkn_itm_.Tid_tblw_td && typeId == Xop_tkn_itm_.Tid_tblw_th) + || (prv_tid == Xop_tkn_itm_.Tid_tblw_th && typeId == Xop_tkn_itm_.Tid_tblw_td) + ) + ) + acs_typeId = prv_tid; + + int acs_pos = -1, acs_len = ctx.Stack_len(); + for (int i = acs_len - 1; i > -1; i--) { // find auto-close pos + byte cur_acs_tid = ctx.Stack_get(i).Tkn_tid(); + switch (acs_typeId) { + case Xop_tkn_itm_.Tid_tblw_tb: // if
    , match only; note that it needs to be handled separately b/c of tb logic below + if (acs_typeId == cur_acs_tid) { + acs_pos = i; + i = -1; // force break; + } + break; + default: // if , match but stop at
    ; do not allow to close outside
    + if (cur_acs_tid == Xop_tkn_itm_.Tid_tblw_tb) //
    ; do not allow to close any 's above
    ; EX:w:Enthalpy_of_fusion; {{States of matter}} + i = -1; // this will skip acs_pos != -1 below and discard token + else if (cur_acs_tid == acs_typeId) { // matches + acs_pos = i; + i = -1; // force break + } + break; + } + } + if (acs_pos != -1) { + Xop_tblw_tkn bgn_tkn = (Xop_tblw_tkn)ctx.Stack_pop_til(root, src, acs_pos, false, bgn_pos, cur_pos, Xop_tkn_itm_.Tid_tblw_td); + switch (wlxr_type) { + case Tblw_type_tb: + ctx.Para().Process_block__bgn_n__end_y(Xop_xnde_tag_.Tag__table); + break; + case Tblw_type_td: + case Tblw_type_th: + ctx.Para().Process_block__bgn_y__end_n(Xop_xnde_tag_.Tag__td); + break; + } + bgn_tkn.Subs_move(root); + bgn_tkn.Src_end_(cur_pos); + } + return cur_pos; + } + public static void Atrs_close(Xop_ctx ctx, byte[] src, Xop_root_tkn root, boolean called_from_xnde) { + Xop_tblw_tkn prv_tkn = ctx.Stack_get_tbl(); + if (prv_tkn == null || prv_tkn.Tblw_xml()) return; // no tblw or tblw_xnde (which does not have tblw atrs) + switch (prv_tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_tblw_tb: case Xop_tkn_itm_.Tid_tblw_tr: // only tb and tr have tblw atrs (EX: "{|id=1\n"); td/th use pipes for atrs (EX: "|id=1|a"); tc has no atrs; te is never on stack + Xop_tblw_wkr.Atrs_make(ctx, src, root, ctx.Tblw(), prv_tkn, called_from_xnde); + break; + } + } + public static boolean Atrs_make(Xop_ctx ctx, byte[] src, Xop_root_tkn root, Xop_tblw_wkr wkr, Xop_tblw_tkn prv_tblw, boolean called_from_xnde) { + if (prv_tblw.Atrs_bgn() != Xop_tblw_wkr.Atrs_null) { // atr_bgn/end is empty or already has explicit value; ignore; + if (prv_tblw.Atrs_bgn() == Atrs_invalid_by_xnde) { // atr range marked invalid; ignore all tkns between prv_tblw and end of root; EX:"|-id=1
    "; PAGE:en.w:A DATE:2014-07-16 + for (int j = root.Subs_len() - 1; j > -1; --j) { + Xop_tkn_itm sub = root.Subs_get(j); + if (sub == prv_tblw) + return false; + else + sub.Ignore_y_(); + } + ctx.App().Usr_dlg().Warn_many("", "", "xnde.invalided attributes could not find previous tkn; page=~{0}", ctx.Page_url_str()); // should never happen; DATE:2014-07-16 + } + return false; + } + int subs_bgn = prv_tblw.Tkn_sub_idx() + 1, subs_end = root.Subs_len() - 1; + int subs_pos = subs_bgn; + Xop_tkn_itm last_atr_tkn = null; + boolean loop = true; + while (loop) { // loop over tkns after prv_tkn to find last_atr_tkn + if (subs_pos > subs_end) break; + Xop_tkn_itm tmp_tkn = root.Subs_get(subs_pos); + switch (tmp_tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_newLine: // nl stops; EX: "{| a b c \nd"; bgn at {| and pick up " a b c " as atrs + case Xop_tkn_itm_.Tid_hdr: case Xop_tkn_itm_.Tid_hr: // hdr/hr incorporate nl into tkn so include these as well; EX: "{|a\n==b==" becomes tblw,txt,hdr (note that \n is part of hdr + case Xop_tkn_itm_.Tid_list: // list stops; EX: "{| a b c\n* d"; "*d" ends atrs; EX: ru.d: DATE:2014-02-22 + loop = false; + break; + default: + ++subs_pos; + last_atr_tkn = tmp_tkn; + break; + } + } + if (last_atr_tkn == null) { // no atrs found; mark tblw_tkn as Atrs_empty + int atr_rng_tid + = called_from_xnde + && !prv_tblw.Tblw_xml() + && prv_tblw.Tkn_tid() == Xop_tkn_itm_.Tid_tblw_tr // called from xnde && current tid is Tblw_tr; EX:"|-
    " PAGE:en.w:A DATE:2014-07-16 + ? Atrs_invalid_by_xnde // invalidate everything + : Atrs_empty + ; + prv_tblw.Atrs_rng_set(atr_rng_tid, atr_rng_tid); + return false; + } + root.Subs_del_between(ctx, subs_bgn, subs_pos); + int atrs_bgn = prv_tblw.Src_end(), atrs_end = last_atr_tkn.Src_end(); + if (prv_tblw.Tkn_tid() == Xop_tkn_itm_.Tid_tblw_tr) // NOTE: if "|-" gobble all trailing dashes; REF: Parser.php!doTableStuff; $line = preg_replace( '#^\|-+#', '', $line ); DATE:2013-06-21 + 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) { + 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); + } + wkr.Cell_pipe_seen_(true); + return true; + } + private void Ignore_ws(Xop_ctx ctx, Xop_root_tkn root) { + int end = root.Subs_len() - 1; + // get last tr, tc, tb; cannot use ctx.Stack_get_tblw b/c this gets last open tblw, and we want last tblw; EX: "
    "; Stack_get_tblw gets
    want + boolean found = false; + Xop_tkn_itm prv_tkn = null; + for (int i = end; i > -1; i--) { + prv_tkn = root.Subs_get(i); + switch (prv_tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_tblw_tr: + case Xop_tkn_itm_.Tid_tblw_tc: + case Xop_tkn_itm_.Tid_tblw_tb: + found = true; + i = -1; + break; + case Xop_tkn_itm_.Tid_tblw_td: // exclude td + case Xop_tkn_itm_.Tid_tblw_th: // exclude th + i = -1; + break; + } + } + if (!found) return; + int bgn = prv_tkn.Tkn_sub_idx() + 1; + int rv = Ignore_ws_rng(ctx, root, bgn, end, true); + if (rv == -1) return; // entire range is ws; don't bother trimming end + Ignore_ws_rng(ctx, root, end, bgn, false); + } + private int Ignore_ws_rng(Xop_ctx ctx, Xop_root_tkn root, int bgn, int end, boolean fwd) { + int cur = bgn, adj = fwd ? 1 : -1; + while (true) { + if (fwd) { + if (cur > end) return -1; + } + else { + if (cur < end) return -1; + } + Xop_tkn_itm ws_tkn = root.Subs_get(cur); + switch (ws_tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_space: case Xop_tkn_itm_.Tid_tab: case Xop_tkn_itm_.Tid_newLine: + case Xop_tkn_itm_.Tid_para: + ws_tkn.Ignore_y_grp_(ctx, root, cur); + break; + case Xop_tkn_itm_.Tid_xnde: + if (ws_tkn.Src_bgn() == ws_tkn.Src_end() // NOTE: para_wkr inserts
    . these should be disabled in Ignore_ws_rng; they are identified as having bgn == end; normal
    s will have bgn < end + && ((Xop_xnde_tkn)ws_tkn).Tag().Id() == Xop_xnde_tag_.Tid__br) + ws_tkn.Ignore_y_grp_(ctx, root, cur); + break; + default: + return cur; + } + cur += adj; + } + } + public static int Handle_false_tblw_match(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int bgn_pos, int cur_pos, Xop_tkn_itm tkn, boolean add_nl) { + if (add_nl) + ctx.Para().Process_nl(ctx, root, src, bgn_pos, cur_pos); + ctx.Subs_add(root, tkn); + return cur_pos; + } + public static final int Atrs_null = -1, Atrs_empty = -2, Atrs_invalid_by_xnde = -3, Atrs_ignore_check = -1; + public static final byte Tblw_type_tb = 0, Tblw_type_te = 1, Tblw_type_tr = 2, Tblw_type_td = 3, Tblw_type_th = 4, Tblw_type_tc = 5, Tblw_type_td2 = 6, Tblw_type_th2 = 7; +} +/* +NOTE_1: +Code tries to emulate HTML tidy behavior. Specifically: +- ignore
    when directly under
    +- if tblw, scan to end of line to ignore attributes +- ignore any closing tblws +EX: +{|id=1 +{|id=2 <- ignore id=2 +|} +|} +*/ \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__atrs_tst.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__atrs_tst.java index a27517de8..8f8259a39 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__atrs_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__atrs_tst.java @@ -13,3 +13,198 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_tblw_wkr__atrs_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Tr() { + fxt.Test_parse_page_wiki(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-style='a'" + , "|b" + , "|}" + ), fxt.tkn_tblw_tb_(0, 20).Subs_ + ( fxt.tkn_tblw_tr_(2, 17).Atrs_rng_(5, 14).Subs_ + ( fxt.tkn_tblw_td_(14, 17).Subs_(fxt.tkn_txt_(16, 17), fxt.tkn_para_blank_(18)) + )) + ); + } + @Test public void Td() { + fxt.Test_parse_page_wiki(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|style='a'|b" + , "|}" + ), fxt.tkn_tblw_tb_(0, 21).Subs_ + ( fxt.tkn_tblw_tr_(2, 18).Subs_ + ( fxt.tkn_tblw_td_(5, 18).Atrs_rng_(7, 16).Subs_(fxt.tkn_txt_(17, 18), fxt.tkn_para_blank_(19)) + )) + ); + } + @Test public void Td_mult() { + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|" + , " {|" + , " |-" + , " | id='1'|" + , " | id='2'|a" + , " | id='3'|" + , " |}" + , "|}" + ) + , String_.Concat_lines_nl_skip_last + ( "
    " + , " " + , " " + , " " + , "
    " + , " " + , " " + , " " + , " " + , " " + , " " + , "
    " + , " a" + , " " + , "
    " + , "
    " + , "" + ) + ); + fxt.Init_para_n_(); + } + @Test public void Tc() { // PAGE:en.w:1920_Palm_Sunday_tornado_outbreak + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|id='1'" + , "|+id='2'|a" + , "|}" + ) + , String_.Concat_lines_nl_skip_last + ( "" + , " " + , "
    a" + , "
    " + , "" + ) + ); + fxt.Init_para_n_(); + } + @Test public void Td_mixed() { + fxt.Test_parse_page_wiki(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|style='a'|b||c" + , "|}" + ), fxt.tkn_tblw_tb_(0, 24).Subs_ + ( fxt.tkn_tblw_tr_(2, 21).Subs_ + ( fxt.tkn_tblw_td_( 5, 18).Atrs_rng_(7, 16).Subs_(fxt.tkn_txt_(17, 18), fxt.tkn_para_blank_(19)) + , fxt.tkn_tblw_td_(18, 21).Subs_(fxt.tkn_txt_(20, 21), fxt.tkn_para_blank_(22)) + )) + ); + } + @Test public void Th() { + fxt.Test_parse_page_wiki(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "!style='a'|b" + , "|}" + ), fxt.tkn_tblw_tb_(0, 21).Subs_ + ( fxt.tkn_tblw_tr_(2, 18).Subs_ + ( fxt.tkn_tblw_th_(5, 18).Atrs_rng_(7, 16).Subs_(fxt.tkn_txt_(17, 18), fxt.tkn_para_blank_(19)) + )) + ); + } + @Test public void Skip_hdr() { + fxt.Test_parse_page_wiki(String_.Concat_lines_nl_skip_last + ( "{|" + , "|+b" + , "!style='a'|b" + , "|}" + ), fxt.tkn_tblw_tb_(0, 22).Caption_count_(1).Subs_ + ( fxt.tkn_tblw_tc_(2, 6).Subs_(fxt.tkn_txt_( 5, 6)) + , fxt.tkn_tblw_tr_(6, 19).Subs_ + ( fxt.tkn_tblw_th_(6, 19).Atrs_rng_(8, 17).Subs_(fxt.tkn_txt_(18, 19), fxt.tkn_para_blank_(20)) + ) + )); + } + @Test public void Td_bg_color() { // PURPOSE: atr_parser should treat # as valid character in unquoted val; PAGE:en.w:UTF8; |bgcolor=#eeeeee|Indic
    0800*
    '''''224''''' + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|bgcolor=#eeeeee|a" + , "|}" + ) + , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + ) + ); + fxt.Init_para_n_(); + } + @Test public void Xnde_tb() { // PURPOSE: xnde should close any open xatrs; PAGE:en.w:Western_Front_(World_War_I); stray > after == Dramatizations == + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|id='1'

    " + , "|a" + , "|}"), String_.Concat_lines_nl_skip_last + ( "

    " + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + )); + } + @Test public void Xnde_tr() { // PURPOSE: xnde should disable all tkns; PAGE:en.w:A DATE:2014-07-16 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-cid='d'
    " // note that id='d' should not show up since invalidates entire line + , "|a" + , "|}" + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + )); + } + @Test public void Xnde_mix_tblw_tblx() { // PURPOSE: issue with
    ; PAGE:en.w:20th_century; {{Decades and years}} + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "
    a" + , "{|id=1" + , "|-" + , "|b" + , "|}
    " + ) + , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , " " + , " " + , " " + , " " + , "
    b" + , "
    " + , "
    " + , "" + ) + ); + fxt.Init_para_n_(); + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__basic_tst.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__basic_tst.java index a27517de8..eaf70c836 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__basic_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__basic_tst.java @@ -13,3 +13,802 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_tblw_wkr__basic_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Td() { // Tb_tr_td_te + fxt.Test_parse_page_wiki("{|\n|-\n|a\n|}" + , fxt.tkn_tblw_tb_(0, 11).Subs_ + ( fxt.tkn_tblw_tr_(2, 8).Subs_ + ( fxt.tkn_tblw_td_(5, 8).Subs_(fxt.tkn_txt_(7, 8), fxt.tkn_para_blank_(9)))) + ); + } + @Test public void Td2() { // Tb_tr_td_td2_te + fxt.Test_parse_page_wiki("{|\n|-\n|a||b\n|}" + , fxt.tkn_tblw_tb_(0, 14).Subs_ + ( fxt.tkn_tblw_tr_(2, 11).Subs_ + ( fxt.tkn_tblw_td_(5, 8).Subs_(fxt.tkn_txt_( 7, 8), fxt.tkn_para_blank_(9)) + , fxt.tkn_tblw_td_(8, 11).Subs_(fxt.tkn_txt_(10, 11), fxt.tkn_para_blank_(12)) + ))); + } + @Test public void Tc() { // Tb_tc_te + fxt.Test_parse_page_wiki("{|\n|+a\n|}" + , fxt.tkn_tblw_tb_(0, 9).Caption_count_(1).Subs_ + ( fxt.tkn_tblw_tc_(2, 6).Subs_ + ( fxt.tkn_txt_(5, 6) + , fxt.tkn_para_blank_(7) + ) + ) + ); + } + @Test public void Tc_longer() { // Tb_tc_tr_td_te + fxt.Test_parse_page_wiki("{|\n|+a\n|-\n|b\n|}" + , fxt.tkn_tblw_tb_(0, 15).Caption_count_(1).Subs_ + ( fxt.tkn_tblw_tc_(2, 6).Subs_(fxt.tkn_txt_(5, 6)) + , fxt.tkn_tblw_tr_(6, 12).Subs_ + ( fxt.tkn_tblw_td_(9, 12).Subs_(fxt.tkn_txt_(11, 12), fxt.tkn_para_blank_(13)) + ) + )); + } + @Test public void Th() { // Tb_th_te + fxt.Test_parse_page_wiki("{|\n|-\n!a\n|}" + , fxt.tkn_tblw_tb_(0, 11).Subs_ + ( fxt.tkn_tblw_tr_(2, 8).Subs_ + ( fxt.tkn_tblw_th_(5, 8).Subs_(fxt.tkn_txt_(7, 8), fxt.tkn_para_blank_(9)) + ))); + } + @Test public void Th2() { // Tb_th_th2_te + fxt.Test_parse_page_wiki("{|\n|-\n!a!!b\n|}" + , fxt.tkn_tblw_tb_(0, 14).Subs_ + ( fxt.tkn_tblw_tr_(2, 11).Subs_ + ( fxt.tkn_tblw_th_(5, 8).Subs_(fxt.tkn_txt_( 7, 8)) + , fxt.tkn_tblw_th_(8, 11).Subs_(fxt.tkn_txt_(10, 11), fxt.tkn_para_blank_(12)) + ))); + } + @Test public void Th2_td_syntax() { // Tb_th_td; || should be treated as th + fxt.Test_parse_page_wiki("{|\n|-\n!a||b\n|}" + , fxt.tkn_tblw_tb_(0, 14).Subs_ + ( fxt.tkn_tblw_tr_(2, 11).Subs_ + ( fxt.tkn_tblw_th_(5, 8).Subs_(fxt.tkn_txt_( 7, 8)) + , fxt.tkn_tblw_th_(8, 11).Subs_(fxt.tkn_txt_(10, 11), fxt.tkn_para_blank_(12)) + ))); + } + @Test public void Tb_td2() { // PAGE:en.w:Hectare; {| class="wikitable" || style="border: 1px solid #FFFFFF;" + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|id='1' || class='a'" + , "|-" + , "|a" + , "|}") + , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + )); + } + @Test public void Td_lnki() { + fxt.Test_parse_page_wiki("{|\n|-\n|[[a|b]]\n|}" + , fxt.tkn_tblw_tb_(0, 17).Subs_ + ( fxt.tkn_tblw_tr_(2, 14).Subs_ + ( fxt.tkn_tblw_td_(5, 14).Subs_(fxt.tkn_lnki_(7, 14), fxt.tkn_para_blank_(15)))) + ); + } + @Test public void Tr_dupe_xnde() { // PURPOSE: redundant tr should not be dropped; see [[Jupiter]] + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "
    a
    " + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + ) + ); + } + @Test public void Tr_dupe_xnde_2() { //
    causes problems + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "
    a
    " + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + ) + ); + } + @Test public void Bang_should_not_make_cell_td_1_bang() { // PURPOSE: "| a! b" ! should not separate cell + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last("{|", "|-", "|a!b", "|}"), String_.Concat_lines_nl_skip_last("", " ", " ", " ", "
    a!b" , "
    ", "")); + } + @Test public void Bang_should_not_make_cell_td_2_bang() { + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last("{|", "|-", "|a!!b", "|}"), String_.Concat_lines_nl_skip_last("", " ", " ", " ", "
    a!!b" , "
    ", "")); + } + @Test public void Bang_should_not_make_cell_th_1_bang() { + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last("{|", "|-", "!a!b", "|}"), String_.Concat_lines_nl_skip_last("", " ", " ", " ", "
    a!b" , "
    ", "")); + } + @Test public void Bang_should_not_make_cell_th_2_bang() { + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last("{|", "|-", "!a!!b", "|}") + , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , "
    a" + , " b" + , "
    " + , "" + )); + } + @Test public void Bang_should_not_make_cell_th_mult_line() { // FIX: make sure code does not disable subsequent bangs + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last("{|", "|-", "!a", "!b", "|}") + , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , "
    a" + , " b" + , "
    " + , "" + )); + } + @Test public void Fix_extra_cell() { // PURPOSE: trim should not affect td; WP:Base32 + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "!id='1'|a" + , "|" + , "!id='2'|b" + , "|-" + , "|a1|| ||b1" + , "|}" + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , "
    a" + , " " + , " b" + , "
    a1" + , " " + , " b1" + , "
    " + , "" + ) + ); + fxt.Init_para_n_(); + } + @Test public void Nl_td() { // PURPOSE:

    inside does not get enclosed + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "" + , "" + , "" + , "" + , "
    " + , "" + , "" + , "a" + , "" + , "" + , "
    " + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    " + , "" + , "


    " + , "a" + , "

    " + , "" + , "


    " + , "

    " + , "
    " + , "" + ) + ); + fxt.Init_para_n_(); + } + @Test public void Trim_ws() { // PURPOSE: trim should be done from both sides + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "" + , "" + , "" + , "" + , "" + , "" + , "a" + , "" + , "" + , "
    " + , "
    " + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "a" + , "
    " + , "
    " + , "" + ) + ); + fxt.Init_para_n_(); + } + @Test public void Trim_ws_tr() { // PURPOSE: trim should be done from both sides + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "" + , "" + , "" + , "" + , "" + , "" + , "" + , "" + , "" + , "" + , "" + , "
    " + , "
    " + , "
    " + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , " " + , " " + , "
    " + , "
    " + , "
    " + , "" + ) + ); + fxt.Init_para_n_(); + } + @Test public void Trim_ws_td() { // PURPOSE: trim should not affect td + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "" + , "" + , "" + , "" + , "
    " + , "" + , "" + , "a" + , "" + , "" + , "
    " + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    " + , "" + , "


    " + , "a" + , "

    " + , "" + , "


    " + , "

    " + , "
    " + , "" + ) + ); + fxt.Init_para_n_(); + } + @Test public void No_wiki_3() { + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|style='a[b]c'|d" + , "|}" + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    d" + , "
    " + , "" + )); + } + @Test public void Trailing_tr_breaks_para_mode() {// PURPOSE.fix: empty trailing tr breaks para mode; EX:w:Sibelius + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|a" + , "|-" // causes lines below not to be put in paras + , "|}" + , "b" + , "" + , "c" + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + , "

    b" + , "

    " + , "" + , "

    c" + , "

    " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Blank_line_should_be_own_para() {// PURPOSE.fix: caption does not begin on own line; EX:w:Old St. Peter's Basilica + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|a" + , "b" + , "|}" + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "" + , "

    b" + , "

    " + , "
    " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Blank_line_should_be_own_para_2() {// PURPOSE.fix: caption does not begin on own line; EX:w:Old St. Peter's Basilica + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|a" + , "b" + , "|-" + , "|}" + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "" + , "

    b" + , "

    " + , "
    " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Bold_stops_at_table() { // PURPOSE: do not allow unclosed bold to extend over tables; + fxt.Test_parse_page_all_str("'''
    a
    ", String_.Concat_lines_nl_skip_last + ( "" + , "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + )); + fxt.Init_defn_clear(); + } + @Test public void Orphaned_tr_breaks_nested_tables() { // PUPRPOSE: should not match outside scope; EX:w:Enthalpy_of_fusion; {{States of matter}} + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "" + , "" + , "" + , "" + , "" + , "
    " + , "" + , "" + , "
    " + , "
    a" + , "
    " + ), + String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , "
    " + , " " + , "
    " + , "
    a" + , "
    " + , "" + ) + ); + } + @Test public void Space_causes_extra_p() {// PURPOSE: "\n\s" should be equivalent to "\n"; EX: w:Earth + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "
    " + , "b" + , "
    c" + , "
    " + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    " + , "" + , "

    b" // used to close

    here;

    b

    + , "
    c" + , "

    " + , "
    " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Br_should_not_be_ignored() {// PURPOSE: document
    's should not be ignored between tables; 20121226 + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|a" + , "|}" + , "
    " + , "{|" + , "|-" + , "|b" + , "|}" + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + , "


    " // (a)
    was being ignored; (b)

    added for TRAILING_TBLW fix; DATE:2017-04-08 + , "

    " + , "" + , " " + , " " + , " " + , "
    b" + , "
    " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void AutoClose_td_when_new_tr() { // retain; needed for de.w:Main_Page; DATE:2013-12-09 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "==a==" + , "|}" + ) + , String_.Concat_lines_nl_skip_last + ( "" + , "" + , "

    a

    " // NOTE: malformed html matches MW + , " " + , " " + , " " + , "
    " + , "
    " + , "" + )); + } + @Test public void Auto_create_table() {// PURPOSE: should create table; EX:w:Hatfield-McCoy_feud; DATE:20121226 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "a" + , "" + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + )); + } + @Test public void List_and_orphaned_td2_should_not_create_tblw() {// PURPOSE: !! was creating table; DATE:2013-04-28 + fxt.Test_parse_page_all_str("*a !! b", String_.Concat_lines_nl_skip_last + ( "
      " + , "
    • a !! b" + , "
    • " + , "
    " + )); + } + @Test public void Tr_trailing_dashes_should_be_stripped() {// PURPOSE: trailing dashes should be stripped; |--- -> |-; EX: |--style="x" was being ignored; DATE:2013-06-21 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-----style='a'" + , "|b" + , "|}" + ), String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
    b" + , "
    " + )); + } + @Test public void Th_without_tr() { // PURPOSE: !! without preceding ! should not create table-cell; DATE:2013-12-18 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|" + , "a!!b" + , "|}" + ), String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
    " + , "a!!b" + , "
    " + )); + } + @Test public void Td_at_eos() {// PURPOSE.fix: !! at eos fails; EX:es.s:Si_mis_manos_pudieran_deshojar; DATE:2014-02-11 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "| !!" // note that "!!" is eos inside the src + , "|}" + ), String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
    " + , "

    " + , "!!" + , "

    " + , "
    " + , "
    " + )); + } + @Test public void Tr_without_tb_should_start_tb() {// PURPOSE: orphaned tr should automatically start table; EX: pl.w:Portal:Technika; DATE:2014-02-13 + fxt.Test_parse_page_all_str("a" + , String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + )); + } + @Test public void Tblx_should_not_close_tblw() {// PURPOSE: should not close {|; EX:fr.w:Exp%C3%A9dition_Endurance; DATE:2014-02-13 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|" + , "" + , "|}" + ) + , String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
    " + , "
    " + )); + } + @Test public void Tblx_should_not_close_tblw_2() {// PURPOSE: should close {|; ignore latter |}; EX:ru.q:Авель; DATE:2014-02-22 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|a" + , "" + , "{|" + , "|-" + , "|b" + , "" + , "{|" + , "|-" + , "|c" + , "" + , "|}" + ) + , String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + , " " + , " " + , " " + , "
    b" + , "
    " + , "" + , " " + , " " + , " " + , "
    c" + , "
    " + )); + } + @Test public void Td_in_list_in_tblw_should_be_ignored() {// PURPOSE: || should be ignored if in list; EX:es.d:casa; DATE:2014-02-15 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|" + , "* a || b" + , "|}" + ) + , String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
    " + , "
      " + , "
    • a || b" + , "
    • " + , "
    " + , "
    " + )); + } + @Test public void List_in_tblw() {// PURPOSE: list should close previous cell; EX: ru.d:Викисловарь:Условные_сокращения; DATE:2014-02-22 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|" + , "{|" + , "*a" + , "|}" + , "|}" + ) + , String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
    " + , " " + , "
      " // NOTE: this should probably be inside
    , but this matches MW behavior; DATE:2014-02-22 + , "
  • a" + , "
  • " + , " " + , " " + , " " + , " " + , "
    " + , "
    " + , "
    " + )); + } +} +// @Test public void Tb_under_tr_is_ignored() { // PURPOSE: table directly under tr is ignored; PAGE:en.w:Category:Dessert stubs; TODO_OLD: complicated, especially to handle 2nd |} +// fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last +// ( "{|" +// , "|-id='a'" +// , "{|style='border:1px;'" +// , "|-id='b'" +// , "|b" +// , "|}" +// , "|}" +// ), String_.Concat_lines_nl_skip_last +// ( "" +// , " " +// , " " +// , " " +// , "
    b" +// , "
    " +// , "" +// )); +// } +// @Test public void Leading_ws() { // PAGE:en.w:Corneal dystrophy (human) +// fxt.Test_parse_page_wiki(String_.Concat_lines_nl_skip_last +// ( " {|" +// , " |-" +// , " |a" +// , " |}" +// ) +// , fxt.tkn_tblw_tb_(1, 15).Subs_ +// ( fxt.tkn_tblw_tr_(3, 11).Subs_ +// ( fxt.tkn_tblw_td_(7, 11).Subs_ +// ( fxt.tkn_txt_()) +// ) +// ) +// ); +// } +// @Test public void Atrs_tb() { // Tb_te // FUTURE: reinstate; WHEN: Template +// fxt.Init_log_(Xop_tblw_log.Tbl_empty).Test_parse_page_wiki("{|style='a'\n|}" +// , fxt.tkn_tblw_tb_(0, 14).Atrs_rng_(2, 11).Subs_ +// ( fxt.tkn_tblw_tr_(11, 11).Subs_ +// ( fxt.tkn_tblw_td_(11, 11) +// ))); +// } +// @Test public void Td_p() { // PURPOSE:

    not being closed correctly +// fxt.Init_para_y_(); +// fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last +// ( "{|" +// , "|-" +// , "|" +// , "a" +// , "|}"), String_.Concat_lines_nl_skip_last +// ( "" +// , " " +// , " " +// , " " +// , "
    " +// , "" +// , "

    a" +// , "

    " +// , "
    " +// , "" +// )); +// fxt.Init_para_n_(); +// } +// @Test public void Tb_tb() { +// fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last +// ( "{|id='1'" +// , "{|id='2'" +// , "|-id='3'" +// , "|a" +// , "|}" +// , "|}"), String_.Concat_lines_nl_skip_last +// ( "" +// , " " +// , " " +// , " " +// , "
    a" +// , "
    " +// , "" +// )); +// } +// @Test public void Tb_tb_2() { +// fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last +// ( "{|id='1'" +// , "{|id='2' " +// , "|a" +// , "
    " +// , "|}" +// , "|}"), String_.Concat_lines_nl_skip_last +// ( "" +// , " " +// , " " +// , " " +// , "
    a" +// , "
    " +// , "" +// )); +// } diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__dangling_tst.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__dangling_tst.java index a27517de8..3040f4583 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__dangling_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__dangling_tst.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_tblw_wkr__dangling_tst { + @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 Dangling_tb_in_xnde() {// PURPOSE: dangling tblw incorrectly auto-closed by ; PAGE:en.w:Atlanta_Olympics; DATE:2014-03-18 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "

    " + , "{|" + , "|-" + , "|" + , "{|" + , "|-" + , "|a" + , "|}" + , "
    " + , "b" + ) + , String_.Concat_lines_nl + ( "
    " + , "" + , " " + , " " + , " " + , "
    " + , " " + , " " + , " " + , " " + , "
    a" + , "
    " + , "" // TIDY.dangling: tidy will correct dangling node; DATE:2014-07-22 + , "" + , "

    b" + , "

    " + , "
    " + , "

    " + )); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__double_pipe_tst.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__double_pipe_tst.java index a27517de8..9354c205c 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__double_pipe_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__double_pipe_tst.java @@ -13,3 +13,94 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_tblw_wkr__double_pipe_tst { + @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 No_tblw() { // PURPOSE: if || has no tblw, treat as lnki; none; DATE:2014-05-06 + fxt.Test_parse_page_all_str("[[A||b|c]]", String_.Concat_lines_nl_skip_last + ( "

    b|c" // NOTE: technically this should be "|b|c", but difficult to implement; DATE:2014-05-06 + , "

    " + , "" + )); + } + @Test public void Lnki_nth() { // PURPOSE: if || is nth pipe, then treat as lnki; PAGE:en.w:Main_Page;de.w:Main_Page; DATE:2014-05-06 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|[[File:A.png|b||c]]" + , "|}" + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    \"c\"" + , "
    " + , "" + ) + ); + } + @Test public void Lnki_list_1st() { // PURPOSE: if || is 1st pipe, but inside list, then treat as lnki; EX:w:Second_Boer_War; DATE:2014-05-05 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|" + , "*[[A||b]]" + , "|}" + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    " + , "" + , "
      " + , "
    • b" // NOTE: technically this should be "|b", but difficult to implement; DATE:2014-05-06 + , "
    • " + , "
    " + , "
    " + , "" + ) + ); + } + @Test public void Double_bang_lnki() { // PURPOSE: do not treat !! as tblw; PAGE:en.w:Pink_(singer); DATE:2014-06-25 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|" + , "[[A!!b]]" + , "|}" + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    " + , "" + , "

    A!!b" + , "

    " + , "
    " + , "" + ) + ); + } + @Test public void Double_bang_list() { // PURPOSE: do not treat !! as tblw; PAGE:en.w:Wikipedia:Featured_picture_candidates; DATE:2014-10-19 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "* a !! b" + , "|}" + ) , String_.Concat_lines_nl_skip_last + ( "" + , "
      " + , "
    • a !! b" + , "
    • " + , "
    " + , " " + , " " + , " " + , "
    " + , "
    " + , "

    " // NOTE:

    is incorrect, but benign + ) + ); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__errs_tst.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__errs_tst.java index a27517de8..fb0e0830f 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__errs_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__errs_tst.java @@ -13,3 +13,80 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_tblw_wkr__errs_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Err_row_empty() { + fxt.Test_parse_page_wiki("{|\n|-\n|-\n|a\n|}" + , fxt.tkn_tblw_tb_(0, 14).Subs_ + ( fxt.tkn_tblw_tr_(2, 5) + , fxt.tkn_tblw_tr_(5, 11).Subs_ + ( fxt.tkn_tblw_td_(8, 11).Subs_(fxt.tkn_txt_(10, 11), fxt.tkn_para_blank_(12)) + )) + ); + } + @Test public void Err_row_trailing() { + fxt.Test_parse_page_wiki("{|\n|-\n|a\n|-\n|}" + , fxt.tkn_tblw_tb_(0, 14).Subs_ + ( fxt.tkn_tblw_tr_(2, 8).Subs_ + ( fxt.tkn_tblw_td_(5, 8).Subs_(fxt.tkn_txt_(7, 8), fxt.tkn_para_blank_(9)) + )) + ); + } + @Test public void Err_caption_after_tr() { + fxt.Test_parse_page_wiki("{|\n|-\n|+a\n|}" + , fxt.tkn_tblw_tb_(0, 12).Caption_count_(1).Subs_ + ( fxt.tkn_tblw_tr_(2, 5) + , fxt.tkn_tblw_tc_(5, 9).Subs_(fxt.tkn_txt_(8, 9), fxt.tkn_para_blank_(10))) + ); + } + @Test public void Err_caption_after_td() { + fxt.Init_log_(Xop_tblw_log.Caption_after_td).Test_parse_page_wiki("{|\n|-\n|a\n|+b\n|}" + , fxt.tkn_tblw_tb_(0, 15).Caption_count_(1).Subs_ + ( fxt.tkn_tblw_tr_(2, 8).Subs_ + ( fxt.tkn_tblw_td_(5, 8).Subs_(fxt.tkn_txt_(7, 8))) + , fxt.tkn_tblw_tc_(8, 12).Subs_(fxt.tkn_txt_(11, 12), fxt.tkn_para_blank_(13))) + ); + } + @Test public void Err_caption_after_tc() { + fxt.Init_log_(Xop_tblw_log.Caption_after_tc).Test_parse_page_wiki("{|\n|+a\n|+b\n|}" + , fxt.tkn_tblw_tb_(0, 13).Caption_count_(2).Subs_ + ( fxt.tkn_tblw_tc_(2, 6).Subs_(fxt.tkn_txt_( 5, 6)) + , fxt.tkn_tblw_tc_(6, 10).Subs_(fxt.tkn_txt_( 9, 10), fxt.tkn_para_blank_(11))) + ); + } + @Test public void Err_row_auto_opened() { + fxt.Test_parse_page_wiki("{|\n|a\n|}" + , fxt.tkn_tblw_tb_(0, 8).Subs_ + ( fxt.tkn_tblw_tr_(2, 5).Subs_ + ( fxt.tkn_tblw_td_(2, 5).Subs_(fxt.tkn_txt_(4, 5), fxt.tkn_para_blank_(6)) + ))); + } + @Test public void Err_caption_auto_closed() { + fxt.Test_parse_page_wiki("{|\n|+a\n|b\n|}" + , fxt.tkn_tblw_tb_(0, 12).Caption_count_(1).Subs_ + ( fxt.tkn_tblw_tc_(2, 6).Subs_(fxt.tkn_txt_(5, 6)) + , fxt.tkn_tblw_tr_(6, 9).Subs_ + ( fxt.tkn_tblw_td_(6, 9).Subs_(fxt.tkn_txt_(8, 9),fxt.tkn_para_blank_(10)) + ))); + } + @Test public void Err_Atrs_dumped_into_text() { // PURPOSE: [[Prawn]] and {{Taxobox}} was dumping text + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|-id='a'" + , "|b" + , "|}" + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    b" + , "
    " + , "" + ) + ); + } +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__nested_tst.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__nested_tst.java index a27517de8..0413d28d3 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__nested_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__nested_tst.java @@ -13,3 +13,151 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_tblw_wkr__nested_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Basic() { + fxt.Test_parse_page_wiki(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|" + , "{|" + , "|-" + , "|a" + , "|}" + , "|b" + , "|}" + ) + , fxt.tkn_tblw_tb_(0, 25).Subs_ + ( fxt.tkn_tblw_tr_(2, 22).Subs_ + ( fxt.tkn_tblw_td_(5, 19).Subs_ + ( fxt.tkn_tblw_tb_(7, 19).Subs_ + ( fxt.tkn_tblw_tr_(10, 16).Subs_ + ( fxt.tkn_tblw_td_(13, 16).Subs_(fxt.tkn_txt_(15, 16), fxt.tkn_para_blank_(17)) + ) + ) + , fxt.tkn_para_blank_(20) + ) + , fxt.tkn_tblw_td_(19, 22).Subs_(fxt.tkn_txt_(21, 22), fxt.tkn_para_blank_(23)) + ) + ) + ); + } + @Test public void Leading_ws() { + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|id='a'" + , "|-" + , "|a" + , "|-" + , "|id='b'|" + , " {|id='c'" + , " |-" + , " |d" + , " |}" + , "|}" + ) + , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , " " + , " " + , "
    a" + , "
    " + , " " + , " " + , " " + , " " + , "
    d" + , "
    " + , "
    " + , "" + ) + ); + fxt.Init_para_n_(); + } + @Test public void Tblx_tblw() { // PURPOSE: if followed by {|, ignore 2nd table; EX: en.b:Wikibooks:Featured_books; DATE:2014-02-08 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "
    " + , "{| cellspacing=\"0\"" + , "|a" + , "|}" + , "
    " + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + )); + } + @Test public void Caption_and_tblw() { // TIDY: don't try to fix sequence; PAGE:es.w:Sevilla; DATE:2014-06-29 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|+" + , "{|" + , "|}" + , "|}"), String_.Concat_lines_nl_skip_last + ( "
    " + , " " + , "
    " + , " " + , " " + , " " + , " " + , "
    " + , "
    " + , "
    " + , "" + )); + } + @Test public void Tb_tr_tb() { // PURPOSE: if , auto-create " + , "
    ; EX:w:Paris; DATE:2014-03-18 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "{|" + , "|}" + , "|}"), String_.Concat_lines_nl_skip_last + ( "" + , " " + , "
    " + , " " + , " " + , " " + , "
    " + , "
    " + , "
    " + , "" + )); + } +// @Test public void Nested_tbl_missing() { // PURPOSE: nested table not rendering properly; EX:ar.s:; DATE:2014-03-18 +// fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last +// ( "{|" +// , "|-" +// , "{|" +// , "|-" +// , "|}" +// , "| width='50%' | a" +// , "|}" +// ), String_.Concat_lines_nl_skip_last +// ( "" +// , " " +// , " " +// , " " +// , " " +// , "
    a" +// , " [[b|c" +// , "
    " +// , "" +// , "

    d" +// , "

    " +// )); +// } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__para_tst.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__para_tst.java index a27517de8..a2dac3194 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__para_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__para_tst.java @@ -13,3 +13,163 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_tblw_wkr__para_tst { + @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 Para() { // PURPOSE: para causing strange breaks; SEE:[[John F. Kennedy]] and "two Supreme Court appointments" + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "

    " + , "|a" + , "

    " + , "|}" + ) , String_.Concat_lines_nl_skip_last + ( "

    " + , " " + , " " + , " " + , "
    a" + , "

    " + , "
    " + , "" + ) + ); + } + @Test public void Nl() { // PURPOSE: para causing strange breaks; SEE:[[John F. Kennedy]] and "two Supreme Court appointments" + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "!a" + , "" + , "|-" + , "|}" + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "" + ) + ); + } + @Test public void Unnecessary_para() { // PURPOSE: tblw causes unnecessary

    ; home/wiki/Dashboard/Image_databases; DATE:2014-02-20 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|" + , "a
    " + , "b" + , "|" + , "c
    " + , "d" + , "|}" + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , "
    " + , "" + , "

    a
    " + , "b" + , "

    " + , "
    " + , "" + , "

    c
    " + , "d" + , "

    " + , "
    " + , "" + ) + ); + } + @Test public void Ws_leading() { // PAGE:en.w:AGPLv3 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , " !a" + , " !b" + , "|}" + ) + , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , "
    a" + , " b" + , "
    " + , "" + ) + ); + } + @Test public void Ws_th_2() { // "\n\s!" should still be interpreted as tblw; s.w:Manchester; DATE:2014-02-14 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|!style='color:red'|a" + , " !style=\"color:blue\"|b" + , "|}" + ) + , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , "
    a" + , " b" + , "
    " + , "" + ) + ); + } + @Test public void Ws_th_3() { // "\n\s!" and "!!" breaks tblw; ru.w:Храмы_Санкт-Петербурга (List of churches in St Petersburg); DATE:2014-02-20 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , " ! id='1' | a !! id='2' | b" + , "|}" + ) + , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , "
    a " + , " b" + , "
    " + , "" + ) + ); + } + @Test public void Tblw_td2_should_not_create_ws() { // PURPOSE: a||b -> a\n||b; EX:none;discovered during luaj test; DATE:2014-04-14 + fxt.Test_parse_page_wiki_str("a||b", "

    a||b\n

    "); + } + @Test public void Para_on_tblw() { // PURPOSE:table following link should automatically add para around link; PAGE:en.w:Template_engine_(web) DATE:2017-04-08 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "[[A]] b" + , "{|" + , "|-" + , "|c" + , "|}" + ) + , String_.Concat_lines_nl_skip_last + ( "

    A b" // NOTE: previously,

    was not included; now added for TRAILING_TBLW fix; DATE:2017-04-08 + , "

    " + , "" + , " " + , " " + , " " + , "
    c" + , "
    " + , "" + ) + ); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__tblx_tst.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__tblx_tst.java index a27517de8..024e9f775 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__tblx_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__tblx_tst.java @@ -13,3 +13,140 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_tblw_wkr__tblx_tst { + @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 Ignore_td() { // PURPOSE: do not parse pipe as td if in ; EX:ru.w:Сочи; DATE:2014-02-22 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "
    " + , " " + , " " + , " " + , "
    a" + , "| b" + , "
    " + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "| b" + , "
    " + , "" + ) + ); + } + @Test public void Ignore_tr() { // PURPOSE: do not parse "\n|-", "\n!" if in ; EX:s.w:Uranus; DATE:2014-05-05 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "
    " + , " " + , " " + , " " + , "
    a" + , "|-" + , "! b" + , "| c" + , "
    " + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a" + , "" + , "

    |-" + , "! b" + , "| c" + , "

    " + , "
    " + , "" + ) + ); + } + @Test public void Tblw_tblx_tblw_fails() { // PURPOSE: {| -> -> \n| was not rendering as " + , "" + , " " + , " fragment within lnki should be ignored; PAGE:en.w:Aargau DATE:2016-08-14 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "
    ; PAGE:en.w:Paris#Demographics; DATE:2014-03-18 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|a" + , "
    " + , "" + , "" + , "|c" + , "
    b
    " + , "|}" + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , " " + , " " + , "
    a" + , "
    " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , "
    b" + , "
    c" + , "
    " + , "
    " + )); + } + @Test public void Auto_tr_after_td() { // PURPOSE: "
    \n|" -> "
    "; PAGE:fi.w:Salibandyn_maailmanmestaruuskilpailut_2012 DATE:2015-09-07 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "
    a" + , " " + , "|b" + , "|}" + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , " " // inserted by transition from " + , " " + , "
    a" + , " " // NOTE: dangling from above just gets auto-closed; no logic in tblw_wkr to actually remove it + , "
    to "\n|" + , " b" + , "
    " + , "" + ) + ); + } + @Test public void Ignore_tr_in_lnki() { // PURPOSE:
    " + , "" + , "]]" + , "" + , "" + , "
    [[A|B
    t_1
    " + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , "" + , " " + , " " + , "
    B" + , "
    t_1" + , "
    " + )); + } +} 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 a27517de8..1aaeb9dcf 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 @@ -13,3 +13,115 @@ 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.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(); + @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 + ( "{|" + , "a" + , "|b" + , "|-" + , "|c" + , "|}" + ) + , String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , "
    a" + , "
    b" + , "
    c" + , "
    " + )); + } + @Test public void Atrs_defect() { // PURPOSE: < in atrs was causing premature termination; PAGE:en.w:Wikipedia:List of hoaxes on Wikipedia + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|id=\"a" + , " " + , " a" + , " " + , " " + , "" + , "" + )); + } + @Test public void Broken_lnki() { // PURPOSE: broken lnki was not closing table properly; PAGE:en.w:Wikipedia:Changing_attribution_for_an_edit; DATE:2014-03-16 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|a" + , "|[[b|c" + , "|}" + , "d" + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , "
    a" + , " [[b|c" + , "
    " + , "" + , "

    d" + , "

    " + )); + } + @Test public void Broken_lnki_2() { // PURPOSE: variation on above; PAGE:hr.b:Knjiga_pojmova_u_zrakoplovstvu/Kratice_u_zrakoplovstvu/S; DATE:2014-09-05 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "| [[A | b" + , "|-" + , "| B" + , "|}" + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , " " + , " " + , "
    [[A | b" + , "
    B" + , "
    " + )); + } + @Test public void Tr_with_pipe_ignores_content() { // PURPOSE: "|-" followed by "|" ignores rest of content; EX: {|\n|-|a\n|} PAGE:lv.w:Starptautiska_kosmosa_stacija; DATE:2015-11-21 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|- |a" + , "|}" + ), String_.Concat_lines_nl_skip_last + ( "" + , "
    " + )); + } + @Test public void Tr_with_pipe_should_ignore() { // PURPOSE: ignore sequences like "\n|- ||"; PAGE: nl.w:Tabel_van_Belgische_gemeenten; DATE:2015-12-03 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|- ||" + , "|a|b" + , "|}" + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    b" + , "
    " + )); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_ws_itm.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_ws_itm.java index a27517de8..52ff1c08c 100644 --- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_ws_itm.java +++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_ws_itm.java @@ -13,3 +13,50 @@ 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.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; +import gplx.xowa.parsers.xndes.*; +public class Xop_tblw_ws_itm { + public byte Tblw_type() {return tblw_type;} private byte tblw_type; + public int Hook_len() {return hook_len;} private int hook_len; + public Xop_tblw_ws_itm(byte tblw_type, int hook_len) {this.tblw_type = tblw_type; this.hook_len = hook_len;} + + public static final byte Type_tb = Xop_tblw_wkr.Tblw_type_tb, Type_te = Xop_tblw_wkr.Tblw_type_te, Type_tr = Xop_tblw_wkr.Tblw_type_tr, Type_tc = Xop_tblw_wkr.Tblw_type_tc + , Type_th = Xop_tblw_wkr.Tblw_type_th, Type_td = Xop_tblw_wkr.Tblw_type_td, Type_nl = 16, Type_xnde = 17; + public static Btrie_slim_mgr trie_() {// MW.REF:Parser.php|doBlockLevels + Btrie_slim_mgr rv = Btrie_slim_mgr.cs(); + trie_itm(rv, Type_tb, Xop_tblw_lxr_ws.Hook_tb); + trie_itm(rv, Type_te, Xop_tblw_lxr_ws.Hook_te); + trie_itm(rv, Type_tr, Xop_tblw_lxr_ws.Hook_tr); + trie_itm(rv, Type_th, Xop_tblw_lxr_ws.Hook_th); + trie_itm(rv, Type_tc, Xop_tblw_lxr_ws.Hook_tc); + trie_itm(rv, Type_td, Byte_ascii.Pipe_bry); + trie_itm(rv, Type_nl, Byte_ascii.Nl_bry); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__table); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__tr); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__td); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__th); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__blockquote); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__h1); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__h2); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__h3); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__h4); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__h5); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__h6); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__pre); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__p); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__div); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__hr); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__li); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__ul); + trie_itm_xnde(rv, Xop_xnde_tag_.Tag__ol); + return rv; + } + private static void trie_itm(Btrie_slim_mgr trie, byte type, byte[] bry) {trie.Add_obj(bry, new Xop_tblw_ws_itm(type, bry.length));} + private static void trie_itm_xnde(Btrie_slim_mgr trie, Xop_xnde_tag tag) { + byte[] tag_name = tag.Name_bry(); + int tag_name_len = tag_name.length; + trie.Add_obj(Bry_.Add(Bry_xnde_bgn, tag_name), new Xop_tblw_ws_itm(Type_xnde, tag_name_len)); + trie.Add_obj(Bry_.Add(Bry_xnde_end, tag_name), new Xop_tblw_ws_itm(Type_xnde, tag_name_len + 1)); + } private static byte[] Bry_xnde_bgn = new byte[] {Byte_ascii.Lt, Byte_ascii.Slash}, Bry_xnde_end = new byte[] {Byte_ascii.Lt}; +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_bldr.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_bldr.java index a27517de8..46762678d 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_bldr.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_bldr.java @@ -13,3 +13,192 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.miscs.*; +public class Arg_bldr { // TS + public boolean Bld(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_arg_wkr wkr, int wkr_typ, Xop_root_tkn root, Xop_tkn_itm tkn, int bgn_pos, int cur_pos, int loop_bgn, int loop_end, byte[] src) { + boolean ws_bgn_chk = true, colon_chk = false, itm_is_static = true, key_exists = false; int ws_bgn_idx = -1, ws_end_idx = -1, cur_itm_subs_len = 0, cur_nde_idx = -1; Arg_nde_tkn cur_nde = null; Arg_itm_tkn cur_itm = null; + int brack_count = 0; + Xop_tkn_itm eq_pending = null; + for (int i = loop_bgn; i < loop_end; i++) { // loop over subs between bookends; if lnki, all tkns between [[ and ]]; if tmpl, {{ and }} + Xop_tkn_itm sub = root.Subs_get(i); + int sub_pos_bgn = sub.Src_bgn_grp(root, i); + if (cur_nde == null) { + cur_nde = tkn_mkr.ArgNde(++cur_nde_idx, sub_pos_bgn); + brack_count = 0; key_exists = false; + } + if (cur_itm == null) { + cur_itm = tkn_mkr.ArgItm(sub_pos_bgn, -1); + itm_is_static = ws_bgn_chk = true; cur_itm_subs_len = 0; ws_bgn_idx = ws_end_idx = -1; + if (eq_pending != null) { // something like "A==B" encountered; zh.w:Wikipedia:条目评选; DATE:2014-08-27 + eq_pending.Src_end_(eq_pending.Src_end() -1); // remove an "=" EX:"A==B" -> "A","=","=B" + cur_itm.Subs_add_grp(eq_pending, root, i); cur_itm_subs_len++; // add the tkn to cur_itm + eq_pending = null; + } + } + switch (sub.Tkn_tid()) { + case Xop_tkn_itm_.Tid_ignore: // comment or *include* tkn; mark itm as non_static for tmpl (forces re-eval) + switch (wkr_typ) { + case Xop_arg_wkr_.Typ_tmpl: + case Xop_arg_wkr_.Typ_prm: + itm_is_static = false; + break; + } + break; + case Xop_tkn_itm_.Tid_para: // NOTE: para can appear in following: [[File:A.png| \n 40px]]; EX: w:Supreme_Court_of_the_United_States; DATE:2014-04-05 + case Xop_tkn_itm_.Tid_newLine: case Xop_tkn_itm_.Tid_space: case Xop_tkn_itm_.Tid_tab: // whitespace + if (ws_bgn_chk) ws_bgn_idx = cur_itm_subs_len; // definite ws at bgn; set ws_bgn_idx, and keep setting until text tkn reached; handles mixed sequence of \s\n\t where last tkn should be ws_bgn_idx + else {if (ws_end_idx == -1) ws_end_idx = cur_itm_subs_len;}; // possible ws at end; may be overriden later; see AdjustWsForTxtTkn + break; + case Xop_tkn_itm_.Tid_colon: + if (wkr_typ == Xop_arg_wkr_.Typ_tmpl) { // treat colons as text; tmpl will do its own : parsing for 1st arg; NOTE: must do ws check else 2nd colon will break; EX: "{{#ifeq: :|a|b|c}}"; DATE:2013-12-10 + if (ws_bgn_chk) ws_bgn_chk = false; else ws_end_idx = -1; // INLINE: AdjustWsForTxtTkn + } + else { + if (cur_nde_idx == 0 && !colon_chk) { // if 1st arg, mark colon pos; needed for lnki; EX: [[Category:A]]; {{#ifeq:1}} + colon_chk = true; + cur_nde.Arg_colon_pos_(sub_pos_bgn); + } + } + break; + case Xop_tkn_itm_.Tid_brack_bgn: ++brack_count; if (ws_bgn_chk) ws_bgn_chk = false; else ws_end_idx = -1; // INLINE: AdjustWsForTxtTkn + break; + case Xop_tkn_itm_.Tid_brack_end: --brack_count; if (ws_bgn_chk) ws_bgn_chk = false; else ws_end_idx = -1; // INLINE: AdjustWsForTxtTkn + break; + case Xop_tkn_itm_.Tid_eq: + if (wkr_typ == Xop_arg_wkr_.Typ_tmpl && brack_count > 0) {} + else if (wkr_typ == Xop_arg_wkr_.Typ_prm) {} // always ignore for prm + else { + if ( cur_nde_idx != 0 // if 1st arg, treat equal_tkn as txt_tkn; i.e.: eq should not be used to separate key/val + && cur_nde.Eq_tkn() == Xop_tkn_null.Null_tkn // only mark key if key is not set; handle multiple-keys; EX: {{name|key1=b=c}}; DATE:2014-02-09 + ) { + Xop_eq_tkn sub_as_eq = (Xop_eq_tkn)sub; + int sub_as_eq_len = sub_as_eq.Eq_len(); + boolean eq_is_spr + = sub_as_eq_len == 1 // eq with len of 1 are considered separators; MW.REF:Preprocessor_DOM.php|preprocessToXml; "if ( $count == 1 && $findEquals )" PAGE:en.w:Wikipedia:Picture_of_the_day/June_2014; DATE:2014-07-21 + || ( cur_itm.Subs_len() > 0 // or eq.len > 1 that occur later in itm; EX: a==b; zh.w:Wikipedia:条目评选; DATE:2014-08-27 + && cur_itm.Subs_get(0).Tkn_tid() != Xop_tkn_itm_.Tid_eq // and 1st tkn is not ==; EX:==a==; 2nd == should not be eq b/c 1st == "deactivates" nde; DATE:2014-08-27 + ); + if (eq_is_spr) { + if (sub_as_eq_len == 1) // =.len == 1 + cur_nde.Eq_tkn_(sub); // set as eq tkn + else // =.len > 1 + eq_pending = sub; // do not set as eq tkn; note that Eq_tkn exists for bookkeeping and is not printed out, + key_exists = true; + Arg_itm_end(ctx, cur_nde, cur_itm, ws_bgn_idx, ws_end_idx, cur_itm_subs_len, sub_pos_bgn, wkr_typ, key_exists, true, itm_is_static, src, cur_nde_idx); + cur_nde.Key_tkn_(cur_itm); + cur_itm = null; + continue; // do not add tkn to cur_itm + } + } + if (ws_bgn_chk) ws_bgn_chk = false; else ws_end_idx = -1; // INLINE: AdjustWsForTxtTkn + break; + } + break; + case Xop_tkn_itm_.Tid_pipe: + if (cur_nde_idx == 0 + && ws_bgn_chk + && !colon_chk + && wkr_typ == Xop_arg_wkr_.Typ_tmpl + ) return false; // 1st arg, but no name; EX: "{{|a}}", "{{ }}"; disregard if lnki, since "[[|a]]" is valid + if (wkr_typ == Xop_arg_wkr_.Typ_tmpl && brack_count > 0) { + break; + } + else { + Arg_itm_end(ctx, cur_nde, cur_itm, ws_bgn_idx, ws_end_idx, cur_itm_subs_len, sub_pos_bgn, wkr_typ, key_exists, false, itm_is_static, src, cur_nde_idx); + cur_nde.Val_tkn_(cur_itm); + if (!wkr.Args_add(ctx, src, tkn, cur_nde, cur_nde_idx)) return false; // NOTE: if invalid, exit now; lnki_wkr expects false if any argument is invalid; DATE:2014-06-06 + cur_nde = null; cur_itm = null; key_exists = false; // reset + continue; // do not add tkn to cur_itm + } + case Xop_tkn_itm_.Tid_tmpl_prm: // nested prm (3 {) or invk (2 {); mark itm_is_static = false and treat tkn as txt + case Xop_tkn_itm_.Tid_tmpl_invk: + itm_is_static = false; + if (ws_bgn_chk) ws_bgn_chk = false; else ws_end_idx = -1; // INLINE: AdjustWsForTxtTkn + break; + case Xop_tkn_itm_.Tid_xnde: + Xop_xnde_tkn sub_as_xnde = (Xop_xnde_tkn)sub; + switch (sub_as_xnde.Tag().Id()) { + case Xop_xnde_tag_.Tid__noinclude: case Xop_xnde_tag_.Tid__includeonly: case Xop_xnde_tag_.Tid__onlyinclude: + itm_is_static = false; + break; + } + if (ws_bgn_chk) ws_bgn_chk = false; else ws_end_idx = -1; // INLINE: AdjustWsForTxtTkn + break; + default: + if (ws_bgn_chk) ws_bgn_chk = false; else ws_end_idx = -1; // INLINE: AdjustWsForTxtTkn + break; + } + cur_itm.Subs_add_grp(sub, root, i); cur_itm_subs_len++; + } + if (brack_count > 0) return false; + if (cur_nde == null) // occurs when | is last tkn; EX: {{name|a|}}; + cur_nde = tkn_mkr.ArgNde(++cur_nde_idx, bgn_pos); + if (cur_itm == null) { // occurs when = is last tkn; EX: {{name|a=}}; + cur_itm = tkn_mkr.ArgItm(bgn_pos, -1); + itm_is_static = ws_bgn_chk = true; cur_itm_subs_len = 0; ws_bgn_idx = ws_end_idx = -1; key_exists = false; + } + Arg_itm_end(ctx, cur_nde, cur_itm, ws_bgn_idx, ws_end_idx, cur_itm_subs_len, bgn_pos, wkr_typ, key_exists, false, itm_is_static, src, cur_nde_idx); + cur_nde.Val_tkn_(cur_itm); + return wkr.Args_add(ctx, src, tkn, cur_nde, cur_nde_idx); + } + private void Arg_itm_end(Xop_ctx ctx, Arg_nde_tkn nde, Arg_itm_tkn itm, int ws_bgn_idx, int ws_end_idx, int subs_len, int lxr_bgn, int wkr_typ, boolean key_exists, boolean cur_itm_is_key, boolean itm_is_static, byte[] src, int arg_idx) { + // PURPOSE: mark tkns Ignore; find dat_bgn, dat_end + int dat_bgn = itm.Src_bgn(), dat_end = lxr_bgn; boolean trim = false; + // trim ws at bgn + boolean wkr_is_not_prm = wkr_typ != Xop_arg_wkr_.Typ_prm; + if (ws_bgn_idx != -1) { // ignore ws if (ws_found_at_bgn && (tmpl_arg || (lnki_arg && key))); lnki_arg && val does not ignore at bgn; EX: [[alt= a b c]] -> " a b c" + switch (wkr_typ) { + case Xop_arg_wkr_.Typ_prm : trim = arg_idx == 0; break; + case Xop_arg_wkr_.Typ_tmpl: trim = key_exists || arg_idx == 0; break; + case Xop_arg_wkr_.Typ_lnki: trim = cur_itm_is_key || !key_exists; break; // NOTE: trim if "a= b"; skip if " a=b" or " a" + } + if (trim) { + for (int i = 0; i <= ws_bgn_idx; i++) { + Xop_tkn_itm sub_tkn = itm.Subs_get(i); // NOTE: tknTypeId should be space, newline, or tab + if (wkr_is_not_prm) + sub_tkn.Ignore_y_grp_(ctx, itm, i); // mark tkn ignore unless wkr is prm; SEE:NOTE_1 + if (i == ws_bgn_idx) + dat_bgn = sub_tkn.Src_end_grp(itm, i); // if last_tkn, set dat_bgn to bgn + } + } + } + // trim ws at end + if (ws_end_idx != -1) { // ignore ws if (ws_found_at_end && (tmpl_arg || (lnki_arg && val))); lnki_arg && key does not ignore at end; EX: [[alt =a b c]] -> unrecognized nde ("alt ") + trim = false; + switch (wkr_typ) { + case Xop_arg_wkr_.Typ_prm : trim = arg_idx == 0; break; + case Xop_arg_wkr_.Typ_tmpl: trim = key_exists || arg_idx == 0; break; // NOTE: never set "trim = true"; PAGE:fr.w:Histoire_de_la_marine_française_sous_Louis_XV_et_Louis_XVI DATE:2015-11-17 + case Xop_arg_wkr_.Typ_lnki: trim = !cur_itm_is_key; break; + } + if (trim) { + for (int i = ws_end_idx; i < subs_len; i++) { + Xop_tkn_itm sub_tkn = itm.Subs_get(i); // NOTE: tknTypeId should be space, newline, or tab + if (wkr_is_not_prm) + sub_tkn.Ignore_y_grp_(ctx, itm, i); // mark tkn ignore unless wkr is prm; SEE:NOTE_1 + if (i == ws_end_idx) + dat_end = sub_tkn.Src_bgn_grp(itm, i); // if 1st_tkn; set dat_end to bgn + } + } + } + itm.Src_end_(lxr_bgn); nde.Src_end_(lxr_bgn); // NOTE: src_end is lxr_bgn; EX: {{a}} has src_end at 3; lxr_bgn for }} + itm.Dat_rng_(dat_bgn, dat_end); // always set dat, even if itm has dynamic parts; EX: {{{ a{{{1}}}b }}} has 4,13, not 3,14 (ignore ws) + +// if (itm_is_static) +// itm.Dat_ary_(dat_end == dat_bgn ? Bry_.Empty : Bry_.Mid(src, dat_bgn, dat_end)); + itm.Itm_static_(itm_is_static); + } + public static final Arg_bldr Instance = new Arg_bldr(); Arg_bldr() {} +} +/* +NOTE_1:mark tkn ignore unless wkr is prm; +need to trim ws when parsing prm_idx/prm_key, but need to preserve raw for output +EX: {{{ 1 }}} +. parsing will set prm_idx = 1 (ws is ignored) +. when calling with {{test|a}} -> "a" +. when calling with {{test}} -> "{{{ 1 }}}" +so basically, "trim" to get Dat_bgn and Dat_end, but do not mark tkn as ignore +alternative would be to set trim = false and then have prm_idx/prm_key do trim; however that would +1) ... involve duplication of trim code +2) ... be less performant (iterating subs again) +*/ \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_itm_tkn.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_itm_tkn.java index a27517de8..bdb2dc803 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_itm_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_itm_tkn.java @@ -13,3 +13,17 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public interface Arg_itm_tkn extends Xop_tkn_itm { + int Dat_bgn(); + int Dat_end(); + Arg_itm_tkn Dat_end_(int v); + Arg_itm_tkn Dat_rng_(int bgn, int end); + Arg_itm_tkn Dat_rng_ary_(byte[] src, int bgn, int end); + byte[] Dat_ary(); + Arg_itm_tkn Dat_ary_(byte[] dat_ary); + byte[] Dat_to_bry(byte[] src); + boolean Dat_ary_had_subst(); void Dat_ary_had_subst_y_(); + byte Itm_static(); Arg_itm_tkn Itm_static_(boolean v); + Arg_itm_tkn Subs_add_ary(Xop_tkn_itm... ary); +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_itm_tkn_base.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_itm_tkn_base.java index a27517de8..04748debf 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_itm_tkn_base.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_itm_tkn_base.java @@ -13,3 +13,39 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Arg_itm_tkn_base extends Xop_tkn_itm_base implements Arg_itm_tkn { + public Arg_itm_tkn_base() {} // for mock + public Arg_itm_tkn_base(int bgn, int end) {this.Tkn_ini_pos(false, bgn, end); dat_bgn = bgn;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_arg_itm;} + public int Dat_bgn() {return dat_bgn;} private int dat_bgn = -1; + public int Dat_end() {return dat_end;} public Arg_itm_tkn Dat_end_(int v) {dat_end = v; return this;} private int dat_end = -1; + public byte[] Dat_to_bry(byte[] src) {return Bry_.Len_eq_0(dat_ary) ? Bry_.Mid(src, dat_bgn, dat_end) : dat_ary;} + public byte[] Dat_ary() {return dat_ary;} private byte[] dat_ary = Bry_.Empty; + public Arg_itm_tkn Dat_ary_(byte[] dat_ary) {this.dat_ary = dat_ary; return this;} + public Arg_itm_tkn Dat_rng_(int bgn, int end) {dat_bgn = bgn; dat_end = end; return this;} + public boolean Dat_ary_had_subst() {return dat_ary_had_subst;} public void Dat_ary_had_subst_y_() {dat_ary_had_subst = true;} private boolean dat_ary_had_subst = false; + public Arg_itm_tkn Dat_rng_ary_(byte[] src, int bgn, int end) { + dat_bgn = bgn; dat_end = end; + dat_ary = Bry_.Mid(src, bgn, end); + return this; + } + public byte Itm_static() {return itm_static;} public Arg_itm_tkn Itm_static_(boolean v) {itm_static = v ? Bool_.Y_byte : Bool_.N_byte; return this;} private byte itm_static = Bool_.__byte; + @Override public void Tmpl_compile(Xop_ctx ctx, byte[] src, Xot_compile_data prep_data) { + int subs_len = this.Subs_len(); + for (int i = 0; i < subs_len; i++) { + Xop_tkn_itm sub = this.Subs_get(i); + sub.Tmpl_compile(ctx, src, prep_data); + } + } + @Override public boolean Tmpl_evaluate(Xop_ctx ctx, byte[] src, Xot_invk caller, Bry_bfr bfr) { + if (dat_ary == Bry_.Empty) { + int subs_len = this.Subs_len(); + for (int i = 0; i < subs_len; i++) + this.Subs_get(i).Tmpl_evaluate(ctx, src, caller, bfr); + } + else bfr.Add(dat_ary); + return true; + } + public Arg_itm_tkn Subs_add_ary(Xop_tkn_itm... ary) {for (Xop_tkn_itm sub : ary) Subs_add(sub); return this;} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_itm_tkn_null.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_itm_tkn_null.java index a27517de8..6dad133a2 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_itm_tkn_null.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_itm_tkn_null.java @@ -13,3 +13,16 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.htmls.*; +public class Arg_itm_tkn_null extends Xop_tkn_null implements Arg_itm_tkn { public int Dat_bgn() {return -1;} + public int Dat_end() {return -1;} public Arg_itm_tkn Dat_end_(int v) {return this;} + public Arg_itm_tkn Dat_rng_(int bgn, int end) {return this;} + public Arg_itm_tkn Dat_rng_ary_(byte[] src, int bgn, int end) {return this;} + public byte[] Dat_ary() {return Bry_.Empty;} public Arg_itm_tkn Dat_ary_(byte[] dat_ary) {return this;} + public byte[] Dat_to_bry(byte[] src) {return Bry_.Empty;} + public Arg_itm_tkn Subs_add_ary(Xop_tkn_itm... ary) {return this;} + public boolean Dat_ary_had_subst() {return false;} public void Dat_ary_had_subst_y_() {} + public byte Itm_static() {return Bool_.__byte;} public Arg_itm_tkn Itm_static_(boolean v) {return this;} + public static final Arg_itm_tkn_null Null_arg_itm = new Arg_itm_tkn_null(); Arg_itm_tkn_null() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_nde_tkn.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_nde_tkn.java index a27517de8..54459bf8f 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_nde_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_nde_tkn.java @@ -13,3 +13,29 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Arg_nde_tkn extends Xop_tkn_itm_base { + public Arg_nde_tkn() {} // for mock + public Arg_nde_tkn(int arg_idx, int bgn) {this.arg_idx = arg_idx; this.Tkn_ini_pos(false, bgn, -1);} private int arg_idx; + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_arg_nde;} + public byte Arg_compiled() {return arg_compiled;} public Arg_nde_tkn Arg_compiled_(byte v) {arg_compiled = v; return this;} private byte arg_compiled = Bool_.__byte; + public int Arg_colon_pos() {return arg_colon_pos;} public Arg_nde_tkn Arg_colon_pos_(int v) {arg_colon_pos = v; return this;} private int arg_colon_pos = -1; + public Arg_itm_tkn Key_tkn() {return key_tkn;} public Arg_nde_tkn Key_tkn_(Arg_itm_tkn v) {key_tkn = v; return this;} Arg_itm_tkn key_tkn = Arg_itm_tkn_null.Null_arg_itm; + public Arg_itm_tkn Val_tkn() {return val_tkn;} public Arg_nde_tkn Val_tkn_(Arg_itm_tkn v) {val_tkn = v; return this;} Arg_itm_tkn val_tkn = Arg_itm_tkn_null.Null_arg_itm; + @gplx.Virtual public boolean KeyTkn_exists() {return key_tkn != Arg_itm_tkn_null.Null_arg_itm;} + public Xop_tkn_itm Eq_tkn() {return eq_tkn;} public Arg_nde_tkn Eq_tkn_(Xop_tkn_itm v) {eq_tkn = v; return this;} private Xop_tkn_itm eq_tkn = Xop_tkn_null.Null_tkn; + @Override public void Tmpl_fmt(Xop_ctx ctx, byte[] src, Xot_fmtr fmtr) {fmtr.Reg_arg(ctx, src, arg_idx, this);} + @Override public void Tmpl_compile(Xop_ctx ctx, byte[] src, Xot_compile_data prep_data) { + key_tkn.Tmpl_compile(ctx, src, prep_data); + eq_tkn.Tmpl_compile(ctx, src, prep_data); + val_tkn.Tmpl_compile(ctx, src, prep_data); + } + @Override public boolean Tmpl_evaluate(Xop_ctx ctx, byte[] src, Xot_invk caller, Bry_bfr bfr) { + key_tkn.Tmpl_evaluate(ctx, src, caller, bfr); + eq_tkn.Tmpl_evaluate(ctx, src, caller, bfr); + val_tkn.Tmpl_evaluate(ctx, src, caller, bfr); + return true; + } + public static final Arg_nde_tkn[] Ary_empty = new Arg_nde_tkn[0]; + public static final Arg_nde_tkn Null = new Arg_nde_tkn(-1, -1); +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_nde_tkn_mock.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_nde_tkn_mock.java index a27517de8..0c2fd78bd 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_nde_tkn_mock.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Arg_nde_tkn_mock.java @@ -13,3 +13,28 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.parsers.miscs.*; +public class Arg_nde_tkn_mock extends Arg_nde_tkn { public Arg_nde_tkn_mock(boolean arg_key_is_int, String k, String v) { + this.key_exists = !arg_key_is_int; + if (key_exists) + this.Key_tkn_(new Arg_itm_tkn_mock(k)); + this.Val_tkn_(new Arg_itm_tkn_mock(v)); + } + public Arg_nde_tkn_mock(String k, String v) { + key_exists = k != null; + if (key_exists) + this.Key_tkn_(new Arg_itm_tkn_mock(k)); + this.Val_tkn_(new Arg_itm_tkn_mock(v)); + } + @Override public boolean KeyTkn_exists() {return key_exists;} private boolean key_exists; +} +class Arg_itm_tkn_mock extends Arg_itm_tkn_base { + public Arg_itm_tkn_mock(String v) { + byte[] dat_ary = Bry_.new_u8(v); + this.Subs_add(new Xop_bry_tkn(-1, -1, dat_ary)); + this.Dat_ary_(dat_ary); + this.val = v; + } String val; + @Override public boolean Tmpl_evaluate(Xop_ctx ctx, byte[] src, Xot_invk caller, Bry_bfr bfr) {bfr.Add_str_u8(val); return true;} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Nowiki_escape_itm.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Nowiki_escape_itm.java index a27517de8..fe235ab12 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Nowiki_escape_itm.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Nowiki_escape_itm.java @@ -13,3 +13,56 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.langs.htmls.entitys.*; import gplx.xowa.parsers.amps.*; +public class Nowiki_escape_itm { + public Nowiki_escape_itm(byte[] src, byte[] trg) {this.src = src; this.trg = trg; this.src_adj = src.length - 1;} + private int src_adj; + public byte[] Src() {return src;} private byte[] src; + public byte[] Trg() {return trg;} private byte[] trg; + public static boolean Escape(Bry_bfr tmp_bfr, byte[] src, int bgn, int end) {// works by escaping all wtxt symbols so that wtxt parser does not hook into any of them + boolean dirty = false; + for (int i = bgn; i < end; i++) { + byte b = src[i]; + Object o = trie.Match_bgn_w_byte(b, src, i, end); + if (o == null) { + if (dirty) + tmp_bfr.Add_byte(b); + } + else { + if (!dirty) { + tmp_bfr.Add_mid(src, bgn, i); + dirty = true; + } + Nowiki_escape_itm itm = (Nowiki_escape_itm)o; + tmp_bfr.Add(itm.Trg()); + i += itm.src_adj; + } + } + return dirty; + } + + private static final Btrie_slim_mgr trie = New_trie(); + private static Btrie_slim_mgr New_trie() { + byte[] pre_bry = new byte[] {Byte_ascii.Nl, Byte_ascii.Space}; // NOTE: must go before New_trie + Btrie_slim_mgr rv = Btrie_slim_mgr.cs(); + New_trie_itm(rv, Byte_ascii.Lt_bry , Gfh_entity_trie.Str__xowa_lt); + New_trie_itm(rv, Byte_ascii.Brack_bgn_bry , Gfh_entity_trie.Str__xowa_brack_bgn); + New_trie_itm(rv, Byte_ascii.Brack_end_bry , Gfh_entity_trie.Str__xowa_brack_end);// PAGE:en.w: Tall_poppy_syndrome DATE:2014-07-23 + New_trie_itm(rv, Byte_ascii.Pipe_bry , Gfh_entity_trie.Str__xowa_pipe); + New_trie_itm(rv, Byte_ascii.Apos_bry , Gfh_entity_trie.Str__xowa_apos); // NOTE: for backward compatibility, use ' note that amp_wkr will turn ' -> ' but ' -> '; DATE:2014-07-03 + New_trie_itm(rv, Byte_ascii.Colon_bry , Gfh_entity_trie.Str__xowa_colon); + New_trie_itm(rv, Byte_ascii.Underline_bry , Gfh_entity_trie.Str__xowa_underline); + New_trie_itm(rv, Byte_ascii.Star_bry , Gfh_entity_trie.Str__xowa_asterisk); + New_trie_itm(rv, Byte_ascii.Dash_bry , Gfh_entity_trie.Str__xowa_dash); // needed to handle "|-"; PAGE:de.w:Liste_von_Vereinen_und_Vereinigungen_von_Gl�ubigen_(r�misch-katholische_Kirche) DATE:2015-01-08 + New_trie_itm(rv, Byte_ascii.Space_bry , Gfh_entity_trie.Str__xowa_space); + New_trie_itm(rv, Byte_ascii.Nl_bry , Gfh_entity_trie.Str__xowa_nl); + New_trie_itm(rv, pre_bry , pre_bry); + return rv; + } + private static void New_trie_itm(Btrie_slim_mgr rv, byte[] src, String trg) {New_trie_itm(rv, src, Bry_.new_u8(trg));} + private static void New_trie_itm(Btrie_slim_mgr rv, byte[] src, byte[] trg) { + Nowiki_escape_itm itm = new Nowiki_escape_itm(src, trg); + rv.Add_obj(src, itm); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_arg_itm_tkn_chkr.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_arg_itm_tkn_chkr.java index a27517de8..e16848111 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_arg_itm_tkn_chkr.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_arg_itm_tkn_chkr.java @@ -13,3 +13,13 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xop_arg_itm_tkn_chkr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Arg_itm_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_arg_itm;} + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { +// Arg_itm_tkn actl = (Arg_itm_tkn)actl_obj; + return err; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_arg_nde_tkn_chkr.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_arg_nde_tkn_chkr.java index a27517de8..5f429a233 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_arg_nde_tkn_chkr.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_arg_nde_tkn_chkr.java @@ -13,3 +13,17 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xop_arg_nde_tkn_chkr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Arg_nde_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_arg_nde;} + public Xop_tkn_chkr_base Key_tkn() {return key_tkn;} public Xop_arg_nde_tkn_chkr Key_tkn_(Xop_tkn_chkr_base v) {key_tkn = v; return this;} private Xop_tkn_chkr_base key_tkn; + public Xop_tkn_chkr_base Val_tkn() {return val_tkn;} public Xop_arg_nde_tkn_chkr Val_tkn_(Xop_tkn_chkr_base v) {val_tkn = v; return this;} private Xop_tkn_chkr_base val_tkn; + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { + Arg_nde_tkn actl = (Arg_nde_tkn)actl_obj; + if (key_tkn != null) err += mgr.Tst_sub_obj(key_tkn, actl.Key_tkn(), path + "." + "key", err); + if (val_tkn != null) err += mgr.Tst_sub_obj(val_tkn, actl.Val_tkn(), path + "." + "val", err); + return err; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_arg_wkr.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_arg_wkr.java index a27517de8..4eea72191 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_arg_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_arg_wkr.java @@ -13,3 +13,7 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public interface Xop_arg_wkr { + boolean Args_add(Xop_ctx ctx, byte[] src, Xop_tkn_itm tkn, Arg_nde_tkn arg, int arg_idx); +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_arg_wkr_.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_arg_wkr_.java index a27517de8..a17d5a451 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_arg_wkr_.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_arg_wkr_.java @@ -13,3 +13,7 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_arg_wkr_ { + public static final int Typ_lnki = 0, Typ_tmpl = 1, Typ_prm = 2; +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_brack_bgn_lxr.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_brack_bgn_lxr.java index a27517de8..a87c3c55f 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_brack_bgn_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_brack_bgn_lxr.java @@ -13,3 +13,17 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +public class Xop_brack_bgn_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_brack_bgn;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Xop_tkn_.Lnki_bgn, 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) { + Xop_tkn_itm tkn = tkn_mkr.Brack_bgn(bgn_pos, cur_pos); + ctx.Subs_add_and_stack(root, tkn); + return cur_pos; + } + public static final Xop_brack_bgn_lxr Instance = new Xop_brack_bgn_lxr(); Xop_brack_bgn_lxr() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_brack_end_lxr.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_brack_end_lxr.java index a27517de8..649eb2ed0 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_brack_end_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_brack_end_lxr.java @@ -13,3 +13,20 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +public class Xop_brack_end_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_brack_end;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Xop_tkn_.Lnki_end, 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) { + int acs_pos = ctx.Stack_idx_typ(Xop_tkn_itm_.Tid_brack_bgn); + if (acs_pos != -1 && ctx.Cur_tkn_tid() != Xop_tkn_itm_.Tid_tmpl_curly_bgn) // NOTE: do not pop tkn if inside tmpl; EX: [[a|{{#switch:{{{1}}}|b=c]]|d=e]]|f]]}} + ctx.Stack_pop_til(root, src, acs_pos, true, bgn_pos, cur_pos, Xop_tkn_itm_.Tid_tmpl_curly_bgn); + Xop_tkn_itm tkn = tkn_mkr.Brack_end(bgn_pos, cur_pos); + ctx.Subs_add(root, tkn); + return cur_pos; + } + public static final Xop_brack_end_lxr Instance = new Xop_brack_end_lxr(); Xop_brack_end_lxr() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_bgn_lxr.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_bgn_lxr.java index a27517de8..5b55489b0 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_bgn_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_bgn_lxr.java @@ -13,3 +13,24 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +import gplx.xowa.parsers.tblws.*; +public class Xop_curly_bgn_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_curly_bgn;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Hook, this);} public static final byte[] Hook = new byte[] {Byte_ascii.Curly_bgn, Byte_ascii.Curly_bgn}; + 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) {return ctx.Curly().MakeTkn_bgn(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos);} + public static final Xop_curly_bgn_lxr Instance = new Xop_curly_bgn_lxr(); Xop_curly_bgn_lxr() {} + public static Btrie_fast_mgr tmpl_bgn_trie_() { // hook sequences for adding new_line to tmpl return; "{|" "|-" ":" ";" "#" "*"; EX: "{{a}}" returns "*"; convert to "\n*" + Btrie_fast_mgr rv = Btrie_fast_mgr.cs(); + rv.Add(Xop_tblw_lxr_ws.Hook_tb, Bry_.Empty); + rv.Add(Bry_.new_a7("|-"), Bry_.Empty); + rv.Add(Byte_ascii.Colon, Bry_.Empty); + rv.Add(Byte_ascii.Semic, Bry_.Empty); + rv.Add(Byte_ascii.Hash, Bry_.Empty); + rv.Add(Byte_ascii.Star, Bry_.Empty); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_bgn_tkn.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_bgn_tkn.java index a27517de8..65dcd587a 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_bgn_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_bgn_tkn.java @@ -13,3 +13,9 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; +public class Xop_curly_bgn_tkn extends Xop_tkn_itm_base { + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_tmpl_curly_bgn;} + public Xop_curly_bgn_tkn(int bgn, int end) {this.Tkn_ini_pos(false, bgn, end);} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_end_lxr.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_end_lxr.java index a27517de8..5339848de 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_end_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_end_lxr.java @@ -13,3 +13,13 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +public class Xop_curly_end_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_curly_end;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Hook, this);} public static final byte[] Hook = new byte[] {Byte_ascii.Curly_end, Byte_ascii.Curly_end}; + 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) {return ctx.Curly().MakeTkn_end(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos);} + public static final Xop_curly_end_lxr Instance = new Xop_curly_end_lxr(); Xop_curly_end_lxr() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_log.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_log.java index a27517de8..f112eeaba 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_log.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_log.java @@ -13,3 +13,15 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.log_msgs.*; +public class Xop_curly_log { + private static final Gfo_msg_grp owner = Gfo_msg_grp_.new_(Xoa_app_.Nde, "curly"); + public static final Gfo_msg_itm + Bgn_not_found = Gfo_msg_itm_.new_warn_(owner, "Bgn_not_found") + , End_should_not_autoclose_anything = Gfo_msg_itm_.new_warn_(owner, "End_should_not_autoclose_anything") + , Bgn_len_0 = Gfo_msg_itm_.new_warn_(owner, "Bgn_len_0") + , Bgn_len_1 = Gfo_msg_itm_.new_warn_(owner, "Bgn_len_1") + , Tmpl_is_empty = Gfo_msg_itm_.new_warn_(owner, "Tmpl_is_empty") + ; +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_wkr.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_wkr.java index a27517de8..fc8938613 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_curly_wkr.java @@ -13,3 +13,147 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.parsers.xndes.*; +public class Xop_curly_wkr implements Xop_ctx_wkr { + public void Ctor_ctx(Xop_ctx ctx) {} + public void Page_bgn(Xop_ctx ctx, Xop_root_tkn root) {} + public void Page_end(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int src_len) {} + public void AutoClose(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int lxr_bgn_pos, int lxr_cur_pos, Xop_tkn_itm tkn) {} + public int MakeTkn_bgn(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int lxr_bgn_pos, int lxr_cur_pos) { + int lxr_end_pos = Bry_find_.Find_fwd_while(src, lxr_cur_pos, src_len, Byte_ascii.Curly_bgn); // NOTE: can be many consecutive {; EX: {{{{{1}}}|a}} + ctx.Subs_add_and_stack(root, tkn_mkr.Tmpl_curly_bgn(lxr_bgn_pos, lxr_end_pos)); + return lxr_end_pos; + } + public int MakeTkn_end(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int lxr_bgn_pos, int lxr_cur_pos) { + if (ctx.Cur_tkn_tid() == Xop_tkn_itm_.Tid_brack_bgn) // WORKAROUND: ignore }} if inside lnki; EX.CM:Template:Protected; {{#switch:a|b=[[a|ja=}}]]}} + return ctx.Lxr_make_txt_(lxr_cur_pos); + int lxr_end_pos = Bry_find_.Find_fwd_while(src, lxr_cur_pos, src_len, Byte_ascii.Curly_end); // NOTE: can be many consecutive }; EX: {{a|{{{1}}}}} + int end_tkn_len = lxr_end_pos - lxr_bgn_pos; + boolean vnt_enabled = ctx.Wiki().Lang().Vnt_mgr().Enabled(); + while (end_tkn_len > 0) { + int acs_pos = -1, acs_len = ctx.Stack_len(); + for (int i = acs_len - 1; i > -1; i--) { // find auto-close pos + Xop_tkn_itm stack_tkn = ctx.Stack_get(i); + switch (stack_tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_tmpl_curly_bgn: // found curly_bgn; mark and exit + acs_pos = i; + i = -1; + break; + case Xop_tkn_itm_.Tid_brack_bgn: // found no curly_bgn, but found brack_bgn; note that extra }} should not close any frames beyond lnki; EX:w:Template:Cite wikisource; w:John Fletcher (playwright) + i = -1; + break; + case Xop_tkn_itm_.Tid_xnde: // found xnde; ignore; handle {{template|}}}} DATE:2014-03-03 + Xop_xnde_tkn stack_xnde = (Xop_xnde_tkn)stack_tkn; + if (stack_xnde.Tag().Xtn()) + i = -1; + break; + } + } + if (acs_pos == -1) { // "}}+" found but no "{{+" found; warn and output literal tkn + ctx.Msg_log().Add_itm_none(Xop_curly_log.Bgn_not_found, src, lxr_bgn_pos, lxr_end_pos); + ctx.Subs_add(root, tkn_mkr.Txt(lxr_bgn_pos, lxr_end_pos)); + return lxr_end_pos; + } + + Xop_curly_bgn_tkn bgn_tkn = (Xop_curly_bgn_tkn)ctx.Stack_pop_til(root, src, acs_pos, true, lxr_bgn_pos, lxr_end_pos, Xop_tkn_itm_.Tid_tmpl_curly_bgn); // NOTE: in theory, an unclosed [[ can be on stack; for now, ignore + int bgn_tkn_len = bgn_tkn.Src_end() - bgn_tkn.Src_bgn(); + int bgn_tkn_pos_bgn = bgn_tkn.Src_bgn();// save original pos_bgn + boolean vnt_dash_adjust = false; + if (vnt_enabled ) { + int curly_bgn_dash = bgn_tkn.Src_bgn() - 1; + if (curly_bgn_dash > -1 && src[curly_bgn_dash] == Byte_ascii.Dash) { // "-" exists before curlies; EX: "-{{" + int curly_end_dash = lxr_end_pos; + if (curly_end_dash < src_len && src[curly_end_dash] == Byte_ascii.Dash) { // "-" exists after curlies; EX: "}}-" + if (bgn_tkn_len > 2 && end_tkn_len > 2) { // more than 3 curlies at bgn / end with flanking dashes; EX: "-{{{ }}}-"; NOTE: 3 is needed b/c 2 will never be reduced; EX: "-{{" will always be "-" and "{{", not "-{" and "{" + int numeric_val = Bry_.To_int_or(src, bgn_tkn.Src_end(), lxr_bgn_pos, -1); + if ( numeric_val != -1 // do not apply if numeric val; EX:"-{{{0}}}-" vs "-{{{#expr:0}}}-" sr.w:Template:Link_FA + && bgn_tkn_len == 3 && end_tkn_len == 3 // exactly 3 tokens; assume param token; "-{{{" -> "-" + "{{{" x> -> "-{" + "{{"; if unbalanced (3,4 or 4,3) fall into code below + ) { + } // noop; PAGE:sr.w:ДНК; EX: DATE:2014-07-03 + else { + --bgn_tkn_len; // reduce bgn curlies by 1; EX: "{{{" -> "{{" + ++bgn_tkn_pos_bgn; // add one to bgn tkn_pos; + --end_tkn_len; // reduce end curlies by 1; EX: "}}}" -> "}}" + --lxr_end_pos; // reduce end by 1; this will "reprocess" the final "}" as a text tkn; EX: "}}}-" -> "}}" and position before "}-" + vnt_dash_adjust = true; + } + } + } + } + } + + int new_tkn_len = 0; + if (bgn_tkn_len == end_tkn_len) // exact match; should be majority of cases + new_tkn_len = bgn_tkn_len; + else if (bgn_tkn_len > end_tkn_len) // more bgn than end; use end, and deduct bgn; EX: {{{{{1}}}|a}} + new_tkn_len = end_tkn_len; + else /*bgn_tkn_len < end_tkn_len*/ // more end than bgn; use bgn, and deduct end; EX: {{a|{{{1}}}}} + new_tkn_len = bgn_tkn_len; + + int keep_curly_bgn = 0; + /* NOTE: this is a semi-hack; if bgn_tkn > new_tkn, then pretend bgn_tkn fits new_tkn, give to bldr, and then adjust back later + EX: {{{{{1}}}|a}} -> bgn_tkn_len=5,new_tkn_len=3 -> change bgn(0, 5) to bgn(2, 5) + The "correct" way is to insert a new_bgn_tkn after cur_bgn_tkn on root, but this would have performance implications: array would have to be resized, and all subs will have to be reindexed + NOTE: bgn curlies should also be preserved if new_tkn_len > 3; EX: {{{{{{1}}}}}}; note that bgn = end, but len > 3 + */ + if (bgn_tkn_len > new_tkn_len || new_tkn_len > 3) { + bgn_tkn.Tkn_ini_pos(false, bgn_tkn.Src_end() - new_tkn_len, bgn_tkn.Src_end()); + keep_curly_bgn = 1; // preserves {{ + } + switch (new_tkn_len) { + case 0: // EXC_CASE: should not happen; warn; + ctx.Msg_log().Add_itm_none(Xop_curly_log.Bgn_len_0, src, bgn_tkn.Src_bgn(), lxr_end_pos); + break; +// case 1: // EXC_CASE: SEE:NOTE_1; +// break; + case 2: // USE_CASE: make invk_tkn + ctx.Invk().Make_tkn(ctx, root, src, lxr_bgn_pos, lxr_bgn_pos + new_tkn_len, bgn_tkn, keep_curly_bgn); + break; + default: // USE_CASE: make prm_tkn; NOTE: 3 or more + new_tkn_len = 3; // gobble 3 at a time; EX: 6 -> 3 -> 0; EX: 7 -> 4 -> 1; + prm_wkr.Make_tkn(ctx, tkn_mkr, root, src, src_len, lxr_bgn_pos, lxr_bgn_pos + new_tkn_len, bgn_tkn, keep_curly_bgn); + break; + } + switch (bgn_tkn_len - new_tkn_len) { // continuation of semi-hack above; some bgn still left over; adjust and throw back on stack + case 1: // 1 tkn; convert curly to generic text tkn + bgn_tkn.Src_end_(bgn_tkn.Src_end() - 1); // NOTE: shorten end of bgn_tkn by 1; TEST + ctx.Stack_add(tkn_mkr.Txt(bgn_tkn_pos_bgn, bgn_tkn.Src_end() - new_tkn_len)); + break; + case 0: // noop + break; + default: + bgn_tkn.Tkn_ini_pos(false, bgn_tkn_pos_bgn, bgn_tkn.Src_end() - new_tkn_len); // bgn(2, 5) -> bgn (0, 2) + ctx.Stack_add(bgn_tkn); + break; + } + if (vnt_dash_adjust) { + Xop_tkn_itm text_tkn = root.Subs_get_or_null(root.Subs_len() - 2); // -2 to get tkn before newly-created tmpl / prm + if (text_tkn == null || text_tkn.Tkn_tid() != Xop_tkn_itm_.Tid_txt) + ctx.Wiki().Appe().Usr_dlg().Warn_many("", "", "token before curly_bgn was not text tkn; src=~{0}", String_.new_u8(src, lxr_bgn_pos, lxr_end_pos)); + else + text_tkn.Src_end_(text_tkn.Src_end() + 1); // +1 to extend txt_tkn with dash be 1 to include curly; EX: "-" "{{{" -> "-{" "{{" + } + + end_tkn_len -= new_tkn_len; + lxr_bgn_pos += new_tkn_len; // move lxr_bgn_pos along + if (end_tkn_len == 1) { // SEE:NOTE_1: + ctx.Subs_add(root, tkn_mkr.Txt(lxr_bgn_pos, lxr_bgn_pos + 1)); + end_tkn_len = 0; + ++lxr_bgn_pos; + } + } + return lxr_end_pos; + } + private Xot_prm_wkr prm_wkr = Xot_prm_wkr.Instance; + public static final byte[] + Hook_prm_bgn = new byte[] {Byte_ascii.Curly_bgn, Byte_ascii.Curly_bgn, Byte_ascii.Curly_bgn} + , Hook_prm_end = new byte[] {Byte_ascii.Curly_end, Byte_ascii.Curly_end, Byte_ascii.Curly_end}; +} +/* +NOTE_1: +. end_tkn_len = 1 can happen when 4 on either side, or other permutations; EX: {{{{leaf_1}}}}; +. cannot put in switch(new_tkn_len) b/c the 1 } will pop any {{ or {{{ off stack; +.. EX: "{{#switch:a|{{{1}}}}=y|n}}"; 4th } will pop {{ of #switch off stack +.. make separate check near end +*/ \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_func_arg_itm.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_func_arg_itm.java index a27517de8..ed49d4a98 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_func_arg_itm.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_func_arg_itm.java @@ -13,3 +13,36 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.xtns.pfuncs.*; +public class Xop_func_arg_itm { + public byte[] key; + public byte[] val; + public void Set(Bry_bfr bfr, Xop_ctx ctx, byte[] src, Xot_invk caller, Xot_invk self, Arg_nde_tkn arg) { + Pf_func_.Eval_arg_or(bfr, ctx, src, caller, self, arg, null); + key = val = Bry_.Empty; + int len = bfr.Len(); byte[] bry = bfr.Bfr(); + int bgn = -1, end = -1; boolean mode_is_key = true; + for (int i = 0; i < len; ++i) { + byte b = bry[i]; + switch (b) { + case Byte_ascii.Eq: + if (mode_is_key) { + mode_is_key = false; + key = Bry_.Mid(bry, bgn, end); + bgn = end = -1; + } + break; + case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: case Byte_ascii.Space: + break; + default: + if (bgn == -1) bgn = i; + end = i + 1; + break; + } + if (bgn != -1) // guard against 'key=' + val = Bry_.Mid(bry, bgn, end); + } + bfr.Clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_subst_tst.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_subst_tst.java index a27517de8..4c70a1802 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_subst_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_subst_tst.java @@ -13,3 +13,62 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.xowa.wikis.ttls.*; +public class Xop_subst_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Before public void init() { + fxt.Reset(); + fxt.Init_defn_clear(); + fxt.Init_defn_add("xo_print", "{{{1}}}"); + fxt.Init_defn_add("!", "|"); + } + @Test public void Wiki_txt_subst() {fxt.Test_parse_tmpl_str_test("{{{1}}}" , "{{subst:test|a}}" , "a");} + @Test public void Wiki_txt_subst_ws() {fxt.Test_parse_tmpl_str_test("{{{1}}}" , "{{ subst:test|a}}" , "a");} + @Test public void Wiki_txt_safesubst() {fxt.Test_parse_tmpl_str_test("{{{1}}}" , "{{safesubst:test|a}}" , "a");} + @Test public void Tmpl_txt_subst_empty() {fxt.Test_parse_tmpl_str_test("{{subst:}}" , "{{test}}" , "{{subst:}}");} + @Test public void Tmpl_txt_safesubst() {fxt.Test_parse_tmpl_str_test("{{safesubst:xo_print|a}}" , "{{test}}" , "a");} + @Test public void Tmpl_prm_subst() {fxt.Test_parse_tmpl_str_test("{{{{{1|subst:}}}xo_print|a}}" , "{{test}}" , "{{subst:xo_print|a}}");} + @Test public void Tmpl_prm_subst_ws() {fxt.Test_parse_tmpl_str_test("{{{{{1| subst:}}}xo_print|a}}" , "{{test}}" , "{{ subst:xo_print|a}}");} + @Test public void Tmpl_prm_safesubst() {fxt.Test_parse_tmpl_str_test("{{{{{1|safesubst:}}}xo_print|a}}" , "{{test}}" , "a");} + @Test public void Tmpl_prm_safesubst_empty() {fxt.Test_parse_tmpl_str_test("{{{{{|safesubst:}}}xo_print|a}}" , "{{test}}" , "a");} + @Test public void Tmpl_txt_subst_pf() {fxt.Test_parse_tmpl_str_test("{{subst:#expr:0}}" , "{{test}}" , "0");} + @Test public void Tmpl_txt_safesubst_prm() {fxt.Test_parse_tmpl_str_test("{{{{{|safesubst:}}}#if:{{{1|}}}{{{{{|safesubst:}}}!}}c1|c2}}" , "{{test}}" , "c2");} + @Test public void Exc_tmpl_prm_safesubst_ns() {fxt.Test_parse_tmpl_str_test("{{{{{|safesubst}}}:NAMESPACE}}" , "{{test}}" , "");} + @Test public void Unreferenced() { // PURPOSE: if subst, but in tmpl stage, do not actually subst; PAGE:en.w:Unreferenced; DATE:2013-01-31 + fxt.Init_defn_clear(); + fxt.Init_defn_add("substcheck", "SUBST"); + fxt.Init_defn_add("ifsubst", String_.Concat_lines_nl + ( "{{ {{{|safesubst:}}}#ifeq:{{ {{{|safesubst:}}}NAMESPACE}}|{{NAMESPACE}}" + , " |{{{no|{{{2|}}}}}}" + , " |{{{yes|{{{1|}}}}}}" + , "}}" + )); + fxt.Test_parse_tmpl_str_test("{{ {{{|safesubst:}}}ifsubst |yes|{{subst:substcheck}}}}", "{{test}}", "{{subst:substcheck}}"); + } + @Test public void Urlencode_missing_ttl() { // PURPOSE: handle missing ttl inside {{does-template-exist}}; EX: en.d:Kazakhstan; DATE:2014-03-25 + fxt.Init_defn_clear(); + fxt.Init_defn_add("test", "{{safesubst:urlencode:{{safesubst:Template:{{{1}}}}}}}"); + fxt.Test_parse_page_tmpl_str("{{test|xyz}}", "%5B%5BTemplate%3Axyz%5D%5D"); // url-encoded version of [[:Template:xyz]]; NOTE: removed leading ":" from ":Template:" DATE:2016-06-24 + } + @Test public void Urlencode_invalid_ttl() { // PURPOSE: handle invalid ttl inside does-template-exist; EX: en.d:peace; DATE:2014-03-31 + fxt.Init_defn_clear(); + fxt.Init_defn_add("test", "{{safesubst:urlencode:{{safesubst:Template:{{{1}}}}}}}"); + fxt.Test_parse_page_tmpl_str("{{test|[xyz]}}", "%7B%7Bsafesubst%3ATemplate%3A%5Bxyz%5D%7D%7D"); // url-encoded version of {{safesubst:Template:xyz}} + } + @Test public void Urlencode_template_ttl() { // PURPOSE: handle template ttl inside does-template-exist; based on above; DATE:2014-03-31 + fxt.Init_defn_clear(); + fxt.Init_defn_add("test", "{{safesubst:urlencode:{{Template:{{{1}}}}}}}"); + fxt.Test_parse_page_tmpl_str("{{test|Template:[xyz]}}", "%7B%7BTemplate%3ATemplate%3A%5Bxyz%5D%7D%7D"); // url-encoded version of {{safesubst:Template:xyz}} + } + @Test public void Nowiki() { // PURPOSE: stack overflow; PAGE:Близкие_друзья_(Сезон_2) DATE:2014-10-21 + fxt.Init_defn_add("ET", ""); + fxt.Init_defn_add("ds", "{{subst:ET|{{subst:ds}}}}"); + fxt.Test_parse_page_tmpl_str("{{subst:ds}}", ""); // {{subst:ds}} causes stack overflow; {{ds}} does not + } + // NOTE: these are actually not good tests; MW does subst just before save; it doesn't do subst on load; in this case, the tests are testing load (which will noop); they need to test save (which xowa doesn't do) + // @Test public void Tmpl_txt_subst() {fxt.Test_parse_tmpl_str_test("{{subst:xo_print|a}}" , "{{test}}" , "a");} + // @Test public void Tmpl_txt_subst_prm() {fxt.Test_parse_tmpl_str_test("{{subst:xo_print|{{{1}}}}}" , "{{test|a}}" , "a");} + //@Test public void Tmpl_txt_safesubst_prm() {fxt.Test_parse_tmpl_str_test("{{{{{|safesubst:}}}ns:Category}}" , "{{test}}" , "Category");} + //@Test public void Tmpl_txt_subst_immed() {fxt.Test_parse_tmpl_str_test("{{xo_print{{subst:!}}a}}" , "{{test}}" , "a");} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_tkn_.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_tkn_.java index a27517de8..820e15e2d 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_tkn_.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_tkn_.java @@ -13,3 +13,13 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_tkn_ { + public static final byte[] + Lnki_bgn = new byte[] {Byte_ascii.Brack_bgn, Byte_ascii.Brack_bgn} + , Lnki_end = new byte[] {Byte_ascii.Brack_end, Byte_ascii.Brack_end} + , Anchor = new byte[] {Byte_ascii.Hash} + ; + public static final int Lnki_bgn_len = 2, Lnki_end_len = 2; + public static final byte Anchor_byte = Byte_ascii.Hash; +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_tkn_print_tst.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_tkn_print_tst.java index a27517de8..6a3048a15 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_tkn_print_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_tkn_print_tst.java @@ -13,3 +13,26 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_tkn_print_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Text() {tst_Print("a ''b'' c [[d]] e");} + @Test public void Prm() {tst_Print("{{{1}}}");} + @Test public void Prm_dflt() {tst_Print("{{{1|a}}}");} + @Test public void Prm_dflt_prm() {tst_Print("{{{1|{{{2}}}}}}");} + @Test public void Tmpl() {tst_Print("{{a}}");} + @Test public void Tmpl_arg() {tst_Print("{{a|1|2}}");} + @Test public void Tmpl_arg_prm() {tst_Print("{{a|1|{{{1}}}}}");} + @Test public void Tmpl_arg_tmpl() {tst_Print("{{a|1|{{b}}}}");} + @Test public void Tmpl_pf() {tst_Print("{{#expr:1}}");} + private void tst_Print(String raw) { + Xop_ctx ctx = fxt.Ctx(); + byte[] raw_bry = Bry_.new_u8(raw); + Xot_defn_tmpl defn = fxt.run_Parse_tmpl(Bry_.Empty, raw_bry); + Xot_fmtr_prm raw_fmtr = new Xot_fmtr_prm(); + defn.Root().Tmpl_fmt(ctx, raw_bry, raw_fmtr); + raw_fmtr.Print(tst_Print_bb); + Tfds.Eq(raw, tst_Print_bb.To_str_and_clear()); + } private Bry_bfr tst_Print_bb = Bry_bfr_.New(); +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_tmpl_log.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_tmpl_log.java index a27517de8..9aa2eb419 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_tmpl_log.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xop_tmpl_log.java @@ -13,3 +13,16 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.log_msgs.*; +public class Xop_tmpl_log { + private static final Gfo_msg_grp owner = Gfo_msg_grp_.new_(Xoa_app_.Nde, "tmpl"); + public static final Gfo_msg_itm + Escaped_tmpl = Gfo_msg_itm_.new_warn_(owner, "Escaped_tmpl") + , Escaped_end = Gfo_msg_itm_.new_warn_(owner, "Escaped_end") + , Key_is_empty = Gfo_msg_itm_.new_note_(owner, "Key_is_empty") + , Dangling = Gfo_msg_itm_.new_note_(owner, "Dangling_tmpl") + , Tmpl_end_autoCloses_something = Gfo_msg_itm_.new_note_(owner, "Tmpl_end_autoCloses_something") + , Tmpl_is_empty = Gfo_msg_itm_.new_note_(owner, "Tmpl_is_empty") + ; +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_compile_data.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_compile_data.java index a27517de8..b923dc514 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_compile_data.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_compile_data.java @@ -13,3 +13,11 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xot_compile_data { + public boolean OnlyInclude_exists; + public boolean Arg_nde_has_key() {return arg_nde_has_key;} private boolean arg_nde_has_key; + public Xot_compile_data Arg_nde_has_key_(boolean v) {arg_nde_has_key = v; return this;} + + public static final Xot_compile_data Noop = new Xot_compile_data(); +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn.java index a27517de8..add3ebb3f 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn.java @@ -13,3 +13,20 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public interface Xot_defn extends Rls_able { + byte Defn_tid(); + byte[] Name(); + int Cache_size(); + boolean Defn_require_colon_arg(); + Xot_defn Clone(int id, byte[] name); +} +class Xot_defn_null implements Xot_defn { + public byte Defn_tid() {return Xot_defn_.Tid_null;} + public boolean Defn_require_colon_arg() {return false;} + public byte[] Name() {return Bry_.Empty;} + public Xot_defn Clone(int id, byte[] name) {return this;} + public int Cache_size() {return 0;} + public void Rls() {} + public static final Xot_defn_null Instance = new Xot_defn_null(); Xot_defn_null() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_.java index a27517de8..5da6109e3 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_.java @@ -13,3 +13,24 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.kwds.*; +public class Xot_defn_ { + public static final Xot_defn Null = Xot_defn_null.Instance; + public static final byte + Tid_null = 0 + , Tid_func = 1 + , Tid_tmpl = 2 + , Tid_subst = Xol_kwd_grp_.Id_subst + , Tid_safesubst = Xol_kwd_grp_.Id_safesubst + , Tid_raw = Xol_kwd_grp_.Id_raw + , Tid_msg = Xol_kwd_grp_.Id_msg + , Tid_msgnw = Xol_kwd_grp_.Id_msgnw + ; + public static boolean Tid_is_substing(byte v) { + switch (v) { + case Tid_subst: return true; // NOTE: safesubst should not return true, else stack overflow; PAGE:en.w:Wikipedia:WikiProject_Celtic_history_and_culture DATE:2015-01-02 + default: return false; + } + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_subst.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_subst.java index a27517de8..6d111b098 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_subst.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_subst.java @@ -13,3 +13,13 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xot_defn_subst implements Xot_defn { + public Xot_defn_subst(byte tid, byte[] name) {this.tid = tid; this.name = name;} + public byte Defn_tid() {return tid;} private byte tid; + public byte[] Name() {return name;} private byte[] name; + public Xot_defn Clone(int id, byte[] name) {return new Xot_defn_subst(tid, name);} + public boolean Defn_require_colon_arg() {return true;} + public void Rls() {name = null;} + public int Cache_size() {return 1024;} // arbitrary size +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_tmpl.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_tmpl.java index a27517de8..f97a6b02d 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_tmpl.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_tmpl.java @@ -13,3 +13,82 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.brys.*; import gplx.xowa.wikis.nss.*; +public class Xot_defn_tmpl implements Xot_defn { + private boolean onlyinclude_parsed = false; + public byte Defn_tid() {return Xot_defn_.Tid_tmpl;} + public boolean Defn_require_colon_arg() {return false;} + public int Cache_size() {return data_raw.length;} + public byte[] Name() {return name;} private byte[] name; private byte[] full_name; + public byte[] Frame_ttl() {return frame_ttl;} public void Frame_ttl_(byte[] v) {frame_ttl = v;} private byte[] frame_ttl; + public byte[] Data_raw() {return data_raw;} private byte[] data_raw; + public byte[] Data_mid() {return data_mid;} public Xot_defn_tmpl Data_mid_(byte[] v) {data_mid = v; return this;} private byte[] data_mid; + public Xop_ctx Ctx() {return ctx;} public Xot_defn_tmpl Ctx_(Xop_ctx v) {ctx = v; return this;} private Xop_ctx ctx; + public Xop_root_tkn Root() {return root;} private Xop_root_tkn root; + public void Init_by_new(Xow_ns ns, byte[] name, byte[] data_raw, Xop_root_tkn root, boolean onlyInclude) { + this.ns = ns; this.name = name; this.data_raw = data_raw; this.root = root; this.onlyInclude_exists = onlyInclude; + ns_id = ns.Id(); + this.full_name = ns.Gen_ttl(name); + } private Xow_ns ns; int ns_id; + public void Init_by_raw(Xop_root_tkn root, boolean onlyInclude_exists) { + this.root = root; this.onlyInclude_exists = onlyInclude_exists; + } + byte[] Extract_onlyinclude(byte[] src, Bry_bfr_mkr bfr_mkr) { + Bry_bfr bfr = bfr_mkr.Get_m001(); + int pos = 0; + int src_len = src.length; + while (true) { + int find_bgn = Bry_find_.Find_fwd(src, Bry_onlyinclude_bgn, pos, src_len); + if (find_bgn == Bry_find_.Not_found) { + break; + } + int find_bgn_lhs = find_bgn + Bry_onlyinclude_bgn_len; + int find_end = Bry_find_.Find_fwd(src, Bry_onlyinclude_end, find_bgn_lhs, src_len); + if (find_end == Bry_find_.Not_found) { + break; + } + bfr.Add_mid(src, find_bgn_lhs, find_end); + pos = find_end + Bry_onlyinclude_end_len; + } + return bfr.To_bry_and_rls(); + } + private static final byte[] Bry_onlyinclude_bgn = Bry_.new_a7(""), Bry_onlyinclude_end = Bry_.new_a7(""); + private static int Bry_onlyinclude_bgn_len = Bry_onlyinclude_bgn.length, Bry_onlyinclude_end_len = Bry_onlyinclude_end.length; + public void Rls() { + if (root != null) root.Clear(); + root = null; + } + public void Parse_tmpl(Xop_ctx ctx) {ctx.Wiki().Parser_mgr().Main().Parse_text_to_defn(this, ctx, ctx.Tkn_mkr(), ns, name, data_raw);} + public boolean Tmpl_evaluate(Xop_ctx ctx, Xot_invk caller, Bry_bfr bfr) { + if (root == null) Parse_tmpl(ctx); + Xoae_page page = ctx.Page(); + if (!ctx.Wiki().Parser_mgr().Tmpl_stack_add(full_name)) { + bfr.Add_str_a7(""); + Xoa_app_.Usr_dlg().Log_many("", "", "template loop detected: url=~{0} name=~{1}", ctx.Page().Url().To_str(), name); + return false; + } + boolean rv = true; + if (onlyInclude_exists) { + Xowe_wiki wiki = ctx.Wiki(); + if (!onlyinclude_parsed) { + onlyinclude_parsed = true; + byte[] new_data = Extract_onlyinclude(data_raw, wiki.Utl__bfr_mkr()); + Xop_ctx new_ctx = Xop_ctx.New__sub(wiki, ctx, page); // COMMENT:changed from ctx.Page() to page; DATE:2016-07-11 + Xot_defn_tmpl tmpl = wiki.Parser_mgr().Main().Parse_text_to_defn_obj(new_ctx, new_ctx.Tkn_mkr(), wiki.Ns_mgr().Ns_template(), Bry_.Empty, new_data); + tmpl.Root().Tmpl_compile(new_ctx, new_data, Xot_compile_data.Noop); + data_raw = new_data; + root = tmpl.Root(); + } + } + int subs_len = root.Subs_len(); + for (int i = 0; i < subs_len; i++) { + boolean result = root.Subs_get(i).Tmpl_evaluate(ctx, data_raw, caller, bfr); + if (!result) rv = false; + } + ctx.Wiki().Parser_mgr().Tmpl_stack_del(); + return rv; + } + public Xot_defn Clone(int id, byte[] name) {throw Err_.new_unimplemented();} + boolean onlyInclude_exists; +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_tmpl_.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_tmpl_.java index a27517de8..abff391a7 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_tmpl_.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_tmpl_.java @@ -13,3 +13,56 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.wikis.nss.*; +public class Xot_defn_tmpl_ { + public static Xot_invk CopyNew(Xop_ctx ctx, Xot_defn_tmpl orig_defn, Xot_invk orig, Xot_invk caller, byte[] src, byte[] frame_ttl) { // SEE:NOTE_1 + Xop_tkn_mkr tkn_mkr = ctx.Tkn_mkr(); + byte[] orig_src = orig_defn.Data_raw(); + Xowe_wiki wiki = ctx.Wiki(); + Xot_invk_temp rv = new Xot_invk_temp(orig.Defn_tid(), orig_src, orig.Name_tkn(), caller.Src_bgn(), caller.Src_end()); + frame_ttl = wiki.Lang().Case_mgr().Case_reuse_1st_upper(frame_ttl); // NOTE: always uppercase 1st; EX:{{navbox -> "Template:Navbox"; PAGE:en.w:Achilles DATE:2014-06-21 + rv.Frame_ttl_(Bry_.Add(wiki.Ns_mgr().Ns_template().Name_db_w_colon(), Xoa_ttl.Replace_unders(frame_ttl))); // NOTE: always prepend "Template:" to frame_ttl; DATE:2014-06-13; always use spaces; DATE:2014-08-14; must be local language; Russian "Шаблон" not English "Template"; PAGE:ru.w:Королевство_Нидерландов DATE:2016-11-23 + int orig_args_len = orig.Args_len(); + boolean tmpl_args_parsing_orig = ctx.Tmpl_args_parsing(); + ctx.Tmpl_args_parsing_(true); + for (int i = 0; i < orig_args_len; i++) { + Arg_nde_tkn orig_arg = orig.Args_get_by_idx(i); + Arg_nde_tkn copy_arg = tkn_mkr.ArgNde(-1, 0); + if (orig_arg.KeyTkn_exists()) { + Arg_itm_tkn key_tkn = orig_arg.Key_tkn(); + copy_arg.Key_tkn_(Make_itm(false, ctx, tkn_mkr, src, key_tkn, caller, orig_arg)); + rv.Args_add_by_key(copy_arg.Key_tkn().Dat_ary(), copy_arg); // NOTE: was originally Bry_.Mid(caller.Src(), key_tkn.Dat_bgn(), key_tkn.Dat_end()) which was wrong; caused {{{increment}}} instead of "increment" + } + else + rv.Args_add_by_idx(copy_arg); // NOTE: not a key, so add to idx_hash; DATE:2014-07-23 + copy_arg.Val_tkn_(Make_itm(true, ctx, tkn_mkr, src, orig_arg.Val_tkn(), caller, orig_arg)); + rv.Args_add(copy_arg); + } + ctx.Tmpl_args_parsing_(tmpl_args_parsing_orig); + return rv; + } + private static Arg_itm_tkn Make_itm(boolean val_tkn, Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, byte[] src, Arg_itm_tkn orig, Xot_invk caller, Arg_nde_tkn orig_arg) { + int subs_len = orig.Subs_len(); + Bry_bfr arg_bfr = Bry_bfr_.New(); + for (int i = 0; i < subs_len; i++) + orig.Subs_get(i).Tmpl_evaluate(ctx, src, caller, arg_bfr); + Arg_itm_tkn rv = tkn_mkr.ArgItm(-1, -1); // NOTE: was -1, 0; DATE:2013-04-10 + byte[] rv_ary = orig_arg.KeyTkn_exists() && val_tkn ? arg_bfr.To_bry_and_clear_and_trim() : arg_bfr.To_bry_and_clear(); // // NOTE: must trim if key_exists; DUPE:TRIM_IF_KEY; PAGE:en.w:Coord in Chernobyl disaster, Sahara + rv.Dat_ary_(rv_ary); + return rv; + } +} +/* +NOTE_1: Creates an invk_temp from an invk + +page {{test_1|a}} +test_1 {{test_2|{{{1|nil_1}}}}} +test_2 {{{1|nil_2}}} + +page : invk_temp gets created for {{test1|a}} where name=test1 and arg1=a +test_1 : invk_temp gets created for {{test_2|{{{1|nil_1}}}}} +1) create the invk_tmp tkn, with name=test2 +2) copy the args and resolve; in this case -> {{test2|a}} +now we can use the invk_temp to call test_2 (and so on if needed) +*/ \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace.java index a27517de8..8d7de393f 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace.java @@ -13,3 +13,42 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.primitives.*; +public interface Xot_defn_trace { + void Clear(); + void Trace_bgn(Xop_ctx ctx, byte[] src, byte[] name, Xot_invk caller, Xot_invk self, Xot_defn defn); + void Trace_end(int trg_bgn, Bry_bfr trg); + void Print(byte[] src, Bry_bfr bb); +} +class Xot_defn_trace_brief implements Xot_defn_trace { + public int Count() {return hash.Count();} + public Xot_defn_trace_itm_brief GetAt(int i) {return (Xot_defn_trace_itm_brief)hash.Get_at(i);} + public void Trace_bgn(Xop_ctx ctx, byte[] src, byte[] name, Xot_invk caller, Xot_invk self, Xot_defn defn) { + int hashKey = Bry_obj_ref.CalcHashCode(name, 0, name.length); + Object o = hash.Get_by(hashKey); + Xot_defn_trace_itm_brief itm = null; + if (o == null) { + itm = new Xot_defn_trace_itm_brief().Name_(name); + hash.Add(hashKey, itm); + } + else + itm = (Xot_defn_trace_itm_brief)o; + itm.Count_add(); + } private Ordered_hash hash = Ordered_hash_.New(); + public void Trace_end(int trg_bgn, Bry_bfr trg) {} + public void Print(byte[] src, Bry_bfr bb) { + int count = hash.Count(); if (count == 0) return; + if (bb.Len() != 0) bb.Add_byte_nl(); // only add newLine if something in bb; needed for tests + for (int i = 0; i < count; i++) { + Xot_defn_trace_itm_brief itm = (Xot_defn_trace_itm_brief)hash.Get_at(i); + bb.Add_int_fixed(itm.Count(), 4).Add_byte(Byte_ascii.Space); + bb.Add(itm.Name()).Add_byte_nl(); + } + } + public void Clear() {hash.Clear();} +} +class Xot_defn_trace_itm_brief {// name,count,depth,args, + public byte[] Name() {return name;} public Xot_defn_trace_itm_brief Name_(byte[] v) {name = v; return this;} private byte[] name = Bry_.Empty; + public int Count() {return count;} public Xot_defn_trace_itm_brief Count_(int v) {count = v; return this;} public void Count_add() {++count;} private int count; +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace_brief_tst.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace_brief_tst.java index a27517de8..0f3bcfa76 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace_brief_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace_brief_tst.java @@ -13,3 +13,44 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.core.strings.*; +public class Xot_defn_trace_brief_tst { + Xot_defn_trace_fxt fxt = new Xot_defn_trace_fxt(); + @Before public void init() { + fxt.Init_defn_clear(); + fxt.Init_defn_add("leaf_a", "{{{1}}}"); + fxt.Init_defn_add("leaf_b", "{{{1}}}"); + fxt.Ctx().Defn_trace_(new Xot_defn_trace_brief()); + } + @Test public void Basic_a_1() {fxt.tst_("{{leaf_a}}" , "0001 leaf_a");} + @Test public void Basic_a_2() {fxt.tst_("{{leaf_a}} {{leaf_a}}" , "0002 leaf_a");} + @Test public void Basic_a_b() {fxt.tst_("{{leaf_a}} {{leaf_b}}" , "0001 leaf_a", "0001 leaf_b");} +} +class Xot_defn_trace_fxt { + private final Xop_fxt fxt = new Xop_fxt(); + public Xop_ctx Ctx() {return fxt.Ctx();} + public void Init_defn_clear() {fxt.Init_defn_clear();} + public void Init_defn_add(String name, String raw) {fxt.Init_defn_add(name, raw);} + public void tst_(String raw, String... expd_ary) { + Xop_ctx ctx = fxt.Ctx(); + ctx.Defn_trace().Clear(); + byte[] src = Bry_.new_u8(raw); + ctx.Page().Ttl_(Xoa_ttl.Parse(fxt.Wiki(), Bry_.new_a7("test"))); + Xop_root_tkn root = ctx.Tkn_mkr().Root(src); + fxt.Parser().Parse_page_all_clear(root, ctx, ctx.Tkn_mkr(), src); + ctx.Defn_trace().Print(src, tmp); + String[] actl_ary = String_.Split(tmp.To_str_and_clear(), (char)Byte_ascii.Nl); + Tfds.Eq_ary(expd_ary, actl_ary); + } private Bry_bfr tmp = Bry_bfr_.New(); + String[] To_str(Xot_defn_trace_itm_brief[] ary) { + String[] rv = new String[ary.length]; + for (int i = 0; i < rv.length; i++) { + Xot_defn_trace_itm_brief itm = ary[i]; + sb.Add(String_.new_u8(itm.Name())).Add("|").Add(itm.Count()); + rv[i] = sb.To_str_and_clear(); + } + return rv; + } + String_bldr sb = String_bldr_.new_(); +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace_dbg.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace_dbg.java index a27517de8..f382fb37e 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace_dbg.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace_dbg.java @@ -13,3 +13,127 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.xtns.pfuncs.*; +public class Xot_defn_trace_dbg implements Xot_defn_trace { + public void Trace_bgn(Xop_ctx ctx, byte[] src, byte[] name, Xot_invk caller, Xot_invk invk, Xot_defn defn) { + if (count++ != 0) bfr.Add_byte_nl(); // do not add new line for 1st template + indent += 2; + // *invk + bfr.Add_byte_repeat(Byte_ascii.Space, indent).Add(Ary_invk_lbl); + bfr.Add_byte_repeat(Byte_ascii.Space, indent).Add(Xop_curly_bgn_lxr.Hook).Add(name); + if (defn.Defn_tid() == Xot_defn_.Tid_func) { + byte[] argx_ary = ((Pf_func_base)defn).Argx_dat(); + bfr.Add_byte(Byte_ascii.Colon).Add(argx_ary); + } + int args_len = invk.Args_len(); + for (int i = 0; i < args_len; i++) { + bfr.Add_byte(Byte_ascii.Pipe); + Arg_nde_tkn nde = invk.Args_get_by_idx(i); + if (nde.KeyTkn_exists()) { +// bfr.Add_mid(src, nde.KeyTkn().Dat_bgn(), nde.KeyTkn().Dat_end()).Add_byte(Byte_ascii.Eq); + bfr.Add(nde.Key_tkn().Dat_ary()).Add_byte(Byte_ascii.Eq); + } +// Arg_itm_tkn val_tkn = nde.ValTkn(); +// if (val_tkn.Itm_static() == Bool_.Y_byte) { +// bfr.Add_mid(src, val_tkn.Dat_bgn(), val_tkn.Dat_end()); +// } +// else { +// Xot_fmtr_prm raw_fmtr = Xot_fmtr_prm.Instance; +// nde.ValTkn().Tmpl_fmt(ctx, src, raw_fmtr); +// raw_fmtr.Print(bfr); +// } + byte[] val_dat_ary = nde.Val_tkn().Dat_ary(); + if (val_dat_ary == Bry_.Empty) { + Xot_fmtr_prm raw_fmtr = prm_fmtr; + nde.Val_tkn().Tmpl_fmt(ctx, src, raw_fmtr); + raw_fmtr.Print(bfr); + } + else + bfr.Add(nde.Val_tkn().Dat_ary()); + } + bfr.Add(Xop_curly_end_lxr.Hook); + bfr.Add_byte_nl(); + + // *name + bfr .Add_byte_repeat(Byte_ascii.Space, indent).Add(Ary_lnk_lbl) + .Add(Xop_tkn_.Lnki_bgn) + .Add(ctx.Wiki().Ns_mgr().Ns_template().Name_db_w_colon()) + .Add(defn.Name()) + .Add(Xop_tkn_.Lnki_end).Add_byte_nl(); + + if (defn.Defn_tid() == Xot_defn_.Tid_tmpl) { + // *args + argKeys.Clear(); + int caller_args = invk.Args_len(); + int key_max = 0; + bfr .Add_byte_repeat(Byte_ascii.Space, indent).Add(Ary_args_lbl); + for (int i = 0; i < caller_args; i++) { + Arg_nde_tkn arg = invk.Args_get_by_idx(i); + int digits = Int_.DigitCount(i + 1); +// byte[] val_ary = Bry_.Mid(src, arg.ValTkn().Dat_bgn(), arg.ValTkn().Dat_end()); + bfr .Add_byte_repeat(Byte_ascii.Space, indent + 2) + .Add_byte_repeat(Byte_ascii.Space, 4 - digits) + .Add_int_fixed(i + 1, digits) + .Add_byte(Byte_ascii.Colon).Add_byte(Byte_ascii.Space) + .Add(arg.Val_tkn().Dat_ary()).Add_byte_nl() +// .Add(val_ary).Add_byte_nl() + ; + if (arg.KeyTkn_exists()) { +// byte[] key_ary = Bry_.Mid(src, arg.KeyTkn().Dat_bgn(), arg.KeyTkn().Dat_end()); + String key_str = String_.new_u8(arg.Key_tkn().Dat_ary()); + int key_str_len = String_.Len(key_str); + if (key_str_len > key_max) key_max = key_str_len; + argKeys.Add(key_str + "=" + String_.new_u8(arg.Val_tkn().Dat_ary())); + } + } + argKeys.Sort(); + for (int i = 0; i < argKeys.Count(); i++) { + String s = (String)argKeys.Get_at(i); + String key = String_.GetStrBefore(s, "="); + String val = String_.GetStrAfter(s, "="); + bfr.Add_byte_repeat(Byte_ascii.Space, indent + 2).Add_str_u8(key) + .Add_byte_repeat(Byte_ascii.Space, key_max - String_.Len(key)) + .Add_byte(Byte_ascii.Colon).Add_byte(Byte_ascii.Space).Add_str_u8(val).Add_byte_nl(); + } + } + + if (defn.Defn_tid() == Xot_defn_.Tid_tmpl) { + Xot_defn_tmpl defn_tmpl = ((Xot_defn_tmpl)defn); + Xop_root_tkn root = defn_tmpl.Root(); +// Fmt(ctx, defn_tmpl.Src(), root, Ary_raw_lbl , null, false); +// Fmt(ctx, defn_tmpl.Src(), root, Ary_fmt_lbl , invk, true); + Fmt(ctx, defn_tmpl.Data_raw(), root, Ary_eval_lbl, invk, false); + } + } private Bry_bfr bfr = Bry_bfr_.New_w_size(128); List_adp argKeys = List_adp_.New(); Xot_fmtr_prm prm_fmtr = new Xot_fmtr_prm(); + private void Fmt(Xop_ctx ctx, byte[] src, Xop_tkn_itm root, byte[] lbl, Xot_invk caller, boolean newLineArgs) { + bfr.Add_byte_repeat(Byte_ascii.Space, indent).Add(lbl); + bfr.Add_byte_repeat(Byte_ascii.Space, indent); + prm_fmtr.Caller_(caller).NewLineArgs_(newLineArgs); + root.Tmpl_fmt(ctx, src, prm_fmtr); + prm_fmtr.Print(bfr); + bfr.Add_byte_nl(); + } + public void Trace_end(int trg_bgn, Bry_bfr trg) { + indent -= 2; + bfr .Add_byte_repeat(Byte_ascii.Space, indent).Add(Ary_result_lbl); + if (trg_bgn < trg.Len()) + bfr .Add_byte_repeat(Byte_ascii.Space, indent).Add_mid(trg.Bfr(), trg_bgn, trg.Len()) + .Add_byte_nl(); + } + public void Print(byte[] src, Bry_bfr bb) { + if (bfr.Len() == 0) return; + if (bb.Len() != 0) bb.Add_byte_nl(); // only add newLine if something in bb; needed for tests + bb .Add(Ary_source_lbl) + .Add(src).Add_byte_nl(); + bb.Add_bfr_and_preserve(bfr); + bfr.Clear(); + } + public void Clear() {bfr.Clear(); indent = 0; count = 0;} + int indent = 0, count = 0; + public static final Xot_defn_trace_dbg Instance = new Xot_defn_trace_dbg(); Xot_defn_trace_dbg() {} + private static final byte[] Ary_invk_lbl = Bry_.new_a7("*invk\n"), Ary_lnk_lbl = Bry_.new_a7("*lnk: "), Ary_args_lbl = Bry_.new_a7("*args\n") + , Ary_result_lbl = Bry_.new_a7("*result\n") + , Ary_eval_lbl = Bry_.new_a7("*eval\n") + , Ary_source_lbl = Bry_.new_a7("*source\n"); +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace_dbg_tst.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace_dbg_tst.java index a27517de8..f6cd4730d 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace_dbg_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace_dbg_tst.java @@ -13,3 +13,35 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xot_defn_trace_dbg_tst { + Xot_defn_trace_fxt fx = new Xot_defn_trace_fxt(); + @Before public void init() { + fx.Init_defn_clear(); + fx.Init_defn_add("print", "{{{1}}}"); + fx.Init_defn_add("concat", "{{{1}}}{{{2}}}"); + fx.Init_defn_add("bool_str", "{{#ifeq:{{{1}}}|1|y|n}}"); + fx.Init_defn_add("mid_1", "{{print|[ {{concat|{{{1}}}|{{{2}}}}} ]}}"); + fx.Ctx().Defn_trace_(Xot_defn_trace_dbg.Instance); + } + @Test public void Tmpl() { + fx.tst_ + ( "{{print|a|key1=b}}" + , "*source" + , "{{print|a|key1=b}}" + , " *invk" + , " {{print|a|key1=b}}" + , " *lnk: [[Template:print]]" + , " *args" + , " 1: a" + , " 2: b" + , " key1: b" + , " *eval" + , " a" + , "*result" + , "a" + ); + } + //@Test public void Basic_b() {fx.tst_("{{mwo_b|2}}" , "[[Test page]]", "00 11 {{mwo_b|1}} -> b");} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace_null.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace_null.java index a27517de8..91d79f7d8 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace_null.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_defn_trace_null.java @@ -13,3 +13,11 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xot_defn_trace_null implements Xot_defn_trace { + public void Clear() {} + public void Trace_bgn(Xop_ctx ctx, byte[] src, byte[] name, Xot_invk caller, Xot_invk self, Xot_defn defn) {} + public void Trace_end(int trg_bgn, Bry_bfr trg) {} + public void Print(byte[] src, Bry_bfr bb) {} + public static final Xot_defn_trace_null Instance = new Xot_defn_trace_null(); Xot_defn_trace_null() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_examples_tst.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_examples_tst.java index a27517de8..fb23ee64b 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_examples_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_examples_tst.java @@ -13,3 +13,117 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xot_examples_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Before public void init() { + Io_mgr.Instance.InitEngine_mem(); + fxt.Reset(); + } + @Test public void Arg_0() {Init_tmpl_for(); fxt.Test_parse_tmpl_str("{{For}}" , "For other uses, see [[Test page (disambiguation)]].");} + @Test public void Arg_1() {Init_tmpl_for(); fxt.Test_parse_tmpl_str("{{For|a}}" , "For a, see [[Test page (disambiguation)]].");} + @Test public void Arg_2() {Init_tmpl_for(); fxt.Test_parse_tmpl_str("{{For|a|b}}" , "For a, see [[b]].");} + @Test public void Arg_3() {Init_tmpl_for(); fxt.Test_parse_tmpl_str("{{For|a|b|c}}" , "For a, see [[b]] and [[c]].");} + @Test public void Arg_4() {Init_tmpl_for(); fxt.Test_parse_tmpl_str("{{For|a|b|c|d}}" , "For a, see [[b]], [[c]], and [[d]].");} + @Test public void Arg_5() {Init_tmpl_for(); fxt.Test_parse_tmpl_str("{{For|a|b|c|d|e}}" , "For a, see [[b]], [[c]], [[d]], and [[e]].");} + @Test public void Arg_1_nil() {Init_tmpl_for(); fxt.Test_parse_tmpl_str("{{For||a|b}}" , "For other uses, see [[a]] and [[b]].");} + @Test public void Main() { + Init_tmpl_main(); fxt.Test_parse_tmpl_str("{{Main|a}}", "Main article: [[a|a]]"); + } + @Test public void About() { + Init_tmpl_about(); fxt.Test_parse_tmpl_str("{{About|abc}}", "This article is about abc. For other uses, see [[Test page (disambiguation)]]."); + } + @Test public void About_2() { // PAGE:en.w:{{About|the NASA space mission||Messenger (disambiguation)}} + Init_tmpl_about(); fxt.Test_parse_tmpl_str("{{About|a||b{{!}}c}}", "This article is about a. For other uses, see [[b|c]]."); + } + @Test public void OtherUses() { + Init_tmpl_other_uses(); fxt.Test_parse_tmpl_str("{{Other uses|abc}}", "For other uses, see [[abc]]."); + } + @Test public void SeeAlso() { + Init_tmpl_see_also(); fxt.Test_parse_tmpl_str("{{See also|abc}}", "See also: [[abc]]"); + } + @Test public void Redirect() { + Init_tmpl_redirect(); fxt.Test_parse_tmpl_str("{{Redirect|abc}}", "\"abc\" redirects here. For other uses, see [[abc (disambiguation)]]."); + } + private void Init_tmpl_main() { + fxt.Init_page_create("Template:Main", String_.Concat_lines_nl + ( "{{#ifeq:{{SUBJECTSPACE}}|Category|The main {{#ifeq:{{NAMESPACE:{{{1}}}}}||article|page}}{{#if:{{{2|}}}|s}} for this [[Wikipedia:Categorization|category]] {{#if:{{{2|}}}|are|is}}|Main {{#ifeq:{{NAMESPACE:{{{1}}}}}||article|page}}{{#if:{{{2|}}}|s}}:}} [[{{{1|{{PAGENAME}}}}}|{{{l1|{{{1|{{PAGENAME}}}}}}}}]]{{#if:{{{2| }}}" + , " |{{#if:{{{3|}}}|, | and }}[[{{{2}}}|{{{l2|{{{2}}}}}}]]}}{{#if:{{{3|}}}" + , " |{{#if:{{{4|}}}|, |, and }}[[{{{3}}}|{{{l3|{{{3}}}}}}]]}}{{#if:{{{4|}}}" + , " |{{#if:{{{5|}}}|, |, and }}[[{{{4}}}|{{{l4|{{{4}}}}}}]]}}{{#if:{{{5|}}}" + , " |{{#if:{{{6|}}}|, |, and }}[[{{{5}}}|{{{l5|{{{5}}}}}}]]}}{{#if:{{{6|}}}" + , " |{{#if:{{{7|}}}|, |, and }}[[{{{6}}}|{{{l6|{{{6}}}}}}]]}}{{#if:{{{7|}}}" + , " |{{#if:{{{8|}}}|, |, and }}[[{{{7}}}|{{{l7|{{{7}}}}}}]]}}{{#if:{{{8|}}}" + , " |{{#if:{{{9|}}}|, |, and }}[[{{{8}}}|{{{l8|{{{8}}}}}}]]}}{{#if:{{{9|}}}" + , " |{{#if:{{{10|}}}|, |, and }}[[{{{9}}}|{{{l9|{{{9}}}}}}]]}}{{#if:{{{10|}}}" + , " |, and [[{{{10}}}|{{{l10|{{{10}}}}}}]]}}{{#if:{{{11| }}}| (too many parameters in {{[[Template:main|main]]}})}}" + )); + } + private void Init_tmpl_for() { + fxt.Init_page_create("Template:For", "For {{#if:{{{1|}}}|{{{1}}}|other uses}}, see [[{{{2|{{PAGENAME}} (disambiguation)}}}]]{{#if:{{{3|}}}|{{#if:{{{4|}}}|, [[{{{3}}}]], {{#if:{{{5|}}}|[[{{{4}}}]], and [[{{{5}}}]]|and [[{{{4}}}]]}}| and [[{{{3}}}]]}}}}."); + } + private void Init_tmpl_other_uses() { + Init_tmpl_about(); + fxt.Init_page_create("Template:Other uses", "{{#if:{{{2|}}}|{{about|||{{{1}}}|and|{{{2|}}}|_nocat=1}}|{{about|||{{{1|{{PAGENAME}} (disambiguation)}}}|_nocat=1}}}}"); + } + private void Init_tmpl_about() { + fxt.Init_page_create("Template:!", "|"); + fxt.Init_page_create("Template:About", String_.Concat_lines_nl + ( "{{#if: {{{1|}}}|This {{#ifeq:{{NAMESPACE}}|{{ns:0}}|article|page}} is about {{{1}}}. }}For {{#if:{{{2|}}}|{{{2}}}|other uses}}, see {{#if:{{{3|}}}|[[{{{3}}}]]{{#ifeq:{{{4|}}}|and| and {{#if:{{{5|}}}|[[{{{5}}}]]|[[{{PAGENAME}} (disambiguation)]]}}}}|[[{{PAGENAME}} (disambiguation)]]}}.{{#if:{{{2|}}}|{{#if:{{{4|}}}|{{#ifeq:{{{4|}}}|and|| For {{#ifeq:{{{4}}}|1|other uses|{{{4}}}}}, see {{#if:{{{5|}}}|[[{{{5}}}]]{{#ifeq:{{{6|}}}|and| and {{#if:{{{7|}}}|[[{{{7}}}]]|[[{{PAGENAME}} (disambiguation)]]}}}}|[[{{PAGENAME}} (disambiguation)]]}}.}}{{#if:{{{6|}}}|{{#ifeq:{{{6|}}}|and|| For {{#ifeq:{{{6}}}|1|other uses|{{{6}}}}}, see {{#if:{{{7|}}}|[[{{{7}}}]]{{#ifeq:{{{8|}}}|and| and {{#if:{{{9|}}}|[[{{{9}}}]]|[[{{PAGENAME}} (disambiguation)]]}}}}|[[{{PAGENAME}} (disambiguation)]]}}.}}{{#if:{{{8|}}}|{{#ifeq:{{{8|}}}|and|| For {{#ifeq:{{{8}}}|1|other uses|{{{8}}}}}, see {{#if:{{{9|}}}|[[{{{9}}}]]|[[{{PAGENAME}} (disambiguation)]]}}.}}}}}}}}}}{{#if:{{{_nocat|}}}||{{#if:{{{1|}}}{{{2|}}}||{{#if:{{{3|}}}|[[Category:Hatnote templates using unusual parameters|A{{PAGENAME}}]]}}}}{{#ifeq:{{str left|{{{1}}}|3}}|is |[[Category:Hatnote templates using unusual parameters|B{{PAGENAME}}]]}}}}" + )); + } + private void Init_tmpl_see_also() { + fxt.Init_page_create("Template:See also", String_.Concat_lines_nl + ( "See also: {{#if:{{{1|}}} |[[{{{1}}}{{#if:{{{label 1|{{{l1|}}}}}}|{{!}}{{{label 1|{{{l1}}}}}}}}]] |'''Error: [[Template:See also|Template must be given at least one article name]]'''" + , "}}{{#if:{{{2|}}}|{{#if:{{{3|}}}|, | and }} [[{{{2}}}{{#if:{{{label 2|{{{l2|}}}}}}|{{!}}{{{label 2|{{{l2}}}}}}}}]]" + , "}}{{#if:{{{3|}}}|{{#if:{{{4|}}}|, |, and }} [[{{{3}}}{{#if:{{{label 3|{{{l3|}}}}}}|{{!}}{{{label 3|{{{l3}}}}}}}}]]" + , "}}{{#if:{{{4|}}}|{{#if:{{{5|}}}|, |, and }} [[{{{4}}}{{#if:{{{label 4|{{{l4|}}}}}}|{{!}}{{{label 4|{{{l4}}}}}}}}]]" + , "}}{{#if:{{{5|}}}|{{#if:{{{6|}}}|, |, and }} [[{{{5}}}{{#if:{{{label 5|{{{l5|}}}}}}|{{!}}{{{label 5|{{{l5}}}}}}}}]]" + , "}}{{#if:{{{6|}}}|{{#if:{{{7|}}}|, |, and }} [[{{{6}}}{{#if:{{{label 6|{{{l6|}}}}}}|{{!}}{{{label 6|{{{l6}}}}}}}}]]" + , "}}{{#if:{{{7|}}}|{{#if:{{{8|}}}|, |, and }} [[{{{7}}}{{#if:{{{label 7|{{{l7|}}}}}}|{{!}}{{{label 7|{{{l7}}}}}}}}]]" + , "}}{{#if:{{{8|}}}|{{#if:{{{9|}}}|, |, and }} [[{{{8}}}{{#if:{{{label 8|{{{l8|}}}}}}|{{!}}{{{label 8|{{{l8}}}}}}}}]]" + , "}}{{#if:{{{9|}}}|{{#if:{{{10|}}}|, |, and }} [[{{{9}}}{{#if:{{{label 9|{{{l9|}}}}}}|{{!}}{{{label 9|{{{l9}}}}}}}}]]" + , "}}{{#if:{{{10|}}}|{{#if:{{{11|}}}|, |, and }} [[{{{10}}}{{#if:{{{label 10|{{{l10|}}}}}}|{{!}}{{{label 10|{{{l10}}}}}}}}]]" + , "}}{{#if:{{{11|}}}|{{#if:{{{12|}}}|, |, and }} [[{{{11}}}{{#if:{{{label 11|{{{l11|}}}}}}|{{!}}{{{label 11|{{{l11}}}}}}}}]]" + , "}}{{#if:{{{12|}}}|{{#if:{{{13|}}}|, |, and }} [[{{{12}}}{{#if:{{{label 12|{{{l12|}}}}}}|{{!}}{{{label 12|{{{l12}}}}}}}}]]" + , "}}{{#if:{{{13|}}}|{{#if:{{{14|}}}|, |, and }} [[{{{13}}}{{#if:{{{label 13|{{{l13|}}}}}}|{{!}}{{{label 13|{{{l13}}}}}}}}]]" + , "}}{{#if:{{{14|}}}|{{#if:{{{15|}}}|, |, and }} [[{{{14}}}{{#if:{{{label 14|{{{l14||}}}}}}|{{!}}{{{label 14|{{{l14}}}}}}}}]]" + , "}}{{#if:{{{15|}}}|, and [[{{{15}}}{{#if:{{{label 15|{{{l15}}}}}}|{{!}}{{{label 15|{{{l15}}}}}}}}]]" + , "}}{{#if:{{{16|}}}| — '''
    Error: [[Template:See also|Too many links specified (maximum is 15)]]'''" + , "}}" + )); + } + private void Init_tmpl_redirect() { + fxt.Init_page_create("Template:Redirect", String_.Concat_lines_nl + ( "\"{{{1}}}\" redirects here. For {{#if:{{{2|}}}|{{{2}}}|other uses}}, see {{#if:{{{3|}}}|[[{{{3}}}]]{{#ifeq:{{{4|}}}|and| and {{#if:{{{5|}}}|[[{{{5}}}]]|[[{{{1}}} (disambiguation)]]}}}}|[[{{{1}}} (disambiguation)]]}}.{{#if:{{{2|}}}|{{#if:{{{4|}}}|{{#ifeq:{{{4|}}}|and|| For {{#ifeq:{{{4}}}|1|other uses|{{{4}}}}}, see {{#if:{{{5|}}}|[[{{{5}}}]]{{#ifeq:{{{6|}}}|and| and {{#if:{{{7|}}}|[[{{{7}}}]]|[[{{{4}}} (disambiguation)]]}}}}|[[{{{4}}} (disambiguation)]]}}.}}{{#if:{{{6|}}}|{{#ifeq:{{{6|}}}|and|| For {{#ifeq:{{{6}}}|1|other uses|{{{6}}}}}, see {{#if:{{{7|}}}|[[{{{7}}}]]{{#ifeq:{{{8|}}}|and| and {{#if:{{{9|}}}|[[{{{9}}}]]|[[{{{6}}} (disambiguation)]]}}}}|[[{{{6}}} (disambiguation)]]}}.}}{{#if:{{{8|}}}|{{#ifeq:{{{8|}}}|and|| For {{#ifeq:{{{8}}}|1|other uses|{{{8}}}}}, see {{#if:{{{9|}}}|[[{{{9}}}]]|[[{{{8}}} (disambiguation)]]}}.}}}}}}}}}}" + )); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_fmtr.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_fmtr.java index a27517de8..5a7fb9189 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_fmtr.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_fmtr.java @@ -13,3 +13,61 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public interface Xot_fmtr { + void Reg_ary(Xop_ctx ctx, byte[] src, boolean tmpl_static, int src_bgn, int src_end, int subs_len, Xop_tkn_itm[] subs); + void Reg_prm(Xop_ctx ctx, byte[] src, Xot_prm_tkn self, int prm_idx, byte[] prm_key, Xop_tkn_itm dflt_tkn); + void Reg_tmpl(Xop_ctx ctx, byte[] src, Xop_tkn_itm name_tkn, int args_len, Arg_nde_tkn[] args); + void Reg_arg(Xop_ctx ctx, byte[] src, int arg_idx, Arg_nde_tkn self_tkn); +} +class Xot_fmtr_prm implements Xot_fmtr { + public Xot_fmtr_prm Caller_(Xot_invk v) {this.caller = v; return this;} private Xot_invk caller; + public Xot_fmtr_prm NewLineArgs_(boolean v) {this.newLineArgs = v; return this;} private boolean newLineArgs = false; + public void Reg_ary(Xop_ctx ctx, byte[] src, boolean tmpl_static, int src_bgn, int src_end, int subs_len, Xop_tkn_itm[] subs) { + if (tmpl_static && src_bgn != -1) trg.Add_mid(src, src_bgn, src_end); // HACK: fails for {{IPA-de|l|lang|De-Ludwig_van_Beethoven.ogg}} + for (int i = 0; i < subs_len; i++) + subs[i].Tmpl_fmt(ctx, src, this); + } + public void Reg_prm(Xop_ctx ctx, byte[] src, Xot_prm_tkn self, int prm_idx, byte[] prm_key, Xop_tkn_itm dflt_tkn) { + if (caller == null) { // raw mode + trg.Add(Bry_bgn); + if (prm_idx == -1) {if (prm_key != null) trg.Add(prm_key);} + else trg.Add_int_variable(prm_idx); + if (dflt_tkn != null) { + trg.Add_byte(Byte_ascii.Pipe); + dflt_tkn.Tmpl_fmt(ctx, src, this); + } + trg.Add(Bry_end); + } + else // invk mode + self.Tmpl_evaluate(ctx, src, caller, trg); + } private static final byte[] Bry_bgn = new byte[] {Byte_ascii.Curly_bgn, Byte_ascii.Curly_bgn, Byte_ascii.Curly_bgn}, Bry_end = new byte[] {Byte_ascii.Curly_end, Byte_ascii.Curly_end, Byte_ascii.Curly_end}; + public void Reg_tmpl(Xop_ctx ctx, byte[] src, Xop_tkn_itm name_tkn, int args_len, Arg_nde_tkn[] args) { + trg.Add(Xop_curly_bgn_lxr.Hook); + ++depth; + name_tkn.Tmpl_fmt(ctx, src, this); + for (int i = 0; i < args_len; i++) { + if (depth == 1 && newLineArgs) trg.Add_byte_nl(); + trg.Add_byte(Byte_ascii.Pipe); + args[i].Tmpl_fmt(ctx, src, this); + } + --depth; + trg.Add(Xop_curly_end_lxr.Hook); + } + public void Write(byte b) {trg.Add_byte(b);} + public void Reg_arg(Xop_ctx ctx, byte[] src, int arg_idx, Arg_nde_tkn self_tkn) { + self_tkn.Key_tkn().Tmpl_fmt(ctx, src, this); + if (self_tkn.KeyTkn_exists()) { + if (arg_idx == 0) { + if (self_tkn.Eq_tkn().Tkn_tid() == Xop_tkn_itm_.Tid_colon) + trg.Add_byte(Byte_ascii.Colon); + } + else + trg.Add_byte(Byte_ascii.Eq); + } + self_tkn.Val_tkn().Tmpl_fmt(ctx, src, this); + } + public void Print(Bry_bfr bb) {bb.Add_bfr_and_preserve(trg); trg.Clear(); depth = 0;} + Bry_bfr trg = Bry_bfr_.New(); int depth = 0; + public static final Xot_fmtr_prm Instance = new Xot_fmtr_prm(); +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk.java index a27517de8..570afd45e 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk.java @@ -13,3 +13,19 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public interface Xot_invk { + byte Defn_tid(); + int Src_bgn(); + int Src_end(); + boolean Frame_is_root(); + byte Frame_tid(); void Frame_tid_(byte v); + byte[] Frame_ttl(); void Frame_ttl_(byte[] v); + int Frame_lifetime(); void Frame_lifetime_(int v); + boolean Rslt_is_redirect(); void Rslt_is_redirect_(boolean v); + int Args_len(); + Arg_nde_tkn Name_tkn(); + Arg_nde_tkn Args_get_by_idx(int i); + Arg_nde_tkn Args_eval_by_idx(byte[] src, int idx); + Arg_nde_tkn Args_get_by_key(byte[] src, byte[] key); +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_mock.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_mock.java index a27517de8..27a93e764 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_mock.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_mock.java @@ -13,3 +13,84 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.xtns.scribunto.*; +public class Xot_invk_mock implements Xot_invk { + Xot_invk_mock(byte defn_tid, int idx_adj, byte[] frame_ttl) { + this.defn_tid = defn_tid; this.idx_adj = idx_adj; this.frame_ttl = frame_ttl; + } private int idx_adj; // SEE NOTE_1: + public int Src_bgn() {return -1;} + public int Src_end() {return -1;} + public byte Defn_tid() {return defn_tid;} private byte defn_tid = Xot_defn_.Tid_null; + public boolean Frame_is_root() {return false;} + public byte Frame_tid() {return scrib_tid;} public void Frame_tid_(byte v) {scrib_tid = v;} private byte scrib_tid; + public byte[] Frame_ttl() {return frame_ttl;} public void Frame_ttl_(byte[] v) {frame_ttl = v;} private byte[] frame_ttl; + public int Frame_lifetime() {return frame_lifetime;} public void Frame_lifetime_(int v) {frame_lifetime = v;} private int frame_lifetime; + public boolean Rslt_is_redirect() {return rslt_is_redirect;} public void Rslt_is_redirect_(boolean v) {rslt_is_redirect = v;} private boolean rslt_is_redirect; + public Arg_nde_tkn Name_tkn() {return Arg_nde_tkn.Null;} + public int Args_len() {return args.Count() + idx_adj;} private Ordered_hash args = Ordered_hash_.New_bry(); + public Arg_nde_tkn Args_get_by_idx(int i) {return (Arg_nde_tkn)args.Get_at(i - idx_adj);} + public Arg_nde_tkn Args_eval_by_idx(byte[] src, int idx) {// DUPE:MW_ARG_RETRIEVE + int cur = 0, list_len = args.Count(); + if (idx >= list_len) return null; + for (int i = 0; i < list_len; i++) { // iterate over list to find nth *non-keyd* arg; SEE:NOTE_1 + Arg_nde_tkn nde = (Arg_nde_tkn)args.Get_at(i); + if (nde.KeyTkn_exists()) { + int key_int = Bry_.To_int_or(nde.Key_tkn().Dat_ary(), -1); + if (key_int == -1) + continue; + else { // key is numeric + if (key_int + idx_adj - 1 == idx) { + return nde; + } + else { + continue; + } + } + } + if ((cur + idx_adj) == idx) return nde; + else ++cur; + } + return Args_get_by_key(src, Bry_.To_a7_bry(idx + 1, 1)); + } + public Arg_nde_tkn Args_get_by_key(byte[] src, byte[] key) {return (Arg_nde_tkn)args.Get_by(key);} + public static Xot_invk_mock new_(byte defn_tid, byte[] frame_ttl, Keyval... args) {return new_(defn_tid, 1, frame_ttl, args);} + public static Xot_invk_mock new_(byte[] frame_ttl, Keyval... args) {return new_(Xot_defn_.Tid_null, 1, frame_ttl, args);} + public static Xot_invk_mock preprocess_(byte[] frame_ttl, Keyval... args) {return new_(Xot_defn_.Tid_null, 1, frame_ttl, args);} + public static Xot_invk_mock test_(byte[] frame_ttl, Keyval... args) {return new_(Xot_defn_.Tid_null, 0, frame_ttl, args);} + public static Xot_invk_mock new_(byte defn_tid, int idx_adj, byte[] frame_ttl, Keyval... args) { + Xot_invk_mock rv = new Xot_invk_mock(defn_tid, idx_adj, frame_ttl); + int len = args.length; + for (int i = 0; i < len; i++) { + Keyval kv = args[i]; + String kv_key_str = kv.Key(); + Object kv_key_obj = kv.Key_as_obj(); + Arg_nde_tkn_mock nde_tkn = null; + if (Type_.Eq_by_obj(kv_key_obj, Int_.Cls_ref_type)) // key is int; EX: 1 => val + nde_tkn = new Arg_nde_tkn_mock(null, kv.Val_to_str_or_empty()); // add w/o key + else if (Type_.Eq_by_obj(kv.Val(), Bool_.Cls_ref_type)) { // val is boolean; EX: key => true || key => false + boolean kv_val_bool = Bool_.Cast(kv.Val()); + if (kv_val_bool) + nde_tkn = new Arg_nde_tkn_mock(kv_key_str, "1"); // true => 1 (PHP behavior) + else + continue; // false => "" (PHP behavior), but dropped from list (emulate MW behavior) wherein Parser.php!argSubstitution silently drops keyVals with value of false; "if ( $text === false && $Object === false )"; DATE:2014-01-02 + } + else // regular nde + nde_tkn = new Arg_nde_tkn_mock(kv_key_str, kv.Val_to_str_or_empty()); // add regular key, val strings + rv.args.Add_if_dupe_use_nth(Bry_.new_u8(kv_key_str), nde_tkn); + } + return rv; + } + public static final Xot_invk_mock Null = new Xot_invk_mock(Xot_defn_.Tid_null, 1, Bry_.Empty); +} +/* +NOTE_1: Xot_invk_mock is being used as a container for two functions +(1) As a substitute for an Invk_tkn; EX: {{#invoke:Mod|Func|arg_1|arg_2}} +. in this case, idx_adj is 1 b/c args will always be 1-based +. EX: Eval_by_idx(1) should return "arg_1". This would be list[0] +. said another way, requested_idx - idx_adj = list_idx; or 1 - 1 = 0 +. Hence, 1 is the idx_adj; +(2) As a substitute for Xot_defn_tmpl_.CopyNew; which occurs in ExpandTemplate +. in this case, idx_adj is 0 b/c args are 0-based +. note that Xot_defn_tmpl_ creates a temporary Object, and its args are 0 based (it doesn't emulate the list[0] for the func_name +*/ \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_sandbox_tst.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_sandbox_tst.java index a27517de8..2377e4152 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_sandbox_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_sandbox_tst.java @@ -13,3 +13,34 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xot_invk_sandbox_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Before public void init() { + fxt.Reset(); + fxt.Init_defn_clear(); + fxt.Init_defn_add("concat", "{{{1}}}{{{2}}}"); + } + @Test public void Basic() { + fxt.Test_parse_tmpl_str("{{concat|a|b}}", "ab"); + } + @Test public void Basic_too_many() { // c gets ignored + fxt.Test_parse_tmpl_str("{{concat|a|b|c}}", "ab"); + } + @Test public void Basic_too_few() { + fxt.Test_parse_tmpl_str("{{concat|a}}", "a{{{2}}}"); + } + @Test public void Basic_else() { + fxt.Init_defn_add("concat", "{{{1}}}{{{2|?}}}"); + fxt.Test_parse_tmpl_str("{{concat|a}}", "a?"); + } + @Test public void Eq_2() { + fxt.Init_defn_add("concat", "{{{lkp1}}}"); + fxt.Test_parse_tmpl_str("{{concat|lkp1=a=b}}", "a=b"); + } + @Test public void Recurse() {fxt.Test_parse_tmpl_str_test("<{{concat|{{{1}}}|{{{2}}}}}>" , "{{test|a|b}}", "");} + @Test public void Recurse_mix() {fxt.Test_parse_tmpl_str_test("{{concat|.{{{1}}}.|{{{2}}}}}" , "{{test|a|b}}", ".a.b");} + @Test public void Recurse_err() {fxt.Test_parse_tmpl_str_test("{{concat|{{{1}}}|{{{2}}}}}" , "{{test1|a|b}}", "[[:Template:test1]]");} // NOTE: make sure test1 does not match test + @Test public void KeyNewLine() {fxt.Test_parse_tmpl_str_test("{{\n concat|a|b}}" , "{{\n test}}", "ab");} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_temp.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_temp.java index a27517de8..c640b673d 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_temp.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_temp.java @@ -13,3 +13,76 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.primitives.*; +import gplx.xowa.xtns.scribunto.*; +public class Xot_invk_temp implements Xot_invk { + private List_adp list = List_adp_.New(); + private Hash_adp_bry arg_key_hash; + private Hash_adp arg_idx_hash; private Int_obj_ref arg_idx_ref; + Xot_invk_temp() {} + public Xot_invk_temp(boolean root_frame) {this.root_frame = root_frame;} + public Xot_invk_temp(byte defn_tid, byte[] src, Arg_nde_tkn name_tkn, int src_bgn, int src_end) { + this.defn_tid = defn_tid; this.src = src; + this.name_tkn = name_tkn; this.src_bgn = src_bgn; this.src_end = src_end; + } + public byte[] Src() {return src;} private byte[] src; public Xot_invk_temp Src_(byte[] src) {this.src = src; return this;} + public byte Defn_tid() {return defn_tid;} private byte defn_tid = Xot_defn_.Tid_null; + public boolean Frame_is_root() {return root_frame;} private boolean root_frame; + public byte Frame_tid() {return scrib_tid;} public void Frame_tid_(byte v) {scrib_tid = v;} private byte scrib_tid; + public byte[] Frame_ttl() {return frame_ttl;} public void Frame_ttl_(byte[] v) {frame_ttl = v;} private byte[] frame_ttl = Bry_.Empty; // NOTE: set frame_ttl to non-null value; PAGE:en.w:Constantine_the_Great {{Christianity}}; DATE:2014-06-26 + public int Frame_lifetime() {return frame_lifetime;} public void Frame_lifetime_(int v) {frame_lifetime = v;} private int frame_lifetime; + public boolean Rslt_is_redirect() {return rslt_is_redirect;} public void Rslt_is_redirect_(boolean v) {rslt_is_redirect = v;} private boolean rslt_is_redirect; + public int Src_bgn() {return src_bgn;} private int src_bgn; + public int Src_end() {return src_end;} private int src_end; + public Arg_nde_tkn Name_tkn() {return name_tkn;} private Arg_nde_tkn name_tkn; + public int Args_len() {return list.Count();} + public Arg_nde_tkn Args_eval_by_idx(byte[] src, int idx) { // NOTE: idx is base0 + return arg_idx_hash == null // only true if no args, or all args are keys; EX: {{A|b=1|c=2}} + ? null + : (Arg_nde_tkn)arg_idx_hash.Get_by(arg_idx_ref.Val_(idx)); // lookup int in hash; needed b/c multiple identical keys should retrieve last, not first; EX: {{A|1=a|1=b}}; PAGE:el.d:ἔχω DATE:2014-07-23 + } + public Arg_nde_tkn Args_get_by_idx(int i) {return (Arg_nde_tkn)list.Get_at(i);} + public Arg_nde_tkn Args_get_by_key(byte[] src, byte[] key) { + return arg_key_hash == null ? null : (Arg_nde_tkn)arg_key_hash.Get_by_bry(key); + } + public void Args_add(Arg_nde_tkn arg) {list.Add(arg);} + public void Args_add_by_key(byte[] key, Arg_nde_tkn arg) { + if (arg_key_hash == null) arg_key_hash = Hash_adp_bry.cs(); // PERF: lazy + arg_key_hash.Add_if_dupe_use_nth(key, arg); + int key_as_int = Bry_.To_int_or(key, Int_.Min_value); + if (key_as_int != Int_.Min_value) // key is int; add it to arg_idx_hash; EX:{{A|1=a}} vs {{A|a}}; DATE:2014-07-23 + Arg_idx_hash_add(key_as_int - List_adp_.Base1, arg); + } + public void Args_add_by_idx(Arg_nde_tkn arg) {Arg_idx_hash_add(++args_add_by_idx, arg);} private int args_add_by_idx = -1; // NOTE: args_add_by_idx needs to be a separate variable; keeps track of args which don't have a key; + private void Arg_idx_hash_add(int int_key, Arg_nde_tkn arg) { + if (arg_idx_hash == null) { + arg_idx_hash = Hash_adp_.New(); + arg_idx_ref = Int_obj_ref.New_neg1(); + } + arg_idx_hash.Add_if_dupe_use_nth(Int_obj_ref.New(int_key), arg); // Add_if_dupe_use_nth to keep latest version; needed for {{A|1=a|1=b}} DATE:2014-07-23 + } + + public static final Xot_invk_temp Page_is_caller = new Xot_invk_temp(true); // SEE NOTE_2 + public static final Xot_invk Null_frame = null; +} +/* +NOTE_1: +The nth arg refers to the nth non-keyd arg; +EX: +defn is {{{1}}} +invk is {{test|key1=a|b}} +{{{1}}} is b b/c b is the 1st non-keyd arg (even though it is the 2nd arg) +*/ +/* +NOTE_2: +Consider a Page with the following Wiki text in the Preview box +WIKI: "a {{mwo_concat|{{{1}}}|b}} c" +TEXT: "a {{{1}}}b c" + +Note that in order to resolve mwo_concat we need to pass in an Xot_invk +This Xot_invk is the "Page_is_caller" ref +Note this has no parameters and is always empty + +Does this static ref have multi-threaded issues? DATE:2017-09-01 +*/ diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_tkn.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_tkn.java index a27517de8..4b5131c77 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_tkn.java @@ -13,3 +13,437 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.envs.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.kwds.*; import gplx.xowa.langs.funcs.*; +import gplx.xowa.xtns.pfuncs.*; import gplx.xowa.xtns.pfuncs.ttls.*; +import gplx.xowa.wikis.pages.*; import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.caches.*; import gplx.xowa.wikis.data.tbls.*; +public class Xot_invk_tkn extends Xop_tkn_itm_base implements Xot_invk { + public Xot_invk_tkn(int bgn, int end) {this.Tkn_ini_pos(false, bgn, end);} + @Override public byte Tkn_tid() {return typeId;} private byte typeId = Xop_tkn_itm_.Tid_tmpl_invk; + public void Tkn_tid_to_txt() {typeId = Xop_tkn_itm_.Tid_txt;} + public Arg_nde_tkn Name_tkn() {return name_tkn;} public Xot_invk_tkn Name_tkn_(Arg_nde_tkn v) {name_tkn = v; return this;} Arg_nde_tkn name_tkn = Arg_nde_tkn.Null; + public byte Defn_tid() {return defn_tid;} private byte defn_tid = Xot_defn_.Tid_null; + public int Tmpl_subst_bgn() {return tmpl_subst_bgn;} private int tmpl_subst_bgn; + public int Tmpl_subst_end() {return tmpl_subst_end;} private int tmpl_subst_end; + public Xot_invk_tkn Tmpl_subst_props_(byte tid, int bgn, int end) {defn_tid = tid; tmpl_subst_bgn = bgn; tmpl_subst_end = end; return this;} + public Xot_defn Tmpl_defn() {return tmpl_defn;} public Xot_invk_tkn Tmpl_defn_(Xot_defn v) {tmpl_defn = v; return this;} private Xot_defn tmpl_defn = Xot_defn_.Null; + public boolean Frame_is_root() {return false;} + public byte Frame_tid() {return scrib_tid;} public void Frame_tid_(byte v) {scrib_tid = v;} private byte scrib_tid; + public byte[] Frame_ttl() {return frame_ttl;} public void Frame_ttl_(byte[] v) {frame_ttl = v;} private byte[] frame_ttl; + public int Frame_lifetime() {return frame_lifetime;} public void Frame_lifetime_(int v) {frame_lifetime = v;} private int frame_lifetime; + public boolean Rslt_is_redirect() {return rslt_is_redirect;} public void Rslt_is_redirect_(boolean v) {rslt_is_redirect = v;} private boolean rslt_is_redirect; + @Override public void Tmpl_fmt(Xop_ctx ctx, byte[] src, Xot_fmtr fmtr) {fmtr.Reg_tmpl(ctx, src, name_tkn, args_len, args);} + @Override public void Tmpl_compile(Xop_ctx ctx, byte[] src, Xot_compile_data prep_data) { + name_tkn.Tmpl_compile(ctx, src, prep_data); + int args_len = this.Args_len(); + for (int i = 0; i < args_len; i++) { + Arg_nde_tkn nde = args[i]; + Xop_tkn_itm key = nde.Key_tkn(); int key_subs_len = key.Subs_len(); + for (int j = 0; j < key_subs_len; j++) + key.Subs_get(j).Tmpl_compile(ctx, src, prep_data); + Xop_tkn_itm val = nde.Val_tkn(); int val_subs_len = val.Subs_len(); + for (int j = 0; j < val_subs_len; j++) + val.Subs_get(j).Tmpl_compile(ctx, src, prep_data); + } + } + @Override public boolean Tmpl_evaluate(Xop_ctx ctx, byte[] src, Xot_invk caller, Bry_bfr bfr) { // this="{{t|{{{0}}}}}" caller="{{t|1}}" + // init common + boolean rv = false; + Xowe_wiki wiki = ctx.Wiki(); + Xol_lang_itm lang = wiki.Lang(); + + // init defn / name + Xot_defn defn = tmpl_defn; + byte[] name_ary = defn.Name(); + byte[] name_ary_orig = Bry_.Empty; + int name_bgn = 0, name_ary_len = 0; + Arg_itm_tkn name_key_tkn = name_tkn.Key_tkn(); + + // init more + byte[] argx_ary = Bry_.Empty; + boolean subst_found = false; + boolean name_had_subst = false; + boolean template_prefix_found = false; + + // tmpl_name does not exist in db; may be dynamic, subst, transclusion, etc.. + if (defn == Xot_defn_.Null) { + // dynamic tmpl; EX:{{{{{1}}}|a}} + if (name_key_tkn.Itm_static() == Bool_.N_byte) { + Bry_bfr name_tkn_bfr = Bry_bfr_.New_w_size(name_tkn.Src_end() - name_tkn.Src_bgn()); + if (defn_tid == Xot_defn_.Tid_subst) + name_tkn_bfr.Add(Get_first_subst_itm(lang.Kwd_mgr())); + name_tkn.Tmpl_evaluate(ctx, src, caller, name_tkn_bfr); + name_ary = name_tkn_bfr.To_bry_and_clear(); + } + // tmpl is static; note that dat_ary is still valid but rest of name may not be; EX: {{subst:name{{{1}}}}} + else + name_ary = Bry_.Mid(src, name_key_tkn.Dat_bgn(), name_key_tkn.Dat_end()); + name_had_subst = name_key_tkn.Dat_ary_had_subst(); + name_ary_orig = name_ary; // cache name_ary_orig + name_ary_len = name_ary.length; + name_bgn = Bry_find_.Find_fwd_while_not_ws(name_ary, 0, name_ary_len); + if ( name_ary_len == 0 // name is blank; can occur with failed inner tmpl; EX: {{ {{does not exist}} }} + || name_bgn == name_ary_len // name is ws; EX: {{test| }} -> {{{{{1}}}}}is whitespace String; PAGE:en.d:wear_one's_heart_on_one's_sleeve; EX:{{t+|fr|avoir le cœur sur la main| }} + ) { + Gfo_usr_dlg_.Instance.Log_many("", "", "parser.tmpl:dynamic is blank; page=~{0}", ctx.Page().Url_bry_safe()); // downgraded from warning to note; PAGE:de.d:país DATE:2016-09-07 + return false; + } + if (name_ary[name_bgn] == Byte_ascii.Colon) { // check 1st letter for transclusion + return Transclude(ctx, wiki, bfr, Bool_.N, name_ary, caller, src); // transclusion; EX: {{:Name of page}} + } + + // ignore "{{Template:"; EX: {{Template:a}} is the same thing as {{a}} + int tmpl_ns_len = wiki.Ns_mgr().Tmpls_get_w_colon(name_ary, name_bgn, name_ary_len); + if (tmpl_ns_len != Bry_find_.Not_found) { + name_ary = Bry_.Mid(name_ary, name_bgn + tmpl_ns_len, name_ary_len); + name_ary_len = name_ary.length; + name_bgn = 0; + template_prefix_found = true; + } + byte[] ns_template_prefix = wiki.Ns_mgr().Ns_template().Name_db_w_colon(); int ns_template_prefix_len = ns_template_prefix.length; + if (name_ary_len > ns_template_prefix_len && Bry_.Match(name_ary, name_bgn, name_bgn + ns_template_prefix_len, ns_template_prefix)) { + name_ary = Bry_.Mid(name_ary, name_bgn + ns_template_prefix_len, name_ary_len); + name_ary_len = name_ary.length; + name_bgn = 0; + } + + Object ns_eval = wiki.Ns_mgr().Names_get_w_colon(name_ary, 0, name_ary_len); // match {{:Portal or {{:Wikipedia + if (ns_eval != null && !template_prefix_found) // do not transclude ns if Template prefix seen earlier; EX: {{Template:Wikipedia:A}} should not transclude "Wikipedia:A"; DATE:2013-04-03 + return SubEval(ctx, wiki, bfr, name_ary, caller, src); + + Xol_func_itm finder = new Xol_func_itm(); // TS.MEM: DATE:2016-07-12 + lang.Func_regy().Find_defn(finder, name_ary, name_bgn, name_ary_len); + defn = finder.Func(); + int finder_tid = finder.Tid(); + int finder_colon_pos = finder.Colon_pos(); + int finder_subst_end = finder.Subst_end(); + + int colon_pos = -1; + switch (finder_tid) { + case Xot_defn_.Tid_subst: // subst is added verbatim; EX: {{subst:!}} -> {{subst:!}}; logic below is to handle printing of arg which could be standardized if src[] was available for tmpl + bfr.Add(Xop_curly_bgn_lxr.Hook).Add(name_ary); + for (int i = 0; i < args_len; i++) { + Arg_nde_tkn nde = args[i]; + bfr.Add_byte(Byte_ascii.Pipe); // add | + bfr.Add_mid(src, nde.Src_bgn(), nde.Src_end()); // add entire arg; "k=v"; note that src must be added, not evaluated, else may be dropped and cause stack overflow; PAGE:ru.w:Близкие_друзья_(Сезон_2) DATE:2014-10-21 + } + Xot_fmtr_prm.Instance.Print(bfr); + bfr.Add(Xop_curly_end_lxr.Hook); + return true; // NOTE: nothing else to do; return + case Xot_defn_.Tid_safesubst: + name_ary = Bry_.Mid(name_ary, finder_subst_end, name_ary_len); // chop off "safesubst:" + name_ary_len = name_ary.length; + if (defn != Xot_defn_.Null) { // func found + if (finder_colon_pos != -1) colon_pos = defn.Name().length; // set colon_pos; SEE NOTE_1 + } + subst_found = true; + break; + case Xot_defn_.Tid_func: + if (defn.Defn_require_colon_arg()) { + colon_pos = Bry_find_.Find_fwd(name_ary, Byte_ascii.Colon); + if (colon_pos == Bry_find_.Not_found) + defn = Xot_defn_.Null; + } + else { + colon_pos = finder_colon_pos; + } + break; + case Xot_defn_.Tid_raw: + case Xot_defn_.Tid_msg: + int raw_colon_pos = Bry_find_.Find_fwd(name_ary, Byte_ascii.Colon); + if (raw_colon_pos == Bry_find_.Not_found) {} // colon missing; EX: {{raw}}; noop and assume template name; DATE:2014-02-11 + else { // colon present; + name_ary = Bry_.Mid(name_ary, finder_subst_end + 1, name_ary_len); // chop off "raw"; +1 is for ":"; note that +1 is in bounds b/c raw_colon was found + name_ary_len = name_ary.length; + Object ns_eval2 = wiki.Ns_mgr().Names_get_w_colon(name_ary, 0, name_ary_len); // match {{:Portal or {{:Wikipedia + if (ns_eval2 != null) { + Xow_ns ns_eval_itm = (Xow_ns)ns_eval2; + int pre_len = ns_eval_itm.Name_enc().length; + if (pre_len < name_ary_len && name_ary[pre_len] == Byte_ascii.Colon) + return SubEval(ctx, wiki, bfr, name_ary, caller, src); + } + } + switch (finder_tid) { + case Xot_defn_.Tid_msg: + defn = Xot_defn_.Null; // null out defn to force template load below; DATE:2014-07-10 + break; + } + break; + } + if (subst_found) {// subst found; remove Template: if it exists; EX: {{safesubst:Template:A}} -> {{A}} not {{Template:A}}; EX:en.d:Kazakhstan; DATE:2014-03-25 + ns_template_prefix = wiki.Ns_mgr().Ns_template().Name_db_w_colon(); ns_template_prefix_len = ns_template_prefix.length; + if (name_ary_len > ns_template_prefix_len && Bry_.Match(name_ary, name_bgn, name_bgn + ns_template_prefix_len, ns_template_prefix)) { + name_ary = Bry_.Mid(name_ary, name_bgn + ns_template_prefix_len, name_ary_len); + name_ary_len = name_ary.length; + name_bgn = 0; + template_prefix_found = true; + } + } + if (colon_pos != -1) { // func; separate name_ary into name_ary and arg_x + argx_ary = Bry_.Trim(name_ary, colon_pos + 1, name_ary_len); // trim bgn ws; needed for "{{formatnum:\n{{#expr:2}}\n}}" + name_ary = Bry_.Mid(name_ary, 0, colon_pos); + } + if (defn == Xot_defn_.Null) { + if (ctx.Tid_is_popup()) { // popup && cur_tmpl > tmpl_max + if (Popup_skip(ctx, name_ary, bfr)) return false; + } + defn = wiki.Cache_mgr().Defn_cache().Get_by_key(name_ary); + if (defn == null) { + if (name_ary_len != 0 ) { // name_ary_len != 0 for direct template inclusions; PAGE:en.w:Human evolution and {{:Human evolution/Species chart}}; && ctx.Tmpl_whitelist().Has(name_ary) + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, Bry_.Add(wiki.Ns_mgr().Ns_template().Name_db_w_colon(), name_ary)); + if (ttl == null) { // ttl is not valid; just output orig; REF.MW:Parser.php|braceSubstitution|if ( !$found ) $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args ); + if (subst_found || template_prefix_found) { // if "subst:" or "Template:" found, use orig name; DATE:2014-03-31 + bfr.Add(Xop_curly_bgn_lxr.Hook).Add(name_ary_orig).Add(Xop_curly_end_lxr.Hook); + return false; + } + else {// output entire tmpl_src WITH args; used to output name only which broke pages; PAGE:en.w:Flag_of_Greenland; DATE:2016-06-21 + bfr.Add(Xop_curly_bgn_lxr.Hook); + bfr.Add(name_ary); + for (int i = 0; i < args_len; ++i) { + Arg_nde_tkn nde = this.Args_get_by_idx(i); + bfr.Add_byte(Byte_ascii.Pipe); + // must evaluate args; "size={{{size|}}}" must become "size="; PAGE:en.w:Europe; en.w:Template:Country_data_Guernsey DATE:2016-10-13 + nde.Tmpl_compile(ctx, src, Xot_compile_data.Noop); + nde.Tmpl_evaluate(ctx, src, caller, bfr); + } + bfr.Add(Xop_curly_end_lxr.Hook); + return false; + } + } + else { // some templates produce null ttls; EX: "Citation needed{{subst" + defn = wiki.Cache_mgr().Defn_cache().Get_by_key(ttl.Page_db()); + if (defn == null && ctx.Tmpl_load_enabled()) + defn = Xot_invk_tkn_.Load_defn(wiki, ctx, this, ttl, name_ary); + } + } + } + if (defn == null) defn = Xot_defn_.Null; + } + } + if ( defn.Defn_tid() == Xot_defn_.Tid_null // name is not a known defn + && lang.Vnt_mgr().Enabled()) { // lang has vnts + Xowd_page_itm page = lang.Vnt_mgr().Convert_mgr().Convert_ttl(wiki, wiki.Ns_mgr().Ns_template(), name_ary); + if (page != Xowd_page_itm.Null) { + name_ary = page.Ttl_page_db(); + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, Bry_.Add(wiki.Ns_mgr().Ns_template().Name_db_w_colon(), name_ary)); + if (ttl == null) { // ttl is not valid; just output orig; REF.MW:Parser.php|braceSubstitution|if ( !$found ) $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args ); + bfr.Add(Xop_curly_bgn_lxr.Hook).Add(name_ary).Add(Xop_curly_end_lxr.Hook); + return false; + } + defn = wiki.Cache_mgr().Defn_cache().Get_by_key(name_ary); + if (defn == null && ctx.Tmpl_load_enabled()) + defn = Xot_invk_tkn_.Load_defn(wiki, ctx, this, ttl, name_ary); + if (defn == null) defn = Xot_defn_.Null; + } + } + Xot_defn_trace trace = ctx.Defn_trace(); int trg_bgn = bfr.Len(); + switch (defn.Defn_tid()) { + case Xot_defn_.Tid_null: // defn is unknown + if (ignore_hash.Get_by_bry(name_ary) == null) { + if (Pfunc_rel2abs.Rel2abs_ttl(name_ary, name_bgn, name_ary_len)) {// rel_path; EX: {{/../Peer page}}; DATE:2013-03-27 + Bry_bfr tmp_bfr = ctx.Wiki().Utl__bfr_mkr().Get_b512(); + name_ary = Pfunc_rel2abs.Rel2abs(tmp_bfr, wiki.Parser_mgr().Rel2abs_ary(), Bry_.Mid(name_ary, name_bgn, name_ary_len), ctx.Page().Ttl().Raw()); + tmp_bfr.Mkr_rls(); + return SubEval(ctx, wiki, bfr, name_ary, caller, src); + } + if (subst_found) + return Transclude(ctx, wiki, bfr, template_prefix_found, name_ary, caller, src); + Xot_invk_tkn_.Print_not_found__w_template(bfr, wiki.Ns_mgr(), name_ary); + return false; + } + break; + case Xot_defn_.Tid_func: + try { + Xot_invk_tkn_.Eval_func(ctx, src, caller, this, bfr, defn, argx_ary); + rv = true; + } catch (Exception e) { + if (Env_.Mode_testing()) + throw Err_.new_exc(e, "xo", "failed to evaluate function", "page", ctx.Page().Ttl().Full_txt_w_ttl_case(), "defn", defn.Name(), "src", String_.new_u8(src, this.Src_bgn(), this.Src_end())); + else { + wiki.Appe().Usr_dlg().Warn_many("", "", "failed to evaluate function: page=~{0} defn=~{1} src=~{2} err=~{3}", ctx.Page().Ttl().Full_txt_w_ttl_case(), defn.Name(), Bry_.Replace_nl_w_tab(src, this.Src_bgn(), this.Src_end()), Err_.Message_gplx_log(e)); + rv = false; + } + } + break; + default: + Xot_defn_tmpl defn_tmpl = (Xot_defn_tmpl)defn; + if (defn_tmpl.Root() == null) defn_tmpl.Parse_tmpl(ctx); // NOTE: defn_tmpl.Root can be null after clearing out cache; must be non-null else will fail in trace; DATE:2013-02-01 + Xot_invk invk_tmpl = Xot_defn_tmpl_.CopyNew(ctx, defn_tmpl, this, caller, src, name_ary); + invk_tmpl.Frame_ttl_(defn_tmpl.Frame_ttl()); // set frame_ttl; needed for redirects; PAGE:en.w:Statutory_city; DATE:2014-08-22 + trace.Trace_bgn(ctx, src, name_ary, caller, invk_tmpl, defn); + + Bry_bfr rslt_bfr = wiki.Utl__bfr_mkr().Get_k004(); + try { + Xot_invk_tkn_.Bld_key(invk_tmpl, name_ary, rslt_bfr); + byte[] rslt_key = rslt_bfr.To_bry_and_clear(); + Object o = wiki.Cache_mgr().Tmpl_result_cache().Get_by(rslt_key); + Xopg_tmpl_prepend_mgr prepend_mgr = ctx.Page().Tmpl_prepend_mgr().Bgn(bfr); + if (o != null) { + byte[] rslt = (byte[])o; + prepend_mgr.End(ctx, bfr, rslt, rslt.length, Bool_.Y); + bfr.Add(rslt); + } + else { + rv = defn_tmpl.Tmpl_evaluate(Xop_ctx.New__sub(wiki, ctx, ctx.Page()), invk_tmpl, rslt_bfr); // create new ctx so __NOTOC__ only applies to template, not page; PAGE:de.w:13._Jahrhundert DATE:2017-06-17 + prepend_mgr.End(ctx, bfr, rslt_bfr.Bfr(), rslt_bfr.Len(), Bool_.Y); + if (name_had_subst) { // current invk had "subst:"; parse incoming invk again to remove effects of subst; PAGE:pt.w:Argentina DATE:2014-09-24 + byte[] tmp_src = rslt_bfr.To_bry_and_clear(); + rslt_bfr.Add(wiki.Parser_mgr().Main().Expand_tmpl(tmp_src)); // this could be cleaner / more optimized + } + if (Cache_enabled) { + byte[] rslt_val = rslt_bfr.To_bry_and_clear(); + bfr.Add(rslt_val); + Hash_adp cache = wiki.Cache_mgr().Tmpl_result_cache(); + cache.Del(rslt_key); + cache.Add(rslt_key, rslt_val); + } + else + bfr.Add_bfr_and_clear(rslt_bfr); + } + trace.Trace_end(trg_bgn, bfr); + } finally {rslt_bfr.Mkr_rls();} + break; + } + return rv; + } + private boolean Popup_skip(Xop_ctx ctx, byte[] ttl, Bry_bfr bfr) { + boolean skip = false; + skip = this.Src_end() - this.Src_bgn() > ctx.Tmpl_tkn_max(); + if (!skip) { + gplx.xowa.htmls.modules.popups.keeplists.Xop_keeplist_wiki tmpl_keeplist = ctx.Tmpl_keeplist(); + if (tmpl_keeplist != null && tmpl_keeplist.Enabled()) { + byte[] ttl_lower = Xoa_ttl.Replace_spaces(ctx.Wiki().Lang().Case_mgr().Case_build_lower(ttl)); + skip = !tmpl_keeplist.Match(ttl_lower); + // if (skip) Tfds.Write_bry(ttl_lower); + } + } + if (skip) { + bfr.Add(gplx.xowa.parsers.miscs.Xop_comm_lxr.Xowa_skip_comment_bry); // add comment tkn; need something to separate ''{{lang|la|Ragusa}}'' else will become ''''; PAGE:en.w:Republic_of_Ragusa; DATE:2014-06-28 + return true; + } + else + return false; + } + private boolean Transclude(Xop_ctx ctx, Xowe_wiki wiki, Bry_bfr bfr, boolean template_prefix_found, byte[] name_ary, Xot_invk caller, byte[] src) { + Xoa_ttl page_ttl = Xoa_ttl.Parse(wiki, name_ary); if (page_ttl == null) return false; // ttl not valid; EX: {{:[[abc]]}} + byte[] transclude_src = null; + if (page_ttl.Ns().Id_is_tmpl()) { // ttl is template; check tmpl_regy first before going to data_mgr + Xot_defn_tmpl tmpl = (Xot_defn_tmpl)wiki.Cache_mgr().Defn_cache().Get_by_key(page_ttl.Page_db()); + if (tmpl != null) transclude_src = tmpl.Data_raw(); + } + if (transclude_src == null && ctx.Tmpl_load_enabled()) { // ttl is template not in cache, or some other ns; do load + Xow_page_cache_itm cache_itm = wiki.Cache_mgr().Page_cache().Get_or_load_as_itm(page_ttl); + if ( cache_itm != null +// && Bry_.Eq(cache_itm.Ttl().Full_db(), ctx.Page().Page_ttl().Full_db()) // make sure that transcluded item is not same as page_ttl; DATE:2014-01-10 + ) { + transclude_src = cache_itm.Wtxt__direct(); + page_ttl = cache_itm.Ttl(); + } + } + if (transclude_src != null) { + // NOTE: must use new page, not current, else transcluded page can cause inconsistent TOC state; PAGE:de.w:Game_of_Thrones DATE:2016-11-21 + Xot_defn_tmpl transclude_tmpl = ctx.Wiki().Parser_mgr().Main().Parse_text_to_defn_obj(Xop_ctx.New__sub(wiki, ctx, Xoae_page.New(wiki, page_ttl)), ctx.Tkn_mkr(), page_ttl.Ns(), page_ttl.Page_db(), transclude_src); + return Eval_sub(ctx, transclude_tmpl, caller, src, bfr); + } + else { + Xot_invk_tkn_.Print_not_found__by_transclude(bfr, wiki.Ns_mgr(), template_prefix_found, name_ary); + return false; + } + } + private boolean Eval_sub(Xop_ctx ctx, Xot_defn_tmpl transclude_tmpl, Xot_invk caller, byte[] src, Bry_bfr doc) { + boolean rv = false; + Xot_invk tmp_tmpl = Xot_defn_tmpl_.CopyNew(ctx, transclude_tmpl, this, caller, src, transclude_tmpl.Name()); + Bry_bfr tmp_bfr = Bry_bfr_.New(); + Xopg_tmpl_prepend_mgr prepend_mgr = ctx.Page().Tmpl_prepend_mgr().Bgn(doc); + rv = transclude_tmpl.Tmpl_evaluate(ctx, tmp_tmpl, tmp_bfr); + prepend_mgr.End(ctx, doc, tmp_bfr.Bfr(), tmp_bfr.Len(), Bool_.Y); + doc.Add_bfr_and_clear(tmp_bfr); + return rv; + } + private boolean SubEval(Xop_ctx ctx, Xowe_wiki wiki, Bry_bfr bfr, byte[] name_ary, Xot_invk caller, byte[] src_for_tkn) { + Xoa_ttl page_ttl = Xoa_ttl.Parse(wiki, name_ary); if (page_ttl == null) return false; // ttl not valid; EX: {{:[[abc]]}} + Xot_defn_tmpl transclude_tmpl = null; + switch (page_ttl.Ns().Id()) { + case Xow_ns_.Tid__template: // ttl is template not in cache, or some other ns; do load + Xot_defn_tmpl tmpl = (Xot_defn_tmpl)wiki.Cache_mgr().Defn_cache().Get_by_key(page_ttl.Page_db()); + if (tmpl != null) { + if (tmpl.Root() == null) tmpl.Parse_tmpl(ctx); + transclude_tmpl = tmpl; + } + break; + case Xow_ns_.Tid__special: + bfr.Add(Xop_tkn_.Lnki_bgn).Add_byte(Byte_ascii.Colon).Add(name_ary).Add(Xop_tkn_.Lnki_end); + return true; + } + if (transclude_tmpl == null && ctx.Tmpl_load_enabled()) { // ttl is template not in cache, or some other ns; do load + Xow_page_cache_itm cache_itm = wiki.Cache_mgr().Page_cache().Get_or_load_as_itm(page_ttl); + if ( cache_itm != null) { + if (!Bry_.Eq(cache_itm.Ttl().Full_db(), ctx.Page().Ttl().Full_db())) { // make sure that transcluded item is not same as page_ttl; DATE:2014-01-10 + transclude_tmpl = ctx.Wiki().Parser_mgr().Main().Parse_text_to_defn_obj(ctx, ctx.Tkn_mkr(), page_ttl.Ns(), page_ttl.Page_db(), cache_itm.Wtxt__direct()); + page_ttl = cache_itm.Ttl(); + } + } + } + if (transclude_tmpl == null) { + bfr.Add(Xop_tkn_.Lnki_bgn).Add(name_ary).Add(Xop_tkn_.Lnki_end); // indicate template was not found; DATE:2014-02-12 + return false; + } + return Eval_sub(ctx, transclude_tmpl, caller, src_for_tkn, bfr); + } + public int Args_len() {return args_len;} private int args_len = 0; + public Arg_nde_tkn Args_get_by_idx(int idx) {return args[idx];} + public Arg_nde_tkn Args_eval_by_idx(byte[] src, int idx) { + int cur = 0; + for (int i = 0; i < args_len; i++) { + Arg_nde_tkn nde = args[i]; + if (nde.KeyTkn_exists()) continue; + if (cur++ == idx) return nde; + } + return null; + } + public Arg_nde_tkn Args_get_by_key(byte[] src, byte[] key) { + for (int i = 0; i < args_len; i++) { + Arg_nde_tkn nde = args[i]; + if (!nde.KeyTkn_exists()) continue; + if (Bry_.Match(src, nde.Key_tkn().Dat_bgn(), nde.Key_tkn().Dat_end(), key)) return nde; // NOTE: dat_ary is guaranteed to exist + } + return null; + } + public void Args_add(Xop_ctx ctx, Arg_nde_tkn arg) { + int newLen = args_len + 1; + if (newLen > args_max) { + args_max = newLen * 2; + args = Resize(args, args_len, args_max); + } + args[args_len] = arg; + arg.Tkn_grp_(this, args_len); + args_len = newLen; + } Arg_nde_tkn[] args = Arg_nde_tkn.Ary_empty; int args_max; + private Arg_nde_tkn[] Resize(Arg_nde_tkn[] src, int cur_len, int new_len) { + Arg_nde_tkn[] rv = new Arg_nde_tkn[new_len]; + Array_.Copy_to(src, 0, rv, 0, cur_len); + return rv; + } + private byte[] Get_first_subst_itm(Xol_kwd_mgr kwd_mgr) { + Xol_kwd_grp grp = kwd_mgr.Get_at(Xol_kwd_grp_.Id_subst); if (grp == null) return Bry_.Empty; + Xol_kwd_itm[] itms = grp.Itms(); + return itms.length == 0 ? Bry_.Empty : itms[0].Val(); + } + private static final Hash_adp_bry ignore_hash = Hash_adp_bry.ci_a7().Add_str_obj("Citation needed{{subst", "").Add_str_obj("Clarify{{subst", ""); // ignore SafeSubst templates + public static boolean Cache_enabled = false; +} +/* +NOTE_1: if (finder.Colon_pos() != -1) colon_pos = finder.Func().Name().length; +Two issues here: +1) "if (finder.Colon_pos() != -1)" + Colon_pos can either be -1 or >0 + EX: -1: safesubst:NAMESPACE + EX: >0: safesubst:#expr:0 + if -1, don't do anything; this will leave colon_pos as -1 +2) "finder.Func().Name().length" + Colon_pos is >0, but refers to String before it was chopped + EX: "safesubst:#expr:0" has Colon_pos of 15 but String is now "#expr:0" + so, get colon_pos by taking the finder.Func().Name().length + NOTE: whitespace does not matter b/c "safesubst: #expr:0" would never be a func; +*/ \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_tkn_.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_tkn_.java index a27517de8..0317f2ee6 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_tkn_.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_tkn_.java @@ -13,3 +13,67 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.langs.kwds.*; +import gplx.xowa.xtns.pfuncs.*; +import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.caches.*; +public class Xot_invk_tkn_ { + public static void Eval_func(Xop_ctx ctx, byte[] src, Xot_invk caller, Xot_invk invk, Bry_bfr bfr, Xot_defn defn, byte[] argx_ary) { + Pf_func_base defn_func = (Pf_func_base)defn; + int defn_func_id = defn_func.Id(); + defn_func = (Pf_func_base)defn_func.New(defn_func_id, defn_func.Name()); // NOTE: always make copy b/c argx_ary may be dynamic + if (argx_ary != Bry_.Empty) defn_func.Argx_dat_(argx_ary); + defn_func.Eval_argx(ctx, src, caller, invk); + if (defn_func_id == Xol_kwd_grp_.Id_invoke) // NOTE: if #invoke, set frame_ttl to argx, not name; EX:{{#invoke:A}} + invk.Frame_ttl_(Bry_.Add(Xow_ns_.Bry__module_w_colon, Xoa_ttl.Replace_unders(defn_func.Argx_dat()))); // NOTE: always prepend "Module:" to frame_ttl; DATE:2014-06-13; NOTE: always use spaces; DATE:2014-08-14; always use canonical English "Module"; DATE:2015-11-09 + Bry_bfr bfr_func = Bry_bfr_.New(); + defn_func.Func_evaluate(bfr_func, ctx, caller, invk, src); + if (caller.Rslt_is_redirect()) // do not prepend if page is redirect; EX:"#REDIRECT" x> "\n#REDIRECT" DATE:2014-07-11 + caller.Rslt_is_redirect_(false); // reset flag; needed for TEST; kludgy, but Rslt_is_redirect is intended for single use + else + ctx.Page().Tmpl_prepend_mgr().End(ctx, bfr, bfr_func.Bfr(), bfr_func.Len(), Bool_.N); + bfr.Add_bfr_and_clear(bfr_func); + } + public static void Bld_key(Xot_invk invk, byte[] name_ary, Bry_bfr bfr) { + bfr.Clear(); + bfr.Add(name_ary); + int args_len = invk.Args_len(); + for (int i = 0; i < args_len; i++) { + Arg_nde_tkn nde = invk.Args_get_by_idx(i); + bfr.Add_byte(Byte_ascii.Pipe); + if (nde.KeyTkn_exists()) { + bfr.Add(nde.Key_tkn().Dat_ary()); + bfr.Add_byte(Byte_ascii.Eq); + } + bfr.Add(nde.Val_tkn().Dat_ary()); + } + } + public static Xot_defn_tmpl Load_defn(Xowe_wiki wiki, Xop_ctx ctx, Xot_invk_tkn invk_tkn, Xoa_ttl ttl, byte[] name_ary) { // recursive loading of templates + Xow_page_cache_itm tmpl_page_itm = wiki.Cache_mgr().Page_cache().Get_or_load_as_itm(ttl); + byte[] tmpl_page_bry = tmpl_page_itm == null ? null : tmpl_page_itm.Wtxt__direct(); + Xot_defn_tmpl rv = null; + if (tmpl_page_bry != null) { + byte old_parse_tid = ctx.Parse_tid(); // NOTE: reusing ctxs is a bad idea; will change Parse_tid and cause strange errors; however, keeping for PERF reasons + Xow_ns ns_tmpl = wiki.Ns_mgr().Ns_template(); + rv = wiki.Parser_mgr().Main().Parse_text_to_defn_obj(Xop_ctx.New__sub(wiki, ctx, ctx.Page()), ctx.Tkn_mkr(), ns_tmpl, name_ary, tmpl_page_bry); // create new ctx so __NOTOC__ only applies to template, not page; PAGE:de.w:13._Jahrhundert DATE:2017-06-17 + Xoa_ttl tmpl_page_ttl = tmpl_page_itm.Ttl(); + byte[] frame_ttl = Bry_.Add(tmpl_page_ttl.Ns().Name_db(), Byte_ascii.Colon_bry, tmpl_page_ttl.Page_txt()); // NOTE: (1) must have ns (Full); (2) must be txt (space, not underscore); EX:Template:Location map+; DATE:2014-08-22; (3) must be local language; Russian "Шаблон" not English "Template"; PAGE:ru.w:Королевство_Нидерландов DATE:2016-11-23 + rv.Frame_ttl_(frame_ttl); // set defn's frame_ttl; needed for redirect_trg; PAGE:en.w:Statutory_city; DATE:2014-08-22 + ctx.Parse_tid_(old_parse_tid); + wiki.Cache_mgr().Defn_cache().Add(rv, ns_tmpl.Case_match()); + } + return rv; + } + public static void Print_not_found__by_transclude(Bry_bfr bfr, Xow_ns_mgr ns_mgr, boolean name_has_template, byte[] name_ary) {// print missing as [[Missing]]; PAGE:en.d:a DATE:2016-06-24 + bfr.Add(Xop_tkn_.Lnki_bgn); + if (name_has_template) + bfr.Add(ns_mgr.Ns_template().Name_db()).Add_byte(Byte_ascii.Colon); + bfr.Add(name_ary); + bfr.Add(Xop_tkn_.Lnki_end); + } + public static void Print_not_found__w_template(Bry_bfr bfr, Xow_ns_mgr ns_mgr, byte[] name_ary) { // print missing as [[:Template:Missing]]; REF:MW: Parser.php|braceSubstitution|$text = "[[:$titleText]]"; EX:en.d:Kazakhstan; DATE:2014-03-25 + bfr.Add(Xop_tkn_.Lnki_bgn).Add_byte(Byte_ascii.Colon); + bfr.Add(ns_mgr.Ns_template().Name_db()).Add_byte(Byte_ascii.Colon); + bfr.Add(name_ary).Add(Xop_tkn_.Lnki_end); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_tkn_chkr.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_tkn_chkr.java index a27517de8..d74861944 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_tkn_chkr.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_tkn_chkr.java @@ -13,3 +13,24 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xot_invk_tkn_chkr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Xot_invk_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_tmpl_invk;} + public Xop_tkn_chkr_base Name_tkn() {return name_tkn;} public Xot_invk_tkn_chkr Name_tkn_(Xop_tkn_chkr_base v) {name_tkn = v; return this;} private Xop_tkn_chkr_base name_tkn; + public Xop_tkn_chkr_base[] Args() {return args;} public Xot_invk_tkn_chkr Args_(Xop_tkn_chkr_base... v) {args = v; return this;} private Xop_tkn_chkr_base[] args = Xop_tkn_chkr_base.Ary_empty; + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { + Xot_invk_tkn actl = (Xot_invk_tkn)actl_obj; + if (name_tkn != null) err += mgr.Tst_sub_obj(name_tkn, actl.Name_tkn(), path + "." + "name", err); + err += mgr.Tst_sub_ary(args, Args_xtoAry(actl), path + "." + "args", err); + return err; + } + Arg_nde_tkn[] Args_xtoAry(Xot_invk_tkn tkn) { + int args_len = tkn.Args_len(); + Arg_nde_tkn[] rv = new Arg_nde_tkn[args_len]; + for (int i = 0; i < args_len; i++) + rv[i] = tkn.Args_get_by_idx(i); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr.java index a27517de8..a5dc512c8 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr.java @@ -13,3 +13,113 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.funcs.*; +public class Xot_invk_wkr implements Xop_ctx_wkr, Xop_arg_wkr { + public void Ctor_ctx(Xop_ctx ctx) {} + public void Page_bgn(Xop_ctx ctx, Xop_root_tkn root) {this.tkn_mkr = ctx.Tkn_mkr();} private Xop_tkn_mkr tkn_mkr; + public void Page_end(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int src_len) {} + public void AutoClose(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int lxr_bgn_pos, int lxr_cur_pos, Xop_tkn_itm tkn) {} + private static Arg_bldr arg_bldr = Arg_bldr.Instance; + public int Make_tkn(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int lxr_cur_pos, int lxr_end_pos, Xop_curly_bgn_tkn bgn_tkn, int keep_curly_bgn) { + Xot_invk_tkn invk = tkn_mkr.Tmpl_invk(bgn_tkn.Src_bgn(), lxr_end_pos); + int loop_bgn = bgn_tkn.Tkn_sub_idx() + 1, loop_end = root.Subs_len(); + + // handle empty template; EX: "{{}}" + if (loop_bgn == loop_end) { + root.Subs_del_after(bgn_tkn.Tkn_sub_idx()); // del invk_bgn_tkn + root.Subs_add(tkn_mkr.Txt(bgn_tkn.Src_bgn(), lxr_end_pos)); // add txt tkn + return lxr_cur_pos; + } + + // make arguments + boolean made = arg_bldr.Bld(ctx, tkn_mkr, this, Xop_arg_wkr_.Typ_tmpl, root, invk, lxr_cur_pos, lxr_end_pos, loop_bgn, loop_end, src); + Arg_itm_tkn key_tkn = invk.Name_tkn().Key_tkn(); + if ( !made // invalid args; + || (key_tkn.Dat_bgn() == key_tkn.Dat_end() && key_tkn.Dat_bgn() != -1)) { // key_tkn is entirely whitespace; EX: {{\n}} + invk.Tkn_tid_to_txt(); + ctx.Subs_add(root, tkn_mkr.Txt(lxr_cur_pos, lxr_end_pos)); + return lxr_cur_pos; + } + root.Subs_del_after(bgn_tkn.Tkn_sub_idx() + keep_curly_bgn); // all tkns should now be converted; delete everything in root + root.Subs_add(invk); + return lxr_cur_pos; + } + public boolean Args_add(Xop_ctx ctx, byte[] src, Xop_tkn_itm tkn, Arg_nde_tkn nde, int nde_idx) { + Xot_invk_tkn invk = (Xot_invk_tkn)tkn; + if (nde_idx == 0) // 1st arg; name_tkn + AddNameArg(ctx, src, invk, nde); + else + invk.Args_add(ctx, nde); + return true; + } + private static void AddNameArg(Xop_ctx ctx, byte[] src, Xot_invk_tkn invk, Arg_nde_tkn nde) { + // make valTkn into a keyTkn; note that argBldr will only generate a valTkn + Arg_itm_tkn key_tkn = nde.Val_tkn(), val_tkn = nde.Key_tkn(); + nde.Key_tkn_(key_tkn).Val_tkn_(val_tkn); + invk.Name_tkn_(nde); + + if (key_tkn.Itm_static() != Bool_.Y_byte) return; // dynamic tkn; can't identify func/name + int colon_pos = -1, txt_bgn = key_tkn.Dat_bgn(), txt_end = key_tkn.Dat_end(); + + Xol_func_itm finder = new Xol_func_itm(); // TS.MEM: DATE:2016-07-12 + ctx.Wiki().Lang().Func_regy().Find_defn(finder, src, txt_bgn, txt_end); + Xot_defn finder_func = finder.Func(); + byte finder_tid = finder.Tid(); + int finder_colon_pos = finder.Colon_pos(); + int finder_subst_bgn = finder.Subst_bgn(); + int finder_subst_end = finder.Subst_end(); + + switch (finder_tid) { + case Xot_defn_.Tid_func: // func + colon_pos = finder_colon_pos; + break; + case Xot_defn_.Tid_subst: // subst/safesubst; mark invk_tkn + case Xot_defn_.Tid_safesubst: + int subst_bgn = finder_subst_bgn, subst_end = finder_subst_end; + invk.Tmpl_subst_props_(finder_tid, subst_bgn, subst_end); + if ((ctx.Parse_tid() == Xop_parser_tid_.Tid__defn && finder_tid == Xot_defn_.Tid_subst) // NOTE: if subst, but in tmpl stage, do not actually subst; PAGE:en.w:Unreferenced; DATE:2013-01-31 + || ctx.Page().Ttl().Ns().Id_is_tmpl()) { // also, if on tmpl page, never evaluate (questionable, but seems to be needed) + } + else { + key_tkn.Dat_rng_ary_(src, subst_end, txt_end); // redo txt_rng to ignore subst + key_tkn.Dat_ary_had_subst_y_(); + } + if (finder_func != Xot_defn_.Null) { + colon_pos = finder_colon_pos; + txt_bgn = subst_end; + } + break; + } + if (colon_pos == -1) return; // no func + invk.Tmpl_defn_(finder_func); + // split key_subs into val based on ":"; note that this does some of the same ws_at_bgn logic as arg_bldr + val_tkn = ctx.Tkn_mkr().ArgItm(colon_pos + 1, key_tkn.Src_end()); + nde.Val_tkn_(val_tkn); + + int del_idx = -1; int val_txt_bgn = - 1; + for (int i = 0; i < key_tkn.Subs_len(); i++) { + Xop_tkn_itm sub = key_tkn.Subs_get(i); + int sub_bgn = sub.Src_bgn(); + if (sub_bgn < colon_pos && !sub.Tkn_immutable()) {} + else if (sub_bgn == colon_pos) {del_idx = i; nde.Eq_tkn_(sub);} + else { + if (val_txt_bgn == -1) { // txt_sub not found yet + switch (sub.Tkn_tid()) { + case Xop_tkn_itm_.Tid_space: case Xop_tkn_itm_.Tid_tab: case Xop_tkn_itm_.Tid_newLine: + sub.Ignore_y_grp_(ctx, key_tkn, i); + break; + default: // txt_sub found + val_txt_bgn = sub_bgn; + break; + } + } + val_tkn.Subs_add_grp(sub, key_tkn, i); + } + } + if (val_txt_bgn == -1) val_txt_bgn = txt_end; // default to txt_end to handle empty; EX: {{padleft: |}} + key_tkn.Subs_del_after(del_idx); + key_tkn.Dat_rng_ary_(src, txt_bgn, colon_pos); + val_tkn.Dat_rng_ary_(src, val_txt_bgn, txt_end); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__basic__tst.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__basic__tst.java index a27517de8..789c5f917 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__basic__tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__basic__tst.java @@ -13,3 +13,426 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.xowa.wikis.ttls.*; import gplx.xowa.wikis.nss.*; +public class Xot_invk_wkr__basic__tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Before public void init() {fxt.Reset();} + @Test public void Basic() { + fxt.Test_parse_page_tmpl("{{a}}", fxt.tkn_tmpl_invk_w_name(0, 5, 2, 3)); + } + @Test public void Arg_many() { + fxt.Test_parse_page_tmpl("{{a|b|c|d}}", fxt.tkn_tmpl_invk_w_name(0, 11, 2, 3) + .Args_(fxt.tkn_arg_val_txt_(4, 5), fxt.tkn_arg_val_txt_(6, 7), fxt.tkn_arg_val_txt_(8, 9))); + } + @Test public void Kv() { + fxt.Test_parse_page_tmpl("{{a|b=c}}", fxt.tkn_tmpl_invk_w_name(0, 9, 2, 3) + .Args_ + ( fxt.tkn_arg_nde_() + . Key_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(4, 5))) + . Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(6, 7))) + )); + } + @Test public void Kv_arg() { + fxt.Test_parse_tmpl("{{a|b={{{1}}}}}", fxt.tkn_tmpl_invk_w_name(0, 15, 2, 3) + .Args_ + ( fxt.tkn_arg_nde_() + . Key_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(4, 5))) + . Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_tmpl_prm_find_(fxt.tkn_txt_(9, 10)))) + )); + } + @Test public void Kv_tmpl_compiled() { + fxt.Test_parse_tmpl("{{a|b={{c}}}}", fxt.tkn_tmpl_invk_w_name(0, 13, 2, 3) + .Args_ + ( fxt.tkn_arg_nde_() + . Key_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(4, 5))) + . Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_tmpl_invk_w_name(6, 11, 8, 9))) + )); + } + @Test public void Kv_tmpl_dynamic() { + fxt.Test_parse_tmpl("{{a|b={{c|{{{1}}}}}}}", fxt.tkn_tmpl_invk_w_name(0, 21, 2, 3) + .Args_ + ( fxt.tkn_arg_nde_() + . Key_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(4, 5))) + . Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_tmpl_invk_w_name(6, 19, 8, 9).Args_(fxt.tkn_arg_nde_().Val_tkn_(fxt.tkn_arg_itm_().Subs_(fxt.tkn_tmpl_prm_find_(fxt.tkn_txt_(13, 14)))))) + ) + )); + } + @Test public void Nest() { + fxt.Test_parse_page_tmpl("{{a|b{{c|d}}e}}", fxt.tkn_tmpl_invk_w_name(0, 15, 2, 3).Args_ + ( fxt.tkn_arg_nde_().Val_tkn_(fxt.tkn_arg_itm_ + ( fxt.tkn_txt_(4, 5) + , fxt.tkn_tmpl_invk_w_name(5, 12, 7, 8).Args_ + ( fxt.tkn_arg_val_txt_(9, 10) + ) + , fxt.tkn_txt_(12, 13) + )) + )); + } + @Test public void Whitespace() { + fxt.Test_parse_page_tmpl("{{a| b = c }}", fxt.tkn_tmpl_invk_w_name(0, 13, 2, 3).Args_ + ( fxt.tkn_arg_nde_() + .Key_tkn_(fxt.tkn_arg_itm_(fxt.tkn_space_(4, 5).Ignore_y_(), fxt.tkn_txt_(5, 6), fxt.tkn_space_( 6, 7).Ignore_y_())) + .Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_space_(8, 9).Ignore_y_(), fxt.tkn_txt_(9, 10), fxt.tkn_space_(10, 11).Ignore_y_())) + )); + } + @Test public void Whitespace_nl() { + fxt.Test_parse_page_tmpl("{{a|b=c\n}}", fxt.tkn_tmpl_invk_w_name(0, 10, 2, 3).Args_ + ( fxt.tkn_arg_nde_() + .Key_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(4, 5))) + .Val_tkn_(fxt.tkn_arg_itm_(fxt.tkn_txt_(6, 7), fxt.tkn_nl_char_(7, 8).Ignore_y_())) + )); + } + @Test public void Err_noName() { // WP: [[Russian language]] + fxt.Test_parse_page_tmpl("{{|}}", fxt.tkn_curly_bgn_(0), fxt.tkn_pipe_(2), fxt.tkn_txt_(3, 5)); + } + @Test public void Err_noName_nl() { + fxt.Test_parse_page_tmpl("{{\n|}}", fxt.tkn_curly_bgn_(0), fxt.tkn_nl_char_len1_(2), fxt.tkn_pipe_(3), fxt.tkn_txt_(4, 6)); + } + @Test public void Err_noName_tab() { + fxt.Test_parse_page_tmpl("{{\t|}}", fxt.tkn_curly_bgn_(0), fxt.tkn_tab_(2), fxt.tkn_pipe_(3), fxt.tkn_txt_(4, 6)); + } + @Test public void Err_empty() { // WP: [[Set theory]] + fxt.Test_parse_page_tmpl("{{}}", fxt.tkn_txt_(0, 4)); + } + @Test public void DynamicName() { + fxt.Init_defn_clear(); + fxt.Init_defn_add("proc_1", "proc_1 called"); + fxt.Init_defn_add("proc_2", "proc_2 called"); + fxt.Test_parse_tmpl_str_test("{{proc_{{{1}}}}}" , "{{test|1}}" , "proc_1 called"); + fxt.Test_parse_tmpl_str_test("{{proc_{{{1}}}}}" , "{{test|2}}" , "proc_2 called"); + fxt.Test_parse_tmpl_str_test("{{proc_{{#expr:1}}}}" , "{{test}}" , "proc_1 called"); + } + @Test public void Comment() { + fxt.Test_parse_tmpl_str_test("b" , "{{test}}" , "b"); + } + @Test public void Err_equal() { // WP:[[E (mathematical constant)]] + fxt.Test_parse_page_tmpl("{{=}}", fxt.tkn_tmpl_invk_(0, 5).Name_tkn_(fxt.tkn_arg_nde_(2, 3).Key_tkn_(fxt.tkn_arg_itm_(fxt.tkn_eq_(2))))); + } + @Test public void Err_dangling() { // WP:[[Antoni Salieri]]; {{icon it}\n + fxt.Test_parse_page_tmpl("{{fail} {{pass}}", fxt.tkn_curly_bgn_(0), fxt.tkn_txt_(2, 7), fxt.tkn_space_(7, 8), fxt.tkn_tmpl_invk_w_name(8, 16, 10, 14)); + } + @Test public void MultipleColon() { + fxt.Init_defn_clear(); + fxt.Init_defn_add("H:IPA", "{{{1}}}"); + fxt.Test_parse_tmpl_str_test("{{H:IPA|{{{1}}}}}" , "{{test|a}}" , "a"); + } + @Test public void RedundantTemplate() { // {{Template:a}} == {{a}} + fxt.Init_defn_clear(); + fxt.Init_defn_add("a", "a"); + fxt.Test_parse_tmpl_str_test("{{Template:a}}" , "{{test}}" , "a"); + fxt.Init_defn_clear(); + } + @Test public void Lnki() { // PURPOSE: pipe inside lnki should not be interpreted for tmpl: WP:[[Template:Quote]] + fxt.Test_parse_tmpl_str_test("{{{1}}}{{{2}}}" , "{{test|[[a|b]]|c}}" , "[[a|b]]c"); + } + @Test public void Lnki2() {// PURPOSE: pipe inside lnki inside tmpl should be interpreted WP:[[H:IPA]] + fxt.Test_parse_tmpl_str_test("[[a|{{#switch:{{{1}}}|b=c]]|d=e]]|f]]}}" , "{{test|b}}" , "[[a|c]]"); + } + @Test public void Nowiki() { // PURPOSE: nowiki tag should be ignored: en.w:Template:Main + fxt.Test_parse_tmpl_str_test("
    class='a' />" , "{{test}}" , "
    class='a' />"); + } + @Test public void Nowiki_if() { // PURPOSE: templates and functions inside nowiki should not be evaluated + fxt.Test_parse_tmpl_str_test("a {{#if:|y|n}} b" , "{{test}}" , "a {{#if:|y|n}} b"); + } + @Test public void Nowiki_endtag() { // PURPOSE: should not match + fxt.Test_parse_page_all_str(" ''b'' c" , " b c"); + } + @Test public void Nowiki_xnde_frag() { // PURPOSE: nowiki did not handle xnde frag and literalized; nowiki_xnde_frag; DATE:2013-01-27 + fxt.Test_parse_page_all_str("
    {{#expr:1}}
    b
    " , "<pre>1
    b
    "); + } + @Test public void Nowiki_lnki() { // PURPOSE: nowiki should noop lnkis; DATE:2013-01-27 + fxt.Test_parse_page_all_str("[[A]]" , "[[A]]"); + } + @Test public void Nowiki_underscore() { // PURPOSE: nowiki did not handle __DISAMBIG__; DATE:2013-07-28 + fxt.Test_parse_page_all_str("__DISAMBIG__" , "__DISAMBIG__"); + } + @Test public void Nowiki_asterisk() { // PURPOSE: nowiki should noop lists; DATE:2013-08-26 + fxt.Test_parse_page_all_str("*a", "*a"); + } + @Test public void Nowiki_space() { // PURPOSE: nowiki should noop space (else pre); DATE:2013-09-03 + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str("a\n b", "

    a\n b\n

    \n"); + fxt.Init_para_n_(); + } + @Test public void LnkiWithPipe_basic() { // PURPOSE: pipe in link should not count towards tmpl: WP:{{H:title|dotted=no|pronunciation:|[[File:Loudspeaker.svg|11px|link=|alt=play]]}} + fxt.Test_parse_tmpl_str_test("{{{1}}}{{{2}}}" , "{{test|[[b=c|d]]}}" , "[[b=c|d]]{{{2}}}"); + } + @Test public void LnkiWithPipe_nested() { + fxt.Test_parse_tmpl_str_test("{{{1}}}{{{2}}}" , "{{test|[[b=c|d[[e|f]]g]]}}" , "[[b=c|d[[e|f]]g]]{{{2}}}"); + } + @Test public void LnkiWithPipe_unclosed() { + fxt.Test_parse_tmpl_str_test("{{{1}}}{{{2}}}" , "{{test|[[b=c|d}}" , "{{test|[[b=c|d}}"); + } + @Test public void Err_tmp_empty() { // PURPOSE: {{{{R from misspelling}} }} + fxt.Test_parse_tmpl_str_test("{{{1}}}" , "{{ {{a}} }}" , "{{[[:Template:a]]}}"); + } + @Test public void Mismatch_bgn() { // PURPOSE: handle {{{ }}; WP:Paris Commune; Infobox Former Country + fxt.Init_defn_clear(); + fxt.Init_defn_add("!", "|"); + fxt.Test_parse_tmpl_str_test("{{#if:|{{{!}}{{!}}}|x}}" , "{{test}}" , "x"); + } + @Test public void Mismatch_tblw() { // PURPOSE: handle {{{!}}; WP:Soviet Union + fxt.Init_defn_clear(); + fxt.Init_defn_add("!", "|"); + fxt.Test_parse_page_all_str("a\n{{{!}}\n|b\n|}", String_.Concat_lines_nl_skip_last + ( "a" + , "" + , " " + , " " + , " " + , "
    b" + , "
    " + , "" + ) + ); + fxt.Init_defn_clear(); + } + @Test public void Lnki_space() { + fxt.Init_defn_clear(); + fxt.Init_defn_add("c", "{{{1}}}"); + fxt.Test_parse_tmpl_str("{{c|[[a|b ]]}}", "[[a|b ]]"); + } + @Test public void Bug_innerTemplate() { // PURPOSE: issue with inner templates not using correct key + fxt.Init_defn_clear(); + fxt.Init_defn_add("temp_1", "{{temp_2|key1=val1}}"); + fxt.Init_defn_add("temp_2", "{{{key1}}}"); + fxt.Test_parse_tmpl_str("{{temp_1}}", "val1"); + } + @Test public void Xnde_xtn_preserved() { // PURPOSE: tmpl was dropping .Xtn ndes; + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_template", "{{{1}}}"); + fxt.Test_parse_page_all_str("{{test_template|a1b}}", "a
    1
    b"); // was just ab + fxt.Init_defn_clear(); + } + @Test public void Recurse() { + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_recurse", "bgn:{{test_recurse}}:end"); + fxt.Test_parse_page_all_str("{{test_recurse}}", "bgn::end"); + fxt.Init_defn_clear(); + } + @Test public void Ws_nl() { + fxt.Test_parse_tmpl_str_test("{{{1}}}" , "{{test|\na}}" , "\na"); + } + @Test public void Ws_trimmed_key_0() { // PURPOSE: control test for key_1, key_2 + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_1", "{{test_2|{{{1}}}}}"); + fxt.Init_defn_add("test_2", "{{{1}}}"); + fxt.Test_parse_tmpl_str("{{test_1| a }}", " a "); + fxt.Init_defn_clear(); + } + @Test public void Ws_trimmed_key_1() { // PURPOSE: trim prm when passed as key; + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_1", "{{test_2|key={{{1}}}}}"); + fxt.Init_defn_add("test_2", "{{{key}}}"); + fxt.Test_parse_tmpl_str("{{test_1| a }}", "a"); + fxt.Init_defn_clear(); + } + @Test public void Ws_trimmed_key_2() { // PURPOSE: trim prm; note that 1 is key not idx; PAGE:en.w:Coord in Chernobyl disaster, Sahara + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_1", "{{test_2|1={{{1}}}}}"); + fxt.Init_defn_add("test_2", "{{{1}}}"); + fxt.Test_parse_tmpl_str("{{test_1| a }}", "a"); + fxt.Init_defn_clear(); + } + @Test public void Ws_trimmed_key_3() { // PURPOSE: trim entire arg only, not individual prm + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_1", "{{test_2|1={{{1}}}{{{2}}}}}"); + fxt.Init_defn_add("test_2", "{{{1}}}"); + fxt.Test_parse_tmpl_str("{{test_1| a | b }}", "a b"); + fxt.Init_defn_clear(); + } + @Test public void Ws_eval_prm() { // PURPOSE: skip ws in prm_idx; EX:it.w:Portale:Giochi_da_tavolo; it.w:Template:Alternate; DATE:2014-02-09 + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_1", String_.Concat_lines_nl_skip_last + ( "{{{ {{#expr: 1}} }}}" + )); + fxt.Test_parse_tmpl_str("{{test_1|a}}", "a"); + fxt.Init_defn_clear(); + } + @Test public void Keyd_arg_is_trimmed() { // PURPOSE: trim entire arg only, not individual prm; PAGE:en.w:William Shakespeare; {{Relatebardtree}} + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_1", "{{test_2|1={{{{{{1}}}}}}}}"); + fxt.Init_defn_add("test_2", "{{{1}}}"); + fxt.Test_parse_tmpl_str("{{test_1| b | b = x }}", "x"); + fxt.Init_defn_clear(); + } + @Test public void Ws_arg() { // PURPOSE: whitespace arg should not throw array index out of bounds; EX.WIKT: wear one's heart on one's sleeve + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_1", "{{{{{1}}}}}"); + fxt.Test_parse_tmpl_str("{{test_1| }}", ""); + fxt.Init_defn_clear(); + } + @Test public void Xnde_xtn_ref_not_arg() { // PURPOSE: }}; arg1 is a not "b"; PAGE:en.w:WWI + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_1", "{{{1}}}"); + fxt.Test_parse_page_tmpl_str("{{test_1|a}}", "a"); + fxt.Init_defn_clear(); + } + @Test public void Multi_bgn_5_end_3_2() { + fxt.Test_parse_tmpl("{{{{{1}}}|a}}", fxt.tkn_tmpl_invk_(0, 13) + .Name_tkn_(fxt.tkn_arg_nde_(2, 9).Key_tkn_(fxt.tkn_arg_itm_(fxt.tkn_tmpl_prm_find_(fxt.tkn_txt_(5, 6))))) + .Args_ + ( fxt.tkn_arg_val_txt_(10, 11) + ) + ); + } + @Test public void Lnki_has_invk_end() {// PURPOSE: [[A|bcd}}]] should not break enclosing templates; EX.CM:Template:Protect + fxt.Test_parse_page_tmpl_str(String_.Concat_lines_nl_skip_last + ( "{{#switch:y" + , " |y=[[A|b}}]]" + , " |#default=n" + , "}}" + ), "[[A|b}}]]"); + } + @Test public void Lnki_has_prm_end() {// PURPOSE: [[A|bcd}}}]] should not break enclosing templates; EX.CM:Template:Protect + fxt.Test_parse_page_tmpl_str(String_.Concat_lines_nl_skip_last + ( "{{#switch:y" + , " |y=[[A|b}}}]]" + , " |#default=n" + , "}}" + ), "[[A|b}}}]]"); + } + @Test public void Tmpl_overrides_pfunc() { // PURPOSE: {{plural|}} overrides {{plural:}} + fxt.Init_defn_clear(); + fxt.Init_defn_add("plural", "{{{1}}}"); + fxt.Test_parse_tmpl_str("{{plural|a}}" , "a"); + fxt.Test_parse_tmpl_str("{{plural:2|a|b}}" , "b"); // make sure pfunc still works + fxt.Init_defn_clear(); + } + @Test public void Tmpl_aliases() { // PURPOSE: handled aliases for Template ns + fxt.Wiki().Ns_mgr().Aliases_add(Xow_ns_.Tid__template, "TemplateAlias"); + fxt.Wiki().Ns_mgr().Init(); + fxt.Init_defn_clear(); + fxt.Init_defn_add("tmpl_key", "tmpl_val"); + fxt.Test_parse_tmpl_str("{{TemplateAlias:tmpl_key}}" , "tmpl_val"); + fxt.Init_defn_clear(); + } + @Test public void Tmpl_aliases_2() { // PURPOSE: handled aliases for other ns; DATE:2013-02-08 + fxt.Wiki().Ns_mgr().Aliases_add(Xow_ns_.Tid__project, "WP"); + fxt.Wiki().Ns_mgr().Init(); + fxt.Init_defn_clear(); + fxt.Init_page_create("Project:tmpl_key", "tmpl_val"); + fxt.Test_parse_tmpl_str("{{WP:tmpl_key}}" , "tmpl_val"); + fxt.Init_defn_clear(); + } + @Test public void Template_loop_across_namespaces() {// PURPOSE: {{Institution:Louvre}} results in template loop b/c it calls {{Louvre}}; EX: c:Mona Lisa + fxt.Init_page_create("Template:Test", "test"); + fxt.Init_page_create("Category:Test", "{{Test}}"); + fxt.Test_parse_page_all_str("{{Category:Test}}", "test"); + } + @Test public void Closing_braces_should_not_extend_beyond_lnki() { // PURPOSE: extra }} should not close any frames beyond lnki; EX:w:Template:Cite wikisource; w:John Fletcher (playwright) + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_b", "{{{test_b_0|}}}"); + fxt.Init_defn_add("test_a", "{{test_b|test_b_0=[[{{{test_a_0}}}}}]]}}"); // NOTE: extra 2 }}; should render literally and not close "{{test_b" + fxt.Test_parse_tmpl_str("{{test_a|test_a_0=1}}" , "[[1}}]]"); + fxt.Init_defn_clear(); + } +// @Test public void Trim_ws_on_sub_tmpls() { // PURPOSE: ws should be trimmed on eval tkns; EX:w:Lackawanna Cut-Off; {{Lackawanna Cut-Off}}; DELETE: no longer needed; DATE:2014-02-04 +// fxt.Init_defn_clear(); +// fxt.Init_defn_add("test_b", "\n\nb\n\n"); +// fxt.Init_defn_add("test_a", "a{{test_b}}c"); +// fxt.Test_parse_tmpl_str("{{test_a}}" , "a\n\nbc"); +// fxt.Init_defn_clear(); +// } + @Test public void Nowiki_tblw() { // PURPOSE: nowiki does not exclude sections with pipe; will cause tables to fail; EX: de.wikipedia.org/wiki/Hilfe:Vorlagenprogrammierung + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|{{ #time:M|Jan}}" + , "|}" + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    {{ #time:M|Jan}}" + , "
    " + , "" + )); + } + @Test public void Template_plus_other_ns() { // PURPOSE.fix: Template:Wikipedia:A was transcluding "Wikipedia:A" instead of "Template:Wikipedia:A"; DATE:2013-04-03 + fxt.Init_defn_clear(); + fxt.Init_page_create("Template:Wikipedia:A" , "B"); + fxt.Test_parse_tmpl_str("{{Template:Wikipedia:A}}" , "B"); + fxt.Init_defn_clear(); + } + @Test public void Return_nl() { // PURPOSE: allow \n to be returned by tmpl; do not trim; EX: zh.wikipedia.org/wiki/北區_(香港); DATE:2014-02-04 + fxt.Init_defn_add("1x", "{{{1}}}"); + fxt.Test_parse_tmpl_str("a{{1x|\n}}b", "a\nb"); + fxt.Init_defn_clear(); + } + @Test public void Ignore_hdr() { // PURPOSE: hdr-like tkns inside tmpl should not be treated as "=" in tmpl_prm; EX: key_1\n==a==; EX:it.b:Wikibooks:Vetrina; DATE:2014-02-09 + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_1", "{{{key_1|null_key_1}}}"); + fxt.Test_parse_tmpl_str(String_.Concat_lines_nl_skip_last // == a === should be treated as hdr; + ( "{{test_1|key_1" + , "== a ==" + , "}}" + ) + , "null_key_1" + ); + fxt.Test_parse_tmpl_str(String_.Concat_lines_nl_skip_last // = a = should not be treated as hdr; + ( "{{test_1|key_1" + , "= a =" + , "}}" + ) + , "a =" + ); + fxt.Init_defn_clear(); + } + @Test public void Ignore_hdr_2() { // PURPOSE: hdr-like logic did not work for "== \n"; PAGE:nl.q:Geert_Wilders; DATE:2014-06-05 + fxt.Init_defn_clear(); + fxt.Init_defn_add("Hdr_w_space", String_.Concat_lines_nl_skip_last + ( "{{#if:{{{key|}}} | " + , "==={{{key}}}=== " // NOTE " " after "===" + , "|}}" + )); + fxt.Test_parse_page_tmpl_str(String_.Concat_lines_nl_skip_last + ( "{{Hdr_w_space" + , "|key=A" + , "}}" + ), "===A===" + ); + fxt.Init_defn_clear(); + } + @Test public void Ignore_hdr_3() { // PURPOSE: tkn with multiple eq should have be treated as value, not header; PAGE:zh.w:Wikipedia:条目评选; DATE:2014-08-27 + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_1", "{{{key_1|null_key_1}}}"); + fxt.Test_parse_page_tmpl_str(String_.Concat_lines_nl_skip_last + ( "{{test_1" + , "|key_1===A==" // note that this is not "===A==", but "=","===A==" + , "}}" + ), "==A==" + ); + fxt.Init_defn_clear(); + } + @Test public void Ignore_hdr_4() { // PURPOSE: variation of above; make sure 2nd "==" doesn't trigger another key; DATE:2014-08-27 + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_1", "{{{key_1|null_key_1}}}"); + fxt.Test_parse_page_tmpl_str(String_.Concat_lines_nl_skip_last + ( "{{test_1" + , "|key_1===A===B" // = should be at "==A", not "==B" + , "}}" + ), "==A===B" + ); + fxt.Init_defn_clear(); + } + @Test public void Tmpl_case_match() { // PURPOSE: template name should match by case; EX:es.d:eclipse; DATE:2014-02-12 + fxt.Init_defn_clear(); + fxt.Init_defn_add("CASE_MATCH", "found", Xow_ns_case_.Tid__all); + fxt.Test_parse_tmpl_str("{{case_match}}", "[[:Template:case_match]]"); // Xot_invk_tkn will do 2 searches: "test" and "Test" + fxt.Test_parse_tmpl_str("{{cASE_MATCH}}", "found"); // Xot_invk_tkn will do 2 searches: "tEST" and "TEST" + fxt.Init_defn_clear(); + } + @Test public void Kv_same() { // PURPOSE: multiple identical keys should retrieve last, not first; EX: {{A|1=a|1=b}}; PAGE:el.d:ἔχω DATE:2014-07-23 + fxt.Init_defn_clear(); + fxt.Init_defn_add("tmpl_1", "{{{1}}}"); + fxt.Test_parse_tmpl_str_test("{{tmpl_1|1=a|1=b}}" , "{{test}}" , "b"); // duplicate "1"; use last + fxt.Test_parse_tmpl_str_test("{{tmpl_1|a|1=b}}" , "{{test}}" , "b"); // "a" has implicit key of "1"; overwritten by "1=b"; verified against MW + fxt.Test_parse_tmpl_str_test("{{tmpl_1|1=a|b}}" , "{{test}}" , "b"); // "b" has implicit key of "1"; overwritten by "1=b"; verified against MW + } + @Test public void Bang() { // PURPOSE: support new bang keyword; DATE:2014-08-05 + fxt.Test_parse_tmpl_str("{{!}}", "|"); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__missing__tst.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__missing__tst.java index a27517de8..33c650e5b 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__missing__tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__missing__tst.java @@ -13,3 +13,33 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.xowa.wikis.ttls.*; import gplx.xowa.wikis.nss.*; +public class Xot_invk_wkr__missing__tst { + @Before public void init() {fxt.Reset();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Missing() { + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_template", "{{[[Template:{{{1}}}|{{{1}}}]]}}"); + fxt.Test_parse_tmpl_str("{{test_template|a}}", "{{[[Template:a|a]]}}"); + fxt.Init_defn_clear(); + } + @Test public void Missing__name_and_args() { // PURPOSE: missing title should return name + args; used to only return name; PAGE:en.w:Flag_of_Greenland; DATE:2016-06-21 + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_template", "{{ {{{1}}} | a | b }}"); + fxt.Test_parse_tmpl_str("{{test_template}}", "{{{{{1}}}| a | b }}"); // NOTE: this should include spaces (" {{{1}}} "), but for now, ignore + fxt.Init_defn_clear(); + } + @Test public void Missing__evaluate_optional() { // PURPOSE: missing title should still evaulate optional args; "{{{a|}}}" -> ""; PAGE:en.w:Europe; en.w:Template:Country_data_Guernsey DATE:2016-10-13 + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_template", "{{ {{{1}}} | {{{a|}}} | b }}"); + fxt.Test_parse_tmpl_str("{{test_template}}", "{{{{{1}}}| | b }}"); // NOTE: "| |" not "| {{{a|}}} |" + fxt.Init_defn_clear(); + } + @Test public void Missing_foreign() { + Xow_ns ns = fxt.Wiki().Ns_mgr().Ns_template(); + byte[] old_ns = ns.Name_db(); + ns.Name_bry_(Bry_.new_a7("Template_foreign")); + fxt.Test_parse_tmpl_str("{{Missing}}", "[[:Template_foreign:Missing]]"); + ns.Name_bry_(old_ns); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__prepend_nl__tst.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__prepend_nl__tst.java index a27517de8..33632fb9a 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__prepend_nl__tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__prepend_nl__tst.java @@ -13,3 +13,107 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xot_invk_wkr__prepend_nl__tst { + @Before public void init() {fxt.Reset();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Basic() { // PURPOSE: if {| : ; # *, auto add new_line REF.MW:Parser.php|braceSubstitution + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_inner", "# a"); + fxt.Test_parse_tmpl_str_test("{{test_inner}}" , "z {{test}}" , "z \n# a"); + fxt.Init_defn_clear(); + } + @Test public void Skip_if_nl_exists() { + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_inner", "# a"); + fxt.Test_parse_tmpl_str_test("{{test_inner}}" , "z \n{{test}}" , "z \n# a"); // NOTE: no \n + fxt.Init_defn_clear(); + } + @Test public void Skip_if_nl_exists_2() { // PURPOSE: \n inside template args should not print \n\n; PAGE:bn.w:লিওনেল_মেসি |ko.w:도쿄_지하철_히비야_선|DATE:2014-05-27 + fxt.Init_defn_clear(); + fxt.Init_defn_add("test_list", "# a"); + fxt.Init_defn_add("test_print", "{{{1}}}"); + fxt.Test_parse_tmpl_str_test(String_.Concat_lines_nl_skip_last + ( "{{test_print|" + , "{{test_list}}" // note that there is a "\n" here, but test_list will return "#"; "#" should not be prepended with \n + , "{{test_list}}" + , "}}" + ), "{{test}}" + , String_.Concat_lines_nl_skip_last + ( "" // NOTE: \n still prints b/c of \n between "{{test_print|" and "{{test_list}}"; should trim ws at start; + , "# a" + , "# a" + )); + fxt.Init_defn_clear(); + } + @Test public void Pfunc() {// PURPOSE: if {| : ; # *, auto add new_line; parser_function variant; PAGE:en.w:Soviet Union; Infobox former country + fxt.Test_parse_tmpl_str_test("" , "z {{#if:true|#a|n}}" , "z \n#a"); + } + @Test public void Bos() { // PURPOSE: function should expand "*a" to "\n*a" even if "*a" is bos; SEE:NOTE_1 PAGE:en.w:Rome and Panoramas; DATE:2014-02-05 + fxt.Test_parse_page_tmpl_str("{{#if:x|*a}}", "\n*a"); + } + @Test public void Tmpl_arg() { // PURPOSE: tmpl arg should auto-create; PAGE:vi.w:Friedrich_II_của_Phổ; DATE:2014-04-26 + fxt.Init_defn_add("cquote" , "*b"); + fxt.Init_defn_add("blockquote" , "
    {{{1}}}
    "); + fxt.Test_html_full_str("a\n{{blockquote|{{cquote}}}}" + , String_.Concat_lines_nl_skip_last + ( "a" + , "
    " + , "
      " + , "
    • b" + , "
    • " + , "
    " + ) + ); + } + @Test public void Nested_1_n() { // PURPOSE: handled nested templates; PAGE:en.w:Central_Line en.w:Panama_Canal; DATE:2014-08-21 + fxt.Init_defn_clear(); + fxt.Init_defn_add("Nest_1" , "a{{Nest_1_1}}"); // 0: no \n + fxt.Init_defn_add("Nest_1_1" , "b\n{{Nest_1_1_1}}"); // 1: \n + fxt.Init_defn_add("Nest_1_1_1" , "*c"); // 2: "*" should not prepend \n b/c (1) has \n; used to only check (0) + fxt.Test_parse_tmpl_str_test("{{Nest_1}}", "{{test}}", String_.Concat_lines_nl_skip_last + ( "ab" // not prepended + , "*c" + )); + } + @Test public void Nested_1_y() { // PURPOSE: handled nested templates; PAGE:en.w:Lackawanna_Cut-Off; DATE:2014-08-21 + fxt.Init_defn_clear(); + fxt.Init_defn_add("Nest_1" , "a\n{{Nest_1_1}}"); // 0: no \n + fxt.Init_defn_add("Nest_1_1" , "b{{Nest_1_1_1}}"); // 1: char + fxt.Init_defn_add("Nest_1_1_1" , "*c"); // 2: "*" should prepend \n b/c (1) has char; used to check (0) and not prepend + fxt.Test_parse_tmpl_str_test("{{Nest_1}}", "{{test}}", String_.Concat_lines_nl_skip_last + ( "a" + , "b" // prepended + , "*c" + )); + } + @Test public void Nested_0_n() { // PURPOSE: handled nested templates variation of above; DATE:2014-08-21 + fxt.Init_defn_clear(); + fxt.Init_defn_add("Nest_1" , "a\n{{Nest_1_1}}"); // 0: \n + fxt.Init_defn_add("Nest_1_1" , "{{Nest_1_1_1}}"); // 1: "" + fxt.Init_defn_add("Nest_1_1_1" , "*b"); // 2: "*" should not prepend \n b/c (1) is empty and (0) has \n + fxt.Test_parse_tmpl_str_test("{{Nest_1}}", "{{test}}", String_.Concat_lines_nl_skip_last + ( "a" // not prepended + , "*b" + )); + } + @Test public void Nested_0_y() { // PURPOSE: handled nested templates variation of above; DATE:2014-08-21 + fxt.Init_defn_clear(); + fxt.Init_defn_add("Nest_1" , "a{{Nest_1_1}}"); // 0: no \n + fxt.Init_defn_add("Nest_1_1" , "{{Nest_1_1_1}}"); // 1: "" + fxt.Init_defn_add("Nest_1_1_1" , "*b"); // 2: "*" should prepend \n b/c (1) is empty and (0) has char + fxt.Test_parse_tmpl_str_test("{{Nest_1}}", "{{test}}", String_.Concat_lines_nl_skip_last + ( "a" // prepended + , "*b" + )); + } +} +/* +NOTE_1: function should expand "*a" to "\n*a" even if "*a" is bos +consider following +Template:Test with text of "#a" +a) "a{{test}}" would return "a\n#a" b/c of rule for auto-adding \n +b) bug was that "{{test}}" would return "#a" b/c "#a" was at bos which would expand to list later + however, needs to be "\n#a" b/c appended to other strings wherein bos would be irrelevant. +Actual situation was very complicated. PAGE:en.w:Rome; +*/ diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__raw_msg__tst.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__raw_msg__tst.java index a27517de8..d0326ad68 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__raw_msg__tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__raw_msg__tst.java @@ -13,3 +13,35 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xot_invk_wkr__raw_msg__tst { + @Before public void init() {fxt.Reset();} private final Xop_fxt fxt = new Xop_fxt(); + @After public void term() {fxt.Init_defn_clear();} + @Test public void Raw() { // PURPOSE: {{raw:A}} is same as {{A}}; EX.WIKT:android; {{raw:ja/script}} + fxt.Init_defn_clear(); + fxt.Init_defn_add("Test 1", "{{#if:|y|{{{1}}}}}"); + fxt.Test_parse_tmpl_str("{{raw:Test 1|a}}", "a"); + fxt.Init_defn_clear(); + } + @Test public void Raw_spanish() { // PURPOSE.fix: {{raw}} should not fail; EX:es.s:Carta_a_Silvia; DATE:2014-02-11 + fxt.Test_parse_tmpl_str("{{raw}}", "[[:Template:raw]]"); // used to fail; now tries to get Template:Raw which doesn't exist + } + @Test public void Special() { // PURPOSE: {{Special:Whatlinkshere}} is same as [[:Special:Whatlinkshere]]; EX.WIKT:android; isValidPageName + fxt.Test_parse_page_tmpl_str("{{Special:Whatlinkshere}}", "[[:Special:Whatlinkshere]]"); + } + @Test public void Special_arg() { // PURPOSE: make sure Special still works with {{{1}}} + fxt.Init_defn_clear(); + fxt.Init_defn_add("Test1", "{{Special:Whatlinkshere/{{{1}}}}}"); + fxt.Test_parse_tmpl_str("{{Test1|-1}}", "[[:Special:Whatlinkshere/-1]]"); + fxt.Init_defn_clear(); + } + @Test public void Raw_special() { // PURPOSE: {{raw:A}} is same as {{A}}; EX.WIKT:android; {{raw:ja/script}} + fxt.Test_parse_tmpl_str("{{raw:Special:Whatlinkshere}}", "[[:Special:Whatlinkshere]]"); + fxt.Init_defn_clear(); + } + @Test public void Msg() { + fxt.Init_defn_add("CURRENTMONTH", "a"); + fxt.Test_parse_tmpl_str("{{msg:CURRENTMONTH}}", "a"); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__transclude__tst.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__transclude__tst.java index a27517de8..df1073f1f 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__transclude__tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_invk_wkr__transclude__tst.java @@ -13,3 +13,42 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xot_invk_wkr__transclude__tst { + @Before public void init() {fxt.Reset();} private final Xop_fxt fxt = new Xop_fxt(); + @After public void term() {fxt.Init_defn_clear();} + @Test public void Basic() { // PURPOSE: {{:Template:Test}} is same as {{Template:Test}}; EX.WIKT:android; japanese and {{:Template:ja/script}} + fxt.Init_defn_add("Test_1", "{{#if:|y|n}}"); // NOTE: must be of form "Test 1"; test_1 will fail + fxt.Test_parse_tmpl_str("{{:Template:Test 1}}", "n"); + } + @Test public void Arguments() { // PURPOSE: transclusion test with arguments + fxt.Init_page_create("PageToTransclude", "a{{{key}}}c"); + fxt.Test_parse_tmpl_str("some text to make this page longer than transclusion {{:PageToTransclude|key=b}}" , "some text to make this page longer than transclusion abc"); + } + @Test public void Redirect() { // PURPOSE: StackOverflowError when transcluded sub-page redirects back to root_page; DATE:2014-01-07 + fxt.Init_page_create("Root/Leaf", "#REDIRECT [[Root]]"); + fxt.Init_page_create("Root", "A.png|a{{/Leaf}}b"); // NOTE: gallery neeeded for XOWA to fail; MW fails if just {{/Leaf}} + fxt.Test_parse_page("Root", "A.png|a{{/Leaf}}b"); + } + @Test public void Missing__sub_page() { // PURPOSE: transclusion of a missing page should create a link, not print an empty String; EX: it.u:Dipartimento:Design; DATE:2014-02-12 + fxt.Page_ttl_("Test_Page"); + fxt.Test_parse_tmpl_str("{{/Sub}}", "[[Test_Page/Sub]]"); + } + @Test public void Missing__colon_prefix() {// PURPOSE: page with colon_prefix should not add Template: PAGE:en.d:a; DATE:2016-06-24 + fxt.Test_parse_tmpl_str("{{:a}}", "[[:a]]"); // ":a", not "Template:A" or "A" + } + @Test public void Colon_by_safesubst() { // SUPPORT: page with colon_prefix should not add Template: PAGE:en.d:a; DATE:2016-06-24 + fxt.Init_defn_add("Test_2", "{{safesubst:Template:{{{1}}}}}"); + fxt.Test_parse_tmpl_str("{{Test 2|b}}", "[[Template:b]]"); + } + @Test public void Colon_w_template() { // SUPPORT: page with colon_prefix should not add Template: PAGE:en.d:a; DATE:2016-06-24 + fxt.Init_defn_add("Test_3", "{{:Template:{{{1}}}}}"); + fxt.Test_parse_tmpl_str("{{Test 3|b}}", "[[:Template:b]]"); + } + @Test public void Toc() { // PURPOSE: __TOC__ in transcluded page should be ignored; PAGE:de.w:Game_of_Thrones DATE:2016-11-21 + fxt.Init_page_create("TranscludedToc", "__TOC__\na"); + fxt.Parser().Expand_tmpl(Bry_.new_u8("{{:TranscludedToc}}")); + Tfds.Eq(false, fxt.Page().Wtxt().Toc().Flag__toc()); // transcluded page is true, but current page should still be false + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_chkr.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_chkr.java index a27517de8..4e39c07f5 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_chkr.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_chkr.java @@ -13,3 +13,16 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xot_prm_chkr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Xot_prm_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_tmpl_prm;} + public Xop_tkn_chkr_base Find_tkn() {return find_tkn;} public Xot_prm_chkr Find_tkn_(Xop_arg_itm_tkn_chkr v) {find_tkn = v; return this;} private Xop_arg_itm_tkn_chkr find_tkn; + public Xop_tkn_chkr_base[] Args() {return args;} public Xot_prm_chkr Args_(Xop_tkn_chkr_base... v) {args = v; return this;} private Xop_tkn_chkr_base[] args = Xop_tkn_chkr_base.Ary_empty; + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { + Xot_prm_tkn actl = (Xot_prm_tkn)actl_obj; + if (find_tkn != null) err += mgr.Tst_sub_obj(find_tkn, actl.Find_tkn(), path + "." + "find", err); + return err; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_log.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_log.java index a27517de8..99020497a 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_log.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_log.java @@ -13,3 +13,15 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.log_msgs.*; +public class Xot_prm_log { + private static final Gfo_msg_grp owner = Gfo_msg_grp_.new_(Xoa_app_.Nde, "tmpl_defn_arg"); + public static final Gfo_msg_itm + Dangling = Gfo_msg_itm_.new_warn_(owner, "Dangling_tmpl_defn_arg") + , Elem_without_tbl = Gfo_msg_itm_.new_warn_(owner, "Elem_without_tbl") + , Lkp_is_nil = Gfo_msg_itm_.new_note_(owner, "Lkp_is_nil") + , Lkp_and_pipe_are_nil = Gfo_msg_itm_.new_warn_(owner, "Lkp_and_pipe_are_nil") + , Prm_has_2_or_more = Gfo_msg_itm_.new_note_(owner, "Prm_has_2_or_more") + ; +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_tkn.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_tkn.java index a27517de8..98d1ac50a 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_tkn.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_tkn.java @@ -13,3 +13,75 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xot_prm_tkn extends Xop_tkn_itm_base { + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_tmpl_prm;} + @Override public void Tmpl_fmt(Xop_ctx ctx, byte[] src, Xot_fmtr fmtr) {fmtr.Reg_prm(ctx, src, this, prm_idx, prm_key, dflt_tkn);} + @Override public void Tmpl_compile(Xop_ctx ctx, byte[] src, Xot_compile_data prep_data) { + if (find_tkn != null) { // NOTE: find_tkn defaults to null + int subs_len = find_tkn.Subs_len(); + for (int i = 0; i < subs_len; i++) { + Xop_tkn_itm sub = find_tkn.Subs_get(i); + switch (sub.Tkn_tid()) { + case Xop_tkn_itm_.Tid_tmpl_invk: case Xop_tkn_itm_.Tid_tmpl_prm: case Xop_tkn_itm_.Tid_ignore: case Xop_tkn_itm_.Tid_xnde: + find_tkn_static = false; + break; + } + sub.Tmpl_compile(ctx, src, prep_data); + } + if (find_tkn_static) { // subs_are_static, so extract idx/key; EX: {{{a b}}} will have 3 subs which are all static; {{{a{{{1}}}b}}} will be dynamic + int find_tkn_bgn = find_tkn.Dat_bgn(), find_tkn_end = find_tkn.Dat_end(); + if (find_tkn_end - find_tkn_bgn > 0) { // NOTE: handles empty find_tkns; EX: {{{|safesubst:}}} + prm_idx = Bry_.To_int_or(src, find_tkn_bgn, find_tkn_end, -1); // parse as number first; note that bgn,end should not include ws; EX: " 1 " will fail + if (prm_idx == -1) prm_key = Bry_.Mid(src, find_tkn_bgn, find_tkn_end);// not a number; parse as key + } + } + } + if (dflt_tkn != null) dflt_tkn.Tmpl_compile(ctx, src, prep_data); + } + @Override public boolean Tmpl_evaluate(Xop_ctx ctx, byte[] src, Xot_invk caller, Bry_bfr bfr) { + if (!find_tkn_static) { + int subs_len = find_tkn.Subs_len(); + Bry_bfr find_bfr = Bry_bfr_.New(); + for (int i = 0; i < subs_len; i++) + find_tkn.Subs_get(i).Tmpl_evaluate(ctx, src, caller, find_bfr); + prm_idx = Bry_.To_int_or__trim_ws(find_bfr.Bfr(), 0, find_bfr.Len(), -1); // parse as number first; NOTE: trim needed to transform "{{{ 1 }}}" to "1"; it.w:Portale:Giochi_da_tavolo; DATE:2014-02-09 + if (prm_idx == -1) + prm_key = find_bfr.To_bry_and_clear_and_trim(); // not a number; parse as key; NOTE: must trim; PAGE:en.w:William Shakespeare; {{Relatebardtree}} + } + Arg_nde_tkn arg_nde = null; + if (prm_idx == -1) { // prm is key; EX: "{{{key1}}}" + if (prm_key != Bry_.Empty) // NOTE: handles empty find_tkns; EX: {{{|safesubst:}}} + arg_nde = caller.Args_get_by_key(src, prm_key); + if (arg_nde == null) {Tmpl_write_missing(ctx, src, caller, bfr); return true;} + } + else { // prm is idx; EX: "{{{1}}}" +// int invk_args_len = caller.Args_len(); +// if (prm_idx > invk_args_len) {Tmpl_write_missing(ctx, src, caller, bfr); return true;} + arg_nde = caller.Args_eval_by_idx(src, prm_idx - List_adp_.Base1); // MW args are Base1; EX: {{test|a|b}}; a is {{{1}}}; b is {{{2}}} + if (arg_nde == null) {Tmpl_write_missing(ctx, src, caller, bfr); return true;} // EX: handles "{{{1}}}{{{2}}}" "{{test|a|keyd=b}}" -> "a{{{2}}}" + } + Arg_itm_tkn arg_val = arg_nde.Val_tkn(); + if (arg_val.Itm_static() == Bool_.Y_byte) + bfr.Add_mid(src, arg_val.Dat_bgn(), arg_val.Dat_end()); + else {// compile arg if dynamic; EX: [[MESSENGER]] "{{About|the NASA space mission||Messenger (disambiguation){{!}}Messenger}}"; {{!}} causes {{{2}}} to be dynamic and its dat_ary will be an empty-String ("") + Bry_bfr arg_val_bfr = Bry_bfr_.New(); + arg_val.Tmpl_evaluate(ctx, src, caller, arg_val_bfr); + bfr.Add_bfr_and_clear(arg_val_bfr); + } + return true; + } + private void Tmpl_write_missing(Xop_ctx ctx, byte[] src, Xot_invk caller, Bry_bfr bfr) { + if (dflt_tkn == null) { // dflt absent; write orig; {{{1}}} or {{{key}}}; + bfr.Add(Xop_curly_wkr.Hook_prm_bgn); + int subs_len = find_tkn.Subs_len(); + for (int i = 0; i < subs_len; i++) + find_tkn.Subs_get(i).Tmpl_evaluate(ctx, src, caller, bfr); + bfr.Add(Xop_curly_wkr.Hook_prm_end); + } else dflt_tkn.Tmpl_evaluate(ctx, src, caller, bfr); // dflt exists; write it + } + int prm_idx = -1; byte[] prm_key = Bry_.Empty; boolean find_tkn_static = true; + public Arg_itm_tkn Find_tkn() {return find_tkn;} public Xot_prm_tkn Find_tkn_(Arg_itm_tkn v) {find_tkn = v; return this;} Arg_itm_tkn find_tkn; + public Xot_prm_tkn Dflt_tkn_(Arg_itm_tkn v) {dflt_tkn = v; return this;} Arg_itm_tkn dflt_tkn; + public Xot_prm_tkn(int bgn, int end) {this.Tkn_ini_pos(false, bgn, end);} +} diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_tkn_tst.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_tkn_tst.java index a27517de8..424375851 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_tkn_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_tkn_tst.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xot_prm_tkn_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Before public void init() {fxt.Reset();} + @Test public void Idx_1() {fxt.Test_parse_tmpl_str_test("{{{1}}}" , "{{test|a|b}}" , "a");} + @Test public void Idx_2() {fxt.Test_parse_tmpl_str_test("{{{2}}}" , "{{test|a|b}}" , "b");} + @Test public void Idx_3_nil() {fxt.Test_parse_tmpl_str_test("{{{3}}}" , "{{test|a|b}}" , "{{{3}}}");} + @Test public void Idx_3_dflt() {fxt.Test_parse_tmpl_str_test("{{{3|c}}}" , "{{test|a|b}}" , "c");} + @Test public void Idx_3_dflt_len0() {fxt.Test_parse_tmpl_str_test("{{{1|}}}" , "{{test}}" , "");} + @Test public void Idx_1_and_2() {fxt.Test_parse_tmpl_str_test("({{{1}}} {{{2}}})" , "{{test|a|b}}" , "(a b)");} + @Test public void Idx_2_len0() {fxt.Test_parse_tmpl_str_test("{{{1}}}" , "{{test||b}}" , "");}// should not fail + @Test public void Key() {fxt.Test_parse_tmpl_str_test("{{{k1}}}" , "{{test|k1=a|k2=b}}" , "a");} + @Test public void Key_nil() {fxt.Test_parse_tmpl_str_test("{{{k3|c}}}" , "{{test|k1=a|k2=b}}" , "c");} + @Test public void Key_exact() {fxt.Test_parse_tmpl_str_test("{{{k|}}}{{{k2|}}}" , "{{test|k=a}}" , "a");} // only {{{k}}} matched + @Test public void Var() {fxt.Test_parse_tmpl_str_test("{{{1|-{{PAGENAME}}-}}}" , "{{test}}" , "-Test page-");} + @Test public void Newline_bgn() {fxt.Test_parse_tmpl_str_test("{{{1}}} {{{2}}}" , "{{test|a|\nb}}" , "a \nb");} + @Test public void Newline_end() {fxt.Test_parse_tmpl_str_test("{{{1}}} {{{2}}}" , "{{test|a|b\n}}" , "a b\n");} + @Test public void Exc_lkp_nil() {fxt.Test_parse_tmpl_str_test("{{{}}}" , "{{test|a|b}}" , "{{{}}}");} + @Test public void Exc_lkp_and_args1() {fxt.Test_parse_tmpl_str_test("{{{|}}}" , "{{test|a|b}}" , "");} + @Test public void Exc_lkp_nil_args1_txt() {fxt.Test_parse_tmpl_str_test("{{{|a}}}" , "{{test|a|b}}" , "a");} + @Test public void Ws_idx() {fxt.Test_parse_tmpl_str_test("{{{ 1 }}}" , "{{test|a|b}}" , "a");} + @Test public void Ws_idx_nil() {fxt.Test_parse_tmpl_str_test("{{{ 1 }}}" , "{{test}}" , "{{{ 1 }}}");} + @Test public void Ws_key() {fxt.Test_parse_tmpl_str_test("{{{ k1 }}}" , "{{test|k1=a|k2=b}}" , "a");} + @Test public void Ws_dflt() {fxt.Test_parse_tmpl_str_test("{{{1| a }}}" , "{{test}}" , " a ");} + @Test public void Dflt_multiple() {fxt.Test_parse_tmpl_str_test("{{{1|a|b}}}" , "{{test}}" , "a");} + @Test public void Keyd_not_idxd() {fxt.Test_parse_tmpl_str_test("{{{1}}}{{{2}}}" , "{{test|a|key=b}}" , "a{{{2}}}");} + @Test public void Keyd_not_idxd_ints() {fxt.Test_parse_tmpl_str_test("{{{1}}}{{{2}}}" , "{{test|1=a|2=b}}" , "ab");} + @Test public void Recurse_1() {fxt.Test_parse_tmpl_str_test("{{{1{{{2|}}}|}}}" , "{{test|a}}" , "a");} // used in {{See}} to test if argument 2 is last + @Test public void Recurse_2() {fxt.Test_parse_tmpl_str_test("{{{1{{{2|}}}|}}}" , "{{test|a|b}}" , "");} + @Test public void Keyd_int() {fxt.Test_parse_tmpl_str_test("{{{1}}}{{{2}}}" , "{{test|2=a|b}}" , "ba");} + @Test public void Keyd_int2() {fxt.Test_parse_tmpl_str_test("{{{1}}}{{{2}}}" , "{{test|2=a|1=b}}" , "ba");} + @Test public void Keyd_int3() {fxt.Test_parse_tmpl_str_test("{{{12}}}" , "{{test|12=a}}" , "a");} + @Test public void Equal_ignored() {fxt.Test_parse_tmpl_str_test("{{{1|b=c}}}" , "{{test}}" , "b=c");} + @Test public void Unresolved() {fxt.Test_parse_tmpl_str_test("" , "{{{a|b}}}" , "b");} + @Test public void Six_ltr() {fxt.Test_parse_tmpl_str_test("{{{{{{1}}}}}}" , "{{test|a}}" , "{{{a}}}");} + @Test public void Six_num() {fxt.Test_parse_tmpl_str_test("{{{{{{1}}}}}}" , "{{test|1}}" , "1");} +} +/* +*/ diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_wkr.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_wkr.java index a27517de8..7cdf129db 100644 --- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_prm_wkr.java @@ -13,3 +13,32 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +class Xot_prm_wkr implements Xop_arg_wkr { + private static Arg_bldr arg_bldr = Arg_bldr.Instance; + public boolean Make_tkn(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int lxr_bgn_pos, int lxr_cur_pos, Xop_curly_bgn_tkn bgn, int keep_curly_bgn) { + int loop_bgn = bgn.Tkn_sub_idx() + 1; // +1 to ignore curly_bgn + int loop_end = root.Subs_len(); + if (loop_bgn == loop_end) {// no tkns; output literal {{{}}} // 2012.03.27:commented out due to {{{{{{}}}}}} + root.Subs_del_after(bgn.Tkn_sub_idx()); + ctx.Msg_log().Add_itm_none(Xot_prm_log.Lkp_is_nil, src, lxr_bgn_pos, lxr_cur_pos); + ctx.Subs_add(root, tkn_mkr.Txt(bgn.Src_bgn(), lxr_cur_pos)); + return false; + } + Xot_prm_tkn prm_tkn = tkn_mkr.Tmpl_prm(bgn.Src_bgn(), lxr_cur_pos); + arg_bldr.Bld(ctx, tkn_mkr, Xot_prm_wkr.Instance, Xop_arg_wkr_.Typ_prm, root, prm_tkn, lxr_bgn_pos, lxr_cur_pos, loop_bgn, loop_end, src); + root.Subs_del_after(bgn.Tkn_sub_idx() + keep_curly_bgn); // NOTE: keep_curly_bgn determines whether or not to delete opening {{{ + root.Subs_add(prm_tkn); + return true; + } + public boolean Args_add(Xop_ctx ctx, byte[] src, Xop_tkn_itm tkn, Arg_nde_tkn arg, int arg_idx) { + Xot_prm_tkn prm = (Xot_prm_tkn)tkn; + switch (arg_idx) { + case 0: prm.Find_tkn_(arg.Val_tkn()); break; + case 1: prm.Dflt_tkn_(arg.Val_tkn()); break; + default: ctx.Msg_log().Add_itm_none(Xot_prm_log.Prm_has_2_or_more, src, arg.Src_bgn(), arg.Src_end()); break; + } + return true; + } + public static final Xot_prm_wkr Instance = new Xot_prm_wkr(); Xot_prm_wkr() {} +} 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 a27517de8..78ad2fc6d 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 @@ -13,3 +13,97 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.envs.*; +import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.miscs.*; +public class Xot_tmpl_wtr { + public static byte[] Write_all(Xop_ctx ctx, Xot_invk frame, Xop_root_tkn root, byte[] src) { + Bry_bfr bfr = ctx.Wiki().Utl__bfr_mkr().Get_m001().Reset_if_gt(Io_mgr.Len_mb); + Write_tkn(bfr, ctx, frame, src, src.length, root); + byte[] rv = bfr.To_bry_and_rls(); + return ctx.Wiki().Parser_mgr().Uniq_mgr().Parse(rv); // NOTE: noops if no UNIQs; // UNIQ; DATE:2017-03-31 + } + private static void Write_tkn(Bry_bfr rslt_bfr, Xop_ctx ctx, Xot_invk frame, byte[] src, int src_len, Xop_tkn_itm tkn) { + switch (tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_root: // write each sub + int subs_len = tkn.Subs_len(); + for (int i = 0; i < subs_len; i++) { + Xop_tkn_itm sub_tkn = tkn.Subs_get(i); + if (!sub_tkn.Ignore()) + Write_tkn(rslt_bfr, ctx, frame, src, src_len, sub_tkn); + } + break; + case Xop_tkn_itm_.Tid_bry: + Xop_bry_tkn bry = (Xop_bry_tkn)tkn; + rslt_bfr.Add(bry.Val()); + break; + case Xop_tkn_itm_.Tid_space: + if (tkn.Tkn_immutable()) + rslt_bfr.Add_byte(Byte_ascii.Space); + else + rslt_bfr.Add_byte_repeat(Byte_ascii.Space, tkn.Src_end() - tkn.Src_bgn()); + break; + case Xop_tkn_itm_.Tid_xnde: + Xop_xnde_tkn xnde = (Xop_xnde_tkn)tkn; + int xnde_tag_id = xnde.Tag().Id(); + switch (xnde_tag_id) { + case Xop_xnde_tag_.Tid__onlyinclude: { + // NOTE: originally "if (ctx.Parse_tid() == Xop_parser_tid_.Tid__tmpl) {" but if not needed; Xot_tmpl_wtr should not be called for tmpls and should not make it to page_wiki + Bry_bfr tmp_bfr = Bry_bfr_.New(); + ctx.Only_include_evaluate_(true); + xnde.Tmpl_evaluate(ctx, src, Xot_invk_temp.Page_is_caller, tmp_bfr); + ctx.Only_include_evaluate_(false); + rslt_bfr.Add_bfr_and_preserve(tmp_bfr); + break; + } + case Xop_xnde_tag_.Tid__includeonly: // noop; DATE:2014-02-12 + break; + case Xop_xnde_tag_.Tid__nowiki: { + 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(); + int nowiki_content_bgn = xnde.Tag_open_end(), nowiki_content_end = xnde.Tag_close_bgn(); + 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(); + } + break; + } + case Xop_xnde_tag_.Tid__xowa_cmd: + gplx.xowa.xtns.xowa_cmds.Xop_xowa_cmd xowa_cmd = (gplx.xowa.xtns.xowa_cmds.Xop_xowa_cmd)xnde.Xnde_xtn(); + rslt_bfr.Add(xowa_cmd.Xtn_html()); + break; + default: + rslt_bfr.Add_mid(src, tkn.Src_bgn(), tkn.Src_end()); // write src from bgn/end + break; + } + break; + default: + rslt_bfr.Add_mid(src, tkn.Src_bgn(), tkn.Src_end()); break; // write src from bgn/end + case Xop_tkn_itm_.Tid_ignore: break; // hide comments and <*include*> ndes + case Xop_tkn_itm_.Tid_tmpl_prm: + tkn.Tmpl_evaluate(ctx, src, Xot_invk_temp.Page_is_caller.Src_(src), rslt_bfr); + break; + case Xop_tkn_itm_.Tid_tvar: + gplx.xowa.xtns.translates.Xop_tvar_tkn tvar_tkn = (gplx.xowa.xtns.translates.Xop_tvar_tkn)tkn; + rslt_bfr.Add(tvar_tkn.Wikitext()); + break; + case Xop_tkn_itm_.Tid_tmpl_invk: + try { + if (frame == Xot_invk_temp.Null_frame) { // NOTE: should probably remove lazy-instantiation and always force frame to be passed in; DATE:2017-09-03 + frame = Xot_invk_temp.Page_is_caller.Src_(src); + } + tkn.Tmpl_evaluate(ctx, src, frame, rslt_bfr); + } + catch (Exception e) { + String err_string = String_.new_u8(src, tkn.Src_bgn(), tkn.Src_end()) + "|" + Type_.Name_by_obj(e) + "|" + Err_.Cast_or_make(e).To_str__log(); + if (Env_.Mode_testing()) + throw Err_.new_exc(e, "xo", err_string); + else + ctx.App().Usr_dlg().Warn_many("", "", "failed to write tkn: page=~{0} err=~{1}", String_.new_u8(ctx.Page().Ttl().Page_db()), err_string); + } + break; + } + } +} 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 a27517de8..5effe2d1f 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 @@ -13,3 +13,119 @@ 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.*; +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; + } + 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 void Parse(Bry_bfr bfr) { + if (general_trie.Count() == 0) return; + byte[] rv = Parse(key_bfr, general_trie, 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) { + int src_len = src.length; + int pos = 0; + int mark_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); + if (o == null) + ++pos; + 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); +// 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; + } + if (is_last) { + if (dirty) + bfr.Add_mid(src, mark_bgn, src_len); + break; + } + } + return dirty ? bfr.To_bry_and_clear() : src; + } + public byte[] Uniq_bry_new() { + return Bry_.Add + ( Bry__uniq__bgn // "\x7fUNIQ" where "\x7f" is (byte)127 + , Random_bry_new(16)); // random hexdecimal String + } + 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(); + 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); + } + byte[] rv = key_bfr.To_bry(0, len); + key_bfr.Clear(); + return rv; + } + + private 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__parse__tst.java b/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_mgr__parse__tst.java index a27517de8..af033a38f 100644 --- a/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_mgr__parse__tst.java +++ b/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_mgr__parse__tst.java @@ -13,3 +13,14 @@ 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 org.junit.*; import gplx.core.tests.*; +public class Xop_uniq_mgr__parse__tst { + private final Xop_fxt fxt = Xop_fxt.New_app_html(); + @Before public void init() {fxt.Reset();} + @Test public void Ref_becomes_UNIQ() { + String wikitext = "b"; + fxt.Init_defn_add("test", "{{#ifeq:{{{1}}}|a" + wikitext + "c|fail|pass}}"); // fail if {{{1}}} is still wikitext; should be UNIQ + fxt.Test__parse__tmpl_to_html("{{test|a" + wikitext + "c}}", "pass"); + } +} 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 a27517de8..8ed2a6cf7 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 @@ -13,3 +13,67 @@ 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 org.junit.*; import gplx.core.tests.*; +public class Xop_uniq_mgr__tst { + private final Xop_uniq_mgr__fxt fxt = new Xop_uniq_mgr__fxt(); + @Before public void init() {fxt.Init();} + @Test public void Test__random_bry() { + fxt.Init_random_int_ary(Int_ary_.New(240563374, 22728940, 1451248133)); + fxt.Test__uniq_bry_new("'\"`UNIQ-E56B4AE15AD0EC68"); + + fxt.Init_random_int_ary(Int_ary_.New(1363621437, 426295411, 421041101)); + fxt.Test__uniq_bry_new("'\"`UNIQ-147363D968C07391"); + } + @Test public void Add_and_get() { + String expd_key = "'\"`UNIQ--item-0-QINU`\"'"; + fxt.Test__add("a", expd_key); + fxt.Test__get(expd_key, "a"); + } + @Test public void Parse__basic() { + String expd_key = "'\"`UNIQ--item-0-QINU`\"'"; + fxt.Test__add("_b_", expd_key); + fxt.Test__parse("a" + expd_key + "c", "a_b_c"); + } + @Test public void Parse__recurse() { + String key_0 = "'\"`UNIQ--item-0-QINU`\"'"; + String key_1 = "'\"`UNIQ--item-1-QINU`\"'"; + String key_2 = "'\"`UNIQ--item-2-QINU`\"'"; + fxt.Test__add("0", key_0); + fxt.Test__add("1-" + key_0 + "-1", key_1); + fxt.Test__add("2-" + key_1 + "-2", key_2); + fxt.Test__parse("3-" + key_2 + "-3", "3-2-1-0-1-2-3"); + } + @Test public void Convert() { + String key = "'\"`UNIQ--item-0-QINU`\"'"; + fxt.Test__add("2", key); + fxt.Test__convert("1" + key + "3", "123"); + } + @Test public void Convert__many() { + String key_0 = "'\"`UNIQ--item-0-QINU`\"'"; + String key_1 = "'\"`UNIQ--item-1-QINU`\"'"; + fxt.Test__add("0", key_0); + fxt.Test__add("1", key_1); + fxt.Test__convert("a " + key_0 + " b " + key_1 + " c", "a 0 b 1 c"); + } +} +class Xop_uniq_mgr__fxt { + private final Xop_uniq_mgr mgr = new Xop_uniq_mgr(); + public Xop_uniq_mgr__fxt Init_random_int_ary(int... v) {mgr.Random_int_ary_(v); return this;} + public void Init() {mgr.Clear();} + public void Test__uniq_bry_new(String expd) { + 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"); + } + public void Test__get(String key, String expd) { + Gftest.Eq__str(expd, String_.new_a7(mgr.Get(Bry_.new_a7(key))), "get"); + } + public void Test__parse(String raw, String expd) { + 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"); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/utils/TstObj_tst.java b/400_xowa/src/gplx/xowa/parsers/utils/TstObj_tst.java index a27517de8..394dad529 100644 --- a/400_xowa/src/gplx/xowa/parsers/utils/TstObj_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/utils/TstObj_tst.java @@ -13,3 +13,219 @@ 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.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.core.strings.*; import gplx.core.type_xtns.*; import gplx.core.stores.*; import gplx.core.envs.*; +interface TstRuleMgr { + boolean SkipChkVal(String expdTypeKey, TstAtr expd); + boolean SkipChkObj(String expdTypeKey, String atrKey, TstObj expd); +} +class Xop_rule_mgr implements TstRuleMgr { + public boolean SkipChkVal(String expdTypeKey, TstAtr expd) { + String key = expdTypeKey + "." + expd.Key(); + Xop_rule_dat ruleDat = (Xop_rule_dat)hash.Get_by(key); if (ruleDat == null) return false; + if (expd.ValType().Eq(expd.Val(), ruleDat.SkipVal())) return true; + return false; + } + public boolean SkipChkObj(String expdTypeKey, String atrKey, TstObj expd) { + String key = expdTypeKey + "." + atrKey; + Xop_rule_dat ruleDat = (Xop_rule_dat)hash.Get_by(key); if (ruleDat == null) return false; + TstAtr expdAtr = (TstAtr)expd.Atrs().Get_by(ruleDat.SubKey()); + if (expdAtr == null) return false; + if (expdAtr.ValType().Eq(expdAtr.Val(), ruleDat.SkipVal())) return true; + return false; + } + public Xop_rule_mgr TypeKey_(String v) {typeKey = v; return this;} private String typeKey; + public Xop_rule_mgr SkipIf_double(String atrKey, double v) {return SkipIfObj(atrKey, null, v);} + public Xop_rule_mgr SkipIf(String atrKey, String v) {return SkipIfObj(atrKey, null, v);} + public Xop_rule_mgr SkipIf(String atrKey, byte v) {return SkipIfObj(atrKey, null, v);} + public Xop_rule_mgr SkipIf(String atrKey, int v) {return SkipIfObj(atrKey, null, v);} + public Xop_rule_mgr SkipIf(String atrKey, boolean v) {return SkipIfObj(atrKey, null, v);} + public Xop_rule_mgr SkipIf(String atrKey, String subKey, Object o) {return SkipIfObj(atrKey, subKey, o);} + public Xop_rule_mgr SkipIfObj_many(String[] atrKeys, String subKey, Object o) {for (String atrKey : atrKeys) SkipIfObj(atrKey, subKey, o); return this;} + Xop_rule_mgr SkipIfObj(String atrKey, String subKey, Object skipVal) { + String key = typeKey + "." + atrKey; + Xop_rule_dat ruleDat = new Xop_rule_dat(key, subKey, skipVal); + hash.Add(key, ruleDat); + return this; + } + public String Reg() {return typeKey;} + Ordered_hash hash = Ordered_hash_.New(); + public static final Xop_rule_mgr Instance = new Xop_rule_mgr(); +} +class Xop_rule_dat { + public String AtrKey() {return atrKey;} private String atrKey; + public String SubKey() {return subKey;} private String subKey; + public Object SkipVal() {return skipVal;} Object skipVal; + public Xop_rule_dat(String atrKey, String subKey, Object skipVal) {this.atrKey = atrKey; this.subKey = subKey; this.skipVal = skipVal;} +} +public class TstObj_tst { + @Test public void Basic() { + tst_(mock_().Val1_(1).Val2_("a"), mock_().Val1_(1).Val2_("a")); +// tst_(mock_().Val1_(3).Val2_("a"), mock_().Val1_(1).Val2_("b")); + } + MockObj mock_() {return new MockObj();} + private void tst_(MockObj expd, MockObj actl) { + TstObj expdChk = TstObj.new_(), actlChk = TstObj.new_(); + expd.SrlObj_Srl(expdChk); + actl.SrlObj_Srl(actlChk); + Eval("", expdChk, actlChk, new Xop_rule_mgr()); + } + private static void Max(int[] ary, int idx, String val) { + int len = String_.Len(val); + if (len > ary[idx]) ary[idx] = len; + } + private static int Add(int[] ary) {int rv = 0; for (int i = 0; i < ary.length; i++) rv += ary[i]; return rv;} + @gplx.Internal protected static void Eval(String raw, TstObj expdChk, TstObj actlChk, TstRuleMgr ruleMgr) { + List_adp rslts = List_adp_.New(); + Eval(rslts, ruleMgr, Ordered_hash_.New(), "", expdChk, actlChk); + + String_bldr sb = String_bldr_.new_(); + sb.Add(raw).Add(Op_sys.Lnx.Nl_str()); + boolean pass = true; + int[] cols = new int[3]; + for (int i = 0; i < rslts.Count(); i++) { + TstRslt rslt = (TstRslt)rslts.Get_at(i); + Max(cols, 0, rslt.EvalStr()); + Max(cols, 1, rslt.Id()); + Max(cols, 2, rslt.Key()); + } + String hdr = String_.Repeat(" ", Add(cols) + 3); + for (int i = 0; i < rslts.Count(); i++) { + TstRslt rslt = (TstRslt)rslts.Get_at(i); +// if (rslt.EvalPass()) continue; + sb .Add(String_.PadEnd(rslt.EvalStr(), cols[0] + 1, " ")) + .Add(String_.PadEnd(rslt.Id(), cols[1] + 1, " ")) + .Add(String_.PadEnd(rslt.Key(), cols[2] + 1, " ")) + .Add(rslt.ExpdStr()).Add(Op_sys.Lnx.Nl_str()); + if (!rslt.EvalPass()) { + sb.Add(hdr).Add(rslt.ActlStr()).Add(Op_sys.Lnx.Nl_str()); + pass = false; + } + } + if (pass) return; + throw Err_.new_wo_type(Op_sys.Lnx.Nl_str() + sb.To_str()); + } + private static void Eval(List_adp rslts, TstRuleMgr ruleMgr, Ordered_hash props, String idx, TstObj expd, TstObj actl) { + int expdLen = expd.Atrs().Count(); + props.Clear(); + for (int i = 0; i < expdLen; i++) { + TstAtr expdAtr = (TstAtr)expd.Atrs().Get_at(i); + String key = expdAtr.Key(); + TstAtr actlAtr = (TstAtr)actl.Atrs().Get_by(key); + if (expdAtr.ValType() == ObjectClassXtn.Instance) { + SrlObj expdSrl = (SrlObj)expdAtr.Val(); + TstObj expdTst = TstObj.new_(); + expdSrl.SrlObj_Srl(expdTst); + TstObj actlTst = TstObj.new_(); + if (actlAtr != null) ((SrlObj)actlAtr.Val()).SrlObj_Srl(actlTst); + if (ruleMgr.SkipChkObj(expdAtr.TypeKey(), key, expdTst)) continue; + Eval(rslts, ruleMgr, Ordered_hash_.New(), idx + "." + key, expdTst, actlTst); + } + else { + if (actlAtr == null) actlAtr = new TstAtr(); + if (ruleMgr.SkipChkVal(expdAtr.TypeKey(), expdAtr)) continue; + Eval(rslts, idx, key, expdAtr, actlAtr, true); + } + props.Add(key, key); + } + int expdSubsLen = expd.Subs().Count(), actlSubsLen = actl.Subs().Count(); + int maxLen = expdSubsLen > actlSubsLen ? expdSubsLen : actlSubsLen; + for (int i = 0; i < maxLen; i++) { + TstObj expdSub = i < expdSubsLen ? (TstObj)expd.Subs().Get_at(i) : TstObj.Null; + TstObj actlSub = i < actlSubsLen ? (TstObj)actl.Subs().Get_at(i) : TstObj.Null; +// if (ruleMgr != null) ruleMgr.Eval(expd.TypeKey(), expdSub.PropName(), expdAtr, actlAtr, skip); + String iAsStr = Int_.To_str(i); + String subId = String_.Eq(idx, "") ? iAsStr : idx + "." + iAsStr; + if (expdSub == TstObj.Null && actlSub != TstObj.Null) { + TstAtr mis = new TstAtr().Key_("idx").Val_(i).ValType_(IntClassXtn.Instance); + rslts.Add(new TstRslt().Expd_(mis).Actl_(mis).EvalPass_(false).EvalStr_("!=") + .Id_(subId).Key_("sub_ref") + .ExpdStr_("null").ActlStr_("not null")); + continue; + } + Eval(rslts, ruleMgr, props, subId, expdSub, actlSub); + } + } + private static void Eval(List_adp rslts, String id, String key, TstAtr expd, TstAtr actl, boolean srcIsExpd) { + int evalType = 0; + boolean evalPass = false; String evalStr = ""; + switch (evalType) { + case 0: + if (expd.ValType().Eq(expd.Val(), actl.Val())) { + evalPass = true; + evalStr = "=="; + } + else { + evalPass = false; + evalStr = "!="; + } + break; + } + TstRslt rslt = new TstRslt().Expd_(expd).Actl_(actl) + .Id_(id).Key_(key) + .EvalType_(evalType).EvalPass_(evalPass).EvalStr_(evalStr) + .ExpdStr_(Object_.Xto_str_strict_or_null_mark(expd.Val())).ActlStr_(Object_.Xto_str_strict_or_null_mark(actl.Val())) + ; + rslts.Add(rslt); + } +} +class MockObj { + public int Val1() {return val1;} public MockObj Val1_(int v) {val1 = v; return this;} private int val1; + public String Val2() {return val2;} public MockObj Val2_(String v) {val2 = v; return this;} private String val2; + public void SrlObj_Srl(SrlMgr mgr) { + mgr.TypeKey_("MockObj"); + val1 = mgr.SrlIntOr("val1", val1); + val2 = mgr.SrlStrOr("val2", val2); + } +} +class TstObj implements SrlMgr { + public boolean Type_rdr() {return false;} + public Ordered_hash Atrs() {return atrs;} private Ordered_hash atrs = Ordered_hash_.New(); + public Object StoreRoot(SrlObj root, String key) {return null;} + public boolean SrlBoolOr(String key, boolean v) {Atrs_add(key, v, BoolClassXtn.Instance); return v;} + public byte SrlByteOr(String key, byte v) {Atrs_add(key, v, ByteClassXtn.Instance); return v;} + public int SrlIntOr(String key, int v) {Atrs_add(key, v, IntClassXtn.Instance); return v;} + public long SrlLongOr(String key, long v) {Atrs_add(key, v, LongClassXtn.Instance); return v;} + public String SrlStrOr(String key, String v) {Atrs_add(key, v, StringClassXtn.Instance); return v;} + public Decimal_adp SrlDecimalOr(String key, Decimal_adp v) {Atrs_add(key, v, DecimalAdpClassXtn.Instance); return v;} + public DateAdp SrlDateOr(String key, DateAdp v) {Atrs_add(key, v, DateAdpClassXtn.Instance); return v;} + public double SrlDoubleOr(String key, double v) {Atrs_add(key, v, DoubleClassXtn.Instance); return v;} + public Object SrlObjOr(String key, Object v) { + Atrs_add(key, v, ObjectClassXtn.Instance); + return v; + } + public void SrlList(String key, List_adp list, SrlObj proto, String itmKey) {} + public String TypeKey() {return typeKey;} public void TypeKey_(String v) {typeKey = v;} private String typeKey; + private void Atrs_add(String key, Object val, ClassXtn valType) { + atrs.Add(key, new TstAtr().TypeKey_(typeKey).Key_(key).Val_(val).ValType_(valType)); + } + public List_adp Subs() {return subs;} List_adp subs = List_adp_.Noop; + public SrlMgr SrlMgr_new(Object o) {return Subs_new();} + public TstObj Subs_new() { + if (subs == List_adp_.Noop) subs = List_adp_.New(); + TstObj rv = TstObj.new_(); + subs.Add(rv); + return rv; + } + public static TstObj new_() {return new TstObj();} TstObj() {} + public static final TstObj Null = new TstObj(); +} +class TstAtr { + public String TypeKey() {return typeKey;} public TstAtr TypeKey_(String v) {typeKey = v; return this;} private String typeKey; + public String Key() {return key;} public TstAtr Key_(String v) {key = v; return this;} private String key; + public Object Val() {return val;} public TstAtr Val_(Object v) {val = v; return this;} Object val; + public ClassXtn ValType() {return valType;} public TstAtr ValType_(ClassXtn v) {valType = v; return this;} ClassXtn valType; +} +class TstRslt { + public TstAtr Expd() {return expd;} public TstRslt Expd_(TstAtr v) {expd = v; return this;} TstAtr expd; + public TstAtr Actl() {return actl;} public TstRslt Actl_(TstAtr v) {actl = v; return this;} TstAtr actl; + public boolean Ignore() {return ignore;} public TstRslt Ignore_y_(boolean v) {ignore = v; return this;} private boolean ignore; + public int EvalType() {return evalType;} public TstRslt EvalType_(int v) {evalType = v; return this;} private int evalType; + public boolean EvalPass() {return evalPass;} public TstRslt EvalPass_(boolean v) {evalPass = v; return this;} private boolean evalPass; + public String EvalStr() {return evalStr;} public TstRslt EvalStr_(String v) {evalStr = v; return this;} private String evalStr; + public String Id() {return id;} public TstRslt Id_(String v) {id = v; return this;} private String id; + public String Key() {return key;} public TstRslt Key_(String v) {key = v; return this;} private String key; + public String ActlStr() {return actlStr;} public TstRslt ActlStr_(String v) {actlStr = v; return this;} private String actlStr; + public String ExpdStr() {return expdStr;} public TstRslt ExpdStr_(String v) {expdStr = v; return this;} private String expdStr; + public static final Object Ignore_null = new Object(); +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/utils/Xop_redirect_mgr.java b/400_xowa/src/gplx/xowa/parsers/utils/Xop_redirect_mgr.java index a27517de8..68fe02806 100644 --- a/400_xowa/src/gplx/xowa/parsers/utils/Xop_redirect_mgr.java +++ b/400_xowa/src/gplx/xowa/parsers/utils/Xop_redirect_mgr.java @@ -13,3 +13,123 @@ 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.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.langs.htmls.*; import gplx.langs.htmls.encoders.*; import gplx.xowa.htmls.*; import gplx.xowa.htmls.hrefs.*; import gplx.xowa.parsers.tmpls.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; import gplx.xowa.langs.kwds.*; +import gplx.xowa.wikis.pages.redirects.*; +public class Xop_redirect_mgr { + private final Xowe_wiki wiki; private final Gfo_url_encoder url_decoder; private Hash_adp_bry redirect_hash; + public Xop_redirect_mgr(Xowe_wiki wiki) {this.wiki = wiki; this.url_decoder = gplx.langs.htmls.encoders.Gfo_url_encoder_.Http_url_ttl;} // NOTE: must be Url_ttl, not Url; PAGE:en.w:Template:Positionskarte+ -> Template:Location_map+, not Template:Location_map DATE:2014-08-21 + public void Clear() {redirect_hash = null;} // TEST: + public boolean Is_redirect(byte[] text, int text_len) {return this.Extract_redirect(text, text_len) != null;} + public Xoa_ttl Extract_redirect_loop(byte[] src) { + Xoa_ttl rv = null; + for (int i = 0; i < Redirect_max_allowed; i++) { + rv = Extract_redirect(src); + if (rv != null) return rv; + } + return null; + } + public Xoa_ttl Extract_redirect(byte[] src) { + if (src == null) return Redirect_null_ttl; + return Extract_redirect(src, src.length); + } + public Xoa_ttl Extract_redirect(byte[] src, int src_len) { // NOTE: this proc is called by every page. be careful of changes; DATE:2014-07-05 + if (src_len == 0) return Redirect_null_ttl; + int bgn = Bry_find_.Find_fwd_while_not_ws(src, 0, src_len); + if (bgn == src_len) return Redirect_null_ttl; // article is entirely whitespace + int kwd_end = Xop_redirect_mgr_.Get_kwd_end_or_end(src, bgn, src_len); + if (kwd_end == src_len) return Redirect_null_ttl; + if (redirect_hash == null) redirect_hash = Xol_kwd_mgr.hash_(wiki.Lang().Kwd_mgr(), Xol_kwd_grp_.Id_redirect); + Object redirect_itm = redirect_hash.Get_by_mid(src, bgn, kwd_end); + if (redirect_itm == null) return Redirect_null_ttl; // not a redirect kwd + int ttl_bgn = Xop_redirect_mgr_.Get_ttl_bgn_or_neg1(src, kwd_end, src_len); + if (ttl_bgn == Bry_find_.Not_found) return Redirect_null_ttl; + ttl_bgn += Xop_tkn_.Lnki_bgn.length; + int ttl_end = Bry_find_.Find_fwd(src, Xop_tkn_.Lnki_end, ttl_bgn); if (ttl_end == Bry_find_.Not_found) return Redirect_null_ttl; + int pipe_pos = Bry_find_.Find_fwd(src, Byte_ascii.Pipe, ttl_bgn); + if ( pipe_pos != Bry_find_.Not_found // if pipe exists; PAGE:da.w:Middelaldercentret; DATE:2015-11-06 + && pipe_pos < ttl_end) // and pipe is before ]]; do not take pipe from next lnki; PAGE:en.w:Template:pp-semi; DATE:2015-11-14 + ttl_end = pipe_pos; // end ttl at pipe + byte[] redirect_bry = Bry_.Mid(src, ttl_bgn, ttl_end); + redirect_bry = url_decoder.Decode(redirect_bry); // NOTE: url-decode links; PAGE: en.w:Watcher_(Buffy_the_Vampire_Slayer); DATE:2014-08-18 + return Xoa_ttl.Parse(wiki, redirect_bry); + } + public static final Xoa_ttl Extract_redirect_is_null = null; + public static final int Redirect_max_allowed = 4; + public static final Xoa_ttl Redirect_null_ttl = null; + public static final byte[] Redirect_null_bry = Bry_.Empty; + private static final byte[] Redirect_bry = Bry_.new_a7("#REDIRECT "); + public static byte[] Make_redirect_text(byte[] redirect_to_ttl) { + return Bry_.Add + ( Redirect_bry // "#REDIRECT " + , Xop_tkn_.Lnki_bgn // "[[" + , redirect_to_ttl // "Page" + , Xop_tkn_.Lnki_end // "]]" + ); + } + public static byte[] Bld_redirect_msg(Xoae_app app, Xowe_wiki wiki, Xopg_redirect_mgr redirect_mgr) { + // NOTE: this assumes that redirect_mgr only has redirect_src, not redirect_trg; note that #REDIRECT [[A]] only adds redirect_src, whereas special redirects add redirect_trg; DATE:2016-07-31 + int len = redirect_mgr.Itms__len(); if (len == 0) return Bry_.Empty; + Bry_bfr redirect_bfr = wiki.Utl__bfr_mkr().Get_b512(); + boolean dirty = false; + for (int i = 0; i < len; i++) { + Xopg_redirect_itm redirect_itm = redirect_mgr.Itms__get_at(i); + if (!redirect_itm.By_wikitext()) continue; // ignore Special:Redirects else Special:Random will always show "redirected from"; DATE:2016-07-05 + dirty = true; + if (i != 0) redirect_bfr.Add(Bry_redirect_dlm); + byte[] ttl_unders = redirect_itm.Ttl().Full_db(); + byte[] ttl_spaces = Xoa_ttl.Replace_unders(ttl_unders); + redirect_bfr.Add(Gfh_bldr_.Bry__a_lhs_w_href) // '' + .Add(ttl_spaces) // 'PageA' + .Add(Gfh_bldr_.Bry__a_rhs); // + } + if (!dirty) return Bry_.Empty; // ignore Special:Redirects else Special:Random will always show "redirected from"; DATE:2016-07-05 + Xol_msg_itm msg_itm = wiki.Lang().Msg_mgr().Itm_by_id_or_null(Xol_msg_itm_.Id_redirectedfrom); + Bry_bfr fmt_bfr = wiki.Utl__bfr_mkr().Get_b512(); + app.Tmp_fmtr().Fmt_(msg_itm.Val()).Bld_bfr_one(fmt_bfr, redirect_bfr); + redirect_bfr.Clear().Mkr_rls(); fmt_bfr.Mkr_rls(); + return fmt_bfr.To_bry_and_clear(); + } private static byte[] Bry_redirect_dlm = Bry_.new_a7(" <--- "), Bry_redirect_arg = Bry_.new_a7("?redirect=no"); +} +class Xop_redirect_mgr_ { + public static int Get_kwd_end_or_end(byte[] src, int bgn, int end) { // get end of kwd + for (int i = bgn; i < end; ++i) { + switch (src[i]) { + case Byte_ascii.Nl: case Byte_ascii.Space: case Byte_ascii.Tab: + case Byte_ascii.Brack_bgn: case Byte_ascii.Colon: + return i; // ASSUME: kwd does not have these chars + default: + break; + } + } + return end; + } + public static int Get_ttl_bgn_or_neg1(byte[] src, int bgn, int end) { // get bgn of ttl + boolean colon_null = true; + for (int i = bgn; i < end; ++i) { + switch (src[i]) { + case Byte_ascii.Nl: case Byte_ascii.Space: case Byte_ascii.Tab: break; // skip all ws + case Byte_ascii.Colon: // allow 1 colon + if (colon_null) + colon_null = false; + else + return Bry_find_.Not_found; + break; + default: + break; + case Byte_ascii.Brack_bgn: + int nxt_pos = i + 1; + if (nxt_pos >= end) return Bry_find_.Not_found; // [ at eos + return src[nxt_pos] == Byte_ascii.Brack_bgn ? i : Bry_find_.Not_found; + } + } + return Bry_find_.Not_found; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/utils/Xop_redirect_mgr_tst.java b/400_xowa/src/gplx/xowa/parsers/utils/Xop_redirect_mgr_tst.java index a27517de8..cda2c374a 100644 --- a/400_xowa/src/gplx/xowa/parsers/utils/Xop_redirect_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/utils/Xop_redirect_mgr_tst.java @@ -13,3 +13,72 @@ 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.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.xowa.langs.kwds.*; import gplx.xowa.parsers.tmpls.*; +public class Xop_redirect_mgr_tst { + @Before public void init() {fxt.Clear();} private Xop_redirect_mgr_fxt fxt = new Xop_redirect_mgr_fxt(); + @Test public void Basic() {fxt.Test_redirect("#REDIRECT [[a]]", "A");} + @Test public void Basic_colon() {fxt.Test_redirect("#REDIRECT:[[a]]", "A");} + @Test public void Ns_help() {fxt.Test_redirect("#REDIRECT [[Help:a]]", "Help:A");} + @Test public void First() {fxt.Test_redirect("#REDIRECT [[a]] [[b]]", "A");} + @Test public void Exc_false_match() {fxt.Test_redirect("#REDIRECTA [[a]]", "");} + @Test public void Exc_lnki_not_found() {fxt.Test_redirect("#REDIRECT test", "");} + @Test public void Ws() {fxt.Test_redirect("\n#REDIRECT [[a]]", "A");} // PAGE:en.w:Germany; {{Template group}} -> \n#REDIRECT [[Template:Navboxes]] + @Test public void U8() { + fxt.Init_u8(); + fxt.Init_kwds(Bool_.N, "#REDIRECT", "#перенаправление"); + fxt.Test_redirect("#REDIRECT [[A]]", "A"); + fxt.Test_redirect("#reDirect [[A]]", "A"); + fxt.Test_redirect("#перенаправление [[A]]", "A"); + fxt.Test_redirect("#ПЕРЕНАПРАВЛЕНИЕ [[A]]", "A"); + } + @Test public void Url_decode() {fxt.Test_redirect("#REDIRECT [[A%28B%29]]" , "A(B)");} // PURPOSE: url-decode links; PAGE:en.w:Watcher_(Buffy_the_Vampire_Slayer); DATE:2014-08-18 + @Test public void Url_decode_plus() {fxt.Test_redirect("#REDIRECT [[A%28B%29+]]", "A(B)+");} // PURPOSE: do not url-decode +; PAGE:en.w:Template:Positionskarte+; DATE:2014-08-22 + @Test public void Amp() {fxt.Test_redirect("#REDIRECT [[A & B]]", "A & B");} // PURPOSE: & -> &; PAGE:en.w:Amadou Bagayoko?redirect=n; DATE:2014-09-23 + @Test public void Frame_ttl() { // PURPOSE: redirect should set invk frame title to redirect_trg, not original; PAGE:en.w:Statutory_city DATE:2014-08-22 + fxt.Test_frame_ttl("Template:A", "#REDIRECT [[Template:B]]", "Template:B", "Template:B"); + } + @Test public void State_collapsed() { // PURPOSE: state=collapsed broke redirects; PAGE:da.w:Middelaldercentret; DATE:2015-11-06 + fxt.Test_redirect("#REDIRECT [[Template:A|state=collapsed]]", "Template:A"); + } + @Test public void Parse_1st_link_only() { // PURPOSE: do not take pipe from 2nd lnki; PAGE:en.w:Template:pp-semi; DATE:2015-11-14 + fxt.Test_redirect("#REDIRECT [[Template:A]][[Category:B|b]]", "Template:A"); + } + @Test public void Redirected_html() { // PURPOSE: "Redirected from" message was using "_" instead of " "; PAGE:en.w:Summer_Solstice; DATE:2015-12-29 + fxt.Test__redirected_html("A_B", "(Redirected from A B)"); + } +} +class Xop_redirect_mgr_fxt { + private final Xop_fxt fxt = new Xop_fxt(); + public void Clear() { + fxt.Reset(); + } + public void Init_kwds(boolean case_match, String... kwds) {fxt.Init_lang_kwds(Xol_kwd_grp_.Id_redirect, case_match, kwds);} + public void Init_u8() { + fxt.Wiki().Lang().Case_mgr_u8_(); + } + public void Test_frame_ttl(String tmpl_ttl_str, String tmpl_wtxt_str, String redirect_ttl, String expd_frame_ttl) { + fxt.Init_page_create(tmpl_ttl_str, tmpl_wtxt_str); // create redirect_src + fxt.Init_page_create(redirect_ttl, "test"); // create redirect_trg + fxt.Test_parse_page_tmpl_tkn("{{" + tmpl_ttl_str + "}}"); // parse {{redirect_src}} + Xoa_ttl tmpl_ttl = Xoa_ttl.Parse(fxt.Wiki(), Bry_.new_u8(tmpl_ttl_str)); + Xot_defn_tmpl defn_tmpl = (Xot_defn_tmpl)fxt.Wiki().Cache_mgr().Defn_cache().Get_by_key(tmpl_ttl.Page_db()); // get defn (which parse should have created) + Tfds.Eq(expd_frame_ttl, String_.new_u8(defn_tmpl.Frame_ttl())); // check frame_ttl + } + public void Test_redirect(String raw_str, String expd_str) { + Xop_redirect_mgr redirect_mgr = fxt.Ctx().Wiki().Redirect_mgr(); + redirect_mgr.Clear(); + byte[] raw_bry = Bry_.new_u8(raw_str); + Xoa_ttl actl_ttl = redirect_mgr.Extract_redirect(raw_bry); + byte[] actl_bry = actl_ttl == null ? Bry_.Empty : actl_ttl.Full_txt_w_ttl_case(); + Tfds.Eq(expd_str, String_.new_u8(actl_bry)); + } + public void Test__redirected_html(String page_str, String expd_str) { + gplx.xowa.wikis.pages.redirects.Xopg_redirect_mgr redirect_mgr = new gplx.xowa.wikis.pages.redirects.Xopg_redirect_mgr(); + Xoa_ttl ttl = fxt.Wiki().Ttl_parse(Bry_.new_u8(page_str)); + Xoa_url url = Xoa_url.New(fxt.Wiki(), ttl); + redirect_mgr.Itms__add__article(url, ttl, Bry_.Empty); + byte[] actl_bry = Xop_redirect_mgr.Bld_redirect_msg(fxt.App(), fxt.Wiki(), redirect_mgr); + Tfds.Eq_str(expd_str, String_.new_u8(actl_bry)); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/utils/Xop_sanitizer.java b/400_xowa/src/gplx/xowa/parsers/utils/Xop_sanitizer.java index a27517de8..2301c3ca3 100644 --- a/400_xowa/src/gplx/xowa/parsers/utils/Xop_sanitizer.java +++ b/400_xowa/src/gplx/xowa/parsers/utils/Xop_sanitizer.java @@ -13,3 +13,98 @@ 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.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.parsers.amps.*; import gplx.core.log_msgs.*; +import gplx.langs.htmls.entitys.*; +public class Xop_sanitizer { + private Btrie_slim_mgr trie = Btrie_slim_mgr.cs(), amp_trie; + private Xop_amp_mgr amp_mgr; + private Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); + public Xop_sanitizer(Xop_amp_mgr amp_mgr, Gfo_msg_log msg_log) { + this.amp_mgr = amp_mgr; this.amp_trie = amp_mgr.Amp_trie(); + trie_add("&" , Tid_amp); + trie_add(" " , Tid_space); + trie_add("%3A" , Tid_colon); + trie_add("%3a" , Tid_colon); + trie_add("%" , Tid_percent); + } + private void trie_add(String hook, byte tid) {trie.Add_stub(hook, tid);} + public byte[] Escape_id(byte[] src) { + boolean dirty = Escape_id(src, 0, src.length, tmp_bfr); + return dirty ? tmp_bfr.To_bry_and_clear() : src; + } + public boolean Escape_id(byte[] src, int bgn, int end, Bry_bfr bfr) { + boolean dirty = false; + int pos = bgn; + boolean loop = true; + while (loop) { + if (pos == end) break; + byte b = src[pos]; + Object o = trie.Match_bgn_w_byte(b, src, pos, end); + if (o == null) { + if (dirty) bfr.Add_byte(b); + ++pos; + } + else { + if (!dirty) { + bfr.Add_mid(src, bgn, pos); + dirty = true; + } + Btrie_itm_stub stub = (Btrie_itm_stub)o; + switch (stub.Tid()) { + case Tid_space: bfr.Add_byte(Byte_ascii.Underline) ; ++pos ; break; + case Tid_percent: bfr.Add_byte(Byte_ascii.Percent) ; ++pos ; break; + case Tid_colon: bfr.Add_byte(Byte_ascii.Colon) ; pos += 3 ; break; + case Tid_amp: + ++pos; + if (pos == end) { + bfr.Add_byte(Byte_ascii.Amp); + loop = false; + continue; + } + b = src[pos]; + Object amp_obj = amp_trie.Match_bgn_w_byte(b, src, pos, end); + if (amp_obj == null) { + bfr.Add_byte(Byte_ascii.Amp); + bfr.Add_byte(b); + ++pos; + } + else { + Gfh_entity_itm itm = (Gfh_entity_itm)amp_obj; + byte itm_tid = itm.Tid(); + switch (itm_tid) { + case Gfh_entity_itm.Tid_name_std: + case Gfh_entity_itm.Tid_name_xowa: + bfr.Add(itm.U8_bry()); + pos += itm.Key_name_len() + 1; // 1 for trailing ";"; EX: for "  ", (a) pos is at "&", (b) "nbsp" is Key_name_len, (c) ";" needs + 1 + break; + case Gfh_entity_itm.Tid_num_dec: + case Gfh_entity_itm.Tid_num_hex: + Xop_amp_mgr_rslt rv = new Xop_amp_mgr_rslt(); + amp_mgr.Parse_ncr(rv, itm_tid == Gfh_entity_itm.Tid_num_hex, src, end, pos - 1, pos + itm.Xml_name_bry().length); + if (rv.Pass()) + bfr.Add_u8_int(rv.Val()); + else + bfr.Add_byte(Byte_ascii.Amp); + pos = rv.Pos(); + break; + } + } + break; + } + } + } + return dirty; + } +// static function escapeClass( $class ) { +// // Convert ugly stuff to underscores and kill underscores in ugly places +// return rtrim( preg_replace( +// array( '/(^[0-9\\-])|[\\x00-\\x20!"#$%&\'()*+,.\\/:;<=>?@[\\]^`{|}~]|\\xC2\\xA0/', '/_+/' ), +// '_', +// $class ), '_' ); +// } + public static byte[] Escape_cls(byte[] v) { + return v; + } + static final byte Tid_amp = 1, Tid_space = 2, Tid_colon = 3, Tid_percent = 4; +} diff --git a/400_xowa/src/gplx/xowa/parsers/utils/Xop_sanitizer_tst.java b/400_xowa/src/gplx/xowa/parsers/utils/Xop_sanitizer_tst.java index a27517de8..fcb941895 100644 --- a/400_xowa/src/gplx/xowa/parsers/utils/Xop_sanitizer_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/utils/Xop_sanitizer_tst.java @@ -13,3 +13,29 @@ 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.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.core.log_msgs.*; import gplx.xowa.parsers.amps.*; +public class Xop_sanitizer_tst { + Xop_sanitizer_fxt fxt = new Xop_sanitizer_fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Space() {fxt.tst_Escape_id("a b" , "a_b");} + @Test public void Colon() {fxt.tst_Escape_id("a%3Ab" , "a:b");} + @Test public void Percent() {fxt.tst_Escape_id("a%b" , "a%b");} + @Test public void Amp_eos() {fxt.tst_Escape_id("a&" , "a&");} + @Test public void Amp_unrecognized() {fxt.tst_Escape_id("a&bcd" , "a&bcd");} + @Test public void Amp_name() {fxt.tst_Escape_id("a<b" , "aguessVariant(src, vnt); + this.pos = 0; + this.src = src; this.src_len = src.length; + while (pos < src_len) { + int curly_bgn = Bry_find_.Find_fwd(src, Bry__curly_bgn, pos, src_len); + if (curly_bgn == Bry_find_.Not_found) { // No more markup, append final segment + Add_output(vnt_itm, convert_needed, src, pos, src_len); + return bfr.To_bry_and_clear(); + } + boolean inside_tag = Is_inside_tag(pos, curly_bgn); + if (inside_tag) { + Add_output(vnt_itm, convert_needed, src, pos, tag_bgn); // Markup found; append segment + Auto_convert(bfr, vnt_itm, src, tag_bgn, tag_end); + pos = tag_end; + } + else { + Add_output(vnt_itm, convert_needed, src, pos, curly_bgn); // Markup found; append segment + pos = curly_bgn; // Advance position + bfr.Add(Parse_recursive(tmp_frame_bfr, vnt_itm, 1)); // Do recursive conversion + } + } + return bfr.To_bry_and_clear(); + } + private boolean Is_inside_tag(int prev_pos, int curly_bgn) { + if ( curly_bgn == 0 // -{ starts at BOS; EX: "-{A}-" + || curly_bgn == prev_pos // -{ starts after last pair; EX: "-{A}--{B}-" + ) return false; + int cur = curly_bgn - 1; + tag_bgn = tag_end = -1; + boolean loop = true; + while (loop) { // scan bwd for < + byte b = src[cur]; + switch (b) { + case Byte_ascii.Angle_bgn: tag_bgn = cur; loop = false; break; + case Byte_ascii.Angle_end: return false; // ">" found; "-{}-" not inside tag + default: --cur; break; + } + if (cur == prev_pos - 1) break; + } + if (tag_bgn == -1) return false; // no "<" found; + loop = true; + cur = curly_bgn + 1; // TODO_OLD: resume at }- + while (loop) { // scan fwd for > + byte b = src[cur]; + switch (b) { + case Byte_ascii.Angle_bgn: return false; // "<" found; "-{}-" not inside tag + case Byte_ascii.Angle_end: tag_end = cur + 1; return true; + default: ++cur; break; + } + if (cur == src_len) break; + } + return false; // no ">" foud + } + private byte[] Parse_recursive(Bry_bfr frame_bfr, Xol_vnt_itm vnt_itm, int depth) { + pos += 2; // skip "-{" + boolean warning_done = false; + boolean frame_bfr_dirty = false; + int bgn_pos = pos; + while (pos < src_len) { + byte b = src[pos]; + Object o = trie.Match_bgn_w_byte(b, src,pos, src_len); + if (o == null) { // char; + ++pos; + continue; + } + switch (((Byte_obj_val)o).Val()) { + case Tid__curly_bgn: + frame_bfr.Add_mid(src, bgn_pos, pos); // add everything from bgn of frame to cur pos; EX: "a" in "-{a-{b}-c}-" + frame_bfr_dirty = true; + if (depth >= max_depth) { + pos += 2; // skip "-{" + frame_bfr.Add(Bry__curly_bgn); + if (!warning_done) { + frame_bfr.Add_str_a7("max depth"); + // wfMessage('language-converter-depth-warning')->numParams($this->mMaxDepth)->inContentLanguage()->text() + frame_bfr.Add_str_a7(""); + warning_done = true; + } + continue; + } + frame_bfr.Add(Parse_recursive(Bry_bfr_.New_w_size(16), vnt_itm, depth + 1)); // Recursively parse another rule + bgn_pos = pos; + break; + case Tid__curly_end: + if (frame_bfr_dirty) { // recursive; use frame_bfr + frame_bfr.Add_mid(src, bgn_pos, pos); // add everything from bgn of frame to cur pos; EX: "a" in "-{a-{b}-c}-" + byte[] frame_bry = frame_bfr.To_bry_and_clear(); + converter_rule.Parse(vnt_itm, frame_bry, 0, frame_bry.length); + } + else // not recursive + converter_rule.Parse(vnt_itm, src, bgn_pos, pos); + Apply_manual_conv(converter_rule); + pos += 2; + return converter_rule.Display(); + default: throw Err_.new_unhandled(-1); // never happens + } + } + Auto_convert(frame_bfr, vnt_itm, src, bgn_pos, src_len); // Unclosed rule + pos = src_len; + return Bry_.Add(Bry__curly_bgn, frame_bfr.To_bry_and_clear()); + } + private void Add_output(Xol_vnt_itm vnt_itm, boolean convert_needed, byte[] src, int bgn, int end) { + if (end - bgn == 0) return; + if (convert_needed) { + Auto_convert(bfr, vnt_itm, src, bgn, end); + } + else + bfr.Add_mid(src, bgn, end); + } + public byte[] Auto_convert(Xol_vnt_itm vnt_itm, byte[] src) { + Auto_convert(tmp_convert_bfr, vnt_itm, src, 0, src.length); + return tmp_convert_bfr.To_bry_and_clear(); + } + private void Auto_convert(Bry_bfr bfr, Xol_vnt_itm vnt_itm, byte[] src, int bgn, int end) { + html_convert_wkr.Init(bfr, vnt_itm); + doc_parser.Parse(html_convert_wkr, src, bgn, end); + } + private void Apply_manual_conv(Vnt_convert_rule rule) { + this.converted_title = rule.Title(); + byte action = rule.Action(); + Vnt_rule_undi_mgr cnv_tbl = rule.Cnv_tbl(); + int len = cnv_tbl.Len(); + for (int i = 0; i < len; ++i) { + Vnt_rule_undi_grp grp = cnv_tbl.Get_at(i); + byte[] grp_key = grp.Vnt(); + Xol_vnt_itm vnt_itm = vnt_regy.Get_by(grp_key); if (vnt_itm == null) continue; + int grp_len = grp.Len(); + Xol_convert_wkr wkr = convert_mgr.Converter_ary()[vnt_itm.Idx()]; + for (int j = 0; j < grp_len; ++j) { + Vnt_rule_undi_itm itm = grp.Get_at(j); + if (action == Byte_ascii.Plus) { + wkr.Add(itm.Src(), itm.Trg()); + } + else if (action == Byte_ascii.Dash) + wkr.Del(itm.Src()); + } + } + } + private static final byte Tid__curly_bgn = 1, Tid__curly_end = 2; + private static final byte[] Bry__curly_bgn = Bry_.new_a7("-{"), Bry__curly_end = Bry_.new_a7("}-"); + private static final Btrie_fast_mgr trie = Btrie_fast_mgr.cs() + .Add_bry_byte(Bry__curly_bgn, Tid__curly_bgn) + .Add_bry_byte(Bry__curly_end, Tid__curly_end); + private static final int max_depth = 32; +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_convert_lang__html__tst.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_convert_lang__html__tst.java index a27517de8..5a7ac8cf8 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_convert_lang__html__tst.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_convert_lang__html__tst.java @@ -13,3 +13,88 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.xowa.langs.vnts.*; import gplx.xowa.langs.vnts.converts.*; +public class Vnt_convert_lang__html__tst { // REF: https://www.mediawiki.org/wiki/Writing_systems/Syntax + private final Vnt_convert_lang_fxt fxt = new Vnt_convert_lang_fxt(); + private String rule; + @Before public void init() { + rule = "-{H|zh-cn:cn;zh-hk:hk;zh-tw:tw}-"; + fxt.Clear(); + } + @Test public void Node() { + fxt.Test_parse(rule + "hkhkhk", "cncncn"); + } + @Test public void Attribs() { + fxt.Test_parse(rule + "hk", "cn"); + } + @Test public void Attribs__title() { + fxt.Test_parse(rule + "hk", "cn"); + } + @Test public void Attribs__alt() { + fxt.Test_parse(rule + "hk", "cn"); + } + @Test public void Attribs__alt_w_embedded_vnt() { // PURPOSE: handle embedded variants inside attribute tags; PAGE:sr.n:Проглашени_победници_„Вики_воли_Земљу" DATE:2015-10-13 + fxt.Test_parse(rule + "hk-{hk}-hk", "cnhkcn"); + } + @Test public void Attribs__skip_url() { + fxt.Test_parse(rule + "hk", "cn"); + } + @Test public void Node__script() { + fxt.Test_parse(rule + "hkhk", "cncn"); + } + @Test public void Node__code() { + fxt.Test_parse(rule + "hkhkhk", "cnhkcn"); + } + @Test public void Node__pre() { + fxt.Test_parse(rule + "hk
    hk
    hk", "cn
    hk
    cn"); + } + @Test public void Node__pre__nested() { + fxt.Test_parse(rule + "hk
    hk
    hk", "cn
    hk
    cn"); + } + @Test public void Recursive__deep() { + fxt.Test_parse("a-{b-{c-{d}-e}-f}-g", "abcdefg"); + } + @Test public void Recursive__many() { + fxt.Test_parse("a-{b-{c}-d-{e}-f}-g", "abcdefg"); + } + @Test public void Recursive__unclosed() { + fxt.Test_parse("a-{b-{c", "a-{b-{c"); + } + @Test public void Recursive__unclosed_2() { + fxt.Test_parse("a-{b-{c}-", "a-{bc"); + } + @Test public void Recursive__failed() { // PURPOSE: handle out of bounds exception; PAGE:zh.w:重庆市 ;DATE:2015-10-01 + fxt.Test_parse("-{zh-cn:a-{b}-c; zh-tw:d-{e}-f; }-", "abc"); + } + @Test public void Unclosed() { + fxt.Test_parse("a-{bc", "a-{bc"); + } + @Test public void Entity__body() { + fxt.Test_parse("-{H|zh-cn:nbsp-cn;zh-hk:nbsp;}-" + " nbsp", " nbsp-cn"); + } + @Test public void Entity__atr() { + fxt.Test_parse("-{H|zh-cn:nbsp-cn;zh-hk:nbsp;}-" + "
    " , "
    "); + } + @Test public void Node__example() { + fxt.Test_parse(rule + String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|A
    " + , "|B
    " + , "-{zh-hans:C;zh-hant:D;}-" + , "|}") + , String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|A
    " + , "|B
    " + , "C" + , "|}" + )); + } + @Test public void Attribs__title__w_vnt() { + fxt.Init_cur("zh-tw"); + fxt.Test_parse("cn", "cn"); // cn not converted to hk + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_convert_lang__syntax__tst.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_convert_lang__syntax__tst.java index a27517de8..8a05fd733 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_convert_lang__syntax__tst.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_convert_lang__syntax__tst.java @@ -13,3 +13,69 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.xowa.langs.vnts.*; import gplx.xowa.langs.vnts.converts.*; +public class Vnt_convert_lang__syntax__tst { // REF: https://www.mediawiki.org/wiki/Writing_systems/Syntax + private final Vnt_convert_lang_fxt fxt = new Vnt_convert_lang_fxt(); + @Test public void Bidi() { + String text = "-{zh-hans:a;zh-hant:b}-"; + fxt.Test_parse_many(text, "a", "zh-hans", "zh-cn", "zh-sg", "zh"); + fxt.Test_parse_many(text, "b", "zh-hant", "zh-hk", "zh-tw"); + } + @Test public void Undi() { + String text = "-{H|cn_k=>zh-cn:cn_v}-cn_k"; + fxt.Test_parse_many(text, "cn_k", "zh", "zh-hans", "zh-hant", "zh-hk", "zh-my", "zh-mo", "zh-sg", "zh-tw"); + fxt.Test_parse_many(text, "cn_v", "zh-cn"); + } + @Test public void Raw() { + fxt.Test_parse_many("-{a}-", "a", "zh-hans", "zh-cn", "zh-sg", "zh", "zh-hant", "zh-hk", "zh-tw"); + fxt.Test_parse_many("-{R|a}-", "a", "zh-hans", "zh-cn", "zh-sg", "zh", "zh-hant", "zh-hk", "zh-tw"); + } + @Test public void Hide() { + String text = "-{H|zh-cn:cn;zh-hk:hk;zh-tw:tw}-cn hk tw"; + fxt.Test_parse_many(text, "cn cn cn", "zh-cn", "zh-sg"); + fxt.Test_parse_many(text, "hk hk hk", "zh-hk"); + fxt.Test_parse_many(text, "tw tw tw", "zh-tw"); + fxt.Test_parse_many(text, "cn hk tw", "zh", "zh-hans", "zh-hant"); + } + @Test public void Aout() { + String text = "-{A|zh-cn:cn;zh-hk:hk;zh-tw:tw}- cn hk tw"; + fxt.Test_parse_many(text, "cn cn cn cn", "zh-cn", "zh-sg"); + fxt.Test_parse_many(text, "hk hk hk hk", "zh-hk"); + fxt.Test_parse_many(text, "tw tw tw tw", "zh-tw"); + fxt.Test_parse_many(text, "cn cn hk tw", "zh", "zh-hans"); + fxt.Test_parse_many(text, "tw cn hk tw", "zh-hant"); + fxt.Test_parse_many("h-{}-k", "hk", "zh-cn"); // semi-disabled + } + @Test public void Del() { + String text = "-{H|zh-cn:cn;zh-hk:hk;zh-tw:tw}-cn hk tw-{-|zh-cn:cn;zh-hk:hk;zh-tw:tw}- cn hk tw"; + fxt.Test_parse_many(text, "cn cn cn cn hk tw", "zh-cn", "zh-sg"); + fxt.Test_parse_many(text, "hk hk hk cn hk tw", "zh-hk"); + fxt.Test_parse_many(text, "tw tw tw cn hk tw", "zh-tw"); + fxt.Test_parse_many(text, "cn hk tw cn hk tw", "zh", "zh-hans", "zh-hant"); + } + @Test public void Title() { + fxt.Test_parse_title("-{}-", null, "", "zh-cn"); + String text = "-{T|zh-cn:cn;zh-hk:hk;zh-tw:tw}-cn hk tw"; + fxt.Test_parse_title(text, "cn", "cn hk tw", "zh-cn"); + fxt.Test_parse_title(text, "cn", "cn hk tw", "zh-sg"); + fxt.Test_parse_title(text, "hk", "cn hk tw", "zh-hk"); + fxt.Test_parse_title(text, "tw", "cn hk tw", "zh-tw"); + fxt.Test_parse_title(text, "cn", "cn hk tw", "zh-hans"); + fxt.Test_parse_title(text, "tw", "cn hk tw", "zh-hant"); + fxt.Test_parse_title(text, null, "cn hk tw", "zh"); + } + @Test public void Descrip() { + String text = "-{D|zh-cn:cn;zh-hk:hk;zh-tw:tw}-"; + fxt.Test_parse_many(text, "ZH-CN:cn;ZH-HK:hk;ZH-TW:tw;", "zh", "zh-hans", "zh-hant", "zh-cn", "zh-hk", "zh-my", "zh-mo", "zh-sg", "zh-tw"); + } + @Test public void Mixture() { + String text = "-{H|zh-cn:cn;zh-hk:hk;zh-tw:tw}--{zh;zh-hans;zh-hant|cn hk tw}- -{zh;zh-cn;zh-hk;zh-tw|cn hk tw}-"; + fxt.Test_parse_many(text, "cn hk tw cn cn cn", "zh-cn", "zh-sg", "zh-hans"); + fxt.Test_parse_many(text, "cn hk tw hk hk hk", "zh-hk"); + fxt.Test_parse_many(text, "cn hk tw tw tw tw", "zh-tw", "zh-hant"); + fxt.Test_parse_many(text, "cn hk tw cn hk tw", "zh"); + } + @Test public void Descrip__undi() {fxt.Test_parse("-{D|cn_k=>zh-cn:cn_v;hk_k=>zh-hk:hk_v}-", "cn_k⇒ZH-CN:cn_v;hk_k⇒ZH-HK:hk_v;");} + @Test public void Descrip__mixd() {fxt.Test_parse("-{D|zh-tw:tw_v;cn_k=>zh-cn:cn_v;hk_k=>zh-hk:hk_v;zh-mo:mo_v}-", "ZH-TW:tw_v;ZH-MO:mo_v;cn_k⇒ZH-CN:cn_v;hk_k⇒ZH-HK:hk_v;");} +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_convert_lang_fxt.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_convert_lang_fxt.java index a27517de8..9d526e4c5 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_convert_lang_fxt.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_convert_lang_fxt.java @@ -13,3 +13,42 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.langs.vnts.*; import gplx.xowa.langs.vnts.converts.*; +class Vnt_convert_lang_fxt { + private final Vnt_convert_lang converter_lang; + private final Xol_convert_mgr convert_mgr = new Xol_convert_mgr(); + private final Xol_vnt_regy vnt_regy = Xol_vnt_regy_fxt.new_chinese(); + private Xol_vnt_itm vnt_itm; + public Vnt_convert_lang_fxt() { + converter_lang = new Vnt_convert_lang(convert_mgr, vnt_regy); + this.Clear(); + } + public void Clear() { + convert_mgr.Init(vnt_regy); + Init_cur("zh-cn"); + } + public Vnt_convert_lang_fxt Init_cur(String vnt) { + byte[] cur_vnt = Bry_.new_a7(vnt); + this.vnt_itm = vnt_regy.Get_by(cur_vnt); + return this; + } + public void Test_parse(String raw, String expd) { + Tfds.Eq_str(expd, String_.new_u8(converter_lang.Parse_page(vnt_itm, -1, Bry_.new_u8(raw)))); + } + public void Test_parse_many(String raw, String expd, String... vnts) { + int len = vnts.length; + for (int i = 0; i < len; ++i) { + String vnt_key = vnts[i]; + Init_cur(vnt_key); + Xol_vnt_itm vnt = vnt_regy.Get_by(Bry_.new_a7(vnt_key)); + Tfds.Eq_str(expd, String_.new_u8(converter_lang.Parse_page(vnt, -1, Bry_.new_u8(raw))), vnt_key); + } + } + public void Test_parse_title(String raw, String expd_title, String expd_text, String vnt_key) { + Init_cur(vnt_key); + Xol_vnt_itm vnt = vnt_regy.Get_by(Bry_.new_a7(vnt_key)); + Tfds.Eq_str(expd_text, String_.new_u8(converter_lang.Parse_page(vnt, -1, Bry_.new_u8(raw))), vnt_key); + Tfds.Eq_str(expd_title, converter_lang.Converted_title()); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_convert_rule.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_convert_rule.java index a27517de8..bc7c92122 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_convert_rule.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_convert_rule.java @@ -13,3 +13,180 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.core.primitives.*; +import gplx.xowa.langs.vnts.*; +class Vnt_convert_rule { // REF.MW: /languages/LanguageConverter.php|ConverterRule + private final Vnt_flag_parser flag_parser = new Vnt_flag_parser(); private final Vnt_flag_code_mgr flag_codes = new Vnt_flag_code_mgr(); private final Vnt_flag_lang_mgr flag_langs = new Vnt_flag_lang_mgr(); + private final Vnt_rule_parser rule_parser = new Vnt_rule_parser(); private final Vnt_rule_undi_mgr rule_undis = new Vnt_rule_undi_mgr(); private final Vnt_rule_bidi_mgr rule_bidis = new Vnt_rule_bidi_mgr(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + private final Ordered_hash cnv_marked_hash = Ordered_hash_.New_bry(); + private Vnt_convert_lang converter; + private Xol_vnt_regy vnt_regy; private byte[] vnt_key; + private Vnt_log_mgr log_mgr; + private byte[] rule_raw; + private boolean rule_parser_init = true; + public byte[] Display() {return display;} private byte[] display; + public byte[] Title() {return title;} private byte[] title; + public byte Action() {return action;} private byte action; + public Vnt_rule_undi_mgr Cnv_tbl() {return cnv_tbl;} private final Vnt_rule_undi_mgr cnv_tbl = new Vnt_rule_undi_mgr(); + public Vnt_convert_rule(Vnt_convert_lang converter, Xol_vnt_regy vnt_regy, Vnt_log_mgr log_mgr) { + this.converter = converter; this.log_mgr = log_mgr; + this.vnt_regy = vnt_regy; + flag_parser.Init(log_mgr); + rule_parser.Init(log_mgr, vnt_regy); + } + public void Parse(Xol_vnt_itm vnt_itm, byte[] src, int src_bgn, int src_end) { + if (rule_parser_init) { // WORKAROUND: initialized vnt_regy on first parse; initializing during constructor doesn't work b/c vnt_regy == 0 due to load order of gfs script; DATE:2016-11-09 + rule_parser_init = false; + rule_parser.Init(log_mgr, vnt_regy); + } + this.vnt_key = vnt_itm.Key(); + this.display = this.title = null; + this.action = Byte_ascii.Null; + int pipe_pos = Bry_find_.Find_fwd(src, Byte_ascii.Pipe, src_bgn, src_end); + flag_parser.Parse(flag_codes, flag_langs, vnt_regy, src, src_bgn, pipe_pos); + int rule_bgn = pipe_pos == -1 ? src_bgn : pipe_pos + 1; + this.rule_raw = Bry_.Mid(src, rule_bgn, src_end); + int flag_langs_count = flag_langs.Count(); + if (flag_langs_count > 0) { // vnts exist in flag; EX: -{zh-hans;zh-hant|text}- + if (flag_langs.Has(vnt_key)) + rule_raw = converter.Auto_convert(vnt_itm, rule_raw); // convert rule text to current language; EX:-{|convert}- + else { + byte[][] fallbacks = vnt_itm.Fallback_ary(); + int fallbacks_len = fallbacks.length; + for (int i = 0; i < fallbacks_len; ++i) { + byte[] fallback = fallbacks[i]; + if (flag_langs.Has(fallback)) { + Xol_vnt_itm fallback_itm = (Xol_vnt_itm)vnt_regy.Get_by(fallback); + rule_raw = converter.Auto_convert(fallback_itm, rule_raw); + break; + } + } + } + flag_codes.Limit(Vnt_flag_code_.Tid_raw); + } + rule_parser.Clear(rule_undis, rule_bidis, rule_raw); + if (!flag_codes.Get(Vnt_flag_code_.Tid_raw) && !flag_codes.Get(Vnt_flag_code_.Tid_name)) + rule_parser.Parse(src, rule_bgn, src_end); + if (log_mgr != null) log_mgr.Log_rule(src_bgn, src_end, Bry_.Mid(src, src_bgn, src_end), flag_codes, flag_langs, rule_undis, rule_bidis); + if (rule_undis.Has_none() && rule_bidis.Has_none()) { + if ( flag_codes.Get(Vnt_flag_code_.Tid_add) + || flag_codes.Get(Vnt_flag_code_.Tid_del) + ) { // fill all variants if text in -{A/H/-|text} without rules + for (int i = 0; i < flag_langs_count; ++i) { + Xol_vnt_itm itm = flag_langs.Get_at(i); + rule_bidis.Set(itm.Key(), rule_raw); + } + } + else if ( !flag_codes.Get(Vnt_flag_code_.Tid_name) + && !flag_codes.Get(Vnt_flag_code_.Tid_title) + ) { + flag_codes.Limit(Vnt_flag_code_.Tid_raw); + } + } + int flag_count = Vnt_flag_code_.Tid__max; + for (int flag = 0; flag < flag_count; ++flag) { + if (!flag_codes.Get(flag)) continue; + switch (flag) { + case Vnt_flag_code_.Tid_raw: display = rule_parser.Raw(); break; // if we don't do content convert, still strip the -{}- tags + case Vnt_flag_code_.Tid_name: // process N flag: output current variant name + byte[] vnt_key_trim = Bry_.Trim(rule_parser.Raw()); + Xol_vnt_itm vnt_itm_trim = vnt_regy.Get_by(vnt_key_trim); + display = vnt_itm_trim == null ? display = Bry_.Empty : vnt_itm_trim.Name(); + break; + case Vnt_flag_code_.Tid_descrip: display = Make_descrip(); break; // process D flag: output rules description + case Vnt_flag_code_.Tid_hide: display = Bry_.Empty; break; // process H,- flag or T only: output nothing + case Vnt_flag_code_.Tid_del: display = Bry_.Empty; action = Byte_ascii.Dash; break; + case Vnt_flag_code_.Tid_add: display = Bry_.Empty; action = Byte_ascii.Plus; break; + case Vnt_flag_code_.Tid_show: display = Make_converted(vnt_itm); break; + case Vnt_flag_code_.Tid_title: display = Bry_.Empty; title = Make_title(vnt_itm); break; + default: break; // ignore unknown flags (but see error case below) + } + } + if (display == null) + display = Bry_.Add(Bry__error_bgn, Bry__error_end); // wfMessage( 'converter-manual-rule-error' )->inContentLanguage()->escaped() + Make_conv_tbl(); + } + private void Make_conv_tbl() { + if (rule_undis.Has_none() && rule_bidis.Has_none()) return; // Special case optimisation + cnv_tbl.Clear(); cnv_marked_hash.Clear(); + int vnt_regy_len = vnt_regy.Len(); + for (int i = 0; i < vnt_regy_len; ++i) { + Xol_vnt_itm vnt = vnt_regy.Get_at(i); + byte[] vnt_key = vnt.Key(); + // bidi: fill in missing variants with fallbacks + byte[] bidi_bry = rule_bidis.Get_text_by_key_or_null(vnt_key); + if (bidi_bry == null) { + bidi_bry = rule_bidis.Get_text_by_ary_or_null(vnt.Fallback_ary()); + if (bidi_bry != null) rule_bidis.Set(vnt_key, bidi_bry); + } + if (bidi_bry != null) { + int marked_len = cnv_marked_hash.Count(); + for (int j = 0; j < marked_len; ++j) { + Xol_vnt_itm marked_itm = (Xol_vnt_itm)cnv_marked_hash.Get_at(j); + byte[] marked_key = marked_itm.Key(); + byte[] marked_bry = rule_bidis.Get_text_by_key_or_null(marked_key); + byte[] cur_bidi_bry = rule_bidis.Get_text_by_key_or_null(vnt_key); + if (vnt.Dir() == Xol_vnt_dir_.Tid__bi) + cnv_tbl.Set(vnt_key, marked_bry, cur_bidi_bry); + if (marked_itm.Dir() == Xol_vnt_dir_.Tid__bi) + cnv_tbl.Set(marked_key, cur_bidi_bry, marked_bry); + } + cnv_marked_hash.Add(vnt_key, vnt); + } + // undi: fill to convert tables + byte[] undi_bry = rule_undis.Get_text_by_key_or_null(vnt_key); + if (vnt.Dir() != Xol_vnt_dir_.Tid__none && undi_bry != null) { + Vnt_rule_undi_grp undi_grp = rule_undis.Get_by(vnt_key); + int undi_grp_len = undi_grp.Len(); + for (int j = 0; j < undi_grp_len; ++j) { + Vnt_rule_undi_itm undi_itm = undi_grp.Get_at(j); + cnv_tbl.Set(vnt_key, undi_itm.Src(), undi_itm.Trg()); + } + } + } + } + private byte[] Make_descrip() { + int len = rule_bidis.Len(); + for (int i = 0; i < len; ++i) { + Vnt_rule_bidi_itm bidi_itm = rule_bidis.Get_at(i); + Xol_vnt_itm vnt_itm = vnt_regy.Get_by(bidi_itm.Vnt()); + tmp_bfr.Add(vnt_itm.Name()).Add_byte_colon().Add(bidi_itm.Text()).Add_byte_semic(); + } + len = rule_undis.Len(); + for (int i = 0; i < len; ++i) { + Vnt_rule_undi_grp undi_grp = rule_undis.Get_at(i); + int sub_len = undi_grp.Len(); + for (int j = 0; j < sub_len; ++j) { + Vnt_rule_undi_itm undi_itm = (Vnt_rule_undi_itm)undi_grp.Get_at(j); + Xol_vnt_itm undi_vnt = vnt_regy.Get_by(undi_grp.Vnt()); + tmp_bfr.Add(undi_itm.Src()).Add(Bry__undi_spr).Add(undi_vnt.Name()).Add_byte_colon().Add(undi_itm.Trg()).Add_byte_semic(); + } + } + return tmp_bfr.To_bry_and_clear(); + } + private byte[] Make_title(Xol_vnt_itm vnt) { + if (vnt.Idx() == 0) { // for mainLanguageCode; EX: "zh" + byte[] rv = rule_bidis.Get_text_by_key_or_null(vnt.Key()); + return rv == null ? rule_undis.Get_text_by_key_or_null(vnt.Key()) : rv; + } + else + return Make_converted(vnt); + } + private byte[] Make_converted(Xol_vnt_itm vnt) { + if (rule_bidis.Len() == 0 && rule_undis.Len() == 0) return rule_raw; + byte[] rv = rule_bidis.Get_text_by_key_or_null(vnt.Key()); // display current variant in bidirectional array + if (rv == null) rv = rule_bidis.Get_text_by_ary_or_null(vnt.Fallback_ary()); // or display current variant in fallbacks + if (rv == null) rv = rule_undis.Get_text_by_key_or_null(vnt.Key()); // or display current variant in unidirectional array + if (rv == null && vnt.Dir() == Xol_vnt_dir_.Tid__none) { // or display first text under disable manual convert + rv = (rule_bidis.Len() > 0) ? rule_bidis.Get_text_at(0) : rule_undis.Get_text_at(0); + } + return rv; + } + private final static byte[] + Bry__error_bgn = Bry_.new_a7("vnt error") + , Bry__error_end = Bry_.new_a7("") + , Bry__undi_spr = Bry_.new_u8("⇒") + ; +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_code_.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_code_.java index a27517de8..ccf633346 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_code_.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_code_.java @@ -13,3 +13,36 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +class Vnt_flag_code_ { + public static final int + Tid_add = 0 // +: EX: -{+|zh-hans:A;zh-hant:B}- -> "A" + , Tid_del = 1 // -: remove; EX: -{-|zh-hans:A;zh-hant:B}- -> "" + , Tid_aout = 2 // A: Add and output; EX: -{A|zh-hans:A;zh-hant:B}- -> "A" + , Tid_hide = 3 // H: Hide macro; EX: -{H|zh-hans:A;zh-hant:B}- -> "" + , Tid_raw = 4 // R: Raw: no convert; EX: -{R|zh-hans:A;zh-hant:B}- -> "zh-hans:A;zh-hant:B" + , Tid_show = 5 // S: Show EX: -{S|zh-hans:A;zh-hant:B}- -> "A" + , Tid_descrip = 6 // D: Describe; EX: -{D|zh-hans:A;zh-hant:B}- -> "简体:A;繁體:B;" (简体=Simplified;繁體=Traditional) + , Tid_name = 7 // N: variant Name EX: -{N|zh-hans:A;zh-hant:B}- -> "" + , Tid_title = 8 // T: page Title; EX: -{T|zh-hans:A;zh-hant:B}- -> "" + , Tid_err = 9 // E: Error EX: -{E|zh-hans:A;zh-hant:B}- -> "A" + , Tid__max = 10 + ; + private static final String[] Tid__names = new String[] + { "+", "-", "A", "H", "R" + , "S", "D", "N", "T", "E" + }; + public static String To_str(int tid) {return Tid__names[tid];} + public static final Hash_adp_bry Regy = Hash_adp_bry.ci_a7() // NOTE: match either lc or uc; EX: -{D}- or -{d}-; + .Add_byte_int(Byte_ascii.Plus , Tid_add) + .Add_byte_int(Byte_ascii.Dash , Tid_del) + .Add_byte_int(Byte_ascii.Ltr_A , Tid_aout) + .Add_byte_int(Byte_ascii.Ltr_H , Tid_hide) + .Add_byte_int(Byte_ascii.Ltr_R , Tid_raw) + .Add_byte_int(Byte_ascii.Ltr_S , Tid_show) + .Add_byte_int(Byte_ascii.Ltr_D , Tid_descrip) + .Add_byte_int(Byte_ascii.Ltr_N , Tid_name) + .Add_byte_int(Byte_ascii.Ltr_T , Tid_title) + .Add_byte_int(Byte_ascii.Ltr_E , Tid_err) + ; +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_code_mgr.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_code_mgr.java index a27517de8..06f05cee1 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_code_mgr.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_code_mgr.java @@ -13,3 +13,42 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +class Vnt_flag_code_mgr { + private final boolean[] ary = new boolean[Ary_len]; private final static int Ary_len = Vnt_flag_code_.Tid__max; + public int Count() {return count;} private int count = 0; + public boolean Get(int tid) {return ary[tid];} + public void Clear() { + count = 0; + for (int i = 0; i < Ary_len; ++i) + ary[i] = false; + } + public void Add(int tid) { + this.Set_y(tid); + ++count; + } + public void Set_y(int tid) {ary[tid] = Bool_.Y;} + public void Set_y_many(int... vals) { + int len = vals.length; + for (int i = 0; i < len; ++i) + ary[vals[i]] = Bool_.Y; + } + public void Set_n(int tid) {ary[tid] = Bool_.N;} + public void Limit(int tid) { + for (int i = 0; i < Ary_len; ++i) + ary[i] = i == tid; + } + public boolean Limit_if_exists(int tid) { + boolean exists = ary[tid]; if (!exists) return false; + this.Limit(tid); + return true; + } + public void To_bfr__dbg(Bry_bfr bfr) { + for (int i = 0; i < Ary_len; ++i) { + if (ary[i]) { + if (bfr.Len_gt_0()) bfr.Add_byte_semic(); + bfr.Add_str_a7(Vnt_flag_code_.To_str(i)); + } + } + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_lang_mgr.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_lang_mgr.java index a27517de8..5d0dfab4b 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_lang_mgr.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_lang_mgr.java @@ -13,3 +13,21 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.langs.vnts.*; +class Vnt_flag_lang_mgr { + private final Ordered_hash regy = Ordered_hash_.New_bry(); + public int Count() {return regy.Count();} + public boolean Has(byte[] vnt) {return regy.Has(vnt);} + public void Clear() {regy.Clear();} + public void Add(Xol_vnt_itm itm) {regy.Add_if_dupe_use_1st(itm.Key(), itm);} + public Xol_vnt_itm Get_at(int i) {return (Xol_vnt_itm)regy.Get_at(i);} + public void To_bfr__dbg(Bry_bfr bfr) { + int len = regy.Count(); + for (int i = 0; i < len; ++i) { + Xol_vnt_itm itm = (Xol_vnt_itm)regy.Get_at(i); + if (bfr.Len_gt_0()) bfr.Add_byte_semic(); + bfr.Add(itm.Key()); + } + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_parser.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_parser.java index a27517de8..89e46b8b0 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_parser.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_parser.java @@ -13,3 +13,54 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.langs.vnts.*; +class Vnt_flag_parser implements gplx.core.brys.Bry_split_wkr { + private final Hash_adp_bry codes_regy = Vnt_flag_code_.Regy; + private Vnt_flag_code_mgr codes; private Vnt_flag_lang_mgr langs; + private Xol_vnt_regy vnt_regy; + private Vnt_log_mgr log_mgr; + public void Init(Vnt_log_mgr log_mgr) { + this.log_mgr = log_mgr; + } + public void Parse(Vnt_flag_code_mgr codes, Vnt_flag_lang_mgr langs, Xol_vnt_regy vnt_regy, byte[] src, int src_bgn, int src_end) { + this.codes = codes; this.langs = langs; this.vnt_regy = vnt_regy; + codes.Clear(); langs.Clear(); + if (src_end != Bry_find_.Not_found) // "|" found; EX: -{A|}- + Bry_split_.Split(src, src_bgn, src_end, Byte_ascii.Semic, true, this); + int codes_count = codes.Count(), langs_count = langs.Count(); + if (codes_count == 0) codes.Set_y(Vnt_flag_code_.Tid_show); + else if (codes.Limit_if_exists(Vnt_flag_code_.Tid_raw)) {} + else if (codes.Limit_if_exists(Vnt_flag_code_.Tid_name)) {} + else if (codes.Limit_if_exists(Vnt_flag_code_.Tid_del)) {} + else if (codes_count == 1 && codes.Get(Vnt_flag_code_.Tid_title)) codes.Set_y(Vnt_flag_code_.Tid_hide); + else if (codes.Get(Vnt_flag_code_.Tid_hide)) { + boolean exists_d = codes.Get(Vnt_flag_code_.Tid_descrip); + boolean exists_t = codes.Get(Vnt_flag_code_.Tid_title); + codes.Clear(); + codes.Set_y_many(Vnt_flag_code_.Tid_add, Vnt_flag_code_.Tid_hide); + if (exists_d) codes.Set_y(Vnt_flag_code_.Tid_descrip); + if (exists_t) codes.Set_y(Vnt_flag_code_.Tid_title); + } + else { + if (codes.Get(Vnt_flag_code_.Tid_aout)) + codes.Set_y_many(Vnt_flag_code_.Tid_add, Vnt_flag_code_.Tid_show); + if (codes.Get(Vnt_flag_code_.Tid_descrip)) + codes.Set_n(Vnt_flag_code_.Tid_show); + if (langs_count > 0) + codes.Clear(); + } + } + public int Split(byte[] src, int itm_bgn, int itm_end) { + int flag_tid = codes_regy.Get_as_int_or(src, itm_bgn, itm_end, -1); + if (flag_tid == -1) { // try to find flags like "zh-hans", "zh-hant"; allow syntaxes like "-{zh-hans;zh-hant|XXXX}-" + Xol_vnt_itm vnt_itm = vnt_regy.Get_by(src, itm_bgn, itm_end); + if (vnt_itm == null) return Bry_split_.Rv__ok; // unknown flag; ignore + if (log_mgr != null) log_mgr.Log_lang(vnt_itm, Vnt_log_mgr.Scope__lang); + langs.Add(vnt_itm); + return Bry_split_.Rv__ok; + } + codes.Add(flag_tid); + return Bry_split_.Rv__ok; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_parser_tst.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_parser_tst.java index a27517de8..0332b3ed2 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_parser_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_flag_parser_tst.java @@ -13,3 +13,42 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.xowa.langs.vnts.*; +public class Vnt_flag_parser_tst { + private final Vnt_flag_parser_fxt fxt = new Vnt_flag_parser_fxt(); + @Test public void Basic() {fxt.Test_parse("D" , "D");} + @Test public void Multiple() {fxt.Test_parse("+;S;E" , "+;S;E");} + @Test public void Ws() {fxt.Test_parse(" + ; S ; E " , "+;S;E");} + @Test public void None() {fxt.Test_parse("" , "S");} + @Test public void Wrong() {fxt.Test_parse("XYZ" , "S");} + @Test public void Raw__limit() {fxt.Test_parse("R;S" , "R");} + @Test public void Name__limit() {fxt.Test_parse("N;S" , "N");} + @Test public void Del_limit() {fxt.Test_parse("-;S" , "-");} + @Test public void Title__also_macro_y() {fxt.Test_parse("T" , "H;T");} + @Test public void Title__also_macro_n() {fxt.Test_parse("T;S" , "S;T");} + @Test public void Hide__remove_all() {fxt.Test_parse("H;S" , "+;H");} + @Test public void Hide__keep_descrip() {fxt.Test_parse("H;S;D" , "+;H;D");} + @Test public void Hide__keep_title() {fxt.Test_parse("H;S;T" , "+;H;T");} + @Test public void Aout__also_show_all() {fxt.Test_parse("A" , "+;A;S");} + @Test public void Descrip__remove_show() {fxt.Test_parse("D;S" , "D");} + @Test public void Aout_w_descrip() {fxt.Test_parse("A;D;S" , "+;A;D");} + @Test public void Lang__one() {fxt.Test_parse("zh-hans" , "S;zh-hans");} + @Test public void Lang__many() {fxt.Test_parse("zh-cn;zh-hk" , "S;zh-cn;zh-hk");} + @Test public void Lang__many__ws() {fxt.Test_parse(" zh-cn ; zh-hk " , "S;zh-cn;zh-hk");} + @Test public void Lang__many__dupe() {fxt.Test_parse("zh-cn;zh-cn" , "S;zh-cn");} + @Test public void Lang__zap__codes() {fxt.Test_parse("+;S;zh-hans;" , "zh-hans");} +} +class Vnt_flag_parser_fxt { + private final Vnt_flag_parser parser = new Vnt_flag_parser(); + private final Vnt_flag_code_mgr codes = new Vnt_flag_code_mgr(); private final Vnt_flag_lang_mgr langs = new Vnt_flag_lang_mgr(); + private final Xol_vnt_regy vnt_regy = Xol_vnt_regy_fxt.new_chinese(); + private final Bry_bfr bfr = Bry_bfr_.New(); + public void Test_parse(String raw, String expd) { + byte[] src = Bry_.new_u8(raw); + parser.Parse(codes, langs, vnt_regy, src, 0, src.length); + codes.To_bfr__dbg(bfr); + langs.To_bfr__dbg(bfr); + Tfds.Eq_str(expd, bfr.To_str_and_clear()); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_html_doc_wkr.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_html_doc_wkr.java index a27517de8..de1f6ad4e 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_html_doc_wkr.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_html_doc_wkr.java @@ -13,3 +13,68 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.primitives.*; import gplx.core.btries.*; +import gplx.xowa.parsers.htmls.*; import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.amps.*; +import gplx.xowa.langs.vnts.*; import gplx.xowa.langs.vnts.converts.*; +import gplx.xowa.htmls.*; +class Vnt_html_doc_wkr implements Mwh_doc_wkr { + private final Hash_adp_bry atr_hash = Hash_adp_bry.ci_a7(); + private final Xol_convert_mgr convert_mgr; private final Xol_vnt_regy vnt_regy; + private Vnt_convert_lang atr_converter; + private Xol_vnt_itm vnt_itm; private int convert_vnt_idx; + private Bry_bfr bfr; + public Vnt_html_doc_wkr(Xol_convert_mgr convert_mgr, Xol_vnt_regy vnt_regy) { + this.convert_mgr = convert_mgr; this.vnt_regy = vnt_regy; + atr_hash.Add_many_str("title", "alt"); + } + public Hash_adp_bry Nde_regy() {return nde_regy;} private final Hash_adp_bry nde_regy = Mwh_doc_wkr_.Nde_regy__mw(); + public void Init(Bry_bfr bfr, Xol_vnt_itm vnt_itm) {this.bfr = bfr; this.vnt_itm = vnt_itm; this.convert_vnt_idx = vnt_itm.Idx();} + public void On_atr_each (Mwh_atr_parser mgr, byte[] src, int nde_tid, boolean valid, boolean repeated, boolean key_exists, byte[] key_bry, byte[] val_bry_manual, int[] itm_ary, int itm_idx) { + boolean literal = true; + if (atr_hash.Get_by_mid(key_bry, 0, key_bry.length) != null) { // title, alt + int val_bgn = itm_ary[itm_idx + Mwh_atr_mgr.Idx_val_bgn]; + int val_end = itm_ary[itm_idx + Mwh_atr_mgr.Idx_val_end]; + if (Bry_find_.Find_fwd(src, Bry__url_frag, val_bgn, val_end) == Bry_find_.Not_found) { // do not convert if urls are present + literal = false; + byte[] val_bry = val_bry_manual == null ? Bry_.Mid(src, val_bgn, val_end) : val_bry_manual; + if (atr_converter == null) atr_converter = new Vnt_convert_lang(convert_mgr, vnt_regy);// NOTE: late instantiation, or else StackOverflow error + val_bry = atr_converter.Parse_bry(vnt_itm, val_bry); + bfr.Add_byte_space(); + bfr.Add(key_bry); + bfr.Add_byte(Byte_ascii.Eq); + byte quote_byte = Mwh_atr_itm_.Calc_qte_byte(itm_ary, itm_idx); + bfr.Add_byte(quote_byte); + bfr.Add(val_bry); + bfr.Add_byte(quote_byte); + } + } + if (literal) { + int atr_bgn = itm_ary[itm_idx + Mwh_atr_mgr.Idx_atr_bgn]; + int atr_end = itm_ary[itm_idx + Mwh_atr_mgr.Idx_atr_end]; + bfr.Add_mid(src, atr_bgn, atr_end); + } + } + public void On_txt_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end) { + switch (nde_tid) { + case Xop_xnde_tag_.Tid__code: + case Xop_xnde_tag_.Tid__script: + case Xop_xnde_tag_.Tid__pre: + bfr.Add_mid(src, itm_bgn, itm_end); + break; + default: + bfr.Add(convert_mgr.Convert_text(convert_vnt_idx, src, itm_bgn, itm_end)); + break; + } + } + public void On_nde_head_bgn(Mwh_doc_parser mgr, byte[] src, int nde_tid, int key_bgn, int key_end) { + bfr.Add_byte(Byte_ascii.Angle_bgn).Add_mid(src, key_bgn, key_end); // EX: "" or ">" + } + public void On_nde_tail_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end) {bfr.Add_mid(src, itm_bgn, itm_end);} + public void On_comment_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end) {bfr.Add_mid(src, itm_bgn, itm_end);} + public void On_entity_end (Mwh_doc_parser mgr, byte[] src, int nde_tid, int itm_bgn, int itm_end) {bfr.Add_mid(src, itm_bgn, itm_end);} + private static final byte[] Bry__url_frag = Bry_.new_a7("://"); // REF.MW: if ( !strpos( $attr, '://' ) ) { +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_log_mgr.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_log_mgr.java index a27517de8..3ae5d1b76 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_log_mgr.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_log_mgr.java @@ -13,3 +13,44 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.dbs.*; +import gplx.xowa.langs.vnts.*; +class Vnt_log_mgr { + private int uid; + private int page_id, rule_idx; + private Xol_vnt_regy vnt_regy; + private int[] vnt_ary = new int[10]; + private Vnt_log_tbl tbl; + public void Init_by_db(Db_conn conn, Xol_vnt_regy vnt_regy) { + this.vnt_regy = vnt_regy; + this.tbl = new Vnt_log_tbl(conn); + tbl.Create_tbl(); + this.uid = 0; + this.page_id = 0; + } + public void Init_by_page(int page_id) { + this.page_id = page_id; + this.rule_idx = -1; + for (int i = 0; i < 10; ++i) + vnt_ary[i] = 0; + } + public void Log_lang(byte[] vnt, int scope) {Log_lang(vnt_regy.Get_by(vnt), scope);} + public void Log_lang(Xol_vnt_itm itm, int scope) { + int idx = itm.Idx(); + int val = vnt_ary[idx]; + vnt_ary[idx] = val == 0 ? scope : val | scope; + } + public void Log_rule(int src_bgn, int src_end, byte[] src_txt, Vnt_flag_code_mgr flag_codes, Vnt_flag_lang_mgr flag_langs, Vnt_rule_undi_mgr rule_undis, Vnt_rule_bidi_mgr rule_bidis) { + tbl.Insert(uid, page_id, ++rule_idx + , flag_codes.Count(), flag_langs.Count(), rule_undis.Len(), rule_bidis.Len() + , flag_codes.Get(Vnt_flag_code_.Tid_add), flag_codes.Get(Vnt_flag_code_.Tid_del), flag_codes.Get(Vnt_flag_code_.Tid_aout), flag_codes.Get(Vnt_flag_code_.Tid_hide), flag_codes.Get(Vnt_flag_code_.Tid_raw), flag_codes.Get(Vnt_flag_code_.Tid_show), flag_codes.Get(Vnt_flag_code_.Tid_descrip), flag_codes.Get(Vnt_flag_code_.Tid_name), flag_codes.Get(Vnt_flag_code_.Tid_title), flag_codes.Get(Vnt_flag_code_.Tid_err) + , vnt_ary[0], vnt_ary[1], vnt_ary[2], vnt_ary[3], vnt_ary[4], vnt_ary[5], vnt_ary[6], vnt_ary[7], vnt_ary[8], vnt_ary[9] + , src_bgn, src_end, src_txt + ); + } + public void Rls() { + tbl.Rls(); + } + public static final int Scope__lang = 1, Scope__undi = 2, Scope__bidi = 4; +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_log_tbl.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_log_tbl.java index a27517de8..90c9baaaa 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_log_tbl.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_log_tbl.java @@ -13,3 +13,92 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.dbs.*; +public class Vnt_log_tbl implements Rls_able { + private final String tbl_name = "log_vnt"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_uid, fld_page_id, fld_rule_idx + , fld_flag_count, fld_lang_count, fld_undi_count, fld_bidi_count + , fld_flag_add, fld_flag_del, fld_flag_aout, fld_flag_hide, fld_flag_raw, fld_flag_show, fld_flag_descrip, fld_flag_name, fld_flag_title, fld_flag_err + , fld_vnt_0, fld_vnt_1, fld_vnt_2, fld_vnt_3, fld_vnt_4, fld_vnt_5, fld_vnt_6, fld_vnt_7, fld_vnt_8, fld_vnt_9 + , fld_src_bgn, fld_src_end, fld_src_txt; + private Db_stmt stmt_insert; + public Vnt_log_tbl(Db_conn conn) { + this.conn = conn; + this.fld_uid = flds.Add_int("uid"); + this.fld_page_id = flds.Add_int("page_id"); + this.fld_rule_idx = flds.Add_int("rule_idx"); + this.fld_flag_count = flds.Add_int("flag_count"); + this.fld_lang_count = flds.Add_int("lang_count"); + this.fld_undi_count = flds.Add_int("undi_count"); + this.fld_bidi_count = flds.Add_int("bidi_count"); + this.fld_flag_add = flds.Add_int("flag_add"); + this.fld_flag_del = flds.Add_int("flag_del"); + this.fld_flag_aout = flds.Add_int("flag_aout"); + this.fld_flag_hide = flds.Add_int("flag_hide"); + this.fld_flag_raw = flds.Add_int("flag_raw"); + this.fld_flag_show = flds.Add_int("flag_show"); + this.fld_flag_descrip = flds.Add_int("flag_descrip"); + this.fld_flag_name = flds.Add_int("flag_name"); + this.fld_flag_title = flds.Add_int("flag_title"); + this.fld_flag_err = flds.Add_int("flag_err"); + this.fld_vnt_0 = flds.Add_int("vnt_0"); + this.fld_vnt_1 = flds.Add_int("vnt_1"); + this.fld_vnt_2 = flds.Add_int("vnt_2"); + this.fld_vnt_3 = flds.Add_int("vnt_3"); + this.fld_vnt_4 = flds.Add_int("vnt_4"); + this.fld_vnt_5 = flds.Add_int("vnt_5"); + this.fld_vnt_6 = flds.Add_int("vnt_6"); + this.fld_vnt_7 = flds.Add_int("vnt_7"); + this.fld_vnt_8 = flds.Add_int("vnt_8"); + this.fld_vnt_9 = flds.Add_int("vnt_9"); + this.fld_src_bgn = flds.Add_int("src_bgn"); + this.fld_src_end = flds.Add_int("src_end"); + this.fld_src_txt = flds.Add_text("src_txt"); + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + } + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Insert(int uid, int page_id, int rule_idx, int flag_count, int lang_count, int undi_count, int bidi_count + , boolean flag_add, boolean flag_del, boolean flag_aout, boolean flag_hide, boolean flag_raw, boolean flag_show, boolean flag_descrip, boolean flag_name, boolean flag_title, boolean flag_err + , int vnt_0, int vnt_1, int vnt_2, int vnt_3, int vnt_4, int vnt_5, int vnt_6, int vnt_7, int vnt_8, int vnt_9 + , int src_bgn, int src_end, byte[] src_txt + ) { + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Clear() + .Val_int(fld_uid, uid) + .Val_int(fld_page_id, page_id) + .Val_int(fld_rule_idx, rule_idx) + .Val_int(fld_flag_count, flag_count) + .Val_int(fld_lang_count, lang_count) + .Val_int(fld_undi_count, undi_count) + .Val_int(fld_bidi_count, bidi_count) + .Val_int_by_bool(fld_flag_add, flag_add) + .Val_int_by_bool(fld_flag_del, flag_del) + .Val_int_by_bool(fld_flag_aout, flag_aout) + .Val_int_by_bool(fld_flag_hide, flag_hide) + .Val_int_by_bool(fld_flag_raw, flag_raw) + .Val_int_by_bool(fld_flag_show, flag_show) + .Val_int_by_bool(fld_flag_descrip, flag_descrip) + .Val_int_by_bool(fld_flag_name, flag_name) + .Val_int_by_bool(fld_flag_title, flag_title) + .Val_int_by_bool(fld_flag_err, flag_err) + .Val_int(fld_vnt_0, vnt_0) + .Val_int(fld_vnt_1, vnt_1) + .Val_int(fld_vnt_2, vnt_2) + .Val_int(fld_vnt_3, vnt_3) + .Val_int(fld_vnt_4, vnt_4) + .Val_int(fld_vnt_5, vnt_5) + .Val_int(fld_vnt_6, vnt_6) + .Val_int(fld_vnt_7, vnt_7) + .Val_int(fld_vnt_8, vnt_8) + .Val_int(fld_vnt_9, vnt_9) + .Val_int(fld_src_bgn, src_bgn) + .Val_int(fld_src_end, src_end) + .Val_bry_as_str(fld_src_txt, src_txt) + .Exec_insert(); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_bidi_mgr.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_bidi_mgr.java index a27517de8..93b08fc50 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_bidi_mgr.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_bidi_mgr.java @@ -13,3 +13,54 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +class Vnt_rule_bidi_mgr { + private final Ordered_hash hash = Ordered_hash_.New_bry(); + public int Len() {return hash.Count();} + public boolean Has_none() {return hash.Count() == 0;} + public void Clear() {hash.Clear();} + public Vnt_rule_bidi_itm Get_at(int i) {return (Vnt_rule_bidi_itm)hash.Get_at(i);} + public Vnt_rule_bidi_itm Get_by(byte[] k) {return (Vnt_rule_bidi_itm)hash.Get_by(k);} + public byte[] Get_text_by_ary_or_null(byte[]... ary) { + int len = ary.length; + byte[] rv = null; + for (int i = 0; i < len; ++i) { + byte[] itm = ary[i]; + Vnt_rule_bidi_itm bidi_itm = (Vnt_rule_bidi_itm)hash.Get_by(itm); if (bidi_itm == null) continue; + rv = Get_text_by_key_or_null(bidi_itm.Vnt()); + if (rv != null) return rv; + } + return rv; + } + public byte[] Get_text_by_key_or_null(byte[] vnt) { + Vnt_rule_bidi_itm rv = (Vnt_rule_bidi_itm)hash.Get_by(vnt); + return rv == null ? null : rv.Text(); + } + public byte[] Get_text_at(int i) { + Vnt_rule_bidi_itm itm = (Vnt_rule_bidi_itm)hash.Get_at(i); + return itm == null ? null : itm.Text(); + } + public void Set(byte[] vnt, byte[] text) { + Vnt_rule_bidi_itm itm = (Vnt_rule_bidi_itm)hash.Get_by(vnt); + if (itm == null) { + itm = new Vnt_rule_bidi_itm(vnt, text); + hash.Add(vnt, itm); + } + else + itm.Text_(text); + } + public void To_bry__dbg(Bry_bfr bfr) { + int len = hash.Count(); + for (int i = 0; i < len; ++i) { + if (i != 0) bfr.Add_byte_nl(); + Vnt_rule_bidi_itm itm = (Vnt_rule_bidi_itm)hash.Get_at(i); + bfr.Add(itm.Vnt()).Add_byte_colon().Add(itm.Text()); + } + } +} +class Vnt_rule_bidi_itm { + public Vnt_rule_bidi_itm(byte[] vnt, byte[] text) {this.vnt = vnt; this.text = text;} + public byte[] Vnt() {return vnt;} private final byte[] vnt; + public byte[] Text() {return text;} private byte[] text; + public void Text_(byte[] v) {this.text = v;} +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_parser.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_parser.java index a27517de8..e33573419 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_parser.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_parser.java @@ -13,3 +13,84 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; +import gplx.xowa.langs.vnts.*; +class Vnt_rule_parser implements gplx.core.brys.Bry_split_wkr { + private final Btrie_slim_mgr vnt_trie = Btrie_slim_mgr.ci_a7(); + private final Btrie_rv trv = new Btrie_rv(); + private Vnt_rule_undi_mgr undis; private Vnt_rule_bidi_mgr bidis; + private int src_end; private byte[] rule_raw; + public byte[] Raw() {return rule_raw;} + private Vnt_log_mgr log_mgr; + public void Init(Vnt_log_mgr log_mgr, Xol_vnt_regy vnt_regy) { + this.log_mgr = log_mgr; + this.vnt_trie.Clear(); + int len = vnt_regy.Len(); + for (int i = 0; i < len; ++i) { + Xol_vnt_itm itm = (Xol_vnt_itm)vnt_regy.Get_at(i); + vnt_trie.Add_obj(itm.Key(), itm); + } + } + public void Clear(Vnt_rule_undi_mgr undis, Vnt_rule_bidi_mgr bidis, byte[] rule_raw) { + this.undis = undis; this.bidis = bidis; + undis.Clear(); bidis.Clear(); + this.rule_raw = rule_raw; + } + public void Parse(byte[] src, int src_bgn, int src_end) { + this.src_end = src_end; + Bry_split_.Split(src, src_bgn, src_end, Byte_ascii.Semic, false, this); // trim=false for "&#entity;" check below + } + public int Split(byte[] src, int itm_bgn, int itm_end) { // macro=>zh-hans:text; + int html_entity_pos = Bry_find_.Find_bwd_while_alphanum(src, itm_end); + byte html_entity_byte = src[html_entity_pos]; + if (html_entity_byte == Byte_ascii.Hash) html_entity_byte = src[html_entity_pos - 2]; // skip #; EX: { + if (html_entity_byte == Byte_ascii.Amp) return Bry_split_.Rv__extend; // reject "&#entity;"; EX: " zh-hans;" + if (itm_end != src_end) { + int nxt_lang_bgn = Bry_find_.Find_fwd(src, Bry__bidi_dlm, itm_end + 1, src_end); // look for next "=>" + if (nxt_lang_bgn == Bry_find_.Not_found) + nxt_lang_bgn = Bry_find_.Find_fwd_while_ws(src, itm_end + 1, src_end); // skip any ws after end ";"; EX: "a:1; b:2"; NOTE: +1 to skip semic; + else + nxt_lang_bgn += 2; + int nxt_lang_end = Bry_find_.Find_fwd(src, Byte_ascii.Colon, nxt_lang_bgn, src_end); // get colon; + if (nxt_lang_end != Bry_find_.Not_found) { + nxt_lang_end = Bry_find_.Find_bwd__skip_ws(src, nxt_lang_end, src_end); // trim + if (vnt_trie.Match_bgn(src, nxt_lang_bgn, nxt_lang_end) == null) return Bry_split_.Rv__extend; // reject ";not_variant"; EX: ";border" in "zh-hans:;zh-hant:" + } + } + int undi_bgn = Bry_find_.Find_fwd_while_ws(src, itm_bgn, itm_end); // skip any ws after bgn ";"; EX: " a=>b:c;" + int undi_end = Bry_find_.Find_fwd(src, Bry__bidi_dlm, undi_bgn, itm_end); // look for "=>" + int lang_bgn = undi_bgn; // default lang_bgn to undi_bgn; assumes no bidi found + if (undi_end != Bry_find_.Not_found) { // "=>" found; bidi exists + lang_bgn = Bry_find_.Find_fwd_while_ws(src, undi_end + 2, itm_end); // set lang_bgn after => and gobble up ws + undi_end = Bry_find_.Find_bwd__skip_ws(src, undi_end, undi_bgn); // trim ws from end of bd; + } + Object vnt_obj = vnt_trie.Match_at(trv, src, lang_bgn, itm_end); + if (vnt_obj == null) + return (itm_bgn == 0) ? Bry_split_.Rv__cancel : Bry_split_.Rv__extend; // if 1st item; cancel rest; otherwise, extend + int lang_end = trv.Pos(); + int text_bgn = Bry_find_.Find_fwd_while_ws(src, lang_end, itm_end); if (src[text_bgn] != Byte_ascii.Colon) return Bry_split_.Rv__extend; + ++text_bgn; + Xol_vnt_itm vnt_itm = (Xol_vnt_itm)vnt_obj; + byte[] vnt_key = vnt_itm.Key(); + byte[] text_bry = Bry_.Mid_w_trim(src, text_bgn, itm_end); + if (undi_end == Bry_find_.Not_found) { + if (log_mgr != null) log_mgr.Log_lang(vnt_itm, Vnt_log_mgr.Scope__bidi); + bidis.Set(vnt_key, text_bry); + } + else { + byte[] undi_bry = Bry_.Mid(src, undi_bgn, undi_end); + if (itm_end - text_bgn > 0) { + if (log_mgr != null) log_mgr.Log_lang(vnt_itm, Vnt_log_mgr.Scope__undi); + undis.Set(vnt_key, undi_bry, text_bry); + } + } + return Bry_split_.Rv__ok; + } + public void To_bry__dbg(Bry_bfr bfr) { + undis.To_bry__dbg(bfr); + if (bfr.Len_gt_0()) bfr.Add_byte_nl(); + bidis.To_bry__dbg(bfr); + } + private static final byte[] Bry__bidi_dlm = Bry_.new_a7("=>"); +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_parser__bidi_tst.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_parser__bidi_tst.java index a27517de8..3bcee3248 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_parser__bidi_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_parser__bidi_tst.java @@ -13,3 +13,13 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Vnt_rule_parser__bidi_tst { + private final Vnt_rule_parser_fxt fxt = new Vnt_rule_parser_fxt(); + @Test public void Basic() {fxt.Test_parse("x1:v1;" , "x1:v1");} + @Test public void Ws() {fxt.Test_parse(" x1 : v1 ;" , "x1:v1");} + @Test public void Entity() {fxt.Test_parse("x1:a x2:b;x2:b;" , "x1:a x2:b" , "x2:b");} + @Test public void Unknown__nth() {fxt.Test_parse("x1:a;wx2:b;x2:b;" , "x1:a;wx2:b" , "x2:b");} + @Test public void Unknown__1st() {fxt.Test_parse("wx1:a;x1:b;" , "");} +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_parser__undi_tst.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_parser__undi_tst.java index a27517de8..6fd73403e 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_parser__undi_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_parser__undi_tst.java @@ -13,3 +13,10 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Vnt_rule_parser__undi_tst { + private final Vnt_rule_parser_fxt fxt = new Vnt_rule_parser_fxt(); + @Test public void One() {fxt.Test_parse("k1=>x1:v1;" , "x1:k1=v1");} + @Test public void Many() {fxt.Test_parse("k1=>x1:v1;k2=>x2:v2;" , "x1:k1=v1", "x2:k2=v2");} +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_parser_fxt.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_parser_fxt.java index a27517de8..d8aca9683 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_parser_fxt.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_parser_fxt.java @@ -13,3 +13,23 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.langs.vnts.*; +class Vnt_rule_parser_fxt { + private final Vnt_rule_parser parser = new Vnt_rule_parser(); private final Vnt_rule_undi_mgr undis = new Vnt_rule_undi_mgr(); private final Vnt_rule_bidi_mgr bidis = new Vnt_rule_bidi_mgr(); + private final Bry_bfr bfr = Bry_bfr_.New_w_size(255); + public Vnt_rule_parser_fxt() { + Xol_vnt_regy vnt_regy = new Xol_vnt_regy(); + vnt_regy.Add(Bry_.new_a7("x1"), Bry_.new_a7("lang1")); + vnt_regy.Add(Bry_.new_a7("x2"), Bry_.new_a7("lang2")); + vnt_regy.Add(Bry_.new_a7("x3"), Bry_.new_a7("lang3")); + parser.Init(null, vnt_regy); + } + public void Test_parse(String raw, String... expd_ary) { + byte[] src = Bry_.new_u8(raw); + parser.Clear(undis, bidis, src); + parser.Parse(src, 0, src.length); + parser.To_bry__dbg(bfr); + Tfds.Eq_str_lines(String_.Concat_lines_nl_skip_last(expd_ary), bfr.To_str_and_clear()); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_undi_mgr.java b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_undi_mgr.java index a27517de8..51e4d4d85 100644 --- a/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_undi_mgr.java +++ b/400_xowa/src/gplx/xowa/parsers/vnts/Vnt_rule_undi_mgr.java @@ -13,3 +13,66 @@ 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.vnts; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +class Vnt_rule_undi_mgr { + private final Ordered_hash hash = Ordered_hash_.New_bry(); + public int Len() {return hash.Count();} + public boolean Has_none() {return hash.Count() == 0;} + public void Clear() {hash.Clear();} + public Vnt_rule_undi_grp Get_at(int i) {return (Vnt_rule_undi_grp)hash.Get_at(i);} + public Vnt_rule_undi_grp Get_by(byte[] key) {return (Vnt_rule_undi_grp)hash.Get_by(key);} + public byte[] Get_text_by_key_or_null(byte[] key) { + Vnt_rule_undi_grp grp = (Vnt_rule_undi_grp)hash.Get_by(key); if (grp == null) return null; + return grp.Len() == 0 ? null : grp.Get_at(0).Trg(); // REF.MW: $disp = $disp[0]; + } + public byte[] Get_text_at(int i) { + Vnt_rule_undi_grp grp = (Vnt_rule_undi_grp)hash.Get_at(i); if (grp == null) return null; + return grp.Len() == 0 ? null : grp.Get_at(0).Trg(); + } + public Vnt_rule_undi_grp Set(byte[] vnt, byte[] src, byte[] trg) { + Vnt_rule_undi_grp grp = (Vnt_rule_undi_grp)hash.Get_by(vnt); + if (grp == null) { + grp = new Vnt_rule_undi_grp(vnt); + hash.Add(vnt, grp); + } + grp.Set(src, trg); + return grp; + } + public void To_bry__dbg(Bry_bfr bfr) { + int len = hash.Count(); + for (int i = 0; i < len; ++i) { + if (i != 0) bfr.Add_byte_nl(); + Vnt_rule_undi_grp grp = (Vnt_rule_undi_grp)hash.Get_at(i); + bfr.Add(grp.Vnt()).Add_byte_colon(); + grp.To_bry__dbg(bfr); + } + } +} +class Vnt_rule_undi_grp { + private final Ordered_hash hash = Ordered_hash_.New_bry(); + public Vnt_rule_undi_grp(byte[] vnt) {this.vnt = vnt;} + public int Len() {return hash.Count();} + public Vnt_rule_undi_itm Get_at(int i) {return (Vnt_rule_undi_itm)hash.Get_at(i);} + public byte[] Vnt() {return vnt;} private final byte[] vnt; + public Vnt_rule_undi_itm Set(byte[] src, byte[] trg) { + Vnt_rule_undi_itm itm = (Vnt_rule_undi_itm)hash.Get_by(src); + if (itm == null) { + itm = new Vnt_rule_undi_itm(src, trg); + hash.Add(src, itm); + } + return itm; + } + public void To_bry__dbg(Bry_bfr bfr) { + int len = hash.Count(); + for (int i = 0; i < len; ++i) { + Vnt_rule_undi_itm itm = (Vnt_rule_undi_itm)hash.Get_at(i); + bfr.Add(itm.Src()).Add_byte_eq().Add(itm.Trg()); + } + } +} +class Vnt_rule_undi_itm { + public Vnt_rule_undi_itm(byte[] src, byte[] trg) {this.src = src; this.trg = trg;} + public byte[] Src() {return src;} private final byte[] src; + public byte[] Trg() {return trg;} private byte[] trg; + public void Trg_(byte[] v) {this.trg = v;} +} 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 a27517de8..27a40660b 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 @@ -13,3 +13,251 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.primitives.*; import gplx.core.btries.*; import gplx.xowa.parsers.htmls.*; +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) { + byte[] key_bry = xatr.Key_bry(); + byte[] chk_bry; int chk_bgn, chk_end; + if (key_bry == null) { + chk_bry = src; + chk_bgn = xatr.Key_bgn(); + chk_end = xatr.Key_end(); + if (chk_end - chk_bgn == 0) return true; // no key; nothing to whitelist; return true + } + else { // key_bry specified manually; EX: "id=1" has a manual key_bry of "id" + chk_bry = key_bry; + chk_bgn = 0; + chk_end = key_bry.length; + } + Object o = key_trie.Match_at(trv, chk_bry, chk_bgn, chk_end); + if (o == null) return false;// unknown atr_key; EX: + Xop_xatr_whitelist_itm itm = (Xop_xatr_whitelist_itm)o; + byte itm_key_tid = itm.Key_tid(); + xatr.Key_tid_(itm_key_tid); + boolean rv = itm.Tags()[tag_id] == 1 // is atr allowed for tag + && (itm.Exact() ? trv.Pos() == chk_end : true) // if exact, check for exact; else always true + ; + switch (itm_key_tid) { + case Mwh_atr_itm_.Key_tid__style: + if (!Scrub_style(xatr, src)) return false; + xatr.Val_bry_(gplx.xowa.parsers.amps.Xop_amp_mgr.Instance.Decode_as_bry(xatr.Val_as_bry())); // NOTE: must decode style values; "&#amp;#000000" -> "#000000"; see MW:checkCss; PAGE:en.w:Boron DATE:2015-07-29 + break; + case Mwh_atr_itm_.Key_tid__role: + if (!Bry_.Eq(Val_role_presentation, xatr.Val_as_bry())) return false; // MW: For now we only support role="presentation"; DATE:2014-04-05 + break; + } + return rv; + } + public Xop_xatr_whitelist_mgr Ini() { // REF.MW:Sanitizer.php|setupAttributeWhitelist + Ini_grp("common" , null , "id", "class", "lang", "dir", "title", "style", "role"); + Ini_grp("block" , "common" , "align"); + Ini_grp("tablealign" , null , "align", "char", "charoff", "valign"); + Ini_grp("tablecell" , null , "abbr", "axis", "headers", "scope", "rowspan", "colspan", "nowrap", "width", "height", "bgcolor"); + + Ini_nde(Xop_xnde_tag_.Tid__div , "block"); + Ini_nde(Xop_xnde_tag_.Tid__center , "common"); + Ini_nde(Xop_xnde_tag_.Tid__span , "block"); + Ini_nde(Xop_xnde_tag_.Tid__h1 , "block"); + Ini_nde(Xop_xnde_tag_.Tid__h2 , "block"); + Ini_nde(Xop_xnde_tag_.Tid__h3 , "block"); + Ini_nde(Xop_xnde_tag_.Tid__h4 , "block"); + Ini_nde(Xop_xnde_tag_.Tid__h5 , "block"); + Ini_nde(Xop_xnde_tag_.Tid__h6 , "block"); + Ini_nde(Xop_xnde_tag_.Tid__em , "common"); + Ini_nde(Xop_xnde_tag_.Tid__strong , "common"); + Ini_nde(Xop_xnde_tag_.Tid__cite , "common"); + Ini_nde(Xop_xnde_tag_.Tid__dfn , "common"); + Ini_nde(Xop_xnde_tag_.Tid__code , "common"); + Ini_nde(Xop_xnde_tag_.Tid__samp , "common"); + Ini_nde(Xop_xnde_tag_.Tid__kbd , "common"); + Ini_nde(Xop_xnde_tag_.Tid__var , "common"); + Ini_nde(Xop_xnde_tag_.Tid__abbr , "common"); + Ini_nde(Xop_xnde_tag_.Tid__blockquote , "common", "cite"); + Ini_nde(Xop_xnde_tag_.Tid__sub , "common"); + Ini_nde(Xop_xnde_tag_.Tid__sup , "common"); + Ini_nde(Xop_xnde_tag_.Tid__p , "block"); + Ini_nde(Xop_xnde_tag_.Tid__br , "id", "class", "title", "style", "clear"); + Ini_nde(Xop_xnde_tag_.Tid__pre , "common", "width"); + Ini_nde(Xop_xnde_tag_.Tid__ins , "common", "cite", "datetime"); + Ini_nde(Xop_xnde_tag_.Tid__del , "common", "cite", "datetime"); + Ini_nde(Xop_xnde_tag_.Tid__ul , "common", "type"); + Ini_nde(Xop_xnde_tag_.Tid__ol , "common", "type", "start"); + Ini_nde(Xop_xnde_tag_.Tid__li , "common", "type", "value"); + Ini_nde(Xop_xnde_tag_.Tid__dl , "common"); + Ini_nde(Xop_xnde_tag_.Tid__dd , "common"); + Ini_nde(Xop_xnde_tag_.Tid__dt , "common"); + Ini_nde(Xop_xnde_tag_.Tid__table , "common", "summary", "width", "border", "frame", "rules", "cellspacing", "cellpadding", "align", "bgcolor"); + Ini_nde(Xop_xnde_tag_.Tid__caption , "common", "align"); + Ini_nde(Xop_xnde_tag_.Tid__thead , "common", "tablealign"); + Ini_nde(Xop_xnde_tag_.Tid__tfoot , "common", "tablealign"); + Ini_nde(Xop_xnde_tag_.Tid__tbody , "common", "tablealign"); + Ini_nde(Xop_xnde_tag_.Tid__colgroup , "common", "span", "width", "tablealign"); + Ini_nde(Xop_xnde_tag_.Tid__col , "common", "span", "width", "tablealign"); + Ini_nde(Xop_xnde_tag_.Tid__tr , "common", "bgcolor", "tablealign"); + Ini_nde(Xop_xnde_tag_.Tid__td , "common", "tablecell", "tablealign"); + Ini_nde(Xop_xnde_tag_.Tid__th , "common", "tablecell", "tablealign"); + Ini_nde(Xop_xnde_tag_.Tid__a , "common", "href", "rel", "rev"); + Ini_nde(Xop_xnde_tag_.Tid__img , "common", "alt", "src", "width", "height"); + Ini_nde(Xop_xnde_tag_.Tid__tt , "common"); + Ini_nde(Xop_xnde_tag_.Tid__b , "common"); + Ini_nde(Xop_xnde_tag_.Tid__i , "common"); + Ini_nde(Xop_xnde_tag_.Tid__big , "common"); + Ini_nde(Xop_xnde_tag_.Tid__small , "common"); + Ini_nde(Xop_xnde_tag_.Tid__strike , "common"); + Ini_nde(Xop_xnde_tag_.Tid__s , "common"); + Ini_nde(Xop_xnde_tag_.Tid__u , "common"); + Ini_nde(Xop_xnde_tag_.Tid__font , "common", "size", "color", "face"); + Ini_nde(Xop_xnde_tag_.Tid__hr , "common", "noshade", "size", "width"); + Ini_nde(Xop_xnde_tag_.Tid__ruby , "common"); + Ini_nde(Xop_xnde_tag_.Tid__rb , "common"); + Ini_nde(Xop_xnde_tag_.Tid__rt , "common"); + Ini_nde(Xop_xnde_tag_.Tid__rp , "common"); + Ini_nde(Xop_xnde_tag_.Tid__math , "class", "style", "id", "title"); + Ini_nde(Xop_xnde_tag_.Tid__time , "class", "datetime"); + Ini_nde(Xop_xnde_tag_.Tid__bdi , "common"); + Ini_nde(Xop_xnde_tag_.Tid__data , "common", "value"); + Ini_nde(Xop_xnde_tag_.Tid__mark , "common"); + Ini_nde(Xop_xnde_tag_.Tid__q , "common"); + Ini_all_loose("data"); + return this; + } + private void Ini_grp(String key_str, String base_grp, String... cur_itms) { + byte[][] itms = Bry_.Ary(cur_itms); + if (base_grp != null) + itms = Bry_.Ary_add(itms, (byte[][])grp_hash.Get_by_bry(Bry_.new_a7(base_grp))); + byte[] key = Bry_.new_a7(key_str); + grp_hash.Add_bry_obj(key, itms); + } + private void Ini_nde(int tag_tid, String... key_strs) { + List_adp keys = List_adp_.New(); + int len = key_strs.length; + for (int i = 0; i < len; i++) { + byte[] key = Bry_.new_a7(key_strs[i]); + Object grp_obj = grp_hash.Get_by_bry(key); // is the key a grp? EX: "common" + if (grp_obj == null) + keys.Add(key); + else { + byte[][] grp_keys = (byte[][])grp_obj; + int grp_keys_len = grp_keys.length; + for (int j = 0; j < grp_keys_len; j++) + keys.Add(grp_keys[j]); + } + } + len = keys.Count(); + for (int i = 0; i < len; i++) { + byte[] key_bry = (byte[])keys.Get_at(i); + Xop_xatr_whitelist_itm itm = (Xop_xatr_whitelist_itm)key_trie.Match_exact(key_bry, 0, key_bry.length); + if (itm == null) { + itm = Ini_key_trie_add(key_bry, true); + key_trie.Add_obj(key_bry, itm); + } + itm.Tags()[tag_tid] = 1; + } + } + private void Ini_all_loose(String key_str) { + byte[] key_bry = Bry_.new_a7(key_str); + Ini_key_trie_add(key_bry, false); + Xop_xatr_whitelist_itm itm = Ini_key_trie_add(key_bry, false); + key_trie.Add_obj(key_bry, itm); + int len = Xop_xnde_tag_.Tid__len; + for (int i = 0; i < len; i++) + itm.Tags()[i] = 1; + } + private Xop_xatr_whitelist_itm Ini_key_trie_add(byte[] key, boolean exact) { + Object key_tid_obj = tid_hash.Get_by(key); + byte key_tid = key_tid_obj == null ? Mwh_atr_itm_.Key_tid__generic : ((Byte_obj_val)key_tid_obj).Val(); + Xop_xatr_whitelist_itm rv = new Xop_xatr_whitelist_itm(key, key_tid, exact); + key_trie.Add_obj(key, rv); + return rv; + } + private Hash_adp_bry tid_hash = Hash_adp_bry.ci_a7() + .Add_str_byte("id", Mwh_atr_itm_.Key_tid__id) + .Add_str_byte("style", Mwh_atr_itm_.Key_tid__style) + .Add_str_byte("role", Mwh_atr_itm_.Key_tid__role) + ; + private Btrie_slim_mgr key_trie = Btrie_slim_mgr.ci_a7(); // NOTE:ci.ascii:HTML.node_name + public boolean Scrub_style(Mwh_atr_itm xatr, byte[] raw) { // REF:Sanitizer.php|checkCss; '! expression | filter\s*: | accelerator\s*: | url\s*\( !ix'; NOTE: this seems to affect MS IE only; DATE:2013-04-01 + byte[] val_bry = xatr.Val_bry(); + byte[] chk_bry; int chk_bgn, chk_end; + if (val_bry == null) { + chk_bry = raw; + chk_bgn = xatr.Val_bgn(); + chk_end = xatr.Val_end(); + if (chk_end - chk_bgn == 0) return true; // no val; nothing to scrub; return true + } + else { // val_bry specified manually; EX: "id=1" has a manual val_bry of "1" + chk_bry = val_bry; + chk_bgn = 0; + chk_end = val_bry.length; + } + int pos = chk_bgn; + while (pos < chk_end) { + Object o = style_trie.Match_at(trv, chk_bry, pos, chk_end); + if (o == null) + ++pos; + else { + pos = trv.Pos(); + byte style_tid = ((Byte_obj_val)o).Val(); + switch (style_tid) { + case Style_expression: + xatr.Val_bry_(Bry_.Empty); + return false; + case Style_filter: + case Style_accelerator: + if (Next_non_ws_byte(chk_bry, pos, chk_end) == Byte_ascii.Colon) { + xatr.Val_bry_(Bry_.Empty); + return false; + } + break; + case Style_url: + case Style_urls: + case Style_image: + case Style_image_set: + if (Next_non_ws_byte(chk_bry, pos, chk_end) == Byte_ascii.Paren_bgn) { + xatr.Val_bry_(Bry_.Empty); + return false; + } + break; + } + } + } + return true; + } + byte Next_non_ws_byte(byte[] raw, int bgn, int end) { + for (int i = bgn; i < end; i++) { + byte b = raw[i]; + switch (b) { + case Byte_ascii.Space: + case Byte_ascii.Tab: + case Byte_ascii.Cr: + case Byte_ascii.Nl: + break; + default: + return b; + } + } + return Byte_ascii.Null; + } + static final byte Style_expression = 0, Style_filter = 1, Style_accelerator = 2, Style_url = 3, Style_urls = 4, Style_comment = 5, Style_image = 6, Style_image_set = 7; + private static final Btrie_slim_mgr style_trie = Btrie_slim_mgr.ci_a7() // NOTE:ci.ascii:Javascript + .Add_str_byte("expression" , Style_expression) + .Add_str_byte("filter" , Style_filter) + .Add_str_byte("accelerator" , Style_accelerator) + .Add_str_byte("url" , Style_url) + .Add_str_byte("urls" , Style_urls) + .Add_str_byte("image" , Style_image) + .Add_str_byte("image-set" , Style_image_set) + .Add_str_byte("/*" , Style_comment) + ; + private static final byte[] Val_role_presentation = Bry_.new_a7("presentation"); +} +class Xop_xatr_whitelist_itm { + public Xop_xatr_whitelist_itm(byte[] key, byte key_tid, boolean exact) {this.key = key; this.key_tid = key_tid; this.exact = exact;} + public byte[] Key() {return key;} private byte[] key; + public byte Key_tid() {return key_tid;} private byte key_tid; + public boolean Exact() {return exact;} private boolean exact; + public byte[] Tags() {return tags;} private byte[] tags = new byte[Xop_xnde_tag_.Tid__len]; +} 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 a27517de8..bc5272929 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 @@ -13,3 +13,61 @@ 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.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(); + @Before public void init() {fxt.Clear();} + @Test public void Basic() { + fxt.Whitelist(Xop_xnde_tag_.Tid__div , "style" , true); + fxt.Whitelist(Xop_xnde_tag_.Tid__div , "xstyle" , false); + fxt.Whitelist(Xop_xnde_tag_.Tid__div , "stylex" , false); + fxt.Whitelist(Xop_xnde_tag_.Tid__div , "styl" , false); + fxt.Whitelist(Xop_xnde_tag_.Tid__img , "alt" , true); + fxt.Whitelist(Xop_xnde_tag_.Tid__img , "span" , false); + fxt.Whitelist(Xop_xnde_tag_.Tid__div , "data-sort-type" , true); + fxt.Whitelist(Xop_xnde_tag_.Tid__data , "value" , true); + fxt.Whitelist(Xop_xnde_tag_.Tid__data , "valuex" , false); + } + @Test public void Role() { + fxt.Whitelist(Xop_xnde_tag_.Tid__div , "role" , "presentation", true); + fxt.Whitelist(Xop_xnde_tag_.Tid__div , "role" , "other", false); + } + @Test public void Scrub() { + fxt.Scrub_style_fail("expression"); + fxt.Scrub_style_fail("filter:a"); + fxt.Scrub_style_fail("filter\t \n:a"); + fxt.Scrub_style_fail("accelerator:a"); + fxt.Scrub_style_fail("url()"); + fxt.Scrub_style_fail("urls()"); + fxt.Scrub_style_pass("filterx"); + } +} +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); + } + 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); + } + 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); + whitelist_mgr.Scrub_style(atr_itm, val_bry); + Tfds.Eq(expd, String_.new_a7(atr_itm.Val_bry())); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_log.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_log.java index a27517de8..8ce6b16fb 100644 --- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_log.java +++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_log.java @@ -13,3 +13,23 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.log_msgs.*; +public class Xop_xnde_log { + private static final Gfo_msg_grp owner = Gfo_msg_grp_.new_(Xoa_app_.Nde, "xnde"); + public static final Gfo_msg_itm + Dangling_xnde = Gfo_msg_itm_.new_warn_(owner, "dangling_xnde") + , End_tag_not_allowed = Gfo_msg_itm_.new_note_(owner, "end_tag_not_allowed") + , Escaped_xnde = Gfo_msg_itm_.new_warn_(owner, "escaped_xnde") + , Invalid_char = Gfo_msg_itm_.new_warn_(owner, "invalid_char") + , Xtn_end_not_found = Gfo_msg_itm_.new_warn_(owner, "xtn_end_not_found") + , Invalid_tbl_sub = Gfo_msg_itm_.new_warn_(owner, "invalid_tbl_sub") + , Invalid_nest = Gfo_msg_itm_.new_warn_(owner, "invalid_nest") + , No_inline = Gfo_msg_itm_.new_warn_(owner, "no_inline") + , Tbl_sub_already_opened = Gfo_msg_itm_.new_warn_(owner, "tbl_sub_already_opened") + , Auto_closing_section = Gfo_msg_itm_.new_warn_(owner, "auto_closing_section") + , Eos_while_closing_tag = Gfo_msg_itm_.new_warn_(owner, "eos_while_closing_tag") + , Sub_sup_swapped = Gfo_msg_itm_.new_warn_(owner, "sub_sup_swapped") + , Restricted_tag = Gfo_msg_itm_.new_warn_(owner, "restricted_tag") + ; +} diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_lxr.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_lxr.java index a27517de8..e31185908 100644 --- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_lxr.java +++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_lxr.java @@ -13,3 +13,13 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.xowa.langs.*; +public class Xop_xnde_lxr implements Xop_lxr { + public int Lxr_tid() {return Xop_lxr_.Tid_xnde;} + public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {core_trie.Add(Byte_ascii.Lt, 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) {return ctx.Xnde().Make_tkn(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos);} + public static final Xop_xnde_lxr Instance = new Xop_xnde_lxr(); Xop_xnde_lxr() {} +} diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tag.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tag.java index a27517de8..ab8eafa1d 100644 --- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tag.java +++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tag.java @@ -13,3 +13,71 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.primitives.*; +public class Xop_xnde_tag { + public Xop_xnde_tag(int id, String name_str) { // NOTE: should only be used by Xop_xnde_tag_ + this.id = id; + this.name_bry = Bry_.new_a7(name_str); + this.name_str = name_str; + this.name_len = name_bry.length; + this.xtn_bgn_tag = Bry_.Add(Byte_ascii.Angle_bgn_bry, name_bry); + this.xtn_end_tag = Bry_.Add(Xop_xnde_tag_.Bry__end_tag_bgn, name_bry); // always force endtag; needed for + this.xtn_end_tag_tmp = new byte[xtn_end_tag.length]; Array_.Copy(xtn_end_tag, xtn_end_tag_tmp); + } + public int Id() {return id;} private final int id; + public byte[] Name_bry() {return name_bry;} private final byte[] name_bry; + public String Name_str() {return name_str;} private final String name_str; + public int Name_len() {return name_len;} private final int name_len; + public byte[] Xtn_bgn_tag() {return xtn_bgn_tag;} private final byte[] xtn_bgn_tag; + public byte[] Xtn_end_tag() {return xtn_end_tag;} private final byte[] xtn_end_tag; + public byte[] Xtn_end_tag_tmp() {return xtn_end_tag_tmp;} private final byte[] xtn_end_tag_tmp; + public boolean Xtn() {return xtn;} public Xop_xnde_tag Xtn_() {xtn = true; return this;} private boolean xtn; + public boolean Xtn_mw() {return xtn_mw;} public Xop_xnde_tag Xtn_mw_() {xtn_mw = true; xtn = true; return this;} private boolean xtn_mw; // NOTE: Xtn_mw_() marks both xtn and xtn_mw as true + public int Bgn_mode() {return bgn_nde_mode;} private int bgn_nde_mode = Xop_xnde_tag_.Bgn_mode__normal; + public Xop_xnde_tag Bgn_mode__inline_() {bgn_nde_mode = Xop_xnde_tag_.Bgn_mode__inline; return this;} + public int End_mode() {return end_nde_mode;} private int end_nde_mode = Xop_xnde_tag_.End_mode__normal; + public Xop_xnde_tag End_mode__inline_() {end_nde_mode = Xop_xnde_tag_.End_mode__inline; return this;} + public Xop_xnde_tag End_mode__escape_() {end_nde_mode = Xop_xnde_tag_.End_mode__escape; return this;} + public boolean Single_only() {return single_only;} public Xop_xnde_tag Single_only_() {single_only = true; return this;} private boolean single_only; + public boolean Tbl_sub() {return tbl_sub;} public Xop_xnde_tag Tbl_sub_() {tbl_sub = true; return this;} private boolean tbl_sub; + public boolean Restricted() {return restricted;} public Xop_xnde_tag Restricted_() {restricted = true; return this;} private boolean restricted; + public boolean No_inline() {return no_inline;} public Xop_xnde_tag No_inline_() {no_inline = true; return this;} private boolean no_inline; + public boolean Inline_by_backslash() {return inline_by_backslash;} public Xop_xnde_tag Inline_by_backslash_() {inline_by_backslash = true; return this;} private boolean inline_by_backslash; + public boolean Section() {return section;} public Xop_xnde_tag Section_() {section = true; return this;} private boolean section; + public boolean Repeat_ends() {return repeat_ends;} public Xop_xnde_tag Repeat_ends_() {repeat_ends = true; return this;} private boolean repeat_ends; + public boolean Repeat_mids() {return repeat_mids;} public Xop_xnde_tag Repeat_mids_() {repeat_mids = true; return this;} private boolean repeat_mids; + public boolean Empty_ignored() {return empty_ignored;} public Xop_xnde_tag Empty_ignored_() {empty_ignored = true; return this;} private boolean empty_ignored; + public boolean Single_only_html() {return single_only_html;} public Xop_xnde_tag Single_only_html_() {single_only_html = true; return this;} private boolean single_only_html; + public boolean Raw() {return raw;} public Xop_xnde_tag Raw_() {raw = true; return this;} private boolean raw; + public static final byte Block_noop = 0, Block_bgn = 1, Block_end = 2; + public byte Block_open() {return block_open;} private byte block_open = Block_noop; + public byte Block_close() {return block_close;} private byte block_close = Block_noop; + public Xop_xnde_tag Block_open_bgn_() {block_open = Block_bgn; return this;} public Xop_xnde_tag Block_open_end_() {block_open = Block_end; return this;} + public Xop_xnde_tag Block_close_bgn_() {block_close = Block_bgn; return this;} public Xop_xnde_tag Block_close_end_() {block_close = Block_end; return this;} + public boolean Xtn_auto_close() {return xtn_auto_close;} public Xop_xnde_tag Xtn_auto_close_() {xtn_auto_close = true; return this;} private boolean xtn_auto_close; + public boolean Ignore_empty() {return ignore_empty;} public Xop_xnde_tag Ignore_empty_() {ignore_empty = true; return this;} private boolean ignore_empty; + public boolean Xtn_skips_template_args() {return xtn_skips_template_args;} public Xop_xnde_tag Xtn_skips_template_args_() {xtn_skips_template_args = true; return this;} private boolean xtn_skips_template_args; + public Ordered_hash Langs() {return langs;} private Ordered_hash langs; private Int_obj_ref langs_key; + public Xop_xnde_tag Langs_(int lang_code, String name) { + if (langs == null) { + langs = Ordered_hash_.New(); + langs_key = Int_obj_ref.New_neg1(); + } + Xop_xnde_tag_lang lang_tag = new Xop_xnde_tag_lang(lang_code, name); + langs.Add(lang_tag.Lang_code(), lang_tag); + return this; + } + public Xop_xnde_tag_lang Langs_get(gplx.xowa.langs.cases.Xol_case_mgr case_mgr, int cur_lang, byte[] src, int bgn, int end) { + if (langs == null) return Xop_xnde_tag_lang.Instance; // no langs defined; always return true; EX: + if (Bry_.Eq(src, bgn, end, name_bry)) return Xop_xnde_tag_lang.Instance; // canonical name (name_bry) is valid in all langs; EX:
    and cur_lang=de + synchronized (langs) { + langs_key.Val_(cur_lang); + } + Xop_xnde_tag_lang lang = (Xop_xnde_tag_lang)langs.Get_by(langs_key); + if (lang == null) return null; // cur tag is a lang tag, but no tag for this lang; EX: "" and cur_lang=de + return Bry_.Eq_ci_a7(lang.Name_bry(), src, bgn, end) + ? lang + : null; + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tag_.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tag_.java index a27517de8..b9738b3df 100644 --- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tag_.java +++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tag_.java @@ -13,3 +13,269 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.langs.*; +public class Xop_xnde_tag_ { + public static final int Bgn_mode__normal = 0, Bgn_mode__inline = 1; + public static final int End_mode__normal = 0, End_mode__inline = 1, End_mode__escape = 2; // escape is for hr which does not support + public static final byte[] Bry__onlyinclude = Bry_.new_a7("onlyinclude"); + public static final byte[] Bry__end_tag_bgn = Bry_.new_a7(" alias DATE:2014-07-18 + trie_tmpl = Btrie_slim_mgr.ci_u8() + , trie_wtxt_main = Btrie_slim_mgr.ci_u8() + , trie_wtxt_tmpl = Btrie_slim_mgr.ci_u8(); + public Btrie_slim_mgr Get_trie(int i) { + if (init_needed) Init_by_hash(null); // TEST: + switch (i) { + case Xop_parser_tid_.Tid__defn: return trie_tmpl; + case Xop_parser_tid_.Tid__tmpl: return trie_wtxt_tmpl; + case Xop_parser_tid_.Tid__wtxt: return trie_wtxt_main; + case Xop_parser_tid_.Tid__null: default: return trie_wtxt_tmpl; // TODO_OLD: should throw Err_.new_unhandled(i); + } + } + public void Init_by_meta(Hash_adp_bry xtn_hash) {Init_by_hash(xtn_hash);} + private void Init_by_hash(Hash_adp_bry xtn_hash) { + this.init_needed = false; + Init_trie(trie_tmpl , xtn_hash, Bool_.Y); + Init_trie(trie_wtxt_tmpl , xtn_hash, Bool_.Y); + Init_trie(trie_wtxt_main , xtn_hash, Bool_.N); + } + private void Init_trie(Btrie_slim_mgr trie, Hash_adp_bry xtn_hash, boolean is_tmpl) { + int len = Xop_xnde_tag_.Tid__len; + Xop_xnde_tag[] ary = Xop_xnde_tag_.Ary; + for (int i = 0; i < len; ++i) { + Xop_xnde_tag xnde = ary[i]; + if (Ignore_xnde(xtn_hash, xnde)) continue; // skip; xtn is not defined in site_meta_db + if (is_tmpl && !xnde.Xtn()) continue; // is_tmpl and basic_xnde; EX: + Add_itm(trie, xnde); + } + if (is_tmpl) { // is_tmpl also has , , , + Add_itm(trie, Xop_xnde_tag_.Tag__nowiki); + Add_itm(trie, Xop_xnde_tag_.Tag__includeonly); + Add_itm(trie, Xop_xnde_tag_.Tag__noinclude); + Add_itm(trie, Xop_xnde_tag_.Tag__onlyinclude); + } + } + private boolean Ignore_xnde(Hash_adp_bry xtn_hash, Xop_xnde_tag xnde) { + return xtn_hash != null // xtn_hash is null during tests or when wiki is not in site_meta_db + && xnde.Xtn_mw() // only apply filter to xtn_xnde, not basic_xnde; EX: not + && !xtn_hash.Has(xnde.Name_bry()) // xtn_xnde is not in xtn_hash + && !Int_.In(xnde.Id(), Xop_xnde_tag_.Tid__translate, Xop_xnde_tag_.Tid__languages) // always include and ; TODO_OLD:filter out when extensions supported in site_cfg; DATE:2015-10-13 + ; // skip; xtn is not defined in site_meta_db + } + private void Add_itm(Btrie_slim_mgr trie, Xop_xnde_tag xnde) { + trie.Add_obj(xnde.Name_bry(), xnde); + Ordered_hash langs = xnde.Langs(); + if (langs != null) { // tag has langs; EX:
    ; DATE:2014-07-18 + int langs_len = langs.Count(); + for (int i = 0; i < langs_len; ++i) { // register each lang's tag; EX: "", "" + Xop_xnde_tag_lang lang = (Xop_xnde_tag_lang)langs.Get_at(i); + trie.Add_obj(lang.Name_bry(), xnde); + } + } + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tag_stack.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tag_stack.java index a27517de8..05602dc30 100644 --- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tag_stack.java +++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tag_stack.java @@ -13,3 +13,21 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +public class Xop_xnde_tag_stack { + public void Push() {xmlTagsStack.Add(xmlTags); xmlTags = new int[Xop_xnde_tag_.Tid__len];} + public void Pop() {xmlTags = (int[])List_adp_.Pop(xmlTagsStack);} + public boolean Has(int id) {return xmlTags[id] != 0;} + public void Add(int id) {++xmlTags[id];} + public void Del(int id) { + int val = --xmlTags[id]; + if (val == -1) xmlTags[id] = 0; + } + public void Clear() { + for (int i = 0; i < Xop_xnde_tag_.Tid__len; i++) + xmlTags[i] = 0; + xmlTagsStack.Clear(); + } + List_adp xmlTagsStack = List_adp_.New(); + int[] xmlTags = new int[Xop_xnde_tag_.Tid__len]; +} 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 a27517de8..29ef3540e 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 @@ -13,3 +13,133 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.xowa.xtns.*; import gplx.xowa.parsers.tblws.*; import gplx.xowa.parsers.tmpls.*; import gplx.xowa.parsers.htmls.*; +public class Xop_xnde_tkn extends Xop_tkn_itm_base implements Xop_tblw_tkn { + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_xnde;} + public int Tblw_tid() {return tag.Id();} // NOTE: tblw tkns actually return xnde as Tblw_tid + public boolean Tblw_xml() {return true;} + public int Tblw_subs_len() {return tblw_subs_len;} public void Tblw_subs_len_add_() {++tblw_subs_len;} private int tblw_subs_len; + public byte CloseMode() {return closeMode;} public Xop_xnde_tkn CloseMode_(byte v) {closeMode = v; return this;} private byte closeMode = Xop_xnde_tkn.CloseMode_null; + public boolean Tag_visible() {return tag_visible;} public Xop_xnde_tkn Tag_visible_(boolean v) {tag_visible = v; return this;} private boolean tag_visible = true; + public int Name_bgn() {return name_bgn;} public Xop_xnde_tkn Name_bgn_(int v) {name_bgn = v; return this;} private int name_bgn = -1; + public int Name_end() {return name_end;} public Xop_xnde_tkn Name_end_(int v) {name_end = v; return this;} private int name_end = -1; + public Xop_xnde_tkn Name_rng_(int bgn, int end) {name_bgn = bgn; name_end = end; return this;} + public int Atrs_bgn() {return atrs_bgn;} public Xop_xnde_tkn Atrs_bgn_(int v) {atrs_bgn = v; return this;} private int atrs_bgn = Xop_tblw_wkr.Atrs_null; + public int Atrs_end() {return atrs_end;} public Xop_xnde_tkn Atrs_end_(int v) {atrs_end = v; return this;} private int atrs_end = Xop_tblw_wkr.Atrs_null; + public Xop_xnde_tkn Atrs_rng_(int bgn, int end) {atrs_bgn = bgn; atrs_end = end; return this;} + public void Atrs_rng_set(int bgn, int end) {Atrs_rng_(bgn, end);} + public Mwh_atr_itm[] Atrs_ary() {return atrs_ary;} + public Xop_xnde_tkn Atrs_ary_ (Mwh_atr_itm[] v) {atrs_ary = v; return this;} private Mwh_atr_itm[] atrs_ary; + public Xop_tblw_tkn Atrs_ary_as_tblw_ (Mwh_atr_itm[] v) {atrs_ary = v; return this;} + public Xop_xnde_tag Tag() {return tag;} public Xop_xnde_tkn Tag_(Xop_xnde_tag v) {tag = v; return this;} private Xop_xnde_tag tag; + public int Tag_open_bgn() {return tag_open_bgn;} private int tag_open_bgn = Int_.Null; + public int Tag_open_end() {return tag_open_end;} private int tag_open_end = Int_.Null; + public Xop_xnde_tkn Tag_open_rng_(int bgn, int end) {this.tag_open_bgn = bgn; this.tag_open_end = end; return this;} + public int Tag_close_bgn() {return tag_close_bgn;} private int tag_close_bgn = Int_.Null; + public int Tag_close_end() {return tag_close_end;} private int tag_close_end = Int_.Null; + public Xop_xnde_tkn Tag_close_rng_(int bgn, int end) {this.tag_close_bgn = bgn; this.tag_close_end = end; return this;} + public Xop_xnde_tkn Subs_add_ary(Xop_tkn_itm... ary) {for (Xop_tkn_itm itm : ary) Subs_add(itm); return this;} + public Xox_xnde Xnde_xtn() {return xnde_xtn;} public Xop_xnde_tkn Xnde_xtn_(Xox_xnde v) {xnde_xtn = v; return this;} private Xox_xnde xnde_xtn; + @Override public void Tmpl_compile(Xop_ctx ctx, byte[] src, Xot_compile_data prep_data) { + switch (tag.Id()) { + case Xop_xnde_tag_.Tid__noinclude: // NOTE: prep_mode is false to force recompile; see Ex_Tmpl_noinclude and {{{1|a}}} + case Xop_xnde_tag_.Tid__includeonly: // NOTE: changed to always ignore ; DATE:2014-05-10 + break; + case Xop_xnde_tag_.Tid__nowiki: { + int subs_len = this.Subs_len(); + for (int i = 0; i < subs_len; i++) { + Xop_tkn_itm sub = this.Subs_get(i); + sub.Tmpl_compile(ctx, src, prep_data); + } + break; + } + case Xop_xnde_tag_.Tid__onlyinclude: { + int subs_len = this.Subs_len(); + for (int i = 0; i < subs_len; i++) { + Xop_tkn_itm sub = this.Subs_get(i); + sub.Tmpl_compile(ctx, src, prep_data); + } + prep_data.OnlyInclude_exists = true; + break; + } + default: { + int subs_len = this.Subs_len(); + for (int i = 0; i < subs_len; i++) { + Xop_tkn_itm sub = this.Subs_get(i); + sub.Tmpl_compile(ctx, src, prep_data); + } + break; // can happen in compile b/c invks are now being compiled + } + } + } +// public static Xop_ctx Hack_ctx; // CHART + @Override public boolean Tmpl_evaluate(Xop_ctx ctx, byte[] src, Xot_invk caller, Bry_bfr bfr) { +// if (ctx.Scribunto) { // CHART +// byte[] key = uniq_mgr.Add(Bry_.Mid(src, this.Src_bgn(), this.Src_end())); +// bfr.Add(key); +// return true; +// } + int subs_len = this.Subs_len(); + switch (tag.Id()) { + case Xop_xnde_tag_.Tid__noinclude: // do not evaluate subs + break; + case Xop_xnde_tag_.Tid__includeonly: // evaluate subs + if (!ctx.Only_include_evaluate()) { + for (int i = 0; i < subs_len; i++) + this.Subs_get(i).Tmpl_evaluate(ctx, src, caller, bfr); + } + break; + case Xop_xnde_tag_.Tid__nowiki: // evaluate subs; add tags + bfr.Add_byte(Byte_ascii.Lt).Add(Xop_xnde_tag_.Tag__nowiki.Name_bry()).Add_byte(Byte_ascii.Gt); + for (int i = 0; i < subs_len; i++) + this.Subs_get(i).Tmpl_evaluate(ctx, src, caller, bfr); + bfr.Add_byte(Byte_ascii.Lt).Add_byte(Byte_ascii.Slash).Add(Xop_xnde_tag_.Tag__nowiki.Name_bry()).Add_byte(Byte_ascii.Gt); + break; + case Xop_xnde_tag_.Tid__onlyinclude: // evaluate subs but toggle onlyinclude flag on/off +// boolean prv_val = ctx.Onlyinclude_enabled; +// ctx.Onlyinclude_enabled = false; + for (int i = 0; i < subs_len; i++) + this.Subs_get(i).Tmpl_evaluate(ctx, src, caller, bfr); +// ctx.Onlyinclude_enabled = prv_val; + break; + default: // ignore tags except for xtn; NOTE: Xtn tags are part of tagRegy_wiki_tmpl stage + if (tag.Xtn()) { + Bry_bfr cur_bfr = bfr; + // UNIQ; DATE:2017-03-31 + boolean is_tmpl_mode = ctx.Wiki().Parser_mgr().Ctx().Parse_tid() == Xop_parser_tid_.Tid__tmpl; + if (is_tmpl_mode) { + cur_bfr = ctx.Wiki().Utl__bfr_mkr().Get_m001().Reset_if_gt(Io_mgr.Len_mb); + } + + // write tag_bgn; EX: + cur_bfr.Add_mid(src, tag_open_bgn, tag_open_end); + + // write subs; must always evaluate subs; handle {{{1}}}; DATE:2014-03-03 + for (int i = 0; i < subs_len; i++) + this.Subs_get(i).Tmpl_evaluate(ctx, src, caller, cur_bfr); + + // write tag_end; EX: + cur_bfr.Add_mid(src, tag_close_bgn, tag_close_end); + + // xtn is unclosed; add a else rest of page will be gobbled; PAGE:en.w:Provinces_and_territories_of_Canada DATE:2014-11-13 + // NOTE: must check for inline else will output trailing '' after '' PAGE:en.w:National_Popular_Vote_Interstate_Compact DATE:2017-04-10 + if (tag_close_bgn == Int_.Min_value && closeMode != Xop_xnde_tkn.CloseMode_inline) { + cur_bfr.Add(tag.Xtn_end_tag()); + cur_bfr.Add(Byte_ascii.Gt_bry); + } + + // 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); + bfr.Add(key); + } + } + break; + } + return true; + } + public static Xop_xnde_tkn new_() {return new Xop_xnde_tkn();} private Xop_xnde_tkn() {} + public static final byte CloseMode_null = 0, CloseMode_inline = 1, CloseMode_pair = 2, CloseMode_open = 3; +} diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tkn_chkr.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tkn_chkr.java index a27517de8..2df2a1071 100644 --- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tkn_chkr.java +++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tkn_chkr.java @@ -13,3 +13,27 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.tests.*; +public class Xop_xnde_tkn_chkr extends Xop_tkn_chkr_base { + @Override public Class TypeOf() {return Xop_xnde_tkn.class;} + @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_xnde;} + public String Xnde_tagId() {return xnde_tagId;} + public Xop_xnde_tkn_chkr Xnde_tagId_(int v) {xnde_tagId = Xop_xnde_tag_.Ary[v].Name_str(); return this;} private String xnde_tagId = null; + public Tst_chkr Xnde_xtn() {return xnde_data;} public Xop_xnde_tkn_chkr Xnde_xtn_(Tst_chkr v) {xnde_data = v; return this;} Tst_chkr xnde_data = null; + public byte CloseMode() {return closeMode;} public Xop_xnde_tkn_chkr CloseMode_(byte v) {closeMode = v; return this;} private byte closeMode = Xop_xnde_tkn.CloseMode_null; + public Xop_xnde_tkn_chkr Name_rng_(int bgn, int end) {name_bgn = bgn; name_end = end; return this;} private int name_bgn = String_.Pos_neg1; int name_end = String_.Pos_neg1; + public Xop_xnde_tkn_chkr Atrs_rng_(int bgn, int end) {atrs_bgn = bgn; atrs_end = end; return this;} private int atrs_bgn = String_.Pos_neg1; int atrs_end = String_.Pos_neg1; + @Override public int Chk_hook(Tst_mgr mgr, String path, Object actl_obj, int err) { + Xop_xnde_tkn actl = (Xop_xnde_tkn)actl_obj; + err += mgr.Tst_val(xnde_tagId == null, path, "xnde_tagId", xnde_tagId, Xop_xnde_tag_.Ary[actl.Tag().Id()].Name_str()); + err += mgr.Tst_val(closeMode == Xop_xnde_tkn.CloseMode_null, path, "close_mode", closeMode, actl.CloseMode()); + err += mgr.Tst_val(name_bgn == String_.Pos_neg1, path, "name_bgn", name_bgn, actl.Name_bgn()); + err += mgr.Tst_val(name_end == String_.Pos_neg1, path, "name_end", name_end, actl.Name_end()); + err += mgr.Tst_val(atrs_bgn == String_.Pos_neg1, path, "atrs_bgn", atrs_bgn, actl.Atrs_bgn()); + err += mgr.Tst_val(atrs_end == String_.Pos_neg1, path, "atrs_end", atrs_end, actl.Atrs_end()); + if (xnde_data != null) + err += mgr.Tst_sub_obj(xnde_data, actl.Xnde_xtn(), path + "." + "xndeData", err); + return err; + } +} 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 a27517de8..36dda2dcc 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 @@ -13,3 +13,719 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import gplx.core.btries.*; import gplx.core.envs.*; import gplx.xowa.apps.progs.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.pfuncs.strings.*; +import gplx.langs.htmls.entitys.*; +import gplx.xowa.parsers.logs.*; import gplx.xowa.parsers.tblws.*; import gplx.xowa.parsers.lnkis.*; import gplx.xowa.parsers.miscs.*; import gplx.xowa.parsers.htmls.*; +public class Xop_xnde_wkr implements Xop_ctx_wkr { + public void Ctor_ctx(Xop_ctx ctx) {} + public boolean Pre_at_bos() {return pre_at_bos;} public void Pre_at_bos_(boolean v) {pre_at_bos = v;} private boolean pre_at_bos; + public void Page_bgn(Xop_ctx ctx, Xop_root_tkn root) {} + public void Page_end(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int src_len) {this.Clear();} + private void Clear() {pre_at_bos = false;} + public void AutoClose(Xop_ctx ctx, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, Xop_tkn_itm tkn, int closing_tkn_tid) { + Xop_xnde_tkn xnde = (Xop_xnde_tkn)tkn; + xnde.Src_end_(src_len); + xnde.Subs_move(root); + if (closing_tkn_tid == Xop_tkn_itm_.Tid_lnki_end) Xop_xnde_wkr_.AutoClose_handle_dangling_nde_in_caption(root, tkn); // PAGE:sr.w:Сићевачка_клисура; DATE:2014-07-03 + ctx.Msg_log().Add_itm_none(Xop_xnde_log.Dangling_xnde, src, xnde.Src_bgn(), xnde.Name_end()); // NOTE: xnde.Src_bgn to start at <; xnde.Name_end b/c xnde.Src_end is -1 + } + private static final Btrie_rv trv = new Btrie_rv(); + 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) { + if (bgn_pos == Xop_parser_.Doc_bgn_bos) bgn_pos = 0; // do not allow -1 pos + if (cur_pos == src_len) return ctx.Lxr_make_txt_(src_len); // "<" is EOS; don't raise error; + Xop_tkn_itm last_tkn = ctx.Stack_get_last(); // BLOCK:invalid_ttl_check + if ( last_tkn != null + && last_tkn.Tkn_tid() == Xop_tkn_itm_.Tid_lnki) { + Xop_lnki_tkn lnki = (Xop_lnki_tkn)last_tkn; + if ( lnki.Pipe_count_is_zero() + // && !Xop_lnki_wkr_.Parse_ttl(ctx, src, lnki, bgn_pos) // NOTE: no ttl parse check; in ttl is automatically invalid; EX: [[ac|d]]; "a" is valid ttl, but "ac" is not + ) { + ctx.Stack_pop_last(); + return Xop_lnki_wkr_.Invalidate_lnki(ctx, src, root, lnki, bgn_pos); + } + } + + // check for "= src_len) return ctx.Lxr_make_txt_(atrs_bgn_pos); // EOS: EX: "" -> "
    + ++tag_end_pos; + break; + case Byte_ascii.Dollar: // handles ; + default: // allow all other symbols by defaults; TODO_OLD: need to filter out some like + break; + // letters / numbers after tag; tag is invalid; EX: "; PAGE:pl.w:Scynk_nadrzewny; DATE:2016-08-07 + tag_obj = null; + break; + } + } + boolean ctx_cur_tid_is_tblw_atr_owner = false; + switch (ctx.Cur_tkn_tid()) { + case Xop_tkn_itm_.Tid_tblw_tb: case Xop_tkn_itm_.Tid_tblw_tr: case Xop_tkn_itm_.Tid_tblw_th: + ctx_cur_tid_is_tblw_atr_owner = true; + break; + } + if (tag_obj == null) { // not a known xml tag; EX: ""; "if 5 < 7 then" + if (ctx.Parse_tid() == Xop_parser_tid_.Tid__wtxt) { + if (ctx_cur_tid_is_tblw_atr_owner) // unknown_tag is occurring inside tblw element (EX: {| style='margin:1em; NOTE: MW does not ignore > inside quotes; EX:
    abc
    ->
    (which is valid wikitext; NOTE: must happen after + if (tag_is_closing) + return Make_xtag_end(ctx, tkn_mkr, root, src, src_len, bgn_pos, gt_pos, tag); + else + return Make_xtag_bgn(ctx, tkn_mkr, root, src, src_len, bgn_pos, gt_pos, name_bgn, name_end, tag, atrs_bgn_pos, src[tag_end_pos], force_xtn_for_nowiki, pre2_hack); + } + private static Xop_tkn_itm Make_bry_tkn(Xop_tkn_mkr tkn_mkr, byte[] src, int bgn_pos, int cur_pos) { + int len = cur_pos - bgn_pos; + byte[] bry = null; + if (len == 1 && src[bgn_pos] == Byte_ascii.Lt) bry = Gfh_entity_.Lt_bry; + else if (len == 2 && src[bgn_pos] == Byte_ascii.Lt + && src[bgn_pos + 1] == Byte_ascii.Slash) bry = Bry_escape_lt_slash; // NOTE: should use bgn_pos, not cur_pos; DATE:2014-10-22 + else bry = Bry_.Add(Gfh_entity_.Lt_bry, Bry_.Mid(src, bgn_pos + 1, cur_pos)); // +1 to skip < + return tkn_mkr.Bry_raw(bgn_pos, cur_pos, bry); + } + private int Make_noinclude(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int gtPos, Xop_xnde_tag tag, int tag_end_pos, boolean tag_is_closing) { + tag_end_pos = Bry_find_.Find_fwd_while(src, tag_end_pos, src_len, Byte_ascii.Space);// NOTE: must skip spaces else "" will not work with safesubst; PAGE:en.w:Wikipedia:Featured_picture_candidates; DATE:2014-06-24 + byte tag_end_byte = src[tag_end_pos]; + if (tag_end_byte == Byte_ascii.Slash) { // inline + boolean valid = true; + for (int i = tag_end_pos; i < gtPos; i++) { + switch (src[i]) { + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: break; + case Byte_ascii.Slash: break; + default: valid = false; break; + } + } + if (valid) { + ctx.Subs_add(root, tkn_mkr.Ignore(bgn_pos, gtPos, Xop_ignore_tkn.Ignore_tid_include_tmpl)); + return gtPos + 1; // 1=adj_next_char + } + else { + return ctx.Lxr_make_txt_(gtPos); + } + } + int end_rhs = -1, findPos = gtPos; + byte[] end_bry = Xop_xnde_tag_.Tag__noinclude.Xtn_end_tag(); int end_bry_len = end_bry.length; + if (tag_is_closing) // ; no end tag to search for; DATE:2014-05-02 + end_rhs = gtPos; + else { // ; search for end tag + while (true) { + int end_lhs = Bry_find_.Find_fwd(src, end_bry, findPos); + if (end_lhs == -1 || (end_lhs + end_bry_len) == src_len) break; // nothing found or EOS; + findPos = end_lhs; + for (int i = end_lhs + end_bry_len; i < src_len; i++) { + switch (src[i]) { + case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: break; + case Byte_ascii.Slash: break; + case Byte_ascii.Gt: end_rhs = i + 1; i = src_len; break; // +1 to place after Gt + default: findPos = i ; i = src_len; break; + } + } + if (end_rhs != -1) break; + } + if (end_rhs == -1) // end tag not found; match to end of String + end_rhs = src_len; + } + ctx.Subs_add(root, tkn_mkr.Ignore(bgn_pos, end_rhs, Xop_ignore_tkn.Ignore_tid_include_tmpl)); + return end_rhs; + } + private boolean pre2_pending = false; + private int Make_xtag_bgn(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int gtPos, int name_bgn, int name_end, Xop_xnde_tag tag, int tag_end_pos, byte tag_end_byte, boolean force_xtn_for_nowiki, boolean pre2_hack) { + boolean inline = false; + int open_tag_end = gtPos + 1, atrs_bgn = -1, atrs_end = -1; // 1=adj_next_char + // calc (a) inline; (b) atrs + switch (tag_end_byte) { // look at last char of tag; EX: for b, following are registered: "b/","b>","b\s","b\n","b\t" + case Byte_ascii.Slash: // "/" EX: "
    ,
     are allowed
    +				inline = true;		
    +				break;
    +			case Byte_ascii.Backslash:	// allow ; EX:w:Mosquito
    +				if (tag.Inline_by_backslash())
    +					src[tag_end_pos] = Byte_ascii.Slash;
    +				break;
    +			case Byte_ascii.Gt:		// ">" "normal" tag; noop
    +				break;
    +			default:				// "\s", "\n", "\t"
    +				atrs_bgn = tag_end_pos;		// set atrs_bgn to first char after ws; EX: "" atrs_bgn = pos(h)
    +				atrs_end = gtPos;			// set atrs_end to gtPos;				EX: "" atrs_end = pos(>)
    +				if (src[gtPos - 1] == Byte_ascii.Slash) {	// adjust if inline
    +					--atrs_end;
    +					inline = true;
    +				}
    +				break;
    +		}
    +		Mwh_atr_itm[] atrs = null;
    +		if (ctx.Parse_tid() == Xop_parser_tid_.Tid__wtxt) {
    +			atrs = ctx.App().Parser_mgr().Xnde__parse_atrs(src, atrs_bgn, atrs_end);
    +		}
    +		if ((	(	tag.Xtn() 
    +				&&	(	ctx.Parse_tid() != Xop_parser_tid_.Tid__defn	// do not gobble up rest if in tmpl; handle {{{1}}}; DATE:2014-03-03
    +					||	tag.Xtn_skips_template_args()					// ignore above if tag specifically skips template args; EX: 
    ; DATE:2014-04-10
    +					)
    +				)
    +			||	(force_xtn_for_nowiki && !inline)
    +			)
    +			)	{
    +			return Make_xnde_xtn(ctx, tkn_mkr, root, src, src_len, tag, bgn_pos, gtPos + 1, name_bgn, name_end, atrs_bgn, atrs_end, atrs, inline, pre2_hack);	// find end tag and do not parse anything inbetween
    +		}
    +		if (tag.Restricted()) {
    +			Xoae_page page = ctx.Page();
    +			if (	page.Html_data().Html_restricted() 
    +				&&	page.Wiki().Domain_tid() != Xow_domain_tid_.Tid__home) {
    +				int end_pos = gtPos + 1;
    +				ctx.Subs_add(root, tkn_mkr.Bry_raw(bgn_pos, end_pos, Bry_.Add(Gfh_entity_.Lt_bry, Bry_.Mid(src, bgn_pos + 1, end_pos)))); // +1 to skip <
    +				return end_pos;
    +			}
    +		}
    +		int prv_acs = ctx.Stack_idx_find_but_stop_at_tbl(Xop_tkn_itm_.Tid_xnde);
    +		Xop_xnde_tkn prv_xnde = prv_acs == -1 ? null : (Xop_xnde_tkn)ctx.Stack_get(prv_acs); //(Xop_xnde_tkn)ctx.Stack_get_typ(Xop_tkn_itm_.Tid_xnde);
    +		int prv_xnde_tagId = prv_xnde == null ? Xop_tkn_itm_.Tid_null : prv_xnde.Tag().Id();
    +
    +		boolean tag_ignore = false;
    +		int tagId = tag.Id();
    +		if (tagId == Xop_xnde_tag_.Tid__table || tag.Tbl_sub()) {							// tbl tag; EX: 
    a
    ,," + , " " + , " " + , "
    , + Tblw_bgn(ctx, tkn_mkr, root, src, src_len, bgn_pos, gtPos + 1, tagId, atrs_bgn, atrs_end); + return gtPos + 1; + } + else if (prv_xnde_tagId == Xop_xnde_tag_.Tid__p && tagId == Xop_xnde_tag_.Tid__p) { + ctx.Msg_log().Add_itm_none(Xop_xnde_log.Auto_closing_section, src, bgn_pos, bgn_pos); + End_tag(ctx, root, prv_xnde, src, src_len, bgn_pos - 1, bgn_pos - 1, tagId, true, tag); + } + else if (tagId == prv_xnde_tagId && tag.Repeat_ends()) { // EX: "ab" -> "ab" + End_tag(ctx, root, prv_xnde, src, src_len, bgn_pos - 1, bgn_pos - 1, tagId, true, tag); + return gtPos + 1; + } + else if (tagId == prv_xnde_tagId && tag.Repeat_mids()) { // EX: "
  • a
  • b" -> "
  • a
  • b" + End_tag(ctx, root, prv_xnde, src, src_len, bgn_pos - 1, bgn_pos - 1, tagId, true, tag); + } + else if (tag.Single_only()) inline = true; //

    not allowed; convert
    to

    will be escaped + else if (tag.No_inline() && inline) { + Xop_xnde_tkn xnde_inline = Xnde_bgn(ctx, tkn_mkr, root, tag, Xop_xnde_tkn.CloseMode_open, src, bgn_pos, open_tag_end, atrs_bgn, atrs_end, atrs); + End_tag(ctx, root, xnde_inline, src, src_len, bgn_pos, gtPos, tagId, false, tag); + ctx.Msg_log().Add_itm_none(Xop_xnde_log.No_inline, src, bgn_pos, gtPos); + return gtPos + Int_.Offset_1; + } + Xop_xnde_tkn xnde = null; + xnde = Xnde_bgn(ctx, tkn_mkr, root, tag, inline ? Xop_xnde_tkn.CloseMode_inline : Xop_xnde_tkn.CloseMode_open, src, bgn_pos, open_tag_end, atrs_bgn, atrs_end, atrs); + if (!inline && tag.Bgn_mode() != Xop_xnde_tag_.Bgn_mode__inline) + ctx.Stack_add(xnde); + if (tag_ignore) + xnde.Tag_visible_(false); + if (tag.Empty_ignored()) ctx.Empty_ignored_y_(); + return open_tag_end; + } + private boolean Stack_find_xnde(Xop_ctx ctx, int cur_tag_id) { + int acs_end = ctx.Stack_len() - 1; + if (acs_end == -1) return false; + for (int i = acs_end; i > -1; i--) { + Xop_tkn_itm tkn = ctx.Stack_get(i); + switch (tkn.Tkn_tid()) { + case Xop_tkn_itm_.Tid_tblw_tb: // needed for badly formed tables;PAGE:ro.b:Pagina_principala DATE:2014-06-26 + case Xop_tkn_itm_.Tid_tblw_td: + case Xop_tkn_itm_.Tid_tblw_th: + case Xop_tkn_itm_.Tid_tblw_tc: // tables always reset tag_stack; EX: should close list; see Stamp Act 1765 + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "
  • should auto-close |-; EX:fr.wikipedia.org/wiki/Napoléon_Ier; DATE:2013-12-09 + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl + ( "{|" + , "|-" + , "" + , "" + , "|}" + ) + , String_.Concat_lines_nl + ( "
  • ; 2nd li is not nested in 1st + return false; + case Xop_tkn_itm_.Tid_xnde: + Xop_xnde_tkn xnde_tkn = (Xop_xnde_tkn)tkn; + int stack_tag_id = xnde_tkn.Tag().Id(); + if (cur_tag_id == Xop_xnde_tag_.Tid__li) { + switch (stack_tag_id) { + case Xop_xnde_tag_.Tid__ul: // ul / ol resets tag_stack for li; EX:
    • ; 2nd li is not nested in 1st + case Xop_xnde_tag_.Tid__ol: + return false; + } + } + if (stack_tag_id == cur_tag_id) return true; + break; + } + } + return false; + } + private void Tblw_bgn(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, int tagId, int atrs_bgn, int atrs_end) { + byte wlxr_type = 0; + switch (tagId) { + case Xop_xnde_tag_.Tid__table: wlxr_type = Xop_tblw_wkr.Tblw_type_tb; break; + case Xop_xnde_tag_.Tid__tr: wlxr_type = Xop_tblw_wkr.Tblw_type_tr; break; + case Xop_xnde_tag_.Tid__td: wlxr_type = Xop_tblw_wkr.Tblw_type_td; break; + case Xop_xnde_tag_.Tid__th: wlxr_type = Xop_tblw_wkr.Tblw_type_th; break; + case Xop_xnde_tag_.Tid__caption: wlxr_type = Xop_tblw_wkr.Tblw_type_tc; break; + } + ctx.Tblw().Make_tkn_bgn(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, true, wlxr_type, Xop_tblw_wkr.Called_from_general, atrs_bgn, atrs_end); + } + private void Tblw_end(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, int tagId) { + int typeId = 0; + byte wlxr_type = 0; + switch (tagId) { + case Xop_xnde_tag_.Tid__table: typeId = Xop_tkn_itm_.Tid_tblw_tb; wlxr_type = Xop_tblw_wkr.Tblw_type_tb; break; + case Xop_xnde_tag_.Tid__tr: typeId = Xop_tkn_itm_.Tid_tblw_tr; wlxr_type = Xop_tblw_wkr.Tblw_type_tr; break; + case Xop_xnde_tag_.Tid__td: typeId = Xop_tkn_itm_.Tid_tblw_td; wlxr_type = Xop_tblw_wkr.Tblw_type_td; break; + case Xop_xnde_tag_.Tid__th: typeId = Xop_tkn_itm_.Tid_tblw_th; wlxr_type = Xop_tblw_wkr.Tblw_type_th; break; + case Xop_xnde_tag_.Tid__caption: typeId = Xop_tkn_itm_.Tid_tblw_tc; wlxr_type = Xop_tblw_wkr.Tblw_type_tc; break; + } + Xop_tblw_tkn prv_tkn = ctx.Stack_get_tbl(); + int prv_tkn_typeId = prv_tkn == null ? -1 : prv_tkn.Tkn_tid(); + ctx.Tblw().Make_tkn_end(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, typeId, wlxr_type, prv_tkn, prv_tkn_typeId, true); +// ctx.Para().Process_block__bgn_n__end_y(ctx, root, src, bgn_pos, cur_pos); + } + private int Make_xtag_end(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos, Xop_xnde_tag end_tag) { + int end_tag_id = end_tag.Id(); + cur_pos = Bry_find_.Find_fwd_while_not_ws(src, cur_pos, src_len) + 1; + int prv_xnde_pos = ctx.Stack_idx_find_but_stop_at_tbl(Xop_tkn_itm_.Tid_xnde); // find any previous xnde on stack + Xop_xnde_tkn bgn_nde = (Xop_xnde_tkn)ctx.Stack_get(prv_xnde_pos); + int bgn_tag_id = bgn_nde == null ? -1 : bgn_nde.Tag().Id(); + + int end_nde_mode = end_tag.End_mode(); + boolean force_end_tag_to_match_bgn_tag = false; + switch (bgn_tag_id) { + case Xop_xnde_tag_.Tid__sub: if (end_tag_id == Xop_xnde_tag_.Tid__sup) force_end_tag_to_match_bgn_tag = true; break; + case Xop_xnde_tag_.Tid__sup: if (end_tag_id == Xop_xnde_tag_.Tid__sub) force_end_tag_to_match_bgn_tag = true; break; + case Xop_xnde_tag_.Tid__mark: if (end_tag_id == Xop_xnde_tag_.Tid__span) force_end_tag_to_match_bgn_tag = true; break; + case Xop_xnde_tag_.Tid__span: if (end_tag_id == Xop_xnde_tag_.Tid__font) force_end_tag_to_match_bgn_tag = true; break; + } + if (force_end_tag_to_match_bgn_tag) { + end_tag_id = bgn_tag_id; + ctx.Msg_log().Add_itm_none(Xop_xnde_log.Sub_sup_swapped, src, bgn_pos, cur_pos); + } + if (end_tag_id == Xop_xnde_tag_.Tid__table || end_tag.Tbl_sub()) { + Tblw_end(ctx, tkn_mkr, root, src, src_len, bgn_pos, cur_pos, end_tag_id); + return cur_pos; + } + if (end_tag.Empty_ignored() && ctx.Empty_ignored() // emulate TidyHtml logic for pruning empty tags; EX: "
    • " -> "") + && bgn_nde != null) { // bgn_nde will be null if only end_nde; EX:WP:Sukhoi Su-47; "* " + ctx.Empty_ignore(root, bgn_nde.Tkn_sub_idx()); + End_tag(ctx, root, bgn_nde, src, src_len, bgn_pos, cur_pos, end_tag_id, true, end_tag); + return cur_pos; + } + switch (end_nde_mode) { + case Xop_xnde_tag_.End_mode__inline: // PATCH.WP: allows
      ,
      and many other variants + Xnde_bgn(ctx, tkn_mkr, root, end_tag, Xop_xnde_tkn.CloseMode_inline, src, bgn_pos, cur_pos, Int_.Min_value, Int_.Min_value, null); // NOTE: atrs is null b/c
      will never have atrs + return cur_pos; + case Xop_xnde_tag_.End_mode__escape: // handle + ctx.Lxr_make_(false); + ctx.Msg_log().Add_itm_none(Xop_xnde_log.Escaped_xnde, src, bgn_pos, cur_pos - 1); + return cur_pos; + } + if (prv_xnde_pos != Xop_ctx.Stack_not_found) { // something found + if (bgn_tag_id == end_tag_id) { // end_nde matches bgn_nde; normal; + End_tag(ctx, root, bgn_nde, src, src_len, bgn_pos, cur_pos, end_tag_id, true, end_tag); + return cur_pos; + } + else { + if (Stack_find_xnde(ctx, end_tag_id)) { // end_tag has bgnTag somewhere in stack; + int end = ctx.Stack_len() - 1; + for (int i = end; i > -1; i--) { // iterate stack and close all nodes until bgn_nde that matches end_nde + Xop_tkn_itm tkn = ctx.Stack_get(i); + if (tkn.Tkn_tid() == Xop_tkn_itm_.Tid_xnde) { + Xop_xnde_tkn xnde_tkn = (Xop_xnde_tkn)tkn; + End_tag(ctx, root, xnde_tkn, src, src_len, bgn_pos, bgn_pos, xnde_tkn.Tag().Id(), false, end_tag); + ctx.Stack_pop_idx(i); + if (xnde_tkn.Tag().Id() == end_tag_id) { + xnde_tkn.Src_end_(cur_pos); + return cur_pos; + } + else + ctx.Msg_log().Add_itm_none(Xop_xnde_log.Auto_closing_section, src, bgn_nde.Src_bgn(), bgn_nde.Name_end()); + } + else + ctx.Stack_auto_close(root, src, tkn, bgn_pos, cur_pos, Xop_tkn_itm_.Tid_xnde); + } + } + } + } + if (end_tag.Restricted()) // restricted tags (like ", "<script src='a'>b</script>"); + } + @Test public void Script_in_syntaxhighlight() { + fxt.Test_parse_page_all_str("", "
      <script>alert('fail');</script>
      "); + } + @Test public void Html5_time() {// PURPOSE: HTML5; should output self (i.e.: must be whitelisted) + fxt.Test_parse_page_wiki_str("", ""); + } + @Test public void Html5_bdi() {// PURPOSE: HTML5; should output self (i.e.: must be whitelisted); DATE:2013-12-07 + fxt.Test_parse_page_wiki_str("a", "a"); + } + @Test public void Html5_mark() {// PURPOSE: HTML5; should output self (i.e.: must be whitelisted); DATE:2014-01-03 + fxt.Test_parse_page_wiki_str("a", "a"); + } + @Test public void Html5_mark_span() {// PURPOSE: should close tag; EX: zh.wikipedia.org/wiki/异体字; DATE:2014-01-03 + fxt.Test_parse_page_wiki_str("a", "a"); + } + @Test public void Html5_wbr() {// PURPOSE: HTML5; should output self (i.e.: must be whitelisted); DATE:2014-01-03 + fxt.Test_parse_page_wiki_str("abc", "abc"); + } + @Test public void Html5_bdo() {// PURPOSE: HTML5; should output self (i.e.: must be whitelisted); DATE:2014-01-03 + fxt.Test_parse_page_wiki_str("a", "a"); + } + @Test public void Pre_always_parsed() { // PURPOSE: pre should not interpret templates; DATE:2014-04-10 + fxt.Init_defn_clear(); + fxt.Init_defn_add("a", "a"); + fxt.Init_defn_add("test", "
      {{a}}
      "); + fxt.Test_parse_page_all_str("{{test}}", "
      {{a}}
      "); + fxt.Init_defn_clear(); + } + @Test public void Quote() {// PURPOSE: handle element; DATE:2015-05-29 + fxt.Test_parse_page_wiki_str("a", "a"); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__blockquote_tst.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__blockquote_tst.java index a27517de8..0fd5ba7ad 100644 --- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__blockquote_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__blockquote_tst.java @@ -13,3 +13,46 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_xnde_wkr__blockquote_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @After public void term() {fxt.Init_para_n_();} + @Test public void Pre() { // PURPOSE: preserve leading spaces within blockquote; PAGE:en.w:Tenerife_airport_disaster + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "
      " + , " a" + , "
      " + ), String_.Concat_lines_nl_skip_last + ( "
      " + , " a" + , "
      " + )); + fxt.Init_para_n_(); + } + @Test public void Trailing_nls() { // PURPOSE: para/pre not working after blockquote; PAGE:en.w:Snappy_(software); DATE:2014-04-25 + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "
      a" + , "
      " + , "" + , "b" + , "" + , " c" + ), String_.Concat_lines_nl_skip_last + ( "
      a" + , "
      " + , "" + , "

      b" + , "

      " + , "" + , "
      c"
      +		, "
      " + )); + fxt.Init_para_n_(); + } + @Test public void Dangling_multiple() { // PURPOSE: handle multiple dangling; PAGE:en.w:Ring_a_Ring_o'_Roses DATE:2014-06-26 + fxt.Test_parse_page_wiki_str("
      a
      b", "
      a
      b
      "); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__err_dangling_tst.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__err_dangling_tst.java index a27517de8..fbf199589 100644 --- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__err_dangling_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__err_dangling_tst.java @@ -13,3 +13,192 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; import gplx.xowa.parsers.lists.*; +public class Xop_xnde_wkr__err_dangling_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @After public void term() {fxt.Init_para_n_();} + @Test public void Basic() { + fxt.Init_log_(Xop_xnde_log.Dangling_xnde) + .Test_parse_page_wiki("
      ", fxt.tkn_xnde_(0, 5)); + } + @Test public void Many() { + fxt.Init_log_(Xop_xnde_log.Dangling_xnde, Xop_xnde_log.Dangling_xnde, Xop_xnde_log.Dangling_xnde) + .Test_parse_page_wiki("
      ", fxt.tkn_xnde_(0, 15).Subs_(fxt.tkn_xnde_(5, 15).Subs_(fxt.tkn_xnde_(10, 15)))); + } + @Test public void Nested() { + fxt.Test_parse_page_wiki_str + ( "
      a
      " + , "
      a
      " + ); + } + @Test public void Center() { + fxt.Init_log_(Xop_xnde_log.Dangling_xnde).Test_parse_page_wiki("a
      b" + , fxt.tkn_txt_(0, 1) + , fxt.tkn_xnde_(1, 10).CloseMode_(Xop_xnde_tkn.CloseMode_open).Subs_(fxt.tkn_txt_(9, 10)) + ); + } + @Test public void P() { + fxt.Init_log_(Xop_xnde_log.Auto_closing_section).Test_parse_page_wiki("a

      b

      c

      " + , fxt.tkn_txt_ (0, 1) + , fxt.tkn_xnde_ (1, 4).Subs_(fxt.tkn_txt_(4, 5)) + , fxt.tkn_xnde_ (5, 13).Subs_(fxt.tkn_txt_(8, 9)) + ); + } + @Test public void Alternating() { // PURPOSE: confirmation test for alternating dangling nodes; PAGE:en.w:Portal:Pornography/Selected_historical_image/Archive; DATE:2014-09-24 + fxt.Test_parse_page_wiki_str + ( "cde" + , "cde" + ); + } + @Test public void Li() { // PURPOSE: auto-close
    • ; NOTE: no longer encloses in
        ; DATE:2014-06-26 + fxt.Test_parse_page_wiki_str + ( "
      • a
      • b" + , String_.Concat_lines_nl_skip_last + ( "
      • a
      • " + , "
      • b
      • " + )); + } + @Test public void Br() { + fxt.Test_parse_page_wiki("
        a" , fxt.tkn_xnde_(0, 4), fxt.tkn_txt_(4, 5)); + fxt.Test_parse_page_wiki("a
        c", fxt.tkn_txt_(0, 1), fxt.tkn_xnde_(1, 12), fxt.tkn_txt_(12, 13)); + } + @Test public void Td_and_td() { // PURPOSE: when "
  • a", 2nd should auto-close + fxt.Test_parse_page_wiki("
    a
    b
    " + , fxt.tkn_tblw_tb_(0, 52).Subs_ + ( fxt.tkn_tblw_tr_(7, 25).Subs_ + ( fxt.tkn_tblw_td_(11, 16).Subs_(fxt.tkn_txt_(15, 16)) // FUTURE: change to 11,20 + , fxt.tkn_tblw_td_(16, 25) // FUTURE: change this to 16, 20 + ) + , fxt.tkn_tblw_tr_(25, 44).Subs_ + ( fxt.tkn_tblw_td_(29, 39).Subs_(fxt.tkn_txt_(33, 34)) + ) + ) + ); + } + @Test public void Tblw_and_tr() {// PURPOSE:
    row1
    row2
    " + , " " + , " " + , " " + , " " + , " " + , " " + , "
    row1" + , "
    row2" + , "
    " + ) + ); + } + @Test public void Tblx_and_b() { + fxt.Init_log_(Xop_xnde_log.Dangling_xnde).Test_parse_page_wiki("
    a
    " + , fxt.tkn_tblw_tb_(0, 36).Subs_ + ( fxt.tkn_tblw_tr_(7, 28).Subs_ + ( fxt.tkn_tblw_td_(11, 19).Subs_ // FUTURE: change to 11,23 + ( fxt.tkn_xnde_(15, 36).Subs_(fxt.tkn_txt_(18, 19)) // FUTURE: should be 19, but xnde.Close() is passing in src_len + ) + , fxt.tkn_tblw_td_(19, 28) // FUTURE: should be 23 + ) + ) + ); + } + @Test public void Tblx_and_li() { // PURPOSE:
  • " + , "
    " + , "*abc
    bcd
    " + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , " " + , " " + , "
    " + , "" + , "
      " + , "
    • abc" + , "
    • " + , "
    " + , "
    bcd" + , "
    " + , "" + ) + ); + fxt.Init_para_n_(); + } + @Test public void Tblx_and_small() { // PURPOSE: should close correctly; see Stamp Act 1765 + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "" + , "
    " + , "abc
    bcd
    " + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , " " + , " " + , "
    " + , "abc" + , "
    bcd" + , "
    " + , "" + ) + ); + fxt.Init_para_n_(); + } + @Test public void Blockquote_and_p() { + fxt.Init_log_(Xop_xnde_log.Auto_closing_section).Test_parse_page_wiki("
    a

    b

    " + , fxt.tkn_xnde_(0, 30).Subs_ + ( fxt.tkn_txt_(12, 13) + , fxt.tkn_xnde_(13, 17).Subs_(fxt.tkn_txt_(16, 17)) + )); + } + @Test public void List_and_b() { + fxt.Init_log_(Xop_xnde_log.Dangling_xnde).Test_parse_page_wiki("*a\n*" + , fxt.tkn_list_bgn_(0, 1, Xop_list_tkn_.List_itmTyp_ul).List_path_(0) + , fxt.tkn_xnde_(1, 7).Subs_(fxt.tkn_txt_(4, 5)) + , fxt.tkn_list_end_(5).List_path_(0) + , fxt.tkn_list_bgn_(5, 7, Xop_list_tkn_.List_itmTyp_ul).List_path_(1) + , fxt.tkn_list_end_(7).List_path_(1) + ); + } + @Test public void Underline() { // PURPOSE: 2nd should auto-close; PAGE:en.b:Textbook_of_Psychiatry/Alcoholism_and_Psychoactive_Substance_Use_Disorders DATE:2014-09-05 + fxt.Test_html_full_str("abc", "abc"); + } + @Test public void Xtn_template() { // PURPOSE: dangling xtns within templates should be auto-closed inside template, not in calling page; PAGE:en.w:Provinces_and_territories_of_Canada DATE:2014-11-13 + fxt.Init_page_create("Template:A", "A"); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "{{A}}" + , " b" // poem should not extend to " b" + ), String_.Concat_lines_nl_skip_last + ( "
    " + , "

    " + , "A" + , "

    " + , "
    " // poem ends here + , " b" + )); + } + @Test public void Section_inline() { // PURPOSE.FIX:do not output trailing '' after '' PAGE:en.w:National_Popular_Vote_Interstate_Compact DATE:2017-04-10 + fxt.Init_page_create("Template:Abc", "
    val
    "); + fxt.Test_parse_page_tmpl_str(String_.Concat_lines_nl_skip_last + ( "{{Abc}}" + ), String_.Concat_lines_nl_skip_last + ( "
    val
    " // fails if "
    val
    " + )); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__err_malformed_tst.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__err_malformed_tst.java index a27517de8..6043ba927 100644 --- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__err_malformed_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__err_malformed_tst.java @@ -13,3 +13,60 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_xnde_wkr__err_malformed_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @After public void term() {fxt.Init_para_n_();} + @Test public void Lt_only() { + fxt.Test_parse_page_wiki("<", fxt.tkn_txt_(0, 1)); + } + @Test public void Eos_while_closing_tag() { + fxt.Init_log_(Xop_xnde_log.Eos_while_closing_tag).Test_parse_page_wiki("

    ; DATE:2014-01-18 + fxt.Wiki().Xtn_mgr().Init_by_wiki(fxt.Wiki()); + fxt.Test_parse_page_all_str("

    ", String_.Concat_lines_nl_skip_last + ( "

    " + , "

    " // NOTE: technically MW / WP does not add this

    ; however, easier to hardcode

    ; no "visual" effect; DATE:2014-04-27 + , "

    </p<

    " + , "

    " + , "
    " + )); + } + @Test public void Incomplete_tag_div() { // PURPOSE: handle broken tags; EX:
    -> <div a; DATE:2014-02-03 + fxt.Test_parse_page_all_str("
    ", "<div a
    "); // note that "
    " is literally printed; // TIDY.dangling: tidy will correct dangling node; DATE:2014-07-22 + } + @Test public void Incomplete_tag_ref() {// PURPOSE: invalid tag shouldn't break parser; EX:w:Cullen_(surname); "http://www.surnamedb.com/Surname/Cullen to be ; EX: w:Exchange_value + fxt.Init_log_(Xop_xnde_log.No_inline); + fxt.Test_parse_page_all_str("", ""); + } + @Test public void Tblw() { // PURPOSE.fix: don't auto-close past tblw PAGE:ro.b:Pagina_principala DATE:2014-06-26 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "
    " + , "{|" // this should stop xnde search + , "
    " + , "
    " // this should not find
    as its bgn_tag; note that it will "drop out" below + , "|}" + , "
    " + ), String_.Concat_lines_nl_skip_last + ( "
    " + , "
    " // TIDY.dangling: tidy will correct dangling node; DATE:2014-07-22 + , "
    " + , " " + , " " + , "" + , "
    " + , "
    " + , "
    " + )); + } + @Test public void Incomplete_tag() { // PURPOSE: handle incomplete tag sequences; DATE:2014-10-22 + fxt.Test_parse_page_all_str("<", "<"); + fxt.Test_parse_page_all_str("" + , "
  • a
    b" + , "
    " + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    a
    b" + , "
    " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Div_should_not_pop_past_td() { // PURPOSE: extra
    should not close
    that is outside of ; PAGE:en.w:Rome en.w:Ankara + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "" + , "" + , "" + , "" + , "
    " + , "
    " // this is
    #1 + , "" + , "" + , "" + , "" + , "" + , "" + , "
    " + , "
    " // this is
    #2 + , "
    " + , "a" + , "
    " + , "
    " + , "
    " + , "b" + , "
    " + , "" // this was supposed to pop
    #2, but can't (b/c of HTML rules); however, do not try to pop
    #1; + , "
    " + , "
    " + , "c" + , "
    " + , "
    " + , "
    " + , "
    " + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    " + , "
    " + , " " + , " " + , " " + , " " + , " " + , " " + , "
    " + , "
    " + , "
    " + , "" + , "

    a" + , "

    " + , "
    " + , "
    " + , "
    " + , "
    " + , "" + , "

    b" + , "

    " + , "
    " + , "" // TIDY.dangling: tidy will correct dangling node; DATE:2014-07-22 + , "
    " + , "
    " + , "" + , "

    c" + , "

    " + , "
    " + , "
    " + , "
    " + , "
    " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Xnde_pops() { // PURPOSE: somehow xnde pops upper nde; PAGE:en.w:Greek government debt crisis; "History of government debt" + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "" + , "{|" + , "|-" + , "|a" + , "|}" + , "" + ), String_.Concat_lines_nl_skip_last + ( "" + , "" + , " " + , " " + , " " + , "
    a" + , "
    " + , "
    " + )); + } + @Test public void Err_inline_extension() { + fxt.Test_parse_page_all_str + ( "" + , "" + ); + } + @Test public void Xnde_para() { // PURPOSE: buggy code caused

    to close everything; keeping test b/c of

    logic + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "" + , "" + , "" + , "" + , "
    " + , "
    " + , "

    " + , "" + , "" + , "

    " + , "
    " + , "
    " + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
    " + , "
    " + , "

    " + , "" + , "" + , "

    " + , "
    " + , "
    " + , "" + ) + ); + } + @Test public void Sup_bug() { // PURPOSE: occurred at ref of UK; a {{cite web|url=http://www.abc.gov/{{dead link|date=December 2011}}|title=UK}} b + fxt.Test_parse_page_wiki_str("x y z", "x y z"); + } + @Test public void Br_backslash() { // PURPOSE: allow ; EX:w:Mosquito; [[Acalyptratae|Acalyptratae]] + fxt.Test_parse_page_all_str("", "
    "); + } + @Test public void Tt_does_not_repeat() { // PURPOSE: handle a; EX:w:Domain name registry + fxt.Test_parse_page_all_str("a", "a"); + } + @Test public void Loose_xnde_names() { // PURPOSE: MW allows and other variations; EX:w:2012_in_film + fxt.Test_parse_page_all_str("a", "a"); + } + @Test public void Anchor_nested() { + fxt.Test_parse_page_all_str("bcd [[e]] f", "b<a>c<a>d e f"); + } + @Test public void Img_should_not_be_xtn() { // PURPOSE: marked as .xtn; unclosed was escaping rest of text; PAGE:de.w:Wikipedia:Technik/Archiv/2014 DATE:2014-11-06 + fxt.Test_parse_page_all_str("''a''", "<img>a"); + } + @Test public void Invalid__percent() { // PURPOSE: invalidate xml tags with %; EX:; PAGE:pl.w:Scynk_nadrzewny; DATE:2016-08-07 + fxt.Test_parse_page_all_str("a
    ", "<b%>a
    "); // NOTE: should be literally printed as , not transformed to + } + @Test public void Meta_link() { // PURPOSE: meta and link tags should not print; EX: ; PAGE:fr.s:La_Dispute; DATE:2017-05-28 + fxt.Test_parse_page_all_str("", ""); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__include_basic_tst.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__include_basic_tst.java index a27517de8..0326a5c5e 100644 --- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__include_basic_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__include_basic_tst.java @@ -13,3 +13,69 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_xnde_wkr__include_basic_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Before public void init() {fxt.Reset();} + @Test public void Tmpl_includeonly() {fxt.Test_parse_tmpl_str_test("abc" , "{{test}}", "abc");} + @Test public void Tmpl_noinclude() {fxt.Test_parse_tmpl_str_test("abc" , "{{test}}", "ac");} + @Test public void Tmpl_onlyinclude() {fxt.Test_parse_tmpl_str_test("abc" , "{{test}}", "b");} + @Test public void Tmpl_onlyinclude_nest() {fxt.Test_parse_tmpl_str_test("{{#ifeq:y|y|abc|n}}" , "{{test}}", "b");} // PURPOSE: check that onlyinclude handles (a) inside {{#if}} function (old engine did not); and (b) that abc are correctly added together + @Test public void Tmpl_onlyinclude_page() {// PURPOSE: handle scenario similar to {{FA Number}} where # of articles is buried in page between onlyinclude tags; added noinclude as additional stress test + fxt.Init_page_create("Transclude_1", "abcd"); + fxt.Test_parse_tmpl_str_test("{{:Transclude_1}}" , "{{test}}", "b"); + } + @Test public void Tmpl_onlyinclude_page2() { // PURPOSE: handle scenario similar to PS3 wherein onlyinclude was being skipped (somewhat correctly) but following text (

    ) was also included
    +		fxt.Init_page_create("Transclude_2", "abcde
    f
    g"); + fxt.Test_parse_tmpl_str_test("{{:Transclude_2}}" , "{{test}}", "bcd"); + } + @Test public void Tmpl_noinclude_unmatched() { // PURPOSE.fix: ignore unmatched ; EX:fi.w:Sergio_Leone; DATE:2014-05-02 + fxt.Test_parse_tmpl_str_test("{{{1|}}}", "{{test|a}}", "a"); // was "{{{test|" + } + + @Test public void Wiki_includeonly() {fxt.Test_parse_page_all_str("abc" , "ac");} + @Test public void Wiki_noinclude() {fxt.Test_parse_page_all_str("abc" , "abc");} + @Test public void Wiki_onlyinclude() {fxt.Test_parse_page_all_str("abc" , "abc");} + @Test public void Wiki_oi_io() {fxt.Test_parse_page_all_str("abcde" , "abde");} + @Test public void Wiki_oi_io_tblw() { + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "" + , "{|" + , "|-" + , "|a" + , "|}" + , "|-" + , "|b" + , "|}" + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , " " + , " " + , "
    a" + , "
    b" + , "
    " + , "" + )); + } +} +/* +-({{{1}}}={{{1}}}round-5)-({{{1}}}={{{1}}}round-4)-({{{1}}}={{{1}}}round-3)-({{{1}}}={{{1}}}round-2)-({{{1}}}={{{1}}}round-1) +{{pp-template}}Called by {{lt|precision/0}} + +==includeonly -- aka: do not eval in template == +main: abc
    +tmpl: {{mwo_include_only|a|b|c}} + +==noinclude -- aka: eval in template only== +main: abc
    +tmpl: {{mwo_no_include|a|b|c}} + +==onlyinclude -- aka: only include in template only (ignore everything else) == +main: abc
    +tmpl: {{mwo_only_include|a|b|c}} +*/ \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__include_uncommon_tst.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__include_uncommon_tst.java index a27517de8..2c4ea782c 100644 --- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__include_uncommon_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__include_uncommon_tst.java @@ -13,3 +13,180 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_xnde_wkr__include_uncommon_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @Before public void init() {fxt.Reset();} + @Test public void Ex_Tmpl_io_oi() { // PURPOSE: not parsing internals; PAGE:en.w:[[Template:MONTHNAME]] + fxt.Test_parse_tmpl_str_test("{{#if:{{{1}}}|a|b}}c", "{{test|1}}", "a"); + } + @Test public void Ex_Tmpl_io_subst() { // PURPOSE: and @gplx.Internal protected subst; PAGE:en.w:[[Template:Dubious]] + fxt.Init_defn_clear(); + fxt.Init_defn_add("mwo_print", "{{{1}}}"); + fxt.Init_defn_add("substcheck", "SUBST"); + fxt.Test_parse_tmpl_str_test(String_.Concat_lines_nl_skip_last + ( "{{mwo_print" + , "|{{subst:substcheck}}" + , "}}" + ), "{{test}}" + , "{{subst:substcheck}}\n" + ); + fxt.Reset(); + fxt.Test_parse_tmpl_str_test(String_.Concat_lines_nl_skip_last + ( "{{mwo_print" + , "|{{safesubst:substcheck}}" + , "}}" + ), "{{test}}" + , "SUBST\n"); + fxt.Init_defn_clear(); + } + @Test public void Ex_Tmpl_noinclude_prm_1() { // PURPOSE: should not process @gplx.Internal protected tkns; PAGE:en.w:[[Template:See]] + fxt.Init_defn_clear(); + fxt.Init_defn_add("mwo_print", "{{{1}}}{{{2}}}"); + fxt.Test_parse_tmpl_str_test + ( "{{mwo_print|{{{1|not_seen}}}|{{{2}}}}}" + , "{{test|a|b}}" + , "ab" + ); + fxt.Init_defn_clear(); + } + @Test public void Ex_Tmpl_noinclude_prm_2() { // PURPOSE: should not process default tkn; + fxt.Test_parse_tmpl_str_test + ( "{{#if: {{{x|y}}} | visible | hidden}}" // {{#if: {{{x|y}}} -> {{#if: {{{x|}} -> hidden + , "{{test}}" + , "hidden" + ); + } + @Test public void Ex_Tmpl_noinclude2() { // PURPOSE: should be separate from tkns {{convert|50|km|0|abbr=on}} + fxt.Init_defn_clear(); + fxt.Init_defn_add("mwo_print", "{{{1}}}{{{2}}}"); + fxt.Test_parse_tmpl_str_test + ( "{{mwo_print{{{?}}}|a|b}}" + , "{{test}}" + , "ab" + ); + fxt.Init_defn_clear(); + } + @Test public void Exception_incompleteTag_matchNext() { // PURPOSE: "bde" + , "{{test}}" + , "ae" + ); + } + @Test public void Exception_noCloseTag() { + fxt.Test_parse_tmpl_str_test + ( "abcde" + , "{{test}}" + , "a" + ); + } + @Test public void Exception_inline() { + fxt.Test_parse_tmpl_str_test + ( "abcde" + , "{{test}}" + , "abcde" + ); + } + @Test public void Exception_inline_2() { + fxt.Test_parse_tmpl_str_test + ( "abcde" + , "{{test}}" + , "abcde" + ); + } + @Test public void Defect_onlyinclude_inside_template() { // PURPOSE: was eating up next template; PAGE:en.w:Wikipedia:Featured_articles + fxt.Test_parse_page_all_str + ( "{{formatnum: 1}} {{formatnum:2}}" + , "1 2" + ); + } + @Test public void Only_include_preserves_nl() { // PURPOSE: given "a\n{|\n", "{|" should be table; PAGE:en.w:Wikipedia:Reference_desk + fxt.Test_parse_page_all_str(String_.Concat_lines_nl + ( "a" + , "==b==" + , "c" + ) +// , "{{test}}" + , String_.Concat_lines_nl + ( "a" + , "" + , "

    b

    " + , "c" + )); + } + @Test public void Only_include_interprets_template() { // PURPOSE: should interpret templates + fxt.Init_defn_clear(); + fxt.Init_defn_add("test", "see_me"); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl + ( "a" + , "{{test}}" + , "c" + ) + , String_.Concat_lines_nl + ( "a" + , "see_me" + , "c" + )); + } + @Test public void Include_only_in_template_name() {// PURPOSE: includeonly in tmpl_name should be ignored; EX:de.w:Wikipedia:Projektdiskussion; DATE:2014-01-24 + fxt.Init_defn_clear(); + fxt.Init_defn_add("test", "abc"); + fxt.Test_parse_page_all_str("{{test}}", "abc"); + } + @Test public void Include_only_in_transcluded_page() {// PURPOSE: include only in transcluded page should be ignored; EX:de.w:Wikipedia:Projektdiskussion; DATE:2014-01-24; DATE:2014-05-10 + fxt.Init_page_create("page", "abc"); // create page in main ns + fxt.Test_parse_page_all_str("{{:safesubst:page}}", "abc"); // will become {{:page}} which should then transclude page + } + @Test public void Include_only_subst_in_function() {// PURPOSE: includeonly and subst inside function should be ignored; PAGE:en.w:WikiProject_Articles_for_creation/BLD_Preload; DATE:2014-04-29 + fxt.Test_parse_page_all_str("{{subst:#expr:0}}", "0"); + } + @Test public void Hdr() { // PURPOSE: includeonly should be evaluated during template parse; EX: es.b:Billar/T�cnica/Clases_de_puentes; DATE:2014-02-12 + fxt.Test_parse_page_all_str("==A==", "

    A

    \n"); + } +// @Test public void Noinclude_nested() { // PURPOSE: nested noincludes don't work; th.w:ISO_3166-1;DATE:2014-04-06 +// fxt.Init_defn_clear(); +// fxt.Init_defn_add("test", "abcde"); +// fxt.Test_parse_page_all_str("{{test}}", "ae"); +// } + +// @Test public void Wiki_includeonly_ignore() {fxt.Test_parse_wiki_text("[[abc]]", "[[ac]]");} // FUTURE: ttl parses by idx, and ignores includeonly: WHEN: upon encountering; may need to redo in other parsers? + @Test public void Defect_noinclude_inside_main() { // PURPOSE: inside main was not returning content; PAGE:en.w:Wikipedia:Featured_articles + fxt.Init_defn_clear(); + fxt.Init_defn_add("Test_tmpl", "{{:Test_page}}"); + fxt.Data_create("Test_page", "a{{#expr:1}}c"); + fxt.Test_parse_page_all_str + ( "{{Test_tmpl}}" + , "1" + ); + fxt.Init_defn_clear(); + } + @Test public void Pre_and_includeonly() { // PAGE:https://en.wikipedia.org/wiki/BSD_licenses DATE:2014-05-23 + fxt.Init_defn_add("pre2", "
    >{{{1}}}
    "); + fxt.Test_parse_page_all_str + ( "{{pre2|a}}" + , String_.Concat_lines_nl_skip_last + ( "
    a
    " + )); + } +// @Test public void Pre_and_includeonly2() { +// fxt.Init_defn_add("pre2", ">{{{1}}}
    "); +// fxt.Test_parse_page_all_str +// ( "{{pre2|a}}" +// , String_.Concat_lines_nl_skip_last +// ( "
    a
    " +// )); +// } + @Test public void Noinclude_inline_w_space_inside_safesubst() { // PURPOSE: "" did not work with safesubst b/c of space; PAGE:en.w:Wikipedia:Featured_picture_candidates; DATE:2014-06-24 + fxt.Test_parse_tmpl_str_test("{{SAFESUBST:#if:val_exists|y|n}}", "{{test}}", "y"); + } + @Test public void Subst() {// PURPOSE: handle subst-includeonly-subst combination; PAGE:pt.w:Argentina DATE:2014-09-24 + fxt.Init_defn_clear(); + fxt.Init_defn_add("test", "{{subst:#switch:1|1=y|default=n}}"); + //fxt.Init_defn_add("test", "{{subst:#switch:1|1=y|default=n}}"); // keeping around for debugging purposes + //fxt.Init_defn_add("test", "{{#switch:1|1=y|default=n}}"); // keeping around for debugging purposes + fxt.Test_parse_page_all_str("{{test}}", "{{subst:#switch:1|1=y|default=n}}"); // note that subst is preserved b/c of + fxt.Test_parse_page_all_str("{{subst:test}}", "y"); // note that expression is evaluated b/c of subst: + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__li_tst.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__li_tst.java index a27517de8..273e1becf 100644 --- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__li_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__li_tst.java @@ -13,3 +13,90 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_xnde_wkr__li_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @After public void term() {fxt.Init_para_n_();} + @Test public void Inside_tblx() { // PURPOSE: auto-close
  • (EX: "
  • a
  • ") was causing 3rd
  • to close incorrectly + fxt.Test_parse_page_wiki_str + ( "
    • a
    • b
    • c
    " + , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
      " + , "
    • a
    • " + , "
    • b
    • " + , "
    • c
    " + , "
    " + , "" + )); + } + @Test public void Li_nested_inside_ul() { // PURPOSE: nested li in ul should not be escaped; DATE:2013-12-04 + fxt.Test_parse_page_wiki_str + ( "
    • a
      • b
    " + , String_.Concat_lines_nl_skip_last + ( "
      " + , "
    • a
        " + , "
      • b
    " // note that
  • b becomes
  • <li>b but
    • b should stay the same + )); + } + @Test public void Empty_ignored() { + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • a" + , "
      • " + , "
      • b" + , "
      • " + , "
      " + ), String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • a" + , "
      • " + , "
      • b" + , "
      • " + , "
      " + )); + } + @Test public void Empty_ignored_error() { // PAGE:en.w:Sukhoi_Su-47; "*
    • " causes error b/c tries to close non-existent node + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "* a" + , "* " + ), String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • a" + , "
      • " + , "
      • " // TIDY.dangling: tidy will correct dangling node; DATE:2014-07-22 + , " " + , "
      " + )); + } + @Test public void Insert_nl() {// PURPOSE:
    • should always be separated by nl, or else items will merge, creating long horizontal scroll bar; EX:w:Music + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str("
      • a
      • b
      " + , String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • a
      • " + , "
      • b
      " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Duplicate() { // PURPOSE: redundant li; EX: "*
    • "; PAGE:it.w:Milano#Bibliographie; DATE:2013-07-23 + fxt.Test_parse_page_all_str("*
    • x
    • ", String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • " + , "
      • x
      • " // TIDY: duplicate li will be stripped out; DATE:2014-06-26 + , " " + , "
      " + )); + } + @Test public void Dangling_inside_xnde() { // PURPOSE.TIDY: handle "
    • a
    • b"; PAGE:ro.w:Pagina principala; DATE:2014-06-26 + fxt.Test_parse_page_all_str("
    • a
    • b", String_.Concat_lines_nl_skip_last + ( "
    • a" + , "
    • b
    • " // TIDY: will (a) move to 1st line + )); + } +} 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 a27517de8..95b1c5580 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 @@ -13,3 +13,130 @@ 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.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(); + @After public void term() {fxt.Init_para_n_();} + @Test public void Basic() { + fxt.Test_parse_page_wiki_str + ( "''a''b" + , "''a''b" + ); + } + @Test public void Template() { + fxt.Init_para_y_(); + fxt.Init_defn_add("nowiki_test", "#a"); + fxt.Test_parse_page_all_str + ( "{{nowiki_test}}" + , String_.Concat_lines_nl_skip_last + ( "

      #a" + , "

      " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void H2() { // PAGE:en.w:HTML + fxt.Test_parse_page_all_str + ( "a

      b

      c" + , String_.Concat_lines_nl_skip_last + ( "a<h1>b<h6>c" + )); + } + @Test public void Lnke() { // PAGE:en.w:Doomsday_argument; [0, 1] + fxt.Test_parse_page_wiki_str("a [0, 1] b", "a [0, 1] b"); // NOTE: not "0" + Byte_.To_str(160) + "1"; depend on browser to translate   + } + @Test public void Xatrs_val_text() { + fxt.Test_parse_page_all_str + ( "
      a>b
      " + , String_.Concat_lines_nl_skip_last + ( "
      b
      " + )); + } + @Test public void Xatrs_val_quote() { + fxt.Test_parse_page_all_str + ( "
      d
      " + , String_.Concat_lines_nl_skip_last + ( "
      d
      " + )); + } + @Test public void Xatrs_eq() { + fxt.Test_parse_page_all_str("
        =\"a\" class=\"b\">
      • d
      ", String_.Concat_lines_nl_skip_last + ( "
        " + , "
      • d
      " + )); + } + @Test public void Tblw_atr() {// PURPOSE: nowiki breaks token + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "{|style=\"background-color:#FFCC99\"" + , "|a" + , "|}" + ) , String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
      a" + , "
      " + , "" + )); + } + @Test public void Prex() { // PURPOSE: nowikis inside pre should be ignored; DATE:2013-03-30 + fxt.Test_parse_page_all_str("
      a<b
      " , "
      a<b
      "); // basic + fxt.Test_parse_page_all_str("
      a<b
      " , "
      a<nowiki><<nowiki>b
      "); // not closed + fxt.Test_parse_page_all_str("
      abc
      " , "
      <nowiki>abc</nowiki>
      "); // nested; this is wrong, but leave for now; should be abc + } + @Test public void Prew() { // PURPOSE: space inside nowiki should be ignored; ru.b:Rubyn DATE:2014-07-03 + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl + ( " a" + , " " // note that "\s" must remain "\s" so that
       continues uninterrupted
      +		), String_.Concat_lines_nl
      +		( "
      a"
      +		, "<b></b>"
      +		, "
      " + ) + ); + fxt.Init_para_n_(); + } + @Test public void Prew_2() { // PURPOSE: prew should continue over nowiki, even if no space DATE:2014-07-03 + fxt.Init_para_y_(); + fxt.Test_parse_page_all_str(String_.Concat_lines_nl + ( " a" + , "b" // note that "b" should be in pre b/c it is part of which is pre'd (even though there is no \n\s) + ), String_.Concat_lines_nl + ( "
      a"
      +		, "b"
      +		, "
      " + ) + ); + fxt.Init_para_n_(); + } + @Test public void Code() { // PURPOSE.fix:HtmlNcr-escaped refs were being ignored; caused by HtmlTidy fix for frwiki templates;DATE:2013-06-27 + fxt.Test_parse_page_all_str("|:", "|:"); + } + @Test public void Brack_end() { // PURPOSE: check that "]" is escaped; PAGE:en.w:Tall_poppy_syndrome; DATE:2014-07-23 + fxt.Test_parse_page_all_str + ( "[[[A]]]" + , "[A]"); // was showing up as [[[A]]] + } + @Test public void Tblw_tr() { // PURPOSE: dash should be escaped in nowiki PAGE:de.w:Liste_von_Vereinen_und_Vereinigungen_von_Gläubigen_(römisch-katholische_Kirche) DATE:2015-01-08 + fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last + ( "{|" + , "|-" + , "|a" + , "|-" // do not treat as "|-" + , "|}" + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , "
      a" + , " -" // "|" creates ; "-" is rendered literally + , "
      " + , "" + )); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__tblx_tst.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__tblx_tst.java index a27517de8..547d071de 100644 --- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__tblx_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__tblx_tst.java @@ -13,3 +13,66 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_xnde_wkr__tblx_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @After public void term() {fxt.Init_para_n_();} + @Test public void Table() { + fxt.Test_parse_page_wiki("a
      b
      c" + , fxt.tkn_txt_ ( 0, 1) + , fxt.tkn_tblw_tb_(1, 35).Subs_ + ( fxt.tkn_tblw_tr_(8, 27).Subs_ + ( fxt.tkn_tblw_td_(12, 22).Subs_(fxt.tkn_txt_(16, 17)) + ) + ) + , fxt.tkn_txt_ (35, 36) + ); + } + @Test public void Ws_bgn() { // PURPOSE: some templates return leading ws; PAGE:en.w:UK + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( " " + , " " + , " " + , " " + , "
      a" + , "
      " + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
      a" + , "
      " + , "" + )); + fxt.Init_para_n_(); + } + @Test public void Td_in_lnki_should_be_ignored() {// PURPOSE: \n| inside lnki should not be interpreted as table cell; EX: uk.w:Дніпро; DATE:2014-03-11 + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "
      " + , "[[File:A.png|150px" + , "|B]]
      " + ), String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , "
      " + , "\"B\"" + , "
      " + )); + fxt.Init_para_n_(); + } + @Test public void Nl() { + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str + ( "\n\n\n\n\n
      " + , "\n" + + "
      \n" + ); + fxt.Init_para_n_(); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__text_block_tst.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__text_block_tst.java index a27517de8..1cc42d491 100644 --- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__text_block_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__text_block_tst.java @@ -13,3 +13,65 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_xnde_wkr__text_block_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @After public void term() {fxt.Init_para_n_();} + @Test public void Source_wikitext() { // PURPOSE.ASSERT: wikitext should be rendered literally; DATE:2014-03-11 + fxt.Test_parse_page_wiki_str("''a''", "
      ''a''
      "); + } + @Test public void Source_nowiki() { // PURPOSE.ASSERT: onlyinclude should be rendered literally; DATE:2014-03-11 + fxt.Test_parse_page_wiki_str("a", "
      <onlyinclude>a</onlyinclude>
      "); + } + @Test public void Source_escape() { + fxt.Test_parse_page_wiki_str("", "
      <b>
      "); + } + @Test public void Source_escape_amp() { // PURPOSE: < should be rendered as &lt; PAGE:uk.b:HTML; DATE:2014-03-11 + fxt.Test_parse_page_wiki_str("<", "
      &lt;
      "); + } + @Test public void Source_pre() { // PURPOSE: handle pre; PAGE:en.w:Comment_(computer_programming); DATE:2014-06-23 + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( " " + , " a" + , " " + ), String_.Concat_lines_nl_skip_last + ( "
      "
      +		, "  a"
      +		, "
      " + )); + fxt.Init_para_n_(); + } + @Test public void Code_dangling() { // PAGE:en.w:HTML; <i> and <center> tags. There are + fxt.Test_parse_page_wiki_str("abc", "abc"); + } + @Test public void Code_do_not_escape() { // PURPOSE: was mistakenly marked as escape, causing inner tags to be rendered incorrectly; PAGE:en.w:UTF8 + fxt.Test_parse_page_all_str + ( "0100100" + , "0100100" + ); + } + @Test public void Pre_and_html_chars() {// PURPOSE:
       should handle '"<> according to context
      +		fxt.Test_parse_page_all_str("
      a	b
      " , "
      a	b
      "); // known ncr/dec; embed and depend on browser transforming; EX: de.w:Wikipedia:Technik/Skin/Werkstatt + fxt.Test_parse_page_all_str("
      a�b
      " , "
      a&#9999999999;b
      "); // unknown ncr/dec; escape & (since browser cannot render); + fxt.Test_parse_page_all_str("
      a&#af ;b
      " , "
      a&#af ;b
      "); // unknown ncr/dec 2 + fxt.Test_parse_page_all_str("
      a	b
      " , "
      a	b
      "); // known ncr/hex + fxt.Test_parse_page_all_str("
      a'b
      " , "
      a'b
      "); // known name; embed + fxt.Test_parse_page_all_str("
      a&apox;b
      " , "
      a&apox;b
      "); // unknown name; escape + fxt.Test_parse_page_all_str("
      &\"<>
      " , "
      &"<>
      "); // no ncr or name; escape; needed for
      ; PAGE:en.w:Alt attribute + } + @Test public void Pre_and_space() {// PURPOSE: make sure pre does not careate

      around it; also, make sure " a" is preserved; DATE:2014-02-20 + fxt.Init_para_y_(); + fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last + ( "
      "
      +		, " a"
      +		, "
      " + ), String_.Concat_lines_nl_skip_last + ( "
      "
      +		, " a"
      +		, "
      " + )); + fxt.Init_para_n_(); + } +} diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__tidy_tst.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__tidy_tst.java index a27517de8..7cb8cd37a 100644 --- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__tidy_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__tidy_tst.java @@ -13,3 +13,32 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_xnde_wkr__tidy_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @After public void term() {fxt.Init_para_n_();} + @Test public void Sub_sup_autocorrect() { + fxt.Test_parse_page_wiki_str("ab", "ab"); + fxt.Test_parse_page_wiki_str("ab", "ab"); + } + @Test public void Span_font_autocorrect() { // PURPOSE: force to close ; EX:w:Rupee; DATE:2014-04-07 + fxt.Test_parse_page_wiki_str("ab", "ab"); + } + @Test public void Move_ws_char() { + fxt.Test_parse_page_all_str("a b c", "a b c"); + } + @Test public void Move_ws_ent() { + fxt.Test_parse_page_all_str("a b c", "a b c"); + } + @Test public void Ignore_empty_tags() { // PURPOSE: ignore tag if marked ignore_empty; EX:uk.b:HTML; DATE:2014-03-12 + fxt.Test_parse_page_all_str("a
      b", "ab");
      +	}
      +//		@Test  public void Escaped_div() {	// NOTE: WP 
      a
      b; MW:
      a
      b
      // REVISIT: 2012-05-11; WP does harder split-span +// fxt.Init_log_(Xop_xnde_log.Auto_closing_section, Xop_xnde_log.Escaped_xnde).Test_parse_page_wiki("
      " +// , fxt.tkn_xnde_(0, 17).Subs_ +// ( fxt.tkn_xnde_(5, 11)) +// , fxt.tkn_ignore_(17, 24) +// ); +// } +} diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__xatrs_tst.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__xatrs_tst.java index a27517de8..9cdde21e1 100644 --- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__xatrs_tst.java +++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__xatrs_tst.java @@ -13,3 +13,46 @@ 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.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*; +import org.junit.*; +public class Xop_xnde_wkr__xatrs_tst { + private final Xop_fxt fxt = new Xop_fxt(); + @After public void term() {fxt.Init_para_n_();} + @Test public void Inline() { + fxt.Test_parse_page_wiki("" , fxt.tkn_xnde_(0, 15).Atrs_rng_(5, 13)); + fxt.Test_parse_page_wiki("" , fxt.tkn_xnde_(0, 21).Atrs_rng_(5, 19)); // ws + } + @Test public void Bgn() { + fxt.Test_parse_page_wiki("
      " , fxt.tkn_xnde_(0, 19).Atrs_rng_(5, 12)); // basic + } + @Test public void Repeated() { // PURPOSE: if atr is repeated, take 1st, not last; EX: it.u:Dipartimento:Fisica_e_Astronomia; DATE:2014-02-09 + fxt.Test_parse_page_all_str("a" , "a"); // two + fxt.Test_parse_page_all_str("a" , "a"); // three + } + @Test public void Non_ws() { // PURPOSE: is valid; symbols function as ws + fxt.Test_parse_page_wiki("" , fxt.tkn_xnde_(0, 7).Atrs_rng_(3, 5)); + } + @Test public void Invalid() { // PURPOSE: make sure brx does not match br + fxt.Test_parse_page_wiki("" , fxt.tkn_bry_(0, 1), fxt.tkn_txt_(1, 6)); + } + @Test public void Id_encode() { + fxt.Test_parse_page_all_str("
      ", "
      "); + } + @Test public void Lt_should_not_be_escaped_in_input() { // PURPOSE: options textboxes were escaped if input's value had "<"; DATE:2014-07-04 + fxt.Page().Html_data().Html_restricted_n_(); + fxt.Test_parse_page_wiki_str("", ""); // NOTE: do not call parse_page_all_str which will call Page.Clear and reset Restricted + fxt.Page().Html_data().Html_restricted_y_(); + } + @Test public void Style__decode() { // PURPOSE: style values should be decoded; PAGE:en.w:Boron; DATE:2015-07-29 + fxt.Test_parse_page_all_str("a", "a"); + } +// @Test public void Unclosed() { // PURPOSE: unclosed atr should be treated as key, which should be ignored; PAGE:en.w:Palace of Versailles +// fxt.Test_parse_page_wiki_str(String_.Concat_lines_nl_skip_last +// ( "a" // id="1� -> key named 'id="1�' which fails whitelist keys +// , "" +// ), String_.Concat_lines_nl_skip_last +// ( "a" +// , "" +// )); +// } +} diff --git a/400_xowa/src/gplx/xowa/specials/Xoa_special_mgr.java b/400_xowa/src/gplx/xowa/specials/Xoa_special_mgr.java index a27517de8..854efd317 100644 --- a/400_xowa/src/gplx/xowa/specials/Xoa_special_mgr.java +++ b/400_xowa/src/gplx/xowa/specials/Xoa_special_mgr.java @@ -13,3 +13,21 @@ 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.specials; import gplx.*; import gplx.xowa.*; +import gplx.xowa.xtns.wbases.specials.*; +public class Xoa_special_mgr implements Gfo_invk { + private Wdata_itemByTitle_cfg wbase_cfg = new Wdata_itemByTitle_cfg(); + private Ordered_hash hash = Ordered_hash_.New(); + public Xoa_special_mgr() { + hash.Add(Wdata_itemByTitle_cfg.Key, wbase_cfg); + } + public void Init_by_app(Xoae_app app) { + wbase_cfg.Init_by_app(app); + } + public void Add(String key, Gfo_invk cfg) {hash.Add(key, cfg);} + public Gfo_invk Get_or_null(String key) {return (Gfo_invk)hash.Get_by(key);} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_get)) return Get_or_null(m.ReadStr("v")); + else return Gfo_invk_.Rv_unhandled; + } private static final String Invk_get = "get"; +} diff --git a/400_xowa/src/gplx/xowa/specials/Xow_special_meta.java b/400_xowa/src/gplx/xowa/specials/Xow_special_meta.java index a27517de8..11332a87d 100644 --- a/400_xowa/src/gplx/xowa/specials/Xow_special_meta.java +++ b/400_xowa/src/gplx/xowa/specials/Xow_special_meta.java @@ -13,3 +13,31 @@ 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.specials; import gplx.*; import gplx.xowa.*; +public class Xow_special_meta { + public Xow_special_meta(int src, String key_str, String... aliases) { + this.src = src; this.key_str = key_str; + this.key_bry = Bry_.new_u8(key_str); + this.ttl_bry = Bry_.Add(gplx.xowa.wikis.nss.Xow_ns_.Bry__special, Byte_ascii.Colon_bry, key_bry); + this.ttl_str = String_.new_u8(ttl_bry); + this.aliases = Bry_.Ary(aliases); + } + public int Src() {return src;} private final int src; // either MW or XOWA + public String Key_str() {return key_str;} private final String key_str; // EX: AllPages + public byte[] Key_bry() {return key_bry;} private final byte[] key_bry; + public String Ttl_str() {return ttl_str;} private final String ttl_str; // EX: Special:AllPages + public byte[] Ttl_bry() {return ttl_bry;} private final byte[] ttl_bry; + public byte[][] Aliases() {return aliases;} private final byte[][] aliases; // EX: Special:RandomPage has Special:Random as alias + public byte[] Display_ttl() {return display_ttl;} private byte[] display_ttl; public Xow_special_meta Display_ttl_(String v) {display_ttl = Bry_.new_u8(v); return this;} + public String Url__home() { + return String_.Concat(gplx.xowa.wikis.domains.Xow_domain_itm_.Str__home, gplx.xowa.htmls.hrefs.Xoh_href_.Str__wiki, ttl_str); + } + + public boolean Match_ttl(Xoa_ttl ttl) { + return ttl.Ns().Id_is_special() && Bry_.Eq(ttl.Root_txt(), key_bry); + } + + public static Xow_special_meta New_xo(String key, String display, String... aliases) { + return new Xow_special_meta(Xow_special_meta_.Src__xowa, key, aliases).Display_ttl_(display); + } +} diff --git a/400_xowa/src/gplx/xowa/specials/Xow_special_meta_.java b/400_xowa/src/gplx/xowa/specials/Xow_special_meta_.java index a27517de8..5821cd85b 100644 --- a/400_xowa/src/gplx/xowa/specials/Xow_special_meta_.java +++ b/400_xowa/src/gplx/xowa/specials/Xow_special_meta_.java @@ -13,3 +13,41 @@ 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.specials; import gplx.*; import gplx.xowa.*; +public class Xow_special_meta_ { + public static final int Src__mw = 1, Src__xowa = 2; + public static final String + Ttl__all_pages = "AllPages" + , Ttl__random = "RandomPage" + , Ttl__random_root_page = "RandomRootPage" + , Ttl__search = "Search" + , Ttl__statistics = "Statistics" + , Ttl__move_page = "MovePage" + , Ttl__my_language = "MyLanguage" + , Ttl__item_by_title = "ItemByTitle" + , Ttl__default_tab = "XowaDefaultTab" + , Ttl__popup_history = "XowaPopupHistory" + , Ttl__system_data = "XowaSystemData" + , Ttl__nearby = "Nearby" + , Ttl__page_history = "XowaHistory" + , Ttl__bookmarks = "XowaBookmarks" + , Ttl__diag = "XowaDiag" + ; + public static final Xow_special_meta + Itm__all_pages = new Xow_special_meta(Src__mw , Ttl__all_pages) + , Itm__random = new Xow_special_meta(Src__mw , Ttl__random) + , Itm__random_root_page = new Xow_special_meta(Src__mw , Ttl__random_root_page) + , Itm__search = new Xow_special_meta(Src__mw , Ttl__search) + , Itm__statistics = new Xow_special_meta(Src__mw , Ttl__statistics) + , Itm__move_page = new Xow_special_meta(Src__xowa , Ttl__move_page) + , Itm__my_language = new Xow_special_meta(Src__xowa , Ttl__my_language) + , Itm__item_by_title = new Xow_special_meta(Src__xowa , Ttl__item_by_title) + , Itm__default_tab = new Xow_special_meta(Src__xowa , Ttl__default_tab) + , Itm__popup_history = new Xow_special_meta(Src__xowa , Ttl__popup_history) + , Itm__system_data = new Xow_special_meta(Src__xowa , Ttl__system_data) + , Itm__nearby = new Xow_special_meta(Src__xowa , Ttl__nearby) + , Itm__page_history = new Xow_special_meta(Src__xowa , Ttl__page_history) + , Itm__bookmarks = new Xow_special_meta(Src__xowa , Ttl__bookmarks) + , Itm__diag = new Xow_special_meta(Src__xowa , Ttl__diag) + ; +} diff --git a/400_xowa/src/gplx/xowa/specials/Xow_special_mgr.java b/400_xowa/src/gplx/xowa/specials/Xow_special_mgr.java index a27517de8..5501c8f4a 100644 --- a/400_xowa/src/gplx/xowa/specials/Xow_special_mgr.java +++ b/400_xowa/src/gplx/xowa/specials/Xow_special_mgr.java @@ -13,3 +13,108 @@ 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.specials; import gplx.*; import gplx.xowa.*; +import gplx.xowa.users.history.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.specials.*; +import gplx.xowa.specials.*; +import gplx.xowa.specials.allPages.*; import gplx.xowa.specials.nearby.*; import gplx.xowa.specials.statistics.*; import gplx.xowa.xtns.translates.*; import gplx.xowa.specials.movePage.*; +import gplx.xowa.specials.xowa.system_data.*; import gplx.xowa.specials.xowa.default_tab.*; import gplx.xowa.specials.xowa.popup_history.*; import gplx.xowa.addons.wikis.imports.*; import gplx.xowa.specials.xowa.diags.*; +import gplx.xowa.xtns.wbases.specials.*; +import gplx.xowa.users.data.*; import gplx.xowa.users.bmks.*; +import gplx.xowa.specials.mgrs.*; import gplx.xowa.addons.wikis.searchs.specials.*; +import gplx.xowa.wikis.pages.*; +public class Xow_special_mgr { + private final Hash_adp_bry hash; + private Xoa_app app; + public Xow_special_mgr(Xowe_wiki wiki, Xol_lang_itm lang) { + this.app = wiki.App(); + hash = Hash_adp_bry.ci_u8(lang.Case_mgr()); + page_allpages = new Xows_page_allpages(wiki); + Evt_lang_changed(wiki.Lang()); + } + public Xows_page_allpages Page_allpages() {return page_allpages;} private final Xows_page_allpages page_allpages; + public Srch_special_page Page_search() {return page_search;} private final Srch_special_page page_search = new Srch_special_page(); + public Xou_history_html Page_history() {return page_history;} private final Xou_history_html page_history = new Xou_history_html(); + public Xoud_history_special Page_history2() {return page_history2;} private final Xoud_history_special page_history2 = new Xoud_history_special(); + public Nearby_mgr Page_nearby() {return page_nearby;} private final Nearby_mgr page_nearby = new Nearby_mgr(); + public Xop_mylanguage_page Page_mylanguage() {return page_mylanguage;} private final Xop_mylanguage_page page_mylanguage = new Xop_mylanguage_page(); + public Wdata_itemByTitle_page Page_itemByTitle() {return page_itemByTitle;} private final Wdata_itemByTitle_page page_itemByTitle = new Wdata_itemByTitle_page(); + public Xop_statistics_page Page_statistics() {return page_statistics;} private final Xop_statistics_page page_statistics = new Xop_statistics_page(); + public Move_page Page_movePage() {return page_movePage;} private final Move_page page_movePage = new Move_page(); + public System_data_page Page_system_data() {return page_system_data;} private final System_data_page page_system_data = new System_data_page(); + public Default_tab_page Page_default_tab() {return page_default_tab;} private final Default_tab_page page_default_tab = new Default_tab_page(); + public Popup_history_page Page_popup_history() {return page_popup_history;} private final Popup_history_page page_popup_history = new Popup_history_page(); + public Xows_bmk_page Page_bmk() {return page_bmk;} private final Xows_bmk_page page_bmk = new Xows_bmk_page(); + public Xows_diag_page Page_diag() {return page_diag;} private final Xows_diag_page page_diag = new Xows_diag_page(); + public void Evt_lang_changed(Xol_lang_itm lang) { + // add special pages by old manual method; DEPRECATED + hash.Clear(); + hash.Add_str_obj(Xow_special_meta_.Ttl__search , page_search); + hash.Add_str_obj(Xow_special_meta_.Ttl__all_pages , page_allpages); + hash.Add_str_obj("prefixindex" , page_allpages); + hash.Add_bry_obj(Xou_history_mgr.Ttl_name , page_history); + hash.Add_str_obj(Xow_special_meta_.Ttl__page_history , page_history2); + hash.Add_str_obj(Xow_special_meta_.Ttl__nearby , page_nearby); + hash.Add_str_obj(Xow_special_meta_.Ttl__my_language , page_mylanguage); + hash.Add_str_obj(Xow_special_meta_.Ttl__item_by_title , page_itemByTitle); + hash.Add_str_obj(Xow_special_meta_.Ttl__statistics , page_statistics); + hash.Add_str_obj(Xow_special_meta_.Ttl__move_page , page_movePage); + hash.Add_str_obj(Xow_special_meta_.Ttl__system_data , page_system_data); + hash.Add_str_obj(Xow_special_meta_.Ttl__default_tab , page_default_tab); + hash.Add_str_obj(Xow_special_meta_.Ttl__popup_history , page_popup_history); + hash.Add_str_obj(Xow_special_meta_.Ttl__bookmarks , page_bmk); + hash.Add_str_obj(Xow_special_meta_.Ttl__diag , page_diag); + + // add app's Special_regy to hash table; needed for case insensitivity by wiki's lang; EX: Special:rANDom; NOTE: needs to go before lang aliases + Xoa_special_regy special_regy = app.Special_regy(); + int len = special_regy.Len(); + for (int i = 0; i < len; ++i) { + Xow_special_page proto = special_regy.Get_at(i); + Xow_special_meta proto_meta = proto.Special__meta(); + hash.Add_if_dupe_use_1st(proto_meta.Key_bry(), proto); + for (byte[] alias : proto_meta.Aliases()) + hash.Add_if_dupe_use_1st(alias, proto); + } + + // add lang's special aliases to hash table; EX: Special:Recherche + Xol_specials_mgr lang_mgr = lang.Specials_mgr(); + len = lang_mgr.Len(); + for (int i = 0; i < len; ++i) { + Xol_specials_itm lang_itm = lang_mgr.Get_at(i); + Xow_special_page page = (Xow_special_page)hash.Get_by_bry(lang_itm.Special()); + if (page == null) continue; // NOTE: ignore specials that are not in XOWA; EX: Special:ChangeEmail + for (byte[] alias : lang_itm.Aliases()) + hash.Add_if_dupe_use_1st(alias, page); + } + } + public void Special__gen(Xoa_app app, Xow_wiki wiki, Xoa_page page, Xoa_url url, Xoa_ttl ttl) { + int slash_pos = Bry_find_.Find_fwd(ttl.Page_txt_wo_qargs(), Xoa_ttl.Subpage_spr); // check for slash + byte[] special_name = slash_pos == Bry_find_.Not_found + ? ttl.Base_txt_wo_qarg() // slash absent; use base_txt; ignore qry args and just get page_names; EX: Search/Earth?fulltext=y; Allpages?from=Earth... + : Bry_.Mid(ttl.Page_txt_wo_qargs(), 0, slash_pos); // slash exists; use root page; EX: Special:ItemByTitle/enwiki/Earth + special_name = Xoa_ttl.Replace_spaces(special_name); // handle spaces; EX:Spezial:Zufällige_Seite + + Xow_special_page special = (Xow_special_page)hash.Get_by_bry(special_name); + if (special != null) { // special found; generate it; + // check safelisted pages; DATE:2017-07-22 + Hash_adp safelist = app.Special_regy().Safelist_pages(); + if (safelist.Count() > 0) { // safelist pages enabled + if (!safelist.Has(special_name)) { + byte[] safelist_failed = Bry_.new_u8("This special page is not listed in the special_pages safelist. Re-run XOWA and list it in the command-line arguments similar to this: \"--http_server.special_pages_safelist " + String_.new_u8(special_name) + "\""); + Xopage_html_data page_data = new Xopage_html_data(special.Special__meta().Display_ttl(), safelist_failed); + page_data.Apply(page); + return; + } + } + + special = special.Special__clone(); + page.Db().Page().Modified_on_(Datetime_now.Get()); + try {special.Special__gen(wiki, page, url, ttl);} + catch (Exception e) {Gfo_log_.Instance.Warn("failed to generate special page", "url", url.To_str(), "err", Err_.Message_gplx_log(e));} + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_search)) return page_search; + else return Gfo_invk_.Rv_unhandled; + } private static final String Invk_search = "search"; +} diff --git a/400_xowa/src/gplx/xowa/specials/Xow_special_page.java b/400_xowa/src/gplx/xowa/specials/Xow_special_page.java index a27517de8..e412a5a8c 100644 --- a/400_xowa/src/gplx/xowa/specials/Xow_special_page.java +++ b/400_xowa/src/gplx/xowa/specials/Xow_special_page.java @@ -13,3 +13,9 @@ 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.specials; import gplx.*; import gplx.xowa.*; +public interface Xow_special_page { + Xow_special_meta Special__meta(); + Xow_special_page Special__clone(); + void Special__gen(Xow_wiki wikii, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl); +} diff --git a/400_xowa/src/gplx/xowa/specials/Xow_special_wtr__base.java b/400_xowa/src/gplx/xowa/specials/Xow_special_wtr__base.java index a27517de8..46a6b5261 100644 --- a/400_xowa/src/gplx/xowa/specials/Xow_special_wtr__base.java +++ b/400_xowa/src/gplx/xowa/specials/Xow_special_wtr__base.java @@ -13,3 +13,31 @@ 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.specials; import gplx.*; import gplx.xowa.*; +import gplx.langs.mustaches.*; import gplx.xowa.wikis.pages.*; +public abstract class Xow_special_wtr__base { + public void Bld_page_by_mustache(Xoa_app app, Xoa_page page, Xow_special_page special) { + Mustache_doc_itm mustache_root = Bld_mustache_root(app); + if (mustache_root == null) { // handle invalid urls; EX: Special:XowaWikiInfo?wiki=deleted_wiki + Handle_invalid(app, page, special); + return; + } + Io_url addon_dir = this.Get_addon_dir(app); + byte[] body = Bld_html_body(addon_dir, mustache_root); + Xopage_html_data page_data = new Xopage_html_data(special.Special__meta().Display_ttl(), body); + Bld_tags(app, addon_dir, page_data); + page_data.Apply(page); + } + @gplx.Virtual protected byte[] Bld_html_body(Io_url addon_dir, gplx.langs.mustaches.Mustache_doc_itm itm) { + byte[] tmpl = Io_mgr.Instance.LoadFilBry(this.Get_mustache_fil(addon_dir)); + return gplx.langs.mustaches.Mustache_wtr_.Write_to_bry(Bry_bfr_.New(), tmpl, itm); + } + + protected abstract Io_url Get_addon_dir(Xoa_app app); + protected abstract Io_url Get_mustache_fil(Io_url addon_dir); + protected abstract Mustache_doc_itm Bld_mustache_root(Xoa_app app); + protected abstract void Bld_tags(Xoa_app app, Io_url addon_dir, Xopage_html_data data); + @gplx.Virtual protected void Handle_invalid(Xoa_app app, Xoa_page page, Xow_special_page special) { + new Xopage_html_data(special.Special__meta().Display_ttl(), Bry_.new_a7("Not available")).Apply(page); + } +} diff --git a/400_xowa/src/gplx/xowa/specials/allPages/Xows_page_allpages.java b/400_xowa/src/gplx/xowa/specials/allPages/Xows_page_allpages.java index a27517de8..3324bb128 100644 --- a/400_xowa/src/gplx/xowa/specials/allPages/Xows_page_allpages.java +++ b/400_xowa/src/gplx/xowa/specials/allPages/Xows_page_allpages.java @@ -13,3 +13,191 @@ 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.specials.allPages; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; +import gplx.core.primitives.*; import gplx.core.net.*; import gplx.core.net.qargs.*; import gplx.core.brys.fmtrs.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.htmls.core.htmls.*; import gplx.xowa.htmls.hrefs.*; import gplx.xowa.htmls.core.wkrs.lnkis.htmls.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.apps.urls.*; +public class Xows_page_allpages implements gplx.core.brys.Bfr_arg, Gfo_invk, Xow_special_page { + public Xows_page_allpages(Xowe_wiki wiki) { + this.wiki = wiki; + html_itm_fmtr = new Xos_pagelist_html_itm_fmtr(this, wiki); + } private Xos_pagelist_html_itm_fmtr html_itm_fmtr; + public Xow_special_meta Special__meta() {return Xow_special_meta_.Itm__all_pages;} + public Xowe_wiki Wiki() {return wiki;} private Xowe_wiki wiki; + public Bry_fmtr Html_all() {return html_all;} Bry_fmtr html_all = Bry_fmtr.new_(String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
      " + , "~{anchor_prv} |" + , "~{anchor_nxt}" + , "
      " + , "" + , "~{grps}" + , "" + , "
      " + , "
      " + , "
      " + , "~{anchor_prv} |" + , "~{anchor_nxt}" + , "
      " + ), "grps", "anchor_prv", "anchor_nxt"); + public Bry_fmtr Html_list_grp() {return html_list_grp;} Bry_fmtr html_list_grp = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , " ~{itms}" + , " " + ), "itms"); + public Bry_fmtr Html_list_itm_normal() {return html_list_itm_normal;} Bry_fmtr html_list_itm_normal = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , " ~{lnki_text}" + ), "itm_pct", "lnki_href", "lnki_title", "lnki_text", "lnki_atr_cls"); + public Bry_fmtr Html_list_itm_redirect() {return html_list_itm_redirect;} Bry_fmtr html_list_itm_redirect = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , " " + ), "itm_pct", "lnki_href", "lnki_title", "lnki_text"); + public Bry_fmtr Html_list_end() {return html_list_end;} Bry_fmtr html_list_end = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "~{lbl_text}" + ), "arg_from", "args__rest", "lbl_text"); + public int Itms_per_page() {return itms_per_page;} private int itms_per_page = 345; + public void Itms_per_page_(int v) {itms_per_page = v; rslt_list_ttls = new Xowd_page_itm[v];} + public int Itms_per_grp() {return itms_per_grp;} private int itms_per_grp = 3; + public boolean List_include_redirects() {return show_redirects;} private boolean show_redirects = true; + public Xowd_page_itm Rslt_prv() {return rslt_prv;} public void Rslt_prv_(Xowd_page_itm v) {rslt_prv = v;} private Xowd_page_itm rslt_prv; + public Xowd_page_itm Rslt_nxt() {return rslt_nxt;} public void Rslt_nxt_(Xowd_page_itm v) {rslt_nxt = v;} private Xowd_page_itm rslt_nxt; + public int Rslt_list_len() {return rslt_list_len;} public void Rslt_list_len_(int v) {rslt_list_len = v;} private int rslt_list_len; + public Xowd_page_itm[] Rslt_list_ttls() {return rslt_list_ttls;} private Xowd_page_itm[] rslt_list_ttls; + public void Special__gen(Xow_wiki wikii, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl) { + Xowe_wiki wiki = (Xowe_wiki)wikii; Xoae_page page = (Xoae_page)pagei; + wiki.Parser_mgr().Ctx().Page().Html_data().Display_ttl_(wiki.Msg_mgr().Val_by_id(Xol_msg_itm_.Id_sp_allpages_hdr)); + url.Page_bry_(Bry_.Add(Bry_.new_a7("Special:"), ttl.Page_txt_wo_qargs())); // HACK: need to re-set Page b/c href_wtr does not eliminate qargs; DATE:2013-02-08 + if (rslt_list_ttls == null) this.Itms_per_page_(itms_per_page); + boolean found = Build_data(url, ttl); if (!found) return; + Build_html(page); + } + private static byte[] Get_from(Gfo_qarg_mgr_old arg_hash, Xowe_wiki wiki, Xoa_url url, Xoa_ttl ttl) { + return ttl.Leaf_bgn() == -1 + ? arg_hash.Get_val_bry_or(Bry_arg_from, null) + : ttl.Leaf_url() + ; + } + public boolean Build_data(Xoa_url url, Xoa_ttl ttl) { + init_ns = wiki.Ns_mgr().Ns_main(); + arg_hash.Load(url.Qargs_ary()); + byte[] from_val = Get_from(arg_hash, wiki, url, ttl); if (from_val == null) return false; + from_val = gplx.langs.htmls.encoders.Gfo_url_encoder_.Id.Decode(from_val); + int ns_val = arg_hash.Get_val_int_or(Bry_arg_ns, init_ns.Id()); init_ns = wiki.Ns_mgr().Ids_get_or_null(ns_val); + boolean hide_redirects_val = arg_hash.Get_val_int_or(Bry_arg_hideredirects, 0) == 1; + for (int i = 0; i < itms_per_page; i++) + rslt_list_ttls[i] = null; + rslt_list_len = 0; + rslt_nxt = rslt_prv = null; + Xoa_ttl from_ttl = Xoa_ttl.Parse(wiki, from_val); if (from_ttl == null) return false; + if (!from_ttl.Ns().Id_is_main()) { // ns specified in title + init_ns = from_ttl.Ns(); + arg_hash.Set_val_by_int(Bry_arg_ns, init_ns.Id()); + arg_hash.Set_val_by_bry(Bry_arg_from, from_ttl.Page_db()); + url.Qargs_ary_(arg_hash.To_ary()); + } + Int_obj_ref rslt_len = Int_obj_ref.New(rslt_list_len); + Xowd_page_itm rslt_nxt2 = new Xowd_page_itm(); + Xowd_page_itm rslt_prv2 = new Xowd_page_itm(); + int all_pages_min = 0;// no minimum for all pages + List_adp rslt_list = List_adp_.New(); + wiki.Db_mgr().Load_mgr().Load_ttls_for_all_pages(Cancelable_.Never, rslt_list, rslt_nxt2, rslt_prv2, rslt_len, init_ns, from_ttl.Page_db(), itms_per_page, all_pages_min, itms_per_page, !hide_redirects_val, true); + rslt_list_len = rslt_len.Val(); + for (int i = 0; i < rslt_list_len; i++) + rslt_list_ttls[i] = (Xowd_page_itm)rslt_list.Get_at(i); + rslt_nxt = rslt_nxt2; + rslt_prv = rslt_prv2; + return true; + } private Gfo_qarg_mgr_old arg_hash = new Gfo_qarg_mgr_old(); + private static final byte[] Bry_arg_from = Bry_.new_a7("from"), Bry_arg_ns = Bry_.new_a7("namespace"), Bry_arg_hideredirects = Bry_.new_a7("hideredirects"); + public Xow_ns Init_ns() {return init_ns;} private Xow_ns init_ns; + public void Build_html(Xoae_page page) { + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_m001(); + Bry_fmtr fmtr = Bry_fmtr.New__tmp(); + + byte[] anchor_prv = Build_html_end(tmp_bfr, fmtr, rslt_prv, false); + byte[] anchor_nxt = Build_html_end(tmp_bfr, fmtr, rslt_nxt, true); + html_all.Bld_bfr_many(tmp_bfr, this, anchor_prv, anchor_nxt); + page.Db().Text().Text_bry_(tmp_bfr.To_bry_and_clear()); + tmp_bfr.Mkr_rls(); + page.Html_data().Html_restricted_n_(); + } + byte[] Build_html_end(Bry_bfr bfr, Bry_fmtr fmtr, Xowd_page_itm itm, boolean fwd) { + Xoa_ttl ttl = Xows_page_allpages.ttl_(wiki, init_ns, itm); if (ttl == null) return Bry_.Empty; // occurs when range is empty; EX: Module:A in simplewikibooks + int msg_id = fwd ? Xol_msg_itm_.Id_sp_allpages_fwd : Xol_msg_itm_.Id_sp_allpages_bwd; + Xol_msg_itm msg_itm = wiki.Lang().Msg_mgr().Itm_by_id_or_null(msg_id); + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_b512(); + try { + byte[] lbl_text = msg_itm.Fmt(tmp_bfr, fmtr, ttl.Full_txt_w_ttl_case()); + byte[] args__rest = arg_hash.Concat(tmp_bfr, Bry_arg_ns, Bry_arg_hideredirects); + byte[] arg_from = gplx.langs.htmls.encoders.Gfo_url_encoder_.Id.Encode(ttl.Page_txt_wo_qargs()); + return html_list_end.Bld_bry_many(bfr, arg_from, args__rest, lbl_text); + } finally {tmp_bfr.Mkr_rls();} + } + public static Xoa_ttl ttl_(Xowe_wiki wiki, Xow_ns ns, Xowd_page_itm itm) { + byte[] ttl_bry = itm.Ttl_page_db(); + if (!ns.Id_is_main()) ttl_bry = Bry_.Add(ns.Name_db_w_colon(), ttl_bry); + return Xoa_ttl.Parse(wiki, ttl_bry); + } + public void Bfr_arg__add(Bry_bfr bfr) { + int len = rslt_list_ttls.length; + html_itm_fmtr.XferAry_bgn(); + for (int i = 0; i < len; i += itms_per_grp) { + html_itm_fmtr.Itm_idx_(i); + html_list_grp.Bld_bfr_many(bfr, html_itm_fmtr); + } + html_itm_fmtr.XferAry_end(); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_html_all_)) html_all.Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_html_list_grp_)) html_list_grp.Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_html_list_itm_normal_)) html_list_itm_normal.Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_html_list_itm_redirect_)) html_list_itm_redirect.Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_itms_per_page_)) Itms_per_page_(m.ReadInt("v")); + else if (ctx.Match(k, Invk_itms_per_grp_)) itms_per_grp = m.ReadInt("v"); + else if (ctx.Match(k, Invk_show_redirects_)) show_redirects = m.ReadYn("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk_html_all_ = "html_all_", Invk_html_list_grp_ = "html_list_grp_", Invk_html_list_itm_normal_ = "html_list_itm_normal_", Invk_html_list_itm_redirect_ = "html_list_itm_redirect_" + , Invk_itms_per_page_ = "itms_per_page_", Invk_itms_per_grp_ = "itms_per_grp_", Invk_show_redirects_ = "show_redirects_"; + public static final String GRP_KEY = "xowa.special.allpages"; + + public Xow_special_page Special__clone() {return this;} +} +class Xos_pagelist_html_itm_fmtr implements gplx.core.brys.Bfr_arg { + public Xos_pagelist_html_itm_fmtr(Xows_page_allpages mgr, Xowe_wiki wiki) { + this.mgr = mgr; this.wiki = wiki; this.href_wtr = wiki.Html__href_wtr(); this.wiki_key = wiki.Domain_bry(); + this.itm_normal = mgr.Html_list_itm_normal(); this.itm_redirect = mgr.Html_list_itm_redirect(); + history_mgr = wiki.Appe().Usere().History_mgr(); + } private Xows_page_allpages mgr; Xowe_wiki wiki; Xoh_href_wtr href_wtr; Bry_fmtr itm_normal, itm_redirect; byte[] wiki_key; gplx.xowa.users.history.Xou_history_mgr history_mgr; + public void Itm_idx_(int v) {itm_idx = v;} private int itm_idx; + public void XferAry_bgn() { + itms_per_grp = mgr.Itms_per_grp(); + ttls = mgr.Rslt_list_ttls(); + ttls_len = ttls.length; + itm_pct = 100 / itms_per_grp; + init_ns = mgr.Init_ns(); + } int itms_per_grp, ttls_len, itm_pct; Xowd_page_itm[] ttls; Xow_ns init_ns; + public void XferAry_end() {ttls = null;} + public void Bfr_arg__add(Bry_bfr bfr) { + int itm_end = itm_idx + itms_per_grp; + for (int i = itm_idx; i < itm_end; i++) { + if (i >= ttls_len) break; // handle odd number of itms; EX: interval=3; items=4; proc gets called on 0 (0-2) and 3 (3-5); ArrayIndex for 4, 5 + Xowd_page_itm ttl_itm = ttls[i]; + if (ttl_itm == null) break; // ttl_itm can be null at bgn or end of title list; EX: list=A-Z; count=5; key=Z; itms=X,Y,Z,null,null + Xoa_ttl ttl = Xows_page_allpages.ttl_(wiki, init_ns, ttl_itm); + byte[] href = href_wtr.Build_to_bry(wiki, ttl); + byte[] title = ttl.Full_txt_w_ttl_case(); + byte[] cls = Xoh_lnki_wtr.Lnki_cls_visited(history_mgr, wiki_key, ttl.Page_txt()); // NOTE: must be ttl.Page_txt() in order to match Xou_history_mgr.Add + Bry_fmtr fmtr = ttl_itm.Redirected() ? itm_redirect : itm_normal; + fmtr.Bld_bfr_many(bfr, itm_pct, href, title, ttl.Full_txt_w_ttl_case(), cls); + } + } +} diff --git a/400_xowa/src/gplx/xowa/specials/allPages/Xows_page_allpages_tst.java b/400_xowa/src/gplx/xowa/specials/allPages/Xows_page_allpages_tst.java index a27517de8..1f8090f21 100644 --- a/400_xowa/src/gplx/xowa/specials/allPages/Xows_page_allpages_tst.java +++ b/400_xowa/src/gplx/xowa/specials/allPages/Xows_page_allpages_tst.java @@ -13,3 +13,192 @@ 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.specials.allPages; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; +import org.junit.*; import gplx.core.net.*; import gplx.core.net.qargs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.tdbs.hives.*; +public class Xows_page_allpages_tst { + @Before public void init() {fxt.Clear();} private Xows_page_allpages_fxt fxt = new Xows_page_allpages_fxt(); + @Test public void Build_data() { + Xow_hive_mgr_fxt.Ttls_create_rng(fxt.Wiki(), 5, 7); + fxt.Clear().Init_ttl_leaf("/B1").Expd_prv("A3").Expd_nxt("B6").Expd_ttls("B1", "B2", "B3", "B4", "B5").Test_build_data(); // SpecialPage:AllPages/B1 + fxt.Clear().Init_arg("from", "B1").Expd_prv("A3").Expd_nxt("B6").Expd_ttls("B1", "B2", "B3", "B4", "B5").Test_build_data(); // single file + fxt.Clear().Init_arg("from", "A6").Expd_prv("A1").Expd_nxt("B4").Expd_ttls("A6", "B0", "B1", "B2", "B3").Test_build_data(); // overflow rhs + fxt.Clear().Init_arg("from", "E4").Expd_prv("D6").Expd_nxt("E6").Expd_ttls("E4", "E5", "E6", null, null).Test_build_data(); // bounds rhs + fxt.Clear().Init_arg("from", "A0").Expd_prv("A0").Expd_nxt("A5").Expd_ttls("A0", "A1", "A2", "A3", "A4").Test_build_data(); // bounds lhs + fxt.Clear().Init_arg("from", "B0a").Expd_prv("A3").Expd_nxt("B6").Expd_ttls("B1", "B2", "B3", "B4", "B5").Test_build_data(); // inexact match; B0a matches "hi" value + fxt.Clear().Init_arg("from", "B1").Init_arg("hideredirects", "1").Expd_prv("A0").Expd_nxt("C4").Expd_ttls("B2", "B4", "B6", "C0", "C2").Test_build_data(); // hide redirects + fxt.Clear().Init_arg("from", "A6").Expd_prv("A0").Expd_nxt("E1").Init_itms_per_page(23).Expd_ttls("A6", "B0", "B1", "B2", "B3", "B4", "B5", "B6", "C0", "C1", "C2", "C3", "C4", "C5", "C6", "D0", "D1", "D2", "D3", "D4", "D5", "D6", "E0").Test_build_data(); // overflow rhs x2 + } + @Test public void Build_html_main() { + Xow_hive_mgr_fxt.Ttls_create_rng(fxt.Wiki(), 5, 7); + fxt.Clear().Init_arg("from", "B2").Init_itms_per_page(5).Test_build_html(String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
      " + , "Previous page (A4) |" + , "Next page (C0)" + , "
      " + , "" + , "" + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , "" + , "
      B2B4
      B6
      " + , "
      " + , "
      " + , "Previous page (A4) |" + , "Next page (C0)" + , "
      " + )); + } + @Test public void Build_html_redirect() { + Xow_hive_mgr_fxt.Ttls_create_rng(fxt.Wiki(), 1, 7); + fxt.Clear().Init_arg("from", "A2").Init_arg("hideredirects", "1").Init_itms_per_page(2).Test_build_html(String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
      " + , "Previous page (A0) |" + , "Next page (A6)" + , "
      " + , "" + , "" + , " " + , " " + , " " + , " " + , "" + , "
      A2A4
      " + , "
      " + , "
      " + , "Previous page (A0) |" + , "Next page (A6)" + , "
      " + )); + } + @Test public void Build_html_ns() { + Xow_hive_mgr_fxt.Ttls_create_rng(fxt.Wiki(), fxt.Wiki().Ns_mgr().Ns_template(), 1, 7); + fxt.Clear().Init_arg("from", "A2").Init_arg("namespace", "10").Init_itms_per_page(2).Test_build_html(String_.Concat_lines_nl + ( "" + , " " + , " " + , " " + , "
      " + , "Previous page (Template:A0) |" + , "Next page (Template:A4)" + , "
      " + , "" + , "" + , " " + , " " + , " " + , " " + , "" + , "
      Template:A2
      " + , "
      " + , "" + )); + } + @Test public void Misc() { + Xow_hive_mgr_fxt.Ttls_create_rng(fxt.Wiki(), fxt.Wiki().Ns_mgr().Ns_template(), 1, 7); + fxt.Clear().Init_arg("from", "Template:B1").Expd_arg("from", "B1").Expd_arg("namespace", "10").Test_build_data(); // extract ns from ttl + fxt.Clear().Init_arg("from", "Template:B1").Expd_display_ttl("All pages").Expd_address_page("Special:AllPages").Test_special_gen(); // display ttl + } +} +class Xows_page_allpages_fxt { + public Xows_page_allpages_fxt Clear() { + if (app == null) { + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + allpages = wiki.Special_mgr().Page_allpages(); + Gfo_invk_.Invk_by_val(allpages, Xows_page_allpages.Invk_itms_per_page_, 5); + } + init_ttl_leaf = ""; + expd_prv = expd_nxt = null; + expd_ttls = null; + expd_display_ttl = null; + return this; + } private Xoae_app app; + public Xowe_wiki Wiki() {return wiki;} private Xowe_wiki wiki; Xows_page_allpages allpages; + public Xows_page_allpages_fxt Init_arg(String key, String val) {init_args.Add(new Gfo_qarg_itm(Bry_.new_a7(key), Bry_.new_a7(val))); return this;} private List_adp init_args = List_adp_.New(); + public Xows_page_allpages_fxt Init_ttl_leaf(String val) {init_ttl_leaf = val; return this;} private String init_ttl_leaf; + public Xows_page_allpages_fxt Init_itms_per_page(int v) {init_itms_per_page = v; return this;} private int init_itms_per_page = 5; + public Xows_page_allpages_fxt Expd_arg(String key, String val) {expd_args.Add(new Gfo_qarg_itm(Bry_.new_a7(key), Bry_.new_a7(val))); return this;} private List_adp expd_args = List_adp_.New(); + public Xows_page_allpages_fxt Expd_prv(String v) {expd_prv = v; return this;} private String expd_prv; + public Xows_page_allpages_fxt Expd_nxt(String v) {expd_nxt = v; return this;} private String expd_nxt; + public Xows_page_allpages_fxt Expd_ttls(String... v) {expd_ttls = v; return this;} private String[] expd_ttls; + public Xows_page_allpages_fxt Expd_display_ttl(String v) {expd_display_ttl = v; return this;} private String expd_display_ttl; + public Xows_page_allpages_fxt Expd_address_page(String v) {expd_address_page = v; return this;} private String expd_address_page; + + public static String Xto_str(Xowe_wiki wiki, Xowd_page_itm v) { + if (v == null) return null; + Xow_ns ns = wiki.Ns_mgr().Ids_get_or_null(v.Ns_id()); + String ns_str = ns == null ? "" : String_.new_a7(ns.Name_db_w_colon()); + return ns_str + String_.new_a7(v.Ttl_page_db()); + } + public static String[] Xto_str_ary(Xowe_wiki wiki, Xowd_page_itm[] ary) { + int ary_len = ary.length; + String[] rv = new String[ary_len]; + for (int i = 0; i < ary_len; i++) { + Xowd_page_itm itm = ary[i]; + rv[i] = Xto_str(wiki, itm); + } + return rv; + } + public static String[] Xto_str_ary(Gfo_qarg_itm[] ary) { + int ary_len = ary.length; + String[] rv = new String[ary_len]; + for (int i = 0; i < ary_len; i++) { + Gfo_qarg_itm itm = ary[i]; + rv[i] = String_.new_u8(itm.Key_bry()) + "=" + String_.new_u8(itm.Val_bry()); + } + return rv; + } + public Xows_page_allpages_fxt Test_special_gen() { + init_url = app.User().Wikii().Utl__url_parser().Parse(Xow_special_meta_.Itm__all_pages.Ttl_bry()); + Xoa_ttl init_ttl = Make_init_ttl(); + allpages.Special__gen(wiki, wiki.Parser_mgr().Ctx().Page(), init_url, init_ttl); + if (expd_display_ttl != null) Tfds.Eq(expd_display_ttl, String_.new_u8(wiki.Parser_mgr().Ctx().Page().Html_data().Display_ttl())); + if (expd_address_page != null) Tfds.Eq(expd_address_page, String_.new_u8(init_url.Page_bry())); + return this; + } + public Xows_page_allpages_fxt Test_build_data() { + Exec_build(); + if (expd_ttls != null) Tfds.Eq_ary_str(expd_ttls, Xto_str_ary(wiki, allpages.Rslt_list_ttls())); + if (expd_nxt != null) Tfds.Eq(expd_nxt, Xto_str(wiki, allpages.Rslt_nxt())); + if (expd_prv != null) Tfds.Eq(expd_prv, Xto_str(wiki, allpages.Rslt_prv())); + if (expd_args.Count() > 0) { + Gfo_qarg_itm[] expd_args_ary = (Gfo_qarg_itm[])expd_args.To_ary(Gfo_qarg_itm.class); + Tfds.Eq_ary_str(Xto_str_ary(expd_args_ary), Xto_str_ary(init_url.Qargs_ary())); + } + return this; + } private Xoa_url init_url = Xoa_url.blank(); + public Xows_page_allpages_fxt Test_build_html(String expd) { + Exec_build(); + allpages.Build_html(wiki.Parser_mgr().Ctx().Page()); + Tfds.Eq_str_lines(expd, String_.new_a7(wiki.Parser_mgr().Ctx().Page().Db().Text().Text_bry())); + return this; + } + private void Exec_build() { + if (allpages.Itms_per_page() != init_itms_per_page) allpages.Itms_per_page_(init_itms_per_page); + init_url.Qargs_ary_((Gfo_qarg_itm[])init_args.To_ary(Gfo_qarg_itm.class)); + init_args.Clear(); + Xoa_ttl init_ttl = Make_init_ttl(); + allpages.Build_data(init_url, init_ttl); + } + private Xoa_ttl Make_init_ttl() {return Xoa_ttl.Parse(wiki, Bry_.new_u8(Xow_special_meta_.Itm__all_pages.Ttl_str() + init_ttl_leaf));} +} diff --git a/400_xowa/src/gplx/xowa/specials/deletes/Xodel_page_special.java b/400_xowa/src/gplx/xowa/specials/deletes/Xodel_page_special.java index a27517de8..cc8908155 100644 --- a/400_xowa/src/gplx/xowa/specials/deletes/Xodel_page_special.java +++ b/400_xowa/src/gplx/xowa/specials/deletes/Xodel_page_special.java @@ -13,3 +13,25 @@ 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.specials.deletes; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; +import gplx.xowa.specials.*; import gplx.xowa.wikis.nss.*; +import gplx.core.net.qargs.*; +public class Xodel_page_special implements Xow_special_page { + public void Special__gen(Xow_wiki wikii, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl) { + Xowe_wiki wiki = (Xowe_wiki)wikii; Xoae_page page = (Xoae_page)pagei; + + Gfo_qarg_mgr url_args = new Gfo_qarg_mgr().Init(url.Qargs_ary()); + byte[] page_title_bry = url_args.Read_bry_or_fail("delete"); + Xoa_ttl page_title = wiki.Ttl_parse(page_title_bry); + + gplx.xowa.addons.wikis.directorys.specials.items.bldrs.Xopg_db_mgr.Delete(wiki, page_title); + + wiki.Data_mgr().Redirect(page, wiki.Props().Main_page()); + } + + public static final String SPECIAL_KEY = "XowaPageDelete"; + public static final byte[] Display_ttl = Bry_.new_a7("Delete Page"); + public Xow_special_meta Special__meta() {return new Xow_special_meta(Xow_special_meta_.Src__mw, SPECIAL_KEY);} + public static final Xow_special_page Prototype = new Xodel_page_special(); + public Xow_special_page Special__clone() {return this;} +} diff --git a/400_xowa/src/gplx/xowa/specials/mgrs/Xoa_special_regy.java b/400_xowa/src/gplx/xowa/specials/mgrs/Xoa_special_regy.java index a27517de8..e532f9ce0 100644 --- a/400_xowa/src/gplx/xowa/specials/mgrs/Xoa_special_regy.java +++ b/400_xowa/src/gplx/xowa/specials/mgrs/Xoa_special_regy.java @@ -13,3 +13,19 @@ 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.specials.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; +import gplx.xowa.specials.*; +public class Xoa_special_regy { + private final Ordered_hash hash = Ordered_hash_.New_bry(); // NOTE: case-sensitive; case-insensitive requires lang, but regy is at app level + public Hash_adp_bry Safelist_pages() {return safelist_pages;} private final Hash_adp_bry safelist_pages = Hash_adp_bry.ci_u8(gplx.xowa.langs.cases.Xol_case_mgr_.U8()); + public int Len() {return hash.Len();} + public Xow_special_page Get_at(int i) {return (Xow_special_page)hash.Get_at(i);} + public Xow_special_page Get_by_or_null(byte[] key) {return (Xow_special_page)hash.Get_by(key);} + public void Add(Xow_special_page page) { + hash.Add(page.Special__meta().Key_bry(), page); + byte[][] aliases = page.Special__meta().Aliases(); + for (byte[] alias : aliases) + hash.Add(alias, page); + } + public void Add_many(Xow_special_page... ary) {for (Xow_special_page itm : ary) Add(itm);} +} diff --git a/400_xowa/src/gplx/xowa/specials/mgrs/Xosp_special_mgr.java b/400_xowa/src/gplx/xowa/specials/mgrs/Xosp_special_mgr.java index a27517de8..1d1285881 100644 --- a/400_xowa/src/gplx/xowa/specials/mgrs/Xosp_special_mgr.java +++ b/400_xowa/src/gplx/xowa/specials/mgrs/Xosp_special_mgr.java @@ -13,3 +13,40 @@ 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.specials.mgrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; +import gplx.core.net.*; +import gplx.xowa.htmls.*; +import gplx.xowa.wikis.*; +import gplx.xowa.langs.specials.*; +public class Xosp_special_mgr { +// private final Xowv_wiki wiki; + private final Hash_adp_bry hash; + public Xosp_special_mgr(Xowv_wiki wiki) { +// this.wiki = wiki; + // hash.Add_str_obj(Xow_special_meta_.Ttl__statistics , page_statistics); + this.hash = Hash_adp_bry.cs(); + } + public void Get_by_ttl(Xoh_page rv, Gfo_url url, Xoa_ttl ttl) { +// Xosp_fbrow_rslt rslt = Xosp_fbrow_special.Gen(url.Qargs(), wiki.Appv().Wiki_mgr()); +// rv.Init(wiki, null, ttl, -1); +// rv.Body_(rslt.Html_body()); +// rv.Html_head_xtn_(rslt.Html_head()); + } + public void Get_by_url1(Xow_wiki wiki, Xoa_page page, Xoa_url url, Xoa_ttl ttl) { + int slash_pos = Bry_find_.Find_fwd(ttl.Page_txt_wo_qargs(), Xoa_ttl.Subpage_spr); // check for slash + byte[] special_name = slash_pos == Bry_find_.Not_found + ? ttl.Base_txt_wo_qarg() // no slash found; use base_txt; ignore qry args and just get page_names; EX: Search/Earth?fulltext=y; Allpages?from=Earth... + : Bry_.Mid(ttl.Page_txt_wo_qargs(), 0, slash_pos); // slash found; use root page; EX: Special:ItemByTitle/enwiki/Earth + Object o = hash.Get_by_bry(special_name); + if (o == null) { + Xol_specials_itm special_itm = wiki.Lang().Specials_mgr().Get_by_alias(special_name); + if (special_itm != null) + o = hash.Get_by_bry(special_itm.Special()); + } + if (o != null) { + // Xow_special_page special = (Xow_special_page)o; + // page.Revision_data().Modified_on_(Datetime_now.Get()); + // special.Special__gen(wiki, page, url, ttl); + } + } +} diff --git a/400_xowa/src/gplx/xowa/specials/movePage/Move_page.java b/400_xowa/src/gplx/xowa/specials/movePage/Move_page.java index a27517de8..4110d9848 100644 --- a/400_xowa/src/gplx/xowa/specials/movePage/Move_page.java +++ b/400_xowa/src/gplx/xowa/specials/movePage/Move_page.java @@ -13,3 +13,172 @@ 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.specials.movePage; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; +import gplx.core.primitives.*; import gplx.core.brys.fmtrs.*; import gplx.core.net.*; import gplx.core.net.qargs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.langs.msgs.*; +import gplx.xowa.htmls.hrefs.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.parsers.utils.*; +public class Move_page implements Xow_special_page { + private Move_trg_ns_list_fmtr ns_list_fmtr = new Move_trg_ns_list_fmtr(); + private Move_url_args args = new Move_url_args(); + private Xoa_ttl src_ttl; + public Xow_special_meta Special__meta() {return Xow_special_meta_.Itm__move_page;} + public void Special__gen(Xow_wiki wikii, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl) { + Xowe_wiki wiki = (Xowe_wiki)wikii; Xoae_page page = (Xoae_page)pagei; + args.Parse(url); + byte[] src_ttl_bry = args.Src_ttl(); + src_ttl = Xoa_ttl.Parse(wiki, src_ttl_bry); + if (args.Submitted()) { + Exec_rename(wiki, page); + return; + } + byte[] html = Bld_html(page); + page.Html_data().Html_restricted_n_(); // [[Special:]] pages allow all HTML + page.Db().Text().Text_bry_(html); + } + private void Exec_rename(Xowe_wiki wiki, Xoae_page page) { + gplx.xowa.wikis.dbs.Xodb_save_mgr save_mgr = wiki.Db_mgr().Save_mgr(); + int trg_ns_id = args.Trg_ns(); + Xow_ns trg_ns = wiki.Ns_mgr().Ids_get_or_null(trg_ns_id); if (trg_ns == null) throw Err_.new_wo_type("unknown ns", "ns", trg_ns_id); + byte[] trg_ttl_bry = args.Trg_ttl(); + Xoa_ttl trg_ttl = Xoa_ttl.Parse(wiki, trg_ns_id, trg_ttl_bry); + Xowd_page_itm src_page = new Xowd_page_itm(); + wiki.Db_mgr().Load_mgr().Load_by_ttl(src_page, src_ttl.Ns(), src_ttl.Page_db()); + page.Db().Page().Id_(src_page.Id()).Modified_on_(src_page.Modified_on()); + page.Db().Text().Text_bry_(src_page.Text()); + if (args.Create_redirect()) { // NOTE: not tested; DATE:2014-02-27 + save_mgr.Data_update(page, Xop_redirect_mgr.Make_redirect_text(trg_ttl.Full_db())); + Xowd_page_itm trg_page = new Xowd_page_itm(); + boolean trg_page_exists = wiki.Db_mgr().Load_mgr().Load_by_ttl(trg_page, trg_ns, trg_ttl_bry); + if (trg_page_exists) + save_mgr.Data_update(page, page.Db().Text().Text_bry()); + else + save_mgr.Data_create(trg_ttl, page.Db().Text().Text_bry()); + } + else + save_mgr.Data_rename(page, trg_ns_id, trg_ttl_bry); + wiki.Data_mgr().Redirect(page, trg_ns.Gen_ttl(trg_ttl_bry)); + } + private byte[] Bld_html(Xoae_page page) { + Xowe_wiki wiki = page.Wikie(); Xow_msg_mgr msg_mgr = wiki.Msg_mgr(); + if (src_ttl == null) return Bry_.Empty; + ns_list_fmtr.Init_by_page(wiki, page, src_ttl); + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_m001(); + wiki.Parser_mgr().Main().Parse_text_to_html(tmp_bfr, wiki.Parser_mgr().Ctx(), page, true, msg_mgr.Val_by_key_obj("movepagetext")); + fmtr_all.Bld_bfr_many(tmp_bfr + , msg_mgr.Val_by_key_obj("move-page-legend") + , Bry_.Add(Xoh_href_.Bry__wiki, src_ttl.Full_db()) + , gplx.langs.htmls.Gfh_utl.Escape_html_as_bry(src_ttl.Full_txt_w_ttl_case()) + , src_ttl.Full_txt_w_ttl_case() + , msg_mgr.Val_by_key_obj("newtitle") + , ns_list_fmtr + , args.Trg_ttl() + , msg_mgr.Val_by_key_obj("move-leave-redirect") + , msg_mgr.Val_by_key_obj("movepagebtn") + ); + return tmp_bfr.To_bry_and_rls(); + } + private Bry_fmtr fmtr_all = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "
      " + , "
      " + , " ~{move-page-legend}" + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , "" + , " " + , " " + , " " + , " " + , "
      Move page:" + , " ~{src_text}" + , "
      " + , " " + , " " + , " " + , " " + , " " + , "
       " + , " " + , "
      " + , "
      " + , "
      " + ), "move-page-legend", "src_href", "src_title", "src_text", "newtitle", "trg_ns_list", "trg_title", "move-leave-redirect", "movepagebtn"); + + public Xow_special_page Special__clone() {return this;} +} +class Move_trg_ns_list_fmtr implements gplx.core.brys.Bfr_arg { + private Xowe_wiki wiki; private Xoa_ttl ttl; + public void Init_by_page(Xowe_wiki wiki, Xoae_page page, Xoa_ttl ttl) { + this.wiki = wiki; + this.ttl = ttl; + } + public void Bfr_arg__add(Bry_bfr bfr) { + Xow_ns_mgr ns_mgr = wiki.Ns_mgr(); + int ns_len = ns_mgr.Ids_len(); + for (int i = 0; i < ns_len; i++) { + Xow_ns ns = ns_mgr.Ids_get_at(i); + if (ns.Is_meta()) continue; // ignore [[Special:]] and [[Media:]] + byte[] bry_selected = ttl.Ns().Id() == ns.Id() ? Bry_selected : Bry_.Empty; + fmtr.Bld_bfr_many(bfr, ns.Id(), bry_selected, ns.Name_combo()); + } + } + private static final byte[] Bry_selected = Bry_.new_a7(" selected=''"); + private Bry_fmtr fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , " " + ), "ns_id", "ns_selected", "ns_name"); +} +class Move_url_args { + public boolean Submitted() {return submitted;} private boolean submitted; + public byte[] Src_ttl() {return src_ttl;} private byte[] src_ttl; + public byte[] Trg_ttl() {return trg_ttl;} private byte[] trg_ttl; + public int Trg_ns() {return trg_ns;} private int trg_ns; + public boolean Create_redirect() {return create_redirect;} private boolean create_redirect; + public void Parse(Xoa_url url) { + this.Clear(); + Gfo_qarg_itm[] args = url.Qargs_ary(); + int args_len = args.length; + for (int i = 0; i < args_len; i++) { + Gfo_qarg_itm arg = args[i]; + Object tid_obj = arg_keys.Get_by(arg.Key_bry()); + byte[] val_bry = arg.Val_bry(); + if (tid_obj != null) { + switch (((Byte_obj_val)tid_obj).Val()) { + case Key_submitted: submitted = true; break; // wpMove will only be in query_args if move button is pressed + case Key_src_ttl: src_ttl = val_bry; break; + case Key_trg_ns: trg_ns = Bry_.To_int(val_bry); break; + case Key_trg_ttl: trg_ttl = val_bry; break; + case Key_create_redirect: create_redirect = Bry_.To_bool_by_int(val_bry); break; + } + } + } + } + private void Clear() { + submitted = false; + src_ttl = trg_ttl = null; + trg_ns = Int_.Min_value; + create_redirect = false; + } + private static final byte Key_submitted = 1, Key_src_ttl = 2, Key_trg_ns = 3, Key_trg_ttl = 4, Key_create_redirect = 5; + private static final Hash_adp_bry arg_keys = Hash_adp_bry.ci_a7() + .Add_str_byte("wpMove" , Key_submitted) + .Add_str_byte("wpOldTitle" , Key_src_ttl) + .Add_str_byte("wpNewTitleNs" , Key_trg_ns) + .Add_str_byte("wpNewTitleMain" , Key_trg_ttl) + .Add_str_byte("wpLeaveRedirect" , Key_create_redirect) + ; +} diff --git a/400_xowa/src/gplx/xowa/specials/nearby/Nearby_mgr.java b/400_xowa/src/gplx/xowa/specials/nearby/Nearby_mgr.java index a27517de8..8e94a75bd 100644 --- a/400_xowa/src/gplx/xowa/specials/nearby/Nearby_mgr.java +++ b/400_xowa/src/gplx/xowa/specials/nearby/Nearby_mgr.java @@ -13,3 +13,187 @@ 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.specials.nearby; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.lnkis.*; +public class Nearby_mgr implements Xow_special_page { + Xowe_wiki wiki; byte[] trg; + private Hash_adp_bry excluded = Hash_adp_bry.ci_a7(); + private Hash_adp_bry visited = Hash_adp_bry.ci_a7(); + List_adp trail = List_adp_.New(); + List_adp results = List_adp_.New(); + int results_cur = 0; +// int depth_max = 5; +// int pages_count = 0; + Bry_bfr tmp_bfr = Bry_bfr_.New(); + public int Results_max() {return results_max;} public Nearby_mgr Results_max_(int v) {results_max = v; return this;} private int results_max = 1; + public Xow_special_meta Special__meta() {return Xow_special_meta_.Itm__nearby;} + public void Special__gen(Xow_wiki wikii, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl) { + Xowe_wiki wiki = (Xowe_wiki)wikii; Xoae_page page = (Xoae_page)pagei; + page.Db().Text().Text_bry_(Bld_html(wiki)); + page.Html_data().Html_restricted_n_(); // [[Special:]] pages allow all HTML +// wiki.Parser_mgr().Parse(page, false); // do not clear else previous Search_text will be lost + } + byte[] Bld_html(Xowe_wiki wiki) { + form_fmtr.Bld_bfr_many(tmp_bfr); + List_adp list = Find_from_to(wiki, Bry_.new_a7("Earth"), Bry_.new_a7("Atom"), excluded); + tmp_bfr.Add_str_a7(""); + int len = list.Count(); + for (int i = 0; i < len; i++) { + Nearby_rslt rslt = (Nearby_rslt)list.Get_at(i); + tmp_bfr.Add_str_a7(""); + int cell_len = rslt.Len(); + for (int j = 0; j < cell_len; j++) { + Xoa_ttl ttl = (Xoa_ttl)rslt.Get_at(j); + tmp_bfr.Add_str_a7(""); + } + tmp_bfr.Add_str_a7(""); + } + tmp_bfr.Add_str_a7("
      [["); + tmp_bfr.Add(ttl.Page_db()); + tmp_bfr.Add_str_a7("]]
      "); + return tmp_bfr.To_bry_and_clear(); + } + Bry_fmtr form_fmtr = Bry_fmtr.new_(String_.Concat_lines_nl + ( "
      " + , "" + , " " + , " " + , " " + , " " + , " " + , "
      From:
      To:
      Max:
      Skip:
      " + , "
      " + )); + Xoa_ttl trg_ttl; + Ordered_hash src_pool = Ordered_hash_.New_bry(); + public List_adp Find_from_to(Xowe_wiki wiki, byte[] src_bry, byte[] trg_bry, Hash_adp_bry excluded) { + this.wiki = wiki; this.excluded = excluded; + Xoa_ttl src_ttl = Xoa_ttl.Parse(wiki, src_bry); if (src_ttl == null) return List_adp_.Noop; + trg_ttl = Xoa_ttl.Parse(wiki, trg_bry); if (trg_ttl == null) return List_adp_.Noop; + trg = trg_ttl.Page_db(); + trail.Clear(); + results.Clear(); + results_cur = 0; +// pages_count = 0; + visited.Clear(); + src_pool.Clear(); + src_pool.Add(src_ttl.Page_db(), new Nearby_itmx(trail, src_ttl)); + Examine_page(src_pool); + return results; + } + private void Examine_page(Ordered_hash src_pool){ + int len = src_pool.Count(); + Ordered_hash next_pool = Ordered_hash_.New_bry(); + for (int i = 0; i < len; i++) { + Nearby_itmx itmx = (Nearby_itmx)src_pool.Get_at(i); + Xoa_ttl ttl = itmx.Ttl(); + byte[] ttl_bry = ttl.Page_db(); + if (excluded.Has(ttl_bry)) continue; + if (visited.Has(ttl_bry)) continue; + visited.Add_bry_bry(ttl_bry); + Xoae_page page = wiki.Data_mgr().Load_page_by_ttl(ttl); + if (page.Db().Page().Exists_n()) continue; + wiki.Parser_mgr().Parse(page, true); + Ordered_hash lnkis = Ordered_hash_.New_bry(); + Collect_lnkis(lnkis, page.Root()); + if (lnkis.Has(trg)) { + ++results_cur; + results.Add(new Nearby_rslt(itmx.Trail(), trg_ttl)); + if (results_cur == results_max) return; + } + int lnkis_len = lnkis.Count(); + for (int j = 0; j < lnkis_len; j++) { + Xoa_ttl lnki_ttl = (Xoa_ttl)lnkis.Get_at(j); + if (next_pool.Has(lnki_ttl.Page_db())) continue; + Nearby_itmx next_itmx = new Nearby_itmx(itmx.Trail(), lnki_ttl); + next_pool.Add(lnki_ttl.Page_db(), next_itmx); + } + } + if (next_pool.Count() > 0) + Examine_page(next_pool); +// ++pages_count; +// wiki.Parser_mgr().Parse(page, true); +// Ordered_hash lnkis = Ordered_hash_.New_bry(); +// int len = lnkis.Count(); +// for (int i = 0; i < len; i++) { +// Xoa_ttl lnki_ttl = (Xoa_ttl)lnkis.Get_at(i); +// if (!lnki_ttl.Ns().Id_main()) continue; +// if (Bry_.Eq(lnki_ttl.Page_db(), trg)) continue; // skip trg page +// trail.Add(lnki_ttl); +// Examine_page(wiki, lnki_ttl, trail); +// List_adp_.Del_at_last(trail); +// if (results_cur == results_max) return; +// } + } +// private void Examine_page(Xowe_wiki wiki, Xoa_ttl ttl, List_adp trail){ +// byte[] ttl_bry = ttl.Page_db(); +// if (excluded.Has(ttl_bry)) return; +// if (visited.Has(ttl_bry)) return; +// visited.Add_bry_bry(ttl_bry); +// Xoae_page page = wiki.Data_mgr().Get_page(ttl, false); +// if (page.Missing()) return; +// ++pages_count; +// wiki.Parser_mgr().Parse(page, true); +// Ordered_hash lnkis = Ordered_hash_.New_bry(); +// Collect_lnkis(lnkis, page.Root()); +// if (lnkis.Has(trg)) { +// ++results_cur; +// results.Add(new Nearby_rslt(trail, trg_ttl)); +// if (results_cur == results_max) return; +// } +// int len = lnkis.Count(); +// for (int i = 0; i < len; i++) { +// Xoa_ttl lnki_ttl = (Xoa_ttl)lnkis.Get_at(i); +// if (!lnki_ttl.Ns().Id_main()) continue; +// if (Bry_.Eq(lnki_ttl.Page_db(), trg)) continue; // skip trg page +// trail.Add(lnki_ttl); +// Examine_page(wiki, lnki_ttl, trail); +// List_adp_.Del_at_last(trail); +// if (results_cur == results_max) return; +// } +// } + private void Collect_lnkis(Ordered_hash lnkis, Xop_tkn_itm tkn) { + if (tkn.Tkn_tid() == Xop_tkn_itm_.Tid_lnki) { + Xop_lnki_tkn lnki_tkn = (Xop_lnki_tkn)tkn; + Xoa_ttl lnki_ttl = lnki_tkn.Ttl(); + if (!lnkis.Has(lnki_ttl.Page_db())) + lnkis.Add(lnki_ttl.Page_db(), lnki_ttl); + } + else { + int len = tkn.Subs_len(); + for (int i = 0; i < len; i++) { + Xop_tkn_itm sub = tkn.Subs_get(i); + Collect_lnkis(lnkis, sub); + } + } + } + + public Xow_special_page Special__clone() {return this;} +} +class Nearby_rslt { + public Nearby_rslt(List_adp trail, Xoa_ttl trg_ttl) { + int len = trail.Count(); + for (int i = 0; i < len; i++) { + Xoa_ttl ttl = (Xoa_ttl)trail.Get_at(i); + list.Add(ttl); + } + list.Add(trg_ttl); + } + public int Len() {return list.Count();} + public Xoa_ttl Get_at(int i) {return (Xoa_ttl)list.Get_at(i);} + List_adp list = List_adp_.New(); +} +class Nearby_itmx { + public Nearby_itmx(List_adp v, Xoa_ttl ttl) { + int len = v.Count(); + for (int i = 0; i < len; i++) { + Xoa_ttl v_ttl = (Xoa_ttl)v.Get_at(i); + trail.Add(v_ttl); + } + trail.Add(ttl); + this.ttl = ttl; + } + public Xoa_ttl Ttl() {return ttl;} private Xoa_ttl ttl; + public List_adp Trail() {return trail;} List_adp trail = List_adp_.New(); +} diff --git a/400_xowa/src/gplx/xowa/specials/nearby/Nearby_mgr_tst.java b/400_xowa/src/gplx/xowa/specials/nearby/Nearby_mgr_tst.java index a27517de8..f13adb913 100644 --- a/400_xowa/src/gplx/xowa/specials/nearby/Nearby_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/specials/nearby/Nearby_mgr_tst.java @@ -13,3 +13,63 @@ 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.specials.nearby; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; +import org.junit.*; +public class Nearby_mgr_tst { + @Before public void init() {fxt.Clear();} Nearby_mgr_fxt fxt = new Nearby_mgr_fxt(); + @Test public void Basic() { + fxt.Init_page("A", "[[B]]"); + fxt.Test_find("A", "B", "A|B"); + } + @Test public void Shortest() { + fxt.Init_page("A", "[[B]]"); + fxt.Init_page("B", "[[C]] [[D]]"); + fxt.Init_page("C", "[[D]]"); + fxt.Test_find("A", "D", "A|B|D"); + fxt.Init_results_max(2).Test_find("A", "D", "A|B|D", "A|B|C|D"); + } + @Test public void Circular() { + fxt.Init_page("A", "[[B]]"); + fxt.Init_page("B", "[[C]]"); + fxt.Init_page("C", "[[A]]"); + fxt.Test_find("A", "D", ""); + } + @Test public void Page_doesnt_exist() { + fxt.Init_page("A", "[[B]]"); + fxt.Test_find("A", "C", ""); + } +} +class Nearby_mgr_fxt { + public Nearby_mgr_fxt Clear() { + if (fxt == null) { + fxt = new Xop_fxt(); + nearby_mgr = new Nearby_mgr(); + excluded = Hash_adp_bry.ci_a7(); + tmp_bfr = Bry_bfr_.New(); + } + fxt.Reset(); + Io_mgr.Instance.InitEngine_mem(); + nearby_mgr.Results_max_(1); + return this; + } private Xop_fxt fxt; Nearby_mgr nearby_mgr; Hash_adp_bry excluded; Bry_bfr tmp_bfr; + public void Init_page(String ttl, String text) {fxt.Init_page_create(ttl, text);} + public Nearby_mgr_fxt Init_results_max(int v) {nearby_mgr.Results_max_(v); return this;} + public void Test_find(String src, String trg, String... expd) { + List_adp actl = nearby_mgr.Find_from_to(fxt.Wiki(), Bry_.new_a7(src), Bry_.new_a7(trg), excluded); + Tfds.Eq_ary(String_.SplitLines_nl(Xto_str(actl)), expd); + } + String Xto_str(List_adp list) { + int len = list.Count(); + for (int i = 0; i < len; i++) { + Nearby_rslt rslt = (Nearby_rslt)list.Get_at(i); + int ttls = rslt.Len(); + if (i != 0) tmp_bfr.Add_byte_nl(); + for (int j = 0; j < ttls; j++) { + Xoa_ttl ttl = rslt.Get_at(j); + if (j != 0) tmp_bfr.Add_byte(Byte_ascii.Pipe); + tmp_bfr.Add(ttl.Page_db()); + } + } + return tmp_bfr.To_str_and_clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/specials/statistics/Xop_statistics_page.java b/400_xowa/src/gplx/xowa/specials/statistics/Xop_statistics_page.java index a27517de8..c6613c195 100644 --- a/400_xowa/src/gplx/xowa/specials/statistics/Xop_statistics_page.java +++ b/400_xowa/src/gplx/xowa/specials/statistics/Xop_statistics_page.java @@ -13,3 +13,123 @@ 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.specials.statistics; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; import gplx.xowa.langs.numbers.*; +import gplx.xowa.wikis.nss.*; +public class Xop_statistics_page implements Xow_special_page { + private Xop_statistics_stats_page_grp stats_page = new Xop_statistics_stats_page_grp(); +// private Xop_statistics_stats_wiki_grp stats_wiki = new Xop_statistics_stats_wiki_grp(); + private Xop_statistics_stats_ns_grp stats_ns = new Xop_statistics_stats_ns_grp(); + public Xow_special_meta Special__meta() {return Xow_special_meta_.Itm__statistics;} + public void Special__gen(Xow_wiki wikii, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl) { + Xowe_wiki wiki = (Xowe_wiki)wikii; Xoae_page page = (Xoae_page)pagei; + byte[] html = Build_html(wiki); + page.Html_data().Html_restricted_n_(); // [[Special:]] pages allow all HTML + page.Db().Text().Text_bry_(html); + } + public byte[] Build_html(Xowe_wiki wiki) { + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_m001(); + stats_page.Wiki_(wiki); +// stats_wiki.Wiki_(wiki); + stats_ns.Wiki_(wiki); + fmtr_all.Bld_bfr_many(tmp_bfr, stats_page, stats_ns); + return tmp_bfr.To_bry_and_rls(); + } + private Bry_fmtr fmtr_all = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "
      " + , "~{page_stats}~{ns_stats}" + , "
      " + , "
      " + ), "page_stats", "ns_stats"); + + public Xow_special_page Special__clone() {return this;} +} +class Xop_statistics_stats_page_grp implements gplx.core.brys.Bfr_arg { + public void Wiki_(Xowe_wiki v) {this.wiki = v;} private Xowe_wiki wiki; + public void Bfr_arg__add(Bry_bfr bfr) { + byte[] lbl_header_pages = wiki.Msg_mgr().Val_by_id(Xol_msg_itm_.Id_statistics_header_pages); + byte[] lbl_articles = wiki.Msg_mgr().Val_by_id(Xol_msg_itm_.Id_statistics_articles); + byte[] lbl_pages = wiki.Msg_mgr().Val_by_id(Xol_msg_itm_.Id_statistics_pages); + byte[] lbl_pages_desc = wiki.Msg_mgr().Val_by_id(Xol_msg_itm_.Id_statistics_pages_desc); + Xol_num_mgr num_mgr = wiki.Lang().Num_mgr(); + fmtr_page.Bld_bfr_many(bfr, lbl_header_pages, lbl_articles, lbl_pages, lbl_pages_desc , num_mgr.Format_num_by_long(wiki.Stats().Num_articles()), num_mgr.Format_num_by_long(wiki.Stats().Num_pages())); + } + private Bry_fmtr fmtr_page = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , " " + , " ~{lbl_header_pages}" + , " " + , " " + , " ~{lbl_articles}" + , " ~{page_count_main}" + , " " + , " " + , " ~{lbl_pages}
      ~{lbl_pages_desc}" + , " ~{page_count_all}" + , " " + ), "lbl_header_pages", "lbl_articles", "lbl_pages", "lbl_pages_desc", "page_count_main", "page_count_all"); +} +class Xop_statistics_stats_ns_grp implements gplx.core.brys.Bfr_arg { + private Xop_statistics_stats_ns_itm ns_itm_fmtr = new Xop_statistics_stats_ns_itm(); + public void Wiki_(Xowe_wiki v) {this.wiki = v; ns_itm_fmtr.Wiki_(v);} private Xowe_wiki wiki; + public void Bfr_arg__add(Bry_bfr bfr) { + byte[] lbl_header_ns = wiki.Msg_mgr().Val_by_id(Xol_msg_itm_.Id_statistics_header_ns); + fmtr_ns_grp.Bld_bfr_many(bfr, lbl_header_ns, ns_itm_fmtr); + } + private Bry_fmtr fmtr_ns_grp = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , " " + , " ~{lbl_header_ns}" + , " ~{ns_itms}" + ), "lbl_header_ns", "ns_itms"); +} +class Xop_statistics_stats_ns_itm implements gplx.core.brys.Bfr_arg { + public void Wiki_(Xowe_wiki v) {this.wiki = v;} private Xowe_wiki wiki; + public void Bfr_arg__add(Bry_bfr bfr) { + Xow_ns_mgr ns_mgr = wiki.Ns_mgr(); + int ns_len = ns_mgr.Count(); + for (int i = 0; i < ns_len; i++) { + Xow_ns ns = ns_mgr.Ids_get_at(i); + if (ns.Is_meta()) continue; + if (ns.Count() == 0) continue; + byte[] ns_name = ns.Id_is_main() ? wiki.Msg_mgr().Val_by_id(Xol_msg_itm_.Id_ns_blankns) : ns.Name_ui(); + fmtr_ns_itm.Bld_bfr_many(bfr, ns_name, wiki.Lang().Num_mgr().Format_num(ns.Count())); + } + } + private Bry_fmtr fmtr_ns_itm = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , " " + , " ~{ns_name}" + , " ~{ns_count}" + , " " + ), "ns_name", "ns_count"); +} +class Xop_statistics_stats_wiki_grp implements gplx.core.brys.Bfr_arg { + public void Wiki_(Xowe_wiki v) {this.wiki = v;} private Xowe_wiki wiki; + public void Bfr_arg__add(Bry_bfr bfr) { + fmtr_wiki.Bld_bfr_many(bfr, wiki.Db_mgr().Tid_name(), wiki.Fsys_mgr().Root_dir().Raw(), Byte_.To_str(wiki.Db_mgr().Category_version()), wiki.Maint_mgr().Wiki_dump_date().XtoStr_fmt_iso_8561()); + } + private Bry_fmtr fmtr_wiki = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , " " + , " Wiki statistics" + , " " + , " " + , " Wiki format" + , " ~{wiki_format}" + , " " + , " " + , " Wiki location" + , " ~{wiki_url}" + , " " + , " " + , " Category level" + , " ~{ctg_version}" + , " " + , " " + , " Last page updated on" + , " ~{page_modified_max}" + , " " + ), "wiki_format", "wiki_url", "ctg_version", "page_modified_max"); +} diff --git a/400_xowa/src/gplx/xowa/specials/statistics/Xop_statistics_page_tst.java b/400_xowa/src/gplx/xowa/specials/statistics/Xop_statistics_page_tst.java index a27517de8..5def6f603 100644 --- a/400_xowa/src/gplx/xowa/specials/statistics/Xop_statistics_page_tst.java +++ b/400_xowa/src/gplx/xowa/specials/statistics/Xop_statistics_page_tst.java @@ -13,3 +13,41 @@ 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.specials.statistics; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; +import org.junit.*; +public class Xop_statistics_page_tst { +@Before public void init() {fxt.Clear();} private Xop_statistics_page_fxt fxt = new Xop_statistics_page_fxt(); + @Test public void Basic() { + fxt.Test_html(String_.Concat_lines_nl_skip_last + ( "
      " + , "" + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , " " + , "
      Page statistics
      Content pages0
      Pages
      All pages in the wiki, including talk pages, redirects, etc.
      0
      Namespace statistics
      " + , "
      " + )); + } +} +class Xop_statistics_page_fxt { + public void Clear() { + parser_fxt = new Xop_fxt(); + parser_fxt.Reset(); + wiki = parser_fxt.Wiki(); + special_page = wiki.Special_mgr().Page_statistics(); + } private Xop_fxt parser_fxt; private Xop_statistics_page special_page; private Xowe_wiki wiki; + public void Test_html(String expd) { + Tfds.Eq_str_lines(expd, String_.new_u8(special_page.Build_html(wiki))); + } +} diff --git a/400_xowa/src/gplx/xowa/specials/xowa/default_tab/Default_tab_page.java b/400_xowa/src/gplx/xowa/specials/xowa/default_tab/Default_tab_page.java index a27517de8..027cb38f8 100644 --- a/400_xowa/src/gplx/xowa/specials/xowa/default_tab/Default_tab_page.java +++ b/400_xowa/src/gplx/xowa/specials/xowa/default_tab/Default_tab_page.java @@ -13,3 +13,23 @@ 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.specials.xowa.default_tab; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; import gplx.xowa.specials.xowa.*; +public class Default_tab_page implements Xow_special_page { + public Xow_special_meta Special__meta() {return Xow_special_meta_.Itm__default_tab;} + public void Special__gen(Xow_wiki wiki, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl) { + Xoae_page page = (Xoae_page)pagei; + page.Db().Text().Text_bry_(Bry_.Empty); + + boolean nightmode_enabled = ((Xoae_app)wiki.App()).Gui_mgr().Nightmode_mgr().Enabled(); + page.Html_data().Custom_html_(nightmode_enabled ? DEFAULT_HTML_NIGHT : DEFAULT_HTML_DAY); + page.Html_data().Custom_tab_name_(Tab_name_bry); + } + public static final byte[] Tab_name_bry = Bry_.new_a7("New Tab"); + + public Xow_special_page Special__clone() {return this;} + + public static final byte[] + DEFAULT_HTML_DAY = Bry_.new_a7("") + , DEFAULT_HTML_NIGHT = Bry_.new_a7("") + ; +} diff --git a/400_xowa/src/gplx/xowa/specials/xowa/diags/Db_rdr_utl.java b/400_xowa/src/gplx/xowa/specials/xowa/diags/Db_rdr_utl.java index a27517de8..cd9c06713 100644 --- a/400_xowa/src/gplx/xowa/specials/xowa/diags/Db_rdr_utl.java +++ b/400_xowa/src/gplx/xowa/specials/xowa/diags/Db_rdr_utl.java @@ -13,3 +13,60 @@ 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.specials.xowa.diags; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; import gplx.xowa.specials.xowa.*; +import gplx.core.stores.*; +import gplx.dbs.*; +import gplx.dbs.engines.mems.*; +public class Db_rdr_utl { + public static void Load_and_write(Db_conn conn, String sql, Bry_bfr bfr) { + Write_to_bfr(bfr, Load(conn, sql)); + } + public static Mem_qry_set Load_as_qry_set(Db_conn conn, Dbmeta_fld_list fld_list, String sql) { + Mem_qry_set qry_set = new Mem_qry_set(); + DataRdr rdr = conn.Exec_sql_as_old_rdr(sql); + try { + int fld_count = rdr.FieldCount(); + while (rdr.MoveNextPeer()) { + Mem_row row = new Mem_row(); + for (int i = 0; i < fld_count; ++i) + row.Add(fld_list.Get_at(i).Name(), rdr.ReadAt(i)); + qry_set.Add(row); + } + } + finally { + rdr.Rls(); + } + return qry_set; + } + public static Object[][] Load(Db_conn conn, String sql) { + List_adp list = List_adp_.New(); + DataRdr rdr = conn.Exec_sql_as_old_rdr(sql); + try { + int fld_count = rdr.FieldCount(); + while (rdr.MoveNextPeer()) { + Object[] row = new Object[fld_count]; + for (int i = 0; i < fld_count; ++i) { + row[i] = rdr.ReadAt(i); + } + list.Add(row); + } + } + finally { + rdr.Rls(); + } + return (Object[][])list.To_ary_and_clear(Object[].class); + } + public static void Write_to_bfr(Bry_bfr bfr, Object[][] rows, int... skip) { + int rows_len = rows.length; + for (int i = 0; i < rows_len; ++i) { + Object[] row = (Object[])rows[i]; + int row_len = row.length; + for (int j = 0; j < row_len; ++j) { + if (skip != null && Int_.In(j, skip)) continue; + Object val = row[j]; + bfr.Add_obj(val).Add_byte_pipe(); + } + bfr.Add_byte_nl(); + } + } +} diff --git a/400_xowa/src/gplx/xowa/specials/xowa/diags/Xows_cmd__file_check.java b/400_xowa/src/gplx/xowa/specials/xowa/diags/Xows_cmd__file_check.java index a27517de8..090dbec2d 100644 --- a/400_xowa/src/gplx/xowa/specials/xowa/diags/Xows_cmd__file_check.java +++ b/400_xowa/src/gplx/xowa/specials/xowa/diags/Xows_cmd__file_check.java @@ -13,3 +13,125 @@ 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.specials.xowa.diags; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; import gplx.xowa.specials.xowa.*; +import gplx.core.net.*; import gplx.core.net.qargs.*; import gplx.core.envs.*; +import gplx.fsdb.*; import gplx.fsdb.meta.*; +import gplx.xowa.files.origs.*; +import gplx.xowa.apps.urls.*; +class Xows_cmd__file_check { + private Io_url tmp_dir; + public void Exec(Bry_bfr bfr, Xoa_app app, Xoa_url url, Gfo_qarg_mgr_old arg_hash) { + byte[] wiki_bry = arg_hash.Get_val_bry_or(Arg_wiki, null); if (wiki_bry == null) {Xoa_app_.Usr_dlg().Warn_many("", "", "special.cmd; no wiki: url=~{0}", url.Raw()); return;} + byte[] file_bry = arg_hash.Get_val_bry_or(Arg_file, null); if (file_bry == null) {Xoa_app_.Usr_dlg().Warn_many("", "", "special.cmd; no file: url=~{0}", url.Raw()); return;} + Xow_wiki wiki = app.Wiki_mgri().Get_by_or_make_init_y(wiki_bry); + tmp_dir = wiki.Fsys_mgr().Tmp_dir(); + boolean schema_1 = wiki.File__fsdb_core().File__schema_is_1(); + Fsdb_db_file atr_main = null; + try {Write_kv(bfr, "machine.op_sys", Op_sys.Cur().Os_name());} catch (Exception e) {bfr.Add_str_u8(Err_.Message_gplx_full(e));} + try {Write_kv(bfr, "app.version", Xoa_app_.Version);} catch (Exception e) {bfr.Add_str_u8(Err_.Message_gplx_full(e));} + try {Write_kv(bfr, "cfg_file_retrieve", ((Xowe_wiki)wiki).File_mgr().Cfg_download().Enabled());} catch (Exception e) {bfr.Add_str_u8(Err_.Message_gplx_full(e));} + try {Write_kv(bfr, "fsdb.schema_is_1", schema_1);} catch (Exception e) {bfr.Add_str_u8(Err_.Message_gplx_full(e));} + try {Write_kv(bfr, "fsdb.mnt_file", wiki.File__fsdb_core().File__mnt_file().Url());} catch (Exception e) {bfr.Add_str_u8(Err_.Message_gplx_full(e));} + try {Write_kv(bfr, "fsdb.abc_file", wiki.File__fsdb_core().File__abc_file__at(Fsm_mnt_mgr.Mnt_idx_main).Url());} catch (Exception e) {bfr.Add_str_u8(Err_.Message_gplx_full(e));} + try { + atr_main = wiki.File__fsdb_core().File__atr_file__at(Fsm_mnt_mgr.Mnt_idx_main); + Write_kv(bfr, "fsdb.atr_file", atr_main.Url()); + } catch (Exception e) {bfr.Add_str_u8(Err_.Message_gplx_full(e));} + try {Write_kv(bfr, "fsdb.orig", wiki.File__fsdb_core().File__orig_tbl_ary()[0].Conn().Conn_info().Raw());} catch (Exception e) {bfr.Add_str_u8(Err_.Message_gplx_full(e));} + Fsdb_sql_mkr sql_mkr = schema_1 ? Fsdb_sql_mkr__v1.Instance : Fsdb_sql_mkr__v2.Instance; + String sql = ""; + try { + Xof_orig_tbl orig_tbl = wiki.File__fsdb_core().File__orig_tbl_ary()[0]; + sql = sql_mkr.Orig_by_ttl(file_bry); + Write_sect(bfr, "fsdb.orig.select", sql); + Db_rdr_utl.Load_and_write(orig_tbl.Conn(), sql, bfr); + } catch (Exception e) {bfr.Add_str_u8(Err_.Message_gplx_full(e));} + Object[][] rows = null; + try { + sql = sql_mkr.Fil_by_ttl(file_bry); + Write_sect(bfr, "fsdb.fil.select", sql); + rows = Db_rdr_utl.Load(atr_main.Conn(), sql); + Db_rdr_utl.Write_to_bfr(bfr, rows); + Write_thms(bfr, file_bry, sql_mkr, wiki.File__mnt_mgr().Mnts__get_main(), atr_main, rows); + } catch (Exception e) {bfr.Add_str_u8(Err_.Message_gplx_full(e));} + } + private void Write_thms(Bry_bfr bfr, byte[] file_bry, Fsdb_sql_mkr sql_mkr, Fsm_mnt_itm mnt_itm, Fsdb_db_file atr_main, Object[][] rows) { + int rows_len = rows.length; + for (int i = 0; i < rows_len; ++i) { + Object[] row = rows[i]; + int file_id = Int_.Cast(row[0]); + String sql = sql_mkr.Thm_by_id(file_id); + Write_sect(bfr, "fsdb.thm.select", sql); + Object[][] thm_rows = Db_rdr_utl.Load(atr_main.Conn(), sql); + Db_rdr_utl.Write_to_bfr(bfr, thm_rows); + Write_bins(bfr, file_bry, sql_mkr, mnt_itm, thm_rows, 0, 6); + Object bin_db_id_obj = row[4]; + if (bin_db_id_obj != null) { + Write_bins(bfr, file_bry, sql_mkr, mnt_itm, rows, 0, 4); + } + } + } + private void Write_bins(Bry_bfr bfr, byte[] file_bry, Fsdb_sql_mkr sql_mkr, Fsm_mnt_itm mnt_itm, Object[][] rows, int owner_id_ordinal, int bin_db_id_ordinal) { + int rows_len = rows.length; + for (int i = 0; i < rows_len; ++i) { + Object[] row = rows[i]; + int bin_db_id = Int_.Cast(row[bin_db_id_ordinal]); if (bin_db_id == -1) continue; + Fsm_bin_fil bin_db = mnt_itm.Bin_mgr().Dbs__get_at(bin_db_id); + int owner_id = Int_.Cast(row[owner_id_ordinal]); + String sql = sql_mkr.Bin_by_id(owner_id); + Write_sect(bfr, "fsdb.bin.select", sql); + Object[][] bin_rows = Db_rdr_utl.Load(bin_db.Conn(), sql); + Db_rdr_utl.Write_to_bfr(bfr, bin_rows, 4); + Export_bins(bfr, file_bry, bin_rows, 0, 4); + } + } + private void Export_bins(Bry_bfr bfr, byte[] file_bry, Object[][] rows, int owner_id_ordinal, int bin_data_ordinal) { + int rows_len = rows.length; + Bry_bfr tmp_bfr = Bry_bfr_.New(); + for (int i = 0; i < rows_len; ++i) { + Object[] row = rows[i]; + int owner_id = Int_.Cast(row[owner_id_ordinal]); + byte[] bin_data = (byte[])row[bin_data_ordinal]; + file_bry = gplx.xowa.files.repos.Xof_itm_ttl_.Remove_invalid(tmp_bfr, file_bry); + Io_url bin_url = tmp_dir.GenSubFil(String_.new_u8(file_bry)); + bin_url = tmp_dir.GenSubFil(bin_url.NameOnly() + "_" + Int_.To_str(owner_id) + bin_url.Ext()); + Write_kv(bfr, "fsdb.bin.export", bin_url.Raw()); + Write_kv(bfr, "fsdb.bin.len", bin_data.length); + Io_mgr.Instance.SaveFilBry(bin_url, bin_data); + } + } + private static void Write_kv(Bry_bfr bfr, String key, Object val) { + bfr.Add_str_u8(key); + bfr.Add_str_a7(" = "); + bfr.Add_obj(val); + bfr.Add_byte_nl(); + } + private static void Write_sect(Bry_bfr bfr, String key, Object val) { + bfr.Add_byte_nl(); + bfr.Add_str_u8("------------------------------------------------------").Add_byte_nl(); + bfr.Add_str_u8(key).Add_str_a7(": ").Add_obj(val).Add_byte_nl(); + bfr.Add_str_u8("------------------------------------------------------").Add_byte_nl(); + } + private static final byte[] Arg_wiki = Bry_.new_a7("wiki"), Arg_file = Bry_.new_a7("file"); + public static final Xows_cmd__file_check Instance = new Xows_cmd__file_check(); Xows_cmd__file_check() {} +} +interface Fsdb_sql_mkr { + String Orig_by_ttl(byte[] ttl); + String Fil_by_ttl(byte[] ttl); + String Thm_by_id(int id); + String Bin_by_id(int id); +} +abstract class Fsdb_sql_mkr__base { + public String Fil_by_ttl(byte[] ttl) {return String_.Format("SELECT * FROM fsdb_fil WHERE fil_name = '{0}';", ttl);} + public String Bin_by_id(int id) {return String_.Format("SELECT * FROM fsdb_bin WHERE bin_owner_id = {0};", id);} +} +class Fsdb_sql_mkr__v1 extends Fsdb_sql_mkr__base implements Fsdb_sql_mkr { + public String Orig_by_ttl(byte[] ttl) {return String_.Format("SELECT * FROM wiki_orig WHERE orig_ttl = '{0}';", ttl);} + public String Thm_by_id(int id) {return String_.Format("SELECT * FROM fsdb_xtn_thm WHERE thm_owner_id = {0};", id);} + public static final Fsdb_sql_mkr Instance = new Fsdb_sql_mkr__v1(); Fsdb_sql_mkr__v1() {} +} +class Fsdb_sql_mkr__v2 extends Fsdb_sql_mkr__base implements Fsdb_sql_mkr { + public String Orig_by_ttl(byte[] ttl) {return String_.Format("SELECT * FROM orig_reg WHERE orig_ttl = '{0}';", ttl);} + public String Thm_by_id(int id) {return String_.Format("SELECT * FROM fsdb_thm WHERE thm_owner_id = {0};", id);} + public static final Fsdb_sql_mkr Instance = new Fsdb_sql_mkr__v2(); Fsdb_sql_mkr__v2() {} +} diff --git a/400_xowa/src/gplx/xowa/specials/xowa/diags/Xows_cmd__fs_check.java b/400_xowa/src/gplx/xowa/specials/xowa/diags/Xows_cmd__fs_check.java index a27517de8..b8084660a 100644 --- a/400_xowa/src/gplx/xowa/specials/xowa/diags/Xows_cmd__fs_check.java +++ b/400_xowa/src/gplx/xowa/specials/xowa/diags/Xows_cmd__fs_check.java @@ -13,3 +13,46 @@ 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.specials.xowa.diags; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; import gplx.xowa.specials.xowa.*; +import gplx.core.ios.*; import gplx.core.net.*; import gplx.core.net.qargs.*; +import gplx.fsdb.meta.*; +import gplx.xowa.apps.urls.*; +class Xows_cmd__fs_check { + public void Exec(Bry_bfr bfr, Xoa_app app, Xoa_url url, Gfo_qarg_mgr_old arg_hash) { + byte[] dir_bry = arg_hash.Get_val_bry_or(Arg_dir, null); + if (dir_bry != null) { + Write_dir(bfr, Io_url_.new_dir_(String_.new_u8(dir_bry))); + return; + } + byte[] wiki_bry = arg_hash.Get_val_bry_or(Arg_wiki, null); if (wiki_bry == null) {Xoa_app_.Usr_dlg().Warn_many("", "", "special.cmd; no wiki: url=~{0}", url.Raw()); return;} + Xow_wiki wiki = app.Wiki_mgri().Get_by_or_make_init_y(wiki_bry); + Io_url wiki_dir = wiki.Fsys_mgr().Root_dir(); + Io_url file_dir = wiki.Fsys_mgr().File_dir(); + Write_dir(bfr, wiki_dir); + Write_dir(bfr, file_dir); + Write_dir(bfr, file_dir.GenSubDir(Fsm_mnt_tbl.Mnt_name_main)); + Write_dir(bfr, file_dir.GenSubDir(Fsm_mnt_tbl.Mnt_name_user)); + Write_dir(bfr, file_dir.GenSubDir("fsdb.update_00")); + } + private void Write_dir(Bry_bfr bfr, Io_url dir_url) { + bfr.Add_byte_nl().Add_str_a7("scanning: ").Add_str_u8(dir_url.Raw()).Add_byte_nl(); + if (!Io_mgr.Instance.ExistsDir(dir_url)) return; + IoItmDir dir_itm = Io_mgr.Instance.QueryDir_args(dir_url).Recur_(false).DirInclude_(true).ExecAsDir(); + IoItmList sub_itms = dir_itm.SubDirs(); int len = sub_itms.Count(); + for (int i = 0; i < len; ++i) { + try { + IoItmDir sub_itm = (IoItmDir)sub_itms.Get_at(i); + bfr.Add_str_a7("dir").Add_byte_pipe().Add_str_u8(sub_itm.Name()).Add_byte_nl(); + } catch (Exception e) {bfr.Add_str_u8(Err_.Message_gplx_full(e));} + } + sub_itms = dir_itm.SubFils(); len = sub_itms.Count(); + for (int i = 0; i < len; ++i) { + try { + IoItmFil sub_itm = (IoItmFil)sub_itms.Get_at(i); + bfr.Add_str_a7("fil").Add_byte_pipe().Add_str_u8(sub_itm.Name()).Add_byte_pipe().Add_long_variable(sub_itm.Size()).Add_byte_pipe().Add_dte(sub_itm.ModifiedTime()).Add_byte_nl(); + } catch (Exception e) {bfr.Add_str_u8(Err_.Message_gplx_full(e));} + } + } + public static final Xows_cmd__fs_check Instance = new Xows_cmd__fs_check(); Xows_cmd__fs_check() {} + private static final byte[] Arg_wiki = Bry_.new_a7("wiki"), Arg_dir = Bry_.new_a7("dir"); +} diff --git a/400_xowa/src/gplx/xowa/specials/xowa/diags/Xows_cmd__sql_dump.java b/400_xowa/src/gplx/xowa/specials/xowa/diags/Xows_cmd__sql_dump.java index a27517de8..6d1736c40 100644 --- a/400_xowa/src/gplx/xowa/specials/xowa/diags/Xows_cmd__sql_dump.java +++ b/400_xowa/src/gplx/xowa/specials/xowa/diags/Xows_cmd__sql_dump.java @@ -13,3 +13,38 @@ 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.specials.xowa.diags; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; import gplx.xowa.specials.xowa.*; +import gplx.core.ios.*; import gplx.core.primitives.*; import gplx.core.net.*; import gplx.core.net.qargs.*; +import gplx.dbs.*; +import gplx.fsdb.meta.*; +import gplx.xowa.apps.urls.*; +class Xows_cmd__sql_dump { + public void Exec(Bry_bfr bfr, Xoa_app app, Xoa_url url, Gfo_qarg_mgr_old arg_hash) { + Db_conn conn = null; + byte[] sql_bry = arg_hash.Get_val_bry_or(Arg_sql, null); if (sql_bry == null) {Xoa_app_.Usr_dlg().Warn_many("", "", "special.cmd; no sql: url=~{0}", url.Raw()); return;} + byte[] wiki_bry = arg_hash.Get_val_bry_or(Arg_wiki, null); + if (wiki_bry == null) { + byte[] db_file_bry = arg_hash.Get_val_bry_or(Arg_db_file, null); if (db_file_bry == null) {Xoa_app_.Usr_dlg().Warn_many("", "", "special.cmd; no db_type: url=~{0}", url.Raw()); return;} + conn = Db_conn_bldr.Instance.Get(Io_url_.new_fil_(String_.new_u8(db_file_bry))); + } + else { + byte[] db_type_bry = arg_hash.Get_val_bry_or(Arg_db_type, null); if (db_type_bry == null) {Xoa_app_.Usr_dlg().Warn_many("", "", "special.cmd; no db_type: url=~{0}", url.Raw()); return;} + Xow_wiki wiki = app.Wiki_mgri().Get_by_or_make_init_y(wiki_bry); + Byte_obj_val db_type_val = (Byte_obj_val)db_type_hash.Get_by_bry(db_type_bry); if (db_type_val == null) {Xoa_app_.Usr_dlg().Warn_many("", "", "special.cmd; bad db_type: url=~{0}", url.Raw()); return;} + switch (db_type_val.Val()) { + case Db_type_wiki_core: conn = wiki.Data__core_mgr().Db__core().Conn(); break; + case Db_type_fsdb_abc: conn = wiki.File__fsdb_core().File__abc_file__at(Fsm_mnt_mgr.Mnt_idx_main).Conn(); break; + case Db_type_fsdb_atr: conn = wiki.File__fsdb_core().File__atr_file__at(Fsm_mnt_mgr.Mnt_idx_main).Conn(); break; + } + } + Db_rdr_utl.Load_and_write(conn, String_.new_u8(sql_bry), bfr); + } + public static final Xows_cmd__sql_dump Instance = new Xows_cmd__sql_dump(); Xows_cmd__sql_dump() {} + private static final byte[] Arg_wiki = Bry_.new_a7("wiki"), Arg_db_file = Bry_.new_a7("db_file"), Arg_db_type = Bry_.new_a7("db_type"), Arg_sql = Bry_.new_a7("sql"); + private static final byte Db_type_fsdb_abc = 1, Db_type_fsdb_atr = 2, Db_type_wiki_core = 3; + private static final Hash_adp_bry db_type_hash = Hash_adp_bry.cs() + .Add_str_byte("fsdb.abc" , Db_type_fsdb_abc) + .Add_str_byte("fsdb.atr" , Db_type_fsdb_atr) + .Add_str_byte("wiki.core" , Db_type_wiki_core) + ; +} diff --git a/400_xowa/src/gplx/xowa/specials/xowa/diags/Xows_diag_page.java b/400_xowa/src/gplx/xowa/specials/xowa/diags/Xows_diag_page.java index a27517de8..0935c10fe 100644 --- a/400_xowa/src/gplx/xowa/specials/xowa/diags/Xows_diag_page.java +++ b/400_xowa/src/gplx/xowa/specials/xowa/diags/Xows_diag_page.java @@ -13,3 +13,34 @@ 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.specials.xowa.diags; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; import gplx.xowa.specials.xowa.*; +import gplx.core.primitives.*; import gplx.core.net.*; import gplx.core.net.qargs.*; +import gplx.xowa.apps.urls.*; +public class Xows_diag_page implements Xow_special_page { + private Gfo_qarg_mgr_old arg_hash = new Gfo_qarg_mgr_old(); + public Xow_special_meta Special__meta() {return Xow_special_meta_.Itm__diag;} + public void Special__gen(Xow_wiki wikii, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl) { + Xowe_wiki wiki = (Xowe_wiki)wikii; Xoae_page page = (Xoae_page)pagei; + arg_hash.Load(url.Qargs_ary()); + byte[] cmd_type_bry = arg_hash.Get_val_bry_or(Arg_type, null); if (cmd_type_bry == null) {Xoa_app_.Usr_dlg().Warn_many("", "", "special.cmd; no type: url=~{0}", url.Raw()); return;} + Byte_obj_val cmd_type_val = (Byte_obj_val)type_hash.Get_by_bry(cmd_type_bry); if (cmd_type_val == null) {Xoa_app_.Usr_dlg().Warn_many("", "", "special.cmd; bad type: url=~{0}", url.Raw()); return;} + Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_m001(); + bfr.Add_str_a7("
      \n");
      +		switch (cmd_type_val.Val()) {
      +			case Type_file_check:	Xows_cmd__file_check.Instance.Exec(bfr, wiki.App(), url, arg_hash); break;
      +			case Type_fs_check:		Xows_cmd__fs_check.Instance.Exec(bfr, wiki.App(), url, arg_hash); break;
      +			case Type_sql_dump:		Xows_cmd__sql_dump.Instance.Exec(bfr, wiki.App(), url, arg_hash); break;
      +		}
      +		bfr.Add_str_a7("
      \n"); + page.Db().Text().Text_bry_(bfr.To_bry_and_clear()); + } + private static final byte[] Arg_type = Bry_.new_a7("type"); + private static final byte Type_file_check = 1, Type_fs_check = 2, Type_sql_dump = 3; + private static final Hash_adp_bry type_hash = Hash_adp_bry.cs() + .Add_str_byte("file.check" , Type_file_check) + .Add_str_byte("fs.check" , Type_fs_check) + .Add_str_byte("sql.dump" , Type_sql_dump) + ; + + public Xow_special_page Special__clone() {return this;} +} diff --git a/400_xowa/src/gplx/xowa/specials/xowa/popup_history/Popup_history_page.java b/400_xowa/src/gplx/xowa/specials/xowa/popup_history/Popup_history_page.java index a27517de8..a8ebdcde3 100644 --- a/400_xowa/src/gplx/xowa/specials/xowa/popup_history/Popup_history_page.java +++ b/400_xowa/src/gplx/xowa/specials/xowa/popup_history/Popup_history_page.java @@ -13,3 +13,30 @@ 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.specials.xowa.popup_history; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; import gplx.xowa.specials.xowa.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.htmls.modules.popups.*; +public class Popup_history_page implements Xow_special_page { + public Xow_special_meta Special__meta() {return Xow_special_meta_.Itm__popup_history;} + public void Special__gen(Xow_wiki wikii, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl) { + Xowe_wiki wiki = (Xowe_wiki)wikii; Xoae_page page = (Xoae_page)pagei; + Xoae_page cur_page = wiki.Appe().Gui_mgr().Browser_win().Active_page(); if (cur_page == null) return; + Ordered_hash hash = cur_page.Popup_mgr().Itms(); + int len = hash.Count(); + Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_k004(); + for (int i = len - 1; i > -1; --i) { + Xow_popup_itm itm = (Xow_popup_itm)hash.Get_at(i); + if (Ttl_chk(itm.Page_ttl())) continue; + fmtr_main.Bld_bfr_many(bfr, itm.Page_href(), itm.Page_ttl().Full_txt_w_ttl_case()); + } + page.Db().Text().Text_bry_(bfr.Trim_end(Byte_ascii.Nl).To_bry_and_rls()); + page.Html_data().Html_restricted_n_(); + } + private Bry_fmtr fmtr_main = Bry_fmtr.new_("~{ttl}\n\n", "href", "ttl"); // NOTE: need to use anchor (as opposed to lnki or lnke) b/c xwiki will not work on all wikis + public static boolean Ttl_chk(Xoa_ttl ttl) { + return ttl.Ns().Id_is_special() + && Bry_.Eq(ttl.Page_db(), Xow_special_meta_.Itm__popup_history.Key_bry()); + } + + public Xow_special_page Special__clone() {return this;} +} diff --git a/400_xowa/src/gplx/xowa/specials/xowa/system_data/System_data_page.java b/400_xowa/src/gplx/xowa/specials/xowa/system_data/System_data_page.java index a27517de8..81bc88704 100644 --- a/400_xowa/src/gplx/xowa/specials/xowa/system_data/System_data_page.java +++ b/400_xowa/src/gplx/xowa/specials/xowa/system_data/System_data_page.java @@ -13,3 +13,58 @@ 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.specials.xowa.system_data; import gplx.*; import gplx.xowa.*; import gplx.xowa.specials.*; import gplx.xowa.specials.xowa.*; +import gplx.core.primitives.*; import gplx.core.brys.fmtrs.*; import gplx.core.net.*; import gplx.core.net.qargs.*; +import gplx.xowa.langs.*; +import gplx.xowa.apps.urls.*; +public class System_data_page implements Xow_special_page { + private Gfo_qarg_mgr_old arg_hash = new Gfo_qarg_mgr_old(); + public Xow_special_meta Special__meta() {return Xow_special_meta_.Itm__system_data;} + public void Special__gen(Xow_wiki wikii, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl) { + Xowe_wiki wiki = (Xowe_wiki)wikii; Xoae_page page = (Xoae_page)pagei; + arg_hash.Load(url.Qargs_ary()); + byte[] file_type = arg_hash.Get_val_bry_or(Arg_type, null); if (file_type == null) return; + Byte_obj_val type_val = (Byte_obj_val)type_hash.Get_by_bry(file_type); if (type_val == null) return; + Io_url file_url = Path_from_type(wiki, type_val.Val()); if (file_url == null) return; + + // get log text + byte[] file_txt = Io_mgr.Instance.LoadFilBry(file_url); + file_txt = gplx.langs.htmls.Gfh_utl.Escape_html_as_bry(file_txt, true, false, false, false, false); // escape < or "
      " in messages will cause pre to break + if (file_txt.length > Io_mgr.Len_mb) { + int file_txt_len = file_txt.length; + file_txt = Bry_.Add + ( Bry_.new_a7("*** truncated to 1 MB due to large file size: " + Int_.To_str(file_txt_len) + " ***\n\n") + , Bry_.Mid(file_txt, file_txt_len - Io_mgr.Len_mb, file_txt_len)); + } + + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_m001(); + fmtr_all.Bld_bfr_many(tmp_bfr, file_url.Raw(), file_txt); + page.Db().Text().Text_bry_(tmp_bfr.To_bry_and_rls()); + } + private static Io_url Path_from_type(Xowe_wiki wiki, byte type) { + Xoae_app app = wiki.Appe(); + switch (type) { + case Type_log_session: return app.Log_wtr().Session_fil(); + case Type_cfg_app: return app.Fsys_mgr().Cfg_app_fil(); + case Type_cfg_lang: return Xol_lang_itm_.xo_lang_fil_(app.Fsys_mgr(), wiki.Lang().Key_str()); + case Type_usr_history: return app.Usere().Fsys_mgr().App_data_history_fil(); + default: return null; + } + } + private static final byte[] Arg_type = Bry_.new_a7("type"); + private static final byte Type_log_session = 1, Type_cfg_app = 2, Type_cfg_lang = 3, Type_usr_history = 6; + private static final Hash_adp_bry type_hash = Hash_adp_bry.cs() + .Add_str_byte("log_session" , Type_log_session) + .Add_str_byte("cfg_app" , Type_cfg_app) + .Add_str_byte("cfg_lang" , Type_cfg_lang) + .Add_str_byte("usr_history" , Type_usr_history) + ; + private Bry_fmtr fmtr_all = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "

      Path: ~{path}" + , "

      " + , "
      "
      +	, "~{text}
      " + ), "path", "text"); + + public Xow_special_page Special__clone() {return this;} +} diff --git a/400_xowa/src/gplx/xowa/users/Xou_fsys_mgr.java b/400_xowa/src/gplx/xowa/users/Xou_fsys_mgr.java index a27517de8..60c824fcc 100644 --- a/400_xowa/src/gplx/xowa/users/Xou_fsys_mgr.java +++ b/400_xowa/src/gplx/xowa/users/Xou_fsys_mgr.java @@ -13,3 +13,30 @@ 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.users; import gplx.*; import gplx.xowa.*; +import gplx.xowa.apps.gfs.*; import gplx.xowa.wikis.*; +public class Xou_fsys_mgr implements Gfo_invk { + private Io_url wiki_root_dir; private Io_url cur_root; + public Xou_fsys_mgr(Io_url user_dir) { + this.cur_root = user_dir; + app_root_dir = cur_root.GenSubDir("app"); + app_temp_dir = app_root_dir.GenSubDir("tmp"); + app_temp_html_dir = app_temp_dir.GenSubDir("html"); + app_data_history_fil = app_root_dir.GenSubFil_nest("data", "history", "page_history.csv"); + wiki_root_dir = cur_root.GenSubDir("wiki"); + } + public Io_url Root_dir() {return cur_root;} + public Io_url Wiki_root_dir() {return wiki_root_dir;} + public Io_url Wiki_html_dir(String wiki) {return wiki_root_dir.GenSubDir_nest(wiki, "html");} + public Io_url App_root_dir() {return app_root_dir;} private Io_url app_root_dir; + public Io_url App_data_history_fil() {return app_data_history_fil;} private Io_url app_data_history_fil; + public Io_url App_temp_dir() {return app_temp_dir;} private Io_url app_temp_dir; + public Io_url App_temp_html_dir() {return app_temp_html_dir;} private Io_url app_temp_html_dir; + public Io_url App_data_dir() {return app_root_dir.GenSubDir_nest("data");} + public Io_url App_data_cfg_dir() {return app_root_dir.GenSubDir_nest("data", "cfg");} + public static final String Name_user_system_cfg = "user_system_cfg.gfs"; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_root_dir)) return cur_root; + else return Gfo_invk_.Rv_unhandled; + } private static final String Invk_root_dir = "root_dir"; +} diff --git a/400_xowa/src/gplx/xowa/users/Xou_user.java b/400_xowa/src/gplx/xowa/users/Xou_user.java index a27517de8..1c6644d43 100644 --- a/400_xowa/src/gplx/xowa/users/Xou_user.java +++ b/400_xowa/src/gplx/xowa/users/Xou_user.java @@ -13,3 +13,15 @@ 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.users; import gplx.*; import gplx.xowa.*; +import gplx.xowa.users.data.*; +import gplx.xowa.users.history.*; +import gplx.xowa.files.caches.*; +public interface Xou_user { + String Key(); + int Gender(); + Xou_fsys_mgr Fsys_mgr(); + Xou_db_mgr User_db_mgr(); + Xou_history_mgr History_mgr(); + Xow_wiki Wikii(); +} diff --git a/400_xowa/src/gplx/xowa/users/Xou_user_.java b/400_xowa/src/gplx/xowa/users/Xou_user_.java index a27517de8..0506ece8a 100644 --- a/400_xowa/src/gplx/xowa/users/Xou_user_.java +++ b/400_xowa/src/gplx/xowa/users/Xou_user_.java @@ -13,3 +13,53 @@ 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.users; import gplx.*; import gplx.xowa.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.cases.*; import gplx.xowa.wikis.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.domains.*; +class Xou_user_ { + public static Xowe_wiki new_or_create_(Xoue_user user, Xoae_app app) { + Io_url wiki_dir = app.Fsys_mgr().Home_wiki_dir(); + Xol_lang_itm lang = app.Lang_mgr().Get_by_or_new(Xol_lang_itm_.Key_en); + lang.Init_by_load(); // NOTE: lang.Load() must occur before Xowe_wiki.new() b/c wiki will create parsers based on lang + Xowe_wiki rv = new Xowe_wiki(app, lang, ns_home_(lang.Case_mgr()), Xow_domain_uid_.To_domain(Xow_domain_uid_.Tid_xowa), wiki_dir); + app.Wiki_mgr().Add(rv); + rv.Sys_cfg().Xowa_cmd_enabled_(true); + rv.Sys_cfg().Xowa_proto_enabled_(true); + return rv; + } + public static void User_system_cfg_make(Gfo_usr_dlg usr_dlg, Io_url cfg_fil) { + usr_dlg.Log_many(GRP_KEY, "user_system_cfg.create", "creating user_system_cfg.gfs: ~{0}", cfg_fil.Raw()); + Io_mgr.Instance.SaveFilStr(cfg_fil, User_system_cfg_text); + } + public static void Bookmarks_make(Xoae_app app, Xowe_wiki home_wiki) { + app.Usr_dlg().Log_many(GRP_KEY, "bookmarks.create", "creating bookmarks page"); + home_wiki.Db_mgr().Save_mgr().Data_create(Xoa_ttl.Parse(home_wiki, Bry_.new_a7("Data:Bookmarks")), Bry_.new_a7(Bookmarks_text)); + } + public static final String User_system_cfg_text = String_.Concat_lines_nl + ( "app.scripts.txns.get('user.prefs.general').version_('" + Xoa_app_.Version + "').bgn();" + , "app.files.download.enabled_('y');" // default to true; DATE:2015-01-05 + , "app.files.math.enabled_('y');" + , "app.files.math.renderer_('mathjax');" + , "app.scripts.txns.get('user.prefs.general').end();\n" + ); + public static final String Bookmarks_text = String_.Concat_lines_nl + ( "Bookmarks are added automatically to the bottom of the page. All other text is not modified." + , "" + , "Please delete bookmarks by editing this page." + ); + private static Xow_ns_mgr ns_home_(Xol_case_mgr case_mgr) { + Xow_ns_mgr rv = new Xow_ns_mgr(case_mgr); + rv = rv.Add_new(-2, "Media").Add_new(-1, "Special").Add_new(0, "").Add_new(1, "Talk").Add_new(2, "User").Add_new(3, "User talk").Add_new(4, "Wikipedia").Add_new(5, "Wikipedia talk") + .Add_new(6, "File").Add_new(7, "File talk").Add_new(8, "MediaWiki").Add_new(9, "MediaWiki talk").Add_new(10, "Template").Add_new(11, "Template talk") + .Add_new(12, "Help").Add_new(13, "Help talk").Add_new(14, "Category").Add_new(15, "Category talk").Add_new(100, "Portal").Add_new(101, "Portal talk") + .Add_new(gplx.xowa.xtns.wbases.Wdata_wiki_mgr.Ns_property, gplx.xowa.xtns.wbases.Wdata_wiki_mgr.Ns_property_name) + .Add_new(730, "Data").Add_new(731, "Data talk") + .Add_new(Xow_ns_.Tid__module, Xow_ns_.Key__module).Add_new(Xow_ns_.Tid__module_talk, Xow_ns_.Key__module_talk) + .Add_defaults() + ; + rv.Init(); + return rv; + } + static final String GRP_KEY = "xowa.user_"; +} diff --git a/400_xowa/src/gplx/xowa/users/Xou_user_mgr.java b/400_xowa/src/gplx/xowa/users/Xou_user_mgr.java index a27517de8..5d1d5b6dc 100644 --- a/400_xowa/src/gplx/xowa/users/Xou_user_mgr.java +++ b/400_xowa/src/gplx/xowa/users/Xou_user_mgr.java @@ -13,3 +13,24 @@ 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.users; import gplx.*; import gplx.xowa.*; +public class Xou_user_mgr implements Gfo_invk { + private final Ordered_hash regy = Ordered_hash_.New(); + private final Xoae_app app; + public Xou_user_mgr(Xoae_app app, Xoue_user user) {this.app = app; this.Add(user);} + public void Add(Xoue_user itm) {regy.Add(itm.Key(), itm);} + private Xoue_user GetByKey(String key) {return (Xoue_user)regy.Get_by(key);} + + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_get)) { + String user_key = m.ReadStr("key"); + Xoue_user user = GetByKey(user_key); + if (user == null) { + user = new Xoue_user(app, app.Fsys_mgr().Root_dir().GenSubDir_nest("user", user_key)); + this.Add(user); + } + return user; + } + else return Gfo_invk_.Rv_unhandled; + } private static final String Invk_get = "get"; +} diff --git a/400_xowa/src/gplx/xowa/users/Xou_user_tst.java b/400_xowa/src/gplx/xowa/users/Xou_user_tst.java index a27517de8..3da511b04 100644 --- a/400_xowa/src/gplx/xowa/users/Xou_user_tst.java +++ b/400_xowa/src/gplx/xowa/users/Xou_user_tst.java @@ -13,3 +13,30 @@ 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.users; import gplx.*; import gplx.xowa.*; +import org.junit.*; import gplx.xowa.wikis.xwikis.*; +public class Xou_user_tst { + private final Xou_user_fxt fxt = new Xou_user_fxt(); + @Test public void Available_from_fsys() { + Io_mgr.Instance.CreateDir(fxt.App().Fsys_mgr().Wiki_dir().GenSubDir("en.wikipedia.org")); + fxt.App().Usere().Available_from_fsys(); + fxt.Test_xwikis + ( fxt.Make_xwiki(Bool_.N, "home") + , fxt.Make_xwiki(Bool_.Y, "en.wikipedia.org") // available_from_fsys should mark as offline=y + ); + } +} +class Xou_user_fxt { + public Xoae_app App() {return app;} private Xoae_app app = Xoa_app_fxt.Make__app__edit(); + public String Make_xwiki(boolean offline, String name) {return String_.Concat_with_str("|", Yn.To_str(offline), name);} + public void Test_xwikis(String... expd) { + Xow_xwiki_mgr xwiki_mgr = app.Usere().Wiki().Xwiki_mgr(); + int len = xwiki_mgr.Len(); + String[] actl = new String[len]; + for (int i = 0; i < len; ++i) { + Xow_xwiki_itm xwiki_itm = xwiki_mgr.Get_at(i); + actl[i] = Make_xwiki(xwiki_itm.Offline(), String_.new_u8(xwiki_itm.Domain_name())); + } + Tfds.Eq_ary_str(expd, actl); + } +} diff --git a/400_xowa/src/gplx/xowa/users/Xoue_user.java b/400_xowa/src/gplx/xowa/users/Xoue_user.java index a27517de8..67758af10 100644 --- a/400_xowa/src/gplx/xowa/users/Xoue_user.java +++ b/400_xowa/src/gplx/xowa/users/Xoue_user.java @@ -13,3 +13,107 @@ 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.users; import gplx.*; import gplx.xowa.*; +import gplx.core.envs.*; +import gplx.dbs.*; import gplx.core.brys.fmtrs.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.xwikis.*; import gplx.xowa.users.history.*; import gplx.xowa.xtns.scribunto.*; import gplx.xowa.users.data.*; +import gplx.xowa.files.*; import gplx.xowa.files.caches.*; +import gplx.xowa.langs.genders.*; +public class Xoue_user implements Xou_user, Gfo_evt_mgr_owner, Gfo_invk { + public Xoue_user(Xoae_app app, Io_url user_dir) { + this.app = app; this.key = user_dir.NameOnly(); + this.ev_mgr = new Gfo_evt_mgr(this); + this.fsys_mgr = new Xou_fsys_mgr(user_dir); + this.user_db_mgr = new Xou_db_mgr(app); + this.history_mgr = new Xou_history_mgr(fsys_mgr.App_data_history_fil()); + } + public Gfo_evt_mgr Evt_mgr() {return ev_mgr;} private final Gfo_evt_mgr ev_mgr; + public String Key() {return key;} private String key; + public Xou_db_mgr User_db_mgr() {return user_db_mgr;} private final Xou_db_mgr user_db_mgr; + public Xow_wiki Wikii() {return this.Wiki();} + public int Gender() {return Xol_gender_.Tid_unknown;} + public Xoae_app Appe() {return app;} private final Xoae_app app; + public Xol_lang_itm Lang() {if (lang == null) {lang = app.Lang_mgr().Get_by_or_new(app.Sys_cfg().Lang()); lang.Init_by_load();} return lang;} private Xol_lang_itm lang; + public void Lang_(Xol_lang_itm v) { + lang = v; + this.Msg_mgr().Lang_(v); + wiki.Msg_mgr().Clear(); // clear home wiki msgs whenever lang changes; else messages cached from old lang will not be replaced; EX:Read/Edit; DATE:2014-05-26 + Gfo_evt_mgr_.Pub_val(this, Evt_lang_changed, lang); + } + public Xou_fsys_mgr Fsys_mgr() {return fsys_mgr;} private Xou_fsys_mgr fsys_mgr; + public Xowe_wiki Wiki() {if (wiki == null) wiki = Xou_user_.new_or_create_(this, app); return wiki;} private Xowe_wiki wiki; + public Xou_history_mgr History_mgr() {return history_mgr;} private Xou_history_mgr history_mgr; + public Xow_msg_mgr Msg_mgr() { + if (msg_mgr == null) + msg_mgr = new Xow_msg_mgr(this.Wiki(), this.Lang()); // NOTE: must call this.Lang() not this.lang, else nullRef exception when using "app.shell.fetch_page"; DATE:2013-04-12 + return msg_mgr; + } private Xow_msg_mgr msg_mgr; + public void Init_by_app(Xoae_app app) { + Io_url user_system_cfg = fsys_mgr.App_data_cfg_dir().GenSubFil(Xou_fsys_mgr.Name_user_system_cfg); + if (!Io_mgr.Instance.ExistsFil(user_system_cfg)) Xou_user_.User_system_cfg_make(app.Usr_dlg(), user_system_cfg); + user_db_mgr.Init_by_app(Bool_.N, fsys_mgr.Root_dir().OwnerDir().GenSubFil("xowa.user." + key + ".sqlite3")); // EX: /xowa/user/xowa.user.anonymous.sqlite3 + if (!Env_.Mode_testing()) { + this.Available_from_fsys(); + // data_mgr.Init_by_app(app); + } + } + public void App_term() { + gplx.xowa.guis.views.Xog_startup_win_.Shutdown(app, app.Gui_mgr().Browser_win().Win_box()); // save window position + history_mgr.Save(app); + if (app.Gui_mgr().Browser_win().Tab_mgr().Page_load_mode_is_url()) + Io_mgr.Instance.DeleteDirDeep(fsys_mgr.App_temp_html_dir()); + app.File_mgr().Cache_mgr().Db_term(); + } + public void Bookmarks_add(byte[] wiki_domain, byte[] ttl_full_txt) { + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_k004(); + bookmarks_add_fmtr.Bld_bfr_many(tmp_bfr, wiki_domain, ttl_full_txt); + byte[] new_entry = tmp_bfr.To_bry_and_rls(); + Xoa_ttl bookmarks_ttl = Xoa_ttl.Parse(wiki, Bry_data_bookmarks); + Xoae_page bookmarks_page = wiki.Data_mgr().Load_page_by_ttl(bookmarks_ttl); + byte[] new_data = Bry_.Add(bookmarks_page.Db().Text().Text_bry(), new_entry); + wiki.Db_mgr().Save_mgr().Data_update(bookmarks_page, new_data); + } private Bry_fmtr bookmarks_add_fmtr = Bry_fmtr.new_("* [[~{wiki_key}:~{page_name}]]\n", "wiki_key", "page_name"); byte[] Bry_data_bookmarks = Bry_.new_a7("Data:Bookmarks"); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_available_from_bulk)) Available_from_bulk(m.ReadBry("v")); + else if (ctx.Match(k, Invk_available_from_fsys)) Available_from_fsys(); + else if (ctx.Match(k, Invk_msgs)) return this.Msg_mgr(); + else if (ctx.Match(k, Invk_lang)) return lang; + else if (ctx.Match(k, Invk_bookmarks_add_fmt_)) bookmarks_add_fmtr.Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_wiki)) return this.Wiki(); // NOTE: mass parse relies on this being this.Wiki(), not wiki + else if (ctx.Match(k, Invk_history)) return history_mgr; + else if (ctx.Match(k, Invk_fsys)) return fsys_mgr; + else if (ctx.Match(k, "name")) return key; //throw Err_.new_unhandled(k); // OBSOLETE: used to return key + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk_available_from_fsys = "available_from_fsys", Invk_available_from_bulk = "available_from_bulk", Invk_bookmarks_add_fmt_ = "bookmarks_add_fmt_" + , Invk_wiki = "wiki", Invk_history = "history", Invk_fsys = "fsys", Invk_lang = "lang", Invk_msgs = "msgs"; + public static final String Key_xowa_user = "anonymous"; + public static final String Evt_lang_changed = "lang_changed"; + public void Available_from_fsys() { + Io_url[] dirs = Io_mgr.Instance.QueryDir_args(app.Fsys_mgr().Wiki_dir()).Recur_(false).DirOnly_().ExecAsUrlAry(); + Xowe_wiki usr_wiki = Wiki(); + int dirs_len = dirs.length; + for (int i = 0; i < dirs_len; i++) { + Io_url dir = dirs[i]; + String name = dir.NameOnly(); + if (String_.Eq(name, gplx.xowa.bldrs.cmds.utils.Xob_core_batch_utl.Dir_dump)) continue; // ignore "#dump" + byte[] dir_name_as_bry = Bry_.new_u8(name); + Xow_xwiki_itm xwiki = Available_add(usr_wiki, dir_name_as_bry); + if (xwiki != null) // Add_full can return null if adding invalid lang; should not apply here, but guard against null ref + xwiki.Offline_(true); // mark xwiki as offline; needed for available wikis sidebar; DATE:2014-09-21 + app.Setup_mgr().Maint_mgr().Wiki_mgr().Add(dir_name_as_bry); + } + } + private void Available_from_bulk(byte[] raw) { + byte[][] wikis = Bry_split_.Split(raw, Byte_ascii.Nl); + Xowe_wiki usr_wiki = Wiki(); + int wikis_len = wikis.length; + for (int i = 0; i < wikis_len; i++) + Available_add(usr_wiki, wikis[i]); + } + private Xow_xwiki_itm Available_add(Xowe_wiki usr_wiki, byte[] wiki_name) { + return usr_wiki.Xwiki_mgr().Add_by_atrs(wiki_name, wiki_name); + } +} diff --git a/400_xowa/src/gplx/xowa/users/Xouv_user.java b/400_xowa/src/gplx/xowa/users/Xouv_user.java index a27517de8..1b71eca0e 100644 --- a/400_xowa/src/gplx/xowa/users/Xouv_user.java +++ b/400_xowa/src/gplx/xowa/users/Xouv_user.java @@ -13,3 +13,28 @@ 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.users; import gplx.*; import gplx.xowa.*; +import gplx.dbs.*; +import gplx.xowa.users.data.*; import gplx.xowa.users.history.*; +import gplx.xowa.files.*; import gplx.xowa.files.caches.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; +import gplx.xowa.langs.genders.*; +public class Xouv_user implements Xou_user { + private Xoa_wiki_mgr wiki_mgr; + public Xouv_user(Xoa_app app, String key, Io_url user_dir) { + this.key = key; + this.fsys_mgr = new Xou_fsys_mgr(user_dir); + this.history_mgr = new Xou_history_mgr(fsys_mgr.App_data_history_fil()); + } + public String Key() {return key;} private String key; + public Xou_fsys_mgr Fsys_mgr() {return fsys_mgr;} private final Xou_fsys_mgr fsys_mgr; + public Xou_history_mgr History_mgr() {return history_mgr;} private final Xou_history_mgr history_mgr; + public Xou_db_mgr User_db_mgr() {return user_db_mgr;} private Xou_db_mgr user_db_mgr; + public int Gender() {return Xol_gender_.Tid_unknown;} + public Xow_wiki Wikii() {if (wiki == null) wiki = wiki_mgr.Get_by_or_make_init_y(Xow_domain_itm_.Bry__home); return wiki;} private Xow_wiki wiki; + public void Init_db(Xoa_app app, Xoa_wiki_mgr wiki_mgr, Io_url db_url) { + this.wiki_mgr = wiki_mgr; + this.user_db_mgr = new Xou_db_mgr(app); + user_db_mgr.Init_by_app(Bool_.Y, db_url); + } +} diff --git a/400_xowa/src/gplx/xowa/users/bmks/Dbui_tbl_itm__bmk.java b/400_xowa/src/gplx/xowa/users/bmks/Dbui_tbl_itm__bmk.java index a27517de8..6457346a7 100644 --- a/400_xowa/src/gplx/xowa/users/bmks/Dbui_tbl_itm__bmk.java +++ b/400_xowa/src/gplx/xowa/users/bmks/Dbui_tbl_itm__bmk.java @@ -13,3 +13,113 @@ 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.users.bmks; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.core.brys.fmtrs.*; import gplx.core.errs.*; +import gplx.langs.jsons.*; import gplx.langs.htmls.*; +import gplx.xowa.users.data.*; import gplx.xowa.users.bmks.*; +import gplx.xowa.htmls.bridges.*; import gplx.xowa.htmls.bridges.dbuis.*; import gplx.xowa.htmls.bridges.dbuis.tbls.*; import gplx.xowa.htmls.bridges.dbuis.fmtrs.*; +public class Dbui_tbl_itm__bmk implements Dbui_tbl_itm { + private final Xoa_app app; private final Xoud_bmk_itm_tbl tbl; + private final Dbui_tbl_fmtr tbl_fmtr = new Dbui_tbl_fmtr(); + private final Dbui_cells_fmtr cells_fmtr = new Dbui_cells_fmtr(); private final Dbui_val_fmtr edit_val_fmtr = Dbui_val_fmtr_.new_edit(); private final Dbui_val_fmtr view_val_fmtr = Dbui_val_fmtr_.new_view(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New_w_size(255); + private final Bridge_msg_bldr msg_bldr; + public Dbui_tbl_itm__bmk(Xoa_app app, Xoud_bmk_itm_tbl tbl) {this.app = app; this.tbl = tbl; this.msg_bldr = app.Html__bridge_mgr().Msg_bldr();} + public byte[] Key() {return key;} private static final byte[] key = Bry_.new_a7("bmk"); + public Dbui_btn_itm[] View_btns() {return view_btns;} + public Dbui_btn_itm[] Edit_btns() {return edit_btns;} + public Dbui_col_itm[] Cols() {return cols;} + public void Reg(Bridge_cmd_mgr bridge_mgr) { + Dbui_cmd_mgr dbui_mgr = Dbui_cmd_mgr.Instance; + dbui_mgr.Init_by_bridge(bridge_mgr); + dbui_mgr.Add(this); + } + public void Select(Bry_bfr bfr, int owner) { + Xoud_bmk_itm_row[] db_rows = tbl.Select_grp(owner); + Xow_wiki usr_wiki = app.User().Wikii(); + byte[] option_link = usr_wiki.Html__lnki_bldr().Href_(Bry_.new_a7("home"), usr_wiki.Ttl_parse(Bry_.new_a7("Options/Bookmarks"))).Img_16x16(gplx.xowa.htmls.core.htmls.utls.Xoh_img_path.Img_option).Bld_to_bry();// HOME + byte[] delete_confirm_msg = app.Api_root().Usr().Bookmarks().Delete_confirm() ? Msg__delete_confirm : Bry_.Empty; + tbl_fmtr.Write(bfr, this, option_link, delete_confirm_msg, To_ui_rows(db_rows)); + } private static final byte[] Msg__delete_confirm = Bry_.new_a7(" data-dbui-delete_confirm_msg='Are you sure you want to delete this bookmark?'"); + public String Del(byte[] row_id, byte[] row_pkey) { + Xoud_bmk_itm_row db_row = Get_db_row(row_pkey); if (db_row == null) return Fail_missing_row(row_pkey); + tbl.Delete(db_row.Id()); + return msg_bldr.To_json_str__empty(); + } + public String Edit(byte[] row_id, byte[] row_pkey) { + Xoud_bmk_itm_row db_row = Get_db_row(row_pkey); if (db_row == null) return Fail_missing_row(row_pkey); + Dbui_row_itm ui_row = Get_ui_row(db_row); + return Write_cells(edit_val_fmtr, edit_btns, row_id, ui_row); + } + public String Save(byte[] row_id, byte[] row_pkey, Dbui_val_hash vals) { + Xoud_bmk_itm_row db_row = Get_db_row(row_pkey); if (db_row == null) return Fail_missing_row(row_pkey); + byte[] new_name = vals.Get_val_as_bry("name"); + byte[] new_url_bry = vals.Get_val_as_bry("url"); + byte[] new_comment = vals.Get_val_as_bry("comment"); + Xoa_url new_url = app.User().Wikii().Utl__url_parser().Parse(new_url_bry); + if (new_url.Page_bry() == null) return msg_bldr.Clear().Notify_fail_(Err_msg.To_str("Url is invalid", "url", new_url.Raw())).To_json_str(); + tbl.Update(db_row.Id(), db_row.Owner(), db_row.Sort(), new_name, new_url.Wiki_bry(), new_url_bry, new_comment); + Dbui_row_itm ui_row = Get_ui_row(row_pkey, new_name, new_url_bry, new_comment); + return Write_cells(view_val_fmtr, view_btns, row_id, ui_row); + } + public String Reorder(byte[][] pkeys, int owner) { + Xoud_bmk_itm_row[] db_rows = tbl.Select_grp(Xoud_bmk_mgr.Owner_root); + int len = db_rows.length; if (len != pkeys.length) return msg_bldr.Clear().Notify_fail_(Err_msg.To_str("Rows have changed")).Notify_hint_("Please reload the page").To_json_str(); + for (int i = 0; i < len; ++i) { + int old_pkey = db_rows[i].Id(); + int new_pkey = Bry_.To_int_or_neg1(pkeys[i]); + if (old_pkey == new_pkey) continue; // order hasn't changed; EX: 5 in list; 4th moved to 5th; 1 through 3 will have same sort order; + tbl.Update_sort(new_pkey, i); + } + return msg_bldr.To_json_str__empty(); + } + public Dbui_row_itm[] To_ui_rows(Xoud_bmk_itm_row[] db_rows) { + int len = db_rows.length; + Dbui_row_itm[] rv = new Dbui_row_itm[len]; + for (int i = 0; i < len; ++i) + rv[i] = Get_ui_row(db_rows[i]); + return rv; + } + private String Write_cells(Dbui_val_fmtr val_fmtr, Dbui_btn_itm[] btns, byte[] row_id, Dbui_row_itm row) { + cells_fmtr.Ctor(val_fmtr, btns); + cells_fmtr.Init(row_id, row); + cells_fmtr.Bfr_arg__add(tmp_bfr); + return app.Html__bridge_mgr().Msg_bldr().Clear().Data("html", tmp_bfr.To_bry_and_clear()).To_json_str(); + } + private Xoud_bmk_itm_row Get_db_row(byte[] pkey) { + int bmk_id = Bry_.To_int(pkey); + return tbl.Select_or_null(bmk_id); + } + private Dbui_row_itm Get_ui_row(Xoud_bmk_itm_row row) {return Get_ui_row(Int_.To_bry(row.Id()), row.Name(), row.Url(), row.Comment());} + private Dbui_row_itm Get_ui_row(byte[] pkey, byte[] name, byte[] url, byte[] comment) { + Dbui_val_itm[] vals = new Dbui_val_itm[3]; + vals[0] = new Dbui_val_itm(name, Gfh_utl.Escape_html_as_bry(tmp_bfr, name)); + vals[1] = new Dbui_val_itm(url, url_fmtr.Bld_bry_many(tmp_bfr, Gfh_utl.Escape_for_atr_val_as_bry(tmp_bfr, Byte_ascii.Apos, url))); + vals[2] = new Dbui_val_itm(comment, Gfh_utl.Escape_html_as_bry(comment)); + return new Dbui_row_itm(this, pkey, vals); + } + private String Fail_missing_row(byte[] row_pkey) { + return msg_bldr.Clear().Notify_fail_(Err_msg.To_str("Item has been deleted", "key", row_pkey)).Notify_hint_("Please reload the page").To_json_str(); + } + private static final Dbui_col_itm[] cols = new Dbui_col_itm[] + { new Dbui_col_itm(Dbui_col_itm.Type_id_str , 150, "name" , "Name") + , new Dbui_col_itm(Dbui_col_itm.Type_id_str , 300, "url" , "Url") + , new Dbui_col_itm(Dbui_col_itm.Type_id_text , 300, "comment" , "Comment") + }; + private static final Dbui_btn_itm[] view_btns = new Dbui_btn_itm[] + { new Dbui_btn_itm("rows__edit" , "edit.png" , "edit") + , new Dbui_btn_itm("rows__delete" , "delete.png" , "delete") + }; + private static final Dbui_btn_itm[] edit_btns = new Dbui_btn_itm[] + { new Dbui_btn_itm("rows__save" , "save.png" , "save") + , new Dbui_btn_itm("rows__cancel" , "cancel.png" , "cancel") + }; + private static final Bry_fmtr url_fmtr = Bry_fmtr.new_("~{url}", "url"); + public static Dbui_tbl_itm__bmk get_or_new(Xoa_app app, Xoud_bmk_itm_tbl db_tbl) { + if (I == null) { + I = new Dbui_tbl_itm__bmk(app, db_tbl); + I.Reg(app.Html__bridge_mgr().Cmd_mgr()); + } + return I; + } private static Dbui_tbl_itm__bmk I; +} diff --git a/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_dir_row.java b/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_dir_row.java index a27517de8..6c95571d7 100644 --- a/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_dir_row.java +++ b/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_dir_row.java @@ -13,3 +13,13 @@ 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.users.bmks; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +public class Xoud_bmk_dir_row { + public Xoud_bmk_dir_row(int id, int owner, int sort, byte[] name) { + this.id = id; this.owner = owner; this.sort = sort; this.name = name; + } + public int Id() {return id;} private final int id; + public int Owner() {return owner;} private final int owner; + public int Sort() {return sort;} private final int sort; + public byte[] Name() {return name;} private final byte[] name; +} diff --git a/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_dir_tbl.java b/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_dir_tbl.java index a27517de8..944daedfa 100644 --- a/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_dir_tbl.java +++ b/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_dir_tbl.java @@ -13,3 +13,46 @@ 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.users.bmks; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; +public class Xoud_bmk_dir_tbl implements Rls_able { + private final String tbl_name = "bmk_dir"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_id, fld_owner, fld_sort, fld_name; + public Xoud_bmk_dir_tbl(Db_conn conn) { + this.conn = conn; + fld_id = flds.Add_int_pkey_autonum("dir_id"); + fld_owner = flds.Add_int("dir_owner"); + fld_sort = flds.Add_int("dir_sort"); + fld_name = flds.Add_str("dir_name", 255); + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public String Tbl_name() {return tbl_name;} + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds.To_fld_ary()));} + public void Insert(int owner, int sort, byte[] name) { + Db_stmt stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Clear() + .Val_int(fld_owner, owner).Val_int(fld_sort, sort).Val_bry_as_str(fld_name, name) + .Exec_insert(); + } + public void Update(int id, int owner, int sort, byte[] name) { + Db_stmt stmt_update = conn.Stmt_update_exclude(tbl_name, flds, fld_id); + stmt_update.Clear() + .Val_int(fld_owner, owner).Val_int(fld_sort, sort).Val_bry_as_str(fld_name, name) + .Crt_int(fld_id, id) + .Exec_update(); + } + public void Delete(int id) { + Db_stmt stmt_delete = conn.Stmt_delete(tbl_name, fld_id); + stmt_delete.Clear().Crt_int(fld_id, id).Exec_delete(); + } +// private Xoud_bmk_dir_row new_row(Db_rdr rdr) { +// return new Xoud_bmk_dir_row +// ( rdr.Read_int(fld_id) +// , rdr.Read_int(fld_owner) +// , rdr.Read_int(fld_sort) +// , rdr.Read_bry_by_str(fld_name) +// ); +// } + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_itm_row.java b/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_itm_row.java index a27517de8..2e0a46b30 100644 --- a/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_itm_row.java +++ b/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_itm_row.java @@ -13,3 +13,16 @@ 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.users.bmks; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +public class Xoud_bmk_itm_row { + public Xoud_bmk_itm_row(int id, int owner, int sort, byte[] name, byte[] wiki, byte[] url, byte[] comment) { + this.id = id; this.owner = owner; this.sort = sort; this.name = name; this.wiki = wiki; this.url = url; this.comment = comment; + } + public int Id() {return id;} private final int id; + public int Owner() {return owner;} private final int owner; + public int Sort() {return sort;} private final int sort; + public byte[] Name() {return name;} private final byte[] name; + public byte[] Wiki() {return wiki;} private final byte[] wiki; + public byte[] Url() {return url;} private final byte[] url; + public byte[] Comment() {return comment;} private final byte[] comment; +} diff --git a/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_itm_tbl.java b/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_itm_tbl.java index a27517de8..5ef3cc65a 100644 --- a/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_itm_tbl.java +++ b/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_itm_tbl.java @@ -13,3 +13,92 @@ 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.users.bmks; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; +public class Xoud_bmk_itm_tbl implements Rls_able { + private final String tbl_name = "bmk_itm"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_id, fld_owner, fld_sort, fld_name, fld_wiki, fld_url, fld_comment; + private Db_stmt stmt_update_sort; + public Xoud_bmk_itm_tbl(Db_conn conn) { + this.conn = conn; + fld_id = flds.Add_int_pkey_autonum("itm_id"); + fld_owner = flds.Add_int("itm_owner"); + fld_sort = flds.Add_int("itm_sort"); + fld_name = flds.Add_str("itm_name" , 255); + fld_wiki = flds.Add_str("itm_wiki" , 255); + fld_url = flds.Add_str("itm_url" , 1024); + fld_comment = flds.Add_str("itm_comment" , 4096); + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public String Tbl_name() {return tbl_name;} + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds.To_fld_ary()));} + public void Insert(int owner, int sort, byte[] name, byte[] wiki, byte[] url, byte[] comment) { + Db_stmt stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Clear() + .Val_int(fld_owner, owner).Val_int(fld_sort, sort).Val_bry_as_str(fld_name, name) + .Val_bry_as_str(fld_wiki, wiki).Val_bry_as_str(fld_url, url).Val_bry_as_str(fld_comment, comment) + .Exec_insert(); + } + public void Update(int id, int owner, int sort, byte[] name, byte[] wiki, byte[] url, byte[] comment) { + Db_stmt stmt_update = conn.Stmt_update_exclude(tbl_name, flds, fld_id); + stmt_update.Clear() + .Val_int(fld_owner, owner).Val_int(fld_sort, sort).Val_bry_as_str(fld_name, name) + .Val_bry_as_str(fld_wiki, wiki).Val_bry_as_str(fld_url, url).Val_bry_as_str(fld_comment, comment) + .Crt_int(fld_id, id) + .Exec_update(); + } + public void Update_sort(int id, int sort) { + if (stmt_update_sort == null) stmt_update_sort = conn.Stmt_update(tbl_name, String_.Ary(fld_id), fld_sort); + stmt_update_sort.Clear().Val_int(fld_sort, sort).Crt_int(fld_id, id).Exec_update();; + } + public void Delete(int id) { + Db_stmt stmt_delete = conn.Stmt_delete(tbl_name, fld_id); + stmt_delete.Clear().Crt_int(fld_id, id).Exec_delete(); + } + public Xoud_bmk_itm_row[] Select_grp(int owner) { + List_adp list = List_adp_.New(); + Db_rdr rdr = conn.Stmt_select_order(tbl_name, flds, String_.Ary(fld_owner), fld_sort) + .Crt_int(fld_owner, owner) + .Exec_select__rls_auto(); + try { + while (rdr.Move_next()) + list.Add(new_row(rdr)); + } + finally {rdr.Rls();} + return (Xoud_bmk_itm_row[])list.To_ary_and_clear(Xoud_bmk_itm_row.class); + } + public int Select_sort_next(int owner) { + Db_rdr rdr = conn.Stmt_select_max(tbl_name, fld_sort, fld_owner).Crt_int(fld_owner, owner).Exec_select__rls_manual(); + try { + int rv = 0; + if (rdr.Move_next()) { + Object rv_obj = rdr.Read_obj(fld_sort); + rv = rv_obj == null ? 0 : Int_.Cast(rv_obj) + 1; + } + return rv; + } + finally {rdr.Rls();} + } + public Xoud_bmk_itm_row Select_or_null(int id) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_id).Crt_int(fld_id, id).Exec_select__rls_manual(); + try { + return rdr.Move_next() ? new_row(rdr) : null; + } + finally {rdr.Rls();} + } + private Xoud_bmk_itm_row new_row(Db_rdr rdr) { + return new Xoud_bmk_itm_row + ( rdr.Read_int(fld_id) + , rdr.Read_int(fld_owner) + , rdr.Read_int(fld_sort) + , rdr.Read_bry_by_str(fld_name) + , rdr.Read_bry_by_str(fld_wiki) + , rdr.Read_bry_by_str(fld_url) + , rdr.Read_bry_by_str(fld_comment) + ); + } + public void Rls() { + stmt_update_sort = null; + } +} diff --git a/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_mgr.java b/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_mgr.java index a27517de8..792be7a58 100644 --- a/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_mgr.java +++ b/400_xowa/src/gplx/xowa/users/bmks/Xoud_bmk_mgr.java @@ -13,3 +13,19 @@ 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.users.bmks; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.dbs.*; +public class Xoud_bmk_mgr { + public Xoud_bmk_itm_tbl Tbl__itm() {return tbl__itm;} private Xoud_bmk_itm_tbl tbl__itm; + public Xoud_bmk_dir_tbl Tbl__dir() {return tbl__dir;} private Xoud_bmk_dir_tbl tbl__dir; + public void Conn_(Db_conn conn, boolean created) { + this.tbl__dir = new Xoud_bmk_dir_tbl(conn); + this.tbl__itm = new Xoud_bmk_itm_tbl(conn); + // if (!conn.Meta_tbl_exists(tbl__dir.Tbl_name())) tbl__dir.Create_tbl(); // bmk_v2 + if (!conn.Meta_tbl_exists(tbl__itm.Tbl_name())) tbl__itm.Create_tbl(); + } + public void Itms__add(int owner, Xoa_url url) { + tbl__itm.Insert(owner, tbl__itm.Select_sort_next(owner), Xoa_ttl.Replace_unders(url.Page_bry()), url.Wiki_bry(), url.To_bry(Bool_.Y, Bool_.Y), Bry_.Empty); + } + public static final int Owner_root = -1; +} diff --git a/400_xowa/src/gplx/xowa/users/bmks/Xows_bmk_page.java b/400_xowa/src/gplx/xowa/users/bmks/Xows_bmk_page.java index a27517de8..94b0b93c4 100644 --- a/400_xowa/src/gplx/xowa/users/bmks/Xows_bmk_page.java +++ b/400_xowa/src/gplx/xowa/users/bmks/Xows_bmk_page.java @@ -13,3 +13,23 @@ 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.users.bmks; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.core.primitives.*; +import gplx.xowa.htmls.bridges.dbuis.tbls.*; +import gplx.xowa.users.data.*; import gplx.xowa.specials.*; +import gplx.xowa.wikis.pages.*; +public class Xows_bmk_page implements Xow_special_page { + public Xow_special_meta Special__meta() {return Xow_special_meta_.Itm__bookmarks;} + public void Special__gen(Xow_wiki wikii, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl) { + Xowe_wiki wiki = (Xowe_wiki)wikii; Xoae_page page = (Xoae_page)pagei; + Xoa_app app = wiki.App(); + Dbui_tbl_itm__bmk ui_tbl = Dbui_tbl_itm__bmk.get_or_new(app, app.User().User_db_mgr().Bmk_mgr().Tbl__itm()); + page.Html_data().Head_mgr().Itm__dbui().Init(app).Enabled_y_(); + Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_m001(); + ui_tbl.Select(bfr, Xoud_bmk_mgr.Owner_root); + + Xopage_html_data page_data = new Xopage_html_data(this.Special__meta().Display_ttl(), bfr.To_bry_and_rls()); + page_data.Apply(page); + } + public Xow_special_page Special__clone() {return this;} +} diff --git a/400_xowa/src/gplx/xowa/users/cfgs/Xocfg_meta_itm.java b/400_xowa/src/gplx/xowa/users/cfgs/Xocfg_meta_itm.java index a27517de8..4355faf30 100644 --- a/400_xowa/src/gplx/xowa/users/cfgs/Xocfg_meta_itm.java +++ b/400_xowa/src/gplx/xowa/users/cfgs/Xocfg_meta_itm.java @@ -13,3 +13,13 @@ 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.users.cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +public class Xocfg_meta_itm { + public Xocfg_meta_itm(String key, String type, String dflt, String version) { + this.key = key; this.type = type; this.dflt = dflt; this.version = version; + } + public String Key() {return key;} private final String key; + public String Type() {return type;} private String type; + public String Dflt() {return dflt;} private String dflt; + public String Version() {return version;} private String version; +} diff --git a/400_xowa/src/gplx/xowa/users/cfgs/Xocfg_meta_tbl.java b/400_xowa/src/gplx/xowa/users/cfgs/Xocfg_meta_tbl.java index a27517de8..8f6cec0be 100644 --- a/400_xowa/src/gplx/xowa/users/cfgs/Xocfg_meta_tbl.java +++ b/400_xowa/src/gplx/xowa/users/cfgs/Xocfg_meta_tbl.java @@ -13,3 +13,26 @@ 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.users.cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.dbs.*; +public class Xocfg_meta_tbl implements Rls_able { + private final String tbl_name; public final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_key, fld_type, fld_dflt, fld_version; + private final Db_conn conn; + public Xocfg_meta_tbl(Db_conn conn) { + this.conn = conn; + tbl_name = Tbl_name; + fld_key = flds.Add_str_pkey ("cfg_key" , 1024); // EX: "xowa.net.web_enabled" + fld_type = flds.Add_str ("cfg_type" , 255); // EX: "yn" + fld_dflt = flds.Add_str ("cfg_dflt" , 1024); // EX: "n" + fld_version = flds.Add_str ("cfg_version" , 16); // EX: "v1.1.1.1" + } + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Insert(String key, String type, String dflt, String version) { + Db_stmt stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Clear().Val_str(fld_key, key).Val_str(fld_type, type).Val_str(fld_dflt, dflt).Val_str(fld_version, version) + .Exec_insert(); + } + public void Rls() {} + public static final String Tbl_name = "cfg_meta"; +} diff --git a/400_xowa/src/gplx/xowa/users/cfgs/Xou_cfg_itm.java b/400_xowa/src/gplx/xowa/users/cfgs/Xou_cfg_itm.java index a27517de8..0b3b9ac30 100644 --- a/400_xowa/src/gplx/xowa/users/cfgs/Xou_cfg_itm.java +++ b/400_xowa/src/gplx/xowa/users/cfgs/Xou_cfg_itm.java @@ -13,3 +13,16 @@ 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.users.cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +public class Xou_cfg_itm { + public Xou_cfg_itm(int usr, String ctx, String key, String val) { + this.usr = usr; this.ctx = ctx; this.key = key; this.val = val; + this.uid = Xou_cfg_mgr.Bld_uid(usr, ctx, key); + } + public String Uid() {return uid;} private final String uid; + public int Usr() {return usr;} private final int usr; + public String Ctx() {return ctx;} private final String ctx; + public String Key() {return key;} private final String key; + public String Val() {return val;} private String val; + public void Val_(String v) {this.val = v;} +} diff --git a/400_xowa/src/gplx/xowa/users/cfgs/Xou_cfg_mgr.java b/400_xowa/src/gplx/xowa/users/cfgs/Xou_cfg_mgr.java index a27517de8..bb46d6e5b 100644 --- a/400_xowa/src/gplx/xowa/users/cfgs/Xou_cfg_mgr.java +++ b/400_xowa/src/gplx/xowa/users/cfgs/Xou_cfg_mgr.java @@ -13,3 +13,59 @@ 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.users.cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.dbs.*; +public class Xou_cfg_mgr { + private Xou_cfg_tbl tbl; + private final Hash_adp hash = Hash_adp_.New(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + public void Init_by_app(Db_conn conn) { + tbl = new Xou_cfg_tbl(conn); + tbl.Conn().Meta_tbl_assert(tbl); + this.Reg(tbl.Select_by_usr_ctx(Usr__anonymous, Ctx__app)); + } + public String Get_app_str_or(String key, String or) { // NOTE: app-level is always loaded at start; don't check db + synchronized (hash) { // LOCK:app-level + String uid = Bld_uid(tmp_bfr, Usr__anonymous, Ctx__app, key); + Xou_cfg_itm itm = (Xou_cfg_itm)hash.Get_by(uid); + return itm == null ? or : itm.Val(); + } + } + public void Set_app_bry(String key, byte[] val) {this.Set_app_str(key, String_.new_u8(val));} + public void Set_app_str(String key, String val) { + synchronized (hash) { // LOCK:app-level + // update val in reg + String uid = Bld_uid(tmp_bfr, Usr__anonymous, Ctx__app, key); + boolean insert = false; + Xou_cfg_itm itm = (Xou_cfg_itm)hash.Get_by(uid); + if (itm == null) { + itm = new Xou_cfg_itm(Usr__anonymous, Ctx__app, key, val); + hash.Add(uid, itm); + insert = true; + } + itm.Val_(val); + + // save to db + tbl.Upsert(insert, itm.Usr(), itm.Ctx(), itm.Key(), itm.Val()); + } + } + + private void Reg(Xou_cfg_itm[] itms) { + synchronized (hash) { // LOCK:app-level + for (Xou_cfg_itm itm : itms) + hash.Add(itm.Uid(), itm); + } + } + + private static final int Usr__anonymous = 1; + private static final String Ctx__app = "app"; + public static String Bld_uid(int usr, String ctx, String key) { + return String_.Concat(Int_.To_str(usr), "|", ctx, "|", key); + } + private static String Bld_uid(Bry_bfr tmp_bfr, int usr, String ctx, String key) { + tmp_bfr.Add_int_variable(usr).Add_byte_pipe(); + tmp_bfr.Add_str_a7(ctx).Add_byte_pipe(); + tmp_bfr.Add_str_u8(key); + return tmp_bfr.To_str_and_clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/users/cfgs/Xou_cfg_tbl.java b/400_xowa/src/gplx/xowa/users/cfgs/Xou_cfg_tbl.java index a27517de8..9d226c594 100644 --- a/400_xowa/src/gplx/xowa/users/cfgs/Xou_cfg_tbl.java +++ b/400_xowa/src/gplx/xowa/users/cfgs/Xou_cfg_tbl.java @@ -13,3 +13,56 @@ 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.users.cfgs; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.dbs.*; +public class Xou_cfg_tbl implements Db_tbl { + public final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_key, fld_usr, fld_ctx, fld_val; + public Xou_cfg_tbl(Db_conn conn) { + this.conn = conn; + this.fld_usr = flds.Add_int ("cfg_usr"); // EX: 1=anonymous; others will require usr_regy + this.fld_ctx = flds.Add_str ("cfg_ctx", 1024); // EX: "app"; "en.w" + this.fld_key = flds.Add_str ("cfg_key", 1024); // EX: "xowa.net.web_enabled" + this.fld_val = flds.Add_str ("cfg_val", 4096); // EX: "y" + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public String Tbl_name() {return tbl_name;} private final String tbl_name = "user_cfg"; + public void Create_tbl() { + conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds + , Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, fld_key, fld_key) + )); + } + public void Upsert(boolean insert, int usr, String ctx, String key, String val) { + if (insert) + Insert(usr, ctx, key, val); + else + Update(usr, ctx, key, val); + } + private void Insert(int usr, String ctx, String key, String val) { + Db_stmt stmt = conn.Stmt_insert(tbl_name, flds); + stmt.Clear().Val_int(fld_usr, usr).Val_str(fld_ctx, ctx).Val_str(fld_key, key).Val_str(fld_val, val).Exec_insert(); + stmt.Rls(); + } + private void Update(int usr, String ctx, String key, String val) { + Db_stmt stmt = conn.Stmt_update(tbl_name, String_.Ary(fld_usr, fld_ctx, fld_key), fld_val); + stmt.Clear().Val_str(fld_val, val).Crt_int(fld_usr, usr).Crt_str(fld_ctx, ctx).Crt_str(fld_key, key).Exec_update(); + stmt.Rls(); + } + public Xou_cfg_itm[] Select_by_usr_ctx(int usr, String ctx) { + List_adp list = List_adp_.New(); + Db_stmt stmt_select = conn.Stmt_select(tbl_name, flds, fld_usr, fld_ctx); + Db_rdr rdr = stmt_select.Clear().Crt_int(fld_usr, usr).Crt_str(fld_ctx, ctx).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + list.Add(Make_itm(rdr)); + } + } finally {rdr.Rls();} + return (Xou_cfg_itm[])list.To_ary_and_clear(Xou_cfg_itm.class); + } + public void Rls() {} + + private Xou_cfg_itm Make_itm(Db_rdr rdr) { + return new Xou_cfg_itm(rdr.Read_int(fld_usr), rdr.Read_str(fld_ctx), rdr.Read_str(fld_key), rdr.Read_str(fld_val)); + } +} diff --git a/400_xowa/src/gplx/xowa/users/data/Xou_db_file.java b/400_xowa/src/gplx/xowa/users/data/Xou_db_file.java index a27517de8..447b1e999 100644 --- a/400_xowa/src/gplx/xowa/users/data/Xou_db_file.java +++ b/400_xowa/src/gplx/xowa/users/data/Xou_db_file.java @@ -13,3 +13,27 @@ 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.users.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.dbs.metas.*; +import gplx.xowa.files.caches.*; +import gplx.xowa.users.bmks.*; import gplx.xowa.users.history.*; +public class Xou_db_file { + private final Db_conn conn; + public Xou_db_file(Db_conn conn) { + this.conn = conn; + this.tbl__cfg = gplx.xowa.wikis.data.Xowd_cfg_tbl_.New(conn); + this.tbl__site = new Xoud_site_tbl(conn); + this.tbl__history = new Xoud_history_tbl(conn); + this.tbl__cache = new Xou_cache_tbl(conn); + } + public Db_cfg_tbl Tbl__cfg() {return tbl__cfg;} private final Db_cfg_tbl tbl__cfg; + public Xoud_site_tbl Tbl__site() {return tbl__site;} private final Xoud_site_tbl tbl__site; + public Xoud_history_tbl Tbl__history() {return tbl__history;} private final Xoud_history_tbl tbl__history; + public Xou_cache_tbl Tbl__cache() {return tbl__cache;} private final Xou_cache_tbl tbl__cache; + public void Init_assert() { + if (!conn.Meta_tbl_exists(tbl__cache.Tbl_name())) { + tbl__cfg.Create_tbl(); + tbl__cache.Create_tbl(); + } + } +} diff --git a/400_xowa/src/gplx/xowa/users/data/Xou_db_mgr.java b/400_xowa/src/gplx/xowa/users/data/Xou_db_mgr.java index a27517de8..3a16932bb 100644 --- a/400_xowa/src/gplx/xowa/users/data/Xou_db_mgr.java +++ b/400_xowa/src/gplx/xowa/users/data/Xou_db_mgr.java @@ -13,3 +13,45 @@ 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.users.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.core.threads.*; import gplx.dbs.*; import gplx.dbs.metas.updates.*; import gplx.dbs.metas.*; +import gplx.xowa.files.caches.*; +import gplx.xowa.users.cfgs.*; import gplx.xowa.users.bmks.*; import gplx.xowa.users.history.*; +public class Xou_db_mgr { + private final Xoa_app app; + private final Xoud_id_mgr id_mgr; + public Xou_db_mgr(Xoa_app app) { + this.app = app; + this.id_mgr = new Xoud_id_mgr(cfg_mgr); + this.site_mgr = new Xoud_site_mgr(id_mgr); + } + public Db_conn Conn() {return conn;} private Db_conn conn; + public Xou_db_file Db_file() {return db_file;} private Xou_db_file db_file; + public Xoud_cfg_mgr Cfg_mgr() {return cfg_mgr;} private final Xoud_cfg_mgr cfg_mgr = new Xoud_cfg_mgr(); + public Xoud_site_mgr Site_mgr() {return site_mgr;} private final Xoud_site_mgr site_mgr; + public Xoud_history_mgr History_mgr() {return history_mgr;} private final Xoud_history_mgr history_mgr = new Xoud_history_mgr(); + public Xoud_bmk_mgr Bmk_mgr() {return bmk_mgr;} private final Xoud_bmk_mgr bmk_mgr = new Xoud_bmk_mgr(); + public Xou_cache_mgr Cache_mgr() {return cache_mgr;} private Xou_cache_mgr cache_mgr; + public Xou_file_itm_finder File__xfer_itm_finder() {return xfer_itm_finder;} private Xou_file_itm_finder xfer_itm_finder; + public Xou_cfg_mgr Cfg() {return cfg;} private final Xou_cfg_mgr cfg = new Xou_cfg_mgr(); + public void Init_by_app(boolean drd, Io_url db_url) { + Db_conn_bldr_data db_conn_bldr = Db_conn_bldr.Instance.Get_or_new(db_url); + this.conn = db_conn_bldr.Conn(); boolean created = db_conn_bldr.Created(); + this.db_file = new Xou_db_file(conn); db_file.Init_assert(); + this.cache_mgr = new Xou_cache_mgr(app.Wiki_mgri(), app.Fsys_mgr().File_dir(), db_file); + this.xfer_itm_finder = new Xou_file_itm_finder(cache_mgr); + this.bmk_mgr.Conn_(conn, created); + cfg.Init_by_app(conn); + // this.history_mgr.Conn_(conn, created); + if (drd) { + this.Init_site_mgr(); +// history_mgr.Conn_(user_conn, created); + } + } + private boolean init_site_mgr; + public void Init_site_mgr() { + if (init_site_mgr) return; init_site_mgr = true; + cfg_mgr.Conn_ (conn, !conn.Meta_tbl_exists(Xoud_cfg_mgr.Tbl_name)); + site_mgr.Conn_(conn, !conn.Meta_tbl_exists(Xoud_site_tbl.Tbl_name)); + } +} diff --git a/400_xowa/src/gplx/xowa/users/data/Xoud_cfg_mgr.java b/400_xowa/src/gplx/xowa/users/data/Xoud_cfg_mgr.java index a27517de8..97d63874a 100644 --- a/400_xowa/src/gplx/xowa/users/data/Xoud_cfg_mgr.java +++ b/400_xowa/src/gplx/xowa/users/data/Xoud_cfg_mgr.java @@ -13,3 +13,68 @@ 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.users.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; +public class Xoud_cfg_mgr { + public Db_cfg_tbl Tbl() {return tbl;} private Db_cfg_tbl tbl; + public static final String Tbl_name = "user_opt"; + public void Conn_(Db_conn new_conn, boolean created) { + tbl = new Db_cfg_tbl(new_conn, Tbl_name); + if (created) tbl.Create_tbl(); + } + public int Select_int_or(String grp, String key, int or) { + String rv = Select_str_or(grp, key, null); + return rv == null ? or : Int_.Parse_or(rv, or); + } + public byte[] Select_bry_or(String key, byte[] or) {return Select_bry_or("" , key, or);} + public byte[] Select_bry_or(String grp, String key, byte[] or) { + String rv = Select_str_or(grp, key, null); + return rv == null ? or : Bry_.new_u8(rv); + } + public String Select_str_or(String grp, String key, String or) { + String rv = tbl.Select_str_or(grp, key, null); + return rv == null ? or : rv; + } + public byte[] Assert_bry_or(String key, byte[] or) {return Assert_bry_or("", key, or);} + public byte[] Assert_bry_or(String grp, String key, byte[] or) { + String rv = tbl.Select_str_or(grp, key, null); + if (rv == null) { + Insert_bry(grp, key, or); + return or; + } + else + return Bry_.new_u8(rv); + } + public void Upsert_int(String grp, String key, int val) { + int exists = Select_int_or(grp, key, Int_.Min_value); + if (exists == Int_.Min_value) + Insert_int(grp, key, val); + else + Update_int(grp, key, val); + } + public void Upsert_str(String grp, String key, String val) { + String exists = Select_str_or(grp, key, null); + if (exists == null) + Insert_str(grp, key, val); + else + Update_str(grp, key, val); + } + public void Update_str(String grp, String key, String val) {Update_bry(grp, key, Bry_.new_u8(val));} + public void Update_bry(String key, byte[] val) {Update_bry("", key, val);} + public void Update_bry(String grp, String key, byte[] val) {tbl.Update_bry(grp, key, val);} + public void Update_int(String grp, String key, int val) {tbl.Update_int(grp, key, val);} + public void Insert_str(String grp, String key, String val) {Insert_bry(grp, key, Bry_.new_u8(val));} + public void Insert_bry(String key, byte[] val) {Insert_bry("", key, val);} + public void Insert_bry(String grp, String key, byte[] val) {tbl.Insert_bry(grp, key, val);} + public void Insert_int(String grp, String key, int val) {tbl.Insert_int(grp, key, val);} + public int Next_id(String tbl_name) { + String grp = "xowa." + tbl_name, key = "next_id"; + int next_id = tbl.Select_int_or(grp, key, 1); // EX: xowa.cfg_history|next_id|1 + int new_val = next_id + 1; + if (next_id == 1) + tbl.Insert_int(grp, key, new_val); + else + tbl.Update_int(grp, key, new_val); + return next_id; + } +} diff --git a/400_xowa/src/gplx/xowa/users/data/Xoud_id_mgr.java b/400_xowa/src/gplx/xowa/users/data/Xoud_id_mgr.java index a27517de8..88449c2db 100644 --- a/400_xowa/src/gplx/xowa/users/data/Xoud_id_mgr.java +++ b/400_xowa/src/gplx/xowa/users/data/Xoud_id_mgr.java @@ -13,3 +13,16 @@ 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.users.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +public class Xoud_id_mgr { + private Xoud_cfg_mgr cfg_mgr; + public Xoud_id_mgr(Xoud_cfg_mgr cfg_mgr) {this.cfg_mgr = cfg_mgr;} + public int Get_next(String key) {return cfg_mgr.Select_int_or(Grp_key, key, 1);} + public void Set_next(String key, int v) {cfg_mgr.Upsert_int(Grp_key, key, v);} + public int Get_next_and_save(String key) { + int rv = Get_next(key); + Set_next(key, rv + 1); + return rv; + } + private static final String Grp_key = "gplx.next_id"; +} diff --git a/400_xowa/src/gplx/xowa/users/data/Xoud_opt_scope.java b/400_xowa/src/gplx/xowa/users/data/Xoud_opt_scope.java index a27517de8..ee2b65ca4 100644 --- a/400_xowa/src/gplx/xowa/users/data/Xoud_opt_scope.java +++ b/400_xowa/src/gplx/xowa/users/data/Xoud_opt_scope.java @@ -13,3 +13,71 @@ 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.users.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.core.btries.*; import gplx.core.primitives.*; +import gplx.xowa.langs.*; +import gplx.xowa.wikis.domains.*; +class Xoud_opt_scope { + public Xoud_opt_scope(int lang_id, int type_id) {this.lang_id = lang_id; this.type_id = type_id;} + public int Lang_id() {return lang_id;} private final int lang_id; + public int Type_id() {return type_id;} private final int type_id; + public String To_str() { + String lang_str = lang_id == Lang_id_wildcard ? "*" : String_.new_u8(Xol_lang_stub_.Get_by_id(lang_id).Key()); + String type_str = type_id == Lang_id_wildcard ? "*" : String_.new_u8(Xow_domain_tid_.Get_type_as_bry(type_id)); + return lang_str + "." + type_str; + } + public static final int Lang_id_wildcard = -1, Type_id_wildcard = -1; + public static final Xoud_opt_scope App = new Xoud_opt_scope(Lang_id_wildcard, Type_id_wildcard); +} +class Xoud_opt_scope_parser { + private Gfo_usr_dlg usr_dlg; private final List_adp list = List_adp_.New(); + public Xoud_opt_scope[] Parse(byte[] src) { + usr_dlg = Gfo_usr_dlg_.Instance; + list.Clear(); + int pos = 0; int src_len = src.length; + while (pos < src_len) { + int comma_pos = Bry_find_.Find_fwd(src, Byte_ascii.Comma, pos, src_len); if (comma_pos == Bry_find_.Not_found) comma_pos = src_len; + Xoud_opt_scope itm = Parse_itm(src, pos, comma_pos); + if (itm == Xoud_opt_scope.App) return Ary_app; + list.Add(itm); + pos = comma_pos + 1; + } + return (Xoud_opt_scope[])list.To_ary_and_clear(Xoud_opt_scope.class); + } + public Xoud_opt_scope Parse_itm(byte[] src, int bgn, int end) { + int lang_dot = Bry_find_.Find_fwd(src, Byte_ascii.Dot, bgn, end); if (lang_dot == Bry_find_.Not_found) return Warn("scope.parse.missing_lang_dot: src=~{0}", src, bgn, end); + int lang_id = Int_.Min_value; + if (lang_dot == 1 && src[bgn] == Byte_ascii.Star) + lang_id = Xoud_opt_scope.Lang_id_wildcard; + else { + Xol_lang_stub lang_itm = Xol_lang_stub_.Get_by_key_or_null(src, bgn, lang_dot); if (lang_itm == null) return Warn("scope.parse.invalid_lang: src=~{0}", src, bgn, end); + lang_id = lang_itm.Id(); + } + Object type_tid_obj = btrie_by_type.Match_bgn(src, lang_dot + 1, end); if (type_tid_obj == null) return Warn("scope.parse.invalid_type: src=~{0}", src, bgn, end); + int type_id = ((Int_obj_val)type_tid_obj).Val(); + return new Xoud_opt_scope(lang_id, type_id); + } + private Xoud_opt_scope Warn(String fmt, byte[] src, int bgn, int end) { + usr_dlg.Warn_many("", "", fmt, String_.new_u8(src, bgn, end)); + return Xoud_opt_scope.App; + } + private static final Btrie_slim_mgr btrie_by_type = Btrie_slim_mgr.cs() + .Add_str_int("w" , Xow_domain_tid_.Tid__wikipedia) + .Add_str_int("d" , Xow_domain_tid_.Tid__wiktionary) + .Add_str_int("s" , Xow_domain_tid_.Tid__wikisource) + .Add_str_int("v" , Xow_domain_tid_.Tid__wikivoyage) + .Add_str_int("q" , Xow_domain_tid_.Tid__wikiquote) + .Add_str_int("b" , Xow_domain_tid_.Tid__wikibooks) + .Add_str_int("u" , Xow_domain_tid_.Tid__wikiversity) + .Add_str_int("n" , Xow_domain_tid_.Tid__wikinews) + .Add_str_int("*" , Xoud_opt_scope.Type_id_wildcard) + .Add_str_int("xowa" , Xow_domain_tid_.Tid__home) + .Add_str_int("wd" , Xow_domain_tid_.Tid__wikidata) + .Add_str_int("c" , Xow_domain_tid_.Tid__commons) + .Add_str_int("species" , Xow_domain_tid_.Tid__species) + .Add_str_int("meta" , Xow_domain_tid_.Tid__meta) + .Add_str_int("mw" , Xow_domain_tid_.Tid__mediawiki) + .Add_str_int("wmf" , Xow_domain_tid_.Tid__wmfblog) + ; + private static final Xoud_opt_scope[] Ary_app = new Xoud_opt_scope[] {Xoud_opt_scope.App}; +} diff --git a/400_xowa/src/gplx/xowa/users/data/Xoud_opt_scope_tst.java b/400_xowa/src/gplx/xowa/users/data/Xoud_opt_scope_tst.java index a27517de8..3c3caf565 100644 --- a/400_xowa/src/gplx/xowa/users/data/Xoud_opt_scope_tst.java +++ b/400_xowa/src/gplx/xowa/users/data/Xoud_opt_scope_tst.java @@ -13,3 +13,38 @@ 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.users.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import org.junit.*; import gplx.xowa.langs.*; +import gplx.xowa.wikis.domains.*; +public class Xoud_opt_scope_tst { + private Xoud_opt_scope_fxt fxt = new Xoud_opt_scope_fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Basic() { + fxt.Test_parse("en.w" , fxt.Make(Xol_lang_stub_.Id_en, Xow_domain_tid_.Tid__wikipedia)); + fxt.Test_parse("en.*" , fxt.Make(Xol_lang_stub_.Id_en, Xoud_opt_scope.Type_id_wildcard)); + fxt.Test_parse("*.w" , fxt.Make(Xoud_opt_scope.Lang_id_wildcard, Xow_domain_tid_.Tid__wikipedia)); + fxt.Test_parse("" , Xoud_opt_scope.App); + fxt.Test_parse("en.w,fr.d" , fxt.Make(Xol_lang_stub_.Id_en, Xow_domain_tid_.Tid__wikipedia), fxt.Make(Xol_lang_stub_.Id_fr, Xow_domain_tid_.Tid__wiktionary)); + } +} +class Xoud_opt_scope_fxt { + private final Xoud_opt_scope_parser parser = new Xoud_opt_scope_parser(); + public void Clear() { + // Gfo_usr_dlg_.I = Xoa_app_.New__usr_dlg__console(); + } + public Xoud_opt_scope Make(int lang_id, int type_id) {return new Xoud_opt_scope(lang_id, type_id);} + public void Test_parse(String raw, Xoud_opt_scope... expd) { + Xoud_opt_scope[] actl = parser.Parse(Bry_.new_u8(raw)); + Tfds.Eq(To_str(expd), To_str(actl)); + } + private static String To_str(Xoud_opt_scope[] ary) { + Bry_bfr bfr = Bry_bfr_.New(); + int len = ary.length; + for (int i = 0; i < len; ++i) { + Xoud_opt_scope itm = ary[i]; + if (i != 0) bfr.Add_str_a7(","); + bfr.Add_str_a7(itm.To_str()); + } + return bfr.To_str_and_clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/users/data/Xoud_regy_row.java b/400_xowa/src/gplx/xowa/users/data/Xoud_regy_row.java index a27517de8..34f554c08 100644 --- a/400_xowa/src/gplx/xowa/users/data/Xoud_regy_row.java +++ b/400_xowa/src/gplx/xowa/users/data/Xoud_regy_row.java @@ -13,3 +13,10 @@ 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.users.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +public class Xoud_regy_row { + public Xoud_regy_row(String cfg_grp, String cfg_key, String cfg_val) {this.cfg_grp = cfg_grp; this.cfg_key = cfg_key; this.cfg_val = cfg_val;} + public String Cfg_grp() {return cfg_grp;} private final String cfg_grp; + public String Cfg_key() {return cfg_key;} private final String cfg_key; + public String Cfg_val() {return cfg_val;} private final String cfg_val; +} diff --git a/400_xowa/src/gplx/xowa/users/data/Xoud_regy_tbl.java b/400_xowa/src/gplx/xowa/users/data/Xoud_regy_tbl.java index a27517de8..56476d970 100644 --- a/400_xowa/src/gplx/xowa/users/data/Xoud_regy_tbl.java +++ b/400_xowa/src/gplx/xowa/users/data/Xoud_regy_tbl.java @@ -13,3 +13,75 @@ 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.users.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.dbs.metas.*; import gplx.dbs.metas.updates.*; +public class Xoud_regy_tbl { + public static final String Tbl_name = "user_regy", Fld_regy_grp = "regy_grp", Fld_regy_key = "regy_key", Fld_regy_val = "regy_val"; + private static final String[] Flds__all = new String[] {Fld_regy_grp, Fld_regy_key, Fld_regy_val}; + public static final Db_idx_itm Idx_core = Db_idx_itm.sql_("CREATE UNIQUE INDEX user_regy_core ON user_regy (regy_grp, regy_key);"); + public static final String Tbl_sql = String_.Concat_lines_nl + ( "CREATE TABLE user_regy" + , "( regy_grp nvarchar(255) NOT NULL -- EX: xowa.schema.site" + , ", regy_key nvarchar(255) NOT NULL -- EX: next_id" + , ", regy_val nvarchar(255) NOT NULL -- EX: 1" + , ");" + ); + private Db_stmt stmt_select_grp, stmt_select_key, stmt_insert, stmt_update, stmt_delete; + public Db_conn Conn() {return conn;} + public Xoud_regy_tbl Conn_(Db_conn v, boolean created) { + this.Rls_all(); conn = v; + if (created) { + Schema_update_cmd cmd = Schema_update_cmd_.Make_tbl_create(Xoud_regy_tbl.Tbl_name , Xoud_regy_tbl.Tbl_sql , Xoud_regy_tbl.Idx_core); + cmd.Exec(null, conn); +// conn.Meta_tbl_create(meta); + } + return this; + } private Db_conn conn; + @gplx.Virtual public void Insert(String grp, String key, String val) { + if (stmt_insert == null) stmt_insert = Db_stmt_.new_insert_(conn, Tbl_name, Flds__all); + stmt_insert.Clear().Val_str(grp).Val_str(key).Val_str(val).Exec_insert(); + } + @gplx.Virtual public void Update(String grp, String key, String val) { + if (stmt_update == null) stmt_update = Db_stmt_.new_update_(conn, Tbl_name, String_.Ary(Fld_regy_grp, Fld_regy_key), Fld_regy_val); + stmt_update.Clear().Val_str(val).Val_str(grp).Val_str(key).Exec_update(); + } + @gplx.Virtual public void Delete(String grp, String key) { + if (stmt_delete == null) stmt_delete = Db_stmt_.new_delete_(conn, Tbl_name, Fld_regy_grp, Fld_regy_key); + stmt_delete.Clear().Val_str(grp).Val_str(key).Exec_delete(); + } + @gplx.Virtual public void Select_by_grp(List_adp rv, String grp) { + if (stmt_select_grp == null) stmt_select_grp = Db_stmt_.new_select_as_rdr(conn, Db_qry__select_in_tbl.new_(Tbl_name, String_.Ary(Fld_regy_grp), Flds__all, Db_qry__select_in_tbl.Order_by_null)); + Db_rdr rdr = stmt_select_grp.Clear().Val_str(grp).Exec_select__rls_manual(); + try { + while (rdr.Move_next()) { + Xoud_regy_row row = Make_row(rdr); + rv.Add(row); + } + } + finally {rdr.Rls();} + } + @gplx.Virtual public String Select_val(String grp, String key) { + if (stmt_select_key == null) stmt_select_key = Db_stmt_.new_select_as_rdr(conn, Db_qry__select_in_tbl.new_(Tbl_name, String_.Ary(Fld_regy_grp, Fld_regy_key), Flds__all, Db_qry__select_in_tbl.Order_by_null)); + Db_rdr rdr = stmt_select_key.Clear().Val_str(Fld_regy_grp, grp).Val_str(Fld_regy_key, key).Exec_select__rls_manual(); + String rv = null; + if (rdr.Move_next()) + rv = rdr.Read_str(Fld_regy_val); + rdr.Rls(); + return rv; + } + private Xoud_regy_row Make_row(Db_rdr rdr) { + return new Xoud_regy_row + ( rdr.Read_str(Fld_regy_grp) + , rdr.Read_str(Fld_regy_key) + , rdr.Read_str(Fld_regy_val) + ); + } + public void Rls_all() { + if (stmt_select_grp != null) {stmt_select_grp.Rls(); stmt_select_grp = null;} + if (stmt_select_key != null) {stmt_select_key.Rls(); stmt_select_key = null;} + if (stmt_insert != null) {stmt_insert.Rls(); stmt_insert = null;} + if (stmt_update != null) {stmt_update.Rls(); stmt_update = null;} + if (stmt_delete != null) {stmt_delete.Rls(); stmt_delete = null;} + conn = null; + } +} diff --git a/400_xowa/src/gplx/xowa/users/data/Xoud_site_mgr.java b/400_xowa/src/gplx/xowa/users/data/Xoud_site_mgr.java index a27517de8..775e35c54 100644 --- a/400_xowa/src/gplx/xowa/users/data/Xoud_site_mgr.java +++ b/400_xowa/src/gplx/xowa/users/data/Xoud_site_mgr.java @@ -13,3 +13,27 @@ 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.users.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.dbs.*; import gplx.xowa.users.data.*; +public class Xoud_site_mgr { + private Xoud_site_tbl tbl; + private final Xoud_id_mgr id_mgr; + public Xoud_site_mgr(Xoud_id_mgr id_mgr) {this.id_mgr = id_mgr;} + public void Conn_(Db_conn conn, boolean created) { + tbl = new Xoud_site_tbl(conn); + if (created) tbl.Create_tbl(); + } + public Xoud_site_row[] Get_all() {return tbl.Select_all();} + public Xoud_site_row Select_by_domain(byte[] domain) {return tbl.Select_by_domain(domain);} + public void Delete_by_domain(byte[] domain) {tbl.Delete_by_domain(domain);} + public void Import(String domain, String name, String path, String date, String xtn) { // insert or update wiki + Xoud_site_row itm = tbl.Select_by_domain(Bry_.new_u8(domain)); + if (itm == null) + tbl.Insert(id_mgr.Get_next_and_save("xowa.user.site"), 0, domain, name, path, date, xtn); + else + tbl.Update(itm.Id(), 0, domain, name, path, date, xtn); + } + public void Update(Xoud_site_row row) { + tbl.Update(row.Id(), row.Priority(), row.Domain(), row.Name(), row.Path(), row.Date(), row.Xtn()); + } +} diff --git a/400_xowa/src/gplx/xowa/users/data/Xoud_site_row.java b/400_xowa/src/gplx/xowa/users/data/Xoud_site_row.java index a27517de8..71bf590b2 100644 --- a/400_xowa/src/gplx/xowa/users/data/Xoud_site_row.java +++ b/400_xowa/src/gplx/xowa/users/data/Xoud_site_row.java @@ -13,3 +13,16 @@ 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.users.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +public class Xoud_site_row { + public Xoud_site_row(int id, int priority, String domain, String name, String path, String date, String xtn) { + this.id = id; this.priority = priority; this.domain = domain; this.name = name; this.path = path; this.date = date; this.xtn = xtn; + } + public int Id() {return id;} private final int id; + public int Priority() {return priority;} private final int priority; + public String Domain() {return domain;} private final String domain; + public String Name() {return name;} private final String name; + public String Path() {return path;} private final String path; + public String Date() {return date;} private String date; public void Date_(String v) {this.date = v;} + public String Xtn() {return xtn;} private String xtn; +} diff --git a/400_xowa/src/gplx/xowa/users/data/Xoud_site_tbl.java b/400_xowa/src/gplx/xowa/users/data/Xoud_site_tbl.java index a27517de8..1f006ce04 100644 --- a/400_xowa/src/gplx/xowa/users/data/Xoud_site_tbl.java +++ b/400_xowa/src/gplx/xowa/users/data/Xoud_site_tbl.java @@ -13,3 +13,77 @@ 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.users.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.dbs.*; +public class Xoud_site_tbl implements Rls_able { + public static final String Tbl_name = "user_site"; + private final String tbl_name = Tbl_name; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_site_id, fld_site_priority, fld_site_domain, fld_site_name, fld_site_path, fld_site_xtn, fld_site_date; + private final Db_conn conn; + public Xoud_site_tbl(Db_conn conn) { + this.conn = conn; + fld_site_id = flds.Add_int_pkey("site_id"); + fld_site_priority = flds.Add_int("site_priority"); // EX: 0=default; 1+ is order if 0 is unavailable + fld_site_domain = flds.Add_str("site_domain", 255); // EX: en.wikipedia.org; NOTE: no protocol (https:) + fld_site_name = flds.Add_str("site_name", 255); // EX: English Wikipedia + fld_site_path = flds.Add_str("site_path", 255); // EX: ~{xowa_root}/wiki/en.wikipedia.org/ + fld_site_date = conn.Meta_fld_append_if_missing(tbl_name, flds, Dbmeta_fld_itm.new_str("site_date", 255).Default_("")); // EX: 2016-06-10 + fld_site_xtn = flds.Add_text("site_xtn"); + conn.Rls_reg(this); + } + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Rls() {} + public void Insert(int site_id, int priority, String domain, String name, String path, String date, String xtn) { + Db_stmt stmt = conn.Stmt_insert(tbl_name, flds); + Exec_stmt(stmt, Bool_.N, site_id, priority, domain, name, path, date, xtn); + } + public void Update(int site_id, int priority, String domain, String name, String path, String date, String xtn) { + Db_stmt stmt = conn.Stmt_update_exclude(tbl_name, flds, fld_site_id); + Exec_stmt(stmt, Bool_.Y, site_id, priority, domain, name, path, date, xtn); + } + public void Delete(int site_id) { + Db_stmt stmt = conn.Stmt_delete(tbl_name, fld_site_id); + stmt.Crt_int(fld_site_id, site_id).Exec_delete(); + } + public void Delete_by_domain(byte[] domain) { + conn.Stmt_delete(tbl_name, fld_site_domain).Crt_bry_as_str(fld_site_domain, domain).Exec_delete(); + } + public Xoud_site_row[] Select_all() { + List_adp rv = List_adp_.New(); + Db_rdr rdr = conn.Stmt_select(tbl_name, flds).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) + rv.Add(New_site(rdr)); + return (Xoud_site_row[])rv.To_ary_and_clear(Xoud_site_row.class); + } + finally {rdr.Rls();} + } + public Xoud_site_row Select_by_domain(byte[] domain) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_site_domain).Crt_bry_as_str(fld_site_domain, domain).Exec_select__rls_auto(); + try {return rdr.Move_next() ? New_site(rdr) : null;} // NOTE: old versions allowed multiple wikis with same domain; only return 1st + finally {rdr.Rls();} + } + private void Exec_stmt(Db_stmt stmt, boolean update, int site_id, int priority, String domain, String name, String path, String date, String xtn) { + if (!update) + stmt.Val_int(fld_site_id, site_id); + stmt.Val_int(fld_site_priority, priority).Val_str(fld_site_domain, domain).Val_str(fld_site_name, name).Val_str(fld_site_path, path) + .Val_str(fld_site_date, date).Val_str(fld_site_xtn, xtn); + if (update) + stmt.Crt_int(fld_site_id, site_id); + if (update) + stmt.Exec_update(); + else + stmt.Exec_insert(); + } + private Xoud_site_row New_site(Db_rdr rdr) { + return new Xoud_site_row + ( rdr.Read_int(fld_site_id) + , rdr.Read_int(fld_site_priority) + , rdr.Read_str(fld_site_domain) + , rdr.Read_str(fld_site_name) + , rdr.Read_str(fld_site_path) + , rdr.Read_str(fld_site_date) + , rdr.Read_str(fld_site_xtn) + ); + } +} diff --git a/400_xowa/src/gplx/xowa/users/data/Xoud_user_tbl.java b/400_xowa/src/gplx/xowa/users/data/Xoud_user_tbl.java index a27517de8..669391f8c 100644 --- a/400_xowa/src/gplx/xowa/users/data/Xoud_user_tbl.java +++ b/400_xowa/src/gplx/xowa/users/data/Xoud_user_tbl.java @@ -13,3 +13,57 @@ 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.users.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.dbs.*; +class Xoud_user_mgr { + private Xoud_user_tbl tbl = new Xoud_user_tbl(); + public void Conn_(Db_conn conn, boolean created) {tbl.Conn_(conn, created);} + public int Get_id_or_new(String name) { + int rv = tbl.Select_id_by_name(name); + if (rv == Int_.Min_value) { + rv = tbl.Select_id_next(); + tbl.Insert(rv, name); + } + return rv; + } +} +class Xoud_user_tbl { + private String tbl_name = "user_user_regy"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private String fld_id, fld_name; + private Db_conn conn; + public void Conn_(Db_conn new_conn, boolean created) { + this.conn = new_conn; flds.Clear(); + fld_id = flds.Add_int_pkey("id"); + fld_name = flds.Add_str("name", 255); + if (created) { + Dbmeta_tbl_itm meta = Dbmeta_tbl_itm.New(tbl_name, flds + , Dbmeta_idx_itm.new_unique_by_tbl(tbl_name, "name", fld_name) + ); + conn.Meta_tbl_create(meta); + } + } + public void Insert(int id, String name) { + Db_stmt stmt = conn.Stmt_insert(tbl_name, flds); + stmt.Val_int(fld_id, id).Val_str(fld_name, name) + .Exec_insert(); + } + public int Select_id_by_name(String name) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_name).Crt_str(fld_name, name).Exec_select__rls_auto(); + try { + return rdr.Move_next() ? rdr.Read_int(fld_id) : Int_.Min_value; + } + finally {rdr.Rls();} + } + public int Select_id_next() { + int rv = 1; + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, Dbmeta_fld_itm.Str_ary_empty).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + int cur = rdr.Read_int(fld_id); + if (cur >= rv) rv = cur + 1; + } + return rv; + } + finally {rdr.Rls();} + } +} diff --git a/400_xowa/src/gplx/xowa/users/history/Dbui_tbl_itm__history.java b/400_xowa/src/gplx/xowa/users/history/Dbui_tbl_itm__history.java index a27517de8..d919eaa29 100644 --- a/400_xowa/src/gplx/xowa/users/history/Dbui_tbl_itm__history.java +++ b/400_xowa/src/gplx/xowa/users/history/Dbui_tbl_itm__history.java @@ -13,3 +13,83 @@ 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.users.history; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.core.brys.fmtrs.*; import gplx.core.errs.*; +import gplx.langs.jsons.*; import gplx.langs.htmls.*; +import gplx.xowa.users.data.*; import gplx.xowa.users.bmks.*; +import gplx.xowa.htmls.bridges.*; import gplx.xowa.htmls.bridges.dbuis.*; import gplx.xowa.htmls.bridges.dbuis.tbls.*; import gplx.xowa.htmls.bridges.dbuis.fmtrs.*; +public class Dbui_tbl_itm__history implements Dbui_tbl_itm { + private final Xoa_app app; private final Xoud_history_tbl tbl; + private final Dbui_tbl_fmtr tbl_fmtr = new Dbui_tbl_fmtr(); + private final Bry_bfr tmp_bfr = Bry_bfr_.New_w_size(255); + private final Bridge_msg_bldr msg_bldr; + public Dbui_tbl_itm__history(Xoa_app app, Xoud_history_tbl tbl) {this.app = app; this.tbl = tbl; this.msg_bldr = app.Html__bridge_mgr().Msg_bldr();} + public byte[] Key() {return key;} private static final byte[] key = Bry_.new_a7("history"); + public Dbui_btn_itm[] View_btns() {return view_btns;} + public Dbui_btn_itm[] Edit_btns() {return Dbui_btn_itm.Ary_empty;} + public Dbui_col_itm[] Cols() {return cols;} + public void Reg(Bridge_cmd_mgr bridge_mgr) { + Dbui_cmd_mgr dbui_mgr = Dbui_cmd_mgr.Instance; + dbui_mgr.Init_by_bridge(bridge_mgr); + dbui_mgr.Add(this); + } + private final List_adp select_list = List_adp_.New(); + public void Select(Bry_bfr bfr, int top) { + tbl.Select_by_top(select_list, 100); + Xoud_history_row[] db_rows = (Xoud_history_row[])select_list.To_ary_and_clear(Xoud_history_row.class); + Xow_wiki usr_wiki = app.User().Wikii(); + byte[] option_link = usr_wiki.Html__lnki_bldr().Href_(Bry_.new_a7("home"), usr_wiki.Ttl_parse(Bry_.new_a7("Options/PageHistory"))).Img_16x16(gplx.xowa.htmls.core.htmls.utls.Xoh_img_path.Img_option).Bld_to_bry();// HOME + byte[] delete_confirm_msg = app.Api_root().Usr().Bookmarks().Delete_confirm() ? Msg__delete_confirm : Bry_.Empty; + tbl_fmtr.Write(bfr, this, option_link, delete_confirm_msg, To_ui_rows(db_rows)); + } private static final byte[] Msg__delete_confirm = Bry_.new_a7(" data-dbui-delete_confirm_msg='Are you sure you want to delete this row?'"); + public String Del(byte[] row_id, byte[] row_pkey) { + Xoud_history_row db_row = Get_db_row(row_pkey); if (db_row == null) return Fail_missing_row(row_pkey); + tbl.Delete(db_row.Id()); + return msg_bldr.To_json_str__empty(); + } + public String Edit(byte[] row_id, byte[] row_pkey) {throw Err_.new_unimplemented();} + public String Save(byte[] row_id, byte[] row_pkey, Dbui_val_hash vals) {throw Err_.new_unimplemented();} + public String Reorder(byte[][] pkeys, int owner) {throw Err_.new_unimplemented();} + public Dbui_row_itm[] To_ui_rows(Xoud_history_row[] db_rows) { + int len = db_rows.length; + Dbui_row_itm[] rv = new Dbui_row_itm[len]; + for (int i = 0; i < len; ++i) + rv[i] = Get_ui_row(db_rows[i]); + return rv; + } + private Xoud_history_row Get_db_row(byte[] pkey) { + int id = Bry_.To_int(pkey); + return tbl.Select_or_null(id); + } + private Dbui_row_itm Get_ui_row(Xoud_history_row row) {return Get_ui_row(Int_.To_bry(row.Id()), row.Wiki(), row.Url(), row.Count(), row.Time());} + private Dbui_row_itm Get_ui_row(byte[] pkey, byte[] wiki, byte[] url, int count, DateAdp time) { + Dbui_val_itm[] vals = new Dbui_val_itm[4]; + vals[0] = new Dbui_val_itm(url, url_fmtr.Bld_bry_many(tmp_bfr, Gfh_utl.Escape_for_atr_val_as_bry(tmp_bfr, Byte_ascii.Apos, url))); + vals[1] = new Dbui_val_itm(wiki, wiki); + byte[] count_bry = Int_.To_bry(count); + vals[2] = new Dbui_val_itm(count_bry, count_bry); + byte[] time_bry = Bry_.new_u8(time.XtoStr_fmt_yyyy_MM_dd_HH_mm_ss()); + vals[3] = new Dbui_val_itm(time_bry, time_bry); + return new Dbui_row_itm(this, pkey, vals); + } + private String Fail_missing_row(byte[] row_pkey) { + return msg_bldr.Clear().Notify_fail_(Err_msg.To_str("Item has been deleted", "key", row_pkey)).Notify_hint_("Please reload the page").To_json_str(); + } + private static final Dbui_col_itm[] cols = new Dbui_col_itm[] + { new Dbui_col_itm(Dbui_col_itm.Type_id_str , 300, "page" , "Page") + , new Dbui_col_itm(Dbui_col_itm.Type_id_str , 150, "wiki" , "Wiki") + , new Dbui_col_itm(Dbui_col_itm.Type_id_int , 80, "views" , "Views") + , new Dbui_col_itm(Dbui_col_itm.Type_id_datetime, 100, "time" , "Time") + }; + private static final Dbui_btn_itm[] view_btns = new Dbui_btn_itm[] + { new Dbui_btn_itm("rows__delete" , "delete.png" , "delete") + }; + private static final Bry_fmtr url_fmtr = Bry_fmtr.new_("~{url}", "url"); + public static Dbui_tbl_itm__history get_or_new(Xoa_app app, Xoud_history_tbl db_tbl) { + if (I == null) { + I = new Dbui_tbl_itm__history(app, db_tbl); + I.Reg(app.Html__bridge_mgr().Cmd_mgr()); + } + return I; + } private static Dbui_tbl_itm__history I; +} diff --git a/400_xowa/src/gplx/xowa/users/history/Xou_history_cfg.java b/400_xowa/src/gplx/xowa/users/history/Xou_history_cfg.java index a27517de8..c88710036 100644 --- a/400_xowa/src/gplx/xowa/users/history/Xou_history_cfg.java +++ b/400_xowa/src/gplx/xowa/users/history/Xou_history_cfg.java @@ -13,3 +13,15 @@ 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.users.history; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +public class Xou_history_cfg implements Gfo_invk { + public Xou_history_cfg() { + this.enabled = true; // CFG: default to true for general user; privacy-conscious users must disable + } + public boolean Enabled() {return enabled;} private boolean enabled; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_enabled_)) enabled = m.ReadBool("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_enabled_ = "enabled_"; +} diff --git a/400_xowa/src/gplx/xowa/users/history/Xou_history_html.java b/400_xowa/src/gplx/xowa/users/history/Xou_history_html.java index a27517de8..39939db41 100644 --- a/400_xowa/src/gplx/xowa/users/history/Xou_history_html.java +++ b/400_xowa/src/gplx/xowa/users/history/Xou_history_html.java @@ -13,3 +13,45 @@ 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.users.history; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.specials.*; +public class Xou_history_html implements gplx.core.brys.Bfr_arg, Xow_special_page { + public Xow_special_meta Special__meta() {return Xow_special_meta_.Itm__page_history;} + public void Special__gen(Xow_wiki wikii, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl) { + Xowe_wiki wiki = (Xowe_wiki)wikii; Xoae_page page = (Xoae_page)pagei; + this.app = wiki.Appe(); this.mgr = app.Usere().History_mgr(); + mgr.Sort(); + Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_m001(); + html_grp.Bld_bfr_many(bfr, this); + page.Db().Text().Text_bry_(bfr.To_bry_and_rls()); + } + public void Bfr_arg__add(Bry_bfr bfr) { + int len = mgr.Len(); + for (int i = 0; i < len; i++) { + Xou_history_itm itm = mgr.Get_at(i); + html_itm.Bld_bfr_many(bfr, itm.Wiki(), itm.Page(), itm.View_count(), itm.View_end().XtoStr_fmt_yyyy_MM_dd_HH_mm()); + } + } private Xou_history_mgr mgr; Xoae_app app; + public Bry_fmtr Html_grp() {return html_grp;} Bry_fmtr html_grp = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , " " + , " " + , " " + , " " + , " " + , " ~{itms}" + , "
      pagewikiviewstime
      " + ), "itms"); + public Bry_fmtr Html_itm() {return html_itm;} Bry_fmtr html_itm = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , " " + , " [[~{itm_wiki}:~{itm_page}|~{itm_page}]]" + , " ~{itm_wiki}" + , " ~{itm_count}" + , " ~{itm_last}" + , " " + ), "itm_wiki", "itm_page", "itm_count", "itm_last"); + + public Xow_special_page Special__clone() {return this;} +} diff --git a/400_xowa/src/gplx/xowa/users/history/Xou_history_itm.java b/400_xowa/src/gplx/xowa/users/history/Xou_history_itm.java index a27517de8..6d816827e 100644 --- a/400_xowa/src/gplx/xowa/users/history/Xou_history_itm.java +++ b/400_xowa/src/gplx/xowa/users/history/Xou_history_itm.java @@ -13,3 +13,65 @@ 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.users.history; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.core.primitives.*; +public class Xou_history_itm { + private Xou_history_itm() {} + public Xou_history_itm(byte[] wiki, byte[] page) { + // remove "\n" from page for Category b/c it breaks the csv_parser; DATE:2016-10-12 + int nl_pos = Bry_find_.Find_fwd(page, Byte_ascii.Nl); + if (nl_pos != Bry_find_.Not_found) { + page = Bry_.Mid(page, 0, nl_pos); + } + + this.wiki = wiki; + this.page = page; + this.key = key_(wiki, page); + this.view_bgn = Datetime_now.Get(); + } + public byte[] Key() {return key;} private byte[] key; + public byte[] Wiki() {return wiki;} private byte[] wiki; + public byte[] Page() {return page;} private byte[] page; + public int View_count() {return view_count;} private int view_count; + public DateAdp View_bgn() {return view_bgn;} DateAdp view_bgn; + public DateAdp View_end() {return view_end;} DateAdp view_end; + public Object Fld(int idx) { + switch (idx) { + case Fld_key : return key; + case Fld_wiki : return wiki; + case Fld_page : return page; + case Fld_view_count : return view_count; + case Fld_view_bgn : return view_bgn; + case Fld_view_end : return view_end; + default : throw Err_.new_unhandled(idx); + } + } + public void Tally() { + view_end = view_count == 0 ? view_bgn : Datetime_now.Get(); + view_count++; + } + public void Merge(Xou_history_itm merge) { + view_count += merge.View_count(); + if (merge.View_bgn().compareTo(view_bgn) < CompareAble_.Same) view_bgn = merge.View_bgn(); + if (merge.View_end().compareTo(view_end) > CompareAble_.Same) view_end = merge.View_end(); + } + public static Xou_history_itm csv_(byte[] ary, Int_obj_ref pos) { + Xou_history_itm rv = new Xou_history_itm(); + rv.view_bgn = Bry_.ReadCsvDte(ary, pos, Bry_.Dlm_fld); + rv.view_end = Bry_.ReadCsvDte(ary, pos, Bry_.Dlm_fld); + rv.view_count = Bry_.ReadCsvInt(ary, pos, Bry_.Dlm_fld); + rv.wiki = Bry_.ReadCsvBry(ary, pos, Bry_.Dlm_fld); + rv.page = Bry_.ReadCsvBry(ary, pos, Bry_.Dlm_row); + rv.key = key_(rv.wiki, rv.page); + return rv; + } + public void Save(Bry_bfr bb) { + bb .Add_dte(view_bgn) .Add_byte(Bry_.Dlm_fld) + .Add_dte(view_end) .Add_byte(Bry_.Dlm_fld) + .Add_int_variable(view_count) .Add_byte(Bry_.Dlm_fld) + .Add(wiki) .Add_byte(Bry_.Dlm_fld) + .Add(page) .Add_byte(Bry_.Dlm_row); + } + public static byte[] key_(byte[] wiki, byte[] page) {return Bry_.Add(wiki, Key_dlm, page);} private static final byte[] Key_dlm = Byte_ascii.Pipe_bry; + public static final byte Fld_key = 0, Fld_wiki = 1, Fld_page = 2, Fld_view_count = 3, Fld_view_bgn = 4, Fld_view_end = 5; +} diff --git a/400_xowa/src/gplx/xowa/users/history/Xou_history_mgr.java b/400_xowa/src/gplx/xowa/users/history/Xou_history_mgr.java index a27517de8..1b33311ea 100644 --- a/400_xowa/src/gplx/xowa/users/history/Xou_history_mgr.java +++ b/400_xowa/src/gplx/xowa/users/history/Xou_history_mgr.java @@ -13,3 +13,153 @@ 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.users.history; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.core.primitives.*; import gplx.core.net.*; import gplx.core.net.qargs.*; import gplx.xowa.htmls.hrefs.*; +public class Xou_history_mgr implements Gfo_invk { + private final Xou_history_html html_mgr = new Xou_history_html(); private Xou_history_sorter sorter = new Xou_history_sorter().Sort_fld_(Xou_history_itm.Fld_view_end).Ascending_(false); + private final Io_url history_fil; + private Ordered_hash itms = Ordered_hash_.New_bry(); + private boolean load_chk = false; + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + private boolean log_all = false; + public Xou_history_mgr(Io_url history_fil) { + this.history_fil = history_fil; + } + public void Init_by_app(Xoa_app app) { + app.Cfg().Bind_many_app(this, Cfg__enabled, Cfg__log_all); + } + public boolean Enabled() {return enabled;} private boolean enabled = true; + public int Len() {return itms.Count();} + public void Clear() {itms.Clear();} + public Xou_history_itm Get_at(int i) {return (Xou_history_itm)itms.Get_at(i);} + public String Get_at_last() { + if (!load_chk) Load(); + int len = itms.Count(); if (len == 0) return String_.new_a7(Xoa_page_.Main_page_bry); // if no history, return Main_page (which should go to home/wiki/Main_page) + Xou_history_itm itm = (Xou_history_itm)itms.Get_at(0); + return String_.new_u8(Bry_.Add(itm.Wiki(), Xoh_href_.Bry__wiki, itm.Page())); + } + public Xou_history_itm Get_or_null(byte[] wiki, byte[] page) { + if (!load_chk) Load(); + byte[] key = Xou_history_itm.key_(wiki, page); + return (Xou_history_itm)itms.Get_by(key); + } + public boolean Has(byte[] wiki, byte[] page) { + if (!load_chk) Load(); + byte[] key = Xou_history_itm.key_(wiki, page); + return itms.Has(key); + } + public void Add(Xoae_page page) { + Xoa_url url = page.Url(); + Xoa_ttl ttl = page.Ttl(); + byte[] page_ttl = null; + if (page.Redirect_trail().Itms__len() > 0) // page was redirected; add src ttl to history, not trg; EX: UK -> United Kingdom; add "UK"; DATE:2014-02-28 + page_ttl = page.Redirect_trail().Itms__get_at_0th_or_null().Ttl().Page_db(); + else { + page_ttl = Bry_.Add(ttl.Ns().Name_db_w_colon(), ttl.Page_txt()); // use ttl.Page_txt() b/c it normalizes space/casing (url.Page_txt does not) + if (url.Qargs_ary().length > 0) + page_ttl = Bry_.Add(page_ttl, url.Qargs_mgr().To_bry()); + } + Add(page.Wiki().App(), url, ttl, page_ttl); + } + public void Add(Xoa_app app, Xoa_url url, Xoa_ttl ttl, byte[] page_ttl) { + if (gplx.xowa.users.history.Xoud_history_mgr.Skip_history(ttl)) return; + if (!load_chk) Load(); + byte[] key = Xou_history_itm.key_(url.Wiki_bry(), page_ttl); + Xou_history_itm itm = (Xou_history_itm)itms.Get_by(key); + if (itm == null) { + itm = new Xou_history_itm(url.Wiki_bry(), To_full_db_w_qargs(url, ttl)); + itms.Add(key, itm); + } + if (log_all) + Io_mgr.Instance.AppendFilStr(history_fil.GenNewNameAndExt("log_all.csv"), String_.Format("{0}|{1}|{2}\n", Datetime_now.Get().XtoStr_fmt_iso_8561_w_tz(), itm.Wiki(), itm.Page())); + itm.Tally(); + } + private byte[] To_full_db_w_qargs(Xoa_url url, Xoa_ttl ttl) { + byte[] page = Xoa_ttl.Replace_spaces(ttl.Full_txt_wo_qarg()); + tmp_bfr.Add(page); + Gfo_qarg_mgr_old qarg_mgr = url.Qargs_mgr(); + qarg_mgr.To_bry(tmp_bfr, gplx.langs.htmls.encoders.Gfo_url_encoder_.Href, Bool_.N); + return tmp_bfr.To_bry_and_clear(); + } + public void Sort() {itms.Sort_by(sorter);} + public void Load() { + if (load_chk) return; + load_chk = true; + itms.Clear(); + Xou_history_itm_srl.Load(Io_mgr.Instance.LoadFilBry(history_fil), itms); + itms.Sort_by(sorter); + } + public void Save(Xoae_app app) { + if (!load_chk) return; // nothing loaded; nothing to save + int itms_len = itms.Count(); + if (itms_len == 0) return; // no items; occurs when history disable; + itms.Sort_by(sorter); + if (itms_len > current_itms_max) itms = Archive(app); + byte[] ary = Xou_history_itm_srl.Save(itms); + Io_mgr.Instance.SaveFilBry(app.Usere().Fsys_mgr().App_data_history_fil(), ary); + } + public Ordered_hash Archive(Xoae_app app) { + // sort all itms; other init + itms.Sort_by(sorter); + Ordered_hash current_itms = Ordered_hash_.New_bry(); + Ordered_hash archive_itms = Ordered_hash_.New_bry(); + + // iterate over all itms + int itms_len = itms.Count(); + for (int i = 0; i < itms_len; i++) { + Xou_history_itm itm = (Xou_history_itm)itms.Get_at(i); + Ordered_hash itms_hash = (i < current_itms_reset) ? current_itms : archive_itms; // if < cutoff, add to current file; else add to archive + itms_hash.Add_if_dupe_use_nth(itm.Key(), itm); // NOTE: dupes should not exist, but if they do, ignore it; else app won't close on first time; DATE:2016-07-14 + } + + // save archive + byte[] ary = Xou_history_itm_srl.Save(archive_itms); + Io_url url = app.Usere().Fsys_mgr().App_data_history_fil().GenNewNameOnly(Datetime_now.Get().XtoStr_fmt_yyyyMMdd_HHmmss_fff()); + Io_mgr.Instance.SaveFilBry(url, ary); + return current_itms; + } private int current_itms_max = 512, current_itms_reset = 256; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_html_grp)) return String_.new_u8(html_mgr.Html_grp().Fmt()); + else if (ctx.Match(k, Invk_html_grp_)) html_mgr.Html_grp().Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_html_itm)) return String_.new_u8(html_mgr.Html_itm().Fmt()); + else if (ctx.Match(k, Invk_html_itm_)) html_mgr.Html_itm().Fmt_(m.ReadBry("v")); + else if (ctx.Match(k, Invk_current_itms_max_)) current_itms_max = m.ReadInt("v"); + else if (ctx.Match(k, Invk_current_itms_reset_)) current_itms_reset = m.ReadInt("v"); + else if (ctx.Match(k, Cfg__enabled)) enabled = m.ReadYn("v"); + else if (ctx.Match(k, Cfg__log_all)) log_all = m.ReadYn("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Invk_html_grp = "html_grp", Invk_html_grp_ = "html_grp_", Invk_html_itm = "html_itm", Invk_html_itm_ = "html_itm_", Invk_current_itms_max_ = "current_itms_max_", Invk_current_itms_reset_ = "current_itms_reset_"; + public static final byte[] Ttl_name = Bry_.new_a7("XowaPageHistory"); + public static final byte[] Ttl_full = Bry_.new_a7("Special:XowaPageHistory"); + private static final String Cfg__enabled = "xowa.app.page_history.enabled", Cfg__log_all = "xowa.app.page_history.log_all"; +} +class Xou_history_itm_srl { + public static void Load(byte[] ary, Ordered_hash list) { + try { + list.Clear(); + int aryLen = ary.length; + if (aryLen == 0) return; // no file + Int_obj_ref pos = Int_obj_ref.New_zero(); + while (true) { + if (pos.Val() == aryLen) break; + Xou_history_itm itm = Xou_history_itm.csv_(ary, pos); + byte[] key = itm.Key(); + Xou_history_itm existing = (Xou_history_itm)list.Get_by(key); + if (existing == null) // new itm; add + list.Add(itm.Key(), itm); + else // existing itm; update + existing.Merge(itm); + } + } + catch (Exception e) {throw Err_.new_parse_exc(e, Xou_history_itm.class, String_.new_u8(ary));} + } + public static byte[] Save(Ordered_hash list) { + Bry_bfr bb = Bry_bfr_.New(); + int listLen = list.Count(); + for (int i = 0; i < listLen; i++) + ((Xou_history_itm)list.Get_at(i)).Save(bb); + return bb.To_bry(); + } +} diff --git a/400_xowa/src/gplx/xowa/users/history/Xou_history_mgr_tst.java b/400_xowa/src/gplx/xowa/users/history/Xou_history_mgr_tst.java index a27517de8..1516b7cb2 100644 --- a/400_xowa/src/gplx/xowa/users/history/Xou_history_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/users/history/Xou_history_mgr_tst.java @@ -13,3 +13,86 @@ 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.users.history; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import org.junit.*; +public class Xou_history_mgr_tst { + private Xou_history_mgr_fxt fxt = new Xou_history_mgr_fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Archive() { + Datetime_now.Manual_y_(); // NOTE: each DateTime_.Now() advances clock by 1 min; adding a new Datetime_now.Get() anywhere will throw off times on this test; DATE:2014-04-01 + fxt.Invk(Xou_history_mgr.Invk_current_itms_max_, 4).Invk(Xou_history_mgr.Invk_current_itms_reset_, 2); + fxt.Add_many("A", "B", "C", "D", "E"); + fxt.Save(); + fxt.List_tst("E", "D"); + fxt.Fil_tst("mem/xowa/user/test_user/app/data/history/20010101_001000.000.csv", String_.Concat_lines_nl + ( "20010101 000500.000|20010101 000500.000|1|en.wikipedia.org|C" + , "20010101 000300.000|20010101 000300.000|1|en.wikipedia.org|B" + , "20010101 000100.000|20010101 000100.000|1|en.wikipedia.org|A" + )); + } + @Test public void Normalize() { + fxt.Clear(); + fxt.Add_many("Category:A_B", "Category:A B", "Category:a B", "Category:_A B_"); + fxt.List_tst("Category:A_B"); + } + @Test public void Args() { + fxt.Clear(); + fxt.Add_one("Special:AllPages", "?from=A"); + fxt.List_tst("Special:AllPages?from=A"); + } + @Test public void Remove_nl() { + fxt.Clear(); + fxt.Add_many("Category:A?pagefrom=B\nB"); + fxt.List_tst("Category:A?pagefrom=B"); + } +} +class Xou_history_mgr_fxt { + Xoae_app app; Xowe_wiki wiki; + Xou_history_mgr under; + public void Clear() { + if (app == null) { + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + under = app.Usere().History_mgr(); + } + Io_mgr.Instance.DeleteDirDeep(Io_url_.new_dir_("mem/xowa/user/test_user/app/data/history/")); + under.Clear(); + } + public Xou_history_mgr_fxt Add_many(String... ary) { + int ary_len = ary.length; + for (int i = 0; i < ary_len; i++) { + String itm = ary[i]; + Add_one(itm, null); + } + return this; + } + public Xou_history_mgr_fxt Add_one(String ttl_str, String arg_str) { + byte[] ttl_bry = Bry_.new_u8(ttl_str); + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, ttl_bry); + Xoae_page page = Xoae_page.New_test(wiki, ttl); + page.Db().Page().Modified_on_(Datetime_now.Get()); + byte[] url_bry = ttl_bry; + if (arg_str != null) url_bry = Bry_.Add(url_bry, Bry_.new_u8(arg_str)); + Xoa_url url = wiki.Utl__url_parser().Parse(url_bry); + page.Url_(url); // set url b/c history_mgr.Add uses url + under.Add(page); + return this; + } + public Xou_history_mgr_fxt List_tst(String... expd) { + int actl_len = under.Len(); + String[] actl = new String[actl_len]; + for (int i = 0; i < actl_len; i++) { + Xou_history_itm itm = under.Get_at(i); + actl[i] = String_.new_u8(itm.Page()); + } + Tfds.Eq_ary_str(expd, actl); + return this; + } + public Xou_history_mgr_fxt Invk(String key, Object v) {Gfo_invk_.Invk_by_val(under, key, v); return this;} + public Xou_history_mgr_fxt Save() {under.Save(app); return this;} + public Xou_history_mgr_fxt Fil_tst(String expd_url, String expd) { + String actl = Io_mgr.Instance.LoadFilStr(expd_url); + Tfds.Eq_str_lines(expd, actl); + return this; + } +} diff --git a/400_xowa/src/gplx/xowa/users/history/Xou_history_sorter.java b/400_xowa/src/gplx/xowa/users/history/Xou_history_sorter.java index a27517de8..edc79d80b 100644 --- a/400_xowa/src/gplx/xowa/users/history/Xou_history_sorter.java +++ b/400_xowa/src/gplx/xowa/users/history/Xou_history_sorter.java @@ -13,3 +13,14 @@ 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.users.history; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +public class Xou_history_sorter implements gplx.core.lists.ComparerAble { + public boolean Ascending() {return ascending;} public Xou_history_sorter Ascending_(boolean v) {ascending = v; return this;} private boolean ascending = false; + public int Sort_fld() {return sort_fld;} public Xou_history_sorter Sort_fld_(int v) {sort_fld = v; return this;} private int sort_fld = Xou_history_itm.Fld_view_end; + public int compare(Object lhsObj, Object rhsObj) { + Xou_history_itm lhs = (Xou_history_itm)lhsObj, rhs = (Xou_history_itm)rhsObj; + int comp = CompareAble_.Compare_obj(lhs.Fld(sort_fld), rhs.Fld(sort_fld)); + if (!ascending) comp *= -1; + return comp; + } +} diff --git a/400_xowa/src/gplx/xowa/users/history/Xoud_history_mgr.java b/400_xowa/src/gplx/xowa/users/history/Xoud_history_mgr.java index a27517de8..b6258446b 100644 --- a/400_xowa/src/gplx/xowa/users/history/Xoud_history_mgr.java +++ b/400_xowa/src/gplx/xowa/users/history/Xoud_history_mgr.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.users.history; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.core.threads.*; import gplx.dbs.*; +import gplx.xowa.specials.*; +public class Xoud_history_mgr implements Gfo_invk { + private Xoud_history_tbl history_tbl; + public void Conn_(Db_conn conn, boolean created) { + this.history_tbl = new Xoud_history_tbl(conn); + if (!conn.Meta_tbl_exists(history_tbl.Tbl_name())) history_tbl.Create_tbl(); + } + public void Update_async(Gfo_async_mgr async_mgr, Xoa_ttl ttl, Xoa_url url) { +// if (Skip_history(ttl)) return; +// async_mgr.Queue(this, Invk_update, "wiki", String_.new_u8(url.Wiki_bry()), "page", String_.new_u8(url.Page_bry()), "qarg", String_.new_u8(url.Args_all_as_bry())); + } + private void Update(String wiki, String page, String qarg) { +// Xoud_history_row row = history_tbl.Select_by_page(wiki, page, qarg); +// DateAdp time = Datetime_now.Get(); +// if (row == null) +// history_tbl.Insert(wiki, page, qarg, time, 1); +// else +// history_tbl.Update(wiki, page, qarg, time, row.Count() + 1); + } + public void Select(List_adp rv, int top) { + history_tbl.Select_by_top(rv, top); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_update)) Update(m.ReadStr("wiki"), m.ReadStr("page"), m.ReadStr("qarg")); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_update = "update"; + public static boolean Skip_history(Xoa_ttl ttl) { + byte[] page_db = ttl.Page_db(); + return ( ttl.Ns().Id_is_special() + && ( Bry_.Eq(page_db, gplx.xowa.users.history.Xou_history_mgr.Ttl_name) // do not add XowaPageHistory to history + || Bry_.Eq(page_db, Xow_special_meta_.Itm__popup_history.Key_bry()) + || Bry_.Eq(page_db, Xow_special_meta_.Itm__default_tab.Key_bry()) + || Bry_.Eq(page_db, Xow_special_meta_.Itm__page_history.Key_bry()) + ) + ); + } +} diff --git a/400_xowa/src/gplx/xowa/users/history/Xoud_history_row.java b/400_xowa/src/gplx/xowa/users/history/Xoud_history_row.java index a27517de8..5ac53a7c3 100644 --- a/400_xowa/src/gplx/xowa/users/history/Xoud_history_row.java +++ b/400_xowa/src/gplx/xowa/users/history/Xoud_history_row.java @@ -13,3 +13,16 @@ 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.users.history; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +public class Xoud_history_row { + public Xoud_history_row(int id, byte[] wiki, byte[] url, DateAdp time, int count) { + this.id = id; + this.wiki = wiki; this.url = url; + this.time = time; this.count = count; + } + public int Id() {return id;} private final int id; + public byte[] Wiki() {return wiki;} private final byte[] wiki; + public byte[] Url() {return url;} private final byte[] url; + public DateAdp Time() {return time;} private final DateAdp time; + public int Count() {return count;} private final int count; +} diff --git a/400_xowa/src/gplx/xowa/users/history/Xoud_history_special.java b/400_xowa/src/gplx/xowa/users/history/Xoud_history_special.java index a27517de8..13add3d05 100644 --- a/400_xowa/src/gplx/xowa/users/history/Xoud_history_special.java +++ b/400_xowa/src/gplx/xowa/users/history/Xoud_history_special.java @@ -13,3 +13,19 @@ 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.users.history; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.xowa.specials.*; +public class Xoud_history_special implements Xow_special_page { + public Xow_special_meta Special__meta() {return Xow_special_meta_.Itm__page_history;} + public void Special__gen(Xow_wiki wikii, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl) { + Xowe_wiki wiki = (Xowe_wiki)wikii; Xoae_page page = (Xoae_page)pagei; + Xoa_app app = wiki.App(); + Dbui_tbl_itm__history ui_tbl = Dbui_tbl_itm__history.get_or_new(app, app.User().User_db_mgr().Db_file().Tbl__history()); + page.Html_data().Head_mgr().Itm__dbui().Init(app).Enabled_y_(); + Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_m001(); + ui_tbl.Select(bfr, 100); + page.Db().Html().Html_bry_(bfr.To_bry_and_rls()); + } + + public Xow_special_page Special__clone() {return this;} +} diff --git a/400_xowa/src/gplx/xowa/users/history/Xoud_history_tbl.java b/400_xowa/src/gplx/xowa/users/history/Xoud_history_tbl.java index a27517de8..f3543d747 100644 --- a/400_xowa/src/gplx/xowa/users/history/Xoud_history_tbl.java +++ b/400_xowa/src/gplx/xowa/users/history/Xoud_history_tbl.java @@ -13,3 +13,82 @@ 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.users.history; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.dbs.metas.*; import gplx.dbs.metas.updates.*; +public class Xoud_history_tbl implements Rls_able { + private final String tbl_name = "user_history"; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_id, fld_wiki, fld_url, fld_time, fld_count; + private final Db_conn conn; private Db_stmt stmt_select_by_page, stmt_select_by_top, stmt_insert, stmt_update, stmt_delete; + public Xoud_history_tbl(Db_conn conn) { + this.conn = conn; + fld_id = flds.Add_int_pkey_autonum("hist_id"); + fld_wiki = flds.Add_str("hist_wiki", 255); + fld_url = flds.Add_str("hist_url", 255); + fld_time = flds.Add_str("hist_time", 20); + fld_count = flds.Add_int("hist_count"); + stmt_insert = stmt_update = stmt_delete = stmt_select_by_page = stmt_select_by_top = null; + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds, Dbmeta_idx_itm.new_unique_by_tbl(tbl_name, "pkey", fld_wiki, fld_url)));} + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + stmt_update = Db_stmt_.Rls(stmt_update); + stmt_delete = Db_stmt_.Rls(stmt_delete); + stmt_select_by_page = Db_stmt_.Rls(stmt_select_by_page); + stmt_select_by_top = Db_stmt_.Rls(stmt_select_by_top); + } + public void Insert(byte[] wiki, byte[] url, DateAdp time, int count) { + if (stmt_insert == null) stmt_insert = conn.Stmt_insert(tbl_name, flds); + stmt_insert.Clear() + .Val_bry_as_str(fld_wiki, wiki) + .Val_bry_as_str(fld_url , url) + .Val_str(fld_time, time.XtoStr_fmt_iso_8561()) + .Val_int(fld_count, count) + .Exec_insert(); + } + public void Update(int id, DateAdp time, int count) { + if (stmt_update == null) stmt_update = conn.Stmt_update(tbl_name, String_.Ary(fld_id), fld_time, fld_count); + stmt_update.Clear() + .Val_str(fld_time, time.XtoStr_fmt_iso_8561()) + .Val_int(fld_count, count) + .Crt_int(fld_id, id) + .Exec_update(); + } + public void Delete(int id) { + Db_stmt stmt_delete = conn.Stmt_delete(tbl_name, fld_id); + stmt_delete.Clear().Crt_int(fld_id, id).Exec_delete(); + } + public Xoud_history_row Select_or_null(int id) { + if (stmt_select_by_page == null) stmt_select_by_page = conn.Stmt_select(tbl_name, flds, fld_id); + Db_rdr rdr = stmt_select_by_page.Clear().Crt_int(fld_id, id).Exec_select__rls_manual(); + try { + return rdr.Move_next() ? new_row(rdr) : null; + } + finally {rdr.Rls();} + } + public void Select_by_top(List_adp rv, int count) { + if (stmt_select_by_top == null) { + Db_qry__select_in_tbl qry = new Db_qry__select_in_tbl(tbl_name, flds.To_str_ary(), null, null, null, fld_time + " DESC", " LIMIT " + Int_.To_str(count)); + stmt_select_by_top = conn.Stmt_new(qry); + } + Db_rdr rdr = stmt_select_by_top.Clear().Exec_select__rls_manual(); + try { + rv.Clear(); + while (rdr.Move_next()) { + Xoud_history_row row = new_row(rdr); + rv.Add(row); + } + } + finally {rdr.Rls();} + } + private Xoud_history_row new_row(Db_rdr rdr) { + return new Xoud_history_row + ( rdr.Read_int(fld_id) + , rdr.Read_bry_by_str(fld_wiki) + , rdr.Read_bry_by_str(fld_url) + , rdr.Read_date_by_str(fld_time) + , rdr.Read_int(fld_count) + ); + } +} diff --git a/400_xowa/src/gplx/xowa/users/wikis/Xofs_url_itm_parser.java b/400_xowa/src/gplx/xowa/users/wikis/Xofs_url_itm_parser.java index a27517de8..45b330f15 100644 --- a/400_xowa/src/gplx/xowa/users/wikis/Xofs_url_itm_parser.java +++ b/400_xowa/src/gplx/xowa/users/wikis/Xofs_url_itm_parser.java @@ -13,3 +13,66 @@ 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.users.wikis; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import gplx.core.envs.*; +class Xofs_url_itm { + public boolean Tid_is_xowa() {return tid_is_xowa;} public void Tid_is_xowa_(boolean v) {tid_is_xowa = v;} private boolean tid_is_xowa = true; + public String Raw() {return raw;} public void Raw_(String v) {raw = v;} private String raw; + public String Url() {return url;} public void Url_(String v) {url = v;} private String url; +} +class Xofs_url_itm_parser { + private static final byte[] Xowa_fs_protocol = Bry_.new_a7("xowa-fs://"); + private static final int Xowa_fa_protocol_len = Xowa_fs_protocol.length; + private Bry_bfr url_bfr = Bry_bfr_.Reset(16); + private Hash_adp_bry names = Hash_adp_bry.cs(); + public byte Dir_spr() {return dir_spr;} public void Dir_spr_(byte v) {dir_spr = v;} private byte dir_spr = Op_sys.Cur().Fsys_dir_spr_byte(); + public void Names_add(String key_str, String val_str) { + byte[] key_bry = Bry_.new_u8(key_str); + byte[] val_bry = Bry_.new_u8(val_str); + names.Add_if_dupe_use_nth(key_bry, val_bry); + } + public void Parse(Xofs_url_itm itm, String raw_str) { + itm.Raw_(raw_str); + byte[] raw = Bry_.new_u8(raw_str); + if (!Bry_.Has_at_bgn(raw, Xowa_fs_protocol)) { // raw does not start with "xowa-fs://"; mark as custom str and exit + itm.Tid_is_xowa_(false); + itm.Url_(raw_str); + return; + } + itm.Tid_is_xowa_(true); + url_bfr.Clear(); + int raw_len = raw.length; + for (int i = Xowa_fa_protocol_len; i < raw_len; ++i) { + byte b = raw[i]; + switch (b) { + case Byte_ascii.Slash: // "xowa-fs://" defines "/" as dir separator + url_bfr.Add_byte(dir_spr); // swap out to cur os dir_spr + break; + case Byte_ascii.Tilde: // "xowa-fs://" defines "~{" as swap symbol + int remaining = raw_len - i - 1; // -1 + if (remaining > 0 && raw[i + 1] == Byte_ascii.Curly_bgn) { // "~{" + if (remaining > 2 && raw[i+2] == Byte_ascii.Tilde && raw[i+3] == Byte_ascii.Curly_bgn) { // "~{~{" -> "~{" + url_bfr.Add_byte(Byte_ascii.Tilde).Add_byte(Byte_ascii.Curly_bgn); + i += 3; + } + else { + int name_bgn = i + 2; // skip "~{" + int name_end = Bry_find_.Find_fwd(raw, Byte_ascii.Curly_end, name_bgn); + byte[] name = (byte[])names.Get_by_mid(raw, name_bgn, name_end); + if (name == null) throw Err_.new_wo_type("name not found", "raw", raw_str, "name", String_.new_u8(raw, name_bgn, name_end)); + url_bfr.Add(name); + i = name_end; + } + } + else { + url_bfr.Add_byte(b); + } + break; + default: + url_bfr.Add_byte(b); + break; + } + } + itm.Url_(url_bfr.To_str_and_clear()); + } +} diff --git a/400_xowa/src/gplx/xowa/users/wikis/Xofs_url_itm_parser_tst.java b/400_xowa/src/gplx/xowa/users/wikis/Xofs_url_itm_parser_tst.java index a27517de8..ebbeebdc2 100644 --- a/400_xowa/src/gplx/xowa/users/wikis/Xofs_url_itm_parser_tst.java +++ b/400_xowa/src/gplx/xowa/users/wikis/Xofs_url_itm_parser_tst.java @@ -13,3 +13,39 @@ 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.users.wikis; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +import org.junit.*; import gplx.core.envs.*; +public class Xofs_url_itm_parser_tst { + @Before public void init() {fxt.Clear();} private Xofs_url_itm_parser_fxt fxt = new Xofs_url_itm_parser_fxt(); + @Test public void Custom() {fxt.Test_parse_custom("/xowa/wiki/en.wikipedia.org/");} + @Test public void Lnx() {fxt.Init_dir_spr_lnx().Init_name("xowa", "/xowa") .Test_parse("xowa-fs://~{xowa}/bin/any/", "/xowa/bin/any/");} + @Test public void Wnt() {fxt.Init_dir_spr_wnt().Init_name("xowa", "C:\\xowa") .Test_parse("xowa-fs://~{xowa}/bin/any/", "C:\\xowa\\bin\\any\\");} + @Test public void Outliers() { + fxt.Init_name("xowa", "/xowa"); + fxt.Test_parse("xowa-fs://ab" , "ab"); // no subst + fxt.Test_parse("xowa-fs://a~b" , "a~b"); // tilde + fxt.Test_parse("xowa-fs://a~{~{b" , "a~{b"); // escape + fxt.Test_parse("xowa-fs://ab~" , "ab~"); // eos + fxt.Test_parse("xowa-fs://ab~{~{" , "ab~{"); // eos + } +} +class Xofs_url_itm_parser_fxt { + private Xofs_url_itm_parser parser; + private Xofs_url_itm itm = new Xofs_url_itm(); + public void Clear() { + parser = new Xofs_url_itm_parser(); + } + public Xofs_url_itm_parser_fxt Init_name(String key, String val) {parser.Names_add(key, val); return this;} + public Xofs_url_itm_parser_fxt Init_dir_spr_lnx() {parser.Dir_spr_(Op_sys.Lnx.Fsys_dir_spr_byte()); return this;} + public Xofs_url_itm_parser_fxt Init_dir_spr_wnt() {parser.Dir_spr_(Op_sys.Wnt.Fsys_dir_spr_byte()); return this;} + public void Test_parse_custom(String raw) { + parser.Parse(itm, raw); + Tfds.Eq(Bool_.N, itm.Tid_is_xowa()); + Tfds.Eq(raw, itm.Url()); + } + public void Test_parse(String raw, String expd) { + parser.Parse(itm, raw); + Tfds.Eq(Bool_.Y, itm.Tid_is_xowa()); + Tfds.Eq(expd, itm.Url()); + } +} diff --git a/400_xowa/src/gplx/xowa/users/wikis/Xou_wiki_mgr.java b/400_xowa/src/gplx/xowa/users/wikis/Xou_wiki_mgr.java index a27517de8..357bb8417 100644 --- a/400_xowa/src/gplx/xowa/users/wikis/Xou_wiki_mgr.java +++ b/400_xowa/src/gplx/xowa/users/wikis/Xou_wiki_mgr.java @@ -13,3 +13,37 @@ 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.users.wikis; import gplx.*; import gplx.xowa.*; import gplx.xowa.users.*; +class Xow_wiki_loader { + public void Load(String mount) { + /* + load mount + Xow_mount_links links = user.Load_links(mount_id); + load data + for (int i = 0; i < links_len; i++) { + switch (link_tid) { + case data: + + wiki.Db_mgr().Root_url() = Get_data(rel_id); + break; + } + } + */ + } +} +class Xou_wiki_itm_source { + public static final int Tid_user = 1, Tid_wmf = 2, Tid_wikia = 3; +} +class Xou_wiki_itm_path_layout { + public static final int Tid_multiple = 1, Tid_root = 2; +} +class Xou_wiki_part { + public int Id() {return id;} public void Id_(int v) {id = v;} private int id; + public boolean Deleted() {return deleted;} public void Deleted_(boolean v) {deleted = v;} private boolean deleted; + public Xofs_url_itm Url() {return url;} private Xofs_url_itm url = new Xofs_url_itm(); + public String Domain() {return domain;} public void Domain_(String v) {domain = v;} private String domain; + public String Version() {return version;} public void Version_(String v) {version = v;} private String version; + public String Source() {return source;} public void Source_(String v) {source = v;} private String source; + public DateAdp Make_date() {return make_date;} public void Make_date_(DateAdp v) {make_date = v;} private DateAdp make_date; + public String Misc() {return misc;} public void Misc_(String v) {misc = v;} private String misc; +} diff --git a/400_xowa/src/gplx/xowa/wikis/Xoa_wiki_mgr.java b/400_xowa/src/gplx/xowa/wikis/Xoa_wiki_mgr.java index a27517de8..c8af76488 100644 --- a/400_xowa/src/gplx/xowa/wikis/Xoa_wiki_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/Xoa_wiki_mgr.java @@ -13,3 +13,15 @@ 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.wikis; import gplx.*; import gplx.xowa.*; +public interface Xoa_wiki_mgr extends Gfo_invk { + int Count(); + boolean Has(byte[] key); + Xow_wiki Get_at(int idx); + Xow_wiki Get_by_or_null(byte[] key); + Xow_wiki Get_by_or_make_init_y(byte[] key); + Xow_wiki Get_by_or_make_init_n(byte[] key); + void Add(Xow_wiki wiki); + Xow_wiki Make(byte[] domain_bry, Io_url wiki_root_dir); + Xow_wiki Import_by_url(Io_url fil); +} diff --git a/400_xowa/src/gplx/xowa/wikis/Xoa_wiki_mgr_.java b/400_xowa/src/gplx/xowa/wikis/Xoa_wiki_mgr_.java index a27517de8..abfc2bfc9 100644 --- a/400_xowa/src/gplx/xowa/wikis/Xoa_wiki_mgr_.java +++ b/400_xowa/src/gplx/xowa/wikis/Xoa_wiki_mgr_.java @@ -13,3 +13,31 @@ 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.wikis; import gplx.*; import gplx.xowa.*; +import gplx.dbs.*; +import gplx.dbs.cfgs.*; import gplx.xowa.wikis.data.*; import gplx.xowa.bldrs.infos.*; +public class Xoa_wiki_mgr_ { + public static Xow_wiki Import_by_url(Xoa_app app, Xoa_wiki_mgr wiki_mgr, Io_url url) { + Db_conn conn = Db_conn_bldr.Instance.Get_or_noop(url); if (conn == Db_conn_.Noop) return null; // invalid url + Db_cfg_tbl cfg_tbl = Xowd_cfg_tbl_.Get_or_null(conn); if (cfg_tbl == null) return null; // no xowa_cfg; + byte[] wiki_domain = cfg_tbl.Select_bry(Xowd_cfg_key_.Grp__bldr_session, Xob_info_session.Cfg_key__wiki_domain); if (wiki_domain == null) return null; + Io_url wiki_root_dir = url.OwnerDir(); + Xow_wiki rv = wiki_mgr.Make(wiki_domain, wiki_root_dir); + wiki_mgr.Add(rv); + rv.Init_by_wiki(); // must init for Modified_latest + String wiki_date = rv.Props().Modified_latest__yyyy_MM_dd(); + app.User().User_db_mgr().Site_mgr().Import(rv.Domain_str(), rv.Domain_str(), wiki_root_dir.Raw(), wiki_date, ""); + if (app.Tid_is_edit()) { + // get_or_new wiki and mark it offline so it can show up in wikis sidebar + gplx.xowa.wikis.xwikis.Xow_xwiki_itm xwiki = app.User().Wikii().Xwiki_mgr().Get_by_key(wiki_domain); + if (xwiki == null) + xwiki = app.User().Wikii().Xwiki_mgr().Add_by_atrs(wiki_domain, wiki_domain); + xwiki.Offline_(true); + ((Xoae_app)app).Gui_mgr().Html_mgr().Portal_mgr().Wikis().Itms_reset(); // dirty wiki list so that next refresh will load itm + } + rv.Rls(); // rls wiki, else open connections will cause later file copies to fail; DATE:2016-06-26 + rv.Init_needed_y_(); // mark Init_needed_y_(), else wiki may have NOOP connection which will hang around on next release + return rv; + } + public static final String Invk__import_by_url = "import_by_url"; +} diff --git a/400_xowa/src/gplx/xowa/wikis/Xoa_wiki_regy.java b/400_xowa/src/gplx/xowa/wikis/Xoa_wiki_regy.java index a27517de8..718c96147 100644 --- a/400_xowa/src/gplx/xowa/wikis/Xoa_wiki_regy.java +++ b/400_xowa/src/gplx/xowa/wikis/Xoa_wiki_regy.java @@ -13,3 +13,37 @@ 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.wikis; import gplx.*; import gplx.xowa.*; +import gplx.xowa.wikis.domains.*; +public class Xoa_wiki_regy { + private Xoae_app app; + private boolean init_needed = true; + private Hash_adp_bry hash = Hash_adp_bry.cs(); + public Xoa_wiki_regy(Xoae_app app) {this.app = app;} + public boolean Has(byte[] domain) {if (init_needed) Init(); return hash.Has(domain);} + public void Clear() { + hash.Clear(); + init_needed = true; + } + public boolean Url_is_invalid_domain(Xoa_url url) { + if (!Bry_.Eq(url.Page_bry(), Xoa_page_.Main_page_bry)) return false; // page is not "Main_Page"; assume not an invalid domain str; EX: "uk/wiki/Main_Page" + if ( Bry_.Eq(Xow_domain_tid_.Bry__home, url.Wiki_bry()) // wiki is "home" + && !Bry_.Eq(Xow_domain_tid_.Bry__home, url.Raw())) // raw is "home"; should be "home/wiki/Main_Page"; DATE:2014-02-09 + return false; // special case to handle "home" which should mean "home" in any wiki, but "home/wiki/Main_Page" in home wiki + return !this.Has(url.Wiki_bry()); + } + private void Init() { + init_needed = false; + Io_url[] wiki_dirs = Io_mgr.Instance.QueryDir_args(app.Fsys_mgr().Wiki_dir()).DirInclude_(true).Recur_(false).ExecAsUrlAry(); + int wiki_dirs_len = wiki_dirs.length; + for (int i = 0; i < wiki_dirs_len; i++) { + Io_url wiki_dir = wiki_dirs[i]; + byte[] domain_bry = Bry_.new_u8(wiki_dir.NameOnly()); + hash.Add(domain_bry, domain_bry); + } + } + public static void Make_wiki_dir(Xoa_app app, String domain_str) { // TEST: fake wiki_dir for Parse_from_url_bar; DATE:2014-02-16 + Io_url wiki_dir = app.Fsys_mgr().Wiki_dir(); + Io_mgr.Instance.CreateDir(wiki_dir.GenSubDir(domain_str)); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/Xoae_wiki_mgr.java b/400_xowa/src/gplx/xowa/wikis/Xoae_wiki_mgr.java index a27517de8..fc43f2ef3 100644 --- a/400_xowa/src/gplx/xowa/wikis/Xoae_wiki_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/Xoae_wiki_mgr.java @@ -13,3 +13,118 @@ 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.wikis; import gplx.*; import gplx.xowa.*; +import gplx.xowa.langs.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.domains.crts.*; import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.metas.*; +import gplx.xowa.addons.wikis.directorys.dbs.*; +public class Xoae_wiki_mgr implements Xoa_wiki_mgr, Gfo_invk { + private final Xoae_app app; + private final List_adp list = List_adp_.New(); private final Hash_adp_bry hash = Hash_adp_bry.ci_a7(); // ASCII:url_domain; EX:en.wikipedia.org + private Xowdir_db_mgr db_mgr; + public Xoae_wiki_mgr(Xoae_app app) { + this.app = app; + this.wiki_regy = new Xoa_wiki_regy(app); + this.wdata_mgr = new Wdata_wiki_mgr(app); + } + public Xoa_wiki_regy Wiki_regy() {return wiki_regy;} private final Xoa_wiki_regy wiki_regy; + public Xow_script_mgr Scripts() {return scripts;} private final Xow_script_mgr scripts = new Xow_script_mgr(); + public Wdata_wiki_mgr Wdata_mgr() {return wdata_mgr;} private final Wdata_wiki_mgr wdata_mgr; + public Xowe_wiki Wiki_commons() { + synchronized (this) { // LOCK:app-level; DATE:2016-07-06 + Xowe_wiki rv = (Xowe_wiki)this.Get_by_or_null(Xow_domain_itm_.Bry__commons); + if (rv != null) rv.Init_assert(); + return rv; + } + } + public void Init_by_app() { + this.db_mgr = new Xowdir_db_mgr(app.User().User_db_mgr().Conn()); + wdata_mgr.Init_by_app(); + } + public int Count() {return list.Count();} + public boolean Has(byte[] key) {return hash.Has(key);} + public Xow_wiki Get_at(int idx) {return (Xow_wiki)list.Get_at(idx);} + public Xowe_wiki Get_at_or_null(int i) {return Int_.Between(i, 0, this.Count() - 1) ? (Xowe_wiki)list.Get_at(i) : null;} + public Xow_wiki Get_by_or_null(byte[] key) {return Bry_.Len_eq_0(key) ? null : (Xowe_wiki)hash.Get_by(key);} + public Xow_wiki Get_by_or_make_init_y(byte[] key) { + synchronized (this) { // LOCK:app-level; DATE:2016-07-06 + Xowe_wiki rv = (Xowe_wiki)this.Get_by_or_null(key); if (rv == null) rv = Make_and_add(key); + rv.Init_assert(); + return rv; + } + } + public Xow_wiki Get_by_or_make_init_n(byte[] key) {return Get_by_or_make(key);} + public Xowe_wiki Get_by_or_make(byte[] key) { + Xowe_wiki rv = (Xowe_wiki)this.Get_by_or_null(key); if (rv == null) rv = Make_and_add(key); + return rv; + } + public void Add(Xow_wiki wiki) { + if (hash.Get_by_bry(wiki.Domain_bry()) != null) return; // if already there, don't add again; basically, Add_if_dupe_use_1st + hash.Add(wiki.Domain_bry(), wiki); + list.Add(wiki); + } + public Xowe_wiki Make_and_add(byte[] domain_bry) { + // get wiki_root_url from either user_wiki or /xowa/wiki/ + Xowdir_wiki_itm user_wiki_itm = db_mgr == null + ? null // TEST: + : db_mgr.Tbl__wiki().Select_by_key_or_null(String_.new_u8(domain_bry)); + + Xowe_wiki rv = null; + if (user_wiki_itm == null) { + rv = (Xowe_wiki)Make(domain_bry, app.Fsys_mgr().Wiki_dir().GenSubDir(String_.new_a7(domain_bry))); + Add(rv); + } + else { + rv = gplx.xowa.addons.wikis.directorys.specials.items.bldrs.Xow_wiki_factory.Load_personal(app, domain_bry, user_wiki_itm.Url().OwnerDir()); + } + return rv; + } + public Xow_wiki Make(byte[] domain_bry, Io_url wiki_root_dir) { + // init domain + Xow_domain_itm domain_itm = Xow_domain_itm_.parse(domain_bry); + + // get lang from domain; if not wmf, default to en + byte[] lang_key = domain_itm.Lang_actl_key(); + if (lang_key == Xol_lang_stub_.Key__unknown) lang_key = Xol_lang_itm_.Key_en; // unknown langs default to english; note that this makes nonwmf english by default + Xol_lang_itm lang = app.Lang_mgr().Get_by_or_new(lang_key); + if (domain_itm.Domain_type_id() == Xow_domain_tid_.Tid__other) { + lang = new Xol_lang_itm(app.Lang_mgr(), Xol_lang_itm_.Key_en).Kwd_mgr__strx_(true); // create a new english lang, but enable strx functions; DATE:2015-08-23 + Xol_lang_itm_.Lang_init(lang); + } + + // load ns from site_meta + Xow_ns_mgr ns_mgr = app.Dbmeta_mgr().Ns__get_or_load(domain_bry); + if (ns_mgr.Ids_len() == 0) ns_mgr = Xow_ns_mgr_.default_(lang.Case_mgr()); // non-wmf wikis will use default ns_mgr + + return new Xowe_wiki(app, lang, ns_mgr, domain_itm, wiki_root_dir); + } + public Xow_wiki Import_by_url(Io_url url) {return Xoa_wiki_mgr_.Import_by_url(app, this, url);} + public void Del(byte[] key) {hash.Del(key);} + public void Clear() {hash.Clear(); list.Clear();} + public void Free_mem(boolean clear_ctx) { + int list_len = list.Count(); + for (int i = 0; i < list_len; i++) { + Xowe_wiki wiki = (Xowe_wiki)list.Get_at(i); +// wiki.Defn_cache().ReduceCache(); + if (clear_ctx) wiki.Parser_mgr().Ctx().Clear_all(); // NOTE: clear_ctx will reset toc and refs + wiki.Cache_mgr().Page_cache().Free_mem(true); + wiki.Cache_mgr().Tmpl_result_cache().Clear(); + } + } + public void Rls() { + int len = list.Count(); + for (int i = 0; i < len; i++) { + Xowe_wiki wiki = (Xowe_wiki)list.Get_at(i); + wiki.Rls(); + } + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_get)) return Get_by_or_make(m.ReadBry("v")); + else if (ctx.Match(k, Invk_scripts)) return scripts; + else if (ctx.Match(k, Invk_wdata)) return wdata_mgr; + else if (ctx.Match(k, Invk_len)) return this.Count(); + else if (ctx.Match(k, Invk_get_at)) return this.Get_at_or_null(m.ReadInt("v")); + else if (ctx.Match(k, Xoa_wiki_mgr_.Invk__import_by_url)) return this.Import_by_url(m.ReadIoUrl("v")); + else return Gfo_invk_.Rv_unhandled; + } private static final String Invk_get = "get", Invk_scripts = "scripts", Invk_wdata = "wdata"; + private static final String Invk_len = "len", Invk_get_at = "get_at"; +} diff --git a/400_xowa/src/gplx/xowa/wikis/Xow_page_tid.java b/400_xowa/src/gplx/xowa/wikis/Xow_page_tid.java index a27517de8..1e6ebb0d7 100644 --- a/400_xowa/src/gplx/xowa/wikis/Xow_page_tid.java +++ b/400_xowa/src/gplx/xowa/wikis/Xow_page_tid.java @@ -13,3 +13,30 @@ 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.wikis; import gplx.*; import gplx.xowa.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.xtns.wbases.*; +public class Xow_page_tid { + public static byte Identify(int wiki_tid, int ns_id, byte[] ttl) { + switch (ns_id) { + case Xow_ns_.Tid__mediawiki: + byte rv = Identify_by_ttl(ttl); + return rv == Tid_wikitext ? Tid_msg : rv; // if mediawiki ns, but not css / js, return msg, not wikitext; DATE:2016-09-12 + case Xow_ns_.Tid__user: + return Identify_by_ttl(ttl); + case Xow_ns_.Tid__module: + return (Bry_.Has_at_end(ttl, Ext_doc)) + ? Tid_wikitext : Tid_lua; + default: + return Wdata_wiki_mgr.Wiki_page_is_json(wiki_tid, ns_id) + ? Tid_json : Tid_wikitext; + } + } + public static byte Identify_by_ttl(byte[] ttl) { + if (Bry_.Has_at_end(ttl, Ext_css)) return Tid_css; + else if (Bry_.Has_at_end(ttl, Ext_js)) return Tid_js; + else return Tid_wikitext; + } + private static final byte[] Ext_js = Bry_.new_a7(".js"), Ext_css = Bry_.new_a7(".css"), Ext_doc= Bry_.new_a7("/doc"); + public static final byte Tid_wikitext = 1, Tid_json = 2, Tid_js = 3, Tid_css = 4, Tid_lua = 5, Tid_msg = 6; +} diff --git a/400_xowa/src/gplx/xowa/wikis/Xowv_repo_mgr.java b/400_xowa/src/gplx/xowa/wikis/Xowv_repo_mgr.java index a27517de8..f20ce4431 100644 --- a/400_xowa/src/gplx/xowa/wikis/Xowv_repo_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/Xowv_repo_mgr.java @@ -13,3 +13,58 @@ 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.wikis; import gplx.*; import gplx.xowa.*; +import gplx.xowa.files.repos.*; +public class Xowv_repo_mgr implements Xow_repo_mgr { + private final List_adp repos = List_adp_.New(); + public Xof_repo_pair[] Repos_ary() {return (Xof_repo_pair[])repos.To_ary(Xof_repo_pair.class);} + public Xof_repo_pair Repos_get_by_wiki(byte[] wiki) { + int len = repos.Count(); + for (int i = 0; i < len; i++) { + Xof_repo_pair pair = (Xof_repo_pair)repos.Get_at(i); + if (Bry_.Eq(wiki, pair.Wiki_domain())) + return pair; + } + return null; + } + public Xof_repo_pair Repos_get_at(int i) {return (Xof_repo_pair)repos.Get_at(i);} + private Xof_repo_pair Repos_get_by_id(int id) { + int len = repos.Count(); + for (int i = 0; i < len; i++) { + Xof_repo_pair pair = (Xof_repo_pair)repos.Get_at(i); + if (pair.Id() == id) return pair; + } + return null; + } + public Xof_repo_itm Get_trg_by_id_or_null(int id, byte[] lnki_ttl, byte[] page_url) { + Xof_repo_pair pair = Repos_get_by_id(id); + if (pair == null) { + Xoa_app_.Usr_dlg().Warn_many("", "", "repo_mgr.invalid_repo: repo=~{0} lnki_ttl=~{1} page_url=~{2}", id, lnki_ttl, page_url); + return null; + } + else + return pair.Trg(); + } + public Xof_repo_itm Get_src_by_id_or_null(int id, byte[] lnki_ttl, byte[] page_url) { + Xof_repo_pair pair = Repos_get_by_id(id); + if (pair == null) { + Xoa_app_.Usr_dlg().Warn_many("", "", "repo_mgr.invalid_repo: repo=~{0} lnki_ttl=~{1} page_url=~{2}", id, lnki_ttl, page_url); + return null; + } + else + return pair.Src(); + } + public Xof_repo_pair Add_repo(Xoa_app app, Io_url wiki_root, byte[] src_repo_key, byte[] trg_repo_key) { + Xof_repo_itm src_repo = Add(app, wiki_root, src_repo_key), trg_repo = Add(app, wiki_root, trg_repo_key); + byte[] src_wiki_key = src_repo.Wiki_domain();//, trg_wiki_key = trg_repo.Wiki_key(); +// if (!Bry_.Eq(src_wiki_key, trg_wiki_key) && !Bry_.Eq(src_wiki_key, Xow_domain_tid_.Bry__home)) throw Err_mgr.Instance.fmt_(GRP_KEY, "add_repo", "wiki keys do not match: ~{0} ~{1}", String_.new_u8(src_wiki_key), String_.new_u8(trg_wiki_key)); + Xof_repo_pair pair = new Xof_repo_pair((byte)repos.Count(), src_wiki_key, src_repo, trg_repo); + repos.Add(pair); + return pair; + } + private Xof_repo_itm Add(Xoa_app app, Io_url wiki_root, byte[] wiki_domain) { + Xof_repo_itm itm = new Xof_repo_itm(wiki_domain, app.Fsys_mgr(), null, wiki_domain); + itm.Root_str_(wiki_root.Raw()); + return itm; + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/Xowv_wiki.java b/400_xowa/src/gplx/xowa/wikis/Xowv_wiki.java index a27517de8..62cf22dc9 100644 --- a/400_xowa/src/gplx/xowa/wikis/Xowv_wiki.java +++ b/400_xowa/src/gplx/xowa/wikis/Xowv_wiki.java @@ -13,3 +13,131 @@ 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.wikis; import gplx.*; import gplx.xowa.*; +import gplx.core.primitives.*; import gplx.core.net.*; import gplx.core.brys.*; import gplx.core.ios.*; +import gplx.dbs.*; +import gplx.xowa.apps.*; +import gplx.xowa.guis.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; import gplx.xowa.langs.cases.*; +import gplx.xowa.files.*; import gplx.xowa.files.origs.*; import gplx.xowa.files.fsdb.*; import gplx.xowa.files.bins.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.metas.*; import gplx.xowa.wikis.data.site_stats.*; import gplx.xowa.wikis.data.*; import gplx.xowa.files.repos.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.wikis.xwikis.*; import gplx.xowa.wikis.ttls.*; import gplx.xowa.addons.*; +import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.htmls.utls.*; import gplx.xowa.htmls.core.*; import gplx.xowa.htmls.core.hzips.*; import gplx.xowa.htmls.bridges.dbuis.tbls.*; import gplx.xowa.htmls.hrefs.*; +import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.fsys.*; +import gplx.xowa.parsers.*; +import gplx.xowa.apps.urls.*; +import gplx.fsdb.*; import gplx.fsdb.meta.*; +import gplx.xowa.specials.mgrs.*; +import gplx.xowa.addons.wikis.htmls.css.bldrs.*; import gplx.xowa.addons.wikis.htmls.css.mgrs.*; import gplx.xowa.addons.wikis.ctgs.htmls.pageboxs.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; +public class Xowv_wiki implements Xow_wiki, Xow_ttl_parser, Gfo_invk { + private final Xof_fsdb_mgr__sql fsdb_mgr; private Fsdb_db_mgr db_core_mgr; + private boolean init_needed = true; + public Xowv_wiki(Xoav_app app, byte[] domain_bry, Io_url wiki_root_dir) { + this.app = app; + + // domain vars + this.domain_bry = domain_bry; + this.domain_str = String_.new_u8(domain_bry); + this.domain_itm = Xow_domain_itm_.parse(domain_bry); + this.domain_tid = domain_itm.Domain_type_id(); + this.domain_abrv = Xow_abrv_wm_.To_abrv(domain_itm); + + this.fsys_mgr = new Xow_fsys_mgr(wiki_root_dir, app.Fsys_mgr().File_dir().GenSubDir(domain_str)); + + this.ns_mgr = Xow_ns_mgr_.default_(app.Utl_case_mgr()); + this.lang = app.Lang_mgr().Get_by_or_en(domain_itm.Lang_actl_key()); // NOTE: must not be null, or causes null ref exception on redlinks in drd; DATE:2016-06-28 + this.msg_mgr = new Xow_msg_mgr(this, lang); + this.html__hdump_mgr = new Xow_hdump_mgr(this); + this.special_mgr = new Xosp_special_mgr(this); + this.fsdb_mgr = new Xof_fsdb_mgr__sql(); + this.url__parser = new Xow_url_parser(this); + this.xwiki_mgr = new Xow_xwiki_mgr(this); + this.stats = new Xowd_site_stats_mgr(this); + this.lnki_bldr = new Xoh_lnki_bldr(app, href_wtr); + this.ctg_catpage_mgr = new Xoctg_catpage_mgr(this); + } + public Xoa_app App() {return app;} + public boolean Type_is_edit() {return Bool_.N;} + public byte[] Domain_bry() {return domain_bry;} private final byte[] domain_bry; + public String Domain_str() {return domain_str;} private final String domain_str; + public Xow_domain_itm Domain_itm() {return domain_itm;} private final Xow_domain_itm domain_itm; + public int Domain_tid() {return domain_tid;} private final int domain_tid; + public byte[] Domain_abrv() {return domain_abrv;} private final byte[] domain_abrv; + public Xow_ns_mgr Ns_mgr() {return ns_mgr;} private final Xow_ns_mgr ns_mgr; + public Xow_fsys_mgr Fsys_mgr() {return fsys_mgr;} private Xow_fsys_mgr fsys_mgr; + public Xow_db_mgr Data__core_mgr() {return data_mgr__core_mgr;} private Xow_db_mgr data_mgr__core_mgr; + public Xow_repo_mgr File__repo_mgr() {return file_mgr__repo_mgr;} private Xowv_repo_mgr file_mgr__repo_mgr = new Xowv_repo_mgr(); + public Xof_fsdb_mode File__fsdb_mode() {return file_mgr__fsdb_mode;} private final Xof_fsdb_mode file_mgr__fsdb_mode = Xof_fsdb_mode.New__v2__gui(); + public Fsdb_db_mgr File__fsdb_core() {return db_core_mgr;} + public Xof_orig_mgr File__orig_mgr() {return orig_mgr;} private final Xof_orig_mgr orig_mgr = new Xof_orig_mgr(); + public Xof_bin_mgr File__bin_mgr() {return fsdb_mgr.Bin_mgr();} + public Fsm_mnt_mgr File__mnt_mgr() {return fsdb_mgr.Mnt_mgr();} + public Xoh_lnki_bldr Html__lnki_bldr() {return lnki_bldr;} private final Xoh_lnki_bldr lnki_bldr; + public Xoh_href_wtr Html__href_wtr() {return href_wtr;} private final Xoh_href_wtr href_wtr = new Xoh_href_wtr(); + public boolean Html__hdump_enabled() {return Bool_.Y;} + public Xow_hdump_mgr Html__hdump_mgr() {return html__hdump_mgr;} private final Xow_hdump_mgr html__hdump_mgr; + public boolean Html__css_installing() {return html__css_installing;} public void Html__css_installing_(boolean v) {html__css_installing = v;} private boolean html__css_installing; + public Xoh_page_wtr_mgr Html__wtr_mgr() {return html__wtr_mgr;} private final Xoh_page_wtr_mgr html__wtr_mgr = new Xoh_page_wtr_mgr(Bool_.Y); + public Xoctg_pagebox_wtr Ctg__pagebox_wtr() {return ctg_pagebox_wtr;} private final Xoctg_pagebox_wtr ctg_pagebox_wtr = new Xoctg_pagebox_wtr(); + public Xoctg_catpage_mgr Ctg__catpage_mgr() {return ctg_catpage_mgr;} private final Xoctg_catpage_mgr ctg_catpage_mgr; + public Xow_msg_mgr Msg_mgr() {return msg_mgr;} private final Xow_msg_mgr msg_mgr; + public byte[] Wtxt__expand_tmpl(byte[] src) {return src;} + public Xow_mw_parser_mgr Mw_parser_mgr() {return mw_parser_mgr;} private final Xow_mw_parser_mgr mw_parser_mgr = new Xow_mw_parser_mgr(); + public Xow_wiki_props Props() {return props;} private final Xow_wiki_props props = new Xow_wiki_props(); + public Xol_lang_itm Lang() {return lang;} private final Xol_lang_itm lang; + public Xol_case_mgr Case_mgr() {if (case_mgr == null) case_mgr = Xol_case_mgr_.U8(); return case_mgr;} private Xol_case_mgr case_mgr; + public Xowd_site_stats_mgr Stats() {return stats;} private final Xowd_site_stats_mgr stats; + public Bry_bfr_mkr Utl__bfr_mkr() {return utl__bry_bfr_mkr;} private final Bry_bfr_mkr utl__bry_bfr_mkr = new Bry_bfr_mkr(); + public Io_stream_zip_mgr Utl__zip_mgr() {return utl__zip_mgr;} private final Io_stream_zip_mgr utl__zip_mgr = new Io_stream_zip_mgr(); + public Xow_url_parser Utl__url_parser() {return url__parser;} private final Xow_url_parser url__parser; + public Xoax_addon_mgr Addon_mgr() {return addon_mgr;} private final Xoax_addon_mgr addon_mgr = new Xoax_addon_mgr(); + public Xosp_special_mgr Special_mgr() {return special_mgr;} private Xosp_special_mgr special_mgr; + public Xow_xwiki_mgr Xwiki_mgr() {return xwiki_mgr;} private final Xow_xwiki_mgr xwiki_mgr; + public Xoav_app Appv() {return app;} private final Xoav_app app; + public boolean Embeddable_enabled() {return embeddable_enabled;} public void Embeddable_enabled_(boolean v) {this.embeddable_enabled = v;} private boolean embeddable_enabled; + public void Init_by_wiki() { + if (!init_needed) return; + init_needed = false; + if (String_.Eq(domain_str, "xowa")) return; // HACK: ignore "xowa" for now; WHEN:converting xowa to sqlitedb + data_mgr__core_mgr = new Xow_db_mgr(fsys_mgr.Root_dir(), this.domain_str); + Xow_db_mgr.Init_by_load(this, gplx.xowa.wikis.data.Xow_db_file__core_.Find_core_fil_or_null(this)); + app.Html__css_installer().Install(this, Xowd_css_core_mgr.Key_mobile); // must init after data_mgr + this.db_core_mgr = Fsdb_db_mgr_.new_detect(this, fsys_mgr.Root_dir(), fsys_mgr.File_dir()); + if (db_core_mgr == null) // no fsdb; occurs during merge; also, will be null for xowa_db; DATE:2016-05-31 + db_core_mgr = Fsdb_db_mgr__v2_bldr.Get_or_make(this, true); + else // fsdb exists; load it + fsdb_mgr.Mnt_mgr().Ctor_by_load(db_core_mgr); + file_mgr__repo_mgr.Add_repo(app, fsys_mgr.File_dir(), Xow_domain_itm_.Bry__commons, Xow_domain_itm_.Bry__commons); + file_mgr__repo_mgr.Add_repo(app, fsys_mgr.File_dir(), domain_bry, domain_bry); + Xof_orig_tbl[] orig_tbls = db_core_mgr == null ? new Xof_orig_tbl[0] : db_core_mgr.File__orig_tbl_ary(); + orig_mgr.Init_by_wiki(this, file_mgr__fsdb_mode, orig_tbls, Xof_url_bldr.new_v2()); + fsdb_mgr.Init_by_wiki(this); + data_mgr__core_mgr.Db__core().Tbl__ns().Select_all(ns_mgr); + data_mgr__core_mgr.Db__core().Tbl__site_stats().Select(stats); + html__hdump_mgr.Init_by_db(this); + app.Addon_mgr().Load_by_wiki(this); + ctg_pagebox_wtr.Init_by_wiki(this); + ctg_catpage_mgr.Init_by_wiki(this); + } + public void Init_by_wiki__force() {init_needed = true; Init_by_wiki();} + public void Init_by_make(Xowd_core_db_props props, gplx.xowa.bldrs.infos.Xob_info_session info_session) { + data_mgr__core_mgr = new Xow_db_mgr(fsys_mgr.Root_dir(), this.domain_str); + data_mgr__core_mgr.Init_by_make(props, info_session); + html__hdump_mgr.Init_by_db(this); + } + public void Rls() { + data_mgr__core_mgr.Rls(); + fsdb_mgr.Rls(); + } + public void Pages_get(Xoh_page rv, Gfo_url url, Xoa_ttl ttl) { + if (init_needed) Init_by_wiki(); + if (ttl.Ns().Id_is_special()) + special_mgr.Get_by_ttl(rv, url, ttl); + else + html__hdump_mgr.Load_mgr().Load_by_xowh(rv, ttl, Bool_.Y); + } + public Xoa_ttl Ttl_parse(byte[] ttl) {return Ttl_parse(ttl, 0, ttl.length);} + public Xoa_ttl Ttl_parse(byte[] src, int src_bgn, int src_end) {return Xoa_ttl.Parse(app.Utl_amp_mgr(), app.Utl_case_mgr(), xwiki_mgr, ns_mgr, src, src_bgn, src_end);} + public Xoa_ttl Ttl_parse(int ns_id, byte[] ttl) {return Xoa_ttl.Parse(this, ns_id, ttl);} + public void Init_needed_y_() {this.init_needed = true;} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {throw Err_.new_unimplemented_w_msg("implemented for Xoa_cfg_mgr");} +} diff --git a/400_xowa/src/gplx/xowa/wikis/caches/Xow_cache_mgr.java b/400_xowa/src/gplx/xowa/wikis/caches/Xow_cache_mgr.java index a27517de8..ae7a8b9c8 100644 --- a/400_xowa/src/gplx/xowa/wikis/caches/Xow_cache_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/caches/Xow_cache_mgr.java @@ -13,3 +13,72 @@ 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.wikis.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.caches.*; +import gplx.xowa.wikis.xwikis.sitelinks.*; +public class Xow_cache_mgr { + private final Xowe_wiki wiki; + public Xow_cache_mgr(Xowe_wiki wiki) { + this.wiki = wiki; + this.page_cache = new Xow_page_cache(wiki); + this.defn_cache = new Xow_defn_cache(wiki.Lang()); + this.ifexist_cache = new Xow_ifexist_cache(wiki, page_cache); + } + public Hash_adp Tmpl_result_cache() {return tmpl_result_cache;} private final Hash_adp tmpl_result_cache = Hash_adp_bry.cs(); + public Xow_defn_cache Defn_cache() {return defn_cache;} private final Xow_defn_cache defn_cache; + public Hash_adp_bry Lst_cache() {return lst_cache;} private final Hash_adp_bry lst_cache = Hash_adp_bry.cs(); + public Hash_adp Misc_cache() {return misc_cache;} private final Hash_adp misc_cache = Hash_adp_.New(); + public Xow_page_cache Page_cache() {return page_cache;} private Xow_page_cache page_cache; + public Gfo_cache_mgr Commons_cache() {return commons_cache;} private Gfo_cache_mgr commons_cache = new Gfo_cache_mgr().Max_size_(64 * Io_mgr.Len_mb).Reduce_by_(32 * Io_mgr.Len_mb); + public Xow_ifexist_cache Ifexist_cache() {return ifexist_cache;} private Xow_ifexist_cache ifexist_cache; + + public Xow_cache_mgr Page_cache_(Xow_page_cache v) {this.page_cache = v; return this;} + public Xow_cache_mgr Commons_cache_(Gfo_cache_mgr v) {this.commons_cache = v; return this;} + public Xow_cache_mgr Ifexist_cache_(Xow_ifexist_cache v) {this.ifexist_cache = v; return this;} + public void Load_wkr_(Xow_page_cache_wkr v) { + page_cache.Load_wkr_(v); + ifexist_cache.Load_wkr_(v); + } + public Keyval[] Scrib_lang_names() { + if (scrib_lang_names == null) { + List_adp list = List_adp_.New(); + Xoa_sitelink_itm_mgr itm_mgr = wiki.App().Xwiki_mgr__sitelink_mgr().Itm_mgr(); + int len = itm_mgr.Len(); + for (int i = 0; i < len; ++i) { + Xoa_sitelink_itm itm = itm_mgr.Get_at(i); + Keyval kv = Keyval_.new_(String_.new_u8(itm.Key()), String_.new_u8(itm.Name())); + list.Add(kv); + } + scrib_lang_names = (Keyval[])list.To_ary(Keyval.class); + } + return scrib_lang_names; + } + public void Free_mem__page() {this.Free_mem(Free_mem__page_tid);} + public void Free_mem__wbase() {this.Free_mem(Free_mem__wbase_tid);} + public void Free_mem__all() {this.Free_mem(Free_mem__all_tid);} + private void Free_mem(int level) { + switch (level) { + case Free_mem__page_tid: + page_cache.Free_mem(false); + break; + case Free_mem__wbase_tid: + page_cache.Free_mem(false); + wiki.Appe().Wiki_mgr().Wdata_mgr().Clear(); + break; + case Free_mem__all_tid: + page_cache.Free_mem(true); + wiki.Appe().Wiki_mgr().Wdata_mgr().Clear(); + commons_cache.Clear(); + ifexist_cache.Clear(); + break; + } + wiki.Ctg__catpage_mgr().Free_mem_all(); + tmpl_result_cache.Clear(); + defn_cache.Free_mem_all(); + lst_cache.Clear(); + misc_cache.Clear(); + // scrib_lang_names = null; + } + private static final int Free_mem__page_tid = 0, Free_mem__wbase_tid = 1, Free_mem__all_tid = 2; + private static Keyval[] scrib_lang_names; +} diff --git a/400_xowa/src/gplx/xowa/wikis/caches/Xow_defn_cache.java b/400_xowa/src/gplx/xowa/wikis/caches/Xow_defn_cache.java index a27517de8..8d948f2d8 100644 --- a/400_xowa/src/gplx/xowa/wikis/caches/Xow_defn_cache.java +++ b/400_xowa/src/gplx/xowa/wikis/caches/Xow_defn_cache.java @@ -13,3 +13,24 @@ 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.wikis.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.intls.*; import gplx.core.caches.*; +import gplx.xowa.langs.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.parsers.tmpls.*; +public class Xow_defn_cache { // stores compiled Xot_defn + private final Xol_lang_itm lang; // needed to lowercase names; + private final Bry_bfr upper_1st_bfr = Bry_bfr_.Reset(255); + private final Gfo_cache_mgr cache = new Gfo_cache_mgr().Max_size_(64 * 1024 * 1024).Reduce_by_(32 * 1024 * 1024); + public Xow_defn_cache(Xol_lang_itm lang) {this.lang = lang;} + public Xot_defn Get_by_key(byte[] name) {return (Xot_defn)cache.Get_by_key(name);} + public void Free_mem_all() {cache.Clear();} + public void Add(Xot_defn defn, byte case_match) { + byte[] name = defn.Name(); + cache.Add_replace(name, defn, defn.Cache_size()); + if (case_match == Xow_ns_case_.Tid__1st) { + name = lang.Case_mgr().Case_build_1st_upper(upper_1st_bfr, name, 0, name.length); + cache.Add_replace(name, defn, 0); + } + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/caches/Xow_ifexist_cache.java b/400_xowa/src/gplx/xowa/wikis/caches/Xow_ifexist_cache.java index a27517de8..fbbbf3855 100644 --- a/400_xowa/src/gplx/xowa/wikis/caches/Xow_ifexist_cache.java +++ b/400_xowa/src/gplx/xowa/wikis/caches/Xow_ifexist_cache.java @@ -13,3 +13,79 @@ 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.wikis.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.caches.*; +import gplx.xowa.wikis.data.tbls.*; +public class Xow_ifexist_cache { + private final Xowe_wiki wiki; + private final Xow_page_cache page_cache; + private final Gfo_cache_mgr cache_mgr = new Gfo_cache_mgr().Max_size_(64 * Io_mgr.Len_mb).Reduce_by_(32 * Io_mgr.Len_mb); + private final Hash_adp ns_loaded_hash = Hash_adp_.New(); + private final Xow_ifexist_itm itm__exists = new Xow_ifexist_itm(Bool_.Y), itm__missing = new Xow_ifexist_itm(Bool_.N); + public Xow_ifexist_cache(Xowe_wiki wiki, Xow_page_cache page_cache) { + this.wiki = wiki; + this.page_cache = page_cache; + } + public Xow_ifexist_cache Cache_sizes_(int max, int reduce) { + cache_mgr.Max_size_(max).Reduce_by_(reduce); + return this; + } + public void Load_wkr_(Xow_page_cache_wkr v) {this.load_wkr = v;} private Xow_page_cache_wkr load_wkr; + public void Clear() { + cache_mgr.Clear(); + ns_loaded_hash.Clear(); + } + public void Add(Xoa_ttl ttl) { + byte[] key = ttl.Full_db(); + cache_mgr.Add(key, itm__exists, key.length); + } + public void Add_ns_loaded(int... ns_ids) { + for (int ns_id : ns_ids) + ns_loaded_hash.Add(ns_id, ns_id); + } + public byte Get_by_mem(Xoa_ttl ttl) { + byte[] ttl_full_db = ttl.Full_db(); + + // check cache_mgr + Xow_ifexist_itm found = (Xow_ifexist_itm)cache_mgr.Get_by_key(ttl_full_db); + if (found != null) return found.Exists() ? Bool_.Y_byte : Bool_.N_byte; + + // check ns_loaded cache (xomp only); if exists, return false, since all pages in ns are loaded, and still not found + if (ns_loaded_hash.Has(ttl.Ns().Id())) return Bool_.N_byte; + + // check page_cache since full page + text could be loaded there + Xow_page_cache_itm itm = (Xow_page_cache_itm)page_cache.Get_or_null(ttl_full_db); + if (itm == Xow_page_cache_itm.Missing) + return Bool_.N_byte; + else if (itm != null) + return Bool_.Y_byte; + + return Bool_.__byte; + } + public boolean Load(Xoa_ttl ttl) { + byte[] key = ttl.Full_db(); + Xow_ifexist_itm itm = null; + // gplx.core.consoles.Console_adp__sys.Instance.Write_str("ifexist_cache:" + String_.new_u8(key)); + + if (load_wkr != null) { + // load_wkr; should call ifexist method, but for now, load entire page + byte[] page_text = load_wkr.Get_page_or_null(key); + itm = page_text == null ? itm__missing : itm__exists; + } + else { + // page_tbl + Xowd_page_itm page_itm = new Xowd_page_itm(); + wiki.Db_mgr().Load_mgr().Load_by_ttl(page_itm, ttl.Ns(), ttl.Page_db()); + itm = page_itm.Exists() ? itm__exists : itm__missing; + } + + // add + cache_mgr.Add(key, itm, key.length); + return itm == itm__exists; + } +} +class Xow_ifexist_itm implements Rls_able { + public Xow_ifexist_itm(boolean exists) {this.exists = exists;} + public boolean Exists() {return exists;} private final boolean exists; + public void Rls() {} +} diff --git a/400_xowa/src/gplx/xowa/wikis/caches/Xow_page_cache.java b/400_xowa/src/gplx/xowa/wikis/caches/Xow_page_cache.java index a27517de8..0f95b8569 100644 --- a/400_xowa/src/gplx/xowa/wikis/caches/Xow_page_cache.java +++ b/400_xowa/src/gplx/xowa/wikis/caches/Xow_page_cache.java @@ -13,3 +13,112 @@ 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.wikis.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public class Xow_page_cache { + private final Object thread_lock = new Object(); + private final Xowe_wiki wiki; + private final Ordered_hash cache = Ordered_hash_.New_bry(); // NOTE: wiki titles are not case-sensitive when ns is "1st-letter" (EX: w:earth an w:Earth); in these cases, two entries will be stored + private final List_adp deleted = List_adp_.New(); + public Xow_page_cache(Xowe_wiki wiki) {this.wiki = wiki;} + public void Load_wkr_(Xow_page_cache_wkr v) {this.load_wkr = v;} private Xow_page_cache_wkr load_wkr; + public Xow_page_cache_itm Get_or_null(byte[] ttl_full_db) {return (Xow_page_cache_itm)cache.Get_by(ttl_full_db);} + public byte[] Get_or_load_as_src(Xoa_ttl ttl) { + Xow_page_cache_itm rv = Get_or_load_as_itm(ttl); + return rv == null ? null : rv.Wtxt__direct(); + } + public void Add(byte[] ttl_full_db, Xow_page_cache_itm itm) { + cache.Add(ttl_full_db, itm); + } + private void Add_safe(byte[] ttl_full_db, Xow_page_cache_itm itm) { + synchronized (thread_lock) { // LOCK:high-usage;DATE:2016-07-14 + if (!cache.Has(ttl_full_db)) { // check again that itm is not in cache; note that this is necessary as cache.Get is not in "synchronized" block (for performance reasons); DATE:2016-12-12 + cache.Add(ttl_full_db, itm); + } + } + } + public void Del(byte[] ttl_full_db) { + cache.Del(ttl_full_db); + } + public Xow_page_cache_itm Get_or_load_as_itm(Xoa_ttl ttl) { + byte[] ttl_full_db = ttl.Full_db(); + Xow_page_cache_itm rv = (Xow_page_cache_itm)cache.Get_by(ttl_full_db); + if (rv == Xow_page_cache_itm.Missing) { + return null; + } + else if (rv == null) { + return Load_page(ttl, ttl_full_db); + } + return rv; + } + private Xow_page_cache_itm Load_page(Xoa_ttl ttl, byte[] ttl_full_db) { + Xow_page_cache_itm rv = null; + Xoa_ttl page_ttl = ttl; + boolean page_exists = false; + byte[] page_text = null; + byte[] page_redirect_from = null; + // gplx.core.consoles.Console_adp__sys.Instance.Write_str("page_cache:" + String_.new_u8(ttl_full_db)); + if (load_wkr != null) { + page_text = load_wkr.Get_page_or_null(ttl_full_db); + page_exists = page_text != null; + } + if (page_text == null) { + Xoae_page page = wiki.Data_mgr().Load_page_by_ttl(ttl); // NOTE: do not call Db_mgr.Load_page; need to handle redirects + page_ttl = page.Ttl(); + page_text = page.Db().Text().Text_bry(); + page_exists = page.Db().Page().Exists(); + page_redirect_from = page.Redirect_trail().Itms__get_wtxt_at_0th_or_null(); + } + if (page_exists) { + rv = new Xow_page_cache_itm(false, page_ttl, page_text, page_redirect_from); + Add_safe(ttl_full_db, rv); + } + else { + Add_safe(ttl_full_db, Xow_page_cache_itm.Missing); + rv = null; + } + return rv; + } + public Xow_page_cache_itm Get_or_load_as_itm_2(Xoa_ttl ttl) { // NOTE: same as Get_or_load_as_itm, but handles redirects to missing pages; DATE:2016-05-02 + byte[] ttl_full_db = ttl.Full_db(); + Xow_page_cache_itm rv = (Xow_page_cache_itm)cache.Get_by(ttl_full_db); + if (rv == Xow_page_cache_itm.Missing) return null; + else if (rv == null) { + Xoae_page page = wiki.Data_mgr().Load_page_by_ttl(ttl); // NOTE: do not call Db_mgr.Load_page; need to handle redirects + if ( page.Db().Page().Exists() // page exists + || page.Redirect_trail().Itms__len() > 0 ) { // page redirects to missing page; note that page.Missing == true and page.Redirected_src() != null; PAGE: en.w:Shah_Rukh_Khan; DATE:2016-05-02 + rv = new Xow_page_cache_itm(false, page.Ttl(), page.Db().Text().Text_bry(), page.Redirect_trail().Itms__get_wtxt_at_0th_or_null()); + Add_safe(ttl_full_db, rv); + } + else { + Add_safe(ttl_full_db, Xow_page_cache_itm.Missing); + rv = null; + } + } + return rv; + } + public void Free_mem(boolean clear_permanent_itms) { + synchronized (thread_lock) { // LOCK:app-level; DATE:2016-07-06 + if (clear_permanent_itms) { + cache.Clear(); + } + else { + // gather non-permanent items + int len = cache.Count(); + for (int i = 0; i < len; i++) { + Xow_page_cache_itm itm = (Xow_page_cache_itm)cache.Get_at(i); + if (!itm.Cache_permanently()) + deleted.Add(itm); + } + + // remove non-permanent items + len = deleted.Len(); + for (int i = 0; i < len; i++) { + Xow_page_cache_itm itm = (Xow_page_cache_itm)deleted.Get_at(i); + if (itm.Ttl() != null) // missing is null + cache.Del(itm.Ttl().Full_db()); + } + deleted.Clear(); + } + } + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/caches/Xow_page_cache_itm.java b/400_xowa/src/gplx/xowa/wikis/caches/Xow_page_cache_itm.java index a27517de8..b893b2a15 100644 --- a/400_xowa/src/gplx/xowa/wikis/caches/Xow_page_cache_itm.java +++ b/400_xowa/src/gplx/xowa/wikis/caches/Xow_page_cache_itm.java @@ -13,3 +13,32 @@ 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.wikis.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.xowa.wikis.data.tbls.*; +public class Xow_page_cache_itm implements Xowd_text_bry_owner { + public Xow_page_cache_itm(boolean cache_permanently, Xoa_ttl ttl, byte[] wtxt__direct, byte[] wtxt__redirect) { + this.cache_permanently = cache_permanently; + this.ttl = ttl; this.wtxt__direct = wtxt__direct; this.wtxt__redirect = wtxt__redirect; + } + public Xoa_ttl Ttl() {return ttl;} private Xoa_ttl ttl; + public byte[] Wtxt__direct() {return wtxt__direct;} private byte[] wtxt__direct; + public byte[] Wtxt__redirect() {return wtxt__redirect;} private byte[] wtxt__redirect; + public byte[] Wtxt__redirect_or_direct() { + return wtxt__redirect == null ? wtxt__direct : wtxt__redirect; + } + public boolean Cache_permanently() {return cache_permanently;} private final boolean cache_permanently; + + // used by xomp + public int Page_id() {return page_id;} private int page_id; + public int Redirect_id() {return redirect_id;} private int redirect_id; + public void Set_text_bry_by_db(byte[] v) {this.wtxt__direct = v;} + public void Set_page_ids(int page_id, int redirect_id) {this.page_id = page_id; this.redirect_id = redirect_id;} + public void Set_redirect(Xoa_ttl ttl, byte[] trg_wtxt) { + this.ttl = ttl; + this.wtxt__redirect = wtxt__direct; + this.wtxt__direct = trg_wtxt; + } + + public static final Xow_page_cache_itm Null = null; + public static final Xow_page_cache_itm Missing = new Xow_page_cache_itm(false, null, null, null); +} diff --git a/400_xowa/src/gplx/xowa/wikis/caches/Xow_page_cache_wkr.java b/400_xowa/src/gplx/xowa/wikis/caches/Xow_page_cache_wkr.java index a27517de8..a58c08050 100644 --- a/400_xowa/src/gplx/xowa/wikis/caches/Xow_page_cache_wkr.java +++ b/400_xowa/src/gplx/xowa/wikis/caches/Xow_page_cache_wkr.java @@ -13,3 +13,7 @@ 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.wikis.caches; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public interface Xow_page_cache_wkr { + byte[] Get_page_or_null(byte[] full_db); +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/Xow_data_mgr_tst.java b/400_xowa/src/gplx/xowa/wikis/data/Xow_data_mgr_tst.java index a27517de8..9465190cf 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/Xow_data_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/data/Xow_data_mgr_tst.java @@ -13,3 +13,150 @@ 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.wikis.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import org.junit.*; import gplx.xowa.wikis.tdbs.*; +import gplx.xowa.wikis.nss.*; +public class Xow_data_mgr_tst { + Xow_data_mgr_fxt fxt = new Xow_data_mgr_fxt(); + @Before public void init() {fxt.Clear(); Datetime_now.Manual_y_();} + @After public void term() {Datetime_now.Manual_n_();} + @Test public void Create() { + fxt .Create("A1", "A1 data") + .Create("B12", "B12 data") + .Create("C123", "C123 data") + .Tst_regy_title("0|A1|C123|3\n") + .Tst_data_title(String_.Concat_lines_nl + ( "!!!!>|!!!!?|!!!!@|" + , "!!!!!|!!!!!|!!!!!|0|!!!!(|A1" + , "!!!!\"|!!!!!|!!!!\"|0|!!!!)|B12" + , "!!!!#|!!!!!|!!!!#|0|!!!!*|C123" + )) + .Tst_data_page(String_.Concat_lines_nl + ( "!!!!9|!!!!;|!!!!=|" + , "!!!!!\t##PX+\tA1\tA1 data\t" + , "!!!!\"\t##PX/\tB12\tB12 data\t" + , "!!!!#\t##PX0\tC123\tC123 data\t" + )) + ; + } + @Test public void Update() { + fxt .Create("A1", "A1 data") + .Create("B12", "B12 data") + .Create("C123", "C123 data") + .Update("B12", "B12 changed") + .Tst_regy_title("0|A1|C123|3\n") + .Tst_data_title(String_.Concat_lines_nl + ( "!!!!>|!!!!?|!!!!@|" + , "!!!!!|!!!!!|!!!!!|0|!!!!(|A1" + , "!!!!\"|!!!!!|!!!!\"|0|!!!!,|B12" + , "!!!!#|!!!!!|!!!!#|0|!!!!*|C123" + )) + .Tst_data_page(String_.Concat_lines_nl + ( "!!!!9|!!!!>|!!!!=|" + , "!!!!!\t##PX+\tA1\tA1 data\t" + , "!!!!\"\t##PX/\tB12\tB12 changed\t" + , "!!!!#\t##PX0\tC123\tC123 data\t" + )) + ; + } + @Test public void Update_zip() { +// fxt.Wiki().Fsys_mgr().Dir_regy()[Xow_ns_.Tid__main].Ext_tid_(gplx.core.ios.streams.Io_stream_tid_.Tid__zip); +// fxt.Wiki().Data_mgr().Zip_mgr_(new Io_zip_mgr_mok()); +// fxt .Create("A1", "A1 data") +// .Create("B12", "B12 data") +// .Create("C123", "C123 data") +// .Update("B12", "B12 changed") +// .Tst_regy_title("0|A1|C123|3\n") +// .Tst_data_title(String_.Concat_lines_nl +// ( "!!!!>|!!!!?|!!!!@|" +// , "!!!!!|!!!!!|!!!!!|0|!!!!(|A1" +// , "!!!!\"|!!!!!|!!!!\"|0|!!!!,|B12" +// , "!!!!#|!!!!!|!!!!#|0|!!!!*|C123" +// )) +// .Tst_data_page(String_.Concat_lines_nl +// ( "zipped:!!!!9|!!!!>|!!!!=|" +// , "!!!!!\t##PX+\tA1\tA1 data\t" +// , "!!!!\"\t##PX/\tB12\tB12 changed\t" +// , "!!!!#\t##PX0\tC123\tC123 data\t" +// )) +// ; + } + @Test public void Create_out_of_order() { + fxt .Create("C123", "C123 data") + .Create("B12", "B12 data") + .Create("A1", "A1 data") + .Tst_regy_title("0|A1|C123|3\n") + .Tst_data_title(String_.Concat_lines_nl + ( "!!!!>|!!!!?|!!!!@|" + , "!!!!#|!!!!!|!!!!#|0|!!!!(|A1" + , "!!!!\"|!!!!!|!!!!\"|0|!!!!)|B12" + , "!!!!!|!!!!!|!!!!!|0|!!!!*|C123" + )) + .Tst_data_page(String_.Concat_lines_nl + ( "!!!!=|!!!!;|!!!!9|" + , "!!!!!\t##PX+\tC123\tC123 data\t" + , "!!!!\"\t##PX/\tB12\tB12 data\t" + , "!!!!#\t##PX0\tA1\tA1 data\t" + )) + ; + } + @Test public void Rename() { + fxt .Create("A1", "A1 data") + .Create("B12", "B12 data") + .Create("C123", "C123 data") + .Rename("C123", "C1234") + .Tst_regy_title("0|A1|C123|3\n") + .Tst_data_title(String_.Concat_lines_nl + ( "!!!!>|!!!!?|!!!!@|" + , "!!!!!|!!!!!|!!!!!|0|!!!!(|A1" + , "!!!!\"|!!!!!|!!!!\"|0|!!!!)|B12" + , "!!!!#|!!!!!|!!!!#|0|!!!!*|C123" + )) + .Tst_data_page(String_.Concat_lines_nl + ( "!!!!9|!!!!;|!!!!=|" + , "!!!!!\t##PX+\tA1\tA1 data\t" + , "!!!!\"\t##PX/\tB12\tB12 data\t" + , "!!!!#\t##PX0\tC123\tC123 data\t" + )) + ; + } +} +class Xow_data_mgr_fxt { + Xoae_app app; + public Xowe_wiki Wiki() {return wiki;} private Xowe_wiki wiki; + public void Clear() { + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + wiki.Db_mgr().Save_mgr().Page_id_next_(0); + } + public Xow_data_mgr_fxt Create(String ttl_str, String data) { + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, Bry_.new_u8(ttl_str)); + wiki.Db_mgr().Save_mgr().Data_create(ttl, Bry_.new_u8(data)); + return this; + } + public Xow_data_mgr_fxt Update(String ttl_str, String data) { + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, Bry_.new_u8(ttl_str)); + Xoae_page page = Xoae_page.New_test(wiki, ttl); + wiki.Db_mgr().Save_mgr().Data_update(page, Bry_.new_u8(data)); + return this; + } + public Xow_data_mgr_fxt Rename(String old_ttl, String new_ttl) { + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, Bry_.new_u8(old_ttl)); + Xoae_page page = Xoae_page.New_test(wiki, ttl); + wiki.Db_mgr().Save_mgr().Data_rename(page, ttl.Ns().Id(), Bry_.new_u8(new_ttl)); + return this; + } + public Xow_data_mgr_fxt Tst_regy_title(String expd) {return Tst_regy(Xotdb_dir_info_.Name_title, expd);} + Xow_data_mgr_fxt Tst_regy(String name, String expd) { + Io_url file_orig = Io_url_.mem_fil_("mem/xowa/wiki/en.wikipedia.org/ns/000/" + name + "/reg.csv"); + Tfds.Eq_str_lines(expd, Io_mgr.Instance.LoadFilStr(file_orig)); + return this; + } + public Xow_data_mgr_fxt Tst_data_page(String expd) {return Tst_data(Xotdb_dir_info_.Tid_page , Xow_ns_.Tid__main, 0, expd);} + public Xow_data_mgr_fxt Tst_data_title(String expd) {return Tst_data(Xotdb_dir_info_.Tid_ttl, Xow_ns_.Tid__main, 0, expd);} + public Xow_data_mgr_fxt Tst_data(byte dir_tid, int ns_id, int fil, String expd) { + Io_url url = wiki.Tdb_fsys_mgr().Url_ns_fil(dir_tid, ns_id, fil); + Tfds.Eq_str_lines(expd, Io_mgr.Instance.LoadFilStr(url)); + return this; + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file.java b/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file.java index a27517de8..d93fa3737 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file.java +++ b/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file.java @@ -13,3 +13,101 @@ 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.wikis.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.bldrs.infos.*; +import gplx.xowa.wikis.data.site_stats.*; +import gplx.xowa.htmls.core.dbs.*; import gplx.xowa.addons.wikis.searchs.dbs.*; +import gplx.xowa.addons.wikis.htmls.css.dbs.*; +import gplx.xowa.xtns.wbases.dbs.*; +public class Xow_db_file { + protected Xow_db_file(Db_cfg_tbl cfg_tbl, Xowd_core_db_props props, Xob_info_session info_session, Xob_info_file info_file, Xow_db_file_schema_props schema_props, int id, byte tid, Io_url url, String ns_ids, int part_id, Guid_adp guid, Db_conn conn, byte cmd_mode) { + this.id = id; this.tid = tid; this.url = url; this.ns_ids = ns_ids; this.part_id = part_id; this.guid = guid; this.db_props = props; + this.conn = conn; this.cmd_mode = cmd_mode; + this.url_rel = url.NameAndExt(); + boolean schema_is_1 = props.Schema_is_1(); + this.tbl__cfg = cfg_tbl; + this.tbl__db = new Xowd_xowa_db_tbl(conn, schema_is_1); + this.tbl__ns = new Xowd_site_ns_tbl(conn, schema_is_1); + this.tbl__site_stats = new Xowd_site_stats_tbl(conn, schema_is_1); + this.tbl__page = new Xowd_page_tbl(conn, schema_is_1); + this.tbl__text = new Xowd_text_tbl(conn, schema_is_1, props.Zip_tid_text()); + this.tbl__html = new Xowd_html_tbl(conn); + this.tbl__css_core = new Xowd_css_core_tbl(conn); + this.tbl__css_file = new Xowd_css_file_tbl(conn); + this.tbl__cat_core = new Xowd_cat_core_tbl(conn, schema_is_1); + this.tbl__cat_link = new Xowd_cat_link_tbl(conn, schema_is_1); + this.tbl__wbase_qid = new Xowd_wbase_qid_tbl(conn, schema_is_1, schema_props == null ? Bool_.N : schema_props.Wbase__qid__src_ttl_has_spaces()); + this.tbl__wbase_pid = new Xowd_wbase_pid_tbl(conn, schema_is_1); + this.tbl__wbase_prop = new Xowb_prop_tbl(conn); + this.info_session = info_session; + this.info_file = info_file; + this.schema_props = schema_props; + } + public int Id() {return id;} private final int id; // unique id in xowa_db + public byte Tid() {return tid;} private final byte tid; + public Db_conn Conn() {return conn;} private final Db_conn conn; + public Io_url Url() {return url;} private final Io_url url; + public String Url_rel() {return url_rel;} private final String url_rel; + public Xowd_core_db_props Db_props() {return db_props;} private final Xowd_core_db_props db_props; + public String Ns_ids() {return ns_ids;} private final String ns_ids; + public int Ns_id_or_fail() {return Int_.Parse(ns_ids);} + public int Part_id() {return part_id;} private final int part_id; + public Guid_adp Guid() {return guid;} private final Guid_adp guid; + public byte Cmd_mode() {return cmd_mode;} public Xow_db_file Cmd_mode_(byte v) {cmd_mode = v; return this;} private byte cmd_mode; + public long File_len() {return file_len;} public Xow_db_file File_len_add(int v) {file_len += v; return this;} private long file_len; + public long File_max() {return file_max;} public Xow_db_file File_max_(long v) {file_max = v; return this;} private long file_max; + public Db_cfg_tbl Tbl__cfg() {return tbl__cfg;} private final Db_cfg_tbl tbl__cfg; + public Xowd_xowa_db_tbl Tbl__db() {return tbl__db;} private final Xowd_xowa_db_tbl tbl__db; + public Xowd_site_ns_tbl Tbl__ns() {return tbl__ns;} private final Xowd_site_ns_tbl tbl__ns; + public Xowd_page_tbl Tbl__page() {return tbl__page;} private Xowd_page_tbl tbl__page; + public Xowd_text_tbl Tbl__text() {return tbl__text;} private final Xowd_text_tbl tbl__text; + public Xowd_html_tbl Tbl__html() {return tbl__html;} private final Xowd_html_tbl tbl__html; + public Xowd_css_core_tbl Tbl__css_core() {return tbl__css_core;} private final Xowd_css_core_tbl tbl__css_core; + public Xowd_css_file_tbl Tbl__css_file() {return tbl__css_file;} private final Xowd_css_file_tbl tbl__css_file; + public Xowd_cat_core_tbl Tbl__cat_core() {return tbl__cat_core;} private final Xowd_cat_core_tbl tbl__cat_core; + public Xowd_cat_link_tbl Tbl__cat_link() {return tbl__cat_link;} private final Xowd_cat_link_tbl tbl__cat_link; + public Xowd_site_stats_tbl Tbl__site_stats() {return tbl__site_stats;} private final Xowd_site_stats_tbl tbl__site_stats; + public Xowd_wbase_qid_tbl Tbl__wbase_qid() {return tbl__wbase_qid;} private final Xowd_wbase_qid_tbl tbl__wbase_qid; + public Xowd_wbase_pid_tbl Tbl__wbase_pid() {return tbl__wbase_pid;} private final Xowd_wbase_pid_tbl tbl__wbase_pid; + public Xowb_prop_tbl Tbl__wbase_prop() {return tbl__wbase_prop;} private final Xowb_prop_tbl tbl__wbase_prop; + public Xob_info_session Info_session() { + if (info_session == null) // NOTE: null when load; !null when make + info_session = Xob_info_session.Load(tbl__cfg); + return info_session; + } private Xob_info_session info_session; + public Xob_info_file Info_file() { + if (info_file == null) // NOTE: null when load; !null when make + info_file = Xob_info_file.Load(tbl__cfg); + return info_file; + } private Xob_info_file info_file; + public Xow_db_file_schema_props Schema_props() { + if (schema_props == null) + schema_props = Xow_db_file_schema_props.load_(tbl__cfg, tid, this.Info_session().Version()); // NOTE: must call .Info_session + return schema_props; + } private Xow_db_file_schema_props schema_props; + + public void Rls() {conn.Rls_conn();} + public Xowd_page_tbl Tbl__page__rebind() { + this.tbl__page = new Xowd_page_tbl(tbl__page.Conn(), tbl__page.schema_is_1); + return tbl__page; + } + + public static final Xow_db_file Null = null; + public static Xow_db_file Make(Xob_info_session info_session, Xowd_core_db_props props, int id, byte tid, Io_url url, String ns_ids, int part_id, String core_file_name, Db_conn conn) { + Guid_adp guid = Guid_adp_.New(); + Xob_info_file info_file = new Xob_info_file(id, Xow_db_file_.To_key(tid), ns_ids, part_id, guid, props.Schema(), core_file_name, url.NameAndExt()); + Db_cfg_tbl cfg_tbl = gplx.xowa.wikis.data.Xowd_cfg_tbl_.New(conn); + Xow_db_file rv = new Xow_db_file(cfg_tbl, props, info_session, info_file, Xow_db_file_schema_props.make_(), id, tid, url, ns_ids, part_id, guid, conn, Db_cmd_mode.Tid_create); + cfg_tbl.Create_tbl(); // always create cfg in each db + return rv; + } + public static Xow_db_file Load(Xowd_core_db_props props, int id, byte tid, Io_url url, String ns_ids, int part_id, Guid_adp guid) { + Db_conn conn = Db_conn_bldr.Instance.Get(url); + if (conn == null) { + Xoa_app_.Usr_dlg().Warn_many("", "", "wiki.db:missing db; tid=~{0} url=~{1}", Xow_db_file_.To_key(tid), url.Raw()); + conn = Db_conn_.Noop; + } + Db_cfg_tbl cfg_tbl = gplx.xowa.wikis.data.Xowd_cfg_tbl_.New(conn); // NOTE: this loads the cfg tbl for the current db, not the core db + return new Xow_db_file(cfg_tbl, props, null, null, null, id, tid, url, ns_ids, part_id, guid, conn, Db_cmd_mode.Tid_ignore); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file_.java b/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file_.java index a27517de8..b2d8994ff 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file_.java +++ b/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file_.java @@ -13,3 +13,49 @@ 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.wikis.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public class Xow_db_file_ { + public static final int Uid__core = 0; + public static final byte + Tid__core = 1, Tid__text = 2, Tid__cat = 3, Tid__search_core = 4, Tid__wbase = 5 // SERIALIZED:v1 + , Tid__cat_core = 6, Tid__cat_link = 7 // SERIALIZED:v2 + , Tid__wiki_solo = 8, Tid__text_solo = 9 + , Tid__html_solo = 10, Tid__html_data = 11 + , Tid__file_solo = 12, Tid__file_core = 13, Tid__file_data = 14, Tid__file_user = 15 + , Tid__search_link = 16, Tid__random = 17, Tid__css = 18 + , Tid__html_user = 19 + ; + private static final String + Key__core = "core", Key__text = "text", Key__cat = "xtn.category", Key__search_core = "xtn.search.core", Key__wbase = "core.wbase" + , Key__cat_core = "xtn.category.core", Key__cat_link = "xtn.category.link" + , Key__text_solo = "text.solo", Key__wiki_solo = "wiki.solo" + , Key__html_solo = "html.solo", Key__html_data = "html" + , Key__file_solo = "file.solo", Key__file_core = "file.core", Key__file_data = "file.data", Key__file_user = "file.user" + , Key__search_link = "xtn.search.link", Key__random = "xtn.random", Key__css = "xtn.css" + , Key__html_user = "html.user" + ; + public static String To_key(byte v) { + switch (v) { + case Tid__core: return Key__core; + case Tid__text: return Key__text; + case Tid__cat: return Key__cat; + case Tid__search_core: return Key__search_core; + case Tid__wbase: return Key__wbase; + case Tid__cat_core: return Key__cat_core; + case Tid__cat_link: return Key__cat_link; + case Tid__wiki_solo: return Key__wiki_solo; + case Tid__text_solo: return Key__text_solo; + case Tid__html_solo: return Key__html_solo; + case Tid__html_data: return Key__html_data; + case Tid__file_solo: return Key__file_solo; + case Tid__file_core: return Key__file_core; + case Tid__file_data: return Key__file_data; + case Tid__file_user: return Key__file_user; + case Tid__search_link: return Key__search_link; + case Tid__random: return Key__random; + case Tid__css: return Key__css; + case Tid__html_user: return Key__html_user; + default: throw Err_.new_unhandled(v); + } + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file__core_.java b/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file__core_.java index a27517de8..017a4277d 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file__core_.java +++ b/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file__core_.java @@ -13,3 +13,119 @@ 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.wikis.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.dbs.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.bldrs.infos.*; +public class Xow_db_file__core_ { + public static Io_url Find_core_fil_or_null(Xow_wiki wiki) {return Find_core_fil_or_null(wiki.Fsys_mgr().Root_dir(), wiki.Domain_str());} + public static Io_url Find_core_fil_or_null(Io_url wiki_root_dir, String domain_str) { + Io_url[] ary = Io_mgr.Instance.QueryDir_fils(wiki_root_dir); + int ary_len = ary.length; if (ary.length == 0) return null; + Io_url rv = Find_core_fil__xowa(ary, ary_len, domain_str); + return rv == null ? Find_core_fil__sqlite3(wiki_root_dir, ary, ary_len, domain_str) : rv; + } + private static Io_url Find_core_fil__xowa(Io_url[] ary, int ary_len, String domain_str) { + // search for ALL ("en.wikipedia.org") or FEW / LOT ("en.wikipedia.org-core") + for (int i = 0; i < ary_len; i++) { + Io_url itm = ary[i]; + if (!String_.Eq(itm.Ext(), ".xowa")) continue; + if ( String_.Eq(itm.NameOnly(), domain_str) // EX: "en.wikipedia.org" + || String_.Eq(itm.NameOnly(), domain_str + "-core") // EX: "en.wikipedia.org-core" + ) { + Xoa_app_.Usr_dlg().Log_many("", "", "wiki.db_core.v2.standard: url=~{0}", itm.Raw()); + return itm; + } + } + + // handle old "FEW" layout where "-text" existed, but not "-core"; DB.FEW: DATE:2016-06-07 + for (int i = 0; i < ary_len; i++) { + Io_url itm = ary[i]; + if (!String_.Eq(itm.Ext(), ".xowa")) continue; + if (String_.Eq(itm.NameOnly(), domain_str + "-text")) { // EX: "en.wikipedia.org-text" + Xoa_app_.Usr_dlg().Log_many("", "", "wiki.db_core.v2.old_few: url=~{0}", itm.Raw()); + return itm; + } + } + + // handle renamed directories; EX: "en.wikipedia.org" -> "en.wikipedia.org-201609"; DATE:2016-10-13 + for (int i = 0; i < ary_len; i++) { + Io_url itm = ary[i]; + if (!String_.Eq(itm.Ext(), ".xowa")) continue; + if (String_.Has_at_end(itm.NameOnly(), "-core")){ // only check "-core" databases. note that this can also include "en.wikipedia.org-file-core.xowa" + Db_conn core_conn = Db_conn_bldr.Instance.Get_or_fail(itm); + + // if db has "xowa_db" then assume that it is the core + if (core_conn.Meta_tbl_exists(gplx.xowa.wikis.data.tbls.Xowd_xowa_db_tbl.TBL_NAME)) { + Xoa_app_.Usr_dlg().Log_many("", "", "wiki.db_core.v2.renamed: url=~{0}", itm.Raw()); + return itm; + } + } + } + return null; + } + private static Io_url Find_core_fil__sqlite3(Io_url wiki_root_dir, Io_url[] ary, int ary_len, String domain_str) { + Io_url rv = null; + String v0_str = domain_str + ".000"; + for (int i = 0; i < ary_len; i++) { + Io_url itm = ary[i]; + if (!String_.Eq(itm.Ext(), ".sqlite3")) continue; + if (String_.Eq(itm.NameOnly(), v0_str)) { // EX: "en.wikipedia.org.000" + Xoa_app_.Usr_dlg().Log_many("", "", "wiki.db_core.v1: url=~{0}", itm.Raw()); + return itm; + } + if (ary_len == 1) { + Xoa_app_.Usr_dlg().Log_many("", "", "wiki.db_core.custom: url=~{0}", itm.Raw()); + return rv; // 1 folder and 1 sqlite file; return it; custom wikis? + } + } + Xoa_app_.Usr_dlg().Log_many("", "", "wiki.db_core.none: dir=~{0}", wiki_root_dir.Raw()); + return rv; + } + + public static boolean Is_core_fil_name(String domain_name, String fil_name) { + Xow_domain_itm domain_itm = Xow_domain_itm_.parse(Bry_.new_u8(domain_name)); + if (domain_itm.Domain_type_id() == Xow_domain_tid_.Tid__other) { + return String_.Has_at_end(fil_name, ".xowa"); + } + String domain_str = domain_itm.Domain_str(); + return ( String_.Eq(fil_name, domain_str + "-text.xowa") + || String_.Eq(fil_name, domain_str + "-core.xowa") + || String_.Eq(fil_name, domain_str + ".xowa") + ); + } + + public static Xow_db_file Make_core_db(Xowd_core_db_props props, Xob_info_session info_session, Io_url wiki_root_dir, String domain_str) { + String core_file_name = Xow_db_file__core_.Core_file_name(props.Layout_text(), domain_str); + byte core_db_tid = Xow_db_file__core_.Core_db_tid(props.Layout_text()); + Io_url core_db_url = wiki_root_dir.GenSubFil(core_file_name); + Db_conn core_conn = Db_conn_bldr.Instance.New(core_db_url); + + // make tbls + Xow_db_file rv = Xow_db_file.Make(info_session, props, Xow_db_file_.Uid__core, core_db_tid, core_db_url, Xob_info_file.Ns_ids_empty, Xob_info_file.Part_id_1st, core_file_name, core_conn); + rv.Tbl__db().Create_tbl(); + rv.Tbl__ns().Create_tbl(); + rv.Tbl__site_stats().Create_tbl(); + rv.Tbl__page().Create_tbl(); + if (props.Layout_text().Tid_is_all_or_few()) { // create in advance else will fail for v2; import wiki -> wiki loads and tries to load categories; v2 category processes and builds tbl; DATE:2015-03-22 + rv.Tbl__cat_core().Create_tbl(); + rv.Tbl__cat_link().Create_tbl(); + } + return rv; + } + private static String Core_file_name(Xow_db_layout layout, String domain_name) { + switch (layout.Tid()) { + case Xow_db_layout.Tid__all: return domain_name + ".xowa"; // EX: en.wikipedia.org.xowa + case Xow_db_layout.Tid__few: //return domain_name + "-text.xowa"; // EX: en.wikipedia.org-text.xowa // DB.FEW: DATE:2016-06-07 + case Xow_db_layout.Tid__lot: return domain_name + "-core.xowa"; // EX: en.wikipedia.org-core.xowa + default: throw Err_.new_unimplemented(); + } + } + public static byte Core_db_tid(Xow_db_layout layout) { + switch (layout.Tid()) { + case Xow_db_layout.Tid__all: return Xow_db_file_.Tid__wiki_solo; + case Xow_db_layout.Tid__few: // return Xow_db_file_.Tid__core; // DB.FEW: DATE:2016-06-07 + case Xow_db_layout.Tid__lot: return Xow_db_file_.Tid__core; + default: throw Err_.new_unimplemented(); + } + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file_hash.java b/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file_hash.java index a27517de8..40af93711 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file_hash.java +++ b/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file_hash.java @@ -13,3 +13,31 @@ 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.wikis.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +class Xow_db_file_hash { + private final Ordered_hash hash = Ordered_hash_.New(); + public int Count_total() {return count_total;} private int count_total; + public void Clear() {hash.Clear(); count_total = 0;} + public Ordered_hash Get_by_tid_or_null(byte tid) {return (Ordered_hash)hash.Get_by(tid);} + public void Add_or_new(Xow_db_file file) { + byte tid = file.Tid(); + Ordered_hash tids = (Ordered_hash)hash.Get_by(tid); + if (tids == null) { + tids = Ordered_hash_.New(); + hash.Add(tid, tids); + } + tids.Add(file.Id(), file); + ++count_total; + } + public void Del(Xow_db_file file) { + Ordered_hash tids = (Ordered_hash)hash.Get_by(file.Tid()); + if (tids == null) throw Err_.new_wo_type("unknown file.tid", "url", file.Url()); + if (!tids.Has(file.Id())) throw Err_.new_wo_type("unknown file.id", "url", file.Url()); + tids.Del(file.Id()); + --count_total; + } + public int Count_of_tid(byte tid) { + Ordered_hash tids = (Ordered_hash)hash.Get_by(tid); + return tids == null ? 0 : tids.Count(); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file_schema_props.java b/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file_schema_props.java index a27517de8..757c59d64 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file_schema_props.java +++ b/400_xowa/src/gplx/xowa/wikis/data/Xow_db_file_schema_props.java @@ -13,3 +13,24 @@ 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.wikis.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.dbs.cfgs.*; import gplx.xowa.bldrs.infos.*; +public class Xow_db_file_schema_props { + Xow_db_file_schema_props(boolean search__word__page_count_exists, boolean wbase__qid__src_ttl_has_spaces) { + this.search__word__page_count_exists = search__word__page_count_exists; + this.wbase__qid__src_ttl_has_spaces = wbase__qid__src_ttl_has_spaces; + } + public boolean Search__word__page_count_exists() {return search__word__page_count_exists;} private final boolean search__word__page_count_exists; + public boolean Wbase__qid__src_ttl_has_spaces() {return wbase__qid__src_ttl_has_spaces;} private final boolean wbase__qid__src_ttl_has_spaces; + public static Xow_db_file_schema_props make_() {return new Xow_db_file_schema_props(Bool_.Y, Bool_.N);} + public static Xow_db_file_schema_props load_(Db_cfg_tbl tbl, int tid, String version) { + boolean search__word__page_count_exists = tbl.Select_yn_or(Grp, Key__col_search_word_page_count, Bool_.N); + boolean wbase__qid__src_ttl_has_spaces = String_.In(version, "2.4.2.1", "2.4.3.1", "2.4.3.2"); + return new Xow_db_file_schema_props(search__word__page_count_exists, wbase__qid__src_ttl_has_spaces); + } + public static final String Grp = Xowd_cfg_key_.Grp__wiki_schema; + public static final String + Key__tbl_css_core = "tbl.css_core" // VERSION:2.4.1 + , Key__col_search_word_page_count = "col.search_word.word_page_count" // VERSION:2.4.2 + ; +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/Xow_db_layout.java b/400_xowa/src/gplx/xowa/wikis/data/Xow_db_layout.java index a27517de8..955c08729 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/Xow_db_layout.java +++ b/400_xowa/src/gplx/xowa/wikis/data/Xow_db_layout.java @@ -13,3 +13,33 @@ 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.wikis.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public class Xow_db_layout { + public Xow_db_layout(int tid) {this.tid = tid;} + public int Tid() {return tid;} private final int tid; + public boolean Tid_is_all() {return tid == Tid__all;} + public boolean Tid_is_all_or_few() {return tid != Tid__lot;} + public boolean Tid_is_lot() {return tid == Tid__lot;} + public String Key() { + switch (tid) { + case Xow_db_layout.Tid__all: return Key__all; + case Xow_db_layout.Tid__few: return Key__few; + case Xow_db_layout.Tid__lot: return Key__lot; + default: throw Err_.new_unimplemented(); + } + } + + public static final int Tid__all = 1, Tid__few = 2, Tid__lot = 3; + public static final String Key__all = "all", Key__few = "few", Key__lot = "lot"; + public static final Xow_db_layout + Itm_all = new Xow_db_layout(Tid__all) + , Itm_few = new Xow_db_layout(Tid__few) + , Itm_lot = new Xow_db_layout(Tid__lot) + ; + public static Xow_db_layout Get_by_name(String v) { + if (String_.Eq(v, Key__all)) return Itm_all; + else if (String_.Eq(v, Key__few)) return Itm_few; + else if (String_.Eq(v, Key__lot)) return Itm_lot; + else throw Err_.new_unimplemented(); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/Xow_db_mgr.java b/400_xowa/src/gplx/xowa/wikis/data/Xow_db_mgr.java index a27517de8..447d548bc 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/Xow_db_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/data/Xow_db_mgr.java @@ -13,3 +13,218 @@ 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.wikis.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.core.lists.hashs.*; +import gplx.xowa.wikis.dbs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.bldrs.infos.*; import gplx.xowa.wikis.metas.*; +public class Xow_db_mgr { + private final Io_url wiki_root_dir; + private final String domain_str; // needed for generating new files; EX: en.wikipedia.org-text.ns.001.xowa + private final Ordered_hash hash_by_id = Ordered_hash_.New(); private final Xow_db_file_hash hash_by_tids = new Xow_db_file_hash(); + private int db_id_next = 0; + public Xow_db_mgr(Io_url wiki_root_dir, String domain_str) { + this.wiki_root_dir = wiki_root_dir; + this.domain_str = domain_str; + } + public Xowd_core_db_props Props() {return props;} private Xowd_core_db_props props = Xowd_core_db_props.Test; + public Db_cfg_tbl Tbl__cfg() {return db__core.Tbl__cfg();} + public Xowd_page_tbl Tbl__page() {return db__core.Tbl__page();} + public Xow_db_file Db__core() {return db__core;} private Xow_db_file db__core; + public Xow_db_file Db__text() {return db__text;} private Xow_db_file db__text; + public Xow_db_file Db__html() {return db__html;} private Xow_db_file db__html; + public Xow_db_file Db__cat_core() {return db__cat_core;} private Xow_db_file db__cat_core; + public Xow_db_file Db__wbase() {return db__wbase;} private Xow_db_file db__wbase; public void Db__wbase_(Xow_db_file v) {db__wbase = v;} + public void Init_by_load(Io_url core_url) { + // clear lists + hash_by_id.Clear(); + hash_by_tids.Clear(); + + // create core_conn / core_db + Db_conn core_conn = Db_conn_bldr.Instance.Get(core_url); + props = Xowd_core_db_props.Cfg_load(core_conn); // load props to get layout_text + Dbs__set_by_tid(Xow_db_file.Load(props, Xow_db_file_.Uid__core, Xow_db_file__core_.Core_db_tid(props.Layout_text()), core_url, Xob_info_file.Ns_ids_empty, Xob_info_file.Part_id_1st, Guid_adp_.Empty)); + + // load dbs from "xowa_db" tbl + Xow_db_file[] ary = db__core.Tbl__db().Select_all(props, core_url.OwnerDir()); + int len = ary.length; + for (int i = 0; i < len; i++) { + Xow_db_file db = ary[i]; + Dbs__set_by_tid(db); + Dbs__add(db); + } + } + public void Init_by_make(Xowd_core_db_props props, Xob_info_session info_session) { + this.props = props; + + // save data + Xow_db_file core_db = Xow_db_file__core_.Make_core_db(props, info_session, wiki_root_dir, domain_str); + Dbs__set_by_tid(core_db); + Dbs__add_and_save(core_db); + props.Cfg_save(db__core.Tbl__cfg()); // NOTE: must save cfg now, especially zip_tid; latter will be reloaded after import is done; + } + public void Rls() { + int len = hash_by_id.Len(); + for (int i = 0; i < len; i++) { + Xow_db_file db_file = (Xow_db_file)hash_by_id.Get_at(i); + db_file.Rls(); + } + } + + public int Dbs__len() {return hash_by_id.Len();} + public Xow_db_file Dbs__get_at(int i) {return (Xow_db_file)hash_by_id.Get_at(i);} + public Xow_db_file Dbs__get_by_id_or_fail(int id) {return (Xow_db_file)hash_by_id.Get_by_or_fail(id);} + public Xow_db_file Dbs__get_by_id_or_null(int id) {return (Xow_db_file)hash_by_id.Get_by(id);} + public Xow_db_file Dbs__get_by_tid_or_core(byte... tids_ary) {Xow_db_file rv = Dbs__get_by_tid_or_null(tids_ary); return rv == null ? db__core : rv;} + public Xow_db_file Dbs__get_by_tid_or_null(byte... tids_ary) { + int tids_len = tids_ary.length; + for (int i = 0; i < tids_len; ++i) { + byte tid = tids_ary[i]; + Ordered_hash tid_dbs = hash_by_tids.Get_by_tid_or_null(tid); if (tid_dbs == null) continue; + int tid_dbs_len = tid_dbs.Len(); + if (tid_dbs_len != 1) { // NOTE: occurs when multiple search imports fail; DATE:2016-04-04 + Xoa_app_.Usr_dlg().Warn_many("", "", "expecting only 1 db for tid; tid=~{0} len=~{1} db_api=~{2}", tid, tid_dbs.Len(), db__core.Conn().Conn_info().Db_api()); + } + return (Xow_db_file)tid_dbs.Get_at(tid_dbs_len - 1); // get last idx; + } + return null; + } + public Xow_db_file Dbs__assert_by_tid(byte tid) { + Xow_db_file rv = Dbs__get_by_tid_or_null(tid); + if (rv == null) { + rv = Dbs__make_by_tid(tid); + } + return rv; + } + public Ordered_hash Dbs__get_hash_by_tid(int tid) {return hash_by_tids.Get_by_tid_or_null((byte)tid);} + public Xow_db_file Dbs__make_by_tid(byte tid) { + int tid_idx = Get_tid_idx(hash_by_tids, tid); + return Dbs__make_by_tid(tid, Xob_info_file.Ns_ids_empty, tid_idx, Get_tid_name(tid_idx, tid)); + } + public Xow_db_file Dbs__make_by_tid(byte tid, String ns_ids, int part_id, String file_name_suffix) { + return Dbs__make_by_id(db_id_next++, tid, ns_ids, part_id, file_name_suffix); + } + public Xow_db_file Dbs__make_by_id(int id, byte tid, String ns_ids, int part_id, String file_name_suffix) { + Io_url url = wiki_root_dir.GenSubFil(domain_str + file_name_suffix); + Xow_db_file rv = Xow_db_file.Make(db__core.Info_session(), props, id, tid, url, ns_ids, part_id, db__core.Url().NameAndExt(), Db_conn_bldr.Instance.New(url)); + Dbs__add_and_save(rv); + Dbs__set_by_tid(rv); + return rv; + } + public Xow_db_file Dbs__remake_by_tid(byte tid) { + Dbs__delete_by_tid(tid); + return Dbs__make_by_tid(tid); + } + public void Dbs__delete_by_tid(byte... tids) { + int len = hash_by_id.Len(); + for (int i = 0; i < len; ++i) { + Xow_db_file db = (Xow_db_file)hash_by_id.Get_at(i); + if (!Byte_.Match_any(db.Tid(), tids)) continue; + db.Rls(); + Io_mgr.Instance.DeleteFil_args(db.Url()).MissingFails_off().Exec(); + db.Cmd_mode_(Db_cmd_mode.Tid_delete); + } + db__core.Tbl__db().Commit_all(this); + + // call init again to regen list of dbs + this.Init_by_load(db__core.Url()); + } + public Xow_db_file Dbs__get_for_create(byte tid, int ns_id) { + Xow_db_file[] ary = Dbs__get_ary(tid, ns_id); + if (ary.length == 0) throw Err_.new_wo_type("no dbs exist; wiki=~{0} type=~{1}", domain_str, tid); + return ary[ary.length - 1]; + } + public Xow_db_file[] Dbs__get_ary(byte tid, int ns_id) { + List_adp rv = List_adp_.New(); + + // loop all dbs + int len = this.Dbs__len(); + for (int i = 0; i < len; i++) { + boolean add = false; + Xow_db_file db = this.Dbs__get_at(i); + switch (tid) { + // cat_link requested + case Xow_db_file_.Tid__cat_link: + switch (db.Tid()) { + case Xow_db_file_.Tid__core: + add = props.Layout_text().Tid_is_all_or_few(); // cat_link will be in "core" if "all" or "few" (-text, -html, -file) + break; + case Xow_db_file_.Tid__cat_link: + add = true; + break; + } + break; + // text requested + case Xow_db_file_.Tid__text: + switch (db.Tid()) { + case Xow_db_file_.Tid__core: + add = props.Layout_text().Tid_is_all(); // text will be in "core" if "all" + break; + case Xow_db_file_.Tid__text_solo: // EX: "en.wikipedia.org-text.xowa" + add = true; // text will be in db if solo; + break; + case Xow_db_file_.Tid__text: // EX: "en.wikipedia.org-text-ns.000.xowa" + int[] db_ns_ids = Int_ary_.Parse(db.Ns_ids(), "|"); // need to handle both "0" and "0|4" + for (int db_ns_id : db_ns_ids) { + if (db_ns_id == ns_id) { + add = true; // text will be in db if ns matches; EX: en.wikipedia.org-text-ns.014.xowa + break; + } + } + break; + } + break; + } + if (add) + rv.Add(db); + } + + return (Xow_db_file[])rv.To_ary_and_clear(Xow_db_file.class); + } + public void Create_page(Xowd_page_tbl core_tbl, Xowd_text_tbl text_tbl, int page_id, int ns_id, byte[] ttl_wo_ns, boolean redirect, DateAdp modified_on, byte[] text_zip_data, int text_raw_len, int random_int, int text_db_id, int html_db_id) { + core_tbl.Insert_cmd_by_batch(page_id, ns_id, ttl_wo_ns, redirect, modified_on, text_raw_len, random_int, text_db_id, html_db_id, -1); + text_tbl.Insert_cmd_by_batch(page_id, text_zip_data); + } + private void Dbs__set_by_tid(Xow_db_file db) { + switch (db.Tid()) { + case Xow_db_file_.Tid__wiki_solo: + case Xow_db_file_.Tid__text_solo: + case Xow_db_file_.Tid__core : {db__core = db; if (props.Layout_text().Tid_is_all_or_few()) db__cat_core = db__text = db; break;} + case Xow_db_file_.Tid__text : {db__text = db; break;} + case Xow_db_file_.Tid__html_data : {db__html = db; break;} + case Xow_db_file_.Tid__wbase : {if (db__wbase == null) db__wbase = db; break;} + case Xow_db_file_.Tid__cat_core : + case Xow_db_file_.Tid__cat : {if (db__cat_core == null) db__cat_core = db; break;} + } + } + private void Dbs__add(Xow_db_file db_file) { + int db_id = db_file.Id(); + hash_by_id.Add(db_id, db_file); + hash_by_tids.Add_or_new(db_file); + if (db_id >= db_id_next) // always set db_id_next to largest value in given set of dbs; EX: dbs=[0,1,10]; db_id_next should be 11 + db_id_next = db_id + 1; + } + private void Dbs__add_and_save(Xow_db_file db_file) { + Dbs__add(db_file); + + db__core.Tbl__db().Commit_all(this); + db_file.Info_file().Save(db_file.Tbl__cfg()); + db_file.Info_session().Save(db_file.Tbl__cfg()); + } + private int Get_tid_idx(Xow_db_file_hash hash, byte tid) {return hash.Count_of_tid(tid) + Int_.Base1;} + private static String Get_tid_name(int tid_idx, byte tid) { + String tid_name = Xow_db_file_.To_key(tid); + String tid_idx_str = ""; + switch (tid) { + case Xow_db_file_.Tid__cat_core : break; + case Xow_db_file_.Tid__cat_link : tid_idx_str = "-db." + Int_.To_str_pad_bgn_zero(tid_idx, 3); break; + default : tid_idx_str = tid_idx == 1 ? "" : "-db." + Int_.To_str_pad_bgn_zero(tid_idx, 3); break; + } + return String_.Format("-{0}{1}.xowa", tid_name, tid_idx_str); // EX: en.wikipedia.org-text-001.sqlite3 + } + + // helper method for wikis to (a) init db_mgr; (b) load wiki.props; should probably be moved to more generic "wiki.Init_by_db()" + public static void Init_by_load(Xow_wiki wiki, Io_url core_url) { + wiki.Data__core_mgr().Init_by_load(core_url); + wiki.Props().Init_by_load(wiki.App(), wiki.Data__core_mgr().Tbl__cfg()); // load Main_page + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/Xowd_cfg_key_.java b/400_xowa/src/gplx/xowa/wikis/data/Xowd_cfg_key_.java index a27517de8..0e9fc6977 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/Xowd_cfg_key_.java +++ b/400_xowa/src/gplx/xowa/wikis/data/Xowd_cfg_key_.java @@ -13,3 +13,29 @@ 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.wikis.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public class Xowd_cfg_key_ { + public static final String + Grp__empty = "" + , Grp__wiki_schema = "xowa.wiki.schema" + , Grp__bldr_session = "xowa.bldr.session" + , Grp__bldr_db = "xowa.bldr.db" + , Grp__bldr_fsdb = "xowa.bldr.fsdb" + + , Grp__wiki_init = "wiki.init" + , Key__init__main_page = "props.main_page" + , Key__init__bldr_version = "props.bldr_version" + , Key__init__modified_latest = "props.modified_latest" + , Key__init__siteinfo_misc = "props.siteinfo_misc" + , Key__init__siteinfo_mainpage = "props.siteinfo_mainpage" + + , Grp__wiki__core = "xowa.wiki.core" + , Key__wiki__core__domain = "xowa.wiki.core.domain" + , Key__wiki__core__name = "xowa.wiki.core.name" + + , Key__wiki__upgrade__version = "xowa.wiki.upgrade.version" + + , Grp__db = "db" + , Key__wiki__page__id_next = "page.id_next" + ; +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/Xowd_cfg_tbl_.java b/400_xowa/src/gplx/xowa/wikis/data/Xowd_cfg_tbl_.java index a27517de8..831ac949c 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/Xowd_cfg_tbl_.java +++ b/400_xowa/src/gplx/xowa/wikis/data/Xowd_cfg_tbl_.java @@ -13,3 +13,33 @@ 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.wikis.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; +public class Xowd_cfg_tbl_ { + public static final String Tbl_name = "xowa_cfg"; + public static Db_cfg_tbl New(gplx.dbs.Db_conn conn) {return New(conn, Tbl_name);} + public static Db_cfg_tbl New(gplx.dbs.Db_conn conn, String tbl_name) {return new Db_cfg_tbl(conn, tbl_name);} + public static Db_cfg_tbl Get_or_null(Db_conn conn) { + return conn.Meta_tbl_exists(Tbl_name) ? new Db_cfg_tbl(conn, Tbl_name) : null; + } + public static Db_cfg_tbl Get_or_fail(Db_conn conn) { + Db_cfg_tbl rv = Get_or_null(conn); + if (rv == null) throw Err_.New("xowa_cfg tbl does not exist: file={0}", conn.Conn_info().Raw()); + return rv; + } + + public static void Upsert__import(Xowe_wiki wiki) { + Db_cfg_tbl cfg_tbl = wiki.Data__core_mgr().Db__core().Tbl__cfg(); + cfg_tbl.Upsert_bry(Xowd_cfg_key_.Grp__wiki_init, Xowd_cfg_key_.Key__init__bldr_version, wiki.Props().Bldr_version()); + cfg_tbl.Upsert_bry(Xowd_cfg_key_.Grp__wiki_init, Xowd_cfg_key_.Key__init__siteinfo_misc, wiki.Props().Siteinfo_misc()); + cfg_tbl.Upsert_bry(Xowd_cfg_key_.Grp__wiki_init, Xowd_cfg_key_.Key__init__siteinfo_mainpage, wiki.Props().Siteinfo_mainpage()); + } + public static void Upsert__create(Xowe_wiki wiki) {Upsert__create(wiki.Data__core_mgr().Db__core().Tbl__cfg(), wiki.Domain_str(), wiki.Domain_str(), wiki.Props().Main_page());} + public static void Upsert__create(Db_cfg_tbl cfg_tbl, String domain, String name, byte[] main_page) { + cfg_tbl.Upsert_str(Xowd_cfg_key_.Grp__empty , Xowd_cfg_key_.Key__wiki__core__domain , domain); + cfg_tbl.Upsert_str(Xowd_cfg_key_.Grp__empty , Xowd_cfg_key_.Key__wiki__core__name , name); + cfg_tbl.Upsert_int(Xowd_cfg_key_.Grp__empty , Xowd_cfg_key_.Key__wiki__upgrade__version , gplx.xowa.addons.wikis.directorys.specials.items.bldrs.Xow_wiki_upgrade_.Upgrade_version__cur); + cfg_tbl.Upsert_bry(Xowd_cfg_key_.Grp__wiki_init, Xowd_cfg_key_.Key__init__main_page , main_page); + cfg_tbl.Upsert_str(Xowd_cfg_key_.Grp__wiki_init, Xowd_cfg_key_.Key__init__modified_latest , Datetime_now.Get().XtoStr_fmt(DateAdp_.Fmt_iso8561_date_time)); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/Xowd_core_db_props.java b/400_xowa/src/gplx/xowa/wikis/data/Xowd_core_db_props.java index a27517de8..f0a9bebee 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/Xowd_core_db_props.java +++ b/400_xowa/src/gplx/xowa/wikis/data/Xowd_core_db_props.java @@ -13,3 +13,68 @@ 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.wikis.data; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; import gplx.dbs.*; import gplx.dbs.cfgs.*; +import gplx.dbs.metas.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.wikis.dbs.*; +public class Xowd_core_db_props { + public Xowd_core_db_props(int schema, Xow_db_layout layout_text, Xow_db_layout layout_html, Xow_db_layout layout_file + , byte zip_tid_text, byte zip_tid_html, boolean hzip_enabled, boolean hzip_mode_is_b256) { + this.schema = schema; + this.layout_text = layout_text; this.layout_html = layout_html; this.layout_file = layout_file; + this.zip_tid_text = zip_tid_text; this.zip_tid_html = zip_tid_html; + this.hzip_enabled = hzip_enabled; this.hzip_mode_is_b256 = hzip_mode_is_b256; + } + public int Schema() {return schema;} private final int schema; + public boolean Schema_is_1() {return schema == 1;} + public Xow_db_layout Layout_text() {return layout_text;} private final Xow_db_layout layout_text; + public Xow_db_layout Layout_html() {return layout_html;} private final Xow_db_layout layout_html; + public Xow_db_layout Layout_file() {return layout_file;} private final Xow_db_layout layout_file; + public byte Zip_tid_text() {return zip_tid_text;} private final byte zip_tid_text; + public byte Zip_tid_html() {return zip_tid_html;} private final byte zip_tid_html; + public boolean Hzip_enabled() {return hzip_enabled;} private final boolean hzip_enabled; + public boolean Hzip_mode_is_b256() {return hzip_mode_is_b256;} private final boolean hzip_mode_is_b256; + public void Cfg_save(Db_cfg_tbl tbl) { + tbl.Conn().Txn_bgn("make__core__cfg__save"); + tbl.Insert_int (Cfg_grp, Cfg_key__schema_version , schema); + tbl.Insert_str (Cfg_grp, Cfg_key__layout_text , layout_text.Key()); + tbl.Insert_str (Cfg_grp, Cfg_key__layout_html , layout_html.Key()); + tbl.Insert_str (Cfg_grp, Cfg_key__layout_file , layout_file.Key()); + tbl.Insert_byte (Cfg_grp, Cfg_key__zip_tid_text , zip_tid_text); + tbl.Insert_byte (Cfg_grp, Cfg_key__zip_tid_html , zip_tid_html); + tbl.Insert_yn (Cfg_grp, Cfg_key__hzip_enabled , hzip_enabled); + tbl.Insert_yn (Cfg_grp, Cfg_key__hzip_mode_is_b256 , hzip_mode_is_b256); + tbl.Conn().Txn_end(); + } + public static Xowd_core_db_props Cfg_load(Db_conn conn) {return Cfg_load(conn, gplx.xowa.wikis.data.Xowd_cfg_tbl_.New(conn));} + public static Xowd_core_db_props Cfg_load(Db_conn conn, Db_cfg_tbl cfg_tbl) { + return cfg_tbl.Select_int_or(Cfg_grp, Cfg_key__schema_version, 1) == 1 + ? new Xowd_core_db_props + ( 1, Xow_db_layout.Itm_lot, Xow_db_layout.Itm_lot, Xow_db_layout.Itm_lot, cfg_tbl.Select_byte_or(Xowe_wiki.Invk_db_mgr, Xodb_mgr_sql.Invk_data_storage_format + , Io_stream_tid_.Tid__gzip), Io_stream_tid_.Tid__gzip, Bool_.Y, Bool_.N) + : Cfg_load(cfg_tbl); + } + private static Xowd_core_db_props Cfg_load(Db_cfg_tbl tbl) { + Db_cfg_hash cfg_hash = tbl.Select_as_hash(Cfg_grp); + return new Xowd_core_db_props + ( cfg_hash.Get_by(Cfg_key__schema_version).To_int() + , Xow_db_layout.Get_by_name(cfg_hash.Get_by(Cfg_key__layout_text).To_str()) + , Xow_db_layout.Get_by_name(cfg_hash.Get_by(Cfg_key__layout_html).To_str()) + , Xow_db_layout.Get_by_name(cfg_hash.Get_by(Cfg_key__layout_file).To_str()) + , cfg_hash.Get_by(Cfg_key__zip_tid_text).To_byte() + , cfg_hash.Get_by(Cfg_key__zip_tid_html).To_byte() + , cfg_hash.Get_by(Cfg_key__hzip_enabled).To_yn_or(Bool_.N) + , cfg_hash.Get_by(Cfg_key__hzip_mode_is_b256).To_yn_or(Bool_.N) + ); + } + private static final String Cfg_grp = gplx.xowa.wikis.data.Xowd_cfg_key_.Grp__wiki__core + , Cfg_key__schema_version = "schema_version" + , Cfg_key__layout_text = "layout_text" + , Cfg_key__layout_html = "layout_html" + , Cfg_key__layout_file = "layout_file" + , Cfg_key__zip_tid_text = "zip_tid_text" + , Cfg_key__zip_tid_html = "zip_tid_html" + , Cfg_key__hzip_enabled = "hzip_enabled" + , Cfg_key__hzip_mode_is_b256 = "hzip_mode_is_b256" + ; + public static final Xowd_core_db_props Test = new Xowd_core_db_props(2, Xow_db_layout.Itm_few, Xow_db_layout.Itm_few, Xow_db_layout.Itm_few, Io_stream_tid_.Tid__raw, Io_stream_tid_.Tid__raw, Bool_.Y, Bool_.Y); +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/fetchers/Xow_page_fetcher.java b/400_xowa/src/gplx/xowa/wikis/data/fetchers/Xow_page_fetcher.java index a27517de8..b55fb62fe 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/fetchers/Xow_page_fetcher.java +++ b/400_xowa/src/gplx/xowa/wikis/data/fetchers/Xow_page_fetcher.java @@ -13,3 +13,10 @@ 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.wikis.data.fetchers; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.xowa.wikis.data.tbls.*; +public interface Xow_page_fetcher { + Xow_page_fetcher Wiki_(Xowe_wiki v); + byte[] Get_by(int ns_id, byte[] ttl); + void Clear(); +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/fetchers/Xow_page_fetcher_test.java b/400_xowa/src/gplx/xowa/wikis/data/fetchers/Xow_page_fetcher_test.java index a27517de8..0873a0e7e 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/fetchers/Xow_page_fetcher_test.java +++ b/400_xowa/src/gplx/xowa/wikis/data/fetchers/Xow_page_fetcher_test.java @@ -13,3 +13,18 @@ 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.wikis.data.fetchers; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.xowa.wikis.data.tbls.*; +public class Xow_page_fetcher_test implements Xow_page_fetcher { + public Xow_page_fetcher Wiki_(Xowe_wiki v) {return this;} + public void Clear() {pages.Clear();} private Hash_adp pages = Hash_adp_.New(); + public void Add(int ns_id, byte[] ttl, byte[] text) { + Xowd_page_itm page = new Xowd_page_itm().Ns_id_(ns_id).Ttl_page_db_(ttl).Text_(text); + pages.Add(Make_key(ns_id, ttl), page); + } + public byte[] Get_by(int ns_id, byte[] ttl) { + Xowd_page_itm rv = (Xowd_page_itm)pages.Get_by(Make_key(ns_id, ttl)); + return rv == null ? null : rv.Text(); + } + String Make_key(int ns_id, byte[] ttl) {return Int_.To_str(ns_id) + "|" + String_.new_u8(ttl);} +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/fetchers/Xow_page_fetcher_wiki.java b/400_xowa/src/gplx/xowa/wikis/data/fetchers/Xow_page_fetcher_wiki.java index a27517de8..b9ff7e699 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/fetchers/Xow_page_fetcher_wiki.java +++ b/400_xowa/src/gplx/xowa/wikis/data/fetchers/Xow_page_fetcher_wiki.java @@ -13,3 +13,13 @@ 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.wikis.data.fetchers; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +public class Xow_page_fetcher_wiki implements Xow_page_fetcher { + public Xow_page_fetcher Wiki_(Xowe_wiki v) {this.wiki = v; return this;} private Xowe_wiki wiki; + public void Clear() {} + public byte[] Get_by(int ns_id, byte[] ttl_bry) { + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, ns_id, ttl_bry); + Xoae_page page = wiki.Data_mgr().Load_page_by_ttl(ttl); // go through data_mgr in case of redirects + return page.Db().Page().Exists_n() ? null : page.Db().Text().Text_bry(); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/site_stats/Xowd_site_stats_mgr.java b/400_xowa/src/gplx/xowa/wikis/data/site_stats/Xowd_site_stats_mgr.java index a27517de8..ee9cdc174 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/site_stats/Xowd_site_stats_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/data/site_stats/Xowd_site_stats_mgr.java @@ -13,3 +13,42 @@ 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.wikis.data.site_stats; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.xowa.wikis.nss.*; +public class Xowd_site_stats_mgr implements Gfo_invk { // REF.MW:https://www.mediawiki.org/wiki/Manual:Site_stats_table + private final Xow_wiki wiki; + public Xowd_site_stats_mgr(Xow_wiki wiki) {this.wiki = wiki;} + public long Num_pages() {return num_pages;} private long num_pages; // ss_total_pages; pages in entire wiki: 16,299,475 + public long Num_articles() {return num_articles;} private long num_articles; // ss_good_articles; pages in main ns w/o redirect: 5,072,469 + public long Num_views() {return num_views;} private long num_views; // ss_total_views + public long Num_edits() {return num_edits;} private long num_edits; // ss_total_edits + public long Num_users() {return num_users;} private long num_users; // ss_users + public long Num_active() {return num_active;} private long num_active; // ss_active_users + public int Num_admins() {return num_admins;} private int num_admins; // ss_admins + public int Num_files() {return num_files;} private int num_files; // ss_images + public void Load_by_db(long num_pages, long num_articles, int num_files, long num_edits, long num_views, long num_users, long num_active, int num_admins) { + this.num_pages = num_pages; + this.num_articles = num_articles; + this.num_files = num_files; + this.num_edits = num_edits; + this.num_views = num_views; + this.num_users = num_users; + this.num_active = num_active; + this.num_admins = num_admins; + } + private Object Number_of_articles_in_ns_(GfoMsg m) { + int ns_id = m.ReadInt("ns_id"); + int count = m.ReadInt("count"); + Xow_ns ns = wiki.Ns_mgr().Ids_get_or_null(ns_id); + if (ns != null) ns.Count_(count); + return this; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_number_of_pages_)) num_pages = m.ReadInt("v"); + else if (ctx.Match(k, Invk_number_of_articles_)) num_articles = m.ReadInt("v"); + else if (ctx.Match(k, Invk_number_of_files_)) num_files = m.ReadInt("v"); + else if (ctx.Match(k, Invk_number_of_articles_in_ns_)) return Number_of_articles_in_ns_(m); + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String Invk_number_of_pages_ = "number_of_pages_", Invk_number_of_articles_ = "number_of_articles_", Invk_number_of_files_ = "number_of_files_", Invk_number_of_articles_in_ns_ = "number_of_articles_in_ns_"; +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/site_stats/Xowd_site_stats_tbl.java b/400_xowa/src/gplx/xowa/wikis/data/site_stats/Xowd_site_stats_tbl.java index a27517de8..5e334f68c 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/site_stats/Xowd_site_stats_tbl.java +++ b/400_xowa/src/gplx/xowa/wikis/data/site_stats/Xowd_site_stats_tbl.java @@ -13,3 +13,46 @@ 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.wikis.data.site_stats; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.site_stats.*; +public class Xowd_site_stats_tbl implements Db_tbl { + private final String fld_row_id, fld_good_articles, fld_total_pages, fld_images; + private final Db_conn conn; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + public Xowd_site_stats_tbl(Db_conn conn, boolean schema_is_1) { + this.conn = conn; + fld_row_id = flds.Add_int_pkey("ss_row_id"); + fld_good_articles = flds.Add_long("ss_good_articles"); + fld_total_pages = flds.Add_long("ss_total_pages"); + fld_images = flds.Add_int("ss_images"); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name = TBL_NAME; + public void Create_tbl() { + conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds)); + conn.Stmt_insert(tbl_name, flds).Val_int(fld_row_id, Site_stats_row_id).Val_long(fld_good_articles, 0).Val_long(fld_total_pages, 0).Val_int(fld_images, 0).Exec_insert(); + } + public void Update(long num_articles, long num_pages, int num_files) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.site_stats: update started: num_articles=~{0} num_pages=~{1} num_files=~{2}", num_articles, num_pages, num_files); + Db_stmt stmt = conn.Stmt_update(tbl_name, String_.Ary(fld_row_id), fld_good_articles, fld_total_pages, fld_images); + stmt.Val_long(fld_good_articles, num_articles).Val_long(fld_total_pages, num_pages).Val_int(fld_images, num_files) + .Crt_int(fld_row_id, Site_stats_row_id) + .Exec_update(); + Gfo_usr_dlg_.Instance.Log_many("", "", "db.site_stats: update done"); + } + public void Select(Xowd_site_stats_mgr stats) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, fld_row_id).Crt_int(fld_row_id, Site_stats_row_id).Exec_select__rls_auto(); + try { + if (rdr.Move_next()) { + stats.Load_by_db + ( rdr.Read_long(fld_total_pages) + , rdr.Read_long(fld_good_articles) + , rdr.Read_int(fld_images) + , 0, 0, 0, 0, 0); + } + } finally {rdr.Rls();} + } + public void Rls() {} + private static final int Site_stats_row_id = 1; + + public static final String TBL_NAME = "site_stats"; + public static Xowd_site_stats_tbl Get_by_key(Db_tbl_owner owner) {return (Xowd_site_stats_tbl)owner.Tbls__get_by_key(TBL_NAME);} +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Select_in_cbk.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Select_in_cbk.java index a27517de8..cc2035790 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Select_in_cbk.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Select_in_cbk.java @@ -13,3 +13,42 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.dbs.*; +public interface Select_in_cbk { + int Hash_max(); + void Write_sql(Bry_bfr bfr, int idx); + void Read_data(Db_rdr rdr); +} +class Select_in_wkr { + private final byte[] sql_bgn; + public Select_in_wkr(byte[] sql_bgn) {this.sql_bgn = sql_bgn;} + public int Make_sql_or_null(Bry_bfr bfr, Select_in_cbk cbk, int bgn) { + // read 50 at a time + int max = cbk.Hash_max(); + int end = bgn + 50; if (end > max) end = max; + if (bgn == end) return -1; // at eos; return; + + // concat itms + bfr.Add(sql_bgn); + for (int i = bgn; i < end; ++i) { + if (i != bgn) bfr.Add_str_a7(", "); + cbk.Write_sql(bfr, i); + } + bfr.Add_byte(Byte_ascii.Paren_end); + return end; + } + public static Select_in_wkr New(Bry_bfr bfr, String tbl_name, String[] select_flds, String where_fld) { + bfr.Add_str_a7("SELECT "); + int select_flds_len = select_flds.length; + for (int i = 0; i < select_flds_len; ++i) { + String select_fld = select_flds[i]; + if (i != 0) bfr.Add_str_a7(", "); + bfr.Add_str_u8(select_fld); + } + bfr.Add_str_a7(" FROM ").Add_str_u8(tbl_name); + bfr.Add_str_a7(" WHERE ").Add_str_u8(where_fld); + bfr.Add_str_a7(" IN ("); + return new Select_in_wkr(bfr.To_bry_and_clear()); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_cat_core_tbl.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_cat_core_tbl.java index a27517de8..566f0908b 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_cat_core_tbl.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_cat_core_tbl.java @@ -13,3 +13,100 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.dbs.*; +public class Xowd_cat_core_tbl implements Db_tbl { + private final String tbl_name; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_id, fld_pages, fld_subcats, fld_files, fld_hidden, fld_link_db_id; + private final Db_conn conn; private Db_stmt stmt_insert, stmt_update, stmt_select; + private final Xowd_cat_core_tbl__in_wkr in_wkr = new Xowd_cat_core_tbl__in_wkr(); + public Db_conn Conn() {return conn;} + public Xowd_cat_core_tbl(Db_conn conn, boolean schema_is_1) { + this.conn = conn; + String fld_link_db_id_name = ""; + if (schema_is_1) {tbl_name = "category"; fld_link_db_id_name = "cat_file_idx";} + else {tbl_name = "cat_core"; fld_link_db_id_name = "cat_link_db_id";} + fld_id = flds.Add_int_pkey ("cat_id"); + fld_pages = flds.Add_int ("cat_pages"); + fld_subcats = flds.Add_int ("cat_subcats"); + fld_files = flds.Add_int ("cat_files"); + fld_hidden = flds.Add_byte ("cat_hidden"); + fld_link_db_id = flds.Add_int(fld_link_db_id_name); + in_wkr.Ctor(this, tbl_name, flds, fld_id); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Insert_bgn() {conn.Txn_bgn("schema__cat_core__insert"); stmt_insert = conn.Stmt_insert(tbl_name, flds);} + public void Insert_end() {conn.Txn_end(); stmt_insert = Db_stmt_.Rls(stmt_insert);} + public void Insert_cmd_by_batch(int id, int pages, int subcats, int files, byte hidden, int link_db_id) { + stmt_insert.Clear() + .Val_int(fld_id, id).Val_int(fld_pages, pages).Val_int(fld_subcats, subcats).Val_int(fld_files, files) + .Val_byte(fld_hidden, hidden).Val_int(fld_link_db_id, link_db_id) + .Exec_insert(); + } + public void Update_bgn() {conn.Txn_bgn("schema__cat_core__update"); stmt_update = conn.Stmt_update(tbl_name, String_.Ary(fld_id), fld_hidden);} + public void Update_end() {conn.Txn_end(); stmt_update = Db_stmt_.Rls(stmt_update);} + public void Update_by_batch(int id, byte hidden) { + stmt_update.Clear().Val_byte(fld_hidden, hidden).Crt_int(fld_id, id).Exec_update(); + } + public void Delete(int page_id) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.cat_core: delete started: db=~{0} page_id=~{1}", conn.Conn_info().Raw(), page_id); + conn.Stmt_delete(tbl_name, fld_id).Crt_int(fld_id, page_id).Exec_delete(); + Gfo_usr_dlg_.Instance.Log_many("", "", "db.cat_core: delete done"); + } + public void Delete_all() {conn.Stmt_delete(tbl_name, Dbmeta_fld_itm.Str_ary_empty).Exec_delete();} + public void Update_page_id(int old_id, int new_id) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.cat_core: update page_id started: db=~{0} old_id=~{1} new_id=~{2}", conn.Conn_info().Raw(), old_id, new_id); + conn.Stmt_update(tbl_name, String_.Ary(fld_id), fld_id).Val_int(fld_id, new_id).Crt_int(fld_id, old_id).Exec_update(); + Gfo_usr_dlg_.Instance.Log_many("", "", "db.cat_core: update page_id done"); + } + public void Update(Xowd_category_itm itm) { + conn.Stmt_update_exclude(tbl_name, flds, fld_id).Clear() + .Val_int(fld_pages, itm.Count_pages()) + .Val_int(fld_subcats, itm.Count_subcs()) + .Val_int(fld_files, itm.Count_files()) + .Val_bool_as_byte(fld_hidden, itm.Hidden()) + .Val_int(fld_link_db_id, itm.File_idx()) + .Crt_int(fld_id, itm.Id()) + .Exec_update(); + } + public Xowd_category_itm Select(int id) { + if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_id); + Db_rdr rdr = stmt_select.Clear().Crt_int(fld_id, id).Exec_select__rls_manual(); + try {return rdr.Move_next() ? Load_itm(rdr) : Xowd_category_itm.Null;} finally {rdr.Rls();} + } + public void Select_by_cat_id_in(Cancelable cancelable, Ordered_hash rv, int bgn, int end) { + in_wkr.Init(rv); + in_wkr.Select_in(cancelable, conn, bgn, end); + } + public void Select_by_cat_id_many(Select_in_cbk cbk) { + int pos = 0; + Bry_bfr bfr = Bry_bfr_.New(); + Select_in_wkr wkr = Select_in_wkr.New(bfr, tbl_name, String_.Ary(fld_id, fld_pages, fld_subcats, fld_files, fld_hidden), fld_id); + while (true) { + pos = wkr.Make_sql_or_null(bfr, cbk, pos); + if (pos == -1) break; + Db_rdr rdr = conn.Stmt_sql(bfr.To_str_and_clear()).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) + cbk.Read_data(rdr); + } finally {rdr.Rls();} + } + } + public Xowd_category_itm Load_itm(Db_rdr rdr) { + return Xowd_category_itm.load_ + ( rdr.Read_int(fld_id) + , rdr.Read_int(fld_link_db_id) + , rdr.Read_bool_by_byte(fld_hidden) + , rdr.Read_int(fld_subcats) + , rdr.Read_int(fld_files) + , rdr.Read_int(fld_pages) + ); + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + stmt_update = Db_stmt_.Rls(stmt_update); + stmt_select = Db_stmt_.Rls(stmt_select); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_cat_core_tbl__in_wkr.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_cat_core_tbl__in_wkr.java index a27517de8..ab39c2760 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_cat_core_tbl__in_wkr.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_cat_core_tbl__in_wkr.java @@ -13,3 +13,31 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.dbs.*; import gplx.dbs.utls.*; +class Xowd_cat_core_tbl__in_wkr extends Db_in_wkr__base { + private Xowd_cat_core_tbl tbl; + private String tbl_name, fld_cat_id; private Dbmeta_fld_list flds; private Ordered_hash hash; + public void Ctor(Xowd_cat_core_tbl tbl, String tbl_name, Dbmeta_fld_list flds, String fld_cat_id) { + this.tbl = tbl; this.tbl_name = tbl_name; this.flds = flds; this.fld_cat_id = fld_cat_id; + } + public void Init(Ordered_hash hash) {this.hash = hash;} + @Override protected Db_qry Make_qry(int bgn, int end) { + Object[] part_ary = In_ary(end - bgn); + return Db_qry_.select_cols_(tbl_name, Db_crt_.New_in(fld_cat_id, part_ary), flds.To_str_ary()); + } + @Override protected void Fill_stmt(Db_stmt stmt, int bgn, int end) { + for (int i = bgn; i < end; i++) { + Xowd_page_itm itm = (Xowd_page_itm)hash.Get_at(i); + stmt.Crt_int(fld_cat_id, itm.Id()); + } + } + @Override protected void Read_data(Cancelable cancelable, Db_rdr rdr) { + while (rdr.Move_next()) { + if (cancelable.Canceled()) return; + Xowd_category_itm ctg_data = tbl.Load_itm(rdr); + Xowd_page_itm page = (Xowd_page_itm)hash.Get_by(ctg_data.Id_val()); + page.Xtn_(ctg_data); + } + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_cat_link_tbl.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_cat_link_tbl.java index a27517de8..2d3465a4e 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_cat_link_tbl.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_cat_link_tbl.java @@ -13,3 +13,76 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.xowa.addons.wikis.ctgs.*; +public class Xowd_cat_link_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_from, fld_to_id, fld_sortkey, fld_timestamp, fld_type_id; + private Db_stmt stmt_insert, stmt_select_in; + public Xowd_cat_link_tbl(Db_conn conn, boolean schema_is_1) { + this.conn = conn; + this.tbl_name = schema_is_1 ? "categorylinks" : "cat_link"; + fld_from = flds.Add_int ("cl_from"); // page_id + fld_to_id = flds.Add_int ("cl_to_id"); // cat_id + fld_type_id = flds.Add_byte ("cl_type_id"); + fld_sortkey = flds.Add_str ("cl_sortkey", 230); + fld_timestamp = flds.Add_str ("cl_timestamp", 14); + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Create_idx() { + conn.Meta_idx_create(Xoa_app_.Usr_dlg() + , Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "main", fld_to_id, fld_type_id, fld_sortkey, fld_from) + , Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "from", fld_from) + ); + } + public void Insert_bgn() {conn.Txn_bgn("schema__cat_link__insert"); stmt_insert = conn.Stmt_insert(tbl_name, flds);} + public void Insert_end() {conn.Txn_end(); stmt_insert = Db_stmt_.Rls(stmt_insert);} + public void Insert_cmd_by_batch(int page_id, int ctg_page_id, byte ctg_tid, byte[] sortkey, int timestamp) { + stmt_insert.Clear() + .Val_int(fld_from , page_id) + .Val_int(fld_to_id , ctg_page_id) + .Val_byte(fld_type_id , ctg_tid) + .Val_bry_as_str(fld_sortkey , sortkey) + .Val_int(fld_timestamp , timestamp) + .Exec_insert(); + } + public void Delete_all() {conn.Stmt_delete(tbl_name, Dbmeta_fld_itm.Str_ary_empty).Exec_delete();} + public int Select_by_type(List_adp list, int cat_page_id, byte arg_tid, byte[] arg_sortkey, boolean arg_is_from, int limit) { + String arg_sortkey_str = arg_sortkey == null ? "" : String_.new_u8(arg_sortkey); + gplx.core.criterias.Criteria comp_crt = !arg_is_from + ? Db_crt_.New_mte(fld_sortkey, arg_sortkey_str) // from: sortkey >= 'val' + : Db_crt_.New_lte(fld_sortkey, arg_sortkey_str); // until: sortkey <= 'val' + Db_qry__select_cmd qry = Db_qry_.select_().Cols_(fld_from, fld_sortkey).From_(tbl_name) + .Where_(gplx.core.criterias.Criteria_.And_many(Db_crt_.New_eq(fld_to_id, -1), Db_crt_.New_eq(fld_type_id, arg_tid), comp_crt)) + .Order_(fld_sortkey, !arg_is_from) + .Limit_(limit + 1); // + 1 to get last_plus_one for next page / previous page + Db_rdr rdr = conn.Stmt_new(qry).Crt_int(fld_to_id, cat_page_id).Crt_byte(fld_type_id, arg_tid).Crt_str(fld_sortkey, arg_sortkey_str).Exec_select__rls_auto(); + int count = 0; + try { + while (rdr.Move_next()) { + int itm_page_id = rdr.Read_int(fld_from); + byte[] itm_sortkey = rdr.Read_bry_by_str(fld_sortkey); + Xowd_page_itm itm = new Xowd_page_itm().Id_(itm_page_id).Xtn_(new Xoctg_page_xtn(arg_tid, itm_sortkey)); + list.Add(itm); + ++count; + } + } finally {rdr.Rls();} + list.Sort_by(Xowd_page_itm_sorter.Ctg_tid_sortkey_asc); + return count; + } + public void Select_in(List_adp rv, int cat_id) { + if (stmt_select_in == null) stmt_select_in = conn.Stmt_select(tbl_name, flds, fld_to_id); + Db_rdr rdr = stmt_select_in.Clear().Crt_int(fld_to_id, cat_id).Exec_select__rls_manual(); + try { + while (rdr.Move_next()) + rv.Add(new Xowd_page_itm().Id_(rdr.Read_int(fld_from))); + } finally {rdr.Rls();} + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + stmt_select_in = Db_stmt_.Rls(stmt_select_in); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_category_itm.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_category_itm.java index a27517de8..1121bd788 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_category_itm.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_category_itm.java @@ -13,3 +13,39 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.core.primitives.*; import gplx.xowa.addons.wikis.ctgs.*; +import gplx.xowa.wikis.nss.*; +public class Xowd_category_itm { + public int Id() {return id;} private int id; + public Int_obj_val Id_val() {if (id_val == null) id_val = new Int_obj_val(id); return id_val;} Int_obj_val id_val; + public int File_idx() {return file_idx;} private int file_idx; + public boolean Hidden() {return hidden;} private boolean hidden; + public int Count_all() {return count_subcs + count_files + count_pages;} + public int Count_subcs() {return count_subcs;} private int count_subcs; + public int Count_files() {return count_files;} private int count_files; + public int Count_pages() {return count_pages;} private int count_pages; + public int Count_by_tid(byte tid) { + switch (tid) { + case Xoa_ctg_mgr.Tid__subc: return count_subcs; + case Xoa_ctg_mgr.Tid__page: return count_pages; + case Xoa_ctg_mgr.Tid__file: return count_files; + default: throw Err_.new_unhandled(tid); + } + } + public void Adjust(int ns, int val) { + switch (ns) { + case Xow_ns_.Tid__category: count_subcs += val; break; + case Xow_ns_.Tid__file : count_files += val; break; + default : count_pages += val; break; + } + } + + public static Xowd_category_itm load_(int id, int file_idx, boolean hidden, int count_subcs, int count_files, int count_pages) { + Xowd_category_itm rv = new Xowd_category_itm(); + rv.id = id; rv.file_idx = file_idx; rv.hidden = hidden; + rv.count_subcs = count_subcs; rv.count_files = count_files; rv.count_pages = count_pages; + return rv; + } + public static final Xowd_category_itm Null = new Xowd_category_itm(); Xowd_category_itm() {} +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_itm.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_itm.java index a27517de8..6bb5f466f 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_itm.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_itm.java @@ -13,3 +13,146 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.core.primitives.*; +import gplx.xowa.wikis.nss.*; +public class Xowd_page_itm { + public Xowd_page_itm() {this.Clear();} + public int Id() {return id;} public Xowd_page_itm Id_(int v) {id = v; id_val = null; return this;} private int id; + public Int_obj_val Id_val() {if (id_val == null) id_val = new Int_obj_val(id); return id_val;} private Int_obj_val id_val; + public int Ns_id() {return ns_id;} public Xowd_page_itm Ns_id_(int v) {ns_id = v; return this;} private int ns_id; + public byte[] Ttl_page_db() {return ttl_page_db;} public Xowd_page_itm Ttl_page_db_(byte[] v) {ttl_page_db = v; return this;} private byte[] ttl_page_db; // EX: Category1 + public byte[] Ttl_full_db() {return ttl_full_db;} private byte[] ttl_full_db; // EX: Category:Category1 + public boolean Redirected() {return redirected;} public Xowd_page_itm Redirected_(boolean v) {redirected = v; return this;} private boolean redirected; + public int Text_len() {return text_len;} public Xowd_page_itm Text_len_(int v) {text_len = v; return this;} private int text_len; + public int Text_db_id() {return text_db_id;} public Xowd_page_itm Text_db_id_(int v) {text_db_id = v; return this;} private int text_db_id; + public int Html_db_id() {return html_db_id;} private int html_db_id; + public int Cat_db_id() {return cat_db_id;} private int cat_db_id = -1; // NOTE: cannot be 0, else catpage loader will try to load cat_links from db.id=0 + public byte[] Text() {return text;} public Xowd_page_itm Text_(byte[] v) {text = v; if (v != null) text_len = v.length; return this;} private byte[] text; + public int Random_int() {return random_int;} private int random_int; + public int Redirect_id() {return redirect_id;} private int redirect_id; + public DateAdp Modified_on() {return modified_on;} public Xowd_page_itm Modified_on_(DateAdp v) {modified_on = v; return this;} private DateAdp modified_on; + public boolean Exists() {return exists;} private boolean exists; + public int Score() {return score;} private int score; + public Xow_ns Ns() {return ns;} private Xow_ns ns; + public Object Xtn() {return xtn;} public Xowd_page_itm Xtn_(Object v) {this.xtn = v; return this;} private Object xtn; + public int Tdb_row_idx() {return tdb_row_idx;} public void Tdb_row_idx_(int v) {tdb_row_idx = v;} private int tdb_row_idx; + public int Rank() {return text_len;} + public Xowd_page_itm Init(int id, byte[] ttl_page_db, boolean redirected, int text_len, int text_db_id, int tdb_row_idx) { + this.id = id; this.ttl_page_db = ttl_page_db; this.redirected = redirected; + this.text_len = text_len; this.text_db_id = text_db_id; this.tdb_row_idx = tdb_row_idx; + id_val = null; + return this; + } + public void Init_by_load__idx(int id, int ns_id, byte[] ttl_page_db, int text_len) { + this.exists = true; // COMMENT: DATE:2016-08-28 + this.id = id; + this.id_val = null; + this.ns_id = ns_id; + this.ttl_page_db = ttl_page_db; + this.text_len = text_len; + } + public void Init_by_load__all(int id, int ns_id, byte[] ttl_page_db, DateAdp modified_on, boolean redirected, int text_len, int random_int, int text_db_id, int html_db_id, int redirect_id, int score, int cat_db_id) { + // same as Init_by_load__idx; COMMENT: DATE:2016-08-28 + this.exists = true; + this.id = id; + this.id_val = null; + this.ns_id = ns_id; + this.ttl_page_db = ttl_page_db; + this.text_len = text_len; + + // other props + this.redirected = redirected; + this.text_db_id = text_db_id; + this.modified_on = modified_on; + this.random_int = random_int; + this.html_db_id = html_db_id; + this.redirect_id = redirect_id; + this.score = score; + this.cat_db_id = cat_db_id; + } + public void Init_by_tdb(int id, int text_db_id, int tdb_row_idx, boolean redirected, int text_len, int ns_id, byte[] ttl_page_db) { + // same as Init_by_load__idx; COMMENT: DATE:2016-08-28 + this.exists = true; + this.id = id; + this.id_val = null; + this.ns_id = ns_id; + this.ttl_page_db = ttl_page_db; + this.text_len = text_len; + + // other props + this.redirected = redirected; + this.text_db_id = text_db_id; + + this.tdb_row_idx = tdb_row_idx; + } + public Xowd_page_itm Ttl_(Xow_ns ns, byte[] ttl_page_db) { + this.ns = ns; + ns_id = ns.Id(); + this.ttl_page_db = ttl_page_db; + this.ttl_full_db = ns.Gen_ttl(ttl_page_db); + return this; + } + public Xowd_page_itm Ttl_(Xoa_ttl ttl) { + ttl_full_db = ttl.Full_txt_w_ttl_case(); + ttl_page_db = ttl.Page_db(); + ns = ttl.Ns(); + ns_id = ns.Id(); + return this; + } + public Xowd_page_itm Ttl_(byte[] v, Xow_ns_mgr ns_mgr) { + ttl_full_db = v; + Object o = ns_mgr.Names_get_w_colon(v, 0, v.length); + if (o == null) { + ns = ns_mgr.Ns_main(); + ttl_page_db = v; + } + else { + ns = (Xow_ns)o; + ttl_page_db = Bry_.Mid(v, ns.Name_ui_w_colon().length, v.length); // EX: "Template:A" -> "Template:" + "A" + } + ns_id = ns.Id(); + return this; + } + public Xowd_page_itm Clear() { + id = Id_null; text_len = 0; // text_len should be 0 b/c text defaults to 0; + text_db_id = tdb_row_idx = 0; // default to 0, b/c some tests do not set and will fail at -1 + ns_id = Int_.Min_value; + ttl_full_db = ttl_page_db = null; text = Bry_.Empty; // default to Ary_empty for entries that have + ns = null; + redirected = exists = false; + modified_on = DateAdp_.MinValue; + id_val = null; + html_db_id = cat_db_id = -1; + redirect_id = -1; + return this; + } + public void Copy(Xowd_page_itm orig) { + this.id = orig.id; + this.text_len = orig.text_len; + this.text_db_id = orig.text_db_id; + this.tdb_row_idx = orig.tdb_row_idx; + this.ns_id = orig.ns_id; + this.ttl_full_db = orig.ttl_full_db; + this.ttl_page_db = orig.ttl_page_db; + this.text = orig.text; + this.ns = orig.ns; + this.redirected = orig.redirected; + this.exists = orig.exists; + this.modified_on = orig.modified_on; + this.id_val = null; + this.html_db_id = orig.html_db_id; + this.cat_db_id = orig.cat_db_id; + } + public void Srl_save(Bry_bfr bfr) {gplx.xowa.wikis.tdbs.Xotdb_page_itm_.Txt_id_save(bfr, this);} + public static final int Id_null = -1, Modified_on_null_int = 0, Redirect_id_null = -1; + public static final Xowd_page_itm[] Ary_empty = new Xowd_page_itm[0]; + public static final Xowd_page_itm Null = null; + public static Xowd_page_itm new_tmp() {return new Xowd_page_itm();} + public static Xowd_page_itm new_srch(int id, int text_len) {return new Xowd_page_itm().Id_(id).Text_len_(text_len);} + + public static final int Db_row_size_fixed = + (8 * 4) // page_id, page_namespace, page_random_int, page_text_db_id, page_html_db_id, page_redirect_id, page_score, page_is_redirect (assume byte saved as int in SQLITE) + + 14 // page_touched + ; +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_itm_sorter.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_itm_sorter.java index a27517de8..4c5e9aa13 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_itm_sorter.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_itm_sorter.java @@ -13,3 +13,39 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.core.lists.*; /*ComparerAble*/ import gplx.xowa.wikis.data.tbls.*; +public class Xowd_page_itm_sorter implements ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + Xowd_page_itm lhs = (Xowd_page_itm)lhsObj, rhs = (Xowd_page_itm)rhsObj; + return order * Compare_rows(compareType, lhs, rhs); + } + private static int Compare_rows(byte compareType, Xowd_page_itm lhs, Xowd_page_itm rhs) { + switch (compareType) { + case Tid_ns_ttl: { + int rv = Int_.Compare(lhs.Ns_id(), rhs.Ns_id()); + return rv == CompareAble_.Same ? Bry_.Compare(lhs.Ttl_page_db(), rhs.Ttl_page_db()) : rv; + } + case Tid_itm_len: return Int_.Compare(lhs.Text_len(), rhs.Text_len()); + case Tid_id: return Int_.Compare(lhs.Id(), rhs.Id()); + case Tid_ttl: return Bry_.Compare(lhs.Ttl_page_db(), rhs.Ttl_page_db()); + case Tid_ctg_tid_sortkey: + gplx.xowa.addons.wikis.ctgs.Xoctg_page_xtn lhs_xtn = (gplx.xowa.addons.wikis.ctgs.Xoctg_page_xtn)lhs.Xtn(); + gplx.xowa.addons.wikis.ctgs.Xoctg_page_xtn rhs_xtn = (gplx.xowa.addons.wikis.ctgs.Xoctg_page_xtn)rhs.Xtn(); + if (lhs_xtn == null || rhs_xtn == null) return CompareAble_.Same; + int tid_comparable = Byte_.Compare(lhs_xtn.Tid(), rhs_xtn.Tid()); + if (tid_comparable != CompareAble_.Same) return tid_comparable; + return Bry_.Compare(lhs_xtn.Sortkey(), rhs_xtn.Sortkey()); + default: throw Err_.new_unhandled(compareType); + } + } + Xowd_page_itm_sorter(byte compareType, int order) {this.compareType = compareType; this.order = order;} + byte compareType; int order; + static final byte Tid_ns_ttl = 0, Tid_itm_len = 2, Tid_id = 3, Tid_ttl = 4, Tid_ctg_tid_sortkey = 5; + static final int Asc = 1, Dsc = -1; + public static final Xowd_page_itm_sorter TitleAsc = new Xowd_page_itm_sorter(Tid_ttl , Asc); + public static final Xowd_page_itm_sorter EnyLenDsc = new Xowd_page_itm_sorter(Tid_itm_len , Dsc); + public static final Xowd_page_itm_sorter IdAsc = new Xowd_page_itm_sorter(Tid_id , Asc); + public static final Xowd_page_itm_sorter Ns_id_TtlAsc = new Xowd_page_itm_sorter(Tid_ns_ttl , Asc); + public static final Xowd_page_itm_sorter Ctg_tid_sortkey_asc = new Xowd_page_itm_sorter(Tid_ctg_tid_sortkey , Asc); +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_itm_tst.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_itm_tst.java index a27517de8..d51907c24 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_itm_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_itm_tst.java @@ -13,3 +13,28 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import org.junit.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.wikis.nss.*; +public class Xowd_page_itm_tst { + @Before public void init() {fxt.Init();} private Xowd_page_itm_fxt fxt = new Xowd_page_itm_fxt(); + @Test public void Ttl_() { + fxt.Test_ttl_("User_talk:A", Xow_ns_.Tid__user_talk, "A"); + fxt.Test_ttl_("User talk:A", Xow_ns_.Tid__user_talk, "A"); + } +} +class Xowd_page_itm_fxt { + public void Init() { + if (ns_mgr == null) { + ns_mgr = new Xow_ns_mgr(gplx.xowa.langs.cases.Xol_case_mgr_.A7()); + ns_mgr.Add_new(Xow_ns_.Tid__main, ""); + ns_mgr.Add_new(Xow_ns_.Tid__user_talk, "User talk"); + ns_mgr.Init_w_defaults(); + tmp_page = new Xowd_page_itm(); + } + } private Xow_ns_mgr ns_mgr; Xowd_page_itm tmp_page; + public void Test_ttl_(String ttl, int expd_ns, String expd_ttl) { + tmp_page.Ttl_(Bry_.new_a7(ttl), ns_mgr); + Tfds.Eq(expd_ns, tmp_page.Ns_id()); + Tfds.Eq(expd_ttl, String_.new_a7(tmp_page.Ttl_page_db())); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl.java index a27517de8..f132d8c01 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl.java @@ -13,3 +13,400 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.core.primitives.*; import gplx.core.criterias.*; +import gplx.dbs.*; import gplx.xowa.*; import gplx.xowa.wikis.dbs.*; import gplx.dbs.qrys.*; +import gplx.xowa.wikis.nss.*; +public class Xowd_page_tbl implements Db_tbl { + private final Object thread_lock = new Object(); + public final boolean schema_is_1; + private String fld_id, fld_ns, fld_title, fld_is_redirect, fld_touched, fld_len, fld_random_int, fld_score, fld_text_db_id, fld_html_db_id, fld_redirect_id, fld_cat_db_id; + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private Db_stmt stmt_select_all_by_ttl, stmt_select_all_by_id, stmt_select_id_by_ttl, stmt_insert; + private final String[] flds_select_all, flds_select_idx; + public Xowd_page_tbl(Db_conn conn, boolean schema_is_1) { + this.conn = conn; this.schema_is_1 = schema_is_1; + String fld_text_db_id_name = ""; + String fld_cat_db_id_name = Dbmeta_fld_itm.Key_null; + if (schema_is_1) {fld_text_db_id_name = "page_file_idx";} + else { + fld_text_db_id_name = "page_text_db_id"; + if (conn.Meta_fld_exists(tbl_name, "page_cat_db_id")) + fld_cat_db_id_name = "page_cat_db_id"; + } + fld_id = flds.Add_int_pkey("page_id"); // int(10); unsigned -- MW:same + fld_ns = flds.Add_int("page_namespace"); // int(11); -- MW:same + fld_title = flds.Add_str("page_title", 255); // varbinary(255); -- MW:blob + fld_is_redirect = flds.Add_int("page_is_redirect"); // tinyint(3); -- MW:same + fld_touched = flds.Add_str("page_touched", 14); // binary(14); -- MW:blob; NOTE: should be revision!rev_timestamp, but needs extra join + fld_len = flds.Add_int("page_len"); // int(10); unsigned -- MW:same except NULL REF: WikiPage.php!updateRevisionOn;" + fld_random_int = flds.Add_int("page_random_int"); // MW:XOWA + fld_text_db_id = flds.Add_int(fld_text_db_id_name); // MW:XOWA + fld_html_db_id = flds.Add_int_dflt("page_html_db_id", -1); // MW:XOWA + fld_redirect_id = flds.Add_int_dflt("page_redirect_id", -1); // MW:XOWA + fld_score = flds.Add_int_dflt(Fld__page_score__key, -1); // MW:XOWA + if (fld_cat_db_id_name != Dbmeta_fld_itm.Key_null) + fld_cat_db_id = flds.Add_int_dflt(fld_cat_db_id_name, -1);// MW:XOWA + flds_select_all = String_.Ary_wo_null(fld_id, fld_ns, fld_title, fld_touched, fld_is_redirect, fld_len, fld_random_int, fld_text_db_id, fld_html_db_id, fld_redirect_id, fld_score, fld_cat_db_id); + flds_select_idx = String_.Ary_wo_null(fld_ns, fld_title, fld_id, fld_len, fld_score); + conn.Rls_reg(this); + } + public Db_conn Conn() {return conn;} private final Db_conn conn; + public String Tbl_name() {return tbl_name;} private final String tbl_name = TBL_NAME; + public Dbmeta_fld_list Flds__all() {return flds;} + public String Fld_page_id() {return fld_id;} + public String Fld_page_ns() {return fld_ns;} + public String Fld_page_title() {return fld_title;} + public String Fld_page_len() {return fld_len;} + public String Fld_page_score() {return fld_score;} public static final String Fld__page_score__key = "page_score"; + public String Fld_text_db_id() {return fld_text_db_id;} + public String Fld_html_db_id() {return fld_html_db_id;} + public String Fld_is_redirect() {return fld_is_redirect;} + public String Fld_random_int() {return fld_random_int;} + public String Fld_modified_on() {return fld_touched;} + public String Fld_redirect_id() {return fld_redirect_id;} + public String[] Flds_select_idx() {return flds_select_idx;} + public String[] Flds_select_all() {return flds_select_all;} + public void Flds__assert() { + conn.Meta_fld_assert(tbl_name, this.Fld_html_db_id() , Dbmeta_fld_tid.Itm__int, -1); + conn.Meta_fld_assert(tbl_name, this.Fld_redirect_id() , Dbmeta_fld_tid.Itm__int, -1); + conn.Meta_fld_assert(tbl_name, Fld__page_score__key , Dbmeta_fld_tid.Itm__int, -1); + } + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds.To_fld_ary()));} + public void Insert(int page_id, int ns_id, byte[] ttl_wo_ns, boolean page_is_redirect, DateAdp modified_on, int page_len, int random_int, int text_db_id, int html_db_id) { + this.Insert_bgn(); + this.Insert_cmd_by_batch(page_id, ns_id, ttl_wo_ns, page_is_redirect, modified_on, page_len, random_int, text_db_id, html_db_id, -1); + this.Insert_end(); + } + public void Insert_bgn() {conn.Txn_bgn("page__insert_bulk"); stmt_insert = conn.Stmt_insert(tbl_name, flds);} + public void Insert_end() {conn.Txn_end(); stmt_insert = Db_stmt_.Rls(stmt_insert);} + public void Insert_cmd_by_batch + (int page_id, int ns_id, byte[] ttl_wo_ns, boolean page_is_redirect, DateAdp modified_on, int page_len, int random_int + , int text_db_id, int html_db_id, int cat_db_id) { + stmt_insert.Clear() + .Val_int(fld_id, page_id) + .Val_int(fld_ns, ns_id) + .Val_bry_as_str(fld_title, ttl_wo_ns) + .Val_bool_as_byte(fld_is_redirect, page_is_redirect) + .Val_str(fld_touched, modified_on.XtoStr_fmt(Page_touched_fmt)) + .Val_int(fld_len, page_len) + .Val_int(fld_random_int, random_int) + .Val_int(fld_text_db_id, text_db_id) + .Val_int(fld_html_db_id, html_db_id) + .Val_int(fld_redirect_id, -1) + .Val_int(fld_score, -1) + .Val_int(fld_cat_db_id, cat_db_id) + .Exec_insert(); + } + public void Insert_by_itm(Db_stmt stmt, Xowd_page_itm itm, int html_db_id) { + stmt.Clear() + .Val_int (fld_id , itm.Id()) + .Val_int (fld_ns , itm.Ns_id()) + .Val_bry_as_str (fld_title , itm.Ttl_page_db()) + .Val_bool_as_byte (fld_is_redirect , itm.Redirected()) + .Val_str (fld_touched , itm.Modified_on().XtoStr_fmt(Xowd_page_tbl.Page_touched_fmt)) + .Val_int (fld_len , itm.Text_len()) + .Val_int (fld_random_int , itm.Random_int()) + .Val_int (fld_text_db_id , itm.Text_db_id()) + .Val_int (fld_html_db_id , html_db_id) + .Val_int (fld_redirect_id , itm.Redirect_id()) + .Val_int (fld_score , itm.Score()) + .Val_int (fld_cat_db_id , -1) + .Exec_insert(); + } + public Xowd_page_itm Select_by_ttl_as_itm_or_null(Xoa_ttl ttl) { + Xowd_page_itm rv = new Xowd_page_itm(); + return Select_by_ttl(rv, ttl) ? rv : null; + } + public boolean Select_by_ttl(Xowd_page_itm rv, Xoa_ttl ttl) {return Select_by_ttl(rv, ttl.Ns(), ttl.Page_db());} + public boolean Select_by_ttl(Xowd_page_itm rv, Xow_ns ns, byte[] ttl) { + if (stmt_select_all_by_ttl == null) stmt_select_all_by_ttl = conn.Stmt_select(tbl_name, flds, String_.Ary(fld_ns, fld_title)); + synchronized (thread_lock) { // LOCK:stmt-rls; DATE:2016-07-06 + Db_rdr rdr = stmt_select_all_by_ttl.Clear().Crt_int(fld_ns, ns.Id()).Crt_bry_as_str(fld_title, ttl).Exec_select__rls_manual(); + try { + if (rdr.Move_next()) { + Read_page__all(rv, rdr); + return true; + } + } + finally {rdr.Rls();} + return false; + } + } + public Xowd_page_itm Select_by_id_or_null(int page_id) { + Xowd_page_itm rv = new Xowd_page_itm(); + boolean exists = Select_by_id(rv, page_id); + return exists ? rv : null; + } + public boolean Select_by_id(Xowd_page_itm rv, int page_id) { + if (stmt_select_all_by_id == null) stmt_select_all_by_id = conn.Stmt_select(tbl_name, flds_select_all, fld_id); + Db_rdr rdr = stmt_select_all_by_id.Clear().Crt_int(fld_id, page_id).Exec_select__rls_manual(); + try { + if (rdr.Move_next()) { + Read_page__all(rv, rdr); + return true; + } + } + finally {rdr.Rls();} + return false; + } + public Db_rdr Select_all__id__ttl() { + Db_qry__select_cmd qry = new Db_qry__select_cmd().From_(tbl_name).Cols_(fld_id, fld_title).Order_asc_(fld_id); + return conn.Stmt_new(qry).Exec_select__rls_auto(); + } + public int Select_id(int ns_id, byte[] ttl) { + if (stmt_select_id_by_ttl == null) stmt_select_id_by_ttl = conn.Stmt_select(tbl_name, flds_select_all, fld_ns, fld_title); + Db_rdr rdr = stmt_select_id_by_ttl.Clear().Crt_int(fld_ns, ns_id).Crt_bry_as_str(fld_title, ttl).Exec_select__rls_manual(); + try { + return rdr.Move_next() ? rdr.Read_int(fld_id) : Xowd_page_itm.Id_null; + } finally {rdr.Rls();} + } + public void Select_in__id(Select_in_cbk cbk) { + int pos = 0; + Bry_bfr bfr = Bry_bfr_.New(); + Select_in_wkr wkr = Select_in_wkr.New(bfr, tbl_name, Flds_select_all(), fld_id); + while (true) { + pos = wkr.Make_sql_or_null(bfr, cbk, pos); + if (pos == -1) break; + Db_rdr rdr = conn.Stmt_sql(bfr.To_str_and_clear()).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) + cbk.Read_data(rdr); + } finally {rdr.Rls();} + } + } + public void Select_in__ttl(Cancelable cancelable, Ordered_hash rv, int ns_id, int bgn, int end) { + Xowd_page_tbl__ttl wkr = new Xowd_page_tbl__ttl(); + wkr.Ctor(this, tbl_name, fld_title); + wkr.Init(rv, ns_id); + wkr.Select_in(cancelable, conn, bgn, end); + } + public void Select_in__ns_ttl(Cancelable cancelable, Ordered_hash rv, Xow_ns_mgr ns_mgr, boolean fill_idx_fields_only, int bgn, int end) { + Xowd_page_tbl__ttl_ns wkr = new Xowd_page_tbl__ttl_ns(); + wkr.Fill_idx_fields_only_(fill_idx_fields_only); + wkr.Ctor(this, tbl_name, fld_title); + wkr.Init(this, ns_mgr, rv); + wkr.Select_in(cancelable, conn, bgn, end); + } + public boolean Select_in__id(Cancelable cancelable, boolean show_progress, List_adp rv) {return Select_in__id(cancelable, false, show_progress, rv, 0, rv.Count());} + public boolean Select_in__id(Cancelable cancelable, boolean skip_table_read, boolean show_progress, List_adp rv, int bgn, int end) { + Xowd_page_itm[] page_ary = (Xowd_page_itm[])rv.To_ary(Xowd_page_itm.class); + int len = page_ary.length; if (len == 0) return false; + Ordered_hash hash = Ordered_hash_.New(); + for (int i = 0; i < len; i++) { + if (cancelable.Canceled()) return false; + Xowd_page_itm p = page_ary[i]; + if (!hash.Has(p.Id_val())) // NOTE: must check if file already exists b/c dynamicPageList currently allows dupes; DATE:2013-07-22 + hash.Add(p.Id_val(), p); + } + hash.Sort_by(Xowd_page_itm_sorter.IdAsc); // sort by ID to reduce disk thrashing; DATE:2015-03-31 + Xowd_page_tbl__id wkr = new Xowd_page_tbl__id(rv, hash, show_progress); + wkr.Ctor(this, tbl_name, fld_id); + wkr.Select_in(cancelable, conn, bgn, end); + return true; + } + public byte[] Select_random(Xow_ns ns) {// ns should be ns_main + int random_int = RandomAdp_.new_().Next(ns.Count()); + Db_rdr rdr = conn.Stmt_select(tbl_name, String_.Ary(fld_title), fld_random_int, fld_ns) + .Crt_int(fld_random_int, random_int).Crt_int(fld_ns, ns.Id()) + .Exec_select__rls_auto(); + try {return rdr.Move_next() ? rdr.Read_bry_by_str(fld_title) : null;} + finally {rdr.Rls();} + } + public void Select_by_search(Cancelable cancelable, List_adp rv, byte[] search, int results_max) { + if (Bry_.Len_eq_0(search)) return; // do not allow empty search + Criteria crt = Criteria_.And_many(Db_crt_.New_eq(fld_ns, Xow_ns_.Tid__main), Db_crt_.New_like(fld_title, "")); + Db_qry__select_cmd qry = Db_qry_.select_().From_(tbl_name).Cols_(fld_id, fld_len, fld_ns, fld_title).Where_(crt); // NOTE: use fields from main index only + search = Bry_.Replace(search, Byte_ascii.Star, Byte_ascii.Percent); + Db_rdr rdr = conn.Stmt_new(qry).Clear().Crt_int(fld_ns, Xow_ns_.Tid__main).Val_bry_as_str(fld_title, search).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + if (cancelable.Canceled()) return; + Xowd_page_itm page = new Xowd_page_itm(); + this.Read_page__idx(page, rdr); + rv.Add(page); + } + } finally {rdr.Rls();} + } + public void Select_for_search_suggest(Cancelable cancelable, List_adp rslt_list, Xow_ns ns, byte[] key, int max_results, int min_page_len, int browse_len, boolean include_redirects, boolean fetch_prv_item) { + String search_bgn = String_.new_u8(key); + String search_end = String_.new_u8(gplx.core.intls.Utf8_.Increment_char_at_last_pos(key)); + String sql = String_.Format + ( "SELECT {0}, {1}, {2}, {3} FROM {4} INDEXED BY {4}__title WHERE {1} = {5} AND {2} BETWEEN '{6}' AND '{7}' ORDER BY {3} DESC LIMIT {8};" + , fld_id, fld_ns, fld_title, fld_len + , tbl_name + , Int_.To_str(ns.Id()), search_bgn, search_end, Int_.To_str(max_results) + ); + Db_qry qry = Db_qry_sql.rdr_(sql); + Db_rdr rdr = conn.Stmt_new(qry).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + if (cancelable.Canceled()) return; + Xowd_page_itm page = new Xowd_page_itm(); + Read_page__idx(page, rdr); + rslt_list.Add(page); + } + rslt_list.Sort_by(Xowd_page_itm_sorter.TitleAsc); + } + finally {rdr.Rls();} + } + public int Select_count_all() {return conn.Exec_select_count_as_int(tbl_name, -1);} + private Db_rdr Load_ttls_starting_with_rdr(int ns_id, byte[] ttl_frag, boolean include_redirects, int max_results, int min_page_len, int browse_len, boolean fwd, boolean search_suggest) { + String ttl_frag_str = String_.new_u8(ttl_frag); + Criteria crt_ttl = fwd ? Db_crt_.New_mte(fld_title, ttl_frag_str) : Db_crt_.New_lt(fld_title, ttl_frag_str); + Criteria crt = Criteria_.And_many(Db_crt_.New_eq(fld_ns, ns_id), crt_ttl, Db_crt_.New_mte(fld_len, min_page_len)); + if (!include_redirects) + crt = Criteria_.And(crt, Db_crt_.New_eq(fld_is_redirect, Byte_.Zero)); + String[] cols = search_suggest + ? flds_select_idx + : flds_select_all + ; + int limit = fwd ? max_results + 1 : max_results; // + 1 to get next item + Db_qry__select_cmd qry = Db_qry_.select_cols_(tbl_name, crt, cols).Limit_(limit).Order_(fld_title, fwd); + Db_stmt stmt = conn.Stmt_new(qry).Crt_int(fld_ns, ns_id).Crt_str(fld_title, ttl_frag_str).Crt_int(fld_len, min_page_len); + if (!include_redirects) + stmt.Crt_bool_as_byte(fld_is_redirect, include_redirects); + return stmt.Exec_select__rls_auto(); + } + public void Select_for_special_all_pages(Cancelable cancelable, List_adp rslt_list, Xowd_page_itm rslt_nxt, Xowd_page_itm rslt_prv, Int_obj_ref rslt_count, Xow_ns ns, byte[] key, int max_results, int min_page_len, int browse_len, boolean include_redirects, boolean fetch_prv_item) { + Xowd_page_itm nxt_itm = null; + int rslt_idx = 0; + boolean max_val_check = max_results == Int_.Max_value; + Db_rdr rdr = Load_ttls_starting_with_rdr(ns.Id(), key, include_redirects, max_results, min_page_len, browse_len, true, true); + try { + while (rdr.Move_next()) { + if (cancelable.Canceled()) return; + Xowd_page_itm page = new Xowd_page_itm(); + Read_page__idx(page, rdr); + if (max_val_check && !Bry_.Has_at_bgn(page.Ttl_page_db(), key)) break; + nxt_itm = page; + if (rslt_idx == max_results) {} // last item which is not meant for rslts, but only for nxt itm + else { + rslt_list.Add(page); + ++rslt_idx; + } + } + if (rslt_nxt != null && nxt_itm != null) // occurs when range is empty; EX: "Module:A" in simplewikibooks + rslt_nxt.Copy(nxt_itm); + if (fetch_prv_item) { // NOTE: Special:AllPages passes in true, but Search_suggest passes in false + if (cancelable.Canceled()) return; + rdr = Load_ttls_starting_with_rdr(ns.Id(), key, include_redirects, max_results, min_page_len, browse_len, false, false); + Xowd_page_itm prv_itm = new Xowd_page_itm(); + boolean found = false; + while (rdr.Move_next()) { + Read_page__all(prv_itm, rdr); + found = true; + } + if (found) + rslt_prv.Copy(prv_itm); + else { // at beginning of range, so no items found; EX: "Module:A" is search, but 1st Module is "Module:B" + if (rslt_list.Count() > 0) // use 1st item + rslt_prv.Copy((Xowd_page_itm)rslt_list.Get_at(0)); + } + } + } + finally {rdr.Rls();} + rslt_count.Val_(rslt_idx); + } + public void Read_page__idx(Xowd_page_itm page, Db_rdr rdr) { + page.Init_by_load__idx + ( rdr.Read_int(fld_id) + , rdr.Read_int(fld_ns) + , rdr.Read_bry_by_str(fld_title) + , rdr.Read_int(fld_len) + ); + } + public void Read_page__all(Xowd_page_itm page, Db_rdr rdr) { + // handle page_score defaulting to page_len + int page_len = rdr.Read_int(fld_len); + int page_score = fld_score == Dbmeta_fld_itm.Key_null ? page_len : rdr.Read_int(fld_score); + int cat_db_id = fld_cat_db_id == Dbmeta_fld_itm.Key_null ? -1 : rdr.Read_int(fld_cat_db_id); + + page.Init_by_load__all + ( rdr.Read_int(fld_id) + , rdr.Read_int(fld_ns) + , rdr.Read_bry_by_str(fld_title) + , DateAdp_.parse_fmt(rdr.Read_str(fld_touched), Page_touched_fmt) + , rdr.Read_bool_by_byte(fld_is_redirect) + , page_len + , rdr.Read_int(fld_random_int) + , rdr.Read_int(fld_text_db_id) + , rdr.Read_int(fld_html_db_id) + , rdr.Read_int(fld_redirect_id) + , page_score + , cat_db_id + ); + } + public void Update__html_db_id(int page_id, int html_db_id) { + Db_stmt stmt = conn.Stmt_update(tbl_name, String_.Ary(fld_id), fld_html_db_id); + stmt.Val_int(fld_html_db_id, html_db_id).Crt_int(fld_id, page_id).Exec_update(); + } + public void Update__cat_db_id(int page_id, int cat_db_id) { + Db_stmt stmt = conn.Stmt_update(tbl_name, String_.Ary(fld_id), fld_cat_db_id); + stmt.Val_int(fld_cat_db_id, cat_db_id).Crt_int(fld_id, page_id).Exec_update(); + } + public void Update__ns__ttl(int page_id, int trg_ns, byte[] trg_ttl) { + for (int i = 0; i < 2; ++i) { + try { + conn.Stmt_update(tbl_name, String_.Ary(fld_id), fld_ns, fld_title) + .Val_int(fld_ns, trg_ns).Val_bry_as_str(fld_title, trg_ttl) + .Crt_int(fld_id, page_id) + .Exec_update(); + break; + } catch (Exception exc) { + if (String_.Has(Err_.Message_gplx_full(exc), "columns page_namespace, page_random_int are not unique")) { // HACK: terrible hack, but moving pages across ns will break UNIQUE index + conn.Exec_sql_args("DROP INDEX {0}__name_random;", tbl_name); // is UNIQUE by default + conn.Exec_sql_args("CREATE INDEX {0}__name_random ON {0} ({1}, {2});", tbl_name, fld_ns, fld_random_int); + } + } + } + } + public void Update__redirect__modified(int page_id, boolean redirect, DateAdp modified) { + conn.Stmt_update(tbl_name, String_.Ary(fld_id), fld_is_redirect, fld_touched) + .Val_int(fld_is_redirect, redirect ? 1 : 0) + .Val_str(fld_touched, modified.XtoStr_fmt(Page_touched_fmt)) + .Crt_int(fld_id, page_id) + .Exec_update() + ; + } + public void Update__redirect(int redirect_to_id, int page_id) { + conn.Stmt_update(tbl_name, String_.Ary(fld_id), fld_is_redirect, fld_redirect_id) + .Val_int(fld_is_redirect, Bool_.Y_int) + .Val_int(fld_redirect_id, redirect_to_id) + .Crt_int(fld_id, page_id) + .Exec_update() + ; + } + + public void Delete(int page_id) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.page: delete started: page_id=~{0}", page_id); + conn.Stmt_delete(tbl_name, fld_id).Crt_int(fld_id, page_id).Exec_delete(); + Gfo_usr_dlg_.Instance.Log_many("", "", "db.page: delete done"); + } + public void Update_page_id(int old_id, int new_id) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.page: update page_id started: old_id=~{0} new_id=~{1}", old_id, new_id); + conn.Stmt_update(tbl_name, String_.Ary(fld_id), fld_id).Val_int(fld_id, new_id).Crt_int(fld_id, old_id).Exec_update(); + Gfo_usr_dlg_.Instance.Log_many("", "", "db.page: update page_id done"); + } + public void Create_idx() { + conn.Meta_idx_create(Xoa_app_.Usr_dlg() + , Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "title" , fld_title, fld_ns) + , Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "random" , fld_random_int) + ); + } + public int Fld_page_score_eval(Db_rdr rdr, int page_len) { + return fld_score == Dbmeta_fld_itm.Key_null ? page_len : rdr.Read_int(fld_score); + } + public void Rls() { + synchronized (thread_lock) {// LOCK:stmt-rls; DATE:2016-07-06 + stmt_select_all_by_ttl = Db_stmt_.Rls(stmt_select_all_by_ttl); + stmt_select_all_by_id = Db_stmt_.Rls(stmt_select_all_by_id); + stmt_select_id_by_ttl = Db_stmt_.Rls(stmt_select_id_by_ttl); + stmt_insert = Db_stmt_.Rls(stmt_insert); + } + } + public static final String Page_touched_fmt = "yyyyMMddHHmmss"; + public static final String TBL_NAME = "page", FLD__page_cat_db_id = "page_cat_db_id"; + public static Xowd_page_tbl Get_by_key(Db_tbl_owner owner) {return (Xowd_page_tbl)owner.Tbls__get_by_key(TBL_NAME);} + public static final int INVALID_PAGE_ID = -1; +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl__id.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl__id.java index a27517de8..b003e7e27 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl__id.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl__id.java @@ -13,3 +13,26 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.core.criterias.*; +import gplx.dbs.*; import gplx.dbs.utls.*; +class Xowd_page_tbl__id extends Xowd_page_tbl__in_wkr__base { + private final List_adp list; // list is original list of ids which may have dupes; needed to fill statement (which takes range of bgn - end); DATE:2013-12-08 + private final Ordered_hash hash; // hash is unique list of ids; needed for fetch from rdr (which indexes by id) + public Xowd_page_tbl__id(List_adp list, Ordered_hash hash, boolean show_progress) { + this.show_progress = show_progress; + this.list = list; this.hash = hash; + this.Fill_idx_fields_only_(true); + } + @Override protected boolean Show_progress() {return show_progress;} private final boolean show_progress; + @Override protected Criteria In_filter(Object[] part_ary) { + return Db_crt_.New_in(this.In_fld_name(), part_ary); + } + @Override protected void Fill_stmt(Db_stmt stmt, int bgn, int end) { + for (int i = bgn; i < end; i++) { + Xowd_page_itm page = (Xowd_page_itm)list.Get_at(i); + stmt.Val_int(page.Id()); + } + } + @Override protected Xowd_page_itm Get_page_or_null(Xowd_page_itm rdr_page) {return (Xowd_page_itm)hash.Get_by(rdr_page.Id_val());} +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl__in_wkr__base.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl__in_wkr__base.java index a27517de8..81d4e7c43 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl__in_wkr__base.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl__in_wkr__base.java @@ -13,3 +13,38 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.core.criterias.*; +import gplx.dbs.*; import gplx.dbs.utls.*; +abstract class Xowd_page_tbl__in_wkr__base extends Db_in_wkr__base { + protected Xowd_page_tbl tbl; private String tbl_name, fld_in_name; + public String Tbl_name() {return tbl_name;} + public void Ctor(Xowd_page_tbl tbl, String tbl_name, String fld_in_name) {this.tbl = tbl; this.tbl_name = tbl_name; this.fld_in_name = fld_in_name;} + public String In_fld_name() {return fld_in_name;} + protected abstract Criteria In_filter(Object[] part_ary); + protected abstract Xowd_page_itm Get_page_or_null(Xowd_page_itm rdr_page); + public boolean Fill_idx_fields_only() {return fill_idx_fields_only;} public void Fill_idx_fields_only_(boolean v) {fill_idx_fields_only = v;} private boolean fill_idx_fields_only; + @Override protected Db_qry Make_qry(int bgn, int end) { + Object[] part_ary = In_ary(end - bgn); + return Db_qry_.select_cols_ + ( this.Tbl_name() + , In_filter(part_ary) + , fill_idx_fields_only ? tbl.Flds_select_idx() : tbl.Flds_select_all() + ); + } + @Override protected void Read_data(Cancelable cancelable, Db_rdr rdr) { + Xowd_page_itm load = new Xowd_page_itm(); + while (rdr.Move_next()) { + if (cancelable.Canceled()) return; + if (fill_idx_fields_only) + tbl.Read_page__idx(load, rdr); + else + tbl.Read_page__all(load, rdr); + + // get page reference from list; copy load values into it; COMMENT:2016-08-28 + Xowd_page_itm page = this.Get_page_or_null(load); + if (page == null) continue; // page not found + page.Copy(load); + } + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl__ttl.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl__ttl.java index a27517de8..07fccd1ae 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl__ttl.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl__ttl.java @@ -13,3 +13,27 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.core.criterias.*; +import gplx.dbs.*; import gplx.dbs.utls.*; +class Xowd_page_tbl__ttl extends Xowd_page_tbl__in_wkr__base { + private Ordered_hash hash; private int in_ns; + @Override protected int Interval() {return 64;} // NOTE: 96+ overflows; EX: w:Space_Liability_Convention; DATE:2013-10-24 + public void Init(Ordered_hash hash, int in_ns) {this.hash = hash; this.in_ns = in_ns;} + @Override protected Criteria In_filter(Object[] part_ary) { + int len = part_ary.length; + Criteria[] crt_ary = new Criteria[len]; + String fld_ns = tbl.Fld_page_ns(); String fld_ttl = tbl.Fld_page_title(); + for (int i = 0; i < len; i++) + crt_ary[i] = Criteria_.And(Db_crt_.New_eq(fld_ns, in_ns), Db_crt_.New_eq(fld_ttl, Bry_.Empty)); + return Criteria_.Or_many(crt_ary); + } + @Override protected void Fill_stmt(Db_stmt stmt, int bgn, int end) { + for (int i = bgn; i < end; i++) { + Xowd_page_itm page = (Xowd_page_itm)hash.Get_at(i); + stmt.Val_int(in_ns); + stmt.Val_bry_as_str(page.Ttl_page_db()); + } + } + @Override protected Xowd_page_itm Get_page_or_null(Xowd_page_itm rdr_page) {return (Xowd_page_itm)hash.Get_by(rdr_page.Ttl_page_db());} +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl__ttl_ns.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl__ttl_ns.java index a27517de8..e6f863c5a 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl__ttl_ns.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl__ttl_ns.java @@ -13,3 +13,38 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.core.criterias.*; +import gplx.dbs.*; import gplx.dbs.utls.*; +import gplx.xowa.wikis.nss.*; +class Xowd_page_tbl__ttl_ns extends Xowd_page_tbl__in_wkr__base { + private Xowd_page_tbl page_tbl; + private Xow_ns_mgr ns_mgr; private Ordered_hash hash; + @Override protected int Interval() {return 64;} // NOTE: 96+ overflows; PAGE:en.w:Space_Liability_Convention; DATE:2013-10-24 + public void Init(Xowd_page_tbl page_tbl, Xow_ns_mgr ns_mgr, Ordered_hash hash) { + this.page_tbl = page_tbl; + this.ns_mgr = ns_mgr; this.hash = hash; + } + @Override protected Criteria In_filter(Object[] part_ary) { + int len = part_ary.length; + Criteria[] crt_ary = new Criteria[len]; + String fld_ns = tbl.Fld_page_ns(); String fld_ttl = tbl.Fld_page_title(); + for (int i = 0; i < len; i++) + crt_ary[i] = Criteria_.And(Db_crt_.New_eq(fld_ns, 0), Db_crt_.New_eq(fld_ttl, Bry_.Empty)); + return Criteria_.Or_many(crt_ary); + } + @Override protected void Fill_stmt(Db_stmt stmt, int bgn, int end) { + for (int i = bgn; i < end; i++) { + Xowd_page_itm page = (Xowd_page_itm)hash.Get_at(i); + stmt.Crt_int(page_tbl.Fld_page_ns(), page.Ns_id()); + stmt.Crt_bry_as_str(page_tbl.Fld_page_title(), page.Ttl_page_db()); + } + } + @Override protected Xowd_page_itm Get_page_or_null(Xowd_page_itm rdr_page) { + Xow_ns ns = ns_mgr.Ids_get_or_null(rdr_page.Ns_id()); + if (ns == null) return null; // NOTE: ns seems to "randomly" be null when threading during redlinks; guard against null; DATE:2014-01-03 + byte[] ttl_wo_ns = rdr_page.Ttl_page_db(); + rdr_page.Ttl_(ns, ttl_wo_ns); + return (Xowd_page_itm)hash.Get_by(rdr_page.Ttl_full_db()); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl_tst.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl_tst.java index a27517de8..8f07ccfa9 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_page_tbl_tst.java @@ -13,3 +13,21 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import org.junit.*; import gplx.xowa.bldrs.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.dbs.*; import gplx.xowa.wikis.data.tbls.*; +public class Xowd_page_tbl_tst { + private Xowd_page_tbl_fxt fxt = new Xowd_page_tbl_fxt(); + @Test public void Find_search_end() { + fxt.Test_find_search_end("ab", "ac"); + fxt.Test_find_search_end("ab%", "ac%"); + } +} +class Xowd_page_tbl_fxt { + public void Test_find_search_end(String val, String expd) {Tfds.Eq(expd, String_.new_u8(Find_search_end(Bry_.new_u8(val))));} + private static byte[] Find_search_end(byte[] orig) { // NOTE: moved from old Xowd_page_tbl; is probably obsolete + byte[] rv = Bry_.Copy(orig); + int rv_len = rv.length; + int increment_pos = rv[rv_len - 1] == Byte_ascii.Percent ? rv_len - 2 : rv_len - 1; // increment last char, unless it is %; if %, increment one before it + return Bry_.Increment_last(rv, increment_pos); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_site_ns_tbl.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_site_ns_tbl.java index a27517de8..44d881348 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_site_ns_tbl.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_site_ns_tbl.java @@ -13,3 +13,77 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; +import gplx.xowa.wikis.nss.*; +public class Xowd_site_ns_tbl implements Db_tbl { + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_id, fld_name, fld_case, fld_count, fld_is_alias; + private final Db_conn conn; + public Xowd_site_ns_tbl(Db_conn conn, boolean schema_is_1) { + this.conn = conn; + this.tbl_name = schema_is_1 ? "xowa_ns" : "site_ns"; + fld_id = flds.Add_int_pkey ("ns_id"); + fld_name = flds.Add_str ("ns_name", 255); + fld_case = flds.Add_byte ("ns_case"); + fld_is_alias = flds.Add_bool ("ns_is_alias"); + fld_count = flds.Add_int ("ns_count"); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Insert(Xow_ns_mgr ns_mgr) { + Db_stmt stmt = conn.Stmt_insert(tbl_name, flds); + int len = ns_mgr.Ids_len(); + for (int i = 0; i < len; i++) { + Xow_ns ns = ns_mgr.Ids_get_at(i); + stmt.Clear() + .Val_int(fld_id, ns.Id()) + .Val_str(fld_name, ns.Name_db_str()) + .Val_byte(fld_case, ns.Case_match()) + .Val_bool_as_byte(fld_is_alias, ns.Is_alias()) + .Val_int(fld_count, ns.Count()) + .Exec_insert(); + ; + } + } + public void Select_all(Xow_ns_mgr ns_mgr) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds).Exec_select__rls_auto(); + try { + ns_mgr.Clear(); + while (rdr.Move_next()) { + int ns_id = rdr.Read_int(fld_id); + byte[] ns_name = rdr.Read_bry_by_str(fld_name); + byte ns_case_match = rdr.Read_byte(fld_case); + int ns_count = rdr.Read_int(fld_count); + boolean ns_is_alias = rdr.Read_byte(fld_is_alias) == Bool_.Y_byte; + ns_mgr.Add_new(ns_id, ns_name, ns_case_match, ns_is_alias); + if (ns_id < 0) continue; // don't load counts for Special / Media + Xow_ns ns = ns_mgr.Ids_get_or_null(ns_id); + ns.Count_(ns_count); + if (ns_count > 0) ns.Exists_(true); // ns has article; mark it as exists, else Talk tab won't show; DATE:2013-12-04 + } + ns_mgr.Init(); + } finally {rdr.Rls();} + } + public int Select_ns_count(int ns_id) { + Db_rdr rdr = conn.Stmt_select(tbl_name, flds, String_.Ary(fld_id)) + .Crt_int(fld_id, ns_id) + .Exec_select__rls_auto(); + try { + return rdr.Move_next() ? Int_.Cast(rdr.Read_int(fld_count)) : 0; + } finally {rdr.Rls();} + } + public void Update_ns_count(int ns_id, int ns_count) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.site_ns: update started: ns_id=~{0} ns_count=~{1}", ns_id, ns_count); + Db_stmt stmt = conn.Stmt_update(tbl_name, String_.Ary(fld_id), fld_count); + stmt.Clear() + .Val_int(fld_count, ns_count) + .Crt_int(fld_id, ns_id) + .Exec_update(); + Gfo_usr_dlg_.Instance.Log_many("", "", "db.site_ns: update done"); + } + public void Rls() {} + + public static final String TBL_NAME = "site_ns"; + public static Xowd_site_ns_tbl Get_by_key(Db_tbl_owner owner) {return (Xowd_site_ns_tbl)owner.Tbls__get_by_key(TBL_NAME);} +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_text_bry_owner.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_text_bry_owner.java index a27517de8..fd5ddffb5 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_text_bry_owner.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_text_bry_owner.java @@ -13,3 +13,8 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +public interface Xowd_text_bry_owner { + int Page_id(); + void Set_text_bry_by_db(byte[] v); +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_text_row.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_text_row.java index a27517de8..8968ba037 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_text_row.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_text_row.java @@ -13,3 +13,12 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +public class Xowd_text_row { + public final int page_id; + public final byte[] text; + public Xowd_text_row(int page_id, byte[] text) { + this.page_id = page_id; + this.text = text; + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_text_tbl.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_text_tbl.java index a27517de8..3019b676d 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_text_tbl.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_text_tbl.java @@ -13,3 +13,82 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.core.ios.*; import gplx.dbs.*; import gplx.dbs.utls.*; +public class Xowd_text_tbl implements Db_tbl { + private final Object thread_lock = new Object(); + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_page_id, fld_text_data; + private final Db_conn conn; private Db_stmt stmt_select, stmt_insert; + private final Io_stream_zip_mgr zip_mgr = new Io_stream_zip_mgr(); private final byte zip_tid; + public String Fld_text_data() {return fld_text_data;} + public Xowd_text_tbl(Db_conn conn, boolean schema_is_1, byte zip_tid) { + this.conn = conn; this.zip_tid = zip_tid; + String fld_text_data_name = ""; + fld_text_data_name = schema_is_1 ? "old_text" : "text_data"; + fld_page_id = flds.Add_int_pkey("page_id"); + fld_text_data = flds.Add_bry(fld_text_data_name); + conn.Rls_reg(this); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name = TBL_NAME; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Insert_bgn() {conn.Txn_bgn("schema__text__insert"); stmt_insert = conn.Stmt_insert(tbl_name, flds);} + public void Insert_end() {conn.Txn_end(); stmt_insert = Db_stmt_.Rls(stmt_insert);} + public void Insert_cmd_by_batch(int page_id, byte[] text_data) { + stmt_insert.Clear().Val_int(fld_page_id, page_id).Val_bry(fld_text_data, text_data).Exec_insert(); + } + public void Update(int page_id, byte[] text) { + Db_stmt stmt = conn.Stmt_update_exclude(tbl_name, flds, fld_page_id); + text = zip_mgr.Zip(zip_tid, text); + stmt.Clear().Val_bry(fld_text_data, text).Crt_int(fld_page_id, page_id).Exec_update(); + } + public void Delete(int page_id) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.text: delete started: db=~{0} page_id=~{1}", conn.Conn_info().Raw(), page_id); + conn.Stmt_delete(tbl_name, fld_page_id).Crt_int(fld_page_id, page_id).Exec_delete(); + Gfo_usr_dlg_.Instance.Log_many("", "", "db.text: delete done"); + } + public void Update_page_id(int old_id, int new_id) { + Gfo_usr_dlg_.Instance.Log_many("", "", "db.text: update page_id started: db=~{0} old_id=~{1} new_id=~{2}", conn.Conn_info().Raw(), old_id, new_id); + conn.Stmt_update(tbl_name, String_.Ary(fld_page_id), fld_page_id).Val_int(fld_page_id, new_id).Crt_int(fld_page_id, old_id).Exec_update(); + Gfo_usr_dlg_.Instance.Log_many("", "", "db.text: update page_id done"); + } + public byte[] Select(int page_id) { + synchronized (thread_lock) { // LOCK:stmt-rls; DATE:2016-07-06 + if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_page_id); + Db_rdr rdr = stmt_select.Clear().Crt_int(fld_page_id, page_id).Exec_select__rls_manual(); + try { + byte[] rv = Bry_.Empty; + if (rdr.Move_next()) { + rv = rdr.Read_bry(fld_text_data); + if (rv == null) rv = Bry_.Empty; // NOTE: defect wherein blank page inserts null not ""; for now always convert null to empty String; DATE:2015-11-08 + rv = zip_mgr.Unzip(zip_tid, rv); + } + return rv; + } finally {rdr.Rls();} + } + } + public Xowd_text_row[] Select_where(byte[] query) { + List_adp list = List_adp_.New(); + Db_rdr rdr = conn.Stmt_sql(Db_sql_.Make_by_fmt(String_.Ary("SELECT * FROM text WHERE text_data LIKE '{0}'") , query)).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + int page_id = rdr.Read_int(fld_page_id); + byte[] text = rdr.Read_bry(fld_text_data); + if (text == null) text = Bry_.Empty; // NOTE: defect wherein blank page inserts null not ""; for now always convert null to empty String; DATE:2015-11-08 + text = zip_mgr.Unzip(zip_tid, text); + list.Add(new Xowd_text_row(page_id, text)); + } + } finally {rdr.Rls();} + return (Xowd_text_row[])list.To_ary_and_clear(Xowd_text_row.class); + } + public byte[] Zip(byte[] data) {return zip_mgr.Zip(zip_tid, data);} + public void Rls() { + synchronized (thread_lock) { // LOCK:stmt-rls; DATE:2016-07-06 + stmt_select = Db_stmt_.Rls(stmt_select); + stmt_insert = Db_stmt_.Rls(stmt_insert); + } + } + + public static final String TBL_NAME = "text"; + public static Xowd_text_tbl Get_by_key(Db_tbl_owner owner) {return (Xowd_text_tbl)owner.Tbls__get_by_key(TBL_NAME);} +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_wbase_pid_tbl.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_wbase_pid_tbl.java index a27517de8..9b56428ee 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_wbase_pid_tbl.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_wbase_pid_tbl.java @@ -13,3 +13,52 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.dbs.*; import gplx.xowa.xtns.wbases.*; +public class Xowd_wbase_pid_tbl implements Rls_able { + private final String tbl_name; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_src_lang, fld_src_ttl, fld_trg_ttl; + private final Db_conn conn; private Db_stmt stmt_select, stmt_insert; + public Xowd_wbase_pid_tbl(Db_conn conn, boolean schema_is_1) { + this.conn = conn; + String fld_prefix = ""; + if (schema_is_1) {tbl_name = "wdata_pids"; fld_prefix = "wp_";} + else {tbl_name = "wbase_pid";} + fld_src_lang = flds.Add_str(fld_prefix + "src_lang", 255); + fld_src_ttl = flds.Add_str(fld_prefix + "src_ttl", 512); + fld_trg_ttl = flds.Add_str(fld_prefix + "trg_ttl", 512); + conn.Rls_reg(this); + } + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Create_idx() {conn.Meta_idx_create(Xoa_app_.Usr_dlg(), Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "src", fld_src_lang, fld_src_ttl));} + public void Insert_bgn() {conn.Txn_bgn("schema__wbase_pid__insert"); stmt_insert = conn.Stmt_insert(tbl_name, flds);} + public void Insert_end() {conn.Txn_end(); stmt_insert = Db_stmt_.Rls(stmt_insert);} + public void Insert_cmd_by_batch(byte[] src_lang, byte[] src_ttl, byte[] trg_ttl) { + stmt_insert.Clear() + .Val_bry_as_str(fld_src_lang, src_lang).Val_bry_as_str(fld_src_ttl, src_ttl).Val_bry_as_str(fld_trg_ttl, trg_ttl) + .Exec_insert(); + } + public int Select_pid(byte[] src_lang, byte[] src_ttl) { + if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_src_lang, fld_src_ttl); + Db_rdr rdr = stmt_select.Clear() + .Crt_bry_as_str(fld_src_lang, src_lang).Crt_bry_as_str(fld_src_ttl, src_ttl) + .Exec_select__rls_manual(); + try { + if (!rdr.Move_next()) return Wdata_wiki_mgr.Pid_null; // occurs when pid exists, but does not have entry for language; see hu.w:Marco Polo argali; DATE: 2014-02-01 + byte[] pid_bry = rdr.Read_bry_by_str(fld_trg_ttl); + return pid_bry == null ? Wdata_wiki_mgr.Pid_null : Bry_.To_int_or(pid_bry, 1, pid_bry.length, Wdata_wiki_mgr.Pid_null); + } + catch (Exception e) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "db.wdata_pids:failed to select pid; lang=~{0} src_ttl=~{1} err=~{2}", src_lang, src_ttl, Err_.Message_gplx_log(e)); + try {stmt_select.Rls();} + catch (Exception e2) {Gfo_usr_dlg_.Instance.Warn_many("", "", "db.wdata_pids: failed to rls stmt; err=~{0}", Err_.Message_gplx_log(e2));} + stmt_select = null; + return Wdata_wiki_mgr.Pid_null; + } + finally {rdr.Rls();} + } + public void Rls() { + stmt_insert = Db_stmt_.Rls(stmt_insert); + stmt_select = Db_stmt_.Rls(stmt_select); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_wbase_qid_tbl.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_wbase_qid_tbl.java index a27517de8..6ffbabf12 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_wbase_qid_tbl.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_wbase_qid_tbl.java @@ -13,3 +13,50 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.dbs.*; +public class Xowd_wbase_qid_tbl implements Rls_able { + private final Object thread_lock = new Object(); + private final String tbl_name; private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_src_wiki, fld_src_ns, fld_src_ttl, fld_trg_ttl; + private final Db_conn conn; private Db_stmt stmt_select, stmt_insert; + private boolean src_ttl_has_spaces; + public Xowd_wbase_qid_tbl(Db_conn conn, boolean schema_is_1, boolean src_ttl_has_spaces) { + this.conn = conn; this.src_ttl_has_spaces = src_ttl_has_spaces; + String fld_prefix = ""; + if (schema_is_1) {tbl_name = "wdata_qids"; fld_prefix = "wq_";} + else {tbl_name = "wbase_qid";} + fld_src_wiki = flds.Add_str(fld_prefix + "src_wiki", 255); + fld_src_ns = flds.Add_int(fld_prefix + "src_ns"); + fld_src_ttl = flds.Add_str(fld_prefix + "src_ttl", 512); + fld_trg_ttl = flds.Add_str(fld_prefix + "trg_ttl", 512); + conn.Rls_reg(this); + } + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public void Create_idx() {conn.Meta_idx_create(Xoa_app_.Usr_dlg(), Dbmeta_idx_itm.new_normal_by_tbl(tbl_name, "src", fld_src_wiki, fld_src_ns, fld_src_ttl));} + public void Insert_bgn() {conn.Txn_bgn("schema__wbase_qid__insert"); stmt_insert = conn.Stmt_insert(tbl_name, flds);} + public void Insert_end() {conn.Txn_end(); stmt_insert = Db_stmt_.Rls(stmt_insert);} + public void Insert_cmd_by_batch(byte[] src_wiki, int src_ns, byte[] src_ttl, byte[] trg_ttl) { + stmt_insert.Clear() + .Val_bry_as_str(fld_src_wiki, src_wiki).Val_int(fld_src_ns, src_ns).Val_bry_as_str(fld_src_ttl, src_ttl).Val_bry_as_str(fld_trg_ttl,trg_ttl) + .Exec_insert(); + } + public byte[] Select_qid(byte[] src_wiki, byte[] src_ns, byte[] src_ttl) { + if (stmt_select == null) stmt_select = conn.Stmt_select(tbl_name, flds, fld_src_wiki, fld_src_ns, fld_src_ttl); + synchronized (stmt_select) { // LOCK:stmt-rls; DATE:2016-07-06 + if (src_ttl_has_spaces) src_ttl = Xoa_ttl.Replace_unders(src_ttl); // NOTE: v2.4.2.1-v2.4.3.2 stores ttl in spaces ("A B"), while xowa will use under form ("A_B"); DATE:2015-04-21 + Db_rdr rdr = stmt_select.Clear() + .Crt_bry_as_str(fld_src_wiki, src_wiki).Crt_int(fld_src_ns, Bry_.To_int_or_neg1(src_ns)).Crt_bry_as_str(fld_src_ttl, src_ttl) + .Exec_select__rls_manual(); + try { + return rdr.Move_next() ? rdr.Read_bry_by_str(fld_trg_ttl) : null; + } finally {rdr.Rls();} + } + } + public void Rls() { + synchronized (thread_lock) { + stmt_insert = Db_stmt_.Rls(stmt_insert); + stmt_select = Db_stmt_.Rls(stmt_select); + } + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_wbase_qid_tbl_tst.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_wbase_qid_tbl_tst.java index a27517de8..55b9ce1a3 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_wbase_qid_tbl_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_wbase_qid_tbl_tst.java @@ -13,3 +13,34 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import org.junit.*; import gplx.dbs.*; +import gplx.xowa.wikis.nss.*; +public class Xowd_wbase_qid_tbl_tst { + private final Xowd_wbase_qid_tbl_fxt fxt = new Xowd_wbase_qid_tbl_fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Space() { + fxt.Exec_insert("enwiki", Xow_ns_.Tid__main, "A B", "q1"); + fxt.Test_select("enwiki", Xow_ns_.Tid__main, "A B", "q1"); + fxt.Test_select("enwiki", Xow_ns_.Tid__main, "A_B", "q1"); + } +} +class Xowd_wbase_qid_tbl_fxt { + private Xowd_wbase_qid_tbl qid_tbl; + public void Clear() { + Io_mgr.Instance.InitEngine_mem(); + Db_conn_bldr.Instance.Reg_default_mem(); + Db_conn conn = Db_conn_bldr.Instance.New(Io_url_.mem_fil_("mem/db/wbase.xowa")); + this.qid_tbl = new Xowd_wbase_qid_tbl(conn, Bool_.N, Bool_.Y); // simulate v2.4.2 with bad "spaces" + qid_tbl.Create_tbl(); + } + public void Exec_insert(String src_wiki, int src_ns, String src_ttl, String trg_ttl) { + qid_tbl.Insert_bgn(); + qid_tbl.Insert_cmd_by_batch(Bry_.new_u8(src_wiki), src_ns, Bry_.new_u8(src_ttl), Bry_.new_u8(trg_ttl)); + qid_tbl.Insert_end(); + } + public void Test_select(String src_wiki, int src_ns, String src_ttl, String expd) { + byte[] actl = qid_tbl.Select_qid(Bry_.new_u8(src_wiki), Bry_.new_a7(Int_.To_str(src_ns)), Bry_.new_u8(src_ttl)); + Tfds.Eq(expd, String_.new_u8(actl)); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_xowa_db_tbl.java b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_xowa_db_tbl.java index a27517de8..8f906b1ac 100644 --- a/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_xowa_db_tbl.java +++ b/400_xowa/src/gplx/xowa/wikis/data/tbls/Xowd_xowa_db_tbl.java @@ -13,3 +13,85 @@ 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.wikis.data.tbls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +import gplx.dbs.*; import gplx.dbs.qrys.*; +import gplx.xowa.wikis.data.*; +public class Xowd_xowa_db_tbl implements Db_tbl { + public static final String Fld_id = "db_id", Fld_type = "db_type", Fld_url = "db_url"; + private final Dbmeta_fld_list flds = new Dbmeta_fld_list(); + private final String fld_id, fld_type, fld_url, fld_ns_ids, fld_part_id, fld_guid; private boolean schema_is_1; + private final Db_conn conn; private final Db_stmt_bldr stmt_bldr = new Db_stmt_bldr(); + public Xowd_xowa_db_tbl(Db_conn conn, boolean schema_is_1) { + this.conn = conn; this.schema_is_1 = schema_is_1; + this.tbl_name = TBL_NAME; + fld_id = flds.Add_int_pkey (Fld_id); + fld_type = flds.Add_byte (Fld_type); + fld_url = flds.Add_str (Fld_url, 512); + if (schema_is_1) { + fld_ns_ids = fld_part_id = fld_guid = Dbmeta_fld_itm.Key_null; + } + else { + fld_ns_ids = flds.Add_str ("db_ns_ids", 255); + fld_part_id = flds.Add_int ("db_part_id"); + fld_guid = flds.Add_str ("db_guid", 36); + } + stmt_bldr.Conn_(conn, tbl_name, flds, fld_id); + } + public String Tbl_name() {return tbl_name;} private final String tbl_name; public static final String TBL_NAME = "xowa_db"; + public void Create_tbl() {conn.Meta_tbl_create(Dbmeta_tbl_itm.New(tbl_name, flds));} + public Xow_db_file[] Select_all(Xowd_core_db_props props, Io_url wiki_root_dir) { + List_adp list = List_adp_.New(); + Db_rdr rdr = conn.Stmt_select(tbl_name, flds).Exec_select__rls_auto(); + try { + while (rdr.Move_next()) { + String ns_ids = ""; int part_id = -1; Guid_adp guid = Guid_adp_.Empty; + if (!schema_is_1) { + ns_ids = rdr.Read_str(fld_ns_ids); + part_id = rdr.Read_int(fld_part_id); + guid = Guid_adp_.Parse(rdr.Read_str(fld_guid)); + } + int db_id = rdr.Read_int(fld_id); + Xow_db_file db_file = Xow_db_file.Load(props, db_id, rdr.Read_byte(fld_type), wiki_root_dir.GenSubFil(rdr.Read_str(fld_url)), ns_ids, part_id, guid); + list.Add(db_file); + } + } finally {rdr.Rls();} + list.Sort_by(Xow_db_file_sorter__id.Instance); + return (Xow_db_file[])list.To_ary_and_clear(Xow_db_file.class); + } + public void Commit_all(Xow_db_mgr core_data_mgr) { + stmt_bldr.Batch_bgn(); + try { + int len = core_data_mgr.Dbs__len(); + for (int i = 0; i < len; i++) + Commit_itm(core_data_mgr.Dbs__get_at(i)); + } finally {stmt_bldr.Batch_end();} + } + public void Upsert(int id, byte tid, String url, String ns_ids, int part_id, String guid) { + gplx.dbs.utls.Db_tbl__crud_.Upsert(conn, tbl_name, flds, String_.Ary(fld_id), id, tid, url, ns_ids, part_id, guid); + } + private void Commit_itm(Xow_db_file itm) { + Db_stmt stmt = stmt_bldr.Get(itm.Cmd_mode()); + switch (itm.Cmd_mode()) { + case Db_cmd_mode.Tid_create: stmt.Clear().Val_int(fld_id, itm.Id()); Commit_itm_vals(stmt, itm); stmt.Exec_insert(); break; + case Db_cmd_mode.Tid_update: stmt.Clear(); Commit_itm_vals(stmt, itm); stmt.Crt_int(fld_id, itm.Id()).Exec_update(); break; + case Db_cmd_mode.Tid_delete: stmt.Clear().Crt_int(fld_id, itm.Id()).Exec_delete(); break; + case Db_cmd_mode.Tid_ignore: break; + default: throw Err_.new_unhandled(itm.Cmd_mode()); + } + itm.Cmd_mode_(Db_cmd_mode.Tid_ignore); + } + private void Commit_itm_vals(Db_stmt stmt, Xow_db_file itm) { + stmt.Val_byte(fld_type, itm.Tid()).Val_str(fld_url, itm.Url_rel()).Val_str(fld_ns_ids, itm.Ns_ids()).Val_int(fld_part_id, itm.Part_id()).Val_str(fld_guid, itm.Guid().To_str()); + } + public void Rls() {} + + public static Xowd_xowa_db_tbl Get_by_key(Db_tbl_owner owner) {return (Xowd_xowa_db_tbl)owner.Tbls__get_by_key(TBL_NAME);} +} +class Xow_db_file_sorter__id implements gplx.core.lists.ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + Xow_db_file lhs = (Xow_db_file)lhsObj; + Xow_db_file rhs = (Xow_db_file)rhsObj; + return Int_.Compare(lhs.Id(), rhs.Id()); + } + public static final Xow_db_file_sorter__id Instance = new Xow_db_file_sorter__id(); Xow_db_file_sorter__id() {} +} diff --git a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_load_mgr.java b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_load_mgr.java index a27517de8..42454de9f 100644 --- a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_load_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_load_mgr.java @@ -13,3 +13,23 @@ 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.wikis.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.primitives.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.addons.wikis.ctgs.bldrs.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.doms.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.urls.*; +public interface Xodb_load_mgr { + void Load_init (Xowe_wiki wiki); + void Load_page (Xowd_page_itm rv, Xow_ns ns); + boolean Load_by_id (Xowd_page_itm rv, int id); + void Load_by_ids (Cancelable cancelable, List_adp rv, int bgn, int end); + boolean Load_by_ttl (Xowd_page_itm rv, Xow_ns ns, byte[] ttl); + void Load_by_ttls (Cancelable cancelable, Ordered_hash rv, boolean fill_idx_fields_only, int bgn, int end); + void Load_ttls_for_all_pages (Cancelable cancelable, List_adp rslt_list, Xowd_page_itm rslt_nxt, Xowd_page_itm rslt_prv, Int_obj_ref rslt_count, Xow_ns ns, byte[] key, int max_results, int min_page_len, int browse_len, boolean include_redirects, boolean fetch_prv_item); + void Load_ttls_for_search_suggest(Cancelable cancelable, List_adp rslt_list, Xow_ns ns, byte[] key, int max_results, int min_page_len, int browse_len, boolean include_redirects, boolean fetch_prv_item); + byte[] Find_random_ttl (Xow_ns ns); + void Clear(); // TEST:helper function + byte[] Load_qid (byte[] wiki_alias, byte[] ns_num, byte[] ttl); + int Load_pid (byte[] lang_key, byte[] pid_name); + Xodb_page_rdr Get_page_rdr (Xowe_wiki wiki); +} diff --git a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_load_mgr_sql.java b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_load_mgr_sql.java index a27517de8..7c9c245ea 100644 --- a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_load_mgr_sql.java +++ b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_load_mgr_sql.java @@ -13,3 +13,68 @@ 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.wikis.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.primitives.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.apps.gfs.*; +import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.metas.*; import gplx.xowa.wikis.data.*; +public class Xodb_load_mgr_sql implements Xodb_load_mgr { + private final Xodb_mgr_sql db_mgr; + public Xodb_load_mgr_sql(Xodb_mgr_sql db_mgr) {this.db_mgr = db_mgr;} + public void Load_init(Xowe_wiki wiki) { + Xow_db_file db_core = wiki.Data__core_mgr().Db__core(); + Load_cfg(wiki); + db_core.Tbl__site_stats().Select(wiki.Stats()); + db_core.Tbl__ns().Select_all(wiki.Ns_mgr()); + } + private static void Load_cfg(Xow_wiki wiki) { + byte[] main_page = null, bldr_version = null, siteinfo_misc = null, siteinfo_mainpage = null; + DateAdp modified_latest = null; + + // load from xowa_cfg + gplx.dbs.cfgs.Db_cfg_hash prop_hash = wiki.Data__core_mgr().Db__core().Tbl__cfg().Select_as_hash(Xowd_cfg_key_.Grp__wiki_init); + int len = prop_hash.Len(); + for (int i = 0; i < len; i++) { + gplx.dbs.cfgs.Db_cfg_itm prop = prop_hash.Get_at(i); + String prop_key = prop.Key(); + try { + if (String_.Eq(prop_key, Xowd_cfg_key_.Key__init__main_page)) main_page = Bry_.new_u8(prop.Val()); + else if (String_.Eq(prop_key, Xowd_cfg_key_.Key__init__bldr_version)) bldr_version = Bry_.new_u8(prop.Val()); + else if (String_.Eq(prop_key, Xowd_cfg_key_.Key__init__siteinfo_misc)) siteinfo_misc = Bry_.new_u8(prop.Val()); + else if (String_.Eq(prop_key, Xowd_cfg_key_.Key__init__siteinfo_mainpage)) siteinfo_mainpage = Bry_.new_u8(prop.Val()); + else if (String_.Eq(prop_key, Xowd_cfg_key_.Key__init__modified_latest)) modified_latest = DateAdp_.parse_gplx(prop.Val()); + } catch (Exception exc) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to set prop; key=~{0} val=~{1} err=~{2}", prop_key, prop.Val(), Err_.Message_gplx_log(exc)); + } + } + + wiki.Props().Init_by_load_2(main_page, bldr_version, siteinfo_misc, siteinfo_mainpage, modified_latest); + } + public boolean Load_by_ttl(Xowd_page_itm rv, Xow_ns ns, byte[] ttl) { + return db_mgr.Core_data_mgr().Tbl__page().Select_by_ttl(rv, ns, ttl); + } + public void Load_by_ttls(Cancelable cancelable, Ordered_hash rv, boolean fill_idx_fields_only, int bgn, int end) { + db_mgr.Core_data_mgr().Tbl__page().Select_in__ns_ttl(cancelable, rv, db_mgr.Wiki().Ns_mgr(), fill_idx_fields_only, bgn, end); + } + public void Load_page(Xowd_page_itm rv, Xow_ns ns) { + if (rv.Text_db_id() == -1) return; // NOTE: page_sync will create pages with -1 text_db_id; DATE:2017-05-06 + + // get text + Xowd_text_tbl text_tbl = db_mgr.Core_data_mgr().Dbs__get_by_id_or_fail(rv.Text_db_id()).Tbl__text(); + byte[] text_bry = text_tbl.Select(rv.Id()); + rv.Text_(text_bry); + } + public boolean Load_by_id (Xowd_page_itm rv, int id) {return db_mgr.Core_data_mgr().Tbl__page().Select_by_id(rv, id);} + public void Load_by_ids(Cancelable cancelable, List_adp rv, int bgn, int end) {db_mgr.Core_data_mgr().Tbl__page().Select_in__id(cancelable, false, true, rv, bgn, end);} + public void Load_ttls_for_all_pages(Cancelable cancelable, List_adp rslt_list, Xowd_page_itm rslt_nxt, Xowd_page_itm rslt_prv, Int_obj_ref rslt_count, Xow_ns ns, byte[] key, int max_results, int min_page_len, int browse_len, boolean include_redirects, boolean fetch_prv_item) { + db_mgr.Core_data_mgr().Tbl__page().Select_for_special_all_pages(cancelable, rslt_list, rslt_nxt, rslt_prv, rslt_count, ns, key, max_results, min_page_len, browse_len, include_redirects, fetch_prv_item); + } + public void Load_ttls_for_search_suggest(Cancelable cancelable, List_adp rslt_list, Xow_ns ns, byte[] key, int max_results, int min_page_len, int browse_len, boolean include_redirects, boolean fetch_prv_item) { + db_mgr.Core_data_mgr().Tbl__page().Select_for_search_suggest(cancelable, rslt_list, ns, key, max_results, min_page_len, browse_len, include_redirects, fetch_prv_item); + } + public byte[] Load_qid(byte[] wiki_alias, byte[] ns_num, byte[] ttl) {return db_mgr.Core_data_mgr().Db__wbase().Tbl__wbase_qid().Select_qid(wiki_alias, ns_num, ttl);} + public int Load_pid(byte[] lang_key, byte[] pid_name) {return db_mgr.Core_data_mgr().Db__wbase().Tbl__wbase_pid().Select_pid(lang_key, pid_name);} + public byte[] Find_random_ttl(Xow_ns ns) {return db_mgr.Core_data_mgr().Tbl__page().Select_random(ns);} + public Xodb_page_rdr Get_page_rdr(Xowe_wiki wiki) {return new Xodb_page_rdr__sql(wiki);} + public void Clear() {} +} diff --git a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_load_mgr_txt.java b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_load_mgr_txt.java index a27517de8..1dd64ae91 100644 --- a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_load_mgr_txt.java +++ b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_load_mgr_txt.java @@ -13,3 +13,397 @@ 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.wikis.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.primitives.*; import gplx.core.brys.*; import gplx.core.flds.*; import gplx.core.envs.*; +import gplx.xowa.addons.wikis.ctgs.bldrs.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.core.encoders.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.wikis.tdbs.*; import gplx.xowa.wikis.tdbs.hives.*; import gplx.xowa.wikis.tdbs.xdats.*; +import gplx.xowa.wikis.pages.*; +import gplx.xowa.addons.wikis.searchs.specials.*; +import gplx.xowa.guis.views.*; +import gplx.xowa.addons.wikis.ctgs.htmls.catpages.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.doms.*; import gplx.xowa.addons.wikis.ctgs.htmls.catpages.urls.*; +public class Xodb_load_mgr_txt implements Xodb_load_mgr { + private final Xob_xdat_file tmp_xdat_file = new Xob_xdat_file(); private final Xob_xdat_itm tmp_xdat_itm = new Xob_xdat_itm(); + private final Xowd_page_itm tmp_page = new Xowd_page_itm(); + private final Object thread_lock = new Object(); + private Xowe_wiki wiki; private Xotdb_fsys_mgr fsys_mgr; + public Xodb_load_mgr_txt(Xowe_wiki wiki) { + this.wiki = wiki; + this.fsys_mgr = wiki.Tdb_fsys_mgr(); + } + public void Load_init (Xowe_wiki wiki) {} + public void Load_page(Xowd_page_itm rv, Xow_ns ns) {Load_page(rv, rv.Text_db_id(), rv.Tdb_row_idx(), ns, false, tmp_xdat_file, tmp_xdat_itm);} + public void Load_page(Xowd_page_itm rv, int txt_fil_idx, int txt_row_idx, Xow_ns ns, boolean timestamp_enabled, Xob_xdat_file xdat_file, Xob_xdat_itm xdat_itm) { + Io_url file = fsys_mgr.Url_ns_fil(Xotdb_dir_info_.Tid_page, ns.Id(), txt_fil_idx); + byte[] bry = Io_mgr.Instance.LoadFilBry(file); int bry_len = bry.length; + xdat_file.Clear().Parse(bry, bry_len, file).GetAt(xdat_itm, txt_row_idx); + Load_page_parse(rv, bry, bry_len, xdat_itm.Itm_bgn(), xdat_itm.Itm_end(), timestamp_enabled); + } + public boolean Load_by_ttl(Xowd_page_itm rv, Xow_ns ns, byte[] ttl) { // NOTE: ttl must be correct case; EX: "Example title" + if (!Env_.Mode_testing() && wiki.Init_needed()) wiki.Init_assert(); // NOTE: need to call assert as wiki_finder (and possibly elsewhere) may call load on commons_wiki without ever asserting; DATE:2013-03-19 + if (!Load_xdat_itm(tmp_xdat_itm, ns, Xotdb_dir_info_.Tid_ttl, ttl, Xotdb_page_itm_.Txt_ttl_pos, Byte_ascii.Tab, true)) return false; + Xotdb_page_itm_.Txt_ttl_load(rv, tmp_xdat_itm.Itm_bry()); + return Bry_.Eq(rv.Ttl_page_db(), ttl); + } + public void Load_by_ttls(Cancelable cancelable, Ordered_hash rv, boolean fill_idx_fields_only, int bgn, int end) {// NOTE: Load_by_ttls just a wrapper around Load_by_ttl; for xdat, Load_by_ttl is fast enough + for (int i = bgn; i < end; i++) { + if (cancelable.Canceled()) return; + Xowd_page_itm page = (Xowd_page_itm)rv.Get_at(i); + Load_by_ttl(page, page.Ns(), page.Ttl_page_db()); + } + } + public void Load_by_ids(Cancelable cancelable, List_adp list, int bgn, int end) { + int prv_fil_idx = -1; + byte[] id_bry = new byte[5]; + int len = end - bgn; + Gfo_usr_dlg_fmt msg_wtr = Gfo_usr_dlg_fmt.fmt_(GRP_KEY, "search2_ids", "resolving ids: ~{0} of ~{1} (~{2})", len, 10f); + for (int i = 0; i < len; i++) { + if (cancelable.Canceled()) return; + Xowd_page_itm itm = (Xowd_page_itm)list.Get_at(i + bgn); + Base85_.Set_bry(itm.Id(), id_bry, 0, 5); + int cur_fil_idx = this.Find_file_idx_by_site(Xotdb_dir_info_.Tid_id, id_bry); + if (cur_fil_idx != prv_fil_idx) { + if (!this.Load_xdat_file(cancelable, tmp_xdat_file, Xotdb_dir_info_.Tid_id, cur_fil_idx)) continue; // file not found; ignore + prv_fil_idx = cur_fil_idx; + } + if (!this.Load_by_id(tmp_page, tmp_xdat_file, id_bry)) continue; // id not found in file; ignore + itm.Ns_id_(tmp_page.Ns_id()).Ttl_page_db_(tmp_page.Ttl_page_db()); + msg_wtr.Write_prog_cur(i, wiki.Appe().Usr_dlg()); + } + } + public void Load_search(Cancelable cancelable, List_adp rv, byte[] search, int results_max) { + Xow_ns ns = wiki.Ns_mgr().Ns_main(); + int search_len = search.length; + byte match_tid = Srch_special_page.Match_tid_all; + if (search_len > 0 && search[search_len - 1] == Byte_ascii.Star) { + search = Bry_.Mid(search, 0, search_len - 1); + match_tid = Srch_special_page.Match_tid_bgn; + } + int bgn_idx = this.Find_file_idx_by_ns(Xotdb_dir_info_.Tid_search_ttl, ns, search); + if (bgn_idx == Xodb_save_mgr_txt.File_idx_unknown) return; + if (match_tid == Srch_special_page.Match_tid_all) { + if (!this.Load_xdat_file(cancelable, tmp_xdat_file, Xotdb_dir_info_.Tid_search_ttl, ns, bgn_idx)) return; + tmp_xdat_file.Find(tmp_xdat_itm, search, 0, Byte_ascii.Pipe, true); + if (tmp_xdat_itm.Missing()) return; + Find_ttls__add_itms(rv, tmp_xdat_file, tmp_xdat_itm); + } + else { + byte[] end_ttl = Bry_.Increment_last(Bry_.Copy(search)); + int end_idx = this.Find_file_idx_by_ns(Xotdb_dir_info_.Tid_search_ttl, ns, end_ttl); + for (int i = bgn_idx; i <= end_idx; i++) { + if (cancelable.Canceled()) return; + this.Load_xdat_file(cancelable, tmp_xdat_file, Xotdb_dir_info_.Tid_search_ttl, ns, i); + if (cancelable.Canceled()) return; + int itm_bgn_idx = 0; + if (i == bgn_idx) { + tmp_xdat_file.Find(tmp_xdat_itm, search, 0, Byte_ascii.Pipe, false); + itm_bgn_idx = tmp_xdat_itm.Itm_idx(); + } + int itm_end_idx = tmp_xdat_file.Count(); + if (i == end_idx) { + tmp_xdat_file.Find(tmp_xdat_itm, end_ttl, 0, Byte_ascii.Pipe, false); + itm_end_idx = tmp_xdat_itm.Itm_idx(); + } + for (int j = itm_bgn_idx; j < itm_end_idx; j++) { + tmp_xdat_file.GetAt(tmp_xdat_itm, j); + Find_ttls__add_itms(rv, tmp_xdat_file, tmp_xdat_itm); + } + } + } + } + private void Find_ttls__add_itms(List_adp rv, Xob_xdat_file rdr, Xob_xdat_itm xdat_itm) { + byte[] raw = rdr.Src(); + int itm_bgn = xdat_itm.Itm_bgn(), itm_end = xdat_itm.Itm_end(); + int pos = Bry_find_.Find_fwd(raw, Byte_ascii.Pipe, itm_bgn, raw.length); + if (pos == Bry_find_.Not_found) throw wiki.Appe().Usr_dlg().Fail_many(GRP_KEY, "invalid_search_file", "search file is invalid"); + pos += Byte_ascii.Len_1; // pipe len + + while (pos < itm_end) { + int page_id = Base85_.To_int_by_bry(raw, pos, pos + 4); + pos += 6; // 5 + 1 for semic; + int page_len = Base85_.To_int_by_bry(raw, pos, pos + 4); + rv.Add(Xowd_page_itm.new_srch(page_id, page_len)); + pos += 6; // 5 + 1 for pipe +// if (match.Itms_len() == max_results) break; + } + } + public boolean Load_by_id(Xowd_page_itm page, int id) {Base85_.Set_bry(id, tmp_id_bry, 0, 5); return Load_by_id(page, tmp_id_bry);} private byte[] tmp_id_bry = new byte[5]; + boolean Load_by_id(Xowd_page_itm page, byte[] id_bry) { + if (!Load_xdat_itm(tmp_xdat_itm, Xotdb_dir_info_.Tid_id, id_bry, true)) return false;; + Xotdb_page_itm_.Txt_id_load(page, tmp_xdat_itm.Itm_bry()); + return true; + } + boolean Load_by_id(Xowd_page_itm page, Xob_xdat_file xdat_file, byte[] id_bry) { + xdat_file.Find(tmp_xdat_itm, id_bry, 0, Byte_ascii.Pipe, true); + if (tmp_xdat_itm.Missing()) return false; + Xotdb_page_itm_.Txt_id_load(page, tmp_xdat_itm.Itm_bry()); + return true; + } + private boolean Load_xdat_itm(Xob_xdat_itm xdat_itm, byte regy_tid, byte[] key, boolean exact) {return Load_xdat_itm(xdat_itm, null, regy_tid, key, 0, Byte_ascii.Pipe, exact);} + private boolean Load_xdat_itm(Xob_xdat_itm xdat_itm, Xow_ns ns, byte regy_tid, byte[] key, int parse_bgn, byte parse_dlm, boolean exact) { + // get regy + Xowd_regy_mgr regy = null; + if (ns == null) + regy = Get_regy_by_site(regy_tid); + else { + regy = Get_regy_by_ns(ns); + if (regy == null) return false; + } + // find file + int fil_idx = regy.Files_find(key); + if (fil_idx == Xowd_regy_mgr.Regy_null) return false; // NOTE: must check for -1, not 0; else defect in which entries in file 0 are ignored; DATE:2013-04-11 + // load file + Io_url fil = ns == null ? fsys_mgr.Url_site_fil(regy_tid, fil_idx) : fsys_mgr.Url_ns_fil(regy_tid, ns.Id(), fil_idx); + Load_xdat_file(Cancelable_.Never, tmp_xdat_file, fil); + // find itm by key + tmp_xdat_file.Find(xdat_itm, key, parse_bgn, parse_dlm, exact); + return !xdat_itm.Missing(); + } private final Int_obj_ref tmp_len = Int_obj_ref.New_zero(); + public boolean Load_xdat_file(Cancelable cancelable, Xob_xdat_file xdat_file, byte regy_tid, int fil_idx) {return Load_xdat_file(cancelable, xdat_file, regy_tid, null, fil_idx);} + boolean Load_xdat_file(Cancelable cancelable, Xob_xdat_file xdat_file, byte regy_tid, Xow_ns ns, int fil_idx) { + Io_url fil = ns == null ? fsys_mgr.Url_site_fil(regy_tid, fil_idx) : fsys_mgr.Url_ns_fil(regy_tid, ns.Id(), fil_idx); + return Load_xdat_file(cancelable, xdat_file, fil); + } + public boolean Load_xdat_file(Cancelable cancelable, Xob_xdat_file xdat_file, Io_url url) { + boolean rv = false; + synchronized (thread_lock) { + if (cancelable.Canceled()) return false; + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_m001(); + byte[] tmp_bry = tmp_bfr.Bfr(); + if (cancelable.Canceled()) return false; + tmp_bry = Io_mgr.Instance.LoadFilBry_reuse(url, tmp_bry, tmp_len); + if (cancelable.Canceled()) return false; + if (tmp_bry.length == 0) + wiki.Appe().Usr_dlg().Warn_many("", "file.empty", "hive file is empty: ~{0}", url.Raw()); + else { + int src_len = tmp_len.Val(); + xdat_file.Clear().Parse(tmp_bry, src_len, url); + xdat_file.Src_len_(src_len); + rv = true; + } + if (cancelable.Canceled()) return false; + tmp_bfr.Clear_and_rls(); + } + return rv; + } + int Find_file_idx_by_ns(byte regy_tid, Xow_ns ns, byte[] key) { + Xowd_regy_mgr regy = new Xowd_regy_mgr(fsys_mgr.Url_ns_reg(ns.Num_str(), regy_tid)); + return regy.Files_find(key); + } + public int Find_file_idx_by_site(byte regy_tid, byte[] key) { + Xowd_regy_mgr regy = site_regys[regy_tid]; + if (regy == null) { + regy = new Xowd_regy_mgr(fsys_mgr.Url_site_reg(regy_tid)); + site_regys[regy_tid] = regy; + } + return regy.Files_find(key); + } + public void Clear() { + int len = ns_regys.length; + for (int i = 0; i < len; i++) + ns_regys[i] = null; + len = site_regys.length; + for (int i = 0; i < len; i++) + site_regys[i] = null; + } + public byte[] Find_random_ttl(Xow_ns ns) { + Xowd_regy_mgr regy_mgr = this.Get_regy_by_ns(ns); + Int_obj_ref count = Int_obj_ref.New_zero(); + Xob_random_itm[] files = Build_random_itms(regy_mgr, count); + int random_idx = RandomAdp_.new_().Next(count.Val() - 1); // get a random idx; -1 since count is super 1 (EX: count of 1 mil; random_idx of 0 - 999,999) + int file_idx = Xowd_regy_mgr_.FindSlot(Xob_random_itm_comparer.Instance, files, new Xob_random_itm(-1, random_idx, -1)); + Io_url file_url = fsys_mgr.Url_ns_fil(Xotdb_dir_info_.Tid_ttl, ns.Id(), file_idx); + Load_xdat_file(Cancelable_.Never, tmp_xdat_file, file_url); + Xob_random_itm file = files[file_idx]; + tmp_xdat_file.GetAt(tmp_xdat_itm, random_idx - file.Bgn()); // get nth row; EX: random_idx=120; .Bgn=103 -> get 17th + Xowd_page_itm page = Xotdb_page_itm_.Txt_ttl_load(tmp_xdat_itm.Itm_bry()); + return page.Ttl_page_db(); + } + private static Xob_random_itm[] Build_random_itms(Xowd_regy_mgr mgr, Int_obj_ref count) { + // convert regy to list of random_itms (similar to regy_itms, but has integer bgn / end; EX: [0]:0,50; [1]:51-102; [2]:103-130) + Xowd_hive_regy_itm[] files_ary = mgr.Files_ary(); + int len = files_ary.length; + Xob_random_itm[] rv = new Xob_random_itm[len]; + int tmp_count = 0; + for (int i = 0; i < len; i++) { + Xowd_hive_regy_itm file = files_ary[i]; + rv[i] = new Xob_random_itm(i, tmp_count, file.Count()); + tmp_count += file.Count(); + } + count.Val_(tmp_count); + return rv; + } + public static boolean Load_page_or_false(Xowd_page_itm page, Xob_xdat_itm xdat, int ns_id) { + byte[] src = xdat.Src(); int itm_end = xdat.Itm_end(); + int bgn = xdat.Itm_bgn(); + int timestamp = Base85_.To_int_by_bry(src, bgn + 6 , bgn + 10); + int ttl_end = Bry_find_.Find_fwd(src, Xotdb_page_itm_.Txt_page_dlm, bgn + 12, itm_end); + if (ttl_end == -1) return false; + byte[] ttl = Bry_.Mid (src, bgn + 12 , ttl_end); + byte[] text = Bry_.Mid (src, ttl_end + 1 , itm_end - 1); + page.Init_by_tdb(-1, -1, xdat.Itm_idx(), Bool_.N, text.length, ns_id, ttl); + page.Modified_on_(Int_flag_bldr_.To_date_short(timestamp)); + page.Text_(text); + return true; + } + private void Load_page_parse(Xowd_page_itm page, byte[] src, int src_len, int row_bgn, int row_end, boolean timestamp_enabled) { // \n\tdate5\tpage_title\tpage_text + int timestamp_bgn = row_bgn + 5 + 1; + int timestamp_end = timestamp_bgn + 5; + if (timestamp_enabled) { + int timestamp = Base85_.To_int_by_bry(src, timestamp_bgn, timestamp_end - 1); + page.Modified_on_(Int_flag_bldr_.To_date_short(timestamp)); + } + int name_bgn = timestamp_end + 1; + int name_end = Bry_find_.Find_fwd(src, Xotdb_page_itm_.Txt_page_dlm, name_bgn, src_len); + page.Text_(Bry_.Mid(src, name_end + 1, row_end - 1)); // +1 to skip dlm + } + Xowd_regy_mgr Get_regy_by_site(byte regy_tid) { + Xowd_regy_mgr rv = site_regys[regy_tid]; + if (rv == null) { + rv = new Xowd_regy_mgr(fsys_mgr.Url_site_reg(regy_tid)); + site_regys[regy_tid] = rv; + } + return rv; + } private Xowd_regy_mgr[] site_regys = new Xowd_regy_mgr[Xotdb_dir_info_.Regy_tid_max]; + Xowd_regy_mgr Get_regy_by_ns(Xow_ns ns) { + int ns_ord = ns.Ord(); + Xowd_regy_mgr rv = ns_regys[ns_ord]; + if (rv == null) { + Io_url file = fsys_mgr.Url_ns_reg(ns.Num_str(), Xotdb_dir_info_.Tid_ttl); + if (!Io_mgr.Instance.ExistsFil(file)) return null; + rv = new Xowd_regy_mgr(file); + ns_regys[ns_ord] = rv; + } + return rv; + } private Xowd_regy_mgr[] ns_regys = new Xowd_regy_mgr[Xow_ns_mgr_.Ordinal_max]; + private Xowd_page_itm tmp_rslt_nxt = new Xowd_page_itm(), tmp_rslt_prv = new Xowd_page_itm(); private Int_obj_ref tmp_rslt_count = Int_obj_ref.New_zero(); + public void Load_ttls_for_search_suggest(Cancelable cancelable, List_adp rslt_list, Xow_ns ns, byte[] key, int max_results, int min_page_len, int browse_len, boolean include_redirects, boolean fetch_prv_item) { + this.Load_ttls_for_all_pages(cancelable, rslt_list, tmp_rslt_nxt, tmp_rslt_prv, tmp_rslt_count, ns, key, max_results, min_page_len, browse_len, include_redirects, fetch_prv_item); + } + public void Load_ttls_for_all_pages(Cancelable cancelable, List_adp rslt_list, Xowd_page_itm rslt_nxt, Xowd_page_itm rslt_prv, Int_obj_ref rslt_count, Xow_ns ns, byte[] key, int max_results, int min_page_len, int browse_len, boolean include_redirects, boolean fetch_prv_item) { + byte dir_tid = Xotdb_dir_info_.Tid_ttl; + Xob_xdat_file cur_xdat_file = new Xob_xdat_file(); + Xob_xdat_itm cur_xdat_itm = new Xob_xdat_itm(); + Xowd_regy_mgr regy = new Xowd_regy_mgr(fsys_mgr.Url_ns_reg(ns.Num_str(), dir_tid)); + int fil_idx = regy.Files_find(key); if (fil_idx == Xowd_regy_mgr.Regy_null) return; + if (!this.Load_xdat_file(Cancelable_.Never, cur_xdat_file, dir_tid, ns, fil_idx)) return; + cur_xdat_file.Find(cur_xdat_itm, key, Xotdb_page_itm_.Txt_ttl_pos, Byte_ascii.Tab, false); + int itm_idx = cur_xdat_itm.Itm_idx(); + if (itm_idx == -1) itm_idx = 0; // nothing found; return; + Special_allpages_query_fwd(rslt_list, rslt_nxt, rslt_count , dir_tid, ns, include_redirects, browse_len, fil_idx, itm_idx , cur_xdat_file, cur_xdat_itm, regy); + Special_allpages_query_bwd(rslt_list, rslt_prv , dir_tid, ns, include_redirects, browse_len, fil_idx, itm_idx - 1, cur_xdat_file, cur_xdat_itm); + } + private void Special_allpages_query_fwd(List_adp rslt_list, Xowd_page_itm rslt_nxt, Int_obj_ref rslt_count, byte dir_tid, Xow_ns ns, boolean include_redirects, int total, int fil_idx, int row_idx, Xob_xdat_file xdat_file, Xob_xdat_itm xdat_itm, Xowd_regy_mgr regy) { + int count = 0; ++total; + boolean loop = true; + int regy_len = regy.Files_ary().length; + int rslt_list_len = rslt_count.Val(); + Xowd_page_itm nxt_itm = null; + while (loop) { + if (fil_idx == regy_len) break; + if (xdat_file == null) { + xdat_file = new Xob_xdat_file(); + this.Load_xdat_file(Cancelable_.Never, xdat_file, dir_tid, ns, fil_idx); + row_idx = 0; + } + int rows_len = xdat_file.Count(); + for (; row_idx < rows_len; row_idx++) { + xdat_file.GetAt(xdat_itm, row_idx); + Xowd_page_itm ttl_itm = Xotdb_page_itm_.Txt_ttl_load(Bry_.Mid(xdat_itm.Src(), xdat_itm.Itm_bgn(), xdat_itm.Itm_end())); + if (!include_redirects && ttl_itm.Redirected()) continue; + ++count; + nxt_itm = ttl_itm; + if (count == total) { + loop = false; + break; + } + else { + rslt_list.Add(ttl_itm); + ++rslt_list_len; + } + } + xdat_file = null; + ++fil_idx; + } + rslt_count.Val_(rslt_list_len); + if (rslt_nxt != null) + rslt_nxt.Copy(nxt_itm); + } + private void Special_allpages_query_bwd(List_adp rslt_list, Xowd_page_itm rslt_prv, byte dir_tid, Xow_ns ns, boolean include_redirects, int total, int fil_idx, int row_idx, Xob_xdat_file xdat_file, Xob_xdat_itm xdat_itm) { + if (row_idx < 0) { + --fil_idx; + row_idx = -1; + } + int count = 0; + boolean loop = true; + Xowd_page_itm prv_itm = null; + while (loop) { + if (fil_idx == -1) break; + if (xdat_file == null) { + xdat_file = new Xob_xdat_file(); + this.Load_xdat_file(Cancelable_.Never, xdat_file, dir_tid, ns, fil_idx); + row_idx = -1; + } + if (row_idx == -1) + row_idx = xdat_file.Count() - 1; + for (; row_idx > -1; row_idx--) { + xdat_file.GetAt(xdat_itm, row_idx); + Xowd_page_itm ttl_itm = Xotdb_page_itm_.Txt_ttl_load(Bry_.Mid(xdat_itm.Src(), xdat_itm.Itm_bgn(), xdat_itm.Itm_end())); + if (!include_redirects && ttl_itm.Redirected()) continue; +// list.Add(ttl_itm); + ++count; + prv_itm = ttl_itm; + if (count == total) { + loop = false; + break; + } + else { +// rslt_list_ttls[rslt_list_len++] = ttl_itm; + } + } + xdat_file = null; + --fil_idx; + } + if (prv_itm == null && rslt_list.Count() > 0) { + prv_itm = (Xowd_page_itm)rslt_list.Get_at(0); + } + if (rslt_prv != null) + rslt_prv.Copy(prv_itm); + } + public byte[] Load_qid(byte[] wiki_alias, byte[] ns_num, byte[] ttl) { + String xwiki_key = String_.new_a7(wiki_alias); + if (qids_root == null) + qids_root = wiki.Appe().Wiki_mgr().Wdata_mgr().Wdata_wiki().Tdb_fsys_mgr().Site_dir().GenSubDir_nest("data", "qid"); + Xob_xdat_itm qid_itm = Load_xdat_itm_by_dir(qids_root.GenSubDir_nest(xwiki_key, String_.new_a7(ns_num)), ttl); if (qid_itm == null) return null; + return Bry_.Mid(qid_itm.Src(), qid_itm.Itm_bgn() + ttl.length + 1, qid_itm.Itm_end()); // extract qid; note that all itms have format of "ttl|qid" + } Io_url qids_root; + public int Load_pid(byte[] lang_key, byte[] pid_name) { + if (pids_root == null) + pids_root = wiki.Appe().Wiki_mgr().Wdata_mgr().Wdata_wiki().Tdb_fsys_mgr().Site_dir().GenSubDir_nest("data", "pid"); + Xob_xdat_itm pid_itm = Load_xdat_itm_by_dir(pids_root.GenSubDir(String_.new_u8(lang_key)), pid_name); if (pid_itm == null) return gplx.xowa.xtns.wbases.Wdata_wiki_mgr.Pid_null; + return Bry_.To_int_or(pid_itm.Src(), pid_itm.Itm_bgn() + pid_name.length + 1 + 1, pid_itm.Itm_end(), gplx.xowa.xtns.wbases.Wdata_wiki_mgr.Pid_null); // extract pid; note that all itms have format of "ttl|pid"; +1=skip pipe; +1 skip p + } Io_url pids_root; + Xob_xdat_itm Load_xdat_itm_by_dir(Io_url dir, byte[] ttl) { + Xoa_hive_mgr hive_mgr = wiki.Appe().Hive_mgr(); + int fil_idx = hive_mgr.Find_fil(dir, ttl); if (fil_idx == Xowd_regy_mgr.Regy_null) return null; // sub_dir not found; EX: commonswiki if qid; fr if pid; + Xob_xdat_file rdr = hive_mgr.Get_rdr(dir, Xotdb_dir_info_.Bry_xdat, fil_idx); + rdr.Find(tmp_xdat_itm, ttl, 0, Byte_ascii.Pipe, true); + return tmp_xdat_itm.Found_exact() ? tmp_xdat_itm : null; + } + public Xodb_page_rdr Get_page_rdr(Xowe_wiki wiki) {return new Xodb_page_rdr__tdb(wiki);} + static final String GRP_KEY = "xowa.wiki.db.load"; +} +class Xob_random_itm { + public int Idx() {return idx;} private int idx; + public int Bgn() {return bgn;} private int bgn; + public int End() {return bgn + len;} + public int Len() {return len;} private int len; + public Xob_random_itm(int idx, int bgn, int len) {this.idx = idx; this.bgn = bgn; this.len = len;} +} +class Xob_random_itm_comparer implements gplx.core.lists.ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + return Int_.Compare(((Xob_random_itm)lhsObj).End(), ((Xob_random_itm)rhsObj).End()); + } + public static final Xob_random_itm_comparer Instance = new Xob_random_itm_comparer(); Xob_random_itm_comparer() {} +} diff --git a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_mgr.java b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_mgr.java index a27517de8..216bb8904 100644 --- a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_mgr.java @@ -13,3 +13,13 @@ 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.wikis.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.*; import gplx.xowa.htmls.core.*; +public interface Xodb_mgr extends Gfo_invk { + byte Tid(); + String Tid_name(); + byte Category_version(); + Xodb_load_mgr Load_mgr(); + Xodb_save_mgr Save_mgr(); + DateAdp Dump_date_query(); // used by maint_mgr +} diff --git a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_mgr_sql.java b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_mgr_sql.java index a27517de8..c5ca91803 100644 --- a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_mgr_sql.java +++ b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_mgr_sql.java @@ -13,3 +13,48 @@ 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.wikis.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; import gplx.dbs.*; import gplx.dbs.qrys.*; import gplx.dbs.engines.sqlite.*; +import gplx.xowa.apps.gfs.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.htmls.core.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.data.*; +public class Xodb_mgr_sql implements Xodb_mgr, Gfo_invk { + public Xodb_mgr_sql(Xowe_wiki wiki) { + this.wiki = wiki; + this.core_data_mgr = new Xow_db_mgr(wiki.Fsys_mgr().Root_dir(), wiki.Domain_str()); + this.load_mgr = new Xodb_load_mgr_sql(this); + this.save_mgr = new Xodb_save_mgr_sql(this); + } + public byte Tid() {return Tid_sql;} public String Tid_name() {return "sqlite3";} public static final byte Tid_sql = 1; + public Xow_db_mgr Core_data_mgr() {return core_data_mgr;} private final Xow_db_mgr core_data_mgr; + public Xowe_wiki Wiki() {return wiki;} private final Xowe_wiki wiki; + public Xodb_load_mgr Load_mgr() {return load_mgr;} private final Xodb_load_mgr_sql load_mgr; + public Xodb_save_mgr Save_mgr() {return save_mgr;} private final Xodb_save_mgr_sql save_mgr; + public byte Category_version() {return category_version;} private byte category_version = Xoa_ctg_mgr.Version_null; + public DateAdp Dump_date_query() { + DateAdp rv = wiki.Props().Modified_latest(); if (rv != null) return rv; + Io_url url = core_data_mgr.Db__core().Url(); + return Io_mgr.Instance.QueryFil(url).ModifiedTime(); + } + public void Category_version_update(boolean version_is_1) { + String grp = Xowd_cfg_key_.Grp__wiki_init; + String key = Xoa_gfs_wtr_.Write_func_chain(Xowe_wiki.Invk_db_mgr, Xodb_mgr_sql.Invk_category_version); + core_data_mgr.Tbl__cfg().Delete_val(grp, key);// always delete ctg version + category_version = version_is_1 ? Xoa_ctg_mgr.Version_1 : Xoa_ctg_mgr.Version_2; + core_data_mgr.Tbl__cfg().Insert_byte(grp, key, category_version); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_data_storage_format)) return Io_stream_tid_.Obsolete_to_str(core_data_mgr.Props().Zip_tid_text()); + else if (ctx.Match(k, Invk_data_storage_format_)) {} // SERIALIZED:000.sqlite3|xowa_cfg; ignore; read from Xow_db_props + else if (ctx.Match(k, Invk_category_version)) return category_version; + else if (ctx.Match(k, Invk_category_version_)) category_version = m.ReadByte("v"); + else if (ctx.Match(k, Invk_search_version)) {return 1;} // return this.Search_version(); // REMOVED: DATE:2016-02-26 + else if (ctx.Match(k, Invk_tid_name)) return this.Tid_name(); + return this; + } + public static final String + Invk_data_storage_format = "data_storage_format", Invk_data_storage_format_ = "data_storage_format_" // SERIALIZED:000.sqlite3|xowa_cfg + , Invk_category_version = "category_version", Invk_category_version_ = "category_version_" // SERIALIZED:000.sqlite3|xowa_cfg + , Invk_search_version = "search_version" + , Invk_tid_name = "tid_name" + ; +} diff --git a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_mgr_txt.java b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_mgr_txt.java index a27517de8..ff9d32ea7 100644 --- a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_mgr_txt.java +++ b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_mgr_txt.java @@ -13,3 +13,49 @@ 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.wikis.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; +import gplx.dbs.*; import gplx.xowa.wikis.data.*; import gplx.xowa.addons.wikis.ctgs.*; import gplx.xowa.htmls.core.*; import gplx.xowa.wikis.tdbs.*; import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.pages.*; +public class Xodb_mgr_txt implements Xodb_mgr { + public Xodb_mgr_txt(Xowe_wiki wiki, Xow_page_mgr data_mgr) { + this.wiki = wiki; + load_mgr = new Xodb_load_mgr_txt(wiki); + save_mgr = new Xodb_save_mgr_txt(wiki, load_mgr); + } private Xowe_wiki wiki; + public byte Tid() {return Tid_txt;} public static final byte Tid_txt = 0; + public String Tid_name() {return "xdat";} + public byte Data_storage_format() {return data_storage_format;} public void Data_storage_format_(byte v) {data_storage_format = v;} private byte data_storage_format = gplx.core.ios.streams.Io_stream_tid_.Tid__raw; + public Xodb_load_mgr Load_mgr() {return load_mgr;} private Xodb_load_mgr_txt load_mgr; + public Xodb_save_mgr Save_mgr() {return save_mgr;} private Xodb_save_mgr_txt save_mgr; + public DateAdp Dump_date_query() { + Io_url url = wiki.Tdb_fsys_mgr().Url_ns_fil(Xotdb_dir_info_.Tid_page, Xow_ns_.Tid__main, 0); + return Io_mgr.Instance.QueryFil(url).ModifiedTime(); + } + public byte Category_version() { + if (category_version == Xoa_ctg_mgr.Version_null) { + if (Io_mgr.Instance.ExistsDir(wiki.Tdb_fsys_mgr().Url_site_dir(Xotdb_dir_info_.Tid_category2_link))) + category_version = Xoa_ctg_mgr.Version_2; + else + category_version = Xoa_ctg_mgr.Version_1; + } + return category_version; + } byte category_version = Xoa_ctg_mgr.Version_null; + public byte Search_version() {return gplx.xowa.addons.wikis.searchs.specials.Srch_special_page.Version_2;} + public void Search_version_refresh() {throw Err_.new_unimplemented();} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_data_storage_format)) return Io_stream_tid_.Obsolete_to_str(data_storage_format); + else if (ctx.Match(k, Invk_data_storage_format_)) data_storage_format = Io_stream_tid_.Obsolete_to_tid(m.ReadStr("v")); + else if (ctx.Match(k, Invk_category_version)) return this.Category_version(); + else if (ctx.Match(k, Invk_category_version_)) category_version = m.ReadByte("v"); + else if (ctx.Match(k, Invk_search_version)) return this.Search_version(); + else if (ctx.Match(k, Invk_tid_name)) return Tid_name(); + return this; + } + public static final String + Invk_data_storage_format = "data_storage_format", Invk_data_storage_format_ = "data_storage_format_" + , Invk_category_version = "category_version", Invk_category_version_ = "category_version_" + , Invk_search_version = "search_version" + , Invk_tid_name = "tid_name" + ; +} diff --git a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_page_rdr.java b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_page_rdr.java index a27517de8..009cb0006 100644 --- a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_page_rdr.java +++ b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_page_rdr.java @@ -13,3 +13,10 @@ 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.wikis.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.xowa.wikis.data.tbls.*; +public interface Xodb_page_rdr { + boolean Move_next(); + boolean Read(Xowd_page_itm page); + void Rls(); +} diff --git a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_page_rdr__sql.java b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_page_rdr__sql.java index a27517de8..17456794e 100644 --- a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_page_rdr__sql.java +++ b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_page_rdr__sql.java @@ -13,3 +13,26 @@ 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.wikis.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.dbs.*; +import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +class Xodb_page_rdr__sql implements Xodb_page_rdr { + private final Xow_db_mgr db_mgr; + private final Xowd_page_tbl page_tbl; private final Db_rdr rdr; + public Xodb_page_rdr__sql(Xowe_wiki wiki) { + this.db_mgr = wiki.Data__core_mgr(); + this.page_tbl = db_mgr.Tbl__page(); + this.rdr = page_tbl.Select_all__id__ttl(); + } + public boolean Move_next() {return rdr.Move_next();} + public boolean Read(Xowd_page_itm page) { + page_tbl.Read_page__all(page, rdr); + Xowd_text_tbl text_tbl = db_mgr.Dbs__get_by_id_or_fail(page.Text_db_id()).Tbl__text(); + page.Text_(text_tbl.Select(page.Id())); + return true; + } + public void Rls() { + rdr.Rls(); + } +} + diff --git a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_page_rdr__tdb.java b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_page_rdr__tdb.java index a27517de8..604cb368b 100644 --- a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_page_rdr__tdb.java +++ b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_page_rdr__tdb.java @@ -13,3 +13,74 @@ 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.wikis.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; +import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.tdbs.*; import gplx.xowa.wikis.tdbs.hives.*; import gplx.xowa.wikis.tdbs.xdats.*; +public class Xodb_page_rdr__tdb implements Xodb_page_rdr { + private final Xow_ns_mgr ns_mgr; private final Xotdb_fsys_mgr fsys_mgr; + private int cur_ns_ord = -1; private Xow_ns cur_ns; private Io_url[] cur_file_ary; private Io_url cur_file; private int cur_file_idx = -1; + private final Xob_xdat_file cur_xdat_file = new Xob_xdat_file(); private int cur_xdat_len, cur_xdat_idx; private final Xob_xdat_itm cur_xdat_itm = new Xob_xdat_itm(); + private int page_id = 0; + public Xodb_page_rdr__tdb(Xowe_wiki wiki) { + this.ns_mgr = wiki.Ns_mgr(); this.fsys_mgr = wiki.Tdb_fsys_mgr(); + } + public boolean Move_next() { + while (true) { + if (cur_ns == null) { + ++this.cur_ns_ord; + if (cur_ns_ord >= ns_mgr.Ords_len()) return false; // no more ns; return false; + this.cur_ns = ns_mgr.Ords_get_at(cur_ns_ord); + this.cur_file_ary = Get_page_url_ary(cur_ns); + if (cur_file_ary == null) { // ns doesn't have files; try next ns; + cur_ns = null; + continue; + } + this.cur_file_idx = -1; + this.cur_file = null; + } + if (cur_file == null) { + ++this.cur_file_idx; + if (cur_file_idx == cur_file_ary.length) { // no more files in ns; try next ns; + cur_ns = null; + continue; + } + this.cur_file = cur_file_ary[cur_file_idx]; + byte[] bry = Io_mgr.Instance.LoadFilBry(cur_file); + cur_xdat_file.Clear().Parse(bry, bry.length, cur_file); + this.cur_xdat_len = cur_xdat_file.Count(); + this.cur_xdat_idx = -1; + } + ++this.cur_xdat_idx; + if (cur_xdat_idx == cur_xdat_len) { // no more rows in file; try next file + cur_file = null; + continue; + } + cur_xdat_file.GetAt(cur_xdat_itm, cur_xdat_idx); + return true; + } + } + public boolean Read(Xowd_page_itm page) { + boolean rv = Xodb_load_mgr_txt.Load_page_or_false(page, cur_xdat_itm, cur_ns.Id()); + if (!rv) + Xoa_app_.Usr_dlg().Warn_many("", "", "page_rdr.tdb; unable to read page; ns=~{0}, file=~{1} itm_idx=~{2}", cur_ns.Id(), cur_file.Raw(), cur_xdat_idx); + // wiki.Db_mgr().Load_mgr().Load_by_ttl(page, cur_ns, page.Ttl_page_db()); + page.Id_(++page_id); + return rv; + } + public void Rls() {} + private Io_url[] Get_page_url_ary(Xow_ns ns) { + Io_url reg_url = fsys_mgr.Url_ns_reg(ns.Num_str(), Xotdb_dir_info_.Tid_ttl); + if (!Io_mgr.Instance.ExistsFil(reg_url)) return null; + Xowd_regy_mgr reg_mgr = new Xowd_regy_mgr(reg_url); + Xowd_hive_regy_itm[] file_ary = reg_mgr.Files_ary(); + int len = file_ary.length; + Io_url[] rv = new Io_url[len]; + for (int i = 0; i < len; ++i) { + int fil_idx = file_ary[i].Idx(); + rv[i] = fsys_mgr.Url_ns_fil(Xotdb_dir_info_.Tid_page, ns.Id(), fil_idx); + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_save_mgr.java b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_save_mgr.java index a27517de8..52f2eea48 100644 --- a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_save_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_save_mgr.java @@ -13,3 +13,13 @@ 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.wikis.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public interface Xodb_save_mgr { + boolean Create_enabled(); void Create_enabled_(boolean v); + boolean Update_modified_on_enabled(); void Update_modified_on_enabled_(boolean v); + int Page_id_next(); void Page_id_next_(int v); + int Data_create(Xoa_ttl ttl, byte[] text); + void Data_update(Xoae_page page, byte[] text); + void Data_rename(Xoae_page page, int trg_ns, byte[] trg_ttl); + void Clear(); +} diff --git a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_save_mgr_sql.java b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_save_mgr_sql.java index a27517de8..2977e9b0f 100644 --- a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_save_mgr_sql.java +++ b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_save_mgr_sql.java @@ -13,3 +13,66 @@ 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.wikis.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.ios.*; import gplx.dbs.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; import gplx.dbs.qrys.*; +import gplx.xowa.wikis.*; +public class Xodb_save_mgr_sql implements Xodb_save_mgr { + private final Xodb_mgr_sql db_mgr; + public Xodb_save_mgr_sql(Xodb_mgr_sql db_mgr) {this.db_mgr = db_mgr;} + public boolean Create_enabled() {return create_enabled;} public void Create_enabled_(boolean v) {create_enabled = v;} private boolean create_enabled; + public boolean Update_modified_on_enabled() {return update_modified_on_enabled;} public void Update_modified_on_enabled_(boolean v) {update_modified_on_enabled = v;} private boolean update_modified_on_enabled; + public int Page_id_next() {return page_id_next;} public void Page_id_next_(int v) {page_id_next = v;} private int page_id_next; + public int Data_create(Xoa_ttl ttl, byte[] text_raw) { + int ns_id = ttl.Ns().Id(); + Xow_db_file db_file = db_mgr.Core_data_mgr().Db__core(); + int ns_count = db_file.Tbl__ns().Select_ns_count(ns_id) + 1; + int page_id = db_file.Tbl__cfg().Select_int_or(Xowd_cfg_key_.Grp__db, Xowd_cfg_key_.Key__wiki__page__id_next, -1); + if (page_id == -1) { // HACK: changed for tests; was dbs.qrys.Db_qry_sql.rdr_("SELECT (Max(page_id) + 1) AS max_page_id FROM page;") + Db_rdr rdr = db_mgr.Core_data_mgr().Tbl__page().Conn().Stmt_select(db_file.Tbl__page().Tbl_name(), String_.Ary(db_file.Tbl__page().Fld_page_id()), Dbmeta_fld_itm.Str_ary_empty).Exec_select__rls_auto(); + try { + int max_page_id = -1; + while (rdr.Move_next()) { + int cur_page_id = rdr.Read_int("page_id"); + if (cur_page_id > max_page_id) max_page_id = cur_page_id; + } + page_id = max_page_id + 1; + db_mgr.Core_data_mgr().Tbl__cfg().Upsert_int(Xowd_cfg_key_.Grp__db, Xowd_cfg_key_.Key__wiki__page__id_next, page_id + 1); + } finally {rdr.Rls();} + } + Xow_db_mgr fsys_mgr = db_mgr.Core_data_mgr(); + Xow_db_file page_text_db = fsys_mgr.Db__text(); + if (page_text_db == null) page_text_db = fsys_mgr.Db__core(); // HACK: needed for create new wiki DATE:2016-10-29 + Xowd_text_tbl page_text_tbl = page_text_db.Tbl__text(); + byte[] text_zip = page_text_tbl.Zip(text_raw); + boolean redirect = db_mgr.Wiki().Redirect_mgr().Is_redirect(text_raw, text_raw.length); + Xowd_page_tbl page_core_tbl = db_mgr.Core_data_mgr().Tbl__page(); + page_core_tbl.Insert_bgn(); + page_text_tbl.Insert_bgn(); + try { + db_mgr.Core_data_mgr().Create_page(page_core_tbl, page_text_tbl, page_id, ns_id, ttl.Page_db(), redirect, Datetime_now.Get(), text_zip, text_raw.length, ns_count, page_text_db.Id(), -1); + db_file.Tbl__ns().Update_ns_count(ns_id, ns_count); + db_file.Tbl__cfg().Update_int(Xowd_cfg_key_.Grp__db, Xowd_cfg_key_.Key__wiki__page__id_next, page_id + 1); + } finally { + page_core_tbl.Insert_end(); + page_text_tbl.Insert_end(); + } + return page_id; + } + public void Data_update(Xoae_page page, byte[] text_raw) { + boolean redirect = db_mgr.Wiki().Redirect_mgr().Is_redirect(text_raw, text_raw.length); + DateAdp modified = update_modified_on_enabled ? Datetime_now.Get() : page.Db().Page().Modified_on(); + int page_id = page.Db().Page().Id(); + db_mgr.Core_data_mgr().Tbl__page().Update__redirect__modified(page_id, redirect, modified); + Xowd_page_itm db_page = new Xowd_page_itm(); + db_mgr.Load_mgr().Load_by_id(db_page, page_id); + Xowd_text_tbl text_tbl = db_mgr.Core_data_mgr().Dbs__get_by_id_or_fail(db_page.Text_db_id()).Tbl__text(); + text_tbl.Update(page_id, text_raw); +// int html_db_id = db_page.Html_db_id(); +// if (html_db_id != -1) +// db_mgr.Core_data_mgr().Tbl__page().Update__html_db_id(page_id, -1); // zap html_db_id so that next load will repopulate it + } + public void Data_rename(Xoae_page page, int trg_ns, byte[] trg_ttl) { + db_mgr.Core_data_mgr().Tbl__page().Update__ns__ttl(page.Db().Page().Id(), trg_ns, trg_ttl); + } + public void Clear() {} +} diff --git a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_save_mgr_txt.java b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_save_mgr_txt.java index a27517de8..6b1e7ceac 100644 --- a/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_save_mgr_txt.java +++ b/400_xowa/src/gplx/xowa/wikis/dbs/Xodb_save_mgr_txt.java @@ -13,3 +13,109 @@ 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.wikis.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.parsers.utils.*; +import gplx.xowa.wikis.tdbs.*; import gplx.xowa.wikis.tdbs.hives.*; import gplx.xowa.wikis.tdbs.xdats.*; +public class Xodb_save_mgr_txt implements Xodb_save_mgr { + public Xodb_save_mgr_txt(Xowe_wiki wiki, Xodb_load_mgr_txt load_mgr) { + this.wiki = wiki; + this.load_mgr = load_mgr; + this.fsys_mgr = wiki.Tdb_fsys_mgr(); + this.redirect_mgr = wiki.Redirect_mgr(); + } private Xowe_wiki wiki; private Xotdb_fsys_mgr fsys_mgr; private Xodb_load_mgr_txt load_mgr; private Xop_redirect_mgr redirect_mgr; + public boolean Create_enabled() {return create_enabled;} public void Create_enabled_(boolean v) {create_enabled = v;} private boolean create_enabled; + public boolean Update_modified_on_enabled() {return update_modified_on_enabled;} public void Update_modified_on_enabled_(boolean v) {update_modified_on_enabled = v;} private boolean update_modified_on_enabled; + public int Page_id_next() {return page_id_next;} public void Page_id_next_(int v) {page_id_next = v;} private int page_id_next = 0; + public void Clear() {page_id_next = 0;} // TEST: needed for ctg_test + public int Data_create(Xoa_ttl ttl, byte[] text) { + Xow_ns ns_itm = ttl.Ns(); byte[] ttl_bry = ttl.Page_db(); + Xowd_page_itm db_page = Xowd_page_itm.new_tmp(); + boolean found = load_mgr.Load_by_ttl(db_page, ns_itm, ttl_bry); + if (found) throw Err_.new_wo_type("create requested but title already exists", "ttl", String_.new_u8(ttl_bry)); + int text_len = text.length; + Bry_bfr tmp = wiki.Utl__bfr_mkr().Get_m001(); + int page_id = page_id_next++; + int fil_idx = 0; + int ns_id = ttl.Ns().Id(); + Xotdb_page_itm_.Txt_page_save(tmp, page_id, Datetime_now.Get(), ttl_bry, text, true); + Io_url page_rdr_url = fsys_mgr.Url_ns_fil(Xotdb_dir_info_.Tid_page, ns_id, fil_idx); + byte[] page_rdr_bry = Io_mgr.Instance.LoadFilBry(page_rdr_url); + Xob_xdat_file page_rdr = new Xob_xdat_file(); + if (Bry_.Len_gt_0(page_rdr_bry)) page_rdr.Parse(page_rdr_bry, page_rdr_bry.length, page_rdr_url); + int row_idx = page_rdr.Count(); + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_b512(); + page_rdr.Insert(tmp_bfr, tmp.To_bry_and_clear()); + this.Data_save(Xotdb_dir_info_.Tid_page, page_rdr, page_rdr_url, tmp_bfr); + tmp_bfr.Mkr_rls(); + + Xoa_ttl redirect_ttl = redirect_mgr.Extract_redirect(text, text_len); + db_page.Init(page_id, ttl.Page_db(), redirect_ttl != null, text_len, fil_idx, row_idx); + Xotdb_page_itm_.Txt_ttl_save(tmp, db_page); + byte[] ttl_row_bry = tmp.To_bry_and_rls(); + Xowd_hive_mgr ttl_hive = new Xowd_hive_mgr(wiki, Xotdb_dir_info_.Tid_ttl); + ttl_hive.Create(ttl.Ns(), ttl.Page_db(), ttl_row_bry, Bry_comparer_fld_last.Instance); + wiki.Db_mgr().Load_mgr().Clear(); // NOTE: need to clear cached regy_ary in load_mgr + return page_id; + } + public void Data_update(Xoae_page page, byte[] text) {Data_update_under(page, text, null);} + public void Data_rename(Xoae_page page, int trg_ns, byte[] trg_ttl) { + if (wiki.Domain_tid() != Xow_domain_tid_.Tid__home) { + wiki.Appe().Usr_dlg().Warn_many("", "", "Only pages in the home wiki can be renamed"); + return; + } + Data_update_under(page, null, trg_ttl); + } + private void Data_update_under(Xoae_page page, byte[] text, byte[] new_ttl) { + Xoa_ttl ttl = page.Ttl(); + Xow_ns ns = ttl.Ns(); byte[] ttl_bry = ttl.Page_db(); + Xowd_page_itm db_page = Xowd_page_itm.new_tmp(); + if (!load_mgr.Load_by_ttl(db_page, ns, ttl_bry)) throw Err_.new_wo_type("update requested but title does not exist", "ttl", String_.new_u8(ttl_bry)); + byte[] old_ttl = ttl_bry; + if (new_ttl != null) { + ttl_bry = new_ttl; + db_page.Ttl_page_db_(new_ttl); + } + // update page + Xob_xdat_file page_rdr = new Xob_xdat_file(); Xob_xdat_itm page_itm = new Xob_xdat_itm(); + load_mgr.Load_page(tmp_page, db_page.Text_db_id(), db_page.Tdb_row_idx(), ns, true, page_rdr, page_itm); + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_b512(); + if (text == null) text = tmp_page.Text(); + int text_len = text.length; + DateAdp modified_on = tmp_page.Modified_on(); + if (update_modified_on_enabled) { + modified_on = Datetime_now.Get(); + page.Db().Page().Modified_on_(modified_on); + } + Xotdb_page_itm_.Txt_page_save(tmp_bfr, db_page.Id(), modified_on, ttl_bry, text, true); + page_rdr.Update(tmp_bfr, page_itm, tmp_bfr.To_bry_and_clear()); + Io_url page_rdr_url = fsys_mgr.Url_ns_fil(Xotdb_dir_info_.Tid_page, ttl.Ns().Id(), db_page.Text_db_id()); + this.Data_save(Xotdb_dir_info_.Tid_page, page_rdr, page_rdr_url, tmp_bfr); + tmp_bfr.Mkr_rls(); + // update ttl + Xoa_ttl redirect_ttl = redirect_mgr.Extract_redirect(text, text_len); + db_page.Text_len_(text_len); + db_page.Redirected_(redirect_ttl != null); + Bry_bfr tmp = wiki.Utl__bfr_mkr().Get_b512(); + Xotdb_page_itm_.Txt_ttl_save(tmp, db_page); + byte[] ttl_row_bry = tmp.To_bry_and_clear(); + tmp.Mkr_rls(); + Xowd_hive_mgr ttl_hive = new Xowd_hive_mgr(wiki, Xotdb_dir_info_.Tid_ttl); + ttl_hive.Update(ns, old_ttl, new_ttl, ttl_row_bry, Xotdb_page_itm_.Txt_ttl_pos, Byte_ascii.Pipe, true, true); + } + private void Data_save(byte dir_tid, Xob_xdat_file xdat_file, Io_url url, Bry_bfr tmp_bfr) { + xdat_file.Save(url); + } + private Xowd_page_itm tmp_page = new Xowd_page_itm(); + public static final int File_idx_unknown = -1; +} +class Bry_comparer_fld_last implements gplx.core.lists.ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + byte[] lhs = (byte[])lhsObj, rhs = (byte[])rhsObj; + int lhs_bgn = Bry_find_.Find_bwd(lhs, Byte_ascii.Pipe); if (lhs_bgn == Bry_find_.Not_found) lhs_bgn = -1; + int rhs_bgn = Bry_find_.Find_bwd(rhs, Byte_ascii.Pipe); if (rhs_bgn == Bry_find_.Not_found) rhs_bgn = -1; + return Bry_.Compare(lhs, lhs_bgn + 1, lhs.length, rhs, rhs_bgn + 1, rhs.length); + } + public static final Bry_comparer_fld_last Instance = new Bry_comparer_fld_last(); +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_wm.java b/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_wm.java index a27517de8..ede4b1bcb 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_wm.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_wm.java @@ -13,3 +13,17 @@ 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.wikis.domains; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.xowa.langs.*; +public class Xow_abrv_wm { + public Xow_abrv_wm(byte[] raw, byte[] lang_domain, Xol_lang_stub lang_actl, int domain_type) { + this.raw = raw; + this.lang_domain = lang_domain; + this.lang_actl = lang_actl; + this.domain_type = domain_type; + } + public byte[] Raw() {return raw;} private final byte[] raw; + public byte[] Lang_domain() {return lang_domain;} private final byte[] lang_domain; + public Xol_lang_stub Lang_actl() {return lang_actl;} private final Xol_lang_stub lang_actl; + public int Domain_type() {return domain_type;} private final int domain_type; +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_wm_.java b/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_wm_.java index a27517de8..2391b308f 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_wm_.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_wm_.java @@ -13,3 +13,166 @@ 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.wikis.domains; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.primitives.*; import gplx.core.btries.*; +import gplx.xowa.langs.*; +public class Xow_abrv_wm_ { + public static Xow_abrv_wm Parse_to_abrv_or_null(byte[] src) { // EX: parse "enwiki" to abrv_itm + if (src == null) return null; + int src_len = src.length; if (src_len == 0) return null; // empty bry + Btrie_rv trv = new Btrie_rv(); + Object o = bry_trie.Match_at(trv, src, src_len - 1, -1); if (o == null) return null; + Xow_abrv_wm rv = Xow_abrv_wm_override.To_abrv_itm_or_null(src); if (rv != null) return rv; + int domain_type = ((Int_obj_ref)o).Val(); + Xol_lang_stub lang_itm = Xol_lang_stub_.Get_by_key_or_intl(src, 0, trv.Pos() + 1); + return new Xow_abrv_wm(src, lang_itm.Key(), lang_itm, domain_type); + } + public static Xow_domain_itm Parse_to_domain_itm(byte[] src) { // EX: parse "enwiki" to "en.wikipedia.org" itm + if (Bry_.Has(src, Byte_ascii.Underline)) // convert "_" to "-"; note that wmf_keys have a strict format of langtype; EX: "zh_yuewiki"; DATE:2014-10-06 + src = Bry_.Replace_create(src, Byte_ascii.Underline, Byte_ascii.Dash); + return Xow_domain_itm_.parse(Xow_abrv_wm_.Parse_to_domain_bry(src)); + } + public static byte[] Parse_to_domain_bry(byte[] src) { // EX: parse "enwiki" to en.wikipedia.org + if (src == null) return null; + int src_len = src.length; if (src_len == 0) return null; // empty bry + Btrie_rv trv = new Btrie_rv(); + Object o = bry_trie.Match_at(trv, src, src_len - 1, - 1); if (o == null) return null; + int domain_type = -1; + byte[] lang = null; + Xow_abrv_wm rv = Xow_abrv_wm_override.To_abrv_itm_or_null(src); + if (rv != null) { + lang = rv.Lang_domain(); + domain_type = rv.Domain_type(); + } + else { + domain_type = ((Int_obj_ref)o).Val(); + } + switch (domain_type) { + case Xow_domain_tid_.Tid__wmfblog: return Xow_domain_itm_.Bry__wmforg; + case Xow_domain_tid_.Tid__wikidata: return Xow_domain_itm_.Bry__wikidata; + case Xow_domain_tid_.Tid__mediawiki: return Xow_domain_itm_.Bry__mediawiki; + case Xow_domain_tid_.Tid__commons: return Xow_domain_itm_.Bry__commons; + case Xow_domain_tid_.Tid__species: return Xow_domain_itm_.Bry__species; + case Xow_domain_tid_.Tid__meta: return Xow_domain_itm_.Bry__meta; + case Xow_domain_tid_.Tid__incubator: return Xow_domain_itm_.Bry__incubator; + case Xow_domain_tid_.Tid__wikipedia: + case Xow_domain_tid_.Tid__wiktionary: + case Xow_domain_tid_.Tid__wikisource: + case Xow_domain_tid_.Tid__wikibooks: + case Xow_domain_tid_.Tid__wikiversity: + case Xow_domain_tid_.Tid__wikiquote: + case Xow_domain_tid_.Tid__wikinews: + case Xow_domain_tid_.Tid__wikivoyage: + case Xow_domain_tid_.Tid__wikimedia: + if (lang == null) { + lang = Bry_.Mid(src, 0, trv.Pos() + 1); // en + if (Bry_.Has(lang, Byte_ascii.Underline)) // convert "_" to "-"; note that wmf_keys have a strict format of langtype; EX: "zh_yuewiki"; DATE:2014-10-06 + lang = Bry_.Replace_create(lang, Byte_ascii.Underline, Byte_ascii.Dash); + } + return Bry_.Add + ( lang + , Byte_ascii.Dot_bry // . + , Xow_domain_tid_.Get_type_as_bry(domain_type) // wikipedia + , Byte_ascii.Dot_bry // . + , Xow_domain_itm_.Seg__org // org + ); + } + return null; + } + public static void To_abrv(Bry_bfr bfr, byte[] lang_key, Int_obj_ref domain_type) { + byte[] suffix_bry = (byte[])int_hash.Get_by(domain_type); if (suffix_bry == null) return; + switch (domain_type.Val()) { + case Xow_domain_tid_.Tid__commons: + case Xow_domain_tid_.Tid__species: + case Xow_domain_tid_.Tid__meta: + case Xow_domain_tid_.Tid__incubator: + case Xow_domain_tid_.Tid__wikidata: + case Xow_domain_tid_.Tid__mediawiki: + case Xow_domain_tid_.Tid__wmfblog: bfr.Add(suffix_bry); break; + case Xow_domain_tid_.Tid__other: break; + default: bfr.Add(lang_key).Add(suffix_bry); break; + } + } + public static byte[] To_abrv(Xow_domain_itm domain_itm) { + int tid = domain_itm.Domain_type_id(); + byte[] suffix = (byte[])int_hash.Get_by(Int_obj_ref.New(tid)); if (suffix == null) return null; + switch (tid) { + case Xow_domain_tid_.Tid__commons: + case Xow_domain_tid_.Tid__species: + case Xow_domain_tid_.Tid__meta: + case Xow_domain_tid_.Tid__incubator: + case Xow_domain_tid_.Tid__wikidata: + case Xow_domain_tid_.Tid__mediawiki: + case Xow_domain_tid_.Tid__wmfblog: return suffix; + case Xow_domain_tid_.Tid__wikipedia: + case Xow_domain_tid_.Tid__wiktionary: + case Xow_domain_tid_.Tid__wikisource: + case Xow_domain_tid_.Tid__wikibooks: + case Xow_domain_tid_.Tid__wikiversity: + case Xow_domain_tid_.Tid__wikiquote: + case Xow_domain_tid_.Tid__wikinews: + case Xow_domain_tid_.Tid__wikivoyage: + case Xow_domain_tid_.Tid__wikimedia: return Bry_.Add(domain_itm.Lang_orig_key(), suffix); + default: throw Err_.new_unhandled(tid); + } + } + private static final Btrie_bwd_mgr bry_trie = Init_trie(); + private static Hash_adp int_hash; + private static Btrie_bwd_mgr Init_trie() { + int_hash = Hash_adp_.New(); + Btrie_bwd_mgr rv = new Btrie_bwd_mgr(false); + Init_trie_itm(rv, int_hash, "wiki" , Xow_domain_tid_.Tid__wikipedia); + Init_trie_itm(rv, int_hash, "wiktionary" , Xow_domain_tid_.Tid__wiktionary); + Init_trie_itm(rv, int_hash, "wikisource" , Xow_domain_tid_.Tid__wikisource); + Init_trie_itm(rv, int_hash, "wikibooks" , Xow_domain_tid_.Tid__wikibooks); + Init_trie_itm(rv, int_hash, "wikiversity" , Xow_domain_tid_.Tid__wikiversity); + Init_trie_itm(rv, int_hash, "wikiquote" , Xow_domain_tid_.Tid__wikiquote); + Init_trie_itm(rv, int_hash, "wikinews" , Xow_domain_tid_.Tid__wikinews); + Init_trie_itm(rv, int_hash, "wikivoyage" , Xow_domain_tid_.Tid__wikivoyage); + Init_trie_itm(rv, int_hash, "wikimedia" , Xow_domain_tid_.Tid__wikimedia); + Init_trie_itm(rv, int_hash, "commonswiki" , Xow_domain_tid_.Tid__commons); + Init_trie_itm(rv, int_hash, "specieswiki" , Xow_domain_tid_.Tid__species); + Init_trie_itm(rv, int_hash, "metawiki" , Xow_domain_tid_.Tid__meta); + Init_trie_itm(rv, int_hash, "incubatorwiki" , Xow_domain_tid_.Tid__incubator); + Init_trie_itm(rv, int_hash, "wikidatawiki" , Xow_domain_tid_.Tid__wikidata); + Init_trie_itm(rv, int_hash, "mediawikiwiki" , Xow_domain_tid_.Tid__mediawiki); + Init_trie_itm(rv, int_hash, "foundationwiki" , Xow_domain_tid_.Tid__wmfblog); + return rv; + } + private static void Init_trie_itm(Btrie_bwd_mgr trie, Hash_adp hash, String str, int tid) { + Int_obj_ref itm = Int_obj_ref.New(tid); + trie.Add(str, itm); + hash.Add(itm, Bry_.new_u8(str)); + } +} +class Xow_abrv_wm_override { + public static Xow_abrv_wm To_abrv_itm_or_null(byte[] abrv) { + Object o = itm_hash.Get_by_bry(abrv); + return o == null ? null : (Xow_abrv_wm)o; + } + public static byte[] To_lang_key_or_null(byte[] domain_bry) { + Object o = lang_hash.Get_by_bry(domain_bry); + return o == null ? null : (byte[])o; + } + private static final Hash_adp_bry itm_hash = itm_hash__make(); + private static Hash_adp_bry lang_hash; + private static Hash_adp_bry itm_hash__make() { + Hash_adp_bry rv = Hash_adp_bry.cs(); + lang_hash = Hash_adp_bry.cs(); + itm_hash__add(rv, lang_hash, "ar.wikimedia.org", "arwikimedia", "ar", Xol_lang_stub_.Id_es, Xow_domain_tid_.Tid__wikimedia); // NOTE: ar means Argentina not Arabic + itm_hash__add(rv, lang_hash, "br.wikimedia.org", "brwikimedia", "br", Xol_lang_stub_.Id_es, Xow_domain_tid_.Tid__wikimedia); // NOTE: br means Brazil not Breto + itm_hash__add(rv, lang_hash, "co.wikimedia.org", "cowikimedia", "co", Xol_lang_stub_.Id_es, Xow_domain_tid_.Tid__wikimedia); // NOTE: co means Columbia not Corsican + itm_hash__add(rv, lang_hash, "ua.wikimedia.org", "ukwikimedia", "ua", Xol_lang_stub_.Id_uk, Xow_domain_tid_.Tid__wikimedia); // NOTE: ua means Ukrainian; NOTE: uk does not means United Kingdom (which redirects to https://wikimedia.org.uk) + itm_hash__add(rv, lang_hash, "ca.wikimedia.org", "cawikimedia", "ca", Xol_lang_stub_.Id_en, Xow_domain_tid_.Tid__wikimedia); // NOTE: ca means Canada not Catalan + itm_hash__add(rv, lang_hash, "be.wikimedia.org", "bewikimedia", "be", Xol_lang_stub_.Id_en, Xow_domain_tid_.Tid__wikimedia); // NOTE: be means Belgium not Belarusian + itm_hash__add(rv, lang_hash, "se.wikimedia.org", "sewikimedia", "se", Xol_lang_stub_.Id_sv, Xow_domain_tid_.Tid__wikimedia); // NOTE: se means Swedish not Northern Sami + return rv; + } + private static void itm_hash__add(Hash_adp_bry hash, Hash_adp_bry lang_hash, String domain, String raw, String lang_domain, int lang_actl, int domain_type) { + byte[] abrv_bry = Bry_.new_u8(raw); + Xol_lang_stub lang_actl_itm = Xol_lang_stub_.Get_by_id(lang_actl); + Xow_abrv_wm itm = new Xow_abrv_wm(abrv_bry, Bry_.new_a7(lang_domain), lang_actl_itm, domain_type); + hash.Add_bry_obj(abrv_bry, itm); + lang_hash.Add_str_obj(domain, lang_actl_itm.Key()); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_wm_tst.java b/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_wm_tst.java index a27517de8..a611e76f5 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_wm_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_wm_tst.java @@ -13,3 +13,82 @@ 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.wikis.domains; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import org.junit.*; import gplx.xowa.langs.*; +public class Xow_abrv_wm_tst { + private Xow_abrv_wm_fxt fxt = new Xow_abrv_wm_fxt(); + @Test public void Parse() { + fxt.Test_parse("foundationwiki" , Xol_lang_stub_.Id__intl , Xow_domain_tid_.Tid__wmfblog); + fxt.Test_parse("wikidatawiki" , Xol_lang_stub_.Id__intl , Xow_domain_tid_.Tid__wikidata); + fxt.Test_parse("mediawikiwiki" , Xol_lang_stub_.Id__intl , Xow_domain_tid_.Tid__mediawiki); + fxt.Test_parse("commonswiki" , Xol_lang_stub_.Id__intl , Xow_domain_tid_.Tid__commons); + fxt.Test_parse("specieswiki" , Xol_lang_stub_.Id__intl , Xow_domain_tid_.Tid__species); + fxt.Test_parse("metawiki" , Xol_lang_stub_.Id__intl , Xow_domain_tid_.Tid__meta); + fxt.Test_parse("incubatorwiki" , Xol_lang_stub_.Id__intl , Xow_domain_tid_.Tid__incubator); + fxt.Test_parse("enwiki" , Xol_lang_stub_.Id_en , Xow_domain_tid_.Tid__wikipedia); + fxt.Test_parse("enwiktionary" , Xol_lang_stub_.Id_en , Xow_domain_tid_.Tid__wiktionary); + fxt.Test_parse("enwikisource" , Xol_lang_stub_.Id_en , Xow_domain_tid_.Tid__wikisource); + fxt.Test_parse("enwikibooks" , Xol_lang_stub_.Id_en , Xow_domain_tid_.Tid__wikibooks); + fxt.Test_parse("enwikiversity" , Xol_lang_stub_.Id_en , Xow_domain_tid_.Tid__wikiversity); + fxt.Test_parse("enwikiquote" , Xol_lang_stub_.Id_en , Xow_domain_tid_.Tid__wikiquote); + fxt.Test_parse("enwikinews" , Xol_lang_stub_.Id_en , Xow_domain_tid_.Tid__wikinews); + fxt.Test_parse("enwikivoyage" , Xol_lang_stub_.Id_en , Xow_domain_tid_.Tid__wikivoyage); + fxt.Test_parse("frwiki" , Xol_lang_stub_.Id_fr , Xow_domain_tid_.Tid__wikipedia); + fxt.Test_parse_null("unknown"); + fxt.Test_parse("plwikimedia" , Xol_lang_stub_.Id_pl , Xow_domain_tid_.Tid__wikimedia); + } + @Test public void Parse_override() { + fxt.Test_parse("arwikimedia" , Xol_lang_stub_.Id_es , Xow_domain_tid_.Tid__wikimedia); + fxt.Test_parse("ukwikimedia" , Xol_lang_stub_.Id_uk , Xow_domain_tid_.Tid__wikimedia); + } + @Test public void To_domain_itm() { + fxt.Test_to_domain_itm("enwiki" , "en" , "en.wikipedia.org"); + fxt.Test_to_domain_itm("zh_yuewiki" , "zh-yue" , "zh-yue.wikipedia.org"); + } + @Test public void To_domain_bry() { + fxt.Test_to_domain_bry("enwiki" , "en.wikipedia.org"); + fxt.Test_to_domain_bry("zh_yuewiki" , "zh-yue.wikipedia.org"); + fxt.Test_to_domain_bry("arwikimedia", "ar.wikimedia.org"); + fxt.Test_to_domain_bry("ukwikimedia", "ua.wikimedia.org"); + } + @Test public void To_abrv() { + fxt.Test_to_abrv("simple.wikipedia.org" , "simplewiki"); + fxt.Test_to_abrv("en.wikipedia.org" , "enwiki"); + fxt.Test_to_abrv("commons.wikimedia.org" , "commonswiki"); + } + @Test public void To_abrv_by_lang() { + fxt.Test_to_abrv_by_lang("en", Xow_domain_tid_.Tid__wikipedia, "enwiki"); + } +} +class Xow_abrv_wm_fxt { + public void Test_parse(String raw, int expd_lang_id, int expd_domain_tid) { + byte[] raw_bry = Bry_.new_a7(raw); + Xow_abrv_wm abrv = Xow_abrv_wm_.Parse_to_abrv_or_null(raw_bry); + Xol_lang_stub actl_lang_itm = abrv.Lang_actl(); + Tfds.Eq(expd_lang_id , actl_lang_itm == null ? Xol_lang_stub_.Id__unknown : actl_lang_itm.Id()); + Tfds.Eq(expd_domain_tid , abrv.Domain_type()); + } + public void Test_parse_null(String raw) { + byte[] raw_bry = Bry_.new_a7(raw); + Xow_abrv_wm abrv = Xow_abrv_wm_.Parse_to_abrv_or_null(raw_bry); + Tfds.Eq_true(abrv == null); + } + public void Test_to_abrv(String domain_str, String expd) { + Xow_domain_itm domain = Xow_domain_itm_.parse(Bry_.new_a7(domain_str)); + byte[] actl = Xow_abrv_wm_.To_abrv(domain); + Tfds.Eq(expd, String_.new_a7(actl)); + } + public void Test_to_abrv_by_lang(String lang_key, int wiki_tid, String expd) { + Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); + Xow_abrv_wm_.To_abrv(tmp_bfr, Bry_.new_a7(lang_key), gplx.core.primitives.Int_obj_ref.New(wiki_tid)); + Tfds.Eq_str(expd, tmp_bfr.To_str_and_clear(), "to_abrv"); + } + public void Test_to_domain_bry(String wmf_key, String expd_domain) { + Tfds.Eq(expd_domain , String_.new_a7(Xow_abrv_wm_.Parse_to_domain_bry(Bry_.new_a7(wmf_key)))); + } + public void Test_to_domain_itm(String wmf_key, String expd_lang_key, String expd_domain) { + Xow_domain_itm domain = Xow_abrv_wm_.Parse_to_domain_itm(Bry_.new_a7(wmf_key)); + Tfds.Eq(expd_lang_key , String_.new_a7(domain.Lang_actl_key())); + Tfds.Eq(expd_domain , String_.new_a7(domain.Domain_bry())); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_xo_.java b/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_xo_.java index a27517de8..e9e65e999 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_xo_.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_xo_.java @@ -13,3 +13,41 @@ 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.wikis.domains; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.xowa.langs.*; +public class Xow_abrv_xo_ { + public static byte[] To_bry(byte[] domain_bry) { + Xow_domain_itm domain_itm = Xow_domain_itm_.parse(domain_bry); + return Xow_abrv_xo_.To_bry(domain_itm.Domain_bry(), domain_itm.Lang_orig_key(), domain_itm.Domain_type()); + } + public static byte[] To_bry(byte[] domain_bry, byte[] lang_key, Xow_domain_tid type) { // en.wikipedia.org -> en.w + byte[] type_abrv = type.Abrv(); + if (type.Multi_lang()) // wikipedia,wiktionary,etc.. + return Bry_.Add(lang_key, Byte_ascii.Dot_bry, type_abrv); + else if (type_abrv.length > 0) // commons,wbase,species,etc.. + return type_abrv; + else // home;wikia;others + return domain_bry; + } + public static Xow_domain_itm To_itm(byte[] src) { + int src_len = src.length; + byte[] domain_bry = src; // default to src; handles unknown abrv like "a.wikia.com";"xowa";others + Xow_domain_tid type = null; + int dot_pos = Bry_find_.Find_fwd(src, Byte_ascii.Dot); + if (dot_pos != Bry_find_.Not_found) { // dot found; EX: "en.w" + type = Xow_domain_tid_.Get_abrv_as_itm(src, dot_pos + 1, src_len); + if (type != null) { // type found; EX: ".w" + Xol_lang_stub lang = Xol_lang_stub_.Get_by_key_or_null(src, 0, dot_pos); + if (lang != null) // lang found; EX: "en." + domain_bry = Bry_.Add(lang.Key(), type.Domain_bry()); + } + } + else { // dot missing; EX: "c" + type = Xow_domain_tid_.Get_abrv_as_itm(src, 0, src_len); + if (type != null) { // type found; EX: "c" + domain_bry = type.Domain_bry(); + } + } + return Xow_domain_itm_.parse(domain_bry); // for consolidation's sake, parse abrv to domain_bry and pass to Xow_domain_itm_.parse() + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_xo__tst.java b/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_xo__tst.java index a27517de8..1ac4659e2 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_xo__tst.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/Xow_abrv_xo__tst.java @@ -13,3 +13,29 @@ 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.wikis.domains; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import org.junit.*; +public class Xow_abrv_xo__tst { + @Before public void init() {fxt.Clear();} private final Xow_abrv_xo__fxt fxt = new Xow_abrv_xo__fxt(); + @Test public void Basic() { + fxt.Test("en.wikipedia.org" , "en.w"); // multi.enwiki + fxt.Test("fr.wiktionary.org" , "fr.d"); // multi.frwiktionary + fxt.Test("commons.wikimedia.org" , "c"); // important.unique.commons + fxt.Test("species.wikimedia.org" , "species"); // important.unique.species + fxt.Test("www.wikidata.org" , "wd"); // important.unique.wikidata + fxt.Test("home" , "home"); // important.unique.xowa + fxt.Test("meta.wikimedia.org" , "meta"); // wikimedia.unique + fxt.Test("pl.wikimedia.org" , "pl.m"); // wikimedia.multi + fxt.Test("a.b.c" , "a.b.c"); // unkonwn + } +} +class Xow_abrv_xo__fxt { + public void Clear() {} + public void Test(String domain_str, String expd_abrv) { + Xow_domain_itm domain = Xow_domain_itm_.parse(Bry_.new_u8(domain_str)); + byte[] actl_abrv = Xow_abrv_xo_.To_bry(domain.Domain_bry(), domain.Lang_actl_key(), domain.Domain_type()); + Tfds.Eq(expd_abrv, String_.new_u8(actl_abrv), "To_bry"); + domain = Xow_abrv_xo_.To_itm(actl_abrv); + Tfds.Eq(domain_str, domain.Domain_str(), "To_itm"); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_itm.java b/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_itm.java index a27517de8..08ebe7c0b 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_itm.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_itm.java @@ -13,3 +13,36 @@ 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.wikis.domains; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.xowa.langs.*; +public class Xow_domain_itm { + Xow_domain_itm(byte[] domain_bry, Xow_domain_tid domain_type, Xol_lang_stub lang_actl_itm, byte[] lang_orig_key) { + this.domain_bry = domain_bry; this.domain_type = domain_type; this.lang_actl_itm = lang_actl_itm; this.lang_orig_key = lang_orig_key; + this.domain_str = String_.new_u8(domain_bry); + this.abrv_wm = Xow_abrv_wm_.To_abrv(this); + this.abrv_xo = Xow_abrv_xo_.To_bry(domain_bry, lang_orig_key, domain_type); + this.abrv_xo_str = String_.new_u8(abrv_xo); + } + public byte[] Domain_bry() {return domain_bry;} private final byte[] domain_bry; + public String Domain_str() {return domain_str;} private final String domain_str; + public Xow_domain_tid Domain_type() {return domain_type;} private final Xow_domain_tid domain_type; + public int Domain_type_id() {return domain_type.Tid();} + public byte[] Abrv_wm() {return abrv_wm;} private final byte[] abrv_wm; // EX: enwiki + public byte[] Abrv_xo() {return abrv_xo;} private final byte[] abrv_xo; // EX: en.w + public String Abrv_xo_str() {return abrv_xo_str;} private final String abrv_xo_str; // EX: en.w + public Xol_lang_stub Lang_actl_itm() {return lang_actl_itm;} private final Xol_lang_stub lang_actl_itm; // EX: zh + public int Lang_actl_uid() {return lang_actl_itm.Id();} + public byte[] Lang_actl_key() {return lang_actl_itm.Key();} + public byte[] Lang_orig_key() {return lang_orig_key;} private final byte[] lang_orig_key; // EX: lzh + public int Sort_idx() {return sort_idx;} public void Sort_idx_(int v) {sort_idx = v;} private int sort_idx = -1; // used for Search + public static Xow_domain_itm new_(byte[] domain_bry, int domain_tid, byte[] lang_key) { + Xol_lang_stub lang_actl_itm = Xol_lang_stub_.Get_by_key_or_intl(lang_key); + return new Xow_domain_itm(domain_bry, Xow_domain_tid_.Get_type_as_itm(domain_tid), lang_actl_itm, lang_actl_itm.Key()); + } + public static Xow_domain_itm new_(byte[] domain_bry, int domain_tid, Xol_lang_stub lang_actl, byte[] lang_orig) { + return new Xow_domain_itm(domain_bry, Xow_domain_tid_.Get_type_as_itm(domain_tid), lang_actl, lang_orig); + } + public static Xow_domain_itm new_(byte[] domain_bry, int domain_tid, byte[] lang_actl_key, byte[] lang_orig_key) { + return new Xow_domain_itm(domain_bry, Xow_domain_tid_.Get_type_as_itm(domain_tid), Xol_lang_stub_.Get_by_key_or_intl(lang_actl_key), lang_orig_key); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_itm_.java b/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_itm_.java index a27517de8..4fe455d30 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_itm_.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_itm_.java @@ -13,3 +13,138 @@ 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.wikis.domains; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.primitives.*; +import gplx.xowa.langs.*; +public class Xow_domain_itm_ { + public static final Xow_domain_itm[] Ary_empty = new Xow_domain_itm[0]; + public static Xow_domain_itm parse(byte[] raw) { + /* + ~{type}.org EX: wikimediafoundation + ~{type}.wikimedia.org EX: commons; species; meta; incubator + ~{lang}.~{type}.org EX: en.wikipedia, etc; + ~www.~{type}.org EX: mediawiki; wikidata; + */ + int raw_len = raw.length; + int dot_0 = Bry_find_.Find_fwd(raw, Byte_ascii.Dot, 0, raw_len); + if (dot_0 == Bry_find_.Not_found) { // 0 dots; check for "home" + return Bry_.Eq(raw, Xow_domain_tid_.Bry__home) + ? Xow_domain_uid_.To_domain(Xow_domain_uid_.Tid_xowa) + : new_other(raw); + } + int dot_1 = Bry_find_.Find_fwd(raw, Byte_ascii.Dot, dot_0 + 1, raw_len); + if (dot_1 == Bry_find_.Not_found) { // 1 dot; check for "wikimediafoundation.org" + return Bry_.Match(raw, 0, dot_0, Xow_domain_tid_.Bry__wmforg) + ? Xow_domain_itm.new_(raw, Xow_domain_tid_.Tid__wmfblog, Xol_lang_stub_.Key__unknown) + : new_other(raw); + } + // 2 dots + int seg_1_tid = Xow_domain_tid_.Get_type_as_tid(raw, dot_0 + 1, dot_1); // parse middle; EX: ".wikipedia." + if (seg_1_tid == Xow_domain_tid_.Tid__null) return new_other(raw); // seg_1 is unknown; return other; + switch (seg_1_tid) { + case Xow_domain_tid_.Tid__wikipedia: case Xow_domain_tid_.Tid__wiktionary: case Xow_domain_tid_.Tid__wikisource: case Xow_domain_tid_.Tid__wikibooks: + case Xow_domain_tid_.Tid__wikiversity: case Xow_domain_tid_.Tid__wikiquote: case Xow_domain_tid_.Tid__wikinews: case Xow_domain_tid_.Tid__wikivoyage: // ~{lang}.~{type}.org + byte[] lang_orig = Bry_.Mid(raw, 0, dot_0); + byte[] lang_actl = Get_lang_code_for_mw_messages_file(lang_orig); + return Xow_domain_itm.new_(raw, seg_1_tid, lang_actl, lang_orig); // NOTE: seg_tids must match wiki_tids + case Xow_domain_tid_.Tid__wikidata: case Xow_domain_tid_.Tid__mediawiki:// ~www.~{type}.org + return Xow_domain_itm.new_(raw, seg_1_tid, Xol_lang_stub_.Key__unknown); + case Xow_domain_tid_.Tid__wikimedia: // ~{type}.wikimedia.org; + int seg_0_tid = Xow_domain_tid_.Get_type_as_tid(raw, 0, dot_0); // try to get "incubator", "meta", etc.. + if (seg_0_tid == Xow_domain_tid_.Tid__null) { // not a known name; try language + byte[] lang_override = Xow_abrv_wm_override.To_lang_key_or_null(raw); // handle "lang-like" wikimedia domains like "ar.wikimedia.org" which is actually to "Argentina Wikimedia" + if (lang_override == null) { + Xol_lang_stub wikimedia_lang = Xol_lang_stub_.Get_by_key_or_null(raw, 0, dot_0); + return wikimedia_lang == null ? new_other(raw) : Xow_domain_itm.new_(raw, Xow_domain_tid_.Tid__wikimedia, wikimedia_lang.Key()); + } + else + return Xow_domain_itm.new_(raw, Xow_domain_tid_.Tid__wikimedia, lang_override, Bry_.Mid(raw, 0, dot_0)); + } + switch (seg_0_tid) { + case Xow_domain_tid_.Tid__commons: case Xow_domain_tid_.Tid__species: case Xow_domain_tid_.Tid__meta: case Xow_domain_tid_.Tid__incubator: + return Xow_domain_itm.new_(raw, seg_0_tid, Xol_lang_stub_.Key__unknown); // NOTE: seg_tids must match wiki_tids; NOTE: lang_key is "en" (really, "multi" but making things easier) + default: + return new_other(raw); + } + case Xow_domain_tid_.Tid__other: + default: + return new_other(raw); + } + } + public static boolean Match_lang(Xow_domain_itm domain, String match) { + // exit early if "*" + if (String_.Eq(match, Lang_key__all)) return true; + + // get lang + String cur = String_.new_u8(domain.Lang_actl_key()); + + // return true if direct match; EX: "en" <-> "en"; "de" <-> "de" + if (String_.Eq(cur, match)) return true; + + // handle special cases + if (String_.Eq(match, "en")) + return String_.In(domain.Domain_str(), "simple.wikipedia.org", "species.wikimedia.org", "www.wikidata.org", "commons.wikimedia.org"); + else if (String_.Eq(match, "zh")) + return String_.Eq(cur, "lzh"); + return false; + } + public static boolean Match_type(Xow_domain_itm domain, String match) { + // exit early if "*" + if (String_.Eq(match, Type_key__all)) return true; + + // get lang + String cur = domain.Domain_type().Key_str(); + + // return true if direct match; EX: "wiki" <-> "wiki"; "wiktionary" <-> "wiktionary" + if (String_.Eq(cur, match)) return true; + + // handle special cases + if (String_.Eq(match, "wikimisc")) + return String_.In(domain.Domain_str(), "species.wikimedia.org", "www.wikidata.org", "commons.wikimedia.org"); + return false; + } + public static final String Lang_key__all = "*", Type_key__all = "*"; + + private static Xow_domain_itm new_other(byte[] raw) {return Xow_domain_itm.new_(raw, Xow_domain_tid_.Tid__other, Xol_lang_stub_.Key__unknown);} + private static byte[] Get_lang_code_for_mw_messages_file(byte[] v) { + Object o = alt_domain__lang_by_subdomain.Get_by_bry(v); + return o == null ? v : (byte[])o; + } + public static byte[] Alt_domain__get_subdomain_by_lang(byte[] lang) { + Object o = alt_domain__subdomain_by_lang.Get_by_bry(lang); + return o == null ? lang : (byte[])o; + } + private static final Hash_adp_bry alt_domain__lang_by_subdomain = Hash_adp_bry.ci_a7() // ASCII:lang_code + .Add_str_obj("simple" , Bry_.new_a7("en")) + .Add_str_obj("zh-classical" , Bry_.new_a7("lzh")) + .Add_str_obj("no" , Bry_.new_a7("nb")) + ; + private static final Hash_adp_bry alt_domain__subdomain_by_lang = Hash_adp_bry.ci_a7() // ASCII:lang_code + .Add_str_obj("lzh" , Bry_.new_a7("zh-classical")) + .Add_str_obj("nb" , Bry_.new_a7("no")) + ; + public static final String + Str__enwiki = "en.wikipedia.org" + , Str__species = "species.wikimedia.org" + , Str__commons = "commons.wikimedia.org" + , Str__wikidata = "www.wikidata.org" + , Str__mediawiki = "www.mediawiki.org" + , Str__meta = "meta.wikimedia.org" + , Str__incubator = "incubator.wikimedia.org" + , Str__wmforg = "wikimediafoundation.org" + , Str__home = "home" + ; + public static final byte[] + Bry__enwiki = Bry_.new_a7(Str__enwiki) + , Bry__species = Bry_.new_a7(Str__species) + , Bry__commons = Bry_.new_a7(Str__commons) + , Bry__wikidata = Bry_.new_a7(Str__wikidata) + , Bry__mediawiki = Bry_.new_a7(Str__mediawiki) + , Bry__meta = Bry_.new_a7(Str__meta) + , Bry__incubator = Bry_.new_a7(Str__incubator) + , Bry__wmforg = Bry_.new_a7(Str__wmforg) + , Bry__home = Bry_.new_a7(Str__home) + , Bry__simplewiki = Bry_.new_a7("simple.wikipedia.org") + ; + public static final byte[] Seg__org = Bry_.new_a7("org"), Seg__www = Bry_.new_a7("www"); +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_itm_tst.java b/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_itm_tst.java index a27517de8..ece3a194d 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_itm_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_itm_tst.java @@ -13,3 +13,41 @@ 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.wikis.domains; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import org.junit.*; import gplx.core.tests.*; +public class Xow_domain_itm_tst { + private final Xow_domain_fxt fxt = new Xow_domain_fxt(); + @Test public void Parse_en_wikipedia() {fxt.Test_parse("en.wikipedia.org" , "en" , "en" , Xow_domain_tid_.Tid__wikipedia);} + @Test public void Parse_fr_wikipedia() {fxt.Test_parse("fr.wikipedia.org" , "fr" , "fr" , Xow_domain_tid_.Tid__wikipedia);} + @Test public void Parse_en_wiktionary() {fxt.Test_parse("en.wiktionary.org" , "en" , "en" , Xow_domain_tid_.Tid__wiktionary);} + @Test public void Parse_zh_classical_wikipedia() {fxt.Test_parse("zh-classical.wikipedia.org" , "zh-classical" , "lzh" , Xow_domain_tid_.Tid__wikipedia);} + @Test public void Parse_commons() {fxt.Test_parse("commons.wikimedia.org" , "" , "" , Xow_domain_tid_.Tid__commons);} + @Test public void Parse_species() {fxt.Test_parse("species.wikimedia.org" , "" , "" , Xow_domain_tid_.Tid__species);} + @Test public void Parse_ru_wikimedia_org() {fxt.Test_parse("ru.wikimedia.org" , "ru" , "ru" , Xow_domain_tid_.Tid__wikimedia);} + @Test public void Parse_home() {fxt.Test_parse("home" , "" , "" , Xow_domain_tid_.Tid__home);} + @Test public void Parse_other() {fxt.Test_parse("other.wiki" , "" , "" , Xow_domain_tid_.Tid__other);} + @Test public void Parse_ua_wikimedia_org() {fxt.Test_parse("ua.wikimedia.org" , "ua" , "uk" , Xow_domain_tid_.Tid__wikimedia);} + @Test public void Parse_ar_wikimedia_org() {fxt.Test_parse("ar.wikimedia.org" , "ar" , "es" , Xow_domain_tid_.Tid__wikimedia);} + @Test public void Parse_blank() {fxt.Test_parse("" , "" , "" , Xow_domain_tid_.Tid__other);} + @Test public void Match_lang() { + fxt.Test__match_lang_y("en", "en.wikipedia.org", "en.wiktionary.org", "simple.wikipedia.org", "species.wikimedia.org", "www.wikidata.org", "commons.wikimedia.org"); + fxt.Test__match_lang_y("fr", "fr.wikipedia.org", "fr.wiktionary.org"); + fxt.Test__match_lang_y("zh", "zh-classical.wikipedia.org"); + } +} +class Xow_domain_fxt { + public void Test_parse(String domain, String expd_orig_lang, String expd_actl_lang, int expd_tid) { + Xow_domain_itm actl = Xow_domain_itm_.parse(Bry_.new_a7(domain)); + Tfds.Eq_str(expd_orig_lang, String_.new_a7((actl.Lang_orig_key()))); + Tfds.Eq_str(expd_actl_lang, String_.new_a7((actl.Lang_actl_key()))); + Tfds.Eq_int(expd_tid, actl.Domain_type_id()); + } + public void Test__match_lang_y(String lang_code, String... domains) {Test__match_lang(Bool_.Y, lang_code, domains);} + public void Test__match_lang(boolean expd, String lang_key_str, String[] domains) { + int len = domains.length; + for (int i = 0; i < len; ++i) { + Xow_domain_itm domain = Xow_domain_itm_.parse(Bry_.new_u8(domains[i])); + Gftest.Eq__bool(expd, Xow_domain_itm_.Match_lang(domain, lang_key_str), lang_key_str + "|" + domains[i]); + } + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_regy.java b/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_regy.java index a27517de8..5847358db 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_regy.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_regy.java @@ -13,3 +13,850 @@ 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.wikis.domains; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public class Xow_domain_regy { + public static String[] All = new String[] +{ "commons.wikimedia.org" +, "species.wikimedia.org" +, "meta.wikimedia.org" +, "incubator.wikimedia.org" +, "www.wikidata.org" +, "www.mediawiki.org" +, "wikimediafoundation.org" +, "en.wikipedia.org" +, "en.wiktionary.org" +, "en.wikisource.org" +, "en.wikibooks.org" +, "en.wikiversity.org" +, "en.wikiquote.org" +, "en.wikinews.org" +, "en.wikivoyage.org" +, "de.wikipedia.org" +, "de.wiktionary.org" +, "de.wikisource.org" +, "de.wikibooks.org" +, "de.wikiversity.org" +, "de.wikiquote.org" +, "de.wikinews.org" +, "de.wikivoyage.org" +, "es.wikipedia.org" +, "es.wiktionary.org" +, "es.wikisource.org" +, "es.wikibooks.org" +, "es.wikiversity.org" +, "es.wikiquote.org" +, "es.wikinews.org" +, "es.wikivoyage.org" +, "fr.wikipedia.org" +, "fr.wiktionary.org" +, "fr.wikisource.org" +, "fr.wikibooks.org" +, "fr.wikiversity.org" +, "fr.wikiquote.org" +, "fr.wikinews.org" +, "fr.wikivoyage.org" +, "it.wikipedia.org" +, "it.wiktionary.org" +, "it.wikisource.org" +, "it.wikibooks.org" +, "it.wikiversity.org" +, "it.wikiquote.org" +, "it.wikinews.org" +, "it.wikivoyage.org" +, "ja.wikipedia.org" +, "ja.wiktionary.org" +, "ja.wikisource.org" +, "ja.wikibooks.org" +, "ja.wikiversity.org" +, "ja.wikiquote.org" +, "ja.wikinews.org" +, "nl.wikipedia.org" +, "nl.wiktionary.org" +, "nl.wikisource.org" +, "nl.wikibooks.org" +, "nl.wikiquote.org" +, "nl.wikinews.org" +, "nl.wikivoyage.org" +, "nl.wikimedia.org" +, "pl.wikipedia.org" +, "pl.wiktionary.org" +, "pl.wikisource.org" +, "pl.wikibooks.org" +, "pl.wikiquote.org" +, "pl.wikinews.org" +, "pl.wikivoyage.org" +, "pl.wikimedia.org" +, "pt.wikipedia.org" +, "pt.wiktionary.org" +, "pt.wikisource.org" +, "pt.wikibooks.org" +, "pt.wikiversity.org" +, "pt.wikiquote.org" +, "pt.wikinews.org" +, "pt.wikivoyage.org" +, "ru.wikipedia.org" +, "ru.wiktionary.org" +, "ru.wikisource.org" +, "ru.wikibooks.org" +, "ru.wikiversity.org" +, "ru.wikiquote.org" +, "ru.wikinews.org" +, "ru.wikivoyage.org" +, "ru.wikimedia.org" +, "ar.wikipedia.org" +, "ar.wiktionary.org" +, "ar.wikisource.org" +, "ar.wikibooks.org" +, "ar.wikiversity.org" +, "ar.wikiquote.org" +, "ar.wikinews.org" +, "ar.wikimedia.org" +, "ca.wikipedia.org" +, "ca.wiktionary.org" +, "ca.wikisource.org" +, "ca.wikibooks.org" +, "ca.wikiquote.org" +, "ca.wikinews.org" +, "ca.wikimedia.org" +, "cs.wikipedia.org" +, "cs.wiktionary.org" +, "cs.wikisource.org" +, "cs.wikibooks.org" +, "cs.wikiversity.org" +, "cs.wikiquote.org" +, "cs.wikinews.org" +, "da.wikipedia.org" +, "da.wiktionary.org" +, "da.wikisource.org" +, "da.wikibooks.org" +, "da.wikiquote.org" +, "eo.wikipedia.org" +, "eo.wiktionary.org" +, "eo.wikisource.org" +, "eo.wikibooks.org" +, "eo.wikiquote.org" +, "eo.wikinews.org" +, "fa.wikipedia.org" +, "fa.wiktionary.org" +, "fa.wikisource.org" +, "fa.wikibooks.org" +, "fa.wikiquote.org" +, "fa.wikinews.org" +, "fa.wikivoyage.org" +, "fi.wikipedia.org" +, "fi.wiktionary.org" +, "fi.wikisource.org" +, "fi.wikivoyage.org" +, "fi.wikibooks.org" +, "fi.wikiversity.org" +, "fi.wikiquote.org" +, "fi.wikinews.org" +, "fi.wikimedia.org" +, "hu.wikipedia.org" +, "hu.wiktionary.org" +, "hu.wikisource.org" +, "hu.wikibooks.org" +, "hu.wikiquote.org" +, "hu.wikinews.org" +, "id.wikipedia.org" +, "id.wiktionary.org" +, "id.wikisource.org" +, "id.wikibooks.org" +, "id.wikiquote.org" +, "kk.wikipedia.org" +, "kk.wiktionary.org" +, "kk.wikibooks.org" +, "kk.wikiquote.org" +, "ko.wikipedia.org" +, "ko.wiktionary.org" +, "ko.wikisource.org" +, "ko.wikibooks.org" +, "ko.wikiversity.org" +, "ko.wikiquote.org" +, "ko.wikinews.org" +, "lt.wikipedia.org" +, "lt.wiktionary.org" +, "lt.wikisource.org" +, "lt.wikibooks.org" +, "lt.wikiquote.org" +, "no.wikipedia.org" +, "no.wiktionary.org" +, "no.wikisource.org" +, "no.wikibooks.org" +, "no.wikiquote.org" +, "no.wikinews.org" +, "no.wikimedia.org" +, "ro.wikipedia.org" +, "ro.wiktionary.org" +, "ro.wikisource.org" +, "ro.wikibooks.org" +, "ro.wikiquote.org" +, "ro.wikinews.org" +, "ro.wikivoyage.org" +, "sk.wikipedia.org" +, "sk.wiktionary.org" +, "sk.wikisource.org" +, "sk.wikibooks.org" +, "sk.wikiquote.org" +, "sr.wikipedia.org" +, "sr.wiktionary.org" +, "sr.wikisource.org" +, "sr.wikibooks.org" +, "sr.wikiquote.org" +, "sr.wikinews.org" +, "sv.wikipedia.org" +, "sv.wiktionary.org" +, "sv.wikisource.org" +, "sv.wikibooks.org" +, "sv.wikiversity.org" +, "sv.wikiquote.org" +, "sv.wikinews.org" +, "sv.wikivoyage.org" +, "tr.wikipedia.org" +, "tr.wiktionary.org" +, "tr.wikisource.org" +, "tr.wikibooks.org" +, "tr.wikiquote.org" +, "tr.wikinews.org" +, "tr.wikimedia.org" +, "uk.wikipedia.org" +, "uk.wiktionary.org" +, "uk.wikisource.org" +, "uk.wikibooks.org" +, "uk.wikiquote.org" +, "uk.wikinews.org" +, "uk.wikivoyage.org" +, "ua.wikimedia.org" +, "vi.wikipedia.org" +, "vi.wiktionary.org" +, "vi.wikisource.org" +, "vi.wikibooks.org" +, "vi.wikiquote.org" +, "vi.wikivoyage.org" +, "zh.wikipedia.org" +, "zh.wiktionary.org" +, "zh.wikisource.org" +, "zh.wikibooks.org" +, "zh.wikiquote.org" +, "zh.wikinews.org" +, "zh.wikivoyage.org" +, "bg.wikipedia.org" +, "bg.wiktionary.org" +, "bg.wikisource.org" +, "bg.wikibooks.org" +, "bg.wikiquote.org" +, "bg.wikinews.org" +, "el.wikipedia.org" +, "el.wiktionary.org" +, "el.wikisource.org" +, "el.wikibooks.org" +, "el.wikiversity.org" +, "el.wikiquote.org" +, "el.wikinews.org" +, "el.wikivoyage.org" +, "et.wikipedia.org" +, "et.wiktionary.org" +, "et.wikisource.org" +, "et.wikibooks.org" +, "et.wikiquote.org" +, "et.wikimedia.org" +, "eu.wikipedia.org" +, "eu.wiktionary.org" +, "eu.wikibooks.org" +, "eu.wikiquote.org" +, "gl.wikipedia.org" +, "gl.wiktionary.org" +, "gl.wikisource.org" +, "gl.wikibooks.org" +, "gl.wikiquote.org" +, "he.wikipedia.org" +, "he.wiktionary.org" +, "he.wikisource.org" +, "he.wikibooks.org" +, "he.wikiquote.org" +, "he.wikinews.org" +, "he.wikivoyage.org" +, "hr.wikipedia.org" +, "hr.wiktionary.org" +, "hr.wikisource.org" +, "hr.wikibooks.org" +, "hr.wikiquote.org" +, "ms.wikipedia.org" +, "ms.wiktionary.org" +, "ms.wikibooks.org" +, "nn.wikipedia.org" +, "nn.wiktionary.org" +, "nn.wikiquote.org" +, "sh.wikipedia.org" +, "sh.wiktionary.org" +, "simple.wikipedia.org" +, "simple.wiktionary.org" +, "simple.wikibooks.org" +, "simple.wikiquote.org" +, "sl.wikipedia.org" +, "sl.wiktionary.org" +, "sl.wikisource.org" +, "sl.wikibooks.org" +, "sl.wikiversity.org" +, "sl.wikiquote.org" +, "th.wikipedia.org" +, "th.wiktionary.org" +, "th.wikisource.org" +, "th.wikibooks.org" +, "th.wikiquote.org" +, "th.wikinews.org" +, "vo.wikipedia.org" +, "vo.wiktionary.org" +, "vo.wikibooks.org" +, "vo.wikiquote.org" +, "hi.wikipedia.org" +, "hi.wiktionary.org" +, "hi.wikibooks.org" +, "hi.wikiquote.org" +, "ia.wikipedia.org" +, "ia.wiktionary.org" +, "ia.wikibooks.org" +, "la.wikipedia.org" +, "la.wiktionary.org" +, "la.wikisource.org" +, "la.wikibooks.org" +, "la.wikiquote.org" +, "aa.wikipedia.org" +, "aa.wiktionary.org" +, "aa.wikibooks.org" +, "ab.wikipedia.org" +, "ab.wiktionary.org" +, "ace.wikipedia.org" +, "af.wikipedia.org" +, "af.wiktionary.org" +, "af.wikibooks.org" +, "af.wikiquote.org" +, "ak.wikipedia.org" +, "ak.wiktionary.org" +, "ak.wikibooks.org" +, "als.wikipedia.org" +, "als.wiktionary.org" +, "als.wikibooks.org" +, "als.wikiquote.org" +, "am.wikipedia.org" +, "am.wiktionary.org" +, "am.wikiquote.org" +, "an.wikipedia.org" +, "an.wiktionary.org" +, "ang.wikipedia.org" +, "ang.wiktionary.org" +, "ang.wikisource.org" +, "ang.wikibooks.org" +, "ang.wikiquote.org" +, "arc.wikipedia.org" +, "arz.wikipedia.org" +, "as.wikipedia.org" +, "as.wiktionary.org" +, "as.wikisource.org" +, "as.wikibooks.org" +, "ast.wikipedia.org" +, "ast.wiktionary.org" +, "ast.wikibooks.org" +, "ast.wikiquote.org" +, "av.wikipedia.org" +, "av.wiktionary.org" +, "ay.wikipedia.org" +, "ay.wiktionary.org" +, "ay.wikibooks.org" +, "az.wikipedia.org" +, "az.wiktionary.org" +, "az.wikisource.org" +, "az.wikibooks.org" +, "az.wikiquote.org" +, "ba.wikipedia.org" +, "ba.wikibooks.org" +, "bar.wikipedia.org" +, "bat-smg.wikipedia.org" +, "bcl.wikipedia.org" +, "be.wikipedia.org" +, "be.wiktionary.org" +, "be.wikisource.org" +, "be.wikibooks.org" +, "be.wikiquote.org" +, "be.wikimedia.org" +, "be-x-old.wikipedia.org" +, "bh.wikipedia.org" +, "bh.wiktionary.org" +, "bi.wikipedia.org" +, "bi.wiktionary.org" +, "bi.wikibooks.org" +, "bjn.wikipedia.org" +, "bm.wikipedia.org" +, "bm.wiktionary.org" +, "bm.wikibooks.org" +, "bm.wikiquote.org" +, "bn.wikipedia.org" +, "bn.wiktionary.org" +, "bn.wikisource.org" +, "bn.wikibooks.org" +, "bo.wikipedia.org" +, "bo.wiktionary.org" +, "bo.wikibooks.org" +, "bpy.wikipedia.org" +, "br.wikipedia.org" +, "br.wiktionary.org" +, "br.wikisource.org" +, "br.wikiquote.org" +, "br.wikimedia.org" +, "bs.wikipedia.org" +, "bs.wiktionary.org" +, "bs.wikisource.org" +, "bs.wikibooks.org" +, "bs.wikiquote.org" +, "bs.wikinews.org" +, "bug.wikipedia.org" +, "bxr.wikipedia.org" +, "cbk-zam.wikipedia.org" +, "cdo.wikipedia.org" +, "ce.wikipedia.org" +, "ceb.wikipedia.org" +, "ch.wikipedia.org" +, "ch.wiktionary.org" +, "ch.wikibooks.org" +, "cho.wikipedia.org" +, "chr.wikipedia.org" +, "chr.wiktionary.org" +, "chy.wikipedia.org" +, "ckb.wikipedia.org" +, "co.wikipedia.org" +, "co.wiktionary.org" +, "co.wikibooks.org" +, "co.wikiquote.org" +, "co.wikimedia.org" +, "cr.wikipedia.org" +, "cr.wiktionary.org" +, "cr.wikiquote.org" +, "crh.wikipedia.org" +, "csb.wikipedia.org" +, "csb.wiktionary.org" +, "cu.wikipedia.org" +, "cv.wikipedia.org" +, "cv.wikibooks.org" +, "cy.wikipedia.org" +, "cy.wiktionary.org" +, "cy.wikisource.org" +, "cy.wikibooks.org" +, "cy.wikiquote.org" +, "diq.wikipedia.org" +, "dsb.wikipedia.org" +, "dv.wikipedia.org" +, "dv.wiktionary.org" +, "dz.wikipedia.org" +, "dz.wiktionary.org" +, "ee.wikipedia.org" +, "eml.wikipedia.org" +, "ext.wikipedia.org" +, "ff.wikipedia.org" +, "fiu-vro.wikipedia.org" +, "fj.wikipedia.org" +, "fj.wiktionary.org" +, "fo.wikipedia.org" +, "fo.wiktionary.org" +, "fo.wikisource.org" +, "frp.wikipedia.org" +, "frr.wikipedia.org" +, "fur.wikipedia.org" +, "fy.wikipedia.org" +, "fy.wiktionary.org" +, "fy.wikibooks.org" +, "ga.wikipedia.org" +, "ga.wiktionary.org" +, "ga.wikibooks.org" +, "ga.wikiquote.org" +, "gag.wikipedia.org" +, "gan.wikipedia.org" +, "gd.wikipedia.org" +, "gd.wiktionary.org" +, "glk.wikipedia.org" +, "gn.wikipedia.org" +, "gn.wiktionary.org" +, "gn.wikibooks.org" +, "got.wikipedia.org" +, "got.wikibooks.org" +, "gu.wikipedia.org" +, "gu.wiktionary.org" +, "gu.wikisource.org" +, "gu.wikibooks.org" +, "gu.wikiquote.org" +, "gv.wikipedia.org" +, "gv.wiktionary.org" +, "ha.wikipedia.org" +, "ha.wiktionary.org" +, "hak.wikipedia.org" +, "haw.wikipedia.org" +, "hif.wikipedia.org" +, "ho.wikipedia.org" +, "hsb.wikipedia.org" +, "hsb.wiktionary.org" +, "ht.wikipedia.org" +, "ht.wikisource.org" +, "hy.wikipedia.org" +, "hy.wiktionary.org" +, "hy.wikisource.org" +, "hy.wikibooks.org" +, "hy.wikiquote.org" +, "hz.wikipedia.org" +, "ie.wikipedia.org" +, "ie.wiktionary.org" +, "ie.wikibooks.org" +, "ig.wikipedia.org" +, "ii.wikipedia.org" +, "ik.wikipedia.org" +, "ik.wiktionary.org" +, "ilo.wikipedia.org" +, "io.wikipedia.org" +, "io.wiktionary.org" +, "is.wikipedia.org" +, "is.wiktionary.org" +, "is.wikisource.org" +, "is.wikibooks.org" +, "is.wikiquote.org" +, "iu.wikipedia.org" +, "iu.wiktionary.org" +, "jbo.wikipedia.org" +, "jbo.wiktionary.org" +, "jv.wikipedia.org" +, "jv.wiktionary.org" +, "ka.wikipedia.org" +, "ka.wiktionary.org" +, "ka.wikibooks.org" +, "ka.wikiquote.org" +, "kaa.wikipedia.org" +, "kab.wikipedia.org" +, "kbd.wikipedia.org" +, "kg.wikipedia.org" +, "ki.wikipedia.org" +, "kj.wikipedia.org" +, "kl.wikipedia.org" +, "kl.wiktionary.org" +, "km.wikipedia.org" +, "km.wiktionary.org" +, "km.wikibooks.org" +, "kn.wikipedia.org" +, "kn.wiktionary.org" +, "kn.wikisource.org" +, "kn.wikibooks.org" +, "kn.wikiquote.org" +, "koi.wikipedia.org" +, "kr.wikipedia.org" +, "kr.wikiquote.org" +, "krc.wikipedia.org" +, "ks.wikipedia.org" +, "ks.wiktionary.org" +, "ks.wikibooks.org" +, "ks.wikiquote.org" +, "ksh.wikipedia.org" +, "ku.wikipedia.org" +, "ku.wiktionary.org" +, "ku.wikibooks.org" +, "ku.wikiquote.org" +, "kv.wikipedia.org" +, "kw.wikipedia.org" +, "kw.wiktionary.org" +, "kw.wikiquote.org" +, "ky.wikipedia.org" +, "ky.wiktionary.org" +, "ky.wikibooks.org" +, "ky.wikiquote.org" +, "lad.wikipedia.org" +, "lb.wikipedia.org" +, "lb.wiktionary.org" +, "lb.wikibooks.org" +, "lb.wikiquote.org" +, "lbe.wikipedia.org" +, "lez.wikipedia.org" +, "lg.wikipedia.org" +, "li.wikipedia.org" +, "li.wiktionary.org" +, "li.wikisource.org" +, "li.wikibooks.org" +, "li.wikiquote.org" +, "lij.wikipedia.org" +, "lmo.wikipedia.org" +, "ln.wikipedia.org" +, "ln.wiktionary.org" +, "ln.wikibooks.org" +, "lo.wikipedia.org" +, "lo.wiktionary.org" +, "ltg.wikipedia.org" +, "lv.wikipedia.org" +, "lv.wiktionary.org" +, "lv.wikibooks.org" +, "mai.wikipedia.org" +, "map-bms.wikipedia.org" +, "mdf.wikipedia.org" +, "mg.wikipedia.org" +, "mg.wiktionary.org" +, "mg.wikibooks.org" +, "mh.wikipedia.org" +, "mh.wiktionary.org" +, "mhr.wikipedia.org" +, "mi.wikipedia.org" +, "mi.wiktionary.org" +, "mi.wikibooks.org" +, "min.wikipedia.org" +, "mk.wikipedia.org" +, "mk.wiktionary.org" +, "mk.wikisource.org" +, "mk.wikibooks.org" +, "mk.wikimedia.org" +, "ml.wikipedia.org" +, "ml.wiktionary.org" +, "ml.wikisource.org" +, "ml.wikibooks.org" +, "ml.wikiquote.org" +, "mn.wikipedia.org" +, "mn.wiktionary.org" +, "mn.wikibooks.org" +, "mo.wikipedia.org" +, "mo.wiktionary.org" +, "mr.wikipedia.org" +, "mr.wiktionary.org" +, "mr.wikisource.org" +, "mr.wikibooks.org" +, "mr.wikiquote.org" +, "mrj.wikipedia.org" +, "mt.wikipedia.org" +, "mt.wiktionary.org" +, "mus.wikipedia.org" +, "mwl.wikipedia.org" +, "my.wikipedia.org" +, "my.wiktionary.org" +, "my.wikibooks.org" +, "myv.wikipedia.org" +, "mzn.wikipedia.org" +, "na.wikipedia.org" +, "na.wiktionary.org" +, "na.wikibooks.org" +, "na.wikiquote.org" +, "nah.wikipedia.org" +, "nah.wiktionary.org" +, "nah.wikibooks.org" +, "nap.wikipedia.org" +, "nds.wikipedia.org" +, "nds.wiktionary.org" +, "nds.wikibooks.org" +, "nds.wikiquote.org" +, "nds-nl.wikipedia.org" +, "ne.wikipedia.org" +, "ne.wiktionary.org" +, "ne.wikibooks.org" +, "new.wikipedia.org" +, "ng.wikipedia.org" +, "nov.wikipedia.org" +, "nrm.wikipedia.org" +, "nso.wikipedia.org" +, "nv.wikipedia.org" +, "ny.wikipedia.org" +, "oc.wikipedia.org" +, "oc.wiktionary.org" +, "oc.wikibooks.org" +, "om.wikipedia.org" +, "om.wiktionary.org" +, "or.wikipedia.org" +, "or.wiktionary.org" +, "or.wikisource.org" +, "os.wikipedia.org" +, "pa.wikipedia.org" +, "pa.wiktionary.org" +, "pa.wikibooks.org" +, "pag.wikipedia.org" +, "pam.wikipedia.org" +, "pap.wikipedia.org" +, "pcd.wikipedia.org" +, "pdc.wikipedia.org" +, "pfl.wikipedia.org" +, "pi.wikipedia.org" +, "pi.wiktionary.org" +, "pih.wikipedia.org" +, "pms.wikipedia.org" +, "pnb.wikipedia.org" +, "pnb.wiktionary.org" +, "pnt.wikipedia.org" +, "ps.wikipedia.org" +, "ps.wiktionary.org" +, "ps.wikibooks.org" +, "qu.wikipedia.org" +, "qu.wiktionary.org" +, "qu.wikibooks.org" +, "qu.wikiquote.org" +, "rm.wikipedia.org" +, "rm.wiktionary.org" +, "rm.wikibooks.org" +, "rmy.wikipedia.org" +, "rn.wikipedia.org" +, "rn.wiktionary.org" +, "roa-rup.wikipedia.org" +, "roa-rup.wiktionary.org" +, "roa-tara.wikipedia.org" +, "rue.wikipedia.org" +, "rw.wikipedia.org" +, "rw.wiktionary.org" +, "sa.wikipedia.org" +, "sa.wiktionary.org" +, "sa.wikisource.org" +, "sa.wikibooks.org" +, "sa.wikiquote.org" +, "sah.wikipedia.org" +, "sah.wikisource.org" +, "sc.wikipedia.org" +, "sc.wiktionary.org" +, "scn.wikipedia.org" +, "scn.wiktionary.org" +, "sco.wikipedia.org" +, "sd.wikipedia.org" +, "sd.wiktionary.org" +, "sd.wikinews.org" +, "se.wikipedia.org" +, "se.wikibooks.org" +, "se.wikimedia.org" +, "sg.wikipedia.org" +, "sg.wiktionary.org" +, "si.wikipedia.org" +, "si.wiktionary.org" +, "si.wikibooks.org" +, "sm.wikipedia.org" +, "sm.wiktionary.org" +, "sn.wikipedia.org" +, "sn.wiktionary.org" +, "so.wikipedia.org" +, "so.wiktionary.org" +, "sq.wikipedia.org" +, "sq.wiktionary.org" +, "sq.wikibooks.org" +, "sq.wikiquote.org" +, "sq.wikinews.org" +, "srn.wikipedia.org" +, "ss.wikipedia.org" +, "ss.wiktionary.org" +, "st.wikipedia.org" +, "st.wiktionary.org" +, "stq.wikipedia.org" +, "su.wikipedia.org" +, "su.wiktionary.org" +, "su.wikibooks.org" +, "su.wikiquote.org" +, "sw.wikipedia.org" +, "sw.wiktionary.org" +, "sw.wikibooks.org" +, "szl.wikipedia.org" +, "ta.wikipedia.org" +, "ta.wiktionary.org" +, "ta.wikisource.org" +, "ta.wikibooks.org" +, "ta.wikiquote.org" +, "ta.wikinews.org" +, "te.wikipedia.org" +, "te.wiktionary.org" +, "te.wikisource.org" +, "te.wikibooks.org" +, "te.wikiquote.org" +, "tet.wikipedia.org" +, "tg.wikipedia.org" +, "tg.wiktionary.org" +, "tg.wikibooks.org" +, "ti.wikipedia.org" +, "ti.wiktionary.org" +, "tk.wikipedia.org" +, "tk.wiktionary.org" +, "tk.wikibooks.org" +, "tk.wikiquote.org" +, "tl.wikipedia.org" +, "tl.wiktionary.org" +, "tl.wikibooks.org" +, "tn.wikipedia.org" +, "tn.wiktionary.org" +, "to.wikipedia.org" +, "to.wiktionary.org" +, "tpi.wikipedia.org" +, "tpi.wiktionary.org" +, "ts.wikipedia.org" +, "ts.wiktionary.org" +, "tt.wikipedia.org" +, "tt.wiktionary.org" +, "tt.wikibooks.org" +, "tt.wikiquote.org" +, "tum.wikipedia.org" +, "tw.wikipedia.org" +, "tw.wiktionary.org" +, "ty.wikipedia.org" +, "tyv.wikipedia.org" +, "udm.wikipedia.org" +, "ug.wikipedia.org" +, "ug.wiktionary.org" +, "ug.wikibooks.org" +, "ug.wikiquote.org" +, "ur.wikipedia.org" +, "ur.wiktionary.org" +, "ur.wikibooks.org" +, "ur.wikiquote.org" +, "uz.wikipedia.org" +, "uz.wiktionary.org" +, "uz.wikibooks.org" +, "uz.wikiquote.org" +, "ve.wikipedia.org" +, "vec.wikipedia.org" +, "vec.wiktionary.org" +, "vec.wikisource.org" +, "vep.wikipedia.org" +, "vls.wikipedia.org" +, "wa.wikipedia.org" +, "wa.wiktionary.org" +, "wa.wikibooks.org" +, "war.wikipedia.org" +, "wo.wikipedia.org" +, "wo.wiktionary.org" +, "wo.wikiquote.org" +, "wuu.wikipedia.org" +, "xal.wikipedia.org" +, "xh.wikipedia.org" +, "xh.wiktionary.org" +, "xh.wikibooks.org" +, "xmf.wikipedia.org" +, "yi.wikipedia.org" +, "yi.wiktionary.org" +, "yi.wikisource.org" +, "yo.wikipedia.org" +, "yo.wiktionary.org" +, "yo.wikibooks.org" +, "za.wikipedia.org" +, "za.wiktionary.org" +, "za.wikibooks.org" +, "za.wikiquote.org" +, "zea.wikipedia.org" +, "zh-classical.wikipedia.org" +, "zh-min-nan.wikipedia.org" +, "zh-min-nan.wiktionary.org" +, "zh-min-nan.wikisource.org" +, "zh-min-nan.wikibooks.org" +, "zh-min-nan.wikiquote.org" +, "zh-yue.wikipedia.org" +, "zu.wikipedia.org" +, "zu.wiktionary.org" +, "zu.wikibooks.org" +, "gom.wikipedia.org" +, "lrc.wikipedia.org" +, "azb.wikipedia.org" +, "ady.wikipedia.org" +, "jam.wikipedia.org" +, "tcy.wikipedia.org" +, "olo.wikipedia.org" +, "dty.wikipedia.org" +, "pa.wikisource.org" +, "atj.wikipedia.org" +, "kbp.wikipedia.org" +, "din.wikipedia.org" +, "hi.wikiversity.org" +}; +//, "als.wikisource.org" +//, "als.wikinews.org" +//, "nds.wikinews.org" +//, "ba.wiktionary.org" +//, "tokipona.wikibooks.org" +//, "ve.wikimedia.org" // NOTE: moved:DATE:2015-04-06 +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_tid.java b/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_tid.java index a27517de8..d50295cf3 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_tid.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_tid.java @@ -13,3 +13,25 @@ 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.wikis.domains; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public class Xow_domain_tid { + public Xow_domain_tid(boolean multi_lang, int src, int tid, byte[] key_bry, byte[] abrv, byte[] domain_bry) { + this.multi_lang = multi_lang; this.src = src; this.tid = tid; this.key_bry = key_bry; this.abrv = abrv; this.domain_bry = domain_bry; + this.key_str = String_.new_u8(key_bry); + } + public boolean Multi_lang() {return multi_lang;} private final boolean multi_lang; // EX: y + public int Src() {return src;} private final int src; // EX: 1 (wm,mw,wk,xo) + public int Tid() {return tid;} private final int tid; // EX: 1 (Tid_wikipedia) + public String Key_str() {return key_str;} private final String key_str; // EX: wikipedia + public byte[] Key_bry() {return key_bry;} private final byte[] key_bry; // EX: wikipedia + public byte[] Abrv() {return abrv;} private final byte[] abrv; // EX: w + public byte[] Domain_bry() {return domain_bry;} private byte[] domain_bry; // EX: .wikipedia.org + public byte[] Display_bry() {return Bry_.Ucase__1st(key_bry);} // EX: Wikipedia + + public static final int + Src__wmf = 1 // administered by wmf; wikipedia, etc. + , Src__wikia = 2 // *.wikia.com + , Src__mw = 3 // mediawiki installations not part of wmf, wikia + , Src__xowa = 4 // xowa + ; +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_tid_.java b/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_tid_.java index a27517de8..39aab49e6 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_tid_.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_tid_.java @@ -13,3 +13,107 @@ 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.wikis.domains; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public class Xow_domain_tid_ { + public static final int + Tid__null = 0 + , Tid__wikipedia = 1 + , Tid__wiktionary = 2 + , Tid__wikisource = 3 + , Tid__wikivoyage = 4 + , Tid__wikiquote = 5 + , Tid__wikibooks = 6 + , Tid__wikiversity = 7 + , Tid__wikinews = 8 + , Tid__wikimedia = 9 + , Tid__species = 10 + , Tid__commons = 11 + , Tid__wikidata = 12 + , Tid__mediawiki = 13 + , Tid__meta = 14 + , Tid__incubator = 15 + , Tid__wmfblog = 16 + , Tid__home = 17 + , Tid__other = 18 + , Tid___len = 19 + ; + public static final String // SERIALIZED:xowa.gfs + Str__wikipedia = "wikipedia" + , Str__wiktionary = "wiktionary" + , Str__wikisource = "wikisource" + , Str__wikivoyage = "wikivoyage" + , Str__wikiquote = "wikiquote" + , Str__wikibooks = "wikibooks" + , Str__wikiversity = "wikiversity" + , Str__wikinews = "wikinews" + , Str__wikimedia = "wikimedia" + , Str__species = "species" + , Str__commons = "commons" + , Str__wikidata = "wikidata" + , Str__mediawiki = "mediawiki" + , Str__meta = "meta" + , Str__incubator = "incubator" + , Str__wmforg = "wikimediafoundation" + , Str__home = "home" + , Str__other = "other" + ; + public static final byte[] + Bry__wikipedia = Bry_.new_a7(Str__wikipedia) + , Bry__wiktionary = Bry_.new_a7(Str__wiktionary) + , Bry__wikisource = Bry_.new_a7(Str__wikisource) + , Bry__wikivoyage = Bry_.new_a7(Str__wikivoyage) + , Bry__wikiquote = Bry_.new_a7(Str__wikiquote) + , Bry__wikibooks = Bry_.new_a7(Str__wikibooks) + , Bry__wikiversity = Bry_.new_a7(Str__wikiversity) + , Bry__wikinews = Bry_.new_a7(Str__wikinews) + , Bry__wikimedia = Bry_.new_a7(Str__wikimedia) + , Bry__species = Bry_.new_a7(Str__species) + , Bry__commons = Bry_.new_a7(Str__commons) + , Bry__wikidata = Bry_.new_a7(Str__wikidata) + , Bry__mediawiki = Bry_.new_a7(Str__mediawiki) + , Bry__meta = Bry_.new_a7(Str__meta) + , Bry__incubator = Bry_.new_a7(Str__incubator) + , Bry__wmforg = Bry_.new_a7(Str__wmforg) + , Bry__home = Bry_.new_a7(Str__home) + , Bry__other = Bry_.new_a7(Str__other) + ; + private static final Xow_domain_tid[] ary = new Xow_domain_tid[Tid___len]; + private static final Hash_adp_bry type_regy = Hash_adp_bry.ci_a7(); // LOC:must go before new_() + private static final Hash_adp_bry abrv_regy = Hash_adp_bry.cs(); // LOC:must go before new_() + public static final Xow_domain_tid + Itm__wikipedia = new_(Bool_.Y , Xow_domain_tid.Src__wmf , Tid__wikipedia , Bry__wikipedia , "w" , ".wikipedia.org") + , Itm__wiktionary = new_(Bool_.Y , Xow_domain_tid.Src__wmf , Tid__wiktionary , Bry__wiktionary , "d" , ".wiktionary.org") + , Itm__wikisource = new_(Bool_.Y , Xow_domain_tid.Src__wmf , Tid__wikisource , Bry__wikisource , "s" , ".wikisource.org") + , Itm__wikivoyage = new_(Bool_.Y , Xow_domain_tid.Src__wmf , Tid__wikivoyage , Bry__wikivoyage , "v" , ".wikivoyage.org") + , Itm__wikiquote = new_(Bool_.Y , Xow_domain_tid.Src__wmf , Tid__wikiquote , Bry__wikiquote , "q" , ".wikiquote.org") + , Itm__wikibooks = new_(Bool_.Y , Xow_domain_tid.Src__wmf , Tid__wikibooks , Bry__wikibooks , "b" , ".wikibooks.org") + , Itm__wikiversity = new_(Bool_.Y , Xow_domain_tid.Src__wmf , Tid__wikiversity , Bry__wikiversity , "u" , ".wikiversity.org") + , Itm__wikinews = new_(Bool_.Y , Xow_domain_tid.Src__wmf , Tid__wikinews , Bry__wikinews , "n" , ".wikinews.org") + , Itm__wikimedia = new_(Bool_.Y , Xow_domain_tid.Src__wmf , Tid__wikimedia , Bry__wikimedia , "m" , ".wikimedia.org") + , Itm__species = new_(Bool_.N , Xow_domain_tid.Src__wmf , Tid__species , Bry__species , "species" , Xow_domain_itm_.Str__species) + , Itm__commons = new_(Bool_.N , Xow_domain_tid.Src__wmf , Tid__commons , Bry__commons , "c" , Xow_domain_itm_.Str__commons) + , Itm__wikidata = new_(Bool_.N , Xow_domain_tid.Src__wmf , Tid__wikidata , Bry__wikidata , "wd" , Xow_domain_itm_.Str__wikidata) + , Itm__mediawiki = new_(Bool_.N , Xow_domain_tid.Src__wmf , Tid__mediawiki , Bry__mediawiki , "mw" , Xow_domain_itm_.Str__mediawiki) + , Itm__meta = new_(Bool_.N , Xow_domain_tid.Src__wmf , Tid__meta , Bry__meta , "meta" , Xow_domain_itm_.Str__meta) + , Itm__incubator = new_(Bool_.N , Xow_domain_tid.Src__wmf , Tid__incubator , Bry__incubator , "qb" , Xow_domain_itm_.Str__incubator) + , Itm__wmforg = new_(Bool_.N , Xow_domain_tid.Src__wmf , Tid__wmfblog , Bry__wmforg , "wmf" , Xow_domain_itm_.Str__wmforg) + , Itm__home = new_(Bool_.N , Xow_domain_tid.Src__xowa, Tid__home , Bry__home , "home" , Xow_domain_itm_.Str__home) + , Itm__other = new_(Bool_.N , Xow_domain_tid.Src__mw , Tid__other , Bry__other , "" , "") + ; + private static Xow_domain_tid new_(boolean multi_lang, int src, int tid, byte[] key_bry, String abrv_xo_str, String domain_bry) { + byte[] abrv_xo_bry = Bry_.new_u8(abrv_xo_str); + Xow_domain_tid rv = new Xow_domain_tid(multi_lang, src, tid, key_bry, abrv_xo_bry, Bry_.new_u8(domain_bry)); + ary[tid] = rv; + type_regy.Add(key_bry, rv); + abrv_regy.Add(abrv_xo_bry, rv); + return rv; + } + public static Xow_domain_tid Get_abrv_as_itm(byte[] src, int bgn, int end) {return (Xow_domain_tid)abrv_regy.Get_by_mid(src, bgn, end);} + public static Xow_domain_tid Get_type_as_itm(int tid) {return ary[tid];} + public static byte[] Get_type_as_bry(int tid) {return ary[tid].Key_bry();} + public static int Get_type_as_tid(byte[] src) {return Get_type_as_tid(src, 0, src.length);} + public static int Get_type_as_tid(byte[] src, int bgn, int end) { + Object o = type_regy.Get_by_mid(src, bgn, end); + return o == null ? Xow_domain_tid_.Tid__null : ((Xow_domain_tid)o).Tid(); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_uid_.java b/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_uid_.java index a27517de8..4e66513cb 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_uid_.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_uid_.java @@ -13,3 +13,88 @@ 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.wikis.domains; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.xowa.langs.*; +public class Xow_domain_uid_ { + public static final int + Tid_null = 0 + , Tid_xowa = 1 + , Tid_commons = 2 + , Tid_wikidata = 3 + , Tid_mediawiki = 20 + , Tid_meta = 21 + , Tid_incubator = 22 + , Tid_wmfblog = 23 + , Tid_species = 24 + ; + private static final int + Tid_sub_wikipedia = 0 + , Tid_sub_wiktionary = 1 + , Tid_sub_wikisource = 2 + , Tid_sub_wikivoyage = 3 + , Tid_sub_wikiquote = 4 + , Tid_sub_wikibooks = 5 + , Tid_sub_wikiversity = 6 + , Tid_sub_wikinews = 7 + , Tid_sub_wikimedia = 8 + ; + private static final int Const_system_reserved = 100, Const_lang_reserved = 20; + public static int To_int(Xow_domain_itm domain) { + int domain_tid = 0; + switch (domain.Domain_type_id()) { + case Xow_domain_tid_.Tid__home: return Tid_xowa; + case Xow_domain_tid_.Tid__commons: return Tid_commons; + case Xow_domain_tid_.Tid__wikidata: return Tid_wikidata; + case Xow_domain_tid_.Tid__mediawiki: return Tid_mediawiki; + case Xow_domain_tid_.Tid__meta: return Tid_meta; + case Xow_domain_tid_.Tid__incubator: return Tid_incubator; + case Xow_domain_tid_.Tid__wmfblog: return Tid_wmfblog; + case Xow_domain_tid_.Tid__species: return Tid_species; + case Xow_domain_tid_.Tid__wikipedia: domain_tid = Tid_sub_wikipedia; break; + case Xow_domain_tid_.Tid__wiktionary: domain_tid = Tid_sub_wiktionary; break; + case Xow_domain_tid_.Tid__wikisource: domain_tid = Tid_sub_wikisource; break; + case Xow_domain_tid_.Tid__wikivoyage: domain_tid = Tid_sub_wikivoyage; break; + case Xow_domain_tid_.Tid__wikiquote: domain_tid = Tid_sub_wikiquote; break; + case Xow_domain_tid_.Tid__wikibooks: domain_tid = Tid_sub_wikibooks; break; + case Xow_domain_tid_.Tid__wikiversity: domain_tid = Tid_sub_wikiversity; break; + case Xow_domain_tid_.Tid__wikinews: domain_tid = Tid_sub_wikinews; break; + case Xow_domain_tid_.Tid__wikimedia: domain_tid = Tid_sub_wikimedia; break; + default: throw Err_.new_unhandled(domain.Domain_type_id()); + } + return Const_system_reserved // reserve first 100 slots + + domain_tid // domain_tid assigned above + + (domain.Lang_actl_uid() * Const_lang_reserved) // reserve 20 wikis per lang + ; + } + public static Xow_domain_itm To_domain(int tid) { + switch (tid) { + case Tid_xowa: return Xow_domain_itm.new_(Xow_domain_itm_.Bry__home, Xow_domain_tid_.Tid__home, Xol_lang_stub_.Key__unknown); + case Tid_commons: return Xow_domain_itm.new_(Xow_domain_itm_.Bry__commons, Xow_domain_tid_.Tid__commons, Xol_lang_stub_.Key__unknown); + case Tid_wikidata: return Xow_domain_itm.new_(Xow_domain_itm_.Bry__wikidata, Xow_domain_tid_.Tid__commons, Xol_lang_stub_.Key__unknown); + case Tid_mediawiki: return Xow_domain_itm.new_(Xow_domain_itm_.Bry__mediawiki, Xow_domain_tid_.Tid__mediawiki, Xol_lang_stub_.Key__unknown); + case Tid_meta: return Xow_domain_itm.new_(Xow_domain_itm_.Bry__meta, Xow_domain_tid_.Tid__meta, Xol_lang_stub_.Key__unknown); + case Tid_incubator: return Xow_domain_itm.new_(Xow_domain_itm_.Bry__incubator, Xow_domain_tid_.Tid__incubator, Xol_lang_stub_.Key__unknown); + case Tid_wmfblog: return Xow_domain_itm.new_(Xow_domain_itm_.Bry__wmforg, Xow_domain_tid_.Tid__wmfblog, Xol_lang_stub_.Key__unknown); + case Tid_species: return Xow_domain_itm.new_(Xow_domain_itm_.Bry__species, Xow_domain_tid_.Tid__species, Xol_lang_stub_.Key__unknown); + } + int tmp = tid - Const_system_reserved; + int lang_id = tmp / 20; + int type_id = tmp % 20; + int tid_int = 0; byte[] tid_bry = null; + switch (type_id) { + case Tid_sub_wikipedia: tid_int = Xow_domain_tid_.Tid__wikipedia; tid_bry = Xow_domain_tid_.Bry__wikipedia; break; + case Tid_sub_wiktionary: tid_int = Xow_domain_tid_.Tid__wiktionary; tid_bry = Xow_domain_tid_.Bry__wiktionary; break; + case Tid_sub_wikisource: tid_int = Xow_domain_tid_.Tid__wikisource; tid_bry = Xow_domain_tid_.Bry__wikisource; break; + case Tid_sub_wikivoyage: tid_int = Xow_domain_tid_.Tid__wikivoyage; tid_bry = Xow_domain_tid_.Bry__wikivoyage; break; + case Tid_sub_wikiquote: tid_int = Xow_domain_tid_.Tid__wikiquote; tid_bry = Xow_domain_tid_.Bry__wikiquote; break; + case Tid_sub_wikibooks: tid_int = Xow_domain_tid_.Tid__wikibooks; tid_bry = Xow_domain_tid_.Bry__wikibooks; break; + case Tid_sub_wikiversity: tid_int = Xow_domain_tid_.Tid__wikiversity; tid_bry = Xow_domain_tid_.Bry__wikiversity; break; + case Tid_sub_wikinews: tid_int = Xow_domain_tid_.Tid__wikinews; tid_bry = Xow_domain_tid_.Bry__wikinews; break; + case Tid_sub_wikimedia: tid_int = Xow_domain_tid_.Tid__wikimedia; tid_bry = Xow_domain_tid_.Bry__wikimedia; break; + default: throw Err_.new_unhandled(type_id); + } + Xol_lang_stub lang = Xol_lang_stub_.Get_by_id(lang_id); + byte[] domain_bry = Bry_.Add(lang.Key(), Byte_ascii.Dot_bry, tid_bry, Byte_ascii.Dot_bry, Xow_domain_itm_.Seg__org); + return Xow_domain_itm.new_(domain_bry, tid_int, lang, lang.Key()); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_uid__tst.java b/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_uid__tst.java index a27517de8..2807be403 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_uid__tst.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/Xow_domain_uid__tst.java @@ -13,3 +13,23 @@ 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.wikis.domains; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import org.junit.*; +public class Xow_domain_uid__tst { + @Before public void init() {fxt.Clear();} private final Xow_domain_uid__fxt fxt = new Xow_domain_uid__fxt(); + @Test public void Basic() { + fxt.Test(Xow_domain_uid_.Tid_commons , "commons.wikimedia.org" , "", Xow_domain_tid_.Tid__commons); + fxt.Test(100 , "en.wikipedia.org" , "en", Xow_domain_tid_.Tid__wikipedia); + } +} +class Xow_domain_uid__fxt { + public void Clear() {} + public void Test(int tid, String domain_str, String expd_lang, int expd_tid) { + byte[] domain_bry = Bry_.new_a7(domain_str); + Xow_domain_itm actl_domain = Xow_domain_uid_.To_domain(tid); + Tfds.Eq_bry(domain_bry , actl_domain.Domain_bry()); + Tfds.Eq_bry(Bry_.new_a7(expd_lang) , actl_domain.Lang_actl_key()); + Tfds.Eq(expd_tid , actl_domain.Domain_type_id()); + Tfds.Eq(tid, Xow_domain_uid_.To_int(actl_domain)); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm.java b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm.java index a27517de8..d46f8df01 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm.java @@ -13,3 +13,8 @@ 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.wikis.domains.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; +import gplx.core.primitives.*; +public interface Xow_domain_crt_itm { + boolean Matches(Xow_domain_itm cur, Xow_domain_itm comp); +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm_.java b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm_.java index a27517de8..f9a69c06e 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm_.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm_.java @@ -13,3 +13,50 @@ 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.wikis.domains.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; +class Xow_domain_crt_itm_ { + public static final Xow_domain_crt_itm Null = null; +} +class Xow_domain_crt_itm__in implements Xow_domain_crt_itm { + private final Xow_domain_crt_itm[] ary; + public Xow_domain_crt_itm__in(Xow_domain_crt_itm[] ary) {this.ary = ary;} + public boolean Matches(Xow_domain_itm cur, Xow_domain_itm comp) { + int len = ary.length; + for (int i = 0; i < len; ++i) { + Xow_domain_crt_itm itm = ary[i]; + if (itm.Matches(cur, comp)) return true; + } + return false; + } +} +class Xow_domain_crt_itm__any_standard implements Xow_domain_crt_itm { + public boolean Matches(Xow_domain_itm cur, Xow_domain_itm comp) { + switch (comp.Domain_type_id()) { + case Xow_domain_tid_.Tid__wikipedia: + case Xow_domain_tid_.Tid__wiktionary: + case Xow_domain_tid_.Tid__wikisource: + case Xow_domain_tid_.Tid__wikivoyage: + case Xow_domain_tid_.Tid__wikiquote: + case Xow_domain_tid_.Tid__wikibooks: + case Xow_domain_tid_.Tid__wikiversity: + case Xow_domain_tid_.Tid__wikinews: return true; + default: return false; + } + } + public static final Xow_domain_crt_itm__any_standard Instance = new Xow_domain_crt_itm__any_standard(); Xow_domain_crt_itm__any_standard() {} +} +class Xow_domain_crt_itm__lang implements Xow_domain_crt_itm { + private final byte[] lang_key; + public Xow_domain_crt_itm__lang(byte[] lang_key) {this.lang_key = lang_key;} + public boolean Matches(Xow_domain_itm cur, Xow_domain_itm comp) {return Bry_.Eq(comp.Lang_orig_key(), lang_key);} +} +class Xow_domain_crt_itm__type implements Xow_domain_crt_itm { + private final int wiki_tid; + public Xow_domain_crt_itm__type(int wiki_tid) {this.wiki_tid = wiki_tid;} + public boolean Matches(Xow_domain_itm cur, Xow_domain_itm comp) {return comp.Domain_type_id() == wiki_tid;} +} +class Xow_domain_crt_itm__wiki implements Xow_domain_crt_itm { + private final byte[] domain; + public Xow_domain_crt_itm__wiki(byte[] domain) {this.domain = domain;} + public boolean Matches(Xow_domain_itm cur, Xow_domain_itm comp) {return Bry_.Eq(comp.Domain_bry(), domain);} +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__any_wiki.java b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__any_wiki.java index a27517de8..a143c7543 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__any_wiki.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__any_wiki.java @@ -13,3 +13,8 @@ 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.wikis.domains.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; +class Xow_domain_crt_itm__any_wiki implements Xow_domain_crt_itm { + public boolean Matches(Xow_domain_itm cur, Xow_domain_itm comp) {return true;} + public static final Xow_domain_crt_itm__any_wiki Instance = new Xow_domain_crt_itm__any_wiki(); Xow_domain_crt_itm__any_wiki() {} +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__none.java b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__none.java index a27517de8..ab80a9151 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__none.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__none.java @@ -13,3 +13,8 @@ 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.wikis.domains.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; +class Xow_domain_crt_itm__none implements Xow_domain_crt_itm { + public boolean Matches(Xow_domain_itm cur, Xow_domain_itm comp) {return false;} + public static final Xow_domain_crt_itm__none Instance = new Xow_domain_crt_itm__none(); Xow_domain_crt_itm__none() {} +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__same_lang.java b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__same_lang.java index a27517de8..d338b0f2a 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__same_lang.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__same_lang.java @@ -13,3 +13,8 @@ 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.wikis.domains.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; +class Xow_domain_crt_itm__same_lang implements Xow_domain_crt_itm { + public boolean Matches(Xow_domain_itm cur, Xow_domain_itm comp) {return Bry_.Eq(cur.Lang_orig_key(), comp.Lang_orig_key());} + public static final Xow_domain_crt_itm__same_lang Instance = new Xow_domain_crt_itm__same_lang(); Xow_domain_crt_itm__same_lang() {} +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__same_type.java b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__same_type.java index a27517de8..4ecf9329e 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__same_type.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__same_type.java @@ -13,3 +13,8 @@ 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.wikis.domains.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; +class Xow_domain_crt_itm__same_type implements Xow_domain_crt_itm { + public boolean Matches(Xow_domain_itm cur, Xow_domain_itm comp) {return cur.Domain_type_id() == comp.Domain_type_id();} + public static final Xow_domain_crt_itm__same_type Instance = new Xow_domain_crt_itm__same_type(); Xow_domain_crt_itm__same_type() {} +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__self.java b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__self.java index a27517de8..d4589f06c 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__self.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm__self.java @@ -13,3 +13,8 @@ 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.wikis.domains.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; +class Xow_domain_crt_itm__self implements Xow_domain_crt_itm { + public boolean Matches(Xow_domain_itm cur, Xow_domain_itm comp) {return Bry_.Eq(cur.Domain_bry(), comp.Domain_bry());} + public static final Xow_domain_crt_itm__self Instance = new Xow_domain_crt_itm__self(); Xow_domain_crt_itm__self() {} +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm_parser.java b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm_parser.java index a27517de8..a8ecf5c2c 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm_parser.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_itm_parser.java @@ -13,3 +13,77 @@ 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.wikis.domains.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; +import gplx.xowa.langs.*; +class Xow_domain_crt_itm_parser { + public Xow_domain_crt_kv_itm[] Parse_as_kv_itms_or_null(byte[] raw) { + List_adp rv = Parse_as_obj_or_null(raw, Bool_.N); + return rv == null ? null : (Xow_domain_crt_kv_itm[])rv.To_ary_and_clear(Xow_domain_crt_kv_itm.class); + } + public Xow_domain_crt_kv_ary[] Parse_as_kv_arys_or_null(byte[] raw) { + List_adp rv = Parse_as_obj_or_null(raw, Bool_.Y); + return rv == null ? null : (Xow_domain_crt_kv_ary[])rv.To_ary_and_clear(Xow_domain_crt_kv_ary.class); + } + public List_adp Parse_as_obj_or_null(byte[] raw, boolean is_ary) { + List_adp rv = List_adp_.New(); + byte[][] line_ary = Bry_split_.Split_lines(raw); + int line_len = line_ary.length; + for (int i = 0; i < line_len; ++i) { + byte[] line = line_ary[i]; + if (line.length == 0) continue; // ignore blank lines + byte[][] word_ary = Bry_split_.Split(line, Byte_ascii.Pipe); + int word_len = word_ary.length; + if (word_len != 2) return null; // not A|B; exit now; + Xow_domain_crt_itm key_itm = Xow_domain_crt_itm_parser.Instance.Parse_as_in(word_ary[0]); + if (key_itm == Xow_domain_crt_itm_.Null) return null; // invalid key; exit; + if (is_ary) { + Xow_domain_crt_itm[] ary_itm = Xow_domain_crt_itm_parser.Instance.Parse_as_ary(word_ary[1]); + if (ary_itm == null) return null; + rv.Add(new Xow_domain_crt_kv_ary(key_itm, ary_itm)); + } + else { + Xow_domain_crt_itm val_itm = Xow_domain_crt_itm_parser.Instance.Parse_as_in(word_ary[1]); + if (val_itm == Xow_domain_crt_itm_.Null) return null; // invalid val; exit; + rv.Add(new Xow_domain_crt_kv_itm(key_itm, val_itm)); + } + } + return rv; + } + public Xow_domain_crt_itm Parse_as_in(byte[] raw) { + Xow_domain_crt_itm[] in_ary = Parse_as_ary(raw); + return in_ary == null ? Xow_domain_crt_itm_.Null : new Xow_domain_crt_itm__in(in_ary); + } + public Xow_domain_crt_itm[] Parse_as_ary(byte[] raw) { + byte[][] terms = Bry_split_.Split(raw, Byte_ascii.Comma, Bool_.Y); + int len = terms.length; + Xow_domain_crt_itm[] rv_ary = new Xow_domain_crt_itm[len]; + for (int i = 0; i < len; ++i) { + Xow_domain_crt_itm itm = Parse_itm(terms[i]); + if (itm == Xow_domain_crt_itm_.Null) return null; // invalid val; exit; + rv_ary[i] = itm; + } + return rv_ary; + } + public Xow_domain_crt_itm Parse_itm(byte[] raw) { + Xow_domain_crt_itm rv = (Xow_domain_crt_itm)itm_hash.Get_by_bry(raw); if (rv != null) return rv; // singleton; EX: , , etc.. + int raw_len = raw.length; + if (Bry_.Has_at_bgn(raw, Wild_lang)) { // EX: *.wikipedia + int wiki_tid = Xow_domain_tid_.Get_type_as_tid(raw, Wild_lang.length, raw_len); + return wiki_tid == Xow_domain_tid_.Tid__null ? Xow_domain_crt_itm_.Null : new Xow_domain_crt_itm__type(wiki_tid); + } + else if (Bry_.Has_at_end(raw, Wild_type)) { // EX: en.* + Xol_lang_stub lang_itm = Xol_lang_stub_.Get_by_key_or_null(raw, 0, raw_len - Wild_type.length); + return lang_itm == null ? Xow_domain_crt_itm_.Null : new Xow_domain_crt_itm__lang(lang_itm.Key()); + } + else + return new Xow_domain_crt_itm__wiki(raw); // EX: en.wikipedia.org + } + private static final Hash_adp_bry itm_hash = Hash_adp_bry.cs() + .Add_str_obj("" , Xow_domain_crt_itm__self.Instance) + .Add_str_obj("" , Xow_domain_crt_itm__same_type.Instance) + .Add_str_obj("" , Xow_domain_crt_itm__same_lang.Instance) + .Add_str_obj("" , Xow_domain_crt_itm__any_wiki.Instance) + ; + private static final byte[] Wild_lang = Bry_.new_a7("*."), Wild_type = Bry_.new_a7(".*"); + public static final Xow_domain_crt_itm_parser Instance = new Xow_domain_crt_itm_parser(); Xow_domain_crt_itm_parser() {} +} diff --git a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_kv_itm_mgr.java b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_kv_itm_mgr.java index a27517de8..135e6a776 100644 --- a/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_kv_itm_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/domains/crts/Xow_domain_crt_kv_itm_mgr.java @@ -13,3 +13,53 @@ 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.wikis.domains.crts; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.domains.*; +public class Xow_domain_crt_kv_itm_mgr { + private final List_adp list = List_adp_.New(); + public void Clear() {list.Clear();} + @gplx.Internal protected void Add(Xow_domain_crt_kv_itm itm) {list.Add(itm);} + public boolean Parse_as_itms(byte[] raw) { + this.Clear(); + Xow_domain_crt_kv_itm[] ary = Xow_domain_crt_itm_parser.Instance.Parse_as_kv_itms_or_null(raw); + if (ary == null) return false; // invalid parse; leave current value as is and exit; + int len = ary.length; + for (int i = 0; i < len; ++i) + this.Add(ary[i]); + return true; + } + public boolean Parse_as_arys(byte[] raw) { + this.Clear(); + Xow_domain_crt_kv_ary[] ary = Xow_domain_crt_itm_parser.Instance.Parse_as_kv_arys_or_null(raw); + if (ary == null) return false; // invalid parse; leave current value as is and exit; + int len = ary.length; + for (int i = 0; i < len; ++i) + list.Add(ary[i]); + return true; + } + public Xow_domain_crt_itm Find_itm(Xow_domain_itm cur, Xow_domain_itm comp) { + int len = list.Count(); + for (int i = 0; i < len; ++i) { + Xow_domain_crt_kv_itm kv = (Xow_domain_crt_kv_itm)list.Get_at(i); + if (kv.Key().Matches(cur, comp)) return kv.Val(); + } + return Xow_domain_crt_itm__none.Instance; + } + public Xow_domain_crt_itm[] Find_ary(Xow_domain_itm cur, Xow_domain_itm comp) { + int len = list.Count(); + for (int i = 0; i < len; ++i) { + Xow_domain_crt_kv_ary kv = (Xow_domain_crt_kv_ary)list.Get_at(i); + if (kv.Key().Matches(cur, comp)) return kv.Val(); + } + return null; + } +} +class Xow_domain_crt_kv_itm { + public Xow_domain_crt_kv_itm(Xow_domain_crt_itm key, Xow_domain_crt_itm val) {this.key = key; this.val = val;} + public Xow_domain_crt_itm Key() {return key;} private final Xow_domain_crt_itm key; + public Xow_domain_crt_itm Val() {return val;} private final Xow_domain_crt_itm val; +} +class Xow_domain_crt_kv_ary { + public Xow_domain_crt_kv_ary(Xow_domain_crt_itm key, Xow_domain_crt_itm[] val) {this.key = key; this.val = val;} + public Xow_domain_crt_itm Key() {return key;} private final Xow_domain_crt_itm key; + public Xow_domain_crt_itm[] Val() {return val;} private final Xow_domain_crt_itm[] val; +} diff --git a/400_xowa/src/gplx/xowa/wikis/fsys/Xow_fsys_mgr.java b/400_xowa/src/gplx/xowa/wikis/fsys/Xow_fsys_mgr.java index a27517de8..fca981228 100644 --- a/400_xowa/src/gplx/xowa/wikis/fsys/Xow_fsys_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/fsys/Xow_fsys_mgr.java @@ -13,3 +13,14 @@ 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.wikis.fsys; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.xowa.apps.fsys.*; +import gplx.xowa.users.*; +public class Xow_fsys_mgr { + public Xow_fsys_mgr(Io_url root_dir, Io_url file_dir) { + this.root_dir = root_dir; this.file_dir = file_dir; this.tmp_dir = root_dir.GenSubDir("tmp"); + } + public Io_url Root_dir() {return root_dir;} private final Io_url root_dir; + public Io_url File_dir() {return file_dir;} private final Io_url file_dir; + public Io_url Tmp_dir() {return tmp_dir;} private final Io_url tmp_dir; +} diff --git a/400_xowa/src/gplx/xowa/wikis/metas/Bfmtr_eval_wiki.java b/400_xowa/src/gplx/xowa/wikis/metas/Bfmtr_eval_wiki.java index a27517de8..16cb05b60 100644 --- a/400_xowa/src/gplx/xowa/wikis/metas/Bfmtr_eval_wiki.java +++ b/400_xowa/src/gplx/xowa/wikis/metas/Bfmtr_eval_wiki.java @@ -13,3 +13,19 @@ 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.wikis.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.brys.fmtrs.*; +import gplx.langs.gfs.*; +public class Bfmtr_eval_wiki implements Bry_fmtr_eval_mgr { + public Bfmtr_eval_wiki(Xowe_wiki wiki) {this.wiki = wiki;} private Xowe_wiki wiki; + public boolean Enabled() {return enabled;} public void Enabled_(boolean v) {enabled = v;} private boolean enabled = true; + public byte[] Eval(byte[] cmd) { + Object rslt = GfsCore.Instance.Exec_bry(cmd, wiki); + return Bry_.new_u8(Object_.Xto_str_strict_or_null_mark(rslt)); + } + public void Eval_mgr_(Bry_fmtr... fmtrs) { + int fmtrs_len = fmtrs.length; + for (int i = 0; i < fmtrs_len; i++) + fmtrs[i].Eval_mgr_(this); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/metas/Xow_html_util.java b/400_xowa/src/gplx/xowa/wikis/metas/Xow_html_util.java index a27517de8..2d6d11762 100644 --- a/400_xowa/src/gplx/xowa/wikis/metas/Xow_html_util.java +++ b/400_xowa/src/gplx/xowa/wikis/metas/Xow_html_util.java @@ -13,3 +13,22 @@ 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.wikis.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public class Xow_html_util implements Gfo_invk { + public Xow_html_util(Xowe_wiki wiki) {this.wiki = wiki;} private Xowe_wiki wiki; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_if_bool)) return If_bool(m.ReadStr("expr"), m.ReadStr("true_val"), m.ReadStr("false_val")); + else if (ctx.Match(k, Invk_if_yn)) return If_yn(m.ReadStr("expr"), m.ReadStr("true_val"), m.ReadStr("false_val")); + else return Gfo_invk_.Rv_unhandled; + } private static final String Invk_if_bool = "if_bool", Invk_if_yn = "if_yn"; + String If_bool(String expr, String true_val, String false_val) { + Object o = wiki.Appe().Gfs_mgr().Run_str(expr); + try {return Bool_.Cast(o) ? true_val : false_val;} + catch (Exception e) {Err_.Noop(e); return "expr failed: " + expr;} + } + String If_yn(String expr, String true_val, String false_val) { + String o = String_.as_(wiki.Appe().Gfs_mgr().Run_str(expr)); + try {return Yn.parse(o) ? true_val : false_val;} + catch (Exception e) {Err_.Noop(e); return "expr failed: " + expr;} + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/metas/Xow_script_mgr.java b/400_xowa/src/gplx/xowa/wikis/metas/Xow_script_mgr.java index a27517de8..53e31ac5b 100644 --- a/400_xowa/src/gplx/xowa/wikis/metas/Xow_script_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/metas/Xow_script_mgr.java @@ -13,3 +13,45 @@ 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.wikis.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.brys.fmtrs.*; import gplx.core.envs.*; +import gplx.xowa.wikis.domains.*; +public class Xow_script_mgr implements Gfo_invk { + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_set)) Set(m.ReadBry("key"), m.ReadBry("wiki_type"), m.ReadBry("script")); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_set = "set"; + public void Exec(Xowe_wiki wiki) { + int len = hash.Count(); + Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_k004(); + for (int i = 0; i < len; i++) { + Xow_script_itm itm = (Xow_script_itm)hash.Get_at(i); + int wiki_tid = wiki.Domain_tid(); + if (Int_.In(wiki_tid, itm.Wiki_tids())) // wiki_tid matches itm + itm.Fmtr().Bld_bfr_many(bfr, wiki.Domain_bry(), Xow_domain_tid_.Get_type_as_bry(wiki_tid), wiki.Lang().Key_bry()); + } + String gfs_script = String_.Replace(bfr.To_str_and_clear(), Op_sys.Wnt.Nl_str(), Op_sys.Lnx.Nl_str()); + wiki.Appe().Gfs_mgr().Run_str(gfs_script); + bfr.Mkr_rls(); + } + public void Set(byte[] key, byte[] wiki_types_raw, byte[] script) { + byte[][] wiki_tid_names = Bry_split_.Split(wiki_types_raw, Byte_ascii.Tilde); + int len = wiki_tid_names.length; + int[] wiki_tids = new int[len]; + for (int i = 0; i < len; i++) + wiki_tids[i] = Xow_domain_tid_.Get_type_as_tid(wiki_tid_names[i]); + + Xow_script_itm itm = new Xow_script_itm(key, wiki_tids, script); + hash.Add_if_dupe_use_nth(itm.Key(), itm); + } + public Ordered_hash hash = Ordered_hash_.New_bry(); +} +class Xow_script_itm { + public Xow_script_itm(byte[] key, int[] wiki_tids, byte[] script) { + this.key = key; this.wiki_tids = wiki_tids; this.fmtr = Bry_fmtr.new_bry_(script, "wiki_key", "wiki_type_name", "wiki_lang"); + } + public byte[] Key() {return key;} private byte[] key; + public int[] Wiki_tids() {return wiki_tids;} private int[] wiki_tids; + public Bry_fmtr Fmtr() {return fmtr;} Bry_fmtr fmtr; +} diff --git a/400_xowa/src/gplx/xowa/wikis/metas/Xow_sys_cfg.java b/400_xowa/src/gplx/xowa/wikis/metas/Xow_sys_cfg.java index a27517de8..509045b66 100644 --- a/400_xowa/src/gplx/xowa/wikis/metas/Xow_sys_cfg.java +++ b/400_xowa/src/gplx/xowa/wikis/metas/Xow_sys_cfg.java @@ -13,3 +13,19 @@ 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.wikis.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public class Xow_sys_cfg implements Gfo_invk { + public Xow_sys_cfg(Xowe_wiki wiki) {} + public boolean Xowa_cmd_enabled() {return xowa_cmd_enabled;} public Xow_sys_cfg Xowa_cmd_enabled_(boolean v) {xowa_cmd_enabled = v; return this;} private boolean xowa_cmd_enabled; + public boolean Xowa_proto_enabled() {return xowa_proto_enabled;} public Xow_sys_cfg Xowa_proto_enabled_(boolean v) {xowa_proto_enabled = v; return this;} private boolean xowa_proto_enabled; + public void Copy(Xow_sys_cfg src) { + this.xowa_cmd_enabled = src.xowa_cmd_enabled; + this.xowa_proto_enabled = src.xowa_proto_enabled; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, "xowa_cmd_enabled_")) xowa_cmd_enabled = m.ReadYn("v"); + else if (ctx.Match(k, "xowa_proto_enabled_")) xowa_proto_enabled = m.ReadYn("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/metas/Xow_user.java b/400_xowa/src/gplx/xowa/wikis/metas/Xow_user.java index a27517de8..a6aa2f63e 100644 --- a/400_xowa/src/gplx/xowa/wikis/metas/Xow_user.java +++ b/400_xowa/src/gplx/xowa/wikis/metas/Xow_user.java @@ -13,3 +13,12 @@ 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.wikis.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public class Xow_user implements Gfo_invk { + public byte[] Name() {return name;} private byte[] name = Bry_.new_a7("anonymous"); + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_name_)) name = m.ReadBry("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_name_ = "name_"; +} diff --git a/400_xowa/src/gplx/xowa/wikis/metas/Xow_wiki_props.java b/400_xowa/src/gplx/xowa/wikis/metas/Xow_wiki_props.java index a27517de8..2f80d45d1 100644 --- a/400_xowa/src/gplx/xowa/wikis/metas/Xow_wiki_props.java +++ b/400_xowa/src/gplx/xowa/wikis/metas/Xow_wiki_props.java @@ -13,3 +13,83 @@ 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.wikis.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.wikis.data.*; +import gplx.xowa.htmls.hrefs.*; +public class Xow_wiki_props { + // MW props for Pfunc_wiki_props and Scrib_lib; EX: {{SITENAME}} + public byte[] Site_name() {return site_name;} private byte[] site_name = Bry_.Empty; + public byte[] Server_name() {return server_name;} private byte[] server_name = Bry_.new_a7("localhost"); + public byte[] Server() {return server;} private byte[] server = Bry_.new_a7("http://localhost"); + public byte[] Article_path() {return article_path;} private byte[] article_path = Xoh_href_.Bry__wiki; + public byte[] Script_path() {return script_path;} private byte[] script_path = Bry_.new_a7("/wiki"); + public byte[] Style_path() {return style_path;} private byte[] style_path = Bry_.new_a7("/wiki/skins"); + public byte[] Content_language() {return content_language;} private byte[] content_language = Bry_.Empty; + public byte[] Direction_mark() {return direction_mark;} private byte[] direction_mark = Bry_.Empty; + public byte[] Current_version() {return CURRENT_VERSION;} private static final byte[] CURRENT_VERSION = Bry_.new_a7("1.21wmf11"); // approximate level of compatibility + + // XO props + public byte Protocol_tid() {return protocol_tid;} private final byte protocol_tid = gplx.core.net.Gfo_protocol_itm.Tid_https; // NOTE: default protocol to https; handles external links like [//a.org]; may need to be changed for wikia or other non-WMF wikis; DATE:2015-07-27 + public byte[] Main_page() {return main_page;} private byte[] main_page = Xoa_page_.Main_page_bry; // HACK: default to Main_Page b/c some code tries to do Xoa_ttl.Parse() which will not work with ""; DATE:2014-02-16 + public byte[] Bldr_version() {return bldr_version;} private byte[] bldr_version = Bry_.Empty; + public int Css_version() {return css_version;} private int css_version = 1; + public byte[] Siteinfo_misc() {return siteinfo_misc;} private byte[] siteinfo_misc = Bry_.Empty; + public byte[] Siteinfo_mainpage() {return siteinfo_mainpage;} private byte[] siteinfo_mainpage = Bry_.Empty; + public DateAdp Modified_latest() {return modified_latest;} private DateAdp modified_latest; + public String Modified_latest__yyyy_MM_dd() {return modified_latest == null ? "" : modified_latest.XtoStr_fmt_yyyy_MM_dd();} + + // setters + public void ContentLanguage_ (byte[] v) {content_language = v;} + public void Bldr_version_ (byte[] v) {bldr_version = v;} + public void Main_page_ (byte[] v) {main_page = v;} + public void Siteinfo_mainpage_(byte[] v) {siteinfo_mainpage = v;} + public void Siteinfo_misc_(byte[] v) { + if (v == null) return; // exit else will fail for personal wikis which don't have a siteinfo_misc + this.siteinfo_misc = v; + int pipe_0 = Bry_find_.Find_fwd(v, Byte_ascii.Pipe); + if (pipe_0 != Bry_find_.Not_found) + this.site_name = Bry_.Mid(siteinfo_misc, 0, pipe_0); + } + + // Init_by_ctor initializes by domain_name, not by db + public void Init_by_ctor(int domain_tid, byte[] domain_bry) { + // initialize site_name to something based on domain_tid; EX: "Wikipedia" + // note that "home" becomes "Home"; will be changed back to "home" in Init_by_load_2 + this.site_name = Bry_.new_a7(String_.UpperFirst(String_.new_a7(Xow_domain_tid_.Get_type_as_bry(domain_tid)))); + + // server_name is domain; EX: "en.wikipedia.org" + this.server_name = domain_bry; + + // server_name is https: + domain EX: "https://en.wikipedia.org" + this.server = Bry_.Add(gplx.core.net.Gfo_protocol_itm.Itm_https.Text_bry(), domain_bry); + } + // Init_by_load initializes by db; called after Init_by_ctor but before Init_by_load_2; should be replaced by Init_by_load_2, but leaving as separate proc b/c of "if (app.Bldr__running())"; DATE:2017-02-17 + public void Init_by_load(Xoa_app app, gplx.dbs.cfgs.Db_cfg_tbl cfg_tbl) { + if (app.Bldr__running()) return; // never load main_page during bldr; note that Init_by_load is called by bldr cmds like css; DATE:2015-07-24 + + // load from xowa_cfg + this.main_page = cfg_tbl.Select_bry_or (Xowd_cfg_key_.Grp__wiki_init, Xowd_cfg_key_.Key__init__main_page, null); + if (main_page == null) { // main_page not found + Xoa_app_.Usr_dlg().Warn_many("", "", "mw_props.load; main_page not found; conn=~{0}", cfg_tbl.Conn().Conn_info().Db_api()); + this.main_page = Xoa_page_.Main_page_bry; + } + + this.modified_latest = cfg_tbl.Select_date_or(Xowd_cfg_key_.Grp__wiki_init, Xowd_cfg_key_.Key__init__modified_latest, null); + } + // Init_by_load_2 is called by Xodb_load_mgr_sql; note that it might be called during bldr; DATE:2017-02-17 + public void Init_by_load_2(byte[] main_page, byte[] bldr_version, byte[] siteinfo_misc, byte[] siteinfo_mainpage, DateAdp modified_latest) { + this.main_page = main_page; + if (main_page == null) { // main_page not found + Xoa_app_.Usr_dlg().Warn_many("", "", "mw_props.load; main_page not found2"); + this.main_page = Xoa_page_.Main_page_bry; + } + + this.bldr_version = bldr_version; + this.siteinfo_mainpage = siteinfo_mainpage; + this.modified_latest = modified_latest; + + // note that Siteinfo_misc_ must be called b/c site_name should come from xowa_cfg / dump.xml's siteinfo_misc, not Init_by_ctor's Get_type_as_bry + // doing "this.siteinfo_misc = siteinfo_misc" will cause "home" to be "Home" and diag will fail; DATE:2017-02-21 + this.Siteinfo_misc_(siteinfo_misc); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/modules/Xow_module_base.java b/400_xowa/src/gplx/xowa/wikis/modules/Xow_module_base.java index a27517de8..3ec41cbfe 100644 --- a/400_xowa/src/gplx/xowa/wikis/modules/Xow_module_base.java +++ b/400_xowa/src/gplx/xowa/wikis/modules/Xow_module_base.java @@ -13,3 +13,16 @@ 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.wikis.modules; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public class Xow_module_base implements Gfo_invk { + public byte Enabled() {return enabled;} private byte enabled = Bool_.__byte; + public boolean Enabled_y() {return enabled == Bool_.Y_byte;} + public boolean Enabled_n() {return enabled == Bool_.N_byte;} + @gplx.Virtual public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_enabled)) return Yn.To_nullable_str(enabled); + else if (ctx.Match(k, Invk_enabled_)) enabled = Yn.To_nullable_byte(m.ReadStr("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_enabled = "enabled", Invk_enabled_ = "enabled_"; +} diff --git a/400_xowa/src/gplx/xowa/wikis/modules/Xow_module_mgr.java b/400_xowa/src/gplx/xowa/wikis/modules/Xow_module_mgr.java index a27517de8..c59d07d99 100644 --- a/400_xowa/src/gplx/xowa/wikis/modules/Xow_module_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/modules/Xow_module_mgr.java @@ -13,3 +13,40 @@ 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.wikis.modules; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.xowa.htmls.heads.*; import gplx.xowa.htmls.modules.popups.*; +public class Xow_module_mgr implements Gfo_invk { + private Hash_adp_bry regy = Hash_adp_bry.cs(); + public Xow_module_mgr(Xowe_wiki wiki) { + this.popup_mgr = new Xow_popup_mgr(wiki); + regy.Add_bry_obj(Xoh_head_itm_.Key__top_icon , itm__top_icon); + regy.Add_bry_obj(Xoh_head_itm_.Key__navframe , itm__navframe); + regy.Add_bry_obj(Xoh_head_itm_.Key__title_rewrite , itm__title_rewrite); + } + public boolean Collapsible__toc() {return collapsible__toc;} private boolean collapsible__toc; + public boolean Collapsible__collapsible() {return collapsible__collapsible;} private boolean collapsible__collapsible; + public boolean Collapsible__navframe() {return collapsible__navframe;} private boolean collapsible__navframe; + public Xow_module_base Itm__top_icon() {return itm__top_icon;} private Xow_module_base itm__top_icon = new Xow_module_base(); + public Xow_module_base Itm__navframe() {return itm__navframe;} private Xow_module_base itm__navframe = new Xow_module_base(); + public Xow_module_base Itm__title_rewrite() {return itm__title_rewrite;} private Xow_module_base itm__title_rewrite = new Xow_module_base(); + public Xow_popup_mgr Popup_mgr() {return popup_mgr;} private Xow_popup_mgr popup_mgr; + public void Init_by_wiki(Xowe_wiki wiki) { + popup_mgr.Init_by_wiki(wiki); + wiki.App().Cfg().Bind_many_wiki(this, wiki, Cfg__collapsible__toc, Cfg__collapsible__collapsible, Cfg__collapsible__navframe); + } + public Xow_module_base Get(byte[] key) {return (Xow_module_base)regy.Get_by_bry(key);} + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_get)) return Get(m.ReadBry("v")); + else if (ctx.Match(k, Cfg__collapsible__toc)) collapsible__toc = m.ReadYn("v"); + else if (ctx.Match(k, Cfg__collapsible__collapsible)) collapsible__collapsible = m.ReadYn("v"); + else if (ctx.Match(k, Cfg__collapsible__navframe)) collapsible__navframe = m.ReadYn("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_get = "get"; + + private static final String + Cfg__collapsible__toc = "xowa.html.collapsibles.toc" + , Cfg__collapsible__collapsible = "xowa.html.collapsibles.navbox" + , Cfg__collapsible__navframe = "xowa.html.collapsibles.navframe" + ; +} diff --git a/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns.java b/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns.java index a27517de8..712629be1 100644 --- a/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns.java +++ b/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns.java @@ -13,3 +13,95 @@ 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.wikis.nss; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.xowa.bldrs.cmds.*; import gplx.xowa.apps.utls.*; +public class Xow_ns implements Gfo_invk { + public Xow_ns(int id, byte case_match, byte[] name, boolean is_alias) { + this.id = id; this.case_match = case_match; this.is_alias = is_alias; + Name_bry_(name); + } + public void Name_bry_(byte[] v) { + if (id == Xow_ns_.Tid__main) { // NOTE: Main will never prefix titles; EX: "Test" vs "Category:Test" + this.name_db_str = ""; + this.name_db = Bry_.Empty; + this.name_db_w_colon = Bry_.Empty; + } + else { + this.name_db = v; + this.name_db_w_colon = Bry_.Add(v, Byte_ascii.Colon); + this.name_db_str = String_.new_u8(v); + } + this.num_str = Int_.To_str_pad_bgn_zero(id, 3); + this.num_bry = Bry_.new_a7(num_str); + synchronized (url_encoder) { // LOCK:static-obj + this.name_enc = url_encoder.Encode(name_db); + } + this.name_ui = Bry_.Replace(name_enc, Byte_ascii.Underline, Byte_ascii.Space); + this.name_ui_w_colon = Bry_.Replace(name_db_w_colon, Byte_ascii.Underline, Byte_ascii.Space); + } + public boolean Exists() {return exists;} public Xow_ns Exists_(boolean v) {exists = v; return this;} private boolean exists; + public byte[] Name_db() {return name_db;} private byte[] name_db; + public byte[] Name_db_w_colon() {return name_db_w_colon;} private byte[] name_db_w_colon; + public String Name_db_str() {return name_db_str;} private String name_db_str; + public byte[] Name_ui() {return name_ui;} private byte[] name_ui; + public byte[] Name_ui_w_colon() {return name_ui_w_colon;} private byte[] name_ui_w_colon; // PERF: for Xoa_ttl + public byte[] Name_enc() {return name_enc;} private byte[] name_enc; + public byte[] Name_combo() {return id == Xow_ns_.Tid__main ? Xow_ns_.Bry__main: name_ui;} // for combo boxes; namely "(Main)" + public String Num_str() {return num_str;} private String num_str; + public byte[] Num_bry() {return num_bry;} private byte[] num_bry; + public int Id() {return id;} private int id; + public int Id_subj_id() {if (id < 0) return id; return Id_is_talk() ? id - 1 : id;} // id< 0: special/media return themself; REF.MW: Namespace.php|getSubject + public int Id_talk_id() {if (id < 0) return id; return Id_is_talk() ? id : id + 1;} // REF.MW: Namespace.php|getTalk + public int Id_alt_id() {if (id < 0) return id; return Id_is_talk() ? Id_subj_id() : Id_talk_id();} // REF.MW: Namespace.php|getTalk + public boolean Id_is_subj() {return !Id_is_talk();} // REF.MW: Namespace.php|isMain + public boolean Id_is_talk() {return id > Xow_ns_.Tid__main && id % 2 == 1;} // REF.MW: Namespace.php|isTalk + public boolean Id_is_media() {return id == Xow_ns_.Tid__media;} + public boolean Id_is_special() {return id == Xow_ns_.Tid__special;} + public boolean Id_is_main() {return id == Xow_ns_.Tid__main;} + public boolean Id_is_file() {return id == Xow_ns_.Tid__file;} + public boolean Id_is_file_or_media() {return id == Xow_ns_.Tid__file || id == Xow_ns_.Tid__media;} + public boolean Id_is_tmpl() {return id == Xow_ns_.Tid__template;} + public boolean Id_is_ctg() {return id == Xow_ns_.Tid__category;} + public boolean Id_is_module() {return id == Xow_ns_.Tid__module;} + public int Ord() {return ord;} public void Ord_(int v) {this.ord = v;} private int ord; + public int Ord_subj_id() {if (id < 0) return ord; return Id_is_talk() ? ord - 1 : ord;} // id< 0: special/media returns self + public int Ord_talk_id() {if (id < 0) return ord; return Id_is_talk() ? ord : ord + 1;} + public byte Case_match() {return case_match;} public void Case_match_(byte v) {case_match = v;} private byte case_match; + public boolean Subpages_enabled() {return subpages_enabled;} private boolean subpages_enabled = false;// CHANGED: id > Xow_ns_.Tid__special; only Special, Media does not have subpages; DATE:2013-10-07 + public Xow_ns Subpages_enabled_(boolean v) {subpages_enabled = v; return this;} + public boolean Is_gender_aware() {return id == Xow_ns_.Tid__user || id == Xow_ns_.Tid__user_talk;} // ASSUME: only User, User_talk are gender aware + public boolean Is_capitalized() {return false;} // ASSUME: always false (?) + public boolean Is_content() {return id == Xow_ns_.Tid__main;} // ASSUME: only Main + public boolean Is_includable() {return true;} // ASSUME: always true (be transcluded?) + public boolean Is_movable() {return id > Xow_ns_.Tid__special;} // ASSUME: only Special, Media cannot move (be renamed?) + public boolean Is_meta() {return id < Xow_ns_.Tid__main;} // ASSUME: only Special, Media + public boolean Is_alias() {return is_alias;} private boolean is_alias; + public int Count() {return count;} public Xow_ns Count_(int v) {count = v; return this;} private int count; + public byte[] Gen_ttl(byte[] page) {return id == Xow_ns_.Tid__main ? page : Bry_.Add(name_db_w_colon, page);} + public void Aliases_add(String alias) { + if (String_.Eq(alias, name_db_str)) return; + if (aliases == null) aliases = Ordered_hash_.New(); + aliases.Add_if_dupe_use_1st(alias, alias); + } private Ordered_hash aliases; + public Keyval[] Aliases_as_scrib_ary() { // NOTE: intended for Scrib_lib_site; DATE:2014-02-15 + if (aliases == null) return Keyval_.Ary_empty; + int len = aliases.Count(); + Keyval[] rv = new Keyval[len]; + for (int i = 0; i < len; i++) { + String alias = (String)aliases.Get_at(i); + rv[i] = Keyval_.int_(i + List_adp_.Base1, alias); + } + return rv; + } + public Xob_ns_file_itm Bldr_data() {return bldr_data;} public void Bldr_data_(Xob_ns_file_itm v) {bldr_data = v;} private Xob_ns_file_itm bldr_data; + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_subpages_enabled_)) this.subpages_enabled = m.ReadYn("v"); + else if (ctx.Match(k, Invk_id)) return id; + else if (ctx.Match(k, Invk_name_txt)) return name_ui; + else if (ctx.Match(k, Invk_name_ui)) return Name_combo(); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_subpages_enabled_ = "subpages_enabled_", Invk_id = "id", Invk_name_txt = "name_txt", Invk_name_ui = "name_ui"; + + private static final Xoa_url_encoder url_encoder = new Xoa_url_encoder(); +} diff --git a/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_.java b/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_.java index a27517de8..2d2002339 100644 --- a/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_.java +++ b/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_.java @@ -13,3 +13,61 @@ 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.wikis.nss; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public class Xow_ns_ { + public static final int // PAGE:en.w:http://www.mediawiki.org/wiki/Help:Namespaces + Tid__media = -2 + , Tid__special = -1 + , Tid__main = 0 , Tid__talk = 1 + , Tid__user = 2 , Tid__user_talk = 3 + , Tid__project = 4 , Tid__project_talk = 5 + , Tid__file = 6 , Tid__file_talk = 7 + , Tid__mediawiki = 8 , Tid__mediawiki_talk = 9 + , Tid__template = 10 , Tid__template_talk = 11 + , Tid__help = 12 , Tid__help_talk = 13 + , Tid__category = 14 , Tid__category_talk = 15 + , Tid__portal = 100 , Tid__portal_talk = 101 + , Tid__module = 828 , Tid__module_talk = 829 + , Tid__null = Int_.Min_value + ; + public static final String + Key__media = "Media" + , Key__special = "Special" + , Key__main = "(Main)" , Key__talk = "Talk" + , Key__user = "User" , Key__user_talk = "User_talk" + , Key__project = "Project" , Key__project_talk = "Project_talk" + , Key__file = "File" , Key__file_talk = "File_talk" + , Key__mediawiki = "MediaWiki" , Key__mediawiki_talk = "MediaWiki_talk" + , Key__template = "Template" , Key__template_talk = "Template_talk" + , Key__help = "Help" , Key__help_talk = "Help_talk" + , Key__category = "Category" , Key__category_talk = "Category_talk" + , Key__portal = "Portal" , Key__portal_talk = "Portal_talk" + , Key__module = "Module" , Key__module_talk = "Module_talk" + , Key__null = "null" + , Key__wikipedia = "Wikipedia" + ; + public static final byte[] + Bry__media = Bry_.new_a7(Key__media) + , Bry__special = Bry_.new_a7(Key__special) + , Bry__main = Bry_.new_a7(Key__main) , Bry__talk = Bry_.new_a7(Key__talk) + , Bry__user = Bry_.new_a7(Key__user) , Bry__user_talk = Bry_.new_a7(Key__user_talk) + , Bry__project = Bry_.new_a7(Key__project) , Bry__project_talk = Bry_.new_a7(Key__project_talk) + , Bry__file = Bry_.new_a7(Key__file) , Bry__file_talk = Bry_.new_a7(Key__file_talk) + , Bry__mediawiki = Bry_.new_a7(Key__mediawiki) , Bry__mediawiki_talk = Bry_.new_a7(Key__mediawiki_talk) + , Bry__template = Bry_.new_a7(Key__template) , Bry__template_talk = Bry_.new_a7(Key__template_talk) + , Bry__help = Bry_.new_a7(Key__help) , Bry__help_talk = Bry_.new_a7(Key__help_talk) + , Bry__category = Bry_.new_a7(Key__category) , Bry__category_talk = Bry_.new_a7(Key__category_talk) + , Bry__portal = Bry_.new_a7(Key__portal) , Bry__portal_talk = Bry_.new_a7(Key__portal_talk) + , Bry__module = Bry_.new_a7(Key__module) , Bry__module_talk = Bry_.new_a7(Key__module_talk) + , Bry__null = Bry_.new_a7(Key__null) + ; + public static final String + Alias__wikipedia = "Wikipedia" + , Alias__image = "Image" + ; + public static final byte[] Alias__image__bry = Bry_.new_a7(Alias__image); + public static final byte[] + Bry__template_w_colon = Bry_.new_a7(Key__template + ":") + , Bry__module_w_colon = Bry_.new_a7(Key__module + ":") + ; +} diff --git a/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_canonical_.java b/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_canonical_.java index a27517de8..2c1165126 100644 --- a/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_canonical_.java +++ b/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_canonical_.java @@ -13,3 +13,116 @@ 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.wikis.nss; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.primitives.*; +public class Xow_ns_canonical_ { + public static final Xow_ns[] Ary = new Xow_ns[] // REF.MW: Namespace.php|$wgCanonicalNamespaceNames + { New_itm(Xow_ns_.Tid__media , Xow_ns_.Key__media) + , New_itm(Xow_ns_.Tid__special , Xow_ns_.Key__special) + , New_itm(Xow_ns_.Tid__talk , Xow_ns_.Key__talk) + , New_itm(Xow_ns_.Tid__user , Xow_ns_.Key__user) + , New_itm(Xow_ns_.Tid__user_talk , Xow_ns_.Key__user_talk) + , New_itm(Xow_ns_.Tid__project , Xow_ns_.Key__project) + , New_itm(Xow_ns_.Tid__project_talk , Xow_ns_.Key__project_talk) + , New_itm(Xow_ns_.Tid__file , Xow_ns_.Key__file) + , New_itm(Xow_ns_.Tid__file_talk , Xow_ns_.Key__file_talk) + , New_itm(Xow_ns_.Tid__mediawiki , Xow_ns_.Key__mediawiki) + , New_itm(Xow_ns_.Tid__mediawiki_talk , Xow_ns_.Key__mediawiki_talk) + , New_itm(Xow_ns_.Tid__template , Xow_ns_.Key__template) + , New_itm(Xow_ns_.Tid__template_talk , Xow_ns_.Key__template_talk) + , New_itm(Xow_ns_.Tid__help , Xow_ns_.Key__help) + , New_itm(Xow_ns_.Tid__help_talk , Xow_ns_.Key__help_talk) + , New_itm(Xow_ns_.Tid__category , Xow_ns_.Key__category) + , New_itm(Xow_ns_.Tid__category_talk , Xow_ns_.Key__category_talk) + , New_itm(Xow_ns_.Tid__module , Xow_ns_.Key__module) + , New_itm(Xow_ns_.Tid__module_talk , Xow_ns_.Key__module_talk) + }; + private static Xow_ns New_itm(int id, String name) {return new Xow_ns(id, Xow_ns_case_.Tid__1st, Bry_.new_a7(name), false);} // NOTE: for id/name reference only; case_match and alias does not matter; + private static Ordered_hash id_hash; + public static int To_id(byte[] key) { + if (id_hash == null) { + id_hash = Ordered_hash_.New_bry(); + int len = Ary.length; + for (int i = 0; i < len; ++i) { + Xow_ns ns = Ary[i]; + id_hash.Add(ns.Name_db(), new Int_obj_val(ns.Id())); + } + } + Object rv_obj = id_hash.Get_by(key); + return rv_obj == null ? Xow_ns_.Tid__null : ((Int_obj_val)rv_obj).Val(); + } + public static String To_canonical_or_local_as_str(Xow_ns ns) { // NOTE: prefer canonical names if they exist; otherwise use local; PAGE:sh.w:Koprno; DATE:2015-11-08 + switch (ns.Id()) { + case Xow_ns_.Tid__media: return Xow_ns_.Key__media; + case Xow_ns_.Tid__special: return Xow_ns_.Key__special; + case Xow_ns_.Tid__talk: return Xow_ns_.Key__talk; + case Xow_ns_.Tid__user: return Xow_ns_.Key__user; + case Xow_ns_.Tid__user_talk: return Xow_ns_.Key__user_talk; + case Xow_ns_.Tid__project: return Xow_ns_.Key__project; + case Xow_ns_.Tid__project_talk: return Xow_ns_.Key__project_talk; + case Xow_ns_.Tid__file: return Xow_ns_.Key__file; + case Xow_ns_.Tid__file_talk: return Xow_ns_.Key__file_talk; + case Xow_ns_.Tid__mediawiki: return Xow_ns_.Key__mediawiki; + case Xow_ns_.Tid__mediawiki_talk: return Xow_ns_.Key__mediawiki_talk; + case Xow_ns_.Tid__template: return Xow_ns_.Key__template; + case Xow_ns_.Tid__template_talk: return Xow_ns_.Key__template_talk; + case Xow_ns_.Tid__help: return Xow_ns_.Key__help; + case Xow_ns_.Tid__help_talk: return Xow_ns_.Key__help_talk; + case Xow_ns_.Tid__category: return Xow_ns_.Key__category; + case Xow_ns_.Tid__category_talk: return Xow_ns_.Key__category_talk; + case Xow_ns_.Tid__module: return Xow_ns_.Key__module; + case Xow_ns_.Tid__module_talk: return Xow_ns_.Key__module_talk; + default: return String_.new_u8(ns.Name_ui()); + } + } + public static byte[] To_canonical_or_local_as_bry(Xow_ns ns) { + switch (ns.Id()) { + case Xow_ns_.Tid__media: return Xow_ns_.Bry__media; + case Xow_ns_.Tid__special: return Xow_ns_.Bry__special; + case Xow_ns_.Tid__talk: return Xow_ns_.Bry__talk; + case Xow_ns_.Tid__user: return Xow_ns_.Bry__user; + case Xow_ns_.Tid__user_talk: return Xow_ns_.Bry__user_talk; + case Xow_ns_.Tid__project: return Xow_ns_.Bry__project; + case Xow_ns_.Tid__project_talk: return Xow_ns_.Bry__project_talk; + case Xow_ns_.Tid__file: return Xow_ns_.Bry__file; + case Xow_ns_.Tid__file_talk: return Xow_ns_.Bry__file_talk; + case Xow_ns_.Tid__mediawiki: return Xow_ns_.Bry__mediawiki; + case Xow_ns_.Tid__mediawiki_talk: return Xow_ns_.Bry__mediawiki_talk; + case Xow_ns_.Tid__template: return Xow_ns_.Bry__template; + case Xow_ns_.Tid__template_talk: return Xow_ns_.Bry__template_talk; + case Xow_ns_.Tid__help: return Xow_ns_.Bry__help; + case Xow_ns_.Tid__help_talk: return Xow_ns_.Bry__help_talk; + case Xow_ns_.Tid__category: return Xow_ns_.Bry__category; + case Xow_ns_.Tid__category_talk: return Xow_ns_.Bry__category_talk; + case Xow_ns_.Tid__module: return Xow_ns_.Bry__module; + case Xow_ns_.Tid__module_talk: return Xow_ns_.Bry__module_talk; + default: return ns.Name_ui(); + } + } + public static byte[] To_canonical_or_local_as_bry_w_colon(Xow_ns ns) { + byte[] rv = null; + switch (ns.Id()) { + case Xow_ns_.Tid__media: rv = Xow_ns_.Bry__media; break; + case Xow_ns_.Tid__special: rv = Xow_ns_.Bry__special; break; + case Xow_ns_.Tid__talk: rv = Xow_ns_.Bry__talk; break; + case Xow_ns_.Tid__user: rv = Xow_ns_.Bry__user; break; + case Xow_ns_.Tid__user_talk: rv = Xow_ns_.Bry__user_talk; break; + case Xow_ns_.Tid__project: rv = Xow_ns_.Bry__project; break; + case Xow_ns_.Tid__project_talk: rv = Xow_ns_.Bry__project_talk; break; + case Xow_ns_.Tid__file: rv = Xow_ns_.Bry__file; break; + case Xow_ns_.Tid__file_talk: rv = Xow_ns_.Bry__file_talk; break; + case Xow_ns_.Tid__mediawiki: rv = Xow_ns_.Bry__mediawiki; break; + case Xow_ns_.Tid__mediawiki_talk: rv = Xow_ns_.Bry__mediawiki_talk; break; + case Xow_ns_.Tid__template: rv = Xow_ns_.Bry__template; break; + case Xow_ns_.Tid__template_talk: rv = Xow_ns_.Bry__template_talk; break; + case Xow_ns_.Tid__help: rv = Xow_ns_.Bry__help; break; + case Xow_ns_.Tid__help_talk: rv = Xow_ns_.Bry__help_talk; break; + case Xow_ns_.Tid__category: rv = Xow_ns_.Bry__category; break; + case Xow_ns_.Tid__category_talk: rv = Xow_ns_.Bry__category_talk; break; + case Xow_ns_.Tid__module: rv = Xow_ns_.Bry__module; break; + case Xow_ns_.Tid__module_talk: rv = Xow_ns_.Bry__module_talk; break; + default: return ns.Name_db_w_colon(); + } + return Bry_.Add(rv, Byte_ascii.Colon_bry); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_case_.java b/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_case_.java index a27517de8..b8009bc34 100644 --- a/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_case_.java +++ b/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_case_.java @@ -13,3 +13,21 @@ 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.wikis.nss; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public class Xow_ns_case_ { + public static final byte Tid__all = 0, Tid__1st = 1; + public static final String Key__all = "case-sensitive", Key__1st = "first-letter"; + public static final byte[] Bry__all = Bry_.new_a7(Key__all), Bry__1st = Bry_.new_a7(Key__1st); + public static byte To_tid(String s) { + if (String_.Eq(s, Key__1st)) return Tid__1st; + else if (String_.Eq(s, Key__all)) return Tid__all; + else throw Err_.new_unhandled(s); + } + public static String To_str(byte tid) { + switch (tid) { + case Tid__all: return Key__all; + case Tid__1st: return Key__1st; + default: throw Err_.new_unhandled(tid); + } + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_mgr.java b/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_mgr.java index a27517de8..92f88f986 100644 --- a/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_mgr.java @@ -13,3 +13,253 @@ 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.wikis.nss; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.primitives.*; import gplx.core.btries.*; import gplx.xowa.langs.cases.*; +import gplx.xowa.bldrs.installs.*; +import gplx.xowa.xtns.scribunto.*; +public class Xow_ns_mgr implements Gfo_invk, gplx.core.lists.ComparerAble { + private Ordered_hash id_hash = Ordered_hash_.New(); // hash for retrieval by id + private Hash_adp_bry name_hash; // hash for retrieval by name; note that ns names are case-insensitive "File:" == "fILe:" + private Hash_adp_bry tmpl_hash; // hash for retrieval by name; PERF for templates + private Ordered_hash aliases = Ordered_hash_.New(); // hash to store aliases; used to populate name_hash; + private final Int_obj_ref id_hash_ref = Int_obj_ref.New_zero(); + public Xow_ns_mgr(Xol_case_mgr case_mgr) { + name_hash = Hash_adp_bry.ci_u8(case_mgr); + tmpl_hash = Hash_adp_bry.ci_u8(case_mgr); + } + public Xow_ns_mgr Clear() { + name_hash.Clear(); + id_hash.Clear(); + tmpl_hash.Clear(); + for (int i = 0; i < ords_len; i++) + ords[i] = null; + ords_len = 0; + ns_count = 0; + ns_file = null; + return this; + } + public Btrie_slim_mgr Category_trie() {return category_trie;} private Btrie_slim_mgr category_trie; + public Xow_ns Ns_main() {return ns_main;} private Xow_ns ns_main; + public Xow_ns Ns_template() {return ns_template;} private Xow_ns ns_template; + public Xow_ns Ns_file() {return ns_file;} private Xow_ns ns_file; + public Xow_ns Ns_category() {return ns_category;} private Xow_ns ns_category; + public Xow_ns Ns_portal() {return ns_portal;} private Xow_ns ns_portal; + public Xow_ns Ns_project() {return ns_project;} private Xow_ns ns_project; + public Xow_ns Ns_module() {return ns_module;} private Xow_ns ns_module; + public Xow_ns Ns_mediawiki() {return ns_mediawiki;} private Xow_ns ns_mediawiki; + public int Ns_page_id() {return ns_page_id;} public void Ns_page_id_(int v) {ns_page_id = v;} private int ns_page_id = Int_.Min_value; + public int Count() {return ns_count;} private int ns_count = 0; + public Xow_ns[] Ords_ary() {return ords;} private Xow_ns[] ords = new Xow_ns[Xow_ns_mgr_.Ordinal_max]; + public int Ords_len() {return ords_len;} private int ords_len; + public Xow_ns Ords_get_at(int ord) {return ords[ord];} + public int Ids_len() {return id_hash.Count();} + public Xow_ns Ids_get_at(int idx) {return (Xow_ns)id_hash.Get_at(idx);} + public Xow_ns Ids_get_or_null(int id) {synchronized (id_hash_ref) {return (Xow_ns)id_hash.Get_by(id_hash_ref.Val_(id));}} // LOCK:hash-key; DATE:2016-07-06 + private Xow_ns Ids_get_or_empty(int id) { + Xow_ns rv = Ids_get_or_null(id); + return rv == null ? Ns__empty : rv; + } private static final Xow_ns Ns__empty = new Xow_ns(Int_.Max_value, Byte_.Zero, Bry_.Empty, false); + public Xow_ns Names_get_or_null(byte[] name_bry) {return this.Names_get_or_null(name_bry, 0, name_bry.length);} + public Xow_ns Names_get_or_null(byte[] src, int bgn, int end) { + Object rv = name_hash.Get_by_mid(src, bgn, end); + return rv == null ? null : ((Xow_ns_mgr_name_itm)rv).Ns(); + } + public Xow_ns Names_get_or_main(byte[] name_bry) { + Xow_ns rv = this.Names_get_or_null(name_bry, 0, name_bry.length); + return rv == null ? this.Ns_main() : rv; + } + public Object Names_get_w_colon(byte[] src, int bgn, int end) { // NOTE: get ns for a name with a ":"; EX: "Template:A" should return "Template" ns + int colon_pos = Bry_find_.Find_fwd(src, Byte_ascii.Colon, bgn, end); + if (colon_pos == Bry_find_.Not_found) return null; // name does not have ":"; return; + Object rv = name_hash.Get_by_mid(src, bgn, colon_pos); + return rv == null ? null : ((Xow_ns_mgr_name_itm)rv).Ns(); + } + public int Tmpls_get_w_colon(byte[] src, int bgn, int end) { // NOTE: get length of template name with a ":"; EX: "Template:A" returns 10; PERF + int colon_pos = Bry_find_.Find_fwd(src, Byte_ascii.Colon, bgn, end); + if (colon_pos == Bry_find_.Not_found) return Bry_find_.Not_found; + Object o = tmpl_hash.Get_by_mid(src, bgn, colon_pos + 1); // +1 to include colon_pos + return o == null ? Bry_find_.Not_found : ((byte[])o).length; + } + public void Aliases_clear() {aliases.Clear();} + public Xow_ns_mgr Aliases_add(int ns_id, String name) { + Keyval kv = Keyval_.new_(name, new Int_obj_val(ns_id)); + aliases.Add_if_dupe_use_nth(name, kv); + return this; + } + public void Aliases_del(String name) {aliases.Del(name);} + public Xow_ns_mgr Init() { + this.Ords_sort(); + this.Rebuild_hashes(); + return this; + } + public void Init_w_defaults() { + this.Add_defaults(); + this.Init(); + } + private void Rebuild_hashes() { + name_hash.Clear(); tmpl_hash.Clear(); + for (int i = 0; i < ords_len; i++) { + Xow_ns ns = ords[i]; + if (ns == null) continue; // TEST: allow gaps in ns numbers; see Talk_skip test and related + if (ns.Id() == Xow_ns_.Tid__project_talk) Fix_project_talk(ns); // NOTE: handle $1 talk as per Language.php!fixVariableInNamespace; placement is important as it must go before key registration but after ord sort + Rebuild_hashes__add(name_hash, ns, ns.Name_db()); + if (ns.Id_is_tmpl()) tmpl_hash.Add(ns.Name_db_w_colon(), ns.Name_db_w_colon()); + } + int aliases_len = aliases.Count(); + for (int i = 0; i < aliases_len; i++) { + Keyval kv = (Keyval)aliases.Get_at(i); + int ns_id = ((Int_obj_val)kv.Val()).Val(); + Xow_ns ns = Ids_get_or_null(ns_id); if (ns == null) continue; // happens when alias exists, but not ns; EX: test has Image alias, but not File alias; should not happen "live" but don't want to fail + ns.Aliases_add(kv.Key()); // register alias with official ns; EX: "Image" will be placed in "File"'s .Aliases + byte[] alias_bry = Bry_.new_u8(kv.Key()); + Rebuild_hashes__add(name_hash, ns, alias_bry); + if (ns.Id_is_tmpl()) { + byte[] alias_name = Bry_.new_u8(kv.Key()); + alias_name = Bry_.Add(alias_name, Byte_ascii.Colon); + tmpl_hash.Add_if_dupe_use_nth(alias_name, alias_name); + } + } + } + private void Fix_project_talk(Xow_ns ns) { + byte[] ns_name = ns.Name_db(); + if (Bry_find_.Find_fwd(ns.Name_db(), Project_talk_fmt_arg)== Bry_find_.Not_found) return; // no $1 found; exit + Xow_ns project_ns = ords[ns.Ord_subj_id()]; + if (project_ns == null) return; // should warn or throw error; for now just exit + ns.Name_bry_(Bry_.Replace(ns_name, Project_talk_fmt_arg, project_ns.Name_db())); + } private static final byte[] Project_talk_fmt_arg = Bry_.new_a7("$1"); + private void Rebuild_hashes__add(Hash_adp_bry hash, Xow_ns ns, byte[] key) { + Xow_ns_mgr_name_itm ns_itm = new Xow_ns_mgr_name_itm(key, ns); + hash.Add_if_dupe_use_nth(key, ns_itm); + if (Bry_find_.Find_fwd(key, Byte_ascii.Underline) != Bry_find_.Not_found) // ns has _; add another entry for space; EX: Help_talk -> Help talk + hash.Add_if_dupe_use_nth(Bry_.Replace(key, Byte_ascii.Underline, Byte_ascii.Space), ns_itm); + } + public Xow_ns_mgr Add_defaults() { // NOTE: needs to happen after File ns is added; i.e.: cannot be put in Xow_ns_mgr() {} ctor + Aliases_add(Xow_ns_.Tid__file , "Image"); // REF.MW: Setup.php; add "Image", "Image talk" for backward compatibility; note that MW hardcodes Image ns as well + Aliases_add(Xow_ns_.Tid__file_talk , "Image_talk"); + Aliases_add(Xow_ns_.Tid__project , "Project"); // always add "Project" ns (EX: Wikipedia is name for en.wikipedia.org; not sure if MW hardcodes, but it is in messages + Aliases_add(Xow_ns_.Tid__module , "Module"); // always add "Module" ns; de.wikipedia.org has "Modul" defined in siteinfo.xml, but also uses Module + return this; + } + public Xow_ns_mgr Add_new(int nsId, String name) {return Add_new(nsId, Bry_.new_u8(name), Xow_ns_case_.Tid__1st, false);} // for tst_ constructor + public Xow_ns_mgr Add_new(int ns_id, byte[] name, byte caseMatchId, boolean alias) { + Bry_.Replace_all_direct(name, Byte_ascii.Space, Byte_ascii.Underline); // standardize on _; EX: User talk -> User_talk; DATE:2013-04-21 + Xow_ns ns = new Xow_ns(ns_id, caseMatchId, name, alias); + switch (ns_id) { + case Xow_ns_.Tid__main: ns_main = ns; break; + case Xow_ns_.Tid__template: ns_template = ns; break; + case Xow_ns_.Tid__portal: ns_portal = ns; break; + case Xow_ns_.Tid__project: ns_project = ns; break; + case Xow_ns_.Tid__mediawiki: ns_mediawiki = ns; break; + case Xow_ns_.Tid__module: ns_module = ns; break; + case Xow_ns_.Tid__file: if (ns_file == null) ns_file = ns; break; // NOTE: if needed, else Image will become the official ns_file + case Xow_ns_.Tid__category: + ns_category = ns; + if (category_trie == null) + category_trie = Btrie_slim_mgr.new_(ns.Case_match() == Xow_ns_case_.Tid__all); + category_trie.Add_obj(ns.Name_db(), this); + break; + } + ++ns_count; + synchronized (id_hash_ref) { // LOCK:hash-key; DATE:2016-07-06 + if (!id_hash.Has(id_hash_ref.Val_(ns_id))) // NOTE: do not add if already exists; avoids alias + id_hash.Add(Int_obj_ref.New(ns.Id()), ns); + } + name_hash.Add_if_dupe_use_nth(ns.Name_db(), new Xow_ns_mgr_name_itm(ns.Name_db(), ns)); + return this; + } + public int compare(Object lhsObj, Object rhsObj) { + Xow_ns lhs = (Xow_ns)lhsObj; + Xow_ns rhs = (Xow_ns)rhsObj; + return Int_.Compare(lhs.Id(), rhs.Id()); + } + private void Ords_sort() { + int ords_cur = 0; + int ns_len = id_hash.Count(); + id_hash.Sort_by(this); + // assert that all items are grouped in pairs of subj, talk; note that subj is even and talk is odd + int nxt_ns_id = Int_.Min_value; + int prv_ns_id = Int_.Min_value; + for (int i = 0; i < ns_len; i++) { + Xow_ns ns = (Xow_ns)id_hash.Get_at(i); + int ns_id = ns.Id(); + if (ns_id < 0 // ignore negative ns (which don't have subj/talk pairing) + || ns.Is_alias() // ignore alias + ) continue; + if (nxt_ns_id != Int_.Min_value) { // nxt_ns_id is set + if (nxt_ns_id != ns_id) // prv was subj, but cur does not match expected talk_id; create talk for prv subj + Ords_sort_add(nxt_ns_id); + nxt_ns_id = Int_.Min_value; // always reset value + } + if (ns_id % 2 == 0) // subj + nxt_ns_id = ns_id + 1; // anticipate nxt_ns_id + else { // talk + if (prv_ns_id != ns_id - 1) // prv was not subj for cur; create subj for current talk + Ords_sort_add(ns_id - 1); + } + prv_ns_id = ns_id; + } + if (nxt_ns_id != Int_.Min_value) // handle trailing ns_id; EX: 0, 1, 2; need to make 3 + Ords_sort_add(nxt_ns_id); + + // sort again b/c new ns may have been added + id_hash.Sort_by(this); + ns_len = id_hash.Count(); + // assign ords; assert that subj has even ordinal index + ords_len = 0; + for (int i = 0; i < ns_len; i++) { + Xow_ns ns = (Xow_ns)id_hash.Get_at(i); + int ns_id = ns.Id(); + if (ns.Is_alias()) continue; // ignore alias + if (ns_id < 0) {} + else { + if (ns_id % 2 == 0) { // subj + if (ords_cur % 2 != 0) { // current ordinal is not even; skip + ++ords_len; + ++ords_cur; + } + } + } + ns.Ord_(ords_cur); + ords[ords_cur++] = ns; + ++ords_len; + } + } + private void Ords_sort_add(int ns_id) { + this.Add_new(ns_id, Int_.To_bry(ns_id), Xow_ns_case_.Tid__1st, false); // NOTE: name and case_match are mostly useless defaults; note that in theory this proc should not be called (all siteInfos should be well-formed) but just in case, create items now so that Get_by_ord() does not fail + } + public byte[] Bld_ttl_w_ns(Bry_bfr bfr, boolean text_form, boolean literalize, int ns_id, byte[] ttl) { + if (ns_id == Xow_ns_.Tid__main) return ttl; + Xow_ns ns = Ids_get_or_null(ns_id); if (ns == null) {Xoa_app_.Usr_dlg().Warn_many("", "", "ns_mgr:uknown ns_id; ns_id=~{0} ttl=~{1}", ns_id, ttl); return ttl;} + if (literalize) bfr.Add_byte(Byte_ascii.Colon); // NOTE: add : to literalize ns; EX: [[Category:A]] will get thrown into category list; [[:Category:A]] will print + bfr.Add(text_form ? ns.Name_ui_w_colon() : ns.Name_db_w_colon()); + bfr.Add(ttl); + return bfr.To_bry_and_clear(); + } + class Xow_ns_mgr_name_itm { + public Xow_ns_mgr_name_itm(byte[] name, Xow_ns ns) {this.name = name; this.name_len = name.length; this.ns = ns;} + public byte[] Name() {return name;} private byte[] name; + public int Name_len() {return name_len;} private int name_len; + public Xow_ns Ns() {return ns;} private Xow_ns ns; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_clear)) this.Clear(); + // NOTE: called by /xowa/bin/any/xowa/cfg/wiki/core/*.gfs for (a) aliases; (b) Subpages_enabled + else if (ctx.Match(k, Invk_add_alias_bulk)) Exec_add_alias_bulk(m.ReadBry("v")); + else if (ctx.Match(k, Invk_get_by_id_or_new)) return this.Ids_get_or_empty(m.ReadInt("v")); // NOTE: if ns doesn't exist, returning empty is fine; DATE:2014-02-15 + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_add_alias_bulk = "add_alias_bulk", Invk_get_by_id_or_new = "get_by_id_or_new"; + public static final String Invk_load = "load", Invk_clear = "clear"; + private void Exec_add_alias_bulk(byte[] raw) { + byte[][] lines = Bry_split_.Split(raw, Byte_ascii.Nl); + int lines_len = lines.length; + for (int i = 0; i < lines_len; i++) { + byte[] line = lines[i]; + if (line.length == 0) continue; + byte[][] flds = Bry_split_.Split(line, Byte_ascii.Pipe); + int cur_id = Bry_.To_int_or(flds[0], Int_.Min_value); + this.Aliases_add(cur_id, String_.new_u8(flds[1])); + } + Ords_sort(); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_mgr_.java b/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_mgr_.java index a27517de8..153fe6d85 100644 --- a/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_mgr_.java +++ b/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_mgr_.java @@ -13,3 +13,38 @@ 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.wikis.nss; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.cases.*; import gplx.xowa.langs.bldrs.*; +public class Xow_ns_mgr_ { + public static final int Ordinal_max = 255; // ASSUME: no more than 255 ns in a wiki; choosing 255 to align with byte (no particular reason why it needs to be a byte, but better than 500, 1000, etc) + public static Xow_ns_mgr default_(Xol_case_mgr case_mgr) { // NOTE: same as en.wikipedia.org's ns circa 2012-01 (currently omitting ns:446,447,710,711) + Xow_ns_mgr rv = new Xow_ns_mgr(case_mgr); + rv = rv.Add_new(-2, "Media").Add_new(-1, "Special").Add_new(0, "").Add_new(1, "Talk").Add_new(2, "User").Add_new(3, "User_talk").Add_new(4, "Wikipedia").Add_new(5, "Wikipedia_talk") + .Add_new(6, "File").Add_new(7, "File_talk").Add_new(8, "MediaWiki").Add_new(9, "MediaWiki_talk").Add_new(10, "Template").Add_new(11, "Template_talk") + .Add_new(12, "Help").Add_new(13, "Help_talk").Add_new(14, "Category").Add_new(15, "Category_talk").Add_new(100, "Portal").Add_new(101, "Portal_talk").Add_new(108, "Book").Add_new(109, "Book_talk") + .Add_new(Xow_ns_.Tid__module, Xow_ns_.Key__module).Add_new(Xow_ns_.Tid__module_talk, Xow_ns_.Key__module_talk) + .Add_defaults() + ; + rv.Init(); + return rv; + } + public static void rebuild_(Xol_lang_itm lang, Xow_ns_mgr ns_mgr) { + Xol_ns_grp ns_names = lang.Ns_names(); + int ns_names_len = ns_names.Len(); + for (int i = 0; i < ns_names_len; i++) { + Xow_ns ns_name = ns_names.Get_at(i); + int ns_id = ns_name.Id(); + Xow_ns ns = ns_mgr.Ids_get_or_null(ns_id); + if (ns == null) continue; // ns_id of -2 will not be found in site_ns + ns.Name_bry_(ns_name.Name_db()); + } + ns_names = lang.Ns_aliases(); + ns_names_len = ns_names.Len(); + for (int i = 0; i < ns_names_len; i++) { + Xow_ns ns_name = ns_names.Get_at(i); + int ns_id = ns_name.Id(); + ns_mgr.Aliases_add(ns_id, ns_name.Name_db_str()); + } + ns_mgr.Init(); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_mgr_tst.java b/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_mgr_tst.java index a27517de8..95c027793 100644 --- a/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_mgr_tst.java @@ -13,3 +13,65 @@ 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.wikis.nss; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import org.junit.*; +import gplx.xowa.langs.cases.*; +public class Xow_ns_mgr_tst { + @Before public void init() {fxt.Clear();} private Xow_ns_mgr_fxt fxt = new Xow_ns_mgr_fxt(); + @Test public void Basic() {fxt.ini_ns_(-2, 0, 1).run_Ords_sort().tst_Ords(-2, -100, 0, 1);} + @Test public void Talk_skip() {fxt.ini_ns_(-2, 0, 2, 3).run_Ords_sort().tst_Ords(-2, -100, 0, 1, 2, 3);} + @Test public void Subj_skip() {fxt.ini_ns_(-2, 1, 2, 3).run_Ords_sort().tst_Ords(-2, -100, 0, 1, 2, 3);} + @Test public void Out_of_order() {fxt.ini_ns_(3, 1, 2, -2).run_Ords_sort().tst_Ords(-2, -100, 0, 1, 2, 3);} + @Test public void Skip_odd() {fxt.ini_ns_(-2, 1, 3).run_Ords_sort().tst_Ords(-2, -100, 0, 1, 2, 3);} + @Test public void Skip_even() {fxt.ini_ns_(-2, 2, 4).run_Ords_sort().tst_Ords(-2, -100, 2, 3, 4, 5);} + @Test public void Ns_alias() { + fxt.Ns_mgr().Aliases_clear(); + fxt.Ns_mgr().Add_new(Xow_ns_.Tid__template, "Template"); + fxt.Ns_mgr().Aliases_add(Xow_ns_.Tid__template, "Templatex"); + fxt.Ns_mgr().Init(); + byte[] name = Bry_.new_a7("Templatex:Abc"); + Tfds.Eq(10, fxt.Ns_mgr().Tmpls_get_w_colon(name, 0, name.length)); + } + @Test public void Utf8() {// PURPOSE: handle different casings for ns_names; PAGE:ru.w:Портрет_итальянского_Ренессанса DATE:2014-07-04 + Xow_ns_mgr ns_mgr = new Xow_ns_mgr(Xol_case_mgr_.U8()); + ns_mgr.Add_new(1234, "Test"); + ns_mgr.Add_new(1235, "файл"); + fxt.Ns_mgr_(ns_mgr); + fxt.Test_ns_name(1234, "Test", "test", "TEST", "tesT"); + fxt.Test_ns_name(1235, "файл", "Файл"); + } +} +class Xow_ns_mgr_fxt { + private Xow_ns_mgr ns_mgr = new Xow_ns_mgr(Xol_case_mgr_.A7()); + public Xow_ns_mgr Ns_mgr() {return ns_mgr;} + public void Ns_mgr_(Xow_ns_mgr v) {this.ns_mgr = v;} + public void Clear() {ns_mgr.Clear();} + public Xow_ns_mgr_fxt ini_ns_(int... ids) { + int ids_len = ids.length; + for (int i = 0; i < ids_len; i++) { + int id = ids[i]; + ns_mgr.Add_new(id, Int_.To_str(id)); + } + return this; + } + public Xow_ns_mgr_fxt run_Ords_sort() {ns_mgr.Init(); return this;} + public Xow_ns_mgr_fxt tst_Ords(int... expd) { + int actl_len = ns_mgr.Ords_len(); + int[] actl = new int[actl_len]; + for (int i = 0; i < actl_len; i++) { + Xow_ns ns_itm = ns_mgr.Ords_ary()[i]; + actl[i] = ns_itm == null ? -100 : ns_itm.Id(); + } + Tfds.Eq_ary(expd, actl); + return this; + } + public void Test_ns_name(int expd_id, String... ns_names) { + int ns_names_len = ns_names.length; + for (int i = 0; i < ns_names_len; ++i) { + String ns_name = ns_names[i]; + Xow_ns actl_ns = ns_mgr.Names_get_or_null(Bry_.new_u8(ns_name)); + int actl_id = actl_ns == null ? Int_.Min_value : actl_ns.Id(); + Tfds.Eq(expd_id, actl_id, ns_name); + } + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_tst.java b/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_tst.java index a27517de8..72c269cb2 100644 --- a/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/nss/Xow_ns_tst.java @@ -13,3 +13,49 @@ 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.wikis.nss; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import org.junit.*; +public class Xow_ns_tst { + @Before public void init() {fxt.Clear();} private Xow_ns_fxt fxt = new Xow_ns_fxt(); + @Test public void Category() { + fxt .Expd_id_subjId_(Xow_ns_.Tid__category) + .Expd_id_talkId_(Xow_ns_.Tid__category_talk) + .Expd_id_subj_(Bool_.Y) + .Expd_id_talk_(Bool_.N) + .Test(Xow_ns_.Tid__category) + ; + } + @Test public void Category_talk() { + fxt .Expd_id_subjId_(Xow_ns_.Tid__category) + .Expd_id_talkId_(Xow_ns_.Tid__category_talk) + .Expd_id_subj_(Bool_.N) + .Expd_id_talk_(Bool_.Y) + .Test(Xow_ns_.Tid__category_talk) + ; + } + @Test public void Special() { + fxt .Expd_id_subjId_(Xow_ns_.Tid__special) + .Expd_id_talkId_(Xow_ns_.Tid__special) + .Expd_id_subj_(Bool_.Y) + .Expd_id_talk_(Bool_.N) + .Test(Xow_ns_.Tid__special) + ; + } +} +class Xow_ns_fxt { + public void Clear() { + expd_id_subjId = expd_id_talkId = Int_.Max_value; + expd_id_subj = expd_id_talk = false; + } + public Xow_ns_fxt Expd_id_subjId_(int v) {expd_id_subjId = v; return this;} private int expd_id_subjId; + public Xow_ns_fxt Expd_id_talkId_(int v) {expd_id_talkId = v; return this;} private int expd_id_talkId; + public Xow_ns_fxt Expd_id_subj_(boolean v) {expd_id_subj = v; return this;} private boolean expd_id_subj; + public Xow_ns_fxt Expd_id_talk_(boolean v) {expd_id_talk = v; return this;} private boolean expd_id_talk; + public void Test(int nsId) { + Xow_ns actl = new Xow_ns(nsId, Xow_ns_case_.Tid__1st, Bry_.Empty, false); + Tfds.Eq(expd_id_subjId, actl.Id_subj_id()); + Tfds.Eq(expd_id_talkId, actl.Id_talk_id()); + Tfds.Eq(expd_id_subj, actl.Id_is_subj()); + Tfds.Eq(expd_id_talk, actl.Id_is_talk()); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/Xoa_page__commons_mgr.java b/400_xowa/src/gplx/xowa/wikis/pages/Xoa_page__commons_mgr.java index a27517de8..d3d689ce9 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/Xoa_page__commons_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/Xoa_page__commons_mgr.java @@ -13,3 +13,13 @@ 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.wikis.pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public class Xoa_page__commons_mgr { + public boolean Xowa_mockup() {return xowa_mockup;} public void Xowa_mockup_(boolean v) {xowa_mockup = v;} private boolean xowa_mockup; + public Xow_wiki Source_wiki() {return source_wiki;} public void Source_wiki_(Xow_wiki v) {source_wiki = v;} private Xow_wiki source_wiki; + public Xow_wiki Source_wiki_or(Xow_wiki or) {return source_wiki == null ? or : source_wiki;} + public void Clear() { + this.xowa_mockup = false; + this.source_wiki = null; + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/Xopage_html_data.java b/400_xowa/src/gplx/xowa/wikis/pages/Xopage_html_data.java index a27517de8..f924337f1 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/Xopage_html_data.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/Xopage_html_data.java @@ -13,3 +13,33 @@ 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.wikis.pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.xowa.wikis.pages.tags.*; import gplx.xowa.wikis.pages.htmls.*; +public class Xopage_html_data { + public Xopage_html_data(byte[] display_ttl, byte[] body) { + this.display_ttl = display_ttl; + this.body = body; + } + public byte[] Display_ttl() {return display_ttl;} private byte[] display_ttl; + public byte[] Body() {return body;} private final byte[] body; + public boolean Cbk_enabled() {return cbk_enabled;} private boolean cbk_enabled; public void Cbk_enabled_y_() {this.cbk_enabled = true;} + public boolean Js_enabled() {return js_enabled;} private boolean js_enabled; public void Js_enabled_y_() {this.js_enabled = true;} + public Xopg_tag_mgr Head_tags() {return head_tags;} private final Xopg_tag_mgr head_tags = new Xopg_tag_mgr(Bool_.Y); + public Xopg_tag_mgr Tail_tags() {return tail_tags;} private final Xopg_tag_mgr tail_tags = new Xopg_tag_mgr(Bool_.N); + + public void Apply(Xoa_page page) { + Xopg_tag_wtr_.Add__tab_uid (head_tags, page.Page_guid()); + + Xopg_html_data html_data = page.Html_data(); + html_data.Html_restricted_n_(); + html_data.Skip_parse_(Bool_.Y); + html_data.Display_ttl_(display_ttl); + html_data.Custom_body_(this.Body()); + html_data.Custom_head_tags().Copy(head_tags); + html_data.Custom_tail_tags().Copy(tail_tags); + html_data.Cbk_enabled_(cbk_enabled); + html_data.Js_enabled_(js_enabled); + } + + public static Xopage_html_data err_(String msg) {return new Xopage_html_data(Bry_.Empty, Bry_.new_u8(msg));} +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/Xopg_module_mgr.java b/400_xowa/src/gplx/xowa/wikis/pages/Xopg_module_mgr.java index a27517de8..6ccc058b8 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/Xopg_module_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/Xopg_module_mgr.java @@ -13,3 +13,42 @@ 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.wikis.pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.bits.*; +public class Xopg_module_mgr { + public boolean Math_exists() {return math_exists;} public void Math_exists_(boolean v) {math_exists = v;} private boolean math_exists; + public boolean Imap_exists() {return imap_exists;} public void Imap_exists_(boolean v) {imap_exists = v;} private boolean imap_exists; + public boolean Gallery_packed_exists() {return gallery_packed_exists;} public void Gallery_packed_exists_(boolean v) {gallery_packed_exists = v;} private boolean gallery_packed_exists; + public boolean Hiero_exists() {return hiero_exists;} public void Hiero_exists_(boolean v) {hiero_exists = v;} private boolean hiero_exists; + public void Init(boolean math, boolean imap, boolean packed, boolean hiero) { + this.math_exists = math; + this.imap_exists = imap; + this.gallery_packed_exists = packed; + this.hiero_exists = hiero; + } + public int Flag() {return Calc_flag(math_exists, imap_exists, gallery_packed_exists, hiero_exists);} + public void Flag_(int v) { + this.math_exists = Bitmask_.Has_int(v, Tid_math); + this.imap_exists = Bitmask_.Has_int(v, Tid_imap); + this.gallery_packed_exists = Bitmask_.Has_int(v, Tid_packed); + this.hiero_exists = Bitmask_.Has_int(v, Tid_hiero); + } + public void Clear() { + math_exists = imap_exists = gallery_packed_exists = hiero_exists = false; + } + private static int Calc_flag(boolean math, boolean imap, boolean packed, boolean hiero) { + int rv = 0; + if (math) rv = Bitmask_.Add_int(rv, Tid_math); + if (imap) rv = Bitmask_.Add_int(rv, Tid_imap); + if (packed) rv = Bitmask_.Add_int(rv, Tid_packed); + if (hiero) rv = Bitmask_.Add_int(rv, Tid_hiero); + return rv; + } + private static final int // SERIALIZED; only supports 8 different types + Tid_math = 1 + , Tid_imap = 2 + , Tid_packed = 4 + , Tid_hiero = 8 + ; + public static final int Tid_null = 0; +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/Xopg_page_.java b/400_xowa/src/gplx/xowa/wikis/pages/Xopg_page_.java index a27517de8..e610b1e30 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/Xopg_page_.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/Xopg_page_.java @@ -13,3 +13,7 @@ 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.wikis.pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public class Xopg_page_ { + public static final byte Tid_read = 0, Tid_edit = 1, Tid_html = 2; +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/Xopg_page_heading.java b/400_xowa/src/gplx/xowa/wikis/pages/Xopg_page_heading.java index a27517de8..f4cbffb0a 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/Xopg_page_heading.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/Xopg_page_heading.java @@ -13,3 +13,35 @@ 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.wikis.pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.brys.*; import gplx.core.brys.fmtrs.*; +import gplx.xowa.wikis.pages.htmls.*; +public class Xopg_page_heading implements Bfr_arg { + private Xowe_wiki wiki; + private Xopg_html_data html_data; + private byte[] ttl_full_db; + private byte[] display_title; + private boolean mode_is_read; + public Xopg_page_heading Init(Xowe_wiki wiki, boolean mode_is_read, Xopg_html_data html_data, byte[] ttl_full_db, byte[] display_title) { + this.wiki = wiki; + this.mode_is_read = mode_is_read; + this.ttl_full_db = ttl_full_db; + this.html_data = html_data; + this.display_title = display_title; + return this; + } + public void Bfr_arg__add(Bry_bfr bfr) { + if (html_data.Xtn_pgbnr() != null) return; // pgbnr exists; don't add title + byte[] edit_lead_section = Bry_.Empty; + if ( wiki.Parser_mgr().Hdr__section_editable__mgr().Enabled() + && mode_is_read) { + Bry_bfr tmp_bfr = Bry_bfr_.New(); + wiki.Parser_mgr().Hdr__section_editable__mgr().Write_html(tmp_bfr, ttl_full_db, Bry_.Empty, Bry__lead_section_hint); + edit_lead_section = tmp_bfr.To_bry_and_clear(); + } + + fmtr.Bld_many(bfr, display_title, edit_lead_section); + } + private static final byte[] Bry__lead_section_hint = Bry_.new_u8("(Lead)"); + private final Bry_fmt fmtr = Bry_fmt.Auto_nl_apos("

      ~{page_title}~{edit_lead_section}

      "); // ~{page_title} +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/Xopg_tab_data.java b/400_xowa/src/gplx/xowa/wikis/pages/Xopg_tab_data.java index a27517de8..9906666bd 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/Xopg_tab_data.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/Xopg_tab_data.java @@ -13,3 +13,15 @@ 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.wikis.pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.xowa.guis.views.*; +public class Xopg_tab_data { + public Xog_tab_itm Tab() {return tab;} public void Tab_(Xog_tab_itm v) {this.tab = v;} private Xog_tab_itm tab; + public boolean Cancel_show() {return cancel_show;} public void Cancel_show_y_() {this.cancel_show = true;} private boolean cancel_show; // used for Special:Search + public Xog_tab_close_mgr Close_mgr() {return close_mgr;} private final Xog_tab_close_mgr close_mgr = new Xog_tab_close_mgr(); + public void Clear() { + this.cancel_show = false; + this.tab = null; + close_mgr.Clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/Xopg_tmpl_prepend_mgr.java b/400_xowa/src/gplx/xowa/wikis/pages/Xopg_tmpl_prepend_mgr.java index a27517de8..de64a9453 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/Xopg_tmpl_prepend_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/Xopg_tmpl_prepend_mgr.java @@ -13,3 +13,48 @@ 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.wikis.pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.btries.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*; +public class Xopg_tmpl_prepend_mgr { + private Bry_bfr[] stack = Bry_bfr_.Ary_empty; private int stack_len, stack_max; + public void Clear() { + stack = Bry_bfr_.Ary_empty; stack_len = stack_max = 0; + } + public boolean Tmpl_args_parsing() {return tmpl_args_parsing;} public void Tmpl_args_parsing_(boolean v) {tmpl_args_parsing = v;} private boolean tmpl_args_parsing; + public Xopg_tmpl_prepend_mgr Bgn(Bry_bfr bfr) { + int new_len = stack_len + 1; + if (new_len > stack_max) { + stack_max += 8; + Bry_bfr[] new_stack = new Bry_bfr[stack_max]; + Array_.Copy_to(stack, new_stack, 0); + stack = new_stack; + } + stack[stack_len] = bfr; + stack_len = new_len; + return this; + } + public void End(Xop_ctx ctx, Bry_bfr bfr, byte[] val, int val_len, boolean called_from_tmpl) { + if ( val_len > 0 // val is not empty + && tmpl_prepend_nl_trie.Match_bgn(val, 0, val_len) != null // val starts with {| : ; # *; REF.MW:Parser.php|braceSubstitution + ) { + boolean add = true; + if (called_from_tmpl) { // called from tmpl + for (int i = stack_len - 1; i > -1; --i) { // iterate backwards over tmpl_stack; + Bry_bfr stack_bfr = stack[i]; + switch (stack_bfr.Get_at_last_or_nil_if_empty()) { + case Byte_ascii.Null: continue; // bfr is empty; ignore it + case Byte_ascii.Nl: add = false; i = -1; break; // bfr ends in \n; don't add and stop; PAGE:bn.w:লিওনেল_মেসি |ko.w:도쿄_지하철_히비야_선|DATE:2014-05-27 + default: i = -1; break; // bfr has char; stop + } + } + } + else // called from func arg; always add \n; EX:vi.w:Friedrich_II_của_Phổ; DATE:2014-04-26 + add = true; + if (add) + bfr.Add_byte(Byte_ascii.Nl); + } + if (called_from_tmpl) --stack_len; + } + private static final Btrie_fast_mgr tmpl_prepend_nl_trie = Xop_curly_bgn_lxr.tmpl_bgn_trie_(); +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/Xow_page_mgr.java b/400_xowa/src/gplx/xowa/wikis/pages/Xow_page_mgr.java index a27517de8..bf27f5a10 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/Xow_page_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/Xow_page_mgr.java @@ -13,3 +13,186 @@ 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.wikis.pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; import gplx.xowa.langs.vnts.*; +import gplx.xowa.guis.views.*; import gplx.xowa.parsers.utils.*; +import gplx.xowa.wikis.pages.dbs.*; import gplx.xowa.wikis.pages.redirects.*; +public class Xow_page_mgr implements Gfo_invk { + private final Xowe_wiki wiki; + public Xow_page_mgr(Xowe_wiki wiki) {this.wiki = wiki;} + public Xoae_page Load_page_by_ttl (Xoa_ttl ttl) {return Load_page(ttl, Bool_.N);} + public Xoae_page Load_page_by_ttl_for_msg (Xoa_ttl ttl) {return Load_page(ttl, Bool_.Y);} + private Xoae_page Load_page(Xoa_ttl ttl, boolean called_from_msg) { + Xoae_page rv = Xoae_page.New(wiki, ttl); + Load_by_ns(rv, wiki.Utl__url_parser().Parse(ttl.Raw()), ttl, called_from_msg); + return rv; + } + private void Load_by_ns(Xoae_page rv, Xoa_url url, Xoa_ttl ttl, boolean called_from_msg) { + rv.Url_(url); // NOTE: must update page.Url(); should combine with Xoae_page.New() + + // WORKAROUND: loop twice in order to allow redirects from regular page to special + for (int i = 0; i < 2; i++) { + Xow_ns ns = ttl.Ns(); + switch (ns.Id()) { + case Xow_ns_.Tid__special: // Special pages are built (not loaded from db) + wiki.Special_mgr().Special__gen(wiki.App(), wiki, rv, url, ttl); + Xopg_redirect_itm redirect_itm = rv.Redirect_trail().Itms__get_at_nth_or_null(); + if (redirect_itm != null) { + rv.Redirect_trail().Clear(); // clear needed; EX: Special:Random -> [[Redirected_page]] -> {must clear here} -> [[Page]] + Load_by_ns(rv, redirect_itm.Url(), redirect_itm.Ttl(), called_from_msg); + } + return; + case Xow_ns_.Tid__mediawiki: // MediaWiki msgs can either be loaded from memory, or from database + if ( !called_from_msg // if called_from_msg, fall through to actual data retrieval below, else infinite loop; DATE:2014-05-09 + && Xow_page_tid.Identify_by_ttl(ttl.Page_db()) == Xow_page_tid.Tid_wikitext // skip ".js" and ".css" pages in MediaWiki; DATE:2014-06-13 + ) { + Xol_lang_itm lang = wiki.Lang(); + byte[] msg_key = ttl.Page_db(); + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_b512(); + msg_key = lang.Case_mgr().Case_build_1st_lower(tmp_bfr, msg_key, 0, msg_key.length); + byte[] msg_val = Xol_msg_mgr_.Get_msg_itm(tmp_bfr, wiki, wiki.Lang(), msg_key).Val(); // NOTE: do not change to Get_msg_val; Get_msg_val, also replaces $1 with values, and $1 needs to be preserved for callers; + rv.Db().Text().Text_bry_(msg_val); + tmp_bfr.Mkr_rls(); + return; + } + break; + } + Xoa_ttl redirect_ttl = Load_from_db(rv, ns, ttl, url.Qargs_mgr().Match(Xoa_url_.Qarg__redirect, Xoa_url_.Qarg__redirect__no)); + // GOTO_BY_LOOP: handle redirect to special + if (redirect_ttl != null && redirect_ttl.Ns().Id_is_special()) { + ttl = redirect_ttl; + url = wiki.Utl__url_parser().Parse(ttl.Raw()); // update url w/ redirected ttl; need to update qargs else will redirect to correct ttl (Special:XowaCfg) but not qargs (grp=xowa.addon.math); EX:src=Options/Math; trg=Special:XowaCfg?grp=xowa.addon.math; DATE:2017-01-02 + rv.Redirect_trail().Clear(); + rv.Db().Page().Exists_y_(); + } + // not a redirect to special; just exit + else + return; + } + } + public Xoa_ttl Load_from_db(Xoae_page rv, Xow_ns ns, Xoa_ttl ttl, boolean redirect_force) { + int redirects = 0; + Xowd_page_itm page_row = Xowd_page_itm.new_tmp(); + while (true) { // loop until (a) no more redirects or (b) page not found + // load from page table + boolean exists = wiki.Db_mgr().Load_mgr().Load_by_ttl(page_row, ns, ttl.Page_db()); + if (!exists) {rv.Db().Page().Exists_n_(); return ttl;} + if (wiki.App().Mode().Tid_is_gui()) // NOTE: must check if gui, else will write during mass build; DATE:2014-05-03 + wiki.Appe().Usr_dlg().Prog_many("", "", "loading page for ~{0}", ttl.Raw()); + + // load page_info + rv.Db().Page().Id_(page_row.Id()).Modified_on_(page_row.Modified_on()).Html_db_id_(page_row.Html_db_id()); + + // load from text table + wiki.Db_mgr().Load_mgr().Load_page(page_row, ns); + byte[] wtxt = page_row.Text(); + rv.Db().Text().Text_bry_(wtxt); + if (redirect_force) return ttl; // redirect_force passed; return page now, even if page is a redirect elsewhere; NOTE: only applies to WTXT, not HTML + + // handle redirects for html_dbs + if ( page_row.Redirect_id() > 0 // redirect exists + && Bry_.Len_eq_0(wtxt)) { // wikitext is not found + Redirect_to_html_page(rv, wiki, page_row); + return ttl; + } + + // handle redirects + Xoa_ttl redirect_ttl = wiki.Redirect_mgr().Extract_redirect(wtxt); + if ( redirect_ttl == null // not a redirect + || redirects++ > 4) // too many redirects; something went wrong + return redirect_ttl; + + // redirect; do some bookkeeping and reset ns / ttl + // NOTE: this adds the target ttl to redirect_mgr (#REDIRECT [[A]]); note that special redirects will add source ttl; DATE:2016-07-31 + rv.Redirect_trail().Itms__add__article(Xoa_url.New(wiki, rv.Ttl()), rv.Ttl(), wtxt);// NOTE: must be url_encoded; EX: "en.wikipedia.org/?!" should generate link of "en.wikipedia.org/%3F!?redirect=no" + rv.Ttl_(redirect_ttl); + ns = redirect_ttl.Ns(); + ttl = redirect_ttl; + } + } + private void Redirect_to_html_page(Xoae_page rv, Xowe_wiki wiki, Xowd_page_itm page_row) { // handle redirects for HtmlDbs; PAGE:fr.b:Wikibooks DATE:2016-07-14 + // load redirect + Xowd_page_itm redirect_row = new Xowd_page_itm(); + wiki.Db_mgr().Load_mgr().Load_by_id(redirect_row, page_row.Redirect_id()); + if (rv.Db().Page().Exists_n()) { + return; + } + + // set redirect data + rv.Db().Page().Id_(redirect_row.Id()).Modified_on_(redirect_row.Modified_on()).Html_db_id_(redirect_row.Html_db_id()); + Xoa_ttl redirect_ttl = wiki.Ttl_parse(redirect_row.Ns_id(), redirect_row.Ttl_page_db()); + rv.Ttl_(redirect_ttl); + rv.Redirect_trail().Itms__add__article(Xoa_url.New(wiki, redirect_ttl), redirect_ttl, Bry_.Empty); // NOTE: must be url_encoded; EX: "en.wikipedia.org/?!" should generate link of "en.wikipedia.org/%3F!?redirect=no" + } + public Xoae_page Load_page_and_parse(Xoa_url url, Xoa_ttl ttl) {return Load_page_and_parse(url, ttl, wiki.Lang(), wiki.Appe().Gui_mgr().Browser_win().Active_tab(), true);} + public Xoae_page Load_page_and_parse(Xoa_url url, Xoa_ttl ttl, Xol_lang_itm lang, Xog_tab_itm tab, boolean parse_page) { + wiki.Init_assert(); + Xoae_page page = Xoae_page.New(wiki, ttl); page.Tab_data().Tab_(tab); + // COMMENT: breaks position-restore when moving back from history; if (tab != null) tab.Page_ref_(page); // HACK: (1) null check for http server; (2) Page_ref_(page) needed for log in xobc + this.Load_by_ns(page, url, ttl, false); + if (page.Db().Page().Exists_n()) { // page doesn't exist; try variants + boolean vnt_missing = true; + Xol_vnt_mgr vnt_mgr = lang.Vnt_mgr(); + if (vnt_mgr.Enabled()) { // if vnt enabled, then try to load by vnt form; DATE:2015-09-15 + gplx.xowa.wikis.data.tbls.Xowd_page_itm page_itm = vnt_mgr.Convert_mgr().Convert_ttl(wiki, ttl); + if (page_itm != null && page_itm.Exists()) { + Xoa_ttl vnt_ttl = Xoa_ttl.Parse(wiki, ttl.Ns().Id(), page_itm.Ttl_page_db()); + page = this.Load_page(vnt_ttl, false); + vnt_missing = page.Db().Page().Exists_n(); + } + } + if (vnt_missing) { + if (ttl.Ns().Id_is_file()) { + Xowe_wiki commons_wiki = (Xowe_wiki)wiki.Appe().Wiki_mgr().Get_by_or_null(wiki.Commons_wiki_key()); + if (commons_wiki != null) { // commons exists + if (!Bry_.Eq(wiki.Domain_bry(), commons_wiki.Domain_bry())) { // !Bry_.Eq is recursion guard + Xoae_page rv = commons_wiki.Data_mgr().Load_page_and_parse(url, ttl, wiki.Lang(), tab, true); + if (rv.Db().Page().Exists()) { + rv.Commons_mgr().Source_wiki_(wiki); + return rv; + } + else { + rv.Db().Page().Exists_y_(); + page.Commons_mgr().Xowa_mockup_(true); + return page; + } + } + } + } + else { + page.Db().Page().Exists_n_(); + return page; + } + } + } + if (page.Db().Page().Exists_n()) return page; // NOTE: commons can return null page + page.Tab_data().Tab_(tab); + page.Lang_(lang); + if (parse_page) + wiki.Parser_mgr().Parse(page, false); // NOTE: do not clear page b/c reused for search + return page; + } + public void Redirect(Xoae_page page, byte[] page_bry) { + Xoa_ttl trg_ttl = Xoa_ttl.Parse(wiki, page_bry); + Xoa_url trg_url = Xoa_url.New(wiki.Domain_bry(), page_bry); + page.Ttl_(trg_ttl).Url_(trg_url); + page.Redirect_trail().Itms__add__article(trg_url, trg_ttl, null); + wiki.Data_mgr().Load_from_db(page, trg_ttl.Ns(), trg_ttl, trg_url.Qargs_mgr().Match(Xoa_url_.Qarg__redirect, Xoa_url_.Qarg__redirect__no)); + } + public void Redirect2(Xowe_wiki wiki2, Xoae_page page, byte[] page_bry) { + Xoa_ttl trg_ttl = Xoa_ttl.Parse(wiki2, page_bry); + Xoa_url trg_url = Xoa_url.New(wiki2.Domain_bry(), page_bry); + page.Ttl_(trg_ttl).Url_(trg_url); + page.Redirect_trail().Itms__add__article(trg_url, trg_ttl, null); + wiki2.Data_mgr().Load_from_db(page, trg_ttl.Ns(), trg_ttl, trg_url.Qargs_mgr().Match(Xoa_url_.Qarg__redirect, Xoa_url_.Qarg__redirect__no)); + } + + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_create_enabled_)) wiki.Db_mgr().Save_mgr().Create_enabled_(m.ReadYn("v")); + else if (ctx.Match(k, Invk_update_modified_on_enabled_)) wiki.Db_mgr().Save_mgr().Update_modified_on_enabled_(m.ReadYn("v")); + else return Gfo_invk_.Rv_unhandled; + return this; + } + private static final String Invk_create_enabled_ = "create_enabled_", Invk_update_modified_on_enabled_ = "update_modified_on_enabled_"; +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/Xowe_page_mgr.java b/400_xowa/src/gplx/xowa/wikis/pages/Xowe_page_mgr.java index a27517de8..a9264526b 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/Xowe_page_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/Xowe_page_mgr.java @@ -13,3 +13,109 @@ 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.wikis.pages; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.net.qargs.*; +import gplx.xowa.guis.views.*; +import gplx.xowa.addons.wikis.pages.syncs.core.*; +import gplx.xowa.wikis.data.tbls.*; +public class Xowe_page_mgr { + private final Xowe_wiki wiki; + private final Bry_bfr tmp_bfr = Bry_bfr_.New(); + private final Gfo_qarg_mgr tmp_qarg_mgr = new Gfo_qarg_mgr(); + public Xowe_page_mgr(Xowe_wiki wiki) {this.wiki = wiki;} + public Xosync_read_mgr Sync_mgr() {return read_mgr;} private final Xosync_read_mgr read_mgr = new Xosync_read_mgr(); + public void Init_by_wiki(Xowe_wiki wiki) { + read_mgr.Init_by_wiki(wiki); + } + public Xoae_page Load_page(Xoa_url url, Xoa_ttl ttl, Xog_tab_itm tab) { // NOTE: called by GUI and HTTP_SERVER; not called by MASS_PARSE + Xoa_app_.Usr_dlg().Log_many("", "", "page.load: url=~{0}", url.To_str()); + Wait_for_popups(wiki.App()); + Xowe_wiki_.Rls_mem_if_needed(wiki); + + // handle curid query_arg; EX:en.wikipedia.org/wiki/?curid=303 DATE:2017-02-15 + Gfo_qarg_itm[] qarg_ary = url.Qargs_ary(); + // if qargs exist... + if (qarg_ary.length > 0) { + try { + tmp_qarg_mgr.Init(qarg_ary); + byte[] curid_bry = tmp_qarg_mgr.Read_bry_or(Xoa_url_.Qarg__curid, null); + // if "curid" qarg exists.... + if (curid_bry != null) { + int curid = Bry_.To_int_or(curid_bry, -1); + Xowd_page_itm tmp_page = wiki.Data__core_mgr().Db__core().Tbl__page().Select_by_id_or_null(curid); + // if curid exists in page tbl... + if (tmp_page != null) { + ttl = wiki.Ttl_parse(tmp_page.Ns_id(), tmp_page.Ttl_page_db()); + // handle "home/wiki/?curid=123"; XO automatically changes to "home/wiki/Main_Page?curid=123"; change back to "home/wiki/?curid=123" + if (url.Page_is_main()) { + url.Page_bry_(Bry_.Empty); + } + } + } + } catch (Exception exc) { + Gfo_usr_dlg_.Instance.Warn_many("", "", "failed to handle cur_id; url=~{0} err=~{1}", url.Raw(), Err_.Message_gplx_log(exc)); + } + } + + // load page meta; wait_for_popups + Xoae_page page = wiki.Data_mgr().Load_page_and_parse(url, ttl, wiki.Lang(), tab, false); + ttl = page.Ttl(); // note that Load_page_and_parse can redirect ttl; EX: Special:Random -> A; DATE:2017-01-05 + Wait_for_popups(wiki.App()); + + // auto-update + Xoa_ttl redirect_ttl = read_mgr.Auto_update(wiki, page, ttl); + if (redirect_ttl != null) { + // page-sync occurred; update ttl to handle any redirection; DATE:2017-05-07 + ttl = redirect_ttl; + + // reload metadata, needed to pick up Html_db_id; DATE:2017-03-13 + page = wiki.Data_mgr().Load_page_and_parse(url, ttl, wiki.Lang(), tab, false); + } + + // load from html_db + boolean from_html_db = page.Db().Page().Html_db_id() != -1; + boolean read_from_html_db_preferred = wiki.Html__hdump_mgr().Load_mgr().Read_preferred(); + if (from_html_db) { + if (read_from_html_db_preferred) { + wiki.Html__hdump_mgr().Load_mgr().Load_by_xowe(page); + int html_len = Bry_.Len(page.Db().Html().Html_bry()); + from_html_db = html_len > 0; // NOTE: archive.org has some wtxt_dbs which included page|html_db_id without actual html_dbs; DATE:2016-06-22 + Gfo_usr_dlg_.Instance.Log_many("", "", "page_load: loaded html; page=~{0} html_len=~{1}", ttl.Full_db(), html_len); + } + else + from_html_db = false; + } + + // load from wtxt_db; occurs if (a) no html_db_id; (b) option says to use wtxt db; (c) html_db_id exists, but no html_db; + if (!from_html_db) { + wiki.Parser_mgr().Parse(page, false); + + // load from html_db if no wtxt found and option just marked as not read_preferred + if ( Bry_.Len_eq_0(page.Db().Text().Text_bry()) // no wtxt found + && !ttl.Ns().Id_is_special() // skip special + && !read_from_html_db_preferred // read preferred not marked + ) { + wiki.Html__hdump_mgr().Load_mgr().Load_by_xowe(page); + from_html_db = Bry_.Len_gt_0(page.Db().Html().Html_bry()); + } + } + page.Html_data().Hdump_exists_(from_html_db); + + // if [[Category]], generate catlinks (subc; page; file) + if (ttl.Ns().Id_is_ctg()) { + wiki.Ctg__catpage_mgr().Write_catpage(tmp_bfr, page); + if (from_html_db) + page.Db().Html().Html_bry_(Bry_.Add(page.Db().Html().Html_bry(), tmp_bfr.To_bry_and_clear())); + else + page.Html_data().Catpage_data_(tmp_bfr.To_bry_and_clear()); + } + + return page; + } + private static void Wait_for_popups(Xoa_app app) {// HACK: wait for popups to finish, else thread errors due to popups and loader mutating cached items + if (app.Mode().Tid_is_http()) return; + int wait_count = 0; + while (gplx.xowa.htmls.modules.popups.Xow_popup_mgr.Running() && ++wait_count < 100) + gplx.core.threads.Thread_adp_.Sleep(10); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_data.java b/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_data.java index a27517de8..99acd8e5f 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_data.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_data.java @@ -13,3 +13,16 @@ 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.wikis.pages.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public class Xopg_db_data { + public Xopg_db_page Page() {return page;} private final Xopg_db_page page = new Xopg_db_page(); + public Xopg_db_text Text() {return text;} private final Xopg_db_text text = new Xopg_db_text(); + public Xopg_db_html Html() {return html;} private final Xopg_db_html html = new Xopg_db_html(); + public Xopg_db_protection Protection() {return protection;} private final Xopg_db_protection protection = new Xopg_db_protection(); + public void Clear() { + page.Clear(); + html.Clear(); + text.Clear(); + protection.Clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_html.java b/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_html.java index a27517de8..ba50810f0 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_html.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_html.java @@ -13,3 +13,17 @@ 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.wikis.pages.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public class Xopg_db_html { + public Xopg_db_html() {this.Clear();} + public byte[] Html_bry() {return html_bry;} private byte[] html_bry; + public void Html_bry_(byte[] v) {this.html_bry = v;} + public int Zip_tid() {return zip_tid;} private int zip_tid; + public int Hzip_tid() {return hzip_tid;} private int hzip_tid; + public void Zip_tids_(int zip_tid, int hzip_tid) {this.zip_tid = zip_tid; this.hzip_tid = hzip_tid;} + public void Clear() { + html_bry = Bry_.Empty; // NOTE: if null, will cause NullPointer exception on Special pages like Special:XowaDownloadCentral; DATE:2016-07-05 + zip_tid = 0; + hzip_tid = 0; + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_page.java b/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_page.java index a27517de8..5cfd9e3a4 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_page.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_page.java @@ -13,3 +13,54 @@ 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.wikis.pages.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +import gplx.xowa.wikis.nss.*; +public class Xopg_db_page { + public Xopg_db_page() {this.Clear();} + // from page table + public boolean Exists() {return exists;} private boolean exists; + public boolean Exists_n() {return !exists;} + public int Id() {return id;} private int id; + public int Ns_id() {return ns_id;} private int ns_id; + public byte[] Ttl_bry() {return ttl_bry;} private byte[] ttl_bry; + public DateAdp Modified_on() {return modified_on;} private DateAdp modified_on; + public int Text_len() {return text_len;} private int text_len; + public int Text_db_id() {return text_db_id;} private int text_db_id; + public int Html_db_id() {return html_db_id;} private int html_db_id; + public int Redirect_to_id() {return redirect_to_id;} private int redirect_to_id; + public int Score() {return score;} private int score; + + public void Exists_y_() {this.Exists_(Bool_.Y);} + public void Exists_n_() {this.Exists_(Bool_.N);} + public void Exists_(boolean v) {this.exists = v;} + public Xopg_db_page Id_(int v) {this.id = v; return this;} + public Xopg_db_page Score_(int v) {this.score = v; return this;} + public Xopg_db_page Modified_on_(DateAdp v) {this.modified_on = v; return this;} + public Xopg_db_page Html_db_id_(int v) {this.html_db_id = v; return this;} + + // wiki-related + public Xow_ns Ns() {return ns;} private Xow_ns ns; + public Xoa_ttl Ttl() {return ttl;} private Xoa_ttl ttl; + + // init methods + public Xopg_db_page Init_by_db(int id, int ns_id, byte[] ttl_bry, DateAdp modified_on, int text_len, int text_db_id, int html_db_id, int redirect_to_id, int score) { + this.id = id; this.ns_id = ns_id; this.ttl_bry = ttl_bry; this.modified_on = modified_on; + this.text_len = text_len; this.text_db_id = text_db_id; this.html_db_id = html_db_id; this.redirect_to_id = redirect_to_id; this.score = score; + this.ns = null; this.ttl = null; + return this; + } + public Xopg_db_page Init_by_wiki(Xow_wiki wiki) { + this.ns = wiki.Ns_mgr().Ids_get_or_null(ns_id); + this.ttl = wiki.Ttl_parse(ns_id, ttl_bry); + return this; + } + public void Init_by_mp(int id, int score) { + this.id = id; + this.score = score; + } + public void Clear() { + this.exists = true; + this.modified_on = DateAdp_.MinValue; // NOTE: must set to MinValue else some tests will fail + this.html_db_id = -1; // NOTE: must set to -1 b/c code checks for -1 to indicate no html; DATE:2016-07-14 + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_protection.java b/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_protection.java index a27517de8..93be10f02 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_protection.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_protection.java @@ -13,3 +13,18 @@ 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.wikis.pages.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public class Xopg_db_protection { + public Xopg_db_protection() {this.Clear();} + public byte[] User() {return user;} public Xopg_db_protection User_(byte[] v) {user = v; return this;} private byte[] user; + public byte[] Protection_level() {return protection_level;} public Xopg_db_protection Protection_level_(byte[] v) {protection_level = v; return this;} private byte[] protection_level; + public byte[] Protection_expiry() {return protection_expiry;} private byte[] protection_expiry; + + public void Clear() { + this.user = Bry_.Empty; + this.protection_level = Bry_.Empty; + this.protection_expiry = Bry__protection_expiry__infinite; + } + + public static final byte[] Bry__protection_expiry__infinite = Bry_.new_a7("infinity");// NOTE: means page never expires; must be "infinity" as per en.w:Module:Effective_protection_expiry DATE:2016-08-05 +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_text.java b/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_text.java index a27517de8..fb36bcd7b 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_text.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/dbs/Xopg_db_text.java @@ -13,3 +13,11 @@ 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.wikis.pages.dbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public class Xopg_db_text { + public byte[] Text_bry() {return text_bry;} private byte[] text_bry = Bry_.Empty; // NOTE: if null, will cause NullPointer exception on Special pages like Special:XowaDownloadCentral; DATE:2016-07-05 + public void Text_bry_(byte[] v) {this.text_bry = v;} + public void Clear() { + // text_bry = Bry_.Empty; // TOMBSTONE: do not set to Bry_.Empty else causes mass parse to noop; also causes Options/Files.Clear to noop; DATE:2016-07-15 + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/hdumps/Xopg_hdump_data.java b/400_xowa/src/gplx/xowa/wikis/pages/hdumps/Xopg_hdump_data.java index a27517de8..0fb95c5b4 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/hdumps/Xopg_hdump_data.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/hdumps/Xopg_hdump_data.java @@ -13,3 +13,11 @@ 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.wikis.pages.hdumps; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +import gplx.xowa.addons.htmls.tocs.*; +public class Xopg_hdump_data { + public List_adp Imgs() {return imgs;} private final List_adp imgs = List_adp_.New(); + public void Clear() { + imgs.Clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/htmls/Xopg_html_data.java b/400_xowa/src/gplx/xowa/wikis/pages/htmls/Xopg_html_data.java index a27517de8..97d4ba17e 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/htmls/Xopg_html_data.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/htmls/Xopg_html_data.java @@ -13,3 +13,83 @@ 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.wikis.pages.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +import gplx.xowa.wikis.pages.skins.*; import gplx.xowa.wikis.pages.tags.*; import gplx.xowa.wikis.pages.lnkis.*; +import gplx.langs.htmls.*; import gplx.xowa.htmls.heads.*; import gplx.xowa.addons.htmls.tocs.*; +import gplx.xowa.xtns.pagebanners.*; import gplx.xowa.xtns.indicators.*; +public class Xopg_html_data { + public Xopg_lnki_list Redlink_list() {return redlink_list;} private final Xopg_lnki_list redlink_list = new Xopg_lnki_list(); + + public boolean Html_restricted() {return html_restricted;} private boolean html_restricted = true; + public void Html_restricted_(boolean v) {html_restricted = v;} public void Html_restricted_n_() {Html_restricted_(Bool_.N);} public void Html_restricted_y_() {Html_restricted_(Bool_.Y);} + public byte[] Display_ttl() { + return ( display_ttl_vnt != null // -{T}- was in document + && display_ttl == null // {{DISPLAYTITLE}} does not exist + && lang_convert_content // __NOCONVERTCONTENT__ exists + && lang_convert_title // __NOCONVERTTITLE__ exists + ) + ? display_ttl_vnt // return variant title; DATE:2014-08-29 + : display_ttl // return normal title + ; + } + public Xopg_html_data Display_ttl_(byte[] v) {display_ttl = v; return this;} private byte[] display_ttl; + public boolean Mode_wtxt_shown() {synchronized (this) {return mode_wtxt_shown;}} public void Mode_wtxt_shown_y_() {synchronized (this) {this.mode_wtxt_shown = true;}} private boolean mode_wtxt_shown; + public byte[] Display_ttl_vnt() {return display_ttl_vnt;} public void Display_ttl_vnt_(byte[] v) {display_ttl_vnt = v;} private byte[] display_ttl_vnt; + public byte[] Content_sub() {return content_sub;} public void Content_sub_(byte[] v) {content_sub = v;} private byte[] content_sub; + public Xopg_page_heading Page_heading() {return page_heading;} private final Xopg_page_heading page_heading = new Xopg_page_heading(); + public String Bmk_pos() {return html_bmk_pos;} public void Bmk_pos_(String v) {html_bmk_pos = v;} private String html_bmk_pos; + public Bry_bfr Portal_div_xtn() {return portal_div_xtn;} private Bry_bfr portal_div_xtn = Bry_bfr_.Reset(255); + public byte[] Edit_preview_w_dbg() {return Bry_.Add(xtn_scribunto_dbg, edit_preview);} public void Edit_preview_(byte[] v) {edit_preview = v;} private byte[] edit_preview = Bry_.Empty; + public int Lnke_autonumber_next() {return lnke_autonumber++;} private int lnke_autonumber = 1; + public int Sect_uid() {return sect_uid;} private int sect_uid = -1; public int Sect_uid_next() {return ++sect_uid;} + public boolean Cbk_enabled() {return cbk_enabled;} private boolean cbk_enabled; public void Cbk_enabled_(boolean v) {this.cbk_enabled = v;} + public boolean Js_enabled() {return js_enabled;} private boolean js_enabled; public void Js_enabled_(boolean v) {this.js_enabled = v;} + public boolean Hdump_exists() {return hdump_exists;} private boolean hdump_exists; public void Hdump_exists_(boolean v) {this.hdump_exists = v;} + public byte[] Catpage_data() {return catpage_data;} private byte[] catpage_data; public void Catpage_data_(byte[] v) {this.catpage_data = v;} + + public boolean Writing_hdr_for_toc() {return writing_hdr_for_toc;} private boolean writing_hdr_for_toc; public void Writing_hdr_for_toc_y_() {writing_hdr_for_toc = Bool_.Y;} public void Writing_hdr_for_toc_n_() {writing_hdr_for_toc = Bool_.N;} + public Xoh_toc_mgr Toc_mgr() {return toc_mgr;} private final Xoh_toc_mgr toc_mgr = new Xoh_toc_mgr(); + + public boolean Lang_convert_content() {return lang_convert_content;} public void Lang_convert_content_(boolean v) {lang_convert_content = v;} private boolean lang_convert_content = true; + public boolean Lang_convert_title() {return lang_convert_title;} public void Lang_convert_title_(boolean v) {lang_convert_title = v;} private boolean lang_convert_title = true; + public Xopg_xtn_skin_mgr Xtn_skin_mgr() {return xtn_skin_mgr;} private Xopg_xtn_skin_mgr xtn_skin_mgr = new Xopg_xtn_skin_mgr(); + public Indicator_html_bldr Indicators() {return indicators;} private final Indicator_html_bldr indicators = new Indicator_html_bldr(); + public int Xtn_gallery_next_id() {return ++xtn_gallery_next_id;} private int xtn_gallery_next_id = -1; + public boolean Xtn_gallery_packed_exists() {return xtn_gallery_packed_exists;} public void Xtn_gallery_packed_exists_y_() {xtn_gallery_packed_exists = true;} private boolean xtn_gallery_packed_exists; + public boolean Xtn_imap_exists() {return xtn_imap_exists;} public void Xtn_imap_exists_y_() {xtn_imap_exists = true;} private boolean xtn_imap_exists; + public int Xtn_imap_next_id() {return ++xtn_imap_next_id;} private int xtn_imap_next_id; // NOTE: must keep separate imap_id b/c html_elem_id is not always set; + public byte[] Xtn_search_text() {return xtn_search_txt;} public void Xtn_search_text_(byte[] v) {xtn_search_txt = v;} private byte[] xtn_search_txt = Bry_.Empty; + public byte[] Xtn_scribunto_dbg() {return xtn_scribunto_dbg;} public void Xtn_scribunto_dbg_(byte[] v) {xtn_scribunto_dbg = Bry_.Add(xtn_scribunto_dbg, v);} private byte[] xtn_scribunto_dbg = Bry_.Empty; + public Pgbnr_itm Xtn_pgbnr() {return xtn_pgbnr;} public void Xtn_pgbnr_(Pgbnr_itm v) {xtn_pgbnr = v;} private Pgbnr_itm xtn_pgbnr; + public Xoh_head_mgr Head_mgr() {return module_mgr;} private Xoh_head_mgr module_mgr = new Xoh_head_mgr(); + public boolean Skip_parse() {return skip_parse;} public void Skip_parse_(boolean v) {skip_parse = v;} private boolean skip_parse; + public Xopg_tag_mgr Custom_head_tags() {return head_tags;} private final Xopg_tag_mgr head_tags = new Xopg_tag_mgr(Bool_.Y); + public Xopg_tag_mgr Custom_tail_tags() {return tail_tags;} private final Xopg_tag_mgr tail_tags = new Xopg_tag_mgr(Bool_.N); + public byte[] Custom_html() {return custom_html;} public Xopg_html_data Custom_html_(byte[] v) {custom_html = v; return this;} private byte[] custom_html; + public byte[] Custom_body() {return custom_body;} public Xopg_html_data Custom_body_(byte[] v) {custom_body = v; return this;} private byte[] custom_body; + public byte[] Custom_tab_name() {return custom_tab_name;} public Xopg_html_data Custom_tab_name_(byte[] v) {custom_tab_name = v; return this;} private byte[] custom_tab_name; + public void Clear() { + redlink_list.Clear(); + toc_mgr.Clear(); + + html_restricted = true; + display_ttl = content_sub = display_ttl_vnt = null; + lang_convert_content = lang_convert_title = true; + lnke_autonumber = 1; + sect_uid = -1; + xtn_skin_mgr.Clear(); + xtn_gallery_packed_exists = false; + xtn_imap_next_id = 0; xtn_gallery_next_id = -1; + xtn_imap_exists = false; + xtn_search_txt = Bry_.Empty; + xtn_scribunto_dbg = Bry_.Empty; + xtn_pgbnr = null; + module_mgr.Clear(); + custom_html = custom_tab_name = null; + indicators.Clear(); + this.mode_wtxt_shown = false; + } + public void Init_by_page(Xoa_ttl ttl) { + redlink_list.Disabled_(ttl.Ns().Id_is_module()); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/lnkis/Xoh_redlink_utl.java b/400_xowa/src/gplx/xowa/wikis/pages/lnkis/Xoh_redlink_utl.java index a27517de8..312b3c548 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/lnkis/Xoh_redlink_utl.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/lnkis/Xoh_redlink_utl.java @@ -13,3 +13,8 @@ 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.wikis.pages.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public class Xoh_redlink_utl { + public static final byte[] Cls_bry = Bry_.new_a7("class='new'"); + public static final String New_str = "new"; +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/lnkis/Xopg_lnki_itm.java b/400_xowa/src/gplx/xowa/wikis/pages/lnkis/Xopg_lnki_itm.java index a27517de8..7a01319f8 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/lnkis/Xopg_lnki_itm.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/lnkis/Xopg_lnki_itm.java @@ -13,3 +13,8 @@ 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.wikis.pages.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public interface Xopg_lnki_itm { + Xoa_ttl Ttl(); + int Html_uid(); void Html_uid_(int v); +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/lnkis/Xopg_lnki_list.java b/400_xowa/src/gplx/xowa/wikis/pages/lnkis/Xopg_lnki_list.java index a27517de8..7c6fdc6b4 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/lnkis/Xopg_lnki_list.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/lnkis/Xopg_lnki_list.java @@ -13,3 +13,37 @@ 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.wikis.pages.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +import gplx.xowa.wikis.nss.*; import gplx.xowa.parsers.lnkis.*; +public class Xopg_lnki_list { + private final List_adp list = List_adp_.New(); + private int lnki_idx; + public Xopg_lnki_list() { + this.Clear(); + } + public boolean Disabled() {return disabled;} private boolean disabled; public Xopg_lnki_list Disabled_(boolean v) {this.disabled = v; return this;} + public int Len() {return list.Len();} + public Xopg_lnki_itm Get_at(int i) {return (Xopg_lnki_itm)list.Get_at(i);} + public void Add_direct(Xopg_lnki_itm lnki) {list.Add(lnki);} // add lnki directly to list without changing html_uid; needed for hdumps which call "Fill_page" to transfer from Xoh_page to Xoae_page + public void Add(Xopg_lnki_itm lnki) { + if (disabled) return; + Xoa_ttl ttl = lnki.Ttl(); if (ttl == null) return; // ttl is null for invalid links + Xow_ns ns = ttl.Ns(); + lnki.Html_uid_(++lnki_idx); // NOTE: set html_id in order html to print out "id='xowa_lnki_1'; want to print out id for consistency's sake, even if these links won't be check for redlinks; DATE:2015-05-07 + if ( ns.Id_is_file_or_media() // ignore files which will usually not be in local wiki (most are in commons), and whose html is built up separately + || (ns.Id_is_ctg() && !ttl.ForceLiteralLink()) // ignore ctgs which have their own html builder, unless it is literal; EX: [[:Category:A]]; DATE:2014-02-24 + || ns.Id_is_special() // ignore special, especially Search; EX: Special:Search/Earth + || ttl.Anch_bgn() == Anch_bgn_anchor_only // anchor only link; EX: [[#anchor]] + || ttl.Wik_itm() != null // xwiki lnki; EX: simplewiki links in homewiki; [[simplewiki:Earth]] + ) + return; + list.Add(lnki); + } + public void Clear() { + lnki_idx = gplx.xowa.htmls.core.wkrs.lnkis.htmls.Xoh_lnki_wtr.Lnki_id_min; // NOTE: must start at 0, so that ++lnki_idx is > 0; html_wtr checks for > 0; DATE:2014-10-09; OLD_COMMENT: NOTE: should be 0, but for historical reasons, 1st lnki starts at 2; EX: id='xowa_lnki_2' + list.Clear(); + } + + public static final String Lnki_id_prefix = "xolnki_"; + private static final int Anch_bgn_anchor_only = 1; // signifies lnki which is only anchor; EX: [[#anchor]] +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/lnkis/Xopg_redlink_mgr.java b/400_xowa/src/gplx/xowa/wikis/pages/lnkis/Xopg_redlink_mgr.java index a27517de8..5fd2705ec 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/lnkis/Xopg_redlink_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/lnkis/Xopg_redlink_mgr.java @@ -13,3 +13,81 @@ 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.wikis.pages.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +import gplx.xowa.guis.cbks.js.*; +import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.langs.vnts.*; +public class Xopg_redlink_mgr implements Gfo_invk { + private final Xoa_page page; private final Xog_js_wkr js_wkr; + public Xopg_redlink_mgr(Xoa_page page, Xog_js_wkr js_wkr) {this.page = page; this.js_wkr = js_wkr; } + private void Redlink() { + // init; exit if redlink disabled (on Module pages) + Xopg_lnki_list lnki_list = page.Html_data().Redlink_list(); if (lnki_list.Disabled()) return; + Gfo_usr_dlg usr_dlg = Gfo_usr_dlg_.Instance; + Xow_wiki wiki = page.Wiki(); + Ordered_hash lnki_hash = Ordered_hash_.New_bry(); + + // create unique list of page_rows + int len = lnki_list.Len(); + usr_dlg.Log_many("", "", "redlink.redlink_bgn: page=~{0} total_links=~{1}", page.Ttl().Raw(), len); + for (int i = 0; i < len; ++i) { + if (usr_dlg.Canceled()) return; + Xoa_ttl ttl = lnki_list.Get_at(i).Ttl(); + Xowd_page_itm page_row = new Xowd_page_itm().Ttl_(ttl); + byte[] full_db = ttl.Full_db(); + if (!lnki_hash.Has(full_db)) // only search page_table once for multiple identical redlinks; EX: "[[Missing]] [[Missing]]" + lnki_hash.Add(full_db, page_row); + } + + // load page_rows from page_tbl + int page_len = lnki_hash.Len(); + for (int i = 0; i < page_len; i += Batch_size) { + if (usr_dlg.Canceled()) return; + int end = i + Batch_size; if (end > page_len) end = page_len; + wiki.Data__core_mgr().Tbl__page().Select_in__ns_ttl(usr_dlg, lnki_hash, wiki.Ns_mgr(), Bool_.Y, i, end); + // wiki.Db_mgr().Load_mgr().Load_by_ttls(usr_dlg, lnki_hash, Bool_.Y, i, end); + } + + // cross-check page_rows against lnki_list; redlink if missing; + boolean vnt_enabled = wiki.Lang().Vnt_mgr().Enabled(); + Xol_vnt_mgr vnt_mgr = wiki.Lang().Vnt_mgr(); + int redlink_count = 0; + for (int i = 0; i < len; ++i) { + Xopg_lnki_itm lnki = lnki_list.Get_at(i); + byte[] full_db = lnki.Ttl().Full_db(); + Xowd_page_itm page_row = (Xowd_page_itm)lnki_hash.Get_by(full_db); + if (page_row.Exists()) continue; // page exists; nothing to do; + + // for vnt languages, convert missing ttl to vnt and check again; EX: [[zh_cn]] will check for page_ttl for [[zh_tw]] + String html_uid = Xopg_lnki_list.Lnki_id_prefix + Int_.To_str(lnki.Html_uid()); + if (vnt_enabled) { + Xowd_page_itm vnt_page = vnt_mgr.Convert_mgr().Convert_ttl(wiki, lnki.Ttl()); // check db + if (vnt_page != null) { // vnt found; update href to point to vnt + Xoa_ttl vnt_ttl = wiki.Ttl_parse(lnki.Ttl().Ns().Id(), vnt_page.Ttl_page_db()); + js_wkr.Html_atr_set(html_uid, "href", "/wiki/" + String_.new_u8(vnt_ttl.Full_url())); + if (!String_.Eq(vnt_mgr.Html__lnki_style(), "")) js_wkr.Html_atr_set(html_uid, "style", vnt_mgr.Html__lnki_style()); // colorize for debugging + continue; + } + } + + // lnki is missing; redlink it + if (usr_dlg.Canceled()) return; + Js_img_mgr.Update_link_missing(js_wkr, html_uid); + ++redlink_count; + } + usr_dlg.Log_many("", "", "redlink.redlink_end: redlinks_run=~{0}", redlink_count); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_run)) {synchronized (this) {Redlink();}} // NOTE: attempt to eliminate random IndexBounds errors; DATE:2014-09-02 + else return Gfo_invk_.Rv_unhandled; + return this; + } public static final String Invk_run = "run"; + private static final int Batch_size = 32; + + public static void Run_async(Xoa_page pg, Xog_js_wkr js_wkr) { + try { + Xopg_redlink_mgr mgr = new Xopg_redlink_mgr(pg, js_wkr); + gplx.core.threads.Thread_adp_.Start_by_key(gplx.xowa.apps.Xoa_thread_.Key_page_redlink, mgr, gplx.xowa.wikis.pages.lnkis.Xopg_redlink_mgr.Invk_run); + } catch (Exception e) {Gfo_usr_dlg_.Instance.Warn_many("", "", "page.thread.redlinks: page=~{0} err=~{1}", pg.Ttl().Raw(), Err_.Message_gplx_full(e));} + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/redirects/Xopg_redirect_itm.java b/400_xowa/src/gplx/xowa/wikis/pages/redirects/Xopg_redirect_itm.java index a27517de8..11ed3624b 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/redirects/Xopg_redirect_itm.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/redirects/Xopg_redirect_itm.java @@ -13,3 +13,15 @@ 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.wikis.pages.redirects; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public class Xopg_redirect_itm { + public Xopg_redirect_itm(Xoa_url url, Xoa_ttl ttl, byte[] wikitext) { + this.url = url; + this.ttl = ttl; + this.wikitext = wikitext; + } + public Xoa_url Url() {return url;} private final Xoa_url url; // EX: "en.wikipedia.org/wiki/A" + public Xoa_ttl Ttl() {return ttl;} private final Xoa_ttl ttl; // EX: "A" + public byte[] Wikitext() {return wikitext;} private final byte[] wikitext; // EX: "#REDIRECT [[A]]" + public boolean By_wikitext() {return wikitext != null;} // true if by "#REDIRECT [[A]]"; false if by Special:Random, Special:Search, etc.; +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/redirects/Xopg_redirect_mgr.java b/400_xowa/src/gplx/xowa/wikis/pages/redirects/Xopg_redirect_mgr.java index a27517de8..ebbb02294 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/redirects/Xopg_redirect_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/redirects/Xopg_redirect_mgr.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.wikis.pages.redirects; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +import gplx.xowa.specials.*; +public class Xopg_redirect_mgr { + private final List_adp itms = List_adp_.New(); + public int Itms__len() {return itms.Len();} + public byte[] Itms__get_wtxt_at_0th_or_null() {return itms.Len() == 0 ? null : this.Itms__get_at(0).Wikitext();} + public Xopg_redirect_itm Itms__get_at_0th_or_null() {return itms.Len() == 0 ? null : (Xopg_redirect_itm)itms.Get_at(0);} + public Xopg_redirect_itm Itms__get_at_nth_or_null() {return itms.Len() == 0 ? null : (Xopg_redirect_itm)itms.Get_at(itms.Len() - 1);} + public Xopg_redirect_itm Itms__get_at(int i) {return (Xopg_redirect_itm)itms.Get_at(i);} + public void Itms__add__article(Xoa_url url, Xoa_ttl ttl, byte[] wikitext) {Itms__add(url, ttl, wikitext);} + public void Itms__add__special(Xow_wiki wiki, Xow_special_meta meta, Keyval... url_args) { + // build url and include args if available + byte[] url_bry = meta.Ttl_bry(); + int url_args_len = url_args.length; + if (url_args_len > 0) { + Bry_bfr bfr = Bry_bfr_.New(); + bfr.Add(url_bry); + for (int i = 0; i < url_args_len; ++i) { + Keyval url_arg = url_args[i]; + bfr.Add_byte(i == 0 ? Byte_ascii.Question : Byte_ascii.Amp); + bfr.Add_str_u8(url_arg.Key()); + bfr.Add_byte(Byte_ascii.Eq); + bfr.Add_obj(url_arg.Val()); + } + url_bry = bfr.To_bry_and_clear(); + } + + // create objects and add to list + Xoa_url url = wiki.Utl__url_parser().Parse(url_bry); + Xoa_ttl ttl = wiki.Ttl_parse(meta.Ttl_bry()); + this.Itms__add(url, ttl, null); + } + private void Itms__add(Xoa_url url, Xoa_ttl ttl, byte[] wikitext) { + Xopg_redirect_itm itm = new Xopg_redirect_itm(url, ttl, wikitext); + itms.Add(itm); + } + public void Clear() { + itms.Clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/redirects/Xopg_redirect_mgr__tst.java b/400_xowa/src/gplx/xowa/wikis/pages/redirects/Xopg_redirect_mgr__tst.java index a27517de8..c5e1eeca5 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/redirects/Xopg_redirect_mgr__tst.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/redirects/Xopg_redirect_mgr__tst.java @@ -13,3 +13,39 @@ 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.wikis.pages.redirects; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +import org.junit.*; import gplx.core.tests.*; import gplx.xowa.specials.*; +public class Xopg_redirect_mgr__tst { + @Before public void init() {fxt.Clear();} private final Xopg_redirect_mgr__fxt fxt = new Xopg_redirect_mgr__fxt(); + @Test public void Basic() { + fxt.Test__Itms__add__special(fxt.Make_meta("XowaTest"), Keyval_.Ary_empty, fxt.Make__itm("en.wikipedia.org/wiki/Special:XowaTest", "Special:XowaTest", null)); + } + @Test public void Args() { + fxt.Test__Itms__add__special + ( fxt.Make_meta("XowaTest"), Keyval_.Ary(Keyval_.new_("k1", "v1"), Keyval_.new_("k2", "v2"), Keyval_.new_("k3", "v3")) + , fxt.Make__itm("en.wikipedia.org/wiki/Special:XowaTest?k1=v1&k2=v2&k3=v3", "Special:XowaTest", null) + ); + } +} +class Xopg_redirect_mgr__fxt { + private Xow_wiki wiki; + private final Xopg_redirect_mgr mgr = new Xopg_redirect_mgr(); + public void Clear() { + mgr.Clear(); + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + this.wiki = Xoa_app_fxt.Make__wiki__edit(app); + } + public Xow_special_meta Make_meta(String key) {return Xow_special_meta.New_xo(key, "test display name");} + public Xopg_redirect_itm Make__itm(String url_str, String ttl_str, String wikitext) { + Xoa_url url = wiki.Utl__url_parser().Parse(Bry_.new_u8(url_str)); + Xoa_ttl ttl = wiki.Ttl_parse(Bry_.new_u8(ttl_str)); + return new Xopg_redirect_itm(url, ttl, Bry_.new_u8_safe(wikitext)); + } + public void Test__Itms__add__special(Xow_special_meta meta, Keyval[] url_args, Xopg_redirect_itm expd) { + mgr.Itms__add__special(wiki, meta, url_args); + Xopg_redirect_itm actl = mgr.Itms__get_at(0); + Gftest.Eq__bry(expd.Ttl().Raw(), actl.Ttl().Raw(), "ttl"); + Gftest.Eq__str(expd.Url().To_str(), actl.Url().To_str(), "url"); + Gftest.Eq__bry(expd.Wikitext(), actl.Wikitext(), "wikitext"); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_fmtr_arg.java b/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_fmtr_arg.java index a27517de8..c6fe48dd3 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_fmtr_arg.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_fmtr_arg.java @@ -13,3 +13,19 @@ 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.wikis.pages.skins; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public class Xopg_xtn_skin_fmtr_arg implements gplx.core.brys.Bfr_arg { + private Xoae_page page; private byte xtn_skin_tid; + public Xopg_xtn_skin_fmtr_arg(Xoae_page page, byte xtn_skin_tid) { + this.page = page; this.xtn_skin_tid = xtn_skin_tid; + } + public void Bfr_arg__add(Bry_bfr bfr) { + Xopg_xtn_skin_mgr mgr = page.Html_data().Xtn_skin_mgr(); + int len = mgr.Count(); + for (int i = 0; i < len; ++i) { + Xopg_xtn_skin_itm itm = mgr.Get_at(i); + if (itm.Tid() == xtn_skin_tid) + itm.Write(bfr, page); + } + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_itm.java b/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_itm.java index a27517de8..7f25699af 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_itm.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_itm.java @@ -13,3 +13,9 @@ 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.wikis.pages.skins; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public interface Xopg_xtn_skin_itm { + byte Tid(); + byte[] Key(); + void Write(Bry_bfr bfr, Xoae_page page); +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_itm_stub.java b/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_itm_stub.java index a27517de8..7395f96e0 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_itm_stub.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_itm_stub.java @@ -13,3 +13,11 @@ 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.wikis.pages.skins; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public class Xopg_xtn_skin_itm_stub implements Xopg_xtn_skin_itm { + private final byte[] val; + public Xopg_xtn_skin_itm_stub(byte[] val) {this.val = val;} + public byte Tid() {return Xopg_xtn_skin_itm_tid.Tid_sidebar;} + public byte[] Key() {return Bry_.Empty;} + public void Write(Bry_bfr bfr, Xoae_page page) {bfr.Add(val);} +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_itm_tid.java b/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_itm_tid.java index a27517de8..9b9cbac7c 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_itm_tid.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_itm_tid.java @@ -13,3 +13,7 @@ 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.wikis.pages.skins; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public class Xopg_xtn_skin_itm_tid { + public static final byte Tid_sidebar = 0; +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_mgr.java b/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_mgr.java index a27517de8..26d5b2e2c 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/skins/Xopg_xtn_skin_mgr.java @@ -13,3 +13,12 @@ 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.wikis.pages.skins; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public class Xopg_xtn_skin_mgr { + private Ordered_hash hash = Ordered_hash_.New_bry(); + public int Count() {return hash.Count();} + public void Add(Xopg_xtn_skin_itm itm) {hash.Add_if_dupe_use_1st(itm.Key(), itm);} + public Xopg_xtn_skin_itm Get_at(int i) {return (Xopg_xtn_skin_itm)hash.Get_at(i);} + public Xopg_xtn_skin_itm Get_or_null(byte[] key) {return (Xopg_xtn_skin_itm)hash.Get_by(key);} + public void Clear() {hash.Clear();} +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_alertify_.java b/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_alertify_.java index a27517de8..fdb9e2e51 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_alertify_.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_alertify_.java @@ -13,3 +13,20 @@ 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.wikis.pages.tags; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +import gplx.xowa.guis.cbks.*; import gplx.core.gfobjs.*; +public class Xopg_alertify_ { + public static void Add_tags(Xopg_tag_mgr head_tags, Io_url http_root) { + Io_url alertify_root = http_root.GenSubDir_nest("bin", "any", "xowa", "html", "res", "lib", "alertify"); + head_tags.Add(Xopg_tag_itm.New_css_file(alertify_root.GenSubFil_nest("themes", "alertify.core.css"))); + head_tags.Add(Xopg_tag_itm.New_css_file(alertify_root.GenSubFil_nest("themes", "alertify.bootstrap.css"))); + head_tags.Add(Xopg_tag_itm.New_js_file(alertify_root.GenSubFil_nest("lib", "alertify-0.3.11.js"))); + head_tags.Add(Xopg_tag_itm.New_js_file(alertify_root.GenSubFil_nest("lib", "xo-alertify-0.0.1.js"))); + } + public static void Exec_log(Xog_json_wkr wkr, String msg) { + wkr.Send_json("xo.alertify.log_by_str", Gfobj_nde.New().Add_str("msg", msg)); + } + public static void Exec_log(Xog_json_wkr wkr, String msg, int wait) { + wkr.Send_json("xo.alertify.log_by_str", Gfobj_nde.New().Add_str("msg", msg).Add_str("wait", Int_.To_str(wait * 1000))); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_itm.java b/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_itm.java index a27517de8..a4e82cbde 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_itm.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_itm.java @@ -13,3 +13,74 @@ 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.wikis.pages.tags; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +import gplx.langs.htmls.*; +public class Xopg_tag_itm { + Xopg_tag_itm(byte tid, byte[] node, byte[] href, byte[] body, Keyval... atrs) { + this.tid = tid; + this.node = node; + this.href = href; + this.body = body; + this.atrs = atrs; + } + public final String Source = "xowa"; + public byte Tid() {return tid;} private final byte tid; + public byte[] Node() {return node;} private final byte[] node; + public byte[] Href() {return href;} private final byte[] href; + public byte[] Body() {return body;} private final byte[] body; + public Keyval[] Atrs() {return atrs;} private final Keyval[] atrs; + public void To_html(Bry_bfr bfr) { + bfr.Add_byte(Byte_ascii.Angle_bgn); + bfr.Add(node); + To_html_atr(bfr, "data-source", "xowa"); + int len = atrs.length; + for (int i = 0; i < len; ++i) { + Keyval atr = atrs[i]; + To_html_atr(bfr, atr.Key(), atr.Val_to_str_or_empty()); + } + bfr.Add_byte(Byte_ascii.Angle_end); + if (!Bry_.Eq(node, Gfh_tag_.Bry__link)) { + if (body != null) { + bfr.Add_byte_nl(); + bfr.Add(body); + bfr.Add_byte_nl(); + } + bfr.Add_byte(Byte_ascii.Angle_bgn).Add_byte(Byte_ascii.Slash); + bfr.Add(node); + bfr.Add_byte(Byte_ascii.Angle_end); + } + bfr.Add_byte_nl(); + } + private static void To_html_atr(Bry_bfr bfr, String key, String val) { + bfr.Add_byte_space(); + bfr.Add_str_a7(key); + bfr.Add_byte(Byte_ascii.Eq); + bfr.Add_byte(Byte_ascii.Quote); + bfr.Add_str_a7(val); + bfr.Add_byte(Byte_ascii.Quote); + } + + public static final byte Tid__css_file = 0, Tid__css_code = 1, Tid__js_file = 2, Tid__js_code = 3, Tid__htm_frag = 4; + private static final Keyval + Tag__type_css = Keyval_.new_("type", "text/css"), Tag__type_js = Keyval_.new_("type", "text/javascript"), Tag__type_html = Keyval_.new_("type", "text/html") + , Tag__rel_stylesheet = Keyval_.new_("rel", "stylesheet"); + public static Xopg_tag_itm New_css_file(Io_url href_url) { + byte[] href = href_url.To_http_file_bry(); + return new Xopg_tag_itm(Tid__css_file, Gfh_tag_.Bry__link , href, null, Tag__type_css, Tag__rel_stylesheet, Keyval_.new_("href", String_.new_u8(href))); + } + public static Xopg_tag_itm New_css_code(byte[] code) { + return new Xopg_tag_itm(Tid__css_code, Gfh_tag_.Bry__style , null, code, Tag__type_css); + } + public static Xopg_tag_itm New_js_file(Io_url src_url) { + byte[] src = src_url.To_http_file_bry(); + return new Xopg_tag_itm(Tid__js_file , Gfh_tag_.Bry__script, src , null, Tag__type_js, Keyval_.new_("src", String_.new_u8(src))); + } + public static Xopg_tag_itm New_js_code(String code) {return New_js_code(Bry_.new_u8(code));} + public static Xopg_tag_itm New_js_code(byte[] code) { + return new Xopg_tag_itm(Tid__js_code , Gfh_tag_.Bry__script, null, code, Tag__type_js); + } + public static Xopg_tag_itm New_htm_frag(Io_url url, String id) { + byte[] html = Io_mgr.Instance.LoadFilBry(url); + return new Xopg_tag_itm(Tid__htm_frag, Gfh_tag_.Bry__script, null, html, Tag__type_html, Keyval_.new_("id", id)); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_mgr.java b/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_mgr.java index a27517de8..fc48832d3 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_mgr.java @@ -13,3 +13,37 @@ 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.wikis.pages.tags; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public class Xopg_tag_mgr { + private final List_adp list = List_adp_.New(); + public Xopg_tag_mgr(boolean pos_is_head) {this.pos_is_head = pos_is_head;} + public boolean Pos_is_head() {return pos_is_head;} private final boolean pos_is_head; + public int Len() {return list.Len();} + public Xopg_tag_itm Get_at(int i) {return (Xopg_tag_itm)list.Get_at(i);} + public void Add(Xopg_tag_itm... ary) {for (Xopg_tag_itm itm : ary) list.Add(itm);} + public void Copy(Xopg_tag_mgr src) { + int len = src.Len(); + for (int i = 0; i < len; ++i) + this.Add(src.Get_at(i)); + } + public byte[] To_html(Bry_bfr bfr) { + int len = this.Len(); + for (int i = 0; i < len; ++i) { + Xopg_tag_itm tag = this.Get_at(i); + tag.To_html(bfr); + } + return bfr.To_bry_and_clear(); + } + public byte[] To_html__style(Bry_bfr bfr) { + int len = this.Len(); + for (int i = 0; i < len; ++i) { + Xopg_tag_itm tag = this.Get_at(i); + if ( Bry_.Eq(tag.Node(), gplx.langs.htmls.Gfh_tag_.Bry__style) + && tag.Body() != null + ) { + tag.To_html(bfr); + } + } + return bfr.To_bry_and_clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_wtr.java b/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_wtr.java index a27517de8..0db3b942a 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_wtr.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_wtr.java @@ -13,3 +13,98 @@ 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.wikis.pages.tags; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +import gplx.langs.htmls.*; +public class Xopg_tag_wtr { + public static boolean Loader_as_script_static = true; // TEST + public static byte[] Write(Bry_bfr bfr, boolean write_loader_tag, Xopg_tag_wtr_cbk cbk, Xopg_tag_mgr tag_mgr) { + boolean loader_as_script = Loader_as_script_static; // allow disabling for dekstop later + boolean embed_loader = !gplx.core.envs.Op_sys.Cur().Tid_is_drd(); // PERF: drd will embed in bundle.js + + // get files_count + int len = tag_mgr.Len(); + int files_total = 0; + for (int i = 0; i < len; ++i) { + Xopg_tag_itm itm = tag_mgr.Get_at(i); + if (loader_as_script) { + switch (itm.Tid()) { + case Xopg_tag_itm.Tid__js_file: + ++files_total; + break; + } + } + } + + // bgn loader_as_script + if (loader_as_script) { + if (write_loader_tag) bfr.Add(Gfh_tag_.Script_lhs_w_type).Add_byte_nl(); + if (embed_loader && tag_mgr.Pos_is_head()) + Wtr.Add_loader_func(bfr); + if (files_total > 0) Wtr.Add_files_bgn(bfr); + } + + // write tags + len = tag_mgr.Len(); + int files_idx = 0; + for (int i = 0; i < len; ++i) { + Xopg_tag_itm itm = tag_mgr.Get_at(i); + if (loader_as_script) { + switch (itm.Tid()) { + case Xopg_tag_itm.Tid__js_file: + Wtr.Add_files_itm(bfr, itm, files_idx++); + break; + } + continue; + } + else + cbk.Write_tag(bfr, itm); + } + + // end loader_as_script + if (loader_as_script) { + if (files_total > 0) Wtr.Add_files_end(bfr, files_total); + if (write_loader_tag) bfr.Add(Gfh_tag_.Script_rhs).Add_byte_nl(); + + for (int i = 0; i < len; ++i) { + Xopg_tag_itm itm = tag_mgr.Get_at(i); + switch (itm.Tid()) { + case Xopg_tag_itm.Tid__js_file: continue; + } + cbk.Write_tag(bfr, itm); + } + } + return bfr.To_bry_and_clear(); + } + static class Wtr { //#*nested + public static void Add_loader_func(Bry_bfr bfr) { + bfr.Add_str_a7_w_nl(" function load_files_sequentially(files, idx, done_cbk) {"); + bfr.Add_str_a7_w_nl(" if (files[idx]) { // idx is valid"); + bfr.Add_str_a7_w_nl(" var script = document.createElement('script');"); + bfr.Add_str_a7_w_nl(" script.setAttribute('type','text/javascript');"); + bfr.Add_str_a7_w_nl(" script.setAttribute('src', files[idx]);"); + bfr.Add_str_a7_w_nl(" script.onload = function(){"); + bfr.Add_str_a7_w_nl(" load_files_sequentially(files, ++idx, done_cbk); // load next file"); + bfr.Add_str_a7_w_nl(" };"); + bfr.Add_str_a7_w_nl(" document.getElementsByTagName('head')[0].appendChild(script)"); + bfr.Add_str_a7_w_nl(" }"); + bfr.Add_str_a7_w_nl(" else { // idx is not valid; finished;"); + bfr.Add_str_a7_w_nl(" done_cbk();"); + bfr.Add_str_a7_w_nl(" }"); + bfr.Add_str_a7_w_nl(" }"); + } + public static void Add_files_bgn(Bry_bfr bfr) { + bfr.Add_str_a7_w_nl(" var files ="); + } + public static void Add_files_itm(Bry_bfr bfr, Xopg_tag_itm itm, int idx) { + bfr.Add_str_a7(idx == 0 ? " [ '" : " , '"); + bfr.Add(itm.Href()); + bfr.Add_byte_apos().Add_byte_nl(); + } + public static void Add_files_end(Bry_bfr bfr, int files_count) { + bfr.Add_str_a7_w_nl(" ];"); + bfr.Add_str_a7_w_nl(" load_files_sequentially(files, 0, function() {"); + bfr.Add_str_a7_w_nl(" console.log('javascript files loaded: count=" + files_count + "');"); + bfr.Add_str_a7_w_nl(" });"); + } + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_wtr_.java b/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_wtr_.java index a27517de8..d70143271 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_wtr_.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_wtr_.java @@ -13,3 +13,68 @@ 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.wikis.pages.tags; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public class Xopg_tag_wtr_ { + public static void Add__baselib(Xopg_tag_mgr head_tags, Io_url http_root) { + Io_url css_dir = http_root.GenSubDir_nest("bin", "any", "xowa", "html", "res", "src", "xowa", "core"); + head_tags.Add(Xopg_tag_itm.New_js_file(css_dir.GenSubFil_nest("Namespace_.js"))); + head_tags.Add(Xopg_tag_itm.New_js_file(css_dir.GenSubFil_nest("String_.js"))); + } + public static void Add__tab_uid(Xopg_tag_mgr head_tags, Guid_adp page_guid) { + head_tags.Add(Xopg_tag_itm.New_js_code("xo_page_guid = '" + page_guid.To_str() + "'")); + } + public static void Add__xocss(Xopg_tag_mgr head_tags, Io_url http_root) { + Io_url css_dir = http_root.GenSubDir_nest("bin", "any", "xowa", "html", "res", "src", "xowa", "xocss", "core"); + head_tags.Add(Xopg_tag_itm.New_css_file(css_dir.GenSubFil_nest("xocss_core-0.0.1.css"))); + head_tags.Add(Xopg_tag_itm.New_css_file(css_dir.GenSubFil_nest("xoimg_core-0.0.1.css"))); + } + public static void Add__xohelp(Xopg_tag_mgr head_tags, Io_url http_root) { + Io_url css_dir = http_root.GenSubDir_nest("bin", "any", "xowa", "html", "res", "src", "xowa", "xocss", "help"); + head_tags.Add(Xopg_tag_itm.New_css_file(css_dir.GenSubFil_nest("xohelp-0.0.1.css"))); + head_tags.Add(Xopg_tag_itm.New_js_file(css_dir.GenSubFil_nest("xohelp-0.0.1.js"))); + } + public static void Add__xolog(Xopg_tag_mgr head_tags, Io_url http_root) { + Io_url dir = http_root.GenSubDir_nest("bin", "any", "xowa", "html", "res", "src", "xowa", "xolog"); + head_tags.Add(Xopg_tag_itm.New_css_file(dir.GenSubFil_nest("xo.log.css"))); + head_tags.Add(Xopg_tag_itm.New_js_file(dir.GenSubFil_nest("xo.log.js"))); + } + public static void Add__xotmpl(Xopg_tag_mgr head_tags, Io_url http_root) { + Io_url dir = http_root.GenSubDir_nest("bin", "any", "xowa", "html", "res", "src", "xowa", "xotmpl"); + head_tags.Add(Xopg_tag_itm.New_js_file(dir.GenSubFil_nest("xo.tmpl.js"))); + } + public static void Add__xoelem(Xopg_tag_mgr head_tags, Io_url http_root) { + Io_url dir = http_root.GenSubDir_nest("bin", "any", "xowa", "html", "res", "src", "xowa", "xoelem"); + head_tags.Add(Xopg_tag_itm.New_js_file(dir.GenSubFil_nest("xo.elem.js"))); + } + public static void Add__xonotify(Xopg_tag_mgr head_tags, Io_url http_root) { + Io_url dir = http_root.GenSubDir_nest("bin", "any", "xowa", "html", "res", "src", "xowa", "xonotify"); + head_tags.Add(Xopg_tag_itm.New_js_file(http_root.GenSubFil_nest("bin", "any", "xowa", "html", "res", "lib", "notifyjs", "notifyjs-0.3.1.js"))); + head_tags.Add(Xopg_tag_itm.New_js_file(dir.GenSubFil_nest("xo.notify.js"))); + } + public static void Add__xoajax(Xopg_tag_mgr head_tags, Io_url http_root, Xoa_app app) { + Io_url dir = http_root.GenSubDir_nest("bin", "any", "xowa", "html", "res", "src", "xowa", "xoajax"); + head_tags.Add(Xopg_tag_itm.New_js_file(dir.GenSubFil_nest("xo.app.js"))); + head_tags.Add(Xopg_tag_itm.New_js_file(dir.GenSubFil_nest(Get_app_js_file(app)))); + head_tags.Add(Xopg_tag_itm.New_js_file(dir.GenSubFil_nest("xo.server.js"))); + } + public static void Add__gui__progbars(Xopg_tag_mgr head_tags, Io_url http_root) { + Io_url dir = http_root.GenSubDir_nest("bin", "any", "xowa", "html", "res", "src", "xowa", "gui", "progbars"); + head_tags.Add(Xopg_tag_itm.New_css_file(dir.GenSubFil_nest("Progbar.css"))); + head_tags.Add(Xopg_tag_itm.New_js_file(dir.GenSubFil_nest("Progbar.js"))); + head_tags.Add(Xopg_tag_itm.New_js_file(dir.GenSubFil_nest("Progbar_util.js"))); + } + public static void Add__ooui(Xopg_tag_mgr head_tags, Io_url http_root) { + Io_url dir = http_root.GenSubDir_nest("bin", "any", "xowa", "html", "res", "lib", "oojs-ui"); + head_tags.Add(Xopg_tag_itm.New_css_file(dir.GenSubFil_nest("oojs-ui-core-mediawiki.css"))); + } + private static String Get_app_js_file(Xoa_app app) { + if (app.Mode().Tid_is_http()) return "xo.app.http_server.js"; + return gplx.core.envs.Op_sys.Cur().Tid_is_drd() ? "xo.app.drd.js" : "xo.app.swt.js"; + } + public static void Add__mustache(Xopg_tag_mgr head_tags, Io_url http_root) { + head_tags.Add(Xopg_tag_itm.New_js_file(http_root.GenSubFil_nest("bin", "any", "xowa", "html", "res", "lib", "mustache", "mustache-2.2.1.js"))); + } + public static void Add__jquery(Xopg_tag_mgr head_tags, Io_url http_root) { + head_tags.Add(Xopg_tag_itm.New_js_file(http_root.GenSubFil_nest("bin", "any", "xowa", "html", "res", "lib", "jquery", "jquery-1.11.3.js"))); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_wtr_cbk.java b/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_wtr_cbk.java index a27517de8..773f53f9d 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_wtr_cbk.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_wtr_cbk.java @@ -13,3 +13,10 @@ 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.wikis.pages.tags; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public interface Xopg_tag_wtr_cbk { + void Write_tag(Bry_bfr bfr, Xopg_tag_itm itm); +} +class Xopg_tag_wtr_cbk__basic implements Xopg_tag_wtr_cbk { + public void Write_tag(Bry_bfr bfr, Xopg_tag_itm itm) {itm.To_html(bfr);} +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_wtr_cbk_.java b/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_wtr_cbk_.java index a27517de8..73e49835b 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_wtr_cbk_.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/tags/Xopg_tag_wtr_cbk_.java @@ -13,3 +13,7 @@ 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.wikis.pages.tags; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public class Xopg_tag_wtr_cbk_ { + public static final Xopg_tag_wtr_cbk Basic = new Xopg_tag_wtr_cbk__basic(); +} \ No newline at end of file diff --git a/400_xowa/src/gplx/xowa/wikis/pages/wtxts/Xopg_toc_mgr.java b/400_xowa/src/gplx/xowa/wikis/pages/wtxts/Xopg_toc_mgr.java index a27517de8..6390a7190 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/wtxts/Xopg_toc_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/wtxts/Xopg_toc_mgr.java @@ -13,3 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.wikis.pages.wtxts; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.hdrs.*; +public class Xopg_toc_mgr { + private Xop_hdr_tkn[] hdrs_ary = Xop_hdr_tkn.Ary_empty; private int hdrs_max, hdrs_len; + public boolean Enabled() { + return !flag__notoc // __NOTOC__ not set + && hdrs_len != 0 // never show TOC if 0 headers, even when __FORCETOC__ + && ( hdrs_len > Hdrs_min // show TOC automatically if 4 or more headers + || flag__toc // or when __TOC__ specified (EX: 2 headers) + || flag__forcetoc // or when __FORCETOC__ to (a) show TOC when < 4 headers or (b) let TOC show at default position; __TOC__ would force TOC to show at __TOC__; __FORCETOC__ can be placed at bottom of page + ) + ; + } + public boolean Flag__toc() {return flag__toc;} + public void Flag__toc_(boolean v) {flag__toc = v;} private boolean flag__toc; // __TOC__ + public void Flag__forcetoc_(boolean v) {flag__forcetoc = v;} private boolean flag__forcetoc; // __FORCETOC__ + public void Flag__notoc_(boolean v) {flag__notoc = v;} private boolean flag__notoc; // __NOTOC__ + + public int Len() {return hdrs_len;} + public Xop_hdr_tkn Get_at(int i) {return hdrs_ary[i];} + public void Add(Xop_hdr_tkn hdr) { + // add tkn + if (hdrs_len == 0) hdr.First_in_doc_y_(); // if 1st hdr, mark it; easier for toc-insertion logic later + + // add to list; logic for bounds checking + int new_len = hdrs_len + 1; + if (new_len > hdrs_max) { + hdrs_max = (new_len * 2) + 1; + hdrs_ary = (Xop_hdr_tkn[])Array_.Resize(hdrs_ary, hdrs_max); + } + hdrs_ary[hdrs_len] = hdr; + hdrs_len = new_len; + } + public void Clear() { + flag__toc = flag__forcetoc = flag__notoc = false; + hdrs_len = 0; hdrs_max = 0; + hdrs_ary = Xop_hdr_tkn.Ary_empty; + } + public static final int Hdrs_min = 3; +} diff --git a/400_xowa/src/gplx/xowa/wikis/pages/wtxts/Xopg_wtxt_data.java b/400_xowa/src/gplx/xowa/wikis/pages/wtxts/Xopg_wtxt_data.java index a27517de8..d5b118f89 100644 --- a/400_xowa/src/gplx/xowa/wikis/pages/wtxts/Xopg_wtxt_data.java +++ b/400_xowa/src/gplx/xowa/wikis/pages/wtxts/Xopg_wtxt_data.java @@ -13,3 +13,18 @@ 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.wikis.pages.wtxts; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.pages.*; +public class Xopg_wtxt_data { + public Xopg_toc_mgr Toc() {return toc;} private final Xopg_toc_mgr toc = new Xopg_toc_mgr(); + public int Ctgs__len() {return ctg_hash == null ? 0 : ctg_hash.Len();} private Ordered_hash ctg_hash; + public Xoa_ttl Ctgs__get_at(int i) {return (Xoa_ttl)ctg_hash.Get_at(i);} + public Xoa_ttl[] Ctgs__to_ary() {return ctg_hash == null ? new Xoa_ttl[0] : (Xoa_ttl[])ctg_hash.To_ary(Xoa_ttl.class);} + public void Ctgs__add(Xoa_ttl ttl) { + if (ctg_hash == null) ctg_hash = Ordered_hash_.New_bry(); + ctg_hash.Add_if_dupe_use_1st(ttl.Full_db(), ttl); + } + public void Clear() { + if (ctg_hash != null) ctg_hash.Clear(); + toc.Clear(); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/ByteAry_fil.java b/400_xowa/src/gplx/xowa/wikis/tdbs/ByteAry_fil.java index a27517de8..c8a8a3a1e 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/ByteAry_fil.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/ByteAry_fil.java @@ -13,3 +13,25 @@ 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.wikis.tdbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.primitives.*; +public class ByteAry_fil { + public List_adp Itms() {return itms;} List_adp itms = List_adp_.New(); + public Io_url Fil() {return fil;} Io_url fil; + public byte[] Raw_bry() {return raw_bry;} private byte[] raw_bry = Bry_.Empty; + public int Raw_len() {return raw_len.Val();} Int_obj_ref raw_len = Int_obj_ref.New_zero(); + public int Raw_max() {return raw_max;} private int raw_max = Io_mgr.Len_mb; + public ByteAry_fil Ini_file(Io_url fil) { + this.fil = fil; + raw_bry = Io_mgr.Instance.LoadFilBry_reuse(fil, raw_bry, raw_len); + return this; + } + public Object Xto_itms(Class itm_type) { + Object rv = itms.To_ary(itm_type); + itms.Clear(); + if (raw_bry.length > raw_max) raw_bry = Bry_.Empty; + raw_len.Val_zero_(); + return rv; + } + public static final ByteAry_fil Instance = new ByteAry_fil(); ByteAry_fil() {} +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_dir_info.java b/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_dir_info.java index a27517de8..a77d845d3 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_dir_info.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_dir_info.java @@ -13,3 +13,40 @@ 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.wikis.tdbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.ios.*; +public class Xotdb_dir_info { + public Xotdb_dir_info(boolean ns_root, byte id, String name) {this.ns_root = ns_root; this.id = id; this.name = name;} + public byte Id() {return id;} private byte id; + public String Name() {return name;} private String name; + public boolean Ns_root() {return ns_root;} private boolean ns_root; + public String Ext() {return ext_str;} private String ext_str = Wtr_xdat_str; + public byte[] Ext_bry() {return ext_bry;} private byte[] ext_bry = Wtr_xdat_bry; + public byte Ext_tid() {return ext_tid;} + public Xotdb_dir_info Ext_tid_(byte v) { + ext_tid = v; + ext_bry = Wtr_ext(v); + ext_str = String_.new_a7(ext_bry); + return this; + } byte ext_tid = gplx.core.ios.streams.Io_stream_tid_.Tid__raw; + public static final String Wtr_xdat_str = ".xdat", Wtr_zip_str = ".zip", Wtr_gz_str = ".gz", Wtr_bz2_str = ".bz2"; + public static final byte[] Wtr_xdat_bry = Bry_.new_a7(Wtr_xdat_str), Wtr_zip_bry = Bry_.new_a7(Wtr_zip_str), Wtr_gz_bry = Bry_.new_a7(Wtr_gz_str), Wtr_bz2_bry = Bry_.new_a7(Wtr_bz2_str); + public static String Wtr_dir(byte v) { + switch (v) { + case gplx.core.ios.streams.Io_stream_tid_.Tid__raw : return ""; + case gplx.core.ios.streams.Io_stream_tid_.Tid__zip : return "_zip"; + case gplx.core.ios.streams.Io_stream_tid_.Tid__gzip : return "_gz"; + case gplx.core.ios.streams.Io_stream_tid_.Tid__bzip2 : return "_bz2"; + default : throw Err_.new_unhandled(v); + } + } + public static byte[] Wtr_ext(byte v) { + switch (v) { + case gplx.core.ios.streams.Io_stream_tid_.Tid__raw : return Wtr_xdat_bry; + case gplx.core.ios.streams.Io_stream_tid_.Tid__zip : return Wtr_zip_bry; + case gplx.core.ios.streams.Io_stream_tid_.Tid__gzip : return Wtr_gz_bry; + case gplx.core.ios.streams.Io_stream_tid_.Tid__bzip2 : return Wtr_bz2_bry; + default : throw Err_.new_unhandled(v); + } + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_dir_info_.java b/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_dir_info_.java index a27517de8..0ec4b16f9 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_dir_info_.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_dir_info_.java @@ -13,3 +13,48 @@ 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.wikis.tdbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +public class Xotdb_dir_info_ { + public static String Tid_name(byte tid) { + switch (tid) { + case Xotdb_dir_info_.Tid_page: return Xotdb_dir_info_.Name_page; + case Xotdb_dir_info_.Tid_ttl: return Xotdb_dir_info_.Name_title; + case Xotdb_dir_info_.Tid_id: return Xotdb_dir_info_.Name_id; + case Xotdb_dir_info_.Tid_category: return Xotdb_dir_info_.Name_category; + case Xotdb_dir_info_.Tid_category2_link: return Xotdb_dir_info_.Name_category2_link; + case Xotdb_dir_info_.Tid_category2_main: return Xotdb_dir_info_.Name_category2_main; + case Xotdb_dir_info_.Tid_search_ttl: return Xotdb_dir_info_.Name_search_ttl; + default: throw Err_.new_unhandled(tid); + } + } + public static Xotdb_dir_info[] regy_() { + Xotdb_dir_info[] rv = new Xotdb_dir_info[5]; + regy_itm_(rv, Bool_.Y, Tid_page); + regy_itm_(rv, Bool_.Y, Tid_ttl); + regy_itm_(rv, Bool_.N, Tid_id); + regy_itm_(rv, Bool_.N, Tid_category); + regy_itm_(rv, Bool_.N, Tid_search_ttl); + return rv; + } + public static boolean Dir_name_is_tdb(String dir_name) { + return String_.In(dir_name, Name_ns, Name_site, Name_cfg, "tmp"); + } + private static void regy_itm_(Xotdb_dir_info[] rv, boolean ns_root, byte id) {rv[id] = new Xotdb_dir_info(ns_root, id, Tid_name(id));} + public static final String Ext_xdat = ".xdat", Ext_csv = ".csv", Ext_zip = ".zip" + , Name_ns = "ns", Name_site = "site", Name_page = "page", Name_title = "title", Name_id = "id", Name_category = "category", Name_search_ttl = "search_title", Name_zip_suffix = "_zip" + , Name_cfg = "cfg" + , Name_reg_fil = "reg.csv", Name_category2 = "category2", Name_category2_link = "link", Name_category2_main = "main" + ; + public static final byte[] Bry_xdat = Bry_.new_a7(Ext_xdat), Bry_csv = Bry_.new_a7(Ext_csv), Bry_zip = Bry_.new_a7(Ext_zip); + public static final byte + Tid_page = 0 + , Tid_ttl = 1 + , Tid_id = 2 + , Tid_category = 3 + , Tid_search_ttl = 4 +// , Tid_category2 = 5 + , Tid_category2_link = 5 + , Tid_category2_main = 6 + ; + public static final byte Regy_tid_max = 7; +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_fsys_mgr.java b/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_fsys_mgr.java index a27517de8..22e1149b2 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_fsys_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_fsys_mgr.java @@ -13,3 +13,69 @@ 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.wikis.tdbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.xowa.bldrs.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.tdbs.utils.*; +public class Xotdb_fsys_mgr { + private final Io_url root_dir; private final Xow_ns_mgr ns_mgr; + public Xotdb_fsys_mgr(Io_url root_dir, Xow_ns_mgr ns_mgr) { + this.root_dir = root_dir; this.ns_mgr = ns_mgr; + this.tmp_dir = root_dir.GenSubDir("tmp"); + this.ns_dir = root_dir.GenSubDir(Xotdb_dir_info_.Name_ns); + this.site_dir = root_dir.GenSubDir(Xotdb_dir_info_.Name_site); + } + public Io_url Tmp_dir() {return tmp_dir;} private final Io_url tmp_dir; + public Io_url Ns_dir() {return ns_dir;} private final Io_url ns_dir; + public Io_url Site_dir() {return site_dir;} private final Io_url site_dir; + public Io_url Cfg_wiki_core_fil() {return root_dir.GenSubFil_nest(Const_url_cfg, "wiki_core.gfs");} + public Io_url Cfg_wiki_stats_fil() {return root_dir.GenSubFil_nest(Const_url_cfg, "wiki_stats.gfs");} + public Xotdb_dir_info[] Tdb_dir_regy() {return dir_regy;} private final Xotdb_dir_info[] dir_regy = Xotdb_dir_info_.regy_(); + public Io_url Url_ns_dir(String ns_num, byte tid) {return ns_dir.GenSubDir_nest(ns_num, Xotdb_dir_info_.Tid_name(tid));} + public Io_url Url_ns_reg(String ns_num, byte tid) {return ns_dir.GenSubFil_nest(ns_num, Xotdb_dir_info_.Tid_name(tid), Xotdb_dir_info_.Name_reg_fil);} + public Io_url Url_ns_fil(byte tid, int ns_id, int fil_idx) { + Xotdb_dir_info dir_info = dir_regy[tid]; + String dir_name = dir_info.Name() + Xotdb_dir_info.Wtr_dir(dir_info.Ext_tid()); + return Xotdb_fsys_mgr.Url_fil(ns_dir.GenSubDir_nest(Int_.To_str_pad_bgn_zero(ns_id, 3), dir_name), fil_idx, dir_regy[tid].Ext_bry()); + } + public Io_url Url_site_fil(byte tid, int fil_idx) {return Xotdb_fsys_mgr.Url_fil(Url_site_dir(tid), fil_idx, Xotdb_dir_info_.Bry_xdat);} + public Io_url Url_site_reg(byte tid) {return Url_site_dir(tid).GenSubFil(Xotdb_dir_info_.Name_reg_fil);} + public Io_url Url_site_dir(byte tid) { + switch (tid) { + case Xotdb_dir_info_.Tid_category2_link: return site_dir.GenSubDir_nest(Xotdb_dir_info_.Name_category2, Xotdb_dir_info_.Name_category2_link); + case Xotdb_dir_info_.Tid_category2_main: return site_dir.GenSubDir_nest(Xotdb_dir_info_.Name_category2, Xotdb_dir_info_.Name_category2_main); + default: return site_dir.GenSubDir_nest(Xotdb_dir_info_.Tid_name(tid)); + } + } + public void Scan_dirs() { + Scan_dirs_zip(this, Xotdb_dir_info_.Tid_page); + Scan_dirs_ns(ns_dir, ns_mgr); + } + private static void Scan_dirs_zip(Xotdb_fsys_mgr fsys_mgr, byte id) { + Io_url[] dirs = Io_mgr.Instance.QueryDir_args(fsys_mgr.Ns_dir().GenSubDir_nest("000")).FilPath_("*page*").DirOnly_().Recur_(false).ExecAsUrlAry(); + int len = dirs.length; + byte tid = gplx.core.ios.streams.Io_stream_tid_.Tid__raw; // needed for Xoa_xowa_exec_tst + for (int i = 0; i < len; i++) { + Io_url dir = dirs[i]; + String dir_name = dir.NameOnly(); + if (String_.Eq(dir_name, "page")) {tid = gplx.core.ios.streams.Io_stream_tid_.Tid__raw; break;} + else if (String_.Eq(dir_name, "page_zip")) tid = gplx.core.ios.streams.Io_stream_tid_.Tid__zip; + else if (String_.Eq(dir_name, "page_gz")) tid = gplx.core.ios.streams.Io_stream_tid_.Tid__gzip; + else if (String_.Eq(dir_name, "page_bz2")) tid = gplx.core.ios.streams.Io_stream_tid_.Tid__bzip2; + } + fsys_mgr.Tdb_dir_regy()[id].Ext_tid_(tid); + } + private static Hash_adp Scan_dirs_ns(Io_url ns_dir, Xow_ns_mgr ns_mgr) { + Io_url[] ns_dirs = Io_mgr.Instance.QueryDir_args(ns_dir).Recur_(false).DirOnly_().ExecAsUrlAry(); + int len = ns_dirs.length; + Hash_adp rv = Hash_adp_.New(); + for (int i = 0; i < len; i++) { + int ns_int = Int_.Parse_or(ns_dirs[i].NameOnly(), Int_.Min_value); if (ns_int == Int_.Min_value) continue; + Xow_ns ns = ns_mgr.Ids_get_or_null(ns_int); if (ns == null) continue; + ns.Exists_(true); + } + return rv; + } + public static Io_url Url_fil(Io_url root_dir, int fil_idx, byte[] ext) {return Xos_url_gen.bld_fil_(root_dir, fil_idx, ext);} private static final String Const_url_cfg = "cfg"; + +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_fsys_mgr_tst.java b/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_fsys_mgr_tst.java index a27517de8..ff8d36c78 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_fsys_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_fsys_mgr_tst.java @@ -13,3 +13,24 @@ 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.wikis.tdbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import org.junit.*; import gplx.xowa.wikis.tdbs.*; +import gplx.xowa.wikis.nss.*; +public class Xotdb_fsys_mgr_tst { + @Before public void init() {fxt.Clear();} private final Xow_fsys_mgr_fxt fxt = new Xow_fsys_mgr_fxt(); + @Test public void Basic() { + fxt.Zip_(Xotdb_dir_info_.Tid_page, Bool_.N).Url_ns_fil(Xotdb_dir_info_.Tid_page, Xow_ns_.Tid__main, 123, "mem/xowa/wiki/en.wikipedia.org/ns/000/page/00/00/00/01/0000000123.xdat"); + fxt.Zip_(Xotdb_dir_info_.Tid_page, Bool_.Y).Url_ns_fil(Xotdb_dir_info_.Tid_page, Xow_ns_.Tid__main, 123, "mem/xowa/wiki/en.wikipedia.org/ns/000/page_zip/00/00/00/01/0000000123.zip"); + } +} +class Xow_fsys_mgr_fxt { + public void Clear() { + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + } + Xoae_app app; Xowe_wiki wiki; + public Xow_fsys_mgr_fxt Zip_(byte tid, boolean v) {wiki.Tdb_fsys_mgr().Tdb_dir_regy()[tid].Ext_tid_(v ? gplx.core.ios.streams.Io_stream_tid_.Tid__zip : gplx.core.ios.streams.Io_stream_tid_.Tid__raw); return this;} + public void Url_ns_fil(byte tid, int ns_id, int fil_idx, String expd) { + Tfds.Eq(expd, wiki.Tdb_fsys_mgr().Url_ns_fil(tid, ns_id, fil_idx).Raw()); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_page_itm_.java b/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_page_itm_.java index a27517de8..7d6b7259d 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_page_itm_.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_page_itm_.java @@ -13,3 +13,74 @@ 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.wikis.tdbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.brys.*; import gplx.core.encoders.*; import gplx.xowa.wikis.data.tbls.*; +public class Xotdb_page_itm_ { + static final int Txt_len_id = 5, Txt_len_fil_idx = 5, Txt_len_row_idx = 5, Txt_len_type = 1, Txt_len_text_len = 5; + public static final int Txt_ttl_pos = Txt_len_id + Txt_len_fil_idx + Txt_len_row_idx + Txt_len_type + Txt_len_text_len + 5; + public static final int Txt_ttl_len__fixed = Txt_len_id + Txt_len_fil_idx + Txt_len_row_idx + Txt_len_type + Txt_len_text_len + 5 + 1; // 5=| 1=\n + public static Xowd_page_itm Txt_ttl_load(byte[] bry) { + Xowd_page_itm rv = new Xowd_page_itm(); + Txt_ttl_load(rv, bry, 0, bry.length); + return rv; + } + public static void Txt_ttl_load(Xowd_page_itm page, byte[] bry) {Txt_ttl_load(page, bry, 0, bry.length);} + private static void Txt_ttl_load(Xowd_page_itm page, byte[] bry, int bgn, int end) { + try { + page.Init_by_tdb + ( Base85_.To_int_by_bry (bry, bgn + 0, bgn + 4) + , Base85_.To_int_by_bry (bry, bgn + 6, bgn + 10) + , Base85_.To_int_by_bry (bry, bgn + 12, bgn + 16) + , bry[18] == Byte_ascii.Num_1 + , Base85_.To_int_by_bry (bry, bgn + 20, bgn + 24) + , page.Ns_id() + , Bry_.Mid (bry, bgn + 26, end) + ); + } catch (Exception e) {throw Err_.new_exc(e, "xo", "parse_by_ttl failed", "ttl", String_.new_u8(bry, bgn, end));} + } + public static void Txt_ttl_save(Bry_bfr bfr, Xowd_page_itm page) {Txt_ttl_save(bfr, page.Id(), page.Text_db_id(), page.Tdb_row_idx(), page.Redirected(), page.Text_len(), page.Ttl_page_db());} + public static void Txt_ttl_save(Bry_bfr bfr, int id, int file_idx, int row_idx, boolean redirect, int text_len, byte[] ttl_wo_ns) { + bfr .Add_base85_len_5(id) .Add_byte_pipe() + .Add_base85_len_5(file_idx) .Add_byte_pipe() + .Add_base85_len_5(row_idx) .Add_byte_pipe() + .Add_byte(redirect ? Byte_ascii.Num_1 : Byte_ascii.Num_0).Add_byte_pipe() + .Add_base85_len_5(text_len) .Add_byte_pipe() + .Add(ttl_wo_ns) .Add_byte_nl() + ; + } + public static void Txt_id_load(Xowd_page_itm page, byte[] bry) {Txt_id_load(page, bry, 0, bry.length);} + private static void Txt_id_load(Xowd_page_itm page, byte[] bry, int bgn, int end) { + try { + page.Clear(); + page.Init_by_tdb + ( Base85_.To_int_by_bry (bry, bgn + 0, bgn + 4) + , Base85_.To_int_by_bry (bry, bgn + 6, bgn + 10) + , Base85_.To_int_by_bry (bry, bgn + 12, bgn + 16) + , bry[18] == Byte_ascii.Num_1 + , Base85_.To_int_by_bry (bry, bgn + 20, bgn + 24) + , Base85_.To_int_by_bry (bry, bgn + 26, bgn + 30) + , Bry_.Mid (bry, bgn + 32, end) + ); + } catch (Exception e) {throw Err_.new_exc(e, "xo", "parse_by_id failed", "id", String_.new_u8(bry, bgn, end));} + } + public static void Txt_id_save(Bry_bfr bfr, Xowd_page_itm page) { + bfr .Add_base85_len_5(page.Id()) .Add_byte_pipe() + .Add_base85_len_5(page.Text_db_id()) .Add_byte_pipe() + .Add_base85_len_5(page.Tdb_row_idx()) .Add_byte_pipe() + .Add_byte(page.Redirected() ? Byte_ascii.Num_1 : Byte_ascii.Num_0).Add_byte_pipe() + .Add_base85_len_5(page.Text_len()) .Add_byte_pipe() + .Add_base85_len_5(page.Ns_id()) .Add_byte_pipe() + .Add(page.Ttl_page_db()) .Add_byte_nl(); + } + public static void Txt_page_save(Bry_bfr bfr, int id, DateAdp modified_on, byte[] title, byte[] text, boolean add_nl) { + int ts = Int_flag_bldr_.To_int_date_short(modified_on.XtoSegAry()); + bfr .Add_base85(id , Base85_.Len_int) .Add_byte(Txt_page_dlm) // needed for mass template load + .Add_base85(ts , Base85_.Len_int) .Add_byte(Txt_page_dlm) + .Add(title) .Add_byte(Txt_page_dlm) // needed for rebuilding ttl files + .Add(text) .Add_byte(Txt_page_dlm); + if (add_nl) + bfr.Add_byte_nl(); // NOTE: each page row is separated by \t\n + } + public static final byte Txt_page_dlm = Byte_ascii.Tab; + public static final int Txt_page_len__fixed = 1 + 5 + 1 + 5 + 1 + 1 + 1; // \tid|date|title|text\n +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_page_raw_parser.java b/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_page_raw_parser.java index a27517de8..f294c047f 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_page_raw_parser.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/Xotdb_page_raw_parser.java @@ -13,3 +13,46 @@ 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.wikis.tdbs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import gplx.core.brys.*; import gplx.core.ios.*; import gplx.core.encoders.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.tdbs.bldrs.*; +public class Xotdb_page_raw_parser { + public void Init(Gfo_usr_dlg usr_dlg, Xowe_wiki wiki, int load_len) { + this.wiki = wiki; ns_mgr = wiki.Ns_mgr(); + rdr = new Io_line_rdr(usr_dlg, new Io_url[1]); + rdr.Line_dlm_(Byte_ascii.Tab).Load_len_(load_len).Key_gen_(Io_line_rdr_key_gen_all.Instance); + } + public void Init_ns(Xow_ns ns_itm) {this.ns_itm = ns_itm;} + public void Reset_one(Io_url url) { + rdr.Reset_one(url); + } + public void Load(Gfo_usr_dlg usr_dlg, Xowe_wiki wiki, Xow_ns ns_itm, Io_url[] urls, int load_len) { + this.wiki = wiki; ns_mgr = wiki.Ns_mgr(); this.ns_itm = ns_itm; + rdr = new Io_line_rdr(usr_dlg, urls); + rdr.Line_dlm_(Byte_ascii.Tab).Load_len_(load_len).Key_gen_(Io_line_rdr_key_gen_all.Instance); + } Io_line_rdr rdr; Xowe_wiki wiki; Xow_ns_mgr ns_mgr; Xow_ns ns_itm; + public void Skip_first_line() { + rdr.Read_next(); + int pos = Bry_find_.Find_fwd(rdr.Bfr(), Byte_ascii.Nl); +// rdr.Move(pos + 1); + rdr.Truncate(pos + 1); + } + public boolean Read(Xowd_page_itm page) { + boolean read = false; + read = rdr.Read_next(); if (!read) return false; + int id = Base85_.To_int_by_bry(rdr.Bfr(), rdr.Key_pos_bgn(), rdr.Key_pos_end() - 2); + page.Id_(id); + read = rdr.Read_next(); if (!read) throw Err_.new_wo_type("could not read timestamp"); + int timestamp = Base85_.To_int_by_bry(rdr.Bfr(), rdr.Key_pos_bgn(), rdr.Key_pos_end() - 1); + page.Modified_on_(Int_flag_bldr_.To_date_short(timestamp)); + read = rdr.Read_next(); if (!read) throw Err_.new_wo_type("could not read ttl"); + byte[] ttl = Bry_.Mid(rdr.Bfr(), rdr.Key_pos_bgn(), rdr.Key_pos_end() - 1); + page.Ttl_(ttl, ns_mgr); + read = rdr.Read_next(); if (!read) throw Err_.new_wo_type("could not read text"); + byte[] text = Bry_.Mid(rdr.Bfr(), rdr.Key_pos_bgn(), rdr.Key_pos_end() - 1); + page.Text_(text); + rdr.Bfr_last_read_add(1); + return true; + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/bldrs/Io_line_rdr_key_gen_all.java b/400_xowa/src/gplx/xowa/wikis/tdbs/bldrs/Io_line_rdr_key_gen_all.java index a27517de8..0faa856af 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/bldrs/Io_line_rdr_key_gen_all.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/bldrs/Io_line_rdr_key_gen_all.java @@ -13,3 +13,11 @@ 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.wikis.tdbs.bldrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.ios.*; +public class Io_line_rdr_key_gen_all implements Io_line_rdr_key_gen { + public void Gen(Io_line_rdr bfr) { + bfr.Key_pos_bgn_(bfr.Itm_pos_bgn()).Key_pos_end_(bfr.Itm_pos_end()); + } + public static final Io_line_rdr_key_gen_all Instance = new Io_line_rdr_key_gen_all(); Io_line_rdr_key_gen_all() {} +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Bry_comparer_bgn_eos.java b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Bry_comparer_bgn_eos.java index a27517de8..7e9c79d19 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Bry_comparer_bgn_eos.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Bry_comparer_bgn_eos.java @@ -13,3 +13,11 @@ 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.wikis.tdbs.hives; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +public class Bry_comparer_bgn_eos implements gplx.core.lists.ComparerAble { + public Bry_comparer_bgn_eos(int bgn) {this.bgn = bgn;} private int bgn; + public int compare(Object lhsObj, Object rhsObj) { + byte[] lhs = (byte[])lhsObj, rhs = (byte[])rhsObj; + return Bry_.Compare(lhs, bgn, lhs.length, rhs, bgn, rhs.length); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xoa_hive_mgr.java b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xoa_hive_mgr.java index a27517de8..87d0ac424 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xoa_hive_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xoa_hive_mgr.java @@ -13,3 +13,26 @@ 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.wikis.tdbs.hives; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.primitives.*; +import gplx.xowa.wikis.tdbs.xdats.*; +public class Xoa_hive_mgr { + public Xoa_hive_mgr(Xoae_app app) {this.app = app;} private Xoae_app app; + public Xob_xdat_itm Itm() {return xdat_itm;} + public int Find_fil(Io_url hive_root, byte[] ttl) { + Io_url hive_url = hive_root.GenSubFil(Xotdb_dir_info_.Name_reg_fil); + if (!hive_url.Eq(regy_mgr.Fil())) + regy_mgr.Init(hive_url); + return regy_mgr.Files_find(ttl); + } private Xowd_regy_mgr regy_mgr = new Xowd_regy_mgr(); Int_obj_ref bry_len = Int_obj_ref.New_zero(); Xob_xdat_file xdat_rdr = new Xob_xdat_file(); Xob_xdat_itm xdat_itm = new Xob_xdat_itm(); + public Xowd_regy_mgr Regy_mgr() {return regy_mgr;} + public Xob_xdat_file Get_rdr(Io_url hive_root, byte[] fil_ext_bry, int fil_idx) { + Bry_bfr tmp_bfr = app.Utl__bfr_mkr().Get_m001(); + byte[] tmp_bry = tmp_bfr.Bfr(); bry_len.Val_zero_(); + Io_url file = Xotdb_fsys_mgr.Url_fil(hive_root, fil_idx, fil_ext_bry); + tmp_bry = Io_mgr.Instance.LoadFilBry_reuse(file, tmp_bry, bry_len); + xdat_rdr.Clear().Parse(tmp_bry, bry_len.Val(), file); + tmp_bfr.Clear_and_rls(); + return xdat_rdr; + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xob_hive_mgr.java b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xob_hive_mgr.java index a27517de8..47a73d0da 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xob_hive_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xob_hive_mgr.java @@ -13,3 +13,184 @@ 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.wikis.tdbs.hives; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.ios.zips.*; +import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.bldrs.sql_dumps.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.specials.*; import gplx.xowa.specials.allPages.*; +import gplx.xowa.wikis.tdbs.xdats.*; +public class Xob_hive_mgr { + public Xob_hive_mgr(Xowe_wiki wiki) {this.wiki = wiki; this.fsys_mgr = wiki.Tdb_fsys_mgr();} private Xowe_wiki wiki; Xotdb_fsys_mgr fsys_mgr; + public void Clear() {regy.Clear(); xdat.Clear();} + public void Special_allpages_query(Xows_page_allpages mgr, Xow_ns ns, byte[] key, int count, boolean include_redirects) { + byte dir_tid = Xotdb_dir_info_.Tid_ttl; + int xdat_idx = Regy__find_file_ns(key, dir_tid, ns.Num_str()); + Xob_xdat_file xdat_main = new Xob_xdat_file(); + xdat_main = xdat_load_(xdat_main, dir_tid, ns, xdat_idx); + xdat_main.Find(xdat_itm, key, Xotdb_page_itm_.Txt_ttl_pos, Byte_ascii.Tab, false); + int itm_idx = xdat_itm.Itm_idx(); + Special_allpages_query_fwd(mgr, dir_tid, ns, include_redirects, count, xdat_idx, itm_idx , xdat_main); + Special_allpages_query_bwd(mgr, dir_tid, ns, include_redirects, count, xdat_idx, itm_idx - 1, xdat_main); + } + Xob_xdat_file xdat_load_(Xob_xdat_file xdat, byte dir_tid, Xow_ns ns, int fil_idx) { + Io_url xdat_url = fsys_mgr.Url_ns_fil(dir_tid, ns.Id(), fil_idx); + byte[] xdat_bry = Io_mgr.Instance.LoadFilBry(xdat_url); + xdat.Parse(xdat_bry, xdat_bry.length, xdat_url); + return xdat; + } + private void Special_allpages_query_fwd(Xows_page_allpages mgr, byte dir_tid, Xow_ns ns, boolean include_redirects, int total, int fil_idx, int row_idx, Xob_xdat_file xdat_file) { + int count = 0; ++total; + boolean loop = true; + int regy_len = regy.Files_ary().length; + int rslt_list_len = mgr.Rslt_list_len(); + Xowd_page_itm[] rslt_list_ttls = mgr.Rslt_list_ttls(); + Xowd_page_itm nxt_itm = null; + while (loop) { + if (fil_idx == regy_len) break; + if (xdat_file == null) { + xdat_file = xdat_load_(this.xdat, dir_tid, ns, fil_idx); + row_idx = 0; + } + int rows_len = xdat_file.Count(); + for (; row_idx < rows_len; row_idx++) { + xdat_file.GetAt(xdat_itm, row_idx); + Xowd_page_itm ttl_itm = Xotdb_page_itm_.Txt_ttl_load(Bry_.Mid(xdat_itm.Src(), xdat_itm.Itm_bgn(), xdat_itm.Itm_end())); + if (!include_redirects && ttl_itm.Redirected()) continue; + ++count; + nxt_itm = ttl_itm; + if (count == total) { + loop = false; + break; + } + else + rslt_list_ttls[rslt_list_len++] = ttl_itm; + } + xdat_file = null; + ++fil_idx; + } + mgr.Rslt_list_len_(rslt_list_len); + mgr.Rslt_nxt_(nxt_itm); + } + private void Special_allpages_query_bwd(Xows_page_allpages mgr, byte dir_tid, Xow_ns ns, boolean include_redirects, int total, int fil_idx, int row_idx, Xob_xdat_file xdat_file) { + if (row_idx < 0) { + --fil_idx; + row_idx = -1; + } + int count = 0; + boolean loop = true; + Xowd_page_itm prv_itm = null; + while (loop) { + if (fil_idx == -1) break; + if (xdat_file == null) { + xdat_file = xdat_load_(this.xdat, dir_tid, ns, fil_idx); + row_idx = -1; + } + if (row_idx == -1) + row_idx = xdat_file.Count() - 1; + for (; row_idx > -1; row_idx--) { + xdat_file.GetAt(xdat_itm, row_idx); + Xowd_page_itm ttl_itm = Xotdb_page_itm_.Txt_ttl_load(Bry_.Mid(xdat_itm.Src(), xdat_itm.Itm_bgn(), xdat_itm.Itm_end())); + if (!include_redirects && ttl_itm.Redirected()) continue; +// list.Add(ttl_itm); + ++count; + prv_itm = ttl_itm; + if (count == total) { + loop = false; + break; + } + else { +// rslt_list_ttls[rslt_list_len++] = ttl_itm; + } + } + xdat_file = null; + --fil_idx; + } + if (prv_itm == null) prv_itm = mgr.Rslt_list_ttls()[0]; + mgr.Rslt_prv_(prv_itm); + } + public void Find_bgn(List_adp list, Xow_ns ns, byte[] key, int count, boolean include_redirects) { + byte dir_tid = Xotdb_dir_info_.Tid_ttl; + int xdat_idx = Regy__find_file_ns(key, dir_tid, ns.Num_str()); + Io_url xdat_url = fsys_mgr.Url_ns_fil(dir_tid, ns.Id(), xdat_idx); + byte[] xdat_bry = Io_mgr.Instance.LoadFilBry(xdat_url); + xdat.Parse(xdat_bry, xdat_bry.length, xdat_url); + xdat.Find(xdat_itm, key, Xotdb_page_itm_.Txt_ttl_pos, Byte_ascii.Tab, false); + Find_nearby_add_fwd(list, dir_tid, ns, include_redirects, count, xdat_idx, xdat_itm.Itm_idx()); + } private Xob_xdat_itm xdat_itm = new Xob_xdat_itm(); //Int_2_ref find_nearby_rslt = new Int_2_ref(); +// private void Find_nearby_add_bwd(List_adp list, byte dir_tid, Xow_ns ns, boolean include_redirects, int total, int fil_bgn, int row_bgn) { +// if (--row_bgn < 0) { +// --fil_bgn; +// row_bgn = -1; +// } +// int fil_idx = fil_bgn; +// boolean first = true; +// int count = 0; +// boolean loop = true; +// while (loop) { +// if (fil_idx == -1) break; +// Io_url xdat_url = fsys_mgr.Url_ns_fil(dir_tid, ns.Id(), fil_idx); +// byte[] xdat_bry = Io_mgr.Instance.LoadFilBry(xdat_url); +// xdat.Parse(xdat_bry, xdat_bry.length, xdat_url); +// int row_idx = first && row_bgn != -1 ? row_bgn : xdat.Count() - 1; +// first = false; +// for (; row_idx > -1; row_idx--) { +// xdat.GetAt(xdat_itm, row_idx); +// Xowd_page_itm ttl_itm = Xotdb_page_itm_.Txt_ttl_load(Bry_.Mid(xdat_itm.Src(), xdat_itm.Itm_bgn(), xdat_itm.Itm_end())); +// if (!include_redirects && ttl_itm.Type_redirect()) continue; +// list.Add(ttl_itm); +// if (++count == total) {loop = false; break;} +// } +// --fil_idx; +// } +// } + private void Find_nearby_add_fwd(List_adp list, byte dir_tid, Xow_ns ns, boolean include_redirects, int total, int fil_bgn, int row_bgn) { + int fil_idx = fil_bgn; + boolean first = true; + int count = 0; + boolean loop = true; + int regy_len = regy.Files_ary().length; + while (loop) { + if (fil_idx == regy_len) break; + Io_url xdat_url = fsys_mgr.Url_ns_fil(dir_tid, ns.Id(), fil_idx); + byte[] xdat_bry = Io_mgr.Instance.LoadFilBry(xdat_url); + xdat.Parse(xdat_bry, xdat_bry.length, xdat_url); + int row_idx = first ? row_bgn : 0; + int rows_len = xdat.Count(); + first = false; + for (; row_idx < rows_len; row_idx++) { + xdat.GetAt(xdat_itm, row_idx); + Xowd_page_itm ttl_itm = Xotdb_page_itm_.Txt_ttl_load(Bry_.Mid(xdat_itm.Src(), xdat_itm.Itm_bgn(), xdat_itm.Itm_end())); + if (!include_redirects && ttl_itm.Redirected()) continue; + list.Add(ttl_itm); + if (++count == total) {loop = false; break;} + } + ++fil_idx; + } + } + public void Create(byte dir_tid, byte[] key, byte[] row) { // Ctg_0; Ctg_0|!!!!"|!!!!# + int xdat_idx = Regy__find_file(key, dir_tid); + if (xdat_idx == Xowd_regy_mgr.Regy_null) { // no entries in regy; create at least one; EX: "" -> "0|A|A|1" + regy.Create(key); + xdat_idx = 0; + } + else + regy.Update_add(0, key); + regy.Save(); + Xdat__create_row(dir_tid, key, row, xdat_idx); + } + int Regy__find_file(byte[] key, byte dir_tid) {return Regy__find_file_by_url(key, fsys_mgr.Url_site_reg(dir_tid));} + int Regy__find_file_ns(byte[] key, byte dir_tid, String ns_num) {return Regy__find_file_by_url(key, fsys_mgr.Url_ns_reg(ns_num, Xotdb_dir_info_.Tid_ttl));} + int Regy__find_file_by_url(byte[] key, Io_url regy_url) {regy.Init(regy_url); return regy.Files_find(key);} private Xowd_regy_mgr regy = new Xowd_regy_mgr(); + private void Xdat__create_row(byte dir_tid, byte[] key, byte[] row, int xdat_idx) { + Io_url xdat_url = fsys_mgr.Url_site_fil(dir_tid, xdat_idx); + byte[] xdat_bry = Io_mgr.Instance.LoadFilBry(xdat_url); + Xob_xdat_file xdat_fil = new Xob_xdat_file(); + if (xdat_bry.length > 0) // if file is not empty, load it and parse it + xdat_fil.Parse(xdat_bry, xdat_bry.length, xdat_url); + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_m001(); + xdat_fil.Insert(tmp_bfr, row); + xdat_fil.Save(xdat_url); + tmp_bfr.Mkr_rls(); + } private Xob_xdat_file xdat = new Xob_xdat_file(); Io_zip_mgr zip_mgr = Io_zip_mgr_base.Instance; +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xob_hive_mgr_tst.java b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xob_hive_mgr_tst.java index a27517de8..e5ae8a86b 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xob_hive_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xob_hive_mgr_tst.java @@ -13,3 +13,15 @@ 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.wikis.tdbs.hives; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import org.junit.*; +public class Xob_hive_mgr_tst { + Xow_hive_mgr_fxt fxt = new Xow_hive_mgr_fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Drilldown() { +// fxt.Files_create_range(10, 10); +// fxt.Drilldown("A00", "J09", "A00", "B09", "C00", "D09", "E00", "F09", "G00", "H09", "I00", "J09"); +// fxt.Drilldown("E00", "F09", "E00", "E03", "E04", "E07", "E08", "F01", "F02", "F05", "F06", "F09"); +// fxt.Drilldown("E08", "F01", "E08", "E09", "F00", "F01"); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xow_hive_mgr_fxt.java b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xow_hive_mgr_fxt.java index a27517de8..3b60e5bd9 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xow_hive_mgr_fxt.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xow_hive_mgr_fxt.java @@ -13,3 +13,99 @@ 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.wikis.tdbs.hives; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.encoders.*; +import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.data.tbls.*; import gplx.xowa.wikis.tdbs.xdats.*; +public class Xow_hive_mgr_fxt { + public void Clear() { + if (hive_mgr == null) { + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + hive_mgr = new Xob_hive_mgr(wiki); + } + hive_mgr.Clear(); + Io_mgr.Instance.InitEngine_mem(); + } private Xob_hive_mgr hive_mgr; Xoae_app app; + public Xowe_wiki Wiki() {return wiki;} private Xowe_wiki wiki; + public void Find_nearby(String key, int count, boolean include_redirects, String... expd) { + List_adp list = List_adp_.New(); + wiki.Hive_mgr().Find_bgn(list, wiki.Ns_mgr().Ns_main(), Bry_.new_a7(key), count, include_redirects); + int actl_len = list.Count(); + String[] actl = new String[actl_len]; + for (int i = 0; i < actl_len; i++) { + Xowd_page_itm itm = (Xowd_page_itm)list.Get_at(i); + actl[i] = String_.new_a7(itm.Ttl_page_db()); + } + Tfds.Eq_ary_str(expd, actl); + } + public static void Ttls_create_rng(Xowe_wiki wiki, int files, int ttls_per_file) {Ttls_create_rng(wiki, wiki.Ns_mgr().Ns_main(), files, ttls_per_file);} + public static void Ttls_create_rng(Xowe_wiki wiki, Xow_ns ns, int files, int ttls_per_file) { + Xob_reg_wtr reg_wtr = new Xob_reg_wtr(); + byte dir_tid = Xotdb_dir_info_.Tid_ttl; + int id = 0; + int ttl_bry_len = Int_.DigitCount(ttls_per_file); + Xob_xdat_file_wtr xdat_wtr = Xob_xdat_file_wtr.new_file_(ttls_per_file * 8, wiki.Tdb_fsys_mgr().Url_ns_dir(ns.Num_str(), Xotdb_dir_info_.Tid_ttl)); + Bry_bfr tmp_bfr = Bry_bfr_.New(); + byte ltr = Byte_ascii.Ltr_A; byte[] ttl_0 = Bry_.Empty, ttl_n = Bry_.Empty; + for (int fil_idx = 0; fil_idx < files; fil_idx++) { + for (int ttl_idx = 0; ttl_idx < ttls_per_file; ttl_idx++) { + tmp_bfr.Add_byte(ltr).Add_int_fixed(ttl_idx, ttl_bry_len); + byte[] ttl_bry = tmp_bfr.To_bry_and_clear(); + if (ttl_idx == 0) ttl_0 = ttl_bry; + else if (ttl_idx == ttls_per_file - 1) ttl_n = ttl_bry; + Xotdb_page_itm_.Txt_ttl_save(xdat_wtr.Bfr(), id++, 0, ttl_idx, ttl_idx % 2 == 1, 1, ttl_bry); + xdat_wtr.Add_idx(Byte_ascii.Null); + } + xdat_wtr.Flush(wiki.Appe().Usr_dlg()); + reg_wtr.Add(ttl_0, ttl_n, ttls_per_file); + ++ltr; + } + reg_wtr.Flush(wiki.Tdb_fsys_mgr().Url_ns_reg(ns.Num_str(), dir_tid)); + } + public Xow_hive_mgr_fxt Create_ctg(String key_str, int... pages) {Create_ctg(app, hive_mgr, key_str, pages); return this;} + public static void Create_ctg(Xoae_app app, Xob_hive_mgr hive_mgr, String key_str, int... pages) { + byte[] key_bry = Bry_.new_a7(key_str); + Bry_bfr bfr = app.Utl__bfr_mkr().Get_b512(); + bfr.Add(key_bry); + int pages_len = pages.length; + for (int i = 0; i < pages_len; i++) + bfr.Add_byte_pipe().Add_base85_len_5(pages[i]); + bfr.Add_byte_nl(); + byte[] row = bfr.To_bry_and_rls(); + hive_mgr.Create(Xotdb_dir_info_.Tid_category, key_bry, row); + } + public Xow_hive_mgr_fxt Create_id(int id, int fil_idx, int row_idx, boolean type_redirect, int itm_len, int ns_id, String ttl) {Create_id(app, hive_mgr, id, fil_idx, row_idx, type_redirect, itm_len, ns_id, ttl); return this;} + public static void Create_id(Xoae_app app, Xob_hive_mgr hive_mgr, int id, int fil_idx, int row_idx, boolean type_redirect, int itm_len, int ns_id, String ttl) { + Bry_bfr bfr = app.Utl__bfr_mkr().Get_b512(); + byte[] key_bry = Base85_.To_bry(id, 5); + bfr .Add(key_bry) .Add_byte_pipe() + .Add_base85_len_5(fil_idx) .Add_byte_pipe() + .Add_base85_len_5(row_idx) .Add_byte_pipe() + .Add_byte(type_redirect ? Byte_ascii.Num_1 : Byte_ascii.Num_0).Add_byte_pipe() + .Add_base85_len_5(itm_len) .Add_byte_pipe() + .Add_base85_len_5(ns_id) .Add_byte_pipe() + .Add_str_u8(ttl) .Add_byte_nl(); + byte[] row = bfr.To_bry_and_clear(); + bfr.Mkr_rls(); + hive_mgr.Create(Xotdb_dir_info_.Tid_id, key_bry, row); + } + public Xow_hive_mgr_fxt Load(String url, String... expd) { + String actl = Io_mgr.Instance.LoadFilStr(url); + Tfds.Eq_ary_str(expd, String_.SplitLines_nl(actl)); + return this; + } +} +class Xob_reg_wtr { + Bry_bfr bfr = Bry_bfr_.New(); int fil_count = 0; + public void Add(byte[] bgn, byte[] end, int itm_count) { + bfr + .Add_int_variable(fil_count++).Add_byte(Byte_ascii.Pipe) + .Add(bgn).Add_byte(Byte_ascii.Pipe) + .Add(end).Add_byte(Byte_ascii.Pipe) + .Add_int_variable(itm_count).Add_byte(Byte_ascii.Nl); + } + public void Flush(Io_url url) { + Io_mgr.Instance.SaveFilBfr(url, bfr); +// Tfds.Dbg(url.Raw() + "\n" + Io_mgr.Instance.LoadFilStr(url)); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_hive_mgr.java b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_hive_mgr.java index a27517de8..d67e38a01 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_hive_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_hive_mgr.java @@ -13,3 +13,82 @@ 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.wikis.tdbs.hives; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.tdbs.xdats.*; +public class Xowd_hive_mgr { + public Xowd_hive_mgr(Xowe_wiki wiki, byte dir_tid) { + this.wiki = wiki; fsys_mgr = wiki.Tdb_fsys_mgr(); this.dir_tid = dir_tid; + dir_tid_reg = dir_tid == Xotdb_dir_info_.Tid_page ? Xotdb_dir_info_.Tid_ttl : dir_tid; + } Xowe_wiki wiki; Xotdb_fsys_mgr fsys_mgr; Xowd_regy_mgr reg_mgr; byte dir_tid; + byte dir_tid_reg; + public void Create(Xow_ns ns, byte[] key, byte[] data, gplx.core.lists.ComparerAble comparer) { + if (reg_mgr == null) reg_mgr = new Xowd_regy_mgr(fsys_mgr.Url_ns_reg(ns.Num_str(), dir_tid_reg)); + int fil_idx = 0; + if (reg_mgr.Files_ary().length == 0) { + reg_mgr.Create(key); + fil_idx = 0; + } + else { + fil_idx = reg_mgr.Files_find(key); + reg_mgr.Update_add(fil_idx, key); + } + Io_url url = fsys_mgr.Url_ns_fil(dir_tid, ns.Id(), fil_idx); + byte[] bry = Io_mgr.Instance.LoadFilBry(url); + Xob_xdat_file xdat = new Xob_xdat_file(); + if (bry != Bry_.Empty) + xdat.Parse(bry, bry.length, url); + Bry_bfr tmp = wiki.Utl__bfr_mkr().Get_m001(); + xdat.Insert(tmp, data); + if (comparer != null) + xdat.Sort(tmp, comparer); + tmp.Mkr_rls(); + xdat.Save(url); + reg_mgr.Save(); + } + public void Create(byte[] key, byte[] data, gplx.core.lists.ComparerAble comparer) { + if (reg_mgr == null) reg_mgr = new Xowd_regy_mgr(fsys_mgr.Url_site_reg(dir_tid)); + int fil_idx = 0; + if (reg_mgr.Files_ary().length == 0) { + reg_mgr.Create(key); + fil_idx = 0; + } + else { + fil_idx = reg_mgr.Files_find(key); + reg_mgr.Update_add(fil_idx, key); + } + Io_url url = fsys_mgr.Url_site_fil(dir_tid, fil_idx); + byte[] bry = Io_mgr.Instance.LoadFilBry(url); + Xob_xdat_file xdat = new Xob_xdat_file(); + if (bry != Bry_.Empty) + xdat.Parse(bry, bry.length, url); + Bry_bfr tmp = wiki.Utl__bfr_mkr().Get_m001(); + xdat.Insert(tmp, data); + if (comparer != null) + xdat.Sort(tmp, comparer); + tmp.Mkr_rls(); + xdat.Save(url); + reg_mgr.Save(); + } + public void Update(Xow_ns ns, byte[] old_key, byte[] new_key, byte[] data, int lkp_bgn, byte lkp_dlm, boolean exact, boolean sort) { + if (reg_mgr == null) reg_mgr = new Xowd_regy_mgr(fsys_mgr.Url_ns_reg(ns.Num_str(), Xotdb_dir_info_.Tid_ttl)); + int fil_idx = reg_mgr.Files_find(old_key); + boolean reg_save = false; + if (new_key != null) + reg_save = reg_mgr.Update_change(fil_idx, old_key, new_key); + Io_url url = fsys_mgr.Url_ns_fil(dir_tid, ns.Id(), fil_idx); + byte[] bry = Io_mgr.Instance.LoadFilBry(url); + Xob_xdat_file xdat = new Xob_xdat_file(); + if (bry != Bry_.Empty) + xdat.Parse(bry, bry.length, url); + Bry_bfr tmp = wiki.Utl__bfr_mkr().Get_m001(); + Xob_xdat_itm itm = new Xob_xdat_itm(); + xdat.Find(itm, old_key, lkp_bgn, lkp_dlm, exact); + if (itm.Missing()) return; + xdat.Update(tmp, itm, data); + if (sort) xdat.Sort(tmp, new Bry_comparer_bgn_eos(lkp_bgn)); + tmp.Mkr_rls(); + xdat.Save(url); + if (reg_save) reg_mgr.Save(); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_hive_mgr_tst.java b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_hive_mgr_tst.java index a27517de8..d3acdf7e5 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_hive_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_hive_mgr_tst.java @@ -13,3 +13,85 @@ 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.wikis.tdbs.hives; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import org.junit.*; +import gplx.xowa.wikis.nss.*; +public class Xowd_hive_mgr_tst { + Xowd_hive_mgr_fxt fxt = new Xowd_hive_mgr_fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Create() { + fxt.Create("A", "A|A data\n") + .Tst_reg(String_.Concat_lines_nl("0|A|A|1")) + .Tst_fil(0, String_.Concat("!!!!*|\n", "A|A data\n")) + ; + } + @Test public void Create_2() { + fxt .Create("A", "A|1\n") + .Create("B", "B|12\n") + .Tst_reg(String_.Concat_lines_nl("0|A|B|2")) + .Tst_fil(0, String_.Concat("!!!!%|!!!!&|\n", "A|1\n", "B|12\n")) + ; + } + @Test public void Create_3() { + fxt .Create("A", "A|1\n") + .Create("B", "B|12\n") + .Create("C", "C|123\n") + .Tst_reg(String_.Concat_lines_nl("0|A|C|3")) + .Tst_fil(0, String_.Concat("!!!!%|!!!!&|!!!!'|\n", "A|1\n", "B|12\n", "C|123\n")) + ; + } + @Test public void Create_sort() { + fxt .Create_and_sort("C", "C|1\n") + .Create_and_sort("A", "A|12\n") + .Create_and_sort("B", "B|123\n") + .Tst_reg(String_.Concat_lines_nl("0|A|C|3")) + .Tst_fil(0, String_.Concat("!!!!&|!!!!'|!!!!%|\n", "A|12\n", "B|123\n", "C|1\n")) + ; + } + @Test public void Update() { + fxt .Create("A", "A|A data\n") + .Create("B", "B|B data\n") + .Create("C", "C|C data\n") + .Tst_reg(String_.Concat_lines_nl("0|A|C|3")) + .Tst_fil(0, String_.Concat("!!!!*|!!!!*|!!!!*|\n", "A|A data\n", "B|B data\n", "C|C data\n")) + .Update("B", "B|changed\n") + .Tst_reg(String_.Concat_lines_nl("0|A|C|3")) + .Tst_fil(0, String_.Concat("!!!!*|!!!!+|!!!!*|\n", "A|A data\n", "B|changed\n", "C|C data\n")) + ; + } +} +class Xowd_hive_mgr_fxt { + Xoae_app app; Xowe_wiki wiki; Xowd_hive_mgr mgr; + public void Clear() { + app = Xoa_app_fxt.Make__app__edit(); + wiki = Xoa_app_fxt.Make__wiki__edit(app); + mgr = new Xowd_hive_mgr(wiki, Xotdb_dir_info_.Tid_page); + } + public Xowd_hive_mgr_fxt Tst_reg(String expd) { + Io_url file_orig = Io_url_.mem_fil_("mem/xowa/wiki/en.wikipedia.org/ns/000/title/reg.csv"); + Tfds.Eq_str_lines(expd, Io_mgr.Instance.LoadFilStr(file_orig)); + return this; + } + public Xowd_hive_mgr_fxt Tst_fil(int fil, String expd) { + Io_url url = wiki.Tdb_fsys_mgr().Url_ns_fil(Xotdb_dir_info_.Tid_page, Xow_ns_.Tid__main, fil); + Tfds.Eq_str_lines(expd, Io_mgr.Instance.LoadFilStr(url)); + return this; + } + public Xowd_hive_mgr_fxt Update(String key, String data) { + mgr.Update(wiki.Ns_mgr().Ns_main(), Bry_.new_a7(key), null, Bry_.new_a7(data), 0, Byte_ascii.Pipe, true, true); + return this; + } + public Xowd_hive_mgr_fxt Create(String key, String data) { + mgr.Create(wiki.Ns_mgr().Ns_main(), Bry_.new_a7(key), Bry_.new_a7(data), null); + return this; + } + public Xowd_hive_mgr_fxt Create_and_sort(String key, String data) { + mgr.Create(wiki.Ns_mgr().Ns_main(), Bry_.new_a7(key), Bry_.new_a7(data), new Bry_comparer_bgn_eos(0)); + return this; + } +// public void Get(String ttl_str, boolean exists) { +// Xoa_ttl ttl = Xoa_ttl.Parse(wiki, Bry_.new_u8(ttl_str)); +// Xowd_page_itm row = mgr.Get(ttl.Ns(), ttl.Full_txt()); +// Tfds.Eq(exists, row != null); +// } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_hive_regy_itm.java b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_hive_regy_itm.java index a27517de8..9e5be2413 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_hive_regy_itm.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_hive_regy_itm.java @@ -13,3 +13,39 @@ 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.wikis.tdbs.hives; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.primitives.*; +public class Xowd_hive_regy_itm {// csv file with the format of "idx|bgn|end|count"; EX: "0|AA|AZ|120\n1|BA|BZ|110" + public Xowd_hive_regy_itm(int idx) {this.idx = idx;} + public int Idx() {return idx;} private int idx; + public byte[] Bgn() {return bgn;} public Xowd_hive_regy_itm Bgn_(byte[] v) {bgn = v; return this;} private byte[] bgn; + public byte[] End() {return end;} public Xowd_hive_regy_itm End_(byte[] v) {end = v; return this;} private byte[] end; + public int Count() {return count;} public Xowd_hive_regy_itm Count_(int v) {this.count = v; return this;} private int count; + public static Xowd_hive_regy_itm[] parse_fil_(ByteAry_fil utl) { + List_adp rv = utl.Itms(); + byte[] ary = utl.Raw_bry(); + int ary_len = utl.Raw_len(); if (ary_len == 0) return Xowd_hive_regy_itm.Ary_empty; //throw Err_mgr.Instance.fmt_("xowa.wiki.data", "title_registry_file_not_found", "title_registry file not found: ~{0}", utl.Fil().Xto_api()); + Int_obj_ref pos = Int_obj_ref.New_zero(); + while (pos.Val() < ary_len) { + Xowd_hive_regy_itm file = new Xowd_hive_regy_itm(); + file.idx = Bry_.ReadCsvInt(ary, pos, Bry_.Dlm_fld); + file.bgn = Bry_.ReadCsvBry(ary, pos, Bry_.Dlm_fld); // skip bgn + file.end = Bry_.ReadCsvBry(ary, pos, Bry_.Dlm_fld); + file.count = Bry_.ReadCsvInt(ary, pos, Bry_.Dlm_row); + rv.Add(file); + } + return (Xowd_hive_regy_itm[])utl.Xto_itms(Xowd_hive_regy_itm.class); + } + public Xowd_hive_regy_itm() {} + public Xowd_hive_regy_itm(int id, byte[] bgn, byte[] end, int count) { + this.idx = id; this.bgn = bgn; this.end = end; this.count = count; + } + public void Srl_save(Bry_bfr bfr) { + bfr .Add_int_variable(idx).Add_byte_pipe() + .Add(bgn).Add_byte_pipe() + .Add(end).Add_byte_pipe() + .Add_int_variable(count).Add_byte_nl(); + } + public static Xowd_hive_regy_itm tmp_() {return new Xowd_hive_regy_itm();} + public static final Xowd_hive_regy_itm[] Ary_empty = new Xowd_hive_regy_itm[0]; +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_regy_mgr.java b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_regy_mgr.java index a27517de8..caffdaed2 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_regy_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_regy_mgr.java @@ -13,3 +13,62 @@ 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.wikis.tdbs.hives; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.lists.*; +public class Xowd_regy_mgr { + public static final int Not_found = -1; + public Xowd_regy_mgr() {} + public Xowd_regy_mgr(Io_url fil) {this.Init(fil);} + public Io_url Fil() {return fil;} Io_url fil; + public void Init(Io_url fil) {this.fil = fil; files_ary = Xowd_hive_regy_itm.parse_fil_(ByteAry_fil.Instance.Ini_file(fil));} + public Xowd_hive_regy_itm[] Files_ary() {return files_ary;} private Xowd_hive_regy_itm[] files_ary; + public void Clear() {files_ary = Xowd_hive_regy_itm.Ary_empty;} + public int Files_find(byte[] key) { + if (files_ary.length == 0) return Xowd_regy_mgr.Regy_null; // NOTE: FindSlot does not accept empty ary; returning 0, b/c Find returns likely file_idx; EX: regy of 0|B|D and 1|F|H; A returns 0; Z returns 1 + return Xowd_regy_mgr_.FindSlot(comparer, files_ary, comparer_itm.End_(key)); + } ComparerAble comparer = Xowd_ttl_file_comparer_end.Instance; Xowd_hive_regy_itm comparer_itm = Xowd_hive_regy_itm.tmp_().Count_(1); + public Xowd_hive_regy_itm Create(byte[] key) { + int itm_idx = files_ary.length; + files_ary = (Xowd_hive_regy_itm[])Array_.Resize(files_ary, itm_idx + 1); + Xowd_hive_regy_itm rv = new Xowd_hive_regy_itm(itm_idx).Bgn_(key).End_(key).Count_(1); + files_ary[itm_idx] = rv; + return rv; + } + public Xowd_hive_regy_itm Update_add(int fil_idx, byte[] key) { + Xowd_hive_regy_itm rv = files_ary[fil_idx]; + rv.Count_(rv.Count() + 1); + if (Bry_.Compare(key, rv.Bgn()) < CompareAble_.Same) + rv.Bgn_(key); + else if (Bry_.Compare(key, rv.End()) > CompareAble_.Same) + rv.End_(key); + return rv; + } + public boolean Update_change(int fil_idx, byte[] old_key, byte[] new_key) { + Xowd_hive_regy_itm rv = files_ary[fil_idx]; + boolean changed = false; + if (Bry_.Eq(old_key, rv.Bgn())) { + rv.Bgn_(new_key); + changed = true; + } + else if (Bry_.Eq(old_key, rv.End())) { + rv.End_(new_key); + changed = true; + } + return changed; + } + public Xowd_hive_regy_itm Update_del(int fil_idx, byte[] key) { + Xowd_hive_regy_itm itm = files_ary[fil_idx]; + itm.Count_(itm.Count() - 1); + throw Err_.new_unimplemented(); // FUTURE: note that deletes are harder; rng ends could be deleted, so would need to open file and get new rng end + } + public void Save() { + Bry_bfr bfr = Bry_bfr_.New(); + int len = files_ary.length; + for (int i = 0; i < len; i++) { + Xowd_hive_regy_itm itm = files_ary[i]; + itm.Srl_save(bfr); + } + Io_mgr.Instance.SaveFilBfr(fil, bfr); + } + public static final int Regy_null = -1; +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_regy_mgr_.java b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_regy_mgr_.java index a27517de8..ff8a90892 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_regy_mgr_.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_regy_mgr_.java @@ -13,3 +13,30 @@ 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.wikis.tdbs.hives; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.lists.*; +public class Xowd_regy_mgr_ { + public static int FindSlot(ComparerAble comparer, Object[] ary, Object itm) {if (itm == null) throw Err_.new_null(); + int aryLen = ary.length; + switch (aryLen) { + case 0: throw Err_.new_wo_type("ary cannot have 0 itms"); + case 1: return 0; + } + int lo = -1, hi = aryLen - 1; // NOTE: -1 is necessary; see test + int curPos = (hi - lo) / 2; + int delta = 1; + while (true) { + Object curSeg = ary[curPos]; + int comp = curSeg == null ? CompareAble_.More : comparer.compare(itm, curSeg); // nulls should only happen for lastAry +// if (dbg) { +// Tfds.Write(curPos, itm.toString(), comp, comp.toString(), curSeg.toString()); +// } + if (comp == CompareAble_.Same) return curPos; + else if (comp > CompareAble_.Same) {lo = curPos; delta = 1;} + else if (comp < CompareAble_.Same) {hi = curPos; delta = -1;} + int dif = hi - lo; + if (dif == 1 || dif == 0) return hi; // NOTE: can be 0 when ary.length == 1 || 2; also, sometimes 0 in some situations + else curPos += (dif / 2) * delta; + } + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_regy_mgr__tst.java b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_regy_mgr__tst.java index a27517de8..3361947ae 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_regy_mgr__tst.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_regy_mgr__tst.java @@ -13,3 +13,23 @@ 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.wikis.tdbs.hives; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import org.junit.*; import gplx.core.lists.*; +public class Xowd_regy_mgr__tst implements ComparerAble { + @Test public void Basic() { + String[] slotAry = new String[] {"b", "e", "h"}; // 0=b 1=e 2=h + tst_FindSlot(slotAry, "f", "h"); // f -> 1 2 -> 2 + tst_FindSlot(slotAry, "c", "e"); // c -> -1 1 -> 0 -> 0 1 -> 1 + tst_FindSlot(slotAry, "a", "b"); // a -> -1 1 -> 0 -> -1 0 -> 0 + } + @Test public void Null() { + String[] slotAry = new String[] {"b", "g", "l", "q", "v", null}; + tst_FindSlot(slotAry, "a", "b"); + tst_FindSlot(slotAry, "b", "b"); + tst_FindSlot(slotAry, "c", "g"); + tst_FindSlot(slotAry, "v", "v"); + tst_FindSlot(slotAry, "w", null); + } + public int compare(Object lhsObj, Object rhsObj) {return CompareAble_.Compare_obj(lhsObj, rhsObj);} + void tst_FindSlot(String[] slotAry, String s, String expd) {Tfds.Eq(expd, slotAry[gplx.xowa.wikis.tdbs.hives.Xowd_regy_mgr_.FindSlot(this, slotAry, s)]);} +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_regy_mgr_tst.java b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_regy_mgr_tst.java index a27517de8..90f549473 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_regy_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_regy_mgr_tst.java @@ -13,3 +13,74 @@ 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.wikis.tdbs.hives; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import org.junit.*; +public class Xowd_regy_mgr_tst { + Xowd_regy_mgr_fxt fxt = new Xowd_regy_mgr_fxt(); + @Before public void init() {fxt.Clear();} + @Test public void Create_cur_is_empty() {fxt.Create("A").Save().Tst_file(String_.Concat_lines_nl("0|A|A|1"));} + @Test public void Create_cur_has_one() {fxt.Load(String_.Concat_lines_nl("0|A|A|1")).Create("B").Save().Tst_file(String_.Concat_lines_nl("0|A|A|1", "1|B|B|1"));} + @Test public void Update_1st_end() {fxt.Load(String_.Concat_lines_nl("0|B|B|1")).Update_add(0, "C").Save().Tst_file(String_.Concat_lines_nl("0|B|C|2"));} + @Test public void Update_1st_bgn() {fxt.Load(String_.Concat_lines_nl("0|B|B|1")).Update_add(0, "A").Save().Tst_file(String_.Concat_lines_nl("0|A|B|2"));} + @Test public void Update_mid() {fxt.Load(String_.Concat_lines_nl("0|B|D|2")).Update_add(0, "C").Save().Tst_file(String_.Concat_lines_nl("0|B|D|3"));} + @Test public void Update_bgn() {fxt.Load(String_.Concat_lines_nl("0|B|D|2")).Update_add(0, "A").Save().Tst_file(String_.Concat_lines_nl("0|A|D|3"));} + @Test public void Update_end() {fxt.Load(String_.Concat_lines_nl("0|B|D|2")).Update_add(0, "E").Save().Tst_file(String_.Concat_lines_nl("0|B|E|3"));} + @Test public void Update_change_bgn() {fxt.Load(String_.Concat_lines_nl("0|B|D|2")).Update_change(0, "B", "A").Save().Tst_file(String_.Concat_lines_nl("0|A|D|2"));} + @Test public void Update_change_end() {fxt.Load(String_.Concat_lines_nl("0|B|D|2")).Update_change(0, "D", "E").Save().Tst_file(String_.Concat_lines_nl("0|B|E|2"));} + @Test public void Update_change_mid() {fxt.Load(String_.Concat_lines_nl("0|B|D|2")).Update_change(0, "C1", "C2").Save().Tst_file(String_.Concat_lines_nl("0|B|D|2"));} + @Test public void Find_none() {fxt.Tst_find("A", Xowd_regy_mgr.Regy_null);} + @Test public void Find_existing() { + fxt.Load(String_.Concat_lines_nl + ( "0|B|D|3" + , "1|E|G|3" + , "2|H|J|3" + )) + .Tst_find("B", 0).Tst_find("C", 0).Tst_find("D", 0) + .Tst_find("A", 0) + .Tst_find("Z", 2) + .Tst_find("Da", 1) + ; + } + @Test public void Find_existing_null() { + fxt.Load(String_.Concat_lines_nl + ( "0|B|D|3" + , "1|D|H|0" + , "2|H|J|3" + )) + .Tst_find("B", 0).Tst_find("C", 0).Tst_find("D", 0) + .Tst_find("A", 0) + .Tst_find("Z", 2) + .Tst_find("Da", 1) // rely on + ; + } +} +class Xowd_regy_mgr_fxt { + Xowd_regy_mgr mgr; Io_url mgr_url; + public void Clear() { + if (mgr == null) { + mgr_url = Io_url_.mem_fil_("mem/hive_regy.csv"); + Io_mgr.Instance.DeleteFil(mgr_url); + mgr = new Xowd_regy_mgr(mgr_url); + } + else { + mgr.Clear(); + } + } + public Xowd_regy_mgr_fxt Create(String key) {mgr.Create(Bry_.new_a7(key)); return this;} + public Xowd_regy_mgr_fxt Update_add(int fil_idx, String key) {mgr.Update_add(fil_idx, Bry_.new_a7(key)); return this;} + public Xowd_regy_mgr_fxt Update_change(int fil_idx, String old_key, String new_key) {mgr.Update_change(fil_idx, Bry_.new_a7(old_key), Bry_.new_a7(new_key)); return this;} + public Xowd_regy_mgr_fxt Load(String lines) { + Io_mgr.Instance.SaveFilStr(mgr_url, lines); + mgr = new Xowd_regy_mgr(mgr_url); + return this; + } + public Xowd_regy_mgr_fxt Save() {mgr.Save(); return this;} + public Xowd_regy_mgr_fxt Tst_file(String expd) { + Tfds.Eq_str_lines(expd, Io_mgr.Instance.LoadFilStr(mgr_url)); + return this; + } + public Xowd_regy_mgr_fxt Tst_find(String find, int expd) { + Tfds.Eq(expd, mgr.Files_find(Bry_.new_a7(find))); + return this; + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_ttl_file_comparer_end.java b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_ttl_file_comparer_end.java index a27517de8..593770f33 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_ttl_file_comparer_end.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/hives/Xowd_ttl_file_comparer_end.java @@ -13,3 +13,13 @@ 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.wikis.tdbs.hives; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +public class Xowd_ttl_file_comparer_end implements gplx.core.lists.ComparerAble { + public int compare(Object lhsObj, Object rhsObj) { + Xowd_hive_regy_itm lhs = (Xowd_hive_regy_itm)lhsObj, rhs = (Xowd_hive_regy_itm)rhsObj; + if (lhs.Count() == 0) return Bry_.Compare(rhs.End(), lhs.Bgn()); + //else if (rhs.Count() == 0) return Bry_.Compare(lhs.End(), rhs.End()); // NOTE: this line mirrors the top, but is actually covered by below + else return Bry_.Compare(lhs.End(), rhs.End()); + } + public static final Xowd_ttl_file_comparer_end Instance = new Xowd_ttl_file_comparer_end(); Xowd_ttl_file_comparer_end() {} +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_fil.java b/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_fil.java index a27517de8..5648bab9d 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_fil.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_fil.java @@ -13,3 +13,47 @@ 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.wikis.tdbs.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.flds.*; +public class Xof_meta_fil { + private final Ordered_hash itms = Ordered_hash_.New_bry(); + public Xof_meta_fil(Xof_meta_mgr meta_mgr, byte[] md5) {this.meta_mgr = meta_mgr; this.md5 = md5;} + public Xof_meta_mgr Owner_mgr() {return meta_mgr;} private final Xof_meta_mgr meta_mgr; + public byte[] Md5() {return md5;} private final byte[] md5; + public void Dirty_() {meta_mgr.Dirty_(this);} + public Xof_meta_itm Get_or_new(byte[] ttl) { + Xof_meta_itm rv = Get_or_null(ttl); + if (rv == null) { + rv = new Xof_meta_itm(this, ttl); + itms.Add(ttl, rv); + } + return rv; + } + public Xof_meta_itm Get_or_null(byte[] ttl) {return (Xof_meta_itm)itms.Get_by(ttl);} + public void Save(Gfo_fld_wtr wtr) { + int itms_len = itms.Count(); + for (int i = 0; i < itms_len; i++) { + Xof_meta_itm itm = (Xof_meta_itm)itms.Get_at(i); + itm.Save(wtr); + } + } + public Xof_meta_fil Load(Gfo_fld_rdr rdr, Xof_meta_thumb_parser parser) { + itms.Clear(); + int bry_len = rdr.Data().length; + while (rdr.Pos() < bry_len) { + Xof_meta_itm itm = new Xof_meta_itm(this, Bry_.Empty); + itm.Load(rdr, parser); + itms.Add(itm.Ttl(), itm); + } + return this; + } + public static Io_url Bld_url(Io_url root, byte[] md5, int depth) { + Bld_url_bfr.Add(root.RawBry()); + for (int i = 0; i < depth - 1; i++) + Bld_url_bfr.Add_byte(md5[i]).Add_byte(root.Info().DirSpr_byte()); + for (int i = 0; i < depth; i++) + Bld_url_bfr.Add_byte(md5[i]); + Bld_url_bfr.Add(Bry_url_ext); + return Io_url_.new_fil_(Bld_url_bfr.To_str_and_clear()); + } private static final byte[] Bry_url_ext = Bry_.new_a7(".csv"); static Bry_bfr Bld_url_bfr = Bry_bfr_.New_w_size(260); // 260 is max path of url +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_fil_tst.java b/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_fil_tst.java index a27517de8..4f008c73c 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_fil_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_fil_tst.java @@ -13,3 +13,15 @@ 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.wikis.tdbs.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import org.junit.*; import gplx.xowa.files.*; +public class Xof_meta_fil_tst { + Xof_meta_fil_fxt fxt = new Xof_meta_fil_fxt(); + @Before public void init() {fxt.Ini();} + @Test public void Bld_url() {fxt.Bld_url("mem/root/", "abcdef", 3, "mem/root/a/b/abc.csv");} +} +class Xof_meta_fil_fxt { + byte[] md5_(byte[] name) {return Xof_file_wkr_.Md5(name);} + public void Ini() {} + public void Bld_url(String root, String md5, int depth, String expd) {Tfds.Eq(expd, Xof_meta_fil.Bld_url(Io_url_.new_dir_(root), Bry_.new_a7(md5), depth).Raw());} +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_itm.java b/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_itm.java index a27517de8..ac0d57df9 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_itm.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_itm.java @@ -13,3 +13,170 @@ 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.wikis.tdbs.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.flds.*; +import gplx.xowa.files.*; import gplx.xowa.files.repos.*; +import gplx.xowa.parsers.utils.*; import gplx.xowa.parsers.lnkis.*; +public class Xof_meta_itm { + public Xof_meta_itm(Xof_meta_fil owner_fil, byte[] ttl) {this.owner_fil = owner_fil; this.ttl = ttl;} + public Xof_meta_fil Owner_fil() {return owner_fil;} private Xof_meta_fil owner_fil; + public byte[] Ttl() {return ttl;} private byte[] ttl; + public byte[] Ptr_ttl() {return ptr_ttl;} public Xof_meta_itm Ptr_ttl_(byte[] v) {this.ptr_ttl = v; return this;} private byte[] ptr_ttl = Xop_redirect_mgr.Redirect_null_bry; + public boolean Ptr_ttl_exists() {return ptr_ttl != Xop_redirect_mgr.Redirect_null_bry;} + public boolean State_new() {return state_new;} private boolean state_new = true; + public int Vrtl_repo() {return vrtl_repo;} private int vrtl_repo = Repo_unknown; + public void Vrtl_repo_(int v) { +// if (vrtl_repo == Xof_meta_itm.Repo_unknown) { // NOTE: only update vrtl_repo when unknown; this assumes that orig and all thumbs sit in same repo (which they should) + vrtl_repo = v; + Dirty(); +// } + } + public Xof_repo_itm Repo_itm(Xowe_wiki wiki) { + switch (vrtl_repo) { + case Xof_meta_itm.Repo_missing : //return null; // DELETE: used to return null, but this caused Redownload_missing to fail; no reason why missing shouldn't return a default repo; DATE:2013-01-26 + case Xof_meta_itm.Repo_unknown : + case Xof_meta_itm.Repo_same : return wiki.Appe().File_mgr().Repo_mgr().Get_by_primary(wiki.Domain_bry()); + default : return wiki.File_mgr().Repo_mgr().Repos_get_at(vrtl_repo).Trg(); + } + } + public int Orig_w() {return orig_w;} private int orig_w; + public int Orig_h() {return orig_h;} private int orig_h; + public byte Orig_exists() {return orig_exists;} private byte orig_exists = Exists_unknown; + public void Orig_exists_(byte v) {if (this.orig_exists == v) return; this.orig_exists = v; Dirty();} + public Xof_meta_thumb[] Thumbs() {return thumbs;} private Xof_meta_thumb[] thumbs = Xof_meta_thumb.Ary_empty; + public boolean Thumbs_indicates_oga() {return Thumbs_get_by_w(0, 0, Xof_lnki_time.Null_as_int) != null;} // if 0,0 exists, then assume no thumbs; needed for oga/ogg + public boolean Thumbs_del(int w) { + int del_idx = -1; + int thumbs_len = thumbs.length; + for (int i = 0; i < thumbs_len; i++) { + Xof_meta_thumb thumb = thumbs[i]; + if (thumb.Width() == w) {del_idx = i; break;} + } + if (del_idx == -1) return false; + Xof_meta_thumb[] thumbs_new = new Xof_meta_thumb[thumbs_len - 1]; + int new_idx = 0; + for (int i = 0; i < thumbs_len; i++) { + Xof_meta_thumb thumb = thumbs[i]; + if (thumb.Width() == w) continue; + thumbs_new[new_idx++] = thumbs[i]; + } + thumbs = thumbs_new; + return true; + } + public Xof_meta_thumb Thumbs_get_img(int w, int w_adj) {return Thumbs_get_by_w(w, w_adj, Xof_lnki_time.Null_as_int);} + public Xof_meta_thumb Thumbs_get_vid(int s) {return Thumbs_get_by_w(Xop_lnki_tkn.Width_null, 0, s);} + Xof_meta_thumb Thumbs_get_by_w(int w, int w_adj, int seek) { + int thumbs_len = thumbs.length; + for (int i = 0; i < thumbs_len; i++) { + Xof_meta_thumb thumb = thumbs[i]; + int tmp_adj = w_adj;// thumb.Width() < 50 ? 0 : w_adj; + if ( (Xof_lnki_time.Null_y(seek) + && Int_.Between(thumb.Width(), w - tmp_adj, w + tmp_adj)) + || (w == Xop_lnki_tkn.Width_null + && (Xof_lnki_time.Null_y(seek) || Int_.In(seek, thumb.Seeks())))) + return thumb; + } + return null; + } + public Xof_meta_thumb Thumbs_get_by_h(int h, int thumbs_len) { + for (int i = 0; i < thumbs_len; i++) { + Xof_meta_thumb thumb = thumbs[i]; + int tmp_adj = 1;//thumb.Height() < 50 ? 0 : 1; + if (Int_.Between(thumb.Height(), h - tmp_adj, h + tmp_adj)) return thumb; + } + return null; + } + public Xof_meta_thumb Thumbs_get_largest(int thumbs_len) { + if (thumbs_len == 0) return null; + int rv_idx = -1, cur_largest = -1; + for (int i = 0; i < thumbs_len; i++) { + Xof_meta_thumb thumb = thumbs[i]; + if (thumb.Width() > cur_largest) { + rv_idx = i; + cur_largest = thumb.Width(); + } + } + return thumbs[rv_idx]; + } + public void Update_all(byte[] ptr_ttl, int w, int h, byte orig_exists, Xof_meta_thumb[] thumbs) { + this.ptr_ttl = ptr_ttl; orig_w = w; orig_h = h; this.orig_exists = orig_exists; this.thumbs = thumbs; Dirty(); + } + public void Update_orig_size(int w, int h) {this.orig_w = w; this.orig_h = h; Dirty();} + public Xof_meta_itm Update_thumb_oga_() { + Update_thumb_add(0, 0).Exists_n_(); + return this; + } + public Xof_meta_thumb Update_thumb_add(int w, int h) { // WARNING: h may not match existing val; will be discarded; EX: 10,20 stored in file; 10,21 passed in; 21 effectively discarded + Xof_meta_thumb thumb = Thumbs_get_img(w, 0); + if (thumb == null) { + int thumbs_len = thumbs.length; + Xof_meta_thumb[] thumbs_new = new Xof_meta_thumb[thumbs_len + 1]; + for (int i = 0; i < thumbs_len; i++) + thumbs_new[i] = thumbs[i]; + thumb = new Xof_meta_thumb().Width_(w).Height_(h).Exists_y_().State_new_(); + thumbs_new[thumbs_len] = thumb; + thumbs = thumbs_new; + Dirty(); + } + return thumb; + } + private void Dirty() {if (owner_fil != null) owner_fil.Dirty_();} + public void Save(Gfo_fld_wtr wtr) { + wtr.Write_bry_escape_fld(ttl); + byte vrtl_repo_byte = Byte_.Max_value_127; + switch (vrtl_repo) { + case Repo_unknown: vrtl_repo_byte = Byte_ascii.Ltr_z; break; + case Repo_same: vrtl_repo_byte = Byte_ascii.Ltr_y; break; + case Repo_missing: vrtl_repo_byte = Byte_ascii.Ltr_x; break; + default: vrtl_repo_byte = (byte)(vrtl_repo + Byte_ascii.Num_0); break; + } + wtr.Write_byte_fld(vrtl_repo_byte); + wtr.Write_bry_escape_fld(ptr_ttl); + wtr.Bfr() .Add_int_fixed(orig_exists, 1) .Add_byte(Xof_meta_thumb_parser.Dlm_exists) + .Add_int_variable(orig_w) .Add_byte(Xof_meta_thumb_parser.Dlm_width) + .Add_int_variable(orig_h) .Add_byte_pipe(); + int thumbs_len = thumbs.length; + Bry_bfr bfr = wtr.Bfr(); + for (int i = 0; i < thumbs_len; i++) { + if (i != 0) bfr.Add_byte(Byte_ascii.Semic); + Xof_meta_thumb thumb = thumbs[i]; + thumb.Save(bfr); + } + wtr.Write_dlm_row(); + state_new = false; + } + public void Load_orig_(int w, int h) {this.orig_w = w; this.orig_h = h;} + public void Load(Gfo_fld_rdr rdr, Xof_meta_thumb_parser parser) { + ttl = rdr.Read_bry_escape(); + byte vrtl_repo_byte = rdr.Read_byte(); + switch (vrtl_repo_byte) { + case Byte_ascii.Ltr_z: vrtl_repo = Repo_unknown; break; + case Byte_ascii.Ltr_y: vrtl_repo = Repo_same; break; + case Byte_ascii.Ltr_x: vrtl_repo = Repo_missing; break; + default: vrtl_repo = (byte)(vrtl_repo_byte - Byte_ascii.Num_0); break; + } + ptr_ttl = rdr.Read_bry_escape(); + rdr.Move_next_simple_fld(); + Xof_meta_thumb orig_itm = parser.Parse_one(rdr.Data(), rdr.Fld_bgn(), rdr.Fld_end()); + orig_exists = orig_itm.Exists(); + orig_w = orig_itm.Width(); + orig_h = orig_itm.Height(); + rdr.Move_next_simple_fld(); + parser.Parse_ary(rdr.Data(), rdr.Fld_bgn(), rdr.Fld_end()); + int thumbs_len = parser.Len(); + thumbs = new Xof_meta_thumb[thumbs_len]; + for (int i = 0; i < thumbs_len; i++) + thumbs[i] = parser.Ary()[i]; + parser.Clear(); + state_new = false; + } + public static final byte Exists_n = 0, Exists_y = 1, Exists_unknown = 2; + public static final byte Repo_unknown = (byte)127, Repo_same = (byte)126, Repo_missing = (byte)125; + public static final byte + Tid_main = 0 + , Tid_ptr = 1 + , Tid_vrtl = 2 + , Tid_null = Byte_.Max_value_127 + ; + public static final byte[] Ptr_ttl_null = Bry_.Empty; +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_mgr.java b/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_mgr.java index a27517de8..fd4733715 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_mgr.java @@ -13,3 +13,104 @@ 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.wikis.tdbs.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.flds.*; +public class Xof_meta_mgr implements Gfo_invk { + private Object[] root = new Object[16]; private final Ordered_hash dirty_fils = Ordered_hash_.New_bry(); + private final Gfo_fld_rdr rdr = Gfo_fld_rdr.xowa_(); private final Xof_meta_thumb_parser parser = new Xof_meta_thumb_parser(); + public Xof_meta_mgr(Xowe_wiki wiki) {this.wiki = wiki; this.root_dir = wiki.Appe().Fsys_mgr().File_dir().GenSubDir_nest("#meta", wiki.Domain_str());} + public Xowe_wiki Wiki() {return wiki;} private Xowe_wiki wiki; + public Io_url Root_dir() {return root_dir;} Io_url root_dir; + public int Depth() {return depth;} public Xof_meta_mgr Depth_(int v) {depth = v; return this;} private int depth = 3; // allows a full english wikipedia to have meta files of approximately 32 kb; otherwise would be 480 kb; most wikis will not get to this size, but worst case is wasting 16 kb in (16 * 16) files which is less than 4 mb + public boolean Append_only() {return append_only;} public Xof_meta_mgr Append_only_(boolean v) {append_only = v; return this;} private boolean append_only; + public Xof_meta_fil Get_fil_or_new(byte[] md5) { + Xof_meta_fil rv = Get_fil_or_null(md5); + if (rv == null) { + if (!append_only) + rv = Load(md5); + if (rv == null) { + rv = Bld_new(root, depth - 1, this, md5, 0); + dirty_fils.Add(md5, rv); + } + } + return rv; + } + public Xof_meta_itm Get_itm_or_new(byte[] ttl) {return Get_itm_or_new(ttl, gplx.xowa.files.Xof_file_wkr_.Md5(ttl));} + public Xof_meta_itm Get_itm_or_new(byte[] ttl, byte[] md5) { + Xof_meta_fil fil = this.Get_fil_or_new(md5); + return fil.Get_or_new(ttl); + } + Xof_meta_fil Get_fil_or_null(byte[] md5) {return Get_fil_or_null_recur(root, depth - 1, md5, 0);} + public void Dirty_(Xof_meta_fil fil) { + byte[] md5 = fil.Md5(); + if (!dirty_fils.Has(md5)) dirty_fils.Add(md5, fil); + } + public void Clear() {root = new Object[16]; dirty_fils.Clear();} + public void Save() {Save(false);} + public void Save(boolean clear) { + int dirty_len = dirty_fils.Count(); + Bry_bfr tmp_bfr = Bry_bfr_.New(); + wtr.Bfr_(tmp_bfr); + for (int i = 0; i < dirty_len; i++) { + Xof_meta_fil fil = (Xof_meta_fil)dirty_fils.Get_at(i); + Io_url fil_url = Xof_meta_fil.Bld_url(root_dir, fil.Md5(), depth); + fil.Save(wtr); + if (append_only) + Io_mgr.Instance.AppendFilBfr(fil_url, tmp_bfr); + else + Io_mgr.Instance.SaveFilBfr(fil_url, tmp_bfr); + } + dirty_fils.Clear(); + if (clear) this.Clear(); + } Gfo_fld_wtr wtr = Gfo_fld_wtr.xowa_(); + Xof_meta_fil Load(byte[] md5) { + Io_url fil_url = Xof_meta_fil.Bld_url(root_dir, md5, depth); + byte[] bry = Io_mgr.Instance.LoadFilBry(fil_url); if (bry == Bry_.Empty) return null; + rdr.Ini(bry, 0); + Xof_meta_fil rv = Bld_new(root, depth - 1, this, md5, 0); // NOTE: need to register file before loading it; defect wherein 2 files with same hash prefix would skip one b/c Loaded file was not registered; EX.WS: en.wikiquote.org/The Hitchhiker's Guide to the Galaxy; NMMP_dolphin_with_locator.jpeg, da6f95736ed249f371f30bf5f1205fbd; Hoags_object.jpg, daed4a54e48e4266bd2f2763b7c4018c + rv.Load(rdr, parser); + return rv; + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_regy_depth_)) depth = m.ReadInt("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_regy_depth_ = "depth_"; + static final String GRP_KEY = "xowa.file_regy."; + private static Xof_meta_fil Get_fil_or_null_recur(Object[] ary, int depth_max, byte[] md5, int md5_idx) { + int ary_idx = Int_.By_hex_byte(md5[md5_idx]); + if (ary_idx < 0 || ary_idx > 15) throw Err_.new_wo_type("md5_not_valid", "md5", String_.new_u8(md5), "idx", md5_idx); + Object o = ary[ary_idx]; + if (o == null) return null; + if (md5_idx == depth_max) { // leaf; return fil + try {return (Xof_meta_fil)o;} catch (Exception exc) {throw Err_.new_cast(exc, Xof_meta_fil.class, o);} + } + Object[] nxt = null; try {nxt = (Object[])o;} catch(Exception exc) {throw Err_.new_cast(exc, Object[].class, o);} + return Get_fil_or_null_recur(nxt, depth_max, md5, md5_idx + 1); + } + private static Xof_meta_fil Bld_new(Object[] ary, int depth_max, Xof_meta_mgr regy_mgr, byte[] md5, int md5_idx) { + int ary_idx = Int_.By_hex_byte(md5[md5_idx]); + if (ary_idx < 0 || ary_idx > 15) throw Err_.new_wo_type("md5_not_valid", "md", String_.new_u8(md5), "idx", md5_idx); + Object o = ary[ary_idx]; + if (md5_idx == depth_max) { // leaf; create itm + Xof_meta_fil rv = null; + if (o == null) { + rv = new Xof_meta_fil(regy_mgr, md5); + ary[ary_idx] = rv; + } + else + rv = (Xof_meta_fil)o; + return rv; + } + else { + Object[] nxt = null; + if (o == null) { + nxt = new Object[16]; + ary[ary_idx] = nxt; + } + else + nxt = (Object[])o; + return Bld_new(nxt, depth_max, regy_mgr, md5, md5_idx + 1); + } + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_mgr_tst.java b/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_mgr_tst.java index a27517de8..4da87fd00 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_mgr_tst.java @@ -13,3 +13,82 @@ 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.wikis.tdbs.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import org.junit.*; +import gplx.core.primitives.*; +import gplx.gfui.*; import gplx.xowa.files.*; +public class Xof_meta_mgr_tst { + Xof_file_regy_fxt fxt = new Xof_file_regy_fxt(); + @Before public void init() {fxt.Ini();} + @Test public void Set_one() { + fxt .Set("A.png", 440, 400, true, "440,400", "220,200") + .tst_Save("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv", "A.png|y||1?440,400|1?440,400;1?220,200"); + } + @Test public void Set_many() { + fxt .Set("A.png" , 440, 400, true, "440,400", "220,200") + .Set("Cabbage.jpg" , 640, 456, false, "220,200", "200,180") + .tst_Save("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv" + , "A.png|y||1?440,400|1?440,400;1?220,200" + , "Cabbage.jpg|y||2?640,456|1?220,200;1?200,180" + ); + } + @Test public void Load_and_add() { + fxt.Save_fil("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv" + , "A.png|y||1?440,400|" + , "Cabbage.jpg|y||2?640,456|1?440,220;1?220,200" + ) + .Set("Wooden_hourglass_3.jpg", 967, 1959, false, "220,200") + .tst_Save("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv" + , "A.png|y||1?440,400|" + , "Cabbage.jpg|y||2?640,456|1?440,220;1?220,200" + , "Wooden_hourglass_3.jpg|y||2?967,1959|1?220,200" + ); + } + @Test public void Load_and_update() { + fxt.Save_fil("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv" + , "A.png|y||0?440,400|" + , "Cabbage.jpg|y||2?640,456|1?440,400;1?220,200" + ) + .Set("A.png", 550, 500, false, "220,200") + .tst_Save("mem/xowa/file/#meta/en.wikipedia.org/7/70.csv" + , "A.png|y||2?550,500|1?220,200" + , "Cabbage.jpg|y||2?640,456|1?440,400;1?220,200" + ); + } +} +class Xof_file_regy_fxt { + Xof_meta_mgr regy_mgr; + byte[] md5_(byte[] name) {return Xof_file_wkr_.Md5(name);} + public void Ini() { + Io_mgr.Instance.InitEngine_mem(); + Xoae_app app = Xoa_app_fxt.Make__app__edit(); + Xowe_wiki wiki = Xoa_app_fxt.Make__wiki__edit(app); + regy_mgr = new Xof_meta_mgr(wiki); + regy_mgr.Clear(); + regy_mgr.Depth_(2); + } + public Xof_file_regy_fxt Save_fil(String url, String... data) {Io_mgr.Instance.SaveFilStr(Io_url_.mem_fil_(url), String_.Concat_lines_nl(data)); return this;} + public Xof_file_regy_fxt Set(String name_str, int w, int h, boolean orig_exists, String... thumbs) { + byte[] name = Bry_.new_u8(name_str); + byte[] md5 = md5_(name); + Xof_meta_itm itm = regy_mgr.Get_itm_or_new(name, md5); + itm.Vrtl_repo_(Xof_meta_itm.Repo_same); // all tests above assume this is main + itm.Update_all(Bry_.Empty, w, h, orig_exists ? Xof_meta_itm.Exists_y : Xof_meta_itm.Exists_unknown, To_ary(thumbs)); + return this; + } + Xof_meta_thumb[] To_ary(String[] ary) { + int len = ary.length; + Xof_meta_thumb[] rv = new Xof_meta_thumb[len]; + Int_ary_parser parser = new Int_ary_parser(); + for (int i = 0; i < len; i++) { + int[] size = parser.Parse_ary(ary[i], Byte_ascii.Comma); + rv[i] = new Xof_meta_thumb().Width_(size[0]).Height_(size[1]).Exists_y_(); + } + return rv; + } + public Xof_file_regy_fxt tst_Save(String url_str, String... expd_ary) { + regy_mgr.Save(); + Tfds.Eq_ary_str(expd_ary, Io_mgr.Instance.LoadFilStr_args(Io_url_.new_fil_(url_str)).ExecAsStrAryLnx()); + return this; + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_thumb.java b/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_thumb.java index a27517de8..156aeb065 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_thumb.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_thumb.java @@ -13,3 +13,37 @@ 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.wikis.tdbs.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +public class Xof_meta_thumb { + public Xof_meta_thumb() {} + public Xof_meta_thumb(byte exists, int width, int height, int[] seeks) {this.exists = exists; this.width = width; this.height = height; this.seeks = seeks; if (seeks == null) seeks = Int_ary_.Empty;} + public byte Exists() {return exists;} private byte exists = Xof_meta_itm.Exists_unknown; // default to y, b/c thumbs are usually added if they exist; handle n when it occurs; unknown should never happen; + public boolean State_new() {return state_new;} public Xof_meta_thumb State_new_() {state_new = true; return this;} private boolean state_new; + public Xof_meta_thumb Exists_(byte v) {exists = v; return this;} + public Xof_meta_thumb Exists_y_() {exists = Xof_meta_itm.Exists_y; return this;} + public Xof_meta_thumb Exists_n_() {exists = Xof_meta_itm.Exists_n; return this;} + public int Width() {return width;} public Xof_meta_thumb Width_(int v) {width = v; return this;} private int width; + public int Height() {return height;} public Xof_meta_thumb Height_(int v) {height = v; return this;} private int height; + public int[] Seeks() {return seeks;} public Xof_meta_thumb Seeks_(int[] v) {seeks = v; return this;} private int[] seeks = Int_ary_.Empty; + public Xof_meta_thumb Seeks_add(int v) { + int seeks_len = seeks.length; + seeks = (int[])Array_.Resize(seeks, seeks_len + 1); + seeks[seeks_len] = v; + return this; + } + public void Save(Bry_bfr bfr) { + bfr .Add_int_fixed(exists, 1) .Add_byte(Xof_meta_thumb_parser.Dlm_exists) + .Add_int_variable(width) .Add_byte(Xof_meta_thumb_parser.Dlm_width) + .Add_int_variable(height); + if (seeks != Int_ary_.Empty) { + bfr.Add_byte(Xof_meta_thumb_parser.Dlm_seek); + int seeks_len = seeks.length; + for (int i = 0; i < seeks_len; i++) { + if (i != 0) bfr.Add_byte(Xof_meta_thumb_parser.Dlm_width); + bfr.Add_int_variable(seeks[i]); + } + } + state_new = false; + } + public static final Xof_meta_thumb[] Ary_empty = new Xof_meta_thumb[0]; +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_thumb_parser.java b/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_thumb_parser.java index a27517de8..6d55c0cb6 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_thumb_parser.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_thumb_parser.java @@ -13,3 +13,59 @@ 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.wikis.tdbs.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.primitives.*; +public class Xof_meta_thumb_parser extends Obj_ary_parser_base { + private final Gfo_number_parser number_parser = new Gfo_number_parser(); + private final Int_ary_parser int_ary_parser = new Int_ary_parser(); + public Xof_meta_thumb[] Ary() {return ary;} private Xof_meta_thumb[] ary = new Xof_meta_thumb[Ary_max]; static final int Ary_max = 16; + public int Len() {return ary_idx;} private int ary_idx; + public void Parse_ary(byte[] bry, int bgn, int end) {super.Parse_core(bry, bgn, end, Byte_ascii.Semic, Byte_ascii.Null);} + public Xof_meta_thumb Parse_one(byte[] bry, int bgn, int end) { + super.Parse_core(bry, bgn, end, Byte_ascii.Semic, Byte_ascii.Null); + return ary[0]; + } + public void Clear() {if (ary.length > Ary_max) ary = new Xof_meta_thumb[Ary_max];} + @Override protected void Ary_len_(int v) { + ary_idx = 0; + if (v > Ary_max) ary = new Xof_meta_thumb[v]; + } + @Override protected void Parse_itm(byte[] bry, int bgn, int end) { // EX: "1:45,40"; "1:45,40:3,4" + Xof_meta_thumb itm = new Xof_meta_thumb(); boolean height_found = false; + if (end - 2 < bgn) throw Err_.new_wo_type("itm must be at least 2 bytes long", "itm", String_.new_u8(bry, bgn, end)); // EX: 4,6 + int pos = bgn; + byte exists_byte = bry[pos]; + switch (exists_byte) { + case Byte_ascii.Num_0: itm.Exists_(Xof_meta_itm.Exists_n); break; + case Byte_ascii.Num_1: itm.Exists_(Xof_meta_itm.Exists_y); break; + case Byte_ascii.Num_2: itm.Exists_(Xof_meta_itm.Exists_unknown); break; + default: throw Err_.new_wo_type("exists must be 0,1,2", "exists", exists_byte, "itm", String_.new_u8(bry, bgn, end)); + } + if (bry[pos + 1] != Dlm_exists) throw Err_.new_wo_type("question must follow exists", "bad_char", bry[pos + 1], "itm", String_.new_u8(bry, bgn, end)); + pos += 2; + int num_bgn = pos; + while (pos < end) { + byte b = bry[pos]; + switch (b) { + case Dlm_width: // "," found; assume width; note that seek commas will be handled by seek + itm.Width_(number_parser.Parse(bry, num_bgn, pos).Rv_as_int()); + num_bgn = pos + Byte_ascii.Len_1; + break; + case Dlm_seek: + itm.Height_(number_parser.Parse(bry, num_bgn, pos).Rv_as_int()); + num_bgn = pos + Byte_ascii.Len_1; + height_found = true; + itm.Seeks_(int_ary_parser.Parse_ary(bry, num_bgn, end, Byte_ascii.Comma)); + pos = end; + break; + } + ++pos; + } + if (!height_found) // handle '1:2,3' as opposed to '1:2,3@4' + itm.Height_(number_parser.Parse(bry, num_bgn, end).Rv_as_int()); + ary[ary_idx++] = itm; + } + static final String GRP_KEY = "xowa.meta.itm.file"; + public static final byte Dlm_exists = Byte_ascii.Question, Dlm_seek = Byte_ascii.At, Dlm_width = Byte_ascii.Comma; + public static final String Dlm_seek_str = "@"; +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_thumb_parser_tst.java b/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_thumb_parser_tst.java index a27517de8..804db0228 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_thumb_parser_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/metas/Xof_meta_thumb_parser_tst.java @@ -13,3 +13,36 @@ 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.wikis.tdbs.metas; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import org.junit.*; import gplx.core.strings.*; +public class Xof_meta_thumb_parser_tst { + Xof_meta_thumb_parser parser = new Xof_meta_thumb_parser(); + @Test public void Exists_y() {Tst_parse("1?45,40", itm_y_(45, 40));} + @Test public void Exists_n() {Tst_parse("0?45,40", itm_n_(45, 40));} + @Test public void Many() {Tst_parse("1?45,40;0?90,80", itm_y_(45, 40), itm_n_(90, 80));} + @Test public void Seek() {Tst_parse("1?45,40@2,3,4", itm_y_(45, 40, 2, 3, 4));} + private void Tst_parse(String raw_str, Xof_meta_thumb... expd) { + byte[] raw = Bry_.new_a7(raw_str); + parser.Parse_ary(raw, 0, raw.length); + Tfds.Eq_str_lines(Xto_str(expd, 0, expd.length), Xto_str(parser.Ary(), 0, parser.Len())); + } + String Xto_str(Xof_meta_thumb[] ary, int bgn, int end) { + for (int i = bgn; i < end; i++) { + Xof_meta_thumb itm = ary[i]; + sb .Add(itm.Exists()).Add(":") + .Add(itm.Width()).Add(",") + .Add(itm.Height()); + int seeks_len = itm.Seeks().length; + for (int j = 0; j < seeks_len; j++) { + int seek = itm.Seeks()[j]; + sb.Add(i == 0 ? "@" : ","); + sb.Add(seek); + } + sb.Add_char_nl(); + } + return sb.To_str_and_clear(); + } String_bldr sb = String_bldr_.new_(); +// Xof_meta_img_chkr img_(int w, int h, params int[] seeks) {return new Xof_meta_img_chkr().Width_(w).Height_(h).Seeks_(seeks);} + Xof_meta_thumb itm_y_(int w, int h, int... seeks) {return new Xof_meta_thumb(Xof_meta_itm.Exists_y, w, h, seeks);} + Xof_meta_thumb itm_n_(int w, int h, int... seeks) {return new Xof_meta_thumb(Xof_meta_itm.Exists_n, w, h, seeks);} +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/stats/Xob_stat_itm.java b/400_xowa/src/gplx/xowa/wikis/tdbs/stats/Xob_stat_itm.java index a27517de8..1783fd2a2 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/stats/Xob_stat_itm.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/stats/Xob_stat_itm.java @@ -13,3 +13,29 @@ 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.wikis.tdbs.stats; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.strings.*; +public class Xob_stat_itm { + public String Ns() {return ns;} private String ns; + public int Fils; + public long Size, SizeMax = Int_.Min_value, SizeMin = Int_.Max_value; + public int SizeMaxIdx, SizeMinIdx; + public void Tally(long size, int idx) { + Fils++; + Size += size; + if (size > SizeMax) {SizeMax = size; SizeMaxIdx = idx;} + if (size < SizeMin) {SizeMin = size; SizeMinIdx = idx;} + } + public void To_str(String_bldr sb) { + XtoStr_fld(sb, ns).XtoStr_fld(sb, Fils).XtoStr_fld(sb, Size).XtoStr_fld(sb, SizeMax).XtoStr_fld(sb, SizeMaxIdx).XtoStr_fld(sb, SizeMin); + sb.Add(Int_.To_str(SizeMinIdx)); + } + Xob_stat_itm XtoStr_fld(String_bldr sb, long v) {sb.Add(Long_.To_str(v)).Add(Xob_stat_itm.Dlm); return this;} + Xob_stat_itm XtoStr_fld(String_bldr sb, int v) {sb.Add(Int_.To_str(v)).Add(Xob_stat_itm.Dlm); return this;} + Xob_stat_itm XtoStr_fld(String_bldr sb, String v) {sb.Add(v).Add(Xob_stat_itm.Dlm); return this;} + public Xob_stat_itm(String ns) { + this.ns = ns; + } + public Object NewByKey(Object o) {return new Xob_stat_itm((String)o);} public static final Xob_stat_itm Instance = new Xob_stat_itm(); Xob_stat_itm() {} + public static final char Dlm = '|'; +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/stats/Xob_stat_mgr.java b/400_xowa/src/gplx/xowa/wikis/tdbs/stats/Xob_stat_mgr.java index a27517de8..7f824d268 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/stats/Xob_stat_mgr.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/stats/Xob_stat_mgr.java @@ -13,3 +13,55 @@ 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.wikis.tdbs.stats; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.strings.*; +import gplx.xowa.wikis.nss.*; +public class Xob_stat_mgr { + public Xob_stat_type GetOrNew(byte tid) { + Xob_stat_type rv = (Xob_stat_type)regy.Get_by(tid); + if (rv == null) { + rv = new Xob_stat_type(tid); + regy.Add(tid, rv); + } + return rv; + } + public String Print(Xow_ns_mgr nsMgr) { + String_bldr sb = String_bldr_.new_(); + for (int i = 0; i < regy.Count(); i++) { + Xob_stat_type typ = (Xob_stat_type)regy.Get_at(i); + sb.Add(String_.PadEnd(Xotdb_dir_info_.Tid_name(typ.Tid()), 6, " ")); + } + sb.Add_str_w_crlf("ns"); + String[] nsAry = GetNmsAry(nsMgr); + for (String ns : nsAry) { + for (int i = 0; i < regy.Count(); i++) { + Xob_stat_type typ = (Xob_stat_type)regy.Get_at(i); + Xob_stat_itm itm = (Xob_stat_itm)typ.GetOrNew(ns); + sb.Add(Int_.To_str_pad_bgn_zero(itm.Fils, 5)).Add(" "); + } + sb.Add_str_w_crlf(ns); + } + return sb.To_str(); + } + public String To_str() { + String_bldr sb = String_bldr_.new_(); + for (int i = 0; i < regy.Count(); i++) { + Xob_stat_type typ = (Xob_stat_type)regy.Get_at(i); + typ.To_str(sb); + } + return sb.To_str(); + } + String[] GetNmsAry(Xow_ns_mgr nsMgr) { + Ordered_hash nsRegy = Ordered_hash_.New(); + for (int i = 0; i < regy.Count(); i++) { + Xob_stat_type typ = (Xob_stat_type)regy.Get_at(i); + for (int j = 0; j < typ.Count(); j++) { + Xob_stat_itm itm = (Xob_stat_itm)typ.GetAt(j); + if (!nsRegy.Has(itm.Ns())) + nsRegy.Add_as_key_and_val(itm.Ns()); + } + } + return (String[])nsRegy.To_ary(String.class); + } + Ordered_hash regy = Ordered_hash_.New(); +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/stats/Xob_stat_type.java b/400_xowa/src/gplx/xowa/wikis/tdbs/stats/Xob_stat_type.java index a27517de8..ee6537ec1 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/stats/Xob_stat_type.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/stats/Xob_stat_type.java @@ -13,3 +13,29 @@ 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.wikis.tdbs.stats; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.strings.*; import gplx.xowa.wikis.tdbs.*; +public class Xob_stat_type { + public byte Tid() {return tid;} private byte tid; + public Xob_stat_type(byte tid) {this.tid = tid;} + public Xob_stat_itm GetOrNew(String ns) { + Xob_stat_itm rv = (Xob_stat_itm)regy.Get_by(ns); + if (rv == null) { + rv = new Xob_stat_itm(ns); + regy.Add(ns, rv); + } + return rv; + } + public Xob_stat_itm GetAt(int i) {return (Xob_stat_itm)regy.Get_at(i);} + public int Count() {return regy.Count();} + public void To_str(String_bldr sb) { + for (int i = 0; i < regy.Count(); i++) { + Xob_stat_itm itm = (Xob_stat_itm)regy.Get_at(i); + sb.Add(Xotdb_dir_info_.Tid_name(tid)).Add(Xob_stat_itm.Dlm); + itm.To_str(sb); + sb.Add(Byte_ascii.Nl); + } + } + Ordered_hash regy = Ordered_hash_.New(); + public static final Xob_stat_type Instance = new Xob_stat_type(); Xob_stat_type() {} +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/utils/Xos_url_gen.java b/400_xowa/src/gplx/xowa/wikis/tdbs/utils/Xos_url_gen.java index a27517de8..4ab15eee5 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/utils/Xos_url_gen.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/utils/Xos_url_gen.java @@ -13,3 +13,34 @@ 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.wikis.tdbs.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.ios.*; +public class Xos_url_gen implements Io_url_gen { + public Xos_url_gen(Io_url root) {this.root = root;} Io_url root; int idx = 0; + public byte[] Ext() {return ext;} public Xos_url_gen Ext_(byte[] v) {ext = v; return this;} private byte[] ext = Bry_.new_a7(".csv"); + public Io_url Cur_url() {return cur_url;} Io_url cur_url; + public Io_url Nxt_url() {cur_url = bld_fil_(root, idx++, ext); return cur_url;} + public Io_url[] Prv_urls() { + int len = idx + 1; + Io_url[] rv = new Io_url[len]; + for (int i = 0; i < len; i++) + rv[i] = bld_fil_(root, i, ext); + return rv; + } + public void Del_all() {} + public static Io_url bld_fil_(Io_url root, int idx, byte[] ext) { + byte dir_spr = root.Info().DirSpr_byte(); + tmp_bfr.Add(root.RawBry()); + int cur_mod = 100000000, cur_idx = idx; + while (cur_mod > 99) { + int val = cur_idx / cur_mod; + tmp_bfr.Add_int_fixed(val, 2).Add_byte(dir_spr); + cur_idx -= (val * cur_mod); + cur_mod /= 100; + } + tmp_bfr.Add_int_fixed(idx, 10); + tmp_bfr.Add(ext); + return Io_url_.new_fil_(tmp_bfr.To_str_and_clear()); + } + private static Bry_bfr tmp_bfr = Bry_bfr_.Reset(256); +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/utils/Xos_url_gen_tst.java b/400_xowa/src/gplx/xowa/wikis/tdbs/utils/Xos_url_gen_tst.java index a27517de8..901cee3f1 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/utils/Xos_url_gen_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/utils/Xos_url_gen_tst.java @@ -13,3 +13,18 @@ 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.wikis.tdbs.utils; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import org.junit.*; +import gplx.core.ios.*; +public class Xos_url_gen_tst { + @Test public void Url_gen() { + tst_url_gen("mem/root/", 0, "mem/root/00/00/00/00/0000000000.csv"); + tst_url_gen("mem/root/", 99, "mem/root/00/00/00/00/0000000099.csv"); + tst_url_gen("mem/root/", 1234567890, "mem/root/12/34/56/78/1234567890.csv"); + } + private void tst_url_gen(String root_str, int idx, String expd) { + Io_url root = Io_url_.mem_fil_(root_str); + Io_url actl_url = Xos_url_gen.bld_fil_(root, idx, Bry_.new_a7(".csv")); + Tfds.Eq(expd, actl_url.Xto_api()); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_file.java b/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_file.java index a27517de8..e44fd4892 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_file.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_file.java @@ -13,3 +13,259 @@ 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.wikis.tdbs.xdats; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; +import gplx.core.encoders.*; +public class Xob_xdat_file { + public byte[] Src() {return src;} private byte[] src; + public int Src_len() {return src_len;} public Xob_xdat_file Src_len_(int v) {src_len = v; return this;} private int src_len; // NOTE: src_len can be different than src.length (occurs when reusing brys) + public Xob_xdat_file Update(Bry_bfr bfr, Xob_xdat_itm itm, byte[] v) { + int ary_len = itm_ends.length; + int itm_idx = itm.Itm_idx(); + int prv = itm_idx == 0 ? 0 : itm_ends[itm_idx - 1]; + int old_end = itm_ends[itm_idx]; + int new_end = prv + v.length; + int dif = new_end - old_end; + itm_ends[itm_idx] = new_end; + for (int i = itm_idx + 1; i < ary_len; i++) { + itm_ends[i] += dif; + } + Src_rebuild_hdr(bfr, ary_len); + bfr.Add_mid(src, itm_0_bgn, itm.Itm_bgn()); + bfr.Add(v); + bfr.Add_mid(src, itm.Itm_end() + 1, src.length); // NOTE: + 1 to skip nl + src = bfr.To_bry_and_clear(); + return this; + } + byte[][] Src_extract_brys(int ary_len) { + byte[][] rv = new byte[ary_len][]; + int itm_bgn = this.itm_0_bgn; + for (int i = 0; i < ary_len; i++) { + int itm_end = itm_ends[i] + itm_0_bgn; + rv[i] = Bry_.Mid(src, itm_bgn, itm_end); + itm_bgn = itm_end; + } + return rv; + } + public void Sort(Bry_bfr bfr, gplx.core.lists.ComparerAble comparer) { + int ary_len = itm_ends.length; + byte[][] brys = Src_extract_brys(ary_len); + Array_.Sort(brys, comparer); + Src_rebuild_hdr(bfr, ary_len); + itm_0_bgn = (ary_len * Len_idx_itm) + Len_itm_dlm; + int itm_bgn = 0; + for (int i = 0; i < ary_len; i++) { + byte[] bry = brys[i]; + int bry_len = bry.length; + int itm_end = itm_bgn + bry_len; + itm_ends[i] = itm_end; + itm_bgn = itm_end; + bfr.Add(bry); + } + src = bfr.To_bry_and_clear(); + } + public void Insert(Bry_bfr bfr, byte[] itm) { + int ary_len = itm_ends.length; + itm_ends = (int[])Array_.Resize(itm_ends, ary_len + 1); + int prv_pos = ary_len == 0 ? 0 : itm_ends[ary_len - 1]; + itm_ends[ary_len] = prv_pos + itm.length; + Src_rebuild(bfr, ary_len + 1, itm); + } + private void Src_rebuild_hdr(Bry_bfr bfr, int ary_len) { + int bgn = 0; + for (int i = 0; i < ary_len; i++) { + int end = itm_ends[i]; + int len = end - bgn; + bfr.Add_base85_len_5(len).Add_byte(Dlm_hdr_fld); + bgn = end; + } + bfr.Add_byte(Dlm_row); + } + private void Src_rebuild(Bry_bfr bfr, int ary_len, byte[] new_itm) { + Src_rebuild_hdr(bfr, ary_len); + Src_rebuild_brys(bfr, ary_len, new_itm); + } + private void Src_rebuild_brys(Bry_bfr bfr, int ary_len, byte[] new_itm) { + int bgn = itm_0_bgn; + boolean insert = new_itm != null; + int ary_end = insert ? ary_len - 1 : ary_len; + for (int i = 0; i < ary_end; i++) { + int end = itm_ends[i] + itm_0_bgn; + bfr.Add_mid(src, bgn, end); + bgn = end; + } + if (insert) bfr.Add(new_itm); + itm_0_bgn = (ary_len * Len_idx_itm) + Len_itm_dlm; + src = bfr.To_bry_and_clear(); + } private static final byte Dlm_hdr_fld = Byte_ascii.Pipe, Dlm_row = Byte_ascii.Nl; + public void Save(Io_url url) { + Bry_bfr bfr = Bry_bfr_.New(); + Srl_save_bry(bfr); + Io_stream_wtr wtr = Io_stream_wtr_.New_by_url(url); + try { + wtr.Open(); + wtr.Write(bfr.Bfr(), 0, bfr.Len()); + wtr.Flush(); + } + catch (Exception e) {throw Err_.new_exc(e, "xo", "failed to save file", "url", url.Xto_api());} + finally { + wtr.Rls(); + } + } + public void Srl_save_bry(Bry_bfr bfr) { + int itm_ends_len = itm_ends.length; + int prv_bgn = 0; + for (int i = 0; i < itm_ends_len; i++) { + int itm_end = itm_ends[i]; + bfr.Add_base85_len_5(itm_end - prv_bgn).Add_byte(Dlm_hdr_fld); + prv_bgn = itm_end; + } + bfr.Add_byte(Dlm_row); + bfr.Add_mid(src, itm_0_bgn, src.length); + } + public byte[] Get_bry(int i) { + int bgn = i == 0 ? itm_0_bgn : itm_0_bgn + itm_ends[i - 1]; + int end = itm_0_bgn + itm_ends[i]; + return Bry_.Mid(src, bgn, end); + } + public int Count() {return itm_ends.length;} + public Xob_xdat_file GetAt(Xob_xdat_itm itm, int idx) { + itm.Src_(src); + itm.Itm_idx_(idx); + itm.Itm_bgn_(itm_0_bgn + (idx == 0 ? 0 : itm_ends[idx - 1])); + itm.Itm_end_(itm_0_bgn + itm_ends[idx] - Len_itm_dlm); + return this; + } + public Xob_xdat_file Find(Xob_xdat_itm itm, byte[] lkp, int lkp_bgn, byte lkp_dlm, boolean exact) { + itm.Clear(); + int itm_idx = Xob_xdat_file_.BinarySearch(itm_0_bgn, src, itm_ends, lkp, lkp_bgn, lkp_dlm, 1, exact, itm); if (itm_idx == String_.Find_none) {return this;} + GetAt(itm, itm_idx); + return this; + } + public Xob_xdat_file Clear() {src = null; itm_ends = Int_ary_.Empty; return this;} + private int[] itm_ends = Int_ary_.Empty; private int itm_0_bgn; + public Xob_xdat_file Parse(byte[] src, int src_len, Io_url url) {// SEE:NOTE_1;xdat format + if (src_len == 0) throw Err_.new_wo_type("file cannot be empty for parse", "url", url.Raw()); + int itm_count = 0, tmp_len = Parse_tmp_len; int[] tmp = Parse_tmp; + try { + int slot_bgn = 0, slot_old = 0, slot_new = 0; + while (true) { + slot_bgn = itm_count * Len_idx_itm; + if (slot_bgn >= src_len) break; + if (src[slot_bgn] == Byte_ascii.Nl) break; + int tmp_val = Base85_.To_int_by_bry(src, slot_bgn, slot_bgn + Offset_base85); + slot_new = slot_old + tmp_val; + int new_idx = itm_count + 1; + if (tmp_len < new_idx) { + tmp_len = new_idx * 2; + tmp = (int[])Array_.Resize(tmp, tmp_len); + } + tmp[itm_count] = slot_new; + itm_count = new_idx; + slot_old = slot_new; + } + int itm_ends_last = slot_new; + itm_ends = new int[itm_count]; + for (int i = 0; i < itm_count; i++) + itm_ends[i] = tmp[i]; + itm_0_bgn = slot_bgn + Len_itm_dlm; + this.src = Bry_.Mid(src, 0, itm_ends_last + itm_0_bgn); + } catch (Exception e) {throw Err_.new_exc(e, "xo", "failed to parse idx", "itm_count", itm_count, "url", url.Raw());} + return this; + } private static final int Parse_tmp_len = 8 * 1024; static int[] Parse_tmp = new int[Parse_tmp_len]; + static final int Len_itm_dlm = 1, Len_idx_itm = 6, Offset_base85 = 4; // 6 = 5 (base85_int) + 1 (new_line/pipe) + static final String GRP_KEY = "xowa.xdat_fil"; + public static byte[] Rebuid_header(byte[] orig, byte[] dlm) { + byte[][] rows = Bry_split_.Split(orig, dlm); + int rows_len = rows.length; + Bry_bfr bfr = Bry_bfr_.New(); + int dlm_len = dlm.length; + for (int i = 1; i < rows_len; i++) { // i=1; skip 1st row (which is empty header) + byte[] row = rows[i]; + int row_len = row.length + dlm_len; + bfr.Add_base85_len_5(row_len).Add_byte(Byte_ascii.Pipe); + } + bfr.Add_byte(Byte_ascii.Nl); + for (int i = 1; i < rows_len; i++) { // i=1; skip 1st row (which is empty header) + byte[] row = rows[i]; + bfr.Add(row); + bfr.Add(dlm); + } + return bfr.To_bry_and_clear(); + } +} +class Xob_xdat_file_ { + public static int BinarySearch(int itm_0_bgn, byte[] src, int[] itm_ends, byte[] lkp, int lkp_bgn, byte lkp_dlm, int itm_end_adj, boolean exact, Xob_xdat_itm xdat_itm) {if (lkp == null) throw Err_.new_null(); + int itm_ends_len = itm_ends.length; if (itm_ends_len == 0) throw Err_.new_wo_type("itm_ends_len cannot have 0 itms"); + int lo = -1, hi = itm_ends_len - 1; // NOTE: -1 is necessary; see test + int itm_idx = (hi - lo) / 2; + int lkp_len = lkp.length; + int delta = 1; + boolean flagged = false; + while (true) { + int itm_bgn = itm_0_bgn + (itm_idx == 0 ? 0 : itm_ends[itm_idx - 1]); + int itm_end = itm_0_bgn + itm_ends[itm_idx] - itm_end_adj; // itm_end_adj to handle ttl .xdat and trailing \n + int fld_bgn = itm_bgn + lkp_bgn, lkp_pos = -1; + int comp = CompareAble_.Same; + for (int i = fld_bgn; i < itm_end; i++) { // see if current itm matches lkp; NOTE: that i < itm_end but will end much earlier (since itm_end includes page text) + byte b = src[i]; + if (b == lkp_dlm) { // fld is done + if (lkp_pos != lkp_len - 1) comp = CompareAble_.More; // lkp has more chars than itm; lkp_dlm reached early + break; + } + lkp_pos = i - fld_bgn; + if (lkp_pos >= lkp_len) { + comp = CompareAble_.Less; // lkp has less chars than itm + break; + } + comp = (lkp[lkp_pos] & 0xff) - (b & 0xff); // subtract src[i] from lkp[lkp_pos] // PATCH.JAVA:need to convert to unsigned byte + if (comp != CompareAble_.Same) break; // if comp != 0 then not equal; break; otherwise if bytes are the same, then comp == 0; + } + if (comp > CompareAble_.Same || (comp == CompareAble_.Same && itm_end - fld_bgn < lkp_len)) {lo = itm_idx; delta = 1;} + else if (comp == CompareAble_.Same) {xdat_itm.Found_exact_y_(); return itm_idx;} + else if (comp < CompareAble_.Same) {hi = itm_idx; delta = -1;} + int itm_dif = hi - lo; +// if (itm_end - 1 > fld_bgn) Tfds.Dbg(comp, itm_dif, String_.new_u8(src, fld_bgn, itm_end - 1)); + switch (itm_dif) { + case 0: return exact ? String_.Find_none : hi; // NOTE: can be 0 when src.length == 1 || 2; also, sometimes 0 in some situations + case -1: + if (flagged) return exact ? String_.Find_none : lo; + else { + itm_idx--; + flagged = true; + } + break; + case 1: + if (flagged) return exact ? String_.Find_none : hi; + else { + itm_idx++; // ++ to always take higher value when !exact???; EX: "ab,ad,af" + if (itm_idx >= itm_ends_len) return String_.Find_none; // NOTE: occurs when there is only 1 item + flagged = true; + } + break; + default: + itm_idx += ((itm_dif / 2) * delta); + break; + } + } + } +} +/* +NOTE_1:xdat format +line 0 : delimited String of article lengths; EX: "00012|00004|00005\n" +line 1+: articles + +pseudo example: (note that ints/dates will be replaced with base85 variants) +== BOF == +00025|00024|00026 +2006-01-01 Ttl1 Abcd +2006-02-01 Ttl2 Abc +2006-03-01 Ttl3 Abcde +== EOF == + +other notes: +. itm_len is entire length of article including text, title, date and any other fields +. line 0 uses len instead of bgn or end b/c len is independent (single len can be changed without having to recalculate entire array) +. however, note that in memory, itm_end_ary will be stored; this will make article extraction quicker: getting nth article means getting nth item in array; +. Parse is written for speed, not correctness; if correctness is needed, write a separate method that validates and call it before calling parse +*/ diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_file_tst.java b/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_file_tst.java index a27517de8..f79797209 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_file_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_file_tst.java @@ -13,3 +13,104 @@ 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.wikis.tdbs.xdats; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import org.junit.*; import gplx.core.strings.*; import gplx.xowa.wikis.tdbs.hives.*; +public class Xob_xdat_file_tst { + @Test public void Find() { + Xob_xdat_file rdr = rdr_("!!!!%|!!!!%|!!!!%|!!!!%|!!!!%|", "0|b", "1|d", "2|f", "3|h", "4|j"); + tst_ReadAt(rdr, 0, "0|b"); + tst_ReadAt(rdr, 1, "1|d"); + tst_ReadAt(rdr, 2, "2|f"); + tst_ReadAt(rdr, 3, "3|h"); + tst_ReadAt(rdr, 4, "4|j"); + tst_Find(rdr, "b", 0, false); + tst_Find(rdr, "j", 4, false); + tst_Find(rdr, "a", 0, false); + tst_Find(rdr, "c", 1, false); + tst_Find(rdr, "k", 4, false); + } + @Test public void Update() { + Xob_xdat_file rdr = rdr_("!!!!%|!!!!%|!!!!%|!!!!%|!!!!%|", "0|b", "1|d", "2|f", "3|h", "4|j"); + tst_Update(rdr, 3, "3|h1\n", String_.Concat_lines_nl_skip_last + ( "!!!!%|!!!!%|!!!!%|!!!!&|!!!!%|" + , "0|b" + , "1|d" + , "2|f" + , "3|h1" + , "4|j" + , "" + )); + } + @Test public void Insert() { + Xob_xdat_file rdr = rdr_("!!!!%|!!!!%|!!!!%|!!!!%|!!!!%|", "0|b", "1|d", "2|f", "3|h", "4|j"); + tst_Insert(rdr, "5|k\n", String_.Concat_lines_nl_skip_last + ( "!!!!%|!!!!%|!!!!%|!!!!%|!!!!%|!!!!%|" + , "0|b" + , "1|d" + , "2|f" + , "3|h" + , "4|j" + , "5|k" + , "" + )); + } + @Test public void Sort() { + Xob_xdat_file rdr = rdr_("!!!!%|!!!!%|!!!!%|!!!!%|!!!!%|", "4|j", "2|f", "0|b", "1|d", "3|h"); + Bry_comparer_bgn_eos comparer = new Bry_comparer_bgn_eos(2); + tst_Sort(rdr, comparer, String_.Concat_lines_nl_skip_last + ( "!!!!%|!!!!%|!!!!%|!!!!%|!!!!%|" + , "0|b" + , "1|d" + , "2|f" + , "3|h" + , "4|j" + , "" + )); + } + @Test public void Rebuild_header() { + String orig = String_.Concat_lines_nl("" , "4|j", "2|f", "0|b", "1|d", "3|h"); + String expd = String_.Concat_lines_nl("!!!!%|!!!!%|!!!!%|!!!!%|!!!!%|" , "4|j", "2|f", "0|b", "1|d", "3|h"); + Rebuild_header_tst(orig, expd); + } + private void Rebuild_header_tst(String orig, String expd) { + Tfds.Eq_str_lines(expd, String_.new_a7(Xob_xdat_file.Rebuid_header(Bry_.new_a7(orig), Bry_.new_a7("\n")))); + } + Bry_bfr tmp = Bry_bfr_.New(); + private void tst_Sort(Xob_xdat_file rdr, gplx.core.lists.ComparerAble comparer, String expd) { + rdr.Sort(tmp, comparer); + Chk_file(rdr, expd); + } + private void tst_Insert(Xob_xdat_file rdr, String new_val, String expd) { + rdr.Insert(tmp, Bry_.new_u8(new_val)); + Chk_file(rdr, expd); + } + private void tst_Update(Xob_xdat_file rdr, int idx, String new_val, String expd) { + Xob_xdat_itm itm = new Xob_xdat_itm(); + rdr.GetAt(itm, idx); + rdr.Update(tmp, itm, Bry_.new_u8(new_val)); + Chk_file(rdr, expd); + } + private void Chk_file(Xob_xdat_file rdr, String expd) { + Io_url url = Io_url_.new_fil_("mem/test.xdat"); + rdr.Save(url); + String actl = Io_mgr.Instance.LoadFilStr(url); + Tfds.Eq_str_lines(expd, actl); + } + private void tst_Find(Xob_xdat_file rdr, String find, int expd, boolean exact) { + rdr.Find(itm, Bry_.new_u8(find), 2, Byte_ascii.Nl, exact); + int id = Bry_.To_int_or(Bry_.Mid(itm.Itm_bry(), 0, 1), -1); + Tfds.Eq(expd, id); + } + private void tst_ReadAt(Xob_xdat_file rdr, int i, String expd) {rdr.GetAt(itm, i); Tfds.Eq(expd, String_.new_u8(itm.Itm_bry()));} + Xob_xdat_itm itm = new Xob_xdat_itm(); + Xob_xdat_file rdr_(String... lines) { + String_bldr sb = String_bldr_.new_(); + int len = lines.length; + for (int i = 0; i < len; i++) { + String line = lines[i]; + sb.Add(line).Add_char_nl(); + } + byte[] bry = Bry_.new_u8(sb.To_str()); + return new Xob_xdat_file().Parse(bry, bry.length, Io_url_.Empty); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_file_wtr.java b/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_file_wtr.java index a27517de8..43448aca0 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_file_wtr.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_file_wtr.java @@ -13,3 +13,134 @@ 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.wikis.tdbs.xdats; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; import gplx.core.encoders.*; import gplx.xowa.wikis.tdbs.*; +public class Xob_xdat_file_wtr { + public static Xob_xdat_file_wtr new_file_(int fil_max, Io_url root_dir) {return new Xob_xdat_file_wtr(fil_max, root_dir, Io_stream_tid_.Tid__raw);} + public static Xob_xdat_file_wtr new_by_tid_(int fil_max, Io_url root_dir, byte dir_tid, byte tid) {return new Xob_xdat_file_wtr(fil_max, root_dir.GenSubDir(Xotdb_dir_info_.Tid_name(dir_tid) + Xotdb_dir_info.Wtr_dir(tid)), tid);} + Xob_xdat_file_wtr(int fil_max, Io_url root_dir, byte wtr_tid) { + this.fil_max = fil_max; + this.root_dir = root_dir; + fil_ext = Xotdb_dir_info.Wtr_ext(wtr_tid); + bfr = Bry_bfr_.New_w_size(fil_max); + idx = new int[fil_max / 8]; // ASSUME: any given row must at least be 8 bytes long + Url_gen(fil_idx); // set 1st url + wtr = Io_stream_wtr_.New_by_tid(wtr_tid); + } int fil_max; Io_stream_wtr wtr; byte[] fil_ext; + public int Fil_idx() {return fil_idx;} public Xob_xdat_file_wtr Fil_idx_(int v) {fil_idx = v; return this;} private int fil_idx; + public int Ns_ord_idx() {return ns_ord_idx;} public Xob_xdat_file_wtr Ns_ord_idx_(int v) {ns_ord_idx = v; return this;} private int ns_ord_idx; // NOTE: optional; needed for page cmd which will flush all wtrs, but needs ns_idx for stats + public Io_url Fil_url() {return fil_url;} + @gplx.Internal protected int[] Idx() {return idx;} private int[] idx; + public int Idx_pos() {return idx_pos;} private int idx_pos; + public Bry_bfr Bfr() {return bfr;} Bry_bfr bfr; + public Xob_xdat_file_wtr Add_idx(byte data_dlm) {return Add_idx_direct(bfr.Len(), data_dlm);} + public Xob_xdat_file_wtr Add_idx_direct(int itm_len, byte data_dlm) { + if (data_dlm != Byte_ascii.Null) { // write closing dlm for data_eny, unless Byte_.Null passed in + bfr.Add_byte(data_dlm); + ++itm_len; + } + if (idx_pos + 1 > idx.length) Idx_resize(idx.length * 2); // resize hdr if necessary + idx[idx_pos++] = itm_len; + return this; + } + public int Fil_len() {return ((idx_pos ) * Len_idx_itm) + bfr.Len();} + public boolean FlushNeeded(int writeLen) {return ((idx_pos + 1) * Len_idx_itm) + bfr.Len() + writeLen > fil_max;} // +1: pending entry will create new idx_itm + public void Flush(Gfo_usr_dlg usr_dlg) { + if (bfr.Len() == 0) return; // nothing to flush + if (this.Fil_len() > fil_max) // NOTE: data can exceed proscribed len; EX: wikt:Category for Italian nouns is 1 MB+ + usr_dlg.Log_many(GRP_KEY, "flush_err", "--ctg exceeds len: expd_len=~{0} actl_len=~{1} url=~{2}", this.Fil_len(), fil_max, fil_url.Xto_api()); + try { + wtr.Url_(fil_url).Open(); + if (idx_pos > 0) // write idx; NOTE: if idx written, then .xdat; else .csv + FlushIdx(wtr); + wtr.Write(bfr.Bfr(), 0, bfr.Len()); // write data; + wtr.Flush(); + } + finally {wtr.Rls();} + Clear(); + this.Url_gen(++fil_idx); + } + public void FlushIdx(Io_stream_wtr wtr) { + int idx_bry_len = (idx_pos * Len_idx_itm) + 1; // 1=\n.length + byte[] idx_bry = new byte[idx_bry_len]; + int prv_pos = 0; // NOTE: prv_pos needed b/c idx[] stores data_end, not data_len + for (int i = 0; i < idx_pos; i++) { + int idx_bry_pos = i * Len_idx_itm; + int cur_pos = idx[i]; + Base85_.Set_bry(cur_pos - prv_pos, idx_bry, idx_bry_pos, Len_base85); + idx_bry[idx_bry_pos + Len_base85] = Dlm_fld; + prv_pos = cur_pos; + } + idx_bry[idx_bry_len - 1] = Byte_ascii.Nl; + wtr.Write(idx_bry, 0, idx_bry_len); + } +// public void Flush(Gfo_usr_dlg usr_dlg) { +// if (bfr.Len() == 0) return; // nothing to flush +// if (this.Fil_len() > fil_max) // NOTE: data can exceed proscribed len; EX: wikt:Category for Italian nouns is 1 MB+ +// usr_dlg.Log_many(GRP_KEY, "flush_err", "--ctg exceeds len: expd_len=~{0} actl_len=~{1} url=~{2}", this.Fil_len(), fil_max, fil_url.Xto_api()); +// IoStream stream = IoStream_.Null; +// try { +// stream = Io_mgr.Instance.OpenStreamWrite(fil_url); +// if (idx_pos > 0) // write idx; NOTE: if idx written, then .xdat; else .csv +// FlushIdx(stream); +// stream.Write(bfr.Bry(), 0, bfr.Len()); // write data; +// } +// finally {stream.Rls();} +// Clear(); +// this.Url_gen(++fil_idx); +// } +// public void FlushIdx(IoStream stream) { +// int idx_bry_len = (idx_pos * Len_idx_itm) + 1; // 1=\n.length +// byte[] idx_bry = new byte[idx_bry_len]; +// int prv_pos = 0; // NOTE: prv_pos needed b/c idx[] stores data_end, not data_len +// for (int i = 0; i < idx_pos; i++) { +// int idx_bry_pos = i * Len_idx_itm; +// int cur_pos = idx[i]; +// Base85_.Set_bry(cur_pos - prv_pos, idx_bry, idx_bry_pos, Len_base85); +// idx_bry[idx_bry_pos + Len_base85] = Dlm_idx; +// prv_pos = cur_pos; +// } +// idx_bry[idx_bry_len - 1] = Byte_ascii.Nl; +// stream.Write(idx_bry, 0, idx_bry_len); +// } + static final int Len_idx_itm = 6, Len_base85 = 5; + public void Clear() {idx_pos = 0; bfr.Clear();} + public void Rls() {bfr.Rls(); idx = null;} + public void Url_gen_add() {Url_gen(++fil_idx);} + private void Url_gen(int newIdx) {fil_url = Xotdb_fsys_mgr.Url_fil(root_dir, newIdx, fil_ext);} Io_url fil_url; Io_url root_dir; + private void Idx_resize(int newLen) {idx = (int[])Array_.Resize(idx, newLen);} + static final String GRP_KEY = "xowa.bldr.xdat_wtr"; + private static final byte Dlm_fld = Byte_ascii.Pipe; +} +class SortAlgo_quick {// quicksort + Object[] ary; int ary_len; gplx.core.lists.ComparerAble comparer; + public void Sort(Object[] ary, int ary_len, gplx.core.lists.ComparerAble comparer) { + if (ary == null || ary_len < 2) return; + this.ary = ary; this.ary_len = ary_len; this.comparer = comparer; + Sort_recurse(0, ary_len - 1); + } + private void Sort_recurse(int lo, int hi) { + int i = lo, j = hi; + int mid_idx = lo + (hi-lo)/2; + Object mid = ary[mid_idx]; // get mid itm + while (i <= j) { // divide into two lists + while (comparer.compare(ary[i], mid) == CompareAble_.Less) // if lhs.cur < mid, then get next from lhs + i++; + while (comparer.compare(ary[j], mid) == CompareAble_.More) // if rhs.cur > mid, then get next from rhs + j--; + + // lhs.cur > mid && rhs.cur < mid; switch lhs.cur and rhs.cur; increase i and j + if (i <= j) { + Object tmp = ary[i]; + ary[i] = ary[j]; + ary[j] = tmp; + i++; + j--; + } + } + if (lo < j) Sort_recurse(lo, j); + if (i < hi) Sort_recurse(i, hi); + } + public static final SortAlgo_quick Instance = new SortAlgo_quick(); SortAlgo_quick() {} +} + diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_file_wtr_tst.java b/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_file_wtr_tst.java index a27517de8..4e86b470f 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_file_wtr_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_file_wtr_tst.java @@ -13,3 +13,40 @@ 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.wikis.tdbs.xdats; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +import org.junit.*; +public class Xob_xdat_file_wtr_tst { + @Test public void Write() { + Io_mgr.Instance.InitEngine_mem(); + Io_url dir = Io_url_.mem_dir_("mem/dir"); + Xob_xdat_file_wtr wtr = Xob_xdat_file_wtr.new_file_(1000, dir); + tst_Write(wtr, "<1", "<1"); + tst_Write(wtr, ">a", "<1>a"); + tst_Write(wtr, ">b", "<1>a>b"); + tst_Add_idx(wtr, 7); + wtr.Bfr().Add(Bry_.new_a7("<2>b>cc")); + tst_Add_idx(wtr, 15); + wtr.Bfr().Add(Bry_.new_a7("<3>c>dd")); + tst_Add_idx(wtr, 23); + tst_Flush(wtr, String_.Concat + ( "!!!!(|!!!!)|!!!!)|\n" + , "<1>a>b\n" + , "<2>b>cc\n" + , "<3>c>dd\n" + )); + } + private void tst_Write(Xob_xdat_file_wtr wtr, String val, String expd) { + wtr.Bfr().Add(Bry_.new_u8(val)); + Tfds.Eq(expd, String_.new_u8(wtr.Bfr().Bfr(), 0, wtr.Bfr().Len())); + } + private void tst_Add_idx(Xob_xdat_file_wtr wtr, int expd) { + wtr.Add_idx(Byte_ascii.Nl); + Tfds.Eq(expd, wtr.Idx()[wtr.Idx_pos() - 1]); + } + private void tst_Flush(Xob_xdat_file_wtr wtr, String expd) { + Io_url url = wtr.Fil_url(); + wtr.Flush(Gfo_usr_dlg_.Test()); + String actl = Io_mgr.Instance.LoadFilStr(url); + Tfds.Eq(expd, actl); + } +} diff --git a/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_itm.java b/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_itm.java index a27517de8..9d2f14751 100644 --- a/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_itm.java +++ b/400_xowa/src/gplx/xowa/wikis/tdbs/xdats/Xob_xdat_itm.java @@ -13,3 +13,15 @@ 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.wikis.tdbs.xdats; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +public class Xob_xdat_itm { + public byte[] Src() {return src;} public Xob_xdat_itm Src_(byte[] v) {src = v; return this;} private byte[] src; + public int Itm_bgn() {return itm_bgn;} public Xob_xdat_itm Itm_bgn_(int v) {itm_bgn = v; return this;} private int itm_bgn = -1; + public int Itm_end() {return itm_end;} public Xob_xdat_itm Itm_end_(int v) {itm_end = v; return this;} private int itm_end = -1; + public int Itm_idx() {return itm_idx;} public Xob_xdat_itm Itm_idx_(int v) {itm_idx = v; return this;} private int itm_idx = -1; + public void Clear() {itm_bgn = itm_end = itm_idx = -1; src = null; found_exact = false;} + public boolean Found_exact() {return found_exact;} private boolean found_exact; + public Xob_xdat_itm Found_exact_y_() {found_exact = true; return this;} + public boolean Missing() {return itm_bgn == -1;} + public byte[] Itm_bry() {return Bry_.Mid(src, itm_bgn, itm_end);} +} diff --git a/400_xowa/src/gplx/xowa/wikis/ttls/Xoa_ttl__err_tst.java b/400_xowa/src/gplx/xowa/wikis/ttls/Xoa_ttl__err_tst.java index a27517de8..7d15326a7 100644 --- a/400_xowa/src/gplx/xowa/wikis/ttls/Xoa_ttl__err_tst.java +++ b/400_xowa/src/gplx/xowa/wikis/ttls/Xoa_ttl__err_tst.java @@ -13,3 +13,19 @@ 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.wikis.ttls; import gplx.*; import gplx.xowa.*; import gplx.xowa.wikis.*; +import org.junit.*; +public class Xoa_ttl__err_tst { + @Before public void init() {fxt.Clear();} private final Xoa_ttl_fxt fxt = new Xoa_ttl_fxt(); + @Test public void Invalid__angle() {fxt.Parse("").Expd_page_txt("Ab").Test();} + @Test public void Comment_eos() {fxt.Init_ttl("Ab will have key of "" + , "" + ); + fxt.Test__html(fxt.Make__html__itms__null("B", "A")); + } + @Test public void Error_skip_line() { // PURPOSE: error should skip rest of line; was failing with array out of bounds; en.n:Portal:Austria/Wikipedia; DATE:2014-01-18 + fxt.Init__warns("dynamic_page_list:unknown_key: page=Test page key=Ctg_0 order"); // ignore warning message + fxt.Exec__parse(" category=Ctg_0 order=descending"); + fxt.Test__html("No pages meet these criteria."); + } + @Test public void Atr_has_template() { // PURPOSE: attribute also has template; DATE:2014-01-31 + fxt.Init__create_ctg("Test_page", "B", "A"); + fxt.Exec__parse + ( "" + , "category={{PAGENAME}}" + , "" + ); + fxt.Test__html(fxt.Make__html__itms__null("B", "A")); + } + @Test public void Err_page_ns_doesnt_exist() {// PURPOSE: check that is not enabled if wiki does not have Page / Index ns; PAGE:fr.w:Wikipedia:Le_Bistro/novembre_2006 DATE:2014-11-28 + // reset categories to (a) remove ns for Page / Index; (b) add back Category (else null error) + fxt.Wiki().Ns_mgr().Clear(); + fxt.Wiki().Ns_mgr().Add_new(14, "Category").Init(); + + fxt.Wiki().Cfg_parser().Xtns().Itm_pages().Reset(); // must reset to clear cached valid ns_page from previous tests + fxt.Fxt().Test_parse_page_wiki_str("category=a", "No pages meet these criteria."); + fxt.Wiki().Cfg_parser().Xtns().Itm_pages().Reset(); // must reset to clear cached invalid ns_page for next tests + + // reset categories for rest of tests + fxt.Wiki().Ns_mgr().Add_new(0, "").Init(); // call .Clear() to remove ns for Page / Index + } + @Test public void Ordermethod__invalid() { // PURPOSE: do not fail if ordermethod is invalid; PAGE:sr.d:Викиречник:Википројекат_1001_арапска_реч/Списак_уноса; DATE:2015-10-16 + fxt.Init__create_ctg("Ctg_0", "A", "B", "C"); + fxt.Exec__parse + ( "" + , "category=Ctg_0" + , "ordermethod=ascending" // should not throw error + , "ordermethod=sortkey" + , "" + ); + fxt.Test__html(fxt.Make__html__itms__null("A", "B", "C")); + } + @Test public void Encode_spaces() {// PURPOSE:encode spaces in href; PAGE:en.q:Wikiquote:Speedy_deletions DATE:2016-01-19 + fxt.Init__create_ctg("Ctg_0", "A B"); + fxt.Exec__parse + ( "" + , "category=Ctg_0" + , "nofollow=true" + , "" + ); + fxt.Test__html(Gfh_utl.Replace_apos_concat_lines + ( "
        " + , "
      • A B
      • " // "/wiki/A_B" not "/wiki/A B" + , "
      " + )); + } + @Test public void Encode_quotes() {// PURPOSE:encode quotes; PAGE:en.b:Wikibooks:Alphabetical_classification/All_Books; DATE:2016-01-21 + fxt.Init__create_ctg("Ctg_0", "A\"B"); + fxt.Exec__parse + ( "" + , "category=Ctg_0" + , "nofollow=true" + , "" + ); + fxt.Test__html(Gfh_utl.Replace_apos_concat_lines + ( "
        " + , "
      • A"B
      • " // "/wiki/A_B" not "/wiki/A B" + , "
      " + )); + } + @Test public void Err__bad_key_causes_out_of_bound() { // PURPOSE: bad key causes out of bounds error; PAGE:de.n:Portal:Brandenburg DATE:2016-04-21 + fxt.Exec__parse + ( "" + , "category=Aa=b c=d" + , "category=B" + ); + fxt.Test__html(String_.Concat_lines_nl_skip_last + ( "<DynamicPageList>" + , "No pages meet these criteria.a=b c=d" + , "No pages meet these criteria." + )); + } +} +class Dpl_page_mok { + public Dpl_page_mok(int id, String ttl) {this.id = id; this.ttl = ttl;} + public int Id() {return id;} private int id; + public String Ttl() {return ttl;} private String ttl; + public static Dpl_page_mok new_(int id, String ttl) {return new Dpl_page_mok(id, ttl);} +} +class Dpl_xnde_fxt { + private final Xop_fxt fxt = new Xop_fxt(); + private final Bry_bfr bfr = Bry_bfr_.New(); + private String exec__raw; + private int next_id; + public void Clear() { + next_id = 100; + fxt.Reset(); + this.exec__raw = String_.Empty; + this.expd_warns = String_.Ary_empty; + fxt.App().Usr_dlg().Gui_wkr().Clear(); + fxt.Wiki().Hive_mgr().Clear(); + fxt.Wiki().Db_mgr().Save_mgr().Clear(); // NOTE: must clear to reset next_id to 0; Init__create_ctg assumes clean slate from 0 + fxt.Wiki().Xtn_mgr().Xtn_proofread().Enabled_y_(); + fxt.Wiki().Db_mgr().Load_mgr().Clear(); // must clear; otherwise fails b/c files get deleted, but wiki.data_mgr caches the Xowd_regy_mgr (the .reg file) in memory; + fxt.Wiki().Ns_mgr().Add_new(Xowc_xtn_pages.Ns_page_id_default, "Page").Add_new(Xowc_xtn_pages.Ns_index_id_default, "Index").Init(); + Io_mgr.Instance.InitEngine_mem(); + } + public Xowe_wiki Wiki() {return fxt.Wiki();} + public Xop_fxt Fxt() {return fxt;} + public void Init__warns(String... v) {expd_warns = v;} private String[] expd_warns; + public void Init__create_ctg(String ctg, String... ttls) { + int len = ttls.length; + Dpl_page_mok[] ary = new Dpl_page_mok[len]; + for (int i = 0; i < len; i++) + ary[i] = Dpl_page_mok.new_(++next_id, ttls[i]); + Init__create_ctg_pages(ctg, ary); + } + public void Init__create_ctg_pages(String ctg_ttl, Dpl_page_mok... pages) { + Xoctg_catpage_ctg ctg = new Xoctg_catpage_ctg(1, Bry_.new_u8(ctg_ttl)); + int pages_len = pages.length; + Xoctg_catpage_tmp tmp = new Xoctg_catpage_tmp(); + for (int i = 0; i < pages_len; i++) { + Dpl_page_mok page = pages[i]; + int id = page.Id(); + String ttl_str = page.Ttl(); + Xoa_ttl ttl = fxt.Wiki().Ttl_parse(Bry_.new_u8(ttl_str)); + Xoae_page page_obj = fxt.Wiki().Data_mgr().Load_page_by_ttl(ttl); + if (page_obj.Db().Page().Exists_n()) { + fxt.Init_page_create(ttl_str, ttl_str); + fxt.Init_id_create (id, 0, 0, false, 5, Xow_ns_.Tid__main, ttl_str); + } + byte tid = gplx.xowa.addons.wikis.ctgs.Xoa_ctg_mgr.Tid__page; + Xoctg_catpage_itm catpage_itm = Xoctg_catpage_itm.New_by_ttl(tid, page.Id(), ttl); + tmp.Add(catpage_itm); + } + tmp.Make_by_ctg(ctg); + Xoctg_catpage_mgr catpage_mgr = fxt.Wiki().Ctg__catpage_mgr(); + catpage_mgr.Cache__add(Bry_.new_u8("Category:" + ctg_ttl), ctg); + } + public String Make__html__itms__null(String... pages) {return this.Make__html(null, pages);} + public String Make__html(String itm_html, String... pages) { + bfr.Add_str_a7("
        ").Add_byte_nl(); + int pages_len = pages.length; + for (int i = 0; i < pages_len; i++) { + String page = pages[i]; + bfr.Add_str_a7("
      • ").Add_str_a7(page).Add_str_a7("
      • ").Add_byte_nl(); + } + bfr.Add_str_a7("
      ").Add_byte_nl(); + return bfr.To_str_and_clear(); + } + public void Exec__parse(String... raw_lines) {this.exec__raw = String_.Concat_lines_nl_skip_last(raw_lines);} + public void Test__html(String expd) { + fxt.Test_parse_page_wiki_str(exec__raw, expd); + fxt.tst_Warn(expd_warns); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Flagged_revs_xtn_mgr.java b/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Flagged_revs_xtn_mgr.java index a27517de8..ab346b462 100644 --- a/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Flagged_revs_xtn_mgr.java +++ b/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Flagged_revs_xtn_mgr.java @@ -13,3 +13,13 @@ 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.flaggedRevs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; +import gplx.xowa.xtns.scribunto.*; import gplx.xowa.xtns.flaggedRevs.scribunto.*; +public class Flagged_revs_xtn_mgr extends Xox_mgr_base { + @Override public byte[] Xtn_key() {return XTN_KEY;} public static final byte[] XTN_KEY = Bry_.new_a7("FlaggedRevs"); + @Override public Xox_mgr Xtn_clone_new() {return new Flagged_revs_xtn_mgr();} + @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 Flagged_revs_lib()); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Pages_using_pending_changes_func.java b/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Pages_using_pending_changes_func.java index a27517de8..afc7a4788 100644 --- a/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Pages_using_pending_changes_func.java +++ b/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Pages_using_pending_changes_func.java @@ -13,3 +13,15 @@ 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.flaggedRevs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.kwds.*; +import gplx.xowa.htmls.*; import gplx.xowa.wikis.pages.skins.*; import gplx.xowa.xtns.pfuncs.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*; +public class Pages_using_pending_changes_func extends Pf_func_base { + @Override public int Id() {return Xol_kwd_grp_.Id_pagesUsingPendingChanges;} + @Override public Pf_func New(int id, byte[] name) {return new Pages_using_pending_changes_func().Name_(name);} + @Override public void Func_evaluate(Bry_bfr bfr, Xop_ctx ctx, Xot_invk caller, Xot_invk self, byte[] src) { + bfr.Add_int_fixed(0, 1); + } + public static final Pages_using_pending_changes_func Instance = new Pages_using_pending_changes_func(); Pages_using_pending_changes_func() {} +} diff --git a/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Pages_using_pending_changes_func_tst.java b/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Pages_using_pending_changes_func_tst.java index a27517de8..f57048f0f 100644 --- a/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Pages_using_pending_changes_func_tst.java +++ b/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Pages_using_pending_changes_func_tst.java @@ -13,3 +13,11 @@ 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.flaggedRevs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; +import org.junit.*; import gplx.xowa.wikis.pages.skins.*; +public class Pages_using_pending_changes_func_tst { + @Before public void init() {fxt.Reset();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Basic() { + fxt.Test_html_full_str("{{PAGESUSINGPENDINGCHANGES}}", "0"); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Pending_change_level_func.java b/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Pending_change_level_func.java index a27517de8..a43e045b3 100644 --- a/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Pending_change_level_func.java +++ b/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Pending_change_level_func.java @@ -13,3 +13,13 @@ 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.flaggedRevs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.kwds.*; +import gplx.xowa.htmls.*; import gplx.xowa.wikis.pages.skins.*; import gplx.xowa.xtns.pfuncs.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*; +public class Pending_change_level_func extends Pf_func_base { + @Override public int Id() {return Xol_kwd_grp_.Id_pendingChangeLevel;} + @Override public Pf_func New(int id, byte[] name) {return new Pending_change_level_func().Name_(name);} + @Override public void Func_evaluate(Bry_bfr bfr, Xop_ctx ctx, Xot_invk caller, Xot_invk self, byte[] src) {}// NOOP + public static final Pending_change_level_func Instance = new Pending_change_level_func(); Pending_change_level_func() {} +} diff --git a/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Pending_change_level_func_tst.java b/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Pending_change_level_func_tst.java index a27517de8..8b5a02a77 100644 --- a/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Pending_change_level_func_tst.java +++ b/400_xowa/src/gplx/xowa/xtns/flaggedRevs/Pending_change_level_func_tst.java @@ -13,3 +13,11 @@ 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.flaggedRevs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; +import org.junit.*; import gplx.xowa.wikis.pages.skins.*; +public class Pending_change_level_func_tst { + @Before public void init() {fxt.Reset();} private final Xop_fxt fxt = new Xop_fxt(); + @Test public void Basic() { + fxt.Test_html_full_str("{{PENDINGCHANGELEVEL}}", ""); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/flaggedRevs/scribunto/Flagged_revs_lib.java b/400_xowa/src/gplx/xowa/xtns/flaggedRevs/scribunto/Flagged_revs_lib.java index a27517de8..b46ebf908 100644 --- a/400_xowa/src/gplx/xowa/xtns/flaggedRevs/scribunto/Flagged_revs_lib.java +++ b/400_xowa/src/gplx/xowa/xtns/flaggedRevs/scribunto/Flagged_revs_lib.java @@ -13,3 +13,49 @@ 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.flaggedRevs.scribunto; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.flaggedRevs.*; +import gplx.xowa.xtns.scribunto.*; import gplx.xowa.xtns.scribunto.libs.*; +import gplx.xowa.xtns.pfuncs.exprs.*; +import gplx.xowa.xtns.scribunto.procs.*; +public class Flagged_revs_lib implements Scrib_lib { + 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;} + public Scrib_lib Core_(Scrib_core v) {this.core = v; return this;} // TEST: + public Scrib_lib Clone_lib(Scrib_core core) {return new Flagged_revs_lib();} + public Scrib_lua_mod Register(Scrib_core core, Io_url script_dir) { + this.core = core; + Init(); + mod = core.RegisterInterface(this, core.App().Fsys_mgr().Bin_xtns_dir().GenSubFil_nest("FlaggedRevs", "scribunto", "mw.ext.FlaggedRevs.lua")); + return mod; + } + public Scrib_proc_mgr Procs() {return procs;} private final Scrib_proc_mgr procs = new Scrib_proc_mgr(); + public boolean Procs_exec(int key, Scrib_proc_args args, Scrib_proc_rslt rslt) { + switch (key) { + case Proc_getStabilitySettings: return GetStabilitySettings(args, rslt); + default: throw Err_.new_unhandled(key); + } + } + private static final int Proc_getStabilitySettings = 0; + public static final String Invk_getStabilitySettings = "getStabilitySettings"; + private static final String[] Proc_names = String_.Ary(Invk_getStabilitySettings); + public boolean GetStabilitySettings(Scrib_proc_args args, Scrib_proc_rslt rslt) { + byte[] page_name = args.Cast_bry_or_null(0); + Xoa_ttl page_ttl = null; + if (page_name == null) + page_ttl = core.Ctx().Page().Ttl(); + else { + page_ttl = core.Wiki().Ttl_parse(page_name); + if (page_ttl == null) return rslt.Init_null(); + } + // if ( !FlaggedRevs::inReviewNamespace( $title ) ) return rslt.Init_null(); + return rslt.Init_obj(getDefaultVisibilitySettings()); + } + private static Keyval[] getDefaultVisibilitySettings() { + Keyval[] rv = new Keyval[3]; + rv[0] = Keyval_.new_("over"+"ride", 0); // FlaggedRevs::isStableShownByDefault() ? 1 : 0, + rv[1] = Keyval_.new_("autoreview", ""); + rv[2] = Keyval_.new_("expiry", "infinity"); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/flaggedRevs/scribunto/Flagged_revs_lib_tst.java b/400_xowa/src/gplx/xowa/xtns/flaggedRevs/scribunto/Flagged_revs_lib_tst.java index a27517de8..eaf1a3110 100644 --- a/400_xowa/src/gplx/xowa/xtns/flaggedRevs/scribunto/Flagged_revs_lib_tst.java +++ b/400_xowa/src/gplx/xowa/xtns/flaggedRevs/scribunto/Flagged_revs_lib_tst.java @@ -13,3 +13,23 @@ 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.flaggedRevs.scribunto; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.flaggedRevs.*; +import org.junit.*; +import gplx.xowa.xtns.scribunto.*; import gplx.xowa.xtns.scribunto.libs.*; import gplx.xowa.xtns.scribunto.engines.mocks.*; +public class Flagged_revs_lib_tst { + private final Mock_scrib_fxt fxt = new Mock_scrib_fxt(); private Flagged_revs_lib lib; + @Before public void init() { + fxt.Clear(); + lib = new Flagged_revs_lib(); + lib.Init(); + lib.Core_(fxt.Core()); + } + @Test public void GetStabilitySettings() { + fxt.Test__proc__objs__nest(lib, Flagged_revs_lib.Invk_getStabilitySettings, Object_.Ary("Page1"), String_.Concat_lines_nl_skip_last + ( "1=" + , " over"+"ride=0" + , " autoreview=" + , " expiry=infinity" + )); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_box_w_fmtr_arg.java b/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_box_w_fmtr_arg.java index a27517de8..7cc2a16a5 100644 --- a/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_box_w_fmtr_arg.java +++ b/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_box_w_fmtr_arg.java @@ -13,3 +13,14 @@ 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.gallery; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; +class Gallery_box_w_fmtr_arg implements gplx.core.brys.Bfr_arg { + private final int width; + public Gallery_box_w_fmtr_arg(int width) {this.width = width;} + public void Bfr_arg__add(Bry_bfr bfr) { + bfr.Add(Style_bgn); + bfr.Add_int_variable(width); + bfr.Add(Style_end); + } + private static final byte[] Style_bgn = Bry_.new_a7("style=\"width:"), Style_end = Bry_.new_a7("px;\""); +} diff --git a/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_img_pad_fmtr_arg.java b/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_img_pad_fmtr_arg.java index a27517de8..767fc254b 100644 --- a/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_img_pad_fmtr_arg.java +++ b/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_img_pad_fmtr_arg.java @@ -13,3 +13,14 @@ 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.gallery; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; +class Gallery_img_pad_fmtr_arg implements gplx.core.brys.Bfr_arg { + private final int vpad; + public Gallery_img_pad_fmtr_arg(int vpad) {this.vpad = vpad;} + public void Bfr_arg__add(Bry_bfr bfr) { + bfr.Add(Style_bgn); + bfr.Add_int_variable(vpad); + bfr.Add(Style_end); + } + private static final byte[] Style_bgn = Bry_.new_a7("style=\"margin:"), Style_end = Bry_.new_a7("px auto;\""); +} diff --git a/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_itm.java b/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_itm.java index a27517de8..87cdcd30c 100644 --- a/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_itm.java +++ b/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_itm.java @@ -13,3 +13,50 @@ 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.gallery; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; +import gplx.xowa.files.*; import gplx.xowa.guis.cbks.js.*; import gplx.xowa.htmls.core.htmls.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.lnkis.*; +public class Gallery_itm implements Js_img_wkr { + private Gallery_xnde xnde; private Xof_file_itm xfer_itm; private Xowe_wiki wiki; private Xop_ctx ctx; private byte[] src; private byte[] gallery_li_id_bry; private int gallery_itm_idx; + public Xoa_ttl Ttl() {return ttl;} public Gallery_itm Ttl_(Xoa_ttl v) {ttl = v; return this;} private Xoa_ttl ttl; + public int Ttl_bgn() {return ttl_bgn;} public Gallery_itm Ttl_bgn_(int v) {ttl_bgn = v; return this;} private int ttl_bgn; + public int Ttl_end() {return ttl_end;} public Gallery_itm Ttl_end_(int v) {ttl_end = v; return this;} private int ttl_end; + public Xof_ext Ext() {return ext;} public Gallery_itm Ext_(Xof_ext v) {ext = v; return this;} private Xof_ext ext; + public byte[] Caption_bry() {return caption_bry;} public Gallery_itm Caption_bry_(byte[] v) {caption_bry = v; return this;} private byte[] caption_bry; + public Xop_root_tkn Caption_tkn() {return caption_tkn;} public Gallery_itm Caption_tkn_(Xop_root_tkn v) {caption_tkn = v; return this;} private Xop_root_tkn caption_tkn; + public int Alt_bgn() {return alt_bgn;} public Gallery_itm Alt_bgn_(int v) {alt_bgn = v; return this;} private int alt_bgn; + public int Alt_end() {return alt_end;} public Gallery_itm Alt_end_(int v) {alt_end = v; return this;} private int alt_end; + public int Link_bgn() {return link_bgn;} public Gallery_itm Link_bgn_(int v) {link_bgn = v; return this;} private int link_bgn; + public int Link_end() {return link_end;} public Gallery_itm Link_end_(int v) {link_end = v; return this;} private int link_end; + public int Page_bgn() {return page_bgn;} public Gallery_itm Page_bgn_(int v) {page_bgn = v; return this;} private int page_bgn; + public int Page_end() {return page_end;} public Gallery_itm Page_end_(int v) {page_end = v; return this;} private int page_end; + public Xop_lnki_tkn Lnki_tkn() {return lnki_tkn;} public Gallery_itm Lnki_tkn_(Xop_lnki_tkn v) {lnki_tkn = v; return this;} private Xop_lnki_tkn lnki_tkn; + public Gallery_itm Reset() { + ttl = null; + ttl_bgn = ttl_end = alt_bgn = alt_end = link_bgn = link_end = page_bgn = page_end = Bry_find_.Not_found; + caption_bry = null; // NOTE: use null instead of ""; more legible tests + caption_tkn = null; + ext = null; + return this; + } + + public void Html_prepare(Xowe_wiki wiki, Xop_ctx ctx, byte[] src, Gallery_xnde xnde, Xof_file_itm xfer_itm, byte[] gallery_li_id_bry, int gallery_itm_idx) { + this.xnde = xnde; this.xfer_itm = xfer_itm; + this.wiki = wiki; this.ctx = ctx; this.src = src; this.gallery_li_id_bry = gallery_li_id_bry; this.gallery_itm_idx = gallery_itm_idx; + } + public void Js_wkr__update_hdoc(Xoa_page page, Xog_js_wkr js_wkr, int html_uid, int html_w, int html_h, Io_url html_view_url, int orig_w, int orig_h, Xof_ext orig_ext, Io_url html_orig_url, byte[] lnki_ttl) { + Gallery_mgr_base gallery_mgr = xnde.Gallery_mgr(); + Bry_bfr bfr = wiki.Utl__bfr_mkr().Get_k004(), tmp_bfr = wiki.Utl__bfr_mkr().Get_k004(); + try { + xfer_itm.Init_at_gallery_end(html_w, html_h, html_view_url, html_orig_url); + Gallery_mgr_wtr.Write_itm(bfr, tmp_bfr, wiki.Appe(), wiki, ctx.Page(), ctx, wiki.Html_mgr().Html_wtr(), Xoh_wtr_ctx.Basic, src, gallery_mgr, xnde, gallery_itm_idx, xfer_itm); + String itm_html = bfr.To_str_and_clear(); + js_wkr.Html_elem_replace_html(String_.new_u8(gallery_li_id_bry), itm_html); + if (gallery_itm_idx == xnde.Itms_len() - 1 && Gallery_mgr_base_.Mode_is_packed(xnde.Mode())) + page.Html_data().Xtn_gallery_packed_exists_y_(); // set flag for packed_gallery; don't fire multiple times; PAGE:en.w:National_Sculpture_Museum_(Valladolid); DATE:2014-07-21 + } + finally { + bfr.Mkr_rls(); tmp_bfr.Mkr_rls(); + } + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_mgr_base.java b/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_mgr_base.java index a27517de8..4421c215a 100644 --- a/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_mgr_base.java +++ b/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_mgr_base.java @@ -13,3 +13,53 @@ 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.gallery; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; +import gplx.xowa.files.*; +import gplx.xowa.parsers.lnkis.*; +public abstract class Gallery_mgr_base { + public abstract byte Tid(); + public int Itm_default_w() {return itm_default_w;} protected int itm_default_w; + public int Itm_default_h() {return itm_default_h;} protected int itm_default_h; + public int Itms_per_row() {return itms_per_row;} protected int itms_per_row; + @gplx.Virtual public int Get_all_padding() {return this.Get_thumb_padding() + this.Get_gb_padding() + this.Get_gb_borders();} // REF.MW: getAllPadding; How many pixels of whitespace surround the thumbnail. + @gplx.Virtual public int Get_gb_padding() {return 5;} // REF.MW: getGBPadding; GB stands for gallerybox (as in the
    • element) + @gplx.Virtual public int Get_gb_borders() {return 8;} // REF.MW: getGBBorders; Get how much extra space the borders around the image takes up. For this mode, it is 2px borders on each side + 2px implied padding on each side from the stylesheet, giving us 2*2+2*2 = 8. + @gplx.Virtual public int Get_gb_width(int thm_w, int thm_h) {// REF.MW: getGBWidth; Width of gallerybox
    • . Generally is the width of the image, plus padding on image plus padding on gallerybox.s + return itm_default_w + this.Get_thumb_padding() + this.Get_gb_padding(); + } + @gplx.Virtual public int Get_vpad(int xnde_h, int html_h) { // REF.MW: getVPad; Get vertical padding for a thumbnail; Generally this is the total height minus how high the thumb is. + if (html_h == -1) html_h = xnde_h; // NOTE: html_h will be -1 on 1st pass; set to dflt_h, else will end up with 115px for hdump; PAGE:en.w:National_Gallery_of_Art DATE:2016-06-19 + return (this.Get_thumb_padding() + xnde_h - html_h) / 2; + } + @gplx.Virtual public int Get_thumb_padding() {return 30;} // REF.MW: getThumbPadding; How much padding such the thumb have between image and inner div that that contains the border. This is both for verical and horizontal padding. (However, it is cut in half in the vertical direction). + @gplx.Virtual public int Get_thumb_div_width(int thm_w) { // REF.MW: getThumbDivWidth; Get the width of the inner div that contains the thumbnail in question. This is the div with the class of "thumb". + return itm_default_w + this.Get_thumb_padding(); + } + @gplx.Virtual public void Get_thumb_size(Xop_lnki_tkn lnki, Xof_ext ext) { // REF.MW: getThumbParams; Get the transform parameters for a thumbnail. + lnki.W_(itm_default_w); + lnki.H_(itm_default_h); + } + @gplx.Virtual public void Get_modules(Xoae_page page) { // REF.MW: getModules; Get a list of modules to include in the page. + page.Html_data().Head_mgr().Itm__gallery_styles().Enabled_y_(); // enable styles or some galleries will show up as list items; PAGE:s.w:Gothic_architecture DATE:2015-11-06 + } + @gplx.Virtual public void Init(int itm_default_w, int itm_default_h, int itms_per_row) { + this.itm_default_w = itm_default_w; + this.itm_default_h = itm_default_h; + this.itms_per_row = itms_per_row; + } + @gplx.Virtual public void Adjust_image_parameters(Xof_file_itm xfer_itm) {} // REF.MW: Adjust the image parameters for a thumbnail. Used by a subclass to insert extra high resolution images. + @gplx.Virtual public void Wrap_gallery_text(Bry_bfr bfr, byte[] gallery_text, int thm_w, int thm_h) { + bfr .Add_str_a7("\n
      ") // NOTE: The newline after
      is needed to accommodate htmltidy + .Add(gallery_text) + .Add_str_a7("\n
      "); // NOTE: prepend "\n"; will cause extra \n when caption exists, but needed when caption doesn't exists; EX: "
      "; \n puts + } +} +class Gallery_mgr_traditional extends Gallery_mgr_base { + @Override public byte Tid() {return Gallery_mgr_base_.Tid__traditional;} +} +class Gallery_mgr_nolines extends Gallery_mgr_base { + @Override public byte Tid() {return Gallery_mgr_base_.Tid__nolines;} + @Override public int Get_thumb_padding() {return 0;} + @Override public int Get_gb_padding() {return 4;} // This accounts for extra space between
    • elements. + @Override public int Get_vpad(int itm_h, int thm_h) {return 0;} +} diff --git a/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_mgr_base_.java b/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_mgr_base_.java index a27517de8..0e7bd9769 100644 --- a/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_mgr_base_.java +++ b/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_mgr_base_.java @@ -13,3 +13,61 @@ 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.gallery; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; +public class Gallery_mgr_base_ { + public static final byte + Tid__traditional = 0 + , Tid__nolines = 1 + , Tid__packed = 2 + , Tid__packed__hover = 3 + , Tid__packed__overlay = 4 + ; + private static final byte[] + Bry__traditional = Bry_.new_a7("traditional") + , Bry__nolines = Bry_.new_a7("nolines") + , Bry__packed = Bry_.new_a7("packed") + , Bry__packed__hover = Bry_.new_a7("packed-hover") + , Bry__packed__overlay = Bry_.new_a7("packed-overlay") + ; + private static final Hash_adp_bry bry_hash = Hash_adp_bry.ci_a7() + .Add_bry_byte(Bry__traditional , Tid__traditional) + .Add_bry_byte(Bry__nolines , Tid__nolines) + .Add_bry_byte(Bry__packed , Tid__packed) + .Add_bry_byte(Bry__packed__hover , Tid__packed__hover) + .Add_bry_byte(Bry__packed__overlay , Tid__packed__overlay) + ; + public static byte To_tid_or_traditional(byte[] bry) { + return bry_hash.Get_as_byte_or(bry, Tid__traditional); + } + public static byte To_tid_or(byte[] src, int bgn, int end, byte or) { + return bry_hash.Get_as_byte_or(src, bgn, end, or); + } + public static byte[] To_bry(byte tid) { + switch (tid) { + case Tid__traditional: return Bry__traditional; + case Tid__nolines: return Bry__nolines; + case Tid__packed: return Bry__packed; + case Tid__packed__hover: return Bry__packed__hover; + case Tid__packed__overlay: return Bry__packed__overlay; + default: throw Err_.new_unhandled(tid); + } + } + public static Gallery_mgr_base New(byte mode) { + switch (mode) { + default: + case Tid__traditional: return new Gallery_mgr_traditional(); + case Tid__nolines: return new Gallery_mgr_nolines(); + case Tid__packed: return new Gallery_mgr_packed_base(); + case Tid__packed__hover: return new Gallery_mgr_packed_hover(); + case Tid__packed__overlay: return new Gallery_mgr_packed_overlay(); + } + } + public static boolean Mode_is_packed(byte tid) { + switch (tid) { + case Tid__packed: + case Tid__packed__hover: + case Tid__packed__overlay: return true; + default: return false; + } + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_mgr_base__basic__tst.java b/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_mgr_base__basic__tst.java index a27517de8..b60629d45 100644 --- a/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_mgr_base__basic__tst.java +++ b/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_mgr_base__basic__tst.java @@ -13,3 +13,173 @@ 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.gallery; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; +import org.junit.*; +import gplx.xowa.htmls.*; import gplx.xowa.files.fsdb.*; +public class Gallery_mgr_base__basic__tst { + private Gallery_mgr_base_fxt fxt = new Gallery_mgr_base_fxt(); + @Before public void init() {fxt.Reset();} + @Test public void Basic() { + fxt.Test_html_str(String_.Concat_lines_nl_skip_last + ( "" + , "A.png|''a1''" + , "B.png|''b1''" + , "" + ), String_.Concat_lines_nl_skip_last + ( "" + )); + fxt.Test_html_modules_js(String_.Concat_lines_nl_skip_last + ( "" + , " " + )); + } + @Test public void Tmpl() { + fxt.Fxt().Init_defn_add("test_tmpl", "b"); + fxt.Test_html_frag("File:A.png|a{{test_tmpl}}c", "

      abc\n

      "); + } + @Test public void Itm_defaults_to_120() { + fxt.Test_html_frag("File:A.png|a", "\"\""); + } + @Test public void Height_fix() { + fxt.Fxt().Wiki().File_mgr().Cfg_set(Xof_fsdb_mgr_cfg.Grp_xowa, Xof_fsdb_mgr_cfg.Key_gallery_fix_defaults, "y"); + fxt.Test_html_frag("File:A.png|a
      c
      ", " width=\"120\" height=\"250\""); + fxt.Test_html_frag("
      "); + fxt.Fxt().Wiki().File_mgr().Cfg_set(Xof_fsdb_mgr_cfg.Grp_xowa, Xof_fsdb_mgr_cfg.Key_gallery_fix_defaults, "n"); + } + @Test public void Alt() { + fxt.Test_html_frag("File:A.png|b|alt=c" + , "\"c\"" + , "

      b\n

      " + ); + } + @Test public void Link() { + fxt.Test_html_frag("File:A.png|b|link=c", "File:A.pdf|b|page=8" + , "A.pdf/120px-8.jpg" // make sure page 8 shows up + ); + } + @Test public void Alt_caption_multiple() { + fxt.Test_html_frag("File:A.png|alt=b|c[[d|e]]f", "

      cef\n

      "); + } + @Test public void Alt_escape_quote() { + fxt.Test_html_frag("File:A.png|b|alt=c\"d'e", "alt=\"c"d'e\""); + } + @Test public void Caption_null() { // PURPOSE: null caption causes page to fail; EX: de.w:Lewis Caroll; Datei:A.png; DATE:2013-10-09 + fxt.Test_html_frag("File:A.png", "
      \n"); + } + @Test public void Ttl_has_no_ns() { // PURPOSE: MW allows ttl to not have ns; DATE: 2013-11-18 + fxt.Test_html_frag("A.png|b", "\"\""); // make sure image is generated + } + @Test public void Ref() { // PURPOSE: inside was not showing up in ; DATE:2013-10-09 + fxt.Test_html_frag("File:A.png|b" + , "

      [1]\n

      " + , String_.Concat_lines_nl + ( "
      " + , "
    1. ^ b
    2. " + , "
    " + ) + ); + } + @Test public void Packed() { + fxt.Test_html_frag("File:A.png|a", "
  • " + , "
    " + ), "hdr_href", "hdr_text", "toggle_btn", "toggle_hdr", "json"); +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_fmtr__langtext_tbl.java b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_fmtr__langtext_tbl.java index a27517de8..8982c9741 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_fmtr__langtext_tbl.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_fmtr__langtext_tbl.java @@ -13,3 +13,115 @@ 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.wbases.hwtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.core.brys.fmtrs.*; +import gplx.langs.htmls.*; +import gplx.xowa.langs.*; import gplx.xowa.wikis.*; import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.apps.apis.xowa.html.*; +class Wdata_fmtr__langtext_tbl implements gplx.core.brys.Bfr_arg { + private Wdata_toc_data toc_data; private Wdata_lang_sorter lang_sorter; private Xoapi_toggle_itm toggle_itm; private Wdata_fmtr__langtext_row fmtr_row; + private byte[] col_hdr_lang_name, col_hdr_lang_code, col_hdr_text; private int list_len; + public void Init_by_ctor(Wdata_toc_data toc_data, Wdata_lang_sorter lang_sorter, Xoapi_toggle_mgr toggle_mgr, String toggle_itm_key, Wdata_fmtr__langtext_row fmtr_row) { + this.toc_data = toc_data; this.lang_sorter = lang_sorter; this.fmtr_row = fmtr_row; + this.toggle_itm = toggle_mgr.Get_or_new(toggle_itm_key); + } + public void Init_by_lang(Wdata_hwtr_msgs msgs, byte[] tbl_hdr, byte[] col_hdr_text) { + this.col_hdr_lang_name = msgs.Langtext_col_lang_name(); this.col_hdr_lang_code = msgs.Langtext_col_lang_code(); this.col_hdr_text = col_hdr_text; + toc_data.Orig_(tbl_hdr); + toggle_itm.Init_msgs(msgs.Toggle_title_y(), msgs.Toggle_title_n()); + } + public void Init_by_wdoc(Ordered_hash list) { + this.list_len = list.Count(); if (list_len == 0) return; + toc_data.Make(list_len); + list.Sort_by(lang_sorter); + fmtr_row.Init_by_page(list); + } + public void Bfr_arg__add(Bry_bfr bfr) { + if (list_len == 0) return; + fmtr.Bld_bfr_many(bfr, toc_data.Href(), toc_data.Text(), col_hdr_lang_name, col_hdr_lang_code, col_hdr_text, toggle_itm.Html_toggle_btn(), toggle_itm.Html_toggle_hdr(), fmtr_row); + } + private final Bry_fmtr fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , "
    " + , "
    " + , "

    ~{hdr_text}~{toggle_btn}

    " + , "
    " + , "
    " + , "
      ~{rows}" + , "
    " + , "
    " + , "
    " + ), "hdr_href", "hdr_text", "hdr_lang_name", "hdr_lang_code", "hdr_page", "toggle_btn", "toggle_hdr", "rows" + ); +} +interface Wdata_fmtr__langtext_row extends gplx.core.brys.Bfr_arg { + void Init_by_page(Ordered_hash list); +} +class Wdata_fmtr__langtext_row_base implements gplx.core.brys.Bfr_arg, Wdata_fmtr__langtext_row { + private Ordered_hash list; + public void Init_by_page(Ordered_hash list) {this.list = list;} + public void Bfr_arg__add(Bry_bfr bfr) { + int len = list.Count(); + for (int i = 0; i < len; ++i) { + Wdata_langtext_itm itm = (Wdata_langtext_itm)list.Get_at(i); + Xol_lang_stub lang_itm = Xol_lang_stub_.Get_by_key_or_intl(itm.Lang()); + row_fmtr.Bld_bfr_many(bfr, itm.Lang(), Gfh_utl.Escape_html_as_bry(lang_itm.Canonical_name()), Gfh_utl.Escape_html_as_bry(itm.Text())); + } + } + private final Bry_fmtr row_fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , "
  • " + , " " + , " ~{lang_code}" + , " " + , " ~{text}" + , "
  • " + ), "lang_code", "lang_name", "text" + ); +// , "
  • " // wikibase-sitelinkview-~{wmf_key} data-wb-siteid='~{wmf_key}' +// , " " +// , " ~{wmf_key}" +// , " " +// , " " +// , " " // wikibase-sitelinkview-link-~{wmf_key} +// , " " +// , " ~{page_name}" +// , " " +// , " ~{badges}" +// , " " +// , " " +// , "
  • " +} +class Wdata_fmtr__alias_row implements gplx.core.brys.Bfr_arg, Wdata_fmtr__langtext_row { + private Ordered_hash list; + public void Init_by_page(Ordered_hash list) {this.list = list;} + public void Bfr_arg__add(Bry_bfr bfr) { + int len = list.Count(); + for (int i = 0; i < len; ++i) { + Wdata_alias_itm itm = (Wdata_alias_itm)list.Get_at(i); + byte[][] vals_ary = itm.Vals(); + int vals_len = vals_ary.length; + for (int j = 0; j < vals_len; ++j) { + byte[] val = vals_ary[j]; + Xol_lang_stub lang_itm = Xol_lang_stub_.Get_by_key_or_intl(itm.Lang()); + byte[] lang_code = Byte_ascii.Dash_bry; + byte[] lang_code_style = lang_code_style_n; + if (j == 0) { + lang_code = lang_itm.Key(); + lang_code_style = Bry_.Empty; + } + row_fmtr.Bld_bfr_many(bfr, lang_code, lang_code_style, Gfh_utl.Escape_html_as_bry(val)); + } + } + } + private static final byte[] lang_code_style_n = Bry_.new_a7("border:1px solid white;background:none;"); + private final Bry_fmtr row_fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , "
  • " + , " ~{lang_code}" + , " " + , " ~{text}" + , "
  • " + ), "lang_code", "lang_code_style", "text" + ); +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_fmtr__oview_tbl.java b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_fmtr__oview_tbl.java index a27517de8..e1efb4cbe 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_fmtr__oview_tbl.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_fmtr__oview_tbl.java @@ -13,3 +13,80 @@ 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.wbases.hwtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.core.brys.fmtrs.*; +import gplx.langs.htmls.encoders.*; +import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.apps.apis.xowa.xtns.*; +class Wdata_fmtr__oview_tbl implements gplx.core.brys.Bfr_arg { + private Xoapi_wikibase wikibase_api; private Gfo_url_encoder href_encoder; + private Wdata_fmtr__oview_alias_itm fmtr_aliases = new Wdata_fmtr__oview_alias_itm(); + private Bry_fmtr slink_fmtr = Bry_fmtr.new_("~{page_text}", "domain_bry", "page_href", "page_text"); + private Bry_bfr tmp_bfr = Bry_bfr_.New_w_size(255); + private Wdata_doc wdoc; + private byte[] hdr_alias_y, hdr_alias_n; + public void Init_by_ctor(Xoapi_wikibase wikibase_api, Gfo_url_encoder href_encoder) {this.wikibase_api = wikibase_api; this.href_encoder = href_encoder;} + public void Init_by_lang(byte[] lang_0, Wdata_hwtr_msgs msgs) { + this.hdr_alias_y = msgs.Oview_alias_y(); + this.hdr_alias_n = msgs.Oview_alias_n(); + } + public void Init_by_wdoc(Wdata_doc wdoc) { + this.wdoc = wdoc; + } + public void Bfr_arg__add(Bry_bfr bfr) { + byte[][] core_langs = wikibase_api.Core_langs(); + byte[] oview_label = Wdata_langtext_itm.Get_text_or_empty(wdoc.Label_list(), core_langs); + byte[] oview_descr = Wdata_langtext_itm.Get_text_or_empty(wdoc.Descr_list(), core_langs); + byte[][] oview_alias = Alias_get_or_empty(wdoc.Alias_list(), core_langs); + byte[] aliases_hdr = oview_alias == Bry_.Ary_empty ? hdr_alias_n : hdr_alias_y; + fmtr_aliases.Init_by_itm(oview_alias); + Wdata_sitelink_itm slink = (Wdata_sitelink_itm)wdoc.Slink_list().Get_by(wikibase_api.Link_wikis()); + if (slink != null) { + oview_label = slink_fmtr.Bld_bry_many(tmp_bfr, slink.Domain_info().Domain_bry(), href_encoder.Encode(slink.Name()), oview_label); + } + row_fmtr.Bld_bfr_many(bfr, wdoc.Qid(), oview_label, oview_descr, aliases_hdr, fmtr_aliases); + } + private Bry_fmtr row_fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , "
    " + , "
    " + , "

    ~{ttl_label}" + , " (~{ttl})" + , "

    " + , "
    ~{ttl_descr}" + , "
    " + , "
    " + , "
      ~{ttl_aliases}" + , "
    " + , "
    " + , "
    " + , "
    " + ), "ttl", "ttl_label", "ttl_descr", "hdr_aliases", "ttl_aliases" + ); + private static byte[][] Alias_get_or_empty(Ordered_hash list, byte[][] langs) { + if (list == null) return Bry_.Ary_empty; + int langs_len = langs.length; + for (int i = 0; i < langs_len; ++i) { + Object itm_obj = list.Get_by(langs[i]); + if (itm_obj != null) { + Wdata_alias_itm itm = (Wdata_alias_itm)itm_obj; + return itm.Vals(); + } + } + return Bry_.Ary_empty; + } +} +class Wdata_fmtr__oview_alias_itm implements gplx.core.brys.Bfr_arg { + private byte[][] ary; + public void Init_by_itm(byte[][] ary) {this.ary = ary;} + public void Bfr_arg__add(Bry_bfr bfr) { + if (ary == null) return; + int len = ary.length; + for (int i = 0; i < len; ++i) + row_fmtr.Bld_bfr_many(bfr, ary[i]); + } + private Bry_fmtr row_fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , "
  • ~{itm}
  • " + ), "itm" + ); +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_fmtr__slink.java b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_fmtr__slink.java index a27517de8..200a0b7c7 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_fmtr__slink.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_fmtr__slink.java @@ -13,3 +13,152 @@ 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.wbases.hwtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.core.brys.fmtrs.*; +import gplx.langs.htmls.encoders.*; import gplx.langs.htmls.*; +import gplx.xowa.langs.*; import gplx.xowa.xtns.wbases.core.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.apps.apis.xowa.html.*; import gplx.xowa.wikis.xwikis.*; +class Wdata_fmtr__slink_grp implements gplx.core.brys.Bfr_arg { + private final Wdata_fmtr__slink_tbl fmtr_tbl = new Wdata_fmtr__slink_tbl(); private boolean is_empty; + public void Init_by_ctor(Wdata_lang_sorter lang_sorter, Xoapi_toggle_mgr toggle_mgr, Wdata_lbl_mgr lbl_regy, Gfo_url_encoder href_encoder, Wdata_fmtr__toc_div fmtr_toc, Xow_xwiki_mgr xwiki_mgr) { + fmtr_tbl.Init_by_ctor(lang_sorter, toggle_mgr, lbl_regy, href_encoder, fmtr_toc, xwiki_mgr); + } + public void Init_by_lang(Wdata_hwtr_msgs msgs) {fmtr_tbl.Init_by_lang(msgs);} + public void Init_by_wdoc(Ordered_hash list) { + this.is_empty = list.Count() == 0; if (is_empty) return; + fmtr_tbl.Init_by_wdoc(list); + } + public void Bfr_arg__add(Bry_bfr bfr) { + if (is_empty) return; + fmtr.Bld_bfr_many(bfr, fmtr_tbl); + } + private final Bry_fmtr fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , "
    " + , "
    ~{grps}" + , "
    " + , "
    " + ), "grps" + ); +} +class Wdata_fmtr__slink_tbl implements gplx.core.brys.Bfr_arg { + private final Wdata_fmtr__slink_row fmtr_row = new Wdata_fmtr__slink_row(); + private final Wdata_slink_grp[] grps = new Wdata_slink_grp[Wdata_slink_grp.Idx__len]; + private Wdata_lang_sorter lang_sorter; private Wdata_hwtr_msgs msgs; + public void Init_by_ctor(Wdata_lang_sorter lang_sorter, Xoapi_toggle_mgr toggle_mgr, Wdata_lbl_mgr lbl_regy, Gfo_url_encoder href_encoder, Wdata_fmtr__toc_div fmtr_toc, Xow_xwiki_mgr xwiki_mgr) { + this.lang_sorter = lang_sorter; + fmtr_row.Init_by_ctor(lbl_regy, href_encoder, xwiki_mgr); + for (int i = 0; i < Wdata_slink_grp.Idx__len; ++i) { + byte[] wiki_name = Wdata_slink_grp.Name_by_tid(i); + String toggle_itm_key = "wikidatawiki-slink-" + String_.new_a7(wiki_name); + Xoapi_toggle_itm toggle_itm = toggle_mgr.Get_or_new(toggle_itm_key); + Wdata_toc_data toc_data = new Wdata_toc_data(fmtr_toc, href_encoder); + grps[i] = new Wdata_slink_grp(i, wiki_name, toggle_itm, toc_data); + } + } + public void Init_by_lang(Wdata_hwtr_msgs msgs) { + this.msgs = msgs; + for (int i = 0; i < Wdata_slink_grp.Idx__len; ++i) { + Wdata_slink_grp grp = grps[i]; + grp.Toc_data().Orig_(Wdata_slink_grp.Msg_by_tid(msgs, i)); + grp.Toggle_itm().Init_msgs(msgs.Toggle_title_y(), msgs.Toggle_title_n()); + } + } + public void Init_by_wdoc(Ordered_hash list) { + Wdata_slink_grp.Sift(grps, list); + for (int i = 0; i < Wdata_slink_grp.Idx__len; ++i) { + Wdata_slink_grp grp = grps[i]; + int itms_count = grp.Rows().Count(); + if (itms_count == 0) continue; + grp.Toc_data().Make(itms_count); + grp.Rows().Sort_by(lang_sorter); + } + } + public void Bfr_arg__add(Bry_bfr bfr) { + for (int i = 0; i < Wdata_slink_grp.Idx__len; ++i) { + Wdata_slink_grp grp = grps[i]; + if (grp.Rows().Count() == 0) continue; + fmtr_row.Init_by_page(grp.Rows()); + Xoapi_toggle_itm toggle_itm = grp.Toggle_itm(); + fmtr.Bld_bfr_many(bfr, grp.Toc_data().Href(), grp.Toc_data().Text(), msgs.Langtext_col_lang_name(), msgs.Langtext_col_lang_code(), msgs.Slink_col_hdr_text(), toggle_itm.Html_toggle_btn(), toggle_itm.Html_toggle_hdr(), fmtr_row); + } + } + private final Bry_fmtr fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , "
    " + , "
    " + , "

    ~{hdr_text}~{toggle_btn}

    " + , "
    " + , "
    " + , "
      ~{rows}" + , "
    " + , "
    " + , "
    " + ), "hdr_href", "hdr_text", "hdr_lang", "hdr_wiki", "hdr_page", "toggle_btn", "toggle_hdr", "rows" + ); +} +class Wdata_fmtr__slink_row implements gplx.core.brys.Bfr_arg { + private final Wdata_fmtr__slink_badges fmtr_badges = new Wdata_fmtr__slink_badges(); private Xow_xwiki_mgr xwiki_mgr; + private Gfo_url_encoder href_encoder; private Ordered_hash list; + public void Init_by_ctor(Wdata_lbl_mgr lbl_regy, Gfo_url_encoder href_encoder, Xow_xwiki_mgr xwiki_mgr) { + this.href_encoder = href_encoder; this.xwiki_mgr = xwiki_mgr; + fmtr_badges.Init_by_ctor(lbl_regy); + } + public void Init_by_page(Ordered_hash list) {this.list = list;} + public void Bfr_arg__add(Bry_bfr bfr) { + int len = list.Count(); + for (int i = 0; i < len; ++i) { + Wdata_sitelink_itm itm = (Wdata_sitelink_itm)list.Get_at(i); + Xow_domain_itm domain_info = itm.Domain_info(); + byte[] wmf_key = domain_info.Abrv_wm(); + Xol_lang_stub lang_itm = domain_info.Lang_actl_itm(); + byte[] lang_key = lang_itm.Key(); + byte[] lang_name = lang_itm.Canonical_name(); + byte[] domain_bry = domain_info.Domain_bry(); + byte[] page_name = itm.Name(); + fmtr_badges.Init_by_itm(itm.Badges()); + byte[] href_site = xwiki_mgr.Get_by_key(domain_bry) == null ? Href_site_http : Href_site_xowa; + fmtr_row.Bld_bfr_many(bfr, lang_name, lang_key, wmf_key, href_site, domain_bry, href_encoder.Encode(page_name), Gfh_utl.Escape_html_as_bry(itm.Name()), fmtr_badges); + } + } + private static final byte[] Href_site_xowa = Bry_.new_a7("/site/"), Href_site_http = Bry_.new_a7("https://"); + private final Bry_fmtr fmtr_row = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , "
  • " // wikibase-sitelinkview-~{wmf_key} data-wb-siteid='~{wmf_key}' + , " " + , " ~{lang_code}" + , " " + , " " + , " " // wikibase-sitelinkview-link-~{wmf_key} + , " " + , " ~{page_name}" + , " " + , " ~{badges}" + , " " + , " " + , "
  • " + ), "lang_name", "lang_code", "wmf_key", "href_site", "href_domain", "href_page", "page_name", "badges" + ); +} +class Wdata_fmtr__slink_badges implements gplx.core.brys.Bfr_arg { + private Wdata_lbl_mgr lbl_regy; private byte[][] badges; + public void Init_by_ctor(Wdata_lbl_mgr lbl_regy) {this.lbl_regy = lbl_regy;} + public void Init_by_itm(byte[][] badges) {this.badges = badges;} + public void Bfr_arg__add(Bry_bfr bfr) { + int len = badges.length; + for (int i = 0; i < len; ++i) { + byte[] ttl = badges[i]; + Wdata_lbl_itm lbl = lbl_regy.Get_itm__ttl(ttl); + byte[] name = Bry_.Empty, cls = Bry_.Empty; + if (lbl != null) { + name = lbl.Text(); + cls = Bry_.Replace(lbl.Text_en(), Byte_ascii.Space_bry, Bry_.Empty); // NOTE: use Text_en; "featured article" -> "featuredarticle"; same for "good article" -> "goodarticle" + } + fmtr_row.Bld_bfr_many(bfr, ttl, cls, name); + } + } + private final Bry_fmtr fmtr_row = Bry_fmtr.new_ + ( "\n " + , "ttl", "cls", "name" + ); +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_fmtr__toc_div.java b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_fmtr__toc_div.java index a27517de8..6fd3db6e7 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_fmtr__toc_div.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_fmtr__toc_div.java @@ -13,3 +13,49 @@ 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.wbases.hwtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.core.brys.fmtrs.*; +import gplx.langs.htmls.encoders.*; +import gplx.xowa.wikis.pages.wtxts.*; +class Wdata_fmtr__toc_div implements gplx.core.brys.Bfr_arg { + private final List_adp itms = List_adp_.New(); private final Wdata_fmtr__toc_itm fmtr_itm = new Wdata_fmtr__toc_itm(); + private byte[] tbl_hdr; + public void Init_by_lang(Wdata_hwtr_msgs msgs) {this.tbl_hdr = msgs.Toc_tbl_hdr();} + public void Init_by_wdoc(Wdata_doc wdoc) {itms.Clear();} + public void Add(Wdata_toc_data toc_data) {itms.Add(toc_data);} + public void Bfr_arg__add(Bry_bfr bfr) { + int itms_len = itms.Count(); + if (itms_len <= Xopg_toc_mgr.Hdrs_min) return; + fmtr_itm.Init_by_itm((Wdata_toc_data[])itms.To_ary_and_clear(Wdata_toc_data.class)); + fmtr.Bld_bfr_many(bfr, tbl_hdr, fmtr_itm); + } + private final Bry_fmtr fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , "
    " + , "
    " + , "

    ~{hdr}

    " + , "
    " + , "
      ~{itms}" + , "
    " + , "
    " + ), "hdr", "itms"); +} +class Wdata_fmtr__toc_itm implements gplx.core.brys.Bfr_arg { + private Wdata_toc_data[] ary; + public void Init_by_itm(Wdata_toc_data[] v) {this.ary = v;} + public void Bfr_arg__add(Bry_bfr bfr) { + int len = ary.length; + for (int i = 0; i < len; ++i) { + Wdata_toc_data itm = ary[i]; + fmtr.Bld_bfr_many(bfr, i + List_adp_.Base1, itm.Href(), itm.Text()); + } + } + private final Bry_fmtr fmtr = Bry_fmtr.new_(String_.Concat_lines_nl_skip_last + ( "" + , "
  • " + , " " + , " ~{text}" + , " " + , "
  • " + ), "idx", "href", "text"); +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_hwtr_mgr.java b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_hwtr_mgr.java index a27517de8..6948b8ac9 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_hwtr_mgr.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_hwtr_mgr.java @@ -13,3 +13,82 @@ 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.wbases.hwtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.core.brys.fmtrs.*; +import gplx.langs.htmls.encoders.*; +import gplx.xowa.langs.*; +import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.apps.apis.xowa.html.*; import gplx.xowa.wikis.xwikis.*; import gplx.xowa.apps.apis.xowa.xtns.*; +public class Wdata_hwtr_mgr { + private final Bry_bfr bfr = Bry_bfr_.Reset(Io_mgr.Len_mb); + @gplx.Internal protected Wdata_fmtr__toc_div Fmtr_toc() {return fmtr_toc;} private final Wdata_fmtr__toc_div fmtr_toc = new Wdata_fmtr__toc_div(); + @gplx.Internal protected Wdata_fmtr__json Fmtr_json() {return fmtr_json;} private final Wdata_fmtr__json fmtr_json = new Wdata_fmtr__json(); + @gplx.Internal protected Wdata_fmtr__claim_grp Fmtr_claim() {return fmtr_claim;} private final Wdata_fmtr__claim_grp fmtr_claim = new Wdata_fmtr__claim_grp(); + private final Wdata_fmtr__langtext_tbl fmtr_label = new Wdata_fmtr__langtext_tbl(); + private final Wdata_fmtr__langtext_tbl fmtr_descr = new Wdata_fmtr__langtext_tbl(); + private final Wdata_fmtr__langtext_tbl fmtr_alias = new Wdata_fmtr__langtext_tbl(); + private final Wdata_fmtr__slink_grp fmtr_slink = new Wdata_fmtr__slink_grp(); + private final Wdata_fmtr__oview_tbl fmtr_oview = new Wdata_fmtr__oview_tbl(); + private Wdata_lang_sorter lang_sorter = new Wdata_lang_sorter(); + public Bry_fmtr Fmtr_main() {return fmtr_main;} private final Bry_fmtr fmtr_main = Bry_fmtr.new_("~{oview}~{toc}~{claims}~{links}~{labels}~{descriptions}~{aliases}~{json}", "oview", "toc", "claims", "links", "labels", "descriptions", "aliases", "json"); + public Wdata_hwtr_msgs Msgs() {return msgs;} private Wdata_hwtr_msgs msgs; + public Wdata_lbl_mgr Lbl_mgr() {return lbl_mgr;} private final Wdata_lbl_mgr lbl_mgr = new Wdata_lbl_mgr(); + public void Init_by_ctor(Xoapi_wikibase wikibase_api, Wdata_wiki_mgr wdata_mgr, Wdata_lbl_wkr lbl_wkr, Gfo_url_encoder href_encoder, Xoapi_toggle_mgr toggle_mgr, Xow_xwiki_mgr xwiki_mgr) { + lbl_mgr.Wkr_(lbl_wkr); + fmtr_oview.Init_by_ctor(wikibase_api, href_encoder); + fmtr_claim.Init_by_ctor(wdata_mgr, new Wdata_toc_data(fmtr_toc, href_encoder), toggle_mgr, lbl_mgr); + fmtr_slink.Init_by_ctor(lang_sorter, toggle_mgr, lbl_mgr, href_encoder, fmtr_toc, xwiki_mgr); + fmtr_label.Init_by_ctor(new Wdata_toc_data(fmtr_toc, href_encoder), lang_sorter, toggle_mgr, "wikidatawiki-label", new Wdata_fmtr__langtext_row_base()); + fmtr_descr.Init_by_ctor(new Wdata_toc_data(fmtr_toc, href_encoder), lang_sorter, toggle_mgr, "wikidatawiki-descr", new Wdata_fmtr__langtext_row_base()); + fmtr_alias.Init_by_ctor(new Wdata_toc_data(fmtr_toc, href_encoder), lang_sorter, toggle_mgr, "wikidatawiki-alias", new Wdata_fmtr__alias_row()); + fmtr_json .Init_by_ctor(new Wdata_toc_data(fmtr_toc, href_encoder), toggle_mgr); + lang_sorter.Langs_(wikibase_api.Sort_langs()); + Gfo_evt_mgr_.Sub_same_many(wikibase_api, lang_sorter, Xoapi_wikibase.Evt_sort_langs_changed); + } + public void Init_by_lang(Xol_lang_itm lang, Wdata_hwtr_msgs msgs) { + this.msgs = msgs; + fmtr_toc.Init_by_lang(msgs); + fmtr_oview.Init_by_lang(lang_sorter.Langs()[0], msgs); + fmtr_claim.Init_by_lang(lang, msgs); + fmtr_slink.Init_by_lang(msgs); + fmtr_label.Init_by_lang(msgs, msgs.Label_tbl_hdr(), msgs.Label_col_hdr()); + fmtr_descr.Init_by_lang(msgs, msgs.Descr_tbl_hdr(), msgs.Descr_col_hdr()); + fmtr_alias.Init_by_lang(msgs, msgs.Alias_tbl_hdr(), msgs.Alias_col_hdr()); + fmtr_json.Init_by_lang(msgs); + } + public void Init_by_wdoc(Wdata_doc wdoc) { + lang_sorter.Init_by_wdoc(wdoc); + fmtr_toc .Init_by_wdoc(wdoc); + fmtr_oview.Init_by_wdoc(wdoc); + fmtr_claim.Init_by_wdoc(wdoc.Qid(), wdoc.Claim_list()); + fmtr_slink.Init_by_wdoc(wdoc.Slink_list()); + fmtr_label.Init_by_wdoc(wdoc.Label_list()); + fmtr_descr.Init_by_wdoc(wdoc.Descr_list()); + fmtr_alias.Init_by_wdoc(wdoc.Alias_list()); + fmtr_json.Init_by_wdoc (wdoc.Jdoc()); + lbl_mgr.Gather_labels(wdoc, lang_sorter); + } + public byte[] Popup(Wdata_doc wdoc) { + fmtr_oview .Init_by_wdoc(wdoc); + fmtr_label.Init_by_wdoc(wdoc.Label_list()); + fmtr_descr.Init_by_wdoc(wdoc.Descr_list()); + fmtr_alias.Init_by_wdoc(wdoc.Alias_list()); + bfr.Add_str_a7("
    "); + bfr.Add_str_a7("
    "); + fmtr_main.Bld_bfr_many(bfr, fmtr_oview, "", "", "", "", "", "", ""); + bfr.Add_str_a7("
    "); + bfr.Add_str_a7("
    "); + return bfr.To_bry_and_clear(); + } + public byte[] Write(Wdata_doc wdoc) { + bfr.Add_str_a7("
    "); + bfr.Add_str_a7("
    "); + fmtr_main.Bld_bfr_many(bfr, fmtr_oview, fmtr_toc, fmtr_claim, fmtr_slink, fmtr_label, fmtr_descr, fmtr_alias, fmtr_json); + bfr.Add_str_a7("
    "); + bfr.Add_str_a7("
    "); + return bfr.To_bry_and_clear(); + } + public static void Write_link_wikidata(Bry_bfr bfr, byte[] href, byte[] text) { + text = gplx.langs.htmls.Gfh_utl.Escape_html_as_bry(text); + fmtr_link_wikidata.Bld_bfr_many(bfr, href, text); + } private static final Bry_fmtr fmtr_link_wikidata = Bry_fmtr.new_("~{text}", "href", "text"); +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_hwtr_mgr_tst.java b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_hwtr_mgr_tst.java index a27517de8..ab38f58ae 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_hwtr_mgr_tst.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_hwtr_mgr_tst.java @@ -13,3 +13,314 @@ 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.wbases.hwtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import org.junit.*; +import gplx.langs.jsons.*; import gplx.langs.htmls.encoders.*; +import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.xtns.wbases.claims.*; import gplx.xowa.xtns.wbases.claims.itms.*; import gplx.xowa.xtns.wbases.parsers.*; import gplx.xowa.apps.apis.xowa.html.*; import gplx.xowa.wikis.xwikis.*; import gplx.xowa.apps.apis.xowa.xtns.*; +public class Wdata_hwtr_mgr_tst { + @Before public void init() {fxt.init();} private Wdata_hwtr_mgr_fxt fxt = new Wdata_hwtr_mgr_fxt(); + @Test public void Stub() {} +// @Test public void Write_label() { +// fxt.Test_doc(fxt.Wdoc_bldr() +// .Add_label("en", "en_label") +// .Add_label("de", "de_label").Xto_wdoc(), String_.Concat_lines_nl_skip_last +// ( "" +// , "

    Labels

    " +// , "" +// , "" +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , "
    LanguageLabel
    enen_label
    dede_label
    " +// )); +// } +// @Test public void Write_descr() { +// fxt.Test_doc(fxt.Wdoc_bldr() +// .Add_description("en", "en_descr") +// .Add_description("de", "de_descr").Xto_wdoc(), String_.Concat_lines_nl_skip_last +// ( "" +// , "

    Descriptions

    " +// , "" +// , "" +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , "
    LanguageDescription
    enen_descr
    dede_descr
    " +// )); +// } +// @Test public void Write_alias() { +// fxt.Test_doc(fxt.Wdoc_bldr() +// .Add_alias("en", "en_1", "en_2") +// .Add_alias("de", "de_1").Xto_wdoc(), String_.Concat_lines_nl_skip_last +// ( "" +// , "

    Aliases

    " +// , "" +// , "" +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , "
    LanguageAlias
    enen_1
    en_2
    dede_1
    " +// )); +// } +// @Test public void Write_slink_tbl_one() { +// fxt +// .Init_resolved_qid(1, "featured article").Init_resolved_qid(2, "good article") +// .Test_doc(fxt.Wdoc_bldr() +// .Add_sitelink("enwiki", "Earth", "Q1", "Q2") +// .Add_sitelink("dewiki", "Erde") +// .Add_sitelink("frwiki", "Terre") +// .Xto_wdoc(), String_.Concat_lines_nl_skip_last +// ( "" +// , "
    " +// , "
    " +// , "
    " +// , "
    " +// , " " +// , "
    " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , "
    LanguageCodeLinked page
    Englishenwiki
    Deutschdewiki
    Françaisfrwiki
    " +// , "
    " +// , "
    " +// , "
    " +// )); +// } +// @Test public void Write_slink_tbl_many() { +// fxt.Test_doc(fxt.Wdoc_bldr() +// .Add_sitelink("enwiki" , "Earth") +// .Add_sitelink("enwiktionary", "Earth") +// .Add_sitelink("enwikiquote" , "Earth") +// .Xto_wdoc(), String_.Concat_lines_nl_skip_last +// ( "" +// , "

    " +// , "

    " +// , "

    Contents

    " +// , " " +// , "
    " +// , "

    " +// , "" +// , "

    Links (Wikipedia)

    " +// , "" +// , "

    " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , "
    SiteLinkBadges
    enwikiEarth
    " +// , "

    " +// , "" +// , "

    Links (Wiktionary)

    " +// , "" +// , "

    " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , "
    SiteLinkBadges
    enwiktionaryEarth
    " +// , "

    " +// , "" +// , "

    Links (Wikiquote)

    " +// , "" +// , "

    " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , "
    SiteLinkBadges
    enwikiquoteEarth
    " +// , "

    " +// )); +// } +// @Test public void Write_claim() { +// Wdata_wiki_mgr_fxt mkr = fxt.Wdata_fxt(); +// fxt +// .Init_resolved_pid(1, "prop_1") +// .Test_doc(fxt.Wdoc_bldr() +// .Add_claims +// ( mkr.Make_claim_string(1, "abc").Qualifiers_(mkr.Make_qualifiers(mkr.Make_qualifiers_grp(2, mkr.Make_claim_string(2, "qual_2")))) +// ).Xto_wdoc(), String_.Concat_lines_nl_skip_last +// ( "" +// , "

    Claims

    " +// , "" +// , "" +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , " " +// , "
    IdNameValueReferencesQualifiersRank
    1prop_1abcnormal
    " +// )); +// } +// @Test public void Write_json() { +// Json_doc jdoc = Json_doc.new_apos_("{ 'node':['val_0', 'val_1'] }"); +// Wdata_doc wdoc = new Wdata_doc(Bry_.Empty, null, jdoc); +// fxt.Test_json(wdoc, String_.Concat_lines_nl_skip_last +// ( "" +// , "

    JSON

    " +// , "" +// , "
    "
    +//			, "{ \"node\":"
    +//			, "  [ \"val_0\""
    +//			, "  , \"val_1\""
    +//			, "  ]"
    +//			, "}"
    +//			, "
    " +// )); +// } +} +class Wdata_hwtr_mgr_fxt { + private Wdata_hwtr_mgr doc_hwtr; private Ordered_hash resolved_ttls = Ordered_hash_.New_bry(); + public Wdata_wiki_mgr_fxt Wdata_fxt() {return wdata_fxt;} private Wdata_wiki_mgr_fxt wdata_fxt = new Wdata_wiki_mgr_fxt(); + public void init() { + if (doc_hwtr == null) { + doc_hwtr = new Wdata_hwtr_mgr(); + Wdata_hwtr_msgs msgs = Wdata_hwtr_msgs.new_en_(); + Xoapi_toggle_mgr toggle_mgr = new Xoapi_toggle_mgr(); + wdata_fxt.Init(); + toggle_mgr.Ctor_by_app(wdata_fxt.App()); // must init, else null error + doc_hwtr.Init_by_ctor(new Xoapi_wikibase(), wdata_fxt.Wdata_mgr(), new Wdata_lbl_wkr__test(resolved_ttls), Gfo_url_encoder_.Href, toggle_mgr, new Xow_xwiki_mgr(wdata_fxt.Wiki())); + doc_hwtr.Init_by_lang(wdata_fxt.Wiki().Lang(), msgs); + } + resolved_ttls.Clear(); + doc_hwtr.Lbl_mgr().Clear(); + } + public Wdata_doc_bldr Wdoc_bldr() {return wdoc_bldr;} private Wdata_doc_bldr wdoc_bldr = new Wdata_doc_bldr(); + public Wdata_hwtr_mgr_fxt Init_resolved_pid(int pid, String lbl) {resolved_ttls.Add(Wdata_lbl_itm.Make_ttl(Bool_.Y, pid), new Wdata_langtext_itm(Bry_.new_a7("en"), Bry_.new_a7(lbl))); return this;} + public Wdata_hwtr_mgr_fxt Init_resolved_qid(int qid, String lbl) {resolved_ttls.Add(Wdata_lbl_itm.Make_ttl(Bool_.N, qid), new Wdata_langtext_itm(Bry_.new_a7("en"), Bry_.new_a7(lbl))); return this;} + public void Test_doc(Wdata_doc wdoc, String expd) { + doc_hwtr.Init_by_wdoc(wdoc); + byte[] actl = doc_hwtr.Write(wdoc); + Tfds.Eq_str_lines(expd, String_.new_u8(actl)); + } + public void Test_claim_val(Wbase_claim_base claim, String expd) { + doc_hwtr.Init_by_wdoc(wdoc_bldr.Add_claims(claim).Xto_wdoc()); + Bry_bfr tmp_bfr = Bry_bfr_.New(); + Wdata_visitor__html_wtr html_wtr = new Wdata_visitor__html_wtr().Init(tmp_bfr, wdata_fxt.Wdata_mgr(), doc_hwtr.Msgs(), doc_hwtr.Lbl_mgr(), wdata_fxt.Wiki().Lang(), Bry_.Empty); + claim.Welcome(html_wtr); + byte[] actl = tmp_bfr.To_bry_and_clear(); + Tfds.Eq(expd, String_.new_u8(actl)); + } + public void Test_json(Wdata_doc wdoc, String expd) { + Wdata_fmtr__json fmtr_json = doc_hwtr.Fmtr_json(); + fmtr_json.Init_by_wdoc(wdoc.Jdoc()); + Bry_bfr tmp_bfr = Bry_bfr_.New(); + fmtr_json.Bfr_arg__add(tmp_bfr); + Tfds.Eq_str_lines(expd, tmp_bfr.To_str_and_clear()); + } +} +class Wdata_lbl_wkr__test implements Wdata_lbl_wkr { + private Ordered_hash found; + public Wdata_lbl_wkr__test(Ordered_hash found) {this.found = found;} + public void Resolve(Wdata_lbl_mgr lbl_mgr, Wdata_lang_sorter sorter) {lbl_mgr.Resolve(found);} +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_hwtr_msgs.java b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_hwtr_msgs.java index a27517de8..582a53513 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_hwtr_msgs.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_hwtr_msgs.java @@ -13,3 +13,215 @@ 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.wbases.hwtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.langs.msgs.*; +import gplx.xowa.wikis.domains.*; +public class Wdata_hwtr_msgs { + public Wdata_hwtr_msgs(byte[][] brys) { int offset = 0; // String[] strs = String_.Ary(brys); // TEST + this.ary = brys; + toggle_title_n = brys[offset + 0]; + toggle_title_y = brys[offset + 1]; + toc_tbl_hdr = brys[offset + 2]; + oview_alias_y = brys[offset + 3]; + oview_alias_n = brys[offset + 4]; offset += 5; + langtext_col_lang_name = brys[offset + 0]; + langtext_col_lang_code = brys[offset + 1]; + slink_tbl_hdr_fmt = brys[offset + 2]; + slink_tbl_hdr_fmt_other = brys[offset + 3]; + slink_col_hdr_text = brys[offset + 4]; offset += 5; + label_tbl_hdr = brys[offset + 0]; + label_col_hdr = brys[offset + 1]; + alias_tbl_hdr = brys[offset + 2]; + alias_col_hdr = brys[offset + 3]; + descr_tbl_hdr = brys[offset + 4]; + descr_col_hdr = brys[offset + 5]; + claim_tbl_hdr = brys[offset + 6]; + json_div_hdr = brys[offset + 7]; offset += 8; + sym_list_comma = brys[offset + 0]; + sym_list_word = brys[offset + 1]; + sym_time_spr = brys[offset + 2]; + sym_plus = brys[offset + 3]; + sym_minus = brys[offset + 4]; + sym_plusminus = brys[offset + 5]; + sym_fmt_parentheses = brys[offset + 6]; offset += 7; + val_tid_novalue = brys[offset + 0]; + val_tid_somevalue = brys[offset + 1]; offset += 2; + this.month_bgn_idx = offset; + time_month_01 = brys[offset + 0]; + time_month_02 = brys[offset + 1]; + time_month_03 = brys[offset + 2]; + time_month_04 = brys[offset + 3]; + time_month_05 = brys[offset + 4]; + time_month_06 = brys[offset + 5]; + time_month_07 = brys[offset + 6]; + time_month_08 = brys[offset + 7]; + time_month_09 = brys[offset + 8]; + time_month_10 = brys[offset + 9]; + time_month_11 = brys[offset + 10]; + time_month_12 = brys[offset + 11]; offset += 12; + time_year_idx = offset; + time_year_1e10_00 = brys[offset + 0]; + time_year_1e10_01 = brys[offset + 1]; + time_year_1e10_02 = brys[offset + 2]; + time_year_1e10_03 = brys[offset + 3]; + time_year_1e10_04 = brys[offset + 4]; + time_year_1e10_05 = brys[offset + 5]; + time_year_1e10_06 = brys[offset + 6]; + time_year_1e10_07 = brys[offset + 7]; + time_year_1e10_08 = brys[offset + 8]; + time_year_1e10_09 = brys[offset + 9]; offset += 10; + time_relative_bc = brys[offset + 0]; + time_relative_ago = brys[offset + 1]; + time_relative_in = brys[offset + 2]; + time_julian = brys[offset + 3]; offset += 4; + geo_dir_n = brys[offset + 0]; + geo_dir_s = brys[offset + 1]; + geo_dir_e = brys[offset + 2]; + geo_dir_w = brys[offset + 3]; + geo_unit_degree = brys[offset + 4]; + geo_unit_minute = brys[offset + 5]; + geo_unit_second = brys[offset + 6]; + geo_meters = brys[offset + 7]; + Bry_fmtr fmtr = Bry_fmtr.new_( slink_tbl_hdr_fmt, "wiki_type"); + Bry_bfr bfr = Bry_bfr_.New_w_size(64); + slink_tbl_hdr_w = fmtr.Bld_bry_many(bfr, Name_(Xow_domain_tid_.Bry__wikipedia)); + slink_tbl_hdr_d = fmtr.Bld_bry_many(bfr, Name_(Xow_domain_tid_.Bry__wiktionary)); + slink_tbl_hdr_s = fmtr.Bld_bry_many(bfr, Name_(Xow_domain_tid_.Bry__wikisource)); + slink_tbl_hdr_v = fmtr.Bld_bry_many(bfr, Name_(Xow_domain_tid_.Bry__wikivoyage)); + slink_tbl_hdr_q = fmtr.Bld_bry_many(bfr, Name_(Xow_domain_tid_.Bry__wikiquote)); + slink_tbl_hdr_b = fmtr.Bld_bry_many(bfr, Name_(Xow_domain_tid_.Bry__wikibooks)); + slink_tbl_hdr_u = fmtr.Bld_bry_many(bfr, Name_(Xow_domain_tid_.Bry__wikiversity)); + slink_tbl_hdr_n = fmtr.Bld_bry_many(bfr, Name_(Xow_domain_tid_.Bry__wikinews)); + slink_tbl_hdr_x = fmtr.Bld_bry_many(bfr, slink_tbl_hdr_fmt_other); + } + public byte[][] Ary() {return ary;} private final byte[][] ary; + public int Month_bgn_idx() {return month_bgn_idx;} private final int month_bgn_idx; + public byte[] Toggle_title_y() {return toggle_title_y;} private byte[] toggle_title_y; + public byte[] Toggle_title_n() {return toggle_title_n;} private byte[] toggle_title_n; + public byte[] Toc_tbl_hdr() {return toc_tbl_hdr;} private final byte[] toc_tbl_hdr; + public byte[] Oview_alias_y() {return oview_alias_y;} private final byte[] oview_alias_y; + public byte[] Oview_alias_n() {return oview_alias_n;} private final byte[] oview_alias_n; + public byte[] Langtext_col_lang_name() {return langtext_col_lang_name;} private final byte[] langtext_col_lang_name; + public byte[] Langtext_col_lang_code() {return langtext_col_lang_code;} private final byte[] langtext_col_lang_code; + public byte[] Label_tbl_hdr() {return label_tbl_hdr;} private final byte[] label_tbl_hdr; + public byte[] Label_col_hdr() {return label_col_hdr;} private final byte[] label_col_hdr; + public byte[] Alias_tbl_hdr() {return alias_tbl_hdr;} private final byte[] alias_tbl_hdr; + public byte[] Alias_col_hdr() {return alias_col_hdr;} private final byte[] alias_col_hdr; + public byte[] Descr_tbl_hdr() {return descr_tbl_hdr;} private final byte[] descr_tbl_hdr; + public byte[] Descr_col_hdr() {return descr_col_hdr;} private final byte[] descr_col_hdr; + public byte[] Slink_tbl_hdr_fmt() {return slink_tbl_hdr_fmt;} private final byte[] slink_tbl_hdr_fmt; + public byte[] Slink_tbl_hdr_fmt_other() {return slink_tbl_hdr_fmt_other;} private final byte[] slink_tbl_hdr_fmt_other; + public byte[] Slink_tbl_hdr_w() {return slink_tbl_hdr_w;} private final byte[] slink_tbl_hdr_w; + public byte[] Slink_tbl_hdr_d() {return slink_tbl_hdr_d;} private final byte[] slink_tbl_hdr_d; + public byte[] Slink_tbl_hdr_s() {return slink_tbl_hdr_s;} private final byte[] slink_tbl_hdr_s; + public byte[] Slink_tbl_hdr_v() {return slink_tbl_hdr_v;} private final byte[] slink_tbl_hdr_v; + public byte[] Slink_tbl_hdr_q() {return slink_tbl_hdr_q;} private final byte[] slink_tbl_hdr_q; + public byte[] Slink_tbl_hdr_b() {return slink_tbl_hdr_b;} private final byte[] slink_tbl_hdr_b; + public byte[] Slink_tbl_hdr_u() {return slink_tbl_hdr_u;} private final byte[] slink_tbl_hdr_u; + public byte[] Slink_tbl_hdr_n() {return slink_tbl_hdr_n;} private final byte[] slink_tbl_hdr_n; + public byte[] Slink_tbl_hdr_x() {return slink_tbl_hdr_x;} private final byte[] slink_tbl_hdr_x; + public byte[] Slink_col_hdr_text() {return slink_col_hdr_text;} private final byte[] slink_col_hdr_text; + public byte[] Claim_tbl_hdr() {return claim_tbl_hdr;} private final byte[] claim_tbl_hdr; + public byte[] Json_div_hdr() {return json_div_hdr;} private final byte[] json_div_hdr; + public byte[] Val_tid_novalue() {return val_tid_novalue;} private final byte[] val_tid_novalue; + public byte[] Val_tid_somevalue() {return val_tid_somevalue;} private final byte[] val_tid_somevalue; + public byte[] Sym_list_comma() {return sym_list_comma;} private final byte[] sym_list_comma; + public byte[] Sym_list_word() {return sym_list_word;} private final byte[] sym_list_word; + public byte[] Sym_time_spr() {return sym_time_spr;} private final byte[] sym_time_spr; + public byte[] Sym_plus() {return sym_plus;} private final byte[] sym_plus; + public byte[] Sym_minus() {return sym_minus;} private final byte[] sym_minus; + public byte[] Sym_plusminus() {return sym_plusminus;} private final byte[] sym_plusminus; + public byte[] Sym_fmt_parentheses() {return sym_fmt_parentheses;} private final byte[] sym_fmt_parentheses; + public byte[] Time_month_01() {return time_month_01;} private final byte[] time_month_01; + public byte[] Time_month_02() {return time_month_02;} private final byte[] time_month_02; + public byte[] Time_month_03() {return time_month_03;} private final byte[] time_month_03; + public byte[] Time_month_04() {return time_month_04;} private final byte[] time_month_04; + public byte[] Time_month_05() {return time_month_05;} private final byte[] time_month_05; + public byte[] Time_month_06() {return time_month_06;} private final byte[] time_month_06; + public byte[] Time_month_07() {return time_month_07;} private final byte[] time_month_07; + public byte[] Time_month_08() {return time_month_08;} private final byte[] time_month_08; + public byte[] Time_month_09() {return time_month_09;} private final byte[] time_month_09; + public byte[] Time_month_10() {return time_month_10;} private final byte[] time_month_10; + public byte[] Time_month_11() {return time_month_11;} private final byte[] time_month_11; + public byte[] Time_month_12() {return time_month_12;} private final byte[] time_month_12; + public int Time_year_idx() {return time_year_idx;} private final int time_year_idx; + public byte[] Time_year_1e10_00() {return time_year_1e10_00;} private final byte[] time_year_1e10_00; + public byte[] Time_year_1e10_01() {return time_year_1e10_01;} private final byte[] time_year_1e10_01; + public byte[] Time_year_1e10_02() {return time_year_1e10_02;} private final byte[] time_year_1e10_02; + public byte[] Time_year_1e10_03() {return time_year_1e10_03;} private final byte[] time_year_1e10_03; + public byte[] Time_year_1e10_04() {return time_year_1e10_04;} private final byte[] time_year_1e10_04; + public byte[] Time_year_1e10_05() {return time_year_1e10_05;} private final byte[] time_year_1e10_05; + public byte[] Time_year_1e10_06() {return time_year_1e10_06;} private final byte[] time_year_1e10_06; + public byte[] Time_year_1e10_07() {return time_year_1e10_07;} private final byte[] time_year_1e10_07; + public byte[] Time_year_1e10_08() {return time_year_1e10_08;} private final byte[] time_year_1e10_08; + public byte[] Time_year_1e10_09() {return time_year_1e10_09;} private final byte[] time_year_1e10_09; + public byte[] Time_relative_bc() {return time_relative_bc;} private final byte[] time_relative_bc; + public byte[] Time_relative_ago() {return time_relative_ago;} private final byte[] time_relative_ago; + public byte[] Time_relative_in() {return time_relative_in;} private final byte[] time_relative_in; + public byte[] Time_julian() {return time_julian;} private final byte[] time_julian; + public byte[] Geo_dir_n() {return geo_dir_n;} private final byte[] geo_dir_n; + public byte[] Geo_dir_s() {return geo_dir_s;} private final byte[] geo_dir_s; + public byte[] Geo_dir_e() {return geo_dir_e;} private final byte[] geo_dir_e; + public byte[] Geo_dir_w() {return geo_dir_w;} private final byte[] geo_dir_w; + public byte[] Geo_unit_degree() {return geo_unit_degree;} private final byte[] geo_unit_degree; + public byte[] Geo_unit_minute() {return geo_unit_minute;} private final byte[] geo_unit_minute; + public byte[] Geo_unit_second() {return geo_unit_second;} private final byte[] geo_unit_second; + public byte[] Geo_meters() {return geo_meters;} private final byte[] geo_meters; + public static Wdata_hwtr_msgs new_en_() { + byte[][] brys = Bry_.Ary + ( "hide", "show", "Contents" + , "Also known as:", "No aliases defined." + , "Language", "Code" + , "Links (~{wiki_type})", "other sites", "Linked page" + , "Labels", "Label" + , "Aliases", "Alias" + , "Descriptions", "Description" + , "Statements" + , "JSON" + , ", ", " ", ":" + , "+", "-", "±", "(~{0})" + , "—", "?" + , "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + , "~{0}", "~{0}0s", "~{0}. century", "~{0}. millenium", "~{0}0,000 years", "~{0}00,000 years", "~{0} million years", "~{0}0 million years", "~{0}00 million years", "~{0} billion years" + , "~{0} BC", "~{0} ago", "in ~{0}", "jul" + , "N", "S", "E", "W" + , "°", "′", "″" + , " m" + ); + return new Wdata_hwtr_msgs(brys); + } + public static Wdata_hwtr_msgs new_(Xow_msg_mgr msg_mgr) { + byte[][] brys = new_brys(msg_mgr + , "hide", "show", "toc" + , "wikibase-aliases-label" , "wikibase-aliases-empty" + , "wikibase-sitelinks-sitename-columnheading" , "wikibase-sitelinks-siteid-columnheading" + , "xowa-wikidata-sitelinks-hdr" , "xowa-wikidata-sitelinks-hdr-special", "wikibase-sitelinks-link-columnheading" + , "xowa-wikidata-labels-hdr" , "xowa-wikidata-labels-col" + , "xowa-wikidata-aliases-hdr" , "xowa-wikidata-aliases-col" + , "xowa-wikidata-descriptions-hdr" , "xowa-wikidata-descriptions-col" + , "wikibase-statements" + , "xowa-wikidata-json" + , "comma-separator", "word-separator", "xowa-wikidata-time-spr" + , "xowa-wikidata-plus", "xowa-wikidata-minus", "xowa-wikidata-plusminus" + , "parentheses" + , "xowa-wikidata-novalue", "xowa-wikidata-somevalue" + , "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" + , "xowa-wikidata-year", "xowa-wikidata-decade", "xowa-wikidata-century", "xowa-wikidata-millenium", "xowa-wikidata-years1e4", "xowa-wikidata-years1e5", "xowa-wikidata-years1e6", "xowa-wikidata-years1e7", "xowa-wikidata-years1e8", "xowa-wikidata-years1e9" + , "ago", "xowa-wikidata-bc", "xowa-wikidata-inTime" + , "xowa-wikidata-julian" + , "xowa-wikidata-north", "xowa-wikidata-south", "xowa-wikidata-east", "xowa-wikidata-west" + , "xowa-wikidata-degree", "xowa-wikidata-minute", "xowa-wikidata-second" + , "xowa-wikidata-meters" + ); + return new Wdata_hwtr_msgs(brys); + } + private static byte[][] new_brys(Xow_msg_mgr msg_mgr, String... ids) { + int len = ids.length; + byte[][] rv = new byte[len][]; + for (int i = 0; i < len; ++i) + rv[i] = msg_mgr.Val_by_key_obj(ids[i]); // TOMBSTONE: do not call "Gfh_utl.Escape_html_as_bry" else "jul" will be rendered literally; PAGE:wd:Q2 DATE:2016-11-10 + return rv; + } + private static byte[] Name_(byte[] v) {return Bry_.Ucase__1st(Bry_.Copy(v));} +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_lbl_itm.java b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_lbl_itm.java index a27517de8..e9b20d3ff 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_lbl_itm.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_lbl_itm.java @@ -13,3 +13,33 @@ 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.wbases.hwtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.xtns.wbases.core.*; +public class Wdata_lbl_itm { + public Wdata_lbl_itm(boolean is_pid, int id, boolean text_en_enabled) { + this.is_pid = is_pid; this.id = id; this.text_en_enabled = text_en_enabled; + this.ttl = Make_ttl(is_pid, id); + } + public boolean Is_pid() {return is_pid;} private final boolean is_pid; + public int Id() {return id;} private final int id; + public byte[] Ttl() {return ttl;} private final byte[] ttl; + public byte[] Lang() {return lang;} private byte[] lang; + public byte[] Text() {return text;} private byte[] text; + public byte[] Text_en() {return text_en;} public void Text_en_(byte[] v) {text_en = v;} private byte[] text_en = Bry_.Empty; + public boolean Text_en_enabled() {return text_en_enabled;} private boolean text_en_enabled; + public void Load_vals(byte[] lang, byte[] text) {this.lang = lang; this.text = text;} + public static byte[] Make_ttl(boolean is_pid, int id) { + return is_pid + ? Bry_.Add(Ttl_prefix_pid, Int_.To_bry(id)) + : Bry_.Add(Ttl_prefix_qid, Int_.To_bry(id)) + ; + } + private static final byte[] Ttl_prefix_pid = Bry_.new_a7("Property:P"), Ttl_prefix_qid = Bry_.new_a7("Q"); + private static final byte[] Extract_ttl_qid = Bry_.new_a7("http://www.wikidata.org/entity/"); + public static byte[] Extract_ttl(byte[] href) { + if (Bry_.Has_at_bgn(href, Extract_ttl_qid)) // qid + return Bry_.Mid(href, Extract_ttl_qid.length, href.length); + else // possibly support pid in future, but so far, nothing referencing just "Property:P##" + return null; + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_lbl_mgr.java b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_lbl_mgr.java index a27517de8..d94480af4 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_lbl_mgr.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_lbl_mgr.java @@ -13,3 +13,125 @@ 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.wbases.hwtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.core.primitives.*; +import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.xtns.wbases.claims.*; import gplx.xowa.xtns.wbases.claims.itms.*; +public class Wdata_lbl_mgr { + private Hash_adp_bry ttl_hash = Hash_adp_bry.ci_a7(); + private Hash_adp qid_hash = Hash_adp_.New(), pid_hash = Hash_adp_.New(); private Int_obj_ref int_hash_key = Int_obj_ref.New_neg1(); + private Wdata_visitor__lbl_gatherer lbl_gatherer; + public Wdata_lbl_mgr() { + lbl_gatherer = new Wdata_visitor__lbl_gatherer(this); + } + public void Clear() {ttl_hash.Clear(); qid_hash.Clear(); pid_hash.Clear(); queue.Clear();} + public List_adp Queue() {return queue;} private List_adp queue = List_adp_.New(); + @gplx.Internal protected void Wkr_(Wdata_lbl_wkr v) {this.wkr = v;} private Wdata_lbl_wkr wkr; + public Wdata_lbl_itm Get_itm__ttl(byte[] ttl) { + Wdata_lbl_itm rv = (Wdata_lbl_itm)ttl_hash.Get_by(ttl); + if (rv == null) Gfo_usr_dlg_.Instance.Warn_many("", "", "wdata.hwtr:unknown entity; ttl=~{0}", String_.new_u8(ttl)); // NOTE: should not happen + return rv; + } + public byte[] Get_text__ttl(byte[] ttl, byte[] or) { + Wdata_lbl_itm rv_itm = Get_itm__ttl(ttl); + return rv_itm == null ? or : rv_itm.Text(); + } + public byte[] Get_text__qid(int id) {return Get_text(Bool_.N, id);} + public byte[] Get_text__pid(int id) {return Get_text(Bool_.Y, id);} + private byte[] Get_text(boolean is_pid, int id) { + Hash_adp hash = is_pid ? pid_hash : qid_hash; + Wdata_lbl_itm rv_itm = (Wdata_lbl_itm)hash.Get_by(int_hash_key.Val_(id)); + if (rv_itm != null) return rv_itm.Text(); // found; return lbl + Gfo_usr_dlg_.Instance.Warn_many("", "", "wdata.hwtr:unknown entity; is_pid=~{0} id=~{1}", Yn.To_str(is_pid), id); // NOTE: should not happen + return Wdata_lbl_itm.Make_ttl(is_pid, id); // missing; return ttl; EX: "Property:P1", "Q1"; + } + public void Queue_if_missing__ttl(byte[] ttl) {Queue_if_missing__ttl(ttl, Bool_.N);} + public void Queue_if_missing__ttl(byte[] ttl, boolean get_en) { + if (ttl == null) {Gfo_usr_dlg_.Instance.Warn_many("", "", "wdata.hwtr:unknown href; href=~{0}", String_.new_u8(ttl)); return;} + boolean has = ttl_hash.Has(ttl); + if (!has) Queue_add(qid_hash, Bool_.N, Qid_int(ttl), get_en); + } + public void Queue_if_missing__qid(int id) {Queue_if_missing(Bool_.N, id);} + public void Queue_if_missing__pid(int id) {Queue_if_missing(Bool_.Y, id);} + private void Queue_if_missing(boolean is_pid, int id) { + Hash_adp hash = is_pid ? pid_hash : qid_hash; + boolean has = hash.Has(int_hash_key.Val_(id)); + if (!has) Queue_add(hash, is_pid, id, Bool_.N); + } + private void Queue_add(Hash_adp hash, boolean is_pid, int id, boolean get_en) { + Wdata_lbl_itm itm = new Wdata_lbl_itm(is_pid, id, get_en); + hash.Add(Int_obj_ref.New(id), itm); + ttl_hash.Add(itm.Ttl(), itm); + queue.Add(itm); + } + public void Resolve(Ordered_hash found) { + int len = queue.Count(); + for (int i = 0; i < len; ++i) { + Wdata_lbl_itm pending_itm = (Wdata_lbl_itm)queue.Get_at(i); + Wdata_langtext_itm found_itm = (Wdata_langtext_itm)found.Get_by(pending_itm.Ttl()); + if (found_itm != null) + pending_itm.Load_vals(found_itm.Lang(), found_itm.Text()); + } + queue.Clear(); + } + public void Gather_labels(Wdata_doc wdoc, Wdata_lang_sorter sorter) { + Ordered_hash claim_list = wdoc.Claim_list(); + int len = claim_list.Count(); + for (int i = 0; i < len; ++i) { + Wbase_claim_grp grp = (Wbase_claim_grp)claim_list.Get_at(i); + int grp_len = grp.Len(); + for (int j = 0; j < grp_len; ++j) { + Wbase_claim_base itm = (Wbase_claim_base)grp.Get_at(j); + this.Queue_if_missing__pid(itm.Pid()); + itm.Welcome(lbl_gatherer); + Wbase_claim_grp_list qual_list = itm.Qualifiers(); + if (qual_list != null) { + int qual_list_len = qual_list.Len(); + for (int k = 0; k < qual_list_len; ++k) { + Wbase_claim_grp qual_grp = qual_list.Get_at(k); + int qual_grp_len = qual_grp.Len(); + for (int m = 0; m < qual_grp_len; ++m) { + Wbase_claim_base qual = qual_grp.Get_at(m); + this.Queue_if_missing__pid(qual.Pid()); + qual.Welcome(lbl_gatherer); + } + } + } + Wbase_references_grp[] ref_grp_ary = itm.References(); + if (ref_grp_ary != null) { + int ref_grp_ary_len = ref_grp_ary.length; + for (int k = 0; k < ref_grp_ary_len; ++k) { + Wbase_references_grp ref_grp = ref_grp_ary[k]; + Wbase_claim_grp_list ref_list = ref_grp.References(); + int ref_list_len = ref_list.Len(); + for (int m = 0; m < ref_list_len; ++m) { + Wbase_claim_grp claim_grp = ref_list.Get_at(m); + int claim_grp_len = claim_grp.Len(); + for (int n = 0; n < claim_grp_len; ++n) { + Wbase_claim_base claim = claim_grp.Get_at(n); + this.Queue_if_missing__pid(claim.Pid()); + claim.Welcome(lbl_gatherer); + } + } + } + } + } + } + Ordered_hash slink_list = wdoc.Slink_list(); + len = slink_list.Count(); + for (int i = 0; i < len; ++i) { + Wdata_sitelink_itm itm = (Wdata_sitelink_itm)slink_list.Get_at(i); + byte[][] badges = itm.Badges(); + int badges_len = badges.length; + for (int j = 0; j < badges_len; ++j) { + byte[] badge = badges[j]; + this.Queue_if_missing__ttl(badge, Bool_.Y); // badges has qid; EX: ["Q1", "Q2"] + } + } + wkr.Resolve(this, sorter); + } + public static int Qid_int(byte[] qid) { + byte qid_0 = qid[0]; + if (qid_0 != Byte_ascii.Ltr_Q && qid_0 != Byte_ascii.Ltr_q) return -1; + return Bry_.To_int_or(qid, 1, qid.length, -1); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_lbl_wkr.java b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_lbl_wkr.java index a27517de8..93c86960e 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_lbl_wkr.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_lbl_wkr.java @@ -13,3 +13,8 @@ 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.wbases.hwtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.xtns.wbases.core.*; +public interface Wdata_lbl_wkr { + void Resolve(Wdata_lbl_mgr lbl_mgr, Wdata_lang_sorter sorter); +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_lbl_wkr_wiki.java b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_lbl_wkr_wiki.java index a27517de8..d13c39085 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_lbl_wkr_wiki.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_lbl_wkr_wiki.java @@ -13,3 +13,36 @@ 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.wbases.hwtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.langs.*; +import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.apps.apis.xowa.xtns.*; +public class Wdata_lbl_wkr_wiki implements Wdata_lbl_wkr { + private Wdata_wiki_mgr wdata_mgr; + private Xoapi_wikibase wikibase_api; + public Wdata_lbl_wkr_wiki(Xoapi_wikibase wikibase_api, Wdata_wiki_mgr wdata_mgr) {this.wikibase_api = wikibase_api; this.wdata_mgr = wdata_mgr;} + public void Resolve(Wdata_lbl_mgr lbl_mgr, Wdata_lang_sorter sorter) { + List_adp queue = lbl_mgr.Queue(); + int len = queue.Count(); + for (int i = 0; i < len; ++i) { + Wdata_lbl_itm itm = (Wdata_lbl_itm)queue.Get_at(i); + Wdata_doc wdoc = wdata_mgr.Doc_mgr.Get_by_xid_or_null(itm.Ttl()); + if (wdoc == null) { + Xoa_app_.Usr_dlg().Warn_many("", "", "wbase.lbl_wkr:page does not exists; page=~{0}", itm.Ttl()); + continue; // handle incomplete wikidata dumps; DATE:2015-06-11 + } + Ordered_hash labels = wdoc.Label_list(); + if (labels.Count() == 0) continue; + labels.Sort_by(sorter); + Wdata_langtext_itm label = Wdata_langtext_itm.Get_itm_or_null(wdoc.Label_list(), wikibase_api.Core_langs()); + if (label == null) + itm.Load_vals(Bry_.Empty, itm.Ttl()); // NOTE: use itm.Ttl() in case no label is found for the core_lang + else { + itm.Load_vals(label.Lang(), label.Text()); + if (itm.Text_en_enabled()) { + Wdata_langtext_itm en_label = (Wdata_langtext_itm)labels.Get_by(Xol_lang_itm_.Key_en); + itm.Text_en_(en_label == null ? Bry_.Empty : en_label.Text()); + } + } + } + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_slink_grp.java b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_slink_grp.java index a27517de8..bd54765e4 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_slink_grp.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_slink_grp.java @@ -13,3 +13,69 @@ 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.wbases.hwtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.apps.apis.xowa.html.*; +import gplx.xowa.wikis.domains.*; +class Wdata_slink_grp { + public Wdata_slink_grp(int tid, byte[] wiki_name, Xoapi_toggle_itm toggle_itm, Wdata_toc_data toc_data) { + this.tid = tid; this.wiki_name = wiki_name; this.toggle_itm = toggle_itm; this.toc_data = toc_data; + } + public int Tid() {return tid;} private final int tid; + public byte[] Wiki_name() {return wiki_name;} private final byte[] wiki_name; + public Wdata_toc_data Toc_data() {return toc_data;} private final Wdata_toc_data toc_data; + public Xoapi_toggle_itm Toggle_itm() {return toggle_itm;} private Xoapi_toggle_itm toggle_itm; + public Ordered_hash Rows() {return rows;} private final Ordered_hash rows = Ordered_hash_.New(); + public static void Sift(Wdata_slink_grp[] rv, Ordered_hash list) { + for (int i = 0; i < Idx__len; ++i) + rv[i].Rows().Clear(); + int list_len = list.Count(); + for (int i = 0; i < list_len; ++i) { + Wdata_sitelink_itm itm = (Wdata_sitelink_itm)list.Get_at(i); + int idx = Idx_by_tid(itm.Domain_info().Domain_type_id()); + rv[idx].Rows().Add(itm.Site(), itm); + } + } + public static int Idx_by_tid(int tid) { + switch (tid) { + case Xow_domain_tid_.Tid__wikipedia: return Idx_w; + case Xow_domain_tid_.Tid__wiktionary: return Idx_d; + case Xow_domain_tid_.Tid__wikisource: return Idx_s; + case Xow_domain_tid_.Tid__wikivoyage: return Idx_v; + case Xow_domain_tid_.Tid__wikiquote: return Idx_q; + case Xow_domain_tid_.Tid__wikibooks: return Idx_b; + case Xow_domain_tid_.Tid__wikiversity: return Idx_u; + case Xow_domain_tid_.Tid__wikinews: return Idx_n; + default: return Idx_x; + } + } + public static byte[] Msg_by_tid(Wdata_hwtr_msgs msgs, int tid) { + switch (tid) { + case Idx_w: return msgs.Slink_tbl_hdr_w(); + case Idx_d: return msgs.Slink_tbl_hdr_d(); + case Idx_s: return msgs.Slink_tbl_hdr_s(); + case Idx_v: return msgs.Slink_tbl_hdr_v(); + case Idx_q: return msgs.Slink_tbl_hdr_q(); + case Idx_b: return msgs.Slink_tbl_hdr_b(); + case Idx_u: return msgs.Slink_tbl_hdr_u(); + case Idx_n: return msgs.Slink_tbl_hdr_n(); + case Idx_x: return msgs.Slink_tbl_hdr_x(); + default: throw Err_.new_unhandled(tid); + } + } + public static byte[] Name_by_tid(int idx) { + switch (idx) { + case Idx_w: return Xow_domain_tid_.Bry__wikipedia; + case Idx_d: return Xow_domain_tid_.Bry__wiktionary; + case Idx_s: return Xow_domain_tid_.Bry__wikisource; + case Idx_v: return Xow_domain_tid_.Bry__wikivoyage; + case Idx_q: return Xow_domain_tid_.Bry__wikiquote; + case Idx_b: return Xow_domain_tid_.Bry__wikibooks; + case Idx_u: return Xow_domain_tid_.Bry__wikiversity; + case Idx_n: return Xow_domain_tid_.Bry__wikinews; + case Idx_x: return Name_special; + default: throw Err_.new_unhandled(idx); + } + } + public static final int Idx__len = 9, Idx_w = 0, Idx_d = 1, Idx_s = 2, Idx_v = 3, Idx_q = 4, Idx_b = 5, Idx_u = 6, Idx_n = 7, Idx_x = 8; + private static final byte[] Name_special = Bry_.new_a7("special"); +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_toc_data.java b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_toc_data.java index a27517de8..bd99bebbf 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_toc_data.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_toc_data.java @@ -13,3 +13,23 @@ 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.wbases.hwtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.core.brys.fmtrs.*; +import gplx.langs.htmls.encoders.*; +class Wdata_toc_data { + private final Wdata_fmtr__toc_div fmtr_toc; + private final Gfo_url_encoder href_encoder; + private final Bry_fmtr text_fmtr = Bry_fmtr.new_("~{orig} (~{len})", "orig", "len"); + private final Bry_bfr tmp_bfr = Bry_bfr_.New_w_size(8); + public Wdata_toc_data(Wdata_fmtr__toc_div fmtr_toc, Gfo_url_encoder href_encoder) {this.fmtr_toc = fmtr_toc; this.href_encoder = href_encoder;} + public Wdata_toc_data Make(int itms_len) { + this.text = itms_len_enable ? text_fmtr.Bld_bry_many(tmp_bfr, orig, itms_len) : orig; + this.href = href_encoder.Encode(orig); + fmtr_toc.Add(this); + return this; + } + public Wdata_toc_data Itms_len_enable_n_() {itms_len_enable = false; return this;} private boolean itms_len_enable = true; + public byte[] Orig() {return orig;} public void Orig_(byte[] v) {orig = v;} private byte[] orig; + public byte[] Href() {return href;} private byte[] href; + public byte[] Text() {return text;} private byte[] text; +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_visitor__html_wtr.java b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_visitor__html_wtr.java index a27517de8..e9eed0c04 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_visitor__html_wtr.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_visitor__html_wtr.java @@ -13,3 +13,49 @@ 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.wbases.hwtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.core.brys.fmtrs.*; +import gplx.xowa.langs.*; +import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.xtns.wbases.claims.*; import gplx.xowa.xtns.wbases.claims.enums.*; import gplx.xowa.xtns.wbases.claims.itms.*; +class Wdata_visitor__html_wtr implements Wbase_claim_visitor { + private Wdata_wiki_mgr wdata_mgr; private Wdata_hwtr_msgs msgs; private Wdata_lbl_mgr lbl_mgr; + private Xol_lang_itm lang; + private byte[] ttl; private Bry_bfr tmp_bfr; + private final Bry_fmtr tmp_time_fmtr = Bry_fmtr.new_(); private final Bry_bfr tmp_time_bfr = Bry_bfr_.New_w_size(32); + public Wdata_visitor__html_wtr Init(Bry_bfr tmp_bfr, Wdata_wiki_mgr wdata_mgr, Wdata_hwtr_msgs msgs, Wdata_lbl_mgr lbl_mgr, Xol_lang_itm lang, byte[] ttl) { + this.wdata_mgr = wdata_mgr; this.msgs = msgs; this.lbl_mgr = lbl_mgr; this.lang = lang; + this.tmp_bfr = tmp_bfr; this.ttl = ttl; + return this; + } + public void Visit_str(Wbase_claim_string itm) { + tmp_bfr.Add(itm.Val_bry()); + } + public void Visit_entity(Wbase_claim_entity itm) { + int entity_id = itm.Entity_id(); + byte[] text = itm.Entity_tid_is_qid() ? lbl_mgr.Get_text__qid(entity_id) : lbl_mgr.Get_text__pid(entity_id); + if (text == null) {// handle incomplete wikidata dumps; DATE:2015-06-11 + Xoa_app_.Usr_dlg().Warn_many("", "", "wbase.html_visitor:page does not exists; page=~{0}", entity_id); + return; + } + Wdata_hwtr_mgr.Write_link_wikidata(tmp_bfr, itm.Page_ttl_gui(), text); + } + public void Visit_monolingualtext(Wbase_claim_monolingualtext itm) { + tmp_bfr.Add(itm.Text()); + tmp_bfr.Add_byte(Byte_ascii.Space).Add_byte(Byte_ascii.Brack_bgn).Add(itm.Lang()).Add_byte(Byte_ascii.Brack_end); + } + public void Visit_quantity(Wbase_claim_quantity itm) { + Wdata_prop_val_visitor.Write_quantity(tmp_bfr, wdata_mgr, lang, itm.Amount(), itm.Lbound(), itm.Ubound(), itm.Unit()); + } + public void Visit_globecoordinate(Wbase_claim_globecoordinate itm) { + Wdata_prop_val_visitor.Write_geo(Bool_.Y, tmp_bfr, lbl_mgr, msgs, itm.Lat(), itm.Lng(), itm.Alt(), itm.Prc(), itm.Glb()); + } + public void Visit_time(Wbase_claim_time itm) { + itm.Write_to_bfr(tmp_bfr, tmp_time_bfr, tmp_time_fmtr, msgs, ttl); + } + public void Visit_system(Wbase_claim_value itm) { + switch (itm.Snak_tid()) { + case Wbase_claim_value_type_.Tid__somevalue: tmp_bfr.Add(msgs.Val_tid_somevalue()); break; + case Wbase_claim_value_type_.Tid__novalue: tmp_bfr.Add(msgs.Val_tid_novalue()); break; + } + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_visitor__html_wtr_tst.java b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_visitor__html_wtr_tst.java index a27517de8..0abc1d4ec 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_visitor__html_wtr_tst.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_visitor__html_wtr_tst.java @@ -13,3 +13,89 @@ 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.wbases.hwtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import org.junit.*; +import gplx.langs.jsons.*; import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.xtns.wbases.claims.itms.times.*; import gplx.xowa.xtns.wbases.parsers.*; import gplx.xowa.apps.apis.xowa.html.*; +public class Wdata_visitor__html_wtr_tst { + @Before public void init() {fxt.init();} private Wdata_hwtr_mgr_fxt fxt = new Wdata_hwtr_mgr_fxt(); + @Test public void Monolingualtext() { + fxt + .Test_claim_val + ( fxt.Wdata_fxt().Make_claim_monolingual(1, "en", "Motto") + , "Motto [en]" + ); + } + @Test public void Time() { + fxt + .Test_claim_val + ( fxt.Wdata_fxt().Make_claim_time(1, "2001-02-03 04:05:06", Wbase_date.Fmt_ymdhns) + , "4:05:06 3 Feb 2001" + ); + } + @Test public void Time__julian() { + fxt + .Test_claim_val + ( fxt.Wdata_fxt().Make_claim_time(1, "2001-02-03 04:05:06", Bry_.Empty, Bry_.new_a7("http://www.wikidata.org/entity/Q1985786")) + , "4:05:06 3 Feb 2001" + ); + } + @Test public void Quantity_ubound_lbound() { + fxt + .Test_claim_val + ( fxt.Wdata_fxt().Make_claim_quantity(1, "50", "", "60", "30") + , "30-60" + ); + } + @Test public void Quantity_same() { + fxt + .Test_claim_val + ( fxt.Wdata_fxt().Make_claim_quantity(1, "50", "1", "60", "40") + , "50±10" + ); + } + @Test public void Quantity_frac() { + fxt + .Test_claim_val + ( fxt.Wdata_fxt().Make_claim_quantity(1, "+0.1234", "1", "+0.1235", "+0.1233") + , "0.1234±0.0001" + ); + } + @Test public void Entity_qid() { + fxt + .Init_resolved_qid(1, "item_1") + .Test_claim_val + ( fxt.Wdata_fxt().Make_claim_entity_qid(1, 1) + , "item_1" + ); + } + @Test public void Entity_pid() { + fxt + .Init_resolved_pid(1, "item_1") + .Test_claim_val + ( fxt.Wdata_fxt().Make_claim_entity_pid(1, 1) + , "item_1" + ); + } + @Test public void Globecoordinate() { + fxt + .Init_resolved_qid(2, "Earth") + .Test_claim_val + ( fxt.Wdata_fxt().Make_claim_geo(1, "51.5072222", "-0.1275", ".000027777", "123", "http://www.wikidata.org/entity/Q2") + , "0°7'39"S, 51°30'26"E (Earth)" + ); + } + @Test public void Globecoordinate__globe__null() { + fxt + .Test_claim_val + ( fxt.Wdata_fxt().Make_claim_geo(1, "51.5072222", "-0.1275", ".000027777", "null", "") + , "0°7'39"S, 51°30'26"E" + ); + } + @Test public void Globecoordinate__precision__0() { // PURPOSE: 0 precision was causing divide by 0 error; PAGE:ru.w:Лысково_(Калужская_область) DATE:2016-11-24 + fxt + .Test_claim_val + ( fxt.Wdata_fxt().Make_claim_geo(1, "51.5072222", "-0.1275", "0", "null", "") + , "0°6'S, 51°30'E" + ); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_visitor__lbl_gatherer.java b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_visitor__lbl_gatherer.java index a27517de8..d4ae413b8 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_visitor__lbl_gatherer.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/hwtrs/Wdata_visitor__lbl_gatherer.java @@ -13,3 +13,29 @@ 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.wbases.hwtrs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.xtns.wbases.claims.*; import gplx.xowa.xtns.wbases.claims.itms.*; +class Wdata_visitor__lbl_gatherer implements Wbase_claim_visitor { + private Wdata_lbl_mgr lbl_mgr; + public Wdata_visitor__lbl_gatherer(Wdata_lbl_mgr lbl_mgr) {this.lbl_mgr = lbl_mgr;} + public void Visit_entity(Wbase_claim_entity itm) { + if (itm.Entity_tid_is_qid()) + lbl_mgr.Queue_if_missing__qid(itm.Entity_id()); + else + lbl_mgr.Queue_if_missing__pid(itm.Entity_id()); + } + public void Visit_time(Wbase_claim_time itm) { + byte[] ttl = Wdata_lbl_itm.Extract_ttl(itm.Calendar()); + itm.Calendar_ttl_(ttl); + lbl_mgr.Queue_if_missing__ttl(ttl); + } + public void Visit_globecoordinate(Wbase_claim_globecoordinate itm) { + byte[] ttl = Wdata_lbl_itm.Extract_ttl(itm.Glb()); + itm.Glb_ttl_(ttl); + lbl_mgr.Queue_if_missing__ttl(ttl); + } + public void Visit_str(Wbase_claim_string itm) {} + public void Visit_monolingualtext(Wbase_claim_monolingualtext itm) {} + public void Visit_quantity(Wbase_claim_quantity itm) {} + public void Visit_system(Wbase_claim_value itm) {} +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Wdata_idx_mgr_base.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Wdata_idx_mgr_base.java index a27517de8..618030809 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Wdata_idx_mgr_base.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Wdata_idx_mgr_base.java @@ -13,3 +13,28 @@ 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.wbases.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.langs.jsons.*; import gplx.core.ios.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.parsers.*; +import gplx.xowa.xtns.wbases.imports.*; +abstract class Wdata_idx_mgr_base { + public void Ctor(Xob_itm_dump_base wkr, Xob_bldr bldr, Xowe_wiki wiki, int dump_fil_len) { + this.wkr = wkr; this.wiki = wiki; this.bldr = bldr; this.dump_fil_len = dump_fil_len; + } Xob_itm_dump_base wkr; protected Xowe_wiki wiki; Xob_bldr bldr; Xol_csv_parser csv_parser = Xol_csv_parser.Instance; protected Ordered_hash hash = Ordered_hash_.New(); protected int dump_fil_len; + public void Flush() { + int len = hash.Count(); + for (int i = 0; i < len; i++) { + Wdata_idx_wtr wtr = (Wdata_idx_wtr)hash.Get_at(i); + wtr.Flush(); + } + } + public void Make() { + int len = hash.Count(); + for (int i = 0; i < len; i++) { + Wdata_idx_wtr wtr = (Wdata_idx_wtr)hash.Get_at(i); + wtr.Make(bldr.Usr_dlg(), wkr.Make_fil_len()); + } + if (wkr.Delete_temp()) Io_mgr.Instance.DeleteDirDeep(wkr.Temp_dir()); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Wdata_idx_wtr.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Wdata_idx_wtr.java index a27517de8..2f3984929 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Wdata_idx_wtr.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Wdata_idx_wtr.java @@ -13,3 +13,40 @@ 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.wbases.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.core.ios.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.texts.tdbs.*; +class Wdata_idx_wtr { + public Wdata_idx_wtr(Io_url dump_dir, int dump_fil_max, Io_url make_dir) { + this.dump_dir = dump_dir; + this.dump_fil_max = dump_fil_max; + dump_url_gen = Io_url_gen_.dir_(dump_dir); + this.make_dir = make_dir; + } private Bry_bfr dump_bfr = Bry_bfr_.Reset(2 * Io_mgr.Len_kb); int dump_fil_max; Io_url dump_dir, make_dir; Io_url_gen dump_url_gen; + public void Write(byte[] ttl, byte[] qid) { + if (dump_bfr.Len() + ttl.length + qid.length + 2 > dump_fil_max) Flush(); // 2 = "|" + "\n"; NOTE: all items have format of "data|qid\n" + dump_bfr.Add(ttl).Add_byte_pipe().Add(qid).Add_byte_nl(); + } + public void Flush() { + Io_mgr.Instance.AppendFilBfr(dump_url_gen.Nxt_url(), dump_bfr); + } + public void Make(Gfo_usr_dlg usr_dlg, int make_fil_len) { + Xobdc_merger.Basic(usr_dlg, dump_url_gen, dump_dir.OwnerDir().GenSubDir("sort"), dump_fil_max, Io_line_rdr_key_gen_.first_pipe, new Xob_make_cmd_site(usr_dlg, make_dir, make_fil_len)); + } + public static Wdata_idx_wtr new_qid_(Xowe_wiki wdata_wiki, String wiki_str, String ns_num, int dump_fil_max) { + Io_url dump_dir = wdata_wiki.Fsys_mgr().Tmp_dir().GenSubDir_nest("wdata.qid", "qid", wiki_str, ns_num, "dump"); // /xowa/wiki/www.wikidata.org/tmp/wdata_qid/ + enwiki/000/dump/ + Io_url make_dir = dir_qid_(wdata_wiki, wiki_str, ns_num); // /xowa/wiki/www.wikidata.org/site/data/qid/ + enwiki/000/ + return new Wdata_idx_wtr(dump_dir, dump_fil_max, make_dir); + } + public static Wdata_idx_wtr new_pid_(Xowe_wiki wdata_wiki, String lang_key, int dump_fil_max) { + Io_url dump_dir = wdata_wiki.Fsys_mgr().Tmp_dir().GenSubDir_nest("wdata.pid", "pid", lang_key, "dump"); // /xowa/wiki/www.wikidata.org/tmp/wdata_pid/ + en/ + Io_url make_dir = dir_pid_(wdata_wiki, lang_key); // /xowa/wiki/www.wikidata.org/site/data/pid/ + en/ + return new Wdata_idx_wtr(dump_dir, dump_fil_max, make_dir); + } + public static Io_url dir_qid_(Xowe_wiki wiki, String wiki_str, String ns_num) { + return wiki.Tdb_fsys_mgr().Site_dir().GenSubDir_nest("data", "qid", wiki_str, ns_num); // /xowa/wiki/www.wikidata.org/site/data/ + qid/enwiki/000/ + } + public static Io_url dir_pid_(Xowe_wiki wiki, String lang_key) { + return wiki.Tdb_fsys_mgr().Site_dir().GenSubDir_nest("data", "pid", lang_key); // /xowa/wiki/www.wikidata.org/site/data/ + pid/en/ + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wbase_ns_parser.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wbase_ns_parser.java index a27517de8..cd472d598 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wbase_ns_parser.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wbase_ns_parser.java @@ -13,3 +13,37 @@ 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.wbases.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.dbs.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.domains.*; +import gplx.xowa.bldrs.wms.sites.*; +class Xob_wbase_ns_parser { + private final Hash_adp_bry ns_mgr_hash = Hash_adp_bry.cs(); + private final Site_core_db core_db; + public Xob_wbase_ns_parser(Io_url url) { + this.core_db = new Site_core_db(url); + } + public void Find(Xob_wbase_ns_parser_rslt rv, byte[] wiki_abrv, byte[] ttl) { // enwiki, Category:Abc + Xow_ns_mgr ns_mgr = (Xow_ns_mgr)ns_mgr_hash.Get_by_bry(wiki_abrv); + rv.Init(Xow_ns_.Tid__main, 0); // default to Main ns + int ttl_len = ttl.length; + int colon_pos = Bry_find_.Find_fwd(ttl, Byte_ascii.Colon, 0, ttl_len); if (colon_pos == Bry_find_.Not_found) return; + if (ns_mgr == null) { // ns_mgr not found; load from db + wiki_abrv = Bry_.Replace(wiki_abrv, Byte_ascii.Underline, Byte_ascii.Dash); + byte[] wiki_domain = Xow_abrv_wm_.Parse_to_domain_bry(wiki_abrv); + ns_mgr = core_db.Load_namespace(wiki_domain); + if (ns_mgr.Count() == 0) {Xoa_app_.Usr_dlg().Warn_many("", "", "wbase.ns_parser:no ns found; abrv=~{0}", wiki_abrv); return;} + ns_mgr_hash.Add_bry_obj(wiki_abrv, ns_mgr); + } + Xow_ns ns = ns_mgr.Names_get_or_null(ttl, 0, colon_pos); if (ns == null) return; // not a ns; EX: "No_namespace:Page_title" + rv.Init(ns.Id(), colon_pos + 1); + } +} +class Xob_wbase_ns_parser_rslt { + public int Ns_id() {return ns_id;} private int ns_id; + public int Ttl_bgn() {return ttl_bgn;} private int ttl_bgn; + public void Init(int ns_id, int ttl_bgn) { + this.ns_id = ns_id; this.ttl_bgn = ttl_bgn; + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_db_cmd.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_db_cmd.java index a27517de8..3cdee38ae 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_db_cmd.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_db_cmd.java @@ -13,3 +13,410 @@ 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.wbases.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.dbs.*; import gplx.dbs.cfgs.*; import gplx.dbs.engines.sqlite.*; import gplx.xowa.bldrs.*; import gplx.xowa.files.fsdb.*; import gplx.xowa.files.origs.*; +import gplx.xowa.bldrs.wkrs.*; +import gplx.langs.jsons.*; +import gplx.xowa.langs.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.bldrs.cmds.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.xtns.wbases.*; import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.xtns.wbases.claims.*; import gplx.xowa.xtns.wbases.claims.itms.*; +public class Xob_wdata_db_cmd extends Xob_dump_mgr_base implements Xob_cmd { + private Wdata_tbl_mgr tbl_mgr = new Wdata_tbl_mgr(); + private Wdata_wiki_mgr wdata_mgr; private Json_parser json_parser; + private byte[] lang_key = Xol_lang_itm_.Key_en; + public Xob_wdata_db_cmd(Xob_bldr bldr, Xowe_wiki wiki) {this.Cmd_ctor(bldr, wiki);} + @Override public String Cmd_key() {return Xob_cmd_keys.Key_wbase_db;} + @Override public byte Init_redirect() {return Bool_.N_byte;} // json will never be found in a redirect + @Override public int[] Init_ns_ary() {return Int_ary_.New(Xow_ns_.Tid__main, Wdata_wiki_mgr.Ns_property);} + @Override protected void Init_reset(Db_conn conn) { + Db_cfg_tbl cfg_tbl = gplx.xowa.wikis.data.Xowd_cfg_tbl_.New(conn); + cfg_tbl.Delete_all(); + } + @Override protected Db_conn Init_db_file() { + Xob_db_file tbl_file = Xob_db_file.New(wiki.Fsys_mgr().Root_dir(), "wdata_db.sqlite3"); + Db_conn conn = tbl_file.Conn(); + tbl_mgr.Init(conn); + return conn; + } + @Override protected void Cmd_bgn_end() { + wdata_mgr = bldr.App().Wiki_mgr().Wdata_mgr(); + json_parser = wdata_mgr.Jdoc_parser(); + tbl_mgr.Conn().Txn_bgn("bldr__wdata_db"); + } + @Override public void Exec_pg_itm_hook(int ns_ord, Xow_ns ns, Xowd_page_itm page, byte[] page_src) { + Json_doc jdoc = json_parser.Parse(page_src); if (jdoc == null) return; // not a json document + Wdata_doc wdoc = new Wdata_doc(page.Ttl_page_db(), wdata_mgr, jdoc); + tbl_mgr.Exec_insert_by_wdoc(lang_key, wdata_mgr, page.Id(), wdoc); + } + @Override public void Exec_commit_hook() { + tbl_mgr.Conn().Txn_sav(); + } + @Override public void Exec_end_hook() { + tbl_mgr.Term(usr_dlg); + } +} +class Wdata_tbl_mgr { + private Wdata_tbl_base[] tbls; private int tbls_len; + public Wdata_tbl_mgr() { + tbls = new Wdata_tbl_base[] {label_tbl, alias_tbl, description_tbl, link_tbl, claim_tbl, claim_time_tbl, claim_geo_tbl}; + tbls_len = tbls.length; + } + public Db_conn Conn() {return conn;} private Db_conn conn; + public Wdata_label_tbl Label_tbl() {return label_tbl;} private Wdata_label_tbl label_tbl = new Wdata_label_tbl(); + public Wdata_alias_tbl Alias_tbl() {return alias_tbl;} private Wdata_alias_tbl alias_tbl = new Wdata_alias_tbl(); + public Wdata_description_tbl Description_tbl() {return description_tbl;} private Wdata_description_tbl description_tbl = new Wdata_description_tbl(); + public Wdata_link_tbl Link_tbl() {return link_tbl;} private Wdata_link_tbl link_tbl = new Wdata_link_tbl(); + public Wbase_claim_tbl Claim_tbl() {return claim_tbl;} private Wbase_claim_tbl claim_tbl = new Wbase_claim_tbl(); + public Wbase_claim_time_tbl Claim_time_tbl() {return claim_time_tbl;} private Wbase_claim_time_tbl claim_time_tbl = new Wbase_claim_time_tbl(); + public Wbase_claim_geo_tbl Claim_geo_tbl() {return claim_geo_tbl;} private Wbase_claim_geo_tbl claim_geo_tbl = new Wbase_claim_geo_tbl(); + public void Init(Db_conn conn) { + this.conn = conn; + for (int i = 0; i < tbls_len; i++) + tbls[i].Init(conn); + } + public void Exec_insert_by_wdoc(byte[] lang_key, Wdata_wiki_mgr wdata_mgr, int page_id, Wdata_doc wdoc) { + for (int i = 0; i < tbls_len; i++) + tbls[i].Exec_insert_by_wdoc(lang_key, wdata_mgr, page_id, wdoc); + } + public void Term(Gfo_usr_dlg usr_dlg) { + conn.Txn_end(); + for (int i = 0; i < tbls_len; i++) + tbls[i].Make_idxs(usr_dlg, conn); + } +} +abstract class Wdata_tbl_base { + public abstract String Tbl_name(); + public abstract String Tbl_create_sql(); + public abstract Db_idx_itm[] Idx_ary(); + public abstract String[] Fld_ary(); + @gplx.Virtual public void Exec_insert_by_wdoc(byte[] lang_key, Wdata_wiki_mgr wdata_mgr, int page_id, Wdata_doc wdoc) {} + public void Make_tbl(Db_conn p) {Sqlite_engine_.Tbl_create(p, this.Tbl_name(), this.Tbl_create_sql());} + public void Make_idxs(Gfo_usr_dlg usr_dlg, Db_conn p) { + Sqlite_engine_.Idx_create(usr_dlg, p, this.Tbl_name(), this.Idx_ary()); + } + public Db_stmt Make_insert_stmt(Db_conn p) {return Db_stmt_.new_insert_(p, this.Tbl_name(), this.Fld_ary());} + public Db_stmt Insert_stmt() {return insert_stmt;} private Db_stmt insert_stmt; + public void Init(Db_conn conn) { + this.Make_tbl(conn); + insert_stmt = this.Make_insert_stmt(conn); + } + public static void Exec_insert_kvs(Db_stmt stmt, int page_id, Ordered_hash hash) { + int len = hash.Count(); + for (int i = 0; i < len; i++) { + Json_kv kv = (Json_kv)hash.Get_at(i); + stmt.Clear() + .Val_int(page_id) + .Val_bry_as_str(kv.Key().Data_bry()) + .Val_bry_as_str(kv.Val().Data_bry()) + .Exec_insert(); + } + } +} +class Wdata_label_tbl extends Wdata_tbl_base { + @Override public String Tbl_name() {return "wdata_label";} + @Override public String Tbl_create_sql() { + return String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS wdata_label" + , "( page_id integer NOT NULL" + , ", lang_key varchar(16) NOT NULL" + , ", val varchar(255) NOT NULL" + , ");" + ); + } + @Override public Db_idx_itm[] Idx_ary() {return new Db_idx_itm[] {Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS wdata_label__main ON wdata_label (page_id, lang_key);")};} + @Override public String[] Fld_ary() {return new String[] {Fld_page_id, Fld_lang_key, Fld_val};} + @Override public void Exec_insert_by_wdoc(byte[] lang_key, Wdata_wiki_mgr wdata_mgr, int page_id, Wdata_doc wdoc) {Exec_insert_kvs(this.Insert_stmt(), page_id, wdoc.Label_list());} + private static final String Fld_page_id = "page_id", Fld_lang_key = "lang_key", Fld_val = "val"; +} +class Wdata_alias_tbl extends Wdata_tbl_base { + @Override public String Tbl_name() {return "wdata_alias";} + @Override public String Tbl_create_sql() { + return String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS wdata_alias" + , "( page_id integer NOT NULL" + , ", lang_key varchar(16) NOT NULL" + , ", val varchar(255) NOT NULL" + , ");" + ); + } + @Override public Db_idx_itm[] Idx_ary() {return new Db_idx_itm[] {Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS wdata_alias__main ON wdata_alias (page_id, lang_key);")};} + @Override public String[] Fld_ary() {return new String[] {Fld_page_id, Fld_lang_key, Fld_val};} + @Override public void Exec_insert_by_wdoc(byte[] lang_key, Wdata_wiki_mgr wdata_mgr, int page_id, Wdata_doc wdoc) { + Ordered_hash hash = wdoc.Alias_list(); + int len = hash.Count(); + Db_stmt insert_stmt = this.Insert_stmt(); + for (int i = 0; i < len; i++) { + Json_kv kv = (Json_kv)hash.Get_at(i); + byte[] key = kv.Key().Data_bry(); + Json_grp val_grp = (Json_grp)kv.Val(); + int val_grp_len = val_grp.Len(); + for (int j = 0; j < val_grp_len; j++) { + Json_itm val_itm = val_grp.Get_at(j); + byte[] val = Bry_.Empty; + if (val_itm.Tid() == Json_itm_.Tid__str) + val = val_itm.Data_bry(); + else if (val_itm.Tid() == Json_itm_.Tid__kv) { // EX: q80 and de aliases + val = ((Json_kv)val_itm).Val().Data_bry(); + } + insert_stmt.Clear() + .Val_int(page_id) + .Val_bry_as_str(key) + .Val_bry_as_str(val) + .Exec_insert(); + } + } + } + private static final String Fld_page_id = "page_id", Fld_lang_key = "lang_key", Fld_val = "val"; +} +class Wdata_description_tbl extends Wdata_tbl_base { + @Override public String Tbl_name() {return "wdata_description";} + @Override public String Tbl_create_sql() { + return String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS wdata_description" + , "( page_id integer NOT NULL" + , ", lang_key varchar(16) NOT NULL" + , ", val varchar(255) NOT NULL" + , ");" + ); + } + @Override public Db_idx_itm[] Idx_ary() {return new Db_idx_itm[] {Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS wdata_description__main ON wdata_description (page_id, lang_key);")};} + @Override public void Exec_insert_by_wdoc(byte[] lang_key, Wdata_wiki_mgr wdata_mgr, int page_id, Wdata_doc wdoc) {Exec_insert_kvs(this.Insert_stmt(), page_id, wdoc.Descr_list());} + @Override public String[] Fld_ary() {return new String[] {Fld_page_id, Fld_lang_key, Fld_val};} + private static final String Fld_page_id = "page_id", Fld_lang_key = "lang_key", Fld_val = "val"; +} +class Wdata_link_tbl extends Wdata_tbl_base { + @Override public String Tbl_name() {return "wdata_link";} + @Override public String Tbl_create_sql() { + return String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS wdata_link" + , "( page_id integer NOT NULL" + , ", wiki_key varchar(255) NOT NULL" + , ", val varchar(255) NOT NULL" + , ");" + ); + } + @Override public Db_idx_itm[] Idx_ary() {return new Db_idx_itm[] {Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS wdata_link__main ON wdata_link (page_id, wiki_key);")};} + @Override public String[] Fld_ary() {return new String[] {Fld_page_id, Fld_wiki_key, Fld_val};} + @Override public void Exec_insert_by_wdoc(byte[] lang_key, Wdata_wiki_mgr wdata_mgr, int page_id, Wdata_doc wdoc) { + Ordered_hash hash = wdoc.Slink_list(); + int len = hash.Count(); + Db_stmt insert_stmt = this.Insert_stmt(); + for (int i = 0; i < len; i++) { + Json_kv kv = (Json_kv)hash.Get_at(i); + byte[] key = kv.Key().Data_bry(); + Json_itm kv_val = kv.Val(); + byte[] val = Bry_.Empty; + if (kv_val.Tid() == Json_itm_.Tid__str) + val = kv_val.Data_bry(); + else { + Json_nde val_nde = (Json_nde)kv.Val(); + Json_kv val_name_kv = (Json_kv)val_nde.Get_at(0); // ASSUME: 1st item is always "name" kv; EX: "name":"Earth" + val = val_name_kv.Val().Data_bry(); + } + insert_stmt.Clear() + .Val_int(page_id) + .Val_bry_as_str(key) + .Val_bry_as_str(val) + .Exec_insert(); + } + } + private static final String Fld_page_id = "page_id", Fld_wiki_key = "wiki_key", Fld_val = "val"; +} +class Wbase_claim_tbl extends Wdata_tbl_base { + @Override public String Tbl_name() {return "wdata_claim";} + @Override public String Tbl_create_sql() { + return String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS wdata_claim" + , "( claim_id integer NOT NULL" + , ", page_id integer NOT NULL" + , ", prop_id integer NOT NULL" // 60; P60 + , ", val_tid smallint NOT NULL" // String;wikibase-entity-id;time;globecoordinate + , ", entity_tid smallint NOT NULL" // null;item + , ", entity_id integer NOT NULL" // null;123 + , ", val_text varchar(255) NOT NULL" + , ", guid varchar(64) NOT NULL" + , ", rank integer NOT NULL" + , ", ref_count integer NOT NULL" + , ", qual_count integer NOT NULL" + , ");" + ); + } + @Override public Db_idx_itm[] Idx_ary() { + return new Db_idx_itm[] + { Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS wdata_claim__main ON wdata_claim (page_id, prop_id, val_tid, entity_tid);") + }; + } + @Override public String[] Fld_ary() {return new String[] {Fld_claim_id, Fld_page_id, Fld_prop_id, Fld_val_tid, Fld_entity_tid, Fld_entity_id, Fld_val_text, Fld_guid, Fld_rank, Fld_ref_count, Fld_qual_count};} + private int next_claim_id = 0; + private Xob_wdata_db_visitor visitor; + @Override public void Exec_insert_by_wdoc(byte[] lang_key, Wdata_wiki_mgr wdata_mgr, int page_id, Wdata_doc wdoc) { + if (visitor == null) visitor = new Xob_wdata_db_visitor(wdata_mgr); + visitor.Init(lang_key); + Ordered_hash list = wdoc.Claim_list(); + int list_len = list.Count(); + for (int i = 0; i < list_len; i++) { + Wbase_claim_grp claim_grp = (Wbase_claim_grp)list.Get_at(i); + int itms_len = claim_grp.Len(); + int entity_id = -1; + byte[] claim_val = Bry_.Empty; + for (int j = 0; j < itms_len; j++) { + Wbase_claim_base claim = claim_grp.Get_at(j); + claim.Welcome(visitor); + claim_val = visitor.Rv(); + Exec_insert(++next_claim_id, page_id, claim_grp.Id(), claim.Val_tid(), claim.Snak_tid(), entity_id, claim_val, claim.Wguid(), claim.Rank_tid(), 0, 0); + } + } + } + public void Exec_insert(int claim_id, int page_id, int prop_id, byte val_tid, byte entity_tid, int entity_id, byte[] val_text, byte[] guid, int rank, int ref_count, int qual_count) { + if (val_text == null) val_text = Bry_.Empty; + if (guid == null) guid = Bry_.Empty; + this.Insert_stmt().Clear() + .Val_int(claim_id) + .Val_int(page_id) + .Val_int(prop_id) + .Val_byte(val_tid) + .Val_byte(entity_tid) + .Val_int(entity_id) + .Val_bry_as_str(val_text) + .Val_bry_as_str(guid) + .Val_int(rank) + .Val_int(ref_count) + .Val_int(qual_count) + .Exec_insert(); + } + private static final String Fld_claim_id = "claim_id", Fld_page_id = "page_id", Fld_prop_id = "prop_id", Fld_val_tid = "val_tid", Fld_entity_tid = "entity_tid", Fld_entity_id = "entity_id", Fld_val_text = "val_text" + , Fld_guid = "guid", Fld_rank = "rank", Fld_ref_count = "ref_count", Fld_qual_count = "qual_count" + ; +} +class Wbase_claim_time_tbl extends Wdata_tbl_base { + @Override public String Tbl_name() {return "wdata_claim_time";} + @Override public String Tbl_create_sql() { + return String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS wdata_claim_time" + , "( claim_id integer NOT NULL" + , ", time_val varchar(64) NOT NULL" // -04540000000-01-01T00:00:00Z + , ", time_tz integer NOT NULL" // 0 + , ", time_before integer NOT NULL" // 0 + , ", time_after integer NOT NULL" // 0 + , ", time_precision integer NOT NULL" // 2; number of digits + , ", time_model varchar(64) NOT NULL" // http:\/\/www.wikidata.org\/entity\/Q1985727 + , ");" + ); + } + @Override public Db_idx_itm[] Idx_ary() { + return new Db_idx_itm[] { + Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS wdata_claim_time__main ON wdata_claim_time (claim_id);") + }; + } + @Override public String[] Fld_ary() {return new String[] {Fld_claim_id, Fld_time_val, Fld_time_tz, Fld_time_before, Fld_time_after, Fld_time_precision, Fld_time_model};} + public void Insert(Db_stmt stmt, int claim_id, byte[] time_val, int tz, int before, int after, int precision, byte[] model) { + stmt.Clear() + .Val_int(claim_id) + .Val_bry_as_str(time_val) + .Val_int(tz) + .Val_int(before) + .Val_int(after) + .Val_int(precision) + .Val_bry_as_str(model) + .Exec_insert(); + } + private static final String Fld_claim_id = "claim_id", Fld_time_val = "time_val", Fld_time_tz = "time_tz", Fld_time_before = "time_before", Fld_time_after = "time_after", Fld_time_precision = "time_precision", Fld_time_model = "time_model"; +} +class Wbase_claim_geo_tbl extends Wdata_tbl_base { + @Override public String Tbl_name() {return "wdata_claim_geo";} + @Override public String Tbl_create_sql() { + return String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS wdata_claim_geo" + , "( claim_id integer NOT NULL" + , ", geo_latitude double NOT NULL" // 41.590833333333 + , ", geo_longitude double NOT NULL" // -93.620833333333 + , ", geo_altitude varchar(255) NOT NULL" // null + , ", geo_precision double NOT NULL" // 0.00027777777777778 + , ", geo_globe integer NOT NULL" // http:\/\/www.wikidata.org\/entity\/Q2 + , ");" + ); + } + @Override public Db_idx_itm[] Idx_ary() { + return new Db_idx_itm[] + { Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS wdata_claim_geo__main ON wdata_claim_geo (claim_id);") + }; + } + public void Insert(Db_stmt stmt, int claim_id, double latitude, double longitude, byte[] altitude, double precision, byte[] globe) { + stmt.Clear() + .Val_int(claim_id) + .Val_double(latitude) + .Val_double(longitude) + .Val_bry_as_str(altitude) + .Val_double(precision) + .Val_bry_as_str(globe) + .Exec_insert(); + } + @Override public String[] Fld_ary() {return new String[] {Fld_claim_id, Fld_geo_latitude, Fld_geo_longitude, Fld_geo_altitude, Fld_geo_precision, Fld_geo_globe};} + private static final String Fld_claim_id = "claim_id", Fld_geo_latitude = "geo_latitude", Fld_geo_longitude = "geo_longitude", Fld_geo_altitude = "geo_altitude", Fld_geo_precision = "geo_precision", Fld_geo_globe = "geo_globe"; +} +class Wdata_ref_tbl extends Wdata_tbl_base { + @Override public String Tbl_name() {return "wdata_ref";} + @Override public String Tbl_create_sql() { + return String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS wdata_ref" + , "( ref_id integer NOT NULL" + , ", page_id integer NOT NULL" + , ", prop_id integer NOT NULL" // 60; P60 + , ", val_tid smallint NOT NULL" // String;wikibase-entity-id;time;globecoordinate + , ", entity_tid smallint NOT NULL" // null;item + , ", entity_id integer NOT NULL" // null;123 + , ", val_text varchar(255) NOT NULL" + , ");" + ); + } + @Override public Db_idx_itm[] Idx_ary() { + return new Db_idx_itm[] { + Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS wdata_ref__main ON wdata_ref (page_id, prop_id, val_tid, entity_tid);") + }; + } + @Override public String[] Fld_ary() {return new String[] {Fld_ref_id, Fld_page_id, Fld_prop_id, Fld_val_tid, Fld_entity_tid, Fld_entity_id, Fld_val_text};} + private static final String Fld_ref_id = "ref_id", Fld_page_id = "page_id", Fld_prop_id = "prop_id", Fld_val_tid = "val_tid", Fld_entity_tid = "entity_tid", Fld_entity_id = "entity_id", Fld_val_text = "val_ext"; +} +class Wdata_qual_tbl extends Wdata_tbl_base { + @Override public String Tbl_name() {return "wdata_qual";} + @Override public String Tbl_create_sql() { + return String_.Concat_lines_nl + ( "CREATE TABLE IF NOT EXISTS wdata_qual" + , "( qual_id integer NOT NULL" + , ", page_id integer NOT NULL" + , ", val_text varchar(4096) NOT NULL" + , ");" + ); + } + @Override public Db_idx_itm[] Idx_ary() { + return new Db_idx_itm[] { + Db_idx_itm.sql_("CREATE INDEX IF NOT EXISTS wdata_qual__main ON wdata_ref (qual_id, page_id);") + }; + } + @Override public String[] Fld_ary() {return new String[] {Fld_qual_id, Fld_page_id, Fld_val_text};} + public void Insert(Db_stmt stmt, int qual_id, int page_id, byte[] val_text) { + stmt.Clear() + .Val_int(qual_id) + .Val_int(page_id) + .Val_bry_as_str(val_text) + .Exec_insert(); + } + private static final String Fld_qual_id = "qual_id", Fld_page_id = "page_id", Fld_val_text = "val_text"; +} +class Xob_wdata_db_visitor implements Wbase_claim_visitor { + private final Wdata_wiki_mgr wdata_mgr; private byte[] lang_key; + public Xob_wdata_db_visitor(Wdata_wiki_mgr wdata_mgr) {this.wdata_mgr = wdata_mgr;} + public void Init(byte[] lang_key) {this.lang_key = lang_key;} + public byte[] Rv() {return rv;} private byte[] rv; + public void Visit_str(Wbase_claim_string itm) {rv = itm.Val_bry();} + public void Visit_monolingualtext(Wbase_claim_monolingualtext itm) {rv = Bry_.Add_w_dlm(Byte_ascii.Pipe, itm.Lang(), itm.Text());} + public void Visit_quantity(Wbase_claim_quantity itm) {rv = itm.Amount();} + public void Visit_time(Wbase_claim_time itm) {rv = itm.Time();} + public void Visit_globecoordinate(Wbase_claim_globecoordinate itm) {rv = Bry_.Add_w_dlm(Byte_ascii.Comma, itm.Lat(), itm.Lng());} + public void Visit_system(Wbase_claim_value itm) {rv = Bry_.Empty;} + public void Visit_entity(Wbase_claim_entity itm) { + Wdata_doc entity_doc = wdata_mgr.Doc_mgr.Get_by_xid_or_null(itm.Page_ttl_db()); + rv = entity_doc == null ? Bry_.Empty : entity_doc.Label_list__get(lang_key); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_pid_base.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_pid_base.java index a27517de8..b948194a7 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_pid_base.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_pid_base.java @@ -13,3 +13,48 @@ 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.wbases.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.langs.jsons.*; import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.xtns.wbases.parsers.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public abstract class Xob_wdata_pid_base extends Xob_itm_dump_base implements Xob_page_wkr, Gfo_invk { + private Json_parser parser; + public Xob_wdata_pid_base Ctor(Xob_bldr bldr, Xowe_wiki wiki) {this.Cmd_ctor(bldr, wiki); return this;} + public abstract String Page_wkr__key(); + public abstract void Pid_bgn(); + public abstract void Pid_add(byte[] src_lang, byte[] src_ttl, byte[] trg_ttl); + public abstract void Pid_datatype(byte[] pid, byte[] datatype_bry); + public abstract void Pid_end(); + public void Page_wkr__bgn() { + this.Init_dump(this.Page_wkr__key(), wiki.Tdb_fsys_mgr().Site_dir().GenSubDir_nest("data", "pid")); // NOTE: must pass in correct make_dir in order to delete earlier version (else make_dirs will append) + parser = bldr.App().Wiki_mgr().Wdata_mgr().Jdoc_parser(); + this.Pid_bgn(); + } + public void Page_wkr__run(Xowd_page_itm page) { + if (page.Ns_id() != Wdata_wiki_mgr.Ns_property) return; + Json_doc jdoc = parser.Parse(page.Text()); + if (jdoc == null) { + bldr.Usr_dlg().Warn_many(GRP_KEY, "json.invalid", "json is invalid: ns=~{0} id=~{1}", page.Ns_id(), String_.new_u8(page.Ttl_page_db())); + return; + } + Parse_jdoc(jdoc); + } + public void Page_wkr__run_cleanup() {} + public void Parse_jdoc(Json_doc jdoc) { + Wdata_doc_parser wdoc_parser = app.Wiki_mgr().Wdata_mgr().Wdoc_parser(jdoc); + byte[] qid = wdoc_parser.Parse_qid(jdoc); + + // add datatype + byte[] datatype = jdoc.Root_nde().Get_as_bry(Wdata_dict_mainsnak.Itm__datatype.Key_str()); + this.Pid_datatype(qid, datatype); + + // add langs + Ordered_hash list = wdoc_parser.Parse_langvals(qid, jdoc, Bool_.Y); + int len = list.Count(); + for (int i = 0; i < len; ++i) { + Wdata_langtext_itm label = (Wdata_langtext_itm)list.Get_at(i); + this.Pid_add(label.Lang(), label.Text(), qid); + } + } + public void Page_wkr__end() {this.Pid_end();} + static final String GRP_KEY = "xowa.wdata.pid_wkr"; +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_pid_base_tst.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_pid_base_tst.java index a27517de8..7909b3411 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_pid_base_tst.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_pid_base_tst.java @@ -13,3 +13,61 @@ 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.wbases.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import org.junit.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; +public class Xob_wdata_pid_base_tst { + gplx.xowa.bldrs.Xob_fxt fxt = new gplx.xowa.bldrs.Xob_fxt().Ctor_mem(); + Io_url reg_(Xowe_wiki wdata, String wiki) {return Wdata_idx_wtr.dir_pid_(wdata, wiki).GenSubFil(Xotdb_dir_info_.Name_reg_fil);} + Io_url ttl_(Xowe_wiki wdata, String wiki, int fil_id) { + Io_url root = Wdata_idx_wtr.dir_pid_(wdata, wiki); + return Xotdb_fsys_mgr.Url_fil(root, fil_id, Xotdb_dir_info_.Bry_xdat); + } + @Test public void Basic() { + fxt.Wiki().Ns_mgr().Add_new(Wdata_wiki_mgr.Ns_property, "Property"); + fxt.doc_ary_ + ( fxt.doc_wo_date_(2, "Property:P2", json_("p2", "label", String_.Ary("en", "p2_en", "fr", "p2_fr"))) + , fxt.doc_wo_date_(1, "Property:P1", json_("p1", "label", String_.Ary("en", "p1_en", "fr", "p1_fr"))) + ) + .Fil_expd(ttl_(fxt.Wiki(), "en", 0) + , "!!!!*|!!!!*|" + , "p1_en|p1" + , "p2_en|p2" + , "" + ) + .Fil_expd + ( reg_(fxt.Wiki(), "en") + , "0|p1_en|p2_en|2" + , "" + ) + .Fil_expd(ttl_(fxt.Wiki(), "fr", 0) + , "!!!!*|!!!!*|" + , "p1_fr|p1" + , "p2_fr|p2" + , "" + ) + .Fil_expd + ( reg_(fxt.Wiki(), "fr") + , "0|p1_fr|p2_fr|2" + , "" + ) + .Run(new Xob_wdata_pid_txt().Ctor(fxt.Bldr(), this.fxt.Wiki())) + ; + } + public static String json_(String entity_id, String grp_key, String[] grp_vals) { + Bry_bfr bfr = Bry_bfr_.New(); + bfr.Add_str_a7("{ 'entity':'").Add_str_u8(entity_id).Add_byte(Byte_ascii.Apos).Add_byte_nl(); + bfr.Add_str_a7(", 'datatype':'commonsMedia'\n"); + bfr.Add_str_a7(", '").Add_str_u8(grp_key).Add_str_a7("':").Add_byte_nl(); + int len = grp_vals.length; + for (int i = 0; i < len; i += 2) { + bfr.Add_byte_repeat(Byte_ascii.Space, 2); + bfr.Add_byte(i == 0 ? Byte_ascii.Curly_bgn : Byte_ascii.Comma).Add_byte(Byte_ascii.Space); + bfr.Add_byte(Byte_ascii.Apos).Add_str_u8(grp_vals[i ]).Add_byte(Byte_ascii.Apos).Add_byte(Byte_ascii.Colon); + bfr.Add_byte(Byte_ascii.Apos).Add_str_u8(grp_vals[i + 1]).Add_byte(Byte_ascii.Apos).Add_byte_nl(); + } + bfr.Add_str_a7(" }").Add_byte_nl(); + bfr.Add_str_a7("}").Add_byte_nl(); + return String_.Replace(bfr.To_str_and_clear(), "'", "\""); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_pid_sql.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_pid_sql.java index a27517de8..8e2cd5e43 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_pid_sql.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_pid_sql.java @@ -13,3 +13,45 @@ 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.wbases.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.wikis.data.*; import gplx.dbs.*; import gplx.xowa.wikis.dbs.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.xtns.wbases.dbs.*; import gplx.xowa.xtns.wbases.claims.enums.*; +public class Xob_wdata_pid_sql extends Xob_wdata_pid_base { + private Xowd_wbase_pid_tbl tbl__pid; + private Xowb_prop_tbl tbl__prop; + private final Ordered_hash datatype_hash = Ordered_hash_.New_bry(); + @Override public String Page_wkr__key() {return gplx.xowa.bldrs.Xob_cmd_keys.Key_wbase_pid;} + @Override public void Pid_bgn() { + Xow_db_mgr db_mgr = wiki.Data__core_mgr(); + + // init datatype_hash + Wbase_enum_hash enum_hash = Wbase_claim_type_.Reg; + byte len = (byte)enum_hash.Len(); + for (byte i = 0; i < len; i++) { + Wbase_claim_type claim_type = (Wbase_claim_type)enum_hash.Get_itm_or(i, null); + datatype_hash.Add(Bry_.new_u8(claim_type.Key_for_scrib()), claim_type); + } + + // init wbase_pid + tbl__pid = db_mgr.Db__wbase().Tbl__wbase_pid(); + tbl__pid.Create_tbl(); + tbl__pid.Insert_bgn(); + + // init wbase_prop + tbl__prop = db_mgr.Db__wbase().Tbl__wbase_prop(); + tbl__prop.Create_tbl(); + tbl__prop.Insert_bgn(); + } + @Override public void Pid_add(byte[] lang_key, byte[] ttl, byte[] pid) { + tbl__pid.Insert_cmd_by_batch(lang_key, ttl, pid); + } + @Override public void Pid_datatype(byte[] pid, byte[] datatype_bry) { + Wbase_claim_type claim_type = (Wbase_claim_type)datatype_hash.Get_by_or_fail(datatype_bry); + tbl__prop.Insert_cmd_by_batch(pid, claim_type.Tid()); + } + @Override public void Pid_end() { + tbl__pid.Insert_end(); + tbl__pid.Create_idx(); + tbl__prop.Insert_end(); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_pid_txt.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_pid_txt.java index a27517de8..d0dce42ca 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_pid_txt.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_pid_txt.java @@ -13,3 +13,36 @@ 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.wbases.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xob_wdata_pid_txt extends Xob_wdata_pid_base { + @Override public String Page_wkr__key() {return gplx.xowa.bldrs.Xob_cmd_keys.Key_tdb_text_wdata_pid;} + @Override public void Pid_bgn() { + pid_bldr = new Wdata_idx_bldr_pid(this, bldr, wiki, dump_fil_len); + } Wdata_idx_bldr_pid pid_bldr; + @Override public void Pid_add(byte[] lang_key, byte[] prop_key, byte[] qid) { + pid_bldr.Add(lang_key, prop_key, qid); + } + @Override public void Pid_datatype(byte[] pid, byte[] datatype_bry) {} + @Override public void Pid_end() { + pid_bldr.Flush(); + pid_bldr.Make(); + } +} +class Wdata_idx_bldr_pid extends Wdata_idx_mgr_base { + public Wdata_idx_bldr_pid(Xob_itm_dump_base wkr, Xob_bldr bldr, Xowe_wiki wiki, int dump_fil_len) {this.Ctor(wkr, bldr, wiki, dump_fil_len);} + public void Add(byte[] lang, byte[] prop_key, byte[] pid) { + Wdata_idx_wtr wtr = Get_or_new(lang); + wtr.Write(prop_key, pid); + } + public Wdata_idx_wtr Get_or_new(byte[] lang_bry) { + String lang = String_.Lower(String_.new_u8(lang_bry)); // NOTE: for some reason, both "en" and "En" can be added; normalize case + Object rv = hash.Get_by(lang); + if (rv == null) { + Wdata_idx_wtr wtr = Wdata_idx_wtr.new_pid_(wiki, lang, dump_fil_len); + hash.Add(lang, wtr); + return wtr; + } + return (Wdata_idx_wtr)rv; + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_qid_base.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_qid_base.java index a27517de8..f4c115645 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_qid_base.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_qid_base.java @@ -13,3 +13,53 @@ 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.wbases.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.langs.jsons.*; import gplx.core.ios.*; import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.xtns.wbases.parsers.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +import gplx.xowa.bldrs.wms.sites.*; +public abstract class Xob_wdata_qid_base extends Xob_itm_dump_base implements Xob_page_wkr, Gfo_invk { + private final Object thread_lock = new Object(); + private Json_parser parser; private Xob_wbase_ns_parser ns_parser; private final Xob_wbase_ns_parser_rslt ns_parser_rslt = new Xob_wbase_ns_parser_rslt(); + public Xob_wdata_qid_base Ctor(Xob_bldr bldr, Xowe_wiki wiki) {this.Cmd_ctor(bldr, wiki); return this;} + public abstract String Page_wkr__key(); + public abstract void Qid_bgn(); + public abstract void Qid_add(byte[] wiki_key, int ns_id, byte[] ttl, byte[] qid); + public abstract void Qid_end(); + public void Page_wkr__bgn() { + this.Init_dump(this.Page_wkr__key(), wiki.Tdb_fsys_mgr().Site_dir().GenSubDir_nest("data", "qid")); // NOTE: must pass in correct make_dir in order to delete earlier version (else make_dirs will append) + this.parser = bldr.App().Wiki_mgr().Wdata_mgr().Jdoc_parser(); + this.ns_parser = new Xob_wbase_ns_parser(bldr.App().Fsys_mgr().Cfg_site_meta_fil()); + this.Qid_bgn(); + } + public void Page_wkr__run(Xowd_page_itm page) { + if (page.Ns_id() != Xow_ns_.Tid__main) return; // qid pages are only in the Main Srch_rslt_cbk + Json_doc jdoc = parser.Parse(page.Text()); + if (jdoc == null) {bldr.Usr_dlg().Warn_many("", "", "json is invalid: ns=~{0} id=~{1}", page.Ns_id(), String_.new_u8(page.Ttl_page_db())); return;} + this.Parse_jdoc(jdoc); + } + public void Page_wkr__run_cleanup() {} + public void Parse_jdoc(Json_doc jdoc) { + synchronized (thread_lock) { + Wdata_doc_parser wdoc_parser = app.Wiki_mgr().Wdata_mgr().Wdoc_parser(jdoc); + byte[] qid = wdoc_parser.Parse_qid(jdoc); + Bry_bfr tmp_bfr = Bry_bfr_.Reset(255); + Ordered_hash sitelinks = wdoc_parser.Parse_sitelinks(qid, jdoc); + int sitelinks_len = sitelinks.Count(); if (sitelinks_len == 0) return; // no subs; return; + for (int i = 0; i < sitelinks_len; i++) { // iterate sitelinks + Wdata_sitelink_itm sitelink = (Wdata_sitelink_itm)sitelinks.Get_at(i); + byte[] sitelink_site = sitelink.Site(), sitelink_ttl = sitelink.Name(); + ns_parser.Find(ns_parser_rslt, sitelink_site, sitelink_ttl); + int sitelink_ns = ns_parser_rslt.Ns_id(); + if (sitelink_ns != Xow_ns_.Tid__main) // ttl not in main; chop off ns portion; EX:Aide:French_title -> French_title + sitelink_ttl = Bry_.Mid(sitelink_ttl, ns_parser_rslt.Ttl_bgn(), sitelink_ttl.length); + sitelink_ttl = wiki.Lang().Case_mgr().Case_build_1st_upper(tmp_bfr, sitelink_ttl, 0, sitelink_ttl.length); + this.Qid_add(sitelink.Site(), sitelink_ns, Xoa_ttl.Replace_spaces(sitelink_ttl), qid); // NOTE: always convert spaces to underscores; EX: "A B" -> "A_B" DATE:2015-04-21 + } + } + } + public void Page_wkr__end() { + this.Qid_end(); + // wiki.Data__core_mgr().Db__wbase().Tbl__cfg().Insert_int("", "", 1); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_qid_base_tst.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_qid_base_tst.java index a27517de8..4ec4229aa 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_qid_base_tst.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_qid_base_tst.java @@ -13,3 +13,161 @@ 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.wbases.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import org.junit.*; +import gplx.xowa.wikis.nss.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.tdbs.*; import gplx.dbs.*; +import gplx.xowa.bldrs.wms.sites.*; +public class Xob_wdata_qid_base_tst { + private gplx.xowa.bldrs.Xob_fxt fxt; // NOTE: reset memory instance (don't just call clear) + @Before public void init() { + this.fxt = new gplx.xowa.bldrs.Xob_fxt().Ctor_mem(); + gplx.dbs.Db_conn_bldr.Instance.Reg_default_mem(); + } + @Test public void Basic() { + fxt.doc_ary_ + ( fxt.doc_wo_date_(2, "q2", Xob_wdata_pid_base_tst.json_("q2", "links", String_.Ary("enwiki", "q2_en", "frwiki", "q2_fr"))) + , fxt.doc_wo_date_(1, "q1", Xob_wdata_pid_base_tst.json_("q1", "links", String_.Ary("enwiki", "q1_en", "frwiki", "q1_fr"))) + ) + .Fil_expd(ttl_(fxt.Wiki(), "enwiki", "000", 0) + , "!!!!*|!!!!*|" + , "Q1_en|q1" + , "Q2_en|q2" + , "" + ) + .Fil_expd + ( reg_(fxt.Wiki(), "enwiki", "000") + , "0|Q1_en|Q2_en|2" + , "" + ) + .Fil_expd(ttl_(fxt.Wiki(), "frwiki", "000", 0) + , "!!!!*|!!!!*|" + , "Q1_fr|q1" + , "Q2_fr|q2" + , "" + ) + .Fil_expd + ( reg_(fxt.Wiki(), "frwiki", "000") + , "0|Q1_fr|Q2_fr|2" + , "" + ) + .Run(new Xob_wdata_qid_txt().Ctor(fxt.Bldr(), this.fxt.Wiki())) + ; + } + @Test public void Ns() { + // setup db + Site_core_db json_db = new Site_core_db(fxt.App().Fsys_mgr().Cfg_site_meta_fil()); + Site_namespace_tbl ns_tbl = json_db.Tbl__namespace(); + ns_tbl.Insert(Bry_.new_a7("en.w"), Xow_ns_.Tid__help, Xow_ns_case_.Bry__1st, Bry_.Empty, Bry_.new_a7("Help"), Bool_.N, Bool_.N, Bry_.Empty); + ns_tbl.Insert(Bry_.new_a7("fr.w"), Xow_ns_.Tid__help, Xow_ns_case_.Bry__1st, Bry_.Empty, Bry_.new_a7("Aide"), Bool_.N, Bool_.N, Bry_.Empty); + // run test + fxt.doc_ary_ + ( fxt.doc_wo_date_(1, "11", Xob_wdata_pid_base_tst.json_("q1", "links", String_.Ary("enwiki", "Help:Q1_en", "frwiki", "Aide:Q1_fr"))) + ) + .Fil_expd(ttl_(fxt.Wiki(), "enwiki", "012", 0) + , "!!!!*|" + , "Q1_en|q1" + , "" + ) + .Fil_expd + ( reg_(fxt.Wiki(), "enwiki", "012") + , "0|Q1_en|Q1_en|1" + , "" + ) + .Fil_expd(ttl_(fxt.Wiki(), "frwiki", "012", 0) + , "!!!!*|" + , "Q1_fr|q1" + , "" + ) + .Fil_expd + ( reg_(fxt.Wiki(), "frwiki", "012") + , "0|Q1_fr|Q1_fr|1" + , "" + ) + .Run(new Xob_wdata_qid_txt().Ctor(fxt.Bldr(), this.fxt.Wiki())) + ; + } + @Test public void Links_w_name() { // PURPOSE: wikidata changed links node from "enwiki:A" to "enwiki:{name:A,badges:[]}"; DATE:2013-09-14 + String q1_str = String_.Concat_lines_nl + ( "{ \"entity\":\"q1\"" + , ", \"links\":" + , " { \"enwiki\":\"q1_en\"" + , " , \"frwiki\":\"q1_fr\"" + , " }" + , "}" + ); + String q2_str = String_.Concat_lines_nl + ( "{ \"entity\":[\"item\",2]" + , ", \"links\":" + , " { \"enwiki\":{\"name\":\"q2_en\",\"badges\":[]}" + , " , \"frwiki\":{\"name\":\"q2_fr\",\"badges\":[]}" + , " }" + , "}" + ); + fxt.doc_ary_ + ( fxt.doc_wo_date_(1, "q1", q1_str) + , fxt.doc_wo_date_(2, "q2", q2_str) + ) + .Fil_expd(ttl_(fxt.Wiki(), "enwiki", "000", 0) + , "!!!!*|!!!!*|" + , "Q1_en|q1" + , "Q2_en|q2" + , "" + ) + .Fil_expd + ( reg_(fxt.Wiki(), "enwiki", "000") + , "0|Q1_en|Q2_en|2" + , "" + ) + .Fil_expd(ttl_(fxt.Wiki(), "frwiki", "000", 0) + , "!!!!*|!!!!*|" + , "Q1_fr|q1" + , "Q2_fr|q2" + , "" + ) + .Fil_expd + ( reg_(fxt.Wiki(), "frwiki", "000") + , "0|Q1_fr|Q2_fr|2" + , "" + ) + .Run(new Xob_wdata_qid_txt().Ctor(fxt.Bldr(), this.fxt.Wiki())) + ; + } + @Test public void Spaces() { // PURPOSE: assert that ttls with spaces are converted to unders DATE:2015-04-21 + fxt.doc_ary_ + ( fxt.doc_wo_date_(2, "q2", Xob_wdata_pid_base_tst.json_("q2", "links", String_.Ary("enwiki", "q2 en", "frwiki", "q2 fr"))) // note "q2 en" not "q2_en" + , fxt.doc_wo_date_(1, "q1", Xob_wdata_pid_base_tst.json_("q1", "links", String_.Ary("enwiki", "q1 en", "frwiki", "q1 fr"))) + ) + .Fil_expd(ttl_(fxt.Wiki(), "enwiki", "000", 0) + , "!!!!*|!!!!*|" + , "Q1_en|q1" + , "Q2_en|q2" // NOTE: q2_en + , "" + ) + .Fil_expd + ( reg_(fxt.Wiki(), "enwiki", "000") + , "0|Q1_en|Q2_en|2" + , "" + ) + .Fil_expd(ttl_(fxt.Wiki(), "frwiki", "000", 0) + , "!!!!*|!!!!*|" + , "Q1_fr|q1" + , "Q2_fr|q2" + , "" + ) + .Fil_expd + ( reg_(fxt.Wiki(), "frwiki", "000") + , "0|Q1_fr|Q2_fr|2" + , "" + ) + .Run(new Xob_wdata_qid_txt().Ctor(fxt.Bldr(), this.fxt.Wiki())) + ; + } + public static Io_url reg_(Xowe_wiki wdata, String wiki, String ns_id) { + return Wdata_idx_wtr.dir_qid_(wdata, wiki, ns_id).GenSubFil(Xotdb_dir_info_.Name_reg_fil); + } + public static Io_url ttl_(Xowe_wiki wdata, String wiki, String ns_id, int fil_id) { + Io_url root = Wdata_idx_wtr.dir_qid_(wdata, wiki, ns_id); + return Xotdb_fsys_mgr.Url_fil(root, fil_id, Xotdb_dir_info_.Bry_xdat); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_qid_sql.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_qid_sql.java index a27517de8..396023631 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_qid_sql.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_qid_sql.java @@ -13,3 +13,28 @@ 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.wbases.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.wikis.data.*; import gplx.dbs.*; import gplx.xowa.wikis.dbs.*; import gplx.xowa.wikis.data.tbls.*; +public class Xob_wdata_qid_sql extends Xob_wdata_qid_base { + private Xowd_wbase_qid_tbl tbl; + @Override public String Page_wkr__key() {return gplx.xowa.bldrs.Xob_cmd_keys.Key_wbase_qid;} + @Override public void Qid_bgn() { + Xow_db_mgr db_mgr = wiki.Db_mgr_as_sql().Core_data_mgr(); + boolean db_is_all_or_few = db_mgr.Props().Layout_text().Tid_is_all_or_few(); + Xow_db_file wbase_db = db_is_all_or_few + ? db_mgr.Db__core() + : db_mgr.Dbs__make_by_tid(Xow_db_file_.Tid__wbase); + if (db_is_all_or_few) + db_mgr.Db__wbase_(wbase_db); + tbl = wbase_db.Tbl__wbase_qid(); + tbl.Create_tbl(); + tbl.Insert_bgn(); + } + @Override public void Qid_add(byte[] wiki_key, int ns_id, byte[] ttl, byte[] qid) { + tbl.Insert_cmd_by_batch(wiki_key, ns_id, ttl, qid); + } + @Override public void Qid_end() { + tbl.Insert_end(); + tbl.Create_idx(); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_qid_txt.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_qid_txt.java index a27517de8..a3244690a 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_qid_txt.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xob_wdata_qid_txt.java @@ -13,3 +13,34 @@ 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.wbases.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.bldrs.*; +public class Xob_wdata_qid_txt extends Xob_wdata_qid_base { + private Wdata_idx_bldr_qid qid_bldr; + @Override public String Page_wkr__key() {return gplx.xowa.bldrs.Xob_cmd_keys.Key_tdb_text_wdata_qid;} + @Override public void Qid_bgn() {qid_bldr = new Wdata_idx_bldr_qid().Ctor(this, bldr, wiki, dump_fil_len);} + @Override public void Qid_add(byte[] wiki_key, int ns_id, byte[] ttl, byte[] qid) { + qid_bldr.Add(String_.new_u8(wiki_key), Int_.To_str_pad_bgn_zero(ns_id, 3), ttl, qid); + } + @Override public void Qid_end() { + qid_bldr.Flush(); + qid_bldr.Make(); + } +} +class Wdata_idx_bldr_qid extends Wdata_idx_mgr_base { + public Wdata_idx_bldr_qid Ctor(Xob_wdata_qid_base wkr, Xob_bldr bldr, Xowe_wiki wiki, int dump_fil_len) {super.Ctor(wkr, bldr, wiki, dump_fil_len); return this;} + public void Add(String wiki_key, String ns_num_str, byte[] ttl, byte[] qid) { + Wdata_idx_wtr wtr = Get_or_new(wiki_key, ns_num_str); + wtr.Write(ttl, qid); + } + private Wdata_idx_wtr Get_or_new(String wiki_key, String ns_num_str) { + String wtr_key = wiki_key + "|" + ns_num_str; + Object rv = hash.Get_by(wtr_key); + if (rv == null) { + Wdata_idx_wtr wtr = Wdata_idx_wtr.new_qid_(wiki, wiki_key, ns_num_str, dump_fil_len); + hash.Add(wtr_key, wtr); + return wtr; + } + return (Wdata_idx_wtr)rv; + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xowb_bldr_addon.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xowb_bldr_addon.java index a27517de8..b7da42e12 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xowb_bldr_addon.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/Xowb_bldr_addon.java @@ -13,3 +13,15 @@ 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.wbases.imports; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.addons.*; +import gplx.xowa.bldrs.wkrs.*; +public class Xowb_bldr_addon implements Xoax_addon_itm, Xoax_addon_itm__bldr { + public Xob_cmd[] Bldr_cmds() { + return new Xob_cmd[] + { gplx.xowa.xtns.wbases.imports.json.Xowb_json_dump_cmd.Prototype + }; + } + + public String Addon__key() {return "xowa.builds.wikibase";} +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/json/Io_stream_rdr_mgr.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/json/Io_stream_rdr_mgr.java index a27517de8..4867b2b02 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/json/Io_stream_rdr_mgr.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/json/Io_stream_rdr_mgr.java @@ -13,3 +13,59 @@ 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.wbases.imports.json; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; import gplx.xowa.xtns.wbases.imports.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; import gplx.core.criterias.*; import gplx.core.envs.*; +class Io_stream_rdr_mgr { + public static Io_stream_rdr Get_rdr_or_null(Io_url src_fil, Io_url src_dir, Io_stream_unzip_mgr unzip_mgr, String... filter_ary) { + IoItmFil src_itm = null; + if (src_fil != null) src_itm = Io_mgr.Instance.QueryFil(src_fil); + + // specified file doesn't exist; try to find similar file based on filter + if (src_itm == null || !src_itm.Exists()) { + src_itm = Get_itm_by_filters(src_dir, filter_ary); + if (src_itm == null) return null; + } + + // return rdr + Io_url src_itm_url = src_itm.Url(); + Io_stream_rdr rv = unzip_mgr.Handles(src_itm_url) + ? unzip_mgr.New_rdr(src_itm_url) + : Io_stream_rdr_.New__raw(src_itm_url); + rv.Len_(src_itm.Size()); + return rv; + } + private static IoItmFil Get_itm_by_filters(Io_url dir, String... filter_ary) { + // create array of matches based on filters + int match_ary_len = filter_ary.length; + Criteria_ioMatch[] match_ary = new Criteria_ioMatch[match_ary_len]; + for (int i = 0; i < match_ary_len; ++i) + match_ary[i] = Criteria_ioMatch.parse(true, filter_ary[i], dir.Info().CaseSensitive()); + + // get files and check each file for match + IoItmFil rv = null; + IoItmHash itm_hash = Io_mgr.Instance.QueryDir_args(dir).ExecAsItmHash(); + int len = itm_hash.Count(); + for (int i = 0; i < len; ++i) { + IoItm_base itm = itm_hash.Get_at(i); + for (int j = 0; j < match_ary_len; ++j) { + if (itm.Type_fil() && match_ary[j].Matches(itm.Url())) + rv = (IoItmFil)itm; // NOTE: this will return the last match; useful for getting latest dump when multiple dumps are in one dir; (assuming latest should alphabetize last) + } + } + return rv; + } +} +class Io_stream_unzip_mgr { + private final String[] zip_exts; + private final boolean stdout_enabled; private final Process_adp stdout_process; + public Io_stream_unzip_mgr(boolean stdout_enabled, Process_adp stdout_process, String[] zip_exts) { + this.stdout_enabled = stdout_enabled; this.stdout_process = stdout_process; this.zip_exts = zip_exts; + } + public boolean Handles(Io_url url) {return String_.In(url.Ext(), zip_exts);} + public Io_stream_rdr New_rdr(Io_url url) { + return stdout_enabled + ? Io_stream_rdr_process.new_(stdout_process.Exe_url(), url, stdout_process.Xto_process_bldr_args(url.Raw())) + : Io_stream_rdr_.New__bzip2(url) + ; + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/json/Xowb_json_dump_cmd.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/json/Xowb_json_dump_cmd.java index a27517de8..882c5c533 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/json/Xowb_json_dump_cmd.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/json/Xowb_json_dump_cmd.java @@ -13,3 +13,26 @@ 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.wbases.imports.json; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; import gplx.xowa.xtns.wbases.imports.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.wkrs.*; +public class Xowb_json_dump_cmd extends Xob_cmd__base { + private final Xowb_json_dump_parser json_dump_parser; + private Io_url src_fil; + public Xowb_json_dump_cmd(Xob_bldr bldr, Xowe_wiki wiki) {super(bldr, wiki); + this.json_dump_parser = new Xowb_json_dump_parser(bldr, wiki); + } + @Override public void Cmd_run() { + json_dump_parser.Parse(src_fil); + } + + @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Invk_src_fil_)) this.src_fil = m.ReadIoUrl("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } private static final String Invk_src_fil_ = "src_fil_"; + + public static final String BLDR_CMD_KEY = "wbase.json_dump"; + @Override public String Cmd_key() {return BLDR_CMD_KEY;} + public static final Xob_cmd Prototype = new Xowb_json_dump_cmd(null, null); + @Override public Xob_cmd Cmd_clone(Xob_bldr bldr, Xowe_wiki wiki) {return new Xowb_json_dump_cmd(bldr, wiki);} +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/json/Xowb_json_dump_db.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/json/Xowb_json_dump_db.java index a27517de8..a30eaf47c 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/json/Xowb_json_dump_db.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/json/Xowb_json_dump_db.java @@ -13,3 +13,88 @@ 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.wbases.imports.json; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; import gplx.xowa.xtns.wbases.imports.*; +import gplx.core.ios.*; +import gplx.langs.jsons.*; +import gplx.xowa.bldrs.*; import gplx.xowa.bldrs.cmds.*; import gplx.xowa.bldrs.cmds.texts.sqls.*; import gplx.xowa.apps.apis.xowa.bldrs.imports.*; +import gplx.xowa.wikis.*; import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.data.*; import gplx.xowa.wikis.data.tbls.*; +import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.xtns.wbases.parsers.*; +class Xowb_json_dump_db { + private final Xoae_app app; private final Gfo_usr_dlg usr_dlg; private final Xowe_wiki wiki; private final Xob_bldr bldr; + private final Json_parser json_parser; + private final Xob_wdata_pid_sql pid_cmd = new Xob_wdata_pid_sql(); private final Xob_wdata_qid_sql qid_cmd = new Xob_wdata_qid_sql(); + private Xow_ns_mgr ns_mgr; private Xow_db_mgr db_mgr; + private Xowd_page_tbl page_tbl; private Xob_ns_to_db_mgr ns_to_db_mgr; + private Io_stream_zip_mgr text_zip_mgr; private byte text_zip_tid; + private DateAdp page_modified_on; + private int page_id = 0, page_count_main = 0; + public Xowb_json_dump_db(Xob_bldr bldr, Xowe_wiki wiki) { + this.app = bldr.App(); this.usr_dlg = app.Usr_dlg(); this.wiki = wiki; this.bldr = bldr; + this.json_parser = bldr.App().Wiki_mgr().Wdata_mgr().Jdoc_parser(); + this.ns_mgr = wiki.Ns_mgr(); + } + public void Parse_all_bgn(long src_fil_len, String src_fil_name) { + // load wiki + Xowe_wiki_.Create(wiki, src_fil_len, src_fil_name); + this.db_mgr = wiki.Data__core_mgr(); + this.page_tbl = db_mgr.Tbl__page(); + pid_cmd.Cmd_ctor(bldr, wiki); qid_cmd.Cmd_ctor(bldr, wiki); + + // create ns_mgr + wiki.Ns_mgr().Add_defaults(); + wiki.Ns_mgr().Add_new(Wdata_wiki_mgr.Ns_property, Wdata_wiki_mgr.Ns_property_name); + wiki.Ns_mgr().Init(); + + // init ns_map + this.ns_to_db_mgr = new Xob_ns_to_db_mgr(new Xob_ns_to_db_wkr__text(), db_mgr, Xobldr_cfg.Max_size__text(app)); + byte[] ns_file_map = Xobldr_cfg.New_ns_file_map(app, src_fil_len); + Xob_ns_file_itm.Init_ns_bldr_data(Xow_db_file_.Tid__text, wiki.Ns_mgr(), ns_file_map); + + // start import + this.text_zip_mgr = wiki.Utl__zip_mgr(); + this.text_zip_tid = Xobldr_cfg.Zip_mode__text(app); + this.page_modified_on = Datetime_now.Get(); + page_tbl.Insert_bgn(); + qid_cmd.Page_wkr__bgn(); + pid_cmd.Pid_bgn(); + } + public void Parse_doc(byte[] json_bry) { + // parse to jdoc + Json_doc jdoc = json_parser.Parse(json_bry); + if (jdoc == null) {usr_dlg.Warn_many("", "", "wbase.json_dump:json is invalid: json=~{0}", json_bry); return;} + + // extract xid + byte[] id = jdoc.Get_val_as_bry_or(Bry__id_key, null); + if (id == null) {usr_dlg.Warn_many("", "", "wbase.json_dump:id is invalid: json=~{0}", json_bry); return;} + boolean jdoc_is_qid = Bry_.Has_at_bgn(id, Byte_ascii.Ltr_Q, 0); + Xow_ns ns = jdoc_is_qid ? ns_mgr.Ns_main() : ns_mgr.Ids_get_or_null(Wdata_wiki_mgr.Ns_property); + + // create page entry + int random_int = ns.Count() + 1; ns.Count_(random_int); + byte[] json_zip = text_zip_mgr.Zip(text_zip_tid, json_bry); + Xow_db_file text_db = ns_to_db_mgr.Get_by_ns(ns.Bldr_data(), json_zip.length); + db_mgr.Create_page(page_tbl, text_db.Tbl__text(), ++page_id, ns.Id(), id, Bool_.N, page_modified_on, json_zip, json_bry.length, random_int, text_db.Id(), -1); + + // insert text + if (jdoc_is_qid) { + qid_cmd.Parse_jdoc(jdoc); + ++page_count_main; + } + else + pid_cmd.Parse_jdoc(jdoc); + } + public void Parse_all_end() { + page_tbl.Insert_end(); + page_tbl.Create_idx(); + qid_cmd.Qid_end(); + pid_cmd.Pid_end(); + ns_to_db_mgr.Rls_all(); + + // cleanup core + Xow_db_file db_core = db_mgr.Db__core(); + db_core.Tbl__site_stats().Update(page_count_main, page_id, ns_mgr.Ns_file().Count()); // save page stats + db_core.Tbl__ns().Insert(ns_mgr); // save ns + db_mgr.Tbl__cfg().Insert_str(Xowd_cfg_key_.Grp__wiki_init, Xowd_cfg_key_.Key__init__modified_latest, page_modified_on.XtoStr_fmt(DateAdp_.Fmt_iso8561_date_time)); + } + private static final byte[] Bry__id_key = Bry_.new_a7("id"); +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/imports/json/Xowb_json_dump_parser.java b/400_xowa/src/gplx/xowa/xtns/wbases/imports/json/Xowb_json_dump_parser.java index a27517de8..96e002c21 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/imports/json/Xowb_json_dump_parser.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/imports/json/Xowb_json_dump_parser.java @@ -13,3 +13,73 @@ 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.wbases.imports.json; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; import gplx.xowa.xtns.wbases.imports.*; +import gplx.core.ios.*; import gplx.core.ios.streams.*; +import gplx.xowa.bldrs.*; +import gplx.xowa.wikis.data.tbls.*; +class Xowb_json_dump_parser { + private final Xob_bldr bldr; private final Xowe_wiki wiki; + public Xowb_json_dump_parser(Xob_bldr bldr, Xowe_wiki wiki) { + this.bldr = bldr; this.wiki = wiki; + } + public void Parse(Io_url json_dump_file) { + // init + Xoae_app app = bldr.App(); Gfo_usr_dlg usr_dlg = app.Usr_dlg(); + Xowb_json_dump_db dump_db = new Xowb_json_dump_db(bldr, wiki); + Io_stream_unzip_mgr unzip_mgr = new Io_stream_unzip_mgr(gplx.xowa.bldrs.installs.Xoi_dump_mgr.Import_bz2_by_stdout(app), app.Prog_mgr().App_decompress_bz2_by_stdout(), String_.Ary(".bz2", ".gz", ".zip")); + + // open buffer from file + Io_stream_rdr stream = Io_stream_rdr_mgr.Get_rdr_or_null(json_dump_file, wiki.Fsys_mgr().Root_dir(), unzip_mgr, "*wikidata-*-all.json", "*wikidata-*-all.json.gz"); + if (stream == null) {usr_dlg.Warn_many("", "", "wbase.import:file not found: src_dir=~{0}", wiki.Fsys_mgr().Root_dir()); return;} + Io_buffer_rdr buffer = Io_buffer_rdr.new_(stream, 10 * Io_mgr.Len_mb); + + try { + // set page_bgn + if (!Bry_.Match(buffer.Bfr(), 0, 3, Bry_.new_a7("[\n{"))) {usr_dlg.Warn_many("", "", "wbase.import:doc_bgn is not '[\n': url=~{0}", stream.Url().Raw()); return;} // validate file; if schema ever changes this will fail + int page_bgn = 2; // 2="[\n" + + // read file and create pages for each json item + dump_db.Parse_all_bgn(stream.Len(), stream.Url().NameAndExt()); + Xowd_page_itm page = new Xowd_page_itm(); + while (true) { + int cur_pos = Parse_doc(dump_db, buffer, page, page_bgn); + if (cur_pos == -1) break; + if (cur_pos < page_bgn) + bldr.Print_prog_msg(buffer.Fil_pos(), buffer.Fil_len(), 1, "reading ~{0} MB: ~{1} ~{2}", Int_.To_str_pad_bgn_zero((int)(buffer.Fil_pos() / Io_mgr.Len_mb), Int_.DigitCount((int)(buffer.Fil_len() / Io_mgr.Len_mb))), "", page.Ttl_page_db()); + page_bgn = cur_pos; + } + dump_db.Parse_all_end(); + } + catch (Exception e) { + String msg = usr_dlg.Warn_many("", "", "dump_rdr:error while reading; url=~{0} err=~{1}", json_dump_file.Raw(), Err_.Message_lang(e)); + throw Err_.new_wo_type(msg); + } + finally {buffer.Rls();} + } + private int Parse_doc(Xowb_json_dump_db dump_db, Io_buffer_rdr rdr, Xowd_page_itm page, int page_bgn) { + // init + int pos = page_bgn; + byte[] bry = rdr.Bfr(); + int bry_len = rdr.Bfr_len(); + + while (true) {// loop 1 byte at a time until nl + if (pos == bry_len) { // refill if at end of 10 MB bfr + rdr.Bfr_load_from(page_bgn); + bry_len = rdr.Bfr_len(); + pos -= page_bgn; + page_bgn = 0; + } + + // read byte; parse if nl; otherwise move to next byte + byte b = bry[pos]; // NOTE: should never be out of bounds b/c json doc will end with "]\n" + if (b == Byte_ascii.Nl) { + if (pos - page_bgn == 1 && bry[page_bgn] == Byte_ascii.Brack_end) // EOF; note that json dump ends with "]\n" + return -1; + dump_db.Parse_doc(Bry_.Mid(bry, page_bgn, pos)); + return pos + 1; + } + else + ++pos; + } + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wbase_claim_factory.java b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wbase_claim_factory.java index a27517de8..797efd16b 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wbase_claim_factory.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wbase_claim_factory.java @@ -13,3 +13,104 @@ 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.wbases.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.langs.jsons.*; +import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.xtns.wbases.claims.*; import gplx.xowa.xtns.wbases.claims.enums.*; import gplx.xowa.xtns.wbases.claims.itms.*; +public class Wbase_claim_factory { + public Wbase_claim_base Parse(byte[] qid, int pid, byte snak_tid, Json_nde nde, byte value_tid, Json_itm value_itm) { + switch (value_tid) { + case Wbase_claim_type_.Tid__string: return new Wbase_claim_string(pid, snak_tid, value_itm.Data_bry()); + case Wbase_claim_type_.Tid__entity: return Parse_datavalue_entity (qid, pid, snak_tid, Json_nde.cast(value_itm)); + case Wbase_claim_type_.Tid__time: return Parse_datavalue_time (qid, pid, snak_tid, Json_nde.cast(value_itm)); + case Wbase_claim_type_.Tid__quantity: return Parse_datavalue_quantity (qid, pid, snak_tid, Json_nde.cast(value_itm)); + case Wbase_claim_type_.Tid__globecoordinate: return Parse_datavalue_globecoordinate (qid, pid, snak_tid, Json_nde.cast(value_itm)); + case Wbase_claim_type_.Tid__monolingualtext: return Parse_datavalue_monolingualtext (qid, pid, snak_tid, Json_nde.cast(value_itm)); + default: throw Err_.new_unhandled_default(value_tid); + } + } + private Wbase_claim_entity Parse_datavalue_entity(byte[] qid, int pid, byte snak_tid, Json_nde nde) { + int len = nde.Len(); + byte entity_tid = Byte_.Max_value_127; + byte[] entity_id_bry = null; + for (int i = 0; i < len; ++i) { + Json_kv sub = Json_kv.cast(nde.Get_at(i)); + byte tid = Wbase_claim_entity_.Reg.Get_tid_or_max_and_log(qid, sub.Key().Data_bry()); if (tid == Byte_.Max_value_127) continue; + switch (tid) { + case Wbase_claim_entity_.Tid__entity_type: entity_tid = Wbase_claim_entity_type_.Reg.Get_tid_or_fail(sub.Val().Data_bry()); break; + case Wbase_claim_entity_.Tid__numeric_id: entity_id_bry = sub.Val().Data_bry(); break; + case Wbase_claim_entity_.Tid__id: break; // ignore + } + } + if (entity_id_bry == null) throw Err_.new_wo_type("pid is invalid entity", "pid", pid); + return new Wbase_claim_entity(pid, snak_tid, entity_tid, entity_id_bry); + } + private Wbase_claim_monolingualtext Parse_datavalue_monolingualtext(byte[] qid, int pid, byte snak_tid, Json_nde nde) { + int len = nde.Len(); + byte[] lang = null, text = null; + for (int i = 0; i < len; ++i) { + Json_kv sub = Json_kv.cast(nde.Get_at(i)); + byte tid = Wbase_claim_monolingualtext_.Reg.Get_tid_or_max_and_log(qid, sub.Key().Data_bry()); if (tid == Byte_.Max_value_127) continue; + byte[] sub_val_bry = sub.Val().Data_bry(); + switch (tid) { + case Wbase_claim_monolingualtext_.Tid__text: text = sub_val_bry; break; + case Wbase_claim_monolingualtext_.Tid__language: lang = sub_val_bry; break; + } + } + if (lang == null || text == null) throw Err_.new_wo_type("pid is invalid monolingualtext", "pid", pid); + return new Wbase_claim_monolingualtext(pid, snak_tid, lang, text); + } + private Wbase_claim_globecoordinate Parse_datavalue_globecoordinate(byte[] qid, int pid, byte snak_tid, Json_nde nde) { + int len = nde.Len(); + byte[] lat = null, lng = null, alt = null, prc = null, glb = null; + for (int i = 0; i < len; ++i) { + Json_kv sub = Json_kv.cast(nde.Get_at(i)); + byte tid = Wbase_claim_globecoordinate_.Reg.Get_tid_or_max_and_log(qid, sub.Key().Data_bry()); if (tid == Byte_.Max_value_127) continue; + byte[] sub_val_bry = sub.Val().Data_bry(); + switch (tid) { + case Wbase_claim_globecoordinate_.Tid__latitude: lat = sub_val_bry; break; + case Wbase_claim_globecoordinate_.Tid__longitude: lng = sub_val_bry; break; + case Wbase_claim_globecoordinate_.Tid__altitude: alt = sub_val_bry; break; + case Wbase_claim_globecoordinate_.Tid__precision: prc = sub_val_bry; break; + case Wbase_claim_globecoordinate_.Tid__globe: glb = sub_val_bry; break; + } + } + if (lat == null || lng == null) throw Err_.new_wo_type("pid is invalid globecoordinate", "pid", pid); + return new Wbase_claim_globecoordinate(pid, snak_tid, lat, lng, alt, prc, glb); + } + private Wbase_claim_quantity Parse_datavalue_quantity(byte[] qid, int pid, byte snak_tid, Json_nde nde) { + int len = nde.Len(); + byte[] amount = null, unit = null, ubound = null, lbound = null; + for (int i = 0; i < len; ++i) { + Json_kv sub = Json_kv.cast(nde.Get_at(i)); + byte tid = Wbase_claim_quantity_.Reg.Get_tid_or_max_and_log(qid, sub.Key().Data_bry()); if (tid == Byte_.Max_value_127) continue; + byte[] sub_val_bry = sub.Val().Data_bry(); + switch (tid) { + case Wbase_claim_quantity_.Tid__amount: amount = sub_val_bry; break; + case Wbase_claim_quantity_.Tid__unit: unit = sub_val_bry; break; + case Wbase_claim_quantity_.Tid__upperbound: ubound = sub_val_bry; break; + case Wbase_claim_quantity_.Tid__lowerbound: lbound = sub_val_bry; break; + } + } + if (amount == null) throw Err_.new_wo_type("pid is invalid quantity", "pid", pid); + return new Wbase_claim_quantity(pid, snak_tid, amount, unit, ubound, lbound); + } + private Wbase_claim_time Parse_datavalue_time(byte[] qid, int pid, byte snak_tid, Json_nde nde) { + int len = nde.Len(); + byte[] time = null, timezone = null, before = null, after = null, precision = null, calendarmodel = null; + for (int i = 0; i < len; ++i) { + Json_kv sub = Json_kv.cast(nde.Get_at(i)); + byte tid = Wbase_claim_time_.Reg.Get_tid_or_max_and_log(qid, sub.Key().Data_bry()); if (tid == Byte_.Max_value_127) continue; + byte[] sub_val_bry = sub.Val().Data_bry(); + switch (tid) { + case Wbase_claim_time_.Tid__time: time = sub_val_bry; break; + case Wbase_claim_time_.Tid__timezone: timezone = sub_val_bry; break; + case Wbase_claim_time_.Tid__before: before = sub_val_bry; break; + case Wbase_claim_time_.Tid__after: after = sub_val_bry; break; + case Wbase_claim_time_.Tid__precision: precision = sub_val_bry; break; + case Wbase_claim_time_.Tid__calendarmodel: calendarmodel = sub_val_bry; break; + } + } + if (time == null) throw Err_.new_wo_type("pid is invalid time", "pid", pid); + return new Wbase_claim_time(pid, snak_tid, time, timezone, before, after, precision, calendarmodel); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_claims_parser_v2.java b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_claims_parser_v2.java index a27517de8..1645d511a 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_claims_parser_v2.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_claims_parser_v2.java @@ -13,3 +13,133 @@ 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.wbases.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.core.primitives.*; +import gplx.langs.jsons.*; import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.xtns.wbases.claims.*; import gplx.xowa.xtns.wbases.claims.enums.*; import gplx.xowa.xtns.wbases.claims.itms.*; +class Wdata_claims_parser_v2 { + private final Wbase_claim_factory factory = new Wbase_claim_factory(); + public void Make_claim_itms(byte[] qid, List_adp claim_itms_list, byte[] src, Json_kv claim_grp) { + Json_ary claim_itms_ary = Json_ary.cast_or_null(claim_grp.Val()); + int claim_itms_len = claim_itms_ary.Len(); + int pid = Parse_pid(claim_grp.Key().Data_bry()); + for (int i = 0; i < claim_itms_len; ++i) { + Json_nde claim_itm_nde = Json_nde.cast(claim_itms_ary.Get_at(i)); + Wbase_claim_base itm = Parse_claim_itm(qid, claim_itm_nde, pid); + if (itm != null) // HACK: itm can be null if value is "somevalue"; DATE:2014-09-20 + claim_itms_list.Add(itm); + } + } + private Wbase_claim_base Parse_claim_itm(byte[] qid, Json_nde nde, int pid) { + int len = nde.Len(); + byte rank_tid = Wbase_claim_rank_.Tid__unknown; + Wbase_claim_base claim_itm = null; Wbase_claim_grp_list qualifiers = null; int[] qualifiers_order = null; Wbase_references_grp[] snaks_grp = null; + for (int i = 0; i < len; ++i) { + Json_kv sub = Json_kv.cast(nde.Get_at(i)); + byte tid = Wdata_dict_claim.Reg.Get_tid_or_max_and_log(qid, sub.Key().Data_bry()); if (tid == Byte_.Max_value_127) continue; + switch (tid) { + case Wdata_dict_claim.Tid__mainsnak: claim_itm = Parse_mainsnak(qid, Json_nde.cast(sub.Val()), pid); break; + case Wdata_dict_claim.Tid__rank: rank_tid = Wbase_claim_rank_.Reg.Get_tid_or(sub.Val().Data_bry(), Wbase_claim_rank_.Tid__unknown); break; + case Wdata_dict_claim.Tid__references: snaks_grp = Parse_references(qid, Json_ary.cast_or_null(sub.Val())); break; + case Wdata_dict_claim.Tid__qualifiers: qualifiers = Parse_qualifiers(qid, Json_nde.cast(sub.Val())); break; + case Wdata_dict_claim.Tid__qualifiers_order: qualifiers_order = Parse_pid_order(Json_ary.cast_or_null(sub.Val())); break; + case Wdata_dict_claim.Tid__type: break; // ignore: "statement" + case Wdata_dict_claim.Tid__id: break; // ignore: "Q2$F909BD1C-D34D-423F-9ED2-3493663321AF" + } + } + if (claim_itm != null) { + claim_itm.Rank_tid_(rank_tid); + if (qualifiers != null) claim_itm.Qualifiers_(qualifiers); + if (qualifiers_order != null) claim_itm.Qualifiers_order_(qualifiers_order); + if (snaks_grp != null) claim_itm.References_(snaks_grp); + } + return claim_itm; + } + public Wbase_references_grp[] Parse_references(byte[] qid, Json_ary owner) { + int len = owner.Len(); + Wbase_references_grp[] rv = new Wbase_references_grp[len]; + for (int i = 0; i < len; ++i) { + Json_nde grp_nde = Json_nde.cast(owner.Get_at(i)); + rv[i] = Parse_references_grp(qid, grp_nde); + } + return rv; + } + private Wbase_references_grp Parse_references_grp(byte[] qid, Json_nde owner) { + int len = owner.Len(); + Wbase_claim_grp_list snaks = null; int[] snaks_order = null; + for (int i = 0; i < len; ++i) { + Json_kv sub = Json_kv.cast(owner.Get_at(i)); + byte tid = Wdata_dict_reference.Reg.Get_tid_or_max_and_log(qid, sub.Key().Data_bry()); if (tid == Byte_.Max_value_127) continue; + switch (tid) { + case Wdata_dict_reference.Tid__hash: break; // ignore: "b923b0d68beb300866b87ead39f61e63ec30d8af" + case Wdata_dict_reference.Tid__snaks: snaks = Parse_qualifiers(qid, Json_nde.cast(sub.Val())); break; + case Wdata_dict_reference.Tid__snaks_order: snaks_order = Parse_pid_order(Json_ary.cast_or_null(sub.Val())); break; + } + } + return new Wbase_references_grp(snaks, snaks_order); + } + public Wbase_claim_grp_list Parse_qualifiers(byte[] qid, Json_nde qualifiers_nde) { + Wbase_claim_grp_list rv = new Wbase_claim_grp_list(); + if (qualifiers_nde == null) return rv; // NOTE:sometimes references can have 0 snaks; return back an empty Wbase_claim_grp_list, not null; PAGE:Птичкин,_Евгений_Николаевич; DATE:2015-02-16 + int len = qualifiers_nde.Len(); + for (int i = 0; i < len; ++i) { + Json_kv qualifier_kv = Json_kv.cast(qualifiers_nde.Get_at(i)); + int pid = Parse_pid(qualifier_kv.Key().Data_bry()); + Wbase_claim_grp claims_grp = Parse_props_grp(qid, pid, Json_ary.cast_or_null(qualifier_kv.Val())); + rv.Add(claims_grp); + } + return rv; + } + public int[] Parse_pid_order(Json_ary ary) { + int len = ary.Len(); + int[] rv = new int[len]; + for (int i = 0; i < len; ++i) { + Json_itm pid_itm = ary.Get_at(i); + rv[i] = Parse_pid(pid_itm.Data_bry()); + } + return rv; + } + private Wbase_claim_grp Parse_props_grp(byte[] qid, int pid, Json_ary props_ary) { + List_adp list = List_adp_.New(); + int len = props_ary.Len(); + for (int i = 0; i < len; ++i) { + Json_nde qualifier_nde = Json_nde.cast(props_ary.Get_at(i)); + Wbase_claim_base qualifier_itm = Parse_mainsnak(qid, qualifier_nde, pid); + list.Add(qualifier_itm); + } + return new Wbase_claim_grp(Int_obj_ref.New(pid), (Wbase_claim_base[])list.To_ary_and_clear(Wbase_claim_base.class)); + } + public Wbase_claim_base Parse_mainsnak(byte[] qid, Json_nde nde, int pid) { + int len = nde.Len(); + byte snak_tid = Byte_.Max_value_127; + for (int i = 0; i < len; ++i) { + Json_kv sub = Json_kv.cast(nde.Get_at(i)); + byte tid = Wdata_dict_mainsnak.Reg.Get_tid_or_max_and_log(qid, sub.Key().Data_bry()); if (tid == Byte_.Max_value_127) continue; + switch (tid) { + case Wdata_dict_mainsnak.Tid__snaktype: snak_tid = Wbase_claim_value_type_.Reg.Get_tid_or_fail(sub.Val().Data_bry()); break; + case Wdata_dict_mainsnak.Tid__datavalue: return Parse_datavalue(qid, pid, snak_tid, Json_nde.cast(sub.Val())); + case Wdata_dict_mainsnak.Tid__datatype: break; // ignore: has values like "wikibase-property"; EX: www.wikidata.org/wiki/Property:P397; DATE:2015-06-12 + case Wdata_dict_mainsnak.Tid__property: break; // ignore: pid already available above + case Wdata_dict_mainsnak.Tid__hash: break; // ignore: "84487fc3f93b4f74ab1cc5a47d78f596f0b49390" + } + } + return new Wbase_claim_value(pid, Wbase_claim_type_.Tid__unknown, snak_tid); // NOTE: mainsnak can be null, especially for qualifiers; PAGE:Q2!P576; DATE:2014-09-20 + } + public Wbase_claim_base Parse_datavalue(byte[] qid, int pid, byte snak_tid, Json_nde nde) { + int len = nde.Len(); + Json_itm value_itm = null; byte value_tid = Wbase_claim_type_.Tid__unknown; + for (int i = 0; i < len; ++i) { + Json_kv sub = Json_kv.cast(nde.Get_at(i)); + byte tid = Wdata_dict_datavalue.Reg.Get_tid_or_max_and_log(qid, sub.Key().Data_bry()); if (tid == Byte_.Max_value_127) continue; + switch (tid) { + case Wdata_dict_datavalue.Tid__type: value_tid = Wbase_claim_type_.Get_tid_or_unknown(sub.Val().Data_bry()); break; + case Wdata_dict_datavalue.Tid__value: value_itm = sub.Val(); break; + case Wdata_dict_datavalue.Tid__error: break; // ignore: "Can only construct GlobeCoordinateValue with a String globe parameter" + } + } + return factory.Parse(qid, pid, snak_tid, nde, value_tid, value_itm); + } + private static int Parse_pid(byte[] pid_bry) { + int rv = Bry_.To_int_or(pid_bry, 1, pid_bry.length, -1); if (rv == -1) throw Err_.new_wo_type("invalid pid", "pid", String_.new_u8(pid_bry)); + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser.java b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser.java index a27517de8..7350272d4 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser.java @@ -13,3 +13,16 @@ 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.wbases.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.langs.jsons.*; import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.xtns.wbases.claims.*; import gplx.xowa.xtns.wbases.claims.itms.*; +public interface Wdata_doc_parser { + byte[] Parse_qid(Json_doc doc); + Ordered_hash Parse_sitelinks(byte[] qid, Json_doc doc); + Ordered_hash Parse_langvals(byte[] qid, Json_doc doc, boolean label_or_description); + Ordered_hash Parse_aliases(byte[] qid, Json_doc doc); + Ordered_hash Parse_claims(byte[] qid, Json_doc doc); + Wbase_claim_base Parse_claims_data(byte[] qid, int pid, byte snak_tid, Json_nde nde); + Wbase_claim_grp_list Parse_qualifiers(byte[] qid, Json_nde nde); + int[] Parse_pid_order(byte[] qid, Json_ary ary); + Wbase_references_grp[] Parse_references(byte[] qid, Json_ary owner); +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_fxt_base.java b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_fxt_base.java index a27517de8..005569bab 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_fxt_base.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_fxt_base.java @@ -13,3 +13,88 @@ 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.wbases.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.langs.jsons.*; import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.xtns.wbases.claims.*; import gplx.xowa.xtns.wbases.claims.enums.*; import gplx.xowa.xtns.wbases.claims.itms.*; +abstract class Wdata_doc_parser_fxt_base { + protected Wdata_doc_parser wdoc_parser; + private final Json_parser json_parser = new Json_parser(); + private final Bry_bfr tmp_time_bfr = Bry_bfr_.New(); + public void Init() { + if (wdoc_parser == null) wdoc_parser = Make_parser(); + } + public abstract Wdata_doc_parser Make_parser(); + public Wdata_sitelink_itm Make_sitelink(String site, String name, String... badges) {return new Wdata_sitelink_itm(Bry_.new_u8(site), Bry_.new_u8(name), Bry_.Ary(badges));} + public Wdata_langtext_itm Make_langval(String lang, String text) {return new Wdata_langtext_itm(Bry_.new_u8(lang), Bry_.new_u8(text));} + public Wdata_alias_itm Make_alias(String lang, String... vals) {return new Wdata_alias_itm(Bry_.new_u8(lang), Bry_.Ary(vals));} + public Wbase_claim_base Make_claim_string (int pid, String val) {return new Wbase_claim_string(pid, Wbase_claim_value_type_.Tid__value, Bry_.new_u8(val));} + public Wbase_claim_base Make_claim_entity_qid (int pid, int eid) {return new Wbase_claim_entity(pid, Wbase_claim_value_type_.Tid__value, Wbase_claim_entity_type_.Tid__item, Int_.To_bry(eid));} + public Wbase_claim_base Make_claim_entity_pid (int pid, int eid) {return new Wbase_claim_entity(pid, Wbase_claim_value_type_.Tid__value, Wbase_claim_entity_type_.Tid__property, Int_.To_bry(eid));} + public Wbase_claim_base Make_claim_monolingualtext (int pid, String lang, String text) {return new Wbase_claim_monolingualtext(pid, Wbase_claim_value_type_.Tid__value, Bry_.new_u8(lang), Bry_.new_u8(text));} + public Wbase_claim_base Make_claim_globecoordinate (int pid, String lat, String lng, String prc) {return new Wbase_claim_globecoordinate(pid, Wbase_claim_value_type_.Tid__value, Bry_.new_u8(lat), Bry_.new_u8(lng), Object_.Bry__null, Bry_.new_u8(prc), Bry_.new_a7("http://www.wikidata.org/entity/Q2"));} + public Wbase_claim_base Make_claim_quantity (int pid, int val, int unit, int ubound, int lbound) {return new Wbase_claim_quantity(pid, Wbase_claim_value_type_.Tid__value, Bry_.new_u8(Int_.To_str(val)), Bry_.new_u8(Int_.To_str(unit)), Bry_.new_u8(Int_.To_str(ubound)), Bry_.new_u8(Int_.To_str(lbound)));} + public Wbase_claim_base Make_claim_time (int pid, String val) {return new Wbase_claim_time(pid, Wbase_claim_value_type_.Tid__value, Wbase_claim_time_.To_bry(tmp_time_bfr, val), Wbase_claim_time_.Dflt__timezone.Val_bry(), Wbase_claim_time_.Dflt__before.Val_bry(), Wbase_claim_time_.Dflt__after.Val_bry(), Wbase_claim_time_.Dflt__precision.Val_bry(), Wbase_claim_time_.Dflt__calendarmodel.Val_bry());} + public Wbase_claim_base Make_claim_novalue (int pid) {return new Wbase_claim_value(pid, Wbase_claim_type_.Tid__unknown, Wbase_claim_value_type_.Tid__novalue);} + + public void Test_entity(String raw, String expd) {Tfds.Eq(expd, String_.new_u8(wdoc_parser.Parse_qid(json_parser.Parse_by_apos(raw))));} + public void Test_sitelinks(String raw, Wdata_sitelink_itm... expd) { + Ordered_hash actl_hash = wdoc_parser.Parse_sitelinks(Q1_bry, json_parser.Parse_by_apos(raw)); + Tfds.Eq_ary_str((Wdata_sitelink_itm[])actl_hash.To_ary(Wdata_sitelink_itm.class), expd); + } + public void Test_labels(String raw, Wdata_langtext_itm... expd) {Test_langvals(raw, Bool_.Y, expd);} + public void Test_descriptions(String raw, Wdata_langtext_itm... expd) {Test_langvals(raw, Bool_.N, expd);} + private void Test_langvals(String raw, boolean labels_or_descriptions, Wdata_langtext_itm... expd) { + Ordered_hash actl_hash = wdoc_parser.Parse_langvals(Q1_bry, json_parser.Parse_by_apos(raw), labels_or_descriptions); + Tfds.Eq_ary_str((Wdata_langtext_itm[])actl_hash.To_ary(Wdata_langtext_itm.class), expd); + } + public void Test_aliases(String raw, Wdata_alias_itm... expd) { + Ordered_hash actl_hash = wdoc_parser.Parse_aliases(Q1_bry, json_parser.Parse_by_apos(raw)); + Tfds.Eq_ary_str((Wdata_alias_itm[])actl_hash.To_ary(Wdata_alias_itm.class), expd); + } + public void Test_claims(String raw, Wbase_claim_base... expd) { + Ordered_hash actl_hash = wdoc_parser.Parse_claims(Q1_bry, json_parser.Parse_by_apos(raw)); + List_adp actl_list = Wbase_claim_grp.Xto_list(actl_hash); + Tfds.Eq_ary_str((Wbase_claim_base[])actl_list.To_ary(Wbase_claim_base.class), expd); + } + public void Test_claims_data(String raw, Wbase_claim_base expd) { + Json_doc jdoc = json_parser.Parse_by_apos(raw); + Wbase_claim_base actl = wdoc_parser.Parse_claims_data(Q1_bry, 1, Wbase_claim_value_type_.Tid__value, jdoc.Root_nde()); + Tfds.Eq(expd.toString(), actl.toString()); + } + public void Test_qualifiers(String raw, Wbase_claim_base... expd_itms) { + Json_doc jdoc = json_parser.Parse_by_apos(raw); + Json_nde qualifiers_nde = Json_nde.cast(Json_kv.cast(jdoc.Root_nde().Get_at(0)).Val()); + Wbase_claim_grp_list actl = wdoc_parser.Parse_qualifiers(Q1_bry, qualifiers_nde); + Tfds.Eq_ary_str(expd_itms, To_ary(actl)); + } + public void Test_references(String raw, int[] expd_order, Wbase_claim_base... expd_itms) { + Json_doc jdoc = json_parser.Parse_by_apos(raw); + Json_ary owner = Json_ary.cast_or_null(Json_kv.cast(jdoc.Root_nde().Get_at(0)).Val()); + Wbase_references_grp[] actl = wdoc_parser.Parse_references(Q1_bry, owner); + Wbase_references_grp actl_grp = actl[0]; + Tfds.Eq_ary(expd_order, actl_grp.References_order()); + Tfds.Eq_ary_str(expd_itms, To_ary(actl_grp.References())); + } + public void Test_pid_order(String raw, int... expd) { + Json_doc jdoc = json_parser.Parse_by_apos(raw); + Json_ary nde = Json_ary.cast_or_null(Json_kv.cast(jdoc.Root_nde().Get_at(0)).Val()); + int[] actl = wdoc_parser.Parse_pid_order(Q1_bry, nde); + Tfds.Eq_ary(expd, actl); + } + Wbase_claim_base[] To_ary(Wbase_claim_grp_list list) { + List_adp rv = List_adp_.New(); + int list_len = list.Len(); + for (int i = 0; i < list_len; ++i) { + Wbase_claim_grp grp = list.Get_at(i); + int grp_len = grp.Len(); + for (int j = 0; j < grp_len; ++j) { + Wbase_claim_base itm = grp.Get_at(j); + rv.Add(itm); + } + } + return (Wbase_claim_base[])rv.To_ary_and_clear(Wbase_claim_base.class); + } + private static final byte[] Q1_bry = Bry_.new_a7("Q1"); +} +class Wdata_doc_parser_v2_fxt extends Wdata_doc_parser_fxt_base { + @Override public Wdata_doc_parser Make_parser() {return new Wdata_doc_parser_v2();} +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v1.java b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v1.java index a27517de8..ae1384077 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v1.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v1.java @@ -13,3 +13,251 @@ 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.wbases.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.core.primitives.*; import gplx.langs.jsons.*; +import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.xtns.wbases.claims.*; import gplx.xowa.xtns.wbases.claims.enums.*; import gplx.xowa.xtns.wbases.claims.itms.*; +public class Wdata_doc_parser_v1 implements Wdata_doc_parser { + public Wdata_doc_parser_v1(Gfo_usr_dlg usr_dlg) {this.usr_dlg = usr_dlg;} private Gfo_usr_dlg usr_dlg; + public Wdata_doc_parser_v1() {} + public byte[] Parse_qid(Json_doc doc) { + try { + Json_itm kv_val = doc.Find_nde(Bry_entity); + switch (kv_val.Tid()) { + case Json_itm_.Tid__str: // "entity":"q1" + return kv_val.Data_bry(); + case Json_itm_.Tid__ary: // "entity":["item",1] + Json_ary kv_val_as_ary = (Json_ary)kv_val; + Json_itm entity_id = kv_val_as_ary.Get_at(1); + return Bry_.Add(Byte_ascii.Ltr_q, entity_id.Data_bry()); + default: + throw Err_.new_unhandled(kv_val.Tid()); + } + } catch (Exception e) {throw Err_.new_exc(e, "xo", "failed to parse qid", "src", String_.new_u8(doc.Src()));} + } + public Ordered_hash Parse_sitelinks(byte[] qid, Json_doc doc) { + try { + Json_nde list_nde = Json_nde.cast(doc.Get_grp(Bry_links)); if (list_nde == null) return Wdata_doc_parser_v1.Empty_ordered_hash_bry; + Ordered_hash rv = Ordered_hash_.New_bry(); + int list_len = list_nde.Len(); + for (int i = 0; i < list_len; ++i) { + Json_kv wiki_kv = Json_kv.cast(list_nde.Get_at(i)); + byte[] site_bry = wiki_kv.Key().Data_bry(); + byte[] title_bry = null; byte[][] badges_bry_ary = null; + if (wiki_kv.Val().Tid() == Json_itm_.Tid__nde) { // v1.2: "enwiki":{name:"Earth", badges:[]} + Json_nde val_nde = Json_nde.cast(wiki_kv.Val()); + Json_kv name_kv = Json_kv.cast(val_nde.Get_at(0)); + title_bry = name_kv.Val().Data_bry(); + Json_kv badges_kv = Json_kv.cast(val_nde.Get_at(1)); + if (badges_kv != null) {// TEST:some tests do not define a badges nde; ignore if null; DATE:2014-09-19 + Json_ary badges_ary = Json_ary.cast_or_null(badges_kv.Val()); + badges_bry_ary = badges_ary.Xto_bry_ary(); + } + } + else { // v1.1: "enwiki":"Earth" + title_bry = wiki_kv.Val().Data_bry(); + } + Wdata_sitelink_itm itm = new Wdata_sitelink_itm(site_bry, title_bry, badges_bry_ary); + rv.Add(site_bry, itm); + } + return rv; + } catch (Exception e) {throw Err_.new_exc(e, "xo", "failed to parse sitelinks", "qid", String_.new_u8(qid));} + } + public Ordered_hash Parse_langvals(byte[] qid, Json_doc doc, boolean label_or_description) { + try { + byte[] langval_key = label_or_description ? Bry_label : Bry_description; + Json_nde list_nde = Json_nde.cast(doc.Get_grp(langval_key)); if (list_nde == null) return Wdata_doc_parser_v1.Empty_ordered_hash_bry; + Ordered_hash rv = Ordered_hash_.New_bry(); + int list_len = list_nde.Len(); + for (int i = 0; i < list_len; ++i) { + Json_kv data_kv = Json_kv.cast(list_nde.Get_at(i)); + byte[] lang_bry = data_kv.Key().Data_bry(); + Wdata_langtext_itm itm = new Wdata_langtext_itm(lang_bry, data_kv.Val().Data_bry()); + rv.Add(lang_bry, itm); + } + return rv; + } catch (Exception e) {throw Err_.new_exc(e, "xo", "failed to parse langvals", "qid", String_.new_u8(qid), "langval_tid", label_or_description);} + } + public Ordered_hash Parse_aliases(byte[] qid, Json_doc doc) { + try { + Json_nde list_nde = Json_nde.cast(doc.Get_grp(Bry_aliases)); if (list_nde == null) return Wdata_doc_parser_v1.Empty_ordered_hash_bry; + Ordered_hash rv = Ordered_hash_.New_bry(); + int list_len = list_nde.Len(); + for (int i = 0; i < list_len; ++i) { + Json_kv data_kv = Json_kv.cast(list_nde.Get_at(i)); + byte[] lang_bry = data_kv.Key().Data_bry(); + byte[][] vals_bry_ary = null; + Json_itm data_val = data_kv.Val(); + switch (data_val.Tid()) { + case Json_itm_.Tid__ary: // EX: 'en':['en_val_1','en_val_2'] + Json_ary vals_ary = Json_ary.cast_or_null(data_val); + vals_bry_ary = vals_ary.Xto_bry_ary(); + break; + case Json_itm_.Tid__nde: // EX: 'en':{'0:en_val_1','1:en_val_2'}; PAGE:wd.q:621080 DATE:2014-09-21 + Json_nde vals_nde = Json_nde.cast(data_val); + int vals_len = vals_nde.Len(); + vals_bry_ary = new byte[vals_len][]; + for (int j = 0; j < vals_len; ++j) { + Json_kv vals_sub_kv = Json_kv.cast(vals_nde.Get_at(j)); + vals_bry_ary[j] = vals_sub_kv.Val().Data_bry(); + } + break; + default: throw Err_.new_unhandled(data_val.Tid()); + } + Wdata_alias_itm itm = new Wdata_alias_itm(lang_bry, vals_bry_ary); + rv.Add(lang_bry, itm); + } + return rv; + } catch (Exception e) {throw Err_.new_exc(e, "xo", "failed to parse aliases", "qid", String_.new_u8(qid));} + } + public Ordered_hash Parse_claims(byte[] qid, Json_doc doc) { + try { + Json_ary list_nde = Json_ary.cast_or_null(doc.Get_grp(Bry_claims)); if (list_nde == null) return Empty_ordered_hash_generic; + List_adp temp_list = List_adp_.New(); + byte[] src = doc.Src(); + int len = list_nde.Len(); + for (int i = 0; i < len; i++) { + Json_nde claim_nde = Json_nde.cast(list_nde.Get_at(i)); + Wbase_claim_base claim_itm = Make_claim_itm(src, claim_nde); + temp_list.Add(claim_itm); + } + return Claims_list_to_hash(temp_list); + } catch (Exception e) {throw Err_.new_exc(e, "xo", "failed to parse claims", "qid", String_.new_u8(doc.Src()));} + } + public Wbase_claim_base Parse_claims_data(byte[] qid, int pid, byte snak_tid, Json_nde nde) {throw Err_.new_unimplemented();} + public static Ordered_hash Claims_list_to_hash(List_adp full_list) { + full_list.Sort(); + Ordered_hash rv = Ordered_hash_.New(); List_adp temp_itms = List_adp_.New(); + int prv_pid = -1; + int len = full_list.Count(); + for (int i = 0; i < len; ++i) { + Wbase_claim_base claim_itm = (Wbase_claim_base)full_list.Get_at(i); + int cur_pid = claim_itm.Pid(); + if (prv_pid != cur_pid && prv_pid != -1) + Claims_list_to_hash__add(rv, prv_pid, temp_itms); + temp_itms.Add(claim_itm); + prv_pid = cur_pid; + } + Claims_list_to_hash__add(rv, prv_pid, temp_itms); + return rv; + } + private static void Claims_list_to_hash__add(Ordered_hash rv, int pid, List_adp temp_itms) { + if (temp_itms.Count() == 0) return; // NOTE: will be empty when claims are empty; EX: "claims": []; PAGE:wd.p:585; DATE:2014-10-03 + Int_obj_ref claim_grp_key = Int_obj_ref.New(pid); + Wbase_claim_grp claim_grp = new Wbase_claim_grp(claim_grp_key, (Wbase_claim_base[])temp_itms.To_ary_and_clear(Wbase_claim_base.class)); + rv.Add(claim_grp_key, claim_grp); + } + private Wbase_claim_base Make_claim_itm(byte[] src, Json_nde prop_nde) { + int len = prop_nde.Len(); // should have 5 (m, q, g, rank, refs), but don't enforce (can rely on keys) + Wbase_claim_base rv = null; + for (int i = 0; i < len; i++) { + Json_kv kv = Json_kv.cast(prop_nde.Get_at(i)); + Json_itm kv_key = kv.Key(); + Byte_obj_val bv = (Byte_obj_val)Prop_key_hash.Get_by_mid(src, kv_key.Src_bgn(), kv_key.Src_end()); + if (bv == null) {Warn("invalid prop node: ~{0}", String_.new_u8(src, kv_key.Src_bgn(), kv_key.Src_end())); return null;} + switch (bv.Val()) { + case Prop_tid_m: + rv = New_prop_by_m(src, Json_ary.cast_or_null(kv.Val())); + if (rv == null) return null; + break; + case Prop_tid_g: + rv.Wguid_(kv.Data_bry()); + break; + case Prop_tid_rank: + rv.Rank_tid_((byte)Int_.Cast(((Json_itm_int)kv.Val()).Data_as_int())); + break; + case Prop_tid_q: + break; + case Prop_tid_refs: + break; + default: throw Err_.new_unhandled(bv.Val()); + } + } + return rv; + } + private Wbase_claim_base New_prop_by_m(byte[] src, Json_ary ary) { + byte snak_tid = Wbase_claim_value_type_.Reg.Get_tid_or_fail(ary.Get_at(0).Data_bry()); + int pid = Json_itm_int.cast(ary.Get_at(1)).Data_as_int(); + switch (snak_tid) { + case Wbase_claim_value_type_.Tid__novalue : return Wbase_claim_value.New_novalue(pid); + case Wbase_claim_value_type_.Tid__somevalue : return Wbase_claim_value.New_somevalue(pid); + } + Json_itm val_tid_itm = ary.Get_at(2); + byte val_tid = Wbase_claim_type_.Get_tid_or_unknown(src, val_tid_itm.Src_bgn(), val_tid_itm.Src_end()); + return Make_itm(pid, snak_tid, val_tid, ary); + } + private Wbase_claim_base Make_itm(int pid, byte snak_tid, byte val_tid, Json_ary ary) { + switch (val_tid) { + case Wbase_claim_type_.Tid__string: + return new Wbase_claim_string(pid, snak_tid, ary.Get_at(3).Data_bry()); + case Wbase_claim_type_.Tid__entity: { + Json_nde sub_nde = Json_nde.cast(ary.Get_at(3)); + Json_kv entity_kv = Json_kv.cast(sub_nde.Get_at(1)); + return new Wbase_claim_entity(pid, snak_tid, Wbase_claim_entity_type_.Tid__item, entity_kv.Val().Data_bry()); + } + case Wbase_claim_type_.Tid__time: { + Json_nde sub_nde = Json_nde.cast(ary.Get_at(3)); + return new Wbase_claim_time(pid, snak_tid, Get_val(sub_nde, 0), Get_val(sub_nde, 1), Get_val(sub_nde, 2), Get_val(sub_nde, 3), Get_val(sub_nde, 4), Get_val(sub_nde, 5)); + } + case Wbase_claim_type_.Tid__globecoordinate: case Wbase_claim_type_.Tid__bad: { + Json_nde sub_nde = Json_nde.cast(ary.Get_at(3)); + return new Wbase_claim_globecoordinate(pid, snak_tid, Get_val(sub_nde, 0), Get_val(sub_nde, 1), Get_val(sub_nde, 2), Get_val(sub_nde, 3), Get_val(sub_nde, 4)); + } + case Wbase_claim_type_.Tid__quantity: { + Json_nde sub_nde = Json_nde.cast(ary.Get_at(3)); + return new Wbase_claim_quantity(pid, snak_tid, Get_val(sub_nde, 0), Get_val(sub_nde, 1), Get_val(sub_nde, 2), Get_val(sub_nde, 3)); + } + case Wbase_claim_type_.Tid__monolingualtext: { + Json_nde sub_nde = Json_nde.cast(ary.Get_at(3)); + return new Wbase_claim_monolingualtext(pid, snak_tid, Get_val(sub_nde, 1), Get_val(sub_nde, 0)); + } + default: {throw Err_.new_unhandled(val_tid);} + } + } + private static byte[] Get_val(Json_nde sub_nde, int i) { + Json_kv kv = Json_kv.cast(sub_nde.Get_at(i)); + return kv.Val().Data_bry(); + } + private void Warn(String fmt, Object... args) {usr_dlg.Warn_many("", "", fmt, args);} + public static final Ordered_hash Empty_ordered_hash_bry = Ordered_hash_.New_bry(), Empty_ordered_hash_generic = Ordered_hash_.New(); + private static final byte Prop_tid_m = 0, Prop_tid_q = 1, Prop_tid_g = 2, Prop_tid_rank = 3, Prop_tid_refs = 4; + private static final Hash_adp_bry Prop_key_hash = Hash_adp_bry.ci_a7() + .Add_bry_byte(Wdata_dict_claim_v1.Bry_m , Prop_tid_m) + .Add_bry_byte(Wdata_dict_claim_v1.Bry_q , Prop_tid_q) + .Add_bry_byte(Wdata_dict_claim_v1.Bry_g , Prop_tid_g) + .Add_bry_byte(Wdata_dict_claim_v1.Bry_rank , Prop_tid_rank) + .Add_bry_byte(Wdata_dict_claim_v1.Bry_refs , Prop_tid_refs); + Ordered_hash Bld_hash(Json_doc doc, byte[] key) { + Json_nde nde = Json_nde.cast(doc.Get_grp(key)); if (nde == null) return Empty_ordered_hash_bry; + Ordered_hash rv = Ordered_hash_.New_bry(); + int len = nde.Len(); + for (int i = 0; i < len; i++) { + Json_kv kv = Json_kv.cast(nde.Get_at(i)); + byte[] kv_key = kv.Key().Data_bry(); + rv.Add(kv_key, kv); + } + return rv; + } + public Wbase_claim_grp_list Parse_qualifiers(byte[] qid, Json_nde nde) {throw Err_.new_unimplemented();} + public Wbase_references_grp[] Parse_references(byte[] qid, Json_ary owner) {throw Err_.new_unimplemented();} + public int[] Parse_pid_order(byte[] qid, Json_ary ary) {throw Err_.new_unimplemented();} + public static final String + Str_entity = "entity" + , Str_id = "id" + , Str_links = "links" + , Str_label = "label" + , Str_aliases = "aliases" + , Str_claims = "claims" + , Str_description = "description" + , Str_name = "name" + ; + public static final byte[] + Bry_entity = Bry_.new_a7(Str_entity) + , Bry_id = Bry_.new_a7(Str_id) + , Bry_links = Bry_.new_a7(Str_links) + , Bry_label = Bry_.new_a7(Str_label) + , Bry_aliases = Bry_.new_a7(Str_aliases) + , Bry_claims = Bry_.new_a7(Str_claims) + , Bry_description = Bry_.new_a7(Str_description) + , Bry_name = Bry_.new_a7(Str_name) + ; +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v1_tst.java b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v1_tst.java index a27517de8..73f1a5c10 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v1_tst.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v1_tst.java @@ -13,3 +13,173 @@ 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.wbases.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import org.junit.*; +import gplx.langs.jsons.*; import gplx.xowa.xtns.wbases.core.*; +public class Wdata_doc_parser_v1_tst { + @Before public void init() {fxt.Init();} private Wdata_doc_parser_v1_fxt fxt = new Wdata_doc_parser_v1_fxt(); + @Test public void Entity_v1_1() { + fxt.Test_entity("{ 'entity':'q1' }", "q1"); + } + @Test public void Entity_v1_2() { + fxt.Test_entity("{ 'entity':['item',1] }", "q1"); + } + @Test public void Sitelink_v1_1() { + fxt.Test_sitelinks(String_.Concat_lines_nl_skip_last + ( "{ 'links':" + , " { 'enwiki':'en_val'" + , " , 'dewiki':'de_val'" + , " , 'frwiki':'fr_val'" + , " }" + , "}" + ) + , fxt.Make_sitelink("enwiki", "en_val") + , fxt.Make_sitelink("dewiki", "de_val") + , fxt.Make_sitelink("frwiki", "fr_val") + ); + } + @Test public void Sitelink_v1_2() { + fxt.Test_sitelinks(String_.Concat_lines_nl_skip_last + ( "{ 'links':" + , " { 'enwiki':" + , " { 'name':'en_val'" + , " , 'badges':" + , " [ 'Q10'" + , " , 'Q11'" + , " , 'Q12'" + , " ]" + , " }" + , " , 'dewiki':" + , " { 'name':'de_val'" + , " , 'badges':" + , " [ 'Q2'" + , " ]" + , " }" + , " , 'frwiki':" + , " { 'name':'fr_val'" + , " , 'badges':[]" + , " }" + , " }" + , "}" + ) + , fxt.Make_sitelink("enwiki", "en_val", "Q10", "Q11", "Q12") + , fxt.Make_sitelink("dewiki", "de_val", "Q2") + , fxt.Make_sitelink("frwiki", "fr_val") + ); + } + @Test public void Labels() { + fxt.Test_labels(String_.Concat_lines_nl_skip_last + ( "{ 'label':" + , " { 'en':'en_val'" + , " , 'de':'de_val'" + , " , 'fr':'fr_val'" + , " }" + , "}" + ) + , fxt.Make_langval("en", "en_val") + , fxt.Make_langval("de", "de_val") + , fxt.Make_langval("fr", "fr_val") + ); + } + @Test public void Descriptions() { + fxt.Test_descriptions(String_.Concat_lines_nl_skip_last + ( "{ 'description':" + , " { 'en':'en_val'" + , " , 'de':'de_val'" + , " , 'fr':'fr_val'" + , " }" + , "}" + ) + , fxt.Make_langval("en", "en_val") + , fxt.Make_langval("de", "de_val") + , fxt.Make_langval("fr", "fr_val") + ); + } + @Test public void Aliases() { + fxt.Test_aliases(String_.Concat_lines_nl_skip_last + ( "{ 'aliases':" + , " { 'en':" + , " [ 'en_val_1'" + , " , 'en_val_2'" + , " , 'en_val_3'" + , " ]" + , " ," + , " 'de':" + , " [ 'de_val_1'" + , " , 'de_val_2'" + , " ]" + , " ," + , " 'fr':" + , " [ 'fr_val_1'" + , " ]" + , " }" + , "}" + ) + , fxt.Make_alias("en", "en_val_1", "en_val_2", "en_val_3") + , fxt.Make_alias("de", "de_val_1", "de_val_2") + , fxt.Make_alias("fr", "fr_val_1") + ); + } + @Test public void Aliases_alt() { + fxt.Test_aliases(String_.Concat_lines_nl_skip_last + ( "{ 'aliases':" + , " { 'en':" + , " { '0':'en_val_1'" + , " , '1':'en_val_2'" + , " , '2':'en_val_3'" + , " }" + , " }" + , "}" + ) + , fxt.Make_alias("en", "en_val_1", "en_val_2", "en_val_3") + ); + } + @Test public void Claims() { + fxt.Test_claims(String_.Concat_lines_nl_skip_last + ( "{ 'claims':" + , " [" + , " { 'm':" + , " [ 'value'" + , " , 1" + , " , 'string'" + , " , 'abc'" + , " ]" + , " , 'q':[]" + , " , 'g':'Q2$e8ba1188-4aec-9e37-a75e-f79466c1913e'" + , " , 'rank':1" + , " , 'refs':[]" + , " }" + , " ]" + , "}" + ) + , fxt.Make_claim_string(1, "abc") + ); + } + @Test public void Claim_bad() { // wikidata flags several entries as "bad"; https://www.wikidata.org/wiki/Wikidata:Project_chat/Archive/2013/10 + fxt.Test_claims(String_.Concat_lines_nl_skip_last + ( "{ 'entity':['item',2]" + , ", 'claims':" + , " [" + , " { 'm':" + , " [ 'value'" + , " , 373" + , " , 'bad'" + , " ," + , " { 'latitude':1" + , " , 'longitude':2" + , " , 'altitude':null" + , " , 'precision':1" + , " , 'globe':'http:\\/\\/www.wikidata.org\\/entity\\/Q2'" + , " }" + , " ]" + , " }" + , " ]" + , "}" + ) + , fxt.Make_claim_globecoordinate(1, "1", "2", "1") // assume "bad" is same as globecoordinate; DATE:2014-09-20 + ); + } +} +class Wdata_doc_parser_v1_fxt extends Wdata_doc_parser_fxt_base { + @Override public Wdata_doc_parser Make_parser() {return new Wdata_doc_parser_v1();} +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v2.java b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v2.java index a27517de8..c48c34285 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v2.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v2.java @@ -13,3 +13,133 @@ 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.wbases.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.langs.jsons.*; import gplx.core.btries.*; +import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.xtns.wbases.claims.*; import gplx.xowa.xtns.wbases.claims.enums.*; import gplx.xowa.xtns.wbases.claims.itms.*; +public class Wdata_doc_parser_v2 implements Wdata_doc_parser { + private Wdata_claims_parser_v2 claims_parser = new Wdata_claims_parser_v2(); + public byte[] Parse_qid(Json_doc doc) { + try { + Json_itm itm = doc.Find_nde(Bry_id); + return Bry_.Lcase__1st(itm.Data_bry()); // standardize on "q" instead of "Q" for compatibility with v1 + } catch (Exception e) {throw Err_.new_exc(e, "xo", "failed to parse qid", "src", String_.new_u8(doc.Src()));} + } + public Ordered_hash Parse_sitelinks(byte[] qid, Json_doc doc) { + try { + Json_nde list_nde = Json_nde.cast(doc.Get_grp(Bry_sitelinks)); if (list_nde == null) return Wdata_doc_parser_v1.Empty_ordered_hash_bry; + Ordered_hash rv = Ordered_hash_.New_bry(); + int list_len = list_nde.Len(); + for (int i = 0; i < list_len; ++i) { + Json_kv data_kv = Json_kv.cast(list_nde.Get_at(i)); + Json_nde data_nde = Json_nde.cast(data_kv.Val()); + int data_nde_len = data_nde.Len(); + Json_kv site_kv = null, name_kv = null; Json_ary badges_ary = null; + for (int j = 0; j < data_nde_len; ++j) { + Json_kv sub = Json_kv.cast(data_nde.Get_at(j)); + byte tid = Wdata_dict_sitelink.Reg.Get_tid_or_max_and_log(qid, sub.Key().Data_bry()); if (tid == Byte_.Max_value_127) continue; + switch (tid) { + case Wdata_dict_sitelink.Tid__site: site_kv = Json_kv.cast(sub); break; + case Wdata_dict_sitelink.Tid__title: name_kv = Json_kv.cast(sub); break; + case Wdata_dict_sitelink.Tid__badges: badges_ary = Json_ary.cast_or_null(Json_kv.cast(sub).Val()); break; + } + } + byte[] site_bry = site_kv.Val().Data_bry(); + Wdata_sitelink_itm itm = new Wdata_sitelink_itm(site_bry, name_kv.Val().Data_bry(), badges_ary.Xto_bry_ary()); + rv.Add(site_bry, itm); + } + return rv; + } catch (Exception e) {throw Err_.new_exc(e, "xo", "failed to parse sitelinks", "qid", String_.new_u8(qid));} + } + public Ordered_hash Parse_langvals(byte[] qid, Json_doc doc, boolean label_or_description) { + try { + byte[] langval_key = label_or_description ? Bry_labels : Bry_descriptions; + Json_nde list_nde = Json_nde.cast(doc.Get_grp(langval_key)); if (list_nde == null) return Wdata_doc_parser_v1.Empty_ordered_hash_bry; + Ordered_hash rv = Ordered_hash_.New_bry(); + int list_len = list_nde.Len(); + for (int i = 0; i < list_len; ++i) { + Json_kv data_kv = Json_kv.cast(list_nde.Get_at(i)); + Json_nde data_nde = Json_nde.cast(data_kv.Val()); + Json_kv text_kv = null; + int data_nde_len = data_nde.Len(); + for (int j = 0; j < data_nde_len; ++j) { + Json_kv sub = Json_kv.cast(data_nde.Get_at(j)); + byte tid = Wdata_dict_langtext.Reg.Get_tid_or_max_and_log(qid, sub.Key().Data_bry()); if (tid == Byte_.Max_value_127) continue; + switch (tid) { + case Wdata_dict_langtext.Tid__language: break; + case Wdata_dict_langtext.Tid__value: text_kv = Json_kv.cast(sub); break; + } + } + byte[] lang_bry = data_kv.Key().Data_bry(); + Wdata_langtext_itm itm = new Wdata_langtext_itm(lang_bry, text_kv.Val().Data_bry()); + rv.Add(lang_bry, itm); + } + return rv; + } catch (Exception e) {throw Err_.new_exc(e, "xo", "failed to parse langvals", "qid", String_.new_u8(qid), "langval_tid", label_or_description);} + } + public Ordered_hash Parse_aliases(byte[] qid, Json_doc doc) { + try { + Json_nde list_nde = Json_nde.cast(doc.Get_grp(Bry_aliases)); if (list_nde == null) return Wdata_doc_parser_v1.Empty_ordered_hash_bry; + Ordered_hash rv = Ordered_hash_.New_bry(); + int list_len = list_nde.Len(); + for (int i = 0; i < list_len; ++i) { + Json_kv data_kv = Json_kv.cast(list_nde.Get_at(i)); + Json_ary vals_ary = Json_ary.cast_or_null(data_kv.Val()); + int vals_len = vals_ary.Len(); + byte[][] vals = new byte[vals_len][]; + for (int j = 0; j < vals_len; ++j) { + Json_nde lang_nde = Json_nde.cast(vals_ary.Get_at(j)); + int k_len = lang_nde.Len(); + for (int k = 0; k < k_len; ++k) { + Json_kv sub = Json_kv.cast(lang_nde.Get_at(k)); + byte tid = Wdata_dict_langtext.Reg.Get_tid_or_max_and_log(qid, sub.Key().Data_bry()); if (tid == Byte_.Max_value_127) continue; + switch (tid) { + case Wdata_dict_langtext.Tid__language: break; + case Wdata_dict_langtext.Tid__value: vals[j] = sub.Val().Data_bry(); break; + } + } + } + byte[] lang_bry = data_kv.Key().Data_bry(); + Wdata_alias_itm itm = new Wdata_alias_itm(lang_bry, vals); + rv.Add(lang_bry, itm); + } + return rv; + } catch (Exception e) {throw Err_.new_exc(e, "xo", "failed to parse sitelinks", "qid", String_.new_u8(qid));} + } + public Ordered_hash Parse_claims(byte[] qid, Json_doc doc) { + synchronized (this) {// TS; DATE:2016-07-06 + try { + Json_nde list_nde = Json_nde.cast(doc.Get_grp(Bry_claims)); if (list_nde == null) return Wdata_doc_parser_v1.Empty_ordered_hash_generic; + List_adp temp_list = List_adp_.New(); + byte[] src = doc.Src(); + int len = list_nde.Len(); + for (int i = 0; i < len; i++) { + Json_kv claim_nde = Json_kv.cast(list_nde.Get_at(i)); + claims_parser.Make_claim_itms(qid, temp_list, src, claim_nde); + } + return Wdata_doc_parser_v1.Claims_list_to_hash(temp_list); + } catch (Exception e) {throw Err_.new_exc(e, "xo", "failed to parse claims", "qid", String_.new_u8(doc.Src()));} + } + } + public Wbase_claim_base Parse_claims_data(byte[] qid, int pid, byte snak_tid, Json_nde nde) {return claims_parser.Parse_datavalue(qid, pid, snak_tid, nde);} + public Wbase_claim_grp_list Parse_qualifiers(byte[] qid, Json_nde nde) {return claims_parser.Parse_qualifiers(qid, nde);} + public Wbase_references_grp[] Parse_references(byte[] qid, Json_ary owner) {return claims_parser.Parse_references(qid, owner);} + public int[] Parse_pid_order(byte[] qid, Json_ary ary) {return claims_parser.Parse_pid_order(ary);} + public static final String + Str_id = "id" + , Str_sitelinks = "sitelinks" + , Str_labels = "labels" + , Str_descriptions = "descriptions" + , Str_aliases = "aliases" + , Str_claims = "claims" + , Str_type = "type" + ; + public static final byte[] + Bry_id = Bry_.new_a7(Str_id) + , Bry_sitelinks = Bry_.new_a7(Str_sitelinks) + , Bry_labels = Bry_.new_a7(Str_labels) + , Bry_descriptions = Bry_.new_a7(Str_descriptions) + , Bry_aliases = Bry_.new_a7(Str_aliases) + , Bry_claims = Bry_.new_a7(Str_claims) + , Bry_type = Bry_.new_a7(Str_type) + ; +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v2__basic__tst.java b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v2__basic__tst.java index a27517de8..0683a02fe 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v2__basic__tst.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v2__basic__tst.java @@ -13,3 +13,246 @@ 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.wbases.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import org.junit.*; +import gplx.langs.jsons.*; import gplx.xowa.xtns.wbases.core.*; +public class Wdata_doc_parser_v2__basic__tst { + @Before public void init() {fxt.Init();} private Wdata_doc_parser_v2_fxt fxt = new Wdata_doc_parser_v2_fxt(); + @Test public void Entity() { + fxt.Test_entity("{ 'id':'Q2' }", "q2"); + } + @Test public void Sitelink() { + fxt.Test_sitelinks(String_.Concat_lines_nl_skip_last + ( "{ 'sitelinks':" + , " { 'enwiki':" + , " { 'site':'enwiki'" + , " , 'title':'en_val'" + , " , 'badges':" + , " [ 'Q10'" + , " , 'Q11'" + , " , 'Q12'" + , " ]" + , " }" + , " , 'dewiki':" + , " { 'site':'dewiki'" + , " , 'title':'de_val'" + , " , 'badges':" + , " [ 'Q2'" + , " ]" + , " }" + , " , 'frwiki':" + , " { 'site':'frwiki'" + , " , 'title':'fr_val'" + , " , 'badges':[]" + , " }" + , " }" + , "}" + ) + , fxt.Make_sitelink("enwiki", "en_val", "Q10", "Q11", "Q12") + , fxt.Make_sitelink("dewiki", "de_val", "Q2") + , fxt.Make_sitelink("frwiki", "fr_val") + ); + } + @Test public void Labels() { + fxt.Test_labels(String_.Concat_lines_nl_skip_last + ( "{ 'labels':" + , " { 'en':" + , " { 'language':'enwiki'" + , " , 'value':'en_val'" + , " }" + , " , 'de':" + , " { 'language':'dewiki'" + , " , 'value':'de_val'" + , " }" + , " , 'fr':" + , " { 'language':'frwiki'" + , " , 'value':'fr_val'" + , " }" + , " }" + , "}" + ) + , fxt.Make_langval("en", "en_val") + , fxt.Make_langval("de", "de_val") + , fxt.Make_langval("fr", "fr_val") + ); + } + @Test public void Descriptions() { + fxt.Test_descriptions(String_.Concat_lines_nl_skip_last + ( "{ 'descriptions':" + , " { 'en':" + , " { 'language':'enwiki'" + , " , 'value':'en_val'" + , " }" + , " , 'de':" + , " { 'language':'dewiki'" + , " , 'value':'de_val'" + , " }" + , " , 'fr':" + , " { 'language':'frwiki'" + , " , 'value':'fr_val'" + , " }" + , " }" + , "}" + ) + , fxt.Make_langval("en", "en_val") + , fxt.Make_langval("de", "de_val") + , fxt.Make_langval("fr", "fr_val") + ); + } + @Test public void Aliases() { + fxt.Test_aliases(String_.Concat_lines_nl_skip_last + ( "{ 'aliases':" + , " { 'en':" + , " [" + , " { 'language':'en'" + , " , 'value':'en_val_1'" + , " }" + , " ," + , " { 'language':'en'" + , " , 'value':'en_val_2'" + , " }" + , " ," + , " { 'language':'en'" + , " , 'value':'en_val_3'" + , " }" + , " ]" + , " ," + , " 'de':" + , " [" + , " { 'language':'de'" + , " , 'value':'de_val_1'" + , " }" + , " ," + , " { 'language':'de'" + , " , 'value':'de_val_2'" + , " }" + , " ]" + , " ," + , " 'fr':" + , " [" + , " { 'language':'fr'" + , " , 'value':'fr_val_1'" + , " }" + , " ]" + , " }" + , "}" + ) + , fxt.Make_alias("en", "en_val_1", "en_val_2", "en_val_3") + , fxt.Make_alias("de", "de_val_1", "de_val_2") + , fxt.Make_alias("fr", "fr_val_1") + ); + } + @Test public void Qualifiers() { + fxt.Test_qualifiers(String_.Concat_lines_nl_skip_last + ( "{ 'qualifiers':" + , " { 'P1':" + , " [ " + , " { 'snaktype':'value'" + , " , 'property':'P1'" + , " , 'hash':''" + , " , 'datavalue':" + , " { 'value':" + , " { 'entity-type':'item'" + , " , 'numeric-id':11" + , " }" + , " , 'type':'wikibase-entityid'" + , " }" + , " }" + , " ," + , " { 'snaktype':'value'" + , " , 'property':'P1'" + , " , 'hash':''" + , " , 'datavalue':" + , " { 'value':" + , " { 'entity-type':'item'" + , " , 'numeric-id':12" + , " }" + , " , 'type':'wikibase-entityid'" + , " }" + , " }" + , " ]" + , " ," + , " 'P2':" + , " [ " + , " { 'snaktype':'value'" + , " , 'property':'P2'" + , " , 'hash':''" + , " , 'datavalue':" + , " { 'value':" + , " { 'entity-type':'item'" + , " , 'numeric-id':21" + , " }" + , " , 'type':'wikibase-entityid'" + , " }" + , " }" + , " ]" + , " }" + , "}" + ), fxt.Make_claim_entity_qid(1, 11), fxt.Make_claim_entity_qid(1, 12), fxt.Make_claim_entity_qid(2, 21) + ); + } + @Test public void Pid_order() { + fxt.Test_pid_order + ( "{ 'qualifiers-order':['P1', 'P2', 'P3'] }" + , 1, 2, 3 + ); + } + @Test public void References() { + fxt.Test_references(String_.Concat_lines_nl_skip_last + ( "{ 'references':" + , " [ " + , " { 'hash':'8e7d51e38606193465d2a1e9d41ba490e06682a6'" + , " , 'snaks':" + , " { 'P2':" + , " [ " + , " { 'snaktype':'value'" + , " , 'property':'P2'" + , " , 'hash':'358e3c0ffa2bfecfe962b39141d99dc2d482110f'" + , " , 'datavalue':" + , " { 'value':" + , " { 'entity-type':'item'" + , " , 'numeric-id':21" + , " }" + , " , 'type':'wikibase-entityid'" + , " }" + , " }" + , " ]" + , " , 'P3':" + , " [ " + , " { 'snaktype':'value'" + , " , 'property':'P3'" + , " , 'hash':'358e3c0ffa2bfecfe962b39141d99dc2d482110f'" + , " , 'datavalue':" + , " { 'value':" + , " { 'entity-type':'item'" + , " , 'numeric-id':31" + , " }" + , " , 'type':'wikibase-entityid'" + , " }" + , " }" + , " ]" + , " }" + , " , 'snaks-order':" + , " [ 'P2'" + , " , 'P3'" + , " ]" + , " }" + , " ]" + , "}" + ), Int_ary_.New(2, 3), fxt.Make_claim_entity_qid(2, 21), fxt.Make_claim_entity_qid(3, 31)) + ; + } + @Test public void References_empty() { // PURPOSE:sometimes references can have 0 snaks; return back an empty Wbase_claim_grp_list, not null; PAGE:Птичкин,_Евгений_Николаевич; DATE:2015-02-16 + fxt.Test_references(String_.Concat_lines_nl_skip_last + ( "{ 'references':" + , " [ " + , " { 'hash':'8e7d51e38606193465d2a1e9d41ba490e06682a6'" + , " , 'snaks':[]" + , " , 'snaks-order':[]" + , " }" + , " ]" + , "}" + ), Int_ary_.Empty) + ; + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v2__claims__tst.java b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v2__claims__tst.java index a27517de8..c00489c7d 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v2__claims__tst.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/parsers/Wdata_doc_parser_v2__claims__tst.java @@ -13,3 +13,179 @@ 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.wbases.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import org.junit.*; +import gplx.langs.jsons.*; import gplx.xowa.xtns.wbases.core.*; +public class Wdata_doc_parser_v2__claims__tst { + @Before public void init() {fxt.Init();} private Wdata_doc_parser_v2_fxt fxt = new Wdata_doc_parser_v2_fxt(); + @Test public void Full__string() { + fxt.Test_claims(String_.Concat_lines_nl_skip_last + ( "{ 'claims':" + , " { 'P1':" + , " [" + , " { 'mainsnak':" + , " { 'snaktype':'value'" + , " , 'property':'P1'" + , " , 'hash':'84487fc3f93b4f74ab1cc5a47d78f596f0b49390'" + , " , 'datavalue':" + , " { 'value':'abc'" + , " , 'type':'string'" + , " }" + , " }" + , " , 'type':'statement'" + , " , 'id':'Q2$e8ba1188-4aec-9e37-a75e-f79466c1913e'" + , " , 'rank':'normal'" + , " }" + , " ]" + , " }" + , "}" + ) + , fxt.Make_claim_string(1, "abc") + ); + } + @Test public void Full__novalue() { + fxt.Test_claims(String_.Concat_lines_nl_skip_last + ( "{ 'claims':" + , " { 'P1':" + , " [" + , " { 'mainsnak':" + , " { 'snaktype':'novalue'" + , " , 'property':'P1'" + , " , 'hash':'84487fc3f93b4f74ab1cc5a47d78f596f0b49390'" + , " }" + , " }" + , " ]" + , " }" + , "}" + ) + , fxt.Make_claim_novalue(1) + ); + } + @Test public void Data__string() { + fxt.Test_claims_data(String_.Concat_lines_nl_skip_last + ( "{ 'value':'abc'" + , ", 'type':'string'" + , "}" + ) + , fxt.Make_claim_string(1, "abc") + ); + } + @Test public void Data__item() { + fxt.Test_claims_data(String_.Concat_lines_nl_skip_last + ( "{ 'value':" + , " { 'entity-type':'item'" + , " , 'numeric-id':'123'" + , " }" + , ", 'type':'wikibase-entityid'" + , "}" + ) + , fxt.Make_claim_entity_qid(1, 123) + ); + } + @Test public void Data__property() { + fxt.Test_claims_data(String_.Concat_lines_nl_skip_last + ( "{ 'value':" + , " { 'entity-type':'property'" + , " , 'numeric-id':'398'" + , " }" + , ", 'type':'wikibase-entityid'" + , "}" + ) + , fxt.Make_claim_entity_pid(1, 398) + ); + } + @Test public void Data__monolingualtext() { + fxt.Test_claims_data(String_.Concat_lines_nl_skip_last + ( "{ 'value':" + , " { 'text':'en_text'" + , " , 'language':'en'" + , " }" + , ", 'type':'monolingualtext'" + , "}" + ) + , fxt.Make_claim_monolingualtext(1, "en", "en_text") + ); + } + @Test public void Data__globecoordinate() { + fxt.Test_claims_data(String_.Concat_lines_nl_skip_last + ( "{ 'value':" + , " { 'latitude':1.2" + , " , 'longitude':3.4" + , " , 'altitude':null" + , " , 'precision':0.0002" + , " , 'globe':'http:\\/\\/www.wikidata.org\\/entity\\/Q2'" + , " }" + , ", 'type':'globecoordinate'" + , "}" + ) + , fxt.Make_claim_globecoordinate(1, "1.2", "3.4", "0.0002") + ); + } + @Test public void Data__quantity() { + fxt.Test_claims_data(String_.Concat_lines_nl_skip_last + ( "{ 'value':" + , " { 'amount':'123'" + , " , 'unit':'2'" + , " , 'upperBound':'125'" + , " , 'lowerBound':'121'" + , " }" + , ", 'type':'quantity'" + , "}" + ) + , fxt.Make_claim_quantity(1, 123, 2, 125, 121) + ); + } + @Test public void Data__time() { + fxt.Test_claims_data(String_.Concat_lines_nl_skip_last + ( "{ 'value':" + , " { 'time':'+00000002001-02-03T04:05:06Z'" + , " , 'timezone':0" + , " , 'before':0" + , " , 'after':0" + , " , 'precision':11" + , " , 'calendarmodel':'http:\\/\\/www.wikidata.org\\/entity\\/Q1985727'" + , " }" + , ", 'type':'time'" + , "}" + ) + , fxt.Make_claim_time(1, "2001-02-03 04:05:06") + ); + } + @Test public void Data__url() { // NOTE:has "String" property-type; EX:wd:Q23548; DATE:2016-07-28 + fxt.Test_claims_data(String_.Concat_lines_nl_skip_last + ( "{ 'value':'http:\\/\\/www.nasa.gov\\/rss\\/dyn\\/breaking_news.rss'" + , ", 'type':'string'" + , "}" + ) + , fxt.Make_claim_string(1, "http://www.nasa.gov/rss/dyn/breaking_news.rss") + ); + } + @Test public void Data__commonsMedia() { // NOTE:has "String" property-type; EX:wd:Q327162; DATE:2016-07-28 + fxt.Test_claims_data(String_.Concat_lines_nl_skip_last + ( "{ 'value':'Tabliczka E40.svg'" + , ", 'type':'string'" + , "}" + ) + , fxt.Make_claim_string(1, "Tabliczka E40.svg") + ); + } + @Test public void Data__externalid() { // NOTE:has "String" property-type; EX:wd:Q77177; DATE:2016-07-28 + fxt.Test_claims_data(String_.Concat_lines_nl_skip_last + ( "{ 'value':'000331371'" + , ", 'type':'string'" + , "}" + ) + , fxt.Make_claim_string(1, "000331371") + ); + } + @Test public void Data__math() { // NOTE:has "String" property-type; EX:wd:Q11518; DATE:2016-07-28 + fxt.Test_claims_data(String_.Concat_lines_nl_skip_last + ( "{ 'value':'a^2+b^2=c^2'" + , ", 'type':'string'" + , "}" + ) + , fxt.Make_claim_string(1, "a^2+b^2=c^2") + ); + } + // www.wikidata.org/wiki/Q11518 +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wbase_statement_mgr_.java b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wbase_statement_mgr_.java index a27517de8..d732c23f6 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wbase_statement_mgr_.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wbase_statement_mgr_.java @@ -13,3 +13,68 @@ 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.wbases.pfuncs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.core.envs.*; +import gplx.core.primitives.*; +import gplx.xowa.parsers.logs.*; import gplx.xowa.xtns.pfuncs.*; import gplx.xowa.xtns.wbases.core.*; import gplx.xowa.xtns.wbases.claims.*; +import gplx.xowa.langs.kwds.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*; +public class Wbase_statement_mgr_ { + public static void Get_wbase_data(Bry_bfr bfr, Xop_ctx ctx, Xot_invk caller, Xot_invk self, byte[] src, Pf_func_base pfunc, boolean mode_is_statements) { + // init + byte[] pid_ttl = pfunc.Eval_argx(ctx, src, caller, self); + Xop_log_property_wkr property_wkr = ctx.Xtn__wikidata__property_wkr(); + long log_time_bgn = 0; + if (property_wkr != null) { + log_time_bgn = System_.Ticks(); + if (!property_wkr.Eval_bgn(ctx.Page(), pid_ttl)) return; + } + Xoae_app app = ctx.App(); + Xowe_wiki wiki = ctx.Wiki(); + Xoa_ttl ttl = ctx.Page().Ttl(); + Wdata_wiki_mgr wdata_mgr = app.Wiki_mgr().Wdata_mgr(); if (!wdata_mgr.Enabled()) return; + + // get pid_int; EX: {{#property:p123}} -> 123 + int pid_int = Wbase_statement_mgr_.Parse_pid(app.Utl_num_parser(), pid_ttl); // parse "p123" to "123" + if (pid_int == Wdata_wiki_mgr.Pid_null) { // pid_ttl is name; EX: {{#property:road_map}} + pid_int = wdata_mgr.Pid_mgr.Get_or_null(wiki.Wdata_wiki_lang(), pid_ttl); + if (pid_int == Wdata_wiki_mgr.Pid_null) { + Print_self(app.Usr_dlg(), bfr, src, self, "prop_not_found", "prop id not found: ~{0} ~{1} ~{2}", wiki.Domain_str(), ttl.Page_db_as_str(), pid_ttl); + return; + } + } + + // get doc from args; EX:{{#property:p123}} -> "current_page"; EX:{{#property:p123|of=Earth}} -> "Q2"; {{#property:p123|q=q2}} -> "Q2"; {{#property:p123|from=p321}} -> "Property:P321" + Wdata_pf_property_data doc_data = Wdata_pf_property_data.Parse(ctx, src, caller, self); + Wdata_doc doc = Wbase_statement_mgr_.Get_doc(wdata_mgr, wiki, ttl, doc_data); + if (doc == null) return; // NOTE: some pages will not have a qid; EX: "Some_unknown_page" will not have a qid in wikidata; if no qid, then all {{#property:p###}} will have no prop_val + + // get val based on pid and doc; EX: {{#property:p123|of=Earth}} -> doc=Q2; pid=123 -> "value of p123 in Q2" + Wbase_claim_grp claim_grp = doc.Claim_list_get(pid_int); + if (claim_grp == null) return;// NOTE: some props may not exist; EX: "Some_known_page" has a qid of 123 but does not have pid 345 required by {{#property:P345|q=123}} + wdata_mgr.Resolve_to_bfr(bfr, claim_grp, wiki.Wdata_wiki_lang(), mode_is_statements); // NOTE: was ctx.Page().Lang().Key_bry(), but fails in simplewiki; DATE:2013-12-02 + if (property_wkr != null) property_wkr.Eval_end(ctx.Page(), pid_ttl, log_time_bgn); + } + public static int Parse_pid(Gfo_number_parser num_parser, byte[] bry) { + int bry_len = bry.length; + if (bry_len < 2) return Wdata_wiki_mgr.Pid_null; // must have at least 2 chars; p# + byte b_0 = bry[0]; + if (b_0 != Byte_ascii.Ltr_p && b_0 != Byte_ascii.Ltr_P) return Wdata_wiki_mgr.Pid_null; + num_parser.Parse(bry, 1, bry_len); + return num_parser.Has_err() ? Wdata_wiki_mgr.Pid_null : num_parser.Rv_as_int(); + } + private static void Print_self(Gfo_usr_dlg usr_dlg, Bry_bfr bfr, byte[] src, Xot_invk self, String warn_cls, String warn_fmt, Object... args) { + bfr.Add_mid(src, self.Src_bgn(), self.Src_end()); + usr_dlg.Log_many("", warn_cls, warn_fmt, args); + } + public static Wdata_doc Get_doc(Wdata_wiki_mgr wdata_mgr, Xowe_wiki wiki, Xoa_ttl ttl, Wdata_pf_property_data data) { + if (Bry_.Len_gt_0(data.Q)) return wdata_mgr.Doc_mgr.Get_by_bry_or_null(data.Q); + else if (Bry_.Len_gt_0(data.From)) return wdata_mgr.Doc_mgr.Get_by_xid_or_null(data.From); // NOTE: by_xid b/c Module passes just "p1" not "Property:P1" + else if (Bry_.Len_gt_0(data.Of)) { + Xoa_ttl of_ttl = Xoa_ttl.Parse(wiki, data.Of); if (of_ttl == null) return null; + byte[] qid = wdata_mgr.Qid_mgr.Get_or_null(wiki, of_ttl); if (qid == null) return null; // NOTE: for now, use wiki.Lang_key(), not page.Lang() + return wdata_mgr.Doc_mgr.Get_by_bry_or_null(qid); + } + else return wdata_mgr.Doc_mgr.Get_by_ttl_or_null(wiki, ttl); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_external_lang_links_data.java b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_external_lang_links_data.java index a27517de8..8bb11d221 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_external_lang_links_data.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_external_lang_links_data.java @@ -13,3 +13,40 @@ 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.wbases.pfuncs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.xtns.pfuncs.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*; +public class Wdata_external_lang_links_data { + private List_adp langs_list = List_adp_.New(); private Hash_adp_bry langs_hash = Hash_adp_bry.ci_a7(); // ASCII:lang_code + public boolean Enabled() {return enabled;} private boolean enabled; public Wdata_external_lang_links_data Enabled_(boolean v) {enabled = v; return this;} + public boolean Sort() {return sort;} public Wdata_external_lang_links_data Sort_(boolean v) {sort = v; return this;} private boolean sort; + public int Langs_len() {return langs_list.Count();} + public Wdata_external_lang_links_data Langs_add(byte[] v) {langs_hash.Add(v, v); langs_list.Add(v); return this;} + public byte[] Langs_get_at(int i) {return (byte[])langs_list.Get_at(i);} + public boolean Langs_hide(byte[] src, int bgn, int end) { + if (sort) return false; + return langs_hash.Get_by_mid(src, bgn, end) == null; + } + public void Reset() { + enabled = false; + sort = false; + langs_list.Clear(); + langs_hash.Clear(); + } + public void Parse(Xop_ctx ctx, byte[] src, Xot_invk caller, Xot_invk self, Pf_func_base pfunc) { + enabled = true; + byte[] argx = pfunc.Eval_argx(ctx, src, caller, self); + if (Bry_.Eq(argx, Key_sort)) {sort = true; return;} // {{noexternallanglinks:*}}; assume it cannot be combined with other langs_hash: EX: {{noexternallanglinks:*|en|fr}} + int args_len = self.Args_len(); + Langs_add(argx); + Bry_bfr tmp_bfr = ctx.Wiki().Utl__bfr_mkr().Get_b128(); + for (int i = 0; i < args_len; i++) { + Arg_nde_tkn nde = self.Args_get_by_idx(i); + nde.Val_tkn().Tmpl_evaluate(ctx, src, caller, tmp_bfr); // NOTE: changed from self to caller; DATE:2016-03-16 + byte[] lang = tmp_bfr.To_bry_and_clear(); + Langs_add(lang); + } + tmp_bfr.Mkr_rls(); + } + public static final byte[] Key_sort = new byte[] {Byte_ascii.Star}; +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_noExternalLangLinks.java b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_noExternalLangLinks.java index a27517de8..f0ff4cd1a 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_noExternalLangLinks.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_noExternalLangLinks.java @@ -13,3 +13,20 @@ 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.wbases.pfuncs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.langs.jsons.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.kwds.*; +import gplx.xowa.xtns.pfuncs.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*; +public class Wdata_pf_noExternalLangLinks extends Pf_func_base { + @Override public int Id() {return Xol_kwd_grp_.Id_noexternallanglinks;} + @Override public Pf_func New(int id, byte[] name) {return new Wdata_pf_noExternalLangLinks().Name_(name);} + @Override public void Func_evaluate(Bry_bfr bfr, Xop_ctx ctx, Xot_invk caller, Xot_invk self, byte[] src) { + ctx.Page().Wdata_external_lang_links().Parse(ctx, src, caller, self, this); + } + public static void Print_self(Gfo_usr_dlg usr_dlg, Bry_bfr bfr, byte[] src, Xot_invk self, String warn_cls, String warn_fmt, Object... args) { + bfr.Add_mid(src, self.Src_bgn(), self.Src_end()); + usr_dlg.Warn_many(GRP_KEY, warn_cls, warn_fmt, args); + } + static final String GRP_KEY = "xowa.xtns.wdata.noexternallanglinks"; +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_noExternalLangLinks_tst.java b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_noExternalLangLinks_tst.java index a27517de8..efef291ad 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_noExternalLangLinks_tst.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_noExternalLangLinks_tst.java @@ -13,3 +13,50 @@ 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.wbases.pfuncs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import org.junit.*; +public class Wdata_pf_noExternalLangLinks_tst { + @Before public void init() {fxt.Clear();} Wdata_pf_noExternalLangLinks_fxt fxt = new Wdata_pf_noExternalLangLinks_fxt(); + @Test public void Basic() { + fxt.Clear().Expd_enabled_(true).Test_parse("{{noexternallanglinks}}"); + fxt.Clear().Expd_enabled_(false).Expd_sort_(false).Expd_langs_().Test_parse(""); + } + @Test public void Selected() { + fxt.Clear().Expd_enabled_(true).Expd_langs_("en", "fr").Test_parse("{{noexternallanglinks:en|fr}}"); + } + @Test public void Sort() { + fxt.Clear().Expd_enabled_(true).Expd_sort_(true).Expd_langs_().Test_parse("{{noexternallanglinks:*}}"); + } +} +class Wdata_pf_noExternalLangLinks_fxt { + public Wdata_pf_noExternalLangLinks_fxt Clear() { + if (parser_fxt == null) { + parser_fxt = new Xop_fxt(); + app = parser_fxt.App(); + wiki = parser_fxt.Wiki(); + data = wiki.Parser_mgr().Ctx().Page().Wdata_external_lang_links(); + } + expd_sort = expd_enabled = Bool_.__byte; + expd_langs = null; + data.Reset(); + return this; + } private Xop_fxt parser_fxt; Xoae_app app; Xowe_wiki wiki; Wdata_external_lang_links_data data; + public Wdata_pf_noExternalLangLinks_fxt Expd_enabled_(boolean v) {expd_enabled = v ? Bool_.Y_byte : Bool_.N_byte; return this;} private byte expd_enabled; + public Wdata_pf_noExternalLangLinks_fxt Expd_sort_(boolean v) {expd_sort = v ? Bool_.Y_byte : Bool_.N_byte; return this;} private byte expd_sort; + public Wdata_pf_noExternalLangLinks_fxt Expd_langs_(String... v) {expd_langs = v; return this;} private String[] expd_langs; + public void Test_parse(String raw) { + byte[] expd = parser_fxt.Test_parse_tmpl_str_rv(raw); + Tfds.Eq(Bry_.Empty, expd); + if (expd_enabled != Bool_.__byte) Tfds.Eq(expd_enabled == Bool_.Y_byte, data.Enabled()); + if (expd_sort != Bool_.__byte) Tfds.Eq(expd_sort == Bool_.Y_byte, data.Sort()); + if (expd_langs != null) Tfds.Eq_ary_str(expd_langs, Data_langs_xto_str_ary()); + } + String[] Data_langs_xto_str_ary() { + int len = data.Langs_len(); + String[] rv = new String[len]; + for (int i = 0; i < len; i++) { + rv[i] = String_.new_u8((byte[])data.Langs_get_at(i)); + } + return rv; + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property.java b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property.java index a27517de8..9de24b797 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property.java @@ -13,3 +13,15 @@ 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.wbases.pfuncs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*; +import gplx.xowa.langs.kwds.*; import gplx.xowa.xtns.pfuncs.*; +public class Wdata_pf_property extends Pf_func_base { + @Override public int Id() {return Xol_kwd_grp_.Id_property;} + @Override public Pf_func New(int id, byte[] name) {return new Wdata_pf_property().Name_(name);} + @Override public void Func_evaluate(Bry_bfr bfr, Xop_ctx ctx, Xot_invk caller, Xot_invk self, byte[] src) {// EX: {{#property:p123|}} + synchronized (this) { // LOCK: must synchronized b/c bfr will later be set as member variable in .Resolve_to_bfr; DATE:2016-07-06 + Wbase_statement_mgr_.Get_wbase_data(bfr, ctx, caller, self, src, this, Bool_.N); + } + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property__basic__tst.java b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property__basic__tst.java index a27517de8..256d28765 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property__basic__tst.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property__basic__tst.java @@ -13,3 +13,129 @@ 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.wbases.pfuncs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import org.junit.*; import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*; +public class Wdata_pf_property__basic__tst { + @Before public void init() {fxt.Init();} private final Wdata_wiki_mgr_fxt fxt = new Wdata_wiki_mgr_fxt(); + @Test public void String() { + fxt.Init_links_add("enwiki", "Test_page", "q1"); + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_string(1, "a"))); + fxt.Test_parse("{{#property:p1}}", "a"); + fxt.Test_parse("{{#property:p2}}", ""); + } + @Test public void Entity() { + fxt.Init_links_add("enwiki", "Test_page", "q1"); + fxt.Init__docs__add(fxt.Wdoc_bldr("q2").Add_label("en", "b").Xto_wdoc()); + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_entity_qid(1, 2))); + fxt.Test_parse("{{#property:p1}}", "b"); + } + @Test public void Entity_fr() { // PURPOSE: non-English wiki should default to English label if non-English label not available; DATE:2013-12-19 + fxt.Wiki().Wdata_wiki_lang_(Bry_.new_a7("fr")); // set wiki to French + fxt.Init_links_add("frwiki", "Test_page", "q1"); // create link for en:Test_page in wikidata + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_entity_qid(1, 2))); // create wdata page Q1 with prop entity reference to Q2 + fxt.Init__docs__add(fxt.Wdoc_bldr("q2").Add_label("en", "b").Xto_wdoc()); // create wdata page Q2 with label in en (not fr) + fxt.Test_parse("{{#property:p1}}", "b"); // parse; should get en label + } + @Test public void Entity_missing() { // PURPOSE: wiki may refer to entity that no longer exists; EX: {{#property:p1}} which links to Q1, but p1 links to Q2 and Q2 was deleted; DATE:2014-02-01 + fxt.Init_links_add("enwiki", "Test_page", "q1"); // create link for en:Test_page in wikidata + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_entity_qid(1, 2))); // create wdata page Q1 with prop entity reference to Q2; note that Q2 is not created + fxt.Test_parse("{{#property:p1}}", ""); // parse; get "" + } + @Test public void Time() { + fxt.Init_links_add("enwiki", "Test_page", "q1"); + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_time(1, "2012-01-02 03:04:05"))); + fxt.Test_parse("{{#property:p1}}", "30405 2 Jan 2012"); // NOTE: format is missing ":" b/c test does not init messages for html_wtr; DATE:2015-08-03 + } + @Test public void Geodata() { + fxt.Init_links_add("enwiki", "Test_page", "q1"); + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_geo(1, "6.789", "1.2345"))); + fxt.Test_parse("{{#property:p1}}", "1°14'4.2"N, 6°47'20.4"E"); + } + @Test public void Quantity__plus_minus__y() { + fxt.Init_links_add("enwiki", "Test_page", "q1"); + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_quantity(1, "+1234", "1", "+1236", "+1232"))); + fxt.Test_parse("{{#property:p1}}", "1,234±2"); + } + @Test public void Quantity__plus_minus__n() { // PURPOSE:do not output ± if lbound == val == ubound; PAGE:en.w:Tintinan DATE:2015-08-02 + fxt.Init_links_add("enwiki", "Test_page", "q1"); + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_quantity(1, "+1234", "1", "+1234", "+1234"))); + fxt.Test_parse("{{#property:p1}}", "1,234"); + } + @Test public void Quantity__range() { // PURPOSE:do not output ± if lbound == val == ubound; PAGE:en.w:Tintinan DATE:2015-08-02 + fxt.Init_links_add("enwiki", "Test_page", "q1"); + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_quantity(1, "+1234", "1", "+1236", "+1233"))); + fxt.Test_parse("{{#property:p1}}", "1,233-1,236"); + } + @Test public void Quantity__long() { // PURPOSE: must cast to long for large numbers; EX:{{#property:P1082}} PAGE:en.w:Earth; DATE:2015-08-02 + fxt.Init_links_add("enwiki", "Test_page", "q1"); + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_quantity(1, "+4321000000", "1", "4321000000", "4321000000"))); + fxt.Test_parse("{{#property:p1}}", "4,321,000,000"); + } + @Test public void Quantity__unit__entity() {// PURPOSE: get entity name; EX:{{#invoke:Wikidata|getUnits|P2386|FETCH_WIKIDATA}} PAGE:en.w:Arecibo_Observatory; DATE:2016-10-11 + fxt.Init_links_add("enwiki", "Test_page", "q1"); + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_quantity(1, "+1234", "http://www.wikidata.org/entity/q2", "+1236", "+1232"))); + Wdata_doc wdoc = fxt.doc_("q2", fxt.Make_claim_string(2, "a")); + wdoc.Label_list().Add(Bry_.new_a7("en"), new gplx.xowa.xtns.wbases.core.Wdata_langtext_itm(Bry_.new_a7("en"), Bry_.new_a7("meter"))); + fxt.Init__docs__add(wdoc); + fxt.Test_parse("{{#property:p1}}", "1,234±2 meter"); + } + @Test public void Quantity__decimal() { + fxt.Init_links_add("enwiki", "Test_page", "q1"); + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_quantity(1, "+1234.50", "1", "+1236.75", "+1232.25"))); + fxt.Test_parse("{{#property:p1}}", "1,234.5±2.25"); + } + @Test public void Monolingualtext() { + fxt.Init_links_add("enwiki", "Test_page", "q1"); + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_monolingual(1, "la", "Lorem ipsum dolor sit amet"))); + fxt.Test_parse("{{#property:p1}}", "Lorem ipsum dolor sit amet"); + } + @Test public void Novalue() { + fxt.Init_links_add("enwiki", "Test_page", "q1"); + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_novalue(1))); + fxt.Test_parse("{{#property:p1}}", "novalue"); + } + @Test public void Somevalue() { + fxt.Init_links_add("enwiki", "Test_page", "q1"); + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_somevalue(1))); + fxt.Test_parse("{{#property:p1}}", "somevalue"); + } + @Test public void Multiple() { + fxt.Init_links_add("enwiki", "Test_page", "q1"); + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_string(1, "a"), fxt.Make_claim_string(1, "b"))); + fxt.Test_parse("{{#property:p1}}", "a"); // only take first; DATE:2015-08-02 + } + @Test public void Q() { + fxt.Init_links_add("enwiki", "Test_page", "q2"); + fxt.Init__docs__add(fxt.doc_("q2", fxt.Make_claim_string(1, "a"))); + fxt.Test_parse("{{#property:p1|q=q2}}", "a"); + } + @Test public void Of() { + fxt.Init_links_add("enwiki", "Of_page", "q2"); + fxt.Init__docs__add(fxt.doc_("q2", fxt.Make_claim_string(1, "a"))); + fxt.Test_parse("{{#property:p1|of=Of_page}}", "a"); + } + @Test public void From() { + fxt.Init__docs__add(fxt.doc_("Property:p2", fxt.Make_claim_string(1, "a"))); + fxt.Test_parse("{{#property:p1|from=p2}}", "a"); + fxt.Test_parse("{{#property:p1|from=}}", ""); + } + @Test public void Pid_as_name() { + fxt.Init_links_add("enwiki", "Test_page", "q2"); + fxt.Init_pids_add("en", "astronomic symbol", 1); + fxt.Init__docs__add(fxt.doc_("q2", fxt.Make_claim_string(1, "a"))); + fxt.Test_parse("{{#property:astronomic symbol}}", "a"); + } + @Test public void Empty_arg() { // PURPOSE: {{#property:p1|}} should not fail / warn; DATE:2013-11-15 + fxt.Init_links_add("enwiki", "Test_page", "q2"); + fxt.Init_pids_add("en", "astronomic symbol", 1); + fxt.Init__docs__add(fxt.doc_("q2", fxt.Make_claim_string(1, "a"))); + fxt.Test_parse("{{#property:p1|}}", "a"); + } + @Test public void Parse_pid() { + fxt.Test_parse_pid ("p123" , 123); // basic + fxt.Test_parse_pid ("P123" , 123); // uppercase + fxt.Test_parse_pid_null ("population"); // name test + fxt.Test_parse_pid_null ("123"); // missing p + fxt.Test_parse_pid_null (""); // empty String test + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property__parse__tst.java b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property__parse__tst.java index a27517de8..c3567dad2 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property__parse__tst.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property__parse__tst.java @@ -13,3 +13,40 @@ 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.wbases.pfuncs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import org.junit.*; import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*; +public class Wdata_pf_property__parse__tst { + @Before public void init() {fxt.Init();} private final Wdata_pf_property_data_fxt fxt = new Wdata_pf_property_data_fxt(); + @Test public void Q() {fxt.Init().Run__parse("{{#property:p1|q=q2}}").Chk_q("q2");} + @Test public void Of() {fxt.Init().Run__parse("{{#property:p1|of=Earth}}").Chk_of("Earth");} + @Test public void From() {fxt.Init().Run__parse("{{#property:p1|from=p2}}").Chk_from("p2");} +} +class Wdata_pf_property_data_fxt { + private Xoae_app app; private Wdata_wiki_mgr wdata_mgr; private Xop_fxt parser_fxt; + public Wdata_pf_property_data_fxt Init() { + if (app == null) { + parser_fxt = new Xop_fxt(); + app = parser_fxt.App(); + wdata_mgr = app.Wiki_mgr().Wdata_mgr(); + } + Io_mgr.Instance.InitEngine_mem(); + wdata_mgr.Clear(); + parser_fxt.Reset(); + actl = null; + return this; + } + private Wdata_pf_property_data actl; + public Wdata_pf_property_data_fxt Run__parse(String raw) { + byte[] raw_bry = Bry_.new_u8(raw); + Xowe_wiki wiki = parser_fxt.Wiki(); Xop_ctx ctx = wiki.Parser_mgr().Ctx(); + Xop_tkn_mkr tkn_mkr = app.Parser_mgr().Tkn_mkr(); + Xop_root_tkn root = tkn_mkr.Root(raw_bry); + wiki.Parser_mgr().Main().Expand_tmpl(root, ctx, tkn_mkr, raw_bry); + Xot_invk tkn = (Xot_invk)root.Subs_get(0); + this.actl = Wdata_pf_property_data.Parse(ctx, raw_bry, Xot_invk_mock.Null, tkn); + return this; + } + public Wdata_pf_property_data_fxt Chk_q(String v) {Tfds.Eq_bry(v, actl.Q); return this;} + public Wdata_pf_property_data_fxt Chk_of(String v) {Tfds.Eq_bry(v, actl.Of); return this;} + public Wdata_pf_property_data_fxt Chk_from(String v) {Tfds.Eq_bry(v, actl.From); return this;} +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property_data.java b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property_data.java index a27517de8..749e48cc8 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property_data.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property_data.java @@ -13,3 +13,55 @@ 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.wbases.pfuncs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.core.primitives.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*; +public class Wdata_pf_property_data { + public Wdata_pf_property_data(byte[] of, byte[] q, byte[] from) { + this.Of = of; this.Q = q; this.From = from; + } + public final byte[] Of; // EX: "earth" + public final byte[] Q; // EX: "Q123" + public final byte[] From; // EX: "p2" + + public static Wdata_pf_property_data Parse(Xop_ctx ctx, byte[] src, Xot_invk caller, Xot_invk self) { + Bry_bfr tmp_bfr = ctx.Wiki().Utl__bfr_mkr().Get_b512(); + byte[] of = null, q = null, from = null; + int args_len = self.Args_len(); + for (int i = 0; i < args_len; ++i) { + Arg_nde_tkn nde = self.Args_get_by_idx(i); + + // get key; EX: "of=", "q=", "from=" + Arg_itm_tkn nde_key = nde.Key_tkn(); + int key_bgn = nde_key.Src_bgn(), key_end = nde_key.Src_end(); + if (key_bgn == key_end && key_bgn == -1) continue; // null arg; ignore, else will throw warning below; EX: {{#property:p1|}}; DATE:2013-11-15 + byte key_tid = atrs_hash.Get_as_byte_or(src, key_bgn, key_end, Byte_.Max_value_127); + switch (key_tid) { + case Byte_.Max_value_127: + ctx.App().Usr_dlg().Warn_many("", "", "unknown key for property: ~{0} ~{1}", String_.new_u8(ctx.Page().Ttl().Full_txt_w_ttl_case()), String_.new_u8(src, self.Src_bgn(), self.Src_end())); + continue; + case Tid__id: // same as "not-found", but don't warn; + continue; + } + + // get val + nde.Val_tkn().Tmpl_evaluate(ctx, src, caller, tmp_bfr); // NOTE: changed from self to caller; DATE:2016-03-16 + byte[] val = tmp_bfr.To_bry_and_clear(); + switch (key_tid) { + case Tid__of: of = val; break; + case Tid__q: q = val; break; + case Tid__from: from = val; break; + default: throw Err_.new_unhandled(key_tid); + } + } + tmp_bfr.Mkr_rls(); + return new Wdata_pf_property_data(of, q, from); + } + private static final byte Tid__of = 0, Tid__q = 1, Tid__from = 2, Tid__id = 3; + private static final Hash_adp_bry atrs_hash = Hash_adp_bry.ci_a7() + .Add_str_byte("of" , Tid__of) + .Add_str_byte("q" , Tid__q) + .Add_str_byte("from" , Tid__from) // "from" is alias as "q" except it seems to handle properties; EX: {{#property:p1|from=Q2}} == {{#property:p1|q=Q2}}; EX: {{#property:p1|from=p2}} + .Add_str_byte("id" , Tid__id) // "id" has no effect, but appears in articles; ignore and don't warn; EX:{{#property:P277|id=Q1322933}} PAGE:en.w:Symfony; DATE:2016-08-13 + ; +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property_fmt.java b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property_fmt.java index a27517de8..e1ef8a0f5 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property_fmt.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_property_fmt.java @@ -13,3 +13,37 @@ 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.wbases.pfuncs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +public class Wdata_pf_property_fmt { + public byte[] Separator() {return separator;} private byte[] separator; + public byte[] Value_template() {return value_template;} private byte[] value_template; + public byte[] Qualifier_template() {return qualifier_template;} private byte[] qualifier_template; + public byte[] Qualifier_separator() {return qualifier_separator;} private byte[] qualifier_separator; + public byte[] Qualifiers_wrapper() {return qualifiers_wrapper;} private byte[] qualifiers_wrapper; + public byte[] Qualifiers_template() {return qualifiers_template;} private byte[] qualifiers_template; + public byte[] Qualifiers_value_separator() {return qualifiers_value_separator;} private byte[] qualifiers_value_separator; + public byte[] Reference_keyvalue_template() {return reference_keyvalue_template;} private byte[] reference_keyvalue_template; + public byte[] Reference_wrapper() {return reference_wrapper;} private byte[] reference_wrapper; + public byte[] References_wrapper() {return references_wrapper;} private byte[] references_wrapper; + public byte[] Reference_keyvalue_separator() {return reference_keyvalue_separator;} private byte[] reference_keyvalue_separator; + public byte[] Reference_template() {return reference_template;} private byte[] reference_template; + public byte[] Reference_value_separator() {return reference_value_separator;} private byte[] reference_value_separator; + public byte[] Reference_separator() {return reference_separator;} private byte[] reference_separator; + public void Init() { + byte[] comma = new byte[] {Byte_ascii.Comma}; + separator = comma; + value_template = Bry_.new_a7("{{{value}}} {{{qualifiers}}}{{{references}}}"); + qualifier_template = Bry_.new_a7("{{{1}}} {{{2}}}"); + qualifier_separator = comma; + qualifiers_wrapper = Bry_.new_a7("{{{1}}}"); + qualifiers_template = Bry_.Empty; + qualifiers_value_separator = comma; + reference_keyvalue_template = Bry_.new_a7("{{{1}}} {{{2}}}"); + reference_wrapper = Bry_.new_a7("{{{1}}}"); + references_wrapper = Bry_.new_a7("{{{1}}}"); + reference_keyvalue_separator = comma; + reference_template = Bry_.Empty; + reference_value_separator = comma; + reference_separator = Bry_.Empty; // "a separator to use between each reference" + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_statements.java b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_statements.java index a27517de8..a0b681118 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_statements.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_statements.java @@ -13,3 +13,15 @@ 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.wbases.pfuncs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*; +import gplx.xowa.langs.kwds.*; import gplx.xowa.xtns.pfuncs.*; +public class Wdata_pf_statements extends Pf_func_base { + @Override public int Id() {return Xol_kwd_grp_.Id_statements;} + @Override public Pf_func New(int id, byte[] name) {return new Wdata_pf_statements().Name_(name);} + @Override public void Func_evaluate(Bry_bfr bfr, Xop_ctx ctx, Xot_invk caller, Xot_invk self, byte[] src) {// EX: {{#statements:p123|}} + synchronized (this) { // LOCK: must synchronized b/c bfr will later be set as member variable in .Resolve_to_bfr; DATE:2016-07-06 + Wbase_statement_mgr_.Get_wbase_data(bfr, ctx, caller, self, src, this, Bool_.Y); + } + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_statements__basic__tst.java b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_statements__basic__tst.java index a27517de8..0b7ceb6d4 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_statements__basic__tst.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_statements__basic__tst.java @@ -13,3 +13,19 @@ 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.wbases.pfuncs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import org.junit.*; import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*; +public class Wdata_pf_statements__basic__tst { + @Before public void init() {fxt.Init();} private final Wdata_wiki_mgr_fxt fxt = new Wdata_wiki_mgr_fxt(); + @Test public void String() { + fxt.Init_links_add("enwiki", "Test_page", "q1"); + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_string(1, "a"))); + fxt.Test_parse("{{#statements:p1}}", "a"); + } + @Test public void Entity() { + fxt.Init_links_add("enwiki", "Test_page", "q1"); + fxt.Init__docs__add(fxt.Wdoc_bldr("q2").Add_label("en", "b").Xto_wdoc()); + fxt.Init__docs__add(fxt.doc_("q1", fxt.Make_claim_entity_qid(1, 2))); + fxt.Test_parse("{{#statements:p1}}", "[[b]]"); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_wbreponame.java b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_wbreponame.java index a27517de8..639f75be0 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_wbreponame.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_wbreponame.java @@ -13,3 +13,15 @@ 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.wbases.pfuncs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.langs.jsons.*; +import gplx.xowa.langs.*; import gplx.xowa.langs.kwds.*; +import gplx.xowa.xtns.pfuncs.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*; +public class Wdata_pf_wbreponame extends Pf_func_base { + @Override public int Id() {return Xol_kwd_grp_.Id_wbreponame;} + @Override public Pf_func New(int id, byte[] name) {return new Wdata_pf_wbreponame().Name_(name);} + @Override public void Func_evaluate(Bry_bfr bfr, Xop_ctx ctx, Xot_invk caller, Xot_invk self, byte[] src) { + bfr.Add(Reponame); // NOTE: MW has logic to look for message named "wbreponame", and returning it if it exists; only applies to non-WMF Wikidatas; DATE:2014-09-07 + } private static final byte[] Reponame = Bry_.new_a7("Wikidata"); +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_wbreponame_tst.java b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_wbreponame_tst.java index a27517de8..5fb6c30f4 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_wbreponame_tst.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/pfuncs/Wdata_pf_wbreponame_tst.java @@ -13,3 +13,22 @@ 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.wbases.pfuncs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import org.junit.*; +public class Wdata_pf_wbreponame_tst { + @Before public void init() {fxt.Clear();} private Wdata_pf_wbreponame_fxt fxt = new Wdata_pf_wbreponame_fxt(); + @Test public void Basic() { + fxt.Test_parse("{{wbreponame}}", "Wikidata"); + } +} +class Wdata_pf_wbreponame_fxt { + public Wdata_pf_wbreponame_fxt Clear() { + if (parser_fxt == null) { + parser_fxt = new Xop_fxt(); + } + return this; + } private Xop_fxt parser_fxt; + public void Test_parse(String raw, String expd) { + parser_fxt.Test_html_full_str(raw, expd); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/specials/Wdata_itemByTitle_cfg.java b/400_xowa/src/gplx/xowa/xtns/wbases/specials/Wdata_itemByTitle_cfg.java index a27517de8..e3b92ae9c 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/specials/Wdata_itemByTitle_cfg.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/specials/Wdata_itemByTitle_cfg.java @@ -13,3 +13,17 @@ 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.wbases.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +public class Wdata_itemByTitle_cfg implements Gfo_invk { + public byte[] Site_default() {return site_default;} private byte[] site_default = Bry_.new_a7("enwiki"); + public void Init_by_app(Xoae_app app) { + app.Cfg().Bind_many_app(this, Cfg__site_default); + } + public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) { + if (ctx.Match(k, Cfg__site_default)) site_default = m.ReadBry("v"); + else return Gfo_invk_.Rv_unhandled; + return this; + } + public static final String Key = "itemByTitle"; + private static final String Cfg__site_default = "xowa.addon.wikibase.item_by_title.site_default"; +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/specials/Wdata_itemByTitle_page.java b/400_xowa/src/gplx/xowa/xtns/wbases/specials/Wdata_itemByTitle_page.java index a27517de8..496489f4b 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/specials/Wdata_itemByTitle_page.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/specials/Wdata_itemByTitle_page.java @@ -13,3 +13,75 @@ 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.wbases.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import gplx.core.net.*; import gplx.core.net.qargs.*; import gplx.core.brys.fmtrs.*; +import gplx.xowa.wikis.domains.*; import gplx.xowa.specials.*; +import gplx.xowa.apps.urls.*; +public class Wdata_itemByTitle_page implements Xow_special_page { + private Gfo_qarg_mgr_old arg_hash = new Gfo_qarg_mgr_old(); + private static final byte[] Arg_site = Bry_.new_a7("site"), Arg_page = Bry_.new_a7("page"); + public Bry_fmtr Html_fmtr() {return html_fmtr;} + private Wdata_itemByTitle_cfg cfg; + public Xow_special_meta Special__meta() {return Xow_special_meta_.Itm__item_by_title;} + public void Special__gen(Xow_wiki wikii, Xoa_page pagei, Xoa_url url, Xoa_ttl ttl) { + Xowe_wiki wiki = (Xowe_wiki)wikii; Xoae_page page = (Xoae_page)pagei; + if (cfg == null) cfg = (Wdata_itemByTitle_cfg)wiki.Appe().Special_mgr().Get_or_null(Wdata_itemByTitle_cfg.Key); + // Special:ItemByTitle/enwiki/Earth -> www.wikidata.org/wiki/Q2 + Gfo_usr_dlg usr_dlg = wiki.Appe().Usr_dlg(); + byte[] site_bry = cfg.Site_default(); + byte[] page_bry = Bry_.Empty; + byte[] raw_bry = ttl.Full_txt_wo_qarg(); // EX: enwiki/Earth + int args_len = url.Qargs_ary().length; + if (args_len > 0) { + arg_hash.Load(url.Qargs_ary()); + site_bry = arg_hash.Get_val_bry_or(Arg_site, Bry_.Empty); + page_bry = arg_hash.Get_val_bry_or(Arg_page, Bry_.Empty); + } + int site_bgn = Bry_find_.Find_fwd(raw_bry, Xoa_ttl.Subpage_spr); + if (site_bgn != Bry_find_.Not_found) { // leaf arg is available + int page_bgn = Bry_find_.Find_fwd(raw_bry, Xoa_ttl.Subpage_spr, site_bgn + 1); + int raw_bry_len = raw_bry.length; + if (page_bgn != Bry_find_.Not_found && page_bgn < raw_bry_len) { // pipe is found and not last char (EX: "enwiki/" is invalid + site_bry = Bry_.Mid(raw_bry, site_bgn + 1, page_bgn); + page_bry = Bry_.Mid(raw_bry, page_bgn + 1, raw_bry_len); + } + } + Xoae_app app = wiki.Appe(); + if (Bry_.Len_gt_0(site_bry) && Bry_.Len_gt_0(page_bry)) + if (Navigate(usr_dlg, app, app.Wiki_mgr().Wdata_mgr(), page, site_bry, page_bry)) return; + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_k004(); + html_fmtr.Bld_bfr_many(tmp_bfr, "Search for items by site and title", "Site", site_bry, "Page", page_bry, "Search"); + page.Db().Text().Text_bry_(tmp_bfr.To_bry_and_rls()); + page.Html_data().Html_restricted_n_(); // [[Special:]] pages allow all HTML + } + private static boolean Navigate(Gfo_usr_dlg usr_dlg, Xoae_app app, Wdata_wiki_mgr wdata_mgr, Xoae_page page, byte[] site_bry, byte[] page_bry) { + page_bry = gplx.langs.htmls.encoders.Gfo_url_encoder_.Http_url.Decode(page_bry); // NOTE: space is converted to + on postback to url; decode + byte[] wiki_domain = Xow_abrv_wm_.Parse_to_domain_bry(site_bry); if (wiki_domain == null) {usr_dlg.Warn_many("", "", "site_bry parse failed; site_bry:~{0}", String_.new_u8(site_bry)); return false;} + Xowe_wiki wiki = app.Wiki_mgr().Get_by_or_make(wiki_domain); if (wiki == null) {usr_dlg.Warn_many("", "", "wiki_domain does not exist; wiki_domain:~{0}", String_.new_u8(wiki_domain)); return false;} + Xoa_ttl wdata_ttl = Xoa_ttl.Parse(wiki, page_bry); if (wdata_ttl == null) {usr_dlg.Warn_many("", "", "ttl is invalid; ttl:~{0}", String_.new_u8(page_bry)); return false;} + Wdata_doc doc = wdata_mgr.Doc_mgr.Get_by_ttl_or_null(wiki, wdata_ttl); if (doc == null) {usr_dlg.Warn_many("", "", "ttl cannot be found in wikidata; ttl:~{0}", String_.new_u8(wdata_ttl.Raw())); return false;} + byte[] qid_bry = doc.Qid(); + wdata_mgr.Wdata_wiki().Data_mgr().Redirect(page, qid_bry); if (page.Db().Page().Exists_n()) {usr_dlg.Warn_many("", "", "qid cannot be found in wikidata; qid:~{0}", String_.new_u8(qid_bry)); return false;} + return true; + } + private static Bry_fmtr html_fmtr = Bry_fmtr.new_(String_.Concat_lines_nl + ( "
    " + , "
    " + , "
    " + , "~{legend}" + , "" + , "" + , "" + , "" + , "" + , "" + , "" + , "
    " + , "
    " + , "
    " + , "
    To change the default site, see Options/Wikibase" // HOME + ) + , "legend", "site_lbl", "site_val", "page_lbl", "page_val", "search_lbl"); + + public Xow_special_page Special__clone() {return this;} +} diff --git a/400_xowa/src/gplx/xowa/xtns/wbases/specials/Wdata_itemByTitle_page_tst.java b/400_xowa/src/gplx/xowa/xtns/wbases/specials/Wdata_itemByTitle_page_tst.java index a27517de8..10675bc42 100644 --- a/400_xowa/src/gplx/xowa/xtns/wbases/specials/Wdata_itemByTitle_page_tst.java +++ b/400_xowa/src/gplx/xowa/xtns/wbases/specials/Wdata_itemByTitle_page_tst.java @@ -13,3 +13,62 @@ 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.wbases.specials; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.wbases.*; +import org.junit.*; import gplx.xowa.wikis.domains.*; +public class Wdata_itemByTitle_page_tst { + @Before public void init() {fxt.Clear();} private Wdata_itemByTitle_page_fxt fxt = new Wdata_itemByTitle_page_fxt(); + @Test public void Url() { + fxt.Init_wdata_page("Q2", "q2_earth"); + fxt.Init_wdata_label("enwiki", "Earth", "Q2"); + fxt.Test_open("Special:ItemByTitle/enwiki/Earth", "q2_earth"); + } + @Test public void Missing() { + fxt.Test_open("Special:ItemByTitle/enwiki/unknown_page", fxt.Expd_html("enwiki", "unknown page")); + } + @Test public void Html() { + fxt.Test_open("Special:ItemByTitle", fxt.Expd_html("enwiki", "")); + } + @Test public void Args() { + fxt.Init_wdata_page("Q2", "q2_earth"); + fxt.Init_wdata_label("enwiki", "Earth", "Q2"); + fxt.Test_open("Special:ItemByTitle?site=enwiki&page=Earth", "q2_earth"); + } + @Test public void Url_decode() { // PURPOSE: spaces not handled; EX: Albert Einstein -> Albert+Einstein; DATE:2013-07-24 + fxt.Init_wdata_page("Q2", "q2_page"); + fxt.Init_wdata_label("enwiki", "A_B", "Q2"); + fxt.Test_open("Special:ItemByTitle?site=enwiki&page=A+B", "q2_page"); + } +} +class Wdata_itemByTitle_page_fxt { + public void Clear() { + parser_fxt = new Xop_fxt(); + parser_fxt.Reset(); + app = parser_fxt.App(); + wiki = parser_fxt.Wiki(); + special_page = wiki.Special_mgr().Page_itemByTitle(); + wdata_fxt = new Wdata_wiki_mgr_fxt().Init(parser_fxt, true); + } private Xop_fxt parser_fxt; private Xoae_app app; private Wdata_itemByTitle_page special_page; private Xowe_wiki wiki; + Wdata_wiki_mgr_fxt wdata_fxt; + public void Init_wdata_page(String qid_ttl, String text) { + Wdata_doc doc = wdata_fxt.Wdoc_bldr(qid_ttl).Xto_wdoc(); + app.Wiki_mgr().Wdata_mgr().Doc_mgr.Add(Bry_.new_a7(qid_ttl), doc); + parser_fxt.Init_page_create(app.Wiki_mgr().Wdata_mgr().Wdata_wiki(), qid_ttl, text); + } + public void Init_wdata_label(String wmf_key_str, String wdata_label, String qid) { + wdata_fxt.Init_qids_add("en", Xow_domain_tid_.Tid__wikipedia, wdata_label, qid); + } + public void Test_open(String link, String expd) { + Xoae_page page = wiki.Parser_mgr().Ctx().Page(); + Xoa_url url = app.User().Wikii().Utl__url_parser().Parse(Bry_.new_u8(link)); + page.Url_(url); + Xoa_ttl ttl = Xoa_ttl.Parse(wiki, Bry_.new_a7(link)); + page.Ttl_(ttl); + special_page.Special__gen(wiki, page, url, ttl); + Tfds.Eq_str_lines(expd, String_.new_a7(page.Db().Text().Text_bry())); + } + public String Expd_html(String wmf_key, String ttl_str) { + Bry_bfr tmp_bfr = wiki.Utl__bfr_mkr().Get_k004(); + special_page.Html_fmtr().Bld_bfr_many(tmp_bfr, "Search for items by site and title", "Site", wmf_key, "Page", ttl_str, "Search"); + return tmp_bfr.To_str_and_rls(); + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/wikias/Random_selection_xnde.java b/400_xowa/src/gplx/xowa/xtns/wikias/Random_selection_xnde.java index a27517de8..dfe603e18 100644 --- a/400_xowa/src/gplx/xowa/xtns/wikias/Random_selection_xnde.java +++ b/400_xowa/src/gplx/xowa/xtns/wikias/Random_selection_xnde.java @@ -13,3 +13,97 @@ 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.wikias; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; +import gplx.xowa.htmls.*; import gplx.xowa.htmls.core.htmls.*; +import gplx.xowa.parsers.*; import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.htmls.*; import gplx.xowa.parsers.tmpls.*; +import gplx.core.btries.*; +import gplx.langs.htmls.*; import gplx.langs.htmls.docs.*; +public class Random_selection_xnde implements Xox_xnde, Mwh_atr_itm_owner2 { + private byte[] val = Bry_.Empty; + private byte[] choicetemplate = null, atr_before = null, atr_after = null; + private Rndsel_option_itm[] options_ary; + private int weight_total = 0; + public void Xatr__set(Xowe_wiki wiki, byte[] src, Mwh_atr_itm xatr, byte xatr_id) { + switch (xatr_id) { + case Xatr__before: atr_before = xatr.Val_as_bry(); break; + case Xatr__after: atr_after = xatr.Val_as_bry(); break; + } + } + public void Xtn_parse(Xowe_wiki wiki, Xop_ctx ctx, Xop_root_tkn root, byte[] src, Xop_xnde_tkn xnde) { + ctx.Para().Process_block__xnde(xnde.Tag(), Xop_xnde_tag.Block_bgn); + + Xox_xnde_.Parse_xatrs(wiki, this, xatrs_hash, src, xnde); + + // parse + List_adp option_list = List_adp_.New(); + Gfh_tag_rdr tag_rdr = Xox_xnde_.New_tag_rdr(ctx, src, xnde).Reg("option", Tag__option).Reg("choicetemplate", Tag__choicetemplate); + while (true) { + Gfh_tag head_tag = tag_rdr.Tag__move_fwd_head(Gfh_tag_.Id__any); + int tag_tid = head_tag.Name_id(); + boolean eos = false; + switch (tag_tid) { + case Gfh_tag_.Id__eos: eos = true; break; + default: continue; + case Tag__option: + case Tag__choicetemplate: break; + } + if (eos) break; + + int head_tag_end = head_tag.Src_end(); + Gfh_tag tail_tag = tag_rdr.Tag__move_fwd_tail(tag_tid); + int tail_tag_bgn = tail_tag.Src_bgn(); + switch (tag_tid) { + case Tag__choicetemplate: { + if (choicetemplate == null) { // only one should be specified; if many, always take 1st + choicetemplate = Bry_.Mid(src, head_tag_end, tail_tag_bgn); + } + break; + } + case Tag__option: { + int weight = tail_tag.Atrs__get_as_int_or(Atr__weight, 1); + option_list.Add(new Rndsel_option_itm(weight, Bry_.Mid(src, head_tag_end, tail_tag_bgn))); + weight_total += weight; + break; + } + } + } + if (weight_total == 0) return; // empty will be 0 + this.options_ary = (Rndsel_option_itm[])option_list.To_ary_and_clear(Rndsel_option_itm.class); + + // randomly select option_bry from all